summaryrefslogtreecommitdiffstats
path: root/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'src/test')
-rw-r--r--src/test/Makefile52
-rw-r--r--src/test/README50
-rw-r--r--src/test/authentication/.gitignore2
-rw-r--r--src/test/authentication/Makefile23
-rw-r--r--src/test/authentication/README26
-rw-r--r--src/test/authentication/t/001_password.pl162
-rw-r--r--src/test/authentication/t/002_saslprep.pl119
-rw-r--r--src/test/examples/.gitignore6
-rw-r--r--src/test/examples/Makefile22
-rw-r--r--src/test/examples/testlibpq.c131
-rw-r--r--src/test/examples/testlibpq2.c152
-rw-r--r--src/test/examples/testlibpq2.sql6
-rw-r--r--src/test/examples/testlibpq3.c231
-rw-r--r--src/test/examples/testlibpq3.sql6
-rw-r--r--src/test/examples/testlibpq4.c163
-rw-r--r--src/test/examples/testlo.c270
-rw-r--r--src/test/examples/testlo64.c301
-rw-r--r--src/test/isolation/.gitignore12
-rw-r--r--src/test/isolation/Makefile73
-rw-r--r--src/test/isolation/README214
-rw-r--r--src/test/isolation/expected/aborted-keyrevoke.out272
-rw-r--r--src/test/isolation/expected/alter-table-1.out3326
-rw-r--r--src/test/isolation/expected/alter-table-2.out1030
-rw-r--r--src/test/isolation/expected/alter-table-3.out785
-rw-r--r--src/test/isolation/expected/alter-table-4.out71
-rw-r--r--src/test/isolation/expected/async-notify.out127
-rw-r--r--src/test/isolation/expected/classroom-scheduling.out379
-rw-r--r--src/test/isolation/expected/create-trigger.out361
-rw-r--r--src/test/isolation/expected/deadlock-hard.out36
-rw-r--r--src/test/isolation/expected/deadlock-parallel.out68
-rw-r--r--src/test/isolation/expected/deadlock-simple.out11
-rw-r--r--src/test/isolation/expected/deadlock-soft-2.out17
-rw-r--r--src/test/isolation/expected/deadlock-soft.out17
-rw-r--r--src/test/isolation/expected/delete-abort-savept-2.out100
-rw-r--r--src/test/isolation/expected/delete-abort-savept.out139
-rw-r--r--src/test/isolation/expected/detach-partition-concurrently-1.out288
-rw-r--r--src/test/isolation/expected/detach-partition-concurrently-2.out76
-rw-r--r--src/test/isolation/expected/detach-partition-concurrently-3.out506
-rw-r--r--src/test/isolation/expected/detach-partition-concurrently-4.out426
-rw-r--r--src/test/isolation/expected/drop-index-concurrently-1.out56
-rw-r--r--src/test/isolation/expected/drop-index-concurrently-1_2.out54
-rw-r--r--src/test/isolation/expected/eval-plan-qual-trigger.out2734
-rw-r--r--src/test/isolation/expected/eval-plan-qual.out1179
-rw-r--r--src/test/isolation/expected/fk-contention.out16
-rw-r--r--src/test/isolation/expected/fk-deadlock.out119
-rw-r--r--src/test/isolation/expected/fk-deadlock2.out95
-rw-r--r--src/test/isolation/expected/fk-deadlock2_1.out105
-rw-r--r--src/test/isolation/expected/fk-deadlock2_2.out105
-rw-r--r--src/test/isolation/expected/fk-deadlock_1.out131
-rw-r--r--src/test/isolation/expected/fk-partitioned-1.out133
-rw-r--r--src/test/isolation/expected/fk-partitioned-2.out76
-rw-r--r--src/test/isolation/expected/freeze-the-dead.out44
-rw-r--r--src/test/isolation/expected/horizons.out335
-rw-r--r--src/test/isolation/expected/index-only-scan.out41
-rw-r--r--src/test/isolation/expected/inherit-temp.out277
-rw-r--r--src/test/isolation/expected/insert-conflict-do-nothing-2.out121
-rw-r--r--src/test/isolation/expected/insert-conflict-do-nothing.out27
-rw-r--r--src/test/isolation/expected/insert-conflict-do-update-2.out27
-rw-r--r--src/test/isolation/expected/insert-conflict-do-update-3.out30
-rw-r--r--src/test/isolation/expected/insert-conflict-do-update.out27
-rw-r--r--src/test/isolation/expected/insert-conflict-specconflict.out553
-rw-r--r--src/test/isolation/expected/lock-committed-keyupdate.out670
-rw-r--r--src/test/isolation/expected/lock-committed-update.out931
-rw-r--r--src/test/isolation/expected/lock-update-delete.out285
-rw-r--r--src/test/isolation/expected/lock-update-delete_1.out273
-rw-r--r--src/test/isolation/expected/lock-update-traversal.out63
-rw-r--r--src/test/isolation/expected/multiple-cic.out24
-rw-r--r--src/test/isolation/expected/multiple-row-versions.out30
-rw-r--r--src/test/isolation/expected/multixact-no-deadlock.out32
-rw-r--r--src/test/isolation/expected/multixact-no-forget.out168
-rw-r--r--src/test/isolation/expected/multixact-no-forget_1.out160
-rw-r--r--src/test/isolation/expected/nowait-2.out55
-rw-r--r--src/test/isolation/expected/nowait-3.out21
-rw-r--r--src/test/isolation/expected/nowait-4.out23
-rw-r--r--src/test/isolation/expected/nowait-4_1.out23
-rw-r--r--src/test/isolation/expected/nowait-5.out43
-rw-r--r--src/test/isolation/expected/nowait.out81
-rw-r--r--src/test/isolation/expected/partial-index.out715
-rw-r--r--src/test/isolation/expected/partition-concurrent-attach.out55
-rw-r--r--src/test/isolation/expected/partition-drop-index-locking.out100
-rw-r--r--src/test/isolation/expected/partition-key-update-1.out129
-rw-r--r--src/test/isolation/expected/partition-key-update-2.out33
-rw-r--r--src/test/isolation/expected/partition-key-update-3.out155
-rw-r--r--src/test/isolation/expected/partition-key-update-4.out72
-rw-r--r--src/test/isolation/expected/plpgsql-toast.out316
-rw-r--r--src/test/isolation/expected/predicate-gin.out581
-rw-r--r--src/test/isolation/expected/predicate-gist.out819
-rw-r--r--src/test/isolation/expected/predicate-hash.out819
-rw-r--r--src/test/isolation/expected/predicate-lock-hot-tuple.out24
-rw-r--r--src/test/isolation/expected/prepared-transactions-cic.out20
-rw-r--r--src/test/isolation/expected/prepared-transactions.out48205
-rw-r--r--src/test/isolation/expected/project-manager.out379
-rw-r--r--src/test/isolation/expected/propagate-lock-delete.out105
-rw-r--r--src/test/isolation/expected/read-only-anomaly-2.out58
-rw-r--r--src/test/isolation/expected/read-only-anomaly-3.out34
-rw-r--r--src/test/isolation/expected/read-only-anomaly.out33
-rw-r--r--src/test/isolation/expected/read-write-unique-2.out37
-rw-r--r--src/test/isolation/expected/read-write-unique-3.out14
-rw-r--r--src/test/isolation/expected/read-write-unique-4.out49
-rw-r--r--src/test/isolation/expected/read-write-unique.out37
-rw-r--r--src/test/isolation/expected/receipt-report.out4215
-rw-r--r--src/test/isolation/expected/referential-integrity.out839
-rw-r--r--src/test/isolation/expected/reindex-concurrently-toast.out775
-rw-r--r--src/test/isolation/expected/reindex-concurrently.out90
-rw-r--r--src/test/isolation/expected/reindex-schema.out17
-rw-r--r--src/test/isolation/expected/ri-trigger.out131
-rw-r--r--src/test/isolation/expected/sequence-ddl.out91
-rw-r--r--src/test/isolation/expected/serializable-parallel-2.out50
-rw-r--r--src/test/isolation/expected/serializable-parallel.out58
-rw-r--r--src/test/isolation/expected/simple-write-skew.out41
-rw-r--r--src/test/isolation/expected/skip-locked-2.out67
-rw-r--r--src/test/isolation/expected/skip-locked-3.out25
-rw-r--r--src/test/isolation/expected/skip-locked-4.out27
-rw-r--r--src/test/isolation/expected/skip-locked-4_1.out23
-rw-r--r--src/test/isolation/expected/skip-locked.out561
-rw-r--r--src/test/isolation/expected/temp-schema-cleanup.out116
-rw-r--r--src/test/isolation/expected/temporal-range-integrity.out379
-rw-r--r--src/test/isolation/expected/timeouts.out81
-rw-r--r--src/test/isolation/expected/total-cash.out349
-rw-r--r--src/test/isolation/expected/truncate-conflict.out115
-rw-r--r--src/test/isolation/expected/tuplelock-conflict.out629
-rw-r--r--src/test/isolation/expected/tuplelock-partition.out24
-rw-r--r--src/test/isolation/expected/tuplelock-update.out49
-rw-r--r--src/test/isolation/expected/tuplelock-upgrade-no-deadlock.out253
-rw-r--r--src/test/isolation/expected/two-ids.out1187
-rw-r--r--src/test/isolation/expected/update-conflict-out.out31
-rw-r--r--src/test/isolation/expected/update-locked-tuple.out55
-rw-r--r--src/test/isolation/expected/vacuum-concurrent-drop.out76
-rw-r--r--src/test/isolation/expected/vacuum-conflict.out149
-rw-r--r--src/test/isolation/expected/vacuum-reltuples.out67
-rw-r--r--src/test/isolation/expected/vacuum-skip-locked.out171
-rw-r--r--src/test/isolation/isolation_main.c152
-rw-r--r--src/test/isolation/isolation_schedule101
-rw-r--r--src/test/isolation/isolationtester.c1149
-rw-r--r--src/test/isolation/isolationtester.h93
-rw-r--r--src/test/isolation/specparse.c1689
-rw-r--r--src/test/isolation/specparse.y280
-rw-r--r--src/test/isolation/specs/aborted-keyrevoke.spec46
-rw-r--r--src/test/isolation/specs/alter-table-1.spec170
-rw-r--r--src/test/isolation/specs/alter-table-2.spec79
-rw-r--r--src/test/isolation/specs/alter-table-3.spec79
-rw-r--r--src/test/isolation/specs/alter-table-4.spec37
-rw-r--r--src/test/isolation/specs/async-notify.spec84
-rw-r--r--src/test/isolation/specs/classroom-scheduling.spec29
-rw-r--r--src/test/isolation/specs/create-trigger.spec54
-rw-r--r--src/test/isolation/specs/deadlock-hard.spec79
-rw-r--r--src/test/isolation/specs/deadlock-parallel.spec113
-rw-r--r--src/test/isolation/specs/deadlock-simple.spec29
-rw-r--r--src/test/isolation/specs/deadlock-soft-2.spec43
-rw-r--r--src/test/isolation/specs/deadlock-soft.spec40
-rw-r--r--src/test/isolation/specs/delete-abort-savept-2.spec34
-rw-r--r--src/test/isolation/specs/delete-abort-savept.spec37
-rw-r--r--src/test/isolation/specs/detach-partition-concurrently-1.spec69
-rw-r--r--src/test/isolation/specs/detach-partition-concurrently-2.spec41
-rw-r--r--src/test/isolation/specs/detach-partition-concurrently-3.spec86
-rw-r--r--src/test/isolation/specs/detach-partition-concurrently-4.spec83
-rw-r--r--src/test/isolation/specs/drop-index-concurrently-1.spec41
-rw-r--r--src/test/isolation/specs/eval-plan-qual-trigger.spec410
-rw-r--r--src/test/isolation/specs/eval-plan-qual.spec359
-rw-r--r--src/test/isolation/specs/fk-contention.spec19
-rw-r--r--src/test/isolation/specs/fk-deadlock.spec46
-rw-r--r--src/test/isolation/specs/fk-deadlock2.spec48
-rw-r--r--src/test/isolation/specs/fk-partitioned-1.spec45
-rw-r--r--src/test/isolation/specs/fk-partitioned-2.spec29
-rw-r--r--src/test/isolation/specs/freeze-the-dead.spec56
-rw-r--r--src/test/isolation/specs/horizons.spec169
-rw-r--r--src/test/isolation/specs/index-only-scan.spec46
-rw-r--r--src/test/isolation/specs/inherit-temp.spec78
-rw-r--r--src/test/isolation/specs/insert-conflict-do-nothing-2.spec34
-rw-r--r--src/test/isolation/specs/insert-conflict-do-nothing.spec40
-rw-r--r--src/test/isolation/specs/insert-conflict-do-update-2.spec40
-rw-r--r--src/test/isolation/specs/insert-conflict-do-update-3.spec69
-rw-r--r--src/test/isolation/specs/insert-conflict-do-update.spec39
-rw-r--r--src/test/isolation/specs/insert-conflict-specconflict.spec259
-rw-r--r--src/test/isolation/specs/lock-committed-keyupdate.spec66
-rw-r--r--src/test/isolation/specs/lock-committed-update.spec62
-rw-r--r--src/test/isolation/specs/lock-update-delete.spec61
-rw-r--r--src/test/isolation/specs/lock-update-traversal.spec39
-rw-r--r--src/test/isolation/specs/multiple-cic.spec43
-rw-r--r--src/test/isolation/specs/multiple-row-versions.spec47
-rw-r--r--src/test/isolation/specs/multixact-no-deadlock.spec35
-rw-r--r--src/test/isolation/specs/multixact-no-forget.spec44
-rw-r--r--src/test/isolation/specs/nowait-2.spec37
-rw-r--r--src/test/isolation/specs/nowait-3.spec33
-rw-r--r--src/test/isolation/specs/nowait-4.spec35
-rw-r--r--src/test/isolation/specs/nowait-5.spec57
-rw-r--r--src/test/isolation/specs/nowait.spec25
-rw-r--r--src/test/isolation/specs/partial-index.spec32
-rw-r--r--src/test/isolation/specs/partition-concurrent-attach.spec43
-rw-r--r--src/test/isolation/specs/partition-drop-index-locking.spec47
-rw-r--r--src/test/isolation/specs/partition-key-update-1.spec86
-rw-r--r--src/test/isolation/specs/partition-key-update-2.spec45
-rw-r--r--src/test/isolation/specs/partition-key-update-3.spec44
-rw-r--r--src/test/isolation/specs/partition-key-update-4.spec76
-rw-r--r--src/test/isolation/specs/plpgsql-toast.spec178
-rw-r--r--src/test/isolation/specs/predicate-gin.spec115
-rw-r--r--src/test/isolation/specs/predicate-gist.spec117
-rw-r--r--src/test/isolation/specs/predicate-hash.spec122
-rw-r--r--src/test/isolation/specs/predicate-lock-hot-tuple.spec37
-rw-r--r--src/test/isolation/specs/prepared-transactions-cic.spec37
-rw-r--r--src/test/isolation/specs/prepared-transactions.spec1507
-rw-r--r--src/test/isolation/specs/project-manager.spec30
-rw-r--r--src/test/isolation/specs/propagate-lock-delete.spec42
-rw-r--r--src/test/isolation/specs/read-only-anomaly-2.spec42
-rw-r--r--src/test/isolation/specs/read-only-anomaly-3.spec39
-rw-r--r--src/test/isolation/specs/read-only-anomaly.spec38
-rw-r--r--src/test/isolation/specs/read-write-unique-2.spec36
-rw-r--r--src/test/isolation/specs/read-write-unique-3.spec33
-rw-r--r--src/test/isolation/specs/read-write-unique-4.spec48
-rw-r--r--src/test/isolation/specs/read-write-unique.spec39
-rw-r--r--src/test/isolation/specs/receipt-report.spec47
-rw-r--r--src/test/isolation/specs/referential-integrity.spec32
-rw-r--r--src/test/isolation/specs/reindex-concurrently-toast.spec119
-rw-r--r--src/test/isolation/specs/reindex-concurrently.spec40
-rw-r--r--src/test/isolation/specs/reindex-schema.spec32
-rw-r--r--src/test/isolation/specs/ri-trigger.spec53
-rw-r--r--src/test/isolation/specs/sequence-ddl.spec41
-rw-r--r--src/test/isolation/specs/serializable-parallel-2.spec30
-rw-r--r--src/test/isolation/specs/serializable-parallel.spec47
-rw-r--r--src/test/isolation/specs/simple-write-skew.spec30
-rw-r--r--src/test/isolation/specs/skip-locked-2.spec41
-rw-r--r--src/test/isolation/specs/skip-locked-3.spec36
-rw-r--r--src/test/isolation/specs/skip-locked-4.spec36
-rw-r--r--src/test/isolation/specs/skip-locked.spec28
-rw-r--r--src/test/isolation/specs/temp-schema-cleanup.spec85
-rw-r--r--src/test/isolation/specs/temporal-range-integrity.spec38
-rw-r--r--src/test/isolation/specs/timeouts.spec49
-rw-r--r--src/test/isolation/specs/total-cash.spec28
-rw-r--r--src/test/isolation/specs/truncate-conflict.spec38
-rw-r--r--src/test/isolation/specs/tuplelock-conflict.spec63
-rw-r--r--src/test/isolation/specs/tuplelock-partition.spec32
-rw-r--r--src/test/isolation/specs/tuplelock-update.spec37
-rw-r--r--src/test/isolation/specs/tuplelock-upgrade-no-deadlock.spec69
-rw-r--r--src/test/isolation/specs/two-ids.spec40
-rw-r--r--src/test/isolation/specs/update-conflict-out.spec54
-rw-r--r--src/test/isolation/specs/update-locked-tuple.spec38
-rw-r--r--src/test/isolation/specs/vacuum-concurrent-drop.spec45
-rw-r--r--src/test/isolation/specs/vacuum-conflict.spec51
-rw-r--r--src/test/isolation/specs/vacuum-reltuples.spec48
-rw-r--r--src/test/isolation/specs/vacuum-skip-locked.spec61
-rw-r--r--src/test/isolation/specscanner.c2220
-rw-r--r--src/test/isolation/specscanner.l157
-rw-r--r--src/test/kerberos/.gitignore2
-rw-r--r--src/test/kerberos/Makefile25
-rw-r--r--src/test/kerberos/README45
-rw-r--r--src/test/kerberos/t/001_auth.pl401
-rw-r--r--src/test/ldap/.gitignore2
-rw-r--r--src/test/ldap/Makefile25
-rw-r--r--src/test/ldap/README52
-rw-r--r--src/test/ldap/authdata.ldif32
-rw-r--r--src/test/ldap/t/001_auth.pl369
-rw-r--r--src/test/locale/.gitignore1
-rw-r--r--src/test/locale/Makefile22
-rw-r--r--src/test/locale/README28
-rw-r--r--src/test/locale/de_DE.ISO8859-1/Makefile7
-rw-r--r--src/test/locale/de_DE.ISO8859-1/README4
-rw-r--r--src/test/locale/de_DE.ISO8859-1/expected/de-ctype.out257
-rw-r--r--src/test/locale/de_DE.ISO8859-1/expected/test-de-char.sql.out23
-rw-r--r--src/test/locale/de_DE.ISO8859-1/expected/test-de-select.sql.out7
-rw-r--r--src/test/locale/de_DE.ISO8859-1/expected/test-de-sort.out20
-rw-r--r--src/test/locale/de_DE.ISO8859-1/expected/test-de-text.sql.out23
-rw-r--r--src/test/locale/de_DE.ISO8859-1/expected/test-de-upper-char.sql.out23
-rw-r--r--src/test/locale/de_DE.ISO8859-1/expected/test-de-upper-text.sql.out23
-rw-r--r--src/test/locale/de_DE.ISO8859-1/expected/test-de-upper-varchar.sql.out23
-rw-r--r--src/test/locale/de_DE.ISO8859-1/expected/test-de-varchar.sql.out23
-rwxr-xr-xsrc/test/locale/de_DE.ISO8859-1/runall64
-rw-r--r--src/test/locale/de_DE.ISO8859-1/test-de-select.sql.in1
-rw-r--r--src/test/locale/de_DE.ISO8859-1/test-de-sort.in20
-rw-r--r--src/test/locale/de_DE.ISO8859-1/test-de-upper.sql.in22
-rw-r--r--src/test/locale/de_DE.ISO8859-1/test-de.sql.in22
-rw-r--r--src/test/locale/gr_GR.ISO8859-7/Makefile7
-rw-r--r--src/test/locale/gr_GR.ISO8859-7/README4
-rw-r--r--src/test/locale/gr_GR.ISO8859-7/expected/gr-ctype.out257
-rw-r--r--src/test/locale/gr_GR.ISO8859-7/expected/test-gr-char.sql.out54
-rw-r--r--src/test/locale/gr_GR.ISO8859-7/expected/test-gr-select.sql.out10
-rw-r--r--src/test/locale/gr_GR.ISO8859-7/expected/test-gr-sort.out7
-rw-r--r--src/test/locale/gr_GR.ISO8859-7/expected/test-gr-text.sql.out54
-rw-r--r--src/test/locale/gr_GR.ISO8859-7/expected/test-gr-varchar.sql.out54
-rwxr-xr-xsrc/test/locale/gr_GR.ISO8859-7/runall49
-rw-r--r--src/test/locale/gr_GR.ISO8859-7/test-gr-select.sql.in1
-rw-r--r--src/test/locale/gr_GR.ISO8859-7/test-gr-sort.in7
-rw-r--r--src/test/locale/gr_GR.ISO8859-7/test-gr.sql.in53
-rw-r--r--src/test/locale/koi8-r/Makefile7
-rw-r--r--src/test/locale/koi8-r/expected/koi8-ctype.out257
-rw-r--r--src/test/locale/koi8-r/expected/test-koi8-char.sql.out54
-rw-r--r--src/test/locale/koi8-r/expected/test-koi8-select.sql.out8
-rw-r--r--src/test/locale/koi8-r/expected/test-koi8-sort.out9
-rw-r--r--src/test/locale/koi8-r/expected/test-koi8-text.sql.out54
-rw-r--r--src/test/locale/koi8-r/expected/test-koi8-varchar.sql.out54
-rwxr-xr-xsrc/test/locale/koi8-r/runall49
-rw-r--r--src/test/locale/koi8-r/test-koi8-select.sql.in1
-rw-r--r--src/test/locale/koi8-r/test-koi8-sort.in9
-rw-r--r--src/test/locale/koi8-r/test-koi8.sql.in53
-rw-r--r--src/test/locale/koi8-to-win1251/Makefile7
-rw-r--r--src/test/locale/koi8-to-win1251/README6
-rw-r--r--src/test/locale/koi8-to-win1251/expected/test-koi8-char.sql.out54
-rw-r--r--src/test/locale/koi8-to-win1251/expected/test-koi8-select.sql.out8
-rw-r--r--src/test/locale/koi8-to-win1251/expected/test-koi8-sort.out9
-rw-r--r--src/test/locale/koi8-to-win1251/expected/test-koi8-text.sql.out54
-rw-r--r--src/test/locale/koi8-to-win1251/expected/test-koi8-varchar.sql.out54
-rwxr-xr-xsrc/test/locale/koi8-to-win1251/runall46
-rw-r--r--src/test/locale/koi8-to-win1251/test-koi8-select.sql.in1
-rw-r--r--src/test/locale/koi8-to-win1251/test-koi8-sort.in9
-rw-r--r--src/test/locale/koi8-to-win1251/test-koi8.sql.in53
-rwxr-xr-xsrc/test/locale/sort-test.pl16
-rwxr-xr-xsrc/test/locale/sort-test.py18
-rw-r--r--src/test/locale/test-ctype.c79
-rw-r--r--src/test/mb/README10
-rw-r--r--src/test/mb/expected/big5.out84
-rw-r--r--src/test/mb/expected/euc_cn.out87
-rw-r--r--src/test/mb/expected/euc_jp.out87
-rw-r--r--src/test/mb/expected/euc_kr.out87
-rw-r--r--src/test/mb/expected/euc_tw.out85
-rw-r--r--src/test/mb/expected/gb18030.out86
-rw-r--r--src/test/mb/expected/mule_internal.out333
-rw-r--r--src/test/mb/expected/sjis.out90
-rw-r--r--src/test/mb/expected/utf8.out87
-rwxr-xr-xsrc/test/mb/mbregress.sh74
-rw-r--r--src/test/mb/sql/big5.sql20
-rw-r--r--src/test/mb/sql/euc_cn.sql19
-rw-r--r--src/test/mb/sql/euc_jp.sql19
-rw-r--r--src/test/mb/sql/euc_kr.sql19
-rw-r--r--src/test/mb/sql/euc_tw.sql20
-rw-r--r--src/test/mb/sql/gb18030.sql19
-rw-r--r--src/test/mb/sql/mule_internal.sql72
-rw-r--r--src/test/mb/sql/sjis.sql20
-rw-r--r--src/test/mb/sql/utf8.sql19
-rw-r--r--src/test/modules/Makefile40
-rw-r--r--src/test/modules/README20
-rw-r--r--src/test/modules/brin/.gitignore4
-rw-r--r--src/test/modules/brin/Makefile17
-rw-r--r--src/test/modules/brin/expected/summarization-and-inprogress-insertion.out51
-rw-r--r--src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec44
-rw-r--r--src/test/modules/brin/t/01_workitems.pl44
-rw-r--r--src/test/modules/brin/t/02_wal_consistency.pl64
-rw-r--r--src/test/modules/commit_ts/.gitignore4
-rw-r--r--src/test/modules/commit_ts/Makefile20
-rw-r--r--src/test/modules/commit_ts/commit_ts.conf1
-rw-r--r--src/test/modules/commit_ts/expected/commit_timestamp.out139
-rw-r--r--src/test/modules/commit_ts/expected/commit_timestamp_1.out119
-rw-r--r--src/test/modules/commit_ts/sql/commit_timestamp.sql64
-rw-r--r--src/test/modules/commit_ts/t/001_base.pl36
-rw-r--r--src/test/modules/commit_ts/t/002_standby.pl66
-rw-r--r--src/test/modules/commit_ts/t/003_standby_2.pl67
-rw-r--r--src/test/modules/commit_ts/t/004_restart.pl152
-rw-r--r--src/test/modules/delay_execution/.gitignore3
-rw-r--r--src/test/modules/delay_execution/Makefile22
-rw-r--r--src/test/modules/delay_execution/delay_execution.c104
-rw-r--r--src/test/modules/delay_execution/expected/partition-addition.out27
-rw-r--r--src/test/modules/delay_execution/expected/partition-removal-1.out233
-rw-r--r--src/test/modules/delay_execution/specs/partition-addition.spec38
-rw-r--r--src/test/modules/delay_execution/specs/partition-removal-1.spec58
-rw-r--r--src/test/modules/dummy_index_am/.gitignore3
-rw-r--r--src/test/modules/dummy_index_am/Makefile20
-rw-r--r--src/test/modules/dummy_index_am/README12
-rw-r--r--src/test/modules/dummy_index_am/dummy_index_am--1.0.sql19
-rw-r--r--src/test/modules/dummy_index_am/dummy_index_am.c333
-rw-r--r--src/test/modules/dummy_index_am/dummy_index_am.control5
-rw-r--r--src/test/modules/dummy_index_am/expected/reloptions.out145
-rw-r--r--src/test/modules/dummy_index_am/sql/reloptions.sql83
-rw-r--r--src/test/modules/dummy_seclabel/.gitignore4
-rw-r--r--src/test/modules/dummy_seclabel/Makefile20
-rw-r--r--src/test/modules/dummy_seclabel/README41
-rw-r--r--src/test/modules/dummy_seclabel/dummy_seclabel--1.0.sql8
-rw-r--r--src/test/modules/dummy_seclabel/dummy_seclabel.c63
-rw-r--r--src/test/modules/dummy_seclabel/dummy_seclabel.control4
-rw-r--r--src/test/modules/dummy_seclabel/expected/dummy_seclabel.out117
-rw-r--r--src/test/modules/dummy_seclabel/sql/dummy_seclabel.sql115
-rw-r--r--src/test/modules/libpq_pipeline/.gitignore5
-rw-r--r--src/test/modules/libpq_pipeline/Makefile25
-rw-r--r--src/test/modules/libpq_pipeline/README1
-rw-r--r--src/test/modules/libpq_pipeline/libpq_pipeline.c1883
-rw-r--r--src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl78
-rw-r--r--src/test/modules/libpq_pipeline/traces/disallowed_in_pipeline.trace6
-rw-r--r--src/test/modules/libpq_pipeline/traces/multi_pipelines.trace23
-rw-r--r--src/test/modules/libpq_pipeline/traces/nosync.trace92
-rw-r--r--src/test/modules/libpq_pipeline/traces/pipeline_abort.trace64
-rw-r--r--src/test/modules/libpq_pipeline/traces/pipeline_idle.trace93
-rw-r--r--src/test/modules/libpq_pipeline/traces/prepared.trace18
-rw-r--r--src/test/modules/libpq_pipeline/traces/simple_pipeline.trace12
-rw-r--r--src/test/modules/libpq_pipeline/traces/singlerow.trace39
-rw-r--r--src/test/modules/libpq_pipeline/traces/transaction.trace61
-rw-r--r--src/test/modules/plsample/.gitignore3
-rw-r--r--src/test/modules/plsample/Makefile20
-rw-r--r--src/test/modules/plsample/README6
-rw-r--r--src/test/modules/plsample/expected/plsample.out36
-rw-r--r--src/test/modules/plsample/plsample--1.0.sql14
-rw-r--r--src/test/modules/plsample/plsample.c187
-rw-r--r--src/test/modules/plsample/plsample.control8
-rw-r--r--src/test/modules/plsample/sql/plsample.sql15
-rw-r--r--src/test/modules/snapshot_too_old/.gitignore3
-rw-r--r--src/test/modules/snapshot_too_old/Makefile28
-rw-r--r--src/test/modules/snapshot_too_old/expected/sto_using_cursor.out19
-rw-r--r--src/test/modules/snapshot_too_old/expected/sto_using_hash_index.out19
-rw-r--r--src/test/modules/snapshot_too_old/expected/sto_using_select.out18
-rw-r--r--src/test/modules/snapshot_too_old/specs/sto_using_cursor.spec38
-rw-r--r--src/test/modules/snapshot_too_old/specs/sto_using_hash_index.spec31
-rw-r--r--src/test/modules/snapshot_too_old/specs/sto_using_select.spec37
-rw-r--r--src/test/modules/snapshot_too_old/sto.conf2
-rw-r--r--src/test/modules/spgist_name_ops/.gitignore4
-rw-r--r--src/test/modules/spgist_name_ops/Makefile23
-rw-r--r--src/test/modules/spgist_name_ops/README8
-rw-r--r--src/test/modules/spgist_name_ops/expected/spgist_name_ops.out112
-rw-r--r--src/test/modules/spgist_name_ops/spgist_name_ops--1.0.sql54
-rw-r--r--src/test/modules/spgist_name_ops/spgist_name_ops.c501
-rw-r--r--src/test/modules/spgist_name_ops/spgist_name_ops.control4
-rw-r--r--src/test/modules/spgist_name_ops/sql/spgist_name_ops.sql51
-rw-r--r--src/test/modules/ssl_passphrase_callback/.gitignore1
-rw-r--r--src/test/modules/ssl_passphrase_callback/Makefile40
-rw-r--r--src/test/modules/ssl_passphrase_callback/server.crt19
-rw-r--r--src/test/modules/ssl_passphrase_callback/server.key30
-rw-r--r--src/test/modules/ssl_passphrase_callback/ssl_passphrase_func.c90
-rw-r--r--src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl77
-rw-r--r--src/test/modules/test_bloomfilter/.gitignore4
-rw-r--r--src/test/modules/test_bloomfilter/Makefile23
-rw-r--r--src/test/modules/test_bloomfilter/README68
-rw-r--r--src/test/modules/test_bloomfilter/expected/test_bloomfilter.out22
-rw-r--r--src/test/modules/test_bloomfilter/sql/test_bloomfilter.sql19
-rw-r--r--src/test/modules/test_bloomfilter/test_bloomfilter--1.0.sql11
-rw-r--r--src/test/modules/test_bloomfilter/test_bloomfilter.c138
-rw-r--r--src/test/modules/test_bloomfilter/test_bloomfilter.control4
-rw-r--r--src/test/modules/test_ddl_deparse/.gitignore4
-rw-r--r--src/test/modules/test_ddl_deparse/Makefile43
-rw-r--r--src/test/modules/test_ddl_deparse/README8
-rw-r--r--src/test/modules/test_ddl_deparse/expected/alter_extension.out0
-rw-r--r--src/test/modules/test_ddl_deparse/expected/alter_function.out15
-rw-r--r--src/test/modules/test_ddl_deparse/expected/alter_sequence.out15
-rw-r--r--src/test/modules/test_ddl_deparse/expected/alter_table.out29
-rw-r--r--src/test/modules/test_ddl_deparse/expected/alter_ts_config.out8
-rw-r--r--src/test/modules/test_ddl_deparse/expected/alter_type_enum.out7
-rw-r--r--src/test/modules/test_ddl_deparse/expected/comment_on.out23
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_conversion.out6
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_domain.out11
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_extension.out5
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_function.out0
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_operator.out0
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_rule.out30
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_schema.out19
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_sequence_1.out11
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_table.out164
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_transform.out15
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_trigger.out18
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_type.out24
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_view.out19
-rw-r--r--src/test/modules/test_ddl_deparse/expected/defprivs.out6
-rw-r--r--src/test/modules/test_ddl_deparse/expected/matviews.out8
-rw-r--r--src/test/modules/test_ddl_deparse/expected/opfamily.out68
-rw-r--r--src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out40
-rw-r--r--src/test/modules/test_ddl_deparse/sql/alter_function.sql17
-rw-r--r--src/test/modules/test_ddl_deparse/sql/alter_sequence.sql15
-rw-r--r--src/test/modules/test_ddl_deparse/sql/alter_table.sql21
-rw-r--r--src/test/modules/test_ddl_deparse/sql/alter_ts_config.sql8
-rw-r--r--src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql6
-rw-r--r--src/test/modules/test_ddl_deparse/sql/comment_on.sql14
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_conversion.sql6
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_domain.sql10
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_extension.sql5
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_rule.sql31
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_schema.sql17
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql11
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_table.sql142
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_transform.sql16
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_trigger.sql18
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_type.sql21
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_view.sql17
-rw-r--r--src/test/modules/test_ddl_deparse/sql/defprivs.sql6
-rw-r--r--src/test/modules/test_ddl_deparse/sql/matviews.sql8
-rw-r--r--src/test/modules/test_ddl_deparse/sql/opfamily.sql52
-rw-r--r--src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql42
-rw-r--r--src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql16
-rw-r--r--src/test/modules/test_ddl_deparse/test_ddl_deparse.c296
-rw-r--r--src/test/modules/test_ddl_deparse/test_ddl_deparse.control4
-rw-r--r--src/test/modules/test_extensions/.gitignore4
-rw-r--r--src/test/modules/test_extensions/Makefile32
-rw-r--r--src/test/modules/test_extensions/expected/test_extdepend.out188
-rw-r--r--src/test/modules/test_extensions/expected/test_extensions.out314
-rw-r--r--src/test/modules/test_extensions/sql/test_extdepend.sql90
-rw-r--r--src/test/modules/test_extensions/sql/test_extensions.sql211
-rw-r--r--src/test/modules/test_extensions/test_ext1--1.0.sql3
-rw-r--r--src/test/modules/test_extensions/test_ext1.control5
-rw-r--r--src/test/modules/test_extensions/test_ext2--1.0.sql3
-rw-r--r--src/test/modules/test_extensions/test_ext2.control4
-rw-r--r--src/test/modules/test_extensions/test_ext3--1.0.sql9
-rw-r--r--src/test/modules/test_extensions/test_ext3.control3
-rw-r--r--src/test/modules/test_extensions/test_ext4--1.0.sql3
-rw-r--r--src/test/modules/test_extensions/test_ext4.control4
-rw-r--r--src/test/modules/test_extensions/test_ext5--1.0.sql3
-rw-r--r--src/test/modules/test_extensions/test_ext5.control3
-rw-r--r--src/test/modules/test_extensions/test_ext6--1.0.sql1
-rw-r--r--src/test/modules/test_extensions/test_ext6.control5
-rw-r--r--src/test/modules/test_extensions/test_ext7--1.0--2.0.sql8
-rw-r--r--src/test/modules/test_extensions/test_ext7--1.0.sql13
-rw-r--r--src/test/modules/test_extensions/test_ext7.control4
-rw-r--r--src/test/modules/test_extensions/test_ext8--1.0.sql21
-rw-r--r--src/test/modules/test_extensions/test_ext8.control4
-rw-r--r--src/test/modules/test_extensions/test_ext_cine--1.0--1.1.sql26
-rw-r--r--src/test/modules/test_extensions/test_ext_cine--1.0.sql25
-rw-r--r--src/test/modules/test_extensions/test_ext_cine.control3
-rw-r--r--src/test/modules/test_extensions/test_ext_cor--1.0.sql20
-rw-r--r--src/test/modules/test_extensions/test_ext_cor.control3
-rw-r--r--src/test/modules/test_extensions/test_ext_cyclic1--1.0.sql3
-rw-r--r--src/test/modules/test_extensions/test_ext_cyclic1.control4
-rw-r--r--src/test/modules/test_extensions/test_ext_cyclic2--1.0.sql3
-rw-r--r--src/test/modules/test_extensions/test_ext_cyclic2.control4
-rw-r--r--src/test/modules/test_extensions/test_ext_evttrig--1.0--2.0.sql7
-rw-r--r--src/test/modules/test_extensions/test_ext_evttrig--1.0.sql16
-rw-r--r--src/test/modules/test_extensions/test_ext_evttrig.control3
-rw-r--r--src/test/modules/test_ginpostinglist/.gitignore4
-rw-r--r--src/test/modules/test_ginpostinglist/Makefile23
-rw-r--r--src/test/modules/test_ginpostinglist/README2
-rw-r--r--src/test/modules/test_ginpostinglist/expected/test_ginpostinglist.out19
-rw-r--r--src/test/modules/test_ginpostinglist/sql/test_ginpostinglist.sql7
-rw-r--r--src/test/modules/test_ginpostinglist/test_ginpostinglist--1.0.sql8
-rw-r--r--src/test/modules/test_ginpostinglist/test_ginpostinglist.c96
-rw-r--r--src/test/modules/test_ginpostinglist/test_ginpostinglist.control4
-rw-r--r--src/test/modules/test_integerset/.gitignore4
-rw-r--r--src/test/modules/test_integerset/Makefile23
-rw-r--r--src/test/modules/test_integerset/README7
-rw-r--r--src/test/modules/test_integerset/expected/test_integerset.out31
-rw-r--r--src/test/modules/test_integerset/sql/test_integerset.sql7
-rw-r--r--src/test/modules/test_integerset/test_integerset--1.0.sql8
-rw-r--r--src/test/modules/test_integerset/test_integerset.c623
-rw-r--r--src/test/modules/test_integerset/test_integerset.control4
-rw-r--r--src/test/modules/test_misc/.gitignore4
-rw-r--r--src/test/modules/test_misc/Makefile14
-rw-r--r--src/test/modules/test_misc/README4
-rw-r--r--src/test/modules/test_misc/t/001_constraint_validation.pl313
-rw-r--r--src/test/modules/test_parser/.gitignore4
-rw-r--r--src/test/modules/test_parser/Makefile23
-rw-r--r--src/test/modules/test_parser/README61
-rw-r--r--src/test/modules/test_parser/expected/test_parser.out44
-rw-r--r--src/test/modules/test_parser/sql/test_parser.sql18
-rw-r--r--src/test/modules/test_parser/test_parser--1.0.sql32
-rw-r--r--src/test/modules/test_parser/test_parser.c127
-rw-r--r--src/test/modules/test_parser/test_parser.control5
-rw-r--r--src/test/modules/test_pg_dump/.gitignore4
-rw-r--r--src/test/modules/test_pg_dump/Makefile21
-rw-r--r--src/test/modules/test_pg_dump/README4
-rw-r--r--src/test/modules/test_pg_dump/expected/test_pg_dump.out93
-rw-r--r--src/test/modules/test_pg_dump/sql/test_pg_dump.sql108
-rw-r--r--src/test/modules/test_pg_dump/t/001_base.pl842
-rw-r--r--src/test/modules/test_pg_dump/test_pg_dump--1.0.sql62
-rw-r--r--src/test/modules/test_pg_dump/test_pg_dump.control3
-rw-r--r--src/test/modules/test_predtest/.gitignore4
-rw-r--r--src/test/modules/test_predtest/Makefile23
-rw-r--r--src/test/modules/test_predtest/README28
-rw-r--r--src/test/modules/test_predtest/expected/test_predtest.out1096
-rw-r--r--src/test/modules/test_predtest/sql/test_predtest.sql442
-rw-r--r--src/test/modules/test_predtest/test_predtest--1.0.sql16
-rw-r--r--src/test/modules/test_predtest/test_predtest.c218
-rw-r--r--src/test/modules/test_predtest/test_predtest.control4
-rw-r--r--src/test/modules/test_rbtree/.gitignore4
-rw-r--r--src/test/modules/test_rbtree/Makefile23
-rw-r--r--src/test/modules/test_rbtree/README13
-rw-r--r--src/test/modules/test_rbtree/expected/test_rbtree.out12
-rw-r--r--src/test/modules/test_rbtree/sql/test_rbtree.sql8
-rw-r--r--src/test/modules/test_rbtree/test_rbtree--1.0.sql8
-rw-r--r--src/test/modules/test_rbtree/test_rbtree.c413
-rw-r--r--src/test/modules/test_rbtree/test_rbtree.control4
-rw-r--r--src/test/modules/test_regex/.gitignore4
-rw-r--r--src/test/modules/test_regex/Makefile23
-rw-r--r--src/test/modules/test_regex/README78
-rw-r--r--src/test/modules/test_regex/expected/test_regex.out4934
-rw-r--r--src/test/modules/test_regex/expected/test_regex_utf8.out100
-rw-r--r--src/test/modules/test_regex/expected/test_regex_utf8_1.out8
-rw-r--r--src/test/modules/test_regex/sql/test_regex.sql1744
-rw-r--r--src/test/modules/test_regex/sql/test_regex_utf8.sql60
-rw-r--r--src/test/modules/test_regex/test_regex--1.0.sql9
-rw-r--r--src/test/modules/test_regex/test_regex.c773
-rw-r--r--src/test/modules/test_regex/test_regex.control4
-rw-r--r--src/test/modules/test_rls_hooks/.gitignore4
-rw-r--r--src/test/modules/test_rls_hooks/Makefile27
-rw-r--r--src/test/modules/test_rls_hooks/README16
-rw-r--r--src/test/modules/test_rls_hooks/expected/test_rls_hooks.out201
-rw-r--r--src/test/modules/test_rls_hooks/rls_hooks.conf1
-rw-r--r--src/test/modules/test_rls_hooks/sql/test_rls_hooks.sql176
-rw-r--r--src/test/modules/test_rls_hooks/test_rls_hooks.c184
-rw-r--r--src/test/modules/test_rls_hooks/test_rls_hooks.control4
-rw-r--r--src/test/modules/test_rls_hooks/test_rls_hooks.h25
-rw-r--r--src/test/modules/test_shm_mq/.gitignore4
-rw-r--r--src/test/modules/test_shm_mq/Makefile25
-rw-r--r--src/test/modules/test_shm_mq/README49
-rw-r--r--src/test/modules/test_shm_mq/expected/test_shm_mq.out36
-rw-r--r--src/test/modules/test_shm_mq/setup.c316
-rw-r--r--src/test/modules/test_shm_mq/sql/test_shm_mq.sql12
-rw-r--r--src/test/modules/test_shm_mq/test.c266
-rw-r--r--src/test/modules/test_shm_mq/test_shm_mq--1.0.sql19
-rw-r--r--src/test/modules/test_shm_mq/test_shm_mq.control4
-rw-r--r--src/test/modules/test_shm_mq/test_shm_mq.h45
-rw-r--r--src/test/modules/test_shm_mq/worker.c197
-rw-r--r--src/test/modules/unsafe_tests/.gitignore4
-rw-r--r--src/test/modules/unsafe_tests/Makefile14
-rw-r--r--src/test/modules/unsafe_tests/README8
-rw-r--r--src/test/modules/unsafe_tests/expected/alter_system_table.out179
-rw-r--r--src/test/modules/unsafe_tests/expected/rolenames.out1091
-rw-r--r--src/test/modules/unsafe_tests/sql/alter_system_table.sql194
-rw-r--r--src/test/modules/unsafe_tests/sql/rolenames.sql504
-rw-r--r--src/test/modules/worker_spi/.gitignore4
-rw-r--r--src/test/modules/worker_spi/Makefile26
-rw-r--r--src/test/modules/worker_spi/dynamic.conf2
-rw-r--r--src/test/modules/worker_spi/expected/worker_spi.out50
-rw-r--r--src/test/modules/worker_spi/sql/worker_spi.sql35
-rw-r--r--src/test/modules/worker_spi/worker_spi--1.0.sql9
-rw-r--r--src/test/modules/worker_spi/worker_spi.c392
-rw-r--r--src/test/modules/worker_spi/worker_spi.control5
-rw-r--r--src/test/perl/Makefile35
-rw-r--r--src/test/perl/PostgreSQL/Test/Cluster.pm13
-rw-r--r--src/test/perl/PostgreSQL/Test/Utils.pm11
-rw-r--r--src/test/perl/PostgresNode.pm2810
-rw-r--r--src/test/perl/PostgresVersion.pm136
-rw-r--r--src/test/perl/README97
-rw-r--r--src/test/perl/RecursiveCopy.pm157
-rw-r--r--src/test/perl/SimpleTee.pm35
-rw-r--r--src/test/perl/TestLib.pm982
-rw-r--r--src/test/recovery/.gitignore2
-rw-r--r--src/test/recovery/Makefile29
-rw-r--r--src/test/recovery/README25
-rw-r--r--src/test/recovery/t/001_stream_rep.pl486
-rw-r--r--src/test/recovery/t/002_archiving.pl99
-rw-r--r--src/test/recovery/t/003_recovery_targets.pl184
-rw-r--r--src/test/recovery/t/004_timeline_switch.pl111
-rw-r--r--src/test/recovery/t/005_replay_delay.pl112
-rw-r--r--src/test/recovery/t/006_logical_decoding.pl195
-rw-r--r--src/test/recovery/t/007_sync_rep.pl219
-rw-r--r--src/test/recovery/t/008_fsm_truncation.pl99
-rw-r--r--src/test/recovery/t/009_twophase.pl482
-rw-r--r--src/test/recovery/t/010_logical_decoding_timelines.pl199
-rw-r--r--src/test/recovery/t/011_crash_recovery.pl64
-rw-r--r--src/test/recovery/t/012_subtransactions.pl219
-rw-r--r--src/test/recovery/t/013_crash_restart.pl248
-rw-r--r--src/test/recovery/t/014_unlogged_reinit.pl82
-rw-r--r--src/test/recovery/t/015_promotion_pages.pl87
-rw-r--r--src/test/recovery/t/016_min_consistency.pl141
-rw-r--r--src/test/recovery/t/017_shm.pl221
-rw-r--r--src/test/recovery/t/018_wal_optimize.pl399
-rw-r--r--src/test/recovery/t/019_replslot_limit.pl428
-rw-r--r--src/test/recovery/t/020_archive_status.pl236
-rw-r--r--src/test/recovery/t/021_row_visibility.pl209
-rw-r--r--src/test/recovery/t/022_crash_temp_files.pl268
-rw-r--r--src/test/recovery/t/023_pitr_prepared_xact.pl89
-rw-r--r--src/test/recovery/t/024_archive_recovery.pl103
-rw-r--r--src/test/recovery/t/025_stuck_on_old_timeline.pl111
-rw-r--r--src/test/recovery/t/026_overwrite_contrecord.pl109
-rw-r--r--src/test/recovery/t/031_recovery_conflict.pl377
-rw-r--r--src/test/recovery/t/033_replay_tsp_drops.pl152
-rw-r--r--src/test/recovery/t/cp_history_files10
-rw-r--r--src/test/regress/.gitignore11
-rw-r--r--src/test/regress/GNUmakefile167
-rw-r--r--src/test/regress/Makefile17
-rw-r--r--src/test/regress/README3
-rw-r--r--src/test/regress/data/agg.data4
-rw-r--r--src/test/regress/data/array.data103
-rw-r--r--src/test/regress/data/constrf.data2
-rw-r--r--src/test/regress/data/constro.data2
-rw-r--r--src/test/regress/data/dept.data2
-rw-r--r--src/test/regress/data/desc.data10000
-rw-r--r--src/test/regress/data/emp.data3
-rw-r--r--src/test/regress/data/hash.data10000
-rw-r--r--src/test/regress/data/jsonb.data1012
-rw-r--r--src/test/regress/data/onek.data1000
-rw-r--r--src/test/regress/data/person.data50
-rw-r--r--src/test/regress/data/real_city.data5
-rw-r--r--src/test/regress/data/rect.data3378
-rw-r--r--src/test/regress/data/streets.data5124
-rw-r--r--src/test/regress/data/stud_emp.data3
-rw-r--r--src/test/regress/data/student.data2
-rw-r--r--src/test/regress/data/tenk.data10000
-rw-r--r--src/test/regress/data/tsearch.data508
-rw-r--r--src/test/regress/expected/.gitignore10
-rw-r--r--src/test/regress/expected/advisory_lock.out275
-rw-r--r--src/test/regress/expected/aggregates.out2815
-rw-r--r--src/test/regress/expected/alter_generic.out747
-rw-r--r--src/test/regress/expected/alter_operator.out139
-rw-r--r--src/test/regress/expected/alter_table.out4593
-rw-r--r--src/test/regress/expected/amutils.out254
-rw-r--r--src/test/regress/expected/arrays.out2436
-rw-r--r--src/test/regress/expected/async.out42
-rw-r--r--src/test/regress/expected/bit.out748
-rw-r--r--src/test/regress/expected/bitmapops.out38
-rw-r--r--src/test/regress/expected/boolean.out559
-rw-r--r--src/test/regress/expected/box.out641
-rw-r--r--src/test/regress/expected/brin.out569
-rw-r--r--src/test/regress/expected/brin_bloom.out428
-rw-r--r--src/test/regress/expected/brin_multi.out462
-rw-r--r--src/test/regress/expected/btree_index.out343
-rw-r--r--src/test/regress/expected/case.out419
-rw-r--r--src/test/regress/expected/char.out122
-rw-r--r--src/test/regress/expected/char_1.out122
-rw-r--r--src/test/regress/expected/char_2.out122
-rw-r--r--src/test/regress/expected/circle.out125
-rw-r--r--src/test/regress/expected/cluster.out601
-rw-r--r--src/test/regress/expected/collate.icu.utf8.out1959
-rw-r--r--src/test/regress/expected/collate.icu.utf8_1.out9
-rw-r--r--src/test/regress/expected/collate.linux.utf8.out1160
-rw-r--r--src/test/regress/expected/collate.linux.utf8_1.out11
-rw-r--r--src/test/regress/expected/collate.out729
-rw-r--r--src/test/regress/expected/combocid.out169
-rw-r--r--src/test/regress/expected/comments.out65
-rw-r--r--src/test/regress/expected/compression.out362
-rw-r--r--src/test/regress/expected/compression_1.out361
-rw-r--r--src/test/regress/expected/conversion.out558
-rw-r--r--src/test/regress/expected/copy2.out663
-rw-r--r--src/test/regress/expected/copydml.out112
-rw-r--r--src/test/regress/expected/copyselect.out151
-rw-r--r--src/test/regress/expected/create_aggregate.out324
-rw-r--r--src/test/regress/expected/create_am.out301
-rw-r--r--src/test/regress/expected/create_cast.out74
-rw-r--r--src/test/regress/expected/create_function_3.out702
-rw-r--r--src/test/regress/expected/create_index.out2845
-rw-r--r--src/test/regress/expected/create_index_spgist.out1338
-rw-r--r--src/test/regress/expected/create_misc.out151
-rw-r--r--src/test/regress/expected/create_operator.out293
-rw-r--r--src/test/regress/expected/create_procedure.out383
-rw-r--r--src/test/regress/expected/create_table.out1321
-rw-r--r--src/test/regress/expected/create_table_like.out519
-rw-r--r--src/test/regress/expected/create_type.out322
-rw-r--r--src/test/regress/expected/create_view.out2096
-rw-r--r--src/test/regress/expected/date.out1497
-rw-r--r--src/test/regress/expected/dbsize.out188
-rw-r--r--src/test/regress/expected/delete.out33
-rw-r--r--src/test/regress/expected/dependency.out150
-rw-r--r--src/test/regress/expected/domain.out1150
-rw-r--r--src/test/regress/expected/drop_if_exists.out342
-rw-r--r--src/test/regress/expected/drop_operator.out61
-rw-r--r--src/test/regress/expected/enum.out691
-rw-r--r--src/test/regress/expected/equivclass.out453
-rw-r--r--src/test/regress/expected/errors.out447
-rw-r--r--src/test/regress/expected/event_trigger.out611
-rw-r--r--src/test/regress/expected/explain.out488
-rw-r--r--src/test/regress/expected/expressions.out381
-rw-r--r--src/test/regress/expected/fast_default.out843
-rw-r--r--src/test/regress/expected/float4-misrounded-input.out961
-rw-r--r--src/test/regress/expected/float4.out961
-rw-r--r--src/test/regress/expected/float8.out1382
-rw-r--r--src/test/regress/expected/foreign_data.out2102
-rw-r--r--src/test/regress/expected/foreign_key.out2568
-rw-r--r--src/test/regress/expected/functional_deps.out232
-rw-r--r--src/test/regress/expected/generated.out1065
-rw-r--r--src/test/regress/expected/geometry.out5200
-rw-r--r--src/test/regress/expected/gin.out290
-rw-r--r--src/test/regress/expected/gist.out394
-rw-r--r--src/test/regress/expected/groupingsets.out2152
-rw-r--r--src/test/regress/expected/guc.out815
-rw-r--r--src/test/regress/expected/hash_func.out374
-rw-r--r--src/test/regress/expected/hash_index.out243
-rw-r--r--src/test/regress/expected/hash_part.out114
-rw-r--r--src/test/regress/expected/horology.out3344
-rw-r--r--src/test/regress/expected/hs_standby_allowed.out218
-rw-r--r--src/test/regress/expected/hs_standby_check.out20
-rw-r--r--src/test/regress/expected/hs_standby_disallowed.out133
-rw-r--r--src/test/regress/expected/hs_standby_functions.out40
-rw-r--r--src/test/regress/expected/identity.out562
-rw-r--r--src/test/regress/expected/incremental_sort.out1676
-rw-r--r--src/test/regress/expected/index_including.out400
-rw-r--r--src/test/regress/expected/index_including_gist.out166
-rw-r--r--src/test/regress/expected/indexing.out1358
-rw-r--r--src/test/regress/expected/indirect_toast.out158
-rw-r--r--src/test/regress/expected/inet.out1058
-rw-r--r--src/test/regress/expected/infinite_recurse.out24
-rw-r--r--src/test/regress/expected/infinite_recurse_1.out16
-rw-r--r--src/test/regress/expected/inherit.out2532
-rw-r--r--src/test/regress/expected/init_privs.out12
-rw-r--r--src/test/regress/expected/insert.out1004
-rw-r--r--src/test/regress/expected/insert_conflict.out866
-rw-r--r--src/test/regress/expected/int2.out312
-rw-r--r--src/test/regress/expected/int4.out439
-rw-r--r--src/test/regress/expected/int8.out934
-rw-r--r--src/test/regress/expected/interval.out1045
-rw-r--r--src/test/regress/expected/join.out6628
-rw-r--r--src/test/regress/expected/join_hash.out1015
-rw-r--r--src/test/regress/expected/json.out2638
-rw-r--r--src/test/regress/expected/json_encoding.out262
-rw-r--r--src/test/regress/expected/json_encoding_1.out246
-rw-r--r--src/test/regress/expected/json_encoding_2.out9
-rw-r--r--src/test/regress/expected/jsonb.out5511
-rw-r--r--src/test/regress/expected/jsonb_jsonpath.out2539
-rw-r--r--src/test/regress/expected/jsonpath.out964
-rw-r--r--src/test/regress/expected/jsonpath_encoding.out180
-rw-r--r--src/test/regress/expected/jsonpath_encoding_1.out168
-rw-r--r--src/test/regress/expected/jsonpath_encoding_2.out9
-rw-r--r--src/test/regress/expected/limit.out694
-rw-r--r--src/test/regress/expected/line.out87
-rw-r--r--src/test/regress/expected/lock.out178
-rw-r--r--src/test/regress/expected/lseg.out44
-rw-r--r--src/test/regress/expected/macaddr.out160
-rw-r--r--src/test/regress/expected/macaddr8.out354
-rw-r--r--src/test/regress/expected/matview.out678
-rw-r--r--src/test/regress/expected/memoize.out283
-rw-r--r--src/test/regress/expected/misc_functions.out276
-rw-r--r--src/test/regress/expected/misc_sanity.out140
-rw-r--r--src/test/regress/expected/money.out505
-rw-r--r--src/test/regress/expected/multirangetypes.out3232
-rw-r--r--src/test/regress/expected/mvcc.out42
-rw-r--r--src/test/regress/expected/name.out197
-rw-r--r--src/test/regress/expected/namespace.out71
-rw-r--r--src/test/regress/expected/numeric.out3226
-rw-r--r--src/test/regress/expected/numeric_big.out2082
-rw-r--r--src/test/regress/expected/numerology.out136
-rw-r--r--src/test/regress/expected/object_address.out623
-rw-r--r--src/test/regress/expected/oid.out122
-rw-r--r--src/test/regress/expected/oidjoins.out266
-rw-r--r--src/test/regress/expected/opr_sanity.out2266
-rw-r--r--src/test/regress/expected/partition_aggregate.out1519
-rw-r--r--src/test/regress/expected/partition_info.out351
-rw-r--r--src/test/regress/expected/partition_join.out4864
-rw-r--r--src/test/regress/expected/partition_prune.out3865
-rw-r--r--src/test/regress/expected/password.out144
-rw-r--r--src/test/regress/expected/path.out82
-rw-r--r--src/test/regress/expected/pg_lsn.out257
-rw-r--r--src/test/regress/expected/plancache.out400
-rw-r--r--src/test/regress/expected/plpgsql.out5728
-rw-r--r--src/test/regress/expected/point.out475
-rw-r--r--src/test/regress/expected/polygon.out308
-rw-r--r--src/test/regress/expected/polymorphism.out2098
-rw-r--r--src/test/regress/expected/portals.out1520
-rw-r--r--src/test/regress/expected/portals_p2.out122
-rw-r--r--src/test/regress/expected/prepare.out189
-rw-r--r--src/test/regress/expected/prepared_xacts.out270
-rw-r--r--src/test/regress/expected/prepared_xacts_1.out266
-rw-r--r--src/test/regress/expected/privileges.out2493
-rw-r--r--src/test/regress/expected/psql.out5985
-rw-r--r--src/test/regress/expected/psql_crosstab.out216
-rw-r--r--src/test/regress/expected/publication.out344
-rw-r--r--src/test/regress/expected/random.out53
-rw-r--r--src/test/regress/expected/rangefuncs.out2447
-rw-r--r--src/test/regress/expected/rangetypes.out1770
-rw-r--r--src/test/regress/expected/regex.linux.utf8.out164
-rw-r--r--src/test/regress/expected/regex.out645
-rw-r--r--src/test/regress/expected/regproc.out437
-rw-r--r--src/test/regress/expected/reindex_catalog.out48
-rw-r--r--src/test/regress/expected/reloptions.out226
-rw-r--r--src/test/regress/expected/replica_identity.out233
-rw-r--r--src/test/regress/expected/returning.out357
-rw-r--r--src/test/regress/expected/roleattributes.out249
-rw-r--r--src/test/regress/expected/rowsecurity.out4042
-rw-r--r--src/test/regress/expected/rowtypes.out1242
-rw-r--r--src/test/regress/expected/rules.out3701
-rw-r--r--src/test/regress/expected/sanity_check.out248
-rw-r--r--src/test/regress/expected/security_label.out44
-rw-r--r--src/test/regress/expected/select.out973
-rw-r--r--src/test/regress/expected/select_distinct.out308
-rw-r--r--src/test/regress/expected/select_distinct_on.out75
-rw-r--r--src/test/regress/expected/select_having.out93
-rw-r--r--src/test/regress/expected/select_having_1.out93
-rw-r--r--src/test/regress/expected/select_having_2.out93
-rw-r--r--src/test/regress/expected/select_implicit.out338
-rw-r--r--src/test/regress/expected/select_implicit_1.out338
-rw-r--r--src/test/regress/expected/select_implicit_2.out338
-rw-r--r--src/test/regress/expected/select_into.out222
-rw-r--r--src/test/regress/expected/select_parallel.out1221
-rw-r--r--src/test/regress/expected/select_views.out1552
-rw-r--r--src/test/regress/expected/sequence.out824
-rw-r--r--src/test/regress/expected/spgist.out88
-rw-r--r--src/test/regress/expected/stats.out211
-rw-r--r--src/test/regress/expected/stats_ext.out3256
-rw-r--r--src/test/regress/expected/strings.out2272
-rw-r--r--src/test/regress/expected/subscription.out269
-rw-r--r--src/test/regress/expected/subselect.out1859
-rw-r--r--src/test/regress/expected/sysviews.out159
-rw-r--r--src/test/regress/expected/tablesample.out331
-rw-r--r--src/test/regress/expected/temp.out392
-rw-r--r--src/test/regress/expected/text.out440
-rw-r--r--src/test/regress/expected/tid.out76
-rw-r--r--src/test/regress/expected/tidrangescan.out300
-rw-r--r--src/test/regress/expected/tidscan.out296
-rw-r--r--src/test/regress/expected/time.out200
-rw-r--r--src/test/regress/expected/timestamp.out2081
-rw-r--r--src/test/regress/expected/timestamptz.out2991
-rw-r--r--src/test/regress/expected/timetz.out233
-rw-r--r--src/test/regress/expected/transactions.out1064
-rw-r--r--src/test/regress/expected/triggers.out3444
-rw-r--r--src/test/regress/expected/truncate.out594
-rw-r--r--src/test/regress/expected/tsdicts.out689
-rw-r--r--src/test/regress/expected/tsearch.out2853
-rw-r--r--src/test/regress/expected/tsrf.out712
-rw-r--r--src/test/regress/expected/tstypes.out1391
-rw-r--r--src/test/regress/expected/tuplesort.out686
-rw-r--r--src/test/regress/expected/txid.out327
-rw-r--r--src/test/regress/expected/type_sanity.out778
-rw-r--r--src/test/regress/expected/typed_table.out133
-rw-r--r--src/test/regress/expected/unicode.out89
-rw-r--r--src/test/regress/expected/unicode_1.out3
-rw-r--r--src/test/regress/expected/union.out1434
-rw-r--r--src/test/regress/expected/updatable_views.out3056
-rw-r--r--src/test/regress/expected/update.out1028
-rw-r--r--src/test/regress/expected/uuid.out159
-rw-r--r--src/test/regress/expected/vacuum.out415
-rw-r--r--src/test/regress/expected/vacuum_parallel.out49
-rw-r--r--src/test/regress/expected/varchar.out111
-rw-r--r--src/test/regress/expected/varchar_1.out111
-rw-r--r--src/test/regress/expected/varchar_2.out111
-rw-r--r--src/test/regress/expected/window.out4065
-rw-r--r--src/test/regress/expected/with.out3215
-rw-r--r--src/test/regress/expected/write_parallel.out80
-rw-r--r--src/test/regress/expected/xid.out462
-rw-r--r--src/test/regress/expected/xml.out1570
-rw-r--r--src/test/regress/expected/xml_1.out1417
-rw-r--r--src/test/regress/expected/xml_2.out1550
-rw-r--r--src/test/regress/expected/xmlmap.out1305
-rw-r--r--src/test/regress/expected/xmlmap_1.out135
-rw-r--r--src/test/regress/input/constraints.source554
-rw-r--r--src/test/regress/input/copy.source265
-rw-r--r--src/test/regress/input/create_function_0.source95
-rw-r--r--src/test/regress/input/create_function_1.source25
-rw-r--r--src/test/regress/input/create_function_2.source88
-rw-r--r--src/test/regress/input/largeobject.source274
-rw-r--r--src/test/regress/input/misc.source262
-rw-r--r--src/test/regress/input/tablespace.source411
-rw-r--r--src/test/regress/output/constraints.source738
-rw-r--r--src/test/regress/output/copy.source221
-rw-r--r--src/test/regress/output/create_function_0.source88
-rw-r--r--src/test/regress/output/create_function_1.source26
-rw-r--r--src/test/regress/output/create_function_2.source69
-rw-r--r--src/test/regress/output/largeobject.source492
-rw-r--r--src/test/regress/output/largeobject_1.source492
-rw-r--r--src/test/regress/output/misc.source692
-rw-r--r--src/test/regress/output/tablespace.source936
-rw-r--r--src/test/regress/parallel_schedule133
-rw-r--r--src/test/regress/pg_regress.c2697
-rw-r--r--src/test/regress/pg_regress.h70
-rw-r--r--src/test/regress/pg_regress_main.c126
-rw-r--r--src/test/regress/regress.c1208
-rwxr-xr-xsrc/test/regress/regressplans.sh101
-rw-r--r--src/test/regress/resultmap3
-rw-r--r--src/test/regress/sql/.gitignore9
-rw-r--r--src/test/regress/sql/advisory_lock.sql146
-rw-r--r--src/test/regress/sql/aggregates.sql1247
-rw-r--r--src/test/regress/sql/alter_generic.sql605
-rw-r--r--src/test/regress/sql/alter_operator.sql100
-rw-r--r--src/test/regress/sql/alter_table.sql3030
-rw-r--r--src/test/regress/sql/amutils.sql99
-rw-r--r--src/test/regress/sql/arrays.sql740
-rw-r--r--src/test/regress/sql/async.sql23
-rw-r--r--src/test/regress/sql/bit.sql231
-rw-r--r--src/test/regress/sql/bitmapops.sql41
-rw-r--r--src/test/regress/sql/boolean.sql262
-rw-r--r--src/test/regress/sql/box.sql283
-rw-r--r--src/test/regress/sql/brin.sql511
-rw-r--r--src/test/regress/sql/brin_bloom.sql376
-rw-r--r--src/test/regress/sql/brin_multi.sql416
-rw-r--r--src/test/regress/sql/btree_index.sql185
-rw-r--r--src/test/regress/sql/case.sql265
-rw-r--r--src/test/regress/sql/char.sql75
-rw-r--r--src/test/regress/sql/circle.sql57
-rw-r--r--src/test/regress/sql/cluster.sql280
-rw-r--r--src/test/regress/sql/collate.icu.utf8.sql758
-rw-r--r--src/test/regress/sql/collate.linux.utf8.sql455
-rw-r--r--src/test/regress/sql/collate.sql281
-rw-r--r--src/test/regress/sql/combocid.sql111
-rw-r--r--src/test/regress/sql/comments.sql42
-rw-r--r--src/test/regress/sql/compression.sql153
-rw-r--r--src/test/regress/sql/conversion.sql221
-rw-r--r--src/test/regress/sql/copy2.sql470
-rw-r--r--src/test/regress/sql/copydml.sql91
-rw-r--r--src/test/regress/sql/copyselect.sql96
-rw-r--r--src/test/regress/sql/create_aggregate.sql330
-rw-r--r--src/test/regress/sql/create_am.sql214
-rw-r--r--src/test/regress/sql/create_cast.sql54
-rw-r--r--src/test/regress/sql/create_function_3.sql391
-rw-r--r--src/test/regress/sql/create_index.sql1242
-rw-r--r--src/test/regress/sql/create_index_spgist.sql429
-rw-r--r--src/test/regress/sql/create_misc.sql217
-rw-r--r--src/test/regress/sql/create_operator.sql234
-rw-r--r--src/test/regress/sql/create_procedure.sql253
-rw-r--r--src/test/regress/sql/create_table.sql977
-rw-r--r--src/test/regress/sql/create_table_like.sql217
-rw-r--r--src/test/regress/sql/create_type.sql235
-rw-r--r--src/test/regress/sql/create_view.sql690
-rw-r--r--src/test/regress/sql/date.sql366
-rw-r--r--src/test/regress/sql/dbsize.sql67
-rw-r--r--src/test/regress/sql/delete.sql25
-rw-r--r--src/test/regress/sql/dependency.sql116
-rw-r--r--src/test/regress/sql/domain.sql788
-rw-r--r--src/test/regress/sql/drop_if_exists.sql304
-rw-r--r--src/test/regress/sql/drop_operator.sql56
-rw-r--r--src/test/regress/sql/enum.sql341
-rw-r--r--src/test/regress/sql/equivclass.sql271
-rw-r--r--src/test/regress/sql/errors.sql370
-rw-r--r--src/test/regress/sql/event_trigger.sql468
-rw-r--r--src/test/regress/sql/explain.sql108
-rw-r--r--src/test/regress/sql/expressions.sql205
-rw-r--r--src/test/regress/sql/fast_default.sql562
-rw-r--r--src/test/regress/sql/float4.sql354
-rw-r--r--src/test/regress/sql/float8.sql500
-rw-r--r--src/test/regress/sql/foreign_data.sql862
-rw-r--r--src/test/regress/sql/foreign_key.sql1831
-rw-r--r--src/test/regress/sql/functional_deps.sql210
-rw-r--r--src/test/regress/sql/generated.sql581
-rw-r--r--src/test/regress/sql/geometry.sql512
-rw-r--r--src/test/regress/sql/gin.sql173
-rw-r--r--src/test/regress/sql/gist.sql177
-rw-r--r--src/test/regress/sql/groupingsets.sql590
-rw-r--r--src/test/regress/sql/guc.sql313
-rw-r--r--src/test/regress/sql/hash_func.sql264
-rw-r--r--src/test/regress/sql/hash_index.sql204
-rw-r--r--src/test/regress/sql/hash_part.sql90
-rw-r--r--src/test/regress/sql/horology.sql570
-rw-r--r--src/test/regress/sql/hs_primary_extremes.sql73
-rw-r--r--src/test/regress/sql/hs_primary_setup.sql25
-rw-r--r--src/test/regress/sql/hs_standby_allowed.sql125
-rw-r--r--src/test/regress/sql/hs_standby_check.sql16
-rw-r--r--src/test/regress/sql/hs_standby_disallowed.sql103
-rw-r--r--src/test/regress/sql/hs_standby_functions.sql24
-rw-r--r--src/test/regress/sql/identity.sql357
-rw-r--r--src/test/regress/sql/incremental_sort.sql283
-rw-r--r--src/test/regress/sql/index_including.sql219
-rw-r--r--src/test/regress/sql/index_including_gist.sql90
-rw-r--r--src/test/regress/sql/indexing.sql749
-rw-r--r--src/test/regress/sql/indirect_toast.sql71
-rw-r--r--src/test/regress/sql/inet.sql254
-rw-r--r--src/test/regress/sql/infinite_recurse.sql29
-rw-r--r--src/test/regress/sql/inherit.sql988
-rw-r--r--src/test/regress/sql/init_privs.sql10
-rw-r--r--src/test/regress/sql/insert.sql626
-rw-r--r--src/test/regress/sql/insert_conflict.sql582
-rw-r--r--src/test/regress/sql/int2.sql118
-rw-r--r--src/test/regress/sql/int4.sql178
-rw-r--r--src/test/regress/sql/int8.sql252
-rw-r--r--src/test/regress/sql/interval.sql357
-rw-r--r--src/test/regress/sql/join.sql2325
-rw-r--r--src/test/regress/sql/join_hash.sql540
-rw-r--r--src/test/regress/sql/json.sql852
-rw-r--r--src/test/regress/sql/json_encoding.sql78
-rw-r--r--src/test/regress/sql/jsonb.sql1484
-rw-r--r--src/test/regress/sql/jsonb_jsonpath.sql587
-rw-r--r--src/test/regress/sql/jsonpath.sql181
-rw-r--r--src/test/regress/sql/jsonpath_encoding.sql59
-rw-r--r--src/test/regress/sql/limit.sql201
-rw-r--r--src/test/regress/sql/line.sql42
-rw-r--r--src/test/regress/sql/lock.sql139
-rw-r--r--src/test/regress/sql/lseg.sql24
-rw-r--r--src/test/regress/sql/macaddr.sql43
-rw-r--r--src/test/regress/sql/macaddr8.sql89
-rw-r--r--src/test/regress/sql/matview.sql297
-rw-r--r--src/test/regress/sql/memoize.sql152
-rw-r--r--src/test/regress/sql/misc_functions.sql102
-rw-r--r--src/test/regress/sql/misc_sanity.sql120
-rw-r--r--src/test/regress/sql/money.sql131
-rw-r--r--src/test/regress/sql/multirangetypes.sql835
-rw-r--r--src/test/regress/sql/mvcc.sql44
-rw-r--r--src/test/regress/sql/name.sql87
-rw-r--r--src/test/regress/sql/namespace.sql44
-rw-r--r--src/test/regress/sql/numeric.sql1365
-rw-r--r--src/test/regress/sql/numeric_big.sql1366
-rw-r--r--src/test/regress/sql/numerology.sql98
-rw-r--r--src/test/regress/sql/object_address.sql287
-rw-r--r--src/test/regress/sql/oid.sql43
-rw-r--r--src/test/regress/sql/oidjoins.sql49
-rw-r--r--src/test/regress/sql/opr_sanity.sql1393
-rw-r--r--src/test/regress/sql/partition_aggregate.sql333
-rw-r--r--src/test/regress/sql/partition_info.sql129
-rw-r--r--src/test/regress/sql/partition_join.sql1144
-rw-r--r--src/test/regress/sql/partition_prune.sql1189
-rw-r--r--src/test/regress/sql/password.sql109
-rw-r--r--src/test/regress/sql/path.sql44
-rw-r--r--src/test/regress/sql/pg_lsn.sql56
-rw-r--r--src/test/regress/sql/plancache.sql225
-rw-r--r--src/test/regress/sql/plpgsql.sql4669
-rw-r--r--src/test/regress/sql/point.sql118
-rw-r--r--src/test/regress/sql/polygon.sql142
-rw-r--r--src/test/regress/sql/polymorphism.sql1137
-rw-r--r--src/test/regress/sql/portals.sql571
-rw-r--r--src/test/regress/sql/portals_p2.sql98
-rw-r--r--src/test/regress/sql/prepare.sql80
-rw-r--r--src/test/regress/sql/prepared_xacts.sql164
-rw-r--r--src/test/regress/sql/privileges.sql1550
-rw-r--r--src/test/regress/sql/psql.sql1485
-rw-r--r--src/test/regress/sql/psql_crosstab.sql124
-rw-r--r--src/test/regress/sql/publication.sql218
-rw-r--r--src/test/regress/sql/random.sql44
-rw-r--r--src/test/regress/sql/rangefuncs.sql784
-rw-r--r--src/test/regress/sql/rangetypes.sql620
-rw-r--r--src/test/regress/sql/regex.linux.utf8.sql46
-rw-r--r--src/test/regress/sql/regex.sql158
-rw-r--r--src/test/regress/sql/regproc.sql122
-rw-r--r--src/test/regress/sql/reindex_catalog.sql52
-rw-r--r--src/test/regress/sql/reloptions.sql132
-rw-r--r--src/test/regress/sql/replica_identity.sql104
-rw-r--r--src/test/regress/sql/returning.sql162
-rw-r--r--src/test/regress/sql/roleattributes.sql98
-rw-r--r--src/test/regress/sql/rowsecurity.sql1873
-rw-r--r--src/test/regress/sql/rowtypes.sql506
-rw-r--r--src/test/regress/sql/rules.sql1259
-rw-r--r--src/test/regress/sql/sanity_check.sql39
-rw-r--r--src/test/regress/sql/security_label.sql45
-rw-r--r--src/test/regress/sql/select.sql269
-rw-r--r--src/test/regress/sql/select_distinct.sql137
-rw-r--r--src/test/regress/sql/select_distinct_on.sql19
-rw-r--r--src/test/regress/sql/select_having.sql50
-rw-r--r--src/test/regress/sql/select_implicit.sql156
-rw-r--r--src/test/regress/sql/select_into.sql138
-rw-r--r--src/test/regress/sql/select_parallel.sql464
-rw-r--r--src/test/regress/sql/select_views.sql155
-rw-r--r--src/test/regress/sql/sequence.sql408
-rw-r--r--src/test/regress/sql/spgist.sql82
-rw-r--r--src/test/regress/sql/stats.sql185
-rw-r--r--src/test/regress/sql/stats_ext.sql1655
-rw-r--r--src/test/regress/sql/strings.sql761
-rw-r--r--src/test/regress/sql/subscription.sql208
-rw-r--r--src/test/regress/sql/subselect.sql939
-rw-r--r--src/test/regress/sql/sysviews.sql65
-rw-r--r--src/test/regress/sql/tablesample.sql110
-rw-r--r--src/test/regress/sql/temp.sql297
-rw-r--r--src/test/regress/sql/text.sql118
-rw-r--r--src/test/regress/sql/tid.sql54
-rw-r--r--src/test/regress/sql/tidrangescan.sql101
-rw-r--r--src/test/regress/sql/tidscan.sql104
-rw-r--r--src/test/regress/sql/time.sql72
-rw-r--r--src/test/regress/sql/timestamp.sql386
-rw-r--r--src/test/regress/sql/timestamptz.sql567
-rw-r--r--src/test/regress/sql/timetz.sql79
-rw-r--r--src/test/regress/sql/transactions.sql604
-rw-r--r--src/test/regress/sql/triggers.sql2588
-rw-r--r--src/test/regress/sql/truncate.sql329
-rw-r--r--src/test/regress/sql/tsdicts.sql253
-rw-r--r--src/test/regress/sql/tsearch.sql795
-rw-r--r--src/test/regress/sql/tsrf.sql185
-rw-r--r--src/test/regress/sql/tstypes.sql268
-rw-r--r--src/test/regress/sql/tuplesort.sql298
-rw-r--r--src/test/regress/sql/txid.sql102
-rw-r--r--src/test/regress/sql/type_sanity.sql597
-rw-r--r--src/test/regress/sql/typed_table.sql75
-rw-r--r--src/test/regress/sql/unicode.sql34
-rw-r--r--src/test/regress/sql/union.sql542
-rw-r--r--src/test/regress/sql/updatable_views.sql1534
-rw-r--r--src/test/regress/sql/update.sql669
-rw-r--r--src/test/regress/sql/uuid.sql85
-rw-r--r--src/test/regress/sql/vacuum.sql320
-rw-r--r--src/test/regress/sql/vacuum_parallel.sql46
-rw-r--r--src/test/regress/sql/varchar.sql66
-rw-r--r--src/test/regress/sql/window.sql1330
-rw-r--r--src/test/regress/sql/with.sql1506
-rw-r--r--src/test/regress/sql/write_parallel.sql43
-rw-r--r--src/test/regress/sql/xid.sql152
-rw-r--r--src/test/regress/sql/xml.sql619
-rw-r--r--src/test/regress/sql/xmlmap.sql63
-rw-r--r--src/test/regress/standby_schedule21
-rw-r--r--src/test/ssl/.gitignore3
-rw-r--r--src/test/ssl/Makefile192
-rw-r--r--src/test/ssl/README102
-rw-r--r--src/test/ssl/cas.config64
-rw-r--r--src/test/ssl/client-dn.config16
-rw-r--r--src/test/ssl/client.config13
-rw-r--r--src/test/ssl/client_ca.config11
-rw-r--r--src/test/ssl/root_ca.config13
-rw-r--r--src/test/ssl/server-cn-and-alt-names.config25
-rw-r--r--src/test/ssl/server-cn-only.config13
-rw-r--r--src/test/ssl/server-multiple-alt-names.config20
-rw-r--r--src/test/ssl/server-no-names.config16
-rw-r--r--src/test/ssl/server-revoked.config15
-rw-r--r--src/test/ssl/server-single-alt-name.config18
-rw-r--r--src/test/ssl/server_ca.config11
-rw-r--r--src/test/ssl/ssl/.gitignore3
-rw-r--r--src/test/ssl/ssl/both-cas-1.crt57
-rw-r--r--src/test/ssl/ssl/both-cas-2.crt57
-rw-r--r--src/test/ssl/ssl/client+client_ca.crt36
-rw-r--r--src/test/ssl/ssl/client-crldir/9bb9e3c3.r011
-rw-r--r--src/test/ssl/ssl/client-der.keybin0 -> 1191 bytes
-rw-r--r--src/test/ssl/ssl/client-dn.crt19
-rw-r--r--src/test/ssl/ssl/client-dn.key27
-rw-r--r--src/test/ssl/ssl/client-encrypted-der.keybin0 -> 1191 bytes
-rw-r--r--src/test/ssl/ssl/client-encrypted-pem.key30
-rw-r--r--src/test/ssl/ssl/client-revoked.crt17
-rw-r--r--src/test/ssl/ssl/client-revoked.key27
-rw-r--r--src/test/ssl/ssl/client.crl11
-rw-r--r--src/test/ssl/ssl/client.crt17
-rw-r--r--src/test/ssl/ssl/client.key27
-rw-r--r--src/test/ssl/ssl/client_ca.crt19
-rw-r--r--src/test/ssl/ssl/client_ca.key27
-rw-r--r--src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r011
-rw-r--r--src/test/ssl/ssl/root+client-crldir/a3d11bff.r011
-rw-r--r--src/test/ssl/ssl/root+client.crl22
-rw-r--r--src/test/ssl/ssl/root+client_ca.crt38
-rw-r--r--src/test/ssl/ssl/root+server-crldir/a3d11bff.r011
-rw-r--r--src/test/ssl/ssl/root+server-crldir/a836cc2d.r011
-rw-r--r--src/test/ssl/ssl/root+server.crl22
-rw-r--r--src/test/ssl/ssl/root+server_ca.crt38
-rw-r--r--src/test/ssl/ssl/root.crl11
-rw-r--r--src/test/ssl/ssl/root_ca.crt19
-rw-r--r--src/test/ssl/ssl/root_ca.key27
-rw-r--r--src/test/ssl/ssl/server-cn-and-alt-names.crt20
-rw-r--r--src/test/ssl/ssl/server-cn-and-alt-names.key27
-rw-r--r--src/test/ssl/ssl/server-cn-only.crt18
-rw-r--r--src/test/ssl/ssl/server-cn-only.key27
-rw-r--r--src/test/ssl/ssl/server-crldir/a836cc2d.r011
-rw-r--r--src/test/ssl/ssl/server-multiple-alt-names.crt20
-rw-r--r--src/test/ssl/ssl/server-multiple-alt-names.key27
-rw-r--r--src/test/ssl/ssl/server-no-names.crt18
-rw-r--r--src/test/ssl/ssl/server-no-names.key27
-rw-r--r--src/test/ssl/ssl/server-password.key30
-rw-r--r--src/test/ssl/ssl/server-revoked.crt18
-rw-r--r--src/test/ssl/ssl/server-revoked.key27
-rw-r--r--src/test/ssl/ssl/server-single-alt-name.crt19
-rw-r--r--src/test/ssl/ssl/server-single-alt-name.key27
-rw-r--r--src/test/ssl/ssl/server-ss.crt19
-rw-r--r--src/test/ssl/ssl/server-ss.key27
-rw-r--r--src/test/ssl/ssl/server.crl11
-rw-r--r--src/test/ssl/ssl/server_ca.crt19
-rw-r--r--src/test/ssl/ssl/server_ca.key27
-rw-r--r--src/test/ssl/t/001_ssltests.pl600
-rw-r--r--src/test/ssl/t/002_scram.pl119
-rw-r--r--src/test/ssl/t/SSLServer.pm205
-rw-r--r--src/test/subscription/.gitignore2
-rw-r--r--src/test/subscription/Makefile27
-rw-r--r--src/test/subscription/README23
-rw-r--r--src/test/subscription/t/001_rep_changes.pl539
-rw-r--r--src/test/subscription/t/002_types.pl568
-rw-r--r--src/test/subscription/t/003_constraints.pl139
-rw-r--r--src/test/subscription/t/004_sync.pl180
-rw-r--r--src/test/subscription/t/005_encoding.pl55
-rw-r--r--src/test/subscription/t/006_rewrite.pl68
-rw-r--r--src/test/subscription/t/007_ddl.pl45
-rw-r--r--src/test/subscription/t/008_diff_schema.pl128
-rw-r--r--src/test/subscription/t/009_matviews.pl52
-rw-r--r--src/test/subscription/t/010_truncate.pl241
-rw-r--r--src/test/subscription/t/011_generated.pl66
-rw-r--r--src/test/subscription/t/012_collation.pl110
-rw-r--r--src/test/subscription/t/013_partition.pl872
-rw-r--r--src/test/subscription/t/014_binary.pl137
-rw-r--r--src/test/subscription/t/015_stream.pl135
-rw-r--r--src/test/subscription/t/016_stream_subxact.pl93
-rw-r--r--src/test/subscription/t/017_stream_ddl.pl129
-rw-r--r--src/test/subscription/t/018_stream_subxact_abort.pl133
-rw-r--r--src/test/subscription/t/019_stream_subxact_ddl_abort.pl87
-rw-r--r--src/test/subscription/t/020_messages.pl148
-rw-r--r--src/test/subscription/t/021_alter_sub_pub.pl98
-rw-r--r--src/test/subscription/t/100_bugs.pl308
1227 files changed, 496806 insertions, 0 deletions
diff --git a/src/test/Makefile b/src/test/Makefile
new file mode 100644
index 0000000..4627591
--- /dev/null
+++ b/src/test/Makefile
@@ -0,0 +1,52 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test
+#
+# Copyright (c) 1994, Regents of the University of California
+#
+# src/test/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+
+SUBDIRS = perl regress isolation modules authentication recovery subscription
+
+# Test suites that are not safe by default but can be run if selected
+# by the user via the whitespace-separated list in variable
+# PG_TEST_EXTRA:
+ifeq ($(with_gssapi),yes)
+ifneq (,$(filter kerberos,$(PG_TEST_EXTRA)))
+SUBDIRS += kerberos
+endif
+endif
+ifeq ($(with_ldap),yes)
+ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
+SUBDIRS += ldap
+endif
+endif
+ifeq ($(with_ssl),openssl)
+ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
+SUBDIRS += ssl
+endif
+endif
+
+# We don't build or execute these by default, but we do want "make
+# clean" etc to recurse into them. (We must filter out those that we
+# have conditionally included into SUBDIRS above, else there will be
+# make confusion.)
+ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos ldap ssl)
+
+# We want to recurse to all subdirs for all standard targets, except that
+# installcheck and install should not recurse into the subdirectory "modules".
+
+recurse_alldirs_targets := $(filter-out installcheck install, $(standard_targets))
+installable_dirs := $(filter-out modules, $(SUBDIRS))
+
+$(call recurse,$(recurse_alldirs_targets))
+$(call recurse,installcheck, $(installable_dirs))
+$(call recurse,install, $(installable_dirs))
+
+$(recurse_always)
diff --git a/src/test/README b/src/test/README
new file mode 100644
index 0000000..afdc767
--- /dev/null
+++ b/src/test/README
@@ -0,0 +1,50 @@
+PostgreSQL tests
+================
+
+This directory contains a variety of test infrastructure as well as some of the
+tests in PostgreSQL. Not all tests are here -- in particular, there are more in
+individual contrib/ modules and in src/bin.
+
+Not all these tests get run by "make check". Check src/test/Makefile to see
+which tests get run automatically.
+
+authentication/
+ Tests for authentication (but see also below)
+
+examples/
+ Demonstration programs for libpq that double as regression tests via
+ "make check"
+
+isolation/
+ Tests for concurrent behavior at the SQL level
+
+kerberos/
+ Tests for Kerberos/GSSAPI authentication and encryption
+
+ldap/
+ Tests for LDAP-based authentication
+
+locale/
+ Sanity checks for locale data, encodings, etc
+
+mb/
+ Tests for multibyte encoding (UTF-8) support
+
+modules/
+ Extensions used only or mainly for test purposes, generally not suitable
+ for installing in production databases
+
+perl/
+ Infrastructure for Perl-based TAP tests
+
+recovery/
+ Test suite for recovery and replication
+
+regress/
+ PostgreSQL's main regression test suite, pg_regress
+
+ssl/
+ Tests to exercise and verify SSL certificate handling
+
+subscription/
+ Tests for logical replication
diff --git a/src/test/authentication/.gitignore b/src/test/authentication/.gitignore
new file mode 100644
index 0000000..871e943
--- /dev/null
+++ b/src/test/authentication/.gitignore
@@ -0,0 +1,2 @@
+# Generated by test suite
+/tmp_check/
diff --git a/src/test/authentication/Makefile b/src/test/authentication/Makefile
new file mode 100644
index 0000000..9aa331e
--- /dev/null
+++ b/src/test/authentication/Makefile
@@ -0,0 +1,23 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/authentication
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/authentication/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/authentication
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
diff --git a/src/test/authentication/README b/src/test/authentication/README
new file mode 100644
index 0000000..a8f27bf
--- /dev/null
+++ b/src/test/authentication/README
@@ -0,0 +1,26 @@
+src/test/authentication/README
+
+Regression tests for authentication
+===================================
+
+This directory contains a test suite for authentication. SSL certificate
+authentication tests are kept separate, in src/test/ssl/, because they
+are more complicated, and are not safe to run in a multi-user system.
+
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+
+Run
+ make check
+or
+ make installcheck
+You can use "make installcheck" if you previously did "make install".
+In that case, the code in the installation tree is tested. With
+"make check", a temporary installation tree is built from the current
+sources and then tested.
+
+Either way, this test initializes, starts, and stops a test Postgres
+cluster.
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
new file mode 100644
index 0000000..1296c53
--- /dev/null
+++ b/src/test/authentication/t/001_password.pl
@@ -0,0 +1,162 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Set of tests for authentication and pg_hba.conf. The following password
+# methods are checked through this test:
+# - Plain
+# - MD5-encrypted
+# - SCRAM-encrypted
+# This test can only run with Unix-domain sockets.
+
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More;
+if (!$use_unix_sockets)
+{
+ plan skip_all =>
+ "authentication tests cannot run without Unix-domain sockets";
+}
+else
+{
+ plan tests => 23;
+}
+
+
+# Delete pg_hba.conf from the given node, add a new entry to it
+# and then execute a reload to refresh it.
+sub reset_pg_hba
+{
+ my $node = shift;
+ my $hba_method = shift;
+
+ unlink($node->data_dir . '/pg_hba.conf');
+ # just for testing purposes, use a continuation line
+ $node->append_conf('pg_hba.conf', "local all all\\\n $hba_method");
+ $node->reload;
+ return;
+}
+
+# Test access for a single role, useful to wrap all tests into one. Extra
+# named parameters are passed to connect_ok/fails as-is.
+sub test_role
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($node, $role, $method, $expected_res, %params) = @_;
+ my $status_string = 'failed';
+ $status_string = 'success' if ($expected_res eq 0);
+
+ my $connstr = "user=$role";
+ my $testname =
+ "authentication $status_string for method $method, role $role";
+
+ if ($expected_res eq 0)
+ {
+ $node->connect_ok($connstr, $testname, %params);
+ }
+ else
+ {
+ # No checks of the error message, only the status code.
+ $node->connect_fails($connstr, $testname, %params);
+ }
+}
+
+# Initialize primary node
+my $node = get_new_node('primary');
+$node->init;
+$node->append_conf('postgresql.conf', "log_connections = on\n");
+$node->start;
+
+# Create 3 roles with different password methods for each one. The same
+# password is used for all of them.
+$node->safe_psql('postgres',
+ "SET password_encryption='scram-sha-256'; CREATE ROLE scram_role LOGIN PASSWORD 'pass';"
+);
+$node->safe_psql('postgres',
+ "SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
+);
+$ENV{"PGPASSWORD"} = 'pass';
+
+# For "trust" method, all users should be able to connect. These users are not
+# considered to be authenticated.
+reset_pg_hba($node, 'trust');
+test_role($node, 'scram_role', 'trust', 0,
+ log_unlike => [qr/connection authenticated:/]);
+test_role($node, 'md5_role', 'trust', 0,
+ log_unlike => [qr/connection authenticated:/]);
+
+# For plain "password" method, all users should also be able to connect.
+reset_pg_hba($node, 'password');
+test_role($node, 'scram_role', 'password', 0,
+ log_like =>
+ [qr/connection authenticated: identity="scram_role" method=password/]);
+test_role($node, 'md5_role', 'password', 0,
+ log_like =>
+ [qr/connection authenticated: identity="md5_role" method=password/]);
+
+# For "scram-sha-256" method, user "scram_role" should be able to connect.
+reset_pg_hba($node, 'scram-sha-256');
+test_role(
+ $node,
+ 'scram_role',
+ 'scram-sha-256',
+ 0,
+ log_like => [
+ qr/connection authenticated: identity="scram_role" method=scram-sha-256/
+ ]);
+test_role($node, 'md5_role', 'scram-sha-256', 2,
+ log_unlike => [qr/connection authenticated:/]);
+
+# Test that bad passwords are rejected.
+$ENV{"PGPASSWORD"} = 'badpass';
+test_role($node, 'scram_role', 'scram-sha-256', 2,
+ log_unlike => [qr/connection authenticated:/]);
+$ENV{"PGPASSWORD"} = 'pass';
+
+# For "md5" method, all users should be able to connect (SCRAM
+# authentication will be performed for the user with a SCRAM secret.)
+reset_pg_hba($node, 'md5');
+test_role($node, 'scram_role', 'md5', 0,
+ log_like =>
+ [qr/connection authenticated: identity="scram_role" method=md5/]);
+test_role($node, 'md5_role', 'md5', 0,
+ log_like =>
+ [qr/connection authenticated: identity="md5_role" method=md5/]);
+
+# Tests for channel binding without SSL.
+# Using the password authentication method; channel binding can't work
+reset_pg_hba($node, 'password');
+$ENV{"PGCHANNELBINDING"} = 'require';
+test_role($node, 'scram_role', 'scram-sha-256', 2);
+# SSL not in use; channel binding still can't work
+reset_pg_hba($node, 'scram-sha-256');
+$ENV{"PGCHANNELBINDING"} = 'require';
+test_role($node, 'scram_role', 'scram-sha-256', 2);
+
+# Test .pgpass processing; but use a temp file, don't overwrite the real one!
+my $pgpassfile = "${TestLib::tmp_check}/pgpass";
+
+delete $ENV{"PGPASSWORD"};
+delete $ENV{"PGCHANNELBINDING"};
+$ENV{"PGPASSFILE"} = $pgpassfile;
+
+unlink($pgpassfile);
+append_to_file(
+ $pgpassfile, qq!
+# This very long comment is just here to exercise handling of long lines in the file. This very long comment is just here to exercise handling of long lines in the file. This very long comment is just here to exercise handling of long lines in the file. This very long comment is just here to exercise handling of long lines in the file. This very long comment is just here to exercise handling of long lines in the file.
+*:*:postgres:scram_role:pass:this is not part of the password.
+!);
+chmod 0600, $pgpassfile or die;
+
+reset_pg_hba($node, 'password');
+test_role($node, 'scram_role', 'password from pgpass', 0);
+test_role($node, 'md5_role', 'password from pgpass', 2);
+
+append_to_file(
+ $pgpassfile, qq!
+*:*:*:md5_role:p\\ass
+!);
+
+test_role($node, 'md5_role', 'password from pgpass', 0);
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
new file mode 100644
index 0000000..5b8b4b2
--- /dev/null
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -0,0 +1,119 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test password normalization in SCRAM.
+#
+# This test can only run with Unix-domain sockets.
+
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More;
+if (!$use_unix_sockets)
+{
+ plan skip_all =>
+ "authentication tests cannot run without Unix-domain sockets";
+}
+else
+{
+ plan tests => 12;
+}
+
+# Delete pg_hba.conf from the given node, add a new entry to it
+# and then execute a reload to refresh it.
+sub reset_pg_hba
+{
+ my $node = shift;
+ my $hba_method = shift;
+
+ unlink($node->data_dir . '/pg_hba.conf');
+ $node->append_conf('pg_hba.conf', "local all all $hba_method");
+ $node->reload;
+ return;
+}
+
+# Test access for a single role, useful to wrap all tests into one.
+sub test_login
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $node = shift;
+ my $role = shift;
+ my $password = shift;
+ my $expected_res = shift;
+ my $status_string = 'failed';
+
+ $status_string = 'success' if ($expected_res eq 0);
+
+ my $connstr = "user=$role";
+ my $testname =
+ "authentication $status_string for role $role with password $password";
+
+ $ENV{"PGPASSWORD"} = $password;
+ if ($expected_res eq 0)
+ {
+ $node->connect_ok($connstr, $testname);
+ }
+ else
+ {
+ # No checks of the error message, only the status code.
+ $node->connect_fails($connstr, $testname);
+ }
+}
+
+# Initialize primary node. Force UTF-8 encoding, so that we can use non-ASCII
+# characters in the passwords below.
+my $node = get_new_node('primary');
+$node->init(extra => [ '--locale=C', '--encoding=UTF8' ]);
+$node->start;
+
+# These tests are based on the example strings from RFC4013.txt,
+# Section "3. Examples":
+#
+# # Input Output Comments
+# - ----- ------ --------
+# 1 I<U+00AD>X IX SOFT HYPHEN mapped to nothing
+# 2 user user no transformation
+# 3 USER USER case preserved, will not match #2
+# 4 <U+00AA> a output is NFKC, input in ISO 8859-1
+# 5 <U+2168> IX output is NFKC, will match #1
+# 6 <U+0007> Error - prohibited character
+# 7 <U+0627><U+0031> Error - bidirectional check
+
+# Create test roles.
+$node->safe_psql(
+ 'postgres',
+ "SET password_encryption='scram-sha-256';
+SET client_encoding='utf8';
+CREATE ROLE saslpreptest1_role LOGIN PASSWORD 'IX';
+CREATE ROLE saslpreptest4a_role LOGIN PASSWORD 'a';
+CREATE ROLE saslpreptest4b_role LOGIN PASSWORD E'\\xc2\\xaa';
+CREATE ROLE saslpreptest6_role LOGIN PASSWORD E'foo\\x07bar';
+CREATE ROLE saslpreptest7_role LOGIN PASSWORD E'foo\\u0627\\u0031bar';
+");
+
+# Require password from now on.
+reset_pg_hba($node, 'scram-sha-256');
+
+# Check that #1 and #5 are treated the same as just 'IX'
+test_login($node, 'saslpreptest1_role', "I\xc2\xadX", 0);
+test_login($node, 'saslpreptest1_role', "\xe2\x85\xa8", 0);
+
+# but different from lower case 'ix'
+test_login($node, 'saslpreptest1_role', "ix", 2);
+
+# Check #4
+test_login($node, 'saslpreptest4a_role', "a", 0);
+test_login($node, 'saslpreptest4a_role', "\xc2\xaa", 0);
+test_login($node, 'saslpreptest4b_role', "a", 0);
+test_login($node, 'saslpreptest4b_role', "\xc2\xaa", 0);
+
+# Check #6 and #7 - In PostgreSQL, contrary to the spec, if the password
+# contains prohibited characters, we use it as is, without normalization.
+test_login($node, 'saslpreptest6_role', "foo\x07bar", 0);
+test_login($node, 'saslpreptest6_role', "foobar", 2);
+
+test_login($node, 'saslpreptest7_role', "foo\xd8\xa71bar", 0);
+test_login($node, 'saslpreptest7_role', "foo1\xd8\xa7bar", 2);
+test_login($node, 'saslpreptest7_role', "foobar", 2);
diff --git a/src/test/examples/.gitignore b/src/test/examples/.gitignore
new file mode 100644
index 0000000..1957ec1
--- /dev/null
+++ b/src/test/examples/.gitignore
@@ -0,0 +1,6 @@
+/testlibpq
+/testlibpq2
+/testlibpq3
+/testlibpq4
+/testlo
+/testlo64
diff --git a/src/test/examples/Makefile b/src/test/examples/Makefile
new file mode 100644
index 0000000..a67f456
--- /dev/null
+++ b/src/test/examples/Makefile
@@ -0,0 +1,22 @@
+#
+# Makefile for example programs
+#
+
+subdir = src/test/examples
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+ifeq ($(PORTNAME), win32)
+LDFLAGS += -lws2_32
+endif
+
+override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
+LDFLAGS_INTERNAL += $(libpq_pgport)
+
+
+PROGS = testlibpq testlibpq2 testlibpq3 testlibpq4 testlo testlo64
+
+all: $(PROGS)
+
+clean distclean maintainer-clean:
+ rm -f $(PROGS) *.o
diff --git a/src/test/examples/testlibpq.c b/src/test/examples/testlibpq.c
new file mode 100644
index 0000000..0372781
--- /dev/null
+++ b/src/test/examples/testlibpq.c
@@ -0,0 +1,131 @@
+/*
+ * src/test/examples/testlibpq.c
+ *
+ *
+ * testlibpq.c
+ *
+ * Test the C version of libpq, the PostgreSQL frontend library.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include "libpq-fe.h"
+
+static void
+exit_nicely(PGconn *conn)
+{
+ PQfinish(conn);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *conninfo;
+ PGconn *conn;
+ PGresult *res;
+ int nFields;
+ int i,
+ j;
+
+ /*
+ * If the user supplies a parameter on the command line, use it as the
+ * conninfo string; otherwise default to setting dbname=postgres and using
+ * environment variables or defaults for all other connection parameters.
+ */
+ if (argc > 1)
+ conninfo = argv[1];
+ else
+ conninfo = "dbname = postgres";
+
+ /* Make a connection to the database */
+ conn = PQconnectdb(conninfo);
+
+ /* Check to see that the backend connection was successfully made */
+ if (PQstatus(conn) != CONNECTION_OK)
+ {
+ fprintf(stderr, "%s", PQerrorMessage(conn));
+ exit_nicely(conn);
+ }
+
+ /* Set always-secure search path, so malicious users can't take control. */
+ res = PQexec(conn,
+ "SELECT pg_catalog.set_config('search_path', '', false)");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+
+ /*
+ * Should PQclear PGresult whenever it is no longer needed to avoid memory
+ * leaks
+ */
+ PQclear(res);
+
+ /*
+ * Our test case here involves using a cursor, for which we must be inside
+ * a transaction block. We could do the whole thing with a single
+ * PQexec() of "select * from pg_database", but that's too trivial to make
+ * a good example.
+ */
+
+ /* Start a transaction block */
+ res = PQexec(conn, "BEGIN");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "BEGIN command failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+ PQclear(res);
+
+ /*
+ * Fetch rows from pg_database, the system catalog of databases
+ */
+ res = PQexec(conn, "DECLARE myportal CURSOR FOR select * from pg_database");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "DECLARE CURSOR failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+ PQclear(res);
+
+ res = PQexec(conn, "FETCH ALL in myportal");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "FETCH ALL failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+
+ /* first, print out the attribute names */
+ nFields = PQnfields(res);
+ for (i = 0; i < nFields; i++)
+ printf("%-15s", PQfname(res, i));
+ printf("\n\n");
+
+ /* next, print out the rows */
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ for (j = 0; j < nFields; j++)
+ printf("%-15s", PQgetvalue(res, i, j));
+ printf("\n");
+ }
+
+ PQclear(res);
+
+ /* close the portal ... we don't bother to check for errors ... */
+ res = PQexec(conn, "CLOSE myportal");
+ PQclear(res);
+
+ /* end the transaction */
+ res = PQexec(conn, "END");
+ PQclear(res);
+
+ /* close the connection to the database and cleanup */
+ PQfinish(conn);
+
+ return 0;
+}
diff --git a/src/test/examples/testlibpq2.c b/src/test/examples/testlibpq2.c
new file mode 100644
index 0000000..6337b31
--- /dev/null
+++ b/src/test/examples/testlibpq2.c
@@ -0,0 +1,152 @@
+/*
+ * src/test/examples/testlibpq2.c
+ *
+ *
+ * testlibpq2.c
+ * Test of the asynchronous notification interface
+ *
+ * Start this program, then from psql in another window do
+ * NOTIFY TBL2;
+ * Repeat four times to get this program to exit.
+ *
+ * Or, if you want to get fancy, try this:
+ * populate a database with the following commands
+ * (provided in src/test/examples/testlibpq2.sql):
+ *
+ * CREATE SCHEMA TESTLIBPQ2;
+ * SET search_path = TESTLIBPQ2;
+ * CREATE TABLE TBL1 (i int4);
+ * CREATE TABLE TBL2 (i int4);
+ * CREATE RULE r1 AS ON INSERT TO TBL1 DO
+ * (INSERT INTO TBL2 VALUES (new.i); NOTIFY TBL2);
+ *
+ * Start this program, then from psql do this four times:
+ *
+ * INSERT INTO TESTLIBPQ2.TBL1 VALUES (10);
+ */
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#include "libpq-fe.h"
+
+static void
+exit_nicely(PGconn *conn)
+{
+ PQfinish(conn);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *conninfo;
+ PGconn *conn;
+ PGresult *res;
+ PGnotify *notify;
+ int nnotifies;
+
+ /*
+ * If the user supplies a parameter on the command line, use it as the
+ * conninfo string; otherwise default to setting dbname=postgres and using
+ * environment variables or defaults for all other connection parameters.
+ */
+ if (argc > 1)
+ conninfo = argv[1];
+ else
+ conninfo = "dbname = postgres";
+
+ /* Make a connection to the database */
+ conn = PQconnectdb(conninfo);
+
+ /* Check to see that the backend connection was successfully made */
+ if (PQstatus(conn) != CONNECTION_OK)
+ {
+ fprintf(stderr, "%s", PQerrorMessage(conn));
+ exit_nicely(conn);
+ }
+
+ /* Set always-secure search path, so malicious users can't take control. */
+ res = PQexec(conn,
+ "SELECT pg_catalog.set_config('search_path', '', false)");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+
+ /*
+ * Should PQclear PGresult whenever it is no longer needed to avoid memory
+ * leaks
+ */
+ PQclear(res);
+
+ /*
+ * Issue LISTEN command to enable notifications from the rule's NOTIFY.
+ */
+ res = PQexec(conn, "LISTEN TBL2");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "LISTEN command failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+ PQclear(res);
+
+ /* Quit after four notifies are received. */
+ nnotifies = 0;
+ while (nnotifies < 4)
+ {
+ /*
+ * Sleep until something happens on the connection. We use select(2)
+ * to wait for input, but you could also use poll() or similar
+ * facilities.
+ */
+ int sock;
+ fd_set input_mask;
+
+ sock = PQsocket(conn);
+
+ if (sock < 0)
+ break; /* shouldn't happen */
+
+ FD_ZERO(&input_mask);
+ FD_SET(sock, &input_mask);
+
+ if (select(sock + 1, &input_mask, NULL, NULL, NULL) < 0)
+ {
+ fprintf(stderr, "select() failed: %s\n", strerror(errno));
+ exit_nicely(conn);
+ }
+
+ /* Now check for input */
+ PQconsumeInput(conn);
+ while ((notify = PQnotifies(conn)) != NULL)
+ {
+ fprintf(stderr,
+ "ASYNC NOTIFY of '%s' received from backend PID %d\n",
+ notify->relname, notify->be_pid);
+ PQfreemem(notify);
+ nnotifies++;
+ PQconsumeInput(conn);
+ }
+ }
+
+ fprintf(stderr, "Done.\n");
+
+ /* close the connection to the database and cleanup */
+ PQfinish(conn);
+
+ return 0;
+}
diff --git a/src/test/examples/testlibpq2.sql b/src/test/examples/testlibpq2.sql
new file mode 100644
index 0000000..e8173e4
--- /dev/null
+++ b/src/test/examples/testlibpq2.sql
@@ -0,0 +1,6 @@
+CREATE SCHEMA TESTLIBPQ2;
+SET search_path = TESTLIBPQ2;
+CREATE TABLE TBL1 (i int4);
+CREATE TABLE TBL2 (i int4);
+CREATE RULE r1 AS ON INSERT TO TBL1 DO
+ (INSERT INTO TBL2 VALUES (new.i); NOTIFY TBL2);
diff --git a/src/test/examples/testlibpq3.c b/src/test/examples/testlibpq3.c
new file mode 100644
index 0000000..4f7b791
--- /dev/null
+++ b/src/test/examples/testlibpq3.c
@@ -0,0 +1,231 @@
+/*
+ * src/test/examples/testlibpq3.c
+ *
+ *
+ * testlibpq3.c
+ * Test out-of-line parameters and binary I/O.
+ *
+ * Before running this, populate a database with the following commands
+ * (provided in src/test/examples/testlibpq3.sql):
+ *
+ * CREATE SCHEMA testlibpq3;
+ * SET search_path = testlibpq3;
+ * SET standard_conforming_strings = ON;
+ * CREATE TABLE test1 (i int4, t text, b bytea);
+ * INSERT INTO test1 values (1, 'joe''s place', '\000\001\002\003\004');
+ * INSERT INTO test1 values (2, 'ho there', '\004\003\002\001\000');
+ *
+ * The expected output is:
+ *
+ * tuple 0: got
+ * i = (4 bytes) 1
+ * t = (11 bytes) 'joe's place'
+ * b = (5 bytes) \000\001\002\003\004
+ *
+ * tuple 0: got
+ * i = (4 bytes) 2
+ * t = (8 bytes) 'ho there'
+ * b = (5 bytes) \004\003\002\001\000
+ */
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+#include "libpq-fe.h"
+
+/* for ntohl/htonl */
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+static void
+exit_nicely(PGconn *conn)
+{
+ PQfinish(conn);
+ exit(1);
+}
+
+/*
+ * This function prints a query result that is a binary-format fetch from
+ * a table defined as in the comment above. We split it out because the
+ * main() function uses it twice.
+ */
+static void
+show_binary_results(PGresult *res)
+{
+ int i,
+ j;
+ int i_fnum,
+ t_fnum,
+ b_fnum;
+
+ /* Use PQfnumber to avoid assumptions about field order in result */
+ i_fnum = PQfnumber(res, "i");
+ t_fnum = PQfnumber(res, "t");
+ b_fnum = PQfnumber(res, "b");
+
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ char *iptr;
+ char *tptr;
+ char *bptr;
+ int blen;
+ int ival;
+
+ /* Get the field values (we ignore possibility they are null!) */
+ iptr = PQgetvalue(res, i, i_fnum);
+ tptr = PQgetvalue(res, i, t_fnum);
+ bptr = PQgetvalue(res, i, b_fnum);
+
+ /*
+ * The binary representation of INT4 is in network byte order, which
+ * we'd better coerce to the local byte order.
+ */
+ ival = ntohl(*((uint32_t *) iptr));
+
+ /*
+ * The binary representation of TEXT is, well, text, and since libpq
+ * was nice enough to append a zero byte to it, it'll work just fine
+ * as a C string.
+ *
+ * The binary representation of BYTEA is a bunch of bytes, which could
+ * include embedded nulls so we have to pay attention to field length.
+ */
+ blen = PQgetlength(res, i, b_fnum);
+
+ printf("tuple %d: got\n", i);
+ printf(" i = (%d bytes) %d\n",
+ PQgetlength(res, i, i_fnum), ival);
+ printf(" t = (%d bytes) '%s'\n",
+ PQgetlength(res, i, t_fnum), tptr);
+ printf(" b = (%d bytes) ", blen);
+ for (j = 0; j < blen; j++)
+ printf("\\%03o", bptr[j]);
+ printf("\n\n");
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *conninfo;
+ PGconn *conn;
+ PGresult *res;
+ const char *paramValues[1];
+ int paramLengths[1];
+ int paramFormats[1];
+ uint32_t binaryIntVal;
+
+ /*
+ * If the user supplies a parameter on the command line, use it as the
+ * conninfo string; otherwise default to setting dbname=postgres and using
+ * environment variables or defaults for all other connection parameters.
+ */
+ if (argc > 1)
+ conninfo = argv[1];
+ else
+ conninfo = "dbname = postgres";
+
+ /* Make a connection to the database */
+ conn = PQconnectdb(conninfo);
+
+ /* Check to see that the backend connection was successfully made */
+ if (PQstatus(conn) != CONNECTION_OK)
+ {
+ fprintf(stderr, "%s", PQerrorMessage(conn));
+ exit_nicely(conn);
+ }
+
+ /* Set always-secure search path, so malicious users can't take control. */
+ res = PQexec(conn, "SET search_path = testlibpq3");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+ PQclear(res);
+
+ /*
+ * The point of this program is to illustrate use of PQexecParams() with
+ * out-of-line parameters, as well as binary transmission of data.
+ *
+ * This first example transmits the parameters as text, but receives the
+ * results in binary format. By using out-of-line parameters we can avoid
+ * a lot of tedious mucking about with quoting and escaping, even though
+ * the data is text. Notice how we don't have to do anything special with
+ * the quote mark in the parameter value.
+ */
+
+ /* Here is our out-of-line parameter value */
+ paramValues[0] = "joe's place";
+
+ res = PQexecParams(conn,
+ "SELECT * FROM test1 WHERE t = $1",
+ 1, /* one param */
+ NULL, /* let the backend deduce param type */
+ paramValues,
+ NULL, /* don't need param lengths since text */
+ NULL, /* default to all text params */
+ 1); /* ask for binary results */
+
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "SELECT failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+
+ show_binary_results(res);
+
+ PQclear(res);
+
+ /*
+ * In this second example we transmit an integer parameter in binary form,
+ * and again retrieve the results in binary form.
+ *
+ * Although we tell PQexecParams we are letting the backend deduce
+ * parameter type, we really force the decision by casting the parameter
+ * symbol in the query text. This is a good safety measure when sending
+ * binary parameters.
+ */
+
+ /* Convert integer value "2" to network byte order */
+ binaryIntVal = htonl((uint32_t) 2);
+
+ /* Set up parameter arrays for PQexecParams */
+ paramValues[0] = (char *) &binaryIntVal;
+ paramLengths[0] = sizeof(binaryIntVal);
+ paramFormats[0] = 1; /* binary */
+
+ res = PQexecParams(conn,
+ "SELECT * FROM test1 WHERE i = $1::int4",
+ 1, /* one param */
+ NULL, /* let the backend deduce param type */
+ paramValues,
+ paramLengths,
+ paramFormats,
+ 1); /* ask for binary results */
+
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "SELECT failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+
+ show_binary_results(res);
+
+ PQclear(res);
+
+ /* close the connection to the database and cleanup */
+ PQfinish(conn);
+
+ return 0;
+}
diff --git a/src/test/examples/testlibpq3.sql b/src/test/examples/testlibpq3.sql
new file mode 100644
index 0000000..35a95ca
--- /dev/null
+++ b/src/test/examples/testlibpq3.sql
@@ -0,0 +1,6 @@
+CREATE SCHEMA testlibpq3;
+SET search_path = testlibpq3;
+SET standard_conforming_strings = ON;
+CREATE TABLE test1 (i int4, t text, b bytea);
+INSERT INTO test1 values (1, 'joe''s place', '\000\001\002\003\004');
+INSERT INTO test1 values (2, 'ho there', '\004\003\002\001\000');
diff --git a/src/test/examples/testlibpq4.c b/src/test/examples/testlibpq4.c
new file mode 100644
index 0000000..da44430
--- /dev/null
+++ b/src/test/examples/testlibpq4.c
@@ -0,0 +1,163 @@
+/*
+ * src/test/examples/testlibpq4.c
+ *
+ *
+ * testlibpq4.c
+ * this test program shows to use LIBPQ to make multiple backend
+ * connections
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include "libpq-fe.h"
+
+static void
+exit_nicely(PGconn *conn1, PGconn *conn2)
+{
+ if (conn1)
+ PQfinish(conn1);
+ if (conn2)
+ PQfinish(conn2);
+ exit(1);
+}
+
+static void
+check_prepare_conn(PGconn *conn, const char *dbName)
+{
+ PGresult *res;
+
+ /* check to see that the backend connection was successfully made */
+ if (PQstatus(conn) != CONNECTION_OK)
+ {
+ fprintf(stderr, "%s", PQerrorMessage(conn));
+ exit(1);
+ }
+
+ /* Set always-secure search path, so malicious users can't take control. */
+ res = PQexec(conn,
+ "SELECT pg_catalog.set_config('search_path', '', false)");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit(1);
+ }
+ PQclear(res);
+}
+
+int
+main(int argc, char **argv)
+{
+ char *pghost,
+ *pgport,
+ *pgoptions;
+ char *dbName1,
+ *dbName2;
+ char *tblName;
+ int nFields;
+ int i,
+ j;
+
+ PGconn *conn1,
+ *conn2;
+
+ /*
+ * PGresult *res1, *res2;
+ */
+ PGresult *res1;
+
+ if (argc != 4)
+ {
+ fprintf(stderr, "usage: %s tableName dbName1 dbName2\n", argv[0]);
+ fprintf(stderr, " compares two tables in two databases\n");
+ exit(1);
+ }
+ tblName = argv[1];
+ dbName1 = argv[2];
+ dbName2 = argv[3];
+
+
+ /*
+ * begin, by setting the parameters for a backend connection if the
+ * parameters are null, then the system will try to use reasonable
+ * defaults by looking up environment variables or, failing that, using
+ * hardwired constants
+ */
+ pghost = NULL; /* host name of the backend */
+ pgport = NULL; /* port of the backend */
+ pgoptions = NULL; /* special options to start up the backend
+ * server */
+
+ /* make a connection to the database */
+ conn1 = PQsetdb(pghost, pgport, pgoptions, NULL, dbName1);
+ check_prepare_conn(conn1, dbName1);
+
+ conn2 = PQsetdb(pghost, pgport, pgoptions, NULL, dbName2);
+ check_prepare_conn(conn2, dbName2);
+
+ /* start a transaction block */
+ res1 = PQexec(conn1, "BEGIN");
+ if (PQresultStatus(res1) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "BEGIN command failed\n");
+ PQclear(res1);
+ exit_nicely(conn1, conn2);
+ }
+
+ /*
+ * make sure to PQclear() a PGresult whenever it is no longer needed to
+ * avoid memory leaks
+ */
+ PQclear(res1);
+
+ /*
+ * fetch instances from the pg_database, the system catalog of databases
+ */
+ res1 = PQexec(conn1, "DECLARE myportal CURSOR FOR select * from pg_database");
+ if (PQresultStatus(res1) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "DECLARE CURSOR command failed\n");
+ PQclear(res1);
+ exit_nicely(conn1, conn2);
+ }
+ PQclear(res1);
+
+ res1 = PQexec(conn1, "FETCH ALL in myportal");
+ if (PQresultStatus(res1) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "FETCH ALL command didn't return tuples properly\n");
+ PQclear(res1);
+ exit_nicely(conn1, conn2);
+ }
+
+ /* first, print out the attribute names */
+ nFields = PQnfields(res1);
+ for (i = 0; i < nFields; i++)
+ printf("%-15s", PQfname(res1, i));
+ printf("\n\n");
+
+ /* next, print out the instances */
+ for (i = 0; i < PQntuples(res1); i++)
+ {
+ for (j = 0; j < nFields; j++)
+ printf("%-15s", PQgetvalue(res1, i, j));
+ printf("\n");
+ }
+
+ PQclear(res1);
+
+ /* close the portal */
+ res1 = PQexec(conn1, "CLOSE myportal");
+ PQclear(res1);
+
+ /* end the transaction */
+ res1 = PQexec(conn1, "END");
+ PQclear(res1);
+
+ /* close the connections to the database and cleanup */
+ PQfinish(conn1);
+ PQfinish(conn2);
+
+/* fclose(debug); */
+ return 0;
+}
diff --git a/src/test/examples/testlo.c b/src/test/examples/testlo.c
new file mode 100644
index 0000000..6d91681
--- /dev/null
+++ b/src/test/examples/testlo.c
@@ -0,0 +1,270 @@
+/*-------------------------------------------------------------------------
+ *
+ * testlo.c
+ * test using large objects with libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/test/examples/testlo.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "libpq-fe.h"
+#include "libpq/libpq-fs.h"
+
+#define BUFSIZE 1024
+
+/*
+ * importFile -
+ * import file "in_filename" into database as large object "lobjOid"
+ *
+ */
+static Oid
+importFile(PGconn *conn, char *filename)
+{
+ Oid lobjId;
+ int lobj_fd;
+ char buf[BUFSIZE];
+ int nbytes,
+ tmp;
+ int fd;
+
+ /*
+ * open the file to be read in
+ */
+ fd = open(filename, O_RDONLY, 0666);
+ if (fd < 0)
+ { /* error */
+ fprintf(stderr, "cannot open unix file\"%s\"\n", filename);
+ }
+
+ /*
+ * create the large object
+ */
+ lobjId = lo_creat(conn, INV_READ | INV_WRITE);
+ if (lobjId == 0)
+ fprintf(stderr, "cannot create large object");
+
+ lobj_fd = lo_open(conn, lobjId, INV_WRITE);
+
+ /*
+ * read in from the Unix file and write to the inversion file
+ */
+ while ((nbytes = read(fd, buf, BUFSIZE)) > 0)
+ {
+ tmp = lo_write(conn, lobj_fd, buf, nbytes);
+ if (tmp < nbytes)
+ fprintf(stderr, "error while reading \"%s\"", filename);
+ }
+
+ close(fd);
+ lo_close(conn, lobj_fd);
+
+ return lobjId;
+}
+
+static void
+pickout(PGconn *conn, Oid lobjId, int start, int len)
+{
+ int lobj_fd;
+ char *buf;
+ int nbytes;
+ int nread;
+
+ lobj_fd = lo_open(conn, lobjId, INV_READ);
+ if (lobj_fd < 0)
+ fprintf(stderr, "cannot open large object %u", lobjId);
+
+ lo_lseek(conn, lobj_fd, start, SEEK_SET);
+ buf = malloc(len + 1);
+
+ nread = 0;
+ while (len - nread > 0)
+ {
+ nbytes = lo_read(conn, lobj_fd, buf, len - nread);
+ buf[nbytes] = '\0';
+ fprintf(stderr, ">>> %s", buf);
+ nread += nbytes;
+ if (nbytes <= 0)
+ break; /* no more data? */
+ }
+ free(buf);
+ fprintf(stderr, "\n");
+ lo_close(conn, lobj_fd);
+}
+
+static void
+overwrite(PGconn *conn, Oid lobjId, int start, int len)
+{
+ int lobj_fd;
+ char *buf;
+ int nbytes;
+ int nwritten;
+ int i;
+
+ lobj_fd = lo_open(conn, lobjId, INV_WRITE);
+ if (lobj_fd < 0)
+ fprintf(stderr, "cannot open large object %u", lobjId);
+
+ lo_lseek(conn, lobj_fd, start, SEEK_SET);
+ buf = malloc(len + 1);
+
+ for (i = 0; i < len; i++)
+ buf[i] = 'X';
+ buf[i] = '\0';
+
+ nwritten = 0;
+ while (len - nwritten > 0)
+ {
+ nbytes = lo_write(conn, lobj_fd, buf + nwritten, len - nwritten);
+ nwritten += nbytes;
+ if (nbytes <= 0)
+ {
+ fprintf(stderr, "\nWRITE FAILED!\n");
+ break;
+ }
+ }
+ free(buf);
+ fprintf(stderr, "\n");
+ lo_close(conn, lobj_fd);
+}
+
+
+/*
+ * exportFile -
+ * export large object "lobjOid" to file "out_filename"
+ *
+ */
+static void
+exportFile(PGconn *conn, Oid lobjId, char *filename)
+{
+ int lobj_fd;
+ char buf[BUFSIZE];
+ int nbytes,
+ tmp;
+ int fd;
+
+ /*
+ * open the large object
+ */
+ lobj_fd = lo_open(conn, lobjId, INV_READ);
+ if (lobj_fd < 0)
+ fprintf(stderr, "cannot open large object %u", lobjId);
+
+ /*
+ * open the file to be written to
+ */
+ fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ if (fd < 0)
+ { /* error */
+ fprintf(stderr, "cannot open unix file\"%s\"",
+ filename);
+ }
+
+ /*
+ * read in from the inversion file and write to the Unix file
+ */
+ while ((nbytes = lo_read(conn, lobj_fd, buf, BUFSIZE)) > 0)
+ {
+ tmp = write(fd, buf, nbytes);
+ if (tmp < nbytes)
+ {
+ fprintf(stderr, "error while writing \"%s\"",
+ filename);
+ }
+ }
+
+ lo_close(conn, lobj_fd);
+ close(fd);
+}
+
+static void
+exit_nicely(PGconn *conn)
+{
+ PQfinish(conn);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ char *in_filename,
+ *out_filename;
+ char *database;
+ Oid lobjOid;
+ PGconn *conn;
+ PGresult *res;
+
+ if (argc != 4)
+ {
+ fprintf(stderr, "Usage: %s database_name in_filename out_filename\n",
+ argv[0]);
+ exit(1);
+ }
+
+ database = argv[1];
+ in_filename = argv[2];
+ out_filename = argv[3];
+
+ /*
+ * set up the connection
+ */
+ conn = PQsetdb(NULL, NULL, NULL, NULL, database);
+
+ /* check to see that the backend connection was successfully made */
+ if (PQstatus(conn) != CONNECTION_OK)
+ {
+ fprintf(stderr, "%s", PQerrorMessage(conn));
+ exit_nicely(conn);
+ }
+
+ /* Set always-secure search path, so malicious users can't take control. */
+ res = PQexec(conn,
+ "SELECT pg_catalog.set_config('search_path', '', false)");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+ PQclear(res);
+
+ res = PQexec(conn, "begin");
+ PQclear(res);
+ printf("importing file \"%s\" ...\n", in_filename);
+/* lobjOid = importFile(conn, in_filename); */
+ lobjOid = lo_import(conn, in_filename);
+ if (lobjOid == 0)
+ fprintf(stderr, "%s\n", PQerrorMessage(conn));
+ else
+ {
+ printf("\tas large object %u.\n", lobjOid);
+
+ printf("picking out bytes 1000-2000 of the large object\n");
+ pickout(conn, lobjOid, 1000, 1000);
+
+ printf("overwriting bytes 1000-2000 of the large object with X's\n");
+ overwrite(conn, lobjOid, 1000, 1000);
+
+ printf("exporting large object to file \"%s\" ...\n", out_filename);
+/* exportFile(conn, lobjOid, out_filename); */
+ if (lo_export(conn, lobjOid, out_filename) < 0)
+ fprintf(stderr, "%s\n", PQerrorMessage(conn));
+ }
+
+ res = PQexec(conn, "end");
+ PQclear(res);
+ PQfinish(conn);
+ return 0;
+}
diff --git a/src/test/examples/testlo64.c b/src/test/examples/testlo64.c
new file mode 100644
index 0000000..23e9109
--- /dev/null
+++ b/src/test/examples/testlo64.c
@@ -0,0 +1,301 @@
+/*-------------------------------------------------------------------------
+ *
+ * testlo64.c
+ * test using large objects with libpq using 64-bit APIs
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/test/examples/testlo64.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "libpq-fe.h"
+#include "libpq/libpq-fs.h"
+
+#define BUFSIZE 1024
+
+/*
+ * importFile -
+ * import file "in_filename" into database as large object "lobjOid"
+ *
+ */
+static Oid
+importFile(PGconn *conn, char *filename)
+{
+ Oid lobjId;
+ int lobj_fd;
+ char buf[BUFSIZE];
+ int nbytes,
+ tmp;
+ int fd;
+
+ /*
+ * open the file to be read in
+ */
+ fd = open(filename, O_RDONLY, 0666);
+ if (fd < 0)
+ { /* error */
+ fprintf(stderr, "cannot open unix file\"%s\"\n", filename);
+ }
+
+ /*
+ * create the large object
+ */
+ lobjId = lo_creat(conn, INV_READ | INV_WRITE);
+ if (lobjId == 0)
+ fprintf(stderr, "cannot create large object");
+
+ lobj_fd = lo_open(conn, lobjId, INV_WRITE);
+
+ /*
+ * read in from the Unix file and write to the inversion file
+ */
+ while ((nbytes = read(fd, buf, BUFSIZE)) > 0)
+ {
+ tmp = lo_write(conn, lobj_fd, buf, nbytes);
+ if (tmp < nbytes)
+ fprintf(stderr, "error while reading \"%s\"", filename);
+ }
+
+ close(fd);
+ lo_close(conn, lobj_fd);
+
+ return lobjId;
+}
+
+static void
+pickout(PGconn *conn, Oid lobjId, pg_int64 start, int len)
+{
+ int lobj_fd;
+ char *buf;
+ int nbytes;
+ int nread;
+
+ lobj_fd = lo_open(conn, lobjId, INV_READ);
+ if (lobj_fd < 0)
+ fprintf(stderr, "cannot open large object %u", lobjId);
+
+ if (lo_lseek64(conn, lobj_fd, start, SEEK_SET) < 0)
+ fprintf(stderr, "error in lo_lseek64: %s", PQerrorMessage(conn));
+
+ if (lo_tell64(conn, lobj_fd) != start)
+ fprintf(stderr, "error in lo_tell64: %s", PQerrorMessage(conn));
+
+ buf = malloc(len + 1);
+
+ nread = 0;
+ while (len - nread > 0)
+ {
+ nbytes = lo_read(conn, lobj_fd, buf, len - nread);
+ buf[nbytes] = '\0';
+ fprintf(stderr, ">>> %s", buf);
+ nread += nbytes;
+ if (nbytes <= 0)
+ break; /* no more data? */
+ }
+ free(buf);
+ fprintf(stderr, "\n");
+ lo_close(conn, lobj_fd);
+}
+
+static void
+overwrite(PGconn *conn, Oid lobjId, pg_int64 start, int len)
+{
+ int lobj_fd;
+ char *buf;
+ int nbytes;
+ int nwritten;
+ int i;
+
+ lobj_fd = lo_open(conn, lobjId, INV_WRITE);
+ if (lobj_fd < 0)
+ fprintf(stderr, "cannot open large object %u", lobjId);
+
+ if (lo_lseek64(conn, lobj_fd, start, SEEK_SET) < 0)
+ fprintf(stderr, "error in lo_lseek64: %s", PQerrorMessage(conn));
+
+ buf = malloc(len + 1);
+
+ for (i = 0; i < len; i++)
+ buf[i] = 'X';
+ buf[i] = '\0';
+
+ nwritten = 0;
+ while (len - nwritten > 0)
+ {
+ nbytes = lo_write(conn, lobj_fd, buf + nwritten, len - nwritten);
+ nwritten += nbytes;
+ if (nbytes <= 0)
+ {
+ fprintf(stderr, "\nWRITE FAILED!\n");
+ break;
+ }
+ }
+ free(buf);
+ fprintf(stderr, "\n");
+ lo_close(conn, lobj_fd);
+}
+
+static void
+my_truncate(PGconn *conn, Oid lobjId, pg_int64 len)
+{
+ int lobj_fd;
+
+ lobj_fd = lo_open(conn, lobjId, INV_READ | INV_WRITE);
+ if (lobj_fd < 0)
+ fprintf(stderr, "cannot open large object %u", lobjId);
+
+ if (lo_truncate64(conn, lobj_fd, len) < 0)
+ fprintf(stderr, "error in lo_truncate64: %s", PQerrorMessage(conn));
+
+ lo_close(conn, lobj_fd);
+}
+
+
+/*
+ * exportFile -
+ * export large object "lobjOid" to file "out_filename"
+ *
+ */
+static void
+exportFile(PGconn *conn, Oid lobjId, char *filename)
+{
+ int lobj_fd;
+ char buf[BUFSIZE];
+ int nbytes,
+ tmp;
+ int fd;
+
+ /*
+ * open the large object
+ */
+ lobj_fd = lo_open(conn, lobjId, INV_READ);
+ if (lobj_fd < 0)
+ fprintf(stderr, "cannot open large object %u", lobjId);
+
+ /*
+ * open the file to be written to
+ */
+ fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ if (fd < 0)
+ { /* error */
+ fprintf(stderr, "cannot open unix file\"%s\"",
+ filename);
+ }
+
+ /*
+ * read in from the inversion file and write to the Unix file
+ */
+ while ((nbytes = lo_read(conn, lobj_fd, buf, BUFSIZE)) > 0)
+ {
+ tmp = write(fd, buf, nbytes);
+ if (tmp < nbytes)
+ {
+ fprintf(stderr, "error while writing \"%s\"",
+ filename);
+ }
+ }
+
+ lo_close(conn, lobj_fd);
+ close(fd);
+}
+
+static void
+exit_nicely(PGconn *conn)
+{
+ PQfinish(conn);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ char *in_filename,
+ *out_filename,
+ *out_filename2;
+ char *database;
+ Oid lobjOid;
+ PGconn *conn;
+ PGresult *res;
+
+ if (argc != 5)
+ {
+ fprintf(stderr, "Usage: %s database_name in_filename out_filename out_filename2\n",
+ argv[0]);
+ exit(1);
+ }
+
+ database = argv[1];
+ in_filename = argv[2];
+ out_filename = argv[3];
+ out_filename2 = argv[4];
+
+ /*
+ * set up the connection
+ */
+ conn = PQsetdb(NULL, NULL, NULL, NULL, database);
+
+ /* check to see that the backend connection was successfully made */
+ if (PQstatus(conn) != CONNECTION_OK)
+ {
+ fprintf(stderr, "%s", PQerrorMessage(conn));
+ exit_nicely(conn);
+ }
+
+ /* Set always-secure search path, so malicious users can't take control. */
+ res = PQexec(conn,
+ "SELECT pg_catalog.set_config('search_path', '', false)");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+ PQclear(res);
+
+ res = PQexec(conn, "begin");
+ PQclear(res);
+ printf("importing file \"%s\" ...\n", in_filename);
+/* lobjOid = importFile(conn, in_filename); */
+ lobjOid = lo_import(conn, in_filename);
+ if (lobjOid == 0)
+ fprintf(stderr, "%s\n", PQerrorMessage(conn));
+ else
+ {
+ printf("\tas large object %u.\n", lobjOid);
+
+ printf("picking out bytes 4294967000-4294968000 of the large object\n");
+ pickout(conn, lobjOid, 4294967000U, 1000);
+
+ printf("overwriting bytes 4294967000-4294968000 of the large object with X's\n");
+ overwrite(conn, lobjOid, 4294967000U, 1000);
+
+ printf("exporting large object to file \"%s\" ...\n", out_filename);
+/* exportFile(conn, lobjOid, out_filename); */
+ if (lo_export(conn, lobjOid, out_filename) < 0)
+ fprintf(stderr, "%s\n", PQerrorMessage(conn));
+
+ printf("truncating to 3294968000 bytes\n");
+ my_truncate(conn, lobjOid, 3294968000U);
+
+ printf("exporting truncated large object to file \"%s\" ...\n", out_filename2);
+ if (lo_export(conn, lobjOid, out_filename2) < 0)
+ fprintf(stderr, "%s\n", PQerrorMessage(conn));
+ }
+
+ res = PQexec(conn, "end");
+ PQclear(res);
+ PQfinish(conn);
+ return 0;
+}
diff --git a/src/test/isolation/.gitignore b/src/test/isolation/.gitignore
new file mode 100644
index 0000000..870dac4
--- /dev/null
+++ b/src/test/isolation/.gitignore
@@ -0,0 +1,12 @@
+# Local binaries
+/isolationtester
+/pg_isolation_regress
+
+# Local generated source files
+/specparse.c
+/specscanner.c
+
+# Generated subdirectories
+/results/
+/output_iso/
+/tmp_check_iso/
diff --git a/src/test/isolation/Makefile b/src/test/isolation/Makefile
new file mode 100644
index 0000000..0d452c8
--- /dev/null
+++ b/src/test/isolation/Makefile
@@ -0,0 +1,73 @@
+#
+# Makefile for isolation tests
+#
+
+PGFILEDESC = "pg_isolation_regress/isolationtester - multi-client test driver"
+PGAPPICON = win32
+
+subdir = src/test/isolation
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) \
+ -I$(srcdir)/../regress $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES) \
+ isolationtester.o \
+ specparse.o
+
+all: isolationtester$(X) pg_isolation_regress$(X)
+
+install: all installdirs
+ $(INSTALL_PROGRAM) pg_isolation_regress$(X) '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_isolation_regress$(X)'
+ $(INSTALL_PROGRAM) isolationtester$(X) '$(DESTDIR)$(pgxsdir)/$(subdir)/isolationtester$(X)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(pgxsdir)/$(subdir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_isolation_regress$(X)'
+ rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/isolationtester$(X)'
+
+submake-regress:
+ $(MAKE) -C $(top_builddir)/src/test/regress pg_regress.o
+
+pg_regress.o: | submake-regress
+ rm -f $@ && $(LN_S) $(top_builddir)/src/test/regress/pg_regress.o .
+
+pg_isolation_regress$(X): isolation_main.o pg_regress.o $(WIN32RES)
+ $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@
+
+isolationtester$(X): $(OBJS) | submake-libpq submake-libpgport
+ $(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@
+
+distprep: specparse.c specscanner.c
+
+# specscanner is compiled as part of specparse
+specparse.o: specscanner.c
+
+# specparse.c and specscanner.c are in the distribution tarball,
+# so do not clean them here
+clean distclean:
+ rm -f isolationtester$(X) pg_isolation_regress$(X) $(OBJS) isolation_main.o
+ rm -f pg_regress.o
+ rm -rf $(pg_regress_clean_files)
+
+maintainer-clean: distclean
+ rm -f specparse.c specscanner.c
+
+installcheck: all
+ $(pg_isolation_regress_installcheck) --schedule=$(srcdir)/isolation_schedule
+
+check: all
+ $(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule
+
+# Non-default tests. It only makes sense to run these if set up to use
+# prepared transactions, via TEMP_CONFIG for the check case, or via the
+# postgresql.conf for the installcheck case.
+installcheck-prepared-txns: all temp-install
+ $(pg_isolation_regress_installcheck) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic
+
+check-prepared-txns: all temp-install
+ $(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic
diff --git a/src/test/isolation/README b/src/test/isolation/README
new file mode 100644
index 0000000..8457a56
--- /dev/null
+++ b/src/test/isolation/README
@@ -0,0 +1,214 @@
+src/test/isolation/README
+
+Isolation tests
+===============
+
+This directory contains a set of tests for concurrent behaviors in
+PostgreSQL. These tests require running multiple interacting transactions,
+which requires management of multiple concurrent connections, and therefore
+can't be tested using the normal pg_regress program. The name "isolation"
+comes from the fact that the original motivation was to test the
+serializable isolation level; but tests for other sorts of concurrent
+behaviors have been added as well.
+
+You can run the tests against the current build tree by typing
+ make check
+Alternatively, you can run against an existing installation by typing
+ make installcheck
+(This will contact a server at the default port expected by libpq.
+You can set PGPORT and so forth in your environment to control this.)
+
+To run just specific test(s) against an installed server,
+you can do something like
+ ./pg_isolation_regress fk-contention fk-deadlock
+(look into the specs/ subdirectory to see the available tests).
+
+Certain tests require the server's max_prepared_transactions parameter to be
+set to at least 3; therefore they are not run by default. To include them in
+the test run, use
+ make check-prepared-txns
+or
+ make installcheck-prepared-txns
+after making sure the server configuration is correct (see TEMP_CONFIG
+to adjust this in the "check" case).
+
+To define tests with overlapping transactions, we use test specification
+files with a custom syntax, which is described in the next section. To add
+a new test, place a spec file in the specs/ subdirectory, add the expected
+output in the expected/ subdirectory, and add the test's name to the
+isolation_schedule file.
+
+isolationtester is a program that uses libpq to open multiple connections,
+and executes a test specified by a spec file. A libpq connection string
+specifies the server and database to connect to; defaults derived from
+environment variables are used otherwise.
+
+pg_isolation_regress is a tool similar to pg_regress, but instead of using
+psql to execute a test, it uses isolationtester. It accepts all the same
+command-line arguments as pg_regress.
+
+By default, isolationtester will wait at most 300 seconds (5 minutes)
+for any one test step to complete. If you need to adjust this, set
+the environment variable PGISOLATIONTIMEOUT to the desired timeout
+in seconds.
+
+
+Test specification
+==================
+
+Each isolation test is defined by a specification file, stored in the specs
+subdirectory. A test specification defines some SQL "steps", groups them
+into "sessions" (where all the steps of one session will be run in the
+same backend), and specifies the "permutations" or orderings of the steps
+that are to be run.
+
+A test specification consists of four parts, in this order:
+
+setup { <SQL> }
+
+ The given SQL block is executed once (per permutation) before running
+ the test. Create any test tables or other required objects here. This
+ part is optional. Multiple setup blocks are allowed if needed; each is
+ run separately, in the given order. (The reason for allowing multiple
+ setup blocks is that each block is run as a single PQexec submission,
+ and some statements such as VACUUM cannot be combined with others in such
+ a block.)
+
+teardown { <SQL> }
+
+ The teardown SQL block is executed once after the test is finished. Use
+ this to clean up in preparation for the next permutation, e.g dropping
+ any test tables created by setup. This part is optional.
+
+session <name>
+
+ There are normally several "session" parts in a spec file. Each
+ session is executed in its own connection. A session part consists
+ of three parts: setup, teardown and one or more "steps". The per-session
+ setup and teardown parts have the same syntax as the per-test setup and
+ teardown described above, but they are executed in each session. The setup
+ part might, for example, contain a "BEGIN" command to begin a transaction.
+
+ Each step has the syntax
+
+ step <name> { <SQL> }
+
+ where <name> is a name identifying this step, and <SQL> is a SQL statement
+ (or statements, separated by semicolons) that is executed in the step.
+ Step names must be unique across the whole spec file.
+
+permutation <step name> ...
+
+ A permutation line specifies a list of steps that are run in that order.
+ Any number of permutation lines can appear. If no permutation lines are
+ given, the test program automatically runs all possible interleavings
+ of the steps from each session (running the steps of any one session in
+ order). Note that the list of steps in a manually specified
+ "permutation" line doesn't actually have to be a permutation of the
+ available steps; it could for instance repeat some steps more than once,
+ or leave others out. Also, each step name can be annotated with some
+ parenthesized markers, which are described below.
+
+Session and step names are SQL identifiers, either plain or double-quoted.
+A difference from standard SQL is that no case-folding occurs, so that
+FOO and "FOO" are the same name while FOO and Foo are different,
+whether you quote them or not. You must use quotes if you want to use
+an isolation test keyword (such as "permutation") as a name.
+
+A # character begins a comment, which extends to the end of the line.
+(This does not work inside <SQL> blocks, however. Use the usual SQL
+comment conventions there.)
+
+There is no way to include a "}" character in an <SQL> block.
+
+For each permutation of the session steps (whether these are manually
+specified in the spec file, or automatically generated), the isolation
+tester runs the main setup part, then per-session setup parts, then
+the selected session steps, then per-session teardown, then the main
+teardown script. Each selected step is sent to the connection associated
+with its session. The main setup and teardown scripts are run in a
+separate "control" session.
+
+
+Support for blocking commands
+=============================
+
+Each step may contain commands that block until further action has been taken
+(most likely, some other session runs a step that unblocks it or causes a
+deadlock). A test that uses this ability must manually specify valid
+permutations, i.e. those that would not expect a blocked session to execute a
+command. If a test fails to follow that rule, isolationtester will cancel it
+after PGISOLATIONTIMEOUT seconds. If the cancel doesn't work, isolationtester
+will exit uncleanly after a total of twice PGISOLATIONTIMEOUT. Testing
+invalid permutations should be avoided because they can make the isolation
+tests take a very long time to run, and they serve no useful testing purpose.
+
+Note that isolationtester recognizes that a command has blocked by looking
+to see if it is shown as waiting in the pg_locks view; therefore, only
+blocks on heavyweight locks will be detected.
+
+
+Dealing with race conditions
+============================
+
+In some cases, the isolationtester's output for a test script may vary
+due to timing issues. One way to deal with that is to create variant
+expected-files, which follow the usual PG convention that variants for
+foo.spec are named foo_1.out, foo_2.out, etc. However, this method is
+discouraged since the extra files are a nuisance for maintenance.
+Instead, it's usually possible to stabilize the test output by applying
+special markers to some of the step names listed in a permutation line.
+
+The general form of a permutation entry is
+
+ <step name> [ ( <marker> [ , <marker> ... ] ) ]
+
+where each marker defines a "blocking condition". The step will not be
+reported as completed before all the blocking conditions are satisfied.
+The possible markers are:
+
+ *
+ <other step name>
+ <other step name> notices <n>
+
+An asterisk marker, such as mystep(*), forces the isolationtester to
+report the step as "waiting" as soon as it's been launched, regardless of
+whether it would have been detected as waiting later. This is useful for
+stabilizing cases that are sometimes reported as waiting and other times
+reported as immediately completing, depending on the relative speeds of
+the step and the isolationtester's status-monitoring queries.
+
+A marker consisting solely of a step name indicates that this step may
+not be reported as completing until that other step has completed.
+This allows stabilizing cases where two queries might be seen to complete
+in either order. Note that this step can be *launched* before the other
+step has completed. (If the other step is used more than once in the
+current permutation, this step cannot complete while any of those
+instances is active.)
+
+A marker of the form "<other step name> notices <n>" (where <n> is a
+positive integer) indicates that this step may not be reported as
+completing until the other step's session has returned at least <n>
+NOTICE messages, counting from when this step is launched. This is useful
+for stabilizing cases where a step can return NOTICE messages before it
+actually completes, and those messages must be synchronized with the
+completions of other steps.
+
+Notice that these markers can only delay reporting of the completion
+of a step, not the launch of a step. The isolationtester will launch
+the next step in a permutation as soon as (A) all prior steps of the
+same session are done, and (B) the immediately preceding step in the
+permutation is done or deemed blocked. For this purpose, "deemed
+blocked" means that it has been seen to be waiting on a database lock,
+or that it is complete but the report of its completion is delayed by
+one of these markers.
+
+In some cases it is important not to launch a step until after the
+completion of a step in another session that could have been deemed
+blocked. An example is that if step s1 in session A is issuing a
+cancel for step s2 in session B, we'd better not launch B's next step
+till we're sure s1 is done. If s1 is blockable, trouble could ensue.
+The best way to prevent that is to create an empty step in session A
+and run it, without any markers, just before the next session B step.
+The empty step cannot be launched until s1 is done, and in turn the
+next session B step cannot be launched until the empty step finishes.
diff --git a/src/test/isolation/expected/aborted-keyrevoke.out b/src/test/isolation/expected/aborted-keyrevoke.out
new file mode 100644
index 0000000..a035426
--- /dev/null
+++ b/src/test/isolation/expected/aborted-keyrevoke.out
@@ -0,0 +1,272 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1s s1u s1r s1l s1c s2l s2c
+step s1s: SAVEPOINT f;
+step s1u: UPDATE foo SET key = 2;
+step s1r: ROLLBACK TO f;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
+step s2l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s1s s1u s1r s1l s2l s1c s2c
+step s1s: SAVEPOINT f;
+step s1u: UPDATE foo SET key = 2;
+step s1r: ROLLBACK TO f;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1s s1u s1r s1l s2l s2c s1c
+step s1s: SAVEPOINT f;
+step s1u: UPDATE foo SET key = 2;
+step s1r: ROLLBACK TO f;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s1s s1u s1r s2l s1l s1c s2c
+step s1s: SAVEPOINT f;
+step s1u: UPDATE foo SET key = 2;
+step s1r: ROLLBACK TO f;
+step s2l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1s s1u s1r s2l s1l s2c s1c
+step s1s: SAVEPOINT f;
+step s1u: UPDATE foo SET key = 2;
+step s1r: ROLLBACK TO f;
+step s2l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s1s s1u s1r s2l s2c s1l s1c
+step s1s: SAVEPOINT f;
+step s1u: UPDATE foo SET key = 2;
+step s1r: ROLLBACK TO f;
+step s2l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
+
+starting permutation: s1s s1u s2l s1r s1l s1c s2c
+step s1s: SAVEPOINT f;
+step s1u: UPDATE foo SET key = 2;
+step s2l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
+step s1r: ROLLBACK TO f;
+step s2l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1s s1u s2l s1r s1l s2c s1c
+step s1s: SAVEPOINT f;
+step s1u: UPDATE foo SET key = 2;
+step s2l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
+step s1r: ROLLBACK TO f;
+step s2l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s1s s1u s2l s1r s2c s1l s1c
+step s1s: SAVEPOINT f;
+step s1u: UPDATE foo SET key = 2;
+step s2l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
+step s1r: ROLLBACK TO f;
+step s2l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
+
+starting permutation: s1s s2l s1u s2c s1r s1l s1c
+step s1s: SAVEPOINT f;
+step s2l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1u: UPDATE foo SET key = 2; <waiting ...>
+step s2c: COMMIT;
+step s1u: <... completed>
+step s1r: ROLLBACK TO f;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
+
+starting permutation: s1s s2l s2c s1u s1r s1l s1c
+step s1s: SAVEPOINT f;
+step s2l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+step s1u: UPDATE foo SET key = 2;
+step s1r: ROLLBACK TO f;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
+
+starting permutation: s2l s1s s1u s2c s1r s1l s1c
+step s2l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1s: SAVEPOINT f;
+step s1u: UPDATE foo SET key = 2; <waiting ...>
+step s2c: COMMIT;
+step s1u: <... completed>
+step s1r: ROLLBACK TO f;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
+
+starting permutation: s2l s1s s2c s1u s1r s1l s1c
+step s2l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1s: SAVEPOINT f;
+step s2c: COMMIT;
+step s1u: UPDATE foo SET key = 2;
+step s1r: ROLLBACK TO f;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
+
+starting permutation: s2l s2c s1s s1u s1r s1l s1c
+step s2l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+step s1s: SAVEPOINT f;
+step s1u: UPDATE foo SET key = 2;
+step s1r: ROLLBACK TO f;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/alter-table-1.out b/src/test/isolation/expected/alter-table-1.out
new file mode 100644
index 0000000..5e88174
--- /dev/null
+++ b/src/test/isolation/expected/alter-table-1.out
@@ -0,0 +1,3326 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1 at1 sc1 s2 at2 sc2 rx1 wx rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 sc2 wx rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx sc2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx rx3 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx rx3 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 sc2 wx rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx sc2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx rx3 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx rx3 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 sc2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 rx3 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 rx3 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx3 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx3 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx3 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 sc2 wx rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx sc2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx rx3 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx rx3 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 sc2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 rx3 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 rx3 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx3 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx3 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx3 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 sc2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 rx3 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 rx3 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx3 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx3 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx3 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx3 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx3 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx3 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx3 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 sc2 wx rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx sc2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx rx3 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx rx3 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 sc2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 rx3 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 rx3 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx3 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx3 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx3 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 sc2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 rx3 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 rx3 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx3 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx3 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx3 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx3 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx3 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx3 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx3 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 sc2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 rx3 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 rx3 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx3 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx3 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx3 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx3 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx3 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx3 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx3 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 sc2 wx rx3 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx sc2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx rx3 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx rx3 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 sc2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 rx3 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 rx3 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx3 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx3 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx3 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 sc2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 rx3 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 rx3 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx3 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx3 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx3 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx3 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx3 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx3 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx3 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 sc2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 rx3 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 rx3 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx3 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx3 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx3 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx3 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx3 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx3 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx3 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 wx at1 rx3 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 wx rx3 at1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 wx rx3 c2 at1 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 sc2 wx rx3 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx sc2 rx3 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx rx3 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx rx3 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 sc2 rx3 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 rx3 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 rx3 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx3 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx3 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx3 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 sc2 rx3 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 rx3 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 rx3 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx3 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx3 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx3 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx3 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx3 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx3 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx3 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 sc2 rx3 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 rx3 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 rx3 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx3 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx3 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx3 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx3 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx3 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx3 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx3 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 wx at1 rx3 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 wx rx3 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 wx rx3 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 at1 rx3 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 rx3 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 rx3 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx3 s1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx3 s1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step s1: BEGIN;
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx3 c2 s1 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx: INSERT INTO b VALUES (0);
+step rx3: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id
+----
+ 3
+ 3
+ 3
+(3 rows)
+
+step c2: COMMIT;
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
diff --git a/src/test/isolation/expected/alter-table-2.out b/src/test/isolation/expected/alter-table-2.out
new file mode 100644
index 0000000..819bc33
--- /dev/null
+++ b/src/test/isolation/expected/alter-table-2.out
@@ -0,0 +1,1030 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1a s1b s1c s2a s2b s2c s2d s2e s2f
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s1b s2a s1c s2b s2c s2d s2e s2f
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2a: BEGIN;
+step s1c: COMMIT;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s1b s2a s2b s1c s2c s2d s2e s2f
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1c: COMMIT;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s1b s2a s2b s2c s1c s2d s2e s2f
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1c: COMMIT;
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s1b s2a s2b s2c s2d s1c s2e s2f
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0); <waiting ...>
+step s1c: COMMIT;
+step s2d: <... completed>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s2a s1b s1c s2b s2c s2d s2e s2f
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s2a s1b s2b s1c s2c s2d s2e s2f
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1c: COMMIT;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s2a s1b s2b s2c s1c s2d s2e s2f
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1c: COMMIT;
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s2a s1b s2b s2c s2d s1c s2e s2f
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0); <waiting ...>
+step s1c: COMMIT;
+step s2d: <... completed>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s2a s2b s1b s1c s2c s2d s2e s2f
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s2a s2b s1b s2c s1c s2d s2e s2f
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1c: COMMIT;
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s2a s2b s1b s2c s2d s1c s2e s2f
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0); <waiting ...>
+step s1c: COMMIT;
+step s2d: <... completed>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s1b s1c s2d s2e s2f
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s1b s2d s1c s2e s2f
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2d: INSERT INTO b VALUES (0); <waiting ...>
+step s1c: COMMIT;
+step s2d: <... completed>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s2d s1b s2e s2f s1c
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s2d s2e s1b s2f s1c
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step s2f: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s2d s2e s2f s1b s1c
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+
+starting permutation: s2a s1a s1b s1c s2b s2c s2d s2e s2f
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s1a s1b s2b s1c s2c s2d s2e s2f
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1c: COMMIT;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s1a s1b s2b s2c s1c s2d s2e s2f
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1c: COMMIT;
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s1a s1b s2b s2c s2d s1c s2e s2f
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0); <waiting ...>
+step s1c: COMMIT;
+step s2d: <... completed>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s1c s2c s2d s2e s2f
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s2c s1c s2d s2e s2f
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1c: COMMIT;
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s2c s2d s1c s2e s2f
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0); <waiting ...>
+step s1c: COMMIT;
+step s2d: <... completed>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s1b s1c s2d s2e s2f
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s1b s2d s1c s2e s2f
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2d: INSERT INTO b VALUES (0); <waiting ...>
+step s1c: COMMIT;
+step s2d: <... completed>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s2d s1b s2e s2f s1c
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s2d s2e s1b s2f s1c
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step s2f: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s2d s2e s2f s1b s1c
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s1c s2c s2d s2e s2f
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s2c s1c s2d s2e s2f
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1c: COMMIT;
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s2c s2d s1c s2e s2f
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0); <waiting ...>
+step s1c: COMMIT;
+step s2d: <... completed>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s1b s1c s2d s2e s2f
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s1b s2d s1c s2e s2f
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2d: INSERT INTO b VALUES (0); <waiting ...>
+step s1c: COMMIT;
+step s2d: <... completed>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s2d s1b s2e s2f s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s2d s2e s1b s2f s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step s2f: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s2d s2e s2f s1b s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s1b s1c s2d s2e s2f
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s1b s2d s1c s2e s2f
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s2d: INSERT INTO b VALUES (0); <waiting ...>
+step s1c: COMMIT;
+step s2d: <... completed>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s2d s1b s2e s2f s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1a: BEGIN;
+step s2d: INSERT INTO b VALUES (0);
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s2d s2e s1b s2f s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1a: BEGIN;
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step s2f: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s2d s2e s2f s1b s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s1a: BEGIN;
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s2d s1a s1b s2e s2f s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s2d s1a s2e s1b s2f s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s1a: BEGIN;
+step s2e: INSERT INTO a VALUES (4);
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step s2f: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s2d s1a s2e s2f s1b s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s1a: BEGIN;
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s2d s2e s1a s1b s2f s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step s2f: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s2d s2e s1a s2f s1b s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s1a: BEGIN;
+step s2f: COMMIT;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s2d s2e s2f s1a s1b s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE;
+a_id
+----
+ 3
+(1 row)
+
+step s2d: INSERT INTO b VALUES (0);
+step s2e: INSERT INTO a VALUES (4);
+step s2f: COMMIT;
+step s1a: BEGIN;
+step s1b: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/alter-table-3.out b/src/test/isolation/expected/alter-table-3.out
new file mode 100644
index 0000000..427364e
--- /dev/null
+++ b/src/test/isolation/expected/alter-table-3.out
@@ -0,0 +1,785 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1a s1b s1c s1d s2a s2b s2c s2d
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s1b s1c s2a s1d s2b s2c s2d
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2a: BEGIN;
+step s1d: COMMIT;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s1b s1c s2a s2b s1d s2c s2d
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1d: COMMIT;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s1b s1c s2a s2b s2c s1d s2d
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s1b s2a s1c s1d s2b s2c s2d
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2a: BEGIN;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s1b s2a s1c s2b s1d s2c s2d
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2a: BEGIN;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1d: COMMIT;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s1b s2a s1c s2b s2c s1d s2d
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2a: BEGIN;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s1b s2a s2b s1c s1d s2c s2d
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s1b s2a s2b s1c s2c s1d s2d
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s1b s2a s2b s2c s1c s1d s2d
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s1b s1c s1d s2b s2c s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s1b s1c s2b s1d s2c s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1d: COMMIT;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s1b s1c s2b s2c s1d s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s1b s2b s1c s1d s2c s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s1b s2b s1c s2c s1d s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s1b s2b s2c s1c s1d s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s2b s1b s1c s1d s2c s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s2b s1b s1c s2c s1d s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s2b s1b s2c s1c s1d s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s1b s1c s1d s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s1b s1c s2d s1d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2d: COMMIT;
+step s1d: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s1b s2d s1c s1d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2d: COMMIT;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s2d s1b s1c s1d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+
+starting permutation: s2a s1a s1b s1c s1d s2b s2c s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s1b s1c s2b s1d s2c s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1d: COMMIT;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s1b s1c s2b s2c s1d s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s1b s2b s1c s1d s2c s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s1b s2b s1c s2c s1d s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s1b s2b s2c s1c s1d s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s1c s1d s2c s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s1c s2c s1d s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s2c s1c s1d s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s1b s1c s1d s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s1b s1c s2d s1d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2d: COMMIT;
+step s1d: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s1b s2d s1c s1d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2d: COMMIT;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s2d s1b s1c s1d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s1c s1d s2c s2d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s1c s2c s1d s2d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s2c s1c s1d s2d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2c: INSERT INTO a VALUES (0); <waiting ...>
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2c: <... completed>
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s1b s1c s1d s2d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2d: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s1b s1c s2d s1d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2d: COMMIT;
+step s1d: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s1b s2d s1c s1d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2d: COMMIT;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s2d s1b s1c s1d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s1b s1c s1d s2d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+step s2d: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s1b s1c s2d s1d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s2d: COMMIT;
+step s1d: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s1b s2d s1c s1d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s2d: COMMIT;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s2d s1b s1c s1d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s1a: BEGIN;
+step s2d: COMMIT;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
+
+starting permutation: s2a s2b s2c s2d s1a s1b s1c s1d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: INSERT INTO a VALUES (0);
+ERROR: duplicate key value violates unique constraint "a_pkey"
+step s2d: COMMIT;
+step s1a: BEGIN;
+step s1b: ALTER TABLE a DISABLE TRIGGER t;
+step s1c: ALTER TABLE a ENABLE TRIGGER t;
+step s1d: COMMIT;
diff --git a/src/test/isolation/expected/alter-table-4.out b/src/test/isolation/expected/alter-table-4.out
new file mode 100644
index 0000000..fc57910
--- /dev/null
+++ b/src/test/isolation/expected/alter-table-4.out
@@ -0,0 +1,71 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s1delc1 s2sel s1c s2sel
+step s1b: BEGIN;
+step s1delc1: ALTER TABLE c1 NO INHERIT p;
+step s2sel: SELECT SUM(a) FROM p; <waiting ...>
+step s1c: COMMIT;
+step s2sel: <... completed>
+sum
+---
+ 11
+(1 row)
+
+step s2sel: SELECT SUM(a) FROM p;
+sum
+---
+ 1
+(1 row)
+
+
+starting permutation: s1b s1delc1 s1addc2 s2sel s1c s2sel
+step s1b: BEGIN;
+step s1delc1: ALTER TABLE c1 NO INHERIT p;
+step s1addc2: ALTER TABLE c2 INHERIT p;
+step s2sel: SELECT SUM(a) FROM p; <waiting ...>
+step s1c: COMMIT;
+step s2sel: <... completed>
+sum
+---
+ 11
+(1 row)
+
+step s2sel: SELECT SUM(a) FROM p;
+sum
+---
+101
+(1 row)
+
+
+starting permutation: s1b s1dropc1 s2sel s1c s2sel
+step s1b: BEGIN;
+step s1dropc1: DROP TABLE c1;
+step s2sel: SELECT SUM(a) FROM p; <waiting ...>
+step s1c: COMMIT;
+step s2sel: <... completed>
+sum
+---
+ 1
+(1 row)
+
+step s2sel: SELECT SUM(a) FROM p;
+sum
+---
+ 1
+(1 row)
+
+
+starting permutation: s1b s1delc1 s1modc1a s2sel s1c s2sel
+step s1b: BEGIN;
+step s1delc1: ALTER TABLE c1 NO INHERIT p;
+step s1modc1a: ALTER TABLE c1 ALTER COLUMN a TYPE float;
+step s2sel: SELECT SUM(a) FROM p; <waiting ...>
+step s1c: COMMIT;
+step s2sel: <... completed>
+ERROR: attribute "a" of relation "c1" does not match parent's type
+step s2sel: SELECT SUM(a) FROM p;
+sum
+---
+ 1
+(1 row)
+
diff --git a/src/test/isolation/expected/async-notify.out b/src/test/isolation/expected/async-notify.out
new file mode 100644
index 0000000..556e180
--- /dev/null
+++ b/src/test/isolation/expected/async-notify.out
@@ -0,0 +1,127 @@
+Parsed test spec with 3 sessions
+
+starting permutation: listenc notify1 notify2 notify3 notifyf
+step listenc: LISTEN c1; LISTEN c2;
+step notify1: NOTIFY c1;
+notifier: NOTIFY "c1" with payload "" from notifier
+step notify2: NOTIFY c2, 'payload';
+notifier: NOTIFY "c2" with payload "payload" from notifier
+step notify3: NOTIFY c3, 'payload3';
+step notifyf: SELECT pg_notify('c2', NULL);
+pg_notify
+---------
+
+(1 row)
+
+notifier: NOTIFY "c2" with payload "" from notifier
+
+starting permutation: listenc notifyd1 notifyd2 notifys1
+step listenc: LISTEN c1; LISTEN c2;
+step notifyd1: NOTIFY c2, 'payload'; NOTIFY c1; NOTIFY "c2", 'payload';
+notifier: NOTIFY "c2" with payload "payload" from notifier
+notifier: NOTIFY "c1" with payload "" from notifier
+step notifyd2: NOTIFY c1; NOTIFY c1; NOTIFY c1, 'p1'; NOTIFY c1, 'p2';
+notifier: NOTIFY "c1" with payload "" from notifier
+notifier: NOTIFY "c1" with payload "p1" from notifier
+notifier: NOTIFY "c1" with payload "p2" from notifier
+step notifys1:
+ BEGIN;
+ NOTIFY c1, 'payload'; NOTIFY "c2", 'payload';
+ NOTIFY c1, 'payload'; NOTIFY "c2", 'payload';
+ SAVEPOINT s1;
+ NOTIFY c1, 'payload'; NOTIFY "c2", 'payload';
+ NOTIFY c1, 'payloads'; NOTIFY "c2", 'payloads';
+ NOTIFY c1, 'payload'; NOTIFY "c2", 'payload';
+ NOTIFY c1, 'payloads'; NOTIFY "c2", 'payloads';
+ RELEASE SAVEPOINT s1;
+ SAVEPOINT s2;
+ NOTIFY c1, 'rpayload'; NOTIFY "c2", 'rpayload';
+ NOTIFY c1, 'rpayloads'; NOTIFY "c2", 'rpayloads';
+ NOTIFY c1, 'rpayload'; NOTIFY "c2", 'rpayload';
+ NOTIFY c1, 'rpayloads'; NOTIFY "c2", 'rpayloads';
+ ROLLBACK TO SAVEPOINT s2;
+ COMMIT;
+
+notifier: NOTIFY "c1" with payload "payload" from notifier
+notifier: NOTIFY "c2" with payload "payload" from notifier
+notifier: NOTIFY "c1" with payload "payloads" from notifier
+notifier: NOTIFY "c2" with payload "payloads" from notifier
+
+starting permutation: llisten notify1 notify2 notify3 notifyf lcheck
+step llisten: LISTEN c1; LISTEN c2;
+step notify1: NOTIFY c1;
+step notify2: NOTIFY c2, 'payload';
+step notify3: NOTIFY c3, 'payload3';
+step notifyf: SELECT pg_notify('c2', NULL);
+pg_notify
+---------
+
+(1 row)
+
+step lcheck: SELECT 1 AS x;
+x
+-
+1
+(1 row)
+
+listener: NOTIFY "c1" with payload "" from notifier
+listener: NOTIFY "c2" with payload "payload" from notifier
+listener: NOTIFY "c2" with payload "" from notifier
+
+starting permutation: listenc llisten notify1 notify2 notify3 notifyf lcheck
+step listenc: LISTEN c1; LISTEN c2;
+step llisten: LISTEN c1; LISTEN c2;
+step notify1: NOTIFY c1;
+notifier: NOTIFY "c1" with payload "" from notifier
+step notify2: NOTIFY c2, 'payload';
+notifier: NOTIFY "c2" with payload "payload" from notifier
+step notify3: NOTIFY c3, 'payload3';
+step notifyf: SELECT pg_notify('c2', NULL);
+pg_notify
+---------
+
+(1 row)
+
+notifier: NOTIFY "c2" with payload "" from notifier
+step lcheck: SELECT 1 AS x;
+x
+-
+1
+(1 row)
+
+listener: NOTIFY "c1" with payload "" from notifier
+listener: NOTIFY "c2" with payload "payload" from notifier
+listener: NOTIFY "c2" with payload "" from notifier
+
+starting permutation: l2listen l2begin notify1 lbegins llisten lcommit l2commit l2stop
+step l2listen: LISTEN c1;
+step l2begin: BEGIN;
+step notify1: NOTIFY c1;
+step lbegins: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step llisten: LISTEN c1; LISTEN c2;
+step lcommit: COMMIT;
+step l2commit: COMMIT;
+listener2: NOTIFY "c1" with payload "" from notifier
+step l2stop: UNLISTEN *;
+
+starting permutation: llisten lbegin usage bignotify usage
+step llisten: LISTEN c1; LISTEN c2;
+step lbegin: BEGIN;
+step usage: SELECT pg_notification_queue_usage() > 0 AS nonzero;
+nonzero
+-------
+f
+(1 row)
+
+step bignotify: SELECT count(pg_notify('c1', s::text)) FROM generate_series(1, 1000) s;
+count
+-----
+ 1000
+(1 row)
+
+step usage: SELECT pg_notification_queue_usage() > 0 AS nonzero;
+nonzero
+-------
+t
+(1 row)
+
diff --git a/src/test/isolation/expected/classroom-scheduling.out b/src/test/isolation/expected/classroom-scheduling.out
new file mode 100644
index 0000000..1d7c885
--- /dev/null
+++ b/src/test/isolation/expected/classroom-scheduling.out
@@ -0,0 +1,379 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rx1 wy1 c1 ry2 wx2 c2
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 1
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 ry2 c1 wx2 c2
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step c1: COMMIT;
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 ry2 wx2 c1 c2
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 wy1 ry2 wx2 c2 c1
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wy1 c1 wx2 c2
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 ry2 wy1 wx2 c1 c2
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wy1 wx2 c2 c1
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 wy1 c1 c2
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 wy1 c2 c1
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 c2 wy1 c1
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 rx1 wy1 c1 wx2 c2
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: ry2 rx1 wy1 wx2 c1 c2
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wy1 wx2 c2 c1
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 wy1 c1 c2
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 wy1 c2 c1
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 c2 wy1 c1
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 wx2 rx1 wy1 c1 c2
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 wx2 rx1 wy1 c2 c1
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 wx2 rx1 c2 wy1 c1
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 0
+(1 row)
+
+step c2: COMMIT;
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 wx2 c2 rx1 wy1 c1
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/create-trigger.out b/src/test/isolation/expected/create-trigger.out
new file mode 100644
index 0000000..7f98678
--- /dev/null
+++ b/src/test/isolation/expected/create-trigger.out
@@ -0,0 +1,361 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1a s1b s1c s2a s2b s2c s2d
+step s1a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s1c: COMMIT;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+
+starting permutation: s1a s1b s2a s1c s2b s2c s2d
+step s1a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s2a: BEGIN;
+step s1c: COMMIT;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+
+starting permutation: s1a s1b s2a s2b s1c s2c s2d
+step s1a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1c: COMMIT;
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+
+starting permutation: s1a s1b s2a s2b s2c s1c s2d
+step s1a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3; <waiting ...>
+step s1c: COMMIT;
+step s2c: <... completed>
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s1b s1c s2b s2c s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s1c: COMMIT;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s1b s2b s1c s2c s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1c: COMMIT;
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s1b s2b s2c s1c s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3; <waiting ...>
+step s1c: COMMIT;
+step s2c: <... completed>
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s2b s1b s1c s2c s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s1c: COMMIT;
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s2b s1b s2c s1c s2d
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s2c: UPDATE a SET i = 4 WHERE i = 3; <waiting ...>
+step s1c: COMMIT;
+step s2c: <... completed>
+step s2d: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s1b s2d s1c
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f(); <waiting ...>
+step s2d: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s2d s1b s1c
+step s1a: BEGIN;
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s1c: COMMIT;
+
+starting permutation: s2a s1a s1b s1c s2b s2c s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s1c: COMMIT;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s1b s2b s1c s2c s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1c: COMMIT;
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s1b s2b s2c s1c s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3; <waiting ...>
+step s1c: COMMIT;
+step s2c: <... completed>
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s1c s2c s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s1c: COMMIT;
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s2c s1c s2d
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s2c: UPDATE a SET i = 4 WHERE i = 3; <waiting ...>
+step s1c: COMMIT;
+step s2c: <... completed>
+step s2d: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s1b s2d s1c
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f(); <waiting ...>
+step s2d: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s2d s1b s1c
+step s2a: BEGIN;
+step s1a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s1c s2c s2d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s1c: COMMIT;
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s2c s1c s2d
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s2c: UPDATE a SET i = 4 WHERE i = 3; <waiting ...>
+step s1c: COMMIT;
+step s2c: <... completed>
+step s2d: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s1b s2d s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f(); <waiting ...>
+step s2d: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s2d s1b s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s1a: BEGIN;
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s1b s2d s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s1a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f(); <waiting ...>
+step s2d: COMMIT;
+step s1b: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s2d s1b s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s1a: BEGIN;
+step s2d: COMMIT;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s2d s1a s1b s1c
+step s2a: BEGIN;
+step s2b: SELECT * FROM a WHERE i = 1 FOR UPDATE;
+i
+-
+1
+(1 row)
+
+step s2c: UPDATE a SET i = 4 WHERE i = 3;
+step s2d: COMMIT;
+step s1a: BEGIN;
+step s1b: CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/deadlock-hard.out b/src/test/isolation/expected/deadlock-hard.out
new file mode 100644
index 0000000..460653f
--- /dev/null
+++ b/src/test/isolation/expected/deadlock-hard.out
@@ -0,0 +1,36 @@
+Parsed test spec with 8 sessions
+
+starting permutation: s1a1 s2a2 s3a3 s4a4 s5a5 s6a6 s7a7 s8a8 s1a2 s2a3 s3a4 s4a5 s5a6 s6a7 s7a8 s8a1 s8c s7c s6c s5c s4c s3c s2c s1c
+step s1a1: LOCK TABLE a1;
+step s2a2: LOCK TABLE a2;
+step s3a3: LOCK TABLE a3;
+step s4a4: LOCK TABLE a4;
+step s5a5: LOCK TABLE a5;
+step s6a6: LOCK TABLE a6;
+step s7a7: LOCK TABLE a7;
+step s8a8: LOCK TABLE a8;
+step s1a2: LOCK TABLE a2; <waiting ...>
+step s2a3: LOCK TABLE a3; <waiting ...>
+step s3a4: LOCK TABLE a4; <waiting ...>
+step s4a5: LOCK TABLE a5; <waiting ...>
+step s5a6: LOCK TABLE a6; <waiting ...>
+step s6a7: LOCK TABLE a7; <waiting ...>
+step s7a8: LOCK TABLE a8; <waiting ...>
+step s8a1: LOCK TABLE a1; <waiting ...>
+step s8a1: <... completed>
+ERROR: deadlock detected
+step s7a8: <... completed>
+step s8c: COMMIT;
+step s7c: COMMIT;
+step s6a7: <... completed>
+step s6c: COMMIT;
+step s5a6: <... completed>
+step s5c: COMMIT;
+step s4a5: <... completed>
+step s4c: COMMIT;
+step s3a4: <... completed>
+step s3c: COMMIT;
+step s2a3: <... completed>
+step s2c: COMMIT;
+step s1a2: <... completed>
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/deadlock-parallel.out b/src/test/isolation/expected/deadlock-parallel.out
new file mode 100644
index 0000000..6fe5e24
--- /dev/null
+++ b/src/test/isolation/expected/deadlock-parallel.out
@@ -0,0 +1,68 @@
+Parsed test spec with 4 sessions
+
+starting permutation: d1a1 d2a2 e1l e2l d1a2 d2a1 d1c e1c d2c e2c
+step d1a1: SELECT lock_share(1,x), lock_excl(3,x) FROM bigt LIMIT 1;
+lock_share|lock_excl
+----------+---------
+ 1| 1
+(1 row)
+
+step d2a2: select lock_share(2,x) FROM bigt LIMIT 1;
+lock_share
+----------
+ 1
+(1 row)
+
+step e1l: SELECT lock_excl(1,x) FROM bigt LIMIT 1; <waiting ...>
+step e2l: SELECT lock_excl(2,x) FROM bigt LIMIT 1; <waiting ...>
+step d1a2: SET force_parallel_mode = on;
+ SET parallel_setup_cost = 0;
+ SET parallel_tuple_cost = 0;
+ SET min_parallel_table_scan_size = 0;
+ SET parallel_leader_participation = off;
+ SET max_parallel_workers_per_gather = 3;
+ SELECT sum(lock_share(2,x)) FROM bigt; <waiting ...>
+step d2a1: SET force_parallel_mode = on;
+ SET parallel_setup_cost = 0;
+ SET parallel_tuple_cost = 0;
+ SET min_parallel_table_scan_size = 0;
+ SET parallel_leader_participation = off;
+ SET max_parallel_workers_per_gather = 3;
+ SELECT sum(lock_share(1,x)) FROM bigt;
+ SET force_parallel_mode = off;
+ RESET parallel_setup_cost;
+ RESET parallel_tuple_cost;
+ SELECT lock_share(3,x) FROM bigt LIMIT 1; <waiting ...>
+step d1a2: <... completed>
+ sum
+-----
+10000
+(1 row)
+
+step d1c: COMMIT;
+step e1l: <... completed>
+lock_excl
+---------
+ 1
+(1 row)
+
+step d2a1: <... completed>
+ sum
+-----
+10000
+(1 row)
+
+lock_share
+----------
+ 1
+(1 row)
+
+step e1c: COMMIT;
+step d2c: COMMIT;
+step e2l: <... completed>
+lock_excl
+---------
+ 1
+(1 row)
+
+step e2c: COMMIT;
diff --git a/src/test/isolation/expected/deadlock-simple.out b/src/test/isolation/expected/deadlock-simple.out
new file mode 100644
index 0000000..8be1538
--- /dev/null
+++ b/src/test/isolation/expected/deadlock-simple.out
@@ -0,0 +1,11 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1as s2as s1ae s2ae s1c s2c
+step s1as: LOCK TABLE a1 IN ACCESS SHARE MODE;
+step s2as: LOCK TABLE a1 IN ACCESS SHARE MODE;
+step s1ae: LOCK TABLE a1 IN ACCESS EXCLUSIVE MODE; <waiting ...>
+step s2ae: LOCK TABLE a1 IN ACCESS EXCLUSIVE MODE;
+ERROR: deadlock detected
+step s1ae: <... completed>
+step s1c: COMMIT;
+step s2c: COMMIT;
diff --git a/src/test/isolation/expected/deadlock-soft-2.out b/src/test/isolation/expected/deadlock-soft-2.out
new file mode 100644
index 0000000..14b0343
--- /dev/null
+++ b/src/test/isolation/expected/deadlock-soft-2.out
@@ -0,0 +1,17 @@
+Parsed test spec with 4 sessions
+
+starting permutation: s1a s2a s2b s3a s4a s1b s1c s2c s3c s4c
+step s1a: LOCK TABLE a1 IN SHARE UPDATE EXCLUSIVE MODE;
+step s2a: LOCK TABLE a2 IN ACCESS SHARE MODE;
+step s2b: LOCK TABLE a1 IN SHARE UPDATE EXCLUSIVE MODE; <waiting ...>
+step s3a: LOCK TABLE a2 IN ACCESS EXCLUSIVE MODE; <waiting ...>
+step s4a: LOCK TABLE a2 IN ACCESS EXCLUSIVE MODE; <waiting ...>
+step s1b: LOCK TABLE a2 IN SHARE UPDATE EXCLUSIVE MODE; <waiting ...>
+step s1b: <... completed>
+step s1c: COMMIT;
+step s2b: <... completed>
+step s2c: COMMIT;
+step s3a: <... completed>
+step s3c: COMMIT;
+step s4a: <... completed>
+step s4c: COMMIT;
diff --git a/src/test/isolation/expected/deadlock-soft.out b/src/test/isolation/expected/deadlock-soft.out
new file mode 100644
index 0000000..24a35da
--- /dev/null
+++ b/src/test/isolation/expected/deadlock-soft.out
@@ -0,0 +1,17 @@
+Parsed test spec with 4 sessions
+
+starting permutation: d1a1 d2a2 e1l e2l d1a2 d2a1 d1c e1c d2c e2c
+step d1a1: LOCK TABLE a1 IN ACCESS SHARE MODE;
+step d2a2: LOCK TABLE a2 IN ACCESS SHARE MODE;
+step e1l: LOCK TABLE a1 IN ACCESS EXCLUSIVE MODE; <waiting ...>
+step e2l: LOCK TABLE a2 IN ACCESS EXCLUSIVE MODE; <waiting ...>
+step d1a2: LOCK TABLE a2 IN ACCESS SHARE MODE; <waiting ...>
+step d2a1: LOCK TABLE a1 IN ACCESS SHARE MODE; <waiting ...>
+step d1a2: <... completed>
+step d1c: COMMIT;
+step e1l: <... completed>
+step e1c: COMMIT;
+step d2a1: <... completed>
+step d2c: COMMIT;
+step e2l: <... completed>
+step e2c: COMMIT;
diff --git a/src/test/isolation/expected/delete-abort-savept-2.out b/src/test/isolation/expected/delete-abort-savept-2.out
new file mode 100644
index 0000000..6fc991a
--- /dev/null
+++ b/src/test/isolation/expected/delete-abort-savept-2.out
@@ -0,0 +1,100 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1l s1svp s1d s1r s2l s1c s2c
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1svp: SAVEPOINT f;
+step s1d: SELECT * FROM foo FOR NO KEY UPDATE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1r: ROLLBACK TO f;
+step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
+step s1c: COMMIT;
+step s2l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s1l s1svp s1d s2l s1r s1c s2c
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1svp: SAVEPOINT f;
+step s1d: SELECT * FROM foo FOR NO KEY UPDATE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
+step s1r: ROLLBACK TO f;
+step s1c: COMMIT;
+step s2l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s1l s1svp s1d s1r s2l2 s1c s2c
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1svp: SAVEPOINT f;
+step s1d: SELECT * FROM foo FOR NO KEY UPDATE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1r: ROLLBACK TO f;
+step s2l2: SELECT * FROM foo FOR NO KEY UPDATE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1l s1svp s1d s2l2 s1r s1c s2c
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1svp: SAVEPOINT f;
+step s1d: SELECT * FROM foo FOR NO KEY UPDATE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2l2: SELECT * FROM foo FOR NO KEY UPDATE; <waiting ...>
+step s1r: ROLLBACK TO f;
+step s2l2: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
diff --git a/src/test/isolation/expected/delete-abort-savept.out b/src/test/isolation/expected/delete-abort-savept.out
new file mode 100644
index 0000000..8f70bab
--- /dev/null
+++ b/src/test/isolation/expected/delete-abort-savept.out
@@ -0,0 +1,139 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1l s1svp s1d s1r s1c s2l s2c
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1svp: SAVEPOINT f;
+step s1d: DELETE FROM foo;
+step s1r: ROLLBACK TO f;
+step s1c: COMMIT;
+step s2l: SELECT * FROM foo FOR UPDATE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s1l s1svp s1d s1r s2l s1c s2c
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1svp: SAVEPOINT f;
+step s1d: DELETE FROM foo;
+step s1r: ROLLBACK TO f;
+step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
+step s1c: COMMIT;
+step s2l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s1l s1svp s1d s2l s1r s1c s2c
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1svp: SAVEPOINT f;
+step s1d: DELETE FROM foo;
+step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
+step s1r: ROLLBACK TO f;
+step s1c: COMMIT;
+step s2l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s1l s1svp s2l s1d s1r s1c s2c
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1svp: SAVEPOINT f;
+step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
+step s1d: DELETE FROM foo;
+step s1r: ROLLBACK TO f;
+step s1c: COMMIT;
+step s2l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s1l s2l s1svp s1d s1r s1c s2c
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
+step s1svp: SAVEPOINT f;
+step s1d: DELETE FROM foo;
+step s1r: ROLLBACK TO f;
+step s1c: COMMIT;
+step s2l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s2l s1l s2c s1svp s1d s1r s1c
+step s2l: SELECT * FROM foo FOR UPDATE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
+step s2c: COMMIT;
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1svp: SAVEPOINT f;
+step s1d: DELETE FROM foo;
+step s1r: ROLLBACK TO f;
+step s1c: COMMIT;
+
+starting permutation: s2l s2c s1l s1svp s1d s1r s1c
+step s2l: SELECT * FROM foo FOR UPDATE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s1svp: SAVEPOINT f;
+step s1d: DELETE FROM foo;
+step s1r: ROLLBACK TO f;
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/detach-partition-concurrently-1.out b/src/test/isolation/expected/detach-partition-concurrently-1.out
new file mode 100644
index 0000000..bae53dd
--- /dev/null
+++ b/src/test/isolation/expected/detach-partition-concurrently-1.out
@@ -0,0 +1,288 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1b s1s s2detach s1s s1c s1s
+step s1b: BEGIN;
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+(1 row)
+
+step s1c: COMMIT;
+step s2detach: <... completed>
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1b s1s s2detach s1s s3s s3i s1c s3i s2drop s1s
+step s1b: BEGIN;
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+(1 row)
+
+step s3s: SELECT * FROM d_listp;
+a
+-
+1
+(1 row)
+
+step s3i: SELECT relpartbound IS NULL FROM pg_class where relname = 'd_listp2';
+?column?
+--------
+f
+(1 row)
+
+step s1c: COMMIT;
+step s2detach: <... completed>
+step s3i: SELECT relpartbound IS NULL FROM pg_class where relname = 'd_listp2';
+?column?
+--------
+t
+(1 row)
+
+step s2drop: DROP TABLE d_listp2;
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1b s1s s2detach s1ins s1s s1c
+step s1b: BEGIN;
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s1ins: INSERT INTO d_listp VALUES (1);
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+1
+(2 rows)
+
+step s1c: COMMIT;
+step s2detach: <... completed>
+
+starting permutation: s1b s1s s1ins2 s2detach s1ins s1s s1c
+step s1b: BEGIN;
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s1ins2: INSERT INTO d_listp VALUES (2);
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s1ins: INSERT INTO d_listp VALUES (1);
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+1
+(2 rows)
+
+step s1c: COMMIT;
+step s2detach: <... completed>
+
+starting permutation: s1brr s1s s2detach s1ins s1s s1c
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s1ins: INSERT INTO d_listp VALUES (1);
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+1
+2
+(3 rows)
+
+step s1c: COMMIT;
+step s2detach: <... completed>
+
+starting permutation: s1brr s1s s2detach s1s s1c
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s1c: COMMIT;
+step s2detach: <... completed>
+
+starting permutation: s1b s1ins2 s2detach s3ins2 s1c
+step s1b: BEGIN;
+step s1ins2: INSERT INTO d_listp VALUES (2);
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s3ins2: INSERT INTO d_listp VALUES (2);
+ERROR: no partition of relation "d_listp" found for row
+step s1c: COMMIT;
+step s2detach: <... completed>
+
+starting permutation: s1brr s1prep s1s s2detach s1s s1exec1 s3s s1dealloc s1c
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1prep: PREPARE f(int) AS INSERT INTO d_listp VALUES ($1);
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s1exec1: EXECUTE f(1);
+step s3s: SELECT * FROM d_listp;
+a
+-
+1
+(1 row)
+
+step s1dealloc: DEALLOCATE f;
+step s1c: COMMIT;
+step s2detach: <... completed>
+
+starting permutation: s1brr s1prep s1exec2 s2detach s1s s1exec2 s3s s1c s1dealloc
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1prep: PREPARE f(int) AS INSERT INTO d_listp VALUES ($1);
+step s1exec2: EXECUTE f(2);
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+2
+(3 rows)
+
+step s1exec2: EXECUTE f(2);
+step s3s: SELECT * FROM d_listp;
+a
+-
+1
+(1 row)
+
+step s1c: COMMIT;
+step s2detach: <... completed>
+step s1dealloc: DEALLOCATE f;
+
+starting permutation: s1brr s1prep s1s s2detach s1s s1exec2 s1c s1dealloc
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1prep: PREPARE f(int) AS INSERT INTO d_listp VALUES ($1);
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s1exec2: EXECUTE f(2);
+step s1c: COMMIT;
+step s2detach: <... completed>
+step s1dealloc: DEALLOCATE f;
+
+starting permutation: s1brr s1prep s2detach s1s s1exec2 s1c s1dealloc
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1prep: PREPARE f(int) AS INSERT INTO d_listp VALUES ($1);
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s1exec2: EXECUTE f(2);
+step s1c: COMMIT;
+step s2detach: <... completed>
+step s1dealloc: DEALLOCATE f;
+
+starting permutation: s1brr s1prep1 s2detach s1s s1exec2 s1c s1dealloc
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1prep1: PREPARE f(int) AS INSERT INTO d_listp VALUES (1);
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s1exec2: EXECUTE f(2);
+step s1c: COMMIT;
+step s2detach: <... completed>
+step s1dealloc: DEALLOCATE f;
+
+starting permutation: s1brr s1prep2 s2detach s1s s1exec2 s1c s1dealloc
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1prep2: PREPARE f(int) AS INSERT INTO d_listp VALUES (2);
+step s2detach: ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; <waiting ...>
+step s1s: SELECT * FROM d_listp;
+a
+-
+1
+2
+(2 rows)
+
+step s1exec2: EXECUTE f(2);
+step s1c: COMMIT;
+step s2detach: <... completed>
+step s1dealloc: DEALLOCATE f;
diff --git a/src/test/isolation/expected/detach-partition-concurrently-2.out b/src/test/isolation/expected/detach-partition-concurrently-2.out
new file mode 100644
index 0000000..6f025d8
--- /dev/null
+++ b/src/test/isolation/expected/detach-partition-concurrently-2.out
@@ -0,0 +1,76 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1b s1s s2d s3i1 s1c
+step s1b: BEGIN;
+step s1s: SELECT * FROM d_lp_fk;
+a
+-
+1
+2
+(2 rows)
+
+step s2d: ALTER TABLE d_lp_fk DETACH PARTITION d_lp_fk_1 CONCURRENTLY; <waiting ...>
+step s3i1: INSERT INTO d_lp_fk_r VALUES (1);
+ERROR: insert or update on table "d_lp_fk_r" violates foreign key constraint "d_lp_fk_r_a_fkey"
+step s1c: COMMIT;
+step s2d: <... completed>
+
+starting permutation: s1b s1s s2d s3i2 s3i2 s1c
+step s1b: BEGIN;
+step s1s: SELECT * FROM d_lp_fk;
+a
+-
+1
+2
+(2 rows)
+
+step s2d: ALTER TABLE d_lp_fk DETACH PARTITION d_lp_fk_1 CONCURRENTLY; <waiting ...>
+step s3i2: INSERT INTO d_lp_fk_r VALUES (2);
+step s3i2: INSERT INTO d_lp_fk_r VALUES (2);
+step s1c: COMMIT;
+step s2d: <... completed>
+
+starting permutation: s1b s1s s3i1 s2d s1c
+step s1b: BEGIN;
+step s1s: SELECT * FROM d_lp_fk;
+a
+-
+1
+2
+(2 rows)
+
+step s3i1: INSERT INTO d_lp_fk_r VALUES (1);
+step s2d: ALTER TABLE d_lp_fk DETACH PARTITION d_lp_fk_1 CONCURRENTLY;
+ERROR: removing partition "d_lp_fk_1" violates foreign key constraint "d_lp_fk_r_a_fkey1"
+step s1c: COMMIT;
+
+starting permutation: s1b s1s s3i2 s2d s1c
+step s1b: BEGIN;
+step s1s: SELECT * FROM d_lp_fk;
+a
+-
+1
+2
+(2 rows)
+
+step s3i2: INSERT INTO d_lp_fk_r VALUES (2);
+step s2d: ALTER TABLE d_lp_fk DETACH PARTITION d_lp_fk_1 CONCURRENTLY; <waiting ...>
+step s1c: COMMIT;
+step s2d: <... completed>
+
+starting permutation: s1b s1s s3b s2d s3i1 s1c s3c
+step s1b: BEGIN;
+step s1s: SELECT * FROM d_lp_fk;
+a
+-
+1
+2
+(2 rows)
+
+step s3b: BEGIN;
+step s2d: ALTER TABLE d_lp_fk DETACH PARTITION d_lp_fk_1 CONCURRENTLY; <waiting ...>
+step s3i1: INSERT INTO d_lp_fk_r VALUES (1);
+ERROR: insert or update on table "d_lp_fk_r" violates foreign key constraint "d_lp_fk_r_a_fkey"
+step s1c: COMMIT;
+step s2d: <... completed>
+step s3c: COMMIT;
diff --git a/src/test/isolation/expected/detach-partition-concurrently-3.out b/src/test/isolation/expected/detach-partition-concurrently-3.out
new file mode 100644
index 0000000..f23f46a
--- /dev/null
+++ b/src/test/isolation/expected/detach-partition-concurrently-3.out
@@ -0,0 +1,506 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s1describe s1alter
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s1describe: SELECT 'd3_listp' AS root, * FROM pg_partition_tree('d3_listp')
+ UNION ALL SELECT 'd3_listp1', * FROM pg_partition_tree('d3_listp1');
+root |relid |parentrelid|isleaf|level
+---------+---------+-----------+------+-----
+d3_listp |d3_listp | |f | 0
+d3_listp |d3_listp2|d3_listp |t | 1
+d3_listp1|d3_listp1| |t | 0
+(3 rows)
+
+step s1alter: ALTER TABLE d3_listp1 ALTER a DROP NOT NULL;
+ERROR: cannot alter partition "d3_listp1" with an incomplete detach
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1insert s1c
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1insert: INSERT INTO d3_listp VALUES (1);
+ERROR: no partition of relation "d3_listp" found for row
+step s1c: COMMIT;
+
+starting permutation: s2snitch s1brr s1s s2detach s1cancel s1insert s1c s1spart
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1insert: INSERT INTO d3_listp VALUES (1);
+step s1c: COMMIT;
+step s1spart: SELECT * FROM d3_listp1;
+a
+-
+1
+1
+(2 rows)
+
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s1insertpart
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s1insertpart: INSERT INTO d3_listp1 VALUES (1);
+
+starting permutation: s2snitch s1b s1s s2detach2 s1cancel s1c s1brr s1insert s1s s1insert s1c
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach2: ALTER TABLE d3_listp DETACH PARTITION d3_listp2 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach2: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1insert: INSERT INTO d3_listp VALUES (1);
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+1
+(2 rows)
+
+step s1insert: INSERT INTO d3_listp VALUES (1);
+step s1c: COMMIT;
+
+starting permutation: s2snitch s1b s1s s2detach2 s1cancel s1c s1brr s1s s1insert s1s s1c
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach2: ALTER TABLE d3_listp DETACH PARTITION d3_listp2 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach2: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s1insert: INSERT INTO d3_listp VALUES (1);
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+1
+(2 rows)
+
+step s1c: COMMIT;
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s1drop s1list
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s1drop: DROP TABLE d3_listp;
+step s1list: SELECT relname FROM pg_catalog.pg_class
+ WHERE relname LIKE 'd3_listp%' ORDER BY 1;
+relname
+-------
+(0 rows)
+
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s1trunc s1spart
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s1trunc: TRUNCATE TABLE d3_listp;
+step s1spart: SELECT * FROM d3_listp1;
+a
+-
+1
+(1 row)
+
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1noop s2detach2 s1c
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1noop:
+step s2detach2: ALTER TABLE d3_listp DETACH PARTITION d3_listp2 CONCURRENTLY;
+ERROR: partition "d3_listp1" already pending detach in partitioned table "public.d3_listp"
+step s1c: COMMIT;
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1noop s2detachfinal s1c s2detach2
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1noop:
+step s2detachfinal: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 FINALIZE; <waiting ...>
+step s1c: COMMIT;
+step s2detachfinal: <... completed>
+step s2detach2: ALTER TABLE d3_listp DETACH PARTITION d3_listp2 CONCURRENTLY;
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s1droppart s2detach2
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s1droppart: DROP TABLE d3_listp1;
+step s2detach2: ALTER TABLE d3_listp DETACH PARTITION d3_listp2 CONCURRENTLY;
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s2begin s2drop s1s s2commit
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s2begin: BEGIN;
+step s2drop: DROP TABLE d3_listp1;
+step s1s: SELECT * FROM d3_listp; <waiting ...>
+step s2commit: COMMIT;
+step s1s: <... completed>
+a
+-
+(0 rows)
+
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s1b s1spart s2detachfinal s1c
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s1b: BEGIN;
+step s1spart: SELECT * FROM d3_listp1;
+a
+-
+1
+(1 row)
+
+step s2detachfinal: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 FINALIZE; <waiting ...>
+step s1c: COMMIT;
+step s2detachfinal: <... completed>
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s1b s1s s2detachfinal s1c
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+(0 rows)
+
+step s2detachfinal: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 FINALIZE;
+step s1c: COMMIT;
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s1b s1spart s2detachfinal s1c
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s1b: BEGIN;
+step s1spart: SELECT * FROM d3_listp1;
+a
+-
+1
+(1 row)
+
+step s2detachfinal: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 FINALIZE; <waiting ...>
+step s1c: COMMIT;
+step s2detachfinal: <... completed>
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s2begin s2detachfinal s2commit
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s2begin: BEGIN;
+step s2detachfinal: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 FINALIZE;
+step s2commit: COMMIT;
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s2begin s2detachfinal s1spart s2commit
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s2begin: BEGIN;
+step s2detachfinal: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 FINALIZE;
+step s1spart: SELECT * FROM d3_listp1; <waiting ...>
+step s2commit: COMMIT;
+step s1spart: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s2begin s2detachfinal s1insertpart s2commit
+step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid();
+step s1b: BEGIN;
+step s1s: SELECT * FROM d3_listp;
+a
+-
+1
+(1 row)
+
+step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; <waiting ...>
+step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: COMMIT;
+step s2begin: BEGIN;
+step s2detachfinal: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 FINALIZE;
+step s1insertpart: INSERT INTO d3_listp1 VALUES (1); <waiting ...>
+step s2commit: COMMIT;
+step s1insertpart: <... completed>
diff --git a/src/test/isolation/expected/detach-partition-concurrently-4.out b/src/test/isolation/expected/detach-partition-concurrently-4.out
new file mode 100644
index 0000000..b652522
--- /dev/null
+++ b/src/test/isolation/expected/detach-partition-concurrently-4.out
@@ -0,0 +1,426 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1insert s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s1s: select * from d4_primary;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1cancel: select pg_cancel_backend(pid) from d4_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1c: commit;
+
+starting permutation: s2snitch s1b s1s s2detach s1insert s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s1s: select * from d4_primary;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s2detach: <... completed>
+step s1c: commit;
+
+starting permutation: s2snitch s1brr s1s s2detach s1cancel s1insert s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1brr: begin isolation level repeatable read;
+step s1s: select * from d4_primary;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1cancel: select pg_cancel_backend(pid) from d4_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1c: commit;
+
+starting permutation: s2snitch s1brr s1s s2detach s1insert s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1brr: begin isolation level repeatable read;
+step s1s: select * from d4_primary;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s2detach: <... completed>
+step s1c: commit;
+
+starting permutation: s2snitch s1b s1declare s2detach s1cancel s1fetchall s1insert s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s1declare: declare f cursor for select * from d4_primary;
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1cancel: select pg_cancel_backend(pid) from d4_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1fetchall: fetch all from f;
+a
+-
+1
+2
+(2 rows)
+
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1c: commit;
+
+starting permutation: s2snitch s1b s1declare s2detach s1fetchall s1insert s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s1declare: declare f cursor for select * from d4_primary;
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1fetchall: fetch all from f;
+a
+-
+1
+2
+(2 rows)
+
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s2detach: <... completed>
+step s1c: commit;
+
+starting permutation: s2snitch s1b s1declare s2detach s1cancel s1svpt s1insert s1rollback s1fetchall s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s1declare: declare f cursor for select * from d4_primary;
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1cancel: select pg_cancel_backend(pid) from d4_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1svpt: savepoint f;
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1rollback: rollback to f;
+step s1fetchall: fetch all from f;
+a
+-
+1
+2
+(2 rows)
+
+step s1c: commit;
+
+starting permutation: s2snitch s1b s1declare s2detach s1svpt s1insert s1rollback s1fetchall s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s1declare: declare f cursor for select * from d4_primary;
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1svpt: savepoint f;
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1rollback: rollback to f;
+step s1fetchall: fetch all from f;
+a
+-
+1
+2
+(2 rows)
+
+step s1c: commit;
+step s2detach: <... completed>
+
+starting permutation: s2snitch s1b s2detach s1declare s1cancel s1fetchall s1insert s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently;
+step s1declare: declare f cursor for select * from d4_primary;
+step s1cancel: select pg_cancel_backend(pid) from d4_pid;
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1fetchall: fetch all from f;
+a
+-
+2
+(1 row)
+
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1c: commit;
+
+starting permutation: s2snitch s1b s2detach s1declare s1fetchall s1insert s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently;
+step s1declare: declare f cursor for select * from d4_primary;
+step s1fetchall: fetch all from f;
+a
+-
+2
+(1 row)
+
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1c: commit;
+
+starting permutation: s2snitch s1b s2detach s1declare s1cancel s1svpt s1insert s1rollback s1fetchall s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently;
+step s1declare: declare f cursor for select * from d4_primary;
+step s1cancel: select pg_cancel_backend(pid) from d4_pid;
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1svpt: savepoint f;
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1rollback: rollback to f;
+step s1fetchall: fetch all from f;
+a
+-
+2
+(1 row)
+
+step s1c: commit;
+
+starting permutation: s2snitch s1b s2detach s1declare s1svpt s1insert s1rollback s1fetchall s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently;
+step s1declare: declare f cursor for select * from d4_primary;
+step s1svpt: savepoint f;
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1rollback: rollback to f;
+step s1fetchall: fetch all from f;
+a
+-
+2
+(1 row)
+
+step s1c: commit;
+
+starting permutation: s2snitch s1brr s1declare2 s1fetchone s2detach s1cancel s1updcur s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1brr: begin isolation level repeatable read;
+step s1declare2: declare f cursor for select * from d4_fk where a = 2;
+step s1fetchone: fetch 1 from f;
+a
+-
+2
+(1 row)
+
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1cancel: select pg_cancel_backend(pid) from d4_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1updcur: update d4_fk set a = 1 where current of f;
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1c: commit;
+
+starting permutation: s2snitch s1brr s1declare2 s1fetchone s2detach s1updcur s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1brr: begin isolation level repeatable read;
+step s1declare2: declare f cursor for select * from d4_fk where a = 2;
+step s1fetchone: fetch 1 from f;
+a
+-
+2
+(1 row)
+
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1updcur: update d4_fk set a = 1 where current of f;
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s2detach: <... completed>
+step s1c: commit;
+
+starting permutation: s2snitch s1brr s1declare2 s1fetchone s1updcur s2detach s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1brr: begin isolation level repeatable read;
+step s1declare2: declare f cursor for select * from d4_fk where a = 2;
+step s1fetchone: fetch 1 from f;
+a
+-
+2
+(1 row)
+
+step s1updcur: update d4_fk set a = 1 where current of f;
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1c: commit;
+step s2detach: <... completed>
+ERROR: removing partition "d4_primary1" violates foreign key constraint "d4_fk_a_fkey1"
+
+starting permutation: s2snitch s1b s1s s2detach s3insert s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s1s: select * from d4_primary;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s3insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1c: commit;
+step s2detach: <... completed>
+
+starting permutation: s2snitch s1b s1s s2detach s3brr s3insert s3commit s1cancel s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s1s: select * from d4_primary;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s3brr: begin isolation level repeatable read;
+step s3insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s3commit: commit;
+step s1cancel: select pg_cancel_backend(pid) from d4_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1c: commit;
+
+starting permutation: s2snitch s1b s1s s2detach s3brr s3insert s3commit s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s1s: select * from d4_primary;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s3brr: begin isolation level repeatable read;
+step s3insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s3commit: commit;
+step s1c: commit;
+step s2detach: <... completed>
+
+starting permutation: s2snitch s1brr s1s s2detach s1cancel s1noop s3vacfreeze s1s s1insert s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1brr: begin isolation level repeatable read;
+step s1s: select * from d4_primary;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1cancel: select pg_cancel_backend(pid) from d4_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1noop:
+step s3vacfreeze: vacuum freeze pg_catalog.pg_inherits;
+step s1s: select * from d4_primary;
+a
+-
+1
+2
+(2 rows)
+
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1c: commit;
+
+starting permutation: s2snitch s1b s1s s2detach s1cancel s1noop s3vacfreeze s1s s1insert s1c
+step s2snitch: insert into d4_pid select pg_backend_pid();
+step s1b: begin;
+step s1s: select * from d4_primary;
+a
+-
+1
+2
+(2 rows)
+
+step s2detach: alter table d4_primary detach partition d4_primary1 concurrently; <waiting ...>
+step s1cancel: select pg_cancel_backend(pid) from d4_pid; <waiting ...>
+step s2detach: <... completed>
+ERROR: canceling statement due to user request
+step s1cancel: <... completed>
+pg_cancel_backend
+-----------------
+t
+(1 row)
+
+step s1noop:
+step s3vacfreeze: vacuum freeze pg_catalog.pg_inherits;
+step s1s: select * from d4_primary;
+a
+-
+2
+(1 row)
+
+step s1insert: insert into d4_fk values (1);
+ERROR: insert or update on table "d4_fk" violates foreign key constraint "d4_fk_a_fkey"
+step s1c: commit;
diff --git a/src/test/isolation/expected/drop-index-concurrently-1.out b/src/test/isolation/expected/drop-index-concurrently-1.out
new file mode 100644
index 0000000..97e1a6e
--- /dev/null
+++ b/src/test/isolation/expected/drop-index-concurrently-1.out
@@ -0,0 +1,56 @@
+Parsed test spec with 3 sessions
+
+starting permutation: noseq chkiso prepi preps begin explaini explains select2 drop insert2 end2 selecti selects end
+step noseq: SET enable_seqscan = false;
+step chkiso: SELECT (setting in ('read committed','read uncommitted')) AS is_read_committed FROM pg_settings WHERE name = 'default_transaction_isolation';
+is_read_committed
+-----------------
+t
+(1 row)
+
+step prepi: PREPARE getrow_idx AS SELECT * FROM test_dc WHERE data=34 ORDER BY id,data;
+step preps: PREPARE getrow_seq AS SELECT * FROM test_dc WHERE data::text=34::text ORDER BY id,data;
+step begin: BEGIN;
+step explaini: EXPLAIN (COSTS OFF) EXECUTE getrow_idx;
+QUERY PLAN
+----------------------------------------------
+Sort
+ Sort Key: id
+ -> Index Scan using test_dc_data on test_dc
+ Index Cond: (data = 34)
+(4 rows)
+
+step explains: EXPLAIN (COSTS OFF) EXECUTE getrow_seq;
+QUERY PLAN
+----------------------------------------------
+Sort
+ Sort Key: id, data
+ -> Index Scan using test_dc_pkey on test_dc
+ Filter: ((data)::text = '34'::text)
+(4 rows)
+
+step select2: SELECT * FROM test_dc WHERE data=34 ORDER BY id,data;
+id|data
+--+----
+34| 34
+(1 row)
+
+step drop: DROP INDEX CONCURRENTLY test_dc_data; <waiting ...>
+step insert2: INSERT INTO test_dc(data) SELECT * FROM generate_series(1, 100);
+step end2: COMMIT;
+step selecti: EXECUTE getrow_idx;
+ id|data
+---+----
+ 34| 34
+134| 34
+(2 rows)
+
+step selects: EXECUTE getrow_seq;
+ id|data
+---+----
+ 34| 34
+134| 34
+(2 rows)
+
+step end: COMMIT;
+step drop: <... completed>
diff --git a/src/test/isolation/expected/drop-index-concurrently-1_2.out b/src/test/isolation/expected/drop-index-concurrently-1_2.out
new file mode 100644
index 0000000..04612d3
--- /dev/null
+++ b/src/test/isolation/expected/drop-index-concurrently-1_2.out
@@ -0,0 +1,54 @@
+Parsed test spec with 3 sessions
+
+starting permutation: noseq chkiso prepi preps begin explaini explains select2 drop insert2 end2 selecti selects end
+step noseq: SET enable_seqscan = false;
+step chkiso: SELECT (setting in ('read committed','read uncommitted')) AS is_read_committed FROM pg_settings WHERE name = 'default_transaction_isolation';
+is_read_committed
+-----------------
+f
+(1 row)
+
+step prepi: PREPARE getrow_idx AS SELECT * FROM test_dc WHERE data=34 ORDER BY id,data;
+step preps: PREPARE getrow_seq AS SELECT * FROM test_dc WHERE data::text=34::text ORDER BY id,data;
+step begin: BEGIN;
+step explaini: EXPLAIN (COSTS OFF) EXECUTE getrow_idx;
+QUERY PLAN
+----------------------------------------------
+Sort
+ Sort Key: id
+ -> Index Scan using test_dc_data on test_dc
+ Index Cond: (data = 34)
+(4 rows)
+
+step explains: EXPLAIN (COSTS OFF) EXECUTE getrow_seq;
+QUERY PLAN
+----------------------------------------------
+Sort
+ Sort Key: id, data
+ -> Index Scan using test_dc_pkey on test_dc
+ Filter: ((data)::text = '34'::text)
+(4 rows)
+
+step select2: SELECT * FROM test_dc WHERE data=34 ORDER BY id,data;
+id|data
+--+----
+34| 34
+(1 row)
+
+step drop: DROP INDEX CONCURRENTLY test_dc_data; <waiting ...>
+step insert2: INSERT INTO test_dc(data) SELECT * FROM generate_series(1, 100);
+step end2: COMMIT;
+step selecti: EXECUTE getrow_idx;
+id|data
+--+----
+34| 34
+(1 row)
+
+step selects: EXECUTE getrow_seq;
+id|data
+--+----
+34| 34
+(1 row)
+
+step end: COMMIT;
+step drop: <... completed>
diff --git a/src/test/isolation/expected/eval-plan-qual-trigger.out b/src/test/isolation/expected/eval-plan-qual-trigger.out
new file mode 100644
index 0000000..f6714c2
--- /dev/null
+++ b/src/test/isolation/expected/eval-plan-qual-trigger.out
@@ -0,0 +1,2734 @@
+unused step name: s2_r
+unused step name: s3_b_rc
+unused step name: s3_c
+unused step name: s3_del_a
+unused step name: s3_r
+unused step name: s3_upd_a_data
+Parsed test spec with 4 sessions
+
+starting permutation: s1_trig_rep_b_u s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s1_c s2_upd_a_data s2_c s0_rep
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1-ups1 <> text mismatch: t
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-ups2)
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-ups2)
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+------------------
+key-a|val-a-s1-ups1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+------------------
+key-a|val-a-s1-ups1-ups2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_u s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s1_r s2_upd_a_data s2_c s0_rep
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+step s1_r: ROLLBACK;
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s1_c s2_del_a s2_c s0_rep
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1-ups1 <> text mismatch: t
+s2: NOTICE: trigger: name rep_b_d; when: BEFORE; lev: ROWs; op: DELETE; old: (key-a,val-a-s1-ups1) new: <NULL>
+s2: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1-ups1) new: <NULL>
+step s2_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s1_r s2_del_a s2_c s0_rep
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+step s1_r: ROLLBACK;
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s2: NOTICE: trigger: name rep_b_d; when: BEFORE; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s2_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_u s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s2_upd_a_data s1_c s2_c s0_rep
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1-ups1 <> text mismatch: t
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-ups2)
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-ups2)
+step s2_upd_a_data: <... completed>
+key |data
+-----+------------------
+key-a|val-a-s1-ups1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+------------------
+key-a|val-a-s1-ups1-ups2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_u s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s2_upd_a_data s1_r s2_c s0_rep
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+step s2_upd_a_data: <... completed>
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s2_upd_a_data s1_c s2_c s0_rep
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1-ups1 <> text mismatch: t
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-ups2)
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-ups2)
+step s2_upd_a_data: <... completed>
+key |data
+-----+------------------
+key-a|val-a-s1-ups1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+------------------
+key-a|val-a-s1-ups1-ups2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s2_upd_a_data s1_r s2_c s0_rep
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+step s2_upd_a_data: <... completed>
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_del_a s2_upd_a_data s1_c s2_c s0_rep
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_d; when: BEFORE; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s1_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-b = text key-a: f
+step s2_upd_a_data: <... completed>
+key|data
+---+----
+(0 rows)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_del_a s2_upd_a_data s1_r s2_c s0_rep
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_d; when: BEFORE; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s1_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+step s2_upd_a_data: <... completed>
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_a_i s1_trig_rep_a_d s1_b_rc s2_b_rc s1_ins_a s2_ins_a s1_c s2_c s0_rep
+step s1_trig_rep_b_i: CREATE TRIGGER rep_b_i BEFORE INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_i: CREATE TRIGGER rep_a_i AFTER INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+s1: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s2)
+step s2_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s2') RETURNING *; <waiting ...>
+step s1_c: COMMIT;
+step s2_ins_a: <... completed>
+ERROR: duplicate key value violates unique constraint "trigtest_pkey"
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_a_i s1_trig_rep_a_d s1_b_rc s2_b_rc s1_ins_a s2_ins_a s1_r s2_c s0_rep
+step s1_trig_rep_b_i: CREATE TRIGGER rep_b_i BEFORE INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_i: CREATE TRIGGER rep_a_i AFTER INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+s1: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s2)
+step s2_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s2') RETURNING *; <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s2)
+step s2_ins_a: <... completed>
+key |data
+-----+--------
+key-a|val-a-s2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-a|val-a-s2
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s2_upsert_a_data s1_c s2_c s0_rep
+step s1_trig_rep_b_i: CREATE TRIGGER rep_b_i BEFORE INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_i: CREATE TRIGGER rep_a_i AFTER INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+s1: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+s1: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s1: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-b,val-b-s1)
+s1: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-b,val-b-s1)
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-upss2)
+step s2_upsert_a_data:
+ INSERT INTO trigtest VALUES ('key-a', 'val-a-upss2')
+ ON CONFLICT (key)
+ DO UPDATE SET data = trigtest.data || '-upserts2'
+ WHERE
+ noisy_oper('upd', trigtest.key, '=', 'key-a') AND
+ noisy_oper('upk', trigtest.data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1-ups1 <> text mismatch: t
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-upserts2)
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-upserts2)
+step s2_upsert_a_data: <... completed>
+key |data
+-----+----------------------
+key-a|val-a-s1-ups1-upserts2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+----------------------
+key-a|val-a-s1-ups1-upserts2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s2_upsert_a_data s1_c s2_c s0_rep
+step s1_trig_rep_b_i: CREATE TRIGGER rep_b_i BEFORE INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_i: CREATE TRIGGER rep_a_i AFTER INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+s1: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+s1: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s1: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-b,val-b-s1)
+s1: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-b,val-b-s1)
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-upss2)
+step s2_upsert_a_data:
+ INSERT INTO trigtest VALUES ('key-a', 'val-a-upss2')
+ ON CONFLICT (key)
+ DO UPDATE SET data = trigtest.data || '-upserts2'
+ WHERE
+ noisy_oper('upd', trigtest.key, '=', 'key-a') AND
+ noisy_oper('upk', trigtest.data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1-ups1 <> text mismatch: t
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-upserts2)
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-upserts2)
+step s2_upsert_a_data: <... completed>
+key |data
+-----+----------------------
+key-a|val-a-s1-ups1-upserts2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+----------------------
+key-a|val-a-s1-ups1-upserts2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u s1_b_rc s2_b_rc s1_ins_a s2_upsert_a_data s1_c s2_c s0_rep
+step s1_trig_rep_b_i: CREATE TRIGGER rep_b_i BEFORE INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_i: CREATE TRIGGER rep_a_i AFTER INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+s1: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-upss2)
+step s2_upsert_a_data:
+ INSERT INTO trigtest VALUES ('key-a', 'val-a-upss2')
+ ON CONFLICT (key)
+ DO UPDATE SET data = trigtest.data || '-upserts2'
+ WHERE
+ noisy_oper('upd', trigtest.key, '=', 'key-a') AND
+ noisy_oper('upk', trigtest.data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-upserts2)
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-upserts2)
+step s2_upsert_a_data: <... completed>
+key |data
+-----+-----------------
+key-a|val-a-s1-upserts2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-----------------
+key-a|val-a-s1-upserts2
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u s1_b_rc s2_b_rc s1_ins_a s2_upsert_a_data s1_r s2_c s0_rep
+step s1_trig_rep_b_i: CREATE TRIGGER rep_b_i BEFORE INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_i: CREATE TRIGGER rep_a_i AFTER INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+s1: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-upss2)
+step s2_upsert_a_data:
+ INSERT INTO trigtest VALUES ('key-a', 'val-a-upss2')
+ ON CONFLICT (key)
+ DO UPDATE SET data = trigtest.data || '-upserts2'
+ WHERE
+ noisy_oper('upd', trigtest.key, '=', 'key-a') AND
+ noisy_oper('upk', trigtest.data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-upss2)
+step s2_upsert_a_data: <... completed>
+key |data
+-----+-----------
+key-a|val-a-upss2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-----------
+key-a|val-a-upss2
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u s1_b_rc s2_b_rc s1_ins_a s1_upd_a_data s2_upsert_a_data s1_c s2_c s0_rep
+step s1_trig_rep_b_i: CREATE TRIGGER rep_b_i BEFORE INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_i: CREATE TRIGGER rep_a_i AFTER INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+s1: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-upss2)
+step s2_upsert_a_data:
+ INSERT INTO trigtest VALUES ('key-a', 'val-a-upss2')
+ ON CONFLICT (key)
+ DO UPDATE SET data = trigtest.data || '-upserts2'
+ WHERE
+ noisy_oper('upd', trigtest.key, '=', 'key-a') AND
+ noisy_oper('upk', trigtest.data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1-ups1 <> text mismatch: t
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-upserts2)
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-upserts2)
+step s2_upsert_a_data: <... completed>
+key |data
+-----+----------------------
+key-a|val-a-s1-ups1-upserts2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+----------------------
+key-a|val-a-s1-ups1-upserts2
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u s1_b_rc s2_b_rc s1_ins_a s1_upd_a_data s2_upsert_a_data s1_r s2_c s0_rep
+step s1_trig_rep_b_i: CREATE TRIGGER rep_b_i BEFORE INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_i: CREATE TRIGGER rep_a_i AFTER INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+s1: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-upss2)
+step s2_upsert_a_data:
+ INSERT INTO trigtest VALUES ('key-a', 'val-a-upss2')
+ ON CONFLICT (key)
+ DO UPDATE SET data = trigtest.data || '-upserts2'
+ WHERE
+ noisy_oper('upd', trigtest.key, '=', 'key-a') AND
+ noisy_oper('upk', trigtest.data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-upss2)
+step s2_upsert_a_data: <... completed>
+key |data
+-----+-----------
+key-a|val-a-upss2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-----------
+key-a|val-a-upss2
+(1 row)
+
+
+starting permutation: s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s2_upd_a_data s1_c s2_c s0_rep
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1-ups1 <> text mismatch: t
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1-ups1) new: (key-a,val-a-s1-ups1-ups2)
+step s2_upd_a_data: <... completed>
+key |data
+-----+------------------
+key-a|val-a-s1-ups1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+------------------
+key-a|val-a-s1-ups1-ups2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s2_upd_a_data s1_r s2_c s0_rep
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+step s2_upd_a_data: <... completed>
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s2_del_a s1_c s2_c s0_rep
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1-ups1 <> text mismatch: t
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1-ups1) new: <NULL>
+step s2_del_a: <... completed>
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_upd_a_data s2_del_a s1_r s2_c s0_rep
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s2_del_a: <... completed>
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_del_a s2_upd_a_data s1_c s2_c s0_rep
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s1_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-b = text key-a: f
+step s2_upd_a_data: <... completed>
+key|data
+---+----
+(0 rows)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_del_a s2_upd_a_data s1_r s2_c s0_rep
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s1_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+step s2_upd_a_data: <... completed>
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_a_d s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_del_a s2_del_a s1_c s2_c s0_rep
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s1_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-b = text key-a: f
+step s2_del_a: <... completed>
+key|data
+---+----
+(0 rows)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_a_d s1_ins_a s1_ins_b s1_b_rc s2_b_rc s1_del_a s2_del_a s1_r s2_c s0_rep
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s1_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s2_del_a: <... completed>
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_u s1_trig_rep_a_u s1_ins_a s1_ins_c s1_b_rc s2_b_rc s1_upd_a_tob s2_upd_a_data s1_c s2_c s0_rep
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_c: INSERT INTO trigtest VALUES ('key-c', 'val-c-s1') RETURNING *;
+key |data
+-----+--------
+key-c|val-c-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upk: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-b,val-a-s1-tobs1)
+s1: NOTICE: upk: text key-c = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-b,val-a-s1-tobs1)
+step s1_upd_a_tob:
+ UPDATE trigtest SET key = 'key-b', data = data || '-tobs1'
+ WHERE
+ noisy_oper('upk', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+--------------
+key-b|val-a-s1-tobs1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: upd: text key-c = text key-a: f
+step s2_upd_a_data: <... completed>
+key|data
+---+----
+(0 rows)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------------
+key-b|val-a-s1-tobs1
+key-c|val-c-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_u s1_trig_rep_a_u s1_ins_a s1_ins_c s1_b_rc s2_b_rc s1_upd_a_tob s2_upd_a_data s1_r s2_c s0_rep
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_c: INSERT INTO trigtest VALUES ('key-c', 'val-c-s1') RETURNING *;
+key |data
+-----+--------
+key-c|val-c-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upk: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-b,val-a-s1-tobs1)
+s1: NOTICE: upk: text key-c = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-b,val-a-s1-tobs1)
+step s1_upd_a_tob:
+ UPDATE trigtest SET key = 'key-b', data = data || '-tobs1'
+ WHERE
+ noisy_oper('upk', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+--------------
+key-b|val-a-s1-tobs1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+s2: NOTICE: upd: text key-c = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+step s2_upd_a_data: <... completed>
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+key-c|val-c-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_u s1_trig_rep_a_u s1_ins_a s1_ins_c s1_b_rc s2_b_rc s1_upd_a_tob s2_upd_b_data s1_c s2_c s0_rep
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_c: INSERT INTO trigtest VALUES ('key-c', 'val-c-s1') RETURNING *;
+key |data
+-----+--------
+key-c|val-c-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upk: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-b,val-a-s1-tobs1)
+s1: NOTICE: upk: text key-c = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-b,val-a-s1-tobs1)
+step s1_upd_a_tob:
+ UPDATE trigtest SET key = 'key-b', data = data || '-tobs1'
+ WHERE
+ noisy_oper('upk', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+--------------
+key-b|val-a-s1-tobs1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-b: f
+s2: NOTICE: upd: text key-c = text key-b: f
+step s2_upd_b_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-b') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key|data
+---+----
+(0 rows)
+
+step s1_c: COMMIT;
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------------
+key-b|val-a-s1-tobs1
+key-c|val-c-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_u s1_trig_rep_a_u s1_ins_a s1_ins_c s1_b_rc s2_b_rc s1_upd_a_tob s2_upd_all_data s1_c s2_c s0_rep
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_c: INSERT INTO trigtest VALUES ('key-c', 'val-c-s1') RETURNING *;
+key |data
+-----+--------
+key-c|val-c-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upk: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-b,val-a-s1-tobs1)
+s1: NOTICE: upk: text key-c = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-b,val-a-s1-tobs1)
+step s1_upd_a_tob:
+ UPDATE trigtest SET key = 'key-b', data = data || '-tobs1'
+ WHERE
+ noisy_oper('upk', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+--------------
+key-b|val-a-s1-tobs1
+(1 row)
+
+s2: NOTICE: upd: text key-a <> text mismatch: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_all_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '<>', 'mismatch') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-b <> text mismatch: t
+s2: NOTICE: upk: text val-a-s1-tobs1 <> text mismatch: t
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-b,val-a-s1-tobs1) new: (key-b,val-a-s1-tobs1-ups2)
+s2: NOTICE: upd: text key-c <> text mismatch: t
+s2: NOTICE: upk: text val-c-s1 <> text mismatch: t
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-c,val-c-s1) new: (key-c,val-c-s1-ups2)
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-b,val-a-s1-tobs1) new: (key-b,val-a-s1-tobs1-ups2)
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-c,val-c-s1) new: (key-c,val-c-s1-ups2)
+step s2_upd_all_data: <... completed>
+key |data
+-----+-------------------
+key-b|val-a-s1-tobs1-ups2
+key-c|val-c-s1-ups2
+(2 rows)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-------------------
+key-b|val-a-s1-tobs1-ups2
+key-c|val-c-s1-ups2
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_c s1_b_rc s2_b_rc s1_del_a s2_upd_a_data s1_c s2_c s0_rep
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_c: INSERT INTO trigtest VALUES ('key-c', 'val-c-s1') RETURNING *;
+key |data
+-----+--------
+key-c|val-c-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_d; when: BEFORE; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+s1: NOTICE: upd: text key-c = text key-a: f
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s1_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-c = text key-a: f
+step s2_upd_a_data: <... completed>
+key|data
+---+----
+(0 rows)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-c|val-c-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_c s1_b_rc s2_b_rc s1_del_a s2_upd_a_data s1_r s2_c s0_rep
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_c: INSERT INTO trigtest VALUES ('key-c', 'val-c-s1') RETURNING *;
+key |data
+-----+--------
+key-c|val-c-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_d; when: BEFORE; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+s1: NOTICE: upd: text key-c = text key-a: f
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s1_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+s2: NOTICE: upd: text key-c = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+step s2_upd_a_data: <... completed>
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+key-c|val-c-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_d s1_trig_rep_a_d s1_ins_a s1_ins_c s1_b_rc s2_b_rc s1_del_a s2_del_a s1_c s2_c s0_rep
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_c: INSERT INTO trigtest VALUES ('key-c', 'val-c-s1') RETURNING *;
+key |data
+-----+--------
+key-c|val-c-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_d; when: BEFORE; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+s1: NOTICE: upd: text key-c = text key-a: f
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s1_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+ <waiting ...>
+step s1_c: COMMIT;
+s2: NOTICE: upd: text key-c = text key-a: f
+step s2_del_a: <... completed>
+key|data
+---+----
+(0 rows)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-c|val-c-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_d s1_trig_rep_a_d s1_ins_a s1_ins_c s1_b_rc s2_b_rc s1_del_a s2_del_a s1_r s2_c s0_rep
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_c: INSERT INTO trigtest VALUES ('key-c', 'val-c-s1') RETURNING *;
+key |data
+-----+--------
+key-c|val-c-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_d; when: BEFORE; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+s1: NOTICE: upd: text key-c = text key-a: f
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s1_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: trigger: name rep_b_d; when: BEFORE; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+s2: NOTICE: upd: text key-c = text key-a: f
+s2: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s2_del_a: <... completed>
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-c|val-c-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_b s1_b_rc s2_b_rc s1_ins_a s1_upd_b_data s2_upd_b_data s1_del_b s1_upd_a_tob s1_c s2_c s0_rep
+step s1_trig_rep_b_i: CREATE TRIGGER rep_b_i BEFORE INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_i: CREATE TRIGGER rep_a_i AFTER INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+s1: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-b,val-b-s1)
+s1: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-b,val-b-s1)
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rc: BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: trigger: name rep_b_i; when: BEFORE; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+s1: NOTICE: trigger: name rep_a_i; when: AFTER; lev: ROWs; op: INSERT; old: <NULL> new: (key-a,val-a-s1)
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s1: NOTICE: upd: text key-b = text key-b: t
+s1: NOTICE: upk: text val-b-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-b,val-b-s1) new: (key-b,val-b-s1-ups1)
+s1: NOTICE: upd: text key-a = text key-b: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-b,val-b-s1) new: (key-b,val-b-s1-ups1)
+step s1_upd_b_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-b') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-b|val-b-s1-ups1
+(1 row)
+
+s2: NOTICE: upd: text key-b = text key-b: t
+s2: NOTICE: upk: text val-b-s1 <> text mismatch: t
+step s2_upd_b_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-b') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+s1: NOTICE: upd: text key-a = text key-b: f
+s1: NOTICE: upd: text key-b = text key-b: t
+s1: NOTICE: upk: text val-b-s1-ups1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_d; when: BEFORE; lev: ROWs; op: DELETE; old: (key-b,val-b-s1-ups1) new: <NULL>
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-b,val-b-s1-ups1) new: <NULL>
+step s1_del_b:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-b') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+-------------
+key-b|val-b-s1-ups1
+(1 row)
+
+s1: NOTICE: upk: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-b,val-a-s1-tobs1)
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-b,val-a-s1-tobs1)
+step s1_upd_a_tob:
+ UPDATE trigtest SET key = 'key-b', data = data || '-tobs1'
+ WHERE
+ noisy_oper('upk', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+--------------
+key-b|val-a-s1-tobs1
+(1 row)
+
+step s1_c: COMMIT;
+step s2_upd_b_data: <... completed>
+key|data
+---+----
+(0 rows)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------------
+key-b|val-a-s1-tobs1
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_u s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rr s2_b_rr s1_upd_a_data s2_upd_a_data s1_c s2_c s0_rep
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rr: BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rr: BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+step s2_upd_a_data: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_u s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rr s2_b_rr s1_upd_a_data s2_upd_a_data s1_r s2_c s0_rep
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rr: BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rr: BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups1)
+step s1_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+
+key |data
+-----+-------------
+key-a|val-a-s1-ups1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+step s2_upd_a_data: <... completed>
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+key-b|val-b-s1
+(2 rows)
+
+
+starting permutation: s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rr s2_b_rr s1_del_a s2_upd_a_data s1_c s2_c s0_rep
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rr: BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rr: BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_d; when: BEFORE; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s1_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_c: COMMIT;
+step s2_upd_a_data: <... completed>
+ERROR: could not serialize access due to concurrent delete
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+
+starting permutation: s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u s1_ins_a s1_ins_b s1_b_rr s2_b_rr s1_del_a s2_upd_a_data s1_r s2_c s0_rep
+step s1_trig_rep_b_d: CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_b_u: CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_d: CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_trig_rep_a_u: CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report();
+step s1_ins_a: INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *;
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+step s1_ins_b: INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *;
+key |data
+-----+--------
+key-b|val-b-s1
+(1 row)
+
+step s1_b_rr: BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2_b_rr: BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+s1: NOTICE: upd: text key-a = text key-a: t
+s1: NOTICE: upk: text val-a-s1 <> text mismatch: t
+s1: NOTICE: trigger: name rep_b_d; when: BEFORE; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+s1: NOTICE: upd: text key-b = text key-a: f
+s1: NOTICE: trigger: name rep_a_d; when: AFTER; lev: ROWs; op: DELETE; old: (key-a,val-a-s1) new: <NULL>
+step s1_del_a:
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+
+key |data
+-----+--------
+key-a|val-a-s1
+(1 row)
+
+s2: NOTICE: upd: text key-a = text key-a: t
+s2: NOTICE: upk: text val-a-s1 <> text mismatch: t
+step s2_upd_a_data:
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+ <waiting ...>
+step s1_r: ROLLBACK;
+s2: NOTICE: trigger: name rep_b_u; when: BEFORE; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+s2: NOTICE: upd: text key-b = text key-a: f
+s2: NOTICE: trigger: name rep_a_u; when: AFTER; lev: ROWs; op: UPDATE; old: (key-a,val-a-s1) new: (key-a,val-a-s1-ups2)
+step s2_upd_a_data: <... completed>
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+(1 row)
+
+step s2_c: COMMIT;
+step s0_rep: SELECT * FROM trigtest ORDER BY key, data
+key |data
+-----+-------------
+key-a|val-a-s1-ups2
+key-b|val-b-s1
+(2 rows)
+
diff --git a/src/test/isolation/expected/eval-plan-qual.out b/src/test/isolation/expected/eval-plan-qual.out
new file mode 100644
index 0000000..d906350
--- /dev/null
+++ b/src/test/isolation/expected/eval-plan-qual.out
@@ -0,0 +1,1179 @@
+Parsed test spec with 3 sessions
+
+starting permutation: wx1 wx2 c1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; <waiting ...>
+step c1: COMMIT;
+step wx2: <... completed>
+balance
+-------
+ 850
+(1 row)
+
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 850
+savings | 600
+(2 rows)
+
+
+starting permutation: wy1 wy2 c1 c2 read
+step wy1: UPDATE accounts SET balance = balance + 500 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 1100
+(1 row)
+
+step wy2: UPDATE accounts SET balance = balance + 1000 WHERE accountid = 'checking' AND balance < 1000 RETURNING balance; <waiting ...>
+step c1: COMMIT;
+step wy2: <... completed>
+balance
+-------
+(0 rows)
+
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 1100
+savings | 600
+(2 rows)
+
+
+starting permutation: wx1 wx2 r1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; <waiting ...>
+step r1: ROLLBACK;
+step wx2: <... completed>
+balance
+-------
+ 1050
+(1 row)
+
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 1050
+savings | 600
+(2 rows)
+
+
+starting permutation: wy1 wy2 r1 c2 read
+step wy1: UPDATE accounts SET balance = balance + 500 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 1100
+(1 row)
+
+step wy2: UPDATE accounts SET balance = balance + 1000 WHERE accountid = 'checking' AND balance < 1000 RETURNING balance; <waiting ...>
+step r1: ROLLBACK;
+step wy2: <... completed>
+balance
+-------
+ 1600
+(1 row)
+
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 1600
+savings | 600
+(2 rows)
+
+
+starting permutation: wx1 d1 wx2 c1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; <waiting ...>
+step c1: COMMIT;
+step wx2: <... completed>
+balance
+-------
+(0 rows)
+
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+savings | 600
+(1 row)
+
+
+starting permutation: wx2 d1 c2 c1 read
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 1050
+(1 row)
+
+step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; <waiting ...>
+step c2: COMMIT;
+step d1: <... completed>
+balance
+-------
+ 1050
+(1 row)
+
+step c1: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+savings | 600
+(1 row)
+
+
+starting permutation: wx2 wx2 d1 c2 c1 read
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 1050
+(1 row)
+
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 1500
+(1 row)
+
+step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; <waiting ...>
+step c2: COMMIT;
+step d1: <... completed>
+balance
+-------
+(0 rows)
+
+step c1: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 1500
+savings | 600
+(2 rows)
+
+
+starting permutation: wx2 d2 d1 c2 c1 read
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 1050
+(1 row)
+
+step d2: DELETE FROM accounts WHERE accountid = 'checking';
+step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; <waiting ...>
+step c2: COMMIT;
+step d1: <... completed>
+balance
+-------
+(0 rows)
+
+step c1: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+savings | 600
+(1 row)
+
+
+starting permutation: wx1 d1 wx2 r1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; <waiting ...>
+step r1: ROLLBACK;
+step wx2: <... completed>
+balance
+-------
+ 1050
+(1 row)
+
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 1050
+savings | 600
+(2 rows)
+
+
+starting permutation: wx2 d1 r2 c1 read
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 1050
+(1 row)
+
+step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; <waiting ...>
+step r2: ROLLBACK;
+step d1: <... completed>
+balance
+-------
+ 600
+(1 row)
+
+step c1: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+savings | 600
+(1 row)
+
+
+starting permutation: wx2 wx2 d1 r2 c1 read
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 1050
+(1 row)
+
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 1500
+(1 row)
+
+step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; <waiting ...>
+step r2: ROLLBACK;
+step d1: <... completed>
+balance
+-------
+ 600
+(1 row)
+
+step c1: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+savings | 600
+(1 row)
+
+
+starting permutation: wx2 d2 d1 r2 c1 read
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 1050
+(1 row)
+
+step d2: DELETE FROM accounts WHERE accountid = 'checking';
+step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; <waiting ...>
+step r2: ROLLBACK;
+step d1: <... completed>
+balance
+-------
+ 600
+(1 row)
+
+step c1: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+savings | 600
+(1 row)
+
+
+starting permutation: d1 wx2 c1 c2 read
+step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance;
+balance
+-------
+ 600
+(1 row)
+
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; <waiting ...>
+step c1: COMMIT;
+step wx2: <... completed>
+balance
+-------
+(0 rows)
+
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+savings | 600
+(1 row)
+
+
+starting permutation: d1 wx2 r1 c2 read
+step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance;
+balance
+-------
+ 600
+(1 row)
+
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; <waiting ...>
+step r1: ROLLBACK;
+step wx2: <... completed>
+balance
+-------
+ 1050
+(1 row)
+
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 1050
+savings | 600
+(2 rows)
+
+
+starting permutation: wnested2 c1 c2 read
+s2: NOTICE: upid: text checking = text checking: t
+s2: NOTICE: up: numeric 600 > numeric 200.0: t
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 600 > numeric 200.0: t
+s2: NOTICE: upid: text savings = text checking: f
+step wnested2:
+ UPDATE accounts SET balance = balance - 1200
+ WHERE noisy_oper('upid', accountid, '=', 'checking')
+ AND noisy_oper('up', balance, '>', 200.0)
+ AND EXISTS (
+ SELECT accountid
+ FROM accounts_ext ae
+ WHERE noisy_oper('lock_id', ae.accountid, '=', accounts.accountid)
+ AND noisy_oper('lock_bal', ae.balance, '>', 200.0)
+ FOR UPDATE
+ );
+
+step c1: COMMIT;
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | -600
+savings | 600
+(2 rows)
+
+
+starting permutation: wx1 wxext1 wnested2 c1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step wxext1: UPDATE accounts_ext SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+s2: NOTICE: upid: text checking = text checking: t
+s2: NOTICE: up: numeric 600 > numeric 200.0: t
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 600 > numeric 200.0: t
+step wnested2:
+ UPDATE accounts SET balance = balance - 1200
+ WHERE noisy_oper('upid', accountid, '=', 'checking')
+ AND noisy_oper('up', balance, '>', 200.0)
+ AND EXISTS (
+ SELECT accountid
+ FROM accounts_ext ae
+ WHERE noisy_oper('lock_id', ae.accountid, '=', accounts.accountid)
+ AND noisy_oper('lock_bal', ae.balance, '>', 200.0)
+ FOR UPDATE
+ );
+ <waiting ...>
+step c1: COMMIT;
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 400 > numeric 200.0: t
+s2: NOTICE: upid: text checking = text checking: t
+s2: NOTICE: up: numeric 400 > numeric 200.0: t
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 600 > numeric 200.0: t
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 400 > numeric 200.0: t
+s2: NOTICE: upid: text savings = text checking: f
+step wnested2: <... completed>
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | -800
+savings | 600
+(2 rows)
+
+
+starting permutation: wx1 wx1 wxext1 wnested2 c1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 200
+(1 row)
+
+step wxext1: UPDATE accounts_ext SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+s2: NOTICE: upid: text checking = text checking: t
+s2: NOTICE: up: numeric 600 > numeric 200.0: t
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 600 > numeric 200.0: t
+step wnested2:
+ UPDATE accounts SET balance = balance - 1200
+ WHERE noisy_oper('upid', accountid, '=', 'checking')
+ AND noisy_oper('up', balance, '>', 200.0)
+ AND EXISTS (
+ SELECT accountid
+ FROM accounts_ext ae
+ WHERE noisy_oper('lock_id', ae.accountid, '=', accounts.accountid)
+ AND noisy_oper('lock_bal', ae.balance, '>', 200.0)
+ FOR UPDATE
+ );
+ <waiting ...>
+step c1: COMMIT;
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 400 > numeric 200.0: t
+s2: NOTICE: upid: text checking = text checking: t
+s2: NOTICE: up: numeric 200 > numeric 200.0: f
+s2: NOTICE: upid: text savings = text checking: f
+step wnested2: <... completed>
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 200
+savings | 600
+(2 rows)
+
+
+starting permutation: wx1 wx1 wxext1 wxext1 wnested2 c1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 200
+(1 row)
+
+step wxext1: UPDATE accounts_ext SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step wxext1: UPDATE accounts_ext SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 200
+(1 row)
+
+s2: NOTICE: upid: text checking = text checking: t
+s2: NOTICE: up: numeric 600 > numeric 200.0: t
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 600 > numeric 200.0: t
+step wnested2:
+ UPDATE accounts SET balance = balance - 1200
+ WHERE noisy_oper('upid', accountid, '=', 'checking')
+ AND noisy_oper('up', balance, '>', 200.0)
+ AND EXISTS (
+ SELECT accountid
+ FROM accounts_ext ae
+ WHERE noisy_oper('lock_id', ae.accountid, '=', accounts.accountid)
+ AND noisy_oper('lock_bal', ae.balance, '>', 200.0)
+ FOR UPDATE
+ );
+ <waiting ...>
+step c1: COMMIT;
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 200 > numeric 200.0: f
+s2: NOTICE: lock_id: text savings = text checking: f
+s2: NOTICE: upid: text savings = text checking: f
+step wnested2: <... completed>
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 200
+savings | 600
+(2 rows)
+
+
+starting permutation: wx1 wxext1 wxext1 wnested2 c1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step wxext1: UPDATE accounts_ext SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step wxext1: UPDATE accounts_ext SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 200
+(1 row)
+
+s2: NOTICE: upid: text checking = text checking: t
+s2: NOTICE: up: numeric 600 > numeric 200.0: t
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 600 > numeric 200.0: t
+step wnested2:
+ UPDATE accounts SET balance = balance - 1200
+ WHERE noisy_oper('upid', accountid, '=', 'checking')
+ AND noisy_oper('up', balance, '>', 200.0)
+ AND EXISTS (
+ SELECT accountid
+ FROM accounts_ext ae
+ WHERE noisy_oper('lock_id', ae.accountid, '=', accounts.accountid)
+ AND noisy_oper('lock_bal', ae.balance, '>', 200.0)
+ FOR UPDATE
+ );
+ <waiting ...>
+step c1: COMMIT;
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 200 > numeric 200.0: f
+s2: NOTICE: lock_id: text savings = text checking: f
+s2: NOTICE: upid: text savings = text checking: f
+step wnested2: <... completed>
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 400
+savings | 600
+(2 rows)
+
+
+starting permutation: wx1 tocds1 wnested2 c1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step tocds1: UPDATE accounts SET accountid = 'cds' WHERE accountid = 'checking';
+s2: NOTICE: upid: text checking = text checking: t
+s2: NOTICE: up: numeric 600 > numeric 200.0: t
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 600 > numeric 200.0: t
+step wnested2:
+ UPDATE accounts SET balance = balance - 1200
+ WHERE noisy_oper('upid', accountid, '=', 'checking')
+ AND noisy_oper('up', balance, '>', 200.0)
+ AND EXISTS (
+ SELECT accountid
+ FROM accounts_ext ae
+ WHERE noisy_oper('lock_id', ae.accountid, '=', accounts.accountid)
+ AND noisy_oper('lock_bal', ae.balance, '>', 200.0)
+ FOR UPDATE
+ );
+ <waiting ...>
+step c1: COMMIT;
+s2: NOTICE: upid: text cds = text checking: f
+s2: NOTICE: upid: text savings = text checking: f
+step wnested2: <... completed>
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+cds | 400
+savings | 600
+(2 rows)
+
+
+starting permutation: wx1 tocdsext1 wnested2 c1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step tocdsext1: UPDATE accounts_ext SET accountid = 'cds' WHERE accountid = 'checking';
+s2: NOTICE: upid: text checking = text checking: t
+s2: NOTICE: up: numeric 600 > numeric 200.0: t
+s2: NOTICE: lock_id: text checking = text checking: t
+s2: NOTICE: lock_bal: numeric 600 > numeric 200.0: t
+step wnested2:
+ UPDATE accounts SET balance = balance - 1200
+ WHERE noisy_oper('upid', accountid, '=', 'checking')
+ AND noisy_oper('up', balance, '>', 200.0)
+ AND EXISTS (
+ SELECT accountid
+ FROM accounts_ext ae
+ WHERE noisy_oper('lock_id', ae.accountid, '=', accounts.accountid)
+ AND noisy_oper('lock_bal', ae.balance, '>', 200.0)
+ FOR UPDATE
+ );
+ <waiting ...>
+step c1: COMMIT;
+s2: NOTICE: lock_id: text cds = text checking: f
+s2: NOTICE: lock_id: text savings = text checking: f
+s2: NOTICE: upid: text savings = text checking: f
+step wnested2: <... completed>
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 400
+savings | 600
+(2 rows)
+
+
+starting permutation: wx1 updwcte c1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step updwcte: WITH doup AS (UPDATE accounts SET balance = balance + 1100 WHERE accountid = 'checking' RETURNING *) UPDATE accounts a SET balance = doup.balance + 100 FROM doup RETURNING *; <waiting ...>
+step c1: COMMIT;
+step updwcte: <... completed>
+accountid|balance|accountid|balance
+---------+-------+---------+-------
+savings | 1600|checking | 1500
+(1 row)
+
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 1500
+savings | 1600
+(2 rows)
+
+
+starting permutation: wx1 updwctefail c1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step updwctefail: WITH doup AS (UPDATE accounts SET balance = balance + 1100 WHERE accountid = 'checking' RETURNING *, update_checking(999)) UPDATE accounts a SET balance = doup.balance + 100 FROM doup RETURNING *; <waiting ...>
+step c1: COMMIT;
+step updwctefail: <... completed>
+ERROR: tuple to be updated was already modified by an operation triggered by the current command
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 400
+savings | 600
+(2 rows)
+
+
+starting permutation: wx1 delwcte c1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step delwcte: WITH doup AS (UPDATE accounts SET balance = balance + 1100 WHERE accountid = 'checking' RETURNING *) DELETE FROM accounts a USING doup RETURNING *; <waiting ...>
+step c1: COMMIT;
+step delwcte: <... completed>
+accountid|balance|accountid|balance
+---------+-------+---------+-------
+savings | 600|checking | 1500
+(1 row)
+
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 1500
+(1 row)
+
+
+starting permutation: wx1 delwctefail c1 c2 read
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 400
+(1 row)
+
+step delwctefail: WITH doup AS (UPDATE accounts SET balance = balance + 1100 WHERE accountid = 'checking' RETURNING *, update_checking(999)) DELETE FROM accounts a USING doup RETURNING *; <waiting ...>
+step c1: COMMIT;
+step delwctefail: <... completed>
+ERROR: tuple to be deleted was already modified by an operation triggered by the current command
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 400
+savings | 600
+(2 rows)
+
+
+starting permutation: upsert1 upsert2 c1 c2 read
+step upsert1:
+ WITH upsert AS
+ (UPDATE accounts SET balance = balance + 500
+ WHERE accountid = 'savings'
+ RETURNING accountid)
+ INSERT INTO accounts SELECT 'savings', 500
+ WHERE NOT EXISTS (SELECT 1 FROM upsert);
+
+step upsert2:
+ WITH upsert AS
+ (UPDATE accounts SET balance = balance + 1234
+ WHERE accountid = 'savings'
+ RETURNING accountid)
+ INSERT INTO accounts SELECT 'savings', 1234
+ WHERE NOT EXISTS (SELECT 1 FROM upsert);
+ <waiting ...>
+step c1: COMMIT;
+step upsert2: <... completed>
+step c2: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 600
+savings | 2334
+(2 rows)
+
+
+starting permutation: readp1 writep1 readp2 c1 c2
+step readp1: SELECT tableoid::regclass, ctid, * FROM p WHERE b IN (0, 1) AND c = 0 FOR UPDATE;
+tableoid|ctid |a|b|c
+--------+-----+-+-+-
+c1 |(0,1)|0|0|0
+c1 |(0,4)|0|1|0
+c2 |(0,1)|1|0|0
+c2 |(0,4)|1|1|0
+c3 |(0,1)|2|0|0
+c3 |(0,4)|2|1|0
+(6 rows)
+
+step writep1: UPDATE p SET b = -1 WHERE a = 1 AND b = 1 AND c = 0;
+step readp2: SELECT tableoid::regclass, ctid, * FROM p WHERE b IN (0, 1) AND c = 0 FOR UPDATE; <waiting ...>
+step c1: COMMIT;
+step readp2: <... completed>
+tableoid|ctid |a|b|c
+--------+-----+-+-+-
+c1 |(0,1)|0|0|0
+c1 |(0,4)|0|1|0
+c2 |(0,1)|1|0|0
+c3 |(0,1)|2|0|0
+c3 |(0,4)|2|1|0
+(5 rows)
+
+step c2: COMMIT;
+
+starting permutation: writep2 returningp1 c1 c2
+step writep2: UPDATE p SET b = -b WHERE a = 1 AND c = 0;
+step returningp1:
+ WITH u AS ( UPDATE p SET b = b WHERE a > 0 RETURNING * )
+ SELECT * FROM u;
+ <waiting ...>
+step c1: COMMIT;
+step returningp1: <... completed>
+a| b|c
+-+--+-
+1| 0|0
+1| 0|1
+1| 0|2
+1|-1|0
+1| 1|1
+1| 1|2
+1|-2|0
+1| 2|1
+1| 2|2
+1|-3|0
+2| 0|0
+2| 0|1
+2| 0|2
+2| 1|0
+2| 1|1
+2| 1|2
+2| 2|0
+2| 2|1
+2| 2|2
+2| 3|0
+(20 rows)
+
+step c2: COMMIT;
+
+starting permutation: writep3a writep3b c1 c2
+step writep3a: UPDATE p SET b = -b WHERE c = 0;
+step writep3b: UPDATE p SET b = -b WHERE c = 0; <waiting ...>
+step c1: COMMIT;
+step writep3b: <... completed>
+step c2: COMMIT;
+
+starting permutation: wx2 partiallock c2 c1 read
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 1050
+(1 row)
+
+step partiallock:
+ SELECT * FROM accounts a1, accounts a2
+ WHERE a1.accountid = a2.accountid
+ FOR UPDATE OF a1;
+ <waiting ...>
+step c2: COMMIT;
+step partiallock: <... completed>
+accountid|balance|accountid|balance
+---------+-------+---------+-------
+checking | 1050|checking | 600
+savings | 600|savings | 600
+(2 rows)
+
+step c1: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 1050
+savings | 600
+(2 rows)
+
+
+starting permutation: wx2 lockwithvalues c2 c1 read
+step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance;
+balance
+-------
+ 1050
+(1 row)
+
+step lockwithvalues:
+ -- Reference rowmark column that differs in type from targetlist at some attno.
+ -- See CAHU7rYZo_C4ULsAx_LAj8az9zqgrD8WDd4hTegDTMM1LMqrBsg@mail.gmail.com
+ SELECT a1.*, v.id FROM accounts a1, (values('checking'::text, 'nan'::text),('savings', 'nan')) v(id, notnumeric)
+ WHERE a1.accountid = v.id AND v.notnumeric != 'einszwei'
+ FOR UPDATE OF a1;
+ <waiting ...>
+step c2: COMMIT;
+step lockwithvalues: <... completed>
+accountid|balance|id
+---------+-------+--------
+checking | 1050|checking
+savings | 600|savings
+(2 rows)
+
+step c1: COMMIT;
+step read: SELECT * FROM accounts ORDER BY accountid;
+accountid|balance
+---------+-------
+checking | 1050
+savings | 600
+(2 rows)
+
+
+starting permutation: wx2_ext partiallock_ext c2 c1 read_ext
+step wx2_ext: UPDATE accounts_ext SET balance = balance + 450;
+step partiallock_ext:
+ SELECT * FROM accounts_ext a1, accounts_ext a2
+ WHERE a1.accountid = a2.accountid
+ FOR UPDATE OF a1;
+ <waiting ...>
+step c2: COMMIT;
+step partiallock_ext: <... completed>
+accountid|balance|other|newcol|newcol2|accountid|balance|other|newcol|newcol2
+---------+-------+-----+------+-------+---------+-------+-----+------+-------
+checking | 1050|other| 42| |checking | 600|other| 42|
+savings | 1150| | 42| |savings | 700| | 42|
+(2 rows)
+
+step c1: COMMIT;
+step read_ext: SELECT * FROM accounts_ext ORDER BY accountid;
+accountid|balance|other|newcol|newcol2
+---------+-------+-----+------+-------
+checking | 1050|other| 42|
+savings | 1150| | 42|
+(2 rows)
+
+
+starting permutation: updateforss readforss c1 c2
+step updateforss:
+ UPDATE table_a SET value = 'newTableAValue' WHERE id = 1;
+ UPDATE table_b SET value = 'newTableBValue' WHERE id = 1;
+
+step readforss:
+ SELECT ta.id AS ta_id, ta.value AS ta_value,
+ (SELECT ROW(tb.id, tb.value)
+ FROM table_b tb WHERE ta.id = tb.id) AS tb_row
+ FROM table_a ta
+ WHERE ta.id = 1 FOR UPDATE OF ta;
+ <waiting ...>
+step c1: COMMIT;
+step readforss: <... completed>
+ta_id|ta_value |tb_row
+-----+--------------+---------------
+ 1|newTableAValue|(1,tableBValue)
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: updateforcip updateforcip2 c1 c2 read_a
+step updateforcip:
+ UPDATE table_a SET value = NULL WHERE id = 1;
+
+step updateforcip2:
+ UPDATE table_a SET value = COALESCE(value, (SELECT text 'newValue')) WHERE id = 1;
+ <waiting ...>
+step c1: COMMIT;
+step updateforcip2: <... completed>
+step c2: COMMIT;
+step read_a: SELECT * FROM table_a ORDER BY id;
+id|value
+--+--------
+ 1|newValue
+(1 row)
+
+
+starting permutation: updateforcip updateforcip3 c1 c2 read_a
+step updateforcip:
+ UPDATE table_a SET value = NULL WHERE id = 1;
+
+step updateforcip3:
+ WITH d(val) AS (SELECT text 'newValue' FROM generate_series(1,1))
+ UPDATE table_a SET value = COALESCE(value, (SELECT val FROM d)) WHERE id = 1;
+ <waiting ...>
+step c1: COMMIT;
+step updateforcip3: <... completed>
+step c2: COMMIT;
+step read_a: SELECT * FROM table_a ORDER BY id;
+id|value
+--+--------
+ 1|newValue
+(1 row)
+
+
+starting permutation: wrtwcte readwcte c1 c2
+step wrtwcte: UPDATE table_a SET value = 'tableAValue2' WHERE id = 1;
+step readwcte:
+ WITH
+ cte1 AS (
+ SELECT id FROM table_b WHERE value = 'tableBValue'
+ ),
+ cte2 AS (
+ SELECT * FROM table_a
+ WHERE id = (SELECT id FROM cte1)
+ FOR UPDATE
+ )
+ SELECT * FROM cte2;
+ <waiting ...>
+step c1: COMMIT;
+step c2: COMMIT;
+step readwcte: <... completed>
+id|value
+--+------------
+ 1|tableAValue2
+(1 row)
+
+
+starting permutation: wrjt selectjoinforupdate c2 c1
+step wrjt: UPDATE jointest SET data = 42 WHERE id = 7;
+step selectjoinforupdate:
+ set local enable_nestloop to 0;
+ set local enable_hashjoin to 0;
+ set local enable_seqscan to 0;
+ explain (costs off)
+ select * from jointest a join jointest b on a.id=b.id for update;
+ select * from jointest a join jointest b on a.id=b.id for update;
+ <waiting ...>
+step c2: COMMIT;
+step selectjoinforupdate: <... completed>
+QUERY PLAN
+----------------------------------------------------------
+LockRows
+ -> Merge Join
+ Merge Cond: (a.id = b.id)
+ -> Index Scan using jointest_id_idx on jointest a
+ -> Index Scan using jointest_id_idx on jointest b
+(5 rows)
+
+id|data|id|data
+--+----+--+----
+ 1| 0| 1| 0
+ 2| 0| 2| 0
+ 3| 0| 3| 0
+ 4| 0| 4| 0
+ 5| 0| 5| 0
+ 6| 0| 6| 0
+ 7| 42| 7| 42
+ 8| 0| 8| 0
+ 9| 0| 9| 0
+10| 0|10| 0
+(10 rows)
+
+step c1: COMMIT;
+
+starting permutation: wrjt selectresultforupdate c2 c1
+step wrjt: UPDATE jointest SET data = 42 WHERE id = 7;
+step selectresultforupdate:
+ select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
+ left join table_a a on a.id = x, jointest jt
+ where jt.id = y;
+ explain (verbose, costs off)
+ select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
+ left join table_a a on a.id = x, jointest jt
+ where jt.id = y for update of jt, ss1, ss2;
+ select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
+ left join table_a a on a.id = x, jointest jt
+ where jt.id = y for update of jt, ss1, ss2;
+ <waiting ...>
+step c2: COMMIT;
+step selectresultforupdate: <... completed>
+x|y|id|value |id|data
+-+-+--+-----------+--+----
+1|7| 1|tableAValue| 7| 0
+(1 row)
+
+QUERY PLAN
+--------------------------------------------------------------------
+LockRows
+ Output: 1, 7, a.id, a.value, jt.id, jt.data, jt.ctid, a.ctid
+ -> Nested Loop Left Join
+ Output: 1, 7, a.id, a.value, jt.id, jt.data, jt.ctid, a.ctid
+ -> Nested Loop
+ Output: jt.id, jt.data, jt.ctid
+ -> Seq Scan on public.jointest jt
+ Output: jt.id, jt.data, jt.ctid
+ Filter: (jt.id = 7)
+ -> Result
+ -> Seq Scan on public.table_a a
+ Output: a.id, a.value, a.ctid
+ Filter: (a.id = 1)
+(13 rows)
+
+x|y|id|value |id|data
+-+-+--+-----------+--+----
+1|7| 1|tableAValue| 7| 42
+(1 row)
+
+step c1: COMMIT;
+
+starting permutation: wrtwcte multireadwcte c1 c2
+step wrtwcte: UPDATE table_a SET value = 'tableAValue2' WHERE id = 1;
+step multireadwcte:
+ WITH updated AS (
+ UPDATE table_a SET value = 'tableAValue3' WHERE id = 1 RETURNING id
+ )
+ SELECT (SELECT id FROM updated) AS subid, * FROM updated;
+ <waiting ...>
+step c1: COMMIT;
+step c2: COMMIT;
+step multireadwcte: <... completed>
+subid|id
+-----+--
+ 1| 1
+(1 row)
+
+
+starting permutation: simplepartupdate complexpartupdate c1 c2
+step simplepartupdate:
+ update parttbl set a = a;
+
+step complexpartupdate:
+ with u as (update parttbl set a = a returning parttbl.*)
+ update parttbl set a = u.a from u;
+ <waiting ...>
+step c1: COMMIT;
+step complexpartupdate: <... completed>
+step c2: COMMIT;
+
+starting permutation: simplepartupdate_route1to2 complexpartupdate_route_err1 c1 c2
+step simplepartupdate_route1to2:
+ update parttbl set a = 2 where c = 1 returning *;
+
+a|b|c
+-+-+-
+2|1|1
+(1 row)
+
+step complexpartupdate_route_err1:
+ with u as (update another_parttbl set a = 1 returning another_parttbl.*)
+ update parttbl p set a = u.a from u where p.a = u.a and p.c = 1 returning p.*;
+ <waiting ...>
+step c1: COMMIT;
+step complexpartupdate_route_err1: <... completed>
+ERROR: tuple to be locked was already moved to another partition due to concurrent update
+step c2: COMMIT;
+
+starting permutation: simplepartupdate_noroute complexpartupdate_route c1 c2
+step simplepartupdate_noroute:
+ update parttbl set b = 2 where c = 1 returning *;
+
+a|b|c
+-+-+-
+1|2|1
+(1 row)
+
+step complexpartupdate_route:
+ with u as (update another_parttbl set a = 1 returning another_parttbl.*)
+ update parttbl p set a = p.b from u where p.a = u.a and p.c = 1 returning p.*;
+ <waiting ...>
+step c1: COMMIT;
+step complexpartupdate_route: <... completed>
+a|b|c
+-+-+-
+2|2|1
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: simplepartupdate_noroute complexpartupdate_doesnt_route c1 c2
+step simplepartupdate_noroute:
+ update parttbl set b = 2 where c = 1 returning *;
+
+a|b|c
+-+-+-
+1|2|1
+(1 row)
+
+step complexpartupdate_doesnt_route:
+ with u as (update another_parttbl set a = 1 returning another_parttbl.*)
+ update parttbl p set a = 3 - p.b from u where p.a = u.a and p.c = 1 returning p.*;
+ <waiting ...>
+step c1: COMMIT;
+step complexpartupdate_doesnt_route: <... completed>
+a|b|c
+-+-+-
+1|2|1
+(1 row)
+
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/fk-contention.out b/src/test/isolation/expected/fk-contention.out
new file mode 100644
index 0000000..0916f7f
--- /dev/null
+++ b/src/test/isolation/expected/fk-contention.out
@@ -0,0 +1,16 @@
+Parsed test spec with 2 sessions
+
+starting permutation: ins com upd
+step ins: INSERT INTO bar VALUES (42);
+step com: COMMIT;
+step upd: UPDATE foo SET b = 'Hello World';
+
+starting permutation: ins upd com
+step ins: INSERT INTO bar VALUES (42);
+step upd: UPDATE foo SET b = 'Hello World';
+step com: COMMIT;
+
+starting permutation: upd ins com
+step upd: UPDATE foo SET b = 'Hello World';
+step ins: INSERT INTO bar VALUES (42);
+step com: COMMIT;
diff --git a/src/test/isolation/expected/fk-deadlock.out b/src/test/isolation/expected/fk-deadlock.out
new file mode 100644
index 0000000..ce6ef8c
--- /dev/null
+++ b/src/test/isolation/expected/fk-deadlock.out
@@ -0,0 +1,119 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1i s1u s1c s2i s2u s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+
+starting permutation: s1i s1u s2i s1c s2u s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1c: COMMIT;
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+
+starting permutation: s1i s1u s2i s2u s1c s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
+step s1c: COMMIT;
+step s2u: <... completed>
+step s2c: COMMIT;
+
+starting permutation: s1i s2i s1u s1c s2u s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+
+starting permutation: s1i s2i s1u s2u s1c s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
+step s1c: COMMIT;
+step s2u: <... completed>
+step s2c: COMMIT;
+
+starting permutation: s1i s2i s2u s1u s2c s1c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
+step s2c: COMMIT;
+step s1u: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s1i s2i s2u s2c s1u s1c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
+
+starting permutation: s2i s1i s1u s1c s2u s2c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+
+starting permutation: s2i s1i s1u s2u s1c s2c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
+step s1c: COMMIT;
+step s2u: <... completed>
+step s2c: COMMIT;
+
+starting permutation: s2i s1i s2u s1u s2c s1c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
+step s2c: COMMIT;
+step s1u: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2i s1i s2u s2c s1u s1c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
+
+starting permutation: s2i s2u s1i s1u s2c s1c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
+step s2c: COMMIT;
+step s1u: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2i s2u s1i s2c s1u s1c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2c: COMMIT;
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
+
+starting permutation: s2i s2u s2c s1i s1u s1c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/fk-deadlock2.out b/src/test/isolation/expected/fk-deadlock2.out
new file mode 100644
index 0000000..41a818d
--- /dev/null
+++ b/src/test/isolation/expected/fk-deadlock2.out
@@ -0,0 +1,95 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1u1 s1u2 s1c s2u1 s2u2 s2c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1c: COMMIT;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+
+starting permutation: s1u1 s1u2 s2u1 s1c s2u2 s2c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s1c: COMMIT;
+step s2u1: <... completed>
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+
+starting permutation: s1u1 s2u1 s1u2 s2u2 s2c s1c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u2: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s1u1 s2u1 s2u2 s1u2 s2c s1c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2c: COMMIT;
+step s1u2: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s1u1 s2u1 s2u2 s2c s1u2 s1c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1c: COMMIT;
+
+starting permutation: s2u1 s1u1 s1u2 s2u2 s2c s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u2: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2u1 s1u1 s2u2 s1u2 s2c s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2c: COMMIT;
+step s1u2: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2u1 s1u1 s2u2 s2c s1u2 s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1c: COMMIT;
+
+starting permutation: s2u1 s2u2 s1u1 s1u2 s2c s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2c: COMMIT;
+step s1u2: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s2u1 s2u2 s1u1 s2c s1u2 s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2c: COMMIT;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1c: COMMIT;
+
+starting permutation: s2u1 s2u2 s2c s1u1 s1u2 s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/fk-deadlock2_1.out b/src/test/isolation/expected/fk-deadlock2_1.out
new file mode 100644
index 0000000..e1d8c69
--- /dev/null
+++ b/src/test/isolation/expected/fk-deadlock2_1.out
@@ -0,0 +1,105 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1u1 s1u2 s1c s2u1 s2u2 s2c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1c: COMMIT;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+
+starting permutation: s1u1 s1u2 s2u1 s1c s2u2 s2c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s1c: COMMIT;
+step s2u1: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+step s2c: COMMIT;
+
+starting permutation: s1u1 s2u1 s1u2 s2u2 s2c s1c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u2: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s1u1 s2u1 s2u2 s1u2 s2c s1c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2c: COMMIT;
+step s1u2: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s1u1 s2u1 s2u2 s2c s1u2 s1c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step s1c: COMMIT;
+
+starting permutation: s2u1 s1u1 s1u2 s2u2 s2c s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u2: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2u1 s1u1 s2u2 s1u2 s2c s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2c: COMMIT;
+step s1u2: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2u1 s1u1 s2u2 s2c s1u2 s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step s1c: COMMIT;
+
+starting permutation: s2u1 s2u2 s1u1 s1u2 s2c s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2c: COMMIT;
+step s1u2: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2u1 s2u2 s1u1 s2c s1u2 s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2c: COMMIT;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step s1c: COMMIT;
+
+starting permutation: s2u1 s2u2 s2c s1u1 s1u2 s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/fk-deadlock2_2.out b/src/test/isolation/expected/fk-deadlock2_2.out
new file mode 100644
index 0000000..9787370
--- /dev/null
+++ b/src/test/isolation/expected/fk-deadlock2_2.out
@@ -0,0 +1,105 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1u1 s1u2 s1c s2u1 s2u2 s2c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1c: COMMIT;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+
+starting permutation: s1u1 s1u2 s2u1 s1c s2u2 s2c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s1c: COMMIT;
+step s2u1: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+step s2c: COMMIT;
+
+starting permutation: s1u1 s2u1 s1u2 s2u2 s2c s1c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u2: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s1u1 s2u1 s2u2 s1u2 s2c s1c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2c: COMMIT;
+step s1u2: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s1u1 s2u1 s2u2 s2c s1u2 s1c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2u1 s1u1 s1u2 s2u2 s2c s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u2: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2u1 s1u1 s2u2 s1u2 s2c s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2c: COMMIT;
+step s1u2: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2u1 s1u1 s2u2 s2c s1u2 s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2u1 s2u2 s1u1 s1u2 s2c s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2c: COMMIT;
+step s1u2: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2u1 s2u2 s1u1 s2c s1u2 s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2c: COMMIT;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2u1 s2u2 s2c s1u1 s1u2 s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/fk-deadlock_1.out b/src/test/isolation/expected/fk-deadlock_1.out
new file mode 100644
index 0000000..c951692
--- /dev/null
+++ b/src/test/isolation/expected/fk-deadlock_1.out
@@ -0,0 +1,131 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1i s1u s1c s2i s2u s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+
+starting permutation: s1i s1u s2i s1c s2u s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1c: COMMIT;
+step s2u: UPDATE parent SET aux = 'baz';
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+
+starting permutation: s1i s1u s2i s2u s1c s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
+step s1c: COMMIT;
+step s2u: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+
+starting permutation: s1i s2i s1u s1c s2u s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
+step s2u: UPDATE parent SET aux = 'baz';
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+
+starting permutation: s1i s2i s1u s2u s1c s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
+step s1c: COMMIT;
+step s2u: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+
+starting permutation: s1i s2i s2u s1u s2c s1c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
+step s2c: COMMIT;
+step s1u: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s1i s2i s2u s2c s1u s1c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+step s1u: UPDATE parent SET aux = 'bar';
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2i s1i s1u s1c s2u s2c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
+step s2u: UPDATE parent SET aux = 'baz';
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+
+starting permutation: s2i s1i s1u s2u s1c s2c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
+step s1c: COMMIT;
+step s2u: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+
+starting permutation: s2i s1i s2u s1u s2c s1c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
+step s2c: COMMIT;
+step s1u: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2i s1i s2u s2c s1u s1c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+step s1u: UPDATE parent SET aux = 'bar';
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2i s2u s1i s1u s2c s1c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
+step s2c: COMMIT;
+step s1u: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2i s2u s1i s2c s1u s1c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2c: COMMIT;
+step s1u: UPDATE parent SET aux = 'bar';
+ERROR: could not serialize access due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s2i s2u s2c s1i s1u s1c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/fk-partitioned-1.out b/src/test/isolation/expected/fk-partitioned-1.out
new file mode 100644
index 0000000..45f2f8c
--- /dev/null
+++ b/src/test/isolation/expected/fk-partitioned-1.out
@@ -0,0 +1,133 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s1d s1c s2b s2a s2c
+step s1b: begin;
+step s1d: delete from ppk1 where a = 1;
+step s1c: commit;
+step s2b: begin;
+step s2a: alter table pfk attach partition pfk1 for values in (1);
+ERROR: insert or update on table "pfk1" violates foreign key constraint "pfk_a_fkey"
+step s2c: commit;
+
+starting permutation: s1b s1d s2b s1c s2a s2c
+step s1b: begin;
+step s1d: delete from ppk1 where a = 1;
+step s2b: begin;
+step s1c: commit;
+step s2a: alter table pfk attach partition pfk1 for values in (1);
+ERROR: insert or update on table "pfk1" violates foreign key constraint "pfk_a_fkey"
+step s2c: commit;
+
+starting permutation: s1b s1d s2b s2a s1c s2c
+step s1b: begin;
+step s1d: delete from ppk1 where a = 1;
+step s2b: begin;
+step s2a: alter table pfk attach partition pfk1 for values in (1); <waiting ...>
+step s1c: commit;
+step s2a: <... completed>
+ERROR: insert or update on table "pfk1" violates foreign key constraint "pfk_a_fkey"
+step s2c: commit;
+
+starting permutation: s1b s2b s1d s1c s2a s2c
+step s1b: begin;
+step s2b: begin;
+step s1d: delete from ppk1 where a = 1;
+step s1c: commit;
+step s2a: alter table pfk attach partition pfk1 for values in (1);
+ERROR: insert or update on table "pfk1" violates foreign key constraint "pfk_a_fkey"
+step s2c: commit;
+
+starting permutation: s1b s2b s1d s2a s1c s2c
+step s1b: begin;
+step s2b: begin;
+step s1d: delete from ppk1 where a = 1;
+step s2a: alter table pfk attach partition pfk1 for values in (1); <waiting ...>
+step s1c: commit;
+step s2a: <... completed>
+ERROR: insert or update on table "pfk1" violates foreign key constraint "pfk_a_fkey"
+step s2c: commit;
+
+starting permutation: s1b s2b s2a s1d s2c s1c
+step s1b: begin;
+step s2b: begin;
+step s2a: alter table pfk attach partition pfk1 for values in (1);
+step s1d: delete from ppk1 where a = 1; <waiting ...>
+step s2c: commit;
+step s1d: <... completed>
+ERROR: update or delete on table "ppk1" violates foreign key constraint "pfk_a_fkey1" on table "pfk"
+step s1c: commit;
+
+starting permutation: s1b s2b s2a s2c s1d s1c
+step s1b: begin;
+step s2b: begin;
+step s2a: alter table pfk attach partition pfk1 for values in (1);
+step s2c: commit;
+step s1d: delete from ppk1 where a = 1;
+ERROR: update or delete on table "ppk1" violates foreign key constraint "pfk_a_fkey1" on table "pfk"
+step s1c: commit;
+
+starting permutation: s2b s1b s1d s1c s2a s2c
+step s2b: begin;
+step s1b: begin;
+step s1d: delete from ppk1 where a = 1;
+step s1c: commit;
+step s2a: alter table pfk attach partition pfk1 for values in (1);
+ERROR: insert or update on table "pfk1" violates foreign key constraint "pfk_a_fkey"
+step s2c: commit;
+
+starting permutation: s2b s1b s1d s2a s1c s2c
+step s2b: begin;
+step s1b: begin;
+step s1d: delete from ppk1 where a = 1;
+step s2a: alter table pfk attach partition pfk1 for values in (1); <waiting ...>
+step s1c: commit;
+step s2a: <... completed>
+ERROR: insert or update on table "pfk1" violates foreign key constraint "pfk_a_fkey"
+step s2c: commit;
+
+starting permutation: s2b s1b s2a s1d s2c s1c
+step s2b: begin;
+step s1b: begin;
+step s2a: alter table pfk attach partition pfk1 for values in (1);
+step s1d: delete from ppk1 where a = 1; <waiting ...>
+step s2c: commit;
+step s1d: <... completed>
+ERROR: update or delete on table "ppk1" violates foreign key constraint "pfk_a_fkey1" on table "pfk"
+step s1c: commit;
+
+starting permutation: s2b s1b s2a s2c s1d s1c
+step s2b: begin;
+step s1b: begin;
+step s2a: alter table pfk attach partition pfk1 for values in (1);
+step s2c: commit;
+step s1d: delete from ppk1 where a = 1;
+ERROR: update or delete on table "ppk1" violates foreign key constraint "pfk_a_fkey1" on table "pfk"
+step s1c: commit;
+
+starting permutation: s2b s2a s1b s1d s2c s1c
+step s2b: begin;
+step s2a: alter table pfk attach partition pfk1 for values in (1);
+step s1b: begin;
+step s1d: delete from ppk1 where a = 1; <waiting ...>
+step s2c: commit;
+step s1d: <... completed>
+ERROR: update or delete on table "ppk1" violates foreign key constraint "pfk_a_fkey1" on table "pfk"
+step s1c: commit;
+
+starting permutation: s2b s2a s1b s2c s1d s1c
+step s2b: begin;
+step s2a: alter table pfk attach partition pfk1 for values in (1);
+step s1b: begin;
+step s2c: commit;
+step s1d: delete from ppk1 where a = 1;
+ERROR: update or delete on table "ppk1" violates foreign key constraint "pfk_a_fkey1" on table "pfk"
+step s1c: commit;
+
+starting permutation: s2b s2a s2c s1b s1d s1c
+step s2b: begin;
+step s2a: alter table pfk attach partition pfk1 for values in (1);
+step s2c: commit;
+step s1b: begin;
+step s1d: delete from ppk1 where a = 1;
+ERROR: update or delete on table "ppk1" violates foreign key constraint "pfk_a_fkey1" on table "pfk"
+step s1c: commit;
diff --git a/src/test/isolation/expected/fk-partitioned-2.out b/src/test/isolation/expected/fk-partitioned-2.out
new file mode 100644
index 0000000..8c6c714
--- /dev/null
+++ b/src/test/isolation/expected/fk-partitioned-2.out
@@ -0,0 +1,76 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s1d s2b s2i s1c s2c
+step s1b: begin;
+step s1d: delete from ppk where a = 1;
+step s2b: begin;
+step s2i: insert into pfk values (1); <waiting ...>
+step s1c: commit;
+step s2i: <... completed>
+ERROR: insert or update on table "pfk1" violates foreign key constraint "pfk_a_fkey"
+step s2c: commit;
+
+starting permutation: s1b s1d s2bs s2i s1c s2c
+step s1b: begin;
+step s1d: delete from ppk where a = 1;
+step s2bs: begin isolation level serializable; select 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2i: insert into pfk values (1); <waiting ...>
+step s1c: commit;
+step s2i: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: commit;
+
+starting permutation: s1b s2b s1d s2i s1c s2c
+step s1b: begin;
+step s2b: begin;
+step s1d: delete from ppk where a = 1;
+step s2i: insert into pfk values (1); <waiting ...>
+step s1c: commit;
+step s2i: <... completed>
+ERROR: insert or update on table "pfk1" violates foreign key constraint "pfk_a_fkey"
+step s2c: commit;
+
+starting permutation: s1b s2bs s1d s2i s1c s2c
+step s1b: begin;
+step s2bs: begin isolation level serializable; select 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s1d: delete from ppk where a = 1;
+step s2i: insert into pfk values (1); <waiting ...>
+step s1c: commit;
+step s2i: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: commit;
+
+starting permutation: s1b s2b s2i s1d s2c s1c
+step s1b: begin;
+step s2b: begin;
+step s2i: insert into pfk values (1);
+step s1d: delete from ppk where a = 1; <waiting ...>
+step s2c: commit;
+step s1d: <... completed>
+ERROR: update or delete on table "ppk1" violates foreign key constraint "pfk_a_fkey1" on table "pfk"
+step s1c: commit;
+
+starting permutation: s1b s2bs s2i s1d s2c s1c
+step s1b: begin;
+step s2bs: begin isolation level serializable; select 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s2i: insert into pfk values (1);
+step s1d: delete from ppk where a = 1; <waiting ...>
+step s2c: commit;
+step s1d: <... completed>
+ERROR: update or delete on table "ppk1" violates foreign key constraint "pfk_a_fkey1" on table "pfk"
+step s1c: commit;
diff --git a/src/test/isolation/expected/freeze-the-dead.out b/src/test/isolation/expected/freeze-the-dead.out
new file mode 100644
index 0000000..88678bd
--- /dev/null
+++ b/src/test/isolation/expected/freeze-the-dead.out
@@ -0,0 +1,44 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_begin s2_begin s3_begin s1_update s2_key_share s3_key_share s1_update s1_commit s2_commit s2_vacuum s1_selectone s3_commit s2_vacuum s1_selectall
+step s1_begin: BEGIN;
+step s2_begin: BEGIN;
+step s3_begin: BEGIN;
+step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3;
+step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE;
+id
+--
+ 3
+(1 row)
+
+step s3_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE;
+id
+--
+ 3
+(1 row)
+
+step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3;
+step s1_commit: COMMIT;
+step s2_commit: COMMIT;
+step s2_vacuum: VACUUM FREEZE tab_freeze;
+step s1_selectone:
+ BEGIN;
+ SET LOCAL enable_seqscan = false;
+ SET LOCAL enable_bitmapscan = false;
+ SELECT * FROM tab_freeze WHERE id = 3;
+ COMMIT;
+
+id|name|x
+--+----+-
+ 3| 333|2
+(1 row)
+
+step s3_commit: COMMIT;
+step s2_vacuum: VACUUM FREEZE tab_freeze;
+step s1_selectall: SELECT * FROM tab_freeze ORDER BY name, id;
+id|name|x
+--+----+-
+ 1| 111|0
+ 3| 333|2
+(2 rows)
+
diff --git a/src/test/isolation/expected/horizons.out b/src/test/isolation/expected/horizons.out
new file mode 100644
index 0000000..4150b2d
--- /dev/null
+++ b/src/test/isolation/expected/horizons.out
@@ -0,0 +1,335 @@
+Parsed test spec with 2 sessions
+
+starting permutation: pruner_create_perm ll_start pruner_query_plan pruner_query pruner_query pruner_delete pruner_query pruner_query ll_commit pruner_drop
+step pruner_create_perm:
+ CREATE TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off);
+ INSERT INTO horizons_tst(data) VALUES(1),(2);
+
+step ll_start:
+ BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ SELECT 1;
+
+?column?
+--------
+ 1
+(1 row)
+
+step pruner_query_plan:
+ EXPLAIN (COSTS OFF) SELECT * FROM horizons_tst ORDER BY data;
+
+QUERY PLAN
+-----------------------------------------------------------
+Index Only Scan using horizons_tst_data_key on horizons_tst
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_delete:
+ DELETE FROM horizons_tst;
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step ll_commit: COMMIT;
+step pruner_drop:
+ DROP TABLE horizons_tst;
+
+
+starting permutation: pruner_create_temp ll_start pruner_query_plan pruner_query pruner_query pruner_delete pruner_query pruner_query ll_commit pruner_drop
+step pruner_create_temp:
+ CREATE TEMPORARY TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off);
+ INSERT INTO horizons_tst(data) VALUES(1),(2);
+
+step ll_start:
+ BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ SELECT 1;
+
+?column?
+--------
+ 1
+(1 row)
+
+step pruner_query_plan:
+ EXPLAIN (COSTS OFF) SELECT * FROM horizons_tst ORDER BY data;
+
+QUERY PLAN
+-----------------------------------------------------------
+Index Only Scan using horizons_tst_data_key on horizons_tst
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_delete:
+ DELETE FROM horizons_tst;
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 0
+(1 row)
+
+step ll_commit: COMMIT;
+step pruner_drop:
+ DROP TABLE horizons_tst;
+
+
+starting permutation: pruner_create_temp ll_start pruner_query pruner_query pruner_begin pruner_delete pruner_query pruner_query ll_commit pruner_commit pruner_drop
+step pruner_create_temp:
+ CREATE TEMPORARY TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off);
+ INSERT INTO horizons_tst(data) VALUES(1),(2);
+
+step ll_start:
+ BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ SELECT 1;
+
+?column?
+--------
+ 1
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_begin: BEGIN;
+step pruner_delete:
+ DELETE FROM horizons_tst;
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step ll_commit: COMMIT;
+step pruner_commit: COMMIT;
+step pruner_drop:
+ DROP TABLE horizons_tst;
+
+
+starting permutation: pruner_create_perm ll_start pruner_query pruner_query pruner_delete pruner_vacuum pruner_query pruner_query ll_commit pruner_drop
+step pruner_create_perm:
+ CREATE TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off);
+ INSERT INTO horizons_tst(data) VALUES(1),(2);
+
+step ll_start:
+ BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ SELECT 1;
+
+?column?
+--------
+ 1
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_delete:
+ DELETE FROM horizons_tst;
+
+step pruner_vacuum:
+ VACUUM horizons_tst;
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step ll_commit: COMMIT;
+step pruner_drop:
+ DROP TABLE horizons_tst;
+
+
+starting permutation: pruner_create_temp ll_start pruner_query pruner_query pruner_delete pruner_vacuum pruner_query pruner_query ll_commit pruner_drop
+step pruner_create_temp:
+ CREATE TEMPORARY TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off);
+ INSERT INTO horizons_tst(data) VALUES(1),(2);
+
+step ll_start:
+ BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ SELECT 1;
+
+?column?
+--------
+ 1
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 2
+(1 row)
+
+step pruner_delete:
+ DELETE FROM horizons_tst;
+
+step pruner_vacuum:
+ VACUUM horizons_tst;
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 0
+(1 row)
+
+step pruner_query:
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+
+?column?
+--------
+ 0
+(1 row)
+
+step ll_commit: COMMIT;
+step pruner_drop:
+ DROP TABLE horizons_tst;
+
diff --git a/src/test/isolation/expected/index-only-scan.out b/src/test/isolation/expected/index-only-scan.out
new file mode 100644
index 0000000..47983eb
--- /dev/null
+++ b/src/test/isolation/expected/index-only-scan.out
@@ -0,0 +1,41 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rxwy1 c1 rywx2 c2
+step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+step c1: COMMIT;
+step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+step c2: COMMIT;
+
+starting permutation: rxwy1 rywx2 c1 c2
+step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxwy1 rywx2 c2 c1
+step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rywx2 rxwy1 c1 c2
+step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rywx2 rxwy1 c2 c1
+step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rywx2 c2 rxwy1 c1
+step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+step c2: COMMIT;
+step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/inherit-temp.out b/src/test/isolation/expected/inherit-temp.out
new file mode 100644
index 0000000..e6f0f22
--- /dev/null
+++ b/src/test/isolation/expected/inherit-temp.out
@@ -0,0 +1,277 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s1_select_p: SELECT a FROM inh_parent;
+a
+-
+1
+2
+3
+4
+(4 rows)
+
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a
+-
+3
+4
+(2 rows)
+
+step s2_select_p: SELECT a FROM inh_parent;
+a
+-
+1
+2
+5
+6
+(4 rows)
+
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a
+-
+5
+6
+(2 rows)
+
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_update_p s1_update_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s1_update_p: UPDATE inh_parent SET a = 11 WHERE a = 1;
+step s1_update_c: UPDATE inh_parent SET a = 13 WHERE a IN (3, 5);
+step s1_select_p: SELECT a FROM inh_parent;
+ a
+--
+ 2
+11
+ 4
+13
+(4 rows)
+
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+ a
+--
+ 4
+13
+(2 rows)
+
+step s2_select_p: SELECT a FROM inh_parent;
+ a
+--
+ 2
+11
+ 5
+ 6
+(4 rows)
+
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a
+-
+5
+6
+(2 rows)
+
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_update_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s2_update_c: UPDATE inh_parent SET a = 15 WHERE a IN (3, 5);
+step s1_select_p: SELECT a FROM inh_parent;
+a
+-
+1
+2
+3
+4
+(4 rows)
+
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a
+-
+3
+4
+(2 rows)
+
+step s2_select_p: SELECT a FROM inh_parent;
+ a
+--
+ 1
+ 2
+ 6
+15
+(4 rows)
+
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+ a
+--
+ 6
+15
+(2 rows)
+
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_delete_p s1_delete_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s1_delete_p: DELETE FROM inh_parent WHERE a = 2;
+step s1_delete_c: DELETE FROM inh_parent WHERE a IN (4, 6);
+step s1_select_p: SELECT a FROM inh_parent;
+a
+-
+1
+3
+(2 rows)
+
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a
+-
+3
+(1 row)
+
+step s2_select_p: SELECT a FROM inh_parent;
+a
+-
+1
+5
+6
+(3 rows)
+
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a
+-
+5
+6
+(2 rows)
+
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_delete_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s2_delete_c: DELETE FROM inh_parent WHERE a IN (4, 6);
+step s1_select_p: SELECT a FROM inh_parent;
+a
+-
+1
+2
+3
+4
+(4 rows)
+
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a
+-
+3
+4
+(2 rows)
+
+step s2_select_p: SELECT a FROM inh_parent;
+a
+-
+1
+2
+5
+(3 rows)
+
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a
+-
+5
+(1 row)
+
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_truncate_p s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s1_truncate_p: TRUNCATE inh_parent;
+step s1_select_p: SELECT a FROM inh_parent;
+a
+-
+(0 rows)
+
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a
+-
+(0 rows)
+
+step s2_select_p: SELECT a FROM inh_parent;
+a
+-
+5
+6
+(2 rows)
+
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a
+-
+5
+6
+(2 rows)
+
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_truncate_p s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s2_truncate_p: TRUNCATE inh_parent;
+step s1_select_p: SELECT a FROM inh_parent;
+a
+-
+3
+4
+(2 rows)
+
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a
+-
+3
+4
+(2 rows)
+
+step s2_select_p: SELECT a FROM inh_parent;
+a
+-
+(0 rows)
+
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a
+-
+(0 rows)
+
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_begin s1_truncate_p s2_select_p s1_commit
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s1_begin: BEGIN;
+step s1_truncate_p: TRUNCATE inh_parent;
+step s2_select_p: SELECT a FROM inh_parent; <waiting ...>
+step s1_commit: COMMIT;
+step s2_select_p: <... completed>
+a
+-
+5
+6
+(2 rows)
+
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_begin s1_truncate_p s2_select_c s1_commit
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s1_begin: BEGIN;
+step s1_truncate_p: TRUNCATE inh_parent;
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a
+-
+5
+6
+(2 rows)
+
+step s1_commit: COMMIT;
diff --git a/src/test/isolation/expected/insert-conflict-do-nothing-2.out b/src/test/isolation/expected/insert-conflict-do-nothing-2.out
new file mode 100644
index 0000000..22d41d3
--- /dev/null
+++ b/src/test/isolation/expected/insert-conflict-do-nothing-2.out
@@ -0,0 +1,121 @@
+Parsed test spec with 2 sessions
+
+starting permutation: beginrr1 beginrr2 donothing1 c1 donothing2 c2 show
+step beginrr1: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step beginrr2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
+step c1: COMMIT;
+step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING;
+step c2: COMMIT;
+step show: SELECT * FROM ints;
+key|val
+---+----------
+ 1|donothing1
+(1 row)
+
+
+starting permutation: beginrr1 beginrr2 donothing2 c2 donothing1 c1 show
+step beginrr1: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step beginrr2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING;
+step c2: COMMIT;
+step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
+step c1: COMMIT;
+step show: SELECT * FROM ints;
+key|val
+---+----------
+ 1|donothing2
+(1 row)
+
+
+starting permutation: beginrr1 beginrr2 donothing1 donothing2 c1 c2 show
+step beginrr1: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step beginrr2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
+step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING; <waiting ...>
+step c1: COMMIT;
+step donothing2: <... completed>
+ERROR: could not serialize access due to concurrent update
+step c2: COMMIT;
+step show: SELECT * FROM ints;
+key|val
+---+----------
+ 1|donothing1
+(1 row)
+
+
+starting permutation: beginrr1 beginrr2 donothing2 donothing1 c2 c1 show
+step beginrr1: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step beginrr2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING;
+step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING; <waiting ...>
+step c2: COMMIT;
+step donothing1: <... completed>
+ERROR: could not serialize access due to concurrent update
+step c1: COMMIT;
+step show: SELECT * FROM ints;
+key|val
+---+----------
+ 1|donothing2
+(1 row)
+
+
+starting permutation: begins1 begins2 donothing1 c1 donothing2 c2 show
+step begins1: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step begins2: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
+step c1: COMMIT;
+step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING;
+step c2: COMMIT;
+step show: SELECT * FROM ints;
+key|val
+---+----------
+ 1|donothing1
+(1 row)
+
+
+starting permutation: begins1 begins2 donothing2 c2 donothing1 c1 show
+step begins1: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step begins2: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING;
+step c2: COMMIT;
+step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
+step c1: COMMIT;
+step show: SELECT * FROM ints;
+key|val
+---+----------
+ 1|donothing2
+(1 row)
+
+
+starting permutation: begins1 begins2 donothing1 donothing2 c1 c2 show
+step begins1: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step begins2: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
+step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING; <waiting ...>
+step c1: COMMIT;
+step donothing2: <... completed>
+ERROR: could not serialize access due to concurrent update
+step c2: COMMIT;
+step show: SELECT * FROM ints;
+key|val
+---+----------
+ 1|donothing1
+(1 row)
+
+
+starting permutation: begins1 begins2 donothing2 donothing1 c2 c1 show
+step begins1: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step begins2: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING;
+step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING; <waiting ...>
+step c2: COMMIT;
+step donothing1: <... completed>
+ERROR: could not serialize access due to concurrent update
+step c1: COMMIT;
+step show: SELECT * FROM ints;
+key|val
+---+----------
+ 1|donothing2
+(1 row)
+
diff --git a/src/test/isolation/expected/insert-conflict-do-nothing.out b/src/test/isolation/expected/insert-conflict-do-nothing.out
new file mode 100644
index 0000000..cadf46d
--- /dev/null
+++ b/src/test/isolation/expected/insert-conflict-do-nothing.out
@@ -0,0 +1,27 @@
+Parsed test spec with 2 sessions
+
+starting permutation: donothing1 donothing2 c1 select2 c2
+step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
+step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2') ON CONFLICT DO NOTHING; <waiting ...>
+step c1: COMMIT;
+step donothing2: <... completed>
+step select2: SELECT * FROM ints;
+key|val
+---+----------
+ 1|donothing1
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: donothing1 donothing2 a1 select2 c2
+step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
+step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2') ON CONFLICT DO NOTHING; <waiting ...>
+step a1: ABORT;
+step donothing2: <... completed>
+step select2: SELECT * FROM ints;
+key|val
+---+----------
+ 1|donothing2
+(1 row)
+
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/insert-conflict-do-update-2.out b/src/test/isolation/expected/insert-conflict-do-update-2.out
new file mode 100644
index 0000000..7acd1ae
--- /dev/null
+++ b/src/test/isolation/expected/insert-conflict-do-update-2.out
@@ -0,0 +1,27 @@
+Parsed test spec with 2 sessions
+
+starting permutation: insert1 insert2 c1 select2 c2
+step insert1: INSERT INTO upsert(key, payload) VALUES('FooFoo', 'insert1') ON CONFLICT (lower(key)) DO UPDATE set key = EXCLUDED.key, payload = upsert.payload || ' updated by insert1';
+step insert2: INSERT INTO upsert(key, payload) VALUES('FOOFOO', 'insert2') ON CONFLICT (lower(key)) DO UPDATE set key = EXCLUDED.key, payload = upsert.payload || ' updated by insert2'; <waiting ...>
+step c1: COMMIT;
+step insert2: <... completed>
+step select2: SELECT * FROM upsert;
+key |payload
+------+--------------------------
+FOOFOO|insert1 updated by insert2
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: insert1 insert2 a1 select2 c2
+step insert1: INSERT INTO upsert(key, payload) VALUES('FooFoo', 'insert1') ON CONFLICT (lower(key)) DO UPDATE set key = EXCLUDED.key, payload = upsert.payload || ' updated by insert1';
+step insert2: INSERT INTO upsert(key, payload) VALUES('FOOFOO', 'insert2') ON CONFLICT (lower(key)) DO UPDATE set key = EXCLUDED.key, payload = upsert.payload || ' updated by insert2'; <waiting ...>
+step a1: ABORT;
+step insert2: <... completed>
+step select2: SELECT * FROM upsert;
+key |payload
+------+-------
+FOOFOO|insert2
+(1 row)
+
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/insert-conflict-do-update-3.out b/src/test/isolation/expected/insert-conflict-do-update-3.out
new file mode 100644
index 0000000..2d7e0b8
--- /dev/null
+++ b/src/test/isolation/expected/insert-conflict-do-update-3.out
@@ -0,0 +1,30 @@
+Parsed test spec with 2 sessions
+
+starting permutation: update2 insert1 c2 select1surprise c1
+step update2: UPDATE colors SET is_active = true WHERE key = 1;
+step insert1:
+ WITH t AS (
+ INSERT INTO colors(key, color, is_active)
+ VALUES(1, 'Brown', true), (2, 'Gray', true)
+ ON CONFLICT (key) DO UPDATE
+ SET color = EXCLUDED.color
+ WHERE colors.is_active)
+ SELECT * FROM colors ORDER BY key; <waiting ...>
+step c2: COMMIT;
+step insert1: <... completed>
+key|color|is_active
+---+-----+---------
+ 1|Red |f
+ 2|Green|f
+ 3|Blue |f
+(3 rows)
+
+step select1surprise: SELECT * FROM colors ORDER BY key;
+key|color|is_active
+---+-----+---------
+ 1|Brown|t
+ 2|Green|f
+ 3|Blue |f
+(3 rows)
+
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/insert-conflict-do-update.out b/src/test/isolation/expected/insert-conflict-do-update.out
new file mode 100644
index 0000000..16c384c
--- /dev/null
+++ b/src/test/isolation/expected/insert-conflict-do-update.out
@@ -0,0 +1,27 @@
+Parsed test spec with 2 sessions
+
+starting permutation: insert1 insert2 c1 select2 c2
+step insert1: INSERT INTO upsert(key, val) VALUES(1, 'insert1') ON CONFLICT (key) DO UPDATE set val = upsert.val || ' updated by insert1';
+step insert2: INSERT INTO upsert(key, val) VALUES(1, 'insert2') ON CONFLICT (key) DO UPDATE set val = upsert.val || ' updated by insert2'; <waiting ...>
+step c1: COMMIT;
+step insert2: <... completed>
+step select2: SELECT * FROM upsert;
+key|val
+---+--------------------------
+ 1|insert1 updated by insert2
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: insert1 insert2 a1 select2 c2
+step insert1: INSERT INTO upsert(key, val) VALUES(1, 'insert1') ON CONFLICT (key) DO UPDATE set val = upsert.val || ' updated by insert1';
+step insert2: INSERT INTO upsert(key, val) VALUES(1, 'insert2') ON CONFLICT (key) DO UPDATE set val = upsert.val || ' updated by insert2'; <waiting ...>
+step a1: ABORT;
+step insert2: <... completed>
+step select2: SELECT * FROM upsert;
+key|val
+---+-------
+ 1|insert2
+(1 row)
+
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/insert-conflict-specconflict.out b/src/test/isolation/expected/insert-conflict-specconflict.out
new file mode 100644
index 0000000..e34a821
--- /dev/null
+++ b/src/test/isolation/expected/insert-conflict-specconflict.out
@@ -0,0 +1,553 @@
+Parsed test spec with 3 sessions
+
+starting permutation: controller_locks controller_show s1_upsert s2_upsert controller_show controller_unlock_1_1 controller_unlock_2_1 controller_unlock_1_3 controller_unlock_2_3 controller_show controller_unlock_2_2 controller_show controller_unlock_1_2 controller_show
+step controller_locks: SELECT pg_advisory_lock(sess, lock), sess, lock FROM generate_series(1, 2) a(sess), generate_series(1,3) b(lock);
+pg_advisory_lock|sess|lock
+----------------+----+----
+ | 1| 1
+ | 1| 2
+ | 1| 3
+ | 2| 1
+ | 2| 2
+ | 2| 3
+(6 rows)
+
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 3
+step s1_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s1') ON CONFLICT (blurt_and_lock_123(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s1'; <waiting ...>
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 3
+step s2_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s2') ON CONFLICT (blurt_and_lock_123(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s2'; <waiting ...>
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step controller_unlock_1_1: SELECT pg_advisory_unlock(1, 1);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step controller_unlock_2_1: SELECT pg_advisory_unlock(2, 1);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step controller_unlock_1_3: SELECT pg_advisory_unlock(1, 3);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 2
+step controller_unlock_2_3: SELECT pg_advisory_unlock(2, 3);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 2
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step controller_unlock_2_2: SELECT pg_advisory_unlock(2, 2);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2_upsert: <... completed>
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+-----------
+k1 |inserted s2
+(1 row)
+
+step controller_unlock_1_2: SELECT pg_advisory_unlock(1, 2);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 2
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 2
+step s1_upsert: <... completed>
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+-----------------------------------
+k1 |inserted s2 with conflict update s1
+(1 row)
+
+
+starting permutation: controller_locks controller_show s1_upsert s2_upsert controller_show controller_unlock_1_1 controller_unlock_2_1 controller_unlock_1_3 controller_unlock_2_3 controller_show controller_unlock_1_2 controller_show controller_unlock_2_2 controller_show
+step controller_locks: SELECT pg_advisory_lock(sess, lock), sess, lock FROM generate_series(1, 2) a(sess), generate_series(1,3) b(lock);
+pg_advisory_lock|sess|lock
+----------------+----+----
+ | 1| 1
+ | 1| 2
+ | 1| 3
+ | 2| 1
+ | 2| 2
+ | 2| 3
+(6 rows)
+
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 3
+step s1_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s1') ON CONFLICT (blurt_and_lock_123(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s1'; <waiting ...>
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 3
+step s2_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s2') ON CONFLICT (blurt_and_lock_123(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s2'; <waiting ...>
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step controller_unlock_1_1: SELECT pg_advisory_unlock(1, 1);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step controller_unlock_2_1: SELECT pg_advisory_unlock(2, 1);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step controller_unlock_1_3: SELECT pg_advisory_unlock(1, 3);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 2
+step controller_unlock_2_3: SELECT pg_advisory_unlock(2, 3);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 2
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step controller_unlock_1_2: SELECT pg_advisory_unlock(1, 2);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1_upsert: <... completed>
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+-----------
+k1 |inserted s1
+(1 row)
+
+step controller_unlock_2_2: SELECT pg_advisory_unlock(2, 2);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 2
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 2
+step s2_upsert: <... completed>
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+-----------------------------------
+k1 |inserted s1 with conflict update s2
+(1 row)
+
+
+starting permutation: controller_locks controller_show s1_insert_toast s2_insert_toast controller_show controller_unlock_1_1 controller_unlock_2_1 controller_unlock_1_3 controller_unlock_2_3 controller_show controller_unlock_1_2 controller_show_count controller_unlock_2_2 controller_show_count
+step controller_locks: SELECT pg_advisory_lock(sess, lock), sess, lock FROM generate_series(1, 2) a(sess), generate_series(1,3) b(lock);
+pg_advisory_lock|sess|lock
+----------------+----+----
+ | 1| 1
+ | 1| 2
+ | 1| 3
+ | 2| 1
+ | 2| 2
+ | 2| 3
+(6 rows)
+
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+s1: NOTICE: blurt_and_lock_123() called for k2 in session 1
+s1: NOTICE: acquiring advisory lock on 3
+step s1_insert_toast: INSERT INTO upserttest VALUES('k2', ctoast_large_val()) ON CONFLICT DO NOTHING; <waiting ...>
+s2: NOTICE: blurt_and_lock_123() called for k2 in session 2
+s2: NOTICE: acquiring advisory lock on 3
+step s2_insert_toast: INSERT INTO upserttest VALUES('k2', ctoast_large_val()) ON CONFLICT DO NOTHING; <waiting ...>
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step controller_unlock_1_1: SELECT pg_advisory_unlock(1, 1);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step controller_unlock_2_1: SELECT pg_advisory_unlock(2, 1);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step controller_unlock_1_3: SELECT pg_advisory_unlock(1, 3);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: blurt_and_lock_123() called for k2 in session 1
+s1: NOTICE: acquiring advisory lock on 2
+step controller_unlock_2_3: SELECT pg_advisory_unlock(2, 3);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s2: NOTICE: blurt_and_lock_123() called for k2 in session 2
+s2: NOTICE: acquiring advisory lock on 2
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step controller_unlock_1_2: SELECT pg_advisory_unlock(1, 2);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1_insert_toast: <... completed>
+step controller_show_count: SELECT COUNT(*) FROM upserttest;
+count
+-----
+ 1
+(1 row)
+
+step controller_unlock_2_2: SELECT pg_advisory_unlock(2, 2);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s2: NOTICE: blurt_and_lock_123() called for k2 in session 2
+s2: NOTICE: acquiring advisory lock on 2
+s2: NOTICE: blurt_and_lock_123() called for k2 in session 2
+s2: NOTICE: acquiring advisory lock on 2
+step s2_insert_toast: <... completed>
+step controller_show_count: SELECT COUNT(*) FROM upserttest;
+count
+-----
+ 1
+(1 row)
+
+
+starting permutation: controller_locks controller_show s1_begin s2_begin s1_upsert s2_upsert controller_show controller_unlock_1_1 controller_unlock_2_1 controller_unlock_1_3 controller_unlock_2_3 controller_show controller_unlock_1_2 controller_show controller_unlock_2_2 controller_show s1_commit controller_show s2_commit controller_show
+step controller_locks: SELECT pg_advisory_lock(sess, lock), sess, lock FROM generate_series(1, 2) a(sess), generate_series(1,3) b(lock);
+pg_advisory_lock|sess|lock
+----------------+----+----
+ | 1| 1
+ | 1| 2
+ | 1| 3
+ | 2| 1
+ | 2| 2
+ | 2| 3
+(6 rows)
+
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step s1_begin: BEGIN;
+step s2_begin: BEGIN;
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 3
+step s1_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s1') ON CONFLICT (blurt_and_lock_123(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s1'; <waiting ...>
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 3
+step s2_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s2') ON CONFLICT (blurt_and_lock_123(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s2'; <waiting ...>
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step controller_unlock_1_1: SELECT pg_advisory_unlock(1, 1);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step controller_unlock_2_1: SELECT pg_advisory_unlock(2, 1);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step controller_unlock_1_3: SELECT pg_advisory_unlock(1, 3);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 2
+step controller_unlock_2_3: SELECT pg_advisory_unlock(2, 3);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 2
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step controller_unlock_1_2: SELECT pg_advisory_unlock(1, 2);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1_upsert: <... completed>
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step controller_unlock_2_2: SELECT pg_advisory_unlock(2, 2);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 2
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 2
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step s1_commit: COMMIT;
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 2
+step s2_upsert: <... completed>
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+-----------
+k1 |inserted s1
+(1 row)
+
+step s2_commit: COMMIT;
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+-----------------------------------
+k1 |inserted s1 with conflict update s2
+(1 row)
+
+
+starting permutation: s1_create_non_unique_index s1_confirm_index_order controller_locks controller_show s2_begin s1_upsert s2_upsert controller_show controller_unlock_1_1 controller_unlock_2_1 controller_unlock_1_3 controller_unlock_2_3 controller_show controller_lock_2_4 controller_unlock_2_2 controller_show controller_unlock_1_2 controller_print_speculative_locks controller_unlock_2_4 s2_noop controller_print_speculative_locks s2_commit s1_noop controller_show controller_print_speculative_locks
+step s1_create_non_unique_index: CREATE INDEX upserttest_key_idx ON upserttest((blurt_and_lock_4(key)));
+step s1_confirm_index_order: SELECT 'upserttest_key_uniq_idx'::regclass::int8 < 'upserttest_key_idx'::regclass::int8;
+?column?
+--------
+t
+(1 row)
+
+step controller_locks: SELECT pg_advisory_lock(sess, lock), sess, lock FROM generate_series(1, 2) a(sess), generate_series(1,3) b(lock);
+pg_advisory_lock|sess|lock
+----------------+----+----
+ | 1| 1
+ | 1| 2
+ | 1| 3
+ | 2| 1
+ | 2| 2
+ | 2| 3
+(6 rows)
+
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step s2_begin: BEGIN;
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 3
+step s1_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s1') ON CONFLICT (blurt_and_lock_123(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s1'; <waiting ...>
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 3
+step s2_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s2') ON CONFLICT (blurt_and_lock_123(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s2'; <waiting ...>
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step controller_unlock_1_1: SELECT pg_advisory_unlock(1, 1);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step controller_unlock_2_1: SELECT pg_advisory_unlock(2, 1);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step controller_unlock_1_3: SELECT pg_advisory_unlock(1, 3);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 2
+step controller_unlock_2_3: SELECT pg_advisory_unlock(2, 3);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s2: NOTICE: blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 2
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step controller_lock_2_4: SELECT pg_advisory_lock(2, 4);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step controller_unlock_2_2: SELECT pg_advisory_unlock(2, 2);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s2: NOTICE: blurt_and_lock_4() called for k1 in session 2
+s2: NOTICE: acquiring advisory lock on 4
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+----
+(0 rows)
+
+step controller_unlock_1_2: SELECT pg_advisory_unlock(1, 2);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 4
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 2
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 2
+step controller_print_speculative_locks:
+ SELECT pa.application_name, locktype, mode, granted
+ FROM pg_locks pl JOIN pg_stat_activity pa USING (pid)
+ WHERE
+ locktype IN ('spectoken', 'transactionid')
+ AND pa.datname = current_database()
+ AND pa.application_name LIKE 'isolation/insert-conflict-specconflict/s%'
+ ORDER BY 1, 2, 3, 4;
+
+application_name |locktype |mode |granted
+-----------------------------------------+-------------+-------------+-------
+isolation/insert-conflict-specconflict/s1|spectoken |ShareLock |f
+isolation/insert-conflict-specconflict/s1|transactionid|ExclusiveLock|t
+isolation/insert-conflict-specconflict/s2|spectoken |ExclusiveLock|t
+isolation/insert-conflict-specconflict/s2|transactionid|ExclusiveLock|t
+(4 rows)
+
+step controller_unlock_2_4: SELECT pg_advisory_unlock(2, 4);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 2
+step s2_upsert: <... completed>
+step s2_noop:
+step controller_print_speculative_locks:
+ SELECT pa.application_name, locktype, mode, granted
+ FROM pg_locks pl JOIN pg_stat_activity pa USING (pid)
+ WHERE
+ locktype IN ('spectoken', 'transactionid')
+ AND pa.datname = current_database()
+ AND pa.application_name LIKE 'isolation/insert-conflict-specconflict/s%'
+ ORDER BY 1, 2, 3, 4;
+
+application_name |locktype |mode |granted
+-----------------------------------------+-------------+-------------+-------
+isolation/insert-conflict-specconflict/s1|transactionid|ExclusiveLock|t
+isolation/insert-conflict-specconflict/s1|transactionid|ShareLock |f
+isolation/insert-conflict-specconflict/s2|transactionid|ExclusiveLock|t
+(3 rows)
+
+step s2_commit: COMMIT;
+s1: NOTICE: blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE: acquiring advisory lock on 2
+step s1_upsert: <... completed>
+step s1_noop:
+step controller_show: SELECT * FROM upserttest;
+key|data
+---+-----------------------------------
+k1 |inserted s2 with conflict update s1
+(1 row)
+
+step controller_print_speculative_locks:
+ SELECT pa.application_name, locktype, mode, granted
+ FROM pg_locks pl JOIN pg_stat_activity pa USING (pid)
+ WHERE
+ locktype IN ('spectoken', 'transactionid')
+ AND pa.datname = current_database()
+ AND pa.application_name LIKE 'isolation/insert-conflict-specconflict/s%'
+ ORDER BY 1, 2, 3, 4;
+
+application_name|locktype|mode|granted
+----------------+--------+----+-------
+(0 rows)
+
diff --git a/src/test/isolation/expected/lock-committed-keyupdate.out b/src/test/isolation/expected/lock-committed-keyupdate.out
new file mode 100644
index 0000000..7de6bc6
--- /dev/null
+++ b/src/test/isolation/expected/lock-committed-keyupdate.out
@@ -0,0 +1,670 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s2b1 s1l s2l s1u s1c s1ul s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s1c: COMMIT;
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b1 s1l s1u s2l s1c s1ul s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b1 s1l s1u s1ul s2l s1c s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b1 s1l s2l s1u s1c s1hint s1ul s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcku_table;
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b1 s1l s1u s2l s1c s1hint s1ul s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcku_table;
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b1 s1l s1u s1ul s2l s1c s1hint s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s1hint: SELECT * FROM lcku_table;
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s2l s1u s1c s1ul s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s1c: COMMIT;
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s1u s2l s1c s1ul s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s1u s1ul s2l s1c s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s2l: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s2l s1u s1c s1hint s1ul s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcku_table;
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s1u s2l s1c s1hint s1ul s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcku_table;
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s1u s1ul s2l s1c s1hint s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s2l: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1hint: SELECT * FROM lcku_table;
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s2l s1u s1c s1ul s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s1c: COMMIT;
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s1u s2l s1c s1ul s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s1u s1ul s2l s1c s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s2l: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s2l s1u s1c s1hint s1ul s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcku_table;
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s1u s2l s1c s1hint s1ul s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcku_table;
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s1u s1ul s2l s1c s1hint s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(578902068);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcku_table SET id = 2 WHERE id = 3;
+step s1ul: SELECT pg_advisory_unlock(578902068);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s2l: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1hint: SELECT * FROM lcku_table;
+id|value
+--+-----
+ 1|one
+ 2|two
+(2 rows)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
diff --git a/src/test/isolation/expected/lock-committed-update.out b/src/test/isolation/expected/lock-committed-update.out
new file mode 100644
index 0000000..84b9ce7
--- /dev/null
+++ b/src/test/isolation/expected/lock-committed-update.out
@@ -0,0 +1,931 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s2b1 s1l s2l s1u s1c s1ul s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1c: COMMIT;
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b1 s1l s1u s2l s1c s1ul s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b1 s1l s2l s1ul s1u s1c s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1c: COMMIT;
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b1 s1l s1u s1ul s2l s1c s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE;
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b1 s1l s2l s1u s1c s1hint s1ul s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcu_table;
+id|value
+--+-----
+ 1|two
+(1 row)
+
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b1 s1l s1u s2l s1c s1hint s1ul s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcu_table;
+id|value
+--+-----
+ 1|two
+(1 row)
+
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b1 s1l s2l s1ul s1u s1c s1hint s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcu_table;
+id|value
+--+-----
+ 1|two
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b1 s1l s1u s1ul s2l s1c s1hint s2c
+step s1b: BEGIN;
+step s2b1: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE;
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcu_table;
+id|value
+--+-----
+ 1|two
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s2l s1u s1c s1ul s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1c: COMMIT;
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s1u s2l s1c s1ul s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s2l s1ul s1u s1c s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1c: COMMIT;
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s1u s1ul s2l s1c s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE;
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s2l s1u s1c s1hint s1ul s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcu_table;
+id|value
+--+-----
+ 1|two
+(1 row)
+
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s1u s2l s1c s1hint s1ul s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcu_table;
+id|value
+--+-----
+ 1|two
+(1 row)
+
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s2l s1ul s1u s1c s1hint s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcu_table;
+id|value
+--+-----
+ 1|two
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b2 s1l s1u s1ul s2l s1c s1hint s2c
+step s1b: BEGIN;
+step s2b2: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE;
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcu_table;
+id|value
+--+-----
+ 1|two
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s2l s1u s1c s1ul s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1c: COMMIT;
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s1u s2l s1c s1ul s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s2l s1ul s1u s1c s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1c: COMMIT;
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s1u s1ul s2l s1c s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE;
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s2l s1u s1c s1hint s1ul s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcu_table;
+id|value
+--+-----
+ 1|two
+(1 row)
+
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s1u s2l s1c s1hint s1ul s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcu_table;
+id|value
+--+-----
+ 1|two
+(1 row)
+
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s2l s1ul s1u s1c s1hint s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; <waiting ...>
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: <... completed>
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcu_table;
+id|value
+--+-----
+ 1|two
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+
+starting permutation: s1b s2b3 s1l s1u s1ul s2l s1c s1hint s2c
+step s1b: BEGIN;
+step s2b3: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1l: SELECT pg_advisory_lock(380170116);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1u: UPDATE lcu_table SET value = 'two' WHERE id = 1;
+step s1ul: SELECT pg_advisory_unlock(380170116);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2l: SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE;
+id|value
+--+-----
+ 1|one
+(1 row)
+
+step s1c: COMMIT;
+step s1hint: SELECT * FROM lcu_table;
+id|value
+--+-----
+ 1|two
+(1 row)
+
+step s2c: COMMIT;
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
diff --git a/src/test/isolation/expected/lock-update-delete.out b/src/test/isolation/expected/lock-update-delete.out
new file mode 100644
index 0000000..f75e25f
--- /dev/null
+++ b/src/test/isolation/expected/lock-update-delete.out
@@ -0,0 +1,285 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2b s1l s2u s2_blocker1 s2_unlock s2c
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker1: DELETE FROM foo;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2c: COMMIT;
+step s1l: <... completed>
+key|value
+---+-----
+(0 rows)
+
+
+starting permutation: s2b s1l s2u s2_blocker2 s2_unlock s2c
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker2: UPDATE foo SET key = 2 WHERE key = 1;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2c: COMMIT;
+step s1l: <... completed>
+key|value
+---+-----
+(0 rows)
+
+
+starting permutation: s2b s1l s2u s2_blocker3 s2_unlock s2c
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker3: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s2b s1l s2u s2_blocker1 s2_unlock s2r
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker1: DELETE FROM foo;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2r: ROLLBACK;
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+
+starting permutation: s2b s1l s2u s2_blocker2 s2_unlock s2r
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker2: UPDATE foo SET key = 2 WHERE key = 1;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2r: ROLLBACK;
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+
+starting permutation: s2b s1l s2u s2_blocker3 s2_unlock s2r
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker3: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2r: ROLLBACK;
+
+starting permutation: s2b s1l s2u s2_blocker1 s2c s2_unlock
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker1: DELETE FROM foo;
+step s2c: COMMIT;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+(0 rows)
+
+
+starting permutation: s2b s1l s2u s2_blocker2 s2c s2_unlock
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker2: UPDATE foo SET key = 2 WHERE key = 1;
+step s2c: COMMIT;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+(0 rows)
+
+
+starting permutation: s2b s1l s2u s2_blocker3 s2c s2_unlock
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker3: UPDATE foo SET value = 2 WHERE key = 1;
+step s2c: COMMIT;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+
+starting permutation: s2b s1l s2u s2_blocker1 s2r s2_unlock
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker1: DELETE FROM foo;
+step s2r: ROLLBACK;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+
+starting permutation: s2b s1l s2u s2_blocker2 s2r s2_unlock
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker2: UPDATE foo SET key = 2 WHERE key = 1;
+step s2r: ROLLBACK;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+
+starting permutation: s2b s1l s2u s2_blocker3 s2r s2_unlock
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker3: UPDATE foo SET value = 2 WHERE key = 1;
+step s2r: ROLLBACK;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/lock-update-delete_1.out b/src/test/isolation/expected/lock-update-delete_1.out
new file mode 100644
index 0000000..c602ac8
--- /dev/null
+++ b/src/test/isolation/expected/lock-update-delete_1.out
@@ -0,0 +1,273 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2b s1l s2u s2_blocker1 s2_unlock s2c
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker1: DELETE FROM foo;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2c: COMMIT;
+step s1l: <... completed>
+ERROR: could not serialize access due to concurrent update
+
+starting permutation: s2b s1l s2u s2_blocker2 s2_unlock s2c
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker2: UPDATE foo SET key = 2 WHERE key = 1;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2c: COMMIT;
+step s1l: <... completed>
+ERROR: could not serialize access due to concurrent update
+
+starting permutation: s2b s1l s2u s2_blocker3 s2_unlock s2c
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker3: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s2b s1l s2u s2_blocker1 s2_unlock s2r
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker1: DELETE FROM foo;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2r: ROLLBACK;
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+
+starting permutation: s2b s1l s2u s2_blocker2 s2_unlock s2r
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker2: UPDATE foo SET key = 2 WHERE key = 1;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s2r: ROLLBACK;
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+
+starting permutation: s2b s1l s2u s2_blocker3 s2_unlock s2r
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker3: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2r: ROLLBACK;
+
+starting permutation: s2b s1l s2u s2_blocker1 s2c s2_unlock
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker1: DELETE FROM foo;
+step s2c: COMMIT;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+ERROR: could not serialize access due to concurrent update
+
+starting permutation: s2b s1l s2u s2_blocker2 s2c s2_unlock
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker2: UPDATE foo SET key = 2 WHERE key = 1;
+step s2c: COMMIT;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+ERROR: could not serialize access due to concurrent update
+
+starting permutation: s2b s1l s2u s2_blocker3 s2c s2_unlock
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker3: UPDATE foo SET value = 2 WHERE key = 1;
+step s2c: COMMIT;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+
+starting permutation: s2b s1l s2u s2_blocker1 s2r s2_unlock
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker1: DELETE FROM foo;
+step s2r: ROLLBACK;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+
+starting permutation: s2b s1l s2u s2_blocker2 s2r s2_unlock
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker2: UPDATE foo SET key = 2 WHERE key = 1;
+step s2r: ROLLBACK;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+
+starting permutation: s2b s1l s2u s2_blocker3 s2r s2_unlock
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2b: BEGIN;
+step s1l: SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker3: UPDATE foo SET value = 2 WHERE key = 1;
+step s2r: ROLLBACK;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1l: <... completed>
+key|value
+---+-----
+ 1| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/lock-update-traversal.out b/src/test/isolation/expected/lock-update-traversal.out
new file mode 100644
index 0000000..6d6a97d
--- /dev/null
+++ b/src/test/isolation/expected/lock-update-traversal.out
@@ -0,0 +1,63 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s2b s1s s2u s1l s2c s2d1 s1c
+step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s2b: BEGIN;
+step s1s: SELECT * FROM foo;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+step s2d1: DELETE FROM foo WHERE key = 1; <waiting ...>
+step s1c: COMMIT;
+step s2d1: <... completed>
+
+starting permutation: s1b s2b s1s s2u s1l s2c s2d2 s1c
+step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s2b: BEGIN;
+step s1s: SELECT * FROM foo;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+step s2d2: UPDATE foo SET key = 3 WHERE key = 1; <waiting ...>
+step s1c: COMMIT;
+step s2d2: <... completed>
+
+starting permutation: s1b s2b s1s s2u s1l s2c s2d3 s1c
+step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s2b: BEGIN;
+step s1s: SELECT * FROM foo;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s1l: SELECT * FROM foo FOR KEY SHARE;
+key|value
+---+-----
+ 1| 1
+(1 row)
+
+step s2c: COMMIT;
+step s2d3: UPDATE foo SET value = 3 WHERE key = 1;
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/multiple-cic.out b/src/test/isolation/expected/multiple-cic.out
new file mode 100644
index 0000000..7a0f326
--- /dev/null
+++ b/src/test/isolation/expected/multiple-cic.out
@@ -0,0 +1,24 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2l s1i s2i
+step s2l: SELECT pg_advisory_lock(281457);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1i:
+ CREATE INDEX CONCURRENTLY mcic_one_pkey ON mcic_one (id)
+ WHERE lck_shr(281457);
+ <waiting ...>
+step s2i:
+ CREATE INDEX CONCURRENTLY mcic_two_pkey ON mcic_two (id)
+ WHERE unlck();
+ <waiting ...>
+step s1i: <... completed>
+step s2i: <... completed>
+unlck
+-----
+t
+(1 row)
+
diff --git a/src/test/isolation/expected/multiple-row-versions.out b/src/test/isolation/expected/multiple-row-versions.out
new file mode 100644
index 0000000..79f492e
--- /dev/null
+++ b/src/test/isolation/expected/multiple-row-versions.out
@@ -0,0 +1,30 @@
+Parsed test spec with 4 sessions
+
+starting permutation: rx1 wx2 c2 wx3 ry3 wy4 rz4 c4 c3 wz1 c1
+step rx1: SELECT * FROM t WHERE id = 1000000;
+ id|txt
+-------+---
+1000000|
+(1 row)
+
+step wx2: UPDATE t SET txt = 'b' WHERE id = 1000000;
+step c2: COMMIT;
+step wx3: UPDATE t SET txt = 'c' WHERE id = 1000000;
+step ry3: SELECT * FROM t WHERE id = 500000;
+ id|txt
+------+---
+500000|
+(1 row)
+
+step wy4: UPDATE t SET txt = 'd' WHERE id = 500000;
+step rz4: SELECT * FROM t WHERE id = 1;
+id|txt
+--+---
+ 1|
+(1 row)
+
+step c4: COMMIT;
+step c3: COMMIT;
+step wz1: UPDATE t SET txt = 'a' WHERE id = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/multixact-no-deadlock.out b/src/test/isolation/expected/multixact-no-deadlock.out
new file mode 100644
index 0000000..4b9ce7b
--- /dev/null
+++ b/src/test/isolation/expected/multixact-no-deadlock.out
@@ -0,0 +1,32 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1lock s2lock s1svpt s3lock s1lock2 s2c s1c s3c
+step s1lock: SELECT * FROM justthis FOR SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2lock: SELECT * FROM justthis FOR SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s1svpt: SAVEPOINT foo;
+step s3lock: SELECT * FROM justthis FOR UPDATE; <waiting ...>
+step s1lock2: SELECT * FROM justthis FOR SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2c: COMMIT;
+step s1c: COMMIT;
+step s3lock: <... completed>
+value
+-----
+ 1
+(1 row)
+
+step s3c: COMMIT;
diff --git a/src/test/isolation/expected/multixact-no-forget.out b/src/test/isolation/expected/multixact-no-forget.out
new file mode 100644
index 0000000..ce06b38
--- /dev/null
+++ b/src/test/isolation/expected/multixact-no-forget.out
@@ -0,0 +1,168 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_show s1_commit s2_commit
+step s1_show: SELECT current_setting('default_transaction_isolation') <> 'read committed';
+?column?
+--------
+f
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_commit: COMMIT;
+
+starting permutation: s1_lock s2_update s2_abort s3_forkeyshr s1_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s2_abort: ROLLBACK;
+step s3_forkeyshr: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_lock s2_update s2_commit s3_forkeyshr s1_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s2_commit: COMMIT;
+step s3_forkeyshr: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 2
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_lock s2_update s1_commit s3_forkeyshr s2_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s1_commit: COMMIT;
+step s3_forkeyshr: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_commit: COMMIT;
+
+starting permutation: s1_lock s2_update s2_abort s3_fornokeyupd s1_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s2_abort: ROLLBACK;
+step s3_fornokeyupd: SELECT * FROM dont_forget FOR NO KEY UPDATE;
+value
+-----
+ 1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_lock s2_update s2_commit s3_fornokeyupd s1_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s2_commit: COMMIT;
+step s3_fornokeyupd: SELECT * FROM dont_forget FOR NO KEY UPDATE;
+value
+-----
+ 2
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_lock s2_update s1_commit s3_fornokeyupd s2_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s1_commit: COMMIT;
+step s3_fornokeyupd: SELECT * FROM dont_forget FOR NO KEY UPDATE; <waiting ...>
+step s2_commit: COMMIT;
+step s3_fornokeyupd: <... completed>
+value
+-----
+ 2
+(1 row)
+
+
+starting permutation: s1_lock s2_update s2_abort s3_forupd s1_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s2_abort: ROLLBACK;
+step s3_forupd: SELECT * FROM dont_forget FOR UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s3_forupd: <... completed>
+value
+-----
+ 1
+(1 row)
+
+
+starting permutation: s1_lock s2_update s2_commit s3_forupd s1_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s2_commit: COMMIT;
+step s3_forupd: SELECT * FROM dont_forget FOR UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s3_forupd: <... completed>
+value
+-----
+ 2
+(1 row)
+
+
+starting permutation: s1_lock s2_update s1_commit s3_forupd s2_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s1_commit: COMMIT;
+step s3_forupd: SELECT * FROM dont_forget FOR UPDATE; <waiting ...>
+step s2_commit: COMMIT;
+step s3_forupd: <... completed>
+value
+-----
+ 2
+(1 row)
+
diff --git a/src/test/isolation/expected/multixact-no-forget_1.out b/src/test/isolation/expected/multixact-no-forget_1.out
new file mode 100644
index 0000000..f15a1e1
--- /dev/null
+++ b/src/test/isolation/expected/multixact-no-forget_1.out
@@ -0,0 +1,160 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_show s1_commit s2_commit
+step s1_show: SELECT current_setting('default_transaction_isolation') <> 'read committed';
+?column?
+--------
+t
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_commit: COMMIT;
+
+starting permutation: s1_lock s2_update s2_abort s3_forkeyshr s1_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s2_abort: ROLLBACK;
+step s3_forkeyshr: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_lock s2_update s2_commit s3_forkeyshr s1_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s2_commit: COMMIT;
+step s3_forkeyshr: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 2
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_lock s2_update s1_commit s3_forkeyshr s2_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s1_commit: COMMIT;
+step s3_forkeyshr: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_commit: COMMIT;
+
+starting permutation: s1_lock s2_update s2_abort s3_fornokeyupd s1_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s2_abort: ROLLBACK;
+step s3_fornokeyupd: SELECT * FROM dont_forget FOR NO KEY UPDATE;
+value
+-----
+ 1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_lock s2_update s2_commit s3_fornokeyupd s1_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s2_commit: COMMIT;
+step s3_fornokeyupd: SELECT * FROM dont_forget FOR NO KEY UPDATE;
+value
+-----
+ 2
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_lock s2_update s1_commit s3_fornokeyupd s2_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s1_commit: COMMIT;
+step s3_fornokeyupd: SELECT * FROM dont_forget FOR NO KEY UPDATE; <waiting ...>
+step s2_commit: COMMIT;
+step s3_fornokeyupd: <... completed>
+ERROR: could not serialize access due to concurrent update
+
+starting permutation: s1_lock s2_update s2_abort s3_forupd s1_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s2_abort: ROLLBACK;
+step s3_forupd: SELECT * FROM dont_forget FOR UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s3_forupd: <... completed>
+value
+-----
+ 1
+(1 row)
+
+
+starting permutation: s1_lock s2_update s2_commit s3_forupd s1_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s2_commit: COMMIT;
+step s3_forupd: SELECT * FROM dont_forget FOR UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s3_forupd: <... completed>
+value
+-----
+ 2
+(1 row)
+
+
+starting permutation: s1_lock s2_update s1_commit s3_forupd s2_commit
+step s1_lock: SELECT * FROM dont_forget FOR KEY SHARE;
+value
+-----
+ 1
+(1 row)
+
+step s2_update: UPDATE dont_forget SET value = 2;
+step s1_commit: COMMIT;
+step s3_forupd: SELECT * FROM dont_forget FOR UPDATE; <waiting ...>
+step s2_commit: COMMIT;
+step s3_forupd: <... completed>
+ERROR: could not serialize access due to concurrent update
diff --git a/src/test/isolation/expected/nowait-2.out b/src/test/isolation/expected/nowait-2.out
new file mode 100644
index 0000000..ba18fa7
--- /dev/null
+++ b/src/test/isolation/expected/nowait-2.out
@@ -0,0 +1,55 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1a s2a s2b s1b s2c
+step s1a: SELECT * FROM foo FOR SHARE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s2a: SELECT * FROM foo FOR SHARE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s2b: SELECT * FROM foo FOR UPDATE NOWAIT;
+ERROR: could not obtain lock on row in relation "foo"
+step s1b: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s2c
+step s2a: SELECT * FROM foo FOR SHARE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s1a: SELECT * FROM foo FOR SHARE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s2b: SELECT * FROM foo FOR UPDATE NOWAIT;
+ERROR: could not obtain lock on row in relation "foo"
+step s1b: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s2c
+step s2a: SELECT * FROM foo FOR SHARE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s2b: SELECT * FROM foo FOR UPDATE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s1a: SELECT * FROM foo FOR SHARE NOWAIT;
+ERROR: could not obtain lock on row in relation "foo"
+step s1b: COMMIT;
+step s2c: COMMIT;
diff --git a/src/test/isolation/expected/nowait-3.out b/src/test/isolation/expected/nowait-3.out
new file mode 100644
index 0000000..19a5b68
--- /dev/null
+++ b/src/test/isolation/expected/nowait-3.out
@@ -0,0 +1,21 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1a s2a s3a s1b s2b s3b
+step s1a: SELECT * FROM foo FOR UPDATE;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s2a: SELECT * FROM foo FOR UPDATE; <waiting ...>
+step s3a: SELECT * FROM foo FOR UPDATE NOWAIT;
+ERROR: could not obtain lock on row in relation "foo"
+step s1b: COMMIT;
+step s2a: <... completed>
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s2b: COMMIT;
+step s3b: COMMIT;
diff --git a/src/test/isolation/expected/nowait-4.out b/src/test/isolation/expected/nowait-4.out
new file mode 100644
index 0000000..be0edbd
--- /dev/null
+++ b/src/test/isolation/expected/nowait-4.out
@@ -0,0 +1,23 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2a s1a s2b s2c s2d s2e s1b s2f
+step s2a: SELECT pg_advisory_lock(0);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1a: SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL FOR UPDATE NOWAIT; <waiting ...>
+step s2b: UPDATE foo SET data = data;
+step s2c: BEGIN;
+step s2d: UPDATE foo SET data = data;
+step s2e: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1a: <... completed>
+ERROR: could not obtain lock on row in relation "foo"
+step s1b: COMMIT;
+step s2f: COMMIT;
diff --git a/src/test/isolation/expected/nowait-4_1.out b/src/test/isolation/expected/nowait-4_1.out
new file mode 100644
index 0000000..05e2fcf
--- /dev/null
+++ b/src/test/isolation/expected/nowait-4_1.out
@@ -0,0 +1,23 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2a s1a s2b s2c s2d s2e s1b s2f
+step s2a: SELECT pg_advisory_lock(0);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1a: SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL FOR UPDATE NOWAIT; <waiting ...>
+step s2b: UPDATE foo SET data = data;
+step s2c: BEGIN;
+step s2d: UPDATE foo SET data = data;
+step s2e: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1a: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1b: COMMIT;
+step s2f: COMMIT;
diff --git a/src/test/isolation/expected/nowait-5.out b/src/test/isolation/expected/nowait-5.out
new file mode 100644
index 0000000..f1aae21
--- /dev/null
+++ b/src/test/isolation/expected/nowait-5.out
@@ -0,0 +1,43 @@
+Parsed test spec with 3 sessions
+
+starting permutation: sl1_prep upd_getlock sl1_exec upd_doupdate lk1_doforshare upd_releaselock
+step sl1_prep:
+ PREPARE sl1_run AS SELECT id FROM test_nowait WHERE pg_advisory_lock(0) is not null FOR UPDATE NOWAIT;
+
+step upd_getlock:
+ SELECT pg_advisory_lock(0);
+
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step sl1_exec:
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+ EXECUTE sl1_run;
+ SELECT xmin, xmax, ctid, * FROM test_nowait;
+ <waiting ...>
+step upd_doupdate:
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+ UPDATE test_nowait SET value = value WHERE id % 2 = 0;
+ COMMIT;
+
+step lk1_doforshare:
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+ SELECT id FROM test_nowait WHERE id % 2 = 0 FOR SHARE;
+
+id
+--
+ 2
+(1 row)
+
+step upd_releaselock:
+ SELECT pg_advisory_unlock(0);
+
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step sl1_exec: <... completed>
+ERROR: could not obtain lock on row in relation "test_nowait"
diff --git a/src/test/isolation/expected/nowait.out b/src/test/isolation/expected/nowait.out
new file mode 100644
index 0000000..ea1cdf0
--- /dev/null
+++ b/src/test/isolation/expected/nowait.out
@@ -0,0 +1,81 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1a s1b s2a s2b
+step s1a: SELECT * FROM foo FOR UPDATE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s1b: COMMIT;
+step s2a: SELECT * FROM foo FOR UPDATE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s2b: COMMIT;
+
+starting permutation: s1a s2a s1b s2b
+step s1a: SELECT * FROM foo FOR UPDATE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s2a: SELECT * FROM foo FOR UPDATE NOWAIT;
+ERROR: could not obtain lock on row in relation "foo"
+step s1b: COMMIT;
+step s2b: COMMIT;
+
+starting permutation: s1a s2a s2b s1b
+step s1a: SELECT * FROM foo FOR UPDATE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s2a: SELECT * FROM foo FOR UPDATE NOWAIT;
+ERROR: could not obtain lock on row in relation "foo"
+step s2b: COMMIT;
+step s1b: COMMIT;
+
+starting permutation: s2a s1a s1b s2b
+step s2a: SELECT * FROM foo FOR UPDATE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s1a: SELECT * FROM foo FOR UPDATE NOWAIT;
+ERROR: could not obtain lock on row in relation "foo"
+step s1b: COMMIT;
+step s2b: COMMIT;
+
+starting permutation: s2a s1a s2b s1b
+step s2a: SELECT * FROM foo FOR UPDATE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s1a: SELECT * FROM foo FOR UPDATE NOWAIT;
+ERROR: could not obtain lock on row in relation "foo"
+step s2b: COMMIT;
+step s1b: COMMIT;
+
+starting permutation: s2a s2b s1a s1b
+step s2a: SELECT * FROM foo FOR UPDATE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s2b: COMMIT;
+step s1a: SELECT * FROM foo FOR UPDATE NOWAIT;
+id|data
+--+----
+ 1|x
+(1 row)
+
+step s1b: COMMIT;
diff --git a/src/test/isolation/expected/partial-index.out b/src/test/isolation/expected/partial-index.out
new file mode 100644
index 0000000..d6cae90
--- /dev/null
+++ b/src/test/isolation/expected/partial-index.out
@@ -0,0 +1,715 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rxy1 wx1 c1 wy2 rxy2 c2
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+(9 rows)
+
+step c2: COMMIT;
+
+starting permutation: rxy1 wx1 wy2 c1 rxy2 c2
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step c1: COMMIT;
+step rxy2: select * from test_t where val2 = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rxy1 wx1 wy2 rxy2 c1 c2
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wx1 wy2 rxy2 c2 c1
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wy2 wx1 c1 rxy2 c2
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
+step rxy2: select * from test_t where val2 = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rxy1 wy2 wx1 rxy2 c1 c2
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wy2 wx1 rxy2 c2 c1
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wy2 rxy2 wx1 c1 c2
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wy2 rxy2 wx1 c2 c1
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wy2 rxy2 c2 wx1 c1
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step c2: COMMIT;
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: wy2 rxy1 wx1 c1 rxy2 c2
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
+step rxy2: select * from test_t where val2 = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: wy2 rxy1 wx1 rxy2 c1 c2
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy1 wx1 rxy2 c2 c1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy1 rxy2 wx1 c1 c2
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy1 rxy2 wx1 c2 c1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy1 rxy2 c2 wx1 c1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step c2: COMMIT;
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: wy2 rxy2 rxy1 wx1 c1 c2
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy2 rxy1 wx1 c2 c1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy2 rxy1 c2 wx1 c1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+ 9|a | 1
+10|a | 1
+(11 rows)
+
+step c2: COMMIT;
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: wy2 rxy2 c2 rxy1 wx1 c1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step c2: COMMIT;
+step rxy1: select * from test_t where val2 = 1;
+id|val1|val2
+--+----+----
+ 0|a | 1
+ 1|a | 1
+ 2|a | 1
+ 3|a | 1
+ 4|a | 1
+ 5|a | 1
+ 6|a | 1
+ 7|a | 1
+ 8|a | 1
+10|a | 1
+(10 rows)
+
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/partition-concurrent-attach.out b/src/test/isolation/expected/partition-concurrent-attach.out
new file mode 100644
index 0000000..53775f4
--- /dev/null
+++ b/src/test/isolation/expected/partition-concurrent-attach.out
@@ -0,0 +1,55 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s1a s2b s2i s1c s2c s2s
+step s1b: begin;
+step s1a: alter table tpart attach partition tpart_2 for values from (100) to (200);
+step s2b: begin;
+step s2i: insert into tpart values (110,'xxx'), (120, 'yyy'), (150, 'zzz'); <waiting ...>
+step s1c: commit;
+step s2i: <... completed>
+ERROR: new row for relation "tpart_default" violates partition constraint
+step s2c: commit;
+step s2s: select tableoid::regclass, * from tpart;
+tableoid| i|j
+--------+---+---
+tpart_2 |110|xxx
+tpart_2 |120|yyy
+tpart_2 |150|zzz
+(3 rows)
+
+
+starting permutation: s1b s1a s2b s2i2 s1c s2c s2s
+step s1b: begin;
+step s1a: alter table tpart attach partition tpart_2 for values from (100) to (200);
+step s2b: begin;
+step s2i2: insert into tpart_default (i, j) values (110, 'xxx'), (120, 'yyy'), (150, 'zzz'); <waiting ...>
+step s1c: commit;
+step s2i2: <... completed>
+ERROR: new row for relation "tpart_default" violates partition constraint
+step s2c: commit;
+step s2s: select tableoid::regclass, * from tpart;
+tableoid| i|j
+--------+---+---
+tpart_2 |110|xxx
+tpart_2 |120|yyy
+tpart_2 |150|zzz
+(3 rows)
+
+
+starting permutation: s1b s2b s2i s1a s2c s1c s2s
+step s1b: begin;
+step s2b: begin;
+step s2i: insert into tpart values (110,'xxx'), (120, 'yyy'), (150, 'zzz');
+step s1a: alter table tpart attach partition tpart_2 for values from (100) to (200); <waiting ...>
+step s2c: commit;
+step s1a: <... completed>
+ERROR: updated partition constraint for default partition "tpart_default_default" would be violated by some row
+step s1c: commit;
+step s2s: select tableoid::regclass, * from tpart;
+tableoid | i|j
+---------------------+---+---
+tpart_default_default|110|xxx
+tpart_default_default|120|yyy
+tpart_default_default|150|zzz
+(3 rows)
+
diff --git a/src/test/isolation/expected/partition-drop-index-locking.out b/src/test/isolation/expected/partition-drop-index-locking.out
new file mode 100644
index 0000000..9acd51d
--- /dev/null
+++ b/src/test/isolation/expected/partition-drop-index-locking.out
@@ -0,0 +1,100 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1begin s1lock s2begin s2drop s1select s3getlocks s1commit s3getlocks s2commit
+step s1begin: BEGIN;
+step s1lock: LOCK TABLE part_drop_index_locking_subpart_child IN ACCESS SHARE MODE;
+step s2begin: BEGIN;
+step s2drop: DROP INDEX part_drop_index_locking_idx; <waiting ...>
+step s1select: SELECT * FROM part_drop_index_locking_subpart_child;
+id
+--
+(0 rows)
+
+step s3getlocks:
+ SELECT s.query, c.relname, l.mode, l.granted
+ FROM pg_locks l
+ JOIN pg_class c ON l.relation = c.oid
+ JOIN pg_stat_activity s ON l.pid = s.pid
+ WHERE c.relname LIKE 'part_drop_index_locking%'
+ ORDER BY s.query, c.relname, l.mode, l.granted;
+
+query |relname |mode |granted
+----------------------------------------------------+---------------------------------------------+-------------------+-------
+DROP INDEX part_drop_index_locking_idx; |part_drop_index_locking |AccessExclusiveLock|t
+DROP INDEX part_drop_index_locking_idx; |part_drop_index_locking_idx |AccessExclusiveLock|t
+DROP INDEX part_drop_index_locking_idx; |part_drop_index_locking_subpart |AccessExclusiveLock|t
+DROP INDEX part_drop_index_locking_idx; |part_drop_index_locking_subpart_child |AccessExclusiveLock|f
+SELECT * FROM part_drop_index_locking_subpart_child;|part_drop_index_locking_subpart_child |AccessShareLock |t
+SELECT * FROM part_drop_index_locking_subpart_child;|part_drop_index_locking_subpart_child_id_idx |AccessShareLock |t
+SELECT * FROM part_drop_index_locking_subpart_child;|part_drop_index_locking_subpart_child_id_idx1|AccessShareLock |t
+(7 rows)
+
+step s1commit: COMMIT;
+step s2drop: <... completed>
+step s3getlocks:
+ SELECT s.query, c.relname, l.mode, l.granted
+ FROM pg_locks l
+ JOIN pg_class c ON l.relation = c.oid
+ JOIN pg_stat_activity s ON l.pid = s.pid
+ WHERE c.relname LIKE 'part_drop_index_locking%'
+ ORDER BY s.query, c.relname, l.mode, l.granted;
+
+query |relname |mode |granted
+---------------------------------------+--------------------------------------------+-------------------+-------
+DROP INDEX part_drop_index_locking_idx;|part_drop_index_locking |AccessExclusiveLock|t
+DROP INDEX part_drop_index_locking_idx;|part_drop_index_locking_idx |AccessExclusiveLock|t
+DROP INDEX part_drop_index_locking_idx;|part_drop_index_locking_subpart |AccessExclusiveLock|t
+DROP INDEX part_drop_index_locking_idx;|part_drop_index_locking_subpart_child |AccessExclusiveLock|t
+DROP INDEX part_drop_index_locking_idx;|part_drop_index_locking_subpart_child_id_idx|AccessExclusiveLock|t
+DROP INDEX part_drop_index_locking_idx;|part_drop_index_locking_subpart_id_idx |AccessExclusiveLock|t
+(6 rows)
+
+step s2commit: COMMIT;
+
+starting permutation: s1begin s1lock s2begin s2dropsub s1select s3getlocks s1commit s3getlocks s2commit
+step s1begin: BEGIN;
+step s1lock: LOCK TABLE part_drop_index_locking_subpart_child IN ACCESS SHARE MODE;
+step s2begin: BEGIN;
+step s2dropsub: DROP INDEX part_drop_index_locking_subpart_idx; <waiting ...>
+step s1select: SELECT * FROM part_drop_index_locking_subpart_child;
+id
+--
+(0 rows)
+
+step s3getlocks:
+ SELECT s.query, c.relname, l.mode, l.granted
+ FROM pg_locks l
+ JOIN pg_class c ON l.relation = c.oid
+ JOIN pg_stat_activity s ON l.pid = s.pid
+ WHERE c.relname LIKE 'part_drop_index_locking%'
+ ORDER BY s.query, c.relname, l.mode, l.granted;
+
+query |relname |mode |granted
+----------------------------------------------------+---------------------------------------------+-------------------+-------
+DROP INDEX part_drop_index_locking_subpart_idx; |part_drop_index_locking_subpart |AccessExclusiveLock|t
+DROP INDEX part_drop_index_locking_subpart_idx; |part_drop_index_locking_subpart_child |AccessExclusiveLock|f
+DROP INDEX part_drop_index_locking_subpart_idx; |part_drop_index_locking_subpart_idx |AccessExclusiveLock|t
+SELECT * FROM part_drop_index_locking_subpart_child;|part_drop_index_locking_subpart_child |AccessShareLock |t
+SELECT * FROM part_drop_index_locking_subpart_child;|part_drop_index_locking_subpart_child_id_idx |AccessShareLock |t
+SELECT * FROM part_drop_index_locking_subpart_child;|part_drop_index_locking_subpart_child_id_idx1|AccessShareLock |t
+(6 rows)
+
+step s1commit: COMMIT;
+step s2dropsub: <... completed>
+step s3getlocks:
+ SELECT s.query, c.relname, l.mode, l.granted
+ FROM pg_locks l
+ JOIN pg_class c ON l.relation = c.oid
+ JOIN pg_stat_activity s ON l.pid = s.pid
+ WHERE c.relname LIKE 'part_drop_index_locking%'
+ ORDER BY s.query, c.relname, l.mode, l.granted;
+
+query |relname |mode |granted
+-----------------------------------------------+---------------------------------------------+-------------------+-------
+DROP INDEX part_drop_index_locking_subpart_idx;|part_drop_index_locking_subpart |AccessExclusiveLock|t
+DROP INDEX part_drop_index_locking_subpart_idx;|part_drop_index_locking_subpart_child |AccessExclusiveLock|t
+DROP INDEX part_drop_index_locking_subpart_idx;|part_drop_index_locking_subpart_child_id_idx1|AccessExclusiveLock|t
+DROP INDEX part_drop_index_locking_subpart_idx;|part_drop_index_locking_subpart_idx |AccessExclusiveLock|t
+(4 rows)
+
+step s2commit: COMMIT;
diff --git a/src/test/isolation/expected/partition-key-update-1.out b/src/test/isolation/expected/partition-key-update-1.out
new file mode 100644
index 0000000..7dee144
--- /dev/null
+++ b/src/test/isolation/expected/partition-key-update-1.out
@@ -0,0 +1,129 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s2b s1u s1c s2d s2c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1u: UPDATE foo SET a=2 WHERE a=1;
+step s1c: COMMIT;
+step s2d: DELETE FROM foo WHERE a=1;
+step s2c: COMMIT;
+
+starting permutation: s1b s2b s1u s2d s1c s2c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1u: UPDATE foo SET a=2 WHERE a=1;
+step s2d: DELETE FROM foo WHERE a=1; <waiting ...>
+step s1c: COMMIT;
+step s2d: <... completed>
+ERROR: tuple to be locked was already moved to another partition due to concurrent update
+step s2c: COMMIT;
+
+starting permutation: s1b s2b s1u s2u s1c s2c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1u: UPDATE foo SET a=2 WHERE a=1;
+step s2u: UPDATE foo SET b='EFG' WHERE a=1; <waiting ...>
+step s1c: COMMIT;
+step s2u: <... completed>
+ERROR: tuple to be locked was already moved to another partition due to concurrent update
+step s2c: COMMIT;
+
+starting permutation: s1b s2b s2d s1u s2c s1c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2d: DELETE FROM foo WHERE a=1;
+step s1u: UPDATE foo SET a=2 WHERE a=1; <waiting ...>
+step s2c: COMMIT;
+step s1u: <... completed>
+step s1c: COMMIT;
+
+starting permutation: s1b s2b s1u2 s1c s2u2 s2c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1u2: UPDATE footrg SET b='EFG' WHERE a=1;
+step s1c: COMMIT;
+step s2u2: UPDATE footrg SET b='XYZ' WHERE a=1;
+step s2c: COMMIT;
+
+starting permutation: s1b s2b s1u2 s2u2 s1c s2c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1u2: UPDATE footrg SET b='EFG' WHERE a=1;
+step s2u2: UPDATE footrg SET b='XYZ' WHERE a=1; <waiting ...>
+step s1c: COMMIT;
+step s2u2: <... completed>
+ERROR: tuple to be locked was already moved to another partition due to concurrent update
+step s2c: COMMIT;
+
+starting permutation: s1b s2b s2u2 s1u2 s2c s1c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2u2: UPDATE footrg SET b='XYZ' WHERE a=1;
+step s1u2: UPDATE footrg SET b='EFG' WHERE a=1; <waiting ...>
+step s2c: COMMIT;
+step s1u2: <... completed>
+ERROR: tuple to be locked was already moved to another partition due to concurrent update
+step s1c: COMMIT;
+
+starting permutation: s1b s2b s1u3pc s2i s1c s2c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1u3pc: UPDATE foo_range_parted SET a=11 WHERE a=7;
+step s2i: INSERT INTO bar VALUES(7); <waiting ...>
+step s1c: COMMIT;
+step s2i: <... completed>
+ERROR: tuple to be locked was already moved to another partition due to concurrent update
+step s2c: COMMIT;
+
+starting permutation: s1b s2b s1u3pc s2i s1r s2c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1u3pc: UPDATE foo_range_parted SET a=11 WHERE a=7;
+step s2i: INSERT INTO bar VALUES(7); <waiting ...>
+step s1r: ROLLBACK;
+step s2i: <... completed>
+step s2c: COMMIT;
+
+starting permutation: s1b s2b s1u3npc s1u3pc s2i s1c s2c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1u3npc: UPDATE foo_range_parted SET b='XYZ' WHERE a=7;
+step s1u3pc: UPDATE foo_range_parted SET a=11 WHERE a=7;
+step s2i: INSERT INTO bar VALUES(7); <waiting ...>
+step s1c: COMMIT;
+step s2i: <... completed>
+ERROR: tuple to be locked was already moved to another partition due to concurrent update
+step s2c: COMMIT;
+
+starting permutation: s1b s2b s1u3npc s1u3pc s2i s1r s2c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1u3npc: UPDATE foo_range_parted SET b='XYZ' WHERE a=7;
+step s1u3pc: UPDATE foo_range_parted SET a=11 WHERE a=7;
+step s2i: INSERT INTO bar VALUES(7); <waiting ...>
+step s1r: ROLLBACK;
+step s2i: <... completed>
+step s2c: COMMIT;
+
+starting permutation: s1b s2b s1u3npc s1u3pc s1u3pc s2i s1c s2c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1u3npc: UPDATE foo_range_parted SET b='XYZ' WHERE a=7;
+step s1u3pc: UPDATE foo_range_parted SET a=11 WHERE a=7;
+step s1u3pc: UPDATE foo_range_parted SET a=11 WHERE a=7;
+step s2i: INSERT INTO bar VALUES(7); <waiting ...>
+step s1c: COMMIT;
+step s2i: <... completed>
+ERROR: tuple to be locked was already moved to another partition due to concurrent update
+step s2c: COMMIT;
+
+starting permutation: s1b s2b s1u3npc s1u3pc s1u3pc s2i s1r s2c
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s1u3npc: UPDATE foo_range_parted SET b='XYZ' WHERE a=7;
+step s1u3pc: UPDATE foo_range_parted SET a=11 WHERE a=7;
+step s1u3pc: UPDATE foo_range_parted SET a=11 WHERE a=7;
+step s2i: INSERT INTO bar VALUES(7); <waiting ...>
+step s1r: ROLLBACK;
+step s2i: <... completed>
+step s2c: COMMIT;
diff --git a/src/test/isolation/expected/partition-key-update-2.out b/src/test/isolation/expected/partition-key-update-2.out
new file mode 100644
index 0000000..f054de5
--- /dev/null
+++ b/src/test/isolation/expected/partition-key-update-2.out
@@ -0,0 +1,33 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1u s2donothing s3donothing s1c s2c s3select s3c
+step s1u: UPDATE foo SET a=2, b=b || ' -> moved by session-1' WHERE a=1;
+step s2donothing: INSERT INTO foo VALUES(1, 'session-2 donothing') ON CONFLICT DO NOTHING; <waiting ...>
+step s3donothing: INSERT INTO foo VALUES(2, 'session-3 donothing') ON CONFLICT DO NOTHING; <waiting ...>
+step s1c: COMMIT;
+step s2donothing: <... completed>
+step s3donothing: <... completed>
+step s2c: COMMIT;
+step s3select: SELECT * FROM foo ORDER BY a;
+a|b
+-+-----------------------------------
+1|session-2 donothing
+2|initial tuple -> moved by session-1
+(2 rows)
+
+step s3c: COMMIT;
+
+starting permutation: s2donothing s1u s3donothing s1c s2c s3select s3c
+step s2donothing: INSERT INTO foo VALUES(1, 'session-2 donothing') ON CONFLICT DO NOTHING;
+step s1u: UPDATE foo SET a=2, b=b || ' -> moved by session-1' WHERE a=1;
+step s3donothing: INSERT INTO foo VALUES(2, 'session-3 donothing') ON CONFLICT DO NOTHING; <waiting ...>
+step s1c: COMMIT;
+step s3donothing: <... completed>
+step s2c: COMMIT;
+step s3select: SELECT * FROM foo ORDER BY a;
+a|b
+-+-----------------------------------
+2|initial tuple -> moved by session-1
+(1 row)
+
+step s3c: COMMIT;
diff --git a/src/test/isolation/expected/partition-key-update-3.out b/src/test/isolation/expected/partition-key-update-3.out
new file mode 100644
index 0000000..b5872b8
--- /dev/null
+++ b/src/test/isolation/expected/partition-key-update-3.out
@@ -0,0 +1,155 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s2beginrr s3beginrr s1u s2donothing s1c s2c s3donothing s3c s2select
+step s2beginrr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s3beginrr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1u: UPDATE foo SET a=2, b=b || ' -> moved by session-1' WHERE a=1;
+step s2donothing: INSERT INTO foo VALUES(1, 'session-2 donothing') ON CONFLICT DO NOTHING; <waiting ...>
+step s1c: COMMIT;
+step s2donothing: <... completed>
+step s2c: COMMIT;
+step s3donothing: INSERT INTO foo VALUES(2, 'session-3 donothing'), (2, 'session-3 donothing2') ON CONFLICT DO NOTHING;
+step s3c: COMMIT;
+step s2select: SELECT * FROM foo ORDER BY a;
+a|b
+-+-----------------------------------
+1|session-2 donothing
+2|initial tuple -> moved by session-1
+(2 rows)
+
+
+starting permutation: s2beginrr s3beginrr s1u s3donothing s1c s3c s2donothing s2c s2select
+step s2beginrr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s3beginrr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1u: UPDATE foo SET a=2, b=b || ' -> moved by session-1' WHERE a=1;
+step s3donothing: INSERT INTO foo VALUES(2, 'session-3 donothing'), (2, 'session-3 donothing2') ON CONFLICT DO NOTHING; <waiting ...>
+step s1c: COMMIT;
+step s3donothing: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s3c: COMMIT;
+step s2donothing: INSERT INTO foo VALUES(1, 'session-2 donothing') ON CONFLICT DO NOTHING;
+step s2c: COMMIT;
+step s2select: SELECT * FROM foo ORDER BY a;
+a|b
+-+-----------------------------------
+1|session-2 donothing
+2|initial tuple -> moved by session-1
+(2 rows)
+
+
+starting permutation: s2beginrr s3beginrr s1u s2donothing s3donothing s1c s2c s3c s2select
+step s2beginrr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s3beginrr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1u: UPDATE foo SET a=2, b=b || ' -> moved by session-1' WHERE a=1;
+step s2donothing: INSERT INTO foo VALUES(1, 'session-2 donothing') ON CONFLICT DO NOTHING; <waiting ...>
+step s3donothing: INSERT INTO foo VALUES(2, 'session-3 donothing'), (2, 'session-3 donothing2') ON CONFLICT DO NOTHING; <waiting ...>
+step s1c: COMMIT;
+step s2donothing: <... completed>
+step s3donothing: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+step s3c: COMMIT;
+step s2select: SELECT * FROM foo ORDER BY a;
+a|b
+-+-----------------------------------
+1|session-2 donothing
+2|initial tuple -> moved by session-1
+(2 rows)
+
+
+starting permutation: s2beginrr s3beginrr s1u s3donothing s2donothing s1c s3c s2c s2select
+step s2beginrr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s3beginrr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1u: UPDATE foo SET a=2, b=b || ' -> moved by session-1' WHERE a=1;
+step s3donothing: INSERT INTO foo VALUES(2, 'session-3 donothing'), (2, 'session-3 donothing2') ON CONFLICT DO NOTHING; <waiting ...>
+step s2donothing: INSERT INTO foo VALUES(1, 'session-2 donothing') ON CONFLICT DO NOTHING; <waiting ...>
+step s1c: COMMIT;
+step s3donothing: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2donothing: <... completed>
+step s3c: COMMIT;
+step s2c: COMMIT;
+step s2select: SELECT * FROM foo ORDER BY a;
+a|b
+-+-----------------------------------
+1|session-2 donothing
+2|initial tuple -> moved by session-1
+(2 rows)
+
+
+starting permutation: s2begins s3begins s1u s2donothing s1c s2c s3donothing s3c s2select
+step s2begins: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s3begins: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1u: UPDATE foo SET a=2, b=b || ' -> moved by session-1' WHERE a=1;
+step s2donothing: INSERT INTO foo VALUES(1, 'session-2 donothing') ON CONFLICT DO NOTHING; <waiting ...>
+step s1c: COMMIT;
+step s2donothing: <... completed>
+step s2c: COMMIT;
+step s3donothing: INSERT INTO foo VALUES(2, 'session-3 donothing'), (2, 'session-3 donothing2') ON CONFLICT DO NOTHING;
+step s3c: COMMIT;
+step s2select: SELECT * FROM foo ORDER BY a;
+a|b
+-+-----------------------------------
+1|session-2 donothing
+2|initial tuple -> moved by session-1
+(2 rows)
+
+
+starting permutation: s2begins s3begins s1u s3donothing s1c s3c s2donothing s2c s2select
+step s2begins: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s3begins: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1u: UPDATE foo SET a=2, b=b || ' -> moved by session-1' WHERE a=1;
+step s3donothing: INSERT INTO foo VALUES(2, 'session-3 donothing'), (2, 'session-3 donothing2') ON CONFLICT DO NOTHING; <waiting ...>
+step s1c: COMMIT;
+step s3donothing: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s3c: COMMIT;
+step s2donothing: INSERT INTO foo VALUES(1, 'session-2 donothing') ON CONFLICT DO NOTHING;
+step s2c: COMMIT;
+step s2select: SELECT * FROM foo ORDER BY a;
+a|b
+-+-----------------------------------
+1|session-2 donothing
+2|initial tuple -> moved by session-1
+(2 rows)
+
+
+starting permutation: s2begins s3begins s1u s2donothing s3donothing s1c s2c s3c s2select
+step s2begins: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s3begins: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1u: UPDATE foo SET a=2, b=b || ' -> moved by session-1' WHERE a=1;
+step s2donothing: INSERT INTO foo VALUES(1, 'session-2 donothing') ON CONFLICT DO NOTHING; <waiting ...>
+step s3donothing: INSERT INTO foo VALUES(2, 'session-3 donothing'), (2, 'session-3 donothing2') ON CONFLICT DO NOTHING; <waiting ...>
+step s1c: COMMIT;
+step s2donothing: <... completed>
+step s3donothing: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2c: COMMIT;
+step s3c: COMMIT;
+step s2select: SELECT * FROM foo ORDER BY a;
+a|b
+-+-----------------------------------
+1|session-2 donothing
+2|initial tuple -> moved by session-1
+(2 rows)
+
+
+starting permutation: s2begins s3begins s1u s3donothing s2donothing s1c s3c s2c s2select
+step s2begins: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s3begins: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step s1u: UPDATE foo SET a=2, b=b || ' -> moved by session-1' WHERE a=1;
+step s3donothing: INSERT INTO foo VALUES(2, 'session-3 donothing'), (2, 'session-3 donothing2') ON CONFLICT DO NOTHING; <waiting ...>
+step s2donothing: INSERT INTO foo VALUES(1, 'session-2 donothing') ON CONFLICT DO NOTHING; <waiting ...>
+step s1c: COMMIT;
+step s3donothing: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s2donothing: <... completed>
+step s3c: COMMIT;
+step s2c: COMMIT;
+step s2select: SELECT * FROM foo ORDER BY a;
+a|b
+-+-----------------------------------
+1|session-2 donothing
+2|initial tuple -> moved by session-1
+(2 rows)
+
diff --git a/src/test/isolation/expected/partition-key-update-4.out b/src/test/isolation/expected/partition-key-update-4.out
new file mode 100644
index 0000000..91fa041
--- /dev/null
+++ b/src/test/isolation/expected/partition-key-update-4.out
@@ -0,0 +1,72 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s2b s2u1 s1u s2c s1c s1s
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2u1: UPDATE foo SET b = b || ' update2' WHERE a = 1;
+step s1u: UPDATE foo SET a = a + 1, b = b || ' update1' WHERE b like '%ABC%'; <waiting ...>
+step s2c: COMMIT;
+step s1u: <... completed>
+step s1c: COMMIT;
+step s1s: SELECT tableoid::regclass, * FROM foo ORDER BY a;
+tableoid|a|b
+--------+-+-------------------
+foo2 |2|ABC update2 update1
+(1 row)
+
+
+starting permutation: s1b s2b s2ut1 s1ut s2c s1c s1st s1stl
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2ut1: UPDATE footrg SET b = b || ' update2' WHERE a = 1;
+step s1ut: UPDATE footrg SET a = a + 1, b = b || ' update1' WHERE b like '%ABC%'; <waiting ...>
+step s2c: COMMIT;
+step s1ut: <... completed>
+step s1c: COMMIT;
+step s1st: SELECT tableoid::regclass, * FROM footrg ORDER BY a;
+tableoid|a|b
+--------+-+-------------------
+footrg2 |2|ABC update2 update1
+(1 row)
+
+step s1stl: SELECT * FROM triglog ORDER BY a;
+a|b
+-+-------------------
+1|ABC update2 trigger
+(1 row)
+
+
+starting permutation: s1b s2b s2u2 s1u s2c s1c s1s
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2u2: UPDATE foo SET b = 'EFG' WHERE a = 1;
+step s1u: UPDATE foo SET a = a + 1, b = b || ' update1' WHERE b like '%ABC%'; <waiting ...>
+step s2c: COMMIT;
+step s1u: <... completed>
+step s1c: COMMIT;
+step s1s: SELECT tableoid::regclass, * FROM foo ORDER BY a;
+tableoid|a|b
+--------+-+---
+foo1 |1|EFG
+(1 row)
+
+
+starting permutation: s1b s2b s2ut2 s1ut s2c s1c s1st s1stl
+step s1b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2b: BEGIN ISOLATION LEVEL READ COMMITTED;
+step s2ut2: UPDATE footrg SET b = 'EFG' WHERE a = 1;
+step s1ut: UPDATE footrg SET a = a + 1, b = b || ' update1' WHERE b like '%ABC%'; <waiting ...>
+step s2c: COMMIT;
+step s1ut: <... completed>
+step s1c: COMMIT;
+step s1st: SELECT tableoid::regclass, * FROM footrg ORDER BY a;
+tableoid|a|b
+--------+-+---
+footrg1 |1|EFG
+(1 row)
+
+step s1stl: SELECT * FROM triglog ORDER BY a;
+a|b
+-+-
+(0 rows)
+
diff --git a/src/test/isolation/expected/plpgsql-toast.out b/src/test/isolation/expected/plpgsql-toast.out
new file mode 100644
index 0000000..0fee795
--- /dev/null
+++ b/src/test/isolation/expected/plpgsql-toast.out
@@ -0,0 +1,316 @@
+Parsed test spec with 2 sessions
+
+starting permutation: lock assign1 vacuum unlock
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+step lock:
+ SELECT pg_advisory_lock(1);
+
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step assign1:
+do $$
+ declare
+ x text;
+ begin
+ select test1.b into x from test1;
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(x) = %', length(x);
+ end;
+$$;
+ <waiting ...>
+step vacuum:
+ VACUUM test1;
+
+step unlock:
+ SELECT pg_advisory_unlock(1);
+
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: length(x) = 6000
+step assign1: <... completed>
+
+starting permutation: lock assign2 vacuum unlock
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+step lock:
+ SELECT pg_advisory_lock(1);
+
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step assign2:
+do $$
+ declare
+ x text;
+ begin
+ x := (select test1.b from test1);
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(x) = %', length(x);
+ end;
+$$;
+ <waiting ...>
+step vacuum:
+ VACUUM test1;
+
+step unlock:
+ SELECT pg_advisory_unlock(1);
+
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: length(x) = 6000
+step assign2: <... completed>
+
+starting permutation: lock assign3 vacuum unlock
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+step lock:
+ SELECT pg_advisory_lock(1);
+
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step assign3:
+do $$
+ declare
+ r record;
+ begin
+ select * into r from test1;
+ r.b := (select test1.b from test1);
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(r) = %', length(r::text);
+ end;
+$$;
+ <waiting ...>
+step vacuum:
+ VACUUM test1;
+
+step unlock:
+ SELECT pg_advisory_unlock(1);
+
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: length(r) = 6004
+step assign3: <... completed>
+
+starting permutation: lock assign4 vacuum unlock
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+step lock:
+ SELECT pg_advisory_lock(1);
+
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step assign4:
+do $$
+ declare
+ r test2;
+ begin
+ select * into r from test1;
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(r) = %', length(r::text);
+ end;
+$$;
+ <waiting ...>
+step vacuum:
+ VACUUM test1;
+
+step unlock:
+ SELECT pg_advisory_unlock(1);
+
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: length(r) = 6004
+step assign4: <... completed>
+
+starting permutation: lock assign5 vacuum unlock
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+step lock:
+ SELECT pg_advisory_lock(1);
+
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step assign5:
+do $$
+ declare
+ r record;
+ begin
+ for r in select test1.b from test1 loop
+ null;
+ end loop;
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(r) = %', length(r::text);
+ end;
+$$;
+ <waiting ...>
+step vacuum:
+ VACUUM test1;
+
+step unlock:
+ SELECT pg_advisory_unlock(1);
+
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: length(r) = 6002
+step assign5: <... completed>
+
+starting permutation: lock assign6 vacuum unlock
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+step lock:
+ SELECT pg_advisory_lock(1);
+
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step assign6:
+do $$
+ declare
+ r record;
+ begin
+ insert into test1 values (2, repeat('bar', 3000));
+ insert into test1 values (3, repeat('baz', 4000));
+ for r in select test1.b from test1 loop
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(r) = %', length(r::text);
+ end loop;
+ end;
+$$;
+ <waiting ...>
+step vacuum:
+ VACUUM test1;
+
+step unlock:
+ SELECT pg_advisory_unlock(1);
+
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+s1: NOTICE: length(r) = 6002
+s1: NOTICE: length(r) = 9002
+s1: NOTICE: length(r) = 12002
+step assign6: <... completed>
+
+starting permutation: fetch-after-commit
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+pg_advisory_unlock_all
+----------------------
+
+(1 row)
+
+s1: NOTICE: length(t) = 6000
+s1: NOTICE: length(t) = 9000
+s1: NOTICE: length(t) = 12000
+step fetch-after-commit:
+do $$
+ declare
+ r record;
+ t text;
+ begin
+ insert into test1 values (2, repeat('bar', 3000));
+ insert into test1 values (3, repeat('baz', 4000));
+ for r in select test1.a from test1 loop
+ commit;
+ select b into t from test1 where a = r.a;
+ raise notice 'length(t) = %', length(t);
+ end loop;
+ end;
+$$;
+
diff --git a/src/test/isolation/expected/predicate-gin.out b/src/test/isolation/expected/predicate-gin.out
new file mode 100644
index 0000000..c032804
--- /dev/null
+++ b/src/test/isolation/expected/predicate-gin.out
@@ -0,0 +1,581 @@
+Parsed test spec with 3 sessions
+
+starting permutation: ra1 ro2 wo1 c1 wa2 c2
+step ra1: select * from gin_tbl where p @> array[1] limit 1;
+p
+---
+{1}
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wa2: insert into gin_tbl values (array[1]);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: ro2 ra1 wo1 c1 wa2 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step ra1: select * from gin_tbl where p @> array[1] limit 1;
+p
+---
+{1}
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wa2: insert into gin_tbl values (array[1]);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: ro2 ra1 wo1 wa2 c1 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step ra1: select * from gin_tbl where p @> array[1] limit 1;
+p
+---
+{1}
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step wa2: insert into gin_tbl values (array[1]);
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ra1 ro2 wa2 wo1 c1 c2
+step ra1: select * from gin_tbl where p @> array[1] limit 1;
+p
+---
+{1}
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wa2: insert into gin_tbl values (array[1]);
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rb1 ro2 wo1 c1 wb2 c2
+step rb1: select count(*) from gin_tbl where p @> array[2];
+count
+-----
+ 1
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wb2: insert into gin_tbl values (array[2]);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: ro2 rb1 wo1 c1 wb2 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step rb1: select count(*) from gin_tbl where p @> array[2];
+count
+-----
+ 1
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wb2: insert into gin_tbl values (array[2]);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: ro2 rb1 wo1 wb2 c1 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step rb1: select count(*) from gin_tbl where p @> array[2];
+count
+-----
+ 1
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step wb2: insert into gin_tbl values (array[2]);
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rb1 ro2 wb2 wo1 c1 c2
+step rb1: select count(*) from gin_tbl where p @> array[2];
+count
+-----
+ 1
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wb2: insert into gin_tbl values (array[2]);
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rc1 ro2 wo1 c1 wc2 c2
+step rc1: select count(*) from gin_tbl where p @> array[800];
+count
+-----
+ 1
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wc2: insert into gin_tbl values (array[800]);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: ro2 rc1 wo1 c1 wc2 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step rc1: select count(*) from gin_tbl where p @> array[800];
+count
+-----
+ 1
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wc2: insert into gin_tbl values (array[800]);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: ro2 rc1 wo1 wc2 c1 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step rc1: select count(*) from gin_tbl where p @> array[800];
+count
+-----
+ 1
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step wc2: insert into gin_tbl values (array[800]);
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rc1 ro2 wc2 wo1 c1 c2
+step rc1: select count(*) from gin_tbl where p @> array[800];
+count
+-----
+ 1
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wc2: insert into gin_tbl values (array[800]);
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ra1 ro2 wo1 c1 wb2 c2
+step ra1: select * from gin_tbl where p @> array[1] limit 1;
+p
+---
+{1}
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wb2: insert into gin_tbl values (array[2]);
+step c2: commit;
+
+starting permutation: ro2 ra1 wo1 c1 wc2 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step ra1: select * from gin_tbl where p @> array[1] limit 1;
+p
+---
+{1}
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wc2: insert into gin_tbl values (array[800]);
+step c2: commit;
+
+starting permutation: ro2 rb1 wo1 wa2 c1 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step rb1: select count(*) from gin_tbl where p @> array[2];
+count
+-----
+ 1
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step wa2: insert into gin_tbl values (array[1]);
+step c1: commit;
+step c2: commit;
+
+starting permutation: rc1 ro2 wa2 wo1 c1 c2
+step rc1: select count(*) from gin_tbl where p @> array[800];
+count
+-----
+ 1
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wa2: insert into gin_tbl values (array[1]);
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step c2: commit;
+
+starting permutation: rb1 ro2 wo1 c1 wa2 c2
+step rb1: select count(*) from gin_tbl where p @> array[2];
+count
+-----
+ 1
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wa2: insert into gin_tbl values (array[1]);
+step c2: commit;
+
+starting permutation: ro2 rb1 wo1 c1 wc2 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step rb1: select count(*) from gin_tbl where p @> array[2];
+count
+-----
+ 1
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wc2: insert into gin_tbl values (array[800]);
+step c2: commit;
+
+starting permutation: ro2 ra1 wo1 wb2 c1 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step ra1: select * from gin_tbl where p @> array[1] limit 1;
+p
+---
+{1}
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step wb2: insert into gin_tbl values (array[2]);
+step c1: commit;
+step c2: commit;
+
+starting permutation: rc1 ro2 wb2 wo1 c1 c2
+step rc1: select count(*) from gin_tbl where p @> array[800];
+count
+-----
+ 1
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wb2: insert into gin_tbl values (array[2]);
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step c2: commit;
+
+starting permutation: rc1 ro2 wo1 c1 wa2 c2
+step rc1: select count(*) from gin_tbl where p @> array[800];
+count
+-----
+ 1
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wa2: insert into gin_tbl values (array[1]);
+step c2: commit;
+
+starting permutation: ro2 rc1 wo1 c1 wb2 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step rc1: select count(*) from gin_tbl where p @> array[800];
+count
+-----
+ 1
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wb2: insert into gin_tbl values (array[2]);
+step c2: commit;
+
+starting permutation: ro2 ra1 wo1 wc2 c1 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step ra1: select * from gin_tbl where p @> array[1] limit 1;
+p
+---
+{1}
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step wc2: insert into gin_tbl values (array[800]);
+step c1: commit;
+step c2: commit;
+
+starting permutation: rb1 ro2 wc2 wo1 c1 c2
+step rb1: select count(*) from gin_tbl where p @> array[2];
+count
+-----
+ 1
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wc2: insert into gin_tbl values (array[800]);
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step c2: commit;
+
+starting permutation: fu ra1 ro2 wo1 c1 wa2 c2
+step fu: alter index ginidx set (fastupdate = on);
+step ra1: select * from gin_tbl where p @> array[1] limit 1;
+p
+---
+{1}
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wa2: insert into gin_tbl values (array[1]);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: fu ra1 ro2 wo1 c1 wb2 c2
+step fu: alter index ginidx set (fastupdate = on);
+step ra1: select * from gin_tbl where p @> array[1] limit 1;
+p
+---
+{1}
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wb2: insert into gin_tbl values (array[2]);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: ra1 ro2 wo1 c1 fu wa2 c2
+step ra1: select * from gin_tbl where p @> array[1] limit 1;
+p
+---
+{1}
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step fu: alter index ginidx set (fastupdate = on);
+step wa2: insert into gin_tbl values (array[1]);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: rd1 ro2 wo1 c1 wd2 c2
+step rd1: select count(*) from gin_tbl where p @> array[2000];
+count
+-----
+ 0
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wd2: insert into gin_tbl values (array[2000]);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: ro2 rd1 wo1 c1 wd2 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step rd1: select count(*) from gin_tbl where p @> array[2000];
+count
+-----
+ 0
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step wd2: insert into gin_tbl values (array[2000]);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: ro2 rd1 wo1 wd2 c1 c2
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step rd1: select count(*) from gin_tbl where p @> array[2000];
+count
+-----
+ 0
+(1 row)
+
+step wo1: insert into other_tbl values (1);
+step wd2: insert into gin_tbl values (array[2000]);
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rd1 ro2 wd2 wo1 c1 c2
+step rd1: select count(*) from gin_tbl where p @> array[2000];
+count
+-----
+ 0
+(1 row)
+
+step ro2: select count(*) from other_tbl;
+count
+-----
+ 0
+(1 row)
+
+step wd2: insert into gin_tbl values (array[2000]);
+step wo1: insert into other_tbl values (1);
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
diff --git a/src/test/isolation/expected/predicate-gist.out b/src/test/isolation/expected/predicate-gist.out
new file mode 100644
index 0000000..ef5d386
--- /dev/null
+++ b/src/test/isolation/expected/predicate-gist.out
@@ -0,0 +1,819 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rxy1 wx1 c1 rxy2 wy2 c2
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step c1: commit;
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2233750
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step c2: commit;
+
+starting permutation: rxy2 wy2 c2 rxy1 wx1 c1
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step c2: commit;
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+316250
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step c1: commit;
+
+starting permutation: rxy3 wx3 c1 rxy4 wy4 c2
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c1: commit;
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c2: commit;
+
+starting permutation: rxy4 wy4 c2 rxy3 wx3 c1
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c2: commit;
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c1: commit;
+
+starting permutation: rxy1 wx1 rxy2 c1 wy2 c2
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step c1: commit;
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: rxy1 wx1 rxy2 wy2 c1 c2
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wx1 rxy2 wy2 c2 c1
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step c2: commit;
+step c1: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 rxy2 wx1 c1 wy2 c2
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step c1: commit;
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: rxy1 rxy2 wx1 wy2 c1 c2
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 rxy2 wx1 wy2 c2 c1
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step c2: commit;
+step c1: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 rxy2 wy2 wx1 c1 c2
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 rxy2 wy2 wx1 c2 c1
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step c2: commit;
+step c1: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 rxy2 wy2 c2 wx1 c1
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step c2: commit;
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: commit;
+
+starting permutation: rxy2 rxy1 wx1 c1 wy2 c2
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step c1: commit;
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: rxy2 rxy1 wx1 wy2 c1 c2
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy2 rxy1 wx1 wy2 c2 c1
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step c2: commit;
+step c1: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy2 rxy1 wy2 wx1 c1 c2
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy2 rxy1 wy2 wx1 c2 c1
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step c2: commit;
+step c1: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy2 rxy1 wy2 c2 wx1 c1
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step c2: commit;
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: commit;
+
+starting permutation: rxy2 wy2 rxy1 wx1 c1 c2
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy2 wy2 rxy1 wx1 c2 c1
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+step c2: commit;
+step c1: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy2 wy2 rxy1 c2 wx1 c1
+step rxy2: select sum(p[0]) from gist_point_tbl where p >> point(7500,7500);
+ sum
+-------
+2188750
+(1 row)
+
+step wy2: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g;
+step rxy1: select sum(p[0]) from gist_point_tbl where p << point(2500, 2500);
+ sum
+------
+311250
+(1 row)
+
+step c2: commit;
+step wx1: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: commit;
+
+starting permutation: rxy3 wx3 rxy4 c1 wy4 c2
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step c1: commit;
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c2: commit;
+
+starting permutation: rxy3 wx3 rxy4 wy4 c1 c2
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c1: commit;
+step c2: commit;
+
+starting permutation: rxy3 wx3 rxy4 wy4 c2 c1
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c2: commit;
+step c1: commit;
+
+starting permutation: rxy3 rxy4 wx3 c1 wy4 c2
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c1: commit;
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c2: commit;
+
+starting permutation: rxy3 rxy4 wx3 wy4 c1 c2
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c1: commit;
+step c2: commit;
+
+starting permutation: rxy3 rxy4 wx3 wy4 c2 c1
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c2: commit;
+step c1: commit;
+
+starting permutation: rxy3 rxy4 wy4 wx3 c1 c2
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c1: commit;
+step c2: commit;
+
+starting permutation: rxy3 rxy4 wy4 wx3 c2 c1
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c2: commit;
+step c1: commit;
+
+starting permutation: rxy3 rxy4 wy4 c2 wx3 c1
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c2: commit;
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c1: commit;
+
+starting permutation: rxy4 rxy3 wx3 c1 wy4 c2
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c1: commit;
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c2: commit;
+
+starting permutation: rxy4 rxy3 wx3 wy4 c1 c2
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c1: commit;
+step c2: commit;
+
+starting permutation: rxy4 rxy3 wx3 wy4 c2 c1
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c2: commit;
+step c1: commit;
+
+starting permutation: rxy4 rxy3 wy4 wx3 c1 c2
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c1: commit;
+step c2: commit;
+
+starting permutation: rxy4 rxy3 wy4 wx3 c2 c1
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c2: commit;
+step c1: commit;
+
+starting permutation: rxy4 rxy3 wy4 c2 wx3 c1
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step c2: commit;
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c1: commit;
+
+starting permutation: rxy4 wy4 rxy3 wx3 c1 c2
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c1: commit;
+step c2: commit;
+
+starting permutation: rxy4 wy4 rxy3 wx3 c2 c1
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c2: commit;
+step c1: commit;
+
+starting permutation: rxy4 wy4 rxy3 c2 wx3 c1
+step rxy4: select sum(p[0]) from gist_point_tbl where p << point(1000,1000);
+ sum
+-----
+49500
+(1 row)
+
+step wy4: insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g;
+step rxy3: select sum(p[0]) from gist_point_tbl where p >> point(6000,6000);
+ sum
+-------
+3202000
+(1 row)
+
+step c2: commit;
+step wx3: insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g;
+step c1: commit;
diff --git a/src/test/isolation/expected/predicate-hash.out b/src/test/isolation/expected/predicate-hash.out
new file mode 100644
index 0000000..2009252
--- /dev/null
+++ b/src/test/isolation/expected/predicate-hash.out
@@ -0,0 +1,819 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rxy1 wx1 c1 rxy2 wy2 c2
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step c1: commit;
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+600
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step c2: commit;
+
+starting permutation: rxy2 wy2 c2 rxy1 wx1 c1
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step c2: commit;
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+400
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step c1: commit;
+
+starting permutation: rxy3 wx3 c1 rxy4 wy4 c2
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c1: commit;
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c2: commit;
+
+starting permutation: rxy4 wy4 c2 rxy3 wx3 c1
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c2: commit;
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c1: commit;
+
+starting permutation: rxy1 wx1 rxy2 c1 wy2 c2
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step c1: commit;
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: rxy1 wx1 rxy2 wy2 c1 c2
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wx1 rxy2 wy2 c2 c1
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step c2: commit;
+step c1: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 rxy2 wx1 c1 wy2 c2
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step c1: commit;
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: rxy1 rxy2 wx1 wy2 c1 c2
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 rxy2 wx1 wy2 c2 c1
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step c2: commit;
+step c1: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 rxy2 wy2 wx1 c1 c2
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 rxy2 wy2 wx1 c2 c1
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step c2: commit;
+step c1: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 rxy2 wy2 c2 wx1 c1
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step c2: commit;
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: commit;
+
+starting permutation: rxy2 rxy1 wx1 c1 wy2 c2
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step c1: commit;
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: commit;
+
+starting permutation: rxy2 rxy1 wx1 wy2 c1 c2
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy2 rxy1 wx1 wy2 c2 c1
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step c2: commit;
+step c1: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy2 rxy1 wy2 wx1 c1 c2
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy2 rxy1 wy2 wx1 c2 c1
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step c2: commit;
+step c1: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy2 rxy1 wy2 c2 wx1 c1
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step c2: commit;
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: commit;
+
+starting permutation: rxy2 wy2 rxy1 wx1 c1 c2
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step c1: commit;
+step c2: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy2 wy2 rxy1 wx1 c2 c1
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+step c2: commit;
+step c1: commit;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy2 wy2 rxy1 c2 wx1 c1
+step rxy2: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy2: insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g;
+step rxy1: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step c2: commit;
+step wx1: insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: commit;
+
+starting permutation: rxy3 wx3 rxy4 c1 wy4 c2
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step c1: commit;
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c2: commit;
+
+starting permutation: rxy3 wx3 rxy4 wy4 c1 c2
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c1: commit;
+step c2: commit;
+
+starting permutation: rxy3 wx3 rxy4 wy4 c2 c1
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c2: commit;
+step c1: commit;
+
+starting permutation: rxy3 rxy4 wx3 c1 wy4 c2
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c1: commit;
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c2: commit;
+
+starting permutation: rxy3 rxy4 wx3 wy4 c1 c2
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c1: commit;
+step c2: commit;
+
+starting permutation: rxy3 rxy4 wx3 wy4 c2 c1
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c2: commit;
+step c1: commit;
+
+starting permutation: rxy3 rxy4 wy4 wx3 c1 c2
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c1: commit;
+step c2: commit;
+
+starting permutation: rxy3 rxy4 wy4 wx3 c2 c1
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c2: commit;
+step c1: commit;
+
+starting permutation: rxy3 rxy4 wy4 c2 wx3 c1
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c2: commit;
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c1: commit;
+
+starting permutation: rxy4 rxy3 wx3 c1 wy4 c2
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c1: commit;
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c2: commit;
+
+starting permutation: rxy4 rxy3 wx3 wy4 c1 c2
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c1: commit;
+step c2: commit;
+
+starting permutation: rxy4 rxy3 wx3 wy4 c2 c1
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c2: commit;
+step c1: commit;
+
+starting permutation: rxy4 rxy3 wy4 wx3 c1 c2
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c1: commit;
+step c2: commit;
+
+starting permutation: rxy4 rxy3 wy4 wx3 c2 c1
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c2: commit;
+step c1: commit;
+
+starting permutation: rxy4 rxy3 wy4 c2 wx3 c1
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step c2: commit;
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c1: commit;
+
+starting permutation: rxy4 wy4 rxy3 wx3 c1 c2
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c1: commit;
+step c2: commit;
+
+starting permutation: rxy4 wy4 rxy3 wx3 c2 c1
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c2: commit;
+step c1: commit;
+
+starting permutation: rxy4 wy4 rxy3 c2 wx3 c1
+step rxy4: select sum(p) from hash_tbl where p=30;
+sum
+---
+300
+(1 row)
+
+step wy4: insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g;
+step rxy3: select sum(p) from hash_tbl where p=20;
+sum
+---
+200
+(1 row)
+
+step c2: commit;
+step wx3: insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g;
+step c1: commit;
diff --git a/src/test/isolation/expected/predicate-lock-hot-tuple.out b/src/test/isolation/expected/predicate-lock-hot-tuple.out
new file mode 100644
index 0000000..d316edb
--- /dev/null
+++ b/src/test/isolation/expected/predicate-lock-hot-tuple.out
@@ -0,0 +1,24 @@
+Parsed test spec with 2 sessions
+
+starting permutation: b1 b2 r1 r2 w1 w2 c1 c2
+step b1: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step b2: BEGIN ISOLATION LEVEL SERIALIZABLE;
+step r1: SELECT * FROM test WHERE i IN (5, 7)
+i|t
+-+----------------
+5|apple
+7|pear_hot_updated
+(2 rows)
+
+step r2: SELECT * FROM test WHERE i IN (5, 7)
+i|t
+-+----------------
+5|apple
+7|pear_hot_updated
+(2 rows)
+
+step w1: UPDATE test SET t = 'pear_xact1' WHERE i = 7
+step w2: UPDATE test SET t = 'apple_xact2' WHERE i = 5
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
diff --git a/src/test/isolation/expected/prepared-transactions-cic.out b/src/test/isolation/expected/prepared-transactions-cic.out
new file mode 100644
index 0000000..ac0ee69
--- /dev/null
+++ b/src/test/isolation/expected/prepared-transactions-cic.out
@@ -0,0 +1,20 @@
+Parsed test spec with 2 sessions
+
+starting permutation: w1 p1 cic2 c1 r2
+step w1: BEGIN; INSERT INTO cic_test VALUES (1);
+step p1: PREPARE TRANSACTION 's1';
+step cic2:
+ CREATE INDEX CONCURRENTLY on cic_test(a);
+
+ERROR: canceling statement due to lock timeout
+step c1: COMMIT PREPARED 's1';
+step r2:
+ SET enable_seqscan to off;
+ SET enable_bitmapscan to off;
+ SELECT * FROM cic_test WHERE a = 1;
+
+a
+-
+1
+(1 row)
+
diff --git a/src/test/isolation/expected/prepared-transactions.out b/src/test/isolation/expected/prepared-transactions.out
new file mode 100644
index 0000000..8a66bf9
--- /dev/null
+++ b/src/test/isolation/expected/prepared-transactions.out
@@ -0,0 +1,48205 @@
+Parsed test spec with 4 sessions
+
+starting permutation: r1 r2 w2 w3 p1 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p1 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p1 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p1 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p1 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p1 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p1 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p2 p1 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p2 p1 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p2 p3 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p2 p3 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p2 p3 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p2 p3 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p2 p3 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 w3 p3 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p1 w3 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p1 w3 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p1 w3 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p1 w3 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p1 w3 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p1 w3 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p1 w3 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p1 p2 w3 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p1 p2 w3 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p2 w3 p1 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p2 w3 p1 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p2 w3 p3 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p2 w3 p3 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p2 w3 p3 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p2 w3 p3 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p2 w3 p3 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p2 p1 w3 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 w2 p2 p1 w3 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 p1 w2 w3 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 p1 w2 w3 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 p1 w2 w3 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 p1 w2 w3 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 p1 w2 w3 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 p1 w2 w3 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 p1 w2 w3 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 p1 w2 p2 w3 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 r2 p1 w2 p2 w3 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p1 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p1 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p1 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p1 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p1 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p1 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p1 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p2 p1 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p2 p1 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p2 p3 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p2 p3 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p2 p3 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p2 p3 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p2 p3 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 r2 p3 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 r2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 r2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 r2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 r2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 r2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 r2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 r2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 p3 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 p3 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 p3 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 p3 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 p3 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 p3 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 p3 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 p3 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p1 p3 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 r2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 p1 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 p1 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 p1 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 p1 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 p1 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 p1 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 p1 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 p1 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 p1 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 c3 r2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 c3 r2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 c3 r2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 c3 r2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 c3 r2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 c3 r2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 c3 p1 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 c3 p1 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 c3 p1 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 w3 p3 c3 p1 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 r2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 r2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 r2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 r2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 r2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 r2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 r2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 p3 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 p3 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 p3 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 p3 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 p3 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 p3 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 p3 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 p3 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w2 p1 w3 p3 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p1 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p1 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p1 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p1 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p1 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p1 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p1 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p2 p1 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p2 p1 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p2 p3 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p2 p3 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p2 p3 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p2 p3 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p2 p3 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 w2 p3 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 w2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 w2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 w2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 w2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 w2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 w2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 w2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 p3 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 p3 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 p3 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 p3 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 p3 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 p3 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 p3 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 p3 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p1 p3 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 w2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 p1 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 p1 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 p1 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 p1 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 p1 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 p1 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 p1 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 p1 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 p1 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 c3 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 c3 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 c3 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 c3 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 c3 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 c3 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 c3 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 c3 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 c3 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 r2 p3 c3 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p1 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p1 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p1 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p1 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p1 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p1 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p1 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p2 p1 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p2 p1 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p2 p3 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p2 p3 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p2 p3 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p2 p3 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p2 p3 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 r2 p3 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 r2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 r2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 r2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 r2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 r2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 r2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 r2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 p3 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 p3 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 p3 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 p3 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 p3 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 p3 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 p3 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 p3 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p1 p3 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 r2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 p1 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 p1 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 p1 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 p1 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 p1 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 p1 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 p1 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 p1 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 p1 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 c3 r2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 c3 r2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 c3 r2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 c3 r2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 c3 r2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 c3 r2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 c3 p1 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 c3 p1 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 c3 p1 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 w2 p3 c3 p1 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 w2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 w2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 w2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 w2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 w2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 w2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 w2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 p3 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 p3 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 p3 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 p3 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 p3 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 p3 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 p3 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 p3 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 r2 p3 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 r2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 r2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 r2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 r2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 r2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 r2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 r2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 p3 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 p3 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 p3 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 p3 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 p3 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 p3 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 p3 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 p3 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 w2 p3 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 r2 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 r2 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 r2 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 r2 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 r2 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 r2 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 r2 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 r2 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 r2 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 w2 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 w2 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 w2 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 w2 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 w2 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 w2 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 w2 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 w2 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 w2 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 c3 r2 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 c3 r2 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 c3 r2 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 c3 r2 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 c3 w2 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 c3 w2 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 c3 w2 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 c3 w2 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 c3 c1 r2 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p1 p3 c3 c1 w2 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 w2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 p1 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 p1 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 p1 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 p1 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 p1 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 p1 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 p1 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 p1 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 p1 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 c3 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 c3 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 c3 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 c3 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 c3 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 c3 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 c3 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 c3 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 c3 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 r2 c3 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 r2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 p1 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 p1 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 p1 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 p1 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 p1 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 p1 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 p1 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 p1 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 p1 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 c3 r2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 c3 r2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 c3 r2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 c3 r2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 c3 r2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 c3 r2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 c3 p1 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 c3 p1 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 c3 p1 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 w2 c3 p1 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 r2 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 r2 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 r2 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 r2 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 r2 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 r2 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 r2 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 r2 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 r2 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 w2 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 w2 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 w2 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 w2 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 w2 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 w2 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 w2 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 w2 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 w2 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 c3 r2 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 c3 r2 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 c3 r2 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 c3 r2 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 c3 w2 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 c3 w2 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 c3 w2 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 c3 w2 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 c3 c1 r2 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 p1 c3 c1 w2 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 r2 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 r2 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 r2 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 r2 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 r2 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 r2 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 r2 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 r2 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 r2 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 r2 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 w2 r2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 w2 r2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 w2 r2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 w2 r2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 w2 r2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 w2 r2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 w2 p1 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 w2 p1 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 w2 p1 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 w2 p1 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 p1 r2 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 p1 r2 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 p1 r2 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 p1 r2 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 p1 w2 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 p1 w2 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 p1 w2 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 p1 w2 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 p1 c1 r2 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 w3 p3 c3 p1 c1 w2 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 r2 w2 w3 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 r2 w2 w3 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 r2 w2 w3 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 r2 w2 w3 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 r2 w2 w3 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 r2 w2 w3 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 r2 w2 w3 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 r2 w2 p2 w3 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 r2 w2 p2 w3 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 r2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 r2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 r2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 r2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 r2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 r2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 r2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 p3 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 p3 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 p3 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 p3 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 p3 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 p3 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 p3 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 p3 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w2 w3 p3 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 w2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 w2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 w2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 w2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 w2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 w2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 w2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 p3 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 p3 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 p3 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 p3 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 p3 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 p3 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 p3 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 p3 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 r2 p3 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 r2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 r2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 r2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 r2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 r2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 r2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 r2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 p3 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 p3 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 p3 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 p3 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 p3 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 p3 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 p3 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 p3 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 w2 p3 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 r2 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 r2 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 r2 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 r2 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 r2 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 r2 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 r2 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 r2 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 r2 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 w2 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 w2 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 w2 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 w2 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 w2 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 w2 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 w2 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 w2 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 w2 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 c3 r2 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 c3 r2 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 c3 r2 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 c3 r2 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 c3 w2 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 c3 w2 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 c3 w2 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 c3 w2 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 c3 c1 r2 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: r1 p1 w3 p3 c3 c1 w2 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p1 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p1 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p1 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p1 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p1 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p1 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p1 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p2 p1 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p2 p1 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p2 p3 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p2 p3 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p2 p3 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p2 p3 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p2 p3 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 w3 p3 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p1 w3 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p1 w3 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p1 w3 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p1 w3 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p1 w3 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p1 w3 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p1 w3 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p1 p2 w3 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p1 p2 w3 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p2 w3 p1 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p2 w3 p1 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p2 w3 p3 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p2 w3 p3 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p2 w3 p3 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p2 w3 p3 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p2 w3 p3 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p2 p1 w3 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 r2 p2 p1 w3 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p1 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p1 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p1 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p1 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p1 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p1 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p1 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p2 p1 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p2 p1 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p2 p3 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p2 p3 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p2 p3 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p2 p3 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p2 p3 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 r2 p3 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 r2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 r2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 r2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 r2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 r2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 r2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 r2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 p3 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 p3 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 p3 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 p3 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 p3 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 p3 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 p3 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 p3 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p1 p3 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 r2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 p1 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 p1 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 p1 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 p1 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 p1 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 p1 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 p1 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 p1 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 p1 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 c3 r2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 c3 r2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 c3 r2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 c3 r2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 c3 r2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 c3 r2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 c3 p1 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 c3 p1 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 c3 p1 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 w3 p3 c3 p1 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 r2 w3 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 r2 w3 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 r2 w3 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 r2 w3 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 r2 w3 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 r2 w3 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 r2 w3 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 r2 p2 w3 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 r2 p2 w3 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 r2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 r2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 r2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 r2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 r2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 r2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 r2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 p3 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 p3 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 p3 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 p3 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 p3 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 p3 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 p3 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 p3 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w2 r1 p1 w3 p3 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p1 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p1 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p1 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p1 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p1 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p1 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p1 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p2 p1 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p2 p1 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p2 p3 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p2 p3 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p2 p3 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p2 p3 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p2 p3 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 w2 p3 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 w2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 w2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 w2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 w2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 w2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 w2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 w2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 p3 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 p3 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 p3 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 p3 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 p3 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 p3 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 p3 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 p3 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p1 p3 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 w2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 p1 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 p1 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 p1 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 p1 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 p1 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 p1 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 p1 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 p1 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 p1 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 c3 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 c3 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 c3 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 c3 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 c3 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 c3 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 c3 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 c3 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 c3 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 r2 p3 c3 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p1 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p1 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p1 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p1 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p1 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p1 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p1 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p2 p1 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p2 p1 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p2 p3 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p2 p3 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p2 p3 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p2 p3 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p2 p3 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 r2 p3 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 r2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 r2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 r2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 r2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 r2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 r2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 r2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 p3 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 p3 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 p3 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 p3 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 p3 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 p3 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 p3 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 p3 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p1 p3 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 r2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 p1 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 p1 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 p1 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 p1 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 p1 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 p1 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 p1 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 p1 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 p1 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 c3 r2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 c3 r2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 c3 r2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 c3 r2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 c3 r2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 c3 r2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 c3 p1 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 c3 p1 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 c3 p1 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 w2 p3 c3 p1 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 w2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 w2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 w2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 w2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 w2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 w2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 w2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 p3 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 p3 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 p3 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 p3 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 p3 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 p3 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 p3 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 p3 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 r2 p3 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 r2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 r2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 r2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 r2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 r2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 r2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 r2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 p3 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 p3 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 p3 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 p3 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 p3 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 p3 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 p3 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 p3 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 w2 p3 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 r2 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 r2 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 r2 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 r2 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 r2 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 r2 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 r2 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 r2 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 r2 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 w2 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 w2 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 w2 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 w2 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 w2 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 w2 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 w2 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 w2 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 w2 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 c3 r2 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 c3 r2 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 c3 r2 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 c3 r2 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 c3 w2 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 c3 w2 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 c3 w2 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 c3 w2 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 c3 c1 r2 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p1 p3 c3 c1 w2 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 w2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 p1 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 p1 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 p1 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 p1 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 p1 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 p1 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 p1 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 p1 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 p1 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 c3 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 c3 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 c3 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 c3 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 c3 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 c3 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 c3 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 c3 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 c3 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 r2 c3 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 r2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 p1 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 p1 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 p1 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 p1 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 p1 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 p1 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 p1 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 p1 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 p1 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 c3 r2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 c3 r2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 c3 r2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 c3 r2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 c3 r2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 c3 r2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 c3 p1 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 c3 p1 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 c3 p1 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 w2 c3 p1 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 r2 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 r2 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 r2 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 r2 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 r2 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 r2 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 r2 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 r2 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 r2 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 w2 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 w2 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 w2 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 w2 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 w2 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 w2 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 w2 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 w2 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 w2 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 c3 r2 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 c3 r2 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 c3 r2 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 c3 r2 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 c3 w2 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 c3 w2 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 c3 w2 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 c3 w2 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 c3 c1 r2 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 p1 c3 c1 w2 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 r2 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 r2 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 r2 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 r2 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 r2 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 r2 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 r2 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 r2 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 r2 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 r2 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 w2 r2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 w2 r2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 w2 r2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 w2 r2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 w2 r2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 w2 r2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 w2 p1 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 w2 p1 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 w2 p1 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 w2 p1 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 p1 r2 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 p1 r2 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 p1 r2 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 p1 r2 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 p1 w2 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 p1 w2 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 p1 w2 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 p1 w2 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 p1 c1 r2 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r1 p3 c3 p1 c1 w2 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p1 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p1 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p1 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p1 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p1 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p1 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p1 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p2 p1 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p2 p1 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p2 p3 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p2 p3 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p2 p3 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p2 p3 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p2 p3 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 w2 p3 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 w2 p2 p3 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 w2 p2 p3 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p2: PREPARE TRANSACTION 's2';
+step p3: PREPARE TRANSACTION 's3';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+ERROR: prepared transaction with identifier "s3" does not exist
+step c2: COMMIT PREPARED 's2';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 w2 p3 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 w2 p3 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 w2 p3 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 w2 p3 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 w2 p3 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 p3 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 p3 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 p3 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 p3 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 p3 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 p3 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 p3 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 p3 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p1 p3 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 w2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 p1 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 p1 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 p1 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 p1 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 p1 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 p1 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 p1 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 p1 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 p1 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 c3 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 c3 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 c3 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 c3 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 c3 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 c3 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 c3 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 c3 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 c3 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 r1 p3 c3 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 w2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 p1 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 p1 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 p1 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 p1 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 p1 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 p1 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 p1 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 p1 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 p1 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 c3 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 c3 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 c3 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 c3 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 c3 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 c3 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 c3 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 c3 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 c3 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 r1 c3 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 c3 r1 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 c3 r1 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 c3 r1 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 c3 r1 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 c3 r1 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 c3 r1 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 c3 r1 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 c3 r1 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 c3 r1 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 r2 p3 c3 r1 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 w2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 p1 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 p1 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 p1 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 p1 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 p1 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 p1 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 p1 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 p1 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 p1 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 c3 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 c3 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 c3 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 c3 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 c3 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 c3 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 c3 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 c3 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 c3 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 r2 c3 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 r2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 p1 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 p1 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 p1 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 p1 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 p1 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 p1 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 p1 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 p1 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 p1 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 c3 r2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 c3 r2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 c3 r2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 c3 r2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 c3 r2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 c3 r2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 c3 p1 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 c3 p1 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 c3 p1 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 w2 c3 p1 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 r2 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 r2 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 r2 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 r2 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 r2 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 r2 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 r2 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 r2 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 r2 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 w2 r2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 w2 r2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 w2 r2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 w2 r2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 w2 r2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 w2 c3 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 w2 c3 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 w2 c3 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 w2 c3 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 c3 r2 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 c3 r2 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 c3 r2 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 c3 r2 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 c3 w2 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 c3 w2 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 c3 w2 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 c3 w2 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 c3 c1 r2 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 p1 c3 c1 w2 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 r2 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 r2 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 r2 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 r2 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 r2 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 r2 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 r2 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 r2 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 r2 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 r2 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 w2 r2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 w2 r2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 w2 r2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 w2 r2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 w2 r2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 w2 r2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 w2 p1 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 w2 p1 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 w2 p1 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 w2 p1 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 p1 r2 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 p1 r2 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 p1 r2 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 p1 r2 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 p1 w2 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 p1 w2 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 p1 w2 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 p1 w2 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 p1 c1 r2 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r1 c3 p1 c1 w2 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 p1 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 p1 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 p1 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 p1 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 p1 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 p2 p1 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 p2 p1 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 p2 c3 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 p2 c3 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 p2 c3 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 c3 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 c3 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 c3 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 c3 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 c3 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 w2 c3 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 p1 w2 p2 c3 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 p1 w2 p2 c3 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c3: COMMIT PREPARED 's3';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 p1 w2 c3 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 p1 w2 c3 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 p1 w2 c3 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 p1 c3 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 p1 c3 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 p1 c3 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 p1 c3 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c3: COMMIT PREPARED 's3';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 c3 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 c3 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 c3 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 c3 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 c3 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 c3 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 c3 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 c3 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 c3 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 r1 c3 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 c3 r1 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 c3 r1 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 c3 r1 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 c3 r1 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 c3 r1 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 c3 r1 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 c3 r1 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 c3 r1 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 c3 r1 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 r2 c3 r1 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 r2 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 r2 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 r2 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 r2 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 r2 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 r2 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 r2 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 r2 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 r2 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 r2 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 w2 r2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 w2 r2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 w2 r2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 w2 r2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 w2 r2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 w2 r2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 w2 p1 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 w2 p1 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 w2 p1 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 w2 p1 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 p1 r2 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 p1 r2 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 p1 r2 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 p1 r2 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 p1 w2 r2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 p1 w2 r2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 p1 w2 r2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 p1 w2 c1 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 p1 c1 r2 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r1 p1 c1 w2 r2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+step r2: SELECT * FROM test3;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r2 r1 w2 p1 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r2 r1 w2 p1 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r2 r1 w2 p1 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r2 r1 w2 p2 p1 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r2 r1 w2 p2 p1 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step p1: PREPARE TRANSACTION 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r2 r1 w2 p2 c2 p1 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r2 r1 p1 w2 p2 c1 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c1: COMMIT PREPARED 's1';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r2 r1 p1 w2 p2 c2 c1 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step c1: COMMIT PREPARED 's1';
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r2 r1 p1 w2 c1 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT PREPARED 's1';
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
+
+starting permutation: w3 p3 c3 r2 r1 p1 c1 w2 p2 c2 check
+a
+-
+(0 rows)
+
+a
+-
+(0 rows)
+
+step w3: INSERT INTO test3 VALUES (3);
+step p3: PREPARE TRANSACTION 's3';
+step c3: COMMIT PREPARED 's3';
+step r2: SELECT * FROM test3;
+c
+-
+(0 rows)
+
+step r1: SELECT * FROM test2;
+b
+-
+(0 rows)
+
+step p1: PREPARE TRANSACTION 's1';
+step c1: COMMIT PREPARED 's1';
+step w2: INSERT INTO test2 VALUES (2);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step p2: PREPARE TRANSACTION 's2';
+step c2: COMMIT PREPARED 's2';
+ERROR: prepared transaction with identifier "s2" does not exist
+step check: SELECT * FROM test1,test2,test3;
+a|b|c
+-+-+-
+(0 rows)
+
diff --git a/src/test/isolation/expected/project-manager.out b/src/test/isolation/expected/project-manager.out
new file mode 100644
index 0000000..902d188
--- /dev/null
+++ b/src/test/isolation/expected/project-manager.out
@@ -0,0 +1,379 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rx1 wy1 c1 ry2 wx2 c2
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 1
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 ry2 c1 wx2 c2
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step c1: COMMIT;
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 ry2 wx2 c1 c2
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 wy1 ry2 wx2 c2 c1
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wy1 c1 wx2 c2
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 ry2 wy1 wx2 c1 c2
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wy1 wx2 c2 c1
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 wy1 c1 c2
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 wy1 c2 c1
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 c2 wy1 c1
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 rx1 wy1 c1 wx2 c2
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: ry2 rx1 wy1 wx2 c1 c2
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wy1 wx2 c2 c1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 wy1 c1 c2
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 wy1 c2 c1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 c2 wy1 c1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 wx2 rx1 wy1 c1 c2
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 wx2 rx1 wy1 c2 c1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 wx2 rx1 c2 wy1 c1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 1
+(1 row)
+
+step c2: COMMIT;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 wx2 c2 rx1 wy1 c1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+-----
+ 0
+(1 row)
+
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/propagate-lock-delete.out b/src/test/isolation/expected/propagate-lock-delete.out
new file mode 100644
index 0000000..222b945
--- /dev/null
+++ b/src/test/isolation/expected/propagate-lock-delete.out
@@ -0,0 +1,105 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1b s1l s2b s2l s3b s3u s3d s1c s2c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s2b: BEGIN;
+step s2l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u: UPDATE parent SET c=lower(c);
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s2c: COMMIT;
+step s3d: <... completed>
+ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s2b s2l s3b s3u s3svu s3d s1c s2c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s2b: BEGIN;
+step s2l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u: UPDATE parent SET c=lower(c);
+step s3svu: SAVEPOINT f; UPDATE parent SET c = 'bbb'; ROLLBACK TO f;
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s2c: COMMIT;
+step s3d: <... completed>
+ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s2b s2l s3b s3u2 s3d s1c s2c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s2b: BEGIN;
+step s2l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u2: UPDATE parent SET i = i;
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s2c: COMMIT;
+step s3d: <... completed>
+ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s2b s2l s3b s3u2 s3svu s3d s1c s2c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s2b: BEGIN;
+step s2l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u2: UPDATE parent SET i = i;
+step s3svu: SAVEPOINT f; UPDATE parent SET c = 'bbb'; ROLLBACK TO f;
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s2c: COMMIT;
+step s3d: <... completed>
+ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s3b s3u s3d s1c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u: UPDATE parent SET c=lower(c);
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s3d: <... completed>
+ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s3b s3u s3svu s3d s1c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u: UPDATE parent SET c=lower(c);
+step s3svu: SAVEPOINT f; UPDATE parent SET c = 'bbb'; ROLLBACK TO f;
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s3d: <... completed>
+ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s3b s3u2 s3d s1c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u2: UPDATE parent SET i = i;
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s3d: <... completed>
+ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s3b s3u2 s3svu s3d s1c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u2: UPDATE parent SET i = i;
+step s3svu: SAVEPOINT f; UPDATE parent SET c = 'bbb'; ROLLBACK TO f;
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s3d: <... completed>
+ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
diff --git a/src/test/isolation/expected/read-only-anomaly-2.out b/src/test/isolation/expected/read-only-anomaly-2.out
new file mode 100644
index 0000000..543ae89
--- /dev/null
+++ b/src/test/isolation/expected/read-only-anomaly-2.out
@@ -0,0 +1,58 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s2rx s2ry s1ry s1wy s1c s2wx s2c s3c
+step s2rx: SELECT balance FROM bank_account WHERE id = 'X';
+balance
+-------
+ 0
+(1 row)
+
+step s2ry: SELECT balance FROM bank_account WHERE id = 'Y';
+balance
+-------
+ 0
+(1 row)
+
+step s1ry: SELECT balance FROM bank_account WHERE id = 'Y';
+balance
+-------
+ 0
+(1 row)
+
+step s1wy: UPDATE bank_account SET balance = 20 WHERE id = 'Y';
+step s1c: COMMIT;
+step s2wx: UPDATE bank_account SET balance = -11 WHERE id = 'X';
+step s2c: COMMIT;
+step s3c: COMMIT;
+
+starting permutation: s2rx s2ry s1ry s1wy s1c s3r s3c s2wx
+step s2rx: SELECT balance FROM bank_account WHERE id = 'X';
+balance
+-------
+ 0
+(1 row)
+
+step s2ry: SELECT balance FROM bank_account WHERE id = 'Y';
+balance
+-------
+ 0
+(1 row)
+
+step s1ry: SELECT balance FROM bank_account WHERE id = 'Y';
+balance
+-------
+ 0
+(1 row)
+
+step s1wy: UPDATE bank_account SET balance = 20 WHERE id = 'Y';
+step s1c: COMMIT;
+step s3r: SELECT id, balance FROM bank_account WHERE id IN ('X', 'Y') ORDER BY id;
+id|balance
+--+-------
+X | 0
+Y | 20
+(2 rows)
+
+step s3c: COMMIT;
+step s2wx: UPDATE bank_account SET balance = -11 WHERE id = 'X';
+ERROR: could not serialize access due to read/write dependencies among transactions
diff --git a/src/test/isolation/expected/read-only-anomaly-3.out b/src/test/isolation/expected/read-only-anomaly-3.out
new file mode 100644
index 0000000..4f7d3f8
--- /dev/null
+++ b/src/test/isolation/expected/read-only-anomaly-3.out
@@ -0,0 +1,34 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s2rx s2ry s1ry s1wy s1c s3r s2wx s2c s3c
+step s2rx: SELECT balance FROM bank_account WHERE id = 'X';
+balance
+-------
+ 0
+(1 row)
+
+step s2ry: SELECT balance FROM bank_account WHERE id = 'Y';
+balance
+-------
+ 0
+(1 row)
+
+step s1ry: SELECT balance FROM bank_account WHERE id = 'Y';
+balance
+-------
+ 0
+(1 row)
+
+step s1wy: UPDATE bank_account SET balance = 20 WHERE id = 'Y';
+step s1c: COMMIT;
+step s3r: SELECT id, balance FROM bank_account WHERE id IN ('X', 'Y') ORDER BY id; <waiting ...>
+step s2wx: UPDATE bank_account SET balance = -11 WHERE id = 'X';
+step s2c: COMMIT;
+step s3r: <... completed>
+id|balance
+--+-------
+X | -11
+Y | 20
+(2 rows)
+
+step s3c: COMMIT;
diff --git a/src/test/isolation/expected/read-only-anomaly.out b/src/test/isolation/expected/read-only-anomaly.out
new file mode 100644
index 0000000..96df5e2
--- /dev/null
+++ b/src/test/isolation/expected/read-only-anomaly.out
@@ -0,0 +1,33 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s2rx s2ry s1ry s1wy s1c s3r s2wx s2c s3c
+step s2rx: SELECT balance FROM bank_account WHERE id = 'X';
+balance
+-------
+ 0
+(1 row)
+
+step s2ry: SELECT balance FROM bank_account WHERE id = 'Y';
+balance
+-------
+ 0
+(1 row)
+
+step s1ry: SELECT balance FROM bank_account WHERE id = 'Y';
+balance
+-------
+ 0
+(1 row)
+
+step s1wy: UPDATE bank_account SET balance = 20 WHERE id = 'Y';
+step s1c: COMMIT;
+step s3r: SELECT id, balance FROM bank_account WHERE id IN ('X', 'Y') ORDER BY id;
+id|balance
+--+-------
+X | 0
+Y | 20
+(2 rows)
+
+step s2wx: UPDATE bank_account SET balance = -11 WHERE id = 'X';
+step s2c: COMMIT;
+step s3c: COMMIT;
diff --git a/src/test/isolation/expected/read-write-unique-2.out b/src/test/isolation/expected/read-write-unique-2.out
new file mode 100644
index 0000000..13b7cdc
--- /dev/null
+++ b/src/test/isolation/expected/read-write-unique-2.out
@@ -0,0 +1,37 @@
+Parsed test spec with 2 sessions
+
+starting permutation: r1 r2 w1 w2 c1 c2
+step r1: SELECT * FROM test WHERE i = 42;
+i
+-
+(0 rows)
+
+step r2: SELECT * FROM test WHERE i = 42;
+i
+-
+(0 rows)
+
+step w1: INSERT INTO test VALUES (42);
+step w2: INSERT INTO test VALUES (42); <waiting ...>
+step c1: COMMIT;
+step w2: <... completed>
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: r1 w1 c1 r2 w2 c2
+step r1: SELECT * FROM test WHERE i = 42;
+i
+-
+(0 rows)
+
+step w1: INSERT INTO test VALUES (42);
+step c1: COMMIT;
+step r2: SELECT * FROM test WHERE i = 42;
+ i
+--
+42
+(1 row)
+
+step w2: INSERT INTO test VALUES (42);
+ERROR: duplicate key value violates unique constraint "test_pkey"
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/read-write-unique-3.out b/src/test/isolation/expected/read-write-unique-3.out
new file mode 100644
index 0000000..7735d5e
--- /dev/null
+++ b/src/test/isolation/expected/read-write-unique-3.out
@@ -0,0 +1,14 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rw1 rw2 c1 c2
+step rw1: SELECT insert_unique(1, '1');
+insert_unique
+-------------
+
+(1 row)
+
+step rw2: SELECT insert_unique(1, '2'); <waiting ...>
+step c1: COMMIT;
+step rw2: <... completed>
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/read-write-unique-4.out b/src/test/isolation/expected/read-write-unique-4.out
new file mode 100644
index 0000000..aa96530
--- /dev/null
+++ b/src/test/isolation/expected/read-write-unique-4.out
@@ -0,0 +1,49 @@
+Parsed test spec with 2 sessions
+
+starting permutation: r1 r2 w1 w2 c1 c2
+step r1: SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016;
+coalesce
+--------
+ 3
+(1 row)
+
+step r2: SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016;
+coalesce
+--------
+ 3
+(1 row)
+
+step w1: INSERT INTO invoice VALUES (2016, 3);
+step w2: INSERT INTO invoice VALUES (2016, 3); <waiting ...>
+step c1: COMMIT;
+step w2: <... completed>
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: r1 w1 w2 c1 c2
+step r1: SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016;
+coalesce
+--------
+ 3
+(1 row)
+
+step w1: INSERT INTO invoice VALUES (2016, 3);
+step w2: INSERT INTO invoice VALUES (2016, 3); <waiting ...>
+step c1: COMMIT;
+step w2: <... completed>
+ERROR: duplicate key value violates unique constraint "invoice_pkey"
+step c2: COMMIT;
+
+starting permutation: r2 w1 w2 c1 c2
+step r2: SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016;
+coalesce
+--------
+ 3
+(1 row)
+
+step w1: INSERT INTO invoice VALUES (2016, 3);
+step w2: INSERT INTO invoice VALUES (2016, 3); <waiting ...>
+step c1: COMMIT;
+step w2: <... completed>
+ERROR: duplicate key value violates unique constraint "invoice_pkey"
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/read-write-unique.out b/src/test/isolation/expected/read-write-unique.out
new file mode 100644
index 0000000..2abecc6
--- /dev/null
+++ b/src/test/isolation/expected/read-write-unique.out
@@ -0,0 +1,37 @@
+Parsed test spec with 2 sessions
+
+starting permutation: r1 r2 w1 w2 c1 c2
+step r1: SELECT * FROM test;
+i
+-
+(0 rows)
+
+step r2: SELECT * FROM test;
+i
+-
+(0 rows)
+
+step w1: INSERT INTO test VALUES (42);
+step w2: INSERT INTO test VALUES (42); <waiting ...>
+step c1: COMMIT;
+step w2: <... completed>
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: r1 w1 c1 r2 w2 c2
+step r1: SELECT * FROM test;
+i
+-
+(0 rows)
+
+step w1: INSERT INTO test VALUES (42);
+step c1: COMMIT;
+step r2: SELECT * FROM test;
+ i
+--
+42
+(1 row)
+
+step w2: INSERT INTO test VALUES (42);
+ERROR: duplicate key value violates unique constraint "test_pkey"
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/receipt-report.out b/src/test/isolation/expected/receipt-report.out
new file mode 100644
index 0000000..1f25018
--- /dev/null
+++ b/src/test/isolation/expected/receipt-report.out
@@ -0,0 +1,4215 @@
+Parsed test spec with 3 sessions
+
+starting permutation: rxwy1 c1 wx2 c2 rx3 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+
+starting permutation: rxwy1 c1 wx2 rx3 c2 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+
+starting permutation: rxwy1 c1 wx2 rx3 ry3 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 c1 wx2 rx3 ry3 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 c1 rx3 wx2 c2 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+
+starting permutation: rxwy1 c1 rx3 wx2 ry3 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 c1 rx3 wx2 ry3 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 c1 rx3 ry3 wx2 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 c1 rx3 ry3 wx2 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 c1 rx3 ry3 c3 wx2 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rxwy1 wx2 c1 c2 rx3 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 c1 rx3 c2 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 c1 rx3 ry3 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 c1 rx3 ry3 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 wx2 c2 c1 rx3 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 c2 rx3 c1 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 c2 rx3 ry3 c1 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 c2 rx3 ry3 c3 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxwy1 wx2 rx3 c1 c2 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 c1 ry3 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 c1 ry3 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 c2 c1 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 c2 ry3 c1 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 c2 ry3 c3 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 ry3 c1 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 ry3 c1 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 ry3 c2 c1 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 ry3 c2 c3 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 ry3 c3 c1 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 ry3 c3 c2 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 rx3 c1 wx2 c2 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 c1 wx2 ry3 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 c1 wx2 ry3 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 c1 ry3 wx2 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 c1 ry3 wx2 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 c1 ry3 c3 wx2 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 c1 c2 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 c1 ry3 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 c1 ry3 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 c2 c1 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 c2 ry3 c1 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 c2 ry3 c3 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 ry3 c1 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 ry3 c1 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 ry3 c2 c1 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 ry3 c2 c3 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 ry3 c3 c1 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 ry3 c3 c2 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 c1 wx2 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 c1 wx2 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 c1 c3 wx2 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 wx2 c1 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 wx2 c1 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 wx2 c2 c1 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 wx2 c2 c3 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 wx2 c3 c1 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 wx2 c3 c2 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 c3 c1 wx2 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 c3 wx2 c1 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 c3 wx2 c2 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rxwy1 c1 c2 rx3 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 c1 rx3 c2 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 c1 rx3 ry3 c2 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 c1 rx3 ry3 c3 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rxwy1 c2 c1 rx3 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+ 3| 12-22-2008| 4.00
+(3 rows)
+
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 c2 rx3 c1 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 c2 rx3 ry3 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 c2 rx3 ry3 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx2 rxwy1 rx3 c1 c2 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 c1 ry3 c2 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 c1 ry3 c3 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 c2 c1 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 c2 ry3 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 c2 ry3 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 ry3 c1 c2 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 ry3 c1 c3 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 ry3 c2 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 ry3 c2 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 ry3 c3 c1 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 ry3 c3 c2 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 c2 rxwy1 c1 rx3 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: wx2 c2 rxwy1 rx3 c1 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: wx2 c2 rxwy1 rx3 ry3 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 c2 rxwy1 rx3 ry3 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 c2 rx3 rxwy1 c1 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: wx2 c2 rx3 rxwy1 ry3 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 c2 rx3 rxwy1 ry3 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 c2 rx3 ry3 rxwy1 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 c2 rx3 ry3 rxwy1 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 c2 rx3 ry3 c3 rxwy1 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-23-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 c1 c2 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 c1 ry3 c2 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 c1 ry3 c3 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 c2 c1 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 c2 ry3 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 c2 ry3 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 ry3 c1 c2 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 ry3 c1 c3 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 ry3 c2 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 ry3 c2 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 ry3 c3 c1 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 ry3 c3 c2 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 c2 rxwy1 c1 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 c2 rxwy1 ry3 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 c2 rxwy1 ry3 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 c2 ry3 rxwy1 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 c2 ry3 rxwy1 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 c2 ry3 c3 rxwy1 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 ry3 rxwy1 c1 c2 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 ry3 rxwy1 c1 c3 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rx3 ry3 rxwy1 c2 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 ry3 rxwy1 c2 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 ry3 rxwy1 c3 c1 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rx3 ry3 rxwy1 c3 c2 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 ry3 c2 rxwy1 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 ry3 c2 rxwy1 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 ry3 c2 c3 rxwy1 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 ry3 c3 rxwy1 c1 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rx3 ry3 c3 rxwy1 c2 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 ry3 c3 c2 rxwy1 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: rx3 rxwy1 c1 wx2 c2 ry3 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 c1 wx2 ry3 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 c1 wx2 ry3 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 c1 ry3 wx2 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 c1 ry3 wx2 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 c1 ry3 c3 wx2 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 c1 c2 ry3 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 c1 ry3 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 c1 ry3 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 c2 c1 ry3 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 c2 ry3 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 c2 ry3 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 ry3 c1 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 ry3 c1 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 ry3 c2 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 ry3 c2 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 ry3 c3 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 ry3 c3 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 c1 wx2 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 c1 wx2 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 c1 c3 wx2 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 wx2 c1 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 wx2 c1 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 wx2 c2 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 wx2 c2 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 wx2 c3 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 wx2 c3 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 c3 c1 wx2 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 c3 wx2 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 c3 wx2 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 c1 c2 ry3 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 c1 ry3 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 c1 ry3 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 c2 c1 ry3 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 c2 ry3 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 c2 ry3 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 ry3 c1 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 ry3 c1 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 ry3 c2 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 ry3 c2 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 ry3 c3 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 ry3 c3 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 c2 rxwy1 c1 ry3 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 c2 rxwy1 ry3 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 c2 rxwy1 ry3 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 c2 ry3 rxwy1 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 c2 ry3 rxwy1 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 c2 ry3 c3 rxwy1 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 ry3 rxwy1 c1 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 ry3 rxwy1 c1 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 wx2 ry3 rxwy1 c2 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 ry3 rxwy1 c2 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 ry3 rxwy1 c3 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 wx2 ry3 rxwy1 c3 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 ry3 c2 rxwy1 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 ry3 c2 rxwy1 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 ry3 c2 c3 rxwy1 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c2: COMMIT;
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 ry3 c3 rxwy1 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 wx2 ry3 c3 rxwy1 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 ry3 c3 c2 rxwy1 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 c1 wx2 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 c1 wx2 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 c1 c3 wx2 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 wx2 c1 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 wx2 c1 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 wx2 c2 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 wx2 c2 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 wx2 c3 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 wx2 c3 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 c3 c1 wx2 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 c3 wx2 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 c3 wx2 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 wx2 rxwy1 c1 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 ry3 wx2 rxwy1 c1 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 wx2 rxwy1 c2 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 ry3 wx2 rxwy1 c2 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 wx2 rxwy1 c3 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 wx2 rxwy1 c3 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 wx2 c2 rxwy1 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 ry3 wx2 c2 rxwy1 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 wx2 c2 c3 rxwy1 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 wx2 c3 rxwy1 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 wx2 c3 rxwy1 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 wx2 c3 c2 rxwy1 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 c3 rxwy1 c1 wx2 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 c3 rxwy1 wx2 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 c3 rxwy1 wx2 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 c3 wx2 rxwy1 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 c3 wx2 rxwy1 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 c3 wx2 c2 rxwy1 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k |deposit_date
+-------+------------
+receipt| 12-22-2008
+(1 row)
+
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no|deposit_date|amount
+----------+------------+------
+ 1| 12-22-2008| 1.00
+ 2| 12-22-2008| 2.00
+(2 rows)
+
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/referential-integrity.out b/src/test/isolation/expected/referential-integrity.out
new file mode 100644
index 0000000..7679397
--- /dev/null
+++ b/src/test/isolation/expected/referential-integrity.out
@@ -0,0 +1,839 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rx1 wy1 c1 rx2 ry2 wx2 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+ 1
+(1 row)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 rx2 c1 ry2 wx2 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step c1: COMMIT;
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 rx2 ry2 c1 wx2 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step c1: COMMIT;
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 rx2 ry2 wx2 c1 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 wy1 rx2 ry2 wx2 c2 c1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 wy1 c1 ry2 wx2 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 rx2 wy1 ry2 c1 wx2 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step c1: COMMIT;
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 rx2 wy1 ry2 wx2 c1 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 wy1 ry2 wx2 c2 c1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 ry2 wy1 c1 wx2 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 rx2 ry2 wy1 wx2 c1 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wy1: INSERT INTO b VALUES (1);
+step wx2: DELETE FROM a WHERE i = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 ry2 wy1 wx2 c2 c1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wy1: INSERT INTO b VALUES (1);
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 ry2 wx2 wy1 c1 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 ry2 wx2 wy1 c2 c1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step wy1: INSERT INTO b VALUES (1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 ry2 wx2 c2 wy1 c1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step wy1: INSERT INTO b VALUES (1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: rx2 rx1 wy1 c1 ry2 wx2 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx2 rx1 wy1 ry2 c1 wx2 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step c1: COMMIT;
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx2 rx1 wy1 ry2 wx2 c1 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 rx1 wy1 ry2 wx2 c2 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 rx1 ry2 wy1 c1 wx2 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx2 rx1 ry2 wy1 wx2 c1 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wy1: INSERT INTO b VALUES (1);
+step wx2: DELETE FROM a WHERE i = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 rx1 ry2 wy1 wx2 c2 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wy1: INSERT INTO b VALUES (1);
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 rx1 ry2 wx2 wy1 c1 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 rx1 ry2 wx2 wy1 c2 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step wy1: INSERT INTO b VALUES (1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 rx1 ry2 wx2 c2 wy1 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step wy1: INSERT INTO b VALUES (1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: rx2 ry2 rx1 wy1 c1 wx2 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx2 ry2 rx1 wy1 wx2 c1 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step wx2: DELETE FROM a WHERE i = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 ry2 rx1 wy1 wx2 c2 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 ry2 rx1 wx2 wy1 c1 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 ry2 rx1 wx2 wy1 c2 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step wy1: INSERT INTO b VALUES (1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 ry2 rx1 wx2 c2 wy1 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step wy1: INSERT INTO b VALUES (1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: rx2 ry2 wx2 rx1 wy1 c1 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 ry2 wx2 rx1 wy1 c2 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step wy1: INSERT INTO b VALUES (1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 ry2 wx2 rx1 c2 wy1 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step c2: COMMIT;
+step wy1: INSERT INTO b VALUES (1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: rx2 ry2 wx2 c2 rx1 wy1 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+-
+1
+(1 row)
+
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+----
+(0 rows)
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+-
+(0 rows)
+
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/reindex-concurrently-toast.out b/src/test/isolation/expected/reindex-concurrently-toast.out
new file mode 100644
index 0000000..a6f1edc
--- /dev/null
+++ b/src/test/isolation/expected/reindex-concurrently-toast.out
@@ -0,0 +1,775 @@
+Parsed test spec with 2 sessions
+
+starting permutation: lrex1 ins1 retab2 end1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step end1: COMMIT;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+ 3|3333333333
+(3 rows)
+
+
+starting permutation: lrex1 ins1 reind2 end1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step end1: COMMIT;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+ 3|3333333333
+(3 rows)
+
+
+starting permutation: lrex1 upd1 retab2 end1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step end1: COMMIT;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|4444444444
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lrex1 upd1 reind2 end1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step end1: COMMIT;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|4444444444
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lrex1 del1 retab2 end1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step del1: DELETE FROM reind_con_wide WHERE id = 2;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step end1: COMMIT;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+(1 row)
+
+
+starting permutation: lrex1 del1 reind2 end1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step del1: DELETE FROM reind_con_wide WHERE id = 2;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step end1: COMMIT;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+(1 row)
+
+
+starting permutation: lrex1 dro1 retab2 end1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step dro1: DROP TABLE reind_con_wide;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step end1: COMMIT;
+step retab2: <... completed>
+ERROR: relation "pg_toast.reind_con_toast" does not exist
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+ERROR: relation "reind_con_wide" does not exist
+
+starting permutation: lrex1 dro1 reind2 end1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step dro1: DROP TABLE reind_con_wide;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step end1: COMMIT;
+step reind2: <... completed>
+ERROR: relation "pg_toast.reind_con_toast_idx" does not exist
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+ERROR: relation "reind_con_wide" does not exist
+
+starting permutation: lrex1 retab2 dro1 end1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast;
+step dro1: DROP TABLE reind_con_wide;
+step end1: COMMIT;
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+ERROR: relation "reind_con_wide" does not exist
+
+starting permutation: lrex1 reind2 dro1 end1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx;
+step dro1: DROP TABLE reind_con_wide;
+step end1: COMMIT;
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+ERROR: relation "reind_con_wide" does not exist
+
+starting permutation: lsha1 ins1 retab2 end1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step end1: COMMIT;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+ 3|3333333333
+(3 rows)
+
+
+starting permutation: lsha1 ins1 reind2 end1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step end1: COMMIT;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+ 3|3333333333
+(3 rows)
+
+
+starting permutation: lsha1 upd1 retab2 end1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step end1: COMMIT;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|4444444444
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lsha1 upd1 reind2 end1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step end1: COMMIT;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|4444444444
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lsha1 del1 retab2 end1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step del1: DELETE FROM reind_con_wide WHERE id = 2;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step end1: COMMIT;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+(1 row)
+
+
+starting permutation: lsha1 del1 reind2 end1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step del1: DELETE FROM reind_con_wide WHERE id = 2;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step end1: COMMIT;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+(1 row)
+
+
+starting permutation: lsha1 dro1 retab2 end1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step dro1: DROP TABLE reind_con_wide;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step end1: COMMIT;
+step retab2: <... completed>
+ERROR: relation "pg_toast.reind_con_toast" does not exist
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+ERROR: relation "reind_con_wide" does not exist
+
+starting permutation: lsha1 dro1 reind2 end1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step dro1: DROP TABLE reind_con_wide;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step end1: COMMIT;
+step reind2: <... completed>
+ERROR: relation "pg_toast.reind_con_toast_idx" does not exist
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+ERROR: relation "reind_con_wide" does not exist
+
+starting permutation: lsha1 retab2 dro1 end1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast;
+step dro1: DROP TABLE reind_con_wide;
+step end1: COMMIT;
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+ERROR: relation "reind_con_wide" does not exist
+
+starting permutation: lsha1 reind2 dro1 end1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx;
+step dro1: DROP TABLE reind_con_wide;
+step end1: COMMIT;
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+ERROR: relation "reind_con_wide" does not exist
+
+starting permutation: lexc1 ins1 retab2 end1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step end1: COMMIT;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+ 3|3333333333
+(3 rows)
+
+
+starting permutation: lexc1 ins1 reind2 end1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step end1: COMMIT;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+ 3|3333333333
+(3 rows)
+
+
+starting permutation: lexc1 upd1 retab2 end1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step end1: COMMIT;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|4444444444
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lexc1 upd1 reind2 end1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step end1: COMMIT;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|4444444444
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lexc1 del1 retab2 end1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step del1: DELETE FROM reind_con_wide WHERE id = 2;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step end1: COMMIT;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+(1 row)
+
+
+starting permutation: lexc1 del1 reind2 end1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step del1: DELETE FROM reind_con_wide WHERE id = 2;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step end1: COMMIT;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+(1 row)
+
+
+starting permutation: lexc1 dro1 retab2 end1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step dro1: DROP TABLE reind_con_wide;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step end1: COMMIT;
+step retab2: <... completed>
+ERROR: relation "pg_toast.reind_con_toast" does not exist
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+ERROR: relation "reind_con_wide" does not exist
+
+starting permutation: lexc1 dro1 reind2 end1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step dro1: DROP TABLE reind_con_wide;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step end1: COMMIT;
+step reind2: <... completed>
+ERROR: relation "pg_toast.reind_con_toast_idx" does not exist
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+ERROR: relation "reind_con_wide" does not exist
+
+starting permutation: lexc1 retab2 dro1 end1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast;
+step dro1: DROP TABLE reind_con_wide;
+step end1: COMMIT;
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+ERROR: relation "reind_con_wide" does not exist
+
+starting permutation: lexc1 reind2 dro1 end1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx;
+step dro1: DROP TABLE reind_con_wide;
+step end1: COMMIT;
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+ERROR: relation "reind_con_wide" does not exist
+
+starting permutation: lrex1 ins1 retab2 rol1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step rol1: ROLLBACK;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lrex1 ins1 reind2 rol1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step rol1: ROLLBACK;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lrex1 upd1 retab2 rol1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step rol1: ROLLBACK;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lrex1 upd1 reind2 rol1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step rol1: ROLLBACK;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lrex1 del1 retab2 rol1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step del1: DELETE FROM reind_con_wide WHERE id = 2;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step rol1: ROLLBACK;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lrex1 del1 reind2 rol1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step del1: DELETE FROM reind_con_wide WHERE id = 2;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step rol1: ROLLBACK;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lrex1 dro1 retab2 rol1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step dro1: DROP TABLE reind_con_wide;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step rol1: ROLLBACK;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lrex1 dro1 reind2 rol1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step dro1: DROP TABLE reind_con_wide;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step rol1: ROLLBACK;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lrex1 retab2 dro1 rol1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast;
+step dro1: DROP TABLE reind_con_wide;
+step rol1: ROLLBACK;
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lrex1 reind2 dro1 rol1 sel2
+step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx;
+step dro1: DROP TABLE reind_con_wide;
+step rol1: ROLLBACK;
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lsha1 ins1 retab2 rol1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step rol1: ROLLBACK;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lsha1 ins1 reind2 rol1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step rol1: ROLLBACK;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lsha1 upd1 retab2 rol1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step rol1: ROLLBACK;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lsha1 upd1 reind2 rol1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step rol1: ROLLBACK;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lsha1 del1 retab2 rol1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step del1: DELETE FROM reind_con_wide WHERE id = 2;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step rol1: ROLLBACK;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lsha1 del1 reind2 rol1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step del1: DELETE FROM reind_con_wide WHERE id = 2;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step rol1: ROLLBACK;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lsha1 dro1 retab2 rol1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step dro1: DROP TABLE reind_con_wide;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step rol1: ROLLBACK;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lsha1 dro1 reind2 rol1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step dro1: DROP TABLE reind_con_wide;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step rol1: ROLLBACK;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lsha1 retab2 dro1 rol1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast;
+step dro1: DROP TABLE reind_con_wide;
+step rol1: ROLLBACK;
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lsha1 reind2 dro1 rol1 sel2
+step lsha1: lock TABLE reind_con_wide in SHARE MODE;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx;
+step dro1: DROP TABLE reind_con_wide;
+step rol1: ROLLBACK;
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lexc1 ins1 retab2 rol1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step rol1: ROLLBACK;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lexc1 ins1 reind2 rol1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step rol1: ROLLBACK;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lexc1 upd1 retab2 rol1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step rol1: ROLLBACK;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lexc1 upd1 reind2 rol1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step rol1: ROLLBACK;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lexc1 del1 retab2 rol1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step del1: DELETE FROM reind_con_wide WHERE id = 2;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step rol1: ROLLBACK;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lexc1 del1 reind2 rol1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step del1: DELETE FROM reind_con_wide WHERE id = 2;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step rol1: ROLLBACK;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lexc1 dro1 retab2 rol1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step dro1: DROP TABLE reind_con_wide;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
+step rol1: ROLLBACK;
+step retab2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lexc1 dro1 reind2 rol1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step dro1: DROP TABLE reind_con_wide;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
+step rol1: ROLLBACK;
+step reind2: <... completed>
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lexc1 retab2 dro1 rol1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast;
+step dro1: DROP TABLE reind_con_wide;
+step rol1: ROLLBACK;
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
+
+starting permutation: lexc1 reind2 dro1 rol1 sel2
+step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
+step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx;
+step dro1: DROP TABLE reind_con_wide;
+step rol1: ROLLBACK;
+step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
+id| substr
+--+----------
+ 1|1111111111
+ 2|2222222222
+(2 rows)
+
diff --git a/src/test/isolation/expected/reindex-concurrently.out b/src/test/isolation/expected/reindex-concurrently.out
new file mode 100644
index 0000000..eea5b2b
--- /dev/null
+++ b/src/test/isolation/expected/reindex-concurrently.out
@@ -0,0 +1,90 @@
+Parsed test spec with 3 sessions
+
+starting permutation: reindex sel1 upd2 ins2 del2 end1 end2
+step reindex: REINDEX TABLE CONCURRENTLY reind_con_tab;
+step sel1: SELECT data FROM reind_con_tab WHERE id = 3;
+data
+----
+aaaa
+(1 row)
+
+step upd2: UPDATE reind_con_tab SET data = 'bbbb' WHERE id = 3;
+step ins2: INSERT INTO reind_con_tab(data) VALUES ('cccc');
+step del2: DELETE FROM reind_con_tab WHERE data = 'cccc';
+step end1: COMMIT;
+step end2: COMMIT;
+
+starting permutation: sel1 reindex upd2 ins2 del2 end1 end2
+step sel1: SELECT data FROM reind_con_tab WHERE id = 3;
+data
+----
+aaaa
+(1 row)
+
+step reindex: REINDEX TABLE CONCURRENTLY reind_con_tab; <waiting ...>
+step upd2: UPDATE reind_con_tab SET data = 'bbbb' WHERE id = 3;
+step ins2: INSERT INTO reind_con_tab(data) VALUES ('cccc');
+step del2: DELETE FROM reind_con_tab WHERE data = 'cccc';
+step end1: COMMIT;
+step end2: COMMIT;
+step reindex: <... completed>
+
+starting permutation: sel1 upd2 reindex ins2 del2 end1 end2
+step sel1: SELECT data FROM reind_con_tab WHERE id = 3;
+data
+----
+aaaa
+(1 row)
+
+step upd2: UPDATE reind_con_tab SET data = 'bbbb' WHERE id = 3;
+step reindex: REINDEX TABLE CONCURRENTLY reind_con_tab; <waiting ...>
+step ins2: INSERT INTO reind_con_tab(data) VALUES ('cccc');
+step del2: DELETE FROM reind_con_tab WHERE data = 'cccc';
+step end1: COMMIT;
+step end2: COMMIT;
+step reindex: <... completed>
+
+starting permutation: sel1 upd2 ins2 reindex del2 end1 end2
+step sel1: SELECT data FROM reind_con_tab WHERE id = 3;
+data
+----
+aaaa
+(1 row)
+
+step upd2: UPDATE reind_con_tab SET data = 'bbbb' WHERE id = 3;
+step ins2: INSERT INTO reind_con_tab(data) VALUES ('cccc');
+step reindex: REINDEX TABLE CONCURRENTLY reind_con_tab; <waiting ...>
+step del2: DELETE FROM reind_con_tab WHERE data = 'cccc';
+step end1: COMMIT;
+step end2: COMMIT;
+step reindex: <... completed>
+
+starting permutation: sel1 upd2 ins2 del2 reindex end1 end2
+step sel1: SELECT data FROM reind_con_tab WHERE id = 3;
+data
+----
+aaaa
+(1 row)
+
+step upd2: UPDATE reind_con_tab SET data = 'bbbb' WHERE id = 3;
+step ins2: INSERT INTO reind_con_tab(data) VALUES ('cccc');
+step del2: DELETE FROM reind_con_tab WHERE data = 'cccc';
+step reindex: REINDEX TABLE CONCURRENTLY reind_con_tab; <waiting ...>
+step end1: COMMIT;
+step end2: COMMIT;
+step reindex: <... completed>
+
+starting permutation: sel1 upd2 ins2 del2 end1 reindex end2
+step sel1: SELECT data FROM reind_con_tab WHERE id = 3;
+data
+----
+aaaa
+(1 row)
+
+step upd2: UPDATE reind_con_tab SET data = 'bbbb' WHERE id = 3;
+step ins2: INSERT INTO reind_con_tab(data) VALUES ('cccc');
+step del2: DELETE FROM reind_con_tab WHERE data = 'cccc';
+step end1: COMMIT;
+step reindex: REINDEX TABLE CONCURRENTLY reind_con_tab; <waiting ...>
+step end2: COMMIT;
+step reindex: <... completed>
diff --git a/src/test/isolation/expected/reindex-schema.out b/src/test/isolation/expected/reindex-schema.out
new file mode 100644
index 0000000..0884e75
--- /dev/null
+++ b/src/test/isolation/expected/reindex-schema.out
@@ -0,0 +1,17 @@
+Parsed test spec with 3 sessions
+
+starting permutation: begin1 lock1 reindex2 drop3 end1
+step begin1: BEGIN;
+step lock1: LOCK reindex_schema.tab_locked IN SHARE UPDATE EXCLUSIVE MODE;
+step reindex2: REINDEX SCHEMA reindex_schema; <waiting ...>
+step drop3: DROP TABLE reindex_schema.tab_dropped;
+step end1: COMMIT;
+step reindex2: <... completed>
+
+starting permutation: begin1 lock1 reindex_conc2 drop3 end1
+step begin1: BEGIN;
+step lock1: LOCK reindex_schema.tab_locked IN SHARE UPDATE EXCLUSIVE MODE;
+step reindex_conc2: REINDEX SCHEMA CONCURRENTLY reindex_schema; <waiting ...>
+step drop3: DROP TABLE reindex_schema.tab_dropped;
+step end1: COMMIT;
+step reindex_conc2: <... completed>
diff --git a/src/test/isolation/expected/ri-trigger.out b/src/test/isolation/expected/ri-trigger.out
new file mode 100644
index 0000000..842df80
--- /dev/null
+++ b/src/test/isolation/expected/ri-trigger.out
@@ -0,0 +1,131 @@
+Parsed test spec with 2 sessions
+
+starting permutation: wxry1 c1 r2 wyrx2 c2
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step c1: COMMIT;
+step r2: SELECT TRUE;
+bool
+----
+t
+(1 row)
+
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+ERROR: child row exists
+step c2: COMMIT;
+
+starting permutation: wxry1 r2 c1 wyrx2 c2
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step r2: SELECT TRUE;
+bool
+----
+t
+(1 row)
+
+step c1: COMMIT;
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: wxry1 r2 wyrx2 c1 c2
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step r2: SELECT TRUE;
+bool
+----
+t
+(1 row)
+
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wxry1 r2 wyrx2 c2 c1
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step r2: SELECT TRUE;
+bool
+----
+t
+(1 row)
+
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: r2 wxry1 c1 wyrx2 c2
+step r2: SELECT TRUE;
+bool
+----
+t
+(1 row)
+
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step c1: COMMIT;
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: r2 wxry1 wyrx2 c1 c2
+step r2: SELECT TRUE;
+bool
+----
+t
+(1 row)
+
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: r2 wxry1 wyrx2 c2 c1
+step r2: SELECT TRUE;
+bool
+----
+t
+(1 row)
+
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: r2 wyrx2 wxry1 c1 c2
+step r2: SELECT TRUE;
+bool
+----
+t
+(1 row)
+
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: r2 wyrx2 wxry1 c2 c1
+step r2: SELECT TRUE;
+bool
+----
+t
+(1 row)
+
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: r2 wyrx2 c2 wxry1 c1
+step r2: SELECT TRUE;
+bool
+----
+t
+(1 row)
+
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step c2: COMMIT;
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+ERROR: parent row missing
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/sequence-ddl.out b/src/test/isolation/expected/sequence-ddl.out
new file mode 100644
index 0000000..52b0538
--- /dev/null
+++ b/src/test/isolation/expected/sequence-ddl.out
@@ -0,0 +1,91 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1alter s1commit s2nv
+step s1alter: ALTER SEQUENCE seq1 MAXVALUE 10;
+step s1commit: COMMIT;
+step s2nv: SELECT nextval('seq1') FROM generate_series(1, 15);
+ERROR: nextval: reached maximum value of sequence "seq1" (10)
+
+starting permutation: s1alter s2nv s1commit
+step s1alter: ALTER SEQUENCE seq1 MAXVALUE 10;
+step s2nv: SELECT nextval('seq1') FROM generate_series(1, 15); <waiting ...>
+step s1commit: COMMIT;
+step s2nv: <... completed>
+ERROR: nextval: reached maximum value of sequence "seq1" (10)
+
+starting permutation: s1restart s2nv s1commit
+step s1restart: ALTER SEQUENCE seq1 RESTART WITH 5;
+step s2nv: SELECT nextval('seq1') FROM generate_series(1, 15); <waiting ...>
+step s1commit: COMMIT;
+step s2nv: <... completed>
+nextval
+-------
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+(15 rows)
+
+
+starting permutation: s1restart s2nv s1commit
+step s1restart: ALTER SEQUENCE seq1 RESTART WITH 5;
+step s2nv: SELECT nextval('seq1') FROM generate_series(1, 15); <waiting ...>
+step s1commit: COMMIT;
+step s2nv: <... completed>
+nextval
+-------
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+(15 rows)
+
+
+starting permutation: s2begin s2nv s1alter2 s2commit s1commit
+step s2begin: BEGIN;
+step s2nv: SELECT nextval('seq1') FROM generate_series(1, 15);
+nextval
+-------
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+(15 rows)
+
+step s1alter2: ALTER SEQUENCE seq1 MAXVALUE 20; <waiting ...>
+step s2commit: COMMIT;
+step s1alter2: <... completed>
+step s1commit: COMMIT;
diff --git a/src/test/isolation/expected/serializable-parallel-2.out b/src/test/isolation/expected/serializable-parallel-2.out
new file mode 100644
index 0000000..92753cc
--- /dev/null
+++ b/src/test/isolation/expected/serializable-parallel-2.out
@@ -0,0 +1,50 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1r s2r1 s1c s2r2 s2c
+step s1r: SELECT * FROM foo;
+ a
+--
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+(10 rows)
+
+step s2r1: SELECT * FROM foo;
+ a
+--
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+(10 rows)
+
+step s1c: COMMIT;
+step s2r2: SELECT * FROM foo;
+ a
+--
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+(10 rows)
+
+step s2c: COMMIT;
diff --git a/src/test/isolation/expected/serializable-parallel.out b/src/test/isolation/expected/serializable-parallel.out
new file mode 100644
index 0000000..543ae89
--- /dev/null
+++ b/src/test/isolation/expected/serializable-parallel.out
@@ -0,0 +1,58 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s2rx s2ry s1ry s1wy s1c s2wx s2c s3c
+step s2rx: SELECT balance FROM bank_account WHERE id = 'X';
+balance
+-------
+ 0
+(1 row)
+
+step s2ry: SELECT balance FROM bank_account WHERE id = 'Y';
+balance
+-------
+ 0
+(1 row)
+
+step s1ry: SELECT balance FROM bank_account WHERE id = 'Y';
+balance
+-------
+ 0
+(1 row)
+
+step s1wy: UPDATE bank_account SET balance = 20 WHERE id = 'Y';
+step s1c: COMMIT;
+step s2wx: UPDATE bank_account SET balance = -11 WHERE id = 'X';
+step s2c: COMMIT;
+step s3c: COMMIT;
+
+starting permutation: s2rx s2ry s1ry s1wy s1c s3r s3c s2wx
+step s2rx: SELECT balance FROM bank_account WHERE id = 'X';
+balance
+-------
+ 0
+(1 row)
+
+step s2ry: SELECT balance FROM bank_account WHERE id = 'Y';
+balance
+-------
+ 0
+(1 row)
+
+step s1ry: SELECT balance FROM bank_account WHERE id = 'Y';
+balance
+-------
+ 0
+(1 row)
+
+step s1wy: UPDATE bank_account SET balance = 20 WHERE id = 'Y';
+step s1c: COMMIT;
+step s3r: SELECT id, balance FROM bank_account WHERE id IN ('X', 'Y') ORDER BY id;
+id|balance
+--+-------
+X | 0
+Y | 20
+(2 rows)
+
+step s3c: COMMIT;
+step s2wx: UPDATE bank_account SET balance = -11 WHERE id = 'X';
+ERROR: could not serialize access due to read/write dependencies among transactions
diff --git a/src/test/isolation/expected/simple-write-skew.out b/src/test/isolation/expected/simple-write-skew.out
new file mode 100644
index 0000000..835500d
--- /dev/null
+++ b/src/test/isolation/expected/simple-write-skew.out
@@ -0,0 +1,41 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rwx1 c1 rwx2 c2
+step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear';
+step c1: COMMIT;
+step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple'
+step c2: COMMIT;
+
+starting permutation: rwx1 rwx2 c1 c2
+step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear';
+step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple'
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rwx1 rwx2 c2 c1
+step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear';
+step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple'
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rwx2 rwx1 c1 c2
+step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple'
+step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rwx2 rwx1 c2 c1
+step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple'
+step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rwx2 c2 rwx1 c1
+step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple'
+step c2: COMMIT;
+step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear';
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/skip-locked-2.out b/src/test/isolation/expected/skip-locked-2.out
new file mode 100644
index 0000000..3302d2e
--- /dev/null
+++ b/src/test/isolation/expected/skip-locked-2.out
@@ -0,0 +1,67 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1a s2a s2b s1b s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s2c
+step s2a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s2c
+step s2a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: COMMIT;
+step s2c: COMMIT;
diff --git a/src/test/isolation/expected/skip-locked-3.out b/src/test/isolation/expected/skip-locked-3.out
new file mode 100644
index 0000000..be1f84d
--- /dev/null
+++ b/src/test/isolation/expected/skip-locked-3.out
@@ -0,0 +1,25 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1a s2a s3a s1b s2b s3b
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE LIMIT 1; <waiting ...>
+step s3a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: COMMIT;
+step s2a: <... completed>
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2b: COMMIT;
+step s3b: COMMIT;
diff --git a/src/test/isolation/expected/skip-locked-4.out b/src/test/isolation/expected/skip-locked-4.out
new file mode 100644
index 0000000..cfa9ae1
--- /dev/null
+++ b/src/test/isolation/expected/skip-locked-4.out
@@ -0,0 +1,27 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2a s1a s2b s2c s2d s2e s1b s2f
+step s2a: SELECT pg_advisory_lock(0);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1a: SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED; <waiting ...>
+step s2b: UPDATE foo SET data = data WHERE id = 1;
+step s2c: BEGIN;
+step s2d: UPDATE foo SET data = data WHERE id = 1;
+step s2e: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1a: <... completed>
+id|data
+--+----
+ 2|x
+(1 row)
+
+step s1b: COMMIT;
+step s2f: COMMIT;
diff --git a/src/test/isolation/expected/skip-locked-4_1.out b/src/test/isolation/expected/skip-locked-4_1.out
new file mode 100644
index 0000000..489dcab
--- /dev/null
+++ b/src/test/isolation/expected/skip-locked-4_1.out
@@ -0,0 +1,23 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2a s1a s2b s2c s2d s2e s1b s2f
+step s2a: SELECT pg_advisory_lock(0);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1a: SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED; <waiting ...>
+step s2b: UPDATE foo SET data = data WHERE id = 1;
+step s2c: BEGIN;
+step s2d: UPDATE foo SET data = data WHERE id = 1;
+step s2e: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1a: <... completed>
+ERROR: could not serialize access due to concurrent update
+step s1b: COMMIT;
+step s2f: COMMIT;
diff --git a/src/test/isolation/expected/skip-locked.out b/src/test/isolation/expected/skip-locked.out
new file mode 100644
index 0000000..3dc5768
--- /dev/null
+++ b/src/test/isolation/expected/skip-locked.out
@@ -0,0 +1,561 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1a s1b s1c s2a s2b s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1c: COMMIT;
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s1a s1b s2a s1c s2b s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1c: COMMIT;
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s1a s1b s2a s2b s1c s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1a s1b s2a s2b s2c s1c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s1a s2a s1b s1c s2b s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1c: COMMIT;
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s1a s2a s1b s2b s1c s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1a s2a s1b s2b s2c s1c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s1a s2a s2b s1b s1c s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1a s2a s2b s1b s2c s1c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s1b s1c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2c: COMMIT;
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1c: COMMIT;
+
+starting permutation: s2a s1a s1b s1c s2b s2c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1c: COMMIT;
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2c: COMMIT;
+
+starting permutation: s2a s1a s1b s2b s1c s2c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2a s1a s1b s2b s2c s1c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s1c s2c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s2c s1c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s1b s1c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2c: COMMIT;
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s1c s2c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s2c s1c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s1b s1c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 2|bar |NEW
+(1 row)
+
+step s2c: COMMIT;
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s1b s1c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s2c: COMMIT;
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id|data|status
+--+----+------
+ 1|foo |NEW
+(1 row)
+
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/temp-schema-cleanup.out b/src/test/isolation/expected/temp-schema-cleanup.out
new file mode 100644
index 0000000..a46e8a7
--- /dev/null
+++ b/src/test/isolation/expected/temp-schema-cleanup.out
@@ -0,0 +1,116 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_create_temp_objects s1_discard_temp s2_check_schema
+step s1_create_temp_objects:
+
+ -- create function large enough to be toasted, to ensure we correctly clean those up, a prior bug
+ -- https://postgr.es/m/CAOFAq3BU5Mf2TTvu8D9n_ZOoFAeQswuzk7yziAb7xuw_qyw5gw%40mail.gmail.com
+ SELECT exec(format($outer$
+ CREATE OR REPLACE FUNCTION pg_temp.long() RETURNS text LANGUAGE sql AS $body$ SELECT %L; $body$$outer$,
+ (SELECT string_agg(g.i::text||':'||random()::text, '|') FROM generate_series(1, 100) g(i))));
+
+ -- The above bug requirs function removal to happen after a catalog
+ -- invalidation. dependency.c sorts objects in descending oid order so
+ -- that newer objects are deleted before older objects, so create a
+ -- table after.
+ CREATE TEMPORARY TABLE invalidate_catalog_cache();
+
+ -- test non-temp function is dropped when depending on temp table
+ CREATE TEMPORARY TABLE just_give_me_a_type(id serial primary key);
+
+ CREATE FUNCTION uses_a_temp_type(just_give_me_a_type) RETURNS int LANGUAGE sql AS $$SELECT 1;$$;
+
+exec
+----
+
+(1 row)
+
+step s1_discard_temp:
+ DISCARD TEMP;
+
+step s2_check_schema:
+ SELECT oid::regclass FROM pg_class WHERE relnamespace = (SELECT oid FROM s1_temp_schema);
+ SELECT oid::regproc FROM pg_proc WHERE pronamespace = (SELECT oid FROM s1_temp_schema);
+ SELECT oid::regproc FROM pg_type WHERE typnamespace = (SELECT oid FROM s1_temp_schema);
+
+oid
+---
+(0 rows)
+
+oid
+---
+(0 rows)
+
+oid
+---
+(0 rows)
+
+
+starting permutation: s1_advisory s2_advisory s1_create_temp_objects s1_exit s2_check_schema
+step s1_advisory:
+ SELECT pg_advisory_lock('pg_namespace'::regclass::int8);
+
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2_advisory:
+ SELECT pg_advisory_lock('pg_namespace'::regclass::int8);
+ <waiting ...>
+step s1_create_temp_objects:
+
+ -- create function large enough to be toasted, to ensure we correctly clean those up, a prior bug
+ -- https://postgr.es/m/CAOFAq3BU5Mf2TTvu8D9n_ZOoFAeQswuzk7yziAb7xuw_qyw5gw%40mail.gmail.com
+ SELECT exec(format($outer$
+ CREATE OR REPLACE FUNCTION pg_temp.long() RETURNS text LANGUAGE sql AS $body$ SELECT %L; $body$$outer$,
+ (SELECT string_agg(g.i::text||':'||random()::text, '|') FROM generate_series(1, 100) g(i))));
+
+ -- The above bug requirs function removal to happen after a catalog
+ -- invalidation. dependency.c sorts objects in descending oid order so
+ -- that newer objects are deleted before older objects, so create a
+ -- table after.
+ CREATE TEMPORARY TABLE invalidate_catalog_cache();
+
+ -- test non-temp function is dropped when depending on temp table
+ CREATE TEMPORARY TABLE just_give_me_a_type(id serial primary key);
+
+ CREATE FUNCTION uses_a_temp_type(just_give_me_a_type) RETURNS int LANGUAGE sql AS $$SELECT 1;$$;
+
+exec
+----
+
+(1 row)
+
+step s1_exit:
+ SELECT pg_terminate_backend(pg_backend_pid());
+
+FATAL: terminating connection due to administrator command
+FATAL: terminating connection due to administrator command
+server closed the connection unexpectedly
+ This probably means the server terminated abnormally
+ before or while processing the request.
+
+step s2_advisory: <... completed>
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2_check_schema:
+ SELECT oid::regclass FROM pg_class WHERE relnamespace = (SELECT oid FROM s1_temp_schema);
+ SELECT oid::regproc FROM pg_proc WHERE pronamespace = (SELECT oid FROM s1_temp_schema);
+ SELECT oid::regproc FROM pg_type WHERE typnamespace = (SELECT oid FROM s1_temp_schema);
+
+oid
+---
+(0 rows)
+
+oid
+---
+(0 rows)
+
+oid
+---
+(0 rows)
+
diff --git a/src/test/isolation/expected/temporal-range-integrity.out b/src/test/isolation/expected/temporal-range-integrity.out
new file mode 100644
index 0000000..039193e
--- /dev/null
+++ b/src/test/isolation/expected/temporal-range-integrity.out
@@ -0,0 +1,379 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rx1 wy1 c1 ry2 wx2 c2
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 1
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 ry2 c1 wx2 c2
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step c1: COMMIT;
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 ry2 wx2 c1 c2
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 wy1 ry2 wx2 c2 c1
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wy1 c1 wx2 c2
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 ry2 wy1 wx2 c1 c2
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wy1 wx2 c2 c1
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 wy1 c1 c2
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 wy1 c2 c1
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 c2 wy1 c1
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 rx1 wy1 c1 wx2 c2
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: ry2 rx1 wy1 wx2 c1 c2
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wy1 wx2 c2 c1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 wy1 c1 c2
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 wy1 c2 c1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 c2 wy1 c1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 wx2 rx1 wy1 c1 c2
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 wx2 rx1 wy1 c2 c1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 wx2 rx1 c2 wy1 c1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 1
+(1 row)
+
+step c2: COMMIT;
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 wx2 c2 rx1 wy1 c1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+-----
+ 0
+(1 row)
+
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+-----
+ 0
+(1 row)
+
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/timeouts.out b/src/test/isolation/expected/timeouts.out
new file mode 100644
index 0000000..9328676
--- /dev/null
+++ b/src/test/isolation/expected/timeouts.out
@@ -0,0 +1,81 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rdtbl sto locktbl
+step rdtbl: SELECT * FROM accounts;
+accountid|balance
+---------+-------
+checking | 600
+savings | 600
+(2 rows)
+
+step sto: SET statement_timeout = '10ms';
+step locktbl: LOCK TABLE accounts; <waiting ...>
+step locktbl: <... completed>
+ERROR: canceling statement due to statement timeout
+
+starting permutation: rdtbl lto locktbl
+step rdtbl: SELECT * FROM accounts;
+accountid|balance
+---------+-------
+checking | 600
+savings | 600
+(2 rows)
+
+step lto: SET lock_timeout = '10ms';
+step locktbl: LOCK TABLE accounts; <waiting ...>
+step locktbl: <... completed>
+ERROR: canceling statement due to lock timeout
+
+starting permutation: rdtbl lsto locktbl
+step rdtbl: SELECT * FROM accounts;
+accountid|balance
+---------+-------
+checking | 600
+savings | 600
+(2 rows)
+
+step lsto: SET lock_timeout = '10ms'; SET statement_timeout = '10s';
+step locktbl: LOCK TABLE accounts; <waiting ...>
+step locktbl: <... completed>
+ERROR: canceling statement due to lock timeout
+
+starting permutation: rdtbl slto locktbl
+step rdtbl: SELECT * FROM accounts;
+accountid|balance
+---------+-------
+checking | 600
+savings | 600
+(2 rows)
+
+step slto: SET lock_timeout = '10s'; SET statement_timeout = '10ms';
+step locktbl: LOCK TABLE accounts; <waiting ...>
+step locktbl: <... completed>
+ERROR: canceling statement due to statement timeout
+
+starting permutation: wrtbl sto update
+step wrtbl: UPDATE accounts SET balance = balance + 100;
+step sto: SET statement_timeout = '10ms';
+step update: DELETE FROM accounts WHERE accountid = 'checking'; <waiting ...>
+step update: <... completed>
+ERROR: canceling statement due to statement timeout
+
+starting permutation: wrtbl lto update
+step wrtbl: UPDATE accounts SET balance = balance + 100;
+step lto: SET lock_timeout = '10ms';
+step update: DELETE FROM accounts WHERE accountid = 'checking'; <waiting ...>
+step update: <... completed>
+ERROR: canceling statement due to lock timeout
+
+starting permutation: wrtbl lsto update
+step wrtbl: UPDATE accounts SET balance = balance + 100;
+step lsto: SET lock_timeout = '10ms'; SET statement_timeout = '10s';
+step update: DELETE FROM accounts WHERE accountid = 'checking'; <waiting ...>
+step update: <... completed>
+ERROR: canceling statement due to lock timeout
+
+starting permutation: wrtbl slto update
+step wrtbl: UPDATE accounts SET balance = balance + 100;
+step slto: SET lock_timeout = '10s'; SET statement_timeout = '10ms';
+step update: DELETE FROM accounts WHERE accountid = 'checking'; <waiting ...>
+step update: <... completed>
+ERROR: canceling statement due to statement timeout
diff --git a/src/test/isolation/expected/total-cash.out b/src/test/isolation/expected/total-cash.out
new file mode 100644
index 0000000..7b00e0d
--- /dev/null
+++ b/src/test/isolation/expected/total-cash.out
@@ -0,0 +1,349 @@
+Parsed test spec with 2 sessions
+
+starting permutation: wx1 rxy1 c1 wy2 rxy2 c2
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c1: COMMIT;
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+---
+800
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: wx1 rxy1 wy2 c1 rxy2 c2
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step c1: COMMIT;
+step rxy2: SELECT SUM(balance) FROM accounts;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: wx1 rxy1 wy2 rxy2 c1 c2
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 rxy1 wy2 rxy2 c2 c1
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 wy2 rxy1 c1 rxy2 c2
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c1: COMMIT;
+step rxy2: SELECT SUM(balance) FROM accounts;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: wx1 wy2 rxy1 rxy2 c1 c2
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 wy2 rxy1 rxy2 c2 c1
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 wy2 rxy2 rxy1 c1 c2
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 wy2 rxy2 rxy1 c2 c1
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 wy2 rxy2 c2 rxy1 c1
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c2: COMMIT;
+step rxy1: SELECT SUM(balance) FROM accounts;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: wy2 wx1 rxy1 c1 rxy2 c2
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c1: COMMIT;
+step rxy2: SELECT SUM(balance) FROM accounts;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: wy2 wx1 rxy1 rxy2 c1 c2
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 wx1 rxy1 rxy2 c2 c1
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 wx1 rxy2 rxy1 c1 c2
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 wx1 rxy2 rxy1 c2 c1
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 wx1 rxy2 c2 rxy1 c1
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c2: COMMIT;
+step rxy1: SELECT SUM(balance) FROM accounts;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: wy2 rxy2 wx1 rxy1 c1 c2
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy2 wx1 rxy1 c2 c1
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy2 wx1 c2 rxy1 c1
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step c2: COMMIT;
+step rxy1: SELECT SUM(balance) FROM accounts;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: wy2 rxy2 c2 wx1 rxy1 c1
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+ sum
+----
+1000
+(1 row)
+
+step c2: COMMIT;
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+---
+800
+(1 row)
+
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/truncate-conflict.out b/src/test/isolation/expected/truncate-conflict.out
new file mode 100644
index 0000000..00d5ce3
--- /dev/null
+++ b/src/test/isolation/expected/truncate-conflict.out
@@ -0,0 +1,115 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_tab_lookup s2_auth s2_truncate s1_commit s2_reset
+step s1_begin: BEGIN;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+--------
+t
+(1 row)
+
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s2_truncate: TRUNCATE truncate_tab;
+ERROR: permission denied for table truncate_tab
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_auth s2_truncate s1_tab_lookup s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s2_truncate: TRUNCATE truncate_tab;
+ERROR: permission denied for table truncate_tab
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+--------
+t
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_auth s1_tab_lookup s2_truncate s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+--------
+t
+(1 row)
+
+step s2_truncate: TRUNCATE truncate_tab;
+ERROR: permission denied for table truncate_tab
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s2_auth s2_truncate s1_begin s1_tab_lookup s1_commit s2_reset
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s2_truncate: TRUNCATE truncate_tab;
+ERROR: permission denied for table truncate_tab
+step s1_begin: BEGIN;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+--------
+t
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s1_tab_lookup s2_grant s2_auth s2_truncate s1_commit s2_reset
+step s1_begin: BEGIN;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+--------
+t
+(1 row)
+
+step s2_grant: GRANT TRUNCATE ON truncate_tab TO regress_truncate_conflict;
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s2_truncate: TRUNCATE truncate_tab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_truncate: <... completed>
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_grant s2_auth s2_truncate s1_tab_lookup s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_grant: GRANT TRUNCATE ON truncate_tab TO regress_truncate_conflict;
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s2_truncate: TRUNCATE truncate_tab;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+--------
+t
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_grant s2_auth s1_tab_lookup s2_truncate s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_grant: GRANT TRUNCATE ON truncate_tab TO regress_truncate_conflict;
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+--------
+t
+(1 row)
+
+step s2_truncate: TRUNCATE truncate_tab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_truncate: <... completed>
+step s2_reset: RESET ROLE;
+
+starting permutation: s2_grant s2_auth s2_truncate s1_begin s1_tab_lookup s1_commit s2_reset
+step s2_grant: GRANT TRUNCATE ON truncate_tab TO regress_truncate_conflict;
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s2_truncate: TRUNCATE truncate_tab;
+step s1_begin: BEGIN;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+--------
+t
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
diff --git a/src/test/isolation/expected/tuplelock-conflict.out b/src/test/isolation/expected/tuplelock-conflict.out
new file mode 100644
index 0000000..d629314
--- /dev/null
+++ b/src/test/isolation/expected/tuplelock-conflict.out
@@ -0,0 +1,629 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock1 s2_tuplock1 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock1 s2_tuplock2 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a
+-
+1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock1 s2_tuplock3 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE;
+a
+-
+1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock1 s2_tuplock4 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock4: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock2 s2_tuplock1 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock2 s2_tuplock2 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a
+-
+1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock2 s2_tuplock3 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock3: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock2 s2_tuplock4 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock4: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock3 s2_tuplock1 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock3 s2_tuplock2 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock2: SELECT * FROM multixact_conflict FOR SHARE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock2: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock3 s2_tuplock3 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock3: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock3 s2_tuplock4 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock4: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock4 s2_tuplock1 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock1: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock4 s2_tuplock2 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock2: SELECT * FROM multixact_conflict FOR SHARE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock2: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock4 s2_tuplock3 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock3: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock4 s2_tuplock4 s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo;
+a
+-
+1
+(1 row)
+
+step s1_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock4: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_tuplock1 s2_tuplock1 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_tuplock1 s2_tuplock2 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a
+-
+1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_tuplock1 s2_tuplock3 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE;
+a
+-
+1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_tuplock1 s2_tuplock4 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock4: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_tuplock2 s2_tuplock1 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_tuplock2 s2_tuplock2 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a
+-
+1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_tuplock2 s2_tuplock3 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock3: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_tuplock2 s2_tuplock4 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock4: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_tuplock3 s2_tuplock1 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a
+-
+1
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_tuplock3 s2_tuplock2 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock2: SELECT * FROM multixact_conflict FOR SHARE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock2: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_tuplock3 s2_tuplock3 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock3: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_tuplock3 s2_tuplock4 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock4: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_tuplock4 s2_tuplock1 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock1: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_tuplock4 s2_tuplock2 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock2: SELECT * FROM multixact_conflict FOR SHARE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock2: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_tuplock4 s2_tuplock3 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock3: <... completed>
+a
+-
+1
+(1 row)
+
+
+starting permutation: s1_begin s1_tuplock4 s2_tuplock4 s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE;
+a
+-
+1
+(1 row)
+
+step s2_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE; <waiting ...>
+step s1_commit: COMMIT;
+step s2_tuplock4: <... completed>
+a
+-
+1
+(1 row)
+
diff --git a/src/test/isolation/expected/tuplelock-partition.out b/src/test/isolation/expected/tuplelock-partition.out
new file mode 100644
index 0000000..369ddf9
--- /dev/null
+++ b/src/test/isolation/expected/tuplelock-partition.out
@@ -0,0 +1,24 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s1update_nokey s2locktuple s1c
+step s1b: BEGIN;
+step s1update_nokey: INSERT INTO parttab (key, col1, col2) VALUES (1, 'a', 'b') ON CONFLICT (key) DO UPDATE SET col1 = 'x', col2 = 'y';
+step s2locktuple: SELECT * FROM parttab FOR KEY SHARE;
+col1|key|col2
+----+---+----
+a | 1|b
+(1 row)
+
+step s1c: COMMIT;
+
+starting permutation: s1b s1update_key s2locktuple s1c
+step s1b: BEGIN;
+step s1update_key: INSERT INTO parttab (key, col1, col2) VALUES (1, 'a', 'b') ON CONFLICT (key) DO UPDATE SET key=1;
+step s2locktuple: SELECT * FROM parttab FOR KEY SHARE; <waiting ...>
+step s1c: COMMIT;
+step s2locktuple: <... completed>
+col1|key|col2
+----+---+----
+a | 1|b
+(1 row)
+
diff --git a/src/test/isolation/expected/tuplelock-update.out b/src/test/isolation/expected/tuplelock-update.out
new file mode 100644
index 0000000..1d37d45
--- /dev/null
+++ b/src/test/isolation/expected/tuplelock-update.out
@@ -0,0 +1,49 @@
+Parsed test spec with 4 sessions
+
+starting permutation: s1_advlock s2_update s3_update s4_update s1_chain s1_begin s1_grablock s1_advunlock1 s1_advunlock2 s1_advunlock3 s1_commit
+step s1_advlock:
+ SELECT pg_advisory_lock(142857),
+ pg_advisory_lock(285714),
+ pg_advisory_lock(571428);
+
+pg_advisory_lock|pg_advisory_lock|pg_advisory_lock
+----------------+----------------+----------------
+ | |
+(1 row)
+
+step s2_update: UPDATE pktab SET data = DEFAULT WHERE pg_advisory_lock_shared(142857) IS NOT NULL; <waiting ...>
+step s3_update: UPDATE pktab SET data = DEFAULT WHERE pg_advisory_lock_shared(285714) IS NOT NULL; <waiting ...>
+step s4_update: UPDATE pktab SET data = DEFAULT WHERE pg_advisory_lock_shared(571428) IS NOT NULL; <waiting ...>
+step s1_chain: UPDATE pktab SET data = DEFAULT;
+step s1_begin: BEGIN;
+step s1_grablock: SELECT * FROM pktab FOR KEY SHARE;
+id|data
+--+----
+ 1| 2
+(1 row)
+
+step s1_advunlock1: SELECT pg_advisory_unlock(142857); <waiting ...>
+step s2_update: <... completed>
+step s1_advunlock1: <... completed>
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1_advunlock2: SELECT pg_advisory_unlock(285714); <waiting ...>
+step s3_update: <... completed>
+step s1_advunlock2: <... completed>
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1_advunlock3: SELECT pg_advisory_unlock(571428); <waiting ...>
+step s4_update: <... completed>
+step s1_advunlock3: <... completed>
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1_commit: COMMIT;
diff --git a/src/test/isolation/expected/tuplelock-upgrade-no-deadlock.out b/src/test/isolation/expected/tuplelock-upgrade-no-deadlock.out
new file mode 100644
index 0000000..2159092
--- /dev/null
+++ b/src/test/isolation/expected/tuplelock-upgrade-no-deadlock.out
@@ -0,0 +1,253 @@
+Parsed test spec with 4 sessions
+
+starting permutation: s1_share s2_for_update s3_share s3_for_update s1_rollback s3_rollback s2_rollback
+step s1_share: select id from tlu_job where id = 1 for share;
+id
+--
+ 1
+(1 row)
+
+step s2_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
+step s3_share: select id from tlu_job where id = 1 for share;
+id
+--
+ 1
+(1 row)
+
+step s3_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
+step s1_rollback: rollback;
+step s3_for_update: <... completed>
+id
+--
+ 1
+(1 row)
+
+step s3_rollback: rollback;
+step s2_for_update: <... completed>
+id
+--
+ 1
+(1 row)
+
+step s2_rollback: rollback;
+
+starting permutation: s1_keyshare s2_for_update s3_keyshare s1_update s3_update s1_rollback s3_rollback s2_rollback
+step s1_keyshare: select id from tlu_job where id = 1 for key share;
+id
+--
+ 1
+(1 row)
+
+step s2_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
+step s3_keyshare: select id from tlu_job where id = 1 for key share;
+id
+--
+ 1
+(1 row)
+
+step s1_update: update tlu_job set name = 'b' where id = 1;
+step s3_update: update tlu_job set name = 'c' where id = 1; <waiting ...>
+step s1_rollback: rollback;
+step s3_update: <... completed>
+step s3_rollback: rollback;
+step s2_for_update: <... completed>
+id
+--
+ 1
+(1 row)
+
+step s2_rollback: rollback;
+
+starting permutation: s1_keyshare s2_for_update s3_keyshare s1_update s3_update s1_commit s3_rollback s2_rollback
+step s1_keyshare: select id from tlu_job where id = 1 for key share;
+id
+--
+ 1
+(1 row)
+
+step s2_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
+step s3_keyshare: select id from tlu_job where id = 1 for key share;
+id
+--
+ 1
+(1 row)
+
+step s1_update: update tlu_job set name = 'b' where id = 1;
+step s3_update: update tlu_job set name = 'c' where id = 1; <waiting ...>
+step s1_commit: commit;
+step s3_update: <... completed>
+step s3_rollback: rollback;
+step s2_for_update: <... completed>
+id
+--
+ 1
+(1 row)
+
+step s2_rollback: rollback;
+
+starting permutation: s1_keyshare s2_for_update s3_keyshare s3_delete s1_rollback s3_rollback s2_rollback
+step s1_keyshare: select id from tlu_job where id = 1 for key share;
+id
+--
+ 1
+(1 row)
+
+step s2_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
+step s3_keyshare: select id from tlu_job where id = 1 for key share;
+id
+--
+ 1
+(1 row)
+
+step s3_delete: delete from tlu_job where id = 1; <waiting ...>
+step s1_rollback: rollback;
+step s3_delete: <... completed>
+step s3_rollback: rollback;
+step s2_for_update: <... completed>
+id
+--
+ 1
+(1 row)
+
+step s2_rollback: rollback;
+
+starting permutation: s1_keyshare s2_for_update s3_keyshare s3_delete s1_rollback s3_commit s2_rollback
+step s1_keyshare: select id from tlu_job where id = 1 for key share;
+id
+--
+ 1
+(1 row)
+
+step s2_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
+step s3_keyshare: select id from tlu_job where id = 1 for key share;
+id
+--
+ 1
+(1 row)
+
+step s3_delete: delete from tlu_job where id = 1; <waiting ...>
+step s1_rollback: rollback;
+step s3_delete: <... completed>
+step s3_commit: commit;
+step s2_for_update: <... completed>
+id
+--
+(0 rows)
+
+step s2_rollback: rollback;
+
+starting permutation: s1_share s2_for_update s3_for_update s1_rollback s2_rollback s3_rollback
+step s1_share: select id from tlu_job where id = 1 for share;
+id
+--
+ 1
+(1 row)
+
+step s2_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
+step s3_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
+step s1_rollback: rollback;
+step s2_for_update: <... completed>
+id
+--
+ 1
+(1 row)
+
+step s2_rollback: rollback;
+step s3_for_update: <... completed>
+id
+--
+ 1
+(1 row)
+
+step s3_rollback: rollback;
+
+starting permutation: s1_share s2_update s3_update s1_rollback s2_rollback s3_rollback
+step s1_share: select id from tlu_job where id = 1 for share;
+id
+--
+ 1
+(1 row)
+
+step s2_update: update tlu_job set name = 'b' where id = 1; <waiting ...>
+step s3_update: update tlu_job set name = 'c' where id = 1; <waiting ...>
+step s1_rollback: rollback;
+step s2_update: <... completed>
+step s2_rollback: rollback;
+step s3_update: <... completed>
+step s3_rollback: rollback;
+
+starting permutation: s1_share s2_delete s3_delete s1_rollback s2_rollback s3_rollback
+step s1_share: select id from tlu_job where id = 1 for share;
+id
+--
+ 1
+(1 row)
+
+step s2_delete: delete from tlu_job where id = 1; <waiting ...>
+step s3_delete: delete from tlu_job where id = 1; <waiting ...>
+step s1_rollback: rollback;
+step s2_delete: <... completed>
+step s2_rollback: rollback;
+step s3_delete: <... completed>
+step s3_rollback: rollback;
+
+starting permutation: s1_keyshare s3_for_update s2_for_keyshare s1_savept_e s1_share s1_savept_f s1_fornokeyupd s2_fornokeyupd s0_begin s0_keyshare s1_rollback_f s0_keyshare s1_rollback_e s1_rollback s2_rollback s0_rollback s3_rollback
+step s1_keyshare: select id from tlu_job where id = 1 for key share;
+id
+--
+ 1
+(1 row)
+
+step s3_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
+step s2_for_keyshare: select id from tlu_job where id = 1 for key share;
+id
+--
+ 1
+(1 row)
+
+step s1_savept_e: savepoint s1_e;
+step s1_share: select id from tlu_job where id = 1 for share;
+id
+--
+ 1
+(1 row)
+
+step s1_savept_f: savepoint s1_f;
+step s1_fornokeyupd: select id from tlu_job where id = 1 for no key update;
+id
+--
+ 1
+(1 row)
+
+step s2_fornokeyupd: select id from tlu_job where id = 1 for no key update; <waiting ...>
+step s0_begin: begin;
+step s0_keyshare: select id from tlu_job where id = 1 for key share;
+id
+--
+ 1
+(1 row)
+
+step s1_rollback_f: rollback to s1_f;
+step s0_keyshare: select id from tlu_job where id = 1 for key share;
+id
+--
+ 1
+(1 row)
+
+step s1_rollback_e: rollback to s1_e;
+step s2_fornokeyupd: <... completed>
+id
+--
+ 1
+(1 row)
+
+step s1_rollback: rollback;
+step s2_rollback: rollback;
+step s0_rollback: rollback;
+step s3_for_update: <... completed>
+id
+--
+ 1
+(1 row)
+
+step s3_rollback: rollback;
diff --git a/src/test/isolation/expected/two-ids.out b/src/test/isolation/expected/two-ids.out
new file mode 100644
index 0000000..2ebd73f
--- /dev/null
+++ b/src/test/isolation/expected/two-ids.out
@@ -0,0 +1,1187 @@
+Parsed test spec with 3 sessions
+
+starting permutation: wx1 c1 rxwy2 c2 ry3 c3
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 3
+(1 row)
+
+step c3: COMMIT;
+
+starting permutation: wx1 c1 rxwy2 ry3 c2 c3
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx1 c1 rxwy2 ry3 c3 c2
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx1 c1 ry3 rxwy2 c2 c3
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx1 c1 ry3 rxwy2 c3 c2
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx1 c1 ry3 c3 rxwy2 c2
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+
+starting permutation: wx1 rxwy2 c1 c2 ry3 c3
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step c3: COMMIT;
+
+starting permutation: wx1 rxwy2 c1 ry3 c2 c3
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: wx1 rxwy2 c1 ry3 c3 c2
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 rxwy2 c2 c1 ry3 c3
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step c3: COMMIT;
+
+starting permutation: wx1 rxwy2 c2 ry3 c1 c3
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx1 rxwy2 c2 ry3 c3 c1
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx1 rxwy2 ry3 c1 c2 c3
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: wx1 rxwy2 ry3 c1 c3 c2
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 rxwy2 ry3 c2 c1 c3
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx1 rxwy2 ry3 c2 c3 c1
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx1 rxwy2 ry3 c3 c1 c2
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx1 rxwy2 ry3 c3 c2 c1
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx1 ry3 c1 rxwy2 c2 c3
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx1 ry3 c1 rxwy2 c3 c2
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx1 ry3 c1 c3 rxwy2 c2
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c1: COMMIT;
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+
+starting permutation: wx1 ry3 rxwy2 c1 c2 c3
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: wx1 ry3 rxwy2 c1 c3 c2
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 ry3 rxwy2 c2 c1 c3
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx1 ry3 rxwy2 c2 c3 c1
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx1 ry3 rxwy2 c3 c1 c2
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx1 ry3 rxwy2 c3 c2 c1
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx1 ry3 c3 c1 rxwy2 c2
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+
+starting permutation: wx1 ry3 c3 rxwy2 c1 c2
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx1 ry3 c3 rxwy2 c2 c1
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 wx1 c1 c2 ry3 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step c3: COMMIT;
+
+starting permutation: rxwy2 wx1 c1 ry3 c2 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: rxwy2 wx1 c1 ry3 c3 c2
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxwy2 wx1 c2 c1 ry3 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step c3: COMMIT;
+
+starting permutation: rxwy2 wx1 c2 ry3 c1 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy2 wx1 c2 ry3 c3 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 wx1 ry3 c1 c2 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: rxwy2 wx1 ry3 c1 c3 c2
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxwy2 wx1 ry3 c2 c1 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy2 wx1 ry3 c2 c3 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 wx1 ry3 c3 c1 c2
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy2 wx1 ry3 c3 c2 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 c2 wx1 c1 ry3 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step c3: COMMIT;
+
+starting permutation: rxwy2 c2 wx1 ry3 c1 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy2 c2 wx1 ry3 c3 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 c2 ry3 wx1 c1 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy2 c2 ry3 wx1 c3 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 c2 ry3 c3 wx1 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+--
+ 2
+(1 row)
+
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+
+starting permutation: rxwy2 ry3 wx1 c1 c2 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: rxwy2 ry3 wx1 c1 c3 c2
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxwy2 ry3 wx1 c2 c1 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy2 ry3 wx1 c2 c3 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 ry3 wx1 c3 c1 c2
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy2 ry3 wx1 c3 c2 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 ry3 c2 wx1 c1 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy2 ry3 c2 wx1 c3 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 ry3 c2 c3 wx1 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c2: COMMIT;
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+
+starting permutation: rxwy2 ry3 c3 wx1 c1 c2
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy2 ry3 c3 wx1 c2 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 ry3 c3 c2 wx1 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+
+starting permutation: ry3 wx1 c1 rxwy2 c2 c3
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: ry3 wx1 c1 rxwy2 c3 c2
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 wx1 c1 c3 rxwy2 c2
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+
+starting permutation: ry3 wx1 rxwy2 c1 c2 c3
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: ry3 wx1 rxwy2 c1 c3 c2
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry3 wx1 rxwy2 c2 c1 c3
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: ry3 wx1 rxwy2 c2 c3 c1
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 wx1 rxwy2 c3 c1 c2
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 wx1 rxwy2 c3 c2 c1
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 wx1 c3 c1 rxwy2 c2
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+
+starting permutation: ry3 wx1 c3 rxwy2 c1 c2
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 wx1 c3 rxwy2 c2 c1
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 rxwy2 wx1 c1 c2 c3
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: ry3 rxwy2 wx1 c1 c3 c2
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry3 rxwy2 wx1 c2 c1 c3
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: ry3 rxwy2 wx1 c2 c3 c1
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 rxwy2 wx1 c3 c1 c2
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 rxwy2 wx1 c3 c2 c1
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 rxwy2 c2 wx1 c1 c3
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: ry3 rxwy2 c2 wx1 c3 c1
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 rxwy2 c2 c3 wx1 c1
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+
+starting permutation: ry3 rxwy2 c3 wx1 c1 c2
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 rxwy2 c3 wx1 c2 c1
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 rxwy2 c3 c2 wx1 c1
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+
+starting permutation: ry3 c3 wx1 c1 rxwy2 c2
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+
+starting permutation: ry3 c3 wx1 rxwy2 c1 c2
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 c3 wx1 rxwy2 c2 c1
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 c3 rxwy2 wx1 c1 c2
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 c3 rxwy2 wx1 c2 c1
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 c3 rxwy2 c2 wx1 c1
+step ry3: select id from D2;
+id
+--
+ 1
+(1 row)
+
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/update-conflict-out.out b/src/test/isolation/expected/update-conflict-out.out
new file mode 100644
index 0000000..1e82bd4
--- /dev/null
+++ b/src/test/isolation/expected/update-conflict-out.out
@@ -0,0 +1,31 @@
+Parsed test spec with 3 sessions
+
+starting permutation: foo_select bar_insert foo_insert foo_commit trouble_update bar_select bar_commit trouble_abort
+step foo_select: SELECT * FROM txn0 WHERE id = 42;
+id|val
+--+---
+(0 rows)
+
+step bar_insert: INSERT INTO txn0 SELECT 42, 'bar_insert';
+step foo_insert: INSERT INTO txn1 SELECT 7, 'foo_insert';
+step foo_commit: COMMIT;
+step trouble_update: UPDATE txn1 SET val = 'add physical version for "bar_select"' WHERE id = 7;
+step bar_select: SELECT * FROM txn1 WHERE id = 7;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step bar_commit: COMMIT;
+step trouble_abort: ABORT;
+
+starting permutation: foo_select bar_insert foo_insert foo_commit trouble_delete bar_select bar_commit trouble_abort
+step foo_select: SELECT * FROM txn0 WHERE id = 42;
+id|val
+--+---
+(0 rows)
+
+step bar_insert: INSERT INTO txn0 SELECT 42, 'bar_insert';
+step foo_insert: INSERT INTO txn1 SELECT 7, 'foo_insert';
+step foo_commit: COMMIT;
+step trouble_delete: DELETE FROM txn1 WHERE id = 7;
+step bar_select: SELECT * FROM txn1 WHERE id = 7;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step bar_commit: COMMIT;
+step trouble_abort: ABORT;
diff --git a/src/test/isolation/expected/update-locked-tuple.out b/src/test/isolation/expected/update-locked-tuple.out
new file mode 100644
index 0000000..1982c75
--- /dev/null
+++ b/src/test/isolation/expected/update-locked-tuple.out
@@ -0,0 +1,55 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s2b s2u s2c s1u1 s1u2 s1c
+step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s2b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s2u: UPDATE users SET sometime = '1830-10-04' WHERE id = 1;
+step s2c: COMMIT;
+step s1u1: UPDATE orders SET name = 'order of olivier (2)', user_id = 1 WHERE id = 1;
+step s1u2: UPDATE orders SET name = 'order of olivier (3)', user_id = 1 WHERE id = 1;
+step s1c: COMMIT;
+
+starting permutation: s1b s2b s2u s1u1 s2c s1u2 s1c
+step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s2b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s2u: UPDATE users SET sometime = '1830-10-04' WHERE id = 1;
+step s1u1: UPDATE orders SET name = 'order of olivier (2)', user_id = 1 WHERE id = 1;
+step s2c: COMMIT;
+step s1u2: UPDATE orders SET name = 'order of olivier (3)', user_id = 1 WHERE id = 1;
+step s1c: COMMIT;
+
+starting permutation: s1b s2b s1u1 s2u s2c s1u2 s1c
+step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s2b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1u1: UPDATE orders SET name = 'order of olivier (2)', user_id = 1 WHERE id = 1;
+step s2u: UPDATE users SET sometime = '1830-10-04' WHERE id = 1;
+step s2c: COMMIT;
+step s1u2: UPDATE orders SET name = 'order of olivier (3)', user_id = 1 WHERE id = 1;
+step s1c: COMMIT;
+
+starting permutation: s1b s1u1 s2b s2u s2c s1u2 s1c
+step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1u1: UPDATE orders SET name = 'order of olivier (2)', user_id = 1 WHERE id = 1;
+step s2b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s2u: UPDATE users SET sometime = '1830-10-04' WHERE id = 1;
+step s2c: COMMIT;
+step s1u2: UPDATE orders SET name = 'order of olivier (3)', user_id = 1 WHERE id = 1;
+step s1c: COMMIT;
+
+starting permutation: s1b s1u1 s2b s1u2 s2u s2c s1c
+step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1u1: UPDATE orders SET name = 'order of olivier (2)', user_id = 1 WHERE id = 1;
+step s2b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1u2: UPDATE orders SET name = 'order of olivier (3)', user_id = 1 WHERE id = 1;
+step s2u: UPDATE users SET sometime = '1830-10-04' WHERE id = 1;
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s1b s1u1 s1u2 s2b s2u s2c s1c
+step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1u1: UPDATE orders SET name = 'order of olivier (2)', user_id = 1 WHERE id = 1;
+step s1u2: UPDATE orders SET name = 'order of olivier (3)', user_id = 1 WHERE id = 1;
+step s2b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s2u: UPDATE users SET sometime = '1830-10-04' WHERE id = 1;
+step s2c: COMMIT;
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/vacuum-concurrent-drop.out b/src/test/isolation/expected/vacuum-concurrent-drop.out
new file mode 100644
index 0000000..cf348d7
--- /dev/null
+++ b/src/test/isolation/expected/vacuum-concurrent-drop.out
@@ -0,0 +1,76 @@
+Parsed test spec with 2 sessions
+
+starting permutation: lock vac_specified drop_and_commit
+step lock:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+step vac_specified: VACUUM part1, part2; <waiting ...>
+step drop_and_commit:
+ DROP TABLE part2;
+ COMMIT;
+
+s2: WARNING: skipping vacuum of "part2" --- relation no longer exists
+step vac_specified: <... completed>
+
+starting permutation: lock vac_all_parts drop_and_commit
+step lock:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+step vac_all_parts: VACUUM parted; <waiting ...>
+step drop_and_commit:
+ DROP TABLE part2;
+ COMMIT;
+
+step vac_all_parts: <... completed>
+
+starting permutation: lock analyze_specified drop_and_commit
+step lock:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+step analyze_specified: ANALYZE part1, part2; <waiting ...>
+step drop_and_commit:
+ DROP TABLE part2;
+ COMMIT;
+
+s2: WARNING: skipping analyze of "part2" --- relation no longer exists
+step analyze_specified: <... completed>
+
+starting permutation: lock analyze_all_parts drop_and_commit
+step lock:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+step analyze_all_parts: ANALYZE parted; <waiting ...>
+step drop_and_commit:
+ DROP TABLE part2;
+ COMMIT;
+
+step analyze_all_parts: <... completed>
+
+starting permutation: lock vac_analyze_specified drop_and_commit
+step lock:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+step vac_analyze_specified: VACUUM ANALYZE part1, part2; <waiting ...>
+step drop_and_commit:
+ DROP TABLE part2;
+ COMMIT;
+
+s2: WARNING: skipping vacuum of "part2" --- relation no longer exists
+step vac_analyze_specified: <... completed>
+
+starting permutation: lock vac_analyze_all_parts drop_and_commit
+step lock:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+step vac_analyze_all_parts: VACUUM ANALYZE parted; <waiting ...>
+step drop_and_commit:
+ DROP TABLE part2;
+ COMMIT;
+
+step vac_analyze_all_parts: <... completed>
diff --git a/src/test/isolation/expected/vacuum-conflict.out b/src/test/isolation/expected/vacuum-conflict.out
new file mode 100644
index 0000000..ffde537
--- /dev/null
+++ b/src/test/isolation/expected/vacuum-conflict.out
@@ -0,0 +1,149 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_lock s2_auth s2_vacuum s1_commit s2_reset
+step s1_begin: BEGIN;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+s2: WARNING: skipping "vacuum_tab" --- only table or database owner can vacuum it
+step s2_vacuum: VACUUM vacuum_tab;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_auth s2_vacuum s1_lock s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+s2: WARNING: skipping "vacuum_tab" --- only table or database owner can vacuum it
+step s2_vacuum: VACUUM vacuum_tab;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_auth s1_lock s2_vacuum s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+s2: WARNING: skipping "vacuum_tab" --- only table or database owner can vacuum it
+step s2_vacuum: VACUUM vacuum_tab;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s2_auth s2_vacuum s1_begin s1_lock s1_commit s2_reset
+step s2_auth: SET ROLE regress_vacuum_conflict;
+s2: WARNING: skipping "vacuum_tab" --- only table or database owner can vacuum it
+step s2_vacuum: VACUUM vacuum_tab;
+step s1_begin: BEGIN;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s1_lock s2_auth s2_analyze s1_commit s2_reset
+step s1_begin: BEGIN;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+s2: WARNING: skipping "vacuum_tab" --- only table or database owner can analyze it
+step s2_analyze: ANALYZE vacuum_tab;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_auth s2_analyze s1_lock s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+s2: WARNING: skipping "vacuum_tab" --- only table or database owner can analyze it
+step s2_analyze: ANALYZE vacuum_tab;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_auth s1_lock s2_analyze s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+s2: WARNING: skipping "vacuum_tab" --- only table or database owner can analyze it
+step s2_analyze: ANALYZE vacuum_tab;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s2_auth s2_analyze s1_begin s1_lock s1_commit s2_reset
+step s2_auth: SET ROLE regress_vacuum_conflict;
+s2: WARNING: skipping "vacuum_tab" --- only table or database owner can analyze it
+step s2_analyze: ANALYZE vacuum_tab;
+step s1_begin: BEGIN;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_grant s1_lock s2_auth s2_vacuum s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_grant: ALTER TABLE vacuum_tab OWNER TO regress_vacuum_conflict;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+step s2_vacuum: VACUUM vacuum_tab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_vacuum: <... completed>
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_grant s2_auth s2_vacuum s1_lock s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_grant: ALTER TABLE vacuum_tab OWNER TO regress_vacuum_conflict;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+step s2_vacuum: VACUUM vacuum_tab;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_grant s2_auth s1_lock s2_vacuum s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_grant: ALTER TABLE vacuum_tab OWNER TO regress_vacuum_conflict;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s2_vacuum: VACUUM vacuum_tab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_vacuum: <... completed>
+step s2_reset: RESET ROLE;
+
+starting permutation: s2_grant s2_auth s2_vacuum s1_begin s1_lock s1_commit s2_reset
+step s2_grant: ALTER TABLE vacuum_tab OWNER TO regress_vacuum_conflict;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+step s2_vacuum: VACUUM vacuum_tab;
+step s1_begin: BEGIN;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_grant s1_lock s2_auth s2_analyze s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_grant: ALTER TABLE vacuum_tab OWNER TO regress_vacuum_conflict;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+step s2_analyze: ANALYZE vacuum_tab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_analyze: <... completed>
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_grant s2_auth s2_analyze s1_lock s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_grant: ALTER TABLE vacuum_tab OWNER TO regress_vacuum_conflict;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+step s2_analyze: ANALYZE vacuum_tab;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_grant s2_auth s1_lock s2_analyze s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_grant: ALTER TABLE vacuum_tab OWNER TO regress_vacuum_conflict;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s2_analyze: ANALYZE vacuum_tab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_analyze: <... completed>
+step s2_reset: RESET ROLE;
+
+starting permutation: s2_grant s2_auth s2_analyze s1_begin s1_lock s1_commit s2_reset
+step s2_grant: ALTER TABLE vacuum_tab OWNER TO regress_vacuum_conflict;
+step s2_auth: SET ROLE regress_vacuum_conflict;
+step s2_analyze: ANALYZE vacuum_tab;
+step s1_begin: BEGIN;
+step s1_lock: LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
diff --git a/src/test/isolation/expected/vacuum-reltuples.out b/src/test/isolation/expected/vacuum-reltuples.out
new file mode 100644
index 0000000..cdbe7f3
--- /dev/null
+++ b/src/test/isolation/expected/vacuum-reltuples.out
@@ -0,0 +1,67 @@
+Parsed test spec with 2 sessions
+
+starting permutation: modify vac stats
+step modify:
+ insert into smalltbl select max(id)+1 from smalltbl;
+
+step vac:
+ vacuum smalltbl;
+
+step stats:
+ select relpages, reltuples from pg_class
+ where oid='smalltbl'::regclass;
+
+relpages|reltuples
+--------+---------
+ 1| 21
+(1 row)
+
+
+starting permutation: modify open fetch1 vac close stats
+step modify:
+ insert into smalltbl select max(id)+1 from smalltbl;
+
+step open:
+ begin;
+ declare c1 cursor for select 1 as dummy from smalltbl;
+
+step fetch1:
+ fetch next from c1;
+
+dummy
+-----
+ 1
+(1 row)
+
+step vac:
+ vacuum smalltbl;
+
+step close:
+ commit;
+
+step stats:
+ select relpages, reltuples from pg_class
+ where oid='smalltbl'::regclass;
+
+relpages|reltuples
+--------+---------
+ 1| 20
+(1 row)
+
+
+starting permutation: modify vac stats
+step modify:
+ insert into smalltbl select max(id)+1 from smalltbl;
+
+step vac:
+ vacuum smalltbl;
+
+step stats:
+ select relpages, reltuples from pg_class
+ where oid='smalltbl'::regclass;
+
+relpages|reltuples
+--------+---------
+ 1| 21
+(1 row)
+
diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out
new file mode 100644
index 0000000..99db281
--- /dev/null
+++ b/src/test/isolation/expected/vacuum-skip-locked.out
@@ -0,0 +1,171 @@
+Parsed test spec with 2 sessions
+
+starting permutation: lock_share vac_specified commit
+step lock_share:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+s2: WARNING: skipping vacuum of "part1" --- lock not available
+step vac_specified: VACUUM (SKIP_LOCKED) part1, part2;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_share vac_all_parts commit
+step lock_share:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+step vac_all_parts: VACUUM (SKIP_LOCKED) parted;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_share analyze_specified commit
+step lock_share:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+s2: WARNING: skipping analyze of "part1" --- lock not available
+step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_share analyze_all_parts commit
+step lock_share:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_share vac_analyze_specified commit
+step lock_share:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+s2: WARNING: skipping vacuum of "part1" --- lock not available
+step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_share vac_analyze_all_parts commit
+step lock_share:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_share vac_full_specified commit
+step lock_share:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+s2: WARNING: skipping vacuum of "part1" --- lock not available
+step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_share vac_full_all_parts commit
+step lock_share:
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+
+step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_specified commit
+step lock_access_exclusive:
+ BEGIN;
+ LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+s2: WARNING: skipping vacuum of "part1" --- lock not available
+step vac_specified: VACUUM (SKIP_LOCKED) part1, part2;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_all_parts commit
+step lock_access_exclusive:
+ BEGIN;
+ LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_all_parts: VACUUM (SKIP_LOCKED) parted;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_access_exclusive analyze_specified commit
+step lock_access_exclusive:
+ BEGIN;
+ LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+s2: WARNING: skipping analyze of "part1" --- lock not available
+step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_access_exclusive analyze_all_parts commit
+step lock_access_exclusive:
+ BEGIN;
+ LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; <waiting ...>
+step commit:
+ COMMIT;
+
+step analyze_all_parts: <... completed>
+
+starting permutation: lock_access_exclusive vac_analyze_specified commit
+step lock_access_exclusive:
+ BEGIN;
+ LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+s2: WARNING: skipping vacuum of "part1" --- lock not available
+step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_analyze_all_parts commit
+step lock_access_exclusive:
+ BEGIN;
+ LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; <waiting ...>
+step commit:
+ COMMIT;
+
+step vac_analyze_all_parts: <... completed>
+
+starting permutation: lock_access_exclusive vac_full_specified commit
+step lock_access_exclusive:
+ BEGIN;
+ LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+s2: WARNING: skipping vacuum of "part1" --- lock not available
+step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2;
+step commit:
+ COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_full_all_parts commit
+step lock_access_exclusive:
+ BEGIN;
+ LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted;
+step commit:
+ COMMIT;
+
diff --git a/src/test/isolation/isolation_main.c b/src/test/isolation/isolation_main.c
new file mode 100644
index 0000000..eff9c4f
--- /dev/null
+++ b/src/test/isolation/isolation_main.c
@@ -0,0 +1,152 @@
+/*-------------------------------------------------------------------------
+ *
+ * isolation_main --- pg_regress test launcher for isolation tests
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/test/isolation/isolation_main.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "pg_regress.h"
+
+char saved_argv0[MAXPGPATH];
+char isolation_exec[MAXPGPATH];
+bool looked_up_isolation_exec = false;
+
+#define PG_ISOLATION_VERSIONSTR "isolationtester (PostgreSQL) " PG_VERSION "\n"
+
+/*
+ * start an isolation tester process for specified file (including
+ * redirection), and return process ID
+ */
+static PID_TYPE
+isolation_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];
+ size_t offset = 0;
+ char *appnameenv;
+
+ /* need to do the path lookup here, check isolation_init() for details */
+ if (!looked_up_isolation_exec)
+ {
+ /* look for isolationtester binary */
+ if (find_other_exec(saved_argv0, "isolationtester",
+ PG_ISOLATION_VERSIONSTR, isolation_exec) != 0)
+ {
+ fprintf(stderr, _("could not find proper isolationtester binary\n"));
+ exit(2);
+ }
+ looked_up_isolation_exec = true;
+ }
+
+ /*
+ * Look for files in the output dir first, consistent with a vpath search.
+ * This is mainly to create more reasonable error messages if the file is
+ * not found. It also allows local test overrides when running pg_regress
+ * outside of the source tree.
+ */
+ snprintf(infile, sizeof(infile), "%s/specs/%s.spec",
+ outputdir, testname);
+ if (!file_exists(infile))
+ snprintf(infile, sizeof(infile), "%s/specs/%s.spec",
+ inputdir, testname);
+
+ snprintf(outfile, sizeof(outfile), "%s/results/%s.out",
+ outputdir, testname);
+
+ snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out",
+ outputdir, testname);
+ if (!file_exists(expectfile))
+ snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out",
+ inputdir, testname);
+
+ add_stringlist_item(resultfiles, outfile);
+ add_stringlist_item(expectfiles, expectfile);
+
+ if (launcher)
+ {
+ offset += snprintf(psql_cmd + offset, sizeof(psql_cmd) - offset,
+ "%s ", launcher);
+ if (offset >= sizeof(psql_cmd))
+ {
+ fprintf(stderr, _("command too long\n"));
+ exit(2);
+ }
+ }
+
+ offset += snprintf(psql_cmd + offset, sizeof(psql_cmd) - offset,
+ "\"%s\" \"dbname=%s\" < \"%s\" > \"%s\" 2>&1",
+ isolation_exec,
+ dblist->str,
+ infile,
+ outfile);
+ if (offset >= sizeof(psql_cmd))
+ {
+ fprintf(stderr, _("command too long\n"));
+ exit(2);
+ }
+
+ appnameenv = psprintf("isolation/%s", testname);
+ setenv("PGAPPNAME", appnameenv, 1);
+ free(appnameenv);
+
+ pid = spawn_process(psql_cmd);
+
+ if (pid == INVALID_PID)
+ {
+ fprintf(stderr, _("could not start process for test %s\n"),
+ testname);
+ exit(2);
+ }
+
+ unsetenv("PGAPPNAME");
+
+ return pid;
+}
+
+static void
+isolation_init(int argc, char **argv)
+{
+ size_t argv0_len;
+
+ /*
+ * We unfortunately cannot do the find_other_exec() lookup to find the
+ * "isolationtester" binary here. regression_main() calls the
+ * initialization functions before parsing the commandline arguments and
+ * thus hasn't changed the library search path at this point which in turn
+ * can cause the "isolationtester -V" invocation that find_other_exec()
+ * does to fail since it's linked to libpq. So we instead copy argv[0]
+ * and do the lookup the first time through isolation_start_test().
+ */
+ argv0_len = strlcpy(saved_argv0, argv[0], MAXPGPATH);
+ if (argv0_len >= MAXPGPATH)
+ {
+ fprintf(stderr, _("path for isolationtester executable is longer than %d bytes\n"),
+ (int) (MAXPGPATH - 1));
+ exit(2);
+ }
+
+ /* set default regression database name */
+ add_stringlist_item(&dblist, "isolation_regression");
+}
+
+int
+main(int argc, char *argv[])
+{
+ return regression_main(argc, argv,
+ isolation_init,
+ isolation_start_test,
+ NULL /* no postfunc needed */ );
+}
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
new file mode 100644
index 0000000..8e87098
--- /dev/null
+++ b/src/test/isolation/isolation_schedule
@@ -0,0 +1,101 @@
+test: read-only-anomaly
+test: read-only-anomaly-2
+test: read-only-anomaly-3
+test: read-write-unique
+test: read-write-unique-2
+test: read-write-unique-3
+test: read-write-unique-4
+test: simple-write-skew
+test: receipt-report
+test: temporal-range-integrity
+test: project-manager
+test: classroom-scheduling
+test: total-cash
+test: referential-integrity
+test: ri-trigger
+test: partial-index
+test: two-ids
+test: multiple-row-versions
+test: index-only-scan
+test: predicate-lock-hot-tuple
+test: update-conflict-out
+test: deadlock-simple
+test: deadlock-hard
+test: deadlock-soft
+test: deadlock-soft-2
+test: deadlock-parallel
+test: detach-partition-concurrently-1
+test: detach-partition-concurrently-2
+test: detach-partition-concurrently-3
+test: detach-partition-concurrently-4
+test: fk-contention
+test: fk-deadlock
+test: fk-deadlock2
+test: fk-partitioned-1
+test: fk-partitioned-2
+test: eval-plan-qual
+test: eval-plan-qual-trigger
+test: lock-update-delete
+test: lock-update-traversal
+test: inherit-temp
+test: temp-schema-cleanup
+test: insert-conflict-do-nothing
+test: insert-conflict-do-nothing-2
+test: insert-conflict-do-update
+test: insert-conflict-do-update-2
+test: insert-conflict-do-update-3
+test: insert-conflict-specconflict
+test: delete-abort-savept
+test: delete-abort-savept-2
+test: aborted-keyrevoke
+test: multixact-no-deadlock
+test: multixact-no-forget
+test: lock-committed-update
+test: lock-committed-keyupdate
+test: update-locked-tuple
+test: reindex-concurrently
+test: reindex-concurrently-toast
+test: reindex-schema
+test: propagate-lock-delete
+test: tuplelock-conflict
+test: tuplelock-update
+test: tuplelock-upgrade-no-deadlock
+test: tuplelock-partition
+test: freeze-the-dead
+test: nowait
+test: nowait-2
+test: nowait-3
+test: nowait-4
+test: nowait-5
+test: skip-locked
+test: skip-locked-2
+test: skip-locked-3
+test: skip-locked-4
+test: drop-index-concurrently-1
+test: multiple-cic
+test: alter-table-1
+test: alter-table-2
+test: alter-table-3
+test: alter-table-4
+test: create-trigger
+test: sequence-ddl
+test: async-notify
+test: vacuum-reltuples
+test: timeouts
+test: vacuum-concurrent-drop
+test: vacuum-conflict
+test: vacuum-skip-locked
+test: horizons
+test: predicate-hash
+test: predicate-gist
+test: predicate-gin
+test: partition-concurrent-attach
+test: partition-drop-index-locking
+test: partition-key-update-1
+test: partition-key-update-2
+test: partition-key-update-3
+test: partition-key-update-4
+test: plpgsql-toast
+test: truncate-conflict
+test: serializable-parallel
+test: serializable-parallel-2
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
new file mode 100644
index 0000000..12179f2
--- /dev/null
+++ b/src/test/isolation/isolationtester.c
@@ -0,0 +1,1149 @@
+/*
+ * src/test/isolation/isolationtester.c
+ *
+ * isolationtester.c
+ * Runs an isolation test specified by a spec file.
+ */
+
+#include "postgres_fe.h"
+
+#include <sys/time.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#include "datatype/timestamp.h"
+#include "isolationtester.h"
+#include "libpq-fe.h"
+#include "pg_getopt.h"
+#include "pqexpbuffer.h"
+
+#define PREP_WAITING "isolationtester_waiting"
+
+/*
+ * conns[0] is the global setup, teardown, and watchdog connection. Additional
+ * connections represent spec-defined sessions.
+ */
+typedef struct IsoConnInfo
+{
+ /* The libpq connection object for this connection. */
+ PGconn *conn;
+ /* The backend PID, in numeric and string formats. */
+ int backend_pid;
+ const char *backend_pid_str;
+ /* Name of the associated session. */
+ const char *sessionname;
+ /* Active step on this connection, or NULL if idle. */
+ PermutationStep *active_step;
+ /* Number of NOTICE messages received from connection. */
+ int total_notices;
+} IsoConnInfo;
+
+static IsoConnInfo *conns = NULL;
+static int nconns = 0;
+
+/* Flag indicating some new NOTICE has arrived */
+static bool any_new_notice = false;
+
+/* Maximum time to wait before giving up on a step (in usec) */
+static int64 max_step_wait = 300 * USECS_PER_SEC;
+
+
+static void check_testspec(TestSpec *testspec);
+static void run_testspec(TestSpec *testspec);
+static void run_all_permutations(TestSpec *testspec);
+static void run_all_permutations_recurse(TestSpec *testspec, int *piles,
+ int nsteps, PermutationStep **steps);
+static void run_named_permutations(TestSpec *testspec);
+static void run_permutation(TestSpec *testspec, int nsteps,
+ PermutationStep **steps);
+
+/* Flag bits for try_complete_step(s) */
+#define STEP_NONBLOCK 0x1 /* return as soon as cmd waits for a lock */
+#define STEP_RETRY 0x2 /* this is a retry of a previously-waiting cmd */
+
+static int try_complete_steps(TestSpec *testspec, PermutationStep **waiting,
+ int nwaiting, int flags);
+static bool try_complete_step(TestSpec *testspec, PermutationStep *pstep,
+ int flags);
+
+static int step_qsort_cmp(const void *a, const void *b);
+static int step_bsearch_cmp(const void *a, const void *b);
+
+static bool step_has_blocker(PermutationStep *pstep);
+static void printResultSet(PGresult *res);
+static void isotesterNoticeProcessor(void *arg, const char *message);
+static void blackholeNoticeProcessor(void *arg, const char *message);
+
+static void
+disconnect_atexit(void)
+{
+ int i;
+
+ for (i = 0; i < nconns; i++)
+ if (conns[i].conn)
+ PQfinish(conns[i].conn);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *conninfo;
+ const char *env_wait;
+ TestSpec *testspec;
+ PGresult *res;
+ PQExpBufferData wait_query;
+ int opt;
+ int i;
+
+ while ((opt = getopt(argc, argv, "V")) != -1)
+ {
+ switch (opt)
+ {
+ case 'V':
+ puts("isolationtester (PostgreSQL) " PG_VERSION);
+ exit(0);
+ default:
+ fprintf(stderr, "Usage: isolationtester [CONNINFO]\n");
+ return EXIT_FAILURE;
+ }
+ }
+
+ /*
+ * Make stdout unbuffered to match stderr; and ensure stderr is unbuffered
+ * too, which it should already be everywhere except sometimes in Windows.
+ */
+ setbuf(stdout, NULL);
+ setbuf(stderr, NULL);
+
+ /*
+ * If the user supplies a non-option parameter on the command line, use it
+ * as the conninfo string; otherwise default to setting dbname=postgres
+ * and using environment variables or defaults for all other connection
+ * parameters.
+ */
+ if (argc > optind)
+ conninfo = argv[optind];
+ else
+ conninfo = "dbname = postgres";
+
+ /*
+ * If PGISOLATIONTIMEOUT is set in the environment, adopt its value (given
+ * in seconds) as the max time to wait for any one step to complete.
+ */
+ env_wait = getenv("PGISOLATIONTIMEOUT");
+ if (env_wait != NULL)
+ max_step_wait = ((int64) atoi(env_wait)) * USECS_PER_SEC;
+
+ /* Read the test spec from stdin */
+ spec_yyparse();
+ testspec = &parseresult;
+
+ /* Perform post-parse checking, and fill in linking fields */
+ check_testspec(testspec);
+
+ printf("Parsed test spec with %d sessions\n", testspec->nsessions);
+
+ /*
+ * Establish connections to the database, one for each session and an
+ * extra for lock wait detection and global work.
+ */
+ nconns = 1 + testspec->nsessions;
+ conns = (IsoConnInfo *) pg_malloc0(nconns * sizeof(IsoConnInfo));
+ atexit(disconnect_atexit);
+
+ for (i = 0; i < nconns; i++)
+ {
+ const char *sessionname;
+
+ if (i == 0)
+ sessionname = "control connection";
+ else
+ sessionname = testspec->sessions[i - 1]->name;
+
+ conns[i].sessionname = sessionname;
+
+ conns[i].conn = PQconnectdb(conninfo);
+ if (PQstatus(conns[i].conn) != CONNECTION_OK)
+ {
+ fprintf(stderr, "Connection %d failed: %s",
+ i, PQerrorMessage(conns[i].conn));
+ exit(1);
+ }
+
+ /*
+ * Set up notice processors for the user-defined connections, so that
+ * messages can get printed prefixed with the session names. The
+ * control connection gets a "blackhole" processor instead (hides all
+ * messages).
+ */
+ if (i != 0)
+ PQsetNoticeProcessor(conns[i].conn,
+ isotesterNoticeProcessor,
+ (void *) &conns[i]);
+ else
+ PQsetNoticeProcessor(conns[i].conn,
+ blackholeNoticeProcessor,
+ NULL);
+
+ /*
+ * Similarly, append the session name to application_name to make it
+ * easier to map spec file sessions to log output and
+ * pg_stat_activity. The reason to append instead of just setting the
+ * name is that we don't know the name of the test currently running.
+ */
+ res = PQexecParams(conns[i].conn,
+ "SELECT set_config('application_name',\n"
+ " current_setting('application_name') || '/' || $1,\n"
+ " false)",
+ 1, NULL,
+ &sessionname,
+ NULL, NULL, 0);
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "setting of application name failed: %s",
+ PQerrorMessage(conns[i].conn));
+ exit(1);
+ }
+
+ /* Save each connection's backend PID for subsequent use. */
+ conns[i].backend_pid = PQbackendPID(conns[i].conn);
+ conns[i].backend_pid_str = psprintf("%d", conns[i].backend_pid);
+ }
+
+ /*
+ * Build the query we'll use to detect lock contention among sessions in
+ * the test specification. Most of the time, we could get away with
+ * simply checking whether a session is waiting for *any* lock: we don't
+ * exactly expect concurrent use of test tables. However, autovacuum will
+ * occasionally take AccessExclusiveLock to truncate a table, and we must
+ * ignore that transient wait.
+ */
+ initPQExpBuffer(&wait_query);
+ appendPQExpBufferStr(&wait_query,
+ "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
+ /* The spec syntax requires at least one session; assume that here. */
+ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
+ for (i = 2; i < nconns; i++)
+ appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
+ appendPQExpBufferStr(&wait_query, "}')");
+
+ res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "prepare of lock wait query failed: %s",
+ PQerrorMessage(conns[0].conn));
+ exit(1);
+ }
+ PQclear(res);
+ termPQExpBuffer(&wait_query);
+
+ /*
+ * Run the permutations specified in the spec, or all if none were
+ * explicitly specified.
+ */
+ run_testspec(testspec);
+
+ return 0;
+}
+
+/*
+ * Validity-check the test spec and fill in cross-links between nodes.
+ */
+static void
+check_testspec(TestSpec *testspec)
+{
+ int nallsteps;
+ Step **allsteps;
+ int i,
+ j,
+ k;
+
+ /* Create a sorted lookup table of all steps. */
+ nallsteps = 0;
+ for (i = 0; i < testspec->nsessions; i++)
+ nallsteps += testspec->sessions[i]->nsteps;
+
+ allsteps = pg_malloc(nallsteps * sizeof(Step *));
+
+ k = 0;
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ for (j = 0; j < testspec->sessions[i]->nsteps; j++)
+ allsteps[k++] = testspec->sessions[i]->steps[j];
+ }
+
+ qsort(allsteps, nallsteps, sizeof(Step *), step_qsort_cmp);
+
+ /* Verify that all step names are unique. */
+ for (i = 1; i < nallsteps; i++)
+ {
+ if (strcmp(allsteps[i - 1]->name,
+ allsteps[i]->name) == 0)
+ {
+ fprintf(stderr, "duplicate step name: %s\n",
+ allsteps[i]->name);
+ exit(1);
+ }
+ }
+
+ /* Set the session index fields in steps. */
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ Session *session = testspec->sessions[i];
+
+ for (j = 0; j < session->nsteps; j++)
+ session->steps[j]->session = i;
+ }
+
+ /*
+ * If we have manually-specified permutations, link PermutationSteps to
+ * Steps, and fill in blocker links.
+ */
+ for (i = 0; i < testspec->npermutations; i++)
+ {
+ Permutation *p = testspec->permutations[i];
+
+ for (j = 0; j < p->nsteps; j++)
+ {
+ PermutationStep *pstep = p->steps[j];
+ Step **this = (Step **) bsearch(pstep->name,
+ allsteps,
+ nallsteps,
+ sizeof(Step *),
+ step_bsearch_cmp);
+
+ if (this == NULL)
+ {
+ fprintf(stderr, "undefined step \"%s\" specified in permutation\n",
+ pstep->name);
+ exit(1);
+ }
+ pstep->step = *this;
+
+ /* Mark the step used, for check below */
+ pstep->step->used = true;
+ }
+
+ /*
+ * Identify any blocker steps. We search only the current
+ * permutation, since steps not used there couldn't be concurrent.
+ * Note that it's OK to reference later permutation steps, so this
+ * can't be combined with the previous loop.
+ */
+ for (j = 0; j < p->nsteps; j++)
+ {
+ PermutationStep *pstep = p->steps[j];
+
+ for (k = 0; k < pstep->nblockers; k++)
+ {
+ PermutationStepBlocker *blocker = pstep->blockers[k];
+ int n;
+
+ if (blocker->blocktype == PSB_ONCE)
+ continue; /* nothing to link to */
+
+ blocker->step = NULL;
+ for (n = 0; n < p->nsteps; n++)
+ {
+ PermutationStep *otherp = p->steps[n];
+
+ if (strcmp(otherp->name, blocker->stepname) == 0)
+ {
+ blocker->step = otherp->step;
+ break;
+ }
+ }
+ if (blocker->step == NULL)
+ {
+ fprintf(stderr, "undefined blocking step \"%s\" referenced in permutation step \"%s\"\n",
+ blocker->stepname, pstep->name);
+ exit(1);
+ }
+ /* can't block on completion of step of own session */
+ if (blocker->step->session == pstep->step->session)
+ {
+ fprintf(stderr, "permutation step \"%s\" cannot block on its own session\n",
+ pstep->name);
+ exit(1);
+ }
+ }
+ }
+ }
+
+ /*
+ * If we have manually-specified permutations, verify that all steps have
+ * been used, warning about anything defined but not used. We can skip
+ * this when using automatically-generated permutations.
+ */
+ if (testspec->permutations)
+ {
+ for (i = 0; i < nallsteps; i++)
+ {
+ if (!allsteps[i]->used)
+ fprintf(stderr, "unused step name: %s\n", allsteps[i]->name);
+ }
+ }
+
+ free(allsteps);
+}
+
+/*
+ * Run the permutations specified in the spec, or all if none were
+ * explicitly specified.
+ */
+static void
+run_testspec(TestSpec *testspec)
+{
+ if (testspec->permutations)
+ run_named_permutations(testspec);
+ else
+ run_all_permutations(testspec);
+}
+
+/*
+ * Run all permutations of the steps and sessions.
+ */
+static void
+run_all_permutations(TestSpec *testspec)
+{
+ int nsteps;
+ int i;
+ PermutationStep *steps;
+ PermutationStep **stepptrs;
+ int *piles;
+
+ /* Count the total number of steps in all sessions */
+ nsteps = 0;
+ for (i = 0; i < testspec->nsessions; i++)
+ nsteps += testspec->sessions[i]->nsteps;
+
+ /* Create PermutationStep workspace array */
+ steps = (PermutationStep *) pg_malloc0(sizeof(PermutationStep) * nsteps);
+ stepptrs = (PermutationStep **) pg_malloc(sizeof(PermutationStep *) * nsteps);
+ for (i = 0; i < nsteps; i++)
+ stepptrs[i] = steps + i;
+
+ /*
+ * To generate the permutations, we conceptually put the steps of each
+ * session on a pile. To generate a permutation, we pick steps from the
+ * piles until all piles are empty. By picking steps from piles in
+ * different order, we get different permutations.
+ *
+ * A pile is actually just an integer which tells how many steps we've
+ * already picked from this pile.
+ */
+ piles = pg_malloc(sizeof(int) * testspec->nsessions);
+ for (i = 0; i < testspec->nsessions; i++)
+ piles[i] = 0;
+
+ run_all_permutations_recurse(testspec, piles, 0, stepptrs);
+
+ free(steps);
+ free(stepptrs);
+ free(piles);
+}
+
+static void
+run_all_permutations_recurse(TestSpec *testspec, int *piles,
+ int nsteps, PermutationStep **steps)
+{
+ int i;
+ bool found = false;
+
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ /* If there's any more steps in this pile, pick it and recurse */
+ if (piles[i] < testspec->sessions[i]->nsteps)
+ {
+ Step *newstep = testspec->sessions[i]->steps[piles[i]];
+
+ /*
+ * These automatically-generated PermutationSteps never have
+ * blocker conditions. So we need only fill these fields, relying
+ * on run_all_permutations() to have zeroed the rest:
+ */
+ steps[nsteps]->name = newstep->name;
+ steps[nsteps]->step = newstep;
+
+ piles[i]++;
+
+ run_all_permutations_recurse(testspec, piles, nsteps + 1, steps);
+
+ piles[i]--;
+
+ found = true;
+ }
+ }
+
+ /* If all the piles were empty, this permutation is completed. Run it */
+ if (!found)
+ run_permutation(testspec, nsteps, steps);
+}
+
+/*
+ * Run permutations given in the test spec
+ */
+static void
+run_named_permutations(TestSpec *testspec)
+{
+ int i;
+
+ for (i = 0; i < testspec->npermutations; i++)
+ {
+ Permutation *p = testspec->permutations[i];
+
+ run_permutation(testspec, p->nsteps, p->steps);
+ }
+}
+
+static int
+step_qsort_cmp(const void *a, const void *b)
+{
+ Step *stepa = *((Step **) a);
+ Step *stepb = *((Step **) b);
+
+ return strcmp(stepa->name, stepb->name);
+}
+
+static int
+step_bsearch_cmp(const void *a, const void *b)
+{
+ char *stepname = (char *) a;
+ Step *step = *((Step **) b);
+
+ return strcmp(stepname, step->name);
+}
+
+/*
+ * Run one permutation
+ */
+static void
+run_permutation(TestSpec *testspec, int nsteps, PermutationStep **steps)
+{
+ PGresult *res;
+ int i;
+ int nwaiting = 0;
+ PermutationStep **waiting;
+
+ waiting = pg_malloc(sizeof(PermutationStep *) * testspec->nsessions);
+
+ printf("\nstarting permutation:");
+ for (i = 0; i < nsteps; i++)
+ printf(" %s", steps[i]->name);
+ printf("\n");
+
+ /* Perform setup */
+ for (i = 0; i < testspec->nsetupsqls; i++)
+ {
+ res = PQexec(conns[0].conn, testspec->setupsqls[i]);
+ if (PQresultStatus(res) == PGRES_TUPLES_OK)
+ {
+ printResultSet(res);
+ }
+ else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "setup failed: %s", PQerrorMessage(conns[0].conn));
+ exit(1);
+ }
+ PQclear(res);
+ }
+
+ /* Perform per-session setup */
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ if (testspec->sessions[i]->setupsql)
+ {
+ res = PQexec(conns[i + 1].conn, testspec->sessions[i]->setupsql);
+ if (PQresultStatus(res) == PGRES_TUPLES_OK)
+ {
+ printResultSet(res);
+ }
+ else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "setup of session %s failed: %s",
+ conns[i + 1].sessionname,
+ PQerrorMessage(conns[i + 1].conn));
+ exit(1);
+ }
+ PQclear(res);
+ }
+ }
+
+ /* Perform steps */
+ for (i = 0; i < nsteps; i++)
+ {
+ PermutationStep *pstep = steps[i];
+ Step *step = pstep->step;
+ IsoConnInfo *iconn = &conns[1 + step->session];
+ PGconn *conn = iconn->conn;
+ bool mustwait;
+ int j;
+
+ /*
+ * Check whether the session that needs to perform the next step is
+ * still blocked on an earlier step. If so, wait for it to finish.
+ */
+ if (iconn->active_step != NULL)
+ {
+ struct timeval start_time;
+
+ gettimeofday(&start_time, NULL);
+
+ while (iconn->active_step != NULL)
+ {
+ PermutationStep *oldstep = iconn->active_step;
+
+ /*
+ * Wait for oldstep. But even though we don't use
+ * STEP_NONBLOCK, it might not complete because of blocker
+ * conditions.
+ */
+ if (!try_complete_step(testspec, oldstep, STEP_RETRY))
+ {
+ /* Done, so remove oldstep from the waiting[] array. */
+ int w;
+
+ for (w = 0; w < nwaiting; w++)
+ {
+ if (oldstep == waiting[w])
+ break;
+ }
+ if (w >= nwaiting)
+ abort(); /* can't happen */
+ if (w + 1 < nwaiting)
+ memmove(&waiting[w], &waiting[w + 1],
+ (nwaiting - (w + 1)) * sizeof(PermutationStep *));
+ nwaiting--;
+ }
+
+ /*
+ * Check for other steps that have finished. We should do
+ * this if oldstep completed, as it might have unblocked
+ * something. On the other hand, if oldstep hasn't completed,
+ * we must poll all the active steps in hopes of unblocking
+ * oldstep. So either way, poll them.
+ */
+ nwaiting = try_complete_steps(testspec, waiting, nwaiting,
+ STEP_NONBLOCK | STEP_RETRY);
+
+ /*
+ * If the target session is still busy, apply a timeout to
+ * keep from hanging indefinitely, which could happen with
+ * incorrect blocker annotations. Use the same 2 *
+ * max_step_wait limit as try_complete_step does for deciding
+ * to die. (We don't bother with trying to cancel anything,
+ * since it's unclear what to cancel in this case.)
+ */
+ if (iconn->active_step != NULL)
+ {
+ struct timeval current_time;
+ int64 td;
+
+ gettimeofday(&current_time, NULL);
+ td = (int64) current_time.tv_sec - (int64) start_time.tv_sec;
+ td *= USECS_PER_SEC;
+ td += (int64) current_time.tv_usec - (int64) start_time.tv_usec;
+ if (td > 2 * max_step_wait)
+ {
+ fprintf(stderr, "step %s timed out after %d seconds\n",
+ iconn->active_step->name,
+ (int) (td / USECS_PER_SEC));
+ fprintf(stderr, "active steps are:");
+ for (j = 1; j < nconns; j++)
+ {
+ IsoConnInfo *oconn = &conns[j];
+
+ if (oconn->active_step != NULL)
+ fprintf(stderr, " %s",
+ oconn->active_step->name);
+ }
+ fprintf(stderr, "\n");
+ exit(1);
+ }
+ }
+ }
+ }
+
+ /* Send the query for this step. */
+ if (!PQsendQuery(conn, step->sql))
+ {
+ fprintf(stdout, "failed to send query for step %s: %s\n",
+ step->name, PQerrorMessage(conn));
+ exit(1);
+ }
+
+ /* Remember we launched a step. */
+ iconn->active_step = pstep;
+
+ /* Remember target number of NOTICEs for any blocker conditions. */
+ for (j = 0; j < pstep->nblockers; j++)
+ {
+ PermutationStepBlocker *blocker = pstep->blockers[j];
+
+ if (blocker->blocktype == PSB_NUM_NOTICES)
+ blocker->target_notices = blocker->num_notices +
+ conns[blocker->step->session + 1].total_notices;
+ }
+
+ /* Try to complete this step without blocking. */
+ mustwait = try_complete_step(testspec, pstep, STEP_NONBLOCK);
+
+ /* Check for completion of any steps that were previously waiting. */
+ nwaiting = try_complete_steps(testspec, waiting, nwaiting,
+ STEP_NONBLOCK | STEP_RETRY);
+
+ /* If this step is waiting, add it to the array of waiters. */
+ if (mustwait)
+ waiting[nwaiting++] = pstep;
+ }
+
+ /* Wait for any remaining queries. */
+ nwaiting = try_complete_steps(testspec, waiting, nwaiting, STEP_RETRY);
+ if (nwaiting != 0)
+ {
+ fprintf(stderr, "failed to complete permutation due to mutually-blocking steps\n");
+ exit(1);
+ }
+
+ /* Perform per-session teardown */
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ if (testspec->sessions[i]->teardownsql)
+ {
+ res = PQexec(conns[i + 1].conn, testspec->sessions[i]->teardownsql);
+ if (PQresultStatus(res) == PGRES_TUPLES_OK)
+ {
+ printResultSet(res);
+ }
+ else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "teardown of session %s failed: %s",
+ conns[i + 1].sessionname,
+ PQerrorMessage(conns[i + 1].conn));
+ /* don't exit on teardown failure */
+ }
+ PQclear(res);
+ }
+ }
+
+ /* Perform teardown */
+ if (testspec->teardownsql)
+ {
+ res = PQexec(conns[0].conn, testspec->teardownsql);
+ if (PQresultStatus(res) == PGRES_TUPLES_OK)
+ {
+ printResultSet(res);
+ }
+ else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "teardown failed: %s",
+ PQerrorMessage(conns[0].conn));
+ /* don't exit on teardown failure */
+ }
+ PQclear(res);
+ }
+
+ free(waiting);
+}
+
+/*
+ * Check for completion of any waiting step(s).
+ * Remove completed ones from the waiting[] array,
+ * and return the new value of nwaiting.
+ * See try_complete_step for the meaning of the flags.
+ */
+static int
+try_complete_steps(TestSpec *testspec, PermutationStep **waiting,
+ int nwaiting, int flags)
+{
+ int old_nwaiting;
+ bool have_blocker;
+
+ do
+ {
+ int w = 0;
+
+ /* Reset latch; we only care about notices received within loop. */
+ any_new_notice = false;
+
+ /* Likewise, these variables reset for each retry. */
+ old_nwaiting = nwaiting;
+ have_blocker = false;
+
+ /* Scan the array, try to complete steps. */
+ while (w < nwaiting)
+ {
+ if (try_complete_step(testspec, waiting[w], flags))
+ {
+ /* Still blocked, leave it alone. */
+ if (waiting[w]->nblockers > 0)
+ have_blocker = true;
+ w++;
+ }
+ else
+ {
+ /* Done, remove it from array. */
+ if (w + 1 < nwaiting)
+ memmove(&waiting[w], &waiting[w + 1],
+ (nwaiting - (w + 1)) * sizeof(PermutationStep *));
+ nwaiting--;
+ }
+ }
+
+ /*
+ * If any of the still-waiting steps have blocker conditions attached,
+ * it's possible that one of the steps we examined afterwards has
+ * released them (either by completing, or by sending a NOTICE). If
+ * any step completions or NOTICEs happened, repeat the loop until
+ * none occurs. Without this provision, completion timing could vary
+ * depending on the order in which the steps appear in the array.
+ */
+ } while (have_blocker && (nwaiting < old_nwaiting || any_new_notice));
+ return nwaiting;
+}
+
+/*
+ * Our caller already sent the query associated with this step. Wait for it
+ * to either complete, or hit a blocking condition.
+ *
+ * When calling this function on behalf of a given step for a second or later
+ * time, pass the STEP_RETRY flag. Do not pass it on the first call.
+ *
+ * Returns true if the step was *not* completed, false if it was completed.
+ * Reasons for non-completion are (a) the STEP_NONBLOCK flag was specified
+ * and the query is waiting to acquire a lock, or (b) the step has an
+ * unsatisfied blocker condition. When STEP_NONBLOCK is given, we assume
+ * that any lock wait will persist until we have executed additional steps.
+ */
+static bool
+try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags)
+{
+ Step *step = pstep->step;
+ IsoConnInfo *iconn = &conns[1 + step->session];
+ PGconn *conn = iconn->conn;
+ fd_set read_set;
+ struct timeval start_time;
+ struct timeval timeout;
+ int sock = PQsocket(conn);
+ int ret;
+ PGresult *res;
+ PGnotify *notify;
+ bool canceled = false;
+
+ /*
+ * If the step is annotated with (*), then on the first call, force it to
+ * wait. This is useful for ensuring consistent output when the step
+ * might or might not complete so fast that we don't observe it waiting.
+ */
+ if (!(flags & STEP_RETRY))
+ {
+ int i;
+
+ for (i = 0; i < pstep->nblockers; i++)
+ {
+ PermutationStepBlocker *blocker = pstep->blockers[i];
+
+ if (blocker->blocktype == PSB_ONCE)
+ {
+ printf("step %s: %s <waiting ...>\n",
+ step->name, step->sql);
+ return true;
+ }
+ }
+ }
+
+ if (sock < 0)
+ {
+ fprintf(stderr, "invalid socket: %s", PQerrorMessage(conn));
+ exit(1);
+ }
+
+ gettimeofday(&start_time, NULL);
+ FD_ZERO(&read_set);
+
+ while (PQisBusy(conn))
+ {
+ FD_SET(sock, &read_set);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 10000; /* Check for lock waits every 10ms. */
+
+ ret = select(sock + 1, &read_set, NULL, NULL, &timeout);
+ if (ret < 0) /* error in select() */
+ {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "select failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ else if (ret == 0) /* select() timeout: check for lock wait */
+ {
+ struct timeval current_time;
+ int64 td;
+
+ /* If it's OK for the step to block, check whether it has. */
+ if (flags & STEP_NONBLOCK)
+ {
+ bool waiting;
+
+ res = PQexecPrepared(conns[0].conn, PREP_WAITING, 1,
+ &conns[step->session + 1].backend_pid_str,
+ NULL, NULL, 0);
+ if (PQresultStatus(res) != PGRES_TUPLES_OK ||
+ PQntuples(res) != 1)
+ {
+ fprintf(stderr, "lock wait query failed: %s",
+ PQerrorMessage(conns[0].conn));
+ exit(1);
+ }
+ waiting = ((PQgetvalue(res, 0, 0))[0] == 't');
+ PQclear(res);
+
+ if (waiting) /* waiting to acquire a lock */
+ {
+ /*
+ * Since it takes time to perform the lock-check query,
+ * some data --- notably, NOTICE messages --- might have
+ * arrived since we looked. We must call PQconsumeInput
+ * and then PQisBusy to collect and process any such
+ * messages. In the (unlikely) case that PQisBusy then
+ * returns false, we might as well go examine the
+ * available result.
+ */
+ if (!PQconsumeInput(conn))
+ {
+ fprintf(stderr, "PQconsumeInput failed: %s\n",
+ PQerrorMessage(conn));
+ exit(1);
+ }
+ if (!PQisBusy(conn))
+ break;
+
+ /*
+ * conn is still busy, so conclude that the step really is
+ * waiting.
+ */
+ if (!(flags & STEP_RETRY))
+ printf("step %s: %s <waiting ...>\n",
+ step->name, step->sql);
+ return true;
+ }
+ /* else, not waiting */
+ }
+
+ /* Figure out how long we've been waiting for this step. */
+ gettimeofday(&current_time, NULL);
+ td = (int64) current_time.tv_sec - (int64) start_time.tv_sec;
+ td *= USECS_PER_SEC;
+ td += (int64) current_time.tv_usec - (int64) start_time.tv_usec;
+
+ /*
+ * After max_step_wait microseconds, try to cancel the query.
+ *
+ * If the user tries to test an invalid permutation, we don't want
+ * to hang forever, especially when this is running in the
+ * buildfarm. This will presumably lead to this permutation
+ * failing, but remaining permutations and tests should still be
+ * OK.
+ */
+ if (td > max_step_wait && !canceled)
+ {
+ PGcancel *cancel = PQgetCancel(conn);
+
+ if (cancel != NULL)
+ {
+ char buf[256];
+
+ if (PQcancel(cancel, buf, sizeof(buf)))
+ {
+ /*
+ * print to stdout not stderr, as this should appear
+ * in the test case's results
+ */
+ printf("isolationtester: canceling step %s after %d seconds\n",
+ step->name, (int) (td / USECS_PER_SEC));
+ canceled = true;
+ }
+ else
+ fprintf(stderr, "PQcancel failed: %s\n", buf);
+ PQfreeCancel(cancel);
+ }
+ }
+
+ /*
+ * After twice max_step_wait, just give up and die.
+ *
+ * Since cleanup steps won't be run in this case, this may cause
+ * later tests to fail. That stinks, but it's better than waiting
+ * forever for the server to respond to the cancel.
+ */
+ if (td > 2 * max_step_wait)
+ {
+ fprintf(stderr, "step %s timed out after %d seconds\n",
+ step->name, (int) (td / USECS_PER_SEC));
+ exit(1);
+ }
+ }
+ else if (!PQconsumeInput(conn)) /* select(): data available */
+ {
+ fprintf(stderr, "PQconsumeInput failed: %s\n",
+ PQerrorMessage(conn));
+ exit(1);
+ }
+ }
+
+ /*
+ * The step is done, but we won't report it as complete so long as there
+ * are blockers.
+ */
+ if (step_has_blocker(pstep))
+ {
+ if (!(flags & STEP_RETRY))
+ printf("step %s: %s <waiting ...>\n",
+ step->name, step->sql);
+ return true;
+ }
+
+ /* Otherwise, go ahead and complete it. */
+ if (flags & STEP_RETRY)
+ printf("step %s: <... completed>\n", step->name);
+ else
+ printf("step %s: %s\n", step->name, step->sql);
+
+ while ((res = PQgetResult(conn)))
+ {
+ switch (PQresultStatus(res))
+ {
+ case PGRES_COMMAND_OK:
+ case PGRES_EMPTY_QUERY:
+ break;
+ case PGRES_TUPLES_OK:
+ printResultSet(res);
+ break;
+ case PGRES_FATAL_ERROR:
+
+ /*
+ * Detail may contain XID values, so we want to just show
+ * primary. Beware however that libpq-generated error results
+ * may not contain subfields, only an old-style message.
+ */
+ {
+ const char *sev = PQresultErrorField(res,
+ PG_DIAG_SEVERITY);
+ const char *msg = PQresultErrorField(res,
+ PG_DIAG_MESSAGE_PRIMARY);
+
+ if (sev && msg)
+ printf("%s: %s\n", sev, msg);
+ else
+ printf("%s\n", PQresultErrorMessage(res));
+ }
+ break;
+ default:
+ printf("unexpected result status: %s\n",
+ PQresStatus(PQresultStatus(res)));
+ }
+ PQclear(res);
+ }
+
+ /* Report any available NOTIFY messages, too */
+ PQconsumeInput(conn);
+ while ((notify = PQnotifies(conn)) != NULL)
+ {
+ /* Try to identify which session it came from */
+ const char *sendername = NULL;
+ char pidstring[32];
+ int i;
+
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ if (notify->be_pid == conns[i + 1].backend_pid)
+ {
+ sendername = conns[i + 1].sessionname;
+ break;
+ }
+ }
+ if (sendername == NULL)
+ {
+ /* Doesn't seem to be any test session, so show the hard way */
+ snprintf(pidstring, sizeof(pidstring), "PID %d", notify->be_pid);
+ sendername = pidstring;
+ }
+ printf("%s: NOTIFY \"%s\" with payload \"%s\" from %s\n",
+ testspec->sessions[step->session]->name,
+ notify->relname, notify->extra, sendername);
+ PQfreemem(notify);
+ PQconsumeInput(conn);
+ }
+
+ /* Connection is now idle. */
+ iconn->active_step = NULL;
+
+ return false;
+}
+
+/* Detect whether a step has any unsatisfied blocker conditions */
+static bool
+step_has_blocker(PermutationStep *pstep)
+{
+ int i;
+
+ for (i = 0; i < pstep->nblockers; i++)
+ {
+ PermutationStepBlocker *blocker = pstep->blockers[i];
+ IsoConnInfo *iconn;
+
+ switch (blocker->blocktype)
+ {
+ case PSB_ONCE:
+ /* Ignore; try_complete_step handles this specially */
+ break;
+ case PSB_OTHER_STEP:
+ /* Block if referenced step is active */
+ iconn = &conns[1 + blocker->step->session];
+ if (iconn->active_step &&
+ iconn->active_step->step == blocker->step)
+ return true;
+ break;
+ case PSB_NUM_NOTICES:
+ /* Block if not enough notices received yet */
+ iconn = &conns[1 + blocker->step->session];
+ if (iconn->total_notices < blocker->target_notices)
+ return true;
+ break;
+ }
+ }
+ return false;
+}
+
+static void
+printResultSet(PGresult *res)
+{
+ PQprintOpt popt;
+
+ memset(&popt, 0, sizeof(popt));
+ popt.header = true;
+ popt.align = true;
+ popt.fieldSep = "|";
+ PQprint(stdout, res, &popt);
+}
+
+/* notice processor for regular user sessions */
+static void
+isotesterNoticeProcessor(void *arg, const char *message)
+{
+ IsoConnInfo *myconn = (IsoConnInfo *) arg;
+
+ /* Prefix the backend's message with the session name. */
+ printf("%s: %s", myconn->sessionname, message);
+ /* Record notices, since we may need this to decide to unblock a step. */
+ myconn->total_notices++;
+ any_new_notice = true;
+}
+
+/* notice processor, hides the message */
+static void
+blackholeNoticeProcessor(void *arg, const char *message)
+{
+ /* do nothing */
+}
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
new file mode 100644
index 0000000..5f30021
--- /dev/null
+++ b/src/test/isolation/isolationtester.h
@@ -0,0 +1,93 @@
+/*-------------------------------------------------------------------------
+ *
+ * isolationtester.h
+ * include file for isolation tests
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/test/isolation/isolationtester.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ISOLATIONTESTER_H
+#define ISOLATIONTESTER_H
+
+/*
+ * The structs declared here are used in the output of specparse.y.
+ * Except where noted, all fields are set in the grammar and not
+ * changed thereafter.
+ */
+typedef struct Step Step;
+
+typedef struct
+{
+ char *name;
+ char *setupsql;
+ char *teardownsql;
+ Step **steps;
+ int nsteps;
+} Session;
+
+struct Step
+{
+ char *name;
+ char *sql;
+ /* These fields are filled by check_testspec(): */
+ int session; /* identifies owning session */
+ bool used; /* has step been used in a permutation? */
+};
+
+typedef enum
+{
+ PSB_ONCE, /* force step to wait once */
+ PSB_OTHER_STEP, /* wait for another step to complete first */
+ PSB_NUM_NOTICES /* wait for N notices from another session */
+} PermutationStepBlockerType;
+
+typedef struct
+{
+ char *stepname;
+ PermutationStepBlockerType blocktype;
+ int num_notices; /* only used for PSB_NUM_NOTICES */
+ /* These fields are filled by check_testspec(): */
+ Step *step; /* link to referenced step (if any) */
+ /* These fields are runtime workspace: */
+ int target_notices; /* total notices needed from other session */
+} PermutationStepBlocker;
+
+typedef struct
+{
+ char *name; /* name of referenced Step */
+ PermutationStepBlocker **blockers;
+ int nblockers;
+ /* These fields are filled by check_testspec(): */
+ Step *step; /* link to referenced Step */
+} PermutationStep;
+
+typedef struct
+{
+ int nsteps;
+ PermutationStep **steps;
+} Permutation;
+
+typedef struct
+{
+ char **setupsqls;
+ int nsetupsqls;
+ char *teardownsql;
+ Session **sessions;
+ int nsessions;
+ Permutation **permutations;
+ int npermutations;
+} TestSpec;
+
+extern TestSpec parseresult;
+
+extern int spec_yyparse(void);
+
+extern int spec_yylex(void);
+extern void spec_yyerror(const char *str);
+
+#endif /* ISOLATIONTESTER_H */
diff --git a/src/test/isolation/specparse.c b/src/test/isolation/specparse.c
new file mode 100644
index 0000000..07da64a
--- /dev/null
+++ b/src/test/isolation/specparse.c
@@ -0,0 +1,1689 @@
+/* A Bison parser, made by GNU Bison 3.7.5. */
+
+/* Bison implementation for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Identify Bison output, and Bison version. */
+#define YYBISON 30705
+
+/* Bison version string. */
+#define YYBISON_VERSION "3.7.5"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 0
+
+/* Push parsers. */
+#define YYPUSH 0
+
+/* Pull parsers. */
+#define YYPULL 1
+
+
+/* Substitute the variable and function names. */
+#define yyparse spec_yyparse
+#define yylex spec_yylex
+#define yyerror spec_yyerror
+#define yydebug spec_yydebug
+#define yynerrs spec_yynerrs
+#define yylval spec_yylval
+#define yychar spec_yychar
+
+/* First part of user prologue. */
+#line 1 "specparse.y"
+
+/*-------------------------------------------------------------------------
+ *
+ * specparse.y
+ * bison grammar for the isolation test file format
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "isolationtester.h"
+
+
+TestSpec parseresult; /* result of parsing is left here */
+
+
+#line 99 "specparse.c"
+
+# ifndef YY_CAST
+# ifdef __cplusplus
+# define YY_CAST(Type, Val) static_cast<Type> (Val)
+# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val)
+# else
+# define YY_CAST(Type, Val) ((Type) (Val))
+# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val))
+# endif
+# endif
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int spec_yydebug;
+#endif
+
+/* Token kinds. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ YYEMPTY = -2,
+ YYEOF = 0, /* "end of file" */
+ YYerror = 256, /* error */
+ YYUNDEF = 257, /* "invalid token" */
+ sqlblock = 258, /* sqlblock */
+ identifier = 259, /* identifier */
+ INTEGER = 260, /* INTEGER */
+ NOTICES = 261, /* NOTICES */
+ PERMUTATION = 262, /* PERMUTATION */
+ SESSION = 263, /* SESSION */
+ SETUP = 264, /* SETUP */
+ STEP = 265, /* STEP */
+ TEARDOWN = 266, /* TEARDOWN */
+ TEST = 267 /* TEST */
+ };
+ typedef enum yytokentype yytoken_kind_t;
+#endif
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 26 "specparse.y"
+
+ char *str;
+ int integer;
+ Session *session;
+ Step *step;
+ Permutation *permutation;
+ PermutationStep *permutationstep;
+ PermutationStepBlocker *blocker;
+ struct
+ {
+ void **elements;
+ int nelements;
+ } ptr_list;
+
+#line 173 "specparse.c"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE spec_yylval;
+
+int spec_yyparse (void);
+
+
+/* Symbol kind. */
+enum yysymbol_kind_t
+{
+ YYSYMBOL_YYEMPTY = -2,
+ YYSYMBOL_YYEOF = 0, /* "end of file" */
+ YYSYMBOL_YYerror = 1, /* error */
+ YYSYMBOL_YYUNDEF = 2, /* "invalid token" */
+ YYSYMBOL_sqlblock = 3, /* sqlblock */
+ YYSYMBOL_identifier = 4, /* identifier */
+ YYSYMBOL_INTEGER = 5, /* INTEGER */
+ YYSYMBOL_NOTICES = 6, /* NOTICES */
+ YYSYMBOL_PERMUTATION = 7, /* PERMUTATION */
+ YYSYMBOL_SESSION = 8, /* SESSION */
+ YYSYMBOL_SETUP = 9, /* SETUP */
+ YYSYMBOL_STEP = 10, /* STEP */
+ YYSYMBOL_TEARDOWN = 11, /* TEARDOWN */
+ YYSYMBOL_TEST = 12, /* TEST */
+ YYSYMBOL_13_ = 13, /* '(' */
+ YYSYMBOL_14_ = 14, /* ')' */
+ YYSYMBOL_15_ = 15, /* ',' */
+ YYSYMBOL_16_ = 16, /* '*' */
+ YYSYMBOL_YYACCEPT = 17, /* $accept */
+ YYSYMBOL_TestSpec = 18, /* TestSpec */
+ YYSYMBOL_setup_list = 19, /* setup_list */
+ YYSYMBOL_opt_setup = 20, /* opt_setup */
+ YYSYMBOL_setup = 21, /* setup */
+ YYSYMBOL_opt_teardown = 22, /* opt_teardown */
+ YYSYMBOL_session_list = 23, /* session_list */
+ YYSYMBOL_session = 24, /* session */
+ YYSYMBOL_step_list = 25, /* step_list */
+ YYSYMBOL_step = 26, /* step */
+ YYSYMBOL_opt_permutation_list = 27, /* opt_permutation_list */
+ YYSYMBOL_permutation_list = 28, /* permutation_list */
+ YYSYMBOL_permutation = 29, /* permutation */
+ YYSYMBOL_permutation_step_list = 30, /* permutation_step_list */
+ YYSYMBOL_permutation_step = 31, /* permutation_step */
+ YYSYMBOL_blocker_list = 32, /* blocker_list */
+ YYSYMBOL_blocker = 33 /* blocker */
+};
+typedef enum yysymbol_kind_t yysymbol_kind_t;
+
+
+
+
+#ifdef short
+# undef short
+#endif
+
+/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure
+ <limits.h> and (if available) <stdint.h> are included
+ so that the code can choose integer types of a good width. */
+
+#ifndef __PTRDIFF_MAX__
+# include <limits.h> /* INFRINGES ON USER NAME SPACE */
+# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stdint.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_STDINT_H
+# endif
+#endif
+
+/* Narrow types that promote to a signed type and that can represent a
+ signed or unsigned integer of at least N bits. In tables they can
+ save space and decrease cache pressure. Promoting to a signed type
+ helps avoid bugs in integer arithmetic. */
+
+#ifdef __INT_LEAST8_MAX__
+typedef __INT_LEAST8_TYPE__ yytype_int8;
+#elif defined YY_STDINT_H
+typedef int_least8_t yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef __INT_LEAST16_MAX__
+typedef __INT_LEAST16_TYPE__ yytype_int16;
+#elif defined YY_STDINT_H
+typedef int_least16_t yytype_int16;
+#else
+typedef short yytype_int16;
+#endif
+
+/* Work around bug in HP-UX 11.23, which defines these macros
+ incorrectly for preprocessor constants. This workaround can likely
+ be removed in 2023, as HPE has promised support for HP-UX 11.23
+ (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of
+ <https://h20195.www2.hpe.com/V2/getpdf.aspx/4AA4-7673ENW.pdf>. */
+#ifdef __hpux
+# undef UINT_LEAST8_MAX
+# undef UINT_LEAST16_MAX
+# define UINT_LEAST8_MAX 255
+# define UINT_LEAST16_MAX 65535
+#endif
+
+#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST8_TYPE__ yytype_uint8;
+#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST8_MAX <= INT_MAX)
+typedef uint_least8_t yytype_uint8;
+#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX
+typedef unsigned char yytype_uint8;
+#else
+typedef short yytype_uint8;
+#endif
+
+#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST16_TYPE__ yytype_uint16;
+#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST16_MAX <= INT_MAX)
+typedef uint_least16_t yytype_uint16;
+#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX
+typedef unsigned short yytype_uint16;
+#else
+typedef int yytype_uint16;
+#endif
+
+#ifndef YYPTRDIFF_T
+# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__
+# define YYPTRDIFF_T __PTRDIFF_TYPE__
+# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__
+# elif defined PTRDIFF_MAX
+# ifndef ptrdiff_t
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# endif
+# define YYPTRDIFF_T ptrdiff_t
+# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX
+# else
+# define YYPTRDIFF_T long
+# define YYPTRDIFF_MAXIMUM LONG_MAX
+# endif
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM \
+ YY_CAST (YYPTRDIFF_T, \
+ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \
+ ? YYPTRDIFF_MAXIMUM \
+ : YY_CAST (YYSIZE_T, -1)))
+
+#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X))
+
+
+/* Stored state numbers (used for stacks). */
+typedef yytype_int8 yy_state_t;
+
+/* State numbers in computations. */
+typedef int yy_state_fast_t;
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(Msgid) Msgid
+# endif
+#endif
+
+
+#ifndef YY_ATTRIBUTE_PURE
+# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+# define YY_ATTRIBUTE_PURE
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# else
+# define YY_ATTRIBUTE_UNUSED
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YY_USE(E) ((void) (E))
+#else
+# define YY_USE(E) /* empty */
+#endif
+
+#if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__
+# define YY_IGNORE_USELESS_CAST_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"")
+# define YY_IGNORE_USELESS_CAST_END \
+ _Pragma ("GCC diagnostic pop")
+#endif
+#ifndef YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_END
+#endif
+
+
+#define YY_ASSERT(E) ((void) (0 && (E)))
+
+#if !defined yyoverflow
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+ /* Use EXIT_SUCCESS as a witness for stdlib.h. */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's 'empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined EXIT_SUCCESS \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* !defined yyoverflow */
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yy_state_t yyss_alloc;
+ YYSTYPE yyvs_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+# define YYSTACK_BYTES(N) \
+ ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
+ do \
+ { \
+ YYPTRDIFF_T yynewbytes; \
+ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
+ Stack = &yyptr->Stack_alloc; \
+ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / YYSIZEOF (*yyptr); \
+ } \
+ while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(Dst, Src, Count) \
+ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src)))
+# else
+# define YYCOPY(Dst, Src, Count) \
+ do \
+ { \
+ YYPTRDIFF_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (Dst)[yyi] = (Src)[yyi]; \
+ } \
+ while (0)
+# endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 3
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 41
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 17
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 17
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 29
+/* YYNSTATES -- Number of states. */
+#define YYNSTATES 43
+
+/* YYMAXUTOK -- Last valid token kind. */
+#define YYMAXUTOK 267
+
+
+/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex, with out-of-bounds checking. */
+#define YYTRANSLATE(YYX) \
+ (0 <= (YYX) && (YYX) <= YYMAXUTOK \
+ ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \
+ : YYSYMBOL_YYUNDEF)
+
+/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex. */
+static const yytype_int8 yytranslate[] =
+{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 13, 14, 16, 2, 15, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12
+};
+
+#if YYDEBUG
+ /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
+static const yytype_int16 yyrline[] =
+{
+ 0, 59, 59, 76, 80, 90, 91, 95, 99, 100,
+ 104, 111, 120, 132, 139, 149, 161, 166, 172, 179,
+ 189, 198, 205, 214, 222, 233, 240, 249, 258, 267
+};
+#endif
+
+/** Accessing symbol of state STATE. */
+#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State])
+
+#if YYDEBUG || 0
+/* The user-facing name of the symbol whose (internal) number is
+ YYSYMBOL. No bounds checking. */
+static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED;
+
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ "\"end of file\"", "error", "\"invalid token\"", "sqlblock",
+ "identifier", "INTEGER", "NOTICES", "PERMUTATION", "SESSION", "SETUP",
+ "STEP", "TEARDOWN", "TEST", "'('", "')'", "','", "'*'", "$accept",
+ "TestSpec", "setup_list", "opt_setup", "setup", "opt_teardown",
+ "session_list", "session", "step_list", "step", "opt_permutation_list",
+ "permutation_list", "permutation", "permutation_step_list",
+ "permutation_step", "blocker_list", "blocker", YY_NULLPTR
+};
+
+static const char *
+yysymbol_name (yysymbol_kind_t yysymbol)
+{
+ return yytname[yysymbol];
+}
+#endif
+
+#ifdef YYPRINT
+/* YYTOKNUM[NUM] -- (External) token number corresponding to the
+ (internal) symbol number NUM (which must be that of a token). */
+static const yytype_int16 yytoknum[] =
+{
+ 0, 256, 257, 258, 259, 260, 261, 262, 263, 264,
+ 265, 266, 267, 40, 41, 44, 42
+};
+#endif
+
+#define YYPACT_NINF (-14)
+
+#define yypact_value_is_default(Yyn) \
+ ((Yyn) == YYPACT_NINF)
+
+#define YYTABLE_NINF (-1)
+
+#define yytable_value_is_error(Yyn) \
+ 0
+
+ /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+static const yytype_int8 yypact[] =
+{
+ -14, 2, -8, -14, 3, 4, -14, 7, -14, -14,
+ 6, -3, -14, 8, 12, -14, -14, 11, -14, 1,
+ -14, 9, 12, -14, -14, 15, -2, -14, -4, -14,
+ 17, -14, -14, 18, -14, -1, -14, -14, 16, -14,
+ -4, -14, -14
+};
+
+ /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ Performed when YYTABLE does not specify something else to do. Zero
+ means the default is an error. */
+static const yytype_int8 yydefact[] =
+{
+ 3, 0, 8, 1, 0, 0, 4, 0, 7, 9,
+ 0, 17, 11, 5, 0, 10, 2, 16, 19, 0,
+ 6, 23, 20, 22, 18, 0, 8, 14, 0, 21,
+ 0, 12, 13, 27, 29, 0, 26, 15, 0, 24,
+ 0, 28, 25
+};
+
+ /* YYPGOTO[NTERM-NUM]. */
+static const yytype_int8 yypgoto[] =
+{
+ -14, -14, -14, -14, 10, 0, -14, 14, -14, 5,
+ -14, -14, 13, -14, 19, -14, -13
+};
+
+ /* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_int8 yydefgoto[] =
+{
+ 0, 1, 2, 19, 6, 7, 11, 12, 26, 27,
+ 16, 17, 18, 22, 23, 35, 36
+};
+
+ /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule whose
+ number is the opposite. If YYTABLE_NINF, syntax error. */
+static const yytype_int8 yytable[] =
+{
+ 33, 4, 3, 5, 14, 10, 8, 9, 25, 5,
+ 13, 25, 34, 39, 40, 10, 21, 4, 14, 30,
+ 37, 41, 28, 20, 38, 15, 31, 42, 0, 0,
+ 24, 32, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 29
+};
+
+static const yytype_int8 yycheck[] =
+{
+ 4, 9, 0, 11, 7, 8, 3, 3, 10, 11,
+ 4, 10, 16, 14, 15, 8, 4, 9, 7, 4,
+ 3, 5, 13, 13, 6, 11, 26, 40, -1, -1,
+ 17, 26, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 22
+};
+
+ /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
+ symbol of state STATE-NUM. */
+static const yytype_int8 yystos[] =
+{
+ 0, 18, 19, 0, 9, 11, 21, 22, 3, 3,
+ 8, 23, 24, 4, 7, 24, 27, 28, 29, 20,
+ 21, 4, 30, 31, 29, 10, 25, 26, 13, 31,
+ 4, 22, 26, 4, 16, 32, 33, 3, 6, 14,
+ 15, 5, 33
+};
+
+ /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */
+static const yytype_int8 yyr1[] =
+{
+ 0, 17, 18, 19, 19, 20, 20, 21, 22, 22,
+ 23, 23, 24, 25, 25, 26, 27, 27, 28, 28,
+ 29, 30, 30, 31, 31, 32, 32, 33, 33, 33
+};
+
+ /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */
+static const yytype_int8 yyr2[] =
+{
+ 0, 2, 4, 0, 2, 0, 1, 2, 0, 2,
+ 2, 1, 5, 2, 1, 3, 1, 0, 2, 1,
+ 2, 2, 1, 1, 4, 3, 1, 1, 3, 1
+};
+
+
+enum { YYENOMEM = -2 };
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+ do \
+ if (yychar == YYEMPTY) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ YYPOPSTACK (yylen); \
+ yystate = *yyssp; \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+ while (0)
+
+/* Backward compatibility with an undocumented macro.
+ Use YYerror or YYUNDEF. */
+#define YYERRCODE YYUNDEF
+
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (0)
+
+/* This macro is provided for backward compatibility. */
+# ifndef YY_LOCATION_PRINT
+# define YY_LOCATION_PRINT(File, Loc) ((void) 0)
+# endif
+
+
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Kind, Value); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (0)
+
+
+/*-----------------------------------.
+| Print this symbol's value on YYO. |
+`-----------------------------------*/
+
+static void
+yy_symbol_value_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ FILE *yyoutput = yyo;
+ YY_USE (yyoutput);
+ if (!yyvaluep)
+ return;
+# ifdef YYPRINT
+ if (yykind < YYNTOKENS)
+ YYPRINT (yyo, yytoknum[yykind], *yyvaluep);
+# endif
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/*---------------------------.
+| Print this symbol on YYO. |
+`---------------------------*/
+
+static void
+yy_symbol_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ YYFPRINTF (yyo, "%s %s (",
+ yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind));
+
+ yy_symbol_value_print (yyo, yykind, yyvaluep);
+ YYFPRINTF (yyo, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+static void
+yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop)
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; yybottom <= yytop; yybottom++)
+ {
+ int yybot = *yybottom;
+ YYFPRINTF (stderr, " %d", yybot);
+ }
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (0)
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+static void
+yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp,
+ int yyrule)
+{
+ int yylno = yyrline[yyrule];
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ YYFPRINTF (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr,
+ YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]),
+ &yyvsp[(yyi + 1) - (yynrhs)]);
+ YYFPRINTF (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyssp, yyvsp, Rule); \
+} while (0)
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args) ((void) 0)
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+
+
+
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg,
+ yysymbol_kind_t yykind, YYSTYPE *yyvaluep)
+{
+ YY_USE (yyvaluep);
+ if (!yymsg)
+ yymsg = "Deleting";
+ YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp);
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/* Lookahead token kind. */
+int yychar;
+
+/* The semantic value of the lookahead symbol. */
+YYSTYPE yylval;
+/* Number of syntax errors so far. */
+int yynerrs;
+
+
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+int
+yyparse (void)
+{
+ yy_state_fast_t yystate = 0;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus = 0;
+
+ /* Refer to the stacks through separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* Their size. */
+ YYPTRDIFF_T yystacksize = YYINITDEPTH;
+
+ /* The state stack: array, bottom, top. */
+ yy_state_t yyssa[YYINITDEPTH];
+ yy_state_t *yyss = yyssa;
+ yy_state_t *yyssp = yyss;
+
+ /* The semantic value stack: array, bottom, top. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs = yyvsa;
+ YYSTYPE *yyvsp = yyvs;
+
+ int yyn;
+ /* The return value of yyparse. */
+ int yyresult;
+ /* Lookahead symbol kind. */
+ yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY;
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+
+
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yychar = YYEMPTY; /* Cause a token to be read. */
+ goto yysetstate;
+
+
+/*------------------------------------------------------------.
+| yynewstate -- push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+
+/*--------------------------------------------------------------------.
+| yysetstate -- set current state (the top of the stack) to yystate. |
+`--------------------------------------------------------------------*/
+yysetstate:
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+ YY_ASSERT (0 <= yystate && yystate < YYNSTATES);
+ YY_IGNORE_USELESS_CAST_BEGIN
+ *yyssp = YY_CAST (yy_state_t, yystate);
+ YY_IGNORE_USELESS_CAST_END
+ YY_STACK_PRINT (yyss, yyssp);
+
+ if (yyss + yystacksize - 1 <= yyssp)
+#if !defined yyoverflow && !defined YYSTACK_RELOCATE
+ goto yyexhaustedlab;
+#else
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYPTRDIFF_T yysize = yyssp - yyss + 1;
+
+# if defined yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ yy_state_t *yyss1 = yyss;
+ YYSTYPE *yyvs1 = yyvs;
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * YYSIZEOF (*yyssp),
+ &yyvs1, yysize * YYSIZEOF (*yyvsp),
+ &yystacksize);
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+# else /* defined YYSTACK_RELOCATE */
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ goto yyexhaustedlab;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yy_state_t *yyss1 = yyss;
+ union yyalloc *yyptr =
+ YY_CAST (union yyalloc *,
+ YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize))));
+ if (! yyptr)
+ goto yyexhaustedlab;
+ YYSTACK_RELOCATE (yyss_alloc, yyss);
+ YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+
+ YY_IGNORE_USELESS_CAST_BEGIN
+ YYDPRINTF ((stderr, "Stack size increased to %ld\n",
+ YY_CAST (long, yystacksize)));
+ YY_IGNORE_USELESS_CAST_END
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */
+
+ if (yystate == YYFINAL)
+ YYACCEPT;
+
+ goto yybackup;
+
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+ /* Do appropriate processing given the current state. Read a
+ lookahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to lookahead token. */
+ yyn = yypact[yystate];
+ if (yypact_value_is_default (yyn))
+ goto yydefault;
+
+ /* Not known => get a lookahead token if don't already have one. */
+
+ /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token\n"));
+ yychar = yylex ();
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = YYEOF;
+ yytoken = YYSYMBOL_YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else if (yychar == YYerror)
+ {
+ /* The scanner already issued an error message, process directly
+ to error recovery. But do not keep the error token as
+ lookahead, it is too special and may lead us to an endless
+ loop in error recovery. */
+ yychar = YYUNDEF;
+ yytoken = YYSYMBOL_YYerror;
+ goto yyerrlab1;
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yytable_value_is_error (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the lookahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+ yystate = yyn;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+ /* Discard the shifted token. */
+ yychar = YYEMPTY;
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ '$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 2: /* TestSpec: setup_list opt_teardown session_list opt_permutation_list */
+#line 63 "specparse.y"
+ {
+ parseresult.setupsqls = (char **) (yyvsp[-3].ptr_list).elements;
+ parseresult.nsetupsqls = (yyvsp[-3].ptr_list).nelements;
+ parseresult.teardownsql = (yyvsp[-2].str);
+ parseresult.sessions = (Session **) (yyvsp[-1].ptr_list).elements;
+ parseresult.nsessions = (yyvsp[-1].ptr_list).nelements;
+ parseresult.permutations = (Permutation **) (yyvsp[0].ptr_list).elements;
+ parseresult.npermutations = (yyvsp[0].ptr_list).nelements;
+ }
+#line 1219 "specparse.c"
+ break;
+
+ case 3: /* setup_list: %empty */
+#line 76 "specparse.y"
+ {
+ (yyval.ptr_list).elements = NULL;
+ (yyval.ptr_list).nelements = 0;
+ }
+#line 1228 "specparse.c"
+ break;
+
+ case 4: /* setup_list: setup_list setup */
+#line 81 "specparse.y"
+ {
+ (yyval.ptr_list).elements = pg_realloc((yyvsp[-1].ptr_list).elements,
+ ((yyvsp[-1].ptr_list).nelements + 1) * sizeof(void *));
+ (yyval.ptr_list).elements[(yyvsp[-1].ptr_list).nelements] = (yyvsp[0].str);
+ (yyval.ptr_list).nelements = (yyvsp[-1].ptr_list).nelements + 1;
+ }
+#line 1239 "specparse.c"
+ break;
+
+ case 5: /* opt_setup: %empty */
+#line 90 "specparse.y"
+ { (yyval.str) = NULL; }
+#line 1245 "specparse.c"
+ break;
+
+ case 6: /* opt_setup: setup */
+#line 91 "specparse.y"
+ { (yyval.str) = (yyvsp[0].str); }
+#line 1251 "specparse.c"
+ break;
+
+ case 7: /* setup: SETUP sqlblock */
+#line 95 "specparse.y"
+ { (yyval.str) = (yyvsp[0].str); }
+#line 1257 "specparse.c"
+ break;
+
+ case 8: /* opt_teardown: %empty */
+#line 99 "specparse.y"
+ { (yyval.str) = NULL; }
+#line 1263 "specparse.c"
+ break;
+
+ case 9: /* opt_teardown: TEARDOWN sqlblock */
+#line 100 "specparse.y"
+ { (yyval.str) = (yyvsp[0].str); }
+#line 1269 "specparse.c"
+ break;
+
+ case 10: /* session_list: session_list session */
+#line 105 "specparse.y"
+ {
+ (yyval.ptr_list).elements = pg_realloc((yyvsp[-1].ptr_list).elements,
+ ((yyvsp[-1].ptr_list).nelements + 1) * sizeof(void *));
+ (yyval.ptr_list).elements[(yyvsp[-1].ptr_list).nelements] = (yyvsp[0].session);
+ (yyval.ptr_list).nelements = (yyvsp[-1].ptr_list).nelements + 1;
+ }
+#line 1280 "specparse.c"
+ break;
+
+ case 11: /* session_list: session */
+#line 112 "specparse.y"
+ {
+ (yyval.ptr_list).nelements = 1;
+ (yyval.ptr_list).elements = pg_malloc(sizeof(void *));
+ (yyval.ptr_list).elements[0] = (yyvsp[0].session);
+ }
+#line 1290 "specparse.c"
+ break;
+
+ case 12: /* session: SESSION identifier opt_setup step_list opt_teardown */
+#line 121 "specparse.y"
+ {
+ (yyval.session) = pg_malloc(sizeof(Session));
+ (yyval.session)->name = (yyvsp[-3].str);
+ (yyval.session)->setupsql = (yyvsp[-2].str);
+ (yyval.session)->steps = (Step **) (yyvsp[-1].ptr_list).elements;
+ (yyval.session)->nsteps = (yyvsp[-1].ptr_list).nelements;
+ (yyval.session)->teardownsql = (yyvsp[0].str);
+ }
+#line 1303 "specparse.c"
+ break;
+
+ case 13: /* step_list: step_list step */
+#line 133 "specparse.y"
+ {
+ (yyval.ptr_list).elements = pg_realloc((yyvsp[-1].ptr_list).elements,
+ ((yyvsp[-1].ptr_list).nelements + 1) * sizeof(void *));
+ (yyval.ptr_list).elements[(yyvsp[-1].ptr_list).nelements] = (yyvsp[0].step);
+ (yyval.ptr_list).nelements = (yyvsp[-1].ptr_list).nelements + 1;
+ }
+#line 1314 "specparse.c"
+ break;
+
+ case 14: /* step_list: step */
+#line 140 "specparse.y"
+ {
+ (yyval.ptr_list).nelements = 1;
+ (yyval.ptr_list).elements = pg_malloc(sizeof(void *));
+ (yyval.ptr_list).elements[0] = (yyvsp[0].step);
+ }
+#line 1324 "specparse.c"
+ break;
+
+ case 15: /* step: STEP identifier sqlblock */
+#line 150 "specparse.y"
+ {
+ (yyval.step) = pg_malloc(sizeof(Step));
+ (yyval.step)->name = (yyvsp[-1].str);
+ (yyval.step)->sql = (yyvsp[0].str);
+ (yyval.step)->session = -1; /* until filled */
+ (yyval.step)->used = false;
+ }
+#line 1336 "specparse.c"
+ break;
+
+ case 16: /* opt_permutation_list: permutation_list */
+#line 162 "specparse.y"
+ {
+ (yyval.ptr_list) = (yyvsp[0].ptr_list);
+ }
+#line 1344 "specparse.c"
+ break;
+
+ case 17: /* opt_permutation_list: %empty */
+#line 166 "specparse.y"
+ {
+ (yyval.ptr_list).elements = NULL;
+ (yyval.ptr_list).nelements = 0;
+ }
+#line 1353 "specparse.c"
+ break;
+
+ case 18: /* permutation_list: permutation_list permutation */
+#line 173 "specparse.y"
+ {
+ (yyval.ptr_list).elements = pg_realloc((yyvsp[-1].ptr_list).elements,
+ ((yyvsp[-1].ptr_list).nelements + 1) * sizeof(void *));
+ (yyval.ptr_list).elements[(yyvsp[-1].ptr_list).nelements] = (yyvsp[0].permutation);
+ (yyval.ptr_list).nelements = (yyvsp[-1].ptr_list).nelements + 1;
+ }
+#line 1364 "specparse.c"
+ break;
+
+ case 19: /* permutation_list: permutation */
+#line 180 "specparse.y"
+ {
+ (yyval.ptr_list).nelements = 1;
+ (yyval.ptr_list).elements = pg_malloc(sizeof(void *));
+ (yyval.ptr_list).elements[0] = (yyvsp[0].permutation);
+ }
+#line 1374 "specparse.c"
+ break;
+
+ case 20: /* permutation: PERMUTATION permutation_step_list */
+#line 190 "specparse.y"
+ {
+ (yyval.permutation) = pg_malloc(sizeof(Permutation));
+ (yyval.permutation)->nsteps = (yyvsp[0].ptr_list).nelements;
+ (yyval.permutation)->steps = (PermutationStep **) (yyvsp[0].ptr_list).elements;
+ }
+#line 1384 "specparse.c"
+ break;
+
+ case 21: /* permutation_step_list: permutation_step_list permutation_step */
+#line 199 "specparse.y"
+ {
+ (yyval.ptr_list).elements = pg_realloc((yyvsp[-1].ptr_list).elements,
+ ((yyvsp[-1].ptr_list).nelements + 1) * sizeof(void *));
+ (yyval.ptr_list).elements[(yyvsp[-1].ptr_list).nelements] = (yyvsp[0].permutationstep);
+ (yyval.ptr_list).nelements = (yyvsp[-1].ptr_list).nelements + 1;
+ }
+#line 1395 "specparse.c"
+ break;
+
+ case 22: /* permutation_step_list: permutation_step */
+#line 206 "specparse.y"
+ {
+ (yyval.ptr_list).nelements = 1;
+ (yyval.ptr_list).elements = pg_malloc(sizeof(void *));
+ (yyval.ptr_list).elements[0] = (yyvsp[0].permutationstep);
+ }
+#line 1405 "specparse.c"
+ break;
+
+ case 23: /* permutation_step: identifier */
+#line 215 "specparse.y"
+ {
+ (yyval.permutationstep) = pg_malloc(sizeof(PermutationStep));
+ (yyval.permutationstep)->name = (yyvsp[0].str);
+ (yyval.permutationstep)->blockers = NULL;
+ (yyval.permutationstep)->nblockers = 0;
+ (yyval.permutationstep)->step = NULL;
+ }
+#line 1417 "specparse.c"
+ break;
+
+ case 24: /* permutation_step: identifier '(' blocker_list ')' */
+#line 223 "specparse.y"
+ {
+ (yyval.permutationstep) = pg_malloc(sizeof(PermutationStep));
+ (yyval.permutationstep)->name = (yyvsp[-3].str);
+ (yyval.permutationstep)->blockers = (PermutationStepBlocker **) (yyvsp[-1].ptr_list).elements;
+ (yyval.permutationstep)->nblockers = (yyvsp[-1].ptr_list).nelements;
+ (yyval.permutationstep)->step = NULL;
+ }
+#line 1429 "specparse.c"
+ break;
+
+ case 25: /* blocker_list: blocker_list ',' blocker */
+#line 234 "specparse.y"
+ {
+ (yyval.ptr_list).elements = pg_realloc((yyvsp[-2].ptr_list).elements,
+ ((yyvsp[-2].ptr_list).nelements + 1) * sizeof(void *));
+ (yyval.ptr_list).elements[(yyvsp[-2].ptr_list).nelements] = (yyvsp[0].blocker);
+ (yyval.ptr_list).nelements = (yyvsp[-2].ptr_list).nelements + 1;
+ }
+#line 1440 "specparse.c"
+ break;
+
+ case 26: /* blocker_list: blocker */
+#line 241 "specparse.y"
+ {
+ (yyval.ptr_list).nelements = 1;
+ (yyval.ptr_list).elements = pg_malloc(sizeof(void *));
+ (yyval.ptr_list).elements[0] = (yyvsp[0].blocker);
+ }
+#line 1450 "specparse.c"
+ break;
+
+ case 27: /* blocker: identifier */
+#line 250 "specparse.y"
+ {
+ (yyval.blocker) = pg_malloc(sizeof(PermutationStepBlocker));
+ (yyval.blocker)->stepname = (yyvsp[0].str);
+ (yyval.blocker)->blocktype = PSB_OTHER_STEP;
+ (yyval.blocker)->num_notices = -1;
+ (yyval.blocker)->step = NULL;
+ (yyval.blocker)->target_notices = -1;
+ }
+#line 1463 "specparse.c"
+ break;
+
+ case 28: /* blocker: identifier NOTICES INTEGER */
+#line 259 "specparse.y"
+ {
+ (yyval.blocker) = pg_malloc(sizeof(PermutationStepBlocker));
+ (yyval.blocker)->stepname = (yyvsp[-2].str);
+ (yyval.blocker)->blocktype = PSB_NUM_NOTICES;
+ (yyval.blocker)->num_notices = (yyvsp[0].integer);
+ (yyval.blocker)->step = NULL;
+ (yyval.blocker)->target_notices = -1;
+ }
+#line 1476 "specparse.c"
+ break;
+
+ case 29: /* blocker: '*' */
+#line 268 "specparse.y"
+ {
+ (yyval.blocker) = pg_malloc(sizeof(PermutationStepBlocker));
+ (yyval.blocker)->stepname = NULL;
+ (yyval.blocker)->blocktype = PSB_ONCE;
+ (yyval.blocker)->num_notices = -1;
+ (yyval.blocker)->step = NULL;
+ (yyval.blocker)->target_notices = -1;
+ }
+#line 1489 "specparse.c"
+ break;
+
+
+#line 1493 "specparse.c"
+
+ default: break;
+ }
+ /* User semantic actions sometimes alter yychar, and that requires
+ that yytoken be updated with the new translation. We take the
+ approach of translating immediately before every use of yytoken.
+ One alternative is translating here after every semantic action,
+ but that translation would be missed if the semantic action invokes
+ YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+ if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an
+ incorrect destructor might then be invoked immediately. In the
+ case of YYERROR or YYBACKUP, subsequent parser actions might lead
+ to an incorrect destructor call or verbose syntax error message
+ before the lookahead is translated. */
+ YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+
+ *++yyvsp = yyval;
+
+ /* Now 'shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+ {
+ const int yylhs = yyr1[yyn] - YYNTOKENS;
+ const int yyi = yypgoto[yylhs] + *yyssp;
+ yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp
+ ? yytable[yyi]
+ : yydefgoto[yylhs]);
+ }
+
+ goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error. |
+`--------------------------------------*/
+yyerrlab:
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar);
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+ yyerror (YY_("syntax error"));
+ }
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse lookahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+ /* Pacify compilers when the user code never invokes YYERROR and the
+ label yyerrorlab therefore never appears in user code. */
+ if (0)
+ YYERROR;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ /* Pop stack until we find a state that shifts the error token. */
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (!yypact_value_is_default (yyn))
+ {
+ yyn += YYSYMBOL_YYerror;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+
+ yydestruct ("Error: popping",
+ YY_ACCESSING_SYMBOL (yystate), yyvsp);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturn;
+
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturn;
+
+
+#if !defined yyoverflow
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here. |
+`-------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (YY_("memory exhausted"));
+ yyresult = 2;
+ goto yyreturn;
+#endif
+
+
+/*-------------------------------------------------------.
+| yyreturn -- parsing is finished, clean up and return. |
+`-------------------------------------------------------*/
+yyreturn:
+ if (yychar != YYEMPTY)
+ {
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = YYTRANSLATE (yychar);
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval);
+ }
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ YY_ACCESSING_SYMBOL (+*yyssp), yyvsp);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+
+ return yyresult;
+}
+
+#line 278 "specparse.y"
+
+
+#include "specscanner.c"
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
new file mode 100644
index 0000000..c25aa1a
--- /dev/null
+++ b/src/test/isolation/specparse.y
@@ -0,0 +1,280 @@
+%{
+/*-------------------------------------------------------------------------
+ *
+ * specparse.y
+ * bison grammar for the isolation test file format
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "isolationtester.h"
+
+
+TestSpec parseresult; /* result of parsing is left here */
+
+%}
+
+%expect 0
+%name-prefix="spec_yy"
+
+%union
+{
+ char *str;
+ int integer;
+ Session *session;
+ Step *step;
+ Permutation *permutation;
+ PermutationStep *permutationstep;
+ PermutationStepBlocker *blocker;
+ struct
+ {
+ void **elements;
+ int nelements;
+ } ptr_list;
+}
+
+%type <ptr_list> setup_list
+%type <str> opt_setup opt_teardown
+%type <str> setup
+%type <ptr_list> step_list session_list permutation_list opt_permutation_list
+%type <ptr_list> permutation_step_list blocker_list
+%type <session> session
+%type <step> step
+%type <permutation> permutation
+%type <permutationstep> permutation_step
+%type <blocker> blocker
+
+%token <str> sqlblock identifier
+%token <integer> INTEGER
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+
+%%
+
+TestSpec:
+ setup_list
+ opt_teardown
+ session_list
+ opt_permutation_list
+ {
+ parseresult.setupsqls = (char **) $1.elements;
+ parseresult.nsetupsqls = $1.nelements;
+ parseresult.teardownsql = $2;
+ parseresult.sessions = (Session **) $3.elements;
+ parseresult.nsessions = $3.nelements;
+ parseresult.permutations = (Permutation **) $4.elements;
+ parseresult.npermutations = $4.nelements;
+ }
+ ;
+
+setup_list:
+ /* EMPTY */
+ {
+ $$.elements = NULL;
+ $$.nelements = 0;
+ }
+ | setup_list setup
+ {
+ $$.elements = pg_realloc($1.elements,
+ ($1.nelements + 1) * sizeof(void *));
+ $$.elements[$1.nelements] = $2;
+ $$.nelements = $1.nelements + 1;
+ }
+ ;
+
+opt_setup:
+ /* EMPTY */ { $$ = NULL; }
+ | setup { $$ = $1; }
+ ;
+
+setup:
+ SETUP sqlblock { $$ = $2; }
+ ;
+
+opt_teardown:
+ /* EMPTY */ { $$ = NULL; }
+ | TEARDOWN sqlblock { $$ = $2; }
+ ;
+
+session_list:
+ session_list session
+ {
+ $$.elements = pg_realloc($1.elements,
+ ($1.nelements + 1) * sizeof(void *));
+ $$.elements[$1.nelements] = $2;
+ $$.nelements = $1.nelements + 1;
+ }
+ | session
+ {
+ $$.nelements = 1;
+ $$.elements = pg_malloc(sizeof(void *));
+ $$.elements[0] = $1;
+ }
+ ;
+
+session:
+ SESSION identifier opt_setup step_list opt_teardown
+ {
+ $$ = pg_malloc(sizeof(Session));
+ $$->name = $2;
+ $$->setupsql = $3;
+ $$->steps = (Step **) $4.elements;
+ $$->nsteps = $4.nelements;
+ $$->teardownsql = $5;
+ }
+ ;
+
+step_list:
+ step_list step
+ {
+ $$.elements = pg_realloc($1.elements,
+ ($1.nelements + 1) * sizeof(void *));
+ $$.elements[$1.nelements] = $2;
+ $$.nelements = $1.nelements + 1;
+ }
+ | step
+ {
+ $$.nelements = 1;
+ $$.elements = pg_malloc(sizeof(void *));
+ $$.elements[0] = $1;
+ }
+ ;
+
+
+step:
+ STEP identifier sqlblock
+ {
+ $$ = pg_malloc(sizeof(Step));
+ $$->name = $2;
+ $$->sql = $3;
+ $$->session = -1; /* until filled */
+ $$->used = false;
+ }
+ ;
+
+
+opt_permutation_list:
+ permutation_list
+ {
+ $$ = $1;
+ }
+ | /* EMPTY */
+ {
+ $$.elements = NULL;
+ $$.nelements = 0;
+ }
+
+permutation_list:
+ permutation_list permutation
+ {
+ $$.elements = pg_realloc($1.elements,
+ ($1.nelements + 1) * sizeof(void *));
+ $$.elements[$1.nelements] = $2;
+ $$.nelements = $1.nelements + 1;
+ }
+ | permutation
+ {
+ $$.nelements = 1;
+ $$.elements = pg_malloc(sizeof(void *));
+ $$.elements[0] = $1;
+ }
+ ;
+
+
+permutation:
+ PERMUTATION permutation_step_list
+ {
+ $$ = pg_malloc(sizeof(Permutation));
+ $$->nsteps = $2.nelements;
+ $$->steps = (PermutationStep **) $2.elements;
+ }
+ ;
+
+permutation_step_list:
+ permutation_step_list permutation_step
+ {
+ $$.elements = pg_realloc($1.elements,
+ ($1.nelements + 1) * sizeof(void *));
+ $$.elements[$1.nelements] = $2;
+ $$.nelements = $1.nelements + 1;
+ }
+ | permutation_step
+ {
+ $$.nelements = 1;
+ $$.elements = pg_malloc(sizeof(void *));
+ $$.elements[0] = $1;
+ }
+ ;
+
+permutation_step:
+ identifier
+ {
+ $$ = pg_malloc(sizeof(PermutationStep));
+ $$->name = $1;
+ $$->blockers = NULL;
+ $$->nblockers = 0;
+ $$->step = NULL;
+ }
+ | identifier '(' blocker_list ')'
+ {
+ $$ = pg_malloc(sizeof(PermutationStep));
+ $$->name = $1;
+ $$->blockers = (PermutationStepBlocker **) $3.elements;
+ $$->nblockers = $3.nelements;
+ $$->step = NULL;
+ }
+ ;
+
+blocker_list:
+ blocker_list ',' blocker
+ {
+ $$.elements = pg_realloc($1.elements,
+ ($1.nelements + 1) * sizeof(void *));
+ $$.elements[$1.nelements] = $3;
+ $$.nelements = $1.nelements + 1;
+ }
+ | blocker
+ {
+ $$.nelements = 1;
+ $$.elements = pg_malloc(sizeof(void *));
+ $$.elements[0] = $1;
+ }
+ ;
+
+blocker:
+ identifier
+ {
+ $$ = pg_malloc(sizeof(PermutationStepBlocker));
+ $$->stepname = $1;
+ $$->blocktype = PSB_OTHER_STEP;
+ $$->num_notices = -1;
+ $$->step = NULL;
+ $$->target_notices = -1;
+ }
+ | identifier NOTICES INTEGER
+ {
+ $$ = pg_malloc(sizeof(PermutationStepBlocker));
+ $$->stepname = $1;
+ $$->blocktype = PSB_NUM_NOTICES;
+ $$->num_notices = $3;
+ $$->step = NULL;
+ $$->target_notices = -1;
+ }
+ | '*'
+ {
+ $$ = pg_malloc(sizeof(PermutationStepBlocker));
+ $$->stepname = NULL;
+ $$->blocktype = PSB_ONCE;
+ $$->num_notices = -1;
+ $$->step = NULL;
+ $$->target_notices = -1;
+ }
+ ;
+
+%%
+
+#include "specscanner.c"
diff --git a/src/test/isolation/specs/aborted-keyrevoke.spec b/src/test/isolation/specs/aborted-keyrevoke.spec
new file mode 100644
index 0000000..4f6f902
--- /dev/null
+++ b/src/test/isolation/specs/aborted-keyrevoke.spec
@@ -0,0 +1,46 @@
+# When a tuple that has been updated is locked, the locking command
+# should traverse the update chain; thus, a DELETE should not be able
+# to proceed until the lock has been released.
+
+setup
+{
+ CREATE TABLE foo (
+ key int PRIMARY KEY,
+ value int
+ );
+
+ INSERT INTO foo VALUES (1, 1);
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+setup { BEGIN; }
+step s1s { SAVEPOINT f; }
+step s1u { UPDATE foo SET key = 2; } # obtain KEY REVOKE
+step s1r { ROLLBACK TO f; } # lose KEY REVOKE
+step s1l { SELECT * FROM foo FOR KEY SHARE; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step s2l { SELECT * FROM foo FOR KEY SHARE; }
+step s2c { COMMIT; }
+
+permutation s1s s1u s1r s1l s1c s2l s2c
+permutation s1s s1u s1r s1l s2l s1c s2c
+permutation s1s s1u s1r s1l s2l s2c s1c
+permutation s1s s1u s1r s2l s1l s1c s2c
+permutation s1s s1u s1r s2l s1l s2c s1c
+permutation s1s s1u s1r s2l s2c s1l s1c
+permutation s1s s1u s2l s1r s1l s1c s2c
+permutation s1s s1u s2l s1r s1l s2c s1c
+permutation s1s s1u s2l s1r s2c s1l s1c
+permutation s1s s2l s1u s2c s1r s1l s1c
+permutation s1s s2l s2c s1u s1r s1l s1c
+permutation s2l s1s s1u s2c s1r s1l s1c
+permutation s2l s1s s2c s1u s1r s1l s1c
+permutation s2l s2c s1s s1u s1r s1l s1c
diff --git a/src/test/isolation/specs/alter-table-1.spec b/src/test/isolation/specs/alter-table-1.spec
new file mode 100644
index 0000000..dfd0ce7
--- /dev/null
+++ b/src/test/isolation/specs/alter-table-1.spec
@@ -0,0 +1,170 @@
+# ALTER TABLE - Add and Validate constraint with concurrent writes
+#
+# VALIDATE allows a minimum of ShareUpdateExclusiveLock
+# so we mix reads with it to see what works or waits
+
+setup
+{
+ CREATE TABLE a (i int PRIMARY KEY);
+ CREATE TABLE b (a_id int);
+ INSERT INTO a VALUES (0), (1), (2), (3);
+ INSERT INTO b SELECT generate_series(1,1000) % 4;
+}
+
+teardown
+{
+ DROP TABLE a, b;
+}
+
+session s1
+step s1 { BEGIN; }
+step at1 { ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; }
+step sc1 { COMMIT; }
+step s2 { BEGIN; }
+step at2 { ALTER TABLE b VALIDATE CONSTRAINT bfk; }
+step sc2 { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step rx1 { SELECT * FROM b WHERE a_id = 1 LIMIT 1; }
+step wx { INSERT INTO b VALUES (0); }
+step rx3 { SELECT * FROM b WHERE a_id = 3 LIMIT 3; }
+step c2 { COMMIT; }
+
+permutation s1 at1 sc1 s2 at2 sc2 rx1 wx rx3 c2
+permutation s1 at1 sc1 s2 at2 rx1 sc2 wx rx3 c2
+permutation s1 at1 sc1 s2 at2 rx1 wx sc2 rx3 c2
+permutation s1 at1 sc1 s2 at2 rx1 wx rx3 sc2 c2
+permutation s1 at1 sc1 s2 at2 rx1 wx rx3 c2 sc2
+permutation s1 at1 sc1 s2 rx1 at2 sc2 wx rx3 c2
+permutation s1 at1 sc1 s2 rx1 at2 wx sc2 rx3 c2
+permutation s1 at1 sc1 s2 rx1 at2 wx rx3 sc2 c2
+permutation s1 at1 sc1 s2 rx1 at2 wx rx3 c2 sc2
+permutation s1 at1 sc1 s2 rx1 wx at2 sc2 rx3 c2
+permutation s1 at1 sc1 s2 rx1 wx at2 rx3 sc2 c2
+permutation s1 at1 sc1 s2 rx1 wx at2 rx3 c2 sc2
+permutation s1 at1 sc1 s2 rx1 wx rx3 at2 sc2 c2
+permutation s1 at1 sc1 s2 rx1 wx rx3 at2 c2 sc2
+permutation s1 at1 sc1 s2 rx1 wx rx3 c2 at2 sc2
+permutation s1 at1 sc1 rx1 s2 at2 sc2 wx rx3 c2
+permutation s1 at1 sc1 rx1 s2 at2 wx sc2 rx3 c2
+permutation s1 at1 sc1 rx1 s2 at2 wx rx3 sc2 c2
+permutation s1 at1 sc1 rx1 s2 at2 wx rx3 c2 sc2
+permutation s1 at1 sc1 rx1 s2 wx at2 sc2 rx3 c2
+permutation s1 at1 sc1 rx1 s2 wx at2 rx3 sc2 c2
+permutation s1 at1 sc1 rx1 s2 wx at2 rx3 c2 sc2
+permutation s1 at1 sc1 rx1 s2 wx rx3 at2 sc2 c2
+permutation s1 at1 sc1 rx1 s2 wx rx3 at2 c2 sc2
+permutation s1 at1 sc1 rx1 s2 wx rx3 c2 at2 sc2
+permutation s1 at1 sc1 rx1 wx s2 at2 sc2 rx3 c2
+permutation s1 at1 sc1 rx1 wx s2 at2 rx3 sc2 c2
+permutation s1 at1 sc1 rx1 wx s2 at2 rx3 c2 sc2
+permutation s1 at1 sc1 rx1 wx s2 rx3 at2 sc2 c2
+permutation s1 at1 sc1 rx1 wx s2 rx3 at2 c2 sc2
+permutation s1 at1 sc1 rx1 wx s2 rx3 c2 at2 sc2
+permutation s1 at1 sc1 rx1 wx rx3 s2 at2 sc2 c2
+permutation s1 at1 sc1 rx1 wx rx3 s2 at2 c2 sc2
+permutation s1 at1 sc1 rx1 wx rx3 s2 c2 at2 sc2
+permutation s1 at1 sc1 rx1 wx rx3 c2 s2 at2 sc2
+permutation s1 at1 rx1 sc1 s2 at2 sc2 wx rx3 c2
+permutation s1 at1 rx1 sc1 s2 at2 wx sc2 rx3 c2
+permutation s1 at1 rx1 sc1 s2 at2 wx rx3 sc2 c2
+permutation s1 at1 rx1 sc1 s2 at2 wx rx3 c2 sc2
+permutation s1 at1 rx1 sc1 s2 wx at2 sc2 rx3 c2
+permutation s1 at1 rx1 sc1 s2 wx at2 rx3 sc2 c2
+permutation s1 at1 rx1 sc1 s2 wx at2 rx3 c2 sc2
+permutation s1 at1 rx1 sc1 s2 wx rx3 at2 sc2 c2
+permutation s1 at1 rx1 sc1 s2 wx rx3 at2 c2 sc2
+permutation s1 at1 rx1 sc1 s2 wx rx3 c2 at2 sc2
+permutation s1 at1 rx1 sc1 wx s2 at2 sc2 rx3 c2
+permutation s1 at1 rx1 sc1 wx s2 at2 rx3 sc2 c2
+permutation s1 at1 rx1 sc1 wx s2 at2 rx3 c2 sc2
+permutation s1 at1 rx1 sc1 wx s2 rx3 at2 sc2 c2
+permutation s1 at1 rx1 sc1 wx s2 rx3 at2 c2 sc2
+permutation s1 at1 rx1 sc1 wx s2 rx3 c2 at2 sc2
+permutation s1 at1 rx1 sc1 wx rx3 s2 at2 sc2 c2
+permutation s1 at1 rx1 sc1 wx rx3 s2 at2 c2 sc2
+permutation s1 at1 rx1 sc1 wx rx3 s2 c2 at2 sc2
+permutation s1 at1 rx1 sc1 wx rx3 c2 s2 at2 sc2
+permutation s1 at1 rx1 wx sc1 s2 at2 sc2 rx3 c2
+permutation s1 at1 rx1 wx sc1 s2 at2 rx3 sc2 c2
+permutation s1 at1 rx1 wx sc1 s2 at2 rx3 c2 sc2
+permutation s1 at1 rx1 wx sc1 s2 rx3 at2 sc2 c2
+permutation s1 at1 rx1 wx sc1 s2 rx3 at2 c2 sc2
+permutation s1 at1 rx1 wx sc1 s2 rx3 c2 at2 sc2
+permutation s1 at1 rx1 wx sc1 rx3 s2 at2 sc2 c2
+permutation s1 at1 rx1 wx sc1 rx3 s2 at2 c2 sc2
+permutation s1 at1 rx1 wx sc1 rx3 s2 c2 at2 sc2
+permutation s1 at1 rx1 wx sc1 rx3 c2 s2 at2 sc2
+permutation s1 rx1 at1 sc1 s2 at2 sc2 wx rx3 c2
+permutation s1 rx1 at1 sc1 s2 at2 wx sc2 rx3 c2
+permutation s1 rx1 at1 sc1 s2 at2 wx rx3 sc2 c2
+permutation s1 rx1 at1 sc1 s2 at2 wx rx3 c2 sc2
+permutation s1 rx1 at1 sc1 s2 wx at2 sc2 rx3 c2
+permutation s1 rx1 at1 sc1 s2 wx at2 rx3 sc2 c2
+permutation s1 rx1 at1 sc1 s2 wx at2 rx3 c2 sc2
+permutation s1 rx1 at1 sc1 s2 wx rx3 at2 sc2 c2
+permutation s1 rx1 at1 sc1 s2 wx rx3 at2 c2 sc2
+permutation s1 rx1 at1 sc1 s2 wx rx3 c2 at2 sc2
+permutation s1 rx1 at1 sc1 wx s2 at2 sc2 rx3 c2
+permutation s1 rx1 at1 sc1 wx s2 at2 rx3 sc2 c2
+permutation s1 rx1 at1 sc1 wx s2 at2 rx3 c2 sc2
+permutation s1 rx1 at1 sc1 wx s2 rx3 at2 sc2 c2
+permutation s1 rx1 at1 sc1 wx s2 rx3 at2 c2 sc2
+permutation s1 rx1 at1 sc1 wx s2 rx3 c2 at2 sc2
+permutation s1 rx1 at1 sc1 wx rx3 s2 at2 sc2 c2
+permutation s1 rx1 at1 sc1 wx rx3 s2 at2 c2 sc2
+permutation s1 rx1 at1 sc1 wx rx3 s2 c2 at2 sc2
+permutation s1 rx1 at1 sc1 wx rx3 c2 s2 at2 sc2
+permutation s1 rx1 at1 wx sc1 s2 at2 sc2 rx3 c2
+permutation s1 rx1 at1 wx sc1 s2 at2 rx3 sc2 c2
+permutation s1 rx1 at1 wx sc1 s2 at2 rx3 c2 sc2
+permutation s1 rx1 at1 wx sc1 s2 rx3 at2 sc2 c2
+permutation s1 rx1 at1 wx sc1 s2 rx3 at2 c2 sc2
+permutation s1 rx1 at1 wx sc1 s2 rx3 c2 at2 sc2
+permutation s1 rx1 at1 wx sc1 rx3 s2 at2 sc2 c2
+permutation s1 rx1 at1 wx sc1 rx3 s2 at2 c2 sc2
+permutation s1 rx1 at1 wx sc1 rx3 s2 c2 at2 sc2
+permutation s1 rx1 at1 wx sc1 rx3 c2 s2 at2 sc2
+permutation s1 rx1 wx at1 rx3 c2 sc1 s2 at2 sc2
+permutation s1 rx1 wx rx3 at1 c2 sc1 s2 at2 sc2
+permutation s1 rx1 wx rx3 c2 at1 sc1 s2 at2 sc2
+permutation rx1 s1 at1 sc1 s2 at2 sc2 wx rx3 c2
+permutation rx1 s1 at1 sc1 s2 at2 wx sc2 rx3 c2
+permutation rx1 s1 at1 sc1 s2 at2 wx rx3 sc2 c2
+permutation rx1 s1 at1 sc1 s2 at2 wx rx3 c2 sc2
+permutation rx1 s1 at1 sc1 s2 wx at2 sc2 rx3 c2
+permutation rx1 s1 at1 sc1 s2 wx at2 rx3 sc2 c2
+permutation rx1 s1 at1 sc1 s2 wx at2 rx3 c2 sc2
+permutation rx1 s1 at1 sc1 s2 wx rx3 at2 sc2 c2
+permutation rx1 s1 at1 sc1 s2 wx rx3 at2 c2 sc2
+permutation rx1 s1 at1 sc1 s2 wx rx3 c2 at2 sc2
+permutation rx1 s1 at1 sc1 wx s2 at2 sc2 rx3 c2
+permutation rx1 s1 at1 sc1 wx s2 at2 rx3 sc2 c2
+permutation rx1 s1 at1 sc1 wx s2 at2 rx3 c2 sc2
+permutation rx1 s1 at1 sc1 wx s2 rx3 at2 sc2 c2
+permutation rx1 s1 at1 sc1 wx s2 rx3 at2 c2 sc2
+permutation rx1 s1 at1 sc1 wx s2 rx3 c2 at2 sc2
+permutation rx1 s1 at1 sc1 wx rx3 s2 at2 sc2 c2
+permutation rx1 s1 at1 sc1 wx rx3 s2 at2 c2 sc2
+permutation rx1 s1 at1 sc1 wx rx3 s2 c2 at2 sc2
+permutation rx1 s1 at1 sc1 wx rx3 c2 s2 at2 sc2
+permutation rx1 s1 at1 wx sc1 s2 at2 sc2 rx3 c2
+permutation rx1 s1 at1 wx sc1 s2 at2 rx3 sc2 c2
+permutation rx1 s1 at1 wx sc1 s2 at2 rx3 c2 sc2
+permutation rx1 s1 at1 wx sc1 s2 rx3 at2 sc2 c2
+permutation rx1 s1 at1 wx sc1 s2 rx3 at2 c2 sc2
+permutation rx1 s1 at1 wx sc1 s2 rx3 c2 at2 sc2
+permutation rx1 s1 at1 wx sc1 rx3 s2 at2 sc2 c2
+permutation rx1 s1 at1 wx sc1 rx3 s2 at2 c2 sc2
+permutation rx1 s1 at1 wx sc1 rx3 s2 c2 at2 sc2
+permutation rx1 s1 at1 wx sc1 rx3 c2 s2 at2 sc2
+permutation rx1 s1 wx at1 rx3 c2 sc1 s2 at2 sc2
+permutation rx1 s1 wx rx3 at1 c2 sc1 s2 at2 sc2
+permutation rx1 s1 wx rx3 c2 at1 sc1 s2 at2 sc2
+permutation rx1 wx s1 at1 rx3 c2 sc1 s2 at2 sc2
+permutation rx1 wx s1 rx3 at1 c2 sc1 s2 at2 sc2
+permutation rx1 wx s1 rx3 c2 at1 sc1 s2 at2 sc2
+permutation rx1 wx rx3 s1 at1 c2 sc1 s2 at2 sc2
+permutation rx1 wx rx3 s1 c2 at1 sc1 s2 at2 sc2
+permutation rx1 wx rx3 c2 s1 at1 sc1 s2 at2 sc2
diff --git a/src/test/isolation/specs/alter-table-2.spec b/src/test/isolation/specs/alter-table-2.spec
new file mode 100644
index 0000000..a3e3131
--- /dev/null
+++ b/src/test/isolation/specs/alter-table-2.spec
@@ -0,0 +1,79 @@
+# ALTER TABLE - Add foreign keys with concurrent reads
+#
+# ADD CONSTRAINT uses ShareRowExclusiveLock so we mix writes with it
+# to see what works or waits.
+
+setup
+{
+ CREATE TABLE a (i int PRIMARY KEY);
+ CREATE TABLE b (a_id int);
+ INSERT INTO a VALUES (0), (1), (2), (3);
+ INSERT INTO b SELECT generate_series(1,1000) % 4;
+}
+
+teardown
+{
+ DROP TABLE a, b;
+}
+
+session s1
+step s1a { BEGIN; }
+step s1b { ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; }
+step s1c { COMMIT; }
+
+session s2
+step s2a { BEGIN; }
+step s2b { SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE; }
+step s2c { SELECT * FROM b WHERE a_id = 3 LIMIT 1 FOR UPDATE; }
+step s2d { INSERT INTO b VALUES (0); }
+step s2e { INSERT INTO a VALUES (4); }
+step s2f { COMMIT; }
+
+permutation s1a s1b s1c s2a s2b s2c s2d s2e s2f
+permutation s1a s1b s2a s1c s2b s2c s2d s2e s2f
+permutation s1a s1b s2a s2b s1c s2c s2d s2e s2f
+permutation s1a s1b s2a s2b s2c s1c s2d s2e s2f
+permutation s1a s1b s2a s2b s2c s2d s1c s2e s2f
+permutation s1a s2a s1b s1c s2b s2c s2d s2e s2f
+permutation s1a s2a s1b s2b s1c s2c s2d s2e s2f
+permutation s1a s2a s1b s2b s2c s1c s2d s2e s2f
+permutation s1a s2a s1b s2b s2c s2d s1c s2e s2f
+permutation s1a s2a s2b s1b s1c s2c s2d s2e s2f
+permutation s1a s2a s2b s1b s2c s1c s2d s2e s2f
+permutation s1a s2a s2b s1b s2c s2d s1c s2e s2f
+permutation s1a s2a s2b s2c s1b s1c s2d s2e s2f
+permutation s1a s2a s2b s2c s1b s2d s1c s2e s2f
+permutation s1a s2a s2b s2c s2d s1b s2e s2f s1c
+permutation s1a s2a s2b s2c s2d s2e s1b s2f s1c
+permutation s1a s2a s2b s2c s2d s2e s2f s1b s1c
+permutation s2a s1a s1b s1c s2b s2c s2d s2e s2f
+permutation s2a s1a s1b s2b s1c s2c s2d s2e s2f
+permutation s2a s1a s1b s2b s2c s1c s2d s2e s2f
+permutation s2a s1a s1b s2b s2c s2d s1c s2e s2f
+permutation s2a s1a s2b s1b s1c s2c s2d s2e s2f
+permutation s2a s1a s2b s1b s2c s1c s2d s2e s2f
+permutation s2a s1a s2b s1b s2c s2d s1c s2e s2f
+permutation s2a s1a s2b s2c s1b s1c s2d s2e s2f
+permutation s2a s1a s2b s2c s1b s2d s1c s2e s2f
+permutation s2a s1a s2b s2c s2d s1b s2e s2f s1c
+permutation s2a s1a s2b s2c s2d s2e s1b s2f s1c
+permutation s2a s1a s2b s2c s2d s2e s2f s1b s1c
+permutation s2a s2b s1a s1b s1c s2c s2d s2e s2f
+permutation s2a s2b s1a s1b s2c s1c s2d s2e s2f
+permutation s2a s2b s1a s1b s2c s2d s1c s2e s2f
+permutation s2a s2b s1a s2c s1b s1c s2d s2e s2f
+permutation s2a s2b s1a s2c s1b s2d s1c s2e s2f
+permutation s2a s2b s1a s2c s2d s1b s2e s2f s1c
+permutation s2a s2b s1a s2c s2d s2e s1b s2f s1c
+permutation s2a s2b s1a s2c s2d s2e s2f s1b s1c
+permutation s2a s2b s2c s1a s1b s1c s2d s2e s2f
+permutation s2a s2b s2c s1a s1b s2d s1c s2e s2f
+permutation s2a s2b s2c s1a s2d s1b s2e s2f s1c
+permutation s2a s2b s2c s1a s2d s2e s1b s2f s1c
+permutation s2a s2b s2c s1a s2d s2e s2f s1b s1c
+permutation s2a s2b s2c s2d s1a s1b s2e s2f s1c
+permutation s2a s2b s2c s2d s1a s2e s1b s2f s1c
+permutation s2a s2b s2c s2d s1a s2e s2f s1b s1c
+permutation s2a s2b s2c s2d s2e s1a s1b s2f s1c
+permutation s2a s2b s2c s2d s2e s1a s2f s1b s1c
+permutation s2a s2b s2c s2d s2e s2f s1a s1b s1c
diff --git a/src/test/isolation/specs/alter-table-3.spec b/src/test/isolation/specs/alter-table-3.spec
new file mode 100644
index 0000000..f70d9c0
--- /dev/null
+++ b/src/test/isolation/specs/alter-table-3.spec
@@ -0,0 +1,79 @@
+# ALTER TABLE - Enable and disable triggers with concurrent reads
+#
+# ENABLE/DISABLE TRIGGER uses ShareRowExclusiveLock so we mix writes with
+# it to see what works or waits.
+
+setup
+{
+ CREATE TABLE a (i int PRIMARY KEY);
+ INSERT INTO a VALUES (0), (1), (2), (3);
+ CREATE FUNCTION f() RETURNS TRIGGER LANGUAGE plpgsql AS 'BEGIN RETURN NULL; END;';
+ CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f();
+}
+
+teardown
+{
+ DROP TABLE a;
+ DROP FUNCTION f();
+}
+
+session s1
+step s1a { BEGIN; }
+step s1b { ALTER TABLE a DISABLE TRIGGER t; }
+step s1c { ALTER TABLE a ENABLE TRIGGER t; }
+step s1d { COMMIT; }
+
+session s2
+step s2a { BEGIN; }
+step s2b { SELECT * FROM a WHERE i = 1 LIMIT 1 FOR UPDATE; }
+step s2c { INSERT INTO a VALUES (0); }
+step s2d { COMMIT; }
+
+permutation s1a s1b s1c s1d s2a s2b s2c s2d
+permutation s1a s1b s1c s2a s1d s2b s2c s2d
+permutation s1a s1b s1c s2a s2b s1d s2c s2d
+permutation s1a s1b s1c s2a s2b s2c s1d s2d
+permutation s1a s1b s2a s1c s1d s2b s2c s2d
+permutation s1a s1b s2a s1c s2b s1d s2c s2d
+permutation s1a s1b s2a s1c s2b s2c s1d s2d
+permutation s1a s1b s2a s2b s1c s1d s2c s2d
+permutation s1a s1b s2a s2b s1c s2c s1d s2d
+permutation s1a s1b s2a s2b s2c s1c s1d s2d
+permutation s1a s2a s1b s1c s1d s2b s2c s2d
+permutation s1a s2a s1b s1c s2b s1d s2c s2d
+permutation s1a s2a s1b s1c s2b s2c s1d s2d
+permutation s1a s2a s1b s2b s1c s1d s2c s2d
+permutation s1a s2a s1b s2b s1c s2c s1d s2d
+permutation s1a s2a s1b s2b s2c s1c s1d s2d
+permutation s1a s2a s2b s1b s1c s1d s2c s2d
+permutation s1a s2a s2b s1b s1c s2c s1d s2d
+permutation s1a s2a s2b s1b s2c s1c s1d s2d
+permutation s1a s2a s2b s2c s1b s1c s1d s2d
+permutation s1a s2a s2b s2c s1b s1c s2d s1d
+permutation s1a s2a s2b s2c s1b s2d s1c s1d
+permutation s1a s2a s2b s2c s2d s1b s1c s1d
+permutation s2a s1a s1b s1c s1d s2b s2c s2d
+permutation s2a s1a s1b s1c s2b s1d s2c s2d
+permutation s2a s1a s1b s1c s2b s2c s1d s2d
+permutation s2a s1a s1b s2b s1c s1d s2c s2d
+permutation s2a s1a s1b s2b s1c s2c s1d s2d
+permutation s2a s1a s1b s2b s2c s1c s1d s2d
+permutation s2a s1a s2b s1b s1c s1d s2c s2d
+permutation s2a s1a s2b s1b s1c s2c s1d s2d
+permutation s2a s1a s2b s1b s2c s1c s1d s2d
+permutation s2a s1a s2b s2c s1b s1c s1d s2d
+permutation s2a s1a s2b s2c s1b s1c s2d s1d
+permutation s2a s1a s2b s2c s1b s2d s1c s1d
+permutation s2a s1a s2b s2c s2d s1b s1c s1d
+permutation s2a s2b s1a s1b s1c s1d s2c s2d
+permutation s2a s2b s1a s1b s1c s2c s1d s2d
+permutation s2a s2b s1a s1b s2c s1c s1d s2d
+permutation s2a s2b s1a s2c s1b s1c s1d s2d
+permutation s2a s2b s1a s2c s1b s1c s2d s1d
+permutation s2a s2b s1a s2c s1b s2d s1c s1d
+permutation s2a s2b s1a s2c s2d s1b s1c s1d
+permutation s2a s2b s2c s1a s1b s1c s1d s2d
+permutation s2a s2b s2c s1a s1b s1c s2d s1d
+permutation s2a s2b s2c s1a s1b s2d s1c s1d
+permutation s2a s2b s2c s1a s2d s1b s1c s1d
+permutation s2a s2b s2c s2d s1a s1b s1c s1d
diff --git a/src/test/isolation/specs/alter-table-4.spec b/src/test/isolation/specs/alter-table-4.spec
new file mode 100644
index 0000000..f143b79
--- /dev/null
+++ b/src/test/isolation/specs/alter-table-4.spec
@@ -0,0 +1,37 @@
+# ALTER TABLE - Add and remove inheritance with concurrent reads
+
+setup
+{
+ CREATE TABLE p (a integer);
+ INSERT INTO p VALUES(1);
+ CREATE TABLE c1 () INHERITS (p);
+ INSERT INTO c1 VALUES(10);
+ CREATE TABLE c2 (a integer);
+ INSERT INTO c2 VALUES(100);
+}
+
+teardown
+{
+ DROP TABLE IF EXISTS c1, c2, p;
+}
+
+session s1
+step s1b { BEGIN; }
+step s1delc1 { ALTER TABLE c1 NO INHERIT p; }
+step s1modc1a { ALTER TABLE c1 ALTER COLUMN a TYPE float; }
+step s1addc2 { ALTER TABLE c2 INHERIT p; }
+step s1dropc1 { DROP TABLE c1; }
+step s1c { COMMIT; }
+
+session s2
+step s2sel { SELECT SUM(a) FROM p; }
+
+# NO INHERIT will not be visible to concurrent select,
+# since we identify children before locking them
+permutation s1b s1delc1 s2sel s1c s2sel
+# adding inheritance likewise is not seen if s1 commits after s2 locks p
+permutation s1b s1delc1 s1addc2 s2sel s1c s2sel
+# but we do cope with DROP on a child table
+permutation s1b s1dropc1 s2sel s1c s2sel
+# this case currently results in an error; doesn't seem worth preventing
+permutation s1b s1delc1 s1modc1a s2sel s1c s2sel
diff --git a/src/test/isolation/specs/async-notify.spec b/src/test/isolation/specs/async-notify.spec
new file mode 100644
index 0000000..0b8cfd9
--- /dev/null
+++ b/src/test/isolation/specs/async-notify.spec
@@ -0,0 +1,84 @@
+# Tests for LISTEN/NOTIFY
+
+# Most of these tests use only the "notifier" session and hence exercise only
+# self-notifies, which are convenient because they minimize timing concerns.
+# Note we assume that each step is delivered to the backend as a single Query
+# message so it will run as one transaction.
+
+session notifier
+step listenc { LISTEN c1; LISTEN c2; }
+step notify1 { NOTIFY c1; }
+step notify2 { NOTIFY c2, 'payload'; }
+step notify3 { NOTIFY c3, 'payload3'; } # not listening to c3
+step notifyf { SELECT pg_notify('c2', NULL); }
+step notifyd1 { NOTIFY c2, 'payload'; NOTIFY c1; NOTIFY "c2", 'payload'; }
+step notifyd2 { NOTIFY c1; NOTIFY c1; NOTIFY c1, 'p1'; NOTIFY c1, 'p2'; }
+step notifys1 {
+ BEGIN;
+ NOTIFY c1, 'payload'; NOTIFY "c2", 'payload';
+ NOTIFY c1, 'payload'; NOTIFY "c2", 'payload';
+ SAVEPOINT s1;
+ NOTIFY c1, 'payload'; NOTIFY "c2", 'payload';
+ NOTIFY c1, 'payloads'; NOTIFY "c2", 'payloads';
+ NOTIFY c1, 'payload'; NOTIFY "c2", 'payload';
+ NOTIFY c1, 'payloads'; NOTIFY "c2", 'payloads';
+ RELEASE SAVEPOINT s1;
+ SAVEPOINT s2;
+ NOTIFY c1, 'rpayload'; NOTIFY "c2", 'rpayload';
+ NOTIFY c1, 'rpayloads'; NOTIFY "c2", 'rpayloads';
+ NOTIFY c1, 'rpayload'; NOTIFY "c2", 'rpayload';
+ NOTIFY c1, 'rpayloads'; NOTIFY "c2", 'rpayloads';
+ ROLLBACK TO SAVEPOINT s2;
+ COMMIT;
+}
+step usage { SELECT pg_notification_queue_usage() > 0 AS nonzero; }
+step bignotify { SELECT count(pg_notify('c1', s::text)) FROM generate_series(1, 1000) s; }
+teardown { UNLISTEN *; }
+
+# The listener session is used for cross-backend notify checks.
+
+session listener
+step llisten { LISTEN c1; LISTEN c2; }
+step lcheck { SELECT 1 AS x; }
+step lbegin { BEGIN; }
+step lbegins { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step lcommit { COMMIT; }
+teardown { UNLISTEN *; }
+
+# In some tests we need a second listener, just to block the queue.
+
+session listener2
+step l2listen { LISTEN c1; }
+step l2begin { BEGIN; }
+step l2commit { COMMIT; }
+step l2stop { UNLISTEN *; }
+
+
+# Trivial cases.
+permutation listenc notify1 notify2 notify3 notifyf
+
+# Check simple and less-simple deduplication.
+permutation listenc notifyd1 notifyd2 notifys1
+
+# Cross-backend notification delivery. We use a "select 1" to force the
+# listener session to check for notifies. In principle we could just wait
+# for delivery, but that would require extra support in isolationtester
+# and might have portability-of-timing issues.
+permutation llisten notify1 notify2 notify3 notifyf lcheck
+
+# Again, with local delivery too.
+permutation listenc llisten notify1 notify2 notify3 notifyf lcheck
+
+# Check for bug when initial listen is only action in a serializable xact,
+# and notify queue is not empty
+permutation l2listen l2begin notify1 lbegins llisten lcommit l2commit l2stop
+
+# Verify that pg_notification_queue_usage correctly reports a non-zero result,
+# after submitting notifications while another connection is listening for
+# those notifications and waiting inside an active transaction. We have to
+# fill a page of the notify SLRU to make this happen, which is a good deal
+# of traffic. To not bloat the expected output, we intentionally don't
+# commit the listener's transaction, so that it never reports these events.
+# Hence, this should be the last test in this script.
+
+permutation llisten lbegin usage bignotify usage
diff --git a/src/test/isolation/specs/classroom-scheduling.spec b/src/test/isolation/specs/classroom-scheduling.spec
new file mode 100644
index 0000000..770715b
--- /dev/null
+++ b/src/test/isolation/specs/classroom-scheduling.spec
@@ -0,0 +1,29 @@
+# Classroom Scheduling test
+#
+# Ensure that the classroom is not scheduled more than once
+# for any moment in time.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE room_reservation (room_id text NOT NULL, start_time timestamp with time zone NOT NULL, end_time timestamp with time zone NOT NULL, description text NOT NULL, CONSTRAINT room_reservation_pkey PRIMARY KEY (room_id, start_time));
+ INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 10:00', TIMESTAMP WITH TIME ZONE '2010-04-01 11:00', 'Bob');
+}
+
+teardown
+{
+ DROP TABLE room_reservation;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rx1 { SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; }
+step wy1 { INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step ry2 { SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; }
+step wx2 { UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; }
+step c2 { COMMIT; }
diff --git a/src/test/isolation/specs/create-trigger.spec b/src/test/isolation/specs/create-trigger.spec
new file mode 100644
index 0000000..9d4710c
--- /dev/null
+++ b/src/test/isolation/specs/create-trigger.spec
@@ -0,0 +1,54 @@
+# CREATE TRIGGER - Add trigger with concurrent reads
+#
+# CREATE TRIGGER uses ShareRowExclusiveLock so we mix writes with it
+# to see what works or waits.
+
+setup
+{
+ CREATE TABLE a (i int);
+ CREATE FUNCTION f() RETURNS TRIGGER LANGUAGE plpgsql AS 'BEGIN RETURN NULL; END;';
+ INSERT INTO a VALUES (0), (1), (2), (3);
+}
+
+teardown
+{
+ DROP TABLE a;
+ DROP FUNCTION f();
+}
+
+session s1
+step s1a { BEGIN; }
+step s1b { CREATE TRIGGER t AFTER UPDATE ON a EXECUTE PROCEDURE f(); }
+step s1c { COMMIT; }
+
+session s2
+step s2a { BEGIN; }
+step s2b { SELECT * FROM a WHERE i = 1 FOR UPDATE; }
+step s2c { UPDATE a SET i = 4 WHERE i = 3; }
+step s2d { COMMIT; }
+
+permutation s1a s1b s1c s2a s2b s2c s2d
+permutation s1a s1b s2a s1c s2b s2c s2d
+permutation s1a s1b s2a s2b s1c s2c s2d
+permutation s1a s1b s2a s2b s2c s1c s2d
+permutation s1a s2a s1b s1c s2b s2c s2d
+permutation s1a s2a s1b s2b s1c s2c s2d
+permutation s1a s2a s1b s2b s2c s1c s2d
+permutation s1a s2a s2b s1b s1c s2c s2d
+permutation s1a s2a s2b s1b s2c s1c s2d
+permutation s1a s2a s2b s2c s1b s2d s1c
+permutation s1a s2a s2b s2c s2d s1b s1c
+permutation s2a s1a s1b s1c s2b s2c s2d
+permutation s2a s1a s1b s2b s1c s2c s2d
+permutation s2a s1a s1b s2b s2c s1c s2d
+permutation s2a s1a s2b s1b s1c s2c s2d
+permutation s2a s1a s2b s1b s2c s1c s2d
+permutation s2a s1a s2b s2c s1b s2d s1c
+permutation s2a s1a s2b s2c s2d s1b s1c
+permutation s2a s2b s1a s1b s1c s2c s2d
+permutation s2a s2b s1a s1b s2c s1c s2d
+permutation s2a s2b s1a s2c s1b s2d s1c
+permutation s2a s2b s1a s2c s2d s1b s1c
+permutation s2a s2b s2c s1a s1b s2d s1c
+permutation s2a s2b s2c s1a s2d s1b s1c
+permutation s2a s2b s2c s2d s1a s1b s1c
diff --git a/src/test/isolation/specs/deadlock-hard.spec b/src/test/isolation/specs/deadlock-hard.spec
new file mode 100644
index 0000000..60bedca
--- /dev/null
+++ b/src/test/isolation/specs/deadlock-hard.spec
@@ -0,0 +1,79 @@
+# This is a straightforward deadlock scenario. Since it involves more than
+# two processes, the main lock detector will find the problem and rollback
+# the session that first discovers it. Set deadlock_timeout in each session
+# so that it's predictable which session fails.
+
+setup
+{
+ CREATE TABLE a1 ();
+ CREATE TABLE a2 ();
+ CREATE TABLE a3 ();
+ CREATE TABLE a4 ();
+ CREATE TABLE a5 ();
+ CREATE TABLE a6 ();
+ CREATE TABLE a7 ();
+ CREATE TABLE a8 ();
+}
+
+teardown
+{
+ DROP TABLE a1, a2, a3, a4, a5, a6, a7, a8;
+}
+
+session s1
+setup { BEGIN; SET deadlock_timeout = '100s'; }
+step s1a1 { LOCK TABLE a1; }
+step s1a2 { LOCK TABLE a2; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN; SET deadlock_timeout = '100s'; }
+step s2a2 { LOCK TABLE a2; }
+step s2a3 { LOCK TABLE a3; }
+step s2c { COMMIT; }
+
+session s3
+setup { BEGIN; SET deadlock_timeout = '100s'; }
+step s3a3 { LOCK TABLE a3; }
+step s3a4 { LOCK TABLE a4; }
+step s3c { COMMIT; }
+
+session s4
+setup { BEGIN; SET deadlock_timeout = '100s'; }
+step s4a4 { LOCK TABLE a4; }
+step s4a5 { LOCK TABLE a5; }
+step s4c { COMMIT; }
+
+session s5
+setup { BEGIN; SET deadlock_timeout = '100s'; }
+step s5a5 { LOCK TABLE a5; }
+step s5a6 { LOCK TABLE a6; }
+step s5c { COMMIT; }
+
+session s6
+setup { BEGIN; SET deadlock_timeout = '100s'; }
+step s6a6 { LOCK TABLE a6; }
+step s6a7 { LOCK TABLE a7; }
+step s6c { COMMIT; }
+
+session s7
+setup { BEGIN; SET deadlock_timeout = '100s'; }
+step s7a7 { LOCK TABLE a7; }
+step s7a8 { LOCK TABLE a8; }
+step s7c { COMMIT; }
+
+session s8
+setup { BEGIN; SET deadlock_timeout = '10ms'; }
+step s8a8 { LOCK TABLE a8; }
+step s8a1 { LOCK TABLE a1; }
+step s8c { COMMIT; }
+
+# Note: when s8a1 detects the deadlock and fails, s7a8 is released, making
+# it timing-dependent which query completion is received first by the tester.
+# To ensure output stability, add a blocking mark to force s8a1's completion
+# to be reported first. There is a second timing dependency, too: the tester
+# might or might not observe s8a1 during its short lock wait state. Apply a
+# dummy blocking mark to s8a1 to ensure it will be reported as "waiting"
+# regardless of that.
+
+permutation s1a1 s2a2 s3a3 s4a4 s5a5 s6a6 s7a7 s8a8 s1a2 s2a3 s3a4 s4a5 s5a6 s6a7 s7a8(s8a1) s8a1(*) s8c s7c s6c s5c s4c s3c s2c s1c
diff --git a/src/test/isolation/specs/deadlock-parallel.spec b/src/test/isolation/specs/deadlock-parallel.spec
new file mode 100644
index 0000000..a050a49
--- /dev/null
+++ b/src/test/isolation/specs/deadlock-parallel.spec
@@ -0,0 +1,113 @@
+# Test deadlock resolution with parallel process groups.
+
+# It's fairly hard to get parallel worker processes to block on locks,
+# since generally they don't want any locks their leader didn't already
+# take. We cheat like mad here by making a function that takes a lock,
+# and is incorrectly marked parallel-safe so that it can execute in a worker.
+
+# Note that we explicitly override any global settings of isolation level
+# or force_parallel_mode, to ensure we're testing what we intend to.
+
+# Otherwise, this is morally equivalent to deadlock-soft.spec:
+# Four-process deadlock with two hard edges and two soft edges.
+# d2 waits for e1 (soft edge), e1 waits for d1 (hard edge),
+# d1 waits for e2 (soft edge), e2 waits for d2 (hard edge).
+# The deadlock detector resolves the deadlock by reversing the d1-e2 edge,
+# unblocking d1.
+
+# However ... it's not actually that well-defined whether the deadlock
+# detector will prefer to unblock d1 or d2. It depends on which backend
+# is first to run DeadLockCheck after the deadlock condition is created:
+# that backend will search outwards from its own wait condition, and will
+# first find a loop involving the *other* lock. We encourage that to be
+# one of the d2a1 parallel workers, which will therefore unblock d1a2
+# workers, by setting a shorter deadlock_timeout in session d2. But on
+# slow machines, one or more d1a2 workers may not yet have reached their
+# lock waits, so that they're not unblocked by the first DeadLockCheck.
+# The next DeadLockCheck may choose to unblock the d2a1 workers instead,
+# which would allow d2a1 to complete before d1a2, causing the test to
+# freeze up because isolationtester isn't expecting that completion order.
+# (In effect, we have an undetectable deadlock because d2 is waiting for
+# d1's completion, but on the client side.) To fix this, introduce an
+# additional lock (advisory lock 3), which is initially taken by d1 and
+# then d2a1 will wait for it after completing the main part of the test.
+# In this way, the deadlock detector can see that d1 must be completed
+# first, regardless of timing.
+
+setup
+{
+ create function lock_share(int,int) returns int language sql as
+ 'select pg_advisory_xact_lock_shared($1); select 1;' parallel safe;
+
+ create function lock_excl(int,int) returns int language sql as
+ 'select pg_advisory_xact_lock($1); select 1;' parallel safe;
+
+ create table bigt as select x from generate_series(1, 10000) x;
+ analyze bigt;
+}
+
+teardown
+{
+ drop function lock_share(int,int);
+ drop function lock_excl(int,int);
+ drop table bigt;
+}
+
+session d1
+setup { BEGIN isolation level repeatable read;
+ SET force_parallel_mode = off;
+ SET deadlock_timeout = '10s';
+}
+# these locks will be taken in the leader, so they will persist:
+step d1a1 { SELECT lock_share(1,x), lock_excl(3,x) FROM bigt LIMIT 1; }
+# this causes all the parallel workers to take locks:
+step d1a2 { SET force_parallel_mode = on;
+ SET parallel_setup_cost = 0;
+ SET parallel_tuple_cost = 0;
+ SET min_parallel_table_scan_size = 0;
+ SET parallel_leader_participation = off;
+ SET max_parallel_workers_per_gather = 3;
+ SELECT sum(lock_share(2,x)) FROM bigt; }
+step d1c { COMMIT; }
+
+session d2
+setup { BEGIN isolation level repeatable read;
+ SET force_parallel_mode = off;
+ SET deadlock_timeout = '10ms';
+}
+# this lock will be taken in the leader, so it will persist:
+step d2a2 { select lock_share(2,x) FROM bigt LIMIT 1; }
+# this causes all the parallel workers to take locks;
+# after which, make the leader take lock 3 to prevent client-driven deadlock
+step d2a1 { SET force_parallel_mode = on;
+ SET parallel_setup_cost = 0;
+ SET parallel_tuple_cost = 0;
+ SET min_parallel_table_scan_size = 0;
+ SET parallel_leader_participation = off;
+ SET max_parallel_workers_per_gather = 3;
+ SELECT sum(lock_share(1,x)) FROM bigt;
+ SET force_parallel_mode = off;
+ RESET parallel_setup_cost;
+ RESET parallel_tuple_cost;
+ SELECT lock_share(3,x) FROM bigt LIMIT 1; }
+step d2c { COMMIT; }
+
+session e1
+setup { BEGIN isolation level repeatable read;
+ SET force_parallel_mode = on;
+ SET deadlock_timeout = '10s';
+}
+# this lock will be taken in a parallel worker, but we don't need it to persist
+step e1l { SELECT lock_excl(1,x) FROM bigt LIMIT 1; }
+step e1c { COMMIT; }
+
+session e2
+setup { BEGIN isolation level repeatable read;
+ SET force_parallel_mode = on;
+ SET deadlock_timeout = '10s';
+}
+# this lock will be taken in a parallel worker, but we don't need it to persist
+step e2l { SELECT lock_excl(2,x) FROM bigt LIMIT 1; }
+step e2c { COMMIT; }
+
+permutation d1a1 d2a2 e1l e2l d1a2 d2a1 d1c e1c d2c e2c
diff --git a/src/test/isolation/specs/deadlock-simple.spec b/src/test/isolation/specs/deadlock-simple.spec
new file mode 100644
index 0000000..3086dc7
--- /dev/null
+++ b/src/test/isolation/specs/deadlock-simple.spec
@@ -0,0 +1,29 @@
+# The deadlock detector has a special case for "simple" deadlocks. A simple
+# deadlock occurs when we attempt a lock upgrade while another process waits
+# for a lock upgrade on the same object; and the sought locks conflict with
+# those already held, so that neither process can complete its upgrade until
+# the other releases locks. Test this scenario.
+
+setup
+{
+ CREATE TABLE a1 ();
+}
+
+teardown
+{
+ DROP TABLE a1;
+}
+
+session s1
+setup { BEGIN; }
+step s1as { LOCK TABLE a1 IN ACCESS SHARE MODE; }
+step s1ae { LOCK TABLE a1 IN ACCESS EXCLUSIVE MODE; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step s2as { LOCK TABLE a1 IN ACCESS SHARE MODE; }
+step s2ae { LOCK TABLE a1 IN ACCESS EXCLUSIVE MODE; }
+step s2c { COMMIT; }
+
+permutation s1as s2as s1ae s2ae s1c s2c
diff --git a/src/test/isolation/specs/deadlock-soft-2.spec b/src/test/isolation/specs/deadlock-soft-2.spec
new file mode 100644
index 0000000..26d9c62
--- /dev/null
+++ b/src/test/isolation/specs/deadlock-soft-2.spec
@@ -0,0 +1,43 @@
+# Soft deadlock requiring reversal of multiple wait-edges. s1 must
+# jump over both s3 and s4 and acquire the lock on a2 immediately,
+# since s3 and s4 are hard-blocked on a1.
+
+setup
+{
+ CREATE TABLE a1 ();
+ CREATE TABLE a2 ();
+}
+
+teardown
+{
+ DROP TABLE a1, a2;
+}
+
+session s1
+setup { BEGIN; SET deadlock_timeout = '10ms'; }
+step s1a { LOCK TABLE a1 IN SHARE UPDATE EXCLUSIVE MODE; }
+step s1b { LOCK TABLE a2 IN SHARE UPDATE EXCLUSIVE MODE; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN; SET deadlock_timeout = '100s'; }
+step s2a { LOCK TABLE a2 IN ACCESS SHARE MODE; }
+step s2b { LOCK TABLE a1 IN SHARE UPDATE EXCLUSIVE MODE; }
+step s2c { COMMIT; }
+
+session s3
+setup { BEGIN; SET deadlock_timeout = '100s'; }
+step s3a { LOCK TABLE a2 IN ACCESS EXCLUSIVE MODE; }
+step s3c { COMMIT; }
+
+session s4
+setup { BEGIN; SET deadlock_timeout = '100s'; }
+step s4a { LOCK TABLE a2 IN ACCESS EXCLUSIVE MODE; }
+step s4c { COMMIT; }
+
+# The expected output for this test assumes that isolationtester will
+# detect step s1b as waiting before the deadlock detector runs and
+# releases s1 from its blocked state. To ensure that happens even in
+# very slow (debug_discard_caches) cases, apply a (*) annotation.
+
+permutation s1a s2a s2b s3a s4a s1b(*) s1c s2c s3c s4c
diff --git a/src/test/isolation/specs/deadlock-soft.spec b/src/test/isolation/specs/deadlock-soft.spec
new file mode 100644
index 0000000..bc9c6a7
--- /dev/null
+++ b/src/test/isolation/specs/deadlock-soft.spec
@@ -0,0 +1,40 @@
+# Four-process deadlock with two hard edges and two soft edges.
+# d2 waits for e1 (soft edge), e1 waits for d1 (hard edge),
+# d1 waits for e2 (soft edge), e2 waits for d2 (hard edge).
+# The deadlock detector resolves the deadlock by reversing the d1-e2 edge,
+# unblocking d1.
+
+setup
+{
+ CREATE TABLE a1 ();
+ CREATE TABLE a2 ();
+}
+
+teardown
+{
+ DROP TABLE a1, a2;
+}
+
+session d1
+setup { BEGIN; SET deadlock_timeout = '10s'; }
+step d1a1 { LOCK TABLE a1 IN ACCESS SHARE MODE; }
+step d1a2 { LOCK TABLE a2 IN ACCESS SHARE MODE; }
+step d1c { COMMIT; }
+
+session d2
+setup { BEGIN; SET deadlock_timeout = '10ms'; }
+step d2a2 { LOCK TABLE a2 IN ACCESS SHARE MODE; }
+step d2a1 { LOCK TABLE a1 IN ACCESS SHARE MODE; }
+step d2c { COMMIT; }
+
+session e1
+setup { BEGIN; SET deadlock_timeout = '10s'; }
+step e1l { LOCK TABLE a1 IN ACCESS EXCLUSIVE MODE; }
+step e1c { COMMIT; }
+
+session e2
+setup { BEGIN; SET deadlock_timeout = '10s'; }
+step e2l { LOCK TABLE a2 IN ACCESS EXCLUSIVE MODE; }
+step e2c { COMMIT; }
+
+permutation d1a1 d2a2 e1l e2l d1a2 d2a1 d1c e1c d2c e2c
diff --git a/src/test/isolation/specs/delete-abort-savept-2.spec b/src/test/isolation/specs/delete-abort-savept-2.spec
new file mode 100644
index 0000000..65bd936
--- /dev/null
+++ b/src/test/isolation/specs/delete-abort-savept-2.spec
@@ -0,0 +1,34 @@
+# A funkier version of delete-abort-savept
+setup
+{
+ CREATE TABLE foo (
+ key INT PRIMARY KEY,
+ value INT
+ );
+
+ INSERT INTO foo VALUES (1, 1);
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+setup { BEGIN; }
+step s1l { SELECT * FROM foo FOR KEY SHARE; }
+step s1svp { SAVEPOINT f; }
+step s1d { SELECT * FROM foo FOR NO KEY UPDATE; }
+step s1r { ROLLBACK TO f; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step s2l { SELECT * FROM foo FOR UPDATE; }
+step s2l2 { SELECT * FROM foo FOR NO KEY UPDATE; }
+step s2c { COMMIT; }
+
+permutation s1l s1svp s1d s1r s2l s1c s2c
+permutation s1l s1svp s1d s2l s1r s1c s2c
+permutation s1l s1svp s1d s1r s2l2 s1c s2c
+permutation s1l s1svp s1d s2l2 s1r s1c s2c
diff --git a/src/test/isolation/specs/delete-abort-savept.spec b/src/test/isolation/specs/delete-abort-savept.spec
new file mode 100644
index 0000000..498ffed
--- /dev/null
+++ b/src/test/isolation/specs/delete-abort-savept.spec
@@ -0,0 +1,37 @@
+# After rolling back a subtransaction that upgraded a lock, the previously
+# held lock should still be held.
+setup
+{
+ CREATE TABLE foo (
+ key INT PRIMARY KEY,
+ value INT
+ );
+
+ INSERT INTO foo VALUES (1, 1);
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+setup { BEGIN; }
+step s1l { SELECT * FROM foo FOR KEY SHARE; }
+step s1svp { SAVEPOINT f; }
+step s1d { DELETE FROM foo; }
+step s1r { ROLLBACK TO f; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step s2l { SELECT * FROM foo FOR UPDATE; }
+step s2c { COMMIT; }
+
+permutation s1l s1svp s1d s1r s1c s2l s2c
+permutation s1l s1svp s1d s1r s2l s1c s2c
+permutation s1l s1svp s1d s2l s1r s1c s2c
+permutation s1l s1svp s2l s1d s1r s1c s2c
+permutation s1l s2l s1svp s1d s1r s1c s2c
+permutation s2l s1l s2c s1svp s1d s1r s1c
+permutation s2l s2c s1l s1svp s1d s1r s1c
diff --git a/src/test/isolation/specs/detach-partition-concurrently-1.spec b/src/test/isolation/specs/detach-partition-concurrently-1.spec
new file mode 100644
index 0000000..835fe89
--- /dev/null
+++ b/src/test/isolation/specs/detach-partition-concurrently-1.spec
@@ -0,0 +1,69 @@
+# Test that detach partition concurrently makes the partition invisible at the
+# correct time.
+
+setup
+{
+ DROP TABLE IF EXISTS d_listp, d_listp1, d_listp2;
+ CREATE TABLE d_listp (a int) PARTITION BY LIST(a);
+ CREATE TABLE d_listp1 PARTITION OF d_listp FOR VALUES IN (1);
+ CREATE TABLE d_listp2 PARTITION OF d_listp FOR VALUES IN (2);
+ INSERT INTO d_listp VALUES (1),(2);
+}
+
+teardown {
+ DROP TABLE IF EXISTS d_listp, d_listp2, d_listp_foobar;
+}
+
+session s1
+step s1b { BEGIN; }
+step s1brr { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step s1s { SELECT * FROM d_listp; }
+step s1ins { INSERT INTO d_listp VALUES (1); }
+step s1ins2 { INSERT INTO d_listp VALUES (2); }
+step s1prep { PREPARE f(int) AS INSERT INTO d_listp VALUES ($1); }
+step s1prep1 { PREPARE f(int) AS INSERT INTO d_listp VALUES (1); }
+step s1prep2 { PREPARE f(int) AS INSERT INTO d_listp VALUES (2); }
+step s1exec1 { EXECUTE f(1); }
+step s1exec2 { EXECUTE f(2); }
+step s1dealloc { DEALLOCATE f; }
+step s1c { COMMIT; }
+
+session s2
+step s2detach { ALTER TABLE d_listp DETACH PARTITION d_listp2 CONCURRENTLY; }
+step s2drop { DROP TABLE d_listp2; }
+
+session s3
+step s3s { SELECT * FROM d_listp; }
+step s3i { SELECT relpartbound IS NULL FROM pg_class where relname = 'd_listp2'; }
+step s3ins2 { INSERT INTO d_listp VALUES (2); }
+
+# The transaction that detaches hangs until it sees any older transaction
+# terminate, as does anybody else.
+permutation s1b s1s s2detach s1s s1c s1s
+
+# relpartbound remains set until s1 commits
+# XXX this could be timing dependent :-(
+permutation s1b s1s s2detach s1s s3s s3i s1c s3i s2drop s1s
+
+# In read-committed mode, the partition disappears from view of concurrent
+# transactions immediately. But if a write lock is held, then the detach
+# has to wait.
+permutation s1b s1s s2detach s1ins s1s s1c
+permutation s1b s1s s1ins2 s2detach s1ins s1s s1c
+
+# In repeatable-read mode, the partition remains visible until commit even
+# if the to-be-detached partition is not locked for write.
+permutation s1brr s1s s2detach s1ins s1s s1c
+permutation s1brr s1s s2detach s1s s1c
+
+# Another process trying to acquire a write lock will be blocked behind the
+# detacher
+permutation s1b s1ins2 s2detach s3ins2 s1c
+
+# a prepared query is not blocked
+permutation s1brr s1prep s1s s2detach s1s s1exec1 s3s s1dealloc s1c
+permutation s1brr s1prep s1exec2 s2detach s1s s1exec2 s3s s1c s1dealloc
+permutation s1brr s1prep s1s s2detach s1s s1exec2 s1c s1dealloc
+permutation s1brr s1prep s2detach s1s s1exec2 s1c s1dealloc
+permutation s1brr s1prep1 s2detach s1s s1exec2 s1c s1dealloc
+permutation s1brr s1prep2 s2detach s1s s1exec2 s1c s1dealloc
diff --git a/src/test/isolation/specs/detach-partition-concurrently-2.spec b/src/test/isolation/specs/detach-partition-concurrently-2.spec
new file mode 100644
index 0000000..fa767ea
--- /dev/null
+++ b/src/test/isolation/specs/detach-partition-concurrently-2.spec
@@ -0,0 +1,41 @@
+# Test that detach partition concurrently makes the partition safe
+# for foreign keys that reference it.
+
+setup
+{
+ DROP TABLE IF EXISTS d_lp_fk, d_lp_fk_1, d_lp_fk_2, d_lp_fk_r;
+
+ CREATE TABLE d_lp_fk (a int PRIMARY KEY) PARTITION BY LIST(a);
+ CREATE TABLE d_lp_fk_1 PARTITION OF d_lp_fk FOR VALUES IN (1);
+ CREATE TABLE d_lp_fk_2 PARTITION OF d_lp_fk FOR VALUES IN (2);
+ INSERT INTO d_lp_fk VALUES (1), (2);
+
+ CREATE TABLE d_lp_fk_r (a int references d_lp_fk);
+}
+
+teardown { DROP TABLE IF EXISTS d_lp_fk, d_lp_fk_1, d_lp_fk_2, d_lp_fk_r; }
+
+session s1
+step s1b { BEGIN; }
+step s1s { SELECT * FROM d_lp_fk; }
+step s1c { COMMIT; }
+
+session s2
+step s2d { ALTER TABLE d_lp_fk DETACH PARTITION d_lp_fk_1 CONCURRENTLY; }
+
+session s3
+step s3b { BEGIN; }
+step s3i1 { INSERT INTO d_lp_fk_r VALUES (1); }
+step s3i2 { INSERT INTO d_lp_fk_r VALUES (2); }
+step s3c { COMMIT; }
+
+# The transaction that detaches hangs until it sees any older transaction
+# terminate.
+permutation s1b s1s s2d s3i1 s1c
+permutation s1b s1s s2d s3i2 s3i2 s1c
+
+permutation s1b s1s s3i1 s2d s1c
+permutation s1b s1s s3i2 s2d s1c
+
+# what if s3 has an uncommitted insertion?
+permutation s1b s1s s3b s2d s3i1 s1c s3c
diff --git a/src/test/isolation/specs/detach-partition-concurrently-3.spec b/src/test/isolation/specs/detach-partition-concurrently-3.spec
new file mode 100644
index 0000000..31aa308
--- /dev/null
+++ b/src/test/isolation/specs/detach-partition-concurrently-3.spec
@@ -0,0 +1,86 @@
+# Try various things to happen to a partition with an incomplete detach
+#
+# Note: When using "s1cancel", mark the target step (the one to be canceled)
+# as blocking "s1cancel". This ensures consistent reporting regardless of
+# whether "s1cancel" finishes before or after the other step reports failure.
+# Also, ensure the step after "s1cancel" is also an s1 step (use "s1noop" if
+# necessary). This ensures we won't move on to the next step until the cancel
+# is complete.
+
+setup
+{
+ CREATE TABLE d3_listp (a int) PARTITION BY LIST(a);
+ CREATE TABLE d3_listp1 PARTITION OF d3_listp FOR VALUES IN (1);
+ CREATE TABLE d3_listp2 PARTITION OF d3_listp FOR VALUES IN (2);
+ CREATE TABLE d3_pid (pid int);
+ INSERT INTO d3_listp VALUES (1);
+}
+
+teardown {
+ DROP TABLE IF EXISTS d3_listp, d3_listp1, d3_listp2, d3_pid;
+}
+
+session s1
+step s1b { BEGIN; }
+step s1brr { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step s1s { SELECT * FROM d3_listp; }
+step s1spart { SELECT * FROM d3_listp1; }
+step s1cancel { SELECT pg_cancel_backend(pid) FROM d3_pid; }
+step s1noop { }
+step s1c { COMMIT; }
+step s1alter { ALTER TABLE d3_listp1 ALTER a DROP NOT NULL; }
+step s1insert { INSERT INTO d3_listp VALUES (1); }
+step s1insertpart { INSERT INTO d3_listp1 VALUES (1); }
+step s1drop { DROP TABLE d3_listp; }
+step s1droppart { DROP TABLE d3_listp1; }
+step s1trunc { TRUNCATE TABLE d3_listp; }
+step s1list { SELECT relname FROM pg_catalog.pg_class
+ WHERE relname LIKE 'd3_listp%' ORDER BY 1; }
+step s1describe { SELECT 'd3_listp' AS root, * FROM pg_partition_tree('d3_listp')
+ UNION ALL SELECT 'd3_listp1', * FROM pg_partition_tree('d3_listp1'); }
+
+session s2
+step s2begin { BEGIN; }
+step s2snitch { INSERT INTO d3_pid SELECT pg_backend_pid(); }
+step s2detach { ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; }
+step s2detach2 { ALTER TABLE d3_listp DETACH PARTITION d3_listp2 CONCURRENTLY; }
+step s2detachfinal { ALTER TABLE d3_listp DETACH PARTITION d3_listp1 FINALIZE; }
+step s2drop { DROP TABLE d3_listp1; }
+step s2commit { COMMIT; }
+
+# Try various things while the partition is in "being detached" state, with
+# no session waiting.
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s1describe s1alter
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1insert s1c
+permutation s2snitch s1brr s1s s2detach s1cancel(s2detach) s1insert s1c s1spart
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s1insertpart
+
+# Test partition descriptor caching
+permutation s2snitch s1b s1s s2detach2 s1cancel(s2detach2) s1c s1brr s1insert s1s s1insert s1c
+permutation s2snitch s1b s1s s2detach2 s1cancel(s2detach2) s1c s1brr s1s s1insert s1s s1c
+
+# "drop" here does both tables
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s1drop s1list
+# "truncate" only does parent, not partition
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s1trunc s1spart
+
+# If a partition pending detach exists, we cannot drop another one
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1noop s2detach2 s1c
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1noop s2detachfinal s1c s2detach2
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s1droppart s2detach2
+
+# When a partition with incomplete detach is dropped, we grab lock on parent too.
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s2begin s2drop s1s s2commit
+
+# Partially detach, then select and try to complete the detach. Reading
+# from partition blocks (AEL is required on partition); reading from parent
+# does not block.
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s1b s1spart s2detachfinal s1c
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s1b s1s s2detachfinal s1c
+
+# DETACH FINALIZE in a transaction block. No insert/select on the partition
+# is allowed concurrently with that.
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s1b s1spart s2detachfinal s1c
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s2begin s2detachfinal s2commit
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s2begin s2detachfinal s1spart s2commit
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s2begin s2detachfinal s1insertpart s2commit
diff --git a/src/test/isolation/specs/detach-partition-concurrently-4.spec b/src/test/isolation/specs/detach-partition-concurrently-4.spec
new file mode 100644
index 0000000..2c02cae
--- /dev/null
+++ b/src/test/isolation/specs/detach-partition-concurrently-4.spec
@@ -0,0 +1,83 @@
+# This test exercises behavior of foreign keys in the face of concurrent
+# detach of partitions in the referenced table.
+# (The cases where the detaching transaction is cancelled is interesting
+# because the locking situation is completely different. I didn't verify
+# that keeping both variants adds any extra coverage.)
+#
+# Note: When using "s1cancel", mark the target step (the one to be canceled)
+# as blocking "s1cancel". This ensures consistent reporting regardless of
+# whether "s1cancel" finishes before or after the other step reports failure.
+# Also, ensure the step after "s1cancel" is also an s1 step (use "s1noop" if
+# necessary). This ensures we won't move on to the next step until the cancel
+# is complete.
+
+setup {
+ drop table if exists d4_primary, d4_primary1, d4_fk, d4_pid;
+ create table d4_primary (a int primary key) partition by list (a);
+ create table d4_primary1 partition of d4_primary for values in (1);
+ create table d4_primary2 partition of d4_primary for values in (2);
+ insert into d4_primary values (1);
+ insert into d4_primary values (2);
+ create table d4_fk (a int references d4_primary);
+ insert into d4_fk values (2);
+ create table d4_pid (pid int);
+}
+
+session s1
+step s1b { begin; }
+step s1brr { begin isolation level repeatable read; }
+step s1s { select * from d4_primary; }
+step s1cancel { select pg_cancel_backend(pid) from d4_pid; }
+step s1noop { }
+step s1insert { insert into d4_fk values (1); }
+step s1c { commit; }
+step s1declare { declare f cursor for select * from d4_primary; }
+step s1declare2 { declare f cursor for select * from d4_fk where a = 2; }
+step s1fetchall { fetch all from f; }
+step s1fetchone { fetch 1 from f; }
+step s1updcur { update d4_fk set a = 1 where current of f; }
+step s1svpt { savepoint f; }
+step s1rollback { rollback to f; }
+
+session s2
+step s2snitch { insert into d4_pid select pg_backend_pid(); }
+step s2detach { alter table d4_primary detach partition d4_primary1 concurrently; }
+
+session s3
+step s3brr { begin isolation level repeatable read; }
+step s3insert { insert into d4_fk values (1); }
+step s3commit { commit; }
+step s3vacfreeze { vacuum freeze pg_catalog.pg_inherits; }
+
+# Trying to insert into a partially detached partition is rejected
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1insert s1c
+permutation s2snitch s1b s1s s2detach s1insert s1c
+# ... even under REPEATABLE READ mode.
+permutation s2snitch s1brr s1s s2detach s1cancel(s2detach) s1insert s1c
+permutation s2snitch s1brr s1s s2detach s1insert s1c
+
+# If you read the referenced table using a cursor, you can see a row that the
+# RI query does not see.
+permutation s2snitch s1b s1declare s2detach s1cancel(s2detach) s1fetchall s1insert s1c
+permutation s2snitch s1b s1declare s2detach s1fetchall s1insert s1c
+permutation s2snitch s1b s1declare s2detach s1cancel(s2detach) s1svpt s1insert s1rollback s1fetchall s1c
+permutation s2snitch s1b s1declare s2detach s1svpt s1insert s1rollback s1fetchall s1c
+permutation s2snitch s1b s2detach s1declare s1cancel(s2detach) s1fetchall s1insert s1c
+permutation s2snitch s1b s2detach s1declare s1fetchall s1insert s1c
+permutation s2snitch s1b s2detach s1declare s1cancel(s2detach) s1svpt s1insert s1rollback s1fetchall s1c
+permutation s2snitch s1b s2detach s1declare s1svpt s1insert s1rollback s1fetchall s1c
+
+# Creating the referencing row using a cursor
+permutation s2snitch s1brr s1declare2 s1fetchone s2detach s1cancel(s2detach) s1updcur s1c
+permutation s2snitch s1brr s1declare2 s1fetchone s2detach s1updcur s1c
+permutation s2snitch s1brr s1declare2 s1fetchone s1updcur s2detach s1c
+
+# Try reading the table from an independent session.
+permutation s2snitch s1b s1s s2detach s3insert s1c
+permutation s2snitch s1b s1s s2detach s3brr s3insert s3commit s1cancel(s2detach) s1c
+permutation s2snitch s1b s1s s2detach s3brr s3insert s3commit s1c
+
+# Try one where we VACUUM FREEZE pg_inherits (to verify that xmin change is
+# handled correctly).
+permutation s2snitch s1brr s1s s2detach s1cancel(s2detach) s1noop s3vacfreeze s1s s1insert s1c
+permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1noop s3vacfreeze s1s s1insert s1c
diff --git a/src/test/isolation/specs/drop-index-concurrently-1.spec b/src/test/isolation/specs/drop-index-concurrently-1.spec
new file mode 100644
index 0000000..812b5de
--- /dev/null
+++ b/src/test/isolation/specs/drop-index-concurrently-1.spec
@@ -0,0 +1,41 @@
+# DROP INDEX CONCURRENTLY
+#
+# This test shows that the concurrent write behaviour works correctly
+# with the expected output being 2 rows at the READ COMMITTED and READ
+# UNCOMMITTED transaction isolation levels, and 1 row at the other
+# transaction isolation levels.
+#
+setup
+{
+ CREATE TABLE test_dc(id serial primary key, data int);
+ INSERT INTO test_dc(data) SELECT * FROM generate_series(1, 100);
+ CREATE INDEX test_dc_data ON test_dc(data);
+}
+
+teardown
+{
+ DROP TABLE test_dc;
+}
+
+session s1
+step noseq { SET enable_seqscan = false; }
+step chkiso { SELECT (setting in ('read committed','read uncommitted')) AS is_read_committed FROM pg_settings WHERE name = 'default_transaction_isolation'; }
+step prepi { PREPARE getrow_idx AS SELECT * FROM test_dc WHERE data=34 ORDER BY id,data; }
+step preps { PREPARE getrow_seq AS SELECT * FROM test_dc WHERE data::text=34::text ORDER BY id,data; }
+step begin { BEGIN; }
+step explaini { EXPLAIN (COSTS OFF) EXECUTE getrow_idx; }
+step explains { EXPLAIN (COSTS OFF) EXECUTE getrow_seq; }
+step selecti { EXECUTE getrow_idx; }
+step selects { EXECUTE getrow_seq; }
+step end { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step select2 { SELECT * FROM test_dc WHERE data=34 ORDER BY id,data; }
+step insert2 { INSERT INTO test_dc(data) SELECT * FROM generate_series(1, 100); }
+step end2 { COMMIT; }
+
+session s3
+step drop { DROP INDEX CONCURRENTLY test_dc_data; }
+
+permutation noseq chkiso prepi preps begin explaini explains select2 drop insert2 end2 selecti selects end
diff --git a/src/test/isolation/specs/eval-plan-qual-trigger.spec b/src/test/isolation/specs/eval-plan-qual-trigger.spec
new file mode 100644
index 0000000..b512edd
--- /dev/null
+++ b/src/test/isolation/specs/eval-plan-qual-trigger.spec
@@ -0,0 +1,410 @@
+setup
+{
+ CREATE TABLE trigtest(key text primary key, data text);
+
+ CREATE FUNCTION noisy_oper(p_comment text, p_a anynonarray, p_op text, p_b anynonarray)
+ RETURNS bool LANGUAGE plpgsql AS $body$
+ DECLARE
+ r bool;
+ BEGIN
+ EXECUTE format('SELECT $1 %s $2', p_op) INTO r USING p_a, p_b;
+ RAISE NOTICE '%: % % % % %: %', p_comment, pg_typeof(p_a), p_a, p_op, pg_typeof(p_b), p_b, r;
+ RETURN r;
+ END;$body$;
+
+ CREATE FUNCTION trig_report() RETURNS TRIGGER LANGUAGE plpgsql AS $body$
+ DECLARE
+ r_new text;
+ r_old text;
+ r_ret record;
+ BEGIN
+ -- In older releases it wasn't allowed to reference OLD/NEW
+ -- when not applicable for TG_WHEN
+ IF TG_OP = 'INSERT' THEN
+ r_old = NULL;
+ r_new = NEW;
+ r_ret = NEW;
+ ELSIF TG_OP = 'DELETE' THEN
+ r_old = OLD;
+ r_new = NULL;
+ r_ret = OLD;
+ ELSIF TG_OP = 'UPDATE' THEN
+ r_old = OLD;
+ r_new = NEW;
+ r_ret = NEW;
+ END IF;
+
+ IF TG_WHEN = 'AFTER' THEN
+ r_ret = NULL;
+ END IF;
+
+ RAISE NOTICE 'trigger: name %; when: %; lev: %s; op: %; old: % new: %',
+ TG_NAME, TG_WHEN, TG_LEVEL, TG_OP, r_old, r_new;
+
+ RETURN r_ret;
+ END;
+ $body$;
+}
+
+teardown
+{
+ DROP TABLE trigtest;
+ DROP FUNCTION noisy_oper(text, anynonarray, text, anynonarray);
+ DROP FUNCTION trig_report();
+}
+
+
+session s0
+step s0_rep { SELECT * FROM trigtest ORDER BY key, data }
+
+session s1
+#setup { }
+step s1_b_rc { BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1; }
+step s1_b_rr { BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; }
+step s1_c { COMMIT; }
+step s1_r { ROLLBACK; }
+step s1_trig_rep_b_i { CREATE TRIGGER rep_b_i BEFORE INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report(); }
+step s1_trig_rep_a_i { CREATE TRIGGER rep_a_i AFTER INSERT ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report(); }
+step s1_trig_rep_b_u { CREATE TRIGGER rep_b_u BEFORE UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report(); }
+step s1_trig_rep_a_u { CREATE TRIGGER rep_a_u AFTER UPDATE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report(); }
+step s1_trig_rep_b_d { CREATE TRIGGER rep_b_d BEFORE DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report(); }
+step s1_trig_rep_a_d { CREATE TRIGGER rep_a_d AFTER DELETE ON trigtest FOR EACH ROW EXECUTE PROCEDURE trig_report(); }
+step s1_ins_a { INSERT INTO trigtest VALUES ('key-a', 'val-a-s1') RETURNING *; }
+step s1_ins_b { INSERT INTO trigtest VALUES ('key-b', 'val-b-s1') RETURNING *; }
+step s1_ins_c { INSERT INTO trigtest VALUES ('key-c', 'val-c-s1') RETURNING *; }
+step s1_del_a {
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+}
+step s1_del_b {
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-b') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+}
+step s1_upd_a_data {
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+}
+step s1_upd_b_data {
+ UPDATE trigtest SET data = data || '-ups1'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-b') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+}
+step s1_upd_a_tob {
+ UPDATE trigtest SET key = 'key-b', data = data || '-tobs1'
+ WHERE
+ noisy_oper('upk', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+}
+
+session s2
+#setup { }
+step s2_b_rc { BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1; }
+step s2_b_rr { BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; }
+step s2_c { COMMIT; }
+step s2_r { ROLLBACK; }
+step s2_ins_a { INSERT INTO trigtest VALUES ('key-a', 'val-a-s2') RETURNING *; }
+step s2_del_a {
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+}
+step s2_upd_a_data {
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+}
+step s2_upd_b_data {
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-b') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+}
+step s2_upd_all_data {
+ UPDATE trigtest SET data = data || '-ups2'
+ WHERE
+ noisy_oper('upd', key, '<>', 'mismatch') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+}
+step s2_upsert_a_data {
+ INSERT INTO trigtest VALUES ('key-a', 'val-a-upss2')
+ ON CONFLICT (key)
+ DO UPDATE SET data = trigtest.data || '-upserts2'
+ WHERE
+ noisy_oper('upd', trigtest.key, '=', 'key-a') AND
+ noisy_oper('upk', trigtest.data, '<>', 'mismatch')
+ RETURNING *;
+}
+
+session s3
+#setup { }
+step s3_b_rc { BEGIN ISOLATION LEVEL READ COMMITTED; SELECT 1; }
+step s3_c { COMMIT; }
+step s3_r { ROLLBACK; }
+step s3_del_a {
+ DELETE FROM trigtest
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *
+}
+step s3_upd_a_data {
+ UPDATE trigtest SET data = data || '-ups3'
+ WHERE
+ noisy_oper('upd', key, '=', 'key-a') AND
+ noisy_oper('upk', data, '<>', 'mismatch')
+ RETURNING *;
+}
+
+### base case verifying that triggers see performed modifications
+# s1 updates, s1 commits, s2 updates
+permutation s1_trig_rep_b_u s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s1_c s2_upd_a_data s2_c
+ s0_rep
+# s1 updates, s1 rolls back, s2 updates
+permutation s1_trig_rep_b_u s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s1_r s2_upd_a_data s2_c
+ s0_rep
+# s1 updates, s1 commits back, s2 deletes
+permutation s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s1_c s2_del_a s2_c
+ s0_rep
+# s1 updates, s1 rolls back back, s2 deletes
+permutation s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s1_r s2_del_a s2_c
+ s0_rep
+
+### Verify EPQ is performed if necessary, and skipped if transaction rolled back
+# s1 updates, s2 updates, s1 commits, EPQ
+permutation s1_trig_rep_b_u s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s2_upd_a_data s1_c s2_c
+ s0_rep
+# s1 updates, s2 updates, s1 rolls back, no EPQ
+permutation s1_trig_rep_b_u s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s2_upd_a_data s1_r s2_c
+ s0_rep
+# s1 updates, s2 deletes, s1 commits, EPQ
+permutation s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s2_upd_a_data s1_c s2_c
+ s0_rep
+# s1 updates, s2 deletes, s1 rolls back, no EPQ
+permutation s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s2_upd_a_data s1_r s2_c
+ s0_rep
+# s1 deletes, s2 updates, s1 commits, EPQ
+permutation s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_del_a s2_upd_a_data s1_c s2_c
+ s0_rep
+# s1 deletes, s2 updates, s1 rolls back, no EPQ
+permutation s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_del_a s2_upd_a_data s1_r s2_c
+ s0_rep
+# s1 inserts, s2 inserts, s1 commits, s2 inserts, unique conflict
+permutation s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_a_i s1_trig_rep_a_d
+ s1_b_rc s2_b_rc
+ s1_ins_a s2_ins_a s1_c s2_c
+ s0_rep
+# s1 inserts, s2 inserts, s1 rolls back, s2 inserts, no unique conflict
+permutation s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_a_i s1_trig_rep_a_d
+ s1_b_rc s2_b_rc
+ s1_ins_a s2_ins_a s1_r s2_c
+ s0_rep
+# s1 updates, s2 upserts, s1 commits, EPQ
+permutation s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s2_upsert_a_data s1_c s2_c
+ s0_rep
+# s1 updates, s2 upserts, s1 rolls back, no EPQ
+permutation s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s2_upsert_a_data s1_c s2_c
+ s0_rep
+# s1 inserts, s2 upserts, s1 commits
+permutation s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_b_rc s2_b_rc
+ s1_ins_a s2_upsert_a_data s1_c s2_c
+ s0_rep
+# s1 inserts, s2 upserts, s1 rolls back
+permutation s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_b_rc s2_b_rc
+ s1_ins_a s2_upsert_a_data s1_r s2_c
+ s0_rep
+# s1 inserts, s2 upserts, s1 updates, s1 commits, EPQ
+permutation s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_b_rc s2_b_rc
+ s1_ins_a s1_upd_a_data s2_upsert_a_data s1_c s2_c
+ s0_rep
+# s1 inserts, s2 upserts, s1 updates, s1 rolls back, no EPQ
+permutation s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_b_rc s2_b_rc
+ s1_ins_a s1_upd_a_data s2_upsert_a_data s1_r s2_c
+ s0_rep
+
+### Verify EPQ is performed if necessary, and skipped if transaction rolled back,
+### just without before triggers (for comparison, no additional row locks)
+# s1 updates, s2 updates, s1 commits, EPQ
+permutation s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s2_upd_a_data s1_c s2_c
+ s0_rep
+# s1 updates, s2 updates, s1 rolls back, no EPQ
+permutation s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s2_upd_a_data s1_r s2_c
+ s0_rep
+# s1 updates, s2 deletes, s1 commits, EPQ
+permutation s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s2_del_a s1_c s2_c
+ s0_rep
+# s1 updates, s2 deletes, s1 rolls back, no EPQ
+permutation s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_upd_a_data s2_del_a s1_r s2_c
+ s0_rep
+# s1 deletes, s2 updates, s1 commits, EPQ
+permutation s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_del_a s2_upd_a_data s1_c s2_c
+ s0_rep
+# s1 deletes, s2 updates, s1 rolls back, no EPQ
+permutation s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_del_a s2_upd_a_data s1_r s2_c
+ s0_rep
+# s1 deletes, s2 deletes, s1 commits, EPQ
+permutation s1_trig_rep_a_d
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_del_a s2_del_a s1_c s2_c
+ s0_rep
+# s1 deletes, s2 deletes, s1 rolls back, no EPQ
+permutation s1_trig_rep_a_d
+ s1_ins_a s1_ins_b s1_b_rc s2_b_rc
+ s1_del_a s2_del_a s1_r s2_c
+ s0_rep
+
+### Verify that an update affecting a row that has been
+### updated/deleted to not match the where clause anymore works
+### correctly
+# s1 updates to different key, s2 updates old key, s1 commits, EPQ failure should lead to no update
+permutation s1_trig_rep_b_u s1_trig_rep_a_u
+ s1_ins_a s1_ins_c s1_b_rc s2_b_rc
+ s1_upd_a_tob s2_upd_a_data s1_c s2_c
+ s0_rep
+# s1 updates to different key, s2 updates old key, s1 rolls back, no EPQ failure
+permutation s1_trig_rep_b_u s1_trig_rep_a_u
+ s1_ins_a s1_ins_c s1_b_rc s2_b_rc
+ s1_upd_a_tob s2_upd_a_data s1_r s2_c
+ s0_rep
+# s1 updates to different key, s2 updates new key, s1 commits, s2 will
+# not see tuple with new key and not block
+permutation s1_trig_rep_b_u s1_trig_rep_a_u
+ s1_ins_a s1_ins_c s1_b_rc s2_b_rc
+ s1_upd_a_tob s2_upd_b_data s1_c s2_c
+ s0_rep
+# s1 updates to different key, s2 updates all keys, s1 commits, s2,
+# will not see tuple with old key, but block on old, and then follow
+# the chain
+permutation s1_trig_rep_b_u s1_trig_rep_a_u
+ s1_ins_a s1_ins_c s1_b_rc s2_b_rc
+ s1_upd_a_tob s2_upd_all_data s1_c s2_c
+ s0_rep
+# s1 deletes, s2 updates, s1 committs, EPQ failure should lead to no update
+permutation s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_c s1_b_rc s2_b_rc
+ s1_del_a s2_upd_a_data s1_c s2_c
+ s0_rep
+# s1 deletes, s2 updates, s1 rolls back, no EPQ failure
+permutation s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_c s1_b_rc s2_b_rc
+ s1_del_a s2_upd_a_data s1_r s2_c
+ s0_rep
+# s1 deletes, s2 deletes, s1 committs, EPQ failure should lead to no delete
+permutation s1_trig_rep_b_d s1_trig_rep_a_d
+ s1_ins_a s1_ins_c s1_b_rc s2_b_rc
+ s1_del_a s2_del_a s1_c s2_c
+ s0_rep
+# s1 deletes, s2 deletes, s1 rolls back, no EPQ failure
+permutation s1_trig_rep_b_d s1_trig_rep_a_d
+ s1_ins_a s1_ins_c s1_b_rc s2_b_rc
+ s1_del_a s2_del_a s1_r s2_c
+ s0_rep
+
+### Verify EPQ with more than two participants works
+## XXX: Disable tests, there is some potential for instability here that's not yet fully understood
+## s1 updates, s2 updates, s3 updates, s1 commits, s2 EPQ, s2 commits, s3 EPQ
+#permutation s1_trig_rep_b_u s1_trig_rep_a_u
+# s1_ins_a s1_ins_b s1_b_rc s2_b_rc s3_b_rc
+# s1_upd_a_data s2_upd_a_data s3_upd_a_data s1_c s2_c s3_c
+# s0_rep
+## s1 updates, s2 updates, s3 updates, s1 commits, s2 EPQ, s2 rolls back, s3 EPQ
+#permutation s1_trig_rep_b_u s1_trig_rep_a_u
+# s1_ins_a s1_ins_b s1_b_rc s2_b_rc s3_b_rc
+# s1_upd_a_data s2_upd_a_data s3_upd_a_data s1_c s2_r s3_c
+# s0_rep
+## s1 updates, s3 updates, s2 upserts, s1 updates, s1 commits, s3 EPQ, s3 deletes, s3 commits, s2 inserts without EPQ recheck
+#permutation s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u
+# s1_ins_a s1_b_rc s2_b_rc s3_b_rc
+# s1_upd_a_data s3_upd_a_data s2_upsert_a_data s1_upd_a_data s1_c s3_del_a s3_c s2_c
+# s0_rep
+## s1 updates, s3 updates, s2 upserts, s1 updates, s1 commits, s3 EPQ, s3 deletes, s3 rolls back, s2 EPQ
+#permutation s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u
+# s1_ins_a s1_b_rc s2_b_rc s3_b_rc
+# s1_upd_a_data s3_upd_a_data s2_upsert_a_data s1_upd_a_data s1_c s3_del_a s3_r s2_c
+# s0_rep
+
+### Document that EPQ doesn't "leap" onto a tuple that would match after blocking
+# s1 inserts a, s1 updates b, s2 updates b, s1 deletes b, s1 updates a to b, s1 commits, s2 EPQ finds tuple deleted
+permutation s1_trig_rep_b_i s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_i s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_b s1_b_rc s2_b_rc
+ s1_ins_a s1_upd_b_data s2_upd_b_data s1_del_b s1_upd_a_tob s1_c s2_c
+ s0_rep
+
+### Triggers for EPQ detect serialization failures
+# s1 updates, s2 updates, s1 commits, serialization failure
+permutation s1_trig_rep_b_u s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rr s2_b_rr
+ s1_upd_a_data s2_upd_a_data s1_c s2_c
+ s0_rep
+# s1 updates, s2 updates, s1 rolls back, s2 succeeds
+permutation s1_trig_rep_b_u s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rr s2_b_rr
+ s1_upd_a_data s2_upd_a_data s1_r s2_c
+ s0_rep
+# s1 deletes, s2 updates, s1 commits, serialization failure
+permutation s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rr s2_b_rr
+ s1_del_a s2_upd_a_data s1_c s2_c
+ s0_rep
+# s1 deletes, s2 updates, s1 rolls back, s2 succeeds
+permutation s1_trig_rep_b_d s1_trig_rep_b_u s1_trig_rep_a_d s1_trig_rep_a_u
+ s1_ins_a s1_ins_b s1_b_rr s2_b_rr
+ s1_del_a s2_upd_a_data s1_r s2_c
+ s0_rep
diff --git a/src/test/isolation/specs/eval-plan-qual.spec b/src/test/isolation/specs/eval-plan-qual.spec
new file mode 100644
index 0000000..4bb9595
--- /dev/null
+++ b/src/test/isolation/specs/eval-plan-qual.spec
@@ -0,0 +1,359 @@
+# Tests for the EvalPlanQual mechanism
+#
+# EvalPlanQual is used in READ COMMITTED isolation level to attempt to
+# re-execute UPDATE and DELETE operations against rows that were updated
+# by some concurrent transaction.
+
+setup
+{
+ CREATE TABLE accounts (accountid text PRIMARY KEY, balance numeric not null);
+ INSERT INTO accounts VALUES ('checking', 600), ('savings', 600);
+
+ CREATE FUNCTION update_checking(int) RETURNS bool LANGUAGE sql AS $$
+ UPDATE accounts SET balance = balance + 1 WHERE accountid = 'checking'; SELECT true;$$;
+
+ CREATE TABLE accounts_ext (accountid text PRIMARY KEY, balance numeric not null, other text);
+ INSERT INTO accounts_ext VALUES ('checking', 600, 'other'), ('savings', 700, null);
+ ALTER TABLE accounts_ext ADD COLUMN newcol int DEFAULT 42;
+ ALTER TABLE accounts_ext ADD COLUMN newcol2 text DEFAULT NULL;
+
+ CREATE TABLE p (a int, b int, c int);
+ CREATE TABLE c1 () INHERITS (p);
+ CREATE TABLE c2 () INHERITS (p);
+ CREATE TABLE c3 () INHERITS (p);
+ INSERT INTO c1 SELECT 0, a / 3, a % 3 FROM generate_series(0, 9) a;
+ INSERT INTO c2 SELECT 1, a / 3, a % 3 FROM generate_series(0, 9) a;
+ INSERT INTO c3 SELECT 2, a / 3, a % 3 FROM generate_series(0, 9) a;
+
+ CREATE TABLE table_a (id integer, value text);
+ CREATE TABLE table_b (id integer, value text);
+ INSERT INTO table_a VALUES (1, 'tableAValue');
+ INSERT INTO table_b VALUES (1, 'tableBValue');
+
+ CREATE TABLE jointest AS SELECT generate_series(1,10) AS id, 0 AS data;
+ CREATE INDEX ON jointest(id);
+
+ CREATE TABLE parttbl (a int, b int, c int) PARTITION BY LIST (a);
+ CREATE TABLE parttbl1 PARTITION OF parttbl FOR VALUES IN (1);
+ CREATE TABLE parttbl2 PARTITION OF parttbl FOR VALUES IN (2);
+ INSERT INTO parttbl VALUES (1, 1, 1);
+
+ CREATE TABLE another_parttbl (a int, b int, c int) PARTITION BY LIST (a);
+ CREATE TABLE another_parttbl1 PARTITION OF another_parttbl FOR VALUES IN (1);
+ CREATE TABLE another_parttbl2 PARTITION OF another_parttbl FOR VALUES IN (2);
+ INSERT INTO another_parttbl VALUES (1, 1, 1);
+
+ CREATE FUNCTION noisy_oper(p_comment text, p_a anynonarray, p_op text, p_b anynonarray)
+ RETURNS bool LANGUAGE plpgsql AS $$
+ DECLARE
+ r bool;
+ BEGIN
+ EXECUTE format('SELECT $1 %s $2', p_op) INTO r USING p_a, p_b;
+ RAISE NOTICE '%: % % % % %: %', p_comment, pg_typeof(p_a), p_a, p_op, pg_typeof(p_b), p_b, r;
+ RETURN r;
+ END;$$;
+}
+
+teardown
+{
+ DROP TABLE accounts;
+ DROP FUNCTION update_checking(int);
+ DROP TABLE accounts_ext;
+ DROP TABLE p CASCADE;
+ DROP TABLE table_a, table_b, jointest;
+ DROP TABLE parttbl;
+ DROP TABLE another_parttbl;
+ DROP FUNCTION noisy_oper(text, anynonarray, text, anynonarray)
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
+# wx1 then wx2 checks the basic case of re-fetching up-to-date values
+step wx1 { UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance; }
+# wy1 then wy2 checks the case where quals pass then fail
+step wy1 { UPDATE accounts SET balance = balance + 500 WHERE accountid = 'checking' RETURNING balance; }
+
+step wxext1 { UPDATE accounts_ext SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance; }
+step tocds1 { UPDATE accounts SET accountid = 'cds' WHERE accountid = 'checking'; }
+step tocdsext1 { UPDATE accounts_ext SET accountid = 'cds' WHERE accountid = 'checking'; }
+
+# d1 then wx1 checks that update can deal with the updated row vanishing
+# wx2 then d1 checks that the delete affects the updated row
+# wx2, wx2 then d1 checks that the delete checks the quals correctly (balance too high)
+# wx2, d2, then d1 checks that delete handles a vanishing row correctly
+step d1 { DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; }
+
+# upsert tests are to check writable-CTE cases
+step upsert1 {
+ WITH upsert AS
+ (UPDATE accounts SET balance = balance + 500
+ WHERE accountid = 'savings'
+ RETURNING accountid)
+ INSERT INTO accounts SELECT 'savings', 500
+ WHERE NOT EXISTS (SELECT 1 FROM upsert);
+}
+
+# tests with table p check inheritance cases:
+# readp1/writep1/readp2 tests a bug where nodeLockRows did the wrong thing
+# when the first updated tuple was in a non-first child table.
+# writep2/returningp1 tests a memory allocation issue
+# writep3a/writep3b tests updates touching more than one table
+
+step readp1 { SELECT tableoid::regclass, ctid, * FROM p WHERE b IN (0, 1) AND c = 0 FOR UPDATE; }
+step writep1 { UPDATE p SET b = -1 WHERE a = 1 AND b = 1 AND c = 0; }
+step writep2 { UPDATE p SET b = -b WHERE a = 1 AND c = 0; }
+step writep3a { UPDATE p SET b = -b WHERE c = 0; }
+step c1 { COMMIT; }
+step r1 { ROLLBACK; }
+
+# these tests are meant to exercise EvalPlanQualFetchRowMark,
+# ie, handling non-locked tables in an EvalPlanQual recheck
+
+step partiallock {
+ SELECT * FROM accounts a1, accounts a2
+ WHERE a1.accountid = a2.accountid
+ FOR UPDATE OF a1;
+}
+step lockwithvalues {
+ -- Reference rowmark column that differs in type from targetlist at some attno.
+ -- See CAHU7rYZo_C4ULsAx_LAj8az9zqgrD8WDd4hTegDTMM1LMqrBsg@mail.gmail.com
+ SELECT a1.*, v.id FROM accounts a1, (values('checking'::text, 'nan'::text),('savings', 'nan')) v(id, notnumeric)
+ WHERE a1.accountid = v.id AND v.notnumeric != 'einszwei'
+ FOR UPDATE OF a1;
+}
+step partiallock_ext {
+ SELECT * FROM accounts_ext a1, accounts_ext a2
+ WHERE a1.accountid = a2.accountid
+ FOR UPDATE OF a1;
+}
+
+# these tests exercise EvalPlanQual with a SubLink sub-select (which should be
+# unaffected by any EPQ recheck behavior in the outer query); cf bug #14034
+
+step updateforss {
+ UPDATE table_a SET value = 'newTableAValue' WHERE id = 1;
+ UPDATE table_b SET value = 'newTableBValue' WHERE id = 1;
+}
+
+# these tests exercise EvalPlanQual with conditional InitPlans which
+# have not been executed prior to the EPQ
+
+step updateforcip {
+ UPDATE table_a SET value = NULL WHERE id = 1;
+}
+
+# these tests exercise mark/restore during EPQ recheck, cf bug #15032
+
+step selectjoinforupdate {
+ set local enable_nestloop to 0;
+ set local enable_hashjoin to 0;
+ set local enable_seqscan to 0;
+ explain (costs off)
+ select * from jointest a join jointest b on a.id=b.id for update;
+ select * from jointest a join jointest b on a.id=b.id for update;
+}
+
+# these tests exercise Result plan nodes participating in EPQ
+
+step selectresultforupdate {
+ select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
+ left join table_a a on a.id = x, jointest jt
+ where jt.id = y;
+ explain (verbose, costs off)
+ select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
+ left join table_a a on a.id = x, jointest jt
+ where jt.id = y for update of jt, ss1, ss2;
+ select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
+ left join table_a a on a.id = x, jointest jt
+ where jt.id = y for update of jt, ss1, ss2;
+}
+
+# test for EPQ on a partitioned result table
+
+step simplepartupdate {
+ update parttbl set a = a;
+}
+
+# test scenarios where update may cause row movement
+
+step simplepartupdate_route1to2 {
+ update parttbl set a = 2 where c = 1 returning *;
+}
+
+step simplepartupdate_noroute {
+ update parttbl set b = 2 where c = 1 returning *;
+}
+
+
+session s2
+setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step wx2 { UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; }
+step wy2 { UPDATE accounts SET balance = balance + 1000 WHERE accountid = 'checking' AND balance < 1000 RETURNING balance; }
+step d2 { DELETE FROM accounts WHERE accountid = 'checking'; }
+
+step upsert2 {
+ WITH upsert AS
+ (UPDATE accounts SET balance = balance + 1234
+ WHERE accountid = 'savings'
+ RETURNING accountid)
+ INSERT INTO accounts SELECT 'savings', 1234
+ WHERE NOT EXISTS (SELECT 1 FROM upsert);
+}
+step wx2_ext { UPDATE accounts_ext SET balance = balance + 450; }
+step readp2 { SELECT tableoid::regclass, ctid, * FROM p WHERE b IN (0, 1) AND c = 0 FOR UPDATE; }
+step returningp1 {
+ WITH u AS ( UPDATE p SET b = b WHERE a > 0 RETURNING * )
+ SELECT * FROM u;
+}
+step writep3b { UPDATE p SET b = -b WHERE c = 0; }
+step readforss {
+ SELECT ta.id AS ta_id, ta.value AS ta_value,
+ (SELECT ROW(tb.id, tb.value)
+ FROM table_b tb WHERE ta.id = tb.id) AS tb_row
+ FROM table_a ta
+ WHERE ta.id = 1 FOR UPDATE OF ta;
+}
+step updateforcip2 {
+ UPDATE table_a SET value = COALESCE(value, (SELECT text 'newValue')) WHERE id = 1;
+}
+step updateforcip3 {
+ WITH d(val) AS (SELECT text 'newValue' FROM generate_series(1,1))
+ UPDATE table_a SET value = COALESCE(value, (SELECT val FROM d)) WHERE id = 1;
+}
+step wrtwcte { UPDATE table_a SET value = 'tableAValue2' WHERE id = 1; }
+step wrjt { UPDATE jointest SET data = 42 WHERE id = 7; }
+step complexpartupdate {
+ with u as (update parttbl set a = a returning parttbl.*)
+ update parttbl set a = u.a from u;
+}
+
+step complexpartupdate_route_err1 {
+ with u as (update another_parttbl set a = 1 returning another_parttbl.*)
+ update parttbl p set a = u.a from u where p.a = u.a and p.c = 1 returning p.*;
+}
+
+step complexpartupdate_route {
+ with u as (update another_parttbl set a = 1 returning another_parttbl.*)
+ update parttbl p set a = p.b from u where p.a = u.a and p.c = 1 returning p.*;
+}
+
+step complexpartupdate_doesnt_route {
+ with u as (update another_parttbl set a = 1 returning another_parttbl.*)
+ update parttbl p set a = 3 - p.b from u where p.a = u.a and p.c = 1 returning p.*;
+}
+
+# Use writable CTEs to create self-updated rows, that then are
+# (updated|deleted). The *fail versions of the tests additionally
+# perform an update, via a function, in a different command, to test
+# behaviour relating to that.
+step updwcte { WITH doup AS (UPDATE accounts SET balance = balance + 1100 WHERE accountid = 'checking' RETURNING *) UPDATE accounts a SET balance = doup.balance + 100 FROM doup RETURNING *; }
+step updwctefail { WITH doup AS (UPDATE accounts SET balance = balance + 1100 WHERE accountid = 'checking' RETURNING *, update_checking(999)) UPDATE accounts a SET balance = doup.balance + 100 FROM doup RETURNING *; }
+step delwcte { WITH doup AS (UPDATE accounts SET balance = balance + 1100 WHERE accountid = 'checking' RETURNING *) DELETE FROM accounts a USING doup RETURNING *; }
+step delwctefail { WITH doup AS (UPDATE accounts SET balance = balance + 1100 WHERE accountid = 'checking' RETURNING *, update_checking(999)) DELETE FROM accounts a USING doup RETURNING *; }
+
+# Check that nested EPQ works correctly
+step wnested2 {
+ UPDATE accounts SET balance = balance - 1200
+ WHERE noisy_oper('upid', accountid, '=', 'checking')
+ AND noisy_oper('up', balance, '>', 200.0)
+ AND EXISTS (
+ SELECT accountid
+ FROM accounts_ext ae
+ WHERE noisy_oper('lock_id', ae.accountid, '=', accounts.accountid)
+ AND noisy_oper('lock_bal', ae.balance, '>', 200.0)
+ FOR UPDATE
+ );
+}
+
+step c2 { COMMIT; }
+step r2 { ROLLBACK; }
+
+session s3
+setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step read { SELECT * FROM accounts ORDER BY accountid; }
+step read_ext { SELECT * FROM accounts_ext ORDER BY accountid; }
+step read_a { SELECT * FROM table_a ORDER BY id; }
+
+# this test exercises EvalPlanQual with a CTE, cf bug #14328
+step readwcte {
+ WITH
+ cte1 AS (
+ SELECT id FROM table_b WHERE value = 'tableBValue'
+ ),
+ cte2 AS (
+ SELECT * FROM table_a
+ WHERE id = (SELECT id FROM cte1)
+ FOR UPDATE
+ )
+ SELECT * FROM cte2;
+}
+
+# this test exercises a different CTE misbehavior, cf bug #14870
+step multireadwcte {
+ WITH updated AS (
+ UPDATE table_a SET value = 'tableAValue3' WHERE id = 1 RETURNING id
+ )
+ SELECT (SELECT id FROM updated) AS subid, * FROM updated;
+}
+
+teardown { COMMIT; }
+
+# test that normal update follows update chains, and reverifies quals
+permutation wx1 wx2 c1 c2 read
+permutation wy1 wy2 c1 c2 read
+permutation wx1 wx2 r1 c2 read
+permutation wy1 wy2 r1 c2 read
+
+# test that deletes follow chains, and if necessary reverifies quals
+permutation wx1 d1 wx2 c1 c2 read
+permutation wx2 d1 c2 c1 read
+permutation wx2 wx2 d1 c2 c1 read
+permutation wx2 d2 d1 c2 c1 read
+permutation wx1 d1 wx2 r1 c2 read
+permutation wx2 d1 r2 c1 read
+permutation wx2 wx2 d1 r2 c1 read
+permutation wx2 d2 d1 r2 c1 read
+permutation d1 wx2 c1 c2 read
+permutation d1 wx2 r1 c2 read
+
+# Check that nested EPQ works correctly
+permutation wnested2 c1 c2 read
+permutation wx1 wxext1 wnested2 c1 c2 read
+permutation wx1 wx1 wxext1 wnested2 c1 c2 read
+permutation wx1 wx1 wxext1 wxext1 wnested2 c1 c2 read
+permutation wx1 wxext1 wxext1 wnested2 c1 c2 read
+permutation wx1 tocds1 wnested2 c1 c2 read
+permutation wx1 tocdsext1 wnested2 c1 c2 read
+
+# test that an update to a self-modified row is ignored when
+# previously updated by the same cid
+permutation wx1 updwcte c1 c2 read
+# test that an update to a self-modified row throws error when
+# previously updated by a different cid
+permutation wx1 updwctefail c1 c2 read
+# test that a delete to a self-modified row is ignored when
+# previously updated by the same cid
+permutation wx1 delwcte c1 c2 read
+# test that a delete to a self-modified row throws error when
+# previously updated by a different cid
+permutation wx1 delwctefail c1 c2 read
+
+permutation upsert1 upsert2 c1 c2 read
+permutation readp1 writep1 readp2 c1 c2
+permutation writep2 returningp1 c1 c2
+permutation writep3a writep3b c1 c2
+permutation wx2 partiallock c2 c1 read
+permutation wx2 lockwithvalues c2 c1 read
+permutation wx2_ext partiallock_ext c2 c1 read_ext
+permutation updateforss readforss c1 c2
+permutation updateforcip updateforcip2 c1 c2 read_a
+permutation updateforcip updateforcip3 c1 c2 read_a
+permutation wrtwcte readwcte c1 c2
+permutation wrjt selectjoinforupdate c2 c1
+permutation wrjt selectresultforupdate c2 c1
+permutation wrtwcte multireadwcte c1 c2
+
+permutation simplepartupdate complexpartupdate c1 c2
+permutation simplepartupdate_route1to2 complexpartupdate_route_err1 c1 c2
+permutation simplepartupdate_noroute complexpartupdate_route c1 c2
+permutation simplepartupdate_noroute complexpartupdate_doesnt_route c1 c2
diff --git a/src/test/isolation/specs/fk-contention.spec b/src/test/isolation/specs/fk-contention.spec
new file mode 100644
index 0000000..f11a1d8
--- /dev/null
+++ b/src/test/isolation/specs/fk-contention.spec
@@ -0,0 +1,19 @@
+setup
+{
+ CREATE TABLE foo (a int PRIMARY KEY, b text);
+ CREATE TABLE bar (a int NOT NULL REFERENCES foo);
+ INSERT INTO foo VALUES (42);
+}
+
+teardown
+{
+ DROP TABLE foo, bar;
+}
+
+session s1
+setup { BEGIN; }
+step ins { INSERT INTO bar VALUES (42); }
+step com { COMMIT; }
+
+session s2
+step upd { UPDATE foo SET b = 'Hello World'; }
diff --git a/src/test/isolation/specs/fk-deadlock.spec b/src/test/isolation/specs/fk-deadlock.spec
new file mode 100644
index 0000000..b4970dd
--- /dev/null
+++ b/src/test/isolation/specs/fk-deadlock.spec
@@ -0,0 +1,46 @@
+setup
+{
+ CREATE TABLE parent (
+ parent_key int PRIMARY KEY,
+ aux text NOT NULL
+ );
+
+ CREATE TABLE child (
+ child_key int PRIMARY KEY,
+ parent_key int NOT NULL REFERENCES parent
+ );
+
+ INSERT INTO parent VALUES (1, 'foo');
+}
+
+teardown
+{
+ DROP TABLE parent, child;
+}
+
+session s1
+setup { BEGIN; SET deadlock_timeout = '100ms'; }
+step s1i { INSERT INTO child VALUES (1, 1); }
+step s1u { UPDATE parent SET aux = 'bar'; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN; SET deadlock_timeout = '10s'; }
+step s2i { INSERT INTO child VALUES (2, 1); }
+step s2u { UPDATE parent SET aux = 'baz'; }
+step s2c { COMMIT; }
+
+permutation s1i s1u s1c s2i s2u s2c
+permutation s1i s1u s2i s1c s2u s2c
+permutation s1i s1u s2i s2u s1c s2c
+permutation s1i s2i s1u s1c s2u s2c
+permutation s1i s2i s1u s2u s1c s2c
+permutation s1i s2i s2u s1u s2c s1c
+permutation s1i s2i s2u s2c s1u s1c
+permutation s2i s1i s1u s1c s2u s2c
+permutation s2i s1i s1u s2u s1c s2c
+permutation s2i s1i s2u s1u s2c s1c
+permutation s2i s1i s2u s2c s1u s1c
+permutation s2i s2u s1i s1u s2c s1c
+permutation s2i s2u s1i s2c s1u s1c
+permutation s2i s2u s2c s1i s1u s1c
diff --git a/src/test/isolation/specs/fk-deadlock2.spec b/src/test/isolation/specs/fk-deadlock2.spec
new file mode 100644
index 0000000..c8e0e4e
--- /dev/null
+++ b/src/test/isolation/specs/fk-deadlock2.spec
@@ -0,0 +1,48 @@
+setup
+{
+ CREATE TABLE A (
+ AID integer not null,
+ Col1 integer,
+ PRIMARY KEY (AID)
+ );
+
+ CREATE TABLE B (
+ BID integer not null,
+ AID integer not null,
+ Col2 integer,
+ PRIMARY KEY (BID),
+ FOREIGN KEY (AID) REFERENCES A(AID)
+ );
+
+ INSERT INTO A (AID) VALUES (1);
+ INSERT INTO B (BID,AID) VALUES (2,1);
+}
+
+teardown
+{
+ DROP TABLE a, b;
+}
+
+session s1
+setup { BEGIN; SET deadlock_timeout = '100ms'; }
+step s1u1 { UPDATE A SET Col1 = 1 WHERE AID = 1; }
+step s1u2 { UPDATE B SET Col2 = 1 WHERE BID = 2; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN; SET deadlock_timeout = '10s'; }
+step s2u1 { UPDATE B SET Col2 = 1 WHERE BID = 2; }
+step s2u2 { UPDATE B SET Col2 = 1 WHERE BID = 2; }
+step s2c { COMMIT; }
+
+permutation s1u1 s1u2 s1c s2u1 s2u2 s2c
+permutation s1u1 s1u2 s2u1 s1c s2u2 s2c
+permutation s1u1 s2u1 s1u2 s2u2 s2c s1c
+permutation s1u1 s2u1 s2u2 s1u2 s2c s1c
+permutation s1u1 s2u1 s2u2 s2c s1u2 s1c
+permutation s2u1 s1u1 s1u2 s2u2 s2c s1c
+permutation s2u1 s1u1 s2u2 s1u2 s2c s1c
+permutation s2u1 s1u1 s2u2 s2c s1u2 s1c
+permutation s2u1 s2u2 s1u1 s1u2 s2c s1c
+permutation s2u1 s2u2 s1u1 s2c s1u2 s1c
+permutation s2u1 s2u2 s2c s1u1 s1u2 s1c
diff --git a/src/test/isolation/specs/fk-partitioned-1.spec b/src/test/isolation/specs/fk-partitioned-1.spec
new file mode 100644
index 0000000..f71ee5c
--- /dev/null
+++ b/src/test/isolation/specs/fk-partitioned-1.spec
@@ -0,0 +1,45 @@
+# Verify that cloning a foreign key constraint to a partition ensures
+# that referenced values exist, even if they're being concurrently
+# deleted.
+setup {
+drop table if exists ppk, pfk, pfk1;
+ create table ppk (a int primary key) partition by list (a);
+ create table ppk1 partition of ppk for values in (1);
+ insert into ppk values (1);
+ create table pfk (a int references ppk) partition by list (a);
+ create table pfk1 (a int not null);
+ insert into pfk1 values (1);
+}
+
+session s1
+step s1b { begin; }
+step s1d { delete from ppk1 where a = 1; }
+step s1c { commit; }
+
+session s2
+step s2b { begin; }
+step s2a { alter table pfk attach partition pfk1 for values in (1); }
+step s2c { commit; }
+
+teardown { drop table ppk, pfk, pfk1; }
+
+permutation s1b s1d s1c s2b s2a s2c
+permutation s1b s1d s2b s1c s2a s2c
+permutation s1b s1d s2b s2a s1c s2c
+#permutation s1b s1d s2b s2a s2c s1c
+permutation s1b s2b s1d s1c s2a s2c
+permutation s1b s2b s1d s2a s1c s2c
+#permutation s1b s2b s1d s2a s2c s1c
+#permutation s1b s2b s2a s1d s1c s2c
+permutation s1b s2b s2a s1d s2c s1c
+permutation s1b s2b s2a s2c s1d s1c
+permutation s2b s1b s1d s1c s2a s2c
+permutation s2b s1b s1d s2a s1c s2c
+#permutation s2b s1b s1d s2a s2c s1c
+#permutation s2b s1b s2a s1d s1c s2c
+permutation s2b s1b s2a s1d s2c s1c
+permutation s2b s1b s2a s2c s1d s1c
+#permutation s2b s2a s1b s1d s1c s2c
+permutation s2b s2a s1b s1d s2c s1c
+permutation s2b s2a s1b s2c s1d s1c
+permutation s2b s2a s2c s1b s1d s1c
diff --git a/src/test/isolation/specs/fk-partitioned-2.spec b/src/test/isolation/specs/fk-partitioned-2.spec
new file mode 100644
index 0000000..209ad59
--- /dev/null
+++ b/src/test/isolation/specs/fk-partitioned-2.spec
@@ -0,0 +1,29 @@
+# Make sure that FKs referencing partitioned tables actually work.
+setup {
+ drop table if exists ppk, pfk, pfk1;
+ create table ppk (a int primary key) partition by list (a);
+ create table ppk1 partition of ppk for values in (1);
+ insert into ppk values (1);
+ create table pfk (a int references ppk) partition by list (a);
+ create table pfk1 partition of pfk for values in (1);
+}
+
+session s1
+step s1b { begin; }
+step s1d { delete from ppk where a = 1; }
+step s1c { commit; }
+
+session s2
+step s2b { begin; }
+step s2bs { begin isolation level serializable; select 1; }
+step s2i { insert into pfk values (1); }
+step s2c { commit; }
+
+teardown { drop table ppk, pfk, pfk1; }
+
+permutation s1b s1d s2b s2i s1c s2c
+permutation s1b s1d s2bs s2i s1c s2c
+permutation s1b s2b s1d s2i s1c s2c
+permutation s1b s2bs s1d s2i s1c s2c
+permutation s1b s2b s2i s1d s2c s1c
+permutation s1b s2bs s2i s1d s2c s1c
diff --git a/src/test/isolation/specs/freeze-the-dead.spec b/src/test/isolation/specs/freeze-the-dead.spec
new file mode 100644
index 0000000..6c34904
--- /dev/null
+++ b/src/test/isolation/specs/freeze-the-dead.spec
@@ -0,0 +1,56 @@
+# Test for interactions of tuple freezing with dead, as well as recently-dead
+# tuples using multixacts via FOR KEY SHARE.
+setup
+{
+ CREATE TABLE tab_freeze (
+ id int PRIMARY KEY,
+ name char(3),
+ x int);
+ INSERT INTO tab_freeze VALUES (1, '111', 0);
+ INSERT INTO tab_freeze VALUES (3, '333', 0);
+}
+
+teardown
+{
+ DROP TABLE tab_freeze;
+}
+
+session s1
+step s1_begin { BEGIN; }
+step s1_update { UPDATE tab_freeze SET x = x + 1 WHERE id = 3; }
+step s1_commit { COMMIT; }
+step s1_selectone {
+ BEGIN;
+ SET LOCAL enable_seqscan = false;
+ SET LOCAL enable_bitmapscan = false;
+ SELECT * FROM tab_freeze WHERE id = 3;
+ COMMIT;
+}
+step s1_selectall { SELECT * FROM tab_freeze ORDER BY name, id; }
+
+session s2
+step s2_begin { BEGIN; }
+step s2_key_share { SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; }
+step s2_commit { COMMIT; }
+step s2_vacuum { VACUUM FREEZE tab_freeze; }
+
+session s3
+step s3_begin { BEGIN; }
+step s3_key_share { SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; }
+step s3_commit { COMMIT; }
+
+# This permutation verifies that a previous bug
+# https://postgr.es/m/E5711E62-8FDF-4DCA-A888-C200BF6B5742@amazon.com
+# https://postgr.es/m/20171102112019.33wb7g5wp4zpjelu@alap3.anarazel.de
+# is not reintroduced. We used to make wrong pruning / freezing
+# decision for multixacts, which could lead to a) broken hot chains b)
+# dead rows being revived.
+permutation s1_begin s2_begin s3_begin # start transactions
+ s1_update s2_key_share s3_key_share # have xmax be a multi with an updater, updater being oldest xid
+ s1_update # create additional row version that has multis
+ s1_commit s2_commit # commit both updater and share locker
+ s2_vacuum # due to bug in freezing logic, we used to *not* prune updated row, and then froze it
+ s1_selectone # if hot chain is broken, the row can't be found via index scan
+ s3_commit # commit remaining open xact
+ s2_vacuum # pruning / freezing in broken hot chains would unset xmax, reviving rows
+ s1_selectall # show borkedness
diff --git a/src/test/isolation/specs/horizons.spec b/src/test/isolation/specs/horizons.spec
new file mode 100644
index 0000000..d5239ff
--- /dev/null
+++ b/src/test/isolation/specs/horizons.spec
@@ -0,0 +1,169 @@
+# Test that pruning and vacuuming pay attention to concurrent sessions
+# in the right way. For normal relations that means that rows cannot
+# be pruned away if there's an older snapshot, in contrast to that
+# temporary tables should nearly always be prunable.
+#
+# NB: Think hard before adding a test showing that rows in permanent
+# tables get pruned - it's quite likely that it'd be racy, e.g. due to
+# an autovacuum worker holding a snapshot.
+
+setup {
+ CREATE OR REPLACE FUNCTION explain_json(p_query text)
+ RETURNS json
+ LANGUAGE plpgsql AS $$
+ DECLARE
+ v_ret json;
+ BEGIN
+ EXECUTE p_query INTO STRICT v_ret;
+ RETURN v_ret;
+ END;$$;
+}
+
+teardown {
+ DROP FUNCTION explain_json(text);
+}
+
+session lifeline
+
+# Start a transaction, force a snapshot to be held
+step ll_start
+{
+ BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ SELECT 1;
+}
+
+step ll_commit { COMMIT; }
+
+
+session pruner
+
+setup
+{
+ SET enable_seqscan = false;
+ SET enable_indexscan = false;
+ SET enable_bitmapscan = false;
+}
+
+step pruner_create_temp
+{
+ CREATE TEMPORARY TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off);
+ INSERT INTO horizons_tst(data) VALUES(1),(2);
+}
+
+step pruner_create_perm
+{
+ CREATE TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off);
+ INSERT INTO horizons_tst(data) VALUES(1),(2);
+}
+
+# Temp tables cannot be dropped in the teardown, so just always do so
+# as part of the permutation
+step pruner_drop
+{
+ DROP TABLE horizons_tst;
+}
+
+step pruner_delete
+{
+ DELETE FROM horizons_tst;
+}
+
+step pruner_begin { BEGIN; }
+step pruner_commit { COMMIT; }
+
+step pruner_vacuum
+{
+ VACUUM horizons_tst;
+}
+
+# Show the heap fetches of an ordered index-only-scan (other plans
+# have been forbidden above) - that tells us how many non-killed leaf
+# entries there are.
+step pruner_query
+{
+ SELECT explain_json($$
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
+}
+
+# Verify that the query plan still is an IOS
+step pruner_query_plan
+{
+ EXPLAIN (COSTS OFF) SELECT * FROM horizons_tst ORDER BY data;
+}
+
+
+# Show that with a permanent relation deleted rows cannot be pruned
+# away if there's a concurrent session still seeing the rows.
+permutation
+ pruner_create_perm
+ ll_start
+ pruner_query_plan
+ # Run query that could do pruning twice, first has chance to prune,
+ # second would not perform heap fetches if first query did.
+ pruner_query
+ pruner_query
+ pruner_delete
+ pruner_query
+ pruner_query
+ ll_commit
+ pruner_drop
+
+# Show that with a temporary relation deleted rows can be pruned away,
+# even if there's a concurrent session with a snapshot from before the
+# deletion. That's safe because the session with the older snapshot
+# cannot access the temporary table.
+permutation
+ pruner_create_temp
+ ll_start
+ pruner_query_plan
+ pruner_query
+ pruner_query
+ pruner_delete
+ pruner_query
+ pruner_query
+ ll_commit
+ pruner_drop
+
+# Verify that pruning in temporary relations doesn't remove rows still
+# visible in the current session
+permutation
+ pruner_create_temp
+ ll_start
+ pruner_query
+ pruner_query
+ pruner_begin
+ pruner_delete
+ pruner_query
+ pruner_query
+ ll_commit
+ pruner_commit
+ pruner_drop
+
+# Show that vacuum cannot remove deleted rows still visible to another
+# session's snapshot, when accessing a permanent table.
+permutation
+ pruner_create_perm
+ ll_start
+ pruner_query
+ pruner_query
+ pruner_delete
+ pruner_vacuum
+ pruner_query
+ pruner_query
+ ll_commit
+ pruner_drop
+
+# Show that vacuum can remove deleted rows still visible to another
+# session's snapshot, when accessing a temporary table.
+permutation
+ pruner_create_temp
+ ll_start
+ pruner_query
+ pruner_query
+ pruner_delete
+ pruner_vacuum
+ pruner_query
+ pruner_query
+ ll_commit
+ pruner_drop
diff --git a/src/test/isolation/specs/index-only-scan.spec b/src/test/isolation/specs/index-only-scan.spec
new file mode 100644
index 0000000..4e4171c
--- /dev/null
+++ b/src/test/isolation/specs/index-only-scan.spec
@@ -0,0 +1,46 @@
+# index-only scan test
+#
+# This test tries to expose problems with the interaction between index-only
+# scans and SSI.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE tabx (id int NOT NULL);
+ INSERT INTO tabx SELECT generate_series(1,10000);
+ ALTER TABLE tabx ADD PRIMARY KEY (id);
+ CREATE TABLE taby (id int NOT NULL);
+ INSERT INTO taby SELECT generate_series(1,10000);
+ ALTER TABLE taby ADD PRIMARY KEY (id);
+}
+setup { VACUUM FREEZE ANALYZE tabx; }
+setup { VACUUM FREEZE ANALYZE taby; }
+
+teardown
+{
+ DROP TABLE tabx;
+ DROP TABLE taby;
+}
+
+session s1
+setup
+{
+ BEGIN ISOLATION LEVEL SERIALIZABLE;
+ SET LOCAL seq_page_cost = 0.1;
+ SET LOCAL random_page_cost = 0.1;
+ SET LOCAL cpu_tuple_cost = 0.03;
+}
+step rxwy1 { DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx); }
+step c1 { COMMIT; }
+
+session s2
+setup
+{
+ BEGIN ISOLATION LEVEL SERIALIZABLE;
+ SET LOCAL seq_page_cost = 0.1;
+ SET LOCAL random_page_cost = 0.1;
+ SET LOCAL cpu_tuple_cost = 0.03;
+}
+step rywx2 { DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby); }
+step c2 { COMMIT; }
diff --git a/src/test/isolation/specs/inherit-temp.spec b/src/test/isolation/specs/inherit-temp.spec
new file mode 100644
index 0000000..644f919
--- /dev/null
+++ b/src/test/isolation/specs/inherit-temp.spec
@@ -0,0 +1,78 @@
+# Tests for inheritance trees with temporary relations
+#
+# Inheritance trees are allowed to mix relations with different persistence
+# as long as a persistent child relation does not try to inherit from a
+# temporary parent. This checks several scenarios with SELECT, INSERT, UPDATE,
+# DELETE and TRUNCATE. Any temporary relation inheriting from the same
+# persistent parent should be isolated and handled only in its own session.
+
+setup
+{
+ CREATE TABLE inh_parent (a int);
+}
+
+teardown
+{
+ DROP TABLE inh_parent;
+}
+
+# Session 1 executes actions which act directly on both the parent and
+# its child. Abbreviation "c" is used for queries working on the child
+# and "p" on the parent.
+session s1
+setup
+{
+ CREATE TEMPORARY TABLE inh_temp_child_s1 () INHERITS (inh_parent);
+}
+step s1_begin { BEGIN; }
+step s1_truncate_p { TRUNCATE inh_parent; }
+step s1_select_p { SELECT a FROM inh_parent; }
+step s1_select_c { SELECT a FROM inh_temp_child_s1; }
+step s1_insert_p { INSERT INTO inh_parent VALUES (1), (2); }
+step s1_insert_c { INSERT INTO inh_temp_child_s1 VALUES (3), (4); }
+step s1_update_p { UPDATE inh_parent SET a = 11 WHERE a = 1; }
+step s1_update_c { UPDATE inh_parent SET a = 13 WHERE a IN (3, 5); }
+step s1_delete_p { DELETE FROM inh_parent WHERE a = 2; }
+step s1_delete_c { DELETE FROM inh_parent WHERE a IN (4, 6); }
+step s1_commit { COMMIT; }
+teardown
+{
+ DROP TABLE inh_temp_child_s1;
+}
+
+# Session 2 executes actions on the parent which act only on the child.
+session s2
+setup
+{
+ CREATE TEMPORARY TABLE inh_temp_child_s2 () INHERITS (inh_parent);
+}
+step s2_truncate_p { TRUNCATE inh_parent; }
+step s2_select_p { SELECT a FROM inh_parent; }
+step s2_select_c { SELECT a FROM inh_temp_child_s2; }
+step s2_insert_c { INSERT INTO inh_temp_child_s2 VALUES (5), (6); }
+step s2_update_c { UPDATE inh_parent SET a = 15 WHERE a IN (3, 5); }
+step s2_delete_c { DELETE FROM inh_parent WHERE a IN (4, 6); }
+teardown
+{
+ DROP TABLE inh_temp_child_s2;
+}
+
+# Check INSERT behavior across sessions
+permutation s1_insert_p s1_insert_c s2_insert_c s1_select_p s1_select_c s2_select_p s2_select_c
+
+# Check UPDATE behavior across sessions
+permutation s1_insert_p s1_insert_c s2_insert_c s1_update_p s1_update_c s1_select_p s1_select_c s2_select_p s2_select_c
+permutation s1_insert_p s1_insert_c s2_insert_c s2_update_c s1_select_p s1_select_c s2_select_p s2_select_c
+
+# Check DELETE behavior across sessions
+permutation s1_insert_p s1_insert_c s2_insert_c s1_delete_p s1_delete_c s1_select_p s1_select_c s2_select_p s2_select_c
+permutation s1_insert_p s1_insert_c s2_insert_c s2_delete_c s1_select_p s1_select_c s2_select_p s2_select_c
+
+# Check TRUNCATE behavior across sessions
+permutation s1_insert_p s1_insert_c s2_insert_c s1_truncate_p s1_select_p s1_select_c s2_select_p s2_select_c
+permutation s1_insert_p s1_insert_c s2_insert_c s2_truncate_p s1_select_p s1_select_c s2_select_p s2_select_c
+
+# TRUNCATE on a parent tree does not block access to temporary child relation
+# of another session, and blocks when scanning the parent.
+permutation s1_insert_p s1_insert_c s2_insert_c s1_begin s1_truncate_p s2_select_p s1_commit
+permutation s1_insert_p s1_insert_c s2_insert_c s1_begin s1_truncate_p s2_select_c s1_commit
diff --git a/src/test/isolation/specs/insert-conflict-do-nothing-2.spec b/src/test/isolation/specs/insert-conflict-do-nothing-2.spec
new file mode 100644
index 0000000..825b7d6
--- /dev/null
+++ b/src/test/isolation/specs/insert-conflict-do-nothing-2.spec
@@ -0,0 +1,34 @@
+# INSERT...ON CONFLICT DO NOTHING test with multiple rows
+# in higher isolation levels
+
+setup
+{
+ CREATE TABLE ints (key int, val text, PRIMARY KEY (key) INCLUDE (val));
+}
+
+teardown
+{
+ DROP TABLE ints;
+}
+
+session s1
+step beginrr1 { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step begins1 { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step donothing1 { INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING; }
+step c1 { COMMIT; }
+step show { SELECT * FROM ints; }
+
+session s2
+step beginrr2 { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step begins2 { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step donothing2 { INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING; }
+step c2 { COMMIT; }
+
+permutation beginrr1 beginrr2 donothing1 c1 donothing2 c2 show
+permutation beginrr1 beginrr2 donothing2 c2 donothing1 c1 show
+permutation beginrr1 beginrr2 donothing1 donothing2 c1 c2 show
+permutation beginrr1 beginrr2 donothing2 donothing1 c2 c1 show
+permutation begins1 begins2 donothing1 c1 donothing2 c2 show
+permutation begins1 begins2 donothing2 c2 donothing1 c1 show
+permutation begins1 begins2 donothing1 donothing2 c1 c2 show
+permutation begins1 begins2 donothing2 donothing1 c2 c1 show
diff --git a/src/test/isolation/specs/insert-conflict-do-nothing.spec b/src/test/isolation/specs/insert-conflict-do-nothing.spec
new file mode 100644
index 0000000..b0e6a37
--- /dev/null
+++ b/src/test/isolation/specs/insert-conflict-do-nothing.spec
@@ -0,0 +1,40 @@
+# INSERT...ON CONFLICT DO NOTHING test
+#
+# This test tries to expose problems with the interaction between concurrent
+# sessions during INSERT...ON CONFLICT DO NOTHING.
+#
+# The convention here is that session 1 always ends up inserting, and session 2
+# always ends up doing nothing.
+
+setup
+{
+ CREATE TABLE ints (key int primary key, val text);
+}
+
+teardown
+{
+ DROP TABLE ints;
+}
+
+session s1
+setup
+{
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+}
+step donothing1 { INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING; }
+step c1 { COMMIT; }
+step a1 { ABORT; }
+
+session s2
+setup
+{
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+}
+step donothing2 { INSERT INTO ints(key, val) VALUES(1, 'donothing2') ON CONFLICT DO NOTHING; }
+step select2 { SELECT * FROM ints; }
+step c2 { COMMIT; }
+
+# Regular case where one session block-waits on another to determine if it
+# should proceed with an insert or do nothing.
+permutation donothing1 donothing2 c1 select2 c2
+permutation donothing1 donothing2 a1 select2 c2
diff --git a/src/test/isolation/specs/insert-conflict-do-update-2.spec b/src/test/isolation/specs/insert-conflict-do-update-2.spec
new file mode 100644
index 0000000..8a7c546
--- /dev/null
+++ b/src/test/isolation/specs/insert-conflict-do-update-2.spec
@@ -0,0 +1,40 @@
+# INSERT...ON CONFLICT DO UPDATE test
+#
+# This test shows a plausible scenario in which the user might wish to UPDATE a
+# value that is also constrained by the unique index that is the arbiter of
+# whether the alternative path should be taken.
+
+setup
+{
+ CREATE TABLE upsert (key text not null, payload text);
+ CREATE UNIQUE INDEX ON upsert(lower(key)) INCLUDE (payload);
+}
+
+teardown
+{
+ DROP TABLE upsert;
+}
+
+session s1
+setup
+{
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+}
+step insert1 { INSERT INTO upsert(key, payload) VALUES('FooFoo', 'insert1') ON CONFLICT (lower(key)) DO UPDATE set key = EXCLUDED.key, payload = upsert.payload || ' updated by insert1'; }
+step c1 { COMMIT; }
+step a1 { ABORT; }
+
+session s2
+setup
+{
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+}
+step insert2 { INSERT INTO upsert(key, payload) VALUES('FOOFOO', 'insert2') ON CONFLICT (lower(key)) DO UPDATE set key = EXCLUDED.key, payload = upsert.payload || ' updated by insert2'; }
+step select2 { SELECT * FROM upsert; }
+step c2 { COMMIT; }
+
+# One session (session 2) block-waits on another (session 1) to determine if it
+# should proceed with an insert or update. The user can still usefully UPDATE
+# a column constrained by a unique index, as the example illustrates.
+permutation insert1 insert2 c1 select2 c2
+permutation insert1 insert2 a1 select2 c2
diff --git a/src/test/isolation/specs/insert-conflict-do-update-3.spec b/src/test/isolation/specs/insert-conflict-do-update-3.spec
new file mode 100644
index 0000000..df67954
--- /dev/null
+++ b/src/test/isolation/specs/insert-conflict-do-update-3.spec
@@ -0,0 +1,69 @@
+# INSERT...ON CONFLICT DO UPDATE test
+#
+# Other INSERT...ON CONFLICT DO UPDATE isolation tests illustrate the "MVCC
+# violation" added to facilitate the feature, whereby a
+# not-visible-to-our-snapshot tuple can be updated by our command all the same.
+# This is generally needed to provide a guarantee of a successful INSERT or
+# UPDATE in READ COMMITTED mode. This MVCC violation is quite distinct from
+# the putative "MVCC violation" that has existed in PostgreSQL for many years,
+# the EvalPlanQual() mechanism, because that mechanism always starts from a
+# tuple that is visible to the command's MVCC snapshot. This test illustrates
+# a slightly distinct user-visible consequence of the same MVCC violation
+# generally associated with INSERT...ON CONFLICT DO UPDATE. The impact of the
+# MVCC violation goes a little beyond updating MVCC-invisible tuples.
+#
+# With INSERT...ON CONFLICT DO UPDATE, the UPDATE predicate is only evaluated
+# once, on this conclusively-locked tuple, and not any other version of the
+# same tuple. It is therefore possible (in READ COMMITTED mode) that the
+# predicate "fail to be satisfied" according to the command's MVCC snapshot.
+# It might simply be that there is no row version visible, but it's also
+# possible that there is some row version visible, but only as a version that
+# doesn't satisfy the predicate. If, however, the conclusively-locked version
+# satisfies the predicate, that's good enough, and the tuple is updated. The
+# MVCC-snapshot-visible row version is denied the opportunity to prevent the
+# UPDATE from taking place, because we don't walk the UPDATE chain in the usual
+# way.
+
+setup
+{
+ CREATE TABLE colors (key int4 PRIMARY KEY, color text, is_active boolean);
+ INSERT INTO colors (key, color, is_active) VALUES(1, 'Red', false);
+ INSERT INTO colors (key, color, is_active) VALUES(2, 'Green', false);
+ INSERT INTO colors (key, color, is_active) VALUES(3, 'Blue', false);
+}
+
+teardown
+{
+ DROP TABLE colors;
+}
+
+session s1
+setup
+{
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+}
+step insert1 {
+ WITH t AS (
+ INSERT INTO colors(key, color, is_active)
+ VALUES(1, 'Brown', true), (2, 'Gray', true)
+ ON CONFLICT (key) DO UPDATE
+ SET color = EXCLUDED.color
+ WHERE colors.is_active)
+ SELECT * FROM colors ORDER BY key;}
+step select1surprise { SELECT * FROM colors ORDER BY key; }
+step c1 { COMMIT; }
+
+session s2
+setup
+{
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+}
+step update2 { UPDATE colors SET is_active = true WHERE key = 1; }
+step c2 { COMMIT; }
+
+# Perhaps surprisingly, the session 1 MVCC-snapshot-visible tuple (the tuple
+# with the pre-populated color 'Red') is denied the opportunity to prevent the
+# UPDATE from taking place -- only the conclusively-locked tuple version
+# matters, and so the tuple with key value 1 was updated to 'Brown' (but not
+# tuple with key value 2, since nothing changed there):
+permutation update2 insert1 c2 select1surprise c1
diff --git a/src/test/isolation/specs/insert-conflict-do-update.spec b/src/test/isolation/specs/insert-conflict-do-update.spec
new file mode 100644
index 0000000..62cdafd
--- /dev/null
+++ b/src/test/isolation/specs/insert-conflict-do-update.spec
@@ -0,0 +1,39 @@
+# INSERT...ON CONFLICT DO UPDATE test
+#
+# This test tries to expose problems with the interaction between concurrent
+# sessions.
+
+setup
+{
+ CREATE TABLE upsert (key int primary key, val text);
+}
+
+teardown
+{
+ DROP TABLE upsert;
+}
+
+session s1
+setup
+{
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+}
+step insert1 { INSERT INTO upsert(key, val) VALUES(1, 'insert1') ON CONFLICT (key) DO UPDATE set val = upsert.val || ' updated by insert1'; }
+step c1 { COMMIT; }
+step a1 { ABORT; }
+
+session s2
+setup
+{
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+}
+step insert2 { INSERT INTO upsert(key, val) VALUES(1, 'insert2') ON CONFLICT (key) DO UPDATE set val = upsert.val || ' updated by insert2'; }
+step select2 { SELECT * FROM upsert; }
+step c2 { COMMIT; }
+
+# One session (session 2) block-waits on another (session 1) to determine if it
+# should proceed with an insert or update. Notably, this entails updating a
+# tuple while there is no version of that tuple visible to the updating
+# session's snapshot. This is permitted only in READ COMMITTED mode.
+permutation insert1 insert2 c1 select2 c2
+permutation insert1 insert2 a1 select2 c2
diff --git a/src/test/isolation/specs/insert-conflict-specconflict.spec b/src/test/isolation/specs/insert-conflict-specconflict.spec
new file mode 100644
index 0000000..0d55a01
--- /dev/null
+++ b/src/test/isolation/specs/insert-conflict-specconflict.spec
@@ -0,0 +1,259 @@
+# INSERT ... ON CONFLICT test verifying that speculative insertion
+# failures are handled
+#
+# Does this by using advisory locks controlling progress of
+# insertions. By waiting when building the index keys, it's possible
+# to schedule concurrent INSERT ON CONFLICTs so that there will always
+# be a speculative conflict.
+
+setup
+{
+ CREATE OR REPLACE FUNCTION blurt_and_lock_123(text) RETURNS text IMMUTABLE LANGUAGE plpgsql AS $$
+ BEGIN
+ RAISE NOTICE 'blurt_and_lock_123() called for % in session %', $1, current_setting('spec.session')::int;
+
+ -- depending on lock state, wait for lock 2 or 3
+ IF pg_try_advisory_xact_lock(current_setting('spec.session')::int, 1) THEN
+ RAISE NOTICE 'acquiring advisory lock on 2';
+ PERFORM pg_advisory_xact_lock(current_setting('spec.session')::int, 2);
+ ELSE
+ RAISE NOTICE 'acquiring advisory lock on 3';
+ PERFORM pg_advisory_xact_lock(current_setting('spec.session')::int, 3);
+ END IF;
+ RETURN $1;
+ END;$$;
+
+ CREATE OR REPLACE FUNCTION blurt_and_lock_4(text) RETURNS text IMMUTABLE LANGUAGE plpgsql AS $$
+ BEGIN
+ RAISE NOTICE 'blurt_and_lock_4() called for % in session %', $1, current_setting('spec.session')::int;
+ RAISE NOTICE 'acquiring advisory lock on 4';
+ PERFORM pg_advisory_xact_lock(current_setting('spec.session')::int, 4);
+ RETURN $1;
+ END;$$;
+
+ CREATE OR REPLACE FUNCTION ctoast_large_val() RETURNS TEXT LANGUAGE SQL AS 'select array_agg(md5(g::text))::text from generate_series(1, 256) g';
+
+ CREATE TABLE upserttest(key text, data text);
+
+ CREATE UNIQUE INDEX upserttest_key_uniq_idx ON upserttest((blurt_and_lock_123(key)));
+}
+
+teardown
+{
+ DROP TABLE upserttest;
+}
+
+session controller
+setup
+{
+ SET default_transaction_isolation = 'read committed';
+}
+step controller_locks {SELECT pg_advisory_lock(sess, lock), sess, lock FROM generate_series(1, 2) a(sess), generate_series(1,3) b(lock);}
+step controller_unlock_1_1 { SELECT pg_advisory_unlock(1, 1); }
+step controller_unlock_2_1 { SELECT pg_advisory_unlock(2, 1); }
+step controller_unlock_1_2 { SELECT pg_advisory_unlock(1, 2); }
+step controller_unlock_2_2 { SELECT pg_advisory_unlock(2, 2); }
+step controller_unlock_1_3 { SELECT pg_advisory_unlock(1, 3); }
+step controller_unlock_2_3 { SELECT pg_advisory_unlock(2, 3); }
+step controller_lock_2_4 { SELECT pg_advisory_lock(2, 4); }
+step controller_unlock_2_4 { SELECT pg_advisory_unlock(2, 4); }
+step controller_show {SELECT * FROM upserttest; }
+step controller_show_count {SELECT COUNT(*) FROM upserttest; }
+step controller_print_speculative_locks {
+ SELECT pa.application_name, locktype, mode, granted
+ FROM pg_locks pl JOIN pg_stat_activity pa USING (pid)
+ WHERE
+ locktype IN ('spectoken', 'transactionid')
+ AND pa.datname = current_database()
+ AND pa.application_name LIKE 'isolation/insert-conflict-specconflict/s%'
+ ORDER BY 1, 2, 3, 4;
+}
+
+session s1
+setup
+{
+ SET default_transaction_isolation = 'read committed';
+ SET spec.session = 1;
+}
+step s1_begin { BEGIN; }
+step s1_create_non_unique_index { CREATE INDEX upserttest_key_idx ON upserttest((blurt_and_lock_4(key))); }
+step s1_confirm_index_order { SELECT 'upserttest_key_uniq_idx'::regclass::int8 < 'upserttest_key_idx'::regclass::int8; }
+step s1_upsert { INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s1') ON CONFLICT (blurt_and_lock_123(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s1'; }
+step s1_insert_toast { INSERT INTO upserttest VALUES('k2', ctoast_large_val()) ON CONFLICT DO NOTHING; }
+step s1_commit { COMMIT; }
+step s1_noop { }
+
+session s2
+setup
+{
+ SET default_transaction_isolation = 'read committed';
+ SET spec.session = 2;
+}
+step s2_begin { BEGIN; }
+step s2_upsert { INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s2') ON CONFLICT (blurt_and_lock_123(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s2'; }
+step s2_insert_toast { INSERT INTO upserttest VALUES('k2', ctoast_large_val()) ON CONFLICT DO NOTHING; }
+step s2_commit { COMMIT; }
+step s2_noop { }
+
+# Test that speculative locks are correctly acquired and released, s2
+# inserts, s1 updates.
+permutation
+ # acquire a number of locks, to control execution flow - the
+ # blurt_and_lock_123 function acquires advisory locks that allow us to
+ # continue after a) the optimistic conflict probe b) after the
+ # insertion of the speculative tuple.
+ controller_locks
+ controller_show
+ s1_upsert s2_upsert
+ controller_show
+ # Switch both sessions to wait on the other lock next time (the speculative insertion)
+ controller_unlock_1_1 controller_unlock_2_1
+ # Allow both sessions to continue
+ controller_unlock_1_3 controller_unlock_2_3
+ controller_show
+ # Allow the second session to finish insertion
+ controller_unlock_2_2
+ # This should now show a successful insertion
+ controller_show
+ # Allow the first session to finish insertion
+ controller_unlock_1_2
+ # This should now show a successful UPSERT
+ controller_show
+
+# Test that speculative locks are correctly acquired and released, s1
+# inserts, s2 updates.
+permutation
+ # acquire a number of locks, to control execution flow - the
+ # blurt_and_lock_123 function acquires advisory locks that allow us to
+ # continue after a) the optimistic conflict probe b) after the
+ # insertion of the speculative tuple.
+ controller_locks
+ controller_show
+ s1_upsert s2_upsert
+ controller_show
+ # Switch both sessions to wait on the other lock next time (the speculative insertion)
+ controller_unlock_1_1 controller_unlock_2_1
+ # Allow both sessions to continue
+ controller_unlock_1_3 controller_unlock_2_3
+ controller_show
+ # Allow the first session to finish insertion
+ controller_unlock_1_2
+ # This should now show a successful insertion
+ controller_show
+ # Allow the second session to finish insertion
+ controller_unlock_2_2
+ # This should now show a successful UPSERT
+ controller_show
+
+# Test that speculatively inserted toast rows do not cause conflicts.
+# s1 inserts successfully, s2 does not.
+permutation
+ # acquire a number of locks, to control execution flow - the
+ # blurt_and_lock_123 function acquires advisory locks that allow us to
+ # continue after a) the optimistic conflict probe b) after the
+ # insertion of the speculative tuple.
+ controller_locks
+ controller_show
+ s1_insert_toast s2_insert_toast
+ controller_show
+ # Switch both sessions to wait on the other lock next time (the speculative insertion)
+ controller_unlock_1_1 controller_unlock_2_1
+ # Allow both sessions to continue
+ controller_unlock_1_3 controller_unlock_2_3
+ controller_show
+ # Allow the first session to finish insertion
+ controller_unlock_1_2
+ # This should now show that 1 additional tuple was inserted successfully
+ controller_show_count
+ # Allow the second session to finish insertion and kill the speculatively inserted tuple
+ controller_unlock_2_2
+ # This should show the same number of tuples as before s2 inserted
+ controller_show_count
+
+# Test that speculative locks are correctly acquired and released, s2
+# inserts, s1 updates. With the added complication that transactions
+# don't immediately commit.
+permutation
+ # acquire a number of locks, to control execution flow - the
+ # blurt_and_lock_123 function acquires advisory locks that allow us to
+ # continue after a) the optimistic conflict probe b) after the
+ # insertion of the speculative tuple.
+ controller_locks
+ controller_show
+ s1_begin s2_begin
+ s1_upsert s2_upsert
+ controller_show
+ # Switch both sessions to wait on the other lock next time (the speculative insertion)
+ controller_unlock_1_1 controller_unlock_2_1
+ # Allow both sessions to continue
+ controller_unlock_1_3 controller_unlock_2_3
+ controller_show
+ # Allow the first session to finish insertion
+ controller_unlock_1_2
+ # But the change isn't visible yet, nor should the second session continue
+ controller_show
+ # Allow the second session to finish insertion, but it's blocked
+ controller_unlock_2_2
+ controller_show
+ # But committing should unblock
+ s1_commit
+ controller_show
+ s2_commit
+ controller_show
+
+# Test that speculative wait is performed if a session sees a speculatively
+# inserted tuple. A speculatively inserted tuple is one which has been inserted
+# both into the table and the unique index but has yet to *complete* the
+# speculative insertion
+permutation
+ # acquire a number of advisory locks to control execution flow - the
+ # blurt_and_lock_123 function acquires advisory locks that allow us to
+ # continue after a) the optimistic conflict probe and b) after the
+ # insertion of the speculative tuple.
+ # blurt_and_lock_4 acquires an advisory lock which allows us to pause
+ # execution c) before completing the speculative insertion
+
+ # create the second index here to avoid affecting the other
+ # permutations.
+ s1_create_non_unique_index
+ # confirm that the insertion into the unique index will happen first
+ s1_confirm_index_order
+ controller_locks
+ controller_show
+ s2_begin
+ # Both sessions wait on advisory locks
+ # (but don't show s2_upsert as complete till we've seen all of s1's notices)
+ s1_upsert s2_upsert (s1_upsert notices 10)
+ controller_show
+ # Switch both sessions to wait on the other lock next time (the speculative insertion)
+ controller_unlock_1_1 controller_unlock_2_1
+ # Allow both sessions to do the optimistic conflict probe and do the
+ # speculative insertion into the table
+ # They will then be waiting on another advisory lock when they attempt to
+ # update the index
+ controller_unlock_1_3 controller_unlock_2_3
+ controller_show
+ # take lock to block second session after inserting in unique index but
+ # before completing the speculative insert
+ controller_lock_2_4
+ # Allow the second session to move forward
+ controller_unlock_2_2
+ # This should still not show a successful insertion
+ controller_show
+ # Allow the first session to continue, it should perform speculative wait
+ controller_unlock_1_2
+ # Should report s1 is waiting on speculative lock
+ controller_print_speculative_locks
+ # Allow s2 to insert into the non-unique index and complete. s1 will
+ # no longer wait on speculative lock, but proceed to wait on the
+ # transaction to finish. The no-op step is needed to ensure that
+ # we don't advance to the reporting step until s2_upsert has completed.
+ controller_unlock_2_4 s2_noop
+ # Should report that s1 is now waiting for s2 to commit
+ controller_print_speculative_locks
+ # Once s2 commits, s1 is finally free to continue to update
+ s2_commit s1_noop
+ # This should now show a successful UPSERT
+ controller_show
+ # Ensure no unexpected locks survive
+ controller_print_speculative_locks
diff --git a/src/test/isolation/specs/lock-committed-keyupdate.spec b/src/test/isolation/specs/lock-committed-keyupdate.spec
new file mode 100644
index 0000000..487f0e0
--- /dev/null
+++ b/src/test/isolation/specs/lock-committed-keyupdate.spec
@@ -0,0 +1,66 @@
+# Test locking of a tuple with a committed key-update. In this case,
+# the update conflicts with the lock, so failures are expected, except
+# in READ COMMITTED isolation mode.
+#
+# Some of the permutations are commented out that work fine in the
+# lock-committed-update test, because in this case the update blocks.
+
+setup
+{
+ DROP TABLE IF EXISTS lcku_table;
+ CREATE TABLE lcku_table (id INTEGER, value TEXT, PRIMARY KEY (id) INCLUDE (value));
+ INSERT INTO lcku_table VALUES (1, 'one');
+ INSERT INTO lcku_table VALUES (3, 'two');
+}
+
+teardown
+{
+ DROP TABLE lcku_table;
+}
+
+session s1
+step s1b { BEGIN; }
+step s1l { SELECT pg_advisory_lock(578902068); }
+step s1u { UPDATE lcku_table SET id = 2 WHERE id = 3; }
+step s1hint { SELECT * FROM lcku_table; }
+step s1ul { SELECT pg_advisory_unlock(578902068); }
+step s1c { COMMIT; }
+teardown { SELECT pg_advisory_unlock_all(); }
+
+session s2
+step s2b1 { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step s2b2 { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step s2b3 { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step s2l { SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; }
+step s2c { COMMIT; }
+teardown { SELECT pg_advisory_unlock_all(); }
+
+permutation s1b s2b1 s1l s2l s1u s1c s1ul s2c
+permutation s1b s2b1 s1l s1u s2l s1c s1ul s2c
+#permutation s1b s2b1 s1l s2l s1ul s1u s1c s2c
+permutation s1b s2b1 s1l s1u s1ul s2l s1c s2c
+
+permutation s1b s2b1 s1l s2l s1u s1c s1hint s1ul s2c
+permutation s1b s2b1 s1l s1u s2l s1c s1hint s1ul s2c
+#permutation s1b s2b1 s1l s2l s1ul s1u s1c s1hint s2c
+permutation s1b s2b1 s1l s1u s1ul s2l s1c s1hint s2c
+
+permutation s1b s2b2 s1l s2l s1u s1c s1ul s2c
+permutation s1b s2b2 s1l s1u s2l s1c s1ul s2c
+#permutation s1b s2b2 s1l s2l s1ul s1u s1c s2c
+permutation s1b s2b2 s1l s1u s1ul s2l s1c s2c
+
+permutation s1b s2b2 s1l s2l s1u s1c s1hint s1ul s2c
+permutation s1b s2b2 s1l s1u s2l s1c s1hint s1ul s2c
+#permutation s1b s2b2 s1l s2l s1ul s1u s1c s1hint s2c
+permutation s1b s2b2 s1l s1u s1ul s2l s1c s1hint s2c
+
+permutation s1b s2b3 s1l s2l s1u s1c s1ul s2c
+permutation s1b s2b3 s1l s1u s2l s1c s1ul s2c
+#permutation s1b s2b3 s1l s2l s1ul s1u s1c s2c
+permutation s1b s2b3 s1l s1u s1ul s2l s1c s2c
+
+permutation s1b s2b3 s1l s2l s1u s1c s1hint s1ul s2c
+permutation s1b s2b3 s1l s1u s2l s1c s1hint s1ul s2c
+#permutation s1b s2b3 s1l s2l s1ul s1u s1c s1hint s2c
+permutation s1b s2b3 s1l s1u s1ul s2l s1c s1hint s2c
diff --git a/src/test/isolation/specs/lock-committed-update.spec b/src/test/isolation/specs/lock-committed-update.spec
new file mode 100644
index 0000000..74d80d5
--- /dev/null
+++ b/src/test/isolation/specs/lock-committed-update.spec
@@ -0,0 +1,62 @@
+# Test locking of a tuple with a committed update. When the lock does not
+# conflict with the update, no blocking and no serializability errors should
+# occur.
+
+setup
+{
+ DROP TABLE IF EXISTS lcu_table;
+ CREATE TABLE lcu_table (id INTEGER PRIMARY KEY, value TEXT);
+ INSERT INTO lcu_table VALUES (1, 'one');
+}
+
+teardown
+{
+ DROP TABLE lcu_table;
+}
+
+session s1
+step s1b { BEGIN; }
+step s1l { SELECT pg_advisory_lock(380170116); }
+step s1u { UPDATE lcu_table SET value = 'two' WHERE id = 1; }
+step s1hint { SELECT * FROM lcu_table; }
+step s1ul { SELECT pg_advisory_unlock(380170116); }
+step s1c { COMMIT; }
+teardown { SELECT pg_advisory_unlock_all(); }
+
+session s2
+step s2b1 { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step s2b2 { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step s2b3 { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step s2l { SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; }
+step s2c { COMMIT; }
+teardown { SELECT pg_advisory_unlock_all(); }
+
+permutation s1b s2b1 s1l s2l s1u s1c s1ul s2c
+permutation s1b s2b1 s1l s1u s2l s1c s1ul s2c
+permutation s1b s2b1 s1l s2l s1ul s1u s1c s2c
+permutation s1b s2b1 s1l s1u s1ul s2l s1c s2c
+
+permutation s1b s2b1 s1l s2l s1u s1c s1hint s1ul s2c
+permutation s1b s2b1 s1l s1u s2l s1c s1hint s1ul s2c
+permutation s1b s2b1 s1l s2l s1ul s1u s1c s1hint s2c
+permutation s1b s2b1 s1l s1u s1ul s2l s1c s1hint s2c
+
+permutation s1b s2b2 s1l s2l s1u s1c s1ul s2c
+permutation s1b s2b2 s1l s1u s2l s1c s1ul s2c
+permutation s1b s2b2 s1l s2l s1ul s1u s1c s2c
+permutation s1b s2b2 s1l s1u s1ul s2l s1c s2c
+
+permutation s1b s2b2 s1l s2l s1u s1c s1hint s1ul s2c
+permutation s1b s2b2 s1l s1u s2l s1c s1hint s1ul s2c
+permutation s1b s2b2 s1l s2l s1ul s1u s1c s1hint s2c
+permutation s1b s2b2 s1l s1u s1ul s2l s1c s1hint s2c
+
+permutation s1b s2b3 s1l s2l s1u s1c s1ul s2c
+permutation s1b s2b3 s1l s1u s2l s1c s1ul s2c
+permutation s1b s2b3 s1l s2l s1ul s1u s1c s2c
+permutation s1b s2b3 s1l s1u s1ul s2l s1c s2c
+
+permutation s1b s2b3 s1l s2l s1u s1c s1hint s1ul s2c
+permutation s1b s2b3 s1l s1u s2l s1c s1hint s1ul s2c
+permutation s1b s2b3 s1l s2l s1ul s1u s1c s1hint s2c
+permutation s1b s2b3 s1l s1u s1ul s2l s1c s1hint s2c
diff --git a/src/test/isolation/specs/lock-update-delete.spec b/src/test/isolation/specs/lock-update-delete.spec
new file mode 100644
index 0000000..b9dd7d1
--- /dev/null
+++ b/src/test/isolation/specs/lock-update-delete.spec
@@ -0,0 +1,61 @@
+# This test verifies behavior when traversing an update chain during
+# locking an old version of the tuple. There are three tests here:
+# 1. update the tuple, then delete it; a second transaction locks the
+# first version. This should raise an error if the DELETE succeeds,
+# but be allowed to continue if it aborts.
+# 2. Same as (1), except that instead of deleting the tuple, we merely
+# update its key. The behavior should be the same as for (1).
+# 3. Same as (2), except that we update the tuple without modifying its
+# key. In this case, no error should be raised.
+# When run in REPEATABLE READ or SERIALIZABLE transaction isolation levels, all
+# permutations that commit s2 cause a serializability error; all permutations
+# that rollback s2 can get through.
+#
+# We use an advisory lock (which is locked during s1's setup) to let s2 obtain
+# its snapshot early and only allow it to actually traverse the update chain
+# when s1 is done creating it.
+
+setup
+{
+ DROP TABLE IF EXISTS foo;
+ CREATE TABLE foo (
+ key int PRIMARY KEY,
+ value int
+ );
+
+ INSERT INTO foo VALUES (1, 1);
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+# obtain lock on the tuple, traversing its update chain
+step s1l { SELECT * FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; }
+
+session s2
+setup { SELECT pg_advisory_lock(0); }
+step s2b { BEGIN; }
+step s2u { UPDATE foo SET value = 2 WHERE key = 1; }
+step s2_blocker1 { DELETE FROM foo; }
+step s2_blocker2 { UPDATE foo SET key = 2 WHERE key = 1; }
+step s2_blocker3 { UPDATE foo SET value = 2 WHERE key = 1; }
+step s2_unlock { SELECT pg_advisory_unlock(0); }
+step s2c { COMMIT; }
+step s2r { ROLLBACK; }
+
+permutation s2b s1l s2u s2_blocker1 s2_unlock s2c
+permutation s2b s1l s2u s2_blocker2 s2_unlock s2c
+permutation s2b s1l s2u s2_blocker3 s2_unlock s2c
+permutation s2b s1l s2u s2_blocker1 s2_unlock s2r
+permutation s2b s1l s2u s2_blocker2 s2_unlock s2r
+permutation s2b s1l s2u s2_blocker3 s2_unlock s2r
+
+permutation s2b s1l s2u s2_blocker1 s2c s2_unlock
+permutation s2b s1l s2u s2_blocker2 s2c s2_unlock
+permutation s2b s1l s2u s2_blocker3 s2c s2_unlock
+permutation s2b s1l s2u s2_blocker1 s2r s2_unlock
+permutation s2b s1l s2u s2_blocker2 s2r s2_unlock
+permutation s2b s1l s2u s2_blocker3 s2r s2_unlock
diff --git a/src/test/isolation/specs/lock-update-traversal.spec b/src/test/isolation/specs/lock-update-traversal.spec
new file mode 100644
index 0000000..9d3d32d
--- /dev/null
+++ b/src/test/isolation/specs/lock-update-traversal.spec
@@ -0,0 +1,39 @@
+# When a tuple that has been updated is locked, the locking command must
+# traverse the update chain; thus, a DELETE (on the newer version of the tuple)
+# should not be able to proceed until the lock has been released. An UPDATE
+# that changes the key should not be allowed to continue either; but an UPDATE
+# that doesn't modify the key should be able to continue immediately.
+
+setup
+{
+ CREATE TABLE foo (
+ key int,
+ value int,
+ PRIMARY KEY (key) INCLUDE (value)
+ );
+
+ INSERT INTO foo VALUES (1, 1);
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+step s1b { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step s1s { SELECT * FROM foo; } # obtain snapshot
+step s1l { SELECT * FROM foo FOR KEY SHARE; } # obtain lock
+step s1c { COMMIT; }
+
+session s2
+step s2b { BEGIN; }
+step s2u { UPDATE foo SET value = 2 WHERE key = 1; }
+step s2c { COMMIT; }
+step s2d1 { DELETE FROM foo WHERE key = 1; }
+step s2d2 { UPDATE foo SET key = 3 WHERE key = 1; }
+step s2d3 { UPDATE foo SET value = 3 WHERE key = 1; }
+
+permutation s1b s2b s1s s2u s1l s2c s2d1 s1c
+permutation s1b s2b s1s s2u s1l s2c s2d2 s1c
+permutation s1b s2b s1s s2u s1l s2c s2d3 s1c
diff --git a/src/test/isolation/specs/multiple-cic.spec b/src/test/isolation/specs/multiple-cic.spec
new file mode 100644
index 0000000..e34a6b0
--- /dev/null
+++ b/src/test/isolation/specs/multiple-cic.spec
@@ -0,0 +1,43 @@
+# Test multiple CREATE INDEX CONCURRENTLY working simultaneously
+
+setup
+{
+ CREATE TABLE mcic_one (
+ id int
+ );
+ CREATE TABLE mcic_two (
+ id int
+ );
+ CREATE FUNCTION lck_shr(bigint) RETURNS bool IMMUTABLE LANGUAGE plpgsql AS $$
+ BEGIN PERFORM pg_advisory_lock_shared($1); RETURN true; END;
+ $$;
+ CREATE FUNCTION unlck() RETURNS bool IMMUTABLE LANGUAGE plpgsql AS $$
+ BEGIN PERFORM pg_advisory_unlock_all(); RETURN true; END;
+ $$;
+}
+teardown
+{
+ DROP TABLE mcic_one, mcic_two;
+ DROP FUNCTION lck_shr(bigint);
+ DROP FUNCTION unlck();
+}
+
+session s1
+step s1i {
+ CREATE INDEX CONCURRENTLY mcic_one_pkey ON mcic_one (id)
+ WHERE lck_shr(281457);
+ }
+teardown { SELECT unlck(); }
+
+
+session s2
+step s2l { SELECT pg_advisory_lock(281457); }
+step s2i {
+ CREATE INDEX CONCURRENTLY mcic_two_pkey ON mcic_two (id)
+ WHERE unlck();
+ }
+
+# (*) marker ensures that s2i is reported as "waiting", even if it
+# completes very quickly
+
+permutation s2l s1i s2i(*)
diff --git a/src/test/isolation/specs/multiple-row-versions.spec b/src/test/isolation/specs/multiple-row-versions.spec
new file mode 100644
index 0000000..0779ea0
--- /dev/null
+++ b/src/test/isolation/specs/multiple-row-versions.spec
@@ -0,0 +1,47 @@
+# Multiple Row Versions test
+#
+# This test is designed to cover some code paths which only occur with
+# four or more transactions interacting with particular timings.
+#
+# Due to long permutation setup time, we are only testing one specific
+# permutation, which should get a serialization error.
+
+setup
+{
+ CREATE TABLE t (id int NOT NULL, txt text) WITH (fillfactor=50);
+ INSERT INTO t (id)
+ SELECT x FROM (SELECT * FROM generate_series(1, 1000000)) a(x);
+ ALTER TABLE t ADD PRIMARY KEY (id);
+}
+
+teardown
+{
+ DROP TABLE t;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rx1 { SELECT * FROM t WHERE id = 1000000; }
+# delay until after T3 commits
+step wz1 { UPDATE t SET txt = 'a' WHERE id = 1; }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step wx2 { UPDATE t SET txt = 'b' WHERE id = 1000000; }
+step c2 { COMMIT; }
+
+session s3
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step wx3 { UPDATE t SET txt = 'c' WHERE id = 1000000; }
+step ry3 { SELECT * FROM t WHERE id = 500000; }
+# delay until after T4 commits
+step c3 { COMMIT; }
+
+session s4
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step wy4 { UPDATE t SET txt = 'd' WHERE id = 500000; }
+step rz4 { SELECT * FROM t WHERE id = 1; }
+step c4 { COMMIT; }
+
+permutation rx1 wx2 c2 wx3 ry3 wy4 rz4 c4 c3 wz1 c1
diff --git a/src/test/isolation/specs/multixact-no-deadlock.spec b/src/test/isolation/specs/multixact-no-deadlock.spec
new file mode 100644
index 0000000..a8af724
--- /dev/null
+++ b/src/test/isolation/specs/multixact-no-deadlock.spec
@@ -0,0 +1,35 @@
+# If we already hold a lock of a given strength, do not deadlock when
+# some other transaction is waiting for a conflicting lock and we try
+# to acquire the same lock we already held.
+setup
+{
+ CREATE TABLE justthis (
+ value int
+ );
+
+ INSERT INTO justthis VALUES (1);
+}
+
+teardown
+{
+ DROP TABLE justthis;
+}
+
+session s1
+setup { BEGIN; }
+step s1lock { SELECT * FROM justthis FOR SHARE; }
+step s1svpt { SAVEPOINT foo; }
+step s1lock2 { SELECT * FROM justthis FOR SHARE; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step s2lock { SELECT * FROM justthis FOR SHARE; } # ensure it's a multi
+step s2c { COMMIT; }
+
+session s3
+setup { BEGIN; }
+step s3lock { SELECT * FROM justthis FOR UPDATE; }
+step s3c { COMMIT; }
+
+permutation s1lock s2lock s1svpt s3lock s1lock2 s2c s1c s3c
diff --git a/src/test/isolation/specs/multixact-no-forget.spec b/src/test/isolation/specs/multixact-no-forget.spec
new file mode 100644
index 0000000..7f8a38b
--- /dev/null
+++ b/src/test/isolation/specs/multixact-no-forget.spec
@@ -0,0 +1,44 @@
+# If transaction A holds a lock, and transaction B does an update,
+# make sure we don't forget the lock if B aborts.
+setup
+{
+ CREATE TABLE dont_forget (
+ value int
+ );
+
+ INSERT INTO dont_forget VALUES (1);
+}
+
+teardown
+{
+ DROP TABLE dont_forget;
+}
+
+session s1
+setup { BEGIN; }
+step s1_show { SELECT current_setting('default_transaction_isolation') <> 'read committed'; }
+step s1_lock { SELECT * FROM dont_forget FOR KEY SHARE; }
+step s1_commit { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step s2_update { UPDATE dont_forget SET value = 2; }
+step s2_abort { ROLLBACK; }
+step s2_commit { COMMIT; }
+
+session s3
+# try cases with both a non-conflicting lock with s1's and a conflicting one
+step s3_forkeyshr { SELECT * FROM dont_forget FOR KEY SHARE; }
+step s3_fornokeyupd { SELECT * FROM dont_forget FOR NO KEY UPDATE; }
+step s3_forupd { SELECT * FROM dont_forget FOR UPDATE; }
+
+permutation s1_show s1_commit s2_commit
+permutation s1_lock s2_update s2_abort s3_forkeyshr s1_commit
+permutation s1_lock s2_update s2_commit s3_forkeyshr s1_commit
+permutation s1_lock s2_update s1_commit s3_forkeyshr s2_commit
+permutation s1_lock s2_update s2_abort s3_fornokeyupd s1_commit
+permutation s1_lock s2_update s2_commit s3_fornokeyupd s1_commit
+permutation s1_lock s2_update s1_commit s3_fornokeyupd s2_commit
+permutation s1_lock s2_update s2_abort s3_forupd s1_commit
+permutation s1_lock s2_update s2_commit s3_forupd s1_commit
+permutation s1_lock s2_update s1_commit s3_forupd s2_commit
diff --git a/src/test/isolation/specs/nowait-2.spec b/src/test/isolation/specs/nowait-2.spec
new file mode 100644
index 0000000..cf892f2
--- /dev/null
+++ b/src/test/isolation/specs/nowait-2.spec
@@ -0,0 +1,37 @@
+# Test NOWAIT with multixact locks.
+
+setup
+{
+ CREATE TABLE foo (
+ id int PRIMARY KEY,
+ data text NOT NULL
+ );
+ INSERT INTO foo VALUES (1, 'x');
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+setup { BEGIN; }
+step s1a { SELECT * FROM foo FOR SHARE NOWAIT; }
+step s1b { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step s2a { SELECT * FROM foo FOR SHARE NOWAIT; }
+step s2b { SELECT * FROM foo FOR UPDATE NOWAIT; }
+step s2c { COMMIT; }
+
+# s1 and s2 both get SHARE lock, creating a multixact lock, then s2
+# tries to upgrade to UPDATE but aborts because it cannot acquire a
+# multi-xact lock
+permutation s1a s2a s2b s1b s2c
+# the same but with the SHARE locks acquired in a different order, so
+# s2 again aborts because it can't acquired a multi-xact lock
+permutation s2a s1a s2b s1b s2c
+# s2 acquires SHARE then UPDATE, then s1 tries to acquire SHARE but
+# can't so aborts because it can't acquire a regular lock
+permutation s2a s2b s1a s1b s2c
diff --git a/src/test/isolation/specs/nowait-3.spec b/src/test/isolation/specs/nowait-3.spec
new file mode 100644
index 0000000..06fb762
--- /dev/null
+++ b/src/test/isolation/specs/nowait-3.spec
@@ -0,0 +1,33 @@
+# Test NOWAIT with tuple locks.
+
+setup
+{
+ CREATE TABLE foo (
+ id int PRIMARY KEY,
+ data text NOT NULL
+ );
+ INSERT INTO foo VALUES (1, 'x');
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+setup { BEGIN; }
+step s1a { SELECT * FROM foo FOR UPDATE; }
+step s1b { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step s2a { SELECT * FROM foo FOR UPDATE; }
+step s2b { COMMIT; }
+
+session s3
+setup { BEGIN; }
+step s3a { SELECT * FROM foo FOR UPDATE NOWAIT; }
+step s3b { COMMIT; }
+
+# s3 skips to second record due to tuple lock held by s2
+permutation s1a s2a s3a s1b s2b s3b
diff --git a/src/test/isolation/specs/nowait-4.spec b/src/test/isolation/specs/nowait-4.spec
new file mode 100644
index 0000000..da80330
--- /dev/null
+++ b/src/test/isolation/specs/nowait-4.spec
@@ -0,0 +1,35 @@
+# Test NOWAIT with an updated tuple chain.
+
+setup
+{
+ CREATE TABLE foo (
+ id int PRIMARY KEY,
+ data text NOT NULL
+ );
+ INSERT INTO foo VALUES (1, 'x');
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+setup { BEGIN; }
+step s1a { SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL FOR UPDATE NOWAIT; }
+step s1b { COMMIT; }
+
+session s2
+step s2a { SELECT pg_advisory_lock(0); }
+step s2b { UPDATE foo SET data = data; }
+step s2c { BEGIN; }
+step s2d { UPDATE foo SET data = data; }
+step s2e { SELECT pg_advisory_unlock(0); }
+step s2f { COMMIT; }
+
+# s1 takes a snapshot but then waits on an advisory lock, then s2
+# updates the row in one transaction, then again in another without
+# committing, before allowing s1 to proceed to try to lock a row;
+# because it has a snapshot that sees the older version, we reach the
+# waiting code in EvalPlanQualFetch which ereports when in NOWAIT mode.
+permutation s2a s1a s2b s2c s2d s2e s1b s2f
diff --git a/src/test/isolation/specs/nowait-5.spec b/src/test/isolation/specs/nowait-5.spec
new file mode 100644
index 0000000..46108de
--- /dev/null
+++ b/src/test/isolation/specs/nowait-5.spec
@@ -0,0 +1,57 @@
+# Test NOWAIT on an updated tuple chain
+
+setup
+{
+
+ DROP TABLE IF EXISTS test_nowait;
+ CREATE TABLE test_nowait (
+ id integer PRIMARY KEY,
+ value integer not null
+ );
+
+ INSERT INTO test_nowait
+ SELECT x,x FROM generate_series(1,2) x;
+}
+
+teardown
+{
+ DROP TABLE test_nowait;
+}
+
+session sl1
+step sl1_prep {
+ PREPARE sl1_run AS SELECT id FROM test_nowait WHERE pg_advisory_lock(0) is not null FOR UPDATE NOWAIT;
+}
+step sl1_exec {
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+ EXECUTE sl1_run;
+ SELECT xmin, xmax, ctid, * FROM test_nowait;
+}
+teardown { COMMIT; }
+
+# A session that's used for an UPDATE of the rows to be locked, for when we're testing ctid
+# chain following.
+session upd
+step upd_getlock {
+ SELECT pg_advisory_lock(0);
+}
+step upd_doupdate {
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+ UPDATE test_nowait SET value = value WHERE id % 2 = 0;
+ COMMIT;
+}
+step upd_releaselock {
+ SELECT pg_advisory_unlock(0);
+}
+
+# A session that acquires locks that sl1 is supposed to avoid blocking on
+session lk1
+step lk1_doforshare {
+ BEGIN ISOLATION LEVEL READ COMMITTED;
+ SELECT id FROM test_nowait WHERE id % 2 = 0 FOR SHARE;
+}
+teardown {
+ COMMIT;
+}
+
+permutation sl1_prep upd_getlock sl1_exec upd_doupdate lk1_doforshare upd_releaselock
diff --git a/src/test/isolation/specs/nowait.spec b/src/test/isolation/specs/nowait.spec
new file mode 100644
index 0000000..a75e54c
--- /dev/null
+++ b/src/test/isolation/specs/nowait.spec
@@ -0,0 +1,25 @@
+# Test NOWAIT when regular row locks can't be acquired.
+
+setup
+{
+ CREATE TABLE foo (
+ id int PRIMARY KEY,
+ data text NOT NULL
+ );
+ INSERT INTO foo VALUES (1, 'x');
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+setup { BEGIN; }
+step s1a { SELECT * FROM foo FOR UPDATE NOWAIT; }
+step s1b { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step s2a { SELECT * FROM foo FOR UPDATE NOWAIT; }
+step s2b { COMMIT; }
diff --git a/src/test/isolation/specs/partial-index.spec b/src/test/isolation/specs/partial-index.spec
new file mode 100644
index 0000000..c033841
--- /dev/null
+++ b/src/test/isolation/specs/partial-index.spec
@@ -0,0 +1,32 @@
+# Partial Index test
+#
+# Make sure that an update which moves a row out of a partial index
+# is handled correctly. In early versions, an attempt at optimization
+# broke this behavior, allowing anomalies.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ create table test_t (id integer, val1 text, val2 integer);
+ create index test_idx on test_t(id) where val2 = 1;
+ insert into test_t (select generate_series(0, 10000), 'a', 2);
+ insert into test_t (select generate_series(0, 10), 'a', 1);
+}
+
+teardown
+{
+ DROP TABLE test_t;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rxy1 { select * from test_t where val2 = 1; }
+step wx1 { update test_t set val2 = 2 where val2 = 1 and id = 10; }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step wy2 { update test_t set val2 = 2 where val2 = 1 and id = 9; }
+step rxy2 { select * from test_t where val2 = 1; }
+step c2 { COMMIT; }
diff --git a/src/test/isolation/specs/partition-concurrent-attach.spec b/src/test/isolation/specs/partition-concurrent-attach.spec
new file mode 100644
index 0000000..fcd4dce
--- /dev/null
+++ b/src/test/isolation/specs/partition-concurrent-attach.spec
@@ -0,0 +1,43 @@
+# Verify that default partition constraint is enforced correctly
+# in light of partitions being added concurrently to its parent
+setup {
+ drop table if exists tpart;
+ create table tpart(i int, j text) partition by range(i);
+ create table tpart_1(like tpart);
+ create table tpart_2(like tpart);
+ create table tpart_default (a int, j text, i int) partition by list (j);
+ create table tpart_default_default (a int, i int, b int, j text);
+ alter table tpart_default_default drop b;
+ alter table tpart_default attach partition tpart_default_default default;
+ alter table tpart_default drop a;
+ alter table tpart attach partition tpart_default default;
+ alter table tpart attach partition tpart_1 for values from(0) to (100);
+ insert into tpart_2 values (110,'xxx'), (120, 'yyy'), (150, 'zzz');
+}
+
+session s1
+step s1b { begin; }
+step s1a { alter table tpart attach partition tpart_2 for values from (100) to (200); }
+step s1c { commit; }
+
+session s2
+step s2b { begin; }
+step s2i { insert into tpart values (110,'xxx'), (120, 'yyy'), (150, 'zzz'); }
+step s2i2 { insert into tpart_default (i, j) values (110, 'xxx'), (120, 'yyy'), (150, 'zzz'); }
+step s2c { commit; }
+step s2s { select tableoid::regclass, * from tpart; }
+
+teardown { drop table tpart; }
+
+# insert into tpart by s2 which routes to tpart_default due to not seeing
+# concurrently added tpart_2 should fail, because the partition constraint
+# of tpart_default would have changed due to tpart_2 having been added
+permutation s1b s1a s2b s2i s1c s2c s2s
+
+# similar to above, but now insert into sub-partitioned tpart_default
+permutation s1b s1a s2b s2i2 s1c s2c s2s
+
+# reverse: now the insert into tpart_default by s2 occurs first followed by
+# attach in s1, which should fail when it scans the leaf default partition
+# find the violating rows
+permutation s1b s2b s2i s1a s2c s1c s2s
diff --git a/src/test/isolation/specs/partition-drop-index-locking.spec b/src/test/isolation/specs/partition-drop-index-locking.spec
new file mode 100644
index 0000000..34e8b52
--- /dev/null
+++ b/src/test/isolation/specs/partition-drop-index-locking.spec
@@ -0,0 +1,47 @@
+# Verify that DROP INDEX properly locks all downward sub-partitions
+# and partitions before locking the indexes.
+
+setup
+{
+ CREATE TABLE part_drop_index_locking (id int) PARTITION BY RANGE(id);
+ CREATE TABLE part_drop_index_locking_subpart PARTITION OF part_drop_index_locking FOR VALUES FROM (1) TO (100) PARTITION BY RANGE(id);
+ CREATE TABLE part_drop_index_locking_subpart_child PARTITION OF part_drop_index_locking_subpart FOR VALUES FROM (1) TO (100);
+ CREATE INDEX part_drop_index_locking_idx ON part_drop_index_locking(id);
+ CREATE INDEX part_drop_index_locking_subpart_idx ON part_drop_index_locking_subpart(id);
+}
+
+teardown
+{
+ DROP TABLE part_drop_index_locking;
+}
+
+# SELECT will take AccessShare lock first on the table and then on its index.
+# We can simulate the case where DROP INDEX starts between those steps
+# by manually taking the table lock beforehand.
+session s1
+step s1begin { BEGIN; }
+step s1lock { LOCK TABLE part_drop_index_locking_subpart_child IN ACCESS SHARE MODE; }
+step s1select { SELECT * FROM part_drop_index_locking_subpart_child; }
+step s1commit { COMMIT; }
+
+session s2
+step s2begin { BEGIN; }
+step s2drop { DROP INDEX part_drop_index_locking_idx; }
+step s2dropsub { DROP INDEX part_drop_index_locking_subpart_idx; }
+step s2commit { COMMIT; }
+
+session s3
+step s3getlocks {
+ SELECT s.query, c.relname, l.mode, l.granted
+ FROM pg_locks l
+ JOIN pg_class c ON l.relation = c.oid
+ JOIN pg_stat_activity s ON l.pid = s.pid
+ WHERE c.relname LIKE 'part_drop_index_locking%'
+ ORDER BY s.query, c.relname, l.mode, l.granted;
+}
+
+# Run DROP INDEX on top partitioned table
+permutation s1begin s1lock s2begin s2drop(s1commit) s1select s3getlocks s1commit s3getlocks s2commit
+
+# Run DROP INDEX on top sub-partition table
+permutation s1begin s1lock s2begin s2dropsub(s1commit) s1select s3getlocks s1commit s3getlocks s2commit
diff --git a/src/test/isolation/specs/partition-key-update-1.spec b/src/test/isolation/specs/partition-key-update-1.spec
new file mode 100644
index 0000000..6b5f422
--- /dev/null
+++ b/src/test/isolation/specs/partition-key-update-1.spec
@@ -0,0 +1,86 @@
+# Test that an error if thrown if the target row has been moved to a
+# different partition by a concurrent session.
+
+setup
+{
+ --
+ -- Setup to test an error from ExecUpdate and ExecDelete.
+ --
+ CREATE TABLE foo (a int, b text) PARTITION BY LIST(a);
+ CREATE TABLE foo1 PARTITION OF foo FOR VALUES IN (1);
+ CREATE TABLE foo2 PARTITION OF foo FOR VALUES IN (2);
+ INSERT INTO foo VALUES (1, 'ABC');
+
+ --
+ -- Setup to test an error from GetTupleForTrigger
+ --
+ CREATE TABLE footrg (a int, b text) PARTITION BY LIST(a);
+ CREATE TABLE footrg1 PARTITION OF footrg FOR VALUES IN (1);
+ CREATE TABLE footrg2 PARTITION OF footrg FOR VALUES IN (2);
+ INSERT INTO footrg VALUES (1, 'ABC');
+ CREATE FUNCTION func_footrg_mod_a() RETURNS TRIGGER AS $$
+ BEGIN
+ NEW.a = 2; -- This is changing partition key column.
+ RETURN NEW;
+ END $$ LANGUAGE PLPGSQL;
+ CREATE TRIGGER footrg_mod_a BEFORE UPDATE ON footrg1
+ FOR EACH ROW EXECUTE PROCEDURE func_footrg_mod_a();
+
+ --
+ -- Setup to test an error from ExecLockRows
+ --
+ CREATE TABLE foo_range_parted (a int, b text) PARTITION BY RANGE(a);
+ CREATE TABLE foo_range_parted1 PARTITION OF foo_range_parted FOR VALUES FROM (1) TO (10);
+ CREATE TABLE foo_range_parted2 PARTITION OF foo_range_parted FOR VALUES FROM (10) TO (20);
+ INSERT INTO foo_range_parted VALUES(7, 'ABC');
+ CREATE UNIQUE INDEX foo_range_parted1_a_unique ON foo_range_parted1 (a);
+ CREATE TABLE bar (a int REFERENCES foo_range_parted1(a));
+}
+
+teardown
+{
+ DROP TABLE foo;
+ DROP TRIGGER footrg_mod_a ON footrg1;
+ DROP FUNCTION func_footrg_mod_a();
+ DROP TABLE footrg;
+ DROP TABLE bar, foo_range_parted;
+}
+
+session s1
+step s1b { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step s1u { UPDATE foo SET a=2 WHERE a=1; }
+step s1u2 { UPDATE footrg SET b='EFG' WHERE a=1; }
+step s1u3pc { UPDATE foo_range_parted SET a=11 WHERE a=7; }
+step s1u3npc { UPDATE foo_range_parted SET b='XYZ' WHERE a=7; }
+step s1c { COMMIT; }
+step s1r { ROLLBACK; }
+
+session s2
+step s2b { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step s2u { UPDATE foo SET b='EFG' WHERE a=1; }
+step s2u2 { UPDATE footrg SET b='XYZ' WHERE a=1; }
+step s2i { INSERT INTO bar VALUES(7); }
+step s2d { DELETE FROM foo WHERE a=1; }
+step s2c { COMMIT; }
+
+# Concurrency error from ExecUpdate and ExecDelete.
+permutation s1b s2b s1u s1c s2d s2c
+permutation s1b s2b s1u s2d s1c s2c
+permutation s1b s2b s1u s2u s1c s2c
+permutation s1b s2b s2d s1u s2c s1c
+
+# Concurrency error from GetTupleForTrigger
+permutation s1b s2b s1u2 s1c s2u2 s2c
+permutation s1b s2b s1u2 s2u2 s1c s2c
+permutation s1b s2b s2u2 s1u2 s2c s1c
+
+# Concurrency error from ExecLockRows
+# test waiting for moved row itself
+permutation s1b s2b s1u3pc s2i s1c s2c
+permutation s1b s2b s1u3pc s2i s1r s2c
+# test waiting for in-partition update, followed by cross-partition move
+permutation s1b s2b s1u3npc s1u3pc s2i s1c s2c
+permutation s1b s2b s1u3npc s1u3pc s2i s1r s2c
+# test waiting for in-partition update, followed by cross-partition move
+permutation s1b s2b s1u3npc s1u3pc s1u3pc s2i s1c s2c
+permutation s1b s2b s1u3npc s1u3pc s1u3pc s2i s1r s2c
diff --git a/src/test/isolation/specs/partition-key-update-2.spec b/src/test/isolation/specs/partition-key-update-2.spec
new file mode 100644
index 0000000..d4cd09b
--- /dev/null
+++ b/src/test/isolation/specs/partition-key-update-2.spec
@@ -0,0 +1,45 @@
+# Concurrent update of a partition key and INSERT...ON CONFLICT DO NOTHING test
+#
+# This test tries to expose problems with the interaction between concurrent
+# sessions during an update of the partition key and INSERT...ON CONFLICT DO
+# NOTHING on a partitioned table.
+#
+# The convention here is that session 1 moves row from one partition to
+# another due update of the partition key and session 2 always ends up
+# inserting, and session 3 always ends up doing nothing.
+#
+# Note: This test is slightly resemble to insert-conflict-do-nothing test.
+
+setup
+{
+ CREATE TABLE foo (a int primary key, b text) PARTITION BY LIST(a);
+ CREATE TABLE foo1 PARTITION OF foo FOR VALUES IN (1);
+ CREATE TABLE foo2 PARTITION OF foo FOR VALUES IN (2);
+ INSERT INTO foo VALUES (1, 'initial tuple');
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step s1u { UPDATE foo SET a=2, b=b || ' -> moved by session-1' WHERE a=1; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step s2donothing { INSERT INTO foo VALUES(1, 'session-2 donothing') ON CONFLICT DO NOTHING; }
+step s2c { COMMIT; }
+
+session s3
+setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step s3donothing { INSERT INTO foo VALUES(2, 'session-3 donothing') ON CONFLICT DO NOTHING; }
+step s3select { SELECT * FROM foo ORDER BY a; }
+step s3c { COMMIT; }
+
+# Regular case where one session block-waits on another to determine if it
+# should proceed with an insert or do nothing.
+permutation s1u s2donothing s3donothing s1c s2c s3select s3c
+permutation s2donothing s1u s3donothing s1c s2c s3select s3c
diff --git a/src/test/isolation/specs/partition-key-update-3.spec b/src/test/isolation/specs/partition-key-update-3.spec
new file mode 100644
index 0000000..d2883e3
--- /dev/null
+++ b/src/test/isolation/specs/partition-key-update-3.spec
@@ -0,0 +1,44 @@
+# Concurrent update of a partition key and INSERT...ON CONFLICT DO NOTHING
+# test on partitioned table with multiple rows in higher isolation levels.
+#
+# Note: This test is resemble to insert-conflict-do-nothing-2 test
+
+setup
+{
+ CREATE TABLE foo (a int primary key, b text) PARTITION BY LIST(a);
+ CREATE TABLE foo1 PARTITION OF foo FOR VALUES IN (1);
+ CREATE TABLE foo2 PARTITION OF foo FOR VALUES IN (2);
+ INSERT INTO foo VALUES (1, 'initial tuple');
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step s1u { UPDATE foo SET a=2, b=b || ' -> moved by session-1' WHERE a=1; }
+step s1c { COMMIT; }
+
+session s2
+step s2beginrr { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step s2begins { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step s2donothing { INSERT INTO foo VALUES(1, 'session-2 donothing') ON CONFLICT DO NOTHING; }
+step s2c { COMMIT; }
+step s2select { SELECT * FROM foo ORDER BY a; }
+
+session s3
+step s3beginrr { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step s3begins { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step s3donothing { INSERT INTO foo VALUES(2, 'session-3 donothing'), (2, 'session-3 donothing2') ON CONFLICT DO NOTHING; }
+step s3c { COMMIT; }
+
+permutation s2beginrr s3beginrr s1u s2donothing s1c s2c s3donothing s3c s2select
+permutation s2beginrr s3beginrr s1u s3donothing s1c s3c s2donothing s2c s2select
+permutation s2beginrr s3beginrr s1u s2donothing s3donothing s1c s2c s3c s2select
+permutation s2beginrr s3beginrr s1u s3donothing s2donothing s1c s3c s2c s2select
+permutation s2begins s3begins s1u s2donothing s1c s2c s3donothing s3c s2select
+permutation s2begins s3begins s1u s3donothing s1c s3c s2donothing s2c s2select
+permutation s2begins s3begins s1u s2donothing s3donothing s1c s2c s3c s2select
+permutation s2begins s3begins s1u s3donothing s2donothing s1c s3c s2c s2select
diff --git a/src/test/isolation/specs/partition-key-update-4.spec b/src/test/isolation/specs/partition-key-update-4.spec
new file mode 100644
index 0000000..6c70816
--- /dev/null
+++ b/src/test/isolation/specs/partition-key-update-4.spec
@@ -0,0 +1,76 @@
+# Test that a row that ends up in a new partition contains changes made by
+# a concurrent transaction.
+
+setup
+{
+ --
+ -- Setup to test concurrent handling of ExecDelete().
+ --
+ CREATE TABLE foo (a int, b text) PARTITION BY LIST(a);
+ CREATE TABLE foo1 PARTITION OF foo FOR VALUES IN (1);
+ CREATE TABLE foo2 PARTITION OF foo FOR VALUES IN (2);
+ INSERT INTO foo VALUES (1, 'ABC');
+
+ --
+ -- Setup to test concurrent handling of GetTupleForTrigger().
+ --
+ CREATE TABLE footrg (a int, b text) PARTITION BY LIST(a);
+ CREATE TABLE triglog as select * from footrg;
+ CREATE TABLE footrg1 PARTITION OF footrg FOR VALUES IN (1);
+ CREATE TABLE footrg2 PARTITION OF footrg FOR VALUES IN (2);
+ INSERT INTO footrg VALUES (1, 'ABC');
+ CREATE FUNCTION func_footrg() RETURNS TRIGGER AS $$
+ BEGIN
+ OLD.b = OLD.b || ' trigger';
+
+ -- This will verify that the trigger is not run *before* the row is
+ -- refetched by EvalPlanQual. The OLD row should contain the changes made
+ -- by the concurrent session.
+ INSERT INTO triglog select OLD.*;
+
+ RETURN OLD;
+ END $$ LANGUAGE PLPGSQL;
+ CREATE TRIGGER footrg_ondel BEFORE DELETE ON footrg1
+ FOR EACH ROW EXECUTE PROCEDURE func_footrg();
+
+}
+
+teardown
+{
+ DROP TABLE foo;
+ DROP TRIGGER footrg_ondel ON footrg1;
+ DROP FUNCTION func_footrg();
+ DROP TABLE footrg;
+ DROP TABLE triglog;
+}
+
+session s1
+step s1b { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step s1u { UPDATE foo SET a = a + 1, b = b || ' update1' WHERE b like '%ABC%'; }
+step s1ut { UPDATE footrg SET a = a + 1, b = b || ' update1' WHERE b like '%ABC%'; }
+step s1s { SELECT tableoid::regclass, * FROM foo ORDER BY a; }
+step s1st { SELECT tableoid::regclass, * FROM footrg ORDER BY a; }
+step s1stl { SELECT * FROM triglog ORDER BY a; }
+step s1c { COMMIT; }
+
+session s2
+step s2b { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step s2u1 { UPDATE foo SET b = b || ' update2' WHERE a = 1; }
+step s2u2 { UPDATE foo SET b = 'EFG' WHERE a = 1; }
+step s2ut1 { UPDATE footrg SET b = b || ' update2' WHERE a = 1; }
+step s2ut2 { UPDATE footrg SET b = 'EFG' WHERE a = 1; }
+step s2c { COMMIT; }
+
+
+# Session s1 is moving a row into another partition, but is waiting for
+# another session s2 that is updating the original row. The row that ends up
+# in the new partition should contain the changes made by session s2.
+permutation s1b s2b s2u1 s1u s2c s1c s1s
+
+# Same as above, except, session s1 is waiting in GetTupleForTrigger().
+permutation s1b s2b s2ut1 s1ut s2c s1c s1st s1stl
+
+# Below two cases are similar to the above two; except that the session s1
+# fails EvalPlanQual() test, so partition key update does not happen.
+permutation s1b s2b s2u2 s1u s2c s1c s1s
+permutation s1b s2b s2ut2 s1ut s2c s1c s1st s1stl
diff --git a/src/test/isolation/specs/plpgsql-toast.spec b/src/test/isolation/specs/plpgsql-toast.spec
new file mode 100644
index 0000000..bb444fc
--- /dev/null
+++ b/src/test/isolation/specs/plpgsql-toast.spec
@@ -0,0 +1,178 @@
+# Test TOAST behavior in PL/pgSQL procedures with transaction control.
+#
+# We need to ensure that values stored in PL/pgSQL variables are free
+# of external TOAST references, because those could disappear after a
+# transaction is committed (leading to errors "missing chunk number
+# ... for toast value ..."). The tests here do this by running VACUUM
+# in a second session. Advisory locks are used to have the VACUUM
+# kick in at the right time. The different "assign" steps test
+# different code paths for variable assignments in PL/pgSQL.
+
+setup
+{
+ CREATE TABLE test1 (a int, b text);
+ ALTER TABLE test1 ALTER COLUMN b SET STORAGE EXTERNAL;
+ INSERT INTO test1 VALUES (1, repeat('foo', 2000));
+ CREATE TYPE test2 AS (a bigint, b text);
+}
+
+teardown
+{
+ DROP TABLE test1;
+ DROP TYPE test2;
+}
+
+session s1
+
+setup
+{
+ SELECT pg_advisory_unlock_all();
+}
+
+# assign_simple_var()
+step assign1
+{
+do $$
+ declare
+ x text;
+ begin
+ select test1.b into x from test1;
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(x) = %', length(x);
+ end;
+$$;
+}
+
+# assign_simple_var()
+step assign2
+{
+do $$
+ declare
+ x text;
+ begin
+ x := (select test1.b from test1);
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(x) = %', length(x);
+ end;
+$$;
+}
+
+# expanded_record_set_field()
+step assign3
+{
+do $$
+ declare
+ r record;
+ begin
+ select * into r from test1;
+ r.b := (select test1.b from test1);
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(r) = %', length(r::text);
+ end;
+$$;
+}
+
+# expanded_record_set_fields()
+step assign4
+{
+do $$
+ declare
+ r test2;
+ begin
+ select * into r from test1;
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(r) = %', length(r::text);
+ end;
+$$;
+}
+
+# expanded_record_set_tuple()
+step assign5
+{
+do $$
+ declare
+ r record;
+ begin
+ for r in select test1.b from test1 loop
+ null;
+ end loop;
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(r) = %', length(r::text);
+ end;
+$$;
+}
+
+# FOR loop must not hold any fetched-but-not-detoasted values across commit
+step assign6
+{
+do $$
+ declare
+ r record;
+ begin
+ insert into test1 values (2, repeat('bar', 3000));
+ insert into test1 values (3, repeat('baz', 4000));
+ for r in select test1.b from test1 loop
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(r) = %', length(r::text);
+ end loop;
+ end;
+$$;
+}
+
+# Check that the results of a query can be detoasted just after committing
+# (there's no interaction with VACUUM here)
+step "fetch-after-commit"
+{
+do $$
+ declare
+ r record;
+ t text;
+ begin
+ insert into test1 values (2, repeat('bar', 3000));
+ insert into test1 values (3, repeat('baz', 4000));
+ for r in select test1.a from test1 loop
+ commit;
+ select b into t from test1 where a = r.a;
+ raise notice 'length(t) = %', length(t);
+ end loop;
+ end;
+$$;
+}
+
+session s2
+setup
+{
+ SELECT pg_advisory_unlock_all();
+}
+step lock
+{
+ SELECT pg_advisory_lock(1);
+}
+step vacuum
+{
+ VACUUM test1;
+}
+step unlock
+{
+ SELECT pg_advisory_unlock(1);
+}
+
+permutation lock assign1 vacuum unlock
+permutation lock assign2 vacuum unlock
+permutation lock assign3 vacuum unlock
+permutation lock assign4 vacuum unlock
+permutation lock assign5 vacuum unlock
+permutation lock assign6 vacuum unlock
+permutation "fetch-after-commit"
diff --git a/src/test/isolation/specs/predicate-gin.spec b/src/test/isolation/specs/predicate-gin.spec
new file mode 100644
index 0000000..e279eaa
--- /dev/null
+++ b/src/test/isolation/specs/predicate-gin.spec
@@ -0,0 +1,115 @@
+# Test for page level predicate locking in gin index
+#
+# Test to verify serialization failures and to check reduced false positives
+#
+# To verify serialization failures, queries and permutations are written in such
+# a way that an index scan (from one transaction) and an index insert (from
+# another transaction) will try to access the same part (sub-tree) of the index
+# whereas to check reduced false positives, they will try to access different
+# parts (sub-tree) of the index.
+
+
+setup
+{
+ create table gin_tbl(p int4[]);
+ insert into gin_tbl select array[1] from generate_series(1, 8192) g;
+ insert into gin_tbl select array[g] from generate_series(2, 800) g;
+ create index ginidx on gin_tbl using gin(p) with (fastupdate = off);
+ create table other_tbl(v int4);
+}
+
+teardown
+{
+ drop table gin_tbl;
+ drop table other_tbl;
+}
+
+session s1
+setup
+{
+ begin isolation level serializable;
+ set enable_seqscan=off;
+}
+
+step ra1 { select * from gin_tbl where p @> array[1] limit 1; }
+step rb1 { select count(*) from gin_tbl where p @> array[2]; }
+step rc1 { select count(*) from gin_tbl where p @> array[800]; }
+step rd1 { select count(*) from gin_tbl where p @> array[2000]; }
+
+step wo1 { insert into other_tbl values (1); }
+
+step c1 { commit; }
+
+session s2
+setup
+{
+ begin isolation level serializable;
+ set enable_seqscan=off;
+}
+
+step ro2 { select count(*) from other_tbl; }
+
+step wa2 { insert into gin_tbl values (array[1]); }
+step wb2 { insert into gin_tbl values (array[2]); }
+step wc2 { insert into gin_tbl values (array[800]); }
+step wd2 { insert into gin_tbl values (array[2000]); }
+
+step c2 { commit; }
+
+session s3
+step fu { alter index ginidx set (fastupdate = on); }
+
+# An index scan (from one transaction) and an index insert (from another
+# transaction) try to access the same part of the index. So, there is a
+# r-w conflict.
+
+permutation ra1 ro2 wo1 c1 wa2 c2
+permutation ro2 ra1 wo1 c1 wa2 c2
+permutation ro2 ra1 wo1 wa2 c1 c2
+permutation ra1 ro2 wa2 wo1 c1 c2
+
+permutation rb1 ro2 wo1 c1 wb2 c2
+permutation ro2 rb1 wo1 c1 wb2 c2
+permutation ro2 rb1 wo1 wb2 c1 c2
+permutation rb1 ro2 wb2 wo1 c1 c2
+
+permutation rc1 ro2 wo1 c1 wc2 c2
+permutation ro2 rc1 wo1 c1 wc2 c2
+permutation ro2 rc1 wo1 wc2 c1 c2
+permutation rc1 ro2 wc2 wo1 c1 c2
+
+# An index scan (from one transaction) and an index insert (from another
+# transaction) try to access different parts of the index. So, there is no
+# r-w conflict.
+
+permutation ra1 ro2 wo1 c1 wb2 c2
+permutation ro2 ra1 wo1 c1 wc2 c2
+permutation ro2 rb1 wo1 wa2 c1 c2
+permutation rc1 ro2 wa2 wo1 c1 c2
+
+permutation rb1 ro2 wo1 c1 wa2 c2
+permutation ro2 rb1 wo1 c1 wc2 c2
+permutation ro2 ra1 wo1 wb2 c1 c2
+permutation rc1 ro2 wb2 wo1 c1 c2
+
+permutation rc1 ro2 wo1 c1 wa2 c2
+permutation ro2 rc1 wo1 c1 wb2 c2
+permutation ro2 ra1 wo1 wc2 c1 c2
+permutation rb1 ro2 wc2 wo1 c1 c2
+
+# With fastupdate = on all index is under predicate lock. So we can't
+# distinguish particular keys.
+
+permutation fu ra1 ro2 wo1 c1 wa2 c2
+permutation fu ra1 ro2 wo1 c1 wb2 c2
+
+# Check fastupdate turned on concurrently.
+
+permutation ra1 ro2 wo1 c1 fu wa2 c2
+
+# Tests for conflicts with previously non-existing key
+
+permutation rd1 ro2 wo1 c1 wd2 c2
+permutation ro2 rd1 wo1 c1 wd2 c2
+permutation ro2 rd1 wo1 wd2 c1 c2
+permutation rd1 ro2 wd2 wo1 c1 c2
diff --git a/src/test/isolation/specs/predicate-gist.spec b/src/test/isolation/specs/predicate-gist.spec
new file mode 100644
index 0000000..9016c6e
--- /dev/null
+++ b/src/test/isolation/specs/predicate-gist.spec
@@ -0,0 +1,117 @@
+# Test for page level predicate locking in gist
+#
+# Test to verify serialization failures and to check reduced false positives
+#
+# To verify serialization failures, queries and permutations are written in such
+# a way that an index scan (from one transaction) and an index insert (from
+# another transaction) will try to access the same part (sub-tree) of the index
+# whereas to check reduced false positives, they will try to access different
+# parts (sub-tree) of the index.
+
+setup
+{
+ create table gist_point_tbl(id int4, p point);
+ create index gist_pointidx on gist_point_tbl using gist(p);
+ insert into gist_point_tbl (id, p)
+ select g, point(g*10, g*10) from generate_series(1, 1000) g;
+}
+
+teardown
+{
+ drop table gist_point_tbl;
+}
+
+session s1
+setup
+{
+ begin isolation level serializable;
+ set enable_seqscan=off;
+ set enable_bitmapscan=off;
+ set enable_indexonlyscan=on;
+}
+
+step rxy1 { select sum(p[0]) from gist_point_tbl where p << point(2500, 2500); }
+step wx1 { insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(15, 20) g; }
+step rxy3 { select sum(p[0]) from gist_point_tbl where p >> point(6000,6000); }
+step wx3 { insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(12, 18) g; }
+step c1 { commit; }
+
+
+session s2
+setup
+{
+ begin isolation level serializable;
+ set enable_seqscan=off;
+ set enable_bitmapscan=off;
+ set enable_indexonlyscan=on;
+}
+
+step rxy2 { select sum(p[0]) from gist_point_tbl where p >> point(7500,7500); }
+step wy2 { insert into gist_point_tbl (id, p)
+ select g, point(g*500, g*500) from generate_series(1, 5) g; }
+step rxy4 { select sum(p[0]) from gist_point_tbl where p << point(1000,1000); }
+step wy4 { insert into gist_point_tbl (id, p)
+ select g, point(g*50, g*50) from generate_series(1, 20) g; }
+step c2 { commit; }
+
+# An index scan (from one transaction) and an index insert (from another
+# transaction) try to access the same part of the index but one transaction
+# commits before other transaction begins so no r-w conflict.
+
+permutation rxy1 wx1 c1 rxy2 wy2 c2
+permutation rxy2 wy2 c2 rxy1 wx1 c1
+
+# An index scan (from one transaction) and an index insert (from another
+# transaction) try to access different parts of the index and also one
+# transaction commits before other transaction begins, so no r-w conflict.
+
+permutation rxy3 wx3 c1 rxy4 wy4 c2
+permutation rxy4 wy4 c2 rxy3 wx3 c1
+
+
+# An index scan (from one transaction) and an index insert (from another
+# transaction) try to access the same part of the index and one transaction
+# begins before other transaction commits so there is a r-w conflict.
+
+permutation rxy1 wx1 rxy2 c1 wy2 c2
+permutation rxy1 wx1 rxy2 wy2 c1 c2
+permutation rxy1 wx1 rxy2 wy2 c2 c1
+permutation rxy1 rxy2 wx1 c1 wy2 c2
+permutation rxy1 rxy2 wx1 wy2 c1 c2
+permutation rxy1 rxy2 wx1 wy2 c2 c1
+permutation rxy1 rxy2 wy2 wx1 c1 c2
+permutation rxy1 rxy2 wy2 wx1 c2 c1
+permutation rxy1 rxy2 wy2 c2 wx1 c1
+permutation rxy2 rxy1 wx1 c1 wy2 c2
+permutation rxy2 rxy1 wx1 wy2 c1 c2
+permutation rxy2 rxy1 wx1 wy2 c2 c1
+permutation rxy2 rxy1 wy2 wx1 c1 c2
+permutation rxy2 rxy1 wy2 wx1 c2 c1
+permutation rxy2 rxy1 wy2 c2 wx1 c1
+permutation rxy2 wy2 rxy1 wx1 c1 c2
+permutation rxy2 wy2 rxy1 wx1 c2 c1
+permutation rxy2 wy2 rxy1 c2 wx1 c1
+
+# An index scan (from one transaction) and an index insert (from another
+# transaction) try to access different parts of the index so no r-w conflict.
+
+permutation rxy3 wx3 rxy4 c1 wy4 c2
+permutation rxy3 wx3 rxy4 wy4 c1 c2
+permutation rxy3 wx3 rxy4 wy4 c2 c1
+permutation rxy3 rxy4 wx3 c1 wy4 c2
+permutation rxy3 rxy4 wx3 wy4 c1 c2
+permutation rxy3 rxy4 wx3 wy4 c2 c1
+permutation rxy3 rxy4 wy4 wx3 c1 c2
+permutation rxy3 rxy4 wy4 wx3 c2 c1
+permutation rxy3 rxy4 wy4 c2 wx3 c1
+permutation rxy4 rxy3 wx3 c1 wy4 c2
+permutation rxy4 rxy3 wx3 wy4 c1 c2
+permutation rxy4 rxy3 wx3 wy4 c2 c1
+permutation rxy4 rxy3 wy4 wx3 c1 c2
+permutation rxy4 rxy3 wy4 wx3 c2 c1
+permutation rxy4 rxy3 wy4 c2 wx3 c1
+permutation rxy4 wy4 rxy3 wx3 c1 c2
+permutation rxy4 wy4 rxy3 wx3 c2 c1
+permutation rxy4 wy4 rxy3 c2 wx3 c1
diff --git a/src/test/isolation/specs/predicate-hash.spec b/src/test/isolation/specs/predicate-hash.spec
new file mode 100644
index 0000000..7ca193b
--- /dev/null
+++ b/src/test/isolation/specs/predicate-hash.spec
@@ -0,0 +1,122 @@
+# Test for page level predicate locking in hash index
+#
+# Test to verify serialization failures and to check reduced false positives
+#
+# To verify serialization failures, queries and permutations are written in such
+# a way that an index scan (from one transaction) and an index insert (from
+# another transaction) will try to access the same bucket of the index
+# whereas to check reduced false positives, they will try to access different
+# buckets of the index.
+
+setup
+{
+ create table hash_tbl(id int4, p integer);
+ create index hash_idx on hash_tbl using hash(p);
+ insert into hash_tbl (id, p)
+ select g, 10 from generate_series(1, 10) g;
+ insert into hash_tbl (id, p)
+ select g, 20 from generate_series(11, 20) g;
+ insert into hash_tbl (id, p)
+ select g, 30 from generate_series(21, 30) g;
+ insert into hash_tbl (id, p)
+ select g, 40 from generate_series(31, 40) g;
+}
+
+teardown
+{
+ drop table hash_tbl;
+}
+
+session s1
+setup
+{
+ begin isolation level serializable;
+ set enable_seqscan=off;
+ set enable_bitmapscan=off;
+ set enable_indexonlyscan=on;
+}
+step rxy1 { select sum(p) from hash_tbl where p=20; }
+step wx1 { insert into hash_tbl (id, p)
+ select g, 30 from generate_series(41, 50) g; }
+step rxy3 { select sum(p) from hash_tbl where p=20; }
+step wx3 { insert into hash_tbl (id, p)
+ select g, 50 from generate_series(41, 50) g; }
+step c1 { commit; }
+
+
+session s2
+setup
+{
+ begin isolation level serializable;
+ set enable_seqscan=off;
+ set enable_bitmapscan=off;
+ set enable_indexonlyscan=on;
+}
+step rxy2 { select sum(p) from hash_tbl where p=30; }
+step wy2 { insert into hash_tbl (id, p)
+ select g, 20 from generate_series(51, 60) g; }
+step rxy4 { select sum(p) from hash_tbl where p=30; }
+step wy4 { insert into hash_tbl (id, p)
+ select g, 60 from generate_series(51, 60) g; }
+step c2 { commit; }
+
+
+# An index scan (from one transaction) and an index insert (from another
+# transaction) try to access the same bucket of the index but one transaction
+# commits before other transaction begins so no r-w conflict.
+
+permutation rxy1 wx1 c1 rxy2 wy2 c2
+permutation rxy2 wy2 c2 rxy1 wx1 c1
+
+# An index scan (from one transaction) and an index insert (from another
+# transaction) try to access different buckets of the index and also one
+# transaction commits before other transaction begins, so no r-w conflict.
+
+permutation rxy3 wx3 c1 rxy4 wy4 c2
+permutation rxy4 wy4 c2 rxy3 wx3 c1
+
+
+# An index scan (from one transaction) and an index insert (from another
+# transaction) try to access the same bucket of the index and one transaction
+# begins before other transaction commits so there is a r-w conflict.
+
+permutation rxy1 wx1 rxy2 c1 wy2 c2
+permutation rxy1 wx1 rxy2 wy2 c1 c2
+permutation rxy1 wx1 rxy2 wy2 c2 c1
+permutation rxy1 rxy2 wx1 c1 wy2 c2
+permutation rxy1 rxy2 wx1 wy2 c1 c2
+permutation rxy1 rxy2 wx1 wy2 c2 c1
+permutation rxy1 rxy2 wy2 wx1 c1 c2
+permutation rxy1 rxy2 wy2 wx1 c2 c1
+permutation rxy1 rxy2 wy2 c2 wx1 c1
+permutation rxy2 rxy1 wx1 c1 wy2 c2
+permutation rxy2 rxy1 wx1 wy2 c1 c2
+permutation rxy2 rxy1 wx1 wy2 c2 c1
+permutation rxy2 rxy1 wy2 wx1 c1 c2
+permutation rxy2 rxy1 wy2 wx1 c2 c1
+permutation rxy2 rxy1 wy2 c2 wx1 c1
+permutation rxy2 wy2 rxy1 wx1 c1 c2
+permutation rxy2 wy2 rxy1 wx1 c2 c1
+permutation rxy2 wy2 rxy1 c2 wx1 c1
+
+# An index scan (from one transaction) and an index insert (from another
+# transaction) try to access different buckets of the index so no r-w conflict.
+
+permutation rxy3 wx3 rxy4 c1 wy4 c2
+permutation rxy3 wx3 rxy4 wy4 c1 c2
+permutation rxy3 wx3 rxy4 wy4 c2 c1
+permutation rxy3 rxy4 wx3 c1 wy4 c2
+permutation rxy3 rxy4 wx3 wy4 c1 c2
+permutation rxy3 rxy4 wx3 wy4 c2 c1
+permutation rxy3 rxy4 wy4 wx3 c1 c2
+permutation rxy3 rxy4 wy4 wx3 c2 c1
+permutation rxy3 rxy4 wy4 c2 wx3 c1
+permutation rxy4 rxy3 wx3 c1 wy4 c2
+permutation rxy4 rxy3 wx3 wy4 c1 c2
+permutation rxy4 rxy3 wx3 wy4 c2 c1
+permutation rxy4 rxy3 wy4 wx3 c1 c2
+permutation rxy4 rxy3 wy4 wx3 c2 c1
+permutation rxy4 rxy3 wy4 c2 wx3 c1
+permutation rxy4 wy4 rxy3 wx3 c1 c2
+permutation rxy4 wy4 rxy3 wx3 c2 c1
+permutation rxy4 wy4 rxy3 c2 wx3 c1
diff --git a/src/test/isolation/specs/predicate-lock-hot-tuple.spec b/src/test/isolation/specs/predicate-lock-hot-tuple.spec
new file mode 100644
index 0000000..5b8aecc
--- /dev/null
+++ b/src/test/isolation/specs/predicate-lock-hot-tuple.spec
@@ -0,0 +1,37 @@
+# Test predicate locks on HOT updated tuples.
+#
+# This test has two serializable transactions. Both select two rows
+# from the table, and then update one of them.
+# If these were serialized (run one at a time), the transaction that
+# runs later would see one of the rows to be updated.
+#
+# Any overlap between the transactions must cause a serialization failure.
+# We used to have a bug in predicate locking HOT updated tuples, which
+# caused the conflict to be missed, if the row was HOT updated.
+
+setup
+{
+ CREATE TABLE test (i int PRIMARY KEY, t text);
+ INSERT INTO test VALUES (5, 'apple'), (7, 'pear'), (11, 'banana');
+ -- HOT-update 'pear' row.
+ UPDATE test SET t = 'pear_hot_updated' WHERE i = 7;
+}
+
+teardown
+{
+ DROP TABLE test;
+}
+
+session s1
+step b1 { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step r1 { SELECT * FROM test WHERE i IN (5, 7) }
+step w1 { UPDATE test SET t = 'pear_xact1' WHERE i = 7 }
+step c1 { COMMIT; }
+
+session s2
+step b2 { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step r2 { SELECT * FROM test WHERE i IN (5, 7) }
+step w2 { UPDATE test SET t = 'apple_xact2' WHERE i = 5 }
+step c2 { COMMIT; }
+
+permutation b1 b2 r1 r2 w1 w2 c1 c2
diff --git a/src/test/isolation/specs/prepared-transactions-cic.spec b/src/test/isolation/specs/prepared-transactions-cic.spec
new file mode 100644
index 0000000..626b1b6
--- /dev/null
+++ b/src/test/isolation/specs/prepared-transactions-cic.spec
@@ -0,0 +1,37 @@
+# This test verifies that CREATE INDEX CONCURRENTLY interacts with prepared
+# transactions correctly.
+setup
+{
+ CREATE TABLE cic_test (a int);
+}
+
+teardown
+{
+ DROP TABLE cic_test;
+}
+
+
+# Sessions for CREATE INDEX CONCURRENTLY test
+session s1
+step w1 { BEGIN; INSERT INTO cic_test VALUES (1); }
+step p1 { PREPARE TRANSACTION 's1'; }
+step c1 { COMMIT PREPARED 's1'; }
+
+session s2
+# The isolation tester never recognizes that a lock of s1 blocks s2, because a
+# prepared transaction's locks have no pid associated. While there's a slight
+# chance of timeout while waiting for an autovacuum-held lock, that wouldn't
+# change the output. Hence, no timeout is too short.
+setup { SET lock_timeout = 10; }
+step cic2
+{
+ CREATE INDEX CONCURRENTLY on cic_test(a);
+}
+step r2
+{
+ SET enable_seqscan to off;
+ SET enable_bitmapscan to off;
+ SELECT * FROM cic_test WHERE a = 1;
+}
+
+permutation w1 p1 cic2 c1 r2
diff --git a/src/test/isolation/specs/prepared-transactions.spec b/src/test/isolation/specs/prepared-transactions.spec
new file mode 100644
index 0000000..78b9d2c
--- /dev/null
+++ b/src/test/isolation/specs/prepared-transactions.spec
@@ -0,0 +1,1507 @@
+# This test verifies that if there's a series of rw-conflicts
+# s1 ---> s2 ---> s3, with s3 committing first
+# at least one transaction will be aborted, regardless of the order in
+# which the conflicts are detected and transactions prepare and
+# commit.
+#
+#
+# Tables test2 and test3 are used to create the
+# s1 --> s2 and s2 --> s3 rw-dependencies respectively
+#
+# test1 isn't involved in the anomaly; s1 only inserts a row into it
+# so that there's an easy way to tell (by looking for that row) if s1
+# successfully committed.
+#
+# force_snapshot is used to force s2 and s3 to take their snapshot
+# immediately after BEGIN, so we can be sure the three transactions
+# overlap.
+setup
+{
+ CREATE TABLE test1 (a int);
+ CREATE TABLE test2 (b int);
+ CREATE TABLE test3 (c int);
+ CREATE TABLE force_snapshot (a int);
+}
+
+teardown
+{
+ DROP TABLE test1;
+ DROP TABLE test2;
+ DROP TABLE test3;
+ DROP TABLE force_snapshot;
+}
+
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; INSERT INTO test1 VALUES (1); }
+step r1 { SELECT * FROM test2; }
+step p1 { PREPARE TRANSACTION 's1'; }
+step c1 { COMMIT PREPARED 's1'; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; SELECT * FROM force_snapshot; }
+step r2 { SELECT * FROM test3; }
+step w2 { INSERT INTO test2 VALUES (2); }
+step p2 { PREPARE TRANSACTION 's2'; }
+step c2 { COMMIT PREPARED 's2'; }
+
+session s3
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; SELECT * FROM force_snapshot; }
+step w3 { INSERT INTO test3 VALUES (3); }
+step p3 { PREPARE TRANSACTION 's3'; }
+step c3 { COMMIT PREPARED 's3'; }
+
+# When run at the end of the permutations below, this SELECT statement
+# should never return any tuples, because at least one of the three
+# transactions involved should be aborted.
+session s4
+step check { SELECT * FROM test1,test2,test3; }
+
+# We run on all permutations of the statements above subject to the
+# following constraints:
+# - each transaction's reads/writes must happen before it prepares
+# - each transaction must prepare before committing
+# - s3 must be the first of the three transactions to commit
+# ...which means that each permutation should fail.
+#
+# To bring the number of permutations down a bit, we further require
+# them to satisfy *one* of the following:
+# - r1 < w2 < r2 < w3 (T1-->T2 conflict on write, T2-->T3 on write)
+# - r1 < w2 < w3 < r2 (T1-->T2 conflict on read, T2-->T3 on write)
+# - w2 < r1 < r2 < w3 (T1-->T2 conflict on write, T2-->T3 on read)
+# - w2 < r1 < w3 < r2 (T1-->T2 conflict on read, T2-->T3 on read)
+# This eliminates some redundant combinations. For example, it doesn't
+# matter if w2 happens before w3 as long as both come before the
+# conflicting reads.
+permutation r1 r2 w2 w3 p1 p2 p3 c3 c1 c2 check
+permutation r1 r2 w2 w3 p1 p2 p3 c3 c2 c1 check
+permutation r1 r2 w2 w3 p1 p3 p2 c3 c1 c2 check
+permutation r1 r2 w2 w3 p1 p3 p2 c3 c2 c1 check
+permutation r1 r2 w2 w3 p1 p3 c3 p2 c1 c2 check
+permutation r1 r2 w2 w3 p1 p3 c3 p2 c2 c1 check
+permutation r1 r2 w2 w3 p1 p3 c3 c1 p2 c2 check
+permutation r1 r2 w2 w3 p2 p1 p3 c3 c1 c2 check
+permutation r1 r2 w2 w3 p2 p1 p3 c3 c2 c1 check
+permutation r1 r2 w2 w3 p2 p3 p1 c3 c1 c2 check
+permutation r1 r2 w2 w3 p2 p3 p1 c3 c2 c1 check
+permutation r1 r2 w2 w3 p2 p3 c3 p1 c1 c2 check
+permutation r1 r2 w2 w3 p2 p3 c3 p1 c2 c1 check
+permutation r1 r2 w2 w3 p2 p3 c3 c2 p1 c1 check
+permutation r1 r2 w2 w3 p3 p1 p2 c3 c1 c2 check
+permutation r1 r2 w2 w3 p3 p1 p2 c3 c2 c1 check
+permutation r1 r2 w2 w3 p3 p1 c3 p2 c1 c2 check
+permutation r1 r2 w2 w3 p3 p1 c3 p2 c2 c1 check
+permutation r1 r2 w2 w3 p3 p1 c3 c1 p2 c2 check
+permutation r1 r2 w2 w3 p3 p2 p1 c3 c1 c2 check
+permutation r1 r2 w2 w3 p3 p2 p1 c3 c2 c1 check
+permutation r1 r2 w2 w3 p3 p2 c3 p1 c1 c2 check
+permutation r1 r2 w2 w3 p3 p2 c3 p1 c2 c1 check
+permutation r1 r2 w2 w3 p3 p2 c3 c2 p1 c1 check
+permutation r1 r2 w2 w3 p3 c3 p1 p2 c1 c2 check
+permutation r1 r2 w2 w3 p3 c3 p1 p2 c2 c1 check
+permutation r1 r2 w2 w3 p3 c3 p1 c1 p2 c2 check
+permutation r1 r2 w2 w3 p3 c3 p2 p1 c1 c2 check
+permutation r1 r2 w2 w3 p3 c3 p2 p1 c2 c1 check
+permutation r1 r2 w2 w3 p3 c3 p2 c2 p1 c1 check
+permutation r1 r2 w2 p1 w3 p2 p3 c3 c1 c2 check
+permutation r1 r2 w2 p1 w3 p2 p3 c3 c2 c1 check
+permutation r1 r2 w2 p1 w3 p3 p2 c3 c1 c2 check
+permutation r1 r2 w2 p1 w3 p3 p2 c3 c2 c1 check
+permutation r1 r2 w2 p1 w3 p3 c3 p2 c1 c2 check
+permutation r1 r2 w2 p1 w3 p3 c3 p2 c2 c1 check
+permutation r1 r2 w2 p1 w3 p3 c3 c1 p2 c2 check
+permutation r1 r2 w2 p1 p2 w3 p3 c3 c1 c2 check
+permutation r1 r2 w2 p1 p2 w3 p3 c3 c2 c1 check
+permutation r1 r2 w2 p2 w3 p1 p3 c3 c1 c2 check
+permutation r1 r2 w2 p2 w3 p1 p3 c3 c2 c1 check
+permutation r1 r2 w2 p2 w3 p3 p1 c3 c1 c2 check
+permutation r1 r2 w2 p2 w3 p3 p1 c3 c2 c1 check
+permutation r1 r2 w2 p2 w3 p3 c3 p1 c1 c2 check
+permutation r1 r2 w2 p2 w3 p3 c3 p1 c2 c1 check
+permutation r1 r2 w2 p2 w3 p3 c3 c2 p1 c1 check
+permutation r1 r2 w2 p2 p1 w3 p3 c3 c1 c2 check
+permutation r1 r2 w2 p2 p1 w3 p3 c3 c2 c1 check
+permutation r1 r2 p1 w2 w3 p2 p3 c3 c1 c2 check
+permutation r1 r2 p1 w2 w3 p2 p3 c3 c2 c1 check
+permutation r1 r2 p1 w2 w3 p3 p2 c3 c1 c2 check
+permutation r1 r2 p1 w2 w3 p3 p2 c3 c2 c1 check
+permutation r1 r2 p1 w2 w3 p3 c3 p2 c1 c2 check
+permutation r1 r2 p1 w2 w3 p3 c3 p2 c2 c1 check
+permutation r1 r2 p1 w2 w3 p3 c3 c1 p2 c2 check
+permutation r1 r2 p1 w2 p2 w3 p3 c3 c1 c2 check
+permutation r1 r2 p1 w2 p2 w3 p3 c3 c2 c1 check
+permutation r1 w2 w3 r2 p1 p2 p3 c3 c1 c2 check
+permutation r1 w2 w3 r2 p1 p2 p3 c3 c2 c1 check
+permutation r1 w2 w3 r2 p1 p3 p2 c3 c1 c2 check
+permutation r1 w2 w3 r2 p1 p3 p2 c3 c2 c1 check
+permutation r1 w2 w3 r2 p1 p3 c3 p2 c1 c2 check
+permutation r1 w2 w3 r2 p1 p3 c3 p2 c2 c1 check
+permutation r1 w2 w3 r2 p1 p3 c3 c1 p2 c2 check
+permutation r1 w2 w3 r2 p2 p1 p3 c3 c1 c2 check
+permutation r1 w2 w3 r2 p2 p1 p3 c3 c2 c1 check
+permutation r1 w2 w3 r2 p2 p3 p1 c3 c1 c2 check
+permutation r1 w2 w3 r2 p2 p3 p1 c3 c2 c1 check
+permutation r1 w2 w3 r2 p2 p3 c3 p1 c1 c2 check
+permutation r1 w2 w3 r2 p2 p3 c3 p1 c2 c1 check
+permutation r1 w2 w3 r2 p2 p3 c3 c2 p1 c1 check
+permutation r1 w2 w3 r2 p3 p1 p2 c3 c1 c2 check
+permutation r1 w2 w3 r2 p3 p1 p2 c3 c2 c1 check
+permutation r1 w2 w3 r2 p3 p1 c3 p2 c1 c2 check
+permutation r1 w2 w3 r2 p3 p1 c3 p2 c2 c1 check
+permutation r1 w2 w3 r2 p3 p1 c3 c1 p2 c2 check
+permutation r1 w2 w3 r2 p3 p2 p1 c3 c1 c2 check
+permutation r1 w2 w3 r2 p3 p2 p1 c3 c2 c1 check
+permutation r1 w2 w3 r2 p3 p2 c3 p1 c1 c2 check
+permutation r1 w2 w3 r2 p3 p2 c3 p1 c2 c1 check
+permutation r1 w2 w3 r2 p3 p2 c3 c2 p1 c1 check
+permutation r1 w2 w3 r2 p3 c3 p1 p2 c1 c2 check
+permutation r1 w2 w3 r2 p3 c3 p1 p2 c2 c1 check
+permutation r1 w2 w3 r2 p3 c3 p1 c1 p2 c2 check
+permutation r1 w2 w3 r2 p3 c3 p2 p1 c1 c2 check
+permutation r1 w2 w3 r2 p3 c3 p2 p1 c2 c1 check
+permutation r1 w2 w3 r2 p3 c3 p2 c2 p1 c1 check
+permutation r1 w2 w3 p1 r2 p2 p3 c3 c1 c2 check
+permutation r1 w2 w3 p1 r2 p2 p3 c3 c2 c1 check
+permutation r1 w2 w3 p1 r2 p3 p2 c3 c1 c2 check
+permutation r1 w2 w3 p1 r2 p3 p2 c3 c2 c1 check
+permutation r1 w2 w3 p1 r2 p3 c3 p2 c1 c2 check
+permutation r1 w2 w3 p1 r2 p3 c3 p2 c2 c1 check
+permutation r1 w2 w3 p1 r2 p3 c3 c1 p2 c2 check
+permutation r1 w2 w3 p1 p3 r2 p2 c3 c1 c2 check
+permutation r1 w2 w3 p1 p3 r2 p2 c3 c2 c1 check
+permutation r1 w2 w3 p1 p3 r2 c3 p2 c1 c2 check
+permutation r1 w2 w3 p1 p3 r2 c3 p2 c2 c1 check
+permutation r1 w2 w3 p1 p3 r2 c3 c1 p2 c2 check
+permutation r1 w2 w3 p1 p3 c3 r2 p2 c1 c2 check
+permutation r1 w2 w3 p1 p3 c3 r2 p2 c2 c1 check
+permutation r1 w2 w3 p1 p3 c3 r2 c1 p2 c2 check
+permutation r1 w2 w3 p1 p3 c3 c1 r2 p2 c2 check
+permutation r1 w2 w3 p3 r2 p1 p2 c3 c1 c2 check
+permutation r1 w2 w3 p3 r2 p1 p2 c3 c2 c1 check
+permutation r1 w2 w3 p3 r2 p1 c3 p2 c1 c2 check
+permutation r1 w2 w3 p3 r2 p1 c3 p2 c2 c1 check
+permutation r1 w2 w3 p3 r2 p1 c3 c1 p2 c2 check
+permutation r1 w2 w3 p3 r2 p2 p1 c3 c1 c2 check
+permutation r1 w2 w3 p3 r2 p2 p1 c3 c2 c1 check
+permutation r1 w2 w3 p3 r2 p2 c3 p1 c1 c2 check
+permutation r1 w2 w3 p3 r2 p2 c3 p1 c2 c1 check
+permutation r1 w2 w3 p3 r2 p2 c3 c2 p1 c1 check
+permutation r1 w2 w3 p3 r2 c3 p1 p2 c1 c2 check
+permutation r1 w2 w3 p3 r2 c3 p1 p2 c2 c1 check
+permutation r1 w2 w3 p3 r2 c3 p1 c1 p2 c2 check
+permutation r1 w2 w3 p3 r2 c3 p2 p1 c1 c2 check
+permutation r1 w2 w3 p3 r2 c3 p2 p1 c2 c1 check
+permutation r1 w2 w3 p3 r2 c3 p2 c2 p1 c1 check
+permutation r1 w2 w3 p3 p1 r2 p2 c3 c1 c2 check
+permutation r1 w2 w3 p3 p1 r2 p2 c3 c2 c1 check
+permutation r1 w2 w3 p3 p1 r2 c3 p2 c1 c2 check
+permutation r1 w2 w3 p3 p1 r2 c3 p2 c2 c1 check
+permutation r1 w2 w3 p3 p1 r2 c3 c1 p2 c2 check
+permutation r1 w2 w3 p3 p1 c3 r2 p2 c1 c2 check
+permutation r1 w2 w3 p3 p1 c3 r2 p2 c2 c1 check
+permutation r1 w2 w3 p3 p1 c3 r2 c1 p2 c2 check
+permutation r1 w2 w3 p3 p1 c3 c1 r2 p2 c2 check
+permutation r1 w2 w3 p3 c3 r2 p1 p2 c1 c2 check
+permutation r1 w2 w3 p3 c3 r2 p1 p2 c2 c1 check
+permutation r1 w2 w3 p3 c3 r2 p1 c1 p2 c2 check
+permutation r1 w2 w3 p3 c3 r2 p2 p1 c1 c2 check
+permutation r1 w2 w3 p3 c3 r2 p2 p1 c2 c1 check
+permutation r1 w2 w3 p3 c3 r2 p2 c2 p1 c1 check
+permutation r1 w2 w3 p3 c3 p1 r2 p2 c1 c2 check
+permutation r1 w2 w3 p3 c3 p1 r2 p2 c2 c1 check
+permutation r1 w2 w3 p3 c3 p1 r2 c1 p2 c2 check
+permutation r1 w2 w3 p3 c3 p1 c1 r2 p2 c2 check
+permutation r1 w2 p1 w3 r2 p2 p3 c3 c1 c2 check
+permutation r1 w2 p1 w3 r2 p2 p3 c3 c2 c1 check
+permutation r1 w2 p1 w3 r2 p3 p2 c3 c1 c2 check
+permutation r1 w2 p1 w3 r2 p3 p2 c3 c2 c1 check
+permutation r1 w2 p1 w3 r2 p3 c3 p2 c1 c2 check
+permutation r1 w2 p1 w3 r2 p3 c3 p2 c2 c1 check
+permutation r1 w2 p1 w3 r2 p3 c3 c1 p2 c2 check
+permutation r1 w2 p1 w3 p3 r2 p2 c3 c1 c2 check
+permutation r1 w2 p1 w3 p3 r2 p2 c3 c2 c1 check
+permutation r1 w2 p1 w3 p3 r2 c3 p2 c1 c2 check
+permutation r1 w2 p1 w3 p3 r2 c3 p2 c2 c1 check
+permutation r1 w2 p1 w3 p3 r2 c3 c1 p2 c2 check
+permutation r1 w2 p1 w3 p3 c3 r2 p2 c1 c2 check
+permutation r1 w2 p1 w3 p3 c3 r2 p2 c2 c1 check
+permutation r1 w2 p1 w3 p3 c3 r2 c1 p2 c2 check
+permutation r1 w2 p1 w3 p3 c3 c1 r2 p2 c2 check
+permutation r1 w3 r2 w2 p1 p2 p3 c3 c1 c2 check
+permutation r1 w3 r2 w2 p1 p2 p3 c3 c2 c1 check
+permutation r1 w3 r2 w2 p1 p3 p2 c3 c1 c2 check
+permutation r1 w3 r2 w2 p1 p3 p2 c3 c2 c1 check
+permutation r1 w3 r2 w2 p1 p3 c3 p2 c1 c2 check
+permutation r1 w3 r2 w2 p1 p3 c3 p2 c2 c1 check
+permutation r1 w3 r2 w2 p1 p3 c3 c1 p2 c2 check
+permutation r1 w3 r2 w2 p2 p1 p3 c3 c1 c2 check
+permutation r1 w3 r2 w2 p2 p1 p3 c3 c2 c1 check
+permutation r1 w3 r2 w2 p2 p3 p1 c3 c1 c2 check
+permutation r1 w3 r2 w2 p2 p3 p1 c3 c2 c1 check
+permutation r1 w3 r2 w2 p2 p3 c3 p1 c1 c2 check
+permutation r1 w3 r2 w2 p2 p3 c3 p1 c2 c1 check
+permutation r1 w3 r2 w2 p2 p3 c3 c2 p1 c1 check
+permutation r1 w3 r2 w2 p3 p1 p2 c3 c1 c2 check
+permutation r1 w3 r2 w2 p3 p1 p2 c3 c2 c1 check
+permutation r1 w3 r2 w2 p3 p1 c3 p2 c1 c2 check
+permutation r1 w3 r2 w2 p3 p1 c3 p2 c2 c1 check
+permutation r1 w3 r2 w2 p3 p1 c3 c1 p2 c2 check
+permutation r1 w3 r2 w2 p3 p2 p1 c3 c1 c2 check
+permutation r1 w3 r2 w2 p3 p2 p1 c3 c2 c1 check
+permutation r1 w3 r2 w2 p3 p2 c3 p1 c1 c2 check
+permutation r1 w3 r2 w2 p3 p2 c3 p1 c2 c1 check
+permutation r1 w3 r2 w2 p3 p2 c3 c2 p1 c1 check
+permutation r1 w3 r2 w2 p3 c3 p1 p2 c1 c2 check
+permutation r1 w3 r2 w2 p3 c3 p1 p2 c2 c1 check
+permutation r1 w3 r2 w2 p3 c3 p1 c1 p2 c2 check
+permutation r1 w3 r2 w2 p3 c3 p2 p1 c1 c2 check
+permutation r1 w3 r2 w2 p3 c3 p2 p1 c2 c1 check
+permutation r1 w3 r2 w2 p3 c3 p2 c2 p1 c1 check
+permutation r1 w3 r2 p1 w2 p2 p3 c3 c1 c2 check
+permutation r1 w3 r2 p1 w2 p2 p3 c3 c2 c1 check
+permutation r1 w3 r2 p1 w2 p3 p2 c3 c1 c2 check
+permutation r1 w3 r2 p1 w2 p3 p2 c3 c2 c1 check
+permutation r1 w3 r2 p1 w2 p3 c3 p2 c1 c2 check
+permutation r1 w3 r2 p1 w2 p3 c3 p2 c2 c1 check
+permutation r1 w3 r2 p1 w2 p3 c3 c1 p2 c2 check
+permutation r1 w3 r2 p1 p3 w2 p2 c3 c1 c2 check
+permutation r1 w3 r2 p1 p3 w2 p2 c3 c2 c1 check
+permutation r1 w3 r2 p1 p3 w2 c3 p2 c1 c2 check
+permutation r1 w3 r2 p1 p3 w2 c3 p2 c2 c1 check
+permutation r1 w3 r2 p1 p3 w2 c3 c1 p2 c2 check
+permutation r1 w3 r2 p1 p3 c3 w2 p2 c1 c2 check
+permutation r1 w3 r2 p1 p3 c3 w2 p2 c2 c1 check
+permutation r1 w3 r2 p1 p3 c3 w2 c1 p2 c2 check
+permutation r1 w3 r2 p1 p3 c3 c1 w2 p2 c2 check
+permutation r1 w3 r2 p3 w2 p1 p2 c3 c1 c2 check
+permutation r1 w3 r2 p3 w2 p1 p2 c3 c2 c1 check
+permutation r1 w3 r2 p3 w2 p1 c3 p2 c1 c2 check
+permutation r1 w3 r2 p3 w2 p1 c3 p2 c2 c1 check
+permutation r1 w3 r2 p3 w2 p1 c3 c1 p2 c2 check
+permutation r1 w3 r2 p3 w2 p2 p1 c3 c1 c2 check
+permutation r1 w3 r2 p3 w2 p2 p1 c3 c2 c1 check
+permutation r1 w3 r2 p3 w2 p2 c3 p1 c1 c2 check
+permutation r1 w3 r2 p3 w2 p2 c3 p1 c2 c1 check
+permutation r1 w3 r2 p3 w2 p2 c3 c2 p1 c1 check
+permutation r1 w3 r2 p3 w2 c3 p1 p2 c1 c2 check
+permutation r1 w3 r2 p3 w2 c3 p1 p2 c2 c1 check
+permutation r1 w3 r2 p3 w2 c3 p1 c1 p2 c2 check
+permutation r1 w3 r2 p3 w2 c3 p2 p1 c1 c2 check
+permutation r1 w3 r2 p3 w2 c3 p2 p1 c2 c1 check
+permutation r1 w3 r2 p3 w2 c3 p2 c2 p1 c1 check
+permutation r1 w3 r2 p3 p1 w2 p2 c3 c1 c2 check
+permutation r1 w3 r2 p3 p1 w2 p2 c3 c2 c1 check
+permutation r1 w3 r2 p3 p1 w2 c3 p2 c1 c2 check
+permutation r1 w3 r2 p3 p1 w2 c3 p2 c2 c1 check
+permutation r1 w3 r2 p3 p1 w2 c3 c1 p2 c2 check
+permutation r1 w3 r2 p3 p1 c3 w2 p2 c1 c2 check
+permutation r1 w3 r2 p3 p1 c3 w2 p2 c2 c1 check
+permutation r1 w3 r2 p3 p1 c3 w2 c1 p2 c2 check
+permutation r1 w3 r2 p3 p1 c3 c1 w2 p2 c2 check
+permutation r1 w3 r2 p3 c3 w2 p1 p2 c1 c2 check
+permutation r1 w3 r2 p3 c3 w2 p1 p2 c2 c1 check
+permutation r1 w3 r2 p3 c3 w2 p1 c1 p2 c2 check
+permutation r1 w3 r2 p3 c3 w2 p2 p1 c1 c2 check
+permutation r1 w3 r2 p3 c3 w2 p2 p1 c2 c1 check
+permutation r1 w3 r2 p3 c3 w2 p2 c2 p1 c1 check
+permutation r1 w3 r2 p3 c3 p1 w2 p2 c1 c2 check
+permutation r1 w3 r2 p3 c3 p1 w2 p2 c2 c1 check
+permutation r1 w3 r2 p3 c3 p1 w2 c1 p2 c2 check
+permutation r1 w3 r2 p3 c3 p1 c1 w2 p2 c2 check
+permutation r1 w3 w2 r2 p1 p2 p3 c3 c1 c2 check
+permutation r1 w3 w2 r2 p1 p2 p3 c3 c2 c1 check
+permutation r1 w3 w2 r2 p1 p3 p2 c3 c1 c2 check
+permutation r1 w3 w2 r2 p1 p3 p2 c3 c2 c1 check
+permutation r1 w3 w2 r2 p1 p3 c3 p2 c1 c2 check
+permutation r1 w3 w2 r2 p1 p3 c3 p2 c2 c1 check
+permutation r1 w3 w2 r2 p1 p3 c3 c1 p2 c2 check
+permutation r1 w3 w2 r2 p2 p1 p3 c3 c1 c2 check
+permutation r1 w3 w2 r2 p2 p1 p3 c3 c2 c1 check
+permutation r1 w3 w2 r2 p2 p3 p1 c3 c1 c2 check
+permutation r1 w3 w2 r2 p2 p3 p1 c3 c2 c1 check
+permutation r1 w3 w2 r2 p2 p3 c3 p1 c1 c2 check
+permutation r1 w3 w2 r2 p2 p3 c3 p1 c2 c1 check
+permutation r1 w3 w2 r2 p2 p3 c3 c2 p1 c1 check
+permutation r1 w3 w2 r2 p3 p1 p2 c3 c1 c2 check
+permutation r1 w3 w2 r2 p3 p1 p2 c3 c2 c1 check
+permutation r1 w3 w2 r2 p3 p1 c3 p2 c1 c2 check
+permutation r1 w3 w2 r2 p3 p1 c3 p2 c2 c1 check
+permutation r1 w3 w2 r2 p3 p1 c3 c1 p2 c2 check
+permutation r1 w3 w2 r2 p3 p2 p1 c3 c1 c2 check
+permutation r1 w3 w2 r2 p3 p2 p1 c3 c2 c1 check
+permutation r1 w3 w2 r2 p3 p2 c3 p1 c1 c2 check
+permutation r1 w3 w2 r2 p3 p2 c3 p1 c2 c1 check
+permutation r1 w3 w2 r2 p3 p2 c3 c2 p1 c1 check
+permutation r1 w3 w2 r2 p3 c3 p1 p2 c1 c2 check
+permutation r1 w3 w2 r2 p3 c3 p1 p2 c2 c1 check
+permutation r1 w3 w2 r2 p3 c3 p1 c1 p2 c2 check
+permutation r1 w3 w2 r2 p3 c3 p2 p1 c1 c2 check
+permutation r1 w3 w2 r2 p3 c3 p2 p1 c2 c1 check
+permutation r1 w3 w2 r2 p3 c3 p2 c2 p1 c1 check
+permutation r1 w3 w2 p1 r2 p2 p3 c3 c1 c2 check
+permutation r1 w3 w2 p1 r2 p2 p3 c3 c2 c1 check
+permutation r1 w3 w2 p1 r2 p3 p2 c3 c1 c2 check
+permutation r1 w3 w2 p1 r2 p3 p2 c3 c2 c1 check
+permutation r1 w3 w2 p1 r2 p3 c3 p2 c1 c2 check
+permutation r1 w3 w2 p1 r2 p3 c3 p2 c2 c1 check
+permutation r1 w3 w2 p1 r2 p3 c3 c1 p2 c2 check
+permutation r1 w3 w2 p1 p3 r2 p2 c3 c1 c2 check
+permutation r1 w3 w2 p1 p3 r2 p2 c3 c2 c1 check
+permutation r1 w3 w2 p1 p3 r2 c3 p2 c1 c2 check
+permutation r1 w3 w2 p1 p3 r2 c3 p2 c2 c1 check
+permutation r1 w3 w2 p1 p3 r2 c3 c1 p2 c2 check
+permutation r1 w3 w2 p1 p3 c3 r2 p2 c1 c2 check
+permutation r1 w3 w2 p1 p3 c3 r2 p2 c2 c1 check
+permutation r1 w3 w2 p1 p3 c3 r2 c1 p2 c2 check
+permutation r1 w3 w2 p1 p3 c3 c1 r2 p2 c2 check
+permutation r1 w3 w2 p3 r2 p1 p2 c3 c1 c2 check
+permutation r1 w3 w2 p3 r2 p1 p2 c3 c2 c1 check
+permutation r1 w3 w2 p3 r2 p1 c3 p2 c1 c2 check
+permutation r1 w3 w2 p3 r2 p1 c3 p2 c2 c1 check
+permutation r1 w3 w2 p3 r2 p1 c3 c1 p2 c2 check
+permutation r1 w3 w2 p3 r2 p2 p1 c3 c1 c2 check
+permutation r1 w3 w2 p3 r2 p2 p1 c3 c2 c1 check
+permutation r1 w3 w2 p3 r2 p2 c3 p1 c1 c2 check
+permutation r1 w3 w2 p3 r2 p2 c3 p1 c2 c1 check
+permutation r1 w3 w2 p3 r2 p2 c3 c2 p1 c1 check
+permutation r1 w3 w2 p3 r2 c3 p1 p2 c1 c2 check
+permutation r1 w3 w2 p3 r2 c3 p1 p2 c2 c1 check
+permutation r1 w3 w2 p3 r2 c3 p1 c1 p2 c2 check
+permutation r1 w3 w2 p3 r2 c3 p2 p1 c1 c2 check
+permutation r1 w3 w2 p3 r2 c3 p2 p1 c2 c1 check
+permutation r1 w3 w2 p3 r2 c3 p2 c2 p1 c1 check
+permutation r1 w3 w2 p3 p1 r2 p2 c3 c1 c2 check
+permutation r1 w3 w2 p3 p1 r2 p2 c3 c2 c1 check
+permutation r1 w3 w2 p3 p1 r2 c3 p2 c1 c2 check
+permutation r1 w3 w2 p3 p1 r2 c3 p2 c2 c1 check
+permutation r1 w3 w2 p3 p1 r2 c3 c1 p2 c2 check
+permutation r1 w3 w2 p3 p1 c3 r2 p2 c1 c2 check
+permutation r1 w3 w2 p3 p1 c3 r2 p2 c2 c1 check
+permutation r1 w3 w2 p3 p1 c3 r2 c1 p2 c2 check
+permutation r1 w3 w2 p3 p1 c3 c1 r2 p2 c2 check
+permutation r1 w3 w2 p3 c3 r2 p1 p2 c1 c2 check
+permutation r1 w3 w2 p3 c3 r2 p1 p2 c2 c1 check
+permutation r1 w3 w2 p3 c3 r2 p1 c1 p2 c2 check
+permutation r1 w3 w2 p3 c3 r2 p2 p1 c1 c2 check
+permutation r1 w3 w2 p3 c3 r2 p2 p1 c2 c1 check
+permutation r1 w3 w2 p3 c3 r2 p2 c2 p1 c1 check
+permutation r1 w3 w2 p3 c3 p1 r2 p2 c1 c2 check
+permutation r1 w3 w2 p3 c3 p1 r2 p2 c2 c1 check
+permutation r1 w3 w2 p3 c3 p1 r2 c1 p2 c2 check
+permutation r1 w3 w2 p3 c3 p1 c1 r2 p2 c2 check
+permutation r1 w3 p1 r2 w2 p2 p3 c3 c1 c2 check
+permutation r1 w3 p1 r2 w2 p2 p3 c3 c2 c1 check
+permutation r1 w3 p1 r2 w2 p3 p2 c3 c1 c2 check
+permutation r1 w3 p1 r2 w2 p3 p2 c3 c2 c1 check
+permutation r1 w3 p1 r2 w2 p3 c3 p2 c1 c2 check
+permutation r1 w3 p1 r2 w2 p3 c3 p2 c2 c1 check
+permutation r1 w3 p1 r2 w2 p3 c3 c1 p2 c2 check
+permutation r1 w3 p1 r2 p3 w2 p2 c3 c1 c2 check
+permutation r1 w3 p1 r2 p3 w2 p2 c3 c2 c1 check
+permutation r1 w3 p1 r2 p3 w2 c3 p2 c1 c2 check
+permutation r1 w3 p1 r2 p3 w2 c3 p2 c2 c1 check
+permutation r1 w3 p1 r2 p3 w2 c3 c1 p2 c2 check
+permutation r1 w3 p1 r2 p3 c3 w2 p2 c1 c2 check
+permutation r1 w3 p1 r2 p3 c3 w2 p2 c2 c1 check
+permutation r1 w3 p1 r2 p3 c3 w2 c1 p2 c2 check
+permutation r1 w3 p1 r2 p3 c3 c1 w2 p2 c2 check
+permutation r1 w3 p1 w2 r2 p2 p3 c3 c1 c2 check
+permutation r1 w3 p1 w2 r2 p2 p3 c3 c2 c1 check
+permutation r1 w3 p1 w2 r2 p3 p2 c3 c1 c2 check
+permutation r1 w3 p1 w2 r2 p3 p2 c3 c2 c1 check
+permutation r1 w3 p1 w2 r2 p3 c3 p2 c1 c2 check
+permutation r1 w3 p1 w2 r2 p3 c3 p2 c2 c1 check
+permutation r1 w3 p1 w2 r2 p3 c3 c1 p2 c2 check
+permutation r1 w3 p1 w2 p3 r2 p2 c3 c1 c2 check
+permutation r1 w3 p1 w2 p3 r2 p2 c3 c2 c1 check
+permutation r1 w3 p1 w2 p3 r2 c3 p2 c1 c2 check
+permutation r1 w3 p1 w2 p3 r2 c3 p2 c2 c1 check
+permutation r1 w3 p1 w2 p3 r2 c3 c1 p2 c2 check
+permutation r1 w3 p1 w2 p3 c3 r2 p2 c1 c2 check
+permutation r1 w3 p1 w2 p3 c3 r2 p2 c2 c1 check
+permutation r1 w3 p1 w2 p3 c3 r2 c1 p2 c2 check
+permutation r1 w3 p1 w2 p3 c3 c1 r2 p2 c2 check
+permutation r1 w3 p1 p3 r2 w2 p2 c3 c1 c2 check
+permutation r1 w3 p1 p3 r2 w2 p2 c3 c2 c1 check
+permutation r1 w3 p1 p3 r2 w2 c3 p2 c1 c2 check
+permutation r1 w3 p1 p3 r2 w2 c3 p2 c2 c1 check
+permutation r1 w3 p1 p3 r2 w2 c3 c1 p2 c2 check
+permutation r1 w3 p1 p3 r2 c3 w2 p2 c1 c2 check
+permutation r1 w3 p1 p3 r2 c3 w2 p2 c2 c1 check
+permutation r1 w3 p1 p3 r2 c3 w2 c1 p2 c2 check
+permutation r1 w3 p1 p3 r2 c3 c1 w2 p2 c2 check
+permutation r1 w3 p1 p3 w2 r2 p2 c3 c1 c2 check
+permutation r1 w3 p1 p3 w2 r2 p2 c3 c2 c1 check
+permutation r1 w3 p1 p3 w2 r2 c3 p2 c1 c2 check
+permutation r1 w3 p1 p3 w2 r2 c3 p2 c2 c1 check
+permutation r1 w3 p1 p3 w2 r2 c3 c1 p2 c2 check
+permutation r1 w3 p1 p3 w2 c3 r2 p2 c1 c2 check
+permutation r1 w3 p1 p3 w2 c3 r2 p2 c2 c1 check
+permutation r1 w3 p1 p3 w2 c3 r2 c1 p2 c2 check
+permutation r1 w3 p1 p3 w2 c3 c1 r2 p2 c2 check
+permutation r1 w3 p1 p3 c3 r2 w2 p2 c1 c2 check
+permutation r1 w3 p1 p3 c3 r2 w2 p2 c2 c1 check
+permutation r1 w3 p1 p3 c3 r2 w2 c1 p2 c2 check
+permutation r1 w3 p1 p3 c3 r2 c1 w2 p2 c2 check
+permutation r1 w3 p1 p3 c3 w2 r2 p2 c1 c2 check
+permutation r1 w3 p1 p3 c3 w2 r2 p2 c2 c1 check
+permutation r1 w3 p1 p3 c3 w2 r2 c1 p2 c2 check
+permutation r1 w3 p1 p3 c3 w2 c1 r2 p2 c2 check
+permutation r1 w3 p1 p3 c3 c1 r2 w2 p2 c2 check
+permutation r1 w3 p1 p3 c3 c1 w2 r2 p2 c2 check
+permutation r1 w3 p3 r2 w2 p1 p2 c3 c1 c2 check
+permutation r1 w3 p3 r2 w2 p1 p2 c3 c2 c1 check
+permutation r1 w3 p3 r2 w2 p1 c3 p2 c1 c2 check
+permutation r1 w3 p3 r2 w2 p1 c3 p2 c2 c1 check
+permutation r1 w3 p3 r2 w2 p1 c3 c1 p2 c2 check
+permutation r1 w3 p3 r2 w2 p2 p1 c3 c1 c2 check
+permutation r1 w3 p3 r2 w2 p2 p1 c3 c2 c1 check
+permutation r1 w3 p3 r2 w2 p2 c3 p1 c1 c2 check
+permutation r1 w3 p3 r2 w2 p2 c3 p1 c2 c1 check
+permutation r1 w3 p3 r2 w2 p2 c3 c2 p1 c1 check
+permutation r1 w3 p3 r2 w2 c3 p1 p2 c1 c2 check
+permutation r1 w3 p3 r2 w2 c3 p1 p2 c2 c1 check
+permutation r1 w3 p3 r2 w2 c3 p1 c1 p2 c2 check
+permutation r1 w3 p3 r2 w2 c3 p2 p1 c1 c2 check
+permutation r1 w3 p3 r2 w2 c3 p2 p1 c2 c1 check
+permutation r1 w3 p3 r2 w2 c3 p2 c2 p1 c1 check
+permutation r1 w3 p3 r2 p1 w2 p2 c3 c1 c2 check
+permutation r1 w3 p3 r2 p1 w2 p2 c3 c2 c1 check
+permutation r1 w3 p3 r2 p1 w2 c3 p2 c1 c2 check
+permutation r1 w3 p3 r2 p1 w2 c3 p2 c2 c1 check
+permutation r1 w3 p3 r2 p1 w2 c3 c1 p2 c2 check
+permutation r1 w3 p3 r2 p1 c3 w2 p2 c1 c2 check
+permutation r1 w3 p3 r2 p1 c3 w2 p2 c2 c1 check
+permutation r1 w3 p3 r2 p1 c3 w2 c1 p2 c2 check
+permutation r1 w3 p3 r2 p1 c3 c1 w2 p2 c2 check
+permutation r1 w3 p3 r2 c3 w2 p1 p2 c1 c2 check
+permutation r1 w3 p3 r2 c3 w2 p1 p2 c2 c1 check
+permutation r1 w3 p3 r2 c3 w2 p1 c1 p2 c2 check
+permutation r1 w3 p3 r2 c3 w2 p2 p1 c1 c2 check
+permutation r1 w3 p3 r2 c3 w2 p2 p1 c2 c1 check
+permutation r1 w3 p3 r2 c3 w2 p2 c2 p1 c1 check
+permutation r1 w3 p3 r2 c3 p1 w2 p2 c1 c2 check
+permutation r1 w3 p3 r2 c3 p1 w2 p2 c2 c1 check
+permutation r1 w3 p3 r2 c3 p1 w2 c1 p2 c2 check
+permutation r1 w3 p3 r2 c3 p1 c1 w2 p2 c2 check
+permutation r1 w3 p3 w2 r2 p1 p2 c3 c1 c2 check
+permutation r1 w3 p3 w2 r2 p1 p2 c3 c2 c1 check
+permutation r1 w3 p3 w2 r2 p1 c3 p2 c1 c2 check
+permutation r1 w3 p3 w2 r2 p1 c3 p2 c2 c1 check
+permutation r1 w3 p3 w2 r2 p1 c3 c1 p2 c2 check
+permutation r1 w3 p3 w2 r2 p2 p1 c3 c1 c2 check
+permutation r1 w3 p3 w2 r2 p2 p1 c3 c2 c1 check
+permutation r1 w3 p3 w2 r2 p2 c3 p1 c1 c2 check
+permutation r1 w3 p3 w2 r2 p2 c3 p1 c2 c1 check
+permutation r1 w3 p3 w2 r2 p2 c3 c2 p1 c1 check
+permutation r1 w3 p3 w2 r2 c3 p1 p2 c1 c2 check
+permutation r1 w3 p3 w2 r2 c3 p1 p2 c2 c1 check
+permutation r1 w3 p3 w2 r2 c3 p1 c1 p2 c2 check
+permutation r1 w3 p3 w2 r2 c3 p2 p1 c1 c2 check
+permutation r1 w3 p3 w2 r2 c3 p2 p1 c2 c1 check
+permutation r1 w3 p3 w2 r2 c3 p2 c2 p1 c1 check
+permutation r1 w3 p3 w2 p1 r2 p2 c3 c1 c2 check
+permutation r1 w3 p3 w2 p1 r2 p2 c3 c2 c1 check
+permutation r1 w3 p3 w2 p1 r2 c3 p2 c1 c2 check
+permutation r1 w3 p3 w2 p1 r2 c3 p2 c2 c1 check
+permutation r1 w3 p3 w2 p1 r2 c3 c1 p2 c2 check
+permutation r1 w3 p3 w2 p1 c3 r2 p2 c1 c2 check
+permutation r1 w3 p3 w2 p1 c3 r2 p2 c2 c1 check
+permutation r1 w3 p3 w2 p1 c3 r2 c1 p2 c2 check
+permutation r1 w3 p3 w2 p1 c3 c1 r2 p2 c2 check
+permutation r1 w3 p3 w2 c3 r2 p1 p2 c1 c2 check
+permutation r1 w3 p3 w2 c3 r2 p1 p2 c2 c1 check
+permutation r1 w3 p3 w2 c3 r2 p1 c1 p2 c2 check
+permutation r1 w3 p3 w2 c3 r2 p2 p1 c1 c2 check
+permutation r1 w3 p3 w2 c3 r2 p2 p1 c2 c1 check
+permutation r1 w3 p3 w2 c3 r2 p2 c2 p1 c1 check
+permutation r1 w3 p3 w2 c3 p1 r2 p2 c1 c2 check
+permutation r1 w3 p3 w2 c3 p1 r2 p2 c2 c1 check
+permutation r1 w3 p3 w2 c3 p1 r2 c1 p2 c2 check
+permutation r1 w3 p3 w2 c3 p1 c1 r2 p2 c2 check
+permutation r1 w3 p3 p1 r2 w2 p2 c3 c1 c2 check
+permutation r1 w3 p3 p1 r2 w2 p2 c3 c2 c1 check
+permutation r1 w3 p3 p1 r2 w2 c3 p2 c1 c2 check
+permutation r1 w3 p3 p1 r2 w2 c3 p2 c2 c1 check
+permutation r1 w3 p3 p1 r2 w2 c3 c1 p2 c2 check
+permutation r1 w3 p3 p1 r2 c3 w2 p2 c1 c2 check
+permutation r1 w3 p3 p1 r2 c3 w2 p2 c2 c1 check
+permutation r1 w3 p3 p1 r2 c3 w2 c1 p2 c2 check
+permutation r1 w3 p3 p1 r2 c3 c1 w2 p2 c2 check
+permutation r1 w3 p3 p1 w2 r2 p2 c3 c1 c2 check
+permutation r1 w3 p3 p1 w2 r2 p2 c3 c2 c1 check
+permutation r1 w3 p3 p1 w2 r2 c3 p2 c1 c2 check
+permutation r1 w3 p3 p1 w2 r2 c3 p2 c2 c1 check
+permutation r1 w3 p3 p1 w2 r2 c3 c1 p2 c2 check
+permutation r1 w3 p3 p1 w2 c3 r2 p2 c1 c2 check
+permutation r1 w3 p3 p1 w2 c3 r2 p2 c2 c1 check
+permutation r1 w3 p3 p1 w2 c3 r2 c1 p2 c2 check
+permutation r1 w3 p3 p1 w2 c3 c1 r2 p2 c2 check
+permutation r1 w3 p3 p1 c3 r2 w2 p2 c1 c2 check
+permutation r1 w3 p3 p1 c3 r2 w2 p2 c2 c1 check
+permutation r1 w3 p3 p1 c3 r2 w2 c1 p2 c2 check
+permutation r1 w3 p3 p1 c3 r2 c1 w2 p2 c2 check
+permutation r1 w3 p3 p1 c3 w2 r2 p2 c1 c2 check
+permutation r1 w3 p3 p1 c3 w2 r2 p2 c2 c1 check
+permutation r1 w3 p3 p1 c3 w2 r2 c1 p2 c2 check
+permutation r1 w3 p3 p1 c3 w2 c1 r2 p2 c2 check
+permutation r1 w3 p3 p1 c3 c1 r2 w2 p2 c2 check
+permutation r1 w3 p3 p1 c3 c1 w2 r2 p2 c2 check
+permutation r1 w3 p3 c3 r2 w2 p1 p2 c1 c2 check
+permutation r1 w3 p3 c3 r2 w2 p1 p2 c2 c1 check
+permutation r1 w3 p3 c3 r2 w2 p1 c1 p2 c2 check
+permutation r1 w3 p3 c3 r2 w2 p2 p1 c1 c2 check
+permutation r1 w3 p3 c3 r2 w2 p2 p1 c2 c1 check
+permutation r1 w3 p3 c3 r2 w2 p2 c2 p1 c1 check
+permutation r1 w3 p3 c3 r2 p1 w2 p2 c1 c2 check
+permutation r1 w3 p3 c3 r2 p1 w2 p2 c2 c1 check
+permutation r1 w3 p3 c3 r2 p1 w2 c1 p2 c2 check
+permutation r1 w3 p3 c3 r2 p1 c1 w2 p2 c2 check
+permutation r1 w3 p3 c3 w2 r2 p1 p2 c1 c2 check
+permutation r1 w3 p3 c3 w2 r2 p1 p2 c2 c1 check
+permutation r1 w3 p3 c3 w2 r2 p1 c1 p2 c2 check
+permutation r1 w3 p3 c3 w2 r2 p2 p1 c1 c2 check
+permutation r1 w3 p3 c3 w2 r2 p2 p1 c2 c1 check
+permutation r1 w3 p3 c3 w2 r2 p2 c2 p1 c1 check
+permutation r1 w3 p3 c3 w2 p1 r2 p2 c1 c2 check
+permutation r1 w3 p3 c3 w2 p1 r2 p2 c2 c1 check
+permutation r1 w3 p3 c3 w2 p1 r2 c1 p2 c2 check
+permutation r1 w3 p3 c3 w2 p1 c1 r2 p2 c2 check
+permutation r1 w3 p3 c3 p1 r2 w2 p2 c1 c2 check
+permutation r1 w3 p3 c3 p1 r2 w2 p2 c2 c1 check
+permutation r1 w3 p3 c3 p1 r2 w2 c1 p2 c2 check
+permutation r1 w3 p3 c3 p1 r2 c1 w2 p2 c2 check
+permutation r1 w3 p3 c3 p1 w2 r2 p2 c1 c2 check
+permutation r1 w3 p3 c3 p1 w2 r2 p2 c2 c1 check
+permutation r1 w3 p3 c3 p1 w2 r2 c1 p2 c2 check
+permutation r1 w3 p3 c3 p1 w2 c1 r2 p2 c2 check
+permutation r1 w3 p3 c3 p1 c1 r2 w2 p2 c2 check
+permutation r1 w3 p3 c3 p1 c1 w2 r2 p2 c2 check
+permutation r1 p1 r2 w2 w3 p2 p3 c3 c1 c2 check
+permutation r1 p1 r2 w2 w3 p2 p3 c3 c2 c1 check
+permutation r1 p1 r2 w2 w3 p3 p2 c3 c1 c2 check
+permutation r1 p1 r2 w2 w3 p3 p2 c3 c2 c1 check
+permutation r1 p1 r2 w2 w3 p3 c3 p2 c1 c2 check
+permutation r1 p1 r2 w2 w3 p3 c3 p2 c2 c1 check
+permutation r1 p1 r2 w2 w3 p3 c3 c1 p2 c2 check
+permutation r1 p1 r2 w2 p2 w3 p3 c3 c1 c2 check
+permutation r1 p1 r2 w2 p2 w3 p3 c3 c2 c1 check
+permutation r1 p1 w2 w3 r2 p2 p3 c3 c1 c2 check
+permutation r1 p1 w2 w3 r2 p2 p3 c3 c2 c1 check
+permutation r1 p1 w2 w3 r2 p3 p2 c3 c1 c2 check
+permutation r1 p1 w2 w3 r2 p3 p2 c3 c2 c1 check
+permutation r1 p1 w2 w3 r2 p3 c3 p2 c1 c2 check
+permutation r1 p1 w2 w3 r2 p3 c3 p2 c2 c1 check
+permutation r1 p1 w2 w3 r2 p3 c3 c1 p2 c2 check
+permutation r1 p1 w2 w3 p3 r2 p2 c3 c1 c2 check
+permutation r1 p1 w2 w3 p3 r2 p2 c3 c2 c1 check
+permutation r1 p1 w2 w3 p3 r2 c3 p2 c1 c2 check
+permutation r1 p1 w2 w3 p3 r2 c3 p2 c2 c1 check
+permutation r1 p1 w2 w3 p3 r2 c3 c1 p2 c2 check
+permutation r1 p1 w2 w3 p3 c3 r2 p2 c1 c2 check
+permutation r1 p1 w2 w3 p3 c3 r2 p2 c2 c1 check
+permutation r1 p1 w2 w3 p3 c3 r2 c1 p2 c2 check
+permutation r1 p1 w2 w3 p3 c3 c1 r2 p2 c2 check
+permutation r1 p1 w3 r2 w2 p2 p3 c3 c1 c2 check
+permutation r1 p1 w3 r2 w2 p2 p3 c3 c2 c1 check
+permutation r1 p1 w3 r2 w2 p3 p2 c3 c1 c2 check
+permutation r1 p1 w3 r2 w2 p3 p2 c3 c2 c1 check
+permutation r1 p1 w3 r2 w2 p3 c3 p2 c1 c2 check
+permutation r1 p1 w3 r2 w2 p3 c3 p2 c2 c1 check
+permutation r1 p1 w3 r2 w2 p3 c3 c1 p2 c2 check
+permutation r1 p1 w3 r2 p3 w2 p2 c3 c1 c2 check
+permutation r1 p1 w3 r2 p3 w2 p2 c3 c2 c1 check
+permutation r1 p1 w3 r2 p3 w2 c3 p2 c1 c2 check
+permutation r1 p1 w3 r2 p3 w2 c3 p2 c2 c1 check
+permutation r1 p1 w3 r2 p3 w2 c3 c1 p2 c2 check
+permutation r1 p1 w3 r2 p3 c3 w2 p2 c1 c2 check
+permutation r1 p1 w3 r2 p3 c3 w2 p2 c2 c1 check
+permutation r1 p1 w3 r2 p3 c3 w2 c1 p2 c2 check
+permutation r1 p1 w3 r2 p3 c3 c1 w2 p2 c2 check
+permutation r1 p1 w3 w2 r2 p2 p3 c3 c1 c2 check
+permutation r1 p1 w3 w2 r2 p2 p3 c3 c2 c1 check
+permutation r1 p1 w3 w2 r2 p3 p2 c3 c1 c2 check
+permutation r1 p1 w3 w2 r2 p3 p2 c3 c2 c1 check
+permutation r1 p1 w3 w2 r2 p3 c3 p2 c1 c2 check
+permutation r1 p1 w3 w2 r2 p3 c3 p2 c2 c1 check
+permutation r1 p1 w3 w2 r2 p3 c3 c1 p2 c2 check
+permutation r1 p1 w3 w2 p3 r2 p2 c3 c1 c2 check
+permutation r1 p1 w3 w2 p3 r2 p2 c3 c2 c1 check
+permutation r1 p1 w3 w2 p3 r2 c3 p2 c1 c2 check
+permutation r1 p1 w3 w2 p3 r2 c3 p2 c2 c1 check
+permutation r1 p1 w3 w2 p3 r2 c3 c1 p2 c2 check
+permutation r1 p1 w3 w2 p3 c3 r2 p2 c1 c2 check
+permutation r1 p1 w3 w2 p3 c3 r2 p2 c2 c1 check
+permutation r1 p1 w3 w2 p3 c3 r2 c1 p2 c2 check
+permutation r1 p1 w3 w2 p3 c3 c1 r2 p2 c2 check
+permutation r1 p1 w3 p3 r2 w2 p2 c3 c1 c2 check
+permutation r1 p1 w3 p3 r2 w2 p2 c3 c2 c1 check
+permutation r1 p1 w3 p3 r2 w2 c3 p2 c1 c2 check
+permutation r1 p1 w3 p3 r2 w2 c3 p2 c2 c1 check
+permutation r1 p1 w3 p3 r2 w2 c3 c1 p2 c2 check
+permutation r1 p1 w3 p3 r2 c3 w2 p2 c1 c2 check
+permutation r1 p1 w3 p3 r2 c3 w2 p2 c2 c1 check
+permutation r1 p1 w3 p3 r2 c3 w2 c1 p2 c2 check
+permutation r1 p1 w3 p3 r2 c3 c1 w2 p2 c2 check
+permutation r1 p1 w3 p3 w2 r2 p2 c3 c1 c2 check
+permutation r1 p1 w3 p3 w2 r2 p2 c3 c2 c1 check
+permutation r1 p1 w3 p3 w2 r2 c3 p2 c1 c2 check
+permutation r1 p1 w3 p3 w2 r2 c3 p2 c2 c1 check
+permutation r1 p1 w3 p3 w2 r2 c3 c1 p2 c2 check
+permutation r1 p1 w3 p3 w2 c3 r2 p2 c1 c2 check
+permutation r1 p1 w3 p3 w2 c3 r2 p2 c2 c1 check
+permutation r1 p1 w3 p3 w2 c3 r2 c1 p2 c2 check
+permutation r1 p1 w3 p3 w2 c3 c1 r2 p2 c2 check
+permutation r1 p1 w3 p3 c3 r2 w2 p2 c1 c2 check
+permutation r1 p1 w3 p3 c3 r2 w2 p2 c2 c1 check
+permutation r1 p1 w3 p3 c3 r2 w2 c1 p2 c2 check
+permutation r1 p1 w3 p3 c3 r2 c1 w2 p2 c2 check
+permutation r1 p1 w3 p3 c3 w2 r2 p2 c1 c2 check
+permutation r1 p1 w3 p3 c3 w2 r2 p2 c2 c1 check
+permutation r1 p1 w3 p3 c3 w2 r2 c1 p2 c2 check
+permutation r1 p1 w3 p3 c3 w2 c1 r2 p2 c2 check
+permutation r1 p1 w3 p3 c3 c1 r2 w2 p2 c2 check
+permutation r1 p1 w3 p3 c3 c1 w2 r2 p2 c2 check
+permutation w2 r1 r2 w3 p1 p2 p3 c3 c1 c2 check
+permutation w2 r1 r2 w3 p1 p2 p3 c3 c2 c1 check
+permutation w2 r1 r2 w3 p1 p3 p2 c3 c1 c2 check
+permutation w2 r1 r2 w3 p1 p3 p2 c3 c2 c1 check
+permutation w2 r1 r2 w3 p1 p3 c3 p2 c1 c2 check
+permutation w2 r1 r2 w3 p1 p3 c3 p2 c2 c1 check
+permutation w2 r1 r2 w3 p1 p3 c3 c1 p2 c2 check
+permutation w2 r1 r2 w3 p2 p1 p3 c3 c1 c2 check
+permutation w2 r1 r2 w3 p2 p1 p3 c3 c2 c1 check
+permutation w2 r1 r2 w3 p2 p3 p1 c3 c1 c2 check
+permutation w2 r1 r2 w3 p2 p3 p1 c3 c2 c1 check
+permutation w2 r1 r2 w3 p2 p3 c3 p1 c1 c2 check
+permutation w2 r1 r2 w3 p2 p3 c3 p1 c2 c1 check
+permutation w2 r1 r2 w3 p2 p3 c3 c2 p1 c1 check
+permutation w2 r1 r2 w3 p3 p1 p2 c3 c1 c2 check
+permutation w2 r1 r2 w3 p3 p1 p2 c3 c2 c1 check
+permutation w2 r1 r2 w3 p3 p1 c3 p2 c1 c2 check
+permutation w2 r1 r2 w3 p3 p1 c3 p2 c2 c1 check
+permutation w2 r1 r2 w3 p3 p1 c3 c1 p2 c2 check
+permutation w2 r1 r2 w3 p3 p2 p1 c3 c1 c2 check
+permutation w2 r1 r2 w3 p3 p2 p1 c3 c2 c1 check
+permutation w2 r1 r2 w3 p3 p2 c3 p1 c1 c2 check
+permutation w2 r1 r2 w3 p3 p2 c3 p1 c2 c1 check
+permutation w2 r1 r2 w3 p3 p2 c3 c2 p1 c1 check
+permutation w2 r1 r2 w3 p3 c3 p1 p2 c1 c2 check
+permutation w2 r1 r2 w3 p3 c3 p1 p2 c2 c1 check
+permutation w2 r1 r2 w3 p3 c3 p1 c1 p2 c2 check
+permutation w2 r1 r2 w3 p3 c3 p2 p1 c1 c2 check
+permutation w2 r1 r2 w3 p3 c3 p2 p1 c2 c1 check
+permutation w2 r1 r2 w3 p3 c3 p2 c2 p1 c1 check
+permutation w2 r1 r2 p1 w3 p2 p3 c3 c1 c2 check
+permutation w2 r1 r2 p1 w3 p2 p3 c3 c2 c1 check
+permutation w2 r1 r2 p1 w3 p3 p2 c3 c1 c2 check
+permutation w2 r1 r2 p1 w3 p3 p2 c3 c2 c1 check
+permutation w2 r1 r2 p1 w3 p3 c3 p2 c1 c2 check
+permutation w2 r1 r2 p1 w3 p3 c3 p2 c2 c1 check
+permutation w2 r1 r2 p1 w3 p3 c3 c1 p2 c2 check
+permutation w2 r1 r2 p1 p2 w3 p3 c3 c1 c2 check
+permutation w2 r1 r2 p1 p2 w3 p3 c3 c2 c1 check
+permutation w2 r1 r2 p2 w3 p1 p3 c3 c1 c2 check
+permutation w2 r1 r2 p2 w3 p1 p3 c3 c2 c1 check
+permutation w2 r1 r2 p2 w3 p3 p1 c3 c1 c2 check
+permutation w2 r1 r2 p2 w3 p3 p1 c3 c2 c1 check
+permutation w2 r1 r2 p2 w3 p3 c3 p1 c1 c2 check
+permutation w2 r1 r2 p2 w3 p3 c3 p1 c2 c1 check
+permutation w2 r1 r2 p2 w3 p3 c3 c2 p1 c1 check
+permutation w2 r1 r2 p2 p1 w3 p3 c3 c1 c2 check
+permutation w2 r1 r2 p2 p1 w3 p3 c3 c2 c1 check
+permutation w2 r1 w3 r2 p1 p2 p3 c3 c1 c2 check
+permutation w2 r1 w3 r2 p1 p2 p3 c3 c2 c1 check
+permutation w2 r1 w3 r2 p1 p3 p2 c3 c1 c2 check
+permutation w2 r1 w3 r2 p1 p3 p2 c3 c2 c1 check
+permutation w2 r1 w3 r2 p1 p3 c3 p2 c1 c2 check
+permutation w2 r1 w3 r2 p1 p3 c3 p2 c2 c1 check
+permutation w2 r1 w3 r2 p1 p3 c3 c1 p2 c2 check
+permutation w2 r1 w3 r2 p2 p1 p3 c3 c1 c2 check
+permutation w2 r1 w3 r2 p2 p1 p3 c3 c2 c1 check
+permutation w2 r1 w3 r2 p2 p3 p1 c3 c1 c2 check
+permutation w2 r1 w3 r2 p2 p3 p1 c3 c2 c1 check
+permutation w2 r1 w3 r2 p2 p3 c3 p1 c1 c2 check
+permutation w2 r1 w3 r2 p2 p3 c3 p1 c2 c1 check
+permutation w2 r1 w3 r2 p2 p3 c3 c2 p1 c1 check
+permutation w2 r1 w3 r2 p3 p1 p2 c3 c1 c2 check
+permutation w2 r1 w3 r2 p3 p1 p2 c3 c2 c1 check
+permutation w2 r1 w3 r2 p3 p1 c3 p2 c1 c2 check
+permutation w2 r1 w3 r2 p3 p1 c3 p2 c2 c1 check
+permutation w2 r1 w3 r2 p3 p1 c3 c1 p2 c2 check
+permutation w2 r1 w3 r2 p3 p2 p1 c3 c1 c2 check
+permutation w2 r1 w3 r2 p3 p2 p1 c3 c2 c1 check
+permutation w2 r1 w3 r2 p3 p2 c3 p1 c1 c2 check
+permutation w2 r1 w3 r2 p3 p2 c3 p1 c2 c1 check
+permutation w2 r1 w3 r2 p3 p2 c3 c2 p1 c1 check
+permutation w2 r1 w3 r2 p3 c3 p1 p2 c1 c2 check
+permutation w2 r1 w3 r2 p3 c3 p1 p2 c2 c1 check
+permutation w2 r1 w3 r2 p3 c3 p1 c1 p2 c2 check
+permutation w2 r1 w3 r2 p3 c3 p2 p1 c1 c2 check
+permutation w2 r1 w3 r2 p3 c3 p2 p1 c2 c1 check
+permutation w2 r1 w3 r2 p3 c3 p2 c2 p1 c1 check
+permutation w2 r1 w3 p1 r2 p2 p3 c3 c1 c2 check
+permutation w2 r1 w3 p1 r2 p2 p3 c3 c2 c1 check
+permutation w2 r1 w3 p1 r2 p3 p2 c3 c1 c2 check
+permutation w2 r1 w3 p1 r2 p3 p2 c3 c2 c1 check
+permutation w2 r1 w3 p1 r2 p3 c3 p2 c1 c2 check
+permutation w2 r1 w3 p1 r2 p3 c3 p2 c2 c1 check
+permutation w2 r1 w3 p1 r2 p3 c3 c1 p2 c2 check
+permutation w2 r1 w3 p1 p3 r2 p2 c3 c1 c2 check
+permutation w2 r1 w3 p1 p3 r2 p2 c3 c2 c1 check
+permutation w2 r1 w3 p1 p3 r2 c3 p2 c1 c2 check
+permutation w2 r1 w3 p1 p3 r2 c3 p2 c2 c1 check
+permutation w2 r1 w3 p1 p3 r2 c3 c1 p2 c2 check
+permutation w2 r1 w3 p1 p3 c3 r2 p2 c1 c2 check
+permutation w2 r1 w3 p1 p3 c3 r2 p2 c2 c1 check
+permutation w2 r1 w3 p1 p3 c3 r2 c1 p2 c2 check
+permutation w2 r1 w3 p1 p3 c3 c1 r2 p2 c2 check
+permutation w2 r1 w3 p3 r2 p1 p2 c3 c1 c2 check
+permutation w2 r1 w3 p3 r2 p1 p2 c3 c2 c1 check
+permutation w2 r1 w3 p3 r2 p1 c3 p2 c1 c2 check
+permutation w2 r1 w3 p3 r2 p1 c3 p2 c2 c1 check
+permutation w2 r1 w3 p3 r2 p1 c3 c1 p2 c2 check
+permutation w2 r1 w3 p3 r2 p2 p1 c3 c1 c2 check
+permutation w2 r1 w3 p3 r2 p2 p1 c3 c2 c1 check
+permutation w2 r1 w3 p3 r2 p2 c3 p1 c1 c2 check
+permutation w2 r1 w3 p3 r2 p2 c3 p1 c2 c1 check
+permutation w2 r1 w3 p3 r2 p2 c3 c2 p1 c1 check
+permutation w2 r1 w3 p3 r2 c3 p1 p2 c1 c2 check
+permutation w2 r1 w3 p3 r2 c3 p1 p2 c2 c1 check
+permutation w2 r1 w3 p3 r2 c3 p1 c1 p2 c2 check
+permutation w2 r1 w3 p3 r2 c3 p2 p1 c1 c2 check
+permutation w2 r1 w3 p3 r2 c3 p2 p1 c2 c1 check
+permutation w2 r1 w3 p3 r2 c3 p2 c2 p1 c1 check
+permutation w2 r1 w3 p3 p1 r2 p2 c3 c1 c2 check
+permutation w2 r1 w3 p3 p1 r2 p2 c3 c2 c1 check
+permutation w2 r1 w3 p3 p1 r2 c3 p2 c1 c2 check
+permutation w2 r1 w3 p3 p1 r2 c3 p2 c2 c1 check
+permutation w2 r1 w3 p3 p1 r2 c3 c1 p2 c2 check
+permutation w2 r1 w3 p3 p1 c3 r2 p2 c1 c2 check
+permutation w2 r1 w3 p3 p1 c3 r2 p2 c2 c1 check
+permutation w2 r1 w3 p3 p1 c3 r2 c1 p2 c2 check
+permutation w2 r1 w3 p3 p1 c3 c1 r2 p2 c2 check
+permutation w2 r1 w3 p3 c3 r2 p1 p2 c1 c2 check
+permutation w2 r1 w3 p3 c3 r2 p1 p2 c2 c1 check
+permutation w2 r1 w3 p3 c3 r2 p1 c1 p2 c2 check
+permutation w2 r1 w3 p3 c3 r2 p2 p1 c1 c2 check
+permutation w2 r1 w3 p3 c3 r2 p2 p1 c2 c1 check
+permutation w2 r1 w3 p3 c3 r2 p2 c2 p1 c1 check
+permutation w2 r1 w3 p3 c3 p1 r2 p2 c1 c2 check
+permutation w2 r1 w3 p3 c3 p1 r2 p2 c2 c1 check
+permutation w2 r1 w3 p3 c3 p1 r2 c1 p2 c2 check
+permutation w2 r1 w3 p3 c3 p1 c1 r2 p2 c2 check
+permutation w2 r1 p1 r2 w3 p2 p3 c3 c1 c2 check
+permutation w2 r1 p1 r2 w3 p2 p3 c3 c2 c1 check
+permutation w2 r1 p1 r2 w3 p3 p2 c3 c1 c2 check
+permutation w2 r1 p1 r2 w3 p3 p2 c3 c2 c1 check
+permutation w2 r1 p1 r2 w3 p3 c3 p2 c1 c2 check
+permutation w2 r1 p1 r2 w3 p3 c3 p2 c2 c1 check
+permutation w2 r1 p1 r2 w3 p3 c3 c1 p2 c2 check
+permutation w2 r1 p1 r2 p2 w3 p3 c3 c1 c2 check
+permutation w2 r1 p1 r2 p2 w3 p3 c3 c2 c1 check
+permutation w2 r1 p1 w3 r2 p2 p3 c3 c1 c2 check
+permutation w2 r1 p1 w3 r2 p2 p3 c3 c2 c1 check
+permutation w2 r1 p1 w3 r2 p3 p2 c3 c1 c2 check
+permutation w2 r1 p1 w3 r2 p3 p2 c3 c2 c1 check
+permutation w2 r1 p1 w3 r2 p3 c3 p2 c1 c2 check
+permutation w2 r1 p1 w3 r2 p3 c3 p2 c2 c1 check
+permutation w2 r1 p1 w3 r2 p3 c3 c1 p2 c2 check
+permutation w2 r1 p1 w3 p3 r2 p2 c3 c1 c2 check
+permutation w2 r1 p1 w3 p3 r2 p2 c3 c2 c1 check
+permutation w2 r1 p1 w3 p3 r2 c3 p2 c1 c2 check
+permutation w2 r1 p1 w3 p3 r2 c3 p2 c2 c1 check
+permutation w2 r1 p1 w3 p3 r2 c3 c1 p2 c2 check
+permutation w2 r1 p1 w3 p3 c3 r2 p2 c1 c2 check
+permutation w2 r1 p1 w3 p3 c3 r2 p2 c2 c1 check
+permutation w2 r1 p1 w3 p3 c3 r2 c1 p2 c2 check
+permutation w2 r1 p1 w3 p3 c3 c1 r2 p2 c2 check
+permutation w3 r1 r2 w2 p1 p2 p3 c3 c1 c2 check
+permutation w3 r1 r2 w2 p1 p2 p3 c3 c2 c1 check
+permutation w3 r1 r2 w2 p1 p3 p2 c3 c1 c2 check
+permutation w3 r1 r2 w2 p1 p3 p2 c3 c2 c1 check
+permutation w3 r1 r2 w2 p1 p3 c3 p2 c1 c2 check
+permutation w3 r1 r2 w2 p1 p3 c3 p2 c2 c1 check
+permutation w3 r1 r2 w2 p1 p3 c3 c1 p2 c2 check
+permutation w3 r1 r2 w2 p2 p1 p3 c3 c1 c2 check
+permutation w3 r1 r2 w2 p2 p1 p3 c3 c2 c1 check
+permutation w3 r1 r2 w2 p2 p3 p1 c3 c1 c2 check
+permutation w3 r1 r2 w2 p2 p3 p1 c3 c2 c1 check
+permutation w3 r1 r2 w2 p2 p3 c3 p1 c1 c2 check
+permutation w3 r1 r2 w2 p2 p3 c3 p1 c2 c1 check
+permutation w3 r1 r2 w2 p2 p3 c3 c2 p1 c1 check
+permutation w3 r1 r2 w2 p3 p1 p2 c3 c1 c2 check
+permutation w3 r1 r2 w2 p3 p1 p2 c3 c2 c1 check
+permutation w3 r1 r2 w2 p3 p1 c3 p2 c1 c2 check
+permutation w3 r1 r2 w2 p3 p1 c3 p2 c2 c1 check
+permutation w3 r1 r2 w2 p3 p1 c3 c1 p2 c2 check
+permutation w3 r1 r2 w2 p3 p2 p1 c3 c1 c2 check
+permutation w3 r1 r2 w2 p3 p2 p1 c3 c2 c1 check
+permutation w3 r1 r2 w2 p3 p2 c3 p1 c1 c2 check
+permutation w3 r1 r2 w2 p3 p2 c3 p1 c2 c1 check
+permutation w3 r1 r2 w2 p3 p2 c3 c2 p1 c1 check
+permutation w3 r1 r2 w2 p3 c3 p1 p2 c1 c2 check
+permutation w3 r1 r2 w2 p3 c3 p1 p2 c2 c1 check
+permutation w3 r1 r2 w2 p3 c3 p1 c1 p2 c2 check
+permutation w3 r1 r2 w2 p3 c3 p2 p1 c1 c2 check
+permutation w3 r1 r2 w2 p3 c3 p2 p1 c2 c1 check
+permutation w3 r1 r2 w2 p3 c3 p2 c2 p1 c1 check
+permutation w3 r1 r2 p1 w2 p2 p3 c3 c1 c2 check
+permutation w3 r1 r2 p1 w2 p2 p3 c3 c2 c1 check
+permutation w3 r1 r2 p1 w2 p3 p2 c3 c1 c2 check
+permutation w3 r1 r2 p1 w2 p3 p2 c3 c2 c1 check
+permutation w3 r1 r2 p1 w2 p3 c3 p2 c1 c2 check
+permutation w3 r1 r2 p1 w2 p3 c3 p2 c2 c1 check
+permutation w3 r1 r2 p1 w2 p3 c3 c1 p2 c2 check
+permutation w3 r1 r2 p1 p3 w2 p2 c3 c1 c2 check
+permutation w3 r1 r2 p1 p3 w2 p2 c3 c2 c1 check
+permutation w3 r1 r2 p1 p3 w2 c3 p2 c1 c2 check
+permutation w3 r1 r2 p1 p3 w2 c3 p2 c2 c1 check
+permutation w3 r1 r2 p1 p3 w2 c3 c1 p2 c2 check
+permutation w3 r1 r2 p1 p3 c3 w2 p2 c1 c2 check
+permutation w3 r1 r2 p1 p3 c3 w2 p2 c2 c1 check
+permutation w3 r1 r2 p1 p3 c3 w2 c1 p2 c2 check
+permutation w3 r1 r2 p1 p3 c3 c1 w2 p2 c2 check
+permutation w3 r1 r2 p3 w2 p1 p2 c3 c1 c2 check
+permutation w3 r1 r2 p3 w2 p1 p2 c3 c2 c1 check
+permutation w3 r1 r2 p3 w2 p1 c3 p2 c1 c2 check
+permutation w3 r1 r2 p3 w2 p1 c3 p2 c2 c1 check
+permutation w3 r1 r2 p3 w2 p1 c3 c1 p2 c2 check
+permutation w3 r1 r2 p3 w2 p2 p1 c3 c1 c2 check
+permutation w3 r1 r2 p3 w2 p2 p1 c3 c2 c1 check
+permutation w3 r1 r2 p3 w2 p2 c3 p1 c1 c2 check
+permutation w3 r1 r2 p3 w2 p2 c3 p1 c2 c1 check
+permutation w3 r1 r2 p3 w2 p2 c3 c2 p1 c1 check
+permutation w3 r1 r2 p3 w2 c3 p1 p2 c1 c2 check
+permutation w3 r1 r2 p3 w2 c3 p1 p2 c2 c1 check
+permutation w3 r1 r2 p3 w2 c3 p1 c1 p2 c2 check
+permutation w3 r1 r2 p3 w2 c3 p2 p1 c1 c2 check
+permutation w3 r1 r2 p3 w2 c3 p2 p1 c2 c1 check
+permutation w3 r1 r2 p3 w2 c3 p2 c2 p1 c1 check
+permutation w3 r1 r2 p3 p1 w2 p2 c3 c1 c2 check
+permutation w3 r1 r2 p3 p1 w2 p2 c3 c2 c1 check
+permutation w3 r1 r2 p3 p1 w2 c3 p2 c1 c2 check
+permutation w3 r1 r2 p3 p1 w2 c3 p2 c2 c1 check
+permutation w3 r1 r2 p3 p1 w2 c3 c1 p2 c2 check
+permutation w3 r1 r2 p3 p1 c3 w2 p2 c1 c2 check
+permutation w3 r1 r2 p3 p1 c3 w2 p2 c2 c1 check
+permutation w3 r1 r2 p3 p1 c3 w2 c1 p2 c2 check
+permutation w3 r1 r2 p3 p1 c3 c1 w2 p2 c2 check
+permutation w3 r1 r2 p3 c3 w2 p1 p2 c1 c2 check
+permutation w3 r1 r2 p3 c3 w2 p1 p2 c2 c1 check
+permutation w3 r1 r2 p3 c3 w2 p1 c1 p2 c2 check
+permutation w3 r1 r2 p3 c3 w2 p2 p1 c1 c2 check
+permutation w3 r1 r2 p3 c3 w2 p2 p1 c2 c1 check
+permutation w3 r1 r2 p3 c3 w2 p2 c2 p1 c1 check
+permutation w3 r1 r2 p3 c3 p1 w2 p2 c1 c2 check
+permutation w3 r1 r2 p3 c3 p1 w2 p2 c2 c1 check
+permutation w3 r1 r2 p3 c3 p1 w2 c1 p2 c2 check
+permutation w3 r1 r2 p3 c3 p1 c1 w2 p2 c2 check
+permutation w3 r1 w2 r2 p1 p2 p3 c3 c1 c2 check
+permutation w3 r1 w2 r2 p1 p2 p3 c3 c2 c1 check
+permutation w3 r1 w2 r2 p1 p3 p2 c3 c1 c2 check
+permutation w3 r1 w2 r2 p1 p3 p2 c3 c2 c1 check
+permutation w3 r1 w2 r2 p1 p3 c3 p2 c1 c2 check
+permutation w3 r1 w2 r2 p1 p3 c3 p2 c2 c1 check
+permutation w3 r1 w2 r2 p1 p3 c3 c1 p2 c2 check
+permutation w3 r1 w2 r2 p2 p1 p3 c3 c1 c2 check
+permutation w3 r1 w2 r2 p2 p1 p3 c3 c2 c1 check
+permutation w3 r1 w2 r2 p2 p3 p1 c3 c1 c2 check
+permutation w3 r1 w2 r2 p2 p3 p1 c3 c2 c1 check
+permutation w3 r1 w2 r2 p2 p3 c3 p1 c1 c2 check
+permutation w3 r1 w2 r2 p2 p3 c3 p1 c2 c1 check
+permutation w3 r1 w2 r2 p2 p3 c3 c2 p1 c1 check
+permutation w3 r1 w2 r2 p3 p1 p2 c3 c1 c2 check
+permutation w3 r1 w2 r2 p3 p1 p2 c3 c2 c1 check
+permutation w3 r1 w2 r2 p3 p1 c3 p2 c1 c2 check
+permutation w3 r1 w2 r2 p3 p1 c3 p2 c2 c1 check
+permutation w3 r1 w2 r2 p3 p1 c3 c1 p2 c2 check
+permutation w3 r1 w2 r2 p3 p2 p1 c3 c1 c2 check
+permutation w3 r1 w2 r2 p3 p2 p1 c3 c2 c1 check
+permutation w3 r1 w2 r2 p3 p2 c3 p1 c1 c2 check
+permutation w3 r1 w2 r2 p3 p2 c3 p1 c2 c1 check
+permutation w3 r1 w2 r2 p3 p2 c3 c2 p1 c1 check
+permutation w3 r1 w2 r2 p3 c3 p1 p2 c1 c2 check
+permutation w3 r1 w2 r2 p3 c3 p1 p2 c2 c1 check
+permutation w3 r1 w2 r2 p3 c3 p1 c1 p2 c2 check
+permutation w3 r1 w2 r2 p3 c3 p2 p1 c1 c2 check
+permutation w3 r1 w2 r2 p3 c3 p2 p1 c2 c1 check
+permutation w3 r1 w2 r2 p3 c3 p2 c2 p1 c1 check
+permutation w3 r1 w2 p1 r2 p2 p3 c3 c1 c2 check
+permutation w3 r1 w2 p1 r2 p2 p3 c3 c2 c1 check
+permutation w3 r1 w2 p1 r2 p3 p2 c3 c1 c2 check
+permutation w3 r1 w2 p1 r2 p3 p2 c3 c2 c1 check
+permutation w3 r1 w2 p1 r2 p3 c3 p2 c1 c2 check
+permutation w3 r1 w2 p1 r2 p3 c3 p2 c2 c1 check
+permutation w3 r1 w2 p1 r2 p3 c3 c1 p2 c2 check
+permutation w3 r1 w2 p1 p3 r2 p2 c3 c1 c2 check
+permutation w3 r1 w2 p1 p3 r2 p2 c3 c2 c1 check
+permutation w3 r1 w2 p1 p3 r2 c3 p2 c1 c2 check
+permutation w3 r1 w2 p1 p3 r2 c3 p2 c2 c1 check
+permutation w3 r1 w2 p1 p3 r2 c3 c1 p2 c2 check
+permutation w3 r1 w2 p1 p3 c3 r2 p2 c1 c2 check
+permutation w3 r1 w2 p1 p3 c3 r2 p2 c2 c1 check
+permutation w3 r1 w2 p1 p3 c3 r2 c1 p2 c2 check
+permutation w3 r1 w2 p1 p3 c3 c1 r2 p2 c2 check
+permutation w3 r1 w2 p3 r2 p1 p2 c3 c1 c2 check
+permutation w3 r1 w2 p3 r2 p1 p2 c3 c2 c1 check
+permutation w3 r1 w2 p3 r2 p1 c3 p2 c1 c2 check
+permutation w3 r1 w2 p3 r2 p1 c3 p2 c2 c1 check
+permutation w3 r1 w2 p3 r2 p1 c3 c1 p2 c2 check
+permutation w3 r1 w2 p3 r2 p2 p1 c3 c1 c2 check
+permutation w3 r1 w2 p3 r2 p2 p1 c3 c2 c1 check
+permutation w3 r1 w2 p3 r2 p2 c3 p1 c1 c2 check
+permutation w3 r1 w2 p3 r2 p2 c3 p1 c2 c1 check
+permutation w3 r1 w2 p3 r2 p2 c3 c2 p1 c1 check
+permutation w3 r1 w2 p3 r2 c3 p1 p2 c1 c2 check
+permutation w3 r1 w2 p3 r2 c3 p1 p2 c2 c1 check
+permutation w3 r1 w2 p3 r2 c3 p1 c1 p2 c2 check
+permutation w3 r1 w2 p3 r2 c3 p2 p1 c1 c2 check
+permutation w3 r1 w2 p3 r2 c3 p2 p1 c2 c1 check
+permutation w3 r1 w2 p3 r2 c3 p2 c2 p1 c1 check
+permutation w3 r1 w2 p3 p1 r2 p2 c3 c1 c2 check
+permutation w3 r1 w2 p3 p1 r2 p2 c3 c2 c1 check
+permutation w3 r1 w2 p3 p1 r2 c3 p2 c1 c2 check
+permutation w3 r1 w2 p3 p1 r2 c3 p2 c2 c1 check
+permutation w3 r1 w2 p3 p1 r2 c3 c1 p2 c2 check
+permutation w3 r1 w2 p3 p1 c3 r2 p2 c1 c2 check
+permutation w3 r1 w2 p3 p1 c3 r2 p2 c2 c1 check
+permutation w3 r1 w2 p3 p1 c3 r2 c1 p2 c2 check
+permutation w3 r1 w2 p3 p1 c3 c1 r2 p2 c2 check
+permutation w3 r1 w2 p3 c3 r2 p1 p2 c1 c2 check
+permutation w3 r1 w2 p3 c3 r2 p1 p2 c2 c1 check
+permutation w3 r1 w2 p3 c3 r2 p1 c1 p2 c2 check
+permutation w3 r1 w2 p3 c3 r2 p2 p1 c1 c2 check
+permutation w3 r1 w2 p3 c3 r2 p2 p1 c2 c1 check
+permutation w3 r1 w2 p3 c3 r2 p2 c2 p1 c1 check
+permutation w3 r1 w2 p3 c3 p1 r2 p2 c1 c2 check
+permutation w3 r1 w2 p3 c3 p1 r2 p2 c2 c1 check
+permutation w3 r1 w2 p3 c3 p1 r2 c1 p2 c2 check
+permutation w3 r1 w2 p3 c3 p1 c1 r2 p2 c2 check
+permutation w3 r1 p1 r2 w2 p2 p3 c3 c1 c2 check
+permutation w3 r1 p1 r2 w2 p2 p3 c3 c2 c1 check
+permutation w3 r1 p1 r2 w2 p3 p2 c3 c1 c2 check
+permutation w3 r1 p1 r2 w2 p3 p2 c3 c2 c1 check
+permutation w3 r1 p1 r2 w2 p3 c3 p2 c1 c2 check
+permutation w3 r1 p1 r2 w2 p3 c3 p2 c2 c1 check
+permutation w3 r1 p1 r2 w2 p3 c3 c1 p2 c2 check
+permutation w3 r1 p1 r2 p3 w2 p2 c3 c1 c2 check
+permutation w3 r1 p1 r2 p3 w2 p2 c3 c2 c1 check
+permutation w3 r1 p1 r2 p3 w2 c3 p2 c1 c2 check
+permutation w3 r1 p1 r2 p3 w2 c3 p2 c2 c1 check
+permutation w3 r1 p1 r2 p3 w2 c3 c1 p2 c2 check
+permutation w3 r1 p1 r2 p3 c3 w2 p2 c1 c2 check
+permutation w3 r1 p1 r2 p3 c3 w2 p2 c2 c1 check
+permutation w3 r1 p1 r2 p3 c3 w2 c1 p2 c2 check
+permutation w3 r1 p1 r2 p3 c3 c1 w2 p2 c2 check
+permutation w3 r1 p1 w2 r2 p2 p3 c3 c1 c2 check
+permutation w3 r1 p1 w2 r2 p2 p3 c3 c2 c1 check
+permutation w3 r1 p1 w2 r2 p3 p2 c3 c1 c2 check
+permutation w3 r1 p1 w2 r2 p3 p2 c3 c2 c1 check
+permutation w3 r1 p1 w2 r2 p3 c3 p2 c1 c2 check
+permutation w3 r1 p1 w2 r2 p3 c3 p2 c2 c1 check
+permutation w3 r1 p1 w2 r2 p3 c3 c1 p2 c2 check
+permutation w3 r1 p1 w2 p3 r2 p2 c3 c1 c2 check
+permutation w3 r1 p1 w2 p3 r2 p2 c3 c2 c1 check
+permutation w3 r1 p1 w2 p3 r2 c3 p2 c1 c2 check
+permutation w3 r1 p1 w2 p3 r2 c3 p2 c2 c1 check
+permutation w3 r1 p1 w2 p3 r2 c3 c1 p2 c2 check
+permutation w3 r1 p1 w2 p3 c3 r2 p2 c1 c2 check
+permutation w3 r1 p1 w2 p3 c3 r2 p2 c2 c1 check
+permutation w3 r1 p1 w2 p3 c3 r2 c1 p2 c2 check
+permutation w3 r1 p1 w2 p3 c3 c1 r2 p2 c2 check
+permutation w3 r1 p1 p3 r2 w2 p2 c3 c1 c2 check
+permutation w3 r1 p1 p3 r2 w2 p2 c3 c2 c1 check
+permutation w3 r1 p1 p3 r2 w2 c3 p2 c1 c2 check
+permutation w3 r1 p1 p3 r2 w2 c3 p2 c2 c1 check
+permutation w3 r1 p1 p3 r2 w2 c3 c1 p2 c2 check
+permutation w3 r1 p1 p3 r2 c3 w2 p2 c1 c2 check
+permutation w3 r1 p1 p3 r2 c3 w2 p2 c2 c1 check
+permutation w3 r1 p1 p3 r2 c3 w2 c1 p2 c2 check
+permutation w3 r1 p1 p3 r2 c3 c1 w2 p2 c2 check
+permutation w3 r1 p1 p3 w2 r2 p2 c3 c1 c2 check
+permutation w3 r1 p1 p3 w2 r2 p2 c3 c2 c1 check
+permutation w3 r1 p1 p3 w2 r2 c3 p2 c1 c2 check
+permutation w3 r1 p1 p3 w2 r2 c3 p2 c2 c1 check
+permutation w3 r1 p1 p3 w2 r2 c3 c1 p2 c2 check
+permutation w3 r1 p1 p3 w2 c3 r2 p2 c1 c2 check
+permutation w3 r1 p1 p3 w2 c3 r2 p2 c2 c1 check
+permutation w3 r1 p1 p3 w2 c3 r2 c1 p2 c2 check
+permutation w3 r1 p1 p3 w2 c3 c1 r2 p2 c2 check
+permutation w3 r1 p1 p3 c3 r2 w2 p2 c1 c2 check
+permutation w3 r1 p1 p3 c3 r2 w2 p2 c2 c1 check
+permutation w3 r1 p1 p3 c3 r2 w2 c1 p2 c2 check
+permutation w3 r1 p1 p3 c3 r2 c1 w2 p2 c2 check
+permutation w3 r1 p1 p3 c3 w2 r2 p2 c1 c2 check
+permutation w3 r1 p1 p3 c3 w2 r2 p2 c2 c1 check
+permutation w3 r1 p1 p3 c3 w2 r2 c1 p2 c2 check
+permutation w3 r1 p1 p3 c3 w2 c1 r2 p2 c2 check
+permutation w3 r1 p1 p3 c3 c1 r2 w2 p2 c2 check
+permutation w3 r1 p1 p3 c3 c1 w2 r2 p2 c2 check
+permutation w3 r1 p3 r2 w2 p1 p2 c3 c1 c2 check
+permutation w3 r1 p3 r2 w2 p1 p2 c3 c2 c1 check
+permutation w3 r1 p3 r2 w2 p1 c3 p2 c1 c2 check
+permutation w3 r1 p3 r2 w2 p1 c3 p2 c2 c1 check
+permutation w3 r1 p3 r2 w2 p1 c3 c1 p2 c2 check
+permutation w3 r1 p3 r2 w2 p2 p1 c3 c1 c2 check
+permutation w3 r1 p3 r2 w2 p2 p1 c3 c2 c1 check
+permutation w3 r1 p3 r2 w2 p2 c3 p1 c1 c2 check
+permutation w3 r1 p3 r2 w2 p2 c3 p1 c2 c1 check
+permutation w3 r1 p3 r2 w2 p2 c3 c2 p1 c1 check
+permutation w3 r1 p3 r2 w2 c3 p1 p2 c1 c2 check
+permutation w3 r1 p3 r2 w2 c3 p1 p2 c2 c1 check
+permutation w3 r1 p3 r2 w2 c3 p1 c1 p2 c2 check
+permutation w3 r1 p3 r2 w2 c3 p2 p1 c1 c2 check
+permutation w3 r1 p3 r2 w2 c3 p2 p1 c2 c1 check
+permutation w3 r1 p3 r2 w2 c3 p2 c2 p1 c1 check
+permutation w3 r1 p3 r2 p1 w2 p2 c3 c1 c2 check
+permutation w3 r1 p3 r2 p1 w2 p2 c3 c2 c1 check
+permutation w3 r1 p3 r2 p1 w2 c3 p2 c1 c2 check
+permutation w3 r1 p3 r2 p1 w2 c3 p2 c2 c1 check
+permutation w3 r1 p3 r2 p1 w2 c3 c1 p2 c2 check
+permutation w3 r1 p3 r2 p1 c3 w2 p2 c1 c2 check
+permutation w3 r1 p3 r2 p1 c3 w2 p2 c2 c1 check
+permutation w3 r1 p3 r2 p1 c3 w2 c1 p2 c2 check
+permutation w3 r1 p3 r2 p1 c3 c1 w2 p2 c2 check
+permutation w3 r1 p3 r2 c3 w2 p1 p2 c1 c2 check
+permutation w3 r1 p3 r2 c3 w2 p1 p2 c2 c1 check
+permutation w3 r1 p3 r2 c3 w2 p1 c1 p2 c2 check
+permutation w3 r1 p3 r2 c3 w2 p2 p1 c1 c2 check
+permutation w3 r1 p3 r2 c3 w2 p2 p1 c2 c1 check
+permutation w3 r1 p3 r2 c3 w2 p2 c2 p1 c1 check
+permutation w3 r1 p3 r2 c3 p1 w2 p2 c1 c2 check
+permutation w3 r1 p3 r2 c3 p1 w2 p2 c2 c1 check
+permutation w3 r1 p3 r2 c3 p1 w2 c1 p2 c2 check
+permutation w3 r1 p3 r2 c3 p1 c1 w2 p2 c2 check
+permutation w3 r1 p3 w2 r2 p1 p2 c3 c1 c2 check
+permutation w3 r1 p3 w2 r2 p1 p2 c3 c2 c1 check
+permutation w3 r1 p3 w2 r2 p1 c3 p2 c1 c2 check
+permutation w3 r1 p3 w2 r2 p1 c3 p2 c2 c1 check
+permutation w3 r1 p3 w2 r2 p1 c3 c1 p2 c2 check
+permutation w3 r1 p3 w2 r2 p2 p1 c3 c1 c2 check
+permutation w3 r1 p3 w2 r2 p2 p1 c3 c2 c1 check
+permutation w3 r1 p3 w2 r2 p2 c3 p1 c1 c2 check
+permutation w3 r1 p3 w2 r2 p2 c3 p1 c2 c1 check
+permutation w3 r1 p3 w2 r2 p2 c3 c2 p1 c1 check
+permutation w3 r1 p3 w2 r2 c3 p1 p2 c1 c2 check
+permutation w3 r1 p3 w2 r2 c3 p1 p2 c2 c1 check
+permutation w3 r1 p3 w2 r2 c3 p1 c1 p2 c2 check
+permutation w3 r1 p3 w2 r2 c3 p2 p1 c1 c2 check
+permutation w3 r1 p3 w2 r2 c3 p2 p1 c2 c1 check
+permutation w3 r1 p3 w2 r2 c3 p2 c2 p1 c1 check
+permutation w3 r1 p3 w2 p1 r2 p2 c3 c1 c2 check
+permutation w3 r1 p3 w2 p1 r2 p2 c3 c2 c1 check
+permutation w3 r1 p3 w2 p1 r2 c3 p2 c1 c2 check
+permutation w3 r1 p3 w2 p1 r2 c3 p2 c2 c1 check
+permutation w3 r1 p3 w2 p1 r2 c3 c1 p2 c2 check
+permutation w3 r1 p3 w2 p1 c3 r2 p2 c1 c2 check
+permutation w3 r1 p3 w2 p1 c3 r2 p2 c2 c1 check
+permutation w3 r1 p3 w2 p1 c3 r2 c1 p2 c2 check
+permutation w3 r1 p3 w2 p1 c3 c1 r2 p2 c2 check
+permutation w3 r1 p3 w2 c3 r2 p1 p2 c1 c2 check
+permutation w3 r1 p3 w2 c3 r2 p1 p2 c2 c1 check
+permutation w3 r1 p3 w2 c3 r2 p1 c1 p2 c2 check
+permutation w3 r1 p3 w2 c3 r2 p2 p1 c1 c2 check
+permutation w3 r1 p3 w2 c3 r2 p2 p1 c2 c1 check
+permutation w3 r1 p3 w2 c3 r2 p2 c2 p1 c1 check
+permutation w3 r1 p3 w2 c3 p1 r2 p2 c1 c2 check
+permutation w3 r1 p3 w2 c3 p1 r2 p2 c2 c1 check
+permutation w3 r1 p3 w2 c3 p1 r2 c1 p2 c2 check
+permutation w3 r1 p3 w2 c3 p1 c1 r2 p2 c2 check
+permutation w3 r1 p3 p1 r2 w2 p2 c3 c1 c2 check
+permutation w3 r1 p3 p1 r2 w2 p2 c3 c2 c1 check
+permutation w3 r1 p3 p1 r2 w2 c3 p2 c1 c2 check
+permutation w3 r1 p3 p1 r2 w2 c3 p2 c2 c1 check
+permutation w3 r1 p3 p1 r2 w2 c3 c1 p2 c2 check
+permutation w3 r1 p3 p1 r2 c3 w2 p2 c1 c2 check
+permutation w3 r1 p3 p1 r2 c3 w2 p2 c2 c1 check
+permutation w3 r1 p3 p1 r2 c3 w2 c1 p2 c2 check
+permutation w3 r1 p3 p1 r2 c3 c1 w2 p2 c2 check
+permutation w3 r1 p3 p1 w2 r2 p2 c3 c1 c2 check
+permutation w3 r1 p3 p1 w2 r2 p2 c3 c2 c1 check
+permutation w3 r1 p3 p1 w2 r2 c3 p2 c1 c2 check
+permutation w3 r1 p3 p1 w2 r2 c3 p2 c2 c1 check
+permutation w3 r1 p3 p1 w2 r2 c3 c1 p2 c2 check
+permutation w3 r1 p3 p1 w2 c3 r2 p2 c1 c2 check
+permutation w3 r1 p3 p1 w2 c3 r2 p2 c2 c1 check
+permutation w3 r1 p3 p1 w2 c3 r2 c1 p2 c2 check
+permutation w3 r1 p3 p1 w2 c3 c1 r2 p2 c2 check
+permutation w3 r1 p3 p1 c3 r2 w2 p2 c1 c2 check
+permutation w3 r1 p3 p1 c3 r2 w2 p2 c2 c1 check
+permutation w3 r1 p3 p1 c3 r2 w2 c1 p2 c2 check
+permutation w3 r1 p3 p1 c3 r2 c1 w2 p2 c2 check
+permutation w3 r1 p3 p1 c3 w2 r2 p2 c1 c2 check
+permutation w3 r1 p3 p1 c3 w2 r2 p2 c2 c1 check
+permutation w3 r1 p3 p1 c3 w2 r2 c1 p2 c2 check
+permutation w3 r1 p3 p1 c3 w2 c1 r2 p2 c2 check
+permutation w3 r1 p3 p1 c3 c1 r2 w2 p2 c2 check
+permutation w3 r1 p3 p1 c3 c1 w2 r2 p2 c2 check
+permutation w3 r1 p3 c3 r2 w2 p1 p2 c1 c2 check
+permutation w3 r1 p3 c3 r2 w2 p1 p2 c2 c1 check
+permutation w3 r1 p3 c3 r2 w2 p1 c1 p2 c2 check
+permutation w3 r1 p3 c3 r2 w2 p2 p1 c1 c2 check
+permutation w3 r1 p3 c3 r2 w2 p2 p1 c2 c1 check
+permutation w3 r1 p3 c3 r2 w2 p2 c2 p1 c1 check
+permutation w3 r1 p3 c3 r2 p1 w2 p2 c1 c2 check
+permutation w3 r1 p3 c3 r2 p1 w2 p2 c2 c1 check
+permutation w3 r1 p3 c3 r2 p1 w2 c1 p2 c2 check
+permutation w3 r1 p3 c3 r2 p1 c1 w2 p2 c2 check
+permutation w3 r1 p3 c3 w2 r2 p1 p2 c1 c2 check
+permutation w3 r1 p3 c3 w2 r2 p1 p2 c2 c1 check
+permutation w3 r1 p3 c3 w2 r2 p1 c1 p2 c2 check
+permutation w3 r1 p3 c3 w2 r2 p2 p1 c1 c2 check
+permutation w3 r1 p3 c3 w2 r2 p2 p1 c2 c1 check
+permutation w3 r1 p3 c3 w2 r2 p2 c2 p1 c1 check
+permutation w3 r1 p3 c3 w2 p1 r2 p2 c1 c2 check
+permutation w3 r1 p3 c3 w2 p1 r2 p2 c2 c1 check
+permutation w3 r1 p3 c3 w2 p1 r2 c1 p2 c2 check
+permutation w3 r1 p3 c3 w2 p1 c1 r2 p2 c2 check
+permutation w3 r1 p3 c3 p1 r2 w2 p2 c1 c2 check
+permutation w3 r1 p3 c3 p1 r2 w2 p2 c2 c1 check
+permutation w3 r1 p3 c3 p1 r2 w2 c1 p2 c2 check
+permutation w3 r1 p3 c3 p1 r2 c1 w2 p2 c2 check
+permutation w3 r1 p3 c3 p1 w2 r2 p2 c1 c2 check
+permutation w3 r1 p3 c3 p1 w2 r2 p2 c2 c1 check
+permutation w3 r1 p3 c3 p1 w2 r2 c1 p2 c2 check
+permutation w3 r1 p3 c3 p1 w2 c1 r2 p2 c2 check
+permutation w3 r1 p3 c3 p1 c1 r2 w2 p2 c2 check
+permutation w3 r1 p3 c3 p1 c1 w2 r2 p2 c2 check
+permutation w3 r2 r1 w2 p1 p2 p3 c3 c1 c2 check
+permutation w3 r2 r1 w2 p1 p2 p3 c3 c2 c1 check
+permutation w3 r2 r1 w2 p1 p3 p2 c3 c1 c2 check
+permutation w3 r2 r1 w2 p1 p3 p2 c3 c2 c1 check
+permutation w3 r2 r1 w2 p1 p3 c3 p2 c1 c2 check
+permutation w3 r2 r1 w2 p1 p3 c3 p2 c2 c1 check
+permutation w3 r2 r1 w2 p1 p3 c3 c1 p2 c2 check
+permutation w3 r2 r1 w2 p2 p1 p3 c3 c1 c2 check
+permutation w3 r2 r1 w2 p2 p1 p3 c3 c2 c1 check
+permutation w3 r2 r1 w2 p2 p3 p1 c3 c1 c2 check
+permutation w3 r2 r1 w2 p2 p3 p1 c3 c2 c1 check
+permutation w3 r2 r1 w2 p2 p3 c3 p1 c1 c2 check
+permutation w3 r2 r1 w2 p2 p3 c3 p1 c2 c1 check
+permutation w3 r2 r1 w2 p2 p3 c3 c2 p1 c1 check
+permutation w3 r2 r1 w2 p3 p1 p2 c3 c1 c2 check
+permutation w3 r2 r1 w2 p3 p1 p2 c3 c2 c1 check
+permutation w3 r2 r1 w2 p3 p1 c3 p2 c1 c2 check
+permutation w3 r2 r1 w2 p3 p1 c3 p2 c2 c1 check
+permutation w3 r2 r1 w2 p3 p1 c3 c1 p2 c2 check
+permutation w3 r2 r1 w2 p3 p2 p1 c3 c1 c2 check
+permutation w3 r2 r1 w2 p3 p2 p1 c3 c2 c1 check
+permutation w3 r2 r1 w2 p3 p2 c3 p1 c1 c2 check
+permutation w3 r2 r1 w2 p3 p2 c3 p1 c2 c1 check
+permutation w3 r2 r1 w2 p3 p2 c3 c2 p1 c1 check
+permutation w3 r2 r1 w2 p3 c3 p1 p2 c1 c2 check
+permutation w3 r2 r1 w2 p3 c3 p1 p2 c2 c1 check
+permutation w3 r2 r1 w2 p3 c3 p1 c1 p2 c2 check
+permutation w3 r2 r1 w2 p3 c3 p2 p1 c1 c2 check
+permutation w3 r2 r1 w2 p3 c3 p2 p1 c2 c1 check
+permutation w3 r2 r1 w2 p3 c3 p2 c2 p1 c1 check
+permutation w3 r2 r1 p1 w2 p2 p3 c3 c1 c2 check
+permutation w3 r2 r1 p1 w2 p2 p3 c3 c2 c1 check
+permutation w3 r2 r1 p1 w2 p3 p2 c3 c1 c2 check
+permutation w3 r2 r1 p1 w2 p3 p2 c3 c2 c1 check
+permutation w3 r2 r1 p1 w2 p3 c3 p2 c1 c2 check
+permutation w3 r2 r1 p1 w2 p3 c3 p2 c2 c1 check
+permutation w3 r2 r1 p1 w2 p3 c3 c1 p2 c2 check
+permutation w3 r2 r1 p1 p3 w2 p2 c3 c1 c2 check
+permutation w3 r2 r1 p1 p3 w2 p2 c3 c2 c1 check
+permutation w3 r2 r1 p1 p3 w2 c3 p2 c1 c2 check
+permutation w3 r2 r1 p1 p3 w2 c3 p2 c2 c1 check
+permutation w3 r2 r1 p1 p3 w2 c3 c1 p2 c2 check
+permutation w3 r2 r1 p1 p3 c3 w2 p2 c1 c2 check
+permutation w3 r2 r1 p1 p3 c3 w2 p2 c2 c1 check
+permutation w3 r2 r1 p1 p3 c3 w2 c1 p2 c2 check
+permutation w3 r2 r1 p1 p3 c3 c1 w2 p2 c2 check
+permutation w3 r2 r1 p3 w2 p1 p2 c3 c1 c2 check
+permutation w3 r2 r1 p3 w2 p1 p2 c3 c2 c1 check
+permutation w3 r2 r1 p3 w2 p1 c3 p2 c1 c2 check
+permutation w3 r2 r1 p3 w2 p1 c3 p2 c2 c1 check
+permutation w3 r2 r1 p3 w2 p1 c3 c1 p2 c2 check
+permutation w3 r2 r1 p3 w2 p2 p1 c3 c1 c2 check
+permutation w3 r2 r1 p3 w2 p2 p1 c3 c2 c1 check
+permutation w3 r2 r1 p3 w2 p2 c3 p1 c1 c2 check
+permutation w3 r2 r1 p3 w2 p2 c3 p1 c2 c1 check
+permutation w3 r2 r1 p3 w2 p2 c3 c2 p1 c1 check
+permutation w3 r2 r1 p3 w2 c3 p1 p2 c1 c2 check
+permutation w3 r2 r1 p3 w2 c3 p1 p2 c2 c1 check
+permutation w3 r2 r1 p3 w2 c3 p1 c1 p2 c2 check
+permutation w3 r2 r1 p3 w2 c3 p2 p1 c1 c2 check
+permutation w3 r2 r1 p3 w2 c3 p2 p1 c2 c1 check
+permutation w3 r2 r1 p3 w2 c3 p2 c2 p1 c1 check
+permutation w3 r2 r1 p3 p1 w2 p2 c3 c1 c2 check
+permutation w3 r2 r1 p3 p1 w2 p2 c3 c2 c1 check
+permutation w3 r2 r1 p3 p1 w2 c3 p2 c1 c2 check
+permutation w3 r2 r1 p3 p1 w2 c3 p2 c2 c1 check
+permutation w3 r2 r1 p3 p1 w2 c3 c1 p2 c2 check
+permutation w3 r2 r1 p3 p1 c3 w2 p2 c1 c2 check
+permutation w3 r2 r1 p3 p1 c3 w2 p2 c2 c1 check
+permutation w3 r2 r1 p3 p1 c3 w2 c1 p2 c2 check
+permutation w3 r2 r1 p3 p1 c3 c1 w2 p2 c2 check
+permutation w3 r2 r1 p3 c3 w2 p1 p2 c1 c2 check
+permutation w3 r2 r1 p3 c3 w2 p1 p2 c2 c1 check
+permutation w3 r2 r1 p3 c3 w2 p1 c1 p2 c2 check
+permutation w3 r2 r1 p3 c3 w2 p2 p1 c1 c2 check
+permutation w3 r2 r1 p3 c3 w2 p2 p1 c2 c1 check
+permutation w3 r2 r1 p3 c3 w2 p2 c2 p1 c1 check
+permutation w3 r2 r1 p3 c3 p1 w2 p2 c1 c2 check
+permutation w3 r2 r1 p3 c3 p1 w2 p2 c2 c1 check
+permutation w3 r2 r1 p3 c3 p1 w2 c1 p2 c2 check
+permutation w3 r2 r1 p3 c3 p1 c1 w2 p2 c2 check
+permutation w3 r2 p3 r1 w2 p1 p2 c3 c1 c2 check
+permutation w3 r2 p3 r1 w2 p1 p2 c3 c2 c1 check
+permutation w3 r2 p3 r1 w2 p1 c3 p2 c1 c2 check
+permutation w3 r2 p3 r1 w2 p1 c3 p2 c2 c1 check
+permutation w3 r2 p3 r1 w2 p1 c3 c1 p2 c2 check
+permutation w3 r2 p3 r1 w2 p2 p1 c3 c1 c2 check
+permutation w3 r2 p3 r1 w2 p2 p1 c3 c2 c1 check
+permutation w3 r2 p3 r1 w2 p2 c3 p1 c1 c2 check
+permutation w3 r2 p3 r1 w2 p2 c3 p1 c2 c1 check
+permutation w3 r2 p3 r1 w2 p2 c3 c2 p1 c1 check
+permutation w3 r2 p3 r1 w2 c3 p1 p2 c1 c2 check
+permutation w3 r2 p3 r1 w2 c3 p1 p2 c2 c1 check
+permutation w3 r2 p3 r1 w2 c3 p1 c1 p2 c2 check
+permutation w3 r2 p3 r1 w2 c3 p2 p1 c1 c2 check
+permutation w3 r2 p3 r1 w2 c3 p2 p1 c2 c1 check
+permutation w3 r2 p3 r1 w2 c3 p2 c2 p1 c1 check
+permutation w3 r2 p3 r1 p1 w2 p2 c3 c1 c2 check
+permutation w3 r2 p3 r1 p1 w2 p2 c3 c2 c1 check
+permutation w3 r2 p3 r1 p1 w2 c3 p2 c1 c2 check
+permutation w3 r2 p3 r1 p1 w2 c3 p2 c2 c1 check
+permutation w3 r2 p3 r1 p1 w2 c3 c1 p2 c2 check
+permutation w3 r2 p3 r1 p1 c3 w2 p2 c1 c2 check
+permutation w3 r2 p3 r1 p1 c3 w2 p2 c2 c1 check
+permutation w3 r2 p3 r1 p1 c3 w2 c1 p2 c2 check
+permutation w3 r2 p3 r1 p1 c3 c1 w2 p2 c2 check
+permutation w3 r2 p3 r1 c3 w2 p1 p2 c1 c2 check
+permutation w3 r2 p3 r1 c3 w2 p1 p2 c2 c1 check
+permutation w3 r2 p3 r1 c3 w2 p1 c1 p2 c2 check
+permutation w3 r2 p3 r1 c3 w2 p2 p1 c1 c2 check
+permutation w3 r2 p3 r1 c3 w2 p2 p1 c2 c1 check
+permutation w3 r2 p3 r1 c3 w2 p2 c2 p1 c1 check
+permutation w3 r2 p3 r1 c3 p1 w2 p2 c1 c2 check
+permutation w3 r2 p3 r1 c3 p1 w2 p2 c2 c1 check
+permutation w3 r2 p3 r1 c3 p1 w2 c1 p2 c2 check
+permutation w3 r2 p3 r1 c3 p1 c1 w2 p2 c2 check
+permutation w3 r2 p3 c3 r1 w2 p1 p2 c1 c2 check
+permutation w3 r2 p3 c3 r1 w2 p1 p2 c2 c1 check
+permutation w3 r2 p3 c3 r1 w2 p1 c1 p2 c2 check
+permutation w3 r2 p3 c3 r1 w2 p2 p1 c1 c2 check
+permutation w3 r2 p3 c3 r1 w2 p2 p1 c2 c1 check
+permutation w3 r2 p3 c3 r1 w2 p2 c2 p1 c1 check
+permutation w3 r2 p3 c3 r1 p1 w2 p2 c1 c2 check
+permutation w3 r2 p3 c3 r1 p1 w2 p2 c2 c1 check
+permutation w3 r2 p3 c3 r1 p1 w2 c1 p2 c2 check
+permutation w3 r2 p3 c3 r1 p1 c1 w2 p2 c2 check
+permutation w3 p3 r1 r2 w2 p1 p2 c3 c1 c2 check
+permutation w3 p3 r1 r2 w2 p1 p2 c3 c2 c1 check
+permutation w3 p3 r1 r2 w2 p1 c3 p2 c1 c2 check
+permutation w3 p3 r1 r2 w2 p1 c3 p2 c2 c1 check
+permutation w3 p3 r1 r2 w2 p1 c3 c1 p2 c2 check
+permutation w3 p3 r1 r2 w2 p2 p1 c3 c1 c2 check
+permutation w3 p3 r1 r2 w2 p2 p1 c3 c2 c1 check
+permutation w3 p3 r1 r2 w2 p2 c3 p1 c1 c2 check
+permutation w3 p3 r1 r2 w2 p2 c3 p1 c2 c1 check
+permutation w3 p3 r1 r2 w2 p2 c3 c2 p1 c1 check
+permutation w3 p3 r1 r2 w2 c3 p1 p2 c1 c2 check
+permutation w3 p3 r1 r2 w2 c3 p1 p2 c2 c1 check
+permutation w3 p3 r1 r2 w2 c3 p1 c1 p2 c2 check
+permutation w3 p3 r1 r2 w2 c3 p2 p1 c1 c2 check
+permutation w3 p3 r1 r2 w2 c3 p2 p1 c2 c1 check
+permutation w3 p3 r1 r2 w2 c3 p2 c2 p1 c1 check
+permutation w3 p3 r1 r2 p1 w2 p2 c3 c1 c2 check
+permutation w3 p3 r1 r2 p1 w2 p2 c3 c2 c1 check
+permutation w3 p3 r1 r2 p1 w2 c3 p2 c1 c2 check
+permutation w3 p3 r1 r2 p1 w2 c3 p2 c2 c1 check
+permutation w3 p3 r1 r2 p1 w2 c3 c1 p2 c2 check
+permutation w3 p3 r1 r2 p1 c3 w2 p2 c1 c2 check
+permutation w3 p3 r1 r2 p1 c3 w2 p2 c2 c1 check
+permutation w3 p3 r1 r2 p1 c3 w2 c1 p2 c2 check
+permutation w3 p3 r1 r2 p1 c3 c1 w2 p2 c2 check
+permutation w3 p3 r1 r2 c3 w2 p1 p2 c1 c2 check
+permutation w3 p3 r1 r2 c3 w2 p1 p2 c2 c1 check
+permutation w3 p3 r1 r2 c3 w2 p1 c1 p2 c2 check
+permutation w3 p3 r1 r2 c3 w2 p2 p1 c1 c2 check
+permutation w3 p3 r1 r2 c3 w2 p2 p1 c2 c1 check
+permutation w3 p3 r1 r2 c3 w2 p2 c2 p1 c1 check
+permutation w3 p3 r1 r2 c3 p1 w2 p2 c1 c2 check
+permutation w3 p3 r1 r2 c3 p1 w2 p2 c2 c1 check
+permutation w3 p3 r1 r2 c3 p1 w2 c1 p2 c2 check
+permutation w3 p3 r1 r2 c3 p1 c1 w2 p2 c2 check
+permutation w3 p3 r1 w2 r2 p1 p2 c3 c1 c2 check
+permutation w3 p3 r1 w2 r2 p1 p2 c3 c2 c1 check
+permutation w3 p3 r1 w2 r2 p1 c3 p2 c1 c2 check
+permutation w3 p3 r1 w2 r2 p1 c3 p2 c2 c1 check
+permutation w3 p3 r1 w2 r2 p1 c3 c1 p2 c2 check
+permutation w3 p3 r1 w2 r2 p2 p1 c3 c1 c2 check
+permutation w3 p3 r1 w2 r2 p2 p1 c3 c2 c1 check
+permutation w3 p3 r1 w2 r2 p2 c3 p1 c1 c2 check
+permutation w3 p3 r1 w2 r2 p2 c3 p1 c2 c1 check
+permutation w3 p3 r1 w2 r2 p2 c3 c2 p1 c1 check
+permutation w3 p3 r1 w2 r2 c3 p1 p2 c1 c2 check
+permutation w3 p3 r1 w2 r2 c3 p1 p2 c2 c1 check
+permutation w3 p3 r1 w2 r2 c3 p1 c1 p2 c2 check
+permutation w3 p3 r1 w2 r2 c3 p2 p1 c1 c2 check
+permutation w3 p3 r1 w2 r2 c3 p2 p1 c2 c1 check
+permutation w3 p3 r1 w2 r2 c3 p2 c2 p1 c1 check
+permutation w3 p3 r1 w2 p1 r2 p2 c3 c1 c2 check
+permutation w3 p3 r1 w2 p1 r2 p2 c3 c2 c1 check
+permutation w3 p3 r1 w2 p1 r2 c3 p2 c1 c2 check
+permutation w3 p3 r1 w2 p1 r2 c3 p2 c2 c1 check
+permutation w3 p3 r1 w2 p1 r2 c3 c1 p2 c2 check
+permutation w3 p3 r1 w2 p1 c3 r2 p2 c1 c2 check
+permutation w3 p3 r1 w2 p1 c3 r2 p2 c2 c1 check
+permutation w3 p3 r1 w2 p1 c3 r2 c1 p2 c2 check
+permutation w3 p3 r1 w2 p1 c3 c1 r2 p2 c2 check
+permutation w3 p3 r1 w2 c3 r2 p1 p2 c1 c2 check
+permutation w3 p3 r1 w2 c3 r2 p1 p2 c2 c1 check
+permutation w3 p3 r1 w2 c3 r2 p1 c1 p2 c2 check
+permutation w3 p3 r1 w2 c3 r2 p2 p1 c1 c2 check
+permutation w3 p3 r1 w2 c3 r2 p2 p1 c2 c1 check
+permutation w3 p3 r1 w2 c3 r2 p2 c2 p1 c1 check
+permutation w3 p3 r1 w2 c3 p1 r2 p2 c1 c2 check
+permutation w3 p3 r1 w2 c3 p1 r2 p2 c2 c1 check
+permutation w3 p3 r1 w2 c3 p1 r2 c1 p2 c2 check
+permutation w3 p3 r1 w2 c3 p1 c1 r2 p2 c2 check
+permutation w3 p3 r1 p1 r2 w2 p2 c3 c1 c2 check
+permutation w3 p3 r1 p1 r2 w2 p2 c3 c2 c1 check
+permutation w3 p3 r1 p1 r2 w2 c3 p2 c1 c2 check
+permutation w3 p3 r1 p1 r2 w2 c3 p2 c2 c1 check
+permutation w3 p3 r1 p1 r2 w2 c3 c1 p2 c2 check
+permutation w3 p3 r1 p1 r2 c3 w2 p2 c1 c2 check
+permutation w3 p3 r1 p1 r2 c3 w2 p2 c2 c1 check
+permutation w3 p3 r1 p1 r2 c3 w2 c1 p2 c2 check
+permutation w3 p3 r1 p1 r2 c3 c1 w2 p2 c2 check
+permutation w3 p3 r1 p1 w2 r2 p2 c3 c1 c2 check
+permutation w3 p3 r1 p1 w2 r2 p2 c3 c2 c1 check
+permutation w3 p3 r1 p1 w2 r2 c3 p2 c1 c2 check
+permutation w3 p3 r1 p1 w2 r2 c3 p2 c2 c1 check
+permutation w3 p3 r1 p1 w2 r2 c3 c1 p2 c2 check
+permutation w3 p3 r1 p1 w2 c3 r2 p2 c1 c2 check
+permutation w3 p3 r1 p1 w2 c3 r2 p2 c2 c1 check
+permutation w3 p3 r1 p1 w2 c3 r2 c1 p2 c2 check
+permutation w3 p3 r1 p1 w2 c3 c1 r2 p2 c2 check
+permutation w3 p3 r1 p1 c3 r2 w2 p2 c1 c2 check
+permutation w3 p3 r1 p1 c3 r2 w2 p2 c2 c1 check
+permutation w3 p3 r1 p1 c3 r2 w2 c1 p2 c2 check
+permutation w3 p3 r1 p1 c3 r2 c1 w2 p2 c2 check
+permutation w3 p3 r1 p1 c3 w2 r2 p2 c1 c2 check
+permutation w3 p3 r1 p1 c3 w2 r2 p2 c2 c1 check
+permutation w3 p3 r1 p1 c3 w2 r2 c1 p2 c2 check
+permutation w3 p3 r1 p1 c3 w2 c1 r2 p2 c2 check
+permutation w3 p3 r1 p1 c3 c1 r2 w2 p2 c2 check
+permutation w3 p3 r1 p1 c3 c1 w2 r2 p2 c2 check
+permutation w3 p3 r1 c3 r2 w2 p1 p2 c1 c2 check
+permutation w3 p3 r1 c3 r2 w2 p1 p2 c2 c1 check
+permutation w3 p3 r1 c3 r2 w2 p1 c1 p2 c2 check
+permutation w3 p3 r1 c3 r2 w2 p2 p1 c1 c2 check
+permutation w3 p3 r1 c3 r2 w2 p2 p1 c2 c1 check
+permutation w3 p3 r1 c3 r2 w2 p2 c2 p1 c1 check
+permutation w3 p3 r1 c3 r2 p1 w2 p2 c1 c2 check
+permutation w3 p3 r1 c3 r2 p1 w2 p2 c2 c1 check
+permutation w3 p3 r1 c3 r2 p1 w2 c1 p2 c2 check
+permutation w3 p3 r1 c3 r2 p1 c1 w2 p2 c2 check
+permutation w3 p3 r1 c3 w2 r2 p1 p2 c1 c2 check
+permutation w3 p3 r1 c3 w2 r2 p1 p2 c2 c1 check
+permutation w3 p3 r1 c3 w2 r2 p1 c1 p2 c2 check
+permutation w3 p3 r1 c3 w2 r2 p2 p1 c1 c2 check
+permutation w3 p3 r1 c3 w2 r2 p2 p1 c2 c1 check
+permutation w3 p3 r1 c3 w2 r2 p2 c2 p1 c1 check
+permutation w3 p3 r1 c3 w2 p1 r2 p2 c1 c2 check
+permutation w3 p3 r1 c3 w2 p1 r2 p2 c2 c1 check
+permutation w3 p3 r1 c3 w2 p1 r2 c1 p2 c2 check
+permutation w3 p3 r1 c3 w2 p1 c1 r2 p2 c2 check
+permutation w3 p3 r1 c3 p1 r2 w2 p2 c1 c2 check
+permutation w3 p3 r1 c3 p1 r2 w2 p2 c2 c1 check
+permutation w3 p3 r1 c3 p1 r2 w2 c1 p2 c2 check
+permutation w3 p3 r1 c3 p1 r2 c1 w2 p2 c2 check
+permutation w3 p3 r1 c3 p1 w2 r2 p2 c1 c2 check
+permutation w3 p3 r1 c3 p1 w2 r2 p2 c2 c1 check
+permutation w3 p3 r1 c3 p1 w2 r2 c1 p2 c2 check
+permutation w3 p3 r1 c3 p1 w2 c1 r2 p2 c2 check
+permutation w3 p3 r1 c3 p1 c1 r2 w2 p2 c2 check
+permutation w3 p3 r1 c3 p1 c1 w2 r2 p2 c2 check
+permutation w3 p3 r2 r1 w2 p1 p2 c3 c1 c2 check
+permutation w3 p3 r2 r1 w2 p1 p2 c3 c2 c1 check
+permutation w3 p3 r2 r1 w2 p1 c3 p2 c1 c2 check
+permutation w3 p3 r2 r1 w2 p1 c3 p2 c2 c1 check
+permutation w3 p3 r2 r1 w2 p1 c3 c1 p2 c2 check
+permutation w3 p3 r2 r1 w2 p2 p1 c3 c1 c2 check
+permutation w3 p3 r2 r1 w2 p2 p1 c3 c2 c1 check
+permutation w3 p3 r2 r1 w2 p2 c3 p1 c1 c2 check
+permutation w3 p3 r2 r1 w2 p2 c3 p1 c2 c1 check
+permutation w3 p3 r2 r1 w2 p2 c3 c2 p1 c1 check
+permutation w3 p3 r2 r1 w2 c3 p1 p2 c1 c2 check
+permutation w3 p3 r2 r1 w2 c3 p1 p2 c2 c1 check
+permutation w3 p3 r2 r1 w2 c3 p1 c1 p2 c2 check
+permutation w3 p3 r2 r1 w2 c3 p2 p1 c1 c2 check
+permutation w3 p3 r2 r1 w2 c3 p2 p1 c2 c1 check
+permutation w3 p3 r2 r1 w2 c3 p2 c2 p1 c1 check
+permutation w3 p3 r2 r1 p1 w2 p2 c3 c1 c2 check
+permutation w3 p3 r2 r1 p1 w2 p2 c3 c2 c1 check
+permutation w3 p3 r2 r1 p1 w2 c3 p2 c1 c2 check
+permutation w3 p3 r2 r1 p1 w2 c3 p2 c2 c1 check
+permutation w3 p3 r2 r1 p1 w2 c3 c1 p2 c2 check
+permutation w3 p3 r2 r1 p1 c3 w2 p2 c1 c2 check
+permutation w3 p3 r2 r1 p1 c3 w2 p2 c2 c1 check
+permutation w3 p3 r2 r1 p1 c3 w2 c1 p2 c2 check
+permutation w3 p3 r2 r1 p1 c3 c1 w2 p2 c2 check
+permutation w3 p3 r2 r1 c3 w2 p1 p2 c1 c2 check
+permutation w3 p3 r2 r1 c3 w2 p1 p2 c2 c1 check
+permutation w3 p3 r2 r1 c3 w2 p1 c1 p2 c2 check
+permutation w3 p3 r2 r1 c3 w2 p2 p1 c1 c2 check
+permutation w3 p3 r2 r1 c3 w2 p2 p1 c2 c1 check
+permutation w3 p3 r2 r1 c3 w2 p2 c2 p1 c1 check
+permutation w3 p3 r2 r1 c3 p1 w2 p2 c1 c2 check
+permutation w3 p3 r2 r1 c3 p1 w2 p2 c2 c1 check
+permutation w3 p3 r2 r1 c3 p1 w2 c1 p2 c2 check
+permutation w3 p3 r2 r1 c3 p1 c1 w2 p2 c2 check
+permutation w3 p3 r2 c3 r1 w2 p1 p2 c1 c2 check
+permutation w3 p3 r2 c3 r1 w2 p1 p2 c2 c1 check
+permutation w3 p3 r2 c3 r1 w2 p1 c1 p2 c2 check
+permutation w3 p3 r2 c3 r1 w2 p2 p1 c1 c2 check
+permutation w3 p3 r2 c3 r1 w2 p2 p1 c2 c1 check
+permutation w3 p3 r2 c3 r1 w2 p2 c2 p1 c1 check
+permutation w3 p3 r2 c3 r1 p1 w2 p2 c1 c2 check
+permutation w3 p3 r2 c3 r1 p1 w2 p2 c2 c1 check
+permutation w3 p3 r2 c3 r1 p1 w2 c1 p2 c2 check
+permutation w3 p3 r2 c3 r1 p1 c1 w2 p2 c2 check
+permutation w3 p3 c3 r1 r2 w2 p1 p2 c1 c2 check
+permutation w3 p3 c3 r1 r2 w2 p1 p2 c2 c1 check
+permutation w3 p3 c3 r1 r2 w2 p1 c1 p2 c2 check
+permutation w3 p3 c3 r1 r2 w2 p2 p1 c1 c2 check
+permutation w3 p3 c3 r1 r2 w2 p2 p1 c2 c1 check
+permutation w3 p3 c3 r1 r2 w2 p2 c2 p1 c1 check
+permutation w3 p3 c3 r1 r2 p1 w2 p2 c1 c2 check
+permutation w3 p3 c3 r1 r2 p1 w2 p2 c2 c1 check
+permutation w3 p3 c3 r1 r2 p1 w2 c1 p2 c2 check
+permutation w3 p3 c3 r1 r2 p1 c1 w2 p2 c2 check
+permutation w3 p3 c3 r1 w2 r2 p1 p2 c1 c2 check
+permutation w3 p3 c3 r1 w2 r2 p1 p2 c2 c1 check
+permutation w3 p3 c3 r1 w2 r2 p1 c1 p2 c2 check
+permutation w3 p3 c3 r1 w2 r2 p2 p1 c1 c2 check
+permutation w3 p3 c3 r1 w2 r2 p2 p1 c2 c1 check
+permutation w3 p3 c3 r1 w2 r2 p2 c2 p1 c1 check
+permutation w3 p3 c3 r1 w2 p1 r2 p2 c1 c2 check
+permutation w3 p3 c3 r1 w2 p1 r2 p2 c2 c1 check
+permutation w3 p3 c3 r1 w2 p1 r2 c1 p2 c2 check
+permutation w3 p3 c3 r1 w2 p1 c1 r2 p2 c2 check
+permutation w3 p3 c3 r1 p1 r2 w2 p2 c1 c2 check
+permutation w3 p3 c3 r1 p1 r2 w2 p2 c2 c1 check
+permutation w3 p3 c3 r1 p1 r2 w2 c1 p2 c2 check
+permutation w3 p3 c3 r1 p1 r2 c1 w2 p2 c2 check
+permutation w3 p3 c3 r1 p1 w2 r2 p2 c1 c2 check
+permutation w3 p3 c3 r1 p1 w2 r2 p2 c2 c1 check
+permutation w3 p3 c3 r1 p1 w2 r2 c1 p2 c2 check
+permutation w3 p3 c3 r1 p1 w2 c1 r2 p2 c2 check
+permutation w3 p3 c3 r1 p1 c1 r2 w2 p2 c2 check
+permutation w3 p3 c3 r1 p1 c1 w2 r2 p2 c2 check
+permutation w3 p3 c3 r2 r1 w2 p1 p2 c1 c2 check
+permutation w3 p3 c3 r2 r1 w2 p1 p2 c2 c1 check
+permutation w3 p3 c3 r2 r1 w2 p1 c1 p2 c2 check
+permutation w3 p3 c3 r2 r1 w2 p2 p1 c1 c2 check
+permutation w3 p3 c3 r2 r1 w2 p2 p1 c2 c1 check
+permutation w3 p3 c3 r2 r1 w2 p2 c2 p1 c1 check
+permutation w3 p3 c3 r2 r1 p1 w2 p2 c1 c2 check
+permutation w3 p3 c3 r2 r1 p1 w2 p2 c2 c1 check
+permutation w3 p3 c3 r2 r1 p1 w2 c1 p2 c2 check
+permutation w3 p3 c3 r2 r1 p1 c1 w2 p2 c2 check
diff --git a/src/test/isolation/specs/project-manager.spec b/src/test/isolation/specs/project-manager.spec
new file mode 100644
index 0000000..42e5fc5
--- /dev/null
+++ b/src/test/isolation/specs/project-manager.spec
@@ -0,0 +1,30 @@
+# Project Manager test
+#
+# Ensure that the person who is on the project as a manager
+# is flagged as a project manager in the person table.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE person (person_id int NOT NULL PRIMARY KEY, name text NOT NULL, is_project_manager bool NOT NULL);
+ INSERT INTO person VALUES (1, 'Robert Haas', true);
+ CREATE TABLE project (project_no int NOT NULL PRIMARY KEY, description text NOT NULL, project_manager int NOT NULL);
+}
+
+teardown
+{
+ DROP TABLE person, project;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rx1 { SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; }
+step wy1 { INSERT INTO project VALUES (101, 'Build Great Wall', 1); }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step ry2 { SELECT count(*) FROM project WHERE project_manager = 1; }
+step wx2 { UPDATE person SET is_project_manager = false WHERE person_id = 1; }
+step c2 { COMMIT; }
diff --git a/src/test/isolation/specs/propagate-lock-delete.spec b/src/test/isolation/specs/propagate-lock-delete.spec
new file mode 100644
index 0000000..641fb84
--- /dev/null
+++ b/src/test/isolation/specs/propagate-lock-delete.spec
@@ -0,0 +1,42 @@
+# When an update propagates a preexisting lock on the updated tuple, make sure
+# we don't ignore the lock in subsequent operations of the new version. (The
+# version with the aborted savepoint uses a slightly different code path).
+setup
+{
+ create table parent (i int, c char(3));
+ create unique index parent_idx on parent (i);
+ insert into parent values (1, 'AAA');
+ create table child (i int references parent(i));
+}
+
+teardown
+{
+ drop table child, parent;
+}
+
+session s1
+step s1b { BEGIN; }
+step s1l { INSERT INTO child VALUES (1); }
+step s1c { COMMIT; }
+
+session s2
+step s2b { BEGIN; }
+step s2l { INSERT INTO child VALUES (1); }
+step s2c { COMMIT; }
+
+session s3
+step s3b { BEGIN; }
+step s3u { UPDATE parent SET c=lower(c); } # no key update
+step s3u2 { UPDATE parent SET i = i; } # key update
+step s3svu { SAVEPOINT f; UPDATE parent SET c = 'bbb'; ROLLBACK TO f; }
+step s3d { DELETE FROM parent; }
+step s3c { COMMIT; }
+
+permutation s1b s1l s2b s2l s3b s3u s3d s1c s2c s3c
+permutation s1b s1l s2b s2l s3b s3u s3svu s3d s1c s2c s3c
+permutation s1b s1l s2b s2l s3b s3u2 s3d s1c s2c s3c
+permutation s1b s1l s2b s2l s3b s3u2 s3svu s3d s1c s2c s3c
+permutation s1b s1l s3b s3u s3d s1c s3c
+permutation s1b s1l s3b s3u s3svu s3d s1c s3c
+permutation s1b s1l s3b s3u2 s3d s1c s3c
+permutation s1b s1l s3b s3u2 s3svu s3d s1c s3c
diff --git a/src/test/isolation/specs/read-only-anomaly-2.spec b/src/test/isolation/specs/read-only-anomaly-2.spec
new file mode 100644
index 0000000..6b579a6
--- /dev/null
+++ b/src/test/isolation/specs/read-only-anomaly-2.spec
@@ -0,0 +1,42 @@
+# The example from the paper "A read-only transaction anomaly under snapshot
+# isolation"[1].
+#
+# Here we test that serializable snapshot isolation (SERIALIZABLE) doesn't
+# suffer from the anomaly, because s2 is aborted upon detection of a cycle.
+#
+# [1] http://www.cs.umb.edu/~poneil/ROAnom.pdf
+
+setup
+{
+ CREATE TABLE bank_account (id TEXT PRIMARY KEY, balance DECIMAL NOT NULL);
+ INSERT INTO bank_account (id, balance) VALUES ('X', 0), ('Y', 0);
+}
+
+teardown
+{
+ DROP TABLE bank_account;
+}
+
+session s1
+setup { BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; }
+step s1ry { SELECT balance FROM bank_account WHERE id = 'Y'; }
+step s1wy { UPDATE bank_account SET balance = 20 WHERE id = 'Y'; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; }
+step s2rx { SELECT balance FROM bank_account WHERE id = 'X'; }
+step s2ry { SELECT balance FROM bank_account WHERE id = 'Y'; }
+step s2wx { UPDATE bank_account SET balance = -11 WHERE id = 'X'; }
+step s2c { COMMIT; }
+
+session s3
+setup { BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; }
+step s3r { SELECT id, balance FROM bank_account WHERE id IN ('X', 'Y') ORDER BY id; }
+step s3c { COMMIT; }
+
+# without s3, s1 and s2 commit
+permutation s2rx s2ry s1ry s1wy s1c s2wx s2c s3c
+
+# once s3 observes the data committed by s1, a cycle is created and s2 aborts
+permutation s2rx s2ry s1ry s1wy s1c s3r s3c s2wx
diff --git a/src/test/isolation/specs/read-only-anomaly-3.spec b/src/test/isolation/specs/read-only-anomaly-3.spec
new file mode 100644
index 0000000..61d9c0b
--- /dev/null
+++ b/src/test/isolation/specs/read-only-anomaly-3.spec
@@ -0,0 +1,39 @@
+# The example from the paper "A read-only transaction anomaly under snapshot
+# isolation"[1].
+#
+# Here we test that serializable snapshot isolation can avoid the anomaly
+# without aborting any transactions, by instead causing s3 to be deferred
+# until a safe snapshot can be taken.
+#
+# [1] http://www.cs.umb.edu/~poneil/ROAnom.pdf
+
+setup
+{
+ CREATE TABLE bank_account (id TEXT PRIMARY KEY, balance DECIMAL NOT NULL);
+ INSERT INTO bank_account (id, balance) VALUES ('X', 0), ('Y', 0);
+}
+
+teardown
+{
+ DROP TABLE bank_account;
+}
+
+session s1
+setup { BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; }
+step s1ry { SELECT balance FROM bank_account WHERE id = 'Y'; }
+step s1wy { UPDATE bank_account SET balance = 20 WHERE id = 'Y'; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; }
+step s2rx { SELECT balance FROM bank_account WHERE id = 'X'; }
+step s2ry { SELECT balance FROM bank_account WHERE id = 'Y'; }
+step s2wx { UPDATE bank_account SET balance = -11 WHERE id = 'X'; }
+step s2c { COMMIT; }
+
+session s3
+setup { BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE; }
+step s3r { SELECT id, balance FROM bank_account WHERE id IN ('X', 'Y') ORDER BY id; }
+step s3c { COMMIT; }
+
+permutation s2rx s2ry s1ry s1wy s1c s3r s2wx s2c s3c
diff --git a/src/test/isolation/specs/read-only-anomaly.spec b/src/test/isolation/specs/read-only-anomaly.spec
new file mode 100644
index 0000000..8ff1af5
--- /dev/null
+++ b/src/test/isolation/specs/read-only-anomaly.spec
@@ -0,0 +1,38 @@
+# The example from the paper "A read-only transaction anomaly under snapshot
+# isolation"[1].
+#
+# Here we use snapshot isolation (REPEATABLE READ), so that s3 sees a state of
+# afairs that is not consistent with any serial ordering of s1 and s2.
+#
+# [1] http://www.cs.umb.edu/~poneil/ROAnom.pdf
+
+setup
+{
+ CREATE TABLE bank_account (id TEXT PRIMARY KEY, balance DECIMAL NOT NULL);
+ INSERT INTO bank_account (id, balance) VALUES ('X', 0), ('Y', 0);
+}
+
+teardown
+{
+ DROP TABLE bank_account;
+}
+
+session s1
+setup { BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; }
+step s1ry { SELECT balance FROM bank_account WHERE id = 'Y'; }
+step s1wy { UPDATE bank_account SET balance = 20 WHERE id = 'Y'; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; }
+step s2rx { SELECT balance FROM bank_account WHERE id = 'X'; }
+step s2ry { SELECT balance FROM bank_account WHERE id = 'Y'; }
+step s2wx { UPDATE bank_account SET balance = -11 WHERE id = 'X'; }
+step s2c { COMMIT; }
+
+session s3
+setup { BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; }
+step s3r { SELECT id, balance FROM bank_account WHERE id IN ('X', 'Y') ORDER BY id; }
+step s3c { COMMIT; }
+
+permutation s2rx s2ry s1ry s1wy s1c s3r s2wx s2c s3c
diff --git a/src/test/isolation/specs/read-write-unique-2.spec b/src/test/isolation/specs/read-write-unique-2.spec
new file mode 100644
index 0000000..16c73e1
--- /dev/null
+++ b/src/test/isolation/specs/read-write-unique-2.spec
@@ -0,0 +1,36 @@
+# Read-write-unique test.
+
+setup
+{
+ CREATE TABLE test (i integer PRIMARY KEY);
+}
+
+teardown
+{
+ DROP TABLE test;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step r1 { SELECT * FROM test WHERE i = 42; }
+step w1 { INSERT INTO test VALUES (42); }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step r2 { SELECT * FROM test WHERE i = 42; }
+step w2 { INSERT INTO test VALUES (42); }
+step c2 { COMMIT; }
+
+# Two SSI transactions see that there is no row with value 42
+# in the table, then try to insert that value; T1 inserts,
+# and then T2 blocks waiting for T1 to commit. Finally,
+# T2 reports a serialization failure.
+
+permutation r1 r2 w1 w2 c1 c2
+
+# If the value is already visible before T2 begins, then a
+# regular unique constraint violation should still be raised
+# by T2.
+
+permutation r1 w1 c1 r2 w2 c2
diff --git a/src/test/isolation/specs/read-write-unique-3.spec b/src/test/isolation/specs/read-write-unique-3.spec
new file mode 100644
index 0000000..cba2c4c
--- /dev/null
+++ b/src/test/isolation/specs/read-write-unique-3.spec
@@ -0,0 +1,33 @@
+# Read-write-unique test.
+# From bug report 9301.
+
+setup
+{
+ CREATE TABLE test (
+ key integer UNIQUE,
+ val text
+ );
+
+ CREATE OR REPLACE FUNCTION insert_unique(k integer, v text) RETURNS void
+ LANGUAGE SQL AS $$
+ INSERT INTO test (key, val) SELECT k, v WHERE NOT EXISTS (SELECT key FROM test WHERE key = k);
+ $$;
+}
+
+teardown
+{
+ DROP FUNCTION insert_unique(integer, text);
+ DROP TABLE test;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rw1 { SELECT insert_unique(1, '1'); }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rw2 { SELECT insert_unique(1, '2'); }
+step c2 { COMMIT; }
+
+permutation rw1 rw2 c1 c2
diff --git a/src/test/isolation/specs/read-write-unique-4.spec b/src/test/isolation/specs/read-write-unique-4.spec
new file mode 100644
index 0000000..9002248
--- /dev/null
+++ b/src/test/isolation/specs/read-write-unique-4.spec
@@ -0,0 +1,48 @@
+# Read-write-unique test.
+# Implementing a gapless sequence of ID numbers for each year.
+
+setup
+{
+ CREATE TABLE invoice (
+ year int,
+ invoice_number int,
+ PRIMARY KEY (year, invoice_number)
+ );
+
+ INSERT INTO invoice VALUES (2016, 1), (2016, 2);
+}
+
+teardown
+{
+ DROP TABLE invoice;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step r1 { SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016; }
+step w1 { INSERT INTO invoice VALUES (2016, 3); }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step r2 { SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016; }
+step w2 { INSERT INTO invoice VALUES (2016, 3); }
+step c2 { COMMIT; }
+
+# if they both read first then there should be an SSI conflict
+permutation r1 r2 w1 w2 c1 c2
+
+# cases where one session doesn't explicitly read before writing:
+
+# if s2 doesn't explicitly read, then trying to insert the value
+# generates a unique constraint violation after s1 commits, as if s2
+# ran after s1
+permutation r1 w1 w2 c1 c2
+
+# if s1 doesn't explicitly read, but s2 does, then s1 inserts and
+# commits first, should s2 experience an SSI failure instead of a
+# unique constraint violation? there is no serial order of operations
+# (s1, s2) or (s2, s1) where s1 succeeds, and s2 doesn't see the row
+# in an explicit select but then fails to insert due to unique
+# constraint violation
+permutation r2 w1 w2 c1 c2
diff --git a/src/test/isolation/specs/read-write-unique.spec b/src/test/isolation/specs/read-write-unique.spec
new file mode 100644
index 0000000..3ce059f
--- /dev/null
+++ b/src/test/isolation/specs/read-write-unique.spec
@@ -0,0 +1,39 @@
+# Read-write-unique test.
+
+setup
+{
+ CREATE TABLE test (i integer PRIMARY KEY);
+}
+
+teardown
+{
+ DROP TABLE test;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step r1 { SELECT * FROM test; }
+step w1 { INSERT INTO test VALUES (42); }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step r2 { SELECT * FROM test; }
+step w2 { INSERT INTO test VALUES (42); }
+step c2 { COMMIT; }
+
+# Two SSI transactions see that there is no row with value 42
+# in the table, then try to insert that value; T1 inserts,
+# and then T2 blocks waiting for T1 to commit. Finally,
+# T2 reports a serialization failure.
+#
+# (In an earlier version of Postgres, T2 would report a unique
+# constraint violation).
+
+permutation r1 r2 w1 w2 c1 c2
+
+# If the value is already visible before T2 begins, then a
+# regular unique constraint violation should still be raised
+# by T2.
+
+permutation r1 w1 c1 r2 w2 c2
diff --git a/src/test/isolation/specs/receipt-report.spec b/src/test/isolation/specs/receipt-report.spec
new file mode 100644
index 0000000..85ac60f
--- /dev/null
+++ b/src/test/isolation/specs/receipt-report.spec
@@ -0,0 +1,47 @@
+# Daily Report of Receipts test.
+#
+# This test doesn't persist a bad state in the database; rather, it
+# provides a view of the data which is not consistent with any
+# order of execution of the serializable transactions. It
+# demonstrates a situation where the deposit date for receipts could
+# be changed and a report of the closed day's receipts subsequently
+# run which will miss a receipt from the date which has been closed.
+#
+# There are only six permutations which must cause a serialization failure.
+# Failure cases are where s1 overlaps both s2 and s3, but s2 commits before
+# s3 executes its first SELECT.
+#
+# As long as s3 is declared READ ONLY there should be no false positives.
+# If s3 were changed to READ WRITE, we would currently expect 42 false
+# positives. Further work dealing with de facto READ ONLY transactions
+# may be able to reduce or eliminate those false positives.
+
+setup
+{
+ CREATE TABLE ctl (k text NOT NULL PRIMARY KEY, deposit_date date NOT NULL);
+ INSERT INTO ctl VALUES ('receipt', DATE '2008-12-22');
+ CREATE TABLE receipt (receipt_no int NOT NULL PRIMARY KEY, deposit_date date NOT NULL, amount numeric(13,2));
+ INSERT INTO receipt VALUES (1, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 1.00);
+ INSERT INTO receipt VALUES (2, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 2.00);
+}
+
+teardown
+{
+ DROP TABLE ctl, receipt;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rxwy1 { INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step wx2 { UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; }
+step c2 { COMMIT; }
+
+session s3
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE, READ ONLY; }
+step rx3 { SELECT * FROM ctl WHERE k = 'receipt'; }
+step ry3 { SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; }
+step c3 { COMMIT; }
diff --git a/src/test/isolation/specs/referential-integrity.spec b/src/test/isolation/specs/referential-integrity.spec
new file mode 100644
index 0000000..ecaa9bb
--- /dev/null
+++ b/src/test/isolation/specs/referential-integrity.spec
@@ -0,0 +1,32 @@
+# Referential Integrity test
+#
+# The assumption here is that the application code issuing the SELECT
+# to test for the presence or absence of a related record would do the
+# right thing -- this script doesn't include that logic.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE a (i int PRIMARY KEY);
+ CREATE TABLE b (a_id int);
+ INSERT INTO a VALUES (1);
+}
+
+teardown
+{
+ DROP TABLE a, b;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rx1 { SELECT i FROM a WHERE i = 1; }
+step wy1 { INSERT INTO b VALUES (1); }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rx2 { SELECT i FROM a WHERE i = 1; }
+step ry2 { SELECT a_id FROM b WHERE a_id = 1; }
+step wx2 { DELETE FROM a WHERE i = 1; }
+step c2 { COMMIT; }
diff --git a/src/test/isolation/specs/reindex-concurrently-toast.spec b/src/test/isolation/specs/reindex-concurrently-toast.spec
new file mode 100644
index 0000000..8188792
--- /dev/null
+++ b/src/test/isolation/specs/reindex-concurrently-toast.spec
@@ -0,0 +1,119 @@
+# REINDEX CONCURRENTLY with toast relations
+#
+# Ensure that concurrent operations work correctly when a REINDEX is performed
+# concurrently on toast relations. Toast relation names are not deterministic,
+# so this abuses of allow_system_table_mods to change the names of toast
+# tables and its indexes so as they can be executed with REINDEX CONCURRENTLY,
+# which cannot be launched in a transaction context.
+
+# Create a table, with deterministic names for its toast relation and indexes.
+# Fortunately ALTER TABLE is transactional, making the renaming of toast
+# relations possible with allow_system_table_mods.
+setup
+{
+ CREATE TABLE reind_con_wide(id int primary key, data text);
+ INSERT INTO reind_con_wide
+ SELECT 1, repeat('1', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+ INSERT INTO reind_con_wide
+ SELECT 2, repeat('2', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
+ SET allow_system_table_mods TO true;
+ DO $$DECLARE r record;
+ BEGIN
+ SELECT INTO r reltoastrelid::regclass::text AS table_name FROM pg_class
+ WHERE oid = 'reind_con_wide'::regclass;
+ EXECUTE 'ALTER TABLE ' || r.table_name || ' RENAME TO reind_con_toast;';
+ SELECT INTO r indexrelid::regclass::text AS index_name FROM pg_index
+ WHERE indrelid = (SELECT oid FROM pg_class where relname = 'reind_con_toast');
+ EXECUTE 'ALTER INDEX ' || r.index_name || ' RENAME TO reind_con_toast_idx;';
+ END$$;
+}
+
+teardown
+{
+ DROP TABLE IF EXISTS reind_con_wide;
+}
+
+session s1
+setup { BEGIN; }
+step lrex1 { lock TABLE reind_con_wide in ROW EXCLUSIVE MODE; }
+step lsha1 { lock TABLE reind_con_wide in SHARE MODE; }
+step lexc1 { lock TABLE reind_con_wide in EXCLUSIVE MODE; }
+step ins1 { INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i); }
+step upd1 { UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1; }
+step del1 { DELETE FROM reind_con_wide WHERE id = 2; }
+step dro1 { DROP TABLE reind_con_wide; }
+step end1 { COMMIT; }
+step rol1 { ROLLBACK; }
+
+session s2
+step retab2 { REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; }
+step reind2 { REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; }
+step sel2 { SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id; }
+
+# Transaction commit with ROW EXCLUSIVE MODE
+permutation lrex1 ins1 retab2 end1 sel2
+permutation lrex1 ins1 reind2 end1 sel2
+permutation lrex1 upd1 retab2 end1 sel2
+permutation lrex1 upd1 reind2 end1 sel2
+permutation lrex1 del1 retab2 end1 sel2
+permutation lrex1 del1 reind2 end1 sel2
+permutation lrex1 dro1 retab2 end1 sel2
+permutation lrex1 dro1 reind2 end1 sel2
+permutation lrex1 retab2 dro1 end1 sel2
+permutation lrex1 reind2 dro1 end1 sel2
+# Transaction commit with SHARE MODE
+permutation lsha1 ins1 retab2 end1 sel2
+permutation lsha1 ins1 reind2 end1 sel2
+permutation lsha1 upd1 retab2 end1 sel2
+permutation lsha1 upd1 reind2 end1 sel2
+permutation lsha1 del1 retab2 end1 sel2
+permutation lsha1 del1 reind2 end1 sel2
+permutation lsha1 dro1 retab2 end1 sel2
+permutation lsha1 dro1 reind2 end1 sel2
+permutation lsha1 retab2 dro1 end1 sel2
+permutation lsha1 reind2 dro1 end1 sel2
+# Transaction commit with EXCLUSIVE MODE
+permutation lexc1 ins1 retab2 end1 sel2
+permutation lexc1 ins1 reind2 end1 sel2
+permutation lexc1 upd1 retab2 end1 sel2
+permutation lexc1 upd1 reind2 end1 sel2
+permutation lexc1 del1 retab2 end1 sel2
+permutation lexc1 del1 reind2 end1 sel2
+permutation lexc1 dro1 retab2 end1 sel2
+permutation lexc1 dro1 reind2 end1 sel2
+permutation lexc1 retab2 dro1 end1 sel2
+permutation lexc1 reind2 dro1 end1 sel2
+
+# Transaction rollback with ROW EXCLUSIVE MODE
+permutation lrex1 ins1 retab2 rol1 sel2
+permutation lrex1 ins1 reind2 rol1 sel2
+permutation lrex1 upd1 retab2 rol1 sel2
+permutation lrex1 upd1 reind2 rol1 sel2
+permutation lrex1 del1 retab2 rol1 sel2
+permutation lrex1 del1 reind2 rol1 sel2
+permutation lrex1 dro1 retab2 rol1 sel2
+permutation lrex1 dro1 reind2 rol1 sel2
+permutation lrex1 retab2 dro1 rol1 sel2
+permutation lrex1 reind2 dro1 rol1 sel2
+# Transaction rollback with SHARE MODE
+permutation lsha1 ins1 retab2 rol1 sel2
+permutation lsha1 ins1 reind2 rol1 sel2
+permutation lsha1 upd1 retab2 rol1 sel2
+permutation lsha1 upd1 reind2 rol1 sel2
+permutation lsha1 del1 retab2 rol1 sel2
+permutation lsha1 del1 reind2 rol1 sel2
+permutation lsha1 dro1 retab2 rol1 sel2
+permutation lsha1 dro1 reind2 rol1 sel2
+permutation lsha1 retab2 dro1 rol1 sel2
+permutation lsha1 reind2 dro1 rol1 sel2
+# Transaction rollback with EXCLUSIVE MODE
+permutation lexc1 ins1 retab2 rol1 sel2
+permutation lexc1 ins1 reind2 rol1 sel2
+permutation lexc1 upd1 retab2 rol1 sel2
+permutation lexc1 upd1 reind2 rol1 sel2
+permutation lexc1 del1 retab2 rol1 sel2
+permutation lexc1 del1 reind2 rol1 sel2
+permutation lexc1 dro1 retab2 rol1 sel2
+permutation lexc1 dro1 reind2 rol1 sel2
+permutation lexc1 retab2 dro1 rol1 sel2
+permutation lexc1 reind2 dro1 rol1 sel2
diff --git a/src/test/isolation/specs/reindex-concurrently.spec b/src/test/isolation/specs/reindex-concurrently.spec
new file mode 100644
index 0000000..31844bd
--- /dev/null
+++ b/src/test/isolation/specs/reindex-concurrently.spec
@@ -0,0 +1,40 @@
+# REINDEX CONCURRENTLY
+#
+# Ensure that concurrent operations work correctly when a REINDEX is performed
+# concurrently.
+
+setup
+{
+ CREATE TABLE reind_con_tab(id serial primary key, data text);
+ INSERT INTO reind_con_tab(data) VALUES ('aa');
+ INSERT INTO reind_con_tab(data) VALUES ('aaa');
+ INSERT INTO reind_con_tab(data) VALUES ('aaaa');
+ INSERT INTO reind_con_tab(data) VALUES ('aaaaa');
+}
+
+teardown
+{
+ DROP TABLE reind_con_tab;
+}
+
+session s1
+setup { BEGIN; }
+step sel1 { SELECT data FROM reind_con_tab WHERE id = 3; }
+step end1 { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step upd2 { UPDATE reind_con_tab SET data = 'bbbb' WHERE id = 3; }
+step ins2 { INSERT INTO reind_con_tab(data) VALUES ('cccc'); }
+step del2 { DELETE FROM reind_con_tab WHERE data = 'cccc'; }
+step end2 { COMMIT; }
+
+session s3
+step reindex { REINDEX TABLE CONCURRENTLY reind_con_tab; }
+
+permutation reindex sel1 upd2 ins2 del2 end1 end2
+permutation sel1 reindex upd2 ins2 del2 end1 end2
+permutation sel1 upd2 reindex ins2 del2 end1 end2
+permutation sel1 upd2 ins2 reindex del2 end1 end2
+permutation sel1 upd2 ins2 del2 reindex end1 end2
+permutation sel1 upd2 ins2 del2 end1 reindex end2
diff --git a/src/test/isolation/specs/reindex-schema.spec b/src/test/isolation/specs/reindex-schema.spec
new file mode 100644
index 0000000..dee4ad7
--- /dev/null
+++ b/src/test/isolation/specs/reindex-schema.spec
@@ -0,0 +1,32 @@
+# REINDEX with schemas
+#
+# Check that concurrent drop of relations while doing a REINDEX
+# SCHEMA allows the command to work.
+
+setup
+{
+ CREATE SCHEMA reindex_schema;
+ CREATE TABLE reindex_schema.tab_locked (a int PRIMARY KEY);
+ CREATE TABLE reindex_schema.tab_dropped (a int PRIMARY KEY);
+}
+
+teardown
+{
+ DROP SCHEMA reindex_schema CASCADE;
+}
+
+session s1
+step begin1 { BEGIN; }
+step lock1 { LOCK reindex_schema.tab_locked IN SHARE UPDATE EXCLUSIVE MODE; }
+step end1 { COMMIT; }
+
+session s2
+step reindex2 { REINDEX SCHEMA reindex_schema; }
+step reindex_conc2 { REINDEX SCHEMA CONCURRENTLY reindex_schema; }
+
+session s3
+step drop3 { DROP TABLE reindex_schema.tab_dropped; }
+
+# The table can be dropped while reindex is waiting.
+permutation begin1 lock1 reindex2 drop3 end1
+permutation begin1 lock1 reindex_conc2 drop3 end1
diff --git a/src/test/isolation/specs/ri-trigger.spec b/src/test/isolation/specs/ri-trigger.spec
new file mode 100644
index 0000000..00fcdff
--- /dev/null
+++ b/src/test/isolation/specs/ri-trigger.spec
@@ -0,0 +1,53 @@
+# RI Trigger test
+#
+# Test trigger-based referential integrity enforcement.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE parent (parent_id SERIAL NOT NULL PRIMARY KEY);
+ CREATE TABLE child (child_id SERIAL NOT NULL PRIMARY KEY, parent_id INTEGER NOT NULL);
+ CREATE FUNCTION ri_parent() RETURNS TRIGGER LANGUAGE PLPGSQL AS $body$
+ BEGIN
+ PERFORM TRUE FROM child WHERE parent_id = OLD.parent_id;
+ IF FOUND THEN
+ RAISE SQLSTATE '23503' USING MESSAGE = 'child row exists';
+ END IF;
+ IF TG_OP = 'DELETE' THEN
+ RETURN OLD;
+ END IF;
+ RETURN NEW;
+ END;
+ $body$;
+ CREATE TRIGGER ri_parent BEFORE UPDATE OR DELETE ON parent FOR EACH ROW EXECUTE PROCEDURE ri_parent();
+ CREATE FUNCTION ri_child() RETURNS TRIGGER LANGUAGE PLPGSQL AS $body$
+ BEGIN
+ PERFORM TRUE FROM parent WHERE parent_id = NEW.parent_id;
+ IF NOT FOUND THEN
+ RAISE SQLSTATE '23503' USING MESSAGE = 'parent row missing';
+ END IF;
+ RETURN NEW;
+ END;
+ $body$;
+ CREATE TRIGGER ri_child BEFORE INSERT OR UPDATE ON child FOR EACH ROW EXECUTE PROCEDURE ri_child();
+ INSERT INTO parent VALUES(0);
+}
+
+teardown
+{
+ DROP TABLE parent, child;
+ DROP FUNCTION ri_parent();
+ DROP FUNCTION ri_child();
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step wxry1 { INSERT INTO child (parent_id) VALUES (0); }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step r2 { SELECT TRUE; }
+step wyrx2 { DELETE FROM parent WHERE parent_id = 0; }
+step c2 { COMMIT; }
diff --git a/src/test/isolation/specs/sequence-ddl.spec b/src/test/isolation/specs/sequence-ddl.spec
new file mode 100644
index 0000000..7ead8af
--- /dev/null
+++ b/src/test/isolation/specs/sequence-ddl.spec
@@ -0,0 +1,41 @@
+# Test sequence usage and concurrent sequence DDL
+
+setup
+{
+ CREATE SEQUENCE seq1;
+}
+
+teardown
+{
+ DROP SEQUENCE seq1;
+}
+
+session s1
+setup { BEGIN; }
+step s1alter { ALTER SEQUENCE seq1 MAXVALUE 10; }
+step s1alter2 { ALTER SEQUENCE seq1 MAXVALUE 20; }
+step s1restart { ALTER SEQUENCE seq1 RESTART WITH 5; }
+step s1commit { COMMIT; }
+
+session s2
+step s2begin { BEGIN; }
+step s2nv { SELECT nextval('seq1') FROM generate_series(1, 15); }
+step s2commit { COMMIT; }
+
+permutation s1alter s1commit s2nv
+
+# Prior to PG10, the s2nv step would see the uncommitted s1alter
+# change, but now it waits.
+permutation s1alter s2nv s1commit
+
+# Prior to PG10, the s2nv step would see the uncommitted s1restart
+# change, but now it waits.
+permutation s1restart s2nv s1commit
+
+# In contrast to ALTER setval() is non-transactional, so it doesn't
+# have to wait.
+permutation s1restart s2nv s1commit
+
+# nextval doesn't release lock until transaction end, so s1alter2 has
+# to wait for s2commit.
+permutation s2begin s2nv s1alter2 s2commit s1commit
diff --git a/src/test/isolation/specs/serializable-parallel-2.spec b/src/test/isolation/specs/serializable-parallel-2.spec
new file mode 100644
index 0000000..f3941f7
--- /dev/null
+++ b/src/test/isolation/specs/serializable-parallel-2.spec
@@ -0,0 +1,30 @@
+# Exercise the case where a read-only serializable transaction has
+# SXACT_FLAG_RO_SAFE set in a parallel query.
+
+setup
+{
+ CREATE TABLE foo AS SELECT generate_series(1, 10)::int a;
+ ALTER TABLE foo SET (parallel_workers = 2);
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+setup { BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; }
+step s1r { SELECT * FROM foo; }
+step s1c { COMMIT; }
+
+session s2
+setup {
+ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY;
+ SET parallel_setup_cost = 0;
+ SET parallel_tuple_cost = 0;
+ }
+step s2r1 { SELECT * FROM foo; }
+step s2r2 { SELECT * FROM foo; }
+step s2c { COMMIT; }
+
+permutation s1r s2r1 s1c s2r2 s2c
diff --git a/src/test/isolation/specs/serializable-parallel.spec b/src/test/isolation/specs/serializable-parallel.spec
new file mode 100644
index 0000000..508648e
--- /dev/null
+++ b/src/test/isolation/specs/serializable-parallel.spec
@@ -0,0 +1,47 @@
+# The example from the paper "A read-only transaction anomaly under snapshot
+# isolation"[1].
+#
+# Here we test that serializable snapshot isolation (SERIALIZABLE) doesn't
+# suffer from the anomaly, because s2 is aborted upon detection of a cycle.
+# In this case the read only query s3 happens to be running in a parallel
+# worker.
+#
+# [1] http://www.cs.umb.edu/~poneil/ROAnom.pdf
+
+setup
+{
+ CREATE TABLE bank_account (id TEXT PRIMARY KEY, balance DECIMAL NOT NULL);
+ INSERT INTO bank_account (id, balance) VALUES ('X', 0), ('Y', 0);
+}
+
+teardown
+{
+ DROP TABLE bank_account;
+}
+
+session s1
+setup { BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; }
+step s1ry { SELECT balance FROM bank_account WHERE id = 'Y'; }
+step s1wy { UPDATE bank_account SET balance = 20 WHERE id = 'Y'; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; }
+step s2rx { SELECT balance FROM bank_account WHERE id = 'X'; }
+step s2ry { SELECT balance FROM bank_account WHERE id = 'Y'; }
+step s2wx { UPDATE bank_account SET balance = -11 WHERE id = 'X'; }
+step s2c { COMMIT; }
+
+session s3
+setup {
+ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+ SET force_parallel_mode = on;
+ }
+step s3r { SELECT id, balance FROM bank_account WHERE id IN ('X', 'Y') ORDER BY id; }
+step s3c { COMMIT; }
+
+# without s3, s1 and s2 commit
+permutation s2rx s2ry s1ry s1wy s1c s2wx s2c s3c
+
+# once s3 observes the data committed by s1, a cycle is created and s2 aborts
+permutation s2rx s2ry s1ry s1wy s1c s3r s3c s2wx
diff --git a/src/test/isolation/specs/simple-write-skew.spec b/src/test/isolation/specs/simple-write-skew.spec
new file mode 100644
index 0000000..ecabbf1
--- /dev/null
+++ b/src/test/isolation/specs/simple-write-skew.spec
@@ -0,0 +1,30 @@
+# Write skew test.
+#
+# This test has two serializable transactions: one which updates all
+# 'apple' rows to 'pear' and one which updates all 'pear' rows to
+# 'apple'. If these were serialized (run one at a time) either
+# value could be present, but not both. One must be rolled back to
+# prevent the write skew anomaly.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE test (i int PRIMARY KEY, t text);
+ INSERT INTO test VALUES (5, 'apple'), (7, 'pear'), (11, 'banana');
+}
+
+teardown
+{
+ DROP TABLE test;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rwx1 { UPDATE test SET t = 'apple' WHERE t = 'pear'; }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rwx2 { UPDATE test SET t = 'pear' WHERE t = 'apple'}
+step c2 { COMMIT; }
diff --git a/src/test/isolation/specs/skip-locked-2.spec b/src/test/isolation/specs/skip-locked-2.spec
new file mode 100644
index 0000000..cfdaa93
--- /dev/null
+++ b/src/test/isolation/specs/skip-locked-2.spec
@@ -0,0 +1,41 @@
+# Test SKIP LOCKED with multixact locks.
+
+setup
+{
+ CREATE TABLE queue (
+ id int PRIMARY KEY,
+ data text NOT NULL,
+ status text NOT NULL
+ );
+ INSERT INTO queue VALUES (1, 'foo', 'NEW'), (2, 'bar', 'NEW');
+}
+
+teardown
+{
+ DROP TABLE queue;
+}
+
+session s1
+setup { BEGIN; }
+step s1a { SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1; }
+step s1b { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step s2a { SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1; }
+step s2b { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
+step s2c { COMMIT; }
+
+# s1 and s2 both get SHARE lock, creating a multixact lock, then s2
+# tries to update to UPDATE but skips the record because it can't
+# acquire a multixact lock
+permutation s1a s2a s2b s1b s2c
+
+# the same but with the SHARE locks acquired in a different order, so
+# s2 again skips because it can't acquired a multixact lock
+permutation s2a s1a s2b s1b s2c
+
+# s2 acquires SHARE then UPDATE, then s1 tries to acquire SHARE but
+# can't so skips the first record because it can't acquire a regular
+# lock
+permutation s2a s2b s1a s1b s2c
diff --git a/src/test/isolation/specs/skip-locked-3.spec b/src/test/isolation/specs/skip-locked-3.spec
new file mode 100644
index 0000000..7921425
--- /dev/null
+++ b/src/test/isolation/specs/skip-locked-3.spec
@@ -0,0 +1,36 @@
+# Test SKIP LOCKED with tuple locks.
+
+setup
+{
+ CREATE TABLE queue (
+ id int PRIMARY KEY,
+ data text NOT NULL,
+ status text NOT NULL
+ );
+ INSERT INTO queue VALUES (1, 'foo', 'NEW'), (2, 'bar', 'NEW');
+}
+
+teardown
+{
+ DROP TABLE queue;
+}
+
+session s1
+setup { BEGIN; }
+step s1a { SELECT * FROM queue ORDER BY id FOR UPDATE LIMIT 1; }
+step s1b { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step s2a { SELECT * FROM queue ORDER BY id FOR UPDATE LIMIT 1; }
+step s2b { COMMIT; }
+
+session s3
+setup { BEGIN; }
+step s3a { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
+step s3b { COMMIT; }
+
+# s3 skips to the second record because it can't obtain the tuple lock
+# (s2 holds the tuple lock because it is next in line to obtain the
+# row lock, and s1 holds the row lock)
+permutation s1a s2a s3a s1b s2b s3b
diff --git a/src/test/isolation/specs/skip-locked-4.spec b/src/test/isolation/specs/skip-locked-4.spec
new file mode 100644
index 0000000..02994a3
--- /dev/null
+++ b/src/test/isolation/specs/skip-locked-4.spec
@@ -0,0 +1,36 @@
+# Test SKIP LOCKED with an updated tuple chain.
+
+setup
+{
+ CREATE TABLE foo (
+ id int PRIMARY KEY,
+ data text NOT NULL
+ );
+ INSERT INTO foo VALUES (1, 'x'), (2, 'x');
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+session s1
+setup { BEGIN; }
+step s1a { SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED; }
+step s1b { COMMIT; }
+
+session s2
+step s2a { SELECT pg_advisory_lock(0); }
+step s2b { UPDATE foo SET data = data WHERE id = 1; }
+step s2c { BEGIN; }
+step s2d { UPDATE foo SET data = data WHERE id = 1; }
+step s2e { SELECT pg_advisory_unlock(0); }
+step s2f { COMMIT; }
+
+# s1 takes a snapshot but then waits on an advisory lock, then s2
+# updates the row in one transaction, then again in another without
+# committing, before allowing s1 to proceed to try to lock a row;
+# because it has a snapshot that sees the older version, we reach the
+# waiting code in EvalPlanQualFetch which skips rows when in SKIP
+# LOCKED mode, so s1 sees the second row
+permutation s2a s1a s2b s2c s2d s2e s1b s2f
diff --git a/src/test/isolation/specs/skip-locked.spec b/src/test/isolation/specs/skip-locked.spec
new file mode 100644
index 0000000..12168f8
--- /dev/null
+++ b/src/test/isolation/specs/skip-locked.spec
@@ -0,0 +1,28 @@
+# Test SKIP LOCKED when regular row locks can't be acquired.
+
+setup
+{
+ CREATE TABLE queue (
+ id int PRIMARY KEY,
+ data text NOT NULL,
+ status text NOT NULL
+ );
+ INSERT INTO queue VALUES (1, 'foo', 'NEW'), (2, 'bar', 'NEW');
+}
+
+teardown
+{
+ DROP TABLE queue;
+}
+
+session s1
+setup { BEGIN; }
+step s1a { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
+step s1b { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
+step s1c { COMMIT; }
+
+session s2
+setup { BEGIN; }
+step s2a { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
+step s2b { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
+step s2c { COMMIT; }
diff --git a/src/test/isolation/specs/temp-schema-cleanup.spec b/src/test/isolation/specs/temp-schema-cleanup.spec
new file mode 100644
index 0000000..a9417b7
--- /dev/null
+++ b/src/test/isolation/specs/temp-schema-cleanup.spec
@@ -0,0 +1,85 @@
+# Test cleanup of objects in temporary schema.
+
+setup {
+ CREATE TABLE s1_temp_schema(oid oid);
+ -- to help create a long function
+ CREATE FUNCTION exec(p_foo text) RETURNS void LANGUAGE plpgsql AS $$BEGIN EXECUTE p_foo; END;$$;
+}
+
+teardown {
+ DROP TABLE s1_temp_schema;
+ DROP FUNCTION exec(text);
+}
+
+session "s1"
+setup {
+ CREATE TEMPORARY TABLE just_to_create_temp_schema();
+ DROP TABLE just_to_create_temp_schema;
+ INSERT INTO s1_temp_schema SELECT pg_my_temp_schema();
+}
+
+step s1_advisory {
+ SELECT pg_advisory_lock('pg_namespace'::regclass::int8);
+}
+
+step s1_create_temp_objects {
+
+ -- create function large enough to be toasted, to ensure we correctly clean those up, a prior bug
+ -- https://postgr.es/m/CAOFAq3BU5Mf2TTvu8D9n_ZOoFAeQswuzk7yziAb7xuw_qyw5gw%40mail.gmail.com
+ SELECT exec(format($outer$
+ CREATE OR REPLACE FUNCTION pg_temp.long() RETURNS text LANGUAGE sql AS $body$ SELECT %L; $body$$outer$,
+ (SELECT string_agg(g.i::text||':'||random()::text, '|') FROM generate_series(1, 100) g(i))));
+
+ -- The above bug requirs function removal to happen after a catalog
+ -- invalidation. dependency.c sorts objects in descending oid order so
+ -- that newer objects are deleted before older objects, so create a
+ -- table after.
+ CREATE TEMPORARY TABLE invalidate_catalog_cache();
+
+ -- test non-temp function is dropped when depending on temp table
+ CREATE TEMPORARY TABLE just_give_me_a_type(id serial primary key);
+
+ CREATE FUNCTION uses_a_temp_type(just_give_me_a_type) RETURNS int LANGUAGE sql AS $$SELECT 1;$$;
+}
+
+step s1_discard_temp {
+ DISCARD TEMP;
+}
+
+step s1_exit {
+ SELECT pg_terminate_backend(pg_backend_pid());
+}
+
+
+session "s2"
+
+step s2_advisory {
+ SELECT pg_advisory_lock('pg_namespace'::regclass::int8);
+}
+
+step s2_check_schema {
+ SELECT oid::regclass FROM pg_class WHERE relnamespace = (SELECT oid FROM s1_temp_schema);
+ SELECT oid::regproc FROM pg_proc WHERE pronamespace = (SELECT oid FROM s1_temp_schema);
+ SELECT oid::regproc FROM pg_type WHERE typnamespace = (SELECT oid FROM s1_temp_schema);
+}
+
+
+# Test temporary object cleanup during DISCARD.
+permutation
+ s1_create_temp_objects
+ s1_discard_temp
+ s2_check_schema
+
+# Test temporary object cleanup during process exit.
+#
+# To check (in s2) if temporary objects (in s1) have properly been removed we
+# need to wait for s1 to finish cleaning up. Luckily session level advisory
+# locks are released only after temp table cleanup.
+permutation
+ s1_advisory
+ s2_advisory
+ s1_create_temp_objects
+ s1_exit
+ s2_check_schema
+
+# Can't run further tests here, because s1's connection is dead
diff --git a/src/test/isolation/specs/temporal-range-integrity.spec b/src/test/isolation/specs/temporal-range-integrity.spec
new file mode 100644
index 0000000..2d4c59c
--- /dev/null
+++ b/src/test/isolation/specs/temporal-range-integrity.spec
@@ -0,0 +1,38 @@
+# Temporal Range Integrity test
+#
+# Snapshot integrity fails with simple referential integrity tests,
+# but those don't make for good demonstrations because people just
+# say that foreign key definitions should be used instead. There
+# are many integrity tests which are conceptually very similar but
+# don't have built-in support which will fail when used in triggers.
+# This is intended to illustrate such cases. It is obviously very
+# hard to exercise all these permutations when the code is actually
+# in a trigger; this test pulls what would normally be inside of
+# triggers out to the top level to control the permutations.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+
+setup
+{
+ CREATE TABLE statute (statute_cite text NOT NULL, eff_date date NOT NULL, exp_date date, CONSTRAINT statute_pkey PRIMARY KEY (statute_cite, eff_date));
+ INSERT INTO statute VALUES ('123.45(1)a', DATE '2008-01-01', NULL);
+ CREATE TABLE offense (offense_no int NOT NULL, statute_cite text NOT NULL, offense_date date NOT NULL, CONSTRAINT offense_pkey PRIMARY KEY (offense_no));
+}
+
+teardown
+{
+ DROP TABLE statute, offense;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rx1 { SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); }
+step wy1 { INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step ry2 { SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; }
+step wx2 { DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; }
+step c2 { COMMIT; }
diff --git a/src/test/isolation/specs/timeouts.spec b/src/test/isolation/specs/timeouts.spec
new file mode 100644
index 0000000..c747b4a
--- /dev/null
+++ b/src/test/isolation/specs/timeouts.spec
@@ -0,0 +1,49 @@
+# Simple tests for statement_timeout and lock_timeout features
+
+setup
+{
+ CREATE TABLE accounts (accountid text PRIMARY KEY, balance numeric not null);
+ INSERT INTO accounts VALUES ('checking', 600), ('savings', 600);
+}
+
+teardown
+{
+ DROP TABLE accounts;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step rdtbl { SELECT * FROM accounts; }
+step wrtbl { UPDATE accounts SET balance = balance + 100; }
+teardown { ABORT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step sto { SET statement_timeout = '10ms'; }
+step lto { SET lock_timeout = '10ms'; }
+step lsto { SET lock_timeout = '10ms'; SET statement_timeout = '10s'; }
+step slto { SET lock_timeout = '10s'; SET statement_timeout = '10ms'; }
+step locktbl { LOCK TABLE accounts; }
+step update { DELETE FROM accounts WHERE accountid = 'checking'; }
+teardown { ABORT; }
+
+# It's possible that the isolation tester will not observe the final
+# steps as "waiting", thanks to the relatively short timeouts we use.
+# We can ensure consistent test output by marking those steps with (*).
+
+# statement timeout, table-level lock
+permutation rdtbl sto locktbl(*)
+# lock timeout, table-level lock
+permutation rdtbl lto locktbl(*)
+# lock timeout expires first, table-level lock
+permutation rdtbl lsto locktbl(*)
+# statement timeout expires first, table-level lock
+permutation rdtbl slto locktbl(*)
+# statement timeout, row-level lock
+permutation wrtbl sto update(*)
+# lock timeout, row-level lock
+permutation wrtbl lto update(*)
+# lock timeout expires first, row-level lock
+permutation wrtbl lsto update(*)
+# statement timeout expires first, row-level lock
+permutation wrtbl slto update(*)
diff --git a/src/test/isolation/specs/total-cash.spec b/src/test/isolation/specs/total-cash.spec
new file mode 100644
index 0000000..d98121a
--- /dev/null
+++ b/src/test/isolation/specs/total-cash.spec
@@ -0,0 +1,28 @@
+# Total Cash test
+#
+# Another famous test of snapshot isolation anomaly.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE accounts (accountid text NOT NULL PRIMARY KEY, balance numeric not null);
+ INSERT INTO accounts VALUES ('checking', 600),('savings',600);
+}
+
+teardown
+{
+ DROP TABLE accounts;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step wx1 { UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; }
+step rxy1 { SELECT SUM(balance) FROM accounts; }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step wy2 { UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; }
+step rxy2 { SELECT SUM(balance) FROM accounts; }
+step c2 { COMMIT; }
diff --git a/src/test/isolation/specs/truncate-conflict.spec b/src/test/isolation/specs/truncate-conflict.spec
new file mode 100644
index 0000000..0f77ff0
--- /dev/null
+++ b/src/test/isolation/specs/truncate-conflict.spec
@@ -0,0 +1,38 @@
+# Tests for locking conflicts with TRUNCATE commands.
+
+setup
+{
+ CREATE ROLE regress_truncate_conflict;
+ CREATE TABLE truncate_tab (a int);
+}
+
+teardown
+{
+ DROP TABLE truncate_tab;
+ DROP ROLE regress_truncate_conflict;
+}
+
+session s1
+step s1_begin { BEGIN; }
+step s1_tab_lookup { SELECT count(*) >= 0 FROM truncate_tab; }
+step s1_commit { COMMIT; }
+
+session s2
+step s2_grant { GRANT TRUNCATE ON truncate_tab TO regress_truncate_conflict; }
+step s2_auth { SET ROLE regress_truncate_conflict; }
+step s2_truncate { TRUNCATE truncate_tab; }
+step s2_reset { RESET ROLE; }
+
+# The role doesn't have privileges to truncate the table, so TRUNCATE should
+# immediately fail without waiting for a lock.
+permutation s1_begin s1_tab_lookup s2_auth s2_truncate s1_commit s2_reset
+permutation s1_begin s2_auth s2_truncate s1_tab_lookup s1_commit s2_reset
+permutation s1_begin s2_auth s1_tab_lookup s2_truncate s1_commit s2_reset
+permutation s2_auth s2_truncate s1_begin s1_tab_lookup s1_commit s2_reset
+
+# The role has privileges to truncate the table, TRUNCATE will block if
+# another session holds a lock on the table and succeed in all cases.
+permutation s1_begin s1_tab_lookup s2_grant s2_auth s2_truncate s1_commit s2_reset
+permutation s1_begin s2_grant s2_auth s2_truncate s1_tab_lookup s1_commit s2_reset
+permutation s1_begin s2_grant s2_auth s1_tab_lookup s2_truncate s1_commit s2_reset
+permutation s2_grant s2_auth s2_truncate s1_begin s1_tab_lookup s1_commit s2_reset
diff --git a/src/test/isolation/specs/tuplelock-conflict.spec b/src/test/isolation/specs/tuplelock-conflict.spec
new file mode 100644
index 0000000..8558230
--- /dev/null
+++ b/src/test/isolation/specs/tuplelock-conflict.spec
@@ -0,0 +1,63 @@
+# Here we verify that tuple lock levels conform to their documented
+# conflict tables.
+
+setup {
+ DROP TABLE IF EXISTS multixact_conflict;
+ CREATE TABLE multixact_conflict (a int primary key);
+ INSERT INTO multixact_conflict VALUES (1);
+}
+
+teardown {
+ DROP TABLE multixact_conflict;
+}
+
+session s1
+step s1_begin { BEGIN; }
+step s1_lcksvpt { SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT foo; }
+step s1_tuplock1 { SELECT * FROM multixact_conflict FOR KEY SHARE; }
+step s1_tuplock2 { SELECT * FROM multixact_conflict FOR SHARE; }
+step s1_tuplock3 { SELECT * FROM multixact_conflict FOR NO KEY UPDATE; }
+step s1_tuplock4 { SELECT * FROM multixact_conflict FOR UPDATE; }
+step s1_commit { COMMIT; }
+
+session s2
+step s2_tuplock1 { SELECT * FROM multixact_conflict FOR KEY SHARE; }
+step s2_tuplock2 { SELECT * FROM multixact_conflict FOR SHARE; }
+step s2_tuplock3 { SELECT * FROM multixact_conflict FOR NO KEY UPDATE; }
+step s2_tuplock4 { SELECT * FROM multixact_conflict FOR UPDATE; }
+
+# The version with savepoints test the multixact cases
+permutation s1_begin s1_lcksvpt s1_tuplock1 s2_tuplock1 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock1 s2_tuplock2 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock1 s2_tuplock3 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock1 s2_tuplock4 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock2 s2_tuplock1 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock2 s2_tuplock2 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock2 s2_tuplock3 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock2 s2_tuplock4 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock3 s2_tuplock1 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock3 s2_tuplock2 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock3 s2_tuplock3 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock3 s2_tuplock4 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock4 s2_tuplock1 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock4 s2_tuplock2 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock4 s2_tuplock3 s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock4 s2_tuplock4 s1_commit
+
+# no multixacts here
+permutation s1_begin s1_tuplock1 s2_tuplock1 s1_commit
+permutation s1_begin s1_tuplock1 s2_tuplock2 s1_commit
+permutation s1_begin s1_tuplock1 s2_tuplock3 s1_commit
+permutation s1_begin s1_tuplock1 s2_tuplock4 s1_commit
+permutation s1_begin s1_tuplock2 s2_tuplock1 s1_commit
+permutation s1_begin s1_tuplock2 s2_tuplock2 s1_commit
+permutation s1_begin s1_tuplock2 s2_tuplock3 s1_commit
+permutation s1_begin s1_tuplock2 s2_tuplock4 s1_commit
+permutation s1_begin s1_tuplock3 s2_tuplock1 s1_commit
+permutation s1_begin s1_tuplock3 s2_tuplock2 s1_commit
+permutation s1_begin s1_tuplock3 s2_tuplock3 s1_commit
+permutation s1_begin s1_tuplock3 s2_tuplock4 s1_commit
+permutation s1_begin s1_tuplock4 s2_tuplock1 s1_commit
+permutation s1_begin s1_tuplock4 s2_tuplock2 s1_commit
+permutation s1_begin s1_tuplock4 s2_tuplock3 s1_commit
+permutation s1_begin s1_tuplock4 s2_tuplock4 s1_commit
diff --git a/src/test/isolation/specs/tuplelock-partition.spec b/src/test/isolation/specs/tuplelock-partition.spec
new file mode 100644
index 0000000..c267b28
--- /dev/null
+++ b/src/test/isolation/specs/tuplelock-partition.spec
@@ -0,0 +1,32 @@
+# Test tuple locking on INSERT ON CONFLICT UPDATE on a partitioned table.
+
+setup
+{
+ DROP TABLE IF EXISTS parttab;
+ CREATE TABLE parttab (col1 text, key INTEGER PRIMARY KEY, col2 text) PARTITION BY LIST (key);
+ CREATE TABLE parttab1 (key INTEGER PRIMARY KEY, col1 text, col2 text);
+ CREATE TABLE parttab2 (key INTEGER PRIMARY KEY, col1 text, col2 text);
+ ALTER TABLE parttab ATTACH PARTITION parttab1 FOR VALUES IN (1);
+ ALTER TABLE parttab ATTACH PARTITION parttab2 FOR VALUES IN (2);
+ INSERT INTO parttab (key, col1, col2) VALUES (1, 'a', 'b');
+}
+
+teardown
+{
+ DROP TABLE parttab;
+}
+
+session s1
+step s1b { BEGIN; }
+step s1update_nokey { INSERT INTO parttab (key, col1, col2) VALUES (1, 'a', 'b') ON CONFLICT (key) DO UPDATE SET col1 = 'x', col2 = 'y'; }
+step s1update_key { INSERT INTO parttab (key, col1, col2) VALUES (1, 'a', 'b') ON CONFLICT (key) DO UPDATE SET key=1; }
+step s1c { COMMIT; }
+
+session s2
+step s2locktuple { SELECT * FROM parttab FOR KEY SHARE; }
+
+# INSERT ON CONFLICT UPDATE, performs an UPDATE on non-key columns
+permutation s1b s1update_nokey s2locktuple s1c
+
+# INSERT ON CONFLICT UPDATE, performs an UPDATE on key column
+permutation s1b s1update_key s2locktuple s1c
diff --git a/src/test/isolation/specs/tuplelock-update.spec b/src/test/isolation/specs/tuplelock-update.spec
new file mode 100644
index 0000000..4b940bc
--- /dev/null
+++ b/src/test/isolation/specs/tuplelock-update.spec
@@ -0,0 +1,37 @@
+setup {
+ DROP TABLE IF EXISTS pktab;
+ CREATE TABLE pktab (id int PRIMARY KEY, data SERIAL NOT NULL);
+ INSERT INTO pktab VALUES (1, DEFAULT);
+}
+
+teardown {
+ DROP TABLE pktab;
+}
+
+session s1
+step s1_advlock {
+ SELECT pg_advisory_lock(142857),
+ pg_advisory_lock(285714),
+ pg_advisory_lock(571428);
+ }
+step s1_chain { UPDATE pktab SET data = DEFAULT; }
+step s1_begin { BEGIN; }
+step s1_grablock { SELECT * FROM pktab FOR KEY SHARE; }
+step s1_advunlock1 { SELECT pg_advisory_unlock(142857); }
+step s1_advunlock2 { SELECT pg_advisory_unlock(285714); }
+step s1_advunlock3 { SELECT pg_advisory_unlock(571428); }
+step s1_commit { COMMIT; }
+
+session s2
+step s2_update { UPDATE pktab SET data = DEFAULT WHERE pg_advisory_lock_shared(142857) IS NOT NULL; }
+
+session s3
+step s3_update { UPDATE pktab SET data = DEFAULT WHERE pg_advisory_lock_shared(285714) IS NOT NULL; }
+
+session s4
+step s4_update { UPDATE pktab SET data = DEFAULT WHERE pg_advisory_lock_shared(571428) IS NOT NULL; }
+
+# We use blocker annotations on the s1_advunlockN steps so that we will not
+# move on to the next step until the other session's released step finishes.
+# This ensures stable ordering of the test output.
+permutation s1_advlock s2_update s3_update s4_update s1_chain s1_begin s1_grablock s1_advunlock1(s2_update) s1_advunlock2(s3_update) s1_advunlock3(s4_update) s1_commit
diff --git a/src/test/isolation/specs/tuplelock-upgrade-no-deadlock.spec b/src/test/isolation/specs/tuplelock-upgrade-no-deadlock.spec
new file mode 100644
index 0000000..6221a27
--- /dev/null
+++ b/src/test/isolation/specs/tuplelock-upgrade-no-deadlock.spec
@@ -0,0 +1,69 @@
+# This test checks that multiple sessions locking a single row in a table
+# do not deadlock each other when one of them upgrades its existing lock
+# while the others are waiting for it.
+
+setup
+{
+ drop table if exists tlu_job;
+ create table tlu_job (id integer primary key, name text);
+
+ insert into tlu_job values(1, 'a');
+}
+
+
+teardown
+{
+ drop table tlu_job;
+}
+
+session s0
+step s0_begin { begin; }
+step s0_keyshare { select id from tlu_job where id = 1 for key share;}
+step s0_rollback { rollback; }
+
+session s1
+setup { begin; }
+step s1_keyshare { select id from tlu_job where id = 1 for key share;}
+step s1_share { select id from tlu_job where id = 1 for share; }
+step s1_fornokeyupd { select id from tlu_job where id = 1 for no key update; }
+step s1_update { update tlu_job set name = 'b' where id = 1; }
+step s1_savept_e { savepoint s1_e; }
+step s1_savept_f { savepoint s1_f; }
+step s1_rollback_e { rollback to s1_e; }
+step s1_rollback_f { rollback to s1_f; }
+step s1_rollback { rollback; }
+step s1_commit { commit; }
+
+session s2
+setup { begin; }
+step s2_for_keyshare { select id from tlu_job where id = 1 for key share; }
+step s2_fornokeyupd { select id from tlu_job where id = 1 for no key update; }
+step s2_for_update { select id from tlu_job where id = 1 for update; }
+step s2_update { update tlu_job set name = 'b' where id = 1; }
+step s2_delete { delete from tlu_job where id = 1; }
+step s2_rollback { rollback; }
+
+session s3
+setup { begin; }
+step s3_keyshare { select id from tlu_job where id = 1 for key share; }
+step s3_share { select id from tlu_job where id = 1 for share; }
+step s3_for_update { select id from tlu_job where id = 1 for update; }
+step s3_update { update tlu_job set name = 'c' where id = 1; }
+step s3_delete { delete from tlu_job where id = 1; }
+step s3_rollback { rollback; }
+step s3_commit { commit; }
+
+# test that s2 will not deadlock with s3 when s1 is rolled back
+permutation s1_share s2_for_update s3_share s3_for_update s1_rollback s3_rollback s2_rollback
+# test that update does not cause deadlocks if it can proceed
+permutation s1_keyshare s2_for_update s3_keyshare s1_update s3_update s1_rollback s3_rollback s2_rollback
+permutation s1_keyshare s2_for_update s3_keyshare s1_update s3_update s1_commit s3_rollback s2_rollback
+# test that delete does not cause deadlocks if it can proceed
+permutation s1_keyshare s2_for_update s3_keyshare s3_delete s1_rollback s3_rollback s2_rollback
+permutation s1_keyshare s2_for_update s3_keyshare s3_delete s1_rollback s3_commit s2_rollback
+# test that sessions that don't upgrade locks acquire them in order
+permutation s1_share s2_for_update s3_for_update s1_rollback s2_rollback s3_rollback
+permutation s1_share s2_update s3_update s1_rollback s2_rollback s3_rollback
+permutation s1_share s2_delete s3_delete s1_rollback s2_rollback s3_rollback
+# test s2 retrying the overall tuple lock algorithm after initially avoiding deadlock
+permutation s1_keyshare s3_for_update s2_for_keyshare s1_savept_e s1_share s1_savept_f s1_fornokeyupd s2_fornokeyupd s0_begin s0_keyshare s1_rollback_f s0_keyshare s1_rollback_e s1_rollback s2_rollback s0_rollback s3_rollback
diff --git a/src/test/isolation/specs/two-ids.spec b/src/test/isolation/specs/two-ids.spec
new file mode 100644
index 0000000..fc0289f
--- /dev/null
+++ b/src/test/isolation/specs/two-ids.spec
@@ -0,0 +1,40 @@
+# Two IDs test
+#
+# Small, simple test showing read-only anomalies.
+#
+# There are only four permutations which must cause a serialization failure.
+# Required failure cases are where s2 overlaps both s1 and s3, but s1
+# commits before s3 executes its first SELECT.
+#
+# If s3 were declared READ ONLY there would be no false positives.
+# With s3 defaulting to READ WRITE, we currently expect 12 false
+# positives. Further work dealing with de facto READ ONLY transactions
+# may be able to reduce or eliminate those false positives.
+
+setup
+{
+ create table D1 (id int not null);
+ create table D2 (id int not null);
+ insert into D1 values (1);
+ insert into D2 values (1);
+}
+
+teardown
+{
+ DROP TABLE D1, D2;
+}
+
+session s1
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step wx1 { update D1 set id = id + 1; }
+step c1 { COMMIT; }
+
+session s2
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step rxwy2 { update D2 set id = (select id+1 from D1); }
+step c2 { COMMIT; }
+
+session s3
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step ry3 { select id from D2; }
+step c3 { COMMIT; }
diff --git a/src/test/isolation/specs/update-conflict-out.spec b/src/test/isolation/specs/update-conflict-out.spec
new file mode 100644
index 0000000..8aad6aa
--- /dev/null
+++ b/src/test/isolation/specs/update-conflict-out.spec
@@ -0,0 +1,54 @@
+# Test for interactions between SSI's "conflict out" handling for heapam and
+# concurrently updated tuple
+#
+# See bug report:
+# https://postgr.es/m/db7b729d-0226-d162-a126-8a8ab2dc4443%40jepsen.io
+
+setup
+{
+ CREATE TABLE txn0(id int4 PRIMARY KEY, val TEXT);
+ CREATE TABLE txn1(id int4 PRIMARY KEY, val TEXT);
+}
+
+teardown
+{
+ DROP TABLE txn0;
+ DROP TABLE txn1;
+}
+
+session foo
+setup { BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; }
+step foo_select { SELECT * FROM txn0 WHERE id = 42; }
+step foo_insert { INSERT INTO txn1 SELECT 7, 'foo_insert'; }
+step foo_commit { COMMIT; }
+
+session bar
+setup { BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; }
+step bar_select { SELECT * FROM txn1 WHERE id = 7; }
+step bar_insert { INSERT INTO txn0 SELECT 42, 'bar_insert'; }
+step bar_commit { COMMIT; }
+
+# This session creates the conditions that confused bar's "conflict out"
+# handling in old releases affected by bug:
+session trouble
+setup { BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; }
+step trouble_update { UPDATE txn1 SET val = 'add physical version for "bar_select"' WHERE id = 7; }
+step trouble_delete { DELETE FROM txn1 WHERE id = 7; }
+step trouble_abort { ABORT; }
+
+permutation foo_select
+ bar_insert
+ foo_insert foo_commit
+ trouble_update # Updates tuple...
+ bar_select # Should observe one distinct XID per version
+ bar_commit # "bar" should fail here at the latest
+ trouble_abort
+
+# Same as above, but "trouble" session DELETEs this time around
+permutation foo_select
+ bar_insert
+ foo_insert foo_commit
+ trouble_delete # Deletes tuple...
+ bar_select # Should observe foo's XID
+ bar_commit # "bar" should fail here at the latest
+ trouble_abort
diff --git a/src/test/isolation/specs/update-locked-tuple.spec b/src/test/isolation/specs/update-locked-tuple.spec
new file mode 100644
index 0000000..0dad792
--- /dev/null
+++ b/src/test/isolation/specs/update-locked-tuple.spec
@@ -0,0 +1,38 @@
+# Test updating a locked tuple. When the lock doesn't conflict with the
+# update, no blocking nor serializability problems should occur.
+
+setup
+{
+ DROP TABLE IF EXISTS users, orders;
+ CREATE TABLE users (id INTEGER PRIMARY KEY,
+ name varchar,
+ sometime timestamp);
+ CREATE TABLE orders (id INTEGER PRIMARY KEY,
+ name varchar,
+ user_id INTEGER REFERENCES users (id));
+ INSERT INTO users (id, name) VALUES (1, 'olivier');
+ INSERT INTO orders (id, name) VALUES (1, 'order of olivier (1)');
+}
+
+teardown
+{
+ DROP TABLE users, orders;
+}
+
+session s1
+step s1b { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step s1u1 { UPDATE orders SET name = 'order of olivier (2)', user_id = 1 WHERE id = 1; }
+step s1u2 { UPDATE orders SET name = 'order of olivier (3)', user_id = 1 WHERE id = 1; }
+step s1c { COMMIT; }
+
+session s2
+step s2b { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step s2u { UPDATE users SET sometime = '1830-10-04' WHERE id = 1; }
+step s2c { COMMIT; }
+
+permutation s1b s2b s2u s2c s1u1 s1u2 s1c
+permutation s1b s2b s2u s1u1 s2c s1u2 s1c
+permutation s1b s2b s1u1 s2u s2c s1u2 s1c
+permutation s1b s1u1 s2b s2u s2c s1u2 s1c
+permutation s1b s1u1 s2b s1u2 s2u s2c s1c
+permutation s1b s1u1 s1u2 s2b s2u s2c s1c
diff --git a/src/test/isolation/specs/vacuum-concurrent-drop.spec b/src/test/isolation/specs/vacuum-concurrent-drop.spec
new file mode 100644
index 0000000..148a2d5
--- /dev/null
+++ b/src/test/isolation/specs/vacuum-concurrent-drop.spec
@@ -0,0 +1,45 @@
+# Test for log messages emitted by VACUUM and ANALYZE when a specified
+# relation is concurrently dropped.
+#
+# This also verifies that log messages are not emitted for concurrently
+# dropped relations that were not specified in the VACUUM or ANALYZE
+# command.
+
+setup
+{
+ CREATE TABLE parted (a INT) PARTITION BY LIST (a);
+ CREATE TABLE part1 PARTITION OF parted FOR VALUES IN (1);
+ CREATE TABLE part2 PARTITION OF parted FOR VALUES IN (2);
+}
+
+teardown
+{
+ DROP TABLE IF EXISTS parted;
+}
+
+session s1
+step lock
+{
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+}
+step drop_and_commit
+{
+ DROP TABLE part2;
+ COMMIT;
+}
+
+session s2
+step vac_specified { VACUUM part1, part2; }
+step vac_all_parts { VACUUM parted; }
+step analyze_specified { ANALYZE part1, part2; }
+step analyze_all_parts { ANALYZE parted; }
+step vac_analyze_specified { VACUUM ANALYZE part1, part2; }
+step vac_analyze_all_parts { VACUUM ANALYZE parted; }
+
+permutation lock vac_specified drop_and_commit
+permutation lock vac_all_parts drop_and_commit
+permutation lock analyze_specified drop_and_commit
+permutation lock analyze_all_parts drop_and_commit
+permutation lock vac_analyze_specified drop_and_commit
+permutation lock vac_analyze_all_parts drop_and_commit
diff --git a/src/test/isolation/specs/vacuum-conflict.spec b/src/test/isolation/specs/vacuum-conflict.spec
new file mode 100644
index 0000000..3cb8926
--- /dev/null
+++ b/src/test/isolation/specs/vacuum-conflict.spec
@@ -0,0 +1,51 @@
+# Tests for locking conflicts with VACUUM and ANALYZE commands.
+
+setup
+{
+ CREATE ROLE regress_vacuum_conflict;
+ CREATE TABLE vacuum_tab (a int);
+}
+
+teardown
+{
+ DROP TABLE vacuum_tab;
+ DROP ROLE regress_vacuum_conflict;
+}
+
+session s1
+step s1_begin { BEGIN; }
+step s1_lock { LOCK vacuum_tab IN SHARE UPDATE EXCLUSIVE MODE; }
+step s1_commit { COMMIT; }
+
+session s2
+step s2_grant { ALTER TABLE vacuum_tab OWNER TO regress_vacuum_conflict; }
+step s2_auth { SET ROLE regress_vacuum_conflict; }
+step s2_vacuum { VACUUM vacuum_tab; }
+step s2_analyze { ANALYZE vacuum_tab; }
+step s2_reset { RESET ROLE; }
+
+# The role doesn't have privileges to vacuum the table, so VACUUM should
+# immediately skip the table without waiting for a lock.
+permutation s1_begin s1_lock s2_auth s2_vacuum s1_commit s2_reset
+permutation s1_begin s2_auth s2_vacuum s1_lock s1_commit s2_reset
+permutation s1_begin s2_auth s1_lock s2_vacuum s1_commit s2_reset
+permutation s2_auth s2_vacuum s1_begin s1_lock s1_commit s2_reset
+
+# Same as previously for ANALYZE
+permutation s1_begin s1_lock s2_auth s2_analyze s1_commit s2_reset
+permutation s1_begin s2_auth s2_analyze s1_lock s1_commit s2_reset
+permutation s1_begin s2_auth s1_lock s2_analyze s1_commit s2_reset
+permutation s2_auth s2_analyze s1_begin s1_lock s1_commit s2_reset
+
+# The role has privileges to vacuum the table, VACUUM will block if
+# another session holds a lock on the table and succeed in all cases.
+permutation s1_begin s2_grant s1_lock s2_auth s2_vacuum s1_commit s2_reset
+permutation s1_begin s2_grant s2_auth s2_vacuum s1_lock s1_commit s2_reset
+permutation s1_begin s2_grant s2_auth s1_lock s2_vacuum s1_commit s2_reset
+permutation s2_grant s2_auth s2_vacuum s1_begin s1_lock s1_commit s2_reset
+
+# Same as previously for ANALYZE
+permutation s1_begin s2_grant s1_lock s2_auth s2_analyze s1_commit s2_reset
+permutation s1_begin s2_grant s2_auth s2_analyze s1_lock s1_commit s2_reset
+permutation s1_begin s2_grant s2_auth s1_lock s2_analyze s1_commit s2_reset
+permutation s2_grant s2_auth s2_analyze s1_begin s1_lock s1_commit s2_reset
diff --git a/src/test/isolation/specs/vacuum-reltuples.spec b/src/test/isolation/specs/vacuum-reltuples.spec
new file mode 100644
index 0000000..ae2f79b
--- /dev/null
+++ b/src/test/isolation/specs/vacuum-reltuples.spec
@@ -0,0 +1,48 @@
+# Test for vacuum's handling of reltuples when pages are skipped due
+# to page pins. We absolutely need to avoid setting reltuples=0 in
+# such cases, since that interferes badly with planning.
+#
+# Expected result in second permutation is 20 tuples rather than 21 as
+# for the others, because vacuum should leave the previous result
+# (from before the insert) in place.
+
+setup {
+ create table smalltbl
+ as select i as id from generate_series(1,20) i;
+ alter table smalltbl set (autovacuum_enabled = off);
+}
+setup {
+ vacuum analyze smalltbl;
+}
+
+teardown {
+ drop table smalltbl;
+}
+
+session worker
+step open {
+ begin;
+ declare c1 cursor for select 1 as dummy from smalltbl;
+}
+step fetch1 {
+ fetch next from c1;
+}
+step close {
+ commit;
+}
+step stats {
+ select relpages, reltuples from pg_class
+ where oid='smalltbl'::regclass;
+}
+
+session vacuumer
+step vac {
+ vacuum smalltbl;
+}
+step modify {
+ insert into smalltbl select max(id)+1 from smalltbl;
+}
+
+permutation modify vac stats
+permutation modify open fetch1 vac close stats
+permutation modify vac stats
diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec
new file mode 100644
index 0000000..3fad6e1
--- /dev/null
+++ b/src/test/isolation/specs/vacuum-skip-locked.spec
@@ -0,0 +1,61 @@
+# Test for SKIP_LOCKED option of VACUUM and ANALYZE commands.
+#
+# This also verifies that log messages are not emitted for skipped relations
+# that were not specified in the VACUUM or ANALYZE command.
+
+setup
+{
+ CREATE TABLE parted (a INT) PARTITION BY LIST (a);
+ CREATE TABLE part1 PARTITION OF parted FOR VALUES IN (1);
+ ALTER TABLE part1 SET (autovacuum_enabled = false);
+ CREATE TABLE part2 PARTITION OF parted FOR VALUES IN (2);
+ ALTER TABLE part2 SET (autovacuum_enabled = false);
+}
+
+teardown
+{
+ DROP TABLE IF EXISTS parted;
+}
+
+session s1
+step lock_share
+{
+ BEGIN;
+ LOCK part1 IN SHARE MODE;
+}
+step lock_access_exclusive
+{
+ BEGIN;
+ LOCK part1 IN ACCESS EXCLUSIVE MODE;
+}
+step commit
+{
+ COMMIT;
+}
+
+session s2
+step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; }
+step vac_all_parts { VACUUM (SKIP_LOCKED) parted; }
+step analyze_specified { ANALYZE (SKIP_LOCKED) part1, part2; }
+step analyze_all_parts { ANALYZE (SKIP_LOCKED) parted; }
+step vac_analyze_specified { VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; }
+step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; }
+step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; }
+step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; }
+
+permutation lock_share vac_specified commit
+permutation lock_share vac_all_parts commit
+permutation lock_share analyze_specified commit
+permutation lock_share analyze_all_parts commit
+permutation lock_share vac_analyze_specified commit
+permutation lock_share vac_analyze_all_parts commit
+permutation lock_share vac_full_specified commit
+permutation lock_share vac_full_all_parts commit
+permutation lock_access_exclusive vac_specified commit
+permutation lock_access_exclusive vac_all_parts commit
+permutation lock_access_exclusive analyze_specified commit
+permutation lock_access_exclusive analyze_all_parts commit
+permutation lock_access_exclusive vac_analyze_specified commit
+permutation lock_access_exclusive vac_analyze_all_parts commit
+permutation lock_access_exclusive vac_full_specified commit
+permutation lock_access_exclusive vac_full_all_parts commit
diff --git a/src/test/isolation/specscanner.c b/src/test/isolation/specscanner.c
new file mode 100644
index 0000000..be0d369
--- /dev/null
+++ b/src/test/isolation/specscanner.c
@@ -0,0 +1,2220 @@
+#line 2 "specscanner.c"
+
+#line 4 "specscanner.c"
+
+#define YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define yy_create_buffer spec_yy_create_buffer
+#define yy_delete_buffer spec_yy_delete_buffer
+#define yy_scan_buffer spec_yy_scan_buffer
+#define yy_scan_string spec_yy_scan_string
+#define yy_scan_bytes spec_yy_scan_bytes
+#define yy_init_buffer spec_yy_init_buffer
+#define yy_flush_buffer spec_yy_flush_buffer
+#define yy_load_buffer_state spec_yy_load_buffer_state
+#define yy_switch_to_buffer spec_yy_switch_to_buffer
+#define yypush_buffer_state spec_yypush_buffer_state
+#define yypop_buffer_state spec_yypop_buffer_state
+#define yyensure_buffer_stack spec_yyensure_buffer_stack
+#define yy_flex_debug spec_yy_flex_debug
+#define yyin spec_yyin
+#define yyleng spec_yyleng
+#define yylex spec_yylex
+#define yylineno spec_yylineno
+#define yyout spec_yyout
+#define yyrestart spec_yyrestart
+#define yytext spec_yytext
+#define yywrap spec_yywrap
+#define yyalloc spec_yyalloc
+#define yyrealloc spec_yyrealloc
+#define yyfree spec_yyfree
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 6
+#define YY_FLEX_SUBMINOR_VERSION 4
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+#ifdef yy_create_buffer
+#define spec_yy_create_buffer_ALREADY_DEFINED
+#else
+#define yy_create_buffer spec_yy_create_buffer
+#endif
+
+#ifdef yy_delete_buffer
+#define spec_yy_delete_buffer_ALREADY_DEFINED
+#else
+#define yy_delete_buffer spec_yy_delete_buffer
+#endif
+
+#ifdef yy_scan_buffer
+#define spec_yy_scan_buffer_ALREADY_DEFINED
+#else
+#define yy_scan_buffer spec_yy_scan_buffer
+#endif
+
+#ifdef yy_scan_string
+#define spec_yy_scan_string_ALREADY_DEFINED
+#else
+#define yy_scan_string spec_yy_scan_string
+#endif
+
+#ifdef yy_scan_bytes
+#define spec_yy_scan_bytes_ALREADY_DEFINED
+#else
+#define yy_scan_bytes spec_yy_scan_bytes
+#endif
+
+#ifdef yy_init_buffer
+#define spec_yy_init_buffer_ALREADY_DEFINED
+#else
+#define yy_init_buffer spec_yy_init_buffer
+#endif
+
+#ifdef yy_flush_buffer
+#define spec_yy_flush_buffer_ALREADY_DEFINED
+#else
+#define yy_flush_buffer spec_yy_flush_buffer
+#endif
+
+#ifdef yy_load_buffer_state
+#define spec_yy_load_buffer_state_ALREADY_DEFINED
+#else
+#define yy_load_buffer_state spec_yy_load_buffer_state
+#endif
+
+#ifdef yy_switch_to_buffer
+#define spec_yy_switch_to_buffer_ALREADY_DEFINED
+#else
+#define yy_switch_to_buffer spec_yy_switch_to_buffer
+#endif
+
+#ifdef yypush_buffer_state
+#define spec_yypush_buffer_state_ALREADY_DEFINED
+#else
+#define yypush_buffer_state spec_yypush_buffer_state
+#endif
+
+#ifdef yypop_buffer_state
+#define spec_yypop_buffer_state_ALREADY_DEFINED
+#else
+#define yypop_buffer_state spec_yypop_buffer_state
+#endif
+
+#ifdef yyensure_buffer_stack
+#define spec_yyensure_buffer_stack_ALREADY_DEFINED
+#else
+#define yyensure_buffer_stack spec_yyensure_buffer_stack
+#endif
+
+#ifdef yylex
+#define spec_yylex_ALREADY_DEFINED
+#else
+#define yylex spec_yylex
+#endif
+
+#ifdef yyrestart
+#define spec_yyrestart_ALREADY_DEFINED
+#else
+#define yyrestart spec_yyrestart
+#endif
+
+#ifdef yylex_init
+#define spec_yylex_init_ALREADY_DEFINED
+#else
+#define yylex_init spec_yylex_init
+#endif
+
+#ifdef yylex_init_extra
+#define spec_yylex_init_extra_ALREADY_DEFINED
+#else
+#define yylex_init_extra spec_yylex_init_extra
+#endif
+
+#ifdef yylex_destroy
+#define spec_yylex_destroy_ALREADY_DEFINED
+#else
+#define yylex_destroy spec_yylex_destroy
+#endif
+
+#ifdef yyget_debug
+#define spec_yyget_debug_ALREADY_DEFINED
+#else
+#define yyget_debug spec_yyget_debug
+#endif
+
+#ifdef yyset_debug
+#define spec_yyset_debug_ALREADY_DEFINED
+#else
+#define yyset_debug spec_yyset_debug
+#endif
+
+#ifdef yyget_extra
+#define spec_yyget_extra_ALREADY_DEFINED
+#else
+#define yyget_extra spec_yyget_extra
+#endif
+
+#ifdef yyset_extra
+#define spec_yyset_extra_ALREADY_DEFINED
+#else
+#define yyset_extra spec_yyset_extra
+#endif
+
+#ifdef yyget_in
+#define spec_yyget_in_ALREADY_DEFINED
+#else
+#define yyget_in spec_yyget_in
+#endif
+
+#ifdef yyset_in
+#define spec_yyset_in_ALREADY_DEFINED
+#else
+#define yyset_in spec_yyset_in
+#endif
+
+#ifdef yyget_out
+#define spec_yyget_out_ALREADY_DEFINED
+#else
+#define yyget_out spec_yyget_out
+#endif
+
+#ifdef yyset_out
+#define spec_yyset_out_ALREADY_DEFINED
+#else
+#define yyset_out spec_yyset_out
+#endif
+
+#ifdef yyget_leng
+#define spec_yyget_leng_ALREADY_DEFINED
+#else
+#define yyget_leng spec_yyget_leng
+#endif
+
+#ifdef yyget_text
+#define spec_yyget_text_ALREADY_DEFINED
+#else
+#define yyget_text spec_yyget_text
+#endif
+
+#ifdef yyget_lineno
+#define spec_yyget_lineno_ALREADY_DEFINED
+#else
+#define yyget_lineno spec_yyget_lineno
+#endif
+
+#ifdef yyset_lineno
+#define spec_yyset_lineno_ALREADY_DEFINED
+#else
+#define yyset_lineno spec_yyset_lineno
+#endif
+
+#ifdef yywrap
+#define spec_yywrap_ALREADY_DEFINED
+#else
+#define yywrap spec_yywrap
+#endif
+
+#ifdef yyalloc
+#define spec_yyalloc_ALREADY_DEFINED
+#else
+#define yyalloc spec_yyalloc
+#endif
+
+#ifdef yyrealloc
+#define spec_yyrealloc_ALREADY_DEFINED
+#else
+#define yyrealloc spec_yyrealloc
+#endif
+
+#ifdef yyfree
+#define spec_yyfree_ALREADY_DEFINED
+#else
+#define yyfree spec_yyfree
+#endif
+
+#ifdef yytext
+#define spec_yytext_ALREADY_DEFINED
+#else
+#define yytext spec_yytext
+#endif
+
+#ifdef yyleng
+#define spec_yyleng_ALREADY_DEFINED
+#else
+#define yyleng spec_yyleng
+#endif
+
+#ifdef yyin
+#define spec_yyin_ALREADY_DEFINED
+#else
+#define yyin spec_yyin
+#endif
+
+#ifdef yyout
+#define spec_yyout_ALREADY_DEFINED
+#else
+#define yyout spec_yyout
+#endif
+
+#ifdef yy_flex_debug
+#define spec_yy_flex_debug_ALREADY_DEFINED
+#else
+#define yy_flex_debug spec_yy_flex_debug
+#endif
+
+#ifdef yylineno
+#define spec_yylineno_ALREADY_DEFINED
+#else
+#define yylineno spec_yylineno
+#endif
+
+/* First, we deal with platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types.
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t;
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX (4294967295U)
+#endif
+
+#ifndef SIZE_MAX
+#define SIZE_MAX (~(size_t)0)
+#endif
+
+#endif /* ! C99 */
+
+#endif /* ! FLEXINT_H */
+
+/* begin standard C++ headers. */
+
+/* TODO: this is always defined, so inline it */
+#define yyconst const
+
+#if defined(__GNUC__) && __GNUC__ >= 3
+#define yynoreturn __attribute__((__noreturn__))
+#else
+#define yynoreturn
+#endif
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an
+ * integer in range [0..255] for use as an array index.
+ */
+#define YY_SC_TO_UI(c) ((YY_CHAR) (c))
+
+/* Enter a start condition. This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN (yy_start) = 1 + 2 *
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state. The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START (((yy_start) - 1) / 2)
+#define YYSTATE YY_START
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE yyrestart( yyin )
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k.
+ * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case.
+ * Ditto for the __ia64__ case accordingly.
+ */
+#define YY_BUF_SIZE 32768
+#else
+#define YY_BUF_SIZE 16384
+#endif /* __ia64__ */
+#endif
+
+/* The state buf must be large enough to hold one state per character in the main buffer.
+ */
+#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type))
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef size_t yy_size_t;
+#endif
+
+extern int yyleng;
+
+extern FILE *yyin, *yyout;
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+ #define YY_LESS_LINENO(n)
+ #define YY_LINENO_REWIND_TO(ptr)
+
+/* Return all but the first "n" matched characters back to the input stream. */
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ int yyless_macro_arg = (n); \
+ YY_LESS_LINENO(yyless_macro_arg);\
+ *yy_cp = (yy_hold_char); \
+ YY_RESTORE_YY_MORE_OFFSET \
+ (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
+ YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+ } \
+ while ( 0 )
+#define unput(c) yyunput( c, (yytext_ptr) )
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+ {
+ FILE *yy_input_file;
+
+ char *yy_ch_buf; /* input buffer */
+ char *yy_buf_pos; /* current position in input buffer */
+
+ /* Size of input buffer in bytes, not including room for EOB
+ * characters.
+ */
+ int yy_buf_size;
+
+ /* Number of characters read into yy_ch_buf, not including EOB
+ * characters.
+ */
+ int yy_n_chars;
+
+ /* Whether we "own" the buffer - i.e., we know we created it,
+ * and can realloc() it to grow it, and should free() it to
+ * delete it.
+ */
+ int yy_is_our_buffer;
+
+ /* Whether this is an "interactive" input source; if so, and
+ * if we're using stdio for input, then we want to use getc()
+ * instead of fread(), to make sure we stop fetching input after
+ * each newline.
+ */
+ int yy_is_interactive;
+
+ /* Whether we're considered to be at the beginning of a line.
+ * If so, '^' rules will be active on the next match, otherwise
+ * not.
+ */
+ int yy_at_bol;
+
+ int yy_bs_lineno; /**< The line count. */
+ int yy_bs_column; /**< The column count. */
+
+ /* Whether to try to fill the input buffer when we reach the
+ * end of it.
+ */
+ int yy_fill_buffer;
+
+ int yy_buffer_status;
+
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+ /* When an EOF's been seen but there's still some text to process
+ * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+ * shouldn't try reading from the input source any more. We might
+ * still have a bunch of tokens to match, though, because of
+ * possible backing-up.
+ *
+ * When we actually see the EOF, we change the status to "new"
+ * (via yyrestart()), so that the user can continue scanning by
+ * just pointing yyin at a new input file.
+ */
+#define YY_BUFFER_EOF_PENDING 2
+
+ };
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+/* Stack of input buffers. */
+static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */
+static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */
+static YY_BUFFER_STATE * yy_buffer_stack = NULL; /**< Stack as an array. */
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ *
+ * Returns the top of the stack, or NULL.
+ */
+#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \
+ ? (yy_buffer_stack)[(yy_buffer_stack_top)] \
+ : NULL)
+/* Same as previous macro, but useful when we know that the buffer stack is not
+ * NULL or when we need an lvalue. For internal use only.
+ */
+#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)]
+
+/* yy_hold_char holds the character lost when yytext is formed. */
+static char yy_hold_char;
+static int yy_n_chars; /* number of characters read into yy_ch_buf */
+int yyleng;
+
+/* Points to current character in buffer. */
+static char *yy_c_buf_p = NULL;
+static int yy_init = 0; /* whether we need to initialize */
+static int yy_start = 0; /* start state number */
+
+/* Flag which is used to allow yywrap()'s to do buffer switches
+ * instead of setting up a fresh yyin. A bit of a hack ...
+ */
+static int yy_did_buffer_switch_on_eof;
+
+void yyrestart ( FILE *input_file );
+void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer );
+YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size );
+void yy_delete_buffer ( YY_BUFFER_STATE b );
+void yy_flush_buffer ( YY_BUFFER_STATE b );
+void yypush_buffer_state ( YY_BUFFER_STATE new_buffer );
+void yypop_buffer_state ( void );
+
+static void yyensure_buffer_stack ( void );
+static void yy_load_buffer_state ( void );
+static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file );
+#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER )
+
+YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size );
+YY_BUFFER_STATE yy_scan_string ( const char *yy_str );
+YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len );
+
+void *yyalloc ( yy_size_t );
+void *yyrealloc ( void *, yy_size_t );
+void yyfree ( void * );
+
+#define yy_new_buffer yy_create_buffer
+#define yy_set_interactive(is_interactive) \
+ { \
+ if ( ! YY_CURRENT_BUFFER ){ \
+ yyensure_buffer_stack (); \
+ YY_CURRENT_BUFFER_LVALUE = \
+ yy_create_buffer( yyin, YY_BUF_SIZE ); \
+ } \
+ YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
+ }
+#define yy_set_bol(at_bol) \
+ { \
+ if ( ! YY_CURRENT_BUFFER ){\
+ yyensure_buffer_stack (); \
+ YY_CURRENT_BUFFER_LVALUE = \
+ yy_create_buffer( yyin, YY_BUF_SIZE ); \
+ } \
+ YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
+ }
+#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
+
+/* Begin user sect3 */
+
+#define spec_yywrap() (/*CONSTCOND*/1)
+#define YY_SKIP_YYWRAP
+typedef flex_uint8_t YY_CHAR;
+
+FILE *yyin = NULL, *yyout = NULL;
+
+typedef int yy_state_type;
+
+extern int yylineno;
+int yylineno = 1;
+
+extern char *yytext;
+#ifdef yytext_ptr
+#undef yytext_ptr
+#endif
+#define yytext_ptr yytext
+
+static yy_state_type yy_get_previous_state ( void );
+static yy_state_type yy_try_NUL_trans ( yy_state_type current_state );
+static int yy_get_next_buffer ( void );
+static void yynoreturn yy_fatal_error ( const char* msg );
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+ (yytext_ptr) = yy_bp; \
+ yyleng = (int) (yy_cp - yy_bp); \
+ (yy_hold_char) = *yy_cp; \
+ *yy_cp = '\0'; \
+ (yy_c_buf_p) = yy_cp;
+#define YY_NUM_RULES 23
+#define YY_END_OF_BUFFER 24
+/* This struct is not used in this scanner,
+ but its presence is necessary. */
+struct yy_trans_info
+ {
+ flex_int32_t yy_verify;
+ flex_int32_t yy_nxt;
+ };
+static const flex_int16_t yy_accept[71] =
+ { 0,
+ 0, 0, 0, 0, 0, 0, 24, 22, 9, 7,
+ 11, 8, 21, 20, 10, 10, 10, 10, 10, 16,
+ 18, 18, 19, 17, 14, 15, 13, 8, 20, 10,
+ 10, 10, 10, 10, 10, 16, 0, 17, 12, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 5,
+ 10, 10, 10, 10, 4, 10, 10, 10, 10, 10,
+ 1, 10, 3, 10, 10, 6, 10, 10, 2, 0
+ } ;
+
+static const YY_CHAR yy_ec[256] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 2, 3,
+ 1, 2, 4, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 2, 1, 5, 6, 7, 1, 1, 1, 8,
+ 8, 8, 1, 8, 1, 1, 1, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 1, 1, 1,
+ 1, 1, 1, 1, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 1, 1, 1, 1, 10, 1, 11, 10, 12, 13,
+
+ 14, 10, 10, 10, 15, 10, 10, 10, 16, 17,
+ 18, 19, 10, 20, 21, 22, 23, 10, 24, 10,
+ 10, 10, 25, 1, 26, 1, 1, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10
+ } ;
+
+static const YY_CHAR yy_meta[27] =
+ { 0,
+ 1, 1, 2, 2, 1, 1, 3, 1, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 1, 1
+ } ;
+
+static const flex_int16_t yy_base[75] =
+ { 0,
+ 0, 0, 25, 50, 27, 28, 96, 97, 97, 97,
+ 97, 0, 97, 86, 0, 76, 79, 20, 78, 33,
+ 97, 34, 97, 97, 97, 97, 86, 0, 81, 0,
+ 67, 68, 18, 73, 75, 39, 42, 97, 97, 69,
+ 59, 53, 50, 53, 51, 58, 46, 52, 47, 0,
+ 52, 50, 41, 44, 0, 43, 38, 47, 40, 32,
+ 0, 33, 0, 33, 34, 0, 30, 30, 0, 97,
+ 76, 79, 82, 42
+ } ;
+
+static const flex_int16_t yy_def[75] =
+ { 0,
+ 70, 1, 71, 71, 72, 72, 70, 70, 70, 70,
+ 70, 73, 70, 70, 74, 74, 74, 74, 74, 70,
+ 70, 70, 70, 70, 70, 70, 70, 73, 70, 74,
+ 74, 74, 74, 74, 74, 70, 70, 70, 70, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 0,
+ 70, 70, 70, 70
+ } ;
+
+static const flex_int16_t yy_nxt[124] =
+ { 0,
+ 8, 9, 10, 9, 11, 12, 8, 13, 14, 15,
+ 15, 15, 15, 15, 15, 15, 16, 15, 17, 15,
+ 18, 19, 15, 15, 20, 8, 22, 23, 22, 26,
+ 26, 27, 27, 33, 36, 37, 36, 37, 42, 43,
+ 36, 34, 36, 37, 30, 37, 69, 68, 67, 66,
+ 24, 22, 23, 22, 65, 64, 63, 62, 61, 38,
+ 60, 59, 58, 57, 56, 55, 54, 38, 53, 52,
+ 51, 50, 49, 48, 47, 24, 21, 21, 21, 25,
+ 25, 25, 28, 46, 28, 45, 44, 41, 40, 29,
+ 39, 35, 32, 31, 29, 70, 7, 70, 70, 70,
+
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70
+ } ;
+
+static const flex_int16_t yy_chk[124] =
+ { 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, 3, 3, 3, 5,
+ 6, 5, 6, 18, 20, 22, 20, 22, 33, 33,
+ 36, 18, 36, 37, 74, 37, 68, 67, 65, 64,
+ 3, 4, 4, 4, 62, 60, 59, 58, 57, 22,
+ 56, 54, 53, 52, 51, 49, 48, 37, 47, 46,
+ 45, 44, 43, 42, 41, 4, 71, 71, 71, 72,
+ 72, 72, 73, 40, 73, 35, 34, 32, 31, 29,
+ 27, 19, 17, 16, 14, 7, 70, 70, 70, 70,
+
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70
+ } ;
+
+static yy_state_type yy_last_accepting_state;
+static char *yy_last_accepting_cpos;
+
+extern int yy_flex_debug;
+int yy_flex_debug = 0;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+char *yytext;
+#line 1 "specscanner.l"
+#line 2 "specscanner.l"
+/*-------------------------------------------------------------------------
+ *
+ * specscanner.l
+ * a lexical scanner for an isolation test specification
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *-------------------------------------------------------------------------
+ */
+
+static int yyline = 1; /* line number for error reporting */
+
+#define LITBUF_INIT 1024 /* initial size of litbuf */
+static char *litbuf = NULL;
+static size_t litbufsize = 0;
+static size_t litbufpos = 0;
+
+static void addlitchar(char c);
+
+/* LCOV_EXCL_START */
+
+#line 775 "specscanner.c"
+#define YY_NO_INPUT 1
+
+#line 778 "specscanner.c"
+
+#define INITIAL 0
+#define sql 1
+#define qident 2
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+#include <unistd.h>
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+static int yy_init_globals ( void );
+
+/* Accessor methods to globals.
+ These are made visible to non-reentrant scanners for convenience. */
+
+int yylex_destroy ( void );
+
+int yyget_debug ( void );
+
+void yyset_debug ( int debug_flag );
+
+YY_EXTRA_TYPE yyget_extra ( void );
+
+void yyset_extra ( YY_EXTRA_TYPE user_defined );
+
+FILE *yyget_in ( void );
+
+void yyset_in ( FILE * _in_str );
+
+FILE *yyget_out ( void );
+
+void yyset_out ( FILE * _out_str );
+
+ int yyget_leng ( void );
+
+char *yyget_text ( void );
+
+int yyget_lineno ( void );
+
+void yyset_lineno ( int _line_number );
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap ( void );
+#else
+extern int yywrap ( void );
+#endif
+#endif
+
+#ifndef YY_NO_UNPUT
+
+#endif
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy ( char *, const char *, int );
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen ( const char * );
+#endif
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+static int yyinput ( void );
+#else
+static int input ( void );
+#endif
+
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k */
+#define YY_READ_BUF_SIZE 16384
+#else
+#define YY_READ_BUF_SIZE 8192
+#endif /* __ia64__ */
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0)
+#endif
+
+/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
+ { \
+ int c = '*'; \
+ int n; \
+ for ( n = 0; n < max_size && \
+ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+ buf[n] = (char) c; \
+ if ( c == '\n' ) \
+ buf[n++] = (char) c; \
+ if ( c == EOF && ferror( yyin ) ) \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ result = n; \
+ } \
+ else \
+ { \
+ errno=0; \
+ while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \
+ { \
+ if( errno != EINTR) \
+ { \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ break; \
+ } \
+ errno=0; \
+ clearerr(yyin); \
+ } \
+ }\
+\
+
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg )
+#endif
+
+/* end tables serialization structures and prototypes */
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int yylex (void);
+
+#define YY_DECL int yylex (void)
+#endif /* !YY_DECL */
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK /*LINTED*/break;
+#endif
+
+#define YY_RULE_SETUP \
+ YY_USER_ACTION
+
+/** The main scanner function which does all the work.
+ */
+YY_DECL
+{
+ yy_state_type yy_current_state;
+ char *yy_cp, *yy_bp;
+ int yy_act;
+
+ if ( !(yy_init) )
+ {
+ (yy_init) = 1;
+
+#ifdef YY_USER_INIT
+ YY_USER_INIT;
+#endif
+
+ if ( ! (yy_start) )
+ (yy_start) = 1; /* first start state */
+
+ if ( ! yyin )
+ yyin = stdin;
+
+ if ( ! yyout )
+ yyout = stdout;
+
+ if ( ! YY_CURRENT_BUFFER ) {
+ yyensure_buffer_stack ();
+ YY_CURRENT_BUFFER_LVALUE =
+ yy_create_buffer( yyin, YY_BUF_SIZE );
+ }
+
+ yy_load_buffer_state( );
+ }
+
+ {
+#line 52 "specscanner.l"
+
+
+
+#line 56 "specscanner.l"
+ /* Allocate litbuf in first call of yylex() */
+ if (litbuf == NULL)
+ {
+ litbuf = pg_malloc(LITBUF_INIT);
+ litbufsize = LITBUF_INIT;
+ }
+
+
+ /* Keywords (must appear before the {identifier} rule!) */
+#line 1009 "specscanner.c"
+
+ while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */
+ {
+ yy_cp = (yy_c_buf_p);
+
+ /* Support of yytext. */
+ *yy_cp = (yy_hold_char);
+
+ /* yy_bp points to the position in yy_ch_buf of the start of
+ * the current run.
+ */
+ yy_bp = yy_cp;
+
+ yy_current_state = (yy_start);
+yy_match:
+ do
+ {
+ YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ;
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 71 )
+ yy_c = yy_meta[yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+ ++yy_cp;
+ }
+ while ( yy_current_state != 70 );
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+
+yy_find_action:
+ yy_act = yy_accept[yy_current_state];
+
+ YY_DO_BEFORE_ACTION;
+
+do_action: /* This label is used only to access EOF actions. */
+
+ switch ( yy_act )
+ { /* beginning of action switch */
+ case 0: /* must back up */
+ /* undo the effects of YY_DO_BEFORE_ACTION */
+ *yy_cp = (yy_hold_char);
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+ goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 65 "specscanner.l"
+{ return NOTICES; }
+ YY_BREAK
+case 2:
+YY_RULE_SETUP
+#line 66 "specscanner.l"
+{ return PERMUTATION; }
+ YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 67 "specscanner.l"
+{ return SESSION; }
+ YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 68 "specscanner.l"
+{ return SETUP; }
+ YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 69 "specscanner.l"
+{ return STEP; }
+ YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 70 "specscanner.l"
+{ return TEARDOWN; }
+ YY_BREAK
+/* Whitespace and comments */
+case 7:
+/* rule 7 can match eol */
+YY_RULE_SETUP
+#line 73 "specscanner.l"
+{ yyline++; }
+ YY_BREAK
+case 8:
+YY_RULE_SETUP
+#line 74 "specscanner.l"
+{ /* ignore */ }
+ YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 75 "specscanner.l"
+{ /* ignore */ }
+ YY_BREAK
+/* Plain identifiers */
+case 10:
+YY_RULE_SETUP
+#line 78 "specscanner.l"
+{
+ yylval.str = pg_strdup(yytext);
+ return(identifier);
+ }
+ YY_BREAK
+/* Quoted identifiers: "foo" */
+case 11:
+YY_RULE_SETUP
+#line 84 "specscanner.l"
+{
+ litbufpos = 0;
+ BEGIN(qident);
+ }
+ YY_BREAK
+case 12:
+YY_RULE_SETUP
+#line 88 "specscanner.l"
+{ addlitchar(yytext[0]); }
+ YY_BREAK
+case 13:
+YY_RULE_SETUP
+#line 89 "specscanner.l"
+{
+ litbuf[litbufpos] = '\0';
+ yylval.str = pg_strdup(litbuf);
+ BEGIN(INITIAL);
+ return(identifier);
+ }
+ YY_BREAK
+case 14:
+YY_RULE_SETUP
+#line 95 "specscanner.l"
+{ addlitchar(yytext[0]); }
+ YY_BREAK
+case 15:
+/* rule 15 can match eol */
+YY_RULE_SETUP
+#line 96 "specscanner.l"
+{ yyerror("unexpected newline in quoted identifier"); }
+ YY_BREAK
+case YY_STATE_EOF(qident):
+#line 97 "specscanner.l"
+{ yyerror("unterminated quoted identifier"); }
+ YY_BREAK
+/* SQL blocks: { UPDATE ... } */
+/* We trim leading/trailing whitespace, otherwise they're unprocessed */
+case 16:
+YY_RULE_SETUP
+#line 101 "specscanner.l"
+{
+
+ litbufpos = 0;
+ BEGIN(sql);
+ }
+ YY_BREAK
+case 17:
+YY_RULE_SETUP
+#line 106 "specscanner.l"
+{
+ litbuf[litbufpos] = '\0';
+ yylval.str = pg_strdup(litbuf);
+ BEGIN(INITIAL);
+ return(sqlblock);
+ }
+ YY_BREAK
+case 18:
+YY_RULE_SETUP
+#line 112 "specscanner.l"
+{
+ addlitchar(yytext[0]);
+ }
+ YY_BREAK
+case 19:
+/* rule 19 can match eol */
+YY_RULE_SETUP
+#line 115 "specscanner.l"
+{
+ yyline++;
+ addlitchar(yytext[0]);
+ }
+ YY_BREAK
+case YY_STATE_EOF(sql):
+#line 119 "specscanner.l"
+{
+ yyerror("unterminated sql block");
+ }
+ YY_BREAK
+/* Numbers and punctuation */
+case 20:
+YY_RULE_SETUP
+#line 124 "specscanner.l"
+{
+ yylval.integer = atoi(yytext);
+ return INTEGER;
+ }
+ YY_BREAK
+case 21:
+YY_RULE_SETUP
+#line 129 "specscanner.l"
+{ return yytext[0]; }
+ YY_BREAK
+/* Anything else is an error */
+case 22:
+YY_RULE_SETUP
+#line 132 "specscanner.l"
+{
+ fprintf(stderr, "syntax error at line %d: unexpected character \"%s\"\n", yyline, yytext);
+ exit(1);
+ }
+ YY_BREAK
+case 23:
+YY_RULE_SETUP
+#line 136 "specscanner.l"
+YY_FATAL_ERROR( "flex scanner jammed" );
+ YY_BREAK
+#line 1228 "specscanner.c"
+case YY_STATE_EOF(INITIAL):
+ yyterminate();
+
+ case YY_END_OF_BUFFER:
+ {
+ /* Amount of text matched not including the EOB char. */
+ int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1;
+
+ /* Undo the effects of YY_DO_BEFORE_ACTION. */
+ *yy_cp = (yy_hold_char);
+ YY_RESTORE_YY_MORE_OFFSET
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW )
+ {
+ /* We're scanning a new file or input source. It's
+ * possible that this happened because the user
+ * just pointed yyin at a new source and called
+ * yylex(). If so, then we have to assure
+ * consistency between YY_CURRENT_BUFFER and our
+ * globals. Here is the right place to do so, because
+ * this is the first action (other than possibly a
+ * back-up) that will match for the new input source.
+ */
+ (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+ YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin;
+ YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL;
+ }
+
+ /* Note that here we test for yy_c_buf_p "<=" to the position
+ * of the first EOB in the buffer, since yy_c_buf_p will
+ * already have been incremented past the NUL character
+ * (since all states make transitions on EOB to the
+ * end-of-buffer state). Contrast this with the test
+ * in input().
+ */
+ if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+ { /* This was really a NUL. */
+ yy_state_type yy_next_state;
+
+ (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state( );
+
+ /* Okay, we're now positioned to make the NUL
+ * transition. We couldn't have
+ * yy_get_previous_state() go ahead and do it
+ * for us because it doesn't know how to deal
+ * with the possibility of jamming (and we don't
+ * want to build jamming into it because then it
+ * will run more slowly).
+ */
+
+ yy_next_state = yy_try_NUL_trans( yy_current_state );
+
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+
+ if ( yy_next_state )
+ {
+ /* Consume the NUL. */
+ yy_cp = ++(yy_c_buf_p);
+ yy_current_state = yy_next_state;
+ goto yy_match;
+ }
+
+ else
+ {
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+ goto yy_find_action;
+ }
+ }
+
+ else switch ( yy_get_next_buffer( ) )
+ {
+ case EOB_ACT_END_OF_FILE:
+ {
+ (yy_did_buffer_switch_on_eof) = 0;
+
+ if ( yywrap( ) )
+ {
+ /* Note: because we've taken care in
+ * yy_get_next_buffer() to have set up
+ * yytext, we can now set up
+ * yy_c_buf_p so that if some total
+ * hoser (like flex itself) wants to
+ * call the scanner after we return the
+ * YY_NULL, it'll still work - another
+ * YY_NULL will get returned.
+ */
+ (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ;
+
+ yy_act = YY_STATE_EOF(YY_START);
+ goto do_action;
+ }
+
+ else
+ {
+ if ( ! (yy_did_buffer_switch_on_eof) )
+ YY_NEW_FILE;
+ }
+ break;
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ (yy_c_buf_p) =
+ (yytext_ptr) + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state( );
+
+ yy_cp = (yy_c_buf_p);
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+ goto yy_match;
+
+ case EOB_ACT_LAST_MATCH:
+ (yy_c_buf_p) =
+ &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)];
+
+ yy_current_state = yy_get_previous_state( );
+
+ yy_cp = (yy_c_buf_p);
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+ goto yy_find_action;
+ }
+ break;
+ }
+
+ default:
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--no action found" );
+ } /* end of action switch */
+ } /* end of scanning one token */
+ } /* end of user's declarations */
+} /* end of yylex */
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ * EOB_ACT_LAST_MATCH -
+ * EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ * EOB_ACT_END_OF_FILE - end of file
+ */
+static int yy_get_next_buffer (void)
+{
+ char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+ char *source = (yytext_ptr);
+ int number_to_move, i;
+ int ret_val;
+
+ if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] )
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--end of buffer missed" );
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 )
+ { /* Don't try to fill the buffer, so this is an EOF. */
+ if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 )
+ {
+ /* We matched a single character, the EOB, so
+ * treat this as a final EOF.
+ */
+ return EOB_ACT_END_OF_FILE;
+ }
+
+ else
+ {
+ /* We matched some text prior to the EOB, first
+ * process it.
+ */
+ return EOB_ACT_LAST_MATCH;
+ }
+ }
+
+ /* Try to read more data. */
+
+ /* First move last chars to start of buffer. */
+ number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr) - 1);
+
+ for ( i = 0; i < number_to_move; ++i )
+ *(dest++) = *(source++);
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+ /* don't do the read, it's not guaranteed to return an EOF,
+ * just force an EOF
+ */
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0;
+
+ else
+ {
+ int num_to_read =
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
+
+ while ( num_to_read <= 0 )
+ { /* Not enough room in the buffer - grow it. */
+
+ /* just a shorter name for the current buffer */
+ YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE;
+
+ int yy_c_buf_p_offset =
+ (int) ((yy_c_buf_p) - b->yy_ch_buf);
+
+ if ( b->yy_is_our_buffer )
+ {
+ int new_size = b->yy_buf_size * 2;
+
+ if ( new_size <= 0 )
+ b->yy_buf_size += b->yy_buf_size / 8;
+ else
+ b->yy_buf_size *= 2;
+
+ b->yy_ch_buf = (char *)
+ /* Include room in for 2 EOB chars. */
+ yyrealloc( (void *) b->yy_ch_buf,
+ (yy_size_t) (b->yy_buf_size + 2) );
+ }
+ else
+ /* Can't grow it, we don't own it. */
+ b->yy_ch_buf = NULL;
+
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR(
+ "fatal error - scanner input buffer overflow" );
+
+ (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+ num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size -
+ number_to_move - 1;
+
+ }
+
+ if ( num_to_read > YY_READ_BUF_SIZE )
+ num_to_read = YY_READ_BUF_SIZE;
+
+ /* Read in more data. */
+ YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
+ (yy_n_chars), num_to_read );
+
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ if ( (yy_n_chars) == 0 )
+ {
+ if ( number_to_move == YY_MORE_ADJ )
+ {
+ ret_val = EOB_ACT_END_OF_FILE;
+ yyrestart( yyin );
+ }
+
+ else
+ {
+ ret_val = EOB_ACT_LAST_MATCH;
+ YY_CURRENT_BUFFER_LVALUE->yy_buffer_status =
+ YY_BUFFER_EOF_PENDING;
+ }
+ }
+
+ else
+ ret_val = EOB_ACT_CONTINUE_SCAN;
+
+ if (((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) {
+ /* Extend the array by 50%, plus the number we really need. */
+ int new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1);
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc(
+ (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size );
+ if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" );
+ /* "- 2" to take care of EOB's */
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2);
+ }
+
+ (yy_n_chars) += number_to_move;
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR;
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR;
+
+ (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0];
+
+ return ret_val;
+}
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+ static yy_state_type yy_get_previous_state (void)
+{
+ yy_state_type yy_current_state;
+ char *yy_cp;
+
+ yy_current_state = (yy_start);
+
+ for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp )
+ {
+ YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 71 )
+ yy_c = yy_meta[yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+ }
+
+ return yy_current_state;
+}
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ * next_state = yy_try_NUL_trans( current_state );
+ */
+ static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state )
+{
+ int yy_is_jam;
+ char *yy_cp = (yy_c_buf_p);
+
+ YY_CHAR yy_c = 1;
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 71 )
+ yy_c = yy_meta[yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+ yy_is_jam = (yy_current_state == 70);
+
+ return yy_is_jam ? 0 : yy_current_state;
+}
+
+#ifndef YY_NO_UNPUT
+
+#endif
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+ static int yyinput (void)
+#else
+ static int input (void)
+#endif
+
+{
+ int c;
+
+ *(yy_c_buf_p) = (yy_hold_char);
+
+ if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR )
+ {
+ /* yy_c_buf_p now points to the character we want to return.
+ * If this occurs *before* the EOB characters, then it's a
+ * valid NUL; if not, then we've hit the end of the buffer.
+ */
+ if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+ /* This was really a NUL. */
+ *(yy_c_buf_p) = '\0';
+
+ else
+ { /* need more input */
+ int offset = (int) ((yy_c_buf_p) - (yytext_ptr));
+ ++(yy_c_buf_p);
+
+ switch ( yy_get_next_buffer( ) )
+ {
+ case EOB_ACT_LAST_MATCH:
+ /* This happens because yy_g_n_b()
+ * sees that we've accumulated a
+ * token and flags that we need to
+ * try matching the token before
+ * proceeding. But for input(),
+ * there's no matching to consider.
+ * So convert the EOB_ACT_LAST_MATCH
+ * to EOB_ACT_END_OF_FILE.
+ */
+
+ /* Reset buffer status. */
+ yyrestart( yyin );
+
+ /*FALLTHROUGH*/
+
+ case EOB_ACT_END_OF_FILE:
+ {
+ if ( yywrap( ) )
+ return 0;
+
+ if ( ! (yy_did_buffer_switch_on_eof) )
+ YY_NEW_FILE;
+#ifdef __cplusplus
+ return yyinput();
+#else
+ return input();
+#endif
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ (yy_c_buf_p) = (yytext_ptr) + offset;
+ break;
+ }
+ }
+ }
+
+ c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */
+ *(yy_c_buf_p) = '\0'; /* preserve yytext */
+ (yy_hold_char) = *++(yy_c_buf_p);
+
+ return c;
+}
+#endif /* ifndef YY_NO_INPUT */
+
+/** Immediately switch to a different input stream.
+ * @param input_file A readable stream.
+ *
+ * @note This function does not reset the start condition to @c INITIAL .
+ */
+ void yyrestart (FILE * input_file )
+{
+
+ if ( ! YY_CURRENT_BUFFER ){
+ yyensure_buffer_stack ();
+ YY_CURRENT_BUFFER_LVALUE =
+ yy_create_buffer( yyin, YY_BUF_SIZE );
+ }
+
+ yy_init_buffer( YY_CURRENT_BUFFER, input_file );
+ yy_load_buffer_state( );
+}
+
+/** Switch to a different input buffer.
+ * @param new_buffer The new input buffer.
+ *
+ */
+ void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer )
+{
+
+ /* TODO. We should be able to replace this entire function body
+ * with
+ * yypop_buffer_state();
+ * yypush_buffer_state(new_buffer);
+ */
+ yyensure_buffer_stack ();
+ if ( YY_CURRENT_BUFFER == new_buffer )
+ return;
+
+ if ( YY_CURRENT_BUFFER )
+ {
+ /* Flush out information for old buffer. */
+ *(yy_c_buf_p) = (yy_hold_char);
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ YY_CURRENT_BUFFER_LVALUE = new_buffer;
+ yy_load_buffer_state( );
+
+ /* We don't actually know whether we did this switch during
+ * EOF (yywrap()) processing, but the only time this flag
+ * is looked at is after yywrap() is called, so it's safe
+ * to go ahead and always set it.
+ */
+ (yy_did_buffer_switch_on_eof) = 1;
+}
+
+static void yy_load_buffer_state (void)
+{
+ (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+ (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
+ yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+ (yy_hold_char) = *(yy_c_buf_p);
+}
+
+/** Allocate and initialize an input buffer state.
+ * @param file A readable stream.
+ * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE.
+ *
+ * @return the allocated buffer state.
+ */
+ YY_BUFFER_STATE yy_create_buffer (FILE * file, int size )
+{
+ YY_BUFFER_STATE b;
+
+ b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_buf_size = size;
+
+ /* yy_ch_buf has to be 2 characters longer than the size given because
+ * we need to put in 2 end-of-buffer characters.
+ */
+ b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) );
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_is_our_buffer = 1;
+
+ yy_init_buffer( b, file );
+
+ return b;
+}
+
+/** Destroy the buffer.
+ * @param b a buffer created with yy_create_buffer()
+ *
+ */
+ void yy_delete_buffer (YY_BUFFER_STATE b )
+{
+
+ if ( ! b )
+ return;
+
+ if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */
+ YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
+
+ if ( b->yy_is_our_buffer )
+ yyfree( (void *) b->yy_ch_buf );
+
+ yyfree( (void *) b );
+}
+
+/* Initializes or reinitializes a buffer.
+ * This function is sometimes called more than once on the same buffer,
+ * such as during a yyrestart() or at EOF.
+ */
+ static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file )
+
+{
+ int oerrno = errno;
+
+ yy_flush_buffer( b );
+
+ b->yy_input_file = file;
+ b->yy_fill_buffer = 1;
+
+ /* If b is the current buffer, then yy_init_buffer was _probably_
+ * called from yyrestart() or through yy_get_next_buffer.
+ * In that case, we don't want to reset the lineno or column.
+ */
+ if (b != YY_CURRENT_BUFFER){
+ b->yy_bs_lineno = 1;
+ b->yy_bs_column = 0;
+ }
+
+ b->yy_is_interactive = 0;
+
+ errno = oerrno;
+}
+
+/** Discard all buffered characters. On the next scan, YY_INPUT will be called.
+ * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER.
+ *
+ */
+ void yy_flush_buffer (YY_BUFFER_STATE b )
+{
+ if ( ! b )
+ return;
+
+ b->yy_n_chars = 0;
+
+ /* We always need two end-of-buffer characters. The first causes
+ * a transition to the end-of-buffer state. The second causes
+ * a jam in that state.
+ */
+ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+ b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+ b->yy_buf_pos = &b->yy_ch_buf[0];
+
+ b->yy_at_bol = 1;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ if ( b == YY_CURRENT_BUFFER )
+ yy_load_buffer_state( );
+}
+
+/** Pushes the new state onto the stack. The new state becomes
+ * the current state. This function will allocate the stack
+ * if necessary.
+ * @param new_buffer The new state.
+ *
+ */
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer )
+{
+ if (new_buffer == NULL)
+ return;
+
+ yyensure_buffer_stack();
+
+ /* This block is copied from yy_switch_to_buffer. */
+ if ( YY_CURRENT_BUFFER )
+ {
+ /* Flush out information for old buffer. */
+ *(yy_c_buf_p) = (yy_hold_char);
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ /* Only push if top exists. Otherwise, replace top. */
+ if (YY_CURRENT_BUFFER)
+ (yy_buffer_stack_top)++;
+ YY_CURRENT_BUFFER_LVALUE = new_buffer;
+
+ /* copied from yy_switch_to_buffer. */
+ yy_load_buffer_state( );
+ (yy_did_buffer_switch_on_eof) = 1;
+}
+
+/** Removes and deletes the top of the stack, if present.
+ * The next element becomes the new top.
+ *
+ */
+void yypop_buffer_state (void)
+{
+ if (!YY_CURRENT_BUFFER)
+ return;
+
+ yy_delete_buffer(YY_CURRENT_BUFFER );
+ YY_CURRENT_BUFFER_LVALUE = NULL;
+ if ((yy_buffer_stack_top) > 0)
+ --(yy_buffer_stack_top);
+
+ if (YY_CURRENT_BUFFER) {
+ yy_load_buffer_state( );
+ (yy_did_buffer_switch_on_eof) = 1;
+ }
+}
+
+/* Allocates the stack if it does not exist.
+ * Guarantees space for at least one push.
+ */
+static void yyensure_buffer_stack (void)
+{
+ yy_size_t num_to_alloc;
+
+ if (!(yy_buffer_stack)) {
+
+ /* First allocation is just for 2 elements, since we don't know if this
+ * scanner will even need a stack. We use 2 instead of 1 to avoid an
+ * immediate realloc on the next call.
+ */
+ num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */
+ (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc
+ (num_to_alloc * sizeof(struct yy_buffer_state*)
+ );
+ if ( ! (yy_buffer_stack) )
+ YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+
+ memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*));
+
+ (yy_buffer_stack_max) = num_to_alloc;
+ (yy_buffer_stack_top) = 0;
+ return;
+ }
+
+ if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){
+
+ /* Increase the buffer to prepare for a possible push. */
+ yy_size_t grow_size = 8 /* arbitrary grow size */;
+
+ num_to_alloc = (yy_buffer_stack_max) + grow_size;
+ (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc
+ ((yy_buffer_stack),
+ num_to_alloc * sizeof(struct yy_buffer_state*)
+ );
+ if ( ! (yy_buffer_stack) )
+ YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+
+ /* zero only the new slots.*/
+ memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*));
+ (yy_buffer_stack_max) = num_to_alloc;
+ }
+}
+
+/** Setup the input buffer state to scan directly from a user-specified character buffer.
+ * @param base the character buffer
+ * @param size the size in bytes of the character buffer
+ *
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size )
+{
+ YY_BUFFER_STATE b;
+
+ if ( size < 2 ||
+ base[size-2] != YY_END_OF_BUFFER_CHAR ||
+ base[size-1] != YY_END_OF_BUFFER_CHAR )
+ /* They forgot to leave room for the EOB's. */
+ return NULL;
+
+ b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" );
+
+ b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */
+ b->yy_buf_pos = b->yy_ch_buf = base;
+ b->yy_is_our_buffer = 0;
+ b->yy_input_file = NULL;
+ b->yy_n_chars = b->yy_buf_size;
+ b->yy_is_interactive = 0;
+ b->yy_at_bol = 1;
+ b->yy_fill_buffer = 0;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ yy_switch_to_buffer( b );
+
+ return b;
+}
+
+/** Setup the input buffer state to scan a string. The next call to yylex() will
+ * scan from a @e copy of @a str.
+ * @param yystr a NUL-terminated string to scan
+ *
+ * @return the newly allocated buffer state object.
+ * @note If you want to scan bytes that may contain NUL values, then use
+ * yy_scan_bytes() instead.
+ */
+YY_BUFFER_STATE yy_scan_string (const char * yystr )
+{
+
+ return yy_scan_bytes( yystr, (int) strlen(yystr) );
+}
+
+/** Setup the input buffer state to scan the given bytes. The next call to yylex() will
+ * scan from a @e copy of @a bytes.
+ * @param yybytes the byte buffer to scan
+ * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes.
+ *
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len )
+{
+ YY_BUFFER_STATE b;
+ char *buf;
+ yy_size_t n;
+ int i;
+
+ /* Get memory for full buffer, including space for trailing EOB's. */
+ n = (yy_size_t) (_yybytes_len + 2);
+ buf = (char *) yyalloc( n );
+ if ( ! buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
+
+ for ( i = 0; i < _yybytes_len; ++i )
+ buf[i] = yybytes[i];
+
+ buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR;
+
+ b = yy_scan_buffer( buf, n );
+ if ( ! b )
+ YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" );
+
+ /* It's okay to grow etc. this buffer, and we should throw it
+ * away when we're done.
+ */
+ b->yy_is_our_buffer = 1;
+
+ return b;
+}
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+static void yynoreturn yy_fatal_error (const char* msg )
+{
+ fprintf( stderr, "%s\n", msg );
+ exit( YY_EXIT_FAILURE );
+}
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ int yyless_macro_arg = (n); \
+ YY_LESS_LINENO(yyless_macro_arg);\
+ yytext[yyleng] = (yy_hold_char); \
+ (yy_c_buf_p) = yytext + yyless_macro_arg; \
+ (yy_hold_char) = *(yy_c_buf_p); \
+ *(yy_c_buf_p) = '\0'; \
+ yyleng = yyless_macro_arg; \
+ } \
+ while ( 0 )
+
+/* Accessor methods (get/set functions) to struct members. */
+
+/** Get the current line number.
+ *
+ */
+int yyget_lineno (void)
+{
+
+ return yylineno;
+}
+
+/** Get the input stream.
+ *
+ */
+FILE *yyget_in (void)
+{
+ return yyin;
+}
+
+/** Get the output stream.
+ *
+ */
+FILE *yyget_out (void)
+{
+ return yyout;
+}
+
+/** Get the length of the current token.
+ *
+ */
+int yyget_leng (void)
+{
+ return yyleng;
+}
+
+/** Get the current token.
+ *
+ */
+
+char *yyget_text (void)
+{
+ return yytext;
+}
+
+/** Set the current line number.
+ * @param _line_number line number
+ *
+ */
+void yyset_lineno (int _line_number )
+{
+
+ yylineno = _line_number;
+}
+
+/** Set the input stream. This does not discard the current
+ * input buffer.
+ * @param _in_str A readable stream.
+ *
+ * @see yy_switch_to_buffer
+ */
+void yyset_in (FILE * _in_str )
+{
+ yyin = _in_str ;
+}
+
+void yyset_out (FILE * _out_str )
+{
+ yyout = _out_str ;
+}
+
+int yyget_debug (void)
+{
+ return yy_flex_debug;
+}
+
+void yyset_debug (int _bdebug )
+{
+ yy_flex_debug = _bdebug ;
+}
+
+static int yy_init_globals (void)
+{
+ /* Initialization is the same as for the non-reentrant scanner.
+ * This function is called from yylex_destroy(), so don't allocate here.
+ */
+
+ (yy_buffer_stack) = NULL;
+ (yy_buffer_stack_top) = 0;
+ (yy_buffer_stack_max) = 0;
+ (yy_c_buf_p) = NULL;
+ (yy_init) = 0;
+ (yy_start) = 0;
+
+/* Defined in main.c */
+#ifdef YY_STDINIT
+ yyin = stdin;
+ yyout = stdout;
+#else
+ yyin = NULL;
+ yyout = NULL;
+#endif
+
+ /* For future reference: Set errno on error, since we are called by
+ * yylex_init()
+ */
+ return 0;
+}
+
+/* yylex_destroy is for both reentrant and non-reentrant scanners. */
+int yylex_destroy (void)
+{
+
+ /* Pop the buffer stack, destroying each element. */
+ while(YY_CURRENT_BUFFER){
+ yy_delete_buffer( YY_CURRENT_BUFFER );
+ YY_CURRENT_BUFFER_LVALUE = NULL;
+ yypop_buffer_state();
+ }
+
+ /* Destroy the stack itself. */
+ yyfree((yy_buffer_stack) );
+ (yy_buffer_stack) = NULL;
+
+ /* Reset the globals. This is important in a non-reentrant scanner so the next time
+ * yylex() is called, initialization will occur. */
+ yy_init_globals( );
+
+ return 0;
+}
+
+/*
+ * Internal utility routines.
+ */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char* s1, const char * s2, int n )
+{
+
+ int i;
+ for ( i = 0; i < n; ++i )
+ s1[i] = s2[i];
+}
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (const char * s )
+{
+ int n;
+ for ( n = 0; s[n]; ++n )
+ ;
+
+ return n;
+}
+#endif
+
+void *yyalloc (yy_size_t size )
+{
+ return malloc(size);
+}
+
+void *yyrealloc (void * ptr, yy_size_t size )
+{
+
+ /* The cast to (char *) in the following accommodates both
+ * implementations that use char* generic pointers, and those
+ * that use void* generic pointers. It works with the latter
+ * because both ANSI C and C++ allow castless assignment from
+ * any pointer type to void*, and deal with argument conversions
+ * as though doing an assignment.
+ */
+ return realloc(ptr, size);
+}
+
+void yyfree (void * ptr )
+{
+ free( (char *) ptr ); /* see yyrealloc() for (char *) cast */
+}
+
+#define YYTABLES_NAME "yytables"
+
+#line 136 "specscanner.l"
+
+
+/* LCOV_EXCL_STOP */
+
+static void
+addlitchar(char c)
+{
+ /* We must always leave room to add a trailing \0 */
+ if (litbufpos >= litbufsize - 1)
+ {
+ /* Double the size of litbuf if it gets full */
+ litbufsize += litbufsize;
+ litbuf = pg_realloc(litbuf, litbufsize);
+ }
+ litbuf[litbufpos++] = c;
+}
+
+void
+yyerror(const char *message)
+{
+ fprintf(stderr, "%s at line %d\n", message, yyline);
+ exit(1);
+}
+
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
new file mode 100644
index 0000000..d9fa6a5
--- /dev/null
+++ b/src/test/isolation/specscanner.l
@@ -0,0 +1,157 @@
+%{
+/*-------------------------------------------------------------------------
+ *
+ * specscanner.l
+ * a lexical scanner for an isolation test specification
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *-------------------------------------------------------------------------
+ */
+
+static int yyline = 1; /* line number for error reporting */
+
+#define LITBUF_INIT 1024 /* initial size of litbuf */
+static char *litbuf = NULL;
+static size_t litbufsize = 0;
+static size_t litbufpos = 0;
+
+static void addlitchar(char c);
+
+/* LCOV_EXCL_START */
+
+%}
+
+%option 8bit
+%option never-interactive
+%option nodefault
+%option noinput
+%option nounput
+%option noyywrap
+%option warn
+%option prefix="spec_yy"
+
+
+%x sql
+%x qident
+
+non_newline [^\n\r]
+space [ \t\r\f]
+
+comment ("#"{non_newline}*)
+
+digit [0-9]
+ident_start [A-Za-z\200-\377_]
+ident_cont [A-Za-z\200-\377_0-9\$]
+
+identifier {ident_start}{ident_cont}*
+
+self [,()*]
+
+%%
+
+%{
+ /* Allocate litbuf in first call of yylex() */
+ if (litbuf == NULL)
+ {
+ litbuf = pg_malloc(LITBUF_INIT);
+ litbufsize = LITBUF_INIT;
+ }
+%}
+
+ /* Keywords (must appear before the {identifier} rule!) */
+notices { return NOTICES; }
+permutation { return PERMUTATION; }
+session { return SESSION; }
+setup { return SETUP; }
+step { return STEP; }
+teardown { return TEARDOWN; }
+
+ /* Whitespace and comments */
+[\n] { yyline++; }
+{comment} { /* ignore */ }
+{space} { /* ignore */ }
+
+ /* Plain identifiers */
+{identifier} {
+ yylval.str = pg_strdup(yytext);
+ return(identifier);
+ }
+
+ /* Quoted identifiers: "foo" */
+\" {
+ litbufpos = 0;
+ BEGIN(qident);
+ }
+<qident>\"\" { addlitchar(yytext[0]); }
+<qident>\" {
+ litbuf[litbufpos] = '\0';
+ yylval.str = pg_strdup(litbuf);
+ BEGIN(INITIAL);
+ return(identifier);
+ }
+<qident>. { addlitchar(yytext[0]); }
+<qident>\n { yyerror("unexpected newline in quoted identifier"); }
+<qident><<EOF>> { yyerror("unterminated quoted identifier"); }
+
+ /* SQL blocks: { UPDATE ... } */
+ /* We trim leading/trailing whitespace, otherwise they're unprocessed */
+"{"{space}* {
+
+ litbufpos = 0;
+ BEGIN(sql);
+ }
+<sql>{space}*"}" {
+ litbuf[litbufpos] = '\0';
+ yylval.str = pg_strdup(litbuf);
+ BEGIN(INITIAL);
+ return(sqlblock);
+ }
+<sql>. {
+ addlitchar(yytext[0]);
+ }
+<sql>\n {
+ yyline++;
+ addlitchar(yytext[0]);
+ }
+<sql><<EOF>> {
+ yyerror("unterminated sql block");
+ }
+
+ /* Numbers and punctuation */
+{digit}+ {
+ yylval.integer = atoi(yytext);
+ return INTEGER;
+ }
+
+{self} { return yytext[0]; }
+
+ /* Anything else is an error */
+. {
+ fprintf(stderr, "syntax error at line %d: unexpected character \"%s\"\n", yyline, yytext);
+ exit(1);
+ }
+%%
+
+/* LCOV_EXCL_STOP */
+
+static void
+addlitchar(char c)
+{
+ /* We must always leave room to add a trailing \0 */
+ if (litbufpos >= litbufsize - 1)
+ {
+ /* Double the size of litbuf if it gets full */
+ litbufsize += litbufsize;
+ litbuf = pg_realloc(litbuf, litbufsize);
+ }
+ litbuf[litbufpos++] = c;
+}
+
+void
+yyerror(const char *message)
+{
+ fprintf(stderr, "%s at line %d\n", message, yyline);
+ exit(1);
+}
diff --git a/src/test/kerberos/.gitignore b/src/test/kerberos/.gitignore
new file mode 100644
index 0000000..871e943
--- /dev/null
+++ b/src/test/kerberos/.gitignore
@@ -0,0 +1,2 @@
+# Generated by test suite
+/tmp_check/
diff --git a/src/test/kerberos/Makefile b/src/test/kerberos/Makefile
new file mode 100644
index 0000000..e34e531
--- /dev/null
+++ b/src/test/kerberos/Makefile
@@ -0,0 +1,25 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/kerberos
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/kerberos/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/kerberos
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+export with_gssapi with_krb_srvnam
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
diff --git a/src/test/kerberos/README b/src/test/kerberos/README
new file mode 100644
index 0000000..fa9c03e
--- /dev/null
+++ b/src/test/kerberos/README
@@ -0,0 +1,45 @@
+src/test/kerberos/README
+
+Tests for Kerberos/GSSAPI functionality
+=======================================
+
+This directory contains a test suite for Kerberos/GSSAPI
+functionality. This requires a full MIT Kerberos installation,
+including server and client tools, and is therefore kept separate and
+not run by default.
+
+CAUTION: The test server run by this test is configured to listen for TCP
+connections on localhost. Any user on the same host is able to log in to the
+test server while the tests are running. Do not run this suite on a multi-user
+system where you don't trust all local users! Also, this test suite creates a
+KDC server that listens for TCP/IP connections on localhost without any real
+access control.
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+
+Run
+ make check
+or
+ make installcheck
+You can use "make installcheck" if you previously did "make install".
+In that case, the code in the installation tree is tested. With
+"make check", a temporary installation tree is built from the current
+sources and then tested.
+
+Either way, this test initializes, starts, and stops a test Postgres
+cluster, as well as a test KDC server.
+
+Requirements
+============
+
+MIT Kerberos server and client tools are required. Heimdal is not
+supported.
+
+Debian/Ubuntu packages: krb5-admin-server krb5-kdc krb5-user
+
+RHEL/CentOS/Fedora packages: krb5-server krb5-workstation
+
+FreeBSD port: krb5 (base system has Heimdal)
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
new file mode 100644
index 0000000..1b8ec8e
--- /dev/null
+++ b/src/test/kerberos/t/001_auth.pl
@@ -0,0 +1,401 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Sets up a KDC and then runs a variety of tests to make sure that the
+# GSSAPI/Kerberos authentication and encryption are working properly,
+# that the options in pg_hba.conf and pg_ident.conf are handled correctly,
+# and that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test.
+#
+# Since this requires setting up a full KDC, it doesn't make much sense
+# to have multiple test scripts (since they'd have to also create their
+# own KDC and that could cause race conditions or other problems)- so
+# just add whatever other tests are needed to here.
+#
+# See the README for additional information.
+
+use strict;
+use warnings;
+use TestLib;
+use PostgresNode;
+use Test::More;
+use Time::HiRes qw(usleep);
+
+if ($ENV{with_gssapi} eq 'yes')
+{
+ plan tests => 44;
+}
+else
+{
+ plan skip_all => 'GSSAPI/Kerberos not supported by this build';
+}
+
+my ($krb5_bin_dir, $krb5_sbin_dir);
+
+if ($^O eq 'darwin')
+{
+ $krb5_bin_dir = '/usr/local/opt/krb5/bin';
+ $krb5_sbin_dir = '/usr/local/opt/krb5/sbin';
+}
+elsif ($^O eq 'freebsd')
+{
+ $krb5_bin_dir = '/usr/local/bin';
+ $krb5_sbin_dir = '/usr/local/sbin';
+}
+elsif ($^O eq 'linux')
+{
+ $krb5_sbin_dir = '/usr/sbin';
+}
+
+my $krb5_config = 'krb5-config';
+my $kinit = 'kinit';
+my $kdb5_util = 'kdb5_util';
+my $kadmin_local = 'kadmin.local';
+my $krb5kdc = 'krb5kdc';
+
+if ($krb5_bin_dir && -d $krb5_bin_dir)
+{
+ $krb5_config = $krb5_bin_dir . '/' . $krb5_config;
+ $kinit = $krb5_bin_dir . '/' . $kinit;
+}
+if ($krb5_sbin_dir && -d $krb5_sbin_dir)
+{
+ $kdb5_util = $krb5_sbin_dir . '/' . $kdb5_util;
+ $kadmin_local = $krb5_sbin_dir . '/' . $kadmin_local;
+ $krb5kdc = $krb5_sbin_dir . '/' . $krb5kdc;
+}
+
+my $host = 'auth-test-localhost.postgresql.example.com';
+my $hostaddr = '127.0.0.1';
+my $realm = 'EXAMPLE.COM';
+
+my $krb5_conf = "${TestLib::tmp_check}/krb5.conf";
+my $kdc_conf = "${TestLib::tmp_check}/kdc.conf";
+my $krb5_cache = "${TestLib::tmp_check}/krb5cc";
+my $krb5_log = "${TestLib::log_path}/krb5libs.log";
+my $kdc_log = "${TestLib::log_path}/krb5kdc.log";
+my $kdc_port = get_free_port();
+my $kdc_datadir = "${TestLib::tmp_check}/krb5kdc";
+my $kdc_pidfile = "${TestLib::tmp_check}/krb5kdc.pid";
+my $keytab = "${TestLib::tmp_check}/krb5.keytab";
+
+my $dbname = 'postgres';
+my $username = 'test1';
+my $application = '001_auth.pl';
+
+note "setting up Kerberos";
+
+my ($stdout, $krb5_version);
+run_log [ $krb5_config, '--version' ], '>', \$stdout
+ or BAIL_OUT("could not execute krb5-config");
+BAIL_OUT("Heimdal is not supported") if $stdout =~ m/heimdal/;
+$stdout =~ m/Kerberos 5 release ([0-9]+\.[0-9]+)/
+ or BAIL_OUT("could not get Kerberos version");
+$krb5_version = $1;
+
+append_to_file(
+ $krb5_conf,
+ qq![logging]
+default = FILE:$krb5_log
+kdc = FILE:$kdc_log
+
+[libdefaults]
+default_realm = $realm
+
+[realms]
+$realm = {
+ kdc = $hostaddr:$kdc_port
+}!);
+
+append_to_file(
+ $kdc_conf,
+ qq![kdcdefaults]
+!);
+
+# For new-enough versions of krb5, use the _listen settings rather
+# than the _ports settings so that we can bind to localhost only.
+if ($krb5_version >= 1.15)
+{
+ append_to_file(
+ $kdc_conf,
+ qq!kdc_listen = $hostaddr:$kdc_port
+kdc_tcp_listen = $hostaddr:$kdc_port
+!);
+}
+else
+{
+ append_to_file(
+ $kdc_conf,
+ qq!kdc_ports = $kdc_port
+kdc_tcp_ports = $kdc_port
+!);
+}
+append_to_file(
+ $kdc_conf,
+ qq!
+[realms]
+$realm = {
+ database_name = $kdc_datadir/principal
+ admin_keytab = FILE:$kdc_datadir/kadm5.keytab
+ acl_file = $kdc_datadir/kadm5.acl
+ key_stash_file = $kdc_datadir/_k5.$realm
+}!);
+
+mkdir $kdc_datadir or die;
+
+# Ensure that we use test's config and cache files, not global ones.
+$ENV{'KRB5_CONFIG'} = $krb5_conf;
+$ENV{'KRB5_KDC_PROFILE'} = $kdc_conf;
+$ENV{'KRB5CCNAME'} = $krb5_cache;
+
+my $service_principal = "$ENV{with_krb_srvnam}/$host";
+
+system_or_bail $kdb5_util, 'create', '-s', '-P', 'secret0';
+
+my $test1_password = 'secret1';
+system_or_bail $kadmin_local, '-q', "addprinc -pw $test1_password test1";
+
+system_or_bail $kadmin_local, '-q', "addprinc -randkey $service_principal";
+system_or_bail $kadmin_local, '-q', "ktadd -k $keytab $service_principal";
+
+system_or_bail $krb5kdc, '-P', $kdc_pidfile;
+
+END
+{
+ kill 'INT', `cat $kdc_pidfile` if -f $kdc_pidfile;
+}
+
+note "setting up PostgreSQL instance";
+
+my $node = get_new_node('node');
+$node->init;
+$node->append_conf(
+ 'postgresql.conf', qq{
+listen_addresses = '$hostaddr'
+krb_server_keyfile = '$keytab'
+log_connections = on
+lc_messages = 'C'
+});
+$node->start;
+
+$node->safe_psql('postgres', 'CREATE USER test1;');
+
+note "running tests";
+
+# Test connection success or failure, and if success, that query returns true.
+sub test_access
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($node, $role, $query, $expected_res, $gssencmode, $test_name,
+ @expect_log_msgs)
+ = @_;
+
+ # need to connect over TCP/IP for Kerberos
+ my $connstr = $node->connstr('postgres')
+ . " user=$role host=$host hostaddr=$hostaddr $gssencmode";
+
+ my %params = (sql => $query,);
+
+ if (@expect_log_msgs)
+ {
+ # Match every message literally.
+ my @regexes = map { qr/\Q$_\E/ } @expect_log_msgs;
+
+ $params{log_like} = \@regexes;
+ }
+
+ if ($expected_res eq 0)
+ {
+ # The result is assumed to match "true", or "t", here.
+ $params{expected_stdout} = qr/^t$/;
+
+ $node->connect_ok($connstr, $test_name, %params);
+ }
+ else
+ {
+ $node->connect_fails($connstr, $test_name, %params);
+ }
+}
+
+# As above, but test for an arbitrary query result.
+sub test_query
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($node, $role, $query, $expected, $gssencmode, $test_name) = @_;
+
+ # need to connect over TCP/IP for Kerberos
+ my $connstr = $node->connstr('postgres')
+ . " user=$role host=$host hostaddr=$hostaddr $gssencmode";
+
+ $node->connect_ok(
+ $connstr, $test_name,
+ sql => $query,
+ expected_stdout => $expected);
+ return;
+}
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{host all all $hostaddr/32 gss map=mymap});
+$node->restart;
+
+test_access($node, 'test1', 'SELECT true', 2, '', 'fails without ticket');
+
+run_log [ $kinit, 'test1' ], \$test1_password or BAIL_OUT($?);
+
+test_access(
+ $node,
+ 'test1',
+ 'SELECT true',
+ 2,
+ '',
+ 'fails without mapping',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
+ "no match in usermap \"mymap\" for user \"test1\"");
+
+$node->append_conf('pg_ident.conf', qq{mymap /^(.*)\@$realm\$ \\1});
+$node->restart;
+
+test_access(
+ $node,
+ 'test1',
+ 'SELECT gss_authenticated AND encrypted from pg_stat_gssapi where pid = pg_backend_pid();',
+ 0,
+ '',
+ 'succeeds with mapping with default gssencmode and host hba',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
+ "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)"
+);
+
+test_access(
+ $node,
+ 'test1',
+ 'SELECT gss_authenticated AND encrypted from pg_stat_gssapi where pid = pg_backend_pid();',
+ 0,
+ 'gssencmode=prefer',
+ 'succeeds with GSS-encrypted access preferred with host hba',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
+ "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)"
+);
+test_access(
+ $node,
+ 'test1',
+ 'SELECT gss_authenticated AND encrypted from pg_stat_gssapi where pid = pg_backend_pid();',
+ 0,
+ 'gssencmode=require',
+ 'succeeds with GSS-encrypted access required with host hba',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
+ "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)"
+);
+
+# Test that we can transport a reasonable amount of data.
+test_query(
+ $node,
+ 'test1',
+ 'SELECT * FROM generate_series(1, 100000);',
+ qr/^1\n.*\n1024\n.*\n9999\n.*\n100000$/s,
+ 'gssencmode=require',
+ 'receiving 100K lines works');
+
+test_query(
+ $node,
+ 'test1',
+ "CREATE TABLE mytab (f1 int primary key);\n"
+ . "COPY mytab FROM STDIN;\n"
+ . join("\n", (1 .. 100000))
+ . "\n\\.\n"
+ . "SELECT COUNT(*) FROM mytab;",
+ qr/^100000$/s,
+ 'gssencmode=require',
+ 'sending 100K lines works');
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{hostgssenc all all $hostaddr/32 gss map=mymap});
+$node->restart;
+
+test_access(
+ $node,
+ 'test1',
+ 'SELECT gss_authenticated AND encrypted from pg_stat_gssapi where pid = pg_backend_pid();',
+ 0,
+ 'gssencmode=prefer',
+ 'succeeds with GSS-encrypted access preferred and hostgssenc hba',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
+ "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)"
+);
+test_access(
+ $node,
+ 'test1',
+ 'SELECT gss_authenticated AND encrypted from pg_stat_gssapi where pid = pg_backend_pid();',
+ 0,
+ 'gssencmode=require',
+ 'succeeds with GSS-encrypted access required and hostgssenc hba',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
+ "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)"
+);
+test_access($node, 'test1', 'SELECT true', 2, 'gssencmode=disable',
+ 'fails with GSS encryption disabled and hostgssenc hba');
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{hostnogssenc all all $hostaddr/32 gss map=mymap});
+$node->restart;
+
+test_access(
+ $node,
+ 'test1',
+ 'SELECT gss_authenticated and not encrypted from pg_stat_gssapi where pid = pg_backend_pid();',
+ 0,
+ 'gssencmode=prefer',
+ 'succeeds with GSS-encrypted access preferred and hostnogssenc hba, but no encryption',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
+ "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=no, principal=test1\@$realm)"
+);
+test_access($node, 'test1', 'SELECT true', 2, 'gssencmode=require',
+ 'fails with GSS-encrypted access required and hostnogssenc hba');
+test_access(
+ $node,
+ 'test1',
+ 'SELECT gss_authenticated and not encrypted from pg_stat_gssapi where pid = pg_backend_pid();',
+ 0,
+ 'gssencmode=disable',
+ 'succeeds with GSS encryption disabled and hostnogssenc hba',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
+ "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=no, principal=test1\@$realm)"
+);
+
+truncate($node->data_dir . '/pg_ident.conf', 0);
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{host all all $hostaddr/32 gss include_realm=0});
+$node->restart;
+
+test_access(
+ $node,
+ 'test1',
+ 'SELECT gss_authenticated AND encrypted from pg_stat_gssapi where pid = pg_backend_pid();',
+ 0,
+ '',
+ 'succeeds with include_realm=0 and defaults',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
+ "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)"
+);
+
+# Reset pg_hba.conf, and cause a usermap failure with an authentication
+# that has passed.
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{host all all $hostaddr/32 gss include_realm=0 krb_realm=EXAMPLE.ORG});
+$node->restart;
+
+test_access(
+ $node,
+ 'test1',
+ 'SELECT true',
+ 2,
+ '',
+ 'fails with wrong krb_realm, but still authenticates',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss");
diff --git a/src/test/ldap/.gitignore b/src/test/ldap/.gitignore
new file mode 100644
index 0000000..871e943
--- /dev/null
+++ b/src/test/ldap/.gitignore
@@ -0,0 +1,2 @@
+# Generated by test suite
+/tmp_check/
diff --git a/src/test/ldap/Makefile b/src/test/ldap/Makefile
new file mode 100644
index 0000000..ed13574
--- /dev/null
+++ b/src/test/ldap/Makefile
@@ -0,0 +1,25 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/ldap
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/ldap/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/ldap
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+export with_ldap
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
diff --git a/src/test/ldap/README b/src/test/ldap/README
new file mode 100644
index 0000000..826b153
--- /dev/null
+++ b/src/test/ldap/README
@@ -0,0 +1,52 @@
+src/test/ldap/README
+
+Tests for LDAP functionality
+============================
+
+This directory contains a test suite for LDAP functionality. This
+requires a full OpenLDAP installation, including server and client
+tools, and is therefore kept separate and not run by default. You
+might need to adjust some paths in the test file to have it find
+OpenLDAP in a place that hadn't been thought of yet.
+
+Also, this test suite creates an LDAP server that listens for TCP/IP
+connections on localhost without any real access control, so it is not
+safe to run this on a system where there might be untrusted local
+users.
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+
+Run
+ make check
+or
+ make installcheck
+You can use "make installcheck" if you previously did "make install".
+In that case, the code in the installation tree is tested. With
+"make check", a temporary installation tree is built from the current
+sources and then tested.
+
+Either way, this test initializes, starts, and stops a test Postgres
+cluster, as well as a test LDAP server.
+
+Requirements
+============
+
+LDAP server and client tools are required.
+
+Debian/Ubuntu packages: slapd ldap-utils
+
+RHEL/CentOS/Fedora packages: openldap-clients openldap-servers
+(You will already have needed openldap and openldap-devel to build.)
+
+FreeBSD: openldap-server
+(You will already have needed openldap-client to build. If building
+from the ports source tree, you want to build net/openldap24-client
+and net/openldap24-server.)
+
+macOS: We do not recommend trying to use the Apple-provided version of
+OpenLDAP; it's very old, plus Apple seem to have changed the launching
+conventions for slapd. The paths in the test file are set on the
+assumption that you installed OpenLDAP using Homebrew or MacPorts.
diff --git a/src/test/ldap/authdata.ldif b/src/test/ldap/authdata.ldif
new file mode 100644
index 0000000..c0a15da
--- /dev/null
+++ b/src/test/ldap/authdata.ldif
@@ -0,0 +1,32 @@
+dn: dc=example,dc=net
+objectClass: top
+objectClass: dcObject
+objectClass: organization
+dc: example
+o: ExampleCo
+
+dn: uid=test1,dc=example,dc=net
+objectClass: inetOrgPerson
+objectClass: posixAccount
+uid: test1
+sn: Lastname
+givenName: Firstname
+cn: First Test User
+displayName: First Test User
+uidNumber: 101
+gidNumber: 100
+homeDirectory: /home/test1
+mail: test1@example.net
+
+dn: uid=test2,dc=example,dc=net
+objectClass: inetOrgPerson
+objectClass: posixAccount
+uid: test2
+sn: Lastname
+givenName: Firstname
+cn: Second Test User
+displayName: Second Test User
+uidNumber: 102
+gidNumber: 100
+homeDirectory: /home/test2
+mail: test2@example.net
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
new file mode 100644
index 0000000..0a310cc
--- /dev/null
+++ b/src/test/ldap/t/001_auth.pl
@@ -0,0 +1,369 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use TestLib;
+use PostgresNode;
+use Test::More;
+
+if ($ENV{with_ldap} eq 'yes')
+{
+ plan tests => 28;
+}
+else
+{
+ plan skip_all => 'LDAP not supported by this build';
+}
+
+my ($slapd, $ldap_bin_dir, $ldap_schema_dir);
+
+$ldap_bin_dir = undef; # usually in PATH
+
+if ($^O eq 'darwin' && -d '/usr/local/opt/openldap')
+{
+ # typical paths for Homebrew
+ $slapd = '/usr/local/opt/openldap/libexec/slapd';
+ $ldap_schema_dir = '/usr/local/etc/openldap/schema';
+}
+elsif ($^O eq 'darwin' && -d '/opt/local/etc/openldap')
+{
+ # typical paths for MacPorts
+ $slapd = '/opt/local/libexec/slapd';
+ $ldap_schema_dir = '/opt/local/etc/openldap/schema';
+}
+elsif ($^O eq 'linux')
+{
+ $slapd = '/usr/sbin/slapd';
+ $ldap_schema_dir = '/etc/ldap/schema' if -d '/etc/ldap/schema';
+ $ldap_schema_dir = '/etc/openldap/schema' if -d '/etc/openldap/schema';
+}
+elsif ($^O eq 'freebsd')
+{
+ $slapd = '/usr/local/libexec/slapd';
+ $ldap_schema_dir = '/usr/local/etc/openldap/schema';
+}
+
+# make your own edits here
+#$slapd = '';
+#$ldap_bin_dir = '';
+#$ldap_schema_dir = '';
+
+$ENV{PATH} = "$ldap_bin_dir:$ENV{PATH}" if $ldap_bin_dir;
+
+my $ldap_datadir = "${TestLib::tmp_check}/openldap-data";
+my $slapd_certs = "${TestLib::tmp_check}/slapd-certs";
+my $slapd_conf = "${TestLib::tmp_check}/slapd.conf";
+my $slapd_pidfile = "${TestLib::tmp_check}/slapd.pid";
+my $slapd_logfile = "${TestLib::log_path}/slapd.log";
+my $ldap_conf = "${TestLib::tmp_check}/ldap.conf";
+my $ldap_server = 'localhost';
+my $ldap_port = get_free_port();
+my $ldaps_port = get_free_port();
+my $ldap_url = "ldap://$ldap_server:$ldap_port";
+my $ldaps_url = "ldaps://$ldap_server:$ldaps_port";
+my $ldap_basedn = 'dc=example,dc=net';
+my $ldap_rootdn = 'cn=Manager,dc=example,dc=net';
+my $ldap_rootpw = 'secret';
+my $ldap_pwfile = "${TestLib::tmp_check}/ldappassword";
+
+note "setting up slapd";
+
+append_to_file(
+ $slapd_conf,
+ qq{include $ldap_schema_dir/core.schema
+include $ldap_schema_dir/cosine.schema
+include $ldap_schema_dir/nis.schema
+include $ldap_schema_dir/inetorgperson.schema
+
+pidfile $slapd_pidfile
+logfile $slapd_logfile
+
+access to *
+ by * read
+ by anonymous auth
+
+database ldif
+directory $ldap_datadir
+
+TLSCACertificateFile $slapd_certs/ca.crt
+TLSCertificateFile $slapd_certs/server.crt
+TLSCertificateKeyFile $slapd_certs/server.key
+
+suffix "dc=example,dc=net"
+rootdn "$ldap_rootdn"
+rootpw $ldap_rootpw});
+
+# don't bother to check the server's cert (though perhaps we should)
+append_to_file(
+ $ldap_conf,
+ qq{TLS_REQCERT never
+});
+
+mkdir $ldap_datadir or die;
+mkdir $slapd_certs or die;
+
+system_or_bail "openssl", "req", "-new", "-nodes", "-keyout",
+ "$slapd_certs/ca.key", "-x509", "-out", "$slapd_certs/ca.crt", "-subj",
+ "/CN=CA";
+system_or_bail "openssl", "req", "-new", "-nodes", "-keyout",
+ "$slapd_certs/server.key", "-out", "$slapd_certs/server.csr", "-subj",
+ "/CN=server";
+system_or_bail "openssl", "x509", "-req", "-in", "$slapd_certs/server.csr",
+ "-CA", "$slapd_certs/ca.crt", "-CAkey", "$slapd_certs/ca.key",
+ "-CAcreateserial", "-out", "$slapd_certs/server.crt";
+
+system_or_bail $slapd, '-f', $slapd_conf, '-h', "$ldap_url $ldaps_url";
+
+END
+{
+ kill 'INT', `cat $slapd_pidfile` if -f $slapd_pidfile;
+}
+
+append_to_file($ldap_pwfile, $ldap_rootpw);
+chmod 0600, $ldap_pwfile or die;
+
+# wait until slapd accepts requests
+my $retries = 0;
+while (1)
+{
+ last
+ if (
+ system_log(
+ "ldapsearch", "-sbase",
+ "-H", $ldap_url,
+ "-b", $ldap_basedn,
+ "-D", $ldap_rootdn,
+ "-y", $ldap_pwfile,
+ "-n", "'objectclass=*'") == 0);
+ die "cannot connect to slapd" if ++$retries >= 300;
+ note "waiting for slapd to accept requests...";
+ Time::HiRes::usleep(1000000);
+}
+
+$ENV{'LDAPURI'} = $ldap_url;
+$ENV{'LDAPBINDDN'} = $ldap_rootdn;
+$ENV{'LDAPCONF'} = $ldap_conf;
+
+note "loading LDAP data";
+
+system_or_bail 'ldapadd', '-x', '-y', $ldap_pwfile, '-f', 'authdata.ldif';
+system_or_bail 'ldappasswd', '-x', '-y', $ldap_pwfile, '-s', 'secret1',
+ 'uid=test1,dc=example,dc=net';
+system_or_bail 'ldappasswd', '-x', '-y', $ldap_pwfile, '-s', 'secret2',
+ 'uid=test2,dc=example,dc=net';
+
+note "setting up PostgreSQL instance";
+
+my $node = get_new_node('node');
+$node->init;
+$node->append_conf('postgresql.conf', "log_connections = on\n");
+$node->start;
+
+$node->safe_psql('postgres', 'CREATE USER test0;');
+$node->safe_psql('postgres', 'CREATE USER test1;');
+$node->safe_psql('postgres', 'CREATE USER "test2@example.net";');
+
+note "running tests";
+
+sub test_access
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($node, $role, $expected_res, $test_name, %params) = @_;
+ my $connstr = "user=$role";
+
+ if ($expected_res eq 0)
+ {
+ $node->connect_ok($connstr, $test_name, %params);
+ }
+ else
+ {
+ # No checks of the error message, only the status code.
+ $node->connect_fails($connstr, $test_name, %params);
+ }
+}
+
+note "simple bind";
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapprefix="uid=" ldapsuffix=",dc=example,dc=net"}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'wrong';
+test_access(
+ $node, 'test0', 2,
+ 'simple bind authentication fails if user not found in LDAP',
+ log_unlike => [qr/connection authenticated:/]);
+test_access(
+ $node, 'test1', 2,
+ 'simple bind authentication fails with wrong password',
+ log_unlike => [qr/connection authenticated:/]);
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access(
+ $node, 'test1', 0,
+ 'simple bind authentication succeeds',
+ log_like => [
+ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/
+ ],);
+
+note "search+bind";
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn"}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'wrong';
+test_access($node, 'test0', 2,
+ 'search+bind authentication fails if user not found in LDAP');
+test_access($node, 'test1', 2,
+ 'search+bind authentication fails with wrong password');
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access(
+ $node, 'test1', 0,
+ 'search+bind authentication succeeds',
+ log_like => [
+ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/
+ ],);
+
+note "multiple servers";
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver="$ldap_server $ldap_server" ldapport=$ldap_port ldapbasedn="$ldap_basedn"}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'wrong';
+test_access($node, 'test0', 2,
+ 'search+bind authentication fails if user not found in LDAP');
+test_access($node, 'test1', 2,
+ 'search+bind authentication fails with wrong password');
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'search+bind authentication succeeds');
+
+note "LDAP URLs";
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapurl="$ldap_url/$ldap_basedn?uid?sub"});
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'wrong';
+test_access($node, 'test0', 2,
+ 'search+bind with LDAP URL authentication fails if user not found in LDAP'
+);
+test_access($node, 'test1', 2,
+ 'search+bind with LDAP URL authentication fails with wrong password');
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0,
+ 'search+bind with LDAP URL authentication succeeds');
+
+note "search filters";
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn" ldapsearchfilter="(|(uid=\$username)(mail=\$username))"}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access(
+ $node, 'test1', 0,
+ 'search filter finds by uid',
+ log_like => [
+ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/
+ ],);
+$ENV{"PGPASSWORD"} = 'secret2';
+test_access(
+ $node,
+ 'test2@example.net',
+ 0,
+ 'search filter finds by mail',
+ log_like => [
+ qr/connection authenticated: identity="uid=test2,dc=example,dc=net" method=ldap/
+ ],);
+
+note "search filters in LDAP URLs";
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapurl="$ldap_url/$ldap_basedn??sub?(|(uid=\$username)(mail=\$username))"}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'search filter finds by uid');
+$ENV{"PGPASSWORD"} = 'secret2';
+test_access($node, 'test2@example.net', 0, 'search filter finds by mail');
+
+# This is not documented: You can combine ldapurl and other ldap*
+# settings. ldapurl is always parsed first, then the other settings
+# override. It might be useful in a case like this.
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapurl="$ldap_url/$ldap_basedn??sub" ldapsearchfilter="(|(uid=\$username)(mail=\$username))"}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'combined LDAP URL and search filter');
+
+note "diagnostic message";
+
+# note bad ldapprefix with a question mark that triggers a diagnostic message
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapprefix="?uid=" ldapsuffix=""}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 2, 'any attempt fails due to bad search pattern');
+
+note "TLS";
+
+# request StartTLS with ldaptls=1
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn" ldapsearchfilter="(uid=\$username)" ldaptls=1}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'StartTLS');
+
+# request LDAPS with ldapscheme=ldaps
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver=$ldap_server ldapscheme=ldaps ldapport=$ldaps_port ldapbasedn="$ldap_basedn" ldapsearchfilter="(uid=\$username)"}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'LDAPS');
+
+# request LDAPS with ldapurl=ldaps://...
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapurl="$ldaps_url/$ldap_basedn??sub?(uid=\$username)"}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'LDAPS with URL');
+
+# bad combination of LDAPS and StartTLS
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapurl="$ldaps_url/$ldap_basedn??sub?(uid=\$username)" ldaptls=1}
+);
+$node->restart;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 2, 'bad combination of LDAPS and StartTLS');
diff --git a/src/test/locale/.gitignore b/src/test/locale/.gitignore
new file mode 100644
index 0000000..620d3df
--- /dev/null
+++ b/src/test/locale/.gitignore
@@ -0,0 +1 @@
+/test-ctype
diff --git a/src/test/locale/Makefile b/src/test/locale/Makefile
new file mode 100644
index 0000000..7ba096b
--- /dev/null
+++ b/src/test/locale/Makefile
@@ -0,0 +1,22 @@
+# src/test/locale/Makefile
+
+subdir = src/test/locale
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+
+PROGS = test-ctype
+DIRS = de_DE.ISO8859-1 gr_GR.ISO8859-7 koi8-r koi8-to-win1251
+
+all: $(PROGS)
+
+clean distclean maintainer-clean:
+ rm -f $(PROGS) *.o
+ rm -rf tmp_check
+ for d in $(DIRS); do \
+ $(MAKE) -C $$d clean || exit; \
+ done
+
+# These behave like installcheck targets.
+check-%: all
+ @$(MAKE) -C `echo $@ | sed 's/^check-//'` test
diff --git a/src/test/locale/README b/src/test/locale/README
new file mode 100644
index 0000000..e290e31
--- /dev/null
+++ b/src/test/locale/README
@@ -0,0 +1,28 @@
+src/test/locale/README
+
+Locales
+=======
+
+This directory contains a set of tests for locales. I provided one C
+program test-ctype.c to test CTYPE support in libc and the installed
+locale data. Then there are test-sort.pl and test-sort.py that test
+collating.
+
+To run a test for some locale run
+ make check-$locale
+for example
+ make check-koi8-r
+
+Currently, there are only tests for a few locales available. The script
+'runall' calls test-ctype to test libc and locale data, test-sort.pl
+(uncomment test-sort.py, if you have a Python interpreter installed), and
+does tests on PostgreSQL with the provided SQL script files.
+
+To add locale tests one needs to create a directory $locale and create
+a Makefile (and other files) similar to koi8-r/*. Actually, the simplest
+(I think) method is just to copy the koi8-r directory and edit/replace
+the files.
+
+Oleg.
+----
+ Oleg Broytmann http://members.xoom.com/phd2/ phd2@earthling.net
diff --git a/src/test/locale/de_DE.ISO8859-1/Makefile b/src/test/locale/de_DE.ISO8859-1/Makefile
new file mode 100644
index 0000000..28a72b7
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/Makefile
@@ -0,0 +1,7 @@
+all:
+
+test:
+ ./runall
+
+clean:
+ rm -f *.out
diff --git a/src/test/locale/de_DE.ISO8859-1/README b/src/test/locale/de_DE.ISO8859-1/README
new file mode 100644
index 0000000..c9e6ee8
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/README
@@ -0,0 +1,4 @@
+src/test/locale/de_DE.ISO8859-1/README
+
+de_DE.ISO-8859-1 (German) locale test.
+Created by Armin Diehl <diehl@net-connection.de>
diff --git a/src/test/locale/de_DE.ISO8859-1/expected/de-ctype.out b/src/test/locale/de_DE.ISO8859-1/expected/de-ctype.out
new file mode 100644
index 0000000..49ec93a
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/expected/de-ctype.out
@@ -0,0 +1,257 @@
+char# char alnum alpha cntrl digit lower graph print punct space upper xdigit lo up
+chr#0 +
+chr#1 +
+chr#2 +
+chr#3 +
+chr#4 +
+chr#5 +
+chr#6 +
+chr#7 +
+chr#8 +
+chr#9 + +
+chr#10 + +
+chr#11 + +
+chr#12 + +
+chr#13 + +
+chr#14 +
+chr#15 +
+chr#16 +
+chr#17 +
+chr#18 +
+chr#19 +
+chr#20 +
+chr#21 +
+chr#22 +
+chr#23 +
+chr#24 +
+chr#25 +
+chr#26 +
+chr#27 +
+chr#28 +
+chr#29 +
+chr#30 +
+chr#31 +
+chr#32 + +
+chr#33 ! + + + ! !
+chr#34 " + + + " "
+chr#35 # + + + # #
+chr#36 $ + + + $ $
+chr#37 % + + + % %
+chr#38 & + + + & &
+chr#39 ' + + + ' '
+chr#40 ( + + + ( (
+chr#41 ) + + + ) )
+chr#42 * + + + * *
+chr#43 + + + + + +
+chr#44 , + + + , ,
+chr#45 - + + + - -
+chr#46 . + + + . .
+chr#47 / + + + / /
+chr#48 0 + + + + + 0 0
+chr#49 1 + + + + + 1 1
+chr#50 2 + + + + + 2 2
+chr#51 3 + + + + + 3 3
+chr#52 4 + + + + + 4 4
+chr#53 5 + + + + + 5 5
+chr#54 6 + + + + + 6 6
+chr#55 7 + + + + + 7 7
+chr#56 8 + + + + + 8 8
+chr#57 9 + + + + + 9 9
+chr#58 : + + + : :
+chr#59 ; + + + ; ;
+chr#60 < + + + < <
+chr#61 = + + + = =
+chr#62 > + + + > >
+chr#63 ? + + + ? ?
+chr#64 @ + + + @ @
+chr#65 A + + + + + + a A
+chr#66 B + + + + + + b B
+chr#67 C + + + + + + c C
+chr#68 D + + + + + + d D
+chr#69 E + + + + + + e E
+chr#70 F + + + + + + f F
+chr#71 G + + + + + g G
+chr#72 H + + + + + h H
+chr#73 I + + + + + i I
+chr#74 J + + + + + j J
+chr#75 K + + + + + k K
+chr#76 L + + + + + l L
+chr#77 M + + + + + m M
+chr#78 N + + + + + n N
+chr#79 O + + + + + o O
+chr#80 P + + + + + p P
+chr#81 Q + + + + + q Q
+chr#82 R + + + + + r R
+chr#83 S + + + + + s S
+chr#84 T + + + + + t T
+chr#85 U + + + + + u U
+chr#86 V + + + + + v V
+chr#87 W + + + + + w W
+chr#88 X + + + + + x X
+chr#89 Y + + + + + y Y
+chr#90 Z + + + + + z Z
+chr#91 [ + + + [ [
+chr#92 \ + + + \ \
+chr#93 ] + + + ] ]
+chr#94 ^ + + + ^ ^
+chr#95 _ + + + _ _
+chr#96 ` + + + ` `
+chr#97 a + + + + + + a A
+chr#98 b + + + + + + b B
+chr#99 c + + + + + + c C
+chr#100 d + + + + + + d D
+chr#101 e + + + + + + e E
+chr#102 f + + + + + + f F
+chr#103 g + + + + + g G
+chr#104 h + + + + + h H
+chr#105 i + + + + + i I
+chr#106 j + + + + + j J
+chr#107 k + + + + + k K
+chr#108 l + + + + + l L
+chr#109 m + + + + + m M
+chr#110 n + + + + + n N
+chr#111 o + + + + + o O
+chr#112 p + + + + + p P
+chr#113 q + + + + + q Q
+chr#114 r + + + + + r R
+chr#115 s + + + + + s S
+chr#116 t + + + + + t T
+chr#117 u + + + + + u U
+chr#118 v + + + + + v V
+chr#119 w + + + + + w W
+chr#120 x + + + + + x X
+chr#121 y + + + + + y Y
+chr#122 z + + + + + z Z
+chr#123 { + + + { {
+chr#124 | + + + | |
+chr#125 } + + + } }
+chr#126 ~ + + + ~ ~
+chr#127 +
+chr#128 +
+chr#129 +
+chr#130 +
+chr#131 +
+chr#132 +
+chr#133 +
+chr#134 +
+chr#135 +
+chr#136 +
+chr#137 +
+chr#138 +
+chr#139 +
+chr#140 +
+chr#141 +
+chr#142 +
+chr#143 +
+chr#144 +
+chr#145 +
+chr#146 +
+chr#147 +
+chr#148 +
+chr#149 +
+chr#150 +
+chr#151 +
+chr#152 +
+chr#153 +
+chr#154 +
+chr#155 +
+chr#156 +
+chr#157 +
+chr#158 +
+chr#159 +
+chr#160 +
+chr#161 ¡ + + + ¡ ¡
+chr#162 ¢ + + + ¢ ¢
+chr#163 £ + + + £ £
+chr#164 ¤ + + + ¤ ¤
+chr#165 ¥ + + + ¥ ¥
+chr#166 ¦ + + + ¦ ¦
+chr#167 § + + + § §
+chr#168 ¨ + + + ¨ ¨
+chr#169 © + + + © ©
+chr#170 ª + + + ª ª
+chr#171 « + + + « «
+chr#172 ¬ + + + ¬ ¬
+chr#173 ­ + + + + ­ ­
+chr#174 ® + + + ® ®
+chr#175 ¯ + + + ¯ ¯
+chr#176 ° + + + ° °
+chr#177 ± + + + ± ±
+chr#178 ² + + + ² ²
+chr#179 ³ + + + ³ ³
+chr#180 ´ + + + ´ ´
+chr#181 µ + + + µ µ
+chr#182 ¶ + + + ¶ ¶
+chr#183 · + + + · ·
+chr#184 ¸ + + + ¸ ¸
+chr#185 ¹ + + + ¹ ¹
+chr#186 º + + + º º
+chr#187 » + + + » »
+chr#188 ¼ + + + ¼ ¼
+chr#189 ½ + + + ½ ½
+chr#190 ¾ + + + ¾ ¾
+chr#191 ¿ + + + ¿ ¿
+chr#192 À + + + + + à À
+chr#193 Á + + + + + á Á
+chr#194 Â + + + + + â Â
+chr#195 Ã + + + + + ã Ã
+chr#196 Ä + + + + + ä Ä
+chr#197 Å + + + + + å Å
+chr#198 Æ + + + + + æ Æ
+chr#199 Ç + + + + + ç Ç
+chr#200 È + + + + + è È
+chr#201 É + + + + + é É
+chr#202 Ê + + + + + ê Ê
+chr#203 Ë + + + + + ë Ë
+chr#204 Ì + + + + + ì Ì
+chr#205 Í + + + + + í Í
+chr#206 Î + + + + + î Î
+chr#207 Ï + + + + + ï Ï
+chr#208 Ð + + + + + ð Ð
+chr#209 Ñ + + + + + ñ Ñ
+chr#210 Ò + + + + + ò Ò
+chr#211 Ó + + + + + ó Ó
+chr#212 Ô + + + + + ô Ô
+chr#213 Õ + + + + + õ Õ
+chr#214 Ö + + + + + ö Ö
+chr#215 × + + + × ×
+chr#216 Ø + + + + + ø Ø
+chr#217 Ù + + + + + ù Ù
+chr#218 Ú + + + + + ú Ú
+chr#219 Û + + + + + û Û
+chr#220 Ü + + + + + ü Ü
+chr#221 Ý + + + + + ý Ý
+chr#222 Þ + + + + + þ Þ
+chr#223 ß + + + + + ß ß
+chr#224 à + + + + + à À
+chr#225 á + + + + + á Á
+chr#226 â + + + + + â Â
+chr#227 ã + + + + + ã Ã
+chr#228 ä + + + + + ä Ä
+chr#229 å + + + + + å Å
+chr#230 æ + + + + + æ Æ
+chr#231 ç + + + + + ç Ç
+chr#232 è + + + + + è È
+chr#233 é + + + + + é É
+chr#234 ê + + + + + ê Ê
+chr#235 ë + + + + + ë Ë
+chr#236 ì + + + + + ì Ì
+chr#237 í + + + + + í Í
+chr#238 î + + + + + î Î
+chr#239 ï + + + + + ï Ï
+chr#240 ð + + + + + ð Ð
+chr#241 ñ + + + + + ñ Ñ
+chr#242 ò + + + + + ò Ò
+chr#243 ó + + + + + ó Ó
+chr#244 ô + + + + + ô Ô
+chr#245 õ + + + + + õ Õ
+chr#246 ö + + + + + ö Ö
+chr#247 ÷ + + + ÷ ÷
+chr#248 ø + + + + + ø Ø
+chr#249 ù + + + + + ù Ù
+chr#250 ú + + + + + ú Ú
+chr#251 û + + + + + û Û
+chr#252 ü + + + + + ü Ü
+chr#253 ý + + + + + ý Ý
+chr#254 þ + + + + + þ Þ
+chr#255 + + + + +
diff --git a/src/test/locale/de_DE.ISO8859-1/expected/test-de-char.sql.out b/src/test/locale/de_DE.ISO8859-1/expected/test-de-char.sql.out
new file mode 100644
index 0000000..7d7201c
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/expected/test-de-char.sql.out
@@ -0,0 +1,23 @@
+ name_en | name_de
+----------------------+--------------------------------
+ AAA | ÄÄÄ
+ aaaaaa | ääää
+ AAAAa | ÄÄÄÄä
+ aaaaaaa | aaaaaa
+ BBBB | BBBB
+ bbbbbb | bbbbb
+ BBBBBB | BBBBBBB
+ CCC | CCCC
+ ddddd | ddddd
+ OOOOO | ÖÖÖÖÖ
+ oooooo | öööööö
+ OOOOOOO | OOOOOOO
+ ooooooo | oooooooo
+ ssssss | ssssss
+ SSSSSSSS | SSSSSSSS
+ ßßßßßß | ßßßßßßß
+ UU | UU
+ uuuu | üüüü
+ UUUUU | ÜÜÜÜÜ
+(19 rows)
+
diff --git a/src/test/locale/de_DE.ISO8859-1/expected/test-de-select.sql.out b/src/test/locale/de_DE.ISO8859-1/expected/test-de-select.sql.out
new file mode 100644
index 0000000..556b866
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/expected/test-de-select.sql.out
@@ -0,0 +1,7 @@
+ name_en | name_de
+----------------------+---------
+ AAA | ÄÄÄ
+ aaaaaa | ääää
+ AAAAa | ÄÄÄÄä
+(3 rows)
+
diff --git a/src/test/locale/de_DE.ISO8859-1/expected/test-de-sort.out b/src/test/locale/de_DE.ISO8859-1/expected/test-de-sort.out
new file mode 100644
index 0000000..4c3fa5a
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/expected/test-de-sort.out
@@ -0,0 +1,20 @@
+Bording
+Burg
+Bürger
+Butter
+drang
+drang
+drängeln
+DRÄNGELN2
+hoardin
+hoch
+höhe
+hose
+Saat
+Säge
+Sarg
+Sorting
+Über
+Unter
+Zögern
+Zoll
diff --git a/src/test/locale/de_DE.ISO8859-1/expected/test-de-text.sql.out b/src/test/locale/de_DE.ISO8859-1/expected/test-de-text.sql.out
new file mode 100644
index 0000000..fd9350a
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/expected/test-de-text.sql.out
@@ -0,0 +1,23 @@
+ name_en | name_de
+----------------------+----------
+ AAA | ÄÄÄ
+ aaaaaa | ääää
+ AAAAa | ÄÄÄÄä
+ aaaaaaa | aaaaaa
+ BBBB | BBBB
+ bbbbbb | bbbbb
+ BBBBBB | BBBBBBB
+ CCC | CCCC
+ ddddd | ddddd
+ OOOOO | ÖÖÖÖÖ
+ oooooo | öööööö
+ OOOOOOO | OOOOOOO
+ ooooooo | oooooooo
+ ssssss | ssssss
+ SSSSSSSS | SSSSSSSS
+ ßßßßßß | ßßßßßßß
+ UU | UU
+ uuuu | üüüü
+ UUUUU | ÜÜÜÜÜ
+(19 rows)
+
diff --git a/src/test/locale/de_DE.ISO8859-1/expected/test-de-upper-char.sql.out b/src/test/locale/de_DE.ISO8859-1/expected/test-de-upper-char.sql.out
new file mode 100644
index 0000000..cafe9d3
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/expected/test-de-upper-char.sql.out
@@ -0,0 +1,23 @@
+ upper
+--------------------------------
+ ÄÄÄ
+ ÄÄÄÄ
+ ÄÄÄÄÄ
+ AAAAAA
+ BBBB
+ BBBBB
+ BBBBBBB
+ CCCC
+ DDDDD
+ ÖÖÖÖÖ
+ ÖÖÖÖÖÖ
+ OOOOOOO
+ OOOOOOOO
+ SSSSSS
+ SSSSSSSS
+ ßßßßßßß
+ UU
+ ÜÜÜÜ
+ ÜÜÜÜÜ
+(19 rows)
+
diff --git a/src/test/locale/de_DE.ISO8859-1/expected/test-de-upper-text.sql.out b/src/test/locale/de_DE.ISO8859-1/expected/test-de-upper-text.sql.out
new file mode 100644
index 0000000..4a4aa12
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/expected/test-de-upper-text.sql.out
@@ -0,0 +1,23 @@
+ upper
+----------
+ ÄÄÄ
+ ÄÄÄÄ
+ ÄÄÄÄÄ
+ AAAAAA
+ BBBB
+ BBBBB
+ BBBBBBB
+ CCCC
+ DDDDD
+ ÖÖÖÖÖ
+ ÖÖÖÖÖÖ
+ OOOOOOO
+ OOOOOOOO
+ SSSSSS
+ SSSSSSSS
+ ßßßßßßß
+ UU
+ ÜÜÜÜ
+ ÜÜÜÜÜ
+(19 rows)
+
diff --git a/src/test/locale/de_DE.ISO8859-1/expected/test-de-upper-varchar.sql.out b/src/test/locale/de_DE.ISO8859-1/expected/test-de-upper-varchar.sql.out
new file mode 100644
index 0000000..4a4aa12
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/expected/test-de-upper-varchar.sql.out
@@ -0,0 +1,23 @@
+ upper
+----------
+ ÄÄÄ
+ ÄÄÄÄ
+ ÄÄÄÄÄ
+ AAAAAA
+ BBBB
+ BBBBB
+ BBBBBBB
+ CCCC
+ DDDDD
+ ÖÖÖÖÖ
+ ÖÖÖÖÖÖ
+ OOOOOOO
+ OOOOOOOO
+ SSSSSS
+ SSSSSSSS
+ ßßßßßßß
+ UU
+ ÜÜÜÜ
+ ÜÜÜÜÜ
+(19 rows)
+
diff --git a/src/test/locale/de_DE.ISO8859-1/expected/test-de-varchar.sql.out b/src/test/locale/de_DE.ISO8859-1/expected/test-de-varchar.sql.out
new file mode 100644
index 0000000..fd9350a
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/expected/test-de-varchar.sql.out
@@ -0,0 +1,23 @@
+ name_en | name_de
+----------------------+----------
+ AAA | ÄÄÄ
+ aaaaaa | ääää
+ AAAAa | ÄÄÄÄä
+ aaaaaaa | aaaaaa
+ BBBB | BBBB
+ bbbbbb | bbbbb
+ BBBBBB | BBBBBBB
+ CCC | CCCC
+ ddddd | ddddd
+ OOOOO | ÖÖÖÖÖ
+ oooooo | öööööö
+ OOOOOOO | OOOOOOO
+ ooooooo | oooooooo
+ ssssss | ssssss
+ SSSSSSSS | SSSSSSSS
+ ßßßßßß | ßßßßßßß
+ UU | UU
+ uuuu | üüüü
+ UUUUU | ÜÜÜÜÜ
+(19 rows)
+
diff --git a/src/test/locale/de_DE.ISO8859-1/runall b/src/test/locale/de_DE.ISO8859-1/runall
new file mode 100755
index 0000000..5368e12
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/runall
@@ -0,0 +1,64 @@
+#! /bin/sh
+
+PATH=..:$PATH
+
+echo "Testing PostgreSQL compilation..."
+
+LC_CTYPE=de_DE.ISO8859-1
+LC_COLLATE=$LC_CTYPE
+export LC_CTYPE LC_COLLATE
+
+echo "Testing LC_CTYPE..."
+if ! test-ctype > de-ctype.out; then
+ exit 1
+fi
+diff expected/de-ctype.out de-ctype.out
+
+echo "Testing LC_COLLATE..."
+perl ../sort-test.pl test-de-sort.in > test-de-sort.out
+diff expected/test-de-sort.out test-de-sort.out
+
+### If you have Python - uncomment the following two lines
+#python ../sort-test.py test-de-sort.in > test-de-sort.out
+#diff expected/test-de-sort.out test-de-sort.out
+
+abort() {
+ [ "$1" ] && echo "$*"
+ exit 1
+}
+
+for f in char varchar text; do
+ if echo $f | grep -q char; then
+ ftype="$f(30)"
+ else
+ ftype="$f"
+ fi
+ echo "Testing PgSQL: sort on $ftype type..."
+
+ dropdb testlocale >/dev/null 2>&1
+ createdb testlocale || abort "createdb failed"
+ psql -X -d testlocale -c "CREATE TABLE wordlist (name_en char(20), name_de $ftype);" >/dev/null 2>&1 || abort "createtable failed"
+ psql -X testlocale < test-de.sql.in > test-de-$f.sql.out 2>/dev/null || abort "test query failed"
+ diff expected/test-de-$f.sql.out test-de-$f.sql.out
+done
+
+for f in char varchar text; do
+ if echo $f | grep -q char; then
+ ftype="$f(30)"
+ else
+ ftype="$f"
+ fi
+ echo "Testing PgSQL: upper () on $ftype type..."
+
+ dropdb testlocale >/dev/null 2>&1
+ createdb testlocale || abort "createdb failed"
+ psql -X -d testlocale -c "CREATE TABLE wordlist (name_en char(20), name_de $ftype);" >/dev/null 2>&1 || abort "createtable failed"
+ psql -X testlocale < test-de-upper.sql.in > test-de-upper-$f.sql.out 2>/dev/null || abort "test query failed"
+ diff expected/test-de-upper-$f.sql.out test-de-upper-$f.sql.out
+done
+
+echo "Testing PgSQL: select on regexp..."
+psql -X testlocale < test-de-select.sql.in > test-de-select.sql.out 2>/dev/null || abort "select query failed"
+diff expected/test-de-select.sql.out test-de-select.sql.out
+dropdb testlocale || abort "dropdb failed"
+echo "Finished."
diff --git a/src/test/locale/de_DE.ISO8859-1/test-de-select.sql.in b/src/test/locale/de_DE.ISO8859-1/test-de-select.sql.in
new file mode 100644
index 0000000..c38a156
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/test-de-select.sql.in
@@ -0,0 +1 @@
+SELECT * FROM wordlist WHERE name_de ~* '^Ä.*' ORDER BY name_de;
diff --git a/src/test/locale/de_DE.ISO8859-1/test-de-sort.in b/src/test/locale/de_DE.ISO8859-1/test-de-sort.in
new file mode 100644
index 0000000..c9b9272
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/test-de-sort.in
@@ -0,0 +1,20 @@
+Sorting
+DRÄNGELN2
+Sarg
+Zögern
+drang
+Zoll
+höhe
+Über
+Bürger
+Burg
+hoch
+hose
+Bording
+drängeln
+Unter
+Butter
+Saat
+Säge
+drang
+hoarding
diff --git a/src/test/locale/de_DE.ISO8859-1/test-de-upper.sql.in b/src/test/locale/de_DE.ISO8859-1/test-de-upper.sql.in
new file mode 100644
index 0000000..5149f8c
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/test-de-upper.sql.in
@@ -0,0 +1,22 @@
+COPY wordlist FROM stdin WITH DELIMITER '|';
+AAA |ÄÄÄ
+AAAAa |ÄÄÄÄä
+BBBB |BBBB
+oooooooo |oooooooo
+uuuu |üüüü
+UUUUU |ÜÜÜÜÜ
+CCC |CCCC
+aaaaaa |ääää
+ßßßßßß |ßßßßßßß
+aaaaaaa |aaaaaa
+bbbbbb |bbbbb
+UU |UU
+oooooo |öööööö
+ddddd |ddddd
+OOOOO |ÖÖÖÖÖ
+OOOOOOO |OOOOOOO
+BBBBBB |BBBBBBB
+SSSSSSSS |SSSSSSSS
+ssssss |ssssss
+\.
+SELECT upper (name_de) FROM wordlist ORDER BY upper (name_de);
diff --git a/src/test/locale/de_DE.ISO8859-1/test-de.sql.in b/src/test/locale/de_DE.ISO8859-1/test-de.sql.in
new file mode 100644
index 0000000..7108d02
--- /dev/null
+++ b/src/test/locale/de_DE.ISO8859-1/test-de.sql.in
@@ -0,0 +1,22 @@
+COPY wordlist FROM stdin WITH DELIMITER '|';
+AAA |ÄÄÄ
+AAAAa |ÄÄÄÄä
+BBBB |BBBB
+ooooooo |oooooooo
+uuuu |üüüü
+UUUUU |ÜÜÜÜÜ
+CCC |CCCC
+aaaaaa |ääää
+ßßßßßß |ßßßßßßß
+aaaaaaa |aaaaaa
+bbbbbb |bbbbb
+UU |UU
+oooooo |öööööö
+ddddd |ddddd
+OOOOO |ÖÖÖÖÖ
+OOOOOOO |OOOOOOO
+BBBBBB |BBBBBBB
+SSSSSSSS |SSSSSSSS
+ssssss |ssssss
+\.
+SELECT * FROM wordlist ORDER BY name_de;
diff --git a/src/test/locale/gr_GR.ISO8859-7/Makefile b/src/test/locale/gr_GR.ISO8859-7/Makefile
new file mode 100644
index 0000000..28a72b7
--- /dev/null
+++ b/src/test/locale/gr_GR.ISO8859-7/Makefile
@@ -0,0 +1,7 @@
+all:
+
+test:
+ ./runall
+
+clean:
+ rm -f *.out
diff --git a/src/test/locale/gr_GR.ISO8859-7/README b/src/test/locale/gr_GR.ISO8859-7/README
new file mode 100644
index 0000000..a3dad44
--- /dev/null
+++ b/src/test/locale/gr_GR.ISO8859-7/README
@@ -0,0 +1,4 @@
+src/test/locale/gr_GR.ISO8859-7/README
+
+gr_GR.ISO8859-7 (Greek) locale test.
+Created by Angelos Karageorgiou <angelos@awesome.incredible.com>
diff --git a/src/test/locale/gr_GR.ISO8859-7/expected/gr-ctype.out b/src/test/locale/gr_GR.ISO8859-7/expected/gr-ctype.out
new file mode 100644
index 0000000..833d706
--- /dev/null
+++ b/src/test/locale/gr_GR.ISO8859-7/expected/gr-ctype.out
@@ -0,0 +1,257 @@
+char# char alnum alpha cntrl digit lower graph print punct space upper xdigit lo up
+chr#0 +
+chr#1 +
+chr#2 +
+chr#3 +
+chr#4 +
+chr#5 +
+chr#6 +
+chr#7 +
+chr#8 +
+chr#9 + +
+chr#10 + +
+chr#11 + +
+chr#12 + +
+chr#13 + +
+chr#14 +
+chr#15 +
+chr#16 +
+chr#17 +
+chr#18 +
+chr#19 +
+chr#20 +
+chr#21 +
+chr#22 +
+chr#23 +
+chr#24 +
+chr#25 +
+chr#26 +
+chr#27 +
+chr#28 +
+chr#29 +
+chr#30 +
+chr#31 +
+chr#32 + +
+chr#33 ! + + + ! !
+chr#34 " + + + " "
+chr#35 # + + + # #
+chr#36 $ + + + $ $
+chr#37 % + + + % %
+chr#38 & + + + & &
+chr#39 ' + + + ' '
+chr#40 ( + + + ( (
+chr#41 ) + + + ) )
+chr#42 * + + + * *
+chr#43 + + + + + +
+chr#44 , + + + , ,
+chr#45 - + + + - -
+chr#46 . + + + . .
+chr#47 / + + + / /
+chr#48 0 + + + + + 0 0
+chr#49 1 + + + + + 1 1
+chr#50 2 + + + + + 2 2
+chr#51 3 + + + + + 3 3
+chr#52 4 + + + + + 4 4
+chr#53 5 + + + + + 5 5
+chr#54 6 + + + + + 6 6
+chr#55 7 + + + + + 7 7
+chr#56 8 + + + + + 8 8
+chr#57 9 + + + + + 9 9
+chr#58 : + + + : :
+chr#59 ; + + + ; ;
+chr#60 < + + + < <
+chr#61 = + + + = =
+chr#62 > + + + > >
+chr#63 ? + + + ? ?
+chr#64 @ + + + @ @
+chr#65 A + + + + + + a A
+chr#66 B + + + + + + b B
+chr#67 C + + + + + + c C
+chr#68 D + + + + + + d D
+chr#69 E + + + + + + e E
+chr#70 F + + + + + + f F
+chr#71 G + + + + + g G
+chr#72 H + + + + + h H
+chr#73 I + + + + + i I
+chr#74 J + + + + + j J
+chr#75 K + + + + + k K
+chr#76 L + + + + + l L
+chr#77 M + + + + + m M
+chr#78 N + + + + + n N
+chr#79 O + + + + + o O
+chr#80 P + + + + + p P
+chr#81 Q + + + + + q Q
+chr#82 R + + + + + r R
+chr#83 S + + + + + s S
+chr#84 T + + + + + t T
+chr#85 U + + + + + u U
+chr#86 V + + + + + v V
+chr#87 W + + + + + w W
+chr#88 X + + + + + x X
+chr#89 Y + + + + + y Y
+chr#90 Z + + + + + z Z
+chr#91 [ + + + [ [
+chr#92 \ + + + \ \
+chr#93 ] + + + ] ]
+chr#94 ^ + + + ^ ^
+chr#95 _ + + + _ _
+chr#96 ` + + + ` `
+chr#97 a + + + + + + a A
+chr#98 b + + + + + + b B
+chr#99 c + + + + + + c C
+chr#100 d + + + + + + d D
+chr#101 e + + + + + + e E
+chr#102 f + + + + + + f F
+chr#103 g + + + + + g G
+chr#104 h + + + + + h H
+chr#105 i + + + + + i I
+chr#106 j + + + + + j J
+chr#107 k + + + + + k K
+chr#108 l + + + + + l L
+chr#109 m + + + + + m M
+chr#110 n + + + + + n N
+chr#111 o + + + + + o O
+chr#112 p + + + + + p P
+chr#113 q + + + + + q Q
+chr#114 r + + + + + r R
+chr#115 s + + + + + s S
+chr#116 t + + + + + t T
+chr#117 u + + + + + u U
+chr#118 v + + + + + v V
+chr#119 w + + + + + w W
+chr#120 x + + + + + x X
+chr#121 y + + + + + y Y
+chr#122 z + + + + + z Z
+chr#123 { + + + { {
+chr#124 | + + + | |
+chr#125 } + + + } }
+chr#126 ~ + + + ~ ~
+chr#127  + + + + +  
+chr#128 € + + + + + € €
+chr#129 + + + + +
+chr#130 ‚ + + + + + ‚ ‚
+chr#131 ƒ + + + + + ƒ ƒ
+chr#132 „ + + + + + „ „
+chr#133 … + + + + + … …
+chr#134 † + + + + + † †
+chr#135 ‡ + + + + + ‡ ‡
+chr#136 ˆ + + + + + ˆ ˆ
+chr#137 ‰ + + + + + ‰ ‰
+chr#138 Š + + + + + Š Š
+chr#139 ‹ + + + + + ‹ ‹
+chr#140 Œ + + + + + Œ Œ
+chr#141 + + + + +
+chr#142 Ž + + + + + Ž Ž
+chr#143 + + + + +
+chr#144 + + + + +
+chr#145 ‘ + + + + + ‘ ‘
+chr#146 ’ + + + + + ’ ’
+chr#147 “ + + + + + “ “
+chr#148 ” + + + + + ” ”
+chr#149 • + + + + + • •
+chr#150 – + + + + + – –
+chr#151 — + + + + + — —
+chr#152 ˜ + + + + + ˜ ˜
+chr#153 ™ + + + + + ™ ™
+chr#154 š + + + + + š š
+chr#155 › + + + + + › ›
+chr#156 œ + + + + + œ œ
+chr#157 + + + + +
+chr#158 ž + + + + + ž ž
+chr#159 Ÿ + + + + + Ÿ Ÿ
+chr#160   + + + +    
+chr#161 ¡ + + + + ¡ ¡
+chr#162 ¢ + + + + + á Á
+chr#163 £ + + + + £ £
+chr#164 ¤ + + + + ¤ ¤
+chr#165 ¥ + + + + ¥ ¥
+chr#166 ¦ + + + + ¦ ¦
+chr#167 § + + + + § §
+chr#168 ¨ + + + + ¨ ¨
+chr#169 © + + + + © ©
+chr#170 ª + + + + ª ª
+chr#171 « + + + + « «
+chr#172 ¬ + + + + ¬ ¬
+chr#173 ­ + + + + ­ ­
+chr#174 ® + + + + ® ®
+chr#175 ¯ + + + + ¯ ¯
+chr#176 ° + + + + ° °
+chr#177 ± + + + + ± ±
+chr#178 ² + + + + ² ²
+chr#179 ³ + + + + ³ ³
+chr#180 ´ + + + + ´ ´
+chr#181 µ + + + + µ µ
+chr#182 ¶ + + + + ¶ ¶
+chr#183 · + + + + · ·
+chr#184 ¸ + + + + + å Å
+chr#185 ¹ + + + + + ç Ç
+chr#186 º + + + + + é É
+chr#187 » + + + + » »
+chr#188 ¼ + + + + + ï Ï
+chr#189 ½ + + + + ½ ½
+chr#190 ¾ + + + + + õ Õ
+chr#191 ¿ + + + + + ù Ù
+chr#192 À + + + + + é É
+chr#193 Á + + + + + á Á
+chr#194 Â + + + + + â Â
+chr#195 Ã + + + + + ã Ã
+chr#196 Ä + + + + + ä Ä
+chr#197 Å + + + + + å Å
+chr#198 Æ + + + + + æ Æ
+chr#199 Ç + + + + + ç Ç
+chr#200 È + + + + + è È
+chr#201 É + + + + + é É
+chr#202 Ê + + + + + ê Ê
+chr#203 Ë + + + + + ë Ë
+chr#204 Ì + + + + + ì Ì
+chr#205 Í + + + + + í Í
+chr#206 Î + + + + + î Î
+chr#207 Ï + + + + + ï Ï
+chr#208 Ð + + + + + ð Ð
+chr#209 Ñ + + + + + ñ Ñ
+chr#210 Ò + + + + + ò Ò
+chr#211 Ó + + + + + ó Ó
+chr#212 Ô + + + + + ô Ô
+chr#213 Õ + + + + + õ Õ
+chr#214 Ö + + + + + ö Ö
+chr#215 × + + + + + ÷ ×
+chr#216 Ø + + + + + ø Ø
+chr#217 Ù + + + + + ù Ù
+chr#218 Ú + + + + + é É
+chr#219 Û + + + + + õ Õ
+chr#220 Ü + + + + + á Á
+chr#221 Ý + + + + + å Å
+chr#222 Þ + + + + + ç Ç
+chr#223 ß + + + + + é É
+chr#224 à + + + + + õ Õ
+chr#225 á + + + + + á Á
+chr#226 â + + + + + â Â
+chr#227 ã + + + + + ã Ã
+chr#228 ä + + + + + ä Ä
+chr#229 å + + + + + å Å
+chr#230 æ + + + + + æ Æ
+chr#231 ç + + + + + ç Ç
+chr#232 è + + + + + è È
+chr#233 é + + + + + é É
+chr#234 ê + + + + + ê Ê
+chr#235 ë + + + + + ë Ë
+chr#236 ì + + + + + ì Ì
+chr#237 í + + + + + í Í
+chr#238 î + + + + + î Î
+chr#239 ï + + + + + ï Ï
+chr#240 ð + + + + + ð Ð
+chr#241 ñ + + + + + ñ Ñ
+chr#242 ò + + + + + ò Ó
+chr#243 ó + + + + + ó Ó
+chr#244 ô + + + + + ô Ô
+chr#245 õ + + + + + õ Õ
+chr#246 ö + + + + + ö Ö
+chr#247 ÷ + + + + + ÷ ×
+chr#248 ø + + + + + ø Ø
+chr#249 ù + + + + + ù Ù
+chr#250 ú + + + + + é É
+chr#251 û + + + + + õ Õ
+chr#252 ü + + + + + ï Ï
+chr#253 ý + + + + + õ Õ
+chr#254 þ + + + + + ù Ù
+chr#255 +
diff --git a/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-char.sql.out b/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-char.sql.out
new file mode 100644
index 0000000..eb62e52
--- /dev/null
+++ b/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-char.sql.out
@@ -0,0 +1,54 @@
+ abbrev | name_en | name_gr
+--------+----------------------+--------------------------------------------------------------
+ ID | Idaho | Áúíôá÷ï
+ IA | Iowa | Áúüâá
+ AL | Alabama | ÁëáìðÜìá
+ AK | Alaska | ÁëÜóêá
+ AZ | Arizona | Áñéæüíá
+ AR | Arkansas | Áñêáíóáò
+ VT | Vermont | Âåñìüíô
+ VA | Virginia | Âéñôæßíéá
+ NC | North Carolina | Âüñåéá Êáñïëßíá
+ ND | North Dakota | Âüñåéá Íôáêüôá
+ FL | Florida | Öëüñéíôá
+ IL | Illinois | Éëëéíüéò
+ IN | Indiana | ÉíôéÜíá
+ CA | California | Êáëéöüñíéá
+ KY | Kentucky | ÊåíôÜêé
+ CO | Colorado | ÊïëïñÜíôï
+ CT | Connecticut | ÊïííÝêôéêáô
+ KA | Kansas | ÊÜíóáò
+ LA | Louisiana | ËïõúæéÜíá
+ MA | Massachusetts | Ìáóóá÷ïõóÝôç
+ MD | Maryland | Ìáßñõëáíô
+ MN | Minnesota | Ìéíåóóüôá
+ MS | Mississippi | Ìéóéóóßðé
+ MO | Missouri | Ìéóóïýñé
+ MT | Montana | ÌïíôÜíá
+ ME | Maine | ÌÝéí
+ MI | Michigan | Ìßôóéãêáí
+ NV | Nevada | ÍåâÜäá
+ NE | Nebraska | ÍåìðñÜóêá
+ DE | Delaware | ÍôÝëáãïõåñ
+ NJ | New Jersey | ÍÝá ÕåñóÝç
+ NY | New York | ÍÝá Õüñêç
+ NH | New Hampshire | ÍÝï ×Üìðóáúñ
+ NM | New Mexico | ÍÝï Ìåîéêü
+ SC | South Carolina | Íüôéá Êáñïëßíá
+ SD | South Dakota | Íüôéá Íôáêüôá
+ OH | Ohio | Ï÷Üéï
+ OK | Oklahoma | Ïêëá÷üìá
+ OR | Oregon | Ïñåãêïí
+ PA | Pennsylvania | ÐåííóõëâÜíéá
+ TN | Tennessee | Ôåííåóß
+ GA | Georgia | Ôæüñôæéá
+ TX | Texas | ÔÝîáò
+ UT | Utah | Ãéïýôá
+ WY | Wyoming | Ãïõáúüìéíãê
+ WV | West Virginia | Ãïõåóô Âéñôæßíéá
+ WI | Wisconsin | Ãïõéóêüíóéí
+ WA | Washington | Ãïõüóéíãêôïí
+ DC | Washington DC | Ãïõüóéíãôïí Íôß Óé
+ RI | Rhode Island | Ñüïõíô Áúëáíô
+(50 rows)
+
diff --git a/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-select.sql.out b/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-select.sql.out
new file mode 100644
index 0000000..abfce69
--- /dev/null
+++ b/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-select.sql.out
@@ -0,0 +1,10 @@
+ abbrev | name_en | name_gr
+--------+----------------------+--------------------
+ UT | Utah | Ãéïýôá
+ WY | Wyoming | Ãïõáúüìéíãê
+ WV | West Virginia | Ãïõåóô Âéñôæßíéá
+ WI | Wisconsin | Ãïõéóêüíóéí
+ WA | Washington | Ãïõüóéíãêôïí
+ DC | Washington DC | Ãïõüóéíãôïí Íôß Óé
+(6 rows)
+
diff --git a/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-sort.out b/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-sort.out
new file mode 100644
index 0000000..ef9b699
--- /dev/null
+++ b/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-sort.out
@@ -0,0 +1,7 @@
+Bording
+Sorting
+hoarding
+Áããåëïò
+ÄïêéìÞ
+Åëëçíéêïý
+Óüñô
diff --git a/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-text.sql.out b/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-text.sql.out
new file mode 100644
index 0000000..0a128a7
--- /dev/null
+++ b/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-text.sql.out
@@ -0,0 +1,54 @@
+ abbrev | name_en | name_gr
+--------+----------------------+--------------------
+ ID | Idaho | Áúíôá÷ï
+ IA | Iowa | Áúüâá
+ AL | Alabama | ÁëáìðÜìá
+ AK | Alaska | ÁëÜóêá
+ AZ | Arizona | Áñéæüíá
+ AR | Arkansas | Áñêáíóáò
+ VT | Vermont | Âåñìüíô
+ VA | Virginia | Âéñôæßíéá
+ NC | North Carolina | Âüñåéá Êáñïëßíá
+ ND | North Dakota | Âüñåéá Íôáêüôá
+ FL | Florida | Öëüñéíôá
+ IL | Illinois | Éëëéíüéò
+ IN | Indiana | ÉíôéÜíá
+ CA | California | Êáëéöüñíéá
+ KY | Kentucky | ÊåíôÜêé
+ CO | Colorado | ÊïëïñÜíôï
+ CT | Connecticut | ÊïííÝêôéêáô
+ KA | Kansas | ÊÜíóáò
+ LA | Louisiana | ËïõúæéÜíá
+ MA | Massachusetts | Ìáóóá÷ïõóÝôç
+ MD | Maryland | Ìáßñõëáíô
+ MN | Minnesota | Ìéíåóóüôá
+ MS | Mississippi | Ìéóéóóßðé
+ MO | Missouri | Ìéóóïýñé
+ MT | Montana | ÌïíôÜíá
+ ME | Maine | ÌÝéí
+ MI | Michigan | Ìßôóéãêáí
+ NV | Nevada | ÍåâÜäá
+ NE | Nebraska | ÍåìðñÜóêá
+ DE | Delaware | ÍôÝëáãïõåñ
+ NJ | New Jersey | ÍÝá ÕåñóÝç
+ NY | New York | ÍÝá Õüñêç
+ NH | New Hampshire | ÍÝï ×Üìðóáúñ
+ NM | New Mexico | ÍÝï Ìåîéêü
+ SC | South Carolina | Íüôéá Êáñïëßíá
+ SD | South Dakota | Íüôéá Íôáêüôá
+ OH | Ohio | Ï÷Üéï
+ OK | Oklahoma | Ïêëá÷üìá
+ OR | Oregon | Ïñåãêïí
+ PA | Pennsylvania | ÐåííóõëâÜíéá
+ TN | Tennessee | Ôåííåóß
+ GA | Georgia | Ôæüñôæéá
+ TX | Texas | ÔÝîáò
+ UT | Utah | Ãéïýôá
+ WY | Wyoming | Ãïõáúüìéíãê
+ WV | West Virginia | Ãïõåóô Âéñôæßíéá
+ WI | Wisconsin | Ãïõéóêüíóéí
+ WA | Washington | Ãïõüóéíãêôïí
+ DC | Washington DC | Ãïõüóéíãôïí Íôß Óé
+ RI | Rhode Island | Ñüïõíô Áúëáíô
+(50 rows)
+
diff --git a/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-varchar.sql.out b/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-varchar.sql.out
new file mode 100644
index 0000000..0a128a7
--- /dev/null
+++ b/src/test/locale/gr_GR.ISO8859-7/expected/test-gr-varchar.sql.out
@@ -0,0 +1,54 @@
+ abbrev | name_en | name_gr
+--------+----------------------+--------------------
+ ID | Idaho | Áúíôá÷ï
+ IA | Iowa | Áúüâá
+ AL | Alabama | ÁëáìðÜìá
+ AK | Alaska | ÁëÜóêá
+ AZ | Arizona | Áñéæüíá
+ AR | Arkansas | Áñêáíóáò
+ VT | Vermont | Âåñìüíô
+ VA | Virginia | Âéñôæßíéá
+ NC | North Carolina | Âüñåéá Êáñïëßíá
+ ND | North Dakota | Âüñåéá Íôáêüôá
+ FL | Florida | Öëüñéíôá
+ IL | Illinois | Éëëéíüéò
+ IN | Indiana | ÉíôéÜíá
+ CA | California | Êáëéöüñíéá
+ KY | Kentucky | ÊåíôÜêé
+ CO | Colorado | ÊïëïñÜíôï
+ CT | Connecticut | ÊïííÝêôéêáô
+ KA | Kansas | ÊÜíóáò
+ LA | Louisiana | ËïõúæéÜíá
+ MA | Massachusetts | Ìáóóá÷ïõóÝôç
+ MD | Maryland | Ìáßñõëáíô
+ MN | Minnesota | Ìéíåóóüôá
+ MS | Mississippi | Ìéóéóóßðé
+ MO | Missouri | Ìéóóïýñé
+ MT | Montana | ÌïíôÜíá
+ ME | Maine | ÌÝéí
+ MI | Michigan | Ìßôóéãêáí
+ NV | Nevada | ÍåâÜäá
+ NE | Nebraska | ÍåìðñÜóêá
+ DE | Delaware | ÍôÝëáãïõåñ
+ NJ | New Jersey | ÍÝá ÕåñóÝç
+ NY | New York | ÍÝá Õüñêç
+ NH | New Hampshire | ÍÝï ×Üìðóáúñ
+ NM | New Mexico | ÍÝï Ìåîéêü
+ SC | South Carolina | Íüôéá Êáñïëßíá
+ SD | South Dakota | Íüôéá Íôáêüôá
+ OH | Ohio | Ï÷Üéï
+ OK | Oklahoma | Ïêëá÷üìá
+ OR | Oregon | Ïñåãêïí
+ PA | Pennsylvania | ÐåííóõëâÜíéá
+ TN | Tennessee | Ôåííåóß
+ GA | Georgia | Ôæüñôæéá
+ TX | Texas | ÔÝîáò
+ UT | Utah | Ãéïýôá
+ WY | Wyoming | Ãïõáúüìéíãê
+ WV | West Virginia | Ãïõåóô Âéñôæßíéá
+ WI | Wisconsin | Ãïõéóêüíóéí
+ WA | Washington | Ãïõüóéíãêôïí
+ DC | Washington DC | Ãïõüóéíãôïí Íôß Óé
+ RI | Rhode Island | Ñüïõíô Áúëáíô
+(50 rows)
+
diff --git a/src/test/locale/gr_GR.ISO8859-7/runall b/src/test/locale/gr_GR.ISO8859-7/runall
new file mode 100755
index 0000000..4db3374
--- /dev/null
+++ b/src/test/locale/gr_GR.ISO8859-7/runall
@@ -0,0 +1,49 @@
+#! /bin/sh
+
+PATH=..:$PATH
+
+echo "Testing PostgreSQL compilation..."
+
+LC_CTYPE=gr_GR.ISO8859-7
+LC_COLLATE=$LC_CTYPE
+export LC_CTYPE LC_COLLATE
+
+echo "Testing LC_CTYPE..."
+if ! test-ctype > gr-ctype.out; then
+ exit 1
+fi
+diff expected/gr-ctype.out gr-ctype.out
+
+echo "Testing LC_COLLATE..."
+perl ../sort-test.pl test-gr-sort.in > test-gr-sort.out
+diff expected/test-gr-sort.out test-gr-sort.out
+
+### If you have Python - uncomment the following two lines
+#python ../sort-test.py test-gr-sort.in > test-gr-sort.out
+#diff expected/test-gr-sort.out test-gr-sort.out
+
+
+abort() {
+ [ "$1" ] && echo "$*"
+ exit 1
+}
+
+for f in char varchar text; do
+ if echo $f | grep -q char; then
+ ftype="$f(60)"
+ else
+ ftype="$f"
+ fi
+ echo "Testing PgSQL: sort on $ftype type..."
+
+ dropdb testlocale >/dev/null 2>&1
+ createdb testlocale || abort "createdb failed"
+ psql -X -d testlocale -c "CREATE TABLE usastates (abbrev char(2), name_en char(20), name_gr $ftype);" >/dev/null 2>&1 || abort "createtable failed"
+ psql -X testlocale < test-gr.sql.in > test-gr-$f.sql.out 2>/dev/null || abort "test query failed"
+ diff expected/test-gr-$f.sql.out test-gr-$f.sql.out
+done
+echo "Testing PgSQL: select on regexp..."
+psql -X testlocale < test-gr-select.sql.in > test-gr-select.sql.out 2>/dev/null || abort "select query failed"
+diff expected/test-gr-select.sql.out test-gr-select.sql.out
+dropdb testlocale || abort "dropdb failed"
+echo "Finished."
diff --git a/src/test/locale/gr_GR.ISO8859-7/test-gr-select.sql.in b/src/test/locale/gr_GR.ISO8859-7/test-gr-select.sql.in
new file mode 100644
index 0000000..e22349c
--- /dev/null
+++ b/src/test/locale/gr_GR.ISO8859-7/test-gr-select.sql.in
@@ -0,0 +1 @@
+SELECT * FROM usastates WHERE name_gr ~* '^Ã.*' ORDER BY name_gr;
diff --git a/src/test/locale/gr_GR.ISO8859-7/test-gr-sort.in b/src/test/locale/gr_GR.ISO8859-7/test-gr-sort.in
new file mode 100644
index 0000000..248f0fa
--- /dev/null
+++ b/src/test/locale/gr_GR.ISO8859-7/test-gr-sort.in
@@ -0,0 +1,7 @@
+Sorting
+ÄïêéìÞ
+Åëëçíéêïý
+Óüñô
+Áããåëïò
+Bording
+hoarding
diff --git a/src/test/locale/gr_GR.ISO8859-7/test-gr.sql.in b/src/test/locale/gr_GR.ISO8859-7/test-gr.sql.in
new file mode 100644
index 0000000..781f4ef
--- /dev/null
+++ b/src/test/locale/gr_GR.ISO8859-7/test-gr.sql.in
@@ -0,0 +1,53 @@
+COPY usastates FROM stdin WITH DELIMITER '|';
+AK|Alaska |ÁëÜóêá
+WA|Washington |Ãïõüóéíãêôïí
+OR|Oregon |Ïñåãêïí
+CA|California |Êáëéöüñíéá
+NV|Nevada |ÍåâÜäá
+ID|Idaho |Áúíôá÷ï
+UT|Utah |Ãéïýôá
+AZ|Arizona |Áñéæüíá
+MT|Montana |ÌïíôÜíá
+WY|Wyoming |Ãïõáúüìéíãê
+CO|Colorado |ÊïëïñÜíôï
+NM|New Mexico |ÍÝï Ìåîéêü
+ND|North Dakota |Âüñåéá Íôáêüôá
+SD|South Dakota |Íüôéá Íôáêüôá
+NE|Nebraska |ÍåìðñÜóêá
+KA|Kansas |ÊÜíóáò
+OK|Oklahoma |Ïêëá÷üìá
+TX|Texas |ÔÝîáò
+MN|Minnesota |Ìéíåóóüôá
+IA|Iowa |Áúüâá
+MO|Missouri |Ìéóóïýñé
+AR|Arkansas |Áñêáíóáò
+LA|Louisiana |ËïõúæéÜíá
+WI|Wisconsin |Ãïõéóêüíóéí
+IL|Illinois |Éëëéíüéò
+IN|Indiana |ÉíôéÜíá
+MS|Mississippi |Ìéóéóóßðé
+AL|Alabama |ÁëáìðÜìá
+MI|Michigan |Ìßôóéãêáí
+OH|Ohio |Ï÷Üéï
+KY|Kentucky |ÊåíôÜêé
+TN|Tennessee |Ôåííåóß
+GA|Georgia |Ôæüñôæéá
+FL|Florida |Öëüñéíôá
+PA|Pennsylvania |ÐåííóõëâÜíéá
+WV|West Virginia |Ãïõåóô Âéñôæßíéá
+VA|Virginia |Âéñôæßíéá
+NC|North Carolina|Âüñåéá Êáñïëßíá
+SC|South Carolina|Íüôéá Êáñïëßíá
+NY|New York |ÍÝá Õüñêç
+NJ|New Jersey |ÍÝá ÕåñóÝç
+DE|Delaware |ÍôÝëáãïõåñ
+MD|Maryland |Ìáßñõëáíô
+DC|Washington DC |Ãïõüóéíãôïí Íôß Óé
+VT|Vermont |Âåñìüíô
+MA|Massachusetts |Ìáóóá÷ïõóÝôç
+CT|Connecticut |ÊïííÝêôéêáô
+ME|Maine |ÌÝéí
+NH|New Hampshire |ÍÝï ×Üìðóáúñ
+RI|Rhode Island |Ñüïõíô Áúëáíô
+\.
+SELECT * FROM usastates ORDER BY name_gr;
diff --git a/src/test/locale/koi8-r/Makefile b/src/test/locale/koi8-r/Makefile
new file mode 100644
index 0000000..28a72b7
--- /dev/null
+++ b/src/test/locale/koi8-r/Makefile
@@ -0,0 +1,7 @@
+all:
+
+test:
+ ./runall
+
+clean:
+ rm -f *.out
diff --git a/src/test/locale/koi8-r/expected/koi8-ctype.out b/src/test/locale/koi8-r/expected/koi8-ctype.out
new file mode 100644
index 0000000..9b1d9d4
--- /dev/null
+++ b/src/test/locale/koi8-r/expected/koi8-ctype.out
@@ -0,0 +1,257 @@
+char# char alnum alpha cntrl digit lower graph print punct space upper xdigit lo up
+chr#0 +
+chr#1 +
+chr#2 +
+chr#3 +
+chr#4 +
+chr#5 +
+chr#6 +
+chr#7 +
+chr#8 +
+chr#9 + +
+chr#10 + +
+chr#11 + +
+chr#12 + +
+chr#13 + +
+chr#14 +
+chr#15 +
+chr#16 +
+chr#17 +
+chr#18 +
+chr#19 +
+chr#20 +
+chr#21 +
+chr#22 +
+chr#23 +
+chr#24 +
+chr#25 +
+chr#26 +
+chr#27 +
+chr#28 +
+chr#29 +
+chr#30 +
+chr#31 +
+chr#32 + +
+chr#33 ! + + + ! !
+chr#34 " + + + " "
+chr#35 # + + + # #
+chr#36 $ + + + $ $
+chr#37 % + + + % %
+chr#38 & + + + & &
+chr#39 ' + + + ' '
+chr#40 ( + + + ( (
+chr#41 ) + + + ) )
+chr#42 * + + + * *
+chr#43 + + + + + +
+chr#44 , + + + , ,
+chr#45 - + + + - -
+chr#46 . + + + . .
+chr#47 / + + + / /
+chr#48 0 + + + + + 0 0
+chr#49 1 + + + + + 1 1
+chr#50 2 + + + + + 2 2
+chr#51 3 + + + + + 3 3
+chr#52 4 + + + + + 4 4
+chr#53 5 + + + + + 5 5
+chr#54 6 + + + + + 6 6
+chr#55 7 + + + + + 7 7
+chr#56 8 + + + + + 8 8
+chr#57 9 + + + + + 9 9
+chr#58 : + + + : :
+chr#59 ; + + + ; ;
+chr#60 < + + + < <
+chr#61 = + + + = =
+chr#62 > + + + > >
+chr#63 ? + + + ? ?
+chr#64 @ + + + @ @
+chr#65 A + + + + + + a A
+chr#66 B + + + + + + b B
+chr#67 C + + + + + + c C
+chr#68 D + + + + + + d D
+chr#69 E + + + + + + e E
+chr#70 F + + + + + + f F
+chr#71 G + + + + + g G
+chr#72 H + + + + + h H
+chr#73 I + + + + + i I
+chr#74 J + + + + + j J
+chr#75 K + + + + + k K
+chr#76 L + + + + + l L
+chr#77 M + + + + + m M
+chr#78 N + + + + + n N
+chr#79 O + + + + + o O
+chr#80 P + + + + + p P
+chr#81 Q + + + + + q Q
+chr#82 R + + + + + r R
+chr#83 S + + + + + s S
+chr#84 T + + + + + t T
+chr#85 U + + + + + u U
+chr#86 V + + + + + v V
+chr#87 W + + + + + w W
+chr#88 X + + + + + x X
+chr#89 Y + + + + + y Y
+chr#90 Z + + + + + z Z
+chr#91 [ + + + [ [
+chr#92 \ + + + \ \
+chr#93 ] + + + ] ]
+chr#94 ^ + + + ^ ^
+chr#95 _ + + + _ _
+chr#96 ` + + + ` `
+chr#97 a + + + + + + a A
+chr#98 b + + + + + + b B
+chr#99 c + + + + + + c C
+chr#100 d + + + + + + d D
+chr#101 e + + + + + + e E
+chr#102 f + + + + + + f F
+chr#103 g + + + + + g G
+chr#104 h + + + + + h H
+chr#105 i + + + + + i I
+chr#106 j + + + + + j J
+chr#107 k + + + + + k K
+chr#108 l + + + + + l L
+chr#109 m + + + + + m M
+chr#110 n + + + + + n N
+chr#111 o + + + + + o O
+chr#112 p + + + + + p P
+chr#113 q + + + + + q Q
+chr#114 r + + + + + r R
+chr#115 s + + + + + s S
+chr#116 t + + + + + t T
+chr#117 u + + + + + u U
+chr#118 v + + + + + v V
+chr#119 w + + + + + w W
+chr#120 x + + + + + x X
+chr#121 y + + + + + y Y
+chr#122 z + + + + + z Z
+chr#123 { + + + { {
+chr#124 | + + + | |
+chr#125 } + + + } }
+chr#126 ~ + + + ~ ~
+chr#127 +
+chr#128 € + + + € €
+chr#129 + + +
+chr#130 ‚ + + + ‚ ‚
+chr#131 ƒ + + + ƒ ƒ
+chr#132 „ + + + „ „
+chr#133 … + + + … …
+chr#134 † + + + † †
+chr#135 ‡ + + + ‡ ‡
+chr#136 ˆ + + + ˆ ˆ
+chr#137 ‰ + + + ‰ ‰
+chr#138 Š + + + Š Š
+chr#139 ‹ + + + ‹ ‹
+chr#140 Œ + + + Œ Œ
+chr#141 + + +
+chr#142 Ž + + + Ž Ž
+chr#143 + + +
+chr#144 + + +
+chr#145 ‘ + + + ‘ ‘
+chr#146 ’ + + + ’ ’
+chr#147 “ + + + “ “
+chr#148 ” + + + ” ”
+chr#149 • + + + • •
+chr#150 – + + + – –
+chr#151 — + + + — —
+chr#152 ˜ + + + ˜ ˜
+chr#153 ™ + + + ™ ™
+chr#154 +
+chr#155 › + + + › ›
+chr#156 œ + + + œ œ
+chr#157 + + +
+chr#158 ž + + + ž ž
+chr#159 Ÿ + + + Ÿ Ÿ
+chr#160   + + +    
+chr#161 ¡ + + + ¡ ¡
+chr#162 ¢ + + + ¢ ¢
+chr#163 £ + + + + + £ ³
+chr#164 ¤ + + + ¤ ¤
+chr#165 ¥ + + + ¥ ¥
+chr#166 ¦ + + + ¦ ¦
+chr#167 § + + + § §
+chr#168 ¨ + + + ¨ ¨
+chr#169 © + + + © ©
+chr#170 ª + + + ª ª
+chr#171 « + + + « «
+chr#172 ¬ + + + ¬ ¬
+chr#173 ­ + + + ­ ­
+chr#174 ® + + + ® ®
+chr#175 ¯ + + + ¯ ¯
+chr#176 ° + + + ° °
+chr#177 ± + + + ± ±
+chr#178 ² + + + ² ²
+chr#179 ³ + + + + + £ ³
+chr#180 ´ + + + ´ ´
+chr#181 µ + + + µ µ
+chr#182 ¶ + + + ¶ ¶
+chr#183 · + + + · ·
+chr#184 ¸ + + + ¸ ¸
+chr#185 ¹ + + + ¹ ¹
+chr#186 º + + + º º
+chr#187 » + + + » »
+chr#188 ¼ + + + ¼ ¼
+chr#189 ½ + + + ½ ½
+chr#190 ¾ + + + ¾ ¾
+chr#191 ¿ + + + ¿ ¿
+chr#192 À + + + + + À à
+chr#193 Á + + + + + Á á
+chr#194 Â + + + + + Â â
+chr#195 Ã + + + + + Ã ã
+chr#196 Ä + + + + + Ä ä
+chr#197 Å + + + + + Å å
+chr#198 Æ + + + + + Æ æ
+chr#199 Ç + + + + + Ç ç
+chr#200 È + + + + + È è
+chr#201 É + + + + + É é
+chr#202 Ê + + + + + Ê ê
+chr#203 Ë + + + + + Ë ë
+chr#204 Ì + + + + + Ì ì
+chr#205 Í + + + + + Í í
+chr#206 Î + + + + + Î î
+chr#207 Ï + + + + + Ï ï
+chr#208 Ð + + + + + Ð ð
+chr#209 Ñ + + + + + Ñ ñ
+chr#210 Ò + + + + + Ò ò
+chr#211 Ó + + + + + Ó ó
+chr#212 Ô + + + + + Ô ô
+chr#213 Õ + + + + + Õ õ
+chr#214 Ö + + + + + Ö ö
+chr#215 × + + + + + × ÷
+chr#216 Ø + + + + + Ø ø
+chr#217 Ù + + + + + Ù ù
+chr#218 Ú + + + + + Ú ú
+chr#219 Û + + + + + Û û
+chr#220 Ü + + + + + Ü ü
+chr#221 Ý + + + + + Ý ý
+chr#222 Þ + + + + + Þ þ
+chr#223 ß + + + + + ß
+chr#224 à + + + + + À à
+chr#225 á + + + + + Á á
+chr#226 â + + + + + Â â
+chr#227 ã + + + + + Ã ã
+chr#228 ä + + + + + Ä ä
+chr#229 å + + + + + Å å
+chr#230 æ + + + + + Æ æ
+chr#231 ç + + + + + Ç ç
+chr#232 è + + + + + È è
+chr#233 é + + + + + É é
+chr#234 ê + + + + + Ê ê
+chr#235 ë + + + + + Ë ë
+chr#236 ì + + + + + Ì ì
+chr#237 í + + + + + Í í
+chr#238 î + + + + + Î î
+chr#239 ï + + + + + Ï ï
+chr#240 ð + + + + + Ð ð
+chr#241 ñ + + + + + Ñ ñ
+chr#242 ò + + + + + Ò ò
+chr#243 ó + + + + + Ó ó
+chr#244 ô + + + + + Ô ô
+chr#245 õ + + + + + Õ õ
+chr#246 ö + + + + + Ö ö
+chr#247 ÷ + + + + + × ÷
+chr#248 ø + + + + + Ø ø
+chr#249 ù + + + + + Ù ù
+chr#250 ú + + + + + Ú ú
+chr#251 û + + + + + Û û
+chr#252 ü + + + + + Ü ü
+chr#253 ý + + + + + Ý ý
+chr#254 þ + + + + + Þ þ
+chr#255 + + + + + ß
diff --git a/src/test/locale/koi8-r/expected/test-koi8-char.sql.out b/src/test/locale/koi8-r/expected/test-koi8-char.sql.out
new file mode 100644
index 0000000..95f52d8
--- /dev/null
+++ b/src/test/locale/koi8-r/expected/test-koi8-char.sql.out
@@ -0,0 +1,54 @@
+ abbrev | name_en | name_ru
+--------+----------------------+--------------------------------------------------------------
+ ID | Idaho | áÊÄÁÈÏ
+ IA | Iowa | áÊÏ×Á
+ AL | Alabama | áÌÁÂÁÍÁ
+ AK | Alaska | áÌÑÓËÁ
+ AZ | Arizona | áÒÉÚÏÎÁ
+ AR | Arkansas | áÒËÁÎÚÁÓ
+ WY | Wyoming | ÷ÁÊÏÍÉÎÇ
+ WA | Washington | ÷ÁÛÉÎÇÔÏÎ
+ VT | Vermont | ÷ÅÒÍÏÎÔ
+ VA | Virginia | փ񀅃냄
+ WI | Wisconsin | ÷ÉÓËÏÎÓÉÎ
+ DE | Delaware | äÅÌÁ×ÜÒ
+ GA | Georgia | äÖÏÒÄÖÉÑ
+ WV | West Virginia | úÁÐÁÄÎÁÑ ÷ÉÒÄÖÉÎÉÑ
+ IL | Illinois | éÌÌÉÎÏÊÓ
+ IN | Indiana | éÎÄÉÁÎÁ
+ CA | California | ëÁÌÉÆÏÒÎÉÑ
+ KA | Kansas | ëÁÎÚÁÓ
+ KY | Kentucky | ëÅÎÔÕËËÉ
+ CO | Colorado | ëÏÌÏÒÁÄÏ
+ CT | Connecticut | ëÏÎÎÅËÔÉËÕÔ
+ LA | Louisiana | ìÕÉÚÉÁÎÁ
+ MA | Massachusetts | íÁÓÓÁÞÕÓÅÔÓ
+ MN | Minnesota | íÉÎÎÅÓÏÔÁ
+ MS | Mississippi | íÉÓÓÉÓÉÐÉ
+ MO | Missouri | íÉÓÓÕÒÉ
+ MI | Michigan | íÉÞÉÇÁÎ
+ MT | Montana | íÏÎÔÁÎÁ
+ ME | Maine | íÜÎ
+ MD | Maryland | íÜÒÉÌÅÎÄ
+ NE | Nebraska | îÅÂÒÁÓËÁ
+ NV | Nevada | îÅ×ÁÄÁ
+ NH | New Hampshire | îØÀ-çÜÍÐÛÉÒ
+ NJ | New Jersey | îØÀ-äÖÅÒÓÉ
+ NY | New York | îØÀ-êÏÒË
+ NM | New Mexico | îØÀ-íÅËÓÉËÏ
+ OH | Ohio | ïÇÁÊÏ
+ OK | Oklahoma | ïËÌÁÈÏÍÁ
+ DC | Washington DC | ÏËÒÕÇ ëÏÌÕÍÂÉÑ (ÓÏÚÄÁÎ ÓÐÅÃÉÁÌØÎÏ ÐÏÄ ÓÔÏÌÉÃÕ)
+ OR | Oregon | ïÒÅÇÏÎ
+ PA | Pennsylvania | ðÅÎÓÉÌØ×ÁÎÉÑ
+ RI | Rhode Island | òÏÄ-áÊÌÅÎÄ
+ ND | North Dakota | óÅ×ÅÒÎÁÑ äÁËÏÔÁ
+ NC | North Carolina | óÅ×ÅÒÎÁÑ ëÁÒÏÌÉÎÁ
+ TN | Tennessee | ôÅÎÎÅÓÓÉ
+ TX | Texas | ôÅÈÁÓ
+ FL | Florida | æÌÏÒÉÄÁ
+ SD | South Dakota | àÖÎÁÑ äÁËÏÔÁ
+ SC | South Carolina | àÖÎÁÑ ëÁÒÏÌÉÎÁ
+ UT | Utah | àÔÁ
+(50 rows)
+
diff --git a/src/test/locale/koi8-r/expected/test-koi8-select.sql.out b/src/test/locale/koi8-r/expected/test-koi8-select.sql.out
new file mode 100644
index 0000000..f5be657
--- /dev/null
+++ b/src/test/locale/koi8-r/expected/test-koi8-select.sql.out
@@ -0,0 +1,8 @@
+ abbrev | name_en | name_ru
+--------+----------------------+------------------------------------------------
+ OH | Ohio | ïÇÁÊÏ
+ OK | Oklahoma | ïËÌÁÈÏÍÁ
+ DC | Washington DC | ÏËÒÕÇ ëÏÌÕÍÂÉÑ (ÓÏÚÄÁÎ ÓÐÅÃÉÁÌØÎÏ ÐÏÄ ÓÔÏÌÉÃÕ)
+ OR | Oregon | ïÒÅÇÏÎ
+(4 rows)
+
diff --git a/src/test/locale/koi8-r/expected/test-koi8-sort.out b/src/test/locale/koi8-r/expected/test-koi8-sort.out
new file mode 100644
index 0000000..0089f41
--- /dev/null
+++ b/src/test/locale/koi8-r/expected/test-koi8-sort.out
@@ -0,0 +1,9 @@
+Bording
+hoarding
+Vesta
+vesta
+ÁÌØÑÎÓ
+áÆÒÉËÁ
+óÅ×ÅÒ
+ãÁÐÌÑ
+àÇ
diff --git a/src/test/locale/koi8-r/expected/test-koi8-text.sql.out b/src/test/locale/koi8-r/expected/test-koi8-text.sql.out
new file mode 100644
index 0000000..3dc3acf
--- /dev/null
+++ b/src/test/locale/koi8-r/expected/test-koi8-text.sql.out
@@ -0,0 +1,54 @@
+ abbrev | name_en | name_ru
+--------+----------------------+------------------------------------------------
+ ID | Idaho | áÊÄÁÈÏ
+ IA | Iowa | áÊÏ×Á
+ AL | Alabama | áÌÁÂÁÍÁ
+ AK | Alaska | áÌÑÓËÁ
+ AZ | Arizona | áÒÉÚÏÎÁ
+ AR | Arkansas | áÒËÁÎÚÁÓ
+ WY | Wyoming | ÷ÁÊÏÍÉÎÇ
+ WA | Washington | ÷ÁÛÉÎÇÔÏÎ
+ VT | Vermont | ÷ÅÒÍÏÎÔ
+ VA | Virginia | փ񀅃냄
+ WI | Wisconsin | ÷ÉÓËÏÎÓÉÎ
+ DE | Delaware | äÅÌÁ×ÜÒ
+ GA | Georgia | äÖÏÒÄÖÉÑ
+ WV | West Virginia | úÁÐÁÄÎÁÑ ÷ÉÒÄÖÉÎÉÑ
+ IL | Illinois | éÌÌÉÎÏÊÓ
+ IN | Indiana | éÎÄÉÁÎÁ
+ CA | California | ëÁÌÉÆÏÒÎÉÑ
+ KA | Kansas | ëÁÎÚÁÓ
+ KY | Kentucky | ëÅÎÔÕËËÉ
+ CO | Colorado | ëÏÌÏÒÁÄÏ
+ CT | Connecticut | ëÏÎÎÅËÔÉËÕÔ
+ LA | Louisiana | ìÕÉÚÉÁÎÁ
+ MA | Massachusetts | íÁÓÓÁÞÕÓÅÔÓ
+ MN | Minnesota | íÉÎÎÅÓÏÔÁ
+ MS | Mississippi | íÉÓÓÉÓÉÐÉ
+ MO | Missouri | íÉÓÓÕÒÉ
+ MI | Michigan | íÉÞÉÇÁÎ
+ MT | Montana | íÏÎÔÁÎÁ
+ ME | Maine | íÜÎ
+ MD | Maryland | íÜÒÉÌÅÎÄ
+ NE | Nebraska | îÅÂÒÁÓËÁ
+ NV | Nevada | îÅ×ÁÄÁ
+ NH | New Hampshire | îØÀ-çÜÍÐÛÉÒ
+ NJ | New Jersey | îØÀ-äÖÅÒÓÉ
+ NY | New York | îØÀ-êÏÒË
+ NM | New Mexico | îØÀ-íÅËÓÉËÏ
+ OH | Ohio | ïÇÁÊÏ
+ OK | Oklahoma | ïËÌÁÈÏÍÁ
+ DC | Washington DC | ÏËÒÕÇ ëÏÌÕÍÂÉÑ (ÓÏÚÄÁÎ ÓÐÅÃÉÁÌØÎÏ ÐÏÄ ÓÔÏÌÉÃÕ)
+ OR | Oregon | ïÒÅÇÏÎ
+ PA | Pennsylvania | ðÅÎÓÉÌØ×ÁÎÉÑ
+ RI | Rhode Island | òÏÄ-áÊÌÅÎÄ
+ ND | North Dakota | óÅ×ÅÒÎÁÑ äÁËÏÔÁ
+ NC | North Carolina | óÅ×ÅÒÎÁÑ ëÁÒÏÌÉÎÁ
+ TN | Tennessee | ôÅÎÎÅÓÓÉ
+ TX | Texas | ôÅÈÁÓ
+ FL | Florida | æÌÏÒÉÄÁ
+ SD | South Dakota | àÖÎÁÑ äÁËÏÔÁ
+ SC | South Carolina | àÖÎÁÑ ëÁÒÏÌÉÎÁ
+ UT | Utah | àÔÁ
+(50 rows)
+
diff --git a/src/test/locale/koi8-r/expected/test-koi8-varchar.sql.out b/src/test/locale/koi8-r/expected/test-koi8-varchar.sql.out
new file mode 100644
index 0000000..3dc3acf
--- /dev/null
+++ b/src/test/locale/koi8-r/expected/test-koi8-varchar.sql.out
@@ -0,0 +1,54 @@
+ abbrev | name_en | name_ru
+--------+----------------------+------------------------------------------------
+ ID | Idaho | áÊÄÁÈÏ
+ IA | Iowa | áÊÏ×Á
+ AL | Alabama | áÌÁÂÁÍÁ
+ AK | Alaska | áÌÑÓËÁ
+ AZ | Arizona | áÒÉÚÏÎÁ
+ AR | Arkansas | áÒËÁÎÚÁÓ
+ WY | Wyoming | ÷ÁÊÏÍÉÎÇ
+ WA | Washington | ÷ÁÛÉÎÇÔÏÎ
+ VT | Vermont | ÷ÅÒÍÏÎÔ
+ VA | Virginia | փ񀅃냄
+ WI | Wisconsin | ÷ÉÓËÏÎÓÉÎ
+ DE | Delaware | äÅÌÁ×ÜÒ
+ GA | Georgia | äÖÏÒÄÖÉÑ
+ WV | West Virginia | úÁÐÁÄÎÁÑ ÷ÉÒÄÖÉÎÉÑ
+ IL | Illinois | éÌÌÉÎÏÊÓ
+ IN | Indiana | éÎÄÉÁÎÁ
+ CA | California | ëÁÌÉÆÏÒÎÉÑ
+ KA | Kansas | ëÁÎÚÁÓ
+ KY | Kentucky | ëÅÎÔÕËËÉ
+ CO | Colorado | ëÏÌÏÒÁÄÏ
+ CT | Connecticut | ëÏÎÎÅËÔÉËÕÔ
+ LA | Louisiana | ìÕÉÚÉÁÎÁ
+ MA | Massachusetts | íÁÓÓÁÞÕÓÅÔÓ
+ MN | Minnesota | íÉÎÎÅÓÏÔÁ
+ MS | Mississippi | íÉÓÓÉÓÉÐÉ
+ MO | Missouri | íÉÓÓÕÒÉ
+ MI | Michigan | íÉÞÉÇÁÎ
+ MT | Montana | íÏÎÔÁÎÁ
+ ME | Maine | íÜÎ
+ MD | Maryland | íÜÒÉÌÅÎÄ
+ NE | Nebraska | îÅÂÒÁÓËÁ
+ NV | Nevada | îÅ×ÁÄÁ
+ NH | New Hampshire | îØÀ-çÜÍÐÛÉÒ
+ NJ | New Jersey | îØÀ-äÖÅÒÓÉ
+ NY | New York | îØÀ-êÏÒË
+ NM | New Mexico | îØÀ-íÅËÓÉËÏ
+ OH | Ohio | ïÇÁÊÏ
+ OK | Oklahoma | ïËÌÁÈÏÍÁ
+ DC | Washington DC | ÏËÒÕÇ ëÏÌÕÍÂÉÑ (ÓÏÚÄÁÎ ÓÐÅÃÉÁÌØÎÏ ÐÏÄ ÓÔÏÌÉÃÕ)
+ OR | Oregon | ïÒÅÇÏÎ
+ PA | Pennsylvania | ðÅÎÓÉÌØ×ÁÎÉÑ
+ RI | Rhode Island | òÏÄ-áÊÌÅÎÄ
+ ND | North Dakota | óÅ×ÅÒÎÁÑ äÁËÏÔÁ
+ NC | North Carolina | óÅ×ÅÒÎÁÑ ëÁÒÏÌÉÎÁ
+ TN | Tennessee | ôÅÎÎÅÓÓÉ
+ TX | Texas | ôÅÈÁÓ
+ FL | Florida | æÌÏÒÉÄÁ
+ SD | South Dakota | àÖÎÁÑ äÁËÏÔÁ
+ SC | South Carolina | àÖÎÁÑ ëÁÒÏÌÉÎÁ
+ UT | Utah | àÔÁ
+(50 rows)
+
diff --git a/src/test/locale/koi8-r/runall b/src/test/locale/koi8-r/runall
new file mode 100755
index 0000000..5f420d6
--- /dev/null
+++ b/src/test/locale/koi8-r/runall
@@ -0,0 +1,49 @@
+#! /bin/sh
+
+PATH=..:$PATH
+
+echo "Testing PostgreSQL compilation..."
+
+LC_CTYPE=ru_RU.KOI8-R
+LC_COLLATE=$LC_CTYPE
+export LC_CTYPE LC_COLLATE
+
+echo "Testing LC_CTYPE..."
+if ! test-ctype > koi8-ctype.out; then
+ exit 1
+fi
+diff expected/koi8-ctype.out koi8-ctype.out
+
+echo "Testing LC_COLLATE..."
+perl ../sort-test.pl test-koi8-sort.in > test-koi8-sort.out
+diff expected/test-koi8-sort.out test-koi8-sort.out
+
+### If you have Python - uncomment the following two lines
+#python ../sort-test.py test-koi8-sort.in > test-koi8-sort.out
+#diff expected/test-koi8-sort.out test-koi8-sort.out
+
+
+abort() {
+ [ "$1" ] && echo "$*"
+ exit 1
+}
+
+for f in char varchar text; do
+ if echo $f | grep -q char; then
+ ftype="$f(60)"
+ else
+ ftype="$f"
+ fi
+ echo "Testing PgSQL: sort on $ftype type..."
+
+ dropdb testlocale >/dev/null 2>&1
+ createdb testlocale || abort "createdb failed"
+ psql -X -d testlocale -c "CREATE TABLE usastates (abbrev char(2), name_en char(20), name_ru $ftype);" >/dev/null 2>&1 || abort "createtable failed"
+ psql -X testlocale < test-koi8.sql.in > test-koi8-$f.sql.out 2>/dev/null || abort "test query failed"
+ diff expected/test-koi8-$f.sql.out test-koi8-$f.sql.out
+done
+echo "Testing PgSQL: select on regexp..."
+psql -X testlocale < test-koi8-select.sql.in > test-koi8-select.sql.out 2>/dev/null || abort "select query failed"
+diff expected/test-koi8-select.sql.out test-koi8-select.sql.out
+dropdb testlocale || abort "dropdb failed"
+echo "Finished."
diff --git a/src/test/locale/koi8-r/test-koi8-select.sql.in b/src/test/locale/koi8-r/test-koi8-select.sql.in
new file mode 100644
index 0000000..5cda505
--- /dev/null
+++ b/src/test/locale/koi8-r/test-koi8-select.sql.in
@@ -0,0 +1 @@
+SELECT * FROM usastates WHERE name_ru ~* '^Ï.*' ORDER BY name_ru;
diff --git a/src/test/locale/koi8-r/test-koi8-sort.in b/src/test/locale/koi8-r/test-koi8-sort.in
new file mode 100644
index 0000000..3394e59
--- /dev/null
+++ b/src/test/locale/koi8-r/test-koi8-sort.in
@@ -0,0 +1,9 @@
+Vesta
+vesta
+àÇ
+ÁÌØÑÎÓ
+áÆÒÉËÁ
+óÅ×ÅÒ
+ãÁÐÌÑ
+Bording
+hoarding
diff --git a/src/test/locale/koi8-r/test-koi8.sql.in b/src/test/locale/koi8-r/test-koi8.sql.in
new file mode 100644
index 0000000..6be8392
--- /dev/null
+++ b/src/test/locale/koi8-r/test-koi8.sql.in
@@ -0,0 +1,53 @@
+COPY usastates FROM stdin WITH DELIMITER '|';
+AK|Alaska |áÌÑÓËÁ
+WA|Washington |÷ÁÛÉÎÇÔÏÎ
+OR|Oregon |ïÒÅÇÏÎ
+CA|California |ëÁÌÉÆÏÒÎÉÑ
+NV|Nevada |îÅ×ÁÄÁ
+ID|Idaho |áÊÄÁÈÏ
+UT|Utah |àÔÁ
+AZ|Arizona |áÒÉÚÏÎÁ
+MT|Montana |íÏÎÔÁÎÁ
+WY|Wyoming |÷ÁÊÏÍÉÎÇ
+CO|Colorado |ëÏÌÏÒÁÄÏ
+NM|New Mexico |îØÀ-íÅËÓÉËÏ
+ND|North Dakota |óÅ×ÅÒÎÁÑ äÁËÏÔÁ
+SD|South Dakota |àÖÎÁÑ äÁËÏÔÁ
+NE|Nebraska |îÅÂÒÁÓËÁ
+KA|Kansas |ëÁÎÚÁÓ
+OK|Oklahoma |ïËÌÁÈÏÍÁ
+TX|Texas |ôÅÈÁÓ
+MN|Minnesota |íÉÎÎÅÓÏÔÁ
+IA|Iowa |áÊÏ×Á
+MO|Missouri |íÉÓÓÕÒÉ
+AR|Arkansas |áÒËÁÎÚÁÓ
+LA|Louisiana |ìÕÉÚÉÁÎÁ
+WI|Wisconsin |÷ÉÓËÏÎÓÉÎ
+IL|Illinois |éÌÌÉÎÏÊÓ
+IN|Indiana |éÎÄÉÁÎÁ
+MS|Mississippi |íÉÓÓÉÓÉÐÉ
+AL|Alabama |áÌÁÂÁÍÁ
+MI|Michigan |íÉÞÉÇÁÎ
+OH|Ohio |ïÇÁÊÏ
+KY|Kentucky |ëÅÎÔÕËËÉ
+TN|Tennessee |ôÅÎÎÅÓÓÉ
+GA|Georgia |äÖÏÒÄÖÉÑ
+FL|Florida |æÌÏÒÉÄÁ
+PA|Pennsylvania |ðÅÎÓÉÌØ×ÁÎÉÑ
+WV|West Virginia |úÁÐÁÄÎÁÑ ÷ÉÒÄÖÉÎÉÑ
+VA|Virginia |փ񀅃냄
+NC|North Carolina|óÅ×ÅÒÎÁÑ ëÁÒÏÌÉÎÁ
+SC|South Carolina|àÖÎÁÑ ëÁÒÏÌÉÎÁ
+NY|New York |îØÀ-êÏÒË
+NJ|New Jersey |îØÀ-äÖÅÒÓÉ
+DE|Delaware |äÅÌÁ×ÜÒ
+MD|Maryland |íÜÒÉÌÅÎÄ
+DC|Washington DC |ÏËÒÕÇ ëÏÌÕÍÂÉÑ (ÓÏÚÄÁÎ ÓÐÅÃÉÁÌØÎÏ ÐÏÄ ÓÔÏÌÉÃÕ)
+VT|Vermont |÷ÅÒÍÏÎÔ
+MA|Massachusetts |íÁÓÓÁÞÕÓÅÔÓ
+CT|Connecticut |ëÏÎÎÅËÔÉËÕÔ
+ME|Maine |íÜÎ
+NH|New Hampshire |îØÀ-çÜÍÐÛÉÒ
+RI|Rhode Island |òÏÄ-áÊÌÅÎÄ
+\.
+SELECT * FROM usastates ORDER BY name_ru;
diff --git a/src/test/locale/koi8-to-win1251/Makefile b/src/test/locale/koi8-to-win1251/Makefile
new file mode 100644
index 0000000..28a72b7
--- /dev/null
+++ b/src/test/locale/koi8-to-win1251/Makefile
@@ -0,0 +1,7 @@
+all:
+
+test:
+ ./runall
+
+clean:
+ rm -f *.out
diff --git a/src/test/locale/koi8-to-win1251/README b/src/test/locale/koi8-to-win1251/README
new file mode 100644
index 0000000..0737803
--- /dev/null
+++ b/src/test/locale/koi8-to-win1251/README
@@ -0,0 +1,6 @@
+src/test/locale/koi8-to-win1251/README
+
+koi8-to-win1251 test. The database should be created in koi8 (createdb -E koi8),
+test uses koi8-to-win1251 converting feature.
+Created by Oleg Broytmann <phd2@earthling.net>. Code for encodings
+converting created by Tatsuo Ishii <t-ishii@sra.co.jp>.
diff --git a/src/test/locale/koi8-to-win1251/expected/test-koi8-char.sql.out b/src/test/locale/koi8-to-win1251/expected/test-koi8-char.sql.out
new file mode 100644
index 0000000..52d8b33
--- /dev/null
+++ b/src/test/locale/koi8-to-win1251/expected/test-koi8-char.sql.out
@@ -0,0 +1,54 @@
+ abbrev | name_en | name_ru
+--------+----------------------+--------------------------------------------------------------
+ ID | Idaho | Àéäàõî
+ IA | Iowa | Àéîâà
+ AL | Alabama | Àëàáàìà
+ AK | Alaska | Àëÿñêà
+ AZ | Arizona | Àðèçîíà
+ AR | Arkansas | Àðêàíçàñ
+ WY | Wyoming | Âàéîìèíã
+ WA | Washington | Âàøèíãòîí
+ VT | Vermont | Âåðìîíò
+ VA | Virginia | Âèðäæèíèÿ
+ WI | Wisconsin | Âèñêîíñèí
+ DE | Delaware | Äåëàâýð
+ GA | Georgia | Äæîðäæèÿ
+ WV | West Virginia | Çàïàäíàÿ Âèðäæèíèÿ
+ IL | Illinois | Èëëèíîéñ
+ IN | Indiana | Èíäèàíà
+ CA | California | Êàëèôîðíèÿ
+ KA | Kansas | Êàíçàñ
+ KY | Kentucky | Êåíòóêêè
+ CO | Colorado | Êîëîðàäî
+ CT | Connecticut | Êîííåêòèêóò
+ LA | Louisiana | Ëóèçèàíà
+ MA | Massachusetts | Ìàññà÷óñåòñ
+ MN | Minnesota | Ìèííåñîòà
+ MS | Mississippi | Ìèññèñèïè
+ MO | Missouri | Ìèññóðè
+ MI | Michigan | Ìè÷èãàí
+ MT | Montana | Ìîíòàíà
+ ME | Maine | Ìýí
+ MD | Maryland | Ìýðèëåíä
+ NE | Nebraska | Íåáðàñêà
+ NV | Nevada | Íåâàäà
+ NH | New Hampshire | Íüþ-Ãýìïøèð
+ NJ | New Jersey | Íüþ-Äæåðñè
+ NY | New York | Íüþ-Éîðê
+ NM | New Mexico | Íüþ-Ìåêñèêî
+ OH | Ohio | Îãàéî
+ OK | Oklahoma | Îêëàõîìà
+ DC | Washington DC | îêðóã Êîëóìáèÿ (ñîçäàí ñïåöèàëüíî ïîä ñòîëèöó)
+ OR | Oregon | Îðåãîí
+ PA | Pennsylvania | Ïåíñèëüâàíèÿ
+ RI | Rhode Island | Ðîä-Àéëåíä
+ ND | North Dakota | Ñåâåðíàÿ Äàêîòà
+ NC | North Carolina | Ñåâåðíàÿ Êàðîëèíà
+ TN | Tennessee | Òåííåññè
+ TX | Texas | Òåõàñ
+ FL | Florida | Ôëîðèäà
+ SD | South Dakota | Þæíàÿ Äàêîòà
+ SC | South Carolina | Þæíàÿ Êàðîëèíà
+ UT | Utah | Þòà
+(50 rows)
+
diff --git a/src/test/locale/koi8-to-win1251/expected/test-koi8-select.sql.out b/src/test/locale/koi8-to-win1251/expected/test-koi8-select.sql.out
new file mode 100644
index 0000000..0feffa2
--- /dev/null
+++ b/src/test/locale/koi8-to-win1251/expected/test-koi8-select.sql.out
@@ -0,0 +1,8 @@
+ abbrev | name_en | name_ru
+--------+----------------------+------------------------------------------------
+ OH | Ohio | Îãàéî
+ OK | Oklahoma | Îêëàõîìà
+ DC | Washington DC | îêðóã Êîëóìáèÿ (ñîçäàí ñïåöèàëüíî ïîä ñòîëèöó)
+ OR | Oregon | Îðåãîí
+(4 rows)
+
diff --git a/src/test/locale/koi8-to-win1251/expected/test-koi8-sort.out b/src/test/locale/koi8-to-win1251/expected/test-koi8-sort.out
new file mode 100644
index 0000000..72242cc
--- /dev/null
+++ b/src/test/locale/koi8-to-win1251/expected/test-koi8-sort.out
@@ -0,0 +1,9 @@
+Bording
+hoarding
+Vesta
+vesta
+Öàïëÿ
+Þã
+àëüÿíñ
+Àôðèêà
+Ñåâåð
diff --git a/src/test/locale/koi8-to-win1251/expected/test-koi8-text.sql.out b/src/test/locale/koi8-to-win1251/expected/test-koi8-text.sql.out
new file mode 100644
index 0000000..eccc1cb
--- /dev/null
+++ b/src/test/locale/koi8-to-win1251/expected/test-koi8-text.sql.out
@@ -0,0 +1,54 @@
+ abbrev | name_en | name_ru
+--------+----------------------+------------------------------------------------
+ ID | Idaho | Àéäàõî
+ IA | Iowa | Àéîâà
+ AL | Alabama | Àëàáàìà
+ AK | Alaska | Àëÿñêà
+ AZ | Arizona | Àðèçîíà
+ AR | Arkansas | Àðêàíçàñ
+ WY | Wyoming | Âàéîìèíã
+ WA | Washington | Âàøèíãòîí
+ VT | Vermont | Âåðìîíò
+ VA | Virginia | Âèðäæèíèÿ
+ WI | Wisconsin | Âèñêîíñèí
+ DE | Delaware | Äåëàâýð
+ GA | Georgia | Äæîðäæèÿ
+ WV | West Virginia | Çàïàäíàÿ Âèðäæèíèÿ
+ IL | Illinois | Èëëèíîéñ
+ IN | Indiana | Èíäèàíà
+ CA | California | Êàëèôîðíèÿ
+ KA | Kansas | Êàíçàñ
+ KY | Kentucky | Êåíòóêêè
+ CO | Colorado | Êîëîðàäî
+ CT | Connecticut | Êîííåêòèêóò
+ LA | Louisiana | Ëóèçèàíà
+ MA | Massachusetts | Ìàññà÷óñåòñ
+ MN | Minnesota | Ìèííåñîòà
+ MS | Mississippi | Ìèññèñèïè
+ MO | Missouri | Ìèññóðè
+ MI | Michigan | Ìè÷èãàí
+ MT | Montana | Ìîíòàíà
+ ME | Maine | Ìýí
+ MD | Maryland | Ìýðèëåíä
+ NE | Nebraska | Íåáðàñêà
+ NV | Nevada | Íåâàäà
+ NH | New Hampshire | Íüþ-Ãýìïøèð
+ NJ | New Jersey | Íüþ-Äæåðñè
+ NY | New York | Íüþ-Éîðê
+ NM | New Mexico | Íüþ-Ìåêñèêî
+ OH | Ohio | Îãàéî
+ OK | Oklahoma | Îêëàõîìà
+ DC | Washington DC | îêðóã Êîëóìáèÿ (ñîçäàí ñïåöèàëüíî ïîä ñòîëèöó)
+ OR | Oregon | Îðåãîí
+ PA | Pennsylvania | Ïåíñèëüâàíèÿ
+ RI | Rhode Island | Ðîä-Àéëåíä
+ ND | North Dakota | Ñåâåðíàÿ Äàêîòà
+ NC | North Carolina | Ñåâåðíàÿ Êàðîëèíà
+ TN | Tennessee | Òåííåññè
+ TX | Texas | Òåõàñ
+ FL | Florida | Ôëîðèäà
+ SD | South Dakota | Þæíàÿ Äàêîòà
+ SC | South Carolina | Þæíàÿ Êàðîëèíà
+ UT | Utah | Þòà
+(50 rows)
+
diff --git a/src/test/locale/koi8-to-win1251/expected/test-koi8-varchar.sql.out b/src/test/locale/koi8-to-win1251/expected/test-koi8-varchar.sql.out
new file mode 100644
index 0000000..eccc1cb
--- /dev/null
+++ b/src/test/locale/koi8-to-win1251/expected/test-koi8-varchar.sql.out
@@ -0,0 +1,54 @@
+ abbrev | name_en | name_ru
+--------+----------------------+------------------------------------------------
+ ID | Idaho | Àéäàõî
+ IA | Iowa | Àéîâà
+ AL | Alabama | Àëàáàìà
+ AK | Alaska | Àëÿñêà
+ AZ | Arizona | Àðèçîíà
+ AR | Arkansas | Àðêàíçàñ
+ WY | Wyoming | Âàéîìèíã
+ WA | Washington | Âàøèíãòîí
+ VT | Vermont | Âåðìîíò
+ VA | Virginia | Âèðäæèíèÿ
+ WI | Wisconsin | Âèñêîíñèí
+ DE | Delaware | Äåëàâýð
+ GA | Georgia | Äæîðäæèÿ
+ WV | West Virginia | Çàïàäíàÿ Âèðäæèíèÿ
+ IL | Illinois | Èëëèíîéñ
+ IN | Indiana | Èíäèàíà
+ CA | California | Êàëèôîðíèÿ
+ KA | Kansas | Êàíçàñ
+ KY | Kentucky | Êåíòóêêè
+ CO | Colorado | Êîëîðàäî
+ CT | Connecticut | Êîííåêòèêóò
+ LA | Louisiana | Ëóèçèàíà
+ MA | Massachusetts | Ìàññà÷óñåòñ
+ MN | Minnesota | Ìèííåñîòà
+ MS | Mississippi | Ìèññèñèïè
+ MO | Missouri | Ìèññóðè
+ MI | Michigan | Ìè÷èãàí
+ MT | Montana | Ìîíòàíà
+ ME | Maine | Ìýí
+ MD | Maryland | Ìýðèëåíä
+ NE | Nebraska | Íåáðàñêà
+ NV | Nevada | Íåâàäà
+ NH | New Hampshire | Íüþ-Ãýìïøèð
+ NJ | New Jersey | Íüþ-Äæåðñè
+ NY | New York | Íüþ-Éîðê
+ NM | New Mexico | Íüþ-Ìåêñèêî
+ OH | Ohio | Îãàéî
+ OK | Oklahoma | Îêëàõîìà
+ DC | Washington DC | îêðóã Êîëóìáèÿ (ñîçäàí ñïåöèàëüíî ïîä ñòîëèöó)
+ OR | Oregon | Îðåãîí
+ PA | Pennsylvania | Ïåíñèëüâàíèÿ
+ RI | Rhode Island | Ðîä-Àéëåíä
+ ND | North Dakota | Ñåâåðíàÿ Äàêîòà
+ NC | North Carolina | Ñåâåðíàÿ Êàðîëèíà
+ TN | Tennessee | Òåííåññè
+ TX | Texas | Òåõàñ
+ FL | Florida | Ôëîðèäà
+ SD | South Dakota | Þæíàÿ Äàêîòà
+ SC | South Carolina | Þæíàÿ Êàðîëèíà
+ UT | Utah | Þòà
+(50 rows)
+
diff --git a/src/test/locale/koi8-to-win1251/runall b/src/test/locale/koi8-to-win1251/runall
new file mode 100755
index 0000000..89306a8
--- /dev/null
+++ b/src/test/locale/koi8-to-win1251/runall
@@ -0,0 +1,46 @@
+#! /bin/sh
+
+PATH=..:$PATH
+
+echo "Testing PostgreSQL compilation..."
+
+LC_CTYPE=ru_RU.KOI8-R
+LC_COLLATE=$LC_CTYPE
+export LC_CTYPE LC_COLLATE
+
+PGCLIENTENCODING=win
+export PGCLIENTENCODING
+
+echo "Testing LC_COLLATE..."
+perl ../sort-test.pl test-koi8-sort.in > test-koi8-sort.out
+diff expected/test-koi8-sort.out test-koi8-sort.out
+
+### If you have Python - uncomment the following two lines
+#python ../sort-test.py test-koi8-sort.in > test-koi8-sort.out
+#diff expected/test-koi8-sort.out test-koi8-sort.out
+
+
+abort() {
+ [ "$1" ] && echo "$*"
+ exit 1
+}
+
+for f in char varchar text; do
+ if echo $f | grep -q char; then
+ ftype="$f(60)"
+ else
+ ftype="$f"
+ fi
+ echo "Testing PgSQL: sort on $ftype type..."
+
+ dropdb testlocale >/dev/null 2>&1
+ createdb testlocale || abort "createdb failed"
+ psql -X -d testlocale -c "CREATE TABLE usastates (abbrev char(2), name_en char(20), name_ru $ftype);" >/dev/null 2>&1 || abort "createtable failed"
+ psql -X testlocale < test-koi8.sql.in > test-koi8-$f.sql.out 2>/dev/null || abort "test query failed"
+ diff expected/test-koi8-$f.sql.out test-koi8-$f.sql.out
+done
+echo "Testing PgSQL: select on regexp..."
+psql -X testlocale < test-koi8-select.sql.in > test-koi8-select.sql.out 2>/dev/null || abort "select query failed"
+diff expected/test-koi8-select.sql.out test-koi8-select.sql.out
+dropdb testlocale || abort "dropdb failed"
+echo "Finished."
diff --git a/src/test/locale/koi8-to-win1251/test-koi8-select.sql.in b/src/test/locale/koi8-to-win1251/test-koi8-select.sql.in
new file mode 100644
index 0000000..22658c8
--- /dev/null
+++ b/src/test/locale/koi8-to-win1251/test-koi8-select.sql.in
@@ -0,0 +1 @@
+SELECT * FROM usastates WHERE name_ru ~* '^î.*' ORDER BY name_ru;
diff --git a/src/test/locale/koi8-to-win1251/test-koi8-sort.in b/src/test/locale/koi8-to-win1251/test-koi8-sort.in
new file mode 100644
index 0000000..3ce0d46
--- /dev/null
+++ b/src/test/locale/koi8-to-win1251/test-koi8-sort.in
@@ -0,0 +1,9 @@
+Vesta
+vesta
+Þã
+àëüÿíñ
+Àôðèêà
+Ñåâåð
+Öàïëÿ
+Bording
+hoarding
diff --git a/src/test/locale/koi8-to-win1251/test-koi8.sql.in b/src/test/locale/koi8-to-win1251/test-koi8.sql.in
new file mode 100644
index 0000000..244e495
--- /dev/null
+++ b/src/test/locale/koi8-to-win1251/test-koi8.sql.in
@@ -0,0 +1,53 @@
+COPY usastates FROM stdin WITH DELIMITER '|';
+AK|Alaska |Àëÿñêà
+WA|Washington |Âàøèíãòîí
+OR|Oregon |Îðåãîí
+CA|California |Êàëèôîðíèÿ
+NV|Nevada |Íåâàäà
+ID|Idaho |Àéäàõî
+UT|Utah |Þòà
+AZ|Arizona |Àðèçîíà
+MT|Montana |Ìîíòàíà
+WY|Wyoming |Âàéîìèíã
+CO|Colorado |Êîëîðàäî
+NM|New Mexico |Íüþ-Ìåêñèêî
+ND|North Dakota |Ñåâåðíàÿ Äàêîòà
+SD|South Dakota |Þæíàÿ Äàêîòà
+NE|Nebraska |Íåáðàñêà
+KA|Kansas |Êàíçàñ
+OK|Oklahoma |Îêëàõîìà
+TX|Texas |Òåõàñ
+MN|Minnesota |Ìèííåñîòà
+IA|Iowa |Àéîâà
+MO|Missouri |Ìèññóðè
+AR|Arkansas |Àðêàíçàñ
+LA|Louisiana |Ëóèçèàíà
+WI|Wisconsin |Âèñêîíñèí
+IL|Illinois |Èëëèíîéñ
+IN|Indiana |Èíäèàíà
+MS|Mississippi |Ìèññèñèïè
+AL|Alabama |Àëàáàìà
+MI|Michigan |Ìè÷èãàí
+OH|Ohio |Îãàéî
+KY|Kentucky |Êåíòóêêè
+TN|Tennessee |Òåííåññè
+GA|Georgia |Äæîðäæèÿ
+FL|Florida |Ôëîðèäà
+PA|Pennsylvania |Ïåíñèëüâàíèÿ
+WV|West Virginia |Çàïàäíàÿ Âèðäæèíèÿ
+VA|Virginia |Âèðäæèíèÿ
+NC|North Carolina|Ñåâåðíàÿ Êàðîëèíà
+SC|South Carolina|Þæíàÿ Êàðîëèíà
+NY|New York |Íüþ-Éîðê
+NJ|New Jersey |Íüþ-Äæåðñè
+DE|Delaware |Äåëàâýð
+MD|Maryland |Ìýðèëåíä
+DC|Washington DC |îêðóã Êîëóìáèÿ (ñîçäàí ñïåöèàëüíî ïîä ñòîëèöó)
+VT|Vermont |Âåðìîíò
+MA|Massachusetts |Ìàññà÷óñåòñ
+CT|Connecticut |Êîííåêòèêóò
+ME|Maine |Ìýí
+NH|New Hampshire |Íüþ-Ãýìïøèð
+RI|Rhode Island |Ðîä-Àéëåíä
+\.
+SELECT * FROM usastates ORDER BY name_ru;
diff --git a/src/test/locale/sort-test.pl b/src/test/locale/sort-test.pl
new file mode 100755
index 0000000..ba8d3f3
--- /dev/null
+++ b/src/test/locale/sort-test.pl
@@ -0,0 +1,16 @@
+#! /usr/bin/perl
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use locale;
+
+open(my $in_fh, '<', $ARGV[0]) || die;
+chop(my (@words) = <$in_fh>);
+close($in_fh);
+
+$" = "\n";
+my (@result) = sort @words;
+
+print "@result\n";
diff --git a/src/test/locale/sort-test.py b/src/test/locale/sort-test.py
new file mode 100755
index 0000000..5301903
--- /dev/null
+++ b/src/test/locale/sort-test.py
@@ -0,0 +1,18 @@
+#! /usr/bin/env python
+
+import sys, string, locale
+locale.setlocale(locale.LC_ALL, "")
+
+if len(sys.argv) != 2:
+ sys.stderr.write("Usage: sort.py filename\n")
+ sys.exit(1)
+
+infile = open(sys.argv[1], 'r')
+list = infile.readlines()
+infile.close()
+
+for i in range(0, len(list)):
+ list[i] = list[i][:-1] # chop!
+
+list.sort(key=locale.strxfrm)
+print('\n'.join(list))
diff --git a/src/test/locale/test-ctype.c b/src/test/locale/test-ctype.c
new file mode 100644
index 0000000..a3f896c
--- /dev/null
+++ b/src/test/locale/test-ctype.c
@@ -0,0 +1,79 @@
+/*
+ * src/test/locale/test-ctype.c
+ */
+
+/*
+
+ test-ctype.c
+
+Written by Oleg BroytMann, phd2@earthling.net
+ with help from Oleg Bartunov, oleg@sai.msu.su
+Copyright (C) 1998 PhiloSoft Design
+
+This is copyrighted but free software. You can use it, modify and distribute
+in original or modified form providing that the author's names and the above
+copyright notice will remain.
+
+Disclaimer, legal notice and absence of warranty.
+ This software provided "as is" without any kind of warranty. In no event
+the author shall be liable for any damage, etc.
+
+*/
+
+#include <stdio.h>
+#include <locale.h>
+#include <ctype.h>
+
+char *flag(int b);
+void describe_char(int c);
+
+#undef LONG_FLAG
+
+char *
+flag(int b)
+{
+#ifdef LONG_FLAG
+ return b ? "yes" : "no";
+#else
+ return b ? "+" : " ";
+#endif
+}
+
+void
+describe_char(int c)
+{
+ unsigned char cp = c,
+ up = toupper(c),
+ lo = tolower(c);
+
+ if (!isprint(cp))
+ cp = ' ';
+ if (!isprint(up))
+ up = ' ';
+ if (!isprint(lo))
+ lo = ' ';
+
+ printf("chr#%-4d%2c%6s%6s%6s%6s%6s%6s%6s%6s%6s%6s%6s%4c%4c\n", c, cp, flag(isalnum(c)), flag(isalpha(c)), flag(iscntrl(c)), flag(isdigit(c)), flag(islower(c)), flag(isgraph(c)), flag(isprint(c)), flag(ispunct(c)), flag(isspace(c)), flag(isupper(c)), flag(isxdigit(c)), lo, up);
+}
+
+int
+main()
+{
+ short c;
+ char *cur_locale;
+
+ cur_locale = setlocale(LC_ALL, "");
+ if (cur_locale)
+ fprintf(stderr, "Successfully set locale to \"%s\"\n", cur_locale);
+ else
+ {
+ fprintf(stderr, "Cannot setup locale. Either your libc does not provide\nlocale support, or your locale data is corrupt, or you have not set\nLANG or LC_CTYPE environment variable to proper value. Program aborted.\n");
+ return 1;
+ }
+
+ printf("char# char alnum alpha cntrl digit lower graph print punct space upper xdigit lo up\n");
+ for (c = 0; c <= 255; c++)
+ describe_char(c);
+
+ return 0;
+}
diff --git a/src/test/mb/README b/src/test/mb/README
new file mode 100644
index 0000000..632e8c3
--- /dev/null
+++ b/src/test/mb/README
@@ -0,0 +1,10 @@
+src/test/mb/README
+
+README for multibyte regression test
+ 1998/7/22
+ Tatsuo Ishii
+
+This directory contains a set of tests for multibyte supporting
+extensions for PostgreSQL. To run the test, simply type:
+
+% sh mbregress.sh
diff --git a/src/test/mb/expected/big5.out b/src/test/mb/expected/big5.out
new file mode 100644
index 0000000..97100cd
--- /dev/null
+++ b/src/test/mb/expected/big5.out
@@ -0,0 +1,84 @@
+drop table ¼t°Ó¸ê®Æ;
+create table ¼t°Ó¸ê®Æ (¦æ·~§O text, ¤½¥q©ïÀY varchar, ¦a§} varchar(16));
+create index ¼t°Ó¸ê®Æindex1 on ¼t°Ó¸ê®Æ using btree (¦æ·~§O);
+create index ¼t°Ó¸ê®Æindex2 on ¼t°Ó¸ê®Æ using hash (¤½¥q©ïÀY);
+insert into ¼t°Ó¸ê®Æ values ('¹q¸£·~', '¹F¹F¬ì§Þ', '¥_A01¤¯');
+insert into ¼t°Ó¸ê®Æ values ('»s³y·~', '°]·½¦³­­¤½¥q', '¤¤B10¤¤');
+insert into ¼t°Ó¸ê®Æ values ('À\¶¼·~', '¬ü¨ýªÑ¥÷¦³­­¤½¥q', '°ªZ01¤E');
+vacuum ¼t°Ó¸ê®Æ;
+select * from ¼t°Ó¸ê®Æ;
+ ¦æ·~§O | ¤½¥q©ïÀY | ¦a§}
+--------+------------------+---------
+ ¹q¸£·~ | ¹F¹F¬ì§Þ | ¥_A01¤¯
+ »s³y·~ | °]·½¦³­­¤½¥q | ¤¤B10¤¤
+ À\¶¼·~ | ¬ü¨ýªÑ¥÷¦³­­¤½¥q | °ªZ01¤E
+(3 rows)
+
+select * from ¼t°Ó¸ê®Æ where ¦a§} = '°ªZ01¤E';
+ ¦æ·~§O | ¤½¥q©ïÀY | ¦a§}
+--------+------------------+---------
+ À\¶¼·~ | ¬ü¨ýªÑ¥÷¦³­­¤½¥q | °ªZ01¤E
+(1 row)
+
+select * from ¼t°Ó¸ê®Æ where ¦a§} ~* '°ªz01¤E';
+ ¦æ·~§O | ¤½¥q©ïÀY | ¦a§}
+--------+------------------+---------
+ À\¶¼·~ | ¬ü¨ýªÑ¥÷¦³­­¤½¥q | °ªZ01¤E
+(1 row)
+
+select * from ¼t°Ó¸ê®Æ where ¦a§} like '_Z01_';
+ ¦æ·~§O | ¤½¥q©ïÀY | ¦a§}
+--------+------------------+---------
+ À\¶¼·~ | ¬ü¨ýªÑ¥÷¦³­­¤½¥q | °ªZ01¤E
+(1 row)
+
+select * from ¼t°Ó¸ê®Æ where ¦a§} like '_Z%';
+ ¦æ·~§O | ¤½¥q©ïÀY | ¦a§}
+--------+------------------+---------
+ À\¶¼·~ | ¬ü¨ýªÑ¥÷¦³­­¤½¥q | °ªZ01¤E
+(1 row)
+
+select * from ¼t°Ó¸ê®Æ where ¤½¥q©ïÀY ~ '¹F¹F¬ì[±H°O§Þ]';
+ ¦æ·~§O | ¤½¥q©ïÀY | ¦a§}
+--------+----------+---------
+ ¹q¸£·~ | ¹F¹F¬ì§Þ | ¥_A01¤¯
+(1 row)
+
+select * from ¼t°Ó¸ê®Æ where ¤½¥q©ïÀY ~* '¹F¹F¬ì[±H°O§Þ]';
+ ¦æ·~§O | ¤½¥q©ïÀY | ¦a§}
+--------+----------+---------
+ ¹q¸£·~ | ¹F¹F¬ì§Þ | ¥_A01¤¯
+(1 row)
+
+select *, character_length(¦æ·~§O) from ¼t°Ó¸ê®Æ;
+ ¦æ·~§O | ¤½¥q©ïÀY | ¦a§} | character_length
+--------+------------------+---------+------------------
+ ¹q¸£·~ | ¹F¹F¬ì§Þ | ¥_A01¤¯ | 3
+ »s³y·~ | °]·½¦³­­¤½¥q | ¤¤B10¤¤ | 3
+ À\¶¼·~ | ¬ü¨ýªÑ¥÷¦³­­¤½¥q | °ªZ01¤E | 3
+(3 rows)
+
+select *, octet_length(¦æ·~§O) from ¼t°Ó¸ê®Æ;
+ ¦æ·~§O | ¤½¥q©ïÀY | ¦a§} | octet_length
+--------+------------------+---------+--------------
+ ¹q¸£·~ | ¹F¹F¬ì§Þ | ¥_A01¤¯ | 6
+ »s³y·~ | °]·½¦³­­¤½¥q | ¤¤B10¤¤ | 6
+ À\¶¼·~ | ¬ü¨ýªÑ¥÷¦³­­¤½¥q | °ªZ01¤E | 6
+(3 rows)
+
+select *, position('¦³­­' in ¤½¥q©ïÀY) from ¼t°Ó¸ê®Æ;
+ ¦æ·~§O | ¤½¥q©ïÀY | ¦a§} | position
+--------+------------------+---------+----------
+ ¹q¸£·~ | ¹F¹F¬ì§Þ | ¥_A01¤¯ | 0
+ »s³y·~ | °]·½¦³­­¤½¥q | ¤¤B10¤¤ | 3
+ À\¶¼·~ | ¬ü¨ýªÑ¥÷¦³­­¤½¥q | °ªZ01¤E | 5
+(3 rows)
+
+select *, substring(¤½¥q©ïÀY from 3 for 6 ) from ¼t°Ó¸ê®Æ;
+ ¦æ·~§O | ¤½¥q©ïÀY | ¦a§} | substring
+--------+------------------+---------+--------------
+ ¹q¸£·~ | ¹F¹F¬ì§Þ | ¥_A01¤¯ | ¬ì§Þ
+ »s³y·~ | °]·½¦³­­¤½¥q | ¤¤B10¤¤ | ¦³­­¤½¥q
+ À\¶¼·~ | ¬ü¨ýªÑ¥÷¦³­­¤½¥q | °ªZ01¤E | ªÑ¥÷¦³­­¤½¥q
+(3 rows)
+
diff --git a/src/test/mb/expected/euc_cn.out b/src/test/mb/expected/euc_cn.out
new file mode 100644
index 0000000..4d046fa
--- /dev/null
+++ b/src/test/mb/expected/euc_cn.out
@@ -0,0 +1,87 @@
+drop table ¼ÆËã»úÊõÓï;
+ERROR: table "¼ÆËã»úÊõÓï" does not exist
+create table ¼ÆËã»úÊõÓï(ÊõÓï text, ·ÖÀàºÅ varchar, ±¸×¢1A char(16));
+create index ¼ÆËã»úÊõÓïindex1 on ¼ÆËã»úÊõÓï using btree(ÊõÓï);
+create index ¼ÆËã»úÊõÓïindex2 on ¼ÆËã»úÊõÓï using btree(·ÖÀàºÅ);
+insert into ¼ÆËã»úÊõÓï values('µçÄÔÏÔʾÆÁ','»úA01ÉÏ');
+insert into ¼ÆËã»úÊõÓï values('µçÄÔͼÐÎ','·ÖB01ÖÐ');
+insert into ¼ÆËã»úÊõÓï values('µçÄÔ³ÌÐòÔ±','ÈËZ01ÏÂ');
+vacuum ¼ÆËã»úÊõÓï;
+select * from ¼ÆËã»úÊõÓï;
+ ÊõÓï | ·ÖÀàºÅ | ±¸×¢1a
+------------+---------+--------
+ µçÄÔÏÔʾÆÁ | »úA01ÉÏ |
+ µçÄÔͼÐÎ | ·ÖB01ÖÐ |
+ µçÄÔ³ÌÐòÔ± | ÈËZ01Ï |
+(3 rows)
+
+select * from ¼ÆËã»úÊõÓï where ·ÖÀàºÅ = 'ÈËZ01ÏÂ';
+ ÊõÓï | ·ÖÀàºÅ | ±¸×¢1a
+------------+---------+--------
+ µçÄÔ³ÌÐòÔ± | ÈËZ01Ï |
+(1 row)
+
+select * from ¼ÆËã»úÊõÓï where ·ÖÀàºÅ ~* 'ÈËz01ÏÂ';
+ ÊõÓï | ·ÖÀàºÅ | ±¸×¢1a
+------------+---------+--------
+ µçÄÔ³ÌÐòÔ± | ÈËZ01Ï |
+(1 row)
+
+select * from ¼ÆËã»úÊõÓï where ·ÖÀàºÅ like '_Z01_';
+ ÊõÓï | ·ÖÀàºÅ | ±¸×¢1a
+------------+---------+--------
+ µçÄÔ³ÌÐòÔ± | ÈËZ01Ï |
+(1 row)
+
+select * from ¼ÆËã»úÊõÓï where ·ÖÀàºÅ like '_Z%';
+ ÊõÓï | ·ÖÀàºÅ | ±¸×¢1a
+------------+---------+--------
+ µçÄÔ³ÌÐòÔ± | ÈËZ01Ï |
+(1 row)
+
+select * from ¼ÆËã»úÊõÓï where ÊõÓï ~ 'µçÄÔ[ÏÔͼ]';
+ ÊõÓï | ·ÖÀàºÅ | ±¸×¢1a
+------------+---------+--------
+ µçÄÔÏÔʾÆÁ | »úA01ÉÏ |
+ µçÄÔͼÐÎ | ·ÖB01ÖÐ |
+(2 rows)
+
+select * from ¼ÆËã»úÊõÓï where ÊõÓï ~* 'µçÄÔ[ÏÔͼ]';
+ ÊõÓï | ·ÖÀàºÅ | ±¸×¢1a
+------------+---------+--------
+ µçÄÔÏÔʾÆÁ | »úA01ÉÏ |
+ µçÄÔͼÐÎ | ·ÖB01ÖÐ |
+(2 rows)
+
+select *,character_length(ÊõÓï) from ¼ÆËã»úÊõÓï;
+ ÊõÓï | ·ÖÀàºÅ | ±¸×¢1a | character_length
+------------+---------+--------+------------------
+ µçÄÔÏÔʾÆÁ | »úA01ÉÏ | | 5
+ µçÄÔͼÐÎ | ·ÖB01ÖÐ | | 4
+ µçÄÔ³ÌÐòÔ± | ÈËZ01Ï | | 5
+(3 rows)
+
+select *,octet_length(ÊõÓï) from ¼ÆËã»úÊõÓï;
+ ÊõÓï | ·ÖÀàºÅ | ±¸×¢1a | octet_length
+------------+---------+--------+--------------
+ µçÄÔÏÔʾÆÁ | »úA01ÉÏ | | 10
+ µçÄÔͼÐÎ | ·ÖB01ÖÐ | | 8
+ µçÄÔ³ÌÐòÔ± | ÈËZ01Ï | | 10
+(3 rows)
+
+select *,position('ÏÔ' in ÊõÓï) from ¼ÆËã»úÊõÓï;
+ ÊõÓï | ·ÖÀàºÅ | ±¸×¢1a | position
+------------+---------+--------+----------
+ µçÄÔÏÔʾÆÁ | »úA01ÉÏ | | 3
+ µçÄÔͼÐÎ | ·ÖB01ÖÐ | | 0
+ µçÄÔ³ÌÐòÔ± | ÈËZ01Ï | | 0
+(3 rows)
+
+select *,substring(ÊõÓï from 3 for 4) from ¼ÆËã»úÊõÓï;
+ ÊõÓï | ·ÖÀàºÅ | ±¸×¢1a | substring
+------------+---------+--------+-----------
+ µçÄÔÏÔʾÆÁ | »úA01ÉÏ | | ÏÔʾÆÁ
+ µçÄÔͼÐÎ | ·ÖB01ÖÐ | | ͼÐÎ
+ µçÄÔ³ÌÐòÔ± | ÈËZ01Ï | | ³ÌÐòÔ±
+(3 rows)
+
diff --git a/src/test/mb/expected/euc_jp.out b/src/test/mb/expected/euc_jp.out
new file mode 100644
index 0000000..15f83e0
--- /dev/null
+++ b/src/test/mb/expected/euc_jp.out
@@ -0,0 +1,87 @@
+drop table ·×»»µ¡ÍѸì;
+ERROR: table "·×»»µ¡ÍѸì" does not exist
+create table ·×»»µ¡ÍѸì (ÍѸì text, ʬÎॳ¡¼¥É varchar, È÷¹Í1A¤À¤è char(16));
+create index ·×»»µ¡ÍѸìindex1 on ·×»»µ¡ÍѸì using btree (ÍѸì);
+create index ·×»»µ¡ÍѸìindex2 on ·×»»µ¡ÍѸì using hash (ʬÎॳ¡¼¥É);
+insert into ·×»»µ¡ÍѸì values('¥³¥ó¥Ô¥å¡¼¥¿¥Ç¥£¥¹¥×¥ì¥¤','µ¡A01¾å');
+insert into ·×»»µ¡ÍѸì values('¥³¥ó¥Ô¥å¡¼¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹','ʬB10Ãæ');
+insert into ·×»»µ¡ÍѸì values('¥³¥ó¥Ô¥å¡¼¥¿¥×¥í¥°¥é¥Þ¡¼','¿ÍZ01²¼');
+vacuum ·×»»µ¡ÍѸì;
+select * from ·×»»µ¡ÍѸì;
+ ÍѸì | ʬÎॳ¡¼¥É | È÷¹Í1a¤À¤è
+----------------------------+------------+------------
+ ¥³¥ó¥Ô¥å¡¼¥¿¥Ç¥£¥¹¥×¥ì¥¤ | µ¡A01¾å |
+ ¥³¥ó¥Ô¥å¡¼¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ʬB10Ãæ |
+ ¥³¥ó¥Ô¥å¡¼¥¿¥×¥í¥°¥é¥Þ¡¼ | ¿ÍZ01²¼ |
+(3 rows)
+
+select * from ·×»»µ¡ÍѸì where ʬÎॳ¡¼¥É = '¿ÍZ01²¼';
+ ÍѸì | ʬÎॳ¡¼¥É | È÷¹Í1a¤À¤è
+--------------------------+------------+------------
+ ¥³¥ó¥Ô¥å¡¼¥¿¥×¥í¥°¥é¥Þ¡¼ | ¿ÍZ01²¼ |
+(1 row)
+
+select * from ·×»»µ¡ÍѸì where ʬÎॳ¡¼¥É ~* '¿Íz01²¼';
+ ÍѸì | ʬÎॳ¡¼¥É | È÷¹Í1a¤À¤è
+--------------------------+------------+------------
+ ¥³¥ó¥Ô¥å¡¼¥¿¥×¥í¥°¥é¥Þ¡¼ | ¿ÍZ01²¼ |
+(1 row)
+
+select * from ·×»»µ¡ÍѸì where ʬÎॳ¡¼¥É like '_Z01_';
+ ÍѸì | ʬÎॳ¡¼¥É | È÷¹Í1a¤À¤è
+--------------------------+------------+------------
+ ¥³¥ó¥Ô¥å¡¼¥¿¥×¥í¥°¥é¥Þ¡¼ | ¿ÍZ01²¼ |
+(1 row)
+
+select * from ·×»»µ¡ÍѸì where ʬÎॳ¡¼¥É like '_Z%';
+ ÍѸì | ʬÎॳ¡¼¥É | È÷¹Í1a¤À¤è
+--------------------------+------------+------------
+ ¥³¥ó¥Ô¥å¡¼¥¿¥×¥í¥°¥é¥Þ¡¼ | ¿ÍZ01²¼ |
+(1 row)
+
+select * from ·×»»µ¡ÍѸì where ÍѸì ~ '¥³¥ó¥Ô¥å¡¼¥¿[¥Ç¥°]';
+ ÍѸì | ʬÎॳ¡¼¥É | È÷¹Í1a¤À¤è
+----------------------------+------------+------------
+ ¥³¥ó¥Ô¥å¡¼¥¿¥Ç¥£¥¹¥×¥ì¥¤ | µ¡A01¾å |
+ ¥³¥ó¥Ô¥å¡¼¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ʬB10Ãæ |
+(2 rows)
+
+select * from ·×»»µ¡ÍѸì where ÍѸì ~* '¥³¥ó¥Ô¥å¡¼¥¿[¥Ç¥°]';
+ ÍѸì | ʬÎॳ¡¼¥É | È÷¹Í1a¤À¤è
+----------------------------+------------+------------
+ ¥³¥ó¥Ô¥å¡¼¥¿¥Ç¥£¥¹¥×¥ì¥¤ | µ¡A01¾å |
+ ¥³¥ó¥Ô¥å¡¼¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ʬB10Ãæ |
+(2 rows)
+
+select *,character_length(ÍѸì) from ·×»»µ¡ÍѸì;
+ ÍѸì | ʬÎॳ¡¼¥É | È÷¹Í1a¤À¤è | character_length
+----------------------------+------------+------------+------------------
+ ¥³¥ó¥Ô¥å¡¼¥¿¥Ç¥£¥¹¥×¥ì¥¤ | µ¡A01¾å | | 12
+ ¥³¥ó¥Ô¥å¡¼¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ʬB10Ãæ | | 13
+ ¥³¥ó¥Ô¥å¡¼¥¿¥×¥í¥°¥é¥Þ¡¼ | ¿ÍZ01²¼ | | 12
+(3 rows)
+
+select *,octet_length(ÍѸì) from ·×»»µ¡ÍѸì;
+ ÍѸì | ʬÎॳ¡¼¥É | È÷¹Í1a¤À¤è | octet_length
+----------------------------+------------+------------+--------------
+ ¥³¥ó¥Ô¥å¡¼¥¿¥Ç¥£¥¹¥×¥ì¥¤ | µ¡A01¾å | | 24
+ ¥³¥ó¥Ô¥å¡¼¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ʬB10Ãæ | | 26
+ ¥³¥ó¥Ô¥å¡¼¥¿¥×¥í¥°¥é¥Þ¡¼ | ¿ÍZ01²¼ | | 24
+(3 rows)
+
+select *,position('¥Ç' in ÍѸì) from ·×»»µ¡ÍѸì;
+ ÍѸì | ʬÎॳ¡¼¥É | È÷¹Í1a¤À¤è | position
+----------------------------+------------+------------+----------
+ ¥³¥ó¥Ô¥å¡¼¥¿¥Ç¥£¥¹¥×¥ì¥¤ | µ¡A01¾å | | 7
+ ¥³¥ó¥Ô¥å¡¼¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ʬB10Ãæ | | 0
+ ¥³¥ó¥Ô¥å¡¼¥¿¥×¥í¥°¥é¥Þ¡¼ | ¿ÍZ01²¼ | | 0
+(3 rows)
+
+select *,substring(ÍѸì from 10 for 4) from ·×»»µ¡ÍѸì;
+ ÍѸì | ʬÎॳ¡¼¥É | È÷¹Í1a¤À¤è | substring
+----------------------------+------------+------------+-----------
+ ¥³¥ó¥Ô¥å¡¼¥¿¥Ç¥£¥¹¥×¥ì¥¤ | µ¡A01¾å | | ¥×¥ì¥¤
+ ¥³¥ó¥Ô¥å¡¼¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ʬB10Ãæ | | ¥£¥Ã¥¯¥¹
+ ¥³¥ó¥Ô¥å¡¼¥¿¥×¥í¥°¥é¥Þ¡¼ | ¿ÍZ01²¼ | | ¥é¥Þ¡¼
+(3 rows)
+
diff --git a/src/test/mb/expected/euc_kr.out b/src/test/mb/expected/euc_kr.out
new file mode 100644
index 0000000..229ffd8
--- /dev/null
+++ b/src/test/mb/expected/euc_kr.out
@@ -0,0 +1,87 @@
+drop table ͪߩѦ¿ë¾î;
+ERROR: table "ͪߩѦ¿ë¾î" does not exist
+create table ͪߩѦ¿ë¾î (¿ë¾î text, ÝÂ×¾ÄÚµå varchar, ºñ°í1A¶ó±¸ char(16));
+create index ͪߩѦ¿ë¾îindex1 on ͪߩѦ¿ë¾î using btree (¿ë¾î);
+create index ͪߩѦ¿ë¾îindex2 on ͪߩѦ¿ë¾î using hash (ÝÂ×¾ÄÚµå);
+insert into ͪߩѦ¿ë¾î values('ÄÄÇ»Å͵ð½ºÇ÷¹ÀÌ', 'ѦA01ß¾');
+insert into ͪߩѦ¿ë¾î values('ÄÄÇ»Åͱ׷¡ÇȽº', 'ÝÂB10ñé');
+insert into ͪߩѦ¿ë¾î values('ÄÄÇ»ÅÍÇÁ·Î±×·¡¸Ó', 'ìÑZ01ù»');
+vacuum ͪߩѦ¿ë¾î;
+select * from ͪߩѦ¿ë¾î;
+ ¿ë¾î | ÝÂ×¾ÄÚµå | ºñ°í1a¶ó±¸
+------------------+----------+------------
+ ÄÄÇ»Å͵ð½ºÇ÷¹ÀÌ | ѦA01ß¾ |
+ ÄÄÇ»Åͱ׷¡ÇȽº | ÝÂB10ñé |
+ ÄÄÇ»ÅÍÇÁ·Î±×·¡¸Ó | ìÑZ01ù» |
+(3 rows)
+
+select * from ͪߩѦ¿ë¾î where ÝÂ×¾ÄÚµå = 'ìÑZ01ù»';
+ ¿ë¾î | ÝÂ×¾ÄÚµå | ºñ°í1a¶ó±¸
+------------------+----------+------------
+ ÄÄÇ»ÅÍÇÁ·Î±×·¡¸Ó | ìÑZ01ù» |
+(1 row)
+
+select * from ͪߩѦ¿ë¾î where ÝÂ×¾ÄÚµå ~* 'ìÑz01ù»';
+ ¿ë¾î | ÝÂ×¾ÄÚµå | ºñ°í1a¶ó±¸
+------------------+----------+------------
+ ÄÄÇ»ÅÍÇÁ·Î±×·¡¸Ó | ìÑZ01ù» |
+(1 row)
+
+select * from ͪߩѦ¿ë¾î where ÝÂ×¾ÄÚµå like '_Z01_';
+ ¿ë¾î | ÝÂ×¾ÄÚµå | ºñ°í1a¶ó±¸
+------------------+----------+------------
+ ÄÄÇ»ÅÍÇÁ·Î±×·¡¸Ó | ìÑZ01ù» |
+(1 row)
+
+select * from ͪߩѦ¿ë¾î where ÝÂ×¾ÄÚµå like '_Z%';
+ ¿ë¾î | ÝÂ×¾ÄÚµå | ºñ°í1a¶ó±¸
+------------------+----------+------------
+ ÄÄÇ»ÅÍÇÁ·Î±×·¡¸Ó | ìÑZ01ù» |
+(1 row)
+
+select * from ͪߩѦ¿ë¾î where ¿ë¾î ~ 'ÄÄÇ»ÅÍ[µð±×]';
+ ¿ë¾î | ÝÂ×¾ÄÚµå | ºñ°í1a¶ó±¸
+------------------+----------+------------
+ ÄÄÇ»Å͵ð½ºÇ÷¹ÀÌ | ѦA01ß¾ |
+ ÄÄÇ»Åͱ׷¡ÇȽº | ÝÂB10ñé |
+(2 rows)
+
+select * from ͪߩѦ¿ë¾î where ¿ë¾î ~* 'ÄÄÇ»ÅÍ[µð±×]';
+ ¿ë¾î | ÝÂ×¾ÄÚµå | ºñ°í1a¶ó±¸
+------------------+----------+------------
+ ÄÄÇ»Å͵ð½ºÇ÷¹ÀÌ | ѦA01ß¾ |
+ ÄÄÇ»Åͱ׷¡ÇȽº | ÝÂB10ñé |
+(2 rows)
+
+select *,character_length(¿ë¾î) from ͪߩѦ¿ë¾î;
+ ¿ë¾î | ÝÂ×¾ÄÚµå | ºñ°í1a¶ó±¸ | character_length
+------------------+----------+------------+------------------
+ ÄÄÇ»Å͵ð½ºÇ÷¹ÀÌ | ѦA01ß¾ | | 8
+ ÄÄÇ»Åͱ׷¡ÇȽº | ÝÂB10ñé | | 7
+ ÄÄÇ»ÅÍÇÁ·Î±×·¡¸Ó | ìÑZ01ù» | | 8
+(3 rows)
+
+select *,octet_length(¿ë¾î) from ͪߩѦ¿ë¾î;
+ ¿ë¾î | ÝÂ×¾ÄÚµå | ºñ°í1a¶ó±¸ | octet_length
+------------------+----------+------------+--------------
+ ÄÄÇ»Å͵ð½ºÇ÷¹ÀÌ | ѦA01ß¾ | | 16
+ ÄÄÇ»Åͱ׷¡ÇȽº | ÝÂB10ñé | | 14
+ ÄÄÇ»ÅÍÇÁ·Î±×·¡¸Ó | ìÑZ01ù» | | 16
+(3 rows)
+
+select *,position('µð' in ¿ë¾î) from ͪߩѦ¿ë¾î;
+ ¿ë¾î | ÝÂ×¾ÄÚµå | ºñ°í1a¶ó±¸ | position
+------------------+----------+------------+----------
+ ÄÄÇ»Å͵ð½ºÇ÷¹ÀÌ | ѦA01ß¾ | | 4
+ ÄÄÇ»Åͱ׷¡ÇȽº | ÝÂB10ñé | | 0
+ ÄÄÇ»ÅÍÇÁ·Î±×·¡¸Ó | ìÑZ01ù» | | 0
+(3 rows)
+
+select *,substring(¿ë¾î from 3 for 4) from ͪߩѦ¿ë¾î;
+ ¿ë¾î | ÝÂ×¾ÄÚµå | ºñ°í1a¶ó±¸ | substring
+------------------+----------+------------+-----------
+ ÄÄÇ»Å͵ð½ºÇ÷¹ÀÌ | ѦA01ß¾ | | Å͵ð½ºÇÃ
+ ÄÄÇ»Åͱ׷¡ÇȽº | ÝÂB10ñé | | Åͱ׷¡ÇÈ
+ ÄÄÇ»ÅÍÇÁ·Î±×·¡¸Ó | ìÑZ01ù» | | ÅÍÇÁ·Î±×
+(3 rows)
+
diff --git a/src/test/mb/expected/euc_tw.out b/src/test/mb/expected/euc_tw.out
new file mode 100644
index 0000000..99eb488
--- /dev/null
+++ b/src/test/mb/expected/euc_tw.out
@@ -0,0 +1,85 @@
+drop table ìÞÙ¸æñÕè;
+ERROR: table "ìÞÙ¸æñÕè" does not exist
+create table ìÞÙ¸æñÕè (ÈçäÆɱ text, ÄüƳÍñó¤ varchar, ÇâÉß varchar(16));
+create index ìÞÙ¸æñÕèindex1 on ìÞÙ¸æñÕè using btree (ÈçäÆɱ);
+create index ìÞÙ¸æñÕèindex2 on ìÞÙ¸æñÕè using hash (ÄüƳÍñó¤);
+insert into ìÞÙ¸æñÕè values ('çÙæªäÆ', 'ç®ç®ÒïÊÀ', 'Æ¡A01Äî');
+insert into ìÞÙ¸æñÕè values ('êûÝßäÆ', 'ØÂäãÈ´ÓîÄüƳ', 'ÄãB10Äã');
+insert into ìÞÙ¸æñÕè values ('ó§ã£äÆ', 'Ó¡ÌÀϴǹȴÓîÄüƳ', 'ØíZ01Ħ');
+vacuum ìÞÙ¸æñÕè;
+select * from ìÞÙ¸æñÕè;
+ ÈçäÆɱ | ÄüƳÍñó¤ | ÇâÉß
+--------+------------------+---------
+ çÙæªäÆ | ç®ç®ÒïÊÀ | Æ¡A01Äî
+ êûÝßäÆ | ØÂäãÈ´ÓîÄüƳ | ÄãB10Äã
+ ó§ã£äÆ | Ó¡ÌÀϴǹȴÓîÄüƳ | ØíZ01Ħ
+(3 rows)
+
+select * from ìÞÙ¸æñÕè where ÇâÉß = 'ØíZ01Ħ';
+ ÈçäÆɱ | ÄüƳÍñó¤ | ÇâÉß
+--------+------------------+---------
+ ó§ã£äÆ | Ó¡ÌÀϴǹȴÓîÄüƳ | ØíZ01Ħ
+(1 row)
+
+select * from ìÞÙ¸æñÕè where ÇâÉß ~* 'Øíz01Ħ';
+ ÈçäÆɱ | ÄüƳÍñó¤ | ÇâÉß
+--------+------------------+---------
+ ó§ã£äÆ | Ó¡ÌÀϴǹȴÓîÄüƳ | ØíZ01Ħ
+(1 row)
+
+select * from ìÞÙ¸æñÕè where ÇâÉß like '_Z01_';
+ ÈçäÆɱ | ÄüƳÍñó¤ | ÇâÉß
+--------+------------------+---------
+ ó§ã£äÆ | Ó¡ÌÀϴǹȴÓîÄüƳ | ØíZ01Ħ
+(1 row)
+
+select * from ìÞÙ¸æñÕè where ÇâÉß like '_Z%';
+ ÈçäÆɱ | ÄüƳÍñó¤ | ÇâÉß
+--------+------------------+---------
+ ó§ã£äÆ | Ó¡ÌÀϴǹȴÓîÄüƳ | ØíZ01Ħ
+(1 row)
+
+select * from ìÞÙ¸æñÕè where ÄüƳÍñó¤ ~ 'ç®ç®Òï[ÙìØ´ÊÀ]';
+ ÈçäÆɱ | ÄüƳÍñó¤ | ÇâÉß
+--------+----------+---------
+ çÙæªäÆ | ç®ç®ÒïÊÀ | Æ¡A01Äî
+(1 row)
+
+select * from ìÞÙ¸æñÕè where ÄüƳÍñó¤ ~* 'ç®ç®Òï[ÙìØ´ÊÀ]';
+ ÈçäÆɱ | ÄüƳÍñó¤ | ÇâÉß
+--------+----------+---------
+ çÙæªäÆ | ç®ç®ÒïÊÀ | Æ¡A01Äî
+(1 row)
+
+select *, character_length(ÈçäÆɱ) from ìÞÙ¸æñÕè;
+ ÈçäÆɱ | ÄüƳÍñó¤ | ÇâÉß | character_length
+--------+------------------+---------+------------------
+ çÙæªäÆ | ç®ç®ÒïÊÀ | Æ¡A01Äî | 3
+ êûÝßäÆ | ØÂäãÈ´ÓîÄüƳ | ÄãB10Äã | 3
+ ó§ã£äÆ | Ó¡ÌÀϴǹȴÓîÄüƳ | ØíZ01Ħ | 3
+(3 rows)
+
+select *, octet_length(ÈçäÆɱ) from ìÞÙ¸æñÕè;
+ ÈçäÆɱ | ÄüƳÍñó¤ | ÇâÉß | octet_length
+--------+------------------+---------+--------------
+ çÙæªäÆ | ç®ç®ÒïÊÀ | Æ¡A01Äî | 6
+ êûÝßäÆ | ØÂäãÈ´ÓîÄüƳ | ÄãB10Äã | 6
+ ó§ã£äÆ | Ó¡ÌÀϴǹȴÓîÄüƳ | ØíZ01Ħ | 6
+(3 rows)
+
+select *, position('È´Óî' in ÄüƳÍñó¤) from ìÞÙ¸æñÕè;
+ ÈçäÆɱ | ÄüƳÍñó¤ | ÇâÉß | position
+--------+------------------+---------+----------
+ çÙæªäÆ | ç®ç®ÒïÊÀ | Æ¡A01Äî | 0
+ êûÝßäÆ | ØÂäãÈ´ÓîÄüƳ | ÄãB10Äã | 3
+ ó§ã£äÆ | Ó¡ÌÀϴǹȴÓîÄüƳ | ØíZ01Ħ | 5
+(3 rows)
+
+select *, substring(ÄüƳÍñó¤ from 3 for 6 ) from ìÞÙ¸æñÕè;
+ ÈçäÆɱ | ÄüƳÍñó¤ | ÇâÉß | substring
+--------+------------------+---------+--------------
+ çÙæªäÆ | ç®ç®ÒïÊÀ | Æ¡A01Äî | ÒïÊÀ
+ êûÝßäÆ | ØÂäãÈ´ÓîÄüƳ | ÄãB10Äã | È´ÓîÄüƳ
+ ó§ã£äÆ | Ó¡ÌÀϴǹȴÓîÄüƳ | ØíZ01Ħ | ϴǹȴÓîÄüƳ
+(3 rows)
+
diff --git a/src/test/mb/expected/gb18030.out b/src/test/mb/expected/gb18030.out
new file mode 100644
index 0000000..b14234d
--- /dev/null
+++ b/src/test/mb/expected/gb18030.out
@@ -0,0 +1,86 @@
+drop table Ó‹Ëã™CÓÃÕZ;
+create table Ó‹Ëã™CÓÃÕZ (ÓÃÕZ text, ·Ö©`¥É varchar, ‚俼1A¤À¤è char(16));
+create index Ó‹Ëã™CÓÃÕZindex1 on Ó‹Ëã™CÓÃÕZ using btree (ÓÃÕZ);
+create index Ó‹Ëã™CÓÃÕZindex2 on Ó‹Ëã™CÓÃÕZ using hash (·Ö©`¥É);
+insert into Ó‹Ëã™CÓÃÕZ values('¥³¥ó¥Ô¥å©`¥¿¥Ç¥£¥¹¥×¥ì¥¤','™CA01ÉÏ');
+insert into Ó‹Ëã™CÓÃÕZ values('¥³¥ó¥Ô¥å©`¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹','·ÖB10ÖÐ');
+insert into Ó‹Ëã™CÓÃÕZ values('¥³¥ó¥Ô¥å©`¥¿¥×¥í¥°¥é¥Þ©`','ÈËZ01ÏÂ');
+vacuum Ó‹Ëã™CÓÃÕZ;
+select * from Ó‹Ëã™CÓÃÕZ;
+ ÓÃÕZ | ·Ö©`¥É | ‚俼1a¤À¤è
+----------------------------+------------+------------
+ ¥³¥ó¥Ô¥å©`¥¿¥Ç¥£¥¹¥×¥ì¥¤ | ™CA01ÉÏ |
+ ¥³¥ó¥Ô¥å©`¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ·ÖB10ÖÐ |
+ ¥³¥ó¥Ô¥å©`¥¿¥×¥í¥°¥é¥Þ©` | ÈËZ01Ï |
+(3 rows)
+
+select * from Ó‹Ëã™CÓÃÕZ where ·Ö©`¥É = 'ÈËZ01ÏÂ';
+ ÓÃÕZ | ·Ö©`¥É | ‚俼1a¤À¤è
+--------------------------+------------+------------
+ ¥³¥ó¥Ô¥å©`¥¿¥×¥í¥°¥é¥Þ©` | ÈËZ01Ï |
+(1 row)
+
+select * from Ó‹Ëã™CÓÃÕZ where ·Ö©`¥É ~* 'ÈËz01ÏÂ';
+ ÓÃÕZ | ·Ö©`¥É | ‚俼1a¤À¤è
+--------------------------+------------+------------
+ ¥³¥ó¥Ô¥å©`¥¿¥×¥í¥°¥é¥Þ©` | ÈËZ01Ï |
+(1 row)
+
+select * from Ó‹Ëã™CÓÃÕZ where ·Ö©`¥É like '_Z01_';
+ ÓÃÕZ | ·Ö©`¥É | ‚俼1a¤À¤è
+--------------------------+------------+------------
+ ¥³¥ó¥Ô¥å©`¥¿¥×¥í¥°¥é¥Þ©` | ÈËZ01Ï |
+(1 row)
+
+select * from Ó‹Ëã™CÓÃÕZ where ·Ö©`¥É like '_Z%';
+ ÓÃÕZ | ·Ö©`¥É | ‚俼1a¤À¤è
+--------------------------+------------+------------
+ ¥³¥ó¥Ô¥å©`¥¿¥×¥í¥°¥é¥Þ©` | ÈËZ01Ï |
+(1 row)
+
+select * from Ó‹Ëã™CÓÃÕZ where ÓÃÕZ ~ '¥³¥ó¥Ô¥å©`¥¿[¥Ç¥°]';
+ ÓÃÕZ | ·Ö©`¥É | ‚俼1a¤À¤è
+----------------------------+------------+------------
+ ¥³¥ó¥Ô¥å©`¥¿¥Ç¥£¥¹¥×¥ì¥¤ | ™CA01ÉÏ |
+ ¥³¥ó¥Ô¥å©`¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ·ÖB10ÖÐ |
+(2 rows)
+
+select * from Ó‹Ëã™CÓÃÕZ where ÓÃÕZ ~* '¥³¥ó¥Ô¥å©`¥¿[¥Ç¥°]';
+ ÓÃÕZ | ·Ö©`¥É | ‚俼1a¤À¤è
+----------------------------+------------+------------
+ ¥³¥ó¥Ô¥å©`¥¿¥Ç¥£¥¹¥×¥ì¥¤ | ™CA01ÉÏ |
+ ¥³¥ó¥Ô¥å©`¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ·ÖB10ÖÐ |
+(2 rows)
+
+select *,character_length(ÓÃÕZ) from Ó‹Ëã™CÓÃÕZ;
+ ÓÃÕZ | ·Ö©`¥É | ‚俼1a¤À¤è | character_length
+----------------------------+------------+------------+------------------
+ ¥³¥ó¥Ô¥å©`¥¿¥Ç¥£¥¹¥×¥ì¥¤ | ™CA01ÉÏ | | 12
+ ¥³¥ó¥Ô¥å©`¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ·ÖB10ÖÐ | | 13
+ ¥³¥ó¥Ô¥å©`¥¿¥×¥í¥°¥é¥Þ©` | ÈËZ01Ï | | 12
+(3 rows)
+
+select *,octet_length(ÓÃÕZ) from Ó‹Ëã™CÓÃÕZ;
+ ÓÃÕZ | ·Ö©`¥É | ‚俼1a¤À¤è | octet_length
+----------------------------+------------+------------+--------------
+ ¥³¥ó¥Ô¥å©`¥¿¥Ç¥£¥¹¥×¥ì¥¤ | ™CA01ÉÏ | | 36
+ ¥³¥ó¥Ô¥å©`¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ·ÖB10ÖÐ | | 39
+ ¥³¥ó¥Ô¥å©`¥¿¥×¥í¥°¥é¥Þ©` | ÈËZ01Ï | | 36
+(3 rows)
+
+select *,position('¥Ç' in ÓÃÕZ) from Ó‹Ëã™CÓÃÕZ;
+ ÓÃÕZ | ·Ö©`¥É | ‚俼1a¤À¤è | position
+----------------------------+------------+------------+----------
+ ¥³¥ó¥Ô¥å©`¥¿¥Ç¥£¥¹¥×¥ì¥¤ | ™CA01ÉÏ | | 7
+ ¥³¥ó¥Ô¥å©`¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ·ÖB10ÖÐ | | 0
+ ¥³¥ó¥Ô¥å©`¥¿¥×¥í¥°¥é¥Þ©` | ÈËZ01Ï | | 0
+(3 rows)
+
+select *,substring(ÓÃÕZ from 10 for 4) from Ó‹Ëã™CÓÃÕZ;
+ ÓÃÕZ | ·Ö©`¥É | ‚俼1a¤À¤è | substring
+----------------------------+------------+------------+-----------
+ ¥³¥ó¥Ô¥å©`¥¿¥Ç¥£¥¹¥×¥ì¥¤ | ™CA01ÉÏ | | ¥×¥ì¥¤
+ ¥³¥ó¥Ô¥å©`¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹ | ·ÖB10ÖÐ | | ¥£¥Ã¥¯¥¹
+ ¥³¥ó¥Ô¥å©`¥¿¥×¥í¥°¥é¥Þ©` | ÈËZ01Ï | | ¥é¥Þ©`
+(3 rows)
+
diff --git a/src/test/mb/expected/mule_internal.out b/src/test/mb/expected/mule_internal.out
new file mode 100644
index 0000000..ac8b57d
--- /dev/null
+++ b/src/test/mb/expected/mule_internal.out
@@ -0,0 +1,333 @@
+drop table ’·×’»»’µ¡’ÍÑ’¸ì;
+ERROR: table "’·×’»»’µ¡’ÍÑ’¸ì" does not exist
+create table ’·×’»»’µ¡’ÍÑ’¸ì (’ÍÑ’¸ì text, ’ʬ’Îà’¥³’¡¼’¥É varchar, ’È÷’¹Í1A’¤À’¤è char(16));
+create index ’·×’»»’µ¡’ÍÑ’¸ìindex1 on ’·×’»»’µ¡’ÍÑ’¸ì using btree (’ÍÑ’¸ì);
+create index ’·×’»»’µ¡’ÍÑ’¸ìindex2 on ’·×’»»’µ¡’ÍÑ’¸ì using hash (’ʬ’Îà’¥³’¡¼’¥É);
+insert into ’·×’»»’µ¡’ÍÑ’¸ì values('’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥Ç’¥£’¥¹’¥×’¥ì’¥¤','’µ¡A01’¾å');
+insert into ’·×’»»’µ¡’ÍÑ’¸ì values('’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥°’¥é’¥Õ’¥£’¥Ã’¥¯’¥¹','’ʬB10’Ãæ');
+insert into ’·×’»»’µ¡’ÍÑ’¸ì values('’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥×’¥í’¥°’¥é’¥Þ’¡¼','’¿ÍZ01’²¼');
+vacuum ’·×’»»’µ¡’ÍÑ’¸ì;
+select * from ’·×’»»’µ¡’ÍÑ’¸ì;
+ ’ÍÑ’¸ì | ’ʬ’Îà’¥³’¡¼’¥É | ’È÷’¹Í1a’¤À’¤è
+----------------------------+------------+------------
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥Ç’¥£’¥¹’¥×’¥ì’¥¤ | ’µ¡A01’¾å |
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥°’¥é’¥Õ’¥£’¥Ã’¥¯’¥¹ | ’ʬB10’Ãæ |
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥×’¥í’¥°’¥é’¥Þ’¡¼ | ’¿ÍZ01’²¼ |
+(3 rows)
+
+select * from ’·×’»»’µ¡’ÍÑ’¸ì where ’ʬ’Îà’¥³’¡¼’¥É = '’¿ÍZ01’²¼';
+ ’ÍÑ’¸ì | ’ʬ’Îà’¥³’¡¼’¥É | ’È÷’¹Í1a’¤À’¤è
+--------------------------+------------+------------
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥×’¥í’¥°’¥é’¥Þ’¡¼ | ’¿ÍZ01’²¼ |
+(1 row)
+
+select * from ’·×’»»’µ¡’ÍÑ’¸ì where ’ʬ’Îà’¥³’¡¼’¥É ~* '’¿Íz01’²¼';
+ ’ÍÑ’¸ì | ’ʬ’Îà’¥³’¡¼’¥É | ’È÷’¹Í1a’¤À’¤è
+--------------------------+------------+------------
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥×’¥í’¥°’¥é’¥Þ’¡¼ | ’¿ÍZ01’²¼ |
+(1 row)
+
+select * from ’·×’»»’µ¡’ÍÑ’¸ì where ’ʬ’Îà’¥³’¡¼’¥É like '_Z01_';
+ ’ÍÑ’¸ì | ’ʬ’Îà’¥³’¡¼’¥É | ’È÷’¹Í1a’¤À’¤è
+--------------------------+------------+------------
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥×’¥í’¥°’¥é’¥Þ’¡¼ | ’¿ÍZ01’²¼ |
+(1 row)
+
+select * from ’·×’»»’µ¡’ÍÑ’¸ì where ’ʬ’Îà’¥³’¡¼’¥É like '_Z%';
+ ’ÍÑ’¸ì | ’ʬ’Îà’¥³’¡¼’¥É | ’È÷’¹Í1a’¤À’¤è
+--------------------------+------------+------------
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥×’¥í’¥°’¥é’¥Þ’¡¼ | ’¿ÍZ01’²¼ |
+(1 row)
+
+select * from ’·×’»»’µ¡’ÍÑ’¸ì where ’ÍÑ’¸ì ~ '’¥³’¥ó’¥Ô’¥å’¡¼’¥¿[’¥Ç’¥°]';
+ ’ÍÑ’¸ì | ’ʬ’Îà’¥³’¡¼’¥É | ’È÷’¹Í1a’¤À’¤è
+----------------------------+------------+------------
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥Ç’¥£’¥¹’¥×’¥ì’¥¤ | ’µ¡A01’¾å |
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥°’¥é’¥Õ’¥£’¥Ã’¥¯’¥¹ | ’ʬB10’Ãæ |
+(2 rows)
+
+select * from ’·×’»»’µ¡’ÍÑ’¸ì where ’ÍÑ’¸ì ~* '’¥³’¥ó’¥Ô’¥å’¡¼’¥¿[’¥Ç’¥°]';
+ ’ÍÑ’¸ì | ’ʬ’Îà’¥³’¡¼’¥É | ’È÷’¹Í1a’¤À’¤è
+----------------------------+------------+------------
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥Ç’¥£’¥¹’¥×’¥ì’¥¤ | ’µ¡A01’¾å |
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥°’¥é’¥Õ’¥£’¥Ã’¥¯’¥¹ | ’ʬB10’Ãæ |
+(2 rows)
+
+select *,character_length(’ÍÑ’¸ì) from ’·×’»»’µ¡’ÍÑ’¸ì;
+ ’ÍÑ’¸ì | ’ʬ’Îà’¥³’¡¼’¥É | ’È÷’¹Í1a’¤À’¤è | character_length
+----------------------------+------------+------------+------------------
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥Ç’¥£’¥¹’¥×’¥ì’¥¤ | ’µ¡A01’¾å | | 12
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥°’¥é’¥Õ’¥£’¥Ã’¥¯’¥¹ | ’ʬB10’Ãæ | | 13
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥×’¥í’¥°’¥é’¥Þ’¡¼ | ’¿ÍZ01’²¼ | | 12
+(3 rows)
+
+select *,octet_length(’ÍÑ’¸ì) from ’·×’»»’µ¡’ÍÑ’¸ì;
+ ’ÍÑ’¸ì | ’ʬ’Îà’¥³’¡¼’¥É | ’È÷’¹Í1a’¤À’¤è | octet_length
+----------------------------+------------+------------+--------------
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥Ç’¥£’¥¹’¥×’¥ì’¥¤ | ’µ¡A01’¾å | | 36
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥°’¥é’¥Õ’¥£’¥Ã’¥¯’¥¹ | ’ʬB10’Ãæ | | 39
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥×’¥í’¥°’¥é’¥Þ’¡¼ | ’¿ÍZ01’²¼ | | 36
+(3 rows)
+
+select *,position('’¥Ç' in ’ÍÑ’¸ì) from ’·×’»»’µ¡’ÍÑ’¸ì;
+ ’ÍÑ’¸ì | ’ʬ’Îà’¥³’¡¼’¥É | ’È÷’¹Í1a’¤À’¤è | position
+----------------------------+------------+------------+----------
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥Ç’¥£’¥¹’¥×’¥ì’¥¤ | ’µ¡A01’¾å | | 7
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥°’¥é’¥Õ’¥£’¥Ã’¥¯’¥¹ | ’ʬB10’Ãæ | | 0
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥×’¥í’¥°’¥é’¥Þ’¡¼ | ’¿ÍZ01’²¼ | | 0
+(3 rows)
+
+select *,substring(’ÍÑ’¸ì from 10 for 4) from ’·×’»»’µ¡’ÍÑ’¸ì;
+ ’ÍÑ’¸ì | ’ʬ’Îà’¥³’¡¼’¥É | ’È÷’¹Í1a’¤À’¤è | substring
+----------------------------+------------+------------+-----------
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥Ç’¥£’¥¹’¥×’¥ì’¥¤ | ’µ¡A01’¾å | | ’¥×’¥ì’¥¤
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥°’¥é’¥Õ’¥£’¥Ã’¥¯’¥¹ | ’ʬB10’Ãæ | | ’¥£’¥Ã’¥¯’¥¹
+ ’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥×’¥í’¥°’¥é’¥Þ’¡¼ | ’¿ÍZ01’²¼ | | ’¥é’¥Þ’¡¼
+(3 rows)
+
+drop table ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+ERROR: table "‘¼Æ‘Ëã‘»ú‘Êõ‘Óï" does not exist
+create table ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï(‘Êõ‘Óï text, ‘·Ö‘Àà‘ºÅ varchar, ‘±¸‘×¢1A char(16));
+create index ‘¼Æ‘Ëã‘»ú‘Êõ‘Óïindex1 on ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï using btree(‘Êõ‘Óï);
+create index ‘¼Æ‘Ëã‘»ú‘Êõ‘Óïindex2 on ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï using btree(‘·Ö‘Àà‘ºÅ);
+insert into ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï values('‘µç‘ÄÔ‘Ïԑʾ‘ÆÁ','‘»úA01‘ÉÏ');
+insert into ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï values('‘µç‘Äԑͼ‘ÐÎ','‘·ÖB01‘ÖÐ');
+insert into ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï values('‘µç‘ÄÔ‘³Ì‘Ðò‘Ô±','‘ÈËZ01‘ÏÂ');
+vacuum ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+ ‘Êõ‘Óï | ‘·Ö‘Àà‘ºÅ | ‘±¸‘×¢1a
+------------+---------+--------
+ ‘µç‘ÄÔ‘Ïԑʾ‘ÆÁ | ‘»úA01‘ÉÏ |
+ ‘µç‘Äԑͼ‘ÐÎ | ‘·ÖB01‘ÖÐ |
+ ‘µç‘ÄÔ‘³Ì‘Ðò‘Ô± | ‘ÈËZ01‘Ï |
+(3 rows)
+
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï where ‘·Ö‘Àà‘ºÅ = '‘ÈËZ01‘ÏÂ';
+ ‘Êõ‘Óï | ‘·Ö‘Àà‘ºÅ | ‘±¸‘×¢1a
+------------+---------+--------
+ ‘µç‘ÄÔ‘³Ì‘Ðò‘Ô± | ‘ÈËZ01‘Ï |
+(1 row)
+
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï where ‘·Ö‘Àà‘ºÅ ~* '‘ÈËz01‘ÏÂ';
+ ‘Êõ‘Óï | ‘·Ö‘Àà‘ºÅ | ‘±¸‘×¢1a
+------------+---------+--------
+ ‘µç‘ÄÔ‘³Ì‘Ðò‘Ô± | ‘ÈËZ01‘Ï |
+(1 row)
+
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï where ‘·Ö‘Àà‘ºÅ like '_Z01_';
+ ‘Êõ‘Óï | ‘·Ö‘Àà‘ºÅ | ‘±¸‘×¢1a
+------------+---------+--------
+ ‘µç‘ÄÔ‘³Ì‘Ðò‘Ô± | ‘ÈËZ01‘Ï |
+(1 row)
+
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï where ‘·Ö‘Àà‘ºÅ like '_Z%';
+ ‘Êõ‘Óï | ‘·Ö‘Àà‘ºÅ | ‘±¸‘×¢1a
+------------+---------+--------
+ ‘µç‘ÄÔ‘³Ì‘Ðò‘Ô± | ‘ÈËZ01‘Ï |
+(1 row)
+
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï where ‘Êõ‘Óï ~ '‘µç‘ÄÔ[‘Ïԑͼ]';
+ ‘Êõ‘Óï | ‘·Ö‘Àà‘ºÅ | ‘±¸‘×¢1a
+------------+---------+--------
+ ‘µç‘ÄÔ‘Ïԑʾ‘ÆÁ | ‘»úA01‘ÉÏ |
+ ‘µç‘Äԑͼ‘ÐÎ | ‘·ÖB01‘ÖÐ |
+(2 rows)
+
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï where ‘Êõ‘Óï ~* '‘µç‘ÄÔ[‘Ïԑͼ]';
+ ‘Êõ‘Óï | ‘·Ö‘Àà‘ºÅ | ‘±¸‘×¢1a
+------------+---------+--------
+ ‘µç‘ÄÔ‘Ïԑʾ‘ÆÁ | ‘»úA01‘ÉÏ |
+ ‘µç‘Äԑͼ‘ÐÎ | ‘·ÖB01‘ÖÐ |
+(2 rows)
+
+select *,character_length(‘Êõ‘Óï) from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+ ‘Êõ‘Óï | ‘·Ö‘Àà‘ºÅ | ‘±¸‘×¢1a | character_length
+------------+---------+--------+------------------
+ ‘µç‘ÄÔ‘Ïԑʾ‘ÆÁ | ‘»úA01‘ÉÏ | | 5
+ ‘µç‘Äԑͼ‘ÐÎ | ‘·ÖB01‘ÖÐ | | 4
+ ‘µç‘ÄÔ‘³Ì‘Ðò‘Ô± | ‘ÈËZ01‘Ï | | 5
+(3 rows)
+
+select *,octet_length(‘Êõ‘Óï) from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+ ‘Êõ‘Óï | ‘·Ö‘Àà‘ºÅ | ‘±¸‘×¢1a | octet_length
+------------+---------+--------+--------------
+ ‘µç‘ÄÔ‘Ïԑʾ‘ÆÁ | ‘»úA01‘ÉÏ | | 15
+ ‘µç‘Äԑͼ‘ÐÎ | ‘·ÖB01‘ÖÐ | | 12
+ ‘µç‘ÄÔ‘³Ì‘Ðò‘Ô± | ‘ÈËZ01‘Ï | | 15
+(3 rows)
+
+select *,position('‘ÏÔ' in ‘Êõ‘Óï) from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+ ‘Êõ‘Óï | ‘·Ö‘Àà‘ºÅ | ‘±¸‘×¢1a | position
+------------+---------+--------+----------
+ ‘µç‘ÄÔ‘Ïԑʾ‘ÆÁ | ‘»úA01‘ÉÏ | | 3
+ ‘µç‘Äԑͼ‘ÐÎ | ‘·ÖB01‘ÖÐ | | 0
+ ‘µç‘ÄÔ‘³Ì‘Ðò‘Ô± | ‘ÈËZ01‘Ï | | 0
+(3 rows)
+
+select *,substring(‘Êõ‘Óï from 3 for 4) from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+ ‘Êõ‘Óï | ‘·Ö‘Àà‘ºÅ | ‘±¸‘×¢1a | substring
+------------+---------+--------+-----------
+ ‘µç‘ÄÔ‘Ïԑʾ‘ÆÁ | ‘»úA01‘ÉÏ | | ‘Ïԑʾ‘ÆÁ
+ ‘µç‘Äԑͼ‘ÐÎ | ‘·ÖB01‘ÖÐ | | ‘ͼ‘ÐÎ
+ ‘µç‘ÄÔ‘³Ì‘Ðò‘Ô± | ‘ÈËZ01‘Ï | | ‘³Ì‘Ðò‘Ô±
+(3 rows)
+
+drop table “ͪ“ß©“Ѧ“¿ë“¾î;
+ERROR: table "“ͪ“ß©“Ѧ“¿ë“¾î" does not exist
+create table “ͪ“ß©“Ѧ“¿ë“¾î (“¿ë“¾î text, “Ý“׾“ÄÚ“µå varchar, “ºñ“°í1A“¶ó“±¸ char(16));
+create index “ͪ“ß©“Ѧ“¿ë“¾îindex1 on “ͪ“ß©“Ѧ“¿ë“¾î using btree (“¿ë“¾î);
+create index “ͪ“ß©“Ѧ“¿ë“¾îindex2 on “ͪ“ß©“Ѧ“¿ë“¾î using hash (“Ý“׾“ÄÚ“µå);
+insert into “ͪ“ß©“Ѧ“¿ë“¾î values('“ÄÄ“Ç»“ÅÍ“µð“½º“ÇÓ·¹“ÀÌ', '“ѦA01“ß¾');
+insert into “ͪ“ß©“Ѧ“¿ë“¾î values('“ÄÄ“Ç»“ÅÍ“±×“·¡“ÇÈ“½º', '“ÝÂB10“ñé');
+insert into “ͪ“ß©“Ѧ“¿ë“¾î values('“ÄÄ“Ç»“ÅÍ“ÇÁ“·Î“±×“·¡“¸Ó', '“ìÑZ01“ù»');
+vacuum “ͪ“ß©“Ѧ“¿ë“¾î;
+select * from “ͪ“ß©“Ѧ“¿ë“¾î;
+ “¿ë“¾î | “Ý“׾“ÄÚ“µå | “ºñ“°í1a“¶ó“±¸
+------------------+----------+------------
+ “ÄÄ“Ç»“ÅÍ“µð“½º“ÇÓ·¹“ÀÌ | “ѦA01“ß¾ |
+ “ÄÄ“Ç»“ÅÍ“±×“·¡“ÇÈ“½º | “ÝÂB10“ñé |
+ “ÄÄ“Ç»“ÅÍ“ÇÁ“·Î“±×“·¡“¸Ó | “ìÑZ01“ù» |
+(3 rows)
+
+select * from “ͪ“ß©“Ѧ“¿ë“¾î where “Ý“׾“ÄÚ“µå = '“ìÑZ01“ù»';
+ “¿ë“¾î | “Ý“׾“ÄÚ“µå | “ºñ“°í1a“¶ó“±¸
+------------------+----------+------------
+ “ÄÄ“Ç»“ÅÍ“ÇÁ“·Î“±×“·¡“¸Ó | “ìÑZ01“ù» |
+(1 row)
+
+select * from “ͪ“ß©“Ѧ“¿ë“¾î where “Ý“׾“ÄÚ“µå ~* '“ìÑz01“ù»';
+ “¿ë“¾î | “Ý“׾“ÄÚ“µå | “ºñ“°í1a“¶ó“±¸
+------------------+----------+------------
+ “ÄÄ“Ç»“ÅÍ“ÇÁ“·Î“±×“·¡“¸Ó | “ìÑZ01“ù» |
+(1 row)
+
+select * from “ͪ“ß©“Ѧ“¿ë“¾î where “Ý“׾“ÄÚ“µå like '_Z01_';
+ “¿ë“¾î | “Ý“׾“ÄÚ“µå | “ºñ“°í1a“¶ó“±¸
+------------------+----------+------------
+ “ÄÄ“Ç»“ÅÍ“ÇÁ“·Î“±×“·¡“¸Ó | “ìÑZ01“ù» |
+(1 row)
+
+select * from “ͪ“ß©“Ѧ“¿ë“¾î where “Ý“׾“ÄÚ“µå like '_Z%';
+ “¿ë“¾î | “Ý“׾“ÄÚ“µå | “ºñ“°í1a“¶ó“±¸
+------------------+----------+------------
+ “ÄÄ“Ç»“ÅÍ“ÇÁ“·Î“±×“·¡“¸Ó | “ìÑZ01“ù» |
+(1 row)
+
+select * from “ͪ“ß©“Ѧ“¿ë“¾î where “¿ë“¾î ~ '“ÄÄ“Ç»“ÅÍ[“µð“±×]';
+ “¿ë“¾î | “Ý“׾“ÄÚ“µå | “ºñ“°í1a“¶ó“±¸
+------------------+----------+------------
+ “ÄÄ“Ç»“ÅÍ“µð“½º“ÇÓ·¹“ÀÌ | “ѦA01“ß¾ |
+ “ÄÄ“Ç»“ÅÍ“±×“·¡“ÇÈ“½º | “ÝÂB10“ñé |
+(2 rows)
+
+select * from “ͪ“ß©“Ѧ“¿ë“¾î where “¿ë“¾î ~* '“ÄÄ“Ç»“ÅÍ[“µð“±×]';
+ “¿ë“¾î | “Ý“׾“ÄÚ“µå | “ºñ“°í1a“¶ó“±¸
+------------------+----------+------------
+ “ÄÄ“Ç»“ÅÍ“µð“½º“ÇÓ·¹“ÀÌ | “ѦA01“ß¾ |
+ “ÄÄ“Ç»“ÅÍ“±×“·¡“ÇÈ“½º | “ÝÂB10“ñé |
+(2 rows)
+
+select *,character_length(“¿ë“¾î) from “ͪ“ß©“Ѧ“¿ë“¾î;
+ “¿ë“¾î | “Ý“׾“ÄÚ“µå | “ºñ“°í1a“¶ó“±¸ | character_length
+------------------+----------+------------+------------------
+ “ÄÄ“Ç»“ÅÍ“µð“½º“ÇÓ·¹“ÀÌ | “ѦA01“ß¾ | | 8
+ “ÄÄ“Ç»“ÅÍ“±×“·¡“ÇÈ“½º | “ÝÂB10“ñé | | 7
+ “ÄÄ“Ç»“ÅÍ“ÇÁ“·Î“±×“·¡“¸Ó | “ìÑZ01“ù» | | 8
+(3 rows)
+
+select *,octet_length(“¿ë“¾î) from “ͪ“ß©“Ѧ“¿ë“¾î;
+ “¿ë“¾î | “Ý“׾“ÄÚ“µå | “ºñ“°í1a“¶ó“±¸ | octet_length
+------------------+----------+------------+--------------
+ “ÄÄ“Ç»“ÅÍ“µð“½º“ÇÓ·¹“ÀÌ | “ѦA01“ß¾ | | 24
+ “ÄÄ“Ç»“ÅÍ“±×“·¡“ÇÈ“½º | “ÝÂB10“ñé | | 21
+ “ÄÄ“Ç»“ÅÍ“ÇÁ“·Î“±×“·¡“¸Ó | “ìÑZ01“ù» | | 24
+(3 rows)
+
+select *,position('“µð' in “¿ë“¾î) from “ͪ“ß©“Ѧ“¿ë“¾î;
+ “¿ë“¾î | “Ý“׾“ÄÚ“µå | “ºñ“°í1a“¶ó“±¸ | position
+------------------+----------+------------+----------
+ “ÄÄ“Ç»“ÅÍ“µð“½º“ÇÓ·¹“ÀÌ | “ѦA01“ß¾ | | 4
+ “ÄÄ“Ç»“ÅÍ“±×“·¡“ÇÈ“½º | “ÝÂB10“ñé | | 0
+ “ÄÄ“Ç»“ÅÍ“ÇÁ“·Î“±×“·¡“¸Ó | “ìÑZ01“ù» | | 0
+(3 rows)
+
+select *,substring(“¿ë“¾î from 3 for 4) from “ͪ“ß©“Ѧ“¿ë“¾î;
+ “¿ë“¾î | “Ý“׾“ÄÚ“µå | “ºñ“°í1a“¶ó“±¸ | substring
+------------------+----------+------------+-----------
+ “ÄÄ“Ç»“ÅÍ“µð“½º“ÇÓ·¹“ÀÌ | “ѦA01“ß¾ | | “ÅÍ“µð“½º“ÇÃ
+ “ÄÄ“Ç»“ÅÍ“±×“·¡“ÇÈ“½º | “ÝÂB10“ñé | | “ÅÍ“±×“·¡“ÇÈ
+ “ÄÄ“Ç»“ÅÍ“ÇÁ“·Î“±×“·¡“¸Ó | “ìÑZ01“ù» | | “ÅÍ“ÇÁ“·Î“±×
+(3 rows)
+
+drop table test;
+ERROR: table "test" does not exist
+create table test (t text);
+insert into test values('ENGLISH');
+insert into test values('FRANÇAIS');
+insert into test values('ESPAÑOL');
+insert into test values('ÍSLENSKA');
+insert into test values('ENGLISH FRANÇAIS ESPAÑOL ÍSLENSKA');
+vacuum test;
+select * from test;
+ t
+-----------------------------------
+ ENGLISH
+ FRANÇAIS
+ ESPAÑOL
+ ÍSLENSKA
+ ENGLISH FRANÇAIS ESPAÑOL ÍSLENSKA
+(5 rows)
+
+select * from test where t = 'ESPAÑOL';
+ t
+---------
+ ESPAÑOL
+(1 row)
+
+select * from test where t ~* 'espaÑol';
+ t
+-----------------------------------
+ ESPAÑOL
+ ENGLISH FRANÇAIS ESPAÑOL ÍSLENSKA
+(2 rows)
+
+select *,character_length(t) from test;
+ t | character_length
+-----------------------------------+------------------
+ ENGLISH | 7
+ FRANÇAIS | 8
+ ESPAÑOL | 7
+ ÍSLENSKA | 8
+ ENGLISH FRANÇAIS ESPAÑOL ÍSLENSKA | 33
+(5 rows)
+
+select *,octet_length(t) from test;
+ t | octet_length
+-----------------------------------+--------------
+ ENGLISH | 7
+ FRANÇAIS | 9
+ ESPAÑOL | 8
+ ÍSLENSKA | 9
+ ENGLISH FRANÇAIS ESPAÑOL ÍSLENSKA | 36
+(5 rows)
+
+select *,position('L' in t) from test;
+ t | position
+-----------------------------------+----------
+ ENGLISH | 4
+ FRANÇAIS | 0
+ ESPAÑOL | 7
+ ÍSLENSKA | 3
+ ENGLISH FRANÇAIS ESPAÑOL ÍSLENSKA | 4
+(5 rows)
+
+select *,substring(t from 3 for 4) from test;
+ t | substring
+-----------------------------------+-----------
+ ENGLISH | GLIS
+ FRANÇAIS | ANÇA
+ ESPAÑOL | PAÑO
+ ÍSLENSKA | LENS
+ ENGLISH FRANÇAIS ESPAÑOL ÍSLENSKA | GLIS
+(5 rows)
+
diff --git a/src/test/mb/expected/sjis.out b/src/test/mb/expected/sjis.out
new file mode 100644
index 0000000..db2bfd0
--- /dev/null
+++ b/src/test/mb/expected/sjis.out
@@ -0,0 +1,90 @@
+drop table ŒvŽZ‹@—pŒê;
+create table ŒvŽZ‹@—pŒê (—pŒê text, •ª—ÞƒR[ƒh varchar, ”õl1A‚¾‚æ char(16));
+create index ŒvŽZ‹@—pŒêindex1 on ŒvŽZ‹@—pŒê using btree (—pŒê);
+create index ŒvŽZ‹@—pŒêindex2 on ŒvŽZ‹@—pŒê using hash (•ª—ÞƒR[ƒh);
+insert into ŒvŽZ‹@—pŒê values('ƒRƒ“ƒsƒ…[ƒ^ƒfƒBƒXƒvƒŒƒC','‹@A01ã');
+insert into ŒvŽZ‹@—pŒê values('ƒRƒ“ƒsƒ…[ƒ^ƒOƒ‰ƒtƒBƒbƒNƒX','•ªB10’†');
+insert into ŒvŽZ‹@—pŒê values('ƒRƒ“ƒsƒ…[ƒ^ƒvƒƒOƒ‰ƒ}[','lZ01‰º');
+vacuum ŒvŽZ‹@—pŒê;
+select * from ŒvŽZ‹@—pŒê;
+ —pŒê | •ª—ÞƒR[ƒh | ”õl1a‚¾‚æ
+----------------------------+------------+------------
+ ƒRƒ“ƒsƒ…[ƒ^ƒfƒBƒXƒvƒŒƒC | ‹@A01ã |
+ ƒRƒ“ƒsƒ…[ƒ^ƒOƒ‰ƒtƒBƒbƒNƒX | •ªB10’† |
+ ƒRƒ“ƒsƒ…[ƒ^ƒvƒƒOƒ‰ƒ}[ | lZ01‰º |
+(3 rows)
+
+select * from ŒvŽZ‹@—pŒê where •ª—ÞƒR[ƒh = 'lZ01‰º';
+ —pŒê | •ª—ÞƒR[ƒh | ”õl1a‚¾‚æ
+--------------------------+------------+------------
+ ƒRƒ“ƒsƒ…[ƒ^ƒvƒƒOƒ‰ƒ}[ | lZ01‰º |
+(1 row)
+
+select * from ŒvŽZ‹@—pŒê where •ª—ÞƒR[ƒh ~* 'lz01‰º';
+ —pŒê | •ª—ÞƒR[ƒh | ”õl1a‚¾‚æ
+--------------------------+------------+------------
+ ƒRƒ“ƒsƒ…[ƒ^ƒvƒƒOƒ‰ƒ}[ | lZ01‰º |
+(1 row)
+
+select * from ŒvŽZ‹@—pŒê where •ª—ÞƒR[ƒh like '_Z01_';
+ —pŒê | •ª—ÞƒR[ƒh | ”õl1a‚¾‚æ
+--------------------------+------------+------------
+ ƒRƒ“ƒsƒ…[ƒ^ƒvƒƒOƒ‰ƒ}[ | lZ01‰º |
+(1 row)
+
+select * from ŒvŽZ‹@—pŒê where •ª—ÞƒR[ƒh like '_Z%';
+ —pŒê | •ª—ÞƒR[ƒh | ”õl1a‚¾‚æ
+--------------------------+------------+------------
+ ƒRƒ“ƒsƒ…[ƒ^ƒvƒƒOƒ‰ƒ}[ | lZ01‰º |
+(1 row)
+
+select * from ŒvŽZ‹@—pŒê where —pŒê ~ 'ƒRƒ“ƒsƒ…[ƒ^[ƒfƒO]';
+ —pŒê | •ª—ÞƒR[ƒh | ”õl1a‚¾‚æ
+----------------------------+------------+------------
+ ƒRƒ“ƒsƒ…[ƒ^ƒfƒBƒXƒvƒŒƒC | ‹@A01ã |
+ ƒRƒ“ƒsƒ…[ƒ^ƒOƒ‰ƒtƒBƒbƒNƒX | •ªB10’† |
+(2 rows)
+
+select * from ŒvŽZ‹@—pŒê where —pŒê ~* 'ƒRƒ“ƒsƒ…[ƒ^[ƒfƒO]';
+ —pŒê | •ª—ÞƒR[ƒh | ”õl1a‚¾‚æ
+----------------------------+------------+------------
+ ƒRƒ“ƒsƒ…[ƒ^ƒfƒBƒXƒvƒŒƒC | ‹@A01ã |
+ ƒRƒ“ƒsƒ…[ƒ^ƒOƒ‰ƒtƒBƒbƒNƒX | •ªB10’† |
+(2 rows)
+
+select *,character_length(—pŒê) from ŒvŽZ‹@—pŒê;
+ —pŒê | •ª—ÞƒR[ƒh | ”õl1a‚¾‚æ | character_length
+----------------------------+------------+------------+------------------
+ ƒRƒ“ƒsƒ…[ƒ^ƒfƒBƒXƒvƒŒƒC | ‹@A01ã | | 12
+ ƒRƒ“ƒsƒ…[ƒ^ƒOƒ‰ƒtƒBƒbƒNƒX | •ªB10’† | | 13
+ ƒRƒ“ƒsƒ…[ƒ^ƒvƒƒOƒ‰ƒ}[ | lZ01‰º | | 12
+(3 rows)
+
+select *,octet_length(—pŒê) from ŒvŽZ‹@—pŒê;
+ —pŒê | •ª—ÞƒR[ƒh | ”õl1a‚¾‚æ | octet_length
+----------------------------+------------+------------+--------------
+ ƒRƒ“ƒsƒ…[ƒ^ƒfƒBƒXƒvƒŒƒC | ‹@A01ã | | 24
+ ƒRƒ“ƒsƒ…[ƒ^ƒOƒ‰ƒtƒBƒbƒNƒX | •ªB10’† | | 26
+ ƒRƒ“ƒsƒ…[ƒ^ƒvƒƒOƒ‰ƒ}[ | lZ01‰º | | 24
+(3 rows)
+
+select *,position('ƒf' in —pŒê) from ŒvŽZ‹@—pŒê;
+ —pŒê | •ª—ÞƒR[ƒh | ”õl1a‚¾‚æ | position
+----------------------------+------------+------------+----------
+ ƒRƒ“ƒsƒ…[ƒ^ƒfƒBƒXƒvƒŒƒC | ‹@A01ã | | 7
+ ƒRƒ“ƒsƒ…[ƒ^ƒOƒ‰ƒtƒBƒbƒNƒX | •ªB10’† | | 0
+ ƒRƒ“ƒsƒ…[ƒ^ƒvƒƒOƒ‰ƒ}[ | lZ01‰º | | 0
+(3 rows)
+
+select *,substring(—pŒê from 10 for 4) from ŒvŽZ‹@—pŒê;
+ —pŒê | •ª—ÞƒR[ƒh | ”õl1a‚¾‚æ | substring
+----------------------------+------------+------------+-----------
+ ƒRƒ“ƒsƒ…[ƒ^ƒfƒBƒXƒvƒŒƒC | ‹@A01ã | | ƒvƒŒƒC
+ ƒRƒ“ƒsƒ…[ƒ^ƒOƒ‰ƒtƒBƒbƒNƒX | •ªB10’† | | ƒBƒbƒNƒX
+ ƒRƒ“ƒsƒ…[ƒ^ƒvƒƒOƒ‰ƒ}[ | lZ01‰º | | ƒ‰ƒ}[
+(3 rows)
+
+copy ŒvŽZ‹@—pŒê to stdout;
+ƒRƒ“ƒsƒ…[ƒ^ƒfƒBƒXƒvƒŒƒC ‹@A01ã \N
+ƒRƒ“ƒsƒ…[ƒ^ƒOƒ‰ƒtƒBƒbƒNƒX •ªB10’† \N
+ƒRƒ“ƒsƒ…[ƒ^ƒvƒƒOƒ‰ƒ}[ lZ01‰º \N
diff --git a/src/test/mb/expected/utf8.out b/src/test/mb/expected/utf8.out
new file mode 100644
index 0000000..8f9f63c
--- /dev/null
+++ b/src/test/mb/expected/utf8.out
@@ -0,0 +1,87 @@
+drop table 計算機用語;
+ERROR: table "計算機用語" does not exist
+create table 計算機用語 (用語 text, 分類コード varchar, 備考1Aã ã‚ˆ char(16));
+create index 計算機用語index1 on 計算機用語 using btree (用語);
+create index 計算機用語index2 on 計算機用語 using hash (分類コード);
+insert into 計算機用語 values('コンピュータディスプレイ','機A01上');
+insert into 計算機用語 values('コンピュータグラフィックス','分B10中');
+insert into 計算機用語 values('コンピュータプログラマー','人Z01下');
+vacuum 計算機用語;
+select * from 計算機用語;
+ 用語 | 分類コード | 備考1aã ã‚ˆ
+----------------------------+------------+------------
+ コンピュータディスプレイ | 機A01上 |
+ コンピュータグラフィックス | 分B10中 |
+ コンピュータプログラマー | 人Z01下 |
+(3 rows)
+
+select * from 計算機用語 where 分類コード = '人Z01下';
+ 用語 | 分類コード | 備考1aã ã‚ˆ
+--------------------------+------------+------------
+ コンピュータプログラマー | 人Z01下 |
+(1 row)
+
+select * from 計算機用語 where 分類コード ~* '人z01下';
+ 用語 | 分類コード | 備考1aã ã‚ˆ
+--------------------------+------------+------------
+ コンピュータプログラマー | 人Z01下 |
+(1 row)
+
+select * from 計算機用語 where 分類コード like '_Z01_';
+ 用語 | 分類コード | 備考1aã ã‚ˆ
+--------------------------+------------+------------
+ コンピュータプログラマー | 人Z01下 |
+(1 row)
+
+select * from 計算機用語 where 分類コード like '_Z%';
+ 用語 | 分類コード | 備考1aã ã‚ˆ
+--------------------------+------------+------------
+ コンピュータプログラマー | 人Z01下 |
+(1 row)
+
+select * from 計算機用語 where 用語 ~ 'コンピュータ[デグ]';
+ 用語 | 分類コード | 備考1aã ã‚ˆ
+----------------------------+------------+------------
+ コンピュータディスプレイ | 機A01上 |
+ コンピュータグラフィックス | 分B10中 |
+(2 rows)
+
+select * from 計算機用語 where 用語 ~* 'コンピュータ[デグ]';
+ 用語 | 分類コード | 備考1aã ã‚ˆ
+----------------------------+------------+------------
+ コンピュータディスプレイ | 機A01上 |
+ コンピュータグラフィックス | 分B10中 |
+(2 rows)
+
+select *,character_length(用語) from 計算機用語;
+ 用語 | 分類コード | 備考1aã ã‚ˆ | character_length
+----------------------------+------------+------------+------------------
+ コンピュータディスプレイ | 機A01上 | | 12
+ コンピュータグラフィックス | 分B10中 | | 13
+ コンピュータプログラマー | 人Z01下 | | 12
+(3 rows)
+
+select *,octet_length(用語) from 計算機用語;
+ 用語 | 分類コード | 備考1aã ã‚ˆ | octet_length
+----------------------------+------------+------------+--------------
+ コンピュータディスプレイ | 機A01上 | | 36
+ コンピュータグラフィックス | 分B10中 | | 39
+ コンピュータプログラマー | 人Z01下 | | 36
+(3 rows)
+
+select *,position('デ' in 用語) from 計算機用語;
+ 用語 | 分類コード | 備考1aã ã‚ˆ | position
+----------------------------+------------+------------+----------
+ コンピュータディスプレイ | 機A01上 | | 7
+ コンピュータグラフィックス | 分B10中 | | 0
+ コンピュータプログラマー | 人Z01下 | | 0
+(3 rows)
+
+select *,substring(用語 from 10 for 4) from 計算機用語;
+ 用語 | 分類コード | 備考1aã ã‚ˆ | substring
+----------------------------+------------+------------+-----------
+ コンピュータディスプレイ | 機A01上 | | プレイ
+ コンピュータグラフィックス | 分B10中 | | ィックス
+ コンピュータプログラマー | 人Z01下 | | ラマー
+(3 rows)
+
diff --git a/src/test/mb/mbregress.sh b/src/test/mb/mbregress.sh
new file mode 100755
index 0000000..c313565
--- /dev/null
+++ b/src/test/mb/mbregress.sh
@@ -0,0 +1,74 @@
+#! /bin/sh
+# src/test/mb/mbregress.sh
+
+if echo '\c' | grep -s c >/dev/null 2>&1
+then
+ ECHO_N="echo -n"
+ ECHO_C=""
+else
+ ECHO_N="echo"
+ ECHO_C='\c'
+fi
+
+if [ ! -d results ];then
+ mkdir results
+fi
+
+dropdb --if-exists utf8
+createdb -T template0 -l C -E UTF8 utf8 || exit 1
+
+PSQL="psql -X -n -e -q"
+
+# in the test list, client-only encodings must follow the server encoding
+# they're to be tested with; see hard-coded cases below
+tests="euc_jp sjis euc_kr euc_cn euc_tw big5 utf8 gb18030 mule_internal"
+
+EXITCODE=0
+
+unset PGCLIENTENCODING
+for i in $tests
+do
+ $ECHO_N "${i} .. " $ECHO_C
+
+ if [ $i = sjis ];then
+ PGCLIENTENCODING=SJIS
+ export PGCLIENTENCODING
+ $PSQL euc_jp < sql/sjis.sql > results/sjis.out 2>&1
+ unset PGCLIENTENCODING
+ elif [ $i = big5 ];then
+ PGCLIENTENCODING=BIG5
+ export PGCLIENTENCODING
+ $PSQL euc_tw < sql/big5.sql > results/big5.out 2>&1
+ unset PGCLIENTENCODING
+ elif [ $i = gb18030 ];then
+ PGCLIENTENCODING=GB18030
+ export PGCLIENTENCODING
+ $PSQL utf8 < sql/gb18030.sql > results/gb18030.out 2>&1
+ unset PGCLIENTENCODING
+ else
+ dropdb $i >/dev/null 2>&1
+ createdb -T template0 -l C -E `echo $i | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` $i >/dev/null
+ $PSQL $i < sql/${i}.sql > results/${i}.out 2>&1
+ fi
+
+ if [ -f expected/${i}-${SYSTEM}.out ]
+ then
+ EXPECTED="expected/${i}-${SYSTEM}.out"
+ else
+ EXPECTED="expected/${i}.out"
+ fi
+
+ if [ `diff ${EXPECTED} results/${i}.out | wc -l` -ne 0 ]
+ then
+ ( diff -C3 ${EXPECTED} results/${i}.out; \
+ echo ""; \
+ echo "----------------------"; \
+ echo "" ) >> regression.diffs
+ echo failed
+ EXITCODE=1
+ else
+ echo ok
+ fi
+done
+
+exit $EXITCODE
diff --git a/src/test/mb/sql/big5.sql b/src/test/mb/sql/big5.sql
new file mode 100644
index 0000000..4e2d100
--- /dev/null
+++ b/src/test/mb/sql/big5.sql
@@ -0,0 +1,20 @@
+drop table ¼t°Ó¸ê®Æ;
+create table ¼t°Ó¸ê®Æ (¦æ·~§O text, ¤½¥q©ïÀY varchar, ¦a§} varchar(16));
+create index ¼t°Ó¸ê®Æindex1 on ¼t°Ó¸ê®Æ using btree (¦æ·~§O);
+create index ¼t°Ó¸ê®Æindex2 on ¼t°Ó¸ê®Æ using hash (¤½¥q©ïÀY);
+insert into ¼t°Ó¸ê®Æ values ('¹q¸£·~', '¹F¹F¬ì§Þ', '¥_A01¤¯');
+insert into ¼t°Ó¸ê®Æ values ('»s³y·~', '°]·½¦³­­¤½¥q', '¤¤B10¤¤');
+insert into ¼t°Ó¸ê®Æ values ('À\¶¼·~', '¬ü¨ýªÑ¥÷¦³­­¤½¥q', '°ªZ01¤E');
+vacuum ¼t°Ó¸ê®Æ;
+select * from ¼t°Ó¸ê®Æ;
+select * from ¼t°Ó¸ê®Æ where ¦a§} = '°ªZ01¤E';
+select * from ¼t°Ó¸ê®Æ where ¦a§} ~* '°ªz01¤E';
+select * from ¼t°Ó¸ê®Æ where ¦a§} like '_Z01_';
+select * from ¼t°Ó¸ê®Æ where ¦a§} like '_Z%';
+select * from ¼t°Ó¸ê®Æ where ¤½¥q©ïÀY ~ '¹F¹F¬ì[±H°O§Þ]';
+select * from ¼t°Ó¸ê®Æ where ¤½¥q©ïÀY ~* '¹F¹F¬ì[±H°O§Þ]';
+
+select *, character_length(¦æ·~§O) from ¼t°Ó¸ê®Æ;
+select *, octet_length(¦æ·~§O) from ¼t°Ó¸ê®Æ;
+select *, position('¦³­­' in ¤½¥q©ïÀY) from ¼t°Ó¸ê®Æ;
+select *, substring(¤½¥q©ïÀY from 3 for 6 ) from ¼t°Ó¸ê®Æ;
diff --git a/src/test/mb/sql/euc_cn.sql b/src/test/mb/sql/euc_cn.sql
new file mode 100644
index 0000000..7cd0b9b
--- /dev/null
+++ b/src/test/mb/sql/euc_cn.sql
@@ -0,0 +1,19 @@
+drop table ¼ÆËã»úÊõÓï;
+create table ¼ÆËã»úÊõÓï(ÊõÓï text, ·ÖÀàºÅ varchar, ±¸×¢1A char(16));
+create index ¼ÆËã»úÊõÓïindex1 on ¼ÆËã»úÊõÓï using btree(ÊõÓï);
+create index ¼ÆËã»úÊõÓïindex2 on ¼ÆËã»úÊõÓï using btree(·ÖÀàºÅ);
+insert into ¼ÆËã»úÊõÓï values('µçÄÔÏÔʾÆÁ','»úA01ÉÏ');
+insert into ¼ÆËã»úÊõÓï values('µçÄÔͼÐÎ','·ÖB01ÖÐ');
+insert into ¼ÆËã»úÊõÓï values('µçÄÔ³ÌÐòÔ±','ÈËZ01ÏÂ');
+vacuum ¼ÆËã»úÊõÓï;
+select * from ¼ÆËã»úÊõÓï;
+select * from ¼ÆËã»úÊõÓï where ·ÖÀàºÅ = 'ÈËZ01ÏÂ';
+select * from ¼ÆËã»úÊõÓï where ·ÖÀàºÅ ~* 'ÈËz01ÏÂ';
+select * from ¼ÆËã»úÊõÓï where ·ÖÀàºÅ like '_Z01_';
+select * from ¼ÆËã»úÊõÓï where ·ÖÀàºÅ like '_Z%';
+select * from ¼ÆËã»úÊõÓï where ÊõÓï ~ 'µçÄÔ[ÏÔͼ]';
+select * from ¼ÆËã»úÊõÓï where ÊõÓï ~* 'µçÄÔ[ÏÔͼ]';
+select *,character_length(ÊõÓï) from ¼ÆËã»úÊõÓï;
+select *,octet_length(ÊõÓï) from ¼ÆËã»úÊõÓï;
+select *,position('ÏÔ' in ÊõÓï) from ¼ÆËã»úÊõÓï;
+select *,substring(ÊõÓï from 3 for 4) from ¼ÆËã»úÊõÓï;
diff --git a/src/test/mb/sql/euc_jp.sql b/src/test/mb/sql/euc_jp.sql
new file mode 100644
index 0000000..2021205
--- /dev/null
+++ b/src/test/mb/sql/euc_jp.sql
@@ -0,0 +1,19 @@
+drop table ·×»»µ¡ÍѸì;
+create table ·×»»µ¡ÍѸì (ÍѸì text, ʬÎॳ¡¼¥É varchar, È÷¹Í1A¤À¤è char(16));
+create index ·×»»µ¡ÍѸìindex1 on ·×»»µ¡ÍѸì using btree (ÍѸì);
+create index ·×»»µ¡ÍѸìindex2 on ·×»»µ¡ÍѸì using hash (ʬÎॳ¡¼¥É);
+insert into ·×»»µ¡ÍѸì values('¥³¥ó¥Ô¥å¡¼¥¿¥Ç¥£¥¹¥×¥ì¥¤','µ¡A01¾å');
+insert into ·×»»µ¡ÍѸì values('¥³¥ó¥Ô¥å¡¼¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹','ʬB10Ãæ');
+insert into ·×»»µ¡ÍѸì values('¥³¥ó¥Ô¥å¡¼¥¿¥×¥í¥°¥é¥Þ¡¼','¿ÍZ01²¼');
+vacuum ·×»»µ¡ÍѸì;
+select * from ·×»»µ¡ÍѸì;
+select * from ·×»»µ¡ÍѸì where ʬÎॳ¡¼¥É = '¿ÍZ01²¼';
+select * from ·×»»µ¡ÍѸì where ʬÎॳ¡¼¥É ~* '¿Íz01²¼';
+select * from ·×»»µ¡ÍѸì where ʬÎॳ¡¼¥É like '_Z01_';
+select * from ·×»»µ¡ÍѸì where ʬÎॳ¡¼¥É like '_Z%';
+select * from ·×»»µ¡ÍѸì where ÍѸì ~ '¥³¥ó¥Ô¥å¡¼¥¿[¥Ç¥°]';
+select * from ·×»»µ¡ÍѸì where ÍѸì ~* '¥³¥ó¥Ô¥å¡¼¥¿[¥Ç¥°]';
+select *,character_length(ÍѸì) from ·×»»µ¡ÍѸì;
+select *,octet_length(ÍѸì) from ·×»»µ¡ÍѸì;
+select *,position('¥Ç' in ÍѸì) from ·×»»µ¡ÍѸì;
+select *,substring(ÍѸì from 10 for 4) from ·×»»µ¡ÍѸì;
diff --git a/src/test/mb/sql/euc_kr.sql b/src/test/mb/sql/euc_kr.sql
new file mode 100644
index 0000000..cf9e07f
--- /dev/null
+++ b/src/test/mb/sql/euc_kr.sql
@@ -0,0 +1,19 @@
+drop table ͪߩѦ¿ë¾î;
+create table ͪߩѦ¿ë¾î (¿ë¾î text, ÝÂ×¾ÄÚµå varchar, ºñ°í1A¶ó±¸ char(16));
+create index ͪߩѦ¿ë¾îindex1 on ͪߩѦ¿ë¾î using btree (¿ë¾î);
+create index ͪߩѦ¿ë¾îindex2 on ͪߩѦ¿ë¾î using hash (ÝÂ×¾ÄÚµå);
+insert into ͪߩѦ¿ë¾î values('ÄÄÇ»Å͵ð½ºÇ÷¹ÀÌ', 'ѦA01ß¾');
+insert into ͪߩѦ¿ë¾î values('ÄÄÇ»Åͱ׷¡ÇȽº', 'ÝÂB10ñé');
+insert into ͪߩѦ¿ë¾î values('ÄÄÇ»ÅÍÇÁ·Î±×·¡¸Ó', 'ìÑZ01ù»');
+vacuum ͪߩѦ¿ë¾î;
+select * from ͪߩѦ¿ë¾î;
+select * from ͪߩѦ¿ë¾î where ÝÂ×¾ÄÚµå = 'ìÑZ01ù»';
+select * from ͪߩѦ¿ë¾î where ÝÂ×¾ÄÚµå ~* 'ìÑz01ù»';
+select * from ͪߩѦ¿ë¾î where ÝÂ×¾ÄÚµå like '_Z01_';
+select * from ͪߩѦ¿ë¾î where ÝÂ×¾ÄÚµå like '_Z%';
+select * from ͪߩѦ¿ë¾î where ¿ë¾î ~ 'ÄÄÇ»ÅÍ[µð±×]';
+select * from ͪߩѦ¿ë¾î where ¿ë¾î ~* 'ÄÄÇ»ÅÍ[µð±×]';
+select *,character_length(¿ë¾î) from ͪߩѦ¿ë¾î;
+select *,octet_length(¿ë¾î) from ͪߩѦ¿ë¾î;
+select *,position('µð' in ¿ë¾î) from ͪߩѦ¿ë¾î;
+select *,substring(¿ë¾î from 3 for 4) from ͪߩѦ¿ë¾î;
diff --git a/src/test/mb/sql/euc_tw.sql b/src/test/mb/sql/euc_tw.sql
new file mode 100644
index 0000000..79f3def
--- /dev/null
+++ b/src/test/mb/sql/euc_tw.sql
@@ -0,0 +1,20 @@
+drop table ìÞÙ¸æñÕè;
+create table ìÞÙ¸æñÕè (ÈçäÆɱ text, ÄüƳÍñó¤ varchar, ÇâÉß varchar(16));
+create index ìÞÙ¸æñÕèindex1 on ìÞÙ¸æñÕè using btree (ÈçäÆɱ);
+create index ìÞÙ¸æñÕèindex2 on ìÞÙ¸æñÕè using hash (ÄüƳÍñó¤);
+insert into ìÞÙ¸æñÕè values ('çÙæªäÆ', 'ç®ç®ÒïÊÀ', 'Æ¡A01Äî');
+insert into ìÞÙ¸æñÕè values ('êûÝßäÆ', 'ØÂäãÈ´ÓîÄüƳ', 'ÄãB10Äã');
+insert into ìÞÙ¸æñÕè values ('ó§ã£äÆ', 'Ó¡ÌÀϴǹȴÓîÄüƳ', 'ØíZ01Ħ');
+vacuum ìÞÙ¸æñÕè;
+select * from ìÞÙ¸æñÕè;
+select * from ìÞÙ¸æñÕè where ÇâÉß = 'ØíZ01Ħ';
+select * from ìÞÙ¸æñÕè where ÇâÉß ~* 'Øíz01Ħ';
+select * from ìÞÙ¸æñÕè where ÇâÉß like '_Z01_';
+select * from ìÞÙ¸æñÕè where ÇâÉß like '_Z%';
+select * from ìÞÙ¸æñÕè where ÄüƳÍñó¤ ~ 'ç®ç®Òï[ÙìØ´ÊÀ]';
+select * from ìÞÙ¸æñÕè where ÄüƳÍñó¤ ~* 'ç®ç®Òï[ÙìØ´ÊÀ]';
+
+select *, character_length(ÈçäÆɱ) from ìÞÙ¸æñÕè;
+select *, octet_length(ÈçäÆɱ) from ìÞÙ¸æñÕè;
+select *, position('È´Óî' in ÄüƳÍñó¤) from ìÞÙ¸æñÕè;
+select *, substring(ÄüƳÍñó¤ from 3 for 6 ) from ìÞÙ¸æñÕè;
diff --git a/src/test/mb/sql/gb18030.sql b/src/test/mb/sql/gb18030.sql
new file mode 100644
index 0000000..91ecaaa
--- /dev/null
+++ b/src/test/mb/sql/gb18030.sql
@@ -0,0 +1,19 @@
+drop table Ó‹Ëã™CÓÃÕZ;
+create table Ó‹Ëã™CÓÃÕZ (ÓÃÕZ text, ·Ö©`¥É varchar, ‚俼1A¤À¤è char(16));
+create index Ó‹Ëã™CÓÃÕZindex1 on Ó‹Ëã™CÓÃÕZ using btree (ÓÃÕZ);
+create index Ó‹Ëã™CÓÃÕZindex2 on Ó‹Ëã™CÓÃÕZ using hash (·Ö©`¥É);
+insert into Ó‹Ëã™CÓÃÕZ values('¥³¥ó¥Ô¥å©`¥¿¥Ç¥£¥¹¥×¥ì¥¤','™CA01ÉÏ');
+insert into Ó‹Ëã™CÓÃÕZ values('¥³¥ó¥Ô¥å©`¥¿¥°¥é¥Õ¥£¥Ã¥¯¥¹','·ÖB10ÖÐ');
+insert into Ó‹Ëã™CÓÃÕZ values('¥³¥ó¥Ô¥å©`¥¿¥×¥í¥°¥é¥Þ©`','ÈËZ01ÏÂ');
+vacuum Ó‹Ëã™CÓÃÕZ;
+select * from Ó‹Ëã™CÓÃÕZ;
+select * from Ó‹Ëã™CÓÃÕZ where ·Ö©`¥É = 'ÈËZ01ÏÂ';
+select * from Ó‹Ëã™CÓÃÕZ where ·Ö©`¥É ~* 'ÈËz01ÏÂ';
+select * from Ó‹Ëã™CÓÃÕZ where ·Ö©`¥É like '_Z01_';
+select * from Ó‹Ëã™CÓÃÕZ where ·Ö©`¥É like '_Z%';
+select * from Ó‹Ëã™CÓÃÕZ where ÓÃÕZ ~ '¥³¥ó¥Ô¥å©`¥¿[¥Ç¥°]';
+select * from Ó‹Ëã™CÓÃÕZ where ÓÃÕZ ~* '¥³¥ó¥Ô¥å©`¥¿[¥Ç¥°]';
+select *,character_length(ÓÃÕZ) from Ó‹Ëã™CÓÃÕZ;
+select *,octet_length(ÓÃÕZ) from Ó‹Ëã™CÓÃÕZ;
+select *,position('¥Ç' in ÓÃÕZ) from Ó‹Ëã™CÓÃÕZ;
+select *,substring(ÓÃÕZ from 10 for 4) from Ó‹Ëã™CÓÃÕZ;
diff --git a/src/test/mb/sql/mule_internal.sql b/src/test/mb/sql/mule_internal.sql
new file mode 100644
index 0000000..2e381f0
--- /dev/null
+++ b/src/test/mb/sql/mule_internal.sql
@@ -0,0 +1,72 @@
+drop table ’·×’»»’µ¡’ÍÑ’¸ì;
+create table ’·×’»»’µ¡’ÍÑ’¸ì (’ÍÑ’¸ì text, ’ʬ’Îà’¥³’¡¼’¥É varchar, ’È÷’¹Í1A’¤À’¤è char(16));
+create index ’·×’»»’µ¡’ÍÑ’¸ìindex1 on ’·×’»»’µ¡’ÍÑ’¸ì using btree (’ÍÑ’¸ì);
+create index ’·×’»»’µ¡’ÍÑ’¸ìindex2 on ’·×’»»’µ¡’ÍÑ’¸ì using hash (’ʬ’Îà’¥³’¡¼’¥É);
+insert into ’·×’»»’µ¡’ÍÑ’¸ì values('’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥Ç’¥£’¥¹’¥×’¥ì’¥¤','’µ¡A01’¾å');
+insert into ’·×’»»’µ¡’ÍÑ’¸ì values('’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥°’¥é’¥Õ’¥£’¥Ã’¥¯’¥¹','’ʬB10’Ãæ');
+insert into ’·×’»»’µ¡’ÍÑ’¸ì values('’¥³’¥ó’¥Ô’¥å’¡¼’¥¿’¥×’¥í’¥°’¥é’¥Þ’¡¼','’¿ÍZ01’²¼');
+vacuum ’·×’»»’µ¡’ÍÑ’¸ì;
+select * from ’·×’»»’µ¡’ÍÑ’¸ì;
+select * from ’·×’»»’µ¡’ÍÑ’¸ì where ’ʬ’Îà’¥³’¡¼’¥É = '’¿ÍZ01’²¼';
+select * from ’·×’»»’µ¡’ÍÑ’¸ì where ’ʬ’Îà’¥³’¡¼’¥É ~* '’¿Íz01’²¼';
+select * from ’·×’»»’µ¡’ÍÑ’¸ì where ’ʬ’Îà’¥³’¡¼’¥É like '_Z01_';
+select * from ’·×’»»’µ¡’ÍÑ’¸ì where ’ʬ’Îà’¥³’¡¼’¥É like '_Z%';
+select * from ’·×’»»’µ¡’ÍÑ’¸ì where ’ÍÑ’¸ì ~ '’¥³’¥ó’¥Ô’¥å’¡¼’¥¿[’¥Ç’¥°]';
+select * from ’·×’»»’µ¡’ÍÑ’¸ì where ’ÍÑ’¸ì ~* '’¥³’¥ó’¥Ô’¥å’¡¼’¥¿[’¥Ç’¥°]';
+select *,character_length(’ÍÑ’¸ì) from ’·×’»»’µ¡’ÍÑ’¸ì;
+select *,octet_length(’ÍÑ’¸ì) from ’·×’»»’µ¡’ÍÑ’¸ì;
+select *,position('’¥Ç' in ’ÍÑ’¸ì) from ’·×’»»’µ¡’ÍÑ’¸ì;
+select *,substring(’ÍÑ’¸ì from 10 for 4) from ’·×’»»’µ¡’ÍÑ’¸ì;
+drop table ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+create table ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï(‘Êõ‘Óï text, ‘·Ö‘Àà‘ºÅ varchar, ‘±¸‘×¢1A char(16));
+create index ‘¼Æ‘Ëã‘»ú‘Êõ‘Óïindex1 on ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï using btree(‘Êõ‘Óï);
+create index ‘¼Æ‘Ëã‘»ú‘Êõ‘Óïindex2 on ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï using btree(‘·Ö‘Àà‘ºÅ);
+insert into ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï values('‘µç‘ÄÔ‘Ïԑʾ‘ÆÁ','‘»úA01‘ÉÏ');
+insert into ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï values('‘µç‘Äԑͼ‘ÐÎ','‘·ÖB01‘ÖÐ');
+insert into ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï values('‘µç‘ÄÔ‘³Ì‘Ðò‘Ô±','‘ÈËZ01‘ÏÂ');
+vacuum ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï where ‘·Ö‘Àà‘ºÅ = '‘ÈËZ01‘ÏÂ';
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï where ‘·Ö‘Àà‘ºÅ ~* '‘ÈËz01‘ÏÂ';
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï where ‘·Ö‘Àà‘ºÅ like '_Z01_';
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï where ‘·Ö‘Àà‘ºÅ like '_Z%';
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï where ‘Êõ‘Óï ~ '‘µç‘ÄÔ[‘Ïԑͼ]';
+select * from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï where ‘Êõ‘Óï ~* '‘µç‘ÄÔ[‘Ïԑͼ]';
+select *,character_length(‘Êõ‘Óï) from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+select *,octet_length(‘Êõ‘Óï) from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+select *,position('‘ÏÔ' in ‘Êõ‘Óï) from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+select *,substring(‘Êõ‘Óï from 3 for 4) from ‘¼Æ‘Ëã‘»ú‘Êõ‘Óï;
+drop table “ͪ“ß©“Ѧ“¿ë“¾î;
+create table “ͪ“ß©“Ѧ“¿ë“¾î (“¿ë“¾î text, “Ý“׾“ÄÚ“µå varchar, “ºñ“°í1A“¶ó“±¸ char(16));
+create index “ͪ“ß©“Ѧ“¿ë“¾îindex1 on “ͪ“ß©“Ѧ“¿ë“¾î using btree (“¿ë“¾î);
+create index “ͪ“ß©“Ѧ“¿ë“¾îindex2 on “ͪ“ß©“Ѧ“¿ë“¾î using hash (“Ý“׾“ÄÚ“µå);
+insert into “ͪ“ß©“Ѧ“¿ë“¾î values('“ÄÄ“Ç»“ÅÍ“µð“½º“ÇÓ·¹“ÀÌ', '“ѦA01“ß¾');
+insert into “ͪ“ß©“Ѧ“¿ë“¾î values('“ÄÄ“Ç»“ÅÍ“±×“·¡“ÇÈ“½º', '“ÝÂB10“ñé');
+insert into “ͪ“ß©“Ѧ“¿ë“¾î values('“ÄÄ“Ç»“ÅÍ“ÇÁ“·Î“±×“·¡“¸Ó', '“ìÑZ01“ù»');
+vacuum “ͪ“ß©“Ѧ“¿ë“¾î;
+select * from “ͪ“ß©“Ѧ“¿ë“¾î;
+select * from “ͪ“ß©“Ѧ“¿ë“¾î where “Ý“׾“ÄÚ“µå = '“ìÑZ01“ù»';
+select * from “ͪ“ß©“Ѧ“¿ë“¾î where “Ý“׾“ÄÚ“µå ~* '“ìÑz01“ù»';
+select * from “ͪ“ß©“Ѧ“¿ë“¾î where “Ý“׾“ÄÚ“µå like '_Z01_';
+select * from “ͪ“ß©“Ѧ“¿ë“¾î where “Ý“׾“ÄÚ“µå like '_Z%';
+select * from “ͪ“ß©“Ѧ“¿ë“¾î where “¿ë“¾î ~ '“ÄÄ“Ç»“ÅÍ[“µð“±×]';
+select * from “ͪ“ß©“Ѧ“¿ë“¾î where “¿ë“¾î ~* '“ÄÄ“Ç»“ÅÍ[“µð“±×]';
+select *,character_length(“¿ë“¾î) from “ͪ“ß©“Ѧ“¿ë“¾î;
+select *,octet_length(“¿ë“¾î) from “ͪ“ß©“Ѧ“¿ë“¾î;
+select *,position('“µð' in “¿ë“¾î) from “ͪ“ß©“Ѧ“¿ë“¾î;
+select *,substring(“¿ë“¾î from 3 for 4) from “ͪ“ß©“Ѧ“¿ë“¾î;
+drop table test;
+create table test (t text);
+insert into test values('ENGLISH');
+insert into test values('FRANÇAIS');
+insert into test values('ESPAÑOL');
+insert into test values('ÍSLENSKA');
+insert into test values('ENGLISH FRANÇAIS ESPAÑOL ÍSLENSKA');
+vacuum test;
+select * from test;
+select * from test where t = 'ESPAÑOL';
+select * from test where t ~* 'espaÑol';
+select *,character_length(t) from test;
+select *,octet_length(t) from test;
+select *,position('L' in t) from test;
+select *,substring(t from 3 for 4) from test;
diff --git a/src/test/mb/sql/sjis.sql b/src/test/mb/sql/sjis.sql
new file mode 100644
index 0000000..dfa66fc
--- /dev/null
+++ b/src/test/mb/sql/sjis.sql
@@ -0,0 +1,20 @@
+drop table ŒvŽZ‹@—pŒê;
+create table ŒvŽZ‹@—pŒê (—pŒê text, •ª—ÞƒR[ƒh varchar, ”õl1A‚¾‚æ char(16));
+create index ŒvŽZ‹@—pŒêindex1 on ŒvŽZ‹@—pŒê using btree (—pŒê);
+create index ŒvŽZ‹@—pŒêindex2 on ŒvŽZ‹@—pŒê using hash (•ª—ÞƒR[ƒh);
+insert into ŒvŽZ‹@—pŒê values('ƒRƒ“ƒsƒ…[ƒ^ƒfƒBƒXƒvƒŒƒC','‹@A01ã');
+insert into ŒvŽZ‹@—pŒê values('ƒRƒ“ƒsƒ…[ƒ^ƒOƒ‰ƒtƒBƒbƒNƒX','•ªB10’†');
+insert into ŒvŽZ‹@—pŒê values('ƒRƒ“ƒsƒ…[ƒ^ƒvƒƒOƒ‰ƒ}[','lZ01‰º');
+vacuum ŒvŽZ‹@—pŒê;
+select * from ŒvŽZ‹@—pŒê;
+select * from ŒvŽZ‹@—pŒê where •ª—ÞƒR[ƒh = 'lZ01‰º';
+select * from ŒvŽZ‹@—pŒê where •ª—ÞƒR[ƒh ~* 'lz01‰º';
+select * from ŒvŽZ‹@—pŒê where •ª—ÞƒR[ƒh like '_Z01_';
+select * from ŒvŽZ‹@—pŒê where •ª—ÞƒR[ƒh like '_Z%';
+select * from ŒvŽZ‹@—pŒê where —pŒê ~ 'ƒRƒ“ƒsƒ…[ƒ^[ƒfƒO]';
+select * from ŒvŽZ‹@—pŒê where —pŒê ~* 'ƒRƒ“ƒsƒ…[ƒ^[ƒfƒO]';
+select *,character_length(—pŒê) from ŒvŽZ‹@—pŒê;
+select *,octet_length(—pŒê) from ŒvŽZ‹@—pŒê;
+select *,position('ƒf' in —pŒê) from ŒvŽZ‹@—pŒê;
+select *,substring(—pŒê from 10 for 4) from ŒvŽZ‹@—pŒê;
+copy ŒvŽZ‹@—pŒê to stdout;
diff --git a/src/test/mb/sql/utf8.sql b/src/test/mb/sql/utf8.sql
new file mode 100644
index 0000000..e722d3d
--- /dev/null
+++ b/src/test/mb/sql/utf8.sql
@@ -0,0 +1,19 @@
+drop table 計算機用語;
+create table 計算機用語 (用語 text, 分類コード varchar, 備考1Aã ã‚ˆ char(16));
+create index 計算機用語index1 on 計算機用語 using btree (用語);
+create index 計算機用語index2 on 計算機用語 using hash (分類コード);
+insert into 計算機用語 values('コンピュータディスプレイ','機A01上');
+insert into 計算機用語 values('コンピュータグラフィックス','分B10中');
+insert into 計算機用語 values('コンピュータプログラマー','人Z01下');
+vacuum 計算機用語;
+select * from 計算機用語;
+select * from 計算機用語 where 分類コード = '人Z01下';
+select * from 計算機用語 where 分類コード ~* '人z01下';
+select * from 計算機用語 where 分類コード like '_Z01_';
+select * from 計算機用語 where 分類コード like '_Z%';
+select * from 計算機用語 where 用語 ~ 'コンピュータ[デグ]';
+select * from 計算機用語 where 用語 ~* 'コンピュータ[デグ]';
+select *,character_length(用語) from 計算機用語;
+select *,octet_length(用語) from 計算機用語;
+select *,position('デ' in 用語) from 計算機用語;
+select *,substring(用語 from 10 for 4) from 計算機用語;
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
new file mode 100644
index 0000000..dffc79b
--- /dev/null
+++ b/src/test/modules/Makefile
@@ -0,0 +1,40 @@
+# src/test/modules/Makefile
+
+subdir = src/test/modules
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+SUBDIRS = \
+ brin \
+ commit_ts \
+ delay_execution \
+ dummy_index_am \
+ dummy_seclabel \
+ libpq_pipeline \
+ plsample \
+ snapshot_too_old \
+ spgist_name_ops \
+ test_bloomfilter \
+ test_ddl_deparse \
+ test_extensions \
+ test_ginpostinglist \
+ test_integerset \
+ test_misc \
+ test_parser \
+ test_pg_dump \
+ test_predtest \
+ test_rbtree \
+ test_regex \
+ test_rls_hooks \
+ test_shm_mq \
+ unsafe_tests \
+ worker_spi
+
+ifeq ($(with_ssl),openssl)
+SUBDIRS += ssl_passphrase_callback
+else
+ALWAYS_SUBDIRS += ssl_passphrase_callback
+endif
+
+$(recurse)
+$(recurse_always)
diff --git a/src/test/modules/README b/src/test/modules/README
new file mode 100644
index 0000000..025ecac
--- /dev/null
+++ b/src/test/modules/README
@@ -0,0 +1,20 @@
+Test extensions and libraries
+=============================
+
+src/test/modules contains PostgreSQL extensions that are primarily or entirely
+intended for testing PostgreSQL and/or to serve as example code. The extensions
+here aren't intended to be installed in a production server and aren't suitable
+for "real work".
+
+Furthermore, while you can do "make install" and "make installcheck" in
+this directory or its children, it is NOT ADVISABLE to do so with a server
+containing valuable data. Some of these tests may have undesirable
+side-effects on roles or other global objects within the tested server.
+"make installcheck-world" at the top level does not recurse into this
+directory.
+
+Most extensions have their own pg_regress tests or isolationtester specs. Some
+are also used by tests elsewhere in the tree.
+
+If you're adding new hooks or other functionality exposed as C-level API this
+is where to add the tests for it.
diff --git a/src/test/modules/brin/.gitignore b/src/test/modules/brin/.gitignore
new file mode 100644
index 0000000..d6d1aaa
--- /dev/null
+++ b/src/test/modules/brin/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/output_iso/
+/tmp_check/
+/tmp_check_iso/
diff --git a/src/test/modules/brin/Makefile b/src/test/modules/brin/Makefile
new file mode 100644
index 0000000..c871593
--- /dev/null
+++ b/src/test/modules/brin/Makefile
@@ -0,0 +1,17 @@
+# src/test/modules/brin/Makefile
+
+EXTRA_INSTALL = contrib/pageinspect
+
+ISOLATION = summarization-and-inprogress-insertion
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/brin
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out b/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out
new file mode 100644
index 0000000..2a4755d
--- /dev/null
+++ b/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out
@@ -0,0 +1,51 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2check s1b s2b s1i s2summ s1c s2c s2check
+step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass);
+itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value
+----------+------+------+--------+--------+-----------+--------
+ 1| 0| 1|f |f |f |{1 .. 1}
+(1 row)
+
+step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s2b: BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1;
+?column?
+--------
+ 1
+(1 row)
+
+step s1i: INSERT INTO brin_iso VALUES (1000);
+step s2summ: SELECT brin_summarize_new_values('brinidx'::regclass);
+brin_summarize_new_values
+-------------------------
+ 1
+(1 row)
+
+step s1c: COMMIT;
+step s2c: COMMIT;
+step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass);
+itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value
+----------+------+------+--------+--------+-----------+-----------
+ 1| 0| 1|f |f |f |{1 .. 1}
+ 2| 1| 1|f |f |f |{1 .. 1000}
+(2 rows)
+
+
+starting permutation: s2check s1b s1i s2vacuum s1c s2check
+step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass);
+itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value
+----------+------+------+--------+--------+-----------+--------
+ 1| 0| 1|f |f |f |{1 .. 1}
+(1 row)
+
+step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1i: INSERT INTO brin_iso VALUES (1000);
+step s2vacuum: VACUUM brin_iso;
+step s1c: COMMIT;
+step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass);
+itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value
+----------+------+------+--------+--------+-----------+-----------
+ 1| 0| 1|f |f |f |{1 .. 1}
+ 2| 1| 1|f |f |f |{1 .. 1000}
+(2 rows)
+
diff --git a/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec b/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec
new file mode 100644
index 0000000..19ac18a
--- /dev/null
+++ b/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec
@@ -0,0 +1,44 @@
+# This test verifies that values inserted in transactions still in progress
+# are considered during concurrent range summarization (either using the
+# brin_summarize_new_values function or regular VACUUM).
+
+setup
+{
+ CREATE TABLE brin_iso (
+ value int
+ ) WITH (fillfactor=10);
+ CREATE INDEX brinidx ON brin_iso USING brin (value) WITH (pages_per_range=1);
+ -- this fills the first page
+ DO $$
+ DECLARE curtid tid;
+ BEGIN
+ LOOP
+ INSERT INTO brin_iso VALUES (1) RETURNING ctid INTO curtid;
+ EXIT WHEN curtid > tid '(1, 0)';
+ END LOOP;
+ END;
+ $$;
+ CREATE EXTENSION IF NOT EXISTS pageinspect;
+}
+
+teardown
+{
+ DROP TABLE brin_iso;
+}
+
+session "s1"
+step "s1b" { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step "s1i" { INSERT INTO brin_iso VALUES (1000); }
+step "s1c" { COMMIT; }
+
+session "s2"
+step "s2b" { BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; }
+step "s2summ" { SELECT brin_summarize_new_values('brinidx'::regclass); }
+step "s2c" { COMMIT; }
+
+step "s2vacuum" { VACUUM brin_iso; }
+
+step "s2check" { SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); }
+
+permutation "s2check" "s1b" "s2b" "s1i" "s2summ" "s1c" "s2c" "s2check"
+permutation "s2check" "s1b" "s1i" "s2vacuum" "s1c" "s2check"
diff --git a/src/test/modules/brin/t/01_workitems.pl b/src/test/modules/brin/t/01_workitems.pl
new file mode 100644
index 0000000..a4f603a
--- /dev/null
+++ b/src/test/modules/brin/t/01_workitems.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Verify that work items work correctly
+
+use strict;
+use warnings;
+
+use TestLib;
+use Test::More tests => 2;
+use PostgresNode;
+
+my $node = get_new_node('tango');
+$node->init;
+$node->append_conf('postgresql.conf', 'autovacuum_naptime=1s');
+$node->start;
+
+$node->safe_psql('postgres', 'create extension pageinspect');
+
+# Create a table with an autosummarizing BRIN index
+$node->safe_psql(
+ 'postgres',
+ 'create table brin_wi (a int) with (fillfactor = 10);
+ create index brin_wi_idx on brin_wi using brin (a) with (pages_per_range=1, autosummarize=on);
+ '
+);
+my $count = $node->safe_psql('postgres',
+ "select count(*) from brin_page_items(get_raw_page('brin_wi_idx', 2), 'brin_wi_idx'::regclass)"
+);
+is($count, '1', "initial index state is correct");
+
+$node->safe_psql('postgres',
+ 'insert into brin_wi select * from generate_series(1, 100)');
+
+$node->poll_query_until(
+ 'postgres',
+ "select count(*) > 1 from brin_page_items(get_raw_page('brin_wi_idx', 2), 'brin_wi_idx'::regclass)",
+ 't');
+
+$count = $node->safe_psql('postgres',
+ "select count(*) > 1 from brin_page_items(get_raw_page('brin_wi_idx', 2), 'brin_wi_idx'::regclass)"
+);
+is($count, 't', "index got summarized");
+$node->stop;
diff --git a/src/test/modules/brin/t/02_wal_consistency.pl b/src/test/modules/brin/t/02_wal_consistency.pl
new file mode 100644
index 0000000..41d920a
--- /dev/null
+++ b/src/test/modules/brin/t/02_wal_consistency.pl
@@ -0,0 +1,64 @@
+# Copyright (c) 2021-2022, PostgreSQL Global Development Group
+
+# Verify WAL consistency
+
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Utils;
+use Test::More;
+use PostgreSQL::Test::Cluster;
+
+# Set up primary
+my $whiskey = PostgreSQL::Test::Cluster->new('whiskey');
+$whiskey->init(allows_streaming => 1);
+$whiskey->append_conf('postgresql.conf', 'wal_consistency_checking = brin');
+$whiskey->start;
+$whiskey->safe_psql('postgres', 'create extension pageinspect');
+is( $whiskey->psql(
+ 'postgres',
+ qq[SELECT pg_create_physical_replication_slot('standby_1');]),
+ 0,
+ 'physical slot created on primary');
+
+# Take backup
+my $backup_name = 'brinbkp';
+$whiskey->backup($backup_name);
+
+# Create streaming standby linking to primary
+my $charlie = PostgreSQL::Test::Cluster->new('charlie');
+$charlie->init_from_backup($whiskey, $backup_name, has_streaming => 1);
+$charlie->append_conf('postgresql.conf', 'primary_slot_name = standby_1');
+$charlie->start;
+
+# Now write some WAL in the primary
+
+$whiskey->safe_psql(
+ 'postgres', qq{
+create table tbl_timestamp0 (d1 timestamp(0) without time zone) with (fillfactor=10);
+create index on tbl_timestamp0 using brin (d1) with (pages_per_range = 1, autosummarize=false);
+});
+# Run a loop that will end when the second revmap page is created
+$whiskey->safe_psql(
+ 'postgres', q{
+do
+$$
+declare
+ current timestamp with time zone := '2019-03-27 08:14:01.123456789 UTC';
+begin
+ loop
+ insert into tbl_timestamp0 select i from
+ generate_series(current, current + interval '1 day', '28 seconds') i;
+ perform brin_summarize_new_values('tbl_timestamp0_d1_idx');
+ if (brin_metapage_info(get_raw_page('tbl_timestamp0_d1_idx', 0))).lastrevmappage > 1 then
+ exit;
+ end if;
+ current := current + interval '1 day';
+ end loop;
+end
+$$;
+});
+
+$whiskey->wait_for_catchup($charlie, 'replay', $whiskey->lsn('insert'));
+
+done_testing();
diff --git a/src/test/modules/commit_ts/.gitignore b/src/test/modules/commit_ts/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/commit_ts/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/commit_ts/Makefile b/src/test/modules/commit_ts/Makefile
new file mode 100644
index 0000000..113bcfa
--- /dev/null
+++ b/src/test/modules/commit_ts/Makefile
@@ -0,0 +1,20 @@
+# src/test/modules/commit_ts/Makefile
+
+REGRESS = commit_timestamp
+REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/commit_ts/commit_ts.conf
+# Disabled because these tests require "track_commit_timestamp = on",
+# which typical installcheck users do not have (e.g. buildfarm clients).
+NO_INSTALLCHECK = 1
+
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/commit_ts
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/commit_ts/commit_ts.conf b/src/test/modules/commit_ts/commit_ts.conf
new file mode 100644
index 0000000..e9d3c35
--- /dev/null
+++ b/src/test/modules/commit_ts/commit_ts.conf
@@ -0,0 +1 @@
+track_commit_timestamp = on
diff --git a/src/test/modules/commit_ts/expected/commit_timestamp.out b/src/test/modules/commit_ts/expected/commit_timestamp.out
new file mode 100644
index 0000000..bb2fda2
--- /dev/null
+++ b/src/test/modules/commit_ts/expected/commit_timestamp.out
@@ -0,0 +1,139 @@
+--
+-- Commit Timestamp
+--
+SHOW track_commit_timestamp;
+ track_commit_timestamp
+------------------------
+ on
+(1 row)
+
+CREATE TABLE committs_test(id serial, ts timestamptz default now());
+INSERT INTO committs_test DEFAULT VALUES;
+INSERT INTO committs_test DEFAULT VALUES;
+INSERT INTO committs_test DEFAULT VALUES;
+SELECT id,
+ pg_xact_commit_timestamp(xmin) >= ts,
+ pg_xact_commit_timestamp(xmin) <= now(),
+ pg_xact_commit_timestamp(xmin) - ts < '60s' -- 60s should give a lot of reserve
+FROM committs_test
+ORDER BY id;
+ id | ?column? | ?column? | ?column?
+----+----------+----------+----------
+ 1 | t | t | t
+ 2 | t | t | t
+ 3 | t | t | t
+(3 rows)
+
+DROP TABLE committs_test;
+SELECT pg_xact_commit_timestamp('0'::xid);
+ERROR: cannot retrieve commit timestamp for transaction 0
+SELECT pg_xact_commit_timestamp('1'::xid);
+ pg_xact_commit_timestamp
+--------------------------
+
+(1 row)
+
+SELECT pg_xact_commit_timestamp('2'::xid);
+ pg_xact_commit_timestamp
+--------------------------
+
+(1 row)
+
+SELECT x.xid::text::bigint > 0 as xid_valid,
+ x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ roident != 0 AS valid_roident
+ FROM pg_last_committed_xact() x;
+ xid_valid | ts_low | ts_high | valid_roident
+-----------+--------+---------+---------------
+ t | t | t | f
+(1 row)
+
+-- Test non-normal transaction ids.
+SELECT * FROM pg_xact_commit_timestamp_origin(NULL); -- ok, NULL
+ timestamp | roident
+-----------+---------
+ |
+(1 row)
+
+SELECT * FROM pg_xact_commit_timestamp_origin('0'::xid); -- error
+ERROR: cannot retrieve commit timestamp for transaction 0
+SELECT * FROM pg_xact_commit_timestamp_origin('1'::xid); -- ok, NULL
+ timestamp | roident
+-----------+---------
+ |
+(1 row)
+
+SELECT * FROM pg_xact_commit_timestamp_origin('2'::xid); -- ok, NULL
+ timestamp | roident
+-----------+---------
+ |
+(1 row)
+
+-- Test transaction without replication origin
+SELECT txid_current() as txid_no_origin \gset
+SELECT x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ roident != 0 AS valid_roident
+ FROM pg_last_committed_xact() x;
+ ts_low | ts_high | valid_roident
+--------+---------+---------------
+ t | t | f
+(1 row)
+
+SELECT x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ roident != 0 AS valid_roident
+ FROM pg_xact_commit_timestamp_origin(:'txid_no_origin') x;
+ ts_low | ts_high | valid_roident
+--------+---------+---------------
+ t | t | f
+(1 row)
+
+-- Test transaction with replication origin
+SELECT pg_replication_origin_create('regress_commit_ts: get_origin') != 0
+ AS valid_roident;
+ valid_roident
+---------------
+ t
+(1 row)
+
+SELECT pg_replication_origin_session_setup('regress_commit_ts: get_origin');
+ pg_replication_origin_session_setup
+-------------------------------------
+
+(1 row)
+
+SELECT txid_current() as txid_with_origin \gset
+SELECT x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ r.roname
+ FROM pg_last_committed_xact() x, pg_replication_origin r
+ WHERE r.roident = x.roident;
+ ts_low | ts_high | roname
+--------+---------+-------------------------------
+ t | t | regress_commit_ts: get_origin
+(1 row)
+
+SELECT x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ r.roname
+ FROM pg_xact_commit_timestamp_origin(:'txid_with_origin') x, pg_replication_origin r
+ WHERE r.roident = x.roident;
+ ts_low | ts_high | roname
+--------+---------+-------------------------------
+ t | t | regress_commit_ts: get_origin
+(1 row)
+
+SELECT pg_replication_origin_session_reset();
+ pg_replication_origin_session_reset
+-------------------------------------
+
+(1 row)
+
+SELECT pg_replication_origin_drop('regress_commit_ts: get_origin');
+ pg_replication_origin_drop
+----------------------------
+
+(1 row)
+
diff --git a/src/test/modules/commit_ts/expected/commit_timestamp_1.out b/src/test/modules/commit_ts/expected/commit_timestamp_1.out
new file mode 100644
index 0000000..f37e701
--- /dev/null
+++ b/src/test/modules/commit_ts/expected/commit_timestamp_1.out
@@ -0,0 +1,119 @@
+--
+-- Commit Timestamp
+--
+SHOW track_commit_timestamp;
+ track_commit_timestamp
+------------------------
+ off
+(1 row)
+
+CREATE TABLE committs_test(id serial, ts timestamptz default now());
+INSERT INTO committs_test DEFAULT VALUES;
+INSERT INTO committs_test DEFAULT VALUES;
+INSERT INTO committs_test DEFAULT VALUES;
+SELECT id,
+ pg_xact_commit_timestamp(xmin) >= ts,
+ pg_xact_commit_timestamp(xmin) <= now(),
+ pg_xact_commit_timestamp(xmin) - ts < '60s' -- 60s should give a lot of reserve
+FROM committs_test
+ORDER BY id;
+ERROR: could not get commit timestamp data
+HINT: Make sure the configuration parameter "track_commit_timestamp" is set.
+DROP TABLE committs_test;
+SELECT pg_xact_commit_timestamp('0'::xid);
+ERROR: cannot retrieve commit timestamp for transaction 0
+SELECT pg_xact_commit_timestamp('1'::xid);
+ pg_xact_commit_timestamp
+--------------------------
+
+(1 row)
+
+SELECT pg_xact_commit_timestamp('2'::xid);
+ pg_xact_commit_timestamp
+--------------------------
+
+(1 row)
+
+SELECT x.xid::text::bigint > 0 as xid_valid,
+ x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ roident != 0 AS valid_roident
+ FROM pg_last_committed_xact() x;
+ERROR: could not get commit timestamp data
+HINT: Make sure the configuration parameter "track_commit_timestamp" is set.
+-- Test non-normal transaction ids.
+SELECT * FROM pg_xact_commit_timestamp_origin(NULL); -- ok, NULL
+ timestamp | roident
+-----------+---------
+ |
+(1 row)
+
+SELECT * FROM pg_xact_commit_timestamp_origin('0'::xid); -- error
+ERROR: cannot retrieve commit timestamp for transaction 0
+SELECT * FROM pg_xact_commit_timestamp_origin('1'::xid); -- ok, NULL
+ timestamp | roident
+-----------+---------
+ |
+(1 row)
+
+SELECT * FROM pg_xact_commit_timestamp_origin('2'::xid); -- ok, NULL
+ timestamp | roident
+-----------+---------
+ |
+(1 row)
+
+-- Test transaction without replication origin
+SELECT txid_current() as txid_no_origin \gset
+SELECT x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ roident != 0 AS valid_roident
+ FROM pg_last_committed_xact() x;
+ERROR: could not get commit timestamp data
+HINT: Make sure the configuration parameter "track_commit_timestamp" is set.
+SELECT x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ roident != 0 AS valid_roident
+ FROM pg_xact_commit_timestamp_origin(:'txid_no_origin') x;
+ERROR: could not get commit timestamp data
+HINT: Make sure the configuration parameter "track_commit_timestamp" is set.
+-- Test transaction with replication origin
+SELECT pg_replication_origin_create('regress_commit_ts: get_origin') != 0
+ AS valid_roident;
+ valid_roident
+---------------
+ t
+(1 row)
+
+SELECT pg_replication_origin_session_setup('regress_commit_ts: get_origin');
+ pg_replication_origin_session_setup
+-------------------------------------
+
+(1 row)
+
+SELECT txid_current() as txid_with_origin \gset
+SELECT x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ r.roname
+ FROM pg_last_committed_xact() x, pg_replication_origin r
+ WHERE r.roident = x.roident;
+ERROR: could not get commit timestamp data
+HINT: Make sure the configuration parameter "track_commit_timestamp" is set.
+SELECT x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ r.roname
+ FROM pg_xact_commit_timestamp_origin(:'txid_with_origin') x, pg_replication_origin r
+ WHERE r.roident = x.roident;
+ERROR: could not get commit timestamp data
+HINT: Make sure the configuration parameter "track_commit_timestamp" is set.
+SELECT pg_replication_origin_session_reset();
+ pg_replication_origin_session_reset
+-------------------------------------
+
+(1 row)
+
+SELECT pg_replication_origin_drop('regress_commit_ts: get_origin');
+ pg_replication_origin_drop
+----------------------------
+
+(1 row)
+
diff --git a/src/test/modules/commit_ts/sql/commit_timestamp.sql b/src/test/modules/commit_ts/sql/commit_timestamp.sql
new file mode 100644
index 0000000..3bb7bb2
--- /dev/null
+++ b/src/test/modules/commit_ts/sql/commit_timestamp.sql
@@ -0,0 +1,64 @@
+--
+-- Commit Timestamp
+--
+SHOW track_commit_timestamp;
+CREATE TABLE committs_test(id serial, ts timestamptz default now());
+
+INSERT INTO committs_test DEFAULT VALUES;
+INSERT INTO committs_test DEFAULT VALUES;
+INSERT INTO committs_test DEFAULT VALUES;
+
+SELECT id,
+ pg_xact_commit_timestamp(xmin) >= ts,
+ pg_xact_commit_timestamp(xmin) <= now(),
+ pg_xact_commit_timestamp(xmin) - ts < '60s' -- 60s should give a lot of reserve
+FROM committs_test
+ORDER BY id;
+
+DROP TABLE committs_test;
+
+SELECT pg_xact_commit_timestamp('0'::xid);
+SELECT pg_xact_commit_timestamp('1'::xid);
+SELECT pg_xact_commit_timestamp('2'::xid);
+
+SELECT x.xid::text::bigint > 0 as xid_valid,
+ x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ roident != 0 AS valid_roident
+ FROM pg_last_committed_xact() x;
+
+-- Test non-normal transaction ids.
+SELECT * FROM pg_xact_commit_timestamp_origin(NULL); -- ok, NULL
+SELECT * FROM pg_xact_commit_timestamp_origin('0'::xid); -- error
+SELECT * FROM pg_xact_commit_timestamp_origin('1'::xid); -- ok, NULL
+SELECT * FROM pg_xact_commit_timestamp_origin('2'::xid); -- ok, NULL
+
+-- Test transaction without replication origin
+SELECT txid_current() as txid_no_origin \gset
+SELECT x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ roident != 0 AS valid_roident
+ FROM pg_last_committed_xact() x;
+SELECT x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ roident != 0 AS valid_roident
+ FROM pg_xact_commit_timestamp_origin(:'txid_no_origin') x;
+
+-- Test transaction with replication origin
+SELECT pg_replication_origin_create('regress_commit_ts: get_origin') != 0
+ AS valid_roident;
+SELECT pg_replication_origin_session_setup('regress_commit_ts: get_origin');
+SELECT txid_current() as txid_with_origin \gset
+SELECT x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ r.roname
+ FROM pg_last_committed_xact() x, pg_replication_origin r
+ WHERE r.roident = x.roident;
+SELECT x.timestamp > '-infinity'::timestamptz AS ts_low,
+ x.timestamp <= now() AS ts_high,
+ r.roname
+ FROM pg_xact_commit_timestamp_origin(:'txid_with_origin') x, pg_replication_origin r
+ WHERE r.roident = x.roident;
+
+SELECT pg_replication_origin_session_reset();
+SELECT pg_replication_origin_drop('regress_commit_ts: get_origin');
diff --git a/src/test/modules/commit_ts/t/001_base.pl b/src/test/modules/commit_ts/t/001_base.pl
new file mode 100644
index 0000000..dd41936
--- /dev/null
+++ b/src/test/modules/commit_ts/t/001_base.pl
@@ -0,0 +1,36 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Single-node test: value can be set, and is still present after recovery
+
+use strict;
+use warnings;
+
+use TestLib;
+use Test::More tests => 2;
+use PostgresNode;
+
+my $node = get_new_node('foxtrot');
+$node->init;
+$node->append_conf('postgresql.conf', 'track_commit_timestamp = on');
+$node->start;
+
+# Create a table, compare "now()" to the commit TS of its xmin
+$node->safe_psql('postgres',
+ 'create table t as select now from (select now(), pg_sleep(1)) f');
+my $true = $node->safe_psql('postgres',
+ 'select t.now - ts.* < \'1s\' from t, pg_class c, pg_xact_commit_timestamp(c.xmin) ts where relname = \'t\''
+);
+is($true, 't', 'commit TS is set');
+my $ts = $node->safe_psql('postgres',
+ 'select ts.* from pg_class, pg_xact_commit_timestamp(xmin) ts where relname = \'t\''
+);
+
+# Verify that we read the same TS after crash recovery
+$node->stop('immediate');
+$node->start;
+
+my $recovered_ts = $node->safe_psql('postgres',
+ 'select ts.* from pg_class, pg_xact_commit_timestamp(xmin) ts where relname = \'t\''
+);
+is($recovered_ts, $ts, 'commit TS remains after crash recovery');
diff --git a/src/test/modules/commit_ts/t/002_standby.pl b/src/test/modules/commit_ts/t/002_standby.pl
new file mode 100644
index 0000000..2664b17
--- /dev/null
+++ b/src/test/modules/commit_ts/t/002_standby.pl
@@ -0,0 +1,66 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test simple scenario involving a standby
+
+use strict;
+use warnings;
+
+use TestLib;
+use Test::More tests => 4;
+use PostgresNode;
+
+my $bkplabel = 'backup';
+my $primary = get_new_node('primary');
+$primary->init(allows_streaming => 1);
+
+$primary->append_conf(
+ 'postgresql.conf', qq{
+ track_commit_timestamp = on
+ max_wal_senders = 5
+ });
+$primary->start;
+$primary->backup($bkplabel);
+
+my $standby = get_new_node('standby');
+$standby->init_from_backup($primary, $bkplabel, has_streaming => 1);
+$standby->start;
+
+for my $i (1 .. 10)
+{
+ $primary->safe_psql('postgres', "create table t$i()");
+}
+my $primary_ts = $primary->safe_psql('postgres',
+ qq{SELECT ts.* FROM pg_class, pg_xact_commit_timestamp(xmin) AS ts WHERE relname = 't10'}
+);
+my $primary_lsn =
+ $primary->safe_psql('postgres', 'select pg_current_wal_lsn()');
+$standby->poll_query_until('postgres',
+ qq{SELECT '$primary_lsn'::pg_lsn <= pg_last_wal_replay_lsn()})
+ or die "standby never caught up";
+
+my $standby_ts = $standby->safe_psql('postgres',
+ qq{select ts.* from pg_class, pg_xact_commit_timestamp(xmin) ts where relname = 't10'}
+);
+is($primary_ts, $standby_ts, "standby gives same value as primary");
+
+$primary->append_conf('postgresql.conf', 'track_commit_timestamp = off');
+$primary->restart;
+$primary->safe_psql('postgres', 'checkpoint');
+$primary_lsn = $primary->safe_psql('postgres', 'select pg_current_wal_lsn()');
+$standby->poll_query_until('postgres',
+ qq{SELECT '$primary_lsn'::pg_lsn <= pg_last_wal_replay_lsn()})
+ or die "standby never caught up";
+$standby->safe_psql('postgres', 'checkpoint');
+
+# This one should raise an error now
+my ($ret, $standby_ts_stdout, $standby_ts_stderr) = $standby->psql('postgres',
+ 'select ts.* from pg_class, pg_xact_commit_timestamp(xmin) ts where relname = \'t10\''
+);
+is($ret, 3, 'standby errors when primary turned feature off');
+is($standby_ts_stdout, '',
+ "standby gives no value when primary turned feature off");
+like(
+ $standby_ts_stderr,
+ qr/could not get commit timestamp data/,
+ 'expected error when primary turned feature off');
diff --git a/src/test/modules/commit_ts/t/003_standby_2.pl b/src/test/modules/commit_ts/t/003_standby_2.pl
new file mode 100644
index 0000000..57ab9b1
--- /dev/null
+++ b/src/test/modules/commit_ts/t/003_standby_2.pl
@@ -0,0 +1,67 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test primary/standby scenario where the track_commit_timestamp GUC is
+# repeatedly toggled on and off.
+use strict;
+use warnings;
+
+use TestLib;
+use Test::More tests => 4;
+use PostgresNode;
+
+my $bkplabel = 'backup';
+my $primary = get_new_node('primary');
+$primary->init(allows_streaming => 1);
+$primary->append_conf(
+ 'postgresql.conf', qq{
+ track_commit_timestamp = on
+ max_wal_senders = 5
+ });
+$primary->start;
+$primary->backup($bkplabel);
+
+my $standby = get_new_node('standby');
+$standby->init_from_backup($primary, $bkplabel, has_streaming => 1);
+$standby->start;
+
+for my $i (1 .. 10)
+{
+ $primary->safe_psql('postgres', "create table t$i()");
+}
+$primary->append_conf('postgresql.conf', 'track_commit_timestamp = off');
+$primary->restart;
+$primary->safe_psql('postgres', 'checkpoint');
+my $primary_lsn =
+ $primary->safe_psql('postgres', 'select pg_current_wal_lsn()');
+$standby->poll_query_until('postgres',
+ qq{SELECT '$primary_lsn'::pg_lsn <= pg_last_wal_replay_lsn()})
+ or die "standby never caught up";
+
+$standby->safe_psql('postgres', 'checkpoint');
+$standby->restart;
+
+my ($psql_ret, $standby_ts_stdout, $standby_ts_stderr) = $standby->psql(
+ 'postgres',
+ qq{SELECT ts.* FROM pg_class, pg_xact_commit_timestamp(xmin) AS ts WHERE relname = 't10'}
+);
+is($psql_ret, 3, 'expect error when getting commit timestamp after restart');
+is($standby_ts_stdout, '', "standby does not return a value after restart");
+like(
+ $standby_ts_stderr,
+ qr/could not get commit timestamp data/,
+ 'expected err msg after restart');
+
+$primary->append_conf('postgresql.conf', 'track_commit_timestamp = on');
+$primary->restart;
+$primary->append_conf('postgresql.conf', 'track_commit_timestamp = off');
+$primary->restart;
+
+system_or_bail('pg_ctl', '-D', $standby->data_dir, 'promote');
+
+$standby->safe_psql('postgres', "create table t11()");
+my $standby_ts = $standby->safe_psql('postgres',
+ qq{SELECT ts.* FROM pg_class, pg_xact_commit_timestamp(xmin) AS ts WHERE relname = 't11'}
+);
+isnt($standby_ts, '',
+ "standby gives valid value ($standby_ts) after promotion");
diff --git a/src/test/modules/commit_ts/t/004_restart.pl b/src/test/modules/commit_ts/t/004_restart.pl
new file mode 100644
index 0000000..bc99319
--- /dev/null
+++ b/src/test/modules/commit_ts/t/004_restart.pl
@@ -0,0 +1,152 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Testing of commit timestamps preservation across restarts
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 16;
+
+my $node_primary = get_new_node('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->append_conf('postgresql.conf', 'track_commit_timestamp = on');
+$node_primary->start;
+
+my ($ret, $stdout, $stderr);
+
+($ret, $stdout, $stderr) =
+ $node_primary->psql('postgres', qq[SELECT pg_xact_commit_timestamp('0');]);
+is($ret, 3, 'getting ts of InvalidTransactionId reports error');
+like(
+ $stderr,
+ qr/cannot retrieve commit timestamp for transaction/,
+ 'expected error from InvalidTransactionId');
+
+($ret, $stdout, $stderr) =
+ $node_primary->psql('postgres', qq[SELECT pg_xact_commit_timestamp('1');]);
+is($ret, 0, 'getting ts of BootstrapTransactionId succeeds');
+is($stdout, '', 'timestamp of BootstrapTransactionId is null');
+
+($ret, $stdout, $stderr) =
+ $node_primary->psql('postgres', qq[SELECT pg_xact_commit_timestamp('2');]);
+is($ret, 0, 'getting ts of FrozenTransactionId succeeds');
+is($stdout, '', 'timestamp of FrozenTransactionId is null');
+
+# Since FirstNormalTransactionId will've occurred during initdb, long before we
+# enabled commit timestamps, it'll be null since we have no cts data for it but
+# cts are enabled.
+is( $node_primary->safe_psql(
+ 'postgres', qq[SELECT pg_xact_commit_timestamp('3');]),
+ '',
+ 'committs for FirstNormalTransactionId is null');
+
+$node_primary->safe_psql('postgres',
+ qq[CREATE TABLE committs_test(x integer, y timestamp with time zone);]);
+
+my $xid = $node_primary->safe_psql(
+ 'postgres', qq[
+ BEGIN;
+ INSERT INTO committs_test(x, y) VALUES (1, current_timestamp);
+ SELECT pg_current_xact_id()::xid;
+ COMMIT;
+]);
+
+my $before_restart_ts = $node_primary->safe_psql('postgres',
+ qq[SELECT pg_xact_commit_timestamp('$xid');]);
+ok($before_restart_ts ne '' && $before_restart_ts ne 'null',
+ 'commit timestamp recorded');
+
+$node_primary->stop('immediate');
+$node_primary->start;
+
+my $after_crash_ts = $node_primary->safe_psql('postgres',
+ qq[SELECT pg_xact_commit_timestamp('$xid');]);
+is($after_crash_ts, $before_restart_ts,
+ 'timestamps before and after crash are equal');
+
+$node_primary->stop('fast');
+$node_primary->start;
+
+my $after_restart_ts = $node_primary->safe_psql('postgres',
+ qq[SELECT pg_xact_commit_timestamp('$xid');]);
+is($after_restart_ts, $before_restart_ts,
+ 'timestamps before and after restart are equal');
+
+# Now disable commit timestamps
+$node_primary->append_conf('postgresql.conf', 'track_commit_timestamp = off');
+$node_primary->stop('fast');
+
+# Start the server, which generates a XLOG_PARAMETER_CHANGE record where
+# the parameter change is registered.
+$node_primary->start;
+
+# Now restart again the server so as no XLOG_PARAMETER_CHANGE record are
+# replayed with the follow-up immediate shutdown.
+$node_primary->restart;
+
+# Move commit timestamps across page boundaries. Things should still
+# be able to work across restarts with those transactions committed while
+# track_commit_timestamp is disabled.
+$node_primary->safe_psql(
+ 'postgres',
+ qq(CREATE PROCEDURE consume_xid(cnt int)
+AS \$\$
+DECLARE
+ i int;
+ BEGIN
+ FOR i in 1..cnt LOOP
+ EXECUTE 'SELECT pg_current_xact_id()';
+ COMMIT;
+ END LOOP;
+ END;
+\$\$
+LANGUAGE plpgsql;
+));
+$node_primary->safe_psql('postgres', 'CALL consume_xid(2000)');
+
+($ret, $stdout, $stderr) = $node_primary->psql('postgres',
+ qq[SELECT pg_xact_commit_timestamp('$xid');]);
+is($ret, 3, 'no commit timestamp from enable tx when cts disabled');
+like(
+ $stderr,
+ qr/could not get commit timestamp data/,
+ 'expected error from enabled tx when committs disabled');
+
+# Do a tx while cts disabled
+my $xid_disabled = $node_primary->safe_psql(
+ 'postgres', qq[
+ BEGIN;
+ INSERT INTO committs_test(x, y) VALUES (2, current_timestamp);
+ SELECT pg_current_xact_id();
+ COMMIT;
+]);
+
+# Should be inaccessible
+($ret, $stdout, $stderr) = $node_primary->psql('postgres',
+ qq[SELECT pg_xact_commit_timestamp('$xid_disabled');]);
+is($ret, 3, 'no commit timestamp when disabled');
+like(
+ $stderr,
+ qr/could not get commit timestamp data/,
+ 'expected error from disabled tx when committs disabled');
+
+# Re-enable, restart and ensure we can still get the old timestamps
+$node_primary->append_conf('postgresql.conf', 'track_commit_timestamp = on');
+
+# An immediate shutdown is used here. At next startup recovery will
+# replay transactions which committed when track_commit_timestamp was
+# disabled, and the facility should be able to work properly.
+$node_primary->stop('immediate');
+$node_primary->start;
+
+my $after_enable_ts = $node_primary->safe_psql('postgres',
+ qq[SELECT pg_xact_commit_timestamp('$xid');]);
+is($after_enable_ts, '', 'timestamp of enabled tx null after re-enable');
+
+my $after_enable_disabled_ts = $node_primary->safe_psql('postgres',
+ qq[SELECT pg_xact_commit_timestamp('$xid_disabled');]);
+is($after_enable_disabled_ts, '',
+ 'timestamp of disabled tx null after re-enable');
+
+$node_primary->stop;
diff --git a/src/test/modules/delay_execution/.gitignore b/src/test/modules/delay_execution/.gitignore
new file mode 100644
index 0000000..ba2160b
--- /dev/null
+++ b/src/test/modules/delay_execution/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/output_iso/
+/tmp_check_iso/
diff --git a/src/test/modules/delay_execution/Makefile b/src/test/modules/delay_execution/Makefile
new file mode 100644
index 0000000..70f24e8
--- /dev/null
+++ b/src/test/modules/delay_execution/Makefile
@@ -0,0 +1,22 @@
+# src/test/modules/delay_execution/Makefile
+
+PGFILEDESC = "delay_execution - allow delay between parsing and execution"
+
+MODULE_big = delay_execution
+OBJS = \
+ $(WIN32RES) \
+ delay_execution.o
+
+ISOLATION = partition-addition \
+ partition-removal-1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/delay_execution
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/delay_execution/delay_execution.c b/src/test/modules/delay_execution/delay_execution.c
new file mode 100644
index 0000000..b3d0841
--- /dev/null
+++ b/src/test/modules/delay_execution/delay_execution.c
@@ -0,0 +1,104 @@
+/*-------------------------------------------------------------------------
+ *
+ * delay_execution.c
+ * Test module to allow delay between parsing and execution of a query.
+ *
+ * The delay is implemented by taking and immediately releasing a specified
+ * advisory lock. If another process has previously taken that lock, the
+ * current process will be blocked until the lock is released; otherwise,
+ * there's no effect. This allows an isolationtester script to reliably
+ * test behaviors where some specified action happens in another backend
+ * between parsing and execution of any desired query.
+ *
+ * Copyright (c) 2020-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/delay_execution/delay_execution.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <limits.h>
+
+#include "optimizer/planner.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/inval.h"
+
+
+PG_MODULE_MAGIC;
+
+/* GUC: advisory lock ID to use. Zero disables the feature. */
+static int post_planning_lock_id = 0;
+
+/* Save previous planner hook user to be a good citizen */
+static planner_hook_type prev_planner_hook = NULL;
+
+/* Module load/unload functions */
+void _PG_init(void);
+void _PG_fini(void);
+
+
+/* planner_hook function to provide the desired delay */
+static PlannedStmt *
+delay_execution_planner(Query *parse, const char *query_string,
+ int cursorOptions, ParamListInfo boundParams)
+{
+ PlannedStmt *result;
+
+ /* Invoke the planner, possibly via a previous hook user */
+ if (prev_planner_hook)
+ result = prev_planner_hook(parse, query_string, cursorOptions,
+ boundParams);
+ else
+ result = standard_planner(parse, query_string, cursorOptions,
+ boundParams);
+
+ /* If enabled, delay by taking and releasing the specified lock */
+ if (post_planning_lock_id != 0)
+ {
+ DirectFunctionCall1(pg_advisory_lock_int8,
+ Int64GetDatum((int64) post_planning_lock_id));
+ DirectFunctionCall1(pg_advisory_unlock_int8,
+ Int64GetDatum((int64) post_planning_lock_id));
+
+ /*
+ * Ensure that we notice any pending invalidations, since the advisory
+ * lock functions don't do this.
+ */
+ AcceptInvalidationMessages();
+ }
+
+ return result;
+}
+
+/* Module load function */
+void
+_PG_init(void)
+{
+ /* Set up the GUC to control which lock is used */
+ DefineCustomIntVariable("delay_execution.post_planning_lock_id",
+ "Sets the advisory lock ID to be locked/unlocked after planning.",
+ "Zero disables the delay.",
+ &post_planning_lock_id,
+ 0,
+ 0, INT_MAX,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+ /* Install our hook */
+ prev_planner_hook = planner_hook;
+ planner_hook = delay_execution_planner;
+}
+
+/* Module unload function (pro forma, not used currently) */
+void
+_PG_fini(void)
+{
+ planner_hook = prev_planner_hook;
+}
diff --git a/src/test/modules/delay_execution/expected/partition-addition.out b/src/test/modules/delay_execution/expected/partition-addition.out
new file mode 100644
index 0000000..7d6572b
--- /dev/null
+++ b/src/test/modules/delay_execution/expected/partition-addition.out
@@ -0,0 +1,27 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2lock s1exec s2addp s2unlock
+step s2lock: SELECT pg_advisory_lock(12345);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1exec: LOAD 'delay_execution';
+ SET delay_execution.post_planning_lock_id = 12345;
+ SELECT * FROM foo WHERE a <> 1 AND a <> (SELECT 3); <waiting ...>
+step s2addp: CREATE TABLE foo2 (LIKE foo);
+ ALTER TABLE foo ATTACH PARTITION foo2 FOR VALUES IN (2);
+ INSERT INTO foo VALUES (2, 'ADD2');
+step s2unlock: SELECT pg_advisory_unlock(12345);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1exec: <... completed>
+a|b
+-+---
+4|GHI
+(1 row)
+
diff --git a/src/test/modules/delay_execution/expected/partition-removal-1.out b/src/test/modules/delay_execution/expected/partition-removal-1.out
new file mode 100644
index 0000000..b81b999
--- /dev/null
+++ b/src/test/modules/delay_execution/expected/partition-removal-1.out
@@ -0,0 +1,233 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s3lock s1b s1exec s2remp s3check s3unlock s3check s1c
+step s3lock: SELECT pg_advisory_lock(12543);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1b: BEGIN;
+step s1exec: SELECT * FROM partrem WHERE a <> 1 AND a <> (SELECT 3); <waiting ...>
+step s2remp: ALTER TABLE partrem DETACH PARTITION partrem2 CONCURRENTLY; <waiting ...>
+step s3check: SELECT * FROM partrem;
+a|b
+-+---
+1|ABC
+3|DEF
+(2 rows)
+
+step s3unlock: SELECT pg_advisory_unlock(12543);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1exec: <... completed>
+a|b
+-+---
+2|JKL
+(1 row)
+
+step s3check: SELECT * FROM partrem;
+a|b
+-+---
+1|ABC
+3|DEF
+(2 rows)
+
+step s1c: COMMIT;
+step s2remp: <... completed>
+
+starting permutation: s3lock s1brr s1exec s2remp s3check s3unlock s3check s1c
+step s3lock: SELECT pg_advisory_lock(12543);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1exec: SELECT * FROM partrem WHERE a <> 1 AND a <> (SELECT 3); <waiting ...>
+step s2remp: ALTER TABLE partrem DETACH PARTITION partrem2 CONCURRENTLY; <waiting ...>
+step s3check: SELECT * FROM partrem;
+a|b
+-+---
+1|ABC
+3|DEF
+(2 rows)
+
+step s3unlock: SELECT pg_advisory_unlock(12543);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1exec: <... completed>
+a|b
+-+---
+2|JKL
+(1 row)
+
+step s3check: SELECT * FROM partrem;
+a|b
+-+---
+1|ABC
+3|DEF
+(2 rows)
+
+step s1c: COMMIT;
+step s2remp: <... completed>
+
+starting permutation: s3lock s1b s1exec2 s2remp s3unlock s1c
+step s3lock: SELECT pg_advisory_lock(12543);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1b: BEGIN;
+step s1exec2: SELECT * FROM partrem WHERE a <> (SELECT 2) AND a <> 1; <waiting ...>
+step s2remp: ALTER TABLE partrem DETACH PARTITION partrem2 CONCURRENTLY; <waiting ...>
+step s3unlock: SELECT pg_advisory_unlock(12543);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1exec2: <... completed>
+a|b
+-+---
+3|DEF
+(1 row)
+
+step s1c: COMMIT;
+step s2remp: <... completed>
+
+starting permutation: s3lock s1brr s1exec2 s2remp s3unlock s1c
+step s3lock: SELECT pg_advisory_lock(12543);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1exec2: SELECT * FROM partrem WHERE a <> (SELECT 2) AND a <> 1; <waiting ...>
+step s2remp: ALTER TABLE partrem DETACH PARTITION partrem2 CONCURRENTLY; <waiting ...>
+step s3unlock: SELECT pg_advisory_unlock(12543);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1exec2: <... completed>
+a|b
+-+---
+3|DEF
+(1 row)
+
+step s1c: COMMIT;
+step s2remp: <... completed>
+
+starting permutation: s3lock s1brr s1prepare s2remp s1execprep s3unlock s1check s1c s1check s1dealloc
+step s3lock: SELECT pg_advisory_lock(12543);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1prepare: PREPARE ins AS INSERT INTO partrem VALUES ($1, 'GHI');
+step s2remp: ALTER TABLE partrem DETACH PARTITION partrem2 CONCURRENTLY; <waiting ...>
+step s1execprep: EXECUTE ins(2); <waiting ...>
+step s3unlock: SELECT pg_advisory_unlock(12543);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1execprep: <... completed>
+step s1check: SELECT * FROM partrem WHERE b = 'GHI';
+a|b
+-+---
+2|GHI
+(1 row)
+
+step s1c: COMMIT;
+step s2remp: <... completed>
+step s1check: SELECT * FROM partrem WHERE b = 'GHI';
+a|b
+-+-
+(0 rows)
+
+step s1dealloc: DEALLOCATE ins;
+
+starting permutation: s1brr s1prepare s2remp s3lock s1execprep s3unlock s1check s1c s1check s1dealloc
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1prepare: PREPARE ins AS INSERT INTO partrem VALUES ($1, 'GHI');
+step s2remp: ALTER TABLE partrem DETACH PARTITION partrem2 CONCURRENTLY; <waiting ...>
+step s3lock: SELECT pg_advisory_lock(12543);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s1execprep: EXECUTE ins(2); <waiting ...>
+step s3unlock: SELECT pg_advisory_unlock(12543);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1execprep: <... completed>
+step s1check: SELECT * FROM partrem WHERE b = 'GHI';
+a|b
+-+---
+2|GHI
+(1 row)
+
+step s1c: COMMIT;
+step s2remp: <... completed>
+step s1check: SELECT * FROM partrem WHERE b = 'GHI';
+a|b
+-+-
+(0 rows)
+
+step s1dealloc: DEALLOCATE ins;
+
+starting permutation: s1brr s1check s3lock s2remp s1prepare s1execprep s3unlock s1check s1c s1check s1dealloc
+step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step s1check: SELECT * FROM partrem WHERE b = 'GHI';
+a|b
+-+-
+(0 rows)
+
+step s3lock: SELECT pg_advisory_lock(12543);
+pg_advisory_lock
+----------------
+
+(1 row)
+
+step s2remp: ALTER TABLE partrem DETACH PARTITION partrem2 CONCURRENTLY; <waiting ...>
+step s1prepare: PREPARE ins AS INSERT INTO partrem VALUES ($1, 'GHI');
+step s1execprep: EXECUTE ins(2); <waiting ...>
+step s3unlock: SELECT pg_advisory_unlock(12543);
+pg_advisory_unlock
+------------------
+t
+(1 row)
+
+step s1execprep: <... completed>
+step s1check: SELECT * FROM partrem WHERE b = 'GHI';
+a|b
+-+---
+2|GHI
+(1 row)
+
+step s1c: COMMIT;
+step s2remp: <... completed>
+step s1check: SELECT * FROM partrem WHERE b = 'GHI';
+a|b
+-+-
+(0 rows)
+
+step s1dealloc: DEALLOCATE ins;
diff --git a/src/test/modules/delay_execution/specs/partition-addition.spec b/src/test/modules/delay_execution/specs/partition-addition.spec
new file mode 100644
index 0000000..2a09482
--- /dev/null
+++ b/src/test/modules/delay_execution/specs/partition-addition.spec
@@ -0,0 +1,38 @@
+# Test addition of a partition with less-than-exclusive locking.
+
+setup
+{
+ CREATE TABLE foo (a int, b text) PARTITION BY LIST(a);
+ CREATE TABLE foo1 PARTITION OF foo FOR VALUES IN (1);
+ CREATE TABLE foo3 PARTITION OF foo FOR VALUES IN (3);
+ CREATE TABLE foo4 PARTITION OF foo FOR VALUES IN (4);
+ INSERT INTO foo VALUES (1, 'ABC');
+ INSERT INTO foo VALUES (3, 'DEF');
+ INSERT INTO foo VALUES (4, 'GHI');
+}
+
+teardown
+{
+ DROP TABLE foo;
+}
+
+# The SELECT will be planned with just the three partitions shown above,
+# of which we expect foo1 to be pruned at planning and foo3 at execution.
+# Then we'll block, and by the time the query is actually executed,
+# partition foo2 will also exist. We expect that not to be scanned.
+# This test is specifically designed to check ExecCreatePartitionPruneState's
+# code for matching up the partition lists in such cases.
+
+session "s1"
+step "s1exec" { LOAD 'delay_execution';
+ SET delay_execution.post_planning_lock_id = 12345;
+ SELECT * FROM foo WHERE a <> 1 AND a <> (SELECT 3); }
+
+session "s2"
+step "s2lock" { SELECT pg_advisory_lock(12345); }
+step "s2unlock" { SELECT pg_advisory_unlock(12345); }
+step "s2addp" { CREATE TABLE foo2 (LIKE foo);
+ ALTER TABLE foo ATTACH PARTITION foo2 FOR VALUES IN (2);
+ INSERT INTO foo VALUES (2, 'ADD2'); }
+
+permutation "s2lock" "s1exec" "s2addp" "s2unlock"
diff --git a/src/test/modules/delay_execution/specs/partition-removal-1.spec b/src/test/modules/delay_execution/specs/partition-removal-1.spec
new file mode 100644
index 0000000..5ee2750
--- /dev/null
+++ b/src/test/modules/delay_execution/specs/partition-removal-1.spec
@@ -0,0 +1,58 @@
+# Test removal of a partition with less-than-exclusive locking.
+
+setup
+{
+ CREATE TABLE partrem (a int, b text) PARTITION BY LIST(a);
+ CREATE TABLE partrem1 PARTITION OF partrem FOR VALUES IN (1);
+ CREATE TABLE partrem2 PARTITION OF partrem FOR VALUES IN (2);
+ CREATE TABLE partrem3 PARTITION OF partrem FOR VALUES IN (3);
+ INSERT INTO partrem VALUES (1, 'ABC');
+ INSERT INTO partrem VALUES (2, 'JKL');
+ INSERT INTO partrem VALUES (3, 'DEF');
+}
+
+teardown
+{
+ DROP TABLE IF EXISTS partrem, partrem2;
+}
+
+session "s1"
+setup { LOAD 'delay_execution';
+ SET delay_execution.post_planning_lock_id = 12543; }
+step "s1b" { BEGIN; }
+step "s1brr" { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step "s1exec" { SELECT * FROM partrem WHERE a <> 1 AND a <> (SELECT 3); }
+step "s1exec2" { SELECT * FROM partrem WHERE a <> (SELECT 2) AND a <> 1; }
+step "s1prepare" { PREPARE ins AS INSERT INTO partrem VALUES ($1, 'GHI'); }
+step "s1execprep" { EXECUTE ins(2); }
+step "s1check" { SELECT * FROM partrem WHERE b = 'GHI'; }
+step "s1c" { COMMIT; }
+step "s1dealloc" { DEALLOCATE ins; }
+
+session "s2"
+step "s2remp" { ALTER TABLE partrem DETACH PARTITION partrem2 CONCURRENTLY; }
+
+session "s3"
+step "s3lock" { SELECT pg_advisory_lock(12543); }
+step "s3unlock" { SELECT pg_advisory_unlock(12543); }
+step "s3check" { SELECT * FROM partrem; }
+
+# The SELECT will be planned with all three partitions shown above,
+# of which we expect partrem1 to be pruned at planning and partrem3 at
+# execution. Then we'll block, and by the time the query is actually
+# executed, detach of partrem2 is already underway (so its row doesn't
+# show up in s3's result), but we expect its row to still appear in the
+# result for s1.
+permutation "s3lock" "s1b" "s1exec" "s2remp" "s3check" "s3unlock" "s3check" "s1c"
+permutation "s3lock" "s1brr" "s1exec" "s2remp" "s3check" "s3unlock" "s3check" "s1c"
+
+# In this case we're testing that after pruning partrem2 at runtime, the
+# query still works correctly.
+permutation "s3lock" "s1b" "s1exec2" "s2remp" "s3unlock" "s1c"
+permutation "s3lock" "s1brr" "s1exec2" "s2remp" "s3unlock" "s1c"
+
+# In this case we test that an insert that's prepared in repeatable read
+# mode still works after detaching.
+permutation "s3lock" "s1brr" "s1prepare" "s2remp" "s1execprep" "s3unlock" "s1check" "s1c" "s1check" "s1dealloc"
+permutation "s1brr" "s1prepare" "s2remp" "s3lock" "s1execprep" "s3unlock" "s1check" "s1c" "s1check" "s1dealloc"
+permutation "s1brr" "s1check" "s3lock" "s2remp" "s1prepare" "s1execprep" "s3unlock" "s1check" "s1c" "s1check" "s1dealloc"
diff --git a/src/test/modules/dummy_index_am/.gitignore b/src/test/modules/dummy_index_am/.gitignore
new file mode 100644
index 0000000..44d119c
--- /dev/null
+++ b/src/test/modules/dummy_index_am/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/results/
diff --git a/src/test/modules/dummy_index_am/Makefile b/src/test/modules/dummy_index_am/Makefile
new file mode 100644
index 0000000..aaf544a
--- /dev/null
+++ b/src/test/modules/dummy_index_am/Makefile
@@ -0,0 +1,20 @@
+# src/test/modules/dummy_index_am/Makefile
+
+MODULES = dummy_index_am
+
+EXTENSION = dummy_index_am
+DATA = dummy_index_am--1.0.sql
+PGFILEDESC = "dummy_index_am - index access method template"
+
+REGRESS = reloptions
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/dummy_index_am
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/dummy_index_am/README b/src/test/modules/dummy_index_am/README
new file mode 100644
index 0000000..61510f0
--- /dev/null
+++ b/src/test/modules/dummy_index_am/README
@@ -0,0 +1,12 @@
+Dummy Index AM
+==============
+
+Dummy index AM is a module for testing any facility usable by an index
+access method, whose code is kept a maximum simple.
+
+This includes tests for all relation option types:
+- boolean
+- enum
+- integer
+- real
+- strings (with and without NULL as default)
diff --git a/src/test/modules/dummy_index_am/dummy_index_am--1.0.sql b/src/test/modules/dummy_index_am/dummy_index_am--1.0.sql
new file mode 100644
index 0000000..005863d
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index_am--1.0.sql
@@ -0,0 +1,19 @@
+/* src/test/modules/dummy_index_am/dummy_index_am--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION dummy_index_am" to load this file. \quit
+
+CREATE FUNCTION dihandler(internal)
+RETURNS index_am_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
+
+-- Access method
+CREATE ACCESS METHOD dummy_index_am TYPE INDEX HANDLER dihandler;
+COMMENT ON ACCESS METHOD dummy_index_am IS 'dummy index access method';
+
+-- Operator classes
+CREATE OPERATOR CLASS int4_ops
+DEFAULT FOR TYPE int4 USING dummy_index_am AS
+ OPERATOR 1 = (int4, int4),
+ FUNCTION 1 hashint4(int4);
diff --git a/src/test/modules/dummy_index_am/dummy_index_am.c b/src/test/modules/dummy_index_am/dummy_index_am.c
new file mode 100644
index 0000000..5365b06
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index_am.c
@@ -0,0 +1,333 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_index_am.c
+ * Index AM template main file.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/test/modules/dummy_index_am/dummy_index_am.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/reloptions.h"
+#include "catalog/index.h"
+#include "commands/vacuum.h"
+#include "nodes/pathnodes.h"
+#include "utils/guc.h"
+#include "utils/rel.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/* parse table for fillRelOptions */
+relopt_parse_elt di_relopt_tab[6];
+
+/* Kind of relation options for dummy index */
+relopt_kind di_relopt_kind;
+
+typedef enum DummyAmEnum
+{
+ DUMMY_AM_ENUM_ONE,
+ DUMMY_AM_ENUM_TWO
+} DummyAmEnum;
+
+/* Dummy index options */
+typedef struct DummyIndexOptions
+{
+ int32 vl_len_; /* varlena header (do not touch directly!) */
+ int option_int;
+ double option_real;
+ bool option_bool;
+ DummyAmEnum option_enum;
+ int option_string_val_offset;
+ int option_string_null_offset;
+} DummyIndexOptions;
+
+relopt_enum_elt_def dummyAmEnumValues[] =
+{
+ {"one", DUMMY_AM_ENUM_ONE},
+ {"two", DUMMY_AM_ENUM_TWO},
+ {(const char *) NULL} /* list terminator */
+};
+
+/* Handler for index AM */
+PG_FUNCTION_INFO_V1(dihandler);
+
+/*
+ * Validation function for string relation options.
+ */
+static void
+validate_string_option(const char *value)
+{
+ ereport(NOTICE,
+ (errmsg("new option value for string parameter %s",
+ value ? value : "NULL")));
+}
+
+/*
+ * This function creates a full set of relation option types,
+ * with various patterns.
+ */
+static void
+create_reloptions_table(void)
+{
+ di_relopt_kind = add_reloption_kind();
+
+ add_int_reloption(di_relopt_kind, "option_int",
+ "Integer option for dummy_index_am",
+ 10, -10, 100, AccessExclusiveLock);
+ di_relopt_tab[0].optname = "option_int";
+ di_relopt_tab[0].opttype = RELOPT_TYPE_INT;
+ di_relopt_tab[0].offset = offsetof(DummyIndexOptions, option_int);
+
+ add_real_reloption(di_relopt_kind, "option_real",
+ "Real option for dummy_index_am",
+ 3.1415, -10, 100, AccessExclusiveLock);
+ di_relopt_tab[1].optname = "option_real";
+ di_relopt_tab[1].opttype = RELOPT_TYPE_REAL;
+ di_relopt_tab[1].offset = offsetof(DummyIndexOptions, option_real);
+
+ add_bool_reloption(di_relopt_kind, "option_bool",
+ "Boolean option for dummy_index_am",
+ true, AccessExclusiveLock);
+ di_relopt_tab[2].optname = "option_bool";
+ di_relopt_tab[2].opttype = RELOPT_TYPE_BOOL;
+ di_relopt_tab[2].offset = offsetof(DummyIndexOptions, option_bool);
+
+ add_enum_reloption(di_relopt_kind, "option_enum",
+ "Enum option for dummy_index_am",
+ dummyAmEnumValues,
+ DUMMY_AM_ENUM_ONE,
+ "Valid values are \"one\" and \"two\".",
+ AccessExclusiveLock);
+ di_relopt_tab[3].optname = "option_enum";
+ di_relopt_tab[3].opttype = RELOPT_TYPE_ENUM;
+ di_relopt_tab[3].offset = offsetof(DummyIndexOptions, option_enum);
+
+ add_string_reloption(di_relopt_kind, "option_string_val",
+ "String option for dummy_index_am with non-NULL default",
+ "DefaultValue", &validate_string_option,
+ AccessExclusiveLock);
+ di_relopt_tab[4].optname = "option_string_val";
+ di_relopt_tab[4].opttype = RELOPT_TYPE_STRING;
+ di_relopt_tab[4].offset = offsetof(DummyIndexOptions,
+ option_string_val_offset);
+
+ /*
+ * String option for dummy_index_am with NULL default, and without
+ * description.
+ */
+ add_string_reloption(di_relopt_kind, "option_string_null",
+ NULL, /* description */
+ NULL, &validate_string_option,
+ AccessExclusiveLock);
+ di_relopt_tab[5].optname = "option_string_null";
+ di_relopt_tab[5].opttype = RELOPT_TYPE_STRING;
+ di_relopt_tab[5].offset = offsetof(DummyIndexOptions,
+ option_string_null_offset);
+}
+
+
+/*
+ * Build a new index.
+ */
+static IndexBuildResult *
+dibuild(Relation heap, Relation index, IndexInfo *indexInfo)
+{
+ IndexBuildResult *result;
+
+ result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
+
+ /* let's pretend that no tuples were scanned */
+ result->heap_tuples = 0;
+ /* and no index tuples were created (that is true) */
+ result->index_tuples = 0;
+
+ return result;
+}
+
+/*
+ * Build an empty index for the initialization fork.
+ */
+static void
+dibuildempty(Relation index)
+{
+ /* No need to build an init fork for a dummy index */
+}
+
+/*
+ * Insert new tuple to index AM.
+ */
+static bool
+diinsert(Relation index, Datum *values, bool *isnull,
+ ItemPointer ht_ctid, Relation heapRel,
+ IndexUniqueCheck checkUnique,
+ bool indexUnchanged,
+ IndexInfo *indexInfo)
+{
+ /* nothing to do */
+ return false;
+}
+
+/*
+ * Bulk deletion of all index entries pointing to a set of table tuples.
+ */
+static IndexBulkDeleteResult *
+dibulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
+ IndexBulkDeleteCallback callback, void *callback_state)
+{
+ /*
+ * There is nothing to delete. Return NULL as there is nothing to pass to
+ * amvacuumcleanup.
+ */
+ return NULL;
+}
+
+/*
+ * Post-VACUUM cleanup for index AM.
+ */
+static IndexBulkDeleteResult *
+divacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
+{
+ /* Index has not been modified, so returning NULL is fine */
+ return NULL;
+}
+
+/*
+ * Estimate cost of index AM.
+ */
+static void
+dicostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
+ Cost *indexStartupCost, Cost *indexTotalCost,
+ Selectivity *indexSelectivity, double *indexCorrelation,
+ double *indexPages)
+{
+ /* Tell planner to never use this index! */
+ *indexStartupCost = 1.0e10;
+ *indexTotalCost = 1.0e10;
+
+ /* Do not care about the rest */
+ *indexSelectivity = 1;
+ *indexCorrelation = 0;
+ *indexPages = 1;
+}
+
+/*
+ * Parse relation options for index AM, returning a DummyIndexOptions
+ * structure filled with option values.
+ */
+static bytea *
+dioptions(Datum reloptions, bool validate)
+{
+ return (bytea *) build_reloptions(reloptions, validate,
+ di_relopt_kind,
+ sizeof(DummyIndexOptions),
+ di_relopt_tab, lengthof(di_relopt_tab));
+}
+
+/*
+ * Validator for index AM.
+ */
+static bool
+divalidate(Oid opclassoid)
+{
+ /* Index is dummy so we are happy with any opclass */
+ return true;
+}
+
+/*
+ * Begin scan of index AM.
+ */
+static IndexScanDesc
+dibeginscan(Relation r, int nkeys, int norderbys)
+{
+ IndexScanDesc scan;
+
+ /* Let's pretend we are doing something */
+ scan = RelationGetIndexScan(r, nkeys, norderbys);
+ return scan;
+}
+
+/*
+ * Rescan of index AM.
+ */
+static void
+direscan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
+ ScanKey orderbys, int norderbys)
+{
+ /* nothing to do */
+}
+
+/*
+ * End scan of index AM.
+ */
+static void
+diendscan(IndexScanDesc scan)
+{
+ /* nothing to do */
+}
+
+/*
+ * Index AM handler function: returns IndexAmRoutine with access method
+ * parameters and callbacks.
+ */
+Datum
+dihandler(PG_FUNCTION_ARGS)
+{
+ IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
+
+ amroutine->amstrategies = 0;
+ amroutine->amsupport = 1;
+ amroutine->amcanorder = false;
+ amroutine->amcanorderbyop = false;
+ amroutine->amcanbackward = false;
+ amroutine->amcanunique = false;
+ amroutine->amcanmulticol = false;
+ amroutine->amoptionalkey = false;
+ amroutine->amsearcharray = false;
+ amroutine->amsearchnulls = false;
+ amroutine->amstorage = false;
+ amroutine->amclusterable = false;
+ amroutine->ampredlocks = false;
+ amroutine->amcanparallel = false;
+ amroutine->amcaninclude = false;
+ amroutine->amusemaintenanceworkmem = false;
+ amroutine->amparallelvacuumoptions = VACUUM_OPTION_NO_PARALLEL;
+ amroutine->amkeytype = InvalidOid;
+
+ amroutine->ambuild = dibuild;
+ amroutine->ambuildempty = dibuildempty;
+ amroutine->aminsert = diinsert;
+ amroutine->ambulkdelete = dibulkdelete;
+ amroutine->amvacuumcleanup = divacuumcleanup;
+ amroutine->amcanreturn = NULL;
+ amroutine->amcostestimate = dicostestimate;
+ amroutine->amoptions = dioptions;
+ amroutine->amproperty = NULL;
+ amroutine->ambuildphasename = NULL;
+ amroutine->amvalidate = divalidate;
+ amroutine->ambeginscan = dibeginscan;
+ amroutine->amrescan = direscan;
+ amroutine->amgettuple = NULL;
+ amroutine->amgetbitmap = NULL;
+ amroutine->amendscan = diendscan;
+ amroutine->ammarkpos = NULL;
+ amroutine->amrestrpos = NULL;
+ amroutine->amestimateparallelscan = NULL;
+ amroutine->aminitparallelscan = NULL;
+ amroutine->amparallelrescan = NULL;
+
+ PG_RETURN_POINTER(amroutine);
+}
+
+void
+_PG_init(void)
+{
+ create_reloptions_table();
+}
diff --git a/src/test/modules/dummy_index_am/dummy_index_am.control b/src/test/modules/dummy_index_am/dummy_index_am.control
new file mode 100644
index 0000000..77bdea0
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index_am.control
@@ -0,0 +1,5 @@
+# dummy_index_am extension
+comment = 'dummy_index_am - index access method template'
+default_version = '1.0'
+module_pathname = '$libdir/dummy_index_am'
+relocatable = true
diff --git a/src/test/modules/dummy_index_am/expected/reloptions.out b/src/test/modules/dummy_index_am/expected/reloptions.out
new file mode 100644
index 0000000..c873a80
--- /dev/null
+++ b/src/test/modules/dummy_index_am/expected/reloptions.out
@@ -0,0 +1,145 @@
+-- Tests for relation options
+CREATE EXTENSION dummy_index_am;
+CREATE TABLE dummy_test_tab (i int4);
+-- Silence validation checks for strings
+SET client_min_messages TO 'warning';
+-- Test with default values.
+CREATE INDEX dummy_test_idx ON dummy_test_tab
+ USING dummy_index_am (i);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+--------
+(0 rows)
+
+DROP INDEX dummy_test_idx;
+-- Test with full set of options.
+-- Allow validation checks for strings, just for the index creation
+SET client_min_messages TO 'notice';
+CREATE INDEX dummy_test_idx ON dummy_test_tab
+ USING dummy_index_am (i) WITH (
+ option_bool = false,
+ option_int = 5,
+ option_real = 3.1,
+ option_enum = 'two',
+ option_string_val = NULL,
+ option_string_null = 'val');
+NOTICE: new option value for string parameter null
+NOTICE: new option value for string parameter val
+-- Silence again validation checks for strings until the end of the test.
+SET client_min_messages TO 'warning';
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+------------------------
+ option_bool=false
+ option_int=5
+ option_real=3.1
+ option_enum=two
+ option_string_val=null
+ option_string_null=val
+(6 rows)
+
+-- ALTER INDEX .. SET
+ALTER INDEX dummy_test_idx SET (option_int = 10);
+ALTER INDEX dummy_test_idx SET (option_bool = true);
+ALTER INDEX dummy_test_idx SET (option_real = 3.2);
+ALTER INDEX dummy_test_idx SET (option_string_val = 'val2');
+ALTER INDEX dummy_test_idx SET (option_string_null = NULL);
+ALTER INDEX dummy_test_idx SET (option_enum = 'one');
+ALTER INDEX dummy_test_idx SET (option_enum = 'three');
+ERROR: invalid value for enum option "option_enum": three
+DETAIL: Valid values are "one" and "two".
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+-------------------------
+ option_int=10
+ option_bool=true
+ option_real=3.2
+ option_string_val=val2
+ option_string_null=null
+ option_enum=one
+(6 rows)
+
+-- ALTER INDEX .. RESET
+ALTER INDEX dummy_test_idx RESET (option_int);
+ALTER INDEX dummy_test_idx RESET (option_bool);
+ALTER INDEX dummy_test_idx RESET (option_real);
+ALTER INDEX dummy_test_idx RESET (option_enum);
+ALTER INDEX dummy_test_idx RESET (option_string_val);
+ALTER INDEX dummy_test_idx RESET (option_string_null);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+--------
+(0 rows)
+
+-- Cross-type checks for reloption values
+-- Integer
+ALTER INDEX dummy_test_idx SET (option_int = 3.3); -- ok
+ALTER INDEX dummy_test_idx SET (option_int = true); -- error
+ERROR: invalid value for integer option "option_int": true
+ALTER INDEX dummy_test_idx SET (option_int = 'val3'); -- error
+ERROR: invalid value for integer option "option_int": val3
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+----------------
+ option_int=3.3
+(1 row)
+
+ALTER INDEX dummy_test_idx RESET (option_int);
+-- Boolean
+ALTER INDEX dummy_test_idx SET (option_bool = 4); -- error
+ERROR: invalid value for boolean option "option_bool": 4
+ALTER INDEX dummy_test_idx SET (option_bool = 1); -- ok, as true
+ALTER INDEX dummy_test_idx SET (option_bool = 3.4); -- error
+ERROR: invalid value for boolean option "option_bool": 3.4
+ALTER INDEX dummy_test_idx SET (option_bool = 'val4'); -- error
+ERROR: invalid value for boolean option "option_bool": val4
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+---------------
+ option_bool=1
+(1 row)
+
+ALTER INDEX dummy_test_idx RESET (option_bool);
+-- Float
+ALTER INDEX dummy_test_idx SET (option_real = 4); -- ok
+ALTER INDEX dummy_test_idx SET (option_real = true); -- error
+ERROR: invalid value for floating point option "option_real": true
+ALTER INDEX dummy_test_idx SET (option_real = 'val5'); -- error
+ERROR: invalid value for floating point option "option_real": val5
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+---------------
+ option_real=4
+(1 row)
+
+ALTER INDEX dummy_test_idx RESET (option_real);
+-- Enum
+ALTER INDEX dummy_test_idx SET (option_enum = 'one'); -- ok
+ALTER INDEX dummy_test_idx SET (option_enum = 0); -- error
+ERROR: invalid value for enum option "option_enum": 0
+DETAIL: Valid values are "one" and "two".
+ALTER INDEX dummy_test_idx SET (option_enum = true); -- error
+ERROR: invalid value for enum option "option_enum": true
+DETAIL: Valid values are "one" and "two".
+ALTER INDEX dummy_test_idx SET (option_enum = 'three'); -- error
+ERROR: invalid value for enum option "option_enum": three
+DETAIL: Valid values are "one" and "two".
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+-----------------
+ option_enum=one
+(1 row)
+
+ALTER INDEX dummy_test_idx RESET (option_enum);
+-- String
+ALTER INDEX dummy_test_idx SET (option_string_val = 4); -- ok
+ALTER INDEX dummy_test_idx SET (option_string_val = 3.5); -- ok
+ALTER INDEX dummy_test_idx SET (option_string_val = true); -- ok, as "true"
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+------------------------
+ option_string_val=true
+(1 row)
+
+ALTER INDEX dummy_test_idx RESET (option_string_val);
+DROP INDEX dummy_test_idx;
diff --git a/src/test/modules/dummy_index_am/sql/reloptions.sql b/src/test/modules/dummy_index_am/sql/reloptions.sql
new file mode 100644
index 0000000..6749d76
--- /dev/null
+++ b/src/test/modules/dummy_index_am/sql/reloptions.sql
@@ -0,0 +1,83 @@
+-- Tests for relation options
+CREATE EXTENSION dummy_index_am;
+
+CREATE TABLE dummy_test_tab (i int4);
+
+-- Silence validation checks for strings
+SET client_min_messages TO 'warning';
+
+-- Test with default values.
+CREATE INDEX dummy_test_idx ON dummy_test_tab
+ USING dummy_index_am (i);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+DROP INDEX dummy_test_idx;
+
+-- Test with full set of options.
+-- Allow validation checks for strings, just for the index creation
+SET client_min_messages TO 'notice';
+CREATE INDEX dummy_test_idx ON dummy_test_tab
+ USING dummy_index_am (i) WITH (
+ option_bool = false,
+ option_int = 5,
+ option_real = 3.1,
+ option_enum = 'two',
+ option_string_val = NULL,
+ option_string_null = 'val');
+-- Silence again validation checks for strings until the end of the test.
+SET client_min_messages TO 'warning';
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+
+-- ALTER INDEX .. SET
+ALTER INDEX dummy_test_idx SET (option_int = 10);
+ALTER INDEX dummy_test_idx SET (option_bool = true);
+ALTER INDEX dummy_test_idx SET (option_real = 3.2);
+ALTER INDEX dummy_test_idx SET (option_string_val = 'val2');
+ALTER INDEX dummy_test_idx SET (option_string_null = NULL);
+ALTER INDEX dummy_test_idx SET (option_enum = 'one');
+ALTER INDEX dummy_test_idx SET (option_enum = 'three');
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+
+-- ALTER INDEX .. RESET
+ALTER INDEX dummy_test_idx RESET (option_int);
+ALTER INDEX dummy_test_idx RESET (option_bool);
+ALTER INDEX dummy_test_idx RESET (option_real);
+ALTER INDEX dummy_test_idx RESET (option_enum);
+ALTER INDEX dummy_test_idx RESET (option_string_val);
+ALTER INDEX dummy_test_idx RESET (option_string_null);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+
+-- Cross-type checks for reloption values
+-- Integer
+ALTER INDEX dummy_test_idx SET (option_int = 3.3); -- ok
+ALTER INDEX dummy_test_idx SET (option_int = true); -- error
+ALTER INDEX dummy_test_idx SET (option_int = 'val3'); -- error
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ALTER INDEX dummy_test_idx RESET (option_int);
+-- Boolean
+ALTER INDEX dummy_test_idx SET (option_bool = 4); -- error
+ALTER INDEX dummy_test_idx SET (option_bool = 1); -- ok, as true
+ALTER INDEX dummy_test_idx SET (option_bool = 3.4); -- error
+ALTER INDEX dummy_test_idx SET (option_bool = 'val4'); -- error
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ALTER INDEX dummy_test_idx RESET (option_bool);
+-- Float
+ALTER INDEX dummy_test_idx SET (option_real = 4); -- ok
+ALTER INDEX dummy_test_idx SET (option_real = true); -- error
+ALTER INDEX dummy_test_idx SET (option_real = 'val5'); -- error
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ALTER INDEX dummy_test_idx RESET (option_real);
+-- Enum
+ALTER INDEX dummy_test_idx SET (option_enum = 'one'); -- ok
+ALTER INDEX dummy_test_idx SET (option_enum = 0); -- error
+ALTER INDEX dummy_test_idx SET (option_enum = true); -- error
+ALTER INDEX dummy_test_idx SET (option_enum = 'three'); -- error
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ALTER INDEX dummy_test_idx RESET (option_enum);
+-- String
+ALTER INDEX dummy_test_idx SET (option_string_val = 4); -- ok
+ALTER INDEX dummy_test_idx SET (option_string_val = 3.5); -- ok
+ALTER INDEX dummy_test_idx SET (option_string_val = true); -- ok, as "true"
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ALTER INDEX dummy_test_idx RESET (option_string_val);
+
+DROP INDEX dummy_test_idx;
diff --git a/src/test/modules/dummy_seclabel/.gitignore b/src/test/modules/dummy_seclabel/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/dummy_seclabel/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/dummy_seclabel/Makefile b/src/test/modules/dummy_seclabel/Makefile
new file mode 100644
index 0000000..d93c964
--- /dev/null
+++ b/src/test/modules/dummy_seclabel/Makefile
@@ -0,0 +1,20 @@
+# src/test/modules/dummy_seclabel/Makefile
+
+MODULES = dummy_seclabel
+PGFILEDESC = "dummy_seclabel - regression testing of the SECURITY LABEL statement"
+
+EXTENSION = dummy_seclabel
+DATA = dummy_seclabel--1.0.sql
+
+REGRESS = dummy_seclabel
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/dummy_seclabel
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/dummy_seclabel/README b/src/test/modules/dummy_seclabel/README
new file mode 100644
index 0000000..a3fcbd7
--- /dev/null
+++ b/src/test/modules/dummy_seclabel/README
@@ -0,0 +1,41 @@
+The dummy_seclabel module exists only to support regression testing of
+the SECURITY LABEL statement. It is not intended to be used in production.
+
+Rationale
+=========
+
+The SECURITY LABEL statement allows the user to assign security labels to
+database objects; however, security labels can only be assigned when
+specifically allowed by a loadable module, so this module is provided to
+allow proper regression testing.
+
+Security label providers intended to be used in production will typically be
+dependent on a platform-specific feature such as SELinux. This module is
+platform-independent, and therefore better-suited to regression testing.
+
+Usage
+=====
+
+Here's a simple example of usage:
+
+# postgresql.conf
+shared_preload_libraries = 'dummy_seclabel'
+
+postgres=# CREATE TABLE t (a int, b text);
+CREATE TABLE
+postgres=# SECURITY LABEL ON TABLE t IS 'classified';
+SECURITY LABEL
+
+The dummy_seclabel module provides only four hardcoded
+labels: unclassified, classified,
+secret, and top secret.
+It does not allow any other strings as security labels.
+
+These labels are not used to enforce access controls. They are only used
+to check whether the SECURITY LABEL statement works as expected,
+or not.
+
+Author
+======
+
+KaiGai Kohei <kaigai@ak.jp.nec.com>
diff --git a/src/test/modules/dummy_seclabel/dummy_seclabel--1.0.sql b/src/test/modules/dummy_seclabel/dummy_seclabel--1.0.sql
new file mode 100644
index 0000000..5f3cb5b
--- /dev/null
+++ b/src/test/modules/dummy_seclabel/dummy_seclabel--1.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/dummy_seclabel/dummy_seclabel--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION dummy_seclabel" to load this file. \quit
+
+CREATE FUNCTION dummy_seclabel_dummy()
+ RETURNS pg_catalog.void
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/dummy_seclabel/dummy_seclabel.c b/src/test/modules/dummy_seclabel/dummy_seclabel.c
new file mode 100644
index 0000000..9da1144
--- /dev/null
+++ b/src/test/modules/dummy_seclabel/dummy_seclabel.c
@@ -0,0 +1,63 @@
+/*
+ * dummy_seclabel.c
+ *
+ * Dummy security label provider.
+ *
+ * This module does not provide anything worthwhile from a security
+ * perspective, but allows regression testing independent of platform-specific
+ * features like SELinux.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ */
+#include "postgres.h"
+
+#include "commands/seclabel.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "utils/rel.h"
+
+PG_MODULE_MAGIC;
+
+/* Entrypoint of the module */
+void _PG_init(void);
+
+PG_FUNCTION_INFO_V1(dummy_seclabel_dummy);
+
+static void
+dummy_object_relabel(const ObjectAddress *object, const char *seclabel)
+{
+ if (seclabel == NULL ||
+ strcmp(seclabel, "unclassified") == 0 ||
+ strcmp(seclabel, "classified") == 0)
+ return;
+
+ if (strcmp(seclabel, "secret") == 0 ||
+ strcmp(seclabel, "top secret") == 0)
+ {
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("only superuser can set '%s' label", seclabel)));
+ return;
+ }
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_NAME),
+ errmsg("'%s' is not a valid security label", seclabel)));
+}
+
+void
+_PG_init(void)
+{
+ register_label_provider("dummy", dummy_object_relabel);
+}
+
+/*
+ * This function is here just so that the extension is not completely empty
+ * and the dynamic library is loaded when CREATE EXTENSION runs.
+ */
+Datum
+dummy_seclabel_dummy(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_VOID();
+}
diff --git a/src/test/modules/dummy_seclabel/dummy_seclabel.control b/src/test/modules/dummy_seclabel/dummy_seclabel.control
new file mode 100644
index 0000000..8c37272
--- /dev/null
+++ b/src/test/modules/dummy_seclabel/dummy_seclabel.control
@@ -0,0 +1,4 @@
+comment = 'Test code for SECURITY LABEL feature'
+default_version = '1.0'
+module_pathname = '$libdir/dummy_seclabel'
+relocatable = true
diff --git a/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out b/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
new file mode 100644
index 0000000..b2d898a
--- /dev/null
+++ b/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
@@ -0,0 +1,117 @@
+--
+-- Test for facilities of security label
+--
+CREATE EXTENSION dummy_seclabel;
+-- initial setups
+SET client_min_messages TO 'warning';
+DROP ROLE IF EXISTS regress_dummy_seclabel_user1;
+DROP ROLE IF EXISTS regress_dummy_seclabel_user2;
+RESET client_min_messages;
+CREATE USER regress_dummy_seclabel_user1 WITH CREATEROLE;
+CREATE USER regress_dummy_seclabel_user2;
+CREATE TABLE dummy_seclabel_tbl1 (a int, b text);
+CREATE TABLE dummy_seclabel_tbl2 (x int, y text);
+CREATE VIEW dummy_seclabel_view1 AS SELECT * FROM dummy_seclabel_tbl2;
+CREATE FUNCTION dummy_seclabel_four() RETURNS integer AS $$SELECT 4$$ language sql;
+CREATE DOMAIN dummy_seclabel_domain AS text;
+ALTER TABLE dummy_seclabel_tbl1 OWNER TO regress_dummy_seclabel_user1;
+ALTER TABLE dummy_seclabel_tbl2 OWNER TO regress_dummy_seclabel_user2;
+--
+-- Test of SECURITY LABEL statement with a plugin
+--
+SET SESSION AUTHORIZATION regress_dummy_seclabel_user1;
+SECURITY LABEL ON TABLE dummy_seclabel_tbl1 IS 'classified'; -- OK
+SECURITY LABEL ON COLUMN dummy_seclabel_tbl1.a IS 'unclassified'; -- OK
+SECURITY LABEL ON COLUMN dummy_seclabel_tbl1 IS 'unclassified'; -- fail
+ERROR: column name must be qualified
+SECURITY LABEL ON TABLE dummy_seclabel_tbl1 IS '...invalid label...'; -- fail
+ERROR: '...invalid label...' is not a valid security label
+SECURITY LABEL FOR 'dummy' ON TABLE dummy_seclabel_tbl1 IS 'unclassified'; -- OK
+SECURITY LABEL FOR 'unknown_seclabel' ON TABLE dummy_seclabel_tbl1 IS 'classified'; -- fail
+ERROR: security label provider "unknown_seclabel" is not loaded
+SECURITY LABEL ON TABLE dummy_seclabel_tbl2 IS 'unclassified'; -- fail (not owner)
+ERROR: must be owner of table dummy_seclabel_tbl2
+SECURITY LABEL ON TABLE dummy_seclabel_tbl1 IS 'secret'; -- fail (not superuser)
+ERROR: only superuser can set 'secret' label
+SECURITY LABEL ON TABLE dummy_seclabel_tbl3 IS 'unclassified'; -- fail (not found)
+ERROR: relation "dummy_seclabel_tbl3" does not exist
+SET SESSION AUTHORIZATION regress_dummy_seclabel_user2;
+SECURITY LABEL ON TABLE dummy_seclabel_tbl1 IS 'unclassified'; -- fail
+ERROR: must be owner of table dummy_seclabel_tbl1
+SECURITY LABEL ON TABLE dummy_seclabel_tbl2 IS 'classified'; -- OK
+--
+-- Test for shared database object
+--
+SET SESSION AUTHORIZATION regress_dummy_seclabel_user1;
+SECURITY LABEL ON ROLE regress_dummy_seclabel_user1 IS 'classified'; -- OK
+SECURITY LABEL ON ROLE regress_dummy_seclabel_user1 IS '...invalid label...'; -- fail
+ERROR: '...invalid label...' is not a valid security label
+SECURITY LABEL FOR 'dummy' ON ROLE regress_dummy_seclabel_user2 IS 'unclassified'; -- OK
+SECURITY LABEL FOR 'unknown_seclabel' ON ROLE regress_dummy_seclabel_user1 IS 'unclassified'; -- fail
+ERROR: security label provider "unknown_seclabel" is not loaded
+SECURITY LABEL ON ROLE regress_dummy_seclabel_user1 IS 'secret'; -- fail (not superuser)
+ERROR: only superuser can set 'secret' label
+SECURITY LABEL ON ROLE regress_dummy_seclabel_user3 IS 'unclassified'; -- fail (not found)
+ERROR: role "regress_dummy_seclabel_user3" does not exist
+SET SESSION AUTHORIZATION regress_dummy_seclabel_user2;
+SECURITY LABEL ON ROLE regress_dummy_seclabel_user2 IS 'unclassified'; -- fail (not privileged)
+ERROR: must have CREATEROLE privilege
+RESET SESSION AUTHORIZATION;
+--
+-- Test for various types of object
+--
+RESET SESSION AUTHORIZATION;
+SECURITY LABEL ON TABLE dummy_seclabel_tbl1 IS 'top secret'; -- OK
+SECURITY LABEL ON VIEW dummy_seclabel_view1 IS 'classified'; -- OK
+SECURITY LABEL ON FUNCTION dummy_seclabel_four() IS 'classified'; -- OK
+SECURITY LABEL ON DOMAIN dummy_seclabel_domain IS 'classified'; -- OK
+CREATE SCHEMA dummy_seclabel_test;
+SECURITY LABEL ON SCHEMA dummy_seclabel_test IS 'unclassified'; -- OK
+SET client_min_messages = error;
+CREATE PUBLICATION dummy_pub;
+CREATE SUBSCRIPTION dummy_sub CONNECTION '' PUBLICATION foo WITH (connect = false, slot_name = NONE);
+RESET client_min_messages;
+SECURITY LABEL ON PUBLICATION dummy_pub IS 'classified';
+SECURITY LABEL ON SUBSCRIPTION dummy_sub IS 'classified';
+SELECT objtype, objname, provider, label FROM pg_seclabels
+ ORDER BY objtype, objname;
+ objtype | objname | provider | label
+--------------+------------------------------+----------+--------------
+ column | dummy_seclabel_tbl1.a | dummy | unclassified
+ domain | dummy_seclabel_domain | dummy | classified
+ function | dummy_seclabel_four() | dummy | classified
+ publication | dummy_pub | dummy | classified
+ role | regress_dummy_seclabel_user1 | dummy | classified
+ role | regress_dummy_seclabel_user2 | dummy | unclassified
+ schema | dummy_seclabel_test | dummy | unclassified
+ subscription | dummy_sub | dummy | classified
+ table | dummy_seclabel_tbl1 | dummy | top secret
+ table | dummy_seclabel_tbl2 | dummy | classified
+ view | dummy_seclabel_view1 | dummy | classified
+(11 rows)
+
+-- check for event trigger
+CREATE FUNCTION event_trigger_test()
+RETURNS event_trigger AS $$
+ BEGIN RAISE NOTICE 'event %: %', TG_EVENT, TG_TAG; END;
+$$ LANGUAGE plpgsql;
+CREATE EVENT TRIGGER always_start ON ddl_command_start
+EXECUTE PROCEDURE event_trigger_test();
+CREATE EVENT TRIGGER always_end ON ddl_command_end
+EXECUTE PROCEDURE event_trigger_test();
+CREATE EVENT TRIGGER always_drop ON sql_drop
+EXECUTE PROCEDURE event_trigger_test();
+CREATE EVENT TRIGGER always_rewrite ON table_rewrite
+EXECUTE PROCEDURE event_trigger_test();
+-- should trigger ddl_command_{start,end}
+SECURITY LABEL ON TABLE dummy_seclabel_tbl1 IS 'classified';
+NOTICE: event ddl_command_start: SECURITY LABEL
+NOTICE: event ddl_command_end: SECURITY LABEL
+-- clean up
+DROP EVENT TRIGGER always_start, always_end, always_drop, always_rewrite;
+DROP VIEW dummy_seclabel_view1;
+DROP TABLE dummy_seclabel_tbl1, dummy_seclabel_tbl2;
+DROP SUBSCRIPTION dummy_sub;
+DROP PUBLICATION dummy_pub;
+DROP ROLE regress_dummy_seclabel_user1;
+DROP ROLE regress_dummy_seclabel_user2;
diff --git a/src/test/modules/dummy_seclabel/sql/dummy_seclabel.sql b/src/test/modules/dummy_seclabel/sql/dummy_seclabel.sql
new file mode 100644
index 0000000..8c347b6
--- /dev/null
+++ b/src/test/modules/dummy_seclabel/sql/dummy_seclabel.sql
@@ -0,0 +1,115 @@
+--
+-- Test for facilities of security label
+--
+CREATE EXTENSION dummy_seclabel;
+
+-- initial setups
+SET client_min_messages TO 'warning';
+
+DROP ROLE IF EXISTS regress_dummy_seclabel_user1;
+DROP ROLE IF EXISTS regress_dummy_seclabel_user2;
+
+RESET client_min_messages;
+
+CREATE USER regress_dummy_seclabel_user1 WITH CREATEROLE;
+CREATE USER regress_dummy_seclabel_user2;
+
+CREATE TABLE dummy_seclabel_tbl1 (a int, b text);
+CREATE TABLE dummy_seclabel_tbl2 (x int, y text);
+CREATE VIEW dummy_seclabel_view1 AS SELECT * FROM dummy_seclabel_tbl2;
+CREATE FUNCTION dummy_seclabel_four() RETURNS integer AS $$SELECT 4$$ language sql;
+CREATE DOMAIN dummy_seclabel_domain AS text;
+
+ALTER TABLE dummy_seclabel_tbl1 OWNER TO regress_dummy_seclabel_user1;
+ALTER TABLE dummy_seclabel_tbl2 OWNER TO regress_dummy_seclabel_user2;
+
+--
+-- Test of SECURITY LABEL statement with a plugin
+--
+SET SESSION AUTHORIZATION regress_dummy_seclabel_user1;
+
+SECURITY LABEL ON TABLE dummy_seclabel_tbl1 IS 'classified'; -- OK
+SECURITY LABEL ON COLUMN dummy_seclabel_tbl1.a IS 'unclassified'; -- OK
+SECURITY LABEL ON COLUMN dummy_seclabel_tbl1 IS 'unclassified'; -- fail
+SECURITY LABEL ON TABLE dummy_seclabel_tbl1 IS '...invalid label...'; -- fail
+SECURITY LABEL FOR 'dummy' ON TABLE dummy_seclabel_tbl1 IS 'unclassified'; -- OK
+SECURITY LABEL FOR 'unknown_seclabel' ON TABLE dummy_seclabel_tbl1 IS 'classified'; -- fail
+SECURITY LABEL ON TABLE dummy_seclabel_tbl2 IS 'unclassified'; -- fail (not owner)
+SECURITY LABEL ON TABLE dummy_seclabel_tbl1 IS 'secret'; -- fail (not superuser)
+SECURITY LABEL ON TABLE dummy_seclabel_tbl3 IS 'unclassified'; -- fail (not found)
+
+SET SESSION AUTHORIZATION regress_dummy_seclabel_user2;
+SECURITY LABEL ON TABLE dummy_seclabel_tbl1 IS 'unclassified'; -- fail
+SECURITY LABEL ON TABLE dummy_seclabel_tbl2 IS 'classified'; -- OK
+
+--
+-- Test for shared database object
+--
+SET SESSION AUTHORIZATION regress_dummy_seclabel_user1;
+
+SECURITY LABEL ON ROLE regress_dummy_seclabel_user1 IS 'classified'; -- OK
+SECURITY LABEL ON ROLE regress_dummy_seclabel_user1 IS '...invalid label...'; -- fail
+SECURITY LABEL FOR 'dummy' ON ROLE regress_dummy_seclabel_user2 IS 'unclassified'; -- OK
+SECURITY LABEL FOR 'unknown_seclabel' ON ROLE regress_dummy_seclabel_user1 IS 'unclassified'; -- fail
+SECURITY LABEL ON ROLE regress_dummy_seclabel_user1 IS 'secret'; -- fail (not superuser)
+SECURITY LABEL ON ROLE regress_dummy_seclabel_user3 IS 'unclassified'; -- fail (not found)
+
+SET SESSION AUTHORIZATION regress_dummy_seclabel_user2;
+SECURITY LABEL ON ROLE regress_dummy_seclabel_user2 IS 'unclassified'; -- fail (not privileged)
+
+RESET SESSION AUTHORIZATION;
+
+--
+-- Test for various types of object
+--
+RESET SESSION AUTHORIZATION;
+
+SECURITY LABEL ON TABLE dummy_seclabel_tbl1 IS 'top secret'; -- OK
+SECURITY LABEL ON VIEW dummy_seclabel_view1 IS 'classified'; -- OK
+SECURITY LABEL ON FUNCTION dummy_seclabel_four() IS 'classified'; -- OK
+SECURITY LABEL ON DOMAIN dummy_seclabel_domain IS 'classified'; -- OK
+CREATE SCHEMA dummy_seclabel_test;
+SECURITY LABEL ON SCHEMA dummy_seclabel_test IS 'unclassified'; -- OK
+
+SET client_min_messages = error;
+CREATE PUBLICATION dummy_pub;
+CREATE SUBSCRIPTION dummy_sub CONNECTION '' PUBLICATION foo WITH (connect = false, slot_name = NONE);
+RESET client_min_messages;
+SECURITY LABEL ON PUBLICATION dummy_pub IS 'classified';
+SECURITY LABEL ON SUBSCRIPTION dummy_sub IS 'classified';
+
+SELECT objtype, objname, provider, label FROM pg_seclabels
+ ORDER BY objtype, objname;
+
+-- check for event trigger
+CREATE FUNCTION event_trigger_test()
+RETURNS event_trigger AS $$
+ BEGIN RAISE NOTICE 'event %: %', TG_EVENT, TG_TAG; END;
+$$ LANGUAGE plpgsql;
+
+CREATE EVENT TRIGGER always_start ON ddl_command_start
+EXECUTE PROCEDURE event_trigger_test();
+
+CREATE EVENT TRIGGER always_end ON ddl_command_end
+EXECUTE PROCEDURE event_trigger_test();
+
+CREATE EVENT TRIGGER always_drop ON sql_drop
+EXECUTE PROCEDURE event_trigger_test();
+
+CREATE EVENT TRIGGER always_rewrite ON table_rewrite
+EXECUTE PROCEDURE event_trigger_test();
+
+-- should trigger ddl_command_{start,end}
+SECURITY LABEL ON TABLE dummy_seclabel_tbl1 IS 'classified';
+
+-- clean up
+DROP EVENT TRIGGER always_start, always_end, always_drop, always_rewrite;
+
+DROP VIEW dummy_seclabel_view1;
+DROP TABLE dummy_seclabel_tbl1, dummy_seclabel_tbl2;
+
+DROP SUBSCRIPTION dummy_sub;
+DROP PUBLICATION dummy_pub;
+
+DROP ROLE regress_dummy_seclabel_user1;
+DROP ROLE regress_dummy_seclabel_user2;
diff --git a/src/test/modules/libpq_pipeline/.gitignore b/src/test/modules/libpq_pipeline/.gitignore
new file mode 100644
index 0000000..3a11e78
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/.gitignore
@@ -0,0 +1,5 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
+/libpq_pipeline
diff --git a/src/test/modules/libpq_pipeline/Makefile b/src/test/modules/libpq_pipeline/Makefile
new file mode 100644
index 0000000..65acc3e
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/Makefile
@@ -0,0 +1,25 @@
+# src/test/modules/libpq_pipeline/Makefile
+
+PGFILEDESC = "libpq_pipeline - test program for pipeline execution"
+PGAPPICON = win32
+
+PROGRAM = libpq_pipeline
+OBJS = $(WIN32RES) libpq_pipeline.o
+
+NO_INSTALL = 1
+
+PG_CPPFLAGS = -I$(libpq_srcdir)
+PG_LIBS_INTERNAL += $(libpq_pgport)
+
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/libpq_pipeline
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/libpq_pipeline/README b/src/test/modules/libpq_pipeline/README
new file mode 100644
index 0000000..d8174dd
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/README
@@ -0,0 +1 @@
+Test programs and libraries for libpq
diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c
new file mode 100644
index 0000000..dfab924
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c
@@ -0,0 +1,1883 @@
+/*-------------------------------------------------------------------------
+ *
+ * libpq_pipeline.c
+ * Verify libpq pipeline execution functionality
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/test/modules/libpq_pipeline/libpq_pipeline.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <sys/time.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#include "catalog/pg_type_d.h"
+#include "common/fe_memutils.h"
+#include "libpq-fe.h"
+#include "pg_getopt.h"
+#include "portability/instr_time.h"
+
+
+static void exit_nicely(PGconn *conn);
+static bool process_result(PGconn *conn, PGresult *res, int results,
+ int numsent);
+
+const char *const progname = "libpq_pipeline";
+
+/* Options and defaults */
+char *tracefile = NULL; /* path to PQtrace() file */
+
+
+#ifdef DEBUG_OUTPUT
+#define pg_debug(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
+#else
+#define pg_debug(...)
+#endif
+
+static const char *const drop_table_sql =
+"DROP TABLE IF EXISTS pq_pipeline_demo";
+static const char *const create_table_sql =
+"CREATE UNLOGGED TABLE pq_pipeline_demo(id serial primary key, itemno integer,"
+"int8filler int8);";
+static const char *const insert_sql =
+"INSERT INTO pq_pipeline_demo(itemno) VALUES ($1)";
+static const char *const insert_sql2 =
+"INSERT INTO pq_pipeline_demo(itemno,int8filler) VALUES ($1, $2)";
+
+/* max char length of an int32/64, plus sign and null terminator */
+#define MAXINTLEN 12
+#define MAXINT8LEN 20
+
+static void
+exit_nicely(PGconn *conn)
+{
+ PQfinish(conn);
+ exit(1);
+}
+
+/*
+ * Print an error to stderr and terminate the program.
+ */
+#define pg_fatal(...) pg_fatal_impl(__LINE__, __VA_ARGS__)
+static void
+pg_attribute_noreturn()
+pg_fatal_impl(int line, const char *fmt,...)
+{
+ va_list args;
+
+
+ fflush(stdout);
+
+ fprintf(stderr, "\n%s:%d: ", progname, line);
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+static void
+test_disallowed_in_pipeline(PGconn *conn)
+{
+ PGresult *res = NULL;
+
+ fprintf(stderr, "test error cases... ");
+
+ if (PQisnonblocking(conn))
+ pg_fatal("Expected blocking connection mode");
+
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("Unable to enter pipeline mode");
+
+ if (PQpipelineStatus(conn) == PQ_PIPELINE_OFF)
+ pg_fatal("Pipeline mode not activated properly");
+
+ /* PQexec should fail in pipeline mode */
+ res = PQexec(conn, "SELECT 1");
+ if (PQresultStatus(res) != PGRES_FATAL_ERROR)
+ pg_fatal("PQexec should fail in pipeline mode but succeeded");
+
+ /* Entering pipeline mode when already in pipeline mode is OK */
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("re-entering pipeline mode should be a no-op but failed");
+
+ if (PQisBusy(conn) != 0)
+ pg_fatal("PQisBusy should return 0 when idle in pipeline mode, returned 1");
+
+ /* ok, back to normal command mode */
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("couldn't exit idle empty pipeline mode");
+
+ if (PQpipelineStatus(conn) != PQ_PIPELINE_OFF)
+ pg_fatal("Pipeline mode not terminated properly");
+
+ /* exiting pipeline mode when not in pipeline mode should be a no-op */
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("pipeline mode exit when not in pipeline mode should succeed but failed");
+
+ /* can now PQexec again */
+ res = PQexec(conn, "SELECT 1");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("PQexec should succeed after exiting pipeline mode but failed with: %s",
+ PQerrorMessage(conn));
+
+ fprintf(stderr, "ok\n");
+}
+
+static void
+test_multi_pipelines(PGconn *conn)
+{
+ PGresult *res = NULL;
+ const char *dummy_params[1] = {"1"};
+ Oid dummy_param_oids[1] = {INT4OID};
+
+ fprintf(stderr, "multi pipeline... ");
+
+ /*
+ * Queue up a couple of small pipelines and process each without returning
+ * to command mode first.
+ */
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
+
+ if (PQsendQueryParams(conn, "SELECT $1", 1, dummy_param_oids,
+ dummy_params, NULL, NULL, 0) != 1)
+ pg_fatal("dispatching first SELECT failed: %s", PQerrorMessage(conn));
+
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("Pipeline sync failed: %s", PQerrorMessage(conn));
+
+ if (PQsendQueryParams(conn, "SELECT $1", 1, dummy_param_oids,
+ dummy_params, NULL, NULL, 0) != 1)
+ pg_fatal("dispatching second SELECT failed: %s", PQerrorMessage(conn));
+
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+
+ /* OK, start processing the results */
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when there's a pipeline item: %s",
+ PQerrorMessage(conn));
+
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("Unexpected result code %s from first pipeline item",
+ PQresStatus(PQresultStatus(res)));
+ PQclear(res);
+ res = NULL;
+
+ if (PQgetResult(conn) != NULL)
+ pg_fatal("PQgetResult returned something extra after first result");
+
+ if (PQexitPipelineMode(conn) != 0)
+ pg_fatal("exiting pipeline mode after query but before sync succeeded incorrectly");
+
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when sync result expected: %s",
+ PQerrorMessage(conn));
+
+ if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
+ pg_fatal("Unexpected result code %s instead of sync result, error: %s",
+ PQresStatus(PQresultStatus(res)), PQerrorMessage(conn));
+ PQclear(res);
+
+ /* second pipeline */
+
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when there's a pipeline item: %s",
+ PQerrorMessage(conn));
+
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("Unexpected result code %s from second pipeline item",
+ PQresStatus(PQresultStatus(res)));
+
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("Expected null result, got %s",
+ PQresStatus(PQresultStatus(res)));
+
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when there's a pipeline item: %s",
+ PQerrorMessage(conn));
+
+ if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
+ pg_fatal("Unexpected result code %s from second pipeline sync",
+ PQresStatus(PQresultStatus(res)));
+
+ /* We're still in pipeline mode ... */
+ if (PQpipelineStatus(conn) == PQ_PIPELINE_OFF)
+ pg_fatal("Fell out of pipeline mode somehow");
+
+ /* until we end it, which we can safely do now */
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("attempt to exit pipeline mode failed when it should've succeeded: %s",
+ PQerrorMessage(conn));
+
+ if (PQpipelineStatus(conn) != PQ_PIPELINE_OFF)
+ pg_fatal("exiting pipeline mode didn't seem to work");
+
+ fprintf(stderr, "ok\n");
+}
+
+/*
+ * Test behavior when a pipeline dispatches a number of commands that are
+ * not flushed by a sync point.
+ */
+static void
+test_nosync(PGconn *conn)
+{
+ int numqueries = 10;
+ int results = 0;
+ int sock = PQsocket(conn);
+
+ fprintf(stderr, "nosync... ");
+
+ if (sock < 0)
+ pg_fatal("invalid socket");
+
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("could not enter pipeline mode");
+ for (int i = 0; i < numqueries; i++)
+ {
+ fd_set input_mask;
+ struct timeval tv;
+
+ if (PQsendQueryParams(conn, "SELECT repeat('xyzxz', 12)",
+ 0, NULL, NULL, NULL, NULL, 0) != 1)
+ pg_fatal("error sending select: %s", PQerrorMessage(conn));
+ PQflush(conn);
+
+ /*
+ * If the server has written anything to us, read (some of) it now.
+ */
+ FD_ZERO(&input_mask);
+ FD_SET(sock, &input_mask);
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ if (select(sock + 1, &input_mask, NULL, NULL, &tv) < 0)
+ {
+ fprintf(stderr, "select() failed: %s\n", strerror(errno));
+ exit_nicely(conn);
+ }
+ if (FD_ISSET(sock, &input_mask) && PQconsumeInput(conn) != 1)
+ pg_fatal("failed to read from server: %s", PQerrorMessage(conn));
+ }
+
+ /* tell server to flush its output buffer */
+ if (PQsendFlushRequest(conn) != 1)
+ pg_fatal("failed to send flush request");
+ PQflush(conn);
+
+ /* Now read all results */
+ for (;;)
+ {
+ PGresult *res;
+
+ res = PQgetResult(conn);
+
+ /* NULL results are only expected after TUPLES_OK */
+ if (res == NULL)
+ pg_fatal("got unexpected NULL result after %d results", results);
+
+ /* We expect exactly one TUPLES_OK result for each query we sent */
+ if (PQresultStatus(res) == PGRES_TUPLES_OK)
+ {
+ PGresult *res2;
+
+ /* and one NULL result should follow each */
+ res2 = PQgetResult(conn);
+ if (res2 != NULL)
+ pg_fatal("expected NULL, got %s",
+ PQresStatus(PQresultStatus(res2)));
+ PQclear(res);
+ results++;
+
+ /* if we're done, we're done */
+ if (results == numqueries)
+ break;
+
+ continue;
+ }
+
+ /* anything else is unexpected */
+ pg_fatal("got unexpected %s\n", PQresStatus(PQresultStatus(res)));
+ }
+
+ fprintf(stderr, "ok\n");
+}
+
+/*
+ * When an operation in a pipeline fails the rest of the pipeline is flushed. We
+ * still have to get results for each pipeline item, but the item will just be
+ * a PGRES_PIPELINE_ABORTED code.
+ *
+ * This intentionally doesn't use a transaction to wrap the pipeline. You should
+ * usually use an xact, but in this case we want to observe the effects of each
+ * statement.
+ */
+static void
+test_pipeline_abort(PGconn *conn)
+{
+ PGresult *res = NULL;
+ const char *dummy_params[1] = {"1"};
+ Oid dummy_param_oids[1] = {INT4OID};
+ int i;
+ int gotrows;
+ bool goterror;
+
+ fprintf(stderr, "aborted pipeline... ");
+
+ res = PQexec(conn, drop_table_sql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("dispatching DROP TABLE failed: %s", PQerrorMessage(conn));
+
+ res = PQexec(conn, create_table_sql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("dispatching CREATE TABLE failed: %s", PQerrorMessage(conn));
+
+ /*
+ * Queue up a couple of small pipelines and process each without returning
+ * to command mode first. Make sure the second operation in the first
+ * pipeline ERRORs.
+ */
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
+
+ dummy_params[0] = "1";
+ if (PQsendQueryParams(conn, insert_sql, 1, dummy_param_oids,
+ dummy_params, NULL, NULL, 0) != 1)
+ pg_fatal("dispatching first insert failed: %s", PQerrorMessage(conn));
+
+ if (PQsendQueryParams(conn, "SELECT no_such_function($1)",
+ 1, dummy_param_oids, dummy_params,
+ NULL, NULL, 0) != 1)
+ pg_fatal("dispatching error select failed: %s", PQerrorMessage(conn));
+
+ dummy_params[0] = "2";
+ if (PQsendQueryParams(conn, insert_sql, 1, dummy_param_oids,
+ dummy_params, NULL, NULL, 0) != 1)
+ pg_fatal("dispatching second insert failed: %s", PQerrorMessage(conn));
+
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+
+ dummy_params[0] = "3";
+ if (PQsendQueryParams(conn, insert_sql, 1, dummy_param_oids,
+ dummy_params, NULL, NULL, 0) != 1)
+ pg_fatal("dispatching second-pipeline insert failed: %s",
+ PQerrorMessage(conn));
+
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+
+ /*
+ * OK, start processing the pipeline results.
+ *
+ * We should get a command-ok for the first query, then a fatal error and
+ * a pipeline aborted message for the second insert, a pipeline-end, then
+ * a command-ok and a pipeline-ok for the second pipeline operation.
+ */
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("Unexpected NULL result: %s", PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("Unexpected result status %s: %s",
+ PQresStatus(PQresultStatus(res)),
+ PQresultErrorMessage(res));
+ PQclear(res);
+
+ /* NULL result to signal end-of-results for this command */
+ if ((res = PQgetResult(conn)) != NULL)
+ pg_fatal("Expected null result, got %s",
+ PQresStatus(PQresultStatus(res)));
+
+ /* Second query caused error, so we expect an error next */
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("Unexpected NULL result: %s", PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_FATAL_ERROR)
+ pg_fatal("Unexpected result code -- expected PGRES_FATAL_ERROR, got %s",
+ PQresStatus(PQresultStatus(res)));
+ PQclear(res);
+
+ /* NULL result to signal end-of-results for this command */
+ if ((res = PQgetResult(conn)) != NULL)
+ pg_fatal("Expected null result, got %s",
+ PQresStatus(PQresultStatus(res)));
+
+ /*
+ * pipeline should now be aborted.
+ *
+ * Note that we could still queue more queries at this point if we wanted;
+ * they'd get added to a new third pipeline since we've already sent a
+ * second. The aborted flag relates only to the pipeline being received.
+ */
+ if (PQpipelineStatus(conn) != PQ_PIPELINE_ABORTED)
+ pg_fatal("pipeline should be flagged as aborted but isn't");
+
+ /* third query in pipeline, the second insert */
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("Unexpected NULL result: %s", PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_PIPELINE_ABORTED)
+ pg_fatal("Unexpected result code -- expected PGRES_PIPELINE_ABORTED, got %s",
+ PQresStatus(PQresultStatus(res)));
+ PQclear(res);
+
+ /* NULL result to signal end-of-results for this command */
+ if ((res = PQgetResult(conn)) != NULL)
+ pg_fatal("Expected null result, got %s", PQresStatus(PQresultStatus(res)));
+
+ if (PQpipelineStatus(conn) != PQ_PIPELINE_ABORTED)
+ pg_fatal("pipeline should be flagged as aborted but isn't");
+
+ /* Ensure we're still in pipeline */
+ if (PQpipelineStatus(conn) == PQ_PIPELINE_OFF)
+ pg_fatal("Fell out of pipeline mode somehow");
+
+ /*
+ * The end of a failed pipeline is a PGRES_PIPELINE_SYNC.
+ *
+ * (This is so clients know to start processing results normally again and
+ * can tell the difference between skipped commands and the sync.)
+ */
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("Unexpected NULL result: %s", PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
+ pg_fatal("Unexpected result code from first pipeline sync\n"
+ "Expected PGRES_PIPELINE_SYNC, got %s",
+ PQresStatus(PQresultStatus(res)));
+ PQclear(res);
+
+ if (PQpipelineStatus(conn) == PQ_PIPELINE_ABORTED)
+ pg_fatal("sync should've cleared the aborted flag but didn't");
+
+ /* We're still in pipeline mode... */
+ if (PQpipelineStatus(conn) == PQ_PIPELINE_OFF)
+ pg_fatal("Fell out of pipeline mode somehow");
+
+ /* the insert from the second pipeline */
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("Unexpected NULL result: %s", PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("Unexpected result code %s from first item in second pipeline",
+ PQresStatus(PQresultStatus(res)));
+ PQclear(res);
+
+ /* Read the NULL result at the end of the command */
+ if ((res = PQgetResult(conn)) != NULL)
+ pg_fatal("Expected null result, got %s", PQresStatus(PQresultStatus(res)));
+
+ /* the second pipeline sync */
+ if ((res = PQgetResult(conn)) == NULL)
+ pg_fatal("Unexpected NULL result: %s", PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
+ pg_fatal("Unexpected result code %s from second pipeline sync",
+ PQresStatus(PQresultStatus(res)));
+ PQclear(res);
+
+ if ((res = PQgetResult(conn)) != NULL)
+ pg_fatal("Expected null result, got %s: %s",
+ PQresStatus(PQresultStatus(res)),
+ PQerrorMessage(conn));
+
+ /* Try to send two queries in one command */
+ if (PQsendQuery(conn, "SELECT 1; SELECT 2") != 1)
+ pg_fatal("failed to send query: %s", PQerrorMessage(conn));
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+ goterror = false;
+ while ((res = PQgetResult(conn)) != NULL)
+ {
+ switch (PQresultStatus(res))
+ {
+ case PGRES_FATAL_ERROR:
+ if (strcmp(PQresultErrorField(res, PG_DIAG_SQLSTATE), "42601") != 0)
+ pg_fatal("expected error about multiple commands, got %s",
+ PQerrorMessage(conn));
+ printf("got expected %s", PQerrorMessage(conn));
+ goterror = true;
+ break;
+ default:
+ pg_fatal("got unexpected status %s", PQresStatus(PQresultStatus(res)));
+ break;
+ }
+ }
+ if (!goterror)
+ pg_fatal("did not get cannot-insert-multiple-commands error");
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("got NULL result");
+ if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
+ pg_fatal("Unexpected result code %s from pipeline sync",
+ PQresStatus(PQresultStatus(res)));
+ fprintf(stderr, "ok\n");
+
+ /* Test single-row mode with an error partways */
+ if (PQsendQuery(conn, "SELECT 1.0/g FROM generate_series(3, -1, -1) g") != 1)
+ pg_fatal("failed to send query: %s", PQerrorMessage(conn));
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+ PQsetSingleRowMode(conn);
+ goterror = false;
+ gotrows = 0;
+ while ((res = PQgetResult(conn)) != NULL)
+ {
+ switch (PQresultStatus(res))
+ {
+ case PGRES_SINGLE_TUPLE:
+ printf("got row: %s\n", PQgetvalue(res, 0, 0));
+ gotrows++;
+ break;
+ case PGRES_FATAL_ERROR:
+ if (strcmp(PQresultErrorField(res, PG_DIAG_SQLSTATE), "22012") != 0)
+ pg_fatal("expected division-by-zero, got: %s (%s)",
+ PQerrorMessage(conn),
+ PQresultErrorField(res, PG_DIAG_SQLSTATE));
+ printf("got expected division-by-zero\n");
+ goterror = true;
+ break;
+ default:
+ pg_fatal("got unexpected result %s", PQresStatus(PQresultStatus(res)));
+ }
+ PQclear(res);
+ }
+ if (!goterror)
+ pg_fatal("did not get division-by-zero error");
+ if (gotrows != 3)
+ pg_fatal("did not get three rows");
+ /* the third pipeline sync */
+ if ((res = PQgetResult(conn)) == NULL)
+ pg_fatal("Unexpected NULL result: %s", PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
+ pg_fatal("Unexpected result code %s from third pipeline sync",
+ PQresStatus(PQresultStatus(res)));
+ PQclear(res);
+
+ /* We're still in pipeline mode... */
+ if (PQpipelineStatus(conn) == PQ_PIPELINE_OFF)
+ pg_fatal("Fell out of pipeline mode somehow");
+
+ /* until we end it, which we can safely do now */
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("attempt to exit pipeline mode failed when it should've succeeded: %s",
+ PQerrorMessage(conn));
+
+ if (PQpipelineStatus(conn) != PQ_PIPELINE_OFF)
+ pg_fatal("exiting pipeline mode didn't seem to work");
+
+ /*-
+ * Since we fired the pipelines off without a surrounding xact, the results
+ * should be:
+ *
+ * - Implicit xact started by server around 1st pipeline
+ * - First insert applied
+ * - Second statement aborted xact
+ * - Third insert skipped
+ * - Sync rolled back first implicit xact
+ * - Implicit xact created by server around 2nd pipeline
+ * - insert applied from 2nd pipeline
+ * - Sync commits 2nd xact
+ *
+ * So we should only have the value 3 that we inserted.
+ */
+ res = PQexec(conn, "SELECT itemno FROM pq_pipeline_demo");
+
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("Expected tuples, got %s: %s",
+ PQresStatus(PQresultStatus(res)), PQerrorMessage(conn));
+ if (PQntuples(res) != 1)
+ pg_fatal("expected 1 result, got %d", PQntuples(res));
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ const char *val = PQgetvalue(res, i, 0);
+
+ if (strcmp(val, "3") != 0)
+ pg_fatal("expected only insert with value 3, got %s", val);
+ }
+
+ PQclear(res);
+
+ fprintf(stderr, "ok\n");
+}
+
+/* State machine enum for test_pipelined_insert */
+enum PipelineInsertStep
+{
+ BI_BEGIN_TX,
+ BI_DROP_TABLE,
+ BI_CREATE_TABLE,
+ BI_PREPARE,
+ BI_INSERT_ROWS,
+ BI_COMMIT_TX,
+ BI_SYNC,
+ BI_DONE
+};
+
+static void
+test_pipelined_insert(PGconn *conn, int n_rows)
+{
+ Oid insert_param_oids[2] = {INT4OID, INT8OID};
+ const char *insert_params[2];
+ char insert_param_0[MAXINTLEN];
+ char insert_param_1[MAXINT8LEN];
+ enum PipelineInsertStep send_step = BI_BEGIN_TX,
+ recv_step = BI_BEGIN_TX;
+ int rows_to_send,
+ rows_to_receive;
+
+ insert_params[0] = insert_param_0;
+ insert_params[1] = insert_param_1;
+
+ rows_to_send = rows_to_receive = n_rows;
+
+ /*
+ * Do a pipelined insert into a table created at the start of the pipeline
+ */
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
+
+ while (send_step != BI_PREPARE)
+ {
+ const char *sql;
+
+ switch (send_step)
+ {
+ case BI_BEGIN_TX:
+ sql = "BEGIN TRANSACTION";
+ send_step = BI_DROP_TABLE;
+ break;
+
+ case BI_DROP_TABLE:
+ sql = drop_table_sql;
+ send_step = BI_CREATE_TABLE;
+ break;
+
+ case BI_CREATE_TABLE:
+ sql = create_table_sql;
+ send_step = BI_PREPARE;
+ break;
+
+ default:
+ pg_fatal("invalid state");
+ sql = NULL; /* keep compiler quiet */
+ }
+
+ pg_debug("sending: %s\n", sql);
+ if (PQsendQueryParams(conn, sql,
+ 0, NULL, NULL, NULL, NULL, 0) != 1)
+ pg_fatal("dispatching %s failed: %s", sql, PQerrorMessage(conn));
+ }
+
+ Assert(send_step == BI_PREPARE);
+ pg_debug("sending: %s\n", insert_sql2);
+ if (PQsendPrepare(conn, "my_insert", insert_sql2, 2, insert_param_oids) != 1)
+ pg_fatal("dispatching PREPARE failed: %s", PQerrorMessage(conn));
+ send_step = BI_INSERT_ROWS;
+
+ /*
+ * Now we start inserting. We'll be sending enough data that we could fill
+ * our output buffer, so to avoid deadlocking we need to enter nonblocking
+ * mode and consume input while we send more output. As results of each
+ * query are processed we should pop them to allow processing of the next
+ * query. There's no need to finish the pipeline before processing
+ * results.
+ */
+ if (PQsetnonblocking(conn, 1) != 0)
+ pg_fatal("failed to set nonblocking mode: %s", PQerrorMessage(conn));
+
+ while (recv_step != BI_DONE)
+ {
+ int sock;
+ fd_set input_mask;
+ fd_set output_mask;
+
+ sock = PQsocket(conn);
+
+ if (sock < 0)
+ break; /* shouldn't happen */
+
+ FD_ZERO(&input_mask);
+ FD_SET(sock, &input_mask);
+ FD_ZERO(&output_mask);
+ FD_SET(sock, &output_mask);
+
+ if (select(sock + 1, &input_mask, &output_mask, NULL, NULL) < 0)
+ {
+ fprintf(stderr, "select() failed: %s\n", strerror(errno));
+ exit_nicely(conn);
+ }
+
+ /*
+ * Process any results, so we keep the server's output buffer free
+ * flowing and it can continue to process input
+ */
+ if (FD_ISSET(sock, &input_mask))
+ {
+ PQconsumeInput(conn);
+
+ /* Read until we'd block if we tried to read */
+ while (!PQisBusy(conn) && recv_step < BI_DONE)
+ {
+ PGresult *res;
+ const char *cmdtag = "";
+ const char *description = "";
+ int status;
+
+ /*
+ * Read next result. If no more results from this query,
+ * advance to the next query
+ */
+ res = PQgetResult(conn);
+ if (res == NULL)
+ continue;
+
+ status = PGRES_COMMAND_OK;
+ switch (recv_step)
+ {
+ case BI_BEGIN_TX:
+ cmdtag = "BEGIN";
+ recv_step++;
+ break;
+ case BI_DROP_TABLE:
+ cmdtag = "DROP TABLE";
+ recv_step++;
+ break;
+ case BI_CREATE_TABLE:
+ cmdtag = "CREATE TABLE";
+ recv_step++;
+ break;
+ case BI_PREPARE:
+ cmdtag = "";
+ description = "PREPARE";
+ recv_step++;
+ break;
+ case BI_INSERT_ROWS:
+ cmdtag = "INSERT";
+ rows_to_receive--;
+ if (rows_to_receive == 0)
+ recv_step++;
+ break;
+ case BI_COMMIT_TX:
+ cmdtag = "COMMIT";
+ recv_step++;
+ break;
+ case BI_SYNC:
+ cmdtag = "";
+ description = "SYNC";
+ status = PGRES_PIPELINE_SYNC;
+ recv_step++;
+ break;
+ case BI_DONE:
+ /* unreachable */
+ pg_fatal("unreachable state");
+ }
+
+ if (PQresultStatus(res) != status)
+ pg_fatal("%s reported status %s, expected %s\n"
+ "Error message: \"%s\"",
+ description, PQresStatus(PQresultStatus(res)),
+ PQresStatus(status), PQerrorMessage(conn));
+
+ if (strncmp(PQcmdStatus(res), cmdtag, strlen(cmdtag)) != 0)
+ pg_fatal("%s expected command tag '%s', got '%s'",
+ description, cmdtag, PQcmdStatus(res));
+
+ pg_debug("Got %s OK\n", cmdtag[0] != '\0' ? cmdtag : description);
+
+ PQclear(res);
+ }
+ }
+
+ /* Write more rows and/or the end pipeline message, if needed */
+ if (FD_ISSET(sock, &output_mask))
+ {
+ PQflush(conn);
+
+ if (send_step == BI_INSERT_ROWS)
+ {
+ snprintf(insert_param_0, MAXINTLEN, "%d", rows_to_send);
+ /* use up some buffer space with a wide value */
+ snprintf(insert_param_1, MAXINT8LEN, "%lld", 1LL << 62);
+
+ if (PQsendQueryPrepared(conn, "my_insert",
+ 2, insert_params, NULL, NULL, 0) == 1)
+ {
+ pg_debug("sent row %d\n", rows_to_send);
+
+ rows_to_send--;
+ if (rows_to_send == 0)
+ send_step++;
+ }
+ else
+ {
+ /*
+ * in nonblocking mode, so it's OK for an insert to fail
+ * to send
+ */
+ fprintf(stderr, "WARNING: failed to send insert #%d: %s\n",
+ rows_to_send, PQerrorMessage(conn));
+ }
+ }
+ else if (send_step == BI_COMMIT_TX)
+ {
+ if (PQsendQueryParams(conn, "COMMIT",
+ 0, NULL, NULL, NULL, NULL, 0) == 1)
+ {
+ pg_debug("sent COMMIT\n");
+ send_step++;
+ }
+ else
+ {
+ fprintf(stderr, "WARNING: failed to send commit: %s\n",
+ PQerrorMessage(conn));
+ }
+ }
+ else if (send_step == BI_SYNC)
+ {
+ if (PQpipelineSync(conn) == 1)
+ {
+ fprintf(stdout, "pipeline sync sent\n");
+ send_step++;
+ }
+ else
+ {
+ fprintf(stderr, "WARNING: pipeline sync failed: %s\n",
+ PQerrorMessage(conn));
+ }
+ }
+ }
+ }
+
+ /* We've got the sync message and the pipeline should be done */
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("attempt to exit pipeline mode failed when it should've succeeded: %s",
+ PQerrorMessage(conn));
+
+ if (PQsetnonblocking(conn, 0) != 0)
+ pg_fatal("failed to clear nonblocking mode: %s", PQerrorMessage(conn));
+
+ fprintf(stderr, "ok\n");
+}
+
+static void
+test_prepared(PGconn *conn)
+{
+ PGresult *res = NULL;
+ Oid param_oids[1] = {INT4OID};
+ Oid expected_oids[4];
+ Oid typ;
+
+ fprintf(stderr, "prepared... ");
+
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
+ if (PQsendPrepare(conn, "select_one", "SELECT $1, '42', $1::numeric, "
+ "interval '1 sec'",
+ 1, param_oids) != 1)
+ pg_fatal("preparing query failed: %s", PQerrorMessage(conn));
+ expected_oids[0] = INT4OID;
+ expected_oids[1] = TEXTOID;
+ expected_oids[2] = NUMERICOID;
+ expected_oids[3] = INTERVALOID;
+ if (PQsendDescribePrepared(conn, "select_one") != 1)
+ pg_fatal("failed to send describePrepared: %s", PQerrorMessage(conn));
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("expected COMMAND_OK, got %s", PQresStatus(PQresultStatus(res)));
+ PQclear(res);
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("expected NULL result");
+
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned NULL");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("expected COMMAND_OK, got %s", PQresStatus(PQresultStatus(res)));
+ if (PQnfields(res) != lengthof(expected_oids))
+ pg_fatal("expected %d columns, got %d",
+ lengthof(expected_oids), PQnfields(res));
+ for (int i = 0; i < PQnfields(res); i++)
+ {
+ typ = PQftype(res, i);
+ if (typ != expected_oids[i])
+ pg_fatal("field %d: expected type %u, got %u",
+ i, expected_oids[i], typ);
+ }
+ PQclear(res);
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("expected NULL result");
+
+ res = PQgetResult(conn);
+ if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
+ pg_fatal("expected PGRES_PIPELINE_SYNC, got %s", PQresStatus(PQresultStatus(res)));
+
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("could not exit pipeline mode: %s", PQerrorMessage(conn));
+
+ PQexec(conn, "BEGIN");
+ PQexec(conn, "DECLARE cursor_one CURSOR FOR SELECT 1");
+ PQenterPipelineMode(conn);
+ if (PQsendDescribePortal(conn, "cursor_one") != 1)
+ pg_fatal("PQsendDescribePortal failed: %s", PQerrorMessage(conn));
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("expected NULL result");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("expected COMMAND_OK, got %s", PQresStatus(PQresultStatus(res)));
+
+ typ = PQftype(res, 0);
+ if (typ != INT4OID)
+ pg_fatal("portal: expected type %u, got %u",
+ INT4OID, typ);
+ PQclear(res);
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("expected NULL result");
+ res = PQgetResult(conn);
+ if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
+ pg_fatal("expected PGRES_PIPELINE_SYNC, got %s", PQresStatus(PQresultStatus(res)));
+
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("could not exit pipeline mode: %s", PQerrorMessage(conn));
+
+ fprintf(stderr, "ok\n");
+}
+
+/* Notice processor: print notices, and count how many we got */
+static void
+notice_processor(void *arg, const char *message)
+{
+ int *n_notices = (int *) arg;
+
+ (*n_notices)++;
+ fprintf(stderr, "NOTICE %d: %s", *n_notices, message);
+}
+
+/* Verify behavior in "idle" state */
+static void
+test_pipeline_idle(PGconn *conn)
+{
+ PGresult *res;
+ int n_notices = 0;
+
+ fprintf(stderr, "\npipeline idle...\n");
+
+ PQsetNoticeProcessor(conn, notice_processor, &n_notices);
+
+ /*
+ * Cause a Close message to be sent to the server, and watch libpq's
+ * reaction to the resulting CloseComplete. libpq must not get in IDLE
+ * state until that has been received.
+ */
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
+
+ if (PQsendQuery(conn, "SELECT 1") != 1)
+ pg_fatal("failed to send query: %s", PQerrorMessage(conn));
+ PQsendFlushRequest(conn);
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when there's a pipeline item: %s",
+ PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("Unexpected result code %s from first pipeline item",
+ PQresStatus(PQresultStatus(res)));
+ PQclear(res);
+
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("expected NULL result");
+
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when there's a pipeline item: %s",
+ PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
+ pg_fatal("Unexpected result code %s instead of PGRES_PIPELINE_SYNC, error: %s",
+ PQresStatus(PQresultStatus(res)), PQerrorMessage(conn));
+ PQclear(res);
+ res = NULL;
+
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("attempt to exit pipeline mode failed when it should've succeeded: %s",
+ PQerrorMessage(conn));
+
+ /*
+ * Must not have got any notices here; note bug as described in
+ * https://postgr.es/m/CA+mi_8bvD0_CW3sumgwPvWdNzXY32itoG_16tDYRu_1S2gV2iw@mail.gmail.com
+ */
+ if (n_notices > 0)
+ pg_fatal("got %d notice(s)", n_notices);
+ fprintf(stderr, "ok - 1\n");
+
+ /*
+ * Verify that we can send a query using simple query protocol after one
+ * in pipeline mode.
+ */
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
+ if (PQsendQuery(conn, "SELECT 1") != 1)
+ pg_fatal("failed to send query: %s", PQerrorMessage(conn));
+ PQsendFlushRequest(conn);
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when there's a pipeline item: %s",
+ PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("unexpected result code %s from first pipeline item",
+ PQresStatus(PQresultStatus(res)));
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("got unexpected non-null result");
+ /* We can exit pipeline mode now */
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("attempt to exit pipeline mode failed when it should've succeeded: %s",
+ PQerrorMessage(conn));
+ res = PQexec(conn, "SELECT 2");
+ if (n_notices > 0)
+ pg_fatal("got %d notice(s)", n_notices);
+ if (res == NULL)
+ pg_fatal("PQexec returned NULL");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("unexpected result code %s from non-pipeline query",
+ PQresStatus(PQresultStatus(res)));
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("did not receive terminating NULL");
+ if (n_notices > 0)
+ pg_fatal("got %d notice(s)", n_notices);
+ fprintf(stderr, "ok - 2\n");
+
+ /*
+ * Case 2: exiting pipeline mode is not OK if a second command is sent.
+ */
+
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
+ if (PQsendQuery(conn, "SELECT 1") != 1)
+ pg_fatal("failed to send query: %s", PQerrorMessage(conn));
+ PQsendFlushRequest(conn);
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when there's a pipeline item: %s",
+ PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("unexpected result code %s from first pipeline item",
+ PQresStatus(PQresultStatus(res)));
+ if (PQsendQuery(conn, "SELECT 2") != 1)
+ pg_fatal("failed to send query: %s", PQerrorMessage(conn));
+ PQsendFlushRequest(conn);
+ /* read terminating null from first query */
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("did not receive terminating NULL");
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when there's a pipeline item: %s",
+ PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("unexpected result code %s from first pipeline item",
+ PQresStatus(PQresultStatus(res)));
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("did not receive terminating NULL");
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("attempt to exit pipeline mode failed when it should've succeeded: %s",
+ PQerrorMessage(conn));
+
+ /* Try to exit pipeline mode in pipeline-idle state */
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
+ if (PQsendQuery(conn, "SELECT 1") != 1)
+ pg_fatal("failed to send query: %s", PQerrorMessage(conn));
+ PQsendFlushRequest(conn);
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when there's a pipeline item: %s",
+ PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("unexpected result code %s from first pipeline item",
+ PQresStatus(PQresultStatus(res)));
+ PQclear(res);
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("did not receive terminating NULL");
+ if (PQsendQuery(conn, "SELECT 2") != 1)
+ pg_fatal("failed to send query: %s", PQerrorMessage(conn));
+ if (PQexitPipelineMode(conn) == 1)
+ pg_fatal("exiting pipeline succeeded when it shouldn't");
+ if (strncmp(PQerrorMessage(conn), "cannot exit pipeline mode",
+ strlen("cannot exit pipeline mode")) != 0)
+ pg_fatal("did not get expected error; got: %s",
+ PQerrorMessage(conn));
+ PQsendFlushRequest(conn);
+ res = PQgetResult(conn);
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("unexpected result code %s from second pipeline item",
+ PQresStatus(PQresultStatus(res)));
+ PQclear(res);
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("did not receive terminating NULL");
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("exiting pipeline failed: %s", PQerrorMessage(conn));
+
+ if (n_notices > 0)
+ pg_fatal("got %d notice(s)", n_notices);
+ fprintf(stderr, "ok - 3\n");
+
+ /* Have a WARNING in the middle of a resultset */
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("entering pipeline mode failed: %s", PQerrorMessage(conn));
+ if (PQsendQuery(conn, "SELECT pg_catalog.pg_advisory_unlock(1,1)") != 1)
+ pg_fatal("failed to send query: %s", PQerrorMessage(conn));
+ PQsendFlushRequest(conn);
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("unexpected NULL result received");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("unexpected result code %s", PQresStatus(PQresultStatus(res)));
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("failed to exit pipeline mode: %s", PQerrorMessage(conn));
+ fprintf(stderr, "ok - 4\n");
+}
+
+static void
+test_simple_pipeline(PGconn *conn)
+{
+ PGresult *res = NULL;
+ const char *dummy_params[1] = {"1"};
+ Oid dummy_param_oids[1] = {INT4OID};
+
+ fprintf(stderr, "simple pipeline... ");
+
+ /*
+ * Enter pipeline mode and dispatch a set of operations, which we'll then
+ * process the results of as they come in.
+ *
+ * For a simple case we should be able to do this without interim
+ * processing of results since our output buffer will give us enough slush
+ * to work with and we won't block on sending. So blocking mode is fine.
+ */
+ if (PQisnonblocking(conn))
+ pg_fatal("Expected blocking connection mode");
+
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
+
+ if (PQsendQueryParams(conn, "SELECT $1",
+ 1, dummy_param_oids, dummy_params,
+ NULL, NULL, 0) != 1)
+ pg_fatal("dispatching SELECT failed: %s", PQerrorMessage(conn));
+
+ if (PQexitPipelineMode(conn) != 0)
+ pg_fatal("exiting pipeline mode with work in progress should fail, but succeeded");
+
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when there's a pipeline item: %s",
+ PQerrorMessage(conn));
+
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("Unexpected result code %s from first pipeline item",
+ PQresStatus(PQresultStatus(res)));
+
+ PQclear(res);
+ res = NULL;
+
+ if (PQgetResult(conn) != NULL)
+ pg_fatal("PQgetResult returned something extra after first query result.");
+
+ /*
+ * Even though we've processed the result there's still a sync to come and
+ * we can't exit pipeline mode yet
+ */
+ if (PQexitPipelineMode(conn) != 0)
+ pg_fatal("exiting pipeline mode after query but before sync succeeded incorrectly");
+
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when sync result PGRES_PIPELINE_SYNC expected: %s",
+ PQerrorMessage(conn));
+
+ if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
+ pg_fatal("Unexpected result code %s instead of PGRES_PIPELINE_SYNC, error: %s",
+ PQresStatus(PQresultStatus(res)), PQerrorMessage(conn));
+
+ PQclear(res);
+ res = NULL;
+
+ if (PQgetResult(conn) != NULL)
+ pg_fatal("PQgetResult returned something extra after pipeline end: %s",
+ PQresStatus(PQresultStatus(res)));
+
+ /* We're still in pipeline mode... */
+ if (PQpipelineStatus(conn) == PQ_PIPELINE_OFF)
+ pg_fatal("Fell out of pipeline mode somehow");
+
+ /* ... until we end it, which we can safely do now */
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("attempt to exit pipeline mode failed when it should've succeeded: %s",
+ PQerrorMessage(conn));
+
+ if (PQpipelineStatus(conn) != PQ_PIPELINE_OFF)
+ pg_fatal("Exiting pipeline mode didn't seem to work");
+
+ fprintf(stderr, "ok\n");
+}
+
+static void
+test_singlerowmode(PGconn *conn)
+{
+ PGresult *res;
+ int i;
+ bool pipeline_ended = false;
+
+ /* 1 pipeline, 3 queries in it */
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s",
+ PQerrorMessage(conn));
+
+ for (i = 0; i < 3; i++)
+ {
+ char *param[1];
+
+ param[0] = psprintf("%d", 44 + i);
+
+ if (PQsendQueryParams(conn,
+ "SELECT generate_series(42, $1)",
+ 1,
+ NULL,
+ (const char **) param,
+ NULL,
+ NULL,
+ 0) != 1)
+ pg_fatal("failed to send query: %s",
+ PQerrorMessage(conn));
+ pfree(param[0]);
+ }
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+
+ for (i = 0; !pipeline_ended; i++)
+ {
+ bool first = true;
+ bool saw_ending_tuplesok;
+ bool isSingleTuple = false;
+
+ /* Set single row mode for only first 2 SELECT queries */
+ if (i < 2)
+ {
+ if (PQsetSingleRowMode(conn) != 1)
+ pg_fatal("PQsetSingleRowMode() failed for i=%d", i);
+ }
+
+ /* Consume rows for this query */
+ saw_ending_tuplesok = false;
+ while ((res = PQgetResult(conn)) != NULL)
+ {
+ ExecStatusType est = PQresultStatus(res);
+
+ if (est == PGRES_PIPELINE_SYNC)
+ {
+ fprintf(stderr, "end of pipeline reached\n");
+ pipeline_ended = true;
+ PQclear(res);
+ if (i != 3)
+ pg_fatal("Expected three results, got %d", i);
+ break;
+ }
+
+ /* Expect SINGLE_TUPLE for queries 0 and 1, TUPLES_OK for 2 */
+ if (first)
+ {
+ if (i <= 1 && est != PGRES_SINGLE_TUPLE)
+ pg_fatal("Expected PGRES_SINGLE_TUPLE for query %d, got %s",
+ i, PQresStatus(est));
+ if (i >= 2 && est != PGRES_TUPLES_OK)
+ pg_fatal("Expected PGRES_TUPLES_OK for query %d, got %s",
+ i, PQresStatus(est));
+ first = false;
+ }
+
+ fprintf(stderr, "Result status %s for query %d", PQresStatus(est), i);
+ switch (est)
+ {
+ case PGRES_TUPLES_OK:
+ fprintf(stderr, ", tuples: %d\n", PQntuples(res));
+ saw_ending_tuplesok = true;
+ if (isSingleTuple)
+ {
+ if (PQntuples(res) == 0)
+ fprintf(stderr, "all tuples received in query %d\n", i);
+ else
+ pg_fatal("Expected to follow PGRES_SINGLE_TUPLE, but received PGRES_TUPLES_OK directly instead");
+ }
+ break;
+
+ case PGRES_SINGLE_TUPLE:
+ isSingleTuple = true;
+ fprintf(stderr, ", %d tuple: %s\n", PQntuples(res), PQgetvalue(res, 0, 0));
+ break;
+
+ default:
+ pg_fatal("unexpected");
+ }
+ PQclear(res);
+ }
+ if (!pipeline_ended && !saw_ending_tuplesok)
+ pg_fatal("didn't get expected terminating TUPLES_OK");
+ }
+
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("failed to end pipeline mode: %s", PQerrorMessage(conn));
+
+ fprintf(stderr, "ok\n");
+}
+
+/*
+ * Simple test to verify that a pipeline is discarded as a whole when there's
+ * an error, ignoring transaction commands.
+ */
+static void
+test_transaction(PGconn *conn)
+{
+ PGresult *res;
+ bool expect_null;
+ int num_syncs = 0;
+
+ res = PQexec(conn, "DROP TABLE IF EXISTS pq_pipeline_tst;"
+ "CREATE TABLE pq_pipeline_tst (id int)");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to create test table: %s",
+ PQerrorMessage(conn));
+ PQclear(res);
+
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s",
+ PQerrorMessage(conn));
+ if (PQsendPrepare(conn, "rollback", "ROLLBACK", 0, NULL) != 1)
+ pg_fatal("could not send prepare on pipeline: %s",
+ PQerrorMessage(conn));
+
+ if (PQsendQueryParams(conn,
+ "BEGIN",
+ 0, NULL, NULL, NULL, NULL, 0) != 1)
+ pg_fatal("failed to send query: %s",
+ PQerrorMessage(conn));
+ if (PQsendQueryParams(conn,
+ "SELECT 0/0",
+ 0, NULL, NULL, NULL, NULL, 0) != 1)
+ pg_fatal("failed to send query: %s",
+ PQerrorMessage(conn));
+
+ /*
+ * send a ROLLBACK using a prepared stmt. Doesn't work because we need to
+ * get out of the pipeline-aborted state first.
+ */
+ if (PQsendQueryPrepared(conn, "rollback", 0, NULL, NULL, NULL, 1) != 1)
+ pg_fatal("failed to execute prepared: %s",
+ PQerrorMessage(conn));
+
+ /* This insert fails because we're in pipeline-aborted state */
+ if (PQsendQueryParams(conn,
+ "INSERT INTO pq_pipeline_tst VALUES (1)",
+ 0, NULL, NULL, NULL, NULL, 0) != 1)
+ pg_fatal("failed to send query: %s",
+ PQerrorMessage(conn));
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+ num_syncs++;
+
+ /*
+ * This insert fails even though the pipeline got a SYNC, because we're in
+ * an aborted transaction
+ */
+ if (PQsendQueryParams(conn,
+ "INSERT INTO pq_pipeline_tst VALUES (2)",
+ 0, NULL, NULL, NULL, NULL, 0) != 1)
+ pg_fatal("failed to send query: %s",
+ PQerrorMessage(conn));
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+ num_syncs++;
+
+ /*
+ * Send ROLLBACK using prepared stmt. This one works because we just did
+ * PQpipelineSync above.
+ */
+ if (PQsendQueryPrepared(conn, "rollback", 0, NULL, NULL, NULL, 1) != 1)
+ pg_fatal("failed to execute prepared: %s",
+ PQerrorMessage(conn));
+
+ /*
+ * Now that we're out of a transaction and in pipeline-good mode, this
+ * insert works
+ */
+ if (PQsendQueryParams(conn,
+ "INSERT INTO pq_pipeline_tst VALUES (3)",
+ 0, NULL, NULL, NULL, NULL, 0) != 1)
+ pg_fatal("failed to send query: %s",
+ PQerrorMessage(conn));
+ /* Send two syncs now -- match up to SYNC messages below */
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+ num_syncs++;
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+ num_syncs++;
+
+ expect_null = false;
+ for (int i = 0;; i++)
+ {
+ ExecStatusType restype;
+
+ res = PQgetResult(conn);
+ if (res == NULL)
+ {
+ printf("%d: got NULL result\n", i);
+ if (!expect_null)
+ pg_fatal("did not expect NULL here");
+ expect_null = false;
+ continue;
+ }
+ restype = PQresultStatus(res);
+ printf("%d: got status %s", i, PQresStatus(restype));
+ if (expect_null)
+ pg_fatal("expected NULL");
+ if (restype == PGRES_FATAL_ERROR)
+ printf("; error: %s", PQerrorMessage(conn));
+ else if (restype == PGRES_PIPELINE_ABORTED)
+ {
+ printf(": command didn't run because pipeline aborted\n");
+ }
+ else
+ printf("\n");
+ PQclear(res);
+
+ if (restype == PGRES_PIPELINE_SYNC)
+ num_syncs--;
+ else
+ expect_null = true;
+ if (num_syncs <= 0)
+ break;
+ }
+ if (PQgetResult(conn) != NULL)
+ pg_fatal("returned something extra after all the syncs: %s",
+ PQresStatus(PQresultStatus(res)));
+
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("failed to end pipeline mode: %s", PQerrorMessage(conn));
+
+ /* We expect to find one tuple containing the value "3" */
+ res = PQexec(conn, "SELECT * FROM pq_pipeline_tst");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("failed to obtain result: %s", PQerrorMessage(conn));
+ if (PQntuples(res) != 1)
+ pg_fatal("did not get 1 tuple");
+ if (strcmp(PQgetvalue(res, 0, 0), "3") != 0)
+ pg_fatal("did not get expected tuple");
+ PQclear(res);
+
+ fprintf(stderr, "ok\n");
+}
+
+/*
+ * In this test mode we send a stream of queries, with one in the middle
+ * causing an error. Verify that we can still send some more after the
+ * error and have libpq work properly.
+ */
+static void
+test_uniqviol(PGconn *conn)
+{
+ int sock = PQsocket(conn);
+ PGresult *res;
+ Oid paramTypes[2] = {INT8OID, INT8OID};
+ const char *paramValues[2];
+ char paramValue0[MAXINT8LEN];
+ char paramValue1[MAXINT8LEN];
+ int ctr = 0;
+ int numsent = 0;
+ int results = 0;
+ bool read_done = false;
+ bool write_done = false;
+ bool error_sent = false;
+ bool got_error = false;
+ int switched = 0;
+ int socketful = 0;
+ fd_set in_fds;
+ fd_set out_fds;
+
+ fprintf(stderr, "uniqviol ...");
+
+ PQsetnonblocking(conn, 1);
+
+ paramValues[0] = paramValue0;
+ paramValues[1] = paramValue1;
+ sprintf(paramValue1, "42");
+
+ res = PQexec(conn, "drop table if exists ppln_uniqviol;"
+ "create table ppln_uniqviol(id bigint primary key, idata bigint)");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to create table: %s", PQerrorMessage(conn));
+
+ res = PQexec(conn, "begin");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to begin transaction: %s", PQerrorMessage(conn));
+
+ res = PQprepare(conn, "insertion",
+ "insert into ppln_uniqviol values ($1, $2) returning id",
+ 2, paramTypes);
+ if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to prepare query: %s", PQerrorMessage(conn));
+
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode");
+
+ while (!read_done)
+ {
+ /*
+ * Avoid deadlocks by reading everything the server has sent before
+ * sending anything. (Special precaution is needed here to process
+ * PQisBusy before testing the socket for read-readiness, because the
+ * socket does not turn read-ready after "sending" queries in aborted
+ * pipeline mode.)
+ */
+ while (PQisBusy(conn) == 0)
+ {
+ bool new_error;
+
+ if (results >= numsent)
+ {
+ if (write_done)
+ read_done = true;
+ break;
+ }
+
+ res = PQgetResult(conn);
+ new_error = process_result(conn, res, results, numsent);
+ if (new_error && got_error)
+ pg_fatal("got two errors");
+ got_error |= new_error;
+ if (results++ >= numsent - 1)
+ {
+ if (write_done)
+ read_done = true;
+ break;
+ }
+ }
+
+ if (read_done)
+ break;
+
+ FD_ZERO(&out_fds);
+ FD_SET(sock, &out_fds);
+
+ FD_ZERO(&in_fds);
+ FD_SET(sock, &in_fds);
+
+ if (select(sock + 1, &in_fds, write_done ? NULL : &out_fds, NULL, NULL) == -1)
+ {
+ if (errno == EINTR)
+ continue;
+ pg_fatal("select() failed: %m");
+ }
+
+ if (FD_ISSET(sock, &in_fds) && PQconsumeInput(conn) == 0)
+ pg_fatal("PQconsumeInput failed: %s", PQerrorMessage(conn));
+
+ /*
+ * If the socket is writable and we haven't finished sending queries,
+ * send some.
+ */
+ if (!write_done && FD_ISSET(sock, &out_fds))
+ {
+ for (;;)
+ {
+ int flush;
+
+ /*
+ * provoke uniqueness violation exactly once after having
+ * switched to read mode.
+ */
+ if (switched >= 1 && !error_sent && ctr % socketful >= socketful / 2)
+ {
+ sprintf(paramValue0, "%d", numsent / 2);
+ fprintf(stderr, "E");
+ error_sent = true;
+ }
+ else
+ {
+ fprintf(stderr, ".");
+ sprintf(paramValue0, "%d", ctr++);
+ }
+
+ if (PQsendQueryPrepared(conn, "insertion", 2, paramValues, NULL, NULL, 0) != 1)
+ pg_fatal("failed to execute prepared query: %s", PQerrorMessage(conn));
+ numsent++;
+
+ /* Are we done writing? */
+ if (socketful != 0 && numsent % socketful == 42 && error_sent)
+ {
+ if (PQsendFlushRequest(conn) != 1)
+ pg_fatal("failed to send flush request");
+ write_done = true;
+ fprintf(stderr, "\ndone writing\n");
+ PQflush(conn);
+ break;
+ }
+
+ /* is the outgoing socket full? */
+ flush = PQflush(conn);
+ if (flush == -1)
+ pg_fatal("failed to flush: %s", PQerrorMessage(conn));
+ if (flush == 1)
+ {
+ if (socketful == 0)
+ socketful = numsent;
+ fprintf(stderr, "\nswitch to reading\n");
+ switched++;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!got_error)
+ pg_fatal("did not get expected error");
+
+ fprintf(stderr, "ok\n");
+}
+
+/*
+ * Subroutine for test_uniqviol; given a PGresult, print it out and consume
+ * the expected NULL that should follow it.
+ *
+ * Returns true if we read a fatal error message, otherwise false.
+ */
+static bool
+process_result(PGconn *conn, PGresult *res, int results, int numsent)
+{
+ PGresult *res2;
+ bool got_error = false;
+
+ if (res == NULL)
+ pg_fatal("got unexpected NULL");
+
+ switch (PQresultStatus(res))
+ {
+ case PGRES_FATAL_ERROR:
+ got_error = true;
+ fprintf(stderr, "result %d/%d (error): %s\n", results, numsent, PQerrorMessage(conn));
+ PQclear(res);
+
+ res2 = PQgetResult(conn);
+ if (res2 != NULL)
+ pg_fatal("expected NULL, got %s",
+ PQresStatus(PQresultStatus(res2)));
+ break;
+
+ case PGRES_TUPLES_OK:
+ fprintf(stderr, "result %d/%d: %s\n", results, numsent, PQgetvalue(res, 0, 0));
+ PQclear(res);
+
+ res2 = PQgetResult(conn);
+ if (res2 != NULL)
+ pg_fatal("expected NULL, got %s",
+ PQresStatus(PQresultStatus(res2)));
+ break;
+
+ case PGRES_PIPELINE_ABORTED:
+ fprintf(stderr, "result %d/%d: pipeline aborted\n", results, numsent);
+ res2 = PQgetResult(conn);
+ if (res2 != NULL)
+ pg_fatal("expected NULL, got %s",
+ PQresStatus(PQresultStatus(res2)));
+ break;
+
+ default:
+ pg_fatal("got unexpected %s", PQresStatus(PQresultStatus(res)));
+ }
+
+ return got_error;
+}
+
+
+static void
+usage(const char *progname)
+{
+ fprintf(stderr, "%s tests libpq's pipeline mode.\n\n", progname);
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr, " %s [OPTION] tests\n", progname);
+ fprintf(stderr, " %s [OPTION] TESTNAME [CONNINFO]\n", progname);
+ fprintf(stderr, "\nOptions:\n");
+ fprintf(stderr, " -t TRACEFILE generate a libpq trace to TRACEFILE\n");
+ fprintf(stderr, " -r NUMROWS use NUMROWS as the test size\n");
+}
+
+static void
+print_test_list(void)
+{
+ printf("disallowed_in_pipeline\n");
+ printf("multi_pipelines\n");
+ printf("nosync\n");
+ printf("pipeline_abort\n");
+ printf("pipeline_idle\n");
+ printf("pipelined_insert\n");
+ printf("prepared\n");
+ printf("simple_pipeline\n");
+ printf("singlerow\n");
+ printf("transaction\n");
+ printf("uniqviol\n");
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *conninfo = "";
+ PGconn *conn;
+ FILE *trace;
+ char *testname;
+ int numrows = 10000;
+ PGresult *res;
+ int c;
+
+ while ((c = getopt(argc, argv, "t:r:")) != -1)
+ {
+ switch (c)
+ {
+ case 't': /* trace file */
+ tracefile = pg_strdup(optarg);
+ break;
+ case 'r': /* numrows */
+ errno = 0;
+ numrows = strtol(optarg, NULL, 10);
+ if (errno != 0 || numrows <= 0)
+ {
+ fprintf(stderr, "couldn't parse \"%s\" as a positive integer\n",
+ optarg);
+ exit(1);
+ }
+ break;
+ }
+ }
+
+ if (optind < argc)
+ {
+ testname = pg_strdup(argv[optind]);
+ optind++;
+ }
+ else
+ {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ if (strcmp(testname, "tests") == 0)
+ {
+ print_test_list();
+ exit(0);
+ }
+
+ if (optind < argc)
+ {
+ conninfo = pg_strdup(argv[optind]);
+ optind++;
+ }
+
+ /* Make a connection to the database */
+ conn = PQconnectdb(conninfo);
+ if (PQstatus(conn) != CONNECTION_OK)
+ {
+ fprintf(stderr, "Connection to database failed: %s\n",
+ PQerrorMessage(conn));
+ exit_nicely(conn);
+ }
+
+ res = PQexec(conn, "SET lc_messages TO \"C\"");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to set lc_messages: %s", PQerrorMessage(conn));
+ res = PQexec(conn, "SET force_parallel_mode = off");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to set force_parallel_mode: %s", PQerrorMessage(conn));
+
+ /* Set the trace file, if requested */
+ if (tracefile != NULL)
+ {
+ if (strcmp(tracefile, "-") == 0)
+ trace = stdout;
+ else
+ trace = fopen(tracefile, "w");
+ if (trace == NULL)
+ pg_fatal("could not open file \"%s\": %m", tracefile);
+
+ /* Make it line-buffered */
+ setvbuf(trace, NULL, PG_IOLBF, 0);
+
+ PQtrace(conn, trace);
+ PQsetTraceFlags(conn,
+ PQTRACE_SUPPRESS_TIMESTAMPS | PQTRACE_REGRESS_MODE);
+ }
+
+ if (strcmp(testname, "disallowed_in_pipeline") == 0)
+ test_disallowed_in_pipeline(conn);
+ else if (strcmp(testname, "multi_pipelines") == 0)
+ test_multi_pipelines(conn);
+ else if (strcmp(testname, "nosync") == 0)
+ test_nosync(conn);
+ else if (strcmp(testname, "pipeline_abort") == 0)
+ test_pipeline_abort(conn);
+ else if (strcmp(testname, "pipeline_idle") == 0)
+ test_pipeline_idle(conn);
+ else if (strcmp(testname, "pipelined_insert") == 0)
+ test_pipelined_insert(conn, numrows);
+ else if (strcmp(testname, "prepared") == 0)
+ test_prepared(conn);
+ else if (strcmp(testname, "simple_pipeline") == 0)
+ test_simple_pipeline(conn);
+ else if (strcmp(testname, "singlerow") == 0)
+ test_singlerowmode(conn);
+ else if (strcmp(testname, "transaction") == 0)
+ test_transaction(conn);
+ else if (strcmp(testname, "uniqviol") == 0)
+ test_uniqviol(conn);
+ else
+ {
+ fprintf(stderr, "\"%s\" is not a recognized test name\n", testname);
+ exit(1);
+ }
+
+ /* close the connection to the database and cleanup */
+ PQfinish(conn);
+ return 0;
+}
diff --git a/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl
new file mode 100644
index 0000000..b02928c
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl
@@ -0,0 +1,78 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+my $node = get_new_node('main');
+$node->init;
+$node->start;
+
+my $numrows = 700;
+
+my ($out, $err) = run_command([ 'libpq_pipeline', 'tests' ]);
+die "oops: $err" unless $err eq '';
+my @tests = split(/\s+/, $out);
+
+mkdir "$TestLib::tmp_check/traces";
+
+for my $testname (@tests)
+{
+ my @extraargs = ('-r', $numrows);
+ my $cmptrace = grep(/^$testname$/,
+ qw(simple_pipeline nosync multi_pipelines prepared singlerow
+ pipeline_abort pipeline_idle transaction
+ disallowed_in_pipeline)) > 0;
+
+ # For a bunch of tests, generate a libpq trace file too.
+ my $traceout = "$TestLib::tmp_check/traces/$testname.trace";
+ if ($cmptrace)
+ {
+ push @extraargs, "-t", $traceout;
+ }
+
+ # Execute the test
+ $node->command_ok(
+ [
+ 'libpq_pipeline', @extraargs,
+ $testname, $node->connstr('postgres')
+ ],
+ "libpq_pipeline $testname");
+
+ # Compare the trace, if requested
+ if ($cmptrace)
+ {
+ my $expected;
+ my $result;
+
+ $expected = slurp_file_eval("traces/$testname.trace");
+ next unless $expected ne "";
+ $result = slurp_file_eval($traceout);
+ next unless $result ne "";
+
+ is($result, $expected, "$testname trace match");
+ }
+}
+
+$node->stop('fast');
+
+done_testing();
+
+sub slurp_file_eval
+{
+ my $filepath = shift;
+ my $contents;
+
+ eval { $contents = slurp_file($filepath); };
+ if ($@)
+ {
+ fail "reading $filepath: $@";
+ return "";
+ }
+ return $contents;
+}
diff --git a/src/test/modules/libpq_pipeline/traces/disallowed_in_pipeline.trace b/src/test/modules/libpq_pipeline/traces/disallowed_in_pipeline.trace
new file mode 100644
index 0000000..dd6df03
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/traces/disallowed_in_pipeline.trace
@@ -0,0 +1,6 @@
+F 13 Query "SELECT 1"
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
+B 11 DataRow 1 1 '1'
+B 13 CommandComplete "SELECT 1"
+B 5 ReadyForQuery I
+F 4 Terminate
diff --git a/src/test/modules/libpq_pipeline/traces/multi_pipelines.trace b/src/test/modules/libpq_pipeline/traces/multi_pipelines.trace
new file mode 100644
index 0000000..4b9ab07
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/traces/multi_pipelines.trace
@@ -0,0 +1,23 @@
+F 21 Parse "" "SELECT $1" 1 NNNN
+F 19 Bind "" "" 0 1 1 '1' 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 4 Sync
+F 21 Parse "" "SELECT $1" 1 NNNN
+F 19 Bind "" "" 0 1 1 '1' 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 4 Sync
+B 4 ParseComplete
+B 4 BindComplete
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
+B 11 DataRow 1 1 '1'
+B 13 CommandComplete "SELECT 1"
+B 5 ReadyForQuery I
+B 4 ParseComplete
+B 4 BindComplete
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
+B 11 DataRow 1 1 '1'
+B 13 CommandComplete "SELECT 1"
+B 5 ReadyForQuery I
+F 4 Terminate
diff --git a/src/test/modules/libpq_pipeline/traces/nosync.trace b/src/test/modules/libpq_pipeline/traces/nosync.trace
new file mode 100644
index 0000000..d99aac6
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/traces/nosync.trace
@@ -0,0 +1,92 @@
+F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 4 Flush
+B 4 ParseComplete
+B 4 BindComplete
+B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
+B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
+B 13 CommandComplete "SELECT 1"
+B 4 ParseComplete
+B 4 BindComplete
+B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
+B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
+B 13 CommandComplete "SELECT 1"
+B 4 ParseComplete
+B 4 BindComplete
+B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
+B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
+B 13 CommandComplete "SELECT 1"
+B 4 ParseComplete
+B 4 BindComplete
+B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
+B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
+B 13 CommandComplete "SELECT 1"
+B 4 ParseComplete
+B 4 BindComplete
+B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
+B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
+B 13 CommandComplete "SELECT 1"
+B 4 ParseComplete
+B 4 BindComplete
+B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
+B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
+B 13 CommandComplete "SELECT 1"
+B 4 ParseComplete
+B 4 BindComplete
+B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
+B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
+B 13 CommandComplete "SELECT 1"
+B 4 ParseComplete
+B 4 BindComplete
+B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
+B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
+B 13 CommandComplete "SELECT 1"
+B 4 ParseComplete
+B 4 BindComplete
+B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
+B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
+B 13 CommandComplete "SELECT 1"
+B 4 ParseComplete
+B 4 BindComplete
+B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
+B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
+B 13 CommandComplete "SELECT 1"
+F 4 Terminate
diff --git a/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace b/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace
new file mode 100644
index 0000000..3fce548
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace
@@ -0,0 +1,64 @@
+F 42 Query "DROP TABLE IF EXISTS pq_pipeline_demo"
+B NN NoticeResponse S "NOTICE" V "NOTICE" C "00000" M "table "pq_pipeline_demo" does not exist, skipping" F "SSSS" L "SSSS" R "SSSS" \x00
+B 15 CommandComplete "DROP TABLE"
+B 5 ReadyForQuery I
+F 99 Query "CREATE UNLOGGED TABLE pq_pipeline_demo(id serial primary key, itemno integer,int8filler int8);"
+B 17 CommandComplete "CREATE TABLE"
+B 5 ReadyForQuery I
+F 60 Parse "" "INSERT INTO pq_pipeline_demo(itemno) VALUES ($1)" 1 NNNN
+F 19 Bind "" "" 0 1 1 '1' 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 39 Parse "" "SELECT no_such_function($1)" 1 NNNN
+F 19 Bind "" "" 0 1 1 '1' 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 60 Parse "" "INSERT INTO pq_pipeline_demo(itemno) VALUES ($1)" 1 NNNN
+F 19 Bind "" "" 0 1 1 '2' 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 4 Sync
+F 60 Parse "" "INSERT INTO pq_pipeline_demo(itemno) VALUES ($1)" 1 NNNN
+F 19 Bind "" "" 0 1 1 '3' 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 4 Sync
+B 4 ParseComplete
+B 4 BindComplete
+B 4 NoData
+B 15 CommandComplete "INSERT 0 1"
+B NN ErrorResponse S "ERROR" V "ERROR" C "42883" M "function no_such_function(integer) does not exist" H "No function matches the given name and argument types. You might need to add explicit type casts." P "8" F "SSSS" L "SSSS" R "SSSS" \x00
+B 5 ReadyForQuery I
+B 4 ParseComplete
+B 4 BindComplete
+B 4 NoData
+B 15 CommandComplete "INSERT 0 1"
+B 5 ReadyForQuery I
+F 26 Parse "" "SELECT 1; SELECT 2" 0
+F 12 Bind "" "" 0 0 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 6 Close P ""
+F 4 Sync
+B NN ErrorResponse S "ERROR" V "ERROR" C "42601" M "cannot insert multiple commands into a prepared statement" F "SSSS" L "SSSS" R "SSSS" \x00
+B 5 ReadyForQuery I
+F 54 Parse "" "SELECT 1.0/g FROM generate_series(3, -1, -1) g" 0
+F 12 Bind "" "" 0 0 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 6 Close P ""
+F 4 Sync
+B 4 ParseComplete
+B 4 BindComplete
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 65535 -1 0
+B 32 DataRow 1 22 '0.33333333333333333333'
+B 32 DataRow 1 22 '0.50000000000000000000'
+B 32 DataRow 1 22 '1.00000000000000000000'
+B NN ErrorResponse S "ERROR" V "ERROR" C "22012" M "division by zero" F "SSSS" L "SSSS" R "SSSS" \x00
+B 5 ReadyForQuery I
+F 40 Query "SELECT itemno FROM pq_pipeline_demo"
+B 31 RowDescription 1 "itemno" NNNN 2 NNNN 4 -1 0
+B 11 DataRow 1 1 '3'
+B 13 CommandComplete "SELECT 1"
+B 5 ReadyForQuery I
+F 4 Terminate
diff --git a/src/test/modules/libpq_pipeline/traces/pipeline_idle.trace b/src/test/modules/libpq_pipeline/traces/pipeline_idle.trace
new file mode 100644
index 0000000..3957ee4
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/traces/pipeline_idle.trace
@@ -0,0 +1,93 @@
+F 16 Parse "" "SELECT 1" 0
+F 12 Bind "" "" 0 0 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 6 Close P ""
+F 4 Flush
+B 4 ParseComplete
+B 4 BindComplete
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
+B 11 DataRow 1 1 '1'
+B 13 CommandComplete "SELECT 1"
+B 4 CloseComplete
+F 4 Sync
+B 5 ReadyForQuery I
+F 16 Parse "" "SELECT 1" 0
+F 12 Bind "" "" 0 0 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 6 Close P ""
+F 4 Flush
+B 4 ParseComplete
+B 4 BindComplete
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
+B 11 DataRow 1 1 '1'
+B 13 CommandComplete "SELECT 1"
+B 4 CloseComplete
+F 13 Query "SELECT 2"
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
+B 11 DataRow 1 1 '2'
+B 13 CommandComplete "SELECT 1"
+B 5 ReadyForQuery I
+F 16 Parse "" "SELECT 1" 0
+F 12 Bind "" "" 0 0 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 6 Close P ""
+F 4 Flush
+B 4 ParseComplete
+B 4 BindComplete
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
+B 11 DataRow 1 1 '1'
+B 13 CommandComplete "SELECT 1"
+B 4 CloseComplete
+F 16 Parse "" "SELECT 2" 0
+F 12 Bind "" "" 0 0 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 6 Close P ""
+F 4 Flush
+B 4 ParseComplete
+B 4 BindComplete
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
+B 11 DataRow 1 1 '2'
+B 13 CommandComplete "SELECT 1"
+B 4 CloseComplete
+F 16 Parse "" "SELECT 1" 0
+F 12 Bind "" "" 0 0 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 6 Close P ""
+F 4 Flush
+B 4 ParseComplete
+B 4 BindComplete
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
+B 11 DataRow 1 1 '1'
+B 13 CommandComplete "SELECT 1"
+B 4 CloseComplete
+F 16 Parse "" "SELECT 2" 0
+F 12 Bind "" "" 0 0 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 6 Close P ""
+F 4 Flush
+B 4 ParseComplete
+B 4 BindComplete
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
+B 11 DataRow 1 1 '2'
+B 13 CommandComplete "SELECT 1"
+B 4 CloseComplete
+F 49 Parse "" "SELECT pg_catalog.pg_advisory_unlock(1,1)" 0
+F 12 Bind "" "" 0 0 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 6 Close P ""
+F 4 Flush
+B 4 ParseComplete
+B 4 BindComplete
+B 43 RowDescription 1 "pg_advisory_unlock" NNNN 0 NNNN 1 -1 0
+B NN NoticeResponse S "WARNING" V "WARNING" C "01000" M "you don't own a lock of type ExclusiveLock" F "SSSS" L "SSSS" R "SSSS" \x00
+B 11 DataRow 1 1 'f'
+B 13 CommandComplete "SELECT 1"
+B 4 CloseComplete
+F 4 Terminate
diff --git a/src/test/modules/libpq_pipeline/traces/prepared.trace b/src/test/modules/libpq_pipeline/traces/prepared.trace
new file mode 100644
index 0000000..1a7de5c
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/traces/prepared.trace
@@ -0,0 +1,18 @@
+F 68 Parse "select_one" "SELECT $1, '42', $1::numeric, interval '1 sec'" 1 NNNN
+F 16 Describe S "select_one"
+F 4 Sync
+B 4 ParseComplete
+B 10 ParameterDescription 1 NNNN
+B 113 RowDescription 4 "?column?" NNNN 0 NNNN 4 -1 0 "?column?" NNNN 0 NNNN 65535 -1 0 "numeric" NNNN 0 NNNN 65535 -1 0 "interval" NNNN 0 NNNN 16 -1 0
+B 5 ReadyForQuery I
+F 10 Query "BEGIN"
+B 10 CommandComplete "BEGIN"
+B 5 ReadyForQuery T
+F 43 Query "DECLARE cursor_one CURSOR FOR SELECT 1"
+B 19 CommandComplete "DECLARE CURSOR"
+B 5 ReadyForQuery T
+F 16 Describe P "cursor_one"
+F 4 Sync
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
+B 5 ReadyForQuery T
+F 4 Terminate
diff --git a/src/test/modules/libpq_pipeline/traces/simple_pipeline.trace b/src/test/modules/libpq_pipeline/traces/simple_pipeline.trace
new file mode 100644
index 0000000..5c94749
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/traces/simple_pipeline.trace
@@ -0,0 +1,12 @@
+F 21 Parse "" "SELECT $1" 1 NNNN
+F 19 Bind "" "" 0 1 1 '1' 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 4 Sync
+B 4 ParseComplete
+B 4 BindComplete
+B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
+B 11 DataRow 1 1 '1'
+B 13 CommandComplete "SELECT 1"
+B 5 ReadyForQuery I
+F 4 Terminate
diff --git a/src/test/modules/libpq_pipeline/traces/singlerow.trace b/src/test/modules/libpq_pipeline/traces/singlerow.trace
new file mode 100644
index 0000000..9de99be
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/traces/singlerow.trace
@@ -0,0 +1,39 @@
+F 38 Parse "" "SELECT generate_series(42, $1)" 0
+F 20 Bind "" "" 0 1 2 '44' 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 38 Parse "" "SELECT generate_series(42, $1)" 0
+F 20 Bind "" "" 0 1 2 '45' 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 38 Parse "" "SELECT generate_series(42, $1)" 0
+F 20 Bind "" "" 0 1 2 '46' 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 4 Sync
+B 4 ParseComplete
+B 4 BindComplete
+B 40 RowDescription 1 "generate_series" NNNN 0 NNNN 4 -1 0
+B 12 DataRow 1 2 '42'
+B 12 DataRow 1 2 '43'
+B 12 DataRow 1 2 '44'
+B 13 CommandComplete "SELECT 3"
+B 4 ParseComplete
+B 4 BindComplete
+B 40 RowDescription 1 "generate_series" NNNN 0 NNNN 4 -1 0
+B 12 DataRow 1 2 '42'
+B 12 DataRow 1 2 '43'
+B 12 DataRow 1 2 '44'
+B 12 DataRow 1 2 '45'
+B 13 CommandComplete "SELECT 4"
+B 4 ParseComplete
+B 4 BindComplete
+B 40 RowDescription 1 "generate_series" NNNN 0 NNNN 4 -1 0
+B 12 DataRow 1 2 '42'
+B 12 DataRow 1 2 '43'
+B 12 DataRow 1 2 '44'
+B 12 DataRow 1 2 '45'
+B 12 DataRow 1 2 '46'
+B 13 CommandComplete "SELECT 5"
+B 5 ReadyForQuery I
+F 4 Terminate
diff --git a/src/test/modules/libpq_pipeline/traces/transaction.trace b/src/test/modules/libpq_pipeline/traces/transaction.trace
new file mode 100644
index 0000000..1dcc237
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/traces/transaction.trace
@@ -0,0 +1,61 @@
+F 79 Query "DROP TABLE IF EXISTS pq_pipeline_tst;CREATE TABLE pq_pipeline_tst (id int)"
+B NN NoticeResponse S "NOTICE" V "NOTICE" C "00000" M "table "pq_pipeline_tst" does not exist, skipping" F "SSSS" L "SSSS" R "SSSS" \x00
+B 15 CommandComplete "DROP TABLE"
+B 17 CommandComplete "CREATE TABLE"
+B 5 ReadyForQuery I
+F 24 Parse "rollback" "ROLLBACK" 0
+F 13 Parse "" "BEGIN" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 18 Parse "" "SELECT 0/0" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 22 Bind "" "rollback" 0 0 1 1
+F 6 Describe P ""
+F 9 Execute "" 0
+F 46 Parse "" "INSERT INTO pq_pipeline_tst VALUES (1)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 4 Sync
+F 46 Parse "" "INSERT INTO pq_pipeline_tst VALUES (2)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 4 Sync
+F 22 Bind "" "rollback" 0 0 1 1
+F 6 Describe P ""
+F 9 Execute "" 0
+F 46 Parse "" "INSERT INTO pq_pipeline_tst VALUES (3)" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 4 Sync
+F 4 Sync
+B 4 ParseComplete
+B 4 ParseComplete
+B 4 BindComplete
+B 4 NoData
+B 10 CommandComplete "BEGIN"
+B 4 ParseComplete
+B NN ErrorResponse S "ERROR" V "ERROR" C "22012" M "division by zero" F "SSSS" L "SSSS" R "SSSS" \x00
+B 5 ReadyForQuery E
+B NN ErrorResponse S "ERROR" V "ERROR" C "25P02" M "current transaction is aborted, commands ignored until end of transaction block" F "SSSS" L "SSSS" R "SSSS" \x00
+B 5 ReadyForQuery E
+B 4 BindComplete
+B 4 NoData
+B 13 CommandComplete "ROLLBACK"
+B 4 ParseComplete
+B 4 BindComplete
+B 4 NoData
+B 15 CommandComplete "INSERT 0 1"
+B 5 ReadyForQuery I
+B 5 ReadyForQuery I
+F 34 Query "SELECT * FROM pq_pipeline_tst"
+B 27 RowDescription 1 "id" NNNN 1 NNNN 4 -1 0
+B 11 DataRow 1 1 '3'
+B 13 CommandComplete "SELECT 1"
+B 5 ReadyForQuery I
+F 4 Terminate
diff --git a/src/test/modules/plsample/.gitignore b/src/test/modules/plsample/.gitignore
new file mode 100644
index 0000000..44d119c
--- /dev/null
+++ b/src/test/modules/plsample/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/results/
diff --git a/src/test/modules/plsample/Makefile b/src/test/modules/plsample/Makefile
new file mode 100644
index 0000000..f1bc334
--- /dev/null
+++ b/src/test/modules/plsample/Makefile
@@ -0,0 +1,20 @@
+# src/test/modules/plsample/Makefile
+
+MODULES = plsample
+
+EXTENSION = plsample
+DATA = plsample--1.0.sql
+PGFILEDESC = "PL/Sample - template for procedural language"
+
+REGRESS = plsample
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/plsample
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/plsample/README b/src/test/modules/plsample/README
new file mode 100644
index 0000000..0ed3193
--- /dev/null
+++ b/src/test/modules/plsample/README
@@ -0,0 +1,6 @@
+PL/Sample
+=========
+
+PL/Sample is an example template of procedural-language handler. It is
+a simple implementation, yet demonstrates some of the things that can be done
+to build a fully functional procedural-language handler.
diff --git a/src/test/modules/plsample/expected/plsample.out b/src/test/modules/plsample/expected/plsample.out
new file mode 100644
index 0000000..a0c318b
--- /dev/null
+++ b/src/test/modules/plsample/expected/plsample.out
@@ -0,0 +1,36 @@
+CREATE EXTENSION plsample;
+-- Create and test some dummy functions
+CREATE FUNCTION plsample_result_text(a1 numeric, a2 text, a3 integer[])
+RETURNS TEXT
+AS $$
+ Example of source with text result.
+$$ LANGUAGE plsample;
+SELECT plsample_result_text(1.23, 'abc', '{4, 5, 6}');
+NOTICE: source text of function "plsample_result_text":
+ Example of source with text result.
+
+NOTICE: argument: 0; name: a1; value: 1.23
+NOTICE: argument: 1; name: a2; value: abc
+NOTICE: argument: 2; name: a3; value: {4,5,6}
+ plsample_result_text
+---------------------------------------
+ +
+ Example of source with text result.+
+
+(1 row)
+
+CREATE FUNCTION plsample_result_void(a1 text[])
+RETURNS VOID
+AS $$
+ Example of source with void result.
+$$ LANGUAGE plsample;
+SELECT plsample_result_void('{foo, bar, hoge}');
+NOTICE: source text of function "plsample_result_void":
+ Example of source with void result.
+
+NOTICE: argument: 0; name: a1; value: {foo,bar,hoge}
+ plsample_result_void
+----------------------
+
+(1 row)
+
diff --git a/src/test/modules/plsample/plsample--1.0.sql b/src/test/modules/plsample/plsample--1.0.sql
new file mode 100644
index 0000000..fc5b280
--- /dev/null
+++ b/src/test/modules/plsample/plsample--1.0.sql
@@ -0,0 +1,14 @@
+/* src/test/modules/plsample/plsample--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION plsample" to load this file. \quit
+
+CREATE FUNCTION plsample_call_handler() RETURNS language_handler
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE TRUSTED LANGUAGE plsample
+ HANDLER plsample_call_handler;
+
+ALTER LANGUAGE plsample OWNER TO @extowner@;
+
+COMMENT ON LANGUAGE plsample IS 'PL/Sample procedural language';
diff --git a/src/test/modules/plsample/plsample.c b/src/test/modules/plsample/plsample.c
new file mode 100644
index 0000000..6fc33c7
--- /dev/null
+++ b/src/test/modules/plsample/plsample.c
@@ -0,0 +1,187 @@
+/*-------------------------------------------------------------------------
+ *
+ * plsample.c
+ * Handler for the PL/Sample procedural language
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/test/modules/plsample/plsample.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/event_trigger.h"
+#include "commands/trigger.h"
+#include "funcapi.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(plsample_call_handler);
+
+static Datum plsample_func_handler(PG_FUNCTION_ARGS);
+
+/*
+ * Handle function, procedure, and trigger calls.
+ */
+Datum
+plsample_call_handler(PG_FUNCTION_ARGS)
+{
+ Datum retval = (Datum) 0;
+
+ PG_TRY();
+ {
+ /*
+ * Determine if called as function or trigger and call appropriate
+ * subhandler.
+ */
+ if (CALLED_AS_TRIGGER(fcinfo))
+ {
+ /*
+ * This function has been called as a trigger function, where
+ * (TriggerData *) fcinfo->context includes the information of the
+ * context.
+ */
+ }
+ else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+ {
+ /*
+ * This function is called as an event trigger function, where
+ * (EventTriggerData *) fcinfo->context includes the information
+ * of the context.
+ */
+ }
+ else
+ {
+ /* Regular function handler */
+ retval = plsample_func_handler(fcinfo);
+ }
+ }
+ PG_FINALLY();
+ {
+ }
+ PG_END_TRY();
+
+ return retval;
+}
+
+/*
+ * plsample_func_handler
+ *
+ * Function called by the call handler for function execution.
+ */
+static Datum
+plsample_func_handler(PG_FUNCTION_ARGS)
+{
+ HeapTuple pl_tuple;
+ Datum ret;
+ char *source;
+ bool isnull;
+ FmgrInfo *arg_out_func;
+ Form_pg_type type_struct;
+ HeapTuple type_tuple;
+ Form_pg_proc pl_struct;
+ volatile MemoryContext proc_cxt = NULL;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ char *proname;
+ Form_pg_type pg_type_entry;
+ Oid result_typioparam;
+ Oid prorettype;
+ FmgrInfo result_in_func;
+ int numargs;
+
+ /* Fetch the source text of the function. */
+ pl_tuple = SearchSysCache(PROCOID,
+ ObjectIdGetDatum(fcinfo->flinfo->fn_oid), 0, 0, 0);
+ if (!HeapTupleIsValid(pl_tuple))
+ elog(ERROR, "cache lookup failed for function %u",
+ fcinfo->flinfo->fn_oid);
+
+ /*
+ * Extract and print the source text of the function. This can be used as
+ * a base for the function validation and execution.
+ */
+ pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
+ proname = pstrdup(NameStr(pl_struct->proname));
+ ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ elog(ERROR, "could not find source text of function \"%s\"",
+ proname);
+ source = DatumGetCString(DirectFunctionCall1(textout, ret));
+ ereport(NOTICE,
+ (errmsg("source text of function \"%s\": %s",
+ proname, source)));
+
+ /*
+ * Allocate a context that will hold all the Postgres data for the
+ * procedure.
+ */
+ proc_cxt = AllocSetContextCreate(TopMemoryContext,
+ "PL/Sample function",
+ ALLOCSET_SMALL_SIZES);
+
+ arg_out_func = (FmgrInfo *) palloc0(fcinfo->nargs * sizeof(FmgrInfo));
+ numargs = get_func_arg_info(pl_tuple, &argtypes, &argnames, &argmodes);
+
+ /*
+ * Iterate through all of the function arguments, printing each input
+ * value.
+ */
+ for (int i = 0; i < numargs; i++)
+ {
+ Oid argtype = pl_struct->proargtypes.values[i];
+ char *value;
+
+ type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));
+ if (!HeapTupleIsValid(type_tuple))
+ elog(ERROR, "cache lookup failed for type %u", argtype);
+
+ type_struct = (Form_pg_type) GETSTRUCT(type_tuple);
+ fmgr_info_cxt(type_struct->typoutput, &(arg_out_func[i]), proc_cxt);
+ ReleaseSysCache(type_tuple);
+
+ value = OutputFunctionCall(&arg_out_func[i], fcinfo->args[i].value);
+ ereport(NOTICE,
+ (errmsg("argument: %d; name: %s; value: %s",
+ i, argnames[i], value)));
+ }
+
+ /* Type of the result */
+ prorettype = pl_struct->prorettype;
+ ReleaseSysCache(pl_tuple);
+
+ /*
+ * Get the required information for input conversion of the return value.
+ *
+ * If the function uses VOID as result, it is better to return NULL.
+ * Anyway, let's be honest. This is just a template, so there is not much
+ * we can do here. This returns NULL except if the result type is text,
+ * where the result is the source text of the function.
+ */
+ if (prorettype != TEXTOID)
+ PG_RETURN_NULL();
+
+ type_tuple = SearchSysCache1(TYPEOID,
+ ObjectIdGetDatum(prorettype));
+ if (!HeapTupleIsValid(type_tuple))
+ elog(ERROR, "cache lookup failed for type %u", prorettype);
+ pg_type_entry = (Form_pg_type) GETSTRUCT(type_tuple);
+ result_typioparam = getTypeIOParam(type_tuple);
+
+ fmgr_info_cxt(pg_type_entry->typinput, &result_in_func, proc_cxt);
+ ReleaseSysCache(type_tuple);
+
+ ret = InputFunctionCall(&result_in_func, source, result_typioparam, -1);
+ PG_RETURN_DATUM(ret);
+}
diff --git a/src/test/modules/plsample/plsample.control b/src/test/modules/plsample/plsample.control
new file mode 100644
index 0000000..1e67251
--- /dev/null
+++ b/src/test/modules/plsample/plsample.control
@@ -0,0 +1,8 @@
+# plsample extension
+comment = 'PL/Sample'
+default_version = '1.0'
+module_pathname = '$libdir/plsample'
+relocatable = false
+schema = pg_catalog
+superuser = false
+trusted = true
diff --git a/src/test/modules/plsample/sql/plsample.sql b/src/test/modules/plsample/sql/plsample.sql
new file mode 100644
index 0000000..bf0fdda
--- /dev/null
+++ b/src/test/modules/plsample/sql/plsample.sql
@@ -0,0 +1,15 @@
+CREATE EXTENSION plsample;
+-- Create and test some dummy functions
+CREATE FUNCTION plsample_result_text(a1 numeric, a2 text, a3 integer[])
+RETURNS TEXT
+AS $$
+ Example of source with text result.
+$$ LANGUAGE plsample;
+SELECT plsample_result_text(1.23, 'abc', '{4, 5, 6}');
+
+CREATE FUNCTION plsample_result_void(a1 text[])
+RETURNS VOID
+AS $$
+ Example of source with void result.
+$$ LANGUAGE plsample;
+SELECT plsample_result_void('{foo, bar, hoge}');
diff --git a/src/test/modules/snapshot_too_old/.gitignore b/src/test/modules/snapshot_too_old/.gitignore
new file mode 100644
index 0000000..ba2160b
--- /dev/null
+++ b/src/test/modules/snapshot_too_old/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/output_iso/
+/tmp_check_iso/
diff --git a/src/test/modules/snapshot_too_old/Makefile b/src/test/modules/snapshot_too_old/Makefile
new file mode 100644
index 0000000..dfb4537
--- /dev/null
+++ b/src/test/modules/snapshot_too_old/Makefile
@@ -0,0 +1,28 @@
+# src/test/modules/snapshot_too_old/Makefile
+
+# Note: because we don't tell the Makefile there are any regression tests,
+# we have to clean those result files explicitly
+EXTRA_CLEAN = $(pg_regress_clean_files)
+
+ISOLATION = sto_using_cursor sto_using_select sto_using_hash_index
+ISOLATION_OPTS = --temp-config $(top_srcdir)/src/test/modules/snapshot_too_old/sto.conf
+
+# Disabled because these tests require "old_snapshot_threshold" >= 0, which
+# typical installcheck users do not have (e.g. buildfarm clients).
+NO_INSTALLCHECK = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/snapshot_too_old
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+# But it can nonetheless be very helpful to run tests on preexisting
+# installation, allow to do so, but only if requested explicitly.
+installcheck-force:
+ $(pg_isolation_regress_installcheck) $(ISOLATION)
diff --git a/src/test/modules/snapshot_too_old/expected/sto_using_cursor.out b/src/test/modules/snapshot_too_old/expected/sto_using_cursor.out
new file mode 100644
index 0000000..06fe4d0
--- /dev/null
+++ b/src/test/modules/snapshot_too_old/expected/sto_using_cursor.out
@@ -0,0 +1,19 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1decl s1f1 s1sleep s2u s1f2
+step s1decl: DECLARE cursor1 CURSOR FOR SELECT c FROM sto1;
+step s1f1: FETCH FIRST FROM cursor1;
+c
+-
+1
+(1 row)
+
+step s1sleep: SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold';
+setting|pg_sleep
+-------+--------
+ 0|
+(1 row)
+
+step s2u: UPDATE sto1 SET c = 1001 WHERE c = 1;
+step s1f2: FETCH FIRST FROM cursor1;
+ERROR: snapshot too old
diff --git a/src/test/modules/snapshot_too_old/expected/sto_using_hash_index.out b/src/test/modules/snapshot_too_old/expected/sto_using_hash_index.out
new file mode 100644
index 0000000..11f827f
--- /dev/null
+++ b/src/test/modules/snapshot_too_old/expected/sto_using_hash_index.out
@@ -0,0 +1,19 @@
+Parsed test spec with 2 sessions
+
+starting permutation: noseq s1f1 s2sleep s2u s1f2
+step noseq: SET enable_seqscan = false;
+step s1f1: SELECT c FROM sto1 where c = 1000;
+ c
+----
+1000
+(1 row)
+
+step s2sleep: SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold';
+setting|pg_sleep
+-------+--------
+ 0|
+(1 row)
+
+step s2u: UPDATE sto1 SET c = 1001 WHERE c = 1000;
+step s1f2: SELECT c FROM sto1 where c = 1001;
+ERROR: snapshot too old
diff --git a/src/test/modules/snapshot_too_old/expected/sto_using_select.out b/src/test/modules/snapshot_too_old/expected/sto_using_select.out
new file mode 100644
index 0000000..e910e5c
--- /dev/null
+++ b/src/test/modules/snapshot_too_old/expected/sto_using_select.out
@@ -0,0 +1,18 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1f1 s1sleep s2u s1f2
+step s1f1: SELECT c FROM sto1 ORDER BY c LIMIT 1;
+c
+-
+1
+(1 row)
+
+step s1sleep: SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold';
+setting|pg_sleep
+-------+--------
+ 0|
+(1 row)
+
+step s2u: UPDATE sto1 SET c = 1001 WHERE c = 1;
+step s1f2: SELECT c FROM sto1 ORDER BY c LIMIT 1;
+ERROR: snapshot too old
diff --git a/src/test/modules/snapshot_too_old/specs/sto_using_cursor.spec b/src/test/modules/snapshot_too_old/specs/sto_using_cursor.spec
new file mode 100644
index 0000000..f3677a8
--- /dev/null
+++ b/src/test/modules/snapshot_too_old/specs/sto_using_cursor.spec
@@ -0,0 +1,38 @@
+# This test provokes a "snapshot too old" error using a cursor.
+#
+# The sleep is needed because with a threshold of zero a statement could error
+# on changes it made. With more normal settings no external delay is needed,
+# but we don't want these tests to run long enough to see that, since
+# granularity is in minutes.
+#
+# Since results depend on the value of old_snapshot_threshold, sneak that into
+# the line generated by the sleep, so that a surprising value isn't so hard
+# to identify.
+
+setup
+{
+ CREATE TABLE sto1 (c int NOT NULL);
+ INSERT INTO sto1 SELECT generate_series(1, 1000);
+}
+setup
+{
+ VACUUM ANALYZE sto1;
+}
+
+teardown
+{
+ DROP TABLE sto1;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step "s1decl" { DECLARE cursor1 CURSOR FOR SELECT c FROM sto1; }
+step "s1f1" { FETCH FIRST FROM cursor1; }
+step "s1sleep" { SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold'; }
+step "s1f2" { FETCH FIRST FROM cursor1; }
+teardown { COMMIT; }
+
+session "s2"
+step "s2u" { UPDATE sto1 SET c = 1001 WHERE c = 1; }
+
+permutation "s1decl" "s1f1" "s1sleep" "s2u" "s1f2"
diff --git a/src/test/modules/snapshot_too_old/specs/sto_using_hash_index.spec b/src/test/modules/snapshot_too_old/specs/sto_using_hash_index.spec
new file mode 100644
index 0000000..33d91ff
--- /dev/null
+++ b/src/test/modules/snapshot_too_old/specs/sto_using_hash_index.spec
@@ -0,0 +1,31 @@
+# This test is like sto_using_select, except that we test access via a
+# hash index.
+
+setup
+{
+ CREATE TABLE sto1 (c int NOT NULL);
+ INSERT INTO sto1 SELECT generate_series(1, 1000);
+ CREATE INDEX idx_sto1 ON sto1 USING HASH (c);
+}
+setup
+{
+ VACUUM ANALYZE sto1;
+}
+
+teardown
+{
+ DROP TABLE sto1;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step "noseq" { SET enable_seqscan = false; }
+step "s1f1" { SELECT c FROM sto1 where c = 1000; }
+step "s1f2" { SELECT c FROM sto1 where c = 1001; }
+teardown { ROLLBACK; }
+
+session "s2"
+step "s2sleep" { SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold'; }
+step "s2u" { UPDATE sto1 SET c = 1001 WHERE c = 1000; }
+
+permutation "noseq" "s1f1" "s2sleep" "s2u" "s1f2"
diff --git a/src/test/modules/snapshot_too_old/specs/sto_using_select.spec b/src/test/modules/snapshot_too_old/specs/sto_using_select.spec
new file mode 100644
index 0000000..80a3176
--- /dev/null
+++ b/src/test/modules/snapshot_too_old/specs/sto_using_select.spec
@@ -0,0 +1,37 @@
+# This test provokes a "snapshot too old" error using SELECT statements.
+#
+# The sleep is needed because with a threshold of zero a statement could error
+# on changes it made. With more normal settings no external delay is needed,
+# but we don't want these tests to run long enough to see that, since
+# granularity is in minutes.
+#
+# Since results depend on the value of old_snapshot_threshold, sneak that into
+# the line generated by the sleep, so that a surprising value isn't so hard
+# to identify.
+
+setup
+{
+ CREATE TABLE sto1 (c int NOT NULL);
+ INSERT INTO sto1 SELECT generate_series(1, 1000);
+}
+setup
+{
+ VACUUM ANALYZE sto1;
+}
+
+teardown
+{
+ DROP TABLE sto1;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL REPEATABLE READ; }
+step "s1f1" { SELECT c FROM sto1 ORDER BY c LIMIT 1; }
+step "s1sleep" { SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold'; }
+step "s1f2" { SELECT c FROM sto1 ORDER BY c LIMIT 1; }
+teardown { COMMIT; }
+
+session "s2"
+step "s2u" { UPDATE sto1 SET c = 1001 WHERE c = 1; }
+
+permutation "s1f1" "s1sleep" "s2u" "s1f2"
diff --git a/src/test/modules/snapshot_too_old/sto.conf b/src/test/modules/snapshot_too_old/sto.conf
new file mode 100644
index 0000000..7eeaeeb
--- /dev/null
+++ b/src/test/modules/snapshot_too_old/sto.conf
@@ -0,0 +1,2 @@
+autovacuum = off
+old_snapshot_threshold = 0
diff --git a/src/test/modules/spgist_name_ops/.gitignore b/src/test/modules/spgist_name_ops/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/spgist_name_ops/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/spgist_name_ops/Makefile b/src/test/modules/spgist_name_ops/Makefile
new file mode 100644
index 0000000..05b2464
--- /dev/null
+++ b/src/test/modules/spgist_name_ops/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/spgist_name_ops/Makefile
+
+MODULE_big = spgist_name_ops
+OBJS = \
+ $(WIN32RES) \
+ spgist_name_ops.o
+PGFILEDESC = "spgist_name_ops - test opclass for SP-GiST"
+
+EXTENSION = spgist_name_ops
+DATA = spgist_name_ops--1.0.sql
+
+REGRESS = spgist_name_ops
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/spgist_name_ops
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/spgist_name_ops/README b/src/test/modules/spgist_name_ops/README
new file mode 100644
index 0000000..119208b
--- /dev/null
+++ b/src/test/modules/spgist_name_ops/README
@@ -0,0 +1,8 @@
+spgist_name_ops implements an SP-GiST operator class that indexes
+columns of type "name", but with storage identical to that used
+by SP-GiST text_ops.
+
+This is not terribly useful in itself, perhaps, but it allows
+testing cases where the indexed data type is different from the leaf
+data type and yet we can reconstruct the original indexed value.
+That situation is not tested by any built-in SP-GiST opclass.
diff --git a/src/test/modules/spgist_name_ops/expected/spgist_name_ops.out b/src/test/modules/spgist_name_ops/expected/spgist_name_ops.out
new file mode 100644
index 0000000..ac0ddce
--- /dev/null
+++ b/src/test/modules/spgist_name_ops/expected/spgist_name_ops.out
@@ -0,0 +1,112 @@
+create extension spgist_name_ops;
+select opcname, amvalidate(opc.oid)
+from pg_opclass opc join pg_am am on am.oid = opcmethod
+where amname = 'spgist' and opcname = 'name_ops';
+ opcname | amvalidate
+----------+------------
+ name_ops | t
+(1 row)
+
+-- warning expected here
+select opcname, amvalidate(opc.oid)
+from pg_opclass opc join pg_am am on am.oid = opcmethod
+where amname = 'spgist' and opcname = 'name_ops_old';
+INFO: SP-GiST leaf data type text does not match declared type name
+ opcname | amvalidate
+--------------+------------
+ name_ops_old | f
+(1 row)
+
+create table t(f1 name, f2 integer, f3 text);
+create index on t using spgist(f1) include(f2, f3);
+\d+ t_f1_f2_f3_idx
+ Index "public.t_f1_f2_f3_idx"
+ Column | Type | Key? | Definition | Storage | Stats target
+--------+---------+------+------------+----------+--------------
+ f1 | text | yes | f1 | extended |
+ f2 | integer | no | f2 | plain |
+ f3 | text | no | f3 | extended |
+spgist, for table "public.t"
+
+insert into t select
+ proname,
+ case when length(proname) % 2 = 0 then pronargs else null end,
+ prosrc from pg_proc;
+vacuum analyze t;
+explain (costs off)
+select * from t
+ where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p'
+ order by 1;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: f1
+ -> Index Only Scan using t_f1_f2_f3_idx on t
+ Index Cond: ((f1 > 'binary_upgrade_set_n'::name) AND (f1 < 'binary_upgrade_set_p'::name))
+(4 rows)
+
+select * from t
+ where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p'
+ order by 1;
+ f1 | f2 | f3
+------------------------------------------------------+----+------------------------------------------------------
+ binary_upgrade_set_next_array_pg_type_oid | | binary_upgrade_set_next_array_pg_type_oid
+ binary_upgrade_set_next_heap_pg_class_oid | | binary_upgrade_set_next_heap_pg_class_oid
+ binary_upgrade_set_next_index_pg_class_oid | 1 | binary_upgrade_set_next_index_pg_class_oid
+ binary_upgrade_set_next_multirange_array_pg_type_oid | 1 | binary_upgrade_set_next_multirange_array_pg_type_oid
+ binary_upgrade_set_next_multirange_pg_type_oid | 1 | binary_upgrade_set_next_multirange_pg_type_oid
+ binary_upgrade_set_next_pg_authid_oid | | binary_upgrade_set_next_pg_authid_oid
+ binary_upgrade_set_next_pg_enum_oid | | binary_upgrade_set_next_pg_enum_oid
+ binary_upgrade_set_next_pg_type_oid | | binary_upgrade_set_next_pg_type_oid
+ binary_upgrade_set_next_toast_pg_class_oid | 1 | binary_upgrade_set_next_toast_pg_class_oid
+(9 rows)
+
+-- Verify clean failure when INCLUDE'd columns result in overlength tuple
+-- The error message details are platform-dependent, so show only SQLSTATE
+\set VERBOSITY sqlstate
+insert into t values(repeat('xyzzy', 12), 42, repeat('xyzzy', 4000));
+ERROR: 54000
+\set VERBOSITY default
+drop index t_f1_f2_f3_idx;
+create index on t using spgist(f1 name_ops_old) include(f2, f3);
+\d+ t_f1_f2_f3_idx
+ Index "public.t_f1_f2_f3_idx"
+ Column | Type | Key? | Definition | Storage | Stats target
+--------+---------+------+------------+----------+--------------
+ f1 | name | yes | f1 | plain |
+ f2 | integer | no | f2 | plain |
+ f3 | text | no | f3 | extended |
+spgist, for table "public.t"
+
+explain (costs off)
+select * from t
+ where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p'
+ order by 1;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: f1
+ -> Index Only Scan using t_f1_f2_f3_idx on t
+ Index Cond: ((f1 > 'binary_upgrade_set_n'::name) AND (f1 < 'binary_upgrade_set_p'::name))
+(4 rows)
+
+select * from t
+ where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p'
+ order by 1;
+ f1 | f2 | f3
+------------------------------------------------------+----+------------------------------------------------------
+ binary_upgrade_set_next_array_pg_type_oid | | binary_upgrade_set_next_array_pg_type_oid
+ binary_upgrade_set_next_heap_pg_class_oid | | binary_upgrade_set_next_heap_pg_class_oid
+ binary_upgrade_set_next_index_pg_class_oid | 1 | binary_upgrade_set_next_index_pg_class_oid
+ binary_upgrade_set_next_multirange_array_pg_type_oid | 1 | binary_upgrade_set_next_multirange_array_pg_type_oid
+ binary_upgrade_set_next_multirange_pg_type_oid | 1 | binary_upgrade_set_next_multirange_pg_type_oid
+ binary_upgrade_set_next_pg_authid_oid | | binary_upgrade_set_next_pg_authid_oid
+ binary_upgrade_set_next_pg_enum_oid | | binary_upgrade_set_next_pg_enum_oid
+ binary_upgrade_set_next_pg_type_oid | | binary_upgrade_set_next_pg_type_oid
+ binary_upgrade_set_next_toast_pg_class_oid | 1 | binary_upgrade_set_next_toast_pg_class_oid
+(9 rows)
+
+\set VERBOSITY sqlstate
+insert into t values(repeat('xyzzy', 12), 42, repeat('xyzzy', 4000));
+ERROR: 54000
+\set VERBOSITY default
diff --git a/src/test/modules/spgist_name_ops/spgist_name_ops--1.0.sql b/src/test/modules/spgist_name_ops/spgist_name_ops--1.0.sql
new file mode 100644
index 0000000..432216e
--- /dev/null
+++ b/src/test/modules/spgist_name_ops/spgist_name_ops--1.0.sql
@@ -0,0 +1,54 @@
+/* src/test/modules/spgist_name_ops/spgist_name_ops--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION spgist_name_ops" to load this file. \quit
+
+CREATE FUNCTION spgist_name_config(internal, internal)
+RETURNS void IMMUTABLE PARALLEL SAFE STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION spgist_name_choose(internal, internal)
+RETURNS void IMMUTABLE PARALLEL SAFE STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION spgist_name_inner_consistent(internal, internal)
+RETURNS void IMMUTABLE PARALLEL SAFE STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION spgist_name_leaf_consistent(internal, internal)
+RETURNS boolean IMMUTABLE PARALLEL SAFE STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION spgist_name_compress(name)
+RETURNS text IMMUTABLE PARALLEL SAFE STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE OPERATOR CLASS name_ops DEFAULT FOR TYPE name
+USING spgist AS
+ OPERATOR 1 < ,
+ OPERATOR 2 <= ,
+ OPERATOR 3 = ,
+ OPERATOR 4 >= ,
+ OPERATOR 5 > ,
+ FUNCTION 1 spgist_name_config(internal, internal),
+ FUNCTION 2 spgist_name_choose(internal, internal),
+ FUNCTION 3 spg_text_picksplit(internal, internal),
+ FUNCTION 4 spgist_name_inner_consistent(internal, internal),
+ FUNCTION 5 spgist_name_leaf_consistent(internal, internal),
+ FUNCTION 6 spgist_name_compress(name),
+ STORAGE text;
+
+-- Also test old-style where the STORAGE clause is disallowed
+CREATE OPERATOR CLASS name_ops_old FOR TYPE name
+USING spgist AS
+ OPERATOR 1 < ,
+ OPERATOR 2 <= ,
+ OPERATOR 3 = ,
+ OPERATOR 4 >= ,
+ OPERATOR 5 > ,
+ FUNCTION 1 spgist_name_config(internal, internal),
+ FUNCTION 2 spgist_name_choose(internal, internal),
+ FUNCTION 3 spg_text_picksplit(internal, internal),
+ FUNCTION 4 spgist_name_inner_consistent(internal, internal),
+ FUNCTION 5 spgist_name_leaf_consistent(internal, internal),
+ FUNCTION 6 spgist_name_compress(name);
diff --git a/src/test/modules/spgist_name_ops/spgist_name_ops.c b/src/test/modules/spgist_name_ops/spgist_name_ops.c
new file mode 100644
index 0000000..63d3faa
--- /dev/null
+++ b/src/test/modules/spgist_name_ops/spgist_name_ops.c
@@ -0,0 +1,501 @@
+/*--------------------------------------------------------------------------
+ *
+ * spgist_name_ops.c
+ * Test opclass for SP-GiST
+ *
+ * This indexes input values of type "name", but the index storage is "text",
+ * with the same choices as made in the core SP-GiST text_ops opclass.
+ * Much of the code is identical to src/backend/access/spgist/spgtextproc.c,
+ * which see for a more detailed header comment.
+ *
+ * Unlike spgtextproc.c, we don't bother with collation-aware logic.
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/test/modules/spgist_name_ops/spgist_name_ops.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/spgist.h"
+#include "catalog/pg_type.h"
+#include "utils/datum.h"
+
+PG_MODULE_MAGIC;
+
+
+PG_FUNCTION_INFO_V1(spgist_name_config);
+Datum
+spgist_name_config(PG_FUNCTION_ARGS)
+{
+ /* spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0); */
+ spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
+
+ cfg->prefixType = TEXTOID;
+ cfg->labelType = INT2OID;
+ cfg->leafType = TEXTOID;
+ cfg->canReturnData = true;
+ cfg->longValuesOK = true; /* suffixing will shorten long values */
+ PG_RETURN_VOID();
+}
+
+/*
+ * Form a text datum from the given not-necessarily-null-terminated string,
+ * using short varlena header format if possible
+ */
+static Datum
+formTextDatum(const char *data, int datalen)
+{
+ char *p;
+
+ p = (char *) palloc(datalen + VARHDRSZ);
+
+ if (datalen + VARHDRSZ_SHORT <= VARATT_SHORT_MAX)
+ {
+ SET_VARSIZE_SHORT(p, datalen + VARHDRSZ_SHORT);
+ if (datalen)
+ memcpy(p + VARHDRSZ_SHORT, data, datalen);
+ }
+ else
+ {
+ SET_VARSIZE(p, datalen + VARHDRSZ);
+ memcpy(p + VARHDRSZ, data, datalen);
+ }
+
+ return PointerGetDatum(p);
+}
+
+/*
+ * Find the length of the common prefix of a and b
+ */
+static int
+commonPrefix(const char *a, const char *b, int lena, int lenb)
+{
+ int i = 0;
+
+ while (i < lena && i < lenb && *a == *b)
+ {
+ a++;
+ b++;
+ i++;
+ }
+
+ return i;
+}
+
+/*
+ * Binary search an array of int16 datums for a match to c
+ *
+ * On success, *i gets the match location; on failure, it gets where to insert
+ */
+static bool
+searchChar(Datum *nodeLabels, int nNodes, int16 c, int *i)
+{
+ int StopLow = 0,
+ StopHigh = nNodes;
+
+ while (StopLow < StopHigh)
+ {
+ int StopMiddle = (StopLow + StopHigh) >> 1;
+ int16 middle = DatumGetInt16(nodeLabels[StopMiddle]);
+
+ if (c < middle)
+ StopHigh = StopMiddle;
+ else if (c > middle)
+ StopLow = StopMiddle + 1;
+ else
+ {
+ *i = StopMiddle;
+ return true;
+ }
+ }
+
+ *i = StopHigh;
+ return false;
+}
+
+PG_FUNCTION_INFO_V1(spgist_name_choose);
+Datum
+spgist_name_choose(PG_FUNCTION_ARGS)
+{
+ spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0);
+ spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1);
+ Name inName = DatumGetName(in->datum);
+ char *inStr = NameStr(*inName);
+ int inSize = strlen(inStr);
+ char *prefixStr = NULL;
+ int prefixSize = 0;
+ int commonLen = 0;
+ int16 nodeChar = 0;
+ int i = 0;
+
+ /* Check for prefix match, set nodeChar to first byte after prefix */
+ if (in->hasPrefix)
+ {
+ text *prefixText = DatumGetTextPP(in->prefixDatum);
+
+ prefixStr = VARDATA_ANY(prefixText);
+ prefixSize = VARSIZE_ANY_EXHDR(prefixText);
+
+ commonLen = commonPrefix(inStr + in->level,
+ prefixStr,
+ inSize - in->level,
+ prefixSize);
+
+ if (commonLen == prefixSize)
+ {
+ if (inSize - in->level > commonLen)
+ nodeChar = *(unsigned char *) (inStr + in->level + commonLen);
+ else
+ nodeChar = -1;
+ }
+ else
+ {
+ /* Must split tuple because incoming value doesn't match prefix */
+ out->resultType = spgSplitTuple;
+
+ if (commonLen == 0)
+ {
+ out->result.splitTuple.prefixHasPrefix = false;
+ }
+ else
+ {
+ out->result.splitTuple.prefixHasPrefix = true;
+ out->result.splitTuple.prefixPrefixDatum =
+ formTextDatum(prefixStr, commonLen);
+ }
+ out->result.splitTuple.prefixNNodes = 1;
+ out->result.splitTuple.prefixNodeLabels =
+ (Datum *) palloc(sizeof(Datum));
+ out->result.splitTuple.prefixNodeLabels[0] =
+ Int16GetDatum(*(unsigned char *) (prefixStr + commonLen));
+
+ out->result.splitTuple.childNodeN = 0;
+
+ if (prefixSize - commonLen == 1)
+ {
+ out->result.splitTuple.postfixHasPrefix = false;
+ }
+ else
+ {
+ out->result.splitTuple.postfixHasPrefix = true;
+ out->result.splitTuple.postfixPrefixDatum =
+ formTextDatum(prefixStr + commonLen + 1,
+ prefixSize - commonLen - 1);
+ }
+
+ PG_RETURN_VOID();
+ }
+ }
+ else if (inSize > in->level)
+ {
+ nodeChar = *(unsigned char *) (inStr + in->level);
+ }
+ else
+ {
+ nodeChar = -1;
+ }
+
+ /* Look up nodeChar in the node label array */
+ if (searchChar(in->nodeLabels, in->nNodes, nodeChar, &i))
+ {
+ /*
+ * Descend to existing node. (If in->allTheSame, the core code will
+ * ignore our nodeN specification here, but that's OK. We still have
+ * to provide the correct levelAdd and restDatum values, and those are
+ * the same regardless of which node gets chosen by core.)
+ */
+ int levelAdd;
+
+ out->resultType = spgMatchNode;
+ out->result.matchNode.nodeN = i;
+ levelAdd = commonLen;
+ if (nodeChar >= 0)
+ levelAdd++;
+ out->result.matchNode.levelAdd = levelAdd;
+ if (inSize - in->level - levelAdd > 0)
+ out->result.matchNode.restDatum =
+ formTextDatum(inStr + in->level + levelAdd,
+ inSize - in->level - levelAdd);
+ else
+ out->result.matchNode.restDatum =
+ formTextDatum(NULL, 0);
+ }
+ else if (in->allTheSame)
+ {
+ /*
+ * Can't use AddNode action, so split the tuple. The upper tuple has
+ * the same prefix as before and uses a dummy node label -2 for the
+ * lower tuple. The lower tuple has no prefix and the same node
+ * labels as the original tuple.
+ *
+ * Note: it might seem tempting to shorten the upper tuple's prefix,
+ * if it has one, then use its last byte as label for the lower tuple.
+ * But that doesn't win since we know the incoming value matches the
+ * whole prefix: we'd just end up splitting the lower tuple again.
+ */
+ out->resultType = spgSplitTuple;
+ out->result.splitTuple.prefixHasPrefix = in->hasPrefix;
+ out->result.splitTuple.prefixPrefixDatum = in->prefixDatum;
+ out->result.splitTuple.prefixNNodes = 1;
+ out->result.splitTuple.prefixNodeLabels = (Datum *) palloc(sizeof(Datum));
+ out->result.splitTuple.prefixNodeLabels[0] = Int16GetDatum(-2);
+ out->result.splitTuple.childNodeN = 0;
+ out->result.splitTuple.postfixHasPrefix = false;
+ }
+ else
+ {
+ /* Add a node for the not-previously-seen nodeChar value */
+ out->resultType = spgAddNode;
+ out->result.addNode.nodeLabel = Int16GetDatum(nodeChar);
+ out->result.addNode.nodeN = i;
+ }
+
+ PG_RETURN_VOID();
+}
+
+/* The picksplit function is identical to the core opclass, so just use that */
+
+PG_FUNCTION_INFO_V1(spgist_name_inner_consistent);
+Datum
+spgist_name_inner_consistent(PG_FUNCTION_ARGS)
+{
+ spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
+ spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
+ text *reconstructedValue;
+ text *reconstrText;
+ int maxReconstrLen;
+ text *prefixText = NULL;
+ int prefixSize = 0;
+ int i;
+
+ /*
+ * Reconstruct values represented at this tuple, including parent data,
+ * prefix of this tuple if any, and the node label if it's non-dummy.
+ * in->level should be the length of the previously reconstructed value,
+ * and the number of bytes added here is prefixSize or prefixSize + 1.
+ *
+ * Recall that reconstructedValues are assumed to be the same type as leaf
+ * datums, so we must use "text" not "name" for them.
+ *
+ * Note: we assume that in->reconstructedValue isn't toasted and doesn't
+ * have a short varlena header. This is okay because it must have been
+ * created by a previous invocation of this routine, and we always emit
+ * long-format reconstructed values.
+ */
+ reconstructedValue = (text *) DatumGetPointer(in->reconstructedValue);
+ Assert(reconstructedValue == NULL ? in->level == 0 :
+ VARSIZE_ANY_EXHDR(reconstructedValue) == in->level);
+
+ maxReconstrLen = in->level + 1;
+ if (in->hasPrefix)
+ {
+ prefixText = DatumGetTextPP(in->prefixDatum);
+ prefixSize = VARSIZE_ANY_EXHDR(prefixText);
+ maxReconstrLen += prefixSize;
+ }
+
+ reconstrText = palloc(VARHDRSZ + maxReconstrLen);
+ SET_VARSIZE(reconstrText, VARHDRSZ + maxReconstrLen);
+
+ if (in->level)
+ memcpy(VARDATA(reconstrText),
+ VARDATA(reconstructedValue),
+ in->level);
+ if (prefixSize)
+ memcpy(((char *) VARDATA(reconstrText)) + in->level,
+ VARDATA_ANY(prefixText),
+ prefixSize);
+ /* last byte of reconstrText will be filled in below */
+
+ /*
+ * Scan the child nodes. For each one, complete the reconstructed value
+ * and see if it's consistent with the query. If so, emit an entry into
+ * the output arrays.
+ */
+ out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+ out->levelAdds = (int *) palloc(sizeof(int) * in->nNodes);
+ out->reconstructedValues = (Datum *) palloc(sizeof(Datum) * in->nNodes);
+ out->nNodes = 0;
+
+ for (i = 0; i < in->nNodes; i++)
+ {
+ int16 nodeChar = DatumGetInt16(in->nodeLabels[i]);
+ int thisLen;
+ bool res = true;
+ int j;
+
+ /* If nodeChar is a dummy value, don't include it in data */
+ if (nodeChar <= 0)
+ thisLen = maxReconstrLen - 1;
+ else
+ {
+ ((unsigned char *) VARDATA(reconstrText))[maxReconstrLen - 1] = nodeChar;
+ thisLen = maxReconstrLen;
+ }
+
+ for (j = 0; j < in->nkeys; j++)
+ {
+ StrategyNumber strategy = in->scankeys[j].sk_strategy;
+ Name inName;
+ char *inStr;
+ int inSize;
+ int r;
+
+ inName = DatumGetName(in->scankeys[j].sk_argument);
+ inStr = NameStr(*inName);
+ inSize = strlen(inStr);
+
+ r = memcmp(VARDATA(reconstrText), inStr,
+ Min(inSize, thisLen));
+
+ switch (strategy)
+ {
+ case BTLessStrategyNumber:
+ case BTLessEqualStrategyNumber:
+ if (r > 0)
+ res = false;
+ break;
+ case BTEqualStrategyNumber:
+ if (r != 0 || inSize < thisLen)
+ res = false;
+ break;
+ case BTGreaterEqualStrategyNumber:
+ case BTGreaterStrategyNumber:
+ if (r < 0)
+ res = false;
+ break;
+ default:
+ elog(ERROR, "unrecognized strategy number: %d",
+ in->scankeys[j].sk_strategy);
+ break;
+ }
+
+ if (!res)
+ break; /* no need to consider remaining conditions */
+ }
+
+ if (res)
+ {
+ out->nodeNumbers[out->nNodes] = i;
+ out->levelAdds[out->nNodes] = thisLen - in->level;
+ SET_VARSIZE(reconstrText, VARHDRSZ + thisLen);
+ out->reconstructedValues[out->nNodes] =
+ datumCopy(PointerGetDatum(reconstrText), false, -1);
+ out->nNodes++;
+ }
+ }
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(spgist_name_leaf_consistent);
+Datum
+spgist_name_leaf_consistent(PG_FUNCTION_ARGS)
+{
+ spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
+ spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
+ int level = in->level;
+ text *leafValue,
+ *reconstrValue = NULL;
+ char *fullValue;
+ int fullLen;
+ bool res;
+ int j;
+
+ /* all tests are exact */
+ out->recheck = false;
+
+ leafValue = DatumGetTextPP(in->leafDatum);
+
+ /* As above, in->reconstructedValue isn't toasted or short. */
+ if (DatumGetPointer(in->reconstructedValue))
+ reconstrValue = (text *) DatumGetPointer(in->reconstructedValue);
+
+ Assert(reconstrValue == NULL ? level == 0 :
+ VARSIZE_ANY_EXHDR(reconstrValue) == level);
+
+ /* Reconstruct the Name represented by this leaf tuple */
+ fullValue = palloc0(NAMEDATALEN);
+ fullLen = level + VARSIZE_ANY_EXHDR(leafValue);
+ Assert(fullLen < NAMEDATALEN);
+ if (VARSIZE_ANY_EXHDR(leafValue) == 0 && level > 0)
+ {
+ memcpy(fullValue, VARDATA(reconstrValue),
+ VARSIZE_ANY_EXHDR(reconstrValue));
+ }
+ else
+ {
+ if (level)
+ memcpy(fullValue, VARDATA(reconstrValue), level);
+ if (VARSIZE_ANY_EXHDR(leafValue) > 0)
+ memcpy(fullValue + level, VARDATA_ANY(leafValue),
+ VARSIZE_ANY_EXHDR(leafValue));
+ }
+ out->leafValue = PointerGetDatum(fullValue);
+
+ /* Perform the required comparison(s) */
+ res = true;
+ for (j = 0; j < in->nkeys; j++)
+ {
+ StrategyNumber strategy = in->scankeys[j].sk_strategy;
+ Name queryName = DatumGetName(in->scankeys[j].sk_argument);
+ char *queryStr = NameStr(*queryName);
+ int queryLen = strlen(queryStr);
+ int r;
+
+ /* Non-collation-aware comparison */
+ r = memcmp(fullValue, queryStr, Min(queryLen, fullLen));
+
+ if (r == 0)
+ {
+ if (queryLen > fullLen)
+ r = -1;
+ else if (queryLen < fullLen)
+ r = 1;
+ }
+
+ switch (strategy)
+ {
+ case BTLessStrategyNumber:
+ res = (r < 0);
+ break;
+ case BTLessEqualStrategyNumber:
+ res = (r <= 0);
+ break;
+ case BTEqualStrategyNumber:
+ res = (r == 0);
+ break;
+ case BTGreaterEqualStrategyNumber:
+ res = (r >= 0);
+ break;
+ case BTGreaterStrategyNumber:
+ res = (r > 0);
+ break;
+ default:
+ elog(ERROR, "unrecognized strategy number: %d",
+ in->scankeys[j].sk_strategy);
+ res = false;
+ break;
+ }
+
+ if (!res)
+ break; /* no need to consider remaining conditions */
+ }
+
+ PG_RETURN_BOOL(res);
+}
+
+PG_FUNCTION_INFO_V1(spgist_name_compress);
+Datum
+spgist_name_compress(PG_FUNCTION_ARGS)
+{
+ Name inName = PG_GETARG_NAME(0);
+ char *inStr = NameStr(*inName);
+
+ PG_RETURN_DATUM(formTextDatum(inStr, strlen(inStr)));
+}
diff --git a/src/test/modules/spgist_name_ops/spgist_name_ops.control b/src/test/modules/spgist_name_ops/spgist_name_ops.control
new file mode 100644
index 0000000..f02df7e
--- /dev/null
+++ b/src/test/modules/spgist_name_ops/spgist_name_ops.control
@@ -0,0 +1,4 @@
+comment = 'Test opclass for SP-GiST'
+default_version = '1.0'
+module_pathname = '$libdir/spgist_name_ops'
+relocatable = true
diff --git a/src/test/modules/spgist_name_ops/sql/spgist_name_ops.sql b/src/test/modules/spgist_name_ops/sql/spgist_name_ops.sql
new file mode 100644
index 0000000..982f221
--- /dev/null
+++ b/src/test/modules/spgist_name_ops/sql/spgist_name_ops.sql
@@ -0,0 +1,51 @@
+create extension spgist_name_ops;
+
+select opcname, amvalidate(opc.oid)
+from pg_opclass opc join pg_am am on am.oid = opcmethod
+where amname = 'spgist' and opcname = 'name_ops';
+
+-- warning expected here
+select opcname, amvalidate(opc.oid)
+from pg_opclass opc join pg_am am on am.oid = opcmethod
+where amname = 'spgist' and opcname = 'name_ops_old';
+
+create table t(f1 name, f2 integer, f3 text);
+create index on t using spgist(f1) include(f2, f3);
+\d+ t_f1_f2_f3_idx
+
+insert into t select
+ proname,
+ case when length(proname) % 2 = 0 then pronargs else null end,
+ prosrc from pg_proc;
+vacuum analyze t;
+
+explain (costs off)
+select * from t
+ where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p'
+ order by 1;
+select * from t
+ where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p'
+ order by 1;
+
+-- Verify clean failure when INCLUDE'd columns result in overlength tuple
+-- The error message details are platform-dependent, so show only SQLSTATE
+\set VERBOSITY sqlstate
+insert into t values(repeat('xyzzy', 12), 42, repeat('xyzzy', 4000));
+\set VERBOSITY default
+
+drop index t_f1_f2_f3_idx;
+
+create index on t using spgist(f1 name_ops_old) include(f2, f3);
+\d+ t_f1_f2_f3_idx
+
+explain (costs off)
+select * from t
+ where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p'
+ order by 1;
+select * from t
+ where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p'
+ order by 1;
+
+\set VERBOSITY sqlstate
+insert into t values(repeat('xyzzy', 12), 42, repeat('xyzzy', 4000));
+\set VERBOSITY default
diff --git a/src/test/modules/ssl_passphrase_callback/.gitignore b/src/test/modules/ssl_passphrase_callback/.gitignore
new file mode 100644
index 0000000..1dbadf7
--- /dev/null
+++ b/src/test/modules/ssl_passphrase_callback/.gitignore
@@ -0,0 +1 @@
+tmp_check
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
new file mode 100644
index 0000000..a34d7ea
--- /dev/null
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -0,0 +1,40 @@
+# ssl_passphrase_callback Makefile
+
+export with_ssl
+
+MODULE_big = ssl_passphrase_func
+OBJS = ssl_passphrase_func.o $(WIN32RES)
+PGFILEDESC = "callback function to provide a passphrase"
+
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/ssl_passphrase_callback
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+SHLIB_LINK += $(filter -lssl -lcrypto -lssleay32 -leay32, $(LIBS))
+
+# Targets to generate or remove the ssl certificate and key
+# Normally not needed. Don't run these targets in a vpath build, the results
+# won't be in the right place if you do.
+
+# needs to agree with what's in the test script
+PASS = FooBaR1
+
+.PHONY: ssl-files ssl-files-clean
+
+ssl-files:
+ openssl req -new -x509 -days 10000 -nodes -out server.crt \
+ -keyout server.ckey -subj "/CN=localhost"
+ openssl rsa -aes256 -in server.ckey -out server.key -passout pass:$(PASS)
+ rm server.ckey
+
+ssl-files-clean:
+ rm -f server.crt server.key
diff --git a/src/test/modules/ssl_passphrase_callback/server.crt b/src/test/modules/ssl_passphrase_callback/server.crt
new file mode 100644
index 0000000..b3c4be4
--- /dev/null
+++ b/src/test/modules/ssl_passphrase_callback/server.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIUfHgPLNys4V0d0cWrzRHqfs91LFMwDQYJKoZIhvcNAQEL
+BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIwMDMyMTE0MDM1OVoXDTQ3MDgw
+NzE0MDM1OVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA2j0PZwmeahBC7QpG7i9/VUVJrLzy+b8oVaqZUO6nlPbY
+wuPISYTO/jqc0XDfs/Gb0kccDJ6bPfNfvSnRTG1omE6OO9YjR0u3296l4bWAmYVq
+q4SesgQmm1Wy8ODNpeGaoBUwR51OB/gFHFjUlqAjRwOmrTCbDiAsLt7e+cx+W26r
+2SrJIweiSJsqaQsMMaqlY2qpHnYgWfqRUTqwXqlno0dXuqBt+KKgqeHMY3w3XS51
+8roOI0+Q9KWsexL/aYnLwMRsHRMZcthhzTK6HD/OrLh9CxURImr4ed9TtsNiZltA
+KqLTeGbtS1D2AvFqJU8n5DvtU+26wDrHu6pEM3kSJQIDAQABo1MwUTAdBgNVHQ4E
+FgQUkkfa08hDnxYs1UjG2ydCBJs1b2AwHwYDVR0jBBgwFoAUkkfa08hDnxYs1UjG
+2ydCBJs1b2AwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAjsJh
+p4tCopCA/Pvxupv3VEwGJ+nbH7Zg/hp+o2IWuHBOK1qrkyXBv34h/69bRnWZ5UFV
+HxQwL7CjNtjZu9SbpKkaHbZXPWANC9fbPKdBz9fAEwunf33KbZe3dPv/7xbJirMz
+e+j5V0LE0Spkr/p89LipXfjGw0jLC8VRTx/vKPnmbiBsCKw5SQKh3w7CcBx84Y6q
+Nc27WQ8ReR4W4X1zHGN6kEV4H+yPN2Z9OlSixTiSNvr2mtJQsZa7gK7Wwfm79RN7
+5Kf3l8b6e2BToJwLorpK9mvu41NtwRzl4UoJ1BFJDyhMplFMd8RcwTW6yT2biOFC
+lYCajcBoms3IiyqBog==
+-----END CERTIFICATE-----
diff --git a/src/test/modules/ssl_passphrase_callback/server.key b/src/test/modules/ssl_passphrase_callback/server.key
new file mode 100644
index 0000000..1475007
--- /dev/null
+++ b/src/test/modules/ssl_passphrase_callback/server.key
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-256-CBC,DB0E7068D4DCE79FFE63C95B8D8F7CEA
+
+Y4uvnlWX/kyulqsmt8aWI55vKFdfJL4wEZItL8ZKlQFuZuxC9w0OworyjTdqO38R
+v9hwnetZBDgK8kEv6U7wR58mTfwHHCGuxYgSiPZtiW7btS4zu16ePdh8oBEzCxjW
+ALrCFt7uvRu5h2AWy/4BgV4gLNVPNB+lJFABtUoiSnUDr7+bcx7UjZ4An6HriGxC
+Kg/N1pKjT/xiKOy+yHtrp1Jih5HYDE4i99jPtMuTROf8Uyz7ibyrdd/E7QNvANQN
+Cmw4I4Xk4hZ68F0iGU0C0wLND3pWbeYPKorpo3PkI4Du+Aqlg15ae5u8CtU3fXGJ
+mq4/qLGAi1sr/45f5P5a3Q8BQpKkCmGopXMaFYOOiaf3YYgD1eVOxLhsCWqUB+O8
+ygcTNRCoKhzY+ULComXp880J3fFk5b92g4Hm1PAO42uDKzhWSnrmCBJ7ynXvnEc+
+JqhiE8Obrp6FBIHvfN26JtHcXTd/bgUMXSh7AXjsotfvPPV0URve9JJG+RnwckeT
+K3AYDOQK/lbqDGliNqHg1WiMSA2oHSqDhUMB0Sm0jh6+jxCQlsmSDvPvJfWRo5wY
+zbZZZARQnFUaHa9CZVdFxbaPGhYU6vAwxDqi42osSJEdf68Gy2KVXcelqpU/2dKk
+aHfTgAWOsajbgt9p+0369TeZb39+zLODdDJnvZYiu1pTASHP5VrJ2xHhu5zOdjXm
+GafYiPwYBM280wkIVQ0HsTX7BViU2R/7W3FqflXgQvBiraVQVwHyaX4bOU1a3rzg
+emHNLTCpRamT0i/D0tkEPgS42bWSVi9ko5Mn9yb+qToBjAOLVUOAOs9Bv3qxawhI
+XFbBDZ7DS59l2yV6eQkrG7DUCLDf4dv4WZeBnhrPe/Jg8HKcsKcJYV3cejZh8sgu
+XHeCU50+jpJDfTZVPW3TjZWmrTqStGwF1UFpj+tTsTcX+OHAY/shFs3bBZulAsMy
+5UWZWzyWHMWr/wbxW7dbhTb1gNmOgpQQz9dunSgcZ8umzSGLa0ZGmnQj9P/kZkQA
+RenuswH5O7CK/MDmf3J6svwyLt/jULmH26MZTcNu7igT6dj3VMSwkoQQaaQdtmzb
+glzN3uqf8qM+CEjV8dxlt8fv6KJV7gvoYfPAz+1pp5DVJBmRo/+b4e/d4QTV9iWS
+ScBYdonc9WXcrjmExX9+Wf/K/IKfLnKLIi2MZ3pwr1n7yY+dMeF6iREYSjFVIpZd
+MH3G9/SxTrqR7X/eHjwdv1UupYYyaDag8wpVn1RMCb0xYqh2/QP1k0pQycckL0WQ
+lieXibEuQhV/heXcqt83G6pGqLImc6YPYU46jdGpPIMyOK+ZSqJTHUWHfRMQTIMz
+varR2M3uhHvwUFzmvjLh/o6I3r0a0Rl1MztpYfjBV6MS4BKYfraWZ0kxCyV+e6tz
+O7vD0P5W2qm6b89Md3nqjUcbOM8AojcfBl3xpQrpSdgJ25YJBoJ9L2I2pIMNCK/x
+yDNEJl7yP87fdHfXZm2VoUXclDUYHyNys9Rtv9NSr+VNkIMcqrCHEgpAxwQQ5NsO
+/vOZe3wjhXXLyRO7Nh5W8jojw3xcb9c9avFUWUvM2BaS4vEYcItUoF4QuHohrCwk
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/modules/ssl_passphrase_callback/ssl_passphrase_func.c b/src/test/modules/ssl_passphrase_callback/ssl_passphrase_func.c
new file mode 100644
index 0000000..6b0a3db
--- /dev/null
+++ b/src/test/modules/ssl_passphrase_callback/ssl_passphrase_func.c
@@ -0,0 +1,90 @@
+/*-------------------------------------------------------------------------
+ *
+ * ssl_passphrase_func.c
+ *
+ * Loadable PostgreSQL module fetch an ssl passphrase for the server cert.
+ * instead of calling an external program. This implementation just hands
+ * back the configured password rot13'd.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <stdio.h>
+
+#include "libpq/libpq.h"
+#include "libpq/libpq-be.h"
+#include "utils/guc.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+void _PG_fini(void);
+
+static char *ssl_passphrase = NULL;
+
+/* callback function */
+static int rot13_passphrase(char *buf, int size, int rwflag, void *userdata);
+
+/* hook function to set the callback */
+static void set_rot13(SSL_CTX *context, bool isServerStart);
+
+/*
+ * Module load callback
+ */
+void
+_PG_init(void)
+{
+ /* Define custom GUC variable. */
+ DefineCustomStringVariable("ssl_passphrase.passphrase",
+ "passphrase before transformation",
+ NULL,
+ &ssl_passphrase,
+ NULL,
+ PGC_SIGHUP,
+ 0, /* no flags required */
+ NULL,
+ NULL,
+ NULL);
+ if (ssl_passphrase)
+ openssl_tls_init_hook = set_rot13;
+}
+
+void
+_PG_fini(void)
+{
+ /* do nothing yet */
+}
+
+static void
+set_rot13(SSL_CTX *context, bool isServerStart)
+{
+ /* warn if the user has set ssl_passphrase_command */
+ if (ssl_passphrase_command[0])
+ ereport(WARNING,
+ (errmsg("ssl_passphrase_command setting ignored by ssl_passphrase_func module")));
+
+ SSL_CTX_set_default_passwd_cb(context, rot13_passphrase);
+}
+
+static int
+rot13_passphrase(char *buf, int size, int rwflag, void *userdata)
+{
+
+ Assert(ssl_passphrase != NULL);
+ strlcpy(buf, ssl_passphrase, size);
+ for (char *p = buf; *p; p++)
+ {
+ char c = *p;
+
+ if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
+ *p = c + 13;
+ else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
+ *p = c - 13;
+ }
+
+ return strlen(buf);
+
+}
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
new file mode 100644
index 0000000..1e2455e
--- /dev/null
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -0,0 +1,77 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+use File::Copy;
+
+use TestLib;
+use Test::More;
+use PostgresNode;
+
+unless ($ENV{with_ssl} eq 'openssl')
+{
+ plan skip_all => 'OpenSSL not supported by this build';
+}
+
+my $clearpass = "FooBaR1";
+my $rot13pass = "SbbOnE1";
+
+# see the Makefile for how the certificate and key have been generated
+
+my $node = get_new_node('main');
+$node->init;
+$node->append_conf('postgresql.conf',
+ "ssl_passphrase.passphrase = '$rot13pass'");
+$node->append_conf('postgresql.conf',
+ "shared_preload_libraries = 'ssl_passphrase_func'");
+$node->append_conf('postgresql.conf', "ssl = 'on'");
+
+my $ddir = $node->data_dir;
+
+# install certificate and protected key
+copy("server.crt", $ddir);
+copy("server.key", $ddir);
+chmod 0600, "$ddir/server.key";
+
+$node->start;
+
+# if the server is running we must have successfully transformed the passphrase
+ok(-e "$ddir/postmaster.pid", "postgres started");
+
+$node->stop('fast');
+
+# should get a warning if ssl_passphrase_command is set
+my $log = $node->rotate_logfile();
+
+$node->append_conf('postgresql.conf',
+ "ssl_passphrase_command = 'echo spl0tz'");
+
+$node->start;
+
+$node->stop('fast');
+
+my $log_contents = slurp_file($log);
+
+like(
+ $log_contents,
+ qr/WARNING.*ssl_passphrase_command setting ignored by ssl_passphrase_func module/,
+ "ssl_passphrase_command set warning");
+
+# set the wrong passphrase
+$node->append_conf('postgresql.conf', "ssl_passphrase.passphrase = 'blurfl'");
+
+# try to start the server again
+my $ret = TestLib::system_log('pg_ctl', '-D', $node->data_dir, '-l',
+ $node->logfile, 'start');
+
+
+# with a bad passphrase the server should not start
+ok($ret, "pg_ctl fails with bad passphrase");
+ok(!-e "$ddir/postmaster.pid", "postgres not started with bad passphrase");
+
+# just in case
+$node->stop('fast');
+
+done_testing();
diff --git a/src/test/modules/test_bloomfilter/.gitignore b/src/test/modules/test_bloomfilter/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_bloomfilter/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_bloomfilter/Makefile b/src/test/modules/test_bloomfilter/Makefile
new file mode 100644
index 0000000..c8b7890
--- /dev/null
+++ b/src/test/modules/test_bloomfilter/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_bloomfilter/Makefile
+
+MODULE_big = test_bloomfilter
+OBJS = \
+ $(WIN32RES) \
+ test_bloomfilter.o
+PGFILEDESC = "test_bloomfilter - test code for Bloom filter library"
+
+EXTENSION = test_bloomfilter
+DATA = test_bloomfilter--1.0.sql
+
+REGRESS = test_bloomfilter
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_bloomfilter
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_bloomfilter/README b/src/test/modules/test_bloomfilter/README
new file mode 100644
index 0000000..4c05efe
--- /dev/null
+++ b/src/test/modules/test_bloomfilter/README
@@ -0,0 +1,68 @@
+test_bloomfilter overview
+=========================
+
+test_bloomfilter is a test harness module for testing Bloom filter library set
+membership operations. It consists of a single SQL-callable function,
+test_bloomfilter(), plus a regression test that calls test_bloomfilter().
+Membership tests are performed against a dataset that the test harness module
+generates.
+
+The test_bloomfilter() function displays instrumentation at DEBUG1 elog level
+(WARNING when the false positive rate exceeds a 1% threshold). This can be
+used to get a sense of the performance characteristics of the Postgres Bloom
+filter implementation under varied conditions.
+
+Bitset size
+-----------
+
+The main bloomfilter.c criteria for sizing its bitset is that the false
+positive rate should not exceed 2% when sufficient bloom_work_mem is available
+(and the caller-supplied estimate of the number of elements turns out to have
+been accurate). A 1% - 2% rate is currently assumed to be suitable for all
+Bloom filter callers.
+
+With an optimal K (number of hash functions), Bloom filters should only have a
+1% false positive rate with just 9.6 bits of memory per element. The Postgres
+implementation's 2% worst case guarantee exists because there is a need for
+some slop due to implementation inflexibility in bitset sizing. Since the
+bitset size is always actually kept to a power of two number of bits, callers
+can have their bloom_work_mem argument truncated down by almost half.
+In practice, callers that make a point of passing a bloom_work_mem that is an
+exact power of two bitset size (such as test_bloomfilter.c) will actually get
+the "9.6 bits per element" 1% false positive rate.
+
+Testing strategy
+----------------
+
+Our approach to regression testing is to test that a Bloom filter has only a 1%
+false positive rate for a single bitset size (2 ^ 23, or 1MB). We test a
+dataset with 838,861 elements, which works out at 10 bits of memory per
+element. We round up from 9.6 bits to 10 bits to make sure that we reliably
+get under 1% for regression testing. Note that a random seed is used in the
+regression tests because the exact false positive rate is inconsistent across
+platforms. Inconsistent hash function behavior is something that the
+regression tests need to be tolerant of anyway.
+
+test_bloomfilter() SQL-callable function
+========================================
+
+The SQL-callable function test_bloomfilter() provides the following arguments:
+
+* "power" is the power of two used to size the Bloom filter's bitset.
+
+The minimum valid argument value is 23 (2^23 bits), or 1MB of memory. The
+maximum valid argument value is 32, or 512MB of memory.
+
+* "nelements" is the number of elements to generate for testing purposes.
+
+* "seed" is a seed value for hashing.
+
+A value < 0 is interpreted as "use random seed". Varying the seed value (or
+specifying -1) should result in small variations in the total number of false
+positives.
+
+* "tests" is the number of tests to run.
+
+This may be increased when it's useful to perform many tests in an interactive
+session. It only makes sense to perform multiple tests when a random seed is
+used.
diff --git a/src/test/modules/test_bloomfilter/expected/test_bloomfilter.out b/src/test/modules/test_bloomfilter/expected/test_bloomfilter.out
new file mode 100644
index 0000000..21c0688
--- /dev/null
+++ b/src/test/modules/test_bloomfilter/expected/test_bloomfilter.out
@@ -0,0 +1,22 @@
+CREATE EXTENSION test_bloomfilter;
+-- See README for explanation of arguments:
+SELECT test_bloomfilter(power => 23,
+ nelements => 838861,
+ seed => -1,
+ tests => 1);
+ test_bloomfilter
+------------------
+
+(1 row)
+
+-- Equivalent "10 bits per element" tests for all possible bitset sizes:
+--
+-- SELECT test_bloomfilter(24, 1677722)
+-- SELECT test_bloomfilter(25, 3355443)
+-- SELECT test_bloomfilter(26, 6710886)
+-- SELECT test_bloomfilter(27, 13421773)
+-- SELECT test_bloomfilter(28, 26843546)
+-- SELECT test_bloomfilter(29, 53687091)
+-- SELECT test_bloomfilter(30, 107374182)
+-- SELECT test_bloomfilter(31, 214748365)
+-- SELECT test_bloomfilter(32, 429496730)
diff --git a/src/test/modules/test_bloomfilter/sql/test_bloomfilter.sql b/src/test/modules/test_bloomfilter/sql/test_bloomfilter.sql
new file mode 100644
index 0000000..9ec159c
--- /dev/null
+++ b/src/test/modules/test_bloomfilter/sql/test_bloomfilter.sql
@@ -0,0 +1,19 @@
+CREATE EXTENSION test_bloomfilter;
+
+-- See README for explanation of arguments:
+SELECT test_bloomfilter(power => 23,
+ nelements => 838861,
+ seed => -1,
+ tests => 1);
+
+-- Equivalent "10 bits per element" tests for all possible bitset sizes:
+--
+-- SELECT test_bloomfilter(24, 1677722)
+-- SELECT test_bloomfilter(25, 3355443)
+-- SELECT test_bloomfilter(26, 6710886)
+-- SELECT test_bloomfilter(27, 13421773)
+-- SELECT test_bloomfilter(28, 26843546)
+-- SELECT test_bloomfilter(29, 53687091)
+-- SELECT test_bloomfilter(30, 107374182)
+-- SELECT test_bloomfilter(31, 214748365)
+-- SELECT test_bloomfilter(32, 429496730)
diff --git a/src/test/modules/test_bloomfilter/test_bloomfilter--1.0.sql b/src/test/modules/test_bloomfilter/test_bloomfilter--1.0.sql
new file mode 100644
index 0000000..7682318
--- /dev/null
+++ b/src/test/modules/test_bloomfilter/test_bloomfilter--1.0.sql
@@ -0,0 +1,11 @@
+/* src/test/modules/test_bloomfilter/test_bloomfilter--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_bloomfilter" to load this file. \quit
+
+CREATE FUNCTION test_bloomfilter(power integer,
+ nelements bigint,
+ seed integer DEFAULT -1,
+ tests integer DEFAULT 1)
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_bloomfilter/test_bloomfilter.c b/src/test/modules/test_bloomfilter/test_bloomfilter.c
new file mode 100644
index 0000000..96c5011
--- /dev/null
+++ b/src/test/modules/test_bloomfilter/test_bloomfilter.c
@@ -0,0 +1,138 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_bloomfilter.c
+ * Test false positive rate of Bloom filter.
+ *
+ * Copyright (c) 2018-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_bloomfilter/test_bloomfilter.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "lib/bloomfilter.h"
+#include "miscadmin.h"
+
+PG_MODULE_MAGIC;
+
+/* Fits decimal representation of PG_INT64_MIN + 2 bytes: */
+#define MAX_ELEMENT_BYTES 21
+/* False positive rate WARNING threshold (1%): */
+#define FPOSITIVE_THRESHOLD 0.01
+
+
+/*
+ * Populate an empty Bloom filter with "nelements" dummy strings.
+ */
+static void
+populate_with_dummy_strings(bloom_filter *filter, int64 nelements)
+{
+ char element[MAX_ELEMENT_BYTES];
+ int64 i;
+
+ for (i = 0; i < nelements; i++)
+ {
+ CHECK_FOR_INTERRUPTS();
+
+ snprintf(element, sizeof(element), "i" INT64_FORMAT, i);
+ bloom_add_element(filter, (unsigned char *) element, strlen(element));
+ }
+}
+
+/*
+ * Returns number of strings that are indicated as probably appearing in Bloom
+ * filter that were in fact never added by populate_with_dummy_strings().
+ * These are false positives.
+ */
+static int64
+nfalsepos_for_missing_strings(bloom_filter *filter, int64 nelements)
+{
+ char element[MAX_ELEMENT_BYTES];
+ int64 nfalsepos = 0;
+ int64 i;
+
+ for (i = 0; i < nelements; i++)
+ {
+ CHECK_FOR_INTERRUPTS();
+
+ snprintf(element, sizeof(element), "M" INT64_FORMAT, i);
+ if (!bloom_lacks_element(filter, (unsigned char *) element,
+ strlen(element)))
+ nfalsepos++;
+ }
+
+ return nfalsepos;
+}
+
+static void
+create_and_test_bloom(int power, int64 nelements, int callerseed)
+{
+ int bloom_work_mem;
+ uint64 seed;
+ int64 nfalsepos;
+ bloom_filter *filter;
+
+ bloom_work_mem = (1L << power) / 8L / 1024L;
+
+ elog(DEBUG1, "bloom_work_mem (KB): %d", bloom_work_mem);
+
+ /*
+ * Generate random seed, or use caller's. Seed should always be a
+ * positive value less than or equal to PG_INT32_MAX, to ensure that any
+ * random seed can be recreated through callerseed if the need arises.
+ * (Don't assume that RAND_MAX cannot exceed PG_INT32_MAX.)
+ */
+ seed = callerseed < 0 ? random() % PG_INT32_MAX : callerseed;
+
+ /* Create Bloom filter, populate it, and report on false positive rate */
+ filter = bloom_create(nelements, bloom_work_mem, seed);
+ populate_with_dummy_strings(filter, nelements);
+ nfalsepos = nfalsepos_for_missing_strings(filter, nelements);
+
+ ereport((nfalsepos > nelements * FPOSITIVE_THRESHOLD) ? WARNING : DEBUG1,
+ (errmsg_internal("seed: " UINT64_FORMAT " false positives: " INT64_FORMAT " (%.6f%%) bitset %.2f%% set",
+ seed, nfalsepos, (double) nfalsepos / nelements,
+ 100.0 * bloom_prop_bits_set(filter))));
+
+ bloom_free(filter);
+}
+
+PG_FUNCTION_INFO_V1(test_bloomfilter);
+
+/*
+ * SQL-callable entry point to perform all tests.
+ *
+ * If a 1% false positive threshold is not met, emits WARNINGs.
+ *
+ * See README for details of arguments.
+ */
+Datum
+test_bloomfilter(PG_FUNCTION_ARGS)
+{
+ int power = PG_GETARG_INT32(0);
+ int64 nelements = PG_GETARG_INT64(1);
+ int seed = PG_GETARG_INT32(2);
+ int tests = PG_GETARG_INT32(3);
+ int i;
+
+ if (power < 23 || power > 32)
+ elog(ERROR, "power argument must be between 23 and 32 inclusive");
+
+ if (tests <= 0)
+ elog(ERROR, "invalid number of tests: %d", tests);
+
+ if (nelements < 0)
+ elog(ERROR, "invalid number of elements: %d", tests);
+
+ for (i = 0; i < tests; i++)
+ {
+ elog(DEBUG1, "beginning test #%d...", i + 1);
+
+ create_and_test_bloom(power, nelements, seed);
+ }
+
+ PG_RETURN_VOID();
+}
diff --git a/src/test/modules/test_bloomfilter/test_bloomfilter.control b/src/test/modules/test_bloomfilter/test_bloomfilter.control
new file mode 100644
index 0000000..99e56ee
--- /dev/null
+++ b/src/test/modules/test_bloomfilter/test_bloomfilter.control
@@ -0,0 +1,4 @@
+comment = 'Test code for Bloom filter library'
+default_version = '1.0'
+module_pathname = '$libdir/test_bloomfilter'
+relocatable = true
diff --git a/src/test/modules/test_ddl_deparse/.gitignore b/src/test/modules/test_ddl_deparse/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_ddl_deparse/Makefile b/src/test/modules/test_ddl_deparse/Makefile
new file mode 100644
index 0000000..3a57a95
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/Makefile
@@ -0,0 +1,43 @@
+# src/test/modules/test_ddl_deparse/Makefile
+
+MODULES = test_ddl_deparse
+PGFILEDESC = "test_ddl_deparse - regression testing for DDL deparsing"
+
+EXTENSION = test_ddl_deparse
+DATA = test_ddl_deparse--1.0.sql
+
+# test_ddl_deparse must be first
+REGRESS = test_ddl_deparse \
+ create_extension \
+ create_schema \
+ create_type \
+ create_conversion \
+ create_domain \
+ create_sequence_1 \
+ create_table \
+ create_transform \
+ alter_table \
+ create_view \
+ create_trigger \
+ create_rule \
+ comment_on \
+ alter_function \
+ alter_sequence \
+ alter_ts_config \
+ alter_type_enum \
+ opfamily \
+ defprivs \
+ matviews
+
+EXTRA_INSTALL = contrib/pg_stat_statements
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_ddl_deparse
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_ddl_deparse/README b/src/test/modules/test_ddl_deparse/README
new file mode 100644
index 0000000..b12a129
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/README
@@ -0,0 +1,8 @@
+test_ddl_deparse is an example of how to use the pg_ddl_command datatype.
+It is not intended to do anything useful on its own; rather, it is a
+demonstration of how to use the datatype, and to provide some unit tests for
+it.
+
+The functions in this extension are intended to be able to process some
+part of the struct and produce some readable output, preferably handling
+all possible cases so that SQL test code can be written.
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_extension.out b/src/test/modules/test_ddl_deparse/expected/alter_extension.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_extension.out
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_function.out b/src/test/modules/test_ddl_deparse/expected/alter_function.out
new file mode 100644
index 0000000..69a3742
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_function.out
@@ -0,0 +1,15 @@
+--
+-- ALTER_FUNCTION
+--
+ALTER FUNCTION plpgsql_function_trigger_1 ()
+ SET SCHEMA foo;
+NOTICE: DDL test: type simple, tag ALTER FUNCTION
+ALTER FUNCTION foo.plpgsql_function_trigger_1()
+ COST 10;
+NOTICE: DDL test: type simple, tag ALTER FUNCTION
+CREATE ROLE regress_alter_function_role;
+ALTER FUNCTION plpgsql_function_trigger_2()
+ OWNER TO regress_alter_function_role;
+ERROR: function plpgsql_function_trigger_2() does not exist
+DROP OWNED BY regress_alter_function_role;
+DROP ROLE regress_alter_function_role;
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_sequence.out b/src/test/modules/test_ddl_deparse/expected/alter_sequence.out
new file mode 100644
index 0000000..319f36f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_sequence.out
@@ -0,0 +1,15 @@
+--
+-- ALTER_SEQUENCE
+--
+ALTER SEQUENCE fkey_table_seq
+ MINVALUE 10
+ START 20
+ CACHE 1
+ NO CYCLE;
+NOTICE: DDL test: type simple, tag ALTER SEQUENCE
+ALTER SEQUENCE fkey_table_seq
+ RENAME TO fkey_table_seq_renamed;
+NOTICE: DDL test: type simple, tag ALTER SEQUENCE
+ALTER SEQUENCE fkey_table_seq_renamed
+ SET SCHEMA foo;
+NOTICE: DDL test: type simple, tag ALTER SEQUENCE
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out
new file mode 100644
index 0000000..141060f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_table.out
@@ -0,0 +1,29 @@
+CREATE TABLE parent (
+ a int
+);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+CREATE TABLE child () INHERITS (parent);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+CREATE TABLE grandchild () INHERITS (child);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+ALTER TABLE parent ADD COLUMN b serial;
+NOTICE: DDL test: type simple, tag CREATE SEQUENCE
+NOTICE: DDL test: type alter table, tag ALTER TABLE
+NOTICE: subcommand: ADD COLUMN (and recurse)
+NOTICE: DDL test: type simple, tag ALTER SEQUENCE
+ALTER TABLE parent RENAME COLUMN b TO c;
+NOTICE: DDL test: type simple, tag ALTER TABLE
+ALTER TABLE parent ADD CONSTRAINT a_pos CHECK (a > 0);
+NOTICE: DDL test: type alter table, tag ALTER TABLE
+NOTICE: subcommand: ADD CONSTRAINT (and recurse)
+CREATE TABLE part (
+ a int
+) PARTITION BY RANGE (a);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+CREATE TABLE part1 PARTITION OF part FOR VALUES FROM (1) to (100);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+ALTER TABLE part ADD PRIMARY KEY (a);
+NOTICE: DDL test: type alter table, tag ALTER TABLE
+NOTICE: subcommand: SET NOT NULL
+NOTICE: subcommand: SET NOT NULL
+NOTICE: subcommand: ADD INDEX
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_ts_config.out b/src/test/modules/test_ddl_deparse/expected/alter_ts_config.out
new file mode 100644
index 0000000..afc352f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_ts_config.out
@@ -0,0 +1,8 @@
+--
+-- ALTER TEXT SEARCH CONFIGURATION
+--
+CREATE TEXT SEARCH CONFIGURATION en (copy=english);
+NOTICE: DDL test: type simple, tag CREATE TEXT SEARCH CONFIGURATION
+ALTER TEXT SEARCH CONFIGURATION en
+ ALTER MAPPING FOR host, email, url, sfloat WITH simple;
+NOTICE: DDL test: type alter text search configuration, tag ALTER TEXT SEARCH CONFIGURATION
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out b/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out
new file mode 100644
index 0000000..74107c2
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out
@@ -0,0 +1,7 @@
+---
+--- ALTER_TYPE_ENUM
+---
+ALTER TYPE enum_test ADD VALUE 'zzz' AFTER 'baz';
+NOTICE: DDL test: type simple, tag ALTER TYPE
+ALTER TYPE enum_test ADD VALUE 'aaa' BEFORE 'foo';
+NOTICE: DDL test: type simple, tag ALTER TYPE
diff --git a/src/test/modules/test_ddl_deparse/expected/comment_on.out b/src/test/modules/test_ddl_deparse/expected/comment_on.out
new file mode 100644
index 0000000..129eff9
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/comment_on.out
@@ -0,0 +1,23 @@
+--
+-- COMMENT_ON
+--
+COMMENT ON SCHEMA foo IS 'This is schema foo';
+NOTICE: DDL test: type simple, tag COMMENT
+COMMENT ON TYPE enum_test IS 'ENUM test';
+NOTICE: DDL test: type simple, tag COMMENT
+COMMENT ON TYPE int2range IS 'RANGE test';
+NOTICE: DDL test: type simple, tag COMMENT
+COMMENT ON DOMAIN japanese_postal_code IS 'DOMAIN test';
+NOTICE: DDL test: type simple, tag COMMENT
+COMMENT ON SEQUENCE fkey_table_seq IS 'SEQUENCE test';
+NOTICE: DDL test: type simple, tag COMMENT
+COMMENT ON TABLE datatype_table IS 'This table should contain all native datatypes';
+NOTICE: DDL test: type simple, tag COMMENT
+COMMENT ON VIEW datatype_view IS 'This is a view';
+NOTICE: DDL test: type simple, tag COMMENT
+COMMENT ON FUNCTION c_function_test() IS 'FUNCTION test';
+ERROR: function c_function_test() does not exist
+COMMENT ON TRIGGER trigger_1 ON datatype_table IS 'TRIGGER test';
+NOTICE: DDL test: type simple, tag COMMENT
+COMMENT ON RULE rule_1 ON datatype_table IS 'RULE test';
+NOTICE: DDL test: type simple, tag COMMENT
diff --git a/src/test/modules/test_ddl_deparse/expected/create_conversion.out b/src/test/modules/test_ddl_deparse/expected/create_conversion.out
new file mode 100644
index 0000000..e8697cf
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_conversion.out
@@ -0,0 +1,6 @@
+---
+--- CREATE_CONVERSION
+---
+-- Simple test should suffice for this
+CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
+NOTICE: DDL test: type simple, tag CREATE CONVERSION
diff --git a/src/test/modules/test_ddl_deparse/expected/create_domain.out b/src/test/modules/test_ddl_deparse/expected/create_domain.out
new file mode 100644
index 0000000..2e7f585
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_domain.out
@@ -0,0 +1,11 @@
+---
+--- CREATE_DOMAIN
+---
+CREATE DOMAIN domainvarchar VARCHAR(5);
+NOTICE: DDL test: type simple, tag CREATE DOMAIN
+CREATE DOMAIN japanese_postal_code AS TEXT
+CHECK(
+ VALUE ~ '^\d{3}$'
+OR VALUE ~ '^\d{3}-\d{4}$'
+);
+NOTICE: DDL test: type simple, tag CREATE DOMAIN
diff --git a/src/test/modules/test_ddl_deparse/expected/create_extension.out b/src/test/modules/test_ddl_deparse/expected/create_extension.out
new file mode 100644
index 0000000..4042e02
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_extension.out
@@ -0,0 +1,5 @@
+---
+--- CREATE_EXTENSION
+---
+CREATE EXTENSION pg_stat_statements;
+NOTICE: DDL test: type simple, tag CREATE EXTENSION
diff --git a/src/test/modules/test_ddl_deparse/expected/create_function.out b/src/test/modules/test_ddl_deparse/expected/create_function.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_function.out
diff --git a/src/test/modules/test_ddl_deparse/expected/create_operator.out b/src/test/modules/test_ddl_deparse/expected/create_operator.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_operator.out
diff --git a/src/test/modules/test_ddl_deparse/expected/create_rule.out b/src/test/modules/test_ddl_deparse/expected/create_rule.out
new file mode 100644
index 0000000..fe3d047
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_rule.out
@@ -0,0 +1,30 @@
+---
+--- CREATE_RULE
+---
+CREATE RULE rule_1 AS
+ ON INSERT
+ TO datatype_table
+ DO NOTHING;
+NOTICE: DDL test: type simple, tag CREATE RULE
+CREATE RULE rule_2 AS
+ ON UPDATE
+ TO datatype_table
+ DO INSERT INTO unlogged_table (id) VALUES(NEW.id);
+NOTICE: DDL test: type simple, tag CREATE RULE
+CREATE RULE rule_3 AS
+ ON DELETE
+ TO datatype_table
+ DO ALSO NOTHING;
+NOTICE: DDL test: type simple, tag CREATE RULE
+CREATE RULE "_RETURN" AS
+ ON SELECT
+ TO like_datatype_table
+ DO INSTEAD
+ SELECT * FROM datatype_view;
+NOTICE: DDL test: type simple, tag CREATE RULE
+CREATE RULE rule_3 AS
+ ON DELETE
+ TO like_datatype_table
+ WHERE id < 100
+ DO ALSO NOTHING;
+NOTICE: DDL test: type simple, tag CREATE RULE
diff --git a/src/test/modules/test_ddl_deparse/expected/create_schema.out b/src/test/modules/test_ddl_deparse/expected/create_schema.out
new file mode 100644
index 0000000..8ab4eb0
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -0,0 +1,19 @@
+--
+-- CREATE_SCHEMA
+--
+CREATE SCHEMA foo;
+NOTICE: DDL test: type simple, tag CREATE SCHEMA
+CREATE SCHEMA IF NOT EXISTS bar;
+NOTICE: DDL test: type simple, tag CREATE SCHEMA
+CREATE SCHEMA baz;
+NOTICE: DDL test: type simple, tag CREATE SCHEMA
+-- Will not be created, and will not be handled by the
+-- event trigger
+CREATE SCHEMA IF NOT EXISTS baz;
+NOTICE: schema "baz" already exists, skipping
+CREATE SCHEMA element_test
+ CREATE TABLE foo (id int)
+ CREATE VIEW bar AS SELECT * FROM foo;
+NOTICE: DDL test: type simple, tag CREATE SCHEMA
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type simple, tag CREATE VIEW
diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
new file mode 100644
index 0000000..5837ea4
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
@@ -0,0 +1,11 @@
+--
+-- CREATE_SEQUENCE
+--
+CREATE SEQUENCE fkey_table_seq
+ INCREMENT BY 1
+ MINVALUE 0
+ MAXVALUE 1000000
+ START 10
+ CACHE 10
+ CYCLE;
+NOTICE: DDL test: type simple, tag CREATE SEQUENCE
diff --git a/src/test/modules/test_ddl_deparse/expected/create_table.out b/src/test/modules/test_ddl_deparse/expected/create_table.out
new file mode 100644
index 0000000..0f2a2c1
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_table.out
@@ -0,0 +1,164 @@
+--
+-- CREATE_TABLE
+--
+-- Datatypes
+CREATE TABLE datatype_table (
+ id SERIAL,
+ id_big BIGSERIAL,
+ is_small SMALLSERIAL,
+ v_bytea BYTEA,
+ v_smallint SMALLINT,
+ v_int INT,
+ v_bigint BIGINT,
+ v_char CHAR(1),
+ v_varchar VARCHAR(10),
+ v_text TEXT,
+ v_bool BOOLEAN,
+ v_inet INET,
+ v_cidr CIDR,
+ v_macaddr MACADDR,
+ v_numeric NUMERIC(1,0),
+ v_real REAL,
+ v_float FLOAT(1),
+ v_float8 FLOAT8,
+ v_money MONEY,
+ v_tsquery TSQUERY,
+ v_tsvector TSVECTOR,
+ v_date DATE,
+ v_time TIME,
+ v_time_tz TIME WITH TIME ZONE,
+ v_timestamp TIMESTAMP,
+ v_timestamp_tz TIMESTAMP WITH TIME ZONE,
+ v_interval INTERVAL,
+ v_bit BIT,
+ v_bit4 BIT(4),
+ v_varbit VARBIT,
+ v_varbit4 VARBIT(4),
+ v_box BOX,
+ v_circle CIRCLE,
+ v_lseg LSEG,
+ v_path PATH,
+ v_point POINT,
+ v_polygon POLYGON,
+ v_json JSON,
+ v_xml XML,
+ v_uuid UUID,
+ v_pg_snapshot pg_snapshot,
+ v_enum ENUM_TEST,
+ v_postal_code japanese_postal_code,
+ v_int2range int2range,
+ PRIMARY KEY (id),
+ UNIQUE (id_big)
+);
+NOTICE: DDL test: type simple, tag CREATE SEQUENCE
+NOTICE: DDL test: type simple, tag CREATE SEQUENCE
+NOTICE: DDL test: type simple, tag CREATE SEQUENCE
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type simple, tag CREATE INDEX
+NOTICE: DDL test: type simple, tag CREATE INDEX
+NOTICE: DDL test: type simple, tag ALTER SEQUENCE
+NOTICE: DDL test: type simple, tag ALTER SEQUENCE
+NOTICE: DDL test: type simple, tag ALTER SEQUENCE
+-- Constraint definitions
+CREATE TABLE IF NOT EXISTS fkey_table (
+ id INT NOT NULL DEFAULT nextval('fkey_table_seq'::REGCLASS),
+ datatype_id INT NOT NULL REFERENCES datatype_table(id),
+ big_id BIGINT NOT NULL,
+ sometext TEXT COLLATE "POSIX",
+ check_col_1 INT NOT NULL CHECK(check_col_1 < 10),
+ check_col_2 INT NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT fkey_big_id
+ FOREIGN KEY (big_id)
+ REFERENCES datatype_table(id_big),
+ EXCLUDE USING btree (check_col_2 WITH =)
+);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type simple, tag CREATE INDEX
+NOTICE: DDL test: type simple, tag CREATE INDEX
+NOTICE: DDL test: type alter table, tag ALTER TABLE
+NOTICE: subcommand: ADD CONSTRAINT (and recurse)
+NOTICE: subcommand: ADD CONSTRAINT (and recurse)
+-- Typed table
+CREATE TABLE employees OF employee_type (
+ PRIMARY KEY (name),
+ salary WITH OPTIONS DEFAULT 1000
+);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type alter table, tag ALTER TABLE
+NOTICE: subcommand: SET NOT NULL
+NOTICE: DDL test: type simple, tag CREATE INDEX
+-- Inheritance
+CREATE TABLE person (
+ id INT NOT NULL PRIMARY KEY,
+ name text,
+ age int4,
+ location point
+);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type simple, tag CREATE INDEX
+CREATE TABLE emp (
+ salary int4,
+ manager name
+) INHERITS (person);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+CREATE TABLE student (
+ gpa float8
+) INHERITS (person);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+CREATE TABLE stud_emp (
+ percent int4
+) INHERITS (emp, student);
+NOTICE: merging multiple inherited definitions of column "id"
+NOTICE: merging multiple inherited definitions of column "name"
+NOTICE: merging multiple inherited definitions of column "age"
+NOTICE: merging multiple inherited definitions of column "location"
+NOTICE: DDL test: type simple, tag CREATE TABLE
+-- Storage parameters
+CREATE TABLE storage (
+ id INT
+) WITH (
+ fillfactor = 10,
+ autovacuum_enabled = FALSE
+);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+-- LIKE
+CREATE TABLE like_datatype_table (
+ LIKE datatype_table
+ EXCLUDING ALL
+);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+CREATE TABLE like_fkey_table (
+ LIKE fkey_table
+ INCLUDING DEFAULTS
+ INCLUDING INDEXES
+ INCLUDING STORAGE
+);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type alter table, tag ALTER TABLE
+NOTICE: subcommand: ALTER COLUMN SET DEFAULT (precooked)
+NOTICE: DDL test: type simple, tag CREATE INDEX
+NOTICE: DDL test: type simple, tag CREATE INDEX
+-- Volatile table types
+CREATE UNLOGGED TABLE unlogged_table (
+ id INT PRIMARY KEY
+);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type simple, tag CREATE INDEX
+CREATE TEMP TABLE temp_table (
+ id INT PRIMARY KEY
+);
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type simple, tag CREATE INDEX
+CREATE TEMP TABLE temp_table_commit_delete (
+ id INT PRIMARY KEY
+)
+ON COMMIT DELETE ROWS;
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type simple, tag CREATE INDEX
+CREATE TEMP TABLE temp_table_commit_drop (
+ id INT PRIMARY KEY
+)
+ON COMMIT DROP;
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type simple, tag CREATE INDEX
diff --git a/src/test/modules/test_ddl_deparse/expected/create_transform.out b/src/test/modules/test_ddl_deparse/expected/create_transform.out
new file mode 100644
index 0000000..5066051
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_transform.out
@@ -0,0 +1,15 @@
+--
+-- CREATE_TRANSFORM
+--
+-- Create a dummy transform
+-- The function FROM SQL should have internal as single argument as well
+-- as return type. The function TO SQL should have as single argument
+-- internal and as return argument the datatype of the transform done.
+-- We choose some random built-in functions that have the right signature.
+-- This won't actually be used, because the SQL function language
+-- doesn't implement transforms (there would be no point).
+CREATE TRANSFORM FOR int LANGUAGE SQL (
+ FROM SQL WITH FUNCTION prsd_lextype(internal),
+ TO SQL WITH FUNCTION int4recv(internal));
+NOTICE: DDL test: type simple, tag CREATE TRANSFORM
+DROP TRANSFORM FOR int LANGUAGE SQL;
diff --git a/src/test/modules/test_ddl_deparse/expected/create_trigger.out b/src/test/modules/test_ddl_deparse/expected/create_trigger.out
new file mode 100644
index 0000000..c89c847
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_trigger.out
@@ -0,0 +1,18 @@
+---
+--- CREATE_TRIGGER
+---
+CREATE FUNCTION plpgsql_function_trigger_1()
+ RETURNS TRIGGER
+ LANGUAGE plpgsql
+AS $$
+BEGIN
+ RETURN NEW;
+END;
+$$;
+NOTICE: DDL test: type simple, tag CREATE FUNCTION
+CREATE TRIGGER trigger_1
+ BEFORE INSERT OR UPDATE
+ ON datatype_table
+ FOR EACH ROW
+ EXECUTE PROCEDURE plpgsql_function_trigger_1();
+NOTICE: DDL test: type simple, tag CREATE TRIGGER
diff --git a/src/test/modules/test_ddl_deparse/expected/create_type.out b/src/test/modules/test_ddl_deparse/expected/create_type.out
new file mode 100644
index 0000000..dadbc8f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_type.out
@@ -0,0 +1,24 @@
+---
+--- CREATE_TYPE
+---
+CREATE FUNCTION text_w_default_in(cstring)
+ RETURNS text_w_default
+ AS 'textin'
+ LANGUAGE internal STABLE STRICT;
+NOTICE: type "text_w_default" is not yet defined
+DETAIL: Creating a shell type definition.
+NOTICE: DDL test: type simple, tag CREATE FUNCTION
+CREATE FUNCTION text_w_default_out(text_w_default)
+ RETURNS cstring
+ AS 'textout'
+ LANGUAGE internal STABLE STRICT ;
+NOTICE: argument type text_w_default is only a shell
+NOTICE: DDL test: type simple, tag CREATE FUNCTION
+CREATE TYPE employee_type AS (name TEXT, salary NUMERIC);
+NOTICE: DDL test: type simple, tag CREATE TYPE
+CREATE TYPE enum_test AS ENUM ('foo', 'bar', 'baz');
+NOTICE: DDL test: type simple, tag CREATE TYPE
+CREATE TYPE int2range AS RANGE (
+ SUBTYPE = int2
+);
+NOTICE: DDL test: type simple, tag CREATE TYPE
diff --git a/src/test/modules/test_ddl_deparse/expected/create_view.out b/src/test/modules/test_ddl_deparse/expected/create_view.out
new file mode 100644
index 0000000..2ae4e2d
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_view.out
@@ -0,0 +1,19 @@
+--
+-- CREATE_VIEW
+--
+CREATE VIEW static_view AS
+ SELECT 'foo'::TEXT AS col;
+NOTICE: DDL test: type simple, tag CREATE VIEW
+CREATE OR REPLACE VIEW static_view AS
+ SELECT 'bar'::TEXT AS col;
+NOTICE: DDL test: type simple, tag CREATE VIEW
+NOTICE: DDL test: type alter table, tag CREATE VIEW
+NOTICE: subcommand: REPLACE RELOPTIONS
+CREATE VIEW datatype_view AS
+ SELECT * FROM datatype_table;
+NOTICE: DDL test: type simple, tag CREATE VIEW
+CREATE RECURSIVE VIEW nums_1_100 (n) AS
+ VALUES (1)
+UNION ALL
+ SELECT n+1 FROM nums_1_100 WHERE n < 100;
+NOTICE: DDL test: type simple, tag CREATE VIEW
diff --git a/src/test/modules/test_ddl_deparse/expected/defprivs.out b/src/test/modules/test_ddl_deparse/expected/defprivs.out
new file mode 100644
index 0000000..66b2680
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/defprivs.out
@@ -0,0 +1,6 @@
+--
+-- ALTER DEFAULT PRIVILEGES
+--
+ALTER DEFAULT PRIVILEGES IN SCHEMA public
+ REVOKE ALL PRIVILEGES ON TABLES FROM public;
+NOTICE: DDL test: type alter default privileges, tag ALTER DEFAULT PRIVILEGES
diff --git a/src/test/modules/test_ddl_deparse/expected/matviews.out b/src/test/modules/test_ddl_deparse/expected/matviews.out
new file mode 100644
index 0000000..69a5627
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/matviews.out
@@ -0,0 +1,8 @@
+--
+-- Materialized views
+--
+CREATE MATERIALIZED VIEW ddl_deparse_mv AS
+ SELECT * FROM datatype_table LIMIT 1 WITH NO DATA;
+NOTICE: DDL test: type simple, tag CREATE MATERIALIZED VIEW
+REFRESH MATERIALIZED VIEW ddl_deparse_mv;
+NOTICE: DDL test: type simple, tag REFRESH MATERIALIZED VIEW
diff --git a/src/test/modules/test_ddl_deparse/expected/opfamily.out b/src/test/modules/test_ddl_deparse/expected/opfamily.out
new file mode 100644
index 0000000..c7e3a23
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/opfamily.out
@@ -0,0 +1,68 @@
+-- copied from equivclass.sql
+create type int8alias1;
+NOTICE: DDL test: type simple, tag CREATE TYPE
+create function int8alias1in(cstring) returns int8alias1
+ strict immutable language internal as 'int8in';
+NOTICE: return type int8alias1 is only a shell
+NOTICE: DDL test: type simple, tag CREATE FUNCTION
+create function int8alias1out(int8alias1) returns cstring
+ strict immutable language internal as 'int8out';
+NOTICE: argument type int8alias1 is only a shell
+NOTICE: DDL test: type simple, tag CREATE FUNCTION
+create type int8alias1 (
+ input = int8alias1in,
+ output = int8alias1out,
+ like = int8
+);
+NOTICE: DDL test: type simple, tag CREATE TYPE
+create type int8alias2;
+NOTICE: DDL test: type simple, tag CREATE TYPE
+create function int8alias2in(cstring) returns int8alias2
+ strict immutable language internal as 'int8in';
+NOTICE: return type int8alias2 is only a shell
+NOTICE: DDL test: type simple, tag CREATE FUNCTION
+create function int8alias2out(int8alias2) returns cstring
+ strict immutable language internal as 'int8out';
+NOTICE: argument type int8alias2 is only a shell
+NOTICE: DDL test: type simple, tag CREATE FUNCTION
+create type int8alias2 (
+ input = int8alias2in,
+ output = int8alias2out,
+ like = int8
+);
+NOTICE: DDL test: type simple, tag CREATE TYPE
+create cast (int8 as int8alias1) without function;
+NOTICE: DDL test: type simple, tag CREATE CAST
+create cast (int8 as int8alias2) without function;
+NOTICE: DDL test: type simple, tag CREATE CAST
+create cast (int8alias1 as int8) without function;
+NOTICE: DDL test: type simple, tag CREATE CAST
+create cast (int8alias2 as int8) without function;
+NOTICE: DDL test: type simple, tag CREATE CAST
+create function int8alias1eq(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+NOTICE: DDL test: type simple, tag CREATE FUNCTION
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias1,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+NOTICE: DDL test: type simple, tag CREATE OPERATOR
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias1);
+NOTICE: DDL test: type alter operator family, tag ALTER OPERATOR FAMILY
+-- copied from alter_table.sql
+create type ctype as (f1 int, f2 text);
+NOTICE: DDL test: type simple, tag CREATE TYPE
+create function same(ctype, ctype) returns boolean language sql
+as 'select $1.f1 is not distinct from $2.f1 and $1.f2 is not distinct from $2.f2';
+NOTICE: DDL test: type simple, tag CREATE FUNCTION
+create operator =(procedure = same, leftarg = ctype, rightarg = ctype);
+NOTICE: DDL test: type simple, tag CREATE OPERATOR
+create operator class ctype_hash_ops
+ default for type ctype using hash as
+ operator 1 =(ctype, ctype);
+NOTICE: DDL test: type simple, tag CREATE OPERATOR FAMILY
+NOTICE: DDL test: type create operator class, tag CREATE OPERATOR CLASS
diff --git a/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out b/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out
new file mode 100644
index 0000000..4a5ea9e
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out
@@ -0,0 +1,40 @@
+CREATE EXTENSION test_ddl_deparse;
+CREATE OR REPLACE FUNCTION test_ddl_deparse()
+ RETURNS event_trigger LANGUAGE plpgsql AS
+$$
+DECLARE
+ r record;
+ r2 record;
+ cmdtype text;
+ objtype text;
+ tag text;
+BEGIN
+ FOR r IN SELECT * FROM pg_event_trigger_ddl_commands()
+ LOOP
+ -- verify that tags match
+ tag = public.get_command_tag(r.command);
+ IF tag <> r.command_tag THEN
+ RAISE NOTICE 'tag % doesn''t match %', tag, r.command_tag;
+ END IF;
+
+ -- log the operation
+ cmdtype = public.get_command_type(r.command);
+ IF cmdtype <> 'grant' THEN
+ RAISE NOTICE 'DDL test: type %, tag %', cmdtype, tag;
+ ELSE
+ RAISE NOTICE 'DDL test: type %, object type %', cmdtype, r.object_type;
+ END IF;
+
+ -- if alter table, log more
+ IF cmdtype = 'alter table' THEN
+ FOR r2 IN SELECT *
+ FROM unnest(public.get_altertable_subcmdtypes(r.command))
+ LOOP
+ RAISE NOTICE ' subcommand: %', r2.unnest;
+ END LOOP;
+ END IF;
+ END LOOP;
+END;
+$$;
+CREATE EVENT TRIGGER test_ddl_deparse
+ON ddl_command_end EXECUTE PROCEDURE test_ddl_deparse();
diff --git a/src/test/modules/test_ddl_deparse/sql/alter_function.sql b/src/test/modules/test_ddl_deparse/sql/alter_function.sql
new file mode 100644
index 0000000..45c8d1e
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/alter_function.sql
@@ -0,0 +1,17 @@
+--
+-- ALTER_FUNCTION
+--
+
+ALTER FUNCTION plpgsql_function_trigger_1 ()
+ SET SCHEMA foo;
+
+ALTER FUNCTION foo.plpgsql_function_trigger_1()
+ COST 10;
+
+CREATE ROLE regress_alter_function_role;
+
+ALTER FUNCTION plpgsql_function_trigger_2()
+ OWNER TO regress_alter_function_role;
+
+DROP OWNED BY regress_alter_function_role;
+DROP ROLE regress_alter_function_role;
diff --git a/src/test/modules/test_ddl_deparse/sql/alter_sequence.sql b/src/test/modules/test_ddl_deparse/sql/alter_sequence.sql
new file mode 100644
index 0000000..9b2799f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/alter_sequence.sql
@@ -0,0 +1,15 @@
+--
+-- ALTER_SEQUENCE
+--
+
+ALTER SEQUENCE fkey_table_seq
+ MINVALUE 10
+ START 20
+ CACHE 1
+ NO CYCLE;
+
+ALTER SEQUENCE fkey_table_seq
+ RENAME TO fkey_table_seq_renamed;
+
+ALTER SEQUENCE fkey_table_seq_renamed
+ SET SCHEMA foo;
diff --git a/src/test/modules/test_ddl_deparse/sql/alter_table.sql b/src/test/modules/test_ddl_deparse/sql/alter_table.sql
new file mode 100644
index 0000000..dec53a0
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/alter_table.sql
@@ -0,0 +1,21 @@
+CREATE TABLE parent (
+ a int
+);
+
+CREATE TABLE child () INHERITS (parent);
+
+CREATE TABLE grandchild () INHERITS (child);
+
+ALTER TABLE parent ADD COLUMN b serial;
+
+ALTER TABLE parent RENAME COLUMN b TO c;
+
+ALTER TABLE parent ADD CONSTRAINT a_pos CHECK (a > 0);
+
+CREATE TABLE part (
+ a int
+) PARTITION BY RANGE (a);
+
+CREATE TABLE part1 PARTITION OF part FOR VALUES FROM (1) to (100);
+
+ALTER TABLE part ADD PRIMARY KEY (a);
diff --git a/src/test/modules/test_ddl_deparse/sql/alter_ts_config.sql b/src/test/modules/test_ddl_deparse/sql/alter_ts_config.sql
new file mode 100644
index 0000000..ac13e21
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/alter_ts_config.sql
@@ -0,0 +1,8 @@
+--
+-- ALTER TEXT SEARCH CONFIGURATION
+--
+
+CREATE TEXT SEARCH CONFIGURATION en (copy=english);
+
+ALTER TEXT SEARCH CONFIGURATION en
+ ALTER MAPPING FOR host, email, url, sfloat WITH simple;
diff --git a/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql b/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql
new file mode 100644
index 0000000..8999b38
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql
@@ -0,0 +1,6 @@
+---
+--- ALTER_TYPE_ENUM
+---
+
+ALTER TYPE enum_test ADD VALUE 'zzz' AFTER 'baz';
+ALTER TYPE enum_test ADD VALUE 'aaa' BEFORE 'foo';
diff --git a/src/test/modules/test_ddl_deparse/sql/comment_on.sql b/src/test/modules/test_ddl_deparse/sql/comment_on.sql
new file mode 100644
index 0000000..fc29a73
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/comment_on.sql
@@ -0,0 +1,14 @@
+--
+-- COMMENT_ON
+--
+
+COMMENT ON SCHEMA foo IS 'This is schema foo';
+COMMENT ON TYPE enum_test IS 'ENUM test';
+COMMENT ON TYPE int2range IS 'RANGE test';
+COMMENT ON DOMAIN japanese_postal_code IS 'DOMAIN test';
+COMMENT ON SEQUENCE fkey_table_seq IS 'SEQUENCE test';
+COMMENT ON TABLE datatype_table IS 'This table should contain all native datatypes';
+COMMENT ON VIEW datatype_view IS 'This is a view';
+COMMENT ON FUNCTION c_function_test() IS 'FUNCTION test';
+COMMENT ON TRIGGER trigger_1 ON datatype_table IS 'TRIGGER test';
+COMMENT ON RULE rule_1 ON datatype_table IS 'RULE test';
diff --git a/src/test/modules/test_ddl_deparse/sql/create_conversion.sql b/src/test/modules/test_ddl_deparse/sql/create_conversion.sql
new file mode 100644
index 0000000..813c66d
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_conversion.sql
@@ -0,0 +1,6 @@
+---
+--- CREATE_CONVERSION
+---
+
+-- Simple test should suffice for this
+CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_domain.sql b/src/test/modules/test_ddl_deparse/sql/create_domain.sql
new file mode 100644
index 0000000..6ab5525
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_domain.sql
@@ -0,0 +1,10 @@
+---
+--- CREATE_DOMAIN
+---
+CREATE DOMAIN domainvarchar VARCHAR(5);
+
+CREATE DOMAIN japanese_postal_code AS TEXT
+CHECK(
+ VALUE ~ '^\d{3}$'
+OR VALUE ~ '^\d{3}-\d{4}$'
+);
diff --git a/src/test/modules/test_ddl_deparse/sql/create_extension.sql b/src/test/modules/test_ddl_deparse/sql/create_extension.sql
new file mode 100644
index 0000000..d23e7fd
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_extension.sql
@@ -0,0 +1,5 @@
+---
+--- CREATE_EXTENSION
+---
+
+CREATE EXTENSION pg_stat_statements;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_rule.sql b/src/test/modules/test_ddl_deparse/sql/create_rule.sql
new file mode 100644
index 0000000..60ac151
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_rule.sql
@@ -0,0 +1,31 @@
+---
+--- CREATE_RULE
+---
+
+
+CREATE RULE rule_1 AS
+ ON INSERT
+ TO datatype_table
+ DO NOTHING;
+
+CREATE RULE rule_2 AS
+ ON UPDATE
+ TO datatype_table
+ DO INSERT INTO unlogged_table (id) VALUES(NEW.id);
+
+CREATE RULE rule_3 AS
+ ON DELETE
+ TO datatype_table
+ DO ALSO NOTHING;
+
+CREATE RULE "_RETURN" AS
+ ON SELECT
+ TO like_datatype_table
+ DO INSTEAD
+ SELECT * FROM datatype_view;
+
+CREATE RULE rule_3 AS
+ ON DELETE
+ TO like_datatype_table
+ WHERE id < 100
+ DO ALSO NOTHING;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_schema.sql b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
new file mode 100644
index 0000000..f314dc2
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -0,0 +1,17 @@
+--
+-- CREATE_SCHEMA
+--
+
+CREATE SCHEMA foo;
+
+CREATE SCHEMA IF NOT EXISTS bar;
+
+CREATE SCHEMA baz;
+
+-- Will not be created, and will not be handled by the
+-- event trigger
+CREATE SCHEMA IF NOT EXISTS baz;
+
+CREATE SCHEMA element_test
+ CREATE TABLE foo (id int)
+ CREATE VIEW bar AS SELECT * FROM foo;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql b/src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql
new file mode 100644
index 0000000..9e6743f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql
@@ -0,0 +1,11 @@
+--
+-- CREATE_SEQUENCE
+--
+
+CREATE SEQUENCE fkey_table_seq
+ INCREMENT BY 1
+ MINVALUE 0
+ MAXVALUE 1000000
+ START 10
+ CACHE 10
+ CYCLE;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_table.sql b/src/test/modules/test_ddl_deparse/sql/create_table.sql
new file mode 100644
index 0000000..39cdb9d
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_table.sql
@@ -0,0 +1,142 @@
+--
+-- CREATE_TABLE
+--
+
+-- Datatypes
+CREATE TABLE datatype_table (
+ id SERIAL,
+ id_big BIGSERIAL,
+ is_small SMALLSERIAL,
+ v_bytea BYTEA,
+ v_smallint SMALLINT,
+ v_int INT,
+ v_bigint BIGINT,
+ v_char CHAR(1),
+ v_varchar VARCHAR(10),
+ v_text TEXT,
+ v_bool BOOLEAN,
+ v_inet INET,
+ v_cidr CIDR,
+ v_macaddr MACADDR,
+ v_numeric NUMERIC(1,0),
+ v_real REAL,
+ v_float FLOAT(1),
+ v_float8 FLOAT8,
+ v_money MONEY,
+ v_tsquery TSQUERY,
+ v_tsvector TSVECTOR,
+ v_date DATE,
+ v_time TIME,
+ v_time_tz TIME WITH TIME ZONE,
+ v_timestamp TIMESTAMP,
+ v_timestamp_tz TIMESTAMP WITH TIME ZONE,
+ v_interval INTERVAL,
+ v_bit BIT,
+ v_bit4 BIT(4),
+ v_varbit VARBIT,
+ v_varbit4 VARBIT(4),
+ v_box BOX,
+ v_circle CIRCLE,
+ v_lseg LSEG,
+ v_path PATH,
+ v_point POINT,
+ v_polygon POLYGON,
+ v_json JSON,
+ v_xml XML,
+ v_uuid UUID,
+ v_pg_snapshot pg_snapshot,
+ v_enum ENUM_TEST,
+ v_postal_code japanese_postal_code,
+ v_int2range int2range,
+ PRIMARY KEY (id),
+ UNIQUE (id_big)
+);
+
+-- Constraint definitions
+
+CREATE TABLE IF NOT EXISTS fkey_table (
+ id INT NOT NULL DEFAULT nextval('fkey_table_seq'::REGCLASS),
+ datatype_id INT NOT NULL REFERENCES datatype_table(id),
+ big_id BIGINT NOT NULL,
+ sometext TEXT COLLATE "POSIX",
+ check_col_1 INT NOT NULL CHECK(check_col_1 < 10),
+ check_col_2 INT NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT fkey_big_id
+ FOREIGN KEY (big_id)
+ REFERENCES datatype_table(id_big),
+ EXCLUDE USING btree (check_col_2 WITH =)
+);
+
+-- Typed table
+
+CREATE TABLE employees OF employee_type (
+ PRIMARY KEY (name),
+ salary WITH OPTIONS DEFAULT 1000
+);
+
+-- Inheritance
+CREATE TABLE person (
+ id INT NOT NULL PRIMARY KEY,
+ name text,
+ age int4,
+ location point
+);
+
+CREATE TABLE emp (
+ salary int4,
+ manager name
+) INHERITS (person);
+
+
+CREATE TABLE student (
+ gpa float8
+) INHERITS (person);
+
+CREATE TABLE stud_emp (
+ percent int4
+) INHERITS (emp, student);
+
+
+-- Storage parameters
+
+CREATE TABLE storage (
+ id INT
+) WITH (
+ fillfactor = 10,
+ autovacuum_enabled = FALSE
+);
+
+-- LIKE
+
+CREATE TABLE like_datatype_table (
+ LIKE datatype_table
+ EXCLUDING ALL
+);
+
+CREATE TABLE like_fkey_table (
+ LIKE fkey_table
+ INCLUDING DEFAULTS
+ INCLUDING INDEXES
+ INCLUDING STORAGE
+);
+
+
+-- Volatile table types
+CREATE UNLOGGED TABLE unlogged_table (
+ id INT PRIMARY KEY
+);
+
+CREATE TEMP TABLE temp_table (
+ id INT PRIMARY KEY
+);
+
+CREATE TEMP TABLE temp_table_commit_delete (
+ id INT PRIMARY KEY
+)
+ON COMMIT DELETE ROWS;
+
+CREATE TEMP TABLE temp_table_commit_drop (
+ id INT PRIMARY KEY
+)
+ON COMMIT DROP;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_transform.sql b/src/test/modules/test_ddl_deparse/sql/create_transform.sql
new file mode 100644
index 0000000..970d89e
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_transform.sql
@@ -0,0 +1,16 @@
+--
+-- CREATE_TRANSFORM
+--
+
+-- Create a dummy transform
+-- The function FROM SQL should have internal as single argument as well
+-- as return type. The function TO SQL should have as single argument
+-- internal and as return argument the datatype of the transform done.
+-- We choose some random built-in functions that have the right signature.
+-- This won't actually be used, because the SQL function language
+-- doesn't implement transforms (there would be no point).
+CREATE TRANSFORM FOR int LANGUAGE SQL (
+ FROM SQL WITH FUNCTION prsd_lextype(internal),
+ TO SQL WITH FUNCTION int4recv(internal));
+
+DROP TRANSFORM FOR int LANGUAGE SQL;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_trigger.sql b/src/test/modules/test_ddl_deparse/sql/create_trigger.sql
new file mode 100644
index 0000000..fc0aef7
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_trigger.sql
@@ -0,0 +1,18 @@
+---
+--- CREATE_TRIGGER
+---
+
+CREATE FUNCTION plpgsql_function_trigger_1()
+ RETURNS TRIGGER
+ LANGUAGE plpgsql
+AS $$
+BEGIN
+ RETURN NEW;
+END;
+$$;
+
+CREATE TRIGGER trigger_1
+ BEFORE INSERT OR UPDATE
+ ON datatype_table
+ FOR EACH ROW
+ EXECUTE PROCEDURE plpgsql_function_trigger_1();
diff --git a/src/test/modules/test_ddl_deparse/sql/create_type.sql b/src/test/modules/test_ddl_deparse/sql/create_type.sql
new file mode 100644
index 0000000..a387cfd
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_type.sql
@@ -0,0 +1,21 @@
+---
+--- CREATE_TYPE
+---
+
+CREATE FUNCTION text_w_default_in(cstring)
+ RETURNS text_w_default
+ AS 'textin'
+ LANGUAGE internal STABLE STRICT;
+
+CREATE FUNCTION text_w_default_out(text_w_default)
+ RETURNS cstring
+ AS 'textout'
+ LANGUAGE internal STABLE STRICT ;
+
+CREATE TYPE employee_type AS (name TEXT, salary NUMERIC);
+
+CREATE TYPE enum_test AS ENUM ('foo', 'bar', 'baz');
+
+CREATE TYPE int2range AS RANGE (
+ SUBTYPE = int2
+);
diff --git a/src/test/modules/test_ddl_deparse/sql/create_view.sql b/src/test/modules/test_ddl_deparse/sql/create_view.sql
new file mode 100644
index 0000000..030b76f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_view.sql
@@ -0,0 +1,17 @@
+--
+-- CREATE_VIEW
+--
+
+CREATE VIEW static_view AS
+ SELECT 'foo'::TEXT AS col;
+
+CREATE OR REPLACE VIEW static_view AS
+ SELECT 'bar'::TEXT AS col;
+
+CREATE VIEW datatype_view AS
+ SELECT * FROM datatype_table;
+
+CREATE RECURSIVE VIEW nums_1_100 (n) AS
+ VALUES (1)
+UNION ALL
+ SELECT n+1 FROM nums_1_100 WHERE n < 100;
diff --git a/src/test/modules/test_ddl_deparse/sql/defprivs.sql b/src/test/modules/test_ddl_deparse/sql/defprivs.sql
new file mode 100644
index 0000000..a0fb4c2
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/defprivs.sql
@@ -0,0 +1,6 @@
+--
+-- ALTER DEFAULT PRIVILEGES
+--
+
+ALTER DEFAULT PRIVILEGES IN SCHEMA public
+ REVOKE ALL PRIVILEGES ON TABLES FROM public;
diff --git a/src/test/modules/test_ddl_deparse/sql/matviews.sql b/src/test/modules/test_ddl_deparse/sql/matviews.sql
new file mode 100644
index 0000000..6e22c52
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/matviews.sql
@@ -0,0 +1,8 @@
+--
+-- Materialized views
+--
+
+CREATE MATERIALIZED VIEW ddl_deparse_mv AS
+ SELECT * FROM datatype_table LIMIT 1 WITH NO DATA;
+
+REFRESH MATERIALIZED VIEW ddl_deparse_mv;
diff --git a/src/test/modules/test_ddl_deparse/sql/opfamily.sql b/src/test/modules/test_ddl_deparse/sql/opfamily.sql
new file mode 100644
index 0000000..b2bacbb
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/opfamily.sql
@@ -0,0 +1,52 @@
+-- copied from equivclass.sql
+create type int8alias1;
+create function int8alias1in(cstring) returns int8alias1
+ strict immutable language internal as 'int8in';
+create function int8alias1out(int8alias1) returns cstring
+ strict immutable language internal as 'int8out';
+create type int8alias1 (
+ input = int8alias1in,
+ output = int8alias1out,
+ like = int8
+);
+
+create type int8alias2;
+create function int8alias2in(cstring) returns int8alias2
+ strict immutable language internal as 'int8in';
+create function int8alias2out(int8alias2) returns cstring
+ strict immutable language internal as 'int8out';
+create type int8alias2 (
+ input = int8alias2in,
+ output = int8alias2out,
+ like = int8
+);
+
+create cast (int8 as int8alias1) without function;
+create cast (int8 as int8alias2) without function;
+create cast (int8alias1 as int8) without function;
+create cast (int8alias2 as int8) without function;
+
+create function int8alias1eq(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias1,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias1);
+
+
+-- copied from alter_table.sql
+create type ctype as (f1 int, f2 text);
+
+create function same(ctype, ctype) returns boolean language sql
+as 'select $1.f1 is not distinct from $2.f1 and $1.f2 is not distinct from $2.f2';
+
+create operator =(procedure = same, leftarg = ctype, rightarg = ctype);
+
+create operator class ctype_hash_ops
+ default for type ctype using hash as
+ operator 1 =(ctype, ctype);
diff --git a/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql b/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql
new file mode 100644
index 0000000..e257a21
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql
@@ -0,0 +1,42 @@
+CREATE EXTENSION test_ddl_deparse;
+
+CREATE OR REPLACE FUNCTION test_ddl_deparse()
+ RETURNS event_trigger LANGUAGE plpgsql AS
+$$
+DECLARE
+ r record;
+ r2 record;
+ cmdtype text;
+ objtype text;
+ tag text;
+BEGIN
+ FOR r IN SELECT * FROM pg_event_trigger_ddl_commands()
+ LOOP
+ -- verify that tags match
+ tag = public.get_command_tag(r.command);
+ IF tag <> r.command_tag THEN
+ RAISE NOTICE 'tag % doesn''t match %', tag, r.command_tag;
+ END IF;
+
+ -- log the operation
+ cmdtype = public.get_command_type(r.command);
+ IF cmdtype <> 'grant' THEN
+ RAISE NOTICE 'DDL test: type %, tag %', cmdtype, tag;
+ ELSE
+ RAISE NOTICE 'DDL test: type %, object type %', cmdtype, r.object_type;
+ END IF;
+
+ -- if alter table, log more
+ IF cmdtype = 'alter table' THEN
+ FOR r2 IN SELECT *
+ FROM unnest(public.get_altertable_subcmdtypes(r.command))
+ LOOP
+ RAISE NOTICE ' subcommand: %', r2.unnest;
+ END LOOP;
+ END IF;
+ END LOOP;
+END;
+$$;
+
+CREATE EVENT TRIGGER test_ddl_deparse
+ON ddl_command_end EXECUTE PROCEDURE test_ddl_deparse();
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql b/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql
new file mode 100644
index 0000000..093005a
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql
@@ -0,0 +1,16 @@
+/* src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ddl_deparse" to load this file. \quit
+
+CREATE FUNCTION get_command_type(pg_ddl_command)
+ RETURNS text IMMUTABLE STRICT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_command_tag(pg_ddl_command)
+ RETURNS text IMMUTABLE STRICT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_altertable_subcmdtypes(pg_ddl_command)
+ RETURNS text[] IMMUTABLE STRICT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
new file mode 100644
index 0000000..1bae1e5
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -0,0 +1,296 @@
+/*----------------------------------------------------------------------
+ * test_ddl_deparse.c
+ * Support functions for the test_ddl_deparse module
+ *
+ * Copyright (c) 2014-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+ *----------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "tcop/deparse_utility.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(get_command_type);
+PG_FUNCTION_INFO_V1(get_command_tag);
+PG_FUNCTION_INFO_V1(get_altertable_subcmdtypes);
+
+/*
+ * Return the textual representation of the struct type used to represent a
+ * command in struct CollectedCommand format.
+ */
+Datum
+get_command_type(PG_FUNCTION_ARGS)
+{
+ CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+ const char *type;
+
+ switch (cmd->type)
+ {
+ case SCT_Simple:
+ type = "simple";
+ break;
+ case SCT_AlterTable:
+ type = "alter table";
+ break;
+ case SCT_Grant:
+ type = "grant";
+ break;
+ case SCT_AlterOpFamily:
+ type = "alter operator family";
+ break;
+ case SCT_AlterDefaultPrivileges:
+ type = "alter default privileges";
+ break;
+ case SCT_CreateOpClass:
+ type = "create operator class";
+ break;
+ case SCT_AlterTSConfig:
+ type = "alter text search configuration";
+ break;
+ default:
+ type = "unknown command type";
+ break;
+ }
+
+ PG_RETURN_TEXT_P(cstring_to_text(type));
+}
+
+/*
+ * Return the command tag corresponding to a parse node contained in a
+ * CollectedCommand struct.
+ */
+Datum
+get_command_tag(PG_FUNCTION_ARGS)
+{
+ CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+
+ if (!cmd->parsetree)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(CreateCommandName(cmd->parsetree)));
+}
+
+/*
+ * Return a text array representation of the subcommands of an ALTER TABLE
+ * command.
+ */
+Datum
+get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
+{
+ CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+ ArrayBuildState *astate = NULL;
+ ListCell *cell;
+
+ if (cmd->type != SCT_AlterTable)
+ elog(ERROR, "command is not ALTER TABLE");
+
+ foreach(cell, cmd->d.alterTable.subcmds)
+ {
+ CollectedATSubcmd *sub = lfirst(cell);
+ AlterTableCmd *subcmd = castNode(AlterTableCmd, sub->parsetree);
+ const char *strtype;
+
+ switch (subcmd->subtype)
+ {
+ case AT_AddColumn:
+ strtype = "ADD COLUMN";
+ break;
+ case AT_AddColumnRecurse:
+ strtype = "ADD COLUMN (and recurse)";
+ break;
+ case AT_AddColumnToView:
+ strtype = "ADD COLUMN TO VIEW";
+ break;
+ case AT_ColumnDefault:
+ strtype = "ALTER COLUMN SET DEFAULT";
+ break;
+ case AT_CookedColumnDefault:
+ strtype = "ALTER COLUMN SET DEFAULT (precooked)";
+ break;
+ case AT_DropNotNull:
+ strtype = "DROP NOT NULL";
+ break;
+ case AT_SetNotNull:
+ strtype = "SET NOT NULL";
+ break;
+ case AT_CheckNotNull:
+ strtype = "CHECK NOT NULL";
+ break;
+ case AT_SetStatistics:
+ strtype = "SET STATS";
+ break;
+ case AT_SetOptions:
+ strtype = "SET OPTIONS";
+ break;
+ case AT_ResetOptions:
+ strtype = "RESET OPTIONS";
+ break;
+ case AT_SetStorage:
+ strtype = "SET STORAGE";
+ break;
+ case AT_DropColumn:
+ strtype = "DROP COLUMN";
+ break;
+ case AT_DropColumnRecurse:
+ strtype = "DROP COLUMN (and recurse)";
+ break;
+ case AT_AddIndex:
+ strtype = "ADD INDEX";
+ break;
+ case AT_ReAddIndex:
+ strtype = "(re) ADD INDEX";
+ break;
+ case AT_AddConstraint:
+ strtype = "ADD CONSTRAINT";
+ break;
+ case AT_AddConstraintRecurse:
+ strtype = "ADD CONSTRAINT (and recurse)";
+ break;
+ case AT_ReAddConstraint:
+ strtype = "(re) ADD CONSTRAINT";
+ break;
+ case AT_AlterConstraint:
+ strtype = "ALTER CONSTRAINT";
+ break;
+ case AT_ValidateConstraint:
+ strtype = "VALIDATE CONSTRAINT";
+ break;
+ case AT_ValidateConstraintRecurse:
+ strtype = "VALIDATE CONSTRAINT (and recurse)";
+ break;
+ case AT_AddIndexConstraint:
+ strtype = "ADD CONSTRAINT (using index)";
+ break;
+ case AT_DropConstraint:
+ strtype = "DROP CONSTRAINT";
+ break;
+ case AT_DropConstraintRecurse:
+ strtype = "DROP CONSTRAINT (and recurse)";
+ break;
+ case AT_ReAddComment:
+ strtype = "(re) ADD COMMENT";
+ break;
+ case AT_AlterColumnType:
+ strtype = "ALTER COLUMN SET TYPE";
+ break;
+ case AT_AlterColumnGenericOptions:
+ strtype = "ALTER COLUMN SET OPTIONS";
+ break;
+ case AT_ChangeOwner:
+ strtype = "CHANGE OWNER";
+ break;
+ case AT_ClusterOn:
+ strtype = "CLUSTER";
+ break;
+ case AT_DropCluster:
+ strtype = "DROP CLUSTER";
+ break;
+ case AT_SetLogged:
+ strtype = "SET LOGGED";
+ break;
+ case AT_SetUnLogged:
+ strtype = "SET UNLOGGED";
+ break;
+ case AT_DropOids:
+ strtype = "DROP OIDS";
+ break;
+ case AT_SetTableSpace:
+ strtype = "SET TABLESPACE";
+ break;
+ case AT_SetRelOptions:
+ strtype = "SET RELOPTIONS";
+ break;
+ case AT_ResetRelOptions:
+ strtype = "RESET RELOPTIONS";
+ break;
+ case AT_ReplaceRelOptions:
+ strtype = "REPLACE RELOPTIONS";
+ break;
+ case AT_EnableTrig:
+ strtype = "ENABLE TRIGGER";
+ break;
+ case AT_EnableAlwaysTrig:
+ strtype = "ENABLE TRIGGER (always)";
+ break;
+ case AT_EnableReplicaTrig:
+ strtype = "ENABLE TRIGGER (replica)";
+ break;
+ case AT_DisableTrig:
+ strtype = "DISABLE TRIGGER";
+ break;
+ case AT_EnableTrigAll:
+ strtype = "ENABLE TRIGGER (all)";
+ break;
+ case AT_DisableTrigAll:
+ strtype = "DISABLE TRIGGER (all)";
+ break;
+ case AT_EnableTrigUser:
+ strtype = "ENABLE TRIGGER (user)";
+ break;
+ case AT_DisableTrigUser:
+ strtype = "DISABLE TRIGGER (user)";
+ break;
+ case AT_EnableRule:
+ strtype = "ENABLE RULE";
+ break;
+ case AT_EnableAlwaysRule:
+ strtype = "ENABLE RULE (always)";
+ break;
+ case AT_EnableReplicaRule:
+ strtype = "ENABLE RULE (replica)";
+ break;
+ case AT_DisableRule:
+ strtype = "DISABLE RULE";
+ break;
+ case AT_AddInherit:
+ strtype = "ADD INHERIT";
+ break;
+ case AT_DropInherit:
+ strtype = "DROP INHERIT";
+ break;
+ case AT_AddOf:
+ strtype = "OF";
+ break;
+ case AT_DropOf:
+ strtype = "NOT OF";
+ break;
+ case AT_ReplicaIdentity:
+ strtype = "REPLICA IDENTITY";
+ break;
+ case AT_EnableRowSecurity:
+ strtype = "ENABLE ROW SECURITY";
+ break;
+ case AT_DisableRowSecurity:
+ strtype = "DISABLE ROW SECURITY";
+ break;
+ case AT_ForceRowSecurity:
+ strtype = "FORCE ROW SECURITY";
+ break;
+ case AT_NoForceRowSecurity:
+ strtype = "NO FORCE ROW SECURITY";
+ break;
+ case AT_GenericOptions:
+ strtype = "SET OPTIONS";
+ break;
+ default:
+ strtype = "unrecognized";
+ break;
+ }
+
+ astate =
+ accumArrayResult(astate, CStringGetTextDatum(strtype),
+ false, TEXTOID, CurrentMemoryContext);
+ }
+
+ if (astate == NULL)
+ elog(ERROR, "empty alter table subcommand list");
+
+ PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));
+}
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.control b/src/test/modules/test_ddl_deparse/test_ddl_deparse.control
new file mode 100644
index 0000000..09112ee
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.control
@@ -0,0 +1,4 @@
+comment = 'Test code for DDL deparse feature'
+default_version = '1.0'
+module_pathname = '$libdir/test_ddl_deparse'
+relocatable = true
diff --git a/src/test/modules/test_extensions/.gitignore b/src/test/modules/test_extensions/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_extensions/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
new file mode 100644
index 0000000..c3139ab
--- /dev/null
+++ b/src/test/modules/test_extensions/Makefile
@@ -0,0 +1,32 @@
+# src/test/modules/test_extensions/Makefile
+
+MODULE = test_extensions
+PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
+
+EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
+ test_ext7 test_ext8 test_ext_cine test_ext_cor \
+ test_ext_cyclic1 test_ext_cyclic2 \
+ test_ext_evttrig
+DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
+ test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \
+ test_ext7--1.0.sql test_ext7--1.0--2.0.sql test_ext8--1.0.sql \
+ test_ext_cine--1.0.sql test_ext_cine--1.0--1.1.sql \
+ test_ext_cor--1.0.sql \
+ test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
+ test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql
+
+REGRESS = test_extensions test_extdepend
+
+# force C locale for output stability
+NO_LOCALE = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_extensions
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_extensions/expected/test_extdepend.out b/src/test/modules/test_extensions/expected/test_extdepend.out
new file mode 100644
index 0000000..0b62015
--- /dev/null
+++ b/src/test/modules/test_extensions/expected/test_extdepend.out
@@ -0,0 +1,188 @@
+--
+-- test ALTER THING name DEPENDS ON EXTENSION
+--
+-- Common setup for all tests
+CREATE TABLE test_extdep_commands (command text);
+COPY test_extdep_commands FROM stdin;
+SELECT * FROM test_extdep_commands;
+ command
+-------------------------------------------------------------------------
+ CREATE SCHEMA test_ext
+ CREATE EXTENSION test_ext5 SCHEMA test_ext
+ SET search_path TO test_ext
+ CREATE TABLE a (a1 int)
+
+ CREATE FUNCTION b() RETURNS TRIGGER LANGUAGE plpgsql AS +
+ $$ BEGIN NEW.a1 := NEW.a1 + 42; RETURN NEW; END; $$
+ ALTER FUNCTION b() DEPENDS ON EXTENSION test_ext5
+
+ CREATE TRIGGER c BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE b()
+ ALTER TRIGGER c ON a DEPENDS ON EXTENSION test_ext5
+
+ CREATE MATERIALIZED VIEW d AS SELECT * FROM a
+ ALTER MATERIALIZED VIEW d DEPENDS ON EXTENSION test_ext5
+
+ CREATE INDEX e ON a (a1)
+ ALTER INDEX e DEPENDS ON EXTENSION test_ext5
+ RESET search_path
+(17 rows)
+
+-- First, test that dependent objects go away when the extension is dropped.
+SELECT * FROM test_extdep_commands \gexec
+ CREATE SCHEMA test_ext
+ CREATE EXTENSION test_ext5 SCHEMA test_ext
+ SET search_path TO test_ext
+ CREATE TABLE a (a1 int)
+
+ CREATE FUNCTION b() RETURNS TRIGGER LANGUAGE plpgsql AS
+ $$ BEGIN NEW.a1 := NEW.a1 + 42; RETURN NEW; END; $$
+ ALTER FUNCTION b() DEPENDS ON EXTENSION test_ext5
+
+ CREATE TRIGGER c BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE b()
+ ALTER TRIGGER c ON a DEPENDS ON EXTENSION test_ext5
+
+ CREATE MATERIALIZED VIEW d AS SELECT * FROM a
+ ALTER MATERIALIZED VIEW d DEPENDS ON EXTENSION test_ext5
+
+ CREATE INDEX e ON a (a1)
+ ALTER INDEX e DEPENDS ON EXTENSION test_ext5
+ RESET search_path
+-- A dependent object made dependent again has no effect
+ALTER FUNCTION test_ext.b() DEPENDS ON EXTENSION test_ext5;
+-- make sure we have the right dependencies on the extension
+SELECT deptype, p.*
+ FROM pg_depend, pg_identify_object(classid, objid, objsubid) AS p
+ WHERE refclassid = 'pg_extension'::regclass AND
+ refobjid = (SELECT oid FROM pg_extension WHERE extname = 'test_ext5')
+ORDER BY type;
+ deptype | type | schema | name | identity
+---------+-------------------+----------+------+-----------------
+ x | function | test_ext | | test_ext.b()
+ x | index | test_ext | e | test_ext.e
+ x | materialized view | test_ext | d | test_ext.d
+ x | trigger | | | c on test_ext.a
+(4 rows)
+
+DROP EXTENSION test_ext5;
+-- anything still depending on the table?
+SELECT deptype, i.*
+ FROM pg_catalog.pg_depend, pg_identify_object(classid, objid, objsubid) i
+WHERE refclassid='pg_class'::regclass AND
+ refobjid='test_ext.a'::regclass AND NOT deptype IN ('i', 'a');
+ deptype | type | schema | name | identity
+---------+------+--------+------+----------
+(0 rows)
+
+DROP SCHEMA test_ext CASCADE;
+NOTICE: drop cascades to table test_ext.a
+-- Second test: If we drop the table, the objects are dropped too and no
+-- vestige remains in pg_depend.
+SELECT * FROM test_extdep_commands \gexec
+ CREATE SCHEMA test_ext
+ CREATE EXTENSION test_ext5 SCHEMA test_ext
+ SET search_path TO test_ext
+ CREATE TABLE a (a1 int)
+
+ CREATE FUNCTION b() RETURNS TRIGGER LANGUAGE plpgsql AS
+ $$ BEGIN NEW.a1 := NEW.a1 + 42; RETURN NEW; END; $$
+ ALTER FUNCTION b() DEPENDS ON EXTENSION test_ext5
+
+ CREATE TRIGGER c BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE b()
+ ALTER TRIGGER c ON a DEPENDS ON EXTENSION test_ext5
+
+ CREATE MATERIALIZED VIEW d AS SELECT * FROM a
+ ALTER MATERIALIZED VIEW d DEPENDS ON EXTENSION test_ext5
+
+ CREATE INDEX e ON a (a1)
+ ALTER INDEX e DEPENDS ON EXTENSION test_ext5
+ RESET search_path
+DROP TABLE test_ext.a; -- should fail, require cascade
+ERROR: cannot drop table test_ext.a because other objects depend on it
+DETAIL: materialized view test_ext.d depends on table test_ext.a
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE test_ext.a CASCADE;
+NOTICE: drop cascades to materialized view test_ext.d
+-- anything still depending on the extension? Should be only function b()
+SELECT deptype, i.*
+ FROM pg_catalog.pg_depend, pg_identify_object(classid, objid, objsubid) i
+ WHERE refclassid='pg_extension'::regclass AND
+ refobjid=(SELECT oid FROM pg_extension WHERE extname='test_ext5');
+ deptype | type | schema | name | identity
+---------+----------+----------+------+--------------
+ x | function | test_ext | | test_ext.b()
+(1 row)
+
+DROP EXTENSION test_ext5;
+DROP SCHEMA test_ext CASCADE;
+-- Third test: we can drop the objects individually
+SELECT * FROM test_extdep_commands \gexec
+ CREATE SCHEMA test_ext
+ CREATE EXTENSION test_ext5 SCHEMA test_ext
+ SET search_path TO test_ext
+ CREATE TABLE a (a1 int)
+
+ CREATE FUNCTION b() RETURNS TRIGGER LANGUAGE plpgsql AS
+ $$ BEGIN NEW.a1 := NEW.a1 + 42; RETURN NEW; END; $$
+ ALTER FUNCTION b() DEPENDS ON EXTENSION test_ext5
+
+ CREATE TRIGGER c BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE b()
+ ALTER TRIGGER c ON a DEPENDS ON EXTENSION test_ext5
+
+ CREATE MATERIALIZED VIEW d AS SELECT * FROM a
+ ALTER MATERIALIZED VIEW d DEPENDS ON EXTENSION test_ext5
+
+ CREATE INDEX e ON a (a1)
+ ALTER INDEX e DEPENDS ON EXTENSION test_ext5
+ RESET search_path
+SET search_path TO test_ext;
+DROP TRIGGER c ON a;
+DROP FUNCTION b();
+DROP MATERIALIZED VIEW d;
+DROP INDEX e;
+SELECT deptype, i.*
+ FROM pg_catalog.pg_depend, pg_identify_object(classid, objid, objsubid) i
+ WHERE (refclassid='pg_extension'::regclass AND
+ refobjid=(SELECT oid FROM pg_extension WHERE extname='test_ext5'))
+ OR (refclassid='pg_class'::regclass AND refobjid='test_ext.a'::regclass)
+ AND NOT deptype IN ('i', 'a');
+ deptype | type | schema | name | identity
+---------+------+--------+------+----------
+(0 rows)
+
+DROP TABLE a;
+RESET search_path;
+DROP SCHEMA test_ext CASCADE;
+NOTICE: drop cascades to extension test_ext5
+-- Fourth test: we can mark the objects as dependent, then unmark; then the
+-- drop of the extension does nothing
+SELECT * FROM test_extdep_commands \gexec
+ CREATE SCHEMA test_ext
+ CREATE EXTENSION test_ext5 SCHEMA test_ext
+ SET search_path TO test_ext
+ CREATE TABLE a (a1 int)
+
+ CREATE FUNCTION b() RETURNS TRIGGER LANGUAGE plpgsql AS
+ $$ BEGIN NEW.a1 := NEW.a1 + 42; RETURN NEW; END; $$
+ ALTER FUNCTION b() DEPENDS ON EXTENSION test_ext5
+
+ CREATE TRIGGER c BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE b()
+ ALTER TRIGGER c ON a DEPENDS ON EXTENSION test_ext5
+
+ CREATE MATERIALIZED VIEW d AS SELECT * FROM a
+ ALTER MATERIALIZED VIEW d DEPENDS ON EXTENSION test_ext5
+
+ CREATE INDEX e ON a (a1)
+ ALTER INDEX e DEPENDS ON EXTENSION test_ext5
+ RESET search_path
+SET search_path TO test_ext;
+ALTER FUNCTION b() NO DEPENDS ON EXTENSION test_ext5;
+ALTER TRIGGER c ON a NO DEPENDS ON EXTENSION test_ext5;
+ALTER MATERIALIZED VIEW d NO DEPENDS ON EXTENSION test_ext5;
+ALTER INDEX e NO DEPENDS ON EXTENSION test_ext5;
+DROP EXTENSION test_ext5;
+DROP TRIGGER c ON a;
+DROP FUNCTION b();
+DROP MATERIALIZED VIEW d;
+DROP INDEX e;
+DROP SCHEMA test_ext CASCADE;
+NOTICE: drop cascades to table a
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
new file mode 100644
index 0000000..821fed3
--- /dev/null
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -0,0 +1,314 @@
+-- test some errors
+CREATE EXTENSION test_ext1;
+ERROR: required extension "test_ext2" is not installed
+HINT: Use CREATE EXTENSION ... CASCADE to install required extensions too.
+CREATE EXTENSION test_ext1 SCHEMA test_ext1;
+ERROR: schema "test_ext1" does not exist
+CREATE EXTENSION test_ext1 SCHEMA test_ext;
+ERROR: schema "test_ext" does not exist
+CREATE SCHEMA test_ext;
+CREATE EXTENSION test_ext1 SCHEMA test_ext;
+ERROR: extension "test_ext1" must be installed in schema "test_ext1"
+-- finally success
+CREATE EXTENSION test_ext1 SCHEMA test_ext CASCADE;
+NOTICE: installing required extension "test_ext2"
+NOTICE: installing required extension "test_ext3"
+NOTICE: installing required extension "test_ext5"
+NOTICE: installing required extension "test_ext4"
+SELECT extname, nspname, extversion, extrelocatable FROM pg_extension e, pg_namespace n WHERE extname LIKE 'test_ext%' AND e.extnamespace = n.oid ORDER BY 1;
+ extname | nspname | extversion | extrelocatable
+-----------+-----------+------------+----------------
+ test_ext1 | test_ext1 | 1.0 | f
+ test_ext2 | test_ext | 1.0 | t
+ test_ext3 | test_ext | 1.0 | t
+ test_ext4 | test_ext | 1.0 | t
+ test_ext5 | test_ext | 1.0 | t
+(5 rows)
+
+CREATE EXTENSION test_ext_cyclic1 CASCADE;
+NOTICE: installing required extension "test_ext_cyclic2"
+ERROR: cyclic dependency detected between extensions "test_ext_cyclic1" and "test_ext_cyclic2"
+DROP SCHEMA test_ext CASCADE;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to extension test_ext3
+drop cascades to extension test_ext5
+drop cascades to extension test_ext2
+drop cascades to extension test_ext4
+drop cascades to extension test_ext1
+CREATE EXTENSION test_ext6;
+DROP EXTENSION test_ext6;
+CREATE EXTENSION test_ext6;
+-- test dropping of member tables that own extensions:
+-- this table will be absorbed into test_ext7
+create table old_table1 (col1 serial primary key);
+create extension test_ext7;
+\dx+ test_ext7
+Objects in extension "test_ext7"
+ Object description
+-------------------------------
+ sequence ext7_table1_col1_seq
+ sequence ext7_table2_col2_seq
+ sequence old_table1_col1_seq
+ table ext7_table1
+ table ext7_table2
+ table old_table1
+(6 rows)
+
+alter extension test_ext7 update to '2.0';
+\dx+ test_ext7
+Objects in extension "test_ext7"
+ Object description
+-------------------------------
+ sequence ext7_table2_col2_seq
+ table ext7_table2
+(2 rows)
+
+-- test handling of temp objects created by extensions
+create extension test_ext8;
+-- \dx+ would expose a variable pg_temp_nn schema name, so we can't use it here
+select regexp_replace(pg_describe_object(classid, objid, objsubid),
+ 'pg_temp_\d+', 'pg_temp', 'g') as "Object description"
+from pg_depend
+where refclassid = 'pg_extension'::regclass and deptype = 'e' and
+ refobjid = (select oid from pg_extension where extname = 'test_ext8')
+order by 1;
+ Object description
+-----------------------------------------
+ function ext8_even(posint)
+ function pg_temp.ext8_temp_even(posint)
+ table ext8_table1
+ table ext8_temp_table1
+ type posint
+(5 rows)
+
+-- Should be possible to drop and recreate this extension
+drop extension test_ext8;
+create extension test_ext8;
+select regexp_replace(pg_describe_object(classid, objid, objsubid),
+ 'pg_temp_\d+', 'pg_temp', 'g') as "Object description"
+from pg_depend
+where refclassid = 'pg_extension'::regclass and deptype = 'e' and
+ refobjid = (select oid from pg_extension where extname = 'test_ext8')
+order by 1;
+ Object description
+-----------------------------------------
+ function ext8_even(posint)
+ function pg_temp.ext8_temp_even(posint)
+ table ext8_table1
+ table ext8_temp_table1
+ type posint
+(5 rows)
+
+-- here we want to start a new session and wait till old one is gone
+select pg_backend_pid() as oldpid \gset
+\c -
+do 'declare c int = 0;
+begin
+ while (select count(*) from pg_stat_activity where pid = '
+ :'oldpid'
+ ') > 0 loop c := c + 1; perform pg_stat_clear_snapshot(); end loop;
+ raise log ''test_extensions looped % times'', c;
+end';
+-- extension should now contain no temp objects
+\dx+ test_ext8
+Objects in extension "test_ext8"
+ Object description
+----------------------------
+ function ext8_even(posint)
+ table ext8_table1
+ type posint
+(3 rows)
+
+-- dropping it should still work
+drop extension test_ext8;
+-- Test creation of extension in temporary schema with two-phase commit,
+-- which should not work. This function wrapper is useful for portability.
+-- Avoid noise caused by CONTEXT and NOTICE messages including the temporary
+-- schema name.
+\set SHOW_CONTEXT never
+SET client_min_messages TO 'warning';
+-- First enforce presence of temporary schema.
+CREATE TEMP TABLE test_ext4_tab ();
+CREATE OR REPLACE FUNCTION create_extension_with_temp_schema()
+ RETURNS VOID AS $$
+ DECLARE
+ tmpschema text;
+ query text;
+ BEGIN
+ SELECT INTO tmpschema pg_my_temp_schema()::regnamespace;
+ query := 'CREATE EXTENSION test_ext4 SCHEMA ' || tmpschema || ' CASCADE;';
+ RAISE NOTICE 'query %', query;
+ EXECUTE query;
+ END; $$ LANGUAGE plpgsql;
+BEGIN;
+SELECT create_extension_with_temp_schema();
+ create_extension_with_temp_schema
+-----------------------------------
+
+(1 row)
+
+PREPARE TRANSACTION 'twophase_extension';
+ERROR: cannot PREPARE a transaction that has operated on temporary objects
+-- Clean up
+DROP TABLE test_ext4_tab;
+DROP FUNCTION create_extension_with_temp_schema();
+RESET client_min_messages;
+\unset SHOW_CONTEXT
+-- Test case of an event trigger run in an extension upgrade script.
+-- See: https://postgr.es/m/20200902193715.6e0269d4@firost
+CREATE EXTENSION test_ext_evttrig;
+ALTER EXTENSION test_ext_evttrig UPDATE TO '2.0';
+DROP EXTENSION test_ext_evttrig;
+-- It's generally bad style to use CREATE OR REPLACE unnecessarily.
+-- Test what happens if an extension does it anyway.
+-- Replacing a shell type or operator is sort of like CREATE OR REPLACE;
+-- check that too.
+CREATE FUNCTION ext_cor_func() RETURNS text
+ AS $$ SELECT 'ext_cor_func: original'::text $$ LANGUAGE sql;
+CREATE EXTENSION test_ext_cor; -- fail
+ERROR: function ext_cor_func() is not a member of extension "test_ext_cor"
+DETAIL: An extension is not allowed to replace an object that it does not own.
+SELECT ext_cor_func();
+ ext_cor_func
+------------------------
+ ext_cor_func: original
+(1 row)
+
+DROP FUNCTION ext_cor_func();
+CREATE VIEW ext_cor_view AS
+ SELECT 'ext_cor_view: original'::text AS col;
+CREATE EXTENSION test_ext_cor; -- fail
+ERROR: view ext_cor_view is not a member of extension "test_ext_cor"
+DETAIL: An extension is not allowed to replace an object that it does not own.
+SELECT ext_cor_func();
+ERROR: function ext_cor_func() does not exist
+LINE 1: SELECT ext_cor_func();
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT * FROM ext_cor_view;
+ col
+------------------------
+ ext_cor_view: original
+(1 row)
+
+DROP VIEW ext_cor_view;
+CREATE TYPE test_ext_type;
+CREATE EXTENSION test_ext_cor; -- fail
+ERROR: type test_ext_type is not a member of extension "test_ext_cor"
+DETAIL: An extension is not allowed to replace an object that it does not own.
+DROP TYPE test_ext_type;
+-- this makes a shell "point <<@@ polygon" operator too
+CREATE OPERATOR @@>> ( PROCEDURE = poly_contain_pt,
+ LEFTARG = polygon, RIGHTARG = point,
+ COMMUTATOR = <<@@ );
+CREATE EXTENSION test_ext_cor; -- fail
+ERROR: operator <<@@(point,polygon) is not a member of extension "test_ext_cor"
+DETAIL: An extension is not allowed to replace an object that it does not own.
+DROP OPERATOR <<@@ (point, polygon);
+CREATE EXTENSION test_ext_cor; -- now it should work
+SELECT ext_cor_func();
+ ext_cor_func
+------------------------------
+ ext_cor_func: from extension
+(1 row)
+
+SELECT * FROM ext_cor_view;
+ col
+------------------------------
+ ext_cor_view: from extension
+(1 row)
+
+SELECT 'x'::test_ext_type;
+ test_ext_type
+---------------
+ x
+(1 row)
+
+SELECT point(0,0) <<@@ polygon(circle(point(0,0),1));
+ ?column?
+----------
+ t
+(1 row)
+
+\dx+ test_ext_cor
+Objects in extension "test_ext_cor"
+ Object description
+------------------------------
+ function ext_cor_func()
+ operator <<@@(point,polygon)
+ type test_ext_type
+ view ext_cor_view
+(4 rows)
+
+--
+-- CREATE IF NOT EXISTS is an entirely unsound thing for an extension
+-- to be doing, but let's at least plug the major security hole in it.
+--
+CREATE COLLATION ext_cine_coll
+ ( LC_COLLATE = "C", LC_CTYPE = "C" );
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: collation ext_cine_coll is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP COLLATION ext_cine_coll;
+CREATE MATERIALIZED VIEW ext_cine_mv AS SELECT 11 AS f1;
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: materialized view ext_cine_mv is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP MATERIALIZED VIEW ext_cine_mv;
+CREATE FOREIGN DATA WRAPPER dummy;
+CREATE SERVER ext_cine_srv FOREIGN DATA WRAPPER dummy;
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: server ext_cine_srv is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP SERVER ext_cine_srv;
+CREATE SCHEMA ext_cine_schema;
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: schema ext_cine_schema is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP SCHEMA ext_cine_schema;
+CREATE SEQUENCE ext_cine_seq;
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: sequence ext_cine_seq is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP SEQUENCE ext_cine_seq;
+CREATE TABLE ext_cine_tab1 (x int);
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: table ext_cine_tab1 is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP TABLE ext_cine_tab1;
+CREATE TABLE ext_cine_tab2 AS SELECT 42 AS y;
+CREATE EXTENSION test_ext_cine; -- fail
+ERROR: table ext_cine_tab2 is not a member of extension "test_ext_cine"
+DETAIL: An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.
+DROP TABLE ext_cine_tab2;
+CREATE EXTENSION test_ext_cine;
+\dx+ test_ext_cine
+Objects in extension "test_ext_cine"
+ Object description
+-----------------------------------
+ collation ext_cine_coll
+ foreign-data wrapper ext_cine_fdw
+ materialized view ext_cine_mv
+ schema ext_cine_schema
+ sequence ext_cine_seq
+ server ext_cine_srv
+ table ext_cine_tab1
+ table ext_cine_tab2
+(8 rows)
+
+ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
+\dx+ test_ext_cine
+Objects in extension "test_ext_cine"
+ Object description
+-----------------------------------
+ collation ext_cine_coll
+ foreign-data wrapper ext_cine_fdw
+ materialized view ext_cine_mv
+ schema ext_cine_schema
+ sequence ext_cine_seq
+ server ext_cine_srv
+ table ext_cine_tab1
+ table ext_cine_tab2
+ table ext_cine_tab3
+(9 rows)
+
diff --git a/src/test/modules/test_extensions/sql/test_extdepend.sql b/src/test/modules/test_extensions/sql/test_extdepend.sql
new file mode 100644
index 0000000..63240a1
--- /dev/null
+++ b/src/test/modules/test_extensions/sql/test_extdepend.sql
@@ -0,0 +1,90 @@
+--
+-- test ALTER THING name DEPENDS ON EXTENSION
+--
+
+-- Common setup for all tests
+CREATE TABLE test_extdep_commands (command text);
+COPY test_extdep_commands FROM stdin;
+ CREATE SCHEMA test_ext
+ CREATE EXTENSION test_ext5 SCHEMA test_ext
+ SET search_path TO test_ext
+ CREATE TABLE a (a1 int)
+
+ CREATE FUNCTION b() RETURNS TRIGGER LANGUAGE plpgsql AS\n $$ BEGIN NEW.a1 := NEW.a1 + 42; RETURN NEW; END; $$
+ ALTER FUNCTION b() DEPENDS ON EXTENSION test_ext5
+
+ CREATE TRIGGER c BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE b()
+ ALTER TRIGGER c ON a DEPENDS ON EXTENSION test_ext5
+
+ CREATE MATERIALIZED VIEW d AS SELECT * FROM a
+ ALTER MATERIALIZED VIEW d DEPENDS ON EXTENSION test_ext5
+
+ CREATE INDEX e ON a (a1)
+ ALTER INDEX e DEPENDS ON EXTENSION test_ext5
+ RESET search_path
+\.
+
+SELECT * FROM test_extdep_commands;
+-- First, test that dependent objects go away when the extension is dropped.
+SELECT * FROM test_extdep_commands \gexec
+-- A dependent object made dependent again has no effect
+ALTER FUNCTION test_ext.b() DEPENDS ON EXTENSION test_ext5;
+-- make sure we have the right dependencies on the extension
+SELECT deptype, p.*
+ FROM pg_depend, pg_identify_object(classid, objid, objsubid) AS p
+ WHERE refclassid = 'pg_extension'::regclass AND
+ refobjid = (SELECT oid FROM pg_extension WHERE extname = 'test_ext5')
+ORDER BY type;
+DROP EXTENSION test_ext5;
+-- anything still depending on the table?
+SELECT deptype, i.*
+ FROM pg_catalog.pg_depend, pg_identify_object(classid, objid, objsubid) i
+WHERE refclassid='pg_class'::regclass AND
+ refobjid='test_ext.a'::regclass AND NOT deptype IN ('i', 'a');
+DROP SCHEMA test_ext CASCADE;
+
+-- Second test: If we drop the table, the objects are dropped too and no
+-- vestige remains in pg_depend.
+SELECT * FROM test_extdep_commands \gexec
+DROP TABLE test_ext.a; -- should fail, require cascade
+DROP TABLE test_ext.a CASCADE;
+-- anything still depending on the extension? Should be only function b()
+SELECT deptype, i.*
+ FROM pg_catalog.pg_depend, pg_identify_object(classid, objid, objsubid) i
+ WHERE refclassid='pg_extension'::regclass AND
+ refobjid=(SELECT oid FROM pg_extension WHERE extname='test_ext5');
+DROP EXTENSION test_ext5;
+DROP SCHEMA test_ext CASCADE;
+
+-- Third test: we can drop the objects individually
+SELECT * FROM test_extdep_commands \gexec
+SET search_path TO test_ext;
+DROP TRIGGER c ON a;
+DROP FUNCTION b();
+DROP MATERIALIZED VIEW d;
+DROP INDEX e;
+
+SELECT deptype, i.*
+ FROM pg_catalog.pg_depend, pg_identify_object(classid, objid, objsubid) i
+ WHERE (refclassid='pg_extension'::regclass AND
+ refobjid=(SELECT oid FROM pg_extension WHERE extname='test_ext5'))
+ OR (refclassid='pg_class'::regclass AND refobjid='test_ext.a'::regclass)
+ AND NOT deptype IN ('i', 'a');
+DROP TABLE a;
+RESET search_path;
+DROP SCHEMA test_ext CASCADE;
+
+-- Fourth test: we can mark the objects as dependent, then unmark; then the
+-- drop of the extension does nothing
+SELECT * FROM test_extdep_commands \gexec
+SET search_path TO test_ext;
+ALTER FUNCTION b() NO DEPENDS ON EXTENSION test_ext5;
+ALTER TRIGGER c ON a NO DEPENDS ON EXTENSION test_ext5;
+ALTER MATERIALIZED VIEW d NO DEPENDS ON EXTENSION test_ext5;
+ALTER INDEX e NO DEPENDS ON EXTENSION test_ext5;
+DROP EXTENSION test_ext5;
+DROP TRIGGER c ON a;
+DROP FUNCTION b();
+DROP MATERIALIZED VIEW d;
+DROP INDEX e;
+DROP SCHEMA test_ext CASCADE;
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
new file mode 100644
index 0000000..41b6cdd
--- /dev/null
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -0,0 +1,211 @@
+-- test some errors
+CREATE EXTENSION test_ext1;
+CREATE EXTENSION test_ext1 SCHEMA test_ext1;
+CREATE EXTENSION test_ext1 SCHEMA test_ext;
+CREATE SCHEMA test_ext;
+CREATE EXTENSION test_ext1 SCHEMA test_ext;
+
+-- finally success
+CREATE EXTENSION test_ext1 SCHEMA test_ext CASCADE;
+
+SELECT extname, nspname, extversion, extrelocatable FROM pg_extension e, pg_namespace n WHERE extname LIKE 'test_ext%' AND e.extnamespace = n.oid ORDER BY 1;
+
+CREATE EXTENSION test_ext_cyclic1 CASCADE;
+
+DROP SCHEMA test_ext CASCADE;
+
+CREATE EXTENSION test_ext6;
+DROP EXTENSION test_ext6;
+CREATE EXTENSION test_ext6;
+
+-- test dropping of member tables that own extensions:
+-- this table will be absorbed into test_ext7
+create table old_table1 (col1 serial primary key);
+create extension test_ext7;
+\dx+ test_ext7
+alter extension test_ext7 update to '2.0';
+\dx+ test_ext7
+
+-- test handling of temp objects created by extensions
+create extension test_ext8;
+
+-- \dx+ would expose a variable pg_temp_nn schema name, so we can't use it here
+select regexp_replace(pg_describe_object(classid, objid, objsubid),
+ 'pg_temp_\d+', 'pg_temp', 'g') as "Object description"
+from pg_depend
+where refclassid = 'pg_extension'::regclass and deptype = 'e' and
+ refobjid = (select oid from pg_extension where extname = 'test_ext8')
+order by 1;
+
+-- Should be possible to drop and recreate this extension
+drop extension test_ext8;
+create extension test_ext8;
+
+select regexp_replace(pg_describe_object(classid, objid, objsubid),
+ 'pg_temp_\d+', 'pg_temp', 'g') as "Object description"
+from pg_depend
+where refclassid = 'pg_extension'::regclass and deptype = 'e' and
+ refobjid = (select oid from pg_extension where extname = 'test_ext8')
+order by 1;
+
+-- here we want to start a new session and wait till old one is gone
+select pg_backend_pid() as oldpid \gset
+\c -
+do 'declare c int = 0;
+begin
+ while (select count(*) from pg_stat_activity where pid = '
+ :'oldpid'
+ ') > 0 loop c := c + 1; perform pg_stat_clear_snapshot(); end loop;
+ raise log ''test_extensions looped % times'', c;
+end';
+
+-- extension should now contain no temp objects
+\dx+ test_ext8
+
+-- dropping it should still work
+drop extension test_ext8;
+
+-- Test creation of extension in temporary schema with two-phase commit,
+-- which should not work. This function wrapper is useful for portability.
+
+-- Avoid noise caused by CONTEXT and NOTICE messages including the temporary
+-- schema name.
+\set SHOW_CONTEXT never
+SET client_min_messages TO 'warning';
+-- First enforce presence of temporary schema.
+CREATE TEMP TABLE test_ext4_tab ();
+CREATE OR REPLACE FUNCTION create_extension_with_temp_schema()
+ RETURNS VOID AS $$
+ DECLARE
+ tmpschema text;
+ query text;
+ BEGIN
+ SELECT INTO tmpschema pg_my_temp_schema()::regnamespace;
+ query := 'CREATE EXTENSION test_ext4 SCHEMA ' || tmpschema || ' CASCADE;';
+ RAISE NOTICE 'query %', query;
+ EXECUTE query;
+ END; $$ LANGUAGE plpgsql;
+BEGIN;
+SELECT create_extension_with_temp_schema();
+PREPARE TRANSACTION 'twophase_extension';
+-- Clean up
+DROP TABLE test_ext4_tab;
+DROP FUNCTION create_extension_with_temp_schema();
+RESET client_min_messages;
+\unset SHOW_CONTEXT
+
+-- Test case of an event trigger run in an extension upgrade script.
+-- See: https://postgr.es/m/20200902193715.6e0269d4@firost
+CREATE EXTENSION test_ext_evttrig;
+ALTER EXTENSION test_ext_evttrig UPDATE TO '2.0';
+DROP EXTENSION test_ext_evttrig;
+
+-- It's generally bad style to use CREATE OR REPLACE unnecessarily.
+-- Test what happens if an extension does it anyway.
+-- Replacing a shell type or operator is sort of like CREATE OR REPLACE;
+-- check that too.
+
+CREATE FUNCTION ext_cor_func() RETURNS text
+ AS $$ SELECT 'ext_cor_func: original'::text $$ LANGUAGE sql;
+
+CREATE EXTENSION test_ext_cor; -- fail
+
+SELECT ext_cor_func();
+
+DROP FUNCTION ext_cor_func();
+
+CREATE VIEW ext_cor_view AS
+ SELECT 'ext_cor_view: original'::text AS col;
+
+CREATE EXTENSION test_ext_cor; -- fail
+
+SELECT ext_cor_func();
+
+SELECT * FROM ext_cor_view;
+
+DROP VIEW ext_cor_view;
+
+CREATE TYPE test_ext_type;
+
+CREATE EXTENSION test_ext_cor; -- fail
+
+DROP TYPE test_ext_type;
+
+-- this makes a shell "point <<@@ polygon" operator too
+CREATE OPERATOR @@>> ( PROCEDURE = poly_contain_pt,
+ LEFTARG = polygon, RIGHTARG = point,
+ COMMUTATOR = <<@@ );
+
+CREATE EXTENSION test_ext_cor; -- fail
+
+DROP OPERATOR <<@@ (point, polygon);
+
+CREATE EXTENSION test_ext_cor; -- now it should work
+
+SELECT ext_cor_func();
+
+SELECT * FROM ext_cor_view;
+
+SELECT 'x'::test_ext_type;
+
+SELECT point(0,0) <<@@ polygon(circle(point(0,0),1));
+
+\dx+ test_ext_cor
+
+--
+-- CREATE IF NOT EXISTS is an entirely unsound thing for an extension
+-- to be doing, but let's at least plug the major security hole in it.
+--
+
+CREATE COLLATION ext_cine_coll
+ ( LC_COLLATE = "C", LC_CTYPE = "C" );
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP COLLATION ext_cine_coll;
+
+CREATE MATERIALIZED VIEW ext_cine_mv AS SELECT 11 AS f1;
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP MATERIALIZED VIEW ext_cine_mv;
+
+CREATE FOREIGN DATA WRAPPER dummy;
+
+CREATE SERVER ext_cine_srv FOREIGN DATA WRAPPER dummy;
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP SERVER ext_cine_srv;
+
+CREATE SCHEMA ext_cine_schema;
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP SCHEMA ext_cine_schema;
+
+CREATE SEQUENCE ext_cine_seq;
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP SEQUENCE ext_cine_seq;
+
+CREATE TABLE ext_cine_tab1 (x int);
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP TABLE ext_cine_tab1;
+
+CREATE TABLE ext_cine_tab2 AS SELECT 42 AS y;
+
+CREATE EXTENSION test_ext_cine; -- fail
+
+DROP TABLE ext_cine_tab2;
+
+CREATE EXTENSION test_ext_cine;
+
+\dx+ test_ext_cine
+
+ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
+
+\dx+ test_ext_cine
diff --git a/src/test/modules/test_extensions/test_ext1--1.0.sql b/src/test/modules/test_extensions/test_ext1--1.0.sql
new file mode 100644
index 0000000..9a4bb1b
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext1--1.0.sql
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext1--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext1" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext1.control b/src/test/modules/test_extensions/test_ext1.control
new file mode 100644
index 0000000..9c069df
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext1.control
@@ -0,0 +1,5 @@
+comment = 'Test extension 1'
+default_version = '1.0'
+schema = 'test_ext1'
+relocatable = false
+requires = 'test_ext2,test_ext4'
diff --git a/src/test/modules/test_extensions/test_ext2--1.0.sql b/src/test/modules/test_extensions/test_ext2--1.0.sql
new file mode 100644
index 0000000..0f6d4ec
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext2--1.0.sql
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext2" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext2.control b/src/test/modules/test_extensions/test_ext2.control
new file mode 100644
index 0000000..946b7d5
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext2.control
@@ -0,0 +1,4 @@
+comment = 'Test extension 2'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext3,test_ext5'
diff --git a/src/test/modules/test_extensions/test_ext3--1.0.sql b/src/test/modules/test_extensions/test_ext3--1.0.sql
new file mode 100644
index 0000000..4fcb63d
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext3--1.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_extensions/test_ext3--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext3" to load this file. \quit
+
+CREATE TABLE test_ext3_table (col_old INT);
+
+ALTER TABLE test_ext3_table RENAME col_old TO col_new;
+
+UPDATE test_ext3_table SET col_new = 0;
diff --git a/src/test/modules/test_extensions/test_ext3.control b/src/test/modules/test_extensions/test_ext3.control
new file mode 100644
index 0000000..5f1afe7
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext3.control
@@ -0,0 +1,3 @@
+comment = 'Test extension 3'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext4--1.0.sql b/src/test/modules/test_extensions/test_ext4--1.0.sql
new file mode 100644
index 0000000..19f051f
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext4--1.0.sql
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext4--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext4" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext4.control b/src/test/modules/test_extensions/test_ext4.control
new file mode 100644
index 0000000..fc62591
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext4.control
@@ -0,0 +1,4 @@
+comment = 'Test extension 4'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext5'
diff --git a/src/test/modules/test_extensions/test_ext5--1.0.sql b/src/test/modules/test_extensions/test_ext5--1.0.sql
new file mode 100644
index 0000000..baf6ef8
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext5--1.0.sql
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext5--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext5" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext5.control b/src/test/modules/test_extensions/test_ext5.control
new file mode 100644
index 0000000..51bc57e
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext5.control
@@ -0,0 +1,3 @@
+comment = 'Test extension 5'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext6--1.0.sql b/src/test/modules/test_extensions/test_ext6--1.0.sql
new file mode 100644
index 0000000..65a4fc5
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext6--1.0.sql
@@ -0,0 +1 @@
+grant usage on schema @extschema@ to public;
diff --git a/src/test/modules/test_extensions/test_ext6.control b/src/test/modules/test_extensions/test_ext6.control
new file mode 100644
index 0000000..04b2146
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext6.control
@@ -0,0 +1,5 @@
+comment = 'test_ext6'
+default_version = '1.0'
+relocatable = false
+superuser = true
+schema = 'test_ext6'
diff --git a/src/test/modules/test_extensions/test_ext7--1.0--2.0.sql b/src/test/modules/test_extensions/test_ext7--1.0--2.0.sql
new file mode 100644
index 0000000..50e3dca
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext7--1.0--2.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/test_extensions/test_ext7--1.0--2.0.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION test_ext7 UPDATE TO '2.0'" to load this file. \quit
+
+-- drop some tables with serial columns
+drop table ext7_table1;
+drop table old_table1;
diff --git a/src/test/modules/test_extensions/test_ext7--1.0.sql b/src/test/modules/test_extensions/test_ext7--1.0.sql
new file mode 100644
index 0000000..0c2d72a
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext7--1.0.sql
@@ -0,0 +1,13 @@
+/* src/test/modules/test_extensions/test_ext7--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext7" to load this file. \quit
+
+-- link some existing serial-owning table to the extension
+alter extension test_ext7 add table old_table1;
+alter extension test_ext7 add sequence old_table1_col1_seq;
+
+-- ordinary member tables with serial columns
+create table ext7_table1 (col1 serial primary key);
+
+create table ext7_table2 (col2 serial primary key);
diff --git a/src/test/modules/test_extensions/test_ext7.control b/src/test/modules/test_extensions/test_ext7.control
new file mode 100644
index 0000000..b58df53
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext7.control
@@ -0,0 +1,4 @@
+comment = 'Test extension 7'
+default_version = '1.0'
+schema = 'public'
+relocatable = false
diff --git a/src/test/modules/test_extensions/test_ext8--1.0.sql b/src/test/modules/test_extensions/test_ext8--1.0.sql
new file mode 100644
index 0000000..1561ffe
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext8--1.0.sql
@@ -0,0 +1,21 @@
+/* src/test/modules/test_extensions/test_ext8--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext8" to load this file. \quit
+
+-- create some random data type
+create domain posint as int check (value > 0);
+
+-- use it in regular and temporary tables and functions
+
+create table ext8_table1 (f1 posint);
+
+create temp table ext8_temp_table1 (f1 posint);
+
+create function ext8_even (posint) returns bool as
+ 'select ($1 % 2) = 0' language sql;
+
+create function pg_temp.ext8_temp_even (posint) returns bool as
+ 'select ($1 % 2) = 0' language sql;
+
+-- we intentionally don't drop the temp objects before exiting
diff --git a/src/test/modules/test_extensions/test_ext8.control b/src/test/modules/test_extensions/test_ext8.control
new file mode 100644
index 0000000..70f8caa
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext8.control
@@ -0,0 +1,4 @@
+comment = 'Test extension 8'
+default_version = '1.0'
+schema = 'public'
+relocatable = false
diff --git a/src/test/modules/test_extensions/test_ext_cine--1.0--1.1.sql b/src/test/modules/test_extensions/test_ext_cine--1.0--1.1.sql
new file mode 100644
index 0000000..6dadfd2
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cine--1.0--1.1.sql
@@ -0,0 +1,26 @@
+/* src/test/modules/test_extensions/test_ext_cine--1.0--1.1.sql */
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION test_ext_cine UPDATE TO '1.1'" to load this file. \quit
+
+--
+-- These are the same commands as in the 1.0 script; we expect them
+-- to do nothing.
+--
+
+CREATE COLLATION IF NOT EXISTS ext_cine_coll
+ ( LC_COLLATE = "POSIX", LC_CTYPE = "POSIX" );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS ext_cine_mv AS SELECT 42 AS f1;
+
+CREATE SERVER IF NOT EXISTS ext_cine_srv FOREIGN DATA WRAPPER ext_cine_fdw;
+
+CREATE SCHEMA IF NOT EXISTS ext_cine_schema;
+
+CREATE SEQUENCE IF NOT EXISTS ext_cine_seq;
+
+CREATE TABLE IF NOT EXISTS ext_cine_tab1 (x int);
+
+CREATE TABLE IF NOT EXISTS ext_cine_tab2 AS SELECT 42 AS y;
+
+-- just to verify the script ran
+CREATE TABLE ext_cine_tab3 (z int);
diff --git a/src/test/modules/test_extensions/test_ext_cine--1.0.sql b/src/test/modules/test_extensions/test_ext_cine--1.0.sql
new file mode 100644
index 0000000..01408ff
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cine--1.0.sql
@@ -0,0 +1,25 @@
+/* src/test/modules/test_extensions/test_ext_cine--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_cine" to load this file. \quit
+
+--
+-- CREATE IF NOT EXISTS is an entirely unsound thing for an extension
+-- to be doing, but let's at least plug the major security hole in it.
+--
+
+CREATE COLLATION IF NOT EXISTS ext_cine_coll
+ ( LC_COLLATE = "POSIX", LC_CTYPE = "POSIX" );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS ext_cine_mv AS SELECT 42 AS f1;
+
+CREATE FOREIGN DATA WRAPPER ext_cine_fdw;
+
+CREATE SERVER IF NOT EXISTS ext_cine_srv FOREIGN DATA WRAPPER ext_cine_fdw;
+
+CREATE SCHEMA IF NOT EXISTS ext_cine_schema;
+
+CREATE SEQUENCE IF NOT EXISTS ext_cine_seq;
+
+CREATE TABLE IF NOT EXISTS ext_cine_tab1 (x int);
+
+CREATE TABLE IF NOT EXISTS ext_cine_tab2 AS SELECT 42 AS y;
diff --git a/src/test/modules/test_extensions/test_ext_cine.control b/src/test/modules/test_extensions/test_ext_cine.control
new file mode 100644
index 0000000..ced713b
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cine.control
@@ -0,0 +1,3 @@
+comment = 'Test extension using CREATE IF NOT EXISTS'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext_cor--1.0.sql b/src/test/modules/test_extensions/test_ext_cor--1.0.sql
new file mode 100644
index 0000000..2e8d89c
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cor--1.0.sql
@@ -0,0 +1,20 @@
+/* src/test/modules/test_extensions/test_ext_cor--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_cor" to load this file. \quit
+
+-- It's generally bad style to use CREATE OR REPLACE unnecessarily.
+-- Test what happens if an extension does it anyway.
+
+CREATE OR REPLACE FUNCTION ext_cor_func() RETURNS text
+ AS $$ SELECT 'ext_cor_func: from extension'::text $$ LANGUAGE sql;
+
+CREATE OR REPLACE VIEW ext_cor_view AS
+ SELECT 'ext_cor_view: from extension'::text AS col;
+
+-- These are for testing replacement of a shell type/operator, which works
+-- enough like an implicit OR REPLACE to be important to check.
+
+CREATE TYPE test_ext_type AS ENUM('x', 'y');
+
+CREATE OPERATOR <<@@ ( PROCEDURE = pt_contained_poly,
+ LEFTARG = point, RIGHTARG = polygon );
diff --git a/src/test/modules/test_extensions/test_ext_cor.control b/src/test/modules/test_extensions/test_ext_cor.control
new file mode 100644
index 0000000..0e972e5
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cor.control
@@ -0,0 +1,3 @@
+comment = 'Test extension using CREATE OR REPLACE'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext_cyclic1--1.0.sql b/src/test/modules/test_extensions/test_ext_cyclic1--1.0.sql
new file mode 100644
index 0000000..81bdaf4
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cyclic1--1.0.sql
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext_cyclic1--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_cyclic1" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext_cyclic1.control b/src/test/modules/test_extensions/test_ext_cyclic1.control
new file mode 100644
index 0000000..aaab403
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cyclic1.control
@@ -0,0 +1,4 @@
+comment = 'Test extension cyclic 1'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_cyclic2'
diff --git a/src/test/modules/test_extensions/test_ext_cyclic2--1.0.sql b/src/test/modules/test_extensions/test_ext_cyclic2--1.0.sql
new file mode 100644
index 0000000..ae2b3e9
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cyclic2--1.0.sql
@@ -0,0 +1,3 @@
+/* src/test/modules/test_extensions/test_ext_cyclic2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_cyclic2" to load this file. \quit
diff --git a/src/test/modules/test_extensions/test_ext_cyclic2.control b/src/test/modules/test_extensions/test_ext_cyclic2.control
new file mode 100644
index 0000000..1e28f96
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_cyclic2.control
@@ -0,0 +1,4 @@
+comment = 'Test extension cyclic 2'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_cyclic1'
diff --git a/src/test/modules/test_extensions/test_ext_evttrig--1.0--2.0.sql b/src/test/modules/test_extensions/test_ext_evttrig--1.0--2.0.sql
new file mode 100644
index 0000000..fdd2f35
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_evttrig--1.0--2.0.sql
@@ -0,0 +1,7 @@
+/* src/test/modules/test_extensions/test_event_trigger--1.0--2.0.sql */
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION test_event_trigger UPDATE TO '2.0'" to load this file. \quit
+
+-- Test extension upgrade with event trigger.
+ALTER EVENT TRIGGER table_rewrite_trg DISABLE;
+ALTER TABLE t DROP COLUMN id;
diff --git a/src/test/modules/test_extensions/test_ext_evttrig--1.0.sql b/src/test/modules/test_extensions/test_ext_evttrig--1.0.sql
new file mode 100644
index 0000000..0071712
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_evttrig--1.0.sql
@@ -0,0 +1,16 @@
+/* src/test/modules/test_extensions/test_event_trigger--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_event_trigger" to load this file. \quit
+
+-- Base table with event trigger, used in a regression test involving
+-- extension upgrades.
+CREATE TABLE t (id text);
+CREATE OR REPLACE FUNCTION _evt_table_rewrite_fnct()
+RETURNS EVENT_TRIGGER LANGUAGE plpgsql AS
+$$
+ BEGIN
+ END;
+$$;
+CREATE EVENT TRIGGER table_rewrite_trg
+ ON table_rewrite
+ EXECUTE PROCEDURE _evt_table_rewrite_fnct();
diff --git a/src/test/modules/test_extensions/test_ext_evttrig.control b/src/test/modules/test_extensions/test_ext_evttrig.control
new file mode 100644
index 0000000..915fae6
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_evttrig.control
@@ -0,0 +1,3 @@
+comment = 'Test extension - event trigger'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_ginpostinglist/.gitignore b/src/test/modules/test_ginpostinglist/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_ginpostinglist/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_ginpostinglist/Makefile b/src/test/modules/test_ginpostinglist/Makefile
new file mode 100644
index 0000000..51b941b
--- /dev/null
+++ b/src/test/modules/test_ginpostinglist/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_ginpostinglist/Makefile
+
+MODULE_big = test_ginpostinglist
+OBJS = \
+ $(WIN32RES) \
+ test_ginpostinglist.o
+PGFILEDESC = "test_ginpostinglist - test code for src/backend/access/gin//ginpostinglist.c"
+
+EXTENSION = test_ginpostinglist
+DATA = test_ginpostinglist--1.0.sql
+
+REGRESS = test_ginpostinglist
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_ginpostinglist
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_ginpostinglist/README b/src/test/modules/test_ginpostinglist/README
new file mode 100644
index 0000000..66684dd
--- /dev/null
+++ b/src/test/modules/test_ginpostinglist/README
@@ -0,0 +1,2 @@
+test_ginpostinglist contains unit tests for the GIN posting list code in
+src/backend/access/gin/ginpostinglist.c.
diff --git a/src/test/modules/test_ginpostinglist/expected/test_ginpostinglist.out b/src/test/modules/test_ginpostinglist/expected/test_ginpostinglist.out
new file mode 100644
index 0000000..4d0beae
--- /dev/null
+++ b/src/test/modules/test_ginpostinglist/expected/test_ginpostinglist.out
@@ -0,0 +1,19 @@
+CREATE EXTENSION test_ginpostinglist;
+--
+-- All the logic is in the test_ginpostinglist() function. It will throw
+-- a error if something fails.
+--
+SELECT test_ginpostinglist();
+NOTICE: testing with (0, 1), (0, 2), max 14 bytes
+NOTICE: encoded 2 item pointers to 10 bytes
+NOTICE: testing with (0, 1), (0, 291), max 14 bytes
+NOTICE: encoded 2 item pointers to 10 bytes
+NOTICE: testing with (0, 1), (4294967294, 291), max 14 bytes
+NOTICE: encoded 1 item pointers to 8 bytes
+NOTICE: testing with (0, 1), (4294967294, 291), max 16 bytes
+NOTICE: encoded 2 item pointers to 16 bytes
+ test_ginpostinglist
+---------------------
+
+(1 row)
+
diff --git a/src/test/modules/test_ginpostinglist/sql/test_ginpostinglist.sql b/src/test/modules/test_ginpostinglist/sql/test_ginpostinglist.sql
new file mode 100644
index 0000000..b8cab7a
--- /dev/null
+++ b/src/test/modules/test_ginpostinglist/sql/test_ginpostinglist.sql
@@ -0,0 +1,7 @@
+CREATE EXTENSION test_ginpostinglist;
+
+--
+-- All the logic is in the test_ginpostinglist() function. It will throw
+-- a error if something fails.
+--
+SELECT test_ginpostinglist();
diff --git a/src/test/modules/test_ginpostinglist/test_ginpostinglist--1.0.sql b/src/test/modules/test_ginpostinglist/test_ginpostinglist--1.0.sql
new file mode 100644
index 0000000..37396a4
--- /dev/null
+++ b/src/test/modules/test_ginpostinglist/test_ginpostinglist--1.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/test_ginpostinglist/test_ginpostinglist--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ginpostinglist" to load this file. \quit
+
+CREATE FUNCTION test_ginpostinglist()
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_ginpostinglist/test_ginpostinglist.c b/src/test/modules/test_ginpostinglist/test_ginpostinglist.c
new file mode 100644
index 0000000..7ce515f
--- /dev/null
+++ b/src/test/modules/test_ginpostinglist/test_ginpostinglist.c
@@ -0,0 +1,96 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_ginpostinglist.c
+ * Test varbyte-encoding in ginpostinglist.c
+ *
+ * Copyright (c) 2019-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_ginpostinglist/test_ginpostinglist.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/gin_private.h"
+#include "access/ginblock.h"
+#include "access/htup_details.h"
+#include "fmgr.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(test_ginpostinglist);
+
+/*
+ * Encodes a pair of TIDs, and decodes it back. The first TID is always
+ * (0, 1), the second one is formed from the blk/off arguments. The 'maxsize'
+ * argument is passed to ginCompressPostingList(); it can be used to test the
+ * overflow checks.
+ *
+ * The reason that we test a pair, instead of just a single TID, is that
+ * the GinPostingList stores the first TID as is, and the varbyte-encoding
+ * is only used for the deltas between TIDs. So testing a single TID would
+ * not exercise the varbyte encoding at all.
+ *
+ * This function prints NOTICEs to describe what is tested, and how large the
+ * resulting GinPostingList is. Any incorrect results, e.g. if the encode +
+ * decode round trip doesn't return the original input, are reported as
+ * ERRORs.
+ */
+static void
+test_itemptr_pair(BlockNumber blk, OffsetNumber off, int maxsize)
+{
+ ItemPointerData orig_itemptrs[2];
+ ItemPointer decoded_itemptrs;
+ GinPostingList *pl;
+ int nwritten;
+ int ndecoded;
+
+ elog(NOTICE, "testing with (%u, %d), (%u, %d), max %d bytes",
+ 0, 1, blk, off, maxsize);
+ ItemPointerSet(&orig_itemptrs[0], 0, 1);
+ ItemPointerSet(&orig_itemptrs[1], blk, off);
+
+ /* Encode, and decode it back */
+ pl = ginCompressPostingList(orig_itemptrs, 2, maxsize, &nwritten);
+ elog(NOTICE, "encoded %d item pointers to %zu bytes",
+ nwritten, SizeOfGinPostingList(pl));
+
+ if (SizeOfGinPostingList(pl) > maxsize)
+ elog(ERROR, "overflow: result was %zu bytes, max %d",
+ SizeOfGinPostingList(pl), maxsize);
+
+ decoded_itemptrs = ginPostingListDecode(pl, &ndecoded);
+ if (nwritten != ndecoded)
+ elog(NOTICE, "encoded %d itemptrs, %d came back", nwritten, ndecoded);
+
+ /* Check the result */
+ if (!ItemPointerEquals(&orig_itemptrs[0], &decoded_itemptrs[0]))
+ elog(ERROR, "mismatch on first itemptr: (%u, %d) vs (%u, %d)",
+ 0, 1,
+ ItemPointerGetBlockNumber(&decoded_itemptrs[0]),
+ ItemPointerGetOffsetNumber(&decoded_itemptrs[0]));
+
+ if (ndecoded == 2 &&
+ !ItemPointerEquals(&orig_itemptrs[0], &decoded_itemptrs[0]))
+ {
+ elog(ERROR, "mismatch on second itemptr: (%u, %d) vs (%u, %d)",
+ 0, 1,
+ ItemPointerGetBlockNumber(&decoded_itemptrs[0]),
+ ItemPointerGetOffsetNumber(&decoded_itemptrs[0]));
+ }
+}
+
+/*
+ * SQL-callable entry point to perform all tests.
+ */
+Datum
+test_ginpostinglist(PG_FUNCTION_ARGS)
+{
+ test_itemptr_pair(0, 2, 14);
+ test_itemptr_pair(0, MaxHeapTuplesPerPage, 14);
+ test_itemptr_pair(MaxBlockNumber, MaxHeapTuplesPerPage, 14);
+ test_itemptr_pair(MaxBlockNumber, MaxHeapTuplesPerPage, 16);
+
+ PG_RETURN_VOID();
+}
diff --git a/src/test/modules/test_ginpostinglist/test_ginpostinglist.control b/src/test/modules/test_ginpostinglist/test_ginpostinglist.control
new file mode 100644
index 0000000..e4f5a7c
--- /dev/null
+++ b/src/test/modules/test_ginpostinglist/test_ginpostinglist.control
@@ -0,0 +1,4 @@
+comment = 'Test code for ginpostinglist.c'
+default_version = '1.0'
+module_pathname = '$libdir/test_ginpostinglist'
+relocatable = true
diff --git a/src/test/modules/test_integerset/.gitignore b/src/test/modules/test_integerset/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_integerset/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_integerset/Makefile b/src/test/modules/test_integerset/Makefile
new file mode 100644
index 0000000..799c17c
--- /dev/null
+++ b/src/test/modules/test_integerset/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_integerset/Makefile
+
+MODULE_big = test_integerset
+OBJS = \
+ $(WIN32RES) \
+ test_integerset.o
+PGFILEDESC = "test_integerset - test code for src/backend/lib/integerset.c"
+
+EXTENSION = test_integerset
+DATA = test_integerset--1.0.sql
+
+REGRESS = test_integerset
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_integerset
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_integerset/README b/src/test/modules/test_integerset/README
new file mode 100644
index 0000000..a8b2718
--- /dev/null
+++ b/src/test/modules/test_integerset/README
@@ -0,0 +1,7 @@
+test_integerset contains unit tests for testing the integer set implementation
+in src/backend/lib/integerset.c.
+
+The tests verify the correctness of the implementation, but they can also be
+used as a micro-benchmark. If you set the 'intset_test_stats' flag in
+test_integerset.c, the tests will print extra information about execution time
+and memory usage.
diff --git a/src/test/modules/test_integerset/expected/test_integerset.out b/src/test/modules/test_integerset/expected/test_integerset.out
new file mode 100644
index 0000000..822dd03
--- /dev/null
+++ b/src/test/modules/test_integerset/expected/test_integerset.out
@@ -0,0 +1,31 @@
+CREATE EXTENSION test_integerset;
+--
+-- All the logic is in the test_integerset() function. It will throw
+-- an error if something fails.
+--
+SELECT test_integerset();
+NOTICE: testing intset with empty set
+NOTICE: testing intset with distances > 2^60 between values
+NOTICE: testing intset with single value 0
+NOTICE: testing intset with single value 1
+NOTICE: testing intset with single value 18446744073709551614
+NOTICE: testing intset with single value 18446744073709551615
+NOTICE: testing intset with value 0, and all between 1000 and 2000
+NOTICE: testing intset with value 1, and all between 1000 and 2000
+NOTICE: testing intset with value 1, and all between 1000 and 2000000
+NOTICE: testing intset with value 18446744073709551614, and all between 1000 and 2000
+NOTICE: testing intset with value 18446744073709551615, and all between 1000 and 2000
+NOTICE: testing intset with pattern "all ones"
+NOTICE: testing intset with pattern "alternating bits"
+NOTICE: testing intset with pattern "clusters of ten"
+NOTICE: testing intset with pattern "clusters of hundred"
+NOTICE: testing intset with pattern "one-every-64k"
+NOTICE: testing intset with pattern "sparse"
+NOTICE: testing intset with pattern "single values, distance > 2^32"
+NOTICE: testing intset with pattern "clusters, distance > 2^32"
+NOTICE: testing intset with pattern "clusters, distance > 2^60"
+ test_integerset
+-----------------
+
+(1 row)
+
diff --git a/src/test/modules/test_integerset/sql/test_integerset.sql b/src/test/modules/test_integerset/sql/test_integerset.sql
new file mode 100644
index 0000000..9d970dd
--- /dev/null
+++ b/src/test/modules/test_integerset/sql/test_integerset.sql
@@ -0,0 +1,7 @@
+CREATE EXTENSION test_integerset;
+
+--
+-- All the logic is in the test_integerset() function. It will throw
+-- an error if something fails.
+--
+SELECT test_integerset();
diff --git a/src/test/modules/test_integerset/test_integerset--1.0.sql b/src/test/modules/test_integerset/test_integerset--1.0.sql
new file mode 100644
index 0000000..d6d5a3f
--- /dev/null
+++ b/src/test/modules/test_integerset/test_integerset--1.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/test_integerset/test_integerset--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_integerset" to load this file. \quit
+
+CREATE FUNCTION test_integerset()
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_integerset/test_integerset.c b/src/test/modules/test_integerset/test_integerset.c
new file mode 100644
index 0000000..21c6f49
--- /dev/null
+++ b/src/test/modules/test_integerset/test_integerset.c
@@ -0,0 +1,623 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_integerset.c
+ * Test integer set data structure.
+ *
+ * Copyright (c) 2019-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_integerset/test_integerset.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "lib/integerset.h"
+#include "miscadmin.h"
+#include "nodes/bitmapset.h"
+#include "storage/block.h"
+#include "storage/itemptr.h"
+#include "utils/memutils.h"
+#include "utils/timestamp.h"
+
+/*
+ * If you enable this, the "pattern" tests will print information about
+ * how long populating, probing, and iterating the test set takes, and
+ * how much memory the test set consumed. That can be used as
+ * micro-benchmark of various operations and input patterns (you might
+ * want to increase the number of values used in each of the test, if
+ * you do that, to reduce noise).
+ *
+ * The information is printed to the server's stderr, mostly because
+ * that's where MemoryContextStats() output goes.
+ */
+static const bool intset_test_stats = false;
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(test_integerset);
+
+/*
+ * A struct to define a pattern of integers, for use with the test_pattern()
+ * function.
+ */
+typedef struct
+{
+ char *test_name; /* short name of the test, for humans */
+ char *pattern_str; /* a bit pattern */
+ uint64 spacing; /* pattern repeats at this interval */
+ uint64 num_values; /* number of integers to set in total */
+} test_spec;
+
+static const test_spec test_specs[] = {
+ {
+ "all ones", "1111111111",
+ 10, 10000000
+ },
+ {
+ "alternating bits", "0101010101",
+ 10, 10000000
+ },
+ {
+ "clusters of ten", "1111111111",
+ 10000, 10000000
+ },
+ {
+ "clusters of hundred",
+ "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
+ 10000, 100000000
+ },
+ {
+ "one-every-64k", "1",
+ 65536, 10000000
+ },
+ {
+ "sparse", "100000000000000000000000000000001",
+ 10000000, 10000000
+ },
+ {
+ "single values, distance > 2^32", "1",
+ UINT64CONST(10000000000), 1000000
+ },
+ {
+ "clusters, distance > 2^32", "10101010",
+ UINT64CONST(10000000000), 10000000
+ },
+ {
+ "clusters, distance > 2^60", "10101010",
+ UINT64CONST(2000000000000000000),
+ 23 /* can't be much higher than this, or we
+ * overflow uint64 */
+ }
+};
+
+static void test_pattern(const test_spec *spec);
+static void test_empty(void);
+static void test_single_value(uint64 value);
+static void check_with_filler(IntegerSet *intset, uint64 x, uint64 value, uint64 filler_min, uint64 filler_max);
+static void test_single_value_and_filler(uint64 value, uint64 filler_min, uint64 filler_max);
+static void test_huge_distances(void);
+
+/*
+ * SQL-callable entry point to perform all tests.
+ */
+Datum
+test_integerset(PG_FUNCTION_ARGS)
+{
+ /* Tests for various corner cases */
+ test_empty();
+ test_huge_distances();
+ test_single_value(0);
+ test_single_value(1);
+ test_single_value(PG_UINT64_MAX - 1);
+ test_single_value(PG_UINT64_MAX);
+ test_single_value_and_filler(0, 1000, 2000);
+ test_single_value_and_filler(1, 1000, 2000);
+ test_single_value_and_filler(1, 1000, 2000000);
+ test_single_value_and_filler(PG_UINT64_MAX - 1, 1000, 2000);
+ test_single_value_and_filler(PG_UINT64_MAX, 1000, 2000);
+
+ /* Test different test patterns, with lots of entries */
+ for (int i = 0; i < lengthof(test_specs); i++)
+ {
+ test_pattern(&test_specs[i]);
+ }
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * Test with a repeating pattern, defined by the 'spec'.
+ */
+static void
+test_pattern(const test_spec *spec)
+{
+ IntegerSet *intset;
+ MemoryContext intset_ctx;
+ MemoryContext old_ctx;
+ TimestampTz starttime;
+ TimestampTz endtime;
+ uint64 n;
+ uint64 last_int;
+ int patternlen;
+ uint64 *pattern_values;
+ uint64 pattern_num_values;
+
+ elog(NOTICE, "testing intset with pattern \"%s\"", spec->test_name);
+ if (intset_test_stats)
+ fprintf(stderr, "-----\ntesting intset with pattern \"%s\"\n", spec->test_name);
+
+ /* Pre-process the pattern, creating an array of integers from it. */
+ patternlen = strlen(spec->pattern_str);
+ pattern_values = palloc(patternlen * sizeof(uint64));
+ pattern_num_values = 0;
+ for (int i = 0; i < patternlen; i++)
+ {
+ if (spec->pattern_str[i] == '1')
+ pattern_values[pattern_num_values++] = i;
+ }
+
+ /*
+ * Allocate the integer set.
+ *
+ * Allocate it in a separate memory context, so that we can print its
+ * memory usage easily. (intset_create() creates a memory context of its
+ * own, too, but we don't have direct access to it, so we cannot call
+ * MemoryContextStats() on it directly).
+ */
+ intset_ctx = AllocSetContextCreate(CurrentMemoryContext,
+ "intset test",
+ ALLOCSET_SMALL_SIZES);
+ MemoryContextSetIdentifier(intset_ctx, spec->test_name);
+ old_ctx = MemoryContextSwitchTo(intset_ctx);
+ intset = intset_create();
+ MemoryContextSwitchTo(old_ctx);
+
+ /*
+ * Add values to the set.
+ */
+ starttime = GetCurrentTimestamp();
+
+ n = 0;
+ last_int = 0;
+ while (n < spec->num_values)
+ {
+ uint64 x = 0;
+
+ for (int i = 0; i < pattern_num_values && n < spec->num_values; i++)
+ {
+ x = last_int + pattern_values[i];
+
+ intset_add_member(intset, x);
+ n++;
+ }
+ last_int += spec->spacing;
+ }
+
+ endtime = GetCurrentTimestamp();
+
+ if (intset_test_stats)
+ fprintf(stderr, "added " UINT64_FORMAT " values in %d ms\n",
+ spec->num_values, (int) (endtime - starttime) / 1000);
+
+ /*
+ * Print stats on the amount of memory used.
+ *
+ * We print the usage reported by intset_memory_usage(), as well as the
+ * stats from the memory context. They should be in the same ballpark,
+ * but it's hard to automate testing that, so if you're making changes to
+ * the implementation, just observe that manually.
+ */
+ if (intset_test_stats)
+ {
+ uint64 mem_usage;
+
+ /*
+ * Also print memory usage as reported by intset_memory_usage(). It
+ * should be in the same ballpark as the usage reported by
+ * MemoryContextStats().
+ */
+ mem_usage = intset_memory_usage(intset);
+ fprintf(stderr, "intset_memory_usage() reported " UINT64_FORMAT " (%0.2f bytes / integer)\n",
+ mem_usage, (double) mem_usage / spec->num_values);
+
+ MemoryContextStats(intset_ctx);
+ }
+
+ /* Check that intset_get_num_entries works */
+ n = intset_num_entries(intset);
+ if (n != spec->num_values)
+ elog(ERROR, "intset_num_entries returned " UINT64_FORMAT ", expected " UINT64_FORMAT, n, spec->num_values);
+
+ /*
+ * Test random-access probes with intset_is_member()
+ */
+ starttime = GetCurrentTimestamp();
+
+ for (n = 0; n < 100000; n++)
+ {
+ bool b;
+ bool expected;
+ uint64 x;
+
+ /*
+ * Pick next value to probe at random. We limit the probes to the
+ * last integer that we added to the set, plus an arbitrary constant
+ * (1000). There's no point in probing the whole 0 - 2^64 range, if
+ * only a small part of the integer space is used. We would very
+ * rarely hit values that are actually in the set.
+ */
+ x = (pg_lrand48() << 31) | pg_lrand48();
+ x = x % (last_int + 1000);
+
+ /* Do we expect this value to be present in the set? */
+ if (x >= last_int)
+ expected = false;
+ else
+ {
+ uint64 idx = x % spec->spacing;
+
+ if (idx >= patternlen)
+ expected = false;
+ else if (spec->pattern_str[idx] == '1')
+ expected = true;
+ else
+ expected = false;
+ }
+
+ /* Is it present according to intset_is_member() ? */
+ b = intset_is_member(intset, x);
+
+ if (b != expected)
+ elog(ERROR, "mismatch at " UINT64_FORMAT ": %d vs %d", x, b, expected);
+ }
+ endtime = GetCurrentTimestamp();
+ if (intset_test_stats)
+ fprintf(stderr, "probed " UINT64_FORMAT " values in %d ms\n",
+ n, (int) (endtime - starttime) / 1000);
+
+ /*
+ * Test iterator
+ */
+ starttime = GetCurrentTimestamp();
+
+ intset_begin_iterate(intset);
+ n = 0;
+ last_int = 0;
+ while (n < spec->num_values)
+ {
+ for (int i = 0; i < pattern_num_values && n < spec->num_values; i++)
+ {
+ uint64 expected = last_int + pattern_values[i];
+ uint64 x;
+
+ if (!intset_iterate_next(intset, &x))
+ break;
+
+ if (x != expected)
+ elog(ERROR, "iterate returned wrong value; got " UINT64_FORMAT ", expected " UINT64_FORMAT, x, expected);
+ n++;
+ }
+ last_int += spec->spacing;
+ }
+ endtime = GetCurrentTimestamp();
+ if (intset_test_stats)
+ fprintf(stderr, "iterated " UINT64_FORMAT " values in %d ms\n",
+ n, (int) (endtime - starttime) / 1000);
+
+ if (n < spec->num_values)
+ elog(ERROR, "iterator stopped short after " UINT64_FORMAT " entries, expected " UINT64_FORMAT, n, spec->num_values);
+ if (n > spec->num_values)
+ elog(ERROR, "iterator returned " UINT64_FORMAT " entries, " UINT64_FORMAT " was expected", n, spec->num_values);
+
+ MemoryContextDelete(intset_ctx);
+}
+
+/*
+ * Test with a set containing a single integer.
+ */
+static void
+test_single_value(uint64 value)
+{
+ IntegerSet *intset;
+ uint64 x;
+ uint64 num_entries;
+ bool found;
+
+ elog(NOTICE, "testing intset with single value " UINT64_FORMAT, value);
+
+ /* Create the set. */
+ intset = intset_create();
+ intset_add_member(intset, value);
+
+ /* Test intset_get_num_entries() */
+ num_entries = intset_num_entries(intset);
+ if (num_entries != 1)
+ elog(ERROR, "intset_num_entries returned " UINT64_FORMAT ", expected 1", num_entries);
+
+ /*
+ * Test intset_is_member() at various special values, like 0 and maximum
+ * possible 64-bit integer, as well as the value itself.
+ */
+ if (intset_is_member(intset, 0) != (value == 0))
+ elog(ERROR, "intset_is_member failed for 0");
+ if (intset_is_member(intset, 1) != (value == 1))
+ elog(ERROR, "intset_is_member failed for 1");
+ if (intset_is_member(intset, PG_UINT64_MAX) != (value == PG_UINT64_MAX))
+ elog(ERROR, "intset_is_member failed for PG_UINT64_MAX");
+ if (intset_is_member(intset, value) != true)
+ elog(ERROR, "intset_is_member failed for the tested value");
+
+ /*
+ * Test iterator
+ */
+ intset_begin_iterate(intset);
+ found = intset_iterate_next(intset, &x);
+ if (!found || x != value)
+ elog(ERROR, "intset_iterate_next failed for " UINT64_FORMAT, x);
+
+ found = intset_iterate_next(intset, &x);
+ if (found)
+ elog(ERROR, "intset_iterate_next failed " UINT64_FORMAT, x);
+}
+
+/*
+ * Test with an integer set that contains:
+ *
+ * - a given single 'value', and
+ * - all integers between 'filler_min' and 'filler_max'.
+ *
+ * This exercises different codepaths than testing just with a single value,
+ * because the implementation buffers newly-added values. If we add just a
+ * single value to the set, we won't test the internal B-tree code at all,
+ * just the code that deals with the buffer.
+ */
+static void
+test_single_value_and_filler(uint64 value, uint64 filler_min, uint64 filler_max)
+{
+ IntegerSet *intset;
+ uint64 x;
+ bool found;
+ uint64 *iter_expected;
+ uint64 n = 0;
+ uint64 num_entries = 0;
+ uint64 mem_usage;
+
+ elog(NOTICE, "testing intset with value " UINT64_FORMAT ", and all between " UINT64_FORMAT " and " UINT64_FORMAT,
+ value, filler_min, filler_max);
+
+ intset = intset_create();
+
+ iter_expected = palloc(sizeof(uint64) * (filler_max - filler_min + 1));
+ if (value < filler_min)
+ {
+ intset_add_member(intset, value);
+ iter_expected[n++] = value;
+ }
+
+ for (x = filler_min; x < filler_max; x++)
+ {
+ intset_add_member(intset, x);
+ iter_expected[n++] = x;
+ }
+
+ if (value >= filler_max)
+ {
+ intset_add_member(intset, value);
+ iter_expected[n++] = value;
+ }
+
+ /* Test intset_get_num_entries() */
+ num_entries = intset_num_entries(intset);
+ if (num_entries != n)
+ elog(ERROR, "intset_num_entries returned " UINT64_FORMAT ", expected " UINT64_FORMAT, num_entries, n);
+
+ /*
+ * Test intset_is_member() at various spots, at and around the values that
+ * we expect to be set, as well as 0 and the maximum possible value.
+ */
+ check_with_filler(intset, 0,
+ value, filler_min, filler_max);
+ check_with_filler(intset, 1,
+ value, filler_min, filler_max);
+ check_with_filler(intset, filler_min - 1,
+ value, filler_min, filler_max);
+ check_with_filler(intset, filler_min,
+ value, filler_min, filler_max);
+ check_with_filler(intset, filler_min + 1,
+ value, filler_min, filler_max);
+ check_with_filler(intset, value - 1,
+ value, filler_min, filler_max);
+ check_with_filler(intset, value,
+ value, filler_min, filler_max);
+ check_with_filler(intset, value + 1,
+ value, filler_min, filler_max);
+ check_with_filler(intset, filler_max - 1,
+ value, filler_min, filler_max);
+ check_with_filler(intset, filler_max,
+ value, filler_min, filler_max);
+ check_with_filler(intset, filler_max + 1,
+ value, filler_min, filler_max);
+ check_with_filler(intset, PG_UINT64_MAX - 1,
+ value, filler_min, filler_max);
+ check_with_filler(intset, PG_UINT64_MAX,
+ value, filler_min, filler_max);
+
+ intset_begin_iterate(intset);
+ for (uint64 i = 0; i < n; i++)
+ {
+ found = intset_iterate_next(intset, &x);
+ if (!found || x != iter_expected[i])
+ elog(ERROR, "intset_iterate_next failed for " UINT64_FORMAT, x);
+ }
+ found = intset_iterate_next(intset, &x);
+ if (found)
+ elog(ERROR, "intset_iterate_next failed " UINT64_FORMAT, x);
+
+ mem_usage = intset_memory_usage(intset);
+ if (mem_usage < 5000 || mem_usage > 500000000)
+ elog(ERROR, "intset_memory_usage() reported suspicious value: " UINT64_FORMAT, mem_usage);
+}
+
+/*
+ * Helper function for test_single_value_and_filler.
+ *
+ * Calls intset_is_member() for value 'x', and checks that the result is what
+ * we expect.
+ */
+static void
+check_with_filler(IntegerSet *intset, uint64 x,
+ uint64 value, uint64 filler_min, uint64 filler_max)
+{
+ bool expected;
+ bool actual;
+
+ expected = (x == value || (filler_min <= x && x < filler_max));
+
+ actual = intset_is_member(intset, x);
+
+ if (actual != expected)
+ elog(ERROR, "intset_is_member failed for " UINT64_FORMAT, x);
+}
+
+/*
+ * Test empty set
+ */
+static void
+test_empty(void)
+{
+ IntegerSet *intset;
+ uint64 x;
+
+ elog(NOTICE, "testing intset with empty set");
+
+ intset = intset_create();
+
+ /* Test intset_is_member() */
+ if (intset_is_member(intset, 0) != false)
+ elog(ERROR, "intset_is_member on empty set returned true");
+ if (intset_is_member(intset, 1) != false)
+ elog(ERROR, "intset_is_member on empty set returned true");
+ if (intset_is_member(intset, PG_UINT64_MAX) != false)
+ elog(ERROR, "intset_is_member on empty set returned true");
+
+ /* Test iterator */
+ intset_begin_iterate(intset);
+ if (intset_iterate_next(intset, &x))
+ elog(ERROR, "intset_iterate_next on empty set returned a value (" UINT64_FORMAT ")", x);
+}
+
+/*
+ * Test with integers that are more than 2^60 apart.
+ *
+ * The Simple-8b encoding used by the set implementation can only encode
+ * values up to 2^60. That makes large differences like this interesting
+ * to test.
+ */
+static void
+test_huge_distances(void)
+{
+ IntegerSet *intset;
+ uint64 values[1000];
+ int num_values = 0;
+ uint64 val = 0;
+ bool found;
+ uint64 x;
+
+ elog(NOTICE, "testing intset with distances > 2^60 between values");
+
+ val = 0;
+ values[num_values++] = val;
+
+ /* Test differences on both sides of the 2^60 boundary. */
+ val += UINT64CONST(1152921504606846976) - 1; /* 2^60 - 1 */
+ values[num_values++] = val;
+
+ val += UINT64CONST(1152921504606846976) - 1; /* 2^60 - 1 */
+ values[num_values++] = val;
+
+ val += UINT64CONST(1152921504606846976); /* 2^60 */
+ values[num_values++] = val;
+
+ val += UINT64CONST(1152921504606846976); /* 2^60 */
+ values[num_values++] = val;
+
+ val += UINT64CONST(1152921504606846976); /* 2^60 */
+ values[num_values++] = val;
+
+ val += UINT64CONST(1152921504606846976) + 1; /* 2^60 + 1 */
+ values[num_values++] = val;
+
+ val += UINT64CONST(1152921504606846976) + 1; /* 2^60 + 1 */
+ values[num_values++] = val;
+
+ val += UINT64CONST(1152921504606846976) + 1; /* 2^60 + 1 */
+ values[num_values++] = val;
+
+ val += UINT64CONST(1152921504606846976) + 2; /* 2^60 + 2 */
+ values[num_values++] = val;
+
+ val += UINT64CONST(1152921504606846976) + 2; /* 2^60 + 2 */
+ values[num_values++] = val;
+
+ val += UINT64CONST(1152921504606846976); /* 2^60 */
+ values[num_values++] = val;
+
+ /*
+ * We're now very close to 2^64, so can't add large values anymore. But
+ * add more smaller values to the end, to make sure that all the above
+ * values get flushed and packed into the tree structure.
+ */
+ while (num_values < 1000)
+ {
+ val += pg_lrand48();
+ values[num_values++] = val;
+ }
+
+ /* Create an IntegerSet using these values */
+ intset = intset_create();
+ for (int i = 0; i < num_values; i++)
+ intset_add_member(intset, values[i]);
+
+ /*
+ * Test intset_is_member() around each of these values
+ */
+ for (int i = 0; i < num_values; i++)
+ {
+ uint64 x = values[i];
+ bool expected;
+ bool result;
+
+ if (x > 0)
+ {
+ expected = (values[i - 1] == x - 1);
+ result = intset_is_member(intset, x - 1);
+ if (result != expected)
+ elog(ERROR, "intset_is_member failed for " UINT64_FORMAT, x - 1);
+ }
+
+ result = intset_is_member(intset, x);
+ if (result != true)
+ elog(ERROR, "intset_is_member failed for " UINT64_FORMAT, x);
+
+ expected = (i != num_values - 1) ? (values[i + 1] == x + 1) : false;
+ result = intset_is_member(intset, x + 1);
+ if (result != expected)
+ elog(ERROR, "intset_is_member failed for " UINT64_FORMAT, x + 1);
+ }
+
+ /*
+ * Test iterator
+ */
+ intset_begin_iterate(intset);
+ for (int i = 0; i < num_values; i++)
+ {
+ found = intset_iterate_next(intset, &x);
+ if (!found || x != values[i])
+ elog(ERROR, "intset_iterate_next failed for " UINT64_FORMAT, x);
+ }
+ found = intset_iterate_next(intset, &x);
+ if (found)
+ elog(ERROR, "intset_iterate_next failed " UINT64_FORMAT, x);
+}
diff --git a/src/test/modules/test_integerset/test_integerset.control b/src/test/modules/test_integerset/test_integerset.control
new file mode 100644
index 0000000..7d20c2d
--- /dev/null
+++ b/src/test/modules/test_integerset/test_integerset.control
@@ -0,0 +1,4 @@
+comment = 'Test code for integerset'
+default_version = '1.0'
+module_pathname = '$libdir/test_integerset'
+relocatable = true
diff --git a/src/test/modules/test_misc/.gitignore b/src/test/modules/test_misc/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_misc/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_misc/Makefile b/src/test/modules/test_misc/Makefile
new file mode 100644
index 0000000..39c6c20
--- /dev/null
+++ b/src/test/modules/test_misc/Makefile
@@ -0,0 +1,14 @@
+# src/test/modules/test_misc/Makefile
+
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_misc
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_misc/README b/src/test/modules/test_misc/README
new file mode 100644
index 0000000..4876733
--- /dev/null
+++ b/src/test/modules/test_misc/README
@@ -0,0 +1,4 @@
+This directory doesn't actually contain any extension module.
+
+What it is is a home for otherwise-unclassified TAP tests that exercise core
+server features. We might equally well have called it, say, src/test/misc.
diff --git a/src/test/modules/test_misc/t/001_constraint_validation.pl b/src/test/modules/test_misc/t/001_constraint_validation.pl
new file mode 100644
index 0000000..3729906
--- /dev/null
+++ b/src/test/modules/test_misc/t/001_constraint_validation.pl
@@ -0,0 +1,313 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Verify that ALTER TABLE optimizes certain operations as expected
+
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 42;
+
+# Initialize a test cluster
+my $node = get_new_node('primary');
+$node->init();
+# Turn message level up to DEBUG1 so that we get the messages we want to see
+$node->append_conf('postgresql.conf', 'client_min_messages = DEBUG1');
+$node->start;
+
+# Run a SQL command and return psql's stderr (including debug messages)
+sub run_sql_command
+{
+ my $sql = shift;
+ my $stderr;
+
+ $node->psql(
+ 'postgres',
+ $sql,
+ stderr => \$stderr,
+ on_error_die => 1,
+ on_error_stop => 1);
+ return $stderr;
+}
+
+# Check whether result of run_sql_command shows that we did a verify pass
+sub is_table_verified
+{
+ my $output = shift;
+ return index($output, 'DEBUG: verifying table') != -1;
+}
+
+my $output;
+
+note "test alter table set not null";
+
+run_sql_command(
+ 'create table atacc1 (test_a int, test_b int);
+ insert into atacc1 values (1, 2);');
+
+$output = run_sql_command('alter table atacc1 alter test_a set not null;');
+ok(is_table_verified($output),
+ 'column test_a without constraint will scan table');
+
+run_sql_command(
+ 'alter table atacc1 alter test_a drop not null;
+ alter table atacc1 add constraint atacc1_constr_a_valid
+ check(test_a is not null);');
+
+# normal run will verify table data
+$output = run_sql_command('alter table atacc1 alter test_a set not null;');
+ok(!is_table_verified($output), 'with constraint will not scan table');
+ok( $output =~
+ m/existing constraints on column "atacc1.test_a" are sufficient to prove that it does not contain nulls/,
+ 'test_a proved by constraints');
+
+run_sql_command('alter table atacc1 alter test_a drop not null;');
+
+# we have check only for test_a column, so we need verify table for test_b
+$output = run_sql_command(
+ 'alter table atacc1 alter test_b set not null, alter test_a set not null;'
+);
+ok(is_table_verified($output), 'table was scanned');
+# we may miss debug message for test_a constraint because we need verify table due test_b
+ok( !( $output =~
+ m/existing constraints on column "atacc1.test_b" are sufficient to prove that it does not contain nulls/
+ ),
+ 'test_b not proved by wrong constraints');
+run_sql_command(
+ 'alter table atacc1 alter test_a drop not null, alter test_b drop not null;'
+);
+
+# test with both columns having check constraints
+run_sql_command(
+ 'alter table atacc1 add constraint atacc1_constr_b_valid check(test_b is not null);'
+);
+$output = run_sql_command(
+ 'alter table atacc1 alter test_b set not null, alter test_a set not null;'
+);
+ok(!is_table_verified($output), 'table was not scanned for both columns');
+ok( $output =~
+ m/existing constraints on column "atacc1.test_a" are sufficient to prove that it does not contain nulls/,
+ 'test_a proved by constraints');
+ok( $output =~
+ m/existing constraints on column "atacc1.test_b" are sufficient to prove that it does not contain nulls/,
+ 'test_b proved by constraints');
+run_sql_command('drop table atacc1;');
+
+note "test alter table attach partition";
+
+run_sql_command(
+ 'CREATE TABLE list_parted2 (
+ a int,
+ b char
+ ) PARTITION BY LIST (a);
+ CREATE TABLE part_3_4 (
+ LIKE list_parted2,
+ CONSTRAINT check_a CHECK (a IN (3)));');
+
+# need NOT NULL to skip table scan
+$output = run_sql_command(
+ 'ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);'
+);
+ok(is_table_verified($output), 'table part_3_4 scanned');
+
+run_sql_command(
+ 'ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+ ALTER TABLE part_3_4 ALTER a SET NOT NULL;');
+
+$output = run_sql_command(
+ 'ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);'
+);
+ok(!is_table_verified($output), 'table part_3_4 not scanned');
+ok( $output =~
+ m/partition constraint for table "part_3_4" is implied by existing constraints/,
+ 'part_3_4 verified by existing constraints');
+
+# test attach default partition
+run_sql_command(
+ 'CREATE TABLE list_parted2_def (
+ LIKE list_parted2,
+ CONSTRAINT check_a CHECK (a IN (5, 6)));');
+$output = run_sql_command(
+ 'ALTER TABLE list_parted2 ATTACH PARTITION list_parted2_def default;');
+ok(!is_table_verified($output), 'table list_parted2_def not scanned');
+ok( $output =~
+ m/partition constraint for table "list_parted2_def" is implied by existing constraints/,
+ 'list_parted2_def verified by existing constraints');
+
+$output = run_sql_command(
+ 'CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);'
+);
+ok(!is_table_verified($output), 'table list_parted2_def not scanned');
+ok( $output =~
+ m/updated partition constraint for default partition "list_parted2_def" is implied by existing constraints/,
+ 'updated partition constraint for default partition list_parted2_def');
+
+# test attach another partitioned table
+run_sql_command(
+ 'CREATE TABLE part_5 (
+ LIKE list_parted2
+ ) PARTITION BY LIST (b);
+ CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN (\'a\');
+ ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 5);'
+);
+$output = run_sql_command(
+ 'ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);');
+ok(!($output =~ m/verifying table "part_5"/), 'table part_5 not scanned');
+ok($output =~ m/verifying table "list_parted2_def"/,
+ 'list_parted2_def scanned');
+ok( $output =~
+ m/partition constraint for table "part_5" is implied by existing constraints/,
+ 'part_5 verified by existing constraints');
+
+run_sql_command(
+ '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
+run_sql_command(
+ 'ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)),
+ ALTER a SET NOT NULL;'
+);
+$output = run_sql_command(
+ 'ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);');
+ok(!($output =~ m/verifying table "part_5"/), 'table part_5 not scanned');
+ok($output =~ m/verifying table "list_parted2_def"/,
+ 'list_parted2_def scanned');
+ok( $output =~
+ m/partition constraint for table "part_5" is implied by existing constraints/,
+ 'part_5 verified by existing constraints');
+
+# 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.
+run_sql_command(
+ '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;');
+$output = run_sql_command(
+ 'ALTER TABLE list_parted2 ATTACH PARTITION part_6 FOR VALUES IN (6);');
+ok(!($output =~ m/verifying table "part_6"/), 'table part_6 not scanned');
+ok($output =~ m/verifying table "list_parted2_def"/,
+ 'list_parted2_def scanned');
+ok( $output =~
+ m/partition constraint for table "part_6" is implied by existing constraints/,
+ 'part_6 verified by existing constraints');
+
+# Similar to above, but the table being attached is a partitioned table
+# whose partition has still different attnos for the root partitioning
+# columns.
+run_sql_command(
+ '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;');
+
+$output = run_sql_command(
+ 'ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN (\'a\', null);'
+);
+ok(!is_table_verified($output), 'table not scanned');
+ok( $output =~
+ m/partition constraint for table "part_7_a_null" is implied by existing constraints/,
+ 'part_7_a_null verified by existing constraints');
+$output = run_sql_command(
+ 'ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);');
+ok(!is_table_verified($output), 'tables not scanned');
+ok( $output =~
+ m/partition constraint for table "part_7" is implied by existing constraints/,
+ 'part_7 verified by existing constraints');
+ok( $output =~
+ m/updated partition constraint for default partition "list_parted2_def" is implied by existing constraints/,
+ 'updated partition constraint for default partition list_parted2_def');
+
+run_sql_command(
+ 'CREATE TABLE range_parted (
+ a int,
+ b int
+ ) PARTITION BY RANGE (a, b);
+ CREATE TABLE range_part1 (
+ a int NOT NULL CHECK (a = 1),
+ b int NOT NULL);');
+
+$output = run_sql_command(
+ 'ALTER TABLE range_parted ATTACH PARTITION range_part1 FOR VALUES FROM (1, 1) TO (1, 10);'
+);
+ok(is_table_verified($output), 'table range_part1 scanned');
+ok( !( $output =~
+ m/partition constraint for table "range_part1" is implied by existing constraints/
+ ),
+ 'range_part1 not verified by existing constraints');
+
+run_sql_command(
+ 'CREATE TABLE range_part2 (
+ a int NOT NULL CHECK (a = 1),
+ b int NOT NULL CHECK (b >= 10 and b < 18)
+);');
+$output = run_sql_command(
+ 'ALTER TABLE range_parted ATTACH PARTITION range_part2 FOR VALUES FROM (1, 10) TO (1, 20);'
+);
+ok(!is_table_verified($output), 'table range_part2 not scanned');
+ok( $output =~
+ m/partition constraint for table "range_part2" is implied by existing constraints/,
+ 'range_part2 verified by existing constraints');
+
+# 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.
+run_sql_command(
+ '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);');
+
+$output = run_sql_command(
+ 'ALTER TABLE quuux ATTACH PARTITION quuux1 FOR VALUES IN (1);');
+ok(is_table_verified($output), 'quuux1 table scanned');
+ok( !( $output =~
+ m/partition constraint for table "quuux1" is implied by existing constraints/
+ ),
+ 'quuux1 verified by existing constraints');
+
+run_sql_command('CREATE TABLE quuux2 (a int, b text);');
+$output = run_sql_command(
+ 'ALTER TABLE quuux ATTACH PARTITION quuux2 FOR VALUES IN (2);');
+ok(!($output =~ m/verifying table "quuux_default1"/),
+ 'quuux_default1 not scanned');
+ok($output =~ m/verifying table "quuux2"/, 'quuux2 scanned');
+ok( $output =~
+ m/updated partition constraint for default partition "quuux_default1" is implied by existing constraints/,
+ 'updated partition constraint for default partition quuux_default1');
+run_sql_command('DROP TABLE quuux1, quuux2;');
+
+# should validate for quuux1, but not for quuux2
+$output = run_sql_command(
+ 'CREATE TABLE quuux1 PARTITION OF quuux FOR VALUES IN (1);');
+ok(!is_table_verified($output), 'tables not scanned');
+ok( !( $output =~
+ m/partition constraint for table "quuux1" is implied by existing constraints/
+ ),
+ 'quuux1 verified by existing constraints');
+$output = run_sql_command(
+ 'CREATE TABLE quuux2 PARTITION OF quuux FOR VALUES IN (2);');
+ok(!is_table_verified($output), 'tables not scanned');
+ok( $output =~
+ m/updated partition constraint for default partition "quuux_default1" is implied by existing constraints/,
+ 'updated partition constraint for default partition quuux_default1');
+run_sql_command('DROP TABLE quuux;');
+
+$node->stop('fast');
diff --git a/src/test/modules/test_parser/.gitignore b/src/test/modules/test_parser/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_parser/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_parser/Makefile b/src/test/modules/test_parser/Makefile
new file mode 100644
index 0000000..5327080
--- /dev/null
+++ b/src/test/modules/test_parser/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_parser/Makefile
+
+MODULE_big = test_parser
+OBJS = \
+ $(WIN32RES) \
+ test_parser.o
+PGFILEDESC = "test_parser - example of a custom parser for full-text search"
+
+EXTENSION = test_parser
+DATA = test_parser--1.0.sql
+
+REGRESS = test_parser
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_parser
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_parser/README b/src/test/modules/test_parser/README
new file mode 100644
index 0000000..0a11ec8
--- /dev/null
+++ b/src/test/modules/test_parser/README
@@ -0,0 +1,61 @@
+test_parser is an example of a custom parser for full-text
+search. It doesn't do anything especially useful, but can serve as
+a starting point for developing your own parser.
+
+test_parser recognizes words separated by white space,
+and returns just two token types:
+
+mydb=# SELECT * FROM ts_token_type('testparser');
+ tokid | alias | description
+-------+-------+---------------
+ 3 | word | Word
+ 12 | blank | Space symbols
+(2 rows)
+
+These token numbers have been chosen to be compatible with the default
+parser's numbering. This allows us to use its headline()
+function, thus keeping the example simple.
+
+Usage
+=====
+
+Installing the test_parser extension creates a text search
+parser testparser. It has no user-configurable parameters.
+
+You can test the parser with, for example,
+
+mydb=# SELECT * FROM ts_parse('testparser', 'That''s my first own parser');
+ tokid | token
+-------+--------
+ 3 | That's
+ 12 |
+ 3 | my
+ 12 |
+ 3 | first
+ 12 |
+ 3 | own
+ 12 |
+ 3 | parser
+
+Real-world use requires setting up a text search configuration
+that uses the parser. For example,
+
+mydb=# CREATE TEXT SEARCH CONFIGURATION testcfg ( PARSER = testparser );
+CREATE TEXT SEARCH CONFIGURATION
+
+mydb=# ALTER TEXT SEARCH CONFIGURATION testcfg
+mydb-# ADD MAPPING FOR word WITH english_stem;
+ALTER TEXT SEARCH CONFIGURATION
+
+mydb=# SELECT to_tsvector('testcfg', 'That''s my first own parser');
+ to_tsvector
+-------------------------------
+ 'that':1 'first':3 'parser':5
+(1 row)
+
+mydb=# SELECT ts_headline('testcfg', 'Supernovae stars are the brightest phenomena in galaxies',
+mydb(# to_tsquery('testcfg', 'star'));
+ ts_headline
+-----------------------------------------------------------------
+ Supernovae <b>stars</b> are the brightest phenomena in galaxies
+(1 row)
diff --git a/src/test/modules/test_parser/expected/test_parser.out b/src/test/modules/test_parser/expected/test_parser.out
new file mode 100644
index 0000000..8a49bc0
--- /dev/null
+++ b/src/test/modules/test_parser/expected/test_parser.out
@@ -0,0 +1,44 @@
+CREATE EXTENSION test_parser;
+-- make test configuration using parser
+CREATE TEXT SEARCH CONFIGURATION testcfg (PARSER = testparser);
+ALTER TEXT SEARCH CONFIGURATION testcfg ADD MAPPING FOR word WITH simple;
+-- ts_parse
+SELECT * FROM ts_parse('testparser', 'That''s simple parser can''t parse urls like http://some.url/here/');
+ tokid | token
+-------+-----------------------
+ 3 | That's
+ 12 |
+ 3 | simple
+ 12 |
+ 3 | parser
+ 12 |
+ 3 | can't
+ 12 |
+ 3 | parse
+ 12 |
+ 3 | urls
+ 12 |
+ 3 | like
+ 12 |
+ 3 | http://some.url/here/
+(15 rows)
+
+SELECT to_tsvector('testcfg','That''s my first own parser');
+ to_tsvector
+-------------------------------------------------
+ 'first':3 'my':2 'own':4 'parser':5 'that''s':1
+(1 row)
+
+SELECT to_tsquery('testcfg', 'star');
+ to_tsquery
+------------
+ 'star'
+(1 row)
+
+SELECT ts_headline('testcfg','Supernovae stars are the brightest phenomena in galaxies',
+ to_tsquery('testcfg', 'stars'));
+ ts_headline
+-----------------------------------------------------------------
+ Supernovae <b>stars</b> are the brightest phenomena in galaxies
+(1 row)
+
diff --git a/src/test/modules/test_parser/sql/test_parser.sql b/src/test/modules/test_parser/sql/test_parser.sql
new file mode 100644
index 0000000..1f21504
--- /dev/null
+++ b/src/test/modules/test_parser/sql/test_parser.sql
@@ -0,0 +1,18 @@
+CREATE EXTENSION test_parser;
+
+-- make test configuration using parser
+
+CREATE TEXT SEARCH CONFIGURATION testcfg (PARSER = testparser);
+
+ALTER TEXT SEARCH CONFIGURATION testcfg ADD MAPPING FOR word WITH simple;
+
+-- ts_parse
+
+SELECT * FROM ts_parse('testparser', 'That''s simple parser can''t parse urls like http://some.url/here/');
+
+SELECT to_tsvector('testcfg','That''s my first own parser');
+
+SELECT to_tsquery('testcfg', 'star');
+
+SELECT ts_headline('testcfg','Supernovae stars are the brightest phenomena in galaxies',
+ to_tsquery('testcfg', 'stars'));
diff --git a/src/test/modules/test_parser/test_parser--1.0.sql b/src/test/modules/test_parser/test_parser--1.0.sql
new file mode 100644
index 0000000..56bb244
--- /dev/null
+++ b/src/test/modules/test_parser/test_parser--1.0.sql
@@ -0,0 +1,32 @@
+/* src/test/modules/test_parser/test_parser--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_parser" to load this file. \quit
+
+CREATE FUNCTION testprs_start(internal, int4)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION testprs_getlexeme(internal, internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION testprs_end(internal)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION testprs_lextype(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE TEXT SEARCH PARSER testparser (
+ START = testprs_start,
+ GETTOKEN = testprs_getlexeme,
+ END = testprs_end,
+ HEADLINE = pg_catalog.prsd_headline,
+ LEXTYPES = testprs_lextype
+);
diff --git a/src/test/modules/test_parser/test_parser.c b/src/test/modules/test_parser/test_parser.c
new file mode 100644
index 0000000..f133676
--- /dev/null
+++ b/src/test/modules/test_parser/test_parser.c
@@ -0,0 +1,127 @@
+/*-------------------------------------------------------------------------
+ *
+ * test_parser.c
+ * Simple example of a text search parser
+ *
+ * Copyright (c) 2007-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_parser/test_parser.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+
+PG_MODULE_MAGIC;
+
+/*
+ * types
+ */
+
+/* self-defined type */
+typedef struct
+{
+ char *buffer; /* text to parse */
+ int len; /* length of the text in buffer */
+ int pos; /* position of the parser */
+} ParserState;
+
+typedef struct
+{
+ int lexid;
+ char *alias;
+ char *descr;
+} LexDescr;
+
+/*
+ * functions
+ */
+PG_FUNCTION_INFO_V1(testprs_start);
+PG_FUNCTION_INFO_V1(testprs_getlexeme);
+PG_FUNCTION_INFO_V1(testprs_end);
+PG_FUNCTION_INFO_V1(testprs_lextype);
+
+Datum
+testprs_start(PG_FUNCTION_ARGS)
+{
+ ParserState *pst = (ParserState *) palloc0(sizeof(ParserState));
+
+ pst->buffer = (char *) PG_GETARG_POINTER(0);
+ pst->len = PG_GETARG_INT32(1);
+ pst->pos = 0;
+
+ PG_RETURN_POINTER(pst);
+}
+
+Datum
+testprs_getlexeme(PG_FUNCTION_ARGS)
+{
+ ParserState *pst = (ParserState *) PG_GETARG_POINTER(0);
+ char **t = (char **) PG_GETARG_POINTER(1);
+ int *tlen = (int *) PG_GETARG_POINTER(2);
+ int startpos = pst->pos;
+ int type;
+
+ *t = pst->buffer + pst->pos;
+
+ if (pst->pos < pst->len &&
+ (pst->buffer)[pst->pos] == ' ')
+ {
+ /* blank type */
+ type = 12;
+ /* go to the next non-space character */
+ while (pst->pos < pst->len &&
+ (pst->buffer)[pst->pos] == ' ')
+ (pst->pos)++;
+ }
+ else
+ {
+ /* word type */
+ type = 3;
+ /* go to the next space character */
+ while (pst->pos < pst->len &&
+ (pst->buffer)[pst->pos] != ' ')
+ (pst->pos)++;
+ }
+
+ *tlen = pst->pos - startpos;
+
+ /* we are finished if (*tlen == 0) */
+ if (*tlen == 0)
+ type = 0;
+
+ PG_RETURN_INT32(type);
+}
+
+Datum
+testprs_end(PG_FUNCTION_ARGS)
+{
+ ParserState *pst = (ParserState *) PG_GETARG_POINTER(0);
+
+ pfree(pst);
+ PG_RETURN_VOID();
+}
+
+Datum
+testprs_lextype(PG_FUNCTION_ARGS)
+{
+ /*
+ * Remarks: - we have to return the blanks for headline reason - we use
+ * the same lexids like Teodor in the default word parser; in this way we
+ * can reuse the headline function of the default word parser.
+ */
+ LexDescr *descr = (LexDescr *) palloc(sizeof(LexDescr) * (2 + 1));
+
+ /* there are only two types in this parser */
+ descr[0].lexid = 3;
+ descr[0].alias = pstrdup("word");
+ descr[0].descr = pstrdup("Word");
+ descr[1].lexid = 12;
+ descr[1].alias = pstrdup("blank");
+ descr[1].descr = pstrdup("Space symbols");
+ descr[2].lexid = 0;
+
+ PG_RETURN_POINTER(descr);
+}
diff --git a/src/test/modules/test_parser/test_parser.control b/src/test/modules/test_parser/test_parser.control
new file mode 100644
index 0000000..36b26b2
--- /dev/null
+++ b/src/test/modules/test_parser/test_parser.control
@@ -0,0 +1,5 @@
+# test_parser extension
+comment = 'example of a custom parser for full-text search'
+default_version = '1.0'
+module_pathname = '$libdir/test_parser'
+relocatable = true
diff --git a/src/test/modules/test_pg_dump/.gitignore b/src/test/modules/test_pg_dump/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_pg_dump/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_pg_dump/Makefile b/src/test/modules/test_pg_dump/Makefile
new file mode 100644
index 0000000..6123b99
--- /dev/null
+++ b/src/test/modules/test_pg_dump/Makefile
@@ -0,0 +1,21 @@
+# src/test/modules/test_pg_dump/Makefile
+
+MODULE = test_pg_dump
+PGFILEDESC = "test_pg_dump - Test pg_dump with an extension"
+
+EXTENSION = test_pg_dump
+DATA = test_pg_dump--1.0.sql
+
+REGRESS = test_pg_dump
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_pg_dump
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_pg_dump/README b/src/test/modules/test_pg_dump/README
new file mode 100644
index 0000000..b7c2e33
--- /dev/null
+++ b/src/test/modules/test_pg_dump/README
@@ -0,0 +1,4 @@
+test_pg_dump is an extension explicitly to test pg_dump when
+extensions are present in the system.
+
+We also make use of this module to test ALTER EXTENSION ADD/DROP.
diff --git a/src/test/modules/test_pg_dump/expected/test_pg_dump.out b/src/test/modules/test_pg_dump/expected/test_pg_dump.out
new file mode 100644
index 0000000..f14f3a6
--- /dev/null
+++ b/src/test/modules/test_pg_dump/expected/test_pg_dump.out
@@ -0,0 +1,93 @@
+CREATE ROLE regress_dump_test_role;
+CREATE EXTENSION test_pg_dump;
+ALTER EXTENSION test_pg_dump ADD DATABASE postgres; -- error
+ERROR: cannot add an object of this type to an extension
+CREATE TABLE test_pg_dump_t1 (c1 int, junk text);
+ALTER TABLE test_pg_dump_t1 DROP COLUMN junk; -- to exercise dropped-col cases
+CREATE VIEW test_pg_dump_v1 AS SELECT * FROM test_pg_dump_t1;
+CREATE MATERIALIZED VIEW test_pg_dump_mv1 AS SELECT * FROM test_pg_dump_t1;
+CREATE SCHEMA test_pg_dump_s1;
+CREATE TYPE test_pg_dump_e1 AS ENUM ('abc', 'def');
+CREATE AGGREGATE newavg (
+ sfunc = int4_avg_accum, basetype = int4, stype = _int8,
+ finalfunc = int8_avg,
+ initcond1 = '{0,0}'
+);
+CREATE FUNCTION test_pg_dump(int) RETURNS int AS $$
+BEGIN
+RETURN abs($1);
+END
+$$ LANGUAGE plpgsql IMMUTABLE;
+CREATE OPERATOR ==== (
+ LEFTARG = int,
+ RIGHTARG = int,
+ PROCEDURE = int4eq,
+ COMMUTATOR = ====
+);
+CREATE ACCESS METHOD gist2 TYPE INDEX HANDLER gisthandler;
+CREATE TYPE casttesttype;
+CREATE FUNCTION casttesttype_in(cstring)
+ RETURNS casttesttype
+ AS 'textin'
+ LANGUAGE internal STRICT IMMUTABLE;
+NOTICE: return type casttesttype is only a shell
+CREATE FUNCTION casttesttype_out(casttesttype)
+ RETURNS cstring
+ AS 'textout'
+ LANGUAGE internal STRICT IMMUTABLE;
+NOTICE: argument type casttesttype is only a shell
+CREATE TYPE casttesttype (
+ internallength = variable,
+ input = casttesttype_in,
+ output = casttesttype_out,
+ alignment = int4
+);
+CREATE CAST (text AS casttesttype) WITHOUT FUNCTION;
+CREATE FOREIGN DATA WRAPPER dummy;
+CREATE SERVER s0 FOREIGN DATA WRAPPER dummy;
+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');
+REVOKE EXECUTE ON FUNCTION test_pg_dump(int) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION test_pg_dump(int) TO regress_dump_test_role;
+GRANT SELECT (c1) ON test_pg_dump_t1 TO regress_dump_test_role;
+GRANT SELECT ON test_pg_dump_v1 TO regress_dump_test_role;
+GRANT USAGE ON FOREIGN DATA WRAPPER dummy TO regress_dump_test_role;
+GRANT USAGE ON FOREIGN SERVER s0 TO regress_dump_test_role;
+GRANT SELECT (c1) ON ft1 TO regress_dump_test_role;
+GRANT SELECT ON ft1 TO regress_dump_test_role;
+GRANT UPDATE ON test_pg_dump_mv1 TO regress_dump_test_role;
+GRANT USAGE ON SCHEMA test_pg_dump_s1 TO regress_dump_test_role;
+GRANT USAGE ON TYPE test_pg_dump_e1 TO regress_dump_test_role;
+ALTER EXTENSION test_pg_dump ADD ACCESS METHOD gist2;
+ALTER EXTENSION test_pg_dump ADD AGGREGATE newavg(int4);
+ALTER EXTENSION test_pg_dump ADD CAST (text AS casttesttype);
+ALTER EXTENSION test_pg_dump ADD FOREIGN DATA WRAPPER dummy;
+ALTER EXTENSION test_pg_dump ADD FOREIGN TABLE ft1;
+ALTER EXTENSION test_pg_dump ADD MATERIALIZED VIEW test_pg_dump_mv1;
+ALTER EXTENSION test_pg_dump ADD OPERATOR ==== (int, int);
+ALTER EXTENSION test_pg_dump ADD SCHEMA test_pg_dump_s1;
+ALTER EXTENSION test_pg_dump ADD SERVER s0;
+ALTER EXTENSION test_pg_dump ADD FUNCTION test_pg_dump(int);
+ALTER EXTENSION test_pg_dump ADD TABLE test_pg_dump_t1;
+ALTER EXTENSION test_pg_dump ADD TYPE test_pg_dump_e1;
+ALTER EXTENSION test_pg_dump ADD VIEW test_pg_dump_v1;
+REVOKE SELECT (c1) ON test_pg_dump_t1 FROM regress_dump_test_role;
+REVOKE SELECT ON test_pg_dump_v1 FROM regress_dump_test_role;
+REVOKE USAGE ON FOREIGN DATA WRAPPER dummy FROM regress_dump_test_role;
+ALTER EXTENSION test_pg_dump DROP ACCESS METHOD gist2;
+ALTER EXTENSION test_pg_dump DROP AGGREGATE newavg(int4);
+ALTER EXTENSION test_pg_dump DROP CAST (text AS casttesttype);
+ALTER EXTENSION test_pg_dump DROP FOREIGN DATA WRAPPER dummy;
+ALTER EXTENSION test_pg_dump DROP FOREIGN TABLE ft1;
+ALTER EXTENSION test_pg_dump DROP FUNCTION test_pg_dump(int);
+ALTER EXTENSION test_pg_dump DROP MATERIALIZED VIEW test_pg_dump_mv1;
+ALTER EXTENSION test_pg_dump DROP OPERATOR ==== (int, int);
+ALTER EXTENSION test_pg_dump DROP SCHEMA test_pg_dump_s1;
+ALTER EXTENSION test_pg_dump DROP SERVER s0;
+ALTER EXTENSION test_pg_dump DROP TABLE test_pg_dump_t1;
+ALTER EXTENSION test_pg_dump DROP TYPE test_pg_dump_e1;
+ALTER EXTENSION test_pg_dump DROP VIEW test_pg_dump_v1;
diff --git a/src/test/modules/test_pg_dump/sql/test_pg_dump.sql b/src/test/modules/test_pg_dump/sql/test_pg_dump.sql
new file mode 100644
index 0000000..a61a7c8
--- /dev/null
+++ b/src/test/modules/test_pg_dump/sql/test_pg_dump.sql
@@ -0,0 +1,108 @@
+CREATE ROLE regress_dump_test_role;
+CREATE EXTENSION test_pg_dump;
+
+ALTER EXTENSION test_pg_dump ADD DATABASE postgres; -- error
+
+CREATE TABLE test_pg_dump_t1 (c1 int, junk text);
+ALTER TABLE test_pg_dump_t1 DROP COLUMN junk; -- to exercise dropped-col cases
+CREATE VIEW test_pg_dump_v1 AS SELECT * FROM test_pg_dump_t1;
+CREATE MATERIALIZED VIEW test_pg_dump_mv1 AS SELECT * FROM test_pg_dump_t1;
+CREATE SCHEMA test_pg_dump_s1;
+CREATE TYPE test_pg_dump_e1 AS ENUM ('abc', 'def');
+
+CREATE AGGREGATE newavg (
+ sfunc = int4_avg_accum, basetype = int4, stype = _int8,
+ finalfunc = int8_avg,
+ initcond1 = '{0,0}'
+);
+
+CREATE FUNCTION test_pg_dump(int) RETURNS int AS $$
+BEGIN
+RETURN abs($1);
+END
+$$ LANGUAGE plpgsql IMMUTABLE;
+
+CREATE OPERATOR ==== (
+ LEFTARG = int,
+ RIGHTARG = int,
+ PROCEDURE = int4eq,
+ COMMUTATOR = ====
+);
+
+CREATE ACCESS METHOD gist2 TYPE INDEX HANDLER gisthandler;
+
+CREATE TYPE casttesttype;
+
+CREATE FUNCTION casttesttype_in(cstring)
+ RETURNS casttesttype
+ AS 'textin'
+ LANGUAGE internal STRICT IMMUTABLE;
+CREATE FUNCTION casttesttype_out(casttesttype)
+ RETURNS cstring
+ AS 'textout'
+ LANGUAGE internal STRICT IMMUTABLE;
+
+CREATE TYPE casttesttype (
+ internallength = variable,
+ input = casttesttype_in,
+ output = casttesttype_out,
+ alignment = int4
+);
+
+CREATE CAST (text AS casttesttype) WITHOUT FUNCTION;
+
+CREATE FOREIGN DATA WRAPPER dummy;
+
+CREATE SERVER s0 FOREIGN DATA WRAPPER dummy;
+
+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');
+
+REVOKE EXECUTE ON FUNCTION test_pg_dump(int) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION test_pg_dump(int) TO regress_dump_test_role;
+
+GRANT SELECT (c1) ON test_pg_dump_t1 TO regress_dump_test_role;
+GRANT SELECT ON test_pg_dump_v1 TO regress_dump_test_role;
+GRANT USAGE ON FOREIGN DATA WRAPPER dummy TO regress_dump_test_role;
+GRANT USAGE ON FOREIGN SERVER s0 TO regress_dump_test_role;
+GRANT SELECT (c1) ON ft1 TO regress_dump_test_role;
+GRANT SELECT ON ft1 TO regress_dump_test_role;
+GRANT UPDATE ON test_pg_dump_mv1 TO regress_dump_test_role;
+GRANT USAGE ON SCHEMA test_pg_dump_s1 TO regress_dump_test_role;
+GRANT USAGE ON TYPE test_pg_dump_e1 TO regress_dump_test_role;
+
+ALTER EXTENSION test_pg_dump ADD ACCESS METHOD gist2;
+ALTER EXTENSION test_pg_dump ADD AGGREGATE newavg(int4);
+ALTER EXTENSION test_pg_dump ADD CAST (text AS casttesttype);
+ALTER EXTENSION test_pg_dump ADD FOREIGN DATA WRAPPER dummy;
+ALTER EXTENSION test_pg_dump ADD FOREIGN TABLE ft1;
+ALTER EXTENSION test_pg_dump ADD MATERIALIZED VIEW test_pg_dump_mv1;
+ALTER EXTENSION test_pg_dump ADD OPERATOR ==== (int, int);
+ALTER EXTENSION test_pg_dump ADD SCHEMA test_pg_dump_s1;
+ALTER EXTENSION test_pg_dump ADD SERVER s0;
+ALTER EXTENSION test_pg_dump ADD FUNCTION test_pg_dump(int);
+ALTER EXTENSION test_pg_dump ADD TABLE test_pg_dump_t1;
+ALTER EXTENSION test_pg_dump ADD TYPE test_pg_dump_e1;
+ALTER EXTENSION test_pg_dump ADD VIEW test_pg_dump_v1;
+
+REVOKE SELECT (c1) ON test_pg_dump_t1 FROM regress_dump_test_role;
+REVOKE SELECT ON test_pg_dump_v1 FROM regress_dump_test_role;
+REVOKE USAGE ON FOREIGN DATA WRAPPER dummy FROM regress_dump_test_role;
+
+ALTER EXTENSION test_pg_dump DROP ACCESS METHOD gist2;
+ALTER EXTENSION test_pg_dump DROP AGGREGATE newavg(int4);
+ALTER EXTENSION test_pg_dump DROP CAST (text AS casttesttype);
+ALTER EXTENSION test_pg_dump DROP FOREIGN DATA WRAPPER dummy;
+ALTER EXTENSION test_pg_dump DROP FOREIGN TABLE ft1;
+ALTER EXTENSION test_pg_dump DROP FUNCTION test_pg_dump(int);
+ALTER EXTENSION test_pg_dump DROP MATERIALIZED VIEW test_pg_dump_mv1;
+ALTER EXTENSION test_pg_dump DROP OPERATOR ==== (int, int);
+ALTER EXTENSION test_pg_dump DROP SCHEMA test_pg_dump_s1;
+ALTER EXTENSION test_pg_dump DROP SERVER s0;
+ALTER EXTENSION test_pg_dump DROP TABLE test_pg_dump_t1;
+ALTER EXTENSION test_pg_dump DROP TYPE test_pg_dump_e1;
+ALTER EXTENSION test_pg_dump DROP VIEW test_pg_dump_v1;
diff --git a/src/test/modules/test_pg_dump/t/001_base.pl b/src/test/modules/test_pg_dump/t/001_base.pl
new file mode 100644
index 0000000..4baca36
--- /dev/null
+++ b/src/test/modules/test_pg_dump/t/001_base.pl
@@ -0,0 +1,842 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+my $tempdir = TestLib::tempdir;
+my $tempdir_short = TestLib::tempdir_short;
+
+###############################################################
+# This structure is based off of the src/bin/pg_dump/t test
+# suite.
+###############################################################
+# Definition of the pg_dump runs to make.
+#
+# Each of these runs are named and those names are used below
+# to define how each test should (or shouldn't) treat a result
+# from a given run.
+#
+# test_key indicates that a given run should simply use the same
+# set of like/unlike tests as another run, and which run that is.
+#
+# dump_cmd is the pg_dump command to run, which is an array of
+# the full command and arguments to run. Note that this is run
+# using $node->command_ok(), so the port does not need to be
+# specified and is pulled from $PGPORT, which is set by the
+# PostgresNode system.
+#
+# restore_cmd is the pg_restore command to run, if any. Note
+# that this should generally be used when the pg_dump goes to
+# a non-text file and that the restore can then be used to
+# generate a text file to run through the tests from the
+# non-text file generated by pg_dump.
+#
+# TODO: Have pg_restore actually restore to an independent
+# database and then pg_dump *that* database (or something along
+# those lines) to validate that part of the process.
+
+my %pgdump_runs = (
+ binary_upgrade => {
+ dump_cmd => [
+ 'pg_dump', '--no-sync',
+ "--file=$tempdir/binary_upgrade.sql", '--schema-only',
+ '--binary-upgrade', '--dbname=postgres',
+ ],
+ },
+ clean => {
+ dump_cmd => [
+ 'pg_dump', "--file=$tempdir/clean.sql",
+ '-c', '--no-sync',
+ '--dbname=postgres',
+ ],
+ },
+ clean_if_exists => {
+ dump_cmd => [
+ 'pg_dump',
+ '--no-sync',
+ "--file=$tempdir/clean_if_exists.sql",
+ '-c',
+ '--if-exists',
+ '--encoding=UTF8', # no-op, just tests that option is accepted
+ 'postgres',
+ ],
+ },
+ createdb => {
+ dump_cmd => [
+ 'pg_dump',
+ '--no-sync',
+ "--file=$tempdir/createdb.sql",
+ '-C',
+ '-R', # no-op, just for testing
+ 'postgres',
+ ],
+ },
+ data_only => {
+ dump_cmd => [
+ 'pg_dump',
+ '--no-sync',
+ "--file=$tempdir/data_only.sql",
+ '-a',
+ '-v', # no-op, just make sure it works
+ 'postgres',
+ ],
+ },
+ defaults => {
+ dump_cmd => [ 'pg_dump', '-f', "$tempdir/defaults.sql", 'postgres', ],
+ },
+ defaults_custom_format => {
+ test_key => 'defaults',
+ dump_cmd => [
+ 'pg_dump', '--no-sync', '-Fc', '-Z6',
+ "--file=$tempdir/defaults_custom_format.dump", 'postgres',
+ ],
+ restore_cmd => [
+ 'pg_restore',
+ "--file=$tempdir/defaults_custom_format.sql",
+ "$tempdir/defaults_custom_format.dump",
+ ],
+ },
+ defaults_dir_format => {
+ test_key => 'defaults',
+ dump_cmd => [
+ 'pg_dump', '--no-sync', '-Fd',
+ "--file=$tempdir/defaults_dir_format", 'postgres',
+ ],
+ restore_cmd => [
+ 'pg_restore',
+ "--file=$tempdir/defaults_dir_format.sql",
+ "$tempdir/defaults_dir_format",
+ ],
+ },
+ defaults_parallel => {
+ test_key => 'defaults',
+ dump_cmd => [
+ 'pg_dump', '--no-sync', '-Fd', '-j2',
+ "--file=$tempdir/defaults_parallel", 'postgres',
+ ],
+ restore_cmd => [
+ 'pg_restore',
+ "--file=$tempdir/defaults_parallel.sql",
+ "$tempdir/defaults_parallel",
+ ],
+ },
+ defaults_tar_format => {
+ test_key => 'defaults',
+ dump_cmd => [
+ 'pg_dump', '--no-sync', '-Ft',
+ "--file=$tempdir/defaults_tar_format.tar", 'postgres',
+ ],
+ restore_cmd => [
+ 'pg_restore',
+ "--file=$tempdir/defaults_tar_format.sql",
+ "$tempdir/defaults_tar_format.tar",
+ ],
+ },
+ exclude_table => {
+ dump_cmd => [
+ 'pg_dump',
+ '--exclude-table=regress_table_dumpable',
+ "--file=$tempdir/exclude_table.sql",
+ 'postgres',
+ ],
+ },
+ extension_schema => {
+ dump_cmd => [
+ 'pg_dump', '--schema=public',
+ "--file=$tempdir/extension_schema.sql", 'postgres',
+ ],
+ },
+ pg_dumpall_globals => {
+ dump_cmd => [
+ 'pg_dumpall', '--no-sync',
+ "--file=$tempdir/pg_dumpall_globals.sql", '-g',
+ ],
+ },
+ no_privs => {
+ dump_cmd => [
+ 'pg_dump', '--no-sync',
+ "--file=$tempdir/no_privs.sql", '-x',
+ 'postgres',
+ ],
+ },
+ no_owner => {
+ dump_cmd => [
+ 'pg_dump', '--no-sync',
+ "--file=$tempdir/no_owner.sql", '-O',
+ 'postgres',
+ ],
+ },
+ schema_only => {
+ dump_cmd => [
+ 'pg_dump', '--no-sync', "--file=$tempdir/schema_only.sql",
+ '-s', 'postgres',
+ ],
+ },
+ section_pre_data => {
+ dump_cmd => [
+ 'pg_dump', '--no-sync',
+ "--file=$tempdir/section_pre_data.sql", '--section=pre-data',
+ 'postgres',
+ ],
+ },
+ section_data => {
+ dump_cmd => [
+ 'pg_dump', '--no-sync',
+ "--file=$tempdir/section_data.sql", '--section=data',
+ 'postgres',
+ ],
+ },
+ section_post_data => {
+ dump_cmd => [
+ 'pg_dump', '--no-sync', "--file=$tempdir/section_post_data.sql",
+ '--section=post-data', 'postgres',
+ ],
+ },
+ with_extension => {
+ dump_cmd => [
+ 'pg_dump', '--no-sync', "--file=$tempdir/with_extension.sql",
+ '--extension=test_pg_dump', 'postgres',
+ ],
+ },
+
+ # plgsql in the list blocks the dump of extension test_pg_dump
+ without_extension => {
+ dump_cmd => [
+ 'pg_dump', '--no-sync', "--file=$tempdir/without_extension.sql",
+ '--extension=plpgsql', 'postgres',
+ ],
+ },
+
+ # plgsql in the list of extensions blocks the dump of extension
+ # test_pg_dump. "public" is the schema used by the extension
+ # test_pg_dump, but none of its objects should be dumped.
+ without_extension_explicit_schema => {
+ dump_cmd => [
+ 'pg_dump',
+ '--no-sync',
+ "--file=$tempdir/without_extension_explicit_schema.sql",
+ '--extension=plpgsql',
+ '--schema=public',
+ 'postgres',
+ ],
+ },
+
+ # plgsql in the list of extensions blocks the dump of extension
+ # test_pg_dump, but not the dump of objects not dependent on the
+ # extension located on a schema maintained by the extension.
+ without_extension_internal_schema => {
+ dump_cmd => [
+ 'pg_dump',
+ '--no-sync',
+ "--file=$tempdir/without_extension_internal_schema.sql",
+ '--extension=plpgsql',
+ '--schema=regress_pg_dump_schema',
+ 'postgres',
+ ],
+ },);
+
+###############################################################
+# Definition of the tests to run.
+#
+# Each test is defined using the log message that will be used.
+#
+# A regexp should be defined for each test which provides the
+# basis for the test. That regexp will be run against the output
+# file of each of the runs which the test is to be run against
+# and the success of the result will depend on if the regexp
+# result matches the expected 'like' or 'unlike' case.
+# The runs listed as 'like' will be checked if they match the
+# regexp and, if so, the test passes. All runs which are not
+# listed as 'like' will be checked to ensure they don't match
+# the regexp; if they do, the test will fail.
+#
+# The below hashes provide convenience sets of runs. Individual
+# runs can be excluded from a general hash by placing that run
+# into the 'unlike' section.
+#
+# There can then be a 'create_sql' and 'create_order' for a
+# given test. The 'create_sql' commands are collected up in
+# 'create_order' and then run against the database prior to any
+# of the pg_dump runs happening. This is what "seeds" the
+# system with objects to be dumped out.
+#
+# Building of this hash takes a bit of time as all of the regexps
+# included in it are compiled. This greatly improves performance
+# as the regexps are used for each run the test applies to.
+
+# Tests which are considered 'full' dumps by pg_dump, but there
+# are flags used to exclude specific items (ACLs, blobs, etc).
+my %full_runs = (
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_table => 1,
+ no_privs => 1,
+ no_owner => 1,
+ with_extension => 1,
+ without_extension => 1);
+
+my %tests = (
+ 'ALTER EXTENSION test_pg_dump' => {
+ create_order => 9,
+ create_sql =>
+ 'ALTER EXTENSION test_pg_dump ADD TABLE regress_pg_dump_table_added;',
+ regexp => qr/^
+ \QCREATE TABLE public.regress_pg_dump_table_added (\E
+ \n\s+\Qcol1 integer NOT NULL,\E
+ \n\s+\Qcol2 integer\E
+ \n\);\n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'CREATE EXTENSION test_pg_dump' => {
+ create_order => 2,
+ create_sql => 'CREATE EXTENSION test_pg_dump;',
+ regexp => qr/^
+ \QCREATE EXTENSION IF NOT EXISTS test_pg_dump WITH SCHEMA public;\E
+ \n/xm,
+ like => {
+ %full_runs,
+ schema_only => 1,
+ section_pre_data => 1,
+ },
+ unlike => { binary_upgrade => 1, without_extension => 1 },
+ },
+
+ 'CREATE ROLE regress_dump_test_role' => {
+ create_order => 1,
+ create_sql => 'CREATE ROLE regress_dump_test_role;',
+ regexp => qr/^CREATE ROLE regress_dump_test_role;\n/m,
+ like => { pg_dumpall_globals => 1, },
+ },
+
+ 'CREATE SEQUENCE regress_pg_dump_table_col1_seq' => {
+ regexp => qr/^
+ \QCREATE SEQUENCE public.regress_pg_dump_table_col1_seq\E
+ \n\s+\QAS integer\E
+ \n\s+\QSTART WITH 1\E
+ \n\s+\QINCREMENT BY 1\E
+ \n\s+\QNO MINVALUE\E
+ \n\s+\QNO MAXVALUE\E
+ \n\s+\QCACHE 1;\E
+ \n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'CREATE TABLE regress_pg_dump_table_added' => {
+ create_order => 7,
+ create_sql =>
+ 'CREATE TABLE regress_pg_dump_table_added (col1 int not null, col2 int);',
+ regexp => qr/^
+ \QCREATE TABLE public.regress_pg_dump_table_added (\E
+ \n\s+\Qcol1 integer NOT NULL,\E
+ \n\s+\Qcol2 integer\E
+ \n\);\n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'CREATE SEQUENCE regress_pg_dump_seq' => {
+ regexp => qr/^
+ \QCREATE SEQUENCE public.regress_pg_dump_seq\E
+ \n\s+\QSTART WITH 1\E
+ \n\s+\QINCREMENT BY 1\E
+ \n\s+\QNO MINVALUE\E
+ \n\s+\QNO MAXVALUE\E
+ \n\s+\QCACHE 1;\E
+ \n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'SETVAL SEQUENCE regress_seq_dumpable' => {
+ create_order => 6,
+ create_sql => qq{SELECT nextval('regress_seq_dumpable');},
+ regexp => qr/^
+ \QSELECT pg_catalog.setval('public.regress_seq_dumpable', 1, true);\E
+ \n/xm,
+ like => {
+ %full_runs,
+ data_only => 1,
+ section_data => 1,
+ extension_schema => 1,
+ },
+ unlike => { without_extension => 1, },
+ },
+
+ 'CREATE TABLE regress_pg_dump_table' => {
+ regexp => qr/^
+ \QCREATE TABLE public.regress_pg_dump_table (\E
+ \n\s+\Qcol1 integer NOT NULL,\E
+ \n\s+\Qcol2 integer,\E
+ \n\s+\QCONSTRAINT regress_pg_dump_table_col2_check CHECK ((col2 > 0))\E
+ \n\);\n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'COPY public.regress_table_dumpable (col1)' => {
+ regexp => qr/^
+ \QCOPY public.regress_table_dumpable (col1) FROM stdin;\E
+ \n/xm,
+ like => {
+ %full_runs,
+ data_only => 1,
+ section_data => 1,
+ extension_schema => 1,
+ },
+ unlike => {
+ binary_upgrade => 1,
+ exclude_table => 1,
+ without_extension => 1,
+ },
+ },
+
+ 'REVOKE ALL ON FUNCTION wgo_then_no_access' => {
+ create_order => 3,
+ create_sql => q{
+ DO $$BEGIN EXECUTE format(
+ 'REVOKE ALL ON FUNCTION wgo_then_no_access()
+ FROM pg_signal_backend, public, %I',
+ (SELECT usename
+ FROM pg_user JOIN pg_proc ON proowner = usesysid
+ WHERE proname = 'wgo_then_no_access')); END$$;},
+ regexp => qr/^
+ \QREVOKE ALL ON FUNCTION public.wgo_then_no_access() FROM PUBLIC;\E
+ \n\QREVOKE ALL ON FUNCTION public.wgo_then_no_access() FROM \E.*;
+ \n\QREVOKE ALL ON FUNCTION public.wgo_then_no_access() FROM pg_signal_backend;\E
+ /xm,
+ like => {
+ %full_runs,
+ schema_only => 1,
+ section_pre_data => 1,
+ },
+ unlike => { no_privs => 1, without_extension => 1, },
+ },
+
+ 'REVOKE GRANT OPTION FOR UPDATE ON SEQUENCE wgo_then_regular' => {
+ create_order => 3,
+ create_sql => 'REVOKE GRANT OPTION FOR UPDATE ON SEQUENCE
+ wgo_then_regular FROM pg_signal_backend;',
+ regexp => qr/^
+ \QREVOKE ALL ON SEQUENCE public.wgo_then_regular FROM pg_signal_backend;\E
+ \n\QGRANT SELECT,UPDATE ON SEQUENCE public.wgo_then_regular TO pg_signal_backend;\E
+ \n\QGRANT USAGE ON SEQUENCE public.wgo_then_regular TO pg_signal_backend WITH GRANT OPTION;\E
+ /xm,
+ like => {
+ %full_runs,
+ schema_only => 1,
+ section_pre_data => 1,
+ },
+ unlike => { no_privs => 1, without_extension => 1, },
+ },
+
+ 'CREATE ACCESS METHOD regress_test_am' => {
+ regexp => qr/^
+ \QCREATE ACCESS METHOD regress_test_am TYPE INDEX HANDLER bthandler;\E
+ \n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'COMMENT ON EXTENSION test_pg_dump' => {
+ regexp => qr/^
+ \QCOMMENT ON EXTENSION test_pg_dump \E
+ \QIS 'Test pg_dump with an extension';\E
+ \n/xm,
+ like => {
+ %full_runs,
+ schema_only => 1,
+ section_pre_data => 1,
+ },
+ unlike => { without_extension => 1, },
+ },
+
+ 'GRANT SELECT regress_pg_dump_table_added pre-ALTER EXTENSION' => {
+ create_order => 8,
+ create_sql =>
+ 'GRANT SELECT ON regress_pg_dump_table_added TO regress_dump_test_role;',
+ regexp => qr/^
+ \QGRANT SELECT ON TABLE public.regress_pg_dump_table_added TO regress_dump_test_role;\E
+ \n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'REVOKE SELECT regress_pg_dump_table_added post-ALTER EXTENSION' => {
+ create_order => 10,
+ create_sql =>
+ 'REVOKE SELECT ON regress_pg_dump_table_added FROM regress_dump_test_role;',
+ regexp => qr/^
+ \QREVOKE SELECT ON TABLE public.regress_pg_dump_table_added FROM regress_dump_test_role;\E
+ \n/xm,
+ like => {
+ %full_runs,
+ schema_only => 1,
+ section_pre_data => 1,
+ },
+ unlike => { no_privs => 1, without_extension => 1, },
+ },
+
+ 'GRANT SELECT ON TABLE regress_pg_dump_table' => {
+ regexp => qr/^
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\E\n
+ \QGRANT SELECT ON TABLE public.regress_pg_dump_table TO regress_dump_test_role;\E\n
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(false);\E
+ \n/xms,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'GRANT SELECT(col1) ON regress_pg_dump_table' => {
+ regexp => qr/^
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\E\n
+ \QGRANT SELECT(col1) ON TABLE public.regress_pg_dump_table TO PUBLIC;\E\n
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(false);\E
+ \n/xms,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'GRANT SELECT(col2) ON regress_pg_dump_table TO regress_dump_test_role'
+ => {
+ create_order => 4,
+ create_sql => 'GRANT SELECT(col2) ON regress_pg_dump_table
+ TO regress_dump_test_role;',
+ regexp => qr/^
+ \QGRANT SELECT(col2) ON TABLE public.regress_pg_dump_table TO regress_dump_test_role;\E
+ \n/xm,
+ like => {
+ %full_runs,
+ schema_only => 1,
+ section_pre_data => 1,
+ },
+ unlike => { no_privs => 1, without_extension => 1 },
+ },
+
+ 'GRANT USAGE ON regress_pg_dump_table_col1_seq TO regress_dump_test_role'
+ => {
+ create_order => 5,
+ create_sql => 'GRANT USAGE ON SEQUENCE regress_pg_dump_table_col1_seq
+ TO regress_dump_test_role;',
+ regexp => qr/^
+ \QGRANT USAGE ON SEQUENCE public.regress_pg_dump_table_col1_seq TO regress_dump_test_role;\E
+ \n/xm,
+ like => {
+ %full_runs,
+ schema_only => 1,
+ section_pre_data => 1,
+ },
+ unlike => { no_privs => 1, without_extension => 1, },
+ },
+
+ 'GRANT USAGE ON regress_pg_dump_seq TO regress_dump_test_role' => {
+ regexp => qr/^
+ \QGRANT USAGE ON SEQUENCE public.regress_pg_dump_seq TO regress_dump_test_role;\E
+ \n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'REVOKE SELECT(col1) ON regress_pg_dump_table' => {
+ create_order => 3,
+ create_sql => 'REVOKE SELECT(col1) ON regress_pg_dump_table
+ FROM PUBLIC;',
+ regexp => qr/^
+ \QREVOKE SELECT(col1) ON TABLE public.regress_pg_dump_table FROM PUBLIC;\E
+ \n/xm,
+ like => {
+ %full_runs,
+ schema_only => 1,
+ section_pre_data => 1,
+ },
+ unlike => { no_privs => 1, without_extension => 1, },
+ },
+
+ # Objects included in extension part of a schema created by this extension */
+ 'CREATE TABLE regress_pg_dump_schema.test_table' => {
+ regexp => qr/^
+ \QCREATE TABLE regress_pg_dump_schema.test_table (\E
+ \n\s+\Qcol1 integer,\E
+ \n\s+\Qcol2 integer,\E
+ \n\s+\QCONSTRAINT test_table_col2_check CHECK ((col2 > 0))\E
+ \n\);\n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'GRANT SELECT ON regress_pg_dump_schema.test_table' => {
+ regexp => qr/^
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\E\n
+ \QGRANT SELECT ON TABLE regress_pg_dump_schema.test_table TO regress_dump_test_role;\E\n
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(false);\E
+ \n/xms,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'CREATE SEQUENCE regress_pg_dump_schema.test_seq' => {
+ regexp => qr/^
+ \QCREATE SEQUENCE regress_pg_dump_schema.test_seq\E
+ \n\s+\QSTART WITH 1\E
+ \n\s+\QINCREMENT BY 1\E
+ \n\s+\QNO MINVALUE\E
+ \n\s+\QNO MAXVALUE\E
+ \n\s+\QCACHE 1;\E
+ \n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'GRANT USAGE ON regress_pg_dump_schema.test_seq' => {
+ regexp => qr/^
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\E\n
+ \QGRANT USAGE ON SEQUENCE regress_pg_dump_schema.test_seq TO regress_dump_test_role;\E\n
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(false);\E
+ \n/xms,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'CREATE TYPE regress_pg_dump_schema.test_type' => {
+ regexp => qr/^
+ \QCREATE TYPE regress_pg_dump_schema.test_type AS (\E
+ \n\s+\Qcol1 integer\E
+ \n\);\n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'GRANT USAGE ON regress_pg_dump_schema.test_type' => {
+ regexp => qr/^
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\E\n
+ \QGRANT ALL ON TYPE regress_pg_dump_schema.test_type TO regress_dump_test_role;\E\n
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(false);\E
+ \n/xms,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'CREATE FUNCTION regress_pg_dump_schema.test_func' => {
+ regexp => qr/^
+ \QCREATE FUNCTION regress_pg_dump_schema.test_func() RETURNS integer\E
+ \n\s+\QLANGUAGE sql\E
+ \n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'GRANT ALL ON regress_pg_dump_schema.test_func' => {
+ regexp => qr/^
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\E\n
+ \QGRANT ALL ON FUNCTION regress_pg_dump_schema.test_func() TO regress_dump_test_role;\E\n
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(false);\E
+ \n/xms,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'CREATE AGGREGATE regress_pg_dump_schema.test_agg' => {
+ regexp => qr/^
+ \QCREATE AGGREGATE regress_pg_dump_schema.test_agg(smallint) (\E
+ \n\s+\QSFUNC = int2_sum,\E
+ \n\s+\QSTYPE = bigint\E
+ \n\);\n/xm,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'GRANT ALL ON regress_pg_dump_schema.test_agg' => {
+ regexp => qr/^
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\E\n
+ \QGRANT ALL ON FUNCTION regress_pg_dump_schema.test_agg(smallint) TO regress_dump_test_role;\E\n
+ \QSELECT pg_catalog.binary_upgrade_set_record_init_privs(false);\E
+ \n/xms,
+ like => { binary_upgrade => 1, },
+ },
+
+ 'ALTER INDEX pkey DEPENDS ON extension' => {
+ create_order => 11,
+ create_sql =>
+ 'CREATE TABLE regress_pg_dump_schema.extdependtab (col1 integer primary key, col2 int);
+ CREATE INDEX ON regress_pg_dump_schema.extdependtab (col2);
+ ALTER INDEX regress_pg_dump_schema.extdependtab_col2_idx DEPENDS ON EXTENSION test_pg_dump;
+ ALTER INDEX regress_pg_dump_schema.extdependtab_pkey DEPENDS ON EXTENSION test_pg_dump;',
+ regexp => qr/^
+ \QALTER INDEX regress_pg_dump_schema.extdependtab_pkey DEPENDS ON EXTENSION test_pg_dump;\E\n
+ /xms,
+ like => {%pgdump_runs},
+ unlike => {
+ data_only => 1,
+ extension_schema => 1,
+ pg_dumpall_globals => 1,
+ section_data => 1,
+ section_pre_data => 1,
+ # Excludes this schema as extension is not listed.
+ without_extension_explicit_schema => 1,
+ },
+ },
+
+ 'ALTER INDEX idx DEPENDS ON extension' => {
+ regexp => qr/^
+ \QALTER INDEX regress_pg_dump_schema.extdependtab_col2_idx DEPENDS ON EXTENSION test_pg_dump;\E\n
+ /xms,
+ like => {%pgdump_runs},
+ unlike => {
+ data_only => 1,
+ extension_schema => 1,
+ pg_dumpall_globals => 1,
+ section_data => 1,
+ section_pre_data => 1,
+ # Excludes this schema as extension is not listed.
+ without_extension_explicit_schema => 1,
+ },
+ },
+
+ # Objects not included in extension, part of schema created by extension
+ 'CREATE TABLE regress_pg_dump_schema.external_tab' => {
+ create_order => 4,
+ create_sql => 'CREATE TABLE regress_pg_dump_schema.external_tab
+ (col1 int);',
+ regexp => qr/^
+ \QCREATE TABLE regress_pg_dump_schema.external_tab (\E
+ \n\s+\Qcol1 integer\E
+ \n\);\n/xm,
+ like => {
+ %full_runs,
+ schema_only => 1,
+ section_pre_data => 1,
+ # Excludes the extension and keeps the schema's data.
+ without_extension_internal_schema => 1,
+ },
+ },);
+
+#########################################
+# Create a PG instance to test actually dumping from
+
+my $node = get_new_node('main');
+$node->init;
+$node->start;
+
+my $port = $node->port;
+
+my $num_tests = 0;
+
+foreach my $run (sort keys %pgdump_runs)
+{
+ my $test_key = $run;
+
+ # Each run of pg_dump is a test itself
+ $num_tests++;
+
+ # If there is a restore cmd, that's another test
+ if ($pgdump_runs{$run}->{restore_cmd})
+ {
+ $num_tests++;
+ }
+
+ if ($pgdump_runs{$run}->{test_key})
+ {
+ $test_key = $pgdump_runs{$run}->{test_key};
+ }
+
+ # Then count all the tests run against each run
+ foreach my $test (sort keys %tests)
+ {
+ # If there is a like entry, but no unlike entry, then we will test the like case
+ if ($tests{$test}->{like}->{$test_key}
+ && !defined($tests{$test}->{unlike}->{$test_key}))
+ {
+ $num_tests++;
+ }
+ else
+ {
+ # We will test everything that isn't a 'like'
+ $num_tests++;
+ }
+ }
+}
+plan tests => $num_tests;
+
+#########################################
+# Set up schemas, tables, etc, to be dumped.
+
+# Build up the create statements
+my $create_sql = '';
+
+foreach my $test (
+ sort {
+ if ($tests{$a}->{create_order} and $tests{$b}->{create_order})
+ {
+ $tests{$a}->{create_order} <=> $tests{$b}->{create_order};
+ }
+ elsif ($tests{$a}->{create_order})
+ {
+ -1;
+ }
+ elsif ($tests{$b}->{create_order})
+ {
+ 1;
+ }
+ else
+ {
+ 0;
+ }
+ } keys %tests)
+{
+ if ($tests{$test}->{create_sql})
+ {
+ $create_sql .= $tests{$test}->{create_sql};
+ }
+}
+
+# Send the combined set of commands to psql
+$node->safe_psql('postgres', $create_sql);
+
+#########################################
+# Run all runs
+
+foreach my $run (sort keys %pgdump_runs)
+{
+
+ my $test_key = $run;
+
+ $node->command_ok(\@{ $pgdump_runs{$run}->{dump_cmd} },
+ "$run: pg_dump runs");
+
+ if ($pgdump_runs{$run}->{restore_cmd})
+ {
+ $node->command_ok(\@{ $pgdump_runs{$run}->{restore_cmd} },
+ "$run: pg_restore runs");
+ }
+
+ if ($pgdump_runs{$run}->{test_key})
+ {
+ $test_key = $pgdump_runs{$run}->{test_key};
+ }
+
+ my $output_file = slurp_file("$tempdir/${run}.sql");
+
+ #########################################
+ # Run all tests where this run is included
+ # as either a 'like' or 'unlike' test.
+
+ foreach my $test (sort keys %tests)
+ {
+ # Run the test listed as a like, unless it is specifically noted
+ # as an unlike (generally due to an explicit exclusion or similar).
+ if ($tests{$test}->{like}->{$test_key}
+ && !defined($tests{$test}->{unlike}->{$test_key}))
+ {
+ if (!ok($output_file =~ $tests{$test}->{regexp},
+ "$run: should dump $test"))
+ {
+ diag("Review $run results in $tempdir");
+ }
+ }
+ else
+ {
+ if (!ok($output_file !~ $tests{$test}->{regexp},
+ "$run: should not dump $test"))
+ {
+ diag("Review $run results in $tempdir");
+ }
+ }
+ }
+}
+
+#########################################
+# Stop the database instance, which will be removed at the end of the tests.
+
+$node->stop('fast');
diff --git a/src/test/modules/test_pg_dump/test_pg_dump--1.0.sql b/src/test/modules/test_pg_dump/test_pg_dump--1.0.sql
new file mode 100644
index 0000000..110f7ee
--- /dev/null
+++ b/src/test/modules/test_pg_dump/test_pg_dump--1.0.sql
@@ -0,0 +1,62 @@
+/* src/test/modules/test_pg_dump/test_pg_dump--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_pg_dump" to load this file. \quit
+
+CREATE TABLE regress_pg_dump_table (
+ col1 serial,
+ col2 int check (col2 > 0)
+);
+
+CREATE SEQUENCE regress_pg_dump_seq;
+
+CREATE SEQUENCE regress_seq_dumpable;
+SELECT pg_catalog.pg_extension_config_dump('regress_seq_dumpable', '');
+
+CREATE TABLE regress_table_dumpable (
+ col1 int check (col1 > 0)
+);
+SELECT pg_catalog.pg_extension_config_dump('regress_table_dumpable', '');
+
+CREATE SCHEMA regress_pg_dump_schema;
+
+GRANT USAGE ON regress_pg_dump_seq TO regress_dump_test_role;
+
+GRANT SELECT ON regress_pg_dump_table TO regress_dump_test_role;
+GRANT SELECT(col1) ON regress_pg_dump_table TO public;
+
+GRANT SELECT(col2) ON regress_pg_dump_table TO regress_dump_test_role;
+REVOKE SELECT(col2) ON regress_pg_dump_table FROM regress_dump_test_role;
+
+CREATE FUNCTION wgo_then_no_access() RETURNS int LANGUAGE SQL AS 'SELECT 1';
+GRANT ALL ON FUNCTION wgo_then_no_access()
+ TO pg_signal_backend WITH GRANT OPTION;
+
+CREATE SEQUENCE wgo_then_regular;
+GRANT ALL ON SEQUENCE wgo_then_regular TO pg_signal_backend WITH GRANT OPTION;
+REVOKE GRANT OPTION FOR SELECT ON SEQUENCE wgo_then_regular
+ FROM pg_signal_backend;
+
+CREATE ACCESS METHOD regress_test_am TYPE INDEX HANDLER bthandler;
+
+-- Create a set of objects that are part of the schema created by
+-- this extension.
+CREATE TABLE regress_pg_dump_schema.test_table (
+ col1 int,
+ col2 int check (col2 > 0)
+);
+GRANT SELECT ON regress_pg_dump_schema.test_table TO regress_dump_test_role;
+
+CREATE SEQUENCE regress_pg_dump_schema.test_seq;
+GRANT USAGE ON regress_pg_dump_schema.test_seq TO regress_dump_test_role;
+
+CREATE TYPE regress_pg_dump_schema.test_type AS (col1 int);
+GRANT USAGE ON TYPE regress_pg_dump_schema.test_type TO regress_dump_test_role;
+
+CREATE FUNCTION regress_pg_dump_schema.test_func () RETURNS int
+AS 'SELECT 1;' LANGUAGE SQL;
+GRANT EXECUTE ON FUNCTION regress_pg_dump_schema.test_func() TO regress_dump_test_role;
+
+CREATE AGGREGATE regress_pg_dump_schema.test_agg(int2)
+(SFUNC = int2_sum, STYPE = int8);
+GRANT EXECUTE ON FUNCTION regress_pg_dump_schema.test_agg(int2) TO regress_dump_test_role;
diff --git a/src/test/modules/test_pg_dump/test_pg_dump.control b/src/test/modules/test_pg_dump/test_pg_dump.control
new file mode 100644
index 0000000..fe3450d
--- /dev/null
+++ b/src/test/modules/test_pg_dump/test_pg_dump.control
@@ -0,0 +1,3 @@
+comment = 'Test pg_dump with an extension'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_predtest/.gitignore b/src/test/modules/test_predtest/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_predtest/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_predtest/Makefile b/src/test/modules/test_predtest/Makefile
new file mode 100644
index 0000000..a235e2a
--- /dev/null
+++ b/src/test/modules/test_predtest/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_predtest/Makefile
+
+MODULE_big = test_predtest
+OBJS = \
+ $(WIN32RES) \
+ test_predtest.o
+PGFILEDESC = "test_predtest - test code for optimizer/util/predtest.c"
+
+EXTENSION = test_predtest
+DATA = test_predtest--1.0.sql
+
+REGRESS = test_predtest
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_predtest
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_predtest/README b/src/test/modules/test_predtest/README
new file mode 100644
index 0000000..2c9bec0
--- /dev/null
+++ b/src/test/modules/test_predtest/README
@@ -0,0 +1,28 @@
+test_predtest is a module for checking the correctness of the optimizer's
+predicate-proof logic, in src/backend/optimizer/util/predtest.c.
+
+The module provides a function that allows direct application of
+predtest.c's exposed functions, predicate_implied_by() and
+predicate_refuted_by(), to arbitrary boolean expressions, with direct
+inspection of the results. This could be done indirectly by checking
+planner results, but it can be difficult to construct end-to-end test
+cases that prove that the expected results were obtained.
+
+In general, the use of this function is like
+ select * from test_predtest('query string')
+where the query string must be a SELECT returning two boolean
+columns, for example
+
+ select * from test_predtest($$
+ select x, not x
+ from (values (false), (true), (null)) as v(x)
+ $$);
+
+The function parses and plans the given query, and then applies the
+predtest.c code to the two boolean expressions in the SELECT list, to see
+if the first expression can be proven or refuted by the second. It also
+executes the query, and checks the resulting rows to see whether any
+claimed implication or refutation relationship actually holds. If the
+query is designed to exercise the expressions on a full set of possible
+input values, as in the example above, then this provides a mechanical
+cross-check as to whether the proof code has given a correct answer.
diff --git a/src/test/modules/test_predtest/expected/test_predtest.out b/src/test/modules/test_predtest/expected/test_predtest.out
new file mode 100644
index 0000000..6d21bcd
--- /dev/null
+++ b/src/test/modules/test_predtest/expected/test_predtest.out
@@ -0,0 +1,1096 @@
+CREATE EXTENSION test_predtest;
+-- Make output more legible
+\pset expanded on
+-- Test data
+-- all combinations of four boolean values
+create table booleans as
+select
+ case i%3 when 0 then true when 1 then false else null end as x,
+ case (i/3)%3 when 0 then true when 1 then false else null end as y,
+ case (i/9)%3 when 0 then true when 1 then false else null end as z,
+ case (i/27)%3 when 0 then true when 1 then false else null end as w
+from generate_series(0, 3*3*3*3-1) i;
+-- all combinations of two integers 0..9, plus null
+create table integers as
+select
+ case i%11 when 10 then null else i%11 end as x,
+ case (i/11)%11 when 10 then null else (i/11)%11 end as y
+from generate_series(0, 11*11-1) i;
+-- and a simple strict function that's opaque to the optimizer
+create function strictf(bool, bool) returns bool
+language plpgsql as $$begin return $1 and not $2; end$$ strict;
+-- a simple function to make arrays opaque to the optimizer
+create function opaque_array(int[]) returns int[]
+language plpgsql as $$begin return $1; end$$ strict;
+-- Basic proof rules for single boolean variables
+select * from test_predtest($$
+select x, x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x, not x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select not x, x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select not x, not x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x is not null, x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x is not null, x is null
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x is null, x is not null
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x is not true, x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x, x is not true
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | t
+
+select * from test_predtest($$
+select x is false, x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x, x is false
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x is unknown, x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x, x is unknown
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | t
+s_r_holds | f
+w_r_holds | t
+
+-- Assorted not-so-trivial refutation rules
+select * from test_predtest($$
+select x is null, x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x, x is null
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | t
+s_r_holds | f
+w_r_holds | t
+
+select * from test_predtest($$
+select strictf(x,y), x is null
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | t
+s_r_holds | f
+w_r_holds | t
+
+select * from test_predtest($$
+select (x is not null) is not true, x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select strictf(x,y), (x is not null) is false
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | t
+s_r_holds | f
+w_r_holds | t
+
+select * from test_predtest($$
+select x > y, (y < x) is false
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+-- Tests involving AND/OR constructs
+select * from test_predtest($$
+select x, x and y
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select not x, x and y
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x, not x and y
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x or y, x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x and y, x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x and y, not x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x and y, y and x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select not y, y and x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x or y, y or x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x or y or z, x or z
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x and z, x and y and z
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select z or w, x or y
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select z and w, x or y
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x, (x and y) or (x and z)
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select (x and y) or z, y and x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select (not x or not y) and z, y and x
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select y or x, (x or y) and z
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select not x and not y, (x or y) and z
+from booleans
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+-- Tests using btree operator knowledge
+select * from test_predtest($$
+select x <= y, x < y
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x <= y, x > y
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x <= y, y >= x
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x <= y, y > x and y < x+2
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x <= 5, x <= 7
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x <= 5, x > 7
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x <= 5, 7 > x
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select 5 >= x, 7 > x
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select 5 >= x, x > 7
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select 5 = x, x = 7
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x is not null, x > 7
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x is not null, int4lt(x,8)
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x is null, x > 7
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x is null, int4lt(x,8)
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x is not null, x < 'foo'
+from (values
+ ('aaa'::varchar), ('zzz'::varchar), (null)) as v(x)
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+-- Cases using ScalarArrayOpExpr
+select * from test_predtest($$
+select x <= 5, x in (1,3,5)
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x <= 5, x in (1,3,5,7)
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x <= 5, x in (1,3,5,null)
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x in (null,1,3,5,7), x in (1,3,5)
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x <= 5, x < all(array[1,3,5])
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | t
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x <= y, x = any(array[1,3,y])
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+-- In these tests, we want to prevent predtest.c from breaking down the
+-- ScalarArrayOpExpr into an AND/OR tree, so as to exercise the logic
+-- that handles ScalarArrayOpExpr directly. We use opaque_array() if
+-- possible, otherwise an array longer than MAX_SAOP_ARRAY_SIZE.
+-- ScalarArrayOpExpr implies scalar IS NOT NULL
+select * from test_predtest($$
+select x is not null, x = any(opaque_array(array[1]))
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+-- but for ALL, we have to be able to prove the array nonempty
+select * from test_predtest($$
+select x is not null, x <> all(opaque_array(array[1]))
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x is not null, x <> all(array[
+ 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
+])
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+select * from test_predtest($$
+select x is not null, x <> all(array[
+ 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,y
+])
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+-- check empty-array cases
+select * from test_predtest($$
+select x is not null, x = any(opaque_array(array[]::int[]))
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | t
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | t
+w_i_holds | t
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x is not null, x <> all(opaque_array(array[]::int[]))
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+-- same thing under a strict function doesn't prove it
+select * from test_predtest($$
+select x is not null, strictf(true, x = any(opaque_array(array[]::int[])))
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+-- ScalarArrayOpExpr refutes scalar IS NULL
+select * from test_predtest($$
+select x is null, x = any(opaque_array(array[1]))
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+-- but for ALL, we have to be able to prove the array nonempty
+select * from test_predtest($$
+select x is null, x <> all(opaque_array(array[1]))
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x is null, x <> all(array[
+ 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
+])
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | f
+s_r_holds | t
+w_r_holds | t
+
+-- check empty-array cases
+select * from test_predtest($$
+select x is null, x = any(opaque_array(array[]::int[]))
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | t
+weak_refuted_by | t
+s_i_holds | t
+w_i_holds | t
+s_r_holds | t
+w_r_holds | t
+
+select * from test_predtest($$
+select x is null, x <> all(opaque_array(array[]::int[]))
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+-- same thing under a strict function doesn't prove it
+select * from test_predtest($$
+select x is null, strictf(true, x = any(opaque_array(array[]::int[])))
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | f
+s_r_holds | f
+w_r_holds | f
+
+-- Also, nullness of the scalar weakly refutes a SAOP
+select * from test_predtest($$
+select x = any(opaque_array(array[1])), x is null
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | t
+s_i_holds | f
+w_i_holds | t
+s_r_holds | f
+w_r_holds | t
+
+-- as does nullness of the array
+select * from test_predtest($$
+select x = any(opaque_array(array[y])), array[y] is null
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | t
+s_i_holds | t
+w_i_holds | t
+s_r_holds | t
+w_r_holds | t
+
+-- ... unless we need to prove array empty
+select * from test_predtest($$
+select x = all(opaque_array(array[1])), x is null
+from integers
+$$);
+-[ RECORD 1 ]-----+--
+strong_implied_by | f
+weak_implied_by | f
+strong_refuted_by | f
+weak_refuted_by | f
+s_i_holds | f
+w_i_holds | t
+s_r_holds | f
+w_r_holds | t
+
diff --git a/src/test/modules/test_predtest/sql/test_predtest.sql b/src/test/modules/test_predtest/sql/test_predtest.sql
new file mode 100644
index 0000000..072eb5b
--- /dev/null
+++ b/src/test/modules/test_predtest/sql/test_predtest.sql
@@ -0,0 +1,442 @@
+CREATE EXTENSION test_predtest;
+
+-- Make output more legible
+\pset expanded on
+
+-- Test data
+
+-- all combinations of four boolean values
+create table booleans as
+select
+ case i%3 when 0 then true when 1 then false else null end as x,
+ case (i/3)%3 when 0 then true when 1 then false else null end as y,
+ case (i/9)%3 when 0 then true when 1 then false else null end as z,
+ case (i/27)%3 when 0 then true when 1 then false else null end as w
+from generate_series(0, 3*3*3*3-1) i;
+
+-- all combinations of two integers 0..9, plus null
+create table integers as
+select
+ case i%11 when 10 then null else i%11 end as x,
+ case (i/11)%11 when 10 then null else (i/11)%11 end as y
+from generate_series(0, 11*11-1) i;
+
+-- and a simple strict function that's opaque to the optimizer
+create function strictf(bool, bool) returns bool
+language plpgsql as $$begin return $1 and not $2; end$$ strict;
+
+-- a simple function to make arrays opaque to the optimizer
+create function opaque_array(int[]) returns int[]
+language plpgsql as $$begin return $1; end$$ strict;
+
+-- Basic proof rules for single boolean variables
+
+select * from test_predtest($$
+select x, x
+from booleans
+$$);
+
+select * from test_predtest($$
+select x, not x
+from booleans
+$$);
+
+select * from test_predtest($$
+select not x, x
+from booleans
+$$);
+
+select * from test_predtest($$
+select not x, not x
+from booleans
+$$);
+
+select * from test_predtest($$
+select x is not null, x
+from booleans
+$$);
+
+select * from test_predtest($$
+select x is not null, x is null
+from integers
+$$);
+
+select * from test_predtest($$
+select x is null, x is not null
+from integers
+$$);
+
+select * from test_predtest($$
+select x is not true, x
+from booleans
+$$);
+
+select * from test_predtest($$
+select x, x is not true
+from booleans
+$$);
+
+select * from test_predtest($$
+select x is false, x
+from booleans
+$$);
+
+select * from test_predtest($$
+select x, x is false
+from booleans
+$$);
+
+select * from test_predtest($$
+select x is unknown, x
+from booleans
+$$);
+
+select * from test_predtest($$
+select x, x is unknown
+from booleans
+$$);
+
+-- Assorted not-so-trivial refutation rules
+
+select * from test_predtest($$
+select x is null, x
+from booleans
+$$);
+
+select * from test_predtest($$
+select x, x is null
+from booleans
+$$);
+
+select * from test_predtest($$
+select strictf(x,y), x is null
+from booleans
+$$);
+
+select * from test_predtest($$
+select (x is not null) is not true, x
+from booleans
+$$);
+
+select * from test_predtest($$
+select strictf(x,y), (x is not null) is false
+from booleans
+$$);
+
+select * from test_predtest($$
+select x > y, (y < x) is false
+from integers
+$$);
+
+-- Tests involving AND/OR constructs
+
+select * from test_predtest($$
+select x, x and y
+from booleans
+$$);
+
+select * from test_predtest($$
+select not x, x and y
+from booleans
+$$);
+
+select * from test_predtest($$
+select x, not x and y
+from booleans
+$$);
+
+select * from test_predtest($$
+select x or y, x
+from booleans
+$$);
+
+select * from test_predtest($$
+select x and y, x
+from booleans
+$$);
+
+select * from test_predtest($$
+select x and y, not x
+from booleans
+$$);
+
+select * from test_predtest($$
+select x and y, y and x
+from booleans
+$$);
+
+select * from test_predtest($$
+select not y, y and x
+from booleans
+$$);
+
+select * from test_predtest($$
+select x or y, y or x
+from booleans
+$$);
+
+select * from test_predtest($$
+select x or y or z, x or z
+from booleans
+$$);
+
+select * from test_predtest($$
+select x and z, x and y and z
+from booleans
+$$);
+
+select * from test_predtest($$
+select z or w, x or y
+from booleans
+$$);
+
+select * from test_predtest($$
+select z and w, x or y
+from booleans
+$$);
+
+select * from test_predtest($$
+select x, (x and y) or (x and z)
+from booleans
+$$);
+
+select * from test_predtest($$
+select (x and y) or z, y and x
+from booleans
+$$);
+
+select * from test_predtest($$
+select (not x or not y) and z, y and x
+from booleans
+$$);
+
+select * from test_predtest($$
+select y or x, (x or y) and z
+from booleans
+$$);
+
+select * from test_predtest($$
+select not x and not y, (x or y) and z
+from booleans
+$$);
+
+-- Tests using btree operator knowledge
+
+select * from test_predtest($$
+select x <= y, x < y
+from integers
+$$);
+
+select * from test_predtest($$
+select x <= y, x > y
+from integers
+$$);
+
+select * from test_predtest($$
+select x <= y, y >= x
+from integers
+$$);
+
+select * from test_predtest($$
+select x <= y, y > x and y < x+2
+from integers
+$$);
+
+select * from test_predtest($$
+select x <= 5, x <= 7
+from integers
+$$);
+
+select * from test_predtest($$
+select x <= 5, x > 7
+from integers
+$$);
+
+select * from test_predtest($$
+select x <= 5, 7 > x
+from integers
+$$);
+
+select * from test_predtest($$
+select 5 >= x, 7 > x
+from integers
+$$);
+
+select * from test_predtest($$
+select 5 >= x, x > 7
+from integers
+$$);
+
+select * from test_predtest($$
+select 5 = x, x = 7
+from integers
+$$);
+
+select * from test_predtest($$
+select x is not null, x > 7
+from integers
+$$);
+
+select * from test_predtest($$
+select x is not null, int4lt(x,8)
+from integers
+$$);
+
+select * from test_predtest($$
+select x is null, x > 7
+from integers
+$$);
+
+select * from test_predtest($$
+select x is null, int4lt(x,8)
+from integers
+$$);
+
+select * from test_predtest($$
+select x is not null, x < 'foo'
+from (values
+ ('aaa'::varchar), ('zzz'::varchar), (null)) as v(x)
+$$);
+
+-- Cases using ScalarArrayOpExpr
+
+select * from test_predtest($$
+select x <= 5, x in (1,3,5)
+from integers
+$$);
+
+select * from test_predtest($$
+select x <= 5, x in (1,3,5,7)
+from integers
+$$);
+
+select * from test_predtest($$
+select x <= 5, x in (1,3,5,null)
+from integers
+$$);
+
+select * from test_predtest($$
+select x in (null,1,3,5,7), x in (1,3,5)
+from integers
+$$);
+
+select * from test_predtest($$
+select x <= 5, x < all(array[1,3,5])
+from integers
+$$);
+
+select * from test_predtest($$
+select x <= y, x = any(array[1,3,y])
+from integers
+$$);
+
+-- In these tests, we want to prevent predtest.c from breaking down the
+-- ScalarArrayOpExpr into an AND/OR tree, so as to exercise the logic
+-- that handles ScalarArrayOpExpr directly. We use opaque_array() if
+-- possible, otherwise an array longer than MAX_SAOP_ARRAY_SIZE.
+
+-- ScalarArrayOpExpr implies scalar IS NOT NULL
+select * from test_predtest($$
+select x is not null, x = any(opaque_array(array[1]))
+from integers
+$$);
+
+-- but for ALL, we have to be able to prove the array nonempty
+select * from test_predtest($$
+select x is not null, x <> all(opaque_array(array[1]))
+from integers
+$$);
+
+select * from test_predtest($$
+select x is not null, x <> all(array[
+ 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
+])
+from integers
+$$);
+
+select * from test_predtest($$
+select x is not null, x <> all(array[
+ 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,y
+])
+from integers
+$$);
+
+-- check empty-array cases
+select * from test_predtest($$
+select x is not null, x = any(opaque_array(array[]::int[]))
+from integers
+$$);
+
+select * from test_predtest($$
+select x is not null, x <> all(opaque_array(array[]::int[]))
+from integers
+$$);
+
+-- same thing under a strict function doesn't prove it
+select * from test_predtest($$
+select x is not null, strictf(true, x = any(opaque_array(array[]::int[])))
+from integers
+$$);
+
+-- ScalarArrayOpExpr refutes scalar IS NULL
+select * from test_predtest($$
+select x is null, x = any(opaque_array(array[1]))
+from integers
+$$);
+
+-- but for ALL, we have to be able to prove the array nonempty
+select * from test_predtest($$
+select x is null, x <> all(opaque_array(array[1]))
+from integers
+$$);
+
+select * from test_predtest($$
+select x is null, x <> all(array[
+ 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
+])
+from integers
+$$);
+
+-- check empty-array cases
+select * from test_predtest($$
+select x is null, x = any(opaque_array(array[]::int[]))
+from integers
+$$);
+
+select * from test_predtest($$
+select x is null, x <> all(opaque_array(array[]::int[]))
+from integers
+$$);
+
+-- same thing under a strict function doesn't prove it
+select * from test_predtest($$
+select x is null, strictf(true, x = any(opaque_array(array[]::int[])))
+from integers
+$$);
+
+-- Also, nullness of the scalar weakly refutes a SAOP
+select * from test_predtest($$
+select x = any(opaque_array(array[1])), x is null
+from integers
+$$);
+
+-- as does nullness of the array
+select * from test_predtest($$
+select x = any(opaque_array(array[y])), array[y] is null
+from integers
+$$);
+
+-- ... unless we need to prove array empty
+select * from test_predtest($$
+select x = all(opaque_array(array[1])), x is null
+from integers
+$$);
diff --git a/src/test/modules/test_predtest/test_predtest--1.0.sql b/src/test/modules/test_predtest/test_predtest--1.0.sql
new file mode 100644
index 0000000..11e1444
--- /dev/null
+++ b/src/test/modules/test_predtest/test_predtest--1.0.sql
@@ -0,0 +1,16 @@
+/* src/test/modules/test_predtest/test_predtest--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_predtest" to load this file. \quit
+
+CREATE FUNCTION test_predtest(query text,
+ OUT strong_implied_by bool,
+ OUT weak_implied_by bool,
+ OUT strong_refuted_by bool,
+ OUT weak_refuted_by bool,
+ OUT s_i_holds bool,
+ OUT w_i_holds bool,
+ OUT s_r_holds bool,
+ OUT w_r_holds bool)
+STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_predtest/test_predtest.c b/src/test/modules/test_predtest/test_predtest.c
new file mode 100644
index 0000000..9c0aadd
--- /dev/null
+++ b/src/test/modules/test_predtest/test_predtest.c
@@ -0,0 +1,218 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_predtest.c
+ * Test correctness of optimizer's predicate proof logic.
+ *
+ * Copyright (c) 2018-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_predtest/test_predtest.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/pg_type.h"
+#include "executor/spi.h"
+#include "funcapi.h"
+#include "nodes/makefuncs.h"
+#include "optimizer/optimizer.h"
+#include "utils/builtins.h"
+
+PG_MODULE_MAGIC;
+
+/*
+ * test_predtest(query text) returns record
+ */
+PG_FUNCTION_INFO_V1(test_predtest);
+
+Datum
+test_predtest(PG_FUNCTION_ARGS)
+{
+ text *txt = PG_GETARG_TEXT_PP(0);
+ char *query_string = text_to_cstring(txt);
+ SPIPlanPtr spiplan;
+ int spirc;
+ TupleDesc tupdesc;
+ bool s_i_holds,
+ w_i_holds,
+ s_r_holds,
+ w_r_holds;
+ CachedPlan *cplan;
+ PlannedStmt *stmt;
+ Plan *plan;
+ Expr *clause1;
+ Expr *clause2;
+ bool strong_implied_by,
+ weak_implied_by,
+ strong_refuted_by,
+ weak_refuted_by;
+ Datum values[8];
+ bool nulls[8];
+ int i;
+
+ /* We use SPI to parse, plan, and execute the test query */
+ if (SPI_connect() != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed");
+
+ /*
+ * First, plan and execute the query, and inspect the results. To the
+ * extent that the query fully exercises the two expressions, this
+ * provides an experimental indication of whether implication or
+ * refutation holds.
+ */
+ spiplan = SPI_prepare(query_string, 0, NULL);
+ if (spiplan == NULL)
+ elog(ERROR, "SPI_prepare failed for \"%s\"", query_string);
+
+ spirc = SPI_execute_plan(spiplan, NULL, NULL, true, 0);
+ if (spirc != SPI_OK_SELECT)
+ elog(ERROR, "failed to execute \"%s\"", query_string);
+ tupdesc = SPI_tuptable->tupdesc;
+ if (tupdesc->natts != 2 ||
+ TupleDescAttr(tupdesc, 0)->atttypid != BOOLOID ||
+ TupleDescAttr(tupdesc, 1)->atttypid != BOOLOID)
+ elog(ERROR, "query must yield two boolean columns");
+
+ s_i_holds = w_i_holds = s_r_holds = w_r_holds = true;
+ for (i = 0; i < SPI_processed; i++)
+ {
+ HeapTuple tup = SPI_tuptable->vals[i];
+ Datum dat;
+ bool isnull;
+ char c1,
+ c2;
+
+ /* Extract column values in a 3-way representation */
+ dat = SPI_getbinval(tup, tupdesc, 1, &isnull);
+ if (isnull)
+ c1 = 'n';
+ else if (DatumGetBool(dat))
+ c1 = 't';
+ else
+ c1 = 'f';
+
+ dat = SPI_getbinval(tup, tupdesc, 2, &isnull);
+ if (isnull)
+ c2 = 'n';
+ else if (DatumGetBool(dat))
+ c2 = 't';
+ else
+ c2 = 'f';
+
+ /* Check for violations of various proof conditions */
+
+ /* strong implication: truth of c2 implies truth of c1 */
+ if (c2 == 't' && c1 != 't')
+ s_i_holds = false;
+ /* weak implication: non-falsity of c2 implies non-falsity of c1 */
+ if (c2 != 'f' && c1 == 'f')
+ w_i_holds = false;
+ /* strong refutation: truth of c2 implies falsity of c1 */
+ if (c2 == 't' && c1 != 'f')
+ s_r_holds = false;
+ /* weak refutation: truth of c2 implies non-truth of c1 */
+ if (c2 == 't' && c1 == 't')
+ w_r_holds = false;
+ }
+
+ /*
+ * Now, dig the clause querytrees out of the plan, and see what predtest.c
+ * does with them.
+ */
+ cplan = SPI_plan_get_cached_plan(spiplan);
+
+ if (list_length(cplan->stmt_list) != 1)
+ elog(ERROR, "failed to decipher query plan");
+ stmt = linitial_node(PlannedStmt, cplan->stmt_list);
+ if (stmt->commandType != CMD_SELECT)
+ elog(ERROR, "failed to decipher query plan");
+ plan = stmt->planTree;
+ Assert(list_length(plan->targetlist) >= 2);
+ clause1 = castNode(TargetEntry, linitial(plan->targetlist))->expr;
+ clause2 = castNode(TargetEntry, lsecond(plan->targetlist))->expr;
+
+ /*
+ * Because the clauses are in the SELECT list, preprocess_expression did
+ * not pass them through canonicalize_qual nor make_ands_implicit.
+ *
+ * We can't do canonicalize_qual here, since it's unclear whether the
+ * expressions ought to be treated as WHERE or CHECK clauses. Fortunately,
+ * useful test expressions wouldn't be affected by those transformations
+ * anyway. We should do make_ands_implicit, though.
+ *
+ * Another way in which this does not exactly duplicate the normal usage
+ * of the proof functions is that they are often given qual clauses
+ * containing RestrictInfo nodes. But since predtest.c just looks through
+ * those anyway, it seems OK to not worry about that point.
+ */
+ clause1 = (Expr *) make_ands_implicit(clause1);
+ clause2 = (Expr *) make_ands_implicit(clause2);
+
+ strong_implied_by = predicate_implied_by((List *) clause1,
+ (List *) clause2,
+ false);
+
+ weak_implied_by = predicate_implied_by((List *) clause1,
+ (List *) clause2,
+ true);
+
+ strong_refuted_by = predicate_refuted_by((List *) clause1,
+ (List *) clause2,
+ false);
+
+ weak_refuted_by = predicate_refuted_by((List *) clause1,
+ (List *) clause2,
+ true);
+
+ /*
+ * Issue warning if any proof is demonstrably incorrect.
+ */
+ if (strong_implied_by && !s_i_holds)
+ elog(WARNING, "strong_implied_by result is incorrect");
+ if (weak_implied_by && !w_i_holds)
+ elog(WARNING, "weak_implied_by result is incorrect");
+ if (strong_refuted_by && !s_r_holds)
+ elog(WARNING, "strong_refuted_by result is incorrect");
+ if (weak_refuted_by && !w_r_holds)
+ elog(WARNING, "weak_refuted_by result is incorrect");
+
+ /*
+ * Clean up and return a record of the results.
+ */
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed");
+
+ tupdesc = CreateTemplateTupleDesc(8);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1,
+ "strong_implied_by", BOOLOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 2,
+ "weak_implied_by", BOOLOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 3,
+ "strong_refuted_by", BOOLOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 4,
+ "weak_refuted_by", BOOLOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 5,
+ "s_i_holds", BOOLOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 6,
+ "w_i_holds", BOOLOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 7,
+ "s_r_holds", BOOLOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 8,
+ "w_r_holds", BOOLOID, -1, 0);
+ tupdesc = BlessTupleDesc(tupdesc);
+
+ MemSet(nulls, 0, sizeof(nulls));
+ values[0] = BoolGetDatum(strong_implied_by);
+ values[1] = BoolGetDatum(weak_implied_by);
+ values[2] = BoolGetDatum(strong_refuted_by);
+ values[3] = BoolGetDatum(weak_refuted_by);
+ values[4] = BoolGetDatum(s_i_holds);
+ values[5] = BoolGetDatum(w_i_holds);
+ values[6] = BoolGetDatum(s_r_holds);
+ values[7] = BoolGetDatum(w_r_holds);
+
+ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
diff --git a/src/test/modules/test_predtest/test_predtest.control b/src/test/modules/test_predtest/test_predtest.control
new file mode 100644
index 0000000..a899a9d
--- /dev/null
+++ b/src/test/modules/test_predtest/test_predtest.control
@@ -0,0 +1,4 @@
+comment = 'Test code for optimizer/util/predtest.c'
+default_version = '1.0'
+module_pathname = '$libdir/test_predtest'
+relocatable = true
diff --git a/src/test/modules/test_rbtree/.gitignore b/src/test/modules/test_rbtree/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_rbtree/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_rbtree/Makefile b/src/test/modules/test_rbtree/Makefile
new file mode 100644
index 0000000..faf376a
--- /dev/null
+++ b/src/test/modules/test_rbtree/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_rbtree/Makefile
+
+MODULE_big = test_rbtree
+OBJS = \
+ $(WIN32RES) \
+ test_rbtree.o
+PGFILEDESC = "test_rbtree - test code for red-black tree library"
+
+EXTENSION = test_rbtree
+DATA = test_rbtree--1.0.sql
+
+REGRESS = test_rbtree
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_rbtree
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_rbtree/README b/src/test/modules/test_rbtree/README
new file mode 100644
index 0000000..d69eb8d
--- /dev/null
+++ b/src/test/modules/test_rbtree/README
@@ -0,0 +1,13 @@
+test_rbtree is a test module for checking the correctness of red-black
+tree operations.
+
+These tests are performed on red-black trees that store integers.
+Since the rbtree logic treats the comparison function as a black
+box, it shouldn't be important exactly what the key type is.
+
+Checking the correctness of traversals is based on the fact that a red-black
+tree is a binary search tree, so the elements should be visited in increasing
+(for Left-Current-Right) or decreasing (for Right-Current-Left) order.
+
+Also, this module does some checks of the correctness of the find, delete
+and leftmost operations.
diff --git a/src/test/modules/test_rbtree/expected/test_rbtree.out b/src/test/modules/test_rbtree/expected/test_rbtree.out
new file mode 100644
index 0000000..3e32956
--- /dev/null
+++ b/src/test/modules/test_rbtree/expected/test_rbtree.out
@@ -0,0 +1,12 @@
+CREATE EXTENSION test_rbtree;
+--
+-- These tests don't produce any interesting output. We're checking that
+-- the operations complete without crashing or hanging and that none of their
+-- internal sanity tests fail.
+--
+SELECT test_rb_tree(10000);
+ test_rb_tree
+--------------
+
+(1 row)
+
diff --git a/src/test/modules/test_rbtree/sql/test_rbtree.sql b/src/test/modules/test_rbtree/sql/test_rbtree.sql
new file mode 100644
index 0000000..d8dc88e
--- /dev/null
+++ b/src/test/modules/test_rbtree/sql/test_rbtree.sql
@@ -0,0 +1,8 @@
+CREATE EXTENSION test_rbtree;
+
+--
+-- These tests don't produce any interesting output. We're checking that
+-- the operations complete without crashing or hanging and that none of their
+-- internal sanity tests fail.
+--
+SELECT test_rb_tree(10000);
diff --git a/src/test/modules/test_rbtree/test_rbtree--1.0.sql b/src/test/modules/test_rbtree/test_rbtree--1.0.sql
new file mode 100644
index 0000000..04f2a3a
--- /dev/null
+++ b/src/test/modules/test_rbtree/test_rbtree--1.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/test_rbtree/test_rbtree--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_rbtree" to load this file. \quit
+
+CREATE FUNCTION test_rb_tree(size INTEGER)
+ RETURNS pg_catalog.void STRICT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_rbtree/test_rbtree.c b/src/test/modules/test_rbtree/test_rbtree.c
new file mode 100644
index 0000000..713ebd1
--- /dev/null
+++ b/src/test/modules/test_rbtree/test_rbtree.c
@@ -0,0 +1,413 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_rbtree.c
+ * Test correctness of red-black tree operations.
+ *
+ * Copyright (c) 2009-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_rbtree/test_rbtree.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "lib/rbtree.h"
+#include "utils/memutils.h"
+
+PG_MODULE_MAGIC;
+
+
+/*
+ * Our test trees store an integer key, and nothing else.
+ */
+typedef struct IntRBTreeNode
+{
+ RBTNode rbtnode;
+ int key;
+} IntRBTreeNode;
+
+
+/*
+ * Node comparator. We don't worry about overflow in the subtraction,
+ * since none of our test keys are negative.
+ */
+static int
+irbt_cmp(const RBTNode *a, const RBTNode *b, void *arg)
+{
+ const IntRBTreeNode *ea = (const IntRBTreeNode *) a;
+ const IntRBTreeNode *eb = (const IntRBTreeNode *) b;
+
+ return ea->key - eb->key;
+}
+
+/*
+ * Node combiner. For testing purposes, just check that library doesn't
+ * try to combine unequal keys.
+ */
+static void
+irbt_combine(RBTNode *existing, const RBTNode *newdata, void *arg)
+{
+ const IntRBTreeNode *eexist = (const IntRBTreeNode *) existing;
+ const IntRBTreeNode *enew = (const IntRBTreeNode *) newdata;
+
+ if (eexist->key != enew->key)
+ elog(ERROR, "red-black tree combines %d into %d",
+ enew->key, eexist->key);
+}
+
+/* Node allocator */
+static RBTNode *
+irbt_alloc(void *arg)
+{
+ return (RBTNode *) palloc(sizeof(IntRBTreeNode));
+}
+
+/* Node freer */
+static void
+irbt_free(RBTNode *node, void *arg)
+{
+ pfree(node);
+}
+
+/*
+ * Create a red-black tree using our support functions
+ */
+static RBTree *
+create_int_rbtree(void)
+{
+ return rbt_create(sizeof(IntRBTreeNode),
+ irbt_cmp,
+ irbt_combine,
+ irbt_alloc,
+ irbt_free,
+ NULL);
+}
+
+/*
+ * Generate a random permutation of the integers 0..size-1
+ */
+static int *
+GetPermutation(int size)
+{
+ int *permutation;
+ int i;
+
+ permutation = (int *) palloc(size * sizeof(int));
+
+ permutation[0] = 0;
+
+ /*
+ * This is the "inside-out" variant of the Fisher-Yates shuffle algorithm.
+ * Notionally, we append each new value to the array and then swap it with
+ * a randomly-chosen array element (possibly including itself, else we
+ * fail to generate permutations with the last integer last). The swap
+ * step can be optimized by combining it with the insertion.
+ */
+ for (i = 1; i < size; i++)
+ {
+ int j = random() % (i + 1);
+
+ if (j < i) /* avoid fetching undefined data if j=i */
+ permutation[i] = permutation[j];
+ permutation[j] = i;
+ }
+
+ return permutation;
+}
+
+/*
+ * Populate an empty RBTree with "size" integers having the values
+ * 0, step, 2*step, 3*step, ..., inserting them in random order
+ */
+static void
+rbt_populate(RBTree *tree, int size, int step)
+{
+ int *permutation = GetPermutation(size);
+ IntRBTreeNode node;
+ bool isNew;
+ int i;
+
+ /* Insert values. We don't expect any collisions. */
+ for (i = 0; i < size; i++)
+ {
+ node.key = step * permutation[i];
+ rbt_insert(tree, (RBTNode *) &node, &isNew);
+ if (!isNew)
+ elog(ERROR, "unexpected !isNew result from rbt_insert");
+ }
+
+ /*
+ * Re-insert the first value to make sure collisions work right. It's
+ * probably not useful to test that case over again for all the values.
+ */
+ if (size > 0)
+ {
+ node.key = step * permutation[0];
+ rbt_insert(tree, (RBTNode *) &node, &isNew);
+ if (isNew)
+ elog(ERROR, "unexpected isNew result from rbt_insert");
+ }
+
+ pfree(permutation);
+}
+
+/*
+ * Check the correctness of left-right traversal.
+ * Left-right traversal is correct if all elements are
+ * visited in increasing order.
+ */
+static void
+testleftright(int size)
+{
+ RBTree *tree = create_int_rbtree();
+ IntRBTreeNode *node;
+ RBTreeIterator iter;
+ int lastKey = -1;
+ int count = 0;
+
+ /* check iteration over empty tree */
+ rbt_begin_iterate(tree, LeftRightWalk, &iter);
+ if (rbt_iterate(&iter) != NULL)
+ elog(ERROR, "left-right walk over empty tree produced an element");
+
+ /* fill tree with consecutive natural numbers */
+ rbt_populate(tree, size, 1);
+
+ /* iterate over the tree */
+ rbt_begin_iterate(tree, LeftRightWalk, &iter);
+
+ while ((node = (IntRBTreeNode *) rbt_iterate(&iter)) != NULL)
+ {
+ /* check that order is increasing */
+ if (node->key <= lastKey)
+ elog(ERROR, "left-right walk gives elements not in sorted order");
+ lastKey = node->key;
+ count++;
+ }
+
+ if (lastKey != size - 1)
+ elog(ERROR, "left-right walk did not reach end");
+ if (count != size)
+ elog(ERROR, "left-right walk missed some elements");
+}
+
+/*
+ * Check the correctness of right-left traversal.
+ * Right-left traversal is correct if all elements are
+ * visited in decreasing order.
+ */
+static void
+testrightleft(int size)
+{
+ RBTree *tree = create_int_rbtree();
+ IntRBTreeNode *node;
+ RBTreeIterator iter;
+ int lastKey = size;
+ int count = 0;
+
+ /* check iteration over empty tree */
+ rbt_begin_iterate(tree, RightLeftWalk, &iter);
+ if (rbt_iterate(&iter) != NULL)
+ elog(ERROR, "right-left walk over empty tree produced an element");
+
+ /* fill tree with consecutive natural numbers */
+ rbt_populate(tree, size, 1);
+
+ /* iterate over the tree */
+ rbt_begin_iterate(tree, RightLeftWalk, &iter);
+
+ while ((node = (IntRBTreeNode *) rbt_iterate(&iter)) != NULL)
+ {
+ /* check that order is decreasing */
+ if (node->key >= lastKey)
+ elog(ERROR, "right-left walk gives elements not in sorted order");
+ lastKey = node->key;
+ count++;
+ }
+
+ if (lastKey != 0)
+ elog(ERROR, "right-left walk did not reach end");
+ if (count != size)
+ elog(ERROR, "right-left walk missed some elements");
+}
+
+/*
+ * Check the correctness of the rbt_find operation by searching for
+ * both elements we inserted and elements we didn't.
+ */
+static void
+testfind(int size)
+{
+ RBTree *tree = create_int_rbtree();
+ int i;
+
+ /* Insert even integers from 0 to 2 * (size-1) */
+ rbt_populate(tree, size, 2);
+
+ /* Check that all inserted elements can be found */
+ for (i = 0; i < size; i++)
+ {
+ IntRBTreeNode node;
+ IntRBTreeNode *resultNode;
+
+ node.key = 2 * i;
+ resultNode = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &node);
+ if (resultNode == NULL)
+ elog(ERROR, "inserted element was not found");
+ if (node.key != resultNode->key)
+ elog(ERROR, "find operation in rbtree gave wrong result");
+ }
+
+ /*
+ * Check that not-inserted elements can not be found, being sure to try
+ * values before the first and after the last element.
+ */
+ for (i = -1; i <= 2 * size; i += 2)
+ {
+ IntRBTreeNode node;
+ IntRBTreeNode *resultNode;
+
+ node.key = i;
+ resultNode = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &node);
+ if (resultNode != NULL)
+ elog(ERROR, "not-inserted element was found");
+ }
+}
+
+/*
+ * Check the correctness of the rbt_leftmost operation.
+ * This operation should always return the smallest element of the tree.
+ */
+static void
+testleftmost(int size)
+{
+ RBTree *tree = create_int_rbtree();
+ IntRBTreeNode *result;
+
+ /* Check that empty tree has no leftmost element */
+ if (rbt_leftmost(tree) != NULL)
+ elog(ERROR, "leftmost node of empty tree is not NULL");
+
+ /* fill tree with consecutive natural numbers */
+ rbt_populate(tree, size, 1);
+
+ /* Check that leftmost element is the smallest one */
+ result = (IntRBTreeNode *) rbt_leftmost(tree);
+ if (result == NULL || result->key != 0)
+ elog(ERROR, "rbt_leftmost gave wrong result");
+}
+
+/*
+ * Check the correctness of the rbt_delete operation.
+ */
+static void
+testdelete(int size, int delsize)
+{
+ RBTree *tree = create_int_rbtree();
+ int *deleteIds;
+ bool *chosen;
+ int i;
+
+ /* fill tree with consecutive natural numbers */
+ rbt_populate(tree, size, 1);
+
+ /* Choose unique ids to delete */
+ deleteIds = (int *) palloc(delsize * sizeof(int));
+ chosen = (bool *) palloc0(size * sizeof(bool));
+
+ for (i = 0; i < delsize; i++)
+ {
+ int k = random() % size;
+
+ while (chosen[k])
+ k = (k + 1) % size;
+ deleteIds[i] = k;
+ chosen[k] = true;
+ }
+
+ /* Delete elements */
+ for (i = 0; i < delsize; i++)
+ {
+ IntRBTreeNode find;
+ IntRBTreeNode *node;
+
+ find.key = deleteIds[i];
+ /* Locate the node to be deleted */
+ node = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &find);
+ if (node == NULL || node->key != deleteIds[i])
+ elog(ERROR, "expected element was not found during deleting");
+ /* Delete it */
+ rbt_delete(tree, (RBTNode *) node);
+ }
+
+ /* Check that deleted elements are deleted */
+ for (i = 0; i < size; i++)
+ {
+ IntRBTreeNode node;
+ IntRBTreeNode *result;
+
+ node.key = i;
+ result = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &node);
+ if (chosen[i])
+ {
+ /* Deleted element should be absent */
+ if (result != NULL)
+ elog(ERROR, "deleted element still present in the rbtree");
+ }
+ else
+ {
+ /* Else it should be present */
+ if (result == NULL || result->key != i)
+ elog(ERROR, "delete operation removed wrong rbtree value");
+ }
+ }
+
+ /* Delete remaining elements, so as to exercise reducing tree to empty */
+ for (i = 0; i < size; i++)
+ {
+ IntRBTreeNode find;
+ IntRBTreeNode *node;
+
+ if (chosen[i])
+ continue;
+ find.key = i;
+ /* Locate the node to be deleted */
+ node = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &find);
+ if (node == NULL || node->key != i)
+ elog(ERROR, "expected element was not found during deleting");
+ /* Delete it */
+ rbt_delete(tree, (RBTNode *) node);
+ }
+
+ /* Tree should now be empty */
+ if (rbt_leftmost(tree) != NULL)
+ elog(ERROR, "deleting all elements failed");
+
+ pfree(deleteIds);
+ pfree(chosen);
+}
+
+/*
+ * SQL-callable entry point to perform all tests
+ *
+ * Argument is the number of entries to put in the trees
+ */
+PG_FUNCTION_INFO_V1(test_rb_tree);
+
+Datum
+test_rb_tree(PG_FUNCTION_ARGS)
+{
+ int size = PG_GETARG_INT32(0);
+
+ if (size <= 0 || size > MaxAllocSize / sizeof(int))
+ elog(ERROR, "invalid size for test_rb_tree: %d", size);
+ testleftright(size);
+ testrightleft(size);
+ testfind(size);
+ testleftmost(size);
+ testdelete(size, Max(size / 10, 1));
+ PG_RETURN_VOID();
+}
diff --git a/src/test/modules/test_rbtree/test_rbtree.control b/src/test/modules/test_rbtree/test_rbtree.control
new file mode 100644
index 0000000..17966a5
--- /dev/null
+++ b/src/test/modules/test_rbtree/test_rbtree.control
@@ -0,0 +1,4 @@
+comment = 'Test code for red-black tree library'
+default_version = '1.0'
+module_pathname = '$libdir/test_rbtree'
+relocatable = true
diff --git a/src/test/modules/test_regex/.gitignore b/src/test/modules/test_regex/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_regex/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_regex/Makefile b/src/test/modules/test_regex/Makefile
new file mode 100644
index 0000000..dfbc5dc
--- /dev/null
+++ b/src/test/modules/test_regex/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_regex/Makefile
+
+MODULE_big = test_regex
+OBJS = \
+ $(WIN32RES) \
+ test_regex.o
+PGFILEDESC = "test_regex - test code for backend/regex/"
+
+EXTENSION = test_regex
+DATA = test_regex--1.0.sql
+
+REGRESS = test_regex test_regex_utf8
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_regex
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_regex/README b/src/test/modules/test_regex/README
new file mode 100644
index 0000000..3ef152d
--- /dev/null
+++ b/src/test/modules/test_regex/README
@@ -0,0 +1,78 @@
+test_regex is a module for testing the regular expression package.
+It is mostly meant to allow us to absorb Tcl's regex test suite.
+Therefore, there are provisions to exercise regex features that
+aren't currently exposed at the SQL level by PostgreSQL.
+
+Currently, one function is provided:
+
+test_regex(pattern text, string text, flags text) returns setof text[]
+
+Reports an error if the pattern is an invalid regex. Otherwise,
+the first row of output contains the number of subexpressions,
+followed by words reporting set bit(s) in the regex's re_info field.
+If the pattern doesn't match the string, that's all.
+If the pattern does match, the next row contains the whole match
+as the first array element. If there are parenthesized subexpression(s),
+following array elements contain the matches to those subexpressions.
+If the "g" (glob) flag is set, then additional row(s) of output similarly
+report any additional matches.
+
+The "flags" argument is a string of zero or more single-character
+flags that modify the behavior of the regex package or the test
+function. As described in Tcl's reg.test file:
+
+The flag characters are complex and a bit eclectic. Generally speaking,
+lowercase letters are compile options, uppercase are expected re_info
+bits, and nonalphabetics are match options, controls for how the test is
+run, or testing options. The one small surprise is that AREs are the
+default, and you must explicitly request lesser flavors of RE. The flags
+are as follows. It is admitted that some are not very mnemonic.
+
+ - no-op (placeholder)
+ 0 report indices not actual strings
+ (This substitutes for Tcl's -indices switch)
+ ! expect partial match, report start position anyway
+ % force small state-set cache in matcher (to test cache replace)
+ ^ beginning of string is not beginning of line
+ $ end of string is not end of line
+ * test is Unicode-specific, needs big character set
+ + provide fake xy equivalence class and ch collating element
+ (Note: the equivalence class is implemented, the
+ collating element is not; so references to [.ch.] fail)
+ , set REG_PROGRESS (only useful in REG_DEBUG builds)
+ . set REG_DUMP (only useful in REG_DEBUG builds)
+ : set REG_MTRACE (only useful in REG_DEBUG builds)
+ ; set REG_FTRACE (only useful in REG_DEBUG builds)
+
+ & test as both ARE and BRE
+ (Not implemented in Postgres, we use separate tests)
+ b BRE
+ e ERE
+ a turn advanced-features bit on (error unless ERE already)
+ q literal string, no metacharacters at all
+
+ g global match (find all matches)
+ i case-independent matching
+ o ("opaque") do not return match locations
+ p newlines are half-magic, excluded from . and [^ only
+ w newlines are half-magic, significant to ^ and $ only
+ n newlines are fully magic, both effects
+ x expanded RE syntax
+ t incomplete-match reporting
+ c canmatch (equivalent to "t0!", in Postgres implementation)
+ s match only at start (REG_BOSONLY)
+
+ A backslash-_a_lphanumeric seen
+ B ERE/ARE literal-_b_race heuristic used
+ E backslash (_e_scape) seen within []
+ H looka_h_ead constraint seen
+ I _i_mpossible to match
+ L _l_ocale-specific construct seen
+ M unportable (_m_achine-specific) construct seen
+ N RE can match empty (_n_ull) string
+ P non-_P_OSIX construct seen
+ Q {} _q_uantifier seen
+ R back _r_eference seen
+ S POSIX-un_s_pecified syntax seen
+ T prefers shortest (_t_iny)
+ U saw original-POSIX botch: unmatched right paren in ERE (_u_gh)
diff --git a/src/test/modules/test_regex/expected/test_regex.out b/src/test/modules/test_regex/expected/test_regex.out
new file mode 100644
index 0000000..06f8881
--- /dev/null
+++ b/src/test/modules/test_regex/expected/test_regex.out
@@ -0,0 +1,4934 @@
+-- This file is based on tests/reg.test from the Tcl distribution,
+-- which is marked
+-- # Copyright (c) 1998, 1999 Henry Spencer. All rights reserved.
+-- The full copyright notice can be found in src/backend/regex/COPYRIGHT.
+-- Most commented lines below are copied from reg.test. Each
+-- test case is followed by an equivalent test using test_regex().
+create extension test_regex;
+set standard_conforming_strings = on;
+-- # support functions and preliminary misc.
+-- # This is sensitive to changes in message wording, but we really have to
+-- # test the code->message expansion at least once.
+-- ::tcltest::test reg-0.1 "regexp error reporting" {
+-- list [catch {regexp (*) ign} msg] $msg
+-- } {1 {couldn't compile regular expression pattern: quantifier operand invalid}}
+select * from test_regex('(*)', '', '');
+ERROR: invalid regular expression: quantifier operand invalid
+-- doing 1 "basic sanity checks"
+-- expectMatch 1.1 & abc abc abc
+select * from test_regex('abc', 'abc', '');
+ test_regex
+------------
+ {0}
+ {abc}
+(2 rows)
+
+select * from test_regex('abc', 'abc', 'b');
+ test_regex
+------------
+ {0}
+ {abc}
+(2 rows)
+
+-- expectNomatch 1.2 & abc def
+select * from test_regex('abc', 'def', '');
+ test_regex
+------------
+ {0}
+(1 row)
+
+select * from test_regex('abc', 'def', 'b');
+ test_regex
+------------
+ {0}
+(1 row)
+
+-- expectMatch 1.3 & abc xyabxabce abc
+select * from test_regex('abc', 'xyabxabce', '');
+ test_regex
+------------
+ {0}
+ {abc}
+(2 rows)
+
+select * from test_regex('abc', 'xyabxabce', 'b');
+ test_regex
+------------
+ {0}
+ {abc}
+(2 rows)
+
+-- doing 2 "invalid option combinations"
+-- expectError 2.1 qe a INVARG
+select * from test_regex('a', '', 'qe');
+ERROR: invalid regular expression: invalid argument to regex function
+-- expectError 2.2 qa a INVARG
+select * from test_regex('a', '', 'qa');
+ERROR: invalid regular expression: invalid argument to regex function
+-- expectError 2.3 qx a INVARG
+select * from test_regex('a', '', 'qx');
+ERROR: invalid regular expression: invalid argument to regex function
+-- expectError 2.4 qn a INVARG
+select * from test_regex('a', '', 'qn');
+ERROR: invalid regular expression: invalid argument to regex function
+-- expectError 2.5 ba a INVARG
+select * from test_regex('a', '', 'ba');
+ERROR: invalid regular expression: invalid argument to regex function
+-- doing 3 "basic syntax"
+-- expectIndices 3.1 &NS "" a {0 -1}
+select * from test_regex('', 'a', '0NS');
+ test_regex
+---------------------------------
+ {0,REG_UUNSPEC,REG_UEMPTYMATCH}
+ {"0 -1"}
+(2 rows)
+
+select * from test_regex('', 'a', '0NSb');
+ test_regex
+---------------------------------
+ {0,REG_UUNSPEC,REG_UEMPTYMATCH}
+ {"0 -1"}
+(2 rows)
+
+-- expectMatch 3.2 NS a| a a
+select * from test_regex('a|', 'a', 'NS');
+ test_regex
+---------------------------------
+ {0,REG_UUNSPEC,REG_UEMPTYMATCH}
+ {a}
+(2 rows)
+
+-- expectMatch 3.3 - a|b a a
+select * from test_regex('a|b', 'a', '-');
+ test_regex
+------------
+ {0}
+ {a}
+(2 rows)
+
+-- expectMatch 3.4 - a|b b b
+select * from test_regex('a|b', 'b', '-');
+ test_regex
+------------
+ {0}
+ {b}
+(2 rows)
+
+-- expectMatch 3.5 NS a||b b b
+select * from test_regex('a||b', 'b', 'NS');
+ test_regex
+---------------------------------
+ {0,REG_UUNSPEC,REG_UEMPTYMATCH}
+ {b}
+(2 rows)
+
+-- expectMatch 3.6 & ab ab ab
+select * from test_regex('ab', 'ab', '');
+ test_regex
+------------
+ {0}
+ {ab}
+(2 rows)
+
+select * from test_regex('ab', 'ab', 'b');
+ test_regex
+------------
+ {0}
+ {ab}
+(2 rows)
+
+-- doing 4 "parentheses"
+-- expectMatch 4.1 - (a)e ae ae a
+select * from test_regex('(a)e', 'ae', '-');
+ test_regex
+------------
+ {1}
+ {ae,a}
+(2 rows)
+
+-- expectMatch 4.2 o (a)e ae
+select * from test_regex('(a)e', 'ae', 'o');
+ test_regex
+------------
+ {0}
+ {NULL}
+(2 rows)
+
+-- expectMatch 4.3 b {\(a\)b} ab ab a
+select * from test_regex('\(a\)b', 'ab', 'b');
+ test_regex
+------------
+ {1}
+ {ab,a}
+(2 rows)
+
+-- expectMatch 4.4 - a((b)c) abc abc bc b
+select * from test_regex('a((b)c)', 'abc', '-');
+ test_regex
+------------
+ {2}
+ {abc,bc,b}
+(2 rows)
+
+-- expectMatch 4.5 - a(b)(c) abc abc b c
+select * from test_regex('a(b)(c)', 'abc', '-');
+ test_regex
+------------
+ {2}
+ {abc,b,c}
+(2 rows)
+
+-- expectError 4.6 - a(b EPAREN
+select * from test_regex('a(b', '', '-');
+ERROR: invalid regular expression: parentheses () not balanced
+-- expectError 4.7 b {a\(b} EPAREN
+select * from test_regex('a\(b', '', 'b');
+ERROR: invalid regular expression: parentheses () not balanced
+-- # sigh, we blew it on the specs here... someday this will be fixed in POSIX,
+-- # but meanwhile, it's fixed in AREs
+-- expectMatch 4.8 eU a)b a)b a)b
+select * from test_regex('a)b', 'a)b', 'eU');
+ test_regex
+-----------------
+ {0,REG_UPBOTCH}
+ {a)b}
+(2 rows)
+
+-- expectError 4.9 - a)b EPAREN
+select * from test_regex('a)b', '', '-');
+ERROR: invalid regular expression: parentheses () not balanced
+-- expectError 4.10 b {a\)b} EPAREN
+select * from test_regex('a\)b', '', 'b');
+ERROR: invalid regular expression: parentheses () not balanced
+-- expectMatch 4.11 P a(?:b)c abc abc
+select * from test_regex('a(?:b)c', 'abc', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {abc}
+(2 rows)
+
+-- expectError 4.12 e a(?:b)c BADRPT
+select * from test_regex('a(?:b)c', '', 'e');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectIndices 4.13 S a()b ab {0 1} {1 0}
+select * from test_regex('a()b', 'ab', '0S');
+ test_regex
+-----------------
+ {1,REG_UUNSPEC}
+ {"0 1","1 0"}
+(2 rows)
+
+-- expectMatch 4.14 SP a(?:)b ab ab
+select * from test_regex('a(?:)b', 'ab', 'SP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNSPEC}
+ {ab}
+(2 rows)
+
+-- expectIndices 4.15 S a(|b)c ac {0 1} {1 0}
+select * from test_regex('a(|b)c', 'ac', '0S');
+ test_regex
+-----------------
+ {1,REG_UUNSPEC}
+ {"0 1","1 0"}
+(2 rows)
+
+-- expectMatch 4.16 S a(b|)c abc abc b
+select * from test_regex('a(b|)c', 'abc', 'S');
+ test_regex
+-----------------
+ {1,REG_UUNSPEC}
+ {abc,b}
+(2 rows)
+
+-- doing 5 "simple one-char matching"
+-- # general case of brackets done later
+-- expectMatch 5.1 & a.b axb axb
+select * from test_regex('a.b', 'axb', '');
+ test_regex
+------------
+ {0}
+ {axb}
+(2 rows)
+
+select * from test_regex('a.b', 'axb', 'b');
+ test_regex
+------------
+ {0}
+ {axb}
+(2 rows)
+
+-- expectNomatch 5.2 &n "a.b" "a\nb"
+select * from test_regex('a.b', E'a\nb', 'n');
+ test_regex
+------------
+ {0}
+(1 row)
+
+select * from test_regex('a.b', E'a\nb', 'nb');
+ test_regex
+------------
+ {0}
+(1 row)
+
+-- expectMatch 5.3 & {a[bc]d} abd abd
+select * from test_regex('a[bc]d', 'abd', '');
+ test_regex
+------------
+ {0}
+ {abd}
+(2 rows)
+
+select * from test_regex('a[bc]d', 'abd', 'b');
+ test_regex
+------------
+ {0}
+ {abd}
+(2 rows)
+
+-- expectMatch 5.4 & {a[bc]d} acd acd
+select * from test_regex('a[bc]d', 'acd', '');
+ test_regex
+------------
+ {0}
+ {acd}
+(2 rows)
+
+select * from test_regex('a[bc]d', 'acd', 'b');
+ test_regex
+------------
+ {0}
+ {acd}
+(2 rows)
+
+-- expectNomatch 5.5 & {a[bc]d} aed
+select * from test_regex('a[bc]d', 'aed', '');
+ test_regex
+------------
+ {0}
+(1 row)
+
+select * from test_regex('a[bc]d', 'aed', 'b');
+ test_regex
+------------
+ {0}
+(1 row)
+
+-- expectNomatch 5.6 & {a[^bc]d} abd
+select * from test_regex('a[^bc]d', 'abd', '');
+ test_regex
+------------
+ {0}
+(1 row)
+
+select * from test_regex('a[^bc]d', 'abd', 'b');
+ test_regex
+------------
+ {0}
+(1 row)
+
+-- expectMatch 5.7 & {a[^bc]d} aed aed
+select * from test_regex('a[^bc]d', 'aed', '');
+ test_regex
+------------
+ {0}
+ {aed}
+(2 rows)
+
+select * from test_regex('a[^bc]d', 'aed', 'b');
+ test_regex
+------------
+ {0}
+ {aed}
+(2 rows)
+
+-- expectNomatch 5.8 &p "a\[^bc]d" "a\nd"
+select * from test_regex('a[^bc]d', E'a\nd', 'p');
+ test_regex
+------------
+ {0}
+(1 row)
+
+select * from test_regex('a[^bc]d', E'a\nd', 'pb');
+ test_regex
+------------
+ {0}
+(1 row)
+
+-- doing 6 "context-dependent syntax"
+-- # plus odds and ends
+-- expectError 6.1 - * BADRPT
+select * from test_regex('*', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectMatch 6.2 b * * *
+select * from test_regex('*', '*', 'b');
+ test_regex
+------------
+ {0}
+ {*}
+(2 rows)
+
+-- expectMatch 6.3 b {\(*\)} * * *
+select * from test_regex('\(*\)', '*', 'b');
+ test_regex
+------------
+ {1}
+ {*,*}
+(2 rows)
+
+-- expectError 6.4 - (*) BADRPT
+select * from test_regex('(*)', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectMatch 6.5 b ^* * *
+select * from test_regex('^*', '*', 'b');
+ test_regex
+------------
+ {0}
+ {*}
+(2 rows)
+
+-- expectError 6.6 - ^* BADRPT
+select * from test_regex('^*', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectNomatch 6.7 & ^b ^b
+select * from test_regex('^b', '^b', '');
+ test_regex
+------------
+ {0}
+(1 row)
+
+select * from test_regex('^b', '^b', 'b');
+ test_regex
+------------
+ {0}
+(1 row)
+
+-- expectMatch 6.8 b x^ x^ x^
+select * from test_regex('x^', 'x^', 'b');
+ test_regex
+------------
+ {0}
+ {x^}
+(2 rows)
+
+-- expectNomatch 6.9 I x^ x
+select * from test_regex('x^', 'x', 'I');
+ test_regex
+---------------------
+ {0,REG_UIMPOSSIBLE}
+(1 row)
+
+-- expectMatch 6.10 n "\n^" "x\nb" "\n"
+select * from test_regex(E'\n^', E'x\nb', 'n');
+ test_regex
+------------
+ {0}
+ {" +
+ "}
+(2 rows)
+
+-- expectNomatch 6.11 bS {\(^b\)} ^b
+select * from test_regex('\(^b\)', '^b', 'bS');
+ test_regex
+-----------------
+ {1,REG_UUNSPEC}
+(1 row)
+
+-- expectMatch 6.12 - (^b) b b b
+select * from test_regex('(^b)', 'b', '-');
+ test_regex
+------------
+ {1}
+ {b,b}
+(2 rows)
+
+-- expectMatch 6.13 & {x$} x x
+select * from test_regex('x$', 'x', '');
+ test_regex
+------------
+ {0}
+ {x}
+(2 rows)
+
+select * from test_regex('x$', 'x', 'b');
+ test_regex
+------------
+ {0}
+ {x}
+(2 rows)
+
+-- expectMatch 6.14 bS {\(x$\)} x x x
+select * from test_regex('\(x$\)', 'x', 'bS');
+ test_regex
+-----------------
+ {1,REG_UUNSPEC}
+ {x,x}
+(2 rows)
+
+-- expectMatch 6.15 - {(x$)} x x x
+select * from test_regex('(x$)', 'x', '-');
+ test_regex
+------------
+ {1}
+ {x,x}
+(2 rows)
+
+-- expectMatch 6.16 b {x$y} "x\$y" "x\$y"
+select * from test_regex('x$y', 'x$y', 'b');
+ test_regex
+------------
+ {0}
+ {x$y}
+(2 rows)
+
+-- expectNomatch 6.17 I {x$y} xy
+select * from test_regex('x$y', 'xy', 'I');
+ test_regex
+---------------------
+ {0,REG_UIMPOSSIBLE}
+(1 row)
+
+-- expectMatch 6.18 n "x\$\n" "x\n" "x\n"
+select * from test_regex(E'x$\n', E'x\n', 'n');
+ test_regex
+------------
+ {0}
+ {"x +
+ "}
+(2 rows)
+
+-- expectError 6.19 - + BADRPT
+select * from test_regex('+', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 6.20 - ? BADRPT
+select * from test_regex('?', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- These two are not yet incorporated in Tcl, cf
+-- https://core.tcl-lang.org/tcl/tktview?name=5ea71fdcd3291c38
+-- expectError 6.21 - {x(\w)(?=(\1))} ESUBREG
+select * from test_regex('x(\w)(?=(\1))', '', '-');
+ERROR: invalid regular expression: invalid backreference number
+-- expectMatch 6.22 HP {x(?=((foo)))} xfoo x
+select * from test_regex('x(?=((foo)))', 'xfoo', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {x}
+(2 rows)
+
+-- doing 7 "simple quantifiers"
+-- expectMatch 7.1 &N a* aa aa
+select * from test_regex('a*', 'aa', 'N');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {aa}
+(2 rows)
+
+select * from test_regex('a*', 'aa', 'Nb');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {aa}
+(2 rows)
+
+-- expectIndices 7.2 &N a* b {0 -1}
+select * from test_regex('a*', 'b', '0N');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {"0 -1"}
+(2 rows)
+
+select * from test_regex('a*', 'b', '0Nb');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {"0 -1"}
+(2 rows)
+
+-- expectMatch 7.3 - a+ aa aa
+select * from test_regex('a+', 'aa', '-');
+ test_regex
+------------
+ {0}
+ {aa}
+(2 rows)
+
+-- expectMatch 7.4 - a?b ab ab
+select * from test_regex('a?b', 'ab', '-');
+ test_regex
+------------
+ {0}
+ {ab}
+(2 rows)
+
+-- expectMatch 7.5 - a?b b b
+select * from test_regex('a?b', 'b', '-');
+ test_regex
+------------
+ {0}
+ {b}
+(2 rows)
+
+-- expectError 7.6 - ** BADRPT
+select * from test_regex('**', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectMatch 7.7 bN ** *** ***
+select * from test_regex('**', '***', 'bN');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {***}
+(2 rows)
+
+-- expectError 7.8 & a** BADRPT
+select * from test_regex('a**', '', '');
+ERROR: invalid regular expression: quantifier operand invalid
+select * from test_regex('a**', '', 'b');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 7.9 & a**b BADRPT
+select * from test_regex('a**b', '', '');
+ERROR: invalid regular expression: quantifier operand invalid
+select * from test_regex('a**b', '', 'b');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 7.10 & *** BADRPT
+select * from test_regex('***', '', '');
+ERROR: invalid regular expression: quantifier operand invalid
+select * from test_regex('***', '', 'b');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 7.11 - a++ BADRPT
+select * from test_regex('a++', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 7.12 - a?+ BADRPT
+select * from test_regex('a?+', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 7.13 - a?* BADRPT
+select * from test_regex('a?*', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 7.14 - a+* BADRPT
+select * from test_regex('a+*', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 7.15 - a*+ BADRPT
+select * from test_regex('a*+', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- tests for ancient brenext() bugs; not currently in Tcl
+select * from test_regex('.*b', 'aaabbb', 'b');
+ test_regex
+------------
+ {0}
+ {aaabbb}
+(2 rows)
+
+select * from test_regex('.\{1,10\}', 'abcdef', 'bQ');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {abcdef}
+(2 rows)
+
+-- doing 8 "braces"
+-- expectMatch 8.1 NQ "a{0,1}" "" ""
+select * from test_regex('a{0,1}', '', 'NQ');
+ test_regex
+---------------------------------
+ {0,REG_UBOUNDS,REG_UEMPTYMATCH}
+ {""}
+(2 rows)
+
+-- expectMatch 8.2 NQ "a{0,1}" ac a
+select * from test_regex('a{0,1}', 'ac', 'NQ');
+ test_regex
+---------------------------------
+ {0,REG_UBOUNDS,REG_UEMPTYMATCH}
+ {a}
+(2 rows)
+
+-- expectError 8.3 - "a{1,0}" BADBR
+select * from test_regex('a{1,0}', '', '-');
+ERROR: invalid regular expression: invalid repetition count(s)
+-- expectError 8.4 - "a{1,2,3}" BADBR
+select * from test_regex('a{1,2,3}', '', '-');
+ERROR: invalid regular expression: invalid repetition count(s)
+-- expectError 8.5 - "a{257}" BADBR
+select * from test_regex('a{257}', '', '-');
+ERROR: invalid regular expression: invalid repetition count(s)
+-- expectError 8.6 - "a{1000}" BADBR
+select * from test_regex('a{1000}', '', '-');
+ERROR: invalid regular expression: invalid repetition count(s)
+-- expectError 8.7 - "a{1" EBRACE
+select * from test_regex('a{1', '', '-');
+ERROR: invalid regular expression: braces {} not balanced
+-- expectError 8.8 - "a{1n}" BADBR
+select * from test_regex('a{1n}', '', '-');
+ERROR: invalid regular expression: invalid repetition count(s)
+-- expectMatch 8.9 BS "a{b" "a\{b" "a\{b"
+select * from test_regex('a{b', 'a{b', 'BS');
+ test_regex
+-----------------------------
+ {0,REG_UBRACES,REG_UUNSPEC}
+ {"a{b"}
+(2 rows)
+
+-- expectMatch 8.10 BS "a{" "a\{" "a\{"
+select * from test_regex('a{', 'a{', 'BS');
+ test_regex
+-----------------------------
+ {0,REG_UBRACES,REG_UUNSPEC}
+ {"a{"}
+(2 rows)
+
+-- expectMatch 8.11 bQ "a\\{0,1\\}b" cb b
+select * from test_regex('a\{0,1\}b', 'cb', 'bQ');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {b}
+(2 rows)
+
+-- expectError 8.12 b "a\\{0,1" EBRACE
+select * from test_regex('a\{0,1', '', 'b');
+ERROR: invalid regular expression: braces {} not balanced
+-- expectError 8.13 - "a{0,1\\" BADBR
+select * from test_regex('a{0,1\', '', '-');
+ERROR: invalid regular expression: invalid repetition count(s)
+-- expectMatch 8.14 Q "a{0}b" ab b
+select * from test_regex('a{0}b', 'ab', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {b}
+(2 rows)
+
+-- expectMatch 8.15 Q "a{0,0}b" ab b
+select * from test_regex('a{0,0}b', 'ab', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {b}
+(2 rows)
+
+-- expectMatch 8.16 Q "a{0,1}b" ab ab
+select * from test_regex('a{0,1}b', 'ab', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {ab}
+(2 rows)
+
+-- expectMatch 8.17 Q "a{0,2}b" b b
+select * from test_regex('a{0,2}b', 'b', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {b}
+(2 rows)
+
+-- expectMatch 8.18 Q "a{0,2}b" aab aab
+select * from test_regex('a{0,2}b', 'aab', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {aab}
+(2 rows)
+
+-- expectMatch 8.19 Q "a{0,}b" aab aab
+select * from test_regex('a{0,}b', 'aab', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {aab}
+(2 rows)
+
+-- expectMatch 8.20 Q "a{1,1}b" aab ab
+select * from test_regex('a{1,1}b', 'aab', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {ab}
+(2 rows)
+
+-- expectMatch 8.21 Q "a{1,3}b" aaaab aaab
+select * from test_regex('a{1,3}b', 'aaaab', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {aaab}
+(2 rows)
+
+-- expectNomatch 8.22 Q "a{1,3}b" b
+select * from test_regex('a{1,3}b', 'b', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+(1 row)
+
+-- expectMatch 8.23 Q "a{1,}b" aab aab
+select * from test_regex('a{1,}b', 'aab', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {aab}
+(2 rows)
+
+-- expectNomatch 8.24 Q "a{2,3}b" ab
+select * from test_regex('a{2,3}b', 'ab', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+(1 row)
+
+-- expectMatch 8.25 Q "a{2,3}b" aaaab aaab
+select * from test_regex('a{2,3}b', 'aaaab', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {aaab}
+(2 rows)
+
+-- expectNomatch 8.26 Q "a{2,}b" ab
+select * from test_regex('a{2,}b', 'ab', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+(1 row)
+
+-- expectMatch 8.27 Q "a{2,}b" aaaab aaaab
+select * from test_regex('a{2,}b', 'aaaab', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {aaaab}
+(2 rows)
+
+-- doing 9 "brackets"
+-- expectMatch 9.1 & {a[bc]} ac ac
+select * from test_regex('a[bc]', 'ac', '');
+ test_regex
+------------
+ {0}
+ {ac}
+(2 rows)
+
+select * from test_regex('a[bc]', 'ac', 'b');
+ test_regex
+------------
+ {0}
+ {ac}
+(2 rows)
+
+-- expectMatch 9.2 & {a[-]} a- a-
+select * from test_regex('a[-]', 'a-', '');
+ test_regex
+------------
+ {0}
+ {a-}
+(2 rows)
+
+select * from test_regex('a[-]', 'a-', 'b');
+ test_regex
+------------
+ {0}
+ {a-}
+(2 rows)
+
+-- expectMatch 9.3 & {a[[.-.]]} a- a-
+select * from test_regex('a[[.-.]]', 'a-', '');
+ test_regex
+------------
+ {0}
+ {a-}
+(2 rows)
+
+select * from test_regex('a[[.-.]]', 'a-', 'b');
+ test_regex
+------------
+ {0}
+ {a-}
+(2 rows)
+
+-- expectMatch 9.4 &L {a[[.zero.]]} a0 a0
+select * from test_regex('a[[.zero.]]', 'a0', 'L');
+ test_regex
+-----------------
+ {0,REG_ULOCALE}
+ {a0}
+(2 rows)
+
+select * from test_regex('a[[.zero.]]', 'a0', 'Lb');
+ test_regex
+-----------------
+ {0,REG_ULOCALE}
+ {a0}
+(2 rows)
+
+-- expectMatch 9.5 &LM {a[[.zero.]-9]} a2 a2
+select * from test_regex('a[[.zero.]-9]', 'a2', 'LM');
+ test_regex
+-----------------------------
+ {0,REG_UUNPORT,REG_ULOCALE}
+ {a2}
+(2 rows)
+
+select * from test_regex('a[[.zero.]-9]', 'a2', 'LMb');
+ test_regex
+-----------------------------
+ {0,REG_UUNPORT,REG_ULOCALE}
+ {a2}
+(2 rows)
+
+-- expectMatch 9.6 &M {a[0-[.9.]]} a2 a2
+select * from test_regex('a[0-[.9.]]', 'a2', 'M');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+ {a2}
+(2 rows)
+
+select * from test_regex('a[0-[.9.]]', 'a2', 'Mb');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+ {a2}
+(2 rows)
+
+-- expectMatch 9.7 &+L {a[[=x=]]} ax ax
+select * from test_regex('a[[=x=]]', 'ax', '+L');
+ test_regex
+-----------------
+ {0,REG_ULOCALE}
+ {ax}
+(2 rows)
+
+select * from test_regex('a[[=x=]]', 'ax', '+Lb');
+ test_regex
+-----------------
+ {0,REG_ULOCALE}
+ {ax}
+(2 rows)
+
+-- expectMatch 9.8 &+L {a[[=x=]]} ay ay
+select * from test_regex('a[[=x=]]', 'ay', '+L');
+ test_regex
+-----------------
+ {0,REG_ULOCALE}
+ {ay}
+(2 rows)
+
+select * from test_regex('a[[=x=]]', 'ay', '+Lb');
+ test_regex
+-----------------
+ {0,REG_ULOCALE}
+ {ay}
+(2 rows)
+
+-- expectNomatch 9.9 &+L {a[[=x=]]} az
+select * from test_regex('a[[=x=]]', 'az', '+L');
+ test_regex
+-----------------
+ {0,REG_ULOCALE}
+(1 row)
+
+select * from test_regex('a[[=x=]]', 'az', '+Lb');
+ test_regex
+-----------------
+ {0,REG_ULOCALE}
+(1 row)
+
+-- expectError 9.10 & {a[0-[=x=]]} ERANGE
+select * from test_regex('a[0-[=x=]]', '', '');
+ERROR: invalid regular expression: invalid character range
+select * from test_regex('a[0-[=x=]]', '', 'b');
+ERROR: invalid regular expression: invalid character range
+-- expectMatch 9.11 &L {a[[:digit:]]} a0 a0
+select * from test_regex('a[[:digit:]]', 'a0', 'L');
+ test_regex
+-----------------
+ {0,REG_ULOCALE}
+ {a0}
+(2 rows)
+
+select * from test_regex('a[[:digit:]]', 'a0', 'Lb');
+ test_regex
+-----------------
+ {0,REG_ULOCALE}
+ {a0}
+(2 rows)
+
+-- expectError 9.12 & {a[[:woopsie:]]} ECTYPE
+select * from test_regex('a[[:woopsie:]]', '', '');
+ERROR: invalid regular expression: invalid character class
+select * from test_regex('a[[:woopsie:]]', '', 'b');
+ERROR: invalid regular expression: invalid character class
+-- expectNomatch 9.13 &L {a[[:digit:]]} ab
+select * from test_regex('a[[:digit:]]', 'ab', 'L');
+ test_regex
+-----------------
+ {0,REG_ULOCALE}
+(1 row)
+
+select * from test_regex('a[[:digit:]]', 'ab', 'Lb');
+ test_regex
+-----------------
+ {0,REG_ULOCALE}
+(1 row)
+
+-- expectError 9.14 & {a[0-[:digit:]]} ERANGE
+select * from test_regex('a[0-[:digit:]]', '', '');
+ERROR: invalid regular expression: invalid character range
+select * from test_regex('a[0-[:digit:]]', '', 'b');
+ERROR: invalid regular expression: invalid character range
+-- expectMatch 9.15 &LP {[[:<:]]a} a a
+select * from test_regex('[[:<:]]a', 'a', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+select * from test_regex('[[:<:]]a', 'a', 'LPb');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectMatch 9.16 &LP {a[[:>:]]} a a
+select * from test_regex('a[[:>:]]', 'a', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+select * from test_regex('a[[:>:]]', 'a', 'LPb');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectError 9.17 & {a[[..]]b} ECOLLATE
+select * from test_regex('a[[..]]b', '', '');
+ERROR: invalid regular expression: invalid collating element
+select * from test_regex('a[[..]]b', '', 'b');
+ERROR: invalid regular expression: invalid collating element
+-- expectError 9.18 & {a[[==]]b} ECOLLATE
+select * from test_regex('a[[==]]b', '', '');
+ERROR: invalid regular expression: invalid collating element
+select * from test_regex('a[[==]]b', '', 'b');
+ERROR: invalid regular expression: invalid collating element
+-- expectError 9.19 & {a[[::]]b} ECTYPE
+select * from test_regex('a[[::]]b', '', '');
+ERROR: invalid regular expression: invalid character class
+select * from test_regex('a[[::]]b', '', 'b');
+ERROR: invalid regular expression: invalid character class
+-- expectError 9.20 & {a[[.a} EBRACK
+select * from test_regex('a[[.a', '', '');
+ERROR: invalid regular expression: brackets [] not balanced
+select * from test_regex('a[[.a', '', 'b');
+ERROR: invalid regular expression: brackets [] not balanced
+-- expectError 9.21 & {a[[=a} EBRACK
+select * from test_regex('a[[=a', '', '');
+ERROR: invalid regular expression: brackets [] not balanced
+select * from test_regex('a[[=a', '', 'b');
+ERROR: invalid regular expression: brackets [] not balanced
+-- expectError 9.22 & {a[[:a} EBRACK
+select * from test_regex('a[[:a', '', '');
+ERROR: invalid regular expression: brackets [] not balanced
+select * from test_regex('a[[:a', '', 'b');
+ERROR: invalid regular expression: brackets [] not balanced
+-- expectError 9.23 & {a[} EBRACK
+select * from test_regex('a[', '', '');
+ERROR: invalid regular expression: brackets [] not balanced
+select * from test_regex('a[', '', 'b');
+ERROR: invalid regular expression: brackets [] not balanced
+-- expectError 9.24 & {a[b} EBRACK
+select * from test_regex('a[b', '', '');
+ERROR: invalid regular expression: brackets [] not balanced
+select * from test_regex('a[b', '', 'b');
+ERROR: invalid regular expression: brackets [] not balanced
+-- expectError 9.25 & {a[b-} EBRACK
+select * from test_regex('a[b-', '', '');
+ERROR: invalid regular expression: brackets [] not balanced
+select * from test_regex('a[b-', '', 'b');
+ERROR: invalid regular expression: brackets [] not balanced
+-- expectError 9.26 & {a[b-c} EBRACK
+select * from test_regex('a[b-c', '', '');
+ERROR: invalid regular expression: brackets [] not balanced
+select * from test_regex('a[b-c', '', 'b');
+ERROR: invalid regular expression: brackets [] not balanced
+-- expectMatch 9.27 &M {a[b-c]} ab ab
+select * from test_regex('a[b-c]', 'ab', 'M');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+ {ab}
+(2 rows)
+
+select * from test_regex('a[b-c]', 'ab', 'Mb');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+ {ab}
+(2 rows)
+
+-- expectMatch 9.28 & {a[b-b]} ab ab
+select * from test_regex('a[b-b]', 'ab', '');
+ test_regex
+------------
+ {0}
+ {ab}
+(2 rows)
+
+select * from test_regex('a[b-b]', 'ab', 'b');
+ test_regex
+------------
+ {0}
+ {ab}
+(2 rows)
+
+-- expectMatch 9.29 &M {a[1-2]} a2 a2
+select * from test_regex('a[1-2]', 'a2', 'M');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+ {a2}
+(2 rows)
+
+select * from test_regex('a[1-2]', 'a2', 'Mb');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+ {a2}
+(2 rows)
+
+-- expectError 9.30 & {a[c-b]} ERANGE
+select * from test_regex('a[c-b]', '', '');
+ERROR: invalid regular expression: invalid character range
+select * from test_regex('a[c-b]', '', 'b');
+ERROR: invalid regular expression: invalid character range
+-- expectError 9.31 & {a[a-b-c]} ERANGE
+select * from test_regex('a[a-b-c]', '', '');
+ERROR: invalid regular expression: invalid character range
+select * from test_regex('a[a-b-c]', '', 'b');
+ERROR: invalid regular expression: invalid character range
+-- expectMatch 9.32 &M {a[--?]b} a?b a?b
+select * from test_regex('a[--?]b', 'a?b', 'M');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+ {a?b}
+(2 rows)
+
+select * from test_regex('a[--?]b', 'a?b', 'Mb');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+ {a?b}
+(2 rows)
+
+-- expectMatch 9.33 & {a[---]b} a-b a-b
+select * from test_regex('a[---]b', 'a-b', '');
+ test_regex
+------------
+ {0}
+ {a-b}
+(2 rows)
+
+select * from test_regex('a[---]b', 'a-b', 'b');
+ test_regex
+------------
+ {0}
+ {a-b}
+(2 rows)
+
+-- expectMatch 9.34 & {a[]b]c} a]c a]c
+select * from test_regex('a[]b]c', 'a]c', '');
+ test_regex
+------------
+ {0}
+ {a]c}
+(2 rows)
+
+select * from test_regex('a[]b]c', 'a]c', 'b');
+ test_regex
+------------
+ {0}
+ {a]c}
+(2 rows)
+
+-- expectMatch 9.35 EP {a[\]]b} a]b a]b
+select * from test_regex('a[\]]b', 'a]b', 'EP');
+ test_regex
+----------------------------
+ {0,REG_UBBS,REG_UNONPOSIX}
+ {a]b}
+(2 rows)
+
+-- expectNomatch 9.36 bE {a[\]]b} a]b
+select * from test_regex('a[\]]b', 'a]b', 'bE');
+ test_regex
+--------------
+ {0,REG_UBBS}
+(1 row)
+
+-- expectMatch 9.37 bE {a[\]]b} "a\\]b" "a\\]b"
+select * from test_regex('a[\]]b', 'a\]b', 'bE');
+ test_regex
+--------------
+ {0,REG_UBBS}
+ {"a\\]b"}
+(2 rows)
+
+-- expectMatch 9.38 eE {a[\]]b} "a\\]b" "a\\]b"
+select * from test_regex('a[\]]b', 'a\]b', 'eE');
+ test_regex
+--------------
+ {0,REG_UBBS}
+ {"a\\]b"}
+(2 rows)
+
+-- expectMatch 9.39 EP {a[\\]b} "a\\b" "a\\b"
+select * from test_regex('a[\\]b', 'a\b', 'EP');
+ test_regex
+----------------------------
+ {0,REG_UBBS,REG_UNONPOSIX}
+ {"a\\b"}
+(2 rows)
+
+-- expectMatch 9.40 eE {a[\\]b} "a\\b" "a\\b"
+select * from test_regex('a[\\]b', 'a\b', 'eE');
+ test_regex
+--------------
+ {0,REG_UBBS}
+ {"a\\b"}
+(2 rows)
+
+-- expectMatch 9.41 bE {a[\\]b} "a\\b" "a\\b"
+select * from test_regex('a[\\]b', 'a\b', 'bE');
+ test_regex
+--------------
+ {0,REG_UBBS}
+ {"a\\b"}
+(2 rows)
+
+-- expectError 9.42 - {a[\Z]b} EESCAPE
+select * from test_regex('a[\Z]b', '', '-');
+ERROR: invalid regular expression: invalid escape \ sequence
+-- expectMatch 9.43 & {a[[b]c} "a\[c" "a\[c"
+select * from test_regex('a[[b]c', 'a[c', '');
+ test_regex
+------------
+ {0}
+ {a[c}
+(2 rows)
+
+select * from test_regex('a[[b]c', 'a[c', 'b');
+ test_regex
+------------
+ {0}
+ {a[c}
+(2 rows)
+
+-- This only works in UTF8 encoding, so it's moved to test_regex_utf8.sql:
+-- expectMatch 9.44 EMP* {a[\u00fe-\u0507][\u00ff-\u0300]b} \
+-- "a\u0102\u02ffb" "a\u0102\u02ffb"
+-- doing 10 "anchors and newlines"
+-- expectMatch 10.1 & ^a a a
+select * from test_regex('^a', 'a', '');
+ test_regex
+------------
+ {0}
+ {a}
+(2 rows)
+
+select * from test_regex('^a', 'a', 'b');
+ test_regex
+------------
+ {0}
+ {a}
+(2 rows)
+
+-- expectNomatch 10.2 &^ ^a a
+select * from test_regex('^a', 'a', '^');
+ test_regex
+------------
+ {0}
+(1 row)
+
+select * from test_regex('^a', 'a', '^b');
+ test_regex
+------------
+ {0}
+(1 row)
+
+-- expectIndices 10.3 &N ^ a {0 -1}
+select * from test_regex('^', 'a', '0N');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {"0 -1"}
+(2 rows)
+
+select * from test_regex('^', 'a', '0Nb');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {"0 -1"}
+(2 rows)
+
+-- expectIndices 10.4 & {a$} aba {2 2}
+select * from test_regex('a$', 'aba', '0');
+ test_regex
+------------
+ {0}
+ {"2 2"}
+(2 rows)
+
+select * from test_regex('a$', 'aba', '0b');
+ test_regex
+------------
+ {0}
+ {"2 2"}
+(2 rows)
+
+-- expectNomatch 10.5 {&$} {a$} a
+select * from test_regex('a$', 'a', '$');
+ test_regex
+------------
+ {0}
+(1 row)
+
+select * from test_regex('a$', 'a', '$b');
+ test_regex
+------------
+ {0}
+(1 row)
+
+-- expectIndices 10.6 &N {$} ab {2 1}
+select * from test_regex('$', 'ab', '0N');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {"2 1"}
+(2 rows)
+
+select * from test_regex('$', 'ab', '0Nb');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {"2 1"}
+(2 rows)
+
+-- expectMatch 10.7 &n ^a a a
+select * from test_regex('^a', 'a', 'n');
+ test_regex
+------------
+ {0}
+ {a}
+(2 rows)
+
+select * from test_regex('^a', 'a', 'nb');
+ test_regex
+------------
+ {0}
+ {a}
+(2 rows)
+
+-- expectMatch 10.8 &n "^a" "b\na" "a"
+select * from test_regex('^a', E'b\na', 'n');
+ test_regex
+------------
+ {0}
+ {a}
+(2 rows)
+
+select * from test_regex('^a', E'b\na', 'nb');
+ test_regex
+------------
+ {0}
+ {a}
+(2 rows)
+
+-- expectIndices 10.9 &w "^a" "a\na" {0 0}
+select * from test_regex('^a', E'a\na', '0w');
+ test_regex
+------------
+ {0}
+ {"0 0"}
+(2 rows)
+
+select * from test_regex('^a', E'a\na', '0wb');
+ test_regex
+------------
+ {0}
+ {"0 0"}
+(2 rows)
+
+-- expectIndices 10.10 &n^ "^a" "a\na" {2 2}
+select * from test_regex('^a', E'a\na', '0n^');
+ test_regex
+------------
+ {0}
+ {"2 2"}
+(2 rows)
+
+select * from test_regex('^a', E'a\na', '0n^b');
+ test_regex
+------------
+ {0}
+ {"2 2"}
+(2 rows)
+
+-- expectMatch 10.11 &n {a$} a a
+select * from test_regex('a$', 'a', 'n');
+ test_regex
+------------
+ {0}
+ {a}
+(2 rows)
+
+select * from test_regex('a$', 'a', 'nb');
+ test_regex
+------------
+ {0}
+ {a}
+(2 rows)
+
+-- expectMatch 10.12 &n "a\$" "a\nb" "a"
+select * from test_regex('a$', E'a\nb', 'n');
+ test_regex
+------------
+ {0}
+ {a}
+(2 rows)
+
+select * from test_regex('a$', E'a\nb', 'nb');
+ test_regex
+------------
+ {0}
+ {a}
+(2 rows)
+
+-- expectIndices 10.13 &n "a\$" "a\na" {0 0}
+select * from test_regex('a$', E'a\na', '0n');
+ test_regex
+------------
+ {0}
+ {"0 0"}
+(2 rows)
+
+select * from test_regex('a$', E'a\na', '0nb');
+ test_regex
+------------
+ {0}
+ {"0 0"}
+(2 rows)
+
+-- expectIndices 10.14 N ^^ a {0 -1}
+select * from test_regex('^^', 'a', '0N');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {"0 -1"}
+(2 rows)
+
+-- expectMatch 10.15 b ^^ ^ ^
+select * from test_regex('^^', '^', 'b');
+ test_regex
+------------
+ {0}
+ {^}
+(2 rows)
+
+-- expectIndices 10.16 N {$$} a {1 0}
+select * from test_regex('$$', 'a', '0N');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {"1 0"}
+(2 rows)
+
+-- expectMatch 10.17 b {$$} "\$" "\$"
+select * from test_regex('$$', '$', 'b');
+ test_regex
+------------
+ {0}
+ {$}
+(2 rows)
+
+-- expectMatch 10.18 &N {^$} "" ""
+select * from test_regex('^$', '', 'N');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {""}
+(2 rows)
+
+select * from test_regex('^$', '', 'Nb');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {""}
+(2 rows)
+
+-- expectNomatch 10.19 &N {^$} a
+select * from test_regex('^$', 'a', 'N');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+(1 row)
+
+select * from test_regex('^$', 'a', 'Nb');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+(1 row)
+
+-- expectIndices 10.20 &nN "^\$" a\n\nb {2 1}
+select * from test_regex('^$', E'a\n\nb', '0nN');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {"2 1"}
+(2 rows)
+
+select * from test_regex('^$', E'a\n\nb', '0nNb');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {"2 1"}
+(2 rows)
+
+-- expectMatch 10.21 N {$^} "" ""
+select * from test_regex('$^', '', 'N');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {""}
+(2 rows)
+
+-- expectMatch 10.22 b {$^} "\$^" "\$^"
+select * from test_regex('$^', '$^', 'b');
+ test_regex
+------------
+ {0}
+ {$^}
+(2 rows)
+
+-- expectMatch 10.23 P {\Aa} a a
+select * from test_regex('\Aa', 'a', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a}
+(2 rows)
+
+-- expectMatch 10.24 ^P {\Aa} a a
+select * from test_regex('\Aa', 'a', '^P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a}
+(2 rows)
+
+-- expectNomatch 10.25 ^nP {\Aa} "b\na"
+select * from test_regex('\Aa', E'b\na', '^nP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 10.26 P {a\Z} a a
+select * from test_regex('a\Z', 'a', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a}
+(2 rows)
+
+-- expectMatch 10.27 \$P {a\Z} a a
+select * from test_regex('a\Z', 'a', '$P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a}
+(2 rows)
+
+-- expectNomatch 10.28 \$nP {a\Z} "a\nb"
+select * from test_regex('a\Z', E'a\nb', '$nP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+(1 row)
+
+-- expectError 10.29 - ^* BADRPT
+select * from test_regex('^*', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 10.30 - {$*} BADRPT
+select * from test_regex('$*', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 10.31 - {\A*} BADRPT
+select * from test_regex('\A*', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 10.32 - {\Z*} BADRPT
+select * from test_regex('\Z*', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- doing 11 "boundary constraints"
+-- expectMatch 11.1 &LP {[[:<:]]a} a a
+select * from test_regex('[[:<:]]a', 'a', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+select * from test_regex('[[:<:]]a', 'a', 'LPb');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectMatch 11.2 &LP {[[:<:]]a} -a a
+select * from test_regex('[[:<:]]a', '-a', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+select * from test_regex('[[:<:]]a', '-a', 'LPb');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectNomatch 11.3 &LP {[[:<:]]a} ba
+select * from test_regex('[[:<:]]a', 'ba', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+select * from test_regex('[[:<:]]a', 'ba', 'LPb');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 11.4 &LP {a[[:>:]]} a a
+select * from test_regex('a[[:>:]]', 'a', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+select * from test_regex('a[[:>:]]', 'a', 'LPb');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectMatch 11.5 &LP {a[[:>:]]} a- a
+select * from test_regex('a[[:>:]]', 'a-', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+select * from test_regex('a[[:>:]]', 'a-', 'LPb');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectNomatch 11.6 &LP {a[[:>:]]} ab
+select * from test_regex('a[[:>:]]', 'ab', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+select * from test_regex('a[[:>:]]', 'ab', 'LPb');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 11.7 bLP {\<a} a a
+select * from test_regex('\<a', 'a', 'bLP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectNomatch 11.8 bLP {\<a} ba
+select * from test_regex('\<a', 'ba', 'bLP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 11.9 bLP {a\>} a a
+select * from test_regex('a\>', 'a', 'bLP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectNomatch 11.10 bLP {a\>} ab
+select * from test_regex('a\>', 'ab', 'bLP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 11.11 LP {\ya} a a
+select * from test_regex('\ya', 'a', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectNomatch 11.12 LP {\ya} ba
+select * from test_regex('\ya', 'ba', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 11.13 LP {a\y} a a
+select * from test_regex('a\y', 'a', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectNomatch 11.14 LP {a\y} ab
+select * from test_regex('a\y', 'ab', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 11.15 LP {a\Y} ab a
+select * from test_regex('a\Y', 'ab', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectNomatch 11.16 LP {a\Y} a-
+select * from test_regex('a\Y', 'a-', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectNomatch 11.17 LP {a\Y} a
+select * from test_regex('a\Y', 'a', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectNomatch 11.18 LP {-\Y} -a
+select * from test_regex('-\Y', '-a', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 11.19 LP {-\Y} -% -
+select * from test_regex('-\Y', '-%', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {-}
+(2 rows)
+
+-- expectNomatch 11.20 LP {\Y-} a-
+select * from test_regex('\Y-', 'a-', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectError 11.21 - {[[:<:]]*} BADRPT
+select * from test_regex('[[:<:]]*', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 11.22 - {[[:>:]]*} BADRPT
+select * from test_regex('[[:>:]]*', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 11.23 b {\<*} BADRPT
+select * from test_regex('\<*', '', 'b');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 11.24 b {\>*} BADRPT
+select * from test_regex('\>*', '', 'b');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 11.25 - {\y*} BADRPT
+select * from test_regex('\y*', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectError 11.26 - {\Y*} BADRPT
+select * from test_regex('\Y*', '', '-');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectMatch 11.27 LP {\ma} a a
+select * from test_regex('\ma', 'a', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectNomatch 11.28 LP {\ma} ba
+select * from test_regex('\ma', 'ba', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 11.29 LP {a\M} a a
+select * from test_regex('a\M', 'a', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+-- expectNomatch 11.30 LP {a\M} ab
+select * from test_regex('a\M', 'ab', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectNomatch 11.31 ILP {\Ma} a
+select * from test_regex('\Ma', 'a', 'ILP');
+ test_regex
+-----------------------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE,REG_UIMPOSSIBLE}
+(1 row)
+
+-- expectNomatch 11.32 ILP {a\m} a
+select * from test_regex('a\m', 'a', 'ILP');
+ test_regex
+-----------------------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE,REG_UIMPOSSIBLE}
+(1 row)
+
+-- doing 12 "character classes"
+-- expectMatch 12.1 LP {a\db} a0b a0b
+select * from test_regex('a\db', 'a0b', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a0b}
+(2 rows)
+
+-- expectNomatch 12.2 LP {a\db} axb
+select * from test_regex('a\db', 'axb', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectNomatch 12.3 LP {a\Db} a0b
+select * from test_regex('a\Db', 'a0b', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 12.4 LP {a\Db} axb axb
+select * from test_regex('a\Db', 'axb', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {axb}
+(2 rows)
+
+-- expectMatch 12.5 LP "a\\sb" "a b" "a b"
+select * from test_regex('a\sb', 'a b', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {"a b"}
+(2 rows)
+
+-- expectMatch 12.6 LP "a\\sb" "a\tb" "a\tb"
+select * from test_regex('a\sb', E'a\tb', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {"a b"}
+(2 rows)
+
+-- expectMatch 12.7 LP "a\\sb" "a\nb" "a\nb"
+select * from test_regex('a\sb', E'a\nb', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {"a +
+ b"}
+(2 rows)
+
+-- expectNomatch 12.8 LP {a\sb} axb
+select * from test_regex('a\sb', 'axb', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 12.9 LP {a\Sb} axb axb
+select * from test_regex('a\Sb', 'axb', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {axb}
+(2 rows)
+
+-- expectNomatch 12.10 LP "a\\Sb" "a b"
+select * from test_regex('a\Sb', 'a b', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 12.11 LP {a\wb} axb axb
+select * from test_regex('a\wb', 'axb', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {axb}
+(2 rows)
+
+-- expectNomatch 12.12 LP {a\wb} a-b
+select * from test_regex('a\wb', 'a-b', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectNomatch 12.13 LP {a\Wb} axb
+select * from test_regex('a\Wb', 'axb', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 12.14 LP {a\Wb} a-b a-b
+select * from test_regex('a\Wb', 'a-b', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a-b}
+(2 rows)
+
+-- expectMatch 12.15 LP {\y\w+z\y} adze-guz guz
+select * from test_regex('\y\w+z\y', 'adze-guz', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {guz}
+(2 rows)
+
+-- expectMatch 12.16 LPE {a[\d]b} a1b a1b
+select * from test_regex('a[\d]b', 'a1b', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {a1b}
+(2 rows)
+
+-- expectMatch 12.17 LPE "a\[\\s]b" "a b" "a b"
+select * from test_regex('a[\s]b', 'a b', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {"a b"}
+(2 rows)
+
+-- expectMatch 12.18 LPE {a[\w]b} axb axb
+select * from test_regex('a[\w]b', 'axb', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {axb}
+(2 rows)
+
+-- these should be invalid
+select * from test_regex('[\w-~]*', 'ab01_~-`**', 'LNPSE');
+ERROR: invalid regular expression: invalid character range
+select * from test_regex('[~-\w]*', 'ab01_~-`**', 'LNPSE');
+ERROR: invalid regular expression: invalid character range
+select * from test_regex('[[:alnum:]-~]*', 'ab01~-`**', 'LNS');
+ERROR: invalid regular expression: invalid character range
+select * from test_regex('[~-[:alnum:]]*', 'ab01~-`**', 'LNS');
+ERROR: invalid regular expression: invalid character range
+-- test complemented char classes within brackets
+select * from test_regex('[\D]', '0123456789abc*', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {a}
+(2 rows)
+
+select * from test_regex('[^\D]', 'abc0123456789*', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {0}
+(2 rows)
+
+select * from test_regex('[1\D7]', '0123456789abc*', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {1}
+(2 rows)
+
+select * from test_regex('[7\D1]', '0123456789abc*', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {1}
+(2 rows)
+
+select * from test_regex('[^0\D1]', 'abc0123456789*', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {2}
+(2 rows)
+
+select * from test_regex('[^1\D0]', 'abc0123456789*', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {2}
+(2 rows)
+
+select * from test_regex('\W', '0123456789abc_*', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {*}
+(2 rows)
+
+select * from test_regex('[\W]', '0123456789abc_*', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {*}
+(2 rows)
+
+select * from test_regex('[\s\S]*', '012 3456789abc_*', 'LNPE');
+ test_regex
+--------------------------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE,REG_UEMPTYMATCH}
+ {"012 3456789abc_*"}
+(2 rows)
+
+-- check char classes' handling of newlines
+select * from test_regex('\s+', E'abc \n def', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {" +
+ "}
+(2 rows)
+
+select * from test_regex('\s+', E'abc \n def', 'nLP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {" +
+ "}
+(2 rows)
+
+select * from test_regex('[\s]+', E'abc \n def', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {" +
+ "}
+(2 rows)
+
+select * from test_regex('[\s]+', E'abc \n def', 'nLPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {" +
+ "}
+(2 rows)
+
+select * from test_regex('\S+', E'abc\ndef', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {abc}
+(2 rows)
+
+select * from test_regex('\S+', E'abc\ndef', 'nLP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {abc}
+(2 rows)
+
+select * from test_regex('[\S]+', E'abc\ndef', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {abc}
+(2 rows)
+
+select * from test_regex('[\S]+', E'abc\ndef', 'nLPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {abc}
+(2 rows)
+
+select * from test_regex('\d+', E'012\n345', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {012}
+(2 rows)
+
+select * from test_regex('\d+', E'012\n345', 'nLP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {012}
+(2 rows)
+
+select * from test_regex('[\d]+', E'012\n345', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {012}
+(2 rows)
+
+select * from test_regex('[\d]+', E'012\n345', 'nLPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {012}
+(2 rows)
+
+select * from test_regex('\D+', E'abc\ndef345', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {"abc +
+ def"}
+(2 rows)
+
+select * from test_regex('\D+', E'abc\ndef345', 'nLP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {"abc +
+ def"}
+(2 rows)
+
+select * from test_regex('[\D]+', E'abc\ndef345', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {"abc +
+ def"}
+(2 rows)
+
+select * from test_regex('[\D]+', E'abc\ndef345', 'nLPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {"abc +
+ def"}
+(2 rows)
+
+select * from test_regex('\w+', E'abc_012\ndef', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {abc_012}
+(2 rows)
+
+select * from test_regex('\w+', E'abc_012\ndef', 'nLP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {abc_012}
+(2 rows)
+
+select * from test_regex('[\w]+', E'abc_012\ndef', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {abc_012}
+(2 rows)
+
+select * from test_regex('[\w]+', E'abc_012\ndef', 'nLPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {abc_012}
+(2 rows)
+
+select * from test_regex('\W+', E'***\n@@@___', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {"*** +
+ @@@"}
+(2 rows)
+
+select * from test_regex('\W+', E'***\n@@@___', 'nLP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {"*** +
+ @@@"}
+(2 rows)
+
+select * from test_regex('[\W]+', E'***\n@@@___', 'LPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {"*** +
+ @@@"}
+(2 rows)
+
+select * from test_regex('[\W]+', E'***\n@@@___', 'nLPE');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_ULOCALE}
+ {"*** +
+ @@@"}
+(2 rows)
+
+-- doing 13 "escapes"
+-- expectError 13.1 & "a\\" EESCAPE
+select * from test_regex('a\', '', '');
+ERROR: invalid regular expression: invalid escape \ sequence
+select * from test_regex('a\', '', 'b');
+ERROR: invalid regular expression: invalid escape \ sequence
+-- expectMatch 13.2 - {a\<b} a<b a<b
+select * from test_regex('a\<b', 'a<b', '-');
+ test_regex
+------------
+ {0}
+ {a<b}
+(2 rows)
+
+-- expectMatch 13.3 e {a\<b} a<b a<b
+select * from test_regex('a\<b', 'a<b', 'e');
+ test_regex
+------------
+ {0}
+ {a<b}
+(2 rows)
+
+-- expectMatch 13.4 bAS {a\wb} awb awb
+select * from test_regex('a\wb', 'awb', 'bAS');
+ test_regex
+------------------------------
+ {0,REG_UBSALNUM,REG_UUNSPEC}
+ {awb}
+(2 rows)
+
+-- expectMatch 13.5 eAS {a\wb} awb awb
+select * from test_regex('a\wb', 'awb', 'eAS');
+ test_regex
+------------------------------
+ {0,REG_UBSALNUM,REG_UUNSPEC}
+ {awb}
+(2 rows)
+
+-- expectMatch 13.6 PL "a\\ab" "a\007b" "a\007b"
+select * from test_regex('a\ab', E'a\007b', 'PL');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {a\x07b}
+(2 rows)
+
+-- expectMatch 13.7 P "a\\bb" "a\bb" "a\bb"
+select * from test_regex('a\bb', E'a\bb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a\x08b}
+(2 rows)
+
+-- expectMatch 13.8 P {a\Bb} "a\\b" "a\\b"
+select * from test_regex('a\Bb', 'a\b', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {"a\\b"}
+(2 rows)
+
+-- expectMatch 13.9 MP "a\\chb" "a\bb" "a\bb"
+select * from test_regex('a\chb', E'a\bb', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+ {a\x08b}
+(2 rows)
+
+-- expectMatch 13.10 MP "a\\cHb" "a\bb" "a\bb"
+select * from test_regex('a\cHb', E'a\bb', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+ {a\x08b}
+(2 rows)
+
+-- expectMatch 13.11 LMP "a\\e" "a\033" "a\033"
+select * from test_regex('a\e', E'a\033', 'LMP');
+ test_regex
+-------------------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT,REG_ULOCALE}
+ {a\x1B}
+(2 rows)
+
+-- expectMatch 13.12 P "a\\fb" "a\fb" "a\fb"
+select * from test_regex('a\fb', E'a\fb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {"a\x0Cb"}
+(2 rows)
+
+-- expectMatch 13.13 P "a\\nb" "a\nb" "a\nb"
+select * from test_regex('a\nb', E'a\nb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {"a +
+ b"}
+(2 rows)
+
+-- expectMatch 13.14 P "a\\rb" "a\rb" "a\rb"
+select * from test_regex('a\rb', E'a\rb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {"a\rb"}
+(2 rows)
+
+-- expectMatch 13.15 P "a\\tb" "a\tb" "a\tb"
+select * from test_regex('a\tb', E'a\tb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {"a b"}
+(2 rows)
+
+-- expectMatch 13.16 P "a\\u0008x" "a\bx" "a\bx"
+select * from test_regex('a\u0008x', E'a\bx', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a\x08x}
+(2 rows)
+
+-- expectMatch 13.17 P {a\u008x} "a\bx" "a\bx"
+-- Tcl has relaxed their code to allow 1-4 hex digits, but Postgres hasn't
+select * from test_regex('a\u008x', E'a\bx', 'P');
+ERROR: invalid regular expression: invalid escape \ sequence
+-- expectMatch 13.18 P "a\\u00088x" "a\b8x" "a\b8x"
+select * from test_regex('a\u00088x', E'a\b8x', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a\x088x}
+(2 rows)
+
+-- expectMatch 13.19 P "a\\U00000008x" "a\bx" "a\bx"
+select * from test_regex('a\U00000008x', E'a\bx', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a\x08x}
+(2 rows)
+
+-- expectMatch 13.20 P {a\U0000008x} "a\bx" "a\bx"
+-- Tcl has relaxed their code to allow 1-8 hex digits, but Postgres hasn't
+select * from test_regex('a\U0000008x', E'a\bx', 'P');
+ERROR: invalid regular expression: invalid escape \ sequence
+-- expectMatch 13.21 P "a\\vb" "a\vb" "a\vb"
+select * from test_regex('a\vb', E'a\013b', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {"a\x0Bb"}
+(2 rows)
+
+-- expectMatch 13.22 MP "a\\x08x" "a\bx" "a\bx"
+select * from test_regex('a\x08x', E'a\bx', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+ {a\x08x}
+(2 rows)
+
+-- expectError 13.23 - {a\xq} EESCAPE
+select * from test_regex('a\xq', '', '-');
+ERROR: invalid regular expression: invalid escape \ sequence
+-- expectMatch 13.24 MP "a\\x08x" "a\bx" "a\bx"
+select * from test_regex('a\x08x', E'a\bx', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+ {a\x08x}
+(2 rows)
+
+-- expectError 13.25 - {a\z} EESCAPE
+select * from test_regex('a\z', '', '-');
+ERROR: invalid regular expression: invalid escape \ sequence
+-- expectMatch 13.26 MP "a\\010b" "a\bb" "a\bb"
+select * from test_regex('a\010b', E'a\bb', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+ {a\x08b}
+(2 rows)
+
+-- These only work in UTF8 encoding, so they're moved to test_regex_utf8.sql:
+-- expectMatch 13.27 P "a\\U00001234x" "a\u1234x" "a\u1234x"
+-- expectMatch 13.28 P {a\U00001234x} "a\u1234x" "a\u1234x"
+-- expectMatch 13.29 P "a\\U0001234x" "a\u1234x" "a\u1234x"
+-- expectMatch 13.30 P {a\U0001234x} "a\u1234x" "a\u1234x"
+-- expectMatch 13.31 P "a\\U000012345x" "a\u12345x" "a\u12345x"
+-- expectMatch 13.32 P {a\U000012345x} "a\u12345x" "a\u12345x"
+-- expectMatch 13.33 P "a\\U1000000x" "a\ufffd0x" "a\ufffd0x"
+-- expectMatch 13.34 P {a\U1000000x} "a\ufffd0x" "a\ufffd0x"
+-- doing 14 "back references"
+-- # ugh
+-- expectMatch 14.1 RP {a(b*)c\1} abbcbb abbcbb bb
+select * from test_regex('a(b*)c\1', 'abbcbb', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {abbcbb,bb}
+(2 rows)
+
+-- expectMatch 14.2 RP {a(b*)c\1} ac ac ""
+select * from test_regex('a(b*)c\1', 'ac', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {ac,""}
+(2 rows)
+
+-- expectNomatch 14.3 RP {a(b*)c\1} abbcb
+select * from test_regex('a(b*)c\1', 'abbcb', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 14.4 RP {a(b*)\1} abbcbb abb b
+select * from test_regex('a(b*)\1', 'abbcbb', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {abb,b}
+(2 rows)
+
+-- expectMatch 14.5 RP {a(b|bb)\1} abbcbb abb b
+select * from test_regex('a(b|bb)\1', 'abbcbb', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {abb,b}
+(2 rows)
+
+-- expectMatch 14.6 RP {a([bc])\1} abb abb b
+select * from test_regex('a([bc])\1', 'abb', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {abb,b}
+(2 rows)
+
+-- expectNomatch 14.7 RP {a([bc])\1} abc
+select * from test_regex('a([bc])\1', 'abc', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 14.8 RP {a([bc])\1} abcabb abb b
+select * from test_regex('a([bc])\1', 'abcabb', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {abb,b}
+(2 rows)
+
+-- expectNomatch 14.9 RP {a([bc])*\1} abc
+select * from test_regex('a([bc])*\1', 'abc', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+(1 row)
+
+-- expectNomatch 14.10 RP {a([bc])\1} abB
+select * from test_regex('a([bc])\1', 'abB', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 14.11 iRP {a([bc])\1} abB abB b
+select * from test_regex('a([bc])\1', 'abB', 'iRP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {abB,b}
+(2 rows)
+
+-- expectMatch 14.12 RP {a([bc])\1+} abbb abbb b
+select * from test_regex('a([bc])\1+', 'abbb', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {abbb,b}
+(2 rows)
+
+-- expectMatch 14.13 QRP "a(\[bc])\\1{3,4}" abbbb abbbb b
+select * from test_regex('a([bc])\1{3,4}', 'abbbb', 'QRP');
+ test_regex
+--------------------------------------------
+ {1,REG_UBACKREF,REG_UBOUNDS,REG_UNONPOSIX}
+ {abbbb,b}
+(2 rows)
+
+-- expectNomatch 14.14 QRP "a(\[bc])\\1{3,4}" abbb
+select * from test_regex('a([bc])\1{3,4}', 'abbb', 'QRP');
+ test_regex
+--------------------------------------------
+ {1,REG_UBACKREF,REG_UBOUNDS,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 14.15 RP {a([bc])\1*} abbb abbb b
+select * from test_regex('a([bc])\1*', 'abbb', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {abbb,b}
+(2 rows)
+
+-- expectMatch 14.16 RP {a([bc])\1*} ab ab b
+select * from test_regex('a([bc])\1*', 'ab', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {ab,b}
+(2 rows)
+
+-- expectMatch 14.17 RP {a([bc])(\1*)} ab ab b ""
+select * from test_regex('a([bc])(\1*)', 'ab', 'RP');
+ test_regex
+--------------------------------
+ {2,REG_UBACKREF,REG_UNONPOSIX}
+ {ab,b,""}
+(2 rows)
+
+-- expectError 14.18 - {a((b)\1)} ESUBREG
+select * from test_regex('a((b)\1)', '', '-');
+ERROR: invalid regular expression: invalid backreference number
+-- expectError 14.19 - {a(b)c\2} ESUBREG
+select * from test_regex('a(b)c\2', '', '-');
+ERROR: invalid regular expression: invalid backreference number
+-- expectMatch 14.20 bR {a\(b*\)c\1} abbcbb abbcbb bb
+select * from test_regex('a\(b*\)c\1', 'abbcbb', 'bR');
+ test_regex
+------------------
+ {1,REG_UBACKREF}
+ {abbcbb,bb}
+(2 rows)
+
+-- expectMatch 14.21 RP {^([bc])\1*$} bbb bbb b
+select * from test_regex('^([bc])\1*$', 'bbb', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {bbb,b}
+(2 rows)
+
+-- expectMatch 14.22 RP {^([bc])\1*$} ccc ccc c
+select * from test_regex('^([bc])\1*$', 'ccc', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {ccc,c}
+(2 rows)
+
+-- expectNomatch 14.23 RP {^([bc])\1*$} bcb
+select * from test_regex('^([bc])\1*$', 'bcb', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 14.24 LRP {^(\w+)( \1)+$} {abc abc abc} {abc abc abc} abc { abc}
+select * from test_regex('^(\w+)( \1)+$', 'abc abc abc', 'LRP');
+ test_regex
+--------------------------------------------
+ {2,REG_UBACKREF,REG_UNONPOSIX,REG_ULOCALE}
+ {"abc abc abc",abc," abc"}
+(2 rows)
+
+-- expectNomatch 14.25 LRP {^(\w+)( \1)+$} {abc abd abc}
+select * from test_regex('^(\w+)( \1)+$', 'abc abd abc', 'LRP');
+ test_regex
+--------------------------------------------
+ {2,REG_UBACKREF,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectNomatch 14.26 LRP {^(\w+)( \1)+$} {abc abc abd}
+select * from test_regex('^(\w+)( \1)+$', 'abc abc abd', 'LRP');
+ test_regex
+--------------------------------------------
+ {2,REG_UBACKREF,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- expectMatch 14.27 RP {^(.+)( \1)+$} {abc abc abc} {abc abc abc} abc { abc}
+select * from test_regex('^(.+)( \1)+$', 'abc abc abc', 'RP');
+ test_regex
+--------------------------------
+ {2,REG_UBACKREF,REG_UNONPOSIX}
+ {"abc abc abc",abc," abc"}
+(2 rows)
+
+-- expectNomatch 14.28 RP {^(.+)( \1)+$} {abc abd abc}
+select * from test_regex('^(.+)( \1)+$', 'abc abd abc', 'RP');
+ test_regex
+--------------------------------
+ {2,REG_UBACKREF,REG_UNONPOSIX}
+(1 row)
+
+-- expectNomatch 14.29 RP {^(.+)( \1)+$} {abc abc abd}
+select * from test_regex('^(.+)( \1)+$', 'abc abc abd', 'RP');
+ test_regex
+--------------------------------
+ {2,REG_UBACKREF,REG_UNONPOSIX}
+(1 row)
+
+-- expectNomatch 14.30 RP {^(.)\1|\1.} {abcdef}
+select * from test_regex('^(.)\1|\1.', 'abcdef', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+(1 row)
+
+-- expectNomatch 14.31 RP {^((.)\2|..)\2} {abadef}
+select * from test_regex('^((.)\2|..)\2', 'abadef', 'RP');
+ test_regex
+--------------------------------
+ {2,REG_UBACKREF,REG_UNONPOSIX}
+(1 row)
+
+-- back reference only matches the string, not any constraints
+select * from test_regex('(^\w+).*\1', 'abc abc abc', 'LRP');
+ test_regex
+--------------------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX,REG_ULOCALE}
+ {"abc abc abc",abc}
+(2 rows)
+
+select * from test_regex('(^\w+\M).*\1', 'abc abcd abd', 'LRP');
+ test_regex
+--------------------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX,REG_ULOCALE}
+ {"abc abc",abc}
+(2 rows)
+
+select * from test_regex('(\w+(?= )).*\1', 'abc abcd abd', 'HLRP');
+ test_regex
+------------------------------------------------------------
+ {1,REG_UBACKREF,REG_ULOOKAROUND,REG_UNONPOSIX,REG_ULOCALE}
+ {"abc abc",abc}
+(2 rows)
+
+-- doing 15 "octal escapes vs back references"
+-- # initial zero is always octal
+-- expectMatch 15.1 MP "a\\010b" "a\bb" "a\bb"
+select * from test_regex('a\010b', E'a\bb', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+ {a\x08b}
+(2 rows)
+
+-- expectMatch 15.2 MP "a\\0070b" "a\0070b" "a\0070b"
+select * from test_regex('a\0070b', E'a\0070b', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+ {a\x070b}
+(2 rows)
+
+-- expectMatch 15.3 MP "a\\07b" "a\007b" "a\007b"
+select * from test_regex('a\07b', E'a\007b', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+ {a\x07b}
+(2 rows)
+
+-- expectMatch 15.4 MP "a(b)(b)(b)(b)(b)(b)(b)(b)(b)(b)\\07c" \
+-- "abbbbbbbbbb\007c" abbbbbbbbbb\007c b b b b b b b b b b
+select * from test_regex('a(b)(b)(b)(b)(b)(b)(b)(b)(b)(b)\07c', E'abbbbbbbbbb\007c', 'MP');
+ test_regex
+----------------------------------------
+ {10,REG_UNONPOSIX,REG_UUNPORT}
+ {abbbbbbbbbb\x07c,b,b,b,b,b,b,b,b,b,b}
+(2 rows)
+
+-- # a single digit is always a backref
+-- expectError 15.5 - {a\7b} ESUBREG
+select * from test_regex('a\7b', '', '-');
+ERROR: invalid regular expression: invalid backreference number
+-- # otherwise it's a backref only if within range (barf!)
+-- expectMatch 15.6 MP "a\\10b" "a\bb" "a\bb"
+select * from test_regex('a\10b', E'a\bb', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+ {a\x08b}
+(2 rows)
+
+-- expectMatch 15.7 MP {a\101b} aAb aAb
+select * from test_regex('a\101b', 'aAb', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+ {aAb}
+(2 rows)
+
+-- expectMatch 15.8 RP {a(b)(b)(b)(b)(b)(b)(b)(b)(b)(b)\10c} \
+-- "abbbbbbbbbbbc" abbbbbbbbbbbc b b b b b b b b b b
+select * from test_regex('a(b)(b)(b)(b)(b)(b)(b)(b)(b)(b)\10c', 'abbbbbbbbbbbc', 'RP');
+ test_regex
+-------------------------------------
+ {10,REG_UBACKREF,REG_UNONPOSIX}
+ {abbbbbbbbbbbc,b,b,b,b,b,b,b,b,b,b}
+(2 rows)
+
+-- # but we're fussy about border cases -- guys who want octal should use the zero
+-- expectError 15.9 - {a((((((((((b\10))))))))))c} ESUBREG
+select * from test_regex('a((((((((((b\10))))))))))c', '', '-');
+ERROR: invalid regular expression: invalid backreference number
+-- # BREs don't have octal, EREs don't have backrefs
+-- expectMatch 15.10 MP "a\\12b" "a\nb" "a\nb"
+select * from test_regex('a\12b', E'a\nb', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+ {"a +
+ b"}
+(2 rows)
+
+-- expectError 15.11 b {a\12b} ESUBREG
+select * from test_regex('a\12b', '', 'b');
+ERROR: invalid regular expression: invalid backreference number
+-- expectMatch 15.12 eAS {a\12b} a12b a12b
+select * from test_regex('a\12b', 'a12b', 'eAS');
+ test_regex
+------------------------------
+ {0,REG_UBSALNUM,REG_UUNSPEC}
+ {a12b}
+(2 rows)
+
+-- expectMatch 15.13 MP {a\701b} a\u00381b a\u00381b
+select * from test_regex('a\701b', 'a81b', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+ {a81b}
+(2 rows)
+
+-- doing 16 "expanded syntax"
+-- expectMatch 16.1 xP "a b c" "abc" "abc"
+select * from test_regex('a b c', 'abc', 'xP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {abc}
+(2 rows)
+
+-- expectMatch 16.2 xP "a b #oops\nc\td" "abcd" "abcd"
+select * from test_regex(E'a b #oops\nc\td', 'abcd', 'xP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {abcd}
+(2 rows)
+
+-- expectMatch 16.3 x "a\\ b\\\tc" "a b\tc" "a b\tc"
+select * from test_regex(E'a\\ b\\\tc', E'a b\tc', 'x');
+ test_regex
+-------------
+ {0}
+ {"a b c"}
+(2 rows)
+
+-- expectMatch 16.4 xP "a b\\#c" "ab#c" "ab#c"
+select * from test_regex('a b\#c', 'ab#c', 'xP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {ab#c}
+(2 rows)
+
+-- expectMatch 16.5 xP "a b\[c d]e" "ab e" "ab e"
+select * from test_regex('a b[c d]e', 'ab e', 'xP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {"ab e"}
+(2 rows)
+
+-- expectMatch 16.6 xP "a b\[c#d]e" "ab#e" "ab#e"
+select * from test_regex('a b[c#d]e', 'ab#e', 'xP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {ab#e}
+(2 rows)
+
+-- expectMatch 16.7 xP "a b\[c#d]e" "abde" "abde"
+select * from test_regex('a b[c#d]e', 'abde', 'xP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {abde}
+(2 rows)
+
+-- expectMatch 16.8 xSPB "ab{ d" "ab\{d" "ab\{d"
+select * from test_regex('ab{ d', 'ab{d', 'xSPB');
+ test_regex
+-------------------------------------------
+ {0,REG_UBRACES,REG_UNONPOSIX,REG_UUNSPEC}
+ {"ab{d"}
+(2 rows)
+
+-- expectMatch 16.9 xPQ "ab{ 1 , 2 }c" "abc" "abc"
+select * from test_regex('ab{ 1 , 2 }c', 'abc', 'xPQ');
+ test_regex
+-------------------------------
+ {0,REG_UBOUNDS,REG_UNONPOSIX}
+ {abc}
+(2 rows)
+
+-- doing 17 "misc syntax"
+-- expectMatch 17.1 P a(?#comment)b ab ab
+select * from test_regex('a(?#comment)b', 'ab', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {ab}
+(2 rows)
+
+-- doing 18 "unmatchable REs"
+-- expectNomatch 18.1 I a^b ab
+select * from test_regex('a^b', 'ab', 'I');
+ test_regex
+---------------------
+ {0,REG_UIMPOSSIBLE}
+(1 row)
+
+-- doing 19 "case independence"
+-- expectMatch 19.1 &i ab Ab Ab
+select * from test_regex('ab', 'Ab', 'i');
+ test_regex
+------------
+ {0}
+ {Ab}
+(2 rows)
+
+select * from test_regex('ab', 'Ab', 'ib');
+ test_regex
+------------
+ {0}
+ {Ab}
+(2 rows)
+
+-- expectMatch 19.2 &i {a[bc]} aC aC
+select * from test_regex('a[bc]', 'aC', 'i');
+ test_regex
+------------
+ {0}
+ {aC}
+(2 rows)
+
+select * from test_regex('a[bc]', 'aC', 'ib');
+ test_regex
+------------
+ {0}
+ {aC}
+(2 rows)
+
+-- expectNomatch 19.3 &i {a[^bc]} aB
+select * from test_regex('a[^bc]', 'aB', 'i');
+ test_regex
+------------
+ {0}
+(1 row)
+
+select * from test_regex('a[^bc]', 'aB', 'ib');
+ test_regex
+------------
+ {0}
+(1 row)
+
+-- expectMatch 19.4 &iM {a[b-d]} aC aC
+select * from test_regex('a[b-d]', 'aC', 'iM');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+ {aC}
+(2 rows)
+
+select * from test_regex('a[b-d]', 'aC', 'iMb');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+ {aC}
+(2 rows)
+
+-- expectNomatch 19.5 &iM {a[^b-d]} aC
+select * from test_regex('a[^b-d]', 'aC', 'iM');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+(1 row)
+
+select * from test_regex('a[^b-d]', 'aC', 'iMb');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+(1 row)
+
+-- doing 20 "directors and embedded options"
+-- expectError 20.1 & ***? BADPAT
+select * from test_regex('***?', '', '');
+ERROR: invalid regular expression: invalid regexp (reg version 0.8)
+select * from test_regex('***?', '', 'b');
+ERROR: invalid regular expression: invalid regexp (reg version 0.8)
+-- expectMatch 20.2 q ***? ***? ***?
+select * from test_regex('***?', '***?', 'q');
+ test_regex
+------------
+ {0}
+ {***?}
+(2 rows)
+
+-- expectMatch 20.3 &P ***=a*b a*b a*b
+select * from test_regex('***=a*b', 'a*b', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a*b}
+(2 rows)
+
+select * from test_regex('***=a*b', 'a*b', 'Pb');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a*b}
+(2 rows)
+
+-- expectMatch 20.4 q ***=a*b ***=a*b ***=a*b
+select * from test_regex('***=a*b', '***=a*b', 'q');
+ test_regex
+------------
+ {0}
+ {***=a*b}
+(2 rows)
+
+-- expectMatch 20.5 bLP {***:\w+} ab ab
+select * from test_regex('***:\w+', 'ab', 'bLP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {ab}
+(2 rows)
+
+-- expectMatch 20.6 eLP {***:\w+} ab ab
+select * from test_regex('***:\w+', 'ab', 'eLP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {ab}
+(2 rows)
+
+-- expectError 20.7 & ***:***=a*b BADRPT
+select * from test_regex('***:***=a*b', '', '');
+ERROR: invalid regular expression: quantifier operand invalid
+select * from test_regex('***:***=a*b', '', 'b');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectMatch 20.8 &P ***:(?b)a+b a+b a+b
+select * from test_regex('***:(?b)a+b', 'a+b', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a+b}
+(2 rows)
+
+select * from test_regex('***:(?b)a+b', 'a+b', 'Pb');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a+b}
+(2 rows)
+
+-- expectMatch 20.9 P (?b)a+b a+b a+b
+select * from test_regex('(?b)a+b', 'a+b', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a+b}
+(2 rows)
+
+-- expectError 20.10 e {(?b)\w+} BADRPT
+select * from test_regex('(?b)\w+', '', 'e');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectMatch 20.11 bAS {(?b)\w+} (?b)w+ (?b)w+
+select * from test_regex('(?b)\w+', '(?b)w+', 'bAS');
+ test_regex
+------------------------------
+ {0,REG_UBSALNUM,REG_UUNSPEC}
+ {(?b)w+}
+(2 rows)
+
+-- expectMatch 20.12 iP (?c)a a a
+select * from test_regex('(?c)a', 'a', 'iP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a}
+(2 rows)
+
+-- expectNomatch 20.13 iP (?c)a A
+select * from test_regex('(?c)a', 'A', 'iP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 20.14 APS {(?e)\W+} WW WW
+select * from test_regex('(?e)\W+', 'WW', 'APS');
+ test_regex
+--------------------------------------------
+ {0,REG_UBSALNUM,REG_UNONPOSIX,REG_UUNSPEC}
+ {WW}
+(2 rows)
+
+-- expectMatch 20.15 P (?i)a+ Aa Aa
+select * from test_regex('(?i)a+', 'Aa', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {Aa}
+(2 rows)
+
+-- expectNomatch 20.16 P "(?m)a.b" "a\nb"
+select * from test_regex('(?m)a.b', E'a\nb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 20.17 P "(?m)^b" "a\nb" "b"
+select * from test_regex('(?m)^b', E'a\nb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {b}
+(2 rows)
+
+-- expectNomatch 20.18 P "(?n)a.b" "a\nb"
+select * from test_regex('(?n)a.b', E'a\nb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 20.19 P "(?n)^b" "a\nb" "b"
+select * from test_regex('(?n)^b', E'a\nb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {b}
+(2 rows)
+
+-- expectNomatch 20.20 P "(?p)a.b" "a\nb"
+select * from test_regex('(?p)a.b', E'a\nb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+(1 row)
+
+-- expectNomatch 20.21 P "(?p)^b" "a\nb"
+select * from test_regex('(?p)^b', E'a\nb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 20.22 P (?q)a+b a+b a+b
+select * from test_regex('(?q)a+b', 'a+b', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a+b}
+(2 rows)
+
+-- expectMatch 20.23 nP "(?s)a.b" "a\nb" "a\nb"
+select * from test_regex('(?s)a.b', E'a\nb', 'nP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {"a +
+ b"}
+(2 rows)
+
+-- expectMatch 20.24 xP "(?t)a b" "a b" "a b"
+select * from test_regex('(?t)a b', 'a b', 'xP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {"a b"}
+(2 rows)
+
+-- expectMatch 20.25 P "(?w)a.b" "a\nb" "a\nb"
+select * from test_regex('(?w)a.b', E'a\nb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {"a +
+ b"}
+(2 rows)
+
+-- expectMatch 20.26 P "(?w)^b" "a\nb" "b"
+select * from test_regex('(?w)^b', E'a\nb', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {b}
+(2 rows)
+
+-- expectMatch 20.27 P "(?x)a b" "ab" "ab"
+select * from test_regex('(?x)a b', 'ab', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {ab}
+(2 rows)
+
+-- expectError 20.28 - (?z)ab BADOPT
+select * from test_regex('(?z)ab', '', '-');
+ERROR: invalid regular expression: invalid embedded option
+-- expectMatch 20.29 P (?ici)a+ Aa Aa
+select * from test_regex('(?ici)a+', 'Aa', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {Aa}
+(2 rows)
+
+-- expectError 20.30 P (?i)(?q)a+ BADRPT
+select * from test_regex('(?i)(?q)a+', '', 'P');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectMatch 20.31 P (?q)(?i)a+ (?i)a+ (?i)a+
+select * from test_regex('(?q)(?i)a+', '(?i)a+', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {(?i)a+}
+(2 rows)
+
+-- expectMatch 20.32 P (?qe)a+ a a
+select * from test_regex('(?qe)a+', 'a', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {a}
+(2 rows)
+
+-- expectMatch 20.33 xP "(?q)a b" "a b" "a b"
+select * from test_regex('(?q)a b', 'a b', 'xP');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {"a b"}
+(2 rows)
+
+-- expectMatch 20.34 P "(?qx)a b" "a b" "a b"
+select * from test_regex('(?qx)a b', 'a b', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {"a b"}
+(2 rows)
+
+-- expectMatch 20.35 P (?qi)ab Ab Ab
+select * from test_regex('(?qi)ab', 'Ab', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {Ab}
+(2 rows)
+
+-- doing 21 "capturing"
+-- expectMatch 21.1 - a(b)c abc abc b
+select * from test_regex('a(b)c', 'abc', '-');
+ test_regex
+------------
+ {1}
+ {abc,b}
+(2 rows)
+
+-- expectMatch 21.2 P a(?:b)c xabc abc
+select * from test_regex('a(?:b)c', 'xabc', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {abc}
+(2 rows)
+
+-- expectMatch 21.3 - a((b))c xabcy abc b b
+select * from test_regex('a((b))c', 'xabcy', '-');
+ test_regex
+------------
+ {2}
+ {abc,b,b}
+(2 rows)
+
+-- expectMatch 21.4 P a(?:(b))c abcy abc b
+select * from test_regex('a(?:(b))c', 'abcy', 'P');
+ test_regex
+-------------------
+ {1,REG_UNONPOSIX}
+ {abc,b}
+(2 rows)
+
+-- expectMatch 21.5 P a((?:b))c abc abc b
+select * from test_regex('a((?:b))c', 'abc', 'P');
+ test_regex
+-------------------
+ {1,REG_UNONPOSIX}
+ {abc,b}
+(2 rows)
+
+-- expectMatch 21.6 P a(?:(?:b))c abc abc
+select * from test_regex('a(?:(?:b))c', 'abc', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {abc}
+(2 rows)
+
+-- expectIndices 21.7 Q "a(b){0}c" ac {0 1} {-1 -1}
+select * from test_regex('a(b){0}c', 'ac', '0Q');
+ test_regex
+-----------------
+ {1,REG_UBOUNDS}
+ {"0 1","-1 -1"}
+(2 rows)
+
+-- expectMatch 21.8 - a(b)c(d)e abcde abcde b d
+select * from test_regex('a(b)c(d)e', 'abcde', '-');
+ test_regex
+-------------
+ {2}
+ {abcde,b,d}
+(2 rows)
+
+-- expectMatch 21.9 - (b)c(d)e bcde bcde b d
+select * from test_regex('(b)c(d)e', 'bcde', '-');
+ test_regex
+------------
+ {2}
+ {bcde,b,d}
+(2 rows)
+
+-- expectMatch 21.10 - a(b)(d)e abde abde b d
+select * from test_regex('a(b)(d)e', 'abde', '-');
+ test_regex
+------------
+ {2}
+ {abde,b,d}
+(2 rows)
+
+-- expectMatch 21.11 - a(b)c(d) abcd abcd b d
+select * from test_regex('a(b)c(d)', 'abcd', '-');
+ test_regex
+------------
+ {2}
+ {abcd,b,d}
+(2 rows)
+
+-- expectMatch 21.12 - (ab)(cd) xabcdy abcd ab cd
+select * from test_regex('(ab)(cd)', 'xabcdy', '-');
+ test_regex
+--------------
+ {2}
+ {abcd,ab,cd}
+(2 rows)
+
+-- expectMatch 21.13 - a(b)?c xabcy abc b
+select * from test_regex('a(b)?c', 'xabcy', '-');
+ test_regex
+------------
+ {1}
+ {abc,b}
+(2 rows)
+
+-- expectIndices 21.14 - a(b)?c xacy {1 2} {-1 -1}
+select * from test_regex('a(b)?c', 'xacy', '0-');
+ test_regex
+-----------------
+ {1}
+ {"1 2","-1 -1"}
+(2 rows)
+
+-- expectMatch 21.15 - a(b)?c(d)?e xabcdey abcde b d
+select * from test_regex('a(b)?c(d)?e', 'xabcdey', '-');
+ test_regex
+-------------
+ {2}
+ {abcde,b,d}
+(2 rows)
+
+-- expectIndices 21.16 - a(b)?c(d)?e xacdey {1 4} {-1 -1} {3 3}
+select * from test_regex('a(b)?c(d)?e', 'xacdey', '0-');
+ test_regex
+-----------------------
+ {2}
+ {"1 4","-1 -1","3 3"}
+(2 rows)
+
+-- expectIndices 21.17 - a(b)?c(d)?e xabcey {1 4} {2 2} {-1 -1}
+select * from test_regex('a(b)?c(d)?e', 'xabcey', '0-');
+ test_regex
+-----------------------
+ {2}
+ {"1 4","2 2","-1 -1"}
+(2 rows)
+
+-- expectIndices 21.18 - a(b)?c(d)?e xacey {1 3} {-1 -1} {-1 -1}
+select * from test_regex('a(b)?c(d)?e', 'xacey', '0-');
+ test_regex
+-------------------------
+ {2}
+ {"1 3","-1 -1","-1 -1"}
+(2 rows)
+
+-- expectMatch 21.19 - a(b)*c xabcy abc b
+select * from test_regex('a(b)*c', 'xabcy', '-');
+ test_regex
+------------
+ {1}
+ {abc,b}
+(2 rows)
+
+-- expectIndices 21.20 - a(b)*c xabbbcy {1 5} {4 4}
+select * from test_regex('a(b)*c', 'xabbbcy', '0-');
+ test_regex
+---------------
+ {1}
+ {"1 5","4 4"}
+(2 rows)
+
+-- expectIndices 21.21 - a(b)*c xacy {1 2} {-1 -1}
+select * from test_regex('a(b)*c', 'xacy', '0-');
+ test_regex
+-----------------
+ {1}
+ {"1 2","-1 -1"}
+(2 rows)
+
+-- expectMatch 21.22 - a(b*)c xabbbcy abbbc bbb
+select * from test_regex('a(b*)c', 'xabbbcy', '-');
+ test_regex
+-------------
+ {1}
+ {abbbc,bbb}
+(2 rows)
+
+-- expectMatch 21.23 - a(b*)c xacy ac ""
+select * from test_regex('a(b*)c', 'xacy', '-');
+ test_regex
+------------
+ {1}
+ {ac,""}
+(2 rows)
+
+-- expectNomatch 21.24 - a(b)+c xacy
+select * from test_regex('a(b)+c', 'xacy', '-');
+ test_regex
+------------
+ {1}
+(1 row)
+
+-- expectMatch 21.25 - a(b)+c xabcy abc b
+select * from test_regex('a(b)+c', 'xabcy', '-');
+ test_regex
+------------
+ {1}
+ {abc,b}
+(2 rows)
+
+-- expectIndices 21.26 - a(b)+c xabbbcy {1 5} {4 4}
+select * from test_regex('a(b)+c', 'xabbbcy', '0-');
+ test_regex
+---------------
+ {1}
+ {"1 5","4 4"}
+(2 rows)
+
+-- expectMatch 21.27 - a(b+)c xabbbcy abbbc bbb
+select * from test_regex('a(b+)c', 'xabbbcy', '-');
+ test_regex
+-------------
+ {1}
+ {abbbc,bbb}
+(2 rows)
+
+-- expectIndices 21.28 Q "a(b){2,3}c" xabbbcy {1 5} {4 4}
+select * from test_regex('a(b){2,3}c', 'xabbbcy', '0Q');
+ test_regex
+-----------------
+ {1,REG_UBOUNDS}
+ {"1 5","4 4"}
+(2 rows)
+
+-- expectIndices 21.29 Q "a(b){2,3}c" xabbcy {1 4} {3 3}
+select * from test_regex('a(b){2,3}c', 'xabbcy', '0Q');
+ test_regex
+-----------------
+ {1,REG_UBOUNDS}
+ {"1 4","3 3"}
+(2 rows)
+
+-- expectNomatch 21.30 Q "a(b){2,3}c" xabcy
+select * from test_regex('a(b){2,3}c', 'xabcy', 'Q');
+ test_regex
+-----------------
+ {1,REG_UBOUNDS}
+(1 row)
+
+-- expectMatch 21.31 LP "\\y(\\w+)\\y" "-- abc-" "abc" "abc"
+select * from test_regex('\y(\w+)\y', '-- abc-', 'LP');
+ test_regex
+-------------------------------
+ {1,REG_UNONPOSIX,REG_ULOCALE}
+ {abc,abc}
+(2 rows)
+
+-- expectMatch 21.32 - a((b|c)d+)+ abacdbd acdbd bd b
+select * from test_regex('a((b|c)d+)+', 'abacdbd', '-');
+ test_regex
+--------------
+ {2}
+ {acdbd,bd,b}
+(2 rows)
+
+-- expectMatch 21.33 N (.*).* abc abc abc
+select * from test_regex('(.*).*', 'abc', 'N');
+ test_regex
+---------------------
+ {1,REG_UEMPTYMATCH}
+ {abc,abc}
+(2 rows)
+
+-- expectMatch 21.34 N (a*)* bc "" ""
+select * from test_regex('(a*)*', 'bc', 'N');
+ test_regex
+---------------------
+ {1,REG_UEMPTYMATCH}
+ {"",""}
+(2 rows)
+
+-- expectMatch 21.35 M { TO (([a-z0-9._]+|"([^"]+|"")+")+)} {asd TO foo} { TO foo} foo o {}
+select * from test_regex(' TO (([a-z0-9._]+|"([^"]+|"")+")+)', 'asd TO foo', 'M');
+ test_regex
+------------------------
+ {3,REG_UUNPORT}
+ {" TO foo",foo,o,NULL}
+(2 rows)
+
+-- expectMatch 21.36 RPQ ((.))(\2){0} xy x x x {}
+select * from test_regex('((.))(\2){0}', 'xy', 'RPQ');
+ test_regex
+--------------------------------------------
+ {3,REG_UBACKREF,REG_UBOUNDS,REG_UNONPOSIX}
+ {x,x,x,NULL}
+(2 rows)
+
+-- expectNomatch 21.39 PQR {(.){0}(\1)} xxx
+select * from test_regex('(.){0}(\1)', 'xxx', 'PQR');
+ test_regex
+--------------------------------------------
+ {2,REG_UBACKREF,REG_UBOUNDS,REG_UNONPOSIX}
+(1 row)
+
+-- expectNomatch 21.40 PQR {((.)){0}(\2)} xxx
+select * from test_regex('((.)){0}(\2)', 'xxx', 'PQR');
+ test_regex
+--------------------------------------------
+ {3,REG_UBACKREF,REG_UBOUNDS,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 21.41 NPQR {((.)){0}(\2){0}} xyz {} {} {} {}
+select * from test_regex('((.)){0}(\2){0}', 'xyz', 'NPQR');
+ test_regex
+------------------------------------------------------------
+ {3,REG_UBACKREF,REG_UBOUNDS,REG_UNONPOSIX,REG_UEMPTYMATCH}
+ {"",NULL,NULL,NULL}
+(2 rows)
+
+-- doing 22 "multicharacter collating elements"
+-- # again ugh
+-- MCCEs are not implemented in Postgres, so we skip all these tests
+-- expectMatch 22.1 &+L {a[c]e} ace ace
+-- select * from test_regex('a[c]e', 'ace', '+L');
+-- select * from test_regex('a[c]e', 'ace', '+Lb');
+-- expectNomatch 22.2 &+IL {a[c]h} ach
+-- select * from test_regex('a[c]h', 'ach', '+IL');
+-- select * from test_regex('a[c]h', 'ach', '+ILb');
+-- expectMatch 22.3 &+L {a[[.ch.]]} ach ach
+-- select * from test_regex('a[[.ch.]]', 'ach', '+L');
+-- select * from test_regex('a[[.ch.]]', 'ach', '+Lb');
+-- expectNomatch 22.4 &+L {a[[.ch.]]} ace
+-- select * from test_regex('a[[.ch.]]', 'ace', '+L');
+-- select * from test_regex('a[[.ch.]]', 'ace', '+Lb');
+-- expectMatch 22.5 &+L {a[c[.ch.]]} ac ac
+-- select * from test_regex('a[c[.ch.]]', 'ac', '+L');
+-- select * from test_regex('a[c[.ch.]]', 'ac', '+Lb');
+-- expectMatch 22.6 &+L {a[c[.ch.]]} ace ac
+-- select * from test_regex('a[c[.ch.]]', 'ace', '+L');
+-- select * from test_regex('a[c[.ch.]]', 'ace', '+Lb');
+-- expectMatch 22.7 &+L {a[c[.ch.]]} ache ach
+-- select * from test_regex('a[c[.ch.]]', 'ache', '+L');
+-- select * from test_regex('a[c[.ch.]]', 'ache', '+Lb');
+-- expectNomatch 22.8 &+L {a[^c]e} ace
+-- select * from test_regex('a[^c]e', 'ace', '+L');
+-- select * from test_regex('a[^c]e', 'ace', '+Lb');
+-- expectMatch 22.9 &+L {a[^c]e} abe abe
+-- select * from test_regex('a[^c]e', 'abe', '+L');
+-- select * from test_regex('a[^c]e', 'abe', '+Lb');
+-- expectMatch 22.10 &+L {a[^c]e} ache ache
+-- select * from test_regex('a[^c]e', 'ache', '+L');
+-- select * from test_regex('a[^c]e', 'ache', '+Lb');
+-- expectNomatch 22.11 &+L {a[^[.ch.]]} ach
+-- select * from test_regex('a[^[.ch.]]', 'ach', '+L');
+-- select * from test_regex('a[^[.ch.]]', 'ach', '+Lb');
+-- expectMatch 22.12 &+L {a[^[.ch.]]} ace ac
+-- select * from test_regex('a[^[.ch.]]', 'ace', '+L');
+-- select * from test_regex('a[^[.ch.]]', 'ace', '+Lb');
+-- expectMatch 22.13 &+L {a[^[.ch.]]} ac ac
+-- select * from test_regex('a[^[.ch.]]', 'ac', '+L');
+-- select * from test_regex('a[^[.ch.]]', 'ac', '+Lb');
+-- expectMatch 22.14 &+L {a[^[.ch.]]} abe ab
+-- select * from test_regex('a[^[.ch.]]', 'abe', '+L');
+-- select * from test_regex('a[^[.ch.]]', 'abe', '+Lb');
+-- expectNomatch 22.15 &+L {a[^c[.ch.]]} ach
+-- select * from test_regex('a[^c[.ch.]]', 'ach', '+L');
+-- select * from test_regex('a[^c[.ch.]]', 'ach', '+Lb');
+-- expectNomatch 22.16 &+L {a[^c[.ch.]]} ace
+-- select * from test_regex('a[^c[.ch.]]', 'ace', '+L');
+-- select * from test_regex('a[^c[.ch.]]', 'ace', '+Lb');
+-- expectNomatch 22.17 &+L {a[^c[.ch.]]} ac
+-- select * from test_regex('a[^c[.ch.]]', 'ac', '+L');
+-- select * from test_regex('a[^c[.ch.]]', 'ac', '+Lb');
+-- expectMatch 22.18 &+L {a[^c[.ch.]]} abe ab
+-- select * from test_regex('a[^c[.ch.]]', 'abe', '+L');
+-- select * from test_regex('a[^c[.ch.]]', 'abe', '+Lb');
+-- expectMatch 22.19 &+L {a[^b]} ac ac
+-- select * from test_regex('a[^b]', 'ac', '+L');
+-- select * from test_regex('a[^b]', 'ac', '+Lb');
+-- expectMatch 22.20 &+L {a[^b]} ace ac
+-- select * from test_regex('a[^b]', 'ace', '+L');
+-- select * from test_regex('a[^b]', 'ace', '+Lb');
+-- expectMatch 22.21 &+L {a[^b]} ach ach
+-- select * from test_regex('a[^b]', 'ach', '+L');
+-- select * from test_regex('a[^b]', 'ach', '+Lb');
+-- expectNomatch 22.22 &+L {a[^b]} abe
+-- select * from test_regex('a[^b]', 'abe', '+L');
+-- select * from test_regex('a[^b]', 'abe', '+Lb');
+-- doing 23 "lookahead constraints"
+-- expectMatch 23.1 HP a(?=b)b* ab ab
+select * from test_regex('a(?=b)b*', 'ab', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {ab}
+(2 rows)
+
+-- expectNomatch 23.2 HP a(?=b)b* a
+select * from test_regex('a(?=b)b*', 'a', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 23.3 HP a(?=b)b*(?=c)c* abc abc
+select * from test_regex('a(?=b)b*(?=c)c*', 'abc', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {abc}
+(2 rows)
+
+-- expectNomatch 23.4 HP a(?=b)b*(?=c)c* ab
+select * from test_regex('a(?=b)b*(?=c)c*', 'ab', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+(1 row)
+
+-- expectNomatch 23.5 HP a(?!b)b* ab
+select * from test_regex('a(?!b)b*', 'ab', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 23.6 HP a(?!b)b* a a
+select * from test_regex('a(?!b)b*', 'a', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {a}
+(2 rows)
+
+-- expectMatch 23.7 HP (?=b)b b b
+select * from test_regex('(?=b)b', 'b', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {b}
+(2 rows)
+
+-- expectNomatch 23.8 HP (?=b)b a
+select * from test_regex('(?=b)b', 'a', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 23.9 HP ...(?!.) abcde cde
+select * from test_regex('...(?!.)', 'abcde', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {cde}
+(2 rows)
+
+-- expectNomatch 23.10 HP ...(?=.) abc
+select * from test_regex('...(?=.)', 'abc', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+(1 row)
+
+-- Postgres addition: lookbehind constraints
+-- expectMatch 23.11 HPN (?<=a)b* ab b
+select * from test_regex('(?<=a)b*', 'ab', 'HPN');
+ test_regex
+---------------------------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX,REG_UEMPTYMATCH}
+ {b}
+(2 rows)
+
+-- expectNomatch 23.12 HPN (?<=a)b* b
+select * from test_regex('(?<=a)b*', 'b', 'HPN');
+ test_regex
+---------------------------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX,REG_UEMPTYMATCH}
+(1 row)
+
+-- expectMatch 23.13 HP (?<=a)b*(?<=b)c* abc bc
+select * from test_regex('(?<=a)b*(?<=b)c*', 'abc', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {bc}
+(2 rows)
+
+-- expectNomatch 23.14 HP (?<=a)b*(?<=b)c* ac
+select * from test_regex('(?<=a)b*(?<=b)c*', 'ac', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+(1 row)
+
+-- expectNomatch 23.15 IHP a(?<!a)b* ab
+select * from test_regex('a(?<!a)b*', 'ab', 'IHP');
+ test_regex
+---------------------------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX,REG_UIMPOSSIBLE}
+(1 row)
+
+-- expectMatch 23.16 HP a(?<!b)b* a a
+select * from test_regex('a(?<!b)b*', 'a', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {a}
+(2 rows)
+
+-- expectMatch 23.17 HP (?<=b)b bb b
+select * from test_regex('(?<=b)b', 'bb', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {b}
+(2 rows)
+
+-- expectNomatch 23.18 HP (?<=b)b b
+select * from test_regex('(?<=b)b', 'b', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+(1 row)
+
+-- expectMatch 23.19 HP (?<=.).. abcde bc
+select * from test_regex('(?<=.)..', 'abcde', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {bc}
+(2 rows)
+
+-- expectMatch 23.20 HP (?<=..)a* aaabb a
+select * from test_regex('(?<=..)a*', 'aaabb', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {a}
+(2 rows)
+
+-- expectMatch 23.21 HP (?<=..)b* aaabb {}
+-- Note: empty match here is correct, it matches after the first 2 characters
+select * from test_regex('(?<=..)b*', 'aaabb', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {""}
+(2 rows)
+
+-- expectMatch 23.22 HP (?<=..)b+ aaabb bb
+select * from test_regex('(?<=..)b+', 'aaabb', 'HP');
+ test_regex
+-----------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {bb}
+(2 rows)
+
+-- doing 24 "non-greedy quantifiers"
+-- expectMatch 24.1 PT ab+? abb ab
+select * from test_regex('ab+?', 'abb', 'PT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {ab}
+(2 rows)
+
+-- expectMatch 24.2 PT ab+?c abbc abbc
+select * from test_regex('ab+?c', 'abbc', 'PT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {abbc}
+(2 rows)
+
+-- expectMatch 24.3 PT ab*? abb a
+select * from test_regex('ab*?', 'abb', 'PT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {a}
+(2 rows)
+
+-- expectMatch 24.4 PT ab*?c abbc abbc
+select * from test_regex('ab*?c', 'abbc', 'PT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {abbc}
+(2 rows)
+
+-- expectMatch 24.5 PT ab?? ab a
+select * from test_regex('ab??', 'ab', 'PT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {a}
+(2 rows)
+
+-- expectMatch 24.6 PT ab??c abc abc
+select * from test_regex('ab??c', 'abc', 'PT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {abc}
+(2 rows)
+
+-- expectMatch 24.7 PQT "ab{2,4}?" abbbb abb
+select * from test_regex('ab{2,4}?', 'abbbb', 'PQT');
+ test_regex
+---------------------------------------------
+ {0,REG_UBOUNDS,REG_UNONPOSIX,REG_USHORTEST}
+ {abb}
+(2 rows)
+
+-- expectMatch 24.8 PQT "ab{2,4}?c" abbbbc abbbbc
+select * from test_regex('ab{2,4}?c', 'abbbbc', 'PQT');
+ test_regex
+---------------------------------------------
+ {0,REG_UBOUNDS,REG_UNONPOSIX,REG_USHORTEST}
+ {abbbbc}
+(2 rows)
+
+-- expectMatch 24.9 - 3z* 123zzzz456 3zzzz
+select * from test_regex('3z*', '123zzzz456', '-');
+ test_regex
+------------
+ {0}
+ {3zzzz}
+(2 rows)
+
+-- expectMatch 24.10 PT 3z*? 123zzzz456 3
+select * from test_regex('3z*?', '123zzzz456', 'PT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {3}
+(2 rows)
+
+-- expectMatch 24.11 - z*4 123zzzz456 zzzz4
+select * from test_regex('z*4', '123zzzz456', '-');
+ test_regex
+------------
+ {0}
+ {zzzz4}
+(2 rows)
+
+-- expectMatch 24.12 PT z*?4 123zzzz456 zzzz4
+select * from test_regex('z*?4', '123zzzz456', 'PT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {zzzz4}
+(2 rows)
+
+-- expectMatch 24.13 PT {^([^/]+?)(?:/([^/]+?))(?:/([^/]+?))?$} {foo/bar/baz} {foo/bar/baz} {foo} {bar} {baz}
+select * from test_regex('^([^/]+?)(?:/([^/]+?))(?:/([^/]+?))?$', 'foo/bar/baz', 'PT');
+ test_regex
+---------------------------------
+ {3,REG_UNONPOSIX,REG_USHORTEST}
+ {foo/bar/baz,foo,bar,baz}
+(2 rows)
+
+-- doing 25 "mixed quantifiers"
+-- # this is very incomplete as yet
+-- # should include |
+-- expectMatch 25.1 PNT {^(.*?)(a*)$} "xyza" xyza xyz a
+select * from test_regex('^(.*?)(a*)$', 'xyza', 'PNT');
+ test_regex
+-------------------------------------------------
+ {2,REG_UNONPOSIX,REG_UEMPTYMATCH,REG_USHORTEST}
+ {xyza,xyz,a}
+(2 rows)
+
+-- expectMatch 25.2 PNT {^(.*?)(a*)$} "xyzaa" xyzaa xyz aa
+select * from test_regex('^(.*?)(a*)$', 'xyzaa', 'PNT');
+ test_regex
+-------------------------------------------------
+ {2,REG_UNONPOSIX,REG_UEMPTYMATCH,REG_USHORTEST}
+ {xyzaa,xyz,aa}
+(2 rows)
+
+-- expectMatch 25.3 PNT {^(.*?)(a*)$} "xyz" xyz xyz ""
+select * from test_regex('^(.*?)(a*)$', 'xyz', 'PNT');
+ test_regex
+-------------------------------------------------
+ {2,REG_UNONPOSIX,REG_UEMPTYMATCH,REG_USHORTEST}
+ {xyz,xyz,""}
+(2 rows)
+
+-- doing 26 "tricky cases"
+-- # attempts to trick the matcher into accepting a short match
+-- expectMatch 26.1 - (week|wee)(night|knights) \
+-- "weeknights" weeknights wee knights
+select * from test_regex('(week|wee)(night|knights)', 'weeknights', '-');
+ test_regex
+--------------------------
+ {2}
+ {weeknights,wee,knights}
+(2 rows)
+
+-- expectMatch 26.2 RP {a(bc*).*\1} abccbccb abccbccb b
+select * from test_regex('a(bc*).*\1', 'abccbccb', 'RP');
+ test_regex
+--------------------------------
+ {1,REG_UBACKREF,REG_UNONPOSIX}
+ {abccbccb,b}
+(2 rows)
+
+-- expectMatch 26.3 - {a(b.[bc]*)+} abcbd abcbd bd
+select * from test_regex('a(b.[bc]*)+', 'abcbd', '-');
+ test_regex
+------------
+ {1}
+ {abcbd,bd}
+(2 rows)
+
+-- doing 27 "implementation misc."
+-- # duplicate arcs are suppressed
+-- expectMatch 27.1 P a(?:b|b)c abc abc
+select * from test_regex('a(?:b|b)c', 'abc', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {abc}
+(2 rows)
+
+-- # make color/subcolor relationship go back and forth
+-- expectMatch 27.2 & {[ab][ab][ab]} aba aba
+select * from test_regex('[ab][ab][ab]', 'aba', '');
+ test_regex
+------------
+ {0}
+ {aba}
+(2 rows)
+
+select * from test_regex('[ab][ab][ab]', 'aba', 'b');
+ test_regex
+------------
+ {0}
+ {aba}
+(2 rows)
+
+-- expectMatch 27.3 & {[ab][ab][ab][ab][ab][ab][ab]} \
+-- "abababa" abababa
+select * from test_regex('[ab][ab][ab][ab][ab][ab][ab]', 'abababa', '');
+ test_regex
+------------
+ {0}
+ {abababa}
+(2 rows)
+
+select * from test_regex('[ab][ab][ab][ab][ab][ab][ab]', 'abababa', 'b');
+ test_regex
+------------
+ {0}
+ {abababa}
+(2 rows)
+
+-- doing 28 "boundary busters etc."
+-- # color-descriptor allocation changes at 10
+-- expectMatch 28.1 & abcdefghijkl "abcdefghijkl" abcdefghijkl
+select * from test_regex('abcdefghijkl', 'abcdefghijkl', '');
+ test_regex
+----------------
+ {0}
+ {abcdefghijkl}
+(2 rows)
+
+select * from test_regex('abcdefghijkl', 'abcdefghijkl', 'b');
+ test_regex
+----------------
+ {0}
+ {abcdefghijkl}
+(2 rows)
+
+-- # so does arc allocation
+-- expectMatch 28.2 P a(?:b|c|d|e|f|g|h|i|j|k|l|m)n "agn" agn
+select * from test_regex('a(?:b|c|d|e|f|g|h|i|j|k|l|m)n', 'agn', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {agn}
+(2 rows)
+
+-- # subexpression tracking also at 10
+-- expectMatch 28.3 - a(((((((((((((b)))))))))))))c \
+-- "abc" abc b b b b b b b b b b b b b
+select * from test_regex('a(((((((((((((b)))))))))))))c', 'abc', '-');
+ test_regex
+---------------------------------
+ {13}
+ {abc,b,b,b,b,b,b,b,b,b,b,b,b,b}
+(2 rows)
+
+-- # state-set handling changes slightly at unsigned size (might be 64...)
+-- # (also stresses arc allocation)
+-- expectMatch 28.4 Q "ab{1,100}c" abbc abbc
+select * from test_regex('ab{1,100}c', 'abbc', 'Q');
+ test_regex
+-----------------
+ {0,REG_UBOUNDS}
+ {abbc}
+(2 rows)
+
+-- expectMatch 28.5 Q "ab{1,100}c" \
+-- "abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc" \
+-- abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc
+select * from test_regex('ab{1,100}c', 'abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc', 'Q');
+ test_regex
+---------------------------------------
+ {0,REG_UBOUNDS}
+ {abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc}
+(2 rows)
+
+-- expectMatch 28.6 Q "ab{1,100}c" \
+-- "abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc"\
+-- abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc
+select * from test_regex('ab{1,100}c', 'abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc', 'Q');
+ test_regex
+------------------------------------------------------------------------
+ {0,REG_UBOUNDS}
+ {abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc}
+(2 rows)
+
+-- # force small cache and bust it, several ways
+-- expectMatch 28.7 LP {\w+abcdefgh} xyzabcdefgh xyzabcdefgh
+select * from test_regex('\w+abcdefgh', 'xyzabcdefgh', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {xyzabcdefgh}
+(2 rows)
+
+-- expectMatch 28.8 %LP {\w+abcdefgh} xyzabcdefgh xyzabcdefgh
+select * from test_regex('\w+abcdefgh', 'xyzabcdefgh', '%LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {xyzabcdefgh}
+(2 rows)
+
+-- expectMatch 28.9 %LP {\w+abcdefghijklmnopqrst} \
+-- "xyzabcdefghijklmnopqrst" xyzabcdefghijklmnopqrst
+select * from test_regex('\w+abcdefghijklmnopqrst', 'xyzabcdefghijklmnopqrst', '%LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {xyzabcdefghijklmnopqrst}
+(2 rows)
+
+-- expectIndices 28.10 %LP {\w+(abcdefgh)?} xyz {0 2} {-1 -1}
+select * from test_regex('\w+(abcdefgh)?', 'xyz', '0%LP');
+ test_regex
+-------------------------------
+ {1,REG_UNONPOSIX,REG_ULOCALE}
+ {"0 2","-1 -1"}
+(2 rows)
+
+-- expectIndices 28.11 %LP {\w+(abcdefgh)?} xyzabcdefg {0 9} {-1 -1}
+select * from test_regex('\w+(abcdefgh)?', 'xyzabcdefg', '0%LP');
+ test_regex
+-------------------------------
+ {1,REG_UNONPOSIX,REG_ULOCALE}
+ {"0 9","-1 -1"}
+(2 rows)
+
+-- expectIndices 28.12 %LP {\w+(abcdefghijklmnopqrst)?} \
+-- "xyzabcdefghijklmnopqrs" {0 21} {-1 -1}
+select * from test_regex('\w+(abcdefghijklmnopqrst)?', 'xyzabcdefghijklmnopqrs', '0%LP');
+ test_regex
+-------------------------------
+ {1,REG_UNONPOSIX,REG_ULOCALE}
+ {"0 21","-1 -1"}
+(2 rows)
+
+-- doing 29 "incomplete matches"
+-- expectPartial 29.1 t def abc {3 2} ""
+select * from test_regex('def', 'abc', '0!t');
+ test_regex
+---------------
+ {0}
+ {"3 2","3 2"}
+(2 rows)
+
+-- expectPartial 29.2 t bcd abc {1 2} ""
+select * from test_regex('bcd', 'abc', '0!t');
+ test_regex
+---------------
+ {0}
+ {"1 2","1 2"}
+(2 rows)
+
+-- expectPartial 29.3 t abc abab {0 3} ""
+select * from test_regex('abc', 'abab', '0!t');
+ test_regex
+---------------
+ {0}
+ {"0 3","0 3"}
+(2 rows)
+
+-- expectPartial 29.4 t abc abdab {3 4} ""
+select * from test_regex('abc', 'abdab', '0!t');
+ test_regex
+---------------
+ {0}
+ {"3 4","3 4"}
+(2 rows)
+
+-- expectIndices 29.5 t abc abc {0 2} {0 2}
+select * from test_regex('abc', 'abc', '0t');
+ test_regex
+---------------
+ {0}
+ {"0 2","0 2"}
+(2 rows)
+
+-- expectIndices 29.6 t abc xyabc {2 4} {2 4}
+select * from test_regex('abc', 'xyabc', '0t');
+ test_regex
+---------------
+ {0}
+ {"2 4","2 4"}
+(2 rows)
+
+-- expectPartial 29.7 t abc+ xyab {2 3} ""
+select * from test_regex('abc+', 'xyab', '0!t');
+ test_regex
+---------------
+ {0}
+ {"2 3","2 3"}
+(2 rows)
+
+-- expectIndices 29.8 t abc+ xyabc {2 4} {2 4}
+select * from test_regex('abc+', 'xyabc', '0t');
+ test_regex
+---------------
+ {0}
+ {"2 4","2 4"}
+(2 rows)
+
+-- knownBug expectIndices 29.9 t abc+ xyabcd {2 4} {6 5}
+select * from test_regex('abc+', 'xyabcd', '0t');
+ test_regex
+---------------
+ {0}
+ {"2 4","2 5"}
+(2 rows)
+
+-- expectIndices 29.10 t abc+ xyabcdd {2 4} {7 6}
+select * from test_regex('abc+', 'xyabcdd', '0t');
+ test_regex
+---------------
+ {0}
+ {"2 4","7 6"}
+(2 rows)
+
+-- expectPartial 29.11 tPT abc+? xyab {2 3} ""
+select * from test_regex('abc+?', 'xyab', '0!tPT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {"2 3","2 3"}
+(2 rows)
+
+-- # the retain numbers in these two may look wrong, but they aren't
+-- expectIndices 29.12 tPT abc+? xyabc {2 4} {5 4}
+select * from test_regex('abc+?', 'xyabc', '0tPT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {"2 4","5 4"}
+(2 rows)
+
+-- expectIndices 29.13 tPT abc+? xyabcc {2 4} {6 5}
+select * from test_regex('abc+?', 'xyabcc', '0tPT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {"2 4","6 5"}
+(2 rows)
+
+-- expectIndices 29.14 tPT abc+? xyabcd {2 4} {6 5}
+select * from test_regex('abc+?', 'xyabcd', '0tPT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {"2 4","6 5"}
+(2 rows)
+
+-- expectIndices 29.15 tPT abc+? xyabcdd {2 4} {7 6}
+select * from test_regex('abc+?', 'xyabcdd', '0tPT');
+ test_regex
+---------------------------------
+ {0,REG_UNONPOSIX,REG_USHORTEST}
+ {"2 4","7 6"}
+(2 rows)
+
+-- expectIndices 29.16 t abcd|bc xyabc {3 4} {2 4}
+select * from test_regex('abcd|bc', 'xyabc', '0t');
+ test_regex
+---------------
+ {0}
+ {"3 4","2 4"}
+(2 rows)
+
+-- expectPartial 29.17 tn .*k "xx\nyyy" {3 5} ""
+select * from test_regex('.*k', E'xx\nyyy', '0!tn');
+ test_regex
+---------------
+ {0}
+ {"3 5","3 5"}
+(2 rows)
+
+-- doing 30 "misc. oddities and old bugs"
+-- expectError 30.1 & *** BADRPT
+select * from test_regex('***', '', '');
+ERROR: invalid regular expression: quantifier operand invalid
+select * from test_regex('***', '', 'b');
+ERROR: invalid regular expression: quantifier operand invalid
+-- expectMatch 30.2 N a?b* abb abb
+select * from test_regex('a?b*', 'abb', 'N');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {abb}
+(2 rows)
+
+-- expectMatch 30.3 N a?b* bb bb
+select * from test_regex('a?b*', 'bb', 'N');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {bb}
+(2 rows)
+
+-- expectMatch 30.4 & a*b aab aab
+select * from test_regex('a*b', 'aab', '');
+ test_regex
+------------
+ {0}
+ {aab}
+(2 rows)
+
+select * from test_regex('a*b', 'aab', 'b');
+ test_regex
+------------
+ {0}
+ {aab}
+(2 rows)
+
+-- expectMatch 30.5 & ^a*b aaaab aaaab
+select * from test_regex('^a*b', 'aaaab', '');
+ test_regex
+------------
+ {0}
+ {aaaab}
+(2 rows)
+
+select * from test_regex('^a*b', 'aaaab', 'b');
+ test_regex
+------------
+ {0}
+ {aaaab}
+(2 rows)
+
+-- expectMatch 30.6 &M {[0-6][1-2][0-3][0-6][1-6][0-6]} \
+-- "010010" 010010
+select * from test_regex('[0-6][1-2][0-3][0-6][1-6][0-6]', '010010', 'M');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+ {010010}
+(2 rows)
+
+select * from test_regex('[0-6][1-2][0-3][0-6][1-6][0-6]', '010010', 'Mb');
+ test_regex
+-----------------
+ {0,REG_UUNPORT}
+ {010010}
+(2 rows)
+
+-- # temporary REG_BOSONLY kludge
+-- expectMatch 30.7 s abc abcd abc
+select * from test_regex('abc', 'abcd', 's');
+ test_regex
+------------
+ {0}
+ {abc}
+(2 rows)
+
+-- expectNomatch 30.8 s abc xabcd
+select * from test_regex('abc', 'xabcd', 's');
+ test_regex
+------------
+ {0}
+(1 row)
+
+-- # back to normal stuff
+-- expectMatch 30.9 HLP {(?n)^(?![t#])\S+} \
+-- "tk\n\n#\n#\nit0" it0
+select * from test_regex('(?n)^(?![t#])\S+', E'tk\n\n#\n#\nit0', 'HLP');
+ test_regex
+-----------------------------------------------
+ {0,REG_ULOOKAROUND,REG_UNONPOSIX,REG_ULOCALE}
+ {it0}
+(2 rows)
+
+-- # Now for tests *not* written by Henry Spencer
+-- # Tests resulting from bugs reported by users
+-- test reg-31.1 {[[:xdigit:]] behaves correctly when followed by [[:space:]]} {
+-- set str {2:::DebugWin32}
+-- set re {([[:xdigit:]])([[:space:]]*)}
+-- list [regexp $re $str match xdigit spaces] $match $xdigit $spaces
+-- # Code used to produce {1 2:::DebugWin32 2 :::DebugWin32} !!!
+-- } {1 2 2 {}}
+select * from test_regex('([[:xdigit:]])([[:space:]]*)', '2:::DebugWin32', 'L');
+ test_regex
+-----------------
+ {2,REG_ULOCALE}
+ {2,2,""}
+(2 rows)
+
+-- test reg-32.1 {canmatch functionality -- at end} testregexp {
+-- set pat {blah}
+-- set line "asd asd"
+-- # can match at the final d, if '%' follows
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 7}
+select * from test_regex('blah', 'asd asd', 'c');
+ test_regex
+---------------
+ {0}
+ {"7 6","7 6"}
+(2 rows)
+
+-- test reg-32.2 {canmatch functionality -- at end} testregexp {
+-- set pat {s%$}
+-- set line "asd asd"
+-- # can only match after the end of the string
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 7}
+select * from test_regex('s%$', 'asd asd', 'c');
+ test_regex
+---------------
+ {0}
+ {"7 6","7 6"}
+(2 rows)
+
+-- test reg-32.3 {canmatch functionality -- not last char} testregexp {
+-- set pat {[^d]%$}
+-- set line "asd asd"
+-- # can only match after the end of the string
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 7}
+select * from test_regex('[^d]%$', 'asd asd', 'c');
+ test_regex
+---------------
+ {0}
+ {"7 6","7 6"}
+(2 rows)
+
+-- test reg-32.3.1 {canmatch functionality -- no match} testregexp {
+-- set pat {\Zx}
+-- set line "asd asd"
+-- # can match the last char, if followed by x
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 -1}
+select * from test_regex('\Zx', 'asd asd', 'cIP');
+ test_regex
+-----------------------------------
+ {0,REG_UNONPOSIX,REG_UIMPOSSIBLE}
+ {"-1 -1","-1 -1"}
+(2 rows)
+
+-- test reg-32.4 {canmatch functionality -- last char} {knownBug testregexp} {
+-- set pat {.x}
+-- set line "asd asd"
+-- # can match the last char, if followed by x
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('.x', 'asd asd', 'c');
+ test_regex
+---------------
+ {0}
+ {"0 6","0 6"}
+(2 rows)
+
+-- test reg-32.4.1 {canmatch functionality -- last char} {knownBug testregexp} {
+-- set pat {.x$}
+-- set line "asd asd"
+-- # can match the last char, if followed by x
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('.x$', 'asd asd', 'c');
+ test_regex
+---------------
+ {0}
+ {"0 6","0 6"}
+(2 rows)
+
+-- test reg-32.5 {canmatch functionality -- last char} {knownBug testregexp} {
+-- set pat {.[^d]x$}
+-- set line "asd asd"
+-- # can match the last char, if followed by not-d and x.
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('.[^d]x$', 'asd asd', 'c');
+ test_regex
+---------------
+ {0}
+ {"0 6","0 6"}
+(2 rows)
+
+-- test reg-32.6 {canmatch functionality -- last char} {knownBug testregexp} {
+-- set pat {[^a]%[^\r\n]*$}
+-- set line "asd asd"
+-- # can match at the final d, if '%' follows
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('[^a]%[^\r\n]*$', 'asd asd', 'cEP');
+ test_regex
+----------------------------
+ {0,REG_UBBS,REG_UNONPOSIX}
+ {"5 6","5 6"}
+(2 rows)
+
+-- test reg-32.7 {canmatch functionality -- last char} {knownBug testregexp} {
+-- set pat {[^a]%$}
+-- set line "asd asd"
+-- # can match at the final d, if '%' follows
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('[^a]%$', 'asd asd', 'c');
+ test_regex
+---------------
+ {0}
+ {"5 6","5 6"}
+(2 rows)
+
+-- test reg-32.8 {canmatch functionality -- last char} {knownBug testregexp} {
+-- set pat {[^x]%$}
+-- set line "asd asd"
+-- # can match at the final d, if '%' follows
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('[^x]%$', 'asd asd', 'c');
+ test_regex
+---------------
+ {0}
+ {"0 6","0 6"}
+(2 rows)
+
+-- test reg-32.9 {canmatch functionality -- more complex case} {knownBug testregexp} {
+-- set pat {((\B\B|\Bh+line)[ \t]*|[^\B]%[^\r\n]*)$}
+-- set line "asd asd"
+-- # can match at the final d, if '%' follows
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('((\B\B|\Bh+line)[ \t]*|[^\B]%[^\r\n]*)$', 'asd asd', 'cEP');
+ test_regex
+-------------------------------
+ {2,REG_UBBS,REG_UNONPOSIX}
+ {"0 6","-1 -1","-1 -1","0 6"}
+(2 rows)
+
+-- # Tests reg-33.*: Checks for bug fixes
+-- test reg-33.1 {Bug 230589} {
+-- regexp {[ ]*(^|[^%])%V} "*%V2" m s
+-- } 1
+select * from test_regex('[ ]*(^|[^%])%V', '*%V2', '-');
+ test_regex
+------------
+ {1}
+ {*%V,*}
+(2 rows)
+
+-- test reg-33.2 {Bug 504785} {
+-- regexp -inline {([^_.]*)([^.]*)\.(..)(.).*} bbcos_001_c01.q1la
+-- } {bbcos_001_c01.q1la bbcos _001_c01 q1 l}
+select * from test_regex('([^_.]*)([^.]*)\.(..)(.).*', 'bbcos_001_c01.q1la', '-');
+ test_regex
+------------------------------------------
+ {4}
+ {bbcos_001_c01.q1la,bbcos,_001_c01,q1,l}
+(2 rows)
+
+-- test reg-33.3 {Bug 505048} {
+-- regexp {\A\s*[^<]*\s*<([^>]+)>} a<a>
+-- } 1
+select * from test_regex('\A\s*[^<]*\s*<([^>]+)>', 'a<a>', 'LP');
+ test_regex
+-------------------------------
+ {1,REG_UNONPOSIX,REG_ULOCALE}
+ {a<a>,a}
+(2 rows)
+
+-- test reg-33.4 {Bug 505048} {
+-- regexp {\A\s*([^b]*)b} ab
+-- } 1
+select * from test_regex('\A\s*([^b]*)b', 'ab', 'LP');
+ test_regex
+-------------------------------
+ {1,REG_UNONPOSIX,REG_ULOCALE}
+ {ab,a}
+(2 rows)
+
+-- test reg-33.5 {Bug 505048} {
+-- regexp {\A\s*[^b]*(b)} ab
+-- } 1
+select * from test_regex('\A\s*[^b]*(b)', 'ab', 'LP');
+ test_regex
+-------------------------------
+ {1,REG_UNONPOSIX,REG_ULOCALE}
+ {ab,b}
+(2 rows)
+
+-- test reg-33.6 {Bug 505048} {
+-- regexp {\A(\s*)[^b]*(b)} ab
+-- } 1
+select * from test_regex('\A(\s*)[^b]*(b)', 'ab', 'LP');
+ test_regex
+-------------------------------
+ {2,REG_UNONPOSIX,REG_ULOCALE}
+ {ab,"",b}
+(2 rows)
+
+-- test reg-33.7 {Bug 505048} {
+-- regexp {\A\s*[^b]*b} ab
+-- } 1
+select * from test_regex('\A\s*[^b]*b', 'ab', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {ab}
+(2 rows)
+
+-- test reg-33.8 {Bug 505048} {
+-- regexp -inline {\A\s*[^b]*b} ab
+-- } ab
+select * from test_regex('\A\s*[^b]*b', 'ab', 'LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {ab}
+(2 rows)
+
+-- test reg-33.9 {Bug 505048} {
+-- regexp -indices -inline {\A\s*[^b]*b} ab
+-- } {{0 1}}
+select * from test_regex('\A\s*[^b]*b', 'ab', '0LP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE}
+ {"0 1"}
+(2 rows)
+
+-- test reg-33.10 {Bug 840258} -body {
+-- regsub {(^|\n)+\.*b} \n.b {} tmp
+-- } -cleanup {
+-- unset tmp
+-- } -result 1
+select * from test_regex('(^|\n)+\.*b', E'\n.b', 'P');
+ test_regex
+-------------------
+ {1,REG_UNONPOSIX}
+ {" +
+ .b"," +
+ "}
+(2 rows)
+
+-- test reg-33.11 {Bug 840258} -body {
+-- regsub {(^|[\n\r]+)\.*\?<.*?(\n|\r)+} \
+-- "TQ\r\n.?<5000267>Test already stopped\r\n" {} tmp
+-- } -cleanup {
+-- unset tmp
+-- } -result 1
+select * from test_regex('(^|[\n\r]+)\.*\?<.*?(\n|\r)+', E'TQ\r\n.?<5000267>Test already stopped\r\n', 'EP');
+ test_regex
+-----------------------------------
+ {2,REG_UBBS,REG_UNONPOSIX}
+ {"\r +
+ .?<5000267>Test already stopped\r+
+ ","\r +
+ "," +
+ "}
+(2 rows)
+
+-- test reg-33.12 {Bug 1810264 - bad read} {
+-- regexp {\3161573148} {\3161573148}
+-- } 0
+select * from test_regex('\3161573148', '\3161573148', 'MP');
+ test_regex
+-------------------------------
+ {0,REG_UNONPOSIX,REG_UUNPORT}
+(1 row)
+
+-- test reg-33.13 {Bug 1810264 - infinite loop} {
+-- regexp {($|^)*} {x}
+-- } 1
+select * from test_regex('($|^)*', 'x', 'N');
+ test_regex
+---------------------
+ {1,REG_UEMPTYMATCH}
+ {"",""}
+(2 rows)
+
+-- # Some environments have small default stack sizes. [Bug 1905562]
+-- test reg-33.14 {Bug 1810264 - super-expensive expression} nonPortable {
+-- regexp {(x{200}){200}$y} {x}
+-- } 0
+-- This might or might not work depending on platform, so skip it
+-- select * from test_regex('(x{200}){200}$y', 'x', 'IQ');
+-- test reg-33.15.1 {Bug 3603557 - an "in the wild" RE} {
+-- lindex [regexp -expanded -about {
+-- ^TETRA_MODE_CMD # Message Type
+-- ([[:blank:]]+) # Pad
+-- (ETS_1_1|ETS_1_2|ETS_2_2) # SystemCode
+-- ([[:blank:]]+) # Pad
+-- (CONTINUOUS|CARRIER|MCCH|TRAFFIC) # SharingMode
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,2}) # ColourCode
+-- ([[:blank:]]+) # Pad
+-- (1|2|3|4|6|9|12|18) # TSReservedFrames
+-- ([[:blank:]]+) # Pad
+-- (PASS|TRUE|FAIL|FALSE) # UPlaneDTX
+-- ([[:blank:]]+) # Pad
+-- (PASS|TRUE|FAIL|FALSE) # Frame18Extension
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,4}) # MCC
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,5}) # MNC
+-- ([[:blank:]]+) # Pad
+-- (BOTH|BCAST|ENQRY|NONE) # NbrCellBcast
+-- ([[:blank:]]+) # Pad
+-- (UNKNOWN|LOW|MEDIUM|HIGH) # CellServiceLevel
+-- ([[:blank:]]+) # Pad
+-- (PASS|TRUE|FAIL|FALSE) # LateEntryInfo
+-- ([[:blank:]]+) # Pad
+-- (300|400) # FrequencyBand
+-- ([[:blank:]]+) # Pad
+-- (NORMAL|REVERSE) # ReverseOperation
+-- ([[:blank:]]+) # Pad
+-- (NONE|\+6\.25|\-6\.25|\+12\.5) # Offset
+-- ([[:blank:]]+) # Pad
+-- (10) # DuplexSpacing
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,4}) # MainCarrierNr
+-- ([[:blank:]]+) # Pad
+-- (0|1|2|3) # NrCSCCH
+-- ([[:blank:]]+) # Pad
+-- (15|20|25|30|35|40|45) # MSTxPwrMax
+-- ([[:blank:]]+) # Pad
+-- (\-125|\-120|\-115|\-110|\-105|\-100|\-95|\-90|\-85|\-80|\-75|\-70|\-65|\-60|\-55|\-50)
+-- # RxLevAccessMin
+-- ([[:blank:]]+) # Pad
+-- (\-53|\-51|\-49|\-47|\-45|\-43|\-41|\-39|\-37|\-35|\-33|\-31|\-29|\-27|\-25|\-23)
+-- # AccessParameter
+-- ([[:blank:]]+) # Pad
+-- (DISABLE|[[:digit:]]{3,4}) # RadioDLTimeout
+-- ([[:blank:]]+) # Pad
+-- (\-[[:digit:]]{2,3}) # RSSIThreshold
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,5}) # CCKIdSCKVerNr
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,5}) # LocationArea
+-- ([[:blank:]]+) # Pad
+-- ([(1|0)]{16}) # SubscriberClass
+-- ([[:blank:]]+) # Pad
+-- ([(1|0)]{12}) # BSServiceDetails
+-- ([[:blank:]]+) # Pad
+-- (RANDOMIZE|IMMEDIATE|[[:digit:]]{1,2}) # IMM
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,2}) # WT
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,2}) # Nu
+-- ([[:blank:]]+) # Pad
+-- ([0-1]) # FrameLngFctr
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,2}) # TSPtr
+-- ([[:blank:]]+) # Pad
+-- ([0-7]) # MinPriority
+-- ([[:blank:]]+) # Pad
+-- (PASS|TRUE|FAIL|FALSE) # ExtdSrvcsEnabled
+-- ([[:blank:]]+) # Pad
+-- (.*) # ConditionalFields
+-- }] 0
+-- } 68
+select * from test_regex($$
+ ^TETRA_MODE_CMD # Message Type
+ ([[:blank:]]+) # Pad
+ (ETS_1_1|ETS_1_2|ETS_2_2) # SystemCode
+ ([[:blank:]]+) # Pad
+ (CONTINUOUS|CARRIER|MCCH|TRAFFIC) # SharingMode
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,2}) # ColourCode
+ ([[:blank:]]+) # Pad
+ (1|2|3|4|6|9|12|18) # TSReservedFrames
+ ([[:blank:]]+) # Pad
+ (PASS|TRUE|FAIL|FALSE) # UPlaneDTX
+ ([[:blank:]]+) # Pad
+ (PASS|TRUE|FAIL|FALSE) # Frame18Extension
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,4}) # MCC
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,5}) # MNC
+ ([[:blank:]]+) # Pad
+ (BOTH|BCAST|ENQRY|NONE) # NbrCellBcast
+ ([[:blank:]]+) # Pad
+ (UNKNOWN|LOW|MEDIUM|HIGH) # CellServiceLevel
+ ([[:blank:]]+) # Pad
+ (PASS|TRUE|FAIL|FALSE) # LateEntryInfo
+ ([[:blank:]]+) # Pad
+ (300|400) # FrequencyBand
+ ([[:blank:]]+) # Pad
+ (NORMAL|REVERSE) # ReverseOperation
+ ([[:blank:]]+) # Pad
+ (NONE|\+6\.25|\-6\.25|\+12\.5) # Offset
+ ([[:blank:]]+) # Pad
+ (10) # DuplexSpacing
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,4}) # MainCarrierNr
+ ([[:blank:]]+) # Pad
+ (0|1|2|3) # NrCSCCH
+ ([[:blank:]]+) # Pad
+ (15|20|25|30|35|40|45) # MSTxPwrMax
+ ([[:blank:]]+) # Pad
+ (\-125|\-120|\-115|\-110|\-105|\-100|\-95|\-90|\-85|\-80|\-75|\-70|\-65|\-60|\-55|\-50)
+ # RxLevAccessMin
+ ([[:blank:]]+) # Pad
+ (\-53|\-51|\-49|\-47|\-45|\-43|\-41|\-39|\-37|\-35|\-33|\-31|\-29|\-27|\-25|\-23)
+ # AccessParameter
+ ([[:blank:]]+) # Pad
+ (DISABLE|[[:digit:]]{3,4}) # RadioDLTimeout
+ ([[:blank:]]+) # Pad
+ (\-[[:digit:]]{2,3}) # RSSIThreshold
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,5}) # CCKIdSCKVerNr
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,5}) # LocationArea
+ ([[:blank:]]+) # Pad
+ ([(1|0)]{16}) # SubscriberClass
+ ([[:blank:]]+) # Pad
+ ([(1|0)]{12}) # BSServiceDetails
+ ([[:blank:]]+) # Pad
+ (RANDOMIZE|IMMEDIATE|[[:digit:]]{1,2}) # IMM
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,2}) # WT
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,2}) # Nu
+ ([[:blank:]]+) # Pad
+ ([0-1]) # FrameLngFctr
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,2}) # TSPtr
+ ([[:blank:]]+) # Pad
+ ([0-7]) # MinPriority
+ ([[:blank:]]+) # Pad
+ (PASS|TRUE|FAIL|FALSE) # ExtdSrvcsEnabled
+ ([[:blank:]]+) # Pad
+ (.*) # ConditionalFields
+ $$, '', 'xLMPQ');
+ test_regex
+--------------------------------------------------------
+ {68,REG_UBOUNDS,REG_UNONPOSIX,REG_UUNPORT,REG_ULOCALE}
+(1 row)
+
+-- test reg-33.16.1 {Bug [8d2c0da36d]- another "in the wild" RE} {
+-- lindex [regexp -about "^MRK:client1: =1339 14HKelly Talisman 10011000 (\[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]*) \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 8 0 8 0 0 0 77 77 1 1 2 0 11 { 1 3 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 13HC6 My Creator 2 3 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 31HC7 Slightly offensive name, huh 3 8 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 23HE-mail:kelly@hotbox.com 4 9 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 17Hcompface must die 5 10 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 0 3HAir 6 12 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 14HPGP public key 7 13 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 16Hkelly@hotbox.com 8 30 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 0 12H2 text/plain 9 30 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 0 13H2 x-kom/basic 10 33 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 1H0 11 14 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 1H3 }\r?"] 0
+-- } 1
+select * from test_regex(E'^MRK:client1: =1339 14HKelly Talisman 10011000 ([0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]*) [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 8 0 8 0 0 0 77 77 1 1 2 0 11 { 1 3 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 13HC6 My Creator 2 3 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 31HC7 Slightly offensive name, huh 3 8 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 23HE-mail:kelly@hotbox.com 4 9 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 17Hcompface must die 5 10 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 0 3HAir 6 12 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 14HPGP public key 7 13 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 16Hkelly@hotbox.com 8 30 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 0 12H2 text/plain 9 30 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 0 13H2 x-kom/basic 10 33 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 1H0 11 14 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 1H3 }\r?', '', 'BMS');
+ test_regex
+-----------------------------------------
+ {1,REG_UBRACES,REG_UUNSPEC,REG_UUNPORT}
+(1 row)
+
+-- test reg-33.15 {constraint fixes} {
+-- regexp {(^)+^} x
+-- } 1
+select * from test_regex('(^)+^', 'x', 'N');
+ test_regex
+---------------------
+ {1,REG_UEMPTYMATCH}
+ {"",""}
+(2 rows)
+
+-- test reg-33.16 {constraint fixes} {
+-- regexp {($^)+} x
+-- } 0
+select * from test_regex('($^)+', 'x', 'N');
+ test_regex
+---------------------
+ {1,REG_UEMPTYMATCH}
+(1 row)
+
+-- test reg-33.17 {constraint fixes} {
+-- regexp {(^$)*} x
+-- } 1
+select * from test_regex('(^$)*', 'x', 'N');
+ test_regex
+---------------------
+ {1,REG_UEMPTYMATCH}
+ {"",NULL}
+(2 rows)
+
+-- test reg-33.18 {constraint fixes} {
+-- regexp {(^(?!aa))+} {aa bb cc}
+-- } 0
+select * from test_regex('(^(?!aa))+', 'aa bb cc', 'HP');
+ test_regex
+-----------------------------------
+ {1,REG_ULOOKAROUND,REG_UNONPOSIX}
+(1 row)
+
+-- test reg-33.19 {constraint fixes} {
+-- regexp {(^(?!aa)(?!bb)(?!cc))+} {aa x}
+-- } 0
+select * from test_regex('(^(?!aa)(?!bb)(?!cc))+', 'aa x', 'HP');
+ test_regex
+-----------------------------------
+ {1,REG_ULOOKAROUND,REG_UNONPOSIX}
+(1 row)
+
+-- test reg-33.20 {constraint fixes} {
+-- regexp {(^(?!aa)(?!bb)(?!cc))+} {bb x}
+-- } 0
+select * from test_regex('(^(?!aa)(?!bb)(?!cc))+', 'bb x', 'HP');
+ test_regex
+-----------------------------------
+ {1,REG_ULOOKAROUND,REG_UNONPOSIX}
+(1 row)
+
+-- test reg-33.21 {constraint fixes} {
+-- regexp {(^(?!aa)(?!bb)(?!cc))+} {cc x}
+-- } 0
+select * from test_regex('(^(?!aa)(?!bb)(?!cc))+', 'cc x', 'HP');
+ test_regex
+-----------------------------------
+ {1,REG_ULOOKAROUND,REG_UNONPOSIX}
+(1 row)
+
+-- test reg-33.22 {constraint fixes} {
+-- regexp {(^(?!aa)(?!bb)(?!cc))+} {dd x}
+-- } 1
+select * from test_regex('(^(?!aa)(?!bb)(?!cc))+', 'dd x', 'HP');
+ test_regex
+-----------------------------------
+ {1,REG_ULOOKAROUND,REG_UNONPOSIX}
+ {"",""}
+(2 rows)
+
+-- test reg-33.23 {} {
+-- regexp {abcd(\m)+xyz} x
+-- } 0
+select * from test_regex('abcd(\m)+xyz', 'x', 'ILP');
+ test_regex
+-----------------------------------------------
+ {1,REG_UNONPOSIX,REG_ULOCALE,REG_UIMPOSSIBLE}
+(1 row)
+
+-- test reg-33.24 {} {
+-- regexp {abcd(\m)+xyz} a
+-- } 0
+select * from test_regex('abcd(\m)+xyz', 'a', 'ILP');
+ test_regex
+-----------------------------------------------
+ {1,REG_UNONPOSIX,REG_ULOCALE,REG_UIMPOSSIBLE}
+(1 row)
+
+-- test reg-33.25 {} {
+-- regexp {^abcd*(((((^(a c(e?d)a+|)+|)+|)+|)+|a)+|)} x
+-- } 0
+select * from test_regex('^abcd*(((((^(a c(e?d)a+|)+|)+|)+|)+|a)+|)', 'x', 'S');
+ test_regex
+-----------------
+ {7,REG_UUNSPEC}
+(1 row)
+
+-- test reg-33.26 {} {
+-- regexp {a^(^)bcd*xy(((((($a+|)+|)+|)+$|)+|)+|)^$} x
+-- } 0
+select * from test_regex('a^(^)bcd*xy(((((($a+|)+|)+|)+$|)+|)+|)^$', 'x', 'IS');
+ test_regex
+---------------------------------
+ {7,REG_UUNSPEC,REG_UIMPOSSIBLE}
+(1 row)
+
+-- test reg-33.27 {} {
+-- regexp {xyz(\Y\Y)+} x
+-- } 0
+select * from test_regex('xyz(\Y\Y)+', 'x', 'LP');
+ test_regex
+-------------------------------
+ {1,REG_UNONPOSIX,REG_ULOCALE}
+(1 row)
+
+-- test reg-33.28 {} {
+-- regexp {x|(?:\M)+} x
+-- } 1
+select * from test_regex('x|(?:\M)+', 'x', 'LNP');
+ test_regex
+-----------------------------------------------
+ {0,REG_UNONPOSIX,REG_ULOCALE,REG_UEMPTYMATCH}
+ {x}
+(2 rows)
+
+-- test reg-33.29 {} {
+-- # This is near the limits of the RE engine
+-- regexp [string repeat x*y*z* 480] x
+-- } 1
+-- The runtime cost of this seems out of proportion to the value,
+-- so for Postgres purposes reduce the repeat to 200x
+select * from test_regex(repeat('x*y*z*', 200), 'x', 'N');
+ test_regex
+---------------------
+ {0,REG_UEMPTYMATCH}
+ {x}
+(2 rows)
+
+-- test reg-33.30 {Bug 1080042} {
+-- regexp {(\Y)+} foo
+-- } 1
+select * from test_regex('(\Y)+', 'foo', 'LNP');
+ test_regex
+-----------------------------------------------
+ {1,REG_UNONPOSIX,REG_ULOCALE,REG_UEMPTYMATCH}
+ {"",""}
+(2 rows)
+
diff --git a/src/test/modules/test_regex/expected/test_regex_utf8.out b/src/test/modules/test_regex/expected/test_regex_utf8.out
new file mode 100644
index 0000000..112698a
--- /dev/null
+++ b/src/test/modules/test_regex/expected/test_regex_utf8.out
@@ -0,0 +1,100 @@
+/*
+ * This test must be run in a database with UTF-8 encoding,
+ * because other encodings don't support all the characters used.
+ */
+SELECT getdatabaseencoding() <> 'UTF8'
+ AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+set client_encoding = utf8;
+set standard_conforming_strings = on;
+-- Run the Tcl test cases that require Unicode
+-- expectMatch 9.44 EMP* {a[\u00fe-\u0507][\u00ff-\u0300]b} \
+-- "a\u0102\u02ffb" "a\u0102\u02ffb"
+select * from test_regex('a[\u00fe-\u0507][\u00ff-\u0300]b', E'a\u0102\u02ffb', 'EMP*');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_UUNPORT}
+ {aĂ˿b}
+(2 rows)
+
+-- expectMatch 13.27 P "a\\U00001234x" "a\u1234x" "a\u1234x"
+select * from test_regex('a\U00001234x', E'a\u1234x', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {aሴx}
+(2 rows)
+
+-- expectMatch 13.28 P {a\U00001234x} "a\u1234x" "a\u1234x"
+select * from test_regex('a\U00001234x', E'a\u1234x', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {aሴx}
+(2 rows)
+
+-- expectMatch 13.29 P "a\\U0001234x" "a\u1234x" "a\u1234x"
+-- Tcl has relaxed their code to allow 1-8 hex digits, but Postgres hasn't
+select * from test_regex('a\U0001234x', E'a\u1234x', 'P');
+ERROR: invalid regular expression: invalid escape \ sequence
+-- expectMatch 13.30 P {a\U0001234x} "a\u1234x" "a\u1234x"
+-- Tcl has relaxed their code to allow 1-8 hex digits, but Postgres hasn't
+select * from test_regex('a\U0001234x', E'a\u1234x', 'P');
+ERROR: invalid regular expression: invalid escape \ sequence
+-- expectMatch 13.31 P "a\\U000012345x" "a\u12345x" "a\u12345x"
+select * from test_regex('a\U000012345x', E'a\u12345x', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {aሴ5x}
+(2 rows)
+
+-- expectMatch 13.32 P {a\U000012345x} "a\u12345x" "a\u12345x"
+select * from test_regex('a\U000012345x', E'a\u12345x', 'P');
+ test_regex
+-------------------
+ {0,REG_UNONPOSIX}
+ {aሴ5x}
+(2 rows)
+
+-- expectMatch 13.33 P "a\\U1000000x" "a\ufffd0x" "a\ufffd0x"
+-- Tcl allows this as a standalone character, but Postgres doesn't
+select * from test_regex('a\U1000000x', E'a\ufffd0x', 'P');
+ERROR: invalid regular expression: invalid escape \ sequence
+-- expectMatch 13.34 P {a\U1000000x} "a\ufffd0x" "a\ufffd0x"
+-- Tcl allows this as a standalone character, but Postgres doesn't
+select * from test_regex('a\U1000000x', E'a\ufffd0x', 'P');
+ERROR: invalid regular expression: invalid escape \ sequence
+-- Additional tests, not derived from Tcl
+-- Exercise logic around high character ranges a bit more
+select * from test_regex('a
+ [\u1000-\u1100]*
+ [\u3000-\u3100]*
+ [\u1234-\u25ff]+
+ [\u2000-\u35ff]*
+ [\u2600-\u2f00]*
+ \u1236\u1236x',
+ E'a\u1234\u1236\u1236x', 'xEMP');
+ test_regex
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_UUNPORT}
+ {aሴሶሶx}
+(2 rows)
+
+select * from test_regex('[[:alnum:]]*[[:upper:]]*[\u1000-\u2000]*\u1237',
+ E'\u1500\u1237', 'ELMP');
+ test_regex
+----------------------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_UUNPORT,REG_ULOCALE}
+ {ᔀሷ}
+(2 rows)
+
+select * from test_regex('[[:alnum:]]*[[:upper:]]*[\u1000-\u2000]*\u1237',
+ E'A\u1239', 'ELMP');
+ test_regex
+----------------------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_UUNPORT,REG_ULOCALE}
+(1 row)
+
diff --git a/src/test/modules/test_regex/expected/test_regex_utf8_1.out b/src/test/modules/test_regex/expected/test_regex_utf8_1.out
new file mode 100644
index 0000000..37aead8
--- /dev/null
+++ b/src/test/modules/test_regex/expected/test_regex_utf8_1.out
@@ -0,0 +1,8 @@
+/*
+ * This test must be run in a database with UTF-8 encoding,
+ * because other encodings don't support all the characters used.
+ */
+SELECT getdatabaseencoding() <> 'UTF8'
+ AS skip_test \gset
+\if :skip_test
+\quit
diff --git a/src/test/modules/test_regex/sql/test_regex.sql b/src/test/modules/test_regex/sql/test_regex.sql
new file mode 100644
index 0000000..bdc0901
--- /dev/null
+++ b/src/test/modules/test_regex/sql/test_regex.sql
@@ -0,0 +1,1744 @@
+-- This file is based on tests/reg.test from the Tcl distribution,
+-- which is marked
+-- # Copyright (c) 1998, 1999 Henry Spencer. All rights reserved.
+-- The full copyright notice can be found in src/backend/regex/COPYRIGHT.
+-- Most commented lines below are copied from reg.test. Each
+-- test case is followed by an equivalent test using test_regex().
+
+create extension test_regex;
+
+set standard_conforming_strings = on;
+
+-- # support functions and preliminary misc.
+-- # This is sensitive to changes in message wording, but we really have to
+-- # test the code->message expansion at least once.
+-- ::tcltest::test reg-0.1 "regexp error reporting" {
+-- list [catch {regexp (*) ign} msg] $msg
+-- } {1 {couldn't compile regular expression pattern: quantifier operand invalid}}
+select * from test_regex('(*)', '', '');
+
+-- doing 1 "basic sanity checks"
+
+-- expectMatch 1.1 & abc abc abc
+select * from test_regex('abc', 'abc', '');
+select * from test_regex('abc', 'abc', 'b');
+-- expectNomatch 1.2 & abc def
+select * from test_regex('abc', 'def', '');
+select * from test_regex('abc', 'def', 'b');
+-- expectMatch 1.3 & abc xyabxabce abc
+select * from test_regex('abc', 'xyabxabce', '');
+select * from test_regex('abc', 'xyabxabce', 'b');
+
+-- doing 2 "invalid option combinations"
+
+-- expectError 2.1 qe a INVARG
+select * from test_regex('a', '', 'qe');
+-- expectError 2.2 qa a INVARG
+select * from test_regex('a', '', 'qa');
+-- expectError 2.3 qx a INVARG
+select * from test_regex('a', '', 'qx');
+-- expectError 2.4 qn a INVARG
+select * from test_regex('a', '', 'qn');
+-- expectError 2.5 ba a INVARG
+select * from test_regex('a', '', 'ba');
+
+-- doing 3 "basic syntax"
+
+-- expectIndices 3.1 &NS "" a {0 -1}
+select * from test_regex('', 'a', '0NS');
+select * from test_regex('', 'a', '0NSb');
+-- expectMatch 3.2 NS a| a a
+select * from test_regex('a|', 'a', 'NS');
+-- expectMatch 3.3 - a|b a a
+select * from test_regex('a|b', 'a', '-');
+-- expectMatch 3.4 - a|b b b
+select * from test_regex('a|b', 'b', '-');
+-- expectMatch 3.5 NS a||b b b
+select * from test_regex('a||b', 'b', 'NS');
+-- expectMatch 3.6 & ab ab ab
+select * from test_regex('ab', 'ab', '');
+select * from test_regex('ab', 'ab', 'b');
+
+-- doing 4 "parentheses"
+
+-- expectMatch 4.1 - (a)e ae ae a
+select * from test_regex('(a)e', 'ae', '-');
+-- expectMatch 4.2 o (a)e ae
+select * from test_regex('(a)e', 'ae', 'o');
+-- expectMatch 4.3 b {\(a\)b} ab ab a
+select * from test_regex('\(a\)b', 'ab', 'b');
+-- expectMatch 4.4 - a((b)c) abc abc bc b
+select * from test_regex('a((b)c)', 'abc', '-');
+-- expectMatch 4.5 - a(b)(c) abc abc b c
+select * from test_regex('a(b)(c)', 'abc', '-');
+-- expectError 4.6 - a(b EPAREN
+select * from test_regex('a(b', '', '-');
+-- expectError 4.7 b {a\(b} EPAREN
+select * from test_regex('a\(b', '', 'b');
+-- # sigh, we blew it on the specs here... someday this will be fixed in POSIX,
+-- # but meanwhile, it's fixed in AREs
+-- expectMatch 4.8 eU a)b a)b a)b
+select * from test_regex('a)b', 'a)b', 'eU');
+-- expectError 4.9 - a)b EPAREN
+select * from test_regex('a)b', '', '-');
+-- expectError 4.10 b {a\)b} EPAREN
+select * from test_regex('a\)b', '', 'b');
+-- expectMatch 4.11 P a(?:b)c abc abc
+select * from test_regex('a(?:b)c', 'abc', 'P');
+-- expectError 4.12 e a(?:b)c BADRPT
+select * from test_regex('a(?:b)c', '', 'e');
+-- expectIndices 4.13 S a()b ab {0 1} {1 0}
+select * from test_regex('a()b', 'ab', '0S');
+-- expectMatch 4.14 SP a(?:)b ab ab
+select * from test_regex('a(?:)b', 'ab', 'SP');
+-- expectIndices 4.15 S a(|b)c ac {0 1} {1 0}
+select * from test_regex('a(|b)c', 'ac', '0S');
+-- expectMatch 4.16 S a(b|)c abc abc b
+select * from test_regex('a(b|)c', 'abc', 'S');
+
+-- doing 5 "simple one-char matching"
+-- # general case of brackets done later
+
+-- expectMatch 5.1 & a.b axb axb
+select * from test_regex('a.b', 'axb', '');
+select * from test_regex('a.b', 'axb', 'b');
+-- expectNomatch 5.2 &n "a.b" "a\nb"
+select * from test_regex('a.b', E'a\nb', 'n');
+select * from test_regex('a.b', E'a\nb', 'nb');
+-- expectMatch 5.3 & {a[bc]d} abd abd
+select * from test_regex('a[bc]d', 'abd', '');
+select * from test_regex('a[bc]d', 'abd', 'b');
+-- expectMatch 5.4 & {a[bc]d} acd acd
+select * from test_regex('a[bc]d', 'acd', '');
+select * from test_regex('a[bc]d', 'acd', 'b');
+-- expectNomatch 5.5 & {a[bc]d} aed
+select * from test_regex('a[bc]d', 'aed', '');
+select * from test_regex('a[bc]d', 'aed', 'b');
+-- expectNomatch 5.6 & {a[^bc]d} abd
+select * from test_regex('a[^bc]d', 'abd', '');
+select * from test_regex('a[^bc]d', 'abd', 'b');
+-- expectMatch 5.7 & {a[^bc]d} aed aed
+select * from test_regex('a[^bc]d', 'aed', '');
+select * from test_regex('a[^bc]d', 'aed', 'b');
+-- expectNomatch 5.8 &p "a\[^bc]d" "a\nd"
+select * from test_regex('a[^bc]d', E'a\nd', 'p');
+select * from test_regex('a[^bc]d', E'a\nd', 'pb');
+
+-- doing 6 "context-dependent syntax"
+-- # plus odds and ends
+
+-- expectError 6.1 - * BADRPT
+select * from test_regex('*', '', '-');
+-- expectMatch 6.2 b * * *
+select * from test_regex('*', '*', 'b');
+-- expectMatch 6.3 b {\(*\)} * * *
+select * from test_regex('\(*\)', '*', 'b');
+-- expectError 6.4 - (*) BADRPT
+select * from test_regex('(*)', '', '-');
+-- expectMatch 6.5 b ^* * *
+select * from test_regex('^*', '*', 'b');
+-- expectError 6.6 - ^* BADRPT
+select * from test_regex('^*', '', '-');
+-- expectNomatch 6.7 & ^b ^b
+select * from test_regex('^b', '^b', '');
+select * from test_regex('^b', '^b', 'b');
+-- expectMatch 6.8 b x^ x^ x^
+select * from test_regex('x^', 'x^', 'b');
+-- expectNomatch 6.9 I x^ x
+select * from test_regex('x^', 'x', 'I');
+-- expectMatch 6.10 n "\n^" "x\nb" "\n"
+select * from test_regex(E'\n^', E'x\nb', 'n');
+-- expectNomatch 6.11 bS {\(^b\)} ^b
+select * from test_regex('\(^b\)', '^b', 'bS');
+-- expectMatch 6.12 - (^b) b b b
+select * from test_regex('(^b)', 'b', '-');
+-- expectMatch 6.13 & {x$} x x
+select * from test_regex('x$', 'x', '');
+select * from test_regex('x$', 'x', 'b');
+-- expectMatch 6.14 bS {\(x$\)} x x x
+select * from test_regex('\(x$\)', 'x', 'bS');
+-- expectMatch 6.15 - {(x$)} x x x
+select * from test_regex('(x$)', 'x', '-');
+-- expectMatch 6.16 b {x$y} "x\$y" "x\$y"
+select * from test_regex('x$y', 'x$y', 'b');
+-- expectNomatch 6.17 I {x$y} xy
+select * from test_regex('x$y', 'xy', 'I');
+-- expectMatch 6.18 n "x\$\n" "x\n" "x\n"
+select * from test_regex(E'x$\n', E'x\n', 'n');
+-- expectError 6.19 - + BADRPT
+select * from test_regex('+', '', '-');
+-- expectError 6.20 - ? BADRPT
+select * from test_regex('?', '', '-');
+
+-- These two are not yet incorporated in Tcl, cf
+-- https://core.tcl-lang.org/tcl/tktview?name=5ea71fdcd3291c38
+-- expectError 6.21 - {x(\w)(?=(\1))} ESUBREG
+select * from test_regex('x(\w)(?=(\1))', '', '-');
+-- expectMatch 6.22 HP {x(?=((foo)))} xfoo x
+select * from test_regex('x(?=((foo)))', 'xfoo', 'HP');
+
+-- doing 7 "simple quantifiers"
+
+-- expectMatch 7.1 &N a* aa aa
+select * from test_regex('a*', 'aa', 'N');
+select * from test_regex('a*', 'aa', 'Nb');
+-- expectIndices 7.2 &N a* b {0 -1}
+select * from test_regex('a*', 'b', '0N');
+select * from test_regex('a*', 'b', '0Nb');
+-- expectMatch 7.3 - a+ aa aa
+select * from test_regex('a+', 'aa', '-');
+-- expectMatch 7.4 - a?b ab ab
+select * from test_regex('a?b', 'ab', '-');
+-- expectMatch 7.5 - a?b b b
+select * from test_regex('a?b', 'b', '-');
+-- expectError 7.6 - ** BADRPT
+select * from test_regex('**', '', '-');
+-- expectMatch 7.7 bN ** *** ***
+select * from test_regex('**', '***', 'bN');
+-- expectError 7.8 & a** BADRPT
+select * from test_regex('a**', '', '');
+select * from test_regex('a**', '', 'b');
+-- expectError 7.9 & a**b BADRPT
+select * from test_regex('a**b', '', '');
+select * from test_regex('a**b', '', 'b');
+-- expectError 7.10 & *** BADRPT
+select * from test_regex('***', '', '');
+select * from test_regex('***', '', 'b');
+-- expectError 7.11 - a++ BADRPT
+select * from test_regex('a++', '', '-');
+-- expectError 7.12 - a?+ BADRPT
+select * from test_regex('a?+', '', '-');
+-- expectError 7.13 - a?* BADRPT
+select * from test_regex('a?*', '', '-');
+-- expectError 7.14 - a+* BADRPT
+select * from test_regex('a+*', '', '-');
+-- expectError 7.15 - a*+ BADRPT
+select * from test_regex('a*+', '', '-');
+-- tests for ancient brenext() bugs; not currently in Tcl
+select * from test_regex('.*b', 'aaabbb', 'b');
+select * from test_regex('.\{1,10\}', 'abcdef', 'bQ');
+
+-- doing 8 "braces"
+
+-- expectMatch 8.1 NQ "a{0,1}" "" ""
+select * from test_regex('a{0,1}', '', 'NQ');
+-- expectMatch 8.2 NQ "a{0,1}" ac a
+select * from test_regex('a{0,1}', 'ac', 'NQ');
+-- expectError 8.3 - "a{1,0}" BADBR
+select * from test_regex('a{1,0}', '', '-');
+-- expectError 8.4 - "a{1,2,3}" BADBR
+select * from test_regex('a{1,2,3}', '', '-');
+-- expectError 8.5 - "a{257}" BADBR
+select * from test_regex('a{257}', '', '-');
+-- expectError 8.6 - "a{1000}" BADBR
+select * from test_regex('a{1000}', '', '-');
+-- expectError 8.7 - "a{1" EBRACE
+select * from test_regex('a{1', '', '-');
+-- expectError 8.8 - "a{1n}" BADBR
+select * from test_regex('a{1n}', '', '-');
+-- expectMatch 8.9 BS "a{b" "a\{b" "a\{b"
+select * from test_regex('a{b', 'a{b', 'BS');
+-- expectMatch 8.10 BS "a{" "a\{" "a\{"
+select * from test_regex('a{', 'a{', 'BS');
+-- expectMatch 8.11 bQ "a\\{0,1\\}b" cb b
+select * from test_regex('a\{0,1\}b', 'cb', 'bQ');
+-- expectError 8.12 b "a\\{0,1" EBRACE
+select * from test_regex('a\{0,1', '', 'b');
+-- expectError 8.13 - "a{0,1\\" BADBR
+select * from test_regex('a{0,1\', '', '-');
+-- expectMatch 8.14 Q "a{0}b" ab b
+select * from test_regex('a{0}b', 'ab', 'Q');
+-- expectMatch 8.15 Q "a{0,0}b" ab b
+select * from test_regex('a{0,0}b', 'ab', 'Q');
+-- expectMatch 8.16 Q "a{0,1}b" ab ab
+select * from test_regex('a{0,1}b', 'ab', 'Q');
+-- expectMatch 8.17 Q "a{0,2}b" b b
+select * from test_regex('a{0,2}b', 'b', 'Q');
+-- expectMatch 8.18 Q "a{0,2}b" aab aab
+select * from test_regex('a{0,2}b', 'aab', 'Q');
+-- expectMatch 8.19 Q "a{0,}b" aab aab
+select * from test_regex('a{0,}b', 'aab', 'Q');
+-- expectMatch 8.20 Q "a{1,1}b" aab ab
+select * from test_regex('a{1,1}b', 'aab', 'Q');
+-- expectMatch 8.21 Q "a{1,3}b" aaaab aaab
+select * from test_regex('a{1,3}b', 'aaaab', 'Q');
+-- expectNomatch 8.22 Q "a{1,3}b" b
+select * from test_regex('a{1,3}b', 'b', 'Q');
+-- expectMatch 8.23 Q "a{1,}b" aab aab
+select * from test_regex('a{1,}b', 'aab', 'Q');
+-- expectNomatch 8.24 Q "a{2,3}b" ab
+select * from test_regex('a{2,3}b', 'ab', 'Q');
+-- expectMatch 8.25 Q "a{2,3}b" aaaab aaab
+select * from test_regex('a{2,3}b', 'aaaab', 'Q');
+-- expectNomatch 8.26 Q "a{2,}b" ab
+select * from test_regex('a{2,}b', 'ab', 'Q');
+-- expectMatch 8.27 Q "a{2,}b" aaaab aaaab
+select * from test_regex('a{2,}b', 'aaaab', 'Q');
+
+-- doing 9 "brackets"
+
+-- expectMatch 9.1 & {a[bc]} ac ac
+select * from test_regex('a[bc]', 'ac', '');
+select * from test_regex('a[bc]', 'ac', 'b');
+-- expectMatch 9.2 & {a[-]} a- a-
+select * from test_regex('a[-]', 'a-', '');
+select * from test_regex('a[-]', 'a-', 'b');
+-- expectMatch 9.3 & {a[[.-.]]} a- a-
+select * from test_regex('a[[.-.]]', 'a-', '');
+select * from test_regex('a[[.-.]]', 'a-', 'b');
+-- expectMatch 9.4 &L {a[[.zero.]]} a0 a0
+select * from test_regex('a[[.zero.]]', 'a0', 'L');
+select * from test_regex('a[[.zero.]]', 'a0', 'Lb');
+-- expectMatch 9.5 &LM {a[[.zero.]-9]} a2 a2
+select * from test_regex('a[[.zero.]-9]', 'a2', 'LM');
+select * from test_regex('a[[.zero.]-9]', 'a2', 'LMb');
+-- expectMatch 9.6 &M {a[0-[.9.]]} a2 a2
+select * from test_regex('a[0-[.9.]]', 'a2', 'M');
+select * from test_regex('a[0-[.9.]]', 'a2', 'Mb');
+-- expectMatch 9.7 &+L {a[[=x=]]} ax ax
+select * from test_regex('a[[=x=]]', 'ax', '+L');
+select * from test_regex('a[[=x=]]', 'ax', '+Lb');
+-- expectMatch 9.8 &+L {a[[=x=]]} ay ay
+select * from test_regex('a[[=x=]]', 'ay', '+L');
+select * from test_regex('a[[=x=]]', 'ay', '+Lb');
+-- expectNomatch 9.9 &+L {a[[=x=]]} az
+select * from test_regex('a[[=x=]]', 'az', '+L');
+select * from test_regex('a[[=x=]]', 'az', '+Lb');
+-- expectError 9.10 & {a[0-[=x=]]} ERANGE
+select * from test_regex('a[0-[=x=]]', '', '');
+select * from test_regex('a[0-[=x=]]', '', 'b');
+-- expectMatch 9.11 &L {a[[:digit:]]} a0 a0
+select * from test_regex('a[[:digit:]]', 'a0', 'L');
+select * from test_regex('a[[:digit:]]', 'a0', 'Lb');
+-- expectError 9.12 & {a[[:woopsie:]]} ECTYPE
+select * from test_regex('a[[:woopsie:]]', '', '');
+select * from test_regex('a[[:woopsie:]]', '', 'b');
+-- expectNomatch 9.13 &L {a[[:digit:]]} ab
+select * from test_regex('a[[:digit:]]', 'ab', 'L');
+select * from test_regex('a[[:digit:]]', 'ab', 'Lb');
+-- expectError 9.14 & {a[0-[:digit:]]} ERANGE
+select * from test_regex('a[0-[:digit:]]', '', '');
+select * from test_regex('a[0-[:digit:]]', '', 'b');
+-- expectMatch 9.15 &LP {[[:<:]]a} a a
+select * from test_regex('[[:<:]]a', 'a', 'LP');
+select * from test_regex('[[:<:]]a', 'a', 'LPb');
+-- expectMatch 9.16 &LP {a[[:>:]]} a a
+select * from test_regex('a[[:>:]]', 'a', 'LP');
+select * from test_regex('a[[:>:]]', 'a', 'LPb');
+-- expectError 9.17 & {a[[..]]b} ECOLLATE
+select * from test_regex('a[[..]]b', '', '');
+select * from test_regex('a[[..]]b', '', 'b');
+-- expectError 9.18 & {a[[==]]b} ECOLLATE
+select * from test_regex('a[[==]]b', '', '');
+select * from test_regex('a[[==]]b', '', 'b');
+-- expectError 9.19 & {a[[::]]b} ECTYPE
+select * from test_regex('a[[::]]b', '', '');
+select * from test_regex('a[[::]]b', '', 'b');
+-- expectError 9.20 & {a[[.a} EBRACK
+select * from test_regex('a[[.a', '', '');
+select * from test_regex('a[[.a', '', 'b');
+-- expectError 9.21 & {a[[=a} EBRACK
+select * from test_regex('a[[=a', '', '');
+select * from test_regex('a[[=a', '', 'b');
+-- expectError 9.22 & {a[[:a} EBRACK
+select * from test_regex('a[[:a', '', '');
+select * from test_regex('a[[:a', '', 'b');
+-- expectError 9.23 & {a[} EBRACK
+select * from test_regex('a[', '', '');
+select * from test_regex('a[', '', 'b');
+-- expectError 9.24 & {a[b} EBRACK
+select * from test_regex('a[b', '', '');
+select * from test_regex('a[b', '', 'b');
+-- expectError 9.25 & {a[b-} EBRACK
+select * from test_regex('a[b-', '', '');
+select * from test_regex('a[b-', '', 'b');
+-- expectError 9.26 & {a[b-c} EBRACK
+select * from test_regex('a[b-c', '', '');
+select * from test_regex('a[b-c', '', 'b');
+-- expectMatch 9.27 &M {a[b-c]} ab ab
+select * from test_regex('a[b-c]', 'ab', 'M');
+select * from test_regex('a[b-c]', 'ab', 'Mb');
+-- expectMatch 9.28 & {a[b-b]} ab ab
+select * from test_regex('a[b-b]', 'ab', '');
+select * from test_regex('a[b-b]', 'ab', 'b');
+-- expectMatch 9.29 &M {a[1-2]} a2 a2
+select * from test_regex('a[1-2]', 'a2', 'M');
+select * from test_regex('a[1-2]', 'a2', 'Mb');
+-- expectError 9.30 & {a[c-b]} ERANGE
+select * from test_regex('a[c-b]', '', '');
+select * from test_regex('a[c-b]', '', 'b');
+-- expectError 9.31 & {a[a-b-c]} ERANGE
+select * from test_regex('a[a-b-c]', '', '');
+select * from test_regex('a[a-b-c]', '', 'b');
+-- expectMatch 9.32 &M {a[--?]b} a?b a?b
+select * from test_regex('a[--?]b', 'a?b', 'M');
+select * from test_regex('a[--?]b', 'a?b', 'Mb');
+-- expectMatch 9.33 & {a[---]b} a-b a-b
+select * from test_regex('a[---]b', 'a-b', '');
+select * from test_regex('a[---]b', 'a-b', 'b');
+-- expectMatch 9.34 & {a[]b]c} a]c a]c
+select * from test_regex('a[]b]c', 'a]c', '');
+select * from test_regex('a[]b]c', 'a]c', 'b');
+-- expectMatch 9.35 EP {a[\]]b} a]b a]b
+select * from test_regex('a[\]]b', 'a]b', 'EP');
+-- expectNomatch 9.36 bE {a[\]]b} a]b
+select * from test_regex('a[\]]b', 'a]b', 'bE');
+-- expectMatch 9.37 bE {a[\]]b} "a\\]b" "a\\]b"
+select * from test_regex('a[\]]b', 'a\]b', 'bE');
+-- expectMatch 9.38 eE {a[\]]b} "a\\]b" "a\\]b"
+select * from test_regex('a[\]]b', 'a\]b', 'eE');
+-- expectMatch 9.39 EP {a[\\]b} "a\\b" "a\\b"
+select * from test_regex('a[\\]b', 'a\b', 'EP');
+-- expectMatch 9.40 eE {a[\\]b} "a\\b" "a\\b"
+select * from test_regex('a[\\]b', 'a\b', 'eE');
+-- expectMatch 9.41 bE {a[\\]b} "a\\b" "a\\b"
+select * from test_regex('a[\\]b', 'a\b', 'bE');
+-- expectError 9.42 - {a[\Z]b} EESCAPE
+select * from test_regex('a[\Z]b', '', '-');
+-- expectMatch 9.43 & {a[[b]c} "a\[c" "a\[c"
+select * from test_regex('a[[b]c', 'a[c', '');
+select * from test_regex('a[[b]c', 'a[c', 'b');
+-- This only works in UTF8 encoding, so it's moved to test_regex_utf8.sql:
+-- expectMatch 9.44 EMP* {a[\u00fe-\u0507][\u00ff-\u0300]b} \
+-- "a\u0102\u02ffb" "a\u0102\u02ffb"
+
+-- doing 10 "anchors and newlines"
+
+-- expectMatch 10.1 & ^a a a
+select * from test_regex('^a', 'a', '');
+select * from test_regex('^a', 'a', 'b');
+-- expectNomatch 10.2 &^ ^a a
+select * from test_regex('^a', 'a', '^');
+select * from test_regex('^a', 'a', '^b');
+-- expectIndices 10.3 &N ^ a {0 -1}
+select * from test_regex('^', 'a', '0N');
+select * from test_regex('^', 'a', '0Nb');
+-- expectIndices 10.4 & {a$} aba {2 2}
+select * from test_regex('a$', 'aba', '0');
+select * from test_regex('a$', 'aba', '0b');
+-- expectNomatch 10.5 {&$} {a$} a
+select * from test_regex('a$', 'a', '$');
+select * from test_regex('a$', 'a', '$b');
+-- expectIndices 10.6 &N {$} ab {2 1}
+select * from test_regex('$', 'ab', '0N');
+select * from test_regex('$', 'ab', '0Nb');
+-- expectMatch 10.7 &n ^a a a
+select * from test_regex('^a', 'a', 'n');
+select * from test_regex('^a', 'a', 'nb');
+-- expectMatch 10.8 &n "^a" "b\na" "a"
+select * from test_regex('^a', E'b\na', 'n');
+select * from test_regex('^a', E'b\na', 'nb');
+-- expectIndices 10.9 &w "^a" "a\na" {0 0}
+select * from test_regex('^a', E'a\na', '0w');
+select * from test_regex('^a', E'a\na', '0wb');
+-- expectIndices 10.10 &n^ "^a" "a\na" {2 2}
+select * from test_regex('^a', E'a\na', '0n^');
+select * from test_regex('^a', E'a\na', '0n^b');
+-- expectMatch 10.11 &n {a$} a a
+select * from test_regex('a$', 'a', 'n');
+select * from test_regex('a$', 'a', 'nb');
+-- expectMatch 10.12 &n "a\$" "a\nb" "a"
+select * from test_regex('a$', E'a\nb', 'n');
+select * from test_regex('a$', E'a\nb', 'nb');
+-- expectIndices 10.13 &n "a\$" "a\na" {0 0}
+select * from test_regex('a$', E'a\na', '0n');
+select * from test_regex('a$', E'a\na', '0nb');
+-- expectIndices 10.14 N ^^ a {0 -1}
+select * from test_regex('^^', 'a', '0N');
+-- expectMatch 10.15 b ^^ ^ ^
+select * from test_regex('^^', '^', 'b');
+-- expectIndices 10.16 N {$$} a {1 0}
+select * from test_regex('$$', 'a', '0N');
+-- expectMatch 10.17 b {$$} "\$" "\$"
+select * from test_regex('$$', '$', 'b');
+-- expectMatch 10.18 &N {^$} "" ""
+select * from test_regex('^$', '', 'N');
+select * from test_regex('^$', '', 'Nb');
+-- expectNomatch 10.19 &N {^$} a
+select * from test_regex('^$', 'a', 'N');
+select * from test_regex('^$', 'a', 'Nb');
+-- expectIndices 10.20 &nN "^\$" a\n\nb {2 1}
+select * from test_regex('^$', E'a\n\nb', '0nN');
+select * from test_regex('^$', E'a\n\nb', '0nNb');
+-- expectMatch 10.21 N {$^} "" ""
+select * from test_regex('$^', '', 'N');
+-- expectMatch 10.22 b {$^} "\$^" "\$^"
+select * from test_regex('$^', '$^', 'b');
+-- expectMatch 10.23 P {\Aa} a a
+select * from test_regex('\Aa', 'a', 'P');
+-- expectMatch 10.24 ^P {\Aa} a a
+select * from test_regex('\Aa', 'a', '^P');
+-- expectNomatch 10.25 ^nP {\Aa} "b\na"
+select * from test_regex('\Aa', E'b\na', '^nP');
+-- expectMatch 10.26 P {a\Z} a a
+select * from test_regex('a\Z', 'a', 'P');
+-- expectMatch 10.27 \$P {a\Z} a a
+select * from test_regex('a\Z', 'a', '$P');
+-- expectNomatch 10.28 \$nP {a\Z} "a\nb"
+select * from test_regex('a\Z', E'a\nb', '$nP');
+-- expectError 10.29 - ^* BADRPT
+select * from test_regex('^*', '', '-');
+-- expectError 10.30 - {$*} BADRPT
+select * from test_regex('$*', '', '-');
+-- expectError 10.31 - {\A*} BADRPT
+select * from test_regex('\A*', '', '-');
+-- expectError 10.32 - {\Z*} BADRPT
+select * from test_regex('\Z*', '', '-');
+
+-- doing 11 "boundary constraints"
+
+-- expectMatch 11.1 &LP {[[:<:]]a} a a
+select * from test_regex('[[:<:]]a', 'a', 'LP');
+select * from test_regex('[[:<:]]a', 'a', 'LPb');
+-- expectMatch 11.2 &LP {[[:<:]]a} -a a
+select * from test_regex('[[:<:]]a', '-a', 'LP');
+select * from test_regex('[[:<:]]a', '-a', 'LPb');
+-- expectNomatch 11.3 &LP {[[:<:]]a} ba
+select * from test_regex('[[:<:]]a', 'ba', 'LP');
+select * from test_regex('[[:<:]]a', 'ba', 'LPb');
+-- expectMatch 11.4 &LP {a[[:>:]]} a a
+select * from test_regex('a[[:>:]]', 'a', 'LP');
+select * from test_regex('a[[:>:]]', 'a', 'LPb');
+-- expectMatch 11.5 &LP {a[[:>:]]} a- a
+select * from test_regex('a[[:>:]]', 'a-', 'LP');
+select * from test_regex('a[[:>:]]', 'a-', 'LPb');
+-- expectNomatch 11.6 &LP {a[[:>:]]} ab
+select * from test_regex('a[[:>:]]', 'ab', 'LP');
+select * from test_regex('a[[:>:]]', 'ab', 'LPb');
+-- expectMatch 11.7 bLP {\<a} a a
+select * from test_regex('\<a', 'a', 'bLP');
+-- expectNomatch 11.8 bLP {\<a} ba
+select * from test_regex('\<a', 'ba', 'bLP');
+-- expectMatch 11.9 bLP {a\>} a a
+select * from test_regex('a\>', 'a', 'bLP');
+-- expectNomatch 11.10 bLP {a\>} ab
+select * from test_regex('a\>', 'ab', 'bLP');
+-- expectMatch 11.11 LP {\ya} a a
+select * from test_regex('\ya', 'a', 'LP');
+-- expectNomatch 11.12 LP {\ya} ba
+select * from test_regex('\ya', 'ba', 'LP');
+-- expectMatch 11.13 LP {a\y} a a
+select * from test_regex('a\y', 'a', 'LP');
+-- expectNomatch 11.14 LP {a\y} ab
+select * from test_regex('a\y', 'ab', 'LP');
+-- expectMatch 11.15 LP {a\Y} ab a
+select * from test_regex('a\Y', 'ab', 'LP');
+-- expectNomatch 11.16 LP {a\Y} a-
+select * from test_regex('a\Y', 'a-', 'LP');
+-- expectNomatch 11.17 LP {a\Y} a
+select * from test_regex('a\Y', 'a', 'LP');
+-- expectNomatch 11.18 LP {-\Y} -a
+select * from test_regex('-\Y', '-a', 'LP');
+-- expectMatch 11.19 LP {-\Y} -% -
+select * from test_regex('-\Y', '-%', 'LP');
+-- expectNomatch 11.20 LP {\Y-} a-
+select * from test_regex('\Y-', 'a-', 'LP');
+-- expectError 11.21 - {[[:<:]]*} BADRPT
+select * from test_regex('[[:<:]]*', '', '-');
+-- expectError 11.22 - {[[:>:]]*} BADRPT
+select * from test_regex('[[:>:]]*', '', '-');
+-- expectError 11.23 b {\<*} BADRPT
+select * from test_regex('\<*', '', 'b');
+-- expectError 11.24 b {\>*} BADRPT
+select * from test_regex('\>*', '', 'b');
+-- expectError 11.25 - {\y*} BADRPT
+select * from test_regex('\y*', '', '-');
+-- expectError 11.26 - {\Y*} BADRPT
+select * from test_regex('\Y*', '', '-');
+-- expectMatch 11.27 LP {\ma} a a
+select * from test_regex('\ma', 'a', 'LP');
+-- expectNomatch 11.28 LP {\ma} ba
+select * from test_regex('\ma', 'ba', 'LP');
+-- expectMatch 11.29 LP {a\M} a a
+select * from test_regex('a\M', 'a', 'LP');
+-- expectNomatch 11.30 LP {a\M} ab
+select * from test_regex('a\M', 'ab', 'LP');
+-- expectNomatch 11.31 ILP {\Ma} a
+select * from test_regex('\Ma', 'a', 'ILP');
+-- expectNomatch 11.32 ILP {a\m} a
+select * from test_regex('a\m', 'a', 'ILP');
+
+-- doing 12 "character classes"
+
+-- expectMatch 12.1 LP {a\db} a0b a0b
+select * from test_regex('a\db', 'a0b', 'LP');
+-- expectNomatch 12.2 LP {a\db} axb
+select * from test_regex('a\db', 'axb', 'LP');
+-- expectNomatch 12.3 LP {a\Db} a0b
+select * from test_regex('a\Db', 'a0b', 'LP');
+-- expectMatch 12.4 LP {a\Db} axb axb
+select * from test_regex('a\Db', 'axb', 'LP');
+-- expectMatch 12.5 LP "a\\sb" "a b" "a b"
+select * from test_regex('a\sb', 'a b', 'LP');
+-- expectMatch 12.6 LP "a\\sb" "a\tb" "a\tb"
+select * from test_regex('a\sb', E'a\tb', 'LP');
+-- expectMatch 12.7 LP "a\\sb" "a\nb" "a\nb"
+select * from test_regex('a\sb', E'a\nb', 'LP');
+-- expectNomatch 12.8 LP {a\sb} axb
+select * from test_regex('a\sb', 'axb', 'LP');
+-- expectMatch 12.9 LP {a\Sb} axb axb
+select * from test_regex('a\Sb', 'axb', 'LP');
+-- expectNomatch 12.10 LP "a\\Sb" "a b"
+select * from test_regex('a\Sb', 'a b', 'LP');
+-- expectMatch 12.11 LP {a\wb} axb axb
+select * from test_regex('a\wb', 'axb', 'LP');
+-- expectNomatch 12.12 LP {a\wb} a-b
+select * from test_regex('a\wb', 'a-b', 'LP');
+-- expectNomatch 12.13 LP {a\Wb} axb
+select * from test_regex('a\Wb', 'axb', 'LP');
+-- expectMatch 12.14 LP {a\Wb} a-b a-b
+select * from test_regex('a\Wb', 'a-b', 'LP');
+-- expectMatch 12.15 LP {\y\w+z\y} adze-guz guz
+select * from test_regex('\y\w+z\y', 'adze-guz', 'LP');
+-- expectMatch 12.16 LPE {a[\d]b} a1b a1b
+select * from test_regex('a[\d]b', 'a1b', 'LPE');
+-- expectMatch 12.17 LPE "a\[\\s]b" "a b" "a b"
+select * from test_regex('a[\s]b', 'a b', 'LPE');
+-- expectMatch 12.18 LPE {a[\w]b} axb axb
+select * from test_regex('a[\w]b', 'axb', 'LPE');
+
+-- these should be invalid
+select * from test_regex('[\w-~]*', 'ab01_~-`**', 'LNPSE');
+select * from test_regex('[~-\w]*', 'ab01_~-`**', 'LNPSE');
+select * from test_regex('[[:alnum:]-~]*', 'ab01~-`**', 'LNS');
+select * from test_regex('[~-[:alnum:]]*', 'ab01~-`**', 'LNS');
+
+-- test complemented char classes within brackets
+select * from test_regex('[\D]', '0123456789abc*', 'LPE');
+select * from test_regex('[^\D]', 'abc0123456789*', 'LPE');
+select * from test_regex('[1\D7]', '0123456789abc*', 'LPE');
+select * from test_regex('[7\D1]', '0123456789abc*', 'LPE');
+select * from test_regex('[^0\D1]', 'abc0123456789*', 'LPE');
+select * from test_regex('[^1\D0]', 'abc0123456789*', 'LPE');
+select * from test_regex('\W', '0123456789abc_*', 'LP');
+select * from test_regex('[\W]', '0123456789abc_*', 'LPE');
+select * from test_regex('[\s\S]*', '012 3456789abc_*', 'LNPE');
+
+-- check char classes' handling of newlines
+select * from test_regex('\s+', E'abc \n def', 'LP');
+select * from test_regex('\s+', E'abc \n def', 'nLP');
+select * from test_regex('[\s]+', E'abc \n def', 'LPE');
+select * from test_regex('[\s]+', E'abc \n def', 'nLPE');
+select * from test_regex('\S+', E'abc\ndef', 'LP');
+select * from test_regex('\S+', E'abc\ndef', 'nLP');
+select * from test_regex('[\S]+', E'abc\ndef', 'LPE');
+select * from test_regex('[\S]+', E'abc\ndef', 'nLPE');
+select * from test_regex('\d+', E'012\n345', 'LP');
+select * from test_regex('\d+', E'012\n345', 'nLP');
+select * from test_regex('[\d]+', E'012\n345', 'LPE');
+select * from test_regex('[\d]+', E'012\n345', 'nLPE');
+select * from test_regex('\D+', E'abc\ndef345', 'LP');
+select * from test_regex('\D+', E'abc\ndef345', 'nLP');
+select * from test_regex('[\D]+', E'abc\ndef345', 'LPE');
+select * from test_regex('[\D]+', E'abc\ndef345', 'nLPE');
+select * from test_regex('\w+', E'abc_012\ndef', 'LP');
+select * from test_regex('\w+', E'abc_012\ndef', 'nLP');
+select * from test_regex('[\w]+', E'abc_012\ndef', 'LPE');
+select * from test_regex('[\w]+', E'abc_012\ndef', 'nLPE');
+select * from test_regex('\W+', E'***\n@@@___', 'LP');
+select * from test_regex('\W+', E'***\n@@@___', 'nLP');
+select * from test_regex('[\W]+', E'***\n@@@___', 'LPE');
+select * from test_regex('[\W]+', E'***\n@@@___', 'nLPE');
+
+
+-- doing 13 "escapes"
+
+-- expectError 13.1 & "a\\" EESCAPE
+select * from test_regex('a\', '', '');
+select * from test_regex('a\', '', 'b');
+-- expectMatch 13.2 - {a\<b} a<b a<b
+select * from test_regex('a\<b', 'a<b', '-');
+-- expectMatch 13.3 e {a\<b} a<b a<b
+select * from test_regex('a\<b', 'a<b', 'e');
+-- expectMatch 13.4 bAS {a\wb} awb awb
+select * from test_regex('a\wb', 'awb', 'bAS');
+-- expectMatch 13.5 eAS {a\wb} awb awb
+select * from test_regex('a\wb', 'awb', 'eAS');
+-- expectMatch 13.6 PL "a\\ab" "a\007b" "a\007b"
+select * from test_regex('a\ab', E'a\007b', 'PL');
+-- expectMatch 13.7 P "a\\bb" "a\bb" "a\bb"
+select * from test_regex('a\bb', E'a\bb', 'P');
+-- expectMatch 13.8 P {a\Bb} "a\\b" "a\\b"
+select * from test_regex('a\Bb', 'a\b', 'P');
+-- expectMatch 13.9 MP "a\\chb" "a\bb" "a\bb"
+select * from test_regex('a\chb', E'a\bb', 'MP');
+-- expectMatch 13.10 MP "a\\cHb" "a\bb" "a\bb"
+select * from test_regex('a\cHb', E'a\bb', 'MP');
+-- expectMatch 13.11 LMP "a\\e" "a\033" "a\033"
+select * from test_regex('a\e', E'a\033', 'LMP');
+-- expectMatch 13.12 P "a\\fb" "a\fb" "a\fb"
+select * from test_regex('a\fb', E'a\fb', 'P');
+-- expectMatch 13.13 P "a\\nb" "a\nb" "a\nb"
+select * from test_regex('a\nb', E'a\nb', 'P');
+-- expectMatch 13.14 P "a\\rb" "a\rb" "a\rb"
+select * from test_regex('a\rb', E'a\rb', 'P');
+-- expectMatch 13.15 P "a\\tb" "a\tb" "a\tb"
+select * from test_regex('a\tb', E'a\tb', 'P');
+-- expectMatch 13.16 P "a\\u0008x" "a\bx" "a\bx"
+select * from test_regex('a\u0008x', E'a\bx', 'P');
+-- expectMatch 13.17 P {a\u008x} "a\bx" "a\bx"
+-- Tcl has relaxed their code to allow 1-4 hex digits, but Postgres hasn't
+select * from test_regex('a\u008x', E'a\bx', 'P');
+-- expectMatch 13.18 P "a\\u00088x" "a\b8x" "a\b8x"
+select * from test_regex('a\u00088x', E'a\b8x', 'P');
+-- expectMatch 13.19 P "a\\U00000008x" "a\bx" "a\bx"
+select * from test_regex('a\U00000008x', E'a\bx', 'P');
+-- expectMatch 13.20 P {a\U0000008x} "a\bx" "a\bx"
+-- Tcl has relaxed their code to allow 1-8 hex digits, but Postgres hasn't
+select * from test_regex('a\U0000008x', E'a\bx', 'P');
+-- expectMatch 13.21 P "a\\vb" "a\vb" "a\vb"
+select * from test_regex('a\vb', E'a\013b', 'P');
+-- expectMatch 13.22 MP "a\\x08x" "a\bx" "a\bx"
+select * from test_regex('a\x08x', E'a\bx', 'MP');
+-- expectError 13.23 - {a\xq} EESCAPE
+select * from test_regex('a\xq', '', '-');
+-- expectMatch 13.24 MP "a\\x08x" "a\bx" "a\bx"
+select * from test_regex('a\x08x', E'a\bx', 'MP');
+-- expectError 13.25 - {a\z} EESCAPE
+select * from test_regex('a\z', '', '-');
+-- expectMatch 13.26 MP "a\\010b" "a\bb" "a\bb"
+select * from test_regex('a\010b', E'a\bb', 'MP');
+-- These only work in UTF8 encoding, so they're moved to test_regex_utf8.sql:
+-- expectMatch 13.27 P "a\\U00001234x" "a\u1234x" "a\u1234x"
+-- expectMatch 13.28 P {a\U00001234x} "a\u1234x" "a\u1234x"
+-- expectMatch 13.29 P "a\\U0001234x" "a\u1234x" "a\u1234x"
+-- expectMatch 13.30 P {a\U0001234x} "a\u1234x" "a\u1234x"
+-- expectMatch 13.31 P "a\\U000012345x" "a\u12345x" "a\u12345x"
+-- expectMatch 13.32 P {a\U000012345x} "a\u12345x" "a\u12345x"
+-- expectMatch 13.33 P "a\\U1000000x" "a\ufffd0x" "a\ufffd0x"
+-- expectMatch 13.34 P {a\U1000000x} "a\ufffd0x" "a\ufffd0x"
+
+-- doing 14 "back references"
+-- # ugh
+
+-- expectMatch 14.1 RP {a(b*)c\1} abbcbb abbcbb bb
+select * from test_regex('a(b*)c\1', 'abbcbb', 'RP');
+-- expectMatch 14.2 RP {a(b*)c\1} ac ac ""
+select * from test_regex('a(b*)c\1', 'ac', 'RP');
+-- expectNomatch 14.3 RP {a(b*)c\1} abbcb
+select * from test_regex('a(b*)c\1', 'abbcb', 'RP');
+-- expectMatch 14.4 RP {a(b*)\1} abbcbb abb b
+select * from test_regex('a(b*)\1', 'abbcbb', 'RP');
+-- expectMatch 14.5 RP {a(b|bb)\1} abbcbb abb b
+select * from test_regex('a(b|bb)\1', 'abbcbb', 'RP');
+-- expectMatch 14.6 RP {a([bc])\1} abb abb b
+select * from test_regex('a([bc])\1', 'abb', 'RP');
+-- expectNomatch 14.7 RP {a([bc])\1} abc
+select * from test_regex('a([bc])\1', 'abc', 'RP');
+-- expectMatch 14.8 RP {a([bc])\1} abcabb abb b
+select * from test_regex('a([bc])\1', 'abcabb', 'RP');
+-- expectNomatch 14.9 RP {a([bc])*\1} abc
+select * from test_regex('a([bc])*\1', 'abc', 'RP');
+-- expectNomatch 14.10 RP {a([bc])\1} abB
+select * from test_regex('a([bc])\1', 'abB', 'RP');
+-- expectMatch 14.11 iRP {a([bc])\1} abB abB b
+select * from test_regex('a([bc])\1', 'abB', 'iRP');
+-- expectMatch 14.12 RP {a([bc])\1+} abbb abbb b
+select * from test_regex('a([bc])\1+', 'abbb', 'RP');
+-- expectMatch 14.13 QRP "a(\[bc])\\1{3,4}" abbbb abbbb b
+select * from test_regex('a([bc])\1{3,4}', 'abbbb', 'QRP');
+-- expectNomatch 14.14 QRP "a(\[bc])\\1{3,4}" abbb
+select * from test_regex('a([bc])\1{3,4}', 'abbb', 'QRP');
+-- expectMatch 14.15 RP {a([bc])\1*} abbb abbb b
+select * from test_regex('a([bc])\1*', 'abbb', 'RP');
+-- expectMatch 14.16 RP {a([bc])\1*} ab ab b
+select * from test_regex('a([bc])\1*', 'ab', 'RP');
+-- expectMatch 14.17 RP {a([bc])(\1*)} ab ab b ""
+select * from test_regex('a([bc])(\1*)', 'ab', 'RP');
+-- expectError 14.18 - {a((b)\1)} ESUBREG
+select * from test_regex('a((b)\1)', '', '-');
+-- expectError 14.19 - {a(b)c\2} ESUBREG
+select * from test_regex('a(b)c\2', '', '-');
+-- expectMatch 14.20 bR {a\(b*\)c\1} abbcbb abbcbb bb
+select * from test_regex('a\(b*\)c\1', 'abbcbb', 'bR');
+-- expectMatch 14.21 RP {^([bc])\1*$} bbb bbb b
+select * from test_regex('^([bc])\1*$', 'bbb', 'RP');
+-- expectMatch 14.22 RP {^([bc])\1*$} ccc ccc c
+select * from test_regex('^([bc])\1*$', 'ccc', 'RP');
+-- expectNomatch 14.23 RP {^([bc])\1*$} bcb
+select * from test_regex('^([bc])\1*$', 'bcb', 'RP');
+-- expectMatch 14.24 LRP {^(\w+)( \1)+$} {abc abc abc} {abc abc abc} abc { abc}
+select * from test_regex('^(\w+)( \1)+$', 'abc abc abc', 'LRP');
+-- expectNomatch 14.25 LRP {^(\w+)( \1)+$} {abc abd abc}
+select * from test_regex('^(\w+)( \1)+$', 'abc abd abc', 'LRP');
+-- expectNomatch 14.26 LRP {^(\w+)( \1)+$} {abc abc abd}
+select * from test_regex('^(\w+)( \1)+$', 'abc abc abd', 'LRP');
+-- expectMatch 14.27 RP {^(.+)( \1)+$} {abc abc abc} {abc abc abc} abc { abc}
+select * from test_regex('^(.+)( \1)+$', 'abc abc abc', 'RP');
+-- expectNomatch 14.28 RP {^(.+)( \1)+$} {abc abd abc}
+select * from test_regex('^(.+)( \1)+$', 'abc abd abc', 'RP');
+-- expectNomatch 14.29 RP {^(.+)( \1)+$} {abc abc abd}
+select * from test_regex('^(.+)( \1)+$', 'abc abc abd', 'RP');
+-- expectNomatch 14.30 RP {^(.)\1|\1.} {abcdef}
+select * from test_regex('^(.)\1|\1.', 'abcdef', 'RP');
+-- expectNomatch 14.31 RP {^((.)\2|..)\2} {abadef}
+select * from test_regex('^((.)\2|..)\2', 'abadef', 'RP');
+
+-- back reference only matches the string, not any constraints
+select * from test_regex('(^\w+).*\1', 'abc abc abc', 'LRP');
+select * from test_regex('(^\w+\M).*\1', 'abc abcd abd', 'LRP');
+select * from test_regex('(\w+(?= )).*\1', 'abc abcd abd', 'HLRP');
+
+-- doing 15 "octal escapes vs back references"
+
+-- # initial zero is always octal
+-- expectMatch 15.1 MP "a\\010b" "a\bb" "a\bb"
+select * from test_regex('a\010b', E'a\bb', 'MP');
+-- expectMatch 15.2 MP "a\\0070b" "a\0070b" "a\0070b"
+select * from test_regex('a\0070b', E'a\0070b', 'MP');
+-- expectMatch 15.3 MP "a\\07b" "a\007b" "a\007b"
+select * from test_regex('a\07b', E'a\007b', 'MP');
+-- expectMatch 15.4 MP "a(b)(b)(b)(b)(b)(b)(b)(b)(b)(b)\\07c" \
+-- "abbbbbbbbbb\007c" abbbbbbbbbb\007c b b b b b b b b b b
+select * from test_regex('a(b)(b)(b)(b)(b)(b)(b)(b)(b)(b)\07c', E'abbbbbbbbbb\007c', 'MP');
+-- # a single digit is always a backref
+-- expectError 15.5 - {a\7b} ESUBREG
+select * from test_regex('a\7b', '', '-');
+-- # otherwise it's a backref only if within range (barf!)
+-- expectMatch 15.6 MP "a\\10b" "a\bb" "a\bb"
+select * from test_regex('a\10b', E'a\bb', 'MP');
+-- expectMatch 15.7 MP {a\101b} aAb aAb
+select * from test_regex('a\101b', 'aAb', 'MP');
+-- expectMatch 15.8 RP {a(b)(b)(b)(b)(b)(b)(b)(b)(b)(b)\10c} \
+-- "abbbbbbbbbbbc" abbbbbbbbbbbc b b b b b b b b b b
+select * from test_regex('a(b)(b)(b)(b)(b)(b)(b)(b)(b)(b)\10c', 'abbbbbbbbbbbc', 'RP');
+-- # but we're fussy about border cases -- guys who want octal should use the zero
+-- expectError 15.9 - {a((((((((((b\10))))))))))c} ESUBREG
+select * from test_regex('a((((((((((b\10))))))))))c', '', '-');
+-- # BREs don't have octal, EREs don't have backrefs
+-- expectMatch 15.10 MP "a\\12b" "a\nb" "a\nb"
+select * from test_regex('a\12b', E'a\nb', 'MP');
+-- expectError 15.11 b {a\12b} ESUBREG
+select * from test_regex('a\12b', '', 'b');
+-- expectMatch 15.12 eAS {a\12b} a12b a12b
+select * from test_regex('a\12b', 'a12b', 'eAS');
+-- expectMatch 15.13 MP {a\701b} a\u00381b a\u00381b
+select * from test_regex('a\701b', 'a81b', 'MP');
+
+-- doing 16 "expanded syntax"
+
+-- expectMatch 16.1 xP "a b c" "abc" "abc"
+select * from test_regex('a b c', 'abc', 'xP');
+-- expectMatch 16.2 xP "a b #oops\nc\td" "abcd" "abcd"
+select * from test_regex(E'a b #oops\nc\td', 'abcd', 'xP');
+-- expectMatch 16.3 x "a\\ b\\\tc" "a b\tc" "a b\tc"
+select * from test_regex(E'a\\ b\\\tc', E'a b\tc', 'x');
+-- expectMatch 16.4 xP "a b\\#c" "ab#c" "ab#c"
+select * from test_regex('a b\#c', 'ab#c', 'xP');
+-- expectMatch 16.5 xP "a b\[c d]e" "ab e" "ab e"
+select * from test_regex('a b[c d]e', 'ab e', 'xP');
+-- expectMatch 16.6 xP "a b\[c#d]e" "ab#e" "ab#e"
+select * from test_regex('a b[c#d]e', 'ab#e', 'xP');
+-- expectMatch 16.7 xP "a b\[c#d]e" "abde" "abde"
+select * from test_regex('a b[c#d]e', 'abde', 'xP');
+-- expectMatch 16.8 xSPB "ab{ d" "ab\{d" "ab\{d"
+select * from test_regex('ab{ d', 'ab{d', 'xSPB');
+-- expectMatch 16.9 xPQ "ab{ 1 , 2 }c" "abc" "abc"
+select * from test_regex('ab{ 1 , 2 }c', 'abc', 'xPQ');
+
+-- doing 17 "misc syntax"
+
+-- expectMatch 17.1 P a(?#comment)b ab ab
+select * from test_regex('a(?#comment)b', 'ab', 'P');
+
+-- doing 18 "unmatchable REs"
+
+-- expectNomatch 18.1 I a^b ab
+select * from test_regex('a^b', 'ab', 'I');
+
+-- doing 19 "case independence"
+
+-- expectMatch 19.1 &i ab Ab Ab
+select * from test_regex('ab', 'Ab', 'i');
+select * from test_regex('ab', 'Ab', 'ib');
+-- expectMatch 19.2 &i {a[bc]} aC aC
+select * from test_regex('a[bc]', 'aC', 'i');
+select * from test_regex('a[bc]', 'aC', 'ib');
+-- expectNomatch 19.3 &i {a[^bc]} aB
+select * from test_regex('a[^bc]', 'aB', 'i');
+select * from test_regex('a[^bc]', 'aB', 'ib');
+-- expectMatch 19.4 &iM {a[b-d]} aC aC
+select * from test_regex('a[b-d]', 'aC', 'iM');
+select * from test_regex('a[b-d]', 'aC', 'iMb');
+-- expectNomatch 19.5 &iM {a[^b-d]} aC
+select * from test_regex('a[^b-d]', 'aC', 'iM');
+select * from test_regex('a[^b-d]', 'aC', 'iMb');
+
+-- doing 20 "directors and embedded options"
+
+-- expectError 20.1 & ***? BADPAT
+select * from test_regex('***?', '', '');
+select * from test_regex('***?', '', 'b');
+-- expectMatch 20.2 q ***? ***? ***?
+select * from test_regex('***?', '***?', 'q');
+-- expectMatch 20.3 &P ***=a*b a*b a*b
+select * from test_regex('***=a*b', 'a*b', 'P');
+select * from test_regex('***=a*b', 'a*b', 'Pb');
+-- expectMatch 20.4 q ***=a*b ***=a*b ***=a*b
+select * from test_regex('***=a*b', '***=a*b', 'q');
+-- expectMatch 20.5 bLP {***:\w+} ab ab
+select * from test_regex('***:\w+', 'ab', 'bLP');
+-- expectMatch 20.6 eLP {***:\w+} ab ab
+select * from test_regex('***:\w+', 'ab', 'eLP');
+-- expectError 20.7 & ***:***=a*b BADRPT
+select * from test_regex('***:***=a*b', '', '');
+select * from test_regex('***:***=a*b', '', 'b');
+-- expectMatch 20.8 &P ***:(?b)a+b a+b a+b
+select * from test_regex('***:(?b)a+b', 'a+b', 'P');
+select * from test_regex('***:(?b)a+b', 'a+b', 'Pb');
+-- expectMatch 20.9 P (?b)a+b a+b a+b
+select * from test_regex('(?b)a+b', 'a+b', 'P');
+-- expectError 20.10 e {(?b)\w+} BADRPT
+select * from test_regex('(?b)\w+', '', 'e');
+-- expectMatch 20.11 bAS {(?b)\w+} (?b)w+ (?b)w+
+select * from test_regex('(?b)\w+', '(?b)w+', 'bAS');
+-- expectMatch 20.12 iP (?c)a a a
+select * from test_regex('(?c)a', 'a', 'iP');
+-- expectNomatch 20.13 iP (?c)a A
+select * from test_regex('(?c)a', 'A', 'iP');
+-- expectMatch 20.14 APS {(?e)\W+} WW WW
+select * from test_regex('(?e)\W+', 'WW', 'APS');
+-- expectMatch 20.15 P (?i)a+ Aa Aa
+select * from test_regex('(?i)a+', 'Aa', 'P');
+-- expectNomatch 20.16 P "(?m)a.b" "a\nb"
+select * from test_regex('(?m)a.b', E'a\nb', 'P');
+-- expectMatch 20.17 P "(?m)^b" "a\nb" "b"
+select * from test_regex('(?m)^b', E'a\nb', 'P');
+-- expectNomatch 20.18 P "(?n)a.b" "a\nb"
+select * from test_regex('(?n)a.b', E'a\nb', 'P');
+-- expectMatch 20.19 P "(?n)^b" "a\nb" "b"
+select * from test_regex('(?n)^b', E'a\nb', 'P');
+-- expectNomatch 20.20 P "(?p)a.b" "a\nb"
+select * from test_regex('(?p)a.b', E'a\nb', 'P');
+-- expectNomatch 20.21 P "(?p)^b" "a\nb"
+select * from test_regex('(?p)^b', E'a\nb', 'P');
+-- expectMatch 20.22 P (?q)a+b a+b a+b
+select * from test_regex('(?q)a+b', 'a+b', 'P');
+-- expectMatch 20.23 nP "(?s)a.b" "a\nb" "a\nb"
+select * from test_regex('(?s)a.b', E'a\nb', 'nP');
+-- expectMatch 20.24 xP "(?t)a b" "a b" "a b"
+select * from test_regex('(?t)a b', 'a b', 'xP');
+-- expectMatch 20.25 P "(?w)a.b" "a\nb" "a\nb"
+select * from test_regex('(?w)a.b', E'a\nb', 'P');
+-- expectMatch 20.26 P "(?w)^b" "a\nb" "b"
+select * from test_regex('(?w)^b', E'a\nb', 'P');
+-- expectMatch 20.27 P "(?x)a b" "ab" "ab"
+select * from test_regex('(?x)a b', 'ab', 'P');
+-- expectError 20.28 - (?z)ab BADOPT
+select * from test_regex('(?z)ab', '', '-');
+-- expectMatch 20.29 P (?ici)a+ Aa Aa
+select * from test_regex('(?ici)a+', 'Aa', 'P');
+-- expectError 20.30 P (?i)(?q)a+ BADRPT
+select * from test_regex('(?i)(?q)a+', '', 'P');
+-- expectMatch 20.31 P (?q)(?i)a+ (?i)a+ (?i)a+
+select * from test_regex('(?q)(?i)a+', '(?i)a+', 'P');
+-- expectMatch 20.32 P (?qe)a+ a a
+select * from test_regex('(?qe)a+', 'a', 'P');
+-- expectMatch 20.33 xP "(?q)a b" "a b" "a b"
+select * from test_regex('(?q)a b', 'a b', 'xP');
+-- expectMatch 20.34 P "(?qx)a b" "a b" "a b"
+select * from test_regex('(?qx)a b', 'a b', 'P');
+-- expectMatch 20.35 P (?qi)ab Ab Ab
+select * from test_regex('(?qi)ab', 'Ab', 'P');
+
+-- doing 21 "capturing"
+
+-- expectMatch 21.1 - a(b)c abc abc b
+select * from test_regex('a(b)c', 'abc', '-');
+-- expectMatch 21.2 P a(?:b)c xabc abc
+select * from test_regex('a(?:b)c', 'xabc', 'P');
+-- expectMatch 21.3 - a((b))c xabcy abc b b
+select * from test_regex('a((b))c', 'xabcy', '-');
+-- expectMatch 21.4 P a(?:(b))c abcy abc b
+select * from test_regex('a(?:(b))c', 'abcy', 'P');
+-- expectMatch 21.5 P a((?:b))c abc abc b
+select * from test_regex('a((?:b))c', 'abc', 'P');
+-- expectMatch 21.6 P a(?:(?:b))c abc abc
+select * from test_regex('a(?:(?:b))c', 'abc', 'P');
+-- expectIndices 21.7 Q "a(b){0}c" ac {0 1} {-1 -1}
+select * from test_regex('a(b){0}c', 'ac', '0Q');
+-- expectMatch 21.8 - a(b)c(d)e abcde abcde b d
+select * from test_regex('a(b)c(d)e', 'abcde', '-');
+-- expectMatch 21.9 - (b)c(d)e bcde bcde b d
+select * from test_regex('(b)c(d)e', 'bcde', '-');
+-- expectMatch 21.10 - a(b)(d)e abde abde b d
+select * from test_regex('a(b)(d)e', 'abde', '-');
+-- expectMatch 21.11 - a(b)c(d) abcd abcd b d
+select * from test_regex('a(b)c(d)', 'abcd', '-');
+-- expectMatch 21.12 - (ab)(cd) xabcdy abcd ab cd
+select * from test_regex('(ab)(cd)', 'xabcdy', '-');
+-- expectMatch 21.13 - a(b)?c xabcy abc b
+select * from test_regex('a(b)?c', 'xabcy', '-');
+-- expectIndices 21.14 - a(b)?c xacy {1 2} {-1 -1}
+select * from test_regex('a(b)?c', 'xacy', '0-');
+-- expectMatch 21.15 - a(b)?c(d)?e xabcdey abcde b d
+select * from test_regex('a(b)?c(d)?e', 'xabcdey', '-');
+-- expectIndices 21.16 - a(b)?c(d)?e xacdey {1 4} {-1 -1} {3 3}
+select * from test_regex('a(b)?c(d)?e', 'xacdey', '0-');
+-- expectIndices 21.17 - a(b)?c(d)?e xabcey {1 4} {2 2} {-1 -1}
+select * from test_regex('a(b)?c(d)?e', 'xabcey', '0-');
+-- expectIndices 21.18 - a(b)?c(d)?e xacey {1 3} {-1 -1} {-1 -1}
+select * from test_regex('a(b)?c(d)?e', 'xacey', '0-');
+-- expectMatch 21.19 - a(b)*c xabcy abc b
+select * from test_regex('a(b)*c', 'xabcy', '-');
+-- expectIndices 21.20 - a(b)*c xabbbcy {1 5} {4 4}
+select * from test_regex('a(b)*c', 'xabbbcy', '0-');
+-- expectIndices 21.21 - a(b)*c xacy {1 2} {-1 -1}
+select * from test_regex('a(b)*c', 'xacy', '0-');
+-- expectMatch 21.22 - a(b*)c xabbbcy abbbc bbb
+select * from test_regex('a(b*)c', 'xabbbcy', '-');
+-- expectMatch 21.23 - a(b*)c xacy ac ""
+select * from test_regex('a(b*)c', 'xacy', '-');
+-- expectNomatch 21.24 - a(b)+c xacy
+select * from test_regex('a(b)+c', 'xacy', '-');
+-- expectMatch 21.25 - a(b)+c xabcy abc b
+select * from test_regex('a(b)+c', 'xabcy', '-');
+-- expectIndices 21.26 - a(b)+c xabbbcy {1 5} {4 4}
+select * from test_regex('a(b)+c', 'xabbbcy', '0-');
+-- expectMatch 21.27 - a(b+)c xabbbcy abbbc bbb
+select * from test_regex('a(b+)c', 'xabbbcy', '-');
+-- expectIndices 21.28 Q "a(b){2,3}c" xabbbcy {1 5} {4 4}
+select * from test_regex('a(b){2,3}c', 'xabbbcy', '0Q');
+-- expectIndices 21.29 Q "a(b){2,3}c" xabbcy {1 4} {3 3}
+select * from test_regex('a(b){2,3}c', 'xabbcy', '0Q');
+-- expectNomatch 21.30 Q "a(b){2,3}c" xabcy
+select * from test_regex('a(b){2,3}c', 'xabcy', 'Q');
+-- expectMatch 21.31 LP "\\y(\\w+)\\y" "-- abc-" "abc" "abc"
+select * from test_regex('\y(\w+)\y', '-- abc-', 'LP');
+-- expectMatch 21.32 - a((b|c)d+)+ abacdbd acdbd bd b
+select * from test_regex('a((b|c)d+)+', 'abacdbd', '-');
+-- expectMatch 21.33 N (.*).* abc abc abc
+select * from test_regex('(.*).*', 'abc', 'N');
+-- expectMatch 21.34 N (a*)* bc "" ""
+select * from test_regex('(a*)*', 'bc', 'N');
+-- expectMatch 21.35 M { TO (([a-z0-9._]+|"([^"]+|"")+")+)} {asd TO foo} { TO foo} foo o {}
+select * from test_regex(' TO (([a-z0-9._]+|"([^"]+|"")+")+)', 'asd TO foo', 'M');
+-- expectMatch 21.36 RPQ ((.))(\2){0} xy x x x {}
+select * from test_regex('((.))(\2){0}', 'xy', 'RPQ');
+-- expectNomatch 21.39 PQR {(.){0}(\1)} xxx
+select * from test_regex('(.){0}(\1)', 'xxx', 'PQR');
+-- expectNomatch 21.40 PQR {((.)){0}(\2)} xxx
+select * from test_regex('((.)){0}(\2)', 'xxx', 'PQR');
+-- expectMatch 21.41 NPQR {((.)){0}(\2){0}} xyz {} {} {} {}
+select * from test_regex('((.)){0}(\2){0}', 'xyz', 'NPQR');
+
+-- doing 22 "multicharacter collating elements"
+-- # again ugh
+
+-- MCCEs are not implemented in Postgres, so we skip all these tests
+-- expectMatch 22.1 &+L {a[c]e} ace ace
+-- select * from test_regex('a[c]e', 'ace', '+L');
+-- select * from test_regex('a[c]e', 'ace', '+Lb');
+-- expectNomatch 22.2 &+IL {a[c]h} ach
+-- select * from test_regex('a[c]h', 'ach', '+IL');
+-- select * from test_regex('a[c]h', 'ach', '+ILb');
+-- expectMatch 22.3 &+L {a[[.ch.]]} ach ach
+-- select * from test_regex('a[[.ch.]]', 'ach', '+L');
+-- select * from test_regex('a[[.ch.]]', 'ach', '+Lb');
+-- expectNomatch 22.4 &+L {a[[.ch.]]} ace
+-- select * from test_regex('a[[.ch.]]', 'ace', '+L');
+-- select * from test_regex('a[[.ch.]]', 'ace', '+Lb');
+-- expectMatch 22.5 &+L {a[c[.ch.]]} ac ac
+-- select * from test_regex('a[c[.ch.]]', 'ac', '+L');
+-- select * from test_regex('a[c[.ch.]]', 'ac', '+Lb');
+-- expectMatch 22.6 &+L {a[c[.ch.]]} ace ac
+-- select * from test_regex('a[c[.ch.]]', 'ace', '+L');
+-- select * from test_regex('a[c[.ch.]]', 'ace', '+Lb');
+-- expectMatch 22.7 &+L {a[c[.ch.]]} ache ach
+-- select * from test_regex('a[c[.ch.]]', 'ache', '+L');
+-- select * from test_regex('a[c[.ch.]]', 'ache', '+Lb');
+-- expectNomatch 22.8 &+L {a[^c]e} ace
+-- select * from test_regex('a[^c]e', 'ace', '+L');
+-- select * from test_regex('a[^c]e', 'ace', '+Lb');
+-- expectMatch 22.9 &+L {a[^c]e} abe abe
+-- select * from test_regex('a[^c]e', 'abe', '+L');
+-- select * from test_regex('a[^c]e', 'abe', '+Lb');
+-- expectMatch 22.10 &+L {a[^c]e} ache ache
+-- select * from test_regex('a[^c]e', 'ache', '+L');
+-- select * from test_regex('a[^c]e', 'ache', '+Lb');
+-- expectNomatch 22.11 &+L {a[^[.ch.]]} ach
+-- select * from test_regex('a[^[.ch.]]', 'ach', '+L');
+-- select * from test_regex('a[^[.ch.]]', 'ach', '+Lb');
+-- expectMatch 22.12 &+L {a[^[.ch.]]} ace ac
+-- select * from test_regex('a[^[.ch.]]', 'ace', '+L');
+-- select * from test_regex('a[^[.ch.]]', 'ace', '+Lb');
+-- expectMatch 22.13 &+L {a[^[.ch.]]} ac ac
+-- select * from test_regex('a[^[.ch.]]', 'ac', '+L');
+-- select * from test_regex('a[^[.ch.]]', 'ac', '+Lb');
+-- expectMatch 22.14 &+L {a[^[.ch.]]} abe ab
+-- select * from test_regex('a[^[.ch.]]', 'abe', '+L');
+-- select * from test_regex('a[^[.ch.]]', 'abe', '+Lb');
+-- expectNomatch 22.15 &+L {a[^c[.ch.]]} ach
+-- select * from test_regex('a[^c[.ch.]]', 'ach', '+L');
+-- select * from test_regex('a[^c[.ch.]]', 'ach', '+Lb');
+-- expectNomatch 22.16 &+L {a[^c[.ch.]]} ace
+-- select * from test_regex('a[^c[.ch.]]', 'ace', '+L');
+-- select * from test_regex('a[^c[.ch.]]', 'ace', '+Lb');
+-- expectNomatch 22.17 &+L {a[^c[.ch.]]} ac
+-- select * from test_regex('a[^c[.ch.]]', 'ac', '+L');
+-- select * from test_regex('a[^c[.ch.]]', 'ac', '+Lb');
+-- expectMatch 22.18 &+L {a[^c[.ch.]]} abe ab
+-- select * from test_regex('a[^c[.ch.]]', 'abe', '+L');
+-- select * from test_regex('a[^c[.ch.]]', 'abe', '+Lb');
+-- expectMatch 22.19 &+L {a[^b]} ac ac
+-- select * from test_regex('a[^b]', 'ac', '+L');
+-- select * from test_regex('a[^b]', 'ac', '+Lb');
+-- expectMatch 22.20 &+L {a[^b]} ace ac
+-- select * from test_regex('a[^b]', 'ace', '+L');
+-- select * from test_regex('a[^b]', 'ace', '+Lb');
+-- expectMatch 22.21 &+L {a[^b]} ach ach
+-- select * from test_regex('a[^b]', 'ach', '+L');
+-- select * from test_regex('a[^b]', 'ach', '+Lb');
+-- expectNomatch 22.22 &+L {a[^b]} abe
+-- select * from test_regex('a[^b]', 'abe', '+L');
+-- select * from test_regex('a[^b]', 'abe', '+Lb');
+
+-- doing 23 "lookahead constraints"
+
+-- expectMatch 23.1 HP a(?=b)b* ab ab
+select * from test_regex('a(?=b)b*', 'ab', 'HP');
+-- expectNomatch 23.2 HP a(?=b)b* a
+select * from test_regex('a(?=b)b*', 'a', 'HP');
+-- expectMatch 23.3 HP a(?=b)b*(?=c)c* abc abc
+select * from test_regex('a(?=b)b*(?=c)c*', 'abc', 'HP');
+-- expectNomatch 23.4 HP a(?=b)b*(?=c)c* ab
+select * from test_regex('a(?=b)b*(?=c)c*', 'ab', 'HP');
+-- expectNomatch 23.5 HP a(?!b)b* ab
+select * from test_regex('a(?!b)b*', 'ab', 'HP');
+-- expectMatch 23.6 HP a(?!b)b* a a
+select * from test_regex('a(?!b)b*', 'a', 'HP');
+-- expectMatch 23.7 HP (?=b)b b b
+select * from test_regex('(?=b)b', 'b', 'HP');
+-- expectNomatch 23.8 HP (?=b)b a
+select * from test_regex('(?=b)b', 'a', 'HP');
+-- expectMatch 23.9 HP ...(?!.) abcde cde
+select * from test_regex('...(?!.)', 'abcde', 'HP');
+-- expectNomatch 23.10 HP ...(?=.) abc
+select * from test_regex('...(?=.)', 'abc', 'HP');
+
+-- Postgres addition: lookbehind constraints
+
+-- expectMatch 23.11 HPN (?<=a)b* ab b
+select * from test_regex('(?<=a)b*', 'ab', 'HPN');
+-- expectNomatch 23.12 HPN (?<=a)b* b
+select * from test_regex('(?<=a)b*', 'b', 'HPN');
+-- expectMatch 23.13 HP (?<=a)b*(?<=b)c* abc bc
+select * from test_regex('(?<=a)b*(?<=b)c*', 'abc', 'HP');
+-- expectNomatch 23.14 HP (?<=a)b*(?<=b)c* ac
+select * from test_regex('(?<=a)b*(?<=b)c*', 'ac', 'HP');
+-- expectNomatch 23.15 IHP a(?<!a)b* ab
+select * from test_regex('a(?<!a)b*', 'ab', 'IHP');
+-- expectMatch 23.16 HP a(?<!b)b* a a
+select * from test_regex('a(?<!b)b*', 'a', 'HP');
+-- expectMatch 23.17 HP (?<=b)b bb b
+select * from test_regex('(?<=b)b', 'bb', 'HP');
+-- expectNomatch 23.18 HP (?<=b)b b
+select * from test_regex('(?<=b)b', 'b', 'HP');
+-- expectMatch 23.19 HP (?<=.).. abcde bc
+select * from test_regex('(?<=.)..', 'abcde', 'HP');
+-- expectMatch 23.20 HP (?<=..)a* aaabb a
+select * from test_regex('(?<=..)a*', 'aaabb', 'HP');
+-- expectMatch 23.21 HP (?<=..)b* aaabb {}
+-- Note: empty match here is correct, it matches after the first 2 characters
+select * from test_regex('(?<=..)b*', 'aaabb', 'HP');
+-- expectMatch 23.22 HP (?<=..)b+ aaabb bb
+select * from test_regex('(?<=..)b+', 'aaabb', 'HP');
+
+-- doing 24 "non-greedy quantifiers"
+
+-- expectMatch 24.1 PT ab+? abb ab
+select * from test_regex('ab+?', 'abb', 'PT');
+-- expectMatch 24.2 PT ab+?c abbc abbc
+select * from test_regex('ab+?c', 'abbc', 'PT');
+-- expectMatch 24.3 PT ab*? abb a
+select * from test_regex('ab*?', 'abb', 'PT');
+-- expectMatch 24.4 PT ab*?c abbc abbc
+select * from test_regex('ab*?c', 'abbc', 'PT');
+-- expectMatch 24.5 PT ab?? ab a
+select * from test_regex('ab??', 'ab', 'PT');
+-- expectMatch 24.6 PT ab??c abc abc
+select * from test_regex('ab??c', 'abc', 'PT');
+-- expectMatch 24.7 PQT "ab{2,4}?" abbbb abb
+select * from test_regex('ab{2,4}?', 'abbbb', 'PQT');
+-- expectMatch 24.8 PQT "ab{2,4}?c" abbbbc abbbbc
+select * from test_regex('ab{2,4}?c', 'abbbbc', 'PQT');
+-- expectMatch 24.9 - 3z* 123zzzz456 3zzzz
+select * from test_regex('3z*', '123zzzz456', '-');
+-- expectMatch 24.10 PT 3z*? 123zzzz456 3
+select * from test_regex('3z*?', '123zzzz456', 'PT');
+-- expectMatch 24.11 - z*4 123zzzz456 zzzz4
+select * from test_regex('z*4', '123zzzz456', '-');
+-- expectMatch 24.12 PT z*?4 123zzzz456 zzzz4
+select * from test_regex('z*?4', '123zzzz456', 'PT');
+-- expectMatch 24.13 PT {^([^/]+?)(?:/([^/]+?))(?:/([^/]+?))?$} {foo/bar/baz} {foo/bar/baz} {foo} {bar} {baz}
+select * from test_regex('^([^/]+?)(?:/([^/]+?))(?:/([^/]+?))?$', 'foo/bar/baz', 'PT');
+
+-- doing 25 "mixed quantifiers"
+-- # this is very incomplete as yet
+-- # should include |
+
+-- expectMatch 25.1 PNT {^(.*?)(a*)$} "xyza" xyza xyz a
+select * from test_regex('^(.*?)(a*)$', 'xyza', 'PNT');
+-- expectMatch 25.2 PNT {^(.*?)(a*)$} "xyzaa" xyzaa xyz aa
+select * from test_regex('^(.*?)(a*)$', 'xyzaa', 'PNT');
+-- expectMatch 25.3 PNT {^(.*?)(a*)$} "xyz" xyz xyz ""
+select * from test_regex('^(.*?)(a*)$', 'xyz', 'PNT');
+
+-- doing 26 "tricky cases"
+
+-- # attempts to trick the matcher into accepting a short match
+-- expectMatch 26.1 - (week|wee)(night|knights) \
+-- "weeknights" weeknights wee knights
+select * from test_regex('(week|wee)(night|knights)', 'weeknights', '-');
+-- expectMatch 26.2 RP {a(bc*).*\1} abccbccb abccbccb b
+select * from test_regex('a(bc*).*\1', 'abccbccb', 'RP');
+-- expectMatch 26.3 - {a(b.[bc]*)+} abcbd abcbd bd
+select * from test_regex('a(b.[bc]*)+', 'abcbd', '-');
+
+-- doing 27 "implementation misc."
+
+-- # duplicate arcs are suppressed
+-- expectMatch 27.1 P a(?:b|b)c abc abc
+select * from test_regex('a(?:b|b)c', 'abc', 'P');
+-- # make color/subcolor relationship go back and forth
+-- expectMatch 27.2 & {[ab][ab][ab]} aba aba
+select * from test_regex('[ab][ab][ab]', 'aba', '');
+select * from test_regex('[ab][ab][ab]', 'aba', 'b');
+-- expectMatch 27.3 & {[ab][ab][ab][ab][ab][ab][ab]} \
+-- "abababa" abababa
+select * from test_regex('[ab][ab][ab][ab][ab][ab][ab]', 'abababa', '');
+select * from test_regex('[ab][ab][ab][ab][ab][ab][ab]', 'abababa', 'b');
+
+-- doing 28 "boundary busters etc."
+
+-- # color-descriptor allocation changes at 10
+-- expectMatch 28.1 & abcdefghijkl "abcdefghijkl" abcdefghijkl
+select * from test_regex('abcdefghijkl', 'abcdefghijkl', '');
+select * from test_regex('abcdefghijkl', 'abcdefghijkl', 'b');
+-- # so does arc allocation
+-- expectMatch 28.2 P a(?:b|c|d|e|f|g|h|i|j|k|l|m)n "agn" agn
+select * from test_regex('a(?:b|c|d|e|f|g|h|i|j|k|l|m)n', 'agn', 'P');
+-- # subexpression tracking also at 10
+-- expectMatch 28.3 - a(((((((((((((b)))))))))))))c \
+-- "abc" abc b b b b b b b b b b b b b
+select * from test_regex('a(((((((((((((b)))))))))))))c', 'abc', '-');
+-- # state-set handling changes slightly at unsigned size (might be 64...)
+-- # (also stresses arc allocation)
+-- expectMatch 28.4 Q "ab{1,100}c" abbc abbc
+select * from test_regex('ab{1,100}c', 'abbc', 'Q');
+-- expectMatch 28.5 Q "ab{1,100}c" \
+-- "abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc" \
+-- abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc
+select * from test_regex('ab{1,100}c', 'abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc', 'Q');
+-- expectMatch 28.6 Q "ab{1,100}c" \
+-- "abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc"\
+-- abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc
+select * from test_regex('ab{1,100}c', 'abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc', 'Q');
+-- # force small cache and bust it, several ways
+-- expectMatch 28.7 LP {\w+abcdefgh} xyzabcdefgh xyzabcdefgh
+select * from test_regex('\w+abcdefgh', 'xyzabcdefgh', 'LP');
+-- expectMatch 28.8 %LP {\w+abcdefgh} xyzabcdefgh xyzabcdefgh
+select * from test_regex('\w+abcdefgh', 'xyzabcdefgh', '%LP');
+-- expectMatch 28.9 %LP {\w+abcdefghijklmnopqrst} \
+-- "xyzabcdefghijklmnopqrst" xyzabcdefghijklmnopqrst
+select * from test_regex('\w+abcdefghijklmnopqrst', 'xyzabcdefghijklmnopqrst', '%LP');
+-- expectIndices 28.10 %LP {\w+(abcdefgh)?} xyz {0 2} {-1 -1}
+select * from test_regex('\w+(abcdefgh)?', 'xyz', '0%LP');
+-- expectIndices 28.11 %LP {\w+(abcdefgh)?} xyzabcdefg {0 9} {-1 -1}
+select * from test_regex('\w+(abcdefgh)?', 'xyzabcdefg', '0%LP');
+-- expectIndices 28.12 %LP {\w+(abcdefghijklmnopqrst)?} \
+-- "xyzabcdefghijklmnopqrs" {0 21} {-1 -1}
+select * from test_regex('\w+(abcdefghijklmnopqrst)?', 'xyzabcdefghijklmnopqrs', '0%LP');
+
+-- doing 29 "incomplete matches"
+
+-- expectPartial 29.1 t def abc {3 2} ""
+select * from test_regex('def', 'abc', '0!t');
+-- expectPartial 29.2 t bcd abc {1 2} ""
+select * from test_regex('bcd', 'abc', '0!t');
+-- expectPartial 29.3 t abc abab {0 3} ""
+select * from test_regex('abc', 'abab', '0!t');
+-- expectPartial 29.4 t abc abdab {3 4} ""
+select * from test_regex('abc', 'abdab', '0!t');
+-- expectIndices 29.5 t abc abc {0 2} {0 2}
+select * from test_regex('abc', 'abc', '0t');
+-- expectIndices 29.6 t abc xyabc {2 4} {2 4}
+select * from test_regex('abc', 'xyabc', '0t');
+-- expectPartial 29.7 t abc+ xyab {2 3} ""
+select * from test_regex('abc+', 'xyab', '0!t');
+-- expectIndices 29.8 t abc+ xyabc {2 4} {2 4}
+select * from test_regex('abc+', 'xyabc', '0t');
+-- knownBug expectIndices 29.9 t abc+ xyabcd {2 4} {6 5}
+select * from test_regex('abc+', 'xyabcd', '0t');
+-- expectIndices 29.10 t abc+ xyabcdd {2 4} {7 6}
+select * from test_regex('abc+', 'xyabcdd', '0t');
+-- expectPartial 29.11 tPT abc+? xyab {2 3} ""
+select * from test_regex('abc+?', 'xyab', '0!tPT');
+-- # the retain numbers in these two may look wrong, but they aren't
+-- expectIndices 29.12 tPT abc+? xyabc {2 4} {5 4}
+select * from test_regex('abc+?', 'xyabc', '0tPT');
+-- expectIndices 29.13 tPT abc+? xyabcc {2 4} {6 5}
+select * from test_regex('abc+?', 'xyabcc', '0tPT');
+-- expectIndices 29.14 tPT abc+? xyabcd {2 4} {6 5}
+select * from test_regex('abc+?', 'xyabcd', '0tPT');
+-- expectIndices 29.15 tPT abc+? xyabcdd {2 4} {7 6}
+select * from test_regex('abc+?', 'xyabcdd', '0tPT');
+-- expectIndices 29.16 t abcd|bc xyabc {3 4} {2 4}
+select * from test_regex('abcd|bc', 'xyabc', '0t');
+-- expectPartial 29.17 tn .*k "xx\nyyy" {3 5} ""
+select * from test_regex('.*k', E'xx\nyyy', '0!tn');
+
+-- doing 30 "misc. oddities and old bugs"
+
+-- expectError 30.1 & *** BADRPT
+select * from test_regex('***', '', '');
+select * from test_regex('***', '', 'b');
+-- expectMatch 30.2 N a?b* abb abb
+select * from test_regex('a?b*', 'abb', 'N');
+-- expectMatch 30.3 N a?b* bb bb
+select * from test_regex('a?b*', 'bb', 'N');
+-- expectMatch 30.4 & a*b aab aab
+select * from test_regex('a*b', 'aab', '');
+select * from test_regex('a*b', 'aab', 'b');
+-- expectMatch 30.5 & ^a*b aaaab aaaab
+select * from test_regex('^a*b', 'aaaab', '');
+select * from test_regex('^a*b', 'aaaab', 'b');
+-- expectMatch 30.6 &M {[0-6][1-2][0-3][0-6][1-6][0-6]} \
+-- "010010" 010010
+select * from test_regex('[0-6][1-2][0-3][0-6][1-6][0-6]', '010010', 'M');
+select * from test_regex('[0-6][1-2][0-3][0-6][1-6][0-6]', '010010', 'Mb');
+-- # temporary REG_BOSONLY kludge
+-- expectMatch 30.7 s abc abcd abc
+select * from test_regex('abc', 'abcd', 's');
+-- expectNomatch 30.8 s abc xabcd
+select * from test_regex('abc', 'xabcd', 's');
+-- # back to normal stuff
+-- expectMatch 30.9 HLP {(?n)^(?![t#])\S+} \
+-- "tk\n\n#\n#\nit0" it0
+select * from test_regex('(?n)^(?![t#])\S+', E'tk\n\n#\n#\nit0', 'HLP');
+
+-- # Now for tests *not* written by Henry Spencer
+
+-- # Tests resulting from bugs reported by users
+-- test reg-31.1 {[[:xdigit:]] behaves correctly when followed by [[:space:]]} {
+-- set str {2:::DebugWin32}
+-- set re {([[:xdigit:]])([[:space:]]*)}
+-- list [regexp $re $str match xdigit spaces] $match $xdigit $spaces
+-- # Code used to produce {1 2:::DebugWin32 2 :::DebugWin32} !!!
+-- } {1 2 2 {}}
+select * from test_regex('([[:xdigit:]])([[:space:]]*)', '2:::DebugWin32', 'L');
+
+-- test reg-32.1 {canmatch functionality -- at end} testregexp {
+-- set pat {blah}
+-- set line "asd asd"
+-- # can match at the final d, if '%' follows
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 7}
+select * from test_regex('blah', 'asd asd', 'c');
+
+-- test reg-32.2 {canmatch functionality -- at end} testregexp {
+-- set pat {s%$}
+-- set line "asd asd"
+-- # can only match after the end of the string
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 7}
+select * from test_regex('s%$', 'asd asd', 'c');
+
+-- test reg-32.3 {canmatch functionality -- not last char} testregexp {
+-- set pat {[^d]%$}
+-- set line "asd asd"
+-- # can only match after the end of the string
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 7}
+select * from test_regex('[^d]%$', 'asd asd', 'c');
+
+-- test reg-32.3.1 {canmatch functionality -- no match} testregexp {
+-- set pat {\Zx}
+-- set line "asd asd"
+-- # can match the last char, if followed by x
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 -1}
+select * from test_regex('\Zx', 'asd asd', 'cIP');
+
+-- test reg-32.4 {canmatch functionality -- last char} {knownBug testregexp} {
+-- set pat {.x}
+-- set line "asd asd"
+-- # can match the last char, if followed by x
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('.x', 'asd asd', 'c');
+
+-- test reg-32.4.1 {canmatch functionality -- last char} {knownBug testregexp} {
+-- set pat {.x$}
+-- set line "asd asd"
+-- # can match the last char, if followed by x
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('.x$', 'asd asd', 'c');
+
+-- test reg-32.5 {canmatch functionality -- last char} {knownBug testregexp} {
+-- set pat {.[^d]x$}
+-- set line "asd asd"
+-- # can match the last char, if followed by not-d and x.
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('.[^d]x$', 'asd asd', 'c');
+
+-- test reg-32.6 {canmatch functionality -- last char} {knownBug testregexp} {
+-- set pat {[^a]%[^\r\n]*$}
+-- set line "asd asd"
+-- # can match at the final d, if '%' follows
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('[^a]%[^\r\n]*$', 'asd asd', 'cEP');
+
+-- test reg-32.7 {canmatch functionality -- last char} {knownBug testregexp} {
+-- set pat {[^a]%$}
+-- set line "asd asd"
+-- # can match at the final d, if '%' follows
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('[^a]%$', 'asd asd', 'c');
+
+-- test reg-32.8 {canmatch functionality -- last char} {knownBug testregexp} {
+-- set pat {[^x]%$}
+-- set line "asd asd"
+-- # can match at the final d, if '%' follows
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('[^x]%$', 'asd asd', 'c');
+
+-- test reg-32.9 {canmatch functionality -- more complex case} {knownBug testregexp} {
+-- set pat {((\B\B|\Bh+line)[ \t]*|[^\B]%[^\r\n]*)$}
+-- set line "asd asd"
+-- # can match at the final d, if '%' follows
+-- set res [testregexp -xflags -- c $pat $line resvar]
+-- lappend res $resvar
+-- } {0 6}
+select * from test_regex('((\B\B|\Bh+line)[ \t]*|[^\B]%[^\r\n]*)$', 'asd asd', 'cEP');
+
+-- # Tests reg-33.*: Checks for bug fixes
+
+-- test reg-33.1 {Bug 230589} {
+-- regexp {[ ]*(^|[^%])%V} "*%V2" m s
+-- } 1
+select * from test_regex('[ ]*(^|[^%])%V', '*%V2', '-');
+
+-- test reg-33.2 {Bug 504785} {
+-- regexp -inline {([^_.]*)([^.]*)\.(..)(.).*} bbcos_001_c01.q1la
+-- } {bbcos_001_c01.q1la bbcos _001_c01 q1 l}
+select * from test_regex('([^_.]*)([^.]*)\.(..)(.).*', 'bbcos_001_c01.q1la', '-');
+
+-- test reg-33.3 {Bug 505048} {
+-- regexp {\A\s*[^<]*\s*<([^>]+)>} a<a>
+-- } 1
+select * from test_regex('\A\s*[^<]*\s*<([^>]+)>', 'a<a>', 'LP');
+
+-- test reg-33.4 {Bug 505048} {
+-- regexp {\A\s*([^b]*)b} ab
+-- } 1
+select * from test_regex('\A\s*([^b]*)b', 'ab', 'LP');
+
+-- test reg-33.5 {Bug 505048} {
+-- regexp {\A\s*[^b]*(b)} ab
+-- } 1
+select * from test_regex('\A\s*[^b]*(b)', 'ab', 'LP');
+
+-- test reg-33.6 {Bug 505048} {
+-- regexp {\A(\s*)[^b]*(b)} ab
+-- } 1
+select * from test_regex('\A(\s*)[^b]*(b)', 'ab', 'LP');
+
+-- test reg-33.7 {Bug 505048} {
+-- regexp {\A\s*[^b]*b} ab
+-- } 1
+select * from test_regex('\A\s*[^b]*b', 'ab', 'LP');
+
+-- test reg-33.8 {Bug 505048} {
+-- regexp -inline {\A\s*[^b]*b} ab
+-- } ab
+select * from test_regex('\A\s*[^b]*b', 'ab', 'LP');
+
+-- test reg-33.9 {Bug 505048} {
+-- regexp -indices -inline {\A\s*[^b]*b} ab
+-- } {{0 1}}
+select * from test_regex('\A\s*[^b]*b', 'ab', '0LP');
+
+-- test reg-33.10 {Bug 840258} -body {
+-- regsub {(^|\n)+\.*b} \n.b {} tmp
+-- } -cleanup {
+-- unset tmp
+-- } -result 1
+select * from test_regex('(^|\n)+\.*b', E'\n.b', 'P');
+
+-- test reg-33.11 {Bug 840258} -body {
+-- regsub {(^|[\n\r]+)\.*\?<.*?(\n|\r)+} \
+-- "TQ\r\n.?<5000267>Test already stopped\r\n" {} tmp
+-- } -cleanup {
+-- unset tmp
+-- } -result 1
+select * from test_regex('(^|[\n\r]+)\.*\?<.*?(\n|\r)+', E'TQ\r\n.?<5000267>Test already stopped\r\n', 'EP');
+
+-- test reg-33.12 {Bug 1810264 - bad read} {
+-- regexp {\3161573148} {\3161573148}
+-- } 0
+select * from test_regex('\3161573148', '\3161573148', 'MP');
+
+-- test reg-33.13 {Bug 1810264 - infinite loop} {
+-- regexp {($|^)*} {x}
+-- } 1
+select * from test_regex('($|^)*', 'x', 'N');
+
+-- # Some environments have small default stack sizes. [Bug 1905562]
+-- test reg-33.14 {Bug 1810264 - super-expensive expression} nonPortable {
+-- regexp {(x{200}){200}$y} {x}
+-- } 0
+-- This might or might not work depending on platform, so skip it
+-- select * from test_regex('(x{200}){200}$y', 'x', 'IQ');
+
+-- test reg-33.15.1 {Bug 3603557 - an "in the wild" RE} {
+-- lindex [regexp -expanded -about {
+-- ^TETRA_MODE_CMD # Message Type
+-- ([[:blank:]]+) # Pad
+-- (ETS_1_1|ETS_1_2|ETS_2_2) # SystemCode
+-- ([[:blank:]]+) # Pad
+-- (CONTINUOUS|CARRIER|MCCH|TRAFFIC) # SharingMode
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,2}) # ColourCode
+-- ([[:blank:]]+) # Pad
+-- (1|2|3|4|6|9|12|18) # TSReservedFrames
+-- ([[:blank:]]+) # Pad
+-- (PASS|TRUE|FAIL|FALSE) # UPlaneDTX
+-- ([[:blank:]]+) # Pad
+-- (PASS|TRUE|FAIL|FALSE) # Frame18Extension
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,4}) # MCC
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,5}) # MNC
+-- ([[:blank:]]+) # Pad
+-- (BOTH|BCAST|ENQRY|NONE) # NbrCellBcast
+-- ([[:blank:]]+) # Pad
+-- (UNKNOWN|LOW|MEDIUM|HIGH) # CellServiceLevel
+-- ([[:blank:]]+) # Pad
+-- (PASS|TRUE|FAIL|FALSE) # LateEntryInfo
+-- ([[:blank:]]+) # Pad
+-- (300|400) # FrequencyBand
+-- ([[:blank:]]+) # Pad
+-- (NORMAL|REVERSE) # ReverseOperation
+-- ([[:blank:]]+) # Pad
+-- (NONE|\+6\.25|\-6\.25|\+12\.5) # Offset
+-- ([[:blank:]]+) # Pad
+-- (10) # DuplexSpacing
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,4}) # MainCarrierNr
+-- ([[:blank:]]+) # Pad
+-- (0|1|2|3) # NrCSCCH
+-- ([[:blank:]]+) # Pad
+-- (15|20|25|30|35|40|45) # MSTxPwrMax
+-- ([[:blank:]]+) # Pad
+-- (\-125|\-120|\-115|\-110|\-105|\-100|\-95|\-90|\-85|\-80|\-75|\-70|\-65|\-60|\-55|\-50)
+-- # RxLevAccessMin
+-- ([[:blank:]]+) # Pad
+-- (\-53|\-51|\-49|\-47|\-45|\-43|\-41|\-39|\-37|\-35|\-33|\-31|\-29|\-27|\-25|\-23)
+-- # AccessParameter
+-- ([[:blank:]]+) # Pad
+-- (DISABLE|[[:digit:]]{3,4}) # RadioDLTimeout
+-- ([[:blank:]]+) # Pad
+-- (\-[[:digit:]]{2,3}) # RSSIThreshold
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,5}) # CCKIdSCKVerNr
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,5}) # LocationArea
+-- ([[:blank:]]+) # Pad
+-- ([(1|0)]{16}) # SubscriberClass
+-- ([[:blank:]]+) # Pad
+-- ([(1|0)]{12}) # BSServiceDetails
+-- ([[:blank:]]+) # Pad
+-- (RANDOMIZE|IMMEDIATE|[[:digit:]]{1,2}) # IMM
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,2}) # WT
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,2}) # Nu
+-- ([[:blank:]]+) # Pad
+-- ([0-1]) # FrameLngFctr
+-- ([[:blank:]]+) # Pad
+-- ([[:digit:]]{1,2}) # TSPtr
+-- ([[:blank:]]+) # Pad
+-- ([0-7]) # MinPriority
+-- ([[:blank:]]+) # Pad
+-- (PASS|TRUE|FAIL|FALSE) # ExtdSrvcsEnabled
+-- ([[:blank:]]+) # Pad
+-- (.*) # ConditionalFields
+-- }] 0
+-- } 68
+select * from test_regex($$
+ ^TETRA_MODE_CMD # Message Type
+ ([[:blank:]]+) # Pad
+ (ETS_1_1|ETS_1_2|ETS_2_2) # SystemCode
+ ([[:blank:]]+) # Pad
+ (CONTINUOUS|CARRIER|MCCH|TRAFFIC) # SharingMode
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,2}) # ColourCode
+ ([[:blank:]]+) # Pad
+ (1|2|3|4|6|9|12|18) # TSReservedFrames
+ ([[:blank:]]+) # Pad
+ (PASS|TRUE|FAIL|FALSE) # UPlaneDTX
+ ([[:blank:]]+) # Pad
+ (PASS|TRUE|FAIL|FALSE) # Frame18Extension
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,4}) # MCC
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,5}) # MNC
+ ([[:blank:]]+) # Pad
+ (BOTH|BCAST|ENQRY|NONE) # NbrCellBcast
+ ([[:blank:]]+) # Pad
+ (UNKNOWN|LOW|MEDIUM|HIGH) # CellServiceLevel
+ ([[:blank:]]+) # Pad
+ (PASS|TRUE|FAIL|FALSE) # LateEntryInfo
+ ([[:blank:]]+) # Pad
+ (300|400) # FrequencyBand
+ ([[:blank:]]+) # Pad
+ (NORMAL|REVERSE) # ReverseOperation
+ ([[:blank:]]+) # Pad
+ (NONE|\+6\.25|\-6\.25|\+12\.5) # Offset
+ ([[:blank:]]+) # Pad
+ (10) # DuplexSpacing
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,4}) # MainCarrierNr
+ ([[:blank:]]+) # Pad
+ (0|1|2|3) # NrCSCCH
+ ([[:blank:]]+) # Pad
+ (15|20|25|30|35|40|45) # MSTxPwrMax
+ ([[:blank:]]+) # Pad
+ (\-125|\-120|\-115|\-110|\-105|\-100|\-95|\-90|\-85|\-80|\-75|\-70|\-65|\-60|\-55|\-50)
+ # RxLevAccessMin
+ ([[:blank:]]+) # Pad
+ (\-53|\-51|\-49|\-47|\-45|\-43|\-41|\-39|\-37|\-35|\-33|\-31|\-29|\-27|\-25|\-23)
+ # AccessParameter
+ ([[:blank:]]+) # Pad
+ (DISABLE|[[:digit:]]{3,4}) # RadioDLTimeout
+ ([[:blank:]]+) # Pad
+ (\-[[:digit:]]{2,3}) # RSSIThreshold
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,5}) # CCKIdSCKVerNr
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,5}) # LocationArea
+ ([[:blank:]]+) # Pad
+ ([(1|0)]{16}) # SubscriberClass
+ ([[:blank:]]+) # Pad
+ ([(1|0)]{12}) # BSServiceDetails
+ ([[:blank:]]+) # Pad
+ (RANDOMIZE|IMMEDIATE|[[:digit:]]{1,2}) # IMM
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,2}) # WT
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,2}) # Nu
+ ([[:blank:]]+) # Pad
+ ([0-1]) # FrameLngFctr
+ ([[:blank:]]+) # Pad
+ ([[:digit:]]{1,2}) # TSPtr
+ ([[:blank:]]+) # Pad
+ ([0-7]) # MinPriority
+ ([[:blank:]]+) # Pad
+ (PASS|TRUE|FAIL|FALSE) # ExtdSrvcsEnabled
+ ([[:blank:]]+) # Pad
+ (.*) # ConditionalFields
+ $$, '', 'xLMPQ');
+
+-- test reg-33.16.1 {Bug [8d2c0da36d]- another "in the wild" RE} {
+-- lindex [regexp -about "^MRK:client1: =1339 14HKelly Talisman 10011000 (\[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]*) \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 8 0 8 0 0 0 77 77 1 1 2 0 11 { 1 3 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 13HC6 My Creator 2 3 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 31HC7 Slightly offensive name, huh 3 8 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 23HE-mail:kelly@hotbox.com 4 9 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 17Hcompface must die 5 10 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 0 3HAir 6 12 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 14HPGP public key 7 13 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 16Hkelly@hotbox.com 8 30 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 0 12H2 text/plain 9 30 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 0 13H2 x-kom/basic 10 33 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 1H0 11 14 8 \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* \[0-9\]* 00000000 1 1H3 }\r?"] 0
+-- } 1
+select * from test_regex(E'^MRK:client1: =1339 14HKelly Talisman 10011000 ([0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]*) [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 8 0 8 0 0 0 77 77 1 1 2 0 11 { 1 3 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 13HC6 My Creator 2 3 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 31HC7 Slightly offensive name, huh 3 8 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 23HE-mail:kelly@hotbox.com 4 9 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 17Hcompface must die 5 10 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 0 3HAir 6 12 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 14HPGP public key 7 13 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 16Hkelly@hotbox.com 8 30 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 0 12H2 text/plain 9 30 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 0 13H2 x-kom/basic 10 33 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 1H0 11 14 8 [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* [0-9]* 00000000 1 1H3 }\r?', '', 'BMS');
+
+-- test reg-33.15 {constraint fixes} {
+-- regexp {(^)+^} x
+-- } 1
+select * from test_regex('(^)+^', 'x', 'N');
+
+-- test reg-33.16 {constraint fixes} {
+-- regexp {($^)+} x
+-- } 0
+select * from test_regex('($^)+', 'x', 'N');
+
+-- test reg-33.17 {constraint fixes} {
+-- regexp {(^$)*} x
+-- } 1
+select * from test_regex('(^$)*', 'x', 'N');
+
+-- test reg-33.18 {constraint fixes} {
+-- regexp {(^(?!aa))+} {aa bb cc}
+-- } 0
+select * from test_regex('(^(?!aa))+', 'aa bb cc', 'HP');
+
+-- test reg-33.19 {constraint fixes} {
+-- regexp {(^(?!aa)(?!bb)(?!cc))+} {aa x}
+-- } 0
+select * from test_regex('(^(?!aa)(?!bb)(?!cc))+', 'aa x', 'HP');
+
+-- test reg-33.20 {constraint fixes} {
+-- regexp {(^(?!aa)(?!bb)(?!cc))+} {bb x}
+-- } 0
+select * from test_regex('(^(?!aa)(?!bb)(?!cc))+', 'bb x', 'HP');
+
+-- test reg-33.21 {constraint fixes} {
+-- regexp {(^(?!aa)(?!bb)(?!cc))+} {cc x}
+-- } 0
+select * from test_regex('(^(?!aa)(?!bb)(?!cc))+', 'cc x', 'HP');
+
+-- test reg-33.22 {constraint fixes} {
+-- regexp {(^(?!aa)(?!bb)(?!cc))+} {dd x}
+-- } 1
+select * from test_regex('(^(?!aa)(?!bb)(?!cc))+', 'dd x', 'HP');
+
+-- test reg-33.23 {} {
+-- regexp {abcd(\m)+xyz} x
+-- } 0
+select * from test_regex('abcd(\m)+xyz', 'x', 'ILP');
+
+-- test reg-33.24 {} {
+-- regexp {abcd(\m)+xyz} a
+-- } 0
+select * from test_regex('abcd(\m)+xyz', 'a', 'ILP');
+
+-- test reg-33.25 {} {
+-- regexp {^abcd*(((((^(a c(e?d)a+|)+|)+|)+|)+|a)+|)} x
+-- } 0
+select * from test_regex('^abcd*(((((^(a c(e?d)a+|)+|)+|)+|)+|a)+|)', 'x', 'S');
+
+-- test reg-33.26 {} {
+-- regexp {a^(^)bcd*xy(((((($a+|)+|)+|)+$|)+|)+|)^$} x
+-- } 0
+select * from test_regex('a^(^)bcd*xy(((((($a+|)+|)+|)+$|)+|)+|)^$', 'x', 'IS');
+
+-- test reg-33.27 {} {
+-- regexp {xyz(\Y\Y)+} x
+-- } 0
+select * from test_regex('xyz(\Y\Y)+', 'x', 'LP');
+
+-- test reg-33.28 {} {
+-- regexp {x|(?:\M)+} x
+-- } 1
+select * from test_regex('x|(?:\M)+', 'x', 'LNP');
+
+-- test reg-33.29 {} {
+-- # This is near the limits of the RE engine
+-- regexp [string repeat x*y*z* 480] x
+-- } 1
+-- The runtime cost of this seems out of proportion to the value,
+-- so for Postgres purposes reduce the repeat to 200x
+select * from test_regex(repeat('x*y*z*', 200), 'x', 'N');
+
+-- test reg-33.30 {Bug 1080042} {
+-- regexp {(\Y)+} foo
+-- } 1
+select * from test_regex('(\Y)+', 'foo', 'LNP');
diff --git a/src/test/modules/test_regex/sql/test_regex_utf8.sql b/src/test/modules/test_regex/sql/test_regex_utf8.sql
new file mode 100644
index 0000000..cfd9396
--- /dev/null
+++ b/src/test/modules/test_regex/sql/test_regex_utf8.sql
@@ -0,0 +1,60 @@
+/*
+ * This test must be run in a database with UTF-8 encoding,
+ * because other encodings don't support all the characters used.
+ */
+
+SELECT getdatabaseencoding() <> 'UTF8'
+ AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+
+set client_encoding = utf8;
+
+set standard_conforming_strings = on;
+
+
+-- Run the Tcl test cases that require Unicode
+
+-- expectMatch 9.44 EMP* {a[\u00fe-\u0507][\u00ff-\u0300]b} \
+-- "a\u0102\u02ffb" "a\u0102\u02ffb"
+select * from test_regex('a[\u00fe-\u0507][\u00ff-\u0300]b', E'a\u0102\u02ffb', 'EMP*');
+
+-- expectMatch 13.27 P "a\\U00001234x" "a\u1234x" "a\u1234x"
+select * from test_regex('a\U00001234x', E'a\u1234x', 'P');
+-- expectMatch 13.28 P {a\U00001234x} "a\u1234x" "a\u1234x"
+select * from test_regex('a\U00001234x', E'a\u1234x', 'P');
+-- expectMatch 13.29 P "a\\U0001234x" "a\u1234x" "a\u1234x"
+-- Tcl has relaxed their code to allow 1-8 hex digits, but Postgres hasn't
+select * from test_regex('a\U0001234x', E'a\u1234x', 'P');
+-- expectMatch 13.30 P {a\U0001234x} "a\u1234x" "a\u1234x"
+-- Tcl has relaxed their code to allow 1-8 hex digits, but Postgres hasn't
+select * from test_regex('a\U0001234x', E'a\u1234x', 'P');
+-- expectMatch 13.31 P "a\\U000012345x" "a\u12345x" "a\u12345x"
+select * from test_regex('a\U000012345x', E'a\u12345x', 'P');
+-- expectMatch 13.32 P {a\U000012345x} "a\u12345x" "a\u12345x"
+select * from test_regex('a\U000012345x', E'a\u12345x', 'P');
+-- expectMatch 13.33 P "a\\U1000000x" "a\ufffd0x" "a\ufffd0x"
+-- Tcl allows this as a standalone character, but Postgres doesn't
+select * from test_regex('a\U1000000x', E'a\ufffd0x', 'P');
+-- expectMatch 13.34 P {a\U1000000x} "a\ufffd0x" "a\ufffd0x"
+-- Tcl allows this as a standalone character, but Postgres doesn't
+select * from test_regex('a\U1000000x', E'a\ufffd0x', 'P');
+
+
+-- Additional tests, not derived from Tcl
+
+-- Exercise logic around high character ranges a bit more
+select * from test_regex('a
+ [\u1000-\u1100]*
+ [\u3000-\u3100]*
+ [\u1234-\u25ff]+
+ [\u2000-\u35ff]*
+ [\u2600-\u2f00]*
+ \u1236\u1236x',
+ E'a\u1234\u1236\u1236x', 'xEMP');
+
+select * from test_regex('[[:alnum:]]*[[:upper:]]*[\u1000-\u2000]*\u1237',
+ E'\u1500\u1237', 'ELMP');
+select * from test_regex('[[:alnum:]]*[[:upper:]]*[\u1000-\u2000]*\u1237',
+ E'A\u1239', 'ELMP');
diff --git a/src/test/modules/test_regex/test_regex--1.0.sql b/src/test/modules/test_regex/test_regex--1.0.sql
new file mode 100644
index 0000000..7d99153
--- /dev/null
+++ b/src/test/modules/test_regex/test_regex--1.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_regex/test_regex--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_regex" to load this file. \quit
+
+CREATE FUNCTION test_regex(pattern text, string text, flags text)
+RETURNS SETOF text[]
+STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_regex/test_regex.c b/src/test/modules/test_regex/test_regex.c
new file mode 100644
index 0000000..095751c
--- /dev/null
+++ b/src/test/modules/test_regex/test_regex.c
@@ -0,0 +1,773 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_regex.c
+ * Test harness for the regular expression package.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_regex/test_regex.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "regex/regex.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+
+PG_MODULE_MAGIC;
+
+
+/* all the options of interest for regex functions */
+typedef struct test_re_flags
+{
+ int cflags; /* compile flags for Spencer's regex code */
+ int eflags; /* execute flags for Spencer's regex code */
+ long info; /* expected re_info bits */
+ bool glob; /* do it globally (for each occurrence) */
+ bool indices; /* report indices not actual strings */
+ bool partial; /* expect partial match */
+} test_re_flags;
+
+/* cross-call state for test_regex() */
+typedef struct test_regex_ctx
+{
+ test_re_flags re_flags; /* flags */
+ rm_detail_t details; /* "details" from execution */
+ text *orig_str; /* data string in original TEXT form */
+ int nmatches; /* number of places where pattern matched */
+ int npatterns; /* number of capturing subpatterns */
+ /* We store start char index and end+1 char index for each match */
+ /* so the number of entries in match_locs is nmatches * npatterns * 2 */
+ int *match_locs; /* 0-based character indexes */
+ int next_match; /* 0-based index of next match to process */
+ /* workspace for build_test_match_result() */
+ Datum *elems; /* has npatterns+1 elements */
+ bool *nulls; /* has npatterns+1 elements */
+ pg_wchar *wide_str; /* wide-char version of original string */
+ char *conv_buf; /* conversion buffer, if needed */
+ int conv_bufsiz; /* size thereof */
+} test_regex_ctx;
+
+/* Local functions */
+static void test_re_compile(text *text_re, int cflags, Oid collation,
+ regex_t *result_re);
+static void parse_test_flags(test_re_flags *flags, text *opts);
+static test_regex_ctx *setup_test_matches(text *orig_str,
+ regex_t *cpattern,
+ test_re_flags *flags,
+ Oid collation,
+ bool use_subpatterns);
+static ArrayType *build_test_info_result(regex_t *cpattern,
+ test_re_flags *flags);
+static ArrayType *build_test_match_result(test_regex_ctx *matchctx);
+
+
+/*
+ * test_regex(pattern text, string text, flags text) returns setof text[]
+ *
+ * This is largely based on regexp.c's regexp_matches, with additions
+ * for debugging purposes.
+ */
+PG_FUNCTION_INFO_V1(test_regex);
+
+Datum
+test_regex(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ test_regex_ctx *matchctx;
+ ArrayType *result_ary;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ text *pattern = PG_GETARG_TEXT_PP(0);
+ text *flags = PG_GETARG_TEXT_PP(2);
+ Oid collation = PG_GET_COLLATION();
+ test_re_flags re_flags;
+ regex_t cpattern;
+ MemoryContext oldcontext;
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ /* Determine options */
+ parse_test_flags(&re_flags, flags);
+
+ /* set up the compiled pattern */
+ test_re_compile(pattern, re_flags.cflags, collation, &cpattern);
+
+ /* be sure to copy the input string into the multi-call ctx */
+ matchctx = setup_test_matches(PG_GETARG_TEXT_P_COPY(1), &cpattern,
+ &re_flags,
+ collation,
+ true);
+
+ /* Pre-create workspace that build_test_match_result needs */
+ matchctx->elems = (Datum *) palloc(sizeof(Datum) *
+ (matchctx->npatterns + 1));
+ matchctx->nulls = (bool *) palloc(sizeof(bool) *
+ (matchctx->npatterns + 1));
+
+ MemoryContextSwitchTo(oldcontext);
+ funcctx->user_fctx = (void *) matchctx;
+
+ /*
+ * Return the first result row, which is info equivalent to Tcl's
+ * "regexp -about" output
+ */
+ result_ary = build_test_info_result(&cpattern, &re_flags);
+
+ pg_regfree(&cpattern);
+
+ SRF_RETURN_NEXT(funcctx, PointerGetDatum(result_ary));
+ }
+ else
+ {
+ /* Each subsequent row describes one match */
+ funcctx = SRF_PERCALL_SETUP();
+ matchctx = (test_regex_ctx *) funcctx->user_fctx;
+
+ if (matchctx->next_match < matchctx->nmatches)
+ {
+ result_ary = build_test_match_result(matchctx);
+ matchctx->next_match++;
+ SRF_RETURN_NEXT(funcctx, PointerGetDatum(result_ary));
+ }
+ }
+
+ SRF_RETURN_DONE(funcctx);
+}
+
+
+/*
+ * test_re_compile - compile a RE
+ *
+ * text_re --- the pattern, expressed as a TEXT object
+ * cflags --- compile options for the pattern
+ * collation --- collation to use for LC_CTYPE-dependent behavior
+ * result_re --- output, compiled RE is stored here
+ *
+ * Pattern is given in the database encoding. We internally convert to
+ * an array of pg_wchar, which is what Spencer's regex package wants.
+ *
+ * Caller must eventually pg_regfree the resulting RE to avoid memory leaks.
+ */
+static void
+test_re_compile(text *text_re, int cflags, Oid collation,
+ regex_t *result_re)
+{
+ int text_re_len = VARSIZE_ANY_EXHDR(text_re);
+ char *text_re_val = VARDATA_ANY(text_re);
+ pg_wchar *pattern;
+ int pattern_len;
+ int regcomp_result;
+ char errMsg[100];
+
+ /* Convert pattern string to wide characters */
+ pattern = (pg_wchar *) palloc((text_re_len + 1) * sizeof(pg_wchar));
+ pattern_len = pg_mb2wchar_with_len(text_re_val,
+ pattern,
+ text_re_len);
+
+ regcomp_result = pg_regcomp(result_re,
+ pattern,
+ pattern_len,
+ cflags,
+ collation);
+
+ pfree(pattern);
+
+ if (regcomp_result != REG_OKAY)
+ {
+ /* re didn't compile (no need for pg_regfree, if so) */
+
+ /*
+ * Here and in other places in this file, do CHECK_FOR_INTERRUPTS
+ * before reporting a regex error. This is so that if the regex
+ * library aborts and returns REG_CANCEL, we don't print an error
+ * message that implies the regex was invalid.
+ */
+ CHECK_FOR_INTERRUPTS();
+
+ pg_regerror(regcomp_result, result_re, errMsg, sizeof(errMsg));
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+ errmsg("invalid regular expression: %s", errMsg)));
+ }
+}
+
+/*
+ * test_re_execute - execute a RE on pg_wchar data
+ *
+ * Returns true on match, false on no match
+ * Arguments are as for pg_regexec
+ */
+static bool
+test_re_execute(regex_t *re, pg_wchar *data, int data_len,
+ int start_search,
+ rm_detail_t *details,
+ int nmatch, regmatch_t *pmatch,
+ int eflags)
+{
+ int regexec_result;
+ char errMsg[100];
+
+ /* Initialize match locations in case engine doesn't */
+ details->rm_extend.rm_so = -1;
+ details->rm_extend.rm_eo = -1;
+ for (int i = 0; i < nmatch; i++)
+ {
+ pmatch[i].rm_so = -1;
+ pmatch[i].rm_eo = -1;
+ }
+
+ /* Perform RE match and return result */
+ regexec_result = pg_regexec(re,
+ data,
+ data_len,
+ start_search,
+ details,
+ nmatch,
+ pmatch,
+ eflags);
+
+ if (regexec_result != REG_OKAY && regexec_result != REG_NOMATCH)
+ {
+ /* re failed??? */
+ CHECK_FOR_INTERRUPTS();
+ pg_regerror(regexec_result, re, errMsg, sizeof(errMsg));
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+ errmsg("regular expression failed: %s", errMsg)));
+ }
+
+ return (regexec_result == REG_OKAY);
+}
+
+
+/*
+ * parse_test_flags - parse the flags argument
+ *
+ * flags --- output argument, filled with desired options
+ * opts --- TEXT object, or NULL for defaults
+ */
+static void
+parse_test_flags(test_re_flags *flags, text *opts)
+{
+ /* these defaults must match Tcl's */
+ int cflags = REG_ADVANCED;
+ int eflags = 0;
+ long info = 0;
+
+ flags->glob = false;
+ flags->indices = false;
+ flags->partial = false;
+
+ if (opts)
+ {
+ char *opt_p = VARDATA_ANY(opts);
+ int opt_len = VARSIZE_ANY_EXHDR(opts);
+ int i;
+
+ for (i = 0; i < opt_len; i++)
+ {
+ switch (opt_p[i])
+ {
+ case '-':
+ /* allowed, no-op */
+ break;
+ case '!':
+ flags->partial = true;
+ break;
+ case '*':
+ /* test requires Unicode --- ignored here */
+ break;
+ case '0':
+ flags->indices = true;
+ break;
+
+ /* These flags correspond to user-exposed RE options: */
+ case 'g': /* global match */
+ flags->glob = true;
+ break;
+ case 'i': /* case insensitive */
+ cflags |= REG_ICASE;
+ break;
+ case 'n': /* \n affects ^ $ . [^ */
+ cflags |= REG_NEWLINE;
+ break;
+ case 'p': /* ~Perl, \n affects . [^ */
+ cflags |= REG_NLSTOP;
+ cflags &= ~REG_NLANCH;
+ break;
+ case 'w': /* weird, \n affects ^ $ only */
+ cflags &= ~REG_NLSTOP;
+ cflags |= REG_NLANCH;
+ break;
+ case 'x': /* expanded syntax */
+ cflags |= REG_EXPANDED;
+ break;
+
+ /* These flags correspond to Tcl's -xflags options: */
+ case 'a':
+ cflags |= REG_ADVF;
+ break;
+ case 'b':
+ cflags &= ~REG_ADVANCED;
+ break;
+ case 'c':
+
+ /*
+ * Tcl calls this TCL_REG_CANMATCH, but it's really
+ * REG_EXPECT. In this implementation we must also set
+ * the partial and indices flags, so that
+ * setup_test_matches and build_test_match_result will
+ * emit the desired data. (They'll emit more fields than
+ * Tcl would, but that's fine.)
+ */
+ cflags |= REG_EXPECT;
+ flags->partial = true;
+ flags->indices = true;
+ break;
+ case 'e':
+ cflags &= ~REG_ADVANCED;
+ cflags |= REG_EXTENDED;
+ break;
+ case 'q':
+ cflags &= ~REG_ADVANCED;
+ cflags |= REG_QUOTE;
+ break;
+ case 'o': /* o for opaque */
+ cflags |= REG_NOSUB;
+ break;
+ case 's': /* s for start */
+ cflags |= REG_BOSONLY;
+ break;
+ case '+':
+ cflags |= REG_FAKE;
+ break;
+ case ',':
+ cflags |= REG_PROGRESS;
+ break;
+ case '.':
+ cflags |= REG_DUMP;
+ break;
+ case ':':
+ eflags |= REG_MTRACE;
+ break;
+ case ';':
+ eflags |= REG_FTRACE;
+ break;
+ case '^':
+ eflags |= REG_NOTBOL;
+ break;
+ case '$':
+ eflags |= REG_NOTEOL;
+ break;
+ case 't':
+ cflags |= REG_EXPECT;
+ break;
+ case '%':
+ eflags |= REG_SMALL;
+ break;
+
+ /* These flags define expected info bits: */
+ case 'A':
+ info |= REG_UBSALNUM;
+ break;
+ case 'B':
+ info |= REG_UBRACES;
+ break;
+ case 'E':
+ info |= REG_UBBS;
+ break;
+ case 'H':
+ info |= REG_ULOOKAROUND;
+ break;
+ case 'I':
+ info |= REG_UIMPOSSIBLE;
+ break;
+ case 'L':
+ info |= REG_ULOCALE;
+ break;
+ case 'M':
+ info |= REG_UUNPORT;
+ break;
+ case 'N':
+ info |= REG_UEMPTYMATCH;
+ break;
+ case 'P':
+ info |= REG_UNONPOSIX;
+ break;
+ case 'Q':
+ info |= REG_UBOUNDS;
+ break;
+ case 'R':
+ info |= REG_UBACKREF;
+ break;
+ case 'S':
+ info |= REG_UUNSPEC;
+ break;
+ case 'T':
+ info |= REG_USHORTEST;
+ break;
+ case 'U':
+ info |= REG_UPBOTCH;
+ break;
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid regular expression test option: \"%.*s\"",
+ pg_mblen(opt_p + i), opt_p + i)));
+ break;
+ }
+ }
+ }
+ flags->cflags = cflags;
+ flags->eflags = eflags;
+ flags->info = info;
+}
+
+/*
+ * setup_test_matches --- do the initial matching
+ *
+ * To simplify memory management, we do all the matching in one swoop.
+ * The returned test_regex_ctx contains the locations of all the substrings
+ * matching the pattern.
+ */
+static test_regex_ctx *
+setup_test_matches(text *orig_str,
+ regex_t *cpattern, test_re_flags *re_flags,
+ Oid collation,
+ bool use_subpatterns)
+{
+ test_regex_ctx *matchctx = palloc0(sizeof(test_regex_ctx));
+ int eml = pg_database_encoding_max_length();
+ int orig_len;
+ pg_wchar *wide_str;
+ int wide_len;
+ regmatch_t *pmatch;
+ int pmatch_len;
+ int array_len;
+ int array_idx;
+ int prev_match_end;
+ int start_search;
+ int maxlen = 0; /* largest fetch length in characters */
+
+ /* save flags */
+ matchctx->re_flags = *re_flags;
+
+ /* save original string --- we'll extract result substrings from it */
+ matchctx->orig_str = orig_str;
+
+ /* convert string to pg_wchar form for matching */
+ orig_len = VARSIZE_ANY_EXHDR(orig_str);
+ wide_str = (pg_wchar *) palloc(sizeof(pg_wchar) * (orig_len + 1));
+ wide_len = pg_mb2wchar_with_len(VARDATA_ANY(orig_str), wide_str, orig_len);
+
+ /* do we want to remember subpatterns? */
+ if (use_subpatterns && cpattern->re_nsub > 0)
+ {
+ matchctx->npatterns = cpattern->re_nsub + 1;
+ pmatch_len = cpattern->re_nsub + 1;
+ }
+ else
+ {
+ use_subpatterns = false;
+ matchctx->npatterns = 1;
+ pmatch_len = 1;
+ }
+
+ /* temporary output space for RE package */
+ pmatch = palloc(sizeof(regmatch_t) * pmatch_len);
+
+ /*
+ * the real output space (grown dynamically if needed)
+ *
+ * use values 2^n-1, not 2^n, so that we hit the limit at 2^28-1 rather
+ * than at 2^27
+ */
+ array_len = re_flags->glob ? 255 : 31;
+ matchctx->match_locs = (int *) palloc(sizeof(int) * array_len);
+ array_idx = 0;
+
+ /* search for the pattern, perhaps repeatedly */
+ prev_match_end = 0;
+ start_search = 0;
+ while (test_re_execute(cpattern, wide_str, wide_len,
+ start_search,
+ &matchctx->details,
+ pmatch_len, pmatch,
+ re_flags->eflags))
+ {
+ /* enlarge output space if needed */
+ while (array_idx + matchctx->npatterns * 2 + 1 > array_len)
+ {
+ array_len += array_len + 1; /* 2^n-1 => 2^(n+1)-1 */
+ if (array_len > MaxAllocSize / sizeof(int))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("too many regular expression matches")));
+ matchctx->match_locs = (int *) repalloc(matchctx->match_locs,
+ sizeof(int) * array_len);
+ }
+
+ /* save this match's locations */
+ for (int i = 0; i < matchctx->npatterns; i++)
+ {
+ int so = pmatch[i].rm_so;
+ int eo = pmatch[i].rm_eo;
+
+ matchctx->match_locs[array_idx++] = so;
+ matchctx->match_locs[array_idx++] = eo;
+ if (so >= 0 && eo >= 0 && (eo - so) > maxlen)
+ maxlen = (eo - so);
+ }
+ matchctx->nmatches++;
+ prev_match_end = pmatch[0].rm_eo;
+
+ /* if not glob, stop after one match */
+ if (!re_flags->glob)
+ break;
+
+ /*
+ * Advance search position. Normally we start the next search at the
+ * end of the previous match; but if the match was of zero length, we
+ * have to advance by one character, or we'd just find the same match
+ * again.
+ */
+ start_search = prev_match_end;
+ if (pmatch[0].rm_so == pmatch[0].rm_eo)
+ start_search++;
+ if (start_search > wide_len)
+ break;
+ }
+
+ /*
+ * If we had no match, but "partial" and "indices" are set, emit the
+ * details.
+ */
+ if (matchctx->nmatches == 0 && re_flags->partial && re_flags->indices)
+ {
+ /* enlarge output space if needed */
+ while (array_idx + matchctx->npatterns * 2 + 1 > array_len)
+ {
+ array_len += array_len + 1; /* 2^n-1 => 2^(n+1)-1 */
+ if (array_len > MaxAllocSize / sizeof(int))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("too many regular expression matches")));
+ matchctx->match_locs = (int *) repalloc(matchctx->match_locs,
+ sizeof(int) * array_len);
+ }
+
+ matchctx->match_locs[array_idx++] = matchctx->details.rm_extend.rm_so;
+ matchctx->match_locs[array_idx++] = matchctx->details.rm_extend.rm_eo;
+ /* we don't have pmatch data, so emit -1 */
+ for (int i = 1; i < matchctx->npatterns; i++)
+ {
+ matchctx->match_locs[array_idx++] = -1;
+ matchctx->match_locs[array_idx++] = -1;
+ }
+ matchctx->nmatches++;
+ }
+
+ Assert(array_idx <= array_len);
+
+ if (eml > 1)
+ {
+ int64 maxsiz = eml * (int64) maxlen;
+ int conv_bufsiz;
+
+ /*
+ * Make the conversion buffer large enough for any substring of
+ * interest.
+ *
+ * Worst case: assume we need the maximum size (maxlen*eml), but take
+ * advantage of the fact that the original string length in bytes is
+ * an upper bound on the byte length of any fetched substring (and we
+ * know that len+1 is safe to allocate because the varlena header is
+ * longer than 1 byte).
+ */
+ if (maxsiz > orig_len)
+ conv_bufsiz = orig_len + 1;
+ else
+ conv_bufsiz = maxsiz + 1; /* safe since maxsiz < 2^30 */
+
+ matchctx->conv_buf = palloc(conv_bufsiz);
+ matchctx->conv_bufsiz = conv_bufsiz;
+ matchctx->wide_str = wide_str;
+ }
+ else
+ {
+ /* No need to keep the wide string if we're in a single-byte charset. */
+ pfree(wide_str);
+ matchctx->wide_str = NULL;
+ matchctx->conv_buf = NULL;
+ matchctx->conv_bufsiz = 0;
+ }
+
+ /* Clean up temp storage */
+ pfree(pmatch);
+
+ return matchctx;
+}
+
+/*
+ * build_test_info_result - build output array describing compiled regexp
+ *
+ * This borrows some code from Tcl's TclRegAbout().
+ */
+static ArrayType *
+build_test_info_result(regex_t *cpattern, test_re_flags *flags)
+{
+ /* Translation data for flag bits in regex_t.re_info */
+ struct infoname
+ {
+ int bit;
+ const char *text;
+ };
+ static const struct infoname infonames[] = {
+ {REG_UBACKREF, "REG_UBACKREF"},
+ {REG_ULOOKAROUND, "REG_ULOOKAROUND"},
+ {REG_UBOUNDS, "REG_UBOUNDS"},
+ {REG_UBRACES, "REG_UBRACES"},
+ {REG_UBSALNUM, "REG_UBSALNUM"},
+ {REG_UPBOTCH, "REG_UPBOTCH"},
+ {REG_UBBS, "REG_UBBS"},
+ {REG_UNONPOSIX, "REG_UNONPOSIX"},
+ {REG_UUNSPEC, "REG_UUNSPEC"},
+ {REG_UUNPORT, "REG_UUNPORT"},
+ {REG_ULOCALE, "REG_ULOCALE"},
+ {REG_UEMPTYMATCH, "REG_UEMPTYMATCH"},
+ {REG_UIMPOSSIBLE, "REG_UIMPOSSIBLE"},
+ {REG_USHORTEST, "REG_USHORTEST"},
+ {0, NULL}
+ };
+ const struct infoname *inf;
+ Datum elems[lengthof(infonames) + 1];
+ int nresults = 0;
+ char buf[80];
+ int dims[1];
+ int lbs[1];
+
+ /* Set up results: first, the number of subexpressions */
+ snprintf(buf, sizeof(buf), "%d", (int) cpattern->re_nsub);
+ elems[nresults++] = PointerGetDatum(cstring_to_text(buf));
+
+ /* Report individual info bit states */
+ for (inf = infonames; inf->bit != 0; inf++)
+ {
+ if (cpattern->re_info & inf->bit)
+ {
+ if (flags->info & inf->bit)
+ elems[nresults++] = PointerGetDatum(cstring_to_text(inf->text));
+ else
+ {
+ snprintf(buf, sizeof(buf), "unexpected %s!", inf->text);
+ elems[nresults++] = PointerGetDatum(cstring_to_text(buf));
+ }
+ }
+ else
+ {
+ if (flags->info & inf->bit)
+ {
+ snprintf(buf, sizeof(buf), "missing %s!", inf->text);
+ elems[nresults++] = PointerGetDatum(cstring_to_text(buf));
+ }
+ }
+ }
+
+ /* And form an array */
+ dims[0] = nresults;
+ lbs[0] = 1;
+ /* XXX: this hardcodes assumptions about the text type */
+ return construct_md_array(elems, NULL, 1, dims, lbs,
+ TEXTOID, -1, false, TYPALIGN_INT);
+}
+
+/*
+ * build_test_match_result - build output array for current match
+ *
+ * Note that if the indices flag is set, we don't need any strings,
+ * just the location data.
+ */
+static ArrayType *
+build_test_match_result(test_regex_ctx *matchctx)
+{
+ char *buf = matchctx->conv_buf;
+ Datum *elems = matchctx->elems;
+ bool *nulls = matchctx->nulls;
+ bool indices = matchctx->re_flags.indices;
+ char bufstr[80];
+ int dims[1];
+ int lbs[1];
+ int loc;
+ int i;
+
+ /* Extract matching substrings from the original string */
+ loc = matchctx->next_match * matchctx->npatterns * 2;
+ for (i = 0; i < matchctx->npatterns; i++)
+ {
+ int so = matchctx->match_locs[loc++];
+ int eo = matchctx->match_locs[loc++];
+
+ if (indices)
+ {
+ /* Report eo this way for consistency with Tcl */
+ snprintf(bufstr, sizeof(bufstr), "%d %d",
+ so, so < 0 ? eo : eo - 1);
+ elems[i] = PointerGetDatum(cstring_to_text(bufstr));
+ nulls[i] = false;
+ }
+ else if (so < 0 || eo < 0)
+ {
+ elems[i] = (Datum) 0;
+ nulls[i] = true;
+ }
+ else if (buf)
+ {
+ int len = pg_wchar2mb_with_len(matchctx->wide_str + so,
+ buf,
+ eo - so);
+
+ Assert(len < matchctx->conv_bufsiz);
+ elems[i] = PointerGetDatum(cstring_to_text_with_len(buf, len));
+ nulls[i] = false;
+ }
+ else
+ {
+ elems[i] = DirectFunctionCall3(text_substr,
+ PointerGetDatum(matchctx->orig_str),
+ Int32GetDatum(so + 1),
+ Int32GetDatum(eo - so));
+ nulls[i] = false;
+ }
+ }
+
+ /* In EXPECT indices mode, also report the "details" */
+ if (indices && (matchctx->re_flags.cflags & REG_EXPECT))
+ {
+ int so = matchctx->details.rm_extend.rm_so;
+ int eo = matchctx->details.rm_extend.rm_eo;
+
+ snprintf(bufstr, sizeof(bufstr), "%d %d",
+ so, so < 0 ? eo : eo - 1);
+ elems[i] = PointerGetDatum(cstring_to_text(bufstr));
+ nulls[i] = false;
+ i++;
+ }
+
+ /* And form an array */
+ dims[0] = i;
+ lbs[0] = 1;
+ /* XXX: this hardcodes assumptions about the text type */
+ return construct_md_array(elems, nulls, 1, dims, lbs,
+ TEXTOID, -1, false, TYPALIGN_INT);
+}
diff --git a/src/test/modules/test_regex/test_regex.control b/src/test/modules/test_regex/test_regex.control
new file mode 100644
index 0000000..bfce100
--- /dev/null
+++ b/src/test/modules/test_regex/test_regex.control
@@ -0,0 +1,4 @@
+comment = 'Test code for backend/regex/'
+default_version = '1.0'
+module_pathname = '$libdir/test_regex'
+relocatable = true
diff --git a/src/test/modules/test_rls_hooks/.gitignore b/src/test/modules/test_rls_hooks/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_rls_hooks/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_rls_hooks/Makefile b/src/test/modules/test_rls_hooks/Makefile
new file mode 100644
index 0000000..a4f7d85
--- /dev/null
+++ b/src/test/modules/test_rls_hooks/Makefile
@@ -0,0 +1,27 @@
+# src/test/modules/test_rls_hooks/Makefile
+
+MODULE_big = test_rls_hooks
+OBJS = \
+ $(WIN32RES) \
+ test_rls_hooks.o
+PGFILEDESC = "test_rls_hooks - example use of RLS hooks"
+
+EXTENSION = test_rls_hooks
+# DATA = test_rls_hooks--1.0.sql
+
+REGRESS = test_rls_hooks
+REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/test_rls_hooks/rls_hooks.conf
+# Disabled because these tests require "shared_preload_libraries=test_rls_hooks",
+# which typical installcheck users do not have (e.g. buildfarm clients).
+NO_INSTALLCHECK = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_rls_hooks
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_rls_hooks/README b/src/test/modules/test_rls_hooks/README
new file mode 100644
index 0000000..c22e0d3
--- /dev/null
+++ b/src/test/modules/test_rls_hooks/README
@@ -0,0 +1,16 @@
+test_rls_hooks is an example of how to use the hooks provided for RLS to
+define additional policies to be used.
+
+Functions
+=========
+test_rls_hooks_permissive(CmdType cmdtype, Relation relation)
+ RETURNS List*
+
+Returns a list of policies which should be added to any existing
+policies on the relation, combined with OR.
+
+test_rls_hooks_restrictive(CmdType cmdtype, Relation relation)
+ RETURNS List*
+
+Returns a list of policies which should be added to any existing
+policies on the relation, combined with AND.
diff --git a/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out b/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out
new file mode 100644
index 0000000..b8c6d38
--- /dev/null
+++ b/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out
@@ -0,0 +1,201 @@
+LOAD 'test_rls_hooks';
+CREATE TABLE rls_test_permissive (
+ username name,
+ supervisor name,
+ data integer
+);
+-- initial test data
+INSERT INTO rls_test_permissive VALUES ('regress_r1','regress_s1',4);
+INSERT INTO rls_test_permissive VALUES ('regress_r2','regress_s2',5);
+INSERT INTO rls_test_permissive VALUES ('regress_r3','regress_s3',6);
+CREATE TABLE rls_test_restrictive (
+ username name,
+ supervisor name,
+ data integer
+);
+-- At least one permissive policy must exist, otherwise
+-- the default deny policy will be applied. For
+-- testing the only-restrictive-policies from the hook,
+-- create a simple 'allow all' policy.
+CREATE POLICY p1 ON rls_test_restrictive USING (true);
+-- initial test data
+INSERT INTO rls_test_restrictive VALUES ('regress_r1','regress_s1',1);
+INSERT INTO rls_test_restrictive VALUES ('regress_r2','regress_s2',2);
+INSERT INTO rls_test_restrictive VALUES ('regress_r3','regress_s3',3);
+CREATE TABLE rls_test_both (
+ username name,
+ supervisor name,
+ data integer
+);
+-- initial test data
+INSERT INTO rls_test_both VALUES ('regress_r1','regress_s1',7);
+INSERT INTO rls_test_both VALUES ('regress_r2','regress_s2',8);
+INSERT INTO rls_test_both VALUES ('regress_r3','regress_s3',9);
+ALTER TABLE rls_test_permissive ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_test_restrictive ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_test_both ENABLE ROW LEVEL SECURITY;
+CREATE ROLE regress_r1;
+CREATE ROLE regress_s1;
+GRANT SELECT,INSERT ON rls_test_permissive TO regress_r1;
+GRANT SELECT,INSERT ON rls_test_restrictive TO regress_r1;
+GRANT SELECT,INSERT ON rls_test_both TO regress_r1;
+GRANT SELECT,INSERT ON rls_test_permissive TO regress_s1;
+GRANT SELECT,INSERT ON rls_test_restrictive TO regress_s1;
+GRANT SELECT,INSERT ON rls_test_both TO regress_s1;
+SET ROLE regress_r1;
+-- With only the hook's policies, permissive
+-- hook's policy is current_user = username
+EXPLAIN (costs off) SELECT * FROM rls_test_permissive;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on rls_test_permissive
+ Filter: ("current_user"() = username)
+(2 rows)
+
+SELECT * FROM rls_test_permissive;
+ username | supervisor | data
+------------+------------+------
+ regress_r1 | regress_s1 | 4
+(1 row)
+
+-- success
+INSERT INTO rls_test_permissive VALUES ('regress_r1','regress_s1',10);
+-- failure
+INSERT INTO rls_test_permissive VALUES ('regress_r4','regress_s4',10);
+ERROR: new row violates row-level security policy for table "rls_test_permissive"
+SET ROLE regress_s1;
+-- With only the hook's policies, restrictive
+-- hook's policy is current_user = supervisor
+EXPLAIN (costs off) SELECT * FROM rls_test_restrictive;
+ QUERY PLAN
+-------------------------------------------
+ Seq Scan on rls_test_restrictive
+ Filter: ("current_user"() = supervisor)
+(2 rows)
+
+SELECT * FROM rls_test_restrictive;
+ username | supervisor | data
+------------+------------+------
+ regress_r1 | regress_s1 | 1
+(1 row)
+
+-- success
+INSERT INTO rls_test_restrictive VALUES ('regress_r1','regress_s1',10);
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('regress_r4','regress_s4',10);
+ERROR: new row violates row-level security policy "extension policy" for table "rls_test_restrictive"
+SET ROLE regress_s1;
+-- With only the hook's policies, both
+-- permissive hook's policy is current_user = username
+-- restrictive hook's policy is current_user = superuser
+-- combined with AND, results in nothing being allowed
+EXPLAIN (costs off) SELECT * FROM rls_test_both;
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ Seq Scan on rls_test_both
+ Filter: ((supervisor = "current_user"()) AND (username = "current_user"()))
+(2 rows)
+
+SELECT * FROM rls_test_both;
+ username | supervisor | data
+----------+------------+------
+(0 rows)
+
+-- failure
+INSERT INTO rls_test_both VALUES ('regress_r1','regress_s1',10);
+ERROR: new row violates row-level security policy for table "rls_test_both"
+-- failure
+INSERT INTO rls_test_both VALUES ('regress_r4','regress_s1',10);
+ERROR: new row violates row-level security policy for table "rls_test_both"
+-- failure
+INSERT INTO rls_test_both VALUES ('regress_r4','regress_s4',10);
+ERROR: new row violates row-level security policy for table "rls_test_both"
+RESET ROLE;
+-- Create "internal" policies, to check that the policies from
+-- the hooks are combined correctly.
+CREATE POLICY p1 ON rls_test_permissive USING (data % 2 = 0);
+-- Remove the original allow-all policy
+DROP POLICY p1 ON rls_test_restrictive;
+CREATE POLICY p1 ON rls_test_restrictive USING (data % 2 = 0);
+CREATE POLICY p1 ON rls_test_both USING (data % 2 = 0);
+SET ROLE regress_r1;
+-- With both internal and hook policies, permissive
+EXPLAIN (costs off) SELECT * FROM rls_test_permissive;
+ QUERY PLAN
+---------------------------------------------------------------
+ Seq Scan on rls_test_permissive
+ Filter: (((data % 2) = 0) OR ("current_user"() = username))
+(2 rows)
+
+SELECT * FROM rls_test_permissive;
+ username | supervisor | data
+------------+------------+------
+ regress_r1 | regress_s1 | 4
+ regress_r3 | regress_s3 | 6
+ regress_r1 | regress_s1 | 10
+(3 rows)
+
+-- success
+INSERT INTO rls_test_permissive VALUES ('regress_r1','regress_s1',7);
+-- success
+INSERT INTO rls_test_permissive VALUES ('regress_r3','regress_s3',10);
+-- failure
+INSERT INTO rls_test_permissive VALUES ('regress_r4','regress_s4',7);
+ERROR: new row violates row-level security policy for table "rls_test_permissive"
+SET ROLE regress_s1;
+-- With both internal and hook policies, restrictive
+EXPLAIN (costs off) SELECT * FROM rls_test_restrictive;
+ QUERY PLAN
+------------------------------------------------------------------
+ Seq Scan on rls_test_restrictive
+ Filter: (("current_user"() = supervisor) AND ((data % 2) = 0))
+(2 rows)
+
+SELECT * FROM rls_test_restrictive;
+ username | supervisor | data
+------------+------------+------
+ regress_r1 | regress_s1 | 10
+(1 row)
+
+-- success
+INSERT INTO rls_test_restrictive VALUES ('regress_r1','regress_s1',8);
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('regress_r3','regress_s3',10);
+ERROR: new row violates row-level security policy "extension policy" for table "rls_test_restrictive"
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('regress_r1','regress_s1',7);
+ERROR: new row violates row-level security policy for table "rls_test_restrictive"
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('regress_r4','regress_s4',7);
+ERROR: new row violates row-level security policy for table "rls_test_restrictive"
+-- With both internal and hook policies, both permissive
+-- and restrictive hook policies
+EXPLAIN (costs off) SELECT * FROM rls_test_both;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------
+ Seq Scan on rls_test_both
+ Filter: (("current_user"() = supervisor) AND (((data % 2) = 0) OR ("current_user"() = username)))
+(2 rows)
+
+SELECT * FROM rls_test_both;
+ username | supervisor | data
+----------+------------+------
+(0 rows)
+
+-- success
+INSERT INTO rls_test_both VALUES ('regress_r1','regress_s1',8);
+-- failure
+INSERT INTO rls_test_both VALUES ('regress_r3','regress_s3',10);
+ERROR: new row violates row-level security policy "extension policy" for table "rls_test_both"
+-- failure
+INSERT INTO rls_test_both VALUES ('regress_r1','regress_s1',7);
+ERROR: new row violates row-level security policy for table "rls_test_both"
+-- failure
+INSERT INTO rls_test_both VALUES ('regress_r4','regress_s4',7);
+ERROR: new row violates row-level security policy for table "rls_test_both"
+RESET ROLE;
+DROP TABLE rls_test_restrictive;
+DROP TABLE rls_test_permissive;
+DROP TABLE rls_test_both;
+DROP ROLE regress_r1;
+DROP ROLE regress_s1;
diff --git a/src/test/modules/test_rls_hooks/rls_hooks.conf b/src/test/modules/test_rls_hooks/rls_hooks.conf
new file mode 100644
index 0000000..a522c0e
--- /dev/null
+++ b/src/test/modules/test_rls_hooks/rls_hooks.conf
@@ -0,0 +1 @@
+shared_preload_libraries = test_rls_hooks
diff --git a/src/test/modules/test_rls_hooks/sql/test_rls_hooks.sql b/src/test/modules/test_rls_hooks/sql/test_rls_hooks.sql
new file mode 100644
index 0000000..746f6dd
--- /dev/null
+++ b/src/test/modules/test_rls_hooks/sql/test_rls_hooks.sql
@@ -0,0 +1,176 @@
+LOAD 'test_rls_hooks';
+
+CREATE TABLE rls_test_permissive (
+ username name,
+ supervisor name,
+ data integer
+);
+
+-- initial test data
+INSERT INTO rls_test_permissive VALUES ('regress_r1','regress_s1',4);
+INSERT INTO rls_test_permissive VALUES ('regress_r2','regress_s2',5);
+INSERT INTO rls_test_permissive VALUES ('regress_r3','regress_s3',6);
+
+CREATE TABLE rls_test_restrictive (
+ username name,
+ supervisor name,
+ data integer
+);
+
+-- At least one permissive policy must exist, otherwise
+-- the default deny policy will be applied. For
+-- testing the only-restrictive-policies from the hook,
+-- create a simple 'allow all' policy.
+CREATE POLICY p1 ON rls_test_restrictive USING (true);
+
+-- initial test data
+INSERT INTO rls_test_restrictive VALUES ('regress_r1','regress_s1',1);
+INSERT INTO rls_test_restrictive VALUES ('regress_r2','regress_s2',2);
+INSERT INTO rls_test_restrictive VALUES ('regress_r3','regress_s3',3);
+
+CREATE TABLE rls_test_both (
+ username name,
+ supervisor name,
+ data integer
+);
+
+-- initial test data
+INSERT INTO rls_test_both VALUES ('regress_r1','regress_s1',7);
+INSERT INTO rls_test_both VALUES ('regress_r2','regress_s2',8);
+INSERT INTO rls_test_both VALUES ('regress_r3','regress_s3',9);
+
+ALTER TABLE rls_test_permissive ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_test_restrictive ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_test_both ENABLE ROW LEVEL SECURITY;
+
+CREATE ROLE regress_r1;
+CREATE ROLE regress_s1;
+
+GRANT SELECT,INSERT ON rls_test_permissive TO regress_r1;
+GRANT SELECT,INSERT ON rls_test_restrictive TO regress_r1;
+GRANT SELECT,INSERT ON rls_test_both TO regress_r1;
+
+GRANT SELECT,INSERT ON rls_test_permissive TO regress_s1;
+GRANT SELECT,INSERT ON rls_test_restrictive TO regress_s1;
+GRANT SELECT,INSERT ON rls_test_both TO regress_s1;
+
+SET ROLE regress_r1;
+
+-- With only the hook's policies, permissive
+-- hook's policy is current_user = username
+EXPLAIN (costs off) SELECT * FROM rls_test_permissive;
+
+SELECT * FROM rls_test_permissive;
+
+-- success
+INSERT INTO rls_test_permissive VALUES ('regress_r1','regress_s1',10);
+
+-- failure
+INSERT INTO rls_test_permissive VALUES ('regress_r4','regress_s4',10);
+
+SET ROLE regress_s1;
+
+-- With only the hook's policies, restrictive
+-- hook's policy is current_user = supervisor
+EXPLAIN (costs off) SELECT * FROM rls_test_restrictive;
+
+SELECT * FROM rls_test_restrictive;
+
+-- success
+INSERT INTO rls_test_restrictive VALUES ('regress_r1','regress_s1',10);
+
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('regress_r4','regress_s4',10);
+
+SET ROLE regress_s1;
+
+-- With only the hook's policies, both
+-- permissive hook's policy is current_user = username
+-- restrictive hook's policy is current_user = superuser
+-- combined with AND, results in nothing being allowed
+EXPLAIN (costs off) SELECT * FROM rls_test_both;
+
+SELECT * FROM rls_test_both;
+
+-- failure
+INSERT INTO rls_test_both VALUES ('regress_r1','regress_s1',10);
+
+-- failure
+INSERT INTO rls_test_both VALUES ('regress_r4','regress_s1',10);
+
+-- failure
+INSERT INTO rls_test_both VALUES ('regress_r4','regress_s4',10);
+
+RESET ROLE;
+
+-- Create "internal" policies, to check that the policies from
+-- the hooks are combined correctly.
+CREATE POLICY p1 ON rls_test_permissive USING (data % 2 = 0);
+
+-- Remove the original allow-all policy
+DROP POLICY p1 ON rls_test_restrictive;
+CREATE POLICY p1 ON rls_test_restrictive USING (data % 2 = 0);
+
+CREATE POLICY p1 ON rls_test_both USING (data % 2 = 0);
+
+SET ROLE regress_r1;
+
+-- With both internal and hook policies, permissive
+EXPLAIN (costs off) SELECT * FROM rls_test_permissive;
+
+SELECT * FROM rls_test_permissive;
+
+-- success
+INSERT INTO rls_test_permissive VALUES ('regress_r1','regress_s1',7);
+
+-- success
+INSERT INTO rls_test_permissive VALUES ('regress_r3','regress_s3',10);
+
+-- failure
+INSERT INTO rls_test_permissive VALUES ('regress_r4','regress_s4',7);
+
+SET ROLE regress_s1;
+
+-- With both internal and hook policies, restrictive
+EXPLAIN (costs off) SELECT * FROM rls_test_restrictive;
+
+SELECT * FROM rls_test_restrictive;
+
+-- success
+INSERT INTO rls_test_restrictive VALUES ('regress_r1','regress_s1',8);
+
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('regress_r3','regress_s3',10);
+
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('regress_r1','regress_s1',7);
+
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('regress_r4','regress_s4',7);
+
+-- With both internal and hook policies, both permissive
+-- and restrictive hook policies
+EXPLAIN (costs off) SELECT * FROM rls_test_both;
+
+SELECT * FROM rls_test_both;
+
+-- success
+INSERT INTO rls_test_both VALUES ('regress_r1','regress_s1',8);
+
+-- failure
+INSERT INTO rls_test_both VALUES ('regress_r3','regress_s3',10);
+
+-- failure
+INSERT INTO rls_test_both VALUES ('regress_r1','regress_s1',7);
+
+-- failure
+INSERT INTO rls_test_both VALUES ('regress_r4','regress_s4',7);
+
+RESET ROLE;
+
+DROP TABLE rls_test_restrictive;
+DROP TABLE rls_test_permissive;
+DROP TABLE rls_test_both;
+
+DROP ROLE regress_r1;
+DROP ROLE regress_s1;
diff --git a/src/test/modules/test_rls_hooks/test_rls_hooks.c b/src/test/modules/test_rls_hooks/test_rls_hooks.c
new file mode 100644
index 0000000..06c48f5
--- /dev/null
+++ b/src/test/modules/test_rls_hooks/test_rls_hooks.c
@@ -0,0 +1,184 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_rls_hooks.c
+ * Code for testing RLS hooks.
+ *
+ * Copyright (c) 2015-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_rls_hooks/test_rls_hooks.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "rewrite/rowsecurity.h"
+#include "test_rls_hooks.h"
+#include "utils/acl.h"
+#include "utils/rel.h"
+#include "utils/relcache.h"
+
+PG_MODULE_MAGIC;
+
+/* Saved hook values in case of unload */
+static row_security_policy_hook_type prev_row_security_policy_hook_permissive = NULL;
+static row_security_policy_hook_type prev_row_security_policy_hook_restrictive = NULL;
+
+void _PG_init(void);
+void _PG_fini(void);
+
+/* Install hooks */
+void
+_PG_init(void)
+{
+ /* Save values for unload */
+ prev_row_security_policy_hook_permissive = row_security_policy_hook_permissive;
+ prev_row_security_policy_hook_restrictive = row_security_policy_hook_restrictive;
+
+ /* Set our hooks */
+ row_security_policy_hook_permissive = test_rls_hooks_permissive;
+ row_security_policy_hook_restrictive = test_rls_hooks_restrictive;
+}
+
+/* Uninstall hooks */
+void
+_PG_fini(void)
+{
+ row_security_policy_hook_permissive = prev_row_security_policy_hook_permissive;
+ row_security_policy_hook_restrictive = prev_row_security_policy_hook_restrictive;
+}
+
+/*
+ * Return permissive policies to be added
+ */
+List *
+test_rls_hooks_permissive(CmdType cmdtype, Relation relation)
+{
+ List *policies = NIL;
+ RowSecurityPolicy *policy = palloc0(sizeof(RowSecurityPolicy));
+ Datum role;
+ FuncCall *n;
+ Node *e;
+ ColumnRef *c;
+ ParseState *qual_pstate;
+ ParseNamespaceItem *nsitem;
+
+ if (strcmp(RelationGetRelationName(relation), "rls_test_permissive") != 0 &&
+ strcmp(RelationGetRelationName(relation), "rls_test_both") != 0)
+ return NIL;
+
+ qual_pstate = make_parsestate(NULL);
+
+ nsitem = addRangeTableEntryForRelation(qual_pstate,
+ relation, AccessShareLock,
+ NULL, false, false);
+ addNSItemToQuery(qual_pstate, nsitem, false, true, true);
+
+ role = ObjectIdGetDatum(ACL_ID_PUBLIC);
+
+ policy->policy_name = pstrdup("extension policy");
+ policy->polcmd = '*';
+ policy->roles = construct_array(&role, 1, OIDOID, sizeof(Oid), true, TYPALIGN_INT);
+
+ /*
+ * policy->qual = (Expr *) makeConst(BOOLOID, -1, InvalidOid,
+ * sizeof(bool), BoolGetDatum(true), false, true);
+ */
+
+ n = makeFuncCall(list_make2(makeString("pg_catalog"),
+ makeString("current_user")),
+ NIL,
+ COERCE_EXPLICIT_CALL,
+ -1);
+
+ c = makeNode(ColumnRef);
+ c->fields = list_make1(makeString("username"));
+ c->location = 0;
+
+ e = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) n, (Node *) c, 0);
+
+ policy->qual = (Expr *) transformWhereClause(qual_pstate, copyObject(e),
+ EXPR_KIND_POLICY,
+ "POLICY");
+ /* Fix up collation information */
+ assign_expr_collations(qual_pstate, (Node *) policy->qual);
+
+ policy->with_check_qual = copyObject(policy->qual);
+ policy->hassublinks = false;
+
+ policies = list_make1(policy);
+
+ return policies;
+}
+
+/*
+ * Return restrictive policies to be added
+ *
+ * Note that a permissive policy must exist or the default-deny policy
+ * will be included and nothing will be visible. If no filtering should
+ * be done except for the restrictive policy, then a single "USING (true)"
+ * permissive policy can be used; see the regression tests.
+ */
+List *
+test_rls_hooks_restrictive(CmdType cmdtype, Relation relation)
+{
+ List *policies = NIL;
+ RowSecurityPolicy *policy = palloc0(sizeof(RowSecurityPolicy));
+ Datum role;
+ FuncCall *n;
+ Node *e;
+ ColumnRef *c;
+ ParseState *qual_pstate;
+ ParseNamespaceItem *nsitem;
+
+ if (strcmp(RelationGetRelationName(relation), "rls_test_restrictive") != 0 &&
+ strcmp(RelationGetRelationName(relation), "rls_test_both") != 0)
+ return NIL;
+
+ qual_pstate = make_parsestate(NULL);
+
+ nsitem = addRangeTableEntryForRelation(qual_pstate,
+ relation, AccessShareLock,
+ NULL, false, false);
+ addNSItemToQuery(qual_pstate, nsitem, false, true, true);
+
+ role = ObjectIdGetDatum(ACL_ID_PUBLIC);
+
+ policy->policy_name = pstrdup("extension policy");
+ policy->polcmd = '*';
+ policy->roles = construct_array(&role, 1, OIDOID, sizeof(Oid), true, TYPALIGN_INT);
+
+ n = makeFuncCall(list_make2(makeString("pg_catalog"),
+ makeString("current_user")),
+ NIL,
+ COERCE_EXPLICIT_CALL,
+ -1);
+
+ c = makeNode(ColumnRef);
+ c->fields = list_make1(makeString("supervisor"));
+ c->location = 0;
+
+ e = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) n, (Node *) c, 0);
+
+ policy->qual = (Expr *) transformWhereClause(qual_pstate, copyObject(e),
+ EXPR_KIND_POLICY,
+ "POLICY");
+ /* Fix up collation information */
+ assign_expr_collations(qual_pstate, (Node *) policy->qual);
+
+ policy->with_check_qual = copyObject(policy->qual);
+ policy->hassublinks = false;
+
+ policies = list_make1(policy);
+
+ return policies;
+}
diff --git a/src/test/modules/test_rls_hooks/test_rls_hooks.control b/src/test/modules/test_rls_hooks/test_rls_hooks.control
new file mode 100644
index 0000000..9f9f13f
--- /dev/null
+++ b/src/test/modules/test_rls_hooks/test_rls_hooks.control
@@ -0,0 +1,4 @@
+comment = 'Test code for RLS hooks'
+default_version = '1.0'
+module_pathname = '$libdir/test_rls_hooks'
+relocatable = true
diff --git a/src/test/modules/test_rls_hooks/test_rls_hooks.h b/src/test/modules/test_rls_hooks/test_rls_hooks.h
new file mode 100644
index 0000000..e5659c8
--- /dev/null
+++ b/src/test/modules/test_rls_hooks/test_rls_hooks.h
@@ -0,0 +1,25 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_rls_hooks.h
+ * Definitions for RLS hooks
+ *
+ * Copyright (c) 2015-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_rls_hooks/test_rls_hooks.h
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#ifndef TEST_RLS_HOOKS_H
+#define TEST_RLS_HOOKS_H
+
+#include <rewrite/rowsecurity.h>
+
+/* Return set of permissive hooks based on CmdType and Relation */
+extern List *test_rls_hooks_permissive(CmdType cmdtype, Relation relation);
+
+/* Return set of restrictive hooks based on CmdType and Relation */
+extern List *test_rls_hooks_restrictive(CmdType cmdtype, Relation relation);
+
+#endif
diff --git a/src/test/modules/test_shm_mq/.gitignore b/src/test/modules/test_shm_mq/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/test_shm_mq/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_shm_mq/Makefile b/src/test/modules/test_shm_mq/Makefile
new file mode 100644
index 0000000..1171ced
--- /dev/null
+++ b/src/test/modules/test_shm_mq/Makefile
@@ -0,0 +1,25 @@
+# src/test/modules/test_shm_mq/Makefile
+
+MODULE_big = test_shm_mq
+OBJS = \
+ $(WIN32RES) \
+ setup.o \
+ test.o \
+ worker.o
+PGFILEDESC = "test_shm_mq - example use of shared memory message queue"
+
+EXTENSION = test_shm_mq
+DATA = test_shm_mq--1.0.sql
+
+REGRESS = test_shm_mq
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_shm_mq
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_shm_mq/README b/src/test/modules/test_shm_mq/README
new file mode 100644
index 0000000..641407b
--- /dev/null
+++ b/src/test/modules/test_shm_mq/README
@@ -0,0 +1,49 @@
+test_shm_mq is an example of how to use dynamic shared memory
+and the shared memory message queue facilities to coordinate a user backend
+with the efforts of one or more background workers. It is not intended to
+do anything useful on its own; rather, it is a demonstration of how these
+facilities can be used, and a unit test of those facilities.
+
+The function is this extension send the same message repeatedly through
+a loop of processes. The message payload, the size of the message queue
+through which it is sent, and the number of processes in the loop are
+configurable. At the end, the message may be verified to ensure that it
+has not been corrupted in transmission.
+
+Functions
+=========
+
+
+test_shm_mq(queue_size int8, message text,
+ repeat_count int4 default 1, num_workers int4 default 1)
+ RETURNS void
+
+This function sends and receives messages synchronously. The user
+backend sends the provided message to the first background worker using
+a message queue of the given size. The first background worker sends
+the message to the second background worker, if the number of workers
+is greater than one, and so forth. Eventually, the last background
+worker sends the message back to the user backend. If the repeat count
+is greater than one, the user backend then sends the message back to
+the first worker. Once the message has been sent and received by all
+the coordinating processes a number of times equal to the repeat count,
+the user backend verifies that the message finally received matches the
+one originally sent and throws an error if not.
+
+
+test_shm_mq_pipelined(queue_size int8, message text,
+ repeat_count int4 default 1, num_workers int4 default 1,
+ verify bool default true)
+ RETURNS void
+
+This function sends the same message multiple times, as specified by the
+repeat count, to the first background worker using a queue of the given
+size. These messages are then forwarded to each background worker in
+turn, in each case using a queue of the given size. Finally, the last
+background worker sends the messages back to the user backend. The user
+backend uses non-blocking sends and receives, so that it may begin receiving
+copies of the message before it has finished sending all copies of the
+message. The 'verify' argument controls whether or not the
+received copies are checked against the message that was sent. (This
+takes nontrivial time so it may be useful to disable it for benchmarking
+purposes.)
diff --git a/src/test/modules/test_shm_mq/expected/test_shm_mq.out b/src/test/modules/test_shm_mq/expected/test_shm_mq.out
new file mode 100644
index 0000000..c4858b0
--- /dev/null
+++ b/src/test/modules/test_shm_mq/expected/test_shm_mq.out
@@ -0,0 +1,36 @@
+CREATE EXTENSION test_shm_mq;
+--
+-- These tests don't produce any interesting output. We're checking that
+-- the operations complete without crashing or hanging and that none of their
+-- internal sanity tests fail.
+--
+SELECT test_shm_mq(1024, '', 2000, 1);
+ test_shm_mq
+-------------
+
+(1 row)
+
+SELECT test_shm_mq(1024, 'a', 2001, 1);
+ test_shm_mq
+-------------
+
+(1 row)
+
+SELECT test_shm_mq(32768, (select string_agg(chr(32+(random()*95)::int), '') from generate_series(1,(100+900*random())::int)), 10000, 1);
+ test_shm_mq
+-------------
+
+(1 row)
+
+SELECT test_shm_mq(100, (select string_agg(chr(32+(random()*95)::int), '') from generate_series(1,(100+200*random())::int)), 10000, 1);
+ test_shm_mq
+-------------
+
+(1 row)
+
+SELECT test_shm_mq_pipelined(16384, (select string_agg(chr(32+(random()*95)::int), '') from generate_series(1,270000)), 200, 3);
+ test_shm_mq_pipelined
+-----------------------
+
+(1 row)
+
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
new file mode 100644
index 0000000..e05e97c
--- /dev/null
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -0,0 +1,316 @@
+/*--------------------------------------------------------------------------
+ *
+ * setup.c
+ * Code to set up a dynamic shared memory segments and a specified
+ * number of background workers for shared memory message queue
+ * testing.
+ *
+ * Copyright (c) 2013-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_shm_mq/setup.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "storage/procsignal.h"
+#include "storage/shm_toc.h"
+#include "test_shm_mq.h"
+#include "utils/memutils.h"
+
+typedef struct
+{
+ int nworkers;
+ BackgroundWorkerHandle *handle[FLEXIBLE_ARRAY_MEMBER];
+} worker_state;
+
+static void setup_dynamic_shared_memory(int64 queue_size, int nworkers,
+ dsm_segment **segp,
+ test_shm_mq_header **hdrp,
+ shm_mq **outp, shm_mq **inp);
+static worker_state *setup_background_workers(int nworkers,
+ dsm_segment *seg);
+static void cleanup_background_workers(dsm_segment *seg, Datum arg);
+static void wait_for_workers_to_become_ready(worker_state *wstate,
+ volatile test_shm_mq_header *hdr);
+static bool check_worker_status(worker_state *wstate);
+
+/*
+ * Set up a dynamic shared memory segment and zero or more background workers
+ * for a test run.
+ */
+void
+test_shm_mq_setup(int64 queue_size, int32 nworkers, dsm_segment **segp,
+ shm_mq_handle **output, shm_mq_handle **input)
+{
+ dsm_segment *seg;
+ test_shm_mq_header *hdr;
+ shm_mq *outq = NULL; /* placate compiler */
+ shm_mq *inq = NULL; /* placate compiler */
+ worker_state *wstate;
+
+ /* Set up a dynamic shared memory segment. */
+ setup_dynamic_shared_memory(queue_size, nworkers, &seg, &hdr, &outq, &inq);
+ *segp = seg;
+
+ /* Register background workers. */
+ wstate = setup_background_workers(nworkers, seg);
+
+ /* Attach the queues. */
+ *output = shm_mq_attach(outq, seg, wstate->handle[0]);
+ *input = shm_mq_attach(inq, seg, wstate->handle[nworkers - 1]);
+
+ /* Wait for workers to become ready. */
+ wait_for_workers_to_become_ready(wstate, hdr);
+
+ /*
+ * Once we reach this point, all workers are ready. We no longer need to
+ * kill them if we die; they'll die on their own as the message queues
+ * shut down.
+ */
+ cancel_on_dsm_detach(seg, cleanup_background_workers,
+ PointerGetDatum(wstate));
+ pfree(wstate);
+}
+
+/*
+ * Set up a dynamic shared memory segment.
+ *
+ * We set up a small control region that contains only a test_shm_mq_header,
+ * plus one region per message queue. There are as many message queues as
+ * the number of workers, plus one.
+ */
+static void
+setup_dynamic_shared_memory(int64 queue_size, int nworkers,
+ dsm_segment **segp, test_shm_mq_header **hdrp,
+ shm_mq **outp, shm_mq **inp)
+{
+ shm_toc_estimator e;
+ int i;
+ Size segsize;
+ dsm_segment *seg;
+ shm_toc *toc;
+ test_shm_mq_header *hdr;
+
+ /* Ensure a valid queue size. */
+ if (queue_size < 0 || ((uint64) queue_size) < shm_mq_minimum_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("queue size must be at least %zu bytes",
+ shm_mq_minimum_size)));
+ if (queue_size != ((Size) queue_size))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("queue size overflows size_t")));
+
+ /*
+ * Estimate how much shared memory we need.
+ *
+ * Because the TOC machinery may choose to insert padding of oddly-sized
+ * requests, we must estimate each chunk separately.
+ *
+ * We need one key to register the location of the header, and we need
+ * nworkers + 1 keys to track the locations of the message queues.
+ */
+ shm_toc_initialize_estimator(&e);
+ shm_toc_estimate_chunk(&e, sizeof(test_shm_mq_header));
+ for (i = 0; i <= nworkers; ++i)
+ shm_toc_estimate_chunk(&e, (Size) queue_size);
+ shm_toc_estimate_keys(&e, 2 + nworkers);
+ segsize = shm_toc_estimate(&e);
+
+ /* Create the shared memory segment and establish a table of contents. */
+ seg = dsm_create(shm_toc_estimate(&e), 0);
+ toc = shm_toc_create(PG_TEST_SHM_MQ_MAGIC, dsm_segment_address(seg),
+ segsize);
+
+ /* Set up the header region. */
+ hdr = shm_toc_allocate(toc, sizeof(test_shm_mq_header));
+ SpinLockInit(&hdr->mutex);
+ hdr->workers_total = nworkers;
+ hdr->workers_attached = 0;
+ hdr->workers_ready = 0;
+ shm_toc_insert(toc, 0, hdr);
+
+ /* Set up one message queue per worker, plus one. */
+ for (i = 0; i <= nworkers; ++i)
+ {
+ shm_mq *mq;
+
+ mq = shm_mq_create(shm_toc_allocate(toc, (Size) queue_size),
+ (Size) queue_size);
+ shm_toc_insert(toc, i + 1, mq);
+
+ if (i == 0)
+ {
+ /* We send messages to the first queue. */
+ shm_mq_set_sender(mq, MyProc);
+ *outp = mq;
+ }
+ if (i == nworkers)
+ {
+ /* We receive messages from the last queue. */
+ shm_mq_set_receiver(mq, MyProc);
+ *inp = mq;
+ }
+ }
+
+ /* Return results to caller. */
+ *segp = seg;
+ *hdrp = hdr;
+}
+
+/*
+ * Register background workers.
+ */
+static worker_state *
+setup_background_workers(int nworkers, dsm_segment *seg)
+{
+ MemoryContext oldcontext;
+ BackgroundWorker worker;
+ worker_state *wstate;
+ int i;
+
+ /*
+ * We need the worker_state object and the background worker handles to
+ * which it points to be allocated in CurTransactionContext rather than
+ * ExprContext; otherwise, they'll be destroyed before the on_dsm_detach
+ * hooks run.
+ */
+ oldcontext = MemoryContextSwitchTo(CurTransactionContext);
+
+ /* Create worker state object. */
+ wstate = MemoryContextAlloc(TopTransactionContext,
+ offsetof(worker_state, handle) +
+ sizeof(BackgroundWorkerHandle *) * nworkers);
+ wstate->nworkers = 0;
+
+ /*
+ * Arrange to kill all the workers if we abort before all workers are
+ * finished hooking themselves up to the dynamic shared memory segment.
+ *
+ * If we die after all the workers have finished hooking themselves up to
+ * the dynamic shared memory segment, we'll mark the two queues to which
+ * we're directly connected as detached, and the worker(s) connected to
+ * those queues will exit, marking any other queues to which they are
+ * connected as detached. This will cause any as-yet-unaware workers
+ * connected to those queues to exit in their turn, and so on, until
+ * everybody exits.
+ *
+ * But suppose the workers which are supposed to connect to the queues to
+ * which we're directly attached exit due to some error before they
+ * actually attach the queues. The remaining workers will have no way of
+ * knowing this. From their perspective, they're still waiting for those
+ * workers to start, when in fact they've already died.
+ */
+ on_dsm_detach(seg, cleanup_background_workers,
+ PointerGetDatum(wstate));
+
+ /* Configure a worker. */
+ memset(&worker, 0, sizeof(worker));
+ worker.bgw_flags = BGWORKER_SHMEM_ACCESS;
+ worker.bgw_start_time = BgWorkerStart_ConsistentState;
+ worker.bgw_restart_time = BGW_NEVER_RESTART;
+ sprintf(worker.bgw_library_name, "test_shm_mq");
+ sprintf(worker.bgw_function_name, "test_shm_mq_main");
+ snprintf(worker.bgw_type, BGW_MAXLEN, "test_shm_mq");
+ worker.bgw_main_arg = UInt32GetDatum(dsm_segment_handle(seg));
+ /* set bgw_notify_pid, so we can detect if the worker stops */
+ worker.bgw_notify_pid = MyProcPid;
+
+ /* Register the workers. */
+ for (i = 0; i < nworkers; ++i)
+ {
+ if (!RegisterDynamicBackgroundWorker(&worker, &wstate->handle[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+ errmsg("could not register background process"),
+ errhint("You may need to increase max_worker_processes.")));
+ ++wstate->nworkers;
+ }
+
+ /* All done. */
+ MemoryContextSwitchTo(oldcontext);
+ return wstate;
+}
+
+static void
+cleanup_background_workers(dsm_segment *seg, Datum arg)
+{
+ worker_state *wstate = (worker_state *) DatumGetPointer(arg);
+
+ while (wstate->nworkers > 0)
+ {
+ --wstate->nworkers;
+ TerminateBackgroundWorker(wstate->handle[wstate->nworkers]);
+ }
+}
+
+static void
+wait_for_workers_to_become_ready(worker_state *wstate,
+ volatile test_shm_mq_header *hdr)
+{
+ bool result = false;
+
+ for (;;)
+ {
+ int workers_ready;
+
+ /* If all the workers are ready, we have succeeded. */
+ SpinLockAcquire(&hdr->mutex);
+ workers_ready = hdr->workers_ready;
+ SpinLockRelease(&hdr->mutex);
+ if (workers_ready >= wstate->nworkers)
+ {
+ result = true;
+ break;
+ }
+
+ /* If any workers (or the postmaster) have died, we have failed. */
+ if (!check_worker_status(wstate))
+ {
+ result = false;
+ break;
+ }
+
+ /* Wait to be signaled. */
+ (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
+ PG_WAIT_EXTENSION);
+
+ /* Reset the latch so we don't spin. */
+ ResetLatch(MyLatch);
+
+ /* An interrupt may have occurred while we were waiting. */
+ CHECK_FOR_INTERRUPTS();
+ }
+
+ if (!result)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+ errmsg("one or more background workers failed to start")));
+}
+
+static bool
+check_worker_status(worker_state *wstate)
+{
+ int n;
+
+ /* If any workers (or the postmaster) have died, we have failed. */
+ for (n = 0; n < wstate->nworkers; ++n)
+ {
+ BgwHandleStatus status;
+ pid_t pid;
+
+ status = GetBackgroundWorkerPid(wstate->handle[n], &pid);
+ if (status == BGWH_STOPPED || status == BGWH_POSTMASTER_DIED)
+ return false;
+ }
+
+ /* Otherwise, things still look OK. */
+ return true;
+}
diff --git a/src/test/modules/test_shm_mq/sql/test_shm_mq.sql b/src/test/modules/test_shm_mq/sql/test_shm_mq.sql
new file mode 100644
index 0000000..9de19d3
--- /dev/null
+++ b/src/test/modules/test_shm_mq/sql/test_shm_mq.sql
@@ -0,0 +1,12 @@
+CREATE EXTENSION test_shm_mq;
+
+--
+-- These tests don't produce any interesting output. We're checking that
+-- the operations complete without crashing or hanging and that none of their
+-- internal sanity tests fail.
+--
+SELECT test_shm_mq(1024, '', 2000, 1);
+SELECT test_shm_mq(1024, 'a', 2001, 1);
+SELECT test_shm_mq(32768, (select string_agg(chr(32+(random()*95)::int), '') from generate_series(1,(100+900*random())::int)), 10000, 1);
+SELECT test_shm_mq(100, (select string_agg(chr(32+(random()*95)::int), '') from generate_series(1,(100+200*random())::int)), 10000, 1);
+SELECT test_shm_mq_pipelined(16384, (select string_agg(chr(32+(random()*95)::int), '') from generate_series(1,270000)), 200, 3);
diff --git a/src/test/modules/test_shm_mq/test.c b/src/test/modules/test_shm_mq/test.c
new file mode 100644
index 0000000..2d8d695
--- /dev/null
+++ b/src/test/modules/test_shm_mq/test.c
@@ -0,0 +1,266 @@
+/*--------------------------------------------------------------------------
+ *
+ * test.c
+ * Test harness code for shared memory message queues.
+ *
+ * Copyright (c) 2013-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_shm_mq/test.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+
+#include "test_shm_mq.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(test_shm_mq);
+PG_FUNCTION_INFO_V1(test_shm_mq_pipelined);
+
+void _PG_init(void);
+
+static void verify_message(Size origlen, char *origdata, Size newlen,
+ char *newdata);
+
+/*
+ * Simple test of the shared memory message queue infrastructure.
+ *
+ * We set up a ring of message queues passing through 1 or more background
+ * processes and eventually looping back to ourselves. We then send a message
+ * through the ring a number of times indicated by the loop count. At the end,
+ * we check whether the final message matches the one we started with.
+ */
+Datum
+test_shm_mq(PG_FUNCTION_ARGS)
+{
+ int64 queue_size = PG_GETARG_INT64(0);
+ text *message = PG_GETARG_TEXT_PP(1);
+ char *message_contents = VARDATA_ANY(message);
+ int message_size = VARSIZE_ANY_EXHDR(message);
+ int32 loop_count = PG_GETARG_INT32(2);
+ int32 nworkers = PG_GETARG_INT32(3);
+ dsm_segment *seg;
+ shm_mq_handle *outqh;
+ shm_mq_handle *inqh;
+ shm_mq_result res;
+ Size len;
+ void *data;
+
+ /* A negative loopcount is nonsensical. */
+ if (loop_count < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("repeat count size must be an integer value greater than or equal to zero")));
+
+ /*
+ * Since this test sends data using the blocking interfaces, it cannot
+ * send data to itself. Therefore, a minimum of 1 worker is required. Of
+ * course, a negative worker count is nonsensical.
+ */
+ if (nworkers <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("number of workers must be an integer value greater than zero")));
+
+ /* Set up dynamic shared memory segment and background workers. */
+ test_shm_mq_setup(queue_size, nworkers, &seg, &outqh, &inqh);
+
+ /* Send the initial message. */
+ res = shm_mq_send(outqh, message_size, message_contents, false);
+ if (res != SHM_MQ_SUCCESS)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("could not send message")));
+
+ /*
+ * Receive a message and send it back out again. Do this a number of
+ * times equal to the loop count.
+ */
+ for (;;)
+ {
+ /* Receive a message. */
+ res = shm_mq_receive(inqh, &len, &data, false);
+ if (res != SHM_MQ_SUCCESS)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("could not receive message")));
+
+ /* If this is supposed to be the last iteration, stop here. */
+ if (--loop_count <= 0)
+ break;
+
+ /* Send it back out. */
+ res = shm_mq_send(outqh, len, data, false);
+ if (res != SHM_MQ_SUCCESS)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("could not send message")));
+ }
+
+ /*
+ * Finally, check that we got back the same message from the last
+ * iteration that we originally sent.
+ */
+ verify_message(message_size, message_contents, len, data);
+
+ /* Clean up. */
+ dsm_detach(seg);
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * Pipelined test of the shared memory message queue infrastructure.
+ *
+ * As in the basic test, we set up a ring of message queues passing through
+ * 1 or more background processes and eventually looping back to ourselves.
+ * Then, we send N copies of the user-specified message through the ring and
+ * receive them all back. Since this might fill up all message queues in the
+ * ring and then stall, we must be prepared to begin receiving the messages
+ * back before we've finished sending them.
+ */
+Datum
+test_shm_mq_pipelined(PG_FUNCTION_ARGS)
+{
+ int64 queue_size = PG_GETARG_INT64(0);
+ text *message = PG_GETARG_TEXT_PP(1);
+ char *message_contents = VARDATA_ANY(message);
+ int message_size = VARSIZE_ANY_EXHDR(message);
+ int32 loop_count = PG_GETARG_INT32(2);
+ int32 nworkers = PG_GETARG_INT32(3);
+ bool verify = PG_GETARG_BOOL(4);
+ int32 send_count = 0;
+ int32 receive_count = 0;
+ dsm_segment *seg;
+ shm_mq_handle *outqh;
+ shm_mq_handle *inqh;
+ shm_mq_result res;
+ Size len;
+ void *data;
+
+ /* A negative loopcount is nonsensical. */
+ if (loop_count < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("repeat count size must be an integer value greater than or equal to zero")));
+
+ /*
+ * Using the nonblocking interfaces, we can even send data to ourselves,
+ * so the minimum number of workers for this test is zero.
+ */
+ if (nworkers < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("number of workers must be an integer value greater than or equal to zero")));
+
+ /* Set up dynamic shared memory segment and background workers. */
+ test_shm_mq_setup(queue_size, nworkers, &seg, &outqh, &inqh);
+
+ /* Main loop. */
+ for (;;)
+ {
+ bool wait = true;
+
+ /*
+ * If we haven't yet sent the message the requisite number of times,
+ * try again to send it now. Note that when shm_mq_send() returns
+ * SHM_MQ_WOULD_BLOCK, the next call to that function must pass the
+ * same message size and contents; that's not an issue here because
+ * we're sending the same message every time.
+ */
+ if (send_count < loop_count)
+ {
+ res = shm_mq_send(outqh, message_size, message_contents, true);
+ if (res == SHM_MQ_SUCCESS)
+ {
+ ++send_count;
+ wait = false;
+ }
+ else if (res == SHM_MQ_DETACHED)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("could not send message")));
+ }
+
+ /*
+ * If we haven't yet received the message the requisite number of
+ * times, try to receive it again now.
+ */
+ if (receive_count < loop_count)
+ {
+ res = shm_mq_receive(inqh, &len, &data, true);
+ if (res == SHM_MQ_SUCCESS)
+ {
+ ++receive_count;
+ /* Verifying every time is slow, so it's optional. */
+ if (verify)
+ verify_message(message_size, message_contents, len, data);
+ wait = false;
+ }
+ else if (res == SHM_MQ_DETACHED)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("could not receive message")));
+ }
+ else
+ {
+ /*
+ * Otherwise, we've received the message enough times. This
+ * shouldn't happen unless we've also sent it enough times.
+ */
+ if (send_count != receive_count)
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("message sent %d times, but received %d times",
+ send_count, receive_count)));
+ break;
+ }
+
+ if (wait)
+ {
+ /*
+ * If we made no progress, wait for one of the other processes to
+ * which we are connected to set our latch, indicating that they
+ * have read or written data and therefore there may now be work
+ * for us to do.
+ */
+ (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
+ PG_WAIT_EXTENSION);
+ ResetLatch(MyLatch);
+ CHECK_FOR_INTERRUPTS();
+ }
+ }
+
+ /* Clean up. */
+ dsm_detach(seg);
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * Verify that two messages are the same.
+ */
+static void
+verify_message(Size origlen, char *origdata, Size newlen, char *newdata)
+{
+ Size i;
+
+ if (origlen != newlen)
+ ereport(ERROR,
+ (errmsg("message corrupted"),
+ errdetail("The original message was %zu bytes but the final message is %zu bytes.",
+ origlen, newlen)));
+
+ for (i = 0; i < origlen; ++i)
+ if (origdata[i] != newdata[i])
+ ereport(ERROR,
+ (errmsg("message corrupted"),
+ errdetail("The new and original messages differ at byte %zu of %zu.", i, origlen)));
+}
diff --git a/src/test/modules/test_shm_mq/test_shm_mq--1.0.sql b/src/test/modules/test_shm_mq/test_shm_mq--1.0.sql
new file mode 100644
index 0000000..56db05d
--- /dev/null
+++ b/src/test/modules/test_shm_mq/test_shm_mq--1.0.sql
@@ -0,0 +1,19 @@
+/* src/test/modules/test_shm_mq/test_shm_mq--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_shm_mq" to load this file. \quit
+
+CREATE FUNCTION test_shm_mq(queue_size pg_catalog.int8,
+ message pg_catalog.text,
+ repeat_count pg_catalog.int4 default 1,
+ num_workers pg_catalog.int4 default 1)
+ RETURNS pg_catalog.void STRICT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_shm_mq_pipelined(queue_size pg_catalog.int8,
+ message pg_catalog.text,
+ repeat_count pg_catalog.int4 default 1,
+ num_workers pg_catalog.int4 default 1,
+ verify pg_catalog.bool default true)
+ RETURNS pg_catalog.void STRICT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_shm_mq/test_shm_mq.control b/src/test/modules/test_shm_mq/test_shm_mq.control
new file mode 100644
index 0000000..d9a74c7
--- /dev/null
+++ b/src/test/modules/test_shm_mq/test_shm_mq.control
@@ -0,0 +1,4 @@
+comment = 'Test code for shared memory message queues'
+default_version = '1.0'
+module_pathname = '$libdir/test_shm_mq'
+relocatable = true
diff --git a/src/test/modules/test_shm_mq/test_shm_mq.h b/src/test/modules/test_shm_mq/test_shm_mq.h
new file mode 100644
index 0000000..a666121
--- /dev/null
+++ b/src/test/modules/test_shm_mq/test_shm_mq.h
@@ -0,0 +1,45 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_shm_mq.h
+ * Definitions for shared memory message queues
+ *
+ * Copyright (c) 2013-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_shm_mq/test_shm_mq.h
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#ifndef TEST_SHM_MQ_H
+#define TEST_SHM_MQ_H
+
+#include "storage/dsm.h"
+#include "storage/shm_mq.h"
+#include "storage/spin.h"
+
+/* Identifier for shared memory segments used by this extension. */
+#define PG_TEST_SHM_MQ_MAGIC 0x79fb2447
+
+/*
+ * This structure is stored in the dynamic shared memory segment. We use
+ * it to determine whether all workers started up OK and successfully
+ * attached to their respective shared message queues.
+ */
+typedef struct
+{
+ slock_t mutex;
+ int workers_total;
+ int workers_attached;
+ int workers_ready;
+} test_shm_mq_header;
+
+/* Set up dynamic shared memory and background workers for test run. */
+extern void test_shm_mq_setup(int64 queue_size, int32 nworkers,
+ dsm_segment **seg, shm_mq_handle **output,
+ shm_mq_handle **input);
+
+/* Main entrypoint for a worker. */
+extern void test_shm_mq_main(Datum) pg_attribute_noreturn();
+
+#endif
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
new file mode 100644
index 0000000..2180776
--- /dev/null
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -0,0 +1,197 @@
+/*--------------------------------------------------------------------------
+ *
+ * worker.c
+ * Code for sample worker making use of shared memory message queues.
+ * Our test worker simply reads messages from one message queue and
+ * writes them back out to another message queue. In a real
+ * application, you'd presumably want the worker to do some more
+ * complex calculation rather than simply returning the input,
+ * but it should be possible to use much of the control logic just
+ * as presented here.
+ *
+ * Copyright (c) 2013-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_shm_mq/worker.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "storage/shm_mq.h"
+#include "storage/shm_toc.h"
+#include "tcop/tcopprot.h"
+
+#include "test_shm_mq.h"
+
+static void attach_to_queues(dsm_segment *seg, shm_toc *toc,
+ int myworkernumber, shm_mq_handle **inqhp,
+ shm_mq_handle **outqhp);
+static void copy_messages(shm_mq_handle *inqh, shm_mq_handle *outqh);
+
+/*
+ * Background worker entrypoint.
+ *
+ * This is intended to demonstrate how a background worker can be used to
+ * facilitate a parallel computation. Most of the logic here is fairly
+ * boilerplate stuff, designed to attach to the shared memory segment,
+ * notify the user backend that we're alive, and so on. The
+ * application-specific bits of logic that you'd replace for your own worker
+ * are attach_to_queues() and copy_messages().
+ */
+void
+test_shm_mq_main(Datum main_arg)
+{
+ dsm_segment *seg;
+ shm_toc *toc;
+ shm_mq_handle *inqh;
+ shm_mq_handle *outqh;
+ volatile test_shm_mq_header *hdr;
+ int myworkernumber;
+ PGPROC *registrant;
+
+ /*
+ * Establish signal handlers.
+ *
+ * We want CHECK_FOR_INTERRUPTS() to kill off this worker process just as
+ * it would a normal user backend. To make that happen, we use die().
+ */
+ pqsignal(SIGTERM, die);
+ BackgroundWorkerUnblockSignals();
+
+ /*
+ * Connect to the dynamic shared memory segment.
+ *
+ * The backend that registered this worker passed us the ID of a shared
+ * memory segment to which we must attach for further instructions. Once
+ * we've mapped the segment in our address space, attach to the table of
+ * contents so we can locate the various data structures we'll need to
+ * find within the segment.
+ *
+ * Note: at this point, we have not created any ResourceOwner in this
+ * process. This will result in our DSM mapping surviving until process
+ * exit, which is fine. If there were a ResourceOwner, it would acquire
+ * ownership of the mapping, but we have no need for that.
+ */
+ seg = dsm_attach(DatumGetInt32(main_arg));
+ if (seg == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("unable to map dynamic shared memory segment")));
+ toc = shm_toc_attach(PG_TEST_SHM_MQ_MAGIC, dsm_segment_address(seg));
+ if (toc == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("bad magic number in dynamic shared memory segment")));
+
+ /*
+ * Acquire a worker number.
+ *
+ * By convention, the process registering this background worker should
+ * have stored the control structure at key 0. We look up that key to
+ * find it. Our worker number gives our identity: there may be just one
+ * worker involved in this parallel operation, or there may be many.
+ */
+ hdr = shm_toc_lookup(toc, 0, false);
+ SpinLockAcquire(&hdr->mutex);
+ myworkernumber = ++hdr->workers_attached;
+ SpinLockRelease(&hdr->mutex);
+ if (myworkernumber > hdr->workers_total)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("too many message queue testing workers already")));
+
+ /*
+ * Attach to the appropriate message queues.
+ */
+ attach_to_queues(seg, toc, myworkernumber, &inqh, &outqh);
+
+ /*
+ * Indicate that we're fully initialized and ready to begin the main part
+ * of the parallel operation.
+ *
+ * Once we signal that we're ready, the user backend is entitled to assume
+ * that our on_dsm_detach callbacks will fire before we disconnect from
+ * the shared memory segment and exit. Generally, that means we must have
+ * attached to all relevant dynamic shared memory data structures by now.
+ */
+ SpinLockAcquire(&hdr->mutex);
+ ++hdr->workers_ready;
+ SpinLockRelease(&hdr->mutex);
+ registrant = BackendPidGetProc(MyBgworkerEntry->bgw_notify_pid);
+ if (registrant == NULL)
+ {
+ elog(DEBUG1, "registrant backend has exited prematurely");
+ proc_exit(1);
+ }
+ SetLatch(&registrant->procLatch);
+
+ /* Do the work. */
+ copy_messages(inqh, outqh);
+
+ /*
+ * We're done. For cleanliness, explicitly detach from the shared memory
+ * segment (that would happen anyway during process exit, though).
+ */
+ dsm_detach(seg);
+ proc_exit(1);
+}
+
+/*
+ * Attach to shared memory message queues.
+ *
+ * We use our worker number to determine to which queue we should attach.
+ * The queues are registered at keys 1..<number-of-workers>. The user backend
+ * writes to queue #1 and reads from queue #<number-of-workers>; each worker
+ * reads from the queue whose number is equal to its worker number and writes
+ * to the next higher-numbered queue.
+ */
+static void
+attach_to_queues(dsm_segment *seg, shm_toc *toc, int myworkernumber,
+ shm_mq_handle **inqhp, shm_mq_handle **outqhp)
+{
+ shm_mq *inq;
+ shm_mq *outq;
+
+ inq = shm_toc_lookup(toc, myworkernumber, false);
+ shm_mq_set_receiver(inq, MyProc);
+ *inqhp = shm_mq_attach(inq, seg, NULL);
+ outq = shm_toc_lookup(toc, myworkernumber + 1, false);
+ shm_mq_set_sender(outq, MyProc);
+ *outqhp = shm_mq_attach(outq, seg, NULL);
+}
+
+/*
+ * Loop, receiving and sending messages, until the connection is broken.
+ *
+ * This is the "real work" performed by this worker process. Everything that
+ * happens before this is initialization of one form or another, and everything
+ * after this point is cleanup.
+ */
+static void
+copy_messages(shm_mq_handle *inqh, shm_mq_handle *outqh)
+{
+ Size len;
+ void *data;
+ shm_mq_result res;
+
+ for (;;)
+ {
+ /* Notice any interrupts that have occurred. */
+ CHECK_FOR_INTERRUPTS();
+
+ /* Receive a message. */
+ res = shm_mq_receive(inqh, &len, &data, false);
+ if (res != SHM_MQ_SUCCESS)
+ break;
+
+ /* Send it back out. */
+ res = shm_mq_send(outqh, len, data, false);
+ if (res != SHM_MQ_SUCCESS)
+ break;
+ }
+}
diff --git a/src/test/modules/unsafe_tests/.gitignore b/src/test/modules/unsafe_tests/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/unsafe_tests/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/unsafe_tests/Makefile b/src/test/modules/unsafe_tests/Makefile
new file mode 100644
index 0000000..3ecf5fc
--- /dev/null
+++ b/src/test/modules/unsafe_tests/Makefile
@@ -0,0 +1,14 @@
+# src/test/modules/unsafe_tests/Makefile
+
+REGRESS = rolenames alter_system_table
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/unsafe_tests
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/unsafe_tests/README b/src/test/modules/unsafe_tests/README
new file mode 100644
index 0000000..a7e5b2a
--- /dev/null
+++ b/src/test/modules/unsafe_tests/README
@@ -0,0 +1,8 @@
+This directory doesn't actually contain any extension module.
+
+What it is is a home for regression tests that we don't want to run
+during "make installcheck" because they could have side-effects that
+seem undesirable for a production installation.
+
+An example is that rolenames.sql tests ALTER USER ALL and so could
+have effects on pre-existing roles.
diff --git a/src/test/modules/unsafe_tests/expected/alter_system_table.out b/src/test/modules/unsafe_tests/expected/alter_system_table.out
new file mode 100644
index 0000000..4b2f63a
--- /dev/null
+++ b/src/test/modules/unsafe_tests/expected/alter_system_table.out
@@ -0,0 +1,179 @@
+--
+-- Tests for things affected by allow_system_table_mods
+--
+-- We run the same set of commands once with allow_system_table_mods
+-- off and then again with on.
+--
+-- The "on" tests should where possible be wrapped in BEGIN/ROLLBACK
+-- blocks so as to not leave a mess around.
+CREATE USER regress_user_ast;
+SET allow_system_table_mods = off;
+-- create new table in pg_catalog
+CREATE TABLE pg_catalog.test (a int);
+ERROR: permission denied to create "pg_catalog.test"
+DETAIL: System catalog modifications are currently disallowed.
+-- anyarray column
+CREATE TABLE t1x (a int, b anyarray);
+ERROR: column "b" has pseudo-type anyarray
+-- index on system catalog
+ALTER TABLE pg_namespace ADD CONSTRAINT foo UNIQUE USING INDEX pg_namespace_nspname_index;
+ERROR: permission denied: "pg_namespace" is a system catalog
+-- write to system catalog table as superuser
+-- (allowed even without allow_system_table_mods)
+INSERT INTO pg_description (objoid, classoid, objsubid, description) VALUES (0, 0, 0, 'foo');
+-- write to system catalog table as normal user
+GRANT INSERT ON pg_description TO regress_user_ast;
+SET ROLE regress_user_ast;
+INSERT INTO pg_description (objoid, classoid, objsubid, description) VALUES (0, 0, 1, 'foo');
+ERROR: permission denied for table pg_description
+RESET ROLE;
+-- policy on system catalog
+CREATE POLICY foo ON pg_description FOR SELECT USING (description NOT LIKE 'secret%');
+ERROR: permission denied: "pg_description" is a system catalog
+-- reserved schema name
+CREATE SCHEMA pg_foo;
+ERROR: unacceptable schema name "pg_foo"
+DETAIL: The prefix "pg_" is reserved for system schemas.
+-- drop system table
+DROP TABLE pg_description;
+ERROR: permission denied: "pg_description" is a system catalog
+-- truncate of system table
+TRUNCATE pg_description;
+ERROR: permission denied: "pg_description" is a system catalog
+-- rename column of system table
+ALTER TABLE pg_description RENAME COLUMN description TO comment;
+ERROR: permission denied: "pg_description" is a system catalog
+-- ATSimplePermissions()
+ALTER TABLE pg_description ALTER COLUMN description SET NOT NULL;
+ERROR: permission denied: "pg_description" is a system catalog
+-- SET STATISTICS
+ALTER TABLE pg_description ALTER COLUMN description SET STATISTICS -1;
+ERROR: permission denied: "pg_description" is a system catalog
+-- foreign key referencing catalog
+CREATE TABLE foo (a oid, b oid, c int, FOREIGN KEY (a, b, c) REFERENCES pg_description);
+ERROR: permission denied: "pg_description" is a system catalog
+-- RangeVarCallbackOwnsRelation()
+CREATE INDEX pg_descripton_test_index ON pg_description (description);
+ERROR: permission denied: "pg_description" is a system catalog
+-- RangeVarCallbackForAlterRelation()
+ALTER TABLE pg_description RENAME TO pg_comment;
+ERROR: permission denied: "pg_description" is a system catalog
+ALTER TABLE pg_description SET SCHEMA public;
+ERROR: permission denied: "pg_description" is a system catalog
+-- reserved tablespace name
+CREATE TABLESPACE pg_foo LOCATION '/no/such/location';
+ERROR: unacceptable tablespace name "pg_foo"
+DETAIL: The prefix "pg_" is reserved for system tablespaces.
+-- triggers
+CREATE FUNCTION tf1() RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RETURN NULL;
+END $$;
+CREATE TRIGGER t1 BEFORE INSERT ON pg_description EXECUTE FUNCTION tf1();
+ERROR: permission denied: "pg_description" is a system catalog
+ALTER TRIGGER t1 ON pg_description RENAME TO t2;
+ERROR: permission denied: "pg_description" is a system catalog
+--DROP TRIGGER t2 ON pg_description;
+-- rules
+CREATE RULE r1 AS ON INSERT TO pg_description DO INSTEAD NOTHING;
+ERROR: permission denied: "pg_description" is a system catalog
+ALTER RULE r1 ON pg_description RENAME TO r2;
+ERROR: permission denied: "pg_description" is a system catalog
+-- now make one to test dropping:
+SET allow_system_table_mods TO on;
+CREATE RULE r2 AS ON INSERT TO pg_description DO INSTEAD NOTHING;
+RESET allow_system_table_mods;
+DROP RULE r2 ON pg_description;
+ERROR: permission denied: "pg_description" is a system catalog
+-- cleanup:
+SET allow_system_table_mods TO on;
+DROP RULE r2 ON pg_description;
+RESET allow_system_table_mods;
+SET allow_system_table_mods = on;
+-- create new table in pg_catalog
+BEGIN;
+CREATE TABLE pg_catalog.test (a int);
+ROLLBACK;
+-- anyarray column
+BEGIN;
+CREATE TABLE t1 (a int, b anyarray);
+ROLLBACK;
+-- index on system catalog
+BEGIN;
+ALTER TABLE pg_namespace ADD CONSTRAINT foo UNIQUE USING INDEX pg_namespace_nspname_index;
+NOTICE: ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index "pg_namespace_nspname_index" to "foo"
+ROLLBACK;
+-- write to system catalog table as superuser
+BEGIN;
+INSERT INTO pg_description (objoid, classoid, objsubid, description) VALUES (0, 0, 2, 'foo');
+ROLLBACK;
+-- write to system catalog table as normal user
+-- (not allowed)
+SET ROLE regress_user_ast;
+INSERT INTO pg_description (objoid, classoid, objsubid, description) VALUES (0, 0, 3, 'foo');
+ERROR: permission denied for table pg_description
+RESET ROLE;
+-- policy on system catalog
+BEGIN;
+CREATE POLICY foo ON pg_description FOR SELECT USING (description NOT LIKE 'secret%');
+ROLLBACK;
+-- reserved schema name
+BEGIN;
+CREATE SCHEMA pg_foo;
+ROLLBACK;
+-- drop system table
+-- (This will fail anyway because it's pinned.)
+BEGIN;
+DROP TABLE pg_description;
+ERROR: cannot drop table pg_description because it is required by the database system
+ROLLBACK;
+-- truncate of system table
+BEGIN;
+TRUNCATE pg_description;
+ROLLBACK;
+-- rename column of system table
+BEGIN;
+ALTER TABLE pg_description RENAME COLUMN description TO comment;
+ROLLBACK;
+-- ATSimplePermissions()
+BEGIN;
+ALTER TABLE pg_description ALTER COLUMN description SET NOT NULL;
+ROLLBACK;
+-- SET STATISTICS
+BEGIN;
+ALTER TABLE pg_description ALTER COLUMN description SET STATISTICS -1;
+ROLLBACK;
+-- foreign key referencing catalog
+BEGIN;
+CREATE TABLE foo (a oid, b oid, c int, FOREIGN KEY (a, b, c) REFERENCES pg_description);
+ROLLBACK;
+-- RangeVarCallbackOwnsRelation()
+BEGIN;
+CREATE INDEX pg_descripton_test_index ON pg_description (description);
+ROLLBACK;
+-- RangeVarCallbackForAlterRelation()
+BEGIN;
+ALTER TABLE pg_description RENAME TO pg_comment;
+ROLLBACK;
+BEGIN;
+ALTER TABLE pg_description SET SCHEMA public;
+ROLLBACK;
+-- reserved tablespace name
+SET client_min_messages = error; -- disable ENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS warning
+CREATE TABLESPACE pg_foo LOCATION '/no/such/location';
+ERROR: directory "/no/such/location" does not exist
+RESET client_min_messages;
+-- triggers
+CREATE TRIGGER t1 BEFORE INSERT ON pg_description EXECUTE FUNCTION tf1();
+ALTER TRIGGER t1 ON pg_description RENAME TO t2;
+DROP TRIGGER t2 ON pg_description;
+-- rules
+CREATE RULE r1 AS ON INSERT TO pg_description DO INSTEAD NOTHING;
+ALTER RULE r1 ON pg_description RENAME TO r2;
+DROP RULE r2 ON pg_description;
+-- cleanup
+REVOKE ALL ON pg_description FROM regress_user_ast;
+DROP USER regress_user_ast;
+DROP FUNCTION tf1;
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
new file mode 100644
index 0000000..eb608fd
--- /dev/null
+++ b/src/test/modules/unsafe_tests/expected/rolenames.out
@@ -0,0 +1,1091 @@
+CREATE FUNCTION chkrolattr()
+ RETURNS TABLE ("role" name, rolekeyword text, canlogin bool, replication bool)
+ AS $$
+SELECT r.rolname, v.keyword, r.rolcanlogin, r.rolreplication
+ FROM pg_roles r
+ JOIN (VALUES(CURRENT_ROLE, 'current_role'),
+ (CURRENT_USER, 'current_user'),
+ (SESSION_USER, 'session_user'),
+ ('current_role', '-'),
+ ('current_user', '-'),
+ ('session_user', '-'),
+ ('Public', '-'),
+ ('None', '-'))
+ AS v(uname, keyword)
+ ON (r.rolname = v.uname)
+ ORDER BY 1, 2;
+$$ LANGUAGE SQL;
+CREATE FUNCTION chksetconfig()
+ RETURNS TABLE (db name, "role" name, rolkeyword text, setconfig text[])
+ AS $$
+SELECT COALESCE(d.datname, 'ALL'), COALESCE(r.rolname, 'ALL'),
+ COALESCE(v.keyword, '-'), s.setconfig
+ FROM pg_db_role_setting s
+ LEFT JOIN pg_roles r ON (r.oid = s.setrole)
+ LEFT JOIN pg_database d ON (d.oid = s.setdatabase)
+ LEFT JOIN (VALUES(CURRENT_ROLE, 'current_role'),
+ (CURRENT_USER, 'current_user'),
+ (SESSION_USER, 'session_user'))
+ AS v(uname, keyword)
+ ON (r.rolname = v.uname)
+ WHERE (r.rolname) IN ('Public', 'current_user', 'regress_testrol1', 'regress_testrol2')
+ORDER BY 1, 2, 3;
+$$ LANGUAGE SQL;
+CREATE FUNCTION chkumapping()
+ RETURNS TABLE (umname name, umserver name, umoptions text[])
+ AS $$
+SELECT r.rolname, s.srvname, m.umoptions
+ FROM pg_user_mapping m
+ LEFT JOIN pg_roles r ON (r.oid = m.umuser)
+ JOIN pg_foreign_server s ON (s.oid = m.umserver)
+ ORDER BY 2, 1;
+$$ LANGUAGE SQL;
+--
+-- We test creation and use of these role names to ensure that the server
+-- correctly distinguishes role keywords from quoted names that look like
+-- those keywords. In a test environment, creation of these roles may
+-- provoke warnings, so hide the warnings by raising client_min_messages.
+--
+SET client_min_messages = ERROR;
+CREATE ROLE "Public";
+CREATE ROLE "None";
+CREATE ROLE "current_role";
+CREATE ROLE "current_user";
+CREATE ROLE "session_user";
+CREATE ROLE "user";
+RESET client_min_messages;
+CREATE ROLE current_user; -- error
+ERROR: CURRENT_USER cannot be used as a role name here
+LINE 1: CREATE ROLE current_user;
+ ^
+CREATE ROLE current_role; -- error
+ERROR: CURRENT_ROLE cannot be used as a role name here
+LINE 1: CREATE ROLE current_role;
+ ^
+CREATE ROLE session_user; -- error
+ERROR: SESSION_USER cannot be used as a role name here
+LINE 1: CREATE ROLE session_user;
+ ^
+CREATE ROLE user; -- error
+ERROR: syntax error at or near "user"
+LINE 1: CREATE ROLE user;
+ ^
+CREATE ROLE all; -- error
+ERROR: syntax error at or near "all"
+LINE 1: CREATE ROLE all;
+ ^
+CREATE ROLE public; -- error
+ERROR: role name "public" is reserved
+LINE 1: CREATE ROLE public;
+ ^
+CREATE ROLE "public"; -- error
+ERROR: role name "public" is reserved
+LINE 1: CREATE ROLE "public";
+ ^
+CREATE ROLE none; -- error
+ERROR: role name "none" is reserved
+LINE 1: CREATE ROLE none;
+ ^
+CREATE ROLE "none"; -- error
+ERROR: role name "none" is reserved
+LINE 1: CREATE ROLE "none";
+ ^
+CREATE ROLE pg_abc; -- error
+ERROR: role name "pg_abc" is reserved
+DETAIL: Role names starting with "pg_" are reserved.
+CREATE ROLE "pg_abc"; -- error
+ERROR: role name "pg_abc" is reserved
+DETAIL: Role names starting with "pg_" are reserved.
+CREATE ROLE pg_abcdef; -- error
+ERROR: role name "pg_abcdef" is reserved
+DETAIL: Role names starting with "pg_" are reserved.
+CREATE ROLE "pg_abcdef"; -- error
+ERROR: role name "pg_abcdef" is reserved
+DETAIL: Role names starting with "pg_" are reserved.
+CREATE ROLE regress_testrol0 SUPERUSER LOGIN;
+CREATE ROLE regress_testrolx SUPERUSER LOGIN;
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+-- ALTER ROLE
+BEGIN;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | f
+ current_user | - | f | f
+ regress_testrol1 | session_user | t | f
+ regress_testrol2 | current_role | f | f
+ regress_testrol2 | current_user | f | f
+ session_user | - | f | f
+(8 rows)
+
+ALTER ROLE CURRENT_ROLE WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | f
+ current_user | - | f | f
+ regress_testrol1 | session_user | t | f
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | f
+(8 rows)
+
+ALTER ROLE "current_role" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | t
+ current_user | - | f | f
+ regress_testrol1 | session_user | t | f
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | f
+(8 rows)
+
+ALTER ROLE CURRENT_ROLE WITH NOREPLICATION;
+ALTER ROLE CURRENT_USER WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | t
+ current_user | - | f | f
+ regress_testrol1 | session_user | t | f
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | f
+(8 rows)
+
+ALTER ROLE "current_user" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | t
+ current_user | - | f | t
+ regress_testrol1 | session_user | t | f
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | f
+(8 rows)
+
+ALTER ROLE SESSION_USER WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | t
+ current_user | - | f | t
+ regress_testrol1 | session_user | t | t
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | f
+(8 rows)
+
+ALTER ROLE "session_user" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | t
+ current_user | - | f | t
+ regress_testrol1 | session_user | t | t
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | t
+(8 rows)
+
+ALTER USER "Public" WITH REPLICATION;
+ALTER USER "None" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | t
+ Public | - | f | t
+ current_role | - | f | t
+ current_user | - | f | t
+ regress_testrol1 | session_user | t | t
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | t
+(8 rows)
+
+ALTER USER regress_testrol1 WITH NOREPLICATION;
+ALTER USER regress_testrol2 WITH NOREPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | t
+ Public | - | f | t
+ current_role | - | f | t
+ current_user | - | f | t
+ regress_testrol1 | session_user | t | f
+ regress_testrol2 | current_role | f | f
+ regress_testrol2 | current_user | f | f
+ session_user | - | f | t
+(8 rows)
+
+ROLLBACK;
+ALTER ROLE USER WITH LOGIN; -- error
+ERROR: syntax error at or near "USER"
+LINE 1: ALTER ROLE USER WITH LOGIN;
+ ^
+ALTER ROLE ALL WITH REPLICATION; -- error
+ERROR: syntax error at or near "WITH"
+LINE 1: ALTER ROLE ALL WITH REPLICATION;
+ ^
+ALTER ROLE SESSION_ROLE WITH NOREPLICATION; -- error
+ERROR: role "session_role" does not exist
+ALTER ROLE PUBLIC WITH NOREPLICATION; -- error
+ERROR: role "public" does not exist
+ALTER ROLE "public" WITH NOREPLICATION; -- error
+ERROR: role "public" does not exist
+ALTER ROLE NONE WITH NOREPLICATION; -- error
+ERROR: role name "none" is reserved
+LINE 1: ALTER ROLE NONE WITH NOREPLICATION;
+ ^
+ALTER ROLE "none" WITH NOREPLICATION; -- error
+ERROR: role name "none" is reserved
+LINE 1: ALTER ROLE "none" WITH NOREPLICATION;
+ ^
+ALTER ROLE nonexistent WITH NOREPLICATION; -- error
+ERROR: role "nonexistent" does not exist
+-- ALTER USER
+BEGIN;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | f
+ current_user | - | f | f
+ regress_testrol1 | session_user | t | f
+ regress_testrol2 | current_role | f | f
+ regress_testrol2 | current_user | f | f
+ session_user | - | f | f
+(8 rows)
+
+ALTER USER CURRENT_ROLE WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | f
+ current_user | - | f | f
+ regress_testrol1 | session_user | t | f
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | f
+(8 rows)
+
+ALTER USER "current_role" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | t
+ current_user | - | f | f
+ regress_testrol1 | session_user | t | f
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | f
+(8 rows)
+
+ALTER USER CURRENT_ROLE WITH NOREPLICATION;
+ALTER USER CURRENT_USER WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | t
+ current_user | - | f | f
+ regress_testrol1 | session_user | t | f
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | f
+(8 rows)
+
+ALTER USER "current_user" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | t
+ current_user | - | f | t
+ regress_testrol1 | session_user | t | f
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | f
+(8 rows)
+
+ALTER USER SESSION_USER WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | t
+ current_user | - | f | t
+ regress_testrol1 | session_user | t | t
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | f
+(8 rows)
+
+ALTER USER "session_user" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | f
+ Public | - | f | f
+ current_role | - | f | t
+ current_user | - | f | t
+ regress_testrol1 | session_user | t | t
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | t
+(8 rows)
+
+ALTER USER "Public" WITH REPLICATION;
+ALTER USER "None" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | t
+ Public | - | f | t
+ current_role | - | f | t
+ current_user | - | f | t
+ regress_testrol1 | session_user | t | t
+ regress_testrol2 | current_role | f | t
+ regress_testrol2 | current_user | f | t
+ session_user | - | f | t
+(8 rows)
+
+ALTER USER regress_testrol1 WITH NOREPLICATION;
+ALTER USER regress_testrol2 WITH NOREPLICATION;
+SELECT * FROM chkrolattr();
+ role | rolekeyword | canlogin | replication
+------------------+--------------+----------+-------------
+ None | - | f | t
+ Public | - | f | t
+ current_role | - | f | t
+ current_user | - | f | t
+ regress_testrol1 | session_user | t | f
+ regress_testrol2 | current_role | f | f
+ regress_testrol2 | current_user | f | f
+ session_user | - | f | t
+(8 rows)
+
+ROLLBACK;
+ALTER USER USER WITH LOGIN; -- error
+ERROR: syntax error at or near "USER"
+LINE 1: ALTER USER USER WITH LOGIN;
+ ^
+ALTER USER ALL WITH REPLICATION; -- error
+ERROR: syntax error at or near "WITH"
+LINE 1: ALTER USER ALL WITH REPLICATION;
+ ^
+ALTER USER SESSION_ROLE WITH NOREPLICATION; -- error
+ERROR: role "session_role" does not exist
+ALTER USER PUBLIC WITH NOREPLICATION; -- error
+ERROR: role "public" does not exist
+ALTER USER "public" WITH NOREPLICATION; -- error
+ERROR: role "public" does not exist
+ALTER USER NONE WITH NOREPLICATION; -- error
+ERROR: role name "none" is reserved
+LINE 1: ALTER USER NONE WITH NOREPLICATION;
+ ^
+ALTER USER "none" WITH NOREPLICATION; -- error
+ERROR: role name "none" is reserved
+LINE 1: ALTER USER "none" WITH NOREPLICATION;
+ ^
+ALTER USER nonexistent WITH NOREPLICATION; -- error
+ERROR: role "nonexistent" does not exist
+-- ALTER ROLE SET/RESET
+SELECT * FROM chksetconfig();
+ db | role | rolkeyword | setconfig
+----+------+------------+-----------
+(0 rows)
+
+ALTER ROLE CURRENT_ROLE SET application_name to 'BAZ';
+ALTER ROLE CURRENT_USER SET application_name to 'FOO';
+ALTER ROLE SESSION_USER SET application_name to 'BAR';
+ALTER ROLE "current_user" SET application_name to 'FOOFOO';
+ALTER ROLE "Public" SET application_name to 'BARBAR';
+ALTER ROLE ALL SET application_name to 'SLAP';
+SELECT * FROM chksetconfig();
+ db | role | rolkeyword | setconfig
+-----+------------------+--------------+---------------------------
+ ALL | Public | - | {application_name=BARBAR}
+ ALL | current_user | - | {application_name=FOOFOO}
+ ALL | regress_testrol1 | session_user | {application_name=BAR}
+ ALL | regress_testrol2 | current_role | {application_name=FOO}
+ ALL | regress_testrol2 | current_user | {application_name=FOO}
+(5 rows)
+
+ALTER ROLE regress_testrol1 SET application_name to 'SLAM';
+SELECT * FROM chksetconfig();
+ db | role | rolkeyword | setconfig
+-----+------------------+--------------+---------------------------
+ ALL | Public | - | {application_name=BARBAR}
+ ALL | current_user | - | {application_name=FOOFOO}
+ ALL | regress_testrol1 | session_user | {application_name=SLAM}
+ ALL | regress_testrol2 | current_role | {application_name=FOO}
+ ALL | regress_testrol2 | current_user | {application_name=FOO}
+(5 rows)
+
+ALTER ROLE CURRENT_ROLE RESET application_name;
+ALTER ROLE CURRENT_USER RESET application_name;
+ALTER ROLE SESSION_USER RESET application_name;
+ALTER ROLE "current_user" RESET application_name;
+ALTER ROLE "Public" RESET application_name;
+ALTER ROLE ALL RESET application_name;
+SELECT * FROM chksetconfig();
+ db | role | rolkeyword | setconfig
+----+------+------------+-----------
+(0 rows)
+
+ALTER ROLE USER SET application_name to 'BOOM'; -- error
+ERROR: syntax error at or near "USER"
+LINE 1: ALTER ROLE USER SET application_name to 'BOOM';
+ ^
+ALTER ROLE PUBLIC SET application_name to 'BOMB'; -- error
+ERROR: role "public" does not exist
+ALTER ROLE nonexistent SET application_name to 'BOMB'; -- error
+ERROR: role "nonexistent" does not exist
+-- ALTER USER SET/RESET
+SELECT * FROM chksetconfig();
+ db | role | rolkeyword | setconfig
+----+------+------------+-----------
+(0 rows)
+
+ALTER USER CURRENT_ROLE SET application_name to 'BAZ';
+ALTER USER CURRENT_USER SET application_name to 'FOO';
+ALTER USER SESSION_USER SET application_name to 'BAR';
+ALTER USER "current_user" SET application_name to 'FOOFOO';
+ALTER USER "Public" SET application_name to 'BARBAR';
+ALTER USER ALL SET application_name to 'SLAP';
+SELECT * FROM chksetconfig();
+ db | role | rolkeyword | setconfig
+-----+------------------+--------------+---------------------------
+ ALL | Public | - | {application_name=BARBAR}
+ ALL | current_user | - | {application_name=FOOFOO}
+ ALL | regress_testrol1 | session_user | {application_name=BAR}
+ ALL | regress_testrol2 | current_role | {application_name=FOO}
+ ALL | regress_testrol2 | current_user | {application_name=FOO}
+(5 rows)
+
+ALTER USER regress_testrol1 SET application_name to 'SLAM';
+SELECT * FROM chksetconfig();
+ db | role | rolkeyword | setconfig
+-----+------------------+--------------+---------------------------
+ ALL | Public | - | {application_name=BARBAR}
+ ALL | current_user | - | {application_name=FOOFOO}
+ ALL | regress_testrol1 | session_user | {application_name=SLAM}
+ ALL | regress_testrol2 | current_role | {application_name=FOO}
+ ALL | regress_testrol2 | current_user | {application_name=FOO}
+(5 rows)
+
+ALTER USER CURRENT_ROLE RESET application_name;
+ALTER USER CURRENT_USER RESET application_name;
+ALTER USER SESSION_USER RESET application_name;
+ALTER USER "current_user" RESET application_name;
+ALTER USER "Public" RESET application_name;
+ALTER USER ALL RESET application_name;
+SELECT * FROM chksetconfig();
+ db | role | rolkeyword | setconfig
+----+------+------------+-----------
+(0 rows)
+
+ALTER USER USER SET application_name to 'BOOM'; -- error
+ERROR: syntax error at or near "USER"
+LINE 1: ALTER USER USER SET application_name to 'BOOM';
+ ^
+ALTER USER PUBLIC SET application_name to 'BOMB'; -- error
+ERROR: role "public" does not exist
+ALTER USER NONE SET application_name to 'BOMB'; -- error
+ERROR: role name "none" is reserved
+LINE 1: ALTER USER NONE SET application_name to 'BOMB';
+ ^
+ALTER USER nonexistent SET application_name to 'BOMB'; -- error
+ERROR: role "nonexistent" does not exist
+-- CREATE SCHEMA
+CREATE SCHEMA newschema1 AUTHORIZATION CURRENT_USER;
+CREATE SCHEMA newschema2 AUTHORIZATION "current_user";
+CREATE SCHEMA newschema3 AUTHORIZATION CURRENT_ROLE;
+CREATE SCHEMA newschema4 AUTHORIZATION SESSION_USER;
+CREATE SCHEMA newschema5 AUTHORIZATION regress_testrolx;
+CREATE SCHEMA newschema6 AUTHORIZATION "Public";
+CREATE SCHEMA newschemax AUTHORIZATION USER; -- error
+ERROR: syntax error at or near "USER"
+LINE 1: CREATE SCHEMA newschemax AUTHORIZATION USER;
+ ^
+CREATE SCHEMA newschemax AUTHORIZATION PUBLIC; -- error
+ERROR: role "public" does not exist
+CREATE SCHEMA newschemax AUTHORIZATION "public"; -- error
+ERROR: role "public" does not exist
+CREATE SCHEMA newschemax AUTHORIZATION NONE; -- error
+ERROR: role name "none" is reserved
+LINE 1: CREATE SCHEMA newschemax AUTHORIZATION NONE;
+ ^
+CREATE SCHEMA newschemax AUTHORIZATION nonexistent; -- error
+ERROR: role "nonexistent" does not exist
+SELECT n.nspname, r.rolname FROM pg_namespace n
+ JOIN pg_roles r ON (r.oid = n.nspowner)
+ WHERE n.nspname LIKE 'newschema_' ORDER BY 1;
+ nspname | rolname
+------------+------------------
+ newschema1 | regress_testrol2
+ newschema2 | current_user
+ newschema3 | regress_testrol2
+ newschema4 | regress_testrol1
+ newschema5 | regress_testrolx
+ newschema6 | Public
+(6 rows)
+
+CREATE SCHEMA IF NOT EXISTS newschema1 AUTHORIZATION CURRENT_USER;
+NOTICE: schema "newschema1" already exists, skipping
+CREATE SCHEMA IF NOT EXISTS newschema2 AUTHORIZATION "current_user";
+NOTICE: schema "newschema2" already exists, skipping
+CREATE SCHEMA IF NOT EXISTS newschema3 AUTHORIZATION CURRENT_ROLE;
+NOTICE: schema "newschema3" already exists, skipping
+CREATE SCHEMA IF NOT EXISTS newschema4 AUTHORIZATION SESSION_USER;
+NOTICE: schema "newschema4" already exists, skipping
+CREATE SCHEMA IF NOT EXISTS newschema5 AUTHORIZATION regress_testrolx;
+NOTICE: schema "newschema5" already exists, skipping
+CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION "Public";
+NOTICE: schema "newschema6" already exists, skipping
+CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION USER; -- error
+ERROR: syntax error at or near "USER"
+LINE 1: CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION USER;
+ ^
+CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION PUBLIC; -- error
+ERROR: role "public" does not exist
+CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION "public"; -- error
+ERROR: role "public" does not exist
+CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION NONE; -- error
+ERROR: role name "none" is reserved
+LINE 1: CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION NONE;
+ ^
+CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION nonexistent; -- error
+ERROR: role "nonexistent" does not exist
+SELECT n.nspname, r.rolname FROM pg_namespace n
+ JOIN pg_roles r ON (r.oid = n.nspowner)
+ WHERE n.nspname LIKE 'newschema_' ORDER BY 1;
+ nspname | rolname
+------------+------------------
+ newschema1 | regress_testrol2
+ newschema2 | current_user
+ newschema3 | regress_testrol2
+ newschema4 | regress_testrol1
+ newschema5 | regress_testrolx
+ newschema6 | Public
+(6 rows)
+
+-- ALTER TABLE OWNER TO
+\c -
+SET SESSION AUTHORIZATION regress_testrol0;
+CREATE TABLE testtab1 (a int);
+CREATE TABLE testtab2 (a int);
+CREATE TABLE testtab3 (a int);
+CREATE TABLE testtab4 (a int);
+CREATE TABLE testtab5 (a int);
+CREATE TABLE testtab6 (a int);
+CREATE TABLE testtab7 (a int);
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+ALTER TABLE testtab1 OWNER TO CURRENT_USER;
+ALTER TABLE testtab2 OWNER TO "current_user";
+ALTER TABLE testtab3 OWNER TO CURRENT_ROLE;
+ALTER TABLE testtab4 OWNER TO SESSION_USER;
+ALTER TABLE testtab5 OWNER TO regress_testrolx;
+ALTER TABLE testtab6 OWNER TO "Public";
+ALTER TABLE testtab7 OWNER TO USER; --error
+ERROR: syntax error at or near "USER"
+LINE 1: ALTER TABLE testtab7 OWNER TO USER;
+ ^
+ALTER TABLE testtab7 OWNER TO PUBLIC; -- error
+ERROR: role "public" does not exist
+ALTER TABLE testtab7 OWNER TO "public"; -- error
+ERROR: role "public" does not exist
+ALTER TABLE testtab7 OWNER TO nonexistent; -- error
+ERROR: role "nonexistent" does not exist
+SELECT c.relname, r.rolname
+ FROM pg_class c JOIN pg_roles r ON (r.oid = c.relowner)
+ WHERE relname LIKE 'testtab_'
+ ORDER BY 1;
+ relname | rolname
+----------+------------------
+ testtab1 | regress_testrol2
+ testtab2 | current_user
+ testtab3 | regress_testrol2
+ testtab4 | regress_testrol1
+ testtab5 | regress_testrolx
+ testtab6 | Public
+ testtab7 | regress_testrol0
+(7 rows)
+
+-- ALTER TABLE, VIEW, MATERIALIZED VIEW, FOREIGN TABLE, SEQUENCE are
+-- changed their owner in the same way.
+-- ALTER AGGREGATE
+\c -
+SET SESSION AUTHORIZATION regress_testrol0;
+CREATE AGGREGATE testagg1(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg2(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg3(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg4(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg5(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg6(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg7(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg8(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg9(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagga(int2) (SFUNC = int2_sum, STYPE = int8);
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+ALTER AGGREGATE testagg1(int2) OWNER TO CURRENT_USER;
+ALTER AGGREGATE testagg2(int2) OWNER TO "current_user";
+ALTER AGGREGATE testagg3(int2) OWNER TO CURRENT_ROLE;
+ALTER AGGREGATE testagg4(int2) OWNER TO SESSION_USER;
+ALTER AGGREGATE testagg5(int2) OWNER TO regress_testrolx;
+ALTER AGGREGATE testagg6(int2) OWNER TO "Public";
+ALTER AGGREGATE testagg6(int2) OWNER TO USER; -- error
+ERROR: syntax error at or near "USER"
+LINE 1: ALTER AGGREGATE testagg6(int2) OWNER TO USER;
+ ^
+ALTER AGGREGATE testagg6(int2) OWNER TO PUBLIC; -- error
+ERROR: role "public" does not exist
+ALTER AGGREGATE testagg6(int2) OWNER TO "public"; -- error
+ERROR: role "public" does not exist
+ALTER AGGREGATE testagg6(int2) OWNER TO nonexistent; -- error
+ERROR: role "nonexistent" does not exist
+SELECT p.proname, r.rolname
+ FROM pg_proc p JOIN pg_roles r ON (r.oid = p.proowner)
+ WHERE proname LIKE 'testagg_'
+ ORDER BY 1;
+ proname | rolname
+----------+------------------
+ testagg1 | regress_testrol2
+ testagg2 | current_user
+ testagg3 | regress_testrol2
+ testagg4 | regress_testrol1
+ testagg5 | regress_testrolx
+ testagg6 | Public
+ testagg7 | regress_testrol0
+ testagg8 | regress_testrol0
+ testagg9 | regress_testrol0
+ testagga | regress_testrol0
+(10 rows)
+
+-- CREATE USER MAPPING
+CREATE FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv1 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv2 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv3 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv4 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv5 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv6 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv7 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv8 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv9 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv10 FOREIGN DATA WRAPPER test_wrapper;
+CREATE USER MAPPING FOR CURRENT_USER SERVER sv1 OPTIONS (user 'CURRENT_USER');
+CREATE USER MAPPING FOR "current_user" SERVER sv2 OPTIONS (user '"current_user"');
+CREATE USER MAPPING FOR CURRENT_ROLE SERVER sv3 OPTIONS (user 'CURRENT_ROLE');
+CREATE USER MAPPING FOR USER SERVER sv4 OPTIONS (user 'USER');
+CREATE USER MAPPING FOR "user" SERVER sv5 OPTIONS (user '"USER"');
+CREATE USER MAPPING FOR SESSION_USER SERVER sv6 OPTIONS (user 'SESSION_USER');
+CREATE USER MAPPING FOR PUBLIC SERVER sv7 OPTIONS (user 'PUBLIC');
+CREATE USER MAPPING FOR "Public" SERVER sv8 OPTIONS (user '"Public"');
+CREATE USER MAPPING FOR regress_testrolx SERVER sv9 OPTIONS (user 'regress_testrolx');
+CREATE USER MAPPING FOR nonexistent SERVER sv10 OPTIONS (user 'nonexistent'); -- error;
+ERROR: role "nonexistent" does not exist
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+------------------+----------+---------------------------
+ regress_testrol2 | sv1 | {user=CURRENT_USER}
+ current_user | sv2 | {"user=\"current_user\""}
+ regress_testrol2 | sv3 | {user=CURRENT_ROLE}
+ regress_testrol2 | sv4 | {user=USER}
+ user | sv5 | {"user=\"USER\""}
+ regress_testrol1 | sv6 | {user=SESSION_USER}
+ | sv7 | {user=PUBLIC}
+ Public | sv8 | {"user=\"Public\""}
+ regress_testrolx | sv9 | {user=regress_testrolx}
+(9 rows)
+
+-- ALTER USER MAPPING
+ALTER USER MAPPING FOR CURRENT_USER SERVER sv1
+ OPTIONS (SET user 'CURRENT_USER_alt');
+ALTER USER MAPPING FOR "current_user" SERVER sv2
+ OPTIONS (SET user '"current_user"_alt');
+ALTER USER MAPPING FOR CURRENT_ROLE SERVER sv3
+ OPTIONS (SET user 'CURRENT_ROLE_alt');
+ALTER USER MAPPING FOR USER SERVER sv4
+ OPTIONS (SET user 'USER_alt');
+ALTER USER MAPPING FOR "user" SERVER sv5
+ OPTIONS (SET user '"user"_alt');
+ALTER USER MAPPING FOR SESSION_USER SERVER sv6
+ OPTIONS (SET user 'SESSION_USER_alt');
+ALTER USER MAPPING FOR PUBLIC SERVER sv7
+ OPTIONS (SET user 'public_alt');
+ALTER USER MAPPING FOR "Public" SERVER sv8
+ OPTIONS (SET user '"Public"_alt');
+ALTER USER MAPPING FOR regress_testrolx SERVER sv9
+ OPTIONS (SET user 'regress_testrolx_alt');
+ALTER USER MAPPING FOR nonexistent SERVER sv10
+ OPTIONS (SET user 'nonexistent_alt'); -- error
+ERROR: role "nonexistent" does not exist
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+------------------+----------+-------------------------------
+ regress_testrol2 | sv1 | {user=CURRENT_USER_alt}
+ current_user | sv2 | {"user=\"current_user\"_alt"}
+ regress_testrol2 | sv3 | {user=CURRENT_ROLE_alt}
+ regress_testrol2 | sv4 | {user=USER_alt}
+ user | sv5 | {"user=\"user\"_alt"}
+ regress_testrol1 | sv6 | {user=SESSION_USER_alt}
+ | sv7 | {user=public_alt}
+ Public | sv8 | {"user=\"Public\"_alt"}
+ regress_testrolx | sv9 | {user=regress_testrolx_alt}
+(9 rows)
+
+-- DROP USER MAPPING
+DROP USER MAPPING FOR CURRENT_USER SERVER sv1;
+DROP USER MAPPING FOR "current_user" SERVER sv2;
+DROP USER MAPPING FOR CURRENT_ROLE SERVER sv3;
+DROP USER MAPPING FOR USER SERVER sv4;
+DROP USER MAPPING FOR "user" SERVER sv5;
+DROP USER MAPPING FOR SESSION_USER SERVER sv6;
+DROP USER MAPPING FOR PUBLIC SERVER sv7;
+DROP USER MAPPING FOR "Public" SERVER sv8;
+DROP USER MAPPING FOR regress_testrolx SERVER sv9;
+DROP USER MAPPING FOR nonexistent SERVER sv10; -- error
+ERROR: role "nonexistent" does not exist
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+--------+----------+-----------
+(0 rows)
+
+CREATE USER MAPPING FOR CURRENT_USER SERVER sv1 OPTIONS (user 'CURRENT_USER');
+CREATE USER MAPPING FOR "current_user" SERVER sv2 OPTIONS (user '"current_user"');
+CREATE USER MAPPING FOR CURRENT_ROLE SERVER sv3 OPTIONS (user 'CURRENT_ROLE');
+CREATE USER MAPPING FOR USER SERVER sv4 OPTIONS (user 'USER');
+CREATE USER MAPPING FOR "user" SERVER sv5 OPTIONS (user '"USER"');
+CREATE USER MAPPING FOR SESSION_USER SERVER sv6 OPTIONS (user 'SESSION_USER');
+CREATE USER MAPPING FOR PUBLIC SERVER sv7 OPTIONS (user 'PUBLIC');
+CREATE USER MAPPING FOR "Public" SERVER sv8 OPTIONS (user '"Public"');
+CREATE USER MAPPING FOR regress_testrolx SERVER sv9 OPTIONS (user 'regress_testrolx');
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+------------------+----------+---------------------------
+ regress_testrol2 | sv1 | {user=CURRENT_USER}
+ current_user | sv2 | {"user=\"current_user\""}
+ regress_testrol2 | sv3 | {user=CURRENT_ROLE}
+ regress_testrol2 | sv4 | {user=USER}
+ user | sv5 | {"user=\"USER\""}
+ regress_testrol1 | sv6 | {user=SESSION_USER}
+ | sv7 | {user=PUBLIC}
+ Public | sv8 | {"user=\"Public\""}
+ regress_testrolx | sv9 | {user=regress_testrolx}
+(9 rows)
+
+-- DROP USER MAPPING IF EXISTS
+DROP USER MAPPING IF EXISTS FOR CURRENT_USER SERVER sv1;
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+------------------+----------+---------------------------
+ current_user | sv2 | {"user=\"current_user\""}
+ regress_testrol2 | sv3 | {user=CURRENT_ROLE}
+ regress_testrol2 | sv4 | {user=USER}
+ user | sv5 | {"user=\"USER\""}
+ regress_testrol1 | sv6 | {user=SESSION_USER}
+ | sv7 | {user=PUBLIC}
+ Public | sv8 | {"user=\"Public\""}
+ regress_testrolx | sv9 | {user=regress_testrolx}
+(8 rows)
+
+DROP USER MAPPING IF EXISTS FOR "current_user" SERVER sv2;
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+------------------+----------+-------------------------
+ regress_testrol2 | sv3 | {user=CURRENT_ROLE}
+ regress_testrol2 | sv4 | {user=USER}
+ user | sv5 | {"user=\"USER\""}
+ regress_testrol1 | sv6 | {user=SESSION_USER}
+ | sv7 | {user=PUBLIC}
+ Public | sv8 | {"user=\"Public\""}
+ regress_testrolx | sv9 | {user=regress_testrolx}
+(7 rows)
+
+DROP USER MAPPING IF EXISTS FOR CURRENT_USER SERVER sv3;
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+------------------+----------+-------------------------
+ regress_testrol2 | sv4 | {user=USER}
+ user | sv5 | {"user=\"USER\""}
+ regress_testrol1 | sv6 | {user=SESSION_USER}
+ | sv7 | {user=PUBLIC}
+ Public | sv8 | {"user=\"Public\""}
+ regress_testrolx | sv9 | {user=regress_testrolx}
+(6 rows)
+
+DROP USER MAPPING IF EXISTS FOR USER SERVER sv4;
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+------------------+----------+-------------------------
+ user | sv5 | {"user=\"USER\""}
+ regress_testrol1 | sv6 | {user=SESSION_USER}
+ | sv7 | {user=PUBLIC}
+ Public | sv8 | {"user=\"Public\""}
+ regress_testrolx | sv9 | {user=regress_testrolx}
+(5 rows)
+
+DROP USER MAPPING IF EXISTS FOR "user" SERVER sv5;
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+------------------+----------+-------------------------
+ regress_testrol1 | sv6 | {user=SESSION_USER}
+ | sv7 | {user=PUBLIC}
+ Public | sv8 | {"user=\"Public\""}
+ regress_testrolx | sv9 | {user=regress_testrolx}
+(4 rows)
+
+DROP USER MAPPING IF EXISTS FOR SESSION_USER SERVER sv6;
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+------------------+----------+-------------------------
+ | sv7 | {user=PUBLIC}
+ Public | sv8 | {"user=\"Public\""}
+ regress_testrolx | sv9 | {user=regress_testrolx}
+(3 rows)
+
+DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER sv7;
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+------------------+----------+-------------------------
+ Public | sv8 | {"user=\"Public\""}
+ regress_testrolx | sv9 | {user=regress_testrolx}
+(2 rows)
+
+DROP USER MAPPING IF EXISTS FOR "Public" SERVER sv8;
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+------------------+----------+-------------------------
+ regress_testrolx | sv9 | {user=regress_testrolx}
+(1 row)
+
+DROP USER MAPPING IF EXISTS FOR regress_testrolx SERVER sv9;
+SELECT * FROM chkumapping();
+ umname | umserver | umoptions
+--------+----------+-----------
+(0 rows)
+
+DROP USER MAPPING IF EXISTS FOR nonexistent SERVER sv10; -- error
+NOTICE: role "nonexistent" does not exist, skipping
+-- GRANT/REVOKE
+GRANT regress_testrol0 TO pg_signal_backend; -- success
+SET ROLE pg_signal_backend; --success
+RESET ROLE;
+CREATE SCHEMA test_roles_schema AUTHORIZATION pg_signal_backend; --success
+SET ROLE regress_testrol2;
+UPDATE pg_proc SET proacl = null WHERE proname LIKE 'testagg_';
+SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_';
+ proname | proacl
+----------+--------
+ testagg1 |
+ testagg2 |
+ testagg3 |
+ testagg4 |
+ testagg5 |
+ testagg6 |
+ testagg7 |
+ testagg8 |
+ testagg9 |
+ testagga |
+(10 rows)
+
+REVOKE ALL PRIVILEGES ON FUNCTION testagg1(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg2(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg3(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg4(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg5(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg6(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg7(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg8(int2) FROM PUBLIC;
+GRANT ALL PRIVILEGES ON FUNCTION testagg1(int2) TO PUBLIC;
+GRANT ALL PRIVILEGES ON FUNCTION testagg2(int2) TO CURRENT_USER;
+GRANT ALL PRIVILEGES ON FUNCTION testagg3(int2) TO "current_user";
+GRANT ALL PRIVILEGES ON FUNCTION testagg4(int2) TO CURRENT_ROLE;
+GRANT ALL PRIVILEGES ON FUNCTION testagg5(int2) TO SESSION_USER;
+GRANT ALL PRIVILEGES ON FUNCTION testagg6(int2) TO "Public";
+GRANT ALL PRIVILEGES ON FUNCTION testagg7(int2) TO regress_testrolx;
+GRANT ALL PRIVILEGES ON FUNCTION testagg8(int2) TO "public";
+GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2)
+ TO current_user, public, regress_testrolx;
+SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_';
+ proname | proacl
+----------+-----------------------------------------------------------------------------------------------------------------------------------
+ testagg1 | {regress_testrol2=X/regress_testrol2,=X/regress_testrol2}
+ testagg2 | {current_user=X/current_user,regress_testrol2=X/current_user}
+ testagg3 | {regress_testrol2=X/regress_testrol2,current_user=X/regress_testrol2}
+ testagg4 | {regress_testrol1=X/regress_testrol1,regress_testrol2=X/regress_testrol1}
+ testagg5 | {regress_testrolx=X/regress_testrolx,regress_testrol1=X/regress_testrolx}
+ testagg6 | {Public=X/Public}
+ testagg7 | {regress_testrol0=X/regress_testrol0,regress_testrolx=X/regress_testrol0}
+ testagg8 | {regress_testrol0=X/regress_testrol0,=X/regress_testrol0}
+ testagg9 | {=X/regress_testrol0,regress_testrol0=X/regress_testrol0,regress_testrol2=X/regress_testrol0,regress_testrolx=X/regress_testrol0}
+ testagga |
+(10 rows)
+
+GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO USER; --error
+ERROR: syntax error at or near "USER"
+LINE 1: GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO USER;
+ ^
+GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO NONE; --error
+ERROR: role name "none" is reserved
+LINE 1: GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO NONE;
+ ^
+GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO "none"; --error
+ERROR: role name "none" is reserved
+LINE 1: GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO "none";
+ ^
+SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_';
+ proname | proacl
+----------+-----------------------------------------------------------------------------------------------------------------------------------
+ testagg1 | {regress_testrol2=X/regress_testrol2,=X/regress_testrol2}
+ testagg2 | {current_user=X/current_user,regress_testrol2=X/current_user}
+ testagg3 | {regress_testrol2=X/regress_testrol2,current_user=X/regress_testrol2}
+ testagg4 | {regress_testrol1=X/regress_testrol1,regress_testrol2=X/regress_testrol1}
+ testagg5 | {regress_testrolx=X/regress_testrolx,regress_testrol1=X/regress_testrolx}
+ testagg6 | {Public=X/Public}
+ testagg7 | {regress_testrol0=X/regress_testrol0,regress_testrolx=X/regress_testrol0}
+ testagg8 | {regress_testrol0=X/regress_testrol0,=X/regress_testrol0}
+ testagg9 | {=X/regress_testrol0,regress_testrol0=X/regress_testrol0,regress_testrol2=X/regress_testrol0,regress_testrolx=X/regress_testrol0}
+ testagga |
+(10 rows)
+
+REVOKE ALL PRIVILEGES ON FUNCTION testagg1(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg2(int2) FROM CURRENT_USER;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg3(int2) FROM "current_user";
+REVOKE ALL PRIVILEGES ON FUNCTION testagg4(int2) FROM CURRENT_ROLE;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg5(int2) FROM SESSION_USER;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg6(int2) FROM "Public";
+REVOKE ALL PRIVILEGES ON FUNCTION testagg7(int2) FROM regress_testrolx;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg8(int2) FROM "public";
+REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2)
+ FROM current_user, public, regress_testrolx;
+SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_';
+ proname | proacl
+----------+---------------------------------------
+ testagg1 | {regress_testrol2=X/regress_testrol2}
+ testagg2 | {current_user=X/current_user}
+ testagg3 | {regress_testrol2=X/regress_testrol2}
+ testagg4 | {regress_testrol1=X/regress_testrol1}
+ testagg5 | {regress_testrolx=X/regress_testrolx}
+ testagg6 | {}
+ testagg7 | {regress_testrol0=X/regress_testrol0}
+ testagg8 | {regress_testrol0=X/regress_testrol0}
+ testagg9 | {regress_testrol0=X/regress_testrol0}
+ testagga |
+(10 rows)
+
+REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM USER; --error
+ERROR: syntax error at or near "USER"
+LINE 1: REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM USER;
+ ^
+REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM NONE; --error
+ERROR: role name "none" is reserved
+LINE 1: REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM NONE;
+ ^
+REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM "none"; --error
+ERROR: role name "none" is reserved
+LINE 1: ...EVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM "none";
+ ^
+SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_';
+ proname | proacl
+----------+---------------------------------------
+ testagg1 | {regress_testrol2=X/regress_testrol2}
+ testagg2 | {current_user=X/current_user}
+ testagg3 | {regress_testrol2=X/regress_testrol2}
+ testagg4 | {regress_testrol1=X/regress_testrol1}
+ testagg5 | {regress_testrolx=X/regress_testrolx}
+ testagg6 | {}
+ testagg7 | {regress_testrol0=X/regress_testrol0}
+ testagg8 | {regress_testrol0=X/regress_testrol0}
+ testagg9 | {regress_testrol0=X/regress_testrol0}
+ testagga |
+(10 rows)
+
+-- DEFAULT MONITORING ROLES
+CREATE ROLE regress_role_haspriv;
+CREATE ROLE regress_role_nopriv;
+-- pg_read_all_stats
+GRANT pg_read_all_stats TO regress_role_haspriv;
+SET SESSION AUTHORIZATION regress_role_haspriv;
+-- returns true with role member of pg_read_all_stats
+SELECT COUNT(*) = 0 AS haspriv FROM pg_stat_activity
+ WHERE query = '<insufficient privilege>';
+ haspriv
+---------
+ t
+(1 row)
+
+SET SESSION AUTHORIZATION regress_role_nopriv;
+-- returns false with role not member of pg_read_all_stats
+SELECT COUNT(*) = 0 AS haspriv FROM pg_stat_activity
+ WHERE query = '<insufficient privilege>';
+ haspriv
+---------
+ f
+(1 row)
+
+RESET SESSION AUTHORIZATION;
+REVOKE pg_read_all_stats FROM regress_role_haspriv;
+-- pg_read_all_settings
+GRANT pg_read_all_settings TO regress_role_haspriv;
+BEGIN;
+-- A GUC using GUC_SUPERUSER_ONLY is useful for negative tests.
+SET LOCAL session_preload_libraries TO 'path-to-preload-libraries';
+SET SESSION AUTHORIZATION regress_role_haspriv;
+-- passes with role member of pg_read_all_settings
+SHOW session_preload_libraries;
+ session_preload_libraries
+-----------------------------
+ "path-to-preload-libraries"
+(1 row)
+
+SET SESSION AUTHORIZATION regress_role_nopriv;
+-- fails with role not member of pg_read_all_settings
+SHOW session_preload_libraries;
+ERROR: must be superuser or a member of pg_read_all_settings to examine "session_preload_libraries"
+RESET SESSION AUTHORIZATION;
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+ROLLBACK;
+REVOKE pg_read_all_settings FROM regress_role_haspriv;
+-- clean up
+\c
+DROP SCHEMA test_roles_schema;
+DROP OWNED BY regress_testrol0, "Public", "current_role", "current_user", regress_testrol1, regress_testrol2, regress_testrolx CASCADE;
+DROP ROLE regress_testrol0, regress_testrol1, regress_testrol2, regress_testrolx;
+DROP ROLE "Public", "None", "current_role", "current_user", "session_user", "user";
+DROP ROLE regress_role_haspriv, regress_role_nopriv;
diff --git a/src/test/modules/unsafe_tests/sql/alter_system_table.sql b/src/test/modules/unsafe_tests/sql/alter_system_table.sql
new file mode 100644
index 0000000..6bfc896
--- /dev/null
+++ b/src/test/modules/unsafe_tests/sql/alter_system_table.sql
@@ -0,0 +1,194 @@
+--
+-- Tests for things affected by allow_system_table_mods
+--
+-- We run the same set of commands once with allow_system_table_mods
+-- off and then again with on.
+--
+-- The "on" tests should where possible be wrapped in BEGIN/ROLLBACK
+-- blocks so as to not leave a mess around.
+
+CREATE USER regress_user_ast;
+
+SET allow_system_table_mods = off;
+
+-- create new table in pg_catalog
+CREATE TABLE pg_catalog.test (a int);
+
+-- anyarray column
+CREATE TABLE t1x (a int, b anyarray);
+
+-- index on system catalog
+ALTER TABLE pg_namespace ADD CONSTRAINT foo UNIQUE USING INDEX pg_namespace_nspname_index;
+
+-- write to system catalog table as superuser
+-- (allowed even without allow_system_table_mods)
+INSERT INTO pg_description (objoid, classoid, objsubid, description) VALUES (0, 0, 0, 'foo');
+
+-- write to system catalog table as normal user
+GRANT INSERT ON pg_description TO regress_user_ast;
+SET ROLE regress_user_ast;
+INSERT INTO pg_description (objoid, classoid, objsubid, description) VALUES (0, 0, 1, 'foo');
+RESET ROLE;
+
+-- policy on system catalog
+CREATE POLICY foo ON pg_description FOR SELECT USING (description NOT LIKE 'secret%');
+
+-- reserved schema name
+CREATE SCHEMA pg_foo;
+
+-- drop system table
+DROP TABLE pg_description;
+
+-- truncate of system table
+TRUNCATE pg_description;
+
+-- rename column of system table
+ALTER TABLE pg_description RENAME COLUMN description TO comment;
+
+-- ATSimplePermissions()
+ALTER TABLE pg_description ALTER COLUMN description SET NOT NULL;
+
+-- SET STATISTICS
+ALTER TABLE pg_description ALTER COLUMN description SET STATISTICS -1;
+
+-- foreign key referencing catalog
+CREATE TABLE foo (a oid, b oid, c int, FOREIGN KEY (a, b, c) REFERENCES pg_description);
+
+-- RangeVarCallbackOwnsRelation()
+CREATE INDEX pg_descripton_test_index ON pg_description (description);
+
+-- RangeVarCallbackForAlterRelation()
+ALTER TABLE pg_description RENAME TO pg_comment;
+ALTER TABLE pg_description SET SCHEMA public;
+
+-- reserved tablespace name
+CREATE TABLESPACE pg_foo LOCATION '/no/such/location';
+
+-- triggers
+CREATE FUNCTION tf1() RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RETURN NULL;
+END $$;
+
+CREATE TRIGGER t1 BEFORE INSERT ON pg_description EXECUTE FUNCTION tf1();
+ALTER TRIGGER t1 ON pg_description RENAME TO t2;
+--DROP TRIGGER t2 ON pg_description;
+
+-- rules
+CREATE RULE r1 AS ON INSERT TO pg_description DO INSTEAD NOTHING;
+ALTER RULE r1 ON pg_description RENAME TO r2;
+-- now make one to test dropping:
+SET allow_system_table_mods TO on;
+CREATE RULE r2 AS ON INSERT TO pg_description DO INSTEAD NOTHING;
+RESET allow_system_table_mods;
+DROP RULE r2 ON pg_description;
+-- cleanup:
+SET allow_system_table_mods TO on;
+DROP RULE r2 ON pg_description;
+RESET allow_system_table_mods;
+
+
+SET allow_system_table_mods = on;
+
+-- create new table in pg_catalog
+BEGIN;
+CREATE TABLE pg_catalog.test (a int);
+ROLLBACK;
+
+-- anyarray column
+BEGIN;
+CREATE TABLE t1 (a int, b anyarray);
+ROLLBACK;
+
+-- index on system catalog
+BEGIN;
+ALTER TABLE pg_namespace ADD CONSTRAINT foo UNIQUE USING INDEX pg_namespace_nspname_index;
+ROLLBACK;
+
+-- write to system catalog table as superuser
+BEGIN;
+INSERT INTO pg_description (objoid, classoid, objsubid, description) VALUES (0, 0, 2, 'foo');
+ROLLBACK;
+
+-- write to system catalog table as normal user
+-- (not allowed)
+SET ROLE regress_user_ast;
+INSERT INTO pg_description (objoid, classoid, objsubid, description) VALUES (0, 0, 3, 'foo');
+RESET ROLE;
+
+-- policy on system catalog
+BEGIN;
+CREATE POLICY foo ON pg_description FOR SELECT USING (description NOT LIKE 'secret%');
+ROLLBACK;
+
+-- reserved schema name
+BEGIN;
+CREATE SCHEMA pg_foo;
+ROLLBACK;
+
+-- drop system table
+-- (This will fail anyway because it's pinned.)
+BEGIN;
+DROP TABLE pg_description;
+ROLLBACK;
+
+-- truncate of system table
+BEGIN;
+TRUNCATE pg_description;
+ROLLBACK;
+
+-- rename column of system table
+BEGIN;
+ALTER TABLE pg_description RENAME COLUMN description TO comment;
+ROLLBACK;
+
+-- ATSimplePermissions()
+BEGIN;
+ALTER TABLE pg_description ALTER COLUMN description SET NOT NULL;
+ROLLBACK;
+
+-- SET STATISTICS
+BEGIN;
+ALTER TABLE pg_description ALTER COLUMN description SET STATISTICS -1;
+ROLLBACK;
+
+-- foreign key referencing catalog
+BEGIN;
+CREATE TABLE foo (a oid, b oid, c int, FOREIGN KEY (a, b, c) REFERENCES pg_description);
+ROLLBACK;
+
+-- RangeVarCallbackOwnsRelation()
+BEGIN;
+CREATE INDEX pg_descripton_test_index ON pg_description (description);
+ROLLBACK;
+
+-- RangeVarCallbackForAlterRelation()
+BEGIN;
+ALTER TABLE pg_description RENAME TO pg_comment;
+ROLLBACK;
+BEGIN;
+ALTER TABLE pg_description SET SCHEMA public;
+ROLLBACK;
+
+-- reserved tablespace name
+SET client_min_messages = error; -- disable ENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS warning
+CREATE TABLESPACE pg_foo LOCATION '/no/such/location';
+RESET client_min_messages;
+
+-- triggers
+CREATE TRIGGER t1 BEFORE INSERT ON pg_description EXECUTE FUNCTION tf1();
+ALTER TRIGGER t1 ON pg_description RENAME TO t2;
+DROP TRIGGER t2 ON pg_description;
+
+-- rules
+CREATE RULE r1 AS ON INSERT TO pg_description DO INSTEAD NOTHING;
+ALTER RULE r1 ON pg_description RENAME TO r2;
+DROP RULE r2 ON pg_description;
+
+
+-- cleanup
+REVOKE ALL ON pg_description FROM regress_user_ast;
+DROP USER regress_user_ast;
+DROP FUNCTION tf1;
diff --git a/src/test/modules/unsafe_tests/sql/rolenames.sql b/src/test/modules/unsafe_tests/sql/rolenames.sql
new file mode 100644
index 0000000..adac365
--- /dev/null
+++ b/src/test/modules/unsafe_tests/sql/rolenames.sql
@@ -0,0 +1,504 @@
+CREATE FUNCTION chkrolattr()
+ RETURNS TABLE ("role" name, rolekeyword text, canlogin bool, replication bool)
+ AS $$
+SELECT r.rolname, v.keyword, r.rolcanlogin, r.rolreplication
+ FROM pg_roles r
+ JOIN (VALUES(CURRENT_ROLE, 'current_role'),
+ (CURRENT_USER, 'current_user'),
+ (SESSION_USER, 'session_user'),
+ ('current_role', '-'),
+ ('current_user', '-'),
+ ('session_user', '-'),
+ ('Public', '-'),
+ ('None', '-'))
+ AS v(uname, keyword)
+ ON (r.rolname = v.uname)
+ ORDER BY 1, 2;
+$$ LANGUAGE SQL;
+
+CREATE FUNCTION chksetconfig()
+ RETURNS TABLE (db name, "role" name, rolkeyword text, setconfig text[])
+ AS $$
+SELECT COALESCE(d.datname, 'ALL'), COALESCE(r.rolname, 'ALL'),
+ COALESCE(v.keyword, '-'), s.setconfig
+ FROM pg_db_role_setting s
+ LEFT JOIN pg_roles r ON (r.oid = s.setrole)
+ LEFT JOIN pg_database d ON (d.oid = s.setdatabase)
+ LEFT JOIN (VALUES(CURRENT_ROLE, 'current_role'),
+ (CURRENT_USER, 'current_user'),
+ (SESSION_USER, 'session_user'))
+ AS v(uname, keyword)
+ ON (r.rolname = v.uname)
+ WHERE (r.rolname) IN ('Public', 'current_user', 'regress_testrol1', 'regress_testrol2')
+ORDER BY 1, 2, 3;
+$$ LANGUAGE SQL;
+
+CREATE FUNCTION chkumapping()
+ RETURNS TABLE (umname name, umserver name, umoptions text[])
+ AS $$
+SELECT r.rolname, s.srvname, m.umoptions
+ FROM pg_user_mapping m
+ LEFT JOIN pg_roles r ON (r.oid = m.umuser)
+ JOIN pg_foreign_server s ON (s.oid = m.umserver)
+ ORDER BY 2, 1;
+$$ LANGUAGE SQL;
+
+--
+-- We test creation and use of these role names to ensure that the server
+-- correctly distinguishes role keywords from quoted names that look like
+-- those keywords. In a test environment, creation of these roles may
+-- provoke warnings, so hide the warnings by raising client_min_messages.
+--
+SET client_min_messages = ERROR;
+
+CREATE ROLE "Public";
+CREATE ROLE "None";
+CREATE ROLE "current_role";
+CREATE ROLE "current_user";
+CREATE ROLE "session_user";
+CREATE ROLE "user";
+
+RESET client_min_messages;
+
+CREATE ROLE current_user; -- error
+CREATE ROLE current_role; -- error
+CREATE ROLE session_user; -- error
+CREATE ROLE user; -- error
+CREATE ROLE all; -- error
+
+CREATE ROLE public; -- error
+CREATE ROLE "public"; -- error
+CREATE ROLE none; -- error
+CREATE ROLE "none"; -- error
+
+CREATE ROLE pg_abc; -- error
+CREATE ROLE "pg_abc"; -- error
+CREATE ROLE pg_abcdef; -- error
+CREATE ROLE "pg_abcdef"; -- error
+
+CREATE ROLE regress_testrol0 SUPERUSER LOGIN;
+CREATE ROLE regress_testrolx SUPERUSER LOGIN;
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+
+-- ALTER ROLE
+BEGIN;
+SELECT * FROM chkrolattr();
+ALTER ROLE CURRENT_ROLE WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER ROLE "current_role" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER ROLE CURRENT_ROLE WITH NOREPLICATION;
+ALTER ROLE CURRENT_USER WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER ROLE "current_user" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER ROLE SESSION_USER WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER ROLE "session_user" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER USER "Public" WITH REPLICATION;
+ALTER USER "None" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER USER regress_testrol1 WITH NOREPLICATION;
+ALTER USER regress_testrol2 WITH NOREPLICATION;
+SELECT * FROM chkrolattr();
+ROLLBACK;
+
+ALTER ROLE USER WITH LOGIN; -- error
+ALTER ROLE ALL WITH REPLICATION; -- error
+ALTER ROLE SESSION_ROLE WITH NOREPLICATION; -- error
+ALTER ROLE PUBLIC WITH NOREPLICATION; -- error
+ALTER ROLE "public" WITH NOREPLICATION; -- error
+ALTER ROLE NONE WITH NOREPLICATION; -- error
+ALTER ROLE "none" WITH NOREPLICATION; -- error
+ALTER ROLE nonexistent WITH NOREPLICATION; -- error
+
+-- ALTER USER
+BEGIN;
+SELECT * FROM chkrolattr();
+ALTER USER CURRENT_ROLE WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER USER "current_role" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER USER CURRENT_ROLE WITH NOREPLICATION;
+ALTER USER CURRENT_USER WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER USER "current_user" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER USER SESSION_USER WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER USER "session_user" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER USER "Public" WITH REPLICATION;
+ALTER USER "None" WITH REPLICATION;
+SELECT * FROM chkrolattr();
+ALTER USER regress_testrol1 WITH NOREPLICATION;
+ALTER USER regress_testrol2 WITH NOREPLICATION;
+SELECT * FROM chkrolattr();
+ROLLBACK;
+
+ALTER USER USER WITH LOGIN; -- error
+ALTER USER ALL WITH REPLICATION; -- error
+ALTER USER SESSION_ROLE WITH NOREPLICATION; -- error
+ALTER USER PUBLIC WITH NOREPLICATION; -- error
+ALTER USER "public" WITH NOREPLICATION; -- error
+ALTER USER NONE WITH NOREPLICATION; -- error
+ALTER USER "none" WITH NOREPLICATION; -- error
+ALTER USER nonexistent WITH NOREPLICATION; -- error
+
+-- ALTER ROLE SET/RESET
+SELECT * FROM chksetconfig();
+ALTER ROLE CURRENT_ROLE SET application_name to 'BAZ';
+ALTER ROLE CURRENT_USER SET application_name to 'FOO';
+ALTER ROLE SESSION_USER SET application_name to 'BAR';
+ALTER ROLE "current_user" SET application_name to 'FOOFOO';
+ALTER ROLE "Public" SET application_name to 'BARBAR';
+ALTER ROLE ALL SET application_name to 'SLAP';
+SELECT * FROM chksetconfig();
+ALTER ROLE regress_testrol1 SET application_name to 'SLAM';
+SELECT * FROM chksetconfig();
+ALTER ROLE CURRENT_ROLE RESET application_name;
+ALTER ROLE CURRENT_USER RESET application_name;
+ALTER ROLE SESSION_USER RESET application_name;
+ALTER ROLE "current_user" RESET application_name;
+ALTER ROLE "Public" RESET application_name;
+ALTER ROLE ALL RESET application_name;
+SELECT * FROM chksetconfig();
+
+
+ALTER ROLE USER SET application_name to 'BOOM'; -- error
+ALTER ROLE PUBLIC SET application_name to 'BOMB'; -- error
+ALTER ROLE nonexistent SET application_name to 'BOMB'; -- error
+
+-- ALTER USER SET/RESET
+SELECT * FROM chksetconfig();
+ALTER USER CURRENT_ROLE SET application_name to 'BAZ';
+ALTER USER CURRENT_USER SET application_name to 'FOO';
+ALTER USER SESSION_USER SET application_name to 'BAR';
+ALTER USER "current_user" SET application_name to 'FOOFOO';
+ALTER USER "Public" SET application_name to 'BARBAR';
+ALTER USER ALL SET application_name to 'SLAP';
+SELECT * FROM chksetconfig();
+ALTER USER regress_testrol1 SET application_name to 'SLAM';
+SELECT * FROM chksetconfig();
+ALTER USER CURRENT_ROLE RESET application_name;
+ALTER USER CURRENT_USER RESET application_name;
+ALTER USER SESSION_USER RESET application_name;
+ALTER USER "current_user" RESET application_name;
+ALTER USER "Public" RESET application_name;
+ALTER USER ALL RESET application_name;
+SELECT * FROM chksetconfig();
+
+
+ALTER USER USER SET application_name to 'BOOM'; -- error
+ALTER USER PUBLIC SET application_name to 'BOMB'; -- error
+ALTER USER NONE SET application_name to 'BOMB'; -- error
+ALTER USER nonexistent SET application_name to 'BOMB'; -- error
+
+-- CREATE SCHEMA
+CREATE SCHEMA newschema1 AUTHORIZATION CURRENT_USER;
+CREATE SCHEMA newschema2 AUTHORIZATION "current_user";
+CREATE SCHEMA newschema3 AUTHORIZATION CURRENT_ROLE;
+CREATE SCHEMA newschema4 AUTHORIZATION SESSION_USER;
+CREATE SCHEMA newschema5 AUTHORIZATION regress_testrolx;
+CREATE SCHEMA newschema6 AUTHORIZATION "Public";
+
+CREATE SCHEMA newschemax AUTHORIZATION USER; -- error
+CREATE SCHEMA newschemax AUTHORIZATION PUBLIC; -- error
+CREATE SCHEMA newschemax AUTHORIZATION "public"; -- error
+CREATE SCHEMA newschemax AUTHORIZATION NONE; -- error
+CREATE SCHEMA newschemax AUTHORIZATION nonexistent; -- error
+
+SELECT n.nspname, r.rolname FROM pg_namespace n
+ JOIN pg_roles r ON (r.oid = n.nspowner)
+ WHERE n.nspname LIKE 'newschema_' ORDER BY 1;
+
+CREATE SCHEMA IF NOT EXISTS newschema1 AUTHORIZATION CURRENT_USER;
+CREATE SCHEMA IF NOT EXISTS newschema2 AUTHORIZATION "current_user";
+CREATE SCHEMA IF NOT EXISTS newschema3 AUTHORIZATION CURRENT_ROLE;
+CREATE SCHEMA IF NOT EXISTS newschema4 AUTHORIZATION SESSION_USER;
+CREATE SCHEMA IF NOT EXISTS newschema5 AUTHORIZATION regress_testrolx;
+CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION "Public";
+
+CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION USER; -- error
+CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION PUBLIC; -- error
+CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION "public"; -- error
+CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION NONE; -- error
+CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION nonexistent; -- error
+
+SELECT n.nspname, r.rolname FROM pg_namespace n
+ JOIN pg_roles r ON (r.oid = n.nspowner)
+ WHERE n.nspname LIKE 'newschema_' ORDER BY 1;
+
+-- ALTER TABLE OWNER TO
+\c -
+SET SESSION AUTHORIZATION regress_testrol0;
+CREATE TABLE testtab1 (a int);
+CREATE TABLE testtab2 (a int);
+CREATE TABLE testtab3 (a int);
+CREATE TABLE testtab4 (a int);
+CREATE TABLE testtab5 (a int);
+CREATE TABLE testtab6 (a int);
+CREATE TABLE testtab7 (a int);
+
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+
+ALTER TABLE testtab1 OWNER TO CURRENT_USER;
+ALTER TABLE testtab2 OWNER TO "current_user";
+ALTER TABLE testtab3 OWNER TO CURRENT_ROLE;
+ALTER TABLE testtab4 OWNER TO SESSION_USER;
+ALTER TABLE testtab5 OWNER TO regress_testrolx;
+ALTER TABLE testtab6 OWNER TO "Public";
+
+ALTER TABLE testtab7 OWNER TO USER; --error
+ALTER TABLE testtab7 OWNER TO PUBLIC; -- error
+ALTER TABLE testtab7 OWNER TO "public"; -- error
+ALTER TABLE testtab7 OWNER TO nonexistent; -- error
+
+SELECT c.relname, r.rolname
+ FROM pg_class c JOIN pg_roles r ON (r.oid = c.relowner)
+ WHERE relname LIKE 'testtab_'
+ ORDER BY 1;
+
+-- ALTER TABLE, VIEW, MATERIALIZED VIEW, FOREIGN TABLE, SEQUENCE are
+-- changed their owner in the same way.
+
+-- ALTER AGGREGATE
+\c -
+SET SESSION AUTHORIZATION regress_testrol0;
+CREATE AGGREGATE testagg1(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg2(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg3(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg4(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg5(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg6(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg7(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg8(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagg9(int2) (SFUNC = int2_sum, STYPE = int8);
+CREATE AGGREGATE testagga(int2) (SFUNC = int2_sum, STYPE = int8);
+
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+
+ALTER AGGREGATE testagg1(int2) OWNER TO CURRENT_USER;
+ALTER AGGREGATE testagg2(int2) OWNER TO "current_user";
+ALTER AGGREGATE testagg3(int2) OWNER TO CURRENT_ROLE;
+ALTER AGGREGATE testagg4(int2) OWNER TO SESSION_USER;
+ALTER AGGREGATE testagg5(int2) OWNER TO regress_testrolx;
+ALTER AGGREGATE testagg6(int2) OWNER TO "Public";
+
+ALTER AGGREGATE testagg6(int2) OWNER TO USER; -- error
+ALTER AGGREGATE testagg6(int2) OWNER TO PUBLIC; -- error
+ALTER AGGREGATE testagg6(int2) OWNER TO "public"; -- error
+ALTER AGGREGATE testagg6(int2) OWNER TO nonexistent; -- error
+
+SELECT p.proname, r.rolname
+ FROM pg_proc p JOIN pg_roles r ON (r.oid = p.proowner)
+ WHERE proname LIKE 'testagg_'
+ ORDER BY 1;
+
+-- CREATE USER MAPPING
+CREATE FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv1 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv2 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv3 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv4 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv5 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv6 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv7 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv8 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv9 FOREIGN DATA WRAPPER test_wrapper;
+CREATE SERVER sv10 FOREIGN DATA WRAPPER test_wrapper;
+
+CREATE USER MAPPING FOR CURRENT_USER SERVER sv1 OPTIONS (user 'CURRENT_USER');
+CREATE USER MAPPING FOR "current_user" SERVER sv2 OPTIONS (user '"current_user"');
+CREATE USER MAPPING FOR CURRENT_ROLE SERVER sv3 OPTIONS (user 'CURRENT_ROLE');
+CREATE USER MAPPING FOR USER SERVER sv4 OPTIONS (user 'USER');
+CREATE USER MAPPING FOR "user" SERVER sv5 OPTIONS (user '"USER"');
+CREATE USER MAPPING FOR SESSION_USER SERVER sv6 OPTIONS (user 'SESSION_USER');
+CREATE USER MAPPING FOR PUBLIC SERVER sv7 OPTIONS (user 'PUBLIC');
+CREATE USER MAPPING FOR "Public" SERVER sv8 OPTIONS (user '"Public"');
+CREATE USER MAPPING FOR regress_testrolx SERVER sv9 OPTIONS (user 'regress_testrolx');
+
+CREATE USER MAPPING FOR nonexistent SERVER sv10 OPTIONS (user 'nonexistent'); -- error;
+
+SELECT * FROM chkumapping();
+
+-- ALTER USER MAPPING
+ALTER USER MAPPING FOR CURRENT_USER SERVER sv1
+ OPTIONS (SET user 'CURRENT_USER_alt');
+ALTER USER MAPPING FOR "current_user" SERVER sv2
+ OPTIONS (SET user '"current_user"_alt');
+ALTER USER MAPPING FOR CURRENT_ROLE SERVER sv3
+ OPTIONS (SET user 'CURRENT_ROLE_alt');
+ALTER USER MAPPING FOR USER SERVER sv4
+ OPTIONS (SET user 'USER_alt');
+ALTER USER MAPPING FOR "user" SERVER sv5
+ OPTIONS (SET user '"user"_alt');
+ALTER USER MAPPING FOR SESSION_USER SERVER sv6
+ OPTIONS (SET user 'SESSION_USER_alt');
+ALTER USER MAPPING FOR PUBLIC SERVER sv7
+ OPTIONS (SET user 'public_alt');
+ALTER USER MAPPING FOR "Public" SERVER sv8
+ OPTIONS (SET user '"Public"_alt');
+ALTER USER MAPPING FOR regress_testrolx SERVER sv9
+ OPTIONS (SET user 'regress_testrolx_alt');
+
+ALTER USER MAPPING FOR nonexistent SERVER sv10
+ OPTIONS (SET user 'nonexistent_alt'); -- error
+
+SELECT * FROM chkumapping();
+
+-- DROP USER MAPPING
+DROP USER MAPPING FOR CURRENT_USER SERVER sv1;
+DROP USER MAPPING FOR "current_user" SERVER sv2;
+DROP USER MAPPING FOR CURRENT_ROLE SERVER sv3;
+DROP USER MAPPING FOR USER SERVER sv4;
+DROP USER MAPPING FOR "user" SERVER sv5;
+DROP USER MAPPING FOR SESSION_USER SERVER sv6;
+DROP USER MAPPING FOR PUBLIC SERVER sv7;
+DROP USER MAPPING FOR "Public" SERVER sv8;
+DROP USER MAPPING FOR regress_testrolx SERVER sv9;
+
+DROP USER MAPPING FOR nonexistent SERVER sv10; -- error
+SELECT * FROM chkumapping();
+
+CREATE USER MAPPING FOR CURRENT_USER SERVER sv1 OPTIONS (user 'CURRENT_USER');
+CREATE USER MAPPING FOR "current_user" SERVER sv2 OPTIONS (user '"current_user"');
+CREATE USER MAPPING FOR CURRENT_ROLE SERVER sv3 OPTIONS (user 'CURRENT_ROLE');
+CREATE USER MAPPING FOR USER SERVER sv4 OPTIONS (user 'USER');
+CREATE USER MAPPING FOR "user" SERVER sv5 OPTIONS (user '"USER"');
+CREATE USER MAPPING FOR SESSION_USER SERVER sv6 OPTIONS (user 'SESSION_USER');
+CREATE USER MAPPING FOR PUBLIC SERVER sv7 OPTIONS (user 'PUBLIC');
+CREATE USER MAPPING FOR "Public" SERVER sv8 OPTIONS (user '"Public"');
+CREATE USER MAPPING FOR regress_testrolx SERVER sv9 OPTIONS (user 'regress_testrolx');
+SELECT * FROM chkumapping();
+
+-- DROP USER MAPPING IF EXISTS
+DROP USER MAPPING IF EXISTS FOR CURRENT_USER SERVER sv1;
+SELECT * FROM chkumapping();
+DROP USER MAPPING IF EXISTS FOR "current_user" SERVER sv2;
+SELECT * FROM chkumapping();
+DROP USER MAPPING IF EXISTS FOR CURRENT_USER SERVER sv3;
+SELECT * FROM chkumapping();
+DROP USER MAPPING IF EXISTS FOR USER SERVER sv4;
+SELECT * FROM chkumapping();
+DROP USER MAPPING IF EXISTS FOR "user" SERVER sv5;
+SELECT * FROM chkumapping();
+DROP USER MAPPING IF EXISTS FOR SESSION_USER SERVER sv6;
+SELECT * FROM chkumapping();
+DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER sv7;
+SELECT * FROM chkumapping();
+DROP USER MAPPING IF EXISTS FOR "Public" SERVER sv8;
+SELECT * FROM chkumapping();
+DROP USER MAPPING IF EXISTS FOR regress_testrolx SERVER sv9;
+SELECT * FROM chkumapping();
+
+DROP USER MAPPING IF EXISTS FOR nonexistent SERVER sv10; -- error
+
+-- GRANT/REVOKE
+GRANT regress_testrol0 TO pg_signal_backend; -- success
+
+SET ROLE pg_signal_backend; --success
+RESET ROLE;
+CREATE SCHEMA test_roles_schema AUTHORIZATION pg_signal_backend; --success
+SET ROLE regress_testrol2;
+
+UPDATE pg_proc SET proacl = null WHERE proname LIKE 'testagg_';
+SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_';
+
+REVOKE ALL PRIVILEGES ON FUNCTION testagg1(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg2(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg3(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg4(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg5(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg6(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg7(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg8(int2) FROM PUBLIC;
+
+GRANT ALL PRIVILEGES ON FUNCTION testagg1(int2) TO PUBLIC;
+GRANT ALL PRIVILEGES ON FUNCTION testagg2(int2) TO CURRENT_USER;
+GRANT ALL PRIVILEGES ON FUNCTION testagg3(int2) TO "current_user";
+GRANT ALL PRIVILEGES ON FUNCTION testagg4(int2) TO CURRENT_ROLE;
+GRANT ALL PRIVILEGES ON FUNCTION testagg5(int2) TO SESSION_USER;
+GRANT ALL PRIVILEGES ON FUNCTION testagg6(int2) TO "Public";
+GRANT ALL PRIVILEGES ON FUNCTION testagg7(int2) TO regress_testrolx;
+GRANT ALL PRIVILEGES ON FUNCTION testagg8(int2) TO "public";
+GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2)
+ TO current_user, public, regress_testrolx;
+
+SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_';
+
+GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO USER; --error
+GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO NONE; --error
+GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO "none"; --error
+
+SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_';
+
+REVOKE ALL PRIVILEGES ON FUNCTION testagg1(int2) FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg2(int2) FROM CURRENT_USER;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg3(int2) FROM "current_user";
+REVOKE ALL PRIVILEGES ON FUNCTION testagg4(int2) FROM CURRENT_ROLE;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg5(int2) FROM SESSION_USER;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg6(int2) FROM "Public";
+REVOKE ALL PRIVILEGES ON FUNCTION testagg7(int2) FROM regress_testrolx;
+REVOKE ALL PRIVILEGES ON FUNCTION testagg8(int2) FROM "public";
+REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2)
+ FROM current_user, public, regress_testrolx;
+
+SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_';
+
+REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM USER; --error
+REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM NONE; --error
+REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM "none"; --error
+
+SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_';
+
+-- DEFAULT MONITORING ROLES
+CREATE ROLE regress_role_haspriv;
+CREATE ROLE regress_role_nopriv;
+
+-- pg_read_all_stats
+GRANT pg_read_all_stats TO regress_role_haspriv;
+SET SESSION AUTHORIZATION regress_role_haspriv;
+-- returns true with role member of pg_read_all_stats
+SELECT COUNT(*) = 0 AS haspriv FROM pg_stat_activity
+ WHERE query = '<insufficient privilege>';
+SET SESSION AUTHORIZATION regress_role_nopriv;
+-- returns false with role not member of pg_read_all_stats
+SELECT COUNT(*) = 0 AS haspriv FROM pg_stat_activity
+ WHERE query = '<insufficient privilege>';
+RESET SESSION AUTHORIZATION;
+REVOKE pg_read_all_stats FROM regress_role_haspriv;
+
+-- pg_read_all_settings
+GRANT pg_read_all_settings TO regress_role_haspriv;
+BEGIN;
+-- A GUC using GUC_SUPERUSER_ONLY is useful for negative tests.
+SET LOCAL session_preload_libraries TO 'path-to-preload-libraries';
+SET SESSION AUTHORIZATION regress_role_haspriv;
+-- passes with role member of pg_read_all_settings
+SHOW session_preload_libraries;
+SET SESSION AUTHORIZATION regress_role_nopriv;
+-- fails with role not member of pg_read_all_settings
+SHOW session_preload_libraries;
+RESET SESSION AUTHORIZATION;
+ROLLBACK;
+REVOKE pg_read_all_settings FROM regress_role_haspriv;
+
+-- clean up
+\c
+
+DROP SCHEMA test_roles_schema;
+DROP OWNED BY regress_testrol0, "Public", "current_role", "current_user", regress_testrol1, regress_testrol2, regress_testrolx CASCADE;
+DROP ROLE regress_testrol0, regress_testrol1, regress_testrol2, regress_testrolx;
+DROP ROLE "Public", "None", "current_role", "current_user", "session_user", "user";
+DROP ROLE regress_role_haspriv, regress_role_nopriv;
diff --git a/src/test/modules/worker_spi/.gitignore b/src/test/modules/worker_spi/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/worker_spi/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/worker_spi/Makefile b/src/test/modules/worker_spi/Makefile
new file mode 100644
index 0000000..cbf9b2e
--- /dev/null
+++ b/src/test/modules/worker_spi/Makefile
@@ -0,0 +1,26 @@
+# src/test/modules/worker_spi/Makefile
+
+MODULES = worker_spi
+
+EXTENSION = worker_spi
+DATA = worker_spi--1.0.sql
+PGFILEDESC = "worker_spi - background worker example"
+
+REGRESS = worker_spi
+
+# enable our module in shared_preload_libraries for dynamic bgworkers
+REGRESS_OPTS = --temp-config $(top_srcdir)/src/test/modules/worker_spi/dynamic.conf
+
+# Disable installcheck to ensure we cover dynamic bgworkers.
+NO_INSTALLCHECK = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/worker_spi
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/worker_spi/dynamic.conf b/src/test/modules/worker_spi/dynamic.conf
new file mode 100644
index 0000000..bfe015f
--- /dev/null
+++ b/src/test/modules/worker_spi/dynamic.conf
@@ -0,0 +1,2 @@
+shared_preload_libraries = worker_spi
+worker_spi.database = contrib_regression
diff --git a/src/test/modules/worker_spi/expected/worker_spi.out b/src/test/modules/worker_spi/expected/worker_spi.out
new file mode 100644
index 0000000..dc0a79b
--- /dev/null
+++ b/src/test/modules/worker_spi/expected/worker_spi.out
@@ -0,0 +1,50 @@
+CREATE EXTENSION worker_spi;
+SELECT worker_spi_launch(4) IS NOT NULL;
+ ?column?
+----------
+ t
+(1 row)
+
+-- wait until the worker completes its initialization
+DO $$
+DECLARE
+ visible bool;
+ loops int := 0;
+BEGIN
+ LOOP
+ visible := table_name IS NOT NULL
+ FROM information_schema.tables
+ WHERE table_schema = 'schema4' AND table_name = 'counted';
+ IF visible OR loops > 120 * 10 THEN EXIT; END IF;
+ PERFORM pg_sleep(0.1);
+ loops := loops + 1;
+ END LOOP;
+END
+$$;
+INSERT INTO schema4.counted VALUES ('total', 0), ('delta', 1);
+SELECT pg_reload_conf();
+ pg_reload_conf
+----------------
+ t
+(1 row)
+
+-- wait until the worker has processed the tuple we just inserted
+DO $$
+DECLARE
+ count int;
+ loops int := 0;
+BEGIN
+ LOOP
+ count := count(*) FROM schema4.counted WHERE type = 'delta';
+ IF count = 0 OR loops > 120 * 10 THEN EXIT; END IF;
+ PERFORM pg_sleep(0.1);
+ loops := loops + 1;
+ END LOOP;
+END
+$$;
+SELECT * FROM schema4.counted;
+ type | value
+-------+-------
+ total | 1
+(1 row)
+
diff --git a/src/test/modules/worker_spi/sql/worker_spi.sql b/src/test/modules/worker_spi/sql/worker_spi.sql
new file mode 100644
index 0000000..4683523
--- /dev/null
+++ b/src/test/modules/worker_spi/sql/worker_spi.sql
@@ -0,0 +1,35 @@
+CREATE EXTENSION worker_spi;
+SELECT worker_spi_launch(4) IS NOT NULL;
+-- wait until the worker completes its initialization
+DO $$
+DECLARE
+ visible bool;
+ loops int := 0;
+BEGIN
+ LOOP
+ visible := table_name IS NOT NULL
+ FROM information_schema.tables
+ WHERE table_schema = 'schema4' AND table_name = 'counted';
+ IF visible OR loops > 120 * 10 THEN EXIT; END IF;
+ PERFORM pg_sleep(0.1);
+ loops := loops + 1;
+ END LOOP;
+END
+$$;
+INSERT INTO schema4.counted VALUES ('total', 0), ('delta', 1);
+SELECT pg_reload_conf();
+-- wait until the worker has processed the tuple we just inserted
+DO $$
+DECLARE
+ count int;
+ loops int := 0;
+BEGIN
+ LOOP
+ count := count(*) FROM schema4.counted WHERE type = 'delta';
+ IF count = 0 OR loops > 120 * 10 THEN EXIT; END IF;
+ PERFORM pg_sleep(0.1);
+ loops := loops + 1;
+ END LOOP;
+END
+$$;
+SELECT * FROM schema4.counted;
diff --git a/src/test/modules/worker_spi/worker_spi--1.0.sql b/src/test/modules/worker_spi/worker_spi--1.0.sql
new file mode 100644
index 0000000..e9d5b07
--- /dev/null
+++ b/src/test/modules/worker_spi/worker_spi--1.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/worker_spi/worker_spi--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION worker_spi" to load this file. \quit
+
+CREATE FUNCTION worker_spi_launch(pg_catalog.int4)
+RETURNS pg_catalog.int4 STRICT
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
diff --git a/src/test/modules/worker_spi/worker_spi.c b/src/test/modules/worker_spi/worker_spi.c
new file mode 100644
index 0000000..d0acef2
--- /dev/null
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -0,0 +1,392 @@
+/* -------------------------------------------------------------------------
+ *
+ * worker_spi.c
+ * Sample background worker code that demonstrates various coding
+ * patterns: establishing a database connection; starting and committing
+ * transactions; using GUC variables, and heeding SIGHUP to reread
+ * the configuration file; reporting to pg_stat_activity; using the
+ * process latch to sleep and exit in case of postmaster death.
+ *
+ * This code connects to a database, creates a schema and table, and summarizes
+ * the numbers contained therein. To see it working, insert an initial value
+ * with "total" type and some initial value; then insert some other rows with
+ * "delta" type. Delta rows will be deleted by this worker and their values
+ * aggregated into the total.
+ *
+ * Copyright (c) 2013-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/worker_spi/worker_spi.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+/* These are always necessary for a bgworker */
+#include "miscadmin.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "storage/ipc.h"
+#include "storage/latch.h"
+#include "storage/lwlock.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+
+/* these headers are used by this particular worker's code */
+#include "access/xact.h"
+#include "executor/spi.h"
+#include "fmgr.h"
+#include "lib/stringinfo.h"
+#include "pgstat.h"
+#include "utils/builtins.h"
+#include "utils/snapmgr.h"
+#include "tcop/utility.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(worker_spi_launch);
+
+void _PG_init(void);
+void worker_spi_main(Datum) pg_attribute_noreturn();
+
+/* GUC variables */
+static int worker_spi_naptime = 10;
+static int worker_spi_total_workers = 2;
+static char *worker_spi_database = NULL;
+
+
+typedef struct worktable
+{
+ const char *schema;
+ const char *name;
+} worktable;
+
+/*
+ * Initialize workspace for a worker process: create the schema if it doesn't
+ * already exist.
+ */
+static void
+initialize_worker_spi(worktable *table)
+{
+ int ret;
+ int ntup;
+ bool isnull;
+ StringInfoData buf;
+
+ SetCurrentStatementStartTimestamp();
+ StartTransactionCommand();
+ SPI_connect();
+ PushActiveSnapshot(GetTransactionSnapshot());
+ pgstat_report_activity(STATE_RUNNING, "initializing worker_spi schema");
+
+ /* XXX could we use CREATE SCHEMA IF NOT EXISTS? */
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "select count(*) from pg_namespace where nspname = '%s'",
+ table->schema);
+
+ debug_query_string = buf.data;
+ ret = SPI_execute(buf.data, true, 0);
+ if (ret != SPI_OK_SELECT)
+ elog(FATAL, "SPI_execute failed: error code %d", ret);
+
+ if (SPI_processed != 1)
+ elog(FATAL, "not a singleton result");
+
+ ntup = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[0],
+ SPI_tuptable->tupdesc,
+ 1, &isnull));
+ if (isnull)
+ elog(FATAL, "null result");
+
+ if (ntup == 0)
+ {
+ debug_query_string = NULL;
+ resetStringInfo(&buf);
+ appendStringInfo(&buf,
+ "CREATE SCHEMA \"%s\" "
+ "CREATE TABLE \"%s\" ("
+ " type text CHECK (type IN ('total', 'delta')), "
+ " value integer)"
+ "CREATE UNIQUE INDEX \"%s_unique_total\" ON \"%s\" (type) "
+ "WHERE type = 'total'",
+ table->schema, table->name, table->name, table->name);
+
+ /* set statement start time */
+ SetCurrentStatementStartTimestamp();
+
+ debug_query_string = buf.data;
+ ret = SPI_execute(buf.data, false, 0);
+
+ if (ret != SPI_OK_UTILITY)
+ elog(FATAL, "failed to create my schema");
+
+ debug_query_string = NULL; /* rest is not statement-specific */
+ }
+
+ SPI_finish();
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ debug_query_string = NULL;
+ pgstat_report_activity(STATE_IDLE, NULL);
+}
+
+void
+worker_spi_main(Datum main_arg)
+{
+ int index = DatumGetInt32(main_arg);
+ worktable *table;
+ StringInfoData buf;
+ char name[20];
+
+ table = palloc(sizeof(worktable));
+ sprintf(name, "schema%d", index);
+ table->schema = pstrdup(name);
+ table->name = pstrdup("counted");
+
+ /* Establish signal handlers before unblocking signals. */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
+ pqsignal(SIGTERM, die);
+
+ /* We're now ready to receive signals */
+ BackgroundWorkerUnblockSignals();
+
+ /* Connect to our database */
+ BackgroundWorkerInitializeConnection(worker_spi_database, NULL, 0);
+
+ elog(LOG, "%s initialized with %s.%s",
+ MyBgworkerEntry->bgw_name, table->schema, table->name);
+ initialize_worker_spi(table);
+
+ /*
+ * Quote identifiers passed to us. Note that this must be done after
+ * initialize_worker_spi, because that routine assumes the names are not
+ * quoted.
+ *
+ * Note some memory might be leaked here.
+ */
+ table->schema = quote_identifier(table->schema);
+ table->name = quote_identifier(table->name);
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf,
+ "WITH deleted AS (DELETE "
+ "FROM %s.%s "
+ "WHERE type = 'delta' RETURNING value), "
+ "total AS (SELECT coalesce(sum(value), 0) as sum "
+ "FROM deleted) "
+ "UPDATE %s.%s "
+ "SET value = %s.value + total.sum "
+ "FROM total WHERE type = 'total' "
+ "RETURNING %s.value",
+ table->schema, table->name,
+ table->schema, table->name,
+ table->name,
+ table->name);
+
+ /*
+ * Main loop: do this until SIGTERM is received and processed by
+ * ProcessInterrupts.
+ */
+ for (;;)
+ {
+ int ret;
+
+ /*
+ * Background workers mustn't call usleep() or any direct equivalent:
+ * instead, they may wait on their process latch, which sleeps as
+ * necessary, but is awakened if postmaster dies. That way the
+ * background process goes away immediately in an emergency.
+ */
+ (void) WaitLatch(MyLatch,
+ WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+ worker_spi_naptime * 1000L,
+ PG_WAIT_EXTENSION);
+ ResetLatch(MyLatch);
+
+ CHECK_FOR_INTERRUPTS();
+
+ /*
+ * In case of a SIGHUP, just reload the configuration.
+ */
+ if (ConfigReloadPending)
+ {
+ ConfigReloadPending = false;
+ ProcessConfigFile(PGC_SIGHUP);
+ }
+
+ /*
+ * Start a transaction on which we can run queries. Note that each
+ * StartTransactionCommand() call should be preceded by a
+ * SetCurrentStatementStartTimestamp() call, which sets both the time
+ * for the statement we're about the run, and also the transaction
+ * start time. Also, each other query sent to SPI should probably be
+ * preceded by SetCurrentStatementStartTimestamp(), so that statement
+ * start time is always up to date.
+ *
+ * The SPI_connect() call lets us run queries through the SPI manager,
+ * and the PushActiveSnapshot() call creates an "active" snapshot
+ * which is necessary for queries to have MVCC data to work on.
+ *
+ * The pgstat_report_activity() call makes our activity visible
+ * through the pgstat views.
+ */
+ SetCurrentStatementStartTimestamp();
+ StartTransactionCommand();
+ SPI_connect();
+ PushActiveSnapshot(GetTransactionSnapshot());
+ debug_query_string = buf.data;
+ pgstat_report_activity(STATE_RUNNING, buf.data);
+
+ /* We can now execute queries via SPI */
+ ret = SPI_execute(buf.data, false, 0);
+
+ if (ret != SPI_OK_UPDATE_RETURNING)
+ elog(FATAL, "cannot select from table %s.%s: error code %d",
+ table->schema, table->name, ret);
+
+ if (SPI_processed > 0)
+ {
+ bool isnull;
+ int32 val;
+
+ val = DatumGetInt32(SPI_getbinval(SPI_tuptable->vals[0],
+ SPI_tuptable->tupdesc,
+ 1, &isnull));
+ if (!isnull)
+ elog(LOG, "%s: count in %s.%s is now %d",
+ MyBgworkerEntry->bgw_name,
+ table->schema, table->name, val);
+ }
+
+ /*
+ * And finish our transaction.
+ */
+ SPI_finish();
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ debug_query_string = NULL;
+ pgstat_report_stat(false);
+ pgstat_report_activity(STATE_IDLE, NULL);
+ }
+
+ /* Not reachable */
+}
+
+/*
+ * Entrypoint of this module.
+ *
+ * We register more than one worker process here, to demonstrate how that can
+ * be done.
+ */
+void
+_PG_init(void)
+{
+ BackgroundWorker worker;
+ unsigned int i;
+
+ /* get the configuration */
+ DefineCustomIntVariable("worker_spi.naptime",
+ "Duration between each check (in seconds).",
+ NULL,
+ &worker_spi_naptime,
+ 10,
+ 1,
+ INT_MAX,
+ PGC_SIGHUP,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+ if (!process_shared_preload_libraries_in_progress)
+ return;
+
+ DefineCustomIntVariable("worker_spi.total_workers",
+ "Number of workers.",
+ NULL,
+ &worker_spi_total_workers,
+ 2,
+ 1,
+ 100,
+ PGC_POSTMASTER,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+ DefineCustomStringVariable("worker_spi.database",
+ "Database to connect to.",
+ NULL,
+ &worker_spi_database,
+ "postgres",
+ PGC_POSTMASTER,
+ 0,
+ NULL, NULL, NULL);
+
+ /* set up common data for all our workers */
+ memset(&worker, 0, sizeof(worker));
+ worker.bgw_flags = BGWORKER_SHMEM_ACCESS |
+ BGWORKER_BACKEND_DATABASE_CONNECTION;
+ worker.bgw_start_time = BgWorkerStart_RecoveryFinished;
+ worker.bgw_restart_time = BGW_NEVER_RESTART;
+ sprintf(worker.bgw_library_name, "worker_spi");
+ sprintf(worker.bgw_function_name, "worker_spi_main");
+ worker.bgw_notify_pid = 0;
+
+ /*
+ * Now fill in worker-specific data, and do the actual registrations.
+ */
+ for (i = 1; i <= worker_spi_total_workers; i++)
+ {
+ snprintf(worker.bgw_name, BGW_MAXLEN, "worker_spi worker %d", i);
+ snprintf(worker.bgw_type, BGW_MAXLEN, "worker_spi");
+ worker.bgw_main_arg = Int32GetDatum(i);
+
+ RegisterBackgroundWorker(&worker);
+ }
+}
+
+/*
+ * Dynamically launch an SPI worker.
+ */
+Datum
+worker_spi_launch(PG_FUNCTION_ARGS)
+{
+ int32 i = PG_GETARG_INT32(0);
+ BackgroundWorker worker;
+ BackgroundWorkerHandle *handle;
+ BgwHandleStatus status;
+ pid_t pid;
+
+ memset(&worker, 0, sizeof(worker));
+ worker.bgw_flags = BGWORKER_SHMEM_ACCESS |
+ BGWORKER_BACKEND_DATABASE_CONNECTION;
+ worker.bgw_start_time = BgWorkerStart_RecoveryFinished;
+ worker.bgw_restart_time = BGW_NEVER_RESTART;
+ sprintf(worker.bgw_library_name, "worker_spi");
+ sprintf(worker.bgw_function_name, "worker_spi_main");
+ snprintf(worker.bgw_name, BGW_MAXLEN, "worker_spi worker %d", i);
+ snprintf(worker.bgw_type, BGW_MAXLEN, "worker_spi");
+ worker.bgw_main_arg = Int32GetDatum(i);
+ /* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */
+ worker.bgw_notify_pid = MyProcPid;
+
+ if (!RegisterDynamicBackgroundWorker(&worker, &handle))
+ PG_RETURN_NULL();
+
+ status = WaitForBackgroundWorkerStartup(handle, &pid);
+
+ if (status == BGWH_STOPPED)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+ errmsg("could not start background process"),
+ errhint("More details may be available in the server log.")));
+ if (status == BGWH_POSTMASTER_DIED)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+ errmsg("cannot start background processes without postmaster"),
+ errhint("Kill all remaining database processes and restart the database.")));
+ Assert(status == BGWH_STARTED);
+
+ PG_RETURN_INT32(pid);
+}
diff --git a/src/test/modules/worker_spi/worker_spi.control b/src/test/modules/worker_spi/worker_spi.control
new file mode 100644
index 0000000..84d6294
--- /dev/null
+++ b/src/test/modules/worker_spi/worker_spi.control
@@ -0,0 +1,5 @@
+# worker_spi extension
+comment = 'Sample background worker'
+default_version = '1.0'
+module_pathname = '$libdir/worker_spi'
+relocatable = true
diff --git a/src/test/perl/Makefile b/src/test/perl/Makefile
new file mode 100644
index 0000000..3d3a95b
--- /dev/null
+++ b/src/test/perl/Makefile
@@ -0,0 +1,35 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/perl
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/perl/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/perl
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+ifeq ($(enable_tap_tests),yes)
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(pgxsdir)/$(subdir)'
+
+install: all installdirs
+ $(INSTALL_DATA) $(srcdir)/TestLib.pm '$(DESTDIR)$(pgxsdir)/$(subdir)/TestLib.pm'
+ $(INSTALL_DATA) $(srcdir)/SimpleTee.pm '$(DESTDIR)$(pgxsdir)/$(subdir)/SimpleTee.pm'
+ $(INSTALL_DATA) $(srcdir)/RecursiveCopy.pm '$(DESTDIR)$(pgxsdir)/$(subdir)/RecursiveCopy.pm'
+ $(INSTALL_DATA) $(srcdir)/PostgresNode.pm '$(DESTDIR)$(pgxsdir)/$(subdir)/PostgresNode.pm'
+ $(INSTALL_DATA) $(srcdir)/PostgresVersion.pm '$(DESTDIR)$(pgxsdir)/$(subdir)/PostgresVersion.pm'
+
+uninstall:
+ rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/TestLib.pm'
+ rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/SimpleTee.pm'
+ rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/RecursiveCopy.pm'
+ rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/PostgresNode.pm'
+ rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/PostgresVersion.pm'
+
+endif
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
new file mode 100644
index 0000000..14e9138
--- /dev/null
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -0,0 +1,13 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+# Allow use of release 15+ Perl package name in older branches, by giving that
+# package the same symbol table as the older package. See PostgresNode::new
+# for supporting heuristics.
+
+use strict;
+use warnings;
+BEGIN { *PostgreSQL::Test::Cluster:: = \*PostgresNode::; }
+use PostgresNode ();
+
+1;
diff --git a/src/test/perl/PostgreSQL/Test/Utils.pm b/src/test/perl/PostgreSQL/Test/Utils.pm
new file mode 100644
index 0000000..2d15bbf
--- /dev/null
+++ b/src/test/perl/PostgreSQL/Test/Utils.pm
@@ -0,0 +1,11 @@
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+# Allow use of release 15+ Perl package name in older branches, by giving that
+# package the same symbol table as the older package.
+
+use strict;
+use warnings;
+BEGIN { *PostgreSQL::Test::Utils:: = \*TestLib::; }
+use TestLib ();
+
+1;
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
new file mode 100644
index 0000000..4b17048
--- /dev/null
+++ b/src/test/perl/PostgresNode.pm
@@ -0,0 +1,2810 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+=pod
+
+=head1 NAME
+
+PostgresNode - class representing PostgreSQL server instance
+
+=head1 SYNOPSIS
+
+ use PostgresNode;
+
+ my $node = PostgresNode->get_new_node('mynode');
+
+ # Create a data directory with initdb
+ $node->init();
+
+ # Start the PostgreSQL server
+ $node->start();
+
+ # Change a setting and restart
+ $node->append_conf('postgresql.conf', 'hot_standby = on');
+ $node->restart();
+
+ # run a query with psql, like:
+ # echo 'SELECT 1' | psql -qAXt postgres -v ON_ERROR_STOP=1
+ $psql_stdout = $node->safe_psql('postgres', 'SELECT 1');
+
+ # Run psql with a timeout, capturing stdout and stderr
+ # as well as the psql exit code. Pass some extra psql
+ # options. If there's an error from psql raise an exception.
+ my ($stdout, $stderr, $timed_out);
+ my $cmdret = $node->psql('postgres', 'SELECT pg_sleep(600)',
+ stdout => \$stdout, stderr => \$stderr,
+ timeout => $TestLib::timeout_default,
+ timed_out => \$timed_out,
+ extra_params => ['--single-transaction'],
+ on_error_die => 1)
+ print "Sleep timed out" if $timed_out;
+
+ # Similar thing, more convenient in common cases
+ my ($cmdret, $stdout, $stderr) =
+ $node->psql('postgres', 'SELECT 1');
+
+ # run query every second until it returns 't'
+ # or times out
+ $node->poll_query_until('postgres', q|SELECT random() < 0.1;|')
+ or die "timed out";
+
+ # Do an online pg_basebackup
+ my $ret = $node->backup('testbackup1');
+
+ # Take a backup of a running server
+ my $ret = $node->backup_fs_hot('testbackup2');
+
+ # Take a backup of a stopped server
+ $node->stop;
+ my $ret = $node->backup_fs_cold('testbackup3')
+
+ # Restore it to create a new independent node (not a replica)
+ my $replica = get_new_node('replica');
+ $replica->init_from_backup($node, 'testbackup');
+ $replica->start;
+
+ # Stop the server
+ $node->stop('fast');
+
+ # Find a free, unprivileged TCP port to bind some other service to
+ my $port = get_free_port();
+
+=head1 DESCRIPTION
+
+PostgresNode contains a set of routines able to work on a PostgreSQL node,
+allowing to start, stop, backup and initialize it with various options.
+The set of nodes managed by a given test is also managed by this module.
+
+In addition to node management, PostgresNode instances have some wrappers
+around Test::More functions to run commands with an environment set up to
+point to the instance.
+
+The IPC::Run module is required.
+
+=cut
+
+package PostgresNode;
+
+use strict;
+use warnings;
+
+use Carp;
+use Config;
+use Cwd;
+use Exporter 'import';
+use Fcntl qw(:mode);
+use File::Basename;
+use File::Path qw(rmtree);
+use File::Spec;
+use File::stat qw(stat);
+use File::Temp ();
+use IPC::Run;
+use PostgresVersion;
+use RecursiveCopy;
+use Socket;
+use Test::More;
+use TestLib ();
+use Time::HiRes qw(usleep);
+use Scalar::Util qw(blessed);
+
+our @EXPORT = qw(
+ get_new_node
+ get_free_port
+);
+
+our ($use_tcp, $test_localhost, $test_pghost, $last_host_assigned,
+ $last_port_assigned, @all_nodes, $died);
+
+INIT
+{
+
+ # Set PGHOST for backward compatibility. This doesn't work for own_host
+ # nodes, so prefer to not rely on this when writing new tests.
+ $use_tcp = !$TestLib::use_unix_sockets;
+ $test_localhost = "127.0.0.1";
+ $last_host_assigned = 1;
+ $test_pghost = $use_tcp ? $test_localhost : TestLib::tempdir_short;
+ $ENV{PGHOST} = $test_pghost;
+ $ENV{PGDATABASE} = 'postgres';
+
+ # Tracking of last port value assigned to accelerate free port lookup.
+ $last_port_assigned = int(rand() * 16384) + 49152;
+}
+
+=pod
+
+=head1 METHODS
+
+=over
+
+=item PostgresNode::new($class, $name, $pghost, $pgport)
+
+Create a new PostgresNode instance. Does not initdb or start it.
+
+You should generally prefer to use get_new_node() instead since it takes care
+of finding port numbers, registering instances for cleanup, etc.
+
+=cut
+
+sub new
+{
+ my ($class, $name, $pghost, $pgport) = @_;
+
+ # Use release 15+ semantics when the arguments look like (node_name,
+ # %params). We can't use $class to decide, because get_new_node() passes
+ # a v14- argument list regardless of the class. $class might be an
+ # out-of-core subclass. $class->isa('PostgresNode') returns true even for
+ # descendants of PostgreSQL::Test::Cluster, so it doesn't help.
+ return $class->get_new_node(@_[ 1 .. $#_ ])
+ if !$pghost
+ or !$pgport
+ or $pghost =~ /^[a-zA-Z0-9_]$/;
+
+ my $testname = basename($0);
+ $testname =~ s/\.[^.]+$//;
+ my $self = {
+ _port => $pgport,
+ _host => $pghost,
+ _basedir => "$TestLib::tmp_check/t_${testname}_${name}_data",
+ _name => $name,
+ _logfile_generation => 0,
+ _logfile_base => "$TestLib::log_path/${testname}_${name}",
+ _logfile => "$TestLib::log_path/${testname}_${name}.log"
+ };
+
+ bless $self, $class;
+ mkdir $self->{_basedir}
+ or
+ BAIL_OUT("could not create data directory \"$self->{_basedir}\": $!");
+ $self->dump_info;
+
+ return $self;
+}
+
+=pod
+
+=item $node->port()
+
+Get the port number assigned to the host. This won't necessarily be a TCP port
+open on the local host since we prefer to use unix sockets if possible.
+
+Use $node->connstr() if you want a connection string.
+
+=cut
+
+sub port
+{
+ my ($self) = @_;
+ return $self->{_port};
+}
+
+=pod
+
+=item $node->host()
+
+Return the host (like PGHOST) for this instance. May be a UNIX socket path.
+
+Use $node->connstr() if you want a connection string.
+
+=cut
+
+sub host
+{
+ my ($self) = @_;
+ return $self->{_host};
+}
+
+=pod
+
+=item $node->basedir()
+
+The directory all the node's files will be within - datadir, archive directory,
+backups, etc.
+
+=cut
+
+sub basedir
+{
+ my ($self) = @_;
+ return $self->{_basedir};
+}
+
+=pod
+
+=item $node->name()
+
+The name assigned to the node at creation time.
+
+=cut
+
+sub name
+{
+ my ($self) = @_;
+ return $self->{_name};
+}
+
+=pod
+
+=item $node->logfile()
+
+Path to the PostgreSQL log file for this instance.
+
+=cut
+
+sub logfile
+{
+ my ($self) = @_;
+ return $self->{_logfile};
+}
+
+=pod
+
+=item $node->connstr()
+
+Get a libpq connection string that will establish a connection to
+this node. Suitable for passing to psql, DBD::Pg, etc.
+
+=cut
+
+sub connstr
+{
+ my ($self, $dbname) = @_;
+ my $pgport = $self->port;
+ my $pghost = $self->host;
+ if (!defined($dbname))
+ {
+ return "port=$pgport host=$pghost";
+ }
+
+ # Escape properly the database string before using it, only
+ # single quotes and backslashes need to be treated this way.
+ $dbname =~ s#\\#\\\\#g;
+ $dbname =~ s#\'#\\\'#g;
+
+ return "port=$pgport host=$pghost dbname='$dbname'";
+}
+
+=pod
+
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+ my ($self) = @_;
+
+ my $dir_stat = stat($self->data_dir);
+
+ defined($dir_stat)
+ or die('unable to stat ' . $self->data_dir);
+
+ return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
+=item $node->data_dir()
+
+Returns the path to the data directory. postgresql.conf and pg_hba.conf are
+always here.
+
+=cut
+
+sub data_dir
+{
+ my ($self) = @_;
+ my $res = $self->basedir;
+ return "$res/pgdata";
+}
+
+=pod
+
+=item $node->archive_dir()
+
+If archiving is enabled, WAL files go here.
+
+=cut
+
+sub archive_dir
+{
+ my ($self) = @_;
+ my $basedir = $self->basedir;
+ return "$basedir/archives";
+}
+
+=pod
+
+=item $node->backup_dir()
+
+The output path for backups taken with $node->backup()
+
+=cut
+
+sub backup_dir
+{
+ my ($self) = @_;
+ my $basedir = $self->basedir;
+ return "$basedir/backup";
+}
+
+=pod
+
+=item $node->info()
+
+Return a string containing human-readable diagnostic information (paths, etc)
+about this node.
+
+=cut
+
+sub info
+{
+ my ($self) = @_;
+ my $_info = '';
+ open my $fh, '>', \$_info or die;
+ print $fh "Name: " . $self->name . "\n";
+ print $fh "Version: " . $self->{_pg_version} . "\n"
+ if $self->{_pg_version};
+ print $fh "Data directory: " . $self->data_dir . "\n";
+ print $fh "Backup directory: " . $self->backup_dir . "\n";
+ print $fh "Archive directory: " . $self->archive_dir . "\n";
+ print $fh "Connection string: " . $self->connstr . "\n";
+ print $fh "Log file: " . $self->logfile . "\n";
+ print $fh "Install Path: ", $self->{_install_path} . "\n"
+ if $self->{_install_path};
+ close $fh or die;
+ return $_info;
+}
+
+=pod
+
+=item $node->dump_info()
+
+Print $node->info()
+
+=cut
+
+sub dump_info
+{
+ my ($self) = @_;
+ print $self->info;
+ return;
+}
+
+
+# Internal method to set up trusted pg_hba.conf for replication. Not
+# documented because you shouldn't use it, it's called automatically if needed.
+sub set_replication_conf
+{
+ my ($self) = @_;
+ my $pgdata = $self->data_dir;
+
+ $self->host eq $test_pghost
+ or croak "set_replication_conf only works with the default host";
+
+ open my $hba, '>>', "$pgdata/pg_hba.conf";
+ print $hba "\n# Allow replication (set up by PostgresNode.pm)\n";
+ if ($TestLib::windows_os && !$TestLib::use_unix_sockets)
+ {
+ print $hba
+ "host replication all $test_localhost/32 sspi include_realm=1 map=regress\n";
+ }
+ close $hba;
+ return;
+}
+
+=pod
+
+=item $node->init(...)
+
+Initialize a new cluster for testing.
+
+Authentication is set up so that only the current OS user can access the
+cluster. On Unix, we use Unix domain socket connections, with the socket in
+a directory that's only accessible to the current user to ensure that.
+On Windows, we use SSPI authentication to ensure the same (by pg_regress
+--config-auth).
+
+WAL archiving can be enabled on this node by passing the keyword parameter
+has_archiving => 1. This is disabled by default.
+
+postgresql.conf can be set up for replication by passing the keyword
+parameter allows_streaming => 'logical' or 'physical' (passing 1 will also
+suffice for physical replication) depending on type of replication that
+should be enabled. This is disabled by default.
+
+The new node is set up in a fast but unsafe configuration where fsync is
+disabled.
+
+=cut
+
+sub init
+{
+ my ($self, %params) = @_;
+ my $port = $self->port;
+ my $pgdata = $self->data_dir;
+ my $host = $self->host;
+
+ local %ENV = $self->_get_env();
+
+ $params{allows_streaming} = 0 unless defined $params{allows_streaming};
+ $params{has_archiving} = 0 unless defined $params{has_archiving};
+
+ mkdir $self->backup_dir;
+ mkdir $self->archive_dir;
+
+ TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+ @{ $params{extra} });
+ TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
+ @{ $params{auth_extra} });
+
+ open my $conf, '>>', "$pgdata/postgresql.conf";
+ print $conf "\n# Added by PostgresNode.pm\n";
+ print $conf "fsync = off\n";
+ print $conf "restart_after_crash = off\n";
+ print $conf "log_line_prefix = '%m [%p] %q%a '\n";
+ print $conf "log_statement = all\n";
+ print $conf "log_replication_commands = on\n";
+ print $conf "wal_retrieve_retry_interval = '500ms'\n";
+
+ # If a setting tends to affect whether tests pass or fail, print it after
+ # TEMP_CONFIG. Otherwise, print it before TEMP_CONFIG, thereby permitting
+ # overrides. Settings that merely improve performance or ease debugging
+ # belong before TEMP_CONFIG.
+ print $conf TestLib::slurp_file($ENV{TEMP_CONFIG})
+ if defined $ENV{TEMP_CONFIG};
+
+ # XXX Neutralize any stats_temp_directory in TEMP_CONFIG. Nodes running
+ # concurrently must not share a stats_temp_directory.
+ print $conf "stats_temp_directory = 'pg_stat_tmp'\n";
+
+ if ($params{allows_streaming})
+ {
+ if ($params{allows_streaming} eq "logical")
+ {
+ print $conf "wal_level = logical\n";
+ }
+ else
+ {
+ print $conf "wal_level = replica\n";
+ }
+ print $conf "max_wal_senders = 10\n";
+ print $conf "max_replication_slots = 10\n";
+ print $conf "wal_log_hints = on\n";
+ print $conf "hot_standby = on\n";
+ # conservative settings to ensure we can run multiple postmasters:
+ print $conf "shared_buffers = 1MB\n";
+ print $conf "max_connections = 10\n";
+ # limit disk space consumption, too:
+ print $conf "max_wal_size = 128MB\n";
+ }
+ else
+ {
+ print $conf "wal_level = minimal\n";
+ print $conf "max_wal_senders = 0\n";
+ }
+
+ print $conf "port = $port\n";
+ if ($use_tcp)
+ {
+ print $conf "unix_socket_directories = ''\n";
+ print $conf "listen_addresses = '$host'\n";
+ }
+ else
+ {
+ print $conf "unix_socket_directories = '$host'\n";
+ print $conf "listen_addresses = ''\n";
+ }
+ close $conf;
+
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
+ $self->set_replication_conf if $params{allows_streaming};
+ $self->enable_archiving if $params{has_archiving};
+ return;
+}
+
+=pod
+
+=item $node->append_conf(filename, str)
+
+A shortcut method to append to files like pg_hba.conf and postgresql.conf.
+
+Does no validation or sanity checking. Does not reload the configuration
+after writing.
+
+A newline is automatically appended to the string.
+
+=cut
+
+sub append_conf
+{
+ my ($self, $filename, $str) = @_;
+
+ my $conffile = $self->data_dir . '/' . $filename;
+
+ TestLib::append_to_file($conffile, $str . "\n");
+
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
+ or die("unable to set permissions for $conffile");
+
+ return;
+}
+
+=pod
+
+=item $node->backup(backup_name)
+
+Create a hot backup with B<pg_basebackup> in subdirectory B<backup_name> of
+B<< $node->backup_dir >>, including the WAL.
+
+By default, WAL files are fetched at the end of the backup, not streamed.
+You can adjust that and other things by passing an array of additional
+B<pg_basebackup> command line options in the keyword parameter backup_options.
+
+You'll have to configure a suitable B<max_wal_senders> on the
+target server since it isn't done by default.
+
+=cut
+
+sub backup
+{
+ my ($self, $backup_name, %params) = @_;
+ my $backup_path = $self->backup_dir . '/' . $backup_name;
+ my $name = $self->name;
+
+ local %ENV = $self->_get_env();
+
+ print "# Taking pg_basebackup $backup_name from node \"$name\"\n";
+ TestLib::system_or_bail(
+ 'pg_basebackup', '-D',
+ $backup_path, '-h',
+ $self->host, '-p',
+ $self->port, '--checkpoint',
+ 'fast', '--no-sync',
+ @{ $params{backup_options} });
+ print "# Backup finished\n";
+ return;
+}
+
+=item $node->backup_fs_hot(backup_name)
+
+Create a backup with a filesystem level copy in subdirectory B<backup_name> of
+B<< $node->backup_dir >>, including WAL.
+
+Archiving must be enabled, as B<pg_start_backup()> and B<pg_stop_backup()> are
+used. This is not checked or enforced.
+
+The backup name is passed as the backup label to B<pg_start_backup()>.
+
+=cut
+
+sub backup_fs_hot
+{
+ my ($self, $backup_name) = @_;
+ $self->_backup_fs($backup_name, 1);
+ return;
+}
+
+=item $node->backup_fs_cold(backup_name)
+
+Create a backup with a filesystem level copy in subdirectory B<backup_name> of
+B<< $node->backup_dir >>, including WAL. The server must be
+stopped as no attempt to handle concurrent writes is made.
+
+Use B<backup> or B<backup_fs_hot> if you want to back up a running server.
+
+=cut
+
+sub backup_fs_cold
+{
+ my ($self, $backup_name) = @_;
+ $self->_backup_fs($backup_name, 0);
+ return;
+}
+
+
+# Common sub of backup_fs_hot and backup_fs_cold
+sub _backup_fs
+{
+ my ($self, $backup_name, $hot) = @_;
+ my $backup_path = $self->backup_dir . '/' . $backup_name;
+ my $port = $self->port;
+ my $name = $self->name;
+
+ print "# Taking filesystem backup $backup_name from node \"$name\"\n";
+
+ if ($hot)
+ {
+ my $stdout = $self->safe_psql('postgres',
+ "SELECT * FROM pg_start_backup('$backup_name');");
+ print "# pg_start_backup: $stdout\n";
+ }
+
+ RecursiveCopy::copypath(
+ $self->data_dir,
+ $backup_path,
+ filterfn => sub {
+ my $src = shift;
+ return ($src ne 'log' and $src ne 'postmaster.pid');
+ });
+
+ if ($hot)
+ {
+
+ # We ignore pg_stop_backup's return value. We also assume archiving
+ # is enabled; otherwise the caller will have to copy the remaining
+ # segments.
+ my $stdout =
+ $self->safe_psql('postgres', 'SELECT * FROM pg_stop_backup();');
+ print "# pg_stop_backup: $stdout\n";
+ }
+
+ print "# Backup finished\n";
+ return;
+}
+
+
+
+=pod
+
+=item $node->init_from_backup(root_node, backup_name)
+
+Initialize a node from a backup, which may come from this node or a different
+node. root_node must be a PostgresNode reference, backup_name the string name
+of a backup previously created on that node with $node->backup.
+
+Does not start the node after initializing it.
+
+By default, the backup is assumed to be plain format. To restore from
+a tar-format backup, pass the name of the tar program to use in the
+keyword parameter tar_program. Note that tablespace tar files aren't
+handled here.
+
+Streaming replication can be enabled on this node by passing the keyword
+parameter has_streaming => 1. This is disabled by default.
+
+Restoring WAL segments from archives using restore_command can be enabled
+by passing the keyword parameter has_restoring => 1. This is disabled by
+default.
+
+If has_restoring is used, standby mode is used by default. To use
+recovery mode instead, pass the keyword parameter standby => 0.
+
+The backup is copied, leaving the original unmodified. pg_hba.conf is
+unconditionally set to enable replication connections.
+
+=cut
+
+sub init_from_backup
+{
+ my ($self, $root_node, $backup_name, %params) = @_;
+ my $backup_path = $root_node->backup_dir . '/' . $backup_name;
+ my $host = $self->host;
+ my $port = $self->port;
+ my $node_name = $self->name;
+ my $root_name = $root_node->name;
+
+ $params{has_streaming} = 0 unless defined $params{has_streaming};
+ $params{has_restoring} = 0 unless defined $params{has_restoring};
+ $params{standby} = 1 unless defined $params{standby};
+
+ print
+ "# Initializing node \"$node_name\" from backup \"$backup_name\" of node \"$root_name\"\n";
+ croak "Backup \"$backup_name\" does not exist at $backup_path"
+ unless -d $backup_path;
+
+ mkdir $self->backup_dir;
+ mkdir $self->archive_dir;
+
+ my $data_path = $self->data_dir;
+ if (defined $params{tar_program})
+ {
+ mkdir($data_path);
+ TestLib::system_or_bail($params{tar_program}, 'xf',
+ $backup_path . '/base.tar',
+ '-C', $data_path);
+ TestLib::system_or_bail(
+ $params{tar_program}, 'xf',
+ $backup_path . '/pg_wal.tar', '-C',
+ $data_path . '/pg_wal');
+ }
+ else
+ {
+ rmdir($data_path);
+ RecursiveCopy::copypath($backup_path, $data_path);
+ }
+ chmod(0700, $data_path);
+
+ # Base configuration for this node
+ $self->append_conf(
+ 'postgresql.conf',
+ qq(
+port = $port
+));
+ if ($use_tcp)
+ {
+ $self->append_conf('postgresql.conf', "listen_addresses = '$host'");
+ }
+ else
+ {
+ $self->append_conf('postgresql.conf',
+ "unix_socket_directories = '$host'");
+ }
+ $self->enable_streaming($root_node) if $params{has_streaming};
+ $self->enable_restoring($root_node, $params{standby})
+ if $params{has_restoring};
+ return;
+}
+
+=pod
+
+=item $node->rotate_logfile()
+
+Switch to a new PostgreSQL log file. This does not alter any running
+PostgreSQL process. Subsequent method calls, including pg_ctl invocations,
+will use the new name. Return the new name.
+
+=cut
+
+sub rotate_logfile
+{
+ my ($self) = @_;
+ $self->{_logfile} = sprintf('%s_%d.log',
+ $self->{_logfile_base},
+ ++$self->{_logfile_generation});
+ return $self->{_logfile};
+}
+
+=pod
+
+=item $node->start(%params) => success_or_failure
+
+Wrapper for pg_ctl start
+
+Start the node and wait until it is ready to accept connections.
+
+=over
+
+=item fail_ok => 1
+
+By default, failure terminates the entire F<prove> invocation. If given,
+instead return a true or false value to indicate success or failure.
+
+=back
+
+=cut
+
+sub start
+{
+ my ($self, %params) = @_;
+ my $port = $self->port;
+ my $pgdata = $self->data_dir;
+ my $name = $self->name;
+ my $ret;
+
+ BAIL_OUT("node \"$name\" is already running") if defined $self->{_pid};
+
+ print("### Starting node \"$name\"\n");
+
+ # Temporarily unset PGAPPNAME so that the server doesn't
+ # inherit it. Otherwise this could affect libpqwalreceiver
+ # connections in confusing ways.
+ local %ENV = $self->_get_env(PGAPPNAME => undef);
+
+ # Note: We set the cluster_name here, not in postgresql.conf (in
+ # sub init) so that it does not get copied to standbys.
+ $ret = TestLib::system_log('pg_ctl', '-D', $self->data_dir, '-l',
+ $self->logfile, '-o', "--cluster-name=$name", 'start');
+
+ if ($ret != 0)
+ {
+ print "# pg_ctl start failed; logfile:\n";
+ print TestLib::slurp_file($self->logfile);
+
+ # pg_ctl could have timed out, so check to see if there's a pid file;
+ # otherwise our END block will fail to shut down the new postmaster.
+ $self->_update_pid(-1);
+
+ BAIL_OUT("pg_ctl start failed") unless $params{fail_ok};
+ return 0;
+ }
+
+ $self->_update_pid(1);
+ return 1;
+}
+
+=pod
+
+=item $node->kill9()
+
+Send SIGKILL (signal 9) to the postmaster.
+
+Note: if the node is already known stopped, this does nothing.
+However, if we think it's running and it's not, it's important for
+this to fail. Otherwise, tests might fail to detect server crashes.
+
+=cut
+
+sub kill9
+{
+ my ($self) = @_;
+ my $name = $self->name;
+ return unless defined $self->{_pid};
+
+ local %ENV = $self->_get_env();
+
+ print "### Killing node \"$name\" using signal 9\n";
+ kill(9, $self->{_pid});
+ $self->{_pid} = undef;
+ return;
+}
+
+=pod
+
+=item $node->stop(mode)
+
+Stop the node using pg_ctl -m $mode and wait for it to stop.
+
+Note: if the node is already known stopped, this does nothing.
+However, if we think it's running and it's not, it's important for
+this to fail. Otherwise, tests might fail to detect server crashes.
+
+With optional extra param fail_ok => 1, returns 0 for failure
+instead of bailing out.
+
+=cut
+
+sub stop
+{
+ my ($self, $mode, %params) = @_;
+ my $pgdata = $self->data_dir;
+ my $name = $self->name;
+ my $ret;
+
+ local %ENV = $self->_get_env();
+
+ $mode = 'fast' unless defined $mode;
+ return 1 unless defined $self->{_pid};
+
+ print "### Stopping node \"$name\" using mode $mode\n";
+ $ret = TestLib::system_log('pg_ctl', '-D', $pgdata,
+ '-m', $mode, 'stop');
+
+ if ($ret != 0)
+ {
+ print "# pg_ctl stop failed: $ret\n";
+
+ # Check to see if we still have a postmaster or not.
+ $self->_update_pid(-1);
+
+ BAIL_OUT("pg_ctl stop failed") unless $params{fail_ok};
+ return 0;
+ }
+
+ $self->_update_pid(0);
+ return 1;
+}
+
+=pod
+
+=item $node->reload()
+
+Reload configuration parameters on the node.
+
+=cut
+
+sub reload
+{
+ my ($self) = @_;
+ my $port = $self->port;
+ my $pgdata = $self->data_dir;
+ my $name = $self->name;
+
+ local %ENV = $self->_get_env();
+
+ print "### Reloading node \"$name\"\n";
+ TestLib::system_or_bail('pg_ctl', '-D', $pgdata, 'reload');
+ return;
+}
+
+=pod
+
+=item $node->restart()
+
+Wrapper for pg_ctl restart
+
+=cut
+
+sub restart
+{
+ my ($self) = @_;
+ my $port = $self->port;
+ my $pgdata = $self->data_dir;
+ my $logfile = $self->logfile;
+ my $name = $self->name;
+
+ local %ENV = $self->_get_env(PGAPPNAME => undef);
+
+ print "### Restarting node \"$name\"\n";
+
+ TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ 'restart');
+
+ $self->_update_pid(1);
+ return;
+}
+
+=pod
+
+=item $node->promote()
+
+Wrapper for pg_ctl promote
+
+=cut
+
+sub promote
+{
+ my ($self) = @_;
+ my $port = $self->port;
+ my $pgdata = $self->data_dir;
+ my $logfile = $self->logfile;
+ my $name = $self->name;
+
+ local %ENV = $self->_get_env();
+
+ print "### Promoting node \"$name\"\n";
+ TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ 'promote');
+ return;
+}
+
+=pod
+
+=item $node->logrotate()
+
+Wrapper for pg_ctl logrotate
+
+=cut
+
+sub logrotate
+{
+ my ($self) = @_;
+ my $port = $self->port;
+ my $pgdata = $self->data_dir;
+ my $logfile = $self->logfile;
+ my $name = $self->name;
+
+ local %ENV = $self->_get_env();
+
+ print "### Rotating log in node \"$name\"\n";
+ TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ 'logrotate');
+ return;
+}
+
+# Internal routine to enable streaming replication on a standby node.
+sub enable_streaming
+{
+ my ($self, $root_node) = @_;
+ my $root_connstr = $root_node->connstr;
+ my $name = $self->name;
+
+ print "### Enabling streaming replication for node \"$name\"\n";
+ $self->append_conf(
+ 'postgresql.conf', qq(
+primary_conninfo='$root_connstr'
+));
+ $self->set_standby_mode();
+ return;
+}
+
+# Internal routine to enable archive recovery command on a standby node
+sub enable_restoring
+{
+ my ($self, $root_node, $standby) = @_;
+ my $path = $root_node->archive_dir;
+ my $name = $self->name;
+
+ print "### Enabling WAL restore for node \"$name\"\n";
+
+ # On Windows, the path specified in the restore command needs to use
+ # double back-slashes to work properly and to be able to detect properly
+ # the file targeted by the copy command, so the directory value used
+ # in this routine, using only one back-slash, need to be properly changed
+ # first. Paths also need to be double-quoted to prevent failures where
+ # the path contains spaces.
+ $path =~ s{\\}{\\\\}g if ($TestLib::windows_os);
+ my $copy_command =
+ $TestLib::windows_os
+ ? qq{copy "$path\\\\%f" "%p"}
+ : qq{cp "$path/%f" "%p"};
+
+ $self->append_conf(
+ 'postgresql.conf', qq(
+restore_command = '$copy_command'
+));
+ if ($standby)
+ {
+ $self->set_standby_mode();
+ }
+ else
+ {
+ $self->set_recovery_mode();
+ }
+ return;
+}
+
+=pod
+
+=item $node->set_recovery_mode()
+
+Place recovery.signal file.
+
+=cut
+
+sub set_recovery_mode
+{
+ my ($self) = @_;
+
+ $self->append_conf('recovery.signal', '');
+ return;
+}
+
+=pod
+
+=item $node->set_standby_mode()
+
+Place standby.signal file.
+
+=cut
+
+sub set_standby_mode
+{
+ my ($self) = @_;
+
+ $self->append_conf('standby.signal', '');
+ return;
+}
+
+# Internal routine to enable archiving
+sub enable_archiving
+{
+ my ($self) = @_;
+ my $path = $self->archive_dir;
+ my $name = $self->name;
+
+ print "### Enabling WAL archiving for node \"$name\"\n";
+
+ # On Windows, the path specified in the restore command needs to use
+ # double back-slashes to work properly and to be able to detect properly
+ # the file targeted by the copy command, so the directory value used
+ # in this routine, using only one back-slash, need to be properly changed
+ # first. Paths also need to be double-quoted to prevent failures where
+ # the path contains spaces.
+ $path =~ s{\\}{\\\\}g if ($TestLib::windows_os);
+ my $copy_command =
+ $TestLib::windows_os
+ ? qq{copy "%p" "$path\\\\%f"}
+ : qq{cp "%p" "$path/%f"};
+
+ # Enable archive_mode and archive_command on node
+ $self->append_conf(
+ 'postgresql.conf', qq(
+archive_mode = on
+archive_command = '$copy_command'
+));
+ return;
+}
+
+# Internal method to update $self->{_pid}
+# $is_running = 1: pid file should be there
+# $is_running = 0: pid file should NOT be there
+# $is_running = -1: we aren't sure
+sub _update_pid
+{
+ my ($self, $is_running) = @_;
+ my $name = $self->name;
+
+ # If we can open the PID file, read its first line and that's the PID we
+ # want.
+ if (open my $pidfile, '<', $self->data_dir . "/postmaster.pid")
+ {
+ chomp($self->{_pid} = <$pidfile>);
+ close $pidfile;
+
+ # If we aren't sure what to expect, validate the PID using kill().
+ # This protects against stale PID files left by crashed postmasters.
+ if ($is_running == -1 && kill(0, $self->{_pid}) == 0)
+ {
+ print
+ "# Stale postmaster.pid file for node \"$name\": PID $self->{_pid} no longer exists\n";
+ $self->{_pid} = undef;
+ return;
+ }
+
+ print "# Postmaster PID for node \"$name\" is $self->{_pid}\n";
+
+ # If we found a pidfile when there shouldn't be one, complain.
+ BAIL_OUT("postmaster.pid unexpectedly present") if $is_running == 0;
+ return;
+ }
+
+ $self->{_pid} = undef;
+ print "# No postmaster PID for node \"$name\"\n";
+
+ # Complain if we expected to find a pidfile.
+ BAIL_OUT("postmaster.pid unexpectedly not present") if $is_running == 1;
+ return;
+}
+
+=pod
+
+=item PostgresNode->get_new_node(node_name, %params)
+
+Build a new object of class C<PostgresNode> (or of a subclass, if you have
+one), assigning a free port number. Remembers the node, to prevent its port
+number from being reused for another node, and to ensure that it gets
+shut down when the test script exits.
+
+You should generally use this instead of C<PostgresNode::new(...)>.
+
+=over
+
+=item port => [1,65535]
+
+By default, this function assigns a port number to each node. Specify this to
+force a particular port number. The caller is responsible for evaluating
+potential conflicts and privilege requirements.
+
+=item own_host => 1
+
+By default, all nodes use the same PGHOST value. If specified, generate a
+PGHOST specific to this node. This allows multiple nodes to use the same
+port.
+
+=item install_path => '/path/to/postgres/installation'
+
+Using this parameter is it possible to have nodes pointing to different
+installations, for testing different versions together or the same version
+with different build parameters. The provided path must be the parent of the
+installation's 'bin' and 'lib' directories. In the common case where this is
+not provided, Postgres binaries will be found in the caller's PATH.
+
+=back
+
+For backwards compatibility, it is also exported as a standalone function,
+which can only create objects of class C<PostgresNode>.
+
+=cut
+
+sub get_new_node
+{
+ my $class = 'PostgresNode';
+ $class = shift if scalar(@_) % 2 != 1;
+ my ($name, %params) = @_;
+
+ # Select a port.
+ my $port;
+ if (defined $params{port})
+ {
+ $port = $params{port};
+ }
+ else
+ {
+ # When selecting a port, we look for an unassigned TCP port number,
+ # even if we intend to use only Unix-domain sockets. This is clearly
+ # necessary on $use_tcp (Windows) configurations, and it seems like a
+ # good idea on Unixen as well.
+ $port = get_free_port();
+ }
+
+ # Select a host.
+ my $host = $test_pghost;
+ if ($params{own_host})
+ {
+ if ($use_tcp)
+ {
+ $last_host_assigned++;
+ $last_host_assigned > 254 and BAIL_OUT("too many own_host nodes");
+ $host = '127.0.0.' . $last_host_assigned;
+ }
+ else
+ {
+ $host = "$test_pghost/$name"; # Assume $name =~ /^[-_a-zA-Z0-9]+$/
+ mkdir $host;
+ }
+ }
+
+ # Lock port number found by creating a new node
+ my $node = $class->new($name, $host, $port);
+
+ if ($params{install_path})
+ {
+ $node->{_install_path} = $params{install_path};
+ }
+
+ # Add node to list of nodes
+ push(@all_nodes, $node);
+
+ $node->_set_pg_version;
+
+ my $v = $node->{_pg_version};
+
+ carp("PostgresNode isn't fully compatible with version " . $v)
+ if $v < 12;
+
+ return $node;
+}
+
+# Private routine to run the pg_config binary found in our environment (or in
+# our install_path, if we have one), and set the version from it
+#
+sub _set_pg_version
+{
+ my ($self) = @_;
+ my $inst = $self->{_install_path};
+ my $pg_config = "pg_config";
+
+ if (defined $inst)
+ {
+ # If the _install_path is invalid, our PATH variables might find an
+ # unrelated pg_config executable elsewhere. Sanity check the
+ # directory.
+ BAIL_OUT("directory not found: $inst")
+ unless -d $inst;
+
+ # If the directory exists but is not the root of a postgresql
+ # installation, or if the user configured using
+ # --bindir=$SOMEWHERE_ELSE, we're not going to find pg_config, so
+ # complain about that, too.
+ $pg_config = "$inst/bin/pg_config";
+ BAIL_OUT("pg_config not found: $pg_config")
+ unless -e $pg_config
+ or ($TestLib::windows_os and -e "$pg_config.exe");
+ BAIL_OUT("pg_config not executable: $pg_config")
+ unless $TestLib::windows_os or -x $pg_config;
+
+ # Leave $pg_config install_path qualified, to be sure we get the right
+ # version information, below, or die trying
+ }
+
+ local %ENV = $self->_get_env();
+
+ # We only want the version field
+ my $version_line = qx{$pg_config --version};
+ BAIL_OUT("$pg_config failed: $!") if $?;
+
+ $self->{_pg_version} = PostgresVersion->new($version_line);
+
+ BAIL_OUT("could not parse pg_config --version output: $version_line")
+ unless defined $self->{_pg_version};
+}
+
+# Private routine to return a copy of the environment with the PATH and
+# (DY)LD_LIBRARY_PATH correctly set when there is an install path set for
+# the node.
+#
+# Routines that call Postgres binaries need to call this routine like this:
+#
+# local %ENV = $self->_get_env{[%extra_settings]);
+#
+# A copy of the environment is taken and node's host and port settings are
+# added as PGHOST and PGPORT, Then the extra settings (if any) are applied.
+# Any setting in %extra_settings with a value that is undefined is deleted
+# the remainder are# set. Then the PATH and (DY)LD_LIBRARY_PATH are adjusted
+# if the node's install path is set, and the copy environment is returned.
+#
+# The install path set in get_new_node needs to be a directory containing
+# bin and lib subdirectories as in a standard PostgreSQL installation, so this
+# can't be used with installations where the bin and lib directories don't have
+# a common parent directory.
+sub _get_env
+{
+ my $self = shift;
+ my %inst_env = (%ENV, PGHOST => $self->{_host}, PGPORT => $self->{_port});
+ # the remaining arguments are modifications to make to the environment
+ my %mods = (@_);
+ while (my ($k, $v) = each %mods)
+ {
+ if (defined $v)
+ {
+ $inst_env{$k} = "$v";
+ }
+ else
+ {
+ delete $inst_env{$k};
+ }
+ }
+ # now fix up the new environment for the install path
+ my $inst = $self->{_install_path};
+ if ($inst)
+ {
+ if ($TestLib::windows_os)
+ {
+ # Windows picks up DLLs from the PATH rather than *LD_LIBRARY_PATH
+ # choose the right path separator
+ if ($Config{osname} eq 'MSWin32')
+ {
+ $inst_env{PATH} = "$inst/bin;$inst/lib;$ENV{PATH}";
+ }
+ else
+ {
+ $inst_env{PATH} = "$inst/bin:$inst/lib:$ENV{PATH}";
+ }
+ }
+ else
+ {
+ my $dylib_name =
+ $Config{osname} eq 'darwin'
+ ? "DYLD_LIBRARY_PATH"
+ : "LD_LIBRARY_PATH";
+ $inst_env{PATH} = "$inst/bin:$ENV{PATH}";
+ if (exists $ENV{$dylib_name})
+ {
+ $inst_env{$dylib_name} = "$inst/lib:$ENV{$dylib_name}";
+ }
+ else
+ {
+ $inst_env{$dylib_name} = "$inst/lib";
+ }
+ }
+ }
+ return (%inst_env);
+}
+
+# Private routine to get an installation path qualified command.
+#
+# IPC::Run maintains a cache, %cmd_cache, mapping commands to paths. Tests
+# which use nodes spanning more than one postgres installation path need to
+# avoid confusing which installation's binaries get run. Setting $ENV{PATH} is
+# insufficient, as IPC::Run does not check to see if the path has changed since
+# caching a command.
+sub installed_command
+{
+ my ($self, $cmd) = @_;
+
+ # Nodes using alternate installation locations use their installation's
+ # bin/ directory explicitly
+ return join('/', $self->{_install_path}, 'bin', $cmd)
+ if defined $self->{_install_path};
+
+ # Nodes implicitly using the default installation location rely on IPC::Run
+ # to find the right binary, which should not cause %cmd_cache confusion,
+ # because no nodes with other installation paths do it that way.
+ return $cmd;
+}
+
+=pod
+
+=item get_free_port()
+
+Locate an unprivileged (high) TCP port that's not currently bound to
+anything. This is used by get_new_node, and is also exported for use
+by test cases that need to start other, non-Postgres servers.
+
+Ports assigned to existing PostgresNode objects are automatically
+excluded, even if those servers are not currently running.
+
+XXX A port available now may become unavailable by the time we start
+the desired service.
+
+=cut
+
+sub get_free_port
+{
+ my $found = 0;
+ my $port = $last_port_assigned;
+
+ while ($found == 0)
+ {
+
+ # advance $port, wrapping correctly around range end
+ $port = 49152 if ++$port >= 65536;
+ print "# Checking port $port\n";
+
+ # Check first that candidate port number is not included in
+ # the list of already-registered nodes.
+ $found = 1;
+ foreach my $node (@all_nodes)
+ {
+ $found = 0 if ($node->port == $port);
+ }
+
+ # Check to see if anything else is listening on this TCP port.
+ # Seek a port available for all possible listen_addresses values,
+ # so callers can harness this port for the widest range of purposes.
+ # The 0.0.0.0 test achieves that for MSYS, which automatically sets
+ # SO_EXCLUSIVEADDRUSE. Testing 0.0.0.0 is insufficient for Windows
+ # native Perl (https://stackoverflow.com/a/14388707), so we also
+ # have to test individual addresses. Doing that for 127.0.0/24
+ # addresses other than 127.0.0.1 might fail with EADDRNOTAVAIL on
+ # non-Linux, non-Windows kernels.
+ #
+ # Thus, 0.0.0.0 and individual 127.0.0/24 addresses are tested
+ # only on Windows and only when TCP usage is requested.
+ if ($found == 1)
+ {
+ foreach my $addr (qw(127.0.0.1),
+ ($use_tcp && $TestLib::windows_os)
+ ? qw(127.0.0.2 127.0.0.3 0.0.0.0)
+ : ())
+ {
+ if (!can_bind($addr, $port))
+ {
+ $found = 0;
+ last;
+ }
+ }
+ }
+ }
+
+ print "# Found port $port\n";
+
+ # Update port for next time
+ $last_port_assigned = $port;
+
+ return $port;
+}
+
+# Internal routine to check whether a host:port is available to bind
+sub can_bind
+{
+ my ($host, $port) = @_;
+ my $iaddr = inet_aton($host);
+ my $paddr = sockaddr_in($port, $iaddr);
+ my $proto = getprotobyname("tcp");
+
+ socket(SOCK, PF_INET, SOCK_STREAM, $proto)
+ or die "socket failed: $!";
+
+ # As in postmaster, don't use SO_REUSEADDR on Windows
+ setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1))
+ unless $TestLib::windows_os;
+ my $ret = bind(SOCK, $paddr) && listen(SOCK, SOMAXCONN);
+ close(SOCK);
+ return $ret;
+}
+
+# Automatically shut down any still-running nodes (in the same order the nodes
+# were created in) when the test script exits.
+END
+{
+
+ # take care not to change the script's exit value
+ my $exit_code = $?;
+
+ foreach my $node (@all_nodes)
+ {
+ $node->teardown_node;
+
+ # skip clean if we are requested to retain the basedir
+ next if defined $ENV{'PG_TEST_NOCLEAN'};
+
+ # clean basedir on clean test invocation
+ $node->clean_node if $exit_code == 0 && TestLib::all_tests_passing();
+ }
+
+ $? = $exit_code;
+}
+
+=pod
+
+=item $node->teardown_node()
+
+Do an immediate stop of the node
+
+=cut
+
+sub teardown_node
+{
+ my $self = shift;
+
+ $self->stop('immediate');
+ return;
+}
+
+=pod
+
+=item $node->clean_node()
+
+Remove the base directory of the node if the node has been stopped.
+
+=cut
+
+sub clean_node
+{
+ my $self = shift;
+
+ rmtree $self->{_basedir} unless defined $self->{_pid};
+ return;
+}
+
+=pod
+
+=item $node->safe_psql($dbname, $sql) => stdout
+
+Invoke B<psql> to run B<sql> on B<dbname> and return its stdout on success.
+Die if the SQL produces an error. Runs with B<ON_ERROR_STOP> set.
+
+Takes optional extra params like timeout and timed_out parameters with the same
+options as psql.
+
+=cut
+
+sub safe_psql
+{
+ my ($self, $dbname, $sql, %params) = @_;
+
+ local %ENV = $self->_get_env();
+
+ my ($stdout, $stderr);
+
+ my $ret = $self->psql(
+ $dbname, $sql,
+ %params,
+ stdout => \$stdout,
+ stderr => \$stderr,
+ on_error_die => 1,
+ on_error_stop => 1);
+
+ # psql can emit stderr from NOTICEs etc
+ if ($stderr ne "")
+ {
+ print "#### Begin standard error\n";
+ print $stderr;
+ print "\n#### End standard error\n";
+ }
+
+ return $stdout;
+}
+
+=pod
+
+=item $node->psql($dbname, $sql, %params) => psql_retval
+
+Invoke B<psql> to execute B<$sql> on B<$dbname> and return the return value
+from B<psql>, which is run with on_error_stop by default so that it will
+stop running sql and return 3 if the passed SQL results in an error.
+
+As a convenience, if B<psql> is called in array context it returns an
+array containing ($retval, $stdout, $stderr).
+
+psql is invoked in tuples-only unaligned mode with reading of B<.psqlrc>
+disabled. That may be overridden by passing extra psql parameters.
+
+stdout and stderr are transformed to UNIX line endings if on Windows. Any
+trailing newline is removed.
+
+Dies on failure to invoke psql but not if psql exits with a nonzero
+return code (unless on_error_die specified).
+
+If psql exits because of a signal, an exception is raised.
+
+=over
+
+=item stdout => \$stdout
+
+B<stdout>, if given, must be a scalar reference to which standard output is
+written. If not given, standard output is not redirected and will be printed
+unless B<psql> is called in array context, in which case it's captured and
+returned.
+
+=item stderr => \$stderr
+
+Same as B<stdout> but gets standard error. If the same scalar is passed for
+both B<stdout> and B<stderr> the results may be interleaved unpredictably.
+
+=item on_error_stop => 1
+
+By default, the B<psql> method invokes the B<psql> program with ON_ERROR_STOP=1
+set, so SQL execution is stopped at the first error and exit code 3 is
+returned. Set B<on_error_stop> to 0 to ignore errors instead.
+
+=item on_error_die => 0
+
+By default, this method returns psql's result code. Pass on_error_die to
+instead die with an informative message.
+
+=item timeout => 'interval'
+
+Set a timeout for the psql call as an interval accepted by B<IPC::Run::timer>
+(integer seconds is fine). This method raises an exception on timeout, unless
+the B<timed_out> parameter is also given.
+
+=item timed_out => \$timed_out
+
+If B<timeout> is set and this parameter is given, the scalar it references
+is set to true if the psql call times out.
+
+=item connstr => B<value>
+
+If set, use this as the connection string for the connection to the
+backend.
+
+=item replication => B<value>
+
+If set, add B<replication=value> to the conninfo string.
+Passing the literal value C<database> results in a logical replication
+connection.
+
+=item extra_params => ['--single-transaction']
+
+If given, it must be an array reference containing additional parameters to B<psql>.
+
+=back
+
+e.g.
+
+ my ($stdout, $stderr, $timed_out);
+ my $cmdret = $node->psql('postgres', 'SELECT pg_sleep(600)',
+ stdout => \$stdout, stderr => \$stderr,
+ timeout => $TestLib::timeout_default,
+ timed_out => \$timed_out,
+ extra_params => ['--single-transaction'])
+
+will set $cmdret to undef and $timed_out to a true value.
+
+ $node->psql('postgres', $sql, on_error_die => 1);
+
+dies with an informative message if $sql fails.
+
+=cut
+
+sub psql
+{
+ my ($self, $dbname, $sql, %params) = @_;
+
+ local %ENV = $self->_get_env();
+
+ my $stdout = $params{stdout};
+ my $stderr = $params{stderr};
+ my $replication = $params{replication};
+ my $timeout = undef;
+ my $timeout_exception = 'psql timed out';
+
+ # Build the connection string.
+ my $psql_connstr;
+ if (defined $params{connstr})
+ {
+ $psql_connstr = $params{connstr};
+ }
+ else
+ {
+ $psql_connstr = $self->connstr($dbname);
+ }
+ $psql_connstr .= defined $replication ? " replication=$replication" : "";
+
+ my @psql_params = (
+ $self->installed_command('psql'),
+ '-XAtq', '-d', $psql_connstr, '-f', '-');
+
+ # If the caller wants an array and hasn't passed stdout/stderr
+ # references, allocate temporary ones to capture them so we
+ # can return them. Otherwise we won't redirect them at all.
+ if (wantarray)
+ {
+ if (!defined($stdout))
+ {
+ my $temp_stdout = "";
+ $stdout = \$temp_stdout;
+ }
+ if (!defined($stderr))
+ {
+ my $temp_stderr = "";
+ $stderr = \$temp_stderr;
+ }
+ }
+
+ $params{on_error_stop} = 1 unless defined $params{on_error_stop};
+ $params{on_error_die} = 0 unless defined $params{on_error_die};
+
+ push @psql_params, '-v', 'ON_ERROR_STOP=1' if $params{on_error_stop};
+ push @psql_params, @{ $params{extra_params} }
+ if defined $params{extra_params};
+
+ $timeout =
+ IPC::Run::timeout($params{timeout}, exception => $timeout_exception)
+ if (defined($params{timeout}));
+
+ ${ $params{timed_out} } = 0 if defined $params{timed_out};
+
+ # IPC::Run would otherwise append to existing contents:
+ $$stdout = "" if ref($stdout);
+ $$stderr = "" if ref($stderr);
+
+ my $ret;
+
+ # Run psql and capture any possible exceptions. If the exception is
+ # because of a timeout and the caller requested to handle that, just return
+ # and set the flag. Otherwise, and for any other exception, rethrow.
+ #
+ # For background, see
+ # https://metacpan.org/pod/release/ETHER/Try-Tiny-0.24/lib/Try/Tiny.pm
+ do
+ {
+ local $@;
+ eval {
+ my @ipcrun_opts = (\@psql_params, '<', \$sql);
+ push @ipcrun_opts, '>', $stdout if defined $stdout;
+ push @ipcrun_opts, '2>', $stderr if defined $stderr;
+ push @ipcrun_opts, $timeout if defined $timeout;
+
+ IPC::Run::run @ipcrun_opts;
+ $ret = $?;
+ };
+ my $exc_save = $@;
+ if ($exc_save)
+ {
+
+ # IPC::Run::run threw an exception. re-throw unless it's a
+ # timeout, which we'll handle by testing is_expired
+ die $exc_save
+ if (blessed($exc_save)
+ || $exc_save !~ /^\Q$timeout_exception\E/);
+
+ $ret = undef;
+
+ die "Got timeout exception '$exc_save' but timer not expired?!"
+ unless $timeout->is_expired;
+
+ if (defined($params{timed_out}))
+ {
+ ${ $params{timed_out} } = 1;
+ }
+ else
+ {
+ die "psql timed out: stderr: '$$stderr'\n"
+ . "while running '@psql_params'";
+ }
+ }
+ };
+
+ if (defined $$stdout)
+ {
+ chomp $$stdout;
+ }
+
+ if (defined $$stderr)
+ {
+ chomp $$stderr;
+ }
+
+ # See http://perldoc.perl.org/perlvar.html#%24CHILD_ERROR
+ # We don't use IPC::Run::Simple to limit dependencies.
+ #
+ # We always die on signal.
+ my $core = $ret & 128 ? " (core dumped)" : "";
+ die "psql exited with signal "
+ . ($ret & 127)
+ . "$core: '$$stderr' while running '@psql_params'"
+ if $ret & 127;
+ $ret = $ret >> 8;
+
+ if ($ret && $params{on_error_die})
+ {
+ die "psql error: stderr: '$$stderr'\nwhile running '@psql_params'"
+ if $ret == 1;
+ die "connection error: '$$stderr'\nwhile running '@psql_params'"
+ if $ret == 2;
+ die
+ "error running SQL: '$$stderr'\nwhile running '@psql_params' with sql '$sql'"
+ if $ret == 3;
+ die "psql returns $ret: '$$stderr'\nwhile running '@psql_params'";
+ }
+
+ if (wantarray)
+ {
+ return ($ret, $$stdout, $$stderr);
+ }
+ else
+ {
+ return $ret;
+ }
+}
+
+=pod
+
+=item $node->background_psql($dbname, \$stdin, \$stdout, $timer, %params) => harness
+
+Invoke B<psql> on B<$dbname> and return an IPC::Run harness object, which the
+caller may use to send input to B<psql>. The process's stdin is sourced from
+the $stdin scalar reference, and its stdout and stderr go to the $stdout
+scalar reference. This allows the caller to act on other parts of the system
+while idling this backend.
+
+The specified timer object is attached to the harness, as well. It's caller's
+responsibility to set the timeout length (usually
+$TestLib::timeout_default), and to restart the timer after
+each command if the timeout is per-command.
+
+psql is invoked in tuples-only unaligned mode with reading of B<.psqlrc>
+disabled. That may be overridden by passing extra psql parameters.
+
+Dies on failure to invoke psql, or if psql fails to connect. Errors occurring
+later are the caller's problem. psql runs with on_error_stop by default so
+that it will stop running sql and return 3 if passed SQL results in an error.
+
+Be sure to "finish" the harness when done with it.
+
+=over
+
+=item on_error_stop => 1
+
+By default, the B<psql> method invokes the B<psql> program with ON_ERROR_STOP=1
+set, so SQL execution is stopped at the first error and exit code 3 is
+returned. Set B<on_error_stop> to 0 to ignore errors instead.
+
+=item replication => B<value>
+
+If set, add B<replication=value> to the conninfo string.
+Passing the literal value C<database> results in a logical replication
+connection.
+
+=item extra_params => ['--single-transaction']
+
+If given, it must be an array reference containing additional parameters to B<psql>.
+
+=back
+
+=cut
+
+sub background_psql
+{
+ my ($self, $dbname, $stdin, $stdout, $timer, %params) = @_;
+
+ local %ENV = $self->_get_env();
+
+ my $replication = $params{replication};
+
+ my @psql_params = (
+ $self->installed_command('psql'),
+ '-XAtq',
+ '-d',
+ $self->connstr($dbname)
+ . (defined $replication ? " replication=$replication" : ""),
+ '-f',
+ '-');
+
+ $params{on_error_stop} = 1 unless defined $params{on_error_stop};
+
+ push @psql_params, '-v', 'ON_ERROR_STOP=1' if $params{on_error_stop};
+ push @psql_params, @{ $params{extra_params} }
+ if defined $params{extra_params};
+
+ # Ensure there is no data waiting to be sent:
+ $$stdin = "" if ref($stdin);
+ # IPC::Run would otherwise append to existing contents:
+ $$stdout = "" if ref($stdout);
+
+ my $harness = IPC::Run::start \@psql_params,
+ '<', $stdin, '>', $stdout, $timer;
+
+ # Request some output, and pump until we see it. This means that psql
+ # connection failures are caught here, relieving callers of the need to
+ # handle those. (Right now, we have no particularly good handling for
+ # errors anyway, but that might be added later.)
+ my $banner = "background_psql: ready";
+ $$stdin = "\\echo $banner\n";
+ pump $harness until $$stdout =~ /$banner/ || $timer->is_expired;
+
+ die "psql startup timed out" if $timer->is_expired;
+
+ return $harness;
+}
+
+=pod
+
+=item $node->interactive_psql($dbname, \$stdin, \$stdout, $timer, %params) => harness
+
+Invoke B<psql> on B<$dbname> and return an IPC::Run harness object,
+which the caller may use to send interactive input to B<psql>.
+The process's stdin is sourced from the $stdin scalar reference,
+and its stdout and stderr go to the $stdout scalar reference.
+ptys are used so that psql thinks it's being called interactively.
+
+The specified timer object is attached to the harness, as well. It's caller's
+responsibility to set the timeout length (usually
+$TestLib::timeout_default), and to restart the timer after
+each command if the timeout is per-command.
+
+psql is invoked in tuples-only unaligned mode with reading of B<.psqlrc>
+disabled. That may be overridden by passing extra psql parameters.
+
+Dies on failure to invoke psql, or if psql fails to connect.
+Errors occurring later are the caller's problem.
+
+Be sure to "finish" the harness when done with it.
+
+The only extra parameter currently accepted is
+
+=over
+
+=item extra_params => ['--single-transaction']
+
+If given, it must be an array reference containing additional parameters to B<psql>.
+
+=back
+
+This requires IO::Pty in addition to IPC::Run.
+
+=cut
+
+sub interactive_psql
+{
+ my ($self, $dbname, $stdin, $stdout, $timer, %params) = @_;
+
+ local %ENV = $self->_get_env();
+
+ my @psql_params = (
+ $self->installed_command('psql'),
+ '-XAt', '-d', $self->connstr($dbname));
+
+ push @psql_params, @{ $params{extra_params} }
+ if defined $params{extra_params};
+
+ # Ensure there is no data waiting to be sent:
+ $$stdin = "" if ref($stdin);
+ # IPC::Run would otherwise append to existing contents:
+ $$stdout = "" if ref($stdout);
+
+ my $harness = IPC::Run::start \@psql_params,
+ '<pty<', $stdin, '>pty>', $stdout, $timer;
+
+ # Pump until we see psql's help banner. This ensures that callers
+ # won't write anything to the pty before it's ready, avoiding an
+ # implementation issue in IPC::Run. Also, it means that psql
+ # connection failures are caught here, relieving callers of
+ # the need to handle those. (Right now, we have no particularly
+ # good handling for errors anyway, but that might be added later.)
+ pump $harness
+ until $$stdout =~ /Type "help" for help/ || $timer->is_expired;
+
+ die "psql startup timed out" if $timer->is_expired;
+
+ return $harness;
+}
+
+# Common sub of pgbench-invoking interfaces. Makes any requested script files
+# and returns pgbench command-line options causing use of those files.
+sub _pgbench_make_files
+{
+ my ($self, $files) = @_;
+ my @file_opts;
+
+ if (defined $files)
+ {
+
+ # note: files are ordered for determinism
+ for my $fn (sort keys %$files)
+ {
+ my $filename = $self->basedir . '/' . $fn;
+ push @file_opts, '-f', $filename;
+
+ # cleanup file weight
+ $filename =~ s/\@\d+$//;
+
+ #push @filenames, $filename;
+ # filenames are expected to be unique on a test
+ if (-e $filename)
+ {
+ ok(0, "$filename must not already exist");
+ unlink $filename or die "cannot unlink $filename: $!";
+ }
+ TestLib::append_to_file($filename, $$files{$fn});
+ }
+ }
+
+ return @file_opts;
+}
+
+=pod
+
+=item $node->pgbench($opts, $stat, $out, $err, $name, $files, @args)
+
+Invoke B<pgbench>, with parameters and files.
+
+=over
+
+=item $opts
+
+Options as a string to be split on spaces.
+
+=item $stat
+
+Expected exit status.
+
+=item $out
+
+Reference to a regexp list that must match stdout.
+
+=item $err
+
+Reference to a regexp list that must match stderr.
+
+=item $name
+
+Name of test for error messages.
+
+=item $files
+
+Reference to filename/contents dictionary.
+
+=item @args
+
+Further raw options or arguments.
+
+=back
+
+=cut
+
+sub pgbench
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($self, $opts, $stat, $out, $err, $name, $files, @args) = @_;
+ my @cmd = (
+ 'pgbench',
+ split(/\s+/, $opts),
+ $self->_pgbench_make_files($files), @args);
+
+ $self->command_checks_all(\@cmd, $stat, $out, $err, $name);
+}
+
+=pod
+
+=item $node->connect_ok($connstr, $test_name, %params)
+
+Attempt a connection with a custom connection string. This is expected
+to succeed.
+
+=over
+
+=item sql => B<value>
+
+If this parameter is set, this query is used for the connection attempt
+instead of the default.
+
+=item expected_stdout => B<value>
+
+If this regular expression is set, matches it with the output generated.
+
+=item log_like => [ qr/required message/ ]
+
+If given, it must be an array reference containing a list of regular
+expressions that must match against the server log, using
+C<Test::More::like()>.
+
+=item log_unlike => [ qr/prohibited message/ ]
+
+If given, it must be an array reference containing a list of regular
+expressions that must NOT match against the server log. They will be
+passed to C<Test::More::unlike()>.
+
+=back
+
+=cut
+
+sub connect_ok
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($self, $connstr, $test_name, %params) = @_;
+
+ my $sql;
+ if (defined($params{sql}))
+ {
+ $sql = $params{sql};
+ }
+ else
+ {
+ $sql = "SELECT \$\$connected with $connstr\$\$";
+ }
+
+ my (@log_like, @log_unlike);
+ if (defined($params{log_like}))
+ {
+ @log_like = @{ $params{log_like} };
+ }
+ if (defined($params{log_unlike}))
+ {
+ @log_unlike = @{ $params{log_unlike} };
+ }
+
+ my $log_location = -s $self->logfile;
+
+ # Never prompt for a password, any callers of this routine should
+ # have set up things properly, and this should not block.
+ my ($ret, $stdout, $stderr) = $self->psql(
+ 'postgres',
+ $sql,
+ extra_params => ['-w'],
+ connstr => "$connstr",
+ on_error_stop => 0);
+
+ is($ret, 0, $test_name);
+
+ if (defined($params{expected_stdout}))
+ {
+ like($stdout, $params{expected_stdout}, "$test_name: matches");
+ }
+ if (@log_like or @log_unlike)
+ {
+ my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
+
+ while (my $regex = shift @log_like)
+ {
+ like($log_contents, $regex, "$test_name: log matches");
+ }
+ while (my $regex = shift @log_unlike)
+ {
+ unlike($log_contents, $regex, "$test_name: log does not match");
+ }
+ }
+}
+
+=pod
+
+=item $node->connect_fails($connstr, $test_name, %params)
+
+Attempt a connection with a custom connection string. This is expected
+to fail.
+
+=over
+
+=item expected_stderr => B<value>
+
+If this regular expression is set, matches it with the output generated.
+
+=item log_like => [ qr/required message/ ]
+
+=item log_unlike => [ qr/prohibited message/ ]
+
+See C<connect_ok(...)>, above.
+
+=back
+
+=cut
+
+sub connect_fails
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($self, $connstr, $test_name, %params) = @_;
+
+ my (@log_like, @log_unlike);
+ if (defined($params{log_like}))
+ {
+ @log_like = @{ $params{log_like} };
+ }
+ if (defined($params{log_unlike}))
+ {
+ @log_unlike = @{ $params{log_unlike} };
+ }
+
+ my $log_location = -s $self->logfile;
+
+ # Never prompt for a password, any callers of this routine should
+ # have set up things properly, and this should not block.
+ my ($ret, $stdout, $stderr) = $self->psql(
+ 'postgres',
+ undef,
+ extra_params => ['-w'],
+ connstr => "$connstr");
+
+ isnt($ret, 0, $test_name);
+
+ if (defined($params{expected_stderr}))
+ {
+ like($stderr, $params{expected_stderr}, "$test_name: matches");
+ }
+
+ if (@log_like or @log_unlike)
+ {
+ my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
+
+ while (my $regex = shift @log_like)
+ {
+ like($log_contents, $regex, "$test_name: log matches");
+ }
+ while (my $regex = shift @log_unlike)
+ {
+ unlike($log_contents, $regex, "$test_name: log does not match");
+ }
+ }
+}
+
+=pod
+
+=item $node->poll_query_until($dbname, $query [, $expected ])
+
+Run B<$query> repeatedly, until it returns the B<$expected> result
+('t', or SQL boolean true, by default).
+Continues polling if B<psql> returns an error result.
+Times out after $TestLib::timeout_default seconds.
+Returns 1 if successful, 0 if timed out.
+
+=cut
+
+sub poll_query_until
+{
+ my ($self, $dbname, $query, $expected) = @_;
+
+ local %ENV = $self->_get_env();
+
+ $expected = 't' unless defined($expected); # default value
+
+ my $cmd = [
+ $self->installed_command('psql'), '-XAt',
+ '-d', $self->connstr($dbname)
+ ];
+ my ($stdout, $stderr);
+ my $max_attempts = 10 * $TestLib::timeout_default;
+ my $attempts = 0;
+
+ while ($attempts < $max_attempts)
+ {
+ my $result = IPC::Run::run $cmd, '<', \$query,
+ '>', \$stdout, '2>', \$stderr;
+
+ chomp($stdout);
+ chomp($stderr);
+
+ if ($stdout eq $expected && $stderr eq '')
+ {
+ return 1;
+ }
+
+ # Wait 0.1 second before retrying.
+ usleep(100_000);
+
+ $attempts++;
+ }
+
+ # Give up. Print the output from the last attempt, hopefully that's useful
+ # for debugging.
+ diag qq(poll_query_until timed out executing this query:
+$query
+expecting this output:
+$expected
+last actual query output:
+$stdout
+with stderr:
+$stderr);
+ return 0;
+}
+
+=pod
+
+=item $node->command_ok(...)
+
+Runs a shell command like TestLib::command_ok, but with PGHOST and PGPORT set
+so that the command will default to connecting to this PostgresNode.
+
+=cut
+
+sub command_ok
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $self = shift;
+
+ local %ENV = $self->_get_env();
+
+ TestLib::command_ok(@_);
+ return;
+}
+
+=pod
+
+=item $node->command_fails(...)
+
+TestLib::command_fails with our connection parameters. See command_ok(...)
+
+=cut
+
+sub command_fails
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $self = shift;
+
+ local %ENV = $self->_get_env();
+
+ TestLib::command_fails(@_);
+ return;
+}
+
+=pod
+
+=item $node->command_like(...)
+
+TestLib::command_like with our connection parameters. See command_ok(...)
+
+=cut
+
+sub command_like
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $self = shift;
+
+ local %ENV = $self->_get_env();
+
+ TestLib::command_like(@_);
+ return;
+}
+
+=pod
+
+=item $node->command_checks_all(...)
+
+TestLib::command_checks_all with our connection parameters. See
+command_ok(...)
+
+=cut
+
+sub command_checks_all
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $self = shift;
+
+ local %ENV = $self->_get_env();
+
+ TestLib::command_checks_all(@_);
+ return;
+}
+
+=pod
+
+=item $node->issues_sql_like(cmd, expected_sql, test_name)
+
+Run a command on the node, then verify that $expected_sql appears in the
+server log file.
+
+=cut
+
+sub issues_sql_like
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($self, $cmd, $expected_sql, $test_name) = @_;
+
+ local %ENV = $self->_get_env();
+
+ my $log_location = -s $self->logfile;
+
+ my $result = TestLib::run_log($cmd);
+ ok($result, "@$cmd exit code 0");
+ my $log = TestLib::slurp_file($self->logfile, $log_location);
+ like($log, $expected_sql, "$test_name: SQL found in server log");
+ return;
+}
+
+=pod
+
+=item $node->run_log(...)
+
+Runs a shell command like TestLib::run_log, but with connection parameters set
+so that the command will default to connecting to this PostgresNode.
+
+=cut
+
+sub run_log
+{
+ my $self = shift;
+
+ local %ENV = $self->_get_env();
+
+ TestLib::run_log(@_);
+ return;
+}
+
+=pod
+
+=item $node->lsn(mode)
+
+Look up WAL locations on the server:
+
+ * insert location (primary only, error on replica)
+ * write location (primary only, error on replica)
+ * flush location (primary only, error on replica)
+ * receive location (always undef on primary)
+ * replay location (always undef on primary)
+
+mode must be specified.
+
+=cut
+
+sub lsn
+{
+ my ($self, $mode) = @_;
+ my %modes = (
+ 'insert' => 'pg_current_wal_insert_lsn()',
+ 'flush' => 'pg_current_wal_flush_lsn()',
+ 'write' => 'pg_current_wal_lsn()',
+ 'receive' => 'pg_last_wal_receive_lsn()',
+ 'replay' => 'pg_last_wal_replay_lsn()');
+
+ $mode = '<undef>' if !defined($mode);
+ croak "unknown mode for 'lsn': '$mode', valid modes are "
+ . join(', ', keys %modes)
+ if !defined($modes{$mode});
+
+ my $result = $self->safe_psql('postgres', "SELECT $modes{$mode}");
+ chomp($result);
+ if ($result eq '')
+ {
+ return;
+ }
+ else
+ {
+ return $result;
+ }
+}
+
+=pod
+
+=item $node->wait_for_catchup(standby_name, mode, target_lsn)
+
+Wait for the node with application_name standby_name (usually from node->name,
+also works for logical subscriptions)
+until its replication location in pg_stat_replication equals or passes the
+upstream's WAL insert point at the time this function is called. By default
+the replay_lsn is waited for, but 'mode' may be specified to wait for any of
+sent|write|flush|replay. The connection catching up must be in a streaming
+state.
+
+If there is no active replication connection from this peer, waits until
+poll_query_until timeout.
+
+Requires that the 'postgres' db exists and is accessible.
+
+target_lsn may be any arbitrary lsn, but is typically $primary_node->lsn('insert').
+If omitted, pg_current_wal_lsn() is used.
+
+This is not a test. It die()s on failure.
+
+=cut
+
+sub wait_for_catchup
+{
+ my ($self, $standby_name, $mode, $target_lsn) = @_;
+ $mode = defined($mode) ? $mode : 'replay';
+ my %valid_modes =
+ ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1);
+ croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
+ . join(', ', keys(%valid_modes))
+ unless exists($valid_modes{$mode});
+
+ # Allow passing of a PostgresNode instance as shorthand
+ if (blessed($standby_name) && $standby_name->isa("PostgresNode"))
+ {
+ $standby_name = $standby_name->name;
+ }
+ my $lsn_expr;
+ if (defined($target_lsn))
+ {
+ $lsn_expr = "'$target_lsn'";
+ }
+ else
+ {
+ $lsn_expr = 'pg_current_wal_lsn()';
+ }
+ print "Waiting for replication conn "
+ . $standby_name . "'s "
+ . $mode
+ . "_lsn to pass "
+ . $lsn_expr . " on "
+ . $self->name . "\n";
+ my $query =
+ qq[SELECT $lsn_expr <= ${mode}_lsn AND state = 'streaming' FROM pg_catalog.pg_stat_replication WHERE application_name = '$standby_name';];
+ $self->poll_query_until('postgres', $query)
+ or croak "timed out waiting for catchup";
+ print "done\n";
+ return;
+}
+
+=pod
+
+=item $node->wait_for_slot_catchup(slot_name, mode, target_lsn)
+
+Wait for the named replication slot to equal or pass the supplied target_lsn.
+The location used is the restart_lsn unless mode is given, in which case it may
+be 'restart' or 'confirmed_flush'.
+
+Requires that the 'postgres' db exists and is accessible.
+
+This is not a test. It die()s on failure.
+
+If the slot is not active, will time out after poll_query_until's timeout.
+
+target_lsn may be any arbitrary lsn, but is typically $primary_node->lsn('insert').
+
+Note that for logical slots, restart_lsn is held down by the oldest in-progress tx.
+
+=cut
+
+sub wait_for_slot_catchup
+{
+ my ($self, $slot_name, $mode, $target_lsn) = @_;
+ $mode = defined($mode) ? $mode : 'restart';
+ if (!($mode eq 'restart' || $mode eq 'confirmed_flush'))
+ {
+ croak "valid modes are restart, confirmed_flush";
+ }
+ croak 'target lsn must be specified' unless defined($target_lsn);
+ print "Waiting for replication slot "
+ . $slot_name . "'s "
+ . $mode
+ . "_lsn to pass "
+ . $target_lsn . " on "
+ . $self->name . "\n";
+ my $query =
+ qq[SELECT '$target_lsn' <= ${mode}_lsn FROM pg_catalog.pg_replication_slots WHERE slot_name = '$slot_name';];
+ $self->poll_query_until('postgres', $query)
+ or croak "timed out waiting for catchup";
+ print "done\n";
+ return;
+}
+
+=pod
+
+=item $node->wait_for_log(regexp, offset)
+
+Waits for the contents of the server log file, starting at the given offset, to
+match the supplied regular expression. Checks the entire log if no offset is
+given. Times out after $TestLib::timeout_default seconds.
+
+If successful, returns the length of the entire log file, in bytes.
+
+=cut
+
+sub wait_for_log
+{
+ my ($self, $regexp, $offset) = @_;
+ $offset = 0 unless defined $offset;
+
+ my $max_attempts = 10 * $TestLib::timeout_default;
+ my $attempts = 0;
+
+ while ($attempts < $max_attempts)
+ {
+ my $log = TestLib::slurp_file($self->logfile, $offset);
+
+ return $offset+length($log) if ($log =~ m/$regexp/);
+
+ # Wait 0.1 second before retrying.
+ usleep(100_000);
+
+ $attempts++;
+ }
+
+ croak "timed out waiting for match: $regexp";
+}
+
+=pod
+
+=item $node->query_hash($dbname, $query, @columns)
+
+Execute $query on $dbname, replacing any appearance of the string __COLUMNS__
+within the query with a comma-separated list of @columns.
+
+If __COLUMNS__ does not appear in the query, its result columns must EXACTLY
+match the order and number (but not necessarily alias) of supplied @columns.
+
+The query must return zero or one rows.
+
+Return a hash-ref representation of the results of the query, with any empty
+or null results as defined keys with an empty-string value. There is no way
+to differentiate between null and empty-string result fields.
+
+If the query returns zero rows, return a hash with all columns empty. There
+is no way to differentiate between zero rows returned and a row with only
+null columns.
+
+=cut
+
+sub query_hash
+{
+ my ($self, $dbname, $query, @columns) = @_;
+ croak 'calls in array context for multi-row results not supported yet'
+ if (wantarray);
+
+ # Replace __COLUMNS__ if found
+ substr($query, index($query, '__COLUMNS__'), length('__COLUMNS__')) =
+ join(', ', @columns)
+ if index($query, '__COLUMNS__') >= 0;
+ my $result = $self->safe_psql($dbname, $query);
+
+ # hash slice, see http://stackoverflow.com/a/16755894/398670 .
+ #
+ # Fills the hash with empty strings produced by x-operator element
+ # duplication if result is an empty row
+ #
+ my %val;
+ @val{@columns} =
+ $result ne '' ? split(qr/\|/, $result, -1) : ('',) x scalar(@columns);
+ return \%val;
+}
+
+=pod
+
+=item $node->slot(slot_name)
+
+Return hash-ref of replication slot data for the named slot, or a hash-ref with
+all values '' if not found. Does not differentiate between null and empty string
+for fields, no field is ever undef.
+
+The restart_lsn and confirmed_flush_lsn fields are returned verbatim, and also
+as a 2-list of [highword, lowword] integer. Since we rely on Perl 5.8.8 we can't
+"use bigint", it's from 5.20, and we can't assume we have Math::Bigint from CPAN
+either.
+
+=cut
+
+sub slot
+{
+ my ($self, $slot_name) = @_;
+ my @columns = (
+ 'plugin', 'slot_type', 'datoid', 'database',
+ 'active', 'active_pid', 'xmin', 'catalog_xmin',
+ 'restart_lsn');
+ return $self->query_hash(
+ 'postgres',
+ "SELECT __COLUMNS__ FROM pg_catalog.pg_replication_slots WHERE slot_name = '$slot_name'",
+ @columns);
+}
+
+=pod
+
+=item $node->pg_recvlogical_upto(self, dbname, slot_name, endpos, timeout_secs, ...)
+
+Invoke pg_recvlogical to read from slot_name on dbname until LSN endpos, which
+corresponds to pg_recvlogical --endpos. Gives up after timeout (if nonzero).
+
+Disallows pg_recvlogical from internally retrying on error by passing --no-loop.
+
+Plugin options are passed as additional keyword arguments.
+
+If called in scalar context, returns stdout, and die()s on timeout or nonzero return.
+
+If called in array context, returns a tuple of (retval, stdout, stderr, timeout).
+timeout is the IPC::Run::Timeout object whose is_expired method can be tested
+to check for timeout. retval is undef on timeout.
+
+=cut
+
+sub pg_recvlogical_upto
+{
+ my ($self, $dbname, $slot_name, $endpos, $timeout_secs, %plugin_options)
+ = @_;
+
+ local %ENV = $self->_get_env();
+
+ my ($stdout, $stderr);
+
+ my $timeout_exception = 'pg_recvlogical timed out';
+
+ croak 'slot name must be specified' unless defined($slot_name);
+ croak 'endpos must be specified' unless defined($endpos);
+
+ my @cmd = (
+ $self->installed_command('pg_recvlogical'),
+ '-S', $slot_name, '--dbname', $self->connstr($dbname));
+ push @cmd, '--endpos', $endpos;
+ push @cmd, '-f', '-', '--no-loop', '--start';
+
+ while (my ($k, $v) = each %plugin_options)
+ {
+ croak "= is not permitted to appear in replication option name"
+ if ($k =~ qr/=/);
+ push @cmd, "-o", "$k=$v";
+ }
+
+ my $timeout;
+ $timeout =
+ IPC::Run::timeout($timeout_secs, exception => $timeout_exception)
+ if $timeout_secs;
+ my $ret = 0;
+
+ do
+ {
+ local $@;
+ eval {
+ IPC::Run::run(\@cmd, ">", \$stdout, "2>", \$stderr, $timeout);
+ $ret = $?;
+ };
+ my $exc_save = $@;
+ if ($exc_save)
+ {
+
+ # IPC::Run::run threw an exception. re-throw unless it's a
+ # timeout, which we'll handle by testing is_expired
+ die $exc_save
+ if (blessed($exc_save) || $exc_save !~ qr/$timeout_exception/);
+
+ $ret = undef;
+
+ die "Got timeout exception '$exc_save' but timer not expired?!"
+ unless $timeout->is_expired;
+
+ die
+ "$exc_save waiting for endpos $endpos with stdout '$stdout', stderr '$stderr'"
+ unless wantarray;
+ }
+ };
+
+ if (wantarray)
+ {
+ return ($ret, $stdout, $stderr, $timeout);
+ }
+ else
+ {
+ die
+ "pg_recvlogical exited with code '$ret', stdout '$stdout' and stderr '$stderr'"
+ if $ret;
+ return $stdout;
+ }
+}
+
+=pod
+
+=item $node->corrupt_page_checksum(self, file, page_offset)
+
+Intentionally corrupt the checksum field of one page in a file.
+The server must be stopped for this to work reliably.
+
+The file name should be specified relative to the cluster datadir.
+page_offset had better be a multiple of the cluster's block size.
+
+=cut
+
+sub corrupt_page_checksum
+{
+ my ($self, $file, $page_offset) = @_;
+ my $pgdata = $self->data_dir;
+ my $pageheader;
+
+ open my $fh, '+<', "$pgdata/$file" or die "open($file) failed: $!";
+ binmode $fh;
+ sysseek($fh, $page_offset, 0) or die "sysseek failed: $!";
+ sysread($fh, $pageheader, 24) or die "sysread failed: $!";
+ # This inverts the pd_checksum field (only); see struct PageHeaderData
+ $pageheader ^= "\0\0\0\0\0\0\0\0\xff\xff";
+ sysseek($fh, $page_offset, 0) or die "sysseek failed: $!";
+ syswrite($fh, $pageheader) or die "syswrite failed: $!";
+ close $fh;
+
+ return;
+}
+
+=pod
+
+=back
+
+=cut
+
+1;
diff --git a/src/test/perl/PostgresVersion.pm b/src/test/perl/PostgresVersion.pm
new file mode 100644
index 0000000..4e764c3
--- /dev/null
+++ b/src/test/perl/PostgresVersion.pm
@@ -0,0 +1,136 @@
+############################################################################
+#
+# PostgresVersion.pm
+#
+# Module encapsulating Postgres Version numbers
+#
+# Copyright (c) 2021, PostgreSQL Global Development Group
+#
+############################################################################
+
+=pod
+
+=head1 NAME
+
+PostgresVersion - class representing PostgreSQL version numbers
+
+=head1 SYNOPSIS
+
+ use PostgresVersion;
+
+ my $version = PostgresVersion->new($version_arg);
+
+ # compare two versions
+ my $bool = $version1 <= $version2;
+
+ # or compare with a number
+ $bool = $version < 12;
+
+ # or with a string
+ $bool = $version lt "13.1";
+
+ # interpolate in a string
+ my $stringyval = "version: $version";
+
+=head1 DESCRIPTION
+
+PostgresVersion encapsulates Postgres version numbers, providing parsing
+of common version formats and comparison operations.
+
+=cut
+
+package PostgresVersion;
+
+use strict;
+use warnings;
+
+use Scalar::Util qw(blessed);
+
+use overload
+ '<=>' => \&_version_cmp,
+ 'cmp' => \&_version_cmp,
+ '""' => \&_stringify;
+
+=pod
+
+=head1 METHODS
+
+=over
+
+=item PostgresVersion->new($version)
+
+Create a new PostgresVersion instance.
+
+The argument can be a number like 12, or a string like '12.2' or the output
+of a Postgres command like `psql --version` or `pg_config --version`;
+
+=back
+
+=cut
+
+sub new
+{
+ my $class = shift;
+ my $arg = shift;
+
+ chomp $arg;
+
+ # Accept standard formats, in case caller has handed us the output of a
+ # postgres command line tool
+ my $devel;
+ ($arg, $devel) = ($1, $2)
+ if (
+ $arg =~ m!^ # beginning of line
+ (?:\(?PostgreSQL\)?\s)? # ignore PostgreSQL marker
+ (\d+(?:\.\d+)*) # version number, dotted notation
+ (devel|(?:alpha|beta|rc)\d+)? # dev marker - see version_stamp.pl
+ !x);
+
+ # Split into an array
+ my @numbers = split(/\./, $arg);
+
+ # Treat development versions as having a minor/micro version one less than
+ # the first released version of that branch.
+ push @numbers, -1 if ($devel);
+
+ $devel ||= "";
+
+ return bless { str => "$arg$devel", num => \@numbers }, $class;
+}
+
+# Routine which compares the _pg_version_array obtained for the two
+# arguments and returns -1, 0, or 1, allowing comparison between two
+# PostgresVersion objects or a PostgresVersion and a version string or number.
+#
+# If the second argument is not a blessed object we call the constructor
+# to make one.
+#
+# Because we're overloading '<=>' and 'cmp' this function supplies us with
+# all the comparison operators ('<' and friends, 'gt' and friends)
+#
+sub _version_cmp
+{
+ my ($a, $b, $swapped) = @_;
+
+ $b = __PACKAGE__->new($b) unless blessed($b);
+
+ ($a, $b) = ($b, $a) if $swapped;
+
+ my ($an, $bn) = ($a->{num}, $b->{num});
+
+ for (my $idx = 0;; $idx++)
+ {
+ return 0 unless (defined $an->[$idx] && defined $bn->[$idx]);
+ return $an->[$idx] <=> $bn->[$idx]
+ if ($an->[$idx] <=> $bn->[$idx]);
+ }
+}
+
+# Render the version number using the saved string.
+sub _stringify
+{
+ my $self = shift;
+ return $self->{str};
+}
+
+1;
diff --git a/src/test/perl/README b/src/test/perl/README
new file mode 100644
index 0000000..655448c
--- /dev/null
+++ b/src/test/perl/README
@@ -0,0 +1,97 @@
+Perl-based TAP tests
+====================
+
+src/test/perl/ contains shared infrastructure that's used by Perl-based tests
+across the source tree, particularly tests in src/bin and src/test. It's used
+to drive tests for backup and restore, replication, etc - anything that can't
+really be expressed using pg_regress or the isolation test framework.
+
+The tests are invoked via perl's 'prove' command, wrapped in PostgreSQL
+makefiles to handle instance setup etc. See the $(prove_check) and
+$(prove_installcheck) targets in Makefile.global. By default every test in the
+t/ subdirectory is run. Individual test(s) can be run instead by passing
+something like PROVE_TESTS="t/001_testname.pl t/002_othertestname.pl" to make.
+
+You should prefer to write tests using pg_regress in src/test/regress, or
+isolation tester specs in src/test/isolation, if possible. If not, check to
+see if your new tests make sense under an existing tree in src/test, like
+src/test/ssl, or should be added to one of the suites for an existing utility.
+
+Note that all tests and test tools should have perltidy run on them before
+patches are submitted, using perltidy --profile=src/tools/pgindent/perltidyrc
+
+By default, to keep the noise low during runs, we do not set any flags via
+PROVE_FLAGS, but this can be done on the 'make' command line if desired, eg:
+
+make check-world PROVE_FLAGS='--verbose'
+
+Writing tests
+-------------
+
+Tests are written using Perl's Test::More with some PostgreSQL-specific
+infrastructure from src/test/perl providing node management, support for
+invoking 'psql' to run queries and get results, etc. You should read the
+documentation for Test::More before trying to write tests.
+
+Test scripts in the t/ subdirectory of a suite are executed in alphabetical
+order.
+
+Each test script should begin with:
+
+ use strict;
+ use warnings;
+ use PostgresNode;
+ use TestLib;
+ # Replace with the number of tests to execute:
+ use Test::More tests => 1;
+
+then it will generally need to set up one or more nodes, run commands
+against them and evaluate the results. For example:
+
+ my $node = PostgresNode->get_new_node('primary');
+ $node->init;
+ $node->start;
+
+ my $ret = $node->safe_psql('postgres', 'SELECT 1');
+ is($ret, '1', 'SELECT 1 returns 1');
+
+ $node->stop('fast');
+
+Test::More::like entails use of the qr// operator. Avoid Perl 5.8.8 bug
+#39185 by not using the "$" regular expression metacharacter in qr// when also
+using the "/m" modifier. Instead of "$", use "\n" or "(?=\n|\z)".
+
+Test::Builder::Level controls how far up in the call stack a test will look
+at when reporting a failure. This should be incremented by any subroutine
+which directly or indirectly calls test routines from Test::More, such as
+ok() or is():
+
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+Read the documentation for more on how to write tests:
+
+ perldoc Test::More
+ perldoc Test::Builder
+
+For available PostgreSQL-specific test methods and some example tests read the
+perldoc for the test modules, e.g.:
+
+ perldoc src/test/perl/PostgresNode.pm
+
+Required Perl
+-------------
+
+Tests must run on perl 5.8.0 and newer. perlbrew is a good way to obtain such
+a Perl; see http://perlbrew.pl .
+
+Just install and
+
+ perlbrew --force install 5.8.0
+ perlbrew use 5.8.0
+ perlbrew install-cpanm
+ cpanm install IPC::Run
+
+then re-run configure to ensure the correct Perl is used when running
+tests. To verify that the right Perl was found:
+
+ grep ^PERL= config.log
diff --git a/src/test/perl/RecursiveCopy.pm b/src/test/perl/RecursiveCopy.pm
new file mode 100644
index 0000000..8a9cc72
--- /dev/null
+++ b/src/test/perl/RecursiveCopy.pm
@@ -0,0 +1,157 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+=pod
+
+=head1 NAME
+
+RecursiveCopy - simple recursive copy implementation
+
+=head1 SYNOPSIS
+
+use RecursiveCopy;
+
+RecursiveCopy::copypath($from, $to, filterfn => sub { return 1; });
+RecursiveCopy::copypath($from, $to);
+
+=cut
+
+package RecursiveCopy;
+
+use strict;
+use warnings;
+
+use Carp;
+use File::Basename;
+use File::Copy;
+
+=pod
+
+=head1 DESCRIPTION
+
+=head2 copypath($from, $to, %params)
+
+Recursively copy all files and directories from $from to $to.
+Does not preserve file metadata (e.g., permissions).
+
+Only regular files and subdirectories are copied. Trying to copy other types
+of directory entries raises an exception.
+
+Raises an exception if a file would be overwritten, the source directory can't
+be read, or any I/O operation fails. However, we silently ignore ENOENT on
+open, because when copying from a live database it's possible for a file/dir
+to be deleted after we see its directory entry but before we can open it.
+
+Always returns true.
+
+If the B<filterfn> parameter is given, it must be a subroutine reference.
+This subroutine will be called for each entry in the source directory with its
+relative path as only parameter; if the subroutine returns true the entry is
+copied, otherwise the file is skipped.
+
+On failure the target directory may be in some incomplete state; no cleanup is
+attempted.
+
+=head1 EXAMPLES
+
+ RecursiveCopy::copypath('/some/path', '/empty/dir',
+ filterfn => sub {
+ # omit log/ and contents
+ my $src = shift;
+ return $src ne 'log';
+ }
+ );
+
+=cut
+
+sub copypath
+{
+ my ($base_src_dir, $base_dest_dir, %params) = @_;
+ my $filterfn;
+
+ if (defined $params{filterfn})
+ {
+ croak "if specified, filterfn must be a subroutine reference"
+ unless defined(ref $params{filterfn})
+ and (ref $params{filterfn} eq 'CODE');
+
+ $filterfn = $params{filterfn};
+ }
+ else
+ {
+ $filterfn = sub { return 1; };
+ }
+
+ # Complain if original path is bogus, because _copypath_recurse won't.
+ croak "\"$base_src_dir\" does not exist" if !-e $base_src_dir;
+
+ # Start recursive copy from current directory
+ return _copypath_recurse($base_src_dir, $base_dest_dir, "", $filterfn);
+}
+
+# Recursive private guts of copypath
+sub _copypath_recurse
+{
+ my ($base_src_dir, $base_dest_dir, $curr_path, $filterfn) = @_;
+ my $srcpath = "$base_src_dir/$curr_path";
+ my $destpath = "$base_dest_dir/$curr_path";
+
+ # invoke the filter and skip all further operation if it returns false
+ return 1 unless &$filterfn($curr_path);
+
+ # Check for symlink -- needed only on source dir
+ # (note: this will fall through quietly if file is already gone)
+ croak "Cannot operate on symlink \"$srcpath\"" if -l $srcpath;
+
+ # Abort if destination path already exists. Should we allow directories
+ # to exist already?
+ croak "Destination path \"$destpath\" already exists" if -e $destpath;
+
+ # If this source path is a file, simply copy it to destination with the
+ # same name and we're done.
+ if (-f $srcpath)
+ {
+ my $fh;
+ unless (open($fh, '<', $srcpath))
+ {
+ return 1 if ($!{ENOENT});
+ die "open($srcpath) failed: $!";
+ }
+ copy($fh, $destpath)
+ or die "copy $srcpath -> $destpath failed: $!";
+ close $fh;
+ return 1;
+ }
+
+ # If it's a directory, create it on dest and recurse into it.
+ if (-d $srcpath)
+ {
+ my $directory;
+ unless (opendir($directory, $srcpath))
+ {
+ return 1 if ($!{ENOENT});
+ die "opendir($srcpath) failed: $!";
+ }
+
+ mkdir($destpath) or die "mkdir($destpath) failed: $!";
+
+ while (my $entry = readdir($directory))
+ {
+ next if ($entry eq '.' or $entry eq '..');
+ _copypath_recurse($base_src_dir, $base_dest_dir,
+ $curr_path eq '' ? $entry : "$curr_path/$entry", $filterfn)
+ or die "copypath $srcpath/$entry -> $destpath/$entry failed";
+ }
+
+ closedir($directory);
+ return 1;
+ }
+
+ # If it disappeared from sight, that's OK.
+ return 1 if !-e $srcpath;
+
+ # Else it's some weird file type; complain.
+ croak "Source path \"$srcpath\" is not a regular file or directory";
+}
+
+1;
diff --git a/src/test/perl/SimpleTee.pm b/src/test/perl/SimpleTee.pm
new file mode 100644
index 0000000..681a36a
--- /dev/null
+++ b/src/test/perl/SimpleTee.pm
@@ -0,0 +1,35 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# A simple 'tee' implementation, using perl tie.
+#
+# Whenever you print to the handle, it gets forwarded to a list of
+# handles. The list of output filehandles is passed to the constructor.
+#
+# This is similar to IO::Tee, but only used for output. Only the PRINT
+# method is currently implemented; that's all we need. We don't want to
+# depend on IO::Tee just for this.
+
+package SimpleTee;
+use strict;
+use warnings;
+
+sub TIEHANDLE
+{
+ my $self = shift;
+ return bless \@_, $self;
+}
+
+sub PRINT
+{
+ my $self = shift;
+ my $ok = 1;
+ for my $fh (@$self)
+ {
+ print $fh @_ or $ok = 0;
+ $fh->flush or $ok = 0;
+ }
+ return $ok;
+}
+
+1;
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
new file mode 100644
index 0000000..610050e
--- /dev/null
+++ b/src/test/perl/TestLib.pm
@@ -0,0 +1,982 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+=pod
+
+=head1 NAME
+
+TestLib - helper module for writing PostgreSQL's C<prove> tests.
+
+=head1 SYNOPSIS
+
+ use TestLib;
+
+ # Test basic output of a command
+ program_help_ok('initdb');
+ program_version_ok('initdb');
+ program_options_handling_ok('initdb');
+
+ # Test option combinations
+ command_fails(['initdb', '--invalid-option'],
+ 'command fails with invalid option');
+ my $tempdir = TestLib::tempdir;
+ command_ok('initdb', '-D', $tempdir);
+
+ # Miscellanea
+ print "on Windows" if $TestLib::windows_os;
+ ok(check_mode_recursive($stream_dir, 0700, 0600),
+ "check stream dir permissions");
+ TestLib::system_log('pg_ctl', 'kill', 'QUIT', $slow_pid);
+
+=head1 DESCRIPTION
+
+C<TestLib> contains a set of routines dedicated to environment setup for
+a PostgreSQL regression test run and includes some low-level routines
+aimed at controlling command execution, logging and test functions.
+
+=cut
+
+# This module should never depend on any other PostgreSQL regression test
+# modules.
+
+package TestLib;
+
+use strict;
+use warnings;
+
+use Carp;
+use Config;
+use Cwd;
+use Exporter 'import';
+use Fcntl qw(:mode :seek);
+use File::Basename;
+use File::Find;
+use File::Spec;
+use File::stat qw(stat);
+use File::Temp ();
+use IPC::Run;
+use SimpleTee;
+
+# specify a recent enough version of Test::More to support the
+# done_testing() function
+use Test::More 0.87;
+
+our @EXPORT = qw(
+ generate_ascii_string
+ slurp_dir
+ slurp_file
+ append_to_file
+ check_mode_recursive
+ chmod_recursive
+ check_pg_config
+ dir_symlink
+ system_or_bail
+ system_log
+ run_log
+ run_command
+ pump_until
+
+ command_ok
+ command_fails
+ command_exit_is
+ program_help_ok
+ program_version_ok
+ program_options_handling_ok
+ command_like
+ command_like_safe
+ command_fails_like
+ command_checks_all
+
+ $windows_os
+ $is_msys2
+ $use_unix_sockets
+);
+
+our ($windows_os, $is_msys2, $use_unix_sockets, $timeout_default,
+ $tmp_check, $log_path, $test_logfile);
+
+BEGIN
+{
+
+ # Set to untranslated messages, to be able to compare program output
+ # with expected strings.
+ delete $ENV{LANGUAGE};
+ delete $ENV{LC_ALL};
+ $ENV{LC_MESSAGES} = 'C';
+
+ # This list should be kept in sync with pg_regress.c.
+ my @envkeys = qw (
+ PGCHANNELBINDING
+ PGCLIENTENCODING
+ PGCONNECT_TIMEOUT
+ PGDATA
+ PGDATABASE
+ PGGSSENCMODE
+ PGGSSLIB
+ PGHOSTADDR
+ PGKRBSRVNAME
+ PGPASSFILE
+ PGPASSWORD
+ PGREQUIREPEER
+ PGREQUIRESSL
+ PGSERVICE
+ PGSERVICEFILE
+ PGSSLCERT
+ PGSSLCRL
+ PGSSLCRLDIR
+ PGSSLKEY
+ PGSSLMAXPROTOCOLVERSION
+ PGSSLMINPROTOCOLVERSION
+ PGSSLMODE
+ PGSSLROOTCERT
+ PGSSLSNI
+ PGTARGETSESSIONATTRS
+ PGUSER
+ PGPORT
+ PGHOST
+ PG_COLOR
+ );
+ delete @ENV{@envkeys};
+
+ $ENV{PGAPPNAME} = basename($0);
+
+ # Must be set early
+ $windows_os = $Config{osname} eq 'MSWin32' || $Config{osname} eq 'msys';
+ # Check if this environment is MSYS2.
+ $is_msys2 = $windows_os && -x '/usr/bin/uname' &&
+ `uname -or` =~ /^[2-9].*Msys/;
+
+ if ($windows_os)
+ {
+ require Win32API::File;
+ Win32API::File->import(
+ qw(createFile OsFHandleOpen CloseHandle));
+ }
+
+ # Specifies whether to use Unix sockets for test setups. On
+ # Windows we don't use them by default since it's not universally
+ # supported, but it can be overridden if desired.
+ $use_unix_sockets =
+ (!$windows_os || defined $ENV{PG_TEST_USE_UNIX_SOCKETS});
+
+ $timeout_default = $ENV{PG_TEST_TIMEOUT_DEFAULT};
+ $timeout_default = 180
+ if not defined $timeout_default or $timeout_default eq '';
+}
+
+=pod
+
+=head1 EXPORTED VARIABLES
+
+=over
+
+=item C<$windows_os>
+
+Set to true when running under Windows, except on Cygwin.
+
+=item C<$is_msys2>
+
+Set to true when running under MSYS2.
+
+=back
+
+=cut
+
+INIT
+{
+
+ # Return EPIPE instead of killing the process with SIGPIPE. An affected
+ # test may still fail, but it's more likely to report useful facts.
+ $SIG{PIPE} = 'IGNORE';
+
+ # Determine output directories, and create them. The base path is the
+ # TESTDIR environment variable, which is normally set by the invoking
+ # Makefile.
+ $tmp_check = $ENV{TESTDIR} ? "$ENV{TESTDIR}/tmp_check" : "tmp_check";
+ $log_path = "$tmp_check/log";
+
+ mkdir $tmp_check;
+ mkdir $log_path;
+
+ # Open the test log file, whose name depends on the test name.
+ $test_logfile = basename($0);
+ $test_logfile =~ s/\.[^.]+$//;
+ $test_logfile = "$log_path/regress_log_$test_logfile";
+ open my $testlog, '>', $test_logfile
+ or die "could not open STDOUT to logfile \"$test_logfile\": $!";
+
+ # Hijack STDOUT and STDERR to the log file
+ open(my $orig_stdout, '>&', \*STDOUT);
+ open(my $orig_stderr, '>&', \*STDERR);
+ open(STDOUT, '>&', $testlog);
+ open(STDERR, '>&', $testlog);
+
+ # The test output (ok ...) needs to be printed to the original STDOUT so
+ # that the 'prove' program can parse it, and display it to the user in
+ # real time. But also copy it to the log file, to provide more context
+ # in the log.
+ my $builder = Test::More->builder;
+ my $fh = $builder->output;
+ tie *$fh, "SimpleTee", $orig_stdout, $testlog;
+ $fh = $builder->failure_output;
+ tie *$fh, "SimpleTee", $orig_stderr, $testlog;
+
+ # Enable auto-flushing for all the file handles. Stderr and stdout are
+ # redirected to the same file, and buffering causes the lines to appear
+ # in the log in confusing order.
+ autoflush STDOUT 1;
+ autoflush STDERR 1;
+ autoflush $testlog 1;
+}
+
+END
+{
+
+ # Test files have several ways of causing prove_check to fail:
+ # 1. Exit with a non-zero status.
+ # 2. Call ok(0) or similar, indicating that a constituent test failed.
+ # 3. Deviate from the planned number of tests.
+ #
+ # Preserve temporary directories after (1) and after (2).
+ $File::Temp::KEEP_ALL = 1 unless $? == 0 && all_tests_passing();
+}
+
+=pod
+
+=head1 ROUTINES
+
+=over
+
+=item all_tests_passing()
+
+Return 1 if all the tests run so far have passed. Otherwise, return 0.
+
+=cut
+
+sub all_tests_passing
+{
+ foreach my $status (Test::More->builder->summary)
+ {
+ return 0 unless $status;
+ }
+ return 1;
+}
+
+=pod
+
+=item tempdir(prefix)
+
+Securely create a temporary directory inside C<$tmp_check>, like C<mkdtemp>,
+and return its name. The directory will be removed automatically at the
+end of the tests.
+
+If C<prefix> is given, the new directory is templated as C<${prefix}_XXXX>.
+Otherwise the template is C<tmp_test_XXXX>.
+
+=cut
+
+sub tempdir
+{
+ my ($prefix) = @_;
+ $prefix = "tmp_test" unless defined $prefix;
+ return File::Temp::tempdir(
+ $prefix . '_XXXX',
+ DIR => $tmp_check,
+ CLEANUP => 1);
+}
+
+=pod
+
+=item tempdir_short()
+
+As above, but the directory is outside the build tree so that it has a short
+name, to avoid path length issues.
+
+=cut
+
+sub tempdir_short
+{
+
+ return File::Temp::tempdir(CLEANUP => 1);
+}
+
+=pod
+
+=item has_wal_read_bug()
+
+Returns true if $tmp_check is subject to a sparc64+ext4 bug that causes WAL
+readers to see zeros if another process simultaneously wrote the same offsets.
+Consult this in tests that fail frequently on affected configurations. The
+bug has made streaming standbys fail to advance, reporting corrupt WAL. It
+has made COMMIT PREPARED fail with "could not read two-phase state from WAL".
+Non-WAL PostgreSQL reads haven't been affected, likely because those readers
+and writers have buffering systems in common. See
+https://postgr.es/m/20220116210241.GC756210@rfd.leadboat.com for details.
+
+=cut
+
+sub has_wal_read_bug
+{
+ return
+ $Config{osname} eq 'linux'
+ && $Config{archname} =~ /^sparc/
+ && !run_log([ qw(df -x ext4), $tmp_check ], '>', '/dev/null', '2>&1');
+}
+
+=pod
+
+=item system_log(@cmd)
+
+Run (via C<system()>) the command passed as argument; the return
+value is passed through.
+
+=cut
+
+sub system_log
+{
+ print("# Running: " . join(" ", @_) . "\n");
+ return system(@_);
+}
+
+=pod
+
+=item system_or_bail(@cmd)
+
+Run (via C<system()>) the command passed as argument, and returns
+if the command is successful.
+On failure, abandon further tests and exit the program.
+
+=cut
+
+sub system_or_bail
+{
+ if (system_log(@_) != 0)
+ {
+ BAIL_OUT("system $_[0] failed");
+ }
+ return;
+}
+
+=pod
+
+=item run_log(@cmd)
+
+Run the given command via C<IPC::Run::run()>, noting it in the log.
+The return value from the command is passed through.
+
+=cut
+
+sub run_log
+{
+ print("# Running: " . join(" ", @{ $_[0] }) . "\n");
+ return IPC::Run::run(@_);
+}
+
+=pod
+
+=item run_command(cmd)
+
+Run (via C<IPC::Run::run()>) the command passed as argument.
+The return value from the command is ignored.
+The return value is C<($stdout, $stderr)>.
+
+=cut
+
+sub run_command
+{
+ my ($cmd) = @_;
+ my ($stdout, $stderr);
+ my $result = IPC::Run::run $cmd, '>', \$stdout, '2>', \$stderr;
+ chomp($stdout);
+ chomp($stderr);
+ return ($stdout, $stderr);
+}
+
+=pod
+
+=item pump_until(proc, timeout, stream, until)
+
+Pump until string is matched on the specified stream, or timeout occurs.
+
+=cut
+
+sub pump_until
+{
+ my ($proc, $timeout, $stream, $until) = @_;
+ $proc->pump_nb();
+ while (1)
+ {
+ last if $$stream =~ /$until/;
+ if ($timeout->is_expired)
+ {
+ diag("pump_until: timeout expired when searching for \"$until\" with stream: \"$$stream\"");
+ return 0;
+ }
+ if (not $proc->pumpable())
+ {
+ diag("pump_until: process terminated unexpectedly when searching for \"$until\" with stream: \"$$stream\"");
+ return 0;
+ }
+ $proc->pump();
+ }
+ return 1;
+}
+
+=pod
+
+=item generate_ascii_string(from_char, to_char)
+
+Generate a string made of the given range of ASCII characters.
+
+=cut
+
+sub generate_ascii_string
+{
+ my ($from_char, $to_char) = @_;
+ my $res;
+
+ for my $i ($from_char .. $to_char)
+ {
+ $res .= sprintf("%c", $i);
+ }
+ return $res;
+}
+
+=pod
+
+=item slurp_dir(dir)
+
+Return the complete list of entries in the specified directory.
+
+=cut
+
+sub slurp_dir
+{
+ my ($dir) = @_;
+ opendir(my $dh, $dir)
+ or croak "could not opendir \"$dir\": $!";
+ my @direntries = readdir $dh;
+ closedir $dh;
+ return @direntries;
+}
+
+=pod
+
+=item slurp_file(filename [, $offset])
+
+Return the full contents of the specified file, beginning from an
+offset position if specified.
+
+=cut
+
+sub slurp_file
+{
+ my ($filename, $offset) = @_;
+ local $/;
+ my $contents;
+ my $fh;
+
+ # On windows open file using win32 APIs, to allow us to set the
+ # FILE_SHARE_DELETE flag ("d" below), otherwise other accesses to the file
+ # may fail.
+ if ($Config{osname} ne 'MSWin32')
+ {
+ open($fh, '<', $filename)
+ or croak "could not read \"$filename\": $!";
+ }
+ else
+ {
+ my $fHandle = createFile($filename, "r", "rwd")
+ or croak "could not open \"$filename\": $^E";
+ OsFHandleOpen($fh = IO::Handle->new(), $fHandle, 'r')
+ or croak "could not read \"$filename\": $^E\n";
+ }
+
+ if (defined($offset))
+ {
+ seek($fh, $offset, SEEK_SET)
+ or croak "could not seek \"$filename\": $!";
+ }
+
+ $contents = <$fh>;
+ close $fh;
+
+ return $contents;
+}
+
+=pod
+
+=item append_to_file(filename, str)
+
+Append a string at the end of a given file. (Note: no newline is appended at
+end of file.)
+
+=cut
+
+sub append_to_file
+{
+ my ($filename, $str) = @_;
+ open my $fh, ">>", $filename
+ or croak "could not write \"$filename\": $!";
+ print $fh $str;
+ close $fh;
+ return;
+}
+
+=pod
+
+=item check_mode_recursive(dir, expected_dir_mode, expected_file_mode, ignore_list)
+
+Check that all file/dir modes in a directory match the expected values,
+ignoring files in C<ignore_list> (basename only).
+
+=cut
+
+sub check_mode_recursive
+{
+ my ($dir, $expected_dir_mode, $expected_file_mode, $ignore_list) = @_;
+
+ # Result defaults to true
+ my $result = 1;
+
+ find(
+ {
+ follow_fast => 1,
+ wanted => sub {
+ # Is file in the ignore list?
+ foreach my $ignore ($ignore_list ? @{$ignore_list} : [])
+ {
+ if ("$dir/$ignore" eq $File::Find::name)
+ {
+ return;
+ }
+ }
+
+ # Allow ENOENT. A running server can delete files, such as
+ # those in pg_stat. Other stat() failures are fatal.
+ my $file_stat = stat($File::Find::name);
+ unless (defined($file_stat))
+ {
+ my $is_ENOENT = $!{ENOENT};
+ my $msg = "unable to stat $File::Find::name: $!";
+ if ($is_ENOENT)
+ {
+ warn $msg;
+ return;
+ }
+ else
+ {
+ die $msg;
+ }
+ }
+
+ my $file_mode = S_IMODE($file_stat->mode);
+
+ # Is this a file?
+ if (S_ISREG($file_stat->mode))
+ {
+ if ($file_mode != $expected_file_mode)
+ {
+ print(
+ *STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_file_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+
+ # Else a directory?
+ elsif (S_ISDIR($file_stat->mode))
+ {
+ if ($file_mode != $expected_dir_mode)
+ {
+ print(
+ *STDERR,
+ sprintf("$File::Find::name mode must be %04o\n",
+ $expected_dir_mode));
+
+ $result = 0;
+ return;
+ }
+ }
+
+ # Else something we can't handle
+ else
+ {
+ die "unknown file type for $File::Find::name";
+ }
+ }
+ },
+ $dir);
+
+ return $result;
+}
+
+=pod
+
+=item chmod_recursive(dir, dir_mode, file_mode)
+
+C<chmod> recursively each file and directory within the given directory.
+
+=cut
+
+sub chmod_recursive
+{
+ my ($dir, $dir_mode, $file_mode) = @_;
+
+ find(
+ {
+ follow_fast => 1,
+ wanted => sub {
+ my $file_stat = stat($File::Find::name);
+
+ if (defined($file_stat))
+ {
+ chmod(
+ S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+ $File::Find::name
+ ) or die "unable to chmod $File::Find::name";
+ }
+ }
+ },
+ $dir);
+ return;
+}
+
+=pod
+
+=item check_pg_config(regexp)
+
+Return the number of matches of the given regular expression
+within the installation's C<pg_config.h>.
+
+=cut
+
+sub check_pg_config
+{
+ my ($regexp) = @_;
+ my ($stdout, $stderr);
+ my $result = IPC::Run::run [ 'pg_config', '--includedir' ], '>',
+ \$stdout, '2>', \$stderr
+ or die "could not execute pg_config";
+ chomp($stdout);
+ $stdout =~ s/\r$//;
+
+ open my $pg_config_h, '<', "$stdout/pg_config.h" or die "$!";
+ my $match = (grep { /^$regexp/ } <$pg_config_h>);
+ close $pg_config_h;
+ return $match;
+}
+
+=pod
+
+=item dir_symlink(oldname, newname)
+
+Portably create a symlink for a directory. On Windows this creates a junction
+point. Elsewhere it just calls perl's builtin symlink.
+
+=cut
+
+sub dir_symlink
+{
+ my $oldname = shift;
+ my $newname = shift;
+ if ($windows_os)
+ {
+ $oldname =~ s,/,\\,g;
+ $newname =~ s,/,\\,g;
+ my $cmd = qq{mklink /j "$newname" "$oldname"};
+ if ($Config{osname} eq 'msys')
+ {
+ # need some indirection on msys
+ $cmd = qq{echo '$cmd' | \$COMSPEC /Q};
+ }
+ system($cmd);
+ }
+ else
+ {
+ symlink $oldname, $newname;
+ }
+ die "No $newname" unless -e $newname;
+}
+
+=pod
+
+=back
+
+=head1 Test::More-LIKE METHODS
+
+=over
+
+=item command_ok(cmd, test_name)
+
+Check that the command runs (via C<run_log>) successfully.
+
+=cut
+
+sub command_ok
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($cmd, $test_name) = @_;
+ my $result = run_log($cmd);
+ ok($result, $test_name);
+ return;
+}
+
+=pod
+
+=item command_fails(cmd, test_name)
+
+Check that the command fails (when run via C<run_log>).
+
+=cut
+
+sub command_fails
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($cmd, $test_name) = @_;
+ my $result = run_log($cmd);
+ ok(!$result, $test_name);
+ return;
+}
+
+=pod
+
+=item command_exit_is(cmd, expected, test_name)
+
+Check that the command exit code matches the expected exit code.
+
+=cut
+
+sub command_exit_is
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($cmd, $expected, $test_name) = @_;
+ print("# Running: " . join(" ", @{$cmd}) . "\n");
+ my $h = IPC::Run::start $cmd;
+ $h->finish();
+
+ # On Windows, the exit status of the process is returned directly as the
+ # process's exit code, while on Unix, it's returned in the high bits
+ # of the exit code (see WEXITSTATUS macro in the standard <sys/wait.h>
+ # header file). IPC::Run's result function always returns exit code >> 8,
+ # assuming the Unix convention, which will always return 0 on Windows as
+ # long as the process was not terminated by an exception. To work around
+ # that, use $h->full_results on Windows instead.
+ my $result =
+ ($Config{osname} eq "MSWin32")
+ ? ($h->full_results)[0]
+ : $h->result(0);
+ is($result, $expected, $test_name);
+ return;
+}
+
+=pod
+
+=item program_help_ok(cmd)
+
+Check that the command supports the C<--help> option.
+
+=cut
+
+sub program_help_ok
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($cmd) = @_;
+ my ($stdout, $stderr);
+ print("# Running: $cmd --help\n");
+ my $result = IPC::Run::run [ $cmd, '--help' ], '>', \$stdout, '2>',
+ \$stderr;
+ ok($result, "$cmd --help exit code 0");
+ isnt($stdout, '', "$cmd --help goes to stdout");
+ is($stderr, '', "$cmd --help nothing to stderr");
+ return;
+}
+
+=pod
+
+=item program_version_ok(cmd)
+
+Check that the command supports the C<--version> option.
+
+=cut
+
+sub program_version_ok
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($cmd) = @_;
+ my ($stdout, $stderr);
+ print("# Running: $cmd --version\n");
+ my $result = IPC::Run::run [ $cmd, '--version' ], '>', \$stdout, '2>',
+ \$stderr;
+ ok($result, "$cmd --version exit code 0");
+ isnt($stdout, '', "$cmd --version goes to stdout");
+ is($stderr, '', "$cmd --version nothing to stderr");
+ return;
+}
+
+=pod
+
+=item program_options_handling_ok(cmd)
+
+Check that a command with an invalid option returns a non-zero
+exit code and error message.
+
+=cut
+
+sub program_options_handling_ok
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($cmd) = @_;
+ my ($stdout, $stderr);
+ print("# Running: $cmd --not-a-valid-option\n");
+ my $result = IPC::Run::run [ $cmd, '--not-a-valid-option' ], '>',
+ \$stdout,
+ '2>', \$stderr;
+ ok(!$result, "$cmd with invalid option nonzero exit code");
+ isnt($stderr, '', "$cmd with invalid option prints error message");
+ return;
+}
+
+=pod
+
+=item command_like(cmd, expected_stdout, test_name)
+
+Check that the command runs successfully and the output
+matches the given regular expression.
+
+=cut
+
+sub command_like
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($cmd, $expected_stdout, $test_name) = @_;
+ my ($stdout, $stderr);
+ print("# Running: " . join(" ", @{$cmd}) . "\n");
+ my $result = IPC::Run::run $cmd, '>', \$stdout, '2>', \$stderr;
+ ok($result, "$test_name: exit code 0");
+ is($stderr, '', "$test_name: no stderr");
+ like($stdout, $expected_stdout, "$test_name: matches");
+ return;
+}
+
+=pod
+
+=item command_like_safe(cmd, expected_stdout, test_name)
+
+Check that the command runs successfully and the output
+matches the given regular expression. Doesn't assume that the
+output files are closed.
+
+=cut
+
+sub command_like_safe
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ # Doesn't rely on detecting end of file on the file descriptors,
+ # which can fail, causing the process to hang, notably on Msys
+ # when used with 'pg_ctl start'
+ my ($cmd, $expected_stdout, $test_name) = @_;
+ my ($stdout, $stderr);
+ my $stdoutfile = File::Temp->new();
+ my $stderrfile = File::Temp->new();
+ print("# Running: " . join(" ", @{$cmd}) . "\n");
+ my $result = IPC::Run::run $cmd, '>', $stdoutfile, '2>', $stderrfile;
+ $stdout = slurp_file($stdoutfile);
+ $stderr = slurp_file($stderrfile);
+ ok($result, "$test_name: exit code 0");
+ is($stderr, '', "$test_name: no stderr");
+ like($stdout, $expected_stdout, "$test_name: matches");
+ return;
+}
+
+=pod
+
+=item command_fails_like(cmd, expected_stderr, test_name)
+
+Check that the command fails and the error message matches
+the given regular expression.
+
+=cut
+
+sub command_fails_like
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($cmd, $expected_stderr, $test_name) = @_;
+ my ($stdout, $stderr);
+ print("# Running: " . join(" ", @{$cmd}) . "\n");
+ my $result = IPC::Run::run $cmd, '>', \$stdout, '2>', \$stderr;
+ ok(!$result, "$test_name: exit code not 0");
+ like($stderr, $expected_stderr, "$test_name: matches");
+ return;
+}
+
+=pod
+
+=item command_checks_all(cmd, ret, out, err, test_name)
+
+Run a command and check its status and outputs.
+Arguments:
+
+=over
+
+=item C<cmd>: Array reference of command and arguments to run
+
+=item C<ret>: Expected exit code
+
+=item C<out>: Expected stdout from command
+
+=item C<err>: Expected stderr from command
+
+=item C<test_name>: test name
+
+=back
+
+=cut
+
+sub command_checks_all
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($cmd, $expected_ret, $out, $err, $test_name) = @_;
+
+ # run command
+ my ($stdout, $stderr);
+ print("# Running: " . join(" ", @{$cmd}) . "\n");
+ IPC::Run::run($cmd, '>', \$stdout, '2>', \$stderr);
+
+ # See http://perldoc.perl.org/perlvar.html#%24CHILD_ERROR
+ my $ret = $?;
+ die "command exited with signal " . ($ret & 127)
+ if $ret & 127;
+ $ret = $ret >> 8;
+
+ # check status
+ ok($ret == $expected_ret,
+ "$test_name status (got $ret vs expected $expected_ret)");
+
+ # check stdout
+ for my $re (@$out)
+ {
+ like($stdout, $re, "$test_name stdout /$re/");
+ }
+
+ # check stderr
+ for my $re (@$err)
+ {
+ like($stderr, $re, "$test_name stderr /$re/");
+ }
+
+ return;
+}
+
+=pod
+
+=back
+
+=cut
+
+1;
diff --git a/src/test/recovery/.gitignore b/src/test/recovery/.gitignore
new file mode 100644
index 0000000..871e943
--- /dev/null
+++ b/src/test/recovery/.gitignore
@@ -0,0 +1,2 @@
+# Generated by test suite
+/tmp_check/
diff --git a/src/test/recovery/Makefile b/src/test/recovery/Makefile
new file mode 100644
index 0000000..288c04b
--- /dev/null
+++ b/src/test/recovery/Makefile
@@ -0,0 +1,29 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/recovery
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/recovery/Makefile
+#
+#-------------------------------------------------------------------------
+
+EXTRA_INSTALL=contrib/test_decoding
+
+subdir = src/test/recovery
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+# required for 017_shm.pl
+REGRESS_SHLIB=$(abs_top_builddir)/src/test/regress/regress$(DLSUFFIX)
+export REGRESS_SHLIB
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
diff --git a/src/test/recovery/README b/src/test/recovery/README
new file mode 100644
index 0000000..632e720
--- /dev/null
+++ b/src/test/recovery/README
@@ -0,0 +1,25 @@
+src/test/recovery/README
+
+Regression tests for recovery and replication
+=============================================
+
+This directory contains a test suite for recovery and replication.
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+Also, to use "make installcheck", you must have built and installed
+contrib/test_decoding in addition to the core code.
+
+Run
+ make check
+or
+ make installcheck
+You can use "make installcheck" if you previously did "make install".
+In that case, the code in the installation tree is tested. With
+"make check", a temporary installation tree is built from the current
+sources and then tested.
+
+Either way, this test initializes, starts, and stops several test Postgres
+clusters.
diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl
new file mode 100644
index 0000000..2a1bc81
--- /dev/null
+++ b/src/test/recovery/t/001_stream_rep.pl
@@ -0,0 +1,486 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Minimal test testing streaming replication
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 49;
+
+# Initialize primary node
+my $node_primary = get_new_node('primary');
+# A specific role is created to perform some tests related to replication,
+# and it needs proper authentication configuration.
+$node_primary->init(
+ allows_streaming => 1,
+ auth_extra => [ '--create-role', 'repl_role' ]);
+$node_primary->start;
+my $backup_name = 'my_backup';
+
+# Take backup
+$node_primary->backup($backup_name);
+
+# Create streaming standby linking to primary
+my $node_standby_1 = get_new_node('standby_1');
+$node_standby_1->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+$node_standby_1->start;
+
+# Take backup of standby 1 (not mandatory, but useful to check if
+# pg_basebackup works on a standby).
+$node_standby_1->backup($backup_name);
+
+# Take a second backup of the standby while the primary is offline.
+$node_primary->stop;
+$node_standby_1->backup('my_backup_2');
+$node_primary->start;
+
+# Create second standby node linking to standby 1
+my $node_standby_2 = get_new_node('standby_2');
+$node_standby_2->init_from_backup($node_standby_1, $backup_name,
+ has_streaming => 1);
+$node_standby_2->start;
+
+# Create some content on primary and check its presence in standby 1
+$node_primary->safe_psql('postgres',
+ "CREATE TABLE tab_int AS SELECT generate_series(1,1002) AS a");
+
+# Wait for standbys to catch up
+$node_primary->wait_for_catchup($node_standby_1, 'replay',
+ $node_primary->lsn('insert'));
+$node_standby_1->wait_for_catchup($node_standby_2, 'replay',
+ $node_standby_1->lsn('replay'));
+
+my $result =
+ $node_standby_1->safe_psql('postgres', "SELECT count(*) FROM tab_int");
+print "standby 1: $result\n";
+is($result, qq(1002), 'check streamed content on standby 1');
+
+$result =
+ $node_standby_2->safe_psql('postgres', "SELECT count(*) FROM tab_int");
+print "standby 2: $result\n";
+is($result, qq(1002), 'check streamed content on standby 2');
+
+# Check that only READ-only queries can run on standbys
+is($node_standby_1->psql('postgres', 'INSERT INTO tab_int VALUES (1)'),
+ 3, 'read-only queries on standby 1');
+is($node_standby_2->psql('postgres', 'INSERT INTO tab_int VALUES (1)'),
+ 3, 'read-only queries on standby 2');
+
+# Tests for connection parameter target_session_attrs
+note "testing connection parameter \"target_session_attrs\"";
+
+# Attempt to connect to $node1, then $node2, using target_session_attrs=$mode.
+# Expect to connect to $target_node (undef for failure) with given $status.
+sub test_target_session_attrs
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $node1 = shift;
+ my $node2 = shift;
+ my $target_node = shift;
+ my $mode = shift;
+ my $status = shift;
+
+ my $node1_host = $node1->host;
+ my $node1_port = $node1->port;
+ my $node1_name = $node1->name;
+ my $node2_host = $node2->host;
+ my $node2_port = $node2->port;
+ my $node2_name = $node2->name;
+ my $target_port = undef;
+ $target_port = $target_node->port if (defined $target_node);
+ my $target_name = undef;
+ $target_name = $target_node->name if (defined $target_node);
+
+ # Build connection string for connection attempt.
+ my $connstr = "host=$node1_host,$node2_host ";
+ $connstr .= "port=$node1_port,$node2_port ";
+ $connstr .= "target_session_attrs=$mode";
+
+ # Attempt to connect, and if successful, get the server port number
+ # we connected to. Note we must pass the SQL command via the command
+ # line not stdin, else Perl may spit up trying to write to stdin of
+ # an already-failed psql process.
+ my ($ret, $stdout, $stderr) =
+ $node1->psql('postgres', undef,
+ extra_params => [ '-d', $connstr, '-c', 'SHOW port;' ]);
+ if ($status == 0)
+ {
+ is( $status == $ret && $stdout eq $target_port,
+ 1,
+ "connect to node $target_name with mode \"$mode\" and $node1_name,$node2_name listed"
+ );
+ }
+ else
+ {
+ print "status = $status\n";
+ print "ret = $ret\n";
+ print "stdout = $stdout\n";
+ print "stderr = $stderr\n";
+
+ is( $status == $ret && !defined $target_node,
+ 1,
+ "fail to connect with mode \"$mode\" and $node1_name,$node2_name listed"
+ );
+ }
+
+ return;
+}
+
+# Connect to primary in "read-write" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_primary,
+ "read-write", 0);
+
+# Connect to primary in "read-write" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_primary,
+ "read-write", 0);
+
+# Connect to primary in "any" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_primary,
+ "any", 0);
+
+# Connect to standby1 in "any" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
+ "any", 0);
+
+# Connect to primary in "primary" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_primary,
+ "primary", 0);
+
+# Connect to primary in "primary" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_primary,
+ "primary", 0);
+
+# Connect to standby1 in "read-only" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
+ "read-only", 0);
+
+# Connect to standby1 in "read-only" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
+ "read-only", 0);
+
+# Connect to primary in "prefer-standby" mode with primary,primary list.
+test_target_session_attrs($node_primary, $node_primary, $node_primary,
+ "prefer-standby", 0);
+
+# Connect to standby1 in "prefer-standby" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
+ "prefer-standby", 0);
+
+# Connect to standby1 in "prefer-standby" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
+ "prefer-standby", 0);
+
+# Connect to standby1 in "standby" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
+ "standby", 0);
+
+# Connect to standby1 in "standby" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
+ "standby", 0);
+
+# Fail to connect in "read-write" mode with standby1,standby2 list.
+test_target_session_attrs($node_standby_1, $node_standby_2, undef,
+ "read-write", 2);
+
+# Fail to connect in "primary" mode with standby1,standby2 list.
+test_target_session_attrs($node_standby_1, $node_standby_2, undef,
+ "primary", 2);
+
+# Fail to connect in "read-only" mode with primary,primary list.
+test_target_session_attrs($node_primary, $node_primary, undef,
+ "read-only", 2);
+
+# Fail to connect in "standby" mode with primary,primary list.
+test_target_session_attrs($node_primary, $node_primary, undef, "standby", 2);
+
+# Test for SHOW commands using a WAL sender connection with a replication
+# role.
+note "testing SHOW commands for replication connection";
+
+$node_primary->psql(
+ 'postgres', "
+CREATE ROLE repl_role REPLICATION LOGIN;
+GRANT pg_read_all_settings TO repl_role;");
+my $primary_host = $node_primary->host;
+my $primary_port = $node_primary->port;
+my $connstr_common = "host=$primary_host port=$primary_port user=repl_role";
+my $connstr_rep = "$connstr_common replication=1";
+my $connstr_db = "$connstr_common replication=database dbname=postgres";
+
+# Test SHOW ALL
+my ($ret, $stdout, $stderr) = $node_primary->psql(
+ 'postgres', 'SHOW ALL;',
+ on_error_die => 1,
+ extra_params => [ '-d', $connstr_rep ]);
+ok($ret == 0, "SHOW ALL with replication role and physical replication");
+($ret, $stdout, $stderr) = $node_primary->psql(
+ 'postgres', 'SHOW ALL;',
+ on_error_die => 1,
+ extra_params => [ '-d', $connstr_db ]);
+ok($ret == 0, "SHOW ALL with replication role and logical replication");
+
+# Test SHOW with a user-settable parameter
+($ret, $stdout, $stderr) = $node_primary->psql(
+ 'postgres', 'SHOW work_mem;',
+ on_error_die => 1,
+ extra_params => [ '-d', $connstr_rep ]);
+ok( $ret == 0,
+ "SHOW with user-settable parameter, replication role and physical replication"
+);
+($ret, $stdout, $stderr) = $node_primary->psql(
+ 'postgres', 'SHOW work_mem;',
+ on_error_die => 1,
+ extra_params => [ '-d', $connstr_db ]);
+ok( $ret == 0,
+ "SHOW with user-settable parameter, replication role and logical replication"
+);
+
+# Test SHOW with a superuser-settable parameter
+($ret, $stdout, $stderr) = $node_primary->psql(
+ 'postgres', 'SHOW primary_conninfo;',
+ on_error_die => 1,
+ extra_params => [ '-d', $connstr_rep ]);
+ok( $ret == 0,
+ "SHOW with superuser-settable parameter, replication role and physical replication"
+);
+($ret, $stdout, $stderr) = $node_primary->psql(
+ 'postgres', 'SHOW primary_conninfo;',
+ on_error_die => 1,
+ extra_params => [ '-d', $connstr_db ]);
+ok( $ret == 0,
+ "SHOW with superuser-settable parameter, replication role and logical replication"
+);
+
+note "switching to physical replication slot";
+
+# Switch to using a physical replication slot. We can do this without a new
+# backup since physical slots can go backwards if needed. Do so on both
+# standbys. Since we're going to be testing things that affect the slot state,
+# also increase the standby feedback interval to ensure timely updates.
+my ($slotname_1, $slotname_2) = ('standby_1', 'standby_2');
+$node_primary->append_conf('postgresql.conf', "max_replication_slots = 4");
+$node_primary->restart;
+is( $node_primary->psql(
+ 'postgres',
+ qq[SELECT pg_create_physical_replication_slot('$slotname_1');]),
+ 0,
+ 'physical slot created on primary');
+$node_standby_1->append_conf('postgresql.conf',
+ "primary_slot_name = $slotname_1");
+$node_standby_1->append_conf('postgresql.conf',
+ "wal_receiver_status_interval = 1");
+$node_standby_1->append_conf('postgresql.conf', "max_replication_slots = 4");
+$node_standby_1->restart;
+is( $node_standby_1->psql(
+ 'postgres',
+ qq[SELECT pg_create_physical_replication_slot('$slotname_2');]),
+ 0,
+ 'physical slot created on intermediate replica');
+$node_standby_2->append_conf('postgresql.conf',
+ "primary_slot_name = $slotname_2");
+$node_standby_2->append_conf('postgresql.conf',
+ "wal_receiver_status_interval = 1");
+# should be able change primary_slot_name without restart
+# will wait effect in get_slot_xmins above
+$node_standby_2->reload;
+
+# Fetch xmin columns from slot's pg_replication_slots row, after waiting for
+# given boolean condition to be true to ensure we've reached a quiescent state
+sub get_slot_xmins
+{
+ my ($node, $slotname, $check_expr) = @_;
+
+ $node->poll_query_until(
+ 'postgres', qq[
+ SELECT $check_expr
+ FROM pg_catalog.pg_replication_slots
+ WHERE slot_name = '$slotname';
+ ]) or die "Timed out waiting for slot xmins to advance";
+
+ my $slotinfo = $node->slot($slotname);
+ return ($slotinfo->{'xmin'}, $slotinfo->{'catalog_xmin'});
+}
+
+# There's no hot standby feedback and there are no logical slots on either peer
+# so xmin and catalog_xmin should be null on both slots.
+my ($xmin, $catalog_xmin) = get_slot_xmins($node_primary, $slotname_1,
+ "xmin IS NULL AND catalog_xmin IS NULL");
+is($xmin, '', 'xmin of non-cascaded slot null with no hs_feedback');
+is($catalog_xmin, '',
+ 'catalog xmin of non-cascaded slot null with no hs_feedback');
+
+($xmin, $catalog_xmin) = get_slot_xmins($node_standby_1, $slotname_2,
+ "xmin IS NULL AND catalog_xmin IS NULL");
+is($xmin, '', 'xmin of cascaded slot null with no hs_feedback');
+is($catalog_xmin, '',
+ 'catalog xmin of cascaded slot null with no hs_feedback');
+
+# Replication still works?
+$node_primary->safe_psql('postgres', 'CREATE TABLE replayed(val integer);');
+
+sub replay_check
+{
+ my $newval = $node_primary->safe_psql('postgres',
+ 'INSERT INTO replayed(val) SELECT coalesce(max(val),0) + 1 AS newval FROM replayed RETURNING val'
+ );
+ $node_primary->wait_for_catchup($node_standby_1, 'replay',
+ $node_primary->lsn('insert'));
+ $node_standby_1->wait_for_catchup($node_standby_2, 'replay',
+ $node_standby_1->lsn('replay'));
+ $node_standby_1->safe_psql('postgres',
+ qq[SELECT 1 FROM replayed WHERE val = $newval])
+ or die "standby_1 didn't replay primary value $newval";
+ $node_standby_2->safe_psql('postgres',
+ qq[SELECT 1 FROM replayed WHERE val = $newval])
+ or die "standby_2 didn't replay standby_1 value $newval";
+ return;
+}
+
+replay_check();
+
+note "enabling hot_standby_feedback";
+
+# Enable hs_feedback. The slot should gain an xmin. We set the status interval
+# so we'll see the results promptly.
+$node_standby_1->safe_psql('postgres',
+ 'ALTER SYSTEM SET hot_standby_feedback = on;');
+$node_standby_1->reload;
+$node_standby_2->safe_psql('postgres',
+ 'ALTER SYSTEM SET hot_standby_feedback = on;');
+$node_standby_2->reload;
+replay_check();
+
+($xmin, $catalog_xmin) = get_slot_xmins($node_primary, $slotname_1,
+ "xmin IS NOT NULL AND catalog_xmin IS NULL");
+isnt($xmin, '', 'xmin of non-cascaded slot non-null with hs feedback');
+is($catalog_xmin, '',
+ 'catalog xmin of non-cascaded slot still null with hs_feedback');
+
+my ($xmin1, $catalog_xmin1) = get_slot_xmins($node_standby_1, $slotname_2,
+ "xmin IS NOT NULL AND catalog_xmin IS NULL");
+isnt($xmin1, '', 'xmin of cascaded slot non-null with hs feedback');
+is($catalog_xmin1, '',
+ 'catalog xmin of cascaded slot still null with hs_feedback');
+
+note "doing some work to advance xmin";
+$node_primary->safe_psql(
+ 'postgres', q{
+do $$
+begin
+ for i in 10000..11000 loop
+ -- use an exception block so that each iteration eats an XID
+ begin
+ insert into tab_int values (i);
+ exception
+ when division_by_zero then null;
+ end;
+ end loop;
+end$$;
+});
+
+$node_primary->safe_psql('postgres', 'VACUUM;');
+$node_primary->safe_psql('postgres', 'CHECKPOINT;');
+
+my ($xmin2, $catalog_xmin2) =
+ get_slot_xmins($node_primary, $slotname_1, "xmin <> '$xmin'");
+note "primary slot's new xmin $xmin2, old xmin $xmin";
+isnt($xmin2, $xmin, 'xmin of non-cascaded slot with hs feedback has changed');
+is($catalog_xmin2, '',
+ 'catalog xmin of non-cascaded slot still null with hs_feedback unchanged'
+);
+
+($xmin2, $catalog_xmin2) =
+ get_slot_xmins($node_standby_1, $slotname_2, "xmin <> '$xmin1'");
+note "standby_1 slot's new xmin $xmin2, old xmin $xmin1";
+isnt($xmin2, $xmin1, 'xmin of cascaded slot with hs feedback has changed');
+is($catalog_xmin2, '',
+ 'catalog xmin of cascaded slot still null with hs_feedback unchanged');
+
+note "disabling hot_standby_feedback";
+
+# Disable hs_feedback. Xmin should be cleared.
+$node_standby_1->safe_psql('postgres',
+ 'ALTER SYSTEM SET hot_standby_feedback = off;');
+$node_standby_1->reload;
+$node_standby_2->safe_psql('postgres',
+ 'ALTER SYSTEM SET hot_standby_feedback = off;');
+$node_standby_2->reload;
+replay_check();
+
+($xmin, $catalog_xmin) = get_slot_xmins($node_primary, $slotname_1,
+ "xmin IS NULL AND catalog_xmin IS NULL");
+is($xmin, '', 'xmin of non-cascaded slot null with hs feedback reset');
+is($catalog_xmin, '',
+ 'catalog xmin of non-cascaded slot still null with hs_feedback reset');
+
+($xmin, $catalog_xmin) = get_slot_xmins($node_standby_1, $slotname_2,
+ "xmin IS NULL AND catalog_xmin IS NULL");
+is($xmin, '', 'xmin of cascaded slot null with hs feedback reset');
+is($catalog_xmin, '',
+ 'catalog xmin of cascaded slot still null with hs_feedback reset');
+
+note "check change primary_conninfo without restart";
+$node_standby_2->append_conf('postgresql.conf', "primary_slot_name = ''");
+$node_standby_2->enable_streaming($node_primary);
+$node_standby_2->reload;
+
+# be sure do not streaming from cascade
+$node_standby_1->stop;
+
+my $newval = $node_primary->safe_psql('postgres',
+ 'INSERT INTO replayed(val) SELECT coalesce(max(val),0) + 1 AS newval FROM replayed RETURNING val'
+);
+$node_primary->wait_for_catchup($node_standby_2, 'replay',
+ $node_primary->lsn('insert'));
+my $is_replayed = $node_standby_2->safe_psql('postgres',
+ qq[SELECT 1 FROM replayed WHERE val = $newval]);
+is($is_replayed, qq(1), "standby_2 didn't replay primary value $newval");
+
+# Drop any existing slots on the primary, for the follow-up tests.
+$node_primary->safe_psql('postgres',
+ "SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots;");
+
+# Test physical slot advancing and its durability. Create a new slot on
+# the primary, not used by any of the standbys. This reserves WAL at creation.
+my $phys_slot = 'phys_slot';
+$node_primary->safe_psql('postgres',
+ "SELECT pg_create_physical_replication_slot('$phys_slot', true);");
+# Generate some WAL, and switch to a new segment, used to check that
+# the previous segment is correctly getting recycled as the slot advancing
+# would recompute the minimum LSN calculated across all slots.
+my $segment_removed = $node_primary->safe_psql('postgres',
+ 'SELECT pg_walfile_name(pg_current_wal_lsn())');
+chomp($segment_removed);
+$node_primary->psql(
+ 'postgres', "
+ CREATE TABLE tab_phys_slot (a int);
+ INSERT INTO tab_phys_slot VALUES (generate_series(1,10));
+ SELECT pg_switch_wal();");
+my $current_lsn =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn();");
+chomp($current_lsn);
+my $psql_rc = $node_primary->psql('postgres',
+ "SELECT pg_replication_slot_advance('$phys_slot', '$current_lsn'::pg_lsn);"
+);
+is($psql_rc, '0', 'slot advancing with physical slot');
+my $phys_restart_lsn_pre = $node_primary->safe_psql('postgres',
+ "SELECT restart_lsn from pg_replication_slots WHERE slot_name = '$phys_slot';"
+);
+chomp($phys_restart_lsn_pre);
+# Slot advance should persist across clean restarts.
+$node_primary->restart;
+my $phys_restart_lsn_post = $node_primary->safe_psql('postgres',
+ "SELECT restart_lsn from pg_replication_slots WHERE slot_name = '$phys_slot';"
+);
+chomp($phys_restart_lsn_post);
+ok( ($phys_restart_lsn_pre cmp $phys_restart_lsn_post) == 0,
+ "physical slot advance persists across restarts");
+
+# Check if the previous segment gets correctly recycled after the
+# server stopped cleanly, causing a shutdown checkpoint to be generated.
+my $primary_data = $node_primary->data_dir;
+ok(!-f "$primary_data/pg_wal/$segment_removed",
+ "WAL segment $segment_removed recycled after physical slot advancing");
diff --git a/src/test/recovery/t/002_archiving.pl b/src/test/recovery/t/002_archiving.pl
new file mode 100644
index 0000000..c8498c7
--- /dev/null
+++ b/src/test/recovery/t/002_archiving.pl
@@ -0,0 +1,99 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# test for archiving with hot standby
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+use File::Copy;
+
+# Initialize primary node, doing archives
+my $node_primary = get_new_node('primary');
+$node_primary->init(
+ has_archiving => 1,
+ allows_streaming => 1);
+my $backup_name = 'my_backup';
+
+# Start it
+$node_primary->start;
+
+# Take backup for standby
+$node_primary->backup($backup_name);
+
+# Initialize standby node from backup, fetching WAL from archives
+my $node_standby = get_new_node('standby');
+# Note that this makes the standby store its contents on the archives
+# of the primary.
+$node_standby->init_from_backup($node_primary, $backup_name,
+ has_restoring => 1);
+$node_standby->append_conf('postgresql.conf',
+ "wal_retrieve_retry_interval = '100ms'");
+$node_standby->start;
+
+# Create some content on primary
+$node_primary->safe_psql('postgres',
+ "CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a");
+my $current_lsn =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn();");
+
+# Force archiving of WAL file to make it present on primary
+$node_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+
+# Add some more content, it should not be present on standby
+$node_primary->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(1001,2000))");
+
+# Wait until necessary replay has been done on standby
+my $caughtup_query =
+ "SELECT '$current_lsn'::pg_lsn <= pg_last_wal_replay_lsn()";
+$node_standby->poll_query_until('postgres', $caughtup_query)
+ or die "Timed out while waiting for standby to catch up";
+
+my $result =
+ $node_standby->safe_psql('postgres', "SELECT count(*) FROM tab_int");
+is($result, qq(1000), 'check content from archives');
+
+# Check the presence of temporary files specifically generated during
+# archive recovery. To ensure the presence of the temporary history
+# file, switch to a timeline large enough to allow a standby to recover
+# a history file from an archive. As this requires at least two timeline
+# switches, promote the existing standby first. Then create a second
+# standby based on the primary, using its archives. Finally, the second
+# standby is promoted.
+$node_standby->promote;
+
+# Wait until the history file has been stored on the archives of the
+# primary once the promotion of the standby completes. This ensures that
+# the second standby created below will be able to restore this file,
+# creating a RECOVERYHISTORY.
+my $primary_archive = $node_primary->archive_dir;
+$caughtup_query =
+ "SELECT size IS NOT NULL FROM pg_stat_file('$primary_archive/00000002.history')";
+$node_primary->poll_query_until('postgres', $caughtup_query)
+ or die "Timed out while waiting for archiving of 00000002.history";
+
+my $node_standby2 = get_new_node('standby2');
+$node_standby2->init_from_backup($node_primary, $backup_name,
+ has_restoring => 1);
+$node_standby2->start;
+
+my $log_location = -s $node_standby2->logfile;
+
+# Now promote standby2, and check that temporary files specifically
+# generated during archive recovery are removed by the end of recovery.
+$node_standby2->promote;
+
+# Check the logs of the standby to see that the commands have failed.
+my $log_contents = slurp_file($node_standby2->logfile, $log_location);
+my $node_standby2_data = $node_standby2->data_dir;
+
+like(
+ $log_contents,
+ qr/restored log file "00000002.history" from archive/s,
+ "00000002.history retrieved from the archives");
+ok( !-f "$node_standby2_data/pg_wal/RECOVERYHISTORY",
+ "RECOVERYHISTORY removed after promotion");
+ok( !-f "$node_standby2_data/pg_wal/RECOVERYXLOG",
+ "RECOVERYXLOG removed after promotion");
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
new file mode 100644
index 0000000..0815e6e
--- /dev/null
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -0,0 +1,184 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test for recovery targets: name, timestamp, XID
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 9;
+use Time::HiRes qw(usleep);
+
+# Create and test a standby from given backup, with a certain recovery target.
+# Choose $until_lsn later than the transaction commit that causes the row
+# count to reach $num_rows, yet not later than the recovery target.
+sub test_recovery_standby
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $test_name = shift;
+ my $node_name = shift;
+ my $node_primary = shift;
+ my $recovery_params = shift;
+ my $num_rows = shift;
+ my $until_lsn = shift;
+
+ my $node_standby = get_new_node($node_name);
+ $node_standby->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+
+ foreach my $param_item (@$recovery_params)
+ {
+ $node_standby->append_conf('postgresql.conf', qq($param_item));
+ }
+
+ $node_standby->start;
+
+ # Wait until standby has replayed enough data
+ my $caughtup_query =
+ "SELECT '$until_lsn'::pg_lsn <= pg_last_wal_replay_lsn()";
+ $node_standby->poll_query_until('postgres', $caughtup_query)
+ or die "Timed out while waiting for standby to catch up";
+
+ # Create some content on primary and check its presence in standby
+ my $result =
+ $node_standby->safe_psql('postgres', "SELECT count(*) FROM tab_int");
+ is($result, qq($num_rows), "check standby content for $test_name");
+
+ # Stop standby node
+ $node_standby->teardown_node;
+
+ return;
+}
+
+# Initialize primary node
+my $node_primary = get_new_node('primary');
+$node_primary->init(has_archiving => 1, allows_streaming => 1);
+
+# Bump the transaction ID epoch. This is useful to stress the portability
+# of recovery_target_xid parsing.
+system_or_bail('pg_resetwal', '--epoch', '1', $node_primary->data_dir);
+
+# Start it
+$node_primary->start;
+
+# Create data before taking the backup, aimed at testing
+# recovery_target = 'immediate'
+$node_primary->safe_psql('postgres',
+ "CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a");
+my $lsn1 =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn();");
+
+# Take backup from which all operations will be run
+$node_primary->backup('my_backup');
+
+# Insert some data with used as a replay reference, with a recovery
+# target TXID.
+$node_primary->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(1001,2000))");
+my $ret = $node_primary->safe_psql('postgres',
+ "SELECT pg_current_wal_lsn(), pg_current_xact_id();");
+my ($lsn2, $recovery_txid) = split /\|/, $ret;
+
+# More data, with recovery target timestamp
+$node_primary->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(2001,3000))");
+my $lsn3 =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn();");
+my $recovery_time = $node_primary->safe_psql('postgres', "SELECT now()");
+
+# Even more data, this time with a recovery target name
+$node_primary->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(3001,4000))");
+my $recovery_name = "my_target";
+my $lsn4 =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn();");
+$node_primary->safe_psql('postgres',
+ "SELECT pg_create_restore_point('$recovery_name');");
+
+# And now for a recovery target LSN
+$node_primary->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(4001,5000))");
+my $lsn5 = my $recovery_lsn =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
+
+$node_primary->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(5001,6000))");
+
+# Force archiving of WAL file
+$node_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+
+# Test recovery targets
+my @recovery_params = ("recovery_target = 'immediate'");
+test_recovery_standby('immediate target',
+ 'standby_1', $node_primary, \@recovery_params, "1000", $lsn1);
+@recovery_params = ("recovery_target_xid = '$recovery_txid'");
+test_recovery_standby('XID', 'standby_2', $node_primary, \@recovery_params,
+ "2000", $lsn2);
+@recovery_params = ("recovery_target_time = '$recovery_time'");
+test_recovery_standby('time', 'standby_3', $node_primary, \@recovery_params,
+ "3000", $lsn3);
+@recovery_params = ("recovery_target_name = '$recovery_name'");
+test_recovery_standby('name', 'standby_4', $node_primary, \@recovery_params,
+ "4000", $lsn4);
+@recovery_params = ("recovery_target_lsn = '$recovery_lsn'");
+test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
+ "5000", $lsn5);
+
+# Multiple targets
+#
+# Multiple conflicting settings are not allowed, but setting the same
+# parameter multiple times or unsetting a parameter and setting a
+# different one is allowed.
+
+@recovery_params = (
+ "recovery_target_name = '$recovery_name'",
+ "recovery_target_name = ''",
+ "recovery_target_time = '$recovery_time'");
+test_recovery_standby('multiple overriding settings',
+ 'standby_6', $node_primary, \@recovery_params, "3000", $lsn3);
+
+my $node_standby = get_new_node('standby_7');
+$node_standby->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+$node_standby->append_conf(
+ 'postgresql.conf', "recovery_target_name = '$recovery_name'
+recovery_target_time = '$recovery_time'");
+
+my $res = run_log(
+ [
+ 'pg_ctl', '-D', $node_standby->data_dir, '-l',
+ $node_standby->logfile, 'start'
+ ]);
+ok(!$res, 'invalid recovery startup fails');
+
+my $logfile = slurp_file($node_standby->logfile());
+ok($logfile =~ qr/multiple recovery targets specified/,
+ 'multiple conflicting settings');
+
+# Check behavior when recovery ends before target is reached
+
+$node_standby = get_new_node('standby_8');
+$node_standby->init_from_backup(
+ $node_primary, 'my_backup',
+ has_restoring => 1,
+ standby => 0);
+$node_standby->append_conf('postgresql.conf',
+ "recovery_target_name = 'does_not_exist'");
+
+run_log(
+ [
+ 'pg_ctl', '-D', $node_standby->data_dir, '-l',
+ $node_standby->logfile, 'start'
+ ]);
+
+# wait for postgres to terminate
+foreach my $i (0 .. 10 * $TestLib::timeout_default)
+{
+ last if !-f $node_standby->data_dir . '/postmaster.pid';
+ usleep(100_000);
+}
+$logfile = slurp_file($node_standby->logfile());
+ok( $logfile =~
+ qr/FATAL: .* recovery ended before configured recovery target was reached/,
+ 'recovery end before target reached is a fatal error');
diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl
new file mode 100644
index 0000000..c101980
--- /dev/null
+++ b/src/test/recovery/t/004_timeline_switch.pl
@@ -0,0 +1,111 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test for timeline switch
+use strict;
+use warnings;
+use File::Path qw(rmtree);
+use PostgresNode;
+use TestLib;
+use Test::More tests => 3;
+
+$ENV{PGDATABASE} = 'postgres';
+
+# Ensure that a cascading standby is able to follow a newly-promoted standby
+# on a new timeline.
+
+# Initialize primary node
+my $node_primary = get_new_node('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# Take backup
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create two standbys linking to it
+my $node_standby_1 = get_new_node('standby_1');
+$node_standby_1->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+$node_standby_1->start;
+my $node_standby_2 = get_new_node('standby_2');
+$node_standby_2->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+$node_standby_2->start;
+
+# Create some content on primary
+$node_primary->safe_psql('postgres',
+ "CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a");
+
+# Wait until standby has replayed enough data on standby 1
+$node_primary->wait_for_catchup($node_standby_1, 'replay',
+ $node_primary->lsn('write'));
+
+# Stop and remove primary
+$node_primary->teardown_node;
+
+# promote standby 1 using "pg_promote", switching it to a new timeline
+my $psql_out = '';
+$node_standby_1->psql(
+ 'postgres',
+ "SELECT pg_promote(wait_seconds => 300)",
+ stdout => \$psql_out);
+is($psql_out, 't', "promotion of standby with pg_promote");
+
+# Switch standby 2 to replay from standby 1
+my $connstr_1 = $node_standby_1->connstr;
+$node_standby_2->append_conf(
+ 'postgresql.conf', qq(
+primary_conninfo='$connstr_1'
+));
+$node_standby_2->restart;
+
+# Insert some data in standby 1 and check its presence in standby 2
+# to ensure that the timeline switch has been done.
+$node_standby_1->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(1001,2000))");
+$node_standby_1->wait_for_catchup($node_standby_2, 'replay',
+ $node_standby_1->lsn('write'));
+
+my $result =
+ $node_standby_2->safe_psql('postgres', "SELECT count(*) FROM tab_int");
+is($result, qq(2000), 'check content of standby 2');
+
+
+# Ensure that a standby is able to follow a primary on a newer timeline
+# when WAL archiving is enabled.
+
+# Initialize primary node
+my $node_primary_2 = get_new_node('primary_2');
+$node_primary_2->init(allows_streaming => 1, has_archiving => 1);
+$node_primary_2->append_conf(
+ 'postgresql.conf', qq(
+wal_keep_size = 512MB
+));
+$node_primary_2->start;
+
+# Take backup
+$node_primary_2->backup($backup_name);
+
+# Create standby node
+my $node_standby_3 = get_new_node('standby_3');
+$node_standby_3->init_from_backup($node_primary_2, $backup_name,
+ has_streaming => 1);
+
+# Restart primary node in standby mode and promote it, switching it
+# to a new timeline.
+$node_primary_2->set_standby_mode;
+$node_primary_2->restart;
+$node_primary_2->promote;
+
+# Start standby node, create some content on primary and check its presence
+# in standby, to ensure that the timeline switch has been done.
+$node_standby_3->start;
+$node_primary_2->safe_psql('postgres',
+ "CREATE TABLE tab_int AS SELECT 1 AS a");
+$node_primary_2->wait_for_catchup($node_standby_3, 'replay',
+ $node_primary_2->lsn('write'));
+
+my $result_2 =
+ $node_standby_3->safe_psql('postgres', "SELECT count(*) FROM tab_int");
+is($result_2, qq(1), 'check content of standby 3');
diff --git a/src/test/recovery/t/005_replay_delay.pl b/src/test/recovery/t/005_replay_delay.pl
new file mode 100644
index 0000000..bd7ed4e
--- /dev/null
+++ b/src/test/recovery/t/005_replay_delay.pl
@@ -0,0 +1,112 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Checks for recovery_min_apply_delay and recovery pause
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 3;
+
+# Initialize primary node
+my $node_primary = get_new_node('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# And some content
+$node_primary->safe_psql('postgres',
+ "CREATE TABLE tab_int AS SELECT generate_series(1, 10) AS a");
+
+# Take backup
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create streaming standby from backup
+my $node_standby = get_new_node('standby');
+my $delay = 3;
+$node_standby->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+$node_standby->append_conf(
+ 'postgresql.conf', qq(
+recovery_min_apply_delay = '${delay}s'
+));
+$node_standby->start;
+
+# Make new content on primary and check its presence in standby depending
+# on the delay applied above. Before doing the insertion, get the
+# current timestamp that will be used as a comparison base. Even on slow
+# machines, this allows to have a predictable behavior when comparing the
+# delay between data insertion moment on primary and replay time on standby.
+my $primary_insert_time = time();
+$node_primary->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(11, 20))");
+
+# Now wait for replay to complete on standby. We're done waiting when the
+# standby has replayed up to the previously saved primary LSN.
+my $until_lsn =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
+
+$node_standby->poll_query_until('postgres',
+ "SELECT (pg_last_wal_replay_lsn() - '$until_lsn'::pg_lsn) >= 0")
+ or die "standby never caught up";
+
+# This test is successful if and only if the LSN has been applied with at least
+# the configured apply delay.
+ok(time() - $primary_insert_time >= $delay,
+ "standby applies WAL only after replication delay");
+
+
+# Check that recovery can be paused or resumed expectedly.
+my $node_standby2 = get_new_node('standby2');
+$node_standby2->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+$node_standby2->start;
+
+# Recovery is not yet paused.
+is( $node_standby2->safe_psql(
+ 'postgres', "SELECT pg_get_wal_replay_pause_state()"),
+ 'not paused',
+ 'pg_get_wal_replay_pause_state() reports not paused');
+
+# Request to pause recovery and wait until it's actually paused.
+$node_standby2->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+$node_primary->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(21,30))");
+$node_standby2->poll_query_until('postgres',
+ "SELECT pg_get_wal_replay_pause_state() = 'paused'")
+ or die "Timed out while waiting for recovery to be paused";
+
+# Even if new WAL records are streamed from the primary,
+# recovery in the paused state doesn't replay them.
+my $receive_lsn =
+ $node_standby2->safe_psql('postgres', "SELECT pg_last_wal_receive_lsn()");
+my $replay_lsn =
+ $node_standby2->safe_psql('postgres', "SELECT pg_last_wal_replay_lsn()");
+$node_primary->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(31,40))");
+$node_standby2->poll_query_until('postgres',
+ "SELECT '$receive_lsn'::pg_lsn < pg_last_wal_receive_lsn()")
+ or die "Timed out while waiting for new WAL to be streamed";
+is( $node_standby2->safe_psql('postgres', "SELECT pg_last_wal_replay_lsn()"),
+ qq($replay_lsn),
+ 'no WAL is replayed in the paused state');
+
+# Request to resume recovery and wait until it's actually resumed.
+$node_standby2->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+$node_standby2->poll_query_until('postgres',
+ "SELECT pg_get_wal_replay_pause_state() = 'not paused' AND pg_last_wal_replay_lsn() > '$replay_lsn'::pg_lsn"
+) or die "Timed out while waiting for recovery to be resumed";
+
+# Check that the paused state ends and promotion continues if a promotion
+# is triggered while recovery is paused.
+$node_standby2->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+$node_primary->safe_psql('postgres',
+ "INSERT INTO tab_int VALUES (generate_series(41,50))");
+$node_standby2->poll_query_until('postgres',
+ "SELECT pg_get_wal_replay_pause_state() = 'paused'")
+ or die "Timed out while waiting for recovery to be paused";
+
+$node_standby2->promote;
+$node_standby2->poll_query_until('postgres', "SELECT NOT pg_is_in_recovery()")
+ or die "Timed out while waiting for promotion to finish";
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
new file mode 100644
index 0000000..ae13c4f
--- /dev/null
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -0,0 +1,195 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Testing of logical decoding using SQL interface and/or pg_recvlogical
+#
+# Most logical decoding tests are in contrib/test_decoding. This module
+# is for work that doesn't fit well there, like where server restarts
+# are required.
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+use Config;
+
+# Initialize primary node
+my $node_primary = get_new_node('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->append_conf(
+ 'postgresql.conf', qq(
+wal_level = logical
+));
+$node_primary->start;
+my $backup_name = 'primary_backup';
+
+$node_primary->safe_psql('postgres',
+ qq[CREATE TABLE decoding_test(x integer, y text);]);
+
+$node_primary->safe_psql('postgres',
+ qq[SELECT pg_create_logical_replication_slot('test_slot', 'test_decoding');]
+);
+
+# Cover walsender error shutdown code
+my ($result, $stdout, $stderr) = $node_primary->psql(
+ 'template1',
+ qq[START_REPLICATION SLOT test_slot LOGICAL 0/0],
+ replication => 'database');
+ok( $stderr =~
+ m/replication slot "test_slot" was not created in this database/,
+ "Logical decoding correctly fails to start");
+
+# Check case of walsender not using a database connection. Logical
+# decoding should not be allowed.
+($result, $stdout, $stderr) = $node_primary->psql(
+ 'template1',
+ qq[START_REPLICATION SLOT s1 LOGICAL 0/1],
+ replication => 'true');
+ok($stderr =~ /ERROR: logical decoding requires a database connection/,
+ "Logical decoding fails on non-database connection");
+
+$node_primary->safe_psql('postgres',
+ qq[INSERT INTO decoding_test(x,y) SELECT s, s::text FROM generate_series(1,10) s;]
+);
+
+# Basic decoding works
+$result = $node_primary->safe_psql('postgres',
+ qq[SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);]);
+is(scalar(my @foobar = split /^/m, $result),
+ 12, 'Decoding produced 12 rows inc BEGIN/COMMIT');
+
+# If we immediately crash the server we might lose the progress we just made
+# and replay the same changes again. But a clean shutdown should never repeat
+# the same changes when we use the SQL decoding interface.
+$node_primary->restart('fast');
+
+# There are no new writes, so the result should be empty.
+$result = $node_primary->safe_psql('postgres',
+ qq[SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);]);
+chomp($result);
+is($result, '', 'Decoding after fast restart repeats no rows');
+
+# Insert some rows and verify that we get the same results from pg_recvlogical
+# and the SQL interface.
+$node_primary->safe_psql('postgres',
+ qq[INSERT INTO decoding_test(x,y) SELECT s, s::text FROM generate_series(1,4) s;]
+);
+
+my $expected = q{BEGIN
+table public.decoding_test: INSERT: x[integer]:1 y[text]:'1'
+table public.decoding_test: INSERT: x[integer]:2 y[text]:'2'
+table public.decoding_test: INSERT: x[integer]:3 y[text]:'3'
+table public.decoding_test: INSERT: x[integer]:4 y[text]:'4'
+COMMIT};
+
+my $stdout_sql = $node_primary->safe_psql('postgres',
+ qq[SELECT data FROM pg_logical_slot_peek_changes('test_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');]
+);
+is($stdout_sql, $expected, 'got expected output from SQL decoding session');
+
+my $endpos = $node_primary->safe_psql('postgres',
+ "SELECT lsn FROM pg_logical_slot_peek_changes('test_slot', NULL, NULL) ORDER BY lsn DESC LIMIT 1;"
+);
+print "waiting to replay $endpos\n";
+
+# Insert some rows after $endpos, which we won't read.
+$node_primary->safe_psql('postgres',
+ qq[INSERT INTO decoding_test(x,y) SELECT s, s::text FROM generate_series(5,50) s;]
+);
+
+my $stdout_recv = $node_primary->pg_recvlogical_upto(
+ 'postgres', 'test_slot', $endpos,
+ $TestLib::timeout_default,
+ 'include-xids' => '0',
+ 'skip-empty-xacts' => '1');
+chomp($stdout_recv);
+is($stdout_recv, $expected,
+ 'got same expected output from pg_recvlogical decoding session');
+
+$node_primary->poll_query_until('postgres',
+ "SELECT EXISTS (SELECT 1 FROM pg_replication_slots WHERE slot_name = 'test_slot' AND active_pid IS NULL)"
+) or die "slot never became inactive";
+
+$stdout_recv = $node_primary->pg_recvlogical_upto(
+ 'postgres', 'test_slot', $endpos,
+ $TestLib::timeout_default,
+ 'include-xids' => '0',
+ 'skip-empty-xacts' => '1');
+chomp($stdout_recv);
+is($stdout_recv, '', 'pg_recvlogical acknowledged changes');
+
+$node_primary->safe_psql('postgres', 'CREATE DATABASE otherdb');
+
+is( $node_primary->psql(
+ 'otherdb',
+ "SELECT lsn FROM pg_logical_slot_peek_changes('test_slot', NULL, NULL) ORDER BY lsn DESC LIMIT 1;"
+ ),
+ 3,
+ 'replaying logical slot from another database fails');
+
+$node_primary->safe_psql('otherdb',
+ qq[SELECT pg_create_logical_replication_slot('otherdb_slot', 'test_decoding');]
+);
+
+# make sure you can't drop a slot while active
+SKIP:
+{
+
+ # some Windows Perls at least don't like IPC::Run's start/kill_kill regime.
+ skip "Test fails on Windows perl", 2 if $Config{osname} eq 'MSWin32';
+
+ my $pg_recvlogical = IPC::Run::start(
+ [
+ 'pg_recvlogical', '-d', $node_primary->connstr('otherdb'),
+ '-S', 'otherdb_slot', '-f', '-', '--start'
+ ]);
+ $node_primary->poll_query_until('otherdb',
+ "SELECT EXISTS (SELECT 1 FROM pg_replication_slots WHERE slot_name = 'otherdb_slot' AND active_pid IS NOT NULL)"
+ ) or die "slot never became active";
+ is($node_primary->psql('postgres', 'DROP DATABASE otherdb'),
+ 3, 'dropping a DB with active logical slots fails');
+ $pg_recvlogical->kill_kill;
+ is($node_primary->slot('otherdb_slot')->{'slot_name'},
+ undef, 'logical slot still exists');
+}
+
+$node_primary->poll_query_until('otherdb',
+ "SELECT EXISTS (SELECT 1 FROM pg_replication_slots WHERE slot_name = 'otherdb_slot' AND active_pid IS NULL)"
+) or die "slot never became inactive";
+
+is($node_primary->psql('postgres', 'DROP DATABASE otherdb'),
+ 0, 'dropping a DB with inactive logical slots succeeds');
+is($node_primary->slot('otherdb_slot')->{'slot_name'},
+ undef, 'logical slot was actually dropped with DB');
+
+# Test logical slot advancing and its durability.
+my $logical_slot = 'logical_slot';
+$node_primary->safe_psql('postgres',
+ "SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+);
+$node_primary->psql(
+ 'postgres', "
+ CREATE TABLE tab_logical_slot (a int);
+ INSERT INTO tab_logical_slot VALUES (generate_series(1,10));");
+my $current_lsn =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn();");
+chomp($current_lsn);
+my $psql_rc = $node_primary->psql('postgres',
+ "SELECT pg_replication_slot_advance('$logical_slot', '$current_lsn'::pg_lsn);"
+);
+is($psql_rc, '0', 'slot advancing with logical slot');
+my $logical_restart_lsn_pre = $node_primary->safe_psql('postgres',
+ "SELECT restart_lsn from pg_replication_slots WHERE slot_name = '$logical_slot';"
+);
+chomp($logical_restart_lsn_pre);
+# Slot advance should persist across clean restarts.
+$node_primary->restart;
+my $logical_restart_lsn_post = $node_primary->safe_psql('postgres',
+ "SELECT restart_lsn from pg_replication_slots WHERE slot_name = '$logical_slot';"
+);
+chomp($logical_restart_lsn_post);
+ok(($logical_restart_lsn_pre cmp $logical_restart_lsn_post) == 0,
+ "logical slot advance persists across restarts");
+
+# done with the node
+$node_primary->stop;
diff --git a/src/test/recovery/t/007_sync_rep.pl b/src/test/recovery/t/007_sync_rep.pl
new file mode 100644
index 0000000..0ff9b5d
--- /dev/null
+++ b/src/test/recovery/t/007_sync_rep.pl
@@ -0,0 +1,219 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Minimal test testing synchronous replication sync_state transition
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 11;
+
+# Query checking sync_priority and sync_state of each standby
+my $check_sql =
+ "SELECT application_name, sync_priority, sync_state FROM pg_stat_replication ORDER BY application_name;";
+
+# Check that sync_state of each standby is expected (waiting till it is).
+# If $setting is given, synchronous_standby_names is set to it and
+# the configuration file is reloaded before the test.
+sub test_sync_state
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($self, $expected, $msg, $setting) = @_;
+
+ if (defined($setting))
+ {
+ $self->safe_psql('postgres',
+ "ALTER SYSTEM SET synchronous_standby_names = '$setting';");
+ $self->reload;
+ }
+
+ ok($self->poll_query_until('postgres', $check_sql, $expected), $msg);
+ return;
+}
+
+# Start a standby and check that it is registered within the WAL sender
+# array of the given primary. This polls the primary's pg_stat_replication
+# until the standby is confirmed as registered.
+sub start_standby_and_wait
+{
+ my ($primary, $standby) = @_;
+ my $primary_name = $primary->name;
+ my $standby_name = $standby->name;
+ my $query =
+ "SELECT count(1) = 1 FROM pg_stat_replication WHERE application_name = '$standby_name'";
+
+ $standby->start;
+
+ print("### Waiting for standby \"$standby_name\" on \"$primary_name\"\n");
+ $primary->poll_query_until('postgres', $query);
+ return;
+}
+
+# Initialize primary node
+my $node_primary = get_new_node('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+my $backup_name = 'primary_backup';
+
+# Take backup
+$node_primary->backup($backup_name);
+
+# Create all the standbys. Their status on the primary is checked to ensure
+# the ordering of each one of them in the WAL sender array of the primary.
+
+# Create standby1 linking to primary
+my $node_standby_1 = get_new_node('standby1');
+$node_standby_1->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+start_standby_and_wait($node_primary, $node_standby_1);
+
+# Create standby2 linking to primary
+my $node_standby_2 = get_new_node('standby2');
+$node_standby_2->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+start_standby_and_wait($node_primary, $node_standby_2);
+
+# Create standby3 linking to primary
+my $node_standby_3 = get_new_node('standby3');
+$node_standby_3->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+start_standby_and_wait($node_primary, $node_standby_3);
+
+# Check that sync_state is determined correctly when
+# synchronous_standby_names is specified in old syntax.
+test_sync_state(
+ $node_primary, qq(standby1|1|sync
+standby2|2|potential
+standby3|0|async),
+ 'old syntax of synchronous_standby_names',
+ 'standby1,standby2');
+
+# Check that all the standbys are considered as either sync or
+# potential when * is specified in synchronous_standby_names.
+# Note that standby1 is chosen as sync standby because
+# it's stored in the head of WalSnd array which manages
+# all the standbys though they have the same priority.
+test_sync_state(
+ $node_primary, qq(standby1|1|sync
+standby2|1|potential
+standby3|1|potential),
+ 'asterisk in synchronous_standby_names',
+ '*');
+
+# Stop and start standbys to rearrange the order of standbys
+# in WalSnd array. Now, if standbys have the same priority,
+# standby2 is selected preferentially and standby3 is next.
+$node_standby_1->stop;
+$node_standby_2->stop;
+$node_standby_3->stop;
+
+# Make sure that each standby reports back to the primary in the wanted
+# order.
+start_standby_and_wait($node_primary, $node_standby_2);
+start_standby_and_wait($node_primary, $node_standby_3);
+
+# Specify 2 as the number of sync standbys.
+# Check that two standbys are in 'sync' state.
+test_sync_state(
+ $node_primary, qq(standby2|2|sync
+standby3|3|sync),
+ '2 synchronous standbys',
+ '2(standby1,standby2,standby3)');
+
+# Start standby1
+start_standby_and_wait($node_primary, $node_standby_1);
+
+# Create standby4 linking to primary
+my $node_standby_4 = get_new_node('standby4');
+$node_standby_4->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+$node_standby_4->start;
+
+# Check that standby1 and standby2 whose names appear earlier in
+# synchronous_standby_names are considered as sync. Also check that
+# standby3 appearing later represents potential, and standby4 is
+# in 'async' state because it's not in the list.
+test_sync_state(
+ $node_primary, qq(standby1|1|sync
+standby2|2|sync
+standby3|3|potential
+standby4|0|async),
+ '2 sync, 1 potential, and 1 async');
+
+# Check that sync_state of each standby is determined correctly
+# when num_sync exceeds the number of names of potential sync standbys
+# specified in synchronous_standby_names.
+test_sync_state(
+ $node_primary, qq(standby1|0|async
+standby2|4|sync
+standby3|3|sync
+standby4|1|sync),
+ 'num_sync exceeds the num of potential sync standbys',
+ '6(standby4,standby0,standby3,standby2)');
+
+# The setting that * comes before another standby name is acceptable
+# but does not make sense in most cases. Check that sync_state is
+# chosen properly even in case of that setting. standby1 is selected
+# as synchronous as it has the highest priority, and is followed by a
+# second standby listed first in the WAL sender array, which is
+# standby2 in this case.
+test_sync_state(
+ $node_primary, qq(standby1|1|sync
+standby2|2|sync
+standby3|2|potential
+standby4|2|potential),
+ 'asterisk before another standby name',
+ '2(standby1,*,standby2)');
+
+# Check that the setting of '2(*)' chooses standby2 and standby3 that are stored
+# earlier in WalSnd array as sync standbys.
+test_sync_state(
+ $node_primary, qq(standby1|1|potential
+standby2|1|sync
+standby3|1|sync
+standby4|1|potential),
+ 'multiple standbys having the same priority are chosen as sync',
+ '2(*)');
+
+# Stop Standby3 which is considered in 'sync' state.
+$node_standby_3->stop;
+
+# Check that the state of standby1 stored earlier in WalSnd array than
+# standby4 is transited from potential to sync.
+test_sync_state(
+ $node_primary, qq(standby1|1|sync
+standby2|1|sync
+standby4|1|potential),
+ 'potential standby found earlier in array is promoted to sync');
+
+# Check that standby1 and standby2 are chosen as sync standbys
+# based on their priorities.
+test_sync_state(
+ $node_primary, qq(standby1|1|sync
+standby2|2|sync
+standby4|0|async),
+ 'priority-based sync replication specified by FIRST keyword',
+ 'FIRST 2(standby1, standby2)');
+
+# Check that all the listed standbys are considered as candidates
+# for sync standbys in a quorum-based sync replication.
+test_sync_state(
+ $node_primary, qq(standby1|1|quorum
+standby2|1|quorum
+standby4|0|async),
+ '2 quorum and 1 async',
+ 'ANY 2(standby1, standby2)');
+
+# Start Standby3 which will be considered in 'quorum' state.
+$node_standby_3->start;
+
+# Check that the setting of 'ANY 2(*)' chooses all standbys as
+# candidates for quorum sync standbys.
+test_sync_state(
+ $node_primary, qq(standby1|1|quorum
+standby2|1|quorum
+standby3|1|quorum
+standby4|1|quorum),
+ 'all standbys are considered as candidates for quorum sync standbys',
+ 'ANY 2(*)');
diff --git a/src/test/recovery/t/008_fsm_truncation.pl b/src/test/recovery/t/008_fsm_truncation.pl
new file mode 100644
index 0000000..14b4b97
--- /dev/null
+++ b/src/test/recovery/t/008_fsm_truncation.pl
@@ -0,0 +1,99 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test WAL replay of FSM changes.
+#
+# FSM changes don't normally need to be WAL-logged, except for truncation.
+# The FSM mustn't return a page that doesn't exist (anymore).
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 1;
+
+my $node_primary = get_new_node('primary');
+$node_primary->init(allows_streaming => 1);
+
+$node_primary->append_conf(
+ 'postgresql.conf', qq{
+fsync = on
+wal_log_hints = on
+max_prepared_transactions = 5
+autovacuum = off
+});
+
+# Create a primary node and its standby, initializing both with some data
+# at the same time.
+$node_primary->start;
+
+$node_primary->backup('primary_backup');
+my $node_standby = get_new_node('standby');
+$node_standby->init_from_backup($node_primary, 'primary_backup',
+ has_streaming => 1);
+$node_standby->start;
+
+$node_primary->psql(
+ 'postgres', qq{
+create table testtab (a int, b char(100));
+insert into testtab select generate_series(1,1000), 'foo';
+insert into testtab select generate_series(1,1000), 'foo';
+delete from testtab where ctid > '(8,0)';
+});
+
+# Take a lock on the table to prevent following vacuum from truncating it
+$node_primary->psql(
+ 'postgres', qq{
+begin;
+lock table testtab in row share mode;
+prepare transaction 'p1';
+});
+
+# Vacuum, update FSM without truncation
+$node_primary->psql('postgres', 'vacuum verbose testtab');
+
+# Force a checkpoint
+$node_primary->psql('postgres', 'checkpoint');
+
+# Now do some more insert/deletes, another vacuum to ensure full-page writes
+# are done
+$node_primary->psql(
+ 'postgres', qq{
+insert into testtab select generate_series(1,1000), 'foo';
+delete from testtab where ctid > '(8,0)';
+vacuum verbose testtab;
+});
+
+# Ensure all buffers are now clean on the standby
+$node_standby->psql('postgres', 'checkpoint');
+
+# Release the lock, vacuum again which should lead to truncation
+$node_primary->psql(
+ 'postgres', qq{
+rollback prepared 'p1';
+vacuum verbose testtab;
+});
+
+$node_primary->psql('postgres', 'checkpoint');
+my $until_lsn =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn();");
+
+# Wait long enough for standby to receive and apply all WAL
+my $caughtup_query =
+ "SELECT '$until_lsn'::pg_lsn <= pg_last_wal_replay_lsn()";
+$node_standby->poll_query_until('postgres', $caughtup_query)
+ or die "Timed out while waiting for standby to catch up";
+
+# Promote the standby
+$node_standby->promote;
+$node_standby->psql('postgres', 'checkpoint');
+
+# Restart to discard in-memory copy of FSM
+$node_standby->restart;
+
+# Insert should work on standby
+is( $node_standby->psql(
+ 'postgres',
+ qq{insert into testtab select generate_series(1,1000), 'foo';}),
+ 0,
+ 'INSERT succeeds with truncated relation FSM');
diff --git a/src/test/recovery/t/009_twophase.pl b/src/test/recovery/t/009_twophase.pl
new file mode 100644
index 0000000..900d181
--- /dev/null
+++ b/src/test/recovery/t/009_twophase.pl
@@ -0,0 +1,482 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Tests dedicated to two-phase commit in recovery
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 24;
+
+my $psql_out = '';
+my $psql_rc = '';
+
+sub configure_and_reload
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($node, $parameter) = @_;
+ my $name = $node->name;
+
+ $node->append_conf(
+ 'postgresql.conf', qq(
+ $parameter
+ ));
+ $node->psql('postgres', "SELECT pg_reload_conf()", stdout => \$psql_out);
+ is($psql_out, 't', "reload node $name with $parameter");
+ return;
+}
+
+# Set up two nodes, which will alternately be primary and replication standby.
+
+# Setup london node
+my $node_london = get_new_node("london");
+$node_london->init(allows_streaming => 1);
+$node_london->append_conf(
+ 'postgresql.conf', qq(
+ max_prepared_transactions = 10
+ log_checkpoints = true
+));
+$node_london->start;
+$node_london->backup('london_backup');
+
+# Setup paris node
+my $node_paris = get_new_node('paris');
+$node_paris->init_from_backup($node_london, 'london_backup',
+ has_streaming => 1);
+$node_paris->start;
+
+# Switch to synchronous replication in both directions
+configure_and_reload($node_london, "synchronous_standby_names = 'paris'");
+configure_and_reload($node_paris, "synchronous_standby_names = 'london'");
+
+# Set up nonce names for current primary and standby nodes
+note "Initially, london is primary and paris is standby";
+my ($cur_primary, $cur_standby) = ($node_london, $node_paris);
+my $cur_primary_name = $cur_primary->name;
+
+# Create table we'll use in the test transactions
+$cur_primary->psql('postgres', "CREATE TABLE t_009_tbl (id int, msg text)");
+
+###############################################################################
+# Check that we can commit and abort transaction after soft restart.
+# Here checkpoint happens before shutdown and no WAL replay will occur at next
+# startup. In this case postgres re-creates shared-memory state from twophase
+# files.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres', "
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (1, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (2, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_1';
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (3, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (4, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_2';");
+$cur_primary->stop;
+$cur_primary->start;
+
+$psql_rc = $cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_1'");
+is($psql_rc, '0', 'Commit prepared transaction after restart');
+
+$psql_rc = $cur_primary->psql('postgres', "ROLLBACK PREPARED 'xact_009_2'");
+is($psql_rc, '0', 'Rollback prepared transaction after restart');
+
+###############################################################################
+# Check that we can commit and abort after a hard restart.
+# At next startup, WAL replay will re-create shared memory state for prepared
+# transaction using dedicated WAL records.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres', "
+ CHECKPOINT;
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (5, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (6, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_3';
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (7, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (8, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_4';");
+$cur_primary->teardown_node;
+$cur_primary->start;
+
+$psql_rc = $cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_3'");
+is($psql_rc, '0', 'Commit prepared transaction after teardown');
+
+$psql_rc = $cur_primary->psql('postgres', "ROLLBACK PREPARED 'xact_009_4'");
+is($psql_rc, '0', 'Rollback prepared transaction after teardown');
+
+###############################################################################
+# Check that WAL replay can handle several transactions with same GID name.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres', "
+ CHECKPOINT;
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (9, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (10, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_5';
+ COMMIT PREPARED 'xact_009_5';
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (11, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (12, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_5';");
+$cur_primary->teardown_node;
+$cur_primary->start;
+
+$psql_rc = $cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_5'");
+is($psql_rc, '0', 'Replay several transactions with same GID');
+
+###############################################################################
+# Check that WAL replay cleans up its shared memory state and releases locks
+# while replaying transaction commits.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres', "
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (13, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (14, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_6';
+ COMMIT PREPARED 'xact_009_6';");
+$cur_primary->teardown_node;
+$cur_primary->start;
+$psql_rc = $cur_primary->psql(
+ 'postgres', "
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (15, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (16, 'issued to ${cur_primary_name}');
+ -- This prepare can fail due to conflicting GID or locks conflicts if
+ -- replay did not fully cleanup its state on previous commit.
+ PREPARE TRANSACTION 'xact_009_7';");
+is($psql_rc, '0', "Cleanup of shared memory state for 2PC commit");
+
+$cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_7'");
+
+###############################################################################
+# Check that WAL replay will cleanup its shared memory state on running standby.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres', "
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (17, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (18, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_8';
+ COMMIT PREPARED 'xact_009_8';");
+$cur_standby->psql(
+ 'postgres',
+ "SELECT count(*) FROM pg_prepared_xacts",
+ stdout => \$psql_out);
+is($psql_out, '0',
+ "Cleanup of shared memory state on running standby without checkpoint");
+
+###############################################################################
+# Same as in previous case, but let's force checkpoint on standby between
+# prepare and commit to use on-disk twophase files.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres', "
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (19, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (20, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_9';");
+$cur_standby->psql('postgres', "CHECKPOINT");
+$cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_9'");
+$cur_standby->psql(
+ 'postgres',
+ "SELECT count(*) FROM pg_prepared_xacts",
+ stdout => \$psql_out);
+is($psql_out, '0',
+ "Cleanup of shared memory state on running standby after checkpoint");
+
+###############################################################################
+# Check that prepared transactions can be committed on promoted standby.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres', "
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (21, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (22, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_10';");
+$cur_primary->teardown_node;
+$cur_standby->promote;
+
+# change roles
+note "Now paris is primary and london is standby";
+($cur_primary, $cur_standby) = ($node_paris, $node_london);
+$cur_primary_name = $cur_primary->name;
+
+# because london is not running at this point, we can't use syncrep commit
+# on this command
+$psql_rc = $cur_primary->psql('postgres',
+ "SET synchronous_commit = off; COMMIT PREPARED 'xact_009_10'");
+is($psql_rc, '0', "Restore of prepared transaction on promoted standby");
+
+# restart old primary as new standby
+$cur_standby->enable_streaming($cur_primary);
+$cur_standby->start;
+
+###############################################################################
+# Check that prepared transactions are replayed after soft restart of standby
+# while primary is down. Since standby knows that primary is down it uses a
+# different code path on startup to ensure that the status of transactions is
+# consistent.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres', "
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (23, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (24, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_11';");
+$cur_primary->stop;
+$cur_standby->restart;
+$cur_standby->promote;
+
+# change roles
+note "Now london is primary and paris is standby";
+($cur_primary, $cur_standby) = ($node_london, $node_paris);
+$cur_primary_name = $cur_primary->name;
+
+$cur_primary->psql(
+ 'postgres',
+ "SELECT count(*) FROM pg_prepared_xacts",
+ stdout => \$psql_out);
+is($psql_out, '1',
+ "Restore prepared transactions from files with primary down");
+
+# restart old primary as new standby
+$cur_standby->enable_streaming($cur_primary);
+$cur_standby->start;
+
+$cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_11'");
+
+###############################################################################
+# Check that prepared transactions are correctly replayed after standby hard
+# restart while primary is down.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres', "
+ BEGIN;
+ INSERT INTO t_009_tbl VALUES (25, 'issued to ${cur_primary_name}');
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl VALUES (26, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_12';
+ ");
+$cur_primary->stop;
+$cur_standby->teardown_node;
+$cur_standby->start;
+$cur_standby->promote;
+
+# change roles
+note "Now paris is primary and london is standby";
+($cur_primary, $cur_standby) = ($node_paris, $node_london);
+$cur_primary_name = $cur_primary->name;
+
+$cur_primary->psql(
+ 'postgres',
+ "SELECT count(*) FROM pg_prepared_xacts",
+ stdout => \$psql_out);
+is($psql_out, '1',
+ "Restore prepared transactions from records with primary down");
+
+# restart old primary as new standby
+$cur_standby->enable_streaming($cur_primary);
+$cur_standby->start;
+
+$cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_12'");
+
+###############################################################################
+# Check for a lock conflict between prepared transaction with DDL inside and
+# replay of XLOG_STANDBY_LOCK wal record.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE t_009_tbl2 (id int, msg text);
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl2 VALUES (27, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_13';
+ -- checkpoint will issue XLOG_STANDBY_LOCK that can conflict with lock
+ -- held by 'create table' statement
+ CHECKPOINT;
+ COMMIT PREPARED 'xact_009_13';");
+
+# Ensure that last transaction is replayed on standby.
+my $cur_primary_lsn =
+ $cur_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
+my $caughtup_query =
+ "SELECT '$cur_primary_lsn'::pg_lsn <= pg_last_wal_replay_lsn()";
+$cur_standby->poll_query_until('postgres', $caughtup_query)
+ or die "Timed out while waiting for standby to catch up";
+
+$cur_standby->psql(
+ 'postgres',
+ "SELECT count(*) FROM t_009_tbl2",
+ stdout => \$psql_out);
+is($psql_out, '1', "Replay prepared transaction with DDL");
+
+###############################################################################
+# Check recovery of prepared transaction with DDL inside after a hard restart
+# of the primary.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE t_009_tbl3 (id int, msg text);
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl3 VALUES (28, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_14';
+ BEGIN;
+ CREATE TABLE t_009_tbl4 (id int, msg text);
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl4 VALUES (29, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_15';");
+
+$cur_primary->teardown_node;
+$cur_primary->start;
+
+$psql_rc = $cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_14'");
+is($psql_rc, '0', 'Commit prepared transaction after teardown');
+
+$psql_rc = $cur_primary->psql('postgres', "ROLLBACK PREPARED 'xact_009_15'");
+is($psql_rc, '0', 'Rollback prepared transaction after teardown');
+
+###############################################################################
+# Check recovery of prepared transaction with DDL inside after a soft restart
+# of the primary.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE t_009_tbl5 (id int, msg text);
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl5 VALUES (30, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_16';
+ BEGIN;
+ CREATE TABLE t_009_tbl6 (id int, msg text);
+ SAVEPOINT s1;
+ INSERT INTO t_009_tbl6 VALUES (31, 'issued to ${cur_primary_name}');
+ PREPARE TRANSACTION 'xact_009_17';");
+
+$cur_primary->stop;
+$cur_primary->start;
+
+$psql_rc = $cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_16'");
+is($psql_rc, '0', 'Commit prepared transaction after restart');
+
+$psql_rc = $cur_primary->psql('postgres', "ROLLBACK PREPARED 'xact_009_17'");
+is($psql_rc, '0', 'Rollback prepared transaction after restart');
+
+###############################################################################
+# Verify expected data appears on both servers.
+###############################################################################
+
+$cur_primary->psql(
+ 'postgres',
+ "SELECT count(*) FROM pg_prepared_xacts",
+ stdout => \$psql_out);
+is($psql_out, '0', "No uncommitted prepared transactions on primary");
+
+$cur_primary->psql(
+ 'postgres',
+ "SELECT * FROM t_009_tbl ORDER BY id",
+ stdout => \$psql_out);
+is( $psql_out, qq{1|issued to london
+2|issued to london
+5|issued to london
+6|issued to london
+9|issued to london
+10|issued to london
+11|issued to london
+12|issued to london
+13|issued to london
+14|issued to london
+15|issued to london
+16|issued to london
+17|issued to london
+18|issued to london
+19|issued to london
+20|issued to london
+21|issued to london
+22|issued to london
+23|issued to paris
+24|issued to paris
+25|issued to london
+26|issued to london},
+ "Check expected t_009_tbl data on primary");
+
+$cur_primary->psql(
+ 'postgres',
+ "SELECT * FROM t_009_tbl2",
+ stdout => \$psql_out);
+is( $psql_out,
+ qq{27|issued to paris},
+ "Check expected t_009_tbl2 data on primary");
+
+$cur_standby->psql(
+ 'postgres',
+ "SELECT count(*) FROM pg_prepared_xacts",
+ stdout => \$psql_out);
+is($psql_out, '0', "No uncommitted prepared transactions on standby");
+
+$cur_standby->psql(
+ 'postgres',
+ "SELECT * FROM t_009_tbl ORDER BY id",
+ stdout => \$psql_out);
+is( $psql_out, qq{1|issued to london
+2|issued to london
+5|issued to london
+6|issued to london
+9|issued to london
+10|issued to london
+11|issued to london
+12|issued to london
+13|issued to london
+14|issued to london
+15|issued to london
+16|issued to london
+17|issued to london
+18|issued to london
+19|issued to london
+20|issued to london
+21|issued to london
+22|issued to london
+23|issued to paris
+24|issued to paris
+25|issued to london
+26|issued to london},
+ "Check expected t_009_tbl data on standby");
+
+$cur_standby->psql(
+ 'postgres',
+ "SELECT * FROM t_009_tbl2",
+ stdout => \$psql_out);
+is( $psql_out,
+ qq{27|issued to paris},
+ "Check expected t_009_tbl2 data on standby");
diff --git a/src/test/recovery/t/010_logical_decoding_timelines.pl b/src/test/recovery/t/010_logical_decoding_timelines.pl
new file mode 100644
index 0000000..973dcb5
--- /dev/null
+++ b/src/test/recovery/t/010_logical_decoding_timelines.pl
@@ -0,0 +1,199 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Demonstrate that logical can follow timeline switches.
+#
+# Logical replication slots can follow timeline switches but it's
+# normally not possible to have a logical slot on a replica where
+# promotion and a timeline switch can occur. The only ways
+# we can create that circumstance are:
+#
+# * By doing a filesystem-level copy of the DB, since pg_basebackup
+# excludes pg_replslot but we can copy it directly; or
+#
+# * by creating a slot directly at the C level on the replica and
+# advancing it as we go using the low level APIs. It can't be done
+# from SQL since logical decoding isn't allowed on replicas.
+#
+# This module uses the first approach to show that timeline following
+# on a logical slot works.
+#
+# (For convenience, it also tests some recovery-related operations
+# on logical slots).
+#
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 13;
+use File::Copy;
+use IPC::Run ();
+use Scalar::Util qw(blessed);
+
+my ($stdout, $stderr, $ret);
+
+# Initialize primary node
+my $node_primary = get_new_node('primary');
+$node_primary->init(allows_streaming => 1, has_archiving => 1);
+$node_primary->append_conf(
+ 'postgresql.conf', q[
+wal_level = 'logical'
+max_replication_slots = 3
+max_wal_senders = 2
+log_min_messages = 'debug2'
+hot_standby_feedback = on
+wal_receiver_status_interval = 1
+]);
+$node_primary->dump_info;
+$node_primary->start;
+
+note "testing logical timeline following with a filesystem-level copy";
+
+$node_primary->safe_psql('postgres',
+ "SELECT pg_create_logical_replication_slot('before_basebackup', 'test_decoding');"
+);
+$node_primary->safe_psql('postgres', "CREATE TABLE decoding(blah text);");
+$node_primary->safe_psql('postgres',
+ "INSERT INTO decoding(blah) VALUES ('beforebb');");
+
+# We also want to verify that DROP DATABASE on a standby with a logical
+# slot works. This isn't strictly related to timeline following, but
+# the only way to get a logical slot on a standby right now is to use
+# the same physical copy trick, so:
+$node_primary->safe_psql('postgres', 'CREATE DATABASE dropme;');
+$node_primary->safe_psql('dropme',
+ "SELECT pg_create_logical_replication_slot('dropme_slot', 'test_decoding');"
+);
+
+$node_primary->safe_psql('postgres', 'CHECKPOINT;');
+
+my $backup_name = 'b1';
+$node_primary->backup_fs_hot($backup_name);
+
+$node_primary->safe_psql('postgres',
+ q[SELECT pg_create_physical_replication_slot('phys_slot');]);
+
+my $node_replica = get_new_node('replica');
+$node_replica->init_from_backup(
+ $node_primary, $backup_name,
+ has_streaming => 1,
+ has_restoring => 1);
+$node_replica->append_conf('postgresql.conf',
+ q[primary_slot_name = 'phys_slot']);
+
+$node_replica->start;
+
+# If we drop 'dropme' on the primary, the standby should drop the
+# db and associated slot.
+is($node_primary->psql('postgres', 'DROP DATABASE dropme'),
+ 0, 'dropped DB with logical slot OK on primary');
+$node_primary->wait_for_catchup($node_replica, 'replay',
+ $node_primary->lsn('insert'));
+is( $node_replica->safe_psql(
+ 'postgres', q[SELECT 1 FROM pg_database WHERE datname = 'dropme']),
+ '',
+ 'dropped DB dropme on standby');
+is($node_primary->slot('dropme_slot')->{'slot_name'},
+ undef, 'logical slot was actually dropped on standby');
+
+# Back to testing failover...
+$node_primary->safe_psql('postgres',
+ "SELECT pg_create_logical_replication_slot('after_basebackup', 'test_decoding');"
+);
+$node_primary->safe_psql('postgres',
+ "INSERT INTO decoding(blah) VALUES ('afterbb');");
+$node_primary->safe_psql('postgres', 'CHECKPOINT;');
+
+# Verify that only the before base_backup slot is on the replica
+$stdout = $node_replica->safe_psql('postgres',
+ 'SELECT slot_name FROM pg_replication_slots ORDER BY slot_name');
+is($stdout, 'before_basebackup',
+ 'Expected to find only slot before_basebackup on replica');
+
+# Examine the physical slot the replica uses to stream changes
+# from the primary to make sure its hot_standby_feedback
+# has locked in a catalog_xmin on the physical slot, and that
+# any xmin is >= the catalog_xmin
+$node_primary->poll_query_until(
+ 'postgres', q[
+ SELECT catalog_xmin IS NOT NULL
+ FROM pg_replication_slots
+ WHERE slot_name = 'phys_slot'
+ ]) or die "slot's catalog_xmin never became set";
+
+my $phys_slot = $node_primary->slot('phys_slot');
+isnt($phys_slot->{'xmin'}, '', 'xmin assigned on physical slot of primary');
+isnt($phys_slot->{'catalog_xmin'},
+ '', 'catalog_xmin assigned on physical slot of primary');
+
+# Ignore wrap-around here, we're on a new cluster:
+cmp_ok(
+ $phys_slot->{'xmin'}, '>=',
+ $phys_slot->{'catalog_xmin'},
+ 'xmin on physical slot must not be lower than catalog_xmin');
+
+$node_primary->safe_psql('postgres', 'CHECKPOINT');
+$node_primary->wait_for_catchup($node_replica, 'write');
+
+# Boom, crash
+$node_primary->stop('immediate');
+
+$node_replica->promote;
+
+$node_replica->safe_psql('postgres',
+ "INSERT INTO decoding(blah) VALUES ('after failover');");
+
+# Shouldn't be able to read from slot created after base backup
+($ret, $stdout, $stderr) = $node_replica->psql('postgres',
+ "SELECT data FROM pg_logical_slot_peek_changes('after_basebackup', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');"
+);
+is($ret, 3, 'replaying from after_basebackup slot fails');
+like(
+ $stderr,
+ qr/replication slot "after_basebackup" does not exist/,
+ 'after_basebackup slot missing');
+
+# Should be able to read from slot created before base backup
+($ret, $stdout, $stderr) = $node_replica->psql(
+ 'postgres',
+ "SELECT data FROM pg_logical_slot_peek_changes('before_basebackup', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');",
+ timeout => $TestLib::timeout_default);
+is($ret, 0, 'replay from slot before_basebackup succeeds');
+
+my $final_expected_output_bb = q(BEGIN
+table public.decoding: INSERT: blah[text]:'beforebb'
+COMMIT
+BEGIN
+table public.decoding: INSERT: blah[text]:'afterbb'
+COMMIT
+BEGIN
+table public.decoding: INSERT: blah[text]:'after failover'
+COMMIT);
+is($stdout, $final_expected_output_bb,
+ 'decoded expected data from slot before_basebackup');
+is($stderr, '', 'replay from slot before_basebackup produces no stderr');
+
+# So far we've peeked the slots, so when we fetch the same info over
+# pg_recvlogical we should get complete results. First, find out the commit lsn
+# of the last transaction. There's no max(pg_lsn), so:
+
+my $endpos = $node_replica->safe_psql('postgres',
+ "SELECT lsn FROM pg_logical_slot_peek_changes('before_basebackup', NULL, NULL) ORDER BY lsn DESC LIMIT 1;"
+);
+
+# now use the walsender protocol to peek the slot changes and make sure we see
+# the same results.
+
+$stdout = $node_replica->pg_recvlogical_upto(
+ 'postgres', 'before_basebackup',
+ $endpos, $TestLib::timeout_default,
+ 'include-xids' => '0',
+ 'skip-empty-xacts' => '1');
+
+# walsender likes to add a newline
+chomp($stdout);
+is($stdout, $final_expected_output_bb,
+ 'got same output from walsender via pg_recvlogical on before_basebackup');
+
+$node_replica->teardown_node();
diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
new file mode 100644
index 0000000..a26e995
--- /dev/null
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -0,0 +1,64 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+#
+# Tests relating to PostgreSQL crash recovery and redo
+#
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More;
+use Config;
+
+plan tests => 3;
+
+my $node = get_new_node('primary');
+$node->init(allows_streaming => 1);
+$node->start;
+
+my ($stdin, $stdout, $stderr) = ('', '', '');
+
+# Ensure that pg_xact_status reports 'aborted' for xacts
+# that were in-progress during crash. To do that, we need
+# an xact to be in-progress when we crash and we need to know
+# its xid.
+my $tx = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+ $node->connstr('postgres')
+ ],
+ '<',
+ \$stdin,
+ '>',
+ \$stdout,
+ '2>',
+ \$stderr);
+$stdin .= q[
+BEGIN;
+CREATE TABLE mine(x integer);
+SELECT pg_current_xact_id();
+];
+$tx->pump until $stdout =~ /[[:digit:]]+[\r\n]$/;
+
+# Status should be in-progress
+my $xid = $stdout;
+chomp($xid);
+
+is($node->safe_psql('postgres', qq[SELECT pg_xact_status('$xid');]),
+ 'in progress', 'own xid is in-progress');
+
+# Crash and restart the postmaster
+$node->stop('immediate');
+$node->start;
+
+# Make sure we really got a new xid
+cmp_ok($node->safe_psql('postgres', 'SELECT pg_current_xact_id()'),
+ '>', $xid, 'new xid after restart is greater');
+
+# and make sure we show the in-progress xact as aborted
+is($node->safe_psql('postgres', qq[SELECT pg_xact_status('$xid');]),
+ 'aborted', 'xid is aborted after crash');
+
+$stdin .= "\\q\n";
+$tx->finish; # wait for psql to quit gracefully
diff --git a/src/test/recovery/t/012_subtransactions.pl b/src/test/recovery/t/012_subtransactions.pl
new file mode 100644
index 0000000..aa84073
--- /dev/null
+++ b/src/test/recovery/t/012_subtransactions.pl
@@ -0,0 +1,219 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Tests dedicated to subtransactions in recovery
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 12;
+
+# Setup primary node
+my $node_primary = get_new_node("primary");
+$node_primary->init(allows_streaming => 1);
+$node_primary->append_conf(
+ 'postgresql.conf', qq(
+ max_prepared_transactions = 10
+ log_checkpoints = true
+));
+$node_primary->start;
+$node_primary->backup('primary_backup');
+$node_primary->psql('postgres', "CREATE TABLE t_012_tbl (id int)");
+
+# Setup standby node
+my $node_standby = get_new_node('standby');
+$node_standby->init_from_backup($node_primary, 'primary_backup',
+ has_streaming => 1);
+$node_standby->start;
+
+# Switch to synchronous replication
+$node_primary->append_conf(
+ 'postgresql.conf', qq(
+ synchronous_standby_names = '*'
+));
+$node_primary->psql('postgres', "SELECT pg_reload_conf()");
+
+my $psql_out = '';
+my $psql_rc = '';
+
+###############################################################################
+# Check that replay will correctly set SUBTRANS and properly advance nextXid
+# so that it won't conflict with savepoint xids.
+###############################################################################
+
+$node_primary->psql(
+ 'postgres', "
+ BEGIN;
+ DELETE FROM t_012_tbl;
+ INSERT INTO t_012_tbl VALUES (43);
+ SAVEPOINT s1;
+ INSERT INTO t_012_tbl VALUES (43);
+ SAVEPOINT s2;
+ INSERT INTO t_012_tbl VALUES (43);
+ SAVEPOINT s3;
+ INSERT INTO t_012_tbl VALUES (43);
+ SAVEPOINT s4;
+ INSERT INTO t_012_tbl VALUES (43);
+ SAVEPOINT s5;
+ INSERT INTO t_012_tbl VALUES (43);
+ PREPARE TRANSACTION 'xact_012_1';
+ CHECKPOINT;");
+
+$node_primary->stop;
+$node_primary->start;
+$node_primary->psql(
+ 'postgres', "
+ -- here we can get xid of previous savepoint if nextXid
+ -- wasn't properly advanced
+ BEGIN;
+ INSERT INTO t_012_tbl VALUES (142);
+ ROLLBACK;
+ COMMIT PREPARED 'xact_012_1';");
+
+$node_primary->psql(
+ 'postgres',
+ "SELECT count(*) FROM t_012_tbl",
+ stdout => \$psql_out);
+is($psql_out, '6', "Check nextXid handling for prepared subtransactions");
+
+###############################################################################
+# Check that replay will correctly set 2PC with more than
+# PGPROC_MAX_CACHED_SUBXIDS subtransactions and also show data properly
+# on promotion
+###############################################################################
+$node_primary->psql('postgres', "DELETE FROM t_012_tbl");
+
+# Function borrowed from src/test/regress/sql/hs_primary_extremes.sql
+$node_primary->psql(
+ 'postgres', "
+ CREATE OR REPLACE FUNCTION hs_subxids (n integer)
+ RETURNS void
+ LANGUAGE plpgsql
+ AS \$\$
+ BEGIN
+ IF n <= 0 THEN RETURN; END IF;
+ INSERT INTO t_012_tbl VALUES (n);
+ PERFORM hs_subxids(n - 1);
+ RETURN;
+ EXCEPTION WHEN raise_exception THEN NULL; END;
+ \$\$;");
+$node_primary->psql(
+ 'postgres', "
+ BEGIN;
+ SELECT hs_subxids(127);
+ COMMIT;");
+$node_primary->wait_for_catchup($node_standby, 'replay',
+ $node_primary->lsn('insert'));
+$node_standby->psql(
+ 'postgres',
+ "SELECT coalesce(sum(id),-1) FROM t_012_tbl",
+ stdout => \$psql_out);
+is($psql_out, '8128', "Visible");
+$node_primary->stop;
+$node_standby->promote;
+
+$node_standby->psql(
+ 'postgres',
+ "SELECT coalesce(sum(id),-1) FROM t_012_tbl",
+ stdout => \$psql_out);
+is($psql_out, '8128', "Visible");
+
+# restore state
+($node_primary, $node_standby) = ($node_standby, $node_primary);
+$node_standby->enable_streaming($node_primary);
+$node_standby->start;
+$node_standby->psql(
+ 'postgres',
+ "SELECT coalesce(sum(id),-1) FROM t_012_tbl",
+ stdout => \$psql_out);
+is($psql_out, '8128', "Visible");
+
+$node_primary->psql('postgres', "DELETE FROM t_012_tbl");
+
+# Function borrowed from src/test/regress/sql/hs_primary_extremes.sql
+$node_primary->psql(
+ 'postgres', "
+ CREATE OR REPLACE FUNCTION hs_subxids (n integer)
+ RETURNS void
+ LANGUAGE plpgsql
+ AS \$\$
+ BEGIN
+ IF n <= 0 THEN RETURN; END IF;
+ INSERT INTO t_012_tbl VALUES (n);
+ PERFORM hs_subxids(n - 1);
+ RETURN;
+ EXCEPTION WHEN raise_exception THEN NULL; END;
+ \$\$;");
+$node_primary->psql(
+ 'postgres', "
+ BEGIN;
+ SELECT hs_subxids(127);
+ PREPARE TRANSACTION 'xact_012_1';");
+$node_primary->wait_for_catchup($node_standby, 'replay',
+ $node_primary->lsn('insert'));
+$node_standby->psql(
+ 'postgres',
+ "SELECT coalesce(sum(id),-1) FROM t_012_tbl",
+ stdout => \$psql_out);
+is($psql_out, '-1', "Not visible");
+$node_primary->stop;
+$node_standby->promote;
+
+$node_standby->psql(
+ 'postgres',
+ "SELECT coalesce(sum(id),-1) FROM t_012_tbl",
+ stdout => \$psql_out);
+is($psql_out, '-1', "Not visible");
+
+# restore state
+($node_primary, $node_standby) = ($node_standby, $node_primary);
+$node_standby->enable_streaming($node_primary);
+$node_standby->start;
+$psql_rc = $node_primary->psql('postgres', "COMMIT PREPARED 'xact_012_1'");
+is($psql_rc, '0',
+ "Restore of PGPROC_MAX_CACHED_SUBXIDS+ prepared transaction on promoted standby"
+);
+
+$node_primary->psql(
+ 'postgres',
+ "SELECT coalesce(sum(id),-1) FROM t_012_tbl",
+ stdout => \$psql_out);
+is($psql_out, '8128', "Visible");
+
+$node_primary->psql('postgres', "DELETE FROM t_012_tbl");
+$node_primary->psql(
+ 'postgres', "
+ BEGIN;
+ SELECT hs_subxids(201);
+ PREPARE TRANSACTION 'xact_012_1';");
+$node_primary->wait_for_catchup($node_standby, 'replay',
+ $node_primary->lsn('insert'));
+$node_standby->psql(
+ 'postgres',
+ "SELECT coalesce(sum(id),-1) FROM t_012_tbl",
+ stdout => \$psql_out);
+is($psql_out, '-1', "Not visible");
+$node_primary->stop;
+$node_standby->promote;
+
+$node_standby->psql(
+ 'postgres',
+ "SELECT coalesce(sum(id),-1) FROM t_012_tbl",
+ stdout => \$psql_out);
+is($psql_out, '-1', "Not visible");
+
+# restore state
+($node_primary, $node_standby) = ($node_standby, $node_primary);
+$node_standby->enable_streaming($node_primary);
+$node_standby->start;
+$psql_rc = $node_primary->psql('postgres', "ROLLBACK PREPARED 'xact_012_1'");
+is($psql_rc, '0',
+ "Rollback of PGPROC_MAX_CACHED_SUBXIDS+ prepared transaction on promoted standby"
+);
+
+$node_primary->psql(
+ 'postgres',
+ "SELECT coalesce(sum(id),-1) FROM t_012_tbl",
+ stdout => \$psql_out);
+is($psql_out, '-1', "Not visible");
diff --git a/src/test/recovery/t/013_crash_restart.pl b/src/test/recovery/t/013_crash_restart.pl
new file mode 100644
index 0000000..0ed1551
--- /dev/null
+++ b/src/test/recovery/t/013_crash_restart.pl
@@ -0,0 +1,248 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+#
+# Tests restarts of postgres due to crashes of a subprocess.
+#
+# Two longer-running psql subprocesses are used: One to kill a
+# backend, triggering a crash-restart cycle, one to detect when
+# postmaster noticed the backend died. The second backend is
+# necessary because it's otherwise hard to determine if postmaster is
+# still accepting new sessions (because it hasn't noticed that the
+# backend died), or because it's already restarted.
+#
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More;
+use Config;
+
+plan tests => 18;
+
+
+# To avoid hanging while expecting some specific input from a psql
+# instance being driven by us, add a timeout high enough that it
+# should never trigger even on very slow machines, unless something
+# is really wrong.
+my $psql_timeout = IPC::Run::timer($TestLib::timeout_default);
+
+my $node = get_new_node('primary');
+$node->init(allows_streaming => 1);
+$node->start();
+
+# by default PostgresNode doesn't doesn't restart after a crash
+$node->safe_psql(
+ 'postgres',
+ q[ALTER SYSTEM SET restart_after_crash = 1;
+ ALTER SYSTEM SET log_connections = 1;
+ SELECT pg_reload_conf();]);
+
+# Run psql, keeping session alive, so we have an alive backend to kill.
+my ($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+my $killme = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+ $node->connstr('postgres')
+ ],
+ '<',
+ \$killme_stdin,
+ '>',
+ \$killme_stdout,
+ '2>',
+ \$killme_stderr,
+ $psql_timeout);
+
+# Need a second psql to check if crash-restart happened.
+my ($monitor_stdin, $monitor_stdout, $monitor_stderr) = ('', '', '');
+my $monitor = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+ $node->connstr('postgres')
+ ],
+ '<',
+ \$monitor_stdin,
+ '>',
+ \$monitor_stdout,
+ '2>',
+ \$monitor_stderr,
+ $psql_timeout);
+
+#create table, insert row that should survive
+$killme_stdin .= q[
+CREATE TABLE alive(status text);
+INSERT INTO alive VALUES($$committed-before-sigquit$$);
+SELECT pg_backend_pid();
+];
+ok(pump_until($killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ 'acquired pid for SIGQUIT');
+my $pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+#insert a row that should *not* survive, due to in-progress xact
+$killme_stdin .= q[
+BEGIN;
+INSERT INTO alive VALUES($$in-progress-before-sigquit$$) RETURNING status;
+];
+ok(pump_until($killme, $psql_timeout, \$killme_stdout, qr/in-progress-before-sigquit/m),
+ 'inserted in-progress-before-sigquit');
+$killme_stdout = '';
+$killme_stderr = '';
+
+
+# Start longrunning query in second session; its failure will signal that
+# crash-restart has occurred. The initial wait for the trivial select is to
+# be sure that psql successfully connected to backend.
+$monitor_stdin .= q[
+SELECT $$psql-connected$$;
+SELECT pg_sleep(3600);
+];
+ok(pump_until($monitor, $psql_timeout, \$monitor_stdout, qr/psql-connected/m),
+ 'monitor connected');
+$monitor_stdout = '';
+$monitor_stderr = '';
+
+# kill once with QUIT - we expect psql to exit, while emitting error message first
+my $ret = TestLib::system_log('pg_ctl', 'kill', 'QUIT', $pid);
+
+# Exactly process should have been alive to be killed
+is($ret, 0, "killed process with SIGQUIT");
+
+# Check that psql sees the killed backend as having been terminated
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( pump_until(
+ $killme,
+ $psql_timeout,
+ \$killme_stderr,
+ qr/WARNING: terminating connection because of unexpected SIGQUIT signal|server closed the connection unexpectedly|connection to server was lost|could not send data to server/m
+ ),
+ "psql query died successfully after SIGQUIT");
+$killme_stderr = '';
+$killme_stdout = '';
+$killme->finish;
+
+# Wait till server restarts - we should get the WARNING here, but
+# sometimes the server is unable to send that, if interrupted while
+# sending.
+ok( pump_until(
+ $monitor,
+ $psql_timeout,
+ \$monitor_stderr,
+ qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost|could not send data to server/m
+ ),
+ "psql monitor died successfully after SIGQUIT");
+$monitor->finish;
+
+# Wait till server restarts
+is($node->poll_query_until('postgres', undef, ''),
+ "1", "reconnected after SIGQUIT");
+
+
+# restart psql processes, now that the crash cycle finished
+($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+$killme->run();
+($monitor_stdin, $monitor_stdout, $monitor_stderr) = ('', '', '');
+$monitor->run();
+
+
+# Acquire pid of new backend
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok(pump_until($killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ "acquired pid for SIGKILL");
+$pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Insert test rows
+$killme_stdin .= q[
+INSERT INTO alive VALUES($$committed-before-sigkill$$) RETURNING status;
+BEGIN;
+INSERT INTO alive VALUES($$in-progress-before-sigkill$$) RETURNING status;
+];
+ok(pump_until($killme, $psql_timeout, \$killme_stdout, qr/in-progress-before-sigkill/m),
+ 'inserted in-progress-before-sigkill');
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Re-start longrunning query in second session; its failure will signal that
+# crash-restart has occurred. The initial wait for the trivial select is to
+# be sure that psql successfully connected to backend.
+$monitor_stdin .= q[
+SELECT $$psql-connected$$;
+SELECT pg_sleep(3600);
+];
+ok(pump_until($monitor, $psql_timeout, \$monitor_stdout, qr/psql-connected/m),
+ 'monitor connected');
+$monitor_stdout = '';
+$monitor_stderr = '';
+
+
+# kill with SIGKILL this time - we expect the backend to exit, without
+# being able to emit an error message
+$ret = TestLib::system_log('pg_ctl', 'kill', 'KILL', $pid);
+is($ret, 0, "killed process with KILL");
+
+# Check that psql sees the server as being terminated. No WARNING,
+# because signal handlers aren't being run on SIGKILL.
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( pump_until(
+ $killme,
+ $psql_timeout,
+ \$killme_stderr,
+ qr/server closed the connection unexpectedly|connection to server was lost|could not send data to server/m
+ ),
+ "psql query died successfully after SIGKILL");
+$killme->finish;
+
+# Wait till server restarts - we should get the WARNING here, but
+# sometimes the server is unable to send that, if interrupted while
+# sending.
+ok( pump_until(
+ $monitor,
+ $psql_timeout,
+ \$monitor_stderr,
+ qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost|could not send data to server/m
+ ),
+ "psql monitor died successfully after SIGKILL");
+$monitor->finish;
+
+# Wait till server restarts
+is($node->poll_query_until('postgres', undef, ''),
+ "1", "reconnected after SIGKILL");
+
+# Make sure the committed rows survived, in-progress ones not
+is( $node->safe_psql('postgres', 'SELECT * FROM alive'),
+ "committed-before-sigquit\ncommitted-before-sigkill",
+ 'data survived');
+
+is( $node->safe_psql(
+ 'postgres',
+ 'INSERT INTO alive VALUES($$before-orderly-restart$$) RETURNING status'
+ ),
+ 'before-orderly-restart',
+ 'can still write after crash restart');
+
+# Just to be sure, check that an orderly restart now still works
+$node->restart();
+
+is( $node->safe_psql('postgres', 'SELECT * FROM alive'),
+ "committed-before-sigquit\ncommitted-before-sigkill\nbefore-orderly-restart",
+ 'data survived');
+
+is( $node->safe_psql(
+ 'postgres',
+ 'INSERT INTO alive VALUES($$after-orderly-restart$$) RETURNING status'
+ ),
+ 'after-orderly-restart',
+ 'can still write after orderly restart');
+
+$node->stop();
diff --git a/src/test/recovery/t/014_unlogged_reinit.pl b/src/test/recovery/t/014_unlogged_reinit.pl
new file mode 100644
index 0000000..6d40535
--- /dev/null
+++ b/src/test/recovery/t/014_unlogged_reinit.pl
@@ -0,0 +1,82 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Tests that unlogged tables are properly reinitialized after a crash.
+#
+# The behavior should be the same when restoring from a backup, but
+# that is not tested here.
+
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 12;
+
+my $node = get_new_node('main');
+
+$node->init;
+$node->start;
+my $pgdata = $node->data_dir;
+
+# Create an unlogged table to test that forks other than init are not
+# copied.
+$node->safe_psql('postgres', 'CREATE UNLOGGED TABLE base_unlogged (id int)');
+
+my $baseUnloggedPath = $node->safe_psql('postgres',
+ q{select pg_relation_filepath('base_unlogged')});
+
+# Test that main and init forks exist.
+ok(-f "$pgdata/${baseUnloggedPath}_init", 'init fork in base exists');
+ok(-f "$pgdata/$baseUnloggedPath", 'main fork in base exists');
+
+# Create an unlogged table in a tablespace.
+
+my $tablespaceDir = TestLib::tempdir;
+
+$node->safe_psql('postgres', "CREATE TABLESPACE ts1 LOCATION '$tablespaceDir'");
+$node->safe_psql('postgres',
+ 'CREATE UNLOGGED TABLE ts1_unlogged (id int) TABLESPACE ts1');
+
+my $ts1UnloggedPath = $node->safe_psql('postgres',
+ q{select pg_relation_filepath('ts1_unlogged')});
+
+# Test that main and init forks exist.
+ok(-f "$pgdata/${ts1UnloggedPath}_init", 'init fork in tablespace exists');
+ok(-f "$pgdata/$ts1UnloggedPath", 'main fork in tablespace exists');
+
+# Crash the postmaster.
+$node->stop('immediate');
+
+# Write fake forks to test that they are removed during recovery.
+append_to_file("$pgdata/${baseUnloggedPath}_vm", 'TEST_VM');
+append_to_file("$pgdata/${baseUnloggedPath}_fsm", 'TEST_FSM');
+
+# Remove main fork to test that it is recopied from init.
+unlink("$pgdata/${baseUnloggedPath}")
+ or BAIL_OUT("could not remove \"${baseUnloggedPath}\": $!");
+
+# the same for the tablespace
+append_to_file("$pgdata/${ts1UnloggedPath}_vm", 'TEST_VM');
+append_to_file("$pgdata/${ts1UnloggedPath}_fsm", 'TEST_FSM');
+unlink("$pgdata/${ts1UnloggedPath}")
+ or BAIL_OUT("could not remove \"${ts1UnloggedPath}\": $!");
+
+$node->start;
+
+# check unlogged table in base
+ok(-f "$pgdata/${baseUnloggedPath}_init", 'init fork in base still exists');
+ok(-f "$pgdata/$baseUnloggedPath", 'main fork in base recreated at startup');
+ok(!-f "$pgdata/${baseUnloggedPath}_vm",
+ 'vm fork in base removed at startup');
+ok( !-f "$pgdata/${baseUnloggedPath}_fsm",
+ 'fsm fork in base removed at startup');
+
+# check unlogged table in tablespace
+ok( -f "$pgdata/${ts1UnloggedPath}_init",
+ 'init fork still exists in tablespace');
+ok(-f "$pgdata/$ts1UnloggedPath",
+ 'main fork in tablespace recreated at startup');
+ok( !-f "$pgdata/${ts1UnloggedPath}_vm",
+ 'vm fork in tablespace removed at startup');
+ok( !-f "$pgdata/${ts1UnloggedPath}_fsm",
+ 'fsm fork in tablespace removed at startup');
diff --git a/src/test/recovery/t/015_promotion_pages.pl b/src/test/recovery/t/015_promotion_pages.pl
new file mode 100644
index 0000000..2b72dc2
--- /dev/null
+++ b/src/test/recovery/t/015_promotion_pages.pl
@@ -0,0 +1,87 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test for promotion handling with WAL records generated post-promotion
+# before the first checkpoint is generated. This test case checks for
+# invalid page references at replay based on the minimum consistent
+# recovery point defined.
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 1;
+
+# Initialize primary node
+my $alpha = get_new_node('alpha');
+$alpha->init(allows_streaming => 1);
+# Setting wal_log_hints to off is important to get invalid page
+# references.
+$alpha->append_conf("postgresql.conf", <<EOF);
+wal_log_hints = off
+EOF
+
+# Start the primary
+$alpha->start;
+
+# setup/start a standby
+$alpha->backup('bkp');
+my $bravo = get_new_node('bravo');
+$bravo->init_from_backup($alpha, 'bkp', has_streaming => 1);
+$bravo->append_conf('postgresql.conf', <<EOF);
+checkpoint_timeout=1h
+EOF
+$bravo->start;
+
+# Dummy table for the upcoming tests.
+$alpha->safe_psql('postgres', 'create table test1 (a int)');
+$alpha->safe_psql('postgres',
+ 'insert into test1 select generate_series(1, 10000)');
+
+# take a checkpoint
+$alpha->safe_psql('postgres', 'checkpoint');
+
+# The following vacuum will set visibility map bits and create
+# problematic WAL records.
+$alpha->safe_psql('postgres', 'vacuum verbose test1');
+# Wait for last record to have been replayed on the standby.
+$alpha->wait_for_catchup($bravo, 'replay', $alpha->lsn('insert'));
+
+# Now force a checkpoint on the standby. This seems unnecessary but for "some"
+# reason, the previous checkpoint on the primary does not reflect on the standby
+# and without an explicit checkpoint, it may start redo recovery from a much
+# older point, which includes even create table and initial page additions.
+$bravo->safe_psql('postgres', 'checkpoint');
+
+# Now just use a dummy table and run some operations to move minRecoveryPoint
+# beyond the previous vacuum.
+$alpha->safe_psql('postgres', 'create table test2 (a int, b text)');
+$alpha->safe_psql('postgres',
+ 'insert into test2 select generate_series(1,10000), md5(random()::text)');
+$alpha->safe_psql('postgres', 'truncate test2');
+
+# Wait again for all records to be replayed.
+$alpha->wait_for_catchup($bravo, 'replay', $alpha->lsn('insert'));
+
+# Do the promotion, which reinitializes minRecoveryPoint in the control
+# file so as WAL is replayed up to the end.
+$bravo->promote;
+
+# Truncate the table on the promoted standby, vacuum and extend it
+# again to create new page references. The first post-recovery checkpoint
+# has not happened yet.
+$bravo->safe_psql('postgres', 'truncate test1');
+$bravo->safe_psql('postgres', 'vacuum verbose test1');
+$bravo->safe_psql('postgres',
+ 'insert into test1 select generate_series(1,1000)');
+
+# Now crash-stop the promoted standby and restart. This makes sure that
+# replay does not see invalid page references because of an invalid
+# minimum consistent recovery point.
+$bravo->stop('immediate');
+$bravo->start;
+
+# Check state of the table after full crash recovery. All its data should
+# be here.
+my $psql_out;
+$bravo->psql('postgres', "SELECT count(*) FROM test1", stdout => \$psql_out);
+is($psql_out, '1000', "Check that table state is correct");
diff --git a/src/test/recovery/t/016_min_consistency.pl b/src/test/recovery/t/016_min_consistency.pl
new file mode 100644
index 0000000..60ecd3d
--- /dev/null
+++ b/src/test/recovery/t/016_min_consistency.pl
@@ -0,0 +1,141 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test for checking consistency of on-disk pages for a cluster with
+# the minimum recovery LSN, ensuring that the updates happen across
+# all processes. In this test, the updates from the startup process
+# and the checkpointer (which triggers non-startup code paths) are
+# both checked.
+
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 1;
+
+# Find the largest LSN in the set of pages part of the given relation
+# file. This is used for offline checks of page consistency. The LSN
+# is historically stored as a set of two numbers of 4 byte-length
+# located at the beginning of each page.
+sub find_largest_lsn
+{
+ my $blocksize = int(shift);
+ my $filename = shift;
+ my ($max_hi, $max_lo) = (0, 0);
+ open(my $fh, "<:raw", $filename)
+ or die "failed to open $filename: $!";
+ my ($buf, $len);
+ while ($len = read($fh, $buf, $blocksize))
+ {
+ $len == $blocksize
+ or die "read only $len of $blocksize bytes from $filename";
+ my ($hi, $lo) = unpack("LL", $buf);
+
+ if ($hi > $max_hi or ($hi == $max_hi and $lo > $max_lo))
+ {
+ ($max_hi, $max_lo) = ($hi, $lo);
+ }
+ }
+ defined($len) or die "read error on $filename: $!";
+ close($fh);
+
+ return sprintf("%X/%X", $max_hi, $max_lo);
+}
+
+# Initialize primary node
+my $primary = get_new_node('primary');
+$primary->init(allows_streaming => 1);
+
+# Set shared_buffers to a very low value to enforce discard and flush
+# of PostgreSQL buffers on standby, enforcing other processes than the
+# startup process to update the minimum recovery LSN in the control
+# file. Autovacuum is disabled so as there is no risk of having other
+# processes than the checkpointer doing page flushes.
+$primary->append_conf("postgresql.conf", <<EOF);
+shared_buffers = 128kB
+autovacuum = off
+EOF
+
+# Start the primary
+$primary->start;
+
+# setup/start a standby
+$primary->backup('bkp');
+my $standby = get_new_node('standby');
+$standby->init_from_backup($primary, 'bkp', has_streaming => 1);
+$standby->start;
+
+# Create base table whose data consistency is checked.
+$primary->safe_psql(
+ 'postgres', "
+CREATE TABLE test1 (a int) WITH (fillfactor = 10);
+INSERT INTO test1 SELECT generate_series(1, 10000);");
+
+# Take a checkpoint and enforce post-checkpoint full page writes
+# which makes the startup process replay those pages, updating
+# minRecoveryPoint.
+$primary->safe_psql('postgres', 'CHECKPOINT;');
+$primary->safe_psql('postgres', 'UPDATE test1 SET a = a + 1;');
+
+# Wait for last record to have been replayed on the standby.
+$primary->wait_for_catchup($standby, 'replay', $primary->lsn('insert'));
+
+# Fill in the standby's shared buffers with the data filled in
+# previously.
+$standby->safe_psql('postgres', 'SELECT count(*) FROM test1;');
+
+# Update the table again, this does not generate full page writes so
+# the standby will replay records associated with it, but the startup
+# process will not flush those pages.
+$primary->safe_psql('postgres', 'UPDATE test1 SET a = a + 1;');
+
+# Extract from the relation the last block created and its relation
+# file, this will be used at the end of the test for sanity checks.
+my $blocksize = $primary->safe_psql('postgres',
+ "SELECT setting::int FROM pg_settings WHERE name = 'block_size';");
+my $last_block = $primary->safe_psql('postgres',
+ "SELECT pg_relation_size('test1')::int / $blocksize - 1;");
+my $relfilenode = $primary->safe_psql('postgres',
+ "SELECT pg_relation_filepath('test1'::regclass);");
+
+# Wait for last record to have been replayed on the standby.
+$primary->wait_for_catchup($standby, 'replay', $primary->lsn('insert'));
+
+# Issue a restart point on the standby now, which makes the checkpointer
+# update minRecoveryPoint.
+$standby->safe_psql('postgres', 'CHECKPOINT;');
+
+# Now shut down the primary violently so as the standby does not
+# receive the shutdown checkpoint, making sure that the startup
+# process does not flush any pages on its side. The standby is
+# cleanly stopped, which makes the checkpointer update minRecoveryPoint
+# with the restart point created at shutdown.
+$primary->stop('immediate');
+$standby->stop('fast');
+
+# Check the data consistency of the instance while offline. This is
+# done by directly scanning the on-disk relation blocks and what
+# pg_controldata lets know.
+my $standby_data = $standby->data_dir;
+my $offline_max_lsn =
+ find_largest_lsn($blocksize, "$standby_data/$relfilenode");
+
+# Fetch minRecoveryPoint from the control file itself
+my ($stdout, $stderr) = run_command([ 'pg_controldata', $standby_data ]);
+my @control_data = split("\n", $stdout);
+my $offline_recovery_lsn = undef;
+foreach (@control_data)
+{
+ if ($_ =~ /^Minimum recovery ending location:\s*(.*)$/mg)
+ {
+ $offline_recovery_lsn = $1;
+ last;
+ }
+}
+die "No minRecoveryPoint in control file found\n"
+ unless defined($offline_recovery_lsn);
+
+# minRecoveryPoint should never be older than the maximum LSN for all
+# the pages on disk.
+ok($offline_recovery_lsn ge $offline_max_lsn,
+ "Check offline that table data is consistent with minRecoveryPoint");
diff --git a/src/test/recovery/t/017_shm.pl b/src/test/recovery/t/017_shm.pl
new file mode 100644
index 0000000..48eb465
--- /dev/null
+++ b/src/test/recovery/t/017_shm.pl
@@ -0,0 +1,221 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+#
+# Tests of pg_shmem.h functions
+#
+use strict;
+use warnings;
+use Config;
+use File::stat qw(stat);
+use IPC::Run 'run';
+use PostgresNode;
+use Test::More;
+use TestLib;
+use Time::HiRes qw(usleep);
+
+# If we don't have shmem support, skip the whole thing
+eval {
+ require IPC::SharedMem;
+ IPC::SharedMem->import;
+ require IPC::SysV;
+ IPC::SysV->import(qw(IPC_CREAT IPC_EXCL S_IRUSR S_IWUSR));
+};
+if ($@ || $windows_os)
+{
+ plan skip_all => 'SysV shared memory not supported by this platform';
+}
+else
+{
+ plan tests => 4;
+}
+
+my $tempdir = TestLib::tempdir;
+
+# Log "ipcs" diffs on a best-effort basis, swallowing any error.
+my $ipcs_before = "$tempdir/ipcs_before";
+eval { run_log [ 'ipcs', '-am' ], '>', $ipcs_before; };
+
+sub log_ipcs
+{
+ eval { run_log [ 'ipcs', '-am' ], '|', [ 'diff', $ipcs_before, '-' ] };
+ return;
+}
+
+# Node setup.
+my $gnat = PostgresNode->get_new_node('gnat');
+$gnat->init;
+
+# Create a shmem segment that will conflict with gnat's first choice
+# of shmem key. (If we fail to create it because something else is
+# already using that key, that's perfectly fine, though the test will
+# exercise a different scenario than it usually does.)
+my $gnat_dir_stat = stat($gnat->data_dir);
+defined($gnat_dir_stat) or die('unable to stat ' . $gnat->data_dir);
+my $gnat_inode = $gnat_dir_stat->ino;
+note "gnat's datadir inode = $gnat_inode";
+
+# Note: must reference IPC::SysV's constants as functions, or this file
+# fails to compile when that module is not available.
+my $gnat_conflict_shm =
+ IPC::SharedMem->new($gnat_inode, 1024,
+ IPC_CREAT() | IPC_EXCL() | S_IRUSR() | S_IWUSR());
+note "could not create conflicting shmem" if !defined($gnat_conflict_shm);
+log_ipcs();
+
+$gnat->start;
+log_ipcs();
+
+$gnat->restart; # should keep same shmem key
+log_ipcs();
+
+# Upon postmaster death, postmaster children exit automatically.
+$gnat->kill9;
+log_ipcs();
+poll_start($gnat); # gnat recycles its former shm key.
+log_ipcs();
+
+note "removing the conflicting shmem ...";
+$gnat_conflict_shm->remove if $gnat_conflict_shm;
+log_ipcs();
+
+# Upon postmaster death, postmaster children exit automatically.
+$gnat->kill9;
+log_ipcs();
+
+# In this start, gnat will use its normal shmem key, and fail to remove
+# the higher-keyed segment that the previous postmaster was using.
+# That's not great, but key collisions should be rare enough to not
+# make this a big problem.
+poll_start($gnat);
+log_ipcs();
+$gnat->stop;
+log_ipcs();
+
+# Re-create the conflicting segment, and start/stop normally, just so
+# this test script doesn't leak the higher-keyed segment.
+note "re-creating conflicting shmem ...";
+$gnat_conflict_shm =
+ IPC::SharedMem->new($gnat_inode, 1024,
+ IPC_CREAT() | IPC_EXCL() | S_IRUSR() | S_IWUSR());
+note "could not create conflicting shmem" if !defined($gnat_conflict_shm);
+log_ipcs();
+
+$gnat->start;
+log_ipcs();
+$gnat->stop;
+log_ipcs();
+
+note "removing the conflicting shmem ...";
+$gnat_conflict_shm->remove if $gnat_conflict_shm;
+log_ipcs();
+
+# Scenarios involving no postmaster.pid, dead postmaster, and a live backend.
+# Use a regress.c function to emulate the responsiveness of a backend working
+# through a CPU-intensive task.
+$gnat->start;
+log_ipcs();
+
+my $regress_shlib = $ENV{REGRESS_SHLIB};
+$gnat->safe_psql('postgres', <<EOSQL);
+CREATE FUNCTION wait_pid(int)
+ RETURNS void
+ AS '$regress_shlib'
+ LANGUAGE C STRICT;
+EOSQL
+my $slow_query = 'SELECT wait_pid(pg_backend_pid())';
+my ($stdout, $stderr);
+my $slow_client = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-d', $gnat->connstr('postgres'),
+ '-c', $slow_query
+ ],
+ '<',
+ \undef,
+ '>',
+ \$stdout,
+ '2>',
+ \$stderr,
+ IPC::Run::timeout(5 * $TestLib::timeout_default));
+ok( $gnat->poll_query_until(
+ 'postgres',
+ "SELECT 1 FROM pg_stat_activity WHERE query = '$slow_query'", '1'),
+ 'slow query started');
+my $slow_pid = $gnat->safe_psql('postgres',
+ "SELECT pid FROM pg_stat_activity WHERE query = '$slow_query'");
+$gnat->kill9;
+unlink($gnat->data_dir . '/postmaster.pid');
+$gnat->rotate_logfile; # on Windows, can't open old log for writing
+log_ipcs();
+# Reject ordinary startup. Retry for the same reasons poll_start() does,
+# every 0.1s for at least $TestLib::timeout_default seconds.
+my $pre_existing_msg = qr/pre-existing shared memory block/;
+{
+ my $max_attempts = 10 * $TestLib::timeout_default;
+ my $attempts = 0;
+ while ($attempts < $max_attempts)
+ {
+ last
+ if $gnat->start(fail_ok => 1)
+ || slurp_file($gnat->logfile) =~ $pre_existing_msg;
+ usleep(100_000);
+ $attempts++;
+ }
+}
+like(slurp_file($gnat->logfile),
+ $pre_existing_msg, 'detected live backend via shared memory');
+# Reject single-user startup.
+my $single_stderr;
+ok( !run_log(
+ [ 'postgres', '--single', '-D', $gnat->data_dir, 'template1' ],
+ '<', \undef, '2>', \$single_stderr),
+ 'live query blocks --single');
+print STDERR $single_stderr;
+like($single_stderr, $pre_existing_msg,
+ 'single-user mode detected live backend via shared memory');
+log_ipcs();
+
+# cleanup slow backend
+TestLib::system_log('pg_ctl', 'kill', 'QUIT', $slow_pid);
+$slow_client->finish; # client has detected backend termination
+log_ipcs();
+
+# now startup should work
+poll_start($gnat);
+log_ipcs();
+
+# finish testing
+$gnat->stop;
+log_ipcs();
+
+
+# We may need retries to start a new postmaster. Causes:
+# - kernel is slow to deliver SIGKILL
+# - postmaster parent is slow to waitpid()
+# - postmaster child is slow to exit in response to SIGQUIT
+# - postmaster child is slow to exit after postmaster death
+sub poll_start
+{
+ my ($node) = @_;
+
+ my $max_attempts = 10 * $TestLib::timeout_default;
+ my $attempts = 0;
+
+ while ($attempts < $max_attempts)
+ {
+ $node->start(fail_ok => 1) && return 1;
+
+ # Wait 0.1 second before retrying.
+ usleep(100_000);
+
+ # Clean up in case the start attempt just timed out or some such.
+ $node->stop('fast', fail_ok => 1);
+
+ $attempts++;
+ }
+
+ # Try one last time without fail_ok, which will BAIL_OUT unless it
+ # succeeds.
+ $node->start && return 1;
+ return 0;
+}
diff --git a/src/test/recovery/t/018_wal_optimize.pl b/src/test/recovery/t/018_wal_optimize.pl
new file mode 100644
index 0000000..69e1462
--- /dev/null
+++ b/src/test/recovery/t/018_wal_optimize.pl
@@ -0,0 +1,399 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test WAL replay when some operation has skipped WAL.
+#
+# These tests exercise code that once violated the mandate described in
+# src/backend/access/transam/README section "Skipping WAL for New
+# RelFileNode". The tests work by committing some transactions, initiating an
+# immediate shutdown, and confirming that the expected data survives recovery.
+# For many years, individual commands made the decision to skip WAL, hence the
+# frequent appearance of COPY in these tests.
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 38;
+
+sub check_orphan_relfilenodes
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($node, $test_name) = @_;
+
+ my $db_oid = $node->safe_psql('postgres',
+ "SELECT oid FROM pg_database WHERE datname = 'postgres'");
+ my $prefix = "base/$db_oid/";
+ my $filepaths_referenced = $node->safe_psql(
+ 'postgres', "
+ SELECT pg_relation_filepath(oid) FROM pg_class
+ WHERE reltablespace = 0 AND relpersistence <> 't' AND
+ pg_relation_filepath(oid) IS NOT NULL;");
+ is_deeply(
+ [
+ sort(map { "$prefix$_" }
+ grep(/^[0-9]+$/, slurp_dir($node->data_dir . "/$prefix")))
+ ],
+ [ sort split /\n/, $filepaths_referenced ],
+ $test_name);
+ return;
+}
+
+# We run this same test suite for both wal_level=minimal and replica.
+sub run_wal_optimize
+{
+ my $wal_level = shift;
+
+ my $node = get_new_node("node_$wal_level");
+ $node->init;
+ $node->append_conf(
+ 'postgresql.conf', qq(
+wal_level = $wal_level
+max_prepared_transactions = 1
+wal_log_hints = on
+wal_skip_threshold = 0
+#wal_debug = on
+));
+ $node->start;
+
+ # Setup
+ my $tablespace_dir = $node->basedir . '/tablespace_other';
+ mkdir($tablespace_dir);
+ my $result;
+
+ # Test redo of CREATE TABLESPACE.
+ $node->safe_psql(
+ 'postgres', "
+ CREATE TABLE moved (id int);
+ INSERT INTO moved VALUES (1);
+ CREATE TABLESPACE other LOCATION '$tablespace_dir';
+ BEGIN;
+ ALTER TABLE moved SET TABLESPACE other;
+ CREATE TABLE originated (id int);
+ INSERT INTO originated VALUES (1);
+ CREATE UNIQUE INDEX ON originated(id) TABLESPACE other;
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->safe_psql('postgres', "SELECT count(*) FROM moved;");
+ is($result, qq(1), "wal_level = $wal_level, CREATE+SET TABLESPACE");
+ $result = $node->safe_psql(
+ 'postgres', "
+ INSERT INTO originated VALUES (1) ON CONFLICT (id)
+ DO UPDATE set id = originated.id + 1
+ RETURNING id;");
+ is($result, qq(2),
+ "wal_level = $wal_level, CREATE TABLESPACE, CREATE INDEX");
+
+ # Test direct truncation optimization. No tuples.
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE trunc (id serial PRIMARY KEY);
+ TRUNCATE trunc;
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->safe_psql('postgres', "SELECT count(*) FROM trunc;");
+ is($result, qq(0), "wal_level = $wal_level, TRUNCATE with empty table");
+
+ # Test truncation with inserted tuples within the same transaction.
+ # Tuples inserted after the truncation should be seen.
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE trunc_ins (id serial PRIMARY KEY);
+ INSERT INTO trunc_ins VALUES (DEFAULT);
+ TRUNCATE trunc_ins;
+ INSERT INTO trunc_ins VALUES (DEFAULT);
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->safe_psql('postgres',
+ "SELECT count(*), min(id) FROM trunc_ins;");
+ is($result, qq(1|2), "wal_level = $wal_level, TRUNCATE INSERT");
+
+ # Same for prepared transaction.
+ # Tuples inserted after the truncation should be seen.
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE twophase (id serial PRIMARY KEY);
+ INSERT INTO twophase VALUES (DEFAULT);
+ TRUNCATE twophase;
+ INSERT INTO twophase VALUES (DEFAULT);
+ PREPARE TRANSACTION 't';
+ COMMIT PREPARED 't';");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->safe_psql('postgres',
+ "SELECT count(*), min(id) FROM trunc_ins;");
+ is($result, qq(1|2), "wal_level = $wal_level, TRUNCATE INSERT PREPARE");
+
+ # Writing WAL at end of xact, instead of syncing.
+ $node->safe_psql(
+ 'postgres', "
+ SET wal_skip_threshold = '1GB';
+ BEGIN;
+ CREATE TABLE noskip (id serial PRIMARY KEY);
+ INSERT INTO noskip (SELECT FROM generate_series(1, 20000) a) ;
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->safe_psql('postgres', "SELECT count(*) FROM noskip;");
+ is($result, qq(20000), "wal_level = $wal_level, end-of-xact WAL");
+
+ # Data file for COPY query in subsequent tests
+ my $basedir = $node->basedir;
+ my $copy_file = "$basedir/copy_data.txt";
+ TestLib::append_to_file(
+ $copy_file, qq(20000,30000
+20001,30001
+20002,30002));
+
+ # Test truncation with inserted tuples using both INSERT and COPY. Tuples
+ # inserted after the truncation should be seen.
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE ins_trunc (id serial PRIMARY KEY, id2 int);
+ INSERT INTO ins_trunc VALUES (DEFAULT, generate_series(1,10000));
+ TRUNCATE ins_trunc;
+ INSERT INTO ins_trunc (id, id2) VALUES (DEFAULT, 10000);
+ COPY ins_trunc FROM '$copy_file' DELIMITER ',';
+ INSERT INTO ins_trunc (id, id2) VALUES (DEFAULT, 10000);
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->safe_psql('postgres', "SELECT count(*) FROM ins_trunc;");
+ is($result, qq(5), "wal_level = $wal_level, TRUNCATE COPY INSERT");
+
+ # Test truncation with inserted tuples using COPY. Tuples copied after
+ # the truncation should be seen.
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE trunc_copy (id serial PRIMARY KEY, id2 int);
+ INSERT INTO trunc_copy VALUES (DEFAULT, generate_series(1,3000));
+ TRUNCATE trunc_copy;
+ COPY trunc_copy FROM '$copy_file' DELIMITER ',';
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result =
+ $node->safe_psql('postgres', "SELECT count(*) FROM trunc_copy;");
+ is($result, qq(3), "wal_level = $wal_level, TRUNCATE COPY");
+
+ # Like previous test, but rollback SET TABLESPACE in a subtransaction.
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE spc_abort (id serial PRIMARY KEY, id2 int);
+ INSERT INTO spc_abort VALUES (DEFAULT, generate_series(1,3000));
+ TRUNCATE spc_abort;
+ SAVEPOINT s;
+ ALTER TABLE spc_abort SET TABLESPACE other; ROLLBACK TO s;
+ COPY spc_abort FROM '$copy_file' DELIMITER ',';
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->safe_psql('postgres', "SELECT count(*) FROM spc_abort;");
+ is($result, qq(3),
+ "wal_level = $wal_level, SET TABLESPACE abort subtransaction");
+
+ # in different subtransaction patterns
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE spc_commit (id serial PRIMARY KEY, id2 int);
+ INSERT INTO spc_commit VALUES (DEFAULT, generate_series(1,3000));
+ TRUNCATE spc_commit;
+ SAVEPOINT s; ALTER TABLE spc_commit SET TABLESPACE other; RELEASE s;
+ COPY spc_commit FROM '$copy_file' DELIMITER ',';
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result =
+ $node->safe_psql('postgres', "SELECT count(*) FROM spc_commit;");
+ is($result, qq(3),
+ "wal_level = $wal_level, SET TABLESPACE commit subtransaction");
+
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE spc_nest (id serial PRIMARY KEY, id2 int);
+ INSERT INTO spc_nest VALUES (DEFAULT, generate_series(1,3000));
+ TRUNCATE spc_nest;
+ SAVEPOINT s;
+ ALTER TABLE spc_nest SET TABLESPACE other;
+ SAVEPOINT s2;
+ ALTER TABLE spc_nest SET TABLESPACE pg_default;
+ ROLLBACK TO s2;
+ SAVEPOINT s2;
+ ALTER TABLE spc_nest SET TABLESPACE pg_default;
+ RELEASE s2;
+ ROLLBACK TO s;
+ COPY spc_nest FROM '$copy_file' DELIMITER ',';
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->safe_psql('postgres', "SELECT count(*) FROM spc_nest;");
+ is($result, qq(3),
+ "wal_level = $wal_level, SET TABLESPACE nested subtransaction");
+
+ $node->safe_psql(
+ 'postgres', "
+ CREATE TABLE spc_hint (id int);
+ INSERT INTO spc_hint VALUES (1);
+ BEGIN;
+ ALTER TABLE spc_hint SET TABLESPACE other;
+ CHECKPOINT;
+ SELECT * FROM spc_hint; -- set hint bit
+ INSERT INTO spc_hint VALUES (2);
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->safe_psql('postgres', "SELECT count(*) FROM spc_hint;");
+ is($result, qq(2), "wal_level = $wal_level, SET TABLESPACE, hint bit");
+
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE idx_hint (c int PRIMARY KEY);
+ SAVEPOINT q; INSERT INTO idx_hint VALUES (1); ROLLBACK TO q;
+ CHECKPOINT;
+ INSERT INTO idx_hint VALUES (1); -- set index hint bit
+ INSERT INTO idx_hint VALUES (2);
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->psql('postgres',);
+ my ($ret, $stdout, $stderr) =
+ $node->psql('postgres', "INSERT INTO idx_hint VALUES (2);");
+ is($ret, qq(3), "wal_level = $wal_level, unique index LP_DEAD");
+ like(
+ $stderr,
+ qr/violates unique/,
+ "wal_level = $wal_level, unique index LP_DEAD message");
+
+ # UPDATE touches two buffers for one row.
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE upd (id serial PRIMARY KEY, id2 int);
+ INSERT INTO upd (id, id2) VALUES (DEFAULT, generate_series(1,10000));
+ COPY upd FROM '$copy_file' DELIMITER ',';
+ UPDATE upd SET id2 = id2 + 1;
+ DELETE FROM upd;
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->safe_psql('postgres', "SELECT count(*) FROM upd;");
+ is($result, qq(0),
+ "wal_level = $wal_level, UPDATE touches two buffers for one row");
+
+ # Test consistency of COPY with INSERT for table created in the same
+ # transaction.
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE ins_copy (id serial PRIMARY KEY, id2 int);
+ INSERT INTO ins_copy VALUES (DEFAULT, 1);
+ COPY ins_copy FROM '$copy_file' DELIMITER ',';
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->safe_psql('postgres', "SELECT count(*) FROM ins_copy;");
+ is($result, qq(4), "wal_level = $wal_level, INSERT COPY");
+
+ # Test consistency of COPY that inserts more to the same table using
+ # triggers. If the INSERTS from the trigger go to the same block data
+ # is copied to, and the INSERTs are WAL-logged, WAL replay will fail when
+ # it tries to replay the WAL record but the "before" image doesn't match,
+ # because not all changes were WAL-logged.
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE ins_trig (id serial PRIMARY KEY, id2 text);
+ CREATE FUNCTION ins_trig_before_row_trig() RETURNS trigger
+ LANGUAGE plpgsql as \$\$
+ BEGIN
+ IF new.id2 NOT LIKE 'triggered%' THEN
+ INSERT INTO ins_trig
+ VALUES (DEFAULT, 'triggered row before' || NEW.id2);
+ END IF;
+ RETURN NEW;
+ END; \$\$;
+ CREATE FUNCTION ins_trig_after_row_trig() RETURNS trigger
+ LANGUAGE plpgsql as \$\$
+ BEGIN
+ IF new.id2 NOT LIKE 'triggered%' THEN
+ INSERT INTO ins_trig
+ VALUES (DEFAULT, 'triggered row after' || NEW.id2);
+ END IF;
+ RETURN NEW;
+ END; \$\$;
+ CREATE TRIGGER ins_trig_before_row_insert
+ BEFORE INSERT ON ins_trig
+ FOR EACH ROW EXECUTE PROCEDURE ins_trig_before_row_trig();
+ CREATE TRIGGER ins_trig_after_row_insert
+ AFTER INSERT ON ins_trig
+ FOR EACH ROW EXECUTE PROCEDURE ins_trig_after_row_trig();
+ COPY ins_trig FROM '$copy_file' DELIMITER ',';
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result = $node->safe_psql('postgres', "SELECT count(*) FROM ins_trig;");
+ is($result, qq(9), "wal_level = $wal_level, COPY with INSERT triggers");
+
+ # Test consistency of INSERT, COPY and TRUNCATE in same transaction block
+ # with TRUNCATE triggers.
+ $node->safe_psql(
+ 'postgres', "
+ BEGIN;
+ CREATE TABLE trunc_trig (id serial PRIMARY KEY, id2 text);
+ CREATE FUNCTION trunc_trig_before_stat_trig() RETURNS trigger
+ LANGUAGE plpgsql as \$\$
+ BEGIN
+ INSERT INTO trunc_trig VALUES (DEFAULT, 'triggered stat before');
+ RETURN NULL;
+ END; \$\$;
+ CREATE FUNCTION trunc_trig_after_stat_trig() RETURNS trigger
+ LANGUAGE plpgsql as \$\$
+ BEGIN
+ INSERT INTO trunc_trig VALUES (DEFAULT, 'triggered stat before');
+ RETURN NULL;
+ END; \$\$;
+ CREATE TRIGGER trunc_trig_before_stat_truncate
+ BEFORE TRUNCATE ON trunc_trig
+ FOR EACH STATEMENT EXECUTE PROCEDURE trunc_trig_before_stat_trig();
+ CREATE TRIGGER trunc_trig_after_stat_truncate
+ AFTER TRUNCATE ON trunc_trig
+ FOR EACH STATEMENT EXECUTE PROCEDURE trunc_trig_after_stat_trig();
+ INSERT INTO trunc_trig VALUES (DEFAULT, 1);
+ TRUNCATE trunc_trig;
+ COPY trunc_trig FROM '$copy_file' DELIMITER ',';
+ COMMIT;");
+ $node->stop('immediate');
+ $node->start;
+ $result =
+ $node->safe_psql('postgres', "SELECT count(*) FROM trunc_trig;");
+ is($result, qq(4),
+ "wal_level = $wal_level, TRUNCATE COPY with TRUNCATE triggers");
+
+ # Test redo of temp table creation.
+ $node->safe_psql(
+ 'postgres', "
+ CREATE TEMP TABLE temp (id serial PRIMARY KEY, id2 text);");
+ $node->stop('immediate');
+ $node->start;
+ check_orphan_relfilenodes($node,
+ "wal_level = $wal_level, no orphan relfilenode remains");
+
+ return;
+}
+
+# Run same test suite for multiple wal_level values.
+run_wal_optimize("minimal");
+run_wal_optimize("replica");
diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl
new file mode 100644
index 0000000..f6dcf0a
--- /dev/null
+++ b/src/test/recovery/t/019_replslot_limit.pl
@@ -0,0 +1,428 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test for replication slot limit
+# Ensure that max_slot_wal_keep_size limits the number of WAL files to
+# be kept by replication slots.
+use strict;
+use warnings;
+
+use TestLib;
+use PostgresNode;
+
+use File::Path qw(rmtree);
+use Test::More tests => $TestLib::windows_os ? 16 : 20;
+use Time::HiRes qw(usleep);
+
+$ENV{PGDATABASE} = 'postgres';
+
+# Initialize primary node, setting wal-segsize to 1MB
+my $node_primary = get_new_node('primary');
+$node_primary->init(allows_streaming => 1, extra => ['--wal-segsize=1']);
+$node_primary->append_conf(
+ 'postgresql.conf', qq(
+min_wal_size = 2MB
+max_wal_size = 4MB
+log_checkpoints = yes
+));
+$node_primary->start;
+$node_primary->safe_psql('postgres',
+ "SELECT pg_create_physical_replication_slot('rep1')");
+
+# The slot state and remain should be null before the first connection
+my $result = $node_primary->safe_psql('postgres',
+ "SELECT restart_lsn IS NULL, wal_status is NULL, safe_wal_size is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'"
+);
+is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"');
+
+
+# Take backup
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create a standby linking to it using the replication slot
+my $node_standby = get_new_node('standby_1');
+$node_standby->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+$node_standby->append_conf('postgresql.conf', "primary_slot_name = 'rep1'");
+
+$node_standby->start;
+
+# Wait until standby has replayed enough data
+my $start_lsn = $node_primary->lsn('write');
+$node_primary->wait_for_catchup($node_standby, 'replay', $start_lsn);
+
+# Stop standby
+$node_standby->stop;
+
+# Preparation done, the slot is the state "reserved" now
+$result = $node_primary->safe_psql('postgres',
+ "SELECT wal_status, safe_wal_size IS NULL FROM pg_replication_slots WHERE slot_name = 'rep1'"
+);
+is($result, "reserved|t", 'check the catching-up state');
+
+# Advance WAL by five segments (= 5MB) on primary
+advance_wal($node_primary, 1);
+$node_primary->safe_psql('postgres', "CHECKPOINT;");
+
+# The slot is always "safe" when fitting max_wal_size
+$result = $node_primary->safe_psql('postgres',
+ "SELECT wal_status, safe_wal_size IS NULL FROM pg_replication_slots WHERE slot_name = 'rep1'"
+);
+is($result, "reserved|t",
+ 'check that it is safe if WAL fits in max_wal_size');
+
+advance_wal($node_primary, 4);
+$node_primary->safe_psql('postgres', "CHECKPOINT;");
+
+# The slot is always "safe" when max_slot_wal_keep_size is not set
+$result = $node_primary->safe_psql('postgres',
+ "SELECT wal_status, safe_wal_size IS NULL FROM pg_replication_slots WHERE slot_name = 'rep1'"
+);
+is($result, "reserved|t", 'check that slot is working');
+
+# The standby can reconnect to primary
+$node_standby->start;
+
+$start_lsn = $node_primary->lsn('write');
+$node_primary->wait_for_catchup($node_standby, 'replay', $start_lsn);
+
+$node_standby->stop;
+
+# Set max_slot_wal_keep_size on primary
+my $max_slot_wal_keep_size_mb = 6;
+$node_primary->append_conf(
+ 'postgresql.conf', qq(
+max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB
+));
+$node_primary->reload;
+
+# The slot is in safe state.
+
+$result = $node_primary->safe_psql('postgres',
+ "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'");
+is($result, "reserved", 'check that max_slot_wal_keep_size is working');
+
+# Advance WAL again then checkpoint, reducing remain by 2 MB.
+advance_wal($node_primary, 2);
+$node_primary->safe_psql('postgres', "CHECKPOINT;");
+
+# The slot is still working
+$result = $node_primary->safe_psql('postgres',
+ "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'");
+is($result, "reserved",
+ 'check that safe_wal_size gets close to the current LSN');
+
+# The standby can reconnect to primary
+$node_standby->start;
+$start_lsn = $node_primary->lsn('write');
+$node_primary->wait_for_catchup($node_standby, 'replay', $start_lsn);
+$node_standby->stop;
+
+# wal_keep_size overrides max_slot_wal_keep_size
+$result = $node_primary->safe_psql('postgres',
+ "ALTER SYSTEM SET wal_keep_size to '8MB'; SELECT pg_reload_conf();");
+# Advance WAL again then checkpoint, reducing remain by 6 MB.
+advance_wal($node_primary, 6);
+$result = $node_primary->safe_psql('postgres',
+ "SELECT wal_status as remain FROM pg_replication_slots WHERE slot_name = 'rep1'"
+);
+is($result, "extended",
+ 'check that wal_keep_size overrides max_slot_wal_keep_size');
+# restore wal_keep_size
+$result = $node_primary->safe_psql('postgres',
+ "ALTER SYSTEM SET wal_keep_size to 0; SELECT pg_reload_conf();");
+
+# The standby can reconnect to primary
+$node_standby->start;
+$start_lsn = $node_primary->lsn('write');
+$node_primary->wait_for_catchup($node_standby, 'replay', $start_lsn);
+$node_standby->stop;
+
+# Advance WAL again without checkpoint, reducing remain by 6 MB.
+advance_wal($node_primary, 6);
+
+# Slot gets into 'reserved' state
+$result = $node_primary->safe_psql('postgres',
+ "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'");
+is($result, "extended", 'check that the slot state changes to "extended"');
+
+# do checkpoint so that the next checkpoint runs too early
+$node_primary->safe_psql('postgres', "CHECKPOINT;");
+
+# Advance WAL again without checkpoint; remain goes to 0.
+advance_wal($node_primary, 1);
+
+# Slot gets into 'unreserved' state and safe_wal_size is negative
+$result = $node_primary->safe_psql('postgres',
+ "SELECT wal_status, safe_wal_size <= 0 FROM pg_replication_slots WHERE slot_name = 'rep1'"
+);
+is($result, "unreserved|t",
+ 'check that the slot state changes to "unreserved"');
+
+# The standby still can connect to primary before a checkpoint
+$node_standby->start;
+
+$start_lsn = $node_primary->lsn('write');
+$node_primary->wait_for_catchup($node_standby, 'replay', $start_lsn);
+
+$node_standby->stop;
+
+ok( !find_in_log(
+ $node_standby,
+ "requested WAL segment [0-9A-F]+ has already been removed"),
+ 'check that required WAL segments are still available');
+
+# Create one checkpoint, to improve stability of the next steps
+$node_primary->safe_psql('postgres', "CHECKPOINT;");
+
+# Prevent other checkpoints from occurring while advancing WAL segments
+$node_primary->safe_psql('postgres',
+ "ALTER SYSTEM SET max_wal_size='40MB'; SELECT pg_reload_conf()");
+
+# Advance WAL again. The slot loses the oldest segment by the next checkpoint
+my $logstart = get_log_size($node_primary);
+advance_wal($node_primary, 7);
+
+# Now create another checkpoint and wait until the WARNING is issued
+$node_primary->safe_psql('postgres',
+ 'ALTER SYSTEM RESET max_wal_size; SELECT pg_reload_conf()');
+$node_primary->safe_psql('postgres', "CHECKPOINT;");
+my $invalidated = 0;
+for (my $i = 0; $i < 10000; $i++)
+{
+ if (find_in_log(
+ $node_primary,
+ "invalidating slot \"rep1\" because its restart_lsn [0-9A-F/]+ exceeds max_slot_wal_keep_size",
+ $logstart))
+ {
+ $invalidated = 1;
+ last;
+ }
+ usleep(100_000);
+}
+ok($invalidated, 'check that slot invalidation has been logged');
+
+$result = $node_primary->safe_psql(
+ 'postgres',
+ qq[
+ SELECT slot_name, active, restart_lsn IS NULL, wal_status, safe_wal_size
+ FROM pg_replication_slots WHERE slot_name = 'rep1']);
+is($result, "rep1|f|t|lost|",
+ 'check that the slot became inactive and the state "lost" persists');
+
+# Wait until current checkpoint ends
+my $checkpoint_ended = 0;
+for (my $i = 0; $i < 10000; $i++)
+{
+ if (find_in_log($node_primary, "checkpoint complete: ", $logstart))
+ {
+ $checkpoint_ended = 1;
+ last;
+ }
+ usleep(100_000);
+}
+ok($checkpoint_ended, 'waited for checkpoint to end');
+
+# The invalidated slot shouldn't keep the old-segment horizon back;
+# see bug #17103: https://postgr.es/m/17103-004130e8f27782c9@postgresql.org
+# Test for this by creating a new slot and comparing its restart LSN
+# to the oldest existing file.
+my $redoseg = $node_primary->safe_psql('postgres',
+ "SELECT pg_walfile_name(lsn) FROM pg_create_physical_replication_slot('s2', true)"
+);
+my $oldestseg = $node_primary->safe_psql('postgres',
+ "SELECT pg_ls_dir AS f FROM pg_ls_dir('pg_wal') WHERE pg_ls_dir ~ '^[0-9A-F]{24}\$' ORDER BY 1 LIMIT 1"
+);
+$node_primary->safe_psql('postgres',
+ qq[SELECT pg_drop_replication_slot('s2')]);
+is($oldestseg, $redoseg, "check that segments have been removed");
+
+# The standby no longer can connect to the primary
+$logstart = get_log_size($node_standby);
+$node_standby->start;
+
+my $failed = 0;
+for (my $i = 0; $i < 10000; $i++)
+{
+ if (find_in_log(
+ $node_standby,
+ "requested WAL segment [0-9A-F]+ has already been removed",
+ $logstart))
+ {
+ $failed = 1;
+ last;
+ }
+ usleep(100_000);
+}
+ok($failed, 'check that replication has been broken');
+
+$node_primary->stop;
+$node_standby->stop;
+
+my $node_primary2 = get_new_node('primary2');
+$node_primary2->init(allows_streaming => 1);
+$node_primary2->append_conf(
+ 'postgresql.conf', qq(
+min_wal_size = 32MB
+max_wal_size = 32MB
+log_checkpoints = yes
+));
+$node_primary2->start;
+$node_primary2->safe_psql('postgres',
+ "SELECT pg_create_physical_replication_slot('rep1')");
+$backup_name = 'my_backup2';
+$node_primary2->backup($backup_name);
+
+$node_primary2->stop;
+$node_primary2->append_conf(
+ 'postgresql.conf', qq(
+max_slot_wal_keep_size = 0
+));
+$node_primary2->start;
+
+$node_standby = get_new_node('standby_2');
+$node_standby->init_from_backup($node_primary2, $backup_name,
+ has_streaming => 1);
+$node_standby->append_conf('postgresql.conf', "primary_slot_name = 'rep1'");
+$node_standby->start;
+my @result =
+ split(
+ '\n',
+ $node_primary2->safe_psql(
+ 'postgres',
+ "CREATE TABLE tt();
+ DROP TABLE tt;
+ SELECT pg_switch_wal();
+ CHECKPOINT;
+ SELECT 'finished';",
+ timeout => $TestLib::timeout_default));
+is($result[1], 'finished', 'check if checkpoint command is not blocked');
+
+$node_primary2->stop;
+$node_standby->stop;
+
+# The next test depends on Perl's `kill`, which apparently is not
+# portable to Windows. (It would be nice to use Test::More's `subtest`,
+# but that's not in the ancient version we require.)
+if ($TestLib::windows_os)
+{
+ done_testing();
+ exit;
+}
+
+# Get a slot terminated while the walsender is active
+# We do this by sending SIGSTOP to the walsender. Skip this on Windows.
+my $node_primary3 = get_new_node('primary3');
+$node_primary3->init(allows_streaming => 1, extra => ['--wal-segsize=1']);
+$node_primary3->append_conf(
+ 'postgresql.conf', qq(
+ min_wal_size = 2MB
+ max_wal_size = 2MB
+ log_checkpoints = yes
+ max_slot_wal_keep_size = 1MB
+ ));
+$node_primary3->start;
+$node_primary3->safe_psql('postgres',
+ "SELECT pg_create_physical_replication_slot('rep3')");
+# Take backup
+$backup_name = 'my_backup';
+$node_primary3->backup($backup_name);
+# Create standby
+my $node_standby3 = get_new_node('standby_3');
+$node_standby3->init_from_backup($node_primary3, $backup_name,
+ has_streaming => 1);
+$node_standby3->append_conf('postgresql.conf', "primary_slot_name = 'rep3'");
+$node_standby3->start;
+$node_primary3->wait_for_catchup($node_standby3->name, 'replay');
+my $senderpid = $node_primary3->safe_psql('postgres',
+ "SELECT pid FROM pg_stat_activity WHERE backend_type = 'walsender'");
+like($senderpid, qr/^[0-9]+$/, "have walsender pid $senderpid");
+my $receiverpid = $node_standby3->safe_psql('postgres',
+ "SELECT pid FROM pg_stat_activity WHERE backend_type = 'walreceiver'");
+like($receiverpid, qr/^[0-9]+$/, "have walreceiver pid $receiverpid");
+
+$logstart = get_log_size($node_primary3);
+# freeze walsender and walreceiver. Slot will still be active, but walreceiver
+# won't get anything anymore.
+kill 'STOP', $senderpid, $receiverpid;
+advance_wal($node_primary3, 2);
+
+my $max_attempts = $TestLib::timeout_default;
+while ($max_attempts-- >= 0)
+{
+ if (find_in_log(
+ $node_primary3,
+ "terminating process $senderpid to release replication slot \"rep3\"",
+ $logstart))
+ {
+ ok(1, "walsender termination logged");
+ last;
+ }
+ sleep 1;
+}
+
+# Now let the walsender continue; slot should be killed now.
+# (Must not let walreceiver run yet; otherwise the standby could start another
+# one before the slot can be killed)
+kill 'CONT', $senderpid;
+$node_primary3->poll_query_until('postgres',
+ "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep3'",
+ "lost")
+ or die "timed out waiting for slot to be lost";
+
+$max_attempts = $TestLib::timeout_default;
+while ($max_attempts-- >= 0)
+{
+ if (find_in_log(
+ $node_primary3,
+ 'invalidating slot "rep3" because its restart_lsn', $logstart))
+ {
+ ok(1, "slot invalidation logged");
+ last;
+ }
+ sleep 1;
+}
+
+# Now let the walreceiver continue, so that the node can be stopped cleanly
+kill 'CONT', $receiverpid;
+
+$node_primary3->stop;
+$node_standby3->stop;
+
+#####################################
+# Advance WAL of $node by $n segments
+sub advance_wal
+{
+ my ($node, $n) = @_;
+
+ # Advance by $n segments (= (16 * $n) MB) on primary
+ for (my $i = 0; $i < $n; $i++)
+ {
+ $node->safe_psql('postgres',
+ "CREATE TABLE t (); DROP TABLE t; SELECT pg_switch_wal();");
+ }
+ return;
+}
+
+# return the size of logfile of $node in bytes
+sub get_log_size
+{
+ my ($node) = @_;
+
+ return (stat $node->logfile)[7];
+}
+
+# find $pat in logfile of $node after $off-th byte
+sub find_in_log
+{
+ my ($node, $pat, $off) = @_;
+
+ $off = 0 unless defined $off;
+ my $log = TestLib::slurp_file($node->logfile);
+ return 0 if (length($log) <= $off);
+
+ $log = substr($log, $off);
+
+ return $log =~ m/$pat/;
+}
diff --git a/src/test/recovery/t/020_archive_status.pl b/src/test/recovery/t/020_archive_status.pl
new file mode 100644
index 0000000..777bf05
--- /dev/null
+++ b/src/test/recovery/t/020_archive_status.pl
@@ -0,0 +1,236 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+#
+# Tests related to WAL archiving and recovery.
+#
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 16;
+use Config;
+
+my $primary = get_new_node('primary');
+$primary->init(
+ has_archiving => 1,
+ allows_streaming => 1);
+$primary->append_conf('postgresql.conf', 'autovacuum = off');
+$primary->start;
+my $primary_data = $primary->data_dir;
+
+# Temporarily use an archive_command value to make the archiver fail,
+# knowing that archiving is enabled. Note that we cannot use a command
+# that does not exist as in this case the archiver process would just exit
+# without reporting the failure to pg_stat_archiver. This also cannot
+# use a plain "false" as that's unportable on Windows. So, instead, as
+# a portable solution, use an archive command based on a command known to
+# work but will fail: copy with an incorrect original path.
+my $incorrect_command =
+ $TestLib::windows_os
+ ? qq{copy "%p_does_not_exist" "%f_does_not_exist"}
+ : qq{cp "%p_does_not_exist" "%f_does_not_exist"};
+$primary->safe_psql(
+ 'postgres', qq{
+ ALTER SYSTEM SET archive_command TO '$incorrect_command';
+ SELECT pg_reload_conf();
+});
+
+# Save the WAL segment currently in use and switch to a new segment.
+# This will be used to track the activity of the archiver.
+my $segment_name_1 = $primary->safe_psql('postgres',
+ q{SELECT pg_walfile_name(pg_current_wal_lsn())});
+my $segment_path_1 = "pg_wal/archive_status/$segment_name_1";
+my $segment_path_1_ready = "$segment_path_1.ready";
+my $segment_path_1_done = "$segment_path_1.done";
+$primary->safe_psql(
+ 'postgres', q{
+ CREATE TABLE mine AS SELECT generate_series(1,10) AS x;
+ SELECT pg_switch_wal();
+ CHECKPOINT;
+});
+
+# Wait for an archive failure.
+$primary->poll_query_until('postgres',
+ q{SELECT failed_count > 0 FROM pg_stat_archiver}, 't')
+ or die "Timed out while waiting for archiving to fail";
+ok( -f "$primary_data/$segment_path_1_ready",
+ ".ready file exists for WAL segment $segment_name_1 waiting to be archived"
+);
+ok( !-f "$primary_data/$segment_path_1_done",
+ ".done file does not exist for WAL segment $segment_name_1 waiting to be archived"
+);
+
+is( $primary->safe_psql(
+ 'postgres', q{
+ SELECT archived_count, last_failed_wal
+ FROM pg_stat_archiver
+ }),
+ "0|$segment_name_1",
+ "pg_stat_archiver failed to archive $segment_name_1");
+
+# Crash the cluster for the next test in charge of checking that non-archived
+# WAL segments are not removed.
+$primary->stop('immediate');
+
+# Recovery tests for the archiving with a standby partially check
+# the recovery behavior when restoring a backup taken using a
+# snapshot with no pg_start/stop_backup. In this situation,
+# the recovered standby should enter first crash recovery then
+# switch to regular archive recovery. Note that the base backup
+# is taken here so as archive_command will fail. This is necessary
+# for the assumptions of the tests done with the standbys below.
+$primary->backup_fs_cold('backup');
+
+$primary->start;
+ok( -f "$primary_data/$segment_path_1_ready",
+ ".ready file for WAL segment $segment_name_1 still exists after crash recovery on primary"
+);
+
+# Allow WAL archiving again and wait for a success.
+$primary->safe_psql(
+ 'postgres', q{
+ ALTER SYSTEM RESET archive_command;
+ SELECT pg_reload_conf();
+});
+
+$primary->poll_query_until('postgres',
+ q{SELECT archived_count FROM pg_stat_archiver}, '1')
+ or die "Timed out while waiting for archiving to finish";
+
+ok(!-f "$primary_data/$segment_path_1_ready",
+ ".ready file for archived WAL segment $segment_name_1 removed");
+
+ok(-f "$primary_data/$segment_path_1_done",
+ ".done file for archived WAL segment $segment_name_1 exists");
+
+is( $primary->safe_psql(
+ 'postgres', q{ SELECT last_archived_wal FROM pg_stat_archiver }),
+ $segment_name_1,
+ "archive success reported in pg_stat_archiver for WAL segment $segment_name_1"
+);
+
+# Create some WAL activity and a new checkpoint so as the next standby can
+# create a restartpoint. As this standby starts in crash recovery because
+# of the cold backup taken previously, it needs a clean restartpoint to deal
+# with existing status files.
+my $segment_name_2 = $primary->safe_psql('postgres',
+ q{SELECT pg_walfile_name(pg_current_wal_lsn())});
+my $segment_path_2 = "pg_wal/archive_status/$segment_name_2";
+my $segment_path_2_ready = "$segment_path_2.ready";
+my $segment_path_2_done = "$segment_path_2.done";
+$primary->safe_psql(
+ 'postgres', q{
+ INSERT INTO mine SELECT generate_series(10,20) AS x;
+ CHECKPOINT;
+});
+
+# Switch to a new segment and use the returned LSN to make sure that
+# standbys have caught up to this point.
+my $primary_lsn = $primary->safe_psql(
+ 'postgres', q{
+ SELECT pg_switch_wal();
+});
+
+$primary->poll_query_until('postgres',
+ q{ SELECT last_archived_wal FROM pg_stat_archiver },
+ $segment_name_2)
+ or die "Timed out while waiting for archiving to finish";
+
+# Test standby with archive_mode = on.
+my $standby1 = get_new_node('standby');
+$standby1->init_from_backup($primary, 'backup', has_restoring => 1);
+$standby1->append_conf('postgresql.conf', "archive_mode = on");
+my $standby1_data = $standby1->data_dir;
+$standby1->start;
+
+# Wait for the replay of the segment switch done previously, ensuring
+# that all segments needed are restored from the archives.
+$standby1->poll_query_until('postgres',
+ qq{ SELECT pg_wal_lsn_diff(pg_last_wal_replay_lsn(), '$primary_lsn') >= 0 }
+) or die "Timed out while waiting for xlog replay on standby1";
+
+$standby1->safe_psql('postgres', q{CHECKPOINT});
+
+# Recovery with archive_mode=on does not keep .ready signal files inherited
+# from backup. Note that this WAL segment existed in the backup.
+ok( !-f "$standby1_data/$segment_path_1_ready",
+ ".ready file for WAL segment $segment_name_1 present in backup got removed with archive_mode=on on standby"
+);
+
+# Recovery with archive_mode=on should not create .ready files.
+# Note that this segment did not exist in the backup.
+ok( !-f "$standby1_data/$segment_path_2_ready",
+ ".ready file for WAL segment $segment_name_2 not created on standby when archive_mode=on on standby"
+);
+
+# Recovery with archive_mode = on creates .done files.
+ok( -f "$standby1_data/$segment_path_2_done",
+ ".done file for WAL segment $segment_name_2 created when archive_mode=on on standby"
+);
+
+# Test recovery with archive_mode = always, which should always keep
+# .ready files if archiving is enabled, though here we want the archive
+# command to fail to persist the .ready files. Note that this node
+# has inherited the archive command of the previous cold backup that
+# will cause archiving failures.
+my $standby2 = get_new_node('standby2');
+$standby2->init_from_backup($primary, 'backup', has_restoring => 1);
+$standby2->append_conf('postgresql.conf', 'archive_mode = always');
+my $standby2_data = $standby2->data_dir;
+$standby2->start;
+
+# Wait for the replay of the segment switch done previously, ensuring
+# that all segments needed are restored from the archives.
+$standby2->poll_query_until('postgres',
+ qq{ SELECT pg_wal_lsn_diff(pg_last_wal_replay_lsn(), '$primary_lsn') >= 0 }
+) or die "Timed out while waiting for xlog replay on standby2";
+
+$standby2->safe_psql('postgres', q{CHECKPOINT});
+
+ok( -f "$standby2_data/$segment_path_1_ready",
+ ".ready file for WAL segment $segment_name_1 existing in backup is kept with archive_mode=always on standby"
+);
+
+ok( -f "$standby2_data/$segment_path_2_ready",
+ ".ready file for WAL segment $segment_name_2 created with archive_mode=always on standby"
+);
+
+# Reset statistics of the archiver for the next checks.
+$standby2->safe_psql('postgres', q{SELECT pg_stat_reset_shared('archiver')});
+
+# Now crash the cluster to check that recovery step does not
+# remove non-archived WAL segments on a standby where archiving
+# is enabled.
+$standby2->stop('immediate');
+$standby2->start;
+
+ok( -f "$standby2_data/$segment_path_1_ready",
+ "WAL segment still ready to archive after crash recovery on standby with archive_mode=always"
+);
+
+# Allow WAL archiving again, and wait for the segments to be archived.
+$standby2->safe_psql(
+ 'postgres', q{
+ ALTER SYSTEM RESET archive_command;
+ SELECT pg_reload_conf();
+});
+$standby2->poll_query_until('postgres',
+ q{SELECT last_archived_wal FROM pg_stat_archiver},
+ $segment_name_2)
+ or die "Timed out while waiting for archiving to finish";
+
+is( $standby2->safe_psql(
+ 'postgres', q{SELECT archived_count FROM pg_stat_archiver}),
+ '2',
+ "correct number of WAL segments archived from standby");
+
+ok( !-f "$standby2_data/$segment_path_1_ready"
+ && !-f "$standby2_data/$segment_path_2_ready",
+ ".ready files removed after archive success with archive_mode=always on standby"
+);
+
+ok( -f "$standby2_data/$segment_path_1_done"
+ && -f "$standby2_data/$segment_path_2_done",
+ ".done files created after archive success with archive_mode=always on standby"
+);
diff --git a/src/test/recovery/t/021_row_visibility.pl b/src/test/recovery/t/021_row_visibility.pl
new file mode 100644
index 0000000..8a554c5
--- /dev/null
+++ b/src/test/recovery/t/021_row_visibility.pl
@@ -0,0 +1,209 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Checks that snapshots on standbys behave in a minimally reasonable
+# way.
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 10;
+use Config;
+
+# Initialize primary node
+my $node_primary = get_new_node('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->append_conf('postgresql.conf', 'max_prepared_transactions=10');
+$node_primary->start;
+
+# Initialize with empty test table
+$node_primary->safe_psql('postgres',
+ 'CREATE TABLE public.test_visibility (data text not null)');
+
+# Take backup
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create streaming standby from backup
+my $node_standby = get_new_node('standby');
+$node_standby->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+$node_standby->append_conf('postgresql.conf', 'max_prepared_transactions=10');
+$node_standby->start;
+
+my $psql_timeout =
+ IPC::Run::timer(2 * $TestLib::timeout_default);
+
+# One psql to primary and standby each, for all queries. That allows
+# to check uncommitted changes being replicated and such.
+my %psql_primary = (stdin => '', stdout => '', stderr => '');
+$psql_primary{run} = IPC::Run::start(
+ [ 'psql', '-XA', '-f', '-', '-d', $node_primary->connstr('postgres') ],
+ '<',
+ \$psql_primary{stdin},
+ '>',
+ \$psql_primary{stdout},
+ '2>',
+ \$psql_primary{stderr},
+ $psql_timeout);
+
+my %psql_standby = ('stdin' => '', 'stdout' => '', 'stderr' => '');
+$psql_standby{run} = IPC::Run::start(
+ [ 'psql', '-XA', '-f', '-', '-d', $node_standby->connstr('postgres') ],
+ '<',
+ \$psql_standby{stdin},
+ '>',
+ \$psql_standby{stdout},
+ '2>',
+ \$psql_standby{stderr},
+ $psql_timeout);
+
+#
+# 1. Check initial data is the same
+#
+ok( send_query_and_wait(
+ \%psql_standby,
+ q/SELECT * FROM test_visibility ORDER BY data;/,
+ qr/^\(0 rows\)$/m),
+ 'data not visible');
+
+#
+# 2. Check if an INSERT is replayed and visible
+#
+$node_primary->psql('postgres',
+ "INSERT INTO test_visibility VALUES ('first insert')");
+$node_primary->wait_for_catchup($node_standby, 'replay',
+ $node_primary->lsn('insert'));
+
+ok( send_query_and_wait(
+ \%psql_standby,
+ q[SELECT * FROM test_visibility ORDER BY data;],
+ qr/first insert.*\n\(1 row\)/m),
+ 'insert visible');
+
+#
+# 3. Verify that uncommitted changes aren't visible.
+#
+ok( send_query_and_wait(
+ \%psql_primary,
+ q[
+BEGIN;
+UPDATE test_visibility SET data = 'first update' RETURNING data;
+ ],
+ qr/^UPDATE 1$/m),
+ 'UPDATE');
+
+$node_primary->psql('postgres', "SELECT txid_current();"); # ensure WAL flush
+$node_primary->wait_for_catchup($node_standby, 'replay',
+ $node_primary->lsn('insert'));
+
+ok( send_query_and_wait(
+ \%psql_standby,
+ q[SELECT * FROM test_visibility ORDER BY data;],
+ qr/first insert.*\n\(1 row\)/m),
+ 'uncommitted update invisible');
+
+#
+# 4. That a commit turns 3. visible
+#
+ok(send_query_and_wait(\%psql_primary, q[COMMIT;], qr/^COMMIT$/m), 'COMMIT');
+
+$node_primary->wait_for_catchup($node_standby, 'replay',
+ $node_primary->lsn('insert'));
+
+ok( send_query_and_wait(
+ \%psql_standby,
+ q[SELECT * FROM test_visibility ORDER BY data;],
+ qr/first update\n\(1 row\)$/m),
+ 'committed update visible');
+
+#
+# 5. Check that changes in prepared xacts is invisible
+#
+ok( send_query_and_wait(
+ \%psql_primary, q[
+DELETE from test_visibility; -- delete old data, so we start with clean slate
+BEGIN;
+INSERT INTO test_visibility VALUES('inserted in prepared will_commit');
+PREPARE TRANSACTION 'will_commit';],
+ qr/^PREPARE TRANSACTION$/m),
+ 'prepared will_commit');
+
+ok( send_query_and_wait(
+ \%psql_primary, q[
+BEGIN;
+INSERT INTO test_visibility VALUES('inserted in prepared will_abort');
+PREPARE TRANSACTION 'will_abort';
+ ],
+ qr/^PREPARE TRANSACTION$/m),
+ 'prepared will_abort');
+
+$node_primary->wait_for_catchup($node_standby, 'replay',
+ $node_primary->lsn('insert'));
+
+ok( send_query_and_wait(
+ \%psql_standby,
+ q[SELECT * FROM test_visibility ORDER BY data;],
+ qr/^\(0 rows\)$/m),
+ 'uncommitted prepared invisible');
+
+# For some variation, finish prepared xacts via separate connections
+$node_primary->safe_psql('postgres', "COMMIT PREPARED 'will_commit';");
+$node_primary->safe_psql('postgres', "ROLLBACK PREPARED 'will_abort';");
+$node_primary->wait_for_catchup($node_standby, 'replay',
+ $node_primary->lsn('insert'));
+
+ok( send_query_and_wait(
+ \%psql_standby,
+ q[SELECT * FROM test_visibility ORDER BY data;],
+ qr/will_commit.*\n\(1 row\)$/m),
+ 'finished prepared visible');
+
+# explicitly shut down psql instances gracefully - to avoid hangs
+# or worse on windows
+$psql_primary{stdin} .= "\\q\n";
+$psql_primary{run}->finish;
+$psql_standby{stdin} .= "\\q\n";
+$psql_standby{run}->finish;
+
+$node_primary->stop;
+$node_standby->stop;
+
+# Send query, wait until string matches
+sub send_query_and_wait
+{
+ my ($psql, $query, $untl) = @_;
+ my $ret;
+
+ # send query
+ $$psql{stdin} .= $query;
+ $$psql{stdin} .= "\n";
+
+ # wait for query results
+ $$psql{run}->pump_nb();
+ while (1)
+ {
+ last if $$psql{stdout} =~ /$untl/;
+
+ if ($psql_timeout->is_expired)
+ {
+ BAIL_OUT("aborting wait: program timed out\n"
+ . "stream contents: >>$$psql{stdout}<<\n"
+ . "pattern searched for: $untl\n");
+ return 0;
+ }
+ if (not $$psql{run}->pumpable())
+ {
+ BAIL_OUT("aborting wait: program died\n"
+ . "stream contents: >>$$psql{stdout}<<\n"
+ . "pattern searched for: $untl\n");
+ return 0;
+ }
+ $$psql{run}->pump();
+ }
+
+ $$psql{stdout} = '';
+
+ return 1;
+}
diff --git a/src/test/recovery/t/022_crash_temp_files.pl b/src/test/recovery/t/022_crash_temp_files.pl
new file mode 100644
index 0000000..b0f5ba0
--- /dev/null
+++ b/src/test/recovery/t/022_crash_temp_files.pl
@@ -0,0 +1,268 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test remove of temporary files after a crash.
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More;
+use Config;
+
+if ($Config{osname} eq 'MSWin32')
+{
+ plan skip_all => 'tests hang on Windows';
+ exit;
+}
+else
+{
+ plan tests => 11;
+}
+
+
+my $psql_timeout = IPC::Run::timer($TestLib::timeout_default);
+
+my $node = get_new_node('node_crash');
+$node->init();
+$node->start();
+
+# By default, PostgresNode doesn't restart after crash
+# Reduce work_mem to generate temporary file with a few number of rows
+$node->safe_psql(
+ 'postgres',
+ q[ALTER SYSTEM SET remove_temp_files_after_crash = on;
+ ALTER SYSTEM SET log_connections = 1;
+ ALTER SYSTEM SET work_mem = '64kB';
+ ALTER SYSTEM SET restart_after_crash = on;
+ SELECT pg_reload_conf();]);
+
+# create table, insert rows
+$node->safe_psql('postgres', q[CREATE TABLE tab_crash (a integer UNIQUE);]);
+
+# Run psql, keeping session alive, so we have an alive backend to kill.
+my ($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+my $killme = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+ $node->connstr('postgres')
+ ],
+ '<',
+ \$killme_stdin,
+ '>',
+ \$killme_stdout,
+ '2>',
+ \$killme_stderr,
+ $psql_timeout);
+
+# Get backend pid
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok(pump_until($killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ 'acquired pid for SIGKILL');
+my $pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Open a 2nd session that will block the 1st one, using the UNIQUE constraint.
+# This will prevent removal of the temporary file created by the 1st session.
+my ($killme_stdin2, $killme_stdout2, $killme_stderr2) = ('', '', '');
+my $killme2 = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+ $node->connstr('postgres')
+ ],
+ '<',
+ \$killme_stdin2,
+ '>',
+ \$killme_stdout2,
+ '2>',
+ \$killme_stderr2,
+ $psql_timeout);
+
+# Insert one tuple and leave the transaction open
+$killme_stdin2 .= q[
+BEGIN;
+INSERT INTO tab_crash (a) VALUES(1);
+SELECT $$insert-tuple-to-lock-next-insert$$;
+];
+pump_until($killme2, $psql_timeout, \$killme_stdout2, qr/insert-tuple-to-lock-next-insert/m);
+$killme_stdout2 = '';
+$killme_stderr2 = '';
+
+# Run the query that generates a temporary file and that will be killed before
+# it finishes. Since the query that generates the temporary file does not
+# return before the connection is killed, use a SELECT before to trigger
+# pump_until.
+$killme_stdin .= q[
+BEGIN;
+SELECT $$in-progress-before-sigkill$$;
+INSERT INTO tab_crash (a) SELECT i FROM generate_series(1, 5000) s(i);
+];
+ok(pump_until($killme, $psql_timeout, \$killme_stdout, qr/in-progress-before-sigkill/m),
+ 'insert in-progress-before-sigkill');
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Wait until the batch insert gets stuck on the lock.
+$killme_stdin2 .= q[
+DO $c$
+DECLARE
+ c INT;
+BEGIN
+ LOOP
+ SELECT COUNT(*) INTO c FROM pg_locks WHERE pid = ] . $pid
+ . q[ AND NOT granted;
+ IF c > 0 THEN
+ EXIT;
+ END IF;
+ END LOOP;
+END; $c$;
+SELECT $$insert-tuple-lock-waiting$$;
+];
+
+pump_until($killme2, $psql_timeout, \$killme_stdout2, qr/insert-tuple-lock-waiting/m);
+$killme_stdout2 = '';
+$killme_stderr2 = '';
+
+# Kill with SIGKILL
+my $ret = TestLib::system_log('pg_ctl', 'kill', 'KILL', $pid);
+is($ret, 0, 'killed process with KILL');
+
+# Close that psql session
+$killme->finish;
+
+# Wait till the other session reports failure, ensuring that the postmaster
+# has noticed its dead child and begun a restart cycle.
+$killme_stdin2 .= qq[
+SELECT pg_sleep($TestLib::timeout_default);
+];
+ok( pump_until(
+ $killme2,
+ \$killme_stderr2,
+ qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost|could not send data to server/m
+ ),
+ "second psql session died successfully after SIGKILL");
+$killme2->finish;
+
+# Wait till server finishes restarting
+$node->poll_query_until('postgres', undef, '');
+
+# Check for temporary files
+is( $node->safe_psql(
+ 'postgres', 'SELECT COUNT(1) FROM pg_ls_dir($$base/pgsql_tmp$$)'),
+ qq(0),
+ 'no temporary files');
+
+#
+# Test old behavior (don't remove temporary files after crash)
+#
+$node->safe_psql(
+ 'postgres',
+ q[ALTER SYSTEM SET remove_temp_files_after_crash = off;
+ SELECT pg_reload_conf();]);
+
+# Restart psql session
+($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+$killme->run();
+
+# Get backend pid
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok(pump_until($killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ 'acquired pid for SIGKILL');
+$pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Restart the 2nd psql session
+($killme_stdin2, $killme_stdout2, $killme_stderr2) = ('', '', '');
+$killme2->run();
+
+# Insert one tuple and leave the transaction open
+$killme_stdin2 .= q[
+BEGIN;
+INSERT INTO tab_crash (a) VALUES(1);
+SELECT $$insert-tuple-to-lock-next-insert$$;
+];
+pump_until($killme2, $psql_timeout, \$killme_stdout2, qr/insert-tuple-to-lock-next-insert/m);
+$killme_stdout2 = '';
+$killme_stderr2 = '';
+
+# Run the query that generates a temporary file and that will be killed before
+# it finishes. Since the query that generates the temporary file does not
+# return before the connection is killed, use a SELECT before to trigger
+# pump_until.
+$killme_stdin .= q[
+BEGIN;
+SELECT $$in-progress-before-sigkill$$;
+INSERT INTO tab_crash (a) SELECT i FROM generate_series(1, 5000) s(i);
+];
+ok(pump_until($killme, $psql_timeout, \$killme_stdout, qr/in-progress-before-sigkill/m),
+ 'insert in-progress-before-sigkill');
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Wait until the batch insert gets stuck on the lock.
+$killme_stdin2 .= q[
+DO $c$
+DECLARE
+ c INT;
+BEGIN
+ LOOP
+ SELECT COUNT(*) INTO c FROM pg_locks WHERE pid = ] . $pid
+ . q[ AND NOT granted;
+ IF c > 0 THEN
+ EXIT;
+ END IF;
+ END LOOP;
+END; $c$;
+SELECT $$insert-tuple-lock-waiting$$;
+];
+
+pump_until($killme2, $psql_timeout, \$killme_stdout2, qr/insert-tuple-lock-waiting/m);
+$killme_stdout2 = '';
+$killme_stderr2 = '';
+
+# Kill with SIGKILL
+$ret = TestLib::system_log('pg_ctl', 'kill', 'KILL', $pid);
+is($ret, 0, 'killed process with KILL');
+
+# Close that psql session
+$killme->finish;
+
+# Wait till the other session reports failure, ensuring that the postmaster
+# has noticed its dead child and begun a restart cycle.
+$killme_stdin2 .= qq[
+SELECT pg_sleep($TestLib::timeout_default);
+];
+ok( pump_until(
+ $killme2,
+ \$killme_stderr2,
+ qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost|could not send data to server/m
+ ),
+ "second psql session died successfully after SIGKILL");
+$killme2->finish;
+
+# Wait till server finishes restarting
+$node->poll_query_until('postgres', undef, '');
+
+# Check for temporary files -- should be there
+is( $node->safe_psql(
+ 'postgres', 'SELECT COUNT(1) FROM pg_ls_dir($$base/pgsql_tmp$$)'),
+ qq(1),
+ 'one temporary file');
+
+# Restart should remove the temporary files
+$node->restart();
+
+# Check the temporary files -- should be gone
+is( $node->safe_psql(
+ 'postgres', 'SELECT COUNT(1) FROM pg_ls_dir($$base/pgsql_tmp$$)'),
+ qq(0),
+ 'temporary file was removed');
+
+$node->stop();
diff --git a/src/test/recovery/t/023_pitr_prepared_xact.pl b/src/test/recovery/t/023_pitr_prepared_xact.pl
new file mode 100644
index 0000000..9190a38
--- /dev/null
+++ b/src/test/recovery/t/023_pitr_prepared_xact.pl
@@ -0,0 +1,89 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test for point-in-time-recovery (PITR) with prepared transactions
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 1;
+use File::Compare;
+
+# Initialize and start primary node with WAL archiving
+my $node_primary = get_new_node('primary');
+$node_primary->init(has_archiving => 1, allows_streaming => 1);
+$node_primary->append_conf(
+ 'postgresql.conf', qq{
+max_prepared_transactions = 10});
+$node_primary->start;
+
+# Take backup
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Initialize node for PITR targeting a very specific restore point, just
+# after a PREPARE TRANSACTION is issued so as we finish with a promoted
+# node where this 2PC transaction needs an explicit COMMIT PREPARED.
+my $node_pitr = get_new_node('node_pitr');
+$node_pitr->init_from_backup(
+ $node_primary, $backup_name,
+ standby => 0,
+ has_restoring => 1);
+$node_pitr->append_conf(
+ 'postgresql.conf', qq{
+recovery_target_name = 'rp'
+recovery_target_action = 'promote'});
+
+# Workload with a prepared transaction and the target restore point.
+$node_primary->psql(
+ 'postgres', qq{
+CREATE TABLE foo(i int);
+BEGIN;
+INSERT INTO foo VALUES(1);
+PREPARE TRANSACTION 'fooinsert';
+SELECT pg_create_restore_point('rp');
+INSERT INTO foo VALUES(2);
+});
+
+# Find next WAL segment to be archived
+my $walfile_to_be_archived = $node_primary->safe_psql('postgres',
+ "SELECT pg_walfile_name(pg_current_wal_lsn());");
+
+# Make WAL segment eligible for archival
+$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal()');
+
+# Wait until the WAL segment has been archived.
+my $archive_wait_query =
+ "SELECT '$walfile_to_be_archived' <= last_archived_wal FROM pg_stat_archiver;";
+$node_primary->poll_query_until('postgres', $archive_wait_query)
+ or die "Timed out while waiting for WAL segment to be archived";
+my $last_archived_wal_file = $walfile_to_be_archived;
+
+# Now start the PITR node.
+$node_pitr->start;
+
+# Wait until the PITR node exits recovery.
+$node_pitr->poll_query_until('postgres', "SELECT pg_is_in_recovery() = 'f';")
+ or die "Timed out while waiting for PITR promotion";
+
+# Commit the prepared transaction in the latest timeline and check its
+# result. There should only be one row in the table, coming from the
+# prepared transaction. The row from the INSERT after the restore point
+# should not show up, since our recovery target was older than the second
+# INSERT done.
+$node_pitr->psql('postgres', qq{COMMIT PREPARED 'fooinsert';});
+my $result = $node_pitr->safe_psql('postgres', "SELECT * FROM foo;");
+is($result, qq{1}, "check table contents after COMMIT PREPARED");
+
+# Insert more data and do a checkpoint. These should be generated on the
+# timeline chosen after the PITR promotion.
+$node_pitr->psql(
+ 'postgres', qq{
+INSERT INTO foo VALUES(3);
+CHECKPOINT;
+});
+
+# Enforce recovery, the checkpoint record generated previously should
+# still be found.
+$node_pitr->stop('immediate');
+$node_pitr->start;
diff --git a/src/test/recovery/t/024_archive_recovery.pl b/src/test/recovery/t/024_archive_recovery.pl
new file mode 100644
index 0000000..1befa82
--- /dev/null
+++ b/src/test/recovery/t/024_archive_recovery.pl
@@ -0,0 +1,103 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test for archive recovery of WAL generated with wal_level=minimal
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 2;
+use Time::HiRes qw(usleep);
+
+# Initialize and start node with wal_level = replica and WAL archiving
+# enabled.
+my $node = get_new_node('orig');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $replica_config = q[
+wal_level = replica
+archive_mode = on
+max_wal_senders = 10
+hot_standby = off
+];
+$node->append_conf('postgresql.conf', $replica_config);
+$node->start;
+
+# Take backup
+my $backup_name = 'my_backup';
+$node->backup($backup_name);
+
+# Restart node with wal_level = minimal and WAL archiving disabled
+# to generate WAL with that setting. Note that such WAL has not been
+# archived yet at this moment because WAL archiving is not enabled.
+$node->append_conf(
+ 'postgresql.conf', q[
+wal_level = minimal
+archive_mode = off
+max_wal_senders = 0
+]);
+$node->restart;
+
+# Restart node with wal_level = replica and WAL archiving enabled
+# to archive WAL previously generated with wal_level = minimal.
+# We ensure the WAL file containing the record indicating the change
+# of wal_level to minimal is archived by checking pg_stat_archiver.
+$node->append_conf('postgresql.conf', $replica_config);
+$node->restart;
+
+# Find next WAL segment to be archived
+my $walfile_to_be_archived = $node->safe_psql('postgres',
+ "SELECT pg_walfile_name(pg_current_wal_lsn());");
+
+# Make WAL segment eligible for archival
+$node->safe_psql('postgres', 'SELECT pg_switch_wal()');
+my $archive_wait_query =
+ "SELECT '$walfile_to_be_archived' <= last_archived_wal FROM pg_stat_archiver;";
+
+# Wait until the WAL segment has been archived.
+$node->poll_query_until('postgres', $archive_wait_query)
+ or die "Timed out while waiting for WAL segment to be archived";
+
+$node->stop;
+
+# Initialize new node from backup, and start archive recovery. Check that
+# archive recovery fails with an error when it detects the WAL record
+# indicating the change of wal_level to minimal and node stops.
+sub test_recovery_wal_level_minimal
+{
+ my ($node_name, $node_text, $standby_setting) = @_;
+
+ my $recovery_node = get_new_node($node_name);
+ $recovery_node->init_from_backup(
+ $node, $backup_name,
+ has_restoring => 1,
+ standby => $standby_setting);
+
+ # Use run_log instead of recovery_node->start because this test expects
+ # that the server ends with an error during recovery.
+ run_log(
+ [
+ 'pg_ctl', '-D',
+ $recovery_node->data_dir, '-l',
+ $recovery_node->logfile, 'start'
+ ]);
+
+ # wait for postgres to terminate
+ foreach my $i (0 .. 10 * $TestLib::timeout_default)
+ {
+ last if !-f $recovery_node->data_dir . '/postmaster.pid';
+ usleep(100_000);
+ }
+
+ # Confirm that the archive recovery fails with an expected error
+ my $logfile = slurp_file($recovery_node->logfile());
+ ok( $logfile =~
+ qr/FATAL: .* WAL was generated with wal_level=minimal, cannot continue recovering/,
+ "$node_text ends with an error because it finds WAL generated with wal_level=minimal"
+ );
+}
+
+# Test for archive recovery
+test_recovery_wal_level_minimal('archive_recovery', 'archive recovery', 0);
+
+# Test for standby server
+test_recovery_wal_level_minimal('standby', 'standby', 1);
diff --git a/src/test/recovery/t/025_stuck_on_old_timeline.pl b/src/test/recovery/t/025_stuck_on_old_timeline.pl
new file mode 100644
index 0000000..023cab4
--- /dev/null
+++ b/src/test/recovery/t/025_stuck_on_old_timeline.pl
@@ -0,0 +1,111 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Testing streaming replication where standby is promoted and a new cascading
+# standby (without WAL) is connected to the promoted standby. Both archiving
+# and streaming are enabled, but only the history file is available from the
+# archive, so the WAL files all have to be streamed. Test that the cascading
+# standby can follow the new primary (promoted standby).
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+
+use File::Basename;
+use FindBin;
+use Test::More tests => 1;
+
+# Initialize primary node
+my $node_primary = get_new_node('primary');
+
+# Set up an archive command that will copy the history file but not the WAL
+# files. No real archive command should behave this way; the point is to
+# simulate a race condition where the new cascading standby starts up after
+# the timeline history file reaches the archive but before any of the WAL files
+# get there.
+$node_primary->init(allows_streaming => 1, has_archiving => 1);
+
+# Note: consistent use of forward slashes here avoids any escaping problems
+# that arise from use of backslashes. That means we need to double-quote all
+# the paths in the archive_command
+my $perlbin = $^X;
+$perlbin =~ s!\\!/!g if $TestLib::windows_os;
+my $archivedir_primary = $node_primary->archive_dir;
+$archivedir_primary =~ s!\\!/!g if $TestLib::windows_os;
+$node_primary->append_conf(
+ 'postgresql.conf', qq(
+archive_command = '"$perlbin" "$FindBin::RealBin/cp_history_files" "%p" "$archivedir_primary/%f"'
+wal_keep_size=128MB
+));
+# Make sure that Msys perl doesn't complain about difficulty in setting locale
+# when called from the archive_command.
+local $ENV{PERL_BADLANG} = 0;
+$node_primary->start;
+
+# Take backup from primary
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+# Create streaming standby linking to primary
+my $node_standby = get_new_node('standby');
+$node_standby->init_from_backup(
+ $node_primary, $backup_name,
+ allows_streaming => 1,
+ has_streaming => 1,
+ has_archiving => 1);
+$node_standby->start;
+
+# Take backup of standby, use -Xnone so that pg_wal is empty.
+$node_standby->backup($backup_name, backup_options => ['-Xnone']);
+
+# Create cascading standby but don't start it yet.
+# Must set up both streaming and archiving.
+my $node_cascade = get_new_node('cascade');
+$node_cascade->init_from_backup($node_standby, $backup_name,
+ has_streaming => 1);
+$node_cascade->enable_restoring($node_primary);
+$node_cascade->append_conf(
+ 'postgresql.conf', qq(
+recovery_target_timeline='latest'
+));
+
+# Promote the standby.
+$node_standby->promote;
+
+# Wait for promotion to complete
+$node_standby->poll_query_until('postgres', "SELECT NOT pg_is_in_recovery();")
+ or die "Timed out while waiting for promotion";
+
+# Find next WAL segment to be archived
+my $walfile_to_be_archived = $node_standby->safe_psql('postgres',
+ "SELECT pg_walfile_name(pg_current_wal_lsn());");
+
+# Make WAL segment eligible for archival
+$node_standby->safe_psql('postgres', 'SELECT pg_switch_wal()');
+
+# Wait until the WAL segment has been archived.
+# Since the history file gets created on promotion and is archived before any
+# WAL segment, this is enough to guarantee that the history file was
+# archived.
+my $archive_wait_query =
+ "SELECT '$walfile_to_be_archived' <= last_archived_wal FROM pg_stat_archiver";
+$node_standby->poll_query_until('postgres', $archive_wait_query)
+ or die "Timed out while waiting for WAL segment to be archived";
+my $last_archived_wal_file = $walfile_to_be_archived;
+
+# Start cascade node
+$node_cascade->start;
+
+# Create some content on promoted standby and check its presence on the
+# cascading standby.
+$node_standby->safe_psql('postgres', "CREATE TABLE tab_int AS SELECT 1 AS a");
+
+# Wait for the replication to catch up
+$node_standby->wait_for_catchup($node_cascade, 'replay',
+ $node_standby->lsn('insert'));
+
+# Check that cascading standby has the new content
+my $result =
+ $node_cascade->safe_psql('postgres', "SELECT count(*) FROM tab_int");
+print "cascade: $result\n";
+is($result, 1, 'check streamed content on cascade standby');
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
new file mode 100644
index 0000000..d451fe6
--- /dev/null
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -0,0 +1,109 @@
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Tests for already-propagated WAL segments ending in incomplete WAL records.
+
+use strict;
+use warnings;
+
+use FindBin;
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+plan tests => 3;
+
+# Test: Create a physical replica that's missing the last WAL file,
+# then restart the primary to create a divergent WAL file and observe
+# that the replica replays the "overwrite contrecord" from that new
+# file and the standby promotes successfully.
+
+my $node = PostgresNode->get_new_node('primary');
+$node->init(allows_streaming => 1);
+# We need these settings for stability of WAL behavior.
+$node->append_conf(
+ 'postgresql.conf', qq(
+autovacuum = off
+wal_keep_size = 1GB
+));
+$node->start;
+
+$node->safe_psql('postgres', 'create table filler (a int, b text)');
+
+# Now consume all remaining room in the current WAL segment, leaving
+# space enough only for the start of a largish record.
+$node->safe_psql(
+ 'postgres', q{
+DO $$
+DECLARE
+ wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
+ remain int;
+ iters int := 0;
+BEGIN
+ LOOP
+ INSERT into filler
+ select g, repeat(md5(g::text), (random() * 60 + 1)::int)
+ from generate_series(1, 10) g;
+
+ remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
+ IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
+ RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
+ EXIT;
+ END IF;
+ iters := iters + 1;
+ END LOOP;
+END
+$$;
+});
+
+my $initfile = $node->safe_psql('postgres',
+ 'SELECT pg_walfile_name(pg_current_wal_insert_lsn())');
+$node->safe_psql('postgres',
+ qq{SELECT pg_logical_emit_message(true, 'test 026', repeat('xyzxz', 123456))}
+);
+#$node->safe_psql('postgres', qq{create table foo ()});
+my $endfile = $node->safe_psql('postgres',
+ 'SELECT pg_walfile_name(pg_current_wal_insert_lsn())');
+ok($initfile ne $endfile, "$initfile differs from $endfile");
+
+# Now stop abruptly, to avoid a stop checkpoint. We can remove the tail file
+# afterwards, and on startup the large message should be overwritten with new
+# contents
+$node->stop('immediate');
+
+unlink $node->basedir . "/pgdata/pg_wal/$endfile"
+ or die "could not unlink " . $node->basedir . "/pgdata/pg_wal/$endfile: $!";
+
+# OK, create a standby at this spot.
+$node->backup_fs_cold('backup');
+my $node_standby = PostgresNode->get_new_node('standby');
+$node_standby->init_from_backup($node, 'backup', has_streaming => 1);
+
+$node_standby->start;
+$node->start;
+
+$node->safe_psql('postgres',
+ qq{create table foo (a text); insert into foo values ('hello')});
+$node->safe_psql('postgres',
+ qq{SELECT pg_logical_emit_message(true, 'test 026', 'AABBCC')});
+
+my $until_lsn = $node->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
+my $caughtup_query =
+ "SELECT '$until_lsn'::pg_lsn <= pg_last_wal_replay_lsn()";
+$node_standby->poll_query_until('postgres', $caughtup_query)
+ or die "Timed out while waiting for standby to catch up";
+
+ok($node_standby->safe_psql('postgres', 'select * from foo') eq 'hello',
+ 'standby replays past overwritten contrecord');
+
+# Verify message appears in standby's log
+my $log = slurp_file($node_standby->logfile);
+like(
+ $log,
+ qr[successfully skipped missing contrecord at],
+ "found log line in standby");
+
+# Verify promotion is successful
+$node_standby->promote;
+
+$node->stop;
+$node_standby->stop;
diff --git a/src/test/recovery/t/031_recovery_conflict.pl b/src/test/recovery/t/031_recovery_conflict.pl
new file mode 100644
index 0000000..72e0fa9
--- /dev/null
+++ b/src/test/recovery/t/031_recovery_conflict.pl
@@ -0,0 +1,377 @@
+# Copyright (c) 2021-2022, PostgreSQL Global Development Group
+
+# Test that connections to a hot standby are correctly canceled when a
+# recovery conflict is detected Also, test that statistics in
+# pg_stat_database_conflicts are populated correctly
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+plan skip_all => "disabled until after minor releases, due to instability";
+
+# Set up nodes
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+
+my $tablespace1 = "test_recovery_conflict_tblspc";
+
+$node_primary->append_conf(
+ 'postgresql.conf', qq[
+
+# Doesn't currently exist pre 15, but might be backpatched later
+#allow_in_place_tablespaces = on
+#temp_tablespaces = $tablespace1
+
+log_temp_files = 0
+
+# for deadlock test
+max_prepared_transactions = 10
+
+# wait some to test the wait paths as well, but not long for obvious reasons
+max_standby_streaming_delay = 50ms
+
+# Some of the recovery conflict logging code only gets exercised after
+# deadlock_timeout. The test doesn't rely on that additional output, but it's
+# nice to get some minimal coverage of that code.
+log_recovery_conflict_waits = on
+deadlock_timeout = 10ms
+]);
+$node_primary->start;
+
+my $backup_name = 'my_backup';
+
+# See allow_in_place_tablespaces comment above
+#$node_primary->safe_psql('postgres',
+# qq[CREATE TABLESPACE $tablespace1 LOCATION '']);
+
+$node_primary->backup($backup_name);
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+$node_standby->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+
+$node_standby->start;
+
+my $test_db = "test_db";
+
+# use a new database, to trigger database recovery conflict
+$node_primary->safe_psql('postgres', "CREATE DATABASE $test_db");
+
+# test schema / data
+my $table1 = "test_recovery_conflict_table1";
+my $table2 = "test_recovery_conflict_table2";
+$node_primary->safe_psql(
+ $test_db, qq[
+CREATE TABLE ${table1}(a int, b int);
+INSERT INTO $table1 SELECT i % 3, 0 FROM generate_series(1,20) i;
+CREATE TABLE ${table2}(a int, b int);
+]);
+my $primary_lsn = $node_primary->lsn('flush');
+$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
+
+
+# a longrunning psql that we can use to trigger conflicts
+my $psql_timeout = IPC::Run::timer($PostgreSQL::Test::Utils::timeout_default);
+my %psql_standby = ('stdin' => '', 'stdout' => '');
+$psql_standby{run} =
+ $node_standby->background_psql($test_db, \$psql_standby{stdin},
+ \$psql_standby{stdout},
+ $psql_timeout);
+$psql_standby{stdout} = '';
+
+my $expected_conflicts = 0;
+
+
+## RECOVERY CONFLICT 1: Buffer pin conflict
+my $sect = "buffer pin conflict";
+$expected_conflicts++;
+
+# Aborted INSERT on primary that will be cleaned up by vacuum. Has to be old
+# enough so that there's not a snapshot conflict before the buffer pin
+# conflict.
+
+$node_primary->safe_psql(
+ $test_db,
+ qq[
+ BEGIN;
+ INSERT INTO $table1 VALUES (1,0);
+ ROLLBACK;
+ -- ensure flush, rollback doesn't do so
+ BEGIN; LOCK $table1; COMMIT;
+ ]);
+
+$primary_lsn = $node_primary->lsn('flush');
+$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
+
+my $cursor1 = "test_recovery_conflict_cursor";
+
+# DECLARE and use a cursor on standby, causing buffer with the only block of
+# the relation to be pinned on the standby
+$psql_standby{stdin} .= qq[
+ BEGIN;
+ DECLARE $cursor1 CURSOR FOR SELECT b FROM $table1;
+ FETCH FORWARD FROM $cursor1;
+ ];
+# FETCH FORWARD should have returned a 0 since all values of b in the table
+# are 0
+ok(pump_until_standby(qr/^0$/m),
+ "$sect: cursor with conflicting pin established");
+
+# to check the log starting now for recovery conflict messages
+my $log_location = -s $node_standby->logfile;
+
+# VACUUM on the primary
+$node_primary->safe_psql($test_db, qq[VACUUM $table1;]);
+
+# Wait for catchup. Existing connection will be terminated before replay is
+# finished, so waiting for catchup ensures that there is no race between
+# encountering the recovery conflict which causes the disconnect and checking
+# the logfile for the terminated connection.
+$primary_lsn = $node_primary->lsn('flush');
+$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
+
+check_conflict_log("User was holding shared buffer pin for too long");
+reconnect_and_clear();
+check_conflict_stat("bufferpin");
+
+
+## RECOVERY CONFLICT 2: Snapshot conflict
+$sect = "snapshot conflict";
+$expected_conflicts++;
+
+$node_primary->safe_psql($test_db,
+ qq[INSERT INTO $table1 SELECT i, 0 FROM generate_series(1,20) i]);
+$primary_lsn = $node_primary->lsn('flush');
+$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
+
+# DECLARE and FETCH from cursor on the standby
+$psql_standby{stdin} .= qq[
+ BEGIN;
+ DECLARE $cursor1 CURSOR FOR SELECT b FROM $table1;
+ FETCH FORWARD FROM $cursor1;
+ ];
+ok( pump_until(
+ $psql_standby{run}, $psql_timeout,
+ \$psql_standby{stdout}, qr/^0$/m,),
+ "$sect: cursor with conflicting snapshot established");
+
+# Do some HOT updates
+$node_primary->safe_psql($test_db,
+ qq[UPDATE $table1 SET a = a + 1 WHERE a > 2;]);
+
+# VACUUM, pruning those dead tuples
+$node_primary->safe_psql($test_db, qq[VACUUM $table1;]);
+
+# Wait for attempted replay of PRUNE records
+$primary_lsn = $node_primary->lsn('flush');
+$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
+
+check_conflict_log(
+ "User query might have needed to see row versions that must be removed");
+reconnect_and_clear();
+check_conflict_stat("snapshot");
+
+
+## RECOVERY CONFLICT 3: Lock conflict
+$sect = "lock conflict";
+$expected_conflicts++;
+
+# acquire lock to conflict with
+$psql_standby{stdin} .= qq[
+ BEGIN;
+ LOCK TABLE $table1 IN ACCESS SHARE MODE;
+ SELECT 1;
+ ];
+ok(pump_until_standby(qr/^1$/m), "$sect: conflicting lock acquired");
+
+# DROP TABLE containing block which standby has in a pinned buffer
+$node_primary->safe_psql($test_db, qq[DROP TABLE $table1;]);
+
+$primary_lsn = $node_primary->lsn('flush');
+$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
+
+check_conflict_log("User was holding a relation lock for too long");
+reconnect_and_clear();
+check_conflict_stat("lock");
+
+
+# See allow_in_place_tablespaces comment above
+### RECOVERY CONFLICT 4: Tablespace conflict
+#$sect = "tablespace conflict";
+#$expected_conflicts++;
+#
+## DECLARE a cursor for a query which, with sufficiently low work_mem, will
+## spill tuples into temp files in the temporary tablespace created during
+## setup.
+#$psql_standby{stdin} .= qq[
+# BEGIN;
+# SET work_mem = '64kB';
+# DECLARE $cursor1 CURSOR FOR
+# SELECT count(*) FROM generate_series(1,6000);
+# FETCH FORWARD FROM $cursor1;
+# ];
+#ok(pump_until_standby(qr/^6000$/m),
+# "$sect: cursor with conflicting temp file established");
+#
+## Drop the tablespace currently containing spill files for the query on the
+## standby
+#$node_primary->safe_psql($test_db, qq[DROP TABLESPACE $tablespace1;]);
+#
+#$primary_lsn = $node_primary->lsn('flush');
+#$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
+#
+#check_conflict_log(
+# "User was or might have been using tablespace that must be dropped");
+#reconnect_and_clear();
+#check_conflict_stat("tablespace");
+
+
+## RECOVERY CONFLICT 5: Deadlock
+SKIP:
+{
+ skip "disabled until after minor releases, due to instability";
+
+$sect = "startup deadlock";
+$expected_conflicts++;
+
+# Generate a few dead rows, to later be cleaned up by vacuum. Then acquire a
+# lock on another relation in a prepared xact, so it's held continuously by
+# the startup process. The standby psql will block acquiring that lock while
+# holding a pin that vacuum needs, triggering the deadlock.
+$node_primary->safe_psql(
+ $test_db,
+ qq[
+CREATE TABLE $table1(a int, b int);
+INSERT INTO $table1 VALUES (1);
+BEGIN;
+INSERT INTO $table1(a) SELECT generate_series(1, 100) i;
+ROLLBACK;
+BEGIN;
+LOCK TABLE $table2;
+PREPARE TRANSACTION 'lock';
+INSERT INTO $table1(a) VALUES (170);
+SELECT txid_current();
+]);
+
+$primary_lsn = $node_primary->lsn('flush');
+$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
+
+$psql_standby{stdin} .= qq[
+ BEGIN;
+ -- hold pin
+ DECLARE $cursor1 CURSOR FOR SELECT a FROM $table1;
+ FETCH FORWARD FROM $cursor1;
+ -- wait for lock held by prepared transaction
+ SELECT * FROM $table2;
+ ];
+ok( pump_until(
+ $psql_standby{run}, $psql_timeout,
+ \$psql_standby{stdout}, qr/^1$/m,),
+ "$sect: cursor holding conflicting pin, also waiting for lock, established"
+);
+
+# just to make sure we're waiting for lock already
+ok( $node_standby->poll_query_until(
+ 'postgres', qq[
+SELECT 'waiting' FROM pg_locks WHERE locktype = 'relation' AND NOT granted;
+], 'waiting'),
+ "$sect: lock acquisition is waiting");
+
+# VACUUM will prune away rows, causing a buffer pin conflict, while standby
+# psql is waiting on lock
+$node_primary->safe_psql($test_db, qq[VACUUM $table1;]);
+$primary_lsn = $node_primary->lsn('flush');
+$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
+
+check_conflict_log("User transaction caused buffer deadlock with recovery.");
+reconnect_and_clear();
+check_conflict_stat("deadlock");
+
+# clean up for next tests
+$node_primary->safe_psql($test_db, qq[ROLLBACK PREPARED 'lock';]);
+}
+
+
+# Check that expected number of conflicts show in pg_stat_database. Needs to
+# be tested before database is dropped, for obvious reasons.
+is( $node_standby->safe_psql(
+ $test_db,
+ qq[SELECT conflicts FROM pg_stat_database WHERE datname='$test_db';]),
+ $expected_conflicts,
+ qq[$expected_conflicts recovery conflicts shown in pg_stat_database]);
+
+
+## RECOVERY CONFLICT 6: Database conflict
+$sect = "database conflict";
+
+$node_primary->safe_psql('postgres', qq[DROP DATABASE $test_db;]);
+
+$primary_lsn = $node_primary->lsn('flush');
+$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
+
+check_conflict_log("User was connected to a database that must be dropped");
+
+
+# explicitly shut down psql instances gracefully - to avoid hangs or worse on
+# windows
+$psql_standby{stdin} .= "\\q\n";
+$psql_standby{run}->finish;
+
+$node_standby->stop();
+$node_primary->stop();
+
+
+done_testing();
+
+
+sub pump_until_standby
+{
+ my $match = shift;
+
+ return pump_until($psql_standby{run}, $psql_timeout,
+ \$psql_standby{stdout}, $match);
+}
+
+sub reconnect_and_clear
+{
+ # If psql isn't dead already, tell it to quit as \q, when already dead,
+ # causes IPC::Run to unhelpfully error out with "ack Broken pipe:".
+ $psql_standby{run}->pump_nb();
+ if ($psql_standby{run}->pumpable())
+ {
+ $psql_standby{stdin} .= "\\q\n";
+ }
+ $psql_standby{run}->finish;
+
+ # restart
+ $psql_standby{run}->run();
+ $psql_standby{stdin} = '';
+ $psql_standby{stdout} = '';
+
+ # Run query to ensure connection has finished re-establishing
+ $psql_standby{stdin} .= qq[SELECT 1;\n];
+ die unless pump_until_standby(qr/^1$/m);
+ $psql_standby{stdout} = '';
+}
+
+sub check_conflict_log
+{
+ my $message = shift;
+ my $old_log_location = $log_location;
+
+ $log_location = $node_standby->wait_for_log(qr/$message/, $log_location);
+
+ cmp_ok($log_location, '>', $old_log_location,
+ "$sect: logfile contains terminated connection due to recovery conflict"
+ );
+}
+
+sub check_conflict_stat
+{
+ # Stats can't easily be checked before 15, requires waiting for stats to
+ # be reported to stats collector and then those messages need to be
+ # processed. Dealt with here to reduce intra-branch difference in the
+ # tests.
+}
diff --git a/src/test/recovery/t/033_replay_tsp_drops.pl b/src/test/recovery/t/033_replay_tsp_drops.pl
new file mode 100644
index 0000000..140d94d
--- /dev/null
+++ b/src/test/recovery/t/033_replay_tsp_drops.pl
@@ -0,0 +1,152 @@
+
+# Copyright (c) 2021-2022, PostgreSQL Global Development Group
+
+# Test replay of tablespace/database creation/drop
+
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+use Time::HiRes qw(usleep);
+
+sub test_tablespace
+{
+ my $node_primary = PostgreSQL::Test::Cluster->new("primary1");
+ $node_primary->init(allows_streaming => 1);
+ $node_primary->start;
+ $node_primary->psql(
+ 'postgres',
+ qq[
+ SET allow_in_place_tablespaces=on;
+ CREATE TABLESPACE dropme_ts1 LOCATION '';
+ CREATE TABLESPACE dropme_ts2 LOCATION '';
+ CREATE TABLESPACE source_ts LOCATION '';
+ CREATE TABLESPACE target_ts LOCATION '';
+ CREATE DATABASE template_db IS_TEMPLATE = true;
+ SELECT pg_create_physical_replication_slot('slot', true);
+ ]);
+ my $backup_name = 'my_backup';
+ $node_primary->backup($backup_name);
+
+ my $node_standby = PostgreSQL::Test::Cluster->new("standby2");
+ $node_standby->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+ $node_standby->append_conf('postgresql.conf',
+ "allow_in_place_tablespaces = on");
+ $node_standby->append_conf('postgresql.conf',
+ "primary_slot_name = slot");
+ $node_standby->start;
+
+ # Make sure the connection is made
+ $node_primary->wait_for_catchup($node_standby, 'write',
+ $node_primary->lsn('write'));
+
+ # Do immediate shutdown just after a sequence of CREATE DATABASE / DROP
+ # DATABASE / DROP TABLESPACE. This causes CREATE DATABASE WAL records
+ # to be applied to already-removed directories.
+ my $query = q[
+ CREATE DATABASE dropme_db1 WITH TABLESPACE dropme_ts1;
+ CREATE TABLE t (a int) TABLESPACE dropme_ts2;
+ CREATE DATABASE dropme_db2 WITH TABLESPACE dropme_ts2;
+ CREATE DATABASE moveme_db TABLESPACE source_ts;
+ ALTER DATABASE moveme_db SET TABLESPACE target_ts;
+ CREATE DATABASE newdb TEMPLATE template_db;
+ ALTER DATABASE template_db IS_TEMPLATE = false;
+ DROP DATABASE dropme_db1;
+ DROP TABLE t;
+ DROP DATABASE dropme_db2; DROP TABLESPACE dropme_ts2;
+ DROP TABLESPACE source_ts;
+ DROP DATABASE template_db;
+ ];
+
+ $node_primary->safe_psql('postgres', $query);
+ $node_primary->wait_for_catchup($node_standby, 'write',
+ $node_primary->lsn('write'));
+
+ # show "create missing directory" log message
+ $node_standby->safe_psql('postgres',
+ "ALTER SYSTEM SET log_min_messages TO debug1;");
+ $node_standby->stop('immediate');
+ # Should restart ignoring directory creation error.
+ is($node_standby->start(fail_ok => 1), 1, "standby node started");
+ $node_standby->stop('immediate');
+}
+
+test_tablespace();
+
+# Ensure that a missing tablespace directory during create database
+# replay immediately causes panic if the standby has already reached
+# consistent state (archive recovery is in progress).
+
+my $node_primary = PostgreSQL::Test::Cluster->new('primary2');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+# Create tablespace
+$node_primary->safe_psql(
+ 'postgres', q[
+ SET allow_in_place_tablespaces=on;
+ CREATE TABLESPACE ts1 LOCATION ''
+ ]);
+$node_primary->safe_psql('postgres',
+ "CREATE DATABASE db1 WITH TABLESPACE ts1");
+
+# Take backup
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+my $node_standby = PostgreSQL::Test::Cluster->new('standby3');
+$node_standby->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+$node_standby->append_conf('postgresql.conf',
+ "allow_in_place_tablespaces = on");
+$node_standby->start;
+
+# Make sure standby reached consistency and starts accepting connections
+$node_standby->poll_query_until('postgres', 'SELECT 1', '1');
+
+# Remove standby tablespace directory so it will be missing when
+# replay resumes.
+my $tspoid = $node_standby->safe_psql('postgres',
+ "SELECT oid FROM pg_tablespace WHERE spcname = 'ts1';");
+my $tspdir = $node_standby->data_dir . "/pg_tblspc/$tspoid";
+File::Path::rmtree($tspdir);
+
+my $logstart = -s $node_standby->logfile;
+
+# Create a database in the tablespace and a table in default tablespace
+$node_primary->safe_psql(
+ 'postgres',
+ q[
+ CREATE TABLE should_not_replay_insertion(a int);
+ CREATE DATABASE db2 WITH TABLESPACE ts1;
+ INSERT INTO should_not_replay_insertion VALUES (1);
+ ]);
+
+# Standby should fail and should not silently skip replaying the wal
+# In this test, PANIC turns into WARNING by allow_in_place_tablespaces.
+# Check the log messages instead of confirming standby failure.
+my $max_attempts = $PostgreSQL::Test::Utils::timeout_default * 10;
+while ($max_attempts-- >= 0)
+{
+ last
+ if (
+ find_in_log(
+ $node_standby, qr!WARNING: ( [A-Z0-9]+:)? creating missing directory: pg_tblspc/!,
+ $logstart));
+ usleep(100_000);
+}
+ok($max_attempts > 0, "invalid directory creation is detected");
+
+done_testing();
+
+# find $pat in logfile of $node after $off-th byte
+sub find_in_log
+{
+ my ($node, $pat, $off) = @_;
+
+ my $log = PostgreSQL::Test::Utils::slurp_file($node->logfile, $off);
+
+ return $log =~ m/$pat/;
+}
diff --git a/src/test/recovery/t/cp_history_files b/src/test/recovery/t/cp_history_files
new file mode 100644
index 0000000..cfeea41
--- /dev/null
+++ b/src/test/recovery/t/cp_history_files
@@ -0,0 +1,10 @@
+#!/usr/bin/perl
+
+use File::Copy;
+use strict;
+use warnings;
+
+die "wrong number of arguments" if @ARGV != 2;
+my ($source, $target) = @ARGV;
+exit if $source !~ /history/;
+copy($source, $target) or die "couldn't copy $source to $target: $!";
diff --git a/src/test/regress/.gitignore b/src/test/regress/.gitignore
new file mode 100644
index 0000000..89129d7
--- /dev/null
+++ b/src/test/regress/.gitignore
@@ -0,0 +1,11 @@
+# Local binaries
+/pg_regress
+
+# Generated subdirectories
+/tmp_check/
+/results/
+/log/
+
+# Note: regression.* are only left behind on a failure; that's why they're not ignored
+#/regression.diffs
+#/regression.out
diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile
new file mode 100644
index 0000000..fe6e0c9
--- /dev/null
+++ b/src/test/regress/GNUmakefile
@@ -0,0 +1,167 @@
+#-------------------------------------------------------------------------
+#
+# GNUmakefile--
+# Makefile for src/test/regress (the regression tests)
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/regress/GNUmakefile
+#
+#-------------------------------------------------------------------------
+
+PGFILEDESC = "pg_regress - test driver"
+PGAPPICON = win32
+
+subdir = src/test/regress
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+# maximum simultaneous connections for parallel tests
+MAXCONNOPT =
+ifdef MAX_CONNECTIONS
+MAXCONNOPT += --max-connections=$(MAX_CONNECTIONS)
+endif
+
+# stuff to pass into build of pg_regress
+EXTRADEFS = '-DHOST_TUPLE="$(host_tuple)"' \
+ '-DSHELLPROG="$(SHELL)"' \
+ '-DDLSUFFIX="$(DLSUFFIX)"'
+
+##
+## Prepare for tests
+##
+
+# Build regression test driver
+
+all: pg_regress$(X)
+
+pg_regress$(X): pg_regress.o pg_regress_main.o $(WIN32RES) | submake-libpgport
+ $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@
+
+# dependencies ensure that path changes propagate
+pg_regress.o: pg_regress.c $(top_builddir)/src/port/pg_config_paths.h
+pg_regress.o: override CPPFLAGS += -I$(top_builddir)/src/port $(EXTRADEFS)
+
+# note: because of the submake dependency, this rule's action is really a no-op
+$(top_builddir)/src/port/pg_config_paths.h: | submake-libpgport
+ $(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
+
+install: all installdirs
+ $(INSTALL_PROGRAM) pg_regress$(X) '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_regress$(X)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(pgxsdir)/$(subdir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_regress$(X)'
+
+
+# Build dynamically-loaded object file for CREATE FUNCTION ... LANGUAGE C.
+
+NAME = regress
+OBJS = $(WIN32RES) regress.o
+
+include $(top_srcdir)/src/Makefile.shlib
+
+all: all-lib
+
+# Ensure parallel safety if a build is started in this directory
+$(OBJS): | submake-libpgport submake-generated-headers
+
+# Test input and expected files. These are created by pg_regress itself, so we
+# don't have a rule to create them. We do need rules to clean them however.
+input_files = $(patsubst $(srcdir)/input/%.source,sql/%.sql, $(wildcard $(srcdir)/input/*.source))
+output_files := $(patsubst $(srcdir)/output/%.source,expected/%.out, $(wildcard $(srcdir)/output/*.source))
+
+
+# not installed by default
+
+regress_data_files = \
+ $(filter-out $(addprefix $(srcdir)/,$(output_files)),$(wildcard $(srcdir)/expected/*.out)) \
+ $(wildcard $(srcdir)/input/*.source) \
+ $(wildcard $(srcdir)/output/*.source) \
+ $(filter-out $(addprefix $(srcdir)/,$(input_files)),$(wildcard $(srcdir)/sql/*.sql)) \
+ $(wildcard $(srcdir)/data/*.data) \
+ $(srcdir)/parallel_schedule $(srcdir)/resultmap
+
+install-tests: all install install-lib installdirs-tests
+ $(MAKE) -C $(top_builddir)/contrib/spi install
+ for file in $(subst $(srcdir)/,,$(regress_data_files)); do \
+ $(INSTALL_DATA) $(srcdir)/$$file '$(DESTDIR)$(pkglibdir)/regress/'$$file || exit; \
+ done
+
+installdirs-tests: installdirs
+ $(MKDIR_P) $(patsubst $(srcdir)/%/,'$(DESTDIR)$(pkglibdir)/regress/%',$(sort $(dir $(regress_data_files))))
+
+
+# Get some extra C modules from contrib/spi
+
+all: refint$(DLSUFFIX) autoinc$(DLSUFFIX)
+
+refint$(DLSUFFIX): $(top_builddir)/contrib/spi/refint$(DLSUFFIX)
+ cp $< $@
+
+autoinc$(DLSUFFIX): $(top_builddir)/contrib/spi/autoinc$(DLSUFFIX)
+ cp $< $@
+
+$(top_builddir)/contrib/spi/refint$(DLSUFFIX): | submake-contrib-spi ;
+
+$(top_builddir)/contrib/spi/autoinc$(DLSUFFIX): | submake-contrib-spi ;
+
+submake-contrib-spi: | submake-libpgport submake-generated-headers
+ $(MAKE) -C $(top_builddir)/contrib/spi
+
+.PHONY: submake-contrib-spi
+
+
+##
+## Run tests
+##
+
+REGRESS_OPTS = --dlpath=. --max-concurrent-tests=20 --make-testtablespace-dir \
+ $(EXTRA_REGRESS_OPTS)
+
+check: all
+ $(pg_regress_check) $(REGRESS_OPTS) --schedule=$(srcdir)/parallel_schedule $(MAXCONNOPT) $(EXTRA_TESTS)
+
+check-tests: all | temp-install
+ $(pg_regress_check) $(REGRESS_OPTS) $(MAXCONNOPT) $(TESTS) $(EXTRA_TESTS)
+
+installcheck: all
+ $(pg_regress_installcheck) $(REGRESS_OPTS) --schedule=$(srcdir)/parallel_schedule --max-connections=1 $(EXTRA_TESTS)
+
+installcheck-parallel: all
+ $(pg_regress_installcheck) $(REGRESS_OPTS) --schedule=$(srcdir)/parallel_schedule $(MAXCONNOPT) $(EXTRA_TESTS)
+
+installcheck-tests: all
+ $(pg_regress_installcheck) $(REGRESS_OPTS) $(TESTS) $(EXTRA_TESTS)
+
+standbycheck: all
+ $(pg_regress_installcheck) $(REGRESS_OPTS) --schedule=$(srcdir)/standby_schedule --use-existing
+
+# old interfaces follow...
+
+runcheck: check
+runtest: installcheck
+runtest-parallel: installcheck-parallel
+
+bigtest: all
+ $(pg_regress_installcheck) $(REGRESS_OPTS) --schedule=$(srcdir)/parallel_schedule --max-connections=1 numeric_big
+
+bigcheck: all | temp-install
+ $(pg_regress_check) $(REGRESS_OPTS) --schedule=$(srcdir)/parallel_schedule $(MAXCONNOPT) numeric_big
+
+
+##
+## Clean up
+##
+
+clean distclean maintainer-clean: clean-lib
+# things built by `all' target
+ rm -f $(OBJS) refint$(DLSUFFIX) autoinc$(DLSUFFIX)
+ rm -f pg_regress_main.o pg_regress.o pg_regress$(X)
+# things created by various check targets
+ rm -f $(output_files) $(input_files)
+ rm -rf testtablespace
+ rm -rf $(pg_regress_clean_files)
diff --git a/src/test/regress/Makefile b/src/test/regress/Makefile
new file mode 100644
index 0000000..7c665ff
--- /dev/null
+++ b/src/test/regress/Makefile
@@ -0,0 +1,17 @@
+# The Postgres make files exploit features of GNU make that other makes
+# do not have. Because it is a common mistake for users to try to build
+# Postgres with a different make, we have this make file that does nothing
+# but tell the user to use GNU make.
+
+# If the user were using GNU make now, this file would not get used because
+# GNU make uses a make file named "GNUmakefile" in preference to "Makefile"
+# if it exists. Postgres is shipped with a "GNUmakefile".
+
+
+# AIX make defaults to building *every* target of the first rule. Start with
+# a single-target, empty rule to make the other targets non-default.
+all:
+
+all install clean check installcheck:
+ @echo "You must use GNU make to use Postgres. It may be installed"
+ @echo "on your system with the name 'gmake'."
diff --git a/src/test/regress/README b/src/test/regress/README
new file mode 100644
index 0000000..0cbf3b6
--- /dev/null
+++ b/src/test/regress/README
@@ -0,0 +1,3 @@
+Documentation concerning how to run these regression tests and interpret
+the results can be found in the PostgreSQL manual, in the chapter
+"Regression Tests".
diff --git a/src/test/regress/data/agg.data b/src/test/regress/data/agg.data
new file mode 100644
index 0000000..d92c7df
--- /dev/null
+++ b/src/test/regress/data/agg.data
@@ -0,0 +1,4 @@
+56 7.8
+100 99.097
+0 0.09561
+42 324.78
diff --git a/src/test/regress/data/array.data b/src/test/regress/data/array.data
new file mode 100644
index 0000000..394d851
--- /dev/null
+++ b/src/test/regress/data/array.data
@@ -0,0 +1,103 @@
+1 {92,75,71,52,64,83} {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038}
+2 {3,6} {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793}
+3 {37,64,95,43,3,41,13,30,11,43} {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246}
+4 {71,39,99,55,33,75,45} {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557}
+5 {50,42,77,50,4} {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104}
+6 {39,35,5,94,17,92,60,32} {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+7 {12,51,88,64,8} {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946}
+8 {60,84} {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407}
+9 {56,52,35,27,80,44,81,22} {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000}
+10 {71,5,45} {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249}
+11 {41,86,74,48,22,74,47,50} {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557}
+12 {17,99,18,52,91,72,0,43,96,23} {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+13 {3,52,34,23} {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658}
+14 {78,57,19} {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909}
+15 {17,14,16,63,67} {AA6416,AAAAAAAAAA646,AAAAA95309}
+16 {14,63,85,11} {AAAAAA66777}
+17 {7,10,81,85} {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356}
+18 {1} {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374}
+19 {52,82,17,74,23,46,69,51,75} {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+20 {72,89,70,51,54,37,8,49,79} {AAAAAA58494}
+21 {2,8,65,10,5,79,43} {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420}
+22 {11,6,56,62,53,30} {AAAAAAAA72908}
+23 {40,90,5,38,72,40,30,10,43,55} {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562}
+24 {94,61,99,35,48} {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219}
+25 {31,1,10,11,27,79,38} {AAAAAAAAAAAAAAAAAA59334,45449}
+26 {71,10,9,69,75} {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009}
+27 {94} {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254}
+28 {14,33,6,34,14} {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601}
+29 {39,21} {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194}
+30 {26,81,47,91,34} {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+31 {80,24,18,21,54} {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938}
+32 {58,79,82,80,67,75,98,10,41} {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533}
+33 {74,73} {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796}
+34 {70,45} {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242}
+35 {23,40} {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084}
+36 {79,82,14,52,30,5,79} {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598}
+37 {53,11,81,39,3,78,58,64,74} {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611}
+38 {59,5,4,95,28} {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387}
+39 {82,43,99,16,74} {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620}
+40 {34} {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623}
+41 {19,26,63,12,93,73,27,94} {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666}
+42 {15,76,82,75,8,91} {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587}
+43 {39,87,91,97,79,28} {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946}
+44 {40,58,68,29,54} {AAAAAAA81898,AAAAAA66777,AAAAAA98232}
+45 {99,45} {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+46 {53,24} {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621}
+47 {98,23,64,12,75,61} {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466}
+48 {76,14} {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037}
+49 {56,5,54,37,49} {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587}
+50 {20,12,37,64,93} {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955}
+51 {47} {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452}
+52 {89,0} {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862}
+53 {38,17} {AAAAAAAAAAA21658}
+54 {70,47} {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322}
+55 {47,79,47,64,72,25,71,24,93} {AAAAAAAAAAAAAAAAAA55796,AAAAA62737}
+56 {33,7,60,54,93,90,77,85,39} {AAAAAAAAAAAAAAAAAA32918,AA42406}
+57 {23,45,10,42,36,21,9,96} {AAAAAAAAAAAAAAAAAAA70415}
+58 {92} {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119}
+59 {9,69,46,77} {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955}
+60 {62,2,59,38,89} {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875}
+61 {72,2,44,95,54,54,13} {AAAAAAAAAAAAAAAAAAA91804}
+62 {83,72,29,73} {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617}
+63 {11,4,61,87} {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938}
+64 {26,19,34,24,81,78} {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+65 {61,5,76,59,17} {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+66 {31,23,70,52,4,33,48,25} {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836}
+67 {31,94,7,10} {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946}
+68 {90,43,38} {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643}
+69 {67,35,99,85,72,86,44} {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955}
+70 {56,70,83} {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242}
+71 {74,26} {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557}
+72 {22,1,16,78,20,91,83} {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+73 {88,25,96,78,65,15,29,19} {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598}
+74 {32} {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+75 {12,96,83,24,71,89,55} {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052}
+76 {92,55,10,7} {AAAAAAAAAAAAAAA67062}
+77 {97,15,32,17,55,59,18,37,50,39} {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+78 {55,89,44,84,34} {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620}
+79 {45} {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+80 {74,89,44,80,0} {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007}
+81 {63,77,54,48,61,53,97} {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121}
+82 {34,60,4,79,78,16,86,89,42,50} {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+83 {14,10} {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119}
+84 {11,83,35,13,96,94} {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183}
+85 {39,60} {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154}
+86 {33,81,72,74,45,36,82} {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176}
+87 {57,27,50,12,97,68} {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505}
+88 {41,90,77,24,6,24} {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+89 {40,32,17,6,30,88} {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+90 {88,75} {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526}
+91 {78} {AAAAAAAAAAAAA62007,AAA99043}
+92 {85,63,49,45} {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089}
+93 {11} {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383}
+94 {98,9,85,62,88,91,60,61,38,86} {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587}
+95 {47,77} {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483}
+96 {23,97,43} {AAAAAAAAAA646,A87088}
+97 {54,2,86,65} {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+98 {38,34,32,89} {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+99 {37,86} {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356}
+100 {85,32,57,39,49,84,32,3,30} {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+101 {} {}
+102 {NULL} {NULL}
+103 \N \N
diff --git a/src/test/regress/data/constrf.data b/src/test/regress/data/constrf.data
new file mode 100644
index 0000000..6ae26eb
--- /dev/null
+++ b/src/test/regress/data/constrf.data
@@ -0,0 +1,2 @@
+5 !check failed 6
+7 check failed 6
diff --git a/src/test/regress/data/constro.data b/src/test/regress/data/constro.data
new file mode 100644
index 0000000..00f8af4
--- /dev/null
+++ b/src/test/regress/data/constro.data
@@ -0,0 +1,2 @@
+4 !check failed 5
+6 OK 4
diff --git a/src/test/regress/data/dept.data b/src/test/regress/data/dept.data
new file mode 100644
index 0000000..c35dd57
--- /dev/null
+++ b/src/test/regress/data/dept.data
@@ -0,0 +1,2 @@
+toy sharon
+shoe bob
diff --git a/src/test/regress/data/desc.data b/src/test/regress/data/desc.data
new file mode 100644
index 0000000..4ff1457
--- /dev/null
+++ b/src/test/regress/data/desc.data
@@ -0,0 +1,10000 @@
+9999 1227676208
+9998 1673273198
+9997 868304211
+9996 999647871
+9995 310717580
+9994 1600952573
+9993 1081719182
+9992 242349705
+9991 1831758177
+9990 463935293
+9989 2044302343
+9988 1923557716
+9987 1243580926
+9986 18212056
+9985 757774765
+9984 1995958541
+9983 677073243
+9982 1290996901
+9981 947110578
+9980 1807402518
+9979 1756582238
+9978 910567096
+9977 2071380026
+9976 924516791
+9975 780851952
+9974 1605398760
+9973 1596663587
+9972 1261944440
+9971 625318191
+9970 1416577811
+9969 1272626725
+9968 228028337
+9967 1362555617
+9966 1414835286
+9965 2065412336
+9964 68367875
+9963 1916678044
+9962 617783889
+9961 345531010
+9960 2055684109
+9959 1367838015
+9958 2026090287
+9957 1165782951
+9956 1395106032
+9955 1488622461
+9954 1614261512
+9953 1048847963
+9952 1017154373
+9951 1681898311
+9950 36543482
+9949 1883506140
+9948 832065446
+9947 129715143
+9946 465981266
+9945 1475336852
+9944 1666391160
+9943 980080569
+9942 180085775
+9941 2136801363
+9940 397289853
+9939 54022194
+9938 2005275087
+9937 310099649
+9936 1294187742
+9935 1645640890
+9934 1447628447
+9933 1870320513
+9932 2008477583
+9931 1397429521
+9930 466924371
+9929 889901157
+9928 2120215631
+9927 537467826
+9926 1699005087
+9925 346258069
+9924 471468088
+9923 2079846849
+9922 1012304481
+9921 1281131881
+9920 849832864
+9919 2054311986
+9918 1417524873
+9917 1504212242
+9916 610807631
+9915 1633384345
+9914 1295251077
+9913 1677073445
+9912 582790715
+9911 126063581
+9910 131526276
+9909 87190204
+9908 907318099
+9907 359634197
+9906 1009954849
+9905 1571350877
+9904 1784646955
+9903 50198926
+9902 1403396142
+9901 1118576425
+9900 1424697538
+9899 2076940193
+9898 1338379718
+9897 1773957562
+9896 65999738
+9895 1766641886
+9894 1481437235
+9893 1337819855
+9892 1230013984
+9891 1105476143
+9890 2011090655
+9889 1493104270
+9888 1443504355
+9887 1931624176
+9886 208961165
+9885 1081217833
+9884 1050593630
+9883 1169187495
+9882 1545547169
+9881 495600511
+9880 1366229130
+9879 1919375727
+9878 1224719003
+9877 1483450870
+9876 722470890
+9875 959755923
+9874 167954735
+9873 666070529
+9872 772985035
+9871 1473939597
+9870 1927680355
+9869 1798223624
+9868 2010940455
+9867 1719221479
+9866 292520326
+9865 875663531
+9864 536627902
+9863 375961092
+9862 1474212847
+9861 1884393362
+9860 1809455435
+9859 79466479
+9858 1284143105
+9857 362286522
+9856 881030546
+9855 1187257317
+9854 1683154312
+9853 554993119
+9852 1950442013
+9851 1773655090
+9850 1418365156
+9849 2030261908
+9848 1196904837
+9847 264963079
+9846 1315496134
+9845 56400360
+9844 186770888
+9843 841498786
+9842 885873822
+9841 1122245059
+9840 1610482790
+9839 208458876
+9838 1505703298
+9837 1135276924
+9836 1182593577
+9835 2064042882
+9834 1548934331
+9833 799718188
+9832 713989305
+9831 1394746368
+9830 600250256
+9829 1447168913
+9828 1345919581
+9827 96885788
+9826 826615858
+9825 326037427
+9824 1384298952
+9823 2056982869
+9822 1284111611
+9821 2067663753
+9820 576750253
+9819 1153402076
+9818 714765773
+9817 1140504476
+9816 78192191
+9815 473997348
+9814 1318010186
+9813 1212009477
+9812 1378499644
+9811 677414946
+9810 1764025409
+9809 475205866
+9808 1173348946
+9807 1589144064
+9806 1733826240
+9805 382875389
+9804 1350053577
+9803 154187963
+9802 199467931
+9801 1414304039
+9800 48826786
+9799 503364468
+9798 620553055
+9797 1019882154
+9796 860070484
+9795 917116636
+9794 1189409464
+9793 1464118846
+9792 1480232616
+9791 130709534
+9790 1352897980
+9789 1583729425
+9788 1075209885
+9787 240768425
+9786 1969977938
+9785 1013666362
+9784 1242981351
+9783 640595240
+9782 1595467716
+9781 903293778
+9780 1651549647
+9779 174881345
+9778 888863273
+9777 790473557
+9776 239090487
+9775 1579638277
+9774 183407458
+9773 2083233185
+9772 105361177
+9771 1843587111
+9770 793750984
+9769 1176428280
+9768 1790777632
+9767 1850920067
+9766 1977956338
+9765 1543435285
+9764 1584367668
+9763 1058699929
+9762 111220866
+9761 2043986839
+9760 1202983297
+9759 1112129554
+9758 1761235135
+9757 61543522
+9756 1145270722
+9755 1329382697
+9754 1565682294
+9753 339687573
+9752 1136529241
+9751 1420586371
+9750 14430505
+9749 861076090
+9748 2083274506
+9747 1456708644
+9746 607066099
+9745 303340949
+9744 1474277100
+9743 487303995
+9742 1289482201
+9741 1076416545
+9740 52809478
+9739 1090314565
+9738 1345955589
+9737 247342347
+9736 266552398
+9735 919256409
+9734 1432214419
+9733 1687864477
+9732 2003200280
+9731 1146574960
+9730 282751704
+9729 1141439775
+9728 2114342480
+9727 431852437
+9726 643344876
+9725 805583149
+9724 192853456
+9723 145095923
+9722 325257068
+9721 275453150
+9720 1484795513
+9719 705205508
+9718 254009991
+9717 1779933556
+9716 2129915192
+9715 119762104
+9714 1161342396
+9713 397860555
+9712 434494516
+9711 199167636
+9710 1877944603
+9709 1952950779
+9708 823762166
+9707 426699180
+9706 962611576
+9705 726171569
+9704 1063539777
+9703 285639459
+9702 1405112773
+9701 861760505
+9700 1179716127
+9699 1998382914
+9698 498094899
+9697 1308759331
+9696 238998981
+9695 498248952
+9694 480326081
+9693 2064883954
+9692 807784058
+9691 1767535207
+9690 21443159
+9689 1852345604
+9688 722773964
+9687 134247887
+9686 618591160
+9685 1732054637
+9684 1832751235
+9683 962174759
+9682 667399599
+9681 629027385
+9680 1522889118
+9679 1451245423
+9678 990339203
+9677 97590597
+9676 1510643051
+9675 676972117
+9674 1468542443
+9673 201779272
+9672 1253406979
+9671 1554213507
+9670 363665606
+9669 2018440444
+9668 1759383933
+9667 2147329594
+9666 828433250
+9665 321598675
+9664 1837948542
+9663 860274521
+9662 2043440795
+9661 1102922102
+9660 1044761243
+9659 2034678919
+9658 1233754444
+9657 1138202974
+9656 448980300
+9655 1803900049
+9654 1064655038
+9653 1203723850
+9652 1586769289
+9651 1363637824
+9650 1786171829
+9649 1425298520
+9648 2088086019
+9647 313367086
+9646 776531802
+9645 1308863779
+9644 1571048785
+9643 2061812584
+9642 1985597314
+9641 1382450183
+9640 1942313221
+9639 363819659
+9638 1190007193
+9637 1437785258
+9636 309381052
+9635 2115642377
+9634 425641528
+9633 735026440
+9632 1962996926
+9631 8761875
+9630 2016651306
+9629 2054041917
+9628 1585698619
+9627 1577338043
+9626 73547936
+9625 1392740097
+9624 217130759
+9623 1848500861
+9622 1565035669
+9621 161470769
+9620 1423035453
+9619 1472804743
+9618 648766718
+9617 779222240
+9616 889801949
+9615 862202865
+9614 1470750113
+9613 188598602
+9612 119499363
+9611 1621777654
+9610 192442990
+9609 504527963
+9608 54438607
+9607 1221848464
+9606 1012143730
+9605 1721838260
+9604 152645451
+9603 416879652
+9602 865858782
+9601 2056438657
+9600 570546904
+9599 439313263
+9598 1980493980
+9597 192958522
+9596 1360207283
+9595 372530723
+9594 1975188076
+9593 55659990
+9592 425465408
+9591 92230926
+9590 1660187698
+9589 643813213
+9588 583002794
+9587 1934047501
+9586 1455955774
+9585 701203347
+9584 742703502
+9583 1996456107
+9582 2143639260
+9581 1762455048
+9580 1567339047
+9579 1118078173
+9578 1639867880
+9577 480083994
+9576 1069203013
+9575 595264078
+9574 855979478
+9573 243690442
+9572 1993816396
+9571 426545519
+9570 75944676
+9569 377588382
+9568 1226589627
+9567 1607963257
+9566 365254093
+9565 1304547293
+9564 2094548962
+9563 1882957150
+9562 542955940
+9561 1929135843
+9560 1656711780
+9559 1873623845
+9558 1335341086
+9557 2029283095
+9556 1191343998
+9555 1606983315
+9554 705047735
+9553 1127732102
+9552 429117059
+9551 1025561086
+9550 122587167
+9549 1087255053
+9548 48875160
+9547 1044603802
+9546 1771588164
+9545 825512571
+9544 748931329
+9543 429433959
+9542 167745765
+9541 1616228014
+9540 1347439539
+9539 615465067
+9538 12334288
+9537 2069525982
+9536 1660897943
+9535 629780591
+9534 761591353
+9533 165413119
+9532 226245370
+9531 816815742
+9530 593794757
+9529 1774912333
+9528 682279847
+9527 1875841419
+9526 1324235360
+9525 63611896
+9524 1177866256
+9523 1826970296
+9522 1005144935
+9521 1489345654
+9520 976685926
+9519 1225467013
+9518 1463150537
+9517 1370846236
+9516 295672473
+9515 1342154204
+9514 657766806
+9513 1280186963
+9512 1229478068
+9511 1699764346
+9510 1603893726
+9509 1425397205
+9508 1102050772
+9507 1530037345
+9506 1307934629
+9505 1495484824
+9504 403535220
+9503 2092259258
+9502 1719102010
+9501 598816685
+9500 134535895
+9499 865436986
+9498 450676973
+9497 618667951
+9496 697975163
+9495 1644748711
+9494 1205950609
+9493 1836004249
+9492 850284370
+9491 1927161570
+9490 26195117
+9489 1753323338
+9488 929794540
+9487 120996332
+9486 713079430
+9485 1162969158
+9484 112676136
+9483 1105486107
+9482 1823776885
+9481 1951564511
+9480 597713574
+9479 73856380
+9478 117462575
+9477 1754049596
+9476 1126502125
+9475 1363159019
+9474 1923866462
+9473 1952202183
+9472 1957723363
+9471 853665024
+9470 148139712
+9469 1663351592
+9468 167461823
+9467 953411909
+9466 1560200990
+9465 1009454561
+9464 794464341
+9463 1426272687
+9462 1809809132
+9461 1244444680
+9460 997367030
+9459 2052682433
+9458 1040243907
+9457 1914309030
+9456 8320196
+9455 1755076971
+9454 1486675921
+9453 308595273
+9452 507772533
+9451 1749920504
+9450 1834101935
+9449 991147626
+9448 1094837903
+9447 901787204
+9446 1977666782
+9445 1321783590
+9444 1552919304
+9443 1070201438
+9442 1804062470
+9441 294371770
+9440 686203201
+9439 1342211451
+9438 103150602
+9437 1305490909
+9436 158947568
+9435 133928303
+9434 1347129077
+9433 1697503309
+9432 428905657
+9431 1904610347
+9430 204200772
+9429 1230541648
+9428 2044362237
+9427 1432650584
+9426 427633109
+9425 1847208570
+9424 1247304438
+9423 1884239064
+9422 621976986
+9421 1664108554
+9420 655082601
+9419 932314731
+9418 1160964492
+9417 1920537961
+9416 1496351548
+9415 907465344
+9414 1665204767
+9413 1258547533
+9412 383998237
+9411 461851019
+9410 191221168
+9409 1528195939
+9408 1183263883
+9407 2116705947
+9406 2105845480
+9405 608927906
+9404 1852506294
+9403 1590002378
+9402 1493302537
+9401 1345847657
+9400 2007731758
+9399 919033836
+9398 802908539
+9397 197153666
+9396 185346146
+9395 690877692
+9394 1225231584
+9393 1730679531
+9392 1229156463
+9391 1837145903
+9390 503144062
+9389 882028287
+9388 1583446830
+9387 253499148
+9386 255333194
+9385 237804015
+9384 523467107
+9383 1203353748
+9382 1067326365
+9381 1003285945
+9380 1426070784
+9379 221998868
+9378 1569834107
+9377 574335976
+9376 264199653
+9375 515843101
+9374 1263109017
+9373 506658637
+9372 1729754268
+9371 574268701
+9370 542939118
+9369 1810578091
+9368 733687689
+9367 112030846
+9366 1119405730
+9365 602150263
+9364 1609204877
+9363 1535569329
+9362 1227535469
+9361 347128176
+9360 253699072
+9359 249644913
+9358 626695093
+9357 1345642815
+9356 1877515689
+9355 1199463094
+9354 1317961297
+9353 1667664809
+9352 1924766611
+9351 845327497
+9350 1580935486
+9349 851734808
+9348 2105282863
+9347 1053991006
+9346 1458710607
+9345 1905024664
+9344 933572481
+9343 688840316
+9342 2111203167
+9341 2066659825
+9340 1988064659
+9339 430908271
+9338 691172361
+9337 131537426
+9336 650309617
+9335 1731320048
+9334 1522098441
+9333 1262076701
+9332 1281870257
+9331 977890556
+9330 1867916730
+9329 1055539905
+9328 519612872
+9327 1574715647
+9326 27681518
+9325 209850880
+9324 1422180130
+9323 472633800
+9322 86729323
+9321 1073031803
+9320 887528282
+9319 526944480
+9318 1540507849
+9317 200258198
+9316 120418525
+9315 769870290
+9314 1941305145
+9313 1014396304
+9312 848259305
+9311 1680294895
+9310 1375487463
+9309 1856527233
+9308 1928082302
+9307 1107335961
+9306 756922633
+9305 1535716564
+9304 449449791
+9303 544207885
+9302 1541643618
+9301 226330352
+9300 458277684
+9299 293201083
+9298 1027858387
+9297 309761992
+9296 152535517
+9295 1702531365
+9294 123121556
+9293 349148327
+9292 1732589166
+9291 1707268491
+9290 1680007602
+9289 687270083
+9288 406525955
+9287 770637558
+9286 406436701
+9285 1253505869
+9284 2069094633
+9283 261010250
+9282 1786392488
+9281 1139215720
+9280 1899696241
+9279 268151502
+9278 1099604600
+9277 392365737
+9276 657886169
+9275 212714747
+9274 2141556594
+9273 223119439
+9272 85930201
+9271 1248442535
+9270 1345955613
+9269 148515692
+9268 140665566
+9267 1472810669
+9266 186640435
+9265 1950870838
+9264 2117425847
+9263 563336713
+9262 816624372
+9261 1045319083
+9260 1300742536
+9259 909370044
+9258 280833382
+9257 1300503734
+9256 849026573
+9255 145426451
+9254 1614597028
+9253 929878913
+9252 508797656
+9251 1518240986
+9250 39611120
+9249 1507330504
+9248 1757748981
+9247 886889852
+9246 398292791
+9245 434766730
+9244 126784546
+9243 893114058
+9242 1024647474
+9241 2084898157
+9240 1107776969
+9239 2020628591
+9238 2109358904
+9237 337278376
+9236 1502868470
+9235 1770787370
+9234 1134246465
+9233 1072106764
+9232 1410077824
+9231 2054737976
+9230 764485700
+9229 238802
+9228 60343471
+9227 135406931
+9226 1833390353
+9225 2066631307
+9224 1784112442
+9223 96356042
+9222 890267793
+9221 1148950800
+9220 1907975653
+9219 1300204915
+9218 1109037712
+9217 1322982251
+9216 760105306
+9215 1652662381
+9214 1557602903
+9213 189370036
+9212 1932820737
+9211 1151502531
+9210 2123022901
+9209 770498593
+9208 517760121
+9207 338571534
+9206 1350515558
+9205 430761706
+9204 360709546
+9203 1226992137
+9202 307621063
+9201 1409839022
+9200 1994394505
+9199 629078769
+9198 314332097
+9197 141195811
+9196 498778137
+9195 1737034311
+9194 1176363514
+9193 635161642
+9192 335864037
+9191 1737546526
+9190 39913088
+9189 584993402
+9188 540099609
+9187 1603858979
+9186 1912862995
+9185 570735270
+9184 1867325292
+9183 406100372
+9182 213830783
+9181 1162322143
+9180 633742410
+9179 1784451367
+9178 1567466683
+9177 86998414
+9176 2125345636
+9175 123523421
+9174 123140643
+9173 1098354172
+9172 1380081279
+9171 1826025942
+9170 1095506925
+9169 1853198694
+9168 130300632
+9167 724781434
+9166 1112315945
+9165 2011100143
+9164 1401170273
+9163 1586300636
+9162 595248554
+9161 1898354283
+9160 1197446917
+9159 583537756
+9158 819614054
+9157 2116847987
+9156 1884017335
+9155 1506762623
+9154 356904486
+9153 705003148
+9152 1919841610
+9151 576863064
+9150 1742339108
+9149 546743995
+9148 1806589379
+9147 1443943261
+9146 2111341419
+9145 1026991464
+9144 890925790
+9143 444598348
+9142 2847247
+9141 1674366233
+9140 1695725310
+9139 370725491
+9138 740882748
+9137 266684137
+9136 1471094808
+9135 1673498957
+9134 1415851589
+9133 1650299638
+9132 388853719
+9131 11710797
+9130 1078740229
+9129 1228082578
+9128 847004069
+9127 1460335079
+9126 1759943500
+9125 1179014187
+9124 1734404660
+9123 1927525070
+9122 1110147688
+9121 1373097615
+9120 917757333
+9119 298395847
+9118 582886224
+9117 779597915
+9116 553017471
+9115 1666743071
+9114 1024144217
+9113 1364043204
+9112 896356686
+9111 1779605404
+9110 933483485
+9109 1429041173
+9108 1047114330
+9107 1214867439
+9106 998316196
+9105 1968278818
+9104 1284645238
+9103 1404140791
+9102 571559409
+9101 1308254789
+9100 1312190376
+9099 1765888797
+9098 1615622725
+9097 1815473530
+9096 1873414067
+9095 1979902078
+9094 68866499
+9093 361307045
+9092 1009767736
+9091 811751841
+9090 790211391
+9089 138159418
+9088 1892862023
+9087 1063626801
+9086 1902937346
+9085 1336457915
+9084 770386385
+9083 1392022461
+9082 430559719
+9081 1614799160
+9080 732491073
+9079 1866099694
+9078 430724977
+9077 1226319160
+9076 2077705848
+9075 1741659052
+9074 1396719409
+9073 2123874097
+9072 91950415
+9071 953154259
+9070 1840115711
+9069 1644200494
+9068 2039958378
+9067 1783204295
+9066 1746607031
+9065 1512107021
+9064 970134342
+9063 1404598306
+9062 1718579302
+9061 871608318
+9060 1066373465
+9059 1874068238
+9058 382705720
+9057 556404108
+9056 293240416
+9055 510914885
+9054 905898195
+9053 1303070872
+9052 659531387
+9051 711943673
+9050 1184074183
+9049 1653655561
+9048 1935877493
+9047 836549573
+9046 1977083398
+9045 2101315399
+9044 1649708637
+9043 443565150
+9042 283758386
+9041 595233568
+9040 1060679529
+9039 56911416
+9038 2045077111
+9037 527851357
+9036 813069953
+9035 342008725
+9034 1941011367
+9033 98526024
+9032 338224840
+9031 1991994712
+9030 488902597
+9029 509969357
+9028 1580827822
+9027 2019274483
+9026 1797989561
+9025 1137653191
+9024 1998867145
+9023 193954522
+9022 118996689
+9021 1153359474
+9020 923549828
+9019 347524610
+9018 1824055811
+9017 1982045742
+9016 1334324583
+9015 1533518248
+9014 1817557013
+9013 1054475069
+9012 1530369269
+9011 226846969
+9010 697640105
+9009 532828172
+9008 1391325111
+9007 1703068386
+9006 734323638
+9005 714543929
+9004 3783884
+9003 2096500302
+9002 1757107074
+9001 1975739131
+9000 411166890
+8999 617111762
+8998 859463444
+8997 443174630
+8996 20407338
+8995 1604035039
+8994 1018656502
+8993 845507671
+8992 1417888342
+8991 1918955727
+8990 1476787311
+8989 1088987733
+8988 1160683674
+8987 290537562
+8986 164488729
+8985 279849514
+8984 3148979
+8983 1590710043
+8982 356834964
+8981 997541097
+8980 983005506
+8979 1142055366
+8978 1945988182
+8977 676781182
+8976 1699284502
+8975 785306983
+8974 1104920502
+8973 175528401
+8972 1685333412
+8971 1139995312
+8970 1116275687
+8969 2115475908
+8968 596704424
+8967 1402912053
+8966 1572001776
+8965 1322383314
+8964 186146697
+8963 1247184422
+8962 1516204008
+8961 328900608
+8960 758272053
+8959 1186249748
+8958 924499004
+8957 880834160
+8956 287388583
+8955 721262334
+8954 2070498198
+8953 1153091530
+8952 607704537
+8951 1362263245
+8950 1199036563
+8949 306224323
+8948 1590254512
+8947 1160681198
+8946 1719344328
+8945 1523756101
+8944 1247457219
+8943 2112408838
+8942 1206736361
+8941 1717341152
+8940 543290888
+8939 1860847282
+8938 543474131
+8937 1421804757
+8936 1216765356
+8935 324817354
+8934 1953662954
+8933 2004729736
+8932 488912369
+8931 329954260
+8930 1551885252
+8929 2024921541
+8928 898861165
+8927 203236670
+8926 957819609
+8925 1281780700
+8924 113557796
+8923 708234953
+8922 2101538615
+8921 301480214
+8920 1919492381
+8919 38355364
+8918 734363643
+8917 66498411
+8916 2060707627
+8915 1754419138
+8914 317019739
+8913 1677599715
+8912 1569117949
+8911 1493372727
+8910 1173867020
+8909 1268969779
+8908 644081926
+8907 218656777
+8906 1615625451
+8905 1359519267
+8904 1983388632
+8903 1623708694
+8902 452844484
+8901 611474476
+8900 1578576742
+8899 1348648582
+8898 1067101931
+8897 1764564113
+8896 89678873
+8895 249584656
+8894 1327725733
+8893 1959561230
+8892 936226220
+8891 2063183251
+8890 1714600218
+8889 1852993969
+8888 125131385
+8887 1127428153
+8886 1896962320
+8885 383107911
+8884 185301188
+8883 971130660
+8882 503732695
+8881 300148170
+8880 849290800
+8879 955210243
+8878 1800827975
+8877 1432046307
+8876 382751793
+8875 2139400405
+8874 906674783
+8873 1371914156
+8872 45131951
+8871 1251679549
+8870 1691856193
+8869 1961496277
+8868 1258969709
+8867 817517275
+8866 436838380
+8865 277601291
+8864 1460842084
+8863 1412026130
+8862 244961012
+8861 1230715898
+8860 1938051865
+8859 587172065
+8858 2103515297
+8857 1889507122
+8856 942126965
+8855 925831659
+8854 2026858864
+8853 2032636666
+8852 121839860
+8851 1696006100
+8850 646803843
+8849 1564728141
+8848 572458450
+8847 1808911218
+8846 525371523
+8845 1158321285
+8844 2094268454
+8843 1802478882
+8842 1827541611
+8841 231119322
+8840 2140193488
+8839 874338918
+8838 1524657897
+8837 981368418
+8836 1504158838
+8835 1172295898
+8834 32640279
+8833 230126186
+8832 1621457912
+8831 1805272595
+8830 1274684249
+8829 48544743
+8828 1792528748
+8827 1177683638
+8826 2010131905
+8825 1056973947
+8824 803991799
+8823 330852764
+8822 1385832823
+8821 704595366
+8820 1123547650
+8819 985376273
+8818 1039356618
+8817 1561620813
+8816 1862126412
+8815 870376289
+8814 1478263322
+8813 1863149132
+8812 1809769041
+8811 953202693
+8810 853945072
+8809 1158825070
+8808 1517663727
+8807 352361999
+8806 948728139
+8805 1274032652
+8804 1698321633
+8803 374851332
+8802 1102925585
+8801 1572913169
+8800 12743847
+8799 97000611
+8798 185896486
+8797 735554801
+8796 373691838
+8795 1679279141
+8794 1818624772
+8793 99396433
+8792 1354788762
+8791 400456550
+8790 1812722396
+8789 1709410485
+8788 1270733509
+8787 168980328
+8786 83357491
+8785 2146460928
+8784 1208090896
+8783 525060629
+8782 1009204059
+8781 650943971
+8780 1583022613
+8779 501583073
+8778 210096931
+8777 243631075
+8776 801524014
+8775 573876807
+8774 171107067
+8773 125408464
+8772 362107485
+8771 1005924974
+8770 1387016683
+8769 1424672694
+8768 1870792420
+8767 654100993
+8766 1064413677
+8765 274295405
+8764 324490378
+8763 1418168222
+8762 434157684
+8761 1792861925
+8760 1277206689
+8759 1643742068
+8758 1626052994
+8757 1271756229
+8756 1108373080
+8755 1705780510
+8754 1137256868
+8753 557146925
+8752 1089521663
+8751 507620986
+8750 440847039
+8749 1339391538
+8748 1847542707
+8747 1783703772
+8746 72524007
+8745 676115549
+8744 211769322
+8743 1312665741
+8742 885875429
+8741 1084918439
+8740 1282616201
+8739 732915690
+8738 360259017
+8737 1596497015
+8736 329610614
+8735 1793729103
+8734 1987621369
+8733 679112101
+8732 140961533
+8731 937899264
+8730 166808931
+8729 5450460
+8728 535368987
+8727 2067756132
+8726 134499360
+8725 551226155
+8724 616258846
+8723 629635882
+8722 116299885
+8721 1897613773
+8720 807561927
+8719 804626915
+8718 1266867531
+8717 1171427157
+8716 1571934450
+8715 907341914
+8714 1937723768
+8713 1274334531
+8712 30049540
+8711 152959739
+8710 724659422
+8709 1833602834
+8708 403305075
+8707 714013562
+8706 1756359294
+8705 1797982161
+8704 1652767570
+8703 1049722104
+8702 512303169
+8701 135511073
+8700 402530277
+8699 246536447
+8698 2018434747
+8697 2131626480
+8696 1451497285
+8695 1652347126
+8694 434926270
+8693 866128721
+8692 1969557602
+8691 1459156618
+8690 630746242
+8689 1783618418
+8688 1380176112
+8687 359525617
+8686 1381187037
+8685 297599919
+8684 877292374
+8683 1784764028
+8682 549675109
+8681 343930353
+8680 1897138312
+8679 10645860
+8678 77243540
+8677 752806562
+8676 1208729640
+8675 706637189
+8674 1285678992
+8673 1517256497
+8672 647191827
+8671 265766722
+8670 264559973
+8669 418387445
+8668 942522810
+8667 366087621
+8666 1696700210
+8665 585368564
+8664 1830273172
+8663 1123253299
+8662 235382479
+8661 185939184
+8660 78980506
+8659 271220625
+8658 402431380
+8657 1082576193
+8656 1629716891
+8655 1743906657
+8654 1895408458
+8653 533362020
+8652 2035109364
+8651 539029249
+8650 266686813
+8649 1144331750
+8648 949399868
+8647 1518089999
+8646 1614611218
+8645 1838956791
+8644 59445362
+8643 1019912270
+8642 1252696523
+8641 228804382
+8640 1470727560
+8639 2045956000
+8638 869170883
+8637 357154246
+8636 683298097
+8635 573446910
+8634 349986084
+8633 1644333987
+8632 1044272793
+8631 2111645502
+8630 1930991452
+8629 1143887961
+8628 788987382
+8627 806008371
+8626 1334651382
+8625 1096354870
+8624 1856280940
+8623 1356379209
+8622 266675207
+8621 890777614
+8620 1737113029
+8619 896080462
+8618 1677204180
+8617 1257926725
+8616 1458644637
+8615 594698948
+8614 586260267
+8613 1978124627
+8612 1696668358
+8611 1354224171
+8610 1507117147
+8609 1113573314
+8608 1362657903
+8607 295723972
+8606 7168161
+8605 1186447757
+8604 1676657765
+8603 385824230
+8602 1860826183
+8601 2047868480
+8600 1322658120
+8599 1124983080
+8598 1956720226
+8597 1840116159
+8596 1097211079
+8595 2125755821
+8594 829679663
+8593 965503326
+8592 1766749828
+8591 1518078393
+8590 1361057082
+8589 479186304
+8588 1584919473
+8587 1082505232
+8586 671666457
+8585 1628003657
+8584 1045514238
+8583 1379519744
+8582 471007480
+8581 583095044
+8580 2139049915
+8579 1211393175
+8578 1106405152
+8577 176210146
+8576 766549855
+8575 1768827579
+8574 1473105222
+8573 1776272932
+8572 1210649757
+8571 735843103
+8570 91148254
+8569 1630025609
+8568 27772001
+8567 1978448053
+8566 1010436496
+8565 131707753
+8564 359005992
+8563 1459084917
+8562 1751929891
+8561 1287563524
+8560 2080642568
+8559 278551850
+8558 1955003494
+8557 2104399463
+8556 36990994
+8555 1439630361
+8554 1156996177
+8553 462419194
+8552 1387953477
+8551 1407097953
+8550 1624173539
+8549 1962839769
+8548 444843319
+8547 1485061221
+8546 850588572
+8545 1137760571
+8544 558177822
+8543 737262119
+8542 1685124678
+8541 1728107796
+8540 708071101
+8539 260183848
+8538 619589112
+8537 2043547896
+8536 1619442061
+8535 1698835227
+8534 527261509
+8533 1218926116
+8532 1525925997
+8531 1473378041
+8530 1480043678
+8529 2123726753
+8528 241560856
+8527 515373133
+8526 947403286
+8525 1722055448
+8524 51676884
+8523 1897381872
+8522 985729302
+8521 1572597355
+8520 962254633
+8519 139112318
+8518 1112251197
+8517 1454566396
+8516 926883399
+8515 113326453
+8514 1600119540
+8513 977553673
+8512 29191017
+8511 1424940830
+8510 1108518684
+8509 812006853
+8508 788225435
+8507 1068237533
+8506 1516286387
+8505 400515945
+8504 172909230
+8503 1201367116
+8502 1886366086
+8501 1549682892
+8500 1231817184
+8499 964670544
+8498 1176323467
+8497 666989056
+8496 463696249
+8495 1197505061
+8494 736326145
+8493 626563176
+8492 935127239
+8491 846616984
+8490 460346158
+8489 1655171885
+8488 1359712567
+8487 998924744
+8486 2001930504
+8485 2096813373
+8484 84135435
+8483 175178710
+8482 2016518637
+8481 1364667812
+8480 636715394
+8479 40281150
+8478 1443204114
+8477 387709490
+8476 895328303
+8475 314919270
+8474 661633507
+8473 770709986
+8472 2117033580
+8471 921695541
+8470 373359425
+8469 564828128
+8468 500974295
+8467 2126302053
+8466 2078146559
+8465 1984616721
+8464 262377822
+8463 2037192809
+8462 166217018
+8461 1427439002
+8460 1634388064
+8459 1608905061
+8458 1800725029
+8457 1410382842
+8456 914789309
+8455 1826751793
+8454 80294736
+8453 866951271
+8452 1685946964
+8451 1976237487
+8450 2068947346
+8449 249005904
+8448 1292436495
+8447 1128284843
+8446 1873559631
+8445 124618317
+8444 345369338
+8443 1887421613
+8442 397350561
+8441 1552205452
+8440 420721246
+8439 394541019
+8438 634165217
+8437 663841222
+8436 1863924231
+8435 40953749
+8434 1818399702
+8433 982422468
+8432 402804745
+8431 704795605
+8430 1774197621
+8429 224005222
+8428 694115752
+8427 2121456883
+8426 1330088106
+8425 47838038
+8424 140804829
+8423 251540897
+8422 945487572
+8421 1436941060
+8420 683800992
+8419 940662503
+8418 522929920
+8417 1167818177
+8416 782915505
+8415 2133621666
+8414 1874751404
+8413 940647534
+8412 1466700367
+8411 2809541
+8410 918040235
+8409 1904363672
+8408 678100436
+8407 593211467
+8406 992925167
+8405 881501762
+8404 1785632652
+8403 1113604097
+8402 1355708495
+8401 178799522
+8400 10679852
+8399 1800224385
+8398 1041400764
+8397 646277714
+8396 1980652054
+8395 1078547209
+8394 1249834113
+8393 851347417
+8392 1715223553
+8391 4825069
+8390 914011139
+8389 1663466462
+8388 157746998
+8387 536791902
+8386 1440550421
+8385 1989751618
+8384 666921299
+8383 1871941863
+8382 22607299
+8381 1709820342
+8380 1472192753
+8379 324828767
+8378 911438505
+8377 1944082322
+8376 955062463
+8375 2026804718
+8374 1673276915
+8373 1606833130
+8372 1102924245
+8371 1702967758
+8370 1284882406
+8369 1511885786
+8368 1967055979
+8367 2110337203
+8366 1543927249
+8365 1129304636
+8364 1510807304
+8363 1245009044
+8362 2084819926
+8361 51757090
+8360 1994561719
+8359 377219237
+8358 222916041
+8357 315479027
+8356 2017354251
+8355 1716092206
+8354 1967144319
+8353 1104584604
+8352 399749110
+8351 1845262180
+8350 798381837
+8349 1675594079
+8348 1517249952
+8347 1032117435
+8346 270805407
+8345 1495712981
+8344 923880473
+8343 2117792805
+8342 321950724
+8341 1738522107
+8340 1883395426
+8339 1322028850
+8338 2115442185
+8337 837751343
+8336 599529899
+8335 298918205
+8334 1191968358
+8333 1459050213
+8332 1397930972
+8331 1707600689
+8330 1976324697
+8329 1679082692
+8328 507348633
+8327 654307483
+8326 495818356
+8325 912769647
+8324 1316343096
+8323 121882139
+8322 306202767
+8321 871638679
+8320 328012227
+8319 1913748050
+8318 1404788672
+8317 21536971
+8316 108236962
+8315 300496250
+8314 1173762257
+8313 1332842014
+8312 234397378
+8311 1147405521
+8310 1770563570
+8309 1045644083
+8308 722498951
+8307 1816523980
+8306 1793266632
+8305 1287963334
+8304 1048470880
+8303 1631851317
+8302 1630209164
+8301 1866331928
+8300 1200252055
+8299 1322017213
+8298 1183264335
+8297 1742062634
+8296 1485448035
+8295 373936217
+8294 606566880
+8293 444704417
+8292 1941353559
+8291 539938364
+8290 1614333655
+8289 306475256
+8288 1805511088
+8287 1104292422
+8286 995258362
+8285 922878596
+8284 66098871
+8283 26356735
+8282 1709762092
+8281 1336236943
+8280 424906570
+8279 2101523238
+8278 1399861099
+8277 1582019265
+8276 768053099
+8275 161415315
+8274 1805237817
+8273 1329622600
+8272 431599262
+8271 308191951
+8270 683067593
+8269 1605673069
+8268 1984052826
+8267 809328118
+8266 1135495754
+8265 1040743618
+8264 580066306
+8263 66628515
+8262 977854410
+8261 1634878303
+8260 881910924
+8259 510041233
+8258 1458700541
+8257 882632492
+8256 1038193550
+8255 968901627
+8254 1360600152
+8253 877345576
+8252 1748933813
+8251 1755722502
+8250 2083859492
+8249 990370953
+8248 1333470138
+8247 1238445784
+8246 1924265095
+8245 1585914147
+8244 1877299701
+8243 1497045866
+8242 646555007
+8241 973409841
+8240 471622773
+8239 2021223123
+8238 470177314
+8237 943309207
+8236 229261812
+8235 1068867239
+8234 62889208
+8233 1092671650
+8232 1332201239
+8231 467813177
+8230 176177762
+8229 2146762079
+8228 1619331330
+8227 489798914
+8226 1669515988
+8225 160847974
+8224 1367451462
+8223 1752361298
+8222 940969732
+8221 758562859
+8220 422252363
+8219 845413708
+8218 1213589506
+8217 1895039639
+8216 1508629731
+8215 427219229
+8214 939359140
+8213 903889860
+8212 1025423093
+8211 772815532
+8210 503232526
+8209 1675797213
+8208 1791961311
+8207 1548793723
+8206 880419999
+8205 1284073809
+8204 1884149647
+8203 1742559679
+8202 916493888
+8201 1332922808
+8200 995965494
+8199 1833862495
+8198 477246091
+8197 1458483356
+8196 1269831100
+8195 2064638338
+8194 1367361889
+8193 608888602
+8192 1330108934
+8191 95556024
+8190 1692457001
+8189 674696372
+8188 1484267625
+8187 786370277
+8186 955680498
+8185 604739871
+8184 1549279783
+8183 166543608
+8182 400657333
+8181 1497109528
+8180 1128337869
+8179 1101922451
+8178 795377214
+8177 507887501
+8176 1812127724
+8175 1285343967
+8174 367579921
+8173 551226839
+8172 746594185
+8171 1230115041
+8170 855676717
+8169 1684965786
+8168 564031395
+8167 560091400
+8166 91121467
+8165 660942498
+8164 734529404
+8163 1271805865
+8162 1063915249
+8161 655412562
+8160 758772047
+8159 906086724
+8158 1866499522
+8157 879527754
+8156 1384574141
+8155 789136890
+8154 204082537
+8153 52170255
+8152 1185689387
+8151 1446218530
+8150 701732313
+8149 620450367
+8148 1437278375
+8147 1657516895
+8146 140307580
+8145 1260900884
+8144 538749782
+8143 1284948528
+8142 1843033770
+8141 1209112047
+8140 666083646
+8139 295585316
+8138 1593844319
+8137 2050572545
+8136 1973045644
+8135 966799250
+8134 1744510897
+8133 79116842
+8132 513033817
+8131 157828524
+8130 936396688
+8129 2026727941
+8128 1668996231
+8127 1077362632
+8126 675445216
+8125 1332403886
+8124 1750931150
+8123 905347655
+8122 1497921590
+8121 565239020
+8120 8940155
+8119 1191699066
+8118 480142787
+8117 176377490
+8116 1118767112
+8115 1002842700
+8114 1565350762
+8113 1477121383
+8112 618864882
+8111 1547448454
+8110 1762751376
+8109 762994749
+8108 470023320
+8107 627045069
+8106 306061648
+8105 1893928802
+8104 453765432
+8103 1586682372
+8102 1290203802
+8101 633789524
+8100 636315941
+8099 2006517704
+8098 1351282725
+8097 336592345
+8096 1473915129
+8095 1917581209
+8094 1981965944
+8093 1185692130
+8092 896407499
+8091 306222523
+8090 85096233
+8089 1980046313
+8088 72931954
+8087 1624783734
+8086 758510376
+8085 1789129377
+8084 383977818
+8083 17902308
+8082 1861853655
+8081 2003353781
+8080 1077425134
+8079 1135706307
+8078 456933101
+8077 723578165
+8076 173279636
+8075 866862923
+8074 603725000
+8073 1967459556
+8072 950366431
+8071 1431169746
+8070 1429990447
+8069 299723596
+8068 532602574
+8067 1581185163
+8066 502110049
+8065 288222999
+8064 1021173710
+8063 1675743420
+8062 1100595897
+8061 1063844834
+8060 233290569
+8059 607796146
+8058 1221535936
+8057 431286225
+8056 1240805916
+8055 740608068
+8054 2074759369
+8053 528107685
+8052 1087960822
+8051 726147348
+8050 1546420680
+8049 353846968
+8048 962426670
+8047 1737553825
+8046 119853165
+8045 353303728
+8044 2063980140
+8043 1320038902
+8042 537469109
+8041 650642834
+8040 898567171
+8039 1996288931
+8038 1945097195
+8037 244379575
+8036 560011453
+8035 973850276
+8034 1335110749
+8033 2104812523
+8032 1442452851
+8031 492799751
+8030 1989792546
+8029 1949487992
+8028 1514473878
+8027 480927868
+8026 504010503
+8025 712698230
+8024 1800130894
+8023 1348612021
+8022 1129170653
+8021 734113853
+8020 1911204326
+8019 1956350502
+8018 233993803
+8017 609122942
+8016 1821057333
+8015 947297910
+8014 1963318266
+8013 1413337306
+8012 421471731
+8011 688663826
+8010 853029287
+8009 654187596
+8008 1436277478
+8007 971246919
+8006 1056752474
+8005 602682578
+8004 1678881073
+8003 842310998
+8002 115019977
+8001 1640448506
+8000 1125809520
+7999 1508864678
+7998 1445477489
+7997 801775648
+7996 828280621
+7995 1302882130
+7994 1731011225
+7993 1066017041
+7992 1584891343
+7991 1320303799
+7990 500120050
+7989 1302081383
+7988 135293169
+7987 1434179541
+7986 793288324
+7985 407720027
+7984 525826179
+7983 1274654440
+7982 560308019
+7981 1914767783
+7980 1399869996
+7979 2029266016
+7978 1744918770
+7977 833594900
+7976 1439849493
+7975 214441475
+7974 487662600
+7973 38432567
+7972 1863985126
+7971 753638947
+7970 194971017
+7969 324033872
+7968 680584056
+7967 142595358
+7966 1218248071
+7965 1909747228
+7964 1865474435
+7963 410707426
+7962 565896991
+7961 282809959
+7960 1185010629
+7959 1213424157
+7958 508793059
+7957 1875056790
+7956 908353361
+7955 1666117531
+7954 1994895656
+7953 758542044
+7952 2022268092
+7951 678525651
+7950 169849013
+7949 566275096
+7948 589416522
+7947 1530477294
+7946 345932299
+7945 1401416926
+7944 497939997
+7943 1881507301
+7942 1990945197
+7941 1539951253
+7940 73054891
+7939 52375659
+7938 1253269449
+7937 918320476
+7936 424604571
+7935 807540645
+7934 1343850237
+7933 1582664476
+7932 1373180444
+7931 1499956482
+7930 1921500548
+7929 1457437487
+7928 305070795
+7927 990159176
+7926 2027644782
+7925 149811317
+7924 1791333087
+7923 1316370005
+7922 588693031
+7921 1455992996
+7920 89109128
+7919 786855366
+7918 220342796
+7917 1335483244
+7916 1032537297
+7915 611908646
+7914 1557955377
+7913 1105472392
+7912 1808452410
+7911 1938569538
+7910 286681804
+7909 1302218063
+7908 1775254736
+7907 445728804
+7906 1721953886
+7905 989423743
+7904 1581843848
+7903 1991377403
+7902 1808647576
+7901 2063226605
+7900 1194885686
+7899 931341372
+7898 1577276352
+7897 155259478
+7896 1346309737
+7895 711274777
+7894 1708601933
+7893 335340090
+7892 1227260876
+7891 1949321313
+7890 1235650200
+7889 901109532
+7888 1901801717
+7887 1755917798
+7886 1925011515
+7885 2074548553
+7884 950939884
+7883 1766869486
+7882 818790588
+7881 506234347
+7880 163314802
+7879 1988436647
+7878 1727747824
+7877 785830993
+7876 1011368604
+7875 1878060131
+7874 1328259815
+7873 1666100891
+7872 796491717
+7871 877306204
+7870 485950253
+7869 1039626208
+7868 1732515283
+7867 866001575
+7866 594141193
+7865 1010969646
+7864 1631497549
+7863 1906764268
+7862 1247173538
+7861 326151344
+7860 47519595
+7859 1627216050
+7858 1123581665
+7857 1974736812
+7856 804977913
+7855 158142028
+7854 1255757965
+7853 444705537
+7852 1603554684
+7851 977837588
+7850 925970170
+7849 1524967457
+7848 977068043
+7847 1997171341
+7846 1605054826
+7845 1492751361
+7844 1081568414
+7843 450953611
+7842 1180150638
+7841 1904349157
+7840 1292274569
+7839 1767432326
+7838 445485015
+7837 721545636
+7836 1381987674
+7835 834860572
+7834 1911279756
+7833 1305346205
+7832 1859244673
+7831 1767441136
+7830 1350053326
+7829 220266431
+7828 822238136
+7827 965439637
+7826 718978847
+7825 360272376
+7824 702070992
+7823 277920376
+7822 1666219015
+7821 78587226
+7820 769545
+7819 1076282477
+7818 2067396279
+7817 1631800330
+7816 915602927
+7815 1154101215
+7814 312600723
+7813 1324702905
+7812 1306162690
+7811 1560201960
+7810 1458864142
+7809 570728932
+7808 385444652
+7807 1758108090
+7806 957749528
+7805 76641469
+7804 1123099547
+7803 143838619
+7802 2102776526
+7801 1638978242
+7800 945203000
+7799 384613689
+7798 1648771231
+7797 461965760
+7796 263368644
+7795 441058471
+7794 841537009
+7793 623483766
+7792 277150831
+7791 589936538
+7790 158674595
+7789 516452862
+7788 160679549
+7787 913295064
+7786 1319199607
+7785 1738383670
+7784 1995422173
+7783 899882411
+7782 2013322411
+7781 735433757
+7780 1174757308
+7779 1848239699
+7778 1760463052
+7777 308803183
+7776 635008543
+7775 813910909
+7774 121348590
+7773 1631604953
+7772 1346119267
+7771 1718162837
+7770 2137690658
+7769 483237239
+7768 121245045
+7767 1207712760
+7766 1767912399
+7765 1787368526
+7764 163907639
+7763 251600471
+7762 464809171
+7761 1908181617
+7760 429256988
+7759 1392863178
+7758 1344736903
+7757 569779527
+7756 1065356539
+7755 419317196
+7754 1872544907
+7753 1259988415
+7752 1872608751
+7751 165082711
+7750 1122454353
+7749 865954125
+7748 1213231156
+7747 946552143
+7746 187454592
+7745 1150887237
+7744 1615275289
+7743 550669401
+7742 1641397943
+7741 862882028
+7740 1596917792
+7739 929977898
+7738 862808488
+7737 481360166
+7736 1043805121
+7735 1516311928
+7734 1322559355
+7733 403209670
+7732 1969827130
+7731 1219429640
+7730 563444714
+7729 2006961109
+7728 327506639
+7727 925419706
+7726 844718267
+7725 1952851772
+7724 694192093
+7723 1707462196
+7722 137534062
+7721 1006654626
+7720 1099335203
+7719 175902210
+7718 678499532
+7717 62343919
+7716 1478760501
+7715 1784268839
+7714 1656972942
+7713 752393261
+7712 1101235257
+7711 711420045
+7710 73540
+7709 1115557625
+7708 2033656425
+7707 1493980207
+7706 1306284459
+7705 640595450
+7704 1693968446
+7703 103129715
+7702 1987248604
+7701 2110349669
+7700 891923001
+7699 1785508655
+7698 1162242842
+7697 522138515
+7696 231227613
+7695 1284739719
+7694 1815317710
+7693 1835021115
+7692 608126993
+7691 2109115499
+7690 328155093
+7689 1036991284
+7688 844625357
+7687 1041714341
+7686 552854624
+7685 726367240
+7684 683033581
+7683 945552897
+7682 752319721
+7681 2133161280
+7680 825247268
+7679 653576980
+7678 1956756814
+7677 1393060974
+7676 1947495409
+7675 1203154744
+7674 800830494
+7673 1731102425
+7672 1358690361
+7671 201739949
+7670 948106827
+7669 369784486
+7668 1554281042
+7667 2024986770
+7666 854304453
+7665 543690145
+7664 676612726
+7663 1853685858
+7662 1506866022
+7661 1718619357
+7660 1264490142
+7659 1433924400
+7658 484136659
+7657 118258117
+7656 358680760
+7655 1754785375
+7654 2121531166
+7653 697355949
+7652 120305629
+7651 98742741
+7650 176404465
+7649 1579669941
+7648 853565219
+7647 753602070
+7646 592230480
+7645 216392984
+7644 1991948030
+7643 599090545
+7642 782995598
+7641 988905875
+7640 794942554
+7639 1070603704
+7638 1662963681
+7637 1010590897
+7636 1348374044
+7635 1148102242
+7634 1184307771
+7633 1105477017
+7632 589195716
+7631 72941622
+7630 1234482697
+7629 1146232025
+7628 1075243640
+7627 876834932
+7626 144210598
+7625 1808808458
+7624 1634479745
+7623 2022788425
+7622 520951484
+7621 688119336
+7620 1392661170
+7619 1570286043
+7618 987439461
+7617 637172234
+7616 909137688
+7615 2140623583
+7614 1580881034
+7613 1003042155
+7612 1951631638
+7611 1859875541
+7610 1473425841
+7609 1931835305
+7608 1869713308
+7607 514861439
+7606 1973766773
+7605 242897026
+7604 558906526
+7603 1111366149
+7602 2018477968
+7601 1590447338
+7600 1145181630
+7599 357647765
+7598 1002021427
+7597 1413918829
+7596 1389838835
+7595 268905821
+7594 1287856974
+7593 946360409
+7592 630127255
+7591 1098149089
+7590 1848163523
+7589 755488935
+7588 661148355
+7587 994299525
+7586 1203774848
+7585 2053579180
+7584 188991945
+7583 1868489141
+7582 1677099962
+7581 19796333
+7580 2137645881
+7579 958564402
+7578 2105552180
+7577 1626816282
+7576 2103438560
+7575 862400624
+7574 371902706
+7573 1115942836
+7572 2113668167
+7571 1660830203
+7570 588425911
+7569 1878746448
+7568 1115292578
+7567 733115606
+7566 126061855
+7565 443478425
+7564 1786262213
+7563 189707885
+7562 1245680534
+7561 2022121968
+7560 437000734
+7559 853863997
+7558 1699197735
+7557 755052822
+7556 805307580
+7555 1482769355
+7554 376479218
+7553 169195612
+7552 1878326908
+7551 718535559
+7550 61727801
+7549 510829599
+7548 1002609490
+7547 1243151556
+7546 1254913575
+7545 987495724
+7544 896216104
+7543 858556151
+7542 527516924
+7541 234921719
+7540 545537625
+7539 2002793953
+7538 1752684593
+7537 671814152
+7536 1094337040
+7535 2083837617
+7534 1345281539
+7533 1911623893
+7532 1900190799
+7531 391816537
+7530 322924232
+7529 1829431559
+7528 48556417
+7527 216428380
+7526 378573604
+7525 636111968
+7524 1751926095
+7523 1805427307
+7522 107467811
+7521 1367497309
+7520 1863409717
+7519 966059893
+7518 1403399671
+7517 15113765
+7516 346935451
+7515 396357424
+7514 459978800
+7513 661294385
+7512 313018526
+7511 672206619
+7510 629720773
+7509 2021207120
+7508 908456913
+7507 1816330624
+7506 1474016261
+7505 1330196795
+7504 183646818
+7503 953465002
+7502 1588699661
+7501 70759240
+7500 343260120
+7499 106495852
+7498 1450857955
+7497 1559928096
+7496 611985933
+7495 720629945
+7494 528644157
+7493 384428786
+7492 2089501237
+7491 1288891566
+7490 2111581285
+7489 1848295952
+7488 619124441
+7487 1007042247
+7486 1702618613
+7485 1833124714
+7484 83338897
+7483 1935255829
+7482 31573612
+7481 439295054
+7480 1911233354
+7479 960873797
+7478 547190859
+7477 1725743766
+7476 1632683806
+7475 520551259
+7474 1888980782
+7473 112887578
+7472 610204882
+7471 1482203809
+7470 767384932
+7469 930815671
+7468 1641993566
+7467 730228010
+7466 1031283939
+7465 227557147
+7464 778612355
+7463 1387236239
+7462 420331148
+7461 241205285
+7460 669767124
+7459 1104539038
+7458 145677338
+7457 933483375
+7456 923703350
+7455 1914846432
+7454 1801551102
+7453 1791527491
+7452 24022475
+7451 1218183462
+7450 2039587843
+7449 185489588
+7448 1475673639
+7447 26639599
+7446 1984246632
+7445 1519796228
+7444 2057830025
+7443 406776973
+7442 1492986293
+7441 1826872858
+7440 1987693890
+7439 37156922
+7438 2047015380
+7437 1414436419
+7436 2099099303
+7435 1791531347
+7434 1954709647
+7433 537407070
+7432 717469115
+7431 1463275758
+7430 95527947
+7429 1883767397
+7428 180835688
+7427 378314554
+7426 1279415921
+7425 1279659506
+7424 1890823957
+7423 583367639
+7422 1899423296
+7421 1986016535
+7420 1889993471
+7419 2012948243
+7418 348726604
+7417 2103361059
+7416 116293222
+7415 1577469659
+7414 26809934
+7413 230957167
+7412 566566730
+7411 1455829371
+7410 1927341126
+7409 573257471
+7408 85541267
+7407 255484033
+7406 1607210420
+7405 1561692233
+7404 1074062232
+7403 491433888
+7402 441879123
+7401 981185366
+7400 1282440070
+7399 1864697041
+7398 604351475
+7397 1048659829
+7396 634974244
+7395 696048282
+7394 1527719858
+7393 2052291070
+7392 840857816
+7391 2033958701
+7390 1637289931
+7389 1934116059
+7388 1896655021
+7387 918740593
+7386 2076551125
+7385 2032819703
+7384 1010902928
+7383 718464211
+7382 451099689
+7381 2140792907
+7380 1370288104
+7379 1671857093
+7378 1113530699
+7377 671332682
+7376 1328905448
+7375 1115776531
+7374 1119813110
+7373 92876866
+7372 1356477466
+7371 724665730
+7370 376833890
+7369 233780241
+7368 1229722796
+7367 2055786841
+7366 1668423619
+7365 730166822
+7364 2002674113
+7363 1641244805
+7362 415001139
+7361 1054225405
+7360 137303679
+7359 718549338
+7358 2005048582
+7357 2011318966
+7356 2055321312
+7355 1358086914
+7354 1581720014
+7353 1017593669
+7352 1495659754
+7351 926726244
+7350 1027262207
+7349 698955422
+7348 342951644
+7347 2145237816
+7346 1699003220
+7345 1236028582
+7344 1906782713
+7343 395147380
+7342 1863526624
+7341 1122697225
+7340 1642426581
+7339 468530697
+7338 712840269
+7337 499555974
+7336 53112728
+7335 27178814
+7334 315165682
+7333 948448708
+7332 1503941125
+7331 1843935449
+7330 1196660470
+7329 273468361
+7328 810711673
+7327 646961668
+7326 429598952
+7325 1037727643
+7324 2009910807
+7323 654993770
+7322 2137815110
+7321 796704332
+7320 583774599
+7319 1029508039
+7318 1147435850
+7317 1254406710
+7316 238455102
+7315 1303855840
+7314 1519985606
+7313 784085488
+7312 900204446
+7311 1394995927
+7310 409856955
+7309 1142870607
+7308 415417969
+7307 685661455
+7306 184390292
+7305 1252147667
+7304 670721337
+7303 618713881
+7302 1899271886
+7301 1230472764
+7300 1033223776
+7299 549698802
+7298 1991353056
+7297 1920467678
+7296 784534509
+7295 1922088830
+7294 1047396181
+7293 1213206475
+7292 71219170
+7291 1108307070
+7290 1796752129
+7289 1476851537
+7288 791052937
+7287 1991063658
+7286 1881904752
+7285 1601853262
+7284 403651393
+7283 124989679
+7282 374228533
+7281 1904817487
+7280 979577958
+7279 1871679148
+7278 958480315
+7277 1310753949
+7276 14940118
+7275 1713160059
+7274 1500359429
+7273 1587732220
+7272 1732973753
+7271 1349573084
+7270 1386603356
+7269 1260239745
+7268 1912647941
+7267 69264226
+7266 873071497
+7265 1718811681
+7264 1850869660
+7263 2086572758
+7262 1563937994
+7261 741851281
+7260 317254133
+7259 1953172119
+7258 1742430432
+7257 1336683323
+7256 1587412265
+7255 1756915073
+7254 1227624729
+7253 646317554
+7252 1292895369
+7251 650033032
+7250 946337172
+7249 1816307656
+7248 1856739030
+7247 1392803904
+7246 1957878168
+7245 574691545
+7244 2127669954
+7243 150786345
+7242 201128864
+7241 472734007
+7240 1584408791
+7239 1317339130
+7238 387168248
+7237 193836259
+7236 365878214
+7235 933982387
+7234 154873687
+7233 1109018378
+7232 1769318625
+7231 1758249523
+7230 1146904497
+7229 1128054458
+7228 365759854
+7227 2132999007
+7226 109058594
+7225 941094711
+7224 464019704
+7223 577591696
+7222 1847464029
+7221 1624071360
+7220 940777650
+7219 1701016916
+7218 2005913136
+7217 1282047485
+7216 1412617598
+7215 1807091822
+7214 373562681
+7213 1654935946
+7212 713861202
+7211 1031273382
+7210 85565759
+7209 1390572531
+7208 951460916
+7207 1600669509
+7206 38962572
+7205 1404343483
+7204 1312147410
+7203 544107812
+7202 2109597529
+7201 641264166
+7200 1392489669
+7199 1161389138
+7198 1018995864
+7197 1572148791
+7196 1668979302
+7195 1678950545
+7194 1241114329
+7193 987431992
+7192 1784297694
+7191 146447113
+7190 1765641872
+7189 1806213813
+7188 288399318
+7187 198821314
+7186 908484804
+7185 1905165299
+7184 1093230620
+7183 1489772946
+7182 1569370187
+7181 1470772319
+7180 79812466
+7179 632379898
+7178 1351609959
+7177 1694601080
+7176 288522099
+7175 1642338407
+7174 1442229602
+7173 670883243
+7172 1299101791
+7171 948208391
+7170 1769751950
+7169 1967824526
+7168 1639893483
+7167 1487528967
+7166 331034461
+7165 681547310
+7164 2042136499
+7163 1094667216
+7162 1369273768
+7161 2125567529
+7160 2005531442
+7159 1566820558
+7158 897729009
+7157 530717667
+7156 1253074342
+7155 1566195505
+7154 335795112
+7153 1769941949
+7152 1409960480
+7151 936990288
+7150 119162359
+7149 532695034
+7148 343857799
+7147 1856755200
+7146 252371478
+7145 1765122503
+7144 343236616
+7143 494021210
+7142 1048614941
+7141 1478760913
+7140 1455798556
+7139 282222983
+7138 1636790064
+7137 958346173
+7136 1592876116
+7135 1383850893
+7134 1459757190
+7133 2064052617
+7132 1236619422
+7131 1949936858
+7130 1227838520
+7129 1474813775
+7128 313746216
+7127 1479017151
+7126 194922554
+7125 1630616041
+7124 156235025
+7123 1546288472
+7122 1650779589
+7121 877265446
+7120 593132489
+7119 409890807
+7118 280323555
+7117 726218944
+7116 1513518584
+7115 1905833916
+7114 716507562
+7113 1011959350
+7112 1185706302
+7111 766391958
+7110 1989454497
+7109 497452383
+7108 836830515
+7107 252939171
+7106 1646072630
+7105 1676307146
+7104 147231471
+7103 1657303980
+7102 836214097
+7101 1909289294
+7100 1636190642
+7099 1896305017
+7098 1279891221
+7097 830613823
+7096 1322782126
+7095 796117730
+7094 2127320099
+7093 1426453227
+7092 953155983
+7091 1240888782
+7090 596941890
+7089 2014397193
+7088 1043855871
+7087 521973287
+7086 9711382
+7085 501559233
+7084 720127613
+7083 2097599251
+7082 1169988501
+7081 688253919
+7080 2077045091
+7079 1736515325
+7078 998863400
+7077 1308007016
+7076 105707700
+7075 2136252298
+7074 840093049
+7073 385425824
+7072 21113338
+7071 1087392728
+7070 629398073
+7069 805576819
+7068 573522891
+7067 483773490
+7066 850777371
+7065 2043812546
+7064 1990445395
+7063 886431317
+7062 829511337
+7061 1086242438
+7060 197032910
+7059 74968603
+7058 2004685811
+7057 542296638
+7056 1949329322
+7055 59595778
+7054 1479054380
+7053 31873694
+7052 20554160
+7051 1580956824
+7050 1836874167
+7049 769038075
+7048 1630807625
+7047 1010094750
+7046 467913967
+7045 1867765524
+7044 2115138959
+7043 1900183969
+7042 1903511399
+7041 1363020167
+7040 513869837
+7039 145624583
+7038 2102283095
+7037 677193992
+7036 640811743
+7035 2111829702
+7034 1214301209
+7033 904202957
+7032 689398407
+7031 754542734
+7030 1229040275
+7029 1802219920
+7028 273122929
+7027 1945090032
+7026 1210725906
+7025 1917455628
+7024 39041618
+7023 2045581204
+7022 342483175
+7021 1398999733
+7020 2097632847
+7019 826779416
+7018 301124108
+7017 1910525749
+7016 1042439439
+7015 715213645
+7014 2111737773
+7013 752118792
+7012 1386314132
+7011 1757886816
+7010 1408220720
+7009 1984159492
+7008 1652296488
+7007 2137937041
+7006 1610376431
+7005 1884092433
+7004 1422431295
+7003 459758475
+7002 1822646330
+7001 1034662134
+7000 481419805
+6999 1431433890
+6998 591494014
+6997 503150949
+6996 1906048414
+6995 1312628350
+6994 1574972453
+6993 787525533
+6992 2095432005
+6991 1663187406
+6990 1097875625
+6989 187107098
+6988 1931823625
+6987 1733394110
+6986 1946271624
+6985 290320647
+6984 1476383161
+6983 353850957
+6982 1491381720
+6981 1549638288
+6980 105590328
+6979 1417767326
+6978 373783061
+6977 1915687702
+6976 715505746
+6975 1150617955
+6974 61446103
+6973 387769160
+6972 2125822318
+6971 391212440
+6970 443168120
+6969 2125752504
+6968 1672869124
+6967 1426349312
+6966 1075662144
+6965 1118522880
+6964 1364679993
+6963 2059268694
+6962 1837133556
+6961 1908324907
+6960 1878847429
+6959 1511965162
+6958 388319122
+6957 1641502978
+6956 257010949
+6955 1592420667
+6954 946422575
+6953 2074228521
+6952 248260629
+6951 73614393
+6950 1175855226
+6949 337386273
+6948 702261580
+6947 1370648754
+6946 1854241599
+6945 327736586
+6944 1172279285
+6943 1817717311
+6942 2092084688
+6941 69814
+6940 865826963
+6939 1164302455
+6938 1050090360
+6937 554346244
+6936 61669319
+6935 1163877097
+6934 1428872972
+6933 1603838734
+6932 180421265
+6931 325168394
+6930 1520005785
+6929 237344450
+6928 1254954213
+6927 943382103
+6926 695080403
+6925 330266076
+6924 1344160038
+6923 872808181
+6922 898373294
+6921 2058358003
+6920 1518836461
+6919 1952690120
+6918 630628322
+6917 374524994
+6916 198369469
+6915 36524288
+6914 383135545
+6913 1172209470
+6912 951890347
+6911 927782233
+6910 1097463102
+6909 311480719
+6908 1102633136
+6907 2033696910
+6906 1272956920
+6905 605314233
+6904 983455832
+6903 1103704578
+6902 83832949
+6901 2090560463
+6900 1217697829
+6899 576623682
+6898 1689747695
+6897 924688136
+6896 1746705713
+6895 1969755870
+6894 1579376430
+6893 1433285682
+6892 1501455368
+6891 1093166822
+6890 1427729681
+6889 1144311467
+6888 1754320651
+6887 594104033
+6886 2138873096
+6885 1173643646
+6884 1232117589
+6883 1602836960
+6882 74746368
+6881 640409628
+6880 1972632745
+6879 1211249840
+6878 1186007447
+6877 497318902
+6876 1050241078
+6875 169252342
+6874 521481284
+6873 1040379017
+6872 2033490397
+6871 1654692915
+6870 400812768
+6869 293009692
+6868 977401617
+6867 1867475473
+6866 1492795354
+6865 313420030
+6864 468300502
+6863 486209608
+6862 5556001
+6861 357143900
+6860 1486329818
+6859 833625648
+6858 1152922019
+6857 580677005
+6856 1509470092
+6855 536036136
+6854 1098897278
+6853 591707961
+6852 1777687863
+6851 1010980176
+6850 1601885828
+6849 1475313842
+6848 161008761
+6847 1016755105
+6846 2123321266
+6845 9862061
+6844 283245593
+6843 1014272017
+6842 639566249
+6841 1740480704
+6840 677291298
+6839 680820943
+6838 947697986
+6837 663981586
+6836 1399174971
+6835 1006585746
+6834 307864029
+6833 111156601
+6832 1147363437
+6831 1319414001
+6830 1351705529
+6829 905652813
+6828 1471639203
+6827 616885883
+6826 1629263374
+6825 917762131
+6824 905831920
+6823 87917102
+6822 1137305780
+6821 302374021
+6820 849971414
+6819 585130723
+6818 1499476224
+6817 151146700
+6816 733509512
+6815 1109049248
+6814 1517779460
+6813 690248536
+6812 336980719
+6811 2106228954
+6810 792782718
+6809 13309711
+6808 1429129620
+6807 2088595887
+6806 356117557
+6805 1288018369
+6804 2006705957
+6803 1135933676
+6802 906934720
+6801 241710624
+6800 1995258445
+6799 734819646
+6798 1423873087
+6797 553877072
+6796 1858537610
+6795 1541346272
+6794 1927939999
+6793 603457899
+6792 1385429336
+6791 552175057
+6790 950381444
+6789 698824714
+6788 1999104858
+6787 390426976
+6786 780850887
+6785 43260976
+6784 772068529
+6783 1559034154
+6782 2044949466
+6781 323671008
+6780 677099334
+6779 851670479
+6778 1804675802
+6777 141111250
+6776 81889930
+6775 1367667528
+6774 381083649
+6773 1764995333
+6772 1288158879
+6771 172115073
+6770 965321185
+6769 1441381373
+6768 1023765684
+6767 2030010463
+6766 773420721
+6765 1255079711
+6764 155916936
+6763 1375764941
+6762 1800560103
+6761 686604621
+6760 700553847
+6759 559954468
+6758 2065457475
+6757 1955843882
+6756 1765842095
+6755 1369300381
+6754 145795158
+6753 448397521
+6752 881934820
+6751 1193278987
+6750 666478853
+6749 535988083
+6748 769780548
+6747 437008274
+6746 1907511249
+6745 464378245
+6744 79508649
+6743 208968576
+6742 799674148
+6741 1994261153
+6740 1295833037
+6739 1082794370
+6738 667960652
+6737 1916169621
+6736 1874093527
+6735 1545139427
+6734 1602003256
+6733 1616795962
+6732 675211094
+6731 1240605634
+6730 768630794
+6729 892193612
+6728 941596021
+6727 696157094
+6726 1810048724
+6725 1317444574
+6724 487365560
+6723 1099999819
+6722 1929402315
+6721 345946737
+6720 423498438
+6719 229470579
+6718 775960482
+6717 305402303
+6716 357499624
+6715 1698542673
+6714 1812187745
+6713 232731144
+6712 1060619186
+6711 1864363426
+6710 1326300501
+6709 1527147064
+6708 1356184491
+6707 1270304873
+6706 314166365
+6705 257297564
+6704 869928333
+6703 361397621
+6702 848165168
+6701 1930501130
+6700 299009613
+6699 72473700
+6698 1229628536
+6697 1771635095
+6696 208791533
+6695 710048905
+6694 1535525906
+6693 141418823
+6692 676501380
+6691 1699931736
+6690 1717469902
+6689 118096135
+6688 2019454603
+6687 1224901457
+6686 640698205
+6685 124768480
+6684 637923486
+6683 2095307967
+6682 1053914291
+6681 1680955770
+6680 508178935
+6679 55995628
+6678 1212980699
+6677 1098886926
+6676 400376540
+6675 2100252391
+6674 1556616044
+6673 1086910851
+6672 62388008
+6671 775691467
+6670 700872594
+6669 674858165
+6668 2011165815
+6667 519579630
+6666 236109189
+6665 67372710
+6664 33547525
+6663 1983077818
+6662 571432569
+6661 558405245
+6660 1827960781
+6659 492568445
+6658 1624881578
+6657 1894686122
+6656 586977971
+6655 692873886
+6654 1218337837
+6653 1104451364
+6652 1587129032
+6651 997918663
+6650 467975070
+6649 1556775656
+6648 1803102736
+6647 1260211956
+6646 1689754530
+6645 1460949337
+6644 2037864383
+6643 780924577
+6642 386038257
+6641 1535013491
+6640 912009300
+6639 181292963
+6638 438748976
+6637 1943793105
+6636 486032105
+6635 400515018
+6634 1643423789
+6633 1622625928
+6632 155117037
+6631 78864124
+6630 1081007315
+6629 2080758306
+6628 2053074122
+6627 932007692
+6626 676348285
+6625 1630010254
+6624 1253228501
+6623 220419174
+6622 636476294
+6621 30353376
+6620 1342299575
+6619 1355246762
+6618 2014504774
+6617 342153399
+6616 1369831221
+6615 908829953
+6614 1074911080
+6613 502850892
+6612 2016398924
+6611 204745293
+6610 1096264514
+6609 1115699843
+6608 1842744506
+6607 38233958
+6606 300369316
+6605 1010889825
+6604 245397981
+6603 1564559665
+6602 541618613
+6601 221842379
+6600 173273650
+6599 148999623
+6598 1404410021
+6597 423063867
+6596 1826262838
+6595 455929110
+6594 993533960
+6593 1222875125
+6592 1025603247
+6591 1428713179
+6590 163332249
+6589 1000146176
+6588 2132899189
+6587 1105674821
+6586 1414725967
+6585 866980329
+6584 1039914676
+6583 870165786
+6582 1554070025
+6581 900699081
+6580 509484435
+6579 1058030556
+6578 815330527
+6577 831854680
+6576 1940319625
+6575 883293299
+6574 469271212
+6573 23555602
+6572 1391286015
+6571 392618990
+6570 964916005
+6569 1897693430
+6568 470220432
+6567 948480911
+6566 1577013555
+6565 603387713
+6564 1577809511
+6563 1712304429
+6562 1059542876
+6561 25457071
+6560 1443297638
+6559 1205141076
+6558 1732903857
+6557 1265918860
+6556 65760145
+6555 544560180
+6554 1460393951
+6553 139215595
+6552 360681351
+6551 496039469
+6550 85368553
+6549 1825113403
+6548 1265194579
+6547 2079520876
+6546 362583468
+6545 1916764023
+6544 1639490932
+6543 76652222
+6542 1206123244
+6541 1641076232
+6540 2069882205
+6539 16435094
+6538 320679875
+6537 2014316367
+6536 1518155048
+6535 2012192774
+6534 1691328485
+6533 1552352439
+6532 269006791
+6531 2001885448
+6530 440036862
+6529 177378777
+6528 1139380931
+6527 1188343676
+6526 1953008557
+6525 2074028197
+6524 183878829
+6523 964354482
+6522 53847042
+6521 683051596
+6520 1378328537
+6519 153331325
+6518 1462529935
+6517 1495914204
+6516 440029944
+6515 285931245
+6514 710640778
+6513 2145898347
+6512 154253665
+6511 1189688150
+6510 1320396357
+6509 55565838
+6508 645763694
+6507 455970749
+6506 322987882
+6505 2113286256
+6504 1743185983
+6503 1836926685
+6502 1112315577
+6501 91628013
+6500 862504517
+6499 1399176834
+6498 371853868
+6497 1212836381
+6496 1004464847
+6495 988654074
+6494 2020181155
+6493 1648310881
+6492 1733509593
+6491 2047999365
+6490 1368005309
+6489 2029897981
+6488 1860785028
+6487 1176598689
+6486 785273426
+6485 441615245
+6484 131677580
+6483 1668436276
+6482 825501990
+6481 98687827
+6480 543924455
+6479 864425607
+6478 1880061603
+6477 679961086
+6476 860268414
+6475 633544845
+6474 1000970679
+6473 1651557969
+6472 974422168
+6471 1860622391
+6470 1867257793
+6469 1797151783
+6468 394711987
+6467 1530683442
+6466 1340138874
+6465 1503637613
+6464 1402628129
+6463 2119665438
+6462 280305572
+6461 1851095260
+6460 187214336
+6459 191406619
+6458 1244624555
+6457 1419169783
+6456 1044921109
+6455 1264320797
+6454 1763596902
+6453 32989753
+6452 1124511821
+6451 2108560031
+6450 366109871
+6449 2011447017
+6448 4157193
+6447 1246516758
+6446 1826474054
+6445 1356194093
+6444 1806606325
+6443 1287831936
+6442 1931783824
+6441 1324754032
+6440 1465910404
+6439 336574351
+6438 457012909
+6437 1038558021
+6436 128055312
+6435 1367957083
+6434 1223332041
+6433 1699016517
+6432 1932451102
+6431 88898953
+6430 606470705
+6429 915528201
+6428 1293969158
+6427 2127787405
+6426 1803056529
+6425 1011931355
+6424 139808976
+6423 1802520519
+6422 1814363530
+6421 1260548451
+6420 2104402838
+6419 1267076761
+6418 184972963
+6417 795446748
+6416 1587394080
+6415 538642118
+6414 1571893916
+6413 481852293
+6412 1969405180
+6411 1595209473
+6410 867741123
+6409 427352382
+6408 208519038
+6407 1236539474
+6406 1962709628
+6405 576522443
+6404 1582989629
+6403 1134433088
+6402 1092545812
+6401 1016922901
+6400 942413442
+6399 626166947
+6398 1259955320
+6397 282037803
+6396 1987978429
+6395 536009
+6394 1345051473
+6393 1026744173
+6392 1845601329
+6391 547286768
+6390 1075575488
+6389 1308956090
+6388 1827166329
+6387 1793814493
+6386 1371036479
+6385 1105541787
+6384 716720585
+6383 2124168091
+6382 1761594818
+6381 1542052798
+6380 1386690435
+6379 1778685297
+6378 612126402
+6377 1779480243
+6376 1801033492
+6375 828276540
+6374 1631460278
+6373 566066728
+6372 192019645
+6371 466378865
+6370 1904451229
+6369 660375639
+6368 785672166
+6367 1259419310
+6366 1084469977
+6365 961234256
+6364 302418328
+6363 797764705
+6362 2098652332
+6361 536645239
+6360 867604087
+6359 1429244643
+6358 2085403258
+6357 721624541
+6356 1077093907
+6355 1394352036
+6354 1491430617
+6353 1322151435
+6352 737477656
+6351 2130393169
+6350 929926396
+6349 1754693839
+6348 2125135452
+6347 1931333509
+6346 148019965
+6345 1234966764
+6344 636256895
+6343 1165081413
+6342 809099147
+6341 1679127654
+6340 1828190346
+6339 645031918
+6338 1723389310
+6337 1971921558
+6336 957000982
+6335 286705272
+6334 1010065571
+6333 1913256736
+6332 2077644265
+6331 669407689
+6330 598725629
+6329 145979546
+6328 352150736
+6327 691051222
+6326 1377677572
+6325 1902426120
+6324 656874380
+6323 1508521096
+6322 392225039
+6321 1130267464
+6320 5257716
+6319 1146076534
+6318 1606673874
+6317 890168688
+6316 1295076614
+6315 1130422199
+6314 425867616
+6313 1104612889
+6312 1484374715
+6311 164067229
+6310 2103221992
+6309 2003752436
+6308 1835514584
+6307 1436684037
+6306 961855987
+6305 1191227894
+6304 356544655
+6303 340657882
+6302 1314531107
+6301 1931664719
+6300 317256953
+6299 2055158055
+6298 915785622
+6297 597208264
+6296 34176841
+6295 2016640123
+6294 1510201080
+6293 1674090564
+6292 1503263380
+6291 1393632153
+6290 1671077238
+6289 1262572676
+6288 1998483568
+6287 476251675
+6286 464301072
+6285 190463725
+6284 1793531132
+6283 261800387
+6282 1148874545
+6281 1628105927
+6280 476036293
+6279 666537954
+6278 1041896449
+6277 644286690
+6276 1080139382
+6275 621198104
+6274 2024180434
+6273 572363583
+6272 23400929
+6271 1406856700
+6270 1015879097
+6269 1867532337
+6268 2020981213
+6267 1046629146
+6266 1234490831
+6265 507569925
+6264 513376743
+6263 116568927
+6262 3013326
+6261 240690704
+6260 1542632233
+6259 1194825563
+6258 798271604
+6257 1808019842
+6256 830204190
+6255 202500684
+6254 1189072828
+6253 165425205
+6252 1933247742
+6251 482336590
+6250 586209478
+6249 1979233251
+6248 1733882220
+6247 420698344
+6246 767589903
+6245 507775799
+6244 597797175
+6243 617323734
+6242 1703968134
+6241 303352240
+6240 1533359135
+6239 2116733599
+6238 633041505
+6237 1513411288
+6236 533252403
+6235 1117921904
+6234 504556599
+6233 272686039
+6232 721420342
+6231 955671411
+6230 1589902748
+6229 1882096038
+6228 364621372
+6227 595770919
+6226 618947014
+6225 664778985
+6224 416736590
+6223 706736238
+6222 1726699375
+6221 2101498139
+6220 895938018
+6219 165511133
+6218 1211643347
+6217 1226106421
+6216 1970384817
+6215 150266169
+6214 951291313
+6213 294444934
+6212 1231448247
+6211 1734718183
+6210 1817794383
+6209 19947847
+6208 1583481196
+6207 1662603249
+6206 1008854688
+6205 260566363
+6204 396501561
+6203 1696368836
+6202 830266939
+6201 986807952
+6200 591050038
+6199 994131828
+6198 1263149024
+6197 1847326035
+6196 179034329
+6195 2059694424
+6194 1085563257
+6193 462722098
+6192 1958281867
+6191 1561188242
+6190 889854792
+6189 1817315245
+6188 342609964
+6187 1061377178
+6186 274815108
+6185 1675939883
+6184 1066301570
+6183 1364056778
+6182 624134199
+6181 1211500400
+6180 151236987
+6179 155191133
+6178 1158576806
+6177 1322914832
+6176 1266101688
+6175 1459969500
+6174 1577783072
+6173 1557177257
+6172 1105318798
+6171 1983618759
+6170 1871142575
+6169 891207651
+6168 815097499
+6167 1350938248
+6166 761762778
+6165 1863795879
+6164 101412556
+6163 1671858663
+6162 1720350954
+6161 140966622
+6160 1218578278
+6159 1975961262
+6158 1542500137
+6157 814153729
+6156 2142559255
+6155 1058241978
+6154 1051805683
+6153 2002284818
+6152 1212819791
+6151 468943066
+6150 52923593
+6149 975805802
+6148 1036573093
+6147 1846090954
+6146 1892615408
+6145 1856408078
+6144 354650702
+6143 1741647961
+6142 1833518330
+6141 214111147
+6140 1168521260
+6139 520204327
+6138 129444873
+6137 1098785268
+6136 1249525692
+6135 1237387762
+6134 143444924
+6133 2107929582
+6132 453280385
+6131 1891873340
+6130 745950132
+6129 404424549
+6128 1980885654
+6127 484258159
+6126 1909831693
+6125 140274437
+6124 1992905835
+6123 582862617
+6122 1949361225
+6121 237013988
+6120 1579853620
+6119 354316287
+6118 1230674042
+6117 1327648663
+6116 1491440252
+6115 150967447
+6114 22889748
+6113 140539555
+6112 573126701
+6111 1313314003
+6110 84666274
+6109 69735992
+6108 1418162283
+6107 1039540758
+6106 955340343
+6105 1289079757
+6104 784107377
+6103 399055232
+6102 1361979450
+6101 48855836
+6100 2058471334
+6099 261691973
+6098 642076503
+6097 1840611217
+6096 638835972
+6095 1326969076
+6094 338396860
+6093 1755891846
+6092 1150492645
+6091 1595044938
+6090 1153823594
+6089 252204957
+6088 1010359682
+6087 1079706594
+6086 1304758914
+6085 1350900697
+6084 1725324394
+6083 857059393
+6082 55873281
+6081 503390709
+6080 2042635368
+6079 1192609163
+6078 1261879296
+6077 129082525
+6076 255433381
+6075 556285111
+6074 2074583955
+6073 735251540
+6072 488067546
+6071 1100287477
+6070 1554262981
+6069 217860116
+6068 1770339648
+6067 1462591075
+6066 1502214357
+6065 1030427774
+6064 176476431
+6063 890835570
+6062 602068252
+6061 898287687
+6060 584685255
+6059 74117000
+6058 1094929691
+6057 1806942633
+6056 1501865848
+6055 447699521
+6054 1295027416
+6053 1221933685
+6052 961907673
+6051 1010747765
+6050 1388995060
+6049 1913552842
+6048 937175782
+6047 705594185
+6046 201982218
+6045 1667665489
+6044 68217565
+6043 974296478
+6042 1328472207
+6041 270207429
+6040 1477431476
+6039 91671905
+6038 863129407
+6037 739911874
+6036 1286114644
+6035 611378787
+6034 428359522
+6033 1425672391
+6032 306150314
+6031 527951252
+6030 1950841644
+6029 925226270
+6028 719734800
+6027 647230170
+6026 511915216
+6025 279932162
+6024 1633275495
+6023 284279651
+6022 1980422273
+6021 1195838479
+6020 73571983
+6019 683400875
+6018 1711570624
+6017 1416993941
+6016 637376619
+6015 1375169387
+6014 339193281
+6013 1945493784
+6012 1644348650
+6011 1236800302
+6010 1554561670
+6009 737519602
+6008 953040909
+6007 251750619
+6006 311552352
+6005 2007925901
+6004 305228473
+6003 2047891918
+6002 1622314395
+6001 1528407692
+6000 1955700100
+5999 1303611474
+5998 413311054
+5997 439802637
+5996 1161438322
+5995 227635565
+5994 446993537
+5993 437437016
+5992 210707667
+5991 1297021397
+5990 1631751502
+5989 804061690
+5988 46024256
+5987 336401237
+5986 1077800659
+5985 839366483
+5984 1878304385
+5983 1249876627
+5982 390932113
+5981 906829048
+5980 283759393
+5979 1302811051
+5978 425967249
+5977 1092598656
+5976 2094005794
+5975 411144082
+5974 385611506
+5973 924304429
+5972 92191818
+5971 318702920
+5970 1115096638
+5969 1515897462
+5968 142173152
+5967 185675488
+5966 2140292748
+5965 724001306
+5964 16927898
+5963 1297455788
+5962 953169162
+5961 1554129625
+5960 1250997141
+5959 1295350265
+5958 1873744679
+5957 1354141420
+5956 605580499
+5955 1975407680
+5954 448434370
+5953 971475337
+5952 966117234
+5951 1235604710
+5950 480861798
+5949 1338644385
+5948 1356288904
+5947 14823167
+5946 706987150
+5945 1169701365
+5944 318952264
+5943 66908585
+5942 1956691439
+5941 723778003
+5940 176529768
+5939 929421149
+5938 1523088362
+5937 1565655494
+5936 168747590
+5935 842836960
+5934 1918315792
+5933 610281921
+5932 46458646
+5931 1805302544
+5930 1827868594
+5929 2044339369
+5928 689769766
+5927 2045820647
+5926 905707050
+5925 1781588810
+5924 1009290445
+5923 1360313307
+5922 490613539
+5921 1774956497
+5920 2026799454
+5919 466038631
+5918 631657235
+5917 186587539
+5916 1843354550
+5915 640078564
+5914 1360493574
+5913 1742657909
+5912 2037862465
+5911 1027270289
+5910 1348173289
+5909 758357922
+5908 760673559
+5907 680251402
+5906 1794823350
+5905 1705949317
+5904 796378313
+5903 113013247
+5902 929896975
+5901 149602925
+5900 1115532778
+5899 1929531595
+5898 1138632318
+5897 1055664604
+5896 1036530201
+5895 1692877391
+5894 1290975271
+5893 1381817596
+5892 1480997501
+5891 24574907
+5890 1143299262
+5889 1840211915
+5888 770167729
+5887 2139062318
+5886 973577612
+5885 100696641
+5884 749699747
+5883 333223285
+5882 394484620
+5881 1279504542
+5880 266596730
+5879 667921886
+5878 1111018220
+5877 1202207889
+5876 2031356737
+5875 1681810102
+5874 776052342
+5873 646775388
+5872 1144964117
+5871 1147849028
+5870 1158454255
+5869 59868174
+5868 893001393
+5867 1593238575
+5866 1912172981
+5865 1802196253
+5864 211879889
+5863 1266400363
+5862 238518333
+5861 1788269234
+5860 1401890826
+5859 1151720592
+5858 866634302
+5857 669471087
+5856 1389362571
+5855 640354327
+5854 1853695669
+5853 1617678853
+5852 66626554
+5851 1874046381
+5850 168486322
+5849 1211872489
+5848 784048797
+5847 1576691766
+5846 426155547
+5845 1384581349
+5844 536845985
+5843 1775686962
+5842 1635804781
+5841 1085095942
+5840 254847634
+5839 1712699327
+5838 295178841
+5837 1238288788
+5836 1381358686
+5835 645772617
+5834 1563677920
+5833 571094303
+5832 2011993185
+5831 1234281389
+5830 921634932
+5829 732419739
+5828 1909841669
+5827 226279975
+5826 963259066
+5825 1919167366
+5824 573727773
+5823 2127132936
+5822 1449192531
+5821 1002237713
+5820 1089997584
+5819 739278204
+5818 785716942
+5817 1546951096
+5816 1039845780
+5815 797952232
+5814 1896260216
+5813 1599233691
+5812 1520839328
+5811 2070589101
+5810 789917101
+5809 1164042494
+5808 331340641
+5807 1796889872
+5806 1822094516
+5805 810264383
+5804 781263080
+5803 329396530
+5802 1796943019
+5801 1279573446
+5800 1471923368
+5799 695354957
+5798 1916644321
+5797 2138157951
+5796 1800035850
+5795 983609778
+5794 469974835
+5793 1718973707
+5792 1037135352
+5791 709914327
+5790 216520771
+5789 1690530135
+5788 1846916071
+5787 2135248357
+5786 1798174528
+5785 1588095737
+5784 1424596552
+5783 1973154762
+5782 809316590
+5781 356796833
+5780 1739248460
+5779 1140510877
+5778 1489431626
+5777 1668559906
+5776 1015626791
+5775 1492697985
+5774 1160805012
+5773 1649173282
+5772 1004956810
+5771 1101588062
+5770 1510412773
+5769 1481249065
+5768 1042802755
+5767 933034543
+5766 1668183116
+5765 81062142
+5764 2093958074
+5763 1907544156
+5762 1502452936
+5761 1494088864
+5760 1010481903
+5759 228756062
+5758 2039839255
+5757 258820334
+5756 710651805
+5755 1972503414
+5754 778779147
+5753 1067799719
+5752 233906302
+5751 1816289361
+5750 1014848855
+5749 70688553
+5748 124884085
+5747 2144217289
+5746 507754894
+5745 1513937157
+5744 487741175
+5743 59216950
+5742 138760509
+5741 1671191392
+5740 58785307
+5739 577378230
+5738 1960549597
+5737 961740612
+5736 986560117
+5735 1908122608
+5734 726092854
+5733 599869209
+5732 897062252
+5731 1273696874
+5730 1601733257
+5729 751661569
+5728 1665587905
+5727 67335841
+5726 1627524834
+5725 1790335734
+5724 1738597111
+5723 1109973434
+5722 52950864
+5721 163217749
+5720 1691405275
+5719 1018115214
+5718 1710417307
+5717 758430576
+5716 1656476113
+5715 448537944
+5714 1375176647
+5713 964033431
+5712 431643
+5711 1708865927
+5710 1858125443
+5709 1244528342
+5708 1738301761
+5707 52426989
+5706 235647758
+5705 386690907
+5704 1011060355
+5703 1599879628
+5702 1145619600
+5701 145400683
+5700 1755592617
+5699 1534397416
+5698 1271620383
+5697 2022735819
+5696 476222377
+5695 517551400
+5694 1737384870
+5693 1575379362
+5692 1566051807
+5691 1182319298
+5690 600284090
+5689 932974699
+5688 1509122748
+5687 1261879362
+5686 1530737577
+5685 692442682
+5684 448106301
+5683 1813794368
+5682 1253391636
+5681 903386948
+5680 2118047814
+5679 1805698453
+5678 1008880584
+5677 1351610853
+5676 1188850282
+5675 783251777
+5674 1388554955
+5673 865659672
+5672 1991770659
+5671 1758705831
+5670 1021263948
+5669 1880340446
+5668 1058175039
+5667 754068983
+5666 285350949
+5665 1048326663
+5664 1098983241
+5663 555065572
+5662 975095272
+5661 633077108
+5660 1820680197
+5659 1485888375
+5658 1549720770
+5657 816680066
+5656 813773061
+5655 1864426857
+5654 1586534694
+5653 1692203001
+5652 1843230201
+5651 1595176830
+5650 2041990012
+5649 766436961
+5648 616848171
+5647 225628807
+5646 2110539546
+5645 323190609
+5644 938964766
+5643 1777332772
+5642 1991879372
+5641 111430213
+5640 700530792
+5639 267194965
+5638 1594989497
+5637 9848376
+5636 1802569390
+5635 1877769025
+5634 73231390
+5633 465906133
+5632 881869022
+5631 1636690545
+5630 1230839986
+5629 1004000131
+5628 672115314
+5627 1832777561
+5626 1377629019
+5625 1269053708
+5624 21196655
+5623 2138841512
+5622 1797696637
+5621 1076793240
+5620 978328659
+5619 1816361205
+5618 803381063
+5617 293657562
+5616 1434147689
+5615 333206774
+5614 478794885
+5613 827534552
+5612 1076801979
+5611 1724684407
+5610 663924364
+5609 690682416
+5608 612109223
+5607 1864704120
+5606 2084100633
+5605 1336663257
+5604 995900002
+5603 584024493
+5602 1382549795
+5601 2025352539
+5600 964575230
+5599 1545546073
+5598 1773854760
+5597 1550545254
+5596 1811580905
+5595 1386271155
+5594 1618840719
+5593 1091887063
+5592 1160512853
+5591 2128819080
+5590 273412177
+5589 684671097
+5588 382213516
+5587 470174289
+5586 1962346325
+5585 606613136
+5584 1403888442
+5583 901594125
+5582 163610188
+5581 386119563
+5580 1112575184
+5579 946703892
+5578 754065431
+5577 1422929614
+5576 868804117
+5575 1500076140
+5574 2101597110
+5573 1118031111
+5572 1766932911
+5571 1984487370
+5570 251497779
+5569 1561513624
+5568 1881448815
+5567 387583604
+5566 2079188183
+5565 719693842
+5564 225758302
+5563 1637505287
+5562 818474885
+5561 475841756
+5560 1746605564
+5559 1950721536
+5558 869808420
+5557 1923084027
+5556 1213769494
+5555 1060752199
+5554 443002948
+5553 1017768879
+5552 1936502589
+5551 1364389943
+5550 1779537780
+5549 1837129218
+5548 77899775
+5547 1401472939
+5546 1468816152
+5545 1898256654
+5544 1880626877
+5543 117109740
+5542 866533332
+5541 205419287
+5540 103038554
+5539 2011397822
+5538 1629809088
+5537 1161754973
+5536 161825302
+5535 441682896
+5534 2048702605
+5533 1897400194
+5532 2038383371
+5531 1015236997
+5530 1753516984
+5529 1971005184
+5528 736952042
+5527 1956539868
+5526 1480081079
+5525 196000615
+5524 1271733258
+5523 1226096653
+5522 1385714747
+5521 99373370
+5520 1286490168
+5519 378064841
+5518 368313066
+5517 327126769
+5516 1668329710
+5515 1351706412
+5514 1031723321
+5513 1675207590
+5512 14071185
+5511 1002619158
+5510 723093846
+5509 1088767229
+5508 1849572520
+5507 1188126192
+5506 1260536016
+5505 411908755
+5504 550783173
+5503 1033465608
+5502 143883210
+5501 67378186
+5500 278284955
+5499 1944460763
+5498 490924105
+5497 540951427
+5496 684806610
+5495 253984426
+5494 957769515
+5493 1172359888
+5492 2087090132
+5491 1007649906
+5490 1878543952
+5489 959363399
+5488 857218779
+5487 1164090302
+5486 1442887095
+5485 2140605768
+5484 1337635226
+5483 29104163
+5482 952113743
+5481 1072787604
+5480 1300530285
+5479 1682451302
+5478 1975714861
+5477 1437663765
+5476 637343018
+5475 227070408
+5474 268025545
+5473 483404987
+5472 755180653
+5471 346906095
+5470 1723937729
+5469 1884817176
+5468 1259654153
+5467 236939679
+5466 1730665559
+5465 1659930370
+5464 314377941
+5463 2097603257
+5462 1441299584
+5461 1127726733
+5460 150431127
+5459 714453649
+5458 1663959952
+5457 864096659
+5456 1973938724
+5455 1413782932
+5454 1188492024
+5453 264847622
+5452 876057526
+5451 1417146089
+5450 1244556390
+5449 2010350168
+5448 1045108284
+5447 1748644453
+5446 1169638220
+5445 153938031
+5444 1619373403
+5443 2068603098
+5442 906950906
+5441 1017847125
+5440 1234735589
+5439 1486998049
+5438 154151616
+5437 1747207431
+5436 2070045386
+5435 1780545950
+5434 218630786
+5433 1334134856
+5432 1947172129
+5431 726845934
+5430 1611250429
+5429 1433818116
+5428 887998573
+5427 250177020
+5426 1823088282
+5425 1709091101
+5424 537725406
+5423 1918829583
+5422 1167774880
+5421 1013191005
+5420 372037805
+5419 1643395585
+5418 840711948
+5417 891170252
+5416 129271050
+5415 1248518770
+5414 1394470773
+5413 601526278
+5412 833867508
+5411 1567436504
+5410 863695508
+5409 1635011806
+5408 1564436311
+5407 521089314
+5406 1528576645
+5405 735910529
+5404 1980857469
+5403 1639268499
+5402 1870368075
+5401 513354013
+5400 1986331009
+5399 1361073409
+5398 1758213482
+5397 1326391120
+5396 1859935262
+5395 2051742347
+5394 541316221
+5393 1672018048
+5392 1546791778
+5391 1671862943
+5390 172479057
+5389 1628351200
+5388 1514124534
+5387 1739676826
+5386 1644183127
+5385 1675228420
+5384 414651261
+5383 1974517917
+5382 1885314417
+5381 1346339350
+5380 3000193
+5379 342606194
+5378 106435160
+5377 828525782
+5376 687715493
+5375 2036791794
+5374 1013026102
+5373 1467503456
+5372 1800421138
+5371 509294666
+5370 902624179
+5369 659939889
+5368 1648621795
+5367 1853954783
+5366 785074898
+5365 187917213
+5364 504950568
+5363 1016936926
+5362 1499538991
+5361 2065924226
+5360 157738408
+5359 580285878
+5358 2131651721
+5357 1986379762
+5356 1325025565
+5355 1817148858
+5354 1937397651
+5353 1215795559
+5352 1971517724
+5351 1542708223
+5350 1239904190
+5349 1321958059
+5348 1802374349
+5347 217127014
+5346 1962983328
+5345 1367695685
+5344 236370655
+5343 503731435
+5342 564879277
+5341 1140481249
+5340 1008156519
+5339 1196153044
+5338 2022348638
+5337 1460704581
+5336 1349004214
+5335 1915621620
+5334 835861870
+5333 586509990
+5332 859198518
+5331 919253113
+5330 2081756152
+5329 318842294
+5328 1402743961
+5327 314502863
+5326 48982111
+5325 109230006
+5324 1993114782
+5323 394689428
+5322 2123375017
+5321 649559665
+5320 1887817521
+5319 1022777175
+5318 1506458379
+5317 434678664
+5316 2128240007
+5315 1459251892
+5314 802816408
+5313 1243373054
+5312 1643058564
+5311 1516209881
+5310 1265616259
+5309 1694935586
+5308 1994632477
+5307 106727018
+5306 624842711
+5305 762494224
+5304 1056423102
+5303 2064092405
+5302 652237486
+5301 540356223
+5300 1663992799
+5299 1767253289
+5298 269860183
+5297 1293513955
+5296 468871729
+5295 1801776331
+5294 133338637
+5293 1343555117
+5292 654355554
+5291 1100597841
+5290 1290584934
+5289 1453138857
+5288 1042020816
+5287 47206486
+5286 1779345904
+5285 884866953
+5284 1963676976
+5283 1434090175
+5282 2125240443
+5281 2095606626
+5280 1669061051
+5279 1158889240
+5278 1070092874
+5277 1232138253
+5276 1197787564
+5275 708233954
+5274 110256738
+5273 516066878
+5272 400099605
+5271 1032467845
+5270 270496040
+5269 370478844
+5268 1298381559
+5267 615567500
+5266 1160175318
+5265 1272800260
+5264 1147420776
+5263 1180224443
+5262 52970183
+5261 1348700345
+5260 58577025
+5259 1243378447
+5258 1821276600
+5257 157153863
+5256 231013158
+5255 345255729
+5254 907110158
+5253 2015553998
+5252 1912512771
+5251 966351202
+5250 1025513751
+5249 436922798
+5248 2108585324
+5247 361858920
+5246 1121881515
+5245 681720686
+5244 308134349
+5243 1225272541
+5242 245570838
+5241 29620761
+5240 1881569933
+5239 1802412187
+5238 1357787173
+5237 25581299
+5236 1615630372
+5235 2127434523
+5234 1219830077
+5233 1946204079
+5232 1121647418
+5231 957075383
+5230 1674907393
+5229 2048906809
+5228 1012365289
+5227 1476020871
+5226 1397527353
+5225 362942807
+5224 580226606
+5223 2088242603
+5222 990040247
+5221 1475589973
+5220 1005249526
+5219 663654831
+5218 1462524930
+5217 1426864638
+5216 53724571
+5215 2044092622
+5214 436149847
+5213 278513587
+5212 1491186255
+5211 590642299
+5210 819317236
+5209 1855988634
+5208 186781815
+5207 1377836298
+5206 953234869
+5205 1816909941
+5204 1005787104
+5203 262754694
+5202 271296686
+5201 1220224257
+5200 2092193742
+5199 198886522
+5198 651379456
+5197 649422482
+5196 895794265
+5195 1456768398
+5194 1520386208
+5193 1252120280
+5192 1082993077
+5191 326385415
+5190 13065043
+5189 1725868536
+5188 609930260
+5187 1565915956
+5186 990714790
+5185 1922694631
+5184 552906367
+5183 1992991196
+5182 1606679999
+5181 1782681269
+5180 403860483
+5179 1588964585
+5178 902753765
+5177 517355522
+5176 372049194
+5175 690480175
+5174 1545613255
+5173 1933046495
+5172 318044600
+5171 72410164
+5170 568844801
+5169 1442771260
+5168 1450575905
+5167 1342094706
+5166 1276519921
+5165 1791157632
+5164 373775321
+5163 1194000793
+5162 1239055237
+5161 1504608188
+5160 1863938803
+5159 594632735
+5158 735153746
+5157 834719277
+5156 1013009589
+5155 1145207242
+5154 316014632
+5153 917708746
+5152 1589130713
+5151 17715414
+5150 879927504
+5149 2033988609
+5148 1216915391
+5147 212273589
+5146 1119225915
+5145 586486346
+5144 372435575
+5143 1473203091
+5142 1364201694
+5141 1022756988
+5140 769317907
+5139 1374233743
+5138 166251338
+5137 1806901920
+5136 968319385
+5135 82519128
+5134 552102395
+5133 1016650780
+5132 1477545638
+5131 644422502
+5130 769454442
+5129 1029219526
+5128 1729106794
+5127 1737430152
+5126 518704645
+5125 95300843
+5124 1703560177
+5123 298299218
+5122 37781242
+5121 1702625752
+5120 948283670
+5119 667653914
+5118 914762693
+5117 630429045
+5116 1987321662
+5115 1793506472
+5114 1369768300
+5113 1497162235
+5112 703885184
+5111 2137451599
+5110 856505649
+5109 1109899634
+5108 405914358
+5107 83732210
+5106 1254799525
+5105 2099152252
+5104 752457138
+5103 2055163540
+5102 247196338
+5101 448326112
+5100 1062799356
+5099 1179507938
+5098 510514881
+5097 1633805951
+5096 33869975
+5095 220405427
+5094 57519601
+5093 934425
+5092 1497499195
+5091 1517610975
+5090 787863058
+5089 317854625
+5088 827815900
+5087 1268739869
+5086 1408144393
+5085 490159426
+5084 1089621288
+5083 1379800348
+5082 640656586
+5081 1741469197
+5080 1731537241
+5079 772773439
+5078 2002583757
+5077 454245753
+5076 1478758719
+5075 1347119633
+5074 1851955914
+5073 304131026
+5072 992364184
+5071 1215172047
+5070 2085294879
+5069 1576477053
+5068 1145637963
+5067 290109454
+5066 1576286350
+5065 32935550
+5064 870389879
+5063 687392273
+5062 1360555014
+5061 1179644570
+5060 689795075
+5059 1666606837
+5058 1057193880
+5057 337656474
+5056 179118580
+5055 28344044
+5054 1996986488
+5053 1495635739
+5052 1795746755
+5051 2015366794
+5050 1886369088
+5049 1277291488
+5048 1441498368
+5047 655464124
+5046 749773487
+5045 1174627693
+5044 354755449
+5043 636783867
+5042 366319795
+5041 1563370778
+5040 69534084
+5039 1795185425
+5038 190703
+5037 1112702413
+5036 1567203222
+5035 888894076
+5034 819864183
+5033 1838228957
+5032 2145080846
+5031 1841431825
+5030 122450689
+5029 352138601
+5028 1487488257
+5027 1028849836
+5026 488153633
+5025 830966489
+5024 380080937
+5023 2129103342
+5022 1756750298
+5021 518455267
+5020 573868426
+5019 1230904964
+5018 527518001
+5017 266870675
+5016 300708675
+5015 112989620
+5014 808307897
+5013 938868318
+5012 567249783
+5011 718618018
+5010 1563180075
+5009 1104315318
+5008 227982202
+5007 1258780275
+5006 292838230
+5005 1876457913
+5004 891296878
+5003 1125916006
+5002 1715778268
+5001 1792942245
+5000 353943568
+4999 1241084501
+4998 2011468615
+4997 656521767
+4996 648768898
+4995 506533939
+4994 1221699839
+4993 2009109318
+4992 1555234915
+4991 525845334
+4990 2138420914
+4989 306997751
+4988 930196289
+4987 414528381
+4986 1606046425
+4985 1509324004
+4984 1693223485
+4983 89689879
+4982 1523171891
+4981 1610418112
+4980 490635816
+4979 304399800
+4978 811477088
+4977 499007937
+4976 367483397
+4975 1314405871
+4974 160679645
+4973 1245838280
+4972 771972438
+4971 474693766
+4970 1928957278
+4969 1844905448
+4968 592315603
+4967 1504934676
+4966 1582305576
+4965 787143228
+4964 1098782672
+4963 695854505
+4962 2018172052
+4961 1248237164
+4960 1743132692
+4959 1723892533
+4958 848434974
+4957 1568355933
+4956 868788544
+4955 1516356546
+4954 2133635761
+4953 82805372
+4952 1746537711
+4951 1218772091
+4950 798941024
+4949 2139111526
+4948 2084400051
+4947 1644554865
+4946 338328292
+4945 1269128764
+4944 542433433
+4943 1833469526
+4942 1464364650
+4941 1074550638
+4940 2029861811
+4939 424022602
+4938 262599872
+4937 1952656023
+4936 406152004
+4935 886451071
+4934 916454823
+4933 1998029156
+4932 1100205460
+4931 294279519
+4930 399802190
+4929 174776759
+4928 855103989
+4927 1479562075
+4926 1582203820
+4925 785983171
+4924 1917302483
+4923 914863669
+4922 1431347996
+4921 1754909832
+4920 1281855688
+4919 1301869807
+4918 1800783234
+4917 815271286
+4916 1102121432
+4915 652342414
+4914 1952247762
+4913 1615366443
+4912 1951091363
+4911 1040342048
+4910 811950766
+4909 77205788
+4908 17870598
+4907 1523632448
+4906 1036201199
+4905 555606496
+4904 1933729259
+4903 622175304
+4902 1598226966
+4901 925428701
+4900 1586659178
+4899 1067723762
+4898 740056587
+4897 69120817
+4896 1709743240
+4895 667340150
+4894 1502118823
+4893 162392651
+4892 1780491629
+4891 129478189
+4890 2101610246
+4889 466584402
+4888 199748375
+4887 1148440820
+4886 1010507172
+4885 1634238637
+4884 848734699
+4883 911905713
+4882 803415677
+4881 1873885574
+4880 1022471450
+4879 1435801965
+4878 1188488237
+4877 1609747750
+4876 1737386837
+4875 414025895
+4874 1104863178
+4873 1008300558
+4872 1182999773
+4871 530503203
+4870 185372113
+4869 1517538361
+4868 1505464170
+4867 72716437
+4866 714485642
+4865 1547350589
+4864 1034332169
+4863 1372640633
+4862 208266052
+4861 1313907227
+4860 2077213462
+4859 953169426
+4858 1603560877
+4857 712993386
+4856 299706121
+4855 98601459
+4854 830822960
+4853 1122332772
+4852 2036917911
+4851 1515097359
+4850 685397337
+4849 1560207348
+4848 1845898776
+4847 774462341
+4846 504884572
+4845 729086279
+4844 1378509770
+4843 574359974
+4842 822928444
+4841 1812945060
+4840 1172522681
+4839 112655676
+4838 803052718
+4837 2105597229
+4836 1185867915
+4835 1489328657
+4834 1339084536
+4833 1867908590
+4832 1442910819
+4831 1402580274
+4830 1857829997
+4829 1364220076
+4828 653463305
+4827 1504959418
+4826 2029654074
+4825 1324856996
+4824 209167196
+4823 1463209248
+4822 436935435
+4821 476710562
+4820 1816682231
+4819 2058418644
+4818 1055322776
+4817 1116812496
+4816 1543436219
+4815 2078008245
+4814 2053641483
+4813 1713048357
+4812 1549320941
+4811 710272768
+4810 1009892342
+4809 1214409099
+4808 1074271409
+4807 1461207709
+4806 766512693
+4805 1465442973
+4804 46417838
+4803 2083987910
+4802 10078593
+4801 78690742
+4800 749116968
+4799 352870579
+4798 1482049650
+4797 1476089957
+4796 1295792222
+4795 566444825
+4794 887921561
+4793 1879940281
+4792 1794010665
+4791 526000439
+4790 1568871434
+4789 699869735
+4788 514982424
+4787 1124798179
+4786 1210654661
+4785 1977871510
+4784 528687304
+4783 1343368715
+4782 703156015
+4781 334911842
+4780 1783485007
+4779 1696168280
+4778 447896406
+4777 1756312084
+4776 1414789871
+4775 830008430
+4774 1455364380
+4773 2115210743
+4772 1334870942
+4771 1804691662
+4770 744124740
+4769 1420510659
+4768 1204562004
+4767 915604825
+4766 588168395
+4765 1563335589
+4764 919917808
+4763 361921122
+4762 311068847
+4761 1094140930
+4760 11018014
+4759 444073255
+4758 1636698721
+4757 684594562
+4756 596110875
+4755 2014769594
+4754 1274715494
+4753 193775462
+4752 1707367356
+4751 1154471383
+4750 2034499083
+4749 27172923
+4748 281378409
+4747 1765371624
+4746 300947704
+4745 1447062776
+4744 1642621136
+4743 1798156366
+4742 1371086003
+4741 2061843930
+4740 600129657
+4739 1976003563
+4738 832342264
+4737 1788710063
+4736 2143170664
+4735 226247273
+4734 1252266741
+4733 1973260526
+4732 350903108
+4731 2014479240
+4730 1604925856
+4729 1473907100
+4728 1995446028
+4727 1769412775
+4726 1557362716
+4725 402335413
+4724 307402238
+4723 120244111
+4722 306760026
+4721 1680194433
+4720 873092974
+4719 269127459
+4718 1873708867
+4717 981799281
+4716 122750487
+4715 650274986
+4714 75976772
+4713 1728260854
+4712 1198026708
+4711 1542566088
+4710 1229501666
+4709 958903242
+4708 1980316546
+4707 606094991
+4706 536443322
+4705 169910138
+4704 2022827813
+4703 1385271149
+4702 368334670
+4701 1024479656
+4700 19033212
+4699 1982996729
+4698 2064028032
+4697 1593110615
+4696 1462010536
+4695 1437118604
+4694 95575386
+4693 774691453
+4692 1394634785
+4691 37632567
+4690 1953969214
+4689 2038777341
+4688 146376972
+4687 1223433881
+4686 905822508
+4685 541973281
+4684 1599731925
+4683 680894332
+4682 498759187
+4681 239123466
+4680 1709733190
+4679 623406675
+4678 422459920
+4677 1810406408
+4676 730750826
+4675 1298655821
+4674 1949059116
+4673 998348157
+4672 1366237936
+4671 532821588
+4670 1107935272
+4669 573406245
+4668 520986193
+4667 626909427
+4666 1497535228
+4665 687319083
+4664 42483819
+4663 57942819
+4662 968205887
+4661 1503341092
+4660 2038739243
+4659 730535333
+4658 1132954832
+4657 1751887338
+4656 1771185603
+4655 224928176
+4654 43214094
+4653 1360608459
+4652 1118644790
+4651 2022836160
+4650 1964147194
+4649 2046810429
+4648 2040139497
+4647 1271287747
+4646 2008830940
+4645 1879886317
+4644 2079901532
+4643 1416237527
+4642 2037896533
+4641 792831691
+4640 11835395
+4639 481025844
+4638 1223354665
+4637 1981150758
+4636 584425608
+4635 1439592409
+4634 1866596843
+4633 686626374
+4632 166687224
+4631 237670554
+4630 370386260
+4629 286851904
+4628 1106833377
+4627 908026656
+4626 1708673244
+4625 410577144
+4624 1253767034
+4623 167861582
+4622 1543944912
+4621 1219318008
+4620 2130180310
+4619 692859447
+4618 37979489
+4617 160253180
+4616 1338869862
+4615 592593413
+4614 1989473432
+4613 1287069841
+4612 1404402132
+4611 1556870688
+4610 1716960674
+4609 178168285
+4608 2044083884
+4607 1931245904
+4606 114553914
+4605 2045282882
+4604 1272905184
+4603 1628926289
+4602 316240114
+4601 2027318968
+4600 1278320825
+4599 1609843252
+4598 725662308
+4597 696256233
+4596 1801743269
+4595 1540811662
+4594 1014115880
+4593 34449026
+4592 185164919
+4591 851085465
+4590 1181338519
+4589 1969927130
+4588 1501473233
+4587 1592869724
+4586 318263396
+4585 51800021
+4584 1335674929
+4583 432602743
+4582 1717592815
+4581 1226233846
+4580 1660270452
+4579 1933198418
+4578 63614371
+4577 2146284650
+4576 658340719
+4575 633111273
+4574 1729042767
+4573 1393069864
+4572 350605464
+4571 853880510
+4570 1301656660
+4569 582064592
+4568 1955583630
+4567 1332334293
+4566 1829624001
+4565 1767294243
+4564 1355646743
+4563 163030415
+4562 1000594154
+4561 362721437
+4560 1497095880
+4559 1735952443
+4558 1651663733
+4557 1449673212
+4556 257194795
+4555 2033144301
+4554 481690853
+4553 109441082
+4552 919815939
+4551 1931878045
+4550 1162619475
+4549 1661469450
+4548 1274857698
+4547 1577986745
+4546 417241882
+4545 1412754503
+4544 282505809
+4543 875162257
+4542 91413204
+4541 1916024520
+4540 1045780528
+4539 2116806014
+4538 899924239
+4537 188289387
+4536 2124171198
+4535 1666593586
+4534 766700089
+4533 992925305
+4532 813418183
+4531 1412125359
+4530 858541352
+4529 47422668
+4528 1478757648
+4527 1766003080
+4526 967982358
+4525 147753712
+4524 1113328362
+4523 697296456
+4522 1094305255
+4521 1405830136
+4520 657020347
+4519 1732116378
+4518 1244227568
+4517 2009586843
+4516 1295480936
+4515 1689563273
+4514 1321341298
+4513 513964937
+4512 1976865377
+4511 122090838
+4510 1016100281
+4509 857491141
+4508 2140118464
+4507 1380814301
+4506 1569072946
+4505 1131245893
+4504 853175403
+4503 1502058378
+4502 134383953
+4501 765995515
+4500 2080851358
+4499 1240021919
+4498 1226923957
+4497 1331003936
+4496 652674718
+4495 270685902
+4494 1200932105
+4493 1854981873
+4492 40276109
+4491 1509672525
+4490 161602568
+4489 794917151
+4488 436635442
+4487 1702147942
+4486 688245545
+4485 781515998
+4484 1860181544
+4483 1199250460
+4482 1645348304
+4481 1119374236
+4480 129456022
+4479 1782769628
+4478 1435901843
+4477 1008872571
+4476 527638898
+4475 67014568
+4474 996861939
+4473 87179888
+4472 1568690667
+4471 1041845682
+4470 1686555205
+4469 749847422
+4468 587347201
+4467 956238055
+4466 130071830
+4465 945176493
+4464 230409793
+4463 1838743228
+4462 1693379305
+4461 1392842605
+4460 1073037083
+4459 606938274
+4458 106671606
+4457 1802603091
+4456 1989450046
+4455 1636478732
+4454 1283651342
+4453 740807308
+4452 1069794438
+4451 2010062324
+4450 1830956041
+4449 1268067099
+4448 1255130730
+4447 1368887275
+4446 12010631
+4445 440459010
+4444 645807548
+4443 2102499905
+4442 548108330
+4441 818843245
+4440 454498481
+4439 730317150
+4438 619775592
+4437 1789654356
+4436 725828261
+4435 438812250
+4434 1399280836
+4433 985050836
+4432 765706145
+4431 1086441031
+4430 1286170999
+4429 1417917639
+4428 764971876
+4427 617676522
+4426 518951749
+4425 1248642737
+4424 566684294
+4423 1421072665
+4422 1057334915
+4421 1949210987
+4420 754931594
+4419 462068766
+4418 1256056467
+4417 814671720
+4416 723079726
+4415 56994374
+4414 2039834327
+4413 1974447951
+4412 1648001424
+4411 1965274828
+4410 199067653
+4409 812327773
+4408 4488889
+4407 180963342
+4406 390373520
+4405 1888261073
+4404 1820589752
+4403 312839805
+4402 1846363485
+4401 1495272154
+4400 321469155
+4399 668494477
+4398 898965890
+4397 1663812786
+4396 50992228
+4395 1245362732
+4394 191307822
+4393 764956954
+4392 666141071
+4391 595266149
+4390 693154520
+4389 2087743522
+4388 1886472687
+4387 1199062093
+4386 922321040
+4385 896115423
+4384 556476597
+4383 74559499
+4382 1775380298
+4381 835673651
+4380 1960785939
+4379 18104311
+4378 421954253
+4377 263711463
+4376 507857237
+4375 77533715
+4374 41897588
+4373 325317598
+4372 2138854298
+4371 1177869008
+4370 596306264
+4369 805140016
+4368 617502249
+4367 1801086806
+4366 1472504964
+4365 1433518921
+4364 579221661
+4363 1743525320
+4362 71802434
+4361 725881196
+4360 856277110
+4359 1641576074
+4358 1165422482
+4357 990357264
+4356 642585496
+4355 847761541
+4354 1268218772
+4353 1868286594
+4352 261257208
+4351 1757275987
+4350 413719398
+4349 1697074475
+4348 1657730721
+4347 344420538
+4346 221813875
+4345 182539639
+4344 86163065
+4343 1011512228
+4342 1876494982
+4341 1333714281
+4340 560366759
+4339 942703106
+4338 1480118700
+4337 1331466975
+4336 1221865145
+4335 1876463292
+4334 1361716487
+4333 2000824112
+4332 887248210
+4331 577710008
+4330 1707942362
+4329 2013403493
+4328 998990578
+4327 317660941
+4326 1869622140
+4325 921782550
+4324 586504332
+4323 1658426433
+4322 1454567195
+4321 711666381
+4320 99545266
+4319 69298860
+4318 1475260600
+4317 1475191082
+4316 258257473
+4315 1357785294
+4314 453528304
+4313 899932431
+4312 451145469
+4311 933791876
+4310 2001079229
+4309 1376383431
+4308 1868321609
+4307 1751139056
+4306 2117234136
+4305 1368524680
+4304 989215081
+4303 784006479
+4302 292881750
+4301 1021328365
+4300 1726203077
+4299 1390281421
+4298 143781353
+4297 77208028
+4296 1878640256
+4295 211195707
+4294 1614699002
+4293 2022321599
+4292 1558881167
+4291 1385268335
+4290 1383889428
+4289 771837831
+4288 1958525035
+4287 117475306
+4286 1021662778
+4285 1505808689
+4284 906639825
+4283 1667220076
+4282 1046336850
+4281 1222245686
+4280 1212953915
+4279 249940173
+4278 1406632943
+4277 499796928
+4276 761923974
+4275 1333227657
+4274 1075642930
+4273 2115370364
+4272 1205287049
+4271 1050083976
+4270 877547011
+4269 1648995049
+4268 1659124813
+4267 2080069294
+4266 609992674
+4265 2003802305
+4264 799798188
+4263 229430667
+4262 638432171
+4261 787043335
+4260 1574226947
+4259 1266414122
+4258 1897658701
+4257 452716346
+4256 1358319129
+4255 1501926350
+4254 459471839
+4253 1831877787
+4252 454266160
+4251 796396676
+4250 1963096391
+4249 713156987
+4248 1635499847
+4247 73405285
+4246 1571637646
+4245 794037258
+4244 127940608
+4243 25558954
+4242 1237823353
+4241 1703775648
+4240 1538442811
+4239 944961365
+4238 1039002375
+4237 1802806156
+4236 1280271106
+4235 380562006
+4234 1365370134
+4233 12754852
+4232 802687368
+4231 1519501696
+4230 1036868282
+4229 1121510601
+4228 2055578641
+4227 395732351
+4226 2140728154
+4225 1673924990
+4224 1047660189
+4223 1810558811
+4222 2016265044
+4221 1888592821
+4220 1308380477
+4219 1889691105
+4218 1289002989
+4217 841462589
+4216 2092948325
+4215 1546078692
+4214 1703697553
+4213 571648607
+4212 634599790
+4211 292861988
+4210 664773273
+4209 1883120303
+4208 1812173906
+4207 658440368
+4206 437436022
+4205 1267516254
+4204 1725358286
+4203 1993352085
+4202 1123370218
+4201 1828660414
+4200 1611406703
+4199 641135930
+4198 1128266095
+4197 381653651
+4196 1495555810
+4195 330169343
+4194 1805143594
+4193 1306551016
+4192 502178333
+4191 126573938
+4190 599589832
+4189 466917888
+4188 1944226428
+4187 1890407945
+4186 1285248684
+4185 1521299718
+4184 911478901
+4183 1410835565
+4182 2054358982
+4181 898963135
+4180 628171729
+4179 6332905
+4178 1445684281
+4177 544657652
+4176 1080565730
+4175 591567584
+4174 144146035
+4173 2044181520
+4172 381945382
+4171 482234288
+4170 700394319
+4169 1229753051
+4168 1293063768
+4167 798096751
+4166 723993705
+4165 189004794
+4164 1975474658
+4163 1678569655
+4162 706961183
+4161 35260445
+4160 329831158
+4159 856665535
+4158 1329152852
+4157 422926710
+4156 978929043
+4155 2021896767
+4154 1614424384
+4153 12515766
+4152 782663835
+4151 2048026077
+4150 1600762501
+4149 83514077
+4148 1073250823
+4147 854116697
+4146 400511617
+4145 1183867858
+4144 209622202
+4143 1809395395
+4142 1343787200
+4141 1299675979
+4140 1336654167
+4139 2049781216
+4138 505759346
+4137 1104058974
+4136 970105741
+4135 1192907698
+4134 1629527258
+4133 1940214213
+4132 1348738497
+4131 1997779296
+4130 853591240
+4129 2054388096
+4128 2025220140
+4127 1454739733
+4126 955985974
+4125 966413277
+4124 1239232931
+4123 1713881955
+4122 559236913
+4121 699149758
+4120 974775254
+4119 746645804
+4118 1830486108
+4117 2036866613
+4116 644494495
+4115 738599869
+4114 1987564305
+4113 1057429871
+4112 472741228
+4111 1441489632
+4110 793916633
+4109 232595193
+4108 1079675474
+4107 1460335296
+4106 1622015364
+4105 1177375176
+4104 1991652849
+4103 1779231610
+4102 1086622972
+4101 1441834048
+4100 2120042804
+4099 1546335155
+4098 1098402122
+4097 1058806863
+4096 215506802
+4095 1389587667
+4094 407176364
+4093 540083173
+4092 739106701
+4091 1960074756
+4090 1016147298
+4089 1085392289
+4088 102151309
+4087 1091886238
+4086 49302307
+4085 1734548272
+4084 265858641
+4083 546074673
+4082 263513238
+4081 240146035
+4080 361814158
+4079 1481064985
+4078 758063477
+4077 2049783946
+4076 1616166095
+4075 1990267401
+4074 90752204
+4073 549818800
+4072 1806672454
+4071 1687771465
+4070 343431926
+4069 1061235941
+4068 1330828353
+4067 1856298103
+4066 651630499
+4065 1822907277
+4064 650480966
+4063 594585255
+4062 1671419522
+4061 1801198060
+4060 1857923447
+4059 2071744708
+4058 1036089981
+4057 515086685
+4056 826027597
+4055 1650711282
+4054 1471035034
+4053 25712606
+4052 184260515
+4051 929931901
+4050 1629566206
+4049 459513860
+4048 2012382538
+4047 915279723
+4046 1959031742
+4045 1066347294
+4044 183594947
+4043 550464386
+4042 206386874
+4041 745436513
+4040 356943112
+4039 634617470
+4038 409605442
+4037 1655404724
+4036 1205817137
+4035 57045243
+4034 151487754
+4033 996766554
+4032 884145456
+4031 1747158462
+4030 765108078
+4029 1342836761
+4028 1245717111
+4027 1532862347
+4026 1191535299
+4025 800314990
+4024 1466450767
+4023 541103133
+4022 543630048
+4021 1872230303
+4020 1065033011
+4019 714286482
+4018 647965766
+4017 946035243
+4016 731684776
+4015 1408567355
+4014 859960420
+4013 1585642081
+4012 193521274
+4011 1719253052
+4010 335831071
+4009 849022036
+4008 1576283981
+4007 352560198
+4006 1503916969
+4005 209050583
+4004 1320383435
+4003 551812940
+4002 231658475
+4001 1688792343
+4000 501441351
+3999 1379729379
+3998 151301462
+3997 445402120
+3996 66411579
+3995 650432166
+3994 256684942
+3993 1741704112
+3992 1623553770
+3991 1976827214
+3990 1224264537
+3989 118997767
+3988 2130085354
+3987 1386882058
+3986 86074823
+3985 1293526343
+3984 1215046081
+3983 1288191016
+3982 1249811010
+3981 1491982885
+3980 142969071
+3979 2130754521
+3978 1492588715
+3977 1367233397
+3976 1179660411
+3975 952104029
+3974 2124875756
+3973 1779074740
+3972 50371588
+3971 999412744
+3970 1537490881
+3969 56039231
+3968 1313317800
+3967 1648352943
+3966 188717178
+3965 472191115
+3964 1174362044
+3963 427341376
+3962 517439575
+3961 1504556002
+3960 1994225508
+3959 1984866126
+3958 32922944
+3957 836559011
+3956 171835977
+3955 945367455
+3954 43715333
+3953 1870546844
+3952 1145221945
+3951 1266540137
+3950 2146877818
+3949 923219321
+3948 951094109
+3947 540484685
+3946 1389841289
+3945 1548069319
+3944 901732441
+3943 1125463012
+3942 241583859
+3941 2141816005
+3940 1833578592
+3939 2036621585
+3938 2014805700
+3937 841126685
+3936 473990899
+3935 1908859450
+3934 2102235187
+3933 1817289690
+3932 580599516
+3931 680057097
+3930 1471633058
+3929 1157666497
+3928 1813030149
+3927 1235039136
+3926 792843678
+3925 448772781
+3924 1947629158
+3923 924658844
+3922 1871152673
+3921 222002623
+3920 315446027
+3919 1606393133
+3918 1680861680
+3917 1550508438
+3916 1786235892
+3915 264378277
+3914 1306485460
+3913 907400083
+3912 1439368068
+3911 352445921
+3910 127010305
+3909 992451907
+3908 1562630686
+3907 105946250
+3906 886375145
+3905 804184857
+3904 1328259934
+3903 1422178090
+3902 345656631
+3901 1570416667
+3900 1014510595
+3899 236593922
+3898 364822819
+3897 1364257368
+3896 1434893626
+3895 2015668482
+3894 725103755
+3893 1725626535
+3892 609212816
+3891 264759540
+3890 688624591
+3889 912421237
+3888 1967640888
+3887 1416483402
+3886 244022977
+3885 878835809
+3884 972493857
+3883 954039539
+3882 780389778
+3881 446916161
+3880 937298883
+3879 21064055
+3878 106076761
+3877 758445829
+3876 925169963
+3875 1611680703
+3874 458528225
+3873 1905326915
+3872 407667495
+3871 109062709
+3870 1205593848
+3869 1797736875
+3868 949183944
+3867 496637985
+3866 639153613
+3865 1856750739
+3864 1406455665
+3863 460344215
+3862 1037001943
+3861 1844275227
+3860 444602300
+3859 1419624837
+3858 668398260
+3857 1088805079
+3856 443989545
+3855 1437467086
+3854 98446030
+3853 525577696
+3852 16740656
+3851 759325723
+3850 340839399
+3849 178853053
+3848 1243377739
+3847 641879706
+3846 299917604
+3845 1167326696
+3844 1204013208
+3843 349465516
+3842 699733067
+3841 757414268
+3840 1307362413
+3839 708955863
+3838 1158583262
+3837 1239916853
+3836 1237665967
+3835 178809398
+3834 819748795
+3833 1709664086
+3832 15741915
+3831 1764860754
+3830 1175876967
+3829 1503280868
+3828 975635292
+3827 1378414821
+3826 990359049
+3825 2065895496
+3824 1420726430
+3823 1486603955
+3822 184738297
+3821 1985371250
+3820 1663431632
+3819 1846443341
+3818 2026419097
+3817 76051043
+3816 1585350146
+3815 2097935736
+3814 467593628
+3813 446598940
+3812 1189586750
+3811 2138260852
+3810 1746314654
+3809 67445560
+3808 1618773543
+3807 979773864
+3806 420168057
+3805 1675485529
+3804 163067483
+3803 1202371689
+3802 533787119
+3801 659944694
+3800 789225462
+3799 1944945793
+3798 512921819
+3797 1057223443
+3796 2105172039
+3795 1651238742
+3794 1881157199
+3793 1582838828
+3792 1970655971
+3791 485778604
+3790 2106435801
+3789 1587380588
+3788 261093194
+3787 2075967009
+3786 1755941063
+3785 1138751206
+3784 908348985
+3783 476816424
+3782 847767934
+3781 1122141190
+3780 519487309
+3779 766540790
+3778 1794761151
+3777 2090771662
+3776 816706381
+3775 1365280016
+3774 1141698409
+3773 1650606436
+3772 413146226
+3771 736324974
+3770 147022875
+3769 1879485667
+3768 1987257402
+3767 1009166725
+3766 1323549892
+3765 522333211
+3764 1828066419
+3763 1395378595
+3762 1623886675
+3761 383275382
+3760 224685409
+3759 30468791
+3758 1978923173
+3757 1269825636
+3756 1167618024
+3755 1279124639
+3754 290983272
+3753 1933691443
+3752 2104812763
+3751 81227144
+3750 1474863687
+3749 576199294
+3748 2097318057
+3747 429481134
+3746 949073253
+3745 1313583592
+3744 952133790
+3743 405373435
+3742 1503583561
+3741 681144207
+3740 896551219
+3739 1285339797
+3738 555935775
+3737 1464924191
+3736 1328583954
+3735 2075654944
+3734 1045930184
+3733 1444791036
+3732 1170693186
+3731 1593417883
+3730 551835857
+3729 1102343421
+3728 1010334415
+3727 699798534
+3726 978842363
+3725 1381410228
+3724 1321795524
+3723 209756128
+3722 458827756
+3721 1528613468
+3720 131392735
+3719 1045382552
+3718 1774609689
+3717 783734464
+3716 1624830992
+3715 543699817
+3714 1957483679
+3713 270989582
+3712 1656305864
+3711 218243764
+3710 125208432
+3709 1579110676
+3708 2104239491
+3707 627764478
+3706 418994007
+3705 2031276566
+3704 904961758
+3703 1599995948
+3702 892955179
+3701 68349764
+3700 583083468
+3699 1999520970
+3698 123501058
+3697 1776407835
+3696 1525486658
+3695 769086235
+3694 922582472
+3693 1940665703
+3692 78363393
+3691 1560928852
+3690 1901487427
+3689 1495141918
+3688 1568035208
+3687 1230909872
+3686 973734433
+3685 1353841410
+3684 1034877601
+3683 1739239915
+3682 145781150
+3681 77195188
+3680 261487921
+3679 1644927602
+3678 1160116668
+3677 72962925
+3676 1870286368
+3675 966481707
+3674 1138321386
+3673 836611994
+3672 1016912480
+3671 1040917857
+3670 2092332354
+3669 954159281
+3668 474034312
+3667 1501898471
+3666 853825363
+3665 1732304603
+3664 690722841
+3663 1509137268
+3662 39178276
+3661 730705123
+3660 2140377292
+3659 670577555
+3658 521407485
+3657 214193798
+3656 196032270
+3655 1381978166
+3654 1208060260
+3653 957682413
+3652 1477751994
+3651 648337196
+3650 1064562167
+3649 188524996
+3648 1922124882
+3647 193634961
+3646 1082125186
+3645 1033674374
+3644 2097052874
+3643 97403529
+3642 891763287
+3641 62753199
+3640 566883545
+3639 590433883
+3638 100333918
+3637 889213357
+3636 811175629
+3635 1492171743
+3634 1693126326
+3633 2107501366
+3632 1516243624
+3631 1516084369
+3630 209297638
+3629 1926183494
+3628 474545284
+3627 1286912966
+3626 1153617186
+3625 1385833505
+3624 2051709820
+3623 559723064
+3622 2040603894
+3621 1289226998
+3620 873695962
+3619 870927206
+3618 1253883457
+3617 888450508
+3616 244065735
+3615 984721657
+3614 141911086
+3613 2034299675
+3612 1678003632
+3611 301329404
+3610 2109902929
+3609 1825153836
+3608 1926741902
+3607 755645823
+3606 1343570678
+3605 851157911
+3604 2123411767
+3603 177041957
+3602 1898203728
+3601 1737543778
+3600 1041539085
+3599 1069868319
+3598 772566308
+3597 1236195427
+3596 1382686794
+3595 593894122
+3594 1492713259
+3593 762482822
+3592 1833510750
+3591 1169676688
+3590 35343540
+3589 2132729102
+3588 626861471
+3587 269161800
+3586 746539421
+3585 357249708
+3584 1454201672
+3583 1988065330
+3582 2071880393
+3581 2000333444
+3580 522071150
+3579 1354257106
+3578 481583157
+3577 1075583991
+3576 779717704
+3575 1166528721
+3574 1100437830
+3573 385867989
+3572 1282986520
+3571 828335409
+3570 964977470
+3569 1952827305
+3568 1834665173
+3567 178672186
+3566 1890965816
+3565 620203971
+3564 907867020
+3563 323036571
+3562 727139282
+3561 1848265296
+3560 542815217
+3559 1913665388
+3558 1386189680
+3557 269611763
+3556 962443776
+3555 905957739
+3554 432852962
+3553 1601351876
+3552 1465994180
+3551 717623287
+3550 1518750287
+3549 1593970807
+3548 574539402
+3547 1462538084
+3546 2122629808
+3545 393849715
+3544 2031025849
+3543 272102421
+3542 1568374167
+3541 1477642862
+3540 1141153883
+3539 786305284
+3538 61861489
+3537 1214461202
+3536 1418288814
+3535 1567929245
+3534 2040548337
+3533 1207085372
+3532 1927705002
+3531 960957542
+3530 462075615
+3529 273203454
+3528 951221612
+3527 480231941
+3526 1984242448
+3525 1508575548
+3524 1587447206
+3523 1862713323
+3522 82601589
+3521 2019507021
+3520 143083884
+3519 56212203
+3518 1618824646
+3517 180689687
+3516 1578995882
+3515 1850527387
+3514 972959196
+3513 553382987
+3512 1278432186
+3511 782068883
+3510 1415781373
+3509 2074176329
+3508 1515500118
+3507 641415891
+3506 1321396512
+3505 211203442
+3504 1787707891
+3503 1079590795
+3502 745009756
+3501 1654501548
+3500 9735930
+3499 2129327322
+3498 436444653
+3497 1590129712
+3496 1040268383
+3495 121529125
+3494 1425973959
+3493 1715423833
+3492 1719629439
+3491 26389386
+3490 400682375
+3489 2109877845
+3488 624699968
+3487 1915780907
+3486 1355214139
+3485 1025612895
+3484 572095201
+3483 190890313
+3482 1285085261
+3481 1351739504
+3480 1414052413
+3479 774365482
+3478 752779817
+3477 1304296676
+3476 1001191648
+3475 241805717
+3474 1613677333
+3473 133206343
+3472 1069854865
+3471 763166082
+3470 1218056894
+3469 567089866
+3468 1089058939
+3467 314915528
+3466 164155753
+3465 1472328198
+3464 549383334
+3463 1399584573
+3462 1314741458
+3461 1757235242
+3460 1549173065
+3459 632385115
+3458 754663705
+3457 1746570721
+3456 1343685706
+3455 1164323826
+3454 1888011282
+3453 1367839344
+3452 924321548
+3451 510719779
+3450 598959687
+3449 109755737
+3448 1920657482
+3447 510974100
+3446 1838102990
+3445 867985304
+3444 1319434499
+3443 850511251
+3442 1062633097
+3441 502764999
+3440 1821590791
+3439 903141366
+3438 402934113
+3437 1764214389
+3436 1913015842
+3435 912054828
+3434 157586740
+3433 939631740
+3432 1997895155
+3431 682356342
+3430 1002571536
+3429 1950085992
+3428 1436183057
+3427 1737823527
+3426 2006043087
+3425 2123330010
+3424 240002278
+3423 1377291502
+3422 768879657
+3421 814565811
+3420 737545945
+3419 87985587
+3418 419136394
+3417 1052672177
+3416 1339023248
+3415 987591739
+3414 1952835855
+3413 816669500
+3412 1176404107
+3411 159491731
+3410 99830886
+3409 57376402
+3408 1137609171
+3407 1638362933
+3406 1606627649
+3405 973384102
+3404 1061643320
+3403 1622714045
+3402 2084543851
+3401 47809163
+3400 1393656933
+3399 1412231657
+3398 2091526553
+3397 1460336695
+3396 1497821248
+3395 628751584
+3394 1354450353
+3393 1572920115
+3392 639745557
+3391 680894069
+3390 395429416
+3389 1832357416
+3388 896445987
+3387 1579028303
+3386 1247319970
+3385 522353748
+3384 1958671280
+3383 1793344124
+3382 716838613
+3381 1119027705
+3380 1169366207
+3379 608951601
+3378 598232401
+3377 164225069
+3376 576719612
+3375 2131397251
+3374 1036323899
+3373 1013834157
+3372 229057112
+3371 672312194
+3370 103766257
+3369 2080803886
+3368 2061894057
+3367 1462774969
+3366 105886342
+3365 2072384781
+3364 2136489675
+3363 673556283
+3362 1177490699
+3361 954871789
+3360 1931931730
+3359 963884761
+3358 585037446
+3357 374092238
+3356 1767840671
+3355 1601459493
+3354 1952998783
+3353 839643575
+3352 623977917
+3351 107887012
+3350 520795303
+3349 1005141138
+3348 32231989
+3347 614318798
+3346 1275384818
+3345 1710369103
+3344 1902340139
+3343 364011705
+3342 910067900
+3341 295736873
+3340 757901785
+3339 788474936
+3338 1974917544
+3337 2136992924
+3336 1473768941
+3335 1579813706
+3334 894894082
+3333 1181617886
+3332 889108201
+3331 213605937
+3330 369834343
+3329 1557839492
+3328 1343527738
+3327 1131061600
+3326 568577103
+3325 928197096
+3324 977481576
+3323 1845111771
+3322 318848271
+3321 1766320426
+3320 75655023
+3319 2053960153
+3318 1877239968
+3317 469346534
+3316 859462306
+3315 911373113
+3314 800301203
+3313 1606603266
+3312 1753593568
+3311 121592963
+3310 468302977
+3309 768392509
+3308 1462189643
+3307 395103838
+3306 1242098842
+3305 292151055
+3304 690705505
+3303 681288144
+3302 811783543
+3301 1478752357
+3300 1017561847
+3299 1386256390
+3298 989262388
+3297 415330642
+3296 153580024
+3295 870948980
+3294 609348824
+3293 1358644798
+3292 1769456748
+3291 412371766
+3290 2036564106
+3289 1753792137
+3288 1194497847
+3287 965866855
+3286 1816528979
+3285 1400342688
+3284 1305263193
+3283 678708239
+3282 1138300289
+3281 985201059
+3280 806886968
+3279 73199139
+3278 1673777315
+3277 1170038588
+3276 1851881980
+3275 560810697
+3274 1627851159
+3273 1359436796
+3272 1811209945
+3271 1573010801
+3270 489489968
+3269 602231204
+3268 1232676366
+3267 118313408
+3266 1953465466
+3265 942418874
+3264 1248975880
+3263 196977058
+3262 1469564340
+3261 15664611
+3260 1365357567
+3259 1070697250
+3258 2084746806
+3257 1941638806
+3256 1808087310
+3255 1137820739
+3254 262042399
+3253 320062134
+3252 2019304919
+3251 1065101150
+3250 1458907392
+3249 1784332028
+3248 368800806
+3247 1112966617
+3246 1689671076
+3245 492445184
+3244 897084400
+3243 54840358
+3242 869946828
+3241 1208978741
+3240 340334434
+3239 371176560
+3238 796249386
+3237 290257492
+3236 1016821175
+3235 1756488407
+3234 1620338182
+3233 1233311269
+3232 979103139
+3231 398867089
+3230 78401453
+3229 1571202408
+3228 1410093588
+3227 946926066
+3226 1679596407
+3225 1488025176
+3224 1265999468
+3223 1344424897
+3222 1008638390
+3221 234972890
+3220 696300343
+3219 345940774
+3218 94660952
+3217 2023839270
+3216 215882217
+3215 1634830718
+3214 1769982004
+3213 1835589307
+3212 1861989572
+3211 498770267
+3210 412729354
+3209 50076942
+3208 1501839033
+3207 1187244627
+3206 817402958
+3205 1930993554
+3204 777385268
+3203 1221471092
+3202 1154909816
+3201 1555384379
+3200 1136257149
+3199 1278959034
+3198 2039089649
+3197 2069552059
+3196 1828410246
+3195 335171509
+3194 479386786
+3193 1031026578
+3192 648124554
+3191 662697615
+3190 140311938
+3189 819944721
+3188 130058557
+3187 607313882
+3186 253857266
+3185 527776558
+3184 1920324794
+3183 1271211736
+3182 1422859952
+3181 1811912630
+3180 1144414882
+3179 1372968375
+3178 1380157631
+3177 1718329127
+3176 409859359
+3175 1743415514
+3174 776083737
+3173 1369484537
+3172 85213943
+3171 2023434430
+3170 1663778377
+3169 1214188738
+3168 1598032436
+3167 1703918140
+3166 1590165273
+3165 797383668
+3164 1834530603
+3163 1964172819
+3162 890714639
+3161 1975663481
+3160 532639058
+3159 1680481704
+3158 566087454
+3157 1749765646
+3156 834472736
+3155 1130129178
+3154 1252400254
+3153 108412164
+3152 126796854
+3151 49891577
+3150 431754998
+3149 1573569403
+3148 963109016
+3147 1784225765
+3146 942245389
+3145 1187858470
+3144 1658201571
+3143 900132955
+3142 1853189807
+3141 1018508853
+3140 425401993
+3139 2107343885
+3138 1771507113
+3137 800648768
+3136 2016871184
+3135 1773476102
+3134 2054152676
+3133 2006350770
+3132 1431533760
+3131 1357716583
+3130 1409576026
+3129 930357060
+3128 846008968
+3127 1583441924
+3126 497365392
+3125 726060572
+3124 1003332324
+3123 1202508677
+3122 1824140813
+3121 700711098
+3120 1234266208
+3119 795012881
+3118 631324014
+3117 1922734194
+3116 126024194
+3115 42112434
+3114 1482152310
+3113 639692718
+3112 474730962
+3111 1893329570
+3110 1394485388
+3109 1772236873
+3108 90472701
+3107 2145514659
+3106 893979740
+3105 10520414
+3104 341942341
+3103 696436093
+3102 596774744
+3101 501176700
+3100 511707614
+3099 1973617750
+3098 432991667
+3097 119948396
+3096 580109600
+3095 1442340363
+3094 1049403406
+3093 302621225
+3092 2115726116
+3091 1029127932
+3090 69387084
+3089 1459015662
+3088 668988686
+3087 589211580
+3086 440581884
+3085 1633815124
+3084 1714865120
+3083 1736306388
+3082 1392690978
+3081 849977737
+3080 1802856869
+3079 1396454377
+3078 878257133
+3077 79952287
+3076 1803572317
+3075 197543646
+3074 1561229318
+3073 1988249289
+3072 184728479
+3071 770640642
+3070 68185033
+3069 391759218
+3068 1393508149
+3067 1138134952
+3066 1218028638
+3065 277488375
+3064 1474097895
+3063 20275474
+3062 233234141
+3061 656710454
+3060 360139246
+3059 1627659152
+3058 1018433778
+3057 1182657210
+3056 1021830108
+3055 851759143
+3054 241124146
+3053 864887383
+3052 2080933167
+3051 2143720249
+3050 2119204252
+3049 1722904582
+3048 1740365707
+3047 680713486
+3046 666206617
+3045 1962806676
+3044 12815167
+3043 790588676
+3042 1920064256
+3041 1940452909
+3040 1524616140
+3039 1077533729
+3038 1321214228
+3037 1116019774
+3036 1811520705
+3035 1197753164
+3034 44254234
+3033 817387440
+3032 1807619876
+3031 753058636
+3030 1785760324
+3029 1324965684
+3028 605829044
+3027 166674634
+3026 941533063
+3025 156942725
+3024 918309624
+3023 244887545
+3022 893166779
+3021 358028585
+3020 403354541
+3019 1438490765
+3018 1056697965
+3017 1925042679
+3016 667898319
+3015 2023101589
+3014 42742420
+3013 219845906
+3012 1413456183
+3011 842530527
+3010 619238681
+3009 408596366
+3008 1413496672
+3007 123461064
+3006 1071765540
+3005 994133264
+3004 1537616936
+3003 1438679245
+3002 1179110764
+3001 482654192
+3000 147229592
+2999 1619085690
+2998 383432620
+2997 448886319
+2996 1395848658
+2995 696645518
+2994 1411259594
+2993 560281038
+2992 1989016652
+2991 1602159661
+2990 1448814268
+2989 625795510
+2988 770592446
+2987 1181080024
+2986 1882300258
+2985 448052412
+2984 609645405
+2983 1347695541
+2982 1748090873
+2981 1004859817
+2980 1576517503
+2979 495777617
+2978 1484314473
+2977 419363407
+2976 733327776
+2975 1780569943
+2974 1962506148
+2973 1054962744
+2972 1291449653
+2971 1707508722
+2970 99221571
+2969 1845826920
+2968 223237031
+2967 1834270750
+2966 1185110373
+2965 835567620
+2964 855112514
+2963 1956583580
+2962 1258950418
+2961 1363221141
+2960 831567215
+2959 267734244
+2958 890978900
+2957 322540034
+2956 571434618
+2955 534604717
+2954 847445187
+2953 1752269236
+2952 1918661686
+2951 1252313256
+2950 1668028992
+2949 1157154095
+2948 1909933489
+2947 1851228178
+2946 604340907
+2945 1825848680
+2944 489120289
+2943 254997426
+2942 955741172
+2941 1593106381
+2940 1484271690
+2939 412434469
+2938 660716547
+2937 1535153059
+2936 979158236
+2935 1376010441
+2934 1724100850
+2933 1639375020
+2932 1125016365
+2931 991216173
+2930 472242241
+2929 509027181
+2928 1843783274
+2927 356374183
+2926 1622578495
+2925 966649030
+2924 763426678
+2923 1742615578
+2922 84240244
+2921 761507591
+2920 1489863415
+2919 1964284461
+2918 552813188
+2917 84084809
+2916 1362107889
+2915 349343480
+2914 870107507
+2913 1043497556
+2912 918209384
+2911 543306703
+2910 932389834
+2909 2096602279
+2908 1580759880
+2907 1432189754
+2906 1958535857
+2905 1487266864
+2904 250994075
+2903 732884676
+2902 1167132779
+2901 615989184
+2900 1294916547
+2899 115868058
+2898 1033932334
+2897 877134243
+2896 1740431152
+2895 2027446564
+2894 882408786
+2893 1919087
+2892 252752163
+2891 267439430
+2890 208694402
+2889 1405778606
+2888 602176572
+2887 203469708
+2886 1361460949
+2885 318610332
+2884 1578617744
+2883 326800804
+2882 111107722
+2881 969090753
+2880 1110030471
+2879 1647683728
+2878 138066421
+2877 93493016
+2876 1181195678
+2875 1225651181
+2874 320134085
+2873 1782488539
+2872 1585451777
+2871 1051264720
+2870 1729540498
+2869 417782304
+2868 522920554
+2867 1153969417
+2866 2142209105
+2865 1738512065
+2864 1774694401
+2863 614969356
+2862 1940708333
+2861 994457204
+2860 1812746506
+2859 5224694
+2858 44317657
+2857 283566240
+2856 772335611
+2855 1034660145
+2854 207502610
+2853 609526991
+2852 1364253981
+2851 610907642
+2850 831024331
+2849 1016537454
+2848 466488049
+2847 1059898888
+2846 1920842579
+2845 1546190787
+2844 1787683052
+2843 1416353012
+2842 52948040
+2841 1167669473
+2840 528344166
+2839 575571081
+2838 423056847
+2837 931892137
+2836 1526758664
+2835 1527239749
+2834 1945287380
+2833 780237197
+2832 949706498
+2831 1935483638
+2830 950139547
+2829 1529180266
+2828 1380372731
+2827 1157141159
+2826 76063630
+2825 162808620
+2824 1817889812
+2823 1744078615
+2822 1925986308
+2821 347716526
+2820 144419593
+2819 1918609091
+2818 1243178523
+2817 1067780910
+2816 1419699484
+2815 504489567
+2814 1493242747
+2813 620013579
+2812 888008846
+2811 1624860607
+2810 744612626
+2809 1743935677
+2808 1196296065
+2807 1043300746
+2806 1134088405
+2805 746521467
+2804 577533251
+2803 9803741
+2802 1977581297
+2801 1568009880
+2800 555110907
+2799 1940482036
+2798 1453116636
+2797 1217564111
+2796 1486734995
+2795 479468662
+2794 384305960
+2793 1470173286
+2792 1599659022
+2791 7377217
+2790 1252021651
+2789 1224122331
+2788 498909606
+2787 738688956
+2786 1722021811
+2785 799685905
+2784 1763964369
+2783 2015865787
+2782 2022884601
+2781 1291556816
+2780 428564542
+2779 1848795528
+2778 609847272
+2777 449774598
+2776 465767495
+2775 1124284663
+2774 916423817
+2773 1157007019
+2772 1602176482
+2771 37099261
+2770 114893244
+2769 1485030444
+2768 453747041
+2767 973647973
+2766 833258151
+2765 16561709
+2764 1027293288
+2763 376928743
+2762 218151634
+2761 375536691
+2760 1655951258
+2759 513332695
+2758 1649584168
+2757 1846707349
+2756 1122208235
+2755 1853639671
+2754 924284952
+2753 472407552
+2752 1587301245
+2751 174089073
+2750 681709544
+2749 2126273592
+2748 1383028033
+2747 1633046257
+2746 1680834428
+2745 1456244124
+2744 1669591829
+2743 879324556
+2742 1042113775
+2741 117146037
+2740 1730835868
+2739 1288728918
+2738 651772293
+2737 437185332
+2736 2093838333
+2735 456329408
+2734 1945893722
+2733 651756596
+2732 868461132
+2731 1852302587
+2730 873436171
+2729 1956727557
+2728 1538608108
+2727 1943428144
+2726 922422396
+2725 649800682
+2724 266338426
+2723 750195879
+2722 1938181656
+2721 1608511300
+2720 938544688
+2719 1196146935
+2718 445439164
+2717 2074267557
+2716 2110938075
+2715 801509872
+2714 414130349
+2713 1552445792
+2712 1295972335
+2711 1900868504
+2710 1612857392
+2709 1293650536
+2708 1342374233
+2707 195442885
+2706 638775257
+2705 1442081737
+2704 1735351923
+2703 93591135
+2702 1925804073
+2701 1059217223
+2700 313694478
+2699 1077491675
+2698 1034305161
+2697 888807426
+2696 1677089718
+2695 172226517
+2694 859102674
+2693 805310774
+2692 1959134839
+2691 742034721
+2690 1163072136
+2689 1011760779
+2688 1232692507
+2687 1791412939
+2686 1660137208
+2685 558492283
+2684 1653021185
+2683 660745492
+2682 2087072048
+2681 2321799
+2680 558494271
+2679 1417414506
+2678 654875279
+2677 2047776144
+2676 607574610
+2675 545184122
+2674 1663761312
+2673 676134700
+2672 1927380305
+2671 848312398
+2670 24912062
+2669 1572370700
+2668 1547885605
+2667 862078644
+2666 29704752
+2665 871778944
+2664 360575325
+2663 117067952
+2662 1789722285
+2661 947374060
+2660 1656825862
+2659 1519142845
+2658 1499107219
+2657 674200224
+2656 138391754
+2655 999391715
+2654 618903883
+2653 1650699386
+2652 102251221
+2651 669657541
+2650 1494930168
+2649 658201775
+2648 809839896
+2647 109691157
+2646 384014832
+2645 2078923557
+2644 765287465
+2643 815448914
+2642 651222638
+2641 355009604
+2640 1447910441
+2639 1310317066
+2638 1542665948
+2637 676106661
+2636 501503318
+2635 2060120447
+2634 1229540306
+2633 1560684913
+2632 607725738
+2631 270579440
+2630 1595750489
+2629 982625638
+2628 1380751090
+2627 499715503
+2626 55296340
+2625 635176016
+2624 897140494
+2623 2096729990
+2622 155769218
+2621 1591533093
+2620 2007301293
+2619 1385239011
+2618 274186943
+2617 878399987
+2616 1491887340
+2615 1716049566
+2614 1427700919
+2613 410277860
+2612 1515022121
+2611 1488389220
+2610 959827304
+2609 771803780
+2608 808813747
+2607 1630029149
+2606 1594050002
+2605 1088302053
+2604 1452394709
+2603 958960866
+2602 2112418071
+2601 1772583748
+2600 1037311998
+2599 1096034986
+2598 927329297
+2597 745575074
+2596 1750058657
+2595 106049998
+2594 479406798
+2593 1453091049
+2592 89428697
+2591 918013855
+2590 1317346150
+2589 1128901306
+2588 2040835319
+2587 705621025
+2586 1598182716
+2585 1081609479
+2584 201027445
+2583 2086795346
+2582 1597934204
+2581 743218341
+2580 679575473
+2579 1477281803
+2578 1325237425
+2577 1867995342
+2576 177634440
+2575 635089136
+2574 1123367630
+2573 1827294608
+2572 2069132516
+2571 1016383085
+2570 845254451
+2569 291736924
+2568 1493459977
+2567 821279299
+2566 266168275
+2565 296967608
+2564 16621301
+2563 1708876591
+2562 135744899
+2561 1108011039
+2560 1024662184
+2559 611725124
+2558 1678202238
+2557 959225839
+2556 504593580
+2555 1658871017
+2554 1631158923
+2553 1605292752
+2552 1407219873
+2551 120652401
+2550 1565464563
+2549 959063779
+2548 1299647363
+2547 690148289
+2546 744627712
+2545 497823479
+2544 713440268
+2543 106984544
+2542 982040157
+2541 1777395592
+2540 1670406756
+2539 23975152
+2538 25568648
+2537 1196492369
+2536 804657997
+2535 704775332
+2534 161222709
+2533 1056093910
+2532 684214407
+2531 1671503422
+2530 1577292449
+2529 65436344
+2528 107131544
+2527 19331220
+2526 1475550564
+2525 1046784476
+2524 251651144
+2523 1510506521
+2522 39828188
+2521 448156094
+2520 968488686
+2519 875316274
+2518 214436067
+2517 801823883
+2516 2124191668
+2515 637643167
+2514 1663266970
+2513 1083528324
+2512 584061436
+2511 958065005
+2510 1751826943
+2509 473914387
+2508 1366800802
+2507 1468276964
+2506 1035269660
+2505 1896047735
+2504 20560924
+2503 637202934
+2502 1626285109
+2501 618778063
+2500 1564371878
+2499 1557961228
+2498 737369428
+2497 1207830715
+2496 1915163724
+2495 2112527691
+2494 1006956288
+2493 1950978697
+2492 542017835
+2491 1311995562
+2490 233720027
+2489 166664803
+2488 898608254
+2487 1724276547
+2486 1286040561
+2485 1040663344
+2484 53581731
+2483 705201964
+2482 1479185029
+2481 110147048
+2480 1738747851
+2479 283549979
+2478 1586128375
+2477 1618236715
+2476 1447716040
+2475 398066725
+2474 269762625
+2473 1549266509
+2472 1220314704
+2471 68323881
+2470 2028892283
+2469 356541163
+2468 1790281152
+2467 772325385
+2466 200874427
+2465 2111668675
+2464 1570509856
+2463 1842444374
+2462 1717258670
+2461 375353032
+2460 413387308
+2459 656927128
+2458 1028107889
+2457 2005428558
+2456 1670694816
+2455 580838597
+2454 1708961963
+2453 2090918331
+2452 1113937761
+2451 1195635050
+2450 671502321
+2449 120511135
+2448 983317587
+2447 1188061650
+2446 1348474090
+2445 2045933178
+2444 1325235669
+2443 201438744
+2442 1667857874
+2441 863773541
+2440 425526377
+2439 1256566898
+2438 155666735
+2437 1826096125
+2436 1349299177
+2435 505913701
+2434 394410005
+2433 1195156824
+2432 1429057066
+2431 1060331542
+2430 1494728790
+2429 555442398
+2428 1133715960
+2427 447269292
+2426 296466595
+2425 1727260133
+2424 1614384483
+2423 513326913
+2422 1419416010
+2421 993426626
+2420 212317463
+2419 1630924319
+2418 919520693
+2417 1084868056
+2416 2010309628
+2415 1147035345
+2414 378075304
+2413 461462128
+2412 1923396015
+2411 411290976
+2410 708106805
+2409 746913900
+2408 2054751369
+2407 1797236682
+2406 1431686120
+2405 154142353
+2404 1224340283
+2403 1481562111
+2402 1847911681
+2401 873614668
+2400 2074099230
+2399 1047459498
+2398 258975803
+2397 1553939475
+2396 980368457
+2395 1930623330
+2394 307844123
+2393 620957857
+2392 301009450
+2391 1935975339
+2390 73905932
+2389 1274933054
+2388 1768098338
+2387 1919968996
+2386 706792752
+2385 1548847500
+2384 1371122978
+2383 2114267975
+2382 1900838971
+2381 1176482115
+2380 504023255
+2379 1058353771
+2378 1462711428
+2377 1900609016
+2376 572896398
+2375 2097607657
+2374 453714319
+2373 350725615
+2372 1554946529
+2371 800452183
+2370 614638865
+2369 520159755
+2368 67091041
+2367 475836121
+2366 1246095352
+2365 359410599
+2364 1629613880
+2363 519352432
+2362 547051925
+2361 1173560043
+2360 167877000
+2359 301420584
+2358 568140302
+2357 219250838
+2356 548846018
+2355 740008425
+2354 1795492177
+2353 194640862
+2352 1610244720
+2351 842485199
+2350 1861254335
+2349 750897887
+2348 485457373
+2347 1512587419
+2346 1446894696
+2345 222170783
+2344 542661128
+2343 1800745784
+2342 1883570398
+2341 1034786774
+2340 733361142
+2339 138802744
+2338 1421548051
+2337 1855164089
+2336 993705889
+2335 726742920
+2334 1959842322
+2333 456053836
+2332 351475431
+2331 245631340
+2330 605419741
+2329 2096109810
+2328 1900058214
+2327 1975615525
+2326 571242309
+2325 354205155
+2324 1277247353
+2323 953006977
+2322 480870175
+2321 859346832
+2320 357027826
+2319 348666916
+2318 1451486839
+2317 263286590
+2316 969926291
+2315 1793632560
+2314 486084032
+2313 1655358002
+2312 1067384641
+2311 1744767654
+2310 1760722371
+2309 1025680701
+2308 1292580503
+2307 694805131
+2306 2042805415
+2305 537652052
+2304 375267488
+2303 1714210982
+2302 1998117743
+2301 402849269
+2300 493056774
+2299 777287864
+2298 1524867500
+2297 1545853059
+2296 698368172
+2295 1765718980
+2294 2020818628
+2293 417900520
+2292 595979151
+2291 132203258
+2290 1555343641
+2289 93741236
+2288 1526224273
+2287 1805337926
+2286 1924686205
+2285 1462051937
+2284 726247919
+2283 888800026
+2282 2042119279
+2281 41703940
+2280 452187151
+2279 1065917240
+2278 1130358934
+2277 754928450
+2276 319537642
+2275 328594433
+2274 687017957
+2273 2119901867
+2272 1221154208
+2271 1220829878
+2270 1025465417
+2269 1094687363
+2268 78919692
+2267 1906632168
+2266 1672518078
+2265 280467651
+2264 1169739829
+2263 1888615370
+2262 1010040527
+2261 502237914
+2260 753462633
+2259 1897489363
+2258 316538679
+2257 64172336
+2256 1079090007
+2255 1035886179
+2254 1567416306
+2253 684543978
+2252 436612874
+2251 976202039
+2250 1058828654
+2249 1844742349
+2248 746379597
+2247 801764501
+2246 67910493
+2245 347119423
+2244 1254923873
+2243 1613671727
+2242 1094436450
+2241 126466845
+2240 1141910186
+2239 1266316896
+2238 1569652932
+2237 1945935689
+2236 736892339
+2235 1931386356
+2234 1417910772
+2233 667501914
+2232 1135152737
+2231 1260034812
+2230 185699235
+2229 689290296
+2228 818399355
+2227 1428136147
+2226 644239678
+2225 394546029
+2224 599273305
+2223 591214267
+2222 1773198972
+2221 739354173
+2220 229822441
+2219 257064153
+2218 1776831856
+2217 399260174
+2216 1694324276
+2215 601722414
+2214 1400166621
+2213 1128457028
+2212 471761541
+2211 1975603201
+2210 704297560
+2209 1343458145
+2208 529424557
+2207 1785750224
+2206 528024916
+2205 69390425
+2204 796233619
+2203 157875960
+2202 481802679
+2201 445862440
+2200 441635456
+2199 905046736
+2198 45050618
+2197 423853326
+2196 828862842
+2195 53025411
+2194 768830705
+2193 2007402779
+2192 361391825
+2191 1516134818
+2190 1110005965
+2189 1978045915
+2188 710223525
+2187 1175109442
+2186 1146577200
+2185 565867248
+2184 129960873
+2183 1572047068
+2182 424159467
+2181 1275787044
+2180 1446178644
+2179 1066030984
+2178 815433228
+2177 460034132
+2176 989516604
+2175 370148956
+2174 1735071394
+2173 350371179
+2172 1863724152
+2171 1724239591
+2170 400811822
+2169 17782130
+2168 76183893
+2167 2139508854
+2166 1802506269
+2165 968943711
+2164 1839117234
+2163 1400179534
+2162 897396814
+2161 530829558
+2160 805911293
+2159 2082380171
+2158 831468715
+2157 144356277
+2156 1045148569
+2155 1722013780
+2154 141707780
+2153 1001657477
+2152 125868423
+2151 1505612131
+2150 460353815
+2149 986144512
+2148 76514380
+2147 445284272
+2146 872446386
+2145 639145425
+2144 653908452
+2143 10831803
+2142 2097043004
+2141 1845942022
+2140 1648055697
+2139 408786616
+2138 362759508
+2137 1254723830
+2136 300391620
+2135 402326735
+2134 71546897
+2133 1308287676
+2132 594268241
+2131 962500290
+2130 1846844491
+2129 661555015
+2128 1037231602
+2127 1256938582
+2126 2648497
+2125 43491092
+2124 1596145357
+2123 783579297
+2122 541303661
+2121 1287207559
+2120 1429097751
+2119 15069543
+2118 113698126
+2117 1584852602
+2116 1938859468
+2115 861614583
+2114 689586069
+2113 955450078
+2112 510259753
+2111 1688256388
+2110 1483182513
+2109 393331867
+2108 108394995
+2107 2107916421
+2106 1183176933
+2105 1139587592
+2104 1955542141
+2103 1256530254
+2102 1608926833
+2101 2080196874
+2100 2072752336
+2099 589905908
+2098 658906518
+2097 993740510
+2096 1808276873
+2095 1366552847
+2094 1649671078
+2093 308937798
+2092 1501965194
+2091 526234118
+2090 1173509432
+2089 1991728796
+2088 223693722
+2087 1399567191
+2086 895266533
+2085 983409390
+2084 351354829
+2083 1148813328
+2082 1619751212
+2081 116927886
+2080 1579861393
+2079 1522749740
+2078 1357638581
+2077 1116291051
+2076 152374280
+2075 2074130327
+2074 1678144407
+2073 2022828915
+2072 1331261566
+2071 1019020924
+2070 1421290355
+2069 1079011825
+2068 929112683
+2067 1439837319
+2066 1491553080
+2065 1499339075
+2064 2012071301
+2063 1123436960
+2062 1282912013
+2061 1657720046
+2060 302540396
+2059 1921425889
+2058 1096462263
+2057 1387767980
+2056 1048212362
+2055 1893936853
+2054 1511141826
+2053 234426943
+2052 1716435583
+2051 97001472
+2050 906772953
+2049 463570342
+2048 1370375460
+2047 1430991902
+2046 1585630291
+2045 277029012
+2044 742868760
+2043 659123483
+2042 601538560
+2041 252249741
+2040 89908241
+2039 2128936684
+2038 1734942393
+2037 1577257255
+2036 1575249666
+2035 368116120
+2034 216427062
+2033 354351255
+2032 820896564
+2031 1508969772
+2030 561257783
+2029 1062256064
+2028 873213527
+2027 1350009058
+2026 2024109802
+2025 813785419
+2024 177501269
+2023 1414140353
+2022 1475137638
+2021 1252865241
+2020 874109660
+2019 1623264698
+2018 1025423698
+2017 1093346447
+2016 688123142
+2015 926506808
+2014 1822974100
+2013 490619019
+2012 569215241
+2011 620085523
+2010 664790995
+2009 660134634
+2008 553687018
+2007 1366826273
+2006 1360830193
+2005 1220898411
+2004 1694703204
+2003 854940937
+2002 1940577120
+2001 1906124148
+2000 635756245
+1999 1358732373
+1998 1185629910
+1997 59428108
+1996 1172507788
+1995 609969448
+1994 1486131429
+1993 1072119676
+1992 540030693
+1991 1999356587
+1990 227441543
+1989 1928246861
+1988 935141556
+1987 98916890
+1986 1417855995
+1985 197504122
+1984 357291567
+1983 1202888577
+1982 1973311672
+1981 2056564255
+1980 66398505
+1979 1445448370
+1978 1446788088
+1977 1480272255
+1976 1819606717
+1975 505889256
+1974 1427804939
+1973 1936062704
+1972 219184692
+1971 581844747
+1970 720494238
+1969 576328137
+1968 186224584
+1967 575660461
+1966 720780327
+1965 100388112
+1964 69938755
+1963 1634258489
+1962 844678133
+1961 759267480
+1960 1064215031
+1959 128524653
+1958 510390866
+1957 737637434
+1956 1889108971
+1955 214967418
+1954 371676098
+1953 448210959
+1952 1136490072
+1951 527863302
+1950 609776167
+1949 733609897
+1948 1773325300
+1947 940898832
+1946 52467316
+1945 2031027661
+1944 286704564
+1943 845960192
+1942 1215568466
+1941 1790340202
+1940 395620162
+1939 144833776
+1938 2003031458
+1937 85836472
+1936 505721706
+1935 1234005485
+1934 1403193627
+1933 1458154923
+1932 570043458
+1931 716153480
+1930 248876614
+1929 326577597
+1928 386899330
+1927 295423448
+1926 365961335
+1925 1440898011
+1924 1225960994
+1923 1991296444
+1922 1985918440
+1921 402880174
+1920 902021649
+1919 1816360982
+1918 681142581
+1917 1889781287
+1916 654194268
+1915 1353990772
+1914 815459195
+1913 643848009
+1912 450340029
+1911 1070734689
+1910 1934792392
+1909 309783690
+1908 1786595718
+1907 769025973
+1906 830126492
+1905 1195050430
+1904 663962027
+1903 687040147
+1902 1209278309
+1901 243465861
+1900 329254150
+1899 2100936814
+1898 2108099909
+1897 1093484966
+1896 1216946102
+1895 522148539
+1894 1602463219
+1893 823080819
+1892 1089274795
+1891 169557458
+1890 1869221241
+1889 1159724010
+1888 1162166714
+1887 1474635456
+1886 1074322091
+1885 10346259
+1884 903650743
+1883 1892208154
+1882 856539265
+1881 140556339
+1880 1431622619
+1879 1165766419
+1878 1627140846
+1877 591545288
+1876 105063946
+1875 143086345
+1874 2133255769
+1873 420496165
+1872 357785997
+1871 1255825143
+1870 282849600
+1869 1383252831
+1868 883990712
+1867 1585951370
+1866 1638505395
+1865 393865282
+1864 1580357392
+1863 1432905761
+1862 1101343226
+1861 2077034432
+1860 1154874392
+1859 394585785
+1858 85401919
+1857 1151820455
+1856 570984713
+1855 1329597585
+1854 1301290641
+1853 763094404
+1852 460585535
+1851 1838256494
+1850 660899141
+1849 840077331
+1848 1060702473
+1847 1484054501
+1846 605773167
+1845 1832051428
+1844 1932783995
+1843 877430625
+1842 137646565
+1841 1122016814
+1840 371834431
+1839 844381877
+1838 1892231084
+1837 490125429
+1836 5593978
+1835 205599634
+1834 1440005704
+1833 1650806607
+1832 278031369
+1831 706757441
+1830 1991632513
+1829 3053937
+1828 1971084719
+1827 903287981
+1826 1998013461
+1825 1955373957
+1824 869012050
+1823 1610517795
+1822 102195263
+1821 1767991852
+1820 777554021
+1819 1324328288
+1818 234304164
+1817 1376134692
+1816 1698754153
+1815 1875826189
+1814 1694404863
+1813 810767181
+1812 505596194
+1811 1440748336
+1810 1377269378
+1809 2029192650
+1808 838787899
+1807 1686631449
+1806 1197603373
+1805 502271019
+1804 2075051913
+1803 733248263
+1802 1806657742
+1801 274977432
+1800 883156369
+1799 1088344532
+1798 152524123
+1797 15710762
+1796 34275931
+1795 387495666
+1794 1853178694
+1793 1248503846
+1792 832963773
+1791 925350623
+1790 1533687688
+1789 1548902977
+1788 1773057782
+1787 505961622
+1786 1829213477
+1785 887986972
+1784 1370229995
+1783 253656527
+1782 1580981451
+1781 623887192
+1780 601960437
+1779 1838121576
+1778 831589277
+1777 336516880
+1776 1759063184
+1775 464355109
+1774 843096925
+1773 1800074481
+1772 1997575542
+1771 718313210
+1770 122453308
+1769 867445607
+1768 1054068601
+1767 1912512105
+1766 310015715
+1765 933255732
+1764 1702015541
+1763 927828071
+1762 1862299806
+1761 1431544444
+1760 1299776488
+1759 1027726065
+1758 1867173147
+1757 885070810
+1756 1283215275
+1755 1575556950
+1754 1454489168
+1753 746342803
+1752 1799179738
+1751 1890343523
+1750 1939781563
+1749 265443557
+1748 79058392
+1747 367234167
+1746 1640903603
+1745 2106472350
+1744 614263215
+1743 124783715
+1742 1677621173
+1741 1130129934
+1740 1811728257
+1739 357424851
+1738 557429892
+1737 120812868
+1736 210496564
+1735 1529671292
+1734 1218439574
+1733 270471096
+1732 1775535231
+1731 834573741
+1730 1711854945
+1729 414705678
+1728 1891994438
+1727 291616197
+1726 1578065290
+1725 536872471
+1724 1923860860
+1723 1711629293
+1722 954044888
+1721 1533736181
+1720 1811285130
+1719 1572547395
+1718 772023602
+1717 120069690
+1716 1900454600
+1715 1516119888
+1714 428851177
+1713 1631616929
+1712 460539106
+1711 1320196321
+1710 572700042
+1709 1690915388
+1708 146928287
+1707 1175242248
+1706 1049856942
+1705 2087509115
+1704 1901619709
+1703 383865833
+1702 706099799
+1701 1360829553
+1700 1090062950
+1699 1420238748
+1698 984124036
+1697 1355121967
+1696 515238984
+1695 2013919644
+1694 1730311231
+1693 390124679
+1692 2047827811
+1691 1528981141
+1690 761712579
+1689 1691215440
+1688 1819576443
+1687 1403387362
+1686 1838702160
+1685 268837671
+1684 1055580782
+1683 1256138504
+1682 1058916886
+1681 917107365
+1680 1173268034
+1679 1544941442
+1678 641058446
+1677 206902820
+1676 1421106187
+1675 665991109
+1674 1381409316
+1673 540790156
+1672 1441286530
+1671 1433344699
+1670 376705517
+1669 1882424631
+1668 904999763
+1667 1117688039
+1666 1772294384
+1665 125114305
+1664 2113575481
+1663 201330090
+1662 1775895748
+1661 356612370
+1660 1856888345
+1659 1505808865
+1658 1999996928
+1657 1550738772
+1656 347806580
+1655 582563656
+1654 1357404432
+1653 138473416
+1652 82870470
+1651 1661459092
+1650 276048919
+1649 966365214
+1648 123835255
+1647 2122550984
+1646 972977152
+1645 880316031
+1644 1372188227
+1643 2095548264
+1642 164084639
+1641 1706345547
+1640 528344936
+1639 1406501125
+1638 110130247
+1637 779885458
+1636 1151596206
+1635 1570964294
+1634 496702204
+1633 1756963111
+1632 491925392
+1631 270086883
+1630 504099090
+1629 306149573
+1628 1158002285
+1627 1417433271
+1626 193334340
+1625 209333163
+1624 499693186
+1623 1843428988
+1622 2009908145
+1621 1263988904
+1620 1537623836
+1619 300981583
+1618 2140871710
+1617 1391002872
+1616 750362757
+1615 1024912535
+1614 716231392
+1613 1813326328
+1612 1567203328
+1611 905067161
+1610 1596215299
+1609 1895943125
+1608 254904919
+1607 686649601
+1606 283183254
+1605 1542116743
+1604 1079038901
+1603 226615321
+1602 1252864020
+1601 185775819
+1600 1259568246
+1599 1234149467
+1598 112815233
+1597 948669121
+1596 917740085
+1595 497388999
+1594 346908666
+1593 1383187930
+1592 305805152
+1591 1708926562
+1590 1270600842
+1589 146620964
+1588 1698102473
+1587 1115959174
+1586 674771480
+1585 1084520077
+1584 1605192855
+1583 1958647878
+1582 217111028
+1581 1818743851
+1580 650162242
+1579 909565698
+1578 1612759871
+1577 860271824
+1576 1755094348
+1575 56567933
+1574 289252722
+1573 893263082
+1572 1114530722
+1571 18714553
+1570 72960586
+1569 310899125
+1568 316409382
+1567 1762909881
+1566 601760455
+1565 1682035802
+1564 191583847
+1563 785465752
+1562 112587088
+1561 159184188
+1560 10824088
+1559 154641667
+1558 1619333131
+1557 613582396
+1556 1658249967
+1555 863607250
+1554 867409049
+1553 1933932652
+1552 1308485636
+1551 1455028978
+1550 205983979
+1549 1937374065
+1548 1301954998
+1547 1556191938
+1546 571019102
+1545 861831266
+1544 1089520858
+1543 270538169
+1542 820302495
+1541 803631597
+1540 1849788819
+1539 457534353
+1538 1856622318
+1537 781857227
+1536 1571326034
+1535 1963778350
+1534 1569448714
+1533 32399659
+1532 774641664
+1531 2105429069
+1530 687334704
+1529 1544725340
+1528 643875348
+1527 755725881
+1526 1893656995
+1525 1871800963
+1524 1702605261
+1523 1559863718
+1522 1727948672
+1521 1518595219
+1520 153073980
+1519 797275689
+1518 1366354963
+1517 440123732
+1516 466671080
+1515 300480933
+1514 41528770
+1513 285889261
+1512 568232997
+1511 362768142
+1510 1094492927
+1509 1067931592
+1508 1033691967
+1507 2040327615
+1506 1359892161
+1505 1538926374
+1504 1189136686
+1503 1611503293
+1502 1492548603
+1501 1377399971
+1500 1461553721
+1499 2079092471
+1498 1798551993
+1497 919558032
+1496 1200604268
+1495 333793276
+1494 143852291
+1493 184010042
+1492 1406789738
+1491 930672983
+1490 152240255
+1489 1860433896
+1488 330604609
+1487 1065874030
+1486 398594961
+1485 180781819
+1484 1879731583
+1483 1826244276
+1482 1338879981
+1481 1647785053
+1480 1476559823
+1479 1201648960
+1478 1855523078
+1477 1642249240
+1476 851190929
+1475 1895872516
+1474 46377771
+1473 1959220363
+1472 149949572
+1471 1560939780
+1470 1726331626
+1469 541995688
+1468 878488203
+1467 1464758717
+1466 775705741
+1465 1016594225
+1464 1074487186
+1463 1360662955
+1462 31769787
+1461 1693839489
+1460 600068374
+1459 1233849873
+1458 1461838935
+1457 149822790
+1456 1333626095
+1455 719834333
+1454 989385485
+1453 231946530
+1452 349684452
+1451 137231021
+1450 1939745623
+1449 1981794231
+1448 350458031
+1447 2107134210
+1446 1595871469
+1445 1039454214
+1444 1745922944
+1443 632921639
+1442 232888737
+1441 1755437531
+1440 682451577
+1439 261572909
+1438 1913773595
+1437 2009377625
+1436 390271530
+1435 1562526434
+1434 984824438
+1433 1528131345
+1432 760594581
+1431 945403562
+1430 232000554
+1429 450245584
+1428 2047707426
+1427 742004601
+1426 1307920952
+1425 1101679565
+1424 370149881
+1423 852154464
+1422 439684555
+1421 515373869
+1420 1934256638
+1419 1980095061
+1418 385922762
+1417 1458487465
+1416 361211265
+1415 962949829
+1414 806565477
+1413 2137969061
+1412 2097953710
+1411 2118799476
+1410 1989147584
+1409 820557599
+1408 2018785026
+1407 351247161
+1406 1024553187
+1405 1009623833
+1404 801931853
+1403 39420876
+1402 1296130790
+1401 310348996
+1400 1045179784
+1399 1637479601
+1398 1289808280
+1397 946027861
+1396 371854720
+1395 455766488
+1394 661995010
+1393 2002259659
+1392 1065381473
+1391 607073142
+1390 129451107
+1389 475769173
+1388 1618883795
+1387 1570456580
+1386 651921988
+1385 370725852
+1384 1012479767
+1383 835249649
+1382 148821476
+1381 1277396111
+1380 100014449
+1379 1637900423
+1378 1943488060
+1377 1009161193
+1376 1696798956
+1375 985132310
+1374 1860976691
+1373 491582856
+1372 1141724740
+1371 1806134837
+1370 1168024364
+1369 99151923
+1368 1265624880
+1367 834041791
+1366 284032851
+1365 517078709
+1364 1537868663
+1363 54921868
+1362 1872808552
+1361 589612300
+1360 1135672994
+1359 706478175
+1358 1971330833
+1357 1248157943
+1356 557976813
+1355 1964155987
+1354 221904376
+1353 1882567304
+1352 735235199
+1351 658404701
+1350 1481391698
+1349 1238336904
+1348 2088585115
+1347 958355750
+1346 1295668150
+1345 1205216099
+1344 1990891218
+1343 54841853
+1342 1471042140
+1341 1042572817
+1340 540509957
+1339 333982573
+1338 1962602720
+1337 748546171
+1336 1443656776
+1335 229110983
+1334 791753805
+1333 948256363
+1332 1066732521
+1331 1166330377
+1330 765765114
+1329 2034998699
+1328 148501361
+1327 7174846
+1326 1026253567
+1325 822893157
+1324 1228920787
+1323 1710983323
+1322 401175605
+1321 1644381943
+1320 717303233
+1319 523035948
+1318 2090152402
+1317 883369016
+1316 1114948180
+1315 1240826296
+1314 1881657607
+1313 948318400
+1312 1661815544
+1311 1137059567
+1310 1227453745
+1309 1939447433
+1308 1037809444
+1307 1733491737
+1306 2104276014
+1305 495400413
+1304 1309862109
+1303 1772907076
+1302 182491248
+1301 1179217470
+1300 1017829015
+1299 758590268
+1298 1008745132
+1297 1473091852
+1296 925737707
+1295 1462753892
+1294 421717552
+1293 1732022492
+1292 993680089
+1291 2025623305
+1290 1701713188
+1289 1981417865
+1288 1555571416
+1287 849326106
+1286 1149195056
+1285 166629779
+1284 1726494400
+1283 744598040
+1282 1868348303
+1281 1869851759
+1280 99250122
+1279 1641445656
+1278 1982655067
+1277 542409031
+1276 423629627
+1275 331368938
+1274 312909165
+1273 130644639
+1272 755078060
+1271 1571384628
+1270 170472337
+1269 1692220811
+1268 1980336209
+1267 1693474888
+1266 1051374300
+1265 1341198862
+1264 469073802
+1263 543577895
+1262 30309304
+1261 1159745872
+1260 470051888
+1259 852387082
+1258 832222809
+1257 1388941637
+1256 1270315354
+1255 404597016
+1254 445765124
+1253 2004126289
+1252 645347918
+1251 226902646
+1250 2034680340
+1249 1704324739
+1248 1217816029
+1247 1651286128
+1246 229499866
+1245 292984988
+1244 1723774526
+1243 889008184
+1242 2107655950
+1241 1210340897
+1240 1738532067
+1239 624481097
+1238 640846511
+1237 639137346
+1236 1224401086
+1235 507796405
+1234 1310889558
+1233 1456811578
+1232 73526006
+1231 1325405869
+1230 327523063
+1229 1228593899
+1228 1729555376
+1227 427625793
+1226 943176512
+1225 1413672713
+1224 1906732746
+1223 218862478
+1222 2116929597
+1221 1088506826
+1220 1156570265
+1219 383394211
+1218 1474824873
+1217 924831041
+1216 2074995250
+1215 1487975329
+1214 332812686
+1213 513433629
+1212 1297959765
+1211 1483174853
+1210 569494385
+1209 1099394721
+1208 1547563659
+1207 133050106
+1206 1475731436
+1205 1915073155
+1204 434270398
+1203 2132967337
+1202 1129288515
+1201 992415755
+1200 1743334141
+1199 2047380918
+1198 285417387
+1197 315882663
+1196 668376695
+1195 724314034
+1194 1444226764
+1193 818225919
+1192 1209775860
+1191 1733535385
+1190 1761165601
+1189 231739224
+1188 455882609
+1187 2134333192
+1186 592018355
+1185 1561561621
+1184 190015564
+1183 997121481
+1182 2091422891
+1181 198565044
+1180 2083094842
+1179 436444279
+1178 1771146933
+1177 1779974151
+1176 1846263356
+1175 1490247747
+1174 785784640
+1173 1589338291
+1172 389633196
+1171 1229391245
+1170 706998368
+1169 1427451477
+1168 1379004223
+1167 1708587000
+1166 1019139547
+1165 1997634423
+1164 1662021822
+1163 1858175026
+1162 1204543966
+1161 978036636
+1160 1277652776
+1159 1774316057
+1158 1787204517
+1157 1041804635
+1156 1944317627
+1155 1742380522
+1154 1617622378
+1153 2138934168
+1152 1061510287
+1151 1654978612
+1150 574901759
+1149 303120690
+1148 737664571
+1147 280899186
+1146 994189511
+1145 256925064
+1144 1100614551
+1143 1703877042
+1142 882339923
+1141 1109665366
+1140 1997870670
+1139 1145895015
+1138 408311930
+1137 1528853447
+1136 46565178
+1135 1308448169
+1134 793090457
+1133 683985186
+1132 580522250
+1131 1577711557
+1130 1338315766
+1129 235848141
+1128 1977482077
+1127 44823995
+1126 1571665905
+1125 1952867107
+1124 680870235
+1123 2110127413
+1122 1564032409
+1121 758389596
+1120 917314041
+1119 294002573
+1118 1456414827
+1117 480739506
+1116 1327768283
+1115 1437796117
+1114 1522068789
+1113 2138432832
+1112 1853490020
+1111 1883928556
+1110 701353436
+1109 469017223
+1108 1099329837
+1107 1247347409
+1106 735762990
+1105 1510063640
+1104 727925919
+1103 1362862547
+1102 1493153067
+1101 344674109
+1100 1747713128
+1099 1293491771
+1098 811665883
+1097 24614970
+1096 1511437408
+1095 1609022140
+1094 388834697
+1093 2069964286
+1092 1192813372
+1091 1270029836
+1090 1449458417
+1089 436574535
+1088 1113717938
+1087 18618710
+1086 1106154365
+1085 1336819098
+1084 1731789745
+1083 1785623881
+1082 1437079396
+1081 1384472797
+1080 784598719
+1079 1601489675
+1078 1880737880
+1077 1736749844
+1076 519421490
+1075 1520384091
+1074 16910573
+1073 383251810
+1072 1762633067
+1071 199661296
+1070 1680491873
+1069 1723098157
+1068 1929538010
+1067 1350127391
+1066 1783263921
+1065 1588956770
+1064 416208768
+1063 1266288509
+1062 620505869
+1061 756238837
+1060 156311898
+1059 1430839707
+1058 1477903817
+1057 1924382487
+1056 434312613
+1055 1468014132
+1054 2047223350
+1053 347316948
+1052 1001025162
+1051 1983073368
+1050 1651218564
+1049 1195332522
+1048 1082068185
+1047 360353789
+1046 1719839271
+1045 136169680
+1044 1905234671
+1043 1964732924
+1042 850243584
+1041 39534910
+1040 417606934
+1039 330364482
+1038 2087317884
+1037 340581240
+1036 933918623
+1035 516975412
+1034 968450901
+1033 1807453579
+1032 1109976610
+1031 1337149809
+1030 1425818667
+1029 379413059
+1028 996527094
+1027 9889685
+1026 2024642785
+1025 86995665
+1024 466988970
+1023 64149982
+1022 843582031
+1021 1953176287
+1020 901005183
+1019 1290864775
+1018 1622976899
+1017 945898505
+1016 602602766
+1015 1902589995
+1014 1433409743
+1013 1865699761
+1012 1547125990
+1011 519879102
+1010 99700673
+1009 77025693
+1008 1543929506
+1007 1570342472
+1006 1519613987
+1005 1273948692
+1004 1554482449
+1003 1778784739
+1002 381634911
+1001 730563551
+1000 340622715
+999 1415928982
+998 502253922
+997 909531429
+996 1690384362
+995 1960492803
+994 1390897281
+993 661296331
+992 1310628447
+991 1700200904
+990 330199388
+989 2102590325
+988 688262009
+987 1867870552
+986 1659972410
+985 884386652
+984 355464004
+983 913530641
+982 1765999088
+981 1470100297
+980 1123433244
+979 676841849
+978 704895354
+977 269980814
+976 15860023
+975 1888312896
+974 892313781
+973 823918898
+972 1438162024
+971 1113189577
+970 228309629
+969 1578574933
+968 1873028268
+967 689244767
+966 1666117796
+965 1029088031
+964 649864356
+963 1838180025
+962 331096942
+961 1355521769
+960 1011938895
+959 609812484
+958 442617915
+957 1951359004
+956 1512406547
+955 746441769
+954 1265871212
+953 1032847355
+952 1937581045
+951 1089157239
+950 765204943
+949 853452430
+948 660981826
+947 964066106
+946 1525150681
+945 1339424773
+944 450150871
+943 1926607852
+942 595609268
+941 2007070739
+940 1387644957
+939 1686548510
+938 2059940785
+937 843940236
+936 39380411
+935 1975421419
+934 697991089
+933 1441826234
+932 826241129
+931 1868768106
+930 912903854
+929 1208063539
+928 1244889585
+927 1843659794
+926 685487792
+925 479559192
+924 956344372
+923 176713973
+922 267642412
+921 1084128615
+920 428175413
+919 1948622485
+918 1475785397
+917 1469040701
+916 513915234
+915 1746026477
+914 743815504
+913 590563780
+912 538962895
+911 1056544406
+910 2094613601
+909 543704720
+908 1647168099
+907 84519366
+906 145949147
+905 745037824
+904 1149180289
+903 976706631
+902 528922380
+901 1765661238
+900 623878521
+899 1216727707
+898 522575747
+897 765330393
+896 887315422
+895 508773818
+894 211916779
+893 2019699405
+892 1896022208
+891 466503575
+890 1755826866
+889 1106618360
+888 1434707250
+887 1877242568
+886 725225196
+885 2070835102
+884 1207063582
+883 1834754746
+882 643433827
+881 2142741822
+880 1556859954
+879 2010094235
+878 397755573
+877 902130275
+876 1082822725
+875 1316726164
+874 216115444
+873 1531002699
+872 352828110
+871 1459678321
+870 1243085491
+869 2006031776
+868 329412285
+867 13801928
+866 553413613
+865 1015099665
+864 760235258
+863 1892896852
+862 263872539
+861 789403848
+860 1179279973
+859 2026067946
+858 381393163
+857 1511355796
+856 670178986
+855 1037954098
+854 1427401275
+853 1211805407
+852 277894792
+851 780823240
+850 1744986249
+849 654729679
+848 927271510
+847 1228513056
+846 686014831
+845 1699303674
+844 963898054
+843 903920771
+842 287917207
+841 494279982
+840 1130266036
+839 1229283563
+838 1452618162
+837 1461796267
+836 1401050318
+835 808000409
+834 751227126
+833 2118315057
+832 713616879
+831 385288241
+830 408010685
+829 1815407824
+828 1355888960
+827 1490922713
+826 83954521
+825 1605857226
+824 760059306
+823 646578035
+822 1614302806
+821 1770648760
+820 2001035378
+819 516473193
+818 2116198496
+817 1375451484
+816 264615002
+815 1929577708
+814 1411386466
+813 469618072
+812 1921138383
+811 1206117292
+810 1189145467
+809 1815953416
+808 1975716892
+807 644617753
+806 710569141
+805 1430218909
+804 94383530
+803 365938885
+802 1710304372
+801 1045692702
+800 1176882929
+799 1064571619
+798 1731453303
+797 1897515381
+796 730863407
+795 1584860134
+794 2139038068
+793 1136894193
+792 793026305
+791 1097829613
+790 1801933912
+789 625583894
+788 251858191
+787 186620788
+786 2111548665
+785 1942480577
+784 8439325
+783 205269174
+782 1427956253
+781 105184966
+780 1377884048
+779 544527714
+778 1105384275
+777 545497983
+776 550234222
+775 344630255
+774 1867398184
+773 1196174476
+772 1336539604
+771 645732753
+770 1461723047
+769 1426851195
+768 333708212
+767 146593168
+766 1905960961
+765 1741452862
+764 791833829
+763 1041208455
+762 1482443929
+761 167442411
+760 845971422
+759 1615313123
+758 661518876
+757 456861261
+756 178181463
+755 1906279491
+754 514524324
+753 2050738006
+752 974868774
+751 883428538
+750 1147284339
+749 832386064
+748 2141777140
+747 760754020
+746 825583447
+745 1501543394
+744 1155574299
+743 1221665431
+742 1881935076
+741 2057172057
+740 312024541
+739 1315129879
+738 1668373882
+737 739738998
+736 1502242987
+735 864752505
+734 259008932
+733 624391418
+732 195237033
+731 2014614454
+730 1653407182
+729 389110160
+728 1437131660
+727 902723033
+726 2089820585
+725 274927105
+724 931410717
+723 1778579434
+722 903453667
+721 142482709
+720 889135046
+719 386530319
+718 6802617
+717 640233745
+716 1752663369
+715 1751401663
+714 1767091966
+713 1245885890
+712 909640890
+711 566805197
+710 388798174
+709 1719769191
+708 1960370540
+707 803621377
+706 480730065
+705 877851568
+704 669515472
+703 391878126
+702 1118467884
+701 1953610521
+700 577482794
+699 750684149
+698 446773223
+697 1162204555
+696 2118795963
+695 311241151
+694 1518957085
+693 788928008
+692 889444387
+691 516923348
+690 135680092
+689 248901301
+688 781350598
+687 402884602
+686 1020625427
+685 506777479
+684 841760773
+683 1200286768
+682 857087715
+681 1337355347
+680 753918305
+679 1732660445
+678 1239039125
+677 1082518971
+676 134105905
+675 88851939
+674 1906867332
+673 863388599
+672 1961878980
+671 367783734
+670 1506837297
+669 1562761887
+668 779371834
+667 135532072
+666 1790731117
+665 1329867955
+664 1569280411
+663 1002033737
+662 653247916
+661 640543086
+660 1883056398
+659 1880279138
+658 1375759521
+657 274573119
+656 1708607477
+655 1967822307
+654 1797173411
+653 1651889073
+652 446368463
+651 1271910918
+650 98316222
+649 1818882982
+648 1598554540
+647 1150187186
+646 1323135287
+645 1418200954
+644 274456606
+643 1539083598
+642 1504034949
+641 399117093
+640 1735895548
+639 1371305225
+638 1919514417
+637 1596987526
+636 713735309
+635 788697380
+634 676620039
+633 928737325
+632 1266460986
+631 920452426
+630 1412267213
+629 1608483279
+628 171671661
+627 1555420862
+626 624883355
+625 56718403
+624 1521453844
+623 525262493
+622 1553572851
+621 774969129
+620 1820840025
+619 1095612683
+618 495747695
+617 180353586
+616 875730580
+615 1931535337
+614 2061649652
+613 2022823161
+612 1950671697
+611 132729724
+610 627086324
+609 138908022
+608 657569916
+607 1130817037
+606 920367487
+605 1932481632
+604 1669720042
+603 1903651261
+602 1663953760
+601 1805461355
+600 748780765
+599 2004329998
+598 983599924
+597 114953257
+596 33967018
+595 99620862
+594 650629200
+593 746484715
+592 851906116
+591 457960168
+590 279221434
+589 1640486439
+588 219882103
+587 711696006
+586 266187582
+585 1000391067
+584 2128347287
+583 1928919928
+582 1395736837
+581 1811763675
+580 1622643456
+579 1643752935
+578 1366024183
+577 872571932
+576 1608580643
+575 1164199873
+574 268527872
+573 2011742335
+572 1154870496
+571 1807107409
+570 821861431
+569 633827507
+568 1970362980
+567 883979062
+566 1611807705
+565 1434965951
+564 1395198394
+563 192669032
+562 467263281
+561 1358903325
+560 238078064
+559 1715009076
+558 1374298857
+557 1366974684
+556 730832366
+555 484751302
+554 1752137878
+553 316583612
+552 306276471
+551 1899467550
+550 445739492
+549 750071524
+548 35172292
+547 201824309
+546 604044060
+545 1744321956
+544 9329377
+543 608904110
+542 1189880904
+541 521042989
+540 1984228077
+539 2085366017
+538 1169503450
+537 535397028
+536 1636264316
+535 1419138673
+534 967702670
+533 36295069
+532 2102074615
+531 899737853
+530 2132088116
+529 1018587028
+528 984176709
+527 889547555
+526 1762320454
+525 414248754
+524 178474830
+523 2000153976
+522 2018327767
+521 1703688595
+520 1864295258
+519 243915183
+518 146027464
+517 438333984
+516 192494932
+515 2142623597
+514 554441052
+513 1635770036
+512 772159681
+511 1251998535
+510 1499023187
+509 1448831049
+508 449101701
+507 1897848424
+506 1715178006
+505 1599969247
+504 1464547706
+503 67964817
+502 51690601
+501 1083487587
+500 2063044791
+499 1242540561
+498 1403750221
+497 569927955
+496 711072724
+495 1909650126
+494 543404635
+493 622269883
+492 135858718
+491 1774412584
+490 1557661130
+489 1425961274
+488 51420250
+487 150887515
+486 2031376580
+485 704208544
+484 1370463916
+483 1449926165
+482 136746849
+481 1470812280
+480 802896834
+479 1748658410
+478 1881136691
+477 996616102
+476 433300718
+475 1647213188
+474 1548278646
+473 381060118
+472 152403674
+471 956633688
+470 1827221014
+469 1493116836
+468 531467836
+467 1641583743
+466 26523320
+465 88802841
+464 1773791408
+463 916475698
+462 1212092401
+461 857381092
+460 1722992334
+459 1406773615
+458 1542068342
+457 1494695354
+456 927907246
+455 581450415
+454 567461695
+453 2047135284
+452 647029331
+451 535572086
+450 1737159237
+449 1953764380
+448 1315357692
+447 233923502
+446 1595821104
+445 52240600
+444 1494809514
+443 591644958
+442 701322752
+441 806770485
+440 425165851
+439 185637271
+438 1466593516
+437 442664995
+436 2015275982
+435 1257531269
+434 1024194088
+433 916410316
+432 1340967012
+431 1952802433
+430 1462796398
+429 228296980
+428 478866369
+427 960617927
+426 927233658
+425 1028255610
+424 2081904732
+423 31889609
+422 309976047
+421 840748599
+420 1367698042
+419 1503235734
+418 357943276
+417 1263117092
+416 886597636
+415 1004176146
+414 1498401495
+413 688039029
+412 166479106
+411 515685481
+410 1487660617
+409 2129984504
+408 317844936
+407 209062247
+406 1565954555
+405 1098865666
+404 2064047905
+403 1218875302
+402 1601097566
+401 1112670032
+400 1473936064
+399 502178471
+398 1448546969
+397 1598094407
+396 1026196843
+395 895344049
+394 718279562
+393 1241156133
+392 811675214
+391 954223961
+390 482805323
+389 104580950
+388 616638098
+387 1501250778
+386 1912199244
+385 198558607
+384 837697039
+383 982716014
+382 1347862060
+381 183978250
+380 197840545
+379 1278598370
+378 564029948
+377 1366462918
+376 292497990
+375 347079253
+374 1645251747
+373 951377872
+372 1892422886
+371 1098919095
+370 1811606711
+369 2023325305
+368 1623465276
+367 553202920
+366 879814844
+365 1932524358
+364 83668835
+363 1911539249
+362 758350810
+361 707094264
+360 337585862
+359 1129038193
+358 339865354
+357 418079491
+356 663553738
+355 929483230
+354 998180195
+353 653718789
+352 784875469
+351 69263690
+350 1767431950
+349 978861274
+348 986100380
+347 216950695
+346 1868694819
+345 1488603765
+344 602140015
+343 546332652
+342 1287254809
+341 2016581228
+340 1622937467
+339 1258403791
+338 1143510461
+337 1838424566
+336 469534085
+335 1115759243
+334 1174173548
+333 1524058219
+332 1573953387
+331 1776796265
+330 367228910
+329 2066990019
+328 465484454
+327 1557865771
+326 1567382944
+325 9834949
+324 144607761
+323 928916505
+322 1033770487
+321 1953497842
+320 1230646958
+319 1550481254
+318 1257650103
+317 1644980262
+316 1762294328
+315 1322362167
+314 201348956
+313 733042434
+312 1070878832
+311 28851018
+310 873070767
+309 1931996549
+308 788869706
+307 27751218
+306 664251018
+305 1092959514
+304 1689289504
+303 1544860931
+302 1156829309
+301 1654447016
+300 1311311810
+299 956846786
+298 499607074
+297 455649505
+296 1413258010
+295 638466439
+294 1123548110
+293 338593567
+292 1845753195
+291 1630772880
+290 695847739
+289 1733150343
+288 1935670574
+287 2082771584
+286 1443631306
+285 1029251894
+284 251483334
+283 172497937
+282 2007455315
+281 1286365931
+280 1387464960
+279 845319549
+278 1267745531
+277 1843393840
+276 485945362
+275 1266873735
+274 2083613853
+273 34842488
+272 233549120
+271 199982522
+270 1154839941
+269 855662305
+268 1691072424
+267 2008624283
+266 1479585042
+265 1074664443
+264 940196892
+263 1640258878
+262 1790229476
+261 112602851
+260 1842585954
+259 760559802
+258 289519037
+257 906418680
+256 1831288250
+255 1271133369
+254 1169280227
+253 1112601051
+252 932516625
+251 1162135765
+250 18620399
+249 1691554768
+248 359374187
+247 871796
+246 1907263635
+245 451102874
+244 1033324614
+243 1883631330
+242 1027486194
+241 1525370463
+240 656393746
+239 1293699306
+238 1523560911
+237 616407981
+236 1068427390
+235 1986809812
+234 1431918615
+233 827594041
+232 1945156571
+231 1029669673
+230 1970567462
+229 936167274
+228 1076755200
+227 1165869316
+226 1884622101
+225 718687198
+224 338616744
+223 7144461
+222 1093980652
+221 1388445504
+220 802761578
+219 17748603
+218 1931774781
+217 2055754961
+216 1115030830
+215 23632304
+214 1571100327
+213 1655437799
+212 1227237584
+211 1881270536
+210 1809552
+209 39985764
+208 225271916
+207 1684234746
+206 1331973014
+205 240833349
+204 41653241
+203 402248941
+202 1004510226
+201 1008989297
+200 2100398121
+199 804698146
+198 1199028821
+197 358068002
+196 827252572
+195 1877477639
+194 1772190194
+193 1097654887
+192 1351866531
+191 1076232049
+190 1604154371
+189 894490264
+188 1050201421
+187 1908142477
+186 484654634
+185 1607076678
+184 943878368
+183 1837313439
+182 1653628247
+181 1187251820
+180 1655998620
+179 465058453
+178 855496398
+177 2131922214
+176 1642581505
+175 929724073
+174 1383806771
+173 1180147592
+172 449334468
+171 199812080
+170 1957444123
+169 1742330119
+168 2124929222
+167 1469034830
+166 733361455
+165 1877081332
+164 525611108
+163 695958145
+162 1640984164
+161 457376267
+160 26030628
+159 1843495542
+158 409835630
+157 1590608390
+156 964264109
+155 794824842
+154 2100932079
+153 1904110196
+152 181314819
+151 1188569794
+150 331755422
+149 1671560053
+148 969960596
+147 2073255973
+146 748115443
+145 462433913
+144 480389604
+143 1183994691
+142 1370187116
+141 854487997
+140 222366505
+139 488409293
+138 1008968663
+137 247847890
+136 943423722
+135 37403310
+134 236097168
+133 68234841
+132 669927517
+131 1944972270
+130 47540636
+129 582905885
+128 879231433
+127 1762494436
+126 1637159959
+125 1207637561
+124 613510023
+123 912362285
+122 1572354774
+121 657238414
+120 218609198
+119 405983097
+118 923444610
+117 507526682
+116 1592866368
+115 1711604400
+114 1239730445
+113 1773385255
+112 961628185
+111 881777823
+110 1993002982
+109 2122002263
+108 1692469219
+107 971565353
+106 11750722
+105 875188881
+104 1514959440
+103 438608545
+102 20694204
+101 87021632
+100 1065740837
+99 432529848
+98 1093229574
+97 1819077520
+96 1148984413
+95 724797674
+94 1782766435
+93 2103755257
+92 693753087
+91 1166371677
+90 1881277452
+89 1858566163
+88 960600376
+87 1359323857
+86 1415279885
+85 1966964761
+84 749976215
+83 357952622
+82 1927865921
+81 987109570
+80 1336792251
+79 1021437628
+78 2110251541
+77 817280338
+76 1604089561
+75 1720625824
+74 854494676
+73 1427937808
+72 1520351356
+71 1735648004
+70 1141275706
+69 1394146965
+68 1431029083
+67 368431899
+66 36311085
+65 1192712804
+64 31044587
+63 616394758
+62 222477805
+61 982670571
+60 205771300
+59 521953594
+58 443286278
+57 1141119263
+56 609347642
+55 1057327263
+54 39098840
+53 1910350293
+52 1168644018
+51 906428292
+50 1024341676
+49 519511913
+48 1564831715
+47 389625717
+46 2110269310
+45 176151752
+44 200274468
+43 1266330320
+42 286662102
+41 126204390
+40 304618920
+39 772843806
+38 1357835880
+37 238316279
+36 337387312
+35 1567399975
+34 970234999
+33 1195857664
+32 410623457
+31 1848007858
+30 539384293
+29 1212135685
+28 2060089600
+27 1533442662
+26 1102020422
+25 846480997
+24 2036166893
+23 1280154196
+22 886008616
+21 649132105
+20 1489080225
+19 634715959
+18 556726251
+17 1388679963
+16 189351248
+15 843938989
+14 2036973298
+13 74070078
+12 961711400
+11 1661301944
+10 915852158
+9 66302641
+8 435456494
+7 1937919553
+6 1415564928
+5 1289013296
+4 1156776517
+3 1269710788
+2 656473370
+1 1345971420
+0 1935401906
diff --git a/src/test/regress/data/emp.data b/src/test/regress/data/emp.data
new file mode 100644
index 0000000..5fc17ff
--- /dev/null
+++ b/src/test/regress/data/emp.data
@@ -0,0 +1,3 @@
+sharon 25 (15,12) 1000 sam
+sam 30 (10,5) 2000 bill
+bill 20 (11,10) 1000 sharon
diff --git a/src/test/regress/data/hash.data b/src/test/regress/data/hash.data
new file mode 100644
index 0000000..97e9709
--- /dev/null
+++ b/src/test/regress/data/hash.data
@@ -0,0 +1,10000 @@
+0 1935401906
+1 1345971420
+2 656473370
+3 1269710788
+4 1156776517
+5 1289013296
+6 1415564928
+7 1937919553
+8 435456494
+9 66302641
+10 915852158
+11 1661301944
+12 961711400
+13 74070078
+14 2036973298
+15 843938989
+16 189351248
+17 1388679963
+18 556726251
+19 634715959
+20 1489080225
+21 649132105
+22 886008616
+23 1280154196
+24 2036166893
+25 846480997
+26 1102020422
+27 1533442662
+28 2060089600
+29 1212135685
+30 539384293
+31 1848007858
+32 410623457
+33 1195857664
+34 970234999
+35 1567399975
+36 337387312
+37 238316279
+38 1357835880
+39 772843806
+40 304618920
+41 126204390
+42 286662102
+43 1266330320
+44 200274468
+45 176151752
+46 2110269310
+47 389625717
+48 1564831715
+49 519511913
+50 1024341676
+51 906428292
+52 1168644018
+53 1910350293
+54 39098840
+55 1057327263
+56 609347642
+57 1141119263
+58 443286278
+59 521953594
+60 205771300
+61 982670571
+62 222477805
+63 616394758
+64 31044587
+65 1192712804
+66 36311085
+67 368431899
+68 1431029083
+69 1394146965
+70 1141275706
+71 1735648004
+72 1520351356
+73 1427937808
+74 854494676
+75 1720625824
+76 1604089561
+77 817280338
+78 2110251541
+79 1021437628
+80 1336792251
+81 987109570
+82 1927865921
+83 357952622
+84 749976215
+85 1966964761
+86 1415279885
+87 1359323857
+88 960600376
+89 1858566163
+90 1881277452
+91 1166371677
+92 693753087
+93 2103755257
+94 1782766435
+95 724797674
+96 1148984413
+97 1819077520
+98 1093229574
+99 432529848
+100 1065740837
+101 87021632
+102 20694204
+103 438608545
+104 1514959440
+105 875188881
+106 11750722
+107 971565353
+108 1692469219
+109 2122002263
+110 1993002982
+111 881777823
+112 961628185
+113 1773385255
+114 1239730445
+115 1711604400
+116 1592866368
+117 507526682
+118 923444610
+119 405983097
+120 218609198
+121 657238414
+122 1572354774
+123 912362285
+124 613510023
+125 1207637561
+126 1637159959
+127 1762494436
+128 879231433
+129 582905885
+130 47540636
+131 1944972270
+132 669927517
+133 68234841
+134 236097168
+135 37403310
+136 943423722
+137 247847890
+138 1008968663
+139 488409293
+140 222366505
+141 854487997
+142 1370187116
+143 1183994691
+144 480389604
+145 462433913
+146 748115443
+147 2073255973
+148 969960596
+149 1671560053
+150 331755422
+151 1188569794
+152 181314819
+153 1904110196
+154 2100932079
+155 794824842
+156 964264109
+157 1590608390
+158 409835630
+159 1843495542
+160 26030628
+161 457376267
+162 1640984164
+163 695958145
+164 525611108
+165 1877081332
+166 733361455
+167 1469034830
+168 2124929222
+169 1742330119
+170 1957444123
+171 199812080
+172 449334468
+173 1180147592
+174 1383806771
+175 929724073
+176 1642581505
+177 2131922214
+178 855496398
+179 465058453
+180 1655998620
+181 1187251820
+182 1653628247
+183 1837313439
+184 943878368
+185 1607076678
+186 484654634
+187 1908142477
+188 1050201421
+189 894490264
+190 1604154371
+191 1076232049
+192 1351866531
+193 1097654887
+194 1772190194
+195 1877477639
+196 827252572
+197 358068002
+198 1199028821
+199 804698146
+200 2100398121
+201 1008989297
+202 1004510226
+203 402248941
+204 41653241
+205 240833349
+206 1331973014
+207 1684234746
+208 225271916
+209 39985764
+210 1809552
+211 1881270536
+212 1227237584
+213 1655437799
+214 1571100327
+215 23632304
+216 1115030830
+217 2055754961
+218 1931774781
+219 17748603
+220 802761578
+221 1388445504
+222 1093980652
+223 7144461
+224 338616744
+225 718687198
+226 1884622101
+227 1165869316
+228 1076755200
+229 936167274
+230 1970567462
+231 1029669673
+232 1945156571
+233 827594041
+234 1431918615
+235 1986809812
+236 1068427390
+237 616407981
+238 1523560911
+239 1293699306
+240 656393746
+241 1525370463
+242 1027486194
+243 1883631330
+244 1033324614
+245 451102874
+246 1907263635
+247 871796
+248 359374187
+249 1691554768
+250 18620399
+251 1162135765
+252 932516625
+253 1112601051
+254 1169280227
+255 1271133369
+256 1831288250
+257 906418680
+258 289519037
+259 760559802
+260 1842585954
+261 112602851
+262 1790229476
+263 1640258878
+264 940196892
+265 1074664443
+266 1479585042
+267 2008624283
+268 1691072424
+269 855662305
+270 1154839941
+271 199982522
+272 233549120
+273 34842488
+274 2083613853
+275 1266873735
+276 485945362
+277 1843393840
+278 1267745531
+279 845319549
+280 1387464960
+281 1286365931
+282 2007455315
+283 172497937
+284 251483334
+285 1029251894
+286 1443631306
+287 2082771584
+288 1935670574
+289 1733150343
+290 695847739
+291 1630772880
+292 1845753195
+293 338593567
+294 1123548110
+295 638466439
+296 1413258010
+297 455649505
+298 499607074
+299 956846786
+300 1311311810
+301 1654447016
+302 1156829309
+303 1544860931
+304 1689289504
+305 1092959514
+306 664251018
+307 27751218
+308 788869706
+309 1931996549
+310 873070767
+311 28851018
+312 1070878832
+313 733042434
+314 201348956
+315 1322362167
+316 1762294328
+317 1644980262
+318 1257650103
+319 1550481254
+320 1230646958
+321 1953497842
+322 1033770487
+323 928916505
+324 144607761
+325 9834949
+326 1567382944
+327 1557865771
+328 465484454
+329 2066990019
+330 367228910
+331 1776796265
+332 1573953387
+333 1524058219
+334 1174173548
+335 1115759243
+336 469534085
+337 1838424566
+338 1143510461
+339 1258403791
+340 1622937467
+341 2016581228
+342 1287254809
+343 546332652
+344 602140015
+345 1488603765
+346 1868694819
+347 216950695
+348 986100380
+349 978861274
+350 1767431950
+351 69263690
+352 784875469
+353 653718789
+354 998180195
+355 929483230
+356 663553738
+357 418079491
+358 339865354
+359 1129038193
+360 337585862
+361 707094264
+362 758350810
+363 1911539249
+364 83668835
+365 1932524358
+366 879814844
+367 553202920
+368 1623465276
+369 2023325305
+370 1811606711
+371 1098919095
+372 1892422886
+373 951377872
+374 1645251747
+375 347079253
+376 292497990
+377 1366462918
+378 564029948
+379 1278598370
+380 197840545
+381 183978250
+382 1347862060
+383 982716014
+384 837697039
+385 198558607
+386 1912199244
+387 1501250778
+388 616638098
+389 104580950
+390 482805323
+391 954223961
+392 811675214
+393 1241156133
+394 718279562
+395 895344049
+396 1026196843
+397 1598094407
+398 1448546969
+399 502178471
+400 1473936064
+401 1112670032
+402 1601097566
+403 1218875302
+404 2064047905
+405 1098865666
+406 1565954555
+407 209062247
+408 317844936
+409 2129984504
+410 1487660617
+411 515685481
+412 166479106
+413 688039029
+414 1498401495
+415 1004176146
+416 886597636
+417 1263117092
+418 357943276
+419 1503235734
+420 1367698042
+421 840748599
+422 309976047
+423 31889609
+424 2081904732
+425 1028255610
+426 927233658
+427 960617927
+428 478866369
+429 228296980
+430 1462796398
+431 1952802433
+432 1340967012
+433 916410316
+434 1024194088
+435 1257531269
+436 2015275982
+437 442664995
+438 1466593516
+439 185637271
+440 425165851
+441 806770485
+442 701322752
+443 591644958
+444 1494809514
+445 52240600
+446 1595821104
+447 233923502
+448 1315357692
+449 1953764380
+450 1737159237
+451 535572086
+452 647029331
+453 2047135284
+454 567461695
+455 581450415
+456 927907246
+457 1494695354
+458 1542068342
+459 1406773615
+460 1722992334
+461 857381092
+462 1212092401
+463 916475698
+464 1773791408
+465 88802841
+466 26523320
+467 1641583743
+468 531467836
+469 1493116836
+470 1827221014
+471 956633688
+472 152403674
+473 381060118
+474 1548278646
+475 1647213188
+476 433300718
+477 996616102
+478 1881136691
+479 1748658410
+480 802896834
+481 1470812280
+482 136746849
+483 1449926165
+484 1370463916
+485 704208544
+486 2031376580
+487 150887515
+488 51420250
+489 1425961274
+490 1557661130
+491 1774412584
+492 135858718
+493 622269883
+494 543404635
+495 1909650126
+496 711072724
+497 569927955
+498 1403750221
+499 1242540561
+500 2063044791
+501 1083487587
+502 51690601
+503 67964817
+504 1464547706
+505 1599969247
+506 1715178006
+507 1897848424
+508 449101701
+509 1448831049
+510 1499023187
+511 1251998535
+512 772159681
+513 1635770036
+514 554441052
+515 2142623597
+516 192494932
+517 438333984
+518 146027464
+519 243915183
+520 1864295258
+521 1703688595
+522 2018327767
+523 2000153976
+524 178474830
+525 414248754
+526 1762320454
+527 889547555
+528 984176709
+529 1018587028
+530 2132088116
+531 899737853
+532 2102074615
+533 36295069
+534 967702670
+535 1419138673
+536 1636264316
+537 535397028
+538 1169503450
+539 2085366017
+540 1984228077
+541 521042989
+542 1189880904
+543 608904110
+544 9329377
+545 1744321956
+546 604044060
+547 201824309
+548 35172292
+549 750071524
+550 445739492
+551 1899467550
+552 306276471
+553 316583612
+554 1752137878
+555 484751302
+556 730832366
+557 1366974684
+558 1374298857
+559 1715009076
+560 238078064
+561 1358903325
+562 467263281
+563 192669032
+564 1395198394
+565 1434965951
+566 1611807705
+567 883979062
+568 1970362980
+569 633827507
+570 821861431
+571 1807107409
+572 1154870496
+573 2011742335
+574 268527872
+575 1164199873
+576 1608580643
+577 872571932
+578 1366024183
+579 1643752935
+580 1622643456
+581 1811763675
+582 1395736837
+583 1928919928
+584 2128347287
+585 1000391067
+586 266187582
+587 711696006
+588 219882103
+589 1640486439
+590 279221434
+591 457960168
+592 851906116
+593 746484715
+594 650629200
+595 99620862
+596 33967018
+597 114953257
+598 983599924
+599 2004329998
+600 748780765
+601 1805461355
+602 1663953760
+603 1903651261
+604 1669720042
+605 1932481632
+606 920367487
+607 1130817037
+608 657569916
+609 138908022
+610 627086324
+611 132729724
+612 1950671697
+613 2022823161
+614 2061649652
+615 1931535337
+616 875730580
+617 180353586
+618 495747695
+619 1095612683
+620 1820840025
+621 774969129
+622 1553572851
+623 525262493
+624 1521453844
+625 56718403
+626 624883355
+627 1555420862
+628 171671661
+629 1608483279
+630 1412267213
+631 920452426
+632 1266460986
+633 928737325
+634 676620039
+635 788697380
+636 713735309
+637 1596987526
+638 1919514417
+639 1371305225
+640 1735895548
+641 399117093
+642 1504034949
+643 1539083598
+644 274456606
+645 1418200954
+646 1323135287
+647 1150187186
+648 1598554540
+649 1818882982
+650 98316222
+651 1271910918
+652 446368463
+653 1651889073
+654 1797173411
+655 1967822307
+656 1708607477
+657 274573119
+658 1375759521
+659 1880279138
+660 1883056398
+661 640543086
+662 653247916
+663 1002033737
+664 1569280411
+665 1329867955
+666 1790731117
+667 135532072
+668 779371834
+669 1562761887
+670 1506837297
+671 367783734
+672 1961878980
+673 863388599
+674 1906867332
+675 88851939
+676 134105905
+677 1082518971
+678 1239039125
+679 1732660445
+680 753918305
+681 1337355347
+682 857087715
+683 1200286768
+684 841760773
+685 506777479
+686 1020625427
+687 402884602
+688 781350598
+689 248901301
+690 135680092
+691 516923348
+692 889444387
+693 788928008
+694 1518957085
+695 311241151
+696 2118795963
+697 1162204555
+698 446773223
+699 750684149
+700 577482794
+701 1953610521
+702 1118467884
+703 391878126
+704 669515472
+705 877851568
+706 480730065
+707 803621377
+708 1960370540
+709 1719769191
+710 388798174
+711 566805197
+712 909640890
+713 1245885890
+714 1767091966
+715 1751401663
+716 1752663369
+717 640233745
+718 6802617
+719 386530319
+720 889135046
+721 142482709
+722 903453667
+723 1778579434
+724 931410717
+725 274927105
+726 2089820585
+727 902723033
+728 1437131660
+729 389110160
+730 1653407182
+731 2014614454
+732 195237033
+733 624391418
+734 259008932
+735 864752505
+736 1502242987
+737 739738998
+738 1668373882
+739 1315129879
+740 312024541
+741 2057172057
+742 1881935076
+743 1221665431
+744 1155574299
+745 1501543394
+746 825583447
+747 760754020
+748 2141777140
+749 832386064
+750 1147284339
+751 883428538
+752 974868774
+753 2050738006
+754 514524324
+755 1906279491
+756 178181463
+757 456861261
+758 661518876
+759 1615313123
+760 845971422
+761 167442411
+762 1482443929
+763 1041208455
+764 791833829
+765 1741452862
+766 1905960961
+767 146593168
+768 333708212
+769 1426851195
+770 1461723047
+771 645732753
+772 1336539604
+773 1196174476
+774 1867398184
+775 344630255
+776 550234222
+777 545497983
+778 1105384275
+779 544527714
+780 1377884048
+781 105184966
+782 1427956253
+783 205269174
+784 8439325
+785 1942480577
+786 2111548665
+787 186620788
+788 251858191
+789 625583894
+790 1801933912
+791 1097829613
+792 793026305
+793 1136894193
+794 2139038068
+795 1584860134
+796 730863407
+797 1897515381
+798 1731453303
+799 1064571619
+800 1176882929
+801 1045692702
+802 1710304372
+803 365938885
+804 94383530
+805 1430218909
+806 710569141
+807 644617753
+808 1975716892
+809 1815953416
+810 1189145467
+811 1206117292
+812 1921138383
+813 469618072
+814 1411386466
+815 1929577708
+816 264615002
+817 1375451484
+818 2116198496
+819 516473193
+820 2001035378
+821 1770648760
+822 1614302806
+823 646578035
+824 760059306
+825 1605857226
+826 83954521
+827 1490922713
+828 1355888960
+829 1815407824
+830 408010685
+831 385288241
+832 713616879
+833 2118315057
+834 751227126
+835 808000409
+836 1401050318
+837 1461796267
+838 1452618162
+839 1229283563
+840 1130266036
+841 494279982
+842 287917207
+843 903920771
+844 963898054
+845 1699303674
+846 686014831
+847 1228513056
+848 927271510
+849 654729679
+850 1744986249
+851 780823240
+852 277894792
+853 1211805407
+854 1427401275
+855 1037954098
+856 670178986
+857 1511355796
+858 381393163
+859 2026067946
+860 1179279973
+861 789403848
+862 263872539
+863 1892896852
+864 760235258
+865 1015099665
+866 553413613
+867 13801928
+868 329412285
+869 2006031776
+870 1243085491
+871 1459678321
+872 352828110
+873 1531002699
+874 216115444
+875 1316726164
+876 1082822725
+877 902130275
+878 397755573
+879 2010094235
+880 1556859954
+881 2142741822
+882 643433827
+883 1834754746
+884 1207063582
+885 2070835102
+886 725225196
+887 1877242568
+888 1434707250
+889 1106618360
+890 1755826866
+891 466503575
+892 1896022208
+893 2019699405
+894 211916779
+895 508773818
+896 887315422
+897 765330393
+898 522575747
+899 1216727707
+900 623878521
+901 1765661238
+902 528922380
+903 976706631
+904 1149180289
+905 745037824
+906 145949147
+907 84519366
+908 1647168099
+909 543704720
+910 2094613601
+911 1056544406
+912 538962895
+913 590563780
+914 743815504
+915 1746026477
+916 513915234
+917 1469040701
+918 1475785397
+919 1948622485
+920 428175413
+921 1084128615
+922 267642412
+923 176713973
+924 956344372
+925 479559192
+926 685487792
+927 1843659794
+928 1244889585
+929 1208063539
+930 912903854
+931 1868768106
+932 826241129
+933 1441826234
+934 697991089
+935 1975421419
+936 39380411
+937 843940236
+938 2059940785
+939 1686548510
+940 1387644957
+941 2007070739
+942 595609268
+943 1926607852
+944 450150871
+945 1339424773
+946 1525150681
+947 964066106
+948 660981826
+949 853452430
+950 765204943
+951 1089157239
+952 1937581045
+953 1032847355
+954 1265871212
+955 746441769
+956 1512406547
+957 1951359004
+958 442617915
+959 609812484
+960 1011938895
+961 1355521769
+962 331096942
+963 1838180025
+964 649864356
+965 1029088031
+966 1666117796
+967 689244767
+968 1873028268
+969 1578574933
+970 228309629
+971 1113189577
+972 1438162024
+973 823918898
+974 892313781
+975 1888312896
+976 15860023
+977 269980814
+978 704895354
+979 676841849
+980 1123433244
+981 1470100297
+982 1765999088
+983 913530641
+984 355464004
+985 884386652
+986 1659972410
+987 1867870552
+988 688262009
+989 2102590325
+990 330199388
+991 1700200904
+992 1310628447
+993 661296331
+994 1390897281
+995 1960492803
+996 1690384362
+997 909531429
+998 502253922
+999 1415928982
+1000 340622715
+1001 730563551
+1002 381634911
+1003 1778784739
+1004 1554482449
+1005 1273948692
+1006 1519613987
+1007 1570342472
+1008 1543929506
+1009 77025693
+1010 99700673
+1011 519879102
+1012 1547125990
+1013 1865699761
+1014 1433409743
+1015 1902589995
+1016 602602766
+1017 945898505
+1018 1622976899
+1019 1290864775
+1020 901005183
+1021 1953176287
+1022 843582031
+1023 64149982
+1024 466988970
+1025 86995665
+1026 2024642785
+1027 9889685
+1028 996527094
+1029 379413059
+1030 1425818667
+1031 1337149809
+1032 1109976610
+1033 1807453579
+1034 968450901
+1035 516975412
+1036 933918623
+1037 340581240
+1038 2087317884
+1039 330364482
+1040 417606934
+1041 39534910
+1042 850243584
+1043 1964732924
+1044 1905234671
+1045 136169680
+1046 1719839271
+1047 360353789
+1048 1082068185
+1049 1195332522
+1050 1651218564
+1051 1983073368
+1052 1001025162
+1053 347316948
+1054 2047223350
+1055 1468014132
+1056 434312613
+1057 1924382487
+1058 1477903817
+1059 1430839707
+1060 156311898
+1061 756238837
+1062 620505869
+1063 1266288509
+1064 416208768
+1065 1588956770
+1066 1783263921
+1067 1350127391
+1068 1929538010
+1069 1723098157
+1070 1680491873
+1071 199661296
+1072 1762633067
+1073 383251810
+1074 16910573
+1075 1520384091
+1076 519421490
+1077 1736749844
+1078 1880737880
+1079 1601489675
+1080 784598719
+1081 1384472797
+1082 1437079396
+1083 1785623881
+1084 1731789745
+1085 1336819098
+1086 1106154365
+1087 18618710
+1088 1113717938
+1089 436574535
+1090 1449458417
+1091 1270029836
+1092 1192813372
+1093 2069964286
+1094 388834697
+1095 1609022140
+1096 1511437408
+1097 24614970
+1098 811665883
+1099 1293491771
+1100 1747713128
+1101 344674109
+1102 1493153067
+1103 1362862547
+1104 727925919
+1105 1510063640
+1106 735762990
+1107 1247347409
+1108 1099329837
+1109 469017223
+1110 701353436
+1111 1883928556
+1112 1853490020
+1113 2138432832
+1114 1522068789
+1115 1437796117
+1116 1327768283
+1117 480739506
+1118 1456414827
+1119 294002573
+1120 917314041
+1121 758389596
+1122 1564032409
+1123 2110127413
+1124 680870235
+1125 1952867107
+1126 1571665905
+1127 44823995
+1128 1977482077
+1129 235848141
+1130 1338315766
+1131 1577711557
+1132 580522250
+1133 683985186
+1134 793090457
+1135 1308448169
+1136 46565178
+1137 1528853447
+1138 408311930
+1139 1145895015
+1140 1997870670
+1141 1109665366
+1142 882339923
+1143 1703877042
+1144 1100614551
+1145 256925064
+1146 994189511
+1147 280899186
+1148 737664571
+1149 303120690
+1150 574901759
+1151 1654978612
+1152 1061510287
+1153 2138934168
+1154 1617622378
+1155 1742380522
+1156 1944317627
+1157 1041804635
+1158 1787204517
+1159 1774316057
+1160 1277652776
+1161 978036636
+1162 1204543966
+1163 1858175026
+1164 1662021822
+1165 1997634423
+1166 1019139547
+1167 1708587000
+1168 1379004223
+1169 1427451477
+1170 706998368
+1171 1229391245
+1172 389633196
+1173 1589338291
+1174 785784640
+1175 1490247747
+1176 1846263356
+1177 1779974151
+1178 1771146933
+1179 436444279
+1180 2083094842
+1181 198565044
+1182 2091422891
+1183 997121481
+1184 190015564
+1185 1561561621
+1186 592018355
+1187 2134333192
+1188 455882609
+1189 231739224
+1190 1761165601
+1191 1733535385
+1192 1209775860
+1193 818225919
+1194 1444226764
+1195 724314034
+1196 668376695
+1197 315882663
+1198 285417387
+1199 2047380918
+1200 1743334141
+1201 992415755
+1202 1129288515
+1203 2132967337
+1204 434270398
+1205 1915073155
+1206 1475731436
+1207 133050106
+1208 1547563659
+1209 1099394721
+1210 569494385
+1211 1483174853
+1212 1297959765
+1213 513433629
+1214 332812686
+1215 1487975329
+1216 2074995250
+1217 924831041
+1218 1474824873
+1219 383394211
+1220 1156570265
+1221 1088506826
+1222 2116929597
+1223 218862478
+1224 1906732746
+1225 1413672713
+1226 943176512
+1227 427625793
+1228 1729555376
+1229 1228593899
+1230 327523063
+1231 1325405869
+1232 73526006
+1233 1456811578
+1234 1310889558
+1235 507796405
+1236 1224401086
+1237 639137346
+1238 640846511
+1239 624481097
+1240 1738532067
+1241 1210340897
+1242 2107655950
+1243 889008184
+1244 1723774526
+1245 292984988
+1246 229499866
+1247 1651286128
+1248 1217816029
+1249 1704324739
+1250 2034680340
+1251 226902646
+1252 645347918
+1253 2004126289
+1254 445765124
+1255 404597016
+1256 1270315354
+1257 1388941637
+1258 832222809
+1259 852387082
+1260 470051888
+1261 1159745872
+1262 30309304
+1263 543577895
+1264 469073802
+1265 1341198862
+1266 1051374300
+1267 1693474888
+1268 1980336209
+1269 1692220811
+1270 170472337
+1271 1571384628
+1272 755078060
+1273 130644639
+1274 312909165
+1275 331368938
+1276 423629627
+1277 542409031
+1278 1982655067
+1279 1641445656
+1280 99250122
+1281 1869851759
+1282 1868348303
+1283 744598040
+1284 1726494400
+1285 166629779
+1286 1149195056
+1287 849326106
+1288 1555571416
+1289 1981417865
+1290 1701713188
+1291 2025623305
+1292 993680089
+1293 1732022492
+1294 421717552
+1295 1462753892
+1296 925737707
+1297 1473091852
+1298 1008745132
+1299 758590268
+1300 1017829015
+1301 1179217470
+1302 182491248
+1303 1772907076
+1304 1309862109
+1305 495400413
+1306 2104276014
+1307 1733491737
+1308 1037809444
+1309 1939447433
+1310 1227453745
+1311 1137059567
+1312 1661815544
+1313 948318400
+1314 1881657607
+1315 1240826296
+1316 1114948180
+1317 883369016
+1318 2090152402
+1319 523035948
+1320 717303233
+1321 1644381943
+1322 401175605
+1323 1710983323
+1324 1228920787
+1325 822893157
+1326 1026253567
+1327 7174846
+1328 148501361
+1329 2034998699
+1330 765765114
+1331 1166330377
+1332 1066732521
+1333 948256363
+1334 791753805
+1335 229110983
+1336 1443656776
+1337 748546171
+1338 1962602720
+1339 333982573
+1340 540509957
+1341 1042572817
+1342 1471042140
+1343 54841853
+1344 1990891218
+1345 1205216099
+1346 1295668150
+1347 958355750
+1348 2088585115
+1349 1238336904
+1350 1481391698
+1351 658404701
+1352 735235199
+1353 1882567304
+1354 221904376
+1355 1964155987
+1356 557976813
+1357 1248157943
+1358 1971330833
+1359 706478175
+1360 1135672994
+1361 589612300
+1362 1872808552
+1363 54921868
+1364 1537868663
+1365 517078709
+1366 284032851
+1367 834041791
+1368 1265624880
+1369 99151923
+1370 1168024364
+1371 1806134837
+1372 1141724740
+1373 491582856
+1374 1860976691
+1375 985132310
+1376 1696798956
+1377 1009161193
+1378 1943488060
+1379 1637900423
+1380 100014449
+1381 1277396111
+1382 148821476
+1383 835249649
+1384 1012479767
+1385 370725852
+1386 651921988
+1387 1570456580
+1388 1618883795
+1389 475769173
+1390 129451107
+1391 607073142
+1392 1065381473
+1393 2002259659
+1394 661995010
+1395 455766488
+1396 371854720
+1397 946027861
+1398 1289808280
+1399 1637479601
+1400 1045179784
+1401 310348996
+1402 1296130790
+1403 39420876
+1404 801931853
+1405 1009623833
+1406 1024553187
+1407 351247161
+1408 2018785026
+1409 820557599
+1410 1989147584
+1411 2118799476
+1412 2097953710
+1413 2137969061
+1414 806565477
+1415 962949829
+1416 361211265
+1417 1458487465
+1418 385922762
+1419 1980095061
+1420 1934256638
+1421 515373869
+1422 439684555
+1423 852154464
+1424 370149881
+1425 1101679565
+1426 1307920952
+1427 742004601
+1428 2047707426
+1429 450245584
+1430 232000554
+1431 945403562
+1432 760594581
+1433 1528131345
+1434 984824438
+1435 1562526434
+1436 390271530
+1437 2009377625
+1438 1913773595
+1439 261572909
+1440 682451577
+1441 1755437531
+1442 232888737
+1443 632921639
+1444 1745922944
+1445 1039454214
+1446 1595871469
+1447 2107134210
+1448 350458031
+1449 1981794231
+1450 1939745623
+1451 137231021
+1452 349684452
+1453 231946530
+1454 989385485
+1455 719834333
+1456 1333626095
+1457 149822790
+1458 1461838935
+1459 1233849873
+1460 600068374
+1461 1693839489
+1462 31769787
+1463 1360662955
+1464 1074487186
+1465 1016594225
+1466 775705741
+1467 1464758717
+1468 878488203
+1469 541995688
+1470 1726331626
+1471 1560939780
+1472 149949572
+1473 1959220363
+1474 46377771
+1475 1895872516
+1476 851190929
+1477 1642249240
+1478 1855523078
+1479 1201648960
+1480 1476559823
+1481 1647785053
+1482 1338879981
+1483 1826244276
+1484 1879731583
+1485 180781819
+1486 398594961
+1487 1065874030
+1488 330604609
+1489 1860433896
+1490 152240255
+1491 930672983
+1492 1406789738
+1493 184010042
+1494 143852291
+1495 333793276
+1496 1200604268
+1497 919558032
+1498 1798551993
+1499 2079092471
+1500 1461553721
+1501 1377399971
+1502 1492548603
+1503 1611503293
+1504 1189136686
+1505 1538926374
+1506 1359892161
+1507 2040327615
+1508 1033691967
+1509 1067931592
+1510 1094492927
+1511 362768142
+1512 568232997
+1513 285889261
+1514 41528770
+1515 300480933
+1516 466671080
+1517 440123732
+1518 1366354963
+1519 797275689
+1520 153073980
+1521 1518595219
+1522 1727948672
+1523 1559863718
+1524 1702605261
+1525 1871800963
+1526 1893656995
+1527 755725881
+1528 643875348
+1529 1544725340
+1530 687334704
+1531 2105429069
+1532 774641664
+1533 32399659
+1534 1569448714
+1535 1963778350
+1536 1571326034
+1537 781857227
+1538 1856622318
+1539 457534353
+1540 1849788819
+1541 803631597
+1542 820302495
+1543 270538169
+1544 1089520858
+1545 861831266
+1546 571019102
+1547 1556191938
+1548 1301954998
+1549 1937374065
+1550 205983979
+1551 1455028978
+1552 1308485636
+1553 1933932652
+1554 867409049
+1555 863607250
+1556 1658249967
+1557 613582396
+1558 1619333131
+1559 154641667
+1560 10824088
+1561 159184188
+1562 112587088
+1563 785465752
+1564 191583847
+1565 1682035802
+1566 601760455
+1567 1762909881
+1568 316409382
+1569 310899125
+1570 72960586
+1571 18714553
+1572 1114530722
+1573 893263082
+1574 289252722
+1575 56567933
+1576 1755094348
+1577 860271824
+1578 1612759871
+1579 909565698
+1580 650162242
+1581 1818743851
+1582 217111028
+1583 1958647878
+1584 1605192855
+1585 1084520077
+1586 674771480
+1587 1115959174
+1588 1698102473
+1589 146620964
+1590 1270600842
+1591 1708926562
+1592 305805152
+1593 1383187930
+1594 346908666
+1595 497388999
+1596 917740085
+1597 948669121
+1598 112815233
+1599 1234149467
+1600 1259568246
+1601 185775819
+1602 1252864020
+1603 226615321
+1604 1079038901
+1605 1542116743
+1606 283183254
+1607 686649601
+1608 254904919
+1609 1895943125
+1610 1596215299
+1611 905067161
+1612 1567203328
+1613 1813326328
+1614 716231392
+1615 1024912535
+1616 750362757
+1617 1391002872
+1618 2140871710
+1619 300981583
+1620 1537623836
+1621 1263988904
+1622 2009908145
+1623 1843428988
+1624 499693186
+1625 209333163
+1626 193334340
+1627 1417433271
+1628 1158002285
+1629 306149573
+1630 504099090
+1631 270086883
+1632 491925392
+1633 1756963111
+1634 496702204
+1635 1570964294
+1636 1151596206
+1637 779885458
+1638 110130247
+1639 1406501125
+1640 528344936
+1641 1706345547
+1642 164084639
+1643 2095548264
+1644 1372188227
+1645 880316031
+1646 972977152
+1647 2122550984
+1648 123835255
+1649 966365214
+1650 276048919
+1651 1661459092
+1652 82870470
+1653 138473416
+1654 1357404432
+1655 582563656
+1656 347806580
+1657 1550738772
+1658 1999996928
+1659 1505808865
+1660 1856888345
+1661 356612370
+1662 1775895748
+1663 201330090
+1664 2113575481
+1665 125114305
+1666 1772294384
+1667 1117688039
+1668 904999763
+1669 1882424631
+1670 376705517
+1671 1433344699
+1672 1441286530
+1673 540790156
+1674 1381409316
+1675 665991109
+1676 1421106187
+1677 206902820
+1678 641058446
+1679 1544941442
+1680 1173268034
+1681 917107365
+1682 1058916886
+1683 1256138504
+1684 1055580782
+1685 268837671
+1686 1838702160
+1687 1403387362
+1688 1819576443
+1689 1691215440
+1690 761712579
+1691 1528981141
+1692 2047827811
+1693 390124679
+1694 1730311231
+1695 2013919644
+1696 515238984
+1697 1355121967
+1698 984124036
+1699 1420238748
+1700 1090062950
+1701 1360829553
+1702 706099799
+1703 383865833
+1704 1901619709
+1705 2087509115
+1706 1049856942
+1707 1175242248
+1708 146928287
+1709 1690915388
+1710 572700042
+1711 1320196321
+1712 460539106
+1713 1631616929
+1714 428851177
+1715 1516119888
+1716 1900454600
+1717 120069690
+1718 772023602
+1719 1572547395
+1720 1811285130
+1721 1533736181
+1722 954044888
+1723 1711629293
+1724 1923860860
+1725 536872471
+1726 1578065290
+1727 291616197
+1728 1891994438
+1729 414705678
+1730 1711854945
+1731 834573741
+1732 1775535231
+1733 270471096
+1734 1218439574
+1735 1529671292
+1736 210496564
+1737 120812868
+1738 557429892
+1739 357424851
+1740 1811728257
+1741 1130129934
+1742 1677621173
+1743 124783715
+1744 614263215
+1745 2106472350
+1746 1640903603
+1747 367234167
+1748 79058392
+1749 265443557
+1750 1939781563
+1751 1890343523
+1752 1799179738
+1753 746342803
+1754 1454489168
+1755 1575556950
+1756 1283215275
+1757 885070810
+1758 1867173147
+1759 1027726065
+1760 1299776488
+1761 1431544444
+1762 1862299806
+1763 927828071
+1764 1702015541
+1765 933255732
+1766 310015715
+1767 1912512105
+1768 1054068601
+1769 867445607
+1770 122453308
+1771 718313210
+1772 1997575542
+1773 1800074481
+1774 843096925
+1775 464355109
+1776 1759063184
+1777 336516880
+1778 831589277
+1779 1838121576
+1780 601960437
+1781 623887192
+1782 1580981451
+1783 253656527
+1784 1370229995
+1785 887986972
+1786 1829213477
+1787 505961622
+1788 1773057782
+1789 1548902977
+1790 1533687688
+1791 925350623
+1792 832963773
+1793 1248503846
+1794 1853178694
+1795 387495666
+1796 34275931
+1797 15710762
+1798 152524123
+1799 1088344532
+1800 883156369
+1801 274977432
+1802 1806657742
+1803 733248263
+1804 2075051913
+1805 502271019
+1806 1197603373
+1807 1686631449
+1808 838787899
+1809 2029192650
+1810 1377269378
+1811 1440748336
+1812 505596194
+1813 810767181
+1814 1694404863
+1815 1875826189
+1816 1698754153
+1817 1376134692
+1818 234304164
+1819 1324328288
+1820 777554021
+1821 1767991852
+1822 102195263
+1823 1610517795
+1824 869012050
+1825 1955373957
+1826 1998013461
+1827 903287981
+1828 1971084719
+1829 3053937
+1830 1991632513
+1831 706757441
+1832 278031369
+1833 1650806607
+1834 1440005704
+1835 205599634
+1836 5593978
+1837 490125429
+1838 1892231084
+1839 844381877
+1840 371834431
+1841 1122016814
+1842 137646565
+1843 877430625
+1844 1932783995
+1845 1832051428
+1846 605773167
+1847 1484054501
+1848 1060702473
+1849 840077331
+1850 660899141
+1851 1838256494
+1852 460585535
+1853 763094404
+1854 1301290641
+1855 1329597585
+1856 570984713
+1857 1151820455
+1858 85401919
+1859 394585785
+1860 1154874392
+1861 2077034432
+1862 1101343226
+1863 1432905761
+1864 1580357392
+1865 393865282
+1866 1638505395
+1867 1585951370
+1868 883990712
+1869 1383252831
+1870 282849600
+1871 1255825143
+1872 357785997
+1873 420496165
+1874 2133255769
+1875 143086345
+1876 105063946
+1877 591545288
+1878 1627140846
+1879 1165766419
+1880 1431622619
+1881 140556339
+1882 856539265
+1883 1892208154
+1884 903650743
+1885 10346259
+1886 1074322091
+1887 1474635456
+1888 1162166714
+1889 1159724010
+1890 1869221241
+1891 169557458
+1892 1089274795
+1893 823080819
+1894 1602463219
+1895 522148539
+1896 1216946102
+1897 1093484966
+1898 2108099909
+1899 2100936814
+1900 329254150
+1901 243465861
+1902 1209278309
+1903 687040147
+1904 663962027
+1905 1195050430
+1906 830126492
+1907 769025973
+1908 1786595718
+1909 309783690
+1910 1934792392
+1911 1070734689
+1912 450340029
+1913 643848009
+1914 815459195
+1915 1353990772
+1916 654194268
+1917 1889781287
+1918 681142581
+1919 1816360982
+1920 902021649
+1921 402880174
+1922 1985918440
+1923 1991296444
+1924 1225960994
+1925 1440898011
+1926 365961335
+1927 295423448
+1928 386899330
+1929 326577597
+1930 248876614
+1931 716153480
+1932 570043458
+1933 1458154923
+1934 1403193627
+1935 1234005485
+1936 505721706
+1937 85836472
+1938 2003031458
+1939 144833776
+1940 395620162
+1941 1790340202
+1942 1215568466
+1943 845960192
+1944 286704564
+1945 2031027661
+1946 52467316
+1947 940898832
+1948 1773325300
+1949 733609897
+1950 609776167
+1951 527863302
+1952 1136490072
+1953 448210959
+1954 371676098
+1955 214967418
+1956 1889108971
+1957 737637434
+1958 510390866
+1959 128524653
+1960 1064215031
+1961 759267480
+1962 844678133
+1963 1634258489
+1964 69938755
+1965 100388112
+1966 720780327
+1967 575660461
+1968 186224584
+1969 576328137
+1970 720494238
+1971 581844747
+1972 219184692
+1973 1936062704
+1974 1427804939
+1975 505889256
+1976 1819606717
+1977 1480272255
+1978 1446788088
+1979 1445448370
+1980 66398505
+1981 2056564255
+1982 1973311672
+1983 1202888577
+1984 357291567
+1985 197504122
+1986 1417855995
+1987 98916890
+1988 935141556
+1989 1928246861
+1990 227441543
+1991 1999356587
+1992 540030693
+1993 1072119676
+1994 1486131429
+1995 609969448
+1996 1172507788
+1997 59428108
+1998 1185629910
+1999 1358732373
+2000 635756245
+2001 1906124148
+2002 1940577120
+2003 854940937
+2004 1694703204
+2005 1220898411
+2006 1360830193
+2007 1366826273
+2008 553687018
+2009 660134634
+2010 664790995
+2011 620085523
+2012 569215241
+2013 490619019
+2014 1822974100
+2015 926506808
+2016 688123142
+2017 1093346447
+2018 1025423698
+2019 1623264698
+2020 874109660
+2021 1252865241
+2022 1475137638
+2023 1414140353
+2024 177501269
+2025 813785419
+2026 2024109802
+2027 1350009058
+2028 873213527
+2029 1062256064
+2030 561257783
+2031 1508969772
+2032 820896564
+2033 354351255
+2034 216427062
+2035 368116120
+2036 1575249666
+2037 1577257255
+2038 1734942393
+2039 2128936684
+2040 89908241
+2041 252249741
+2042 601538560
+2043 659123483
+2044 742868760
+2045 277029012
+2046 1585630291
+2047 1430991902
+2048 1370375460
+2049 463570342
+2050 906772953
+2051 97001472
+2052 1716435583
+2053 234426943
+2054 1511141826
+2055 1893936853
+2056 1048212362
+2057 1387767980
+2058 1096462263
+2059 1921425889
+2060 302540396
+2061 1657720046
+2062 1282912013
+2063 1123436960
+2064 2012071301
+2065 1499339075
+2066 1491553080
+2067 1439837319
+2068 929112683
+2069 1079011825
+2070 1421290355
+2071 1019020924
+2072 1331261566
+2073 2022828915
+2074 1678144407
+2075 2074130327
+2076 152374280
+2077 1116291051
+2078 1357638581
+2079 1522749740
+2080 1579861393
+2081 116927886
+2082 1619751212
+2083 1148813328
+2084 351354829
+2085 983409390
+2086 895266533
+2087 1399567191
+2088 223693722
+2089 1991728796
+2090 1173509432
+2091 526234118
+2092 1501965194
+2093 308937798
+2094 1649671078
+2095 1366552847
+2096 1808276873
+2097 993740510
+2098 658906518
+2099 589905908
+2100 2072752336
+2101 2080196874
+2102 1608926833
+2103 1256530254
+2104 1955542141
+2105 1139587592
+2106 1183176933
+2107 2107916421
+2108 108394995
+2109 393331867
+2110 1483182513
+2111 1688256388
+2112 510259753
+2113 955450078
+2114 689586069
+2115 861614583
+2116 1938859468
+2117 1584852602
+2118 113698126
+2119 15069543
+2120 1429097751
+2121 1287207559
+2122 541303661
+2123 783579297
+2124 1596145357
+2125 43491092
+2126 2648497
+2127 1256938582
+2128 1037231602
+2129 661555015
+2130 1846844491
+2131 962500290
+2132 594268241
+2133 1308287676
+2134 71546897
+2135 402326735
+2136 300391620
+2137 1254723830
+2138 362759508
+2139 408786616
+2140 1648055697
+2141 1845942022
+2142 2097043004
+2143 10831803
+2144 653908452
+2145 639145425
+2146 872446386
+2147 445284272
+2148 76514380
+2149 986144512
+2150 460353815
+2151 1505612131
+2152 125868423
+2153 1001657477
+2154 141707780
+2155 1722013780
+2156 1045148569
+2157 144356277
+2158 831468715
+2159 2082380171
+2160 805911293
+2161 530829558
+2162 897396814
+2163 1400179534
+2164 1839117234
+2165 968943711
+2166 1802506269
+2167 2139508854
+2168 76183893
+2169 17782130
+2170 400811822
+2171 1724239591
+2172 1863724152
+2173 350371179
+2174 1735071394
+2175 370148956
+2176 989516604
+2177 460034132
+2178 815433228
+2179 1066030984
+2180 1446178644
+2181 1275787044
+2182 424159467
+2183 1572047068
+2184 129960873
+2185 565867248
+2186 1146577200
+2187 1175109442
+2188 710223525
+2189 1978045915
+2190 1110005965
+2191 1516134818
+2192 361391825
+2193 2007402779
+2194 768830705
+2195 53025411
+2196 828862842
+2197 423853326
+2198 45050618
+2199 905046736
+2200 441635456
+2201 445862440
+2202 481802679
+2203 157875960
+2204 796233619
+2205 69390425
+2206 528024916
+2207 1785750224
+2208 529424557
+2209 1343458145
+2210 704297560
+2211 1975603201
+2212 471761541
+2213 1128457028
+2214 1400166621
+2215 601722414
+2216 1694324276
+2217 399260174
+2218 1776831856
+2219 257064153
+2220 229822441
+2221 739354173
+2222 1773198972
+2223 591214267
+2224 599273305
+2225 394546029
+2226 644239678
+2227 1428136147
+2228 818399355
+2229 689290296
+2230 185699235
+2231 1260034812
+2232 1135152737
+2233 667501914
+2234 1417910772
+2235 1931386356
+2236 736892339
+2237 1945935689
+2238 1569652932
+2239 1266316896
+2240 1141910186
+2241 126466845
+2242 1094436450
+2243 1613671727
+2244 1254923873
+2245 347119423
+2246 67910493
+2247 801764501
+2248 746379597
+2249 1844742349
+2250 1058828654
+2251 976202039
+2252 436612874
+2253 684543978
+2254 1567416306
+2255 1035886179
+2256 1079090007
+2257 64172336
+2258 316538679
+2259 1897489363
+2260 753462633
+2261 502237914
+2262 1010040527
+2263 1888615370
+2264 1169739829
+2265 280467651
+2266 1672518078
+2267 1906632168
+2268 78919692
+2269 1094687363
+2270 1025465417
+2271 1220829878
+2272 1221154208
+2273 2119901867
+2274 687017957
+2275 328594433
+2276 319537642
+2277 754928450
+2278 1130358934
+2279 1065917240
+2280 452187151
+2281 41703940
+2282 2042119279
+2283 888800026
+2284 726247919
+2285 1462051937
+2286 1924686205
+2287 1805337926
+2288 1526224273
+2289 93741236
+2290 1555343641
+2291 132203258
+2292 595979151
+2293 417900520
+2294 2020818628
+2295 1765718980
+2296 698368172
+2297 1545853059
+2298 1524867500
+2299 777287864
+2300 493056774
+2301 402849269
+2302 1998117743
+2303 1714210982
+2304 375267488
+2305 537652052
+2306 2042805415
+2307 694805131
+2308 1292580503
+2309 1025680701
+2310 1760722371
+2311 1744767654
+2312 1067384641
+2313 1655358002
+2314 486084032
+2315 1793632560
+2316 969926291
+2317 263286590
+2318 1451486839
+2319 348666916
+2320 357027826
+2321 859346832
+2322 480870175
+2323 953006977
+2324 1277247353
+2325 354205155
+2326 571242309
+2327 1975615525
+2328 1900058214
+2329 2096109810
+2330 605419741
+2331 245631340
+2332 351475431
+2333 456053836
+2334 1959842322
+2335 726742920
+2336 993705889
+2337 1855164089
+2338 1421548051
+2339 138802744
+2340 733361142
+2341 1034786774
+2342 1883570398
+2343 1800745784
+2344 542661128
+2345 222170783
+2346 1446894696
+2347 1512587419
+2348 485457373
+2349 750897887
+2350 1861254335
+2351 842485199
+2352 1610244720
+2353 194640862
+2354 1795492177
+2355 740008425
+2356 548846018
+2357 219250838
+2358 568140302
+2359 301420584
+2360 167877000
+2361 1173560043
+2362 547051925
+2363 519352432
+2364 1629613880
+2365 359410599
+2366 1246095352
+2367 475836121
+2368 67091041
+2369 520159755
+2370 614638865
+2371 800452183
+2372 1554946529
+2373 350725615
+2374 453714319
+2375 2097607657
+2376 572896398
+2377 1900609016
+2378 1462711428
+2379 1058353771
+2380 504023255
+2381 1176482115
+2382 1900838971
+2383 2114267975
+2384 1371122978
+2385 1548847500
+2386 706792752
+2387 1919968996
+2388 1768098338
+2389 1274933054
+2390 73905932
+2391 1935975339
+2392 301009450
+2393 620957857
+2394 307844123
+2395 1930623330
+2396 980368457
+2397 1553939475
+2398 258975803
+2399 1047459498
+2400 2074099230
+2401 873614668
+2402 1847911681
+2403 1481562111
+2404 1224340283
+2405 154142353
+2406 1431686120
+2407 1797236682
+2408 2054751369
+2409 746913900
+2410 708106805
+2411 411290976
+2412 1923396015
+2413 461462128
+2414 378075304
+2415 1147035345
+2416 2010309628
+2417 1084868056
+2418 919520693
+2419 1630924319
+2420 212317463
+2421 993426626
+2422 1419416010
+2423 513326913
+2424 1614384483
+2425 1727260133
+2426 296466595
+2427 447269292
+2428 1133715960
+2429 555442398
+2430 1494728790
+2431 1060331542
+2432 1429057066
+2433 1195156824
+2434 394410005
+2435 505913701
+2436 1349299177
+2437 1826096125
+2438 155666735
+2439 1256566898
+2440 425526377
+2441 863773541
+2442 1667857874
+2443 201438744
+2444 1325235669
+2445 2045933178
+2446 1348474090
+2447 1188061650
+2448 983317587
+2449 120511135
+2450 671502321
+2451 1195635050
+2452 1113937761
+2453 2090918331
+2454 1708961963
+2455 580838597
+2456 1670694816
+2457 2005428558
+2458 1028107889
+2459 656927128
+2460 413387308
+2461 375353032
+2462 1717258670
+2463 1842444374
+2464 1570509856
+2465 2111668675
+2466 200874427
+2467 772325385
+2468 1790281152
+2469 356541163
+2470 2028892283
+2471 68323881
+2472 1220314704
+2473 1549266509
+2474 269762625
+2475 398066725
+2476 1447716040
+2477 1618236715
+2478 1586128375
+2479 283549979
+2480 1738747851
+2481 110147048
+2482 1479185029
+2483 705201964
+2484 53581731
+2485 1040663344
+2486 1286040561
+2487 1724276547
+2488 898608254
+2489 166664803
+2490 233720027
+2491 1311995562
+2492 542017835
+2493 1950978697
+2494 1006956288
+2495 2112527691
+2496 1915163724
+2497 1207830715
+2498 737369428
+2499 1557961228
+2500 1564371878
+2501 618778063
+2502 1626285109
+2503 637202934
+2504 20560924
+2505 1896047735
+2506 1035269660
+2507 1468276964
+2508 1366800802
+2509 473914387
+2510 1751826943
+2511 958065005
+2512 584061436
+2513 1083528324
+2514 1663266970
+2515 637643167
+2516 2124191668
+2517 801823883
+2518 214436067
+2519 875316274
+2520 968488686
+2521 448156094
+2522 39828188
+2523 1510506521
+2524 251651144
+2525 1046784476
+2526 1475550564
+2527 19331220
+2528 107131544
+2529 65436344
+2530 1577292449
+2531 1671503422
+2532 684214407
+2533 1056093910
+2534 161222709
+2535 704775332
+2536 804657997
+2537 1196492369
+2538 25568648
+2539 23975152
+2540 1670406756
+2541 1777395592
+2542 982040157
+2543 106984544
+2544 713440268
+2545 497823479
+2546 744627712
+2547 690148289
+2548 1299647363
+2549 959063779
+2550 1565464563
+2551 120652401
+2552 1407219873
+2553 1605292752
+2554 1631158923
+2555 1658871017
+2556 504593580
+2557 959225839
+2558 1678202238
+2559 611725124
+2560 1024662184
+2561 1108011039
+2562 135744899
+2563 1708876591
+2564 16621301
+2565 296967608
+2566 266168275
+2567 821279299
+2568 1493459977
+2569 291736924
+2570 845254451
+2571 1016383085
+2572 2069132516
+2573 1827294608
+2574 1123367630
+2575 635089136
+2576 177634440
+2577 1867995342
+2578 1325237425
+2579 1477281803
+2580 679575473
+2581 743218341
+2582 1597934204
+2583 2086795346
+2584 201027445
+2585 1081609479
+2586 1598182716
+2587 705621025
+2588 2040835319
+2589 1128901306
+2590 1317346150
+2591 918013855
+2592 89428697
+2593 1453091049
+2594 479406798
+2595 106049998
+2596 1750058657
+2597 745575074
+2598 927329297
+2599 1096034986
+2600 1037311998
+2601 1772583748
+2602 2112418071
+2603 958960866
+2604 1452394709
+2605 1088302053
+2606 1594050002
+2607 1630029149
+2608 808813747
+2609 771803780
+2610 959827304
+2611 1488389220
+2612 1515022121
+2613 410277860
+2614 1427700919
+2615 1716049566
+2616 1491887340
+2617 878399987
+2618 274186943
+2619 1385239011
+2620 2007301293
+2621 1591533093
+2622 155769218
+2623 2096729990
+2624 897140494
+2625 635176016
+2626 55296340
+2627 499715503
+2628 1380751090
+2629 982625638
+2630 1595750489
+2631 270579440
+2632 607725738
+2633 1560684913
+2634 1229540306
+2635 2060120447
+2636 501503318
+2637 676106661
+2638 1542665948
+2639 1310317066
+2640 1447910441
+2641 355009604
+2642 651222638
+2643 815448914
+2644 765287465
+2645 2078923557
+2646 384014832
+2647 109691157
+2648 809839896
+2649 658201775
+2650 1494930168
+2651 669657541
+2652 102251221
+2653 1650699386
+2654 618903883
+2655 999391715
+2656 138391754
+2657 674200224
+2658 1499107219
+2659 1519142845
+2660 1656825862
+2661 947374060
+2662 1789722285
+2663 117067952
+2664 360575325
+2665 871778944
+2666 29704752
+2667 862078644
+2668 1547885605
+2669 1572370700
+2670 24912062
+2671 848312398
+2672 1927380305
+2673 676134700
+2674 1663761312
+2675 545184122
+2676 607574610
+2677 2047776144
+2678 654875279
+2679 1417414506
+2680 558494271
+2681 2321799
+2682 2087072048
+2683 660745492
+2684 1653021185
+2685 558492283
+2686 1660137208
+2687 1791412939
+2688 1232692507
+2689 1011760779
+2690 1163072136
+2691 742034721
+2692 1959134839
+2693 805310774
+2694 859102674
+2695 172226517
+2696 1677089718
+2697 888807426
+2698 1034305161
+2699 1077491675
+2700 313694478
+2701 1059217223
+2702 1925804073
+2703 93591135
+2704 1735351923
+2705 1442081737
+2706 638775257
+2707 195442885
+2708 1342374233
+2709 1293650536
+2710 1612857392
+2711 1900868504
+2712 1295972335
+2713 1552445792
+2714 414130349
+2715 801509872
+2716 2110938075
+2717 2074267557
+2718 445439164
+2719 1196146935
+2720 938544688
+2721 1608511300
+2722 1938181656
+2723 750195879
+2724 266338426
+2725 649800682
+2726 922422396
+2727 1943428144
+2728 1538608108
+2729 1956727557
+2730 873436171
+2731 1852302587
+2732 868461132
+2733 651756596
+2734 1945893722
+2735 456329408
+2736 2093838333
+2737 437185332
+2738 651772293
+2739 1288728918
+2740 1730835868
+2741 117146037
+2742 1042113775
+2743 879324556
+2744 1669591829
+2745 1456244124
+2746 1680834428
+2747 1633046257
+2748 1383028033
+2749 2126273592
+2750 681709544
+2751 174089073
+2752 1587301245
+2753 472407552
+2754 924284952
+2755 1853639671
+2756 1122208235
+2757 1846707349
+2758 1649584168
+2759 513332695
+2760 1655951258
+2761 375536691
+2762 218151634
+2763 376928743
+2764 1027293288
+2765 16561709
+2766 833258151
+2767 973647973
+2768 453747041
+2769 1485030444
+2770 114893244
+2771 37099261
+2772 1602176482
+2773 1157007019
+2774 916423817
+2775 1124284663
+2776 465767495
+2777 449774598
+2778 609847272
+2779 1848795528
+2780 428564542
+2781 1291556816
+2782 2022884601
+2783 2015865787
+2784 1763964369
+2785 799685905
+2786 1722021811
+2787 738688956
+2788 498909606
+2789 1224122331
+2790 1252021651
+2791 7377217
+2792 1599659022
+2793 1470173286
+2794 384305960
+2795 479468662
+2796 1486734995
+2797 1217564111
+2798 1453116636
+2799 1940482036
+2800 555110907
+2801 1568009880
+2802 1977581297
+2803 9803741
+2804 577533251
+2805 746521467
+2806 1134088405
+2807 1043300746
+2808 1196296065
+2809 1743935677
+2810 744612626
+2811 1624860607
+2812 888008846
+2813 620013579
+2814 1493242747
+2815 504489567
+2816 1419699484
+2817 1067780910
+2818 1243178523
+2819 1918609091
+2820 144419593
+2821 347716526
+2822 1925986308
+2823 1744078615
+2824 1817889812
+2825 162808620
+2826 76063630
+2827 1157141159
+2828 1380372731
+2829 1529180266
+2830 950139547
+2831 1935483638
+2832 949706498
+2833 780237197
+2834 1945287380
+2835 1527239749
+2836 1526758664
+2837 931892137
+2838 423056847
+2839 575571081
+2840 528344166
+2841 1167669473
+2842 52948040
+2843 1416353012
+2844 1787683052
+2845 1546190787
+2846 1920842579
+2847 1059898888
+2848 466488049
+2849 1016537454
+2850 831024331
+2851 610907642
+2852 1364253981
+2853 609526991
+2854 207502610
+2855 1034660145
+2856 772335611
+2857 283566240
+2858 44317657
+2859 5224694
+2860 1812746506
+2861 994457204
+2862 1940708333
+2863 614969356
+2864 1774694401
+2865 1738512065
+2866 2142209105
+2867 1153969417
+2868 522920554
+2869 417782304
+2870 1729540498
+2871 1051264720
+2872 1585451777
+2873 1782488539
+2874 320134085
+2875 1225651181
+2876 1181195678
+2877 93493016
+2878 138066421
+2879 1647683728
+2880 1110030471
+2881 969090753
+2882 111107722
+2883 326800804
+2884 1578617744
+2885 318610332
+2886 1361460949
+2887 203469708
+2888 602176572
+2889 1405778606
+2890 208694402
+2891 267439430
+2892 252752163
+2893 1919087
+2894 882408786
+2895 2027446564
+2896 1740431152
+2897 877134243
+2898 1033932334
+2899 115868058
+2900 1294916547
+2901 615989184
+2902 1167132779
+2903 732884676
+2904 250994075
+2905 1487266864
+2906 1958535857
+2907 1432189754
+2908 1580759880
+2909 2096602279
+2910 932389834
+2911 543306703
+2912 918209384
+2913 1043497556
+2914 870107507
+2915 349343480
+2916 1362107889
+2917 84084809
+2918 552813188
+2919 1964284461
+2920 1489863415
+2921 761507591
+2922 84240244
+2923 1742615578
+2924 763426678
+2925 966649030
+2926 1622578495
+2927 356374183
+2928 1843783274
+2929 509027181
+2930 472242241
+2931 991216173
+2932 1125016365
+2933 1639375020
+2934 1724100850
+2935 1376010441
+2936 979158236
+2937 1535153059
+2938 660716547
+2939 412434469
+2940 1484271690
+2941 1593106381
+2942 955741172
+2943 254997426
+2944 489120289
+2945 1825848680
+2946 604340907
+2947 1851228178
+2948 1909933489
+2949 1157154095
+2950 1668028992
+2951 1252313256
+2952 1918661686
+2953 1752269236
+2954 847445187
+2955 534604717
+2956 571434618
+2957 322540034
+2958 890978900
+2959 267734244
+2960 831567215
+2961 1363221141
+2962 1258950418
+2963 1956583580
+2964 855112514
+2965 835567620
+2966 1185110373
+2967 1834270750
+2968 223237031
+2969 1845826920
+2970 99221571
+2971 1707508722
+2972 1291449653
+2973 1054962744
+2974 1962506148
+2975 1780569943
+2976 733327776
+2977 419363407
+2978 1484314473
+2979 495777617
+2980 1576517503
+2981 1004859817
+2982 1748090873
+2983 1347695541
+2984 609645405
+2985 448052412
+2986 1882300258
+2987 1181080024
+2988 770592446
+2989 625795510
+2990 1448814268
+2991 1602159661
+2992 1989016652
+2993 560281038
+2994 1411259594
+2995 696645518
+2996 1395848658
+2997 448886319
+2998 383432620
+2999 1619085690
+3000 147229592
+3001 482654192
+3002 1179110764
+3003 1438679245
+3004 1537616936
+3005 994133264
+3006 1071765540
+3007 123461064
+3008 1413496672
+3009 408596366
+3010 619238681
+3011 842530527
+3012 1413456183
+3013 219845906
+3014 42742420
+3015 2023101589
+3016 667898319
+3017 1925042679
+3018 1056697965
+3019 1438490765
+3020 403354541
+3021 358028585
+3022 893166779
+3023 244887545
+3024 918309624
+3025 156942725
+3026 941533063
+3027 166674634
+3028 605829044
+3029 1324965684
+3030 1785760324
+3031 753058636
+3032 1807619876
+3033 817387440
+3034 44254234
+3035 1197753164
+3036 1811520705
+3037 1116019774
+3038 1321214228
+3039 1077533729
+3040 1524616140
+3041 1940452909
+3042 1920064256
+3043 790588676
+3044 12815167
+3045 1962806676
+3046 666206617
+3047 680713486
+3048 1740365707
+3049 1722904582
+3050 2119204252
+3051 2143720249
+3052 2080933167
+3053 864887383
+3054 241124146
+3055 851759143
+3056 1021830108
+3057 1182657210
+3058 1018433778
+3059 1627659152
+3060 360139246
+3061 656710454
+3062 233234141
+3063 20275474
+3064 1474097895
+3065 277488375
+3066 1218028638
+3067 1138134952
+3068 1393508149
+3069 391759218
+3070 68185033
+3071 770640642
+3072 184728479
+3073 1988249289
+3074 1561229318
+3075 197543646
+3076 1803572317
+3077 79952287
+3078 878257133
+3079 1396454377
+3080 1802856869
+3081 849977737
+3082 1392690978
+3083 1736306388
+3084 1714865120
+3085 1633815124
+3086 440581884
+3087 589211580
+3088 668988686
+3089 1459015662
+3090 69387084
+3091 1029127932
+3092 2115726116
+3093 302621225
+3094 1049403406
+3095 1442340363
+3096 580109600
+3097 119948396
+3098 432991667
+3099 1973617750
+3100 511707614
+3101 501176700
+3102 596774744
+3103 696436093
+3104 341942341
+3105 10520414
+3106 893979740
+3107 2145514659
+3108 90472701
+3109 1772236873
+3110 1394485388
+3111 1893329570
+3112 474730962
+3113 639692718
+3114 1482152310
+3115 42112434
+3116 126024194
+3117 1922734194
+3118 631324014
+3119 795012881
+3120 1234266208
+3121 700711098
+3122 1824140813
+3123 1202508677
+3124 1003332324
+3125 726060572
+3126 497365392
+3127 1583441924
+3128 846008968
+3129 930357060
+3130 1409576026
+3131 1357716583
+3132 1431533760
+3133 2006350770
+3134 2054152676
+3135 1773476102
+3136 2016871184
+3137 800648768
+3138 1771507113
+3139 2107343885
+3140 425401993
+3141 1018508853
+3142 1853189807
+3143 900132955
+3144 1658201571
+3145 1187858470
+3146 942245389
+3147 1784225765
+3148 963109016
+3149 1573569403
+3150 431754998
+3151 49891577
+3152 126796854
+3153 108412164
+3154 1252400254
+3155 1130129178
+3156 834472736
+3157 1749765646
+3158 566087454
+3159 1680481704
+3160 532639058
+3161 1975663481
+3162 890714639
+3163 1964172819
+3164 1834530603
+3165 797383668
+3166 1590165273
+3167 1703918140
+3168 1598032436
+3169 1214188738
+3170 1663778377
+3171 2023434430
+3172 85213943
+3173 1369484537
+3174 776083737
+3175 1743415514
+3176 409859359
+3177 1718329127
+3178 1380157631
+3179 1372968375
+3180 1144414882
+3181 1811912630
+3182 1422859952
+3183 1271211736
+3184 1920324794
+3185 527776558
+3186 253857266
+3187 607313882
+3188 130058557
+3189 819944721
+3190 140311938
+3191 662697615
+3192 648124554
+3193 1031026578
+3194 479386786
+3195 335171509
+3196 1828410246
+3197 2069552059
+3198 2039089649
+3199 1278959034
+3200 1136257149
+3201 1555384379
+3202 1154909816
+3203 1221471092
+3204 777385268
+3205 1930993554
+3206 817402958
+3207 1187244627
+3208 1501839033
+3209 50076942
+3210 412729354
+3211 498770267
+3212 1861989572
+3213 1835589307
+3214 1769982004
+3215 1634830718
+3216 215882217
+3217 2023839270
+3218 94660952
+3219 345940774
+3220 696300343
+3221 234972890
+3222 1008638390
+3223 1344424897
+3224 1265999468
+3225 1488025176
+3226 1679596407
+3227 946926066
+3228 1410093588
+3229 1571202408
+3230 78401453
+3231 398867089
+3232 979103139
+3233 1233311269
+3234 1620338182
+3235 1756488407
+3236 1016821175
+3237 290257492
+3238 796249386
+3239 371176560
+3240 340334434
+3241 1208978741
+3242 869946828
+3243 54840358
+3244 897084400
+3245 492445184
+3246 1689671076
+3247 1112966617
+3248 368800806
+3249 1784332028
+3250 1458907392
+3251 1065101150
+3252 2019304919
+3253 320062134
+3254 262042399
+3255 1137820739
+3256 1808087310
+3257 1941638806
+3258 2084746806
+3259 1070697250
+3260 1365357567
+3261 15664611
+3262 1469564340
+3263 196977058
+3264 1248975880
+3265 942418874
+3266 1953465466
+3267 118313408
+3268 1232676366
+3269 602231204
+3270 489489968
+3271 1573010801
+3272 1811209945
+3273 1359436796
+3274 1627851159
+3275 560810697
+3276 1851881980
+3277 1170038588
+3278 1673777315
+3279 73199139
+3280 806886968
+3281 985201059
+3282 1138300289
+3283 678708239
+3284 1305263193
+3285 1400342688
+3286 1816528979
+3287 965866855
+3288 1194497847
+3289 1753792137
+3290 2036564106
+3291 412371766
+3292 1769456748
+3293 1358644798
+3294 609348824
+3295 870948980
+3296 153580024
+3297 415330642
+3298 989262388
+3299 1386256390
+3300 1017561847
+3301 1478752357
+3302 811783543
+3303 681288144
+3304 690705505
+3305 292151055
+3306 1242098842
+3307 395103838
+3308 1462189643
+3309 768392509
+3310 468302977
+3311 121592963
+3312 1753593568
+3313 1606603266
+3314 800301203
+3315 911373113
+3316 859462306
+3317 469346534
+3318 1877239968
+3319 2053960153
+3320 75655023
+3321 1766320426
+3322 318848271
+3323 1845111771
+3324 977481576
+3325 928197096
+3326 568577103
+3327 1131061600
+3328 1343527738
+3329 1557839492
+3330 369834343
+3331 213605937
+3332 889108201
+3333 1181617886
+3334 894894082
+3335 1579813706
+3336 1473768941
+3337 2136992924
+3338 1974917544
+3339 788474936
+3340 757901785
+3341 295736873
+3342 910067900
+3343 364011705
+3344 1902340139
+3345 1710369103
+3346 1275384818
+3347 614318798
+3348 32231989
+3349 1005141138
+3350 520795303
+3351 107887012
+3352 623977917
+3353 839643575
+3354 1952998783
+3355 1601459493
+3356 1767840671
+3357 374092238
+3358 585037446
+3359 963884761
+3360 1931931730
+3361 954871789
+3362 1177490699
+3363 673556283
+3364 2136489675
+3365 2072384781
+3366 105886342
+3367 1462774969
+3368 2061894057
+3369 2080803886
+3370 103766257
+3371 672312194
+3372 229057112
+3373 1013834157
+3374 1036323899
+3375 2131397251
+3376 576719612
+3377 164225069
+3378 598232401
+3379 608951601
+3380 1169366207
+3381 1119027705
+3382 716838613
+3383 1793344124
+3384 1958671280
+3385 522353748
+3386 1247319970
+3387 1579028303
+3388 896445987
+3389 1832357416
+3390 395429416
+3391 680894069
+3392 639745557
+3393 1572920115
+3394 1354450353
+3395 628751584
+3396 1497821248
+3397 1460336695
+3398 2091526553
+3399 1412231657
+3400 1393656933
+3401 47809163
+3402 2084543851
+3403 1622714045
+3404 1061643320
+3405 973384102
+3406 1606627649
+3407 1638362933
+3408 1137609171
+3409 57376402
+3410 99830886
+3411 159491731
+3412 1176404107
+3413 816669500
+3414 1952835855
+3415 987591739
+3416 1339023248
+3417 1052672177
+3418 419136394
+3419 87985587
+3420 737545945
+3421 814565811
+3422 768879657
+3423 1377291502
+3424 240002278
+3425 2123330010
+3426 2006043087
+3427 1737823527
+3428 1436183057
+3429 1950085992
+3430 1002571536
+3431 682356342
+3432 1997895155
+3433 939631740
+3434 157586740
+3435 912054828
+3436 1913015842
+3437 1764214389
+3438 402934113
+3439 903141366
+3440 1821590791
+3441 502764999
+3442 1062633097
+3443 850511251
+3444 1319434499
+3445 867985304
+3446 1838102990
+3447 510974100
+3448 1920657482
+3449 109755737
+3450 598959687
+3451 510719779
+3452 924321548
+3453 1367839344
+3454 1888011282
+3455 1164323826
+3456 1343685706
+3457 1746570721
+3458 754663705
+3459 632385115
+3460 1549173065
+3461 1757235242
+3462 1314741458
+3463 1399584573
+3464 549383334
+3465 1472328198
+3466 164155753
+3467 314915528
+3468 1089058939
+3469 567089866
+3470 1218056894
+3471 763166082
+3472 1069854865
+3473 133206343
+3474 1613677333
+3475 241805717
+3476 1001191648
+3477 1304296676
+3478 752779817
+3479 774365482
+3480 1414052413
+3481 1351739504
+3482 1285085261
+3483 190890313
+3484 572095201
+3485 1025612895
+3486 1355214139
+3487 1915780907
+3488 624699968
+3489 2109877845
+3490 400682375
+3491 26389386
+3492 1719629439
+3493 1715423833
+3494 1425973959
+3495 121529125
+3496 1040268383
+3497 1590129712
+3498 436444653
+3499 2129327322
+3500 9735930
+3501 1654501548
+3502 745009756
+3503 1079590795
+3504 1787707891
+3505 211203442
+3506 1321396512
+3507 641415891
+3508 1515500118
+3509 2074176329
+3510 1415781373
+3511 782068883
+3512 1278432186
+3513 553382987
+3514 972959196
+3515 1850527387
+3516 1578995882
+3517 180689687
+3518 1618824646
+3519 56212203
+3520 143083884
+3521 2019507021
+3522 82601589
+3523 1862713323
+3524 1587447206
+3525 1508575548
+3526 1984242448
+3527 480231941
+3528 951221612
+3529 273203454
+3530 462075615
+3531 960957542
+3532 1927705002
+3533 1207085372
+3534 2040548337
+3535 1567929245
+3536 1418288814
+3537 1214461202
+3538 61861489
+3539 786305284
+3540 1141153883
+3541 1477642862
+3542 1568374167
+3543 272102421
+3544 2031025849
+3545 393849715
+3546 2122629808
+3547 1462538084
+3548 574539402
+3549 1593970807
+3550 1518750287
+3551 717623287
+3552 1465994180
+3553 1601351876
+3554 432852962
+3555 905957739
+3556 962443776
+3557 269611763
+3558 1386189680
+3559 1913665388
+3560 542815217
+3561 1848265296
+3562 727139282
+3563 323036571
+3564 907867020
+3565 620203971
+3566 1890965816
+3567 178672186
+3568 1834665173
+3569 1952827305
+3570 964977470
+3571 828335409
+3572 1282986520
+3573 385867989
+3574 1100437830
+3575 1166528721
+3576 779717704
+3577 1075583991
+3578 481583157
+3579 1354257106
+3580 522071150
+3581 2000333444
+3582 2071880393
+3583 1988065330
+3584 1454201672
+3585 357249708
+3586 746539421
+3587 269161800
+3588 626861471
+3589 2132729102
+3590 35343540
+3591 1169676688
+3592 1833510750
+3593 762482822
+3594 1492713259
+3595 593894122
+3596 1382686794
+3597 1236195427
+3598 772566308
+3599 1069868319
+3600 1041539085
+3601 1737543778
+3602 1898203728
+3603 177041957
+3604 2123411767
+3605 851157911
+3606 1343570678
+3607 755645823
+3608 1926741902
+3609 1825153836
+3610 2109902929
+3611 301329404
+3612 1678003632
+3613 2034299675
+3614 141911086
+3615 984721657
+3616 244065735
+3617 888450508
+3618 1253883457
+3619 870927206
+3620 873695962
+3621 1289226998
+3622 2040603894
+3623 559723064
+3624 2051709820
+3625 1385833505
+3626 1153617186
+3627 1286912966
+3628 474545284
+3629 1926183494
+3630 209297638
+3631 1516084369
+3632 1516243624
+3633 2107501366
+3634 1693126326
+3635 1492171743
+3636 811175629
+3637 889213357
+3638 100333918
+3639 590433883
+3640 566883545
+3641 62753199
+3642 891763287
+3643 97403529
+3644 2097052874
+3645 1033674374
+3646 1082125186
+3647 193634961
+3648 1922124882
+3649 188524996
+3650 1064562167
+3651 648337196
+3652 1477751994
+3653 957682413
+3654 1208060260
+3655 1381978166
+3656 196032270
+3657 214193798
+3658 521407485
+3659 670577555
+3660 2140377292
+3661 730705123
+3662 39178276
+3663 1509137268
+3664 690722841
+3665 1732304603
+3666 853825363
+3667 1501898471
+3668 474034312
+3669 954159281
+3670 2092332354
+3671 1040917857
+3672 1016912480
+3673 836611994
+3674 1138321386
+3675 966481707
+3676 1870286368
+3677 72962925
+3678 1160116668
+3679 1644927602
+3680 261487921
+3681 77195188
+3682 145781150
+3683 1739239915
+3684 1034877601
+3685 1353841410
+3686 973734433
+3687 1230909872
+3688 1568035208
+3689 1495141918
+3690 1901487427
+3691 1560928852
+3692 78363393
+3693 1940665703
+3694 922582472
+3695 769086235
+3696 1525486658
+3697 1776407835
+3698 123501058
+3699 1999520970
+3700 583083468
+3701 68349764
+3702 892955179
+3703 1599995948
+3704 904961758
+3705 2031276566
+3706 418994007
+3707 627764478
+3708 2104239491
+3709 1579110676
+3710 125208432
+3711 218243764
+3712 1656305864
+3713 270989582
+3714 1957483679
+3715 543699817
+3716 1624830992
+3717 783734464
+3718 1774609689
+3719 1045382552
+3720 131392735
+3721 1528613468
+3722 458827756
+3723 209756128
+3724 1321795524
+3725 1381410228
+3726 978842363
+3727 699798534
+3728 1010334415
+3729 1102343421
+3730 551835857
+3731 1593417883
+3732 1170693186
+3733 1444791036
+3734 1045930184
+3735 2075654944
+3736 1328583954
+3737 1464924191
+3738 555935775
+3739 1285339797
+3740 896551219
+3741 681144207
+3742 1503583561
+3743 405373435
+3744 952133790
+3745 1313583592
+3746 949073253
+3747 429481134
+3748 2097318057
+3749 576199294
+3750 1474863687
+3751 81227144
+3752 2104812763
+3753 1933691443
+3754 290983272
+3755 1279124639
+3756 1167618024
+3757 1269825636
+3758 1978923173
+3759 30468791
+3760 224685409
+3761 383275382
+3762 1623886675
+3763 1395378595
+3764 1828066419
+3765 522333211
+3766 1323549892
+3767 1009166725
+3768 1987257402
+3769 1879485667
+3770 147022875
+3771 736324974
+3772 413146226
+3773 1650606436
+3774 1141698409
+3775 1365280016
+3776 816706381
+3777 2090771662
+3778 1794761151
+3779 766540790
+3780 519487309
+3781 1122141190
+3782 847767934
+3783 476816424
+3784 908348985
+3785 1138751206
+3786 1755941063
+3787 2075967009
+3788 261093194
+3789 1587380588
+3790 2106435801
+3791 485778604
+3792 1970655971
+3793 1582838828
+3794 1881157199
+3795 1651238742
+3796 2105172039
+3797 1057223443
+3798 512921819
+3799 1944945793
+3800 789225462
+3801 659944694
+3802 533787119
+3803 1202371689
+3804 163067483
+3805 1675485529
+3806 420168057
+3807 979773864
+3808 1618773543
+3809 67445560
+3810 1746314654
+3811 2138260852
+3812 1189586750
+3813 446598940
+3814 467593628
+3815 2097935736
+3816 1585350146
+3817 76051043
+3818 2026419097
+3819 1846443341
+3820 1663431632
+3821 1985371250
+3822 184738297
+3823 1486603955
+3824 1420726430
+3825 2065895496
+3826 990359049
+3827 1378414821
+3828 975635292
+3829 1503280868
+3830 1175876967
+3831 1764860754
+3832 15741915
+3833 1709664086
+3834 819748795
+3835 178809398
+3836 1237665967
+3837 1239916853
+3838 1158583262
+3839 708955863
+3840 1307362413
+3841 757414268
+3842 699733067
+3843 349465516
+3844 1204013208
+3845 1167326696
+3846 299917604
+3847 641879706
+3848 1243377739
+3849 178853053
+3850 340839399
+3851 759325723
+3852 16740656
+3853 525577696
+3854 98446030
+3855 1437467086
+3856 443989545
+3857 1088805079
+3858 668398260
+3859 1419624837
+3860 444602300
+3861 1844275227
+3862 1037001943
+3863 460344215
+3864 1406455665
+3865 1856750739
+3866 639153613
+3867 496637985
+3868 949183944
+3869 1797736875
+3870 1205593848
+3871 109062709
+3872 407667495
+3873 1905326915
+3874 458528225
+3875 1611680703
+3876 925169963
+3877 758445829
+3878 106076761
+3879 21064055
+3880 937298883
+3881 446916161
+3882 780389778
+3883 954039539
+3884 972493857
+3885 878835809
+3886 244022977
+3887 1416483402
+3888 1967640888
+3889 912421237
+3890 688624591
+3891 264759540
+3892 609212816
+3893 1725626535
+3894 725103755
+3895 2015668482
+3896 1434893626
+3897 1364257368
+3898 364822819
+3899 236593922
+3900 1014510595
+3901 1570416667
+3902 345656631
+3903 1422178090
+3904 1328259934
+3905 804184857
+3906 886375145
+3907 105946250
+3908 1562630686
+3909 992451907
+3910 127010305
+3911 352445921
+3912 1439368068
+3913 907400083
+3914 1306485460
+3915 264378277
+3916 1786235892
+3917 1550508438
+3918 1680861680
+3919 1606393133
+3920 315446027
+3921 222002623
+3922 1871152673
+3923 924658844
+3924 1947629158
+3925 448772781
+3926 792843678
+3927 1235039136
+3928 1813030149
+3929 1157666497
+3930 1471633058
+3931 680057097
+3932 580599516
+3933 1817289690
+3934 2102235187
+3935 1908859450
+3936 473990899
+3937 841126685
+3938 2014805700
+3939 2036621585
+3940 1833578592
+3941 2141816005
+3942 241583859
+3943 1125463012
+3944 901732441
+3945 1548069319
+3946 1389841289
+3947 540484685
+3948 951094109
+3949 923219321
+3950 2146877818
+3951 1266540137
+3952 1145221945
+3953 1870546844
+3954 43715333
+3955 945367455
+3956 171835977
+3957 836559011
+3958 32922944
+3959 1984866126
+3960 1994225508
+3961 1504556002
+3962 517439575
+3963 427341376
+3964 1174362044
+3965 472191115
+3966 188717178
+3967 1648352943
+3968 1313317800
+3969 56039231
+3970 1537490881
+3971 999412744
+3972 50371588
+3973 1779074740
+3974 2124875756
+3975 952104029
+3976 1179660411
+3977 1367233397
+3978 1492588715
+3979 2130754521
+3980 142969071
+3981 1491982885
+3982 1249811010
+3983 1288191016
+3984 1215046081
+3985 1293526343
+3986 86074823
+3987 1386882058
+3988 2130085354
+3989 118997767
+3990 1224264537
+3991 1976827214
+3992 1623553770
+3993 1741704112
+3994 256684942
+3995 650432166
+3996 66411579
+3997 445402120
+3998 151301462
+3999 1379729379
+4000 501441351
+4001 1688792343
+4002 231658475
+4003 551812940
+4004 1320383435
+4005 209050583
+4006 1503916969
+4007 352560198
+4008 1576283981
+4009 849022036
+4010 335831071
+4011 1719253052
+4012 193521274
+4013 1585642081
+4014 859960420
+4015 1408567355
+4016 731684776
+4017 946035243
+4018 647965766
+4019 714286482
+4020 1065033011
+4021 1872230303
+4022 543630048
+4023 541103133
+4024 1466450767
+4025 800314990
+4026 1191535299
+4027 1532862347
+4028 1245717111
+4029 1342836761
+4030 765108078
+4031 1747158462
+4032 884145456
+4033 996766554
+4034 151487754
+4035 57045243
+4036 1205817137
+4037 1655404724
+4038 409605442
+4039 634617470
+4040 356943112
+4041 745436513
+4042 206386874
+4043 550464386
+4044 183594947
+4045 1066347294
+4046 1959031742
+4047 915279723
+4048 2012382538
+4049 459513860
+4050 1629566206
+4051 929931901
+4052 184260515
+4053 25712606
+4054 1471035034
+4055 1650711282
+4056 826027597
+4057 515086685
+4058 1036089981
+4059 2071744708
+4060 1857923447
+4061 1801198060
+4062 1671419522
+4063 594585255
+4064 650480966
+4065 1822907277
+4066 651630499
+4067 1856298103
+4068 1330828353
+4069 1061235941
+4070 343431926
+4071 1687771465
+4072 1806672454
+4073 549818800
+4074 90752204
+4075 1990267401
+4076 1616166095
+4077 2049783946
+4078 758063477
+4079 1481064985
+4080 361814158
+4081 240146035
+4082 263513238
+4083 546074673
+4084 265858641
+4085 1734548272
+4086 49302307
+4087 1091886238
+4088 102151309
+4089 1085392289
+4090 1016147298
+4091 1960074756
+4092 739106701
+4093 540083173
+4094 407176364
+4095 1389587667
+4096 215506802
+4097 1058806863
+4098 1098402122
+4099 1546335155
+4100 2120042804
+4101 1441834048
+4102 1086622972
+4103 1779231610
+4104 1991652849
+4105 1177375176
+4106 1622015364
+4107 1460335296
+4108 1079675474
+4109 232595193
+4110 793916633
+4111 1441489632
+4112 472741228
+4113 1057429871
+4114 1987564305
+4115 738599869
+4116 644494495
+4117 2036866613
+4118 1830486108
+4119 746645804
+4120 974775254
+4121 699149758
+4122 559236913
+4123 1713881955
+4124 1239232931
+4125 966413277
+4126 955985974
+4127 1454739733
+4128 2025220140
+4129 2054388096
+4130 853591240
+4131 1997779296
+4132 1348738497
+4133 1940214213
+4134 1629527258
+4135 1192907698
+4136 970105741
+4137 1104058974
+4138 505759346
+4139 2049781216
+4140 1336654167
+4141 1299675979
+4142 1343787200
+4143 1809395395
+4144 209622202
+4145 1183867858
+4146 400511617
+4147 854116697
+4148 1073250823
+4149 83514077
+4150 1600762501
+4151 2048026077
+4152 782663835
+4153 12515766
+4154 1614424384
+4155 2021896767
+4156 978929043
+4157 422926710
+4158 1329152852
+4159 856665535
+4160 329831158
+4161 35260445
+4162 706961183
+4163 1678569655
+4164 1975474658
+4165 189004794
+4166 723993705
+4167 798096751
+4168 1293063768
+4169 1229753051
+4170 700394319
+4171 482234288
+4172 381945382
+4173 2044181520
+4174 144146035
+4175 591567584
+4176 1080565730
+4177 544657652
+4178 1445684281
+4179 6332905
+4180 628171729
+4181 898963135
+4182 2054358982
+4183 1410835565
+4184 911478901
+4185 1521299718
+4186 1285248684
+4187 1890407945
+4188 1944226428
+4189 466917888
+4190 599589832
+4191 126573938
+4192 502178333
+4193 1306551016
+4194 1805143594
+4195 330169343
+4196 1495555810
+4197 381653651
+4198 1128266095
+4199 641135930
+4200 1611406703
+4201 1828660414
+4202 1123370218
+4203 1993352085
+4204 1725358286
+4205 1267516254
+4206 437436022
+4207 658440368
+4208 1812173906
+4209 1883120303
+4210 664773273
+4211 292861988
+4212 634599790
+4213 571648607
+4214 1703697553
+4215 1546078692
+4216 2092948325
+4217 841462589
+4218 1289002989
+4219 1889691105
+4220 1308380477
+4221 1888592821
+4222 2016265044
+4223 1810558811
+4224 1047660189
+4225 1673924990
+4226 2140728154
+4227 395732351
+4228 2055578641
+4229 1121510601
+4230 1036868282
+4231 1519501696
+4232 802687368
+4233 12754852
+4234 1365370134
+4235 380562006
+4236 1280271106
+4237 1802806156
+4238 1039002375
+4239 944961365
+4240 1538442811
+4241 1703775648
+4242 1237823353
+4243 25558954
+4244 127940608
+4245 794037258
+4246 1571637646
+4247 73405285
+4248 1635499847
+4249 713156987
+4250 1963096391
+4251 796396676
+4252 454266160
+4253 1831877787
+4254 459471839
+4255 1501926350
+4256 1358319129
+4257 452716346
+4258 1897658701
+4259 1266414122
+4260 1574226947
+4261 787043335
+4262 638432171
+4263 229430667
+4264 799798188
+4265 2003802305
+4266 609992674
+4267 2080069294
+4268 1659124813
+4269 1648995049
+4270 877547011
+4271 1050083976
+4272 1205287049
+4273 2115370364
+4274 1075642930
+4275 1333227657
+4276 761923974
+4277 499796928
+4278 1406632943
+4279 249940173
+4280 1212953915
+4281 1222245686
+4282 1046336850
+4283 1667220076
+4284 906639825
+4285 1505808689
+4286 1021662778
+4287 117475306
+4288 1958525035
+4289 771837831
+4290 1383889428
+4291 1385268335
+4292 1558881167
+4293 2022321599
+4294 1614699002
+4295 211195707
+4296 1878640256
+4297 77208028
+4298 143781353
+4299 1390281421
+4300 1726203077
+4301 1021328365
+4302 292881750
+4303 784006479
+4304 989215081
+4305 1368524680
+4306 2117234136
+4307 1751139056
+4308 1868321609
+4309 1376383431
+4310 2001079229
+4311 933791876
+4312 451145469
+4313 899932431
+4314 453528304
+4315 1357785294
+4316 258257473
+4317 1475191082
+4318 1475260600
+4319 69298860
+4320 99545266
+4321 711666381
+4322 1454567195
+4323 1658426433
+4324 586504332
+4325 921782550
+4326 1869622140
+4327 317660941
+4328 998990578
+4329 2013403493
+4330 1707942362
+4331 577710008
+4332 887248210
+4333 2000824112
+4334 1361716487
+4335 1876463292
+4336 1221865145
+4337 1331466975
+4338 1480118700
+4339 942703106
+4340 560366759
+4341 1333714281
+4342 1876494982
+4343 1011512228
+4344 86163065
+4345 182539639
+4346 221813875
+4347 344420538
+4348 1657730721
+4349 1697074475
+4350 413719398
+4351 1757275987
+4352 261257208
+4353 1868286594
+4354 1268218772
+4355 847761541
+4356 642585496
+4357 990357264
+4358 1165422482
+4359 1641576074
+4360 856277110
+4361 725881196
+4362 71802434
+4363 1743525320
+4364 579221661
+4365 1433518921
+4366 1472504964
+4367 1801086806
+4368 617502249
+4369 805140016
+4370 596306264
+4371 1177869008
+4372 2138854298
+4373 325317598
+4374 41897588
+4375 77533715
+4376 507857237
+4377 263711463
+4378 421954253
+4379 18104311
+4380 1960785939
+4381 835673651
+4382 1775380298
+4383 74559499
+4384 556476597
+4385 896115423
+4386 922321040
+4387 1199062093
+4388 1886472687
+4389 2087743522
+4390 693154520
+4391 595266149
+4392 666141071
+4393 764956954
+4394 191307822
+4395 1245362732
+4396 50992228
+4397 1663812786
+4398 898965890
+4399 668494477
+4400 321469155
+4401 1495272154
+4402 1846363485
+4403 312839805
+4404 1820589752
+4405 1888261073
+4406 390373520
+4407 180963342
+4408 4488889
+4409 812327773
+4410 199067653
+4411 1965274828
+4412 1648001424
+4413 1974447951
+4414 2039834327
+4415 56994374
+4416 723079726
+4417 814671720
+4418 1256056467
+4419 462068766
+4420 754931594
+4421 1949210987
+4422 1057334915
+4423 1421072665
+4424 566684294
+4425 1248642737
+4426 518951749
+4427 617676522
+4428 764971876
+4429 1417917639
+4430 1286170999
+4431 1086441031
+4432 765706145
+4433 985050836
+4434 1399280836
+4435 438812250
+4436 725828261
+4437 1789654356
+4438 619775592
+4439 730317150
+4440 454498481
+4441 818843245
+4442 548108330
+4443 2102499905
+4444 645807548
+4445 440459010
+4446 12010631
+4447 1368887275
+4448 1255130730
+4449 1268067099
+4450 1830956041
+4451 2010062324
+4452 1069794438
+4453 740807308
+4454 1283651342
+4455 1636478732
+4456 1989450046
+4457 1802603091
+4458 106671606
+4459 606938274
+4460 1073037083
+4461 1392842605
+4462 1693379305
+4463 1838743228
+4464 230409793
+4465 945176493
+4466 130071830
+4467 956238055
+4468 587347201
+4469 749847422
+4470 1686555205
+4471 1041845682
+4472 1568690667
+4473 87179888
+4474 996861939
+4475 67014568
+4476 527638898
+4477 1008872571
+4478 1435901843
+4479 1782769628
+4480 129456022
+4481 1119374236
+4482 1645348304
+4483 1199250460
+4484 1860181544
+4485 781515998
+4486 688245545
+4487 1702147942
+4488 436635442
+4489 794917151
+4490 161602568
+4491 1509672525
+4492 40276109
+4493 1854981873
+4494 1200932105
+4495 270685902
+4496 652674718
+4497 1331003936
+4498 1226923957
+4499 1240021919
+4500 2080851358
+4501 765995515
+4502 134383953
+4503 1502058378
+4504 853175403
+4505 1131245893
+4506 1569072946
+4507 1380814301
+4508 2140118464
+4509 857491141
+4510 1016100281
+4511 122090838
+4512 1976865377
+4513 513964937
+4514 1321341298
+4515 1689563273
+4516 1295480936
+4517 2009586843
+4518 1244227568
+4519 1732116378
+4520 657020347
+4521 1405830136
+4522 1094305255
+4523 697296456
+4524 1113328362
+4525 147753712
+4526 967982358
+4527 1766003080
+4528 1478757648
+4529 47422668
+4530 858541352
+4531 1412125359
+4532 813418183
+4533 992925305
+4534 766700089
+4535 1666593586
+4536 2124171198
+4537 188289387
+4538 899924239
+4539 2116806014
+4540 1045780528
+4541 1916024520
+4542 91413204
+4543 875162257
+4544 282505809
+4545 1412754503
+4546 417241882
+4547 1577986745
+4548 1274857698
+4549 1661469450
+4550 1162619475
+4551 1931878045
+4552 919815939
+4553 109441082
+4554 481690853
+4555 2033144301
+4556 257194795
+4557 1449673212
+4558 1651663733
+4559 1735952443
+4560 1497095880
+4561 362721437
+4562 1000594154
+4563 163030415
+4564 1355646743
+4565 1767294243
+4566 1829624001
+4567 1332334293
+4568 1955583630
+4569 582064592
+4570 1301656660
+4571 853880510
+4572 350605464
+4573 1393069864
+4574 1729042767
+4575 633111273
+4576 658340719
+4577 2146284650
+4578 63614371
+4579 1933198418
+4580 1660270452
+4581 1226233846
+4582 1717592815
+4583 432602743
+4584 1335674929
+4585 51800021
+4586 318263396
+4587 1592869724
+4588 1501473233
+4589 1969927130
+4590 1181338519
+4591 851085465
+4592 185164919
+4593 34449026
+4594 1014115880
+4595 1540811662
+4596 1801743269
+4597 696256233
+4598 725662308
+4599 1609843252
+4600 1278320825
+4601 2027318968
+4602 316240114
+4603 1628926289
+4604 1272905184
+4605 2045282882
+4606 114553914
+4607 1931245904
+4608 2044083884
+4609 178168285
+4610 1716960674
+4611 1556870688
+4612 1404402132
+4613 1287069841
+4614 1989473432
+4615 592593413
+4616 1338869862
+4617 160253180
+4618 37979489
+4619 692859447
+4620 2130180310
+4621 1219318008
+4622 1543944912
+4623 167861582
+4624 1253767034
+4625 410577144
+4626 1708673244
+4627 908026656
+4628 1106833377
+4629 286851904
+4630 370386260
+4631 237670554
+4632 166687224
+4633 686626374
+4634 1866596843
+4635 1439592409
+4636 584425608
+4637 1981150758
+4638 1223354665
+4639 481025844
+4640 11835395
+4641 792831691
+4642 2037896533
+4643 1416237527
+4644 2079901532
+4645 1879886317
+4646 2008830940
+4647 1271287747
+4648 2040139497
+4649 2046810429
+4650 1964147194
+4651 2022836160
+4652 1118644790
+4653 1360608459
+4654 43214094
+4655 224928176
+4656 1771185603
+4657 1751887338
+4658 1132954832
+4659 730535333
+4660 2038739243
+4661 1503341092
+4662 968205887
+4663 57942819
+4664 42483819
+4665 687319083
+4666 1497535228
+4667 626909427
+4668 520986193
+4669 573406245
+4670 1107935272
+4671 532821588
+4672 1366237936
+4673 998348157
+4674 1949059116
+4675 1298655821
+4676 730750826
+4677 1810406408
+4678 422459920
+4679 623406675
+4680 1709733190
+4681 239123466
+4682 498759187
+4683 680894332
+4684 1599731925
+4685 541973281
+4686 905822508
+4687 1223433881
+4688 146376972
+4689 2038777341
+4690 1953969214
+4691 37632567
+4692 1394634785
+4693 774691453
+4694 95575386
+4695 1437118604
+4696 1462010536
+4697 1593110615
+4698 2064028032
+4699 1982996729
+4700 19033212
+4701 1024479656
+4702 368334670
+4703 1385271149
+4704 2022827813
+4705 169910138
+4706 536443322
+4707 606094991
+4708 1980316546
+4709 958903242
+4710 1229501666
+4711 1542566088
+4712 1198026708
+4713 1728260854
+4714 75976772
+4715 650274986
+4716 122750487
+4717 981799281
+4718 1873708867
+4719 269127459
+4720 873092974
+4721 1680194433
+4722 306760026
+4723 120244111
+4724 307402238
+4725 402335413
+4726 1557362716
+4727 1769412775
+4728 1995446028
+4729 1473907100
+4730 1604925856
+4731 2014479240
+4732 350903108
+4733 1973260526
+4734 1252266741
+4735 226247273
+4736 2143170664
+4737 1788710063
+4738 832342264
+4739 1976003563
+4740 600129657
+4741 2061843930
+4742 1371086003
+4743 1798156366
+4744 1642621136
+4745 1447062776
+4746 300947704
+4747 1765371624
+4748 281378409
+4749 27172923
+4750 2034499083
+4751 1154471383
+4752 1707367356
+4753 193775462
+4754 1274715494
+4755 2014769594
+4756 596110875
+4757 684594562
+4758 1636698721
+4759 444073255
+4760 11018014
+4761 1094140930
+4762 311068847
+4763 361921122
+4764 919917808
+4765 1563335589
+4766 588168395
+4767 915604825
+4768 1204562004
+4769 1420510659
+4770 744124740
+4771 1804691662
+4772 1334870942
+4773 2115210743
+4774 1455364380
+4775 830008430
+4776 1414789871
+4777 1756312084
+4778 447896406
+4779 1696168280
+4780 1783485007
+4781 334911842
+4782 703156015
+4783 1343368715
+4784 528687304
+4785 1977871510
+4786 1210654661
+4787 1124798179
+4788 514982424
+4789 699869735
+4790 1568871434
+4791 526000439
+4792 1794010665
+4793 1879940281
+4794 887921561
+4795 566444825
+4796 1295792222
+4797 1476089957
+4798 1482049650
+4799 352870579
+4800 749116968
+4801 78690742
+4802 10078593
+4803 2083987910
+4804 46417838
+4805 1465442973
+4806 766512693
+4807 1461207709
+4808 1074271409
+4809 1214409099
+4810 1009892342
+4811 710272768
+4812 1549320941
+4813 1713048357
+4814 2053641483
+4815 2078008245
+4816 1543436219
+4817 1116812496
+4818 1055322776
+4819 2058418644
+4820 1816682231
+4821 476710562
+4822 436935435
+4823 1463209248
+4824 209167196
+4825 1324856996
+4826 2029654074
+4827 1504959418
+4828 653463305
+4829 1364220076
+4830 1857829997
+4831 1402580274
+4832 1442910819
+4833 1867908590
+4834 1339084536
+4835 1489328657
+4836 1185867915
+4837 2105597229
+4838 803052718
+4839 112655676
+4840 1172522681
+4841 1812945060
+4842 822928444
+4843 574359974
+4844 1378509770
+4845 729086279
+4846 504884572
+4847 774462341
+4848 1845898776
+4849 1560207348
+4850 685397337
+4851 1515097359
+4852 2036917911
+4853 1122332772
+4854 830822960
+4855 98601459
+4856 299706121
+4857 712993386
+4858 1603560877
+4859 953169426
+4860 2077213462
+4861 1313907227
+4862 208266052
+4863 1372640633
+4864 1034332169
+4865 1547350589
+4866 714485642
+4867 72716437
+4868 1505464170
+4869 1517538361
+4870 185372113
+4871 530503203
+4872 1182999773
+4873 1008300558
+4874 1104863178
+4875 414025895
+4876 1737386837
+4877 1609747750
+4878 1188488237
+4879 1435801965
+4880 1022471450
+4881 1873885574
+4882 803415677
+4883 911905713
+4884 848734699
+4885 1634238637
+4886 1010507172
+4887 1148440820
+4888 199748375
+4889 466584402
+4890 2101610246
+4891 129478189
+4892 1780491629
+4893 162392651
+4894 1502118823
+4895 667340150
+4896 1709743240
+4897 69120817
+4898 740056587
+4899 1067723762
+4900 1586659178
+4901 925428701
+4902 1598226966
+4903 622175304
+4904 1933729259
+4905 555606496
+4906 1036201199
+4907 1523632448
+4908 17870598
+4909 77205788
+4910 811950766
+4911 1040342048
+4912 1951091363
+4913 1615366443
+4914 1952247762
+4915 652342414
+4916 1102121432
+4917 815271286
+4918 1800783234
+4919 1301869807
+4920 1281855688
+4921 1754909832
+4922 1431347996
+4923 914863669
+4924 1917302483
+4925 785983171
+4926 1582203820
+4927 1479562075
+4928 855103989
+4929 174776759
+4930 399802190
+4931 294279519
+4932 1100205460
+4933 1998029156
+4934 916454823
+4935 886451071
+4936 406152004
+4937 1952656023
+4938 262599872
+4939 424022602
+4940 2029861811
+4941 1074550638
+4942 1464364650
+4943 1833469526
+4944 542433433
+4945 1269128764
+4946 338328292
+4947 1644554865
+4948 2084400051
+4949 2139111526
+4950 798941024
+4951 1218772091
+4952 1746537711
+4953 82805372
+4954 2133635761
+4955 1516356546
+4956 868788544
+4957 1568355933
+4958 848434974
+4959 1723892533
+4960 1743132692
+4961 1248237164
+4962 2018172052
+4963 695854505
+4964 1098782672
+4965 787143228
+4966 1582305576
+4967 1504934676
+4968 592315603
+4969 1844905448
+4970 1928957278
+4971 474693766
+4972 771972438
+4973 1245838280
+4974 160679645
+4975 1314405871
+4976 367483397
+4977 499007937
+4978 811477088
+4979 304399800
+4980 490635816
+4981 1610418112
+4982 1523171891
+4983 89689879
+4984 1693223485
+4985 1509324004
+4986 1606046425
+4987 414528381
+4988 930196289
+4989 306997751
+4990 2138420914
+4991 525845334
+4992 1555234915
+4993 2009109318
+4994 1221699839
+4995 506533939
+4996 648768898
+4997 656521767
+4998 2011468615
+4999 1241084501
+5000 353943568
+5001 1792942245
+5002 1715778268
+5003 1125916006
+5004 891296878
+5005 1876457913
+5006 292838230
+5007 1258780275
+5008 227982202
+5009 1104315318
+5010 1563180075
+5011 718618018
+5012 567249783
+5013 938868318
+5014 808307897
+5015 112989620
+5016 300708675
+5017 266870675
+5018 527518001
+5019 1230904964
+5020 573868426
+5021 518455267
+5022 1756750298
+5023 2129103342
+5024 380080937
+5025 830966489
+5026 488153633
+5027 1028849836
+5028 1487488257
+5029 352138601
+5030 122450689
+5031 1841431825
+5032 2145080846
+5033 1838228957
+5034 819864183
+5035 888894076
+5036 1567203222
+5037 1112702413
+5038 190703
+5039 1795185425
+5040 69534084
+5041 1563370778
+5042 366319795
+5043 636783867
+5044 354755449
+5045 1174627693
+5046 749773487
+5047 655464124
+5048 1441498368
+5049 1277291488
+5050 1886369088
+5051 2015366794
+5052 1795746755
+5053 1495635739
+5054 1996986488
+5055 28344044
+5056 179118580
+5057 337656474
+5058 1057193880
+5059 1666606837
+5060 689795075
+5061 1179644570
+5062 1360555014
+5063 687392273
+5064 870389879
+5065 32935550
+5066 1576286350
+5067 290109454
+5068 1145637963
+5069 1576477053
+5070 2085294879
+5071 1215172047
+5072 992364184
+5073 304131026
+5074 1851955914
+5075 1347119633
+5076 1478758719
+5077 454245753
+5078 2002583757
+5079 772773439
+5080 1731537241
+5081 1741469197
+5082 640656586
+5083 1379800348
+5084 1089621288
+5085 490159426
+5086 1408144393
+5087 1268739869
+5088 827815900
+5089 317854625
+5090 787863058
+5091 1517610975
+5092 1497499195
+5093 934425
+5094 57519601
+5095 220405427
+5096 33869975
+5097 1633805951
+5098 510514881
+5099 1179507938
+5100 1062799356
+5101 448326112
+5102 247196338
+5103 2055163540
+5104 752457138
+5105 2099152252
+5106 1254799525
+5107 83732210
+5108 405914358
+5109 1109899634
+5110 856505649
+5111 2137451599
+5112 703885184
+5113 1497162235
+5114 1369768300
+5115 1793506472
+5116 1987321662
+5117 630429045
+5118 914762693
+5119 667653914
+5120 948283670
+5121 1702625752
+5122 37781242
+5123 298299218
+5124 1703560177
+5125 95300843
+5126 518704645
+5127 1737430152
+5128 1729106794
+5129 1029219526
+5130 769454442
+5131 644422502
+5132 1477545638
+5133 1016650780
+5134 552102395
+5135 82519128
+5136 968319385
+5137 1806901920
+5138 166251338
+5139 1374233743
+5140 769317907
+5141 1022756988
+5142 1364201694
+5143 1473203091
+5144 372435575
+5145 586486346
+5146 1119225915
+5147 212273589
+5148 1216915391
+5149 2033988609
+5150 879927504
+5151 17715414
+5152 1589130713
+5153 917708746
+5154 316014632
+5155 1145207242
+5156 1013009589
+5157 834719277
+5158 735153746
+5159 594632735
+5160 1863938803
+5161 1504608188
+5162 1239055237
+5163 1194000793
+5164 373775321
+5165 1791157632
+5166 1276519921
+5167 1342094706
+5168 1450575905
+5169 1442771260
+5170 568844801
+5171 72410164
+5172 318044600
+5173 1933046495
+5174 1545613255
+5175 690480175
+5176 372049194
+5177 517355522
+5178 902753765
+5179 1588964585
+5180 403860483
+5181 1782681269
+5182 1606679999
+5183 1992991196
+5184 552906367
+5185 1922694631
+5186 990714790
+5187 1565915956
+5188 609930260
+5189 1725868536
+5190 13065043
+5191 326385415
+5192 1082993077
+5193 1252120280
+5194 1520386208
+5195 1456768398
+5196 895794265
+5197 649422482
+5198 651379456
+5199 198886522
+5200 2092193742
+5201 1220224257
+5202 271296686
+5203 262754694
+5204 1005787104
+5205 1816909941
+5206 953234869
+5207 1377836298
+5208 186781815
+5209 1855988634
+5210 819317236
+5211 590642299
+5212 1491186255
+5213 278513587
+5214 436149847
+5215 2044092622
+5216 53724571
+5217 1426864638
+5218 1462524930
+5219 663654831
+5220 1005249526
+5221 1475589973
+5222 990040247
+5223 2088242603
+5224 580226606
+5225 362942807
+5226 1397527353
+5227 1476020871
+5228 1012365289
+5229 2048906809
+5230 1674907393
+5231 957075383
+5232 1121647418
+5233 1946204079
+5234 1219830077
+5235 2127434523
+5236 1615630372
+5237 25581299
+5238 1357787173
+5239 1802412187
+5240 1881569933
+5241 29620761
+5242 245570838
+5243 1225272541
+5244 308134349
+5245 681720686
+5246 1121881515
+5247 361858920
+5248 2108585324
+5249 436922798
+5250 1025513751
+5251 966351202
+5252 1912512771
+5253 2015553998
+5254 907110158
+5255 345255729
+5256 231013158
+5257 157153863
+5258 1821276600
+5259 1243378447
+5260 58577025
+5261 1348700345
+5262 52970183
+5263 1180224443
+5264 1147420776
+5265 1272800260
+5266 1160175318
+5267 615567500
+5268 1298381559
+5269 370478844
+5270 270496040
+5271 1032467845
+5272 400099605
+5273 516066878
+5274 110256738
+5275 708233954
+5276 1197787564
+5277 1232138253
+5278 1070092874
+5279 1158889240
+5280 1669061051
+5281 2095606626
+5282 2125240443
+5283 1434090175
+5284 1963676976
+5285 884866953
+5286 1779345904
+5287 47206486
+5288 1042020816
+5289 1453138857
+5290 1290584934
+5291 1100597841
+5292 654355554
+5293 1343555117
+5294 133338637
+5295 1801776331
+5296 468871729
+5297 1293513955
+5298 269860183
+5299 1767253289
+5300 1663992799
+5301 540356223
+5302 652237486
+5303 2064092405
+5304 1056423102
+5305 762494224
+5306 624842711
+5307 106727018
+5308 1994632477
+5309 1694935586
+5310 1265616259
+5311 1516209881
+5312 1643058564
+5313 1243373054
+5314 802816408
+5315 1459251892
+5316 2128240007
+5317 434678664
+5318 1506458379
+5319 1022777175
+5320 1887817521
+5321 649559665
+5322 2123375017
+5323 394689428
+5324 1993114782
+5325 109230006
+5326 48982111
+5327 314502863
+5328 1402743961
+5329 318842294
+5330 2081756152
+5331 919253113
+5332 859198518
+5333 586509990
+5334 835861870
+5335 1915621620
+5336 1349004214
+5337 1460704581
+5338 2022348638
+5339 1196153044
+5340 1008156519
+5341 1140481249
+5342 564879277
+5343 503731435
+5344 236370655
+5345 1367695685
+5346 1962983328
+5347 217127014
+5348 1802374349
+5349 1321958059
+5350 1239904190
+5351 1542708223
+5352 1971517724
+5353 1215795559
+5354 1937397651
+5355 1817148858
+5356 1325025565
+5357 1986379762
+5358 2131651721
+5359 580285878
+5360 157738408
+5361 2065924226
+5362 1499538991
+5363 1016936926
+5364 504950568
+5365 187917213
+5366 785074898
+5367 1853954783
+5368 1648621795
+5369 659939889
+5370 902624179
+5371 509294666
+5372 1800421138
+5373 1467503456
+5374 1013026102
+5375 2036791794
+5376 687715493
+5377 828525782
+5378 106435160
+5379 342606194
+5380 3000193
+5381 1346339350
+5382 1885314417
+5383 1974517917
+5384 414651261
+5385 1675228420
+5386 1644183127
+5387 1739676826
+5388 1514124534
+5389 1628351200
+5390 172479057
+5391 1671862943
+5392 1546791778
+5393 1672018048
+5394 541316221
+5395 2051742347
+5396 1859935262
+5397 1326391120
+5398 1758213482
+5399 1361073409
+5400 1986331009
+5401 513354013
+5402 1870368075
+5403 1639268499
+5404 1980857469
+5405 735910529
+5406 1528576645
+5407 521089314
+5408 1564436311
+5409 1635011806
+5410 863695508
+5411 1567436504
+5412 833867508
+5413 601526278
+5414 1394470773
+5415 1248518770
+5416 129271050
+5417 891170252
+5418 840711948
+5419 1643395585
+5420 372037805
+5421 1013191005
+5422 1167774880
+5423 1918829583
+5424 537725406
+5425 1709091101
+5426 1823088282
+5427 250177020
+5428 887998573
+5429 1433818116
+5430 1611250429
+5431 726845934
+5432 1947172129
+5433 1334134856
+5434 218630786
+5435 1780545950
+5436 2070045386
+5437 1747207431
+5438 154151616
+5439 1486998049
+5440 1234735589
+5441 1017847125
+5442 906950906
+5443 2068603098
+5444 1619373403
+5445 153938031
+5446 1169638220
+5447 1748644453
+5448 1045108284
+5449 2010350168
+5450 1244556390
+5451 1417146089
+5452 876057526
+5453 264847622
+5454 1188492024
+5455 1413782932
+5456 1973938724
+5457 864096659
+5458 1663959952
+5459 714453649
+5460 150431127
+5461 1127726733
+5462 1441299584
+5463 2097603257
+5464 314377941
+5465 1659930370
+5466 1730665559
+5467 236939679
+5468 1259654153
+5469 1884817176
+5470 1723937729
+5471 346906095
+5472 755180653
+5473 483404987
+5474 268025545
+5475 227070408
+5476 637343018
+5477 1437663765
+5478 1975714861
+5479 1682451302
+5480 1300530285
+5481 1072787604
+5482 952113743
+5483 29104163
+5484 1337635226
+5485 2140605768
+5486 1442887095
+5487 1164090302
+5488 857218779
+5489 959363399
+5490 1878543952
+5491 1007649906
+5492 2087090132
+5493 1172359888
+5494 957769515
+5495 253984426
+5496 684806610
+5497 540951427
+5498 490924105
+5499 1944460763
+5500 278284955
+5501 67378186
+5502 143883210
+5503 1033465608
+5504 550783173
+5505 411908755
+5506 1260536016
+5507 1188126192
+5508 1849572520
+5509 1088767229
+5510 723093846
+5511 1002619158
+5512 14071185
+5513 1675207590
+5514 1031723321
+5515 1351706412
+5516 1668329710
+5517 327126769
+5518 368313066
+5519 378064841
+5520 1286490168
+5521 99373370
+5522 1385714747
+5523 1226096653
+5524 1271733258
+5525 196000615
+5526 1480081079
+5527 1956539868
+5528 736952042
+5529 1971005184
+5530 1753516984
+5531 1015236997
+5532 2038383371
+5533 1897400194
+5534 2048702605
+5535 441682896
+5536 161825302
+5537 1161754973
+5538 1629809088
+5539 2011397822
+5540 103038554
+5541 205419287
+5542 866533332
+5543 117109740
+5544 1880626877
+5545 1898256654
+5546 1468816152
+5547 1401472939
+5548 77899775
+5549 1837129218
+5550 1779537780
+5551 1364389943
+5552 1936502589
+5553 1017768879
+5554 443002948
+5555 1060752199
+5556 1213769494
+5557 1923084027
+5558 869808420
+5559 1950721536
+5560 1746605564
+5561 475841756
+5562 818474885
+5563 1637505287
+5564 225758302
+5565 719693842
+5566 2079188183
+5567 387583604
+5568 1881448815
+5569 1561513624
+5570 251497779
+5571 1984487370
+5572 1766932911
+5573 1118031111
+5574 2101597110
+5575 1500076140
+5576 868804117
+5577 1422929614
+5578 754065431
+5579 946703892
+5580 1112575184
+5581 386119563
+5582 163610188
+5583 901594125
+5584 1403888442
+5585 606613136
+5586 1962346325
+5587 470174289
+5588 382213516
+5589 684671097
+5590 273412177
+5591 2128819080
+5592 1160512853
+5593 1091887063
+5594 1618840719
+5595 1386271155
+5596 1811580905
+5597 1550545254
+5598 1773854760
+5599 1545546073
+5600 964575230
+5601 2025352539
+5602 1382549795
+5603 584024493
+5604 995900002
+5605 1336663257
+5606 2084100633
+5607 1864704120
+5608 612109223
+5609 690682416
+5610 663924364
+5611 1724684407
+5612 1076801979
+5613 827534552
+5614 478794885
+5615 333206774
+5616 1434147689
+5617 293657562
+5618 803381063
+5619 1816361205
+5620 978328659
+5621 1076793240
+5622 1797696637
+5623 2138841512
+5624 21196655
+5625 1269053708
+5626 1377629019
+5627 1832777561
+5628 672115314
+5629 1004000131
+5630 1230839986
+5631 1636690545
+5632 881869022
+5633 465906133
+5634 73231390
+5635 1877769025
+5636 1802569390
+5637 9848376
+5638 1594989497
+5639 267194965
+5640 700530792
+5641 111430213
+5642 1991879372
+5643 1777332772
+5644 938964766
+5645 323190609
+5646 2110539546
+5647 225628807
+5648 616848171
+5649 766436961
+5650 2041990012
+5651 1595176830
+5652 1843230201
+5653 1692203001
+5654 1586534694
+5655 1864426857
+5656 813773061
+5657 816680066
+5658 1549720770
+5659 1485888375
+5660 1820680197
+5661 633077108
+5662 975095272
+5663 555065572
+5664 1098983241
+5665 1048326663
+5666 285350949
+5667 754068983
+5668 1058175039
+5669 1880340446
+5670 1021263948
+5671 1758705831
+5672 1991770659
+5673 865659672
+5674 1388554955
+5675 783251777
+5676 1188850282
+5677 1351610853
+5678 1008880584
+5679 1805698453
+5680 2118047814
+5681 903386948
+5682 1253391636
+5683 1813794368
+5684 448106301
+5685 692442682
+5686 1530737577
+5687 1261879362
+5688 1509122748
+5689 932974699
+5690 600284090
+5691 1182319298
+5692 1566051807
+5693 1575379362
+5694 1737384870
+5695 517551400
+5696 476222377
+5697 2022735819
+5698 1271620383
+5699 1534397416
+5700 1755592617
+5701 145400683
+5702 1145619600
+5703 1599879628
+5704 1011060355
+5705 386690907
+5706 235647758
+5707 52426989
+5708 1738301761
+5709 1244528342
+5710 1858125443
+5711 1708865927
+5712 431643
+5713 964033431
+5714 1375176647
+5715 448537944
+5716 1656476113
+5717 758430576
+5718 1710417307
+5719 1018115214
+5720 1691405275
+5721 163217749
+5722 52950864
+5723 1109973434
+5724 1738597111
+5725 1790335734
+5726 1627524834
+5727 67335841
+5728 1665587905
+5729 751661569
+5730 1601733257
+5731 1273696874
+5732 897062252
+5733 599869209
+5734 726092854
+5735 1908122608
+5736 986560117
+5737 961740612
+5738 1960549597
+5739 577378230
+5740 58785307
+5741 1671191392
+5742 138760509
+5743 59216950
+5744 487741175
+5745 1513937157
+5746 507754894
+5747 2144217289
+5748 124884085
+5749 70688553
+5750 1014848855
+5751 1816289361
+5752 233906302
+5753 1067799719
+5754 778779147
+5755 1972503414
+5756 710651805
+5757 258820334
+5758 2039839255
+5759 228756062
+5760 1010481903
+5761 1494088864
+5762 1502452936
+5763 1907544156
+5764 2093958074
+5765 81062142
+5766 1668183116
+5767 933034543
+5768 1042802755
+5769 1481249065
+5770 1510412773
+5771 1101588062
+5772 1004956810
+5773 1649173282
+5774 1160805012
+5775 1492697985
+5776 1015626791
+5777 1668559906
+5778 1489431626
+5779 1140510877
+5780 1739248460
+5781 356796833
+5782 809316590
+5783 1973154762
+5784 1424596552
+5785 1588095737
+5786 1798174528
+5787 2135248357
+5788 1846916071
+5789 1690530135
+5790 216520771
+5791 709914327
+5792 1037135352
+5793 1718973707
+5794 469974835
+5795 983609778
+5796 1800035850
+5797 2138157951
+5798 1916644321
+5799 695354957
+5800 1471923368
+5801 1279573446
+5802 1796943019
+5803 329396530
+5804 781263080
+5805 810264383
+5806 1822094516
+5807 1796889872
+5808 331340641
+5809 1164042494
+5810 789917101
+5811 2070589101
+5812 1520839328
+5813 1599233691
+5814 1896260216
+5815 797952232
+5816 1039845780
+5817 1546951096
+5818 785716942
+5819 739278204
+5820 1089997584
+5821 1002237713
+5822 1449192531
+5823 2127132936
+5824 573727773
+5825 1919167366
+5826 963259066
+5827 226279975
+5828 1909841669
+5829 732419739
+5830 921634932
+5831 1234281389
+5832 2011993185
+5833 571094303
+5834 1563677920
+5835 645772617
+5836 1381358686
+5837 1238288788
+5838 295178841
+5839 1712699327
+5840 254847634
+5841 1085095942
+5842 1635804781
+5843 1775686962
+5844 536845985
+5845 1384581349
+5846 426155547
+5847 1576691766
+5848 784048797
+5849 1211872489
+5850 168486322
+5851 1874046381
+5852 66626554
+5853 1617678853
+5854 1853695669
+5855 640354327
+5856 1389362571
+5857 669471087
+5858 866634302
+5859 1151720592
+5860 1401890826
+5861 1788269234
+5862 238518333
+5863 1266400363
+5864 211879889
+5865 1802196253
+5866 1912172981
+5867 1593238575
+5868 893001393
+5869 59868174
+5870 1158454255
+5871 1147849028
+5872 1144964117
+5873 646775388
+5874 776052342
+5875 1681810102
+5876 2031356737
+5877 1202207889
+5878 1111018220
+5879 667921886
+5880 266596730
+5881 1279504542
+5882 394484620
+5883 333223285
+5884 749699747
+5885 100696641
+5886 973577612
+5887 2139062318
+5888 770167729
+5889 1840211915
+5890 1143299262
+5891 24574907
+5892 1480997501
+5893 1381817596
+5894 1290975271
+5895 1692877391
+5896 1036530201
+5897 1055664604
+5898 1138632318
+5899 1929531595
+5900 1115532778
+5901 149602925
+5902 929896975
+5903 113013247
+5904 796378313
+5905 1705949317
+5906 1794823350
+5907 680251402
+5908 760673559
+5909 758357922
+5910 1348173289
+5911 1027270289
+5912 2037862465
+5913 1742657909
+5914 1360493574
+5915 640078564
+5916 1843354550
+5917 186587539
+5918 631657235
+5919 466038631
+5920 2026799454
+5921 1774956497
+5922 490613539
+5923 1360313307
+5924 1009290445
+5925 1781588810
+5926 905707050
+5927 2045820647
+5928 689769766
+5929 2044339369
+5930 1827868594
+5931 1805302544
+5932 46458646
+5933 610281921
+5934 1918315792
+5935 842836960
+5936 168747590
+5937 1565655494
+5938 1523088362
+5939 929421149
+5940 176529768
+5941 723778003
+5942 1956691439
+5943 66908585
+5944 318952264
+5945 1169701365
+5946 706987150
+5947 14823167
+5948 1356288904
+5949 1338644385
+5950 480861798
+5951 1235604710
+5952 966117234
+5953 971475337
+5954 448434370
+5955 1975407680
+5956 605580499
+5957 1354141420
+5958 1873744679
+5959 1295350265
+5960 1250997141
+5961 1554129625
+5962 953169162
+5963 1297455788
+5964 16927898
+5965 724001306
+5966 2140292748
+5967 185675488
+5968 142173152
+5969 1515897462
+5970 1115096638
+5971 318702920
+5972 92191818
+5973 924304429
+5974 385611506
+5975 411144082
+5976 2094005794
+5977 1092598656
+5978 425967249
+5979 1302811051
+5980 283759393
+5981 906829048
+5982 390932113
+5983 1249876627
+5984 1878304385
+5985 839366483
+5986 1077800659
+5987 336401237
+5988 46024256
+5989 804061690
+5990 1631751502
+5991 1297021397
+5992 210707667
+5993 437437016
+5994 446993537
+5995 227635565
+5996 1161438322
+5997 439802637
+5998 413311054
+5999 1303611474
+6000 1955700100
+6001 1528407692
+6002 1622314395
+6003 2047891918
+6004 305228473
+6005 2007925901
+6006 311552352
+6007 251750619
+6008 953040909
+6009 737519602
+6010 1554561670
+6011 1236800302
+6012 1644348650
+6013 1945493784
+6014 339193281
+6015 1375169387
+6016 637376619
+6017 1416993941
+6018 1711570624
+6019 683400875
+6020 73571983
+6021 1195838479
+6022 1980422273
+6023 284279651
+6024 1633275495
+6025 279932162
+6026 511915216
+6027 647230170
+6028 719734800
+6029 925226270
+6030 1950841644
+6031 527951252
+6032 306150314
+6033 1425672391
+6034 428359522
+6035 611378787
+6036 1286114644
+6037 739911874
+6038 863129407
+6039 91671905
+6040 1477431476
+6041 270207429
+6042 1328472207
+6043 974296478
+6044 68217565
+6045 1667665489
+6046 201982218
+6047 705594185
+6048 937175782
+6049 1913552842
+6050 1388995060
+6051 1010747765
+6052 961907673
+6053 1221933685
+6054 1295027416
+6055 447699521
+6056 1501865848
+6057 1806942633
+6058 1094929691
+6059 74117000
+6060 584685255
+6061 898287687
+6062 602068252
+6063 890835570
+6064 176476431
+6065 1030427774
+6066 1502214357
+6067 1462591075
+6068 1770339648
+6069 217860116
+6070 1554262981
+6071 1100287477
+6072 488067546
+6073 735251540
+6074 2074583955
+6075 556285111
+6076 255433381
+6077 129082525
+6078 1261879296
+6079 1192609163
+6080 2042635368
+6081 503390709
+6082 55873281
+6083 857059393
+6084 1725324394
+6085 1350900697
+6086 1304758914
+6087 1079706594
+6088 1010359682
+6089 252204957
+6090 1153823594
+6091 1595044938
+6092 1150492645
+6093 1755891846
+6094 338396860
+6095 1326969076
+6096 638835972
+6097 1840611217
+6098 642076503
+6099 261691973
+6100 2058471334
+6101 48855836
+6102 1361979450
+6103 399055232
+6104 784107377
+6105 1289079757
+6106 955340343
+6107 1039540758
+6108 1418162283
+6109 69735992
+6110 84666274
+6111 1313314003
+6112 573126701
+6113 140539555
+6114 22889748
+6115 150967447
+6116 1491440252
+6117 1327648663
+6118 1230674042
+6119 354316287
+6120 1579853620
+6121 237013988
+6122 1949361225
+6123 582862617
+6124 1992905835
+6125 140274437
+6126 1909831693
+6127 484258159
+6128 1980885654
+6129 404424549
+6130 745950132
+6131 1891873340
+6132 453280385
+6133 2107929582
+6134 143444924
+6135 1237387762
+6136 1249525692
+6137 1098785268
+6138 129444873
+6139 520204327
+6140 1168521260
+6141 214111147
+6142 1833518330
+6143 1741647961
+6144 354650702
+6145 1856408078
+6146 1892615408
+6147 1846090954
+6148 1036573093
+6149 975805802
+6150 52923593
+6151 468943066
+6152 1212819791
+6153 2002284818
+6154 1051805683
+6155 1058241978
+6156 2142559255
+6157 814153729
+6158 1542500137
+6159 1975961262
+6160 1218578278
+6161 140966622
+6162 1720350954
+6163 1671858663
+6164 101412556
+6165 1863795879
+6166 761762778
+6167 1350938248
+6168 815097499
+6169 891207651
+6170 1871142575
+6171 1983618759
+6172 1105318798
+6173 1557177257
+6174 1577783072
+6175 1459969500
+6176 1266101688
+6177 1322914832
+6178 1158576806
+6179 155191133
+6180 151236987
+6181 1211500400
+6182 624134199
+6183 1364056778
+6184 1066301570
+6185 1675939883
+6186 274815108
+6187 1061377178
+6188 342609964
+6189 1817315245
+6190 889854792
+6191 1561188242
+6192 1958281867
+6193 462722098
+6194 1085563257
+6195 2059694424
+6196 179034329
+6197 1847326035
+6198 1263149024
+6199 994131828
+6200 591050038
+6201 986807952
+6202 830266939
+6203 1696368836
+6204 396501561
+6205 260566363
+6206 1008854688
+6207 1662603249
+6208 1583481196
+6209 19947847
+6210 1817794383
+6211 1734718183
+6212 1231448247
+6213 294444934
+6214 951291313
+6215 150266169
+6216 1970384817
+6217 1226106421
+6218 1211643347
+6219 165511133
+6220 895938018
+6221 2101498139
+6222 1726699375
+6223 706736238
+6224 416736590
+6225 664778985
+6226 618947014
+6227 595770919
+6228 364621372
+6229 1882096038
+6230 1589902748
+6231 955671411
+6232 721420342
+6233 272686039
+6234 504556599
+6235 1117921904
+6236 533252403
+6237 1513411288
+6238 633041505
+6239 2116733599
+6240 1533359135
+6241 303352240
+6242 1703968134
+6243 617323734
+6244 597797175
+6245 507775799
+6246 767589903
+6247 420698344
+6248 1733882220
+6249 1979233251
+6250 586209478
+6251 482336590
+6252 1933247742
+6253 165425205
+6254 1189072828
+6255 202500684
+6256 830204190
+6257 1808019842
+6258 798271604
+6259 1194825563
+6260 1542632233
+6261 240690704
+6262 3013326
+6263 116568927
+6264 513376743
+6265 507569925
+6266 1234490831
+6267 1046629146
+6268 2020981213
+6269 1867532337
+6270 1015879097
+6271 1406856700
+6272 23400929
+6273 572363583
+6274 2024180434
+6275 621198104
+6276 1080139382
+6277 644286690
+6278 1041896449
+6279 666537954
+6280 476036293
+6281 1628105927
+6282 1148874545
+6283 261800387
+6284 1793531132
+6285 190463725
+6286 464301072
+6287 476251675
+6288 1998483568
+6289 1262572676
+6290 1671077238
+6291 1393632153
+6292 1503263380
+6293 1674090564
+6294 1510201080
+6295 2016640123
+6296 34176841
+6297 597208264
+6298 915785622
+6299 2055158055
+6300 317256953
+6301 1931664719
+6302 1314531107
+6303 340657882
+6304 356544655
+6305 1191227894
+6306 961855987
+6307 1436684037
+6308 1835514584
+6309 2003752436
+6310 2103221992
+6311 164067229
+6312 1484374715
+6313 1104612889
+6314 425867616
+6315 1130422199
+6316 1295076614
+6317 890168688
+6318 1606673874
+6319 1146076534
+6320 5257716
+6321 1130267464
+6322 392225039
+6323 1508521096
+6324 656874380
+6325 1902426120
+6326 1377677572
+6327 691051222
+6328 352150736
+6329 145979546
+6330 598725629
+6331 669407689
+6332 2077644265
+6333 1913256736
+6334 1010065571
+6335 286705272
+6336 957000982
+6337 1971921558
+6338 1723389310
+6339 645031918
+6340 1828190346
+6341 1679127654
+6342 809099147
+6343 1165081413
+6344 636256895
+6345 1234966764
+6346 148019965
+6347 1931333509
+6348 2125135452
+6349 1754693839
+6350 929926396
+6351 2130393169
+6352 737477656
+6353 1322151435
+6354 1491430617
+6355 1394352036
+6356 1077093907
+6357 721624541
+6358 2085403258
+6359 1429244643
+6360 867604087
+6361 536645239
+6362 2098652332
+6363 797764705
+6364 302418328
+6365 961234256
+6366 1084469977
+6367 1259419310
+6368 785672166
+6369 660375639
+6370 1904451229
+6371 466378865
+6372 192019645
+6373 566066728
+6374 1631460278
+6375 828276540
+6376 1801033492
+6377 1779480243
+6378 612126402
+6379 1778685297
+6380 1386690435
+6381 1542052798
+6382 1761594818
+6383 2124168091
+6384 716720585
+6385 1105541787
+6386 1371036479
+6387 1793814493
+6388 1827166329
+6389 1308956090
+6390 1075575488
+6391 547286768
+6392 1845601329
+6393 1026744173
+6394 1345051473
+6395 536009
+6396 1987978429
+6397 282037803
+6398 1259955320
+6399 626166947
+6400 942413442
+6401 1016922901
+6402 1092545812
+6403 1134433088
+6404 1582989629
+6405 576522443
+6406 1962709628
+6407 1236539474
+6408 208519038
+6409 427352382
+6410 867741123
+6411 1595209473
+6412 1969405180
+6413 481852293
+6414 1571893916
+6415 538642118
+6416 1587394080
+6417 795446748
+6418 184972963
+6419 1267076761
+6420 2104402838
+6421 1260548451
+6422 1814363530
+6423 1802520519
+6424 139808976
+6425 1011931355
+6426 1803056529
+6427 2127787405
+6428 1293969158
+6429 915528201
+6430 606470705
+6431 88898953
+6432 1932451102
+6433 1699016517
+6434 1223332041
+6435 1367957083
+6436 128055312
+6437 1038558021
+6438 457012909
+6439 336574351
+6440 1465910404
+6441 1324754032
+6442 1931783824
+6443 1287831936
+6444 1806606325
+6445 1356194093
+6446 1826474054
+6447 1246516758
+6448 4157193
+6449 2011447017
+6450 366109871
+6451 2108560031
+6452 1124511821
+6453 32989753
+6454 1763596902
+6455 1264320797
+6456 1044921109
+6457 1419169783
+6458 1244624555
+6459 191406619
+6460 187214336
+6461 1851095260
+6462 280305572
+6463 2119665438
+6464 1402628129
+6465 1503637613
+6466 1340138874
+6467 1530683442
+6468 394711987
+6469 1797151783
+6470 1867257793
+6471 1860622391
+6472 974422168
+6473 1651557969
+6474 1000970679
+6475 633544845
+6476 860268414
+6477 679961086
+6478 1880061603
+6479 864425607
+6480 543924455
+6481 98687827
+6482 825501990
+6483 1668436276
+6484 131677580
+6485 441615245
+6486 785273426
+6487 1176598689
+6488 1860785028
+6489 2029897981
+6490 1368005309
+6491 2047999365
+6492 1733509593
+6493 1648310881
+6494 2020181155
+6495 988654074
+6496 1004464847
+6497 1212836381
+6498 371853868
+6499 1399176834
+6500 862504517
+6501 91628013
+6502 1112315577
+6503 1836926685
+6504 1743185983
+6505 2113286256
+6506 322987882
+6507 455970749
+6508 645763694
+6509 55565838
+6510 1320396357
+6511 1189688150
+6512 154253665
+6513 2145898347
+6514 710640778
+6515 285931245
+6516 440029944
+6517 1495914204
+6518 1462529935
+6519 153331325
+6520 1378328537
+6521 683051596
+6522 53847042
+6523 964354482
+6524 183878829
+6525 2074028197
+6526 1953008557
+6527 1188343676
+6528 1139380931
+6529 177378777
+6530 440036862
+6531 2001885448
+6532 269006791
+6533 1552352439
+6534 1691328485
+6535 2012192774
+6536 1518155048
+6537 2014316367
+6538 320679875
+6539 16435094
+6540 2069882205
+6541 1641076232
+6542 1206123244
+6543 76652222
+6544 1639490932
+6545 1916764023
+6546 362583468
+6547 2079520876
+6548 1265194579
+6549 1825113403
+6550 85368553
+6551 496039469
+6552 360681351
+6553 139215595
+6554 1460393951
+6555 544560180
+6556 65760145
+6557 1265918860
+6558 1732903857
+6559 1205141076
+6560 1443297638
+6561 25457071
+6562 1059542876
+6563 1712304429
+6564 1577809511
+6565 603387713
+6566 1577013555
+6567 948480911
+6568 470220432
+6569 1897693430
+6570 964916005
+6571 392618990
+6572 1391286015
+6573 23555602
+6574 469271212
+6575 883293299
+6576 1940319625
+6577 831854680
+6578 815330527
+6579 1058030556
+6580 509484435
+6581 900699081
+6582 1554070025
+6583 870165786
+6584 1039914676
+6585 866980329
+6586 1414725967
+6587 1105674821
+6588 2132899189
+6589 1000146176
+6590 163332249
+6591 1428713179
+6592 1025603247
+6593 1222875125
+6594 993533960
+6595 455929110
+6596 1826262838
+6597 423063867
+6598 1404410021
+6599 148999623
+6600 173273650
+6601 221842379
+6602 541618613
+6603 1564559665
+6604 245397981
+6605 1010889825
+6606 300369316
+6607 38233958
+6608 1842744506
+6609 1115699843
+6610 1096264514
+6611 204745293
+6612 2016398924
+6613 502850892
+6614 1074911080
+6615 908829953
+6616 1369831221
+6617 342153399
+6618 2014504774
+6619 1355246762
+6620 1342299575
+6621 30353376
+6622 636476294
+6623 220419174
+6624 1253228501
+6625 1630010254
+6626 676348285
+6627 932007692
+6628 2053074122
+6629 2080758306
+6630 1081007315
+6631 78864124
+6632 155117037
+6633 1622625928
+6634 1643423789
+6635 400515018
+6636 486032105
+6637 1943793105
+6638 438748976
+6639 181292963
+6640 912009300
+6641 1535013491
+6642 386038257
+6643 780924577
+6644 2037864383
+6645 1460949337
+6646 1689754530
+6647 1260211956
+6648 1803102736
+6649 1556775656
+6650 467975070
+6651 997918663
+6652 1587129032
+6653 1104451364
+6654 1218337837
+6655 692873886
+6656 586977971
+6657 1894686122
+6658 1624881578
+6659 492568445
+6660 1827960781
+6661 558405245
+6662 571432569
+6663 1983077818
+6664 33547525
+6665 67372710
+6666 236109189
+6667 519579630
+6668 2011165815
+6669 674858165
+6670 700872594
+6671 775691467
+6672 62388008
+6673 1086910851
+6674 1556616044
+6675 2100252391
+6676 400376540
+6677 1098886926
+6678 1212980699
+6679 55995628
+6680 508178935
+6681 1680955770
+6682 1053914291
+6683 2095307967
+6684 637923486
+6685 124768480
+6686 640698205
+6687 1224901457
+6688 2019454603
+6689 118096135
+6690 1717469902
+6691 1699931736
+6692 676501380
+6693 141418823
+6694 1535525906
+6695 710048905
+6696 208791533
+6697 1771635095
+6698 1229628536
+6699 72473700
+6700 299009613
+6701 1930501130
+6702 848165168
+6703 361397621
+6704 869928333
+6705 257297564
+6706 314166365
+6707 1270304873
+6708 1356184491
+6709 1527147064
+6710 1326300501
+6711 1864363426
+6712 1060619186
+6713 232731144
+6714 1812187745
+6715 1698542673
+6716 357499624
+6717 305402303
+6718 775960482
+6719 229470579
+6720 423498438
+6721 345946737
+6722 1929402315
+6723 1099999819
+6724 487365560
+6725 1317444574
+6726 1810048724
+6727 696157094
+6728 941596021
+6729 892193612
+6730 768630794
+6731 1240605634
+6732 675211094
+6733 1616795962
+6734 1602003256
+6735 1545139427
+6736 1874093527
+6737 1916169621
+6738 667960652
+6739 1082794370
+6740 1295833037
+6741 1994261153
+6742 799674148
+6743 208968576
+6744 79508649
+6745 464378245
+6746 1907511249
+6747 437008274
+6748 769780548
+6749 535988083
+6750 666478853
+6751 1193278987
+6752 881934820
+6753 448397521
+6754 145795158
+6755 1369300381
+6756 1765842095
+6757 1955843882
+6758 2065457475
+6759 559954468
+6760 700553847
+6761 686604621
+6762 1800560103
+6763 1375764941
+6764 155916936
+6765 1255079711
+6766 773420721
+6767 2030010463
+6768 1023765684
+6769 1441381373
+6770 965321185
+6771 172115073
+6772 1288158879
+6773 1764995333
+6774 381083649
+6775 1367667528
+6776 81889930
+6777 141111250
+6778 1804675802
+6779 851670479
+6780 677099334
+6781 323671008
+6782 2044949466
+6783 1559034154
+6784 772068529
+6785 43260976
+6786 780850887
+6787 390426976
+6788 1999104858
+6789 698824714
+6790 950381444
+6791 552175057
+6792 1385429336
+6793 603457899
+6794 1927939999
+6795 1541346272
+6796 1858537610
+6797 553877072
+6798 1423873087
+6799 734819646
+6800 1995258445
+6801 241710624
+6802 906934720
+6803 1135933676
+6804 2006705957
+6805 1288018369
+6806 356117557
+6807 2088595887
+6808 1429129620
+6809 13309711
+6810 792782718
+6811 2106228954
+6812 336980719
+6813 690248536
+6814 1517779460
+6815 1109049248
+6816 733509512
+6817 151146700
+6818 1499476224
+6819 585130723
+6820 849971414
+6821 302374021
+6822 1137305780
+6823 87917102
+6824 905831920
+6825 917762131
+6826 1629263374
+6827 616885883
+6828 1471639203
+6829 905652813
+6830 1351705529
+6831 1319414001
+6832 1147363437
+6833 111156601
+6834 307864029
+6835 1006585746
+6836 1399174971
+6837 663981586
+6838 947697986
+6839 680820943
+6840 677291298
+6841 1740480704
+6842 639566249
+6843 1014272017
+6844 283245593
+6845 9862061
+6846 2123321266
+6847 1016755105
+6848 161008761
+6849 1475313842
+6850 1601885828
+6851 1010980176
+6852 1777687863
+6853 591707961
+6854 1098897278
+6855 536036136
+6856 1509470092
+6857 580677005
+6858 1152922019
+6859 833625648
+6860 1486329818
+6861 357143900
+6862 5556001
+6863 486209608
+6864 468300502
+6865 313420030
+6866 1492795354
+6867 1867475473
+6868 977401617
+6869 293009692
+6870 400812768
+6871 1654692915
+6872 2033490397
+6873 1040379017
+6874 521481284
+6875 169252342
+6876 1050241078
+6877 497318902
+6878 1186007447
+6879 1211249840
+6880 1972632745
+6881 640409628
+6882 74746368
+6883 1602836960
+6884 1232117589
+6885 1173643646
+6886 2138873096
+6887 594104033
+6888 1754320651
+6889 1144311467
+6890 1427729681
+6891 1093166822
+6892 1501455368
+6893 1433285682
+6894 1579376430
+6895 1969755870
+6896 1746705713
+6897 924688136
+6898 1689747695
+6899 576623682
+6900 1217697829
+6901 2090560463
+6902 83832949
+6903 1103704578
+6904 983455832
+6905 605314233
+6906 1272956920
+6907 2033696910
+6908 1102633136
+6909 311480719
+6910 1097463102
+6911 927782233
+6912 951890347
+6913 1172209470
+6914 383135545
+6915 36524288
+6916 198369469
+6917 374524994
+6918 630628322
+6919 1952690120
+6920 1518836461
+6921 2058358003
+6922 898373294
+6923 872808181
+6924 1344160038
+6925 330266076
+6926 695080403
+6927 943382103
+6928 1254954213
+6929 237344450
+6930 1520005785
+6931 325168394
+6932 180421265
+6933 1603838734
+6934 1428872972
+6935 1163877097
+6936 61669319
+6937 554346244
+6938 1050090360
+6939 1164302455
+6940 865826963
+6941 69814
+6942 2092084688
+6943 1817717311
+6944 1172279285
+6945 327736586
+6946 1854241599
+6947 1370648754
+6948 702261580
+6949 337386273
+6950 1175855226
+6951 73614393
+6952 248260629
+6953 2074228521
+6954 946422575
+6955 1592420667
+6956 257010949
+6957 1641502978
+6958 388319122
+6959 1511965162
+6960 1878847429
+6961 1908324907
+6962 1837133556
+6963 2059268694
+6964 1364679993
+6965 1118522880
+6966 1075662144
+6967 1426349312
+6968 1672869124
+6969 2125752504
+6970 443168120
+6971 391212440
+6972 2125822318
+6973 387769160
+6974 61446103
+6975 1150617955
+6976 715505746
+6977 1915687702
+6978 373783061
+6979 1417767326
+6980 105590328
+6981 1549638288
+6982 1491381720
+6983 353850957
+6984 1476383161
+6985 290320647
+6986 1946271624
+6987 1733394110
+6988 1931823625
+6989 187107098
+6990 1097875625
+6991 1663187406
+6992 2095432005
+6993 787525533
+6994 1574972453
+6995 1312628350
+6996 1906048414
+6997 503150949
+6998 591494014
+6999 1431433890
+7000 481419805
+7001 1034662134
+7002 1822646330
+7003 459758475
+7004 1422431295
+7005 1884092433
+7006 1610376431
+7007 2137937041
+7008 1652296488
+7009 1984159492
+7010 1408220720
+7011 1757886816
+7012 1386314132
+7013 752118792
+7014 2111737773
+7015 715213645
+7016 1042439439
+7017 1910525749
+7018 301124108
+7019 826779416
+7020 2097632847
+7021 1398999733
+7022 342483175
+7023 2045581204
+7024 39041618
+7025 1917455628
+7026 1210725906
+7027 1945090032
+7028 273122929
+7029 1802219920
+7030 1229040275
+7031 754542734
+7032 689398407
+7033 904202957
+7034 1214301209
+7035 2111829702
+7036 640811743
+7037 677193992
+7038 2102283095
+7039 145624583
+7040 513869837
+7041 1363020167
+7042 1903511399
+7043 1900183969
+7044 2115138959
+7045 1867765524
+7046 467913967
+7047 1010094750
+7048 1630807625
+7049 769038075
+7050 1836874167
+7051 1580956824
+7052 20554160
+7053 31873694
+7054 1479054380
+7055 59595778
+7056 1949329322
+7057 542296638
+7058 2004685811
+7059 74968603
+7060 197032910
+7061 1086242438
+7062 829511337
+7063 886431317
+7064 1990445395
+7065 2043812546
+7066 850777371
+7067 483773490
+7068 573522891
+7069 805576819
+7070 629398073
+7071 1087392728
+7072 21113338
+7073 385425824
+7074 840093049
+7075 2136252298
+7076 105707700
+7077 1308007016
+7078 998863400
+7079 1736515325
+7080 2077045091
+7081 688253919
+7082 1169988501
+7083 2097599251
+7084 720127613
+7085 501559233
+7086 9711382
+7087 521973287
+7088 1043855871
+7089 2014397193
+7090 596941890
+7091 1240888782
+7092 953155983
+7093 1426453227
+7094 2127320099
+7095 796117730
+7096 1322782126
+7097 830613823
+7098 1279891221
+7099 1896305017
+7100 1636190642
+7101 1909289294
+7102 836214097
+7103 1657303980
+7104 147231471
+7105 1676307146
+7106 1646072630
+7107 252939171
+7108 836830515
+7109 497452383
+7110 1989454497
+7111 766391958
+7112 1185706302
+7113 1011959350
+7114 716507562
+7115 1905833916
+7116 1513518584
+7117 726218944
+7118 280323555
+7119 409890807
+7120 593132489
+7121 877265446
+7122 1650779589
+7123 1546288472
+7124 156235025
+7125 1630616041
+7126 194922554
+7127 1479017151
+7128 313746216
+7129 1474813775
+7130 1227838520
+7131 1949936858
+7132 1236619422
+7133 2064052617
+7134 1459757190
+7135 1383850893
+7136 1592876116
+7137 958346173
+7138 1636790064
+7139 282222983
+7140 1455798556
+7141 1478760913
+7142 1048614941
+7143 494021210
+7144 343236616
+7145 1765122503
+7146 252371478
+7147 1856755200
+7148 343857799
+7149 532695034
+7150 119162359
+7151 936990288
+7152 1409960480
+7153 1769941949
+7154 335795112
+7155 1566195505
+7156 1253074342
+7157 530717667
+7158 897729009
+7159 1566820558
+7160 2005531442
+7161 2125567529
+7162 1369273768
+7163 1094667216
+7164 2042136499
+7165 681547310
+7166 331034461
+7167 1487528967
+7168 1639893483
+7169 1967824526
+7170 1769751950
+7171 948208391
+7172 1299101791
+7173 670883243
+7174 1442229602
+7175 1642338407
+7176 288522099
+7177 1694601080
+7178 1351609959
+7179 632379898
+7180 79812466
+7181 1470772319
+7182 1569370187
+7183 1489772946
+7184 1093230620
+7185 1905165299
+7186 908484804
+7187 198821314
+7188 288399318
+7189 1806213813
+7190 1765641872
+7191 146447113
+7192 1784297694
+7193 987431992
+7194 1241114329
+7195 1678950545
+7196 1668979302
+7197 1572148791
+7198 1018995864
+7199 1161389138
+7200 1392489669
+7201 641264166
+7202 2109597529
+7203 544107812
+7204 1312147410
+7205 1404343483
+7206 38962572
+7207 1600669509
+7208 951460916
+7209 1390572531
+7210 85565759
+7211 1031273382
+7212 713861202
+7213 1654935946
+7214 373562681
+7215 1807091822
+7216 1412617598
+7217 1282047485
+7218 2005913136
+7219 1701016916
+7220 940777650
+7221 1624071360
+7222 1847464029
+7223 577591696
+7224 464019704
+7225 941094711
+7226 109058594
+7227 2132999007
+7228 365759854
+7229 1128054458
+7230 1146904497
+7231 1758249523
+7232 1769318625
+7233 1109018378
+7234 154873687
+7235 933982387
+7236 365878214
+7237 193836259
+7238 387168248
+7239 1317339130
+7240 1584408791
+7241 472734007
+7242 201128864
+7243 150786345
+7244 2127669954
+7245 574691545
+7246 1957878168
+7247 1392803904
+7248 1856739030
+7249 1816307656
+7250 946337172
+7251 650033032
+7252 1292895369
+7253 646317554
+7254 1227624729
+7255 1756915073
+7256 1587412265
+7257 1336683323
+7258 1742430432
+7259 1953172119
+7260 317254133
+7261 741851281
+7262 1563937994
+7263 2086572758
+7264 1850869660
+7265 1718811681
+7266 873071497
+7267 69264226
+7268 1912647941
+7269 1260239745
+7270 1386603356
+7271 1349573084
+7272 1732973753
+7273 1587732220
+7274 1500359429
+7275 1713160059
+7276 14940118
+7277 1310753949
+7278 958480315
+7279 1871679148
+7280 979577958
+7281 1904817487
+7282 374228533
+7283 124989679
+7284 403651393
+7285 1601853262
+7286 1881904752
+7287 1991063658
+7288 791052937
+7289 1476851537
+7290 1796752129
+7291 1108307070
+7292 71219170
+7293 1213206475
+7294 1047396181
+7295 1922088830
+7296 784534509
+7297 1920467678
+7298 1991353056
+7299 549698802
+7300 1033223776
+7301 1230472764
+7302 1899271886
+7303 618713881
+7304 670721337
+7305 1252147667
+7306 184390292
+7307 685661455
+7308 415417969
+7309 1142870607
+7310 409856955
+7311 1394995927
+7312 900204446
+7313 784085488
+7314 1519985606
+7315 1303855840
+7316 238455102
+7317 1254406710
+7318 1147435850
+7319 1029508039
+7320 583774599
+7321 796704332
+7322 2137815110
+7323 654993770
+7324 2009910807
+7325 1037727643
+7326 429598952
+7327 646961668
+7328 810711673
+7329 273468361
+7330 1196660470
+7331 1843935449
+7332 1503941125
+7333 948448708
+7334 315165682
+7335 27178814
+7336 53112728
+7337 499555974
+7338 712840269
+7339 468530697
+7340 1642426581
+7341 1122697225
+7342 1863526624
+7343 395147380
+7344 1906782713
+7345 1236028582
+7346 1699003220
+7347 2145237816
+7348 342951644
+7349 698955422
+7350 1027262207
+7351 926726244
+7352 1495659754
+7353 1017593669
+7354 1581720014
+7355 1358086914
+7356 2055321312
+7357 2011318966
+7358 2005048582
+7359 718549338
+7360 137303679
+7361 1054225405
+7362 415001139
+7363 1641244805
+7364 2002674113
+7365 730166822
+7366 1668423619
+7367 2055786841
+7368 1229722796
+7369 233780241
+7370 376833890
+7371 724665730
+7372 1356477466
+7373 92876866
+7374 1119813110
+7375 1115776531
+7376 1328905448
+7377 671332682
+7378 1113530699
+7379 1671857093
+7380 1370288104
+7381 2140792907
+7382 451099689
+7383 718464211
+7384 1010902928
+7385 2032819703
+7386 2076551125
+7387 918740593
+7388 1896655021
+7389 1934116059
+7390 1637289931
+7391 2033958701
+7392 840857816
+7393 2052291070
+7394 1527719858
+7395 696048282
+7396 634974244
+7397 1048659829
+7398 604351475
+7399 1864697041
+7400 1282440070
+7401 981185366
+7402 441879123
+7403 491433888
+7404 1074062232
+7405 1561692233
+7406 1607210420
+7407 255484033
+7408 85541267
+7409 573257471
+7410 1927341126
+7411 1455829371
+7412 566566730
+7413 230957167
+7414 26809934
+7415 1577469659
+7416 116293222
+7417 2103361059
+7418 348726604
+7419 2012948243
+7420 1889993471
+7421 1986016535
+7422 1899423296
+7423 583367639
+7424 1890823957
+7425 1279659506
+7426 1279415921
+7427 378314554
+7428 180835688
+7429 1883767397
+7430 95527947
+7431 1463275758
+7432 717469115
+7433 537407070
+7434 1954709647
+7435 1791531347
+7436 2099099303
+7437 1414436419
+7438 2047015380
+7439 37156922
+7440 1987693890
+7441 1826872858
+7442 1492986293
+7443 406776973
+7444 2057830025
+7445 1519796228
+7446 1984246632
+7447 26639599
+7448 1475673639
+7449 185489588
+7450 2039587843
+7451 1218183462
+7452 24022475
+7453 1791527491
+7454 1801551102
+7455 1914846432
+7456 923703350
+7457 933483375
+7458 145677338
+7459 1104539038
+7460 669767124
+7461 241205285
+7462 420331148
+7463 1387236239
+7464 778612355
+7465 227557147
+7466 1031283939
+7467 730228010
+7468 1641993566
+7469 930815671
+7470 767384932
+7471 1482203809
+7472 610204882
+7473 112887578
+7474 1888980782
+7475 520551259
+7476 1632683806
+7477 1725743766
+7478 547190859
+7479 960873797
+7480 1911233354
+7481 439295054
+7482 31573612
+7483 1935255829
+7484 83338897
+7485 1833124714
+7486 1702618613
+7487 1007042247
+7488 619124441
+7489 1848295952
+7490 2111581285
+7491 1288891566
+7492 2089501237
+7493 384428786
+7494 528644157
+7495 720629945
+7496 611985933
+7497 1559928096
+7498 1450857955
+7499 106495852
+7500 343260120
+7501 70759240
+7502 1588699661
+7503 953465002
+7504 183646818
+7505 1330196795
+7506 1474016261
+7507 1816330624
+7508 908456913
+7509 2021207120
+7510 629720773
+7511 672206619
+7512 313018526
+7513 661294385
+7514 459978800
+7515 396357424
+7516 346935451
+7517 15113765
+7518 1403399671
+7519 966059893
+7520 1863409717
+7521 1367497309
+7522 107467811
+7523 1805427307
+7524 1751926095
+7525 636111968
+7526 378573604
+7527 216428380
+7528 48556417
+7529 1829431559
+7530 322924232
+7531 391816537
+7532 1900190799
+7533 1911623893
+7534 1345281539
+7535 2083837617
+7536 1094337040
+7537 671814152
+7538 1752684593
+7539 2002793953
+7540 545537625
+7541 234921719
+7542 527516924
+7543 858556151
+7544 896216104
+7545 987495724
+7546 1254913575
+7547 1243151556
+7548 1002609490
+7549 510829599
+7550 61727801
+7551 718535559
+7552 1878326908
+7553 169195612
+7554 376479218
+7555 1482769355
+7556 805307580
+7557 755052822
+7558 1699197735
+7559 853863997
+7560 437000734
+7561 2022121968
+7562 1245680534
+7563 189707885
+7564 1786262213
+7565 443478425
+7566 126061855
+7567 733115606
+7568 1115292578
+7569 1878746448
+7570 588425911
+7571 1660830203
+7572 2113668167
+7573 1115942836
+7574 371902706
+7575 862400624
+7576 2103438560
+7577 1626816282
+7578 2105552180
+7579 958564402
+7580 2137645881
+7581 19796333
+7582 1677099962
+7583 1868489141
+7584 188991945
+7585 2053579180
+7586 1203774848
+7587 994299525
+7588 661148355
+7589 755488935
+7590 1848163523
+7591 1098149089
+7592 630127255
+7593 946360409
+7594 1287856974
+7595 268905821
+7596 1389838835
+7597 1413918829
+7598 1002021427
+7599 357647765
+7600 1145181630
+7601 1590447338
+7602 2018477968
+7603 1111366149
+7604 558906526
+7605 242897026
+7606 1973766773
+7607 514861439
+7608 1869713308
+7609 1931835305
+7610 1473425841
+7611 1859875541
+7612 1951631638
+7613 1003042155
+7614 1580881034
+7615 2140623583
+7616 909137688
+7617 637172234
+7618 987439461
+7619 1570286043
+7620 1392661170
+7621 688119336
+7622 520951484
+7623 2022788425
+7624 1634479745
+7625 1808808458
+7626 144210598
+7627 876834932
+7628 1075243640
+7629 1146232025
+7630 1234482697
+7631 72941622
+7632 589195716
+7633 1105477017
+7634 1184307771
+7635 1148102242
+7636 1348374044
+7637 1010590897
+7638 1662963681
+7639 1070603704
+7640 794942554
+7641 988905875
+7642 782995598
+7643 599090545
+7644 1991948030
+7645 216392984
+7646 592230480
+7647 753602070
+7648 853565219
+7649 1579669941
+7650 176404465
+7651 98742741
+7652 120305629
+7653 697355949
+7654 2121531166
+7655 1754785375
+7656 358680760
+7657 118258117
+7658 484136659
+7659 1433924400
+7660 1264490142
+7661 1718619357
+7662 1506866022
+7663 1853685858
+7664 676612726
+7665 543690145
+7666 854304453
+7667 2024986770
+7668 1554281042
+7669 369784486
+7670 948106827
+7671 201739949
+7672 1358690361
+7673 1731102425
+7674 800830494
+7675 1203154744
+7676 1947495409
+7677 1393060974
+7678 1956756814
+7679 653576980
+7680 825247268
+7681 2133161280
+7682 752319721
+7683 945552897
+7684 683033581
+7685 726367240
+7686 552854624
+7687 1041714341
+7688 844625357
+7689 1036991284
+7690 328155093
+7691 2109115499
+7692 608126993
+7693 1835021115
+7694 1815317710
+7695 1284739719
+7696 231227613
+7697 522138515
+7698 1162242842
+7699 1785508655
+7700 891923001
+7701 2110349669
+7702 1987248604
+7703 103129715
+7704 1693968446
+7705 640595450
+7706 1306284459
+7707 1493980207
+7708 2033656425
+7709 1115557625
+7710 73540
+7711 711420045
+7712 1101235257
+7713 752393261
+7714 1656972942
+7715 1784268839
+7716 1478760501
+7717 62343919
+7718 678499532
+7719 175902210
+7720 1099335203
+7721 1006654626
+7722 137534062
+7723 1707462196
+7724 694192093
+7725 1952851772
+7726 844718267
+7727 925419706
+7728 327506639
+7729 2006961109
+7730 563444714
+7731 1219429640
+7732 1969827130
+7733 403209670
+7734 1322559355
+7735 1516311928
+7736 1043805121
+7737 481360166
+7738 862808488
+7739 929977898
+7740 1596917792
+7741 862882028
+7742 1641397943
+7743 550669401
+7744 1615275289
+7745 1150887237
+7746 187454592
+7747 946552143
+7748 1213231156
+7749 865954125
+7750 1122454353
+7751 165082711
+7752 1872608751
+7753 1259988415
+7754 1872544907
+7755 419317196
+7756 1065356539
+7757 569779527
+7758 1344736903
+7759 1392863178
+7760 429256988
+7761 1908181617
+7762 464809171
+7763 251600471
+7764 163907639
+7765 1787368526
+7766 1767912399
+7767 1207712760
+7768 121245045
+7769 483237239
+7770 2137690658
+7771 1718162837
+7772 1346119267
+7773 1631604953
+7774 121348590
+7775 813910909
+7776 635008543
+7777 308803183
+7778 1760463052
+7779 1848239699
+7780 1174757308
+7781 735433757
+7782 2013322411
+7783 899882411
+7784 1995422173
+7785 1738383670
+7786 1319199607
+7787 913295064
+7788 160679549
+7789 516452862
+7790 158674595
+7791 589936538
+7792 277150831
+7793 623483766
+7794 841537009
+7795 441058471
+7796 263368644
+7797 461965760
+7798 1648771231
+7799 384613689
+7800 945203000
+7801 1638978242
+7802 2102776526
+7803 143838619
+7804 1123099547
+7805 76641469
+7806 957749528
+7807 1758108090
+7808 385444652
+7809 570728932
+7810 1458864142
+7811 1560201960
+7812 1306162690
+7813 1324702905
+7814 312600723
+7815 1154101215
+7816 915602927
+7817 1631800330
+7818 2067396279
+7819 1076282477
+7820 769545
+7821 78587226
+7822 1666219015
+7823 277920376
+7824 702070992
+7825 360272376
+7826 718978847
+7827 965439637
+7828 822238136
+7829 220266431
+7830 1350053326
+7831 1767441136
+7832 1859244673
+7833 1305346205
+7834 1911279756
+7835 834860572
+7836 1381987674
+7837 721545636
+7838 445485015
+7839 1767432326
+7840 1292274569
+7841 1904349157
+7842 1180150638
+7843 450953611
+7844 1081568414
+7845 1492751361
+7846 1605054826
+7847 1997171341
+7848 977068043
+7849 1524967457
+7850 925970170
+7851 977837588
+7852 1603554684
+7853 444705537
+7854 1255757965
+7855 158142028
+7856 804977913
+7857 1974736812
+7858 1123581665
+7859 1627216050
+7860 47519595
+7861 326151344
+7862 1247173538
+7863 1906764268
+7864 1631497549
+7865 1010969646
+7866 594141193
+7867 866001575
+7868 1732515283
+7869 1039626208
+7870 485950253
+7871 877306204
+7872 796491717
+7873 1666100891
+7874 1328259815
+7875 1878060131
+7876 1011368604
+7877 785830993
+7878 1727747824
+7879 1988436647
+7880 163314802
+7881 506234347
+7882 818790588
+7883 1766869486
+7884 950939884
+7885 2074548553
+7886 1925011515
+7887 1755917798
+7888 1901801717
+7889 901109532
+7890 1235650200
+7891 1949321313
+7892 1227260876
+7893 335340090
+7894 1708601933
+7895 711274777
+7896 1346309737
+7897 155259478
+7898 1577276352
+7899 931341372
+7900 1194885686
+7901 2063226605
+7902 1808647576
+7903 1991377403
+7904 1581843848
+7905 989423743
+7906 1721953886
+7907 445728804
+7908 1775254736
+7909 1302218063
+7910 286681804
+7911 1938569538
+7912 1808452410
+7913 1105472392
+7914 1557955377
+7915 611908646
+7916 1032537297
+7917 1335483244
+7918 220342796
+7919 786855366
+7920 89109128
+7921 1455992996
+7922 588693031
+7923 1316370005
+7924 1791333087
+7925 149811317
+7926 2027644782
+7927 990159176
+7928 305070795
+7929 1457437487
+7930 1921500548
+7931 1499956482
+7932 1373180444
+7933 1582664476
+7934 1343850237
+7935 807540645
+7936 424604571
+7937 918320476
+7938 1253269449
+7939 52375659
+7940 73054891
+7941 1539951253
+7942 1990945197
+7943 1881507301
+7944 497939997
+7945 1401416926
+7946 345932299
+7947 1530477294
+7948 589416522
+7949 566275096
+7950 169849013
+7951 678525651
+7952 2022268092
+7953 758542044
+7954 1994895656
+7955 1666117531
+7956 908353361
+7957 1875056790
+7958 508793059
+7959 1213424157
+7960 1185010629
+7961 282809959
+7962 565896991
+7963 410707426
+7964 1865474435
+7965 1909747228
+7966 1218248071
+7967 142595358
+7968 680584056
+7969 324033872
+7970 194971017
+7971 753638947
+7972 1863985126
+7973 38432567
+7974 487662600
+7975 214441475
+7976 1439849493
+7977 833594900
+7978 1744918770
+7979 2029266016
+7980 1399869996
+7981 1914767783
+7982 560308019
+7983 1274654440
+7984 525826179
+7985 407720027
+7986 793288324
+7987 1434179541
+7988 135293169
+7989 1302081383
+7990 500120050
+7991 1320303799
+7992 1584891343
+7993 1066017041
+7994 1731011225
+7995 1302882130
+7996 828280621
+7997 801775648
+7998 1445477489
+7999 1508864678
+8000 1125809520
+8001 1640448506
+8002 115019977
+8003 842310998
+8004 1678881073
+8005 602682578
+8006 1056752474
+8007 971246919
+8008 1436277478
+8009 654187596
+8010 853029287
+8011 688663826
+8012 421471731
+8013 1413337306
+8014 1963318266
+8015 947297910
+8016 1821057333
+8017 609122942
+8018 233993803
+8019 1956350502
+8020 1911204326
+8021 734113853
+8022 1129170653
+8023 1348612021
+8024 1800130894
+8025 712698230
+8026 504010503
+8027 480927868
+8028 1514473878
+8029 1949487992
+8030 1989792546
+8031 492799751
+8032 1442452851
+8033 2104812523
+8034 1335110749
+8035 973850276
+8036 560011453
+8037 244379575
+8038 1945097195
+8039 1996288931
+8040 898567171
+8041 650642834
+8042 537469109
+8043 1320038902
+8044 2063980140
+8045 353303728
+8046 119853165
+8047 1737553825
+8048 962426670
+8049 353846968
+8050 1546420680
+8051 726147348
+8052 1087960822
+8053 528107685
+8054 2074759369
+8055 740608068
+8056 1240805916
+8057 431286225
+8058 1221535936
+8059 607796146
+8060 233290569
+8061 1063844834
+8062 1100595897
+8063 1675743420
+8064 1021173710
+8065 288222999
+8066 502110049
+8067 1581185163
+8068 532602574
+8069 299723596
+8070 1429990447
+8071 1431169746
+8072 950366431
+8073 1967459556
+8074 603725000
+8075 866862923
+8076 173279636
+8077 723578165
+8078 456933101
+8079 1135706307
+8080 1077425134
+8081 2003353781
+8082 1861853655
+8083 17902308
+8084 383977818
+8085 1789129377
+8086 758510376
+8087 1624783734
+8088 72931954
+8089 1980046313
+8090 85096233
+8091 306222523
+8092 896407499
+8093 1185692130
+8094 1981965944
+8095 1917581209
+8096 1473915129
+8097 336592345
+8098 1351282725
+8099 2006517704
+8100 636315941
+8101 633789524
+8102 1290203802
+8103 1586682372
+8104 453765432
+8105 1893928802
+8106 306061648
+8107 627045069
+8108 470023320
+8109 762994749
+8110 1762751376
+8111 1547448454
+8112 618864882
+8113 1477121383
+8114 1565350762
+8115 1002842700
+8116 1118767112
+8117 176377490
+8118 480142787
+8119 1191699066
+8120 8940155
+8121 565239020
+8122 1497921590
+8123 905347655
+8124 1750931150
+8125 1332403886
+8126 675445216
+8127 1077362632
+8128 1668996231
+8129 2026727941
+8130 936396688
+8131 157828524
+8132 513033817
+8133 79116842
+8134 1744510897
+8135 966799250
+8136 1973045644
+8137 2050572545
+8138 1593844319
+8139 295585316
+8140 666083646
+8141 1209112047
+8142 1843033770
+8143 1284948528
+8144 538749782
+8145 1260900884
+8146 140307580
+8147 1657516895
+8148 1437278375
+8149 620450367
+8150 701732313
+8151 1446218530
+8152 1185689387
+8153 52170255
+8154 204082537
+8155 789136890
+8156 1384574141
+8157 879527754
+8158 1866499522
+8159 906086724
+8160 758772047
+8161 655412562
+8162 1063915249
+8163 1271805865
+8164 734529404
+8165 660942498
+8166 91121467
+8167 560091400
+8168 564031395
+8169 1684965786
+8170 855676717
+8171 1230115041
+8172 746594185
+8173 551226839
+8174 367579921
+8175 1285343967
+8176 1812127724
+8177 507887501
+8178 795377214
+8179 1101922451
+8180 1128337869
+8181 1497109528
+8182 400657333
+8183 166543608
+8184 1549279783
+8185 604739871
+8186 955680498
+8187 786370277
+8188 1484267625
+8189 674696372
+8190 1692457001
+8191 95556024
+8192 1330108934
+8193 608888602
+8194 1367361889
+8195 2064638338
+8196 1269831100
+8197 1458483356
+8198 477246091
+8199 1833862495
+8200 995965494
+8201 1332922808
+8202 916493888
+8203 1742559679
+8204 1884149647
+8205 1284073809
+8206 880419999
+8207 1548793723
+8208 1791961311
+8209 1675797213
+8210 503232526
+8211 772815532
+8212 1025423093
+8213 903889860
+8214 939359140
+8215 427219229
+8216 1508629731
+8217 1895039639
+8218 1213589506
+8219 845413708
+8220 422252363
+8221 758562859
+8222 940969732
+8223 1752361298
+8224 1367451462
+8225 160847974
+8226 1669515988
+8227 489798914
+8228 1619331330
+8229 2146762079
+8230 176177762
+8231 467813177
+8232 1332201239
+8233 1092671650
+8234 62889208
+8235 1068867239
+8236 229261812
+8237 943309207
+8238 470177314
+8239 2021223123
+8240 471622773
+8241 973409841
+8242 646555007
+8243 1497045866
+8244 1877299701
+8245 1585914147
+8246 1924265095
+8247 1238445784
+8248 1333470138
+8249 990370953
+8250 2083859492
+8251 1755722502
+8252 1748933813
+8253 877345576
+8254 1360600152
+8255 968901627
+8256 1038193550
+8257 882632492
+8258 1458700541
+8259 510041233
+8260 881910924
+8261 1634878303
+8262 977854410
+8263 66628515
+8264 580066306
+8265 1040743618
+8266 1135495754
+8267 809328118
+8268 1984052826
+8269 1605673069
+8270 683067593
+8271 308191951
+8272 431599262
+8273 1329622600
+8274 1805237817
+8275 161415315
+8276 768053099
+8277 1582019265
+8278 1399861099
+8279 2101523238
+8280 424906570
+8281 1336236943
+8282 1709762092
+8283 26356735
+8284 66098871
+8285 922878596
+8286 995258362
+8287 1104292422
+8288 1805511088
+8289 306475256
+8290 1614333655
+8291 539938364
+8292 1941353559
+8293 444704417
+8294 606566880
+8295 373936217
+8296 1485448035
+8297 1742062634
+8298 1183264335
+8299 1322017213
+8300 1200252055
+8301 1866331928
+8302 1630209164
+8303 1631851317
+8304 1048470880
+8305 1287963334
+8306 1793266632
+8307 1816523980
+8308 722498951
+8309 1045644083
+8310 1770563570
+8311 1147405521
+8312 234397378
+8313 1332842014
+8314 1173762257
+8315 300496250
+8316 108236962
+8317 21536971
+8318 1404788672
+8319 1913748050
+8320 328012227
+8321 871638679
+8322 306202767
+8323 121882139
+8324 1316343096
+8325 912769647
+8326 495818356
+8327 654307483
+8328 507348633
+8329 1679082692
+8330 1976324697
+8331 1707600689
+8332 1397930972
+8333 1459050213
+8334 1191968358
+8335 298918205
+8336 599529899
+8337 837751343
+8338 2115442185
+8339 1322028850
+8340 1883395426
+8341 1738522107
+8342 321950724
+8343 2117792805
+8344 923880473
+8345 1495712981
+8346 270805407
+8347 1032117435
+8348 1517249952
+8349 1675594079
+8350 798381837
+8351 1845262180
+8352 399749110
+8353 1104584604
+8354 1967144319
+8355 1716092206
+8356 2017354251
+8357 315479027
+8358 222916041
+8359 377219237
+8360 1994561719
+8361 51757090
+8362 2084819926
+8363 1245009044
+8364 1510807304
+8365 1129304636
+8366 1543927249
+8367 2110337203
+8368 1967055979
+8369 1511885786
+8370 1284882406
+8371 1702967758
+8372 1102924245
+8373 1606833130
+8374 1673276915
+8375 2026804718
+8376 955062463
+8377 1944082322
+8378 911438505
+8379 324828767
+8380 1472192753
+8381 1709820342
+8382 22607299
+8383 1871941863
+8384 666921299
+8385 1989751618
+8386 1440550421
+8387 536791902
+8388 157746998
+8389 1663466462
+8390 914011139
+8391 4825069
+8392 1715223553
+8393 851347417
+8394 1249834113
+8395 1078547209
+8396 1980652054
+8397 646277714
+8398 1041400764
+8399 1800224385
+8400 10679852
+8401 178799522
+8402 1355708495
+8403 1113604097
+8404 1785632652
+8405 881501762
+8406 992925167
+8407 593211467
+8408 678100436
+8409 1904363672
+8410 918040235
+8411 2809541
+8412 1466700367
+8413 940647534
+8414 1874751404
+8415 2133621666
+8416 782915505
+8417 1167818177
+8418 522929920
+8419 940662503
+8420 683800992
+8421 1436941060
+8422 945487572
+8423 251540897
+8424 140804829
+8425 47838038
+8426 1330088106
+8427 2121456883
+8428 694115752
+8429 224005222
+8430 1774197621
+8431 704795605
+8432 402804745
+8433 982422468
+8434 1818399702
+8435 40953749
+8436 1863924231
+8437 663841222
+8438 634165217
+8439 394541019
+8440 420721246
+8441 1552205452
+8442 397350561
+8443 1887421613
+8444 345369338
+8445 124618317
+8446 1873559631
+8447 1128284843
+8448 1292436495
+8449 249005904
+8450 2068947346
+8451 1976237487
+8452 1685946964
+8453 866951271
+8454 80294736
+8455 1826751793
+8456 914789309
+8457 1410382842
+8458 1800725029
+8459 1608905061
+8460 1634388064
+8461 1427439002
+8462 166217018
+8463 2037192809
+8464 262377822
+8465 1984616721
+8466 2078146559
+8467 2126302053
+8468 500974295
+8469 564828128
+8470 373359425
+8471 921695541
+8472 2117033580
+8473 770709986
+8474 661633507
+8475 314919270
+8476 895328303
+8477 387709490
+8478 1443204114
+8479 40281150
+8480 636715394
+8481 1364667812
+8482 2016518637
+8483 175178710
+8484 84135435
+8485 2096813373
+8486 2001930504
+8487 998924744
+8488 1359712567
+8489 1655171885
+8490 460346158
+8491 846616984
+8492 935127239
+8493 626563176
+8494 736326145
+8495 1197505061
+8496 463696249
+8497 666989056
+8498 1176323467
+8499 964670544
+8500 1231817184
+8501 1549682892
+8502 1886366086
+8503 1201367116
+8504 172909230
+8505 400515945
+8506 1516286387
+8507 1068237533
+8508 788225435
+8509 812006853
+8510 1108518684
+8511 1424940830
+8512 29191017
+8513 977553673
+8514 1600119540
+8515 113326453
+8516 926883399
+8517 1454566396
+8518 1112251197
+8519 139112318
+8520 962254633
+8521 1572597355
+8522 985729302
+8523 1897381872
+8524 51676884
+8525 1722055448
+8526 947403286
+8527 515373133
+8528 241560856
+8529 2123726753
+8530 1480043678
+8531 1473378041
+8532 1525925997
+8533 1218926116
+8534 527261509
+8535 1698835227
+8536 1619442061
+8537 2043547896
+8538 619589112
+8539 260183848
+8540 708071101
+8541 1728107796
+8542 1685124678
+8543 737262119
+8544 558177822
+8545 1137760571
+8546 850588572
+8547 1485061221
+8548 444843319
+8549 1962839769
+8550 1624173539
+8551 1407097953
+8552 1387953477
+8553 462419194
+8554 1156996177
+8555 1439630361
+8556 36990994
+8557 2104399463
+8558 1955003494
+8559 278551850
+8560 2080642568
+8561 1287563524
+8562 1751929891
+8563 1459084917
+8564 359005992
+8565 131707753
+8566 1010436496
+8567 1978448053
+8568 27772001
+8569 1630025609
+8570 91148254
+8571 735843103
+8572 1210649757
+8573 1776272932
+8574 1473105222
+8575 1768827579
+8576 766549855
+8577 176210146
+8578 1106405152
+8579 1211393175
+8580 2139049915
+8581 583095044
+8582 471007480
+8583 1379519744
+8584 1045514238
+8585 1628003657
+8586 671666457
+8587 1082505232
+8588 1584919473
+8589 479186304
+8590 1361057082
+8591 1518078393
+8592 1766749828
+8593 965503326
+8594 829679663
+8595 2125755821
+8596 1097211079
+8597 1840116159
+8598 1956720226
+8599 1124983080
+8600 1322658120
+8601 2047868480
+8602 1860826183
+8603 385824230
+8604 1676657765
+8605 1186447757
+8606 7168161
+8607 295723972
+8608 1362657903
+8609 1113573314
+8610 1507117147
+8611 1354224171
+8612 1696668358
+8613 1978124627
+8614 586260267
+8615 594698948
+8616 1458644637
+8617 1257926725
+8618 1677204180
+8619 896080462
+8620 1737113029
+8621 890777614
+8622 266675207
+8623 1356379209
+8624 1856280940
+8625 1096354870
+8626 1334651382
+8627 806008371
+8628 788987382
+8629 1143887961
+8630 1930991452
+8631 2111645502
+8632 1044272793
+8633 1644333987
+8634 349986084
+8635 573446910
+8636 683298097
+8637 357154246
+8638 869170883
+8639 2045956000
+8640 1470727560
+8641 228804382
+8642 1252696523
+8643 1019912270
+8644 59445362
+8645 1838956791
+8646 1614611218
+8647 1518089999
+8648 949399868
+8649 1144331750
+8650 266686813
+8651 539029249
+8652 2035109364
+8653 533362020
+8654 1895408458
+8655 1743906657
+8656 1629716891
+8657 1082576193
+8658 402431380
+8659 271220625
+8660 78980506
+8661 185939184
+8662 235382479
+8663 1123253299
+8664 1830273172
+8665 585368564
+8666 1696700210
+8667 366087621
+8668 942522810
+8669 418387445
+8670 264559973
+8671 265766722
+8672 647191827
+8673 1517256497
+8674 1285678992
+8675 706637189
+8676 1208729640
+8677 752806562
+8678 77243540
+8679 10645860
+8680 1897138312
+8681 343930353
+8682 549675109
+8683 1784764028
+8684 877292374
+8685 297599919
+8686 1381187037
+8687 359525617
+8688 1380176112
+8689 1783618418
+8690 630746242
+8691 1459156618
+8692 1969557602
+8693 866128721
+8694 434926270
+8695 1652347126
+8696 1451497285
+8697 2131626480
+8698 2018434747
+8699 246536447
+8700 402530277
+8701 135511073
+8702 512303169
+8703 1049722104
+8704 1652767570
+8705 1797982161
+8706 1756359294
+8707 714013562
+8708 403305075
+8709 1833602834
+8710 724659422
+8711 152959739
+8712 30049540
+8713 1274334531
+8714 1937723768
+8715 907341914
+8716 1571934450
+8717 1171427157
+8718 1266867531
+8719 804626915
+8720 807561927
+8721 1897613773
+8722 116299885
+8723 629635882
+8724 616258846
+8725 551226155
+8726 134499360
+8727 2067756132
+8728 535368987
+8729 5450460
+8730 166808931
+8731 937899264
+8732 140961533
+8733 679112101
+8734 1987621369
+8735 1793729103
+8736 329610614
+8737 1596497015
+8738 360259017
+8739 732915690
+8740 1282616201
+8741 1084918439
+8742 885875429
+8743 1312665741
+8744 211769322
+8745 676115549
+8746 72524007
+8747 1783703772
+8748 1847542707
+8749 1339391538
+8750 440847039
+8751 507620986
+8752 1089521663
+8753 557146925
+8754 1137256868
+8755 1705780510
+8756 1108373080
+8757 1271756229
+8758 1626052994
+8759 1643742068
+8760 1277206689
+8761 1792861925
+8762 434157684
+8763 1418168222
+8764 324490378
+8765 274295405
+8766 1064413677
+8767 654100993
+8768 1870792420
+8769 1424672694
+8770 1387016683
+8771 1005924974
+8772 362107485
+8773 125408464
+8774 171107067
+8775 573876807
+8776 801524014
+8777 243631075
+8778 210096931
+8779 501583073
+8780 1583022613
+8781 650943971
+8782 1009204059
+8783 525060629
+8784 1208090896
+8785 2146460928
+8786 83357491
+8787 168980328
+8788 1270733509
+8789 1709410485
+8790 1812722396
+8791 400456550
+8792 1354788762
+8793 99396433
+8794 1818624772
+8795 1679279141
+8796 373691838
+8797 735554801
+8798 185896486
+8799 97000611
+8800 12743847
+8801 1572913169
+8802 1102925585
+8803 374851332
+8804 1698321633
+8805 1274032652
+8806 948728139
+8807 352361999
+8808 1517663727
+8809 1158825070
+8810 853945072
+8811 953202693
+8812 1809769041
+8813 1863149132
+8814 1478263322
+8815 870376289
+8816 1862126412
+8817 1561620813
+8818 1039356618
+8819 985376273
+8820 1123547650
+8821 704595366
+8822 1385832823
+8823 330852764
+8824 803991799
+8825 1056973947
+8826 2010131905
+8827 1177683638
+8828 1792528748
+8829 48544743
+8830 1274684249
+8831 1805272595
+8832 1621457912
+8833 230126186
+8834 32640279
+8835 1172295898
+8836 1504158838
+8837 981368418
+8838 1524657897
+8839 874338918
+8840 2140193488
+8841 231119322
+8842 1827541611
+8843 1802478882
+8844 2094268454
+8845 1158321285
+8846 525371523
+8847 1808911218
+8848 572458450
+8849 1564728141
+8850 646803843
+8851 1696006100
+8852 121839860
+8853 2032636666
+8854 2026858864
+8855 925831659
+8856 942126965
+8857 1889507122
+8858 2103515297
+8859 587172065
+8860 1938051865
+8861 1230715898
+8862 244961012
+8863 1412026130
+8864 1460842084
+8865 277601291
+8866 436838380
+8867 817517275
+8868 1258969709
+8869 1961496277
+8870 1691856193
+8871 1251679549
+8872 45131951
+8873 1371914156
+8874 906674783
+8875 2139400405
+8876 382751793
+8877 1432046307
+8878 1800827975
+8879 955210243
+8880 849290800
+8881 300148170
+8882 503732695
+8883 971130660
+8884 185301188
+8885 383107911
+8886 1896962320
+8887 1127428153
+8888 125131385
+8889 1852993969
+8890 1714600218
+8891 2063183251
+8892 936226220
+8893 1959561230
+8894 1327725733
+8895 249584656
+8896 89678873
+8897 1764564113
+8898 1067101931
+8899 1348648582
+8900 1578576742
+8901 611474476
+8902 452844484
+8903 1623708694
+8904 1983388632
+8905 1359519267
+8906 1615625451
+8907 218656777
+8908 644081926
+8909 1268969779
+8910 1173867020
+8911 1493372727
+8912 1569117949
+8913 1677599715
+8914 317019739
+8915 1754419138
+8916 2060707627
+8917 66498411
+8918 734363643
+8919 38355364
+8920 1919492381
+8921 301480214
+8922 2101538615
+8923 708234953
+8924 113557796
+8925 1281780700
+8926 957819609
+8927 203236670
+8928 898861165
+8929 2024921541
+8930 1551885252
+8931 329954260
+8932 488912369
+8933 2004729736
+8934 1953662954
+8935 324817354
+8936 1216765356
+8937 1421804757
+8938 543474131
+8939 1860847282
+8940 543290888
+8941 1717341152
+8942 1206736361
+8943 2112408838
+8944 1247457219
+8945 1523756101
+8946 1719344328
+8947 1160681198
+8948 1590254512
+8949 306224323
+8950 1199036563
+8951 1362263245
+8952 607704537
+8953 1153091530
+8954 2070498198
+8955 721262334
+8956 287388583
+8957 880834160
+8958 924499004
+8959 1186249748
+8960 758272053
+8961 328900608
+8962 1516204008
+8963 1247184422
+8964 186146697
+8965 1322383314
+8966 1572001776
+8967 1402912053
+8968 596704424
+8969 2115475908
+8970 1116275687
+8971 1139995312
+8972 1685333412
+8973 175528401
+8974 1104920502
+8975 785306983
+8976 1699284502
+8977 676781182
+8978 1945988182
+8979 1142055366
+8980 983005506
+8981 997541097
+8982 356834964
+8983 1590710043
+8984 3148979
+8985 279849514
+8986 164488729
+8987 290537562
+8988 1160683674
+8989 1088987733
+8990 1476787311
+8991 1918955727
+8992 1417888342
+8993 845507671
+8994 1018656502
+8995 1604035039
+8996 20407338
+8997 443174630
+8998 859463444
+8999 617111762
+9000 411166890
+9001 1975739131
+9002 1757107074
+9003 2096500302
+9004 3783884
+9005 714543929
+9006 734323638
+9007 1703068386
+9008 1391325111
+9009 532828172
+9010 697640105
+9011 226846969
+9012 1530369269
+9013 1054475069
+9014 1817557013
+9015 1533518248
+9016 1334324583
+9017 1982045742
+9018 1824055811
+9019 347524610
+9020 923549828
+9021 1153359474
+9022 118996689
+9023 193954522
+9024 1998867145
+9025 1137653191
+9026 1797989561
+9027 2019274483
+9028 1580827822
+9029 509969357
+9030 488902597
+9031 1991994712
+9032 338224840
+9033 98526024
+9034 1941011367
+9035 342008725
+9036 813069953
+9037 527851357
+9038 2045077111
+9039 56911416
+9040 1060679529
+9041 595233568
+9042 283758386
+9043 443565150
+9044 1649708637
+9045 2101315399
+9046 1977083398
+9047 836549573
+9048 1935877493
+9049 1653655561
+9050 1184074183
+9051 711943673
+9052 659531387
+9053 1303070872
+9054 905898195
+9055 510914885
+9056 293240416
+9057 556404108
+9058 382705720
+9059 1874068238
+9060 1066373465
+9061 871608318
+9062 1718579302
+9063 1404598306
+9064 970134342
+9065 1512107021
+9066 1746607031
+9067 1783204295
+9068 2039958378
+9069 1644200494
+9070 1840115711
+9071 953154259
+9072 91950415
+9073 2123874097
+9074 1396719409
+9075 1741659052
+9076 2077705848
+9077 1226319160
+9078 430724977
+9079 1866099694
+9080 732491073
+9081 1614799160
+9082 430559719
+9083 1392022461
+9084 770386385
+9085 1336457915
+9086 1902937346
+9087 1063626801
+9088 1892862023
+9089 138159418
+9090 790211391
+9091 811751841
+9092 1009767736
+9093 361307045
+9094 68866499
+9095 1979902078
+9096 1873414067
+9097 1815473530
+9098 1615622725
+9099 1765888797
+9100 1312190376
+9101 1308254789
+9102 571559409
+9103 1404140791
+9104 1284645238
+9105 1968278818
+9106 998316196
+9107 1214867439
+9108 1047114330
+9109 1429041173
+9110 933483485
+9111 1779605404
+9112 896356686
+9113 1364043204
+9114 1024144217
+9115 1666743071
+9116 553017471
+9117 779597915
+9118 582886224
+9119 298395847
+9120 917757333
+9121 1373097615
+9122 1110147688
+9123 1927525070
+9124 1734404660
+9125 1179014187
+9126 1759943500
+9127 1460335079
+9128 847004069
+9129 1228082578
+9130 1078740229
+9131 11710797
+9132 388853719
+9133 1650299638
+9134 1415851589
+9135 1673498957
+9136 1471094808
+9137 266684137
+9138 740882748
+9139 370725491
+9140 1695725310
+9141 1674366233
+9142 2847247
+9143 444598348
+9144 890925790
+9145 1026991464
+9146 2111341419
+9147 1443943261
+9148 1806589379
+9149 546743995
+9150 1742339108
+9151 576863064
+9152 1919841610
+9153 705003148
+9154 356904486
+9155 1506762623
+9156 1884017335
+9157 2116847987
+9158 819614054
+9159 583537756
+9160 1197446917
+9161 1898354283
+9162 595248554
+9163 1586300636
+9164 1401170273
+9165 2011100143
+9166 1112315945
+9167 724781434
+9168 130300632
+9169 1853198694
+9170 1095506925
+9171 1826025942
+9172 1380081279
+9173 1098354172
+9174 123140643
+9175 123523421
+9176 2125345636
+9177 86998414
+9178 1567466683
+9179 1784451367
+9180 633742410
+9181 1162322143
+9182 213830783
+9183 406100372
+9184 1867325292
+9185 570735270
+9186 1912862995
+9187 1603858979
+9188 540099609
+9189 584993402
+9190 39913088
+9191 1737546526
+9192 335864037
+9193 635161642
+9194 1176363514
+9195 1737034311
+9196 498778137
+9197 141195811
+9198 314332097
+9199 629078769
+9200 1994394505
+9201 1409839022
+9202 307621063
+9203 1226992137
+9204 360709546
+9205 430761706
+9206 1350515558
+9207 338571534
+9208 517760121
+9209 770498593
+9210 2123022901
+9211 1151502531
+9212 1932820737
+9213 189370036
+9214 1557602903
+9215 1652662381
+9216 760105306
+9217 1322982251
+9218 1109037712
+9219 1300204915
+9220 1907975653
+9221 1148950800
+9222 890267793
+9223 96356042
+9224 1784112442
+9225 2066631307
+9226 1833390353
+9227 135406931
+9228 60343471
+9229 238802
+9230 764485700
+9231 2054737976
+9232 1410077824
+9233 1072106764
+9234 1134246465
+9235 1770787370
+9236 1502868470
+9237 337278376
+9238 2109358904
+9239 2020628591
+9240 1107776969
+9241 2084898157
+9242 1024647474
+9243 893114058
+9244 126784546
+9245 434766730
+9246 398292791
+9247 886889852
+9248 1757748981
+9249 1507330504
+9250 39611120
+9251 1518240986
+9252 508797656
+9253 929878913
+9254 1614597028
+9255 145426451
+9256 849026573
+9257 1300503734
+9258 280833382
+9259 909370044
+9260 1300742536
+9261 1045319083
+9262 816624372
+9263 563336713
+9264 2117425847
+9265 1950870838
+9266 186640435
+9267 1472810669
+9268 140665566
+9269 148515692
+9270 1345955613
+9271 1248442535
+9272 85930201
+9273 223119439
+9274 2141556594
+9275 212714747
+9276 657886169
+9277 392365737
+9278 1099604600
+9279 268151502
+9280 1899696241
+9281 1139215720
+9282 1786392488
+9283 261010250
+9284 2069094633
+9285 1253505869
+9286 406436701
+9287 770637558
+9288 406525955
+9289 687270083
+9290 1680007602
+9291 1707268491
+9292 1732589166
+9293 349148327
+9294 123121556
+9295 1702531365
+9296 152535517
+9297 309761992
+9298 1027858387
+9299 293201083
+9300 458277684
+9301 226330352
+9302 1541643618
+9303 544207885
+9304 449449791
+9305 1535716564
+9306 756922633
+9307 1107335961
+9308 1928082302
+9309 1856527233
+9310 1375487463
+9311 1680294895
+9312 848259305
+9313 1014396304
+9314 1941305145
+9315 769870290
+9316 120418525
+9317 200258198
+9318 1540507849
+9319 526944480
+9320 887528282
+9321 1073031803
+9322 86729323
+9323 472633800
+9324 1422180130
+9325 209850880
+9326 27681518
+9327 1574715647
+9328 519612872
+9329 1055539905
+9330 1867916730
+9331 977890556
+9332 1281870257
+9333 1262076701
+9334 1522098441
+9335 1731320048
+9336 650309617
+9337 131537426
+9338 691172361
+9339 430908271
+9340 1988064659
+9341 2066659825
+9342 2111203167
+9343 688840316
+9344 933572481
+9345 1905024664
+9346 1458710607
+9347 1053991006
+9348 2105282863
+9349 851734808
+9350 1580935486
+9351 845327497
+9352 1924766611
+9353 1667664809
+9354 1317961297
+9355 1199463094
+9356 1877515689
+9357 1345642815
+9358 626695093
+9359 249644913
+9360 253699072
+9361 347128176
+9362 1227535469
+9363 1535569329
+9364 1609204877
+9365 602150263
+9366 1119405730
+9367 112030846
+9368 733687689
+9369 1810578091
+9370 542939118
+9371 574268701
+9372 1729754268
+9373 506658637
+9374 1263109017
+9375 515843101
+9376 264199653
+9377 574335976
+9378 1569834107
+9379 221998868
+9380 1426070784
+9381 1003285945
+9382 1067326365
+9383 1203353748
+9384 523467107
+9385 237804015
+9386 255333194
+9387 253499148
+9388 1583446830
+9389 882028287
+9390 503144062
+9391 1837145903
+9392 1229156463
+9393 1730679531
+9394 1225231584
+9395 690877692
+9396 185346146
+9397 197153666
+9398 802908539
+9399 919033836
+9400 2007731758
+9401 1345847657
+9402 1493302537
+9403 1590002378
+9404 1852506294
+9405 608927906
+9406 2105845480
+9407 2116705947
+9408 1183263883
+9409 1528195939
+9410 191221168
+9411 461851019
+9412 383998237
+9413 1258547533
+9414 1665204767
+9415 907465344
+9416 1496351548
+9417 1920537961
+9418 1160964492
+9419 932314731
+9420 655082601
+9421 1664108554
+9422 621976986
+9423 1884239064
+9424 1247304438
+9425 1847208570
+9426 427633109
+9427 1432650584
+9428 2044362237
+9429 1230541648
+9430 204200772
+9431 1904610347
+9432 428905657
+9433 1697503309
+9434 1347129077
+9435 133928303
+9436 158947568
+9437 1305490909
+9438 103150602
+9439 1342211451
+9440 686203201
+9441 294371770
+9442 1804062470
+9443 1070201438
+9444 1552919304
+9445 1321783590
+9446 1977666782
+9447 901787204
+9448 1094837903
+9449 991147626
+9450 1834101935
+9451 1749920504
+9452 507772533
+9453 308595273
+9454 1486675921
+9455 1755076971
+9456 8320196
+9457 1914309030
+9458 1040243907
+9459 2052682433
+9460 997367030
+9461 1244444680
+9462 1809809132
+9463 1426272687
+9464 794464341
+9465 1009454561
+9466 1560200990
+9467 953411909
+9468 167461823
+9469 1663351592
+9470 148139712
+9471 853665024
+9472 1957723363
+9473 1952202183
+9474 1923866462
+9475 1363159019
+9476 1126502125
+9477 1754049596
+9478 117462575
+9479 73856380
+9480 597713574
+9481 1951564511
+9482 1823776885
+9483 1105486107
+9484 112676136
+9485 1162969158
+9486 713079430
+9487 120996332
+9488 929794540
+9489 1753323338
+9490 26195117
+9491 1927161570
+9492 850284370
+9493 1836004249
+9494 1205950609
+9495 1644748711
+9496 697975163
+9497 618667951
+9498 450676973
+9499 865436986
+9500 134535895
+9501 598816685
+9502 1719102010
+9503 2092259258
+9504 403535220
+9505 1495484824
+9506 1307934629
+9507 1530037345
+9508 1102050772
+9509 1425397205
+9510 1603893726
+9511 1699764346
+9512 1229478068
+9513 1280186963
+9514 657766806
+9515 1342154204
+9516 295672473
+9517 1370846236
+9518 1463150537
+9519 1225467013
+9520 976685926
+9521 1489345654
+9522 1005144935
+9523 1826970296
+9524 1177866256
+9525 63611896
+9526 1324235360
+9527 1875841419
+9528 682279847
+9529 1774912333
+9530 593794757
+9531 816815742
+9532 226245370
+9533 165413119
+9534 761591353
+9535 629780591
+9536 1660897943
+9537 2069525982
+9538 12334288
+9539 615465067
+9540 1347439539
+9541 1616228014
+9542 167745765
+9543 429433959
+9544 748931329
+9545 825512571
+9546 1771588164
+9547 1044603802
+9548 48875160
+9549 1087255053
+9550 122587167
+9551 1025561086
+9552 429117059
+9553 1127732102
+9554 705047735
+9555 1606983315
+9556 1191343998
+9557 2029283095
+9558 1335341086
+9559 1873623845
+9560 1656711780
+9561 1929135843
+9562 542955940
+9563 1882957150
+9564 2094548962
+9565 1304547293
+9566 365254093
+9567 1607963257
+9568 1226589627
+9569 377588382
+9570 75944676
+9571 426545519
+9572 1993816396
+9573 243690442
+9574 855979478
+9575 595264078
+9576 1069203013
+9577 480083994
+9578 1639867880
+9579 1118078173
+9580 1567339047
+9581 1762455048
+9582 2143639260
+9583 1996456107
+9584 742703502
+9585 701203347
+9586 1455955774
+9587 1934047501
+9588 583002794
+9589 643813213
+9590 1660187698
+9591 92230926
+9592 425465408
+9593 55659990
+9594 1975188076
+9595 372530723
+9596 1360207283
+9597 192958522
+9598 1980493980
+9599 439313263
+9600 570546904
+9601 2056438657
+9602 865858782
+9603 416879652
+9604 152645451
+9605 1721838260
+9606 1012143730
+9607 1221848464
+9608 54438607
+9609 504527963
+9610 192442990
+9611 1621777654
+9612 119499363
+9613 188598602
+9614 1470750113
+9615 862202865
+9616 889801949
+9617 779222240
+9618 648766718
+9619 1472804743
+9620 1423035453
+9621 161470769
+9622 1565035669
+9623 1848500861
+9624 217130759
+9625 1392740097
+9626 73547936
+9627 1577338043
+9628 1585698619
+9629 2054041917
+9630 2016651306
+9631 8761875
+9632 1962996926
+9633 735026440
+9634 425641528
+9635 2115642377
+9636 309381052
+9637 1437785258
+9638 1190007193
+9639 363819659
+9640 1942313221
+9641 1382450183
+9642 1985597314
+9643 2061812584
+9644 1571048785
+9645 1308863779
+9646 776531802
+9647 313367086
+9648 2088086019
+9649 1425298520
+9650 1786171829
+9651 1363637824
+9652 1586769289
+9653 1203723850
+9654 1064655038
+9655 1803900049
+9656 448980300
+9657 1138202974
+9658 1233754444
+9659 2034678919
+9660 1044761243
+9661 1102922102
+9662 2043440795
+9663 860274521
+9664 1837948542
+9665 321598675
+9666 828433250
+9667 2147329594
+9668 1759383933
+9669 2018440444
+9670 363665606
+9671 1554213507
+9672 1253406979
+9673 201779272
+9674 1468542443
+9675 676972117
+9676 1510643051
+9677 97590597
+9678 990339203
+9679 1451245423
+9680 1522889118
+9681 629027385
+9682 667399599
+9683 962174759
+9684 1832751235
+9685 1732054637
+9686 618591160
+9687 134247887
+9688 722773964
+9689 1852345604
+9690 21443159
+9691 1767535207
+9692 807784058
+9693 2064883954
+9694 480326081
+9695 498248952
+9696 238998981
+9697 1308759331
+9698 498094899
+9699 1998382914
+9700 1179716127
+9701 861760505
+9702 1405112773
+9703 285639459
+9704 1063539777
+9705 726171569
+9706 962611576
+9707 426699180
+9708 823762166
+9709 1952950779
+9710 1877944603
+9711 199167636
+9712 434494516
+9713 397860555
+9714 1161342396
+9715 119762104
+9716 2129915192
+9717 1779933556
+9718 254009991
+9719 705205508
+9720 1484795513
+9721 275453150
+9722 325257068
+9723 145095923
+9724 192853456
+9725 805583149
+9726 643344876
+9727 431852437
+9728 2114342480
+9729 1141439775
+9730 282751704
+9731 1146574960
+9732 2003200280
+9733 1687864477
+9734 1432214419
+9735 919256409
+9736 266552398
+9737 247342347
+9738 1345955589
+9739 1090314565
+9740 52809478
+9741 1076416545
+9742 1289482201
+9743 487303995
+9744 1474277100
+9745 303340949
+9746 607066099
+9747 1456708644
+9748 2083274506
+9749 861076090
+9750 14430505
+9751 1420586371
+9752 1136529241
+9753 339687573
+9754 1565682294
+9755 1329382697
+9756 1145270722
+9757 61543522
+9758 1761235135
+9759 1112129554
+9760 1202983297
+9761 2043986839
+9762 111220866
+9763 1058699929
+9764 1584367668
+9765 1543435285
+9766 1977956338
+9767 1850920067
+9768 1790777632
+9769 1176428280
+9770 793750984
+9771 1843587111
+9772 105361177
+9773 2083233185
+9774 183407458
+9775 1579638277
+9776 239090487
+9777 790473557
+9778 888863273
+9779 174881345
+9780 1651549647
+9781 903293778
+9782 1595467716
+9783 640595240
+9784 1242981351
+9785 1013666362
+9786 1969977938
+9787 240768425
+9788 1075209885
+9789 1583729425
+9790 1352897980
+9791 130709534
+9792 1480232616
+9793 1464118846
+9794 1189409464
+9795 917116636
+9796 860070484
+9797 1019882154
+9798 620553055
+9799 503364468
+9800 48826786
+9801 1414304039
+9802 199467931
+9803 154187963
+9804 1350053577
+9805 382875389
+9806 1733826240
+9807 1589144064
+9808 1173348946
+9809 475205866
+9810 1764025409
+9811 677414946
+9812 1378499644
+9813 1212009477
+9814 1318010186
+9815 473997348
+9816 78192191
+9817 1140504476
+9818 714765773
+9819 1153402076
+9820 576750253
+9821 2067663753
+9822 1284111611
+9823 2056982869
+9824 1384298952
+9825 326037427
+9826 826615858
+9827 96885788
+9828 1345919581
+9829 1447168913
+9830 600250256
+9831 1394746368
+9832 713989305
+9833 799718188
+9834 1548934331
+9835 2064042882
+9836 1182593577
+9837 1135276924
+9838 1505703298
+9839 208458876
+9840 1610482790
+9841 1122245059
+9842 885873822
+9843 841498786
+9844 186770888
+9845 56400360
+9846 1315496134
+9847 264963079
+9848 1196904837
+9849 2030261908
+9850 1418365156
+9851 1773655090
+9852 1950442013
+9853 554993119
+9854 1683154312
+9855 1187257317
+9856 881030546
+9857 362286522
+9858 1284143105
+9859 79466479
+9860 1809455435
+9861 1884393362
+9862 1474212847
+9863 375961092
+9864 536627902
+9865 875663531
+9866 292520326
+9867 1719221479
+9868 2010940455
+9869 1798223624
+9870 1927680355
+9871 1473939597
+9872 772985035
+9873 666070529
+9874 167954735
+9875 959755923
+9876 722470890
+9877 1483450870
+9878 1224719003
+9879 1919375727
+9880 1366229130
+9881 495600511
+9882 1545547169
+9883 1169187495
+9884 1050593630
+9885 1081217833
+9886 208961165
+9887 1931624176
+9888 1443504355
+9889 1493104270
+9890 2011090655
+9891 1105476143
+9892 1230013984
+9893 1337819855
+9894 1481437235
+9895 1766641886
+9896 65999738
+9897 1773957562
+9898 1338379718
+9899 2076940193
+9900 1424697538
+9901 1118576425
+9902 1403396142
+9903 50198926
+9904 1784646955
+9905 1571350877
+9906 1009954849
+9907 359634197
+9908 907318099
+9909 87190204
+9910 131526276
+9911 126063581
+9912 582790715
+9913 1677073445
+9914 1295251077
+9915 1633384345
+9916 610807631
+9917 1504212242
+9918 1417524873
+9919 2054311986
+9920 849832864
+9921 1281131881
+9922 1012304481
+9923 2079846849
+9924 471468088
+9925 346258069
+9926 1699005087
+9927 537467826
+9928 2120215631
+9929 889901157
+9930 466924371
+9931 1397429521
+9932 2008477583
+9933 1870320513
+9934 1447628447
+9935 1645640890
+9936 1294187742
+9937 310099649
+9938 2005275087
+9939 54022194
+9940 397289853
+9941 2136801363
+9942 180085775
+9943 980080569
+9944 1666391160
+9945 1475336852
+9946 465981266
+9947 129715143
+9948 832065446
+9949 1883506140
+9950 36543482
+9951 1681898311
+9952 1017154373
+9953 1048847963
+9954 1614261512
+9955 1488622461
+9956 1395106032
+9957 1165782951
+9958 2026090287
+9959 1367838015
+9960 2055684109
+9961 345531010
+9962 617783889
+9963 1916678044
+9964 68367875
+9965 2065412336
+9966 1414835286
+9967 1362555617
+9968 228028337
+9969 1272626725
+9970 1416577811
+9971 625318191
+9972 1261944440
+9973 1596663587
+9974 1605398760
+9975 780851952
+9976 924516791
+9977 2071380026
+9978 910567096
+9979 1756582238
+9980 1807402518
+9981 947110578
+9982 1290996901
+9983 677073243
+9984 1995958541
+9985 757774765
+9986 18212056
+9987 1243580926
+9988 1923557716
+9989 2044302343
+9990 463935293
+9991 1831758177
+9992 242349705
+9993 1081719182
+9994 1600952573
+9995 310717580
+9996 999647871
+9997 868304211
+9998 1673273198
+9999 1227676208
diff --git a/src/test/regress/data/jsonb.data b/src/test/regress/data/jsonb.data
new file mode 100644
index 0000000..622501b
--- /dev/null
+++ b/src/test/regress/data/jsonb.data
@@ -0,0 +1,1012 @@
+{"line":1, "date":"CB", "node":"AA"}
+{"cleaned":false, "status":59, "line":2, "disabled":false, "node":"CBB"}
+{"indexed":true, "status":35, "line":3, "disabled":false, "wait":"CAA", "subtitle":"BA", "user":"CCA"}
+{"line":4, "disabled":true, "space":"BB"}
+{"cleaned":false, "line":5, "wait":"BB", "query":"CAC", "coauthors":"ACA", "node":"CBA"}
+{"world":"CB", "query":"CBC", "indexed":false, "line":6, "pos":92, "date":"AAB", "space":"CB", "coauthors":"ACA", "node":"CBC"}
+{"state":98, "org":43, "line":7, "pos":97}
+{"auth":"BB", "title":"CAC", "query":"BA", "status":94, "line":8, "coauthors":"BBB"}
+{"auth":"BAC", "title":"CAA", "wait":"CA", "bad":true, "query":"AA", "indexed":true, "line":9, "pos":56}
+{"title":"AAC", "bad":true, "user":"AAB", "query":"AC", "line":10, "node":"AB"}
+{"world":"CAC", "user":"AB", "query":"ACA", "indexed":true, "line":11, "space":"CB"}
+{"line":12, "pos":72, "abstract":"BBA", "space":"AAC"}
+{}
+{"world":"CC", "query":"AA", "line":14, "disabled":false, "date":"CAC", "coauthors":"AB"}
+{"org":68, "title":"BBB", "query":"BAC", "line":15, "public":false}
+{"org":73, "user":"AA", "indexed":true, "line":16, "date":"CCC", "public":true, "coauthors":"AB"}
+{"indexed":false, "line":17}
+{"state":23, "auth":"BCC", "org":38, "status":28, "line":18, "disabled":false, "abstract":"CB"}
+{"state":99, "auth":"CA", "indexed":true, "line":19, "date":"BA"}
+{"wait":"CBA", "user":"BBA", "indexed":true, "line":20, "disabled":false, "abstract":"BA", "date":"ABA"}
+{"org":10, "query":"AC", "indexed":false, "line":21, "disabled":true, "abstract":"CA", "pos":44}
+{"state":65, "title":"AC", "user":"AAC", "cleaned":true, "status":93, "line":22, "abstract":"ABC", "node":"CCC"}
+{"subtitle":"AC", "user":"CCC", "line":23}
+{"state":67, "world":"ACB", "bad":true, "user":"CB", "line":24, "disabled":true}
+{}
+{"state":65, "title":"CBC", "wait":"AAC", "bad":true, "query":"ACA", "line":26, "disabled":false, "space":"CA"}
+{"auth":"BAA", "state":68, "indexed":true, "line":27, "space":"BA"}
+{"indexed":false, "line":28, "disabled":true, "space":"CC", "node":"BB"}
+{"auth":"BAB", "org":80, "title":"BBA", "query":"BBC", "status":3, "line":29}
+{"title":"AC", "status":16, "cleaned":true, "line":30}
+{"state":39, "world":"AAB", "user":"BB", "line":31, "disabled":true}
+{"wait":"BC", "bad":false, "query":"AA", "line":32, "coauthors":"CAC"}
+{"line":33, "pos":97}
+{"title":"AA", "world":"CCA", "wait":"CC", "bad":true, "status":86, "line":34, "disabled":true, "node":"ACA"}
+{}
+{"world":"BCC", "title":"ACB", "org":61, "status":99, "cleaned":true, "line":36, "pos":76, "space":"ACC", "coauthors":"AA", "node":"CB"}
+{"title":"CAA", "cleaned":false, "line":37, "abstract":"ACA", "node":"BC"}
+{"auth":"BC", "title":"BA", "world":"ACA", "indexed":true, "line":38, "abstract":"AAA", "public":true}
+{"org":90, "line":39, "public":false}
+{"state":16, "indexed":true, "line":40, "pos":53}
+{"auth":"AAB", "wait":"CAC", "status":44, "line":41}
+{"subtitle":"ACA", "bad":true, "line":42}
+{"org":19, "world":"BC", "user":"ABA", "indexed":false, "line":43, "disabled":true, "pos":48, "abstract":"CAB", "space":"CCB"}
+{"indexed":false, "line":44}
+{"indexed":true, "line":45}
+{"status":84, "line":46, "date":"CCC"}
+{"state":94, "title":"BAB", "bad":true, "user":"BBB", "indexed":true, "line":47, "public":false}
+{"org":90, "subtitle":"BAC", "query":"CAC", "cleaned":false, "line":48, "disabled":true, "abstract":"CC", "pos":17, "space":"BCA"}
+{"world":"CBC", "line":49}
+{"org":24, "line":50, "date":"CA", "public":false}
+{"world":"BC", "indexed":true, "status":44, "line":51, "pos":59, "date":"BA", "public":true}
+{"org":98, "line":52}
+{"title":"CA", "world":"ABC", "subtitle":"CBB", "line":53, "abstract":"BBA", "date":"ACB", "node":"CA"}
+{"user":"BAB", "cleaned":true, "line":54}
+{"subtitle":"CAA", "line":55, "disabled":false, "pos":55, "abstract":"AB", "public":false, "coauthors":"AA"}
+{"wait":"CC", "user":"CC", "cleaned":true, "line":56, "pos":73, "node":"ABC"}
+{"title":"BCC", "wait":"ABC", "indexed":true, "line":57, "disabled":false}
+{"org":42, "title":"BB", "line":58, "disabled":true, "public":true, "coauthors":"BCC"}
+{"wait":"CAB", "title":"CCB", "query":"BAC", "status":66, "line":59, "disabled":true}
+{"user":"CAC", "line":60}
+{"user":"BBB", "line":61, "disabled":false, "pos":31}
+{"org":18, "line":62, "coauthors":"CCC", "node":"CA"}
+{"line":63, "coauthors":"AB"}
+{"org":25, "wait":"CA", "line":64, "abstract":"BA", "date":"BBB"}
+{"title":"CB", "wait":"CC", "bad":false, "user":"BBB", "line":65, "abstract":"ACA", "public":true}
+{"line":66, "coauthors":"AC"}
+{"state":20, "wait":"CCB", "bad":true, "line":67, "abstract":"CB"}
+{"state":79, "wait":"BAC", "bad":false, "status":11, "line":68, "abstract":"BC", "public":true, "coauthors":"CBA"}
+{"state":39, "title":"CCA", "bad":false, "query":"BBA", "line":69, "pos":42, "public":false}
+{"title":"BC", "subtitle":"CA", "query":"BC", "line":70}
+{}
+{"bad":true, "query":"BBB", "line":72}
+{"state":35, "world":"CC", "bad":false, "line":73, "space":"BB", "public":false}
+{"title":"ACC", "wait":"CAB", "subtitle":"CB", "status":19, "line":74, "disabled":false, "space":"BAA", "coauthors":"CBC", "node":"AC"}
+{"subtitle":"BCB", "indexed":false, "status":83, "line":75, "public":true}
+{"state":32, "line":76, "disabled":false, "pos":66, "space":"CC"}
+{"state":43, "cleaned":true, "line":77}
+{}
+{"state":97, "wait":"CBA", "indexed":false, "cleaned":false, "line":79, "abstract":"CB", "date":"ACC", "public":false}
+{"user":"AAB", "line":80, "pos":85, "date":"AC"}
+{"world":"AC", "wait":"CC", "subtitle":"AAB", "bad":false, "cleaned":false, "line":81, "pos":91, "node":"CCC"}
+{}
+{"org":87, "bad":false, "user":"AAC", "query":"CCC", "line":83, "disabled":false, "abstract":"AC", "date":"CCA", "public":false}
+{"state":50, "line":84}
+{"wait":"AA", "subtitle":"AA", "query":"BB", "status":97, "line":85, "disabled":true, "abstract":"CB"}
+{}
+{"subtitle":"CA", "query":"BC", "line":87}
+{}
+{"title":"CC", "line":89, "disabled":false, "pos":49, "date":"CCB", "space":"CB", "node":"BB"}
+{"auth":"CC", "wait":"AA", "title":"BC", "bad":true, "line":90}
+{"state":37, "org":85, "indexed":false, "line":91, "space":"CAA", "public":true, "coauthors":"BA"}
+{"wait":"BBB", "title":"BBC", "org":95, "subtitle":"AC", "line":92, "pos":23, "date":"AC", "public":true, "space":"BBC"}
+{"org":48, "user":"AC", "line":93, "space":"CCC"}
+{}
+{"state":77, "wait":"ABA", "subtitle":"AC", "user":"BA", "status":43, "line":95, "public":false}
+{"title":"CA", "indexed":true, "status":26, "line":96}
+{"auth":"BCA", "subtitle":"ACC", "user":"CA", "line":97, "disabled":false, "node":"ACB"}
+{"query":"BB", "line":98, "coauthors":"AB"}
+{}
+{"auth":"AA", "title":"ACB", "org":58, "subtitle":"AC", "bad":false, "cleaned":false, "line":100, "space":"ACC", "public":true}
+{"subtitle":"AAB", "bad":false, "line":101, "public":true}
+{"subtitle":"AAA", "indexed":false, "cleaned":false, "line":102, "disabled":true, "pos":35}
+{}
+{"world":"CAC", "org":10, "query":"AAA", "cleaned":true, "status":79, "indexed":true, "line":104, "pos":65, "public":false, "node":"BAB"}
+{"bad":false, "line":105, "abstract":"BA", "node":"CBB"}
+{"world":"BB", "wait":"BAA", "title":"ACA", "line":106, "date":"CBC", "space":"BA"}
+{"state":92, "wait":"CAC", "title":"AAA", "bad":false, "line":107, "abstract":"CBC", "date":"BCC", "public":false}
+{"title":"CCC", "indexed":true, "line":108, "abstract":"ACB", "public":false, "coauthors":"ABB"}
+{"auth":"BB", "query":"ACC", "status":68, "line":109}
+{"user":"CC", "cleaned":false, "indexed":true, "line":110, "date":"BAA", "space":"BCB"}
+{"auth":"CC", "org":4, "wait":"BAC", "bad":false, "indexed":false, "line":111, "pos":55, "node":"BBC"}
+{"line":112, "disabled":true}
+{"org":66, "cleaned":true, "indexed":false, "line":113, "pos":96}
+{"world":"CA", "title":"ACA", "org":83, "query":"BAC", "user":"BBC", "indexed":false, "line":114}
+{"subtitle":"BCC", "line":115, "space":"AA", "public":true, "node":"CBA"}
+{"state":77, "status":23, "line":116}
+{"bad":false, "status":4, "line":117, "node":"CC"}
+{"state":99, "title":"BCC", "query":"AC", "status":98, "line":118, "date":"BA"}
+{"status":55, "line":119, "public":false, "coauthors":"BBA", "node":"BCA"}
+{"query":"CAA", "status":40, "indexed":false, "line":120, "disabled":false, "coauthors":"CA"}
+{"title":"BBC", "org":82, "subtitle":"ACB", "line":121, "abstract":"BB", "node":"CC"}
+{"state":66, "world":"AB", "subtitle":"BA", "query":"CB", "line":122, "abstract":"BBC", "pos":65, "date":"BAB"}
+{"state":96, "title":"CBC", "status":44, "line":123, "abstract":"BA", "space":"ACA", "node":"AAC"}
+{"auth":"CA", "state":59, "bad":false, "cleaned":false, "line":124, "pos":41, "date":"BBA", "coauthors":"ABB"}
+{"wait":"ACC", "line":125}
+{"org":30, "wait":"CBB", "subtitle":"CCA", "cleaned":true, "line":126, "date":"AC", "node":"ABC"}
+{}
+{"auth":"BBA", "org":66, "subtitle":"CCB", "bad":true, "cleaned":false, "line":128, "abstract":"BB", "public":true, "coauthors":"BA"}
+{"subtitle":"AC", "bad":false, "user":"BAA", "line":129, "date":"BCB", "node":"BAC"}
+{"wait":"CC", "subtitle":"CA", "line":130, "disabled":false, "pos":49, "node":"BA"}
+{"indexed":false, "line":131, "pos":79, "date":"AAA", "node":"CAC"}
+{"wait":"AC", "world":"CB", "title":"AAA", "user":"ABC", "indexed":false, "status":15, "line":132, "coauthors":"BA"}
+{"state":96, "bad":true, "line":133, "disabled":false, "space":"BAC", "coauthors":"ABA"}
+{"world":"BAC", "line":134}
+{"title":"CCC", "line":135, "coauthors":"CC"}
+{"cleaned":true, "line":136}
+{"bad":true, "query":"CCA", "user":"CA", "cleaned":false, "line":137, "disabled":true}
+{}
+{"world":"CC", "subtitle":"BBB", "line":139}
+{"wait":"CA", "status":2, "line":140}
+{"world":"BC", "bad":false, "user":"BBC", "query":"ACB", "line":141, "pos":33, "space":"ACA"}
+{"state":92, "title":"CA", "bad":true, "query":"AB", "line":142, "abstract":"BA", "date":"ABB", "space":"BC", "coauthors":"CAA"}
+{"state":79, "query":"AB", "user":"CCA", "indexed":true, "cleaned":true, "line":143, "public":true}
+{"org":37, "query":"CA", "cleaned":true, "line":144, "disabled":true, "date":"CC"}
+{"wait":"AC", "title":"CBA", "user":"AAA", "status":24, "line":145, "date":"CBC", "public":false, "coauthors":"BAC", "node":"ACC"}
+{"user":"CA", "indexed":true, "line":146, "disabled":false, "coauthors":"BA"}
+{"wait":"BC", "org":35, "bad":false, "query":"CBB", "line":147, "date":"AAA", "public":false, "space":"BBB"}
+{"org":56, "user":"AB", "indexed":true, "line":148}
+{}
+{"title":"CBB", "org":78, "subtitle":"CBA", "bad":true, "user":"AAB", "line":150, "disabled":true, "abstract":"BAC"}
+{"world":"CCA", "query":"BC", "cleaned":true, "indexed":false, "line":151, "abstract":"BC", "pos":43, "coauthors":"AB", "node":"CBA"}
+{"auth":"ABA", "status":13, "line":152, "date":"AA"}
+{"world":"CA", "line":153, "space":"CBC"}
+{"world":"BA", "user":"BBB", "status":72, "line":154}
+{"auth":"ABB", "line":155, "disabled":true, "node":"BBC"}
+{"world":"BBB", "bad":false, "line":156, "abstract":"CBC"}
+{"line":157, "pos":60, "node":"ACC"}
+{"line":158, "node":"CC"}
+{"line":159, "public":true}
+{}
+{"query":"BA", "status":53, "cleaned":false, "line":161, "public":true}
+{"line":162, "date":"CC"}
+{}
+{"title":"BC", "bad":false, "query":"CC", "line":164, "abstract":"CCB", "date":"BA"}
+{"status":36, "line":165}
+{"title":"AB", "bad":false, "status":64, "line":166, "abstract":"AB", "coauthors":"AA", "node":"AA"}
+{"wait":"AA", "line":167}
+{"subtitle":"CBC", "user":"AC", "cleaned":false, "line":168, "disabled":true, "coauthors":"BAB", "node":"CC"}
+{"state":34, "status":73, "cleaned":true, "line":169, "abstract":"BC", "public":false, "space":"BBC", "node":"BAA"}
+{"state":10, "auth":"BBB", "bad":true, "indexed":false, "status":34, "line":170, "abstract":"BC"}
+{"subtitle":"AAA", "bad":false, "user":"ACA", "status":53, "line":171, "disabled":false, "date":"AAA"}
+{"subtitle":"CB", "query":"CC", "indexed":true, "line":172, "node":"BBC"}
+{"state":5, "world":"ABC", "bad":false, "line":173, "public":false}
+{"subtitle":"AC", "line":174}
+{"auth":"AC", "org":72, "query":"CA", "indexed":false, "cleaned":true, "line":175, "disabled":true, "pos":54}
+{"title":"BCB", "bad":false, "line":176, "pos":35, "coauthors":"AAC", "node":"ABB"}
+{"title":"BB", "cleaned":true, "status":26, "line":177}
+{"state":61, "wait":"BB", "world":"CB", "query":"BAB", "line":178, "abstract":"BB", "date":"CBB", "space":"CA", "node":"AB"}
+{"wait":"CA", "cleaned":false, "indexed":true, "line":179, "space":"CBC"}
+{"org":68, "line":180}
+{"wait":"ABB", "subtitle":"CCC", "cleaned":true, "line":181, "abstract":"BC", "coauthors":"BA"}
+{"title":"ACA", "subtitle":"AAB", "line":182, "node":"BAC"}
+{}
+{}
+{"subtitle":"BA", "query":"BBB", "indexed":true, "cleaned":true, "line":185, "node":"BCC"}
+{"org":6, "title":"BCC", "user":"BA", "line":186, "pos":67, "abstract":"CBA", "coauthors":"CBB", "node":"CBC"}
+{"org":50, "title":"CAB", "subtitle":"CB", "query":"CBB", "line":187, "coauthors":"CA", "node":"CC"}
+{"bad":false, "line":188, "node":"CCB"}
+{"org":4, "world":"AAC", "query":"CAC", "line":189, "pos":90, "node":"AC"}
+{"state":86, "line":190, "pos":79}
+{"org":98, "title":"AAC", "cleaned":true, "line":191, "space":"BC", "coauthors":"AA"}
+{"wait":"CAA", "bad":false, "user":"BC", "status":23, "line":192, "disabled":true, "date":"CA", "coauthors":"BBB"}
+{"status":26, "line":193, "disabled":true}
+{"world":"CA", "subtitle":"CCC", "query":"ABB", "status":86, "line":194, "pos":97, "space":"CAC"}
+{"cleaned":true, "line":195}
+{"state":53, "org":84, "wait":"BC", "query":"BCC", "line":196, "disabled":true, "abstract":"AAC", "node":"CAC"}
+{"state":25, "status":70, "cleaned":false, "line":197, "disabled":true, "space":"AA", "public":false}
+{"org":82, "subtitle":"AAC", "line":198}
+{"org":87, "bad":true, "status":69, "line":199, "public":false}
+{"wait":"CC", "org":60, "subtitle":"BCA", "bad":true, "cleaned":false, "indexed":true, "line":200, "date":"BA"}
+{"state":9, "world":"CAA", "org":78, "user":"ACB", "cleaned":true, "line":201, "disabled":true, "abstract":"ACC", "public":false}
+{"state":50, "world":"AAA", "title":"CAA", "user":"AB", "status":37, "line":202, "disabled":false}
+{"org":36, "subtitle":"CB", "query":"BAA", "status":35, "line":203, "abstract":"CC"}
+{"auth":"CCC", "bad":true, "query":"CB", "status":84, "line":204, "disabled":false, "date":"BB"}
+{"auth":"AC", "query":"BA", "indexed":false, "line":205, "date":"AAB", "space":"ABB"}
+{"state":30, "world":"CCA", "query":"CC", "user":"BAA", "line":206}
+{"title":"CAB", "wait":"BAB", "bad":true, "query":"BCB", "indexed":true, "status":48, "cleaned":true, "line":207, "node":"ACB"}
+{"state":97, "subtitle":"BC", "status":99, "line":208, "abstract":"CB"}
+{"title":"CA", "world":"BBA", "bad":true, "indexed":false, "cleaned":false, "status":82, "line":209, "disabled":false, "pos":44, "space":"ACA"}
+{"line":210, "public":true}
+{"line":211, "space":"BBC", "node":"AAA"}
+{"wait":"BAA", "org":50, "line":212, "abstract":"BB", "public":true, "space":"AB"}
+{"line":213, "pos":57, "date":"CC", "space":"AC"}
+{"state":23, "user":"BAB", "query":"BCB", "line":214, "abstract":"BAB"}
+{"world":"ACB", "org":21, "line":215, "abstract":"AC", "public":false}
+{"state":14, "wait":"ACB", "org":79, "title":"BB", "subtitle":"BA", "line":216}
+{"wait":"BC", "line":217, "date":"BB"}
+{"wait":"AC", "user":"BB", "indexed":false, "status":83, "line":218}
+{"auth":"BC", "org":9, "user":"BA", "status":31, "line":219, "disabled":false}
+{"state":80, "world":"BA", "wait":"CA", "line":220, "pos":65, "node":"CAC"}
+{"wait":"AC", "subtitle":"ABB", "status":79, "indexed":true, "line":221, "abstract":"AC", "pos":33, "space":"BA"}
+{"state":69, "org":83, "world":"CBC", "subtitle":"CAC", "cleaned":false, "line":222, "space":"BC", "node":"CCA"}
+{"line":223, "abstract":"BC"}
+{}
+{"world":"BB", "title":"BC", "bad":false, "query":"BBC", "cleaned":false, "line":225, "disabled":false, "public":true}
+{"line":226, "date":"AC"}
+{"auth":"CB", "subtitle":"AB", "indexed":false, "status":2, "line":227, "pos":53, "space":"AB", "coauthors":"BCA"}
+{"title":"ABA", "org":36, "line":228, "space":"AA"}
+{"world":"AB", "line":229, "pos":78, "date":"BC", "space":"CC"}
+{"wait":"BBC", "org":47, "cleaned":true, "status":5, "line":230, "pos":2, "date":"CCA"}
+{"line":231, "coauthors":"CB"}
+{"state":1, "user":"CAA", "cleaned":false, "line":232, "date":"BA", "public":true, "coauthors":"AAA", "node":"BCC"}
+{"auth":"AB", "world":"CAC", "query":"BC", "cleaned":true, "line":233, "pos":47, "space":"AB", "node":"AB"}
+{"title":"CAA", "line":234, "pos":9, "public":true, "node":"AB"}
+{"auth":"CCA", "title":"AA", "org":6, "subtitle":"CA", "cleaned":true, "status":12, "indexed":false, "line":235, "space":"ABB"}
+{"auth":"CA", "bad":false, "query":"BC", "status":61, "cleaned":false, "line":236, "disabled":true, "public":true}
+{"user":"BCB", "line":237, "pos":70, "node":"CBA"}
+{"query":"CCB", "line":238, "disabled":true, "coauthors":"BAB", "node":"BC"}
+{"auth":"AC", "org":73, "title":"CA", "bad":false, "status":94, "line":239, "abstract":"CC"}
+{"subtitle":"BC", "indexed":false, "line":240, "disabled":true}
+{"auth":"AAC", "org":73, "title":"CB", "bad":true, "query":"CA", "cleaned":true, "line":241, "disabled":false, "public":false}
+{"line":242, "public":false}
+{"auth":"AC", "title":"BC", "status":61, "line":243, "disabled":false}
+{"auth":"ABB", "bad":false, "indexed":false, "line":244, "abstract":"BAB", "date":"ABC", "coauthors":"BC"}
+{"query":"BA", "line":245, "disabled":false, "space":"BAB"}
+{"world":"BCC", "bad":false, "indexed":false, "line":246, "disabled":true, "pos":80, "public":false, "coauthors":"BC"}
+{"indexed":true, "line":247}
+{"wait":"CCA", "subtitle":"CBB", "bad":false, "line":248, "pos":83, "public":false, "space":"BA"}
+{}
+{"auth":"ABA", "org":13, "title":"BA", "bad":false, "indexed":true, "line":250, "disabled":false, "abstract":"BBA", "date":"AB"}
+{"state":37, "title":"AAA", "bad":false, "line":251, "disabled":false, "coauthors":"CBC"}
+{"auth":"ACB", "world":"AC", "title":"CAA", "subtitle":"BCA", "bad":false, "status":32, "line":252, "pos":84}
+{"query":"BA", "indexed":false, "status":0, "line":253, "abstract":"CCB", "pos":48, "date":"AC", "space":"AAC"}
+{"subtitle":"BBA", "line":254, "node":"AAA"}
+{"query":"AC", "user":"CAA", "status":13, "line":255, "public":true, "coauthors":"BCC"}
+{"auth":"AAA", "state":31, "line":256}
+{}
+{}
+{"wait":"AC", "query":"AAA", "cleaned":true, "indexed":false, "line":259, "pos":89, "coauthors":"BCA", "node":"BC"}
+{"world":"CC", "query":"BB", "line":260}
+{}
+{"org":99, "bad":false, "user":"ABA", "line":262, "abstract":"BA", "coauthors":"BCC"}
+{"auth":"CAC", "world":"CBC", "subtitle":"CA", "bad":false, "status":22, "line":263, "pos":4, "public":true, "node":"BB"}
+{"wait":"BB", "subtitle":"BCC", "indexed":true, "line":264, "node":"CAC"}
+{"subtitle":"BB", "query":"CBB", "line":265}
+{"state":35, "query":"AA", "line":266, "coauthors":"AAA"}
+{"status":6, "line":267, "pos":66}
+{"auth":"BAA", "subtitle":"CCA", "bad":false, "query":"CCB", "line":268, "public":true, "space":"CAB", "node":"CAC"}
+{"world":"AC", "org":58, "user":"AC", "line":269, "node":"AB"}
+{"auth":"BCB", "org":36, "title":"AB", "line":270, "abstract":"CAB", "date":"CAB", "public":true, "coauthors":"CB", "node":"AB"}
+{"cleaned":true, "line":271}
+{"world":"ACC", "cleaned":true, "status":11, "line":272, "disabled":false, "abstract":"AA", "space":"BCA", "node":"BA"}
+{"cleaned":true, "line":273, "pos":50, "public":true}
+{"status":95, "line":274, "abstract":"BB", "coauthors":"AC"}
+{"auth":"BCC", "state":80, "cleaned":true, "line":275, "abstract":"AC"}
+{"wait":"BA", "line":276}
+{"org":62, "subtitle":"CAA", "query":"BA", "user":"BCC", "indexed":false, "line":277, "disabled":false, "abstract":"ACA", "date":"AB"}
+{"org":63, "bad":true, "line":278, "pos":26, "coauthors":"BA"}
+{"auth":"CBB", "indexed":false, "line":279, "pos":40, "space":"CA", "coauthors":"CC"}
+{"auth":"BA", "line":280, "abstract":"AAA", "public":true, "coauthors":"CAC"}
+{"org":10, "status":16, "line":281, "date":"CCC", "space":"AC"}
+{"org":76, "user":"BBC", "indexed":false, "line":282, "pos":56, "node":"CBA"}
+{"auth":"CA", "subtitle":"AB", "query":"AA", "indexed":true, "line":283, "disabled":false, "coauthors":"ABC", "node":"CAA"}
+{"title":"BA", "status":91, "line":284, "pos":7, "coauthors":"BB"}
+{"wait":"CCA", "line":285, "public":true}
+{"world":"AC", "line":286, "disabled":true}
+{"line":287, "abstract":"AAA"}
+{"user":"CCB", "status":50, "line":288, "public":false}
+{"state":41, "world":"CCC", "query":"AA", "line":289, "disabled":true, "pos":49, "public":false}
+{"wait":"CBC", "line":290, "abstract":"CCA", "space":"BBC"}
+{"auth":"CCB", "world":"BAB", "user":"CCC", "status":93, "line":291, "pos":77, "node":"BAC"}
+{"wait":"BCC", "org":8, "user":"AC", "cleaned":true, "line":292, "disabled":true, "pos":67, "date":"AA"}
+{"org":56, "query":"BCA", "line":293, "pos":81, "coauthors":"AAA", "node":"CAB"}
+{"world":"CB", "subtitle":"CBC", "bad":true, "query":"ACB", "indexed":false, "line":294, "pos":58, "date":"BC", "node":"CB"}
+{"wait":"BC", "user":"CA", "line":295}
+{"world":"ABA", "wait":"BA", "user":"BB", "status":65, "line":296, "pos":45, "date":"BB"}
+{}
+{}
+{"auth":"BA", "user":"AA", "indexed":false, "line":299, "space":"ABA", "public":false, "coauthors":"BC"}
+{"line":300, "space":"ABA"}
+{"state":36, "org":16, "world":"BBC", "status":13, "line":301, "public":false}
+{"subtitle":"CB", "user":"BC", "line":302, "date":"AA", "coauthors":"CAC"}
+{"wait":"CBC", "indexed":true, "cleaned":true, "line":303, "date":"ACC", "public":true}
+{"user":"CAC", "status":81, "line":304, "node":"CAB"}
+{"title":"CBB", "org":89, "subtitle":"CAA", "user":"CCA", "indexed":true, "line":305}
+{"state":10, "title":"CBA", "org":66, "cleaned":true, "line":306, "pos":59, "coauthors":"CAC"}
+{}
+{"auth":"AAA", "world":"AC", "wait":"ACA", "subtitle":"BAA", "status":64, "line":308, "node":"CCA"}
+{"state":31, "world":"CCC", "title":"BCB", "cleaned":false, "status":11, "line":309, "disabled":true, "date":"AA"}
+{"title":"BC", "subtitle":"CB", "indexed":false, "line":310, "disabled":true, "abstract":"BA", "space":"ACA"}
+{"wait":"ABB", "cleaned":true, "indexed":false, "line":311, "space":"CAB"}
+{}
+{"subtitle":"CA", "line":313}
+{"org":91, "title":"CAB", "line":314, "date":"CA"}
+{}
+{"state":65, "line":316, "node":"CC"}
+{"line":317, "space":"AA"}
+{}
+{"wait":"AA", "indexed":true, "line":319}
+{"wait":"BB", "org":42, "world":"AC", "subtitle":"ACC", "indexed":true, "line":320, "disabled":true}
+{}
+{"auth":"CAC", "line":322}
+{}
+{"line":324, "pos":38, "space":"CC", "node":"BBC"}
+{"title":"CC", "line":325, "public":true, "coauthors":"BAC", "node":"ACC"}
+{"world":"CC", "subtitle":"BBC", "bad":false, "user":"BA", "line":326, "date":"AAA", "space":"AA"}
+{"state":81, "title":"BC", "wait":"BA", "indexed":false, "status":48, "line":327, "coauthors":"AB"}
+{"line":328, "space":"ABB"}
+{"line":329, "date":"CCA"}
+{}
+{"auth":"BB", "world":"BAB", "subtitle":"BA", "query":"ABB", "line":331, "disabled":true, "date":"AAA", "node":"BC"}
+{"auth":"ABA", "title":"CC", "user":"CBA", "line":332, "disabled":true, "space":"ACC"}
+{"org":98, "subtitle":"ACB", "line":333, "abstract":"BC", "public":false, "coauthors":"BC", "node":"ABA"}
+{}
+{"world":"BC", "subtitle":"BAC", "user":"AB", "query":"BAA", "cleaned":true, "line":335, "space":"AC", "node":"BAA"}
+{"state":76, "indexed":true, "cleaned":false, "line":336, "node":"CAC"}
+{"org":95, "status":84, "line":337}
+{}
+{"world":"BBA", "title":"BCC", "subtitle":"ACB", "query":"BA", "line":339, "space":"ABC", "node":"AC"}
+{"title":"CBB", "user":"CBA", "cleaned":true, "line":340, "public":true, "space":"CB", "coauthors":"CAB"}
+{"wait":"AA", "status":82, "line":341}
+{"world":"CC", "line":342}
+{"auth":"BAB", "title":"CAC", "query":"BCC", "indexed":true, "line":343}
+{"org":77, "world":"BAC", "subtitle":"AA", "user":"ABA", "line":344}
+{"state":99, "org":56, "world":"CC", "title":"CAB", "wait":"CB", "subtitle":"BCC", "line":345, "pos":65}
+{"state":68, "org":97, "title":"AA", "indexed":true, "line":346, "node":"CC"}
+{"state":3, "title":"CBC", "user":"BAA", "status":98, "line":347, "disabled":true, "pos":96, "date":"BBA"}
+{"auth":"BAA", "world":"ABB", "line":348, "disabled":false, "abstract":"ACA", "pos":66, "space":"CCC", "coauthors":"CBB", "node":"BC"}
+{}
+{"status":54, "line":350}
+{"wait":"CC", "query":"ABA", "user":"AB", "status":76, "cleaned":false, "line":351, "abstract":"CBA"}
+{"line":352, "disabled":true, "public":false}
+{"state":93, "org":92, "status":88, "line":353, "space":"AB", "coauthors":"CB"}
+{"org":34, "wait":"ABC", "world":"CBA", "bad":false, "query":"BB", "indexed":false, "line":354, "date":"CB", "public":true}
+{"wait":"CBA", "title":"CAC", "cleaned":true, "indexed":true, "line":355, "pos":9, "date":"CAA"}
+{"user":"BC", "indexed":false, "cleaned":true, "status":73, "line":356, "disabled":true, "space":"CB"}
+{"state":20, "cleaned":false, "line":357, "pos":28, "abstract":"CCB", "space":"BC"}
+{"state":17, "wait":"ABC", "query":"CB", "cleaned":false, "status":4, "line":358, "disabled":false}
+{}
+{"state":83, "world":"CC", "org":53, "cleaned":false, "status":64, "line":360, "abstract":"CBC", "coauthors":"BC"}
+{"title":"BB", "indexed":false, "line":361}
+{"state":49, "wait":"BCA", "line":362}
+{"world":"CCC", "title":"CA", "query":"CCC", "cleaned":true, "line":363, "space":"AA", "coauthors":"AAC"}
+{"state":8, "wait":"BBB", "line":364, "pos":70, "public":false, "space":"BAA", "coauthors":"AB"}
+{"state":20, "indexed":false, "status":87, "cleaned":true, "line":365, "public":true}
+{}
+{"state":92, "title":"CCC", "subtitle":"CAB", "status":39, "line":367}
+{"state":54, "org":38, "line":368}
+{}
+{"auth":"ACA", "subtitle":"CBC", "status":52, "line":370, "date":"ACC", "public":true}
+{"indexed":true, "line":371, "pos":98, "node":"CBA"}
+{"world":"BA", "status":40, "line":372, "coauthors":"AA"}
+{}
+{"query":"BA", "indexed":false, "cleaned":true, "line":374, "date":"BCC"}
+{"query":"CA", "indexed":true, "line":375, "public":false}
+{"auth":"CCA", "wait":"BBC", "bad":false, "status":91, "line":376, "abstract":"BBC", "date":"ABA"}
+{"user":"BA", "query":"CB", "status":86, "indexed":false, "line":377, "pos":83, "abstract":"BCC", "space":"CBC", "public":true}
+{"title":"ACA", "org":15, "wait":"CBC", "status":85, "line":378}
+{"state":57, "bad":true, "line":379, "abstract":"BC", "date":"CAC"}
+{"world":"CC", "cleaned":true, "line":380}
+{"title":"CB", "subtitle":"AC", "line":381, "public":false}
+{}
+{}
+{"status":12, "line":384, "coauthors":"CC"}
+{"auth":"BAC", "bad":false, "line":385, "abstract":"CBB", "public":false, "space":"BBC"}
+{}
+{}
+{"world":"BBC", "bad":true, "status":71, "cleaned":false, "line":388, "node":"BB"}
+{"cleaned":false, "line":389}
+{"state":73, "line":390}
+{"wait":"BB", "org":5, "subtitle":"BAA", "bad":false, "indexed":false, "line":391, "public":false, "node":"BAA"}
+{"auth":"CCC", "org":51, "bad":false, "cleaned":true, "line":392, "space":"AC", "node":"CC"}
+{}
+{"line":394, "abstract":"ACC", "public":true}
+{"org":44, "subtitle":"BAC", "query":"BAC", "line":395}
+{"wait":"BC", "line":396}
+{"state":68, "world":"AB", "title":"ABB", "user":"CBC", "cleaned":false, "indexed":true, "line":397, "abstract":"BA", "pos":11}
+{"world":"CA", "title":"AB", "subtitle":"BC", "user":"BCB", "line":398}
+{"bad":true, "query":"BCC", "line":399}
+{"wait":"BB", "user":"BB", "cleaned":true, "indexed":false, "line":400, "date":"BC", "public":false}
+{}
+{"wait":"BA", "line":402}
+{"title":"AC", "subtitle":"BCB", "query":"BA", "line":403}
+{}
+{"auth":"BA", "org":19, "query":"CCB", "line":405, "pos":82, "date":"CAA"}
+{"state":26, "world":"CB", "subtitle":"AB", "cleaned":false, "line":406, "disabled":true, "date":"AC"}
+{}
+{"state":11, "bad":true, "indexed":true, "line":408, "pos":79, "abstract":"BA", "date":"CB", "space":"BBA"}
+{"auth":"AC", "status":59, "line":409}
+{"org":15, "line":410, "disabled":true, "date":"BAC", "space":"CCA"}
+{}
+{}
+{"state":65, "world":"AB", "status":69, "line":413, "space":"BA"}
+{}
+{"title":"CCB", "line":415}
+{"title":"BAB", "subtitle":"CA", "indexed":false, "line":416, "public":true}
+{"wait":"CAB", "user":"CAB", "cleaned":true, "line":417, "date":"BC", "coauthors":"BBA"}
+{"subtitle":"ABA", "user":"BB", "query":"AA", "indexed":true, "line":418, "pos":8, "space":"BB", "coauthors":"CBA"}
+{"state":11, "indexed":true, "line":419, "node":"AA"}
+{"state":86, "cleaned":false, "line":420, "pos":2, "node":"CBC"}
+{"org":73, "line":421, "disabled":false}
+{"query":"BAC", "user":"CB", "status":69, "line":422}
+{"status":22, "line":423}
+{"auth":"CB", "wait":"CCA", "world":"AAB", "line":424, "disabled":false, "space":"BA", "public":false}
+{"state":81, "world":"AC", "subtitle":"CBA", "bad":true, "cleaned":false, "indexed":true, "line":425, "date":"AAB", "coauthors":"BC", "node":"BAC"}
+{"wait":"CB", "query":"BCC", "status":97, "line":426}
+{"org":47, "query":"CB", "cleaned":true, "line":427, "date":"CC"}
+{"org":33, "query":"AC", "status":48, "indexed":false, "line":428, "disabled":true, "abstract":"BC", "space":"ACC"}
+{"org":10, "query":"AB", "line":429, "pos":77, "date":"BC"}
+{"line":430, "pos":7, "abstract":"CCA", "space":"AA"}
+{"bad":false, "user":"CA", "query":"CAB", "line":431, "node":"AC"}
+{"auth":"CA", "bad":false, "line":432}
+{}
+{"auth":"BAA", "org":98, "title":"CCC", "world":"BAC", "line":434, "public":true}
+{"state":54, "wait":"AA", "user":"BBA", "indexed":false, "line":435, "disabled":true, "pos":12, "space":"AB"}
+{"world":"AC", "title":"CA", "query":"AAA", "line":436, "space":"AB", "coauthors":"AA"}
+{"auth":"CB", "wait":"CCC", "bad":false, "line":437, "pos":42, "date":"ABC", "space":"AB", "coauthors":"ABC"}
+{"auth":"CBB", "title":"BB", "query":"CB", "line":438, "pos":15, "abstract":"BC", "node":"BBB"}
+{"title":"CC", "line":439, "disabled":false}
+{"title":"AB", "line":440, "disabled":false}
+{"org":3, "bad":true, "user":"BCB", "query":"AB", "indexed":false, "cleaned":true, "line":441, "disabled":false, "space":"BA", "node":"BB"}
+{"state":62, "user":"BCC", "status":12, "line":442, "pos":58, "date":"CC", "node":"CB"}
+{"world":"BCB", "bad":true, "line":443, "space":"AAB"}
+{"state":56, "bad":false, "cleaned":false, "line":444, "disabled":false, "date":"CA", "space":"BBB", "public":true}
+{}
+{"org":31, "world":"ABC", "cleaned":true, "line":446, "disabled":true, "public":true, "coauthors":"CB"}
+{"state":54, "indexed":true, "line":447}
+{"state":98, "title":"AC", "wait":"AAC", "world":"BC", "bad":false, "line":448, "disabled":true, "public":true, "node":"ABB"}
+{"world":"AAC", "indexed":true, "line":449, "disabled":true, "pos":61}
+{"org":56, "title":"CA", "line":450}
+{"auth":"BBB", "line":451, "pos":58, "date":"BB", "space":"ABA"}
+{"auth":"AB", "world":"CA", "cleaned":true, "line":452}
+{"bad":true, "line":453, "disabled":true, "abstract":"AC", "pos":20, "date":"ABB", "node":"CAB"}
+{}
+{"state":91, "wait":"AC", "org":96, "world":"AA", "subtitle":"BBC", "query":"AA", "cleaned":true, "line":455, "public":false}
+{"status":99, "line":456, "disabled":true}
+{"org":86, "line":457, "public":true, "coauthors":"AC"}
+{"status":14, "cleaned":true, "line":458, "disabled":true}
+{"world":"AB", "user":"CB", "query":"AAB", "line":459, "pos":66, "public":false, "node":"BBA"}
+{"state":58, "world":"BB", "wait":"CBA", "title":"BCA", "line":460, "pos":95, "abstract":"CCA", "space":"BC", "coauthors":"CB"}
+{}
+{"auth":"CAC", "title":"AB", "query":"BBA", "user":"CB", "line":462, "abstract":"BCC", "pos":89, "coauthors":"ABB"}
+{"org":13, "bad":false, "query":"AA", "status":49, "line":463, "disabled":false}
+{"bad":true, "cleaned":false, "line":464, "coauthors":"BB"}
+{"org":14, "query":"BA", "line":465, "pos":25, "abstract":"BBA", "space":"AAA", "node":"CAC"}
+{"org":63, "title":"CA", "subtitle":"ACC", "query":"BAC", "status":76, "line":466, "abstract":"ACA"}
+{"wait":"BA", "subtitle":"BC", "line":467, "disabled":false, "abstract":"AC"}
+{"org":76, "title":"CA", "query":"AB", "line":468, "public":false}
+{"state":95, "world":"AC", "bad":false, "status":65, "cleaned":false, "line":469, "disabled":false}
+{"wait":"AB", "subtitle":"AA", "bad":false, "user":"CC", "query":"BBC", "status":6, "line":470, "date":"CCC"}
+{"state":82, "bad":true, "indexed":true, "line":471, "date":"BB", "coauthors":"AAA"}
+{}
+{"state":12, "auth":"ACB", "world":"CBC", "bad":false, "indexed":true, "line":473, "date":"CA", "space":"ABB", "coauthors":"CC"}
+{"subtitle":"AA", "bad":false, "user":"ACC", "line":474, "pos":86, "abstract":"CAC", "space":"BBA"}
+{"cleaned":true, "line":475}
+{"title":"CC", "wait":"BB", "status":6, "line":476, "abstract":"ACC", "date":"CB", "space":"BA", "public":true}
+{"state":96, "wait":"BA", "org":30, "subtitle":"BB", "user":"CBB", "status":19, "line":477}
+{"state":78, "org":99, "title":"CC", "line":478, "node":"BAB"}
+{"world":"CBC", "bad":false, "line":479, "date":"ACB", "public":true, "node":"CB"}
+{"state":0, "query":"ABC", "status":65, "line":480, "disabled":true, "space":"CBA", "node":"BA"}
+{"auth":"BAC", "org":24, "subtitle":"BBC", "bad":false, "user":"CAC", "line":481, "date":"BBB", "public":true, "coauthors":"CBA"}
+{"org":18, "bad":true, "cleaned":false, "status":3, "indexed":true, "line":482, "date":"BB", "coauthors":"ACC"}
+{"wait":"CB", "user":"AC", "line":483, "disabled":false}
+{"world":"AC", "subtitle":"AA", "query":"AAB", "line":484, "disabled":true, "space":"CAA"}
+{"line":485, "pos":2, "space":"CA"}
+{"org":42, "indexed":false, "line":486, "date":"CB"}
+{"org":3, "wait":"CAA", "subtitle":"CA", "cleaned":true, "line":487, "disabled":true}
+{"org":68, "subtitle":"CCB", "query":"CAA", "cleaned":false, "status":46, "line":488, "pos":87, "public":false, "node":"BC"}
+{}
+{"status":60, "cleaned":false, "line":490, "space":"CC", "node":"BCB"}
+{"state":42, "org":9, "subtitle":"CBA", "user":"BA", "status":96, "line":491, "pos":36}
+{"state":16, "title":"BCC", "user":"ABC", "indexed":false, "status":24, "line":492, "disabled":true, "node":"CBC"}
+{"auth":"CC", "wait":"BBB", "line":493, "disabled":false, "public":false, "coauthors":"AB"}
+{}
+{"wait":"BB", "title":"BBC", "subtitle":"BA", "status":3, "cleaned":false, "line":495, "disabled":false, "coauthors":"AB", "node":"BAC"}
+{}
+{"query":"CC", "indexed":false, "line":497, "coauthors":"CAC", "node":"BC"}
+{"auth":"BBA", "state":68, "line":498}
+{"state":21, "title":"CCB", "wait":"AAA", "subtitle":"CCC", "user":"BAA", "indexed":true, "line":499, "coauthors":"BB"}
+{"auth":"AAA", "subtitle":"CC", "bad":true, "user":"CC", "indexed":true, "line":500, "disabled":true, "date":"AB", "node":"AC"}
+{"auth":"BB", "title":"CCA", "user":"BA", "cleaned":true, "line":501, "pos":37, "space":"BA", "public":false}
+{"auth":"BCA", "line":502, "date":"BA"}
+{"world":"ABA", "bad":true, "indexed":false, "line":503, "disabled":true, "abstract":"AC", "pos":1, "public":false}
+{"auth":"BBB", "subtitle":"ACB", "line":504, "space":"AC", "node":"BB"}
+{"auth":"CAC", "state":19, "title":"ACA", "wait":"BA", "query":"CC", "line":505}
+{"subtitle":"BC", "cleaned":false, "indexed":false, "line":506, "date":"CAB", "public":false, "node":"ABC"}
+{"state":87, "wait":"CCC", "query":"CAC", "user":"CBB", "line":507, "abstract":"BBC", "date":"AA", "coauthors":"CA"}
+{"auth":"AC", "subtitle":"BC", "bad":false, "query":"ABA", "user":"CBB", "indexed":true, "cleaned":false, "line":508, "coauthors":"BA"}
+{"auth":"AA", "title":"ABA", "subtitle":"CCA", "query":"CC", "line":509, "pos":27, "node":"CBB"}
+{"org":5, "title":"CAC", "subtitle":"BBB", "line":510, "pos":76, "abstract":"AAB", "space":"AA"}
+{"bad":true, "line":511}
+{"wait":"ACB", "indexed":false, "line":512}
+{"auth":"CBA", "world":"BA", "bad":true, "user":"CBA", "query":"CC", "line":513, "public":false, "coauthors":"CC"}
+{}
+{"state":97, "wait":"BB", "line":515, "date":"CBC", "space":"CA"}
+{"auth":"CBC", "line":516, "disabled":true}
+{"state":91, "user":"CCA", "line":517, "coauthors":"BA", "node":"CBA"}
+{"bad":false, "cleaned":true, "line":518, "space":"AAB"}
+{}
+{"title":"CA", "cleaned":false, "status":38, "line":520}
+{"auth":"BCA", "world":"AC", "org":71, "user":"CA", "line":521, "abstract":"AAB"}
+{"bad":true, "line":522, "pos":28, "abstract":"BAA"}
+{"line":523, "coauthors":"CBC", "node":"AAB"}
+{"status":51, "cleaned":false, "line":524}
+{"query":"AAB", "line":525, "disabled":true, "date":"AA", "public":true, "coauthors":"CA"}
+{"org":15, "user":"AC", "cleaned":false, "line":526, "coauthors":"CAC", "node":"BAB"}
+{"world":"ABA", "line":527, "disabled":true, "public":true}
+{"auth":"BBC", "state":48, "bad":false, "line":528, "abstract":"BB", "date":"BAC", "space":"BA", "public":true}
+{"auth":"BA", "wait":"CAC", "subtitle":"ABC", "query":"CB", "indexed":false, "cleaned":false, "line":529, "disabled":true, "date":"CA"}
+{"wait":"AC", "world":"ABA", "org":55, "bad":true, "indexed":true, "line":530, "pos":32, "space":"BCA", "public":true}
+{"title":"CBC", "wait":"BAA", "line":531}
+{"world":"AA", "line":532, "pos":35, "space":"AAB", "public":true}
+{"line":533, "space":"AB", "coauthors":"BA"}
+{"auth":"CBC", "world":"BB", "line":534, "space":"ACA", "coauthors":"CBB"}
+{"wait":"ACA", "status":47, "line":535, "public":true, "node":"BAA"}
+{"org":16, "subtitle":"BBB", "line":536, "abstract":"AC", "space":"CB", "coauthors":"CC", "node":"CBC"}
+{"wait":"AAB", "line":537, "abstract":"AB", "space":"CAC"}
+{"query":"CAC", "line":538}
+{"world":"AC", "query":"AAA", "indexed":false, "status":18, "line":539, "pos":62, "space":"BC", "coauthors":"BAC"}
+{"org":30, "world":"AA", "query":"BC", "user":"BAC", "status":12, "cleaned":true, "line":540, "space":"AB"}
+{"org":30, "user":"CCB", "query":"BB", "cleaned":false, "line":541, "disabled":true, "public":true, "node":"CBA"}
+{}
+{"subtitle":"ABB", "bad":true, "line":543}
+{"subtitle":"BBB", "bad":true, "line":544, "pos":43, "coauthors":"ABB"}
+{}
+{"subtitle":"AB", "user":"BA", "line":546, "node":"CB"}
+{"title":"BBB", "user":"AA", "line":547, "abstract":"CBB", "pos":45}
+{"wait":"CCB", "title":"AC", "world":"AAA", "line":548, "abstract":"BBC", "pos":23, "coauthors":"ACC"}
+{"org":55, "subtitle":"BA", "line":549, "disabled":true, "date":"CB", "space":"AA"}
+{"org":39, "cleaned":true, "line":550, "public":false}
+{"state":41, "auth":"CC", "world":"CB", "line":551, "space":"AAB"}
+{}
+{"state":26, "bad":false, "query":"BAA", "status":84, "indexed":true, "line":553, "disabled":false, "coauthors":"CC", "node":"CBB"}
+{"world":"ABA", "user":"CCC", "query":"ABB", "line":554, "space":"ABC", "node":"AAA"}
+{"state":18, "wait":"CCB", "bad":true, "user":"BA", "line":555, "space":"CC", "coauthors":"BB", "node":"BBB"}
+{"auth":"AA", "state":71, "subtitle":"AA", "query":"ACC", "indexed":true, "line":556, "space":"BAB", "public":false}
+{"indexed":true, "cleaned":true, "line":557, "disabled":false, "abstract":"AB"}
+{"auth":"BCC", "title":"ACB", "world":"BCA", "user":"BAB", "cleaned":false, "line":558, "space":"BB", "coauthors":"CBC"}
+{}
+{"auth":"ACC", "org":18, "wait":"AB", "status":1, "indexed":true, "line":560}
+{"status":8, "line":561, "abstract":"BA", "public":false}
+{"state":27, "title":"ABA", "bad":true, "query":"AAB", "indexed":false, "line":562, "pos":86, "public":true, "coauthors":"BA"}
+{}
+{"title":"BAC", "wait":"CCC", "user":"BA", "line":564, "disabled":false, "date":"BB", "public":true, "space":"CB", "coauthors":"CCB"}
+{"wait":"CAA", "line":565, "pos":80, "space":"AB"}
+{"auth":"CBB", "subtitle":"BCA", "user":"CB", "line":566, "abstract":"BC", "date":"AB"}
+{"title":"CCB", "status":78, "line":567, "pos":68, "node":"BA"}
+{"auth":"BC", "query":"AB", "line":568, "space":"AB", "node":"BB"}
+{}
+{"line":570, "pos":54}
+{"world":"BBB", "user":"CC", "indexed":true, "line":571, "abstract":"CC", "coauthors":"BA", "node":"ABB"}
+{"state":41, "line":572}
+{"subtitle":"CBC", "cleaned":true, "line":573, "node":"BCB"}
+{"title":"ABA", "line":574, "pos":27, "space":"CC"}
+{"status":29, "indexed":false, "cleaned":false, "line":575, "pos":52, "public":false, "coauthors":"ACC"}
+{"title":"BBB", "org":86, "wait":"AAA", "user":"CC", "query":"CA", "line":576, "disabled":false, "date":"AB", "node":"BC"}
+{"line":577, "abstract":"CAA", "date":"BB"}
+{"auth":"CCC", "subtitle":"BBB", "query":"ABA", "line":578, "pos":99, "space":"CCB", "public":true, "coauthors":"ACA", "node":"ACB"}
+{"wait":"BCC", "line":579}
+{"state":99, "world":"BAC", "user":"CA", "line":580}
+{"state":55, "world":"AAA", "title":"AAA", "cleaned":false, "line":581, "date":"AC", "public":true, "node":"AA"}
+{"query":"ACC", "cleaned":true, "line":582, "disabled":false}
+{"auth":"AAB", "query":"BAC", "line":583}
+{"auth":"AA", "user":"BAC", "line":584}
+{}
+{"org":96, "wait":"BC", "bad":false, "cleaned":false, "status":96, "line":586, "pos":95}
+{"auth":"BC", "subtitle":"BCB", "bad":true, "user":"BBC", "line":587, "pos":79, "node":"BA"}
+{"state":55, "line":588}
+{"title":"ABC", "world":"AB", "subtitle":"CBC", "user":"BA", "query":"BAB", "line":589, "date":"AC", "node":"CB"}
+{"world":"BAA", "bad":false, "user":"AAB", "cleaned":false, "indexed":false, "line":590}
+{"title":"CB", "wait":"BC", "subtitle":"BAC", "cleaned":true, "line":591, "disabled":false, "abstract":"CBB", "public":false, "node":"ACC"}
+{"user":"BC", "line":592, "public":false}
+{}
+{"wait":"CC", "org":57, "title":"BAC", "line":594, "abstract":"AA"}
+{"auth":"BBC", "state":3, "world":"AAC", "query":"BA", "line":595, "coauthors":"BB"}
+{}
+{"subtitle":"CC", "user":"CC", "line":597}
+{"wait":"BBA", "user":"AAA", "line":598, "space":"ACB", "node":"AA"}
+{"auth":"BB", "user":"ABA", "line":599, "abstract":"AB", "node":"BA"}
+{}
+{"world":"AAA", "user":"BB", "cleaned":false, "line":601, "space":"AC", "coauthors":"ABB"}
+{"title":"CAB", "bad":false, "line":602, "coauthors":"ABB"}
+{}
+{"world":"CCC", "org":79, "line":604}
+{"org":56, "query":"AB", "cleaned":true, "indexed":true, "status":20, "line":605, "public":true, "coauthors":"ACA"}
+{"auth":"BBC", "org":13, "subtitle":"CC", "bad":true, "user":"ABC", "line":606, "date":"CA", "public":false}
+{"query":"BA", "line":607}
+{"bad":true, "line":608, "pos":12, "coauthors":"CB"}
+{"bad":false, "status":42, "line":609}
+{}
+{"bad":true, "line":611}
+{"auth":"CCA", "subtitle":"BC", "bad":true, "query":"CAA", "cleaned":false, "line":612, "public":false, "node":"CBA"}
+{"org":65, "query":"BC", "line":613}
+{}
+{"wait":"BAC", "title":"AAB", "user":"CAC", "line":615, "pos":69, "space":"CC", "node":"AAC"}
+{"bad":false, "line":616, "abstract":"AB", "pos":65, "coauthors":"BBB"}
+{}
+{"org":38, "world":"BA", "line":618, "coauthors":"AA", "node":"BC"}
+{"cleaned":false, "line":619, "disabled":false}
+{"auth":"BC", "line":620, "pos":79, "date":"AB", "coauthors":"BAA", "node":"CB"}
+{"auth":"CAA", "title":"CB", "user":"BAC", "cleaned":false, "line":621, "public":false, "space":"CBA"}
+{}
+{"bad":false, "status":12, "line":623}
+{"auth":"BBB", "wait":"BAC", "org":36, "title":"AB", "indexed":false, "cleaned":false, "line":624, "date":"AB", "coauthors":"CB"}
+{"wait":"AA", "subtitle":"AB", "query":"CCB", "line":625, "node":"CBB"}
+{"wait":"BC", "subtitle":"BA", "bad":true, "user":"AA", "line":626, "pos":3, "date":"BB"}
+{"org":28, "user":"BC", "query":"AC", "status":63, "line":627, "pos":45, "public":true, "node":"BC"}
+{"query":"BC", "status":47, "line":628, "disabled":false, "date":"CA", "public":false}
+{}
+{"wait":"CB", "line":630, "pos":67, "coauthors":"AC"}
+{"org":33, "world":"BBB", "query":"BB", "status":92, "line":631}
+{"state":65, "title":"AC", "world":"CBC", "query":"CBC", "line":632, "date":"CAC", "space":"CC", "coauthors":"CC"}
+{}
+{"auth":"CC", "query":"BCA", "status":46, "line":634, "disabled":false, "pos":69}
+{"wait":"CB", "line":635, "pos":34}
+{"state":9, "wait":"CC", "status":23, "line":636, "disabled":true, "date":"BB", "space":"AC"}
+{"user":"CCB", "indexed":false, "cleaned":true, "line":637, "pos":65, "date":"AA", "public":true}
+{"auth":"BC", "wait":"AB", "title":"BB", "bad":true, "line":638, "abstract":"ACC", "date":"BC", "public":false}
+{"state":44, "auth":"BC", "world":"CBC", "line":639, "disabled":false, "date":"CAA"}
+{"world":"CB", "title":"ACB", "user":"BA", "query":"AA", "line":640, "disabled":true, "space":"AC"}
+{"state":37, "line":641, "disabled":true, "pos":66}
+{"world":"AAA", "bad":true, "user":"AAA", "query":"BA", "line":642, "disabled":true, "coauthors":"CBC"}
+{"world":"BA", "title":"ABB", "org":96, "bad":false, "query":"AAA", "status":75, "cleaned":false, "line":643, "space":"BA"}
+{"state":36, "org":66, "subtitle":"AA", "query":"CA", "cleaned":true, "status":79, "line":644, "date":"CB"}
+{"wait":"BC", "line":645, "date":"CBA", "space":"BCB", "public":true, "node":"ABA"}
+{"auth":"BB", "org":37, "query":"CAA", "indexed":true, "line":646, "abstract":"CBA", "coauthors":"CBA"}
+{}
+{}
+{"state":58, "world":"BAB", "org":11, "user":"CC", "line":649}
+{"title":"CB", "status":19, "line":650, "disabled":false, "public":false, "coauthors":"AA"}
+{"user":"BBC", "indexed":true, "line":651, "disabled":true, "pos":8}
+{"query":"CC", "cleaned":true, "indexed":false, "line":652, "pos":67, "date":"AA"}
+{"auth":"AAC", "line":653, "disabled":true, "public":false, "coauthors":"AAA", "node":"CBB"}
+{"bad":true, "query":"AC", "line":654, "disabled":false}
+{"world":"CCA", "org":15, "bad":false, "user":"CCC", "line":655, "public":true, "space":"CC"}
+{"line":656, "coauthors":"BBB"}
+{"title":"BA", "line":657, "date":"ACB"}
+{"user":"BC", "query":"CC", "cleaned":true, "line":658, "pos":51, "abstract":"BA"}
+{"subtitle":"CCA", "user":"CCA", "cleaned":false, "line":659, "abstract":"BA", "pos":95, "date":"CA"}
+{"auth":"CA", "state":23, "org":19, "bad":false, "user":"BCB", "indexed":false, "line":660, "date":"ABA"}
+{"state":64, "org":97, "bad":false, "indexed":false, "line":661, "space":"BAB", "coauthors":"BB", "node":"BA"}
+{"status":11, "line":662}
+{"title":"BCC", "org":44, "subtitle":"ACB", "cleaned":false, "line":663, "pos":58}
+{"auth":"ABB", "bad":true, "line":664, "pos":82, "coauthors":"CC", "node":"AB"}
+{"bad":false, "cleaned":true, "status":25, "line":665, "disabled":false, "abstract":"BB", "public":true}
+{"wait":"AC", "user":"CB", "line":666, "pos":71, "abstract":"ACA", "coauthors":"CBB"}
+{"title":"AA", "bad":true, "user":"BB", "line":667, "date":"CA", "space":"BC", "node":"CC"}
+{}
+{"auth":"AAB", "line":669}
+{"wait":"AAC", "query":"ABA", "status":35, "line":670, "disabled":false, "pos":56}
+{"org":3, "line":671}
+{"state":46, "bad":false, "cleaned":true, "line":672}
+{"state":30, "org":9, "status":72, "line":673, "abstract":"ACA", "coauthors":"CB"}
+{"auth":"BB", "wait":"CA", "title":"BBB", "bad":true, "user":"AAA", "status":86, "indexed":false, "line":674, "node":"BCC"}
+{"indexed":true, "line":675, "pos":63}
+{"bad":true, "query":"CBB", "status":5, "line":676, "abstract":"CCC", "public":false, "space":"BB"}
+{"title":"BBB", "org":60, "bad":true, "cleaned":false, "line":677, "pos":82, "date":"BAA", "space":"BB", "coauthors":"CAA"}
+{}
+{}
+{"state":73, "bad":false, "cleaned":false, "line":680, "abstract":"CA", "date":"CCA", "space":"CB"}
+{"state":92, "query":"CC", "line":681, "abstract":"AB", "date":"BBB", "public":true, "coauthors":"CBA"}
+{"subtitle":"CCA", "line":682}
+{"world":"BAC", "subtitle":"AC", "line":683, "disabled":true, "abstract":"AA", "pos":55, "space":"AC", "node":"CA"}
+{"state":75, "world":"ACA", "query":"BC", "line":684, "coauthors":"AAC"}
+{"status":21, "line":685}
+{"state":39, "wait":"CB", "title":"CBC", "query":"BB", "cleaned":true, "line":686, "disabled":false}
+{"world":"CCA", "wait":"AB", "user":"CC", "query":"BB", "cleaned":true, "line":687}
+{"auth":"CAC", "state":94, "wait":"ACC", "title":"BBC", "user":"BB", "line":688, "disabled":false, "pos":16, "coauthors":"AAC"}
+{}
+{"org":43, "line":690}
+{}
+{"state":4, "title":"CA", "subtitle":"AA", "query":"BC", "line":692, "pos":57, "date":"BCA", "public":false, "coauthors":"ABB"}
+{"wait":"BBA", "line":693}
+{"auth":"BCA", "bad":false, "user":"BBA", "line":694, "disabled":false, "date":"CC", "public":true, "coauthors":"CB"}
+{"state":66, "wait":"BB", "user":"CC", "indexed":true, "line":695, "pos":99, "space":"BCA"}
+{"org":1, "line":696, "disabled":false, "space":"BCC", "coauthors":"BC"}
+{"auth":"BC", "cleaned":true, "indexed":false, "line":697, "space":"CBB"}
+{"wait":"AC", "indexed":false, "line":698, "pos":44}
+{"wait":"AA", "title":"BBB", "org":31, "indexed":true, "line":699, "disabled":false}
+{"auth":"BB", "world":"ACC", "bad":true, "indexed":false, "line":700, "abstract":"CB", "pos":5, "space":"ACB", "node":"CC"}
+{"cleaned":false, "line":701, "space":"CB"}
+{"line":702, "space":"CCC"}
+{"world":"CA", "subtitle":"ABA", "line":703, "pos":5, "date":"BA", "coauthors":"AB"}
+{}
+{"line":705, "date":"BBB"}
+{"state":10, "query":"CB", "status":70, "line":706, "abstract":"ABA", "date":"BC"}
+{"auth":"CB", "line":707}
+{"wait":"BBA", "cleaned":true, "line":708, "pos":94, "date":"CBC"}
+{"state":86, "org":5, "world":"BB", "indexed":false, "line":709, "date":"BBB", "space":"CA", "public":true}
+{"world":"ACA", "query":"ABC", "status":40, "line":710, "disabled":true, "public":true, "node":"CA"}
+{"bad":true, "line":711}
+{"query":"AB", "line":712, "coauthors":"BBC", "node":"AA"}
+{"user":"ABB", "line":713, "public":false, "space":"AAA", "node":"BBA"}
+{"auth":"AC", "wait":"BAC", "bad":true, "line":714, "public":false}
+{"line":715, "abstract":"CA", "public":false}
+{"user":"AC", "indexed":true, "line":716, "coauthors":"CB"}
+{"state":4, "title":"ABB", "org":26, "indexed":true, "line":717, "public":true, "coauthors":"CCA", "node":"AC"}
+{"wait":"CA", "title":"CCA", "world":"CCC", "line":718, "abstract":"ACA"}
+{"auth":"ACA", "org":29, "subtitle":"AA", "user":"CA", "status":24, "indexed":true, "line":719, "public":false, "node":"CA"}
+{}
+{"line":721, "disabled":true, "abstract":"BAC"}
+{"world":"BC", "line":722}
+{"state":27, "auth":"AA", "title":"BC", "world":"CC", "query":"BCC", "line":723, "disabled":true, "pos":9, "public":false, "node":"BCC"}
+{"org":78, "wait":"ABA", "cleaned":true, "indexed":true, "line":724, "date":"ACB", "space":"AA"}
+{"state":60, "line":725}
+{}
+{}
+{"wait":"ABA", "title":"CAC", "user":"CCC", "line":728}
+{"wait":"CC", "indexed":true, "status":39, "line":729, "disabled":true, "public":false}
+{"auth":"CB", "subtitle":"BBA", "line":730, "coauthors":"CAC"}
+{"world":"CBB", "line":731, "space":"BCB"}
+{"cleaned":true, "line":732}
+{"org":67, "bad":true, "line":733, "pos":9, "node":"ACC"}
+{"world":"BC", "wait":"CAC", "org":58, "subtitle":"ACC", "bad":true, "query":"CAA", "line":734, "abstract":"BCA", "pos":1, "public":true}
+{"state":45, "query":"AB", "indexed":false, "line":735, "pos":82, "date":"BC", "public":false, "coauthors":"BA"}
+{"state":68, "title":"BC", "cleaned":true, "status":34, "line":736, "disabled":true, "node":"BBB"}
+{"auth":"AC", "line":737}
+{"line":738, "date":"BA", "space":"CCC", "public":false}
+{"line":739, "node":"BAA"}
+{"org":72, "title":"BC", "line":740, "pos":51, "coauthors":"CA"}
+{"state":72, "user":"CCB", "query":"ACA", "line":741}
+{"org":80, "subtitle":"BBA", "bad":true, "user":"BC", "line":742, "pos":52, "coauthors":"BCA"}
+{}
+{"query":"BC", "line":744, "abstract":"AB", "public":false, "node":"BAC"}
+{"world":"CAC", "line":745}
+{"auth":"CBB", "title":"AA", "user":"AB", "line":746, "pos":35, "public":false, "space":"AAB"}
+{"state":69, "world":"AB", "org":78, "subtitle":"BA", "bad":false, "line":747, "node":"AAA"}
+{"bad":true, "line":748, "public":true}
+{"wait":"BC", "org":47, "query":"BBB", "line":749}
+{"title":"BBB", "line":750}
+{"org":33, "query":"CB", "line":751, "disabled":true}
+{"subtitle":"BB", "line":752, "space":"CC"}
+{"org":89, "line":753}
+{"auth":"ABA", "line":754, "coauthors":"ACC"}
+{"subtitle":"BA", "line":755, "pos":47}
+{"state":81, "subtitle":"CB", "query":"AB", "status":25, "cleaned":false, "line":756, "pos":72, "date":"BA", "coauthors":"BCA"}
+{"state":46, "status":88, "line":757, "disabled":false, "public":true}
+{"world":"AB", "line":758, "disabled":true, "abstract":"BB", "coauthors":"AAA"}
+{"query":"AC", "line":759, "abstract":"AAB"}
+{"auth":"BC", "indexed":false, "line":760, "abstract":"BA", "node":"CAA"}
+{"state":10, "auth":"BAC", "title":"BC", "query":"BCA", "cleaned":true, "line":761, "disabled":true, "space":"ACC", "coauthors":"ABA"}
+{"line":762, "disabled":true, "pos":43}
+{"world":"CBA", "user":"BBC", "indexed":true, "line":763}
+{"wait":"ACB", "query":"BA", "status":22, "line":764, "pos":70, "abstract":"BAC", "public":false, "space":"BC"}
+{}
+{"line":766, "disabled":false, "abstract":"CBC", "date":"CA"}
+{"title":"CC", "bad":true, "user":"BCC", "indexed":false, "line":767, "date":"BCB", "node":"AAA"}
+{"title":"CB", "line":768, "abstract":"AA", "node":"ABB"}
+{"org":21, "user":"ABC", "line":769, "abstract":"BB", "date":"CBB", "space":"CC"}
+{"auth":"AC", "org":66, "user":"CC", "line":770, "public":false, "space":"CA", "coauthors":"AA"}
+{"org":58, "line":771, "coauthors":"BCC", "node":"AC"}
+{}
+{"auth":"BC", "wait":"CC", "line":773, "abstract":"ACC", "pos":98, "date":"CCC", "space":"ABB", "node":"CB"}
+{}
+{"query":"BC", "user":"AC", "indexed":true, "line":775, "abstract":"AAA"}
+{"subtitle":"BAA", "indexed":false, "line":776}
+{"line":777, "pos":33, "date":"CCB", "public":true}
+{"world":"BCA", "bad":true, "line":778}
+{"auth":"CA", "line":779, "date":"AC", "space":"CAC"}
+{"title":"BB", "bad":false, "cleaned":true, "line":780, "disabled":false, "date":"BAB", "space":"ACB"}
+{"auth":"CAC", "title":"AAB", "subtitle":"CA", "bad":false, "line":781, "disabled":false, "space":"CB"}
+{"state":78, "auth":"AC", "bad":true, "status":46, "line":782, "abstract":"CCA", "pos":97, "public":true}
+{"user":"BBA", "line":783}
+{}
+{"state":63, "title":"CA", "cleaned":true, "line":785, "abstract":"BA", "space":"BCC"}
+{"line":786, "node":"CAC"}
+{"line":787, "pos":65}
+{"line":788, "space":"ABB"}
+{}
+{"org":14, "line":790, "abstract":"CAB", "coauthors":"BBC"}
+{"subtitle":"CBA", "cleaned":false, "line":791, "disabled":false, "pos":57, "node":"CB"}
+{"auth":"CAA", "org":84, "wait":"AB", "indexed":true, "status":51, "line":792, "abstract":"CC"}
+{"org":72, "bad":true, "line":793, "space":"ACA"}
+{}
+{"auth":"BC", "state":76, "wait":"CC", "user":"ABB", "cleaned":false, "line":795, "pos":99, "abstract":"CA"}
+{"wait":"CCA", "world":"CBC", "line":796, "date":"CB", "public":false}
+{"state":49, "line":797, "coauthors":"CC"}
+{"wait":"BBB", "title":"ABB", "org":74, "line":798, "disabled":false, "pos":34, "space":"BB"}
+{"line":799, "abstract":"CB"}
+{"state":84, "user":"ABB", "cleaned":false, "status":18, "line":800, "disabled":true, "date":"CCA", "node":"BA"}
+{"state":81, "auth":"CB", "world":"CA", "user":"CAA", "line":801, "date":"AC", "space":"CBC", "coauthors":"BCB"}
+{"org":4, "line":802, "disabled":false, "abstract":"ABA", "public":false}
+{"auth":"CBC", "state":99, "cleaned":true, "line":803, "disabled":true, "space":"BC", "node":"BBC"}
+{"auth":"AC", "wait":"CA", "cleaned":false, "line":804, "pos":54, "date":"BAA", "public":true, "space":"AB"}
+{}
+{"auth":"BCB", "wait":"BCC", "subtitle":"AAA", "line":806}
+{"line":807, "disabled":false, "space":"ACA"}
+{"org":96, "query":"CBA", "line":808, "disabled":false, "pos":74, "space":"CA", "public":false}
+{}
+{"state":12, "title":"AA", "bad":false, "status":20, "line":810, "disabled":true, "coauthors":"CAC", "node":"AB"}
+{"auth":"ABC", "line":811, "date":"CA"}
+{"title":"AB", "indexed":false, "line":812, "disabled":false, "node":"AAC"}
+{}
+{"world":"CBA", "status":15, "line":814, "abstract":"CBA"}
+{"status":49, "line":815, "pos":49}
+{"subtitle":"CAB", "line":816}
+{}
+{}
+{"world":"CAC", "title":"CB", "wait":"AA", "query":"CA", "indexed":true, "line":819, "disabled":true}
+{"auth":"ABB", "wait":"AC", "query":"CC", "cleaned":true, "indexed":false, "line":820, "abstract":"AA", "public":false, "node":"AB"}
+{"org":5, "wait":"BA", "indexed":true, "line":821, "node":"AB"}
+{"title":"CC", "wait":"CC", "bad":false, "query":"BCC", "indexed":false, "line":822, "pos":27, "date":"CB", "node":"CBA"}
+{"query":"BC", "status":28, "line":823, "public":false}
+{"status":1, "line":824, "abstract":"BB"}
+{}
+{"auth":"AA", "title":"BC", "query":"CA", "status":33, "line":826}
+{"state":9, "title":"BB", "subtitle":"ACC", "bad":true, "query":"BA", "status":41, "line":827, "abstract":"ACB", "public":false}
+{"auth":"AB", "subtitle":"CAB", "line":828, "public":false, "coauthors":"AB", "node":"BAC"}
+{"line":829, "disabled":false, "public":true, "node":"CBC"}
+{"auth":"BAB", "line":830}
+{"wait":"BBA", "bad":true, "indexed":false, "line":831, "space":"BB"}
+{"org":70, "wait":"BC", "world":"AC", "indexed":true, "status":96, "line":832, "disabled":true, "space":"AB"}
+{"state":8, "world":"BAB", "bad":true, "indexed":true, "status":18, "line":833, "date":"BA", "space":"BA"}
+{"query":"AB", "line":834}
+{"bad":true, "status":5, "line":835}
+{"world":"BAC", "subtitle":"BB", "bad":false, "user":"AB", "indexed":false, "cleaned":true, "line":836}
+{"line":837, "public":false}
+{"line":838, "pos":7}
+{"auth":"CA", "query":"ABB", "indexed":true, "line":839, "public":true}
+{"wait":"CC", "bad":false, "line":840, "date":"AAB", "public":false, "coauthors":"BCB"}
+{"auth":"AB", "state":97, "org":24, "line":841, "pos":41, "node":"BC"}
+{"wait":"BB", "world":"CBA", "user":"BAA", "status":18, "line":842, "date":"BAB", "public":true}
+{"title":"BA", "subtitle":"CA", "query":"CCB", "line":843, "space":"BB"}
+{"auth":"BB", "world":"ACA", "line":844, "pos":29}
+{"state":65, "org":40, "query":"CAA", "user":"AB", "indexed":false, "cleaned":false, "line":845}
+{"title":"CB", "cleaned":false, "indexed":false, "line":846}
+{"wait":"CAA", "indexed":false, "line":847, "disabled":false}
+{}
+{"title":"CB", "query":"CC", "line":849, "abstract":"CB", "pos":10, "public":true}
+{"auth":"CB", "subtitle":"BA", "cleaned":true, "line":850, "disabled":true, "pos":84}
+{"org":45, "wait":"BA", "query":"CCC", "line":851, "date":"BCA", "coauthors":"CAB"}
+{"state":84, "title":"CB", "line":852, "pos":71, "space":"CA"}
+{"line":853, "public":true, "node":"AA"}
+{"subtitle":"BC", "user":"AB", "cleaned":true, "line":854, "public":true}
+{"state":26, "wait":"BA", "world":"BBB", "user":"BB", "status":53, "line":855, "abstract":"ABA", "pos":72, "space":"AC"}
+{"line":856, "public":false, "coauthors":"CA"}
+{"wait":"ABB", "subtitle":"CBA", "line":857, "pos":44}
+{"auth":"ABA", "wait":"CC", "org":0, "bad":true, "query":"BB", "line":858, "disabled":false, "public":true}
+{"bad":false, "user":"AA", "line":859, "pos":90}
+{"world":"BCC", "title":"BAA", "bad":false, "user":"CAA", "query":"BBC", "line":860, "date":"CAA", "space":"BCB", "public":true, "coauthors":"CB"}
+{"title":"BAB", "world":"BC", "subtitle":"AA", "cleaned":false, "status":9, "line":861, "pos":95}
+{"title":"AC", "line":862}
+{"wait":"CB", "bad":false, "status":89, "line":863, "coauthors":"AB"}
+{"subtitle":"ACC", "indexed":true, "cleaned":true, "line":864}
+{"title":"AB", "subtitle":"CBB", "query":"ACA", "indexed":true, "line":865, "disabled":true}
+{"title":"BC", "world":"BB", "query":"AA", "user":"ACB", "status":43, "cleaned":false, "line":866, "coauthors":"CBC", "node":"ACB"}
+{}
+{"org":25, "wait":"AC", "indexed":false, "line":868, "disabled":false, "abstract":"CA", "pos":48}
+{"bad":true, "status":34, "line":869, "pos":32, "date":"AC", "public":true, "node":"AA"}
+{"state":33, "wait":"AAC", "indexed":true, "status":20, "line":870, "abstract":"BA"}
+{"wait":"CCC", "subtitle":"AC", "line":871, "disabled":false, "space":"BA", "public":true, "coauthors":"BCC"}
+{"status":49, "line":872}
+{"state":90, "title":"ACC", "world":"CBB", "subtitle":"BAB", "bad":false, "status":94, "line":873, "abstract":"CB"}
+{"title":"BCB", "line":874}
+{"cleaned":false, "line":875}
+{"wait":"BAA", "subtitle":"BBC", "line":876}
+{"auth":"AB", "org":35, "bad":false, "indexed":false, "line":877, "coauthors":"BBA"}
+{"line":878, "public":false}
+{}
+{"auth":"CB", "wait":"CBC", "indexed":false, "line":880, "public":true}
+{"query":"CC", "status":4, "line":881, "disabled":true, "node":"CA"}
+{"title":"BB", "line":882}
+{"state":53, "bad":false, "cleaned":false, "status":63, "line":883, "coauthors":"BAA"}
+{"auth":"ACA", "world":"AC", "user":"CBC", "line":884, "date":"BCA", "node":"BBC"}
+{"auth":"BAB", "state":11, "world":"CB", "org":77, "query":"BA", "cleaned":false, "line":885, "space":"AC"}
+{"world":"CA", "user":"CA", "line":886, "node":"CB"}
+{"state":32, "org":50, "wait":"AA", "line":887, "disabled":false, "space":"BBA", "public":false}
+{"cleaned":true, "line":888, "pos":70, "node":"ABC"}
+{"org":63, "user":"AAB", "query":"BB", "line":889, "date":"BC", "space":"CBB", "node":"ABC"}
+{"wait":"BB", "user":"BB", "query":"CB", "line":890, "space":"BB", "coauthors":"BA", "node":"ABC"}
+{"auth":"BC", "world":"CC", "subtitle":"CB", "line":891, "public":false, "coauthors":"BC"}
+{"state":13, "org":38, "line":892, "coauthors":"BC", "node":"ABC"}
+{"auth":"CC", "world":"CAC", "line":893, "date":"BBA", "node":"CBC"}
+{}
+{"auth":"AA", "line":895, "coauthors":"BB"}
+{"auth":"AA", "state":76, "status":85, "line":896, "date":"CCC", "public":true, "coauthors":"AB"}
+{"auth":"AB", "indexed":true, "cleaned":false, "line":897}
+{"line":898, "coauthors":"CBB"}
+{}
+{"wait":"ACC", "line":900, "abstract":"BBA"}
+{"auth":"AA", "wait":"BCB", "cleaned":false, "line":901, "abstract":"AAC"}
+{"state":68, "title":"AC", "subtitle":"BB", "line":902}
+{"state":41, "wait":"ABA", "bad":false, "user":"BBA", "status":46, "line":903, "node":"AAB"}
+{}
+{"cleaned":false, "line":905, "pos":33}
+{"bad":false, "query":"BA", "line":906, "pos":48, "space":"CB", "public":true}
+{"query":"CB", "indexed":true, "line":907, "pos":41, "abstract":"CBB", "space":"BA", "public":false, "node":"BC"}
+{"title":"AB", "line":908}
+{"auth":"BC", "title":"CB", "line":909, "disabled":false, "space":"CA", "public":true, "coauthors":"BC"}
+{"world":"AA", "user":"ABA", "indexed":false, "line":910, "abstract":"CC"}
+{"auth":"CCA", "indexed":false, "line":911, "date":"AC", "public":false}
+{"world":"AAB", "bad":true, "line":912}
+{"subtitle":"CBB", "line":913, "public":true}
+{"wait":"CB", "line":914, "disabled":false, "pos":71, "date":"BA", "space":"CBA", "public":false, "coauthors":"BB"}
+{"org":67, "wait":"CA", "bad":false, "line":915, "disabled":false, "public":true}
+{}
+{"line":917, "date":"CB"}
+{"auth":"CBB", "world":"AAA", "status":83, "indexed":false, "line":918, "disabled":true, "date":"CBA", "coauthors":"ACC"}
+{"wait":"AB", "title":"BA", "status":33, "line":919, "disabled":false}
+{"wait":"ACB", "cleaned":false, "line":920, "abstract":"AA", "coauthors":"BCB"}
+{"wait":"ABB", "org":40, "world":"BC", "subtitle":"CA", "user":"AAC", "status":14, "indexed":true, "line":921, "pos":66}
+{"auth":"BA", "org":22, "wait":"BAB", "bad":true, "user":"ACC", "status":32, "line":922}
+{"world":"BA", "query":"CAB", "status":0, "line":923}
+{"org":84, "bad":true, "line":924, "coauthors":"BAB"}
+{"auth":"ACC", "subtitle":"AAA", "query":"CCA", "cleaned":false, "line":925, "pos":60, "space":"BC"}
+{"wait":"BC", "subtitle":"CCC", "bad":false, "cleaned":false, "indexed":false, "line":926, "public":false, "coauthors":"AC"}
+{"auth":"CA", "state":91, "org":6, "world":"AA", "wait":"ABB", "query":"AAC", "line":927, "date":"CA", "node":"BAC"}
+{"world":"BCA", "query":"AA", "user":"BBC", "line":928, "disabled":false}
+{"world":"CBC", "user":"CBC", "line":929, "date":"CAC"}
+{"world":"BCB", "bad":false, "user":"BB", "line":930}
+{"auth":"CA", "world":"AA", "query":"ABA", "user":"AA", "indexed":true, "line":931, "coauthors":"BBC"}
+{"auth":"BAA", "bad":true, "line":932, "disabled":false, "pos":93, "abstract":"CCA", "date":"BBA", "coauthors":"AA"}
+{"line":933, "space":"CAB", "node":"AB"}
+{}
+{"status":60, "cleaned":true, "indexed":true, "line":935, "pos":35, "space":"CAB", "node":"BBB"}
+{"wait":"AAA", "bad":true, "status":26, "line":936, "abstract":"ACB", "space":"BBA", "coauthors":"CCC"}
+{"title":"BA", "bad":true, "line":937, "date":"BBA", "public":true}
+{}
+{"query":"BA", "user":"BA", "line":939, "disabled":true}
+{"line":940, "date":"CC"}
+{"wait":"CAB", "query":"BCA", "user":"BC", "line":941, "pos":8, "coauthors":"ACC"}
+{"line":942, "space":"BA"}
+{"auth":"CA", "org":11, "line":943, "pos":99}
+{"org":83, "line":944, "disabled":false, "date":"BBA", "space":"AC", "node":"AC"}
+{"world":"CCA", "line":945, "node":"BC"}
+{"org":95, "title":"CA", "world":"BA", "line":946, "pos":36, "coauthors":"CA"}
+{"state":93, "line":947}
+{"cleaned":false, "status":5, "line":948, "abstract":"BB", "public":false, "coauthors":"ABC"}
+{"world":"CA", "org":61, "bad":false, "query":"CC", "cleaned":true, "line":949, "pos":14, "space":"CC"}
+{"state":91, "line":950, "abstract":"BA", "date":"AB"}
+{"auth":"BBC", "line":951, "date":"BB"}
+{"auth":"BAB", "line":952, "disabled":true, "node":"AAA"}
+{"auth":"CAA", "subtitle":"ABA", "bad":true, "line":953}
+{"auth":"CA", "wait":"BB", "org":12, "user":"BCC", "cleaned":false, "line":954, "public":false, "coauthors":"AA"}
+{"org":93, "cleaned":true, "line":955, "disabled":true, "public":true, "node":"ACA"}
+{"line":956, "pos":10}
+{"org":74, "world":"CCC", "subtitle":"AB", "user":"AAA", "cleaned":true, "line":957, "pos":70, "public":true, "node":"CC"}
+{"state":51, "line":958}
+{"world":"CCA", "title":"BCB", "user":"AB", "indexed":true, "line":959, "disabled":true, "pos":21, "date":"CBC"}
+{"org":86, "wait":"BC", "query":"BB", "user":"AA", "indexed":true, "line":960, "pos":58, "date":"AB"}
+{"line":961, "node":"CC"}
+{"auth":"BCB", "world":"ACC", "subtitle":"CA", "bad":true, "user":"BA", "indexed":false, "line":962, "public":false}
+{}
+{"status":37, "line":964}
+{"state":70, "status":76, "indexed":false, "line":965, "disabled":true, "space":"BB"}
+{}
+{"state":67, "world":"CA", "title":"AA", "line":967, "abstract":"BA", "space":"BAA"}
+{"auth":"CA", "world":"AA", "bad":true, "query":"BC", "status":53, "indexed":false, "line":968, "date":"AB", "node":"BAA"}
+{"query":"AC", "cleaned":true, "line":969, "abstract":"BC", "space":"CAB", "coauthors":"BAA"}
+{"wait":"BCA", "world":"CB", "title":"BC", "indexed":false, "line":970, "disabled":true, "pos":70, "date":"AB"}
+{}
+{"subtitle":"BC", "query":"AA", "line":972}
+{"line":973, "public":true}
+{"org":75, "world":"AAB", "subtitle":"BB", "user":"CC", "line":974, "space":"CA"}
+{"auth":"BCB", "cleaned":true, "line":975}
+{"title":"BAC", "user":"CB", "line":976, "public":false}
+{"subtitle":"BAC", "indexed":false, "cleaned":false, "line":977, "disabled":false, "abstract":"ABC", "space":"ABA"}
+{"state":63, "bad":false, "line":978, "pos":93, "node":"AAC"}
+{}
+{"cleaned":false, "line":980, "abstract":"CCB"}
+{"state":40, "title":"ABA", "subtitle":"CAB", "query":"BC", "line":981, "date":"CA", "coauthors":"AB"}
+{}
+{"auth":"ABA", "subtitle":"ACC", "user":"AA", "query":"AC", "cleaned":true, "line":983, "date":"ACB", "node":"CB"}
+{"state":32, "title":"ABC", "org":58, "status":95, "line":984, "disabled":true, "pos":6, "space":"CBB"}
+{"title":"BCC", "subtitle":"CCC", "user":"BBC", "line":985, "public":false, "coauthors":"CCB", "node":"AA"}
+{"subtitle":"ACA", "query":"BCC", "status":43, "cleaned":true, "indexed":true, "line":986, "abstract":"CAC"}
+{}
+{"world":"CAB", "org":21, "indexed":true, "line":988, "abstract":"ABC"}
+{"title":"CBC", "status":66, "line":989}
+{}
+{"array":[5]}
+{"array":["foo", "bar", "baz"]}
+{"array":["bar", "baz", "foo"]}
+{"array":["bar", "baz"]}
+{"array":["baz", "foo"]}
+{"line":991, "abstract":"BA", "node":"BBB"}
+{"line":992, "disabled":true, "pos":29, "public":false}
+{"state":53, "wait":"CB", "subtitle":"CCC", "line":993, "date":"CAC", "public":false, "coauthors":"BB"}
+{"wait":"CBA", "title":"CA", "subtitle":"BB", "user":"BAA", "line":994, "disabled":true, "date":"BB", "coauthors":"CCC", "node":"CC"}
+{"title":"BB", "user":"AA", "query":"CAA", "status":43, "line":995, "pos":6, "abstract":"CC", "public":true}
+{"wait":"AC", "query":"BA", "line":996, "coauthors":"BB", "node":"CCC"}
+{"auth":"BC", "title":"CAC", "subtitle":"BA", "line":997, "date":"BAA"}
+{"wait":"AB", "user":"ABC", "line":998, "pos":41, "node":"CAC"}
+{"state":4, "title":"AC", "bad":true, "status":59, "line":999, "disabled":true}
+{"user":"BC", "line":1000}
+{"wait":null, "line":1000}
+{"age":25}
+{"age":25.0}
+{"foo": {"bar": "baz"}}
+{"foo": {"blah": "baz"}}
+{"fool": {"bar": "baz"}}
+{}
diff --git a/src/test/regress/data/onek.data b/src/test/regress/data/onek.data
new file mode 100644
index 0000000..1605bbe
--- /dev/null
+++ b/src/test/regress/data/onek.data
@@ -0,0 +1,1000 @@
+147 0 1 3 7 7 7 47 147 147 147 14 15 RFAAAA AAAAAA AAAAxx
+931 1 1 3 1 11 1 31 131 431 931 2 3 VJAAAA BAAAAA HHHHxx
+714 2 0 2 4 14 4 14 114 214 714 8 9 MBAAAA CAAAAA OOOOxx
+711 3 1 3 1 11 1 11 111 211 711 2 3 JBAAAA DAAAAA VVVVxx
+883 4 1 3 3 3 3 83 83 383 883 6 7 ZHAAAA EAAAAA AAAAxx
+439 5 1 3 9 19 9 39 39 439 439 18 19 XQAAAA FAAAAA HHHHxx
+670 6 0 2 0 10 0 70 70 170 670 0 1 UZAAAA GAAAAA OOOOxx
+543 7 1 3 3 3 3 43 143 43 543 6 7 XUAAAA HAAAAA VVVVxx
+425 8 1 1 5 5 5 25 25 425 425 10 11 JQAAAA IAAAAA AAAAxx
+800 9 0 0 0 0 0 0 0 300 800 0 1 UEAAAA JAAAAA HHHHxx
+489 10 1 1 9 9 9 89 89 489 489 18 19 VSAAAA KAAAAA OOOOxx
+494 11 0 2 4 14 4 94 94 494 494 8 9 ATAAAA LAAAAA VVVVxx
+880 12 0 0 0 0 0 80 80 380 880 0 1 WHAAAA MAAAAA AAAAxx
+611 13 1 3 1 11 1 11 11 111 611 2 3 NXAAAA NAAAAA HHHHxx
+226 14 0 2 6 6 6 26 26 226 226 12 13 SIAAAA OAAAAA OOOOxx
+774 15 0 2 4 14 4 74 174 274 774 8 9 UDAAAA PAAAAA VVVVxx
+298 16 0 2 8 18 8 98 98 298 298 16 17 MLAAAA QAAAAA AAAAxx
+682 17 0 2 2 2 2 82 82 182 682 4 5 GAAAAA RAAAAA HHHHxx
+864 18 0 0 4 4 4 64 64 364 864 8 9 GHAAAA SAAAAA OOOOxx
+183 19 1 3 3 3 3 83 183 183 183 6 7 BHAAAA TAAAAA VVVVxx
+885 20 1 1 5 5 5 85 85 385 885 10 11 BIAAAA UAAAAA AAAAxx
+997 21 1 1 7 17 7 97 197 497 997 14 15 JMAAAA VAAAAA HHHHxx
+966 22 0 2 6 6 6 66 166 466 966 12 13 ELAAAA WAAAAA OOOOxx
+389 23 1 1 9 9 9 89 189 389 389 18 19 ZOAAAA XAAAAA VVVVxx
+846 24 0 2 6 6 6 46 46 346 846 12 13 OGAAAA YAAAAA AAAAxx
+206 25 0 2 6 6 6 6 6 206 206 12 13 YHAAAA ZAAAAA HHHHxx
+239 26 1 3 9 19 9 39 39 239 239 18 19 FJAAAA ABAAAA OOOOxx
+365 27 1 1 5 5 5 65 165 365 365 10 11 BOAAAA BBAAAA VVVVxx
+204 28 0 0 4 4 4 4 4 204 204 8 9 WHAAAA CBAAAA AAAAxx
+690 29 0 2 0 10 0 90 90 190 690 0 1 OAAAAA DBAAAA HHHHxx
+69 30 1 1 9 9 9 69 69 69 69 18 19 RCAAAA EBAAAA OOOOxx
+358 31 0 2 8 18 8 58 158 358 358 16 17 UNAAAA FBAAAA VVVVxx
+269 32 1 1 9 9 9 69 69 269 269 18 19 JKAAAA GBAAAA AAAAxx
+663 33 1 3 3 3 3 63 63 163 663 6 7 NZAAAA HBAAAA HHHHxx
+608 34 0 0 8 8 8 8 8 108 608 16 17 KXAAAA IBAAAA OOOOxx
+398 35 0 2 8 18 8 98 198 398 398 16 17 IPAAAA JBAAAA VVVVxx
+330 36 0 2 0 10 0 30 130 330 330 0 1 SMAAAA KBAAAA AAAAxx
+529 37 1 1 9 9 9 29 129 29 529 18 19 JUAAAA LBAAAA HHHHxx
+555 38 1 3 5 15 5 55 155 55 555 10 11 JVAAAA MBAAAA OOOOxx
+746 39 0 2 6 6 6 46 146 246 746 12 13 SCAAAA NBAAAA VVVVxx
+558 40 0 2 8 18 8 58 158 58 558 16 17 MVAAAA OBAAAA AAAAxx
+574 41 0 2 4 14 4 74 174 74 574 8 9 CWAAAA PBAAAA HHHHxx
+343 42 1 3 3 3 3 43 143 343 343 6 7 FNAAAA QBAAAA OOOOxx
+120 43 0 0 0 0 0 20 120 120 120 0 1 QEAAAA RBAAAA VVVVxx
+461 44 1 1 1 1 1 61 61 461 461 2 3 TRAAAA SBAAAA AAAAxx
+754 45 0 2 4 14 4 54 154 254 754 8 9 ADAAAA TBAAAA HHHHxx
+772 46 0 0 2 12 2 72 172 272 772 4 5 SDAAAA UBAAAA OOOOxx
+749 47 1 1 9 9 9 49 149 249 749 18 19 VCAAAA VBAAAA VVVVxx
+386 48 0 2 6 6 6 86 186 386 386 12 13 WOAAAA WBAAAA AAAAxx
+9 49 1 1 9 9 9 9 9 9 9 18 19 JAAAAA XBAAAA HHHHxx
+771 50 1 3 1 11 1 71 171 271 771 2 3 RDAAAA YBAAAA OOOOxx
+470 51 0 2 0 10 0 70 70 470 470 0 1 CSAAAA ZBAAAA VVVVxx
+238 52 0 2 8 18 8 38 38 238 238 16 17 EJAAAA ACAAAA AAAAxx
+86 53 0 2 6 6 6 86 86 86 86 12 13 IDAAAA BCAAAA HHHHxx
+56 54 0 0 6 16 6 56 56 56 56 12 13 ECAAAA CCAAAA OOOOxx
+767 55 1 3 7 7 7 67 167 267 767 14 15 NDAAAA DCAAAA VVVVxx
+363 56 1 3 3 3 3 63 163 363 363 6 7 ZNAAAA ECAAAA AAAAxx
+655 57 1 3 5 15 5 55 55 155 655 10 11 FZAAAA FCAAAA HHHHxx
+394 58 0 2 4 14 4 94 194 394 394 8 9 EPAAAA GCAAAA OOOOxx
+223 59 1 3 3 3 3 23 23 223 223 6 7 PIAAAA HCAAAA VVVVxx
+946 60 0 2 6 6 6 46 146 446 946 12 13 KKAAAA ICAAAA AAAAxx
+863 61 1 3 3 3 3 63 63 363 863 6 7 FHAAAA JCAAAA HHHHxx
+913 62 1 1 3 13 3 13 113 413 913 6 7 DJAAAA KCAAAA OOOOxx
+737 63 1 1 7 17 7 37 137 237 737 14 15 JCAAAA LCAAAA VVVVxx
+65 64 1 1 5 5 5 65 65 65 65 10 11 NCAAAA MCAAAA AAAAxx
+251 65 1 3 1 11 1 51 51 251 251 2 3 RJAAAA NCAAAA HHHHxx
+686 66 0 2 6 6 6 86 86 186 686 12 13 KAAAAA OCAAAA OOOOxx
+971 67 1 3 1 11 1 71 171 471 971 2 3 JLAAAA PCAAAA VVVVxx
+775 68 1 3 5 15 5 75 175 275 775 10 11 VDAAAA QCAAAA AAAAxx
+577 69 1 1 7 17 7 77 177 77 577 14 15 FWAAAA RCAAAA HHHHxx
+830 70 0 2 0 10 0 30 30 330 830 0 1 YFAAAA SCAAAA OOOOxx
+787 71 1 3 7 7 7 87 187 287 787 14 15 HEAAAA TCAAAA VVVVxx
+898 72 0 2 8 18 8 98 98 398 898 16 17 OIAAAA UCAAAA AAAAxx
+588 73 0 0 8 8 8 88 188 88 588 16 17 QWAAAA VCAAAA HHHHxx
+872 74 0 0 2 12 2 72 72 372 872 4 5 OHAAAA WCAAAA OOOOxx
+397 75 1 1 7 17 7 97 197 397 397 14 15 HPAAAA XCAAAA VVVVxx
+51 76 1 3 1 11 1 51 51 51 51 2 3 ZBAAAA YCAAAA AAAAxx
+381 77 1 1 1 1 1 81 181 381 381 2 3 ROAAAA ZCAAAA HHHHxx
+632 78 0 0 2 12 2 32 32 132 632 4 5 IYAAAA ADAAAA OOOOxx
+31 79 1 3 1 11 1 31 31 31 31 2 3 FBAAAA BDAAAA VVVVxx
+855 80 1 3 5 15 5 55 55 355 855 10 11 XGAAAA CDAAAA AAAAxx
+699 81 1 3 9 19 9 99 99 199 699 18 19 XAAAAA DDAAAA HHHHxx
+562 82 0 2 2 2 2 62 162 62 562 4 5 QVAAAA EDAAAA OOOOxx
+681 83 1 1 1 1 1 81 81 181 681 2 3 FAAAAA FDAAAA VVVVxx
+585 84 1 1 5 5 5 85 185 85 585 10 11 NWAAAA GDAAAA AAAAxx
+35 85 1 3 5 15 5 35 35 35 35 10 11 JBAAAA HDAAAA HHHHxx
+962 86 0 2 2 2 2 62 162 462 962 4 5 ALAAAA IDAAAA OOOOxx
+282 87 0 2 2 2 2 82 82 282 282 4 5 WKAAAA JDAAAA VVVVxx
+254 88 0 2 4 14 4 54 54 254 254 8 9 UJAAAA KDAAAA AAAAxx
+514 89 0 2 4 14 4 14 114 14 514 8 9 UTAAAA LDAAAA HHHHxx
+406 90 0 2 6 6 6 6 6 406 406 12 13 QPAAAA MDAAAA OOOOxx
+544 91 0 0 4 4 4 44 144 44 544 8 9 YUAAAA NDAAAA VVVVxx
+704 92 0 0 4 4 4 4 104 204 704 8 9 CBAAAA ODAAAA AAAAxx
+948 93 0 0 8 8 8 48 148 448 948 16 17 MKAAAA PDAAAA HHHHxx
+412 94 0 0 2 12 2 12 12 412 412 4 5 WPAAAA QDAAAA OOOOxx
+200 95 0 0 0 0 0 0 0 200 200 0 1 SHAAAA RDAAAA VVVVxx
+583 96 1 3 3 3 3 83 183 83 583 6 7 LWAAAA SDAAAA AAAAxx
+486 97 0 2 6 6 6 86 86 486 486 12 13 SSAAAA TDAAAA HHHHxx
+666 98 0 2 6 6 6 66 66 166 666 12 13 QZAAAA UDAAAA OOOOxx
+436 99 0 0 6 16 6 36 36 436 436 12 13 UQAAAA VDAAAA VVVVxx
+842 100 0 2 2 2 2 42 42 342 842 4 5 KGAAAA WDAAAA AAAAxx
+99 101 1 3 9 19 9 99 99 99 99 18 19 VDAAAA XDAAAA HHHHxx
+656 102 0 0 6 16 6 56 56 156 656 12 13 GZAAAA YDAAAA OOOOxx
+673 103 1 1 3 13 3 73 73 173 673 6 7 XZAAAA ZDAAAA VVVVxx
+371 104 1 3 1 11 1 71 171 371 371 2 3 HOAAAA AEAAAA AAAAxx
+869 105 1 1 9 9 9 69 69 369 869 18 19 LHAAAA BEAAAA HHHHxx
+569 106 1 1 9 9 9 69 169 69 569 18 19 XVAAAA CEAAAA OOOOxx
+616 107 0 0 6 16 6 16 16 116 616 12 13 SXAAAA DEAAAA VVVVxx
+612 108 0 0 2 12 2 12 12 112 612 4 5 OXAAAA EEAAAA AAAAxx
+505 109 1 1 5 5 5 5 105 5 505 10 11 LTAAAA FEAAAA HHHHxx
+922 110 0 2 2 2 2 22 122 422 922 4 5 MJAAAA GEAAAA OOOOxx
+221 111 1 1 1 1 1 21 21 221 221 2 3 NIAAAA HEAAAA VVVVxx
+388 112 0 0 8 8 8 88 188 388 388 16 17 YOAAAA IEAAAA AAAAxx
+567 113 1 3 7 7 7 67 167 67 567 14 15 VVAAAA JEAAAA HHHHxx
+58 114 0 2 8 18 8 58 58 58 58 16 17 GCAAAA KEAAAA OOOOxx
+316 115 0 0 6 16 6 16 116 316 316 12 13 EMAAAA LEAAAA VVVVxx
+659 116 1 3 9 19 9 59 59 159 659 18 19 JZAAAA MEAAAA AAAAxx
+501 117 1 1 1 1 1 1 101 1 501 2 3 HTAAAA NEAAAA HHHHxx
+815 118 1 3 5 15 5 15 15 315 815 10 11 JFAAAA OEAAAA OOOOxx
+638 119 0 2 8 18 8 38 38 138 638 16 17 OYAAAA PEAAAA VVVVxx
+696 120 0 0 6 16 6 96 96 196 696 12 13 UAAAAA QEAAAA AAAAxx
+734 121 0 2 4 14 4 34 134 234 734 8 9 GCAAAA REAAAA HHHHxx
+237 122 1 1 7 17 7 37 37 237 237 14 15 DJAAAA SEAAAA OOOOxx
+816 123 0 0 6 16 6 16 16 316 816 12 13 KFAAAA TEAAAA VVVVxx
+917 124 1 1 7 17 7 17 117 417 917 14 15 HJAAAA UEAAAA AAAAxx
+844 125 0 0 4 4 4 44 44 344 844 8 9 MGAAAA VEAAAA HHHHxx
+657 126 1 1 7 17 7 57 57 157 657 14 15 HZAAAA WEAAAA OOOOxx
+952 127 0 0 2 12 2 52 152 452 952 4 5 QKAAAA XEAAAA VVVVxx
+519 128 1 3 9 19 9 19 119 19 519 18 19 ZTAAAA YEAAAA AAAAxx
+792 129 0 0 2 12 2 92 192 292 792 4 5 MEAAAA ZEAAAA HHHHxx
+275 130 1 3 5 15 5 75 75 275 275 10 11 PKAAAA AFAAAA OOOOxx
+319 131 1 3 9 19 9 19 119 319 319 18 19 HMAAAA BFAAAA VVVVxx
+487 132 1 3 7 7 7 87 87 487 487 14 15 TSAAAA CFAAAA AAAAxx
+945 133 1 1 5 5 5 45 145 445 945 10 11 JKAAAA DFAAAA HHHHxx
+584 134 0 0 4 4 4 84 184 84 584 8 9 MWAAAA EFAAAA OOOOxx
+765 135 1 1 5 5 5 65 165 265 765 10 11 LDAAAA FFAAAA VVVVxx
+814 136 0 2 4 14 4 14 14 314 814 8 9 IFAAAA GFAAAA AAAAxx
+359 137 1 3 9 19 9 59 159 359 359 18 19 VNAAAA HFAAAA HHHHxx
+548 138 0 0 8 8 8 48 148 48 548 16 17 CVAAAA IFAAAA OOOOxx
+811 139 1 3 1 11 1 11 11 311 811 2 3 FFAAAA JFAAAA VVVVxx
+531 140 1 3 1 11 1 31 131 31 531 2 3 LUAAAA KFAAAA AAAAxx
+104 141 0 0 4 4 4 4 104 104 104 8 9 AEAAAA LFAAAA HHHHxx
+33 142 1 1 3 13 3 33 33 33 33 6 7 HBAAAA MFAAAA OOOOxx
+404 143 0 0 4 4 4 4 4 404 404 8 9 OPAAAA NFAAAA VVVVxx
+995 144 1 3 5 15 5 95 195 495 995 10 11 HMAAAA OFAAAA AAAAxx
+408 145 0 0 8 8 8 8 8 408 408 16 17 SPAAAA PFAAAA HHHHxx
+93 146 1 1 3 13 3 93 93 93 93 6 7 PDAAAA QFAAAA OOOOxx
+794 147 0 2 4 14 4 94 194 294 794 8 9 OEAAAA RFAAAA VVVVxx
+833 148 1 1 3 13 3 33 33 333 833 6 7 BGAAAA SFAAAA AAAAxx
+615 149 1 3 5 15 5 15 15 115 615 10 11 RXAAAA TFAAAA HHHHxx
+333 150 1 1 3 13 3 33 133 333 333 6 7 VMAAAA UFAAAA OOOOxx
+357 151 1 1 7 17 7 57 157 357 357 14 15 TNAAAA VFAAAA VVVVxx
+999 152 1 3 9 19 9 99 199 499 999 18 19 LMAAAA WFAAAA AAAAxx
+515 153 1 3 5 15 5 15 115 15 515 10 11 VTAAAA XFAAAA HHHHxx
+685 154 1 1 5 5 5 85 85 185 685 10 11 JAAAAA YFAAAA OOOOxx
+692 155 0 0 2 12 2 92 92 192 692 4 5 QAAAAA ZFAAAA VVVVxx
+627 156 1 3 7 7 7 27 27 127 627 14 15 DYAAAA AGAAAA AAAAxx
+654 157 0 2 4 14 4 54 54 154 654 8 9 EZAAAA BGAAAA HHHHxx
+115 158 1 3 5 15 5 15 115 115 115 10 11 LEAAAA CGAAAA OOOOxx
+75 159 1 3 5 15 5 75 75 75 75 10 11 XCAAAA DGAAAA VVVVxx
+14 160 0 2 4 14 4 14 14 14 14 8 9 OAAAAA EGAAAA AAAAxx
+148 161 0 0 8 8 8 48 148 148 148 16 17 SFAAAA FGAAAA HHHHxx
+201 162 1 1 1 1 1 1 1 201 201 2 3 THAAAA GGAAAA OOOOxx
+862 163 0 2 2 2 2 62 62 362 862 4 5 EHAAAA HGAAAA VVVVxx
+634 164 0 2 4 14 4 34 34 134 634 8 9 KYAAAA IGAAAA AAAAxx
+589 165 1 1 9 9 9 89 189 89 589 18 19 RWAAAA JGAAAA HHHHxx
+142 166 0 2 2 2 2 42 142 142 142 4 5 MFAAAA KGAAAA OOOOxx
+545 167 1 1 5 5 5 45 145 45 545 10 11 ZUAAAA LGAAAA VVVVxx
+983 168 1 3 3 3 3 83 183 483 983 6 7 VLAAAA MGAAAA AAAAxx
+87 169 1 3 7 7 7 87 87 87 87 14 15 JDAAAA NGAAAA HHHHxx
+335 170 1 3 5 15 5 35 135 335 335 10 11 XMAAAA OGAAAA OOOOxx
+915 171 1 3 5 15 5 15 115 415 915 10 11 FJAAAA PGAAAA VVVVxx
+286 172 0 2 6 6 6 86 86 286 286 12 13 ALAAAA QGAAAA AAAAxx
+361 173 1 1 1 1 1 61 161 361 361 2 3 XNAAAA RGAAAA HHHHxx
+97 174 1 1 7 17 7 97 97 97 97 14 15 TDAAAA SGAAAA OOOOxx
+98 175 0 2 8 18 8 98 98 98 98 16 17 UDAAAA TGAAAA VVVVxx
+377 176 1 1 7 17 7 77 177 377 377 14 15 NOAAAA UGAAAA AAAAxx
+525 177 1 1 5 5 5 25 125 25 525 10 11 FUAAAA VGAAAA HHHHxx
+448 178 0 0 8 8 8 48 48 448 448 16 17 GRAAAA WGAAAA OOOOxx
+154 179 0 2 4 14 4 54 154 154 154 8 9 YFAAAA XGAAAA VVVVxx
+866 180 0 2 6 6 6 66 66 366 866 12 13 IHAAAA YGAAAA AAAAxx
+741 181 1 1 1 1 1 41 141 241 741 2 3 NCAAAA ZGAAAA HHHHxx
+172 182 0 0 2 12 2 72 172 172 172 4 5 QGAAAA AHAAAA OOOOxx
+843 183 1 3 3 3 3 43 43 343 843 6 7 LGAAAA BHAAAA VVVVxx
+378 184 0 2 8 18 8 78 178 378 378 16 17 OOAAAA CHAAAA AAAAxx
+804 185 0 0 4 4 4 4 4 304 804 8 9 YEAAAA DHAAAA HHHHxx
+596 186 0 0 6 16 6 96 196 96 596 12 13 YWAAAA EHAAAA OOOOxx
+77 187 1 1 7 17 7 77 77 77 77 14 15 ZCAAAA FHAAAA VVVVxx
+572 188 0 0 2 12 2 72 172 72 572 4 5 AWAAAA GHAAAA AAAAxx
+444 189 0 0 4 4 4 44 44 444 444 8 9 CRAAAA HHAAAA HHHHxx
+47 190 1 3 7 7 7 47 47 47 47 14 15 VBAAAA IHAAAA OOOOxx
+274 191 0 2 4 14 4 74 74 274 274 8 9 OKAAAA JHAAAA VVVVxx
+40 192 0 0 0 0 0 40 40 40 40 0 1 OBAAAA KHAAAA AAAAxx
+339 193 1 3 9 19 9 39 139 339 339 18 19 BNAAAA LHAAAA HHHHxx
+13 194 1 1 3 13 3 13 13 13 13 6 7 NAAAAA MHAAAA OOOOxx
+878 195 0 2 8 18 8 78 78 378 878 16 17 UHAAAA NHAAAA VVVVxx
+53 196 1 1 3 13 3 53 53 53 53 6 7 BCAAAA OHAAAA AAAAxx
+939 197 1 3 9 19 9 39 139 439 939 18 19 DKAAAA PHAAAA HHHHxx
+928 198 0 0 8 8 8 28 128 428 928 16 17 SJAAAA QHAAAA OOOOxx
+886 199 0 2 6 6 6 86 86 386 886 12 13 CIAAAA RHAAAA VVVVxx
+267 200 1 3 7 7 7 67 67 267 267 14 15 HKAAAA SHAAAA AAAAxx
+105 201 1 1 5 5 5 5 105 105 105 10 11 BEAAAA THAAAA HHHHxx
+312 202 0 0 2 12 2 12 112 312 312 4 5 AMAAAA UHAAAA OOOOxx
+552 203 0 0 2 12 2 52 152 52 552 4 5 GVAAAA VHAAAA VVVVxx
+918 204 0 2 8 18 8 18 118 418 918 16 17 IJAAAA WHAAAA AAAAxx
+114 205 0 2 4 14 4 14 114 114 114 8 9 KEAAAA XHAAAA HHHHxx
+805 206 1 1 5 5 5 5 5 305 805 10 11 ZEAAAA YHAAAA OOOOxx
+875 207 1 3 5 15 5 75 75 375 875 10 11 RHAAAA ZHAAAA VVVVxx
+225 208 1 1 5 5 5 25 25 225 225 10 11 RIAAAA AIAAAA AAAAxx
+495 209 1 3 5 15 5 95 95 495 495 10 11 BTAAAA BIAAAA HHHHxx
+150 210 0 2 0 10 0 50 150 150 150 0 1 UFAAAA CIAAAA OOOOxx
+759 211 1 3 9 19 9 59 159 259 759 18 19 FDAAAA DIAAAA VVVVxx
+149 212 1 1 9 9 9 49 149 149 149 18 19 TFAAAA EIAAAA AAAAxx
+480 213 0 0 0 0 0 80 80 480 480 0 1 MSAAAA FIAAAA HHHHxx
+1 214 1 1 1 1 1 1 1 1 1 2 3 BAAAAA GIAAAA OOOOxx
+557 215 1 1 7 17 7 57 157 57 557 14 15 LVAAAA HIAAAA VVVVxx
+295 216 1 3 5 15 5 95 95 295 295 10 11 JLAAAA IIAAAA AAAAxx
+854 217 0 2 4 14 4 54 54 354 854 8 9 WGAAAA JIAAAA HHHHxx
+420 218 0 0 0 0 0 20 20 420 420 0 1 EQAAAA KIAAAA OOOOxx
+414 219 0 2 4 14 4 14 14 414 414 8 9 YPAAAA LIAAAA VVVVxx
+758 220 0 2 8 18 8 58 158 258 758 16 17 EDAAAA MIAAAA AAAAxx
+879 221 1 3 9 19 9 79 79 379 879 18 19 VHAAAA NIAAAA HHHHxx
+332 222 0 0 2 12 2 32 132 332 332 4 5 UMAAAA OIAAAA OOOOxx
+78 223 0 2 8 18 8 78 78 78 78 16 17 ADAAAA PIAAAA VVVVxx
+851 224 1 3 1 11 1 51 51 351 851 2 3 TGAAAA QIAAAA AAAAxx
+592 225 0 0 2 12 2 92 192 92 592 4 5 UWAAAA RIAAAA HHHHxx
+979 226 1 3 9 19 9 79 179 479 979 18 19 RLAAAA SIAAAA OOOOxx
+989 227 1 1 9 9 9 89 189 489 989 18 19 BMAAAA TIAAAA VVVVxx
+752 228 0 0 2 12 2 52 152 252 752 4 5 YCAAAA UIAAAA AAAAxx
+214 229 0 2 4 14 4 14 14 214 214 8 9 GIAAAA VIAAAA HHHHxx
+453 230 1 1 3 13 3 53 53 453 453 6 7 LRAAAA WIAAAA OOOOxx
+540 231 0 0 0 0 0 40 140 40 540 0 1 UUAAAA XIAAAA VVVVxx
+597 232 1 1 7 17 7 97 197 97 597 14 15 ZWAAAA YIAAAA AAAAxx
+356 233 0 0 6 16 6 56 156 356 356 12 13 SNAAAA ZIAAAA HHHHxx
+720 234 0 0 0 0 0 20 120 220 720 0 1 SBAAAA AJAAAA OOOOxx
+367 235 1 3 7 7 7 67 167 367 367 14 15 DOAAAA BJAAAA VVVVxx
+762 236 0 2 2 2 2 62 162 262 762 4 5 IDAAAA CJAAAA AAAAxx
+986 237 0 2 6 6 6 86 186 486 986 12 13 YLAAAA DJAAAA HHHHxx
+924 238 0 0 4 4 4 24 124 424 924 8 9 OJAAAA EJAAAA OOOOxx
+779 239 1 3 9 19 9 79 179 279 779 18 19 ZDAAAA FJAAAA VVVVxx
+684 240 0 0 4 4 4 84 84 184 684 8 9 IAAAAA GJAAAA AAAAxx
+413 241 1 1 3 13 3 13 13 413 413 6 7 XPAAAA HJAAAA HHHHxx
+479 242 1 3 9 19 9 79 79 479 479 18 19 LSAAAA IJAAAA OOOOxx
+731 243 1 3 1 11 1 31 131 231 731 2 3 DCAAAA JJAAAA VVVVxx
+409 244 1 1 9 9 9 9 9 409 409 18 19 TPAAAA KJAAAA AAAAxx
+372 245 0 0 2 12 2 72 172 372 372 4 5 IOAAAA LJAAAA HHHHxx
+139 246 1 3 9 19 9 39 139 139 139 18 19 JFAAAA MJAAAA OOOOxx
+717 247 1 1 7 17 7 17 117 217 717 14 15 PBAAAA NJAAAA VVVVxx
+539 248 1 3 9 19 9 39 139 39 539 18 19 TUAAAA OJAAAA AAAAxx
+318 249 0 2 8 18 8 18 118 318 318 16 17 GMAAAA PJAAAA HHHHxx
+208 250 0 0 8 8 8 8 8 208 208 16 17 AIAAAA QJAAAA OOOOxx
+797 251 1 1 7 17 7 97 197 297 797 14 15 REAAAA RJAAAA VVVVxx
+661 252 1 1 1 1 1 61 61 161 661 2 3 LZAAAA SJAAAA AAAAxx
+50 253 0 2 0 10 0 50 50 50 50 0 1 YBAAAA TJAAAA HHHHxx
+102 254 0 2 2 2 2 2 102 102 102 4 5 YDAAAA UJAAAA OOOOxx
+484 255 0 0 4 4 4 84 84 484 484 8 9 QSAAAA VJAAAA VVVVxx
+108 256 0 0 8 8 8 8 108 108 108 16 17 EEAAAA WJAAAA AAAAxx
+140 257 0 0 0 0 0 40 140 140 140 0 1 KFAAAA XJAAAA HHHHxx
+996 258 0 0 6 16 6 96 196 496 996 12 13 IMAAAA YJAAAA OOOOxx
+687 259 1 3 7 7 7 87 87 187 687 14 15 LAAAAA ZJAAAA VVVVxx
+241 260 1 1 1 1 1 41 41 241 241 2 3 HJAAAA AKAAAA AAAAxx
+923 261 1 3 3 3 3 23 123 423 923 6 7 NJAAAA BKAAAA HHHHxx
+500 262 0 0 0 0 0 0 100 0 500 0 1 GTAAAA CKAAAA OOOOxx
+536 263 0 0 6 16 6 36 136 36 536 12 13 QUAAAA DKAAAA VVVVxx
+490 264 0 2 0 10 0 90 90 490 490 0 1 WSAAAA EKAAAA AAAAxx
+773 265 1 1 3 13 3 73 173 273 773 6 7 TDAAAA FKAAAA HHHHxx
+19 266 1 3 9 19 9 19 19 19 19 18 19 TAAAAA GKAAAA OOOOxx
+534 267 0 2 4 14 4 34 134 34 534 8 9 OUAAAA HKAAAA VVVVxx
+941 268 1 1 1 1 1 41 141 441 941 2 3 FKAAAA IKAAAA AAAAxx
+477 269 1 1 7 17 7 77 77 477 477 14 15 JSAAAA JKAAAA HHHHxx
+173 270 1 1 3 13 3 73 173 173 173 6 7 RGAAAA KKAAAA OOOOxx
+113 271 1 1 3 13 3 13 113 113 113 6 7 JEAAAA LKAAAA VVVVxx
+526 272 0 2 6 6 6 26 126 26 526 12 13 GUAAAA MKAAAA AAAAxx
+727 273 1 3 7 7 7 27 127 227 727 14 15 ZBAAAA NKAAAA HHHHxx
+302 274 0 2 2 2 2 2 102 302 302 4 5 QLAAAA OKAAAA OOOOxx
+789 275 1 1 9 9 9 89 189 289 789 18 19 JEAAAA PKAAAA VVVVxx
+447 276 1 3 7 7 7 47 47 447 447 14 15 FRAAAA QKAAAA AAAAxx
+884 277 0 0 4 4 4 84 84 384 884 8 9 AIAAAA RKAAAA HHHHxx
+718 278 0 2 8 18 8 18 118 218 718 16 17 QBAAAA SKAAAA OOOOxx
+818 279 0 2 8 18 8 18 18 318 818 16 17 MFAAAA TKAAAA VVVVxx
+466 280 0 2 6 6 6 66 66 466 466 12 13 YRAAAA UKAAAA AAAAxx
+131 281 1 3 1 11 1 31 131 131 131 2 3 BFAAAA VKAAAA HHHHxx
+503 282 1 3 3 3 3 3 103 3 503 6 7 JTAAAA WKAAAA OOOOxx
+364 283 0 0 4 4 4 64 164 364 364 8 9 AOAAAA XKAAAA VVVVxx
+934 284 0 2 4 14 4 34 134 434 934 8 9 YJAAAA YKAAAA AAAAxx
+542 285 0 2 2 2 2 42 142 42 542 4 5 WUAAAA ZKAAAA HHHHxx
+146 286 0 2 6 6 6 46 146 146 146 12 13 QFAAAA ALAAAA OOOOxx
+652 287 0 0 2 12 2 52 52 152 652 4 5 CZAAAA BLAAAA VVVVxx
+566 288 0 2 6 6 6 66 166 66 566 12 13 UVAAAA CLAAAA AAAAxx
+788 289 0 0 8 8 8 88 188 288 788 16 17 IEAAAA DLAAAA HHHHxx
+168 290 0 0 8 8 8 68 168 168 168 16 17 MGAAAA ELAAAA OOOOxx
+736 291 0 0 6 16 6 36 136 236 736 12 13 ICAAAA FLAAAA VVVVxx
+795 292 1 3 5 15 5 95 195 295 795 10 11 PEAAAA GLAAAA AAAAxx
+103 293 1 3 3 3 3 3 103 103 103 6 7 ZDAAAA HLAAAA HHHHxx
+763 294 1 3 3 3 3 63 163 263 763 6 7 JDAAAA ILAAAA OOOOxx
+256 295 0 0 6 16 6 56 56 256 256 12 13 WJAAAA JLAAAA VVVVxx
+63 296 1 3 3 3 3 63 63 63 63 6 7 LCAAAA KLAAAA AAAAxx
+702 297 0 2 2 2 2 2 102 202 702 4 5 ABAAAA LLAAAA HHHHxx
+390 298 0 2 0 10 0 90 190 390 390 0 1 APAAAA MLAAAA OOOOxx
+116 299 0 0 6 16 6 16 116 116 116 12 13 MEAAAA NLAAAA VVVVxx
+354 300 0 2 4 14 4 54 154 354 354 8 9 QNAAAA OLAAAA AAAAxx
+162 301 0 2 2 2 2 62 162 162 162 4 5 GGAAAA PLAAAA HHHHxx
+71 302 1 3 1 11 1 71 71 71 71 2 3 TCAAAA QLAAAA OOOOxx
+916 303 0 0 6 16 6 16 116 416 916 12 13 GJAAAA RLAAAA VVVVxx
+565 304 1 1 5 5 5 65 165 65 565 10 11 TVAAAA SLAAAA AAAAxx
+509 305 1 1 9 9 9 9 109 9 509 18 19 PTAAAA TLAAAA HHHHxx
+20 306 0 0 0 0 0 20 20 20 20 0 1 UAAAAA ULAAAA OOOOxx
+813 307 1 1 3 13 3 13 13 313 813 6 7 HFAAAA VLAAAA VVVVxx
+80 308 0 0 0 0 0 80 80 80 80 0 1 CDAAAA WLAAAA AAAAxx
+400 309 0 0 0 0 0 0 0 400 400 0 1 KPAAAA XLAAAA HHHHxx
+888 310 0 0 8 8 8 88 88 388 888 16 17 EIAAAA YLAAAA OOOOxx
+825 311 1 1 5 5 5 25 25 325 825 10 11 TFAAAA ZLAAAA VVVVxx
+401 312 1 1 1 1 1 1 1 401 401 2 3 LPAAAA AMAAAA AAAAxx
+158 313 0 2 8 18 8 58 158 158 158 16 17 CGAAAA BMAAAA HHHHxx
+973 314 1 1 3 13 3 73 173 473 973 6 7 LLAAAA CMAAAA OOOOxx
+324 315 0 0 4 4 4 24 124 324 324 8 9 MMAAAA DMAAAA VVVVxx
+873 316 1 1 3 13 3 73 73 373 873 6 7 PHAAAA EMAAAA AAAAxx
+676 317 0 0 6 16 6 76 76 176 676 12 13 AAAAAA FMAAAA HHHHxx
+199 318 1 3 9 19 9 99 199 199 199 18 19 RHAAAA GMAAAA OOOOxx
+304 319 0 0 4 4 4 4 104 304 304 8 9 SLAAAA HMAAAA VVVVxx
+338 320 0 2 8 18 8 38 138 338 338 16 17 ANAAAA IMAAAA AAAAxx
+743 321 1 3 3 3 3 43 143 243 743 6 7 PCAAAA JMAAAA HHHHxx
+730 322 0 2 0 10 0 30 130 230 730 0 1 CCAAAA KMAAAA OOOOxx
+130 323 0 2 0 10 0 30 130 130 130 0 1 AFAAAA LMAAAA VVVVxx
+224 324 0 0 4 4 4 24 24 224 224 8 9 QIAAAA MMAAAA AAAAxx
+216 325 0 0 6 16 6 16 16 216 216 12 13 IIAAAA NMAAAA HHHHxx
+2 326 0 2 2 2 2 2 2 2 2 4 5 CAAAAA OMAAAA OOOOxx
+836 327 0 0 6 16 6 36 36 336 836 12 13 EGAAAA PMAAAA VVVVxx
+443 328 1 3 3 3 3 43 43 443 443 6 7 BRAAAA QMAAAA AAAAxx
+777 329 1 1 7 17 7 77 177 277 777 14 15 XDAAAA RMAAAA HHHHxx
+126 330 0 2 6 6 6 26 126 126 126 12 13 WEAAAA SMAAAA OOOOxx
+117 331 1 1 7 17 7 17 117 117 117 14 15 NEAAAA TMAAAA VVVVxx
+633 332 1 1 3 13 3 33 33 133 633 6 7 JYAAAA UMAAAA AAAAxx
+310 333 0 2 0 10 0 10 110 310 310 0 1 YLAAAA VMAAAA HHHHxx
+622 334 0 2 2 2 2 22 22 122 622 4 5 YXAAAA WMAAAA OOOOxx
+268 335 0 0 8 8 8 68 68 268 268 16 17 IKAAAA XMAAAA VVVVxx
+384 336 0 0 4 4 4 84 184 384 384 8 9 UOAAAA YMAAAA AAAAxx
+460 337 0 0 0 0 0 60 60 460 460 0 1 SRAAAA ZMAAAA HHHHxx
+475 338 1 3 5 15 5 75 75 475 475 10 11 HSAAAA ANAAAA OOOOxx
+624 339 0 0 4 4 4 24 24 124 624 8 9 AYAAAA BNAAAA VVVVxx
+826 340 0 2 6 6 6 26 26 326 826 12 13 UFAAAA CNAAAA AAAAxx
+680 341 0 0 0 0 0 80 80 180 680 0 1 EAAAAA DNAAAA HHHHxx
+306 342 0 2 6 6 6 6 106 306 306 12 13 ULAAAA ENAAAA OOOOxx
+896 343 0 0 6 16 6 96 96 396 896 12 13 MIAAAA FNAAAA VVVVxx
+30 344 0 2 0 10 0 30 30 30 30 0 1 EBAAAA GNAAAA AAAAxx
+576 345 0 0 6 16 6 76 176 76 576 12 13 EWAAAA HNAAAA HHHHxx
+551 346 1 3 1 11 1 51 151 51 551 2 3 FVAAAA INAAAA OOOOxx
+639 347 1 3 9 19 9 39 39 139 639 18 19 PYAAAA JNAAAA VVVVxx
+975 348 1 3 5 15 5 75 175 475 975 10 11 NLAAAA KNAAAA AAAAxx
+882 349 0 2 2 2 2 82 82 382 882 4 5 YHAAAA LNAAAA HHHHxx
+160 350 0 0 0 0 0 60 160 160 160 0 1 EGAAAA MNAAAA OOOOxx
+522 351 0 2 2 2 2 22 122 22 522 4 5 CUAAAA NNAAAA VVVVxx
+620 352 0 0 0 0 0 20 20 120 620 0 1 WXAAAA ONAAAA AAAAxx
+719 353 1 3 9 19 9 19 119 219 719 18 19 RBAAAA PNAAAA HHHHxx
+88 354 0 0 8 8 8 88 88 88 88 16 17 KDAAAA QNAAAA OOOOxx
+614 355 0 2 4 14 4 14 14 114 614 8 9 QXAAAA RNAAAA VVVVxx
+54 356 0 2 4 14 4 54 54 54 54 8 9 CCAAAA SNAAAA AAAAxx
+209 357 1 1 9 9 9 9 9 209 209 18 19 BIAAAA TNAAAA HHHHxx
+67 358 1 3 7 7 7 67 67 67 67 14 15 PCAAAA UNAAAA OOOOxx
+809 359 1 1 9 9 9 9 9 309 809 18 19 DFAAAA VNAAAA VVVVxx
+982 360 0 2 2 2 2 82 182 482 982 4 5 ULAAAA WNAAAA AAAAxx
+817 361 1 1 7 17 7 17 17 317 817 14 15 LFAAAA XNAAAA HHHHxx
+187 362 1 3 7 7 7 87 187 187 187 14 15 FHAAAA YNAAAA OOOOxx
+992 363 0 0 2 12 2 92 192 492 992 4 5 EMAAAA ZNAAAA VVVVxx
+580 364 0 0 0 0 0 80 180 80 580 0 1 IWAAAA AOAAAA AAAAxx
+658 365 0 2 8 18 8 58 58 158 658 16 17 IZAAAA BOAAAA HHHHxx
+222 366 0 2 2 2 2 22 22 222 222 4 5 OIAAAA COAAAA OOOOxx
+667 367 1 3 7 7 7 67 67 167 667 14 15 RZAAAA DOAAAA VVVVxx
+715 368 1 3 5 15 5 15 115 215 715 10 11 NBAAAA EOAAAA AAAAxx
+990 369 0 2 0 10 0 90 190 490 990 0 1 CMAAAA FOAAAA HHHHxx
+22 370 0 2 2 2 2 22 22 22 22 4 5 WAAAAA GOAAAA OOOOxx
+362 371 0 2 2 2 2 62 162 362 362 4 5 YNAAAA HOAAAA VVVVxx
+376 372 0 0 6 16 6 76 176 376 376 12 13 MOAAAA IOAAAA AAAAxx
+246 373 0 2 6 6 6 46 46 246 246 12 13 MJAAAA JOAAAA HHHHxx
+300 374 0 0 0 0 0 0 100 300 300 0 1 OLAAAA KOAAAA OOOOxx
+231 375 1 3 1 11 1 31 31 231 231 2 3 XIAAAA LOAAAA VVVVxx
+151 376 1 3 1 11 1 51 151 151 151 2 3 VFAAAA MOAAAA AAAAxx
+29 377 1 1 9 9 9 29 29 29 29 18 19 DBAAAA NOAAAA HHHHxx
+297 378 1 1 7 17 7 97 97 297 297 14 15 LLAAAA OOAAAA OOOOxx
+403 379 1 3 3 3 3 3 3 403 403 6 7 NPAAAA POAAAA VVVVxx
+716 380 0 0 6 16 6 16 116 216 716 12 13 OBAAAA QOAAAA AAAAxx
+260 381 0 0 0 0 0 60 60 260 260 0 1 AKAAAA ROAAAA HHHHxx
+170 382 0 2 0 10 0 70 170 170 170 0 1 OGAAAA SOAAAA OOOOxx
+285 383 1 1 5 5 5 85 85 285 285 10 11 ZKAAAA TOAAAA VVVVxx
+82 384 0 2 2 2 2 82 82 82 82 4 5 EDAAAA UOAAAA AAAAxx
+958 385 0 2 8 18 8 58 158 458 958 16 17 WKAAAA VOAAAA HHHHxx
+175 386 1 3 5 15 5 75 175 175 175 10 11 TGAAAA WOAAAA OOOOxx
+671 387 1 3 1 11 1 71 71 171 671 2 3 VZAAAA XOAAAA VVVVxx
+822 388 0 2 2 2 2 22 22 322 822 4 5 QFAAAA YOAAAA AAAAxx
+573 389 1 1 3 13 3 73 173 73 573 6 7 BWAAAA ZOAAAA HHHHxx
+723 390 1 3 3 3 3 23 123 223 723 6 7 VBAAAA APAAAA OOOOxx
+195 391 1 3 5 15 5 95 195 195 195 10 11 NHAAAA BPAAAA VVVVxx
+197 392 1 1 7 17 7 97 197 197 197 14 15 PHAAAA CPAAAA AAAAxx
+755 393 1 3 5 15 5 55 155 255 755 10 11 BDAAAA DPAAAA HHHHxx
+42 394 0 2 2 2 2 42 42 42 42 4 5 QBAAAA EPAAAA OOOOxx
+897 395 1 1 7 17 7 97 97 397 897 14 15 NIAAAA FPAAAA VVVVxx
+309 396 1 1 9 9 9 9 109 309 309 18 19 XLAAAA GPAAAA AAAAxx
+724 397 0 0 4 4 4 24 124 224 724 8 9 WBAAAA HPAAAA HHHHxx
+474 398 0 2 4 14 4 74 74 474 474 8 9 GSAAAA IPAAAA OOOOxx
+345 399 1 1 5 5 5 45 145 345 345 10 11 HNAAAA JPAAAA VVVVxx
+678 400 0 2 8 18 8 78 78 178 678 16 17 CAAAAA KPAAAA AAAAxx
+757 401 1 1 7 17 7 57 157 257 757 14 15 DDAAAA LPAAAA HHHHxx
+600 402 0 0 0 0 0 0 0 100 600 0 1 CXAAAA MPAAAA OOOOxx
+184 403 0 0 4 4 4 84 184 184 184 8 9 CHAAAA NPAAAA VVVVxx
+155 404 1 3 5 15 5 55 155 155 155 10 11 ZFAAAA OPAAAA AAAAxx
+136 405 0 0 6 16 6 36 136 136 136 12 13 GFAAAA PPAAAA HHHHxx
+889 406 1 1 9 9 9 89 89 389 889 18 19 FIAAAA QPAAAA OOOOxx
+95 407 1 3 5 15 5 95 95 95 95 10 11 RDAAAA RPAAAA VVVVxx
+549 408 1 1 9 9 9 49 149 49 549 18 19 DVAAAA SPAAAA AAAAxx
+81 409 1 1 1 1 1 81 81 81 81 2 3 DDAAAA TPAAAA HHHHxx
+679 410 1 3 9 19 9 79 79 179 679 18 19 DAAAAA UPAAAA OOOOxx
+27 411 1 3 7 7 7 27 27 27 27 14 15 BBAAAA VPAAAA VVVVxx
+748 412 0 0 8 8 8 48 148 248 748 16 17 UCAAAA WPAAAA AAAAxx
+107 413 1 3 7 7 7 7 107 107 107 14 15 DEAAAA XPAAAA HHHHxx
+870 414 0 2 0 10 0 70 70 370 870 0 1 MHAAAA YPAAAA OOOOxx
+848 415 0 0 8 8 8 48 48 348 848 16 17 QGAAAA ZPAAAA VVVVxx
+764 416 0 0 4 4 4 64 164 264 764 8 9 KDAAAA AQAAAA AAAAxx
+535 417 1 3 5 15 5 35 135 35 535 10 11 PUAAAA BQAAAA HHHHxx
+211 418 1 3 1 11 1 11 11 211 211 2 3 DIAAAA CQAAAA OOOOxx
+625 419 1 1 5 5 5 25 25 125 625 10 11 BYAAAA DQAAAA VVVVxx
+96 420 0 0 6 16 6 96 96 96 96 12 13 SDAAAA EQAAAA AAAAxx
+828 421 0 0 8 8 8 28 28 328 828 16 17 WFAAAA FQAAAA HHHHxx
+229 422 1 1 9 9 9 29 29 229 229 18 19 VIAAAA GQAAAA OOOOxx
+602 423 0 2 2 2 2 2 2 102 602 4 5 EXAAAA HQAAAA VVVVxx
+742 424 0 2 2 2 2 42 142 242 742 4 5 OCAAAA IQAAAA AAAAxx
+451 425 1 3 1 11 1 51 51 451 451 2 3 JRAAAA JQAAAA HHHHxx
+991 426 1 3 1 11 1 91 191 491 991 2 3 DMAAAA KQAAAA OOOOxx
+301 427 1 1 1 1 1 1 101 301 301 2 3 PLAAAA LQAAAA VVVVxx
+510 428 0 2 0 10 0 10 110 10 510 0 1 QTAAAA MQAAAA AAAAxx
+299 429 1 3 9 19 9 99 99 299 299 18 19 NLAAAA NQAAAA HHHHxx
+961 430 1 1 1 1 1 61 161 461 961 2 3 ZKAAAA OQAAAA OOOOxx
+3 431 1 3 3 3 3 3 3 3 3 6 7 DAAAAA PQAAAA VVVVxx
+106 432 0 2 6 6 6 6 106 106 106 12 13 CEAAAA QQAAAA AAAAxx
+591 433 1 3 1 11 1 91 191 91 591 2 3 TWAAAA RQAAAA HHHHxx
+700 434 0 0 0 0 0 0 100 200 700 0 1 YAAAAA SQAAAA OOOOxx
+841 435 1 1 1 1 1 41 41 341 841 2 3 JGAAAA TQAAAA VVVVxx
+829 436 1 1 9 9 9 29 29 329 829 18 19 XFAAAA UQAAAA AAAAxx
+508 437 0 0 8 8 8 8 108 8 508 16 17 OTAAAA VQAAAA HHHHxx
+750 438 0 2 0 10 0 50 150 250 750 0 1 WCAAAA WQAAAA OOOOxx
+665 439 1 1 5 5 5 65 65 165 665 10 11 PZAAAA XQAAAA VVVVxx
+157 440 1 1 7 17 7 57 157 157 157 14 15 BGAAAA YQAAAA AAAAxx
+694 441 0 2 4 14 4 94 94 194 694 8 9 SAAAAA ZQAAAA HHHHxx
+176 442 0 0 6 16 6 76 176 176 176 12 13 UGAAAA ARAAAA OOOOxx
+950 443 0 2 0 10 0 50 150 450 950 0 1 OKAAAA BRAAAA VVVVxx
+970 444 0 2 0 10 0 70 170 470 970 0 1 ILAAAA CRAAAA AAAAxx
+496 445 0 0 6 16 6 96 96 496 496 12 13 CTAAAA DRAAAA HHHHxx
+429 446 1 1 9 9 9 29 29 429 429 18 19 NQAAAA ERAAAA OOOOxx
+907 447 1 3 7 7 7 7 107 407 907 14 15 XIAAAA FRAAAA VVVVxx
+72 448 0 0 2 12 2 72 72 72 72 4 5 UCAAAA GRAAAA AAAAxx
+186 449 0 2 6 6 6 86 186 186 186 12 13 EHAAAA HRAAAA HHHHxx
+713 450 1 1 3 13 3 13 113 213 713 6 7 LBAAAA IRAAAA OOOOxx
+432 451 0 0 2 12 2 32 32 432 432 4 5 QQAAAA JRAAAA VVVVxx
+735 452 1 3 5 15 5 35 135 235 735 10 11 HCAAAA KRAAAA AAAAxx
+516 453 0 0 6 16 6 16 116 16 516 12 13 WTAAAA LRAAAA HHHHxx
+964 454 0 0 4 4 4 64 164 464 964 8 9 CLAAAA MRAAAA OOOOxx
+840 455 0 0 0 0 0 40 40 340 840 0 1 IGAAAA NRAAAA VVVVxx
+550 456 0 2 0 10 0 50 150 50 550 0 1 EVAAAA ORAAAA AAAAxx
+360 457 0 0 0 0 0 60 160 360 360 0 1 WNAAAA PRAAAA HHHHxx
+827 458 1 3 7 7 7 27 27 327 827 14 15 VFAAAA QRAAAA OOOOxx
+959 459 1 3 9 19 9 59 159 459 959 18 19 XKAAAA RRAAAA VVVVxx
+454 460 0 2 4 14 4 54 54 454 454 8 9 MRAAAA SRAAAA AAAAxx
+819 461 1 3 9 19 9 19 19 319 819 18 19 NFAAAA TRAAAA HHHHxx
+745 462 1 1 5 5 5 45 145 245 745 10 11 RCAAAA URAAAA OOOOxx
+279 463 1 3 9 19 9 79 79 279 279 18 19 TKAAAA VRAAAA VVVVxx
+426 464 0 2 6 6 6 26 26 426 426 12 13 KQAAAA WRAAAA AAAAxx
+70 465 0 2 0 10 0 70 70 70 70 0 1 SCAAAA XRAAAA HHHHxx
+637 466 1 1 7 17 7 37 37 137 637 14 15 NYAAAA YRAAAA OOOOxx
+417 467 1 1 7 17 7 17 17 417 417 14 15 BQAAAA ZRAAAA VVVVxx
+586 468 0 2 6 6 6 86 186 86 586 12 13 OWAAAA ASAAAA AAAAxx
+314 469 0 2 4 14 4 14 114 314 314 8 9 CMAAAA BSAAAA HHHHxx
+101 470 1 1 1 1 1 1 101 101 101 2 3 XDAAAA CSAAAA OOOOxx
+205 471 1 1 5 5 5 5 5 205 205 10 11 XHAAAA DSAAAA VVVVxx
+969 472 1 1 9 9 9 69 169 469 969 18 19 HLAAAA ESAAAA AAAAxx
+217 473 1 1 7 17 7 17 17 217 217 14 15 JIAAAA FSAAAA HHHHxx
+281 474 1 1 1 1 1 81 81 281 281 2 3 VKAAAA GSAAAA OOOOxx
+984 475 0 0 4 4 4 84 184 484 984 8 9 WLAAAA HSAAAA VVVVxx
+366 476 0 2 6 6 6 66 166 366 366 12 13 COAAAA ISAAAA AAAAxx
+483 477 1 3 3 3 3 83 83 483 483 6 7 PSAAAA JSAAAA HHHHxx
+838 478 0 2 8 18 8 38 38 338 838 16 17 GGAAAA KSAAAA OOOOxx
+64 479 0 0 4 4 4 64 64 64 64 8 9 MCAAAA LSAAAA VVVVxx
+981 480 1 1 1 1 1 81 181 481 981 2 3 TLAAAA MSAAAA AAAAxx
+538 481 0 2 8 18 8 38 138 38 538 16 17 SUAAAA NSAAAA HHHHxx
+39 482 1 3 9 19 9 39 39 39 39 18 19 NBAAAA OSAAAA OOOOxx
+60 483 0 0 0 0 0 60 60 60 60 0 1 ICAAAA PSAAAA VVVVxx
+874 484 0 2 4 14 4 74 74 374 874 8 9 QHAAAA QSAAAA AAAAxx
+955 485 1 3 5 15 5 55 155 455 955 10 11 TKAAAA RSAAAA HHHHxx
+347 486 1 3 7 7 7 47 147 347 347 14 15 JNAAAA SSAAAA OOOOxx
+227 487 1 3 7 7 7 27 27 227 227 14 15 TIAAAA TSAAAA VVVVxx
+44 488 0 0 4 4 4 44 44 44 44 8 9 SBAAAA USAAAA AAAAxx
+446 489 0 2 6 6 6 46 46 446 446 12 13 ERAAAA VSAAAA HHHHxx
+605 490 1 1 5 5 5 5 5 105 605 10 11 HXAAAA WSAAAA OOOOxx
+570 491 0 2 0 10 0 70 170 70 570 0 1 YVAAAA XSAAAA VVVVxx
+895 492 1 3 5 15 5 95 95 395 895 10 11 LIAAAA YSAAAA AAAAxx
+760 493 0 0 0 0 0 60 160 260 760 0 1 GDAAAA ZSAAAA HHHHxx
+428 494 0 0 8 8 8 28 28 428 428 16 17 MQAAAA ATAAAA OOOOxx
+628 495 0 0 8 8 8 28 28 128 628 16 17 EYAAAA BTAAAA VVVVxx
+933 496 1 1 3 13 3 33 133 433 933 6 7 XJAAAA CTAAAA AAAAxx
+263 497 1 3 3 3 3 63 63 263 263 6 7 DKAAAA DTAAAA HHHHxx
+729 498 1 1 9 9 9 29 129 229 729 18 19 BCAAAA ETAAAA OOOOxx
+860 499 0 0 0 0 0 60 60 360 860 0 1 CHAAAA FTAAAA VVVVxx
+76 500 0 0 6 16 6 76 76 76 76 12 13 YCAAAA GTAAAA AAAAxx
+293 501 1 1 3 13 3 93 93 293 293 6 7 HLAAAA HTAAAA HHHHxx
+296 502 0 0 6 16 6 96 96 296 296 12 13 KLAAAA ITAAAA OOOOxx
+124 503 0 0 4 4 4 24 124 124 124 8 9 UEAAAA JTAAAA VVVVxx
+568 504 0 0 8 8 8 68 168 68 568 16 17 WVAAAA KTAAAA AAAAxx
+337 505 1 1 7 17 7 37 137 337 337 14 15 ZMAAAA LTAAAA HHHHxx
+464 506 0 0 4 4 4 64 64 464 464 8 9 WRAAAA MTAAAA OOOOxx
+582 507 0 2 2 2 2 82 182 82 582 4 5 KWAAAA NTAAAA VVVVxx
+207 508 1 3 7 7 7 7 7 207 207 14 15 ZHAAAA OTAAAA AAAAxx
+518 509 0 2 8 18 8 18 118 18 518 16 17 YTAAAA PTAAAA HHHHxx
+513 510 1 1 3 13 3 13 113 13 513 6 7 TTAAAA QTAAAA OOOOxx
+127 511 1 3 7 7 7 27 127 127 127 14 15 XEAAAA RTAAAA VVVVxx
+396 512 0 0 6 16 6 96 196 396 396 12 13 GPAAAA STAAAA AAAAxx
+781 513 1 1 1 1 1 81 181 281 781 2 3 BEAAAA TTAAAA HHHHxx
+233 514 1 1 3 13 3 33 33 233 233 6 7 ZIAAAA UTAAAA OOOOxx
+709 515 1 1 9 9 9 9 109 209 709 18 19 HBAAAA VTAAAA VVVVxx
+325 516 1 1 5 5 5 25 125 325 325 10 11 NMAAAA WTAAAA AAAAxx
+143 517 1 3 3 3 3 43 143 143 143 6 7 NFAAAA XTAAAA HHHHxx
+824 518 0 0 4 4 4 24 24 324 824 8 9 SFAAAA YTAAAA OOOOxx
+122 519 0 2 2 2 2 22 122 122 122 4 5 SEAAAA ZTAAAA VVVVxx
+10 520 0 2 0 10 0 10 10 10 10 0 1 KAAAAA AUAAAA AAAAxx
+41 521 1 1 1 1 1 41 41 41 41 2 3 PBAAAA BUAAAA HHHHxx
+618 522 0 2 8 18 8 18 18 118 618 16 17 UXAAAA CUAAAA OOOOxx
+161 523 1 1 1 1 1 61 161 161 161 2 3 FGAAAA DUAAAA VVVVxx
+801 524 1 1 1 1 1 1 1 301 801 2 3 VEAAAA EUAAAA AAAAxx
+768 525 0 0 8 8 8 68 168 268 768 16 17 ODAAAA FUAAAA HHHHxx
+642 526 0 2 2 2 2 42 42 142 642 4 5 SYAAAA GUAAAA OOOOxx
+803 527 1 3 3 3 3 3 3 303 803 6 7 XEAAAA HUAAAA VVVVxx
+317 528 1 1 7 17 7 17 117 317 317 14 15 FMAAAA IUAAAA AAAAxx
+938 529 0 2 8 18 8 38 138 438 938 16 17 CKAAAA JUAAAA HHHHxx
+649 530 1 1 9 9 9 49 49 149 649 18 19 ZYAAAA KUAAAA OOOOxx
+738 531 0 2 8 18 8 38 138 238 738 16 17 KCAAAA LUAAAA VVVVxx
+344 532 0 0 4 4 4 44 144 344 344 8 9 GNAAAA MUAAAA AAAAxx
+399 533 1 3 9 19 9 99 199 399 399 18 19 JPAAAA NUAAAA HHHHxx
+609 534 1 1 9 9 9 9 9 109 609 18 19 LXAAAA OUAAAA OOOOxx
+677 535 1 1 7 17 7 77 77 177 677 14 15 BAAAAA PUAAAA VVVVxx
+478 536 0 2 8 18 8 78 78 478 478 16 17 KSAAAA QUAAAA AAAAxx
+452 537 0 0 2 12 2 52 52 452 452 4 5 KRAAAA RUAAAA HHHHxx
+261 538 1 1 1 1 1 61 61 261 261 2 3 BKAAAA SUAAAA OOOOxx
+449 539 1 1 9 9 9 49 49 449 449 18 19 HRAAAA TUAAAA VVVVxx
+433 540 1 1 3 13 3 33 33 433 433 6 7 RQAAAA UUAAAA AAAAxx
+5 541 1 1 5 5 5 5 5 5 5 10 11 FAAAAA VUAAAA HHHHxx
+664 542 0 0 4 4 4 64 64 164 664 8 9 OZAAAA WUAAAA OOOOxx
+887 543 1 3 7 7 7 87 87 387 887 14 15 DIAAAA XUAAAA VVVVxx
+546 544 0 2 6 6 6 46 146 46 546 12 13 AVAAAA YUAAAA AAAAxx
+253 545 1 1 3 13 3 53 53 253 253 6 7 TJAAAA ZUAAAA HHHHxx
+235 546 1 3 5 15 5 35 35 235 235 10 11 BJAAAA AVAAAA OOOOxx
+258 547 0 2 8 18 8 58 58 258 258 16 17 YJAAAA BVAAAA VVVVxx
+621 548 1 1 1 1 1 21 21 121 621 2 3 XXAAAA CVAAAA AAAAxx
+998 549 0 2 8 18 8 98 198 498 998 16 17 KMAAAA DVAAAA HHHHxx
+236 550 0 0 6 16 6 36 36 236 236 12 13 CJAAAA EVAAAA OOOOxx
+537 551 1 1 7 17 7 37 137 37 537 14 15 RUAAAA FVAAAA VVVVxx
+769 552 1 1 9 9 9 69 169 269 769 18 19 PDAAAA GVAAAA AAAAxx
+921 553 1 1 1 1 1 21 121 421 921 2 3 LJAAAA HVAAAA HHHHxx
+951 554 1 3 1 11 1 51 151 451 951 2 3 PKAAAA IVAAAA OOOOxx
+240 555 0 0 0 0 0 40 40 240 240 0 1 GJAAAA JVAAAA VVVVxx
+644 556 0 0 4 4 4 44 44 144 644 8 9 UYAAAA KVAAAA AAAAxx
+352 557 0 0 2 12 2 52 152 352 352 4 5 ONAAAA LVAAAA HHHHxx
+613 558 1 1 3 13 3 13 13 113 613 6 7 PXAAAA MVAAAA OOOOxx
+784 559 0 0 4 4 4 84 184 284 784 8 9 EEAAAA NVAAAA VVVVxx
+61 560 1 1 1 1 1 61 61 61 61 2 3 JCAAAA OVAAAA AAAAxx
+144 561 0 0 4 4 4 44 144 144 144 8 9 OFAAAA PVAAAA HHHHxx
+94 562 0 2 4 14 4 94 94 94 94 8 9 QDAAAA QVAAAA OOOOxx
+270 563 0 2 0 10 0 70 70 270 270 0 1 KKAAAA RVAAAA VVVVxx
+942 564 0 2 2 2 2 42 142 442 942 4 5 GKAAAA SVAAAA AAAAxx
+756 565 0 0 6 16 6 56 156 256 756 12 13 CDAAAA TVAAAA HHHHxx
+321 566 1 1 1 1 1 21 121 321 321 2 3 JMAAAA UVAAAA OOOOxx
+36 567 0 0 6 16 6 36 36 36 36 12 13 KBAAAA VVAAAA VVVVxx
+232 568 0 0 2 12 2 32 32 232 232 4 5 YIAAAA WVAAAA AAAAxx
+430 569 0 2 0 10 0 30 30 430 430 0 1 OQAAAA XVAAAA HHHHxx
+177 570 1 1 7 17 7 77 177 177 177 14 15 VGAAAA YVAAAA OOOOxx
+220 571 0 0 0 0 0 20 20 220 220 0 1 MIAAAA ZVAAAA VVVVxx
+109 572 1 1 9 9 9 9 109 109 109 18 19 FEAAAA AWAAAA AAAAxx
+419 573 1 3 9 19 9 19 19 419 419 18 19 DQAAAA BWAAAA HHHHxx
+135 574 1 3 5 15 5 35 135 135 135 10 11 FFAAAA CWAAAA OOOOxx
+610 575 0 2 0 10 0 10 10 110 610 0 1 MXAAAA DWAAAA VVVVxx
+956 576 0 0 6 16 6 56 156 456 956 12 13 UKAAAA EWAAAA AAAAxx
+626 577 0 2 6 6 6 26 26 126 626 12 13 CYAAAA FWAAAA HHHHxx
+375 578 1 3 5 15 5 75 175 375 375 10 11 LOAAAA GWAAAA OOOOxx
+976 579 0 0 6 16 6 76 176 476 976 12 13 OLAAAA HWAAAA VVVVxx
+152 580 0 0 2 12 2 52 152 152 152 4 5 WFAAAA IWAAAA AAAAxx
+308 581 0 0 8 8 8 8 108 308 308 16 17 WLAAAA JWAAAA HHHHxx
+445 582 1 1 5 5 5 45 45 445 445 10 11 DRAAAA KWAAAA OOOOxx
+326 583 0 2 6 6 6 26 126 326 326 12 13 OMAAAA LWAAAA VVVVxx
+422 584 0 2 2 2 2 22 22 422 422 4 5 GQAAAA MWAAAA AAAAxx
+972 585 0 0 2 12 2 72 172 472 972 4 5 KLAAAA NWAAAA HHHHxx
+45 586 1 1 5 5 5 45 45 45 45 10 11 TBAAAA OWAAAA OOOOxx
+725 587 1 1 5 5 5 25 125 225 725 10 11 XBAAAA PWAAAA VVVVxx
+753 588 1 1 3 13 3 53 153 253 753 6 7 ZCAAAA QWAAAA AAAAxx
+493 589 1 1 3 13 3 93 93 493 493 6 7 ZSAAAA RWAAAA HHHHxx
+601 590 1 1 1 1 1 1 1 101 601 2 3 DXAAAA SWAAAA OOOOxx
+463 591 1 3 3 3 3 63 63 463 463 6 7 VRAAAA TWAAAA VVVVxx
+303 592 1 3 3 3 3 3 103 303 303 6 7 RLAAAA UWAAAA AAAAxx
+59 593 1 3 9 19 9 59 59 59 59 18 19 HCAAAA VWAAAA HHHHxx
+595 594 1 3 5 15 5 95 195 95 595 10 11 XWAAAA WWAAAA OOOOxx
+807 595 1 3 7 7 7 7 7 307 807 14 15 BFAAAA XWAAAA VVVVxx
+424 596 0 0 4 4 4 24 24 424 424 8 9 IQAAAA YWAAAA AAAAxx
+521 597 1 1 1 1 1 21 121 21 521 2 3 BUAAAA ZWAAAA HHHHxx
+341 598 1 1 1 1 1 41 141 341 341 2 3 DNAAAA AXAAAA OOOOxx
+571 599 1 3 1 11 1 71 171 71 571 2 3 ZVAAAA BXAAAA VVVVxx
+165 600 1 1 5 5 5 65 165 165 165 10 11 JGAAAA CXAAAA AAAAxx
+908 601 0 0 8 8 8 8 108 408 908 16 17 YIAAAA DXAAAA HHHHxx
+351 602 1 3 1 11 1 51 151 351 351 2 3 NNAAAA EXAAAA OOOOxx
+334 603 0 2 4 14 4 34 134 334 334 8 9 WMAAAA FXAAAA VVVVxx
+636 604 0 0 6 16 6 36 36 136 636 12 13 MYAAAA GXAAAA AAAAxx
+138 605 0 2 8 18 8 38 138 138 138 16 17 IFAAAA HXAAAA HHHHxx
+438 606 0 2 8 18 8 38 38 438 438 16 17 WQAAAA IXAAAA OOOOxx
+391 607 1 3 1 11 1 91 191 391 391 2 3 BPAAAA JXAAAA VVVVxx
+395 608 1 3 5 15 5 95 195 395 395 10 11 FPAAAA KXAAAA AAAAxx
+502 609 0 2 2 2 2 2 102 2 502 4 5 ITAAAA LXAAAA HHHHxx
+85 610 1 1 5 5 5 85 85 85 85 10 11 HDAAAA MXAAAA OOOOxx
+786 611 0 2 6 6 6 86 186 286 786 12 13 GEAAAA NXAAAA VVVVxx
+619 612 1 3 9 19 9 19 19 119 619 18 19 VXAAAA OXAAAA AAAAxx
+440 613 0 0 0 0 0 40 40 440 440 0 1 YQAAAA PXAAAA HHHHxx
+949 614 1 1 9 9 9 49 149 449 949 18 19 NKAAAA QXAAAA OOOOxx
+691 615 1 3 1 11 1 91 91 191 691 2 3 PAAAAA RXAAAA VVVVxx
+348 616 0 0 8 8 8 48 148 348 348 16 17 KNAAAA SXAAAA AAAAxx
+506 617 0 2 6 6 6 6 106 6 506 12 13 MTAAAA TXAAAA HHHHxx
+192 618 0 0 2 12 2 92 192 192 192 4 5 KHAAAA UXAAAA OOOOxx
+369 619 1 1 9 9 9 69 169 369 369 18 19 FOAAAA VXAAAA VVVVxx
+311 620 1 3 1 11 1 11 111 311 311 2 3 ZLAAAA WXAAAA AAAAxx
+273 621 1 1 3 13 3 73 73 273 273 6 7 NKAAAA XXAAAA HHHHxx
+770 622 0 2 0 10 0 70 170 270 770 0 1 QDAAAA YXAAAA OOOOxx
+191 623 1 3 1 11 1 91 191 191 191 2 3 JHAAAA ZXAAAA VVVVxx
+90 624 0 2 0 10 0 90 90 90 90 0 1 MDAAAA AYAAAA AAAAxx
+163 625 1 3 3 3 3 63 163 163 163 6 7 HGAAAA BYAAAA HHHHxx
+350 626 0 2 0 10 0 50 150 350 350 0 1 MNAAAA CYAAAA OOOOxx
+55 627 1 3 5 15 5 55 55 55 55 10 11 DCAAAA DYAAAA VVVVxx
+488 628 0 0 8 8 8 88 88 488 488 16 17 USAAAA EYAAAA AAAAxx
+215 629 1 3 5 15 5 15 15 215 215 10 11 HIAAAA FYAAAA HHHHxx
+732 630 0 0 2 12 2 32 132 232 732 4 5 ECAAAA GYAAAA OOOOxx
+688 631 0 0 8 8 8 88 88 188 688 16 17 MAAAAA HYAAAA VVVVxx
+520 632 0 0 0 0 0 20 120 20 520 0 1 AUAAAA IYAAAA AAAAxx
+62 633 0 2 2 2 2 62 62 62 62 4 5 KCAAAA JYAAAA HHHHxx
+423 634 1 3 3 3 3 23 23 423 423 6 7 HQAAAA KYAAAA OOOOxx
+242 635 0 2 2 2 2 42 42 242 242 4 5 IJAAAA LYAAAA VVVVxx
+193 636 1 1 3 13 3 93 193 193 193 6 7 LHAAAA MYAAAA AAAAxx
+648 637 0 0 8 8 8 48 48 148 648 16 17 YYAAAA NYAAAA HHHHxx
+459 638 1 3 9 19 9 59 59 459 459 18 19 RRAAAA OYAAAA OOOOxx
+196 639 0 0 6 16 6 96 196 196 196 12 13 OHAAAA PYAAAA VVVVxx
+476 640 0 0 6 16 6 76 76 476 476 12 13 ISAAAA QYAAAA AAAAxx
+903 641 1 3 3 3 3 3 103 403 903 6 7 TIAAAA RYAAAA HHHHxx
+974 642 0 2 4 14 4 74 174 474 974 8 9 MLAAAA SYAAAA OOOOxx
+603 643 1 3 3 3 3 3 3 103 603 6 7 FXAAAA TYAAAA VVVVxx
+12 644 0 0 2 12 2 12 12 12 12 4 5 MAAAAA UYAAAA AAAAxx
+599 645 1 3 9 19 9 99 199 99 599 18 19 BXAAAA VYAAAA HHHHxx
+914 646 0 2 4 14 4 14 114 414 914 8 9 EJAAAA WYAAAA OOOOxx
+7 647 1 3 7 7 7 7 7 7 7 14 15 HAAAAA XYAAAA VVVVxx
+213 648 1 1 3 13 3 13 13 213 213 6 7 FIAAAA YYAAAA AAAAxx
+174 649 0 2 4 14 4 74 174 174 174 8 9 SGAAAA ZYAAAA HHHHxx
+392 650 0 0 2 12 2 92 192 392 392 4 5 CPAAAA AZAAAA OOOOxx
+674 651 0 2 4 14 4 74 74 174 674 8 9 YZAAAA BZAAAA VVVVxx
+650 652 0 2 0 10 0 50 50 150 650 0 1 AZAAAA CZAAAA AAAAxx
+8 653 0 0 8 8 8 8 8 8 8 16 17 IAAAAA DZAAAA HHHHxx
+492 654 0 0 2 12 2 92 92 492 492 4 5 YSAAAA EZAAAA OOOOxx
+322 655 0 2 2 2 2 22 122 322 322 4 5 KMAAAA FZAAAA VVVVxx
+315 656 1 3 5 15 5 15 115 315 315 10 11 DMAAAA GZAAAA AAAAxx
+380 657 0 0 0 0 0 80 180 380 380 0 1 QOAAAA HZAAAA HHHHxx
+353 658 1 1 3 13 3 53 153 353 353 6 7 PNAAAA IZAAAA OOOOxx
+892 659 0 0 2 12 2 92 92 392 892 4 5 IIAAAA JZAAAA VVVVxx
+932 660 0 0 2 12 2 32 132 432 932 4 5 WJAAAA KZAAAA AAAAxx
+993 661 1 1 3 13 3 93 193 493 993 6 7 FMAAAA LZAAAA HHHHxx
+859 662 1 3 9 19 9 59 59 359 859 18 19 BHAAAA MZAAAA OOOOxx
+806 663 0 2 6 6 6 6 6 306 806 12 13 AFAAAA NZAAAA VVVVxx
+145 664 1 1 5 5 5 45 145 145 145 10 11 PFAAAA OZAAAA AAAAxx
+373 665 1 1 3 13 3 73 173 373 373 6 7 JOAAAA PZAAAA HHHHxx
+418 666 0 2 8 18 8 18 18 418 418 16 17 CQAAAA QZAAAA OOOOxx
+865 667 1 1 5 5 5 65 65 365 865 10 11 HHAAAA RZAAAA VVVVxx
+462 668 0 2 2 2 2 62 62 462 462 4 5 URAAAA SZAAAA AAAAxx
+24 669 0 0 4 4 4 24 24 24 24 8 9 YAAAAA TZAAAA HHHHxx
+920 670 0 0 0 0 0 20 120 420 920 0 1 KJAAAA UZAAAA OOOOxx
+672 671 0 0 2 12 2 72 72 172 672 4 5 WZAAAA VZAAAA VVVVxx
+92 672 0 0 2 12 2 92 92 92 92 4 5 ODAAAA WZAAAA AAAAxx
+721 673 1 1 1 1 1 21 121 221 721 2 3 TBAAAA XZAAAA HHHHxx
+646 674 0 2 6 6 6 46 46 146 646 12 13 WYAAAA YZAAAA OOOOxx
+910 675 0 2 0 10 0 10 110 410 910 0 1 AJAAAA ZZAAAA VVVVxx
+909 676 1 1 9 9 9 9 109 409 909 18 19 ZIAAAA AABAAA AAAAxx
+630 677 0 2 0 10 0 30 30 130 630 0 1 GYAAAA BABAAA HHHHxx
+482 678 0 2 2 2 2 82 82 482 482 4 5 OSAAAA CABAAA OOOOxx
+559 679 1 3 9 19 9 59 159 59 559 18 19 NVAAAA DABAAA VVVVxx
+853 680 1 1 3 13 3 53 53 353 853 6 7 VGAAAA EABAAA AAAAxx
+141 681 1 1 1 1 1 41 141 141 141 2 3 LFAAAA FABAAA HHHHxx
+266 682 0 2 6 6 6 66 66 266 266 12 13 GKAAAA GABAAA OOOOxx
+835 683 1 3 5 15 5 35 35 335 835 10 11 DGAAAA HABAAA VVVVxx
+164 684 0 0 4 4 4 64 164 164 164 8 9 IGAAAA IABAAA AAAAxx
+629 685 1 1 9 9 9 29 29 129 629 18 19 FYAAAA JABAAA HHHHxx
+203 686 1 3 3 3 3 3 3 203 203 6 7 VHAAAA KABAAA OOOOxx
+411 687 1 3 1 11 1 11 11 411 411 2 3 VPAAAA LABAAA VVVVxx
+930 688 0 2 0 10 0 30 130 430 930 0 1 UJAAAA MABAAA AAAAxx
+435 689 1 3 5 15 5 35 35 435 435 10 11 TQAAAA NABAAA HHHHxx
+563 690 1 3 3 3 3 63 163 63 563 6 7 RVAAAA OABAAA OOOOxx
+960 691 0 0 0 0 0 60 160 460 960 0 1 YKAAAA PABAAA VVVVxx
+733 692 1 1 3 13 3 33 133 233 733 6 7 FCAAAA QABAAA AAAAxx
+967 693 1 3 7 7 7 67 167 467 967 14 15 FLAAAA RABAAA HHHHxx
+668 694 0 0 8 8 8 68 68 168 668 16 17 SZAAAA SABAAA OOOOxx
+994 695 0 2 4 14 4 94 194 494 994 8 9 GMAAAA TABAAA VVVVxx
+129 696 1 1 9 9 9 29 129 129 129 18 19 ZEAAAA UABAAA AAAAxx
+954 697 0 2 4 14 4 54 154 454 954 8 9 SKAAAA VABAAA HHHHxx
+68 698 0 0 8 8 8 68 68 68 68 16 17 QCAAAA WABAAA OOOOxx
+79 699 1 3 9 19 9 79 79 79 79 18 19 BDAAAA XABAAA VVVVxx
+121 700 1 1 1 1 1 21 121 121 121 2 3 REAAAA YABAAA AAAAxx
+740 701 0 0 0 0 0 40 140 240 740 0 1 MCAAAA ZABAAA HHHHxx
+902 702 0 2 2 2 2 2 102 402 902 4 5 SIAAAA ABBAAA OOOOxx
+695 703 1 3 5 15 5 95 95 195 695 10 11 TAAAAA BBBAAA VVVVxx
+455 704 1 3 5 15 5 55 55 455 455 10 11 NRAAAA CBBAAA AAAAxx
+89 705 1 1 9 9 9 89 89 89 89 18 19 LDAAAA DBBAAA HHHHxx
+893 706 1 1 3 13 3 93 93 393 893 6 7 JIAAAA EBBAAA OOOOxx
+202 707 0 2 2 2 2 2 2 202 202 4 5 UHAAAA FBBAAA VVVVxx
+132 708 0 0 2 12 2 32 132 132 132 4 5 CFAAAA GBBAAA AAAAxx
+782 709 0 2 2 2 2 82 182 282 782 4 5 CEAAAA HBBAAA HHHHxx
+512 710 0 0 2 12 2 12 112 12 512 4 5 STAAAA IBBAAA OOOOxx
+857 711 1 1 7 17 7 57 57 357 857 14 15 ZGAAAA JBBAAA VVVVxx
+248 712 0 0 8 8 8 48 48 248 248 16 17 OJAAAA KBBAAA AAAAxx
+858 713 0 2 8 18 8 58 58 358 858 16 17 AHAAAA LBBAAA HHHHxx
+527 714 1 3 7 7 7 27 127 27 527 14 15 HUAAAA MBBAAA OOOOxx
+450 715 0 2 0 10 0 50 50 450 450 0 1 IRAAAA NBBAAA VVVVxx
+712 716 0 0 2 12 2 12 112 212 712 4 5 KBAAAA OBBAAA AAAAxx
+153 717 1 1 3 13 3 53 153 153 153 6 7 XFAAAA PBBAAA HHHHxx
+587 718 1 3 7 7 7 87 187 87 587 14 15 PWAAAA QBBAAA OOOOxx
+593 719 1 1 3 13 3 93 193 93 593 6 7 VWAAAA RBBAAA VVVVxx
+249 720 1 1 9 9 9 49 49 249 249 18 19 PJAAAA SBBAAA AAAAxx
+128 721 0 0 8 8 8 28 128 128 128 16 17 YEAAAA TBBAAA HHHHxx
+675 722 1 3 5 15 5 75 75 175 675 10 11 ZZAAAA UBBAAA OOOOxx
+929 723 1 1 9 9 9 29 129 429 929 18 19 TJAAAA VBBAAA VVVVxx
+156 724 0 0 6 16 6 56 156 156 156 12 13 AGAAAA WBBAAA AAAAxx
+415 725 1 3 5 15 5 15 15 415 415 10 11 ZPAAAA XBBAAA HHHHxx
+28 726 0 0 8 8 8 28 28 28 28 16 17 CBAAAA YBBAAA OOOOxx
+18 727 0 2 8 18 8 18 18 18 18 16 17 SAAAAA ZBBAAA VVVVxx
+255 728 1 3 5 15 5 55 55 255 255 10 11 VJAAAA ACBAAA AAAAxx
+793 729 1 1 3 13 3 93 193 293 793 6 7 NEAAAA BCBAAA HHHHxx
+554 730 0 2 4 14 4 54 154 54 554 8 9 IVAAAA CCBAAA OOOOxx
+467 731 1 3 7 7 7 67 67 467 467 14 15 ZRAAAA DCBAAA VVVVxx
+410 732 0 2 0 10 0 10 10 410 410 0 1 UPAAAA ECBAAA AAAAxx
+651 733 1 3 1 11 1 51 51 151 651 2 3 BZAAAA FCBAAA HHHHxx
+287 734 1 3 7 7 7 87 87 287 287 14 15 BLAAAA GCBAAA OOOOxx
+640 735 0 0 0 0 0 40 40 140 640 0 1 QYAAAA HCBAAA VVVVxx
+245 736 1 1 5 5 5 45 45 245 245 10 11 LJAAAA ICBAAA AAAAxx
+21 737 1 1 1 1 1 21 21 21 21 2 3 VAAAAA JCBAAA HHHHxx
+83 738 1 3 3 3 3 83 83 83 83 6 7 FDAAAA KCBAAA OOOOxx
+228 739 0 0 8 8 8 28 28 228 228 16 17 UIAAAA LCBAAA VVVVxx
+323 740 1 3 3 3 3 23 123 323 323 6 7 LMAAAA MCBAAA AAAAxx
+594 741 0 2 4 14 4 94 194 94 594 8 9 WWAAAA NCBAAA HHHHxx
+528 742 0 0 8 8 8 28 128 28 528 16 17 IUAAAA OCBAAA OOOOxx
+276 743 0 0 6 16 6 76 76 276 276 12 13 QKAAAA PCBAAA VVVVxx
+598 744 0 2 8 18 8 98 198 98 598 16 17 AXAAAA QCBAAA AAAAxx
+635 745 1 3 5 15 5 35 35 135 635 10 11 LYAAAA RCBAAA HHHHxx
+868 746 0 0 8 8 8 68 68 368 868 16 17 KHAAAA SCBAAA OOOOxx
+290 747 0 2 0 10 0 90 90 290 290 0 1 ELAAAA TCBAAA VVVVxx
+468 748 0 0 8 8 8 68 68 468 468 16 17 ASAAAA UCBAAA AAAAxx
+689 749 1 1 9 9 9 89 89 189 689 18 19 NAAAAA VCBAAA HHHHxx
+799 750 1 3 9 19 9 99 199 299 799 18 19 TEAAAA WCBAAA OOOOxx
+210 751 0 2 0 10 0 10 10 210 210 0 1 CIAAAA XCBAAA VVVVxx
+346 752 0 2 6 6 6 46 146 346 346 12 13 INAAAA YCBAAA AAAAxx
+957 753 1 1 7 17 7 57 157 457 957 14 15 VKAAAA ZCBAAA HHHHxx
+905 754 1 1 5 5 5 5 105 405 905 10 11 VIAAAA ADBAAA OOOOxx
+523 755 1 3 3 3 3 23 123 23 523 6 7 DUAAAA BDBAAA VVVVxx
+899 756 1 3 9 19 9 99 99 399 899 18 19 PIAAAA CDBAAA AAAAxx
+867 757 1 3 7 7 7 67 67 367 867 14 15 JHAAAA DDBAAA HHHHxx
+11 758 1 3 1 11 1 11 11 11 11 2 3 LAAAAA EDBAAA OOOOxx
+320 759 0 0 0 0 0 20 120 320 320 0 1 IMAAAA FDBAAA VVVVxx
+766 760 0 2 6 6 6 66 166 266 766 12 13 MDAAAA GDBAAA AAAAxx
+84 761 0 0 4 4 4 84 84 84 84 8 9 GDAAAA HDBAAA HHHHxx
+507 762 1 3 7 7 7 7 107 7 507 14 15 NTAAAA IDBAAA OOOOxx
+471 763 1 3 1 11 1 71 71 471 471 2 3 DSAAAA JDBAAA VVVVxx
+517 764 1 1 7 17 7 17 117 17 517 14 15 XTAAAA KDBAAA AAAAxx
+234 765 0 2 4 14 4 34 34 234 234 8 9 AJAAAA LDBAAA HHHHxx
+988 766 0 0 8 8 8 88 188 488 988 16 17 AMAAAA MDBAAA OOOOxx
+473 767 1 1 3 13 3 73 73 473 473 6 7 FSAAAA NDBAAA VVVVxx
+66 768 0 2 6 6 6 66 66 66 66 12 13 OCAAAA ODBAAA AAAAxx
+530 769 0 2 0 10 0 30 130 30 530 0 1 KUAAAA PDBAAA HHHHxx
+834 770 0 2 4 14 4 34 34 334 834 8 9 CGAAAA QDBAAA OOOOxx
+894 771 0 2 4 14 4 94 94 394 894 8 9 KIAAAA RDBAAA VVVVxx
+481 772 1 1 1 1 1 81 81 481 481 2 3 NSAAAA SDBAAA AAAAxx
+280 773 0 0 0 0 0 80 80 280 280 0 1 UKAAAA TDBAAA HHHHxx
+705 774 1 1 5 5 5 5 105 205 705 10 11 DBAAAA UDBAAA OOOOxx
+218 775 0 2 8 18 8 18 18 218 218 16 17 KIAAAA VDBAAA VVVVxx
+560 776 0 0 0 0 0 60 160 60 560 0 1 OVAAAA WDBAAA AAAAxx
+123 777 1 3 3 3 3 23 123 123 123 6 7 TEAAAA XDBAAA HHHHxx
+289 778 1 1 9 9 9 89 89 289 289 18 19 DLAAAA YDBAAA OOOOxx
+189 779 1 1 9 9 9 89 189 189 189 18 19 HHAAAA ZDBAAA VVVVxx
+541 780 1 1 1 1 1 41 141 41 541 2 3 VUAAAA AEBAAA AAAAxx
+876 781 0 0 6 16 6 76 76 376 876 12 13 SHAAAA BEBAAA HHHHxx
+504 782 0 0 4 4 4 4 104 4 504 8 9 KTAAAA CEBAAA OOOOxx
+643 783 1 3 3 3 3 43 43 143 643 6 7 TYAAAA DEBAAA VVVVxx
+73 784 1 1 3 13 3 73 73 73 73 6 7 VCAAAA EEBAAA AAAAxx
+465 785 1 1 5 5 5 65 65 465 465 10 11 XRAAAA FEBAAA HHHHxx
+861 786 1 1 1 1 1 61 61 361 861 2 3 DHAAAA GEBAAA OOOOxx
+355 787 1 3 5 15 5 55 155 355 355 10 11 RNAAAA HEBAAA VVVVxx
+441 788 1 1 1 1 1 41 41 441 441 2 3 ZQAAAA IEBAAA AAAAxx
+219 789 1 3 9 19 9 19 19 219 219 18 19 LIAAAA JEBAAA HHHHxx
+839 790 1 3 9 19 9 39 39 339 839 18 19 HGAAAA KEBAAA OOOOxx
+271 791 1 3 1 11 1 71 71 271 271 2 3 LKAAAA LEBAAA VVVVxx
+212 792 0 0 2 12 2 12 12 212 212 4 5 EIAAAA MEBAAA AAAAxx
+904 793 0 0 4 4 4 4 104 404 904 8 9 UIAAAA NEBAAA HHHHxx
+244 794 0 0 4 4 4 44 44 244 244 8 9 KJAAAA OEBAAA OOOOxx
+751 795 1 3 1 11 1 51 151 251 751 2 3 XCAAAA PEBAAA VVVVxx
+944 796 0 0 4 4 4 44 144 444 944 8 9 IKAAAA QEBAAA AAAAxx
+305 797 1 1 5 5 5 5 105 305 305 10 11 TLAAAA REBAAA HHHHxx
+617 798 1 1 7 17 7 17 17 117 617 14 15 TXAAAA SEBAAA OOOOxx
+891 799 1 3 1 11 1 91 91 391 891 2 3 HIAAAA TEBAAA VVVVxx
+653 800 1 1 3 13 3 53 53 153 653 6 7 DZAAAA UEBAAA AAAAxx
+845 801 1 1 5 5 5 45 45 345 845 10 11 NGAAAA VEBAAA HHHHxx
+936 802 0 0 6 16 6 36 136 436 936 12 13 AKAAAA WEBAAA OOOOxx
+91 803 1 3 1 11 1 91 91 91 91 2 3 NDAAAA XEBAAA VVVVxx
+442 804 0 2 2 2 2 42 42 442 442 4 5 ARAAAA YEBAAA AAAAxx
+498 805 0 2 8 18 8 98 98 498 498 16 17 ETAAAA ZEBAAA HHHHxx
+987 806 1 3 7 7 7 87 187 487 987 14 15 ZLAAAA AFBAAA OOOOxx
+194 807 0 2 4 14 4 94 194 194 194 8 9 MHAAAA BFBAAA VVVVxx
+927 808 1 3 7 7 7 27 127 427 927 14 15 RJAAAA CFBAAA AAAAxx
+607 809 1 3 7 7 7 7 7 107 607 14 15 JXAAAA DFBAAA HHHHxx
+119 810 1 3 9 19 9 19 119 119 119 18 19 PEAAAA EFBAAA OOOOxx
+182 811 0 2 2 2 2 82 182 182 182 4 5 AHAAAA FFBAAA VVVVxx
+606 812 0 2 6 6 6 6 6 106 606 12 13 IXAAAA GFBAAA AAAAxx
+849 813 1 1 9 9 9 49 49 349 849 18 19 RGAAAA HFBAAA HHHHxx
+34 814 0 2 4 14 4 34 34 34 34 8 9 IBAAAA IFBAAA OOOOxx
+683 815 1 3 3 3 3 83 83 183 683 6 7 HAAAAA JFBAAA VVVVxx
+134 816 0 2 4 14 4 34 134 134 134 8 9 EFAAAA KFBAAA AAAAxx
+331 817 1 3 1 11 1 31 131 331 331 2 3 TMAAAA LFBAAA HHHHxx
+808 818 0 0 8 8 8 8 8 308 808 16 17 CFAAAA MFBAAA OOOOxx
+703 819 1 3 3 3 3 3 103 203 703 6 7 BBAAAA NFBAAA VVVVxx
+669 820 1 1 9 9 9 69 69 169 669 18 19 TZAAAA OFBAAA AAAAxx
+264 821 0 0 4 4 4 64 64 264 264 8 9 EKAAAA PFBAAA HHHHxx
+277 822 1 1 7 17 7 77 77 277 277 14 15 RKAAAA QFBAAA OOOOxx
+877 823 1 1 7 17 7 77 77 377 877 14 15 THAAAA RFBAAA VVVVxx
+783 824 1 3 3 3 3 83 183 283 783 6 7 DEAAAA SFBAAA AAAAxx
+791 825 1 3 1 11 1 91 191 291 791 2 3 LEAAAA TFBAAA HHHHxx
+171 826 1 3 1 11 1 71 171 171 171 2 3 PGAAAA UFBAAA OOOOxx
+564 827 0 0 4 4 4 64 164 64 564 8 9 SVAAAA VFBAAA VVVVxx
+230 828 0 2 0 10 0 30 30 230 230 0 1 WIAAAA WFBAAA AAAAxx
+881 829 1 1 1 1 1 81 81 381 881 2 3 XHAAAA XFBAAA HHHHxx
+890 830 0 2 0 10 0 90 90 390 890 0 1 GIAAAA YFBAAA OOOOxx
+374 831 0 2 4 14 4 74 174 374 374 8 9 KOAAAA ZFBAAA VVVVxx
+697 832 1 1 7 17 7 97 97 197 697 14 15 VAAAAA AGBAAA AAAAxx
+4 833 0 0 4 4 4 4 4 4 4 8 9 EAAAAA BGBAAA HHHHxx
+385 834 1 1 5 5 5 85 185 385 385 10 11 VOAAAA CGBAAA OOOOxx
+739 835 1 3 9 19 9 39 139 239 739 18 19 LCAAAA DGBAAA VVVVxx
+623 836 1 3 3 3 3 23 23 123 623 6 7 ZXAAAA EGBAAA AAAAxx
+547 837 1 3 7 7 7 47 147 47 547 14 15 BVAAAA FGBAAA HHHHxx
+532 838 0 0 2 12 2 32 132 32 532 4 5 MUAAAA GGBAAA OOOOxx
+383 839 1 3 3 3 3 83 183 383 383 6 7 TOAAAA HGBAAA VVVVxx
+181 840 1 1 1 1 1 81 181 181 181 2 3 ZGAAAA IGBAAA AAAAxx
+327 841 1 3 7 7 7 27 127 327 327 14 15 PMAAAA JGBAAA HHHHxx
+701 842 1 1 1 1 1 1 101 201 701 2 3 ZAAAAA KGBAAA OOOOxx
+111 843 1 3 1 11 1 11 111 111 111 2 3 HEAAAA LGBAAA VVVVxx
+977 844 1 1 7 17 7 77 177 477 977 14 15 PLAAAA MGBAAA AAAAxx
+431 845 1 3 1 11 1 31 31 431 431 2 3 PQAAAA NGBAAA HHHHxx
+456 846 0 0 6 16 6 56 56 456 456 12 13 ORAAAA OGBAAA OOOOxx
+368 847 0 0 8 8 8 68 168 368 368 16 17 EOAAAA PGBAAA VVVVxx
+32 848 0 0 2 12 2 32 32 32 32 4 5 GBAAAA QGBAAA AAAAxx
+125 849 1 1 5 5 5 25 125 125 125 10 11 VEAAAA RGBAAA HHHHxx
+847 850 1 3 7 7 7 47 47 347 847 14 15 PGAAAA SGBAAA OOOOxx
+485 851 1 1 5 5 5 85 85 485 485 10 11 RSAAAA TGBAAA VVVVxx
+387 852 1 3 7 7 7 87 187 387 387 14 15 XOAAAA UGBAAA AAAAxx
+288 853 0 0 8 8 8 88 88 288 288 16 17 CLAAAA VGBAAA HHHHxx
+919 854 1 3 9 19 9 19 119 419 919 18 19 JJAAAA WGBAAA OOOOxx
+393 855 1 1 3 13 3 93 193 393 393 6 7 DPAAAA XGBAAA VVVVxx
+953 856 1 1 3 13 3 53 153 453 953 6 7 RKAAAA YGBAAA AAAAxx
+798 857 0 2 8 18 8 98 198 298 798 16 17 SEAAAA ZGBAAA HHHHxx
+940 858 0 0 0 0 0 40 140 440 940 0 1 EKAAAA AHBAAA OOOOxx
+198 859 0 2 8 18 8 98 198 198 198 16 17 QHAAAA BHBAAA VVVVxx
+25 860 1 1 5 5 5 25 25 25 25 10 11 ZAAAAA CHBAAA AAAAxx
+190 861 0 2 0 10 0 90 190 190 190 0 1 IHAAAA DHBAAA HHHHxx
+820 862 0 0 0 0 0 20 20 320 820 0 1 OFAAAA EHBAAA OOOOxx
+15 863 1 3 5 15 5 15 15 15 15 10 11 PAAAAA FHBAAA VVVVxx
+427 864 1 3 7 7 7 27 27 427 427 14 15 LQAAAA GHBAAA AAAAxx
+349 865 1 1 9 9 9 49 149 349 349 18 19 LNAAAA HHBAAA HHHHxx
+785 866 1 1 5 5 5 85 185 285 785 10 11 FEAAAA IHBAAA OOOOxx
+340 867 0 0 0 0 0 40 140 340 340 0 1 CNAAAA JHBAAA VVVVxx
+292 868 0 0 2 12 2 92 92 292 292 4 5 GLAAAA KHBAAA AAAAxx
+17 869 1 1 7 17 7 17 17 17 17 14 15 RAAAAA LHBAAA HHHHxx
+985 870 1 1 5 5 5 85 185 485 985 10 11 XLAAAA MHBAAA OOOOxx
+645 871 1 1 5 5 5 45 45 145 645 10 11 VYAAAA NHBAAA VVVVxx
+631 872 1 3 1 11 1 31 31 131 631 2 3 HYAAAA OHBAAA AAAAxx
+761 873 1 1 1 1 1 61 161 261 761 2 3 HDAAAA PHBAAA HHHHxx
+707 874 1 3 7 7 7 7 107 207 707 14 15 FBAAAA QHBAAA OOOOxx
+776 875 0 0 6 16 6 76 176 276 776 12 13 WDAAAA RHBAAA VVVVxx
+856 876 0 0 6 16 6 56 56 356 856 12 13 YGAAAA SHBAAA AAAAxx
+978 877 0 2 8 18 8 78 178 478 978 16 17 QLAAAA THBAAA HHHHxx
+710 878 0 2 0 10 0 10 110 210 710 0 1 IBAAAA UHBAAA OOOOxx
+604 879 0 0 4 4 4 4 4 104 604 8 9 GXAAAA VHBAAA VVVVxx
+291 880 1 3 1 11 1 91 91 291 291 2 3 FLAAAA WHBAAA AAAAxx
+747 881 1 3 7 7 7 47 147 247 747 14 15 TCAAAA XHBAAA HHHHxx
+837 882 1 1 7 17 7 37 37 337 837 14 15 FGAAAA YHBAAA OOOOxx
+722 883 0 2 2 2 2 22 122 222 722 4 5 UBAAAA ZHBAAA VVVVxx
+925 884 1 1 5 5 5 25 125 425 925 10 11 PJAAAA AIBAAA AAAAxx
+49 885 1 1 9 9 9 49 49 49 49 18 19 XBAAAA BIBAAA HHHHxx
+832 886 0 0 2 12 2 32 32 332 832 4 5 AGAAAA CIBAAA OOOOxx
+336 887 0 0 6 16 6 36 136 336 336 12 13 YMAAAA DIBAAA VVVVxx
+185 888 1 1 5 5 5 85 185 185 185 10 11 DHAAAA EIBAAA AAAAxx
+434 889 0 2 4 14 4 34 34 434 434 8 9 SQAAAA FIBAAA HHHHxx
+284 890 0 0 4 4 4 84 84 284 284 8 9 YKAAAA GIBAAA OOOOxx
+812 891 0 0 2 12 2 12 12 312 812 4 5 GFAAAA HIBAAA VVVVxx
+810 892 0 2 0 10 0 10 10 310 810 0 1 EFAAAA IIBAAA AAAAxx
+252 893 0 0 2 12 2 52 52 252 252 4 5 SJAAAA JIBAAA HHHHxx
+965 894 1 1 5 5 5 65 165 465 965 10 11 DLAAAA KIBAAA OOOOxx
+110 895 0 2 0 10 0 10 110 110 110 0 1 GEAAAA LIBAAA VVVVxx
+698 896 0 2 8 18 8 98 98 198 698 16 17 WAAAAA MIBAAA AAAAxx
+283 897 1 3 3 3 3 83 83 283 283 6 7 XKAAAA NIBAAA HHHHxx
+533 898 1 1 3 13 3 33 133 33 533 6 7 NUAAAA OIBAAA OOOOxx
+662 899 0 2 2 2 2 62 62 162 662 4 5 MZAAAA PIBAAA VVVVxx
+329 900 1 1 9 9 9 29 129 329 329 18 19 RMAAAA QIBAAA AAAAxx
+250 901 0 2 0 10 0 50 50 250 250 0 1 QJAAAA RIBAAA HHHHxx
+407 902 1 3 7 7 7 7 7 407 407 14 15 RPAAAA SIBAAA OOOOxx
+823 903 1 3 3 3 3 23 23 323 823 6 7 RFAAAA TIBAAA VVVVxx
+852 904 0 0 2 12 2 52 52 352 852 4 5 UGAAAA UIBAAA AAAAxx
+871 905 1 3 1 11 1 71 71 371 871 2 3 NHAAAA VIBAAA HHHHxx
+118 906 0 2 8 18 8 18 118 118 118 16 17 OEAAAA WIBAAA OOOOxx
+912 907 0 0 2 12 2 12 112 412 912 4 5 CJAAAA XIBAAA VVVVxx
+458 908 0 2 8 18 8 58 58 458 458 16 17 QRAAAA YIBAAA AAAAxx
+926 909 0 2 6 6 6 26 126 426 926 12 13 QJAAAA ZIBAAA HHHHxx
+328 910 0 0 8 8 8 28 128 328 328 16 17 QMAAAA AJBAAA OOOOxx
+980 911 0 0 0 0 0 80 180 480 980 0 1 SLAAAA BJBAAA VVVVxx
+259 912 1 3 9 19 9 59 59 259 259 18 19 ZJAAAA CJBAAA AAAAxx
+900 913 0 0 0 0 0 0 100 400 900 0 1 QIAAAA DJBAAA HHHHxx
+137 914 1 1 7 17 7 37 137 137 137 14 15 HFAAAA EJBAAA OOOOxx
+159 915 1 3 9 19 9 59 159 159 159 18 19 DGAAAA FJBAAA VVVVxx
+243 916 1 3 3 3 3 43 43 243 243 6 7 JJAAAA GJBAAA AAAAxx
+472 917 0 0 2 12 2 72 72 472 472 4 5 ESAAAA HJBAAA HHHHxx
+796 918 0 0 6 16 6 96 196 296 796 12 13 QEAAAA IJBAAA OOOOxx
+382 919 0 2 2 2 2 82 182 382 382 4 5 SOAAAA JJBAAA VVVVxx
+911 920 1 3 1 11 1 11 111 411 911 2 3 BJAAAA KJBAAA AAAAxx
+179 921 1 3 9 19 9 79 179 179 179 18 19 XGAAAA LJBAAA HHHHxx
+778 922 0 2 8 18 8 78 178 278 778 16 17 YDAAAA MJBAAA OOOOxx
+405 923 1 1 5 5 5 5 5 405 405 10 11 PPAAAA NJBAAA VVVVxx
+265 924 1 1 5 5 5 65 65 265 265 10 11 FKAAAA OJBAAA AAAAxx
+556 925 0 0 6 16 6 56 156 56 556 12 13 KVAAAA PJBAAA HHHHxx
+16 926 0 0 6 16 6 16 16 16 16 12 13 QAAAAA QJBAAA OOOOxx
+706 927 0 2 6 6 6 6 106 206 706 12 13 EBAAAA RJBAAA VVVVxx
+497 928 1 1 7 17 7 97 97 497 497 14 15 DTAAAA SJBAAA AAAAxx
+708 929 0 0 8 8 8 8 108 208 708 16 17 GBAAAA TJBAAA HHHHxx
+46 930 0 2 6 6 6 46 46 46 46 12 13 UBAAAA UJBAAA OOOOxx
+901 931 1 1 1 1 1 1 101 401 901 2 3 RIAAAA VJBAAA VVVVxx
+416 932 0 0 6 16 6 16 16 416 416 12 13 AQAAAA WJBAAA AAAAxx
+307 933 1 3 7 7 7 7 107 307 307 14 15 VLAAAA XJBAAA HHHHxx
+166 934 0 2 6 6 6 66 166 166 166 12 13 KGAAAA YJBAAA OOOOxx
+178 935 0 2 8 18 8 78 178 178 178 16 17 WGAAAA ZJBAAA VVVVxx
+499 936 1 3 9 19 9 99 99 499 499 18 19 FTAAAA AKBAAA AAAAxx
+257 937 1 1 7 17 7 57 57 257 257 14 15 XJAAAA BKBAAA HHHHxx
+342 938 0 2 2 2 2 42 142 342 342 4 5 ENAAAA CKBAAA OOOOxx
+850 939 0 2 0 10 0 50 50 350 850 0 1 SGAAAA DKBAAA VVVVxx
+313 940 1 1 3 13 3 13 113 313 313 6 7 BMAAAA EKBAAA AAAAxx
+831 941 1 3 1 11 1 31 31 331 831 2 3 ZFAAAA FKBAAA HHHHxx
+57 942 1 1 7 17 7 57 57 57 57 14 15 FCAAAA GKBAAA OOOOxx
+37 943 1 1 7 17 7 37 37 37 37 14 15 LBAAAA HKBAAA VVVVxx
+511 944 1 3 1 11 1 11 111 11 511 2 3 RTAAAA IKBAAA AAAAxx
+578 945 0 2 8 18 8 78 178 78 578 16 17 GWAAAA JKBAAA HHHHxx
+100 946 0 0 0 0 0 0 100 100 100 0 1 WDAAAA KKBAAA OOOOxx
+935 947 1 3 5 15 5 35 135 435 935 10 11 ZJAAAA LKBAAA VVVVxx
+821 948 1 1 1 1 1 21 21 321 821 2 3 PFAAAA MKBAAA AAAAxx
+294 949 0 2 4 14 4 94 94 294 294 8 9 ILAAAA NKBAAA HHHHxx
+575 950 1 3 5 15 5 75 175 75 575 10 11 DWAAAA OKBAAA OOOOxx
+272 951 0 0 2 12 2 72 72 272 272 4 5 MKAAAA PKBAAA VVVVxx
+491 952 1 3 1 11 1 91 91 491 491 2 3 XSAAAA QKBAAA AAAAxx
+43 953 1 3 3 3 3 43 43 43 43 6 7 RBAAAA RKBAAA HHHHxx
+167 954 1 3 7 7 7 67 167 167 167 14 15 LGAAAA SKBAAA OOOOxx
+457 955 1 1 7 17 7 57 57 457 457 14 15 PRAAAA TKBAAA VVVVxx
+647 956 1 3 7 7 7 47 47 147 647 14 15 XYAAAA UKBAAA AAAAxx
+180 957 0 0 0 0 0 80 180 180 180 0 1 YGAAAA VKBAAA HHHHxx
+48 958 0 0 8 8 8 48 48 48 48 16 17 WBAAAA WKBAAA OOOOxx
+553 959 1 1 3 13 3 53 153 53 553 6 7 HVAAAA XKBAAA VVVVxx
+188 960 0 0 8 8 8 88 188 188 188 16 17 GHAAAA YKBAAA AAAAxx
+262 961 0 2 2 2 2 62 62 262 262 4 5 CKAAAA ZKBAAA HHHHxx
+728 962 0 0 8 8 8 28 128 228 728 16 17 ACAAAA ALBAAA OOOOxx
+581 963 1 1 1 1 1 81 181 81 581 2 3 JWAAAA BLBAAA VVVVxx
+937 964 1 1 7 17 7 37 137 437 937 14 15 BKAAAA CLBAAA AAAAxx
+370 965 0 2 0 10 0 70 170 370 370 0 1 GOAAAA DLBAAA HHHHxx
+590 966 0 2 0 10 0 90 190 90 590 0 1 SWAAAA ELBAAA OOOOxx
+421 967 1 1 1 1 1 21 21 421 421 2 3 FQAAAA FLBAAA VVVVxx
+693 968 1 1 3 13 3 93 93 193 693 6 7 RAAAAA GLBAAA AAAAxx
+906 969 0 2 6 6 6 6 106 406 906 12 13 WIAAAA HLBAAA HHHHxx
+802 970 0 2 2 2 2 2 2 302 802 4 5 WEAAAA ILBAAA OOOOxx
+38 971 0 2 8 18 8 38 38 38 38 16 17 MBAAAA JLBAAA VVVVxx
+790 972 0 2 0 10 0 90 190 290 790 0 1 KEAAAA KLBAAA AAAAxx
+726 973 0 2 6 6 6 26 126 226 726 12 13 YBAAAA LLBAAA HHHHxx
+23 974 1 3 3 3 3 23 23 23 23 6 7 XAAAAA MLBAAA OOOOxx
+641 975 1 1 1 1 1 41 41 141 641 2 3 RYAAAA NLBAAA VVVVxx
+524 976 0 0 4 4 4 24 124 24 524 8 9 EUAAAA OLBAAA AAAAxx
+169 977 1 1 9 9 9 69 169 169 169 18 19 NGAAAA PLBAAA HHHHxx
+6 978 0 2 6 6 6 6 6 6 6 12 13 GAAAAA QLBAAA OOOOxx
+943 979 1 3 3 3 3 43 143 443 943 6 7 HKAAAA RLBAAA VVVVxx
+26 980 0 2 6 6 6 26 26 26 26 12 13 ABAAAA SLBAAA AAAAxx
+469 981 1 1 9 9 9 69 69 469 469 18 19 BSAAAA TLBAAA HHHHxx
+968 982 0 0 8 8 8 68 168 468 968 16 17 GLAAAA ULBAAA OOOOxx
+947 983 1 3 7 7 7 47 147 447 947 14 15 LKAAAA VLBAAA VVVVxx
+133 984 1 1 3 13 3 33 133 133 133 6 7 DFAAAA WLBAAA AAAAxx
+52 985 0 0 2 12 2 52 52 52 52 4 5 ACAAAA XLBAAA HHHHxx
+660 986 0 0 0 0 0 60 60 160 660 0 1 KZAAAA YLBAAA OOOOxx
+780 987 0 0 0 0 0 80 180 280 780 0 1 AEAAAA ZLBAAA VVVVxx
+963 988 1 3 3 3 3 63 163 463 963 6 7 BLAAAA AMBAAA AAAAxx
+561 989 1 1 1 1 1 61 161 61 561 2 3 PVAAAA BMBAAA HHHHxx
+402 990 0 2 2 2 2 2 2 402 402 4 5 MPAAAA CMBAAA OOOOxx
+437 991 1 1 7 17 7 37 37 437 437 14 15 VQAAAA DMBAAA VVVVxx
+112 992 0 0 2 12 2 12 112 112 112 4 5 IEAAAA EMBAAA AAAAxx
+247 993 1 3 7 7 7 47 47 247 247 14 15 NJAAAA FMBAAA HHHHxx
+579 994 1 3 9 19 9 79 179 79 579 18 19 HWAAAA GMBAAA OOOOxx
+379 995 1 3 9 19 9 79 179 379 379 18 19 POAAAA HMBAAA VVVVxx
+74 996 0 2 4 14 4 74 74 74 74 8 9 WCAAAA IMBAAA AAAAxx
+744 997 0 0 4 4 4 44 144 244 744 8 9 QCAAAA JMBAAA HHHHxx
+0 998 0 0 0 0 0 0 0 0 0 0 1 AAAAAA KMBAAA OOOOxx
+278 999 0 2 8 18 8 78 78 278 278 16 17 SKAAAA LMBAAA VVVVxx
diff --git a/src/test/regress/data/person.data b/src/test/regress/data/person.data
new file mode 100644
index 0000000..3a65b9f
--- /dev/null
+++ b/src/test/regress/data/person.data
@@ -0,0 +1,50 @@
+mike 40 (3.1,6.2)
+joe 20 (5.5,2.5)
+sally 34 (3.8,45.8)
+sandra 19 (9.345,09.6)
+alex 30 (1.352,8.2)
+sue 50 (8.34,7.375)
+denise 24 (3.78,87.90)
+sarah 88 (8.4,2.3)
+teresa 38 (7.7,1.8)
+nan 28 (6.35,0.43)
+leah 68 (0.6,3.37)
+wendy 78 (2.62,03.3)
+melissa 28 (3.089,087.23)
+joan 18 (9.4,47.04)
+mary 08 (3.7,39.20)
+jane 58 (1.34,0.44)
+liza 38 (9.76,6.90)
+jean 28 (8.561,7.3)
+jenifer 38 (6.6,23.3)
+juanita 58 (4.57,35.8)
+susan 78 (6.579,3)
+zena 98 (0.35,0)
+martie 88 (8.358,.93)
+chris 78 (9.78,2)
+pat 18 (1.19,0.6)
+zola 58 (2.56,4.3)
+louise 98 (5.0,8.7)
+edna 18 (1.53,3.5)
+bertha 88 (2.75,9.4)
+sumi 38 (1.15,0.6)
+koko 88 (1.7,5.5)
+gina 18 (9.82,7.5)
+rean 48 (8.5,5.0)
+sharon 78 (9.237,8.8)
+paula 68 (0.5,0.5)
+julie 68 (3.6,7.2)
+belinda 38 (8.9,1.7)
+karen 48 (8.73,0.0)
+carina 58 (4.27,8.8)
+diane 18 (5.912,5.3)
+esther 98 (5.36,7.6)
+trudy 88 (6.01,0.5)
+fanny 08 (1.2,0.9)
+carmen 78 (3.8,8.2)
+lita 25 (1.3,8.7)
+pamela 48 (8.21,9.3)
+sandy 38 (3.8,0.2)
+trisha 88 (1.29,2.2)
+uma 78 (9.73,6.4)
+velma 68 (8.8,8.9)
diff --git a/src/test/regress/data/real_city.data b/src/test/regress/data/real_city.data
new file mode 100644
index 0000000..79d7a6c
--- /dev/null
+++ b/src/test/regress/data/real_city.data
@@ -0,0 +1,5 @@
+0 Oakland ((-122,37.9),(-121.7,37.9),(-121.7,37.4),(-122,37.4))
+0 Oakland ((-121.7,37.4),(-121.7,37),(-122.1,37),(-122.1,37.3),(-122,37.3),(-122,37.4))
+0 Oakland ((-122.1,37.3),(-122.2,37.5),(-122,37.5))
+0 Berkeley ((-122.3,37.9),(-122,37.9),(-122,37.6),(-122.3,37.6))
+0 Lafayette ((-122.3,37.4),(-122.2,37.4),(-122.2,37),(-122.3,37))
diff --git a/src/test/regress/data/rect.data b/src/test/regress/data/rect.data
new file mode 100644
index 0000000..e32b6fe
--- /dev/null
+++ b/src/test/regress/data/rect.data
@@ -0,0 +1,3378 @@
+(12699,9028,12654,8987)
+(22689,4680,22614,4626)
+(43263,47296,43217,47217)
+(6184,8397,6182,8379)
+(863,28537,788,28456)
+(33783,4733,33746,4693)
+(40456,47134,40426,47087)
+(45950,8153,45887,8060)
+(33433,36474,33399,36460)
+(41106,22017,41086,21962)
+(19214,36781,19179,36767)
+(11582,40823,11498,40737)
+(35565,5404,35546,5360)
+(26489,17387,26405,17356)
+(30874,13849,30796,13814)
+(38255,1619,38227,1593)
+(4445,32006,4405,31914)
+(3923,32921,3876,32913)
+(36054,39464,36032,39434)
+(46540,6780,46524,6758)
+(12184,45811,12118,45787)
+(13198,17090,13143,17051)
+(30939,44578,30865,44486)
+(12502,4939,12431,4902)
+(3250,1108,3169,1063)
+(34029,41240,33976,41180)
+(47057,44018,46967,43927)
+(699,10114,686,10058)
+(5925,26020,5845,25979)
+(9462,39388,9382,39388)
+(270,32616,226,32607)
+(3959,49145,3861,49115)
+(207,40886,179,40879)
+(48480,43312,48412,43233)
+(37183,37209,37161,37110)
+(13576,13505,13521,13487)
+(5877,1037,5818,1036)
+(6777,16694,6776,16692)
+(49362,13905,49299,13845)
+(29356,14606,29313,14562)
+(5492,6976,5441,6971)
+(288,49588,204,49571)
+(36698,37213,36682,37158)
+(718,41336,645,41272)
+(8725,23369,8660,23333)
+(40115,9894,40025,9818)
+(40051,41181,40015,41153)
+(5739,1740,5715,1731)
+(25120,27935,25054,27876)
+(27475,46084,27447,46003)
+\N
+(33197,3252,33161,3245)
+(10892,15691,10869,15662)
+(39012,44712,38995,44640)
+(4506,6484,4458,6459)
+(13970,26316,13964,26236)
+(28009,28104,27968,28030)
+(5991,27613,5906,27607)
+(23649,6338,23610,6314)
+(25942,10008,25911,9928)
+(25651,29943,25590,29906)
+\N
+(24555,40334,24546,40330)
+(46870,43762,46789,43709)
+\N
+(20030,2752,19945,2687)
+(30758,26754,30718,26678)
+\N
+(4320,44673,4286,44625)
+\N
+(1011,15576,939,15574)
+(41936,40699,41854,40655)
+(20594,19002,20561,18995)
+(9388,41056,9325,41042)
+(34771,46693,34751,46645)
+(49398,46359,49332,46357)
+\N
+(23115,35380,23036,35306)
+(46305,34840,46283,34765)
+(16768,21692,16691,21647)
+(28695,3128,28654,3112)
+(22182,7107,22107,7074)
+(14567,1210,14468,1139)
+(14156,37139,14136,37119)
+(33500,38351,33477,38286)
+(39983,41981,39944,41954)
+(26773,20824,26719,20813)
+(42516,22947,42460,22932)
+(26127,10701,26044,10650)
+(17808,13803,17724,13710)
+(14913,49873,14849,49836)
+(37013,820,36955,736)
+(39071,1399,39022,1381)
+\N
+(9785,42546,9687,42540)
+(13423,14066,13354,14052)
+(3417,14558,3336,14478)
+(25212,46368,25128,46316)
+(10124,39848,10027,39820)
+(39722,39226,39656,39162)
+(6298,28101,6250,28076)
+(45852,5846,45809,5750)
+(48292,4885,48290,4841)
+(18905,4454,18894,4424)
+(18965,43474,18902,43444)
+(39843,28239,39761,28199)
+(18087,44660,18019,44632)
+(33886,10382,33794,10286)
+(38383,13163,38362,13092)
+(18861,25050,18842,24965)
+(29887,14326,29806,14274)
+(18733,11644,18698,11644)
+(5119,37952,5089,37950)
+(16191,34884,16149,34864)
+(29544,1104,29496,1062)
+(27740,41555,27701,41540)
+(4672,4087,4633,4060)
+(45441,38994,45377,38958)
+(3272,1176,3232,1146)
+(12820,26606,12790,26575)
+(30910,7590,30877,7512)
+(42476,39152,42377,39127)
+(6562,38490,6542,38447)
+(30046,20332,29988,20259)
+(40723,15950,40671,15949)
+(4945,46857,4908,46817)
+(47986,16882,47963,16877)
+(9842,22339,9805,22305)
+(29831,23169,29818,23122)
+(12322,34404,12250,34312)
+(22846,11091,22759,10992)
+(47627,2424,47603,2397)
+(18375,43632,18347,43577)
+(40441,974,40394,965)
+(34260,10573,34194,10522)
+(32914,9549,32828,9503)
+(49023,37827,48978,37799)
+(22183,10691,22111,10669)
+\N
+(38036,15828,38014,15759)
+(34604,16801,34508,16746)
+(26737,29997,26675,29976)
+(47375,40298,47293,40210)
+(771,2661,732,2649)
+(28514,25659,28504,25577)
+(13438,46494,13376,46455)
+(7187,17877,7125,17786)
+(49957,43390,49897,43384)
+(26543,20067,26482,20057)
+(16416,29803,16385,29724)
+(36353,7484,36286,7414)
+(26498,3377,26415,3358)
+(28990,32205,28936,32193)
+(45005,3842,45001,3816)
+(21672,23566,21603,23566)
+(33360,43465,33302,43429)
+\N
+(29884,9544,29838,9520)
+\N
+(5599,15012,5596,14930)
+(22396,21481,22344,21422)
+(24810,14955,24780,14887)
+(47114,18866,47081,18784)
+(39013,39245,38953,39237)
+(12863,40534,12803,40529)
+(351,37068,310,37019)
+\N
+(12916,34327,12891,34240)
+\N
+(49191,2694,49170,2628)
+(24127,38407,24050,38325)
+(3264,23053,3213,23007)
+(8172,30385,8144,30336)
+(19630,35716,19573,35640)
+(42554,5148,42521,5117)
+(42168,33453,42136,33426)
+(17732,32093,17666,32057)
+(1039,16626,1037,16587)
+(21287,7757,21265,7679)
+(47063,8260,47039,8225)
+(38645,16238,38561,16204)
+(18258,25358,18196,25341)
+(30458,1742,30458,1695)
+(35147,9273,35121,9233)
+(7670,16625,7642,16545)
+(49503,23432,49484,23383)
+(31089,23146,31062,23093)
+(47758,2734,47670,2703)
+\N
+(35276,1027,35259,972)
+(26337,17603,26313,17579)
+(35649,16777,35626,16777)
+(42454,5105,42362,5101)
+(21682,24951,21646,24920)
+\N
+(48383,25174,48303,25156)
+(14672,3532,14601,3460)
+(22570,22587,22515,22512)
+(23566,25623,23484,25573)
+(9530,24542,9504,24459)
+(41271,451,41236,401)
+(5556,37528,5502,37527)
+(12479,25042,12447,24991)
+(16568,22916,16499,22864)
+(42700,13084,42676,12992)
+\N
+(35523,40973,35504,40932)
+(32948,16962,32857,16901)
+(7808,13469,7712,13469)
+(13920,35203,13870,35131)
+(22731,31563,22658,31557)
+\N
+(22909,43956,22900,43857)
+(33077,35080,33074,35030)
+(48064,29307,48022,29280)
+(20232,46682,20212,46613)
+(29949,16790,29867,16711)
+(30260,32029,30180,31979)
+(17184,34503,17110,34482)
+(16066,42687,16039,42648)
+(2947,19819,2857,19788)
+(4900,47934,4818,47894)
+(27193,19014,27174,18976)
+\N
+(15597,27948,15590,27939)
+(11090,28623,11002,28589)
+(26956,18651,26920,18620)
+(3107,47753,3103,47711)
+(6745,24151,6711,24083)
+(43923,19213,43871,19124)
+(33451,23578,33370,23534)
+(8944,20605,8862,20601)
+(14905,7536,14892,7441)
+(2412,18357,2383,18354)
+(37060,1443,36974,1366)
+(15501,6230,15429,6190)
+\N
+(30333,50,30273,6)
+(35567,9965,35482,9912)
+(49847,7128,49798,7067)
+\N
+(27685,36396,27668,36384)
+(43832,18491,43825,18431)
+(36849,34600,36785,34589)
+(2348,47938,2307,47902)
+\N
+(20473,22131,20445,22113)
+(38486,4293,38471,4288)
+(30611,30451,30553,30400)
+(3883,21299,3819,21260)
+(7696,37555,7644,37534)
+(22399,7913,22317,7911)
+(42565,38605,42500,38598)
+(36595,12151,36500,12106)
+(587,35217,571,35123)
+(5764,15300,5764,15231)
+(12003,21265,11983,21210)
+(42564,4803,42470,4737)
+(42359,36834,42271,36746)
+(44700,14680,44658,14670)
+(19690,5627,19620,5607)
+(17780,43602,17714,43565)
+(45073,3491,45041,3434)
+(35043,2136,35017,2084)
+\N
+(39653,19215,39646,19198)
+(23970,25560,23935,25502)
+(28698,49233,28600,49223)
+(30266,3605,30245,3540)
+(25538,7857,25500,7791)
+(17711,1757,17708,1756)
+(5248,594,5190,587)
+(2730,32454,2671,32436)
+(1722,49089,1635,49067)
+(40954,5743,40921,5722)
+\N
+(21382,4426,21298,4331)
+(7885,18629,7872,18605)
+(42838,6459,42748,6451)
+(8217,19894,8207,19845)
+(20489,18524,20433,18520)
+(17383,23559,17309,23515)
+(38952,38968,38934,38913)
+(44665,18137,44636,18051)
+(22416,41220,22383,41213)
+(9901,664,9818,646)
+(23475,21981,23449,21973)
+(41875,17991,41818,17988)
+(36517,47731,36509,47713)
+(37595,49849,37581,49834)
+(38771,32720,38748,32684)
+(810,38523,736,38452)
+(29695,14942,29665,14907)
+(31911,15168,31906,15113)
+(3454,36839,3438,36831)
+(4832,47554,4820,47473)
+\N
+(11590,8292,11539,8272)
+(8193,33323,8106,33317)
+(16043,14799,16001,14710)
+(19574,11395,19514,11316)
+(26290,41424,26224,41342)
+(22844,12516,22807,12471)
+\N
+(15709,49580,15655,49553)
+(13387,28084,13379,28066)
+(2780,38807,2690,38711)
+(22031,32458,22028,32377)
+(13511,3351,13440,3297)
+(14648,26473,14614,26383)
+(17798,19885,17726,19852)
+(32355,27940,32324,27861)
+(43773,21031,43767,20985)
+(15419,45759,15403,45666)
+(770,38863,729,38806)
+(21221,35619,21183,35596)
+(38924,31021,38894,30961)
+(7395,32439,7345,32416)
+(2324,25118,2268,25074)
+(2958,15089,2935,15087)
+(2424,160,2424,81)
+(12123,18644,12099,18616)
+(7459,30276,7422,30218)
+(15847,45488,15814,45428)
+(26409,29897,26389,29863)
+(12336,34322,12279,34322)
+(9440,23550,9396,23466)
+(4991,30850,4905,30768)
+(47262,11940,47201,11939)
+(30584,42868,30555,42838)
+(23144,24089,23056,24067)
+\N
+(35930,11609,35847,11573)
+(7812,17271,7789,17203)
+(17946,37554,17878,37480)
+(27356,32869,27298,32813)
+(29971,47783,29933,47697)
+(26075,46494,25988,46451)
+(39314,41366,39289,41269)
+(31708,42900,31688,42865)
+(4510,10231,4439,10203)
+(43806,8482,43758,8446)
+(45990,49694,45927,49617)
+(48815,27640,48782,27573)
+(41675,26733,41622,26723)
+(23229,7709,23175,7693)
+(48976,17733,48962,17731)
+(10686,41470,10597,41434)
+(18053,27059,17989,27012)
+\N
+(35495,25950,35459,25912)
+(41896,45014,41881,44999)
+(22654,41896,22572,41801)
+(18581,7087,18524,6988)
+\N
+(14697,22406,14681,22311)
+(40092,28122,40043,28030)
+(35844,24243,35816,24238)
+(1254,25653,1250,25644)
+(1603,21730,1556,21640)
+(33048,21779,32991,21763)
+(29979,1632,29916,1592)
+(8620,633,8580,620)
+(22992,27035,22932,27008)
+(21409,29315,21390,29309)
+(3610,44748,3547,44699)
+(20402,9318,20343,9267)
+(31001,8709,30908,8658)
+(46840,47640,46773,47551)
+(49173,4705,49143,4630)
+(5339,31657,5251,31622)
+(8644,49668,8630,49648)
+(45387,2893,45309,2885)
+(47641,31020,47584,30941)
+(40238,10636,40208,10568)
+(19247,36924,19227,36924)
+(917,19957,827,19887)
+(40967,17841,40870,17820)
+(15850,4109,15794,4085)
+(20181,30916,20085,30870)
+(161,24465,107,24374)
+(21737,49690,21667,49663)
+(10328,20911,10232,20852)
+(24187,49823,24128,49768)
+(36084,4578,36007,4501)
+(38771,31741,38673,31674)
+(2202,30102,2111,30006)
+(27322,16074,27228,16039)
+(6843,17280,6765,17248)
+(16972,39744,16912,39700)
+(10608,38741,10553,38708)
+\N
+(4917,34801,4828,34766)
+(39281,33659,39268,33618)
+(31706,7119,31645,7063)
+(3427,44006,3422,44004)
+\N
+(10134,42608,10044,42599)
+(26294,32080,26200,32068)
+(21777,34680,21769,34606)
+(23373,25957,23314,25915)
+(10710,8401,10681,8400)
+(42062,19458,42019,19394)
+(26530,43036,26458,43004)
+(3394,46081,3360,46077)
+(38743,33953,38677,33924)
+(32438,8226,32345,8160)
+(9210,27333,9118,27301)
+(19594,1600,19568,1551)
+(10003,12278,9952,12255)
+(31737,7206,31650,7146)
+(16594,15821,16502,15759)
+(28208,30296,28189,30278)
+(30602,46237,30555,46185)
+(20715,5155,20697,5140)
+(48892,35271,48793,35210)
+(3175,5590,3113,5525)
+(34220,27947,34132,27865)
+(35105,39792,35011,39727)
+(21919,27314,21839,27286)
+\N
+(23963,3723,23917,3699)
+(16312,14078,16236,14045)
+(19233,49824,19185,49794)
+(1447,11768,1356,11699)
+(17311,17709,17224,17653)
+(11962,31709,11871,31627)
+(21355,40131,21355,40085)
+(33750,35273,33724,35180)
+(38896,25539,38879,25524)
+(39569,44899,39569,44893)
+(11075,41547,11039,41500)
+(3215,12202,3199,12127)
+(46215,33458,46132,33455)
+(15121,38012,15083,37974)
+(44448,18726,44412,18690)
+(3899,38263,3870,38262)
+(13854,13353,13786,13298)
+(8252,5402,8191,5320)
+(46849,37968,46820,37897)
+(16422,13957,16376,13897)
+(47369,7665,47353,7629)
+(11982,40874,11956,40806)
+\N
+(9552,27580,9496,27562)
+(32247,19399,32176,19337)
+(32704,2169,32635,2091)
+(7471,44213,7411,44130)
+(48433,7096,48379,7089)
+(37357,6543,37338,6452)
+(30460,29624,30433,29535)
+(20350,28794,20341,28705)
+(6326,32360,6267,32317)
+(1711,47519,1654,47430)
+(49540,16510,49521,16426)
+\N
+(26975,618,26908,579)
+(24118,30880,24020,30821)
+(3675,15477,3625,15418)
+(44953,9577,44953,9530)
+(38323,7965,38235,7910)
+(6629,36482,6579,36448)
+(33953,16460,33878,16408)
+(49222,16790,49186,16695)
+(17308,16951,17274,16904)
+(14135,6888,14077,6833)
+(38617,47768,38603,47760)
+(7345,10992,7290,10914)
+(35261,42152,35176,42096)
+(28586,4809,28544,4735)
+(37521,25299,37495,25217)
+(41941,17954,41912,17915)
+(1209,46863,1171,46863)
+(20103,34947,20048,34896)
+(32716,33816,32656,33769)
+(11113,6531,11036,6467)
+(48635,7321,48563,7262)
+(28435,37059,28349,37014)
+(12311,17208,12232,17112)
+(1466,48010,1379,48008)
+(11226,11997,11223,11925)
+(46896,32540,46821,32510)
+(32661,31255,32632,31187)
+(37739,20376,37655,20306)
+(44002,43326,43920,43257)
+(30337,1023,30271,968)
+(34436,23357,34432,23345)
+\N
+(21367,8168,21353,8091)
+(36370,21611,36369,21569)
+(4152,36488,4080,36476)
+(17696,13924,17664,13853)
+(34252,19395,34159,19316)
+(12574,3072,12573,2975)
+(3995,21243,3943,21167)
+(44553,30126,44513,30108)
+\N
+(4599,45275,4552,45254)
+(33191,11404,33176,11348)
+\N
+(14245,18633,14177,18540)
+(32457,20705,32393,20700)
+(40052,10499,40016,10457)
+(29824,44065,29785,44037)
+(31613,12565,31557,12543)
+(42692,29000,42652,28996)
+\N
+(40680,22219,40603,22140)
+\N
+(33575,27661,33488,27644)
+(46194,1385,46184,1355)
+(38442,48501,38407,48426)
+(25305,21544,25236,21523)
+(15562,8226,15561,8208)
+\N
+(20844,43614,20752,43558)
+(22566,30541,22554,30532)
+(2760,47802,2672,47789)
+(25515,30745,25433,30675)
+(48382,45134,48382,45093)
+(9940,27094,9871,27087)
+\N
+(48690,44361,48610,44338)
+(18992,11585,18899,11582)
+(21551,49983,21492,49885)
+(46778,29113,46770,29071)
+(43219,9593,43212,9548)
+(40291,1248,40224,1190)
+(12687,22225,12635,22219)
+(49372,38790,49306,38721)
+(49503,46808,49411,46798)
+(24745,5162,24732,5138)
+(5046,26517,5023,26424)
+(5583,46538,5495,46531)
+(6084,35950,6079,35895)
+(3503,23096,3437,23024)
+\N
+(45275,8420,45244,8418)
+(13514,45251,13491,45249)
+(42112,2748,42047,2668)
+\N
+(7810,21907,7806,21878)
+(48378,36029,48303,35979)
+(32568,48605,32510,48563)
+(859,18915,810,18915)
+(41963,17950,41939,17915)
+\N
+(42723,8031,42685,7955)
+\N
+(19587,5965,19556,5961)
+(8713,33083,8629,32996)
+(21243,7769,21226,7740)
+(43752,43026,43720,42944)
+(7883,41311,7859,41242)
+(10178,47874,10157,47826)
+(32177,48725,32093,48646)
+(22960,2784,22953,2774)
+(25101,49159,25087,49090)
+(32142,48915,32086,48850)
+(6636,44887,6590,44825)
+(37814,11606,37769,11578)
+(2870,23198,2820,23121)
+(21025,16364,20947,16271)
+(31341,36137,31269,36114)
+(38921,7906,38888,7831)
+(6966,17259,6922,17199)
+(32426,13344,32401,13253)
+(8084,30572,8078,30572)
+(42230,47674,42150,47603)
+(20724,44854,20724,44830)
+(27471,38453,27454,38430)
+(24590,37973,24544,37941)
+(45832,26077,45772,26031)
+(9589,24239,9582,24156)
+(37484,49472,37409,49432)
+(30044,19340,30004,19333)
+(16966,14632,16936,14572)
+(9439,40491,9403,40482)
+(28945,5814,28913,5805)
+(43788,41302,43746,41231)
+(33631,43451,33614,43354)
+(17590,49396,17510,49324)
+(15173,32572,15109,32507)
+(1912,23580,1840,23504)
+(38165,16185,38076,16154)
+(6729,1179,6637,1177)
+\N
+(6994,45406,6983,45325)
+(2912,21327,2908,21305)
+(14678,14244,14659,14222)
+(29944,14959,29898,14900)
+(47432,35658,47407,35610)
+(25542,39243,25466,39149)
+(5330,7206,5304,7165)
+(24790,27196,24695,27118)
+(38806,1961,38795,1906)
+(23290,4487,23212,4416)
+\N
+(35035,24337,34990,24297)
+(5549,38948,5549,38891)
+(24558,15492,24501,15425)
+(4636,3011,4574,2933)
+(26522,39986,26451,39940)
+(33486,18424,33410,18366)
+(36638,14324,36625,14287)
+(35115,41236,35055,41191)
+(31927,16896,31841,16806)
+(5796,43937,5697,43886)
+(25681,41645,25663,41608)
+(10962,42777,10894,42732)
+(32715,11026,32672,10991)
+(45803,20406,45710,20371)
+(34730,17672,34658,17606)
+(8809,6323,8798,6232)
+\N
+(39471,23837,39390,23749)
+\N
+(34078,17435,33987,17433)
+(9133,4544,9041,4509)
+(47274,29126,47242,29060)
+(6404,28488,6403,28475)
+(48894,49751,48846,49694)
+(17324,43023,17301,42972)
+(15599,8433,15557,8386)
+(48575,10202,48488,10175)
+(27638,24428,27608,24378)
+(45277,47456,45240,47422)
+(26482,46607,26482,46570)
+(41400,33898,41397,33802)
+\N
+(49853,18504,49848,18503)
+(11528,25165,11476,25080)
+(49902,41752,49818,41746)
+(1956,47506,1922,47424)
+(21834,22058,21802,21964)
+\N
+(19414,21842,19386,21822)
+(34801,13722,34744,13681)
+(13924,29243,13835,29160)
+(47749,21986,47664,21894)
+(47051,39582,46974,39489)
+(31287,49923,31236,49913)
+(47429,8625,47337,8585)
+(46987,44364,46901,44277)
+(16158,27510,16099,27467)
+(41184,6400,41148,6317)
+(1847,42471,1829,42426)
+\N
+(14409,48602,14320,48555)
+\N
+(38137,42951,38045,42918)
+(42875,2312,42832,2243)
+(27242,30617,27181,30535)
+(24882,44559,24812,44548)
+(22021,1596,22015,1581)
+(24300,1523,24250,1443)
+(43946,35909,43869,35868)
+(816,15988,776,15967)
+(25243,9401,25237,9332)
+(27967,25958,27928,25949)
+(6575,33949,6484,33900)
+(44812,35980,44800,35913)
+(37577,13064,37495,13019)
+\N
+(30891,29967,30814,29884)
+(15829,28836,15753,28807)
+(11128,34180,11126,34117)
+(9834,12537,9801,12508)
+(4899,29069,4809,29024)
+(29370,38459,29276,38382)
+(40743,46653,40647,46559)
+(9618,2723,9578,2631)
+(32542,26837,32515,26769)
+(5625,13409,5576,13355)
+(47490,19229,47472,19203)
+(48118,40275,48063,40203)
+(19245,20549,19227,20546)
+(25312,22243,25280,22164)
+(18797,28934,18723,28881)
+(31609,49393,31512,49366)
+(26183,32888,26135,32824)
+(46198,26153,46180,26149)
+\N
+(45383,16904,45353,16888)
+(7132,11408,7091,11338)
+(48262,43227,48236,43159)
+(31722,12861,31675,12810)
+\N
+(41695,48924,41691,48921)
+(48318,12877,48287,12802)
+(12069,32241,11978,32231)
+(8395,2694,8380,2661)
+(19552,34590,19550,34497)
+(12203,26166,12187,26143)
+(35745,9571,35654,9542)
+(22384,22535,22352,22439)
+(21459,28189,21360,28189)
+(7418,7203,7343,7182)
+(39497,48412,39413,48318)
+(1058,11132,979,11051)
+(45623,31417,45548,31381)
+\N
+(23887,31921,23876,31891)
+(7797,1244,7785,1155)
+(23679,43650,23594,43644)
+(21891,30561,21833,30485)
+(4069,6870,4019,6785)
+(5134,25117,5103,25034)
+(36101,41895,36085,41810)
+(39617,39211,39544,39191)
+(37437,6604,37434,6585)
+\N
+(7749,32601,7740,32515)
+(26203,34991,26159,34946)
+(31856,39006,31783,39003)
+(45828,24767,45788,24723)
+\N
+(49836,35965,49757,35871)
+(44113,49024,44033,48995)
+(38237,22326,38187,22253)
+(45235,19087,45190,19005)
+(1588,45285,1520,45254)
+(46628,8701,46552,8665)
+(47707,18258,47668,18250)
+(9377,26162,9325,26079)
+(28331,16766,28302,16731)
+(15792,27875,15727,27809)
+(16454,1972,16415,1967)
+(21012,15828,20972,15784)
+(27465,30603,27390,30560)
+(39256,7697,39225,7604)
+(25908,32801,25854,32770)
+(25215,40109,25201,40106)
+\N
+(23280,4613,23190,4596)
+(32440,30879,32405,30807)
+(49156,4224,49126,4126)
+(20005,40423,19911,40370)
+(20978,8226,20930,8170)
+(32127,22611,32126,22579)
+(21764,26509,21701,26455)
+\N
+(32923,2834,32914,2830)
+(7499,25331,7426,25300)
+(6163,36942,6107,36908)
+(41118,14583,41034,14486)
+(21211,33369,21208,33331)
+(7899,27682,7853,27603)
+(16546,48436,16535,48400)
+(24898,40195,24855,40174)
+(43029,982,43004,952)
+(26266,7962,26252,7950)
+\N
+(11308,44367,11210,44322)
+(8902,28402,8808,28334)
+(11671,19619,11665,19549)
+(47202,23593,47153,23505)
+(21981,40220,21905,40160)
+(46721,2514,46687,2471)
+(3450,33839,3424,33811)
+(41854,45864,41762,45792)
+(40183,47816,40114,47742)
+(26119,33910,26077,33816)
+(3430,16518,3365,16500)
+(40063,32176,40005,32166)
+(38702,15253,38679,15187)
+(17719,12291,17658,12257)
+(46131,30669,46068,30587)
+(42738,10952,42731,10907)
+(8721,45155,8650,45076)
+(45317,26123,45244,26113)
+(42694,11561,42614,11490)
+(10043,12479,10009,12391)
+(27584,2345,27578,2257)
+(30889,8253,30866,8167)
+\N
+(5176,48928,5107,48838)
+(9781,21023,9745,20976)
+(32430,27908,32404,27859)
+(3984,7391,3973,7352)
+(18904,8094,18842,8091)
+(20573,5508,20482,5496)
+(7806,44368,7753,44297)
+(18875,41452,18817,41376)
+(6632,12142,6566,12079)
+(33066,17865,33055,17854)
+(45726,19628,45714,19589)
+(26971,18459,26941,18423)
+(26554,23641,26515,23592)
+(45503,1325,45441,1231)
+(11898,20164,11880,20115)
+(27868,22837,27843,22776)
+(34931,8206,34855,8144)
+(42375,33603,42350,33539)
+(3184,8308,3129,8238)
+(26667,15813,26661,15785)
+\N
+(5760,49617,5730,49546)
+(794,27001,777,26992)
+(13518,45289,13459,45235)
+\N
+(34430,29754,34363,29736)
+(37912,24574,37880,24543)
+(8130,2270,8083,2258)
+\N
+(26930,21516,26848,21455)
+(3634,33511,3592,33489)
+(33080,5036,33035,4972)
+(48389,13942,48316,13915)
+(9231,5298,9150,5232)
+(1357,10601,1321,10548)
+\N
+(35175,15295,35091,15269)
+(33917,36863,33879,36784)
+(8279,12052,8239,12021)
+(11868,19083,11862,19034)
+(24019,30777,24006,30703)
+(44619,6959,44618,6938)
+(28610,2626,28523,2582)
+(29579,41801,29482,41775)
+(23448,37609,23396,37534)
+(40676,11252,40670,11191)
+(39656,14077,39564,13999)
+(33060,31042,33033,30950)
+(11720,6816,11654,6792)
+(13775,28873,13730,28868)
+(47851,39121,47802,39084)
+(30923,40255,30860,40199)
+(44169,15070,44085,15015)
+(42574,28664,42558,28590)
+(8993,43487,8941,43460)
+(40782,11648,40763,11631)
+(18516,10143,18423,10137)
+(39068,551,39005,491)
+\N
+(39672,12000,39575,11913)
+(18508,37761,18464,37712)
+(19083,35318,19079,35280)
+(30286,13736,30222,13672)
+(7223,9164,7132,9069)
+(20764,29286,20700,29210)
+(5733,8063,5699,8058)
+(8566,43873,8549,43797)
+(22126,27444,22062,27366)
+(15105,8717,15078,8660)
+(43987,33145,43940,33083)
+\N
+(46833,38652,46755,38612)
+(47768,27202,47681,27169)
+(22792,1183,22731,1152)
+(25650,43310,25562,43247)
+(37084,20116,37045,20057)
+(47461,32556,47423,32555)
+\N
+(41225,18124,41215,18117)
+(17623,25218,17553,25158)
+(13770,21703,13770,21700)
+(48958,35441,48870,35388)
+(2976,1808,2892,1802)
+(45118,22318,45049,22224)
+(42287,26616,42281,26560)
+(25525,6327,25468,6244)
+\N
+(40756,31634,40713,31568)
+(23105,26565,23078,26565)
+(48268,39862,48265,39827)
+(41656,26254,41567,26243)
+(28062,17920,28045,17825)
+(6443,17321,6402,17238)
+(10191,45466,10151,45447)
+(18097,39706,18043,39649)
+(37592,3244,37569,3197)
+(29809,5978,29762,5950)
+(12145,11251,12130,11202)
+(37507,42999,37446,42956)
+(10820,2866,10782,2830)
+(36440,42904,36421,42832)
+(38370,3386,38279,3311)
+(9345,17279,9313,17197)
+(20477,14864,20395,14807)
+(37147,37769,37110,37729)
+(15325,36135,15284,36053)
+(29034,32897,29009,32854)
+(2116,22274,2037,22216)
+(15078,38330,15048,38251)
+(7968,33600,7914,33573)
+(832,23851,770,23786)
+(38669,4348,38594,4344)
+(8521,48573,8425,48564)
+(1060,43320,969,43289)
+(26170,10150,26144,10069)
+(32324,8539,32285,8506)
+(13121,18044,13109,18021)
+(1597,9383,1594,9367)
+(49539,35164,49505,35065)
+(39464,10295,39409,10261)
+(8921,37898,8825,37803)
+(31171,47076,31093,47039)
+(7178,41397,7108,41304)
+(16240,34832,16162,34761)
+(2829,20119,2782,20091)
+(45854,21265,45810,21250)
+(6382,12106,6315,12030)
+(22301,46291,22291,46274)
+(34142,14181,34078,14158)
+(11258,29748,11198,29742)
+\N
+(37450,6943,37398,6882)
+(41675,27207,41643,27130)
+(13578,49562,13573,49479)
+(37132,37397,37081,37301)
+(49404,37193,49332,37170)
+(33536,31809,33444,31735)
+(45990,42751,45893,42708)
+(38852,20510,38802,20509)
+(27453,15836,27391,15802)
+(9347,29004,9284,28946)
+(44871,27727,44778,27668)
+(14978,19646,14970,19644)
+(23243,47091,23166,47080)
+(45204,21431,45167,21370)
+(14082,22316,14078,22235)
+(42778,22694,42744,22606)
+(4834,25241,4760,25196)
+(20497,18110,20494,18038)
+(45738,35524,45706,35496)
+(21575,5151,21493,5092)
+(2194,10052,2172,9960)
+\N
+(47735,24472,47682,24460)
+(46740,35700,46695,35609)
+(24647,42807,24568,42779)
+(18000,30576,17975,30506)
+(48638,46630,48544,46628)
+(48508,33600,48477,33578)
+(38703,45408,38670,45313)
+(21712,15015,21625,14956)
+(5840,42007,5768,41992)
+(44011,11138,43953,11117)
+(3899,33262,3897,33238)
+(30142,23967,30096,23927)
+(36950,13226,36908,13141)
+(13130,26915,13071,26873)
+(38576,35408,38539,35392)
+(16776,46244,16700,46176)
+(38251,25969,38168,25948)
+\N
+(3512,32256,3417,32242)
+(31923,31225,31832,31197)
+(5144,4969,5124,4937)
+(34499,46164,34430,46162)
+\N
+(39432,31907,39388,31828)
+(17316,24606,17221,24533)
+(20751,49352,20709,49323)
+(41673,30418,41623,30377)
+(29026,24400,28971,24345)
+(21929,30617,21894,30598)
+(35539,12421,35536,12355)
+(24938,45583,24870,45525)
+\N
+(27442,33090,27353,33064)
+(23949,12046,23949,12036)
+(11399,377,11360,294)
+(47099,9989,47023,9942)
+(641,33118,639,33084)
+(13687,41308,13682,41290)
+\N
+(3682,17727,3645,17660)
+(13262,19396,13185,19357)
+(18791,389,18774,366)
+(12489,45384,12403,45369)
+\N
+(12065,6364,12015,6325)
+\N
+(32705,23886,32619,23827)
+\N
+(7004,37333,6911,37240)
+(28594,38078,28530,38050)
+(5805,21797,5710,21701)
+(41145,18905,41058,18873)
+(35599,10002,35591,9956)
+(5387,39087,5326,38994)
+(11703,14003,11671,13912)
+(4093,10472,4091,10470)
+\N
+(14110,49740,14063,49695)
+(4170,470,4097,463)
+(22219,17296,22164,17221)
+(2505,20879,2446,20842)
+\N
+(47235,24744,47151,24667)
+(30035,23234,30013,23197)
+(3489,11659,3461,11607)
+(38435,46322,38429,46230)
+(12315,32880,12277,32854)
+(33350,35297,33317,35263)
+(18845,37671,18836,37589)
+(24855,23554,24783,23520)
+(48251,44461,48188,44408)
+(17695,43353,17605,43286)
+(4964,21292,4893,21270)
+(33919,29907,33852,29878)
+(29139,40010,29084,39957)
+(41611,37750,41572,37741)
+(41773,34717,41682,34700)
+(8225,7424,8221,7363)
+(1785,28248,1771,28219)
+(21553,36307,21505,36257)
+(7552,18199,7527,18119)
+\N
+(14410,30977,14349,30944)
+\N
+(20940,49142,20901,49069)
+(36892,5522,36810,5478)
+(40192,20926,40179,20926)
+(44702,15182,44641,15117)
+(43431,4921,43337,4827)
+(41129,21654,41084,21642)
+(6205,42785,6113,42722)
+(23714,10224,23666,10205)
+(9318,35175,9274,35139)
+(40698,12676,40618,12627)
+(49954,1340,49905,1294)
+(32774,33062,32763,33062)
+(4336,22183,4241,22157)
+(10241,47657,10151,47592)
+(6746,16718,6666,16634)
+(26842,49694,26839,49680)
+(34870,47437,34820,47347)
+(26365,22266,26326,22183)
+(39859,932,39829,840)
+(33995,10888,33902,10793)
+(32972,22342,32951,22340)
+\N
+(19951,10161,19932,10111)
+(26779,45188,26745,45151)
+(11235,13593,11184,13589)
+(27334,20968,27288,20953)
+(9586,43102,9488,43085)
+(43935,49759,43925,49680)
+(10548,37032,10474,36955)
+(9326,14927,9295,14848)
+(41340,11312,41311,11303)
+(6500,44553,6454,44515)
+\N
+(8198,26841,8104,26749)
+(47761,34183,47702,34140)
+(43637,17912,43577,17910)
+(17623,11138,17590,11122)
+(48122,13132,48077,13060)
+(27911,39796,27908,39777)
+(1108,7918,1080,7832)
+(18776,24329,18699,24326)
+(1171,37901,1075,37871)
+(38437,33948,38364,33907)
+(1913,11593,1817,11533)
+(22684,266,22656,181)
+(13299,17075,13241,17074)
+(6924,30196,6851,30113)
+\N
+(4367,13150,4298,13053)
+(37381,6101,37380,6046)
+(10307,28383,10270,28349)
+(12283,8636,12256,8610)
+(20230,32775,20144,32723)
+(32942,12812,32905,12714)
+(46140,7138,46140,7047)
+(37235,29436,37161,29425)
+(42486,25454,42478,25444)
+(47860,46973,47842,46961)
+(41760,21026,41662,20955)
+(29663,20088,29566,20026)
+(19167,33241,19101,33235)
+(12306,37845,12301,37803)
+(11288,873,11203,857)
+(30309,5120,30282,5060)
+(46927,19737,46856,19687)
+(16664,20052,16649,19989)
+(7330,8675,7296,8613)
+(45067,45724,44991,45631)
+(45317,10862,45218,10842)
+(15012,47009,14998,46956)
+(47882,10146,47813,10099)
+(31571,46215,31511,46148)
+(32257,2619,32187,2531)
+(38924,41305,38872,41285)
+(49981,34876,49898,34786)
+(30501,35099,30418,35011)
+\N
+(45862,41438,45854,41434)
+(38448,31878,38391,31822)
+(8278,43463,8274,43378)
+(5883,30629,5878,30564)
+(49501,40346,49447,40275)
+(31651,43116,31560,43106)
+(44244,32940,44244,32926)
+\N
+(17941,18079,17938,18035)
+(9518,32524,9470,32511)
+(30707,43469,30686,43457)
+(3284,46542,3187,46477)
+(43423,29642,43393,29602)
+(19940,16825,19877,16736)
+(26194,47446,26194,47407)
+(30386,24675,30333,24652)
+(42707,44466,42688,44456)
+\N
+(43395,18525,43320,18467)
+(28346,32259,28276,32196)
+(45106,40786,45026,40767)
+(36734,20414,36722,20363)
+(37140,11569,37099,11475)
+(8967,6409,8882,6341)
+(31036,27923,30993,27890)
+(22442,47682,22347,47663)
+(32511,24029,32482,23970)
+(22593,34444,22519,34399)
+(41534,15495,41518,15455)
+\N
+(35862,19997,35818,19928)
+(31419,8323,31404,8285)
+(31036,19023,30978,19000)
+(46900,15192,46891,15102)
+(12774,9651,12765,9604)
+(49985,6436,49927,6338)
+(7184,47344,7089,47285)
+(12792,45021,12740,45011)
+(15019,27192,14940,27096)
+(35415,23106,35381,23095)
+(42129,14283,42095,14245)
+(29375,45807,29347,45743)
+(21763,24916,21700,24889)
+(47656,8794,47579,8774)
+(6139,49571,6059,49472)
+(44492,45607,44483,45532)
+(22699,4301,22628,4240)
+(27407,24241,27335,24158)
+\N
+(38424,34460,38403,34458)
+(46572,48456,46554,48402)
+(39676,29056,39643,28981)
+(4202,33076,4107,33010)
+(32499,10592,32482,10575)
+(22504,45417,22459,45378)
+(49619,40322,49619,40268)
+(14463,9305,14426,9224)
+(10070,20300,10035,20211)
+(35060,28561,34965,28553)
+(23970,47522,23887,47428)
+(46803,19155,46790,19131)
+\N
+(46151,49848,46058,49830)
+(45266,40766,45209,40738)
+(31041,32195,31007,32110)
+(41401,17245,41334,17224)
+(37445,654,37435,602)
+(45568,31904,45508,31857)
+(29326,7923,29285,7896)
+(27078,34643,27027,34606)
+(34492,43443,34437,43345)
+(34109,4307,34083,4265)
+(2755,45325,2727,45312)
+(12571,24218,12536,24195)
+(41224,2454,41149,2445)
+(711,34828,655,34788)
+(9104,18865,9036,18850)
+(3508,26816,3456,26771)
+(20159,16212,20116,16160)
+(36871,7425,36777,7421)
+(2751,45244,2734,45222)
+(35867,28071,35769,28052)
+(46878,35730,46850,35725)
+(20610,35086,20513,35037)
+(3903,32612,3887,32517)
+(9330,40226,9289,40169)
+(6338,28242,6329,28184)
+(35668,18344,35606,18304)
+(29892,48927,29878,48879)
+(26999,646,26932,612)
+(36377,38898,36338,38847)
+(40289,31459,40236,31436)
+(30377,1164,30306,1069)
+(7642,12183,7590,12112)
+(40325,1716,40296,1662)
+(36412,38787,36318,38691)
+(3967,33268,3923,33261)
+(33914,40774,33873,40763)
+(45978,41431,45963,41332)
+(39195,12546,39120,12520)
+(29962,30878,29941,30846)
+(9365,10732,9310,10726)
+(28801,23943,28740,23885)
+(28934,38858,28928,38807)
+(22126,45897,22068,45803)
+(2923,33832,2918,33751)
+(25116,2276,25083,2272)
+(31174,14546,31144,14460)
+(11728,9072,11658,9004)
+(19804,49195,19730,49125)
+(23090,28826,23010,28787)
+(33989,27553,33947,27486)
+(39702,47613,39641,47553)
+(31397,3607,31304,3519)
+(5835,9262,5791,9226)
+(40112,37022,40038,36926)
+(12346,29356,12282,29344)
+(28503,9623,28469,9591)
+(38449,43143,38378,43066)
+(36950,37311,36905,37265)
+(34824,5729,34818,5706)
+(9288,26969,9225,26900)
+(2535,42176,2478,42159)
+(29098,49051,29085,49031)
+(44759,33326,44727,33230)
+(42849,2970,42821,2919)
+(46014,27193,45985,27151)
+(14506,13713,14417,13626)
+(19342,44905,19332,44895)
+(38178,37003,38147,36925)
+(29179,27310,29084,27288)
+(42713,10158,42671,10060)
+(43336,38389,43290,38326)
+(41260,34410,41245,34327)
+(27907,2695,27830,2596)
+(16309,44972,16222,44966)
+(6230,22262,6214,22249)
+\N
+(9266,39458,9175,39447)
+(33120,33548,33087,33538)
+(43659,11416,43599,11375)
+(49707,39258,49702,39159)
+(23520,22140,23486,22072)
+(24736,46502,24668,46412)
+(7826,16851,7730,16807)
+(39114,6048,39056,5965)
+(11859,8753,11764,8701)
+(42254,48367,42240,48328)
+(26136,49185,26056,49175)
+(38395,11209,38334,11137)
+(33249,9425,33209,9348)
+(22131,38502,22112,38460)
+(5306,24344,5267,24268)
+(30292,1198,30233,1149)
+(9903,10896,9850,10806)
+(25568,22911,25487,22868)
+(22048,43391,22043,43362)
+(20852,25827,20851,25766)
+(35204,17119,35114,17093)
+(5575,43431,5554,43410)
+(17727,13623,17678,13560)
+(14721,29520,14709,29461)
+(40317,42220,40267,42166)
+(31435,31012,31386,30931)
+(40655,10103,40645,10006)
+(35783,17802,35773,17763)
+(34874,10210,34856,10200)
+(3694,14279,3610,14239)
+(27854,5493,27799,5433)
+(34913,7234,34894,7220)
+(15758,26445,15738,26421)
+(23710,7272,23705,7270)
+\N
+(33679,13468,33628,13415)
+\N
+(31271,40495,31178,40461)
+(759,187,662,163)
+(14419,40434,14402,40381)
+(45879,42933,45814,42872)
+(167,17214,92,17184)
+(9964,12210,9958,12195)
+(35834,46257,35817,46211)
+(26077,5629,25978,5621)
+(46177,44640,46082,44544)
+(44780,28753,44707,28692)
+(35491,24729,35425,24690)
+(33914,34190,33914,34131)
+(17709,33253,17668,33227)
+(45516,11888,45423,11848)
+(24497,24752,24411,24710)
+(30333,5952,30331,5886)
+(444,12587,430,12497)
+(7592,22353,7541,22287)
+\N
+(13387,37414,13329,37318)
+\N
+(21504,35227,21449,35210)
+(18533,12909,18438,12848)
+(41049,27148,41048,27088)
+(18205,12222,18151,12140)
+(18026,5164,18026,5156)
+(34104,29862,34006,29815)
+(18520,49686,18454,49602)
+(37000,41493,36920,41424)
+(43025,25711,42986,25687)
+(38620,47018,38535,46934)
+(24119,36813,24023,36739)
+(48887,26359,48879,26302)
+(47827,14625,47810,14609)
+(10792,30746,10776,30716)
+(30384,40672,30318,40582)
+(48417,22790,48358,22746)
+(14854,5819,14785,5798)
+(19142,44414,19085,44406)
+(31179,27081,31145,27005)
+\N
+(19692,8711,19659,8642)
+(39689,14082,39603,14051)
+(11181,39091,11119,39002)
+(46015,23374,45936,23328)
+(12517,49702,12427,49690)
+(21926,21137,21841,21111)
+(31956,12509,31870,12494)
+(5895,2030,5851,2020)
+(27094,5447,27014,5377)
+(35781,8717,35780,8618)
+(14012,12023,13972,12015)
+(1702,12442,1696,12419)
+(28549,5251,28462,5248)
+(26441,21007,26360,20925)
+(49820,7990,49771,7967)
+(26424,29698,26339,29693)
+(35146,6820,35071,6817)
+\N
+(15438,18788,15435,18729)
+(47115,5235,47096,5143)
+(33982,9002,33915,8925)
+(14206,37041,14174,36955)
+(24300,36616,24232,36613)
+(44658,1788,44580,1769)
+\N
+(31539,43550,31463,43464)
+\N
+(16722,9673,16633,9652)
+(44813,20573,44733,20544)
+\N
+(42114,32559,42040,32552)
+(41561,36244,41477,36241)
+(39589,33796,39548,33716)
+(20365,26770,20329,26709)
+(28511,208,28479,114)
+(10010,25524,9930,25508)
+\N
+(1549,45666,1512,45621)
+(16193,1927,16166,1869)
+(34486,11500,34421,11401)
+(14048,37944,13994,37901)
+(21692,9594,21617,9496)
+(2568,37899,2557,37811)
+(4360,24503,4278,24443)
+(50027,49230,49951,49214)
+(44849,14867,44836,14813)
+(16695,34896,16683,34840)
+(12600,35217,12593,35129)
+(23113,24009,23030,23962)
+(49907,30225,49810,30158)
+(18026,25208,17970,25208)
+(49711,39844,49651,39790)
+(5427,42682,5357,42637)
+(23901,14221,23802,14184)
+(15470,12185,15376,12163)
+(47302,34023,47292,34001)
+(24336,17418,24315,17393)
+(13948,17043,13903,16970)
+(8555,8986,8530,8953)
+(48830,6038,48743,5986)
+(48720,40687,48623,40610)
+(21161,30970,21146,30896)
+(9507,36316,9411,36261)
+\N
+(36643,18136,36614,18106)
+(1858,7457,1851,7402)
+(24452,44306,24372,44252)
+\N
+(3292,807,3205,806)
+(6845,30694,6792,30627)
+(21333,25786,21237,25751)
+(23008,22574,22999,22511)
+(8790,8893,8772,8806)
+(43333,47968,43264,47900)
+(5377,24103,5302,24076)
+(18410,23993,18329,23907)
+(24752,19126,24713,19069)
+(49772,11378,49696,11293)
+(3468,12920,3396,12873)
+(1746,40342,1736,40333)
+(49187,29737,49139,29681)
+(27657,44952,27581,44917)
+\N
+(35407,30177,35345,30151)
+(4071,40568,4058,40544)
+(25998,30513,25965,30452)
+(8195,45403,8097,45310)
+(8276,41689,8183,41670)
+\N
+(48435,28550,48355,28455)
+\N
+(8139,25449,8136,25380)
+(20302,25574,20297,25531)
+\N
+(22055,46659,22034,46567)
+(3531,49962,3463,49934)
+(46828,46938,46739,46902)
+(42294,786,42212,739)
+(8779,3292,8761,3275)
+(48146,46170,48082,46151)
+(21571,10000,21531,9919)
+(35526,26029,35450,25945)
+(38893,22225,38865,22197)
+(22189,37520,22132,37497)
+(810,43261,751,43198)
+(10352,39144,10290,39093)
+(8740,35435,8720,35432)
+(31657,13551,31583,13484)
+(39803,4019,39755,4014)
+(46353,7853,46312,7824)
+(30078,48975,30021,48970)
+(2847,32036,2819,31966)
+(25250,10147,25165,10140)
+\N
+(15643,38953,15585,38947)
+(40792,29798,40731,29731)
+(43249,26858,43215,26835)
+(47229,2199,47201,2134)
+(10052,23601,9958,23570)
+(38981,21615,38892,21604)
+(3651,45004,3570,44917)
+(21503,8261,21409,8166)
+(13518,34201,13465,34105)
+(13899,25117,13836,25114)
+(18327,17403,18301,17349)
+(19503,13648,19483,13607)
+(3554,19487,3529,19466)
+(41102,43355,41070,43314)
+(4663,45858,4583,45765)
+(3971,3023,3931,2975)
+(37124,7061,37080,6993)
+(48530,47172,48459,47160)
+(14575,29843,14509,29750)
+(43443,23124,43357,23038)
+(8864,48290,8857,48263)
+(41597,39852,41577,39791)
+(35610,33392,35556,33353)
+(36415,17906,36328,17846)
+(24919,43933,24839,43883)
+(7457,14056,7395,14051)
+(43851,4090,43801,4080)
+(43567,18468,43471,18388)
+(16711,6084,16652,6055)
+(45888,45934,45846,45880)
+(45630,9313,45585,9248)
+(27119,25969,27094,25884)
+(36155,11420,36120,11405)
+(41880,47111,41808,47049)
+\N
+(17554,20379,17482,20374)
+(38848,5936,38763,5869)
+(28324,31019,28276,30944)
+(43257,17152,43176,17091)
+(42717,24613,42691,24527)
+(16786,41486,16763,41403)
+(19259,28780,19160,28711)
+(25843,28265,25760,28171)
+(48645,34816,48546,34755)
+(7004,49289,6976,49236)
+(30261,21833,30181,21776)
+(5290,46672,5219,46661)
+(21237,31901,21188,31849)
+(23340,38537,23253,38472)
+(17269,3682,17183,3586)
+\N
+(48200,15377,48110,15369)
+(16546,22195,16477,22142)
+(21436,8460,21378,8449)
+\N
+(46598,17235,46577,17138)
+\N
+(30212,36184,30152,36092)
+(18037,155,17941,109)
+(4945,29201,4933,29184)
+(32835,18782,32770,18750)
+(34160,33104,34120,33007)
+(5151,26989,5149,26909)
+(1801,15549,1710,15461)
+(48988,34819,48951,34764)
+(20904,32547,20856,32497)
+\N
+(32654,35183,32606,35144)
+(14336,11763,14328,11712)
+(30546,23808,30463,23773)
+(6813,21006,6781,20924)
+\N
+(14199,22030,14185,21934)
+(3783,14709,3747,14658)
+(49428,47052,49422,46973)
+(29551,27682,29470,27654)
+(29170,37260,29151,37181)
+(48924,24689,48894,24680)
+(48497,34052,48453,33966)
+\N
+(21263,8203,21242,8176)
+(46537,3797,46462,3735)
+(18406,14579,18393,14563)
+\N
+(11583,16529,11536,16471)
+(10564,46257,10478,46228)
+(49769,34513,49761,34458)
+\N
+(9202,6482,9138,6391)
+(40387,37411,40357,37360)
+(11966,11802,11888,11751)
+(15551,47438,15486,47406)
+(12017,43288,11969,43230)
+(9717,22574,9701,22495)
+\N
+(35083,49443,35075,49355)
+(33857,9320,33813,9269)
+(32106,10581,32012,10560)
+(14345,12485,14273,12424)
+(24187,46416,24175,46402)
+(43854,42159,43808,42129)
+(35399,40707,35359,40646)
+(29585,25576,29493,25556)
+(24919,7829,24911,7753)
+\N
+(17049,48390,17022,48304)
+(25224,35012,25217,34922)
+(47397,20853,47346,20779)
+(17221,16558,17181,16516)
+(8669,16491,8645,16486)
+(23502,44241,23484,44164)
+(36169,37046,36072,37010)
+(44775,32394,44763,32357)
+(30685,36871,30662,36792)
+(21783,47642,21714,47630)
+(34847,27467,34761,27372)
+(43925,49912,43888,49878)
+(16455,27861,16364,27813)
+(38406,18310,38329,18309)
+(5408,9461,5319,9426)
+(41856,36900,41784,36854)
+(23723,4460,23646,4448)
+(18454,40138,18430,40046)
+(17505,36822,17418,36763)
+(36686,33534,36641,33476)
+(11347,9454,11289,9436)
+\N
+(27816,34752,27745,34736)
+(44213,8559,44162,8461)
+(45359,26789,45315,26776)
+(31249,19475,31224,19421)
+(25917,44239,25819,44149)
+(47313,40691,47264,40685)
+(40577,33848,40513,33794)
+(9606,45253,9582,45174)
+(30005,24521,29910,24496)
+(49332,35375,49309,35299)
+\N
+(12164,33871,12075,33820)
+(19598,43327,19593,43314)
+\N
+(3818,28584,3815,28504)
+\N
+(35579,8611,35541,8604)
+(8811,20986,8750,20954)
+(16139,44777,16128,44686)
+(35550,41501,35534,41458)
+(43180,11927,43109,11891)
+(45798,8465,45711,8460)
+(18196,6886,18126,6845)
+(1774,32167,1701,32073)
+(7030,40790,7029,40711)
+(11676,23009,11665,22915)
+(33990,22561,33953,22474)
+\N
+(30366,9447,30284,9353)
+(37626,32913,37596,32853)
+(7730,42561,7665,42470)
+(49347,8403,49315,8387)
+(6874,3499,6812,3458)
+(44189,16999,44169,16964)
+(6312,30167,6231,30083)
+(18932,6611,18909,6518)
+(32262,13076,32223,13057)
+(45989,249,45910,222)
+(42710,855,42692,796)
+(25562,9849,25535,9802)
+(13348,46719,13260,46689)
+(30022,42196,30005,42160)
+\N
+(22263,45954,22243,45950)
+(18918,18890,18820,18795)
+(31918,12003,31852,11989)
+(12252,39453,12211,39398)
+(40208,9789,40194,9759)
+(35943,21767,35914,21693)
+(18439,10706,18383,10618)
+(2803,18999,2778,18925)
+(14953,27444,14875,27397)
+(12587,22025,12545,21928)
+(33930,21090,33918,21009)
+(10444,2606,10407,2553)
+(28700,29782,28665,29703)
+\N
+(1402,13497,1397,13465)
+\N
+(24155,3075,24083,3062)
+(38378,1864,38339,1849)
+(29261,49910,29247,49818)
+(38139,37073,38098,37057)
+\N
+(24468,41130,24418,41053)
+(9989,1015,9959,939)
+(47001,33561,46994,33518)
+(47058,16030,46983,16012)
+(35509,1814,35426,1748)
+(3630,48019,3597,47923)
+(47781,12986,47741,12947)
+(16364,9908,16356,9882)
+(17290,41508,17287,41410)
+(42423,26477,42349,26434)
+(10039,920,9952,833)
+(16851,21338,16846,21314)
+\N
+(23104,7700,23062,7688)
+(5619,2079,5611,2075)
+(31471,49632,31375,49549)
+(25793,12526,25783,12456)
+(3935,29528,3866,29513)
+\N
+(5957,1646,5947,1595)
+(2467,22376,2429,22349)
+(43715,32673,43664,32595)
+(6726,13093,6636,12994)
+(31477,18347,31421,18299)
+(34232,36635,34200,36552)
+(49061,14516,49008,14442)
+(43996,6129,43955,6074)
+(7728,33802,7670,33703)
+\N
+(6131,36766,6053,36749)
+(35791,16361,35696,16329)
+(45759,8935,45675,8886)
+(43634,2029,43537,1940)
+(4916,32233,4844,32181)
+(46701,23508,46623,23477)
+(29590,4893,29552,4871)
+(38647,4423,38574,4396)
+(7593,25845,7497,25751)
+(8510,43552,8432,43492)
+(18791,39181,18730,39162)
+(7462,2956,7454,2858)
+(1394,26795,1392,26780)
+(16707,21993,16609,21932)
+(26838,10866,26803,10836)
+(31642,29842,31585,29760)
+(21891,3502,21863,3406)
+(13258,587,13250,507)
+(6072,47397,6021,47369)
+(16605,49730,16579,49659)
+(42830,40981,42791,40981)
+(12975,3706,12913,3637)
+(30925,21660,30826,21649)
+(1455,14229,1410,14156)
+\N
+(17583,16486,17562,16474)
+(33377,3387,33333,3381)
+(784,6177,750,6095)
+(22111,44110,22106,44013)
+(1444,403,1346,344)
+(4010,46220,3982,46212)
+(17932,8150,17861,8127)
+(38685,31466,38636,31416)
+(14257,11549,14242,11522)
+(14990,15217,14904,15211)
+(21395,21533,21307,21520)
+\N
+(31948,33725,31885,33694)
+(433,49033,390,48961)
+(45205,609,45173,523)
+(25065,35494,25003,35455)
+(33265,6677,33224,6611)
+(18179,22345,18133,22256)
+(3916,13759,3820,13732)
+(1696,13478,1604,13436)
+(47203,25980,47130,25907)
+(24913,13361,24868,13268)
+(13824,40177,13792,40130)
+(25671,13555,25585,13494)
+\N
+(20133,37769,20105,37679)
+\N
+(26368,16734,26288,16726)
+(30545,35438,30458,35376)
+(48816,22926,48812,22831)
+(48807,31389,48739,31330)
+(11003,10859,10950,10765)
+(17288,8570,17247,8485)
+(38377,31415,38331,31379)
+\N
+(19085,23425,19059,23326)
+(40059,17068,40052,17006)
+(18811,13493,18734,13394)
+(36319,17197,36225,17181)
+(14939,38780,14863,38714)
+(49539,17656,49479,17629)
+(42530,45951,42466,45854)
+(27318,26654,27233,26610)
+(49980,35004,49937,34963)
+(18326,32558,18322,32502)
+(45951,28555,45896,28481)
+(12104,33531,12014,33501)
+(22311,41113,22215,41066)
+(25073,18721,25047,18656)
+\N
+(14524,13486,14510,13390)
+(40040,36688,40000,36599)
+(21594,11473,21563,11436)
+(44031,22274,43938,22187)
+(729,30683,668,30601)
+(14114,20873,14102,20803)
+(28239,41377,28222,41308)
+(26404,11922,26317,11843)
+(41660,34586,41585,34501)
+\N
+(21128,2384,21101,2368)
+(30209,16952,30156,16858)
+(39078,24963,39045,24898)
+(5598,1348,5499,1294)
+\N
+(38474,7436,38450,7364)
+(15117,45734,15024,45693)
+\N
+(23909,39853,23888,39780)
+(24292,30183,24282,30148)
+(48871,17661,48868,17637)
+(918,18752,847,18708)
+\N
+(43615,16162,43606,16104)
+(33763,47410,33751,47409)
+(4798,6485,4773,6388)
+\N
+(18524,41539,18433,41518)
+(47745,42449,47651,42364)
+(38936,21237,38864,21204)
+\N
+(5251,3516,5194,3475)
+(22269,36269,22183,36228)
+(18736,40983,18685,40947)
+(38393,15444,38356,15363)
+(38134,29898,38103,29862)
+(37789,39557,37732,39474)
+(31906,23005,31838,23003)
+(10647,40094,10560,40040)
+(9914,41547,9867,41545)
+(44221,443,44125,433)
+(41479,10936,41381,10847)
+(42586,6301,42563,6235)
+(2504,17588,2449,17554)
+(7045,18782,7028,18764)
+(41840,32018,41768,31938)
+(38416,17158,38330,17060)
+\N
+(8605,39015,8605,38933)
+(5764,43548,5719,43496)
+\N
+(20789,29902,20696,29843)
+\N
+(36104,47896,36079,47816)
+(31736,13834,31722,13832)
+(32617,19701,32597,19684)
+(1671,18997,1622,18945)
+(36007,26545,36005,26535)
+(31864,17494,31820,17455)
+(27346,28388,27303,28289)
+(8191,9653,8133,9589)
+(7501,21616,7405,21536)
+(35450,9580,35368,9563)
+(29281,37276,29247,37255)
+(6225,17192,6200,17135)
+\N
+(43689,8119,43670,8028)
+(41917,49601,41835,49563)
+(44295,13116,44205,13078)
+(22721,44772,22667,44748)
+(32640,11107,32636,11050)
+(20639,28851,20613,28839)
+\N
+(32479,10159,32446,10061)
+(27251,16978,27196,16959)
+(41401,33148,41339,33074)
+\N
+(49001,8538,48989,8444)
+(37958,35843,37874,35802)
+(46969,41229,46903,41138)
+(18541,8876,18541,8870)
+(4080,31634,4061,31627)
+(8097,35240,8040,35152)
+(18470,21414,18463,21412)
+(20914,17897,20838,17869)
+(42688,11681,42666,11641)
+(47525,25005,47443,24907)
+(32439,14438,32397,14400)
+\N
+(39667,19626,39622,19542)
+(1212,44525,1169,44516)
+(29766,4433,29668,4401)
+(25847,49657,25813,49605)
+(33859,17356,33827,17263)
+(28989,45953,28904,45854)
+(37211,30830,37113,30819)
+\N
+(45220,26382,45219,26340)
+(12312,43250,12234,43246)
+(37775,41504,37762,41421)
+(45889,33499,45822,33411)
+(49461,22601,49369,22553)
+(39857,33844,39816,33824)
+(46102,15822,46030,15778)
+(46605,31239,46598,31170)
+(23925,5856,23862,5808)
+(15459,4262,15407,4241)
+(12019,4907,12015,4818)
+(38258,17973,38229,17923)
+(40575,29566,40477,29521)
+\N
+(29715,45919,29697,45891)
+(11694,9510,11670,9490)
+(7053,44257,7012,44231)
+(16465,8603,16391,8505)
+(29170,15592,29098,15527)
+(20400,37354,20345,37328)
+(5281,10265,5252,10184)
+(6084,48782,6058,48727)
+(11006,6889,10971,6796)
+(16299,19461,16286,19411)
+(13718,29192,13642,29106)
+(3999,2965,3963,2903)
+(18509,12235,18430,12208)
+(49542,38575,49537,38534)
+(15093,41715,15071,41634)
+(6802,8385,6714,8300)
+(15127,17507,15097,17424)
+(36921,3025,36835,2995)
+(32117,24327,32101,24262)
+(27244,24151,27165,24104)
+(36339,42360,36313,42358)
+(47288,46252,47245,46184)
+(37867,6649,37818,6565)
+(14886,22103,14865,22089)
+(39611,17952,39513,17951)
+(37329,31436,37298,31436)
+(5715,39115,5698,39099)
+(13266,7364,13203,7296)
+(16076,10945,16006,10942)
+(7197,41509,7126,41413)
+(14411,40868,14330,40772)
+(12872,33481,12862,33454)
+(17786,19616,17758,19560)
+(1052,37358,996,37311)
+(42825,12643,42762,12625)
+(20007,49858,19921,49778)
+(27155,6355,27072,6257)
+(14117,40208,14022,40155)
+(47280,34069,47279,34028)
+(17551,15803,17482,15763)
+(1725,6673,1676,6649)
+(43984,31128,43961,31105)
+(43772,47042,43731,47038)
+(46901,47317,46817,47228)
+(19877,14179,19837,14168)
+(20691,19989,20675,19935)
+(4011,18914,3963,18817)
+(1023,23378,933,23317)
+(30051,46118,29966,46039)
+(43499,46488,43496,46409)
+\N
+(43531,2412,43447,2396)
+\N
+(16034,32285,15976,32220)
+(12817,21365,12740,21298)
+(7607,47293,7585,47293)
+(32512,12218,32463,12170)
+(1848,21496,1839,21439)
+(17567,23073,17478,23046)
+(35813,31847,35807,31792)
+\N
+(563,30859,540,30842)
+(13145,15488,13063,15433)
+(36754,37479,36731,37411)
+(1125,26069,1057,25997)
+(4539,20676,4519,20618)
+(8476,34721,8409,34681)
+(7794,25691,7727,25656)
+(23842,514,23800,473)
+(47678,41396,47668,41365)
+(6837,25974,6799,25892)
+(13355,11174,13304,11161)
+\N
+(37243,25548,37158,25471)
+(12528,30208,12441,30205)
+(14929,1672,14886,1607)
+(27263,49026,27263,49010)
+(15892,21645,15835,21642)
+(29446,48978,29360,48967)
+(41304,9892,41211,9825)
+(37418,49393,37338,49296)
+(41146,32178,41120,32165)
+(28738,13326,28722,13266)
+(14899,36595,14873,36559)
+(1973,31435,1921,31426)
+(19485,17742,19421,17661)
+(33072,20995,32980,20903)
+(47091,30055,47080,30037)
+(45753,12998,45686,12992)
+\N
+(11528,7826,11509,7794)
+(21104,13921,21060,13836)
+(16768,15491,16747,15470)
+(13279,20396,13249,20326)
+(4342,49518,4339,49446)
+(20413,15476,20349,15447)
+(45532,5649,45484,5627)
+(18647,27196,18619,27115)
+(1326,17473,1261,17400)
+(47646,19644,47588,19609)
+(35088,1813,35080,1732)
+(38461,34839,38410,34838)
+(34358,11540,34285,11506)
+\N
+(26969,7078,26953,6989)
+(12629,40352,12617,40264)
+(33800,7037,33731,6992)
+(24462,13518,24392,13486)
+(33164,47357,33096,47329)
+(15422,18451,15413,18376)
+(19643,12916,19567,12912)
+(40860,42125,40770,42050)
+(49103,29614,49039,29606)
+(36319,35582,36222,35528)
+(8924,36083,8873,36018)
+(49603,44022,49505,44021)
+(7783,40633,7702,40618)
+(25388,49107,25346,49042)
+(28375,38947,28306,38919)
+(47324,22672,47321,22660)
+(2287,8808,2266,8719)
+(44343,16339,44248,16318)
+(2374,28839,2336,28798)
+(22913,40710,22819,40688)
+\N
+(47747,684,47658,627)
+(16043,46011,16021,45984)
+(34958,32168,34903,32092)
+(4840,49328,4752,49258)
+(24341,2087,24330,2009)
+(18378,19374,18327,19358)
+(48165,7217,48156,7141)
+(14232,6044,14182,6004)
+(23080,4196,22983,4191)
+(259,1850,175,1820)
+(270,29508,264,29440)
+(45088,11375,45050,11295)
+(29666,39386,29656,39302)
+(8712,8782,8660,8713)
+(15900,6650,15855,6561)
+(28946,28348,28917,28347)
+(32544,25845,32538,25779)
+(44047,6957,43951,6942)
+(36465,588,36382,503)
+\N
+(28167,26679,28150,26673)
+(16065,4268,15975,4180)
+(12950,23494,12893,23494)
+(30145,24679,30056,24654)
+(3027,16162,3001,16071)
+(8259,34537,8202,34484)
+(41447,1515,41427,1454)
+(18407,28362,18309,28303)
+(21393,41872,21328,41816)
+(46040,26497,45996,26408)
+\N
+(49944,25163,49902,25153)
+\N
+(16195,11843,16159,11831)
+(44257,15270,44254,15214)
+(49760,4791,49699,4713)
+(22558,33709,22519,33681)
+(28375,10003,28336,9938)
+(18179,24310,18106,24256)
+(707,30688,664,30669)
+(5851,26118,5822,26037)
+(4266,1292,4221,1217)
+(16516,11331,16432,11248)
+(32374,38277,32313,38245)
+(21939,8015,21927,7952)
+(34322,32051,34242,32003)
+(6262,35977,6260,35953)
+(16717,38594,16622,38498)
+(14564,3433,14535,3425)
+(21078,1000,20994,974)
+(28584,956,28575,868)
+(5538,9962,5465,9870)
+(34183,44102,34175,44085)
+\N
+(42507,10289,42441,10288)
+(12671,19936,12594,19920)
+(24835,12179,24770,12173)
+(15664,11538,15598,11494)
+(28892,24446,28821,24350)
+(41654,26720,41570,26632)
+(36583,387,36503,357)
+(10842,34824,10795,34788)
+(11518,42588,11429,42565)
+(12577,40322,12486,40266)
+(2453,4045,2439,3956)
+(31837,33705,31803,33681)
+(24403,27711,24383,27705)
+(4431,2748,4337,2656)
+\N
+(3036,2887,3014,2826)
+(37664,16118,37615,16022)
+(8606,18063,8587,18038)
+(24738,25458,24656,25362)
+(45756,34022,45671,33948)
+(34079,15236,33981,15171)
+(9251,22488,9228,22470)
+(25136,2809,25126,2717)
+(5548,47695,5543,47685)
+(13765,40800,13707,40754)
+(25216,30678,25144,30677)
+(22441,17169,22392,17106)
+(1091,4770,1054,4734)
+(36311,50073,36258,49987)
+(22461,33163,22457,33128)
+(35873,28907,35845,28867)
+(42907,15848,42904,15785)
+(6549,24897,6540,24861)
+(21928,37764,21891,37681)
+(21237,41132,21139,41086)
+(12207,24266,12173,24235)
+(40643,49770,40574,49687)
+(32833,35686,32815,35674)
+\N
+(14545,18143,14541,18098)
+(33892,42783,33884,42707)
+(33933,8381,33921,8369)
+(12450,19044,12403,19002)
+(10176,45158,10088,45145)
+(35828,12080,35732,12022)
+(28102,13694,28061,13666)
+(49432,31744,49340,31711)
+(16192,37743,16162,37697)
+(46830,867,46756,790)
+(9200,28048,9159,27986)
+(13397,19369,13340,19288)
+(30879,43562,30785,43545)
+(21995,48224,21920,48143)
+\N
+(11871,47569,11809,47568)
+(29366,22196,29280,22154)
+(26243,28176,26203,28116)
+(28995,35031,28906,35014)
+(29384,39276,29352,39183)
+(8497,13798,8471,13789)
+(7412,27226,7334,27220)
+(25403,47678,25363,47654)
+(11599,5556,11574,5502)
+(44056,5123,44008,5111)
+(49603,30877,49579,30840)
+(32261,45876,32206,45865)
+(35104,41659,35048,41587)
+\N
+(5457,35844,5376,35782)
+(29423,3977,29354,3959)
+(18059,3001,17965,2961)
+(8509,5691,8463,5620)
+\N
+(27118,5762,27083,5747)
+(2991,48605,2939,48559)
+(44482,3484,44425,3459)
+(45143,16439,45046,16365)
+(2236,37531,2147,37530)
+(41561,3217,41490,3210)
+\N
+(6270,27200,6171,27166)
+(49195,24871,49138,24798)
+\N
+(46985,38881,46897,38845)
+(37486,23522,37404,23441)
+(26907,14490,26900,14391)
+(30829,16111,30756,16056)
+(3644,17291,3587,17262)
+(20508,49775,20472,49680)
+\N
+(43279,8972,43198,8936)
+(33744,7470,33734,7439)
+(46303,20538,46284,20498)
+(10365,48246,10291,48154)
+(12636,24987,12545,24933)
+(40998,46992,40989,46916)
+(30536,6073,30531,6018)
+(22102,9643,22051,9594)
+(18616,34348,18530,34332)
+(8222,8907,8123,8848)
+(45698,28860,45698,28770)
+(26958,1748,26924,1726)
+\N
+(26735,35073,26659,35025)
+(48370,40813,48293,40737)
+(13140,993,13108,934)
+(10588,22893,10528,22883)
+(23645,40789,23567,40698)
+(49548,12374,49546,12329)
+(41135,39626,41100,39602)
+(41374,10856,41328,10769)
+(12234,5765,12146,5674)
+(12832,46941,12764,46917)
+(47886,34532,47851,34500)
+(23777,10549,23735,10495)
+(1291,16913,1194,16873)
+\N
+(29239,30554,29202,30500)
+\N
+(36485,30007,36454,29924)
+(7067,11320,7045,11229)
+(16939,30482,16904,30462)
+(27423,34386,27379,34303)
+(35170,32021,35155,31979)
+(42570,36477,42474,36457)
+(19695,679,19682,594)
+(47537,39450,47446,39450)
+(19410,22942,19375,22922)
+(34216,40166,34152,40158)
+(37000,24351,36972,24299)
+(24989,1681,24954,1672)
+(54,38679,3,38602)
+(41461,40693,41411,40599)
+(7576,46054,7545,45963)
+(35505,28262,35413,28222)
+(1158,16976,1145,16927)
+(23494,42291,23437,42229)
+(32894,32519,32880,32485)
+(604,13413,509,13401)
+(18396,19712,18355,19646)
+\N
+(26657,28234,26597,28191)
+(24240,47211,24154,47191)
+(41778,10741,41766,10730)
+(44022,43776,44010,43677)
+(35967,30055,35906,29969)
+(28878,18042,28806,18027)
+(31507,27302,31428,27267)
+(13267,21935,13265,21872)
+(122,46832,64,46762)
+(10348,45916,10306,45844)
+(22962,12644,22927,12607)
+(6320,22290,6284,22247)
+(2297,11372,2216,11298)
+(29366,36660,29325,36654)
+(13962,39307,13921,39220)
+(11094,19151,11092,19143)
+(32289,23776,32258,23760)
+\N
+(36044,17356,35956,17273)
+(46304,38692,46232,38675)
+(10934,42999,10922,42909)
+(4271,21177,4207,21093)
+(7837,19926,7747,19905)
+(25537,36605,25477,36584)
+(22161,14999,22079,14962)
+(5127,31243,5074,31213)
+\N
+(14904,40664,14838,40593)
+(29308,8480,29268,8438)
+(17731,7410,17699,7352)
+(44840,29293,44797,29248)
+(15523,31519,15505,31485)
+(34429,38479,34421,38478)
+(3530,23456,3440,23390)
+(4699,6889,4603,6796)
+(47405,48524,47389,48514)
+\N
+(23357,43160,23305,43156)
+(16923,1995,16860,1937)
+(47592,33853,47537,33758)
+(31624,37490,31595,37473)
+(42321,13380,42303,13337)
+(3088,16094,3079,16060)
+(22884,2955,22856,2857)
+(17784,23073,17724,23044)
+(32638,45577,32553,45512)
+(13876,44091,13801,44000)
+(27844,24384,27758,24330)
+(28178,10225,28155,10167)
+(39910,14277,39857,14241)
+(30372,19524,30301,19514)
+(38732,43151,38724,43151)
+(32628,2068,32547,2068)
+(13950,28652,13932,28566)
+(38996,41070,38919,40993)
+(31759,45246,31676,45215)
+\N
+(5424,34145,5382,34106)
+(14727,45600,14699,45547)
+\N
+(31429,21537,31414,21499)
+(14740,3420,14650,3323)
+(21793,39498,21743,39471)
+(18102,25924,18037,25868)
+(33299,683,33213,594)
+(45882,48765,45809,48721)
+(49215,4098,49180,4067)
+(49698,33743,49614,33663)
+(21532,5215,21514,5151)
+(24840,26877,24826,26808)
+(32680,28433,32631,28364)
+(20661,27511,20584,27414)
+(28048,30385,28009,30315)
+(45403,42533,45389,42464)
+(46531,36947,46531,36850)
+(36943,32817,36865,32737)
+\N
+(37984,43763,37888,43748)
+(20593,10650,20557,10610)
+(5387,40595,5326,40585)
+(34412,10600,34352,10539)
+(7237,47546,7206,47451)
+(39931,26644,39915,26598)
+(29843,4734,29800,4669)
+(37503,8867,37406,8821)
+(2583,2373,2570,2294)
+(29275,46433,29256,46350)
+(3332,45620,3287,45581)
+(22472,39287,22472,39257)
+(36786,18907,36708,18884)
+(45503,28576,45482,28494)
+(33262,28386,33163,28365)
+(3606,49757,3538,49697)
+(2082,49380,1991,49281)
+(12065,3734,11983,3663)
+(15606,9048,15596,9028)
+(14687,19309,14637,19263)
+(4568,15461,4499,15428)
+\N
+(43938,7429,43923,7391)
+\N
+(2168,50012,2108,49914)
+(16022,8934,15963,8928)
+(24567,39147,24561,39102)
+\N
+(42781,14149,42765,14088)
+(39501,21084,39468,21078)
+(6697,29628,6693,29584)
+(11441,16164,11364,16125)
+(39946,1920,39868,1844)
+\N
+(18138,45512,18111,45438)
+\N
+(20799,41217,20718,41138)
+(30264,16697,30240,16639)
+\N
+(30746,50040,30727,49992)
+(37429,43273,37423,43205)
+(22854,28863,22789,28810)
+(11380,48298,11287,48242)
+(16471,37273,16439,37223)
+(32737,39842,32661,39811)
+(30959,3447,30949,3357)
+(36396,13263,36348,13187)
+(29607,14625,29531,14619)
+(7851,43399,7824,43334)
+(38515,14575,38496,14492)
+(29125,3289,29086,3264)
+(6866,10476,6839,10424)
+(318,31489,235,31404)
+(1140,7007,1113,6945)
+(36574,9291,36484,9275)
+\N
+(40320,40937,40246,40866)
+(588,25849,552,25801)
+(6728,42539,6645,42507)
+(12180,6185,12123,6123)
+(32913,44123,32899,44037)
+(25464,16803,25441,16749)
+(23711,5829,23695,5750)
+(31424,34930,31377,34906)
+(42171,8298,42124,8222)
+(451,31104,375,31083)
+(39996,3278,39943,3260)
+(25816,40396,25735,40362)
+(34471,28587,34399,28547)
+(45344,21540,45297,21496)
+(27269,16787,27246,16763)
+(18070,4469,18022,4423)
+\N
+(12668,16367,12645,16295)
+(13823,17276,13730,17251)
+(20555,45544,20511,45498)
+(35893,42189,35861,42177)
+(37081,45730,37076,45705)
+(17270,15651,17201,15552)
+(48690,46034,48667,45945)
+(456,16088,368,16023)
+(48707,12416,48670,12363)
+(29692,11509,29614,11483)
+(7005,3668,6981,3574)
+(12162,389,12103,309)
+(12371,24983,12366,24964)
+(6886,48414,6868,48327)
+(10653,26234,10624,26142)
+(8526,48205,8517,48117)
+(10521,31892,10480,31798)
+(43353,1086,43281,1071)
+(21007,35650,20998,35649)
+(2343,4396,2310,4320)
+(29379,12895,29284,12891)
+(27662,17407,27570,17313)
+(9845,29346,9807,29321)
+(43855,38669,43790,38599)
+\N
+(20461,44189,20397,44158)
+(11627,17368,11581,17289)
+(2971,38855,2938,38807)
+(43204,47082,43128,47018)
+(9930,46902,9909,46871)
+(30561,48461,30536,48365)
+(44059,7591,44038,7563)
+(46260,16898,46162,16886)
+(27491,2891,27396,2814)
+(36512,26034,36455,25941)
+(31193,20022,31100,19942)
+(17057,13643,16960,13621)
+(26897,3399,26844,3318)
+(1760,5504,1683,5431)
+(29347,5511,29346,5450)
+(38761,42083,38688,41999)
+(11226,4089,11165,4068)
+(46427,42983,46361,42970)
+(12958,30737,12912,30712)
+(44432,46521,44333,46443)
+(16124,2948,16113,2852)
+\N
+(24704,25422,24635,25340)
+(30833,46152,30790,46122)
+(4487,37006,4473,36968)
+(41047,23376,41036,23327)
+(16312,49392,16298,49330)
+(30081,14687,30042,14660)
+(11160,13954,11103,13938)
+(33207,23246,33143,23168)
+(14872,7635,14860,7585)
+(20139,23987,20059,23955)
+(10946,49757,10923,49746)
+(39438,36158,39426,36134)
+(35502,2385,35464,2327)
+(17073,42173,16987,42130)
+(6079,17258,6068,17195)
+(40458,15752,40364,15728)
+(23340,7879,23313,7806)
+\N
+(31819,15096,31762,15059)
+(31159,40864,31158,40780)
+(26975,32144,26915,32113)
+(34530,10378,34440,10298)
+(18855,49577,18780,49528)
+(16787,16625,16723,16586)
+(32330,26538,32314,26458)
+(34270,28674,34265,28595)
+(10022,16026,10006,15962)
+(23143,1479,23095,1469)
+(33676,4483,33583,4408)
+(31066,22074,31059,22035)
+(21603,47121,21563,47082)
+(30051,4244,30021,4157)
+(30634,39478,30615,39446)
+(34404,48724,34393,48724)
+(31103,21414,31039,21380)
+(22945,47397,22849,47313)
+(18133,32025,18073,31941)
+(4053,25759,3977,25667)
+(39185,39091,39102,39068)
+(43287,7407,43225,7314)
+(13137,31188,13112,31182)
+(46264,1438,46258,1389)
+(22804,43892,22769,43822)
+(7542,1044,7487,983)
+(33022,8321,32925,8267)
+(384,39161,286,39073)
+(28205,24401,28142,24382)
+(31708,39086,31696,39026)
+(36626,15708,36560,15690)
+(17099,16924,17079,16924)
+(10817,6989,10747,6955)
+(24338,19293,24291,19277)
+(27566,17576,27544,17545)
+(23041,38384,22970,38320)
+\N
+(12786,8485,12702,8435)
+(13876,49473,13813,49448)
+(31585,46998,31490,46929)
+\N
+(30227,8768,30206,8715)
+(32062,39306,32023,39292)
+(25003,35753,24921,35687)
+(3281,6758,3232,6704)
+\N
+(11395,30299,11376,30220)
+(5088,15275,5007,15203)
+(31100,39538,31003,39444)
+(2741,17877,2726,17793)
+(42897,48620,42860,48537)
+(4230,15778,4181,15776)
+(17835,27530,17815,27431)
+(34189,10933,34135,10921)
+(7537,39974,7494,39973)
+(21554,3507,21528,3476)
+(9350,32326,9273,32275)
+(16455,8874,16420,8793)
+\N
+(7346,34235,7330,34224)
+(16417,48134,16352,48066)
+\N
+(41916,4971,41849,4886)
+(15856,1522,15807,1521)
+(41549,40218,41494,40144)
+\N
+(9978,16226,9972,16181)
+(14856,13312,14808,13283)
+(38490,41641,38428,41583)
+(25828,7438,25807,7378)
+(21876,30633,21796,30587)
+(1908,14279,1825,14247)
+\N
+(32207,10251,32121,10184)
+(370,9493,328,9441)
+(42072,17634,41974,17600)
+\N
+(47298,9910,47235,9846)
+(17856,11266,17782,11225)
+(35009,21400,34956,21396)
+(18337,11145,18335,11133)
+\N
+(25425,9139,25381,9085)
+(35642,27783,35621,27782)
+(3629,33164,3575,33163)
+(17151,41255,17115,41204)
+(17417,5835,17402,5751)
+(33407,14226,33329,14141)
+(1930,29955,1889,29931)
+(41101,10942,41065,10844)
+(36333,27288,36281,27233)
+(21423,36868,21367,36825)
+(36385,19566,36341,19510)
+(27073,38301,27066,38232)
+(43989,34187,43984,34174)
+(48366,7488,48316,7483)
+(37497,36075,37415,36043)
+(46917,9891,46887,9870)
+(37179,657,37103,634)
+(3877,44736,3811,44684)
+(30556,2975,30547,2962)
+(7629,11447,7547,11416)
+(45687,48147,45591,48088)
+(5635,7184,5571,7146)
+(9611,47327,9541,47246)
+(7119,48224,7117,48152)
+(15233,26480,15138,26430)
+(37468,1526,37466,1513)
+\N
+(20855,2786,20828,2711)
+(30538,44084,30480,44061)
+(42231,41527,42149,41454)
+(14963,13239,14952,13146)
+(26819,43996,26745,43934)
+(42172,35953,42086,35928)
+(28785,12611,28710,12534)
+(14089,1704,14047,1629)
+(4343,26242,4341,26169)
+(20327,42244,20231,42212)
+(33671,12700,33666,12630)
+(42144,32642,42128,32569)
+(26590,19483,26503,19442)
+(21741,46259,21723,46226)
+(8822,34700,8760,34693)
+\N
+(2710,33521,2675,33505)
+(26067,19998,26026,19989)
+(12244,34509,12202,34489)
+\N
+(47162,598,47119,499)
+(33093,49382,33068,49359)
+(35170,26340,35153,26264)
+(22552,35785,22490,35735)
+(36791,23032,36781,22976)
+(22857,10857,22833,10797)
+\N
+(47207,37405,47138,37365)
+(21867,2836,21854,2811)
+(3387,31487,3311,31456)
+(47174,48121,47167,48101)
+(24415,22232,24366,22224)
+(7970,29251,7959,29211)
+(18635,31294,18539,31221)
+(8403,13380,8370,13372)
+(738,18097,737,18054)
+(37238,19195,37218,19114)
+(582,47934,570,47897)
+(12359,4635,12350,4619)
+(43272,2013,43195,1958)
+(47568,27149,47521,27088)
+(24695,12827,24661,12796)
+(26259,14077,26168,14019)
+\N
+(48478,36135,48425,36092)
+(5230,39250,5206,39174)
+(3488,18562,3423,18489)
+(39502,16331,39460,16275)
+(18296,1478,18233,1471)
+\N
+(28627,12430,28559,12410)
+(25257,21981,25206,21954)
+\N
+(2410,41192,2325,41142)
+(43681,9631,43587,9538)
+\N
+(15086,45309,15064,45270)
+(13824,40807,13759,40787)
+(7090,2207,7062,2159)
+(3685,2480,3630,2391)
+(14810,38335,14801,38275)
+(26668,38018,26581,38012)
+(45562,1517,45506,1424)
+(11001,32481,10962,32402)
+(27743,25245,27673,25161)
+(15952,10598,15948,10535)
+(12705,13308,12694,13232)
+(31992,21195,31975,21118)
+(25834,16652,25745,16626)
+(21022,43625,20990,43576)
+(45094,27254,45000,27240)
+(9688,42601,9643,42533)
+(17746,24659,17694,24616)
+(1509,38859,1503,38809)
+(2067,20438,2041,20369)
+(7885,44528,7839,44444)
+(27432,33052,27422,32987)
+(26577,17157,26563,17142)
+(10815,35985,10734,35908)
+(44891,24067,44794,23979)
+(48626,1900,48595,1850)
+\N
+(40659,35541,40659,35489)
+(22231,26628,22210,26579)
+(37408,23016,37375,22919)
+(5920,15916,5906,15895)
+\N
+(33125,9952,33037,9880)
+(12142,29705,12141,29670)
+(3672,20995,3649,20899)
+(39147,31967,39101,31907)
+\N
+(33812,48458,33748,48399)
+(25038,14639,24978,14586)
+(3859,16010,3857,15994)
+(31926,39496,31889,39417)
+(49300,28064,49297,28026)
+(24121,38305,24048,38256)
+(9252,4205,9155,4149)
+(36124,30451,36056,30395)
+(28809,49557,28794,49533)
+(30500,44504,30471,44476)
+(26866,42395,26822,42332)
+(48195,1784,48101,1734)
+(46201,14109,46112,14097)
+\N
+(2415,9975,2354,9914)
+(30485,9581,30415,9558)
+(6385,36838,6305,36838)
+(2799,11189,2723,11095)
+(21998,20503,21923,20406)
+(29151,10714,29090,10671)
+(28850,29276,28757,29207)
+(43386,48845,43305,48834)
+(25173,8310,25101,8294)
+(34244,32352,34204,32342)
+(35595,23728,35533,23672)
+(1122,13581,1119,13538)
+\N
+(388,21716,296,21678)
+(48782,11064,48701,11005)
+(40293,12997,40213,12927)
+\N
+(28194,46428,28113,46414)
+(4791,18118,4708,18105)
+(471,29808,448,29775)
+(3536,37803,3447,37737)
+(1336,28416,1275,28392)
+(16484,48478,16422,48454)
+(25846,19320,25811,19296)
+(48669,27703,48575,27615)
+(24032,44217,24029,44127)
+(12236,5019,12233,4986)
+(1179,29838,1113,29778)
+(33893,22049,33867,21955)
+(16718,19462,16700,19440)
+(17992,49438,17894,49433)
+(35163,39941,35081,39885)
+(33897,8362,33853,8328)
+(2480,6640,2456,6599)
+(28011,19729,27937,19679)
+(15819,41516,15809,41440)
+(29818,9136,29747,9089)
+(28551,37016,28529,36941)
+(36406,26879,36374,26872)
+(16821,48925,16758,48914)
+(23692,48163,23595,48160)
+\N
+(4803,10619,4759,10522)
+(46600,33581,46553,33518)
+(41349,11767,41310,11710)
+(20856,29642,20799,29562)
+(16559,46161,16504,46131)
+(23041,1300,23003,1287)
+(16630,44902,16554,44853)
+(43065,14299,43013,14274)
+(24818,22397,24796,22348)
+(22282,24949,22218,24921)
+(36668,28538,36631,28456)
+(8080,1220,8018,1146)
+(47282,34302,47277,34269)
+(35603,33558,35557,33495)
+(44764,32189,44700,32175)
+\N
+(46488,23965,46449,23868)
+(46314,15047,46216,15013)
+(6348,25381,6286,25363)
+(3871,49288,3819,49251)
+(462,38894,398,38867)
+(23196,29214,23136,29169)
+(29024,9775,29016,9759)
+(42016,18555,41934,18472)
+(8772,45981,8692,45973)
+(11028,1351,10986,1278)
+(26684,21668,26641,21656)
+\N
+(37262,26005,37260,25947)
+(14899,44069,14814,44066)
+\N
+(39635,18701,39587,18698)
+(28528,22948,28457,22857)
+(7755,36528,7681,36454)
+(32461,1172,32427,1106)
+\N
+(18775,27359,18736,27329)
+(15379,20031,15337,19934)
+(45888,33592,45881,33544)
+(44013,24694,43962,24645)
+\N
+(43347,10699,43343,10699)
+(49999,27218,49908,27176)
+(13698,17326,13630,17317)
+(34850,44313,34775,44302)
+(38076,49235,37983,49214)
+(35570,40218,35500,40136)
+(40062,28973,40032,28878)
+(3567,39847,3523,39781)
+(498,2442,480,2401)
+(29660,43620,29577,43561)
+(10946,47356,10878,47351)
+(8073,44233,8005,44144)
+(9720,13473,9710,13462)
+(3643,38014,3598,37932)
+(16887,1408,16810,1375)
+(7559,27914,7508,27874)
+(30356,18573,30275,18569)
+(12193,48176,12130,48116)
+(11884,7756,11819,7731)
+(18293,33272,18227,33234)
+(46697,47874,46696,47828)
+(35788,32517,35760,32446)
+(33877,36987,33821,36958)
+(31253,22819,31184,22808)
+(7744,23115,7729,23103)
+(21291,39817,21219,39778)
+(13877,43379,13861,43290)
+(42955,1406,42876,1382)
+(49232,15950,49210,15880)
+(48419,32001,48326,31902)
+(18940,43246,18860,43150)
+(32317,38240,32310,38201)
+(11307,48298,11304,48222)
+(38015,18190,38000,18176)
+(27821,1177,27818,1131)
+(18935,26757,18865,26682)
+(42659,48284,42562,48244)
+(30185,23350,30146,23291)
+\N
+(16496,11970,16441,11919)
+(162,26040,120,25963)
+(24238,47784,24185,47746)
+(32326,8612,32274,8568)
+(26141,13423,26051,13407)
+(40132,22815,40089,22812)
+(21151,48794,21056,48740)
+\N
+(22044,28358,22031,28334)
+(6680,14746,6605,14669)
+(40686,25139,40632,25070)
+(22823,27549,22816,27507)
+(2513,22841,2427,22811)
+(36316,27787,36218,27728)
+(554,35489,540,35441)
+(536,30674,534,30609)
+\N
+(25385,38468,25295,38416)
+(19467,47386,19437,47317)
+(22425,38591,22387,38536)
+(32493,17321,32396,17298)
+(40115,47315,40109,47235)
+(25002,2107,24963,2104)
+(3901,9790,3898,9706)
+\N
+(40316,1721,40315,1658)
+(40089,3454,40074,3443)
+(793,17897,761,17897)
+(6490,43552,6434,43522)
+(10825,487,10820,405)
+(47703,36067,47641,36011)
+\N
+(4480,11671,4468,11653)
+(37713,10642,37711,10615)
+(12315,5302,12273,5203)
+\N
+(8709,6617,8647,6557)
+(24467,30535,24455,30494)
+(40440,32757,40369,32668)
+(49449,42447,49426,42428)
+(44867,11197,44792,11137)
+(39173,33241,39143,33187)
+(43836,2212,43803,2184)
+(23819,47613,23739,47575)
+(20583,2134,20485,2042)
+(48922,6169,48889,6111)
+(5230,44613,5131,44604)
+(37060,8051,37032,7975)
+(19148,36711,19112,36704)
+(36305,4216,36243,4118)
+(6329,39089,6302,39047)
+(36703,26367,36623,26307)
+(44753,19721,44701,19631)
+(42094,43310,42094,43285)
+(4276,22377,4241,22352)
+(30329,18906,30327,18815)
+(21970,19605,21871,19590)
+(23722,41924,23709,41861)
+(30965,39775,30908,39692)
+(32394,37895,32351,37890)
+(23968,42162,23873,42095)
+(1776,2621,1732,2548)
+(24951,47758,24900,47679)
+(32917,35771,32847,35753)
+(5428,27773,5343,27769)
+\N
+(19650,142,19630,51)
+(39769,17276,39743,17229)
+(5171,24562,5119,24470)
+(32976,35249,32917,35199)
+\N
+(4174,24603,4099,24504)
+(38565,36960,38535,36926)
+(39084,4328,39031,4301)
+(32153,38043,32070,37990)
+(38085,30640,38041,30603)
+(14269,18426,14185,18422)
+(42941,30850,42892,30788)
+(32403,25999,32339,25960)
+(16906,191,16816,139)
+(3456,48722,3418,48721)
+(3050,18287,3022,18243)
+(6331,8439,6234,8364)
+(5331,20797,5319,20793)
+(39225,37408,39216,37348)
+(34510,19838,34488,19810)
+(45789,33873,45770,33786)
+(369,1457,278,1409)
+(16531,43785,16482,43729)
+(11974,14789,11973,14730)
+(23128,6811,23094,6798)
+(43962,33659,43944,33599)
+(20967,3115,20947,3079)
+(39257,38606,39241,38595)
+(22431,8246,22381,8235)
+(26007,14672,25996,14593)
+(24762,4261,24675,4261)
+(35402,32077,35343,31988)
+(5141,16476,5139,16393)
+(16439,17564,16344,17472)
+(36983,46663,36903,46567)
+(35170,14144,35162,14048)
+(22290,7841,22283,7810)
+(22414,38398,22404,38319)
+(9011,18177,8932,18150)
+\N
+(154,4019,138,3990)
+(20447,4998,20383,4970)
+(38867,35757,38795,35659)
+(32322,15845,32227,15804)
+\N
+(29889,12142,29852,12055)
+(36235,36918,36217,36897)
+(41620,6581,41568,6581)
+(24758,38504,24731,38483)
+(42524,12904,42473,12895)
+(17954,49975,17865,49915)
+(1938,39019,1927,39013)
+(4864,33279,4817,33258)
+(45373,41967,45313,41885)
+(28786,19028,28782,18978)
+(41913,44950,41911,44908)
+(33408,14698,33392,14681)
+(27602,3460,27576,3419)
+(3336,3728,3334,3715)
+(9099,910,9080,813)
+(34141,6403,34071,6367)
+(48270,17216,48252,17130)
+(2549,16546,2461,16474)
+(27802,33669,27735,33642)
+(48419,1682,48323,1583)
+(5094,41211,5002,41123)
+(11192,6217,11190,6146)
+(6979,18503,6959,18421)
+(41210,48187,41140,48143)
+(15303,29527,15273,29441)
+(12326,45572,12267,45570)
+(29293,5861,29212,5826)
+(23847,37241,23761,37178)
+(44656,23926,44653,23831)
+(30043,16194,29977,16105)
+(902,9358,879,9339)
+(23850,46501,23834,46494)
+(42333,13300,42287,13246)
+(25226,18086,25169,18005)
+(40252,12082,40183,12038)
+(49275,18076,49216,18055)
+(8255,28878,8238,28862)
+(11325,41286,11320,41235)
+(16948,18588,16926,18528)
+(31394,1099,31374,1038)
+(30705,35772,30637,35766)
+(3858,39131,3771,39125)
+(17565,24892,17515,24808)
+(9221,49715,9216,49661)
+(44945,25769,44875,25722)
+(33408,13563,33310,13527)
+(48505,4407,48408,4373)
+(21859,37217,21763,37217)
+(39393,14422,39335,14364)
+\N
+(19905,1154,19841,1098)
+(25946,10388,25906,10366)
+(10104,13748,10027,13746)
+(5822,24629,5820,24599)
+(38194,11287,38127,11252)
+(15694,46757,15625,46716)
+(326,18837,285,18817)
+(49611,47078,49533,47052)
+(48233,18850,48150,18842)
+\N
+(29239,9962,29208,9875)
+(40062,44554,39973,44460)
+(19135,20729,19059,20643)
+(31969,40664,31896,40643)
+\N
+(3725,9191,3711,9095)
+(44280,40158,44264,40108)
+(37236,42756,37160,42694)
+(27958,19055,27888,18959)
+(45270,17661,45187,17601)
+(12115,39546,12061,39525)
+(10227,32295,10168,32231)
+(39264,31123,39226,31085)
+(6566,40000,6532,39904)
+(30058,6975,30012,6903)
+(49631,6909,49597,6823)
+(42168,10926,42134,10905)
+(44892,30042,44858,29970)
+(19540,19803,19495,19788)
+(18403,25454,18371,25404)
+(22929,26795,22841,26722)
+(16648,30213,16626,30174)
+(3440,7495,3429,7468)
+(30708,49028,30643,48998)
+(26258,14164,26255,14151)
+(44206,31653,44121,31637)
+(1510,15179,1426,15130)
+(6986,30496,6887,30416)
+(7192,43403,7138,43339)
+(39921,22071,39866,21976)
+(45870,17011,45796,16919)
+(15939,9563,15917,9539)
+(23728,24737,23691,24725)
+(6444,40416,6363,40375)
+(21899,23861,21857,23765)
+(20610,36765,20533,36742)
+(46520,33082,46433,32983)
+(21406,20902,21311,20895)
+\N
+(37913,42300,37814,42269)
+(18216,8177,18161,8173)
+(32967,8258,32899,8244)
+(14978,40230,14971,40149)
+(30343,39152,30266,39101)
+(25917,5835,25843,5806)
+\N
+(5169,45366,5141,45314)
+\N
+(16221,20898,16209,20875)
+(13151,19869,13145,19811)
+(44399,2801,44337,2713)
+\N
+(10959,48311,10957,48230)
+(4794,11711,4732,11661)
+(764,10149,762,10091)
+(15985,46067,15898,46028)
+(41434,22870,41342,22867)
+(43769,23796,43743,23756)
+(10017,18440,9919,18384)
+(21141,43119,21097,43112)
+(7782,13424,7694,13398)
+(25088,36224,25059,36150)
+(46325,48722,46241,48631)
+\N
+(11042,33125,11011,33071)
+(22347,13460,22290,13375)
+(3508,20538,3483,20536)
+(5331,42945,5272,42875)
+\N
+(2368,15537,2339,15503)
+(45314,31830,45254,31817)
+(34358,2649,34319,2589)
+\N
+(17576,30407,17572,30323)
+(29836,41324,29746,41287)
+(21036,39996,21014,39899)
+(26886,6460,26787,6400)
+(15709,5625,15627,5558)
+(37415,15979,37414,15911)
+(47761,16860,47728,16813)
+(35814,48252,35755,48173)
+\N
+(28559,20810,28496,20715)
+(12034,11921,12002,11905)
+(1818,27450,1805,27406)
+(33810,45499,33806,45413)
+(17376,18175,17323,18138)
+(34106,28135,34049,28106)
+(44947,23165,44919,23091)
+(37670,41904,37616,41840)
+(12614,15027,12555,14969)
+(43301,75,43227,43)
+\N
+(27526,15096,27450,15088)
+(26947,33409,26853,33333)
+(1537,43572,1471,43499)
+\N
+(21607,35452,21605,35375)
+(24869,46565,24818,46531)
+(4774,30335,4723,30257)
+(11615,18316,11579,18310)
+(18444,15819,18354,15763)
+(47267,22574,47203,22518)
+(22287,49538,22203,49511)
+(43010,16270,43010,16202)
+\N
+(1623,8350,1578,8254)
+(21220,43808,21137,43748)
+(40397,16471,40358,16434)
+\N
+(34839,1377,34744,1327)
+(17096,5730,17090,5637)
+\N
+(28156,37782,28155,37723)
+(3672,5686,3586,5638)
+(21856,48656,21840,48638)
+(6907,7791,6892,7761)
+(17952,21370,17862,21350)
+(37793,13461,37784,13381)
+(14740,49655,14709,49604)
+(21690,6337,21593,6289)
+\N
+(10423,33548,10364,33498)
+\N
+(39187,23274,39136,23197)
+\N
+(21882,37247,21835,37167)
+\N
+(11343,16957,11281,16914)
+(38279,43400,38264,43352)
+(23167,30271,23086,30224)
+(46278,6037,46180,5964)
+(28626,31165,28605,31095)
+\N
+(31018,367,30946,333)
+(23541,12541,23530,12523)
+(49741,14535,49691,14511)
+(31444,12702,31425,12612)
+\N
+(22406,26536,22316,26534)
+(6807,9761,6758,9723)
+(15698,1941,15687,1848)
+(49310,4625,49295,4584)
+(21345,18939,21269,18887)
+(31433,30493,31411,30439)
+(44980,12400,44950,12372)
+(25054,13949,24984,13949)
+(40538,7253,40483,7212)
+(16967,8627,16936,8604)
+(26872,3646,26804,3594)
+(24575,42883,24530,42883)
+(11823,5755,11771,5721)
+\N
+(2553,46189,2513,46174)
+(24993,14552,24898,14470)
+(28453,1719,28419,1665)
+(8925,22603,8878,22589)
+(47635,15380,47546,15378)
+(35378,18112,35324,18058)
+(27347,22264,27293,22200)
+\N
+(44323,29044,44273,28958)
+(41538,38324,41484,38290)
+(19128,49932,19112,49849)
+(17904,12548,17867,12503)
+(35103,14426,35092,14336)
+(29807,10142,29714,10052)
+(44507,22903,44462,22847)
+(11419,13324,11399,13251)
+(8573,42221,8562,42123)
+(46798,45843,46765,45765)
+(12028,31783,11967,31749)
+(10635,45300,10604,45251)
+(9626,8248,9587,8194)
+(18290,741,18246,732)
+(39949,44672,39932,44641)
+(7897,11692,7893,11637)
+(20165,42246,20112,42168)
+(4341,48390,4285,48338)
+(30126,28913,30088,28869)
+(40565,1733,40472,1721)
+(9981,30147,9915,30133)
+(47292,25511,47217,25462)
+(20137,24489,20104,24392)
+(2385,28283,2381,28189)
+(20429,10052,20357,10009)
+(8395,38568,8348,38480)
+(17381,36112,17349,36038)
+(37845,30953,37759,30926)
+(27452,12732,27411,12652)
+(38196,32186,38114,32116)
+\N
+(6527,49356,6508,49315)
+(43891,29789,43856,29723)
+(6146,37192,6085,37107)
+\N
+(42012,28897,41939,28808)
+\N
+(14909,13815,14846,13757)
+(11120,24095,11035,24049)
+(3132,41545,3053,41526)
+(40084,40315,39994,40261)
+(39671,17445,39576,17361)
+(47135,35853,47085,35831)
+(39297,1941,39290,1911)
+(47143,35898,47072,35880)
+(16017,6711,15989,6686)
+(47110,30305,47087,30213)
+(38102,27639,38091,27602)
+(17954,22544,17863,22453)
+(39891,11791,39815,11739)
+(13996,20290,13922,20278)
+(22284,23143,22190,23081)
+(25345,24019,25313,24017)
+(47134,44803,47055,44761)
+(41360,16573,41326,16503)
+(10464,1071,10457,998)
+\N
+(23515,47517,23451,47499)
+(9308,8452,9238,8392)
+(28695,5657,28671,5644)
+(45104,9913,45077,9871)
+(337,455,240,359)
+(11562,45479,11472,45428)
+(11952,18466,11931,18425)
+\N
+(35789,5154,35775,5128)
+(19024,18299,18979,18230)
+(43056,38113,42975,38067)
+(10075,26847,10064,26806)
+(3065,8107,3029,8038)
+(24766,19059,24749,18985)
+(14438,24805,14413,24708)
+(9523,3058,9485,2998)
+(24516,31262,24478,31204)
+(49513,26044,49434,26035)
+(14110,38528,14103,38461)
+(31679,35618,31619,35618)
+(10029,20258,10008,20248)
+(39269,37586,39233,37539)
+(12343,8197,12247,8113)
+(11155,44223,11111,44134)
+(25437,20606,25338,20534)
+(46604,16156,46570,16131)
+(4636,14004,4592,13941)
+(15975,29628,15912,29556)
+(49887,24274,49805,24184)
+(11812,13440,11723,13418)
+(21589,38179,21531,38085)
+(32255,44463,32219,44454)
+(15023,12698,14989,12687)
+(28906,48630,28818,48568)
+(28886,38905,28861,38832)
+(34786,22285,34740,22240)
+\N
+(46513,46780,46425,46780)
+\N
+(26626,31759,26551,31677)
+(19792,25967,19763,25933)
+(20432,14394,20388,14365)
+(27092,7301,27052,7278)
+(22283,987,22198,928)
+(6197,24363,6112,24311)
+(46601,49259,46551,49231)
+(12392,48052,12363,48038)
+(46116,31386,46067,31356)
+(7354,16855,7289,16778)
+(47501,42808,47495,42761)
+(16461,25487,16391,25398)
+(42678,18798,42678,18756)
+(9466,18207,9419,18185)
+(17467,14177,17416,14097)
+(28533,31886,28487,31832)
+(13225,38472,13188,38395)
+(5180,40970,5173,40902)
+(83,10271,15,10265)
+(2111,6784,2016,6690)
+(41835,11064,41798,10995)
+(29273,48585,29181,48536)
+(29066,21615,28985,21543)
+(19805,44143,19727,44128)
+(48919,21468,48875,21467)
+(28790,34287,28721,34251)
+(10911,33074,10869,32989)
+(6111,16519,6032,16489)
+(43889,33838,43837,33768)
+(32323,21685,32304,21644)
+(9552,27819,9539,27753)
+(38266,49852,38233,49844)
+(37672,48362,37663,48277)
+(32550,47029,32529,46931)
+(46307,6620,46272,6616)
+(23192,46608,23105,46566)
+(30399,48330,30335,48239)
+(36268,25058,36235,24984)
+(19181,8120,19089,8098)
+(24376,19983,24294,19925)
+(18297,18375,18202,18292)
+\N
+(31608,6215,31575,6168)
+(12788,49510,12784,49468)
+(46071,13013,46035,12991)
+(27647,8218,27582,8201)
+(49580,11076,49537,11050)
+\N
+(35501,33782,35501,33687)
+(19969,3148,19964,3082)
+(37728,49153,37726,49152)
+(5322,48440,5321,48435)
+(48003,10096,47904,10005)
+(39361,22318,39348,22236)
+(30488,7456,30437,7430)
+(18533,39476,18481,39394)
+(39462,23701,39433,23604)
+(26701,18300,26686,18235)
+(17405,35577,17387,35517)
+(33971,29928,33953,29919)
+(6328,10241,6276,10217)
+(32459,44259,32453,44217)
+(1715,42385,1647,42357)
+(48113,6960,48103,6872)
+(30561,4255,30476,4240)
+(38907,43619,38827,43553)
+(29149,20773,29070,20698)
+(17006,1543,16970,1497)
+\N
+(11737,18808,11714,18788)
+(13019,30534,13005,30481)
+(39224,31729,39191,31683)
+(4942,41680,4907,41596)
+(12287,37187,12188,37172)
+(30758,29579,30725,29531)
+\N
+(16604,17963,16581,17912)
+(19459,15888,19409,15812)
+(34696,24783,34600,24725)
+(21621,14159,21558,14110)
+(12193,46149,12145,46096)
+(37781,4715,37692,4635)
+(41854,44125,41807,44040)
+(23604,23585,23571,23533)
+(7853,36967,7797,36908)
+(2755,13279,2720,13206)
+(4314,15424,4283,15383)
+(29584,12685,29493,12594)
+(25138,33726,25042,33691)
+(38393,10270,38326,10185)
+(4247,12615,4225,12567)
+(36100,33156,36100,33107)
+(20024,40796,20016,40708)
+(3927,44892,3914,44843)
+(10317,43168,10226,43096)
+(22057,3419,22042,3334)
+(37097,21814,37025,21811)
+(32084,21564,31996,21491)
+(34079,39921,34058,39911)
+(23078,47459,23018,47373)
+(38109,616,38082,568)
+(11862,40382,11764,40292)
+(33403,33320,33389,33289)
+(36639,24829,36623,24829)
+(12995,45080,12992,45040)
+(16545,19981,16532,19891)
+(26155,10659,26154,10634)
+(24423,255,24360,213)
+(823,22487,781,22442)
+(12823,20064,12735,20040)
+(19688,11710,19681,11654)
+(2892,20452,2836,20424)
+(15533,10807,15464,10711)
+(46994,41143,46955,41082)
+(18155,2421,18069,2392)
+(2628,12688,2605,12602)
+(35128,8396,35044,8365)
+(44765,49615,44758,49524)
+(11226,44529,11178,44515)
+(31334,32463,31291,32456)
+(43224,23387,43168,23364)
+(30882,10414,30798,10395)
+(29139,967,29139,923)
+(29959,45244,29877,45223)
+(19946,217,19941,118)
+(49732,22033,49642,22012)
+(32914,15360,32879,15290)
+(47825,21097,47747,21030)
+(10788,5131,10746,5086)
+\N
+(15497,9698,15481,9678)
+(10617,47195,10601,47117)
+(42392,10583,42340,10550)
+(10753,33520,10669,33509)
+(5553,21580,5521,21527)
+(36840,12336,36817,12320)
+(49785,12554,49702,12553)
+(17737,38349,17639,38277)
+(48000,7823,47956,7814)
+(5019,3184,4931,3160)
+(30120,3524,30063,3492)
+(37044,2016,37001,1942)
+(23496,38566,23469,38528)
+(17255,48957,17200,48903)
+(27815,2138,27808,2090)
+(40440,11129,40368,11105)
+(35305,21772,35272,21717)
+(41308,45065,41229,44973)
+(14893,28807,14817,28789)
+(30776,45824,30731,45772)
+(742,40724,652,40672)
+(5985,41133,5927,41097)
+(9576,10226,9540,10218)
+(21407,23207,21323,23160)
+(44880,34228,44877,34169)
+(29146,49694,29143,49682)
+(28502,34886,28471,34832)
+\N
+(30662,5584,30604,5528)
+(12612,26081,12552,26001)
+(17166,49308,17098,49270)
+(9586,14116,9488,14104)
+(37323,47576,37264,47482)
+(48009,49713,48004,49614)
+(49308,23780,49297,23760)
+(8667,32342,8592,32294)
+(37826,48560,37822,48485)
+\N
+(24493,18653,24486,18616)
+(17914,3850,17887,3775)
+(34270,43873,34231,43826)
+(7753,44715,7660,44651)
+(44328,36364,44265,36350)
+(10146,3030,10111,2975)
+(35273,40106,35269,40062)
+\N
+(38566,43846,38547,43760)
+(12400,41394,12377,41378)
+(45196,38286,45153,38250)
+(48511,14972,48428,14883)
+(25939,36328,25886,36277)
+(38997,11007,38979,10917)
+(30342,518,30244,453)
+(6876,7468,6867,7454)
+(17566,27575,17566,27480)
+(18869,28538,18858,28475)
+(16825,33309,16726,33255)
+(14585,26111,14490,26035)
+(28743,49392,28664,49349)
+(26652,23359,26618,23297)
+\N
+(40129,33653,40102,33584)
+(41074,26393,41038,26389)
+(3869,33564,3869,33536)
+(28455,14205,28364,14163)
+(13866,45603,13770,45543)
+(21666,30586,21578,30544)
+(29978,11931,29893,11868)
+(1594,1043,1517,971)
+(948,1201,907,1156)
+(27547,13692,27545,13677)
+(13661,38184,13566,38154)
+(2389,40026,2317,39938)
+(35481,46379,35481,46320)
+\N
+(26917,45698,26864,45689)
+(23933,41617,23909,41539)
+(8912,8471,8862,8401)
+(9625,4747,9558,4692)
+(34743,35056,34721,34969)
+(39544,21762,39475,21717)
+\N
+(11741,26330,11656,26293)
+(39015,1315,38966,1285)
+(13418,44237,13326,44202)
+(2107,17672,2093,17616)
+(42448,28844,42370,28764)
+(49843,5175,49808,5145)
+(6536,23000,6467,22958)
+(11114,5822,11027,5739)
+(48457,11074,48384,11024)
+(12343,23110,12310,23074)
+(17300,24847,17276,24825)
+(8823,8253,8793,8238)
+(3449,171,3354,108)
+\N
+(21650,23955,21605,23883)
+(13260,3234,13193,3214)
+(25361,10896,25305,10806)
+(25051,25042,25011,25001)
+(25044,25088,25015,25005)
+\N
+(25007,25061,25002,25013)
+(25066,25105,25003,25007)
+(25028,25012,25015,25011)
+(25031,25057,25006,25018)
+(25015,25042,25004,25012)
+(25091,25049,25019,25019)
+(25023,25011,25000,25004)
+\N
+(25053,25104,25010,25012)
+(25058,25001,25018,25000)
+(25059,25051,25008,25016)
+(25043,25069,25007,25004)
+(25006,25101,25002,25002)
+(25095,25012,25014,25007)
+(25054,25052,25019,25013)
+(25108,25077,25009,25018)
+(25007,25023,25003,25002)
+\N
+(25076,25098,25002,25016)
+(25030,25077,25012,25006)
diff --git a/src/test/regress/data/streets.data b/src/test/regress/data/streets.data
new file mode 100644
index 0000000..935b045
--- /dev/null
+++ b/src/test/regress/data/streets.data
@@ -0,0 +1,5124 @@
+A St [(-122.0265,37.049),(-122.0271,37.045)]
+A St [(-122.089,37.71),(-122.0886,37.711)]
+A St [(-122.0985,37.671),(-122.0981,37.674)]
+A St [(-122.0991,37.668),(-122.0988,37.669)]
+A St [(-122.103419,37.667),(-122.103439,37.667)]
+A St [(-122.103913,37.66632),(-122.104037,37.66611),(-122.104051,37.66609)]
+A St [(-122.106469,37.66446),(-122.1067,37.664)]
+A St [(-122.107,37.664),(-122.107101,37.66425),(-122.1074,37.665)]
+A St [(-122.1172,37.659),(-122.119506,37.65661)]
+Abbie St [(-121.867486,37.54243),(-121.868,37.545)]
+Acacia Ave [(-122.2353,37.457),(-122.2344,37.461)]
+Acacia Ave [(-122.2364,37.436),(-122.2364,37.443)]
+Acacia Ave [(-122.2415,37.435),(-122.2407,37.437)]
+Acadia Ct [(-121.9007,37.773),(-121.9016,37.768)]
+Acapulco Way [(-122.0517,37.91),(-122.0519,37.911)]
+Access Rd 162 [(-121.9469,37.993),(-121.9475,37.993)]
+Access Rd 25 [(-121.9283,37.894),(-121.9283,37.9)]
+Access Rd 29 [(-121.9339,37.854),(-121.9343,37.85)]
+Acton Cir [(-122.2824,37.681),(-122.2824,37.688)]
+Ada St [(-122.2487,37.398),(-122.2496,37.401)]
+Ada St [(-122.2807,37.807),(-122.2797,37.811)]
+Adams Ave [(-121.742,37.829),(-121.742,37.822)]
+Adams Ave [(-122.1906,37.253),(-122.1893,37.272)]
+Adams St [(-122.2349,37.542),(-122.2341,37.537)]
+Adams St [(-122.2364,37.553),(-122.2357,37.548)]
+Adason Dr [(-122.1315,37.016),(-122.1288,37.009)]
+Addison St [(-122.2735,37.705),(-122.2722,37.707)]
+Addison St [(-122.2856,37.688),(-122.2854,37.689)]
+Addison St [(-122.2874,37.686),(-122.2864,37.688)]
+Addison Way [(-121.9044,37.881),(-121.9044,37.889)]
+Addison Way [(-121.9044,37.895),(-121.9044,37.899)]
+Adelina Common [(-121.925847,37.29985),(-121.925765,37.29493)]
+Adeline St [(-122.2728,37.442),(-122.2725,37.451)]
+Adeline St [(-122.2785,37.291),(-122.2783,37.296)]
+Adelle St [(-121.7793,37.841),(-121.7797,37.849)]
+Admirality Lane [(-122.2424,37.323),(-122.2429,37.318)]
+Adobe Dr [(-122.0304,37.579),(-122.0278,37.592)]
+Adrian Ave [(-122.1019,37.389),(-122.1019,37.369)]
+Adriano St [(-122.032,37.663),(-122.0312,37.653)]
+Agate Ct [(-121.801,37.653),(-121.8008,37.649)]
+Agena Cir [(-122.0694,37.847),(-122.0696,37.839)]
+Agua Fria Creek [(-121.909487,37.94485),(-121.910653,37.94809)]
+Agua Fria Creek [(-121.9125,37.95367),(-121.9138,37.958)]
+Agua Fria Creek [(-121.9254,37.922),(-121.9281,37.889)]
+Agua Fria Creek [(-121.935,37.828),(-121.9356,37.826)]
+Agua Vista [(-122.0796,37.896),(-122.0792,37.896)]
+Agua Vista St [(-122.2089,37.839),(-122.2069,37.819)]
+Aileen St [(-122.2612,37.42),(-122.2622,37.421)]
+Airport Road [(-122.2085,37.147),(-122.2101,37.154)]
+Aladdin Ave [(-122.1532,37.088),(-122.1577,37.068)]
+Alameda Ave [(-122.2197,37.68),(-122.2192,37.68)]
+Alameda Ave [(-122.2555,37.689),(-122.2534,37.68)]
+Alameda Ave [(-122.2605,37.713),(-122.2588,37.704)]
+Alameda Dr [(-121.8756,37.746),(-121.8763,37.746)]
+Alameda Road [(-122.2955,37.875),(-122.2963,37.871)]
+Alameda Belt Line Railroad [(-122.2586,37.769),(-122.2624,37.784)]
+Alameda Belt Line Railroad [(-122.2697,37.798),(-122.2709,37.797)]
+Alameda Creek [(-121.8283,37.151),(-121.8273,37.142)]
+Alameda Creek [(-121.909502,37.93892),(-121.909,37.94)]
+Alameda Creek [(-121.930593,37.93785),(-121.930096,37.94011)]
+Alameda Creek [(-121.9466,37.974),(-121.9503,37.973)]
+Alameda Creek [(-121.9691,37.748),(-121.97,37.73)]
+Alameda Creek [(-121.9724,37.727),(-121.9738,37.726)]
+Alameda Creek [(-121.978805,37.72143),(-121.9839,37.717)]
+Alameda Creek [(-122.0136,37.734),(-122.0165,37.748)]
+Alameda Creek [(-122.022956,37.77306),(-122.025,37.781)]
+Alameda Creek [(-122.038,37.877),(-122.0446,37.873)]
+Alameda Creek [(-122.0513,37.248),(-122.0556,37.215)]
+Alameda Diversion [(-121.774647,37.97333),(-121.772718,37.98591)]
+Alamo Canal [(-121.910434,37.73476),(-121.9101,37.726)]
+Alamo Canal [(-121.9117,37.768),(-121.911261,37.75648)]
+Alamo Creek [(-121.910523,37.2611),(-121.910923,37.26374)]
+Albany St [(-122.0327,37.129),(-122.0326,37.115)]
+Albany Ter [(-122.2864,37.868),(-122.2856,37.867)]
+Alborg Ct [(-122.0492,37.154),(-122.0484,37.15)]
+Alcatraz Ave [(-122.2525,37.515),(-122.2531,37.514)]
+Alcatraz Ave [(-122.2617,37.502),(-122.2624,37.501)]
+Alcatraz Ave [(-122.279,37.479),(-122.2773,37.482)]
+Alcatraz Ave [(-122.2817,37.475),(-122.2825,37.475)]
+Alcosta Blvd [(-121.938,37.237),(-121.9392,37.235)]
+Alden Lane [(-121.786092,37.56057),(-121.7837,37.56)]
+Alden Lane [(-121.7978,37.561),(-121.795183,37.561)]
+Alden Road [(-122.1116,37.817),(-122.110686,37.81989),(-122.1097,37.823)]
+Alder Ct [(-122.0117,37.66),(-122.0109,37.653)]
+Alexander Ct [(-121.8708,37.845),(-121.8706,37.841)]
+Alexander St [(-121.7888,37.724),(-121.7874,37.724)]
+Alexandria St [(-122.1411,37.892),(-122.1417,37.892)]
+Algonquin Ave [(-121.7851,37.888),(-121.7852,37.891)]
+Alhambra Lane [(-122.2107,37.368),(-122.2102,37.367)]
+Alicante Dr [(-122.0211,37.587),(-122.0198,37.582)]
+Alice St [(-122.086,37.644),(-122.0848,37.625)]
+Alice St [(-122.2696,37.967),(-122.2695,37.969)]
+Alice St [(-122.2722,37.927),(-122.2714,37.94)]
+Alice Way [(-121.7968,37.718),(-121.7958,37.718)]
+Alice Way [(-122.0715,37.836),(-122.072,37.833)]
+Alida St [(-122.2025,37.06),(-122.2019,37.056)]
+Alisal St [(-121.86925,37.34979),(-121.8685,37.326)]
+Alisal St [(-121.87,37.382),(-121.8695,37.35)]
+Aliso Ave [(-122.1809,37.953),(-122.18,37.946)]
+Allegro Ct [(-121.9755,37.201),(-121.9764,37.201)]
+Allen Ct [(-122.0131,37.602),(-122.0117,37.597)]
+Allendale Ave [(-122.2048,37.863),(-122.2041,37.858),(-122.2035,37.851)]
+Allendale Ave [(-122.2067,37.882),(-122.2065,37.879)]
+Allison Dr [(-122.0748,37.863),(-122.073,37.855)]
+Allston Way [(-122.2799,37.677),(-122.2787,37.681)]
+Alma Ct [(-121.9087,37.799),(-121.9089,37.814)]
+Almaden Blvd [(-122.0551,37.008),(-122.0551,37.016)]
+Almaden Pl [(-121.9603,37.697),(-121.9601,37.693)]
+Almeria Dr [(-122.0711,37.224),(-122.0736,37.224)]
+Almond Ave [(-121.7387,37.75527),(-121.7387,37.74118)]
+Almond Ave [(-121.7388,37.778),(-121.7387,37.772)]
+Almond Road [(-122.0818,37.132),(-122.0831,37.116)]
+Aloe Ct [(-121.9158,37.922),(-121.9152,37.927)]
+Alpine Ter [(-122.2374,37.445),(-122.2377,37.459)]
+Alta Dr [(-122.0109,37.424),(-122.0101,37.419)]
+Alta Vista Ave [(-122.2483,37.174),(-122.2473,37.167)]
+Altamont Creek [(-121.7422,37.178),(-121.7413,37.203)]
+Altamont Creek [(-121.7509,37.149),(-121.7474,37.154)]
+Altamont Pass Road [(-121.659901,37.44449),(-121.666828,37.41016)]
+Altimirano Dr [(-121.8781,37.0193),(-121.8713,37.01707)]
+Alton Ct [(-121.9977,37.581),(-121.9981,37.576)]
+Alvarado Blvd [(-122.0562,37.829),(-122.055814,37.82723)]
+Alvarado Road [(-122.2339,37.608),(-122.2322,37.616)]
+Alvarado Road [(-122.2391,37.573),(-122.2397,37.58)]
+Alvarado St [(-122.1505,37.05),(-122.1494,37.03)]
+Alvarado Niles Road [(-122.0325,37.903),(-122.0316,37.9)]
+Alvarado Niles Road [(-122.049848,37.95115),(-122.0473,37.945)]
+Alvord Way [(-121.9085,37.891),(-121.9093,37.889)]
+Amador St [(-122.0963,37.614),(-122.0962,37.609)]
+Amador St [(-122.0981,37.647),(-122.0966,37.635)]
+Amador St [(-122.0999,37.664),(-122.099,37.655)]
+Amador Valley Blvd [(-121.9198,37.146),(-121.9211,37.138)]
+Amador Valley Ct [(-121.9365,37.068),(-121.937909,37.06375)]
+Amarillo Ct [(-121.9439,37.044),(-121.9432,37.046)]
+Ambar Pl [(-121.9494,37.482),(-121.9479,37.474)]
+Amber Ct [(-121.7997,37.708),(-121.7997,37.704)]
+Amber Way [(-121.8025,37.707),(-121.8015,37.708)]
+American Ave [(-122.1271,37.478),(-122.1281,37.489)]
+Ames Ter [(-121.9962,37.763),(-121.9955,37.762)]
+Amherst Ct [(-122.1571,37.036),(-122.1575,37.034)]
+Anchor Dr [(-122.3027,37.374),(-122.3032,37.383)]
+Andrade Road [(-121.8842,37.741),(-121.8841,37.738)]
+Andrade Road [(-121.8853,37.565),(-121.8855,37.564)]
+Andrea Cir [(-121.733218,37.88641),(-121.733286,37.90617)]
+Andrews St [(-121.7814,37.834),(-121.7814,37.829)]
+Angela St [(-121.865522,37.55324),(-121.8652,37.552)]
+Angela St [(-121.8795,37.607),(-121.8798,37.608)]
+Angus Way [(-122.098774,37.86535),(-122.0986,37.861)]
+Anita Ct [(-121.9655,37.744),(-121.9653,37.738)]
+Ann St [(-121.9888,37.604),(-121.9894,37.603)]
+Anna Maria St [(-121.7957,37.756),(-121.7958,37.725)]
+Annerley Road [(-122.2328,37.168),(-122.2325,37.17)]
+Antelope Ct [(-122.0653,37.773),(-122.0648,37.773)]
+Antonio St [(-122.1642,37.251),(-122.1653,37.247)]
+Anza St [(-121.9184,37.306),(-121.9197,37.304)]
+Anza Way [(-121.7794,37.714),(-121.7788,37.714)]
+Apgar St [(-122.2709,37.288),(-122.2719,37.29)]
+Apgar St [(-122.278,37.291),(-122.2785,37.291)]
+Apollo Cir [(-122.068531,37.87654),(-122.0686,37.877)]
+Appian Way [(-122.0022,37.98),(-122.0019,37.983)]
+Apple Ave [(-122.0909,37.85),(-122.0901,37.857)]
+Applewood St [(-121.9629,37.192),(-121.9616,37.168)]
+Apricot Lane [(-121.9471,37.401),(-121.9456,37.392)]
+Aquarius Cir [(-122.0669,37.877),(-122.0674,37.88)]
+Arbor Dr [(-121.8506,37.576),(-121.8521,37.578)]
+Arbor St [(-122.2587,37.758),(-122.2582,37.765)]
+Arcade Lane [(-122.2514,37.865),(-122.251558,37.86316)]
+Arch St [(-122.2639,37.79),(-122.2638,37.782)]
+Arch St [(-122.2647,37.846),(-122.2646,37.844)]
+Archer Ave [(-121.9879,37.627),(-121.9888,37.626)]
+Arden Road [(-122.0978,37.177),(-122.1,37.177)]
+Ardenwood Blvd [(-122.063701,37.59653),(-122.063302,37.58815)]
+Ardmore Dr [(-122.1308,37.211),(-122.1293,37.212)]
+Ardo St [(-122.0295,37.682),(-122.0302,37.674)]
+Ardo St [(-122.0306,37.659),(-122.0312,37.653)]
+Arena St [(-122.155014,37.82347),(-122.1559,37.82)]
+Arendt Way [(-121.8717,37.606),(-121.871,37.602)]
+Argonaut Way [(-121.993,37.475),(-121.9926,37.473)]
+Argonne St [(-122.146,37.806),(-122.1455,37.801),(-122.1451,37.796)]
+Arizona St [(-122.0381,37.901),(-122.0367,37.898)]
+Arizona St [(-122.044507,37.905),(-122.0443,37.904)]
+Arizona St [(-122.1985,37.978),(-122.1981,37.974)]
+Ark Dr [(-122.1313,37.029),(-122.1313,37.036)]
+Arkansas Pl [(-121.9148,37.696),(-121.9149,37.699)]
+Arlington Ave [(-122.2699,37.43),(-122.2719,37.428)]
+Arlington Ave [(-122.276,37.024),(-122.276,37.014)]
+Arlington Ave [(-122.276,37.988),(-122.2753,37.974)]
+Arlington Dr [(-121.8802,37.408),(-121.8807,37.394)]
+Arlington Road [(-121.7957,37.898),(-121.7956,37.906)]
+Armata St [(-121.9236,37.858),(-121.9232,37.853)]
+Arnold Ct [(-122.0887,37.669),(-122.0894,37.666)]
+Arnold Road [(-121.8923,37.113),(-121.8924,37.111)]
+Arnold Road [(-121.8924,37.06),(-121.8924,37.062)]
+Arrowhead Dr [(-122.1943,37.389),(-122.1908,37.366)]
+Arroyo Dr [(-121.9049,37.509),(-121.9029,37.516)]
+Arroyo Road [(-121.749307,37.14717),(-121.7481,37.14957)]
+Arroyo Road [(-121.7506,37.189),(-121.75,37.18541)]
+Arroyo Road [(-121.7555,37.258),(-121.7556,37.251)]
+Arroyo Road [(-121.76696,37.7112),(-121.76687,37.7094)]
+Arroyo Road [(-121.7671,37.654),(-121.767086,37.65214),(-121.767,37.641)]
+Arroyo de la Laguna [(-121.9064,37.612),(-121.9047,37.551)]
+Arroyo del Valle [(-121.607487,37.89841),(-121.612773,37.92638)]
+Arroyo del Valle [(-121.654588,37.36507),(-121.656972,37.4088)]
+Arroyo del Valle [(-121.75,37.20484),(-121.7584,37.208)]
+Arroyo del Valle [(-121.8049,37.539),(-121.7856,37.463)]
+Arroyo del Valle [(-121.8751,37.656),(-121.8731,37.646)]
+Arroyo Las Positas [(-121.7308,37.87),(-121.72772,37.85435)]
+Arroyo Las Positas [(-121.7349,37.943),(-121.734048,37.9262)]
+Arroyo Las Positas [(-121.7836,37.997),(-121.783492,37.99605)]
+Arroyo Las Positas [(-121.7973,37.997),(-121.7957,37.005)]
+Arroyo Las Positas [(-121.8473,37.965),(-121.8312,37.992)]
+Arroyo Las Positas [(-121.858962,37.94925),(-121.858919,37.95878)]
+Arroyo Mocho [(-121.553409,37.25257),(-121.565204,37.37327)]
+Arroyo Mocho [(-121.625,37.83316),(-121.624698,37.83019)]
+Arroyo Mocho [(-121.660579,37.01388),(-121.668949,37.027),(-121.682578,37.10817)]
+Arroyo Mocho [(-121.7316,37.595),(-121.7186,37.466)]
+Arroyo Mocho Canal [(-121.90854,37.78099),(-121.907797,37.78392)]
+Arroyo Seco [(-121.655796,37.50684),(-121.657215,37.51096)]
+Arroyo Seco [(-121.7073,37.766),(-121.6997,37.729)]
+Arroyuelo Ave [(-122.2496,37.271),(-122.249564,37.2728)]
+Ascot Dr [(-122.1934,37.217),(-122.1926,37.219)]
+Ascot Dr [(-122.1969,37.211),(-122.1951,37.207)]
+Ash St [(-122.0384,37.259),(-122.0388,37.276)]
+Ash St [(-122.0408,37.31),(-122.04,37.292)]
+Ashby Ave [(-122.2494,37.579),(-122.2485,37.579)]
+Ashby Ave [(-122.2526,37.574),(-122.2518,37.574)]
+Ashby Ave [(-122.264,37.557),(-122.263,37.559)]
+Ashland Ave [(-122.1178,37.941),(-122.1178,37.93)]
+Ashland Ave [(-122.1179,37.914),(-122.1179,37.913)]
+Ashwood Common [(-121.962832,37.21086),(-121.963052,37.21067)]
+Asilomar Dr [(-122.2028,37.298),(-122.20355,37.2905)]
+Asilomar Dr [(-122.2041,37.333),(-122.204,37.312)]
+Aspinwall Road [(-122.2074,37.39),(-122.2068,37.39)]
+Aster Ct [(-121.9125,37.72),(-121.9129,37.719)]
+At and Sf Railroad [(-122.2765,37.347),(-122.2768,37.339)]
+At and Sf Railroad [(-122.2767,37.463),(-122.2766,37.454)]
+At and Sf Railroad [(-122.2785,37.544),(-122.2784,37.535)]
+At and Sf Railroad [(-122.281389,37.30695),(-122.282488,37.30491)]
+At and Sf Railroad [(-122.2827,37.611),(-122.2821,37.603)]
+At and Sf Railroad [(-122.2844,37.293),(-122.2848,37.291)]
+At and Sf Railroad [(-122.2878,37.788),(-122.2874,37.783)]
+At and Sf Railroad [(-122.288,37.3),(-122.288,37.294)]
+Atherton St [(-122.0819,37.68),(-122.0809,37.669)]
+Atherton St [(-122.0838,37.7),(-122.0829,37.69)]
+Atherton St [(-122.1701,37.612),(-122.1696,37.606)]
+Athol Ave [(-122.2535,37.01),(-122.2523,37.016)]
+Atlantic Ave [(-122.2831,37.804),(-122.2816,37.803)]
+Atlantic St [(-122.0371,37.018),(-122.0382,37.018)]
+Atlas Ave [(-122.1889,37.964),(-122.1882,37.966)]
+Atwater Ct [(-122.0076,37.662),(-122.0084,37.654)]
+Auburn Ave [(-122.25,37.489),(-122.25035,37.4945)]
+Audrey Dr [(-122.069,37.13),(-122.0683,37.131)]
+Audubon St [(-122.0388,37.261),(-122.0383,37.258)]
+Aughinbaugh Way [(-122.2491,37.473),(-122.249,37.471)]
+Augustine Pl [(-122.0169,37.732),(-122.0163,37.725)]
+Aurora Dr [(-122.1804,37.973),(-122.18,37.966)]
+Auseon Ave [(-122.1653,37.565),(-122.165,37.567)]
+Austin St [(-121.943,37.422),(-121.9435,37.425)]
+Autumn Oak Dr [(-121.749435,37.09187),(-121.749806,37.10065)]
+Avalon Ave [(-122.2477,37.597),(-122.246,37.598)]
+Avenue 130th [(-122.1851,37.044),(-122.1872,37.036)]
+Avenue 134th [(-122.1823,37.002),(-122.1851,37.992)]
+Avenue 140th [(-122.1656,37.003),(-122.1691,37.988)]
+Avenue A [(-122.3005,37.885),(-122.3024,37.885)]
+Avenue A [(-122.3035,37.885),(-122.3076,37.886)]
+Avenue D [(-122.298,37.848),(-122.3024,37.849)]
+Avenue F [(-122.2943,37.831),(-122.2971,37.832)]
+Avenue L [(-122.296,37.757),(-122.2985,37.757)]
+Avoca Ave [(-122.2211,37.413),(-122.2204,37.416)]
+Ayala Ave [(-122.2587,37.429),(-122.2584,37.435)]
+Azalea Ct [(-121.7365,37.13),(-121.7357,37.136)]
+Azevedo Ave [(-122.0639,37.756),(-122.0641,37.75)]
+Aztec Ct [(-121.922,37.92),(-121.921,37.92)]
+B St [(-121.8924,37.95133),(-121.8924,37.952)]
+B St [(-122.0241,37.05),(-122.0248,37.045)]
+B St [(-122.0531,37.434),(-122.0537,37.434)]
+B St [(-122.0656,37.823),(-122.0652,37.825)]
+B St [(-122.0799,37.742),(-122.0782,37.753)]
+B St [(-122.087,37.707),(-122.0863,37.709)]
+B St [(-122.0955,37.673),(-122.0944,37.677)]
+B St [(-122.1749,37.451),(-122.1743,37.443)]
+Bach Ct [(-121.9778,37.295),(-121.9769,37.292)]
+Bahama Ave [(-122.1039,37.335),(-122.1031,37.321)]
+Bahama Com [(-122.0361,37.72),(-122.036031,37.72046),(-122.0358,37.722)]
+Baine Ave [(-122.0089,37.565),(-122.0104,37.546)]
+Bairo Ct [(-121.9505,37.398),(-121.9498,37.393)]
+Baker St [(-122.2792,37.495),(-122.2791,37.488)]
+Balboa Dr [(-122.1982,37.319),(-122.1971,37.333)]
+Balboa Way [(-122.0205,37.519),(-122.0207,37.517)]
+Baldwin Pl [(-122.0274,37.697),(-122.0265,37.692)]
+Ballantyne Dr [(-121.858907,37.985),(-121.8585,37.985)]
+Ballantyne Dr [(-121.8611,37.986),(-121.8605,37.985)]
+Ballena Blvd [(-122.2854,37.691),(-122.285393,37.68924)]
+Ballentine Dr [(-121.859765,37.96825),(-121.860477,37.96784)]
+Balmoral Dr [(-122.1639,37.981),(-122.1635,37.988)]
+Balmoral Dr [(-122.1658,37.027),(-122.1656,37.042)]
+Balmoral St [(-122.055,37.971),(-122.0555,37.979)]
+Banbury St [(-122.0943,37.495),(-122.0949,37.493)]
+Bancroft Ave [(-122.1475,37.288),(-122.147,37.276)]
+Bancroft Ave [(-122.1485,37.311),(-122.1481,37.303)]
+Bancroft Ave [(-122.1518,37.358),(-122.1511,37.349)]
+Bancroft Ave [(-122.15714,37.4242),(-122.156,37.409)]
+Bancroft Ave [(-122.1585,37.445),(-122.1583,37.441)]
+Bancroft Ave [(-122.1643,37.523),(-122.1631,37.508),(-122.1621,37.493)]
+Bancroft Ave [(-122.1718,37.62),(-122.1715,37.617)]
+Bancroft Ave [(-122.1796,37.689),(-122.1792,37.684)]
+Bancroft Ave [(-122.1903,37.706),(-122.189,37.705)]
+Bancroft Ave [(-122.2041,37.716),(-122.202,37.716)]
+Bancroft Ct [(-122.1329,37.109),(-122.1322,37.116)]
+Bancroft Way [(-122.2753,37.667),(-122.2742,37.668)]
+Bancroft Way [(-122.2846,37.654),(-122.283747,37.655)]
+Bancroft Way [(-122.291,37.644),(-122.2899,37.647)]
+Bandon Dr [(-121.9311,37.234),(-121.931,37.237)]
+Banyan Tree Road [(-121.9901,37.317),(-121.9877,37.304)]
+Barbers Point Road [(-122.2957,37.896),(-122.2965,37.894)]
+Barcelona Ave [(-122.0896,37.276),(-122.0894,37.253)]
+Barcelona St [(-122.1459,37.639),(-122.1445,37.623)]
+Barcelona Way [(-122.0787,37.86),(-122.0783,37.858)]
+Bardolph Cir [(-122.0583,37.718),(-122.0586,37.726)]
+Barlow Dr [(-122.0891,37.034),(-122.088,37.037),(-122.086558,37.03988)]
+Barlow Dr [(-122.0915,37.03),(-122.0903,37.032)]
+Bart Ramp [(-122.0495,37.208),(-122.0473,37.196)]
+Bart Access Road [(-122.0346,37.081),(-122.0329,37.057)]
+Bartlett Lane [(-122.111062,37.71771),(-122.10988,37.70276)]
+Bartlett St [(-122.2071,37.902),(-122.2053,37.913)]
+Barton Dr [(-121.9655,37.744),(-121.9644,37.749)]
+Bates Dr [(-122.0328,37.748),(-122.0322,37.745)]
+Baumberg Ave [(-122.0987,37.241),(-122.0985,37.237)]
+Bautista St [(-121.9227,37.29),(-121.9225,37.284)]
+Bay St [(-121.9611,37.33),(-121.9627,37.329)]
+Bay St [(-122.2642,37.732),(-122.2641,37.751)]
+Bay Area Rapid Transit [(-121.983355,37.64329),(-121.982907,37.63842)]
+Bay Area Rapid Transit [(-122.0049,37.816),(-122.004,37.809)]
+Bay Area Rapid Transit [(-122.007,37.833),(-122.0062,37.827)]
+Bay Area Rapid Transit [(-122.02,37.935),(-122.0193,37.926)]
+Bay Area Rapid Transit [(-122.0309,37.057),(-122.0281,37.027),(-122.0262,37.001)]
+Bay Area Rapid Transit [(-122.0813,37.661),(-122.0806,37.654)]
+Bay Area Rapid Transit [(-122.0981,37.779),(-122.0963,37.767)]
+Bay Area Rapid Transit [(-122.1694,37.311),(-122.1679,37.3)]
+Bay Area Rapid Transit [(-122.2086,37.641),(-122.2061,37.619)]
+Bay Area Rapid Transit [(-122.2129,37.676),(-122.212,37.668)]
+Bay Area Rapid Transit [(-122.2172,37.713),(-122.2184,37.719)]
+Bay Area Rapid Transit [(-122.231147,37.54912),(-122.229,37.562)]
+Bay Area Rapid Transit [(-122.2349,37.525),(-122.2339,37.532)]
+Bay Area Rapid Transit [(-122.2571,37.427),(-122.2563,37.431)]
+Bay Area Rapid Transit [(-122.2658,37.337),(-122.2644,37.38)]
+Bay Area Rapid Transit [(-122.267508,37.25368),(-122.2674,37.258)]
+Bay Forest Dr [(-122.2139,37.561),(-122.2142,37.565)]
+Bay Walk Road [(-122.2471,37.389),(-122.2462,37.389)]
+Bayfield Pl [(-121.9665,37.204),(-121.9664,37.196)]
+Baylor St [(-122.0272,37.93),(-122.0284,37.903)]
+Bayview Ave [(-122.0584,37.864),(-122.0581,37.855)]
+Bayview Dr [(-122.2386,37.511),(-122.2379,37.514)]
+Beachwood Way [(-121.8817,37.731),(-121.8807,37.731)]
+Beacon St [(-122.2484,37.09),(-122.2472,37.088)]
+Beard Road [(-122.0417,37.819),(-122.0424,37.81)]
+Beard Road [(-122.0447,37.778),(-122.045,37.775)]
+Beaumont Ave [(-122.2263,37.033),(-122.2262,37.04)]
+Becket Dr [(-122.0509,37.005),(-122.0509,37.033)]
+Bedelio Ter [(-122.019,37.579),(-122.018,37.574)]
+Bedford St [(-121.9333,37.403),(-121.9338,37.402)]
+Bedford Way [(-121.928,37.149),(-121.9288,37.15)]
+Beecham Ct [(-121.8693,37.959),(-121.8704,37.959)]
+Beechmont Lane [(-122.0971,37.558),(-122.0984,37.555)]
+Begier Ave [(-122.15,37.314),(-122.1488,37.317)]
+Begonia Dr [(-122.1334,37.01),(-122.1342,37.01)]
+Begonia St [(-122.0153,37.785),(-122.0156,37.772)]
+Begonia St [(-122.0218,37.797),(-122.022,37.789)]
+Bel Aire St [(-122.0717,37.726),(-122.0714,37.725)]
+Bell St [(-121.99126,37.4916),(-121.991407,37.49215)]
+Belleview Dr [(-122.1626,37.325),(-122.1635,37.32)]
+Bellevue Ave [(-122.2529,37.13),(-122.2521,37.111)]
+Bellflower Dr [(-122.0103,37.317),(-122.009979,37.31387)]
+Bellhaven Ave [(-122.0354,37.414),(-122.0364,37.405)]
+Belmont Ave [(-122.0708,37.588),(-122.0703,37.582)]
+Belvedere Ave [(-122.1768,37.918),(-122.1772,37.918)]
+Belvedere Ave [(-122.2892,37.767),(-122.2888,37.759)]
+Benecia Ave [(-122.0077,37.222),(-122.0076,37.225)]
+Benedict Dr [(-122.1326,37.204),(-122.1323,37.199)]
+Benner Ct [(-121.9063,37.891),(-121.9076,37.888)]
+Bennington Lane [(-122.103818,37.36136),(-122.1045,37.361)]
+Benson Road [(-122.083217,37.94765),(-122.0891,37.928)]
+Benton St [(-122.2605,37.713),(-122.2605,37.731)]
+Berkeley Way [(-122.2747,37.722),(-122.2726,37.725)]
+Berlin Way [(-121.7774,37.649),(-121.7766,37.649)]
+Bernal Ave [(-121.8556,37.668),(-121.85626,37.68656)]
+Bernal Ave [(-121.895208,37.57837),(-121.884914,37.57603)]
+Bernhardt Dr [(-122.1852,37.297),(-122.1847,37.292)]
+Bernhardt St [(-122.1326,37.399),(-122.1322,37.449)]
+Berwind Ave [(-121.7308,37.183),(-121.7303,37.181)]
+Besco Dr [(-121.9764,37.32),(-121.9761,37.311)]
+Bess Ave [(-121.765239,37.625),(-121.763602,37.625)]
+Best Ave [(-122.1622,37.284),(-122.1636,37.278)]
+Betlen Dr [(-121.9407,37.026),(-121.9397,37.029)]
+Betlen Dr [(-121.9507,37.018),(-121.950121,37.01678)]
+Bettencourt St [(-122.0479,37.34),(-122.0473,37.337)]
+Beverly Ave [(-122.1578,37.382),(-122.1572,37.375)]
+Beverly Ave [(-122.1586,37.395),(-122.1582,37.388)]
+Beverly St [(-121.736023,37.85177),(-121.737956,37.84632)]
+Bianca Way [(-121.7244,37.946),(-121.7257,37.945)]
+Bianca Way [(-121.7281,37.939),(-121.729,37.937)]
+Biddle Ave [(-122.0317,37.425),(-122.0329,37.417)]
+Bidwell Dr [(-121.9748,37.448),(-121.9763,37.427)]
+Bidwell Dr [(-121.9763,37.422),(-121.9764,37.42)]
+Biehs Ct [(-122.2289,37.386),(-122.2283,37.391)]
+Big Burn Road [(-122.0918,37.802),(-122.1091,37.788)]
+Binnacle Hill [(-122.2269,37.533),(-122.2274,37.523)]
+Birch St [(-122.0269,37.368),(-122.0254,37.36)]
+Birch St [(-122.1617,37.425),(-122.1614,37.417)]
+Birch St [(-122.1653,37.478),(-122.1641,37.464)]
+Birch St [(-122.1673,37.509),(-122.1661,37.492)]
+Birch Creek Dr [(-121.8641,37.629),(-121.8642,37.64)]
+Birdsall Ave [(-122.1907,37.774),(-122.1907,37.781)]
+Birdsall Ave [(-122.191,37.789),(-122.1911,37.796)]
+Birkdale Dr [(-122.0515,37.373),(-122.0521,37.368)]
+Birkdale Way [(-122.0406,37.17),(-122.0386,37.153)]
+Biscayne Ave [(-122.0734,37.278),(-122.0734,37.274)]
+Bishop Ave [(-121.9911,37.635),(-121.9921,37.632)]
+Bitterroot Ave [(-122.0091,37.276),(-122.0087,37.282)]
+Black Ave [(-121.8816,37.721),(-121.8826,37.721)]
+Black Ave [(-121.8909,37.704),(-121.892,37.698)]
+Black Ave [(-121.8964,37.701),(-121.8967,37.706)]
+Blackbird Way [(-121.8867,37.801),(-121.8876,37.801),(-121.8882,37.801)]
+Blackstone Way [(-122.0393,37.724),(-122.0388,37.721)]
+Blackstone Way [(-122.0418,37.736),(-122.043,37.743)]
+Blacow Road [(-121.9909,37.33),(-121.9895,37.324)]
+Blacow Road [(-122.0061,37.409),(-122.0053,37.405)]
+Blacow Road [(-122.0179,37.469),(-122.0167,37.465)]
+Blair Ave [(-122.2225,37.27),(-122.2217,37.276)]
+Blair Ave [(-122.2364,37.263),(-122.2359,37.267)]
+Blaisdell Way [(-121.9858,37.816),(-121.9853,37.811)]
+Blake St [(-122.2622,37.639),(-122.2599,37.642)]
+Blake St [(-122.2864,37.605),(-122.2845,37.608)]
+Blanchard St [(-121.97,37.382),(-121.9693,37.382)]
+Blanchard St [(-121.9729,37.38),(-121.9709,37.383)]
+Blanding Ave [(-122.2313,37.68),(-122.2328,37.686)]
+Blewett St [(-121.9732,37.373),(-121.9733,37.369)]
+Bloomington Way [(-121.9448,37.205),(-121.9434,37.204)]
+Blossom Ct [(-121.8766,37.395),(-121.876493,37.39469)]
+Blossom Ct [(-122.0212,37.772),(-122.0213,37.769)]
+Blossom Way [(-122.1096,37.758),(-122.1087,37.764),(-122.1057,37.774)]
+Blue Coral [(-121.965392,37.69509),(-121.965261,37.70132)]
+Bluebell Dr [(-121.74,37.151),(-121.7411,37.161)]
+Bluefield Lane [(-122.1024,37.584),(-122.1033,37.561)]
+Blythe St [(-122.0704,37.745),(-122.0711,37.739)]
+Boar Cir [(-121.912463,37.08667),(-121.912335,37.09052)]
+Bobwhite Ter [(-122.046797,37.80224),(-122.046672,37.80179)]
+Bockman Road [(-122.1206,37.713),(-122.122,37.712)]
+Bodie Ter [(-121.9253,37.884),(-121.9247,37.887)]
+Boeing St [(-122.2122,37.34),(-122.2112,37.322)]
+Bolero Ave [(-122.0904,37.297),(-122.0913,37.297)]
+Bonar St [(-122.2857,37.653),(-122.2856,37.642)]
+Bond St [(-122.2071,37.718),(-122.2067,37.716)]
+Bond St [(-122.2126,37.75),(-122.2116,37.739)]
+Bonde Way [(-122.0077,37.59),(-122.0084,37.58)]
+Bonita Ave [(-122.2355,37.306),(-122.235,37.296)]
+Bonita Ave [(-122.2727,37.843),(-122.2725,37.835)]
+Bonner Ave [(-121.9748,37.635),(-121.976,37.631)]
+Bonnie St [(-122.0332,37.381),(-122.0324,37.378)]
+Booker Way [(-122.0898,37.464),(-122.0902,37.454)]
+Boone Dr [(-121.9825,37.329),(-121.9829,37.324)]
+Boone Dr [(-122.0271,37.151),(-122.02815,37.14124)]
+Bordeaux St [(-121.7685,37.688),(-121.7687,37.664)]
+Boston Ave [(-122.2132,37.961),(-122.2129,37.969)]
+Boulevard Way [(-122.2427,37.18),(-122.2423,37.181)]
+Bourbon Dr [(-122.0869,37.194),(-122.0878,37.192)]
+Bowditch St [(-122.2559,37.665),(-122.2557,37.656)]
+Bowie Common [(-122.042847,37.64532),(-122.042808,37.64484)]
+Boxwood Way [(-121.9329,37.094),(-121.9335,37.096)]
+Bradrick Dr [(-122.138,37.962),(-122.1361,37.963)]
+Bradshire Road [(-122.0885,37.204),(-122.0883,37.2)]
+Bramble Ct [(-122.0944,37.941),(-122.0951,37.94)]
+Brann St [(-122.1806,37.709),(-122.1785,37.705)]
+Brayton Ct [(-122.0123,37.423),(-122.0114,37.418)]
+Breakwater Ave [(-122.1196,37.294),(-122.1203,37.282)]
+Brentford St [(-122.1965,37.581),(-122.1964,37.564)]
+Breton Dr [(-122.0435,37.463),(-122.043,37.458)]
+Brian St [(-122.0686,37.348),(-122.0693,37.344)]
+Briar Cliff Road [(-122.1409,37.647),(-122.1382,37.658)]
+Briarwood Dr [(-121.7663,37.915),(-121.7652,37.916)]
+Brickell Way [(-122.067,37.104),(-122.067,37.101)]
+Bridge Ct [(-122.0879,37.848),(-122.0874,37.844)]
+Bridgepointe Dr [(-122.0514,37.305),(-122.0509,37.299)]
+Bridgeview Dr [(-122.2112,37.133),(-122.21,37.138)]
+Bridgewood Ter [(-122.0042,37.639),(-122.0047,37.632)]
+Brier St [(-122.0806,37.959),(-122.0805,37.963)]
+Brighton Ave [(-122.2944,37.979),(-122.2934,37.979)]
+Brighton Dr [(-121.9263,37.188),(-121.9277,37.189),(-121.9285,37.19)]
+Brighton Dr [(-121.931,37.198),(-121.9312,37.197)]
+Briscoe Ter [(-121.948491,37.4184),(-121.948634,37.41645)]
+Bristol Blvd [(-122.1674,37.353),(-122.1698,37.342)]
+Bristolwood Road [(-121.9165,37.78),(-121.9164,37.787)]
+Broadmoor Blvd [(-122.147,37.397),(-122.1466,37.399)]
+Broadmoor Blvd [(-122.156,37.358),(-122.1546,37.364)]
+Broadmoor St [(-121.7313,37.257),(-121.7313,37.263)]
+Broadmoor St [(-121.7314,37.194),(-121.7314,37.199)]
+Broadmoor St [(-121.7314,37.213),(-121.7314,37.221)]
+Broadmore Ave [(-122.095,37.522),(-122.0936,37.497)]
+Broadway [(-122.2212,37.5),(-122.2204,37.517)]
+Broadway [(-122.2372,37.631),(-122.236753,37.63675)]
+Broadway [(-122.2391,37.493),(-122.2386,37.495)]
+Broadway [(-122.2409,37.586),(-122.2395,37.601)]
+Broadway [(-122.243008,37.55961),(-122.2427,37.563)]
+Broadway [(-122.245,37.45),(-122.2443,37.46),(-122.2436,37.469)]
+Broadway [(-122.2457,37.528),(-122.2455,37.529)]
+Broadway [(-122.2472,37.418),(-122.2468,37.426)]
+Broadway [(-122.2539,37.316),(-122.2525,37.337)]
+Broadway [(-122.2598,37.222),(-122.2596,37.227)]
+Broadway [(-122.2632,37.167),(-122.2626,37.177),(-122.2617,37.19)]
+Broadway [(-122.2719,37.028),(-122.2714,37.036)]
+Broadway [(-122.2727,37.015),(-122.2723,37.021)]
+Broadway Ter [(-122.2429,37.393),(-122.2413,37.397)]
+Brookdale Ave [(-122.2043,37.834),(-122.2032,37.824)]
+Brookdale Ave [(-122.2092,37.878),(-122.208568,37.87208)]
+Brookdale Ave [(-122.2095,37.888),(-122.2088,37.882)]
+Brookdale Blvd [(-122.0915,37.164),(-122.0912,37.166)]
+Brookdale Blvd [(-122.0965,37.123),(-122.0958,37.133)]
+Brooklyn Ave [(-122.2425,37.029),(-122.2416,37.026)]
+Brooklyn Ave [(-122.2455,37.04),(-122.2445,37.036)]
+Brooklyn Ave [(-122.2502,37.055),(-122.2495,37.053)]
+Brookside Ct [(-121.9218,37.902),(-121.9213,37.908)]
+Browning Ct [(-122.037289,37.766),(-122.038366,37.76228)]
+Browning St [(-122.2874,37.686),(-122.2872,37.669)]
+Bruce Ct [(-122.0595,37.084),(-122.0588,37.076)]
+Bruce Dr [(-121.9442,37.309),(-121.945,37.312)]
+Brunetti Lane [(-122.136,37.91),(-122.1351,37.906)]
+Bruns Road [(-121.603992,37.95307),(-121.6046,37.049)]
+Brush St [(-122.2788,37.065),(-122.2784,37.07)]
+Brush St [(-122.283,37.989),(-122.2827,37.994)]
+Brush Ramp St [(-122.2758,37.107),(-122.27511,37.11304),(-122.275,37.114)]
+Bryant St [(-121.9216,37.321),(-121.9213,37.316)]
+Bryce Canyon Ct [(-121.9008,37.78),(-121.9017,37.78)]
+Buchanan St [(-122.3022,37.877),(-122.3014,37.878)]
+Buckeye Pl [(-122.0448,37.336),(-122.0452,37.332)]
+Buckingham Blvd [(-122.2231,37.59),(-122.2214,37.606)]
+Buckingham Way [(-122.0647,37.214),(-122.0653,37.214)]
+Buckingham Way [(-122.0689,37.208),(-122.0693,37.207)]
+Buckner Ter [(-122.060105,37.62504),(-122.059743,37.62326)]
+Bucks Lake St [(-122.0559,37.882),(-122.0546,37.874)]
+Buckskin Road [(-121.7421,37.213),(-121.7421,37.22)]
+Budwing Ter [(-121.9516,37.136),(-121.951826,37.13555)]
+Buena Ave [(-122.2786,37.792),(-122.2773,37.797)]
+Buena Ave [(-122.2813,37.781),(-122.2807,37.782)]
+Buena Vista Ave [(-122.2301,37.437),(-122.2295,37.424)]
+Buena Vista Ave [(-122.2337,37.651),(-122.2328,37.645)]
+Buena Vista Ave [(-122.2359,37.47),(-122.2353,37.468)]
+Buena Vista Ave [(-122.251,37.735),(-122.2499,37.73)]
+Buena Vista Ave [(-122.2687,37.774),(-122.2673,37.773)]
+Buena Vista Ave [(-122.271,37.774),(-122.2698,37.774)]
+Buena Vista Way [(-122.2609,37.805),(-122.2597,37.809)]
+Bullard Dr [(-122.2157,37.297),(-122.2138,37.276)]
+Bullard St [(-121.9694,37.355),(-121.97,37.349)]
+Burdeck Dr [(-122.1939,37.099),(-122.1932,37.091)]
+Burdette St [(-121.9789,37.609),(-121.9795,37.611)]
+Burdette St [(-121.98,37.62),(-121.9801,37.626)]
+Burdick St [(-122.0273,37.421),(-122.0266,37.418)]
+Burk St [(-122.2501,37.101),(-122.2502,37.106)]
+Burkhart Ave [(-122.1422,37.856),(-122.1431,37.859)]
+Burlington St [(-122.2046,37.057),(-122.2042,37.058)]
+Burnett St [(-122.2823,37.539),(-122.281,37.541)]
+Burnham Way [(-121.9242,37.176),(-121.9243,37.183)]
+Burnside Ct [(-122.0063,37.345),(-122.0069,37.338)]
+Busby Ave [(-122.1545,37.79),(-122.154983,37.78916)]
+Butte Ct [(-121.783,37.938),(-121.783,37.934)]
+Butterfield Dr [(-122.0838,37.002),(-122.0834,37.987)]
+C St [(-122.0218,37.05),(-122.0224,37.045)]
+C St [(-122.0737,37.767),(-122.0722,37.778)]
+C St [(-122.0773,37.742),(-122.0756,37.754)]
+C St [(-122.0906,37.681),(-122.0896,37.684)]
+C St [(-122.1737,37.418),(-122.1723,37.399),(-122.1716,37.393)]
+C St [(-122.1768,37.46),(-122.1749,37.435)]
+Cabello St [(-122.078,37.811),(-122.0783,37.807)]
+Cabernet Ct [(-121.8636,37.593),(-121.8641,37.582)]
+Cabot Blvd [(-122.1334,37.412),(-122.1326,37.399)]
+Cabot Ct [(-121.9848,37.583),(-121.9833,37.583)]
+Cabral Dr [(-122.0294,37.569),(-122.0288,37.563)]
+Cabral Dr [(-122.0348,37.648),(-122.035,37.643)]
+Cabrillo Dr [(-122.0153,37.515),(-122.0144,37.511)]
+Cabrillo Dr [(-122.0325,37.637),(-122.0318,37.633)]
+Cabrillo Dr [(-122.091,37.218),(-122.0932,37.222)]
+Cadiz Dr [(-122.0239,37.655),(-122.0235,37.653)]
+Calaroga Ave [(-122.0886,37.297),(-122.0885,37.276)]
+Calaroga Ave [(-122.0892,37.374),(-122.0888,37.361)]
+Calaroga Ave [(-122.09,37.386),(-122.0897,37.38)]
+Calaroga Ave [(-122.101,37.493),(-122.1006,37.487)]
+Calaveras Ave [(-121.9924,37.364),(-121.9927,37.359)]
+Calaveras Ave [(-122.1864,37.845),(-122.1854,37.841)]
+Calaveras Road [(-121.8389,37.143),(-121.8338,37.128)]
+Calaveras Road [(-121.8476,37.209),(-121.8431,37.178)]
+Calaveras Creek [(-121.8203,37.035),(-121.8207,37.931)]
+Calaveras Creek [(-121.8531,37.337),(-121.8517,37.316)]
+Calaveras Creek [(-121.8637,37.611),(-121.8628,37.587)]
+Calcott Ct [(-122.0306,37.822),(-122.0311,37.817)]
+Caldecott Lane [(-122.2312,37.512),(-122.2261,37.491)]
+Calhoun St [(-122.0542,37.43),(-122.0521,37.428)]
+Calhoun St [(-122.2409,37.553),(-122.2405,37.551)]
+Caliban Dr [(-122.0553,37.765),(-122.053955,37.75806)]
+Caliente Dr [(-122.1393,37.993),(-122.1409,37.99),(-122.1417,37.993)]
+California St [(-122.1952,37.942),(-122.1946,37.935)]
+California St [(-122.2032,37.005),(-122.2016,37.996)]
+California St [(-122.2767,37.563),(-122.2767,37.554)]
+California St [(-122.2791,37.698),(-122.2787,37.681)]
+California St [(-122.2793,37.743),(-122.2794,37.733)]
+California St [(-122.2795,37.761),(-122.2795,37.751)]
+California Aqueduct [(-121.587742,37.65201),(-121.600239,37.70939)]
+California Aqueduct [(-121.622944,37.98443),(-121.622669,37.98611)]
+Call Ave [(-122.0435,37.56),(-122.0436,37.566)]
+Calle Alegre [(-121.895178,37.71975),(-121.8946,37.725)]
+Calle Altamira [(-121.8994,37.669),(-121.8988,37.656)]
+Calle de la Mesa [(-121.905106,37.71532),(-121.906643,37.69977)]
+Calle de Monte [(-122.2452,37.344),(-122.2456,37.34)]
+Calle Morelia [(-121.9003,37.684),(-121.9006,37.691)]
+Calmar Ave [(-122.2384,37.105),(-122.2381,37.113)]
+Camanoe Lane [(-122.2339,37.33),(-122.2334,37.326)]
+Cambio Ct [(-122.0217,37.558),(-122.0226,37.552)]
+Cambridge Ave [(-122.1616,37.335),(-122.1575,37.353)]
+Cambridge Way [(-122.2442,37.231),(-122.2419,37.244)]
+Camden St [(-121.9932,37.571),(-121.992,37.564)]
+Camden St [(-121.9956,37.603),(-121.9955,37.598)]
+Camden St [(-122.1823,37.735),(-122.1817,37.73)]
+Camelford Pl [(-122.1933,37.211),(-122.1919,37.216)]
+Camelia Dr [(-121.7852,37.695),(-121.7852,37.686)]
+Camelia St [(-122.2928,37.792),(-122.2916,37.792)]
+Camero Pl [(-121.9461,37.463),(-121.9453,37.454)]
+Camero Way [(-121.9481,37.459),(-121.9488,37.456)]
+Cameron Ave [(-121.86732,37.8431),(-121.865836,37.84371)]
+Cameron Ave [(-122.1316,37.502),(-122.1327,37.481)]
+Camino del Valle [(-122.2431,37.343),(-122.2437,37.334)]
+Camino Santa Barbara [(-121.9314,37.446),(-121.9303,37.436)]
+Camino Segura [(-121.900094,37.71647),(-121.9002,37.726)]
+Campbell St [(-122.2941,37.123),(-122.2936,37.129)]
+Campbell St [(-122.2985,37.066),(-122.2981,37.077)]
+Campus Dr [(-122.0578,37.665),(-122.0545,37.66)]
+Campus Dr [(-122.1626,37.858),(-122.1611,37.843)]
+Campus Dr [(-122.1704,37.905),(-122.1678,37.868),(-122.1671,37.865)]
+Canary Ct [(-122.019,37.856),(-122.0192,37.85)]
+Canfield Dr [(-121.9952,37.488),(-121.9955,37.482)]
+Canon Ave [(-122.21715,37.01951),(-122.2151,37.053)]
+Canterbury Lane [(-121.9277,37.141),(-121.9276,37.149)]
+Canterbury St [(-121.995,37.573),(-121.9948,37.57)]
+Canyon Heights Dr [(-121.9431,37.568),(-121.9433,37.571)]
+Canyon Heights Dr [(-121.9513,37.618),(-121.9528,37.627)]
+Canyon Heights Dr [(-121.9595,37.76),(-121.9596,37.753)]
+Cape Cod Dr [(-122.1351,37.928),(-122.1331,37.928)]
+Capella Lane [(-122.2345,37.322),(-122.2352,37.326)]
+Capitan Dr [(-121.84488,37.65695),(-121.84458,37.65195)]
+Capricorn Ave [(-122.2176,37.404),(-122.2164,37.384)]
+Capulet Cir [(-122.057612,37.69268),(-122.0578,37.7)]
+Cardinal Dr [(-121.7865,37.805),(-121.7858,37.805)]
+Caribbean Com [(-122.0361,37.73),(-122.0366,37.724)]
+Carleton St [(-122.2641,37.617),(-122.2619,37.62)]
+Carlos Bee Blvd [(-122.065049,37.59928),(-122.0639,37.596)]
+Carlston Ave [(-122.23,37.132),(-122.2307,37.141)]
+Carmel Ave [(-122.2891,37.979),(-122.2893,37.968),(-122.2893,37.95)]
+Carmel Dr [(-122.0965,37.135),(-122.0958,37.133)]
+Carmel Way [(-122.1394,37.979),(-122.1386,37.98)]
+Carmel Way [(-122.1419,37.98),(-122.1411,37.98)]
+Carmen St [(-121.9501,37.38),(-121.9484,37.369)]
+Carnation Way [(-121.9975,37.775),(-121.996,37.773)]
+Carol Ave [(-121.9537,37.283),(-121.955,37.282)]
+Carol Ave [(-121.9574,37.279),(-121.959,37.277)]
+Carol Ave [(-121.9657,37.27),(-121.9679,37.268)]
+Caroline St [(-122.2676,37.695),(-122.2676,37.706)]
+Carolyn St [(-122.1108,37.038),(-122.1091,37.028)]
+Carpentier St [(-122.1567,37.203),(-122.1561,37.194)]
+Carriage Circle Com [(-122.0128,37.433),(-122.0129,37.431)]
+Carrol Road [(-121.659839,37.19494),(-121.659626,37.19326)]
+Carson St [(-122.1846,37.9),(-122.1843,37.901)]
+Carver Lane [(-121.877,37.057),(-121.8811,37.068)]
+Cascade Road [(-122.1832,37.241),(-122.1808,37.216)]
+Cascade St [(-122.0839,37.416),(-122.0831,37.416)]
+Cascade St [(-122.0894,37.448),(-122.0887,37.431)]
+Cassiopia St [(-121.735979,37.18311),(-121.735979,37.19069)]
+Castilian Road [(-121.9447,37.135),(-121.9445,37.14)]
+Castille Lane [(-122.0826,37.811),(-122.082655,37.8124)]
+Castillejo Road [(-121.9397,37.313),(-121.9396,37.304)]
+Castle Park Way [(-122.1936,37.15),(-122.1933,37.151)]
+Castlewood Dr [(-121.8895,37.376),(-121.8902,37.373)]
+Castro St [(-122.1629,37.144),(-122.1636,37.141)]
+Castro St [(-122.2739,37.114),(-122.2735,37.121)]
+Castro St [(-122.2749,37.1),(-122.2746,37.103)]
+Castro St [(-122.2757,37.084),(-122.2753,37.091)]
+Castro St [(-122.2782,37.045),(-122.2778,37.052)]
+Castro St [(-122.2787,37.03),(-122.2785,37.038)]
+Castro Valley Blvd [(-122.0478,37.966),(-122.047,37.969)]
+Castro Valley Blvd [(-122.049131,37.96068),(-122.048358,37.96377)]
+Castro Valley Blvd [(-122.0604,37.92),(-122.0585,37.925)]
+Castro Valley Blvd [(-122.081,37.954),(-122.0801,37.955)]
+Castro Valley Blvd [(-122.086,37.939),(-122.0853,37.942)]
+Catalina Ave [(-122.2458,37.329),(-122.246102,37.33109)]
+Catalina Dr [(-121.7837,37.628),(-121.7834,37.628)]
+Catalina Dr [(-121.7892,37.66),(-121.7896,37.649)]
+Catalpa Way [(-122.0852,37.218),(-122.088,37.207)]
+Cato Ct [(-122.0691,37.944),(-122.0694,37.938)]
+Catron Dr [(-122.1716,37.27),(-122.1711,37.275)]
+Cavalier Lane [(-121.9361,37.175),(-121.9363,37.189)]
+Cavendish Dr [(-122.0477,37.158),(-122.0475,37.151)]
+Cavour St [(-122.2555,37.375),(-122.2561,37.379)]
+Cayuga Pl [(-121.9205,37.023),(-121.9218,37.02)]
+Cayuga Way [(-121.9233,37.013),(-121.9239,37.008)]
+Cedar Blvd [(-122.0214,37.402),(-122.0193,37.391)]
+Cedar Blvd [(-122.0282,37.446),(-122.0265,37.43)]
+Cedar Dr [(-121.7964,37.859),(-121.7941,37.858),(-121.7931,37.858)]
+Cedar Lane [(-121.9173,37.08),(-121.9183,37.083),(-121.9196,37.089),(-121.92,37.098)]
+Cedar St [(-121.9188,37.277),(-121.92,37.276)]
+Cedar St [(-122.260055,37.79558),(-122.2582,37.798)]
+Cedar St [(-122.2841,37.764),(-122.2818,37.766)]
+Cedar St [(-122.2864,37.76),(-122.2858,37.762)]
+Cedar St [(-122.2913,37.755),(-122.2905,37.756)]
+Cedar St [(-122.2945,37.75),(-122.2934,37.753)]
+Cedar St [(-122.3011,37.737),(-122.2999,37.739)]
+Cedar St [(-122.3043,37.729),(-122.3032,37.732)]
+Cedar St [(-122.3045,37.078),(-122.3041,37.087)]
+Cedarwood Lane [(-121.8746,37.706),(-121.8746,37.721)]
+Celia St [(-122.0611,37.3),(-122.0616,37.299)]
+Celia St [(-122.0623,37.297),(-122.0631,37.296)]
+Center St [(-122.0598,37.052),(-122.0593,37.046)]
+Center St [(-122.0599,37.111),(-122.06,37.106)]
+Center St [(-122.0606,37.968),(-122.0608,37.958)]
+Center St [(-122.2674,37.704),(-122.267,37.704)]
+Central Ave [(-122.0064,37.524),(-122.0079,37.505)]
+Central Ave [(-122.0118,37.455),(-122.0129,37.443)]
+Central Ave [(-122.0148,37.415),(-122.0157,37.404)]
+Central Ave [(-122.0302,37.226),(-122.0325,37.196)]
+Central Ave [(-122.0487,37.144),(-122.0452,37.158)]
+Central Ave [(-122.2309,37.579),(-122.2276,37.557)]
+Central Ave [(-122.2343,37.602),(-122.2331,37.595)]
+Central Ave [(-122.27,37.715),(-122.2685,37.714)]
+Central Ave [(-122.2787,37.718),(-122.2777,37.717),(-122.2762,37.717)]
+Central Ave [(-122.2906,37.769),(-122.2905,37.756)]
+Central Blvd [(-122.0643,37.553),(-122.0633,37.552),(-122.0622,37.545)]
+Central Blvd [(-122.0703,37.582),(-122.0697,37.586)]
+Cerrito St [(-122.3023,37.93),(-122.3018,37.918)]
+Cerro Vista Pl [(-121.73402,37.74008),(-121.735461,37.73955)]
+Chabolyn Ter [(-122.242959,37.5192),(-122.2429,37.519)]
+Chabot Ct [(-122.2432,37.489),(-122.2439,37.5)]
+Chabot Canal [(-121.9027,37.804),(-121.9027,37.812)]
+Chabot Canal [(-121.9036,37.013),(-121.9044,37.017)]
+Chabot Canal [(-121.9044,37.017),(-121.9037,37.02)]
+Chabot Crest [(-122.2425,37.504),(-122.2427,37.514)]
+Chambers Dr [(-122.2004,37.352),(-122.1972,37.368)]
+Chambers Lane [(-122.2001,37.359),(-122.1975,37.371)]
+Champagne Pl [(-121.950378,37.11376),(-121.949579,37.11174)]
+Champion St [(-122.214,37.991),(-122.2147,37.002)]
+Champion St [(-122.2146,37.977),(-122.2145,37.982)]
+Chance St [(-122.0536,37.259),(-122.0534,37.256)]
+Chandler Road [(-122.10851,37.48139),(-122.108787,37.48677)]
+Channel St [(-122.1372,37.71),(-122.1369,37.706)]
+Channing Way [(-122.2453,37.425),(-122.2466,37.408)]
+Channing Way [(-122.2638,37.664),(-122.2629,37.665),(-122.2606,37.669)]
+Channing Way [(-122.2695,37.657),(-122.2669,37.66)]
+Channing Way [(-122.2727,37.652),(-122.2717,37.653)]
+Channing Way [(-122.2806,37.641),(-122.2795,37.641)]
+Channing Way [(-122.2842,37.636),(-122.2835,37.637)]
+Channing Way [(-122.292641,37.62357),(-122.2925,37.624)]
+Chapel Ct [(-122.1508,37.897),(-122.1508,37.894)]
+Chapman Dr [(-122.0421,37.504),(-122.0414,37.498)]
+Chardonnay Dr [(-121.846993,37.64201),(-121.846448,37.64146)]
+Charles St [(-122.0255,37.505),(-122.0252,37.499)]
+Charlotte Way [(-121.7247,37.839),(-121.7246,37.836)]
+Charlotte Way [(-121.7261,37.856),(-121.7254,37.851)]
+Charlotte Way [(-121.733871,37.88044),(-121.734295,37.88051)]
+Charter Oaks Dr [(-122.0574,37.212),(-122.0568,37.22)]
+Chateau Way [(-121.7627,37.727),(-121.7627,37.723)]
+Chateau Park Ct [(-121.9658,37.196),(-121.966,37.193)]
+Chatsworth St [(-121.7766,37.502),(-121.7766,37.496)]
+Chaucer Dr [(-122.0339,37.813),(-122.0342,37.812)]
+Chaucer Dr [(-122.0357,37.794),(-122.0362,37.788)]
+Chelsea Dr [(-122.03044,37.5228),(-122.0303,37.522)]
+Chelsea Way [(-122.068,37.224),(-122.0686,37.223)]
+Chelton Dr [(-122.189,37.293),(-122.1887,37.304)]
+Chemult Com [(-121.9254,37.878),(-121.9255,37.881)]
+Cherry Lane [(-121.966799,37.63085),(-121.966874,37.63373)]
+Cherry St [(-122.0266,37.297),(-122.0258,37.294)]
+Cherry St [(-122.040954,37.37918),(-122.04,37.369)]
+Cherry St [(-122.0429,37.396),(-122.0424,37.392)]
+Cherry St [(-122.0437,37.42),(-122.0434,37.413)]
+Cherry St [(-122.1511,37.161),(-122.1503,37.149)]
+Cherry St [(-122.1671,37.488),(-122.1661,37.474)]
+Cherry St [(-122.1691,37.512),(-122.1684,37.502)]
+Cherrywood Dr [(-122.023,37.838),(-122.0237,37.82)]
+Cheryl Cir [(-121.8979,37.8),(-121.8957,37.794)]
+Cheryl Ann Cir [(-122.0754,37.352),(-122.076,37.358)]
+Chester St [(-122.0791,37.955),(-122.079,37.932)]
+Chester St [(-122.2949,37.07),(-122.2946,37.078)]
+Chestnut St [(-122.2482,37.733),(-122.2475,37.742)]
+Chestnut St [(-122.2853,37.069),(-122.2848,37.084)]
+Chestnut St [(-122.2873,37.722),(-122.2873,37.711)]
+Chetwood Ave [(-121.9591,37.232),(-121.960057,37.23087)]
+Chetwood St [(-122.2521,37.167),(-122.2513,37.169)]
+Cheyenne River Com [(-122.0521,37.779),(-122.0524,37.775)]
+Chiltern Dr [(-121.9414,37.433),(-121.9412,37.424)]
+Chiltern Dr [(-121.9447,37.457),(-121.9442,37.454)]
+Chimney Rock [(-122.13,37.701),(-122.12905,37.70195)]
+Chippendale Dr [(-122.0665,37.843),(-122.068,37.828)]
+Chisholm Ct [(-122.0773,37.42),(-122.077,37.409)]
+Choctaw Dr [(-121.9179,37.87),(-121.9172,37.876)]
+Chris Commons [(-121.73647,37.89437),(-121.736359,37.88665)]
+Chrisholm Pl [(-121.9599,37.726),(-121.9592,37.732)]
+Christensen Ct [(-122.0863,37.074),(-122.0863,37.065)]
+Christensen Lane [(-122.085026,37.06463),(-122.0844,37.064)]
+Christensen Road [(-121.625309,37.79774),(-121.621265,37.83993)]
+Christina Ct [(-121.8654,37.629),(-121.8651,37.616)]
+Christine Dr [(-122.0759,37.739),(-122.0756,37.734)]
+Christine St [(-122.0364,37.385),(-122.0357,37.379)]
+Christy St [(-121.9662,37.022),(-121.9658,37.019)]
+Church St [(-122.179,37.675),(-122.1785,37.678)]
+Cindy Lane [(-121.7346,37.8244),(-121.734673,37.83042)]
+Circle Way [(-121.9418,37.013),(-121.9422,37.02)]
+Citron Way [(-122.1008,37.461),(-122.1017,37.46)]
+Civic Terrace Ave [(-122.0251,37.389),(-122.0263,37.374)]
+Clara St [(-122.1855,37.377),(-122.1851,37.381)]
+Clara St [(-122.187,37.339),(-122.1865,37.357)]
+Claremont Ave [(-122.2429,37.607),(-122.2421,37.609)]
+Claremont Ave [(-122.243294,37.59318),(-122.2434,37.59)]
+Claremont Ave [(-122.246,37.565),(-122.2461,37.562)]
+Claremont Ave [(-122.2508,37.509),(-122.2505,37.514)]
+Claremont Ave [(-122.2591,37.408),(-122.2586,37.413)]
+Claremont Ave [(-122.2612,37.386),(-122.2604,37.393)]
+Claremont Pl [(-122.0542,37.995),(-122.0542,37.008)]
+Clarendon Cres [(-122.2278,37.126),(-122.2266,37.119)]
+Claret Road [(-121.757012,37.72568),(-121.757266,37.71391)]
+Clarewood Lane [(-122.2343,37.393),(-122.232365,37.38802)]
+Clarke Lane [(-122.236271,37.29202),(-122.236552,37.28906)]
+Clarke St [(-122.1568,37.225),(-122.1562,37.217)]
+Clausen Ct [(-122.040846,37.6758),(-122.040845,37.66913)]
+Clawiter Road [(-122.1186,37.321),(-122.1186,37.308)]
+Clawiter Road [(-122.1187,37.442),(-122.1188,37.435)]
+Clay St [(-122.2733,37.051),(-122.2729,37.059)]
+Clay St [(-122.2755,37.017),(-122.2751,37.024)]
+Clement Ave [(-122.2525,37.765),(-122.252147,37.76429)]
+Cleveland Ave [(-122.3061,37.895),(-122.3058,37.889)]
+Cleveland St [(-122.2435,37.048),(-122.2418,37.042)]
+Clifton St [(-122.2526,37.383),(-122.2533,37.388)]
+Clipper Dr [(-122.2438,37.42),(-122.2437,37.406)]
+Clover St [(-122.023,37.805),(-122.0217,37.801)]
+Clubhouse Dr [(-121.8179,37.971),(-121.8181,37.972)]
+Clubhouse Dr [(-122.121,37.67),(-122.1215,37.671)]
+Clubhouse Dr [(-122.1227,37.671),(-122.1234,37.67)]
+Cluny Pl [(-122.0438,37.432),(-122.0433,37.432)]
+Coach Dr [(-122.1383,37.735),(-122.1355,37.706)]
+Cobblestone Dr [(-122.00122,37.8492),(-122.000944,37.84795)]
+Coco Palm Dr [(-121.9905,37.311),(-121.991,37.305)]
+Codornices Creek [(-122.2986,37.83),(-122.2994,37.828)]
+Codornices Creek [(-122.3069,37.818),(-122.3074,37.817)]
+Cody Ct [(-121.9853,37.324),(-121.986,37.316)]
+Coit Ave [(-121.9244,37.346),(-121.9244,37.338)]
+Coit Ave [(-121.9245,37.352),(-121.924594,37.362)]
+Colby St [(-122.1282,37.959),(-122.1279,37.959)]
+Cold Water Dr [(-122.0403,37.068),(-122.041,37.069)]
+Cole Pl [(-122.057,37.343),(-122.0564,37.334)]
+Cole St [(-122.1974,37.724),(-122.1975,37.716)]
+Cole St [(-122.1975,37.749),(-122.1962,37.76)]
+Coleen St [(-121.79,37.763),(-121.7892,37.744)]
+Coleport Landing [(-122.2374,37.426),(-122.2378,37.42)]
+Coleport Landing [(-122.237889,37.41293),(-122.2379,37.412)]
+Colette St [(-122.063,37.46),(-122.0623,37.451)]
+Colette St [(-122.06565,37.4825),(-122.0646,37.479)]
+Colgate Dr [(-122.0271,37.94),(-122.0249,37.933)]
+Colgate St [(-122.1545,37.019),(-122.1538,37.014)]
+Colima Ct [(-122.0251,37.664),(-122.0245,37.661)]
+Coliseum Way [(-122.19759,37.4533),(-122.1948,37.444)]
+Coliseum Way [(-122.2001,37.47),(-122.1978,37.516)]
+Coliseum Way [(-122.2113,37.626),(-122.2085,37.592),(-122.2063,37.568)]
+College Ave [(-121.7675,37.745),(-121.7658,37.745)]
+College Ave [(-121.7693,37.744),(-121.769,37.744)]
+College Ave [(-122.2506,37.367),(-122.2508,37.374)]
+College Ave [(-122.2511,37.421),(-122.2512,37.429)]
+College Ave [(-122.2516,37.474),(-122.2518,37.483)]
+Collier Dr [(-122.1409,37.299),(-122.14,37.302)]
+Colonial Loma Verde Dr [(-122.1184,37.849),(-122.117278,37.85528)]
+Colony Ct [(-122.061894,37.27773),(-122.061805,37.27497)]
+Colorados Dr [(-122.1757,37.281),(-122.1748,37.301)]
+Columbia Dr [(-122.0574,37.168),(-122.0568,37.183)]
+Columbia Dr [(-122.057463,37.28811),(-122.057463,37.28593)]
+Columbian Dr [(-122.1635,37.727),(-122.1627,37.734)]
+Columbine Pl [(-122.0122,37.321),(-122.013,37.33)]
+Columbus Ave [(-121.776,37.679),(-121.7753,37.682)]
+Columbus Ave [(-121.7786,37.68),(-121.7777,37.681)]
+Colusa Ave [(-122.2786,37.835),(-122.2784,37.831)]
+Colusa Ave [(-122.2794,37.922),(-122.2793,37.916)]
+Colusa Ave [(-122.2812,37.943),(-122.2805,37.939)]
+Colusa Ave [(-122.2847,37.973),(-122.2846,37.967)]
+Colville Pl [(-122.0419,37.709),(-122.0402,37.702)]
+Concannon Blvd [(-121.7804,37.608),(-121.779,37.608)]
+Concord St [(-121.8551,37.602),(-121.856,37.593)]
+Constitution Dr [(-121.816179,37.01178),(-121.816179,37.01023)]
+Contra Costa Ave [(-122.0153,37.551),(-122.0141,37.545)]
+Contra Costa Ave [(-122.2754,37.944),(-122.2754,37.918)]
+Contreras Pl [(-122.0386,37.553),(-122.0387,37.55)]
+Conway Ter [(-122.044884,37.64441),(-122.04466,37.64514)]
+Coolidge Ave [(-122.2007,37.058),(-122.1992,37.06)]
+Coolidge Ave [(-122.2104,37.957),(-122.2099,37.962)]
+Coolidge Ave [(-122.2171,37.872),(-122.2169,37.875)]
+Coral Road [(-122.1907,37.34),(-122.1902,37.334)]
+Core Ter [(-122.047353,37.65391),(-122.047303,37.65185)]
+Corey Way [(-122.0699,37.054),(-122.0698,37.046)]
+Cormorant Ter [(-122.045468,37.80753),(-122.044829,37.80392)]
+Cornell Ave [(-122.2956,37.925),(-122.2949,37.906),(-122.2939,37.875)]
+Corning Ct [(-122.0689,37.688),(-122.0685,37.68)]
+Cornish Dr [(-122.0228,37.75),(-122.0225,37.754)]
+Coronado Lane [(-121.9026,37.843),(-121.9028,37.843)]
+Corral Hollow Creek [(-121.590572,37.01116),(-121.599735,37.10676)]
+Corriea Way [(-121.9501,37.402),(-121.9505,37.398)]
+Corte de Flores [(-121.908126,37.71073),(-121.90924,37.71391)]
+Corte Eulalia [(-122.1142,37.78),(-122.1154,37.776)]
+Corte Munras [(-121.900576,37.74452),(-121.900804,37.74889)]
+Corte Vera Cruz [(-121.9036,37.639),(-121.9038,37.642)]
+Corte Yolanda [(-122.1426,37.753),(-122.1423,37.749)]
+Cortland Way [(-121.7892,37.934),(-121.7885,37.939)]
+Corvair St [(-122.2126,37.364),(-122.2132,37.36)]
+Corvallis St [(-122.1527,37.974),(-122.1521,37.97)]
+Cosgrave Ave [(-122.1621,37.62),(-122.1616,37.626)]
+Cosmic Way [(-121.9659,37.423),(-121.9666,37.414)]
+Cottage St [(-122.2593,37.713),(-122.258931,37.71803)]
+Cotter Way [(-122.0904,37.818),(-122.0882,37.829)]
+Cotton Ct [(-122.0462,37.123),(-122.0469,37.117)]
+Cottonwood St [(-121.9116,37.732),(-121.9115,37.725)]
+Cottonwood St [(-121.912,37.74),(-121.9118,37.735)]
+Country Dr [(-121.9903,37.52),(-121.9916,37.506)]
+Court St [(-122.2331,37.583),(-122.2324,37.589),(-122.2314,37.598)]
+Courtland Ave [(-122.2041,37.801),(-122.2032,37.815)]
+Courtland Ave [(-122.2084,37.76),(-122.2068,37.772)]
+Cove Road [(-122.2468,37.425),(-122.2474,37.408)]
+Covington Way [(-121.7935,37.936),(-121.7911,37.942)]
+Cowing Road [(-122.0002,37.934),(-121.9772,37.782)]
+Cowper St [(-122.2908,37.673),(-122.2894,37.675)]
+Coyote Hills Slough [(-122.0904,37.85),(-122.0953,37.829)]
+Coyote Hills Slough [(-122.1075,37.687),(-122.1285,37.643)]
+Coyote River [(-121.931582,37.60707),(-121.932309,37.60824)]
+Coyote River [(-121.9505,37.629),(-121.9582,37.646)]
+Coyote River [(-121.9746,37.617),(-121.9863,37.648)]
+Cragmont Ave [(-122.2616,37.921),(-122.2603,37.911)]
+Cragmont Ave [(-122.266,37.95),(-122.2656,37.943)]
+Craig St [(-121.9869,37.61),(-121.9864,37.601)]
+Crane Ave [(-122.0578,37.103),(-122.058,37.086)]
+Creed Road [(-122.2249,37.094),(-122.2256,37.101)]
+Creekside Dr [(-121.924308,37.89385),(-121.925368,37.89008)]
+Creekside Dr [(-121.926024,37.88774),(-121.926337,37.88663)]
+Creekside Ter [(-121.997958,37.64593),(-121.998047,37.63504)]
+Creekwood Dr [(-122.043309,37.66911),(-122.041422,37.66099)]
+Crellin Road [(-121.846446,37.59189),(-121.846,37.591),(-121.845775,37.59073)]
+Crest Ave [(-122.1039,37.067),(-122.1038,37.066)]
+Crest Ave [(-122.162,37.699),(-122.1568,37.664)]
+Crest Ct [(-122.0566,37.049),(-122.0571,37.05)]
+Crest Lane [(-122.0558,37.047),(-122.0546,37.047)]
+Crest Road [(-122.2149,37.216),(-122.2153,37.221)]
+Crestline Road [(-121.887,37.789),(-121.8846,37.793)]
+Crestmont Dr [(-122.1775,37.029),(-122.1798,37.044)]
+Creston Road [(-122.2639,37.002),(-122.2613,37.986),(-122.2602,37.978),(-122.2598,37.973)]
+Crestwood St [(-121.9589,37.159),(-121.961,37.156)]
+Crisfield Lane [(-121.871,37.814),(-121.8718,37.813)]
+Crocker Ave [(-122.2242,37.186),(-122.2243,37.171)]
+Crockwood Ter [(-122.045255,37.66569),(-122.045487,37.66262)]
+Cromwell Way [(-121.7723,37.932),(-121.7713,37.933)]
+Crosby St [(-122.146,37.96),(-122.145,37.953)]
+Cross Road [(-121.666843,37.7387),(-121.664768,37.7474)]
+Crow Ct [(-121.8797,37.911),(-121.8801,37.91)]
+Crow Canyon Road [(-122.0106,37.674),(-122.0102,37.675)]
+Crow Canyon Road [(-122.0497,37.029),(-122.0479,37.028)]
+Crow Canyon Road [(-122.0552,37.938),(-122.0545,37.967)]
+Crow Canyon Creek [(-122.0425,37.051),(-122.0426,37.049)]
+Crow Canyon Creek [(-122.043,37.905),(-122.0368,37.71)]
+Crow Canyon Creek [(-122.046308,37.0015),(-122.046833,37.00133)]
+Croxton Ave [(-122.2591,37.219),(-122.2584,37.211)]
+Cryer St [(-122.1024,37.357),(-122.1035,37.351)]
+Crystal Lane [(-121.868866,37.50763),(-121.870709,37.51024)]
+Crystaline Dr [(-121.925856,37),(-121.925869,37.00527)]
+Cull Canyon Road [(-122.0536,37.435),(-122.0499,37.315)]
+Cull Canyon Reservoir [(-122.0546,37.039),(-122.0553,37.089)]
+Cull Creek [(-122.0624,37.875),(-122.0582,37.527)]
+Culver St [(-122.1998,37.865),(-122.1996,37.862)]
+Cumberland Ave [(-122.1467,37.945),(-122.1507,37.944)]
+Curran Way [(-122.2074,37.96),(-122.207,37.966)]
+Curtis St [(-121.9765,37.246),(-121.9778,37.229)]
+Curtis St [(-122.2866,37.981),(-122.2866,37.968),(-122.2867,37.949)]
+Curtis St [(-122.2877,37.65),(-122.2877,37.639)]
+Curtis St [(-122.2881,37.848),(-122.2883,37.831)]
+Curtner Road [(-121.909,37.928),(-121.9084,37.928)]
+Curtner Road [(-121.9117,37.939),(-121.9105,37.93)]
+Cutler Ave [(-122.013942,37.74913),(-122.0142,37.745)]
+Cypress St [(-122.2883,37.177),(-122.2884,37.184)]
+Cypress St [(-122.2886,37.241),(-122.2883,37.247)]
+Cypress St [(-122.2899,37.142),(-122.2894,37.156)]
+Cypress St [(-122.2908,37.104),(-122.2905,37.113)]
+Cypress St [(-122.2931,37.047),(-122.2928,37.055)]
+Cypress Point Dr [(-121.7251,37.24),(-121.724,37.24)]
+D St [(-122.0239,37.017),(-122.0242,37.015)]
+D St [(-122.0529,37.421),(-122.0534,37.42)]
+D St [(-122.055,37.798),(-122.0541,37.796),(-122.0529,37.794)]
+D St [(-122.056892,37.79896),(-122.0564,37.8)]
+D St [(-122.0746,37.745),(-122.0741,37.749)]
+D St [(-122.179,37.476),(-122.1785,37.47)]
+D St [(-122.1811,37.505),(-122.1805,37.497)]
+Dagnino Road [(-121.7462,37.306),(-121.7461,37.379)]
+Daisy St [(-122.1817,37.843),(-122.18,37.848),(-122.179,37.851)]
+Daisy St [(-122.1857,37.858),(-122.185,37.851)]
+Dalgo Road [(-121.947,37.529),(-121.9475,37.524)]
+Dalton Way [(-122.0293,37.927),(-122.0297,37.915)]
+Dalton Com [(-121.994,37.555),(-121.9944,37.556)]
+Damon Slough [(-122.2057,37.533),(-122.2063,37.531)]
+Dana St [(-122.2578,37.501),(-122.2579,37.507)]
+Dana St [(-122.2583,37.548),(-122.2582,37.541)]
+Daniels Dr [(-122.1346,37.317),(-122.1335,37.323)]
+Darius Way [(-122.1272,37.18),(-122.1267,37.164)]
+Darwin Dr [(-122.0335,37.776),(-122.0349,37.767)]
+Darwin Dr [(-122.0359,37.763),(-122.0366,37.755)]
+Darwin St [(-122.0996,37.317),(-122.1024,37.311)]
+Daryl Ave [(-122.115,37.883),(-122.1149,37.866)]
+Dashwood Ave [(-122.177,37.618),(-122.1755,37.627)]
+Davenport Ave [(-122.1827,37.892),(-122.1823,37.872)]
+David St [(-122.0637,37.958),(-122.0608,37.958)]
+Davis St [(-122.1624,37.225),(-122.1632,37.222),(-122.1647,37.218)]
+Davis St [(-122.1702,37.203),(-122.171,37.201)]
+Davis St [(-122.1719,37.199),(-122.1725,37.198)]
+Davis St [(-122.1831,37.165),(-122.1835,37.165)]
+Davis St [(-122.1857,37.158),(-122.1921,37.139)]
+Davis St [(-122.217903,37.89337),(-122.216,37.885)]
+Davona Dr [(-121.9261,37.222),(-121.9278,37.218)]
+Davy Ct [(-121.9902,37.629),(-121.99,37.623)]
+Dawe Ave [(-122.0783,37.927),(-122.0783,37.912)]
+Dawes St [(-122.2122,37.24),(-122.2112,37.226)]
+Dawn View Ct [(-122.041,37.144),(-122.0403,37.143)]
+Dayle Ct [(-121.943,37.37),(-121.9421,37.369)]
+De Brum Commons [(-121.924934,37.30872),(-121.924728,37.30014)]
+De la Cruz Road [(-122.0554,37.305),(-122.055,37.301)]
+Dearborn St [(-122.0274,37.107),(-122.0275,37.101)]
+Decatur Way [(-122.0868,37.296),(-122.0863,37.267)]
+Decoto Road [(-122.0159,37.006),(-122.016,37.002),(-122.0164,37.993)]
+Decoto Road [(-122.0175,37.961),(-122.0177,37.955)]
+Decoto Road [(-122.0189,37.925),(-122.019,37.921)]
+Decoto Road [(-122.0292,37.733),(-122.0315,37.707)]
+Decoto Road [(-122.0361,37.656),(-122.0364,37.652)]
+Deep Creek Road [(-122.049391,37.64053),(-122.04928,37.63888)]
+Deep Creek Road [(-122.0533,37.749),(-122.053709,37.74218)]
+Deep Creek Road [(-122.0536,37.697),(-122.0517,37.674),(-122.0513,37.668)]
+Deer Oaks Dr [(-121.909133,37.54092),(-121.907389,37.54544)]
+Deer Park Way [(-122.0279,37.526),(-122.0266,37.524)]
+Deer Trail Pl [(-122.0455,37.669),(-122.044472,37.66938)]
+Deering St [(-122.2146,37.904),(-122.2126,37.897)]
+Deervale Road [(-121.9376,37.178),(-121.9374,37.184)]
+Deerwood St [(-122.1775,37.623),(-122.177,37.618)]
+Del Valle Pkwy [(-121.875441,37.66004),(-121.874759,37.65728)]
+Del Valle Road [(-121.688828,37.70896),(-121.691152,37.7458)]
+Delaware Dr [(-121.951,37.203),(-121.9517,37.201)]
+Delaware St [(-122.2016,37.926),(-122.2015,37.925)]
+Delaware Way [(-121.786362,37.92959),(-121.7863,37.93)]
+Delmar Ave [(-121.7889,37.693),(-121.787,37.704)]
+Delmar Ave [(-122.2453,37.545),(-122.2444,37.556)]
+Delores Dr [(-122.0742,37.735),(-122.0752,37.727)]
+Delta Ter [(-121.9592,37.113),(-121.959,37.111)]
+Delta Mendota Canal [(-121.562031,37.5571),(-121.573749,37.57374)]
+Delta Mendota Canal [(-121.57819,37.7187),(-121.578403,37.71976)]
+Delta Mendota Canal [(-121.589243,37.84355),(-121.589222,37.84389)]
+Denise Ct [(-121.9418,37.142),(-121.9414,37.127)]
+Denise St [(-121.9469,37.37),(-121.945,37.359)]
+Denker Dr [(-121.9086,37.861),(-121.9082,37.863),(-121.9079,37.864)]
+Dennison St [(-122.2379,37.796),(-122.2374,37.796)]
+Dennison St [(-122.2428,37.791),(-122.2412,37.793),(-122.239,37.795)]
+Denslowe St [(-122.1851,37.336),(-122.1847,37.332)]
+Denton Ave [(-122.1118,37.467),(-122.108659,37.47689)]
+Denton Ave [(-122.1118,37.467),(-122.112277,37.46666)]
+Depot Road [(-122.127518,37.3808),(-122.1284,37.38)]
+Depot Road [(-122.1302,37.38),(-122.1323,37.379)]
+Derby St [(-122.246,37.628),(-122.2451,37.629)]
+Derby St [(-122.2617,37.612),(-122.2595,37.615)]
+Derby St [(-122.2688,37.602),(-122.2663,37.606)]
+Dering Pl [(-122.0192,37.701),(-122.0183,37.693)]
+Devonshire Ave [(-122.1466,37.926),(-122.1498,37.924)]
+Devonshire Ave [(-122.1507,37.924),(-122.1517,37.924)]
+Dewey St [(-122.1483,37.862),(-122.1482,37.857)]
+Diablo Ave [(-122.1186,37.358),(-122.1236,37.358)]
+Diablo Pl [(-122.0543,37.973),(-122.0534,37.974)]
+Diamond Dr [(-121.8008,37.669),(-121.7988,37.682)]
+Diana Common [(-122.056116,37.61386),(-122.056337,37.62065)]
+Dichondra Pl [(-122.009872,37.31128),(-122.009205,37.31438)]
+Dillo St [(-122.1303,37.024),(-122.1303,37.016)]
+Dimond Ave [(-122.2167,37.994),(-122.2162,37.006)]
+Dixon St [(-122.0524,37.32),(-122.0514,37.311)]
+Doane St [(-121.9531,37.149),(-121.9562,37.14)]
+Doane St [(-121.9581,37.135),(-121.9597,37.13)]
+Dobbel Ave [(-122.0319,37.46),(-122.0328,37.463)]
+Dobbel Ave [(-122.0417,37.52),(-122.0408,37.515)]
+Dohr St [(-122.2805,37.56),(-122.2804,37.548)]
+Dolores Ave [(-122.1517,37.224),(-122.1498,37.229)]
+Dolores Dr [(-121.8732,37.524),(-121.8724,37.519)]
+Dolores Dr [(-121.875651,37.52705),(-121.87384,37.52324)]
+Dolores St [(-122.078,37.842),(-122.0779,37.833)]
+Dominici Dr [(-122.0149,37.756),(-122.0151,37.75)]
+Donalban Cir [(-122.0464,37.71),(-122.0475,37.709)]
+Donegal Ct [(-122.0433,37.549),(-122.0421,37.543)]
+Donlan Canyon Creek [(-121.9687,37.097),(-121.9584,37.101)]
+Donna Way [(-122.1333,37.606),(-122.1316,37.599)]
+Donner Way [(-121.9969,37.37),(-121.9945,37.357)]
+Donohue Dr [(-121.932,37.091),(-121.9327,37.106)]
+Doolittle Dr [(-122.174643,37.98341),(-122.1735,37.965)]
+Doolittle Dr [(-122.1793,37.052),(-122.1787,37.043)]
+Doolittle Dr [(-122.193162,37.21747),(-122.193,37.216)]
+Doolittle Dr [(-122.224088,37.448),(-122.2226,37.448)]
+Dorisa Ave [(-122.1462,37.555),(-122.1448,37.572)]
+Dorman Road [(-121.9061,37.834),(-121.9063,37.841)]
+Dorne Pl [(-121.9455,37.397),(-121.9456,37.392)]
+Dorothy Pl [(-122.2301,37.566),(-122.2302,37.571)]
+Dorsey Ave [(-121.9992,37.495),(-121.9997,37.489)]
+Dorthea Ct [(-121.9064,37.519),(-121.908617,37.52369)]
+Dougherty Road [(-121.908447,37.2552),(-121.90838,37.2379)]
+Dougherty Road [(-121.9093,37.127),(-121.9093,37.15418)]
+Dougherty Road [(-121.909939,37.28669),(-121.909373,37.30234),(-121.9092,37.305)]
+Douglas Dr [(-122.1705,37.227),(-122.1698,37.214)]
+Dover Way [(-121.7929,37.906),(-121.7917,37.906)]
+Dowe Ave [(-122.0439,37.903),(-122.04363,37.91098)]
+Dowling Blvd [(-122.1467,37.359),(-122.1447,37.359)]
+Dowling Blvd [(-122.1552,37.323),(-122.1542,37.335)]
+Downing Pl [(-122.0549,37.035),(-122.0546,37.041)]
+Doyle St [(-122.2835,37.337),(-122.2836,37.344)]
+Drake Dr [(-122.2056,37.311),(-122.2043,37.302)]
+Drew St [(-122.1288,37.938),(-122.1281,37.939)]
+Drew Ter [(-121.956729,37.21923),(-121.956991,37.21808)]
+Driftwood Dr [(-122.0109,37.482),(-122.0113,37.477)]
+Driftwood Way [(-121.919,37.794),(-121.92,37.795)]
+Driftwood Way [(-122.1726,37.924),(-122.1734,37.924)]
+Driscoll Road [(-121.9482,37.403),(-121.948451,37.39995)]
+Dry Creek [(-121.706461,37.12006),(-121.708998,37.15525)]
+Dry Creek [(-122.0367,37.88386),(-122.038,37.877)]
+Dublin Blvd [(-121.9096,37.047),(-121.9115,37.034)]
+Dublin Blvd [(-121.945852,37.98712),(-121.944625,37.99187)]
+Dublin Road [(-121.955087,37.97529),(-121.946646,37.94858)]
+Dublin Way [(-122.251,37.425),(-122.2513,37.43)]
+Dublin Canyon Road [(-121.94428,37.95399),(-121.943721,37.95527)]
+Dublin Creek [(-121.9422,37.974),(-121.955,37.984)]
+Dublin Green Dr [(-121.939669,37.10516),(-121.9384,37.097)]
+Dudley Ave [(-122.222,37.241),(-122.2221,37.234)]
+Duffel Pl [(-122.0641,37.434),(-122.0637,37.429)]
+Dunbar Pl [(-122.011,37.632),(-122.0121,37.621)]
+Dundee Ct [(-122.1458,37.166),(-122.1451,37.16)]
+Dunkirk Ave [(-122.1254,37.526),(-122.1228,37.531)]
+Dunn Road [(-122.1195,37.452),(-122.1211,37.451)]
+Durant Ave [(-122.2603,37.678),(-122.2584,37.68)]
+Durant Ave [(-122.2671,37.669),(-122.2652,37.67)]
+Durham Road [(-121.9216,37.146),(-121.9234,37.149)]
+Durham Road [(-121.957278,37.10033),(-121.957885,37.09857)]
+Durham Road [(-121.9605,37.091),(-121.9616,37.087)]
+Durham Road [(-121.9634,37.081),(-121.9642,37.078)]
+Durham Way [(-122.0321,37.656),(-122.0311,37.642)]
+Durillo Dr [(-121.9464,37.446),(-121.9457,37.441)]
+Durk Way [(-122.097393,37.57955),(-122.097541,37.58144)]
+Dusterberry Way [(-122.0141,37.565),(-122.0135,37.562)]
+Dutton Ave [(-122.1403,37.339),(-122.1394,37.34)]
+Dutton Ave [(-122.155,37.319),(-122.1534,37.323)]
+Dwight Way [(-122.2483,37.662),(-122.2472,37.661)]
+Dwight Way [(-122.2546,37.657),(-122.2533,37.659)]
+Dwight Way [(-122.2573,37.654),(-122.256,37.656)]
+Dwight Way [(-122.286,37.616),(-122.2851,37.617)]
+Dwight Way [(-122.2933,37.602),(-122.292,37.605),(-122.291,37.607)]
+Dyer St [(-122.0684,37.028),(-122.0691,37.005)]
+Dyer St [(-122.0701,37.969),(-122.0703,37.965)]
+E St [(-122.019,37.037),(-122.0195,37.032),(-122.0203,37.027),(-122.0208,37.022)]
+E St [(-122.1832,37.505),(-122.1826,37.498),(-122.182,37.49)]
+Eagle Ave [(-122.2342,37.668),(-122.2335,37.664)]
+Eagle Ave [(-122.2539,37.76),(-122.253215,37.75657)]
+Earhart Road [(-122.2004,37.256),(-122.2004,37.259)]
+Earhart Road [(-122.2086,37.296),(-122.2012,37.255)]
+Earhart Road [(-122.2186,37.394),(-122.2181,37.389)]
+Earl St [(-122.1564,37.709),(-122.1552,37.697)]
+East Ave [(-121.7203,37.799),(-121.7176,37.801)]
+East Ave [(-121.7296,37.8),(-121.7251,37.8)]
+East Ave [(-121.7424,37.799),(-121.7416,37.799)]
+East Ave [(-121.7597,37.798),(-121.7558,37.8)]
+East Ave [(-121.7641,37.8),(-121.763,37.8)]
+East Ave [(-122.0361,37.719),(-122.0352,37.733)]
+Easterday Way [(-121.9916,37.829),(-121.9899,37.814)]
+Eastlawn St [(-122.1989,37.629),(-122.1982,37.623)]
+Eastlawn St [(-122.2005,37.644),(-122.2,37.638)]
+Eastman Ave [(-122.20024,37.86217),(-122.1998,37.865)]
+Eastman Ave [(-122.2028,37.845),(-122.2012,37.856)]
+Eastman Ct [(-122.0732,37.492),(-122.0722,37.486)]
+Eastpark Ter [(-122.041182,37.77686),(-122.042271,37.78226)]
+Eastshore Hwy [(-122.3057,37.785),(-122.3051,37.755)]
+Ebbetts St [(-122.0061,37.205),(-122.005,37.203)]
+Eden Ave [(-122.1143,37.505),(-122.1142,37.491)]
+Eden Creek [(-122.0218,37.996),(-122.0222,37.961)]
+Eden Creek [(-122.022037,37.00675),(-122.0221,37.998)]
+Eden Landing Road [(-122.1204,37.268),(-122.1204,37.267)]
+Eden Landing Road [(-122.1213,37.226),(-122.1213,37.223)]
+Edenberry St [(-121.9347,37.23),(-121.9349,37.235)]
+Edes Ave [(-122.1833,37.363),(-122.1821,37.359)]
+Edes Ave [(-122.1948,37.444),(-122.1941,37.444)]
+Edgebrook Dr [(-122.057639,37.71523),(-122.057506,37.72472)]
+Edgehill Ct [(-122.1305,37.239),(-122.1304,37.232)]
+Edgemoor St [(-122.1492,37.918),(-122.1492,37.91)]
+Edgewater Dr [(-122.0285,37.526),(-122.028611,37.52295)]
+Edgewater Dr [(-122.0306,37.542),(-122.0298,37.538)]
+Edgewater Dr [(-122.0327,37.552),(-122.0315,37.546)]
+Edgewater Dr [(-122.0387,37.579),(-122.038315,37.57785)]
+Edgewater Dr [(-122.0401,37.57),(-122.0405,37.565)]
+Edgewater Dr [(-122.201,37.379),(-122.2042,37.41)]
+Edison Way [(-121.9443,37.098),(-121.9473,37.084)]
+Edith St [(-122.02,37.43),(-122.0197,37.429)]
+Edith St [(-122.077,37.638),(-122.0764,37.631)]
+Edith St [(-122.2764,37.774),(-122.2763,37.765)]
+Edith Way [(-122.0733,37.825),(-122.0716,37.804)]
+Edwards Ave [(-122.1652,37.749),(-122.1638,37.759)]
+Edwards Lane [(-122.0599,37.016),(-122.058514,37.01551)]
+Eggers Ct [(-121.9924,37.585),(-121.991,37.576)]
+Eggers Dr [(-122.0002,37.465),(-122.0017,37.451)]
+Ehle St [(-122.103,37.973),(-122.1017,37.959)]
+Eilene Dr [(-121.869254,37.87019),(-121.869113,37.87253)]
+Eilene Dr [(-121.869618,37.86592),(-121.869389,37.86861)]
+El Caminito [(-121.7883,37.673),(-121.7852,37.67)]
+El Caminito [(-121.7922,37.696),(-121.7907,37.685),(-121.7898,37.681)]
+El Camino Real [(-122.2409,37.559),(-122.2407,37.556)]
+El Centro Ave [(-122.2191,37.116),(-122.2185,37.118)]
+El Dorado Dr [(-121.7803,37.659),(-121.7801,37.647)]
+El Monte Ave [(-122.1594,37.624),(-122.1591,37.63)]
+El Paseo [(-122.244718,37.33016),(-122.2454,37.333)]
+El Portal [(-122.2429,37.359),(-122.2434,37.361)]
+El Portal Ave [(-121.9993,37.83),(-121.9997,37.817)]
+El Portal Ct [(-122.2554,37.844),(-122.2552,37.85)]
+El Rey Ave [(-122.0219,37.536),(-122.0225,37.53)]
+Elba Way [(-121.9223,37.143),(-121.923,37.139)]
+Elbert St [(-122.2205,37.107),(-122.2195,37.095)]
+Elder Way [(-122.0819,37.229),(-122.0827,37.229)]
+Eldridge Ave [(-122.0883,37.402),(-122.0878,37.395)]
+Eldridge Ave [(-122.0896,37.423),(-122.0888,37.408)]
+Elgin St [(-122.1106,37.911),(-122.1102,37.911)]
+Elgin St [(-122.113,37.911),(-122.1119,37.911)]
+Elgin St [(-122.1222,37.95),(-122.1213,37.945)]
+Elk Ct [(-121.912463,37.08667),(-121.913575,37.09125)]
+Elko Ct [(-122.1499,37.778),(-122.1496,37.773)]
+Ellen Way [(-122.0685,37.83),(-122.0687,37.828)]
+Ellen Way [(-122.0711,37.801),(-122.0716,37.796)]
+Ellis St [(-122.2726,37.542),(-122.2723,37.524)]
+Ellsworth St [(-122.2611,37.575),(-122.2608,37.57)]
+Elm St [(-121.7815,37.865),(-121.7807,37.865)]
+Elm St [(-122.04,37.27),(-122.0392,37.254)]
+Elmhurst St [(-122.0916,37.568),(-122.0918,37.564)]
+Elmridge Ct [(-121.8867,37.707),(-121.8871,37.702)]
+Elsie Ave [(-122.1497,37.21),(-122.1447,37.223)]
+Elverton Dr [(-122.2028,37.487),(-122.2017,37.471)]
+Elvessa St [(-122.1296,37.528),(-122.129,37.522)]
+Elysian Fields Dr [(-122.1275,37.646),(-122.1275,37.649)]
+Elysian Fields Dr [(-122.1336,37.595),(-122.133,37.594)]
+Elysian Fields Dr [(-122.1369,37.589),(-122.1361,37.595)]
+Embarcadero [(-122.2527,37.894),(-122.246486,37.86908)]
+Embarcadero [(-122.276,37.961),(-122.2747,37.956)]
+Embarcadero [(-122.2836,37.99),(-122.283,37.989)]
+Emerald Ave [(-121.9247,37.13),(-121.925,37.136)]
+Emerald St [(-122.2557,37.309),(-122.2557,37.315)]
+Emerson St [(-121.9276,37.345),(-121.9284,37.347)]
+Emerson St [(-122.2688,37.543),(-122.2678,37.544)]
+Emily Ct [(-122.0726,37.121),(-122.0712,37.124)]
+Empire Road [(-122.1873,37.278),(-122.1858,37.277)]
+Encinitas Way [(-122.0759,37.917),(-122.0756,37.911)]
+Encino Dr [(-121.7959,37.688),(-121.7947,37.688)]
+Endeavour Way [(-122.0655,37.842),(-122.0647,37.84)]
+Endicott St [(-122.1436,37.931),(-122.1435,37.911)]
+Enfield Dr [(-122.0273,37.519),(-122.0277,37.507)]
+Enos Way [(-121.7677,37.896),(-121.7673,37.91)]
+Ensenada Ave [(-122.2833,37.933),(-122.2825,37.928)]
+Enterprise Pl [(-121.9568,37.009),(-121.9558,37.002)]
+Erica Pl [(-122.0299,37.165),(-122.0293,37.169)]
+Erie St [(-122.2419,37.122),(-122.2408,37.134)]
+Escher Dr [(-122.1962,37.258),(-122.1988,37.302)]
+Essex St [(-122.2677,37.537),(-122.2657,37.539)]
+Essex Way [(-121.9735,37.257),(-121.9749,37.249)]
+Estabrook St [(-122.1527,37.164),(-122.1535,37.161)]
+Estabrook St [(-122.1539,37.16),(-122.1547,37.156)]
+Estates Dr [(-122.2136,37.158),(-122.2126,37.169)]
+Estates Dr [(-122.2139,37.272),(-122.2135,37.26)]
+Estates Dr [(-122.2178,37.33),(-122.2183,37.325)]
+Estates Dr [(-122.2225,37.37),(-122.218617,37.34167)]
+Estudillo Ave [(-122.1446,37.274),(-122.1425,37.28)]
+Estudillo Ave [(-122.1509,37.256),(-122.1498,37.26)]
+Estudillo Ave [(-122.1547,37.245),(-122.1551,37.244)]
+Estudillo Ave [(-122.1584,37.229),(-122.1595,37.225)]
+Estudillo Ave [(-122.1608,37.22),(-122.1621,37.214)]
+Estudillo Canal [(-122.1282,37.969),(-122.1288,37.964)]
+Estudillo Canal [(-122.1323,37.929),(-122.1363,37.924),(-122.1369,37.924)]
+Estudillo Canal [(-122.1397,37.923),(-122.14066,37.92204)]
+Estudillo Canal [(-122.1569,37.861),(-122.1587,37.858)]
+Eucalyptus Ct [(-122.064324,37.37425),(-122.064548,37.37733)]
+Euclid Ave [(-122.2618,37.893),(-122.2612,37.887)]
+Euclid Ave [(-122.2627,37.907),(-122.2621,37.902)]
+Euclid Ave [(-122.2632,37.942),(-122.2644,37.93)]
+Euclid Ave [(-122.2671,37.009),(-122.2666,37.987)]
+Eugene St [(-121.963937,37.38628),(-121.9642,37.383)]
+Eugene St [(-121.9659,37.364),(-121.9664,37.358)]
+Evans St [(-121.7778,37.607),(-121.7777,37.602)]
+Evelyn Ave [(-122.2906,37.814),(-122.2903,37.807)]
+Evelyn Ave [(-122.2919,37.843),(-122.2909,37.83)]
+Everett Ave [(-122.2182,37.02),(-122.2172,37.05)]
+Everett Ave [(-122.219,37.094),(-122.218,37.087)]
+Everett St [(-122.236,37.678),(-122.2353,37.687)]
+Everett St [(-122.2385,37.647),(-122.238,37.653)]
+Everglade St [(-122.0822,37.275),(-122.083,37.272)]
+Everglades Lane [(-121.8005,37.87),(-121.800259,37.87469)]
+Everglades Park Dr [(-121.9703,37.175),(-121.9718,37.164)]
+Evergreen Ave [(-122.1367,37.234),(-122.1361,37.224)]
+Evergreen St [(-122.0877,37.458),(-122.087,37.455)]
+Excelsior Ave [(-122.2224,37.019),(-122.2215,37.016)]
+Excelsior Ave [(-122.2338,37.059),(-122.2312,37.051)]
+F St [(-122.0191,37.017),(-122.0198,37.013)]
+F St [(-122.0223,37.993),(-122.0225,37.993)]
+F St [(-122.0246,37.976),(-122.0247,37.974)]
+F Bay [(-122.152,37.358),(-122.150473,37.24998)]
+F Bay [(-122.2842,37.681),(-122.2841,37.68)]
+F Bay [(-122.3138,37.645),(-122.3154,37.643)]
+Faber St [(-122.0694,37.105),(-122.075,37.106)]
+Fabian Way [(-122.0703,37.27),(-122.0684,37.269)]
+Fabian Com [(-121.9951,37.537),(-121.9947,37.534)]
+Fair Ave [(-122.0298,37.395),(-122.0304,37.386)]
+Fairbrook Ct [(-121.9207,37.829),(-121.9218,37.826)]
+Fairfax Ave [(-122.1997,37.733),(-122.1997,37.724)]
+Fairfax Ave [(-122.2022,37.784),(-122.2015,37.778)]
+Fairlands Dr [(-121.871,37.976),(-121.8719,37.961)]
+Fairlands Dr [(-121.8731,37.956),(-121.8738,37.952)]
+Fairlands Road [(-122.0583,37.784),(-122.059,37.781)]
+Fairmont Dr [(-122.1135,37.144),(-122.1109,37.143)]
+Fairmount Ave [(-122.2525,37.23),(-122.2511,37.234)]
+Fairmount Ave [(-122.2561,37.194),(-122.2553,37.202)]
+Fairview Ave [(-121.999,37.428),(-121.9863,37.351)]
+Fairview Ave [(-122.0165,37.549),(-122.016,37.525)]
+Fairview Ave [(-122.02765,37.68521),(-122.0274,37.669),(-122.0267,37.646)]
+Fairview Ave [(-122.038562,37.76258),(-122.0379,37.762)]
+Fairview Ave [(-122.0418,37.748),(-122.0402,37.764)]
+Fairview Ave [(-122.0432,37.752),(-122.0428,37.751)]
+Fairview Ave [(-122.049,37.788),(-122.0482,37.775)]
+Fairview Ave [(-122.2404,37.22),(-122.2401,37.227)]
+Fairview St [(-122.2792,37.495),(-122.2776,37.498)]
+Fairway Ct [(-122.0536,37.334),(-122.0539,37.33)]
+Fairway Dr [(-122.1753,37.994),(-122.176433,37.98933)]
+Fairway St [(-122.0353,37.237),(-122.0343,37.238)]
+Fairway St [(-122.0375,37.221),(-122.0368,37.226)]
+Fairwood St [(-121.9539,37.183),(-121.9573,37.178)]
+Fall Creek Road [(-121.910898,37.2702),(-121.909729,37.25584)]
+Fallbrook Way [(-122.1285,37.533),(-122.1278,37.522)]
+Fallon St [(-122.2637,37.969),(-122.2633,37.977)]
+Fallon St [(-122.2661,37.931),(-122.2658,37.935)]
+Falls Ter [(-122.048735,37.61624),(-122.048402,37.60954)]
+Falmouth Pl [(-122.0301,37.513),(-122.031,37.502)]
+Farallon Dr [(-122.1681,37.938),(-122.1698,37.937)]
+Fargo Ave [(-122.1468,37.881),(-122.1476,37.887)]
+Farm Hill Dr [(-122.0328,37.523),(-122.0333,37.511)]
+Farnsworth St [(-122.1507,37.952),(-122.1507,37.944)]
+Farwell Dr [(-121.9867,37.255),(-121.9841,37.264)]
+Farwell Dr [(-121.996,37.291),(-121.9952,37.287)]
+Farwell Dr [(-121.9966,37.316),(-121.9962,37.314)]
+Farwell Dr [(-122.0133,37.392),(-122.0125,37.389),(-122.01186,37.3858)]
+Faulkner Dr [(-122.0034,37.53),(-122.0049,37.515)]
+Feldspar Ct [(-122.0722,37.874),(-122.0734,37.87)]
+Fellows St [(-122.0652,37.814),(-122.066,37.805)]
+Fellows St [(-122.0682,37.78),(-122.0687,37.775)]
+Fenico Ter [(-122.0166,37.562),(-122.0168,37.559)]
+Fenwick Way [(-121.947144,37.20116),(-121.9451,37.202)]
+Fenwick Way [(-121.9473,37.192),(-121.9472,37.199)]
+Fernside Ave [(-122.2337,37.514),(-122.2343,37.509)]
+Fernside Blvd [(-122.2256,37.575),(-122.2249,37.581)]
+Fernside Blvd [(-122.2271,37.645),(-122.2265,37.64)]
+Ferro St [(-122.2996,37.975),(-122.3025,37.968)]
+Ferry St [(-122.3128,37.147),(-122.3113,37.159)]
+Ferry St [(-122.3185,37.097),(-122.3155,37.122)]
+Field St [(-122.1604,37.721),(-122.1596,37.728)]
+Fielding Ct [(-122.0393,37.689),(-122.0384,37.688)]
+Fiji Way [(-122.1747,37.968),(-122.1753,37.965)]
+Filbert St [(-122.036,37.259),(-122.0356,37.252)]
+Filbert St [(-122.2779,37.215),(-122.2772,37.233)]
+Filbert St [(-122.2802,37.151),(-122.2796,37.168)]
+Finback Way [(-122.1783,37.934),(-122.1776,37.936)]
+Findlay Way [(-121.7463,37.753),(-121.7454,37.753)]
+Fir Ave [(-122.2385,37.318),(-122.2379,37.314)]
+Firestone Ct [(-122.1246,37.671),(-122.1246,37.666)]
+First St [(-121.7572,37.875),(-121.7576,37.863)]
+First St [(-121.7636,37.843),(-121.7644,37.84)]
+Firth Ct [(-122.1359,37.93),(-122.1363,37.924)]
+Fisk Ct [(-122.155,37.972),(-122.1545,37.968)]
+Fitzerald St [(-122.28185,37.26208),(-122.282237,37.26273)]
+Flagg St [(-122.0921,37.71),(-122.0914,37.7)]
+Fleet Road [(-122.2235,37.085),(-122.2229,37.078)]
+Fleming Ave [(-122.1949,37.791),(-122.1948,37.783)]
+Fletcher Lane [(-122.0779,37.668),(-122.0762,37.683)]
+Flint Ct [(-122.1074,37.711),(-122.1085,37.704)]
+Flint River Ter [(-122.054,37.78),(-122.0536,37.778)]
+Flora St [(-122.1882,37.625),(-122.1879,37.622)]
+Florence Ave [(-122.2253,37.376),(-122.225,37.373)]
+Florence Road [(-121.7743,37.711),(-121.7743,37.687)]
+Floresta Blvd [(-122.146,37.994),(-122.1478,37.98)]
+Florio St [(-122.2504,37.502),(-122.2494,37.504)]
+Flynn Road [(-121.657764,37.18891),(-121.657276,37.18952)]
+Folsom Ave [(-122.0587,37.271),(-122.0596,37.269)]
+Folsom Ave [(-122.0628,37.259),(-122.062855,37.25882)]
+Fontaine Ct [(-122.1525,37.685),(-122.1517,37.688)]
+Fontana Dr [(-122.2458,37.36),(-122.2463,37.354)]
+Fontes Dr [(-121.933352,37.40768),(-121.93251,37.40887)]
+Fontonett Ave [(-121.795158,37.619),(-121.7947,37.619)]
+Foothill Blvd [(-122.0798,37.709),(-122.0798,37.688)]
+Foothill Blvd [(-122.0882,37.829),(-122.0869,37.822)]
+Foothill Blvd [(-122.0926,37.862),(-122.0918,37.857)]
+Foothill Blvd [(-122.0983,37.907),(-122.0981,37.905)]
+Foothill Blvd [(-122.1455,37.421),(-122.145202,37.41752)]
+Foothill Blvd [(-122.1775,37.697),(-122.1764,37.696)]
+Foothill Blvd [(-122.1948,37.725),(-122.1928,37.721)]
+Foothill Blvd [(-122.2018,37.724),(-122.2008,37.724)]
+Foothill Blvd [(-122.2136,37.77),(-122.2129,37.763)]
+Foothill Blvd [(-122.2216,37.837),(-122.2206,37.833)]
+Foothill Blvd [(-122.2289,37.846),(-122.2278,37.845)]
+Foothill Blvd [(-122.2414,37.9),(-122.2403,37.893)]
+Foothill Blvd [(-122.2454,37.921),(-122.2442,37.91)]
+Foothill Lane [(-121.905417,37.52303),(-121.905087,37.51659)]
+Foothill Road [(-121.8956,37.413),(-121.8967,37.419)]
+Foothill Road [(-121.9075,37.547),(-121.907696,37.55141)]
+Foothill Road [(-121.923245,37.81689),(-121.9236,37.82)]
+Foothill Road [(-121.932,37.93),(-121.9335,37.958)]
+Foothill Knolls Dr [(-121.909407,37.65999),(-121.9094,37.657)]
+Fordham Way [(-121.7488,37.869),(-121.7481,37.869),(-121.7472,37.868)]
+Forest Pl [(-122.0672,37.051),(-122.0673,37.044)]
+Forest St [(-122.2541,37.443),(-122.2547,37.445)]
+Forest Glen Pl [(-122.067707,37.01875),(-122.068338,37.01945),(-122.068308,37.0263)]
+Forest Hill Ave [(-122.2118,37.034),(-122.2086,37.069)]
+Forestland Way [(-122.1886,37.334),(-122.1901,37.329)]
+Fortune Way [(-122.1877,37.693),(-122.187,37.688)]
+Foster Ct [(-122.0745,37.396),(-122.074447,37.39388)]
+Foster St [(-121.9683,37.367),(-121.9664,37.358)]
+Fountain St [(-122.2306,37.593),(-122.2293,37.605)]
+Foxfire Pl [(-122.063987,37.27349),(-122.064027,37.27459)]
+Foxswallow Road [(-121.8776,37.793),(-121.8796,37.794)]
+Fraga Road [(-122.0397,37.975),(-122.0393,37.987)]
+France Road [(-122.045563,37.6829),(-122.045452,37.68292)]
+Francisco St [(-121.8687,37.749),(-121.8696,37.75)]
+Francisco St [(-122.286,37.733),(-122.285,37.735),(-122.2839,37.737)]
+Franklin Ave [(-122.0605,37.028),(-122.0614,37.039)]
+Franklin Dr [(-121.9049,37.954),(-121.9084,37.934)]
+Franklin St [(-122.2687,37.055),(-122.2679,37.068)]
+Franklin St [(-122.2702,37.03),(-122.2698,37.037)]
+Franklin St [(-122.2719,37.003),(-122.2715,37.009)]
+Franklin St [(-122.2744,37.961),(-122.2741,37.968)]
+Frederick Road [(-122.174,37.244),(-122.1735,37.225)]
+Frederiksen Lane [(-121.9251,37.162),(-121.9256,37.162)]
+Fredi St [(-122.0764,37.934),(-122.0767,37.93)]
+Freedom Ave [(-122.1219,37.081),(-122.1215,37.075)]
+Freeman Pl [(-122.0375,37.866),(-122.037548,37.86536)]
+Fremont Ave [(-122.1429,37.008),(-122.145652,37.04405)]
+Fremont Blvd [(-121.9347,37.663),(-121.9324,37.65)]
+Fremont Blvd [(-121.955914,37.22071),(-121.9556,37.213)]
+Fremont Blvd [(-121.960056,37.3392),(-121.9584,37.33)]
+Fremont Blvd [(-121.964,37.362),(-121.9632,37.357)]
+Fremont Blvd [(-122.0043,37.573),(-122.0038,37.57)]
+Fremont Blvd [(-122.006647,37.58506),(-122.0071,37.587)]
+Fremont Blvd [(-122.02826,37.6908),(-122.02694,37.6842)]
+Fremont Blvd [(-122.0348,37.724),(-122.0315,37.707)]
+Fremont Blvd [(-122.0373,37.736),(-122.0361,37.73)]
+Fremont Blvd [(-122.0392,37.746),(-122.039154,37.74569)]
+Fremont St [(-122.284,37.403),(-122.2841,37.407)]
+Frontage Road [(-122.3051,37.664),(-122.2994,37.5)]
+Frontage Road [(-122.3074,37.781),(-122.3048,37.71)]
+Fruitvale Ave [(-122.2158,37.985),(-122.2157,37.987)]
+Fruitvale Ave [(-122.2166,37.966),(-122.2163,37.97)]
+Fruitvale Ave [(-122.2182,37.923),(-122.218,37.929)]
+Fruitvale Ave [(-122.2257,37.754),(-122.2256,37.756)]
+Fruitvale Ave [(-122.2272,37.719),(-122.2269,37.725)]
+Fruitvale Ave [(-122.2285,37.693),(-122.227522,37.71256)]
+Fulmar Ter [(-122.045623,37.80299),(-122.045824,37.80101)]
+Funston Gate Ct [(-121.8824,37.847),(-121.883,37.842)]
+Fuschia Ct [(-122.0204,37.325),(-122.0199,37.323)]
+G St [(-122.0167,37.018),(-122.0174,37.013)]
+Gable Dr [(-121.9178,37.798),(-121.9175,37.799)]
+Gading Road [(-122.0801,37.343),(-122.08,37.336)]
+Gading Road [(-122.0802,37.388),(-122.0802,37.38)]
+Gail Dr [(-122.0853,37.858),(-122.0854,37.853)]
+Gainsborough Ct [(-122.2448,37.473),(-122.2447,37.461)]
+Gainsborough Ter [(-122.0369,37.764),(-122.0369,37.756)]
+Galaxy Dr [(-122.0675,37.866),(-122.068,37.86)]
+Galindo Dr [(-121.9071,37.895),(-121.9041,37.891)]
+Galindo St [(-122.2158,37.84),(-122.2149,37.837)]
+Gallegos Ave [(-121.9286,37.324),(-121.9285,37.318)]
+Galleon Pl [(-122.178053,37.9223),(-122.177578,37.92252)]
+Galloway St [(-121.7473,37.177),(-121.7459,37.18)]
+Galloway Common [(-121.7459,37.18),(-121.745647,37.17049)]
+Galsworthy Ct [(-121.995,37.586),(-121.9955,37.585)]
+Galt St [(-122.1507,37.886),(-122.1507,37.88)]
+Galway Bay [(-122.2473,37.389),(-122.2475,37.384)]
+Gamay Dr [(-121.9093,37.676),(-121.909,37.67)]
+Ganet Ter [(-122.045424,37.79478),(-122.04533,37.79418)]
+Ganton Ct [(-122.0379,37.146),(-122.038,37.152)]
+Garden Ave [(-122.1077,37.626),(-122.1075,37.61)]
+Garfield Ave [(-122.1718,37.638),(-122.1713,37.632)]
+Garfield Ave [(-122.1746,37.664),(-122.174,37.66)]
+Garin Ave [(-122.033003,37.2842),(-122.032,37.282)]
+Garland Ct [(-122.0924,37.192),(-122.0926,37.177)]
+Garrison Ave [(-122.0785,37.075),(-122.0785,37.051)]
+Garrone Ave [(-122.0498,37.437),(-122.0513,37.434)]
+Garwood Glenn Dr [(-122.06406,37.68946),(-122.064269,37.68336)]
+Gateview Ave [(-122.3047,37.921),(-122.3033,37.9)]
+Gatewood St [(-121.9574,37.185),(-121.9573,37.178)]
+Gawain Ct [(-122.0251,37.745),(-122.02374,37.7382)]
+Geary Road [(-121.770923,37.99805),(-121.770222,37.9976)]
+Geary Road [(-121.7784,37.014),(-121.7986,37.06)]
+Geary Road [(-121.8277,37.073),(-121.8279,37.082)]
+Gem Ave [(-122.0167,37.823),(-122.0171,37.809)]
+Genoa St [(-122.2725,37.46),(-122.2727,37.467)]
+Georgean St [(-122.1024,37.877),(-122.1006,37.892)]
+Gertrude Dr [(-121.996,37.398),(-121.9966,37.39)]
+Gettysburg Ave [(-122.1089,37.366),(-122.1089,37.357)]
+Giannini Ct [(-122.0254,37.16),(-122.0256,37.157)]
+Gibbons Dr [(-122.2289,37.633),(-122.2283,37.633)]
+Gibbons Dr [(-122.2325,37.625),(-122.2317,37.629)]
+Gibraltar Dr [(-121.9025,37.96),(-121.9008,37.96)]
+Gibraltar Dr [(-122.0202,37.624),(-122.0207,37.619)]
+Gibraltar Dr [(-122.0242,37.603),(-122.0252,37.608)]
+Gibraltar Dr [(-122.026,37.613),(-122.026476,37.61522)]
+Gilbert Pl [(-121.9823,37.655),(-121.9836,37.653)]
+Gilman St [(-122.2883,37.813),(-122.2877,37.812)]
+Gilman St [(-122.2942,37.808),(-122.2933,37.81)]
+Gilman St [(-122.2962,37.803),(-122.2951,37.806)]
+Girvin Dr [(-122.1957,37.291),(-122.1904,37.299)]
+Gisler Way [(-122.042,37.285),(-122.0392,37.298)]
+Glacier Dr [(-121.8016,37.804),(-121.8017,37.809)]
+Glade St [(-122.0819,37.592),(-122.08171,37.5977)]
+Glen Ave [(-122.2514,37.257),(-122.2505,37.258)]
+Glen Dr [(-122.1459,37.316),(-122.1422,37.315)]
+Glen Alpine Road [(-122.2198,37.223),(-122.2185,37.239),(-122.217349,37.236)]
+Glen Ellen Dr [(-122.0479,37.85),(-122.0475,37.851)]
+Glendale Dr [(-122.0074,37.46),(-122.0067,37.456)]
+Glenmoor Dr [(-122.0046,37.466),(-122.0038,37.461)]
+Glenora Way [(-121.9143,37.317),(-121.9146,37.316)]
+Glenview Dr [(-122.001,37.413),(-122.0002,37.409)]
+Glenwood St [(-121.7799,37.771),(-121.7794,37.771)]
+Gliddon St [(-122.0575,37.066),(-122.058,37.045)]
+Godwit Ct [(-122.0141,37.84),(-122.0141,37.845)]
+Gold Creek [(-121.922096,37.88859),(-121.920744,37.88463)]
+Goldcrest Cir [(-121.8909,37.725),(-121.8917,37.73)]
+Golden Gate Way [(-122.2378,37.497),(-122.238055,37.49522)]
+Goldengate Ave [(-122.2324,37.451),(-122.232584,37.45192)]
+Golf Dr [(-121.7449,37.136),(-121.7443,37.142)]
+Golf Links Road [(-122.1425,37.565),(-122.1415,37.569)]
+Golf Links Road [(-122.1491,37.533),(-122.1482,37.536)]
+Gomes Road [(-121.949156,37.42192),(-121.9493,37.42)]
+Gordon St [(-122.0405,37.752),(-122.0408,37.748)]
+Gordon St [(-122.2001,37.788),(-122.1998,37.785)]
+Gotubin Common [(-122.053546,37.59037),(-122.054013,37.58887)]
+Grace Ave [(-122.276,37.431),(-122.2764,37.43)]
+Graffian St [(-122.16774,37.3758),(-122.1677,37.375)]
+Graham Ave [(-122.0311,37.321),(-122.032,37.31)]
+Graham Way [(-122.1328,37.128),(-122.1321,37.134)]
+Granada Cir [(-122.0734,37.229),(-122.0736,37.228)]
+Granada Cir [(-122.0741,37.234),(-122.0742,37.23)]
+Granada Dr [(-122.0734,37.212),(-122.0734,37.207)]
+Granada Dr [(-122.0738,37.22),(-122.0736,37.216)]
+Grand Ave [(-122.2442,37.18),(-122.244,37.184)]
+Grand Ave [(-122.2479,37.111),(-122.2478,37.115)]
+Grand Ave [(-122.2684,37.123),(-122.269233,37.12388)]
+Grand Ave [(-122.2722,37.128),(-122.2729,37.129)]
+Grand Ave [(-122.2812,37.154),(-122.2823,37.156),(-122.2834,37.159)]
+Grand St [(-122.2612,37.637),(-122.2609,37.643)]
+Grand Lake Dr [(-122.0553,37.89),(-122.056,37.894)]
+Grand Lake Dr [(-122.0591,37.864),(-122.0587,37.861)]
+Grandbrook Park Ct [(-121.9661,37.136),(-121.9658,37.14)]
+Grandview Dr [(-122.229712,37.58234),(-122.230071,37.57618)]
+Grange Ter [(-122.04547,37.62805),(-122.045749,37.62695)]
+Granger Ave [(-122.0778,37.984),(-122.0784,37.985)]
+Grant Ave [(-122.1491,37.732),(-122.1512,37.722)]
+Grant Ave [(-122.1523,37.717),(-122.1535,37.712)]
+Grant Ave [(-122.1545,37.703),(-122.1567,37.683)]
+Grant St [(-122.2725,37.579),(-122.2723,37.57)]
+Grant St [(-122.2729,37.597),(-122.2727,37.588)]
+Grant St [(-122.2738,37.632),(-122.2735,37.623)]
+Grant Line Road [(-121.583469,37.45746),(-121.583118,37.4641)]
+Granville Dr [(-122.0105,37.357),(-122.01,37.354)]
+Grass Valley Ct [(-122.1194,37.518),(-122.1187,37.516)]
+Grass Valley Road [(-122.1246,37.764),(-122.1176,37.634)]
+Gray Fox Cir [(-121.84122,37.60072),(-121.841235,37.60161)]
+Grayson St [(-122.2883,37.56),(-122.2871,37.563)]
+Grayson St [(-122.2916,37.553),(-122.2906,37.556)]
+Greenhills [(-122.082437,37.05551),(-122.08375,37.05557)]
+Greenly Dr [(-122.1595,37.704),(-122.1578,37.696)]
+Greenly Dr [(-122.1635,37.727),(-122.161,37.716)]
+Greenridge Road [(-122.0454,37.151),(-122.043982,37.1588),(-122.0441,37.178)]
+Greens Lane [(-121.8938,37.339),(-121.894,37.311)]
+Greentree Ct [(-121.8851,37.696),(-121.8855,37.692)]
+Greenview Dr [(-122.0652,37.907),(-122.064379,37.90817)]
+Greenview Dr [(-122.0688,37.905),(-122.0683,37.905),(-122.0663,37.907)]
+Greenville Road [(-121.6957,37.798),(-121.6956,37.778)]
+Greenville Road [(-121.6959,37.044),(-121.6959,37.034)]
+Greenville Road [(-121.6999,37.175),(-121.6993,37.169)]
+Greenwood Road [(-121.8795,37.705),(-121.8795,37.721)]
+Greenwood Road [(-121.8795,37.745),(-121.8796,37.766)]
+Greenwood Road [(-121.8796,37.801),(-121.8796,37.802)]
+Greer Ave [(-122.1393,37.889),(-122.1401,37.889)]
+Gresel St [(-122.0345,37.178),(-122.0338,37.182)]
+Gresham Ct [(-121.8752,37.943),(-121.8754,37.946)]
+Grimmer Blvd [(-121.9555,37.054),(-121.9534,37.05)]
+Grimmer Blvd [(-121.9625,37.394),(-121.9633,37.383)]
+Grimmer Blvd [(-121.9636,37.133),(-121.962341,37.11577)]
+Grimmer Blvd [(-121.966619,37.178),(-121.966,37.165)]
+Grimmer Blvd [(-121.967,37.332),(-121.9682,37.315)]
+Grizzly Peak Blvd [(-122.2112,37.568),(-122.2105,37.561)]
+Grizzly Peak Blvd [(-122.2213,37.638),(-122.2127,37.581)]
+Grizzly Peak Blvd [(-122.254,37.915),(-122.2536,37.912)]
+Grizzly Peak Blvd [(-122.26,37.965),(-122.259,37.952)]
+Grizzly Peak Blvd [(-122.2689,37.035),(-122.2667,37.02)]
+Grouse Way [(-122.0136,37.821),(-122.013412,37.83099)]
+Grove St [(-122.2364,37.582),(-122.2357,37.59)]
+Grove Way [(-122.0643,37.884),(-122.062679,37.89162),(-122.061796,37.89578),(-122.0609,37.9)]
+Grove Way [(-122.078,37.842),(-122.0759,37.843)]
+Grove Way [(-122.1041,37.76),(-122.102947,37.76318)]
+Grovenor Dr [(-122.0893,37.224),(-122.0887,37.209)]
+Gulfstream St [(-121.8645,37.976),(-121.8645,37.993)]
+Gull Ct [(-122.0594,37.775),(-122.0595,37.782)]
+Gull Way [(-121.8004,37.846),(-121.7996,37.845)]
+Gunn Dr [(-122.1902,37.347),(-122.190933,37.35637),(-122.192,37.357)]
+Guthrie St [(-121.861539,37.97796),(-121.860503,37.97781)]
+H St [(-121.7625,37.766),(-121.7621,37.756)]
+H St [(-122.0204,37.974),(-122.0207,37.972)]
+Hacienda Ave [(-122.1192,37.754),(-122.1214,37.746)]
+Hacienda Ave [(-122.1225,37.742),(-122.1235,37.738)]
+Hacienda Ave [(-122.125,37.729),(-122.1259,37.719)]
+Hackamore Lane [(-121.922474,37.86233),(-121.9223,37.863)]
+Hackamore Lane [(-121.924324,37.85594),(-121.9239,37.857)]
+Hackamore Com [(-121.9233,37.864),(-121.922542,37.86411)]
+Haddon Pl [(-122.2416,37.099),(-122.2411,37.103)]
+Hagemann Dr [(-121.79953,37.88118),(-121.7995,37.883)]
+Haight Ave [(-122.2881,37.767),(-122.2883,37.756)]
+Haley St [(-122.0444,37.367),(-122.042309,37.35074)]
+Haley St [(-122.0585,37.436),(-122.0579,37.432)]
+Halkin Lane [(-122.2684,37.983),(-122.2677,37.985)]
+Halliday Ave [(-122.175147,37.63369),(-122.1747,37.629)]
+Hamilton Pl [(-122.2592,37.151),(-122.2604,37.16)]
+Hamilton St [(-122.188,37.549),(-122.1875,37.544)]
+Hamlin St [(-122.009871,37.36805),(-122.0092,37.365)]
+Hampel St [(-122.2248,37.078),(-122.2244,37.073)]
+Hampton Road [(-122.1086,37.838),(-122.1073,37.84)]
+Hampton Road [(-122.2146,37.189),(-122.2139,37.183)]
+Hampton Road [(-122.222,37.177),(-122.2209,37.179)]
+Hamrick Lane [(-122.0831,37.344),(-122.0839,37.342)]
+Hancock Dr [(-121.9569,37.419),(-121.957,37.413)]
+Hanly Road [(-122.2137,37.067),(-122.2135,37.075)]
+Hannah St [(-122.2854,37.216),(-122.2861,37.237)]
+Hanover Ave [(-122.2488,37.034),(-122.2477,37.037)]
+Hanover Ave [(-122.2536,37.031),(-122.253,37.031)]
+Hanover St [(-121.789629,37.92481),(-121.7892,37.934)]
+Hanover St [(-121.7911,37.942),(-121.7923,37.95)]
+Hanover St [(-121.7939,37.918),(-121.7928,37.918)]
+Hansen Ave [(-122.0121,37.523),(-122.0124,37.519)]
+Hansen Dr [(-121.8984,37.7),(-121.898897,37.69774)]
+Hansen Road [(-122.0551,37.706),(-122.055,37.712)]
+Hansom Dr [(-122.1369,37.75),(-122.1354,37.726)]
+Happy Valley Road [(-121.877541,37.35652),(-121.871788,37.33636)]
+Happy Valley Road [(-121.879849,37.36461),(-121.877975,37.35804)]
+Happyland Ave [(-122.1044,37.666),(-122.104441,37.66152)]
+Harbor Bay Pkwy [(-122.233076,37.25225),(-122.236357,37.2515),(-122.2385,37.251)]
+Harbor Light Road [(-122.2667,37.606),(-122.2651,37.631)]
+Harbor View Ave [(-122.1872,37.927),(-122.1866,37.921)]
+Harder Road [(-122.0693,37.506),(-122.0688,37.506)]
+Harder Road [(-122.0793,37.489),(-122.0802,37.488)]
+Harlan St [(-122.1477,37.195),(-122.151,37.182)]
+Harlon Ct [(-122.0223,37.447),(-122.0229,37.445)]
+Harmon Ave [(-122.194,37.68),(-122.1924,37.664),(-122.1911,37.652)]
+Harpers Ferry Ct [(-121.9019,37.758),(-121.902271,37.7633)]
+Harrington Ave [(-122.2115,37.847),(-122.2108,37.851)]
+Harris Road [(-122.0659,37.372),(-122.0675,37.363)]
+Harris Road [(-122.0681,37.36),(-122.0705,37.347)]
+Harrisburg Ave [(-122.011,37.776),(-122.0113,37.773)]
+Harrison St [(-122.1539,37.268),(-122.1535,37.259)]
+Harrison St [(-122.2544,37.197),(-122.2542,37.199)]
+Harrison St [(-122.2577,37.172),(-122.2569,37.176)]
+Harrison St [(-122.2621,37.108),(-122.262,37.11)]
+Harrison St [(-122.2722,37.952),(-122.2717,37.958)]
+Harrison St Ramp [(-122.262,37.11),(-122.2613,37.114)]
+Hartford Dr [(-122.0297,37.94),(-122.0295,37.944)]
+Hartley Gate Ct [(-121.8803,37.863),(-121.8812,37.871)]
+Hartman Road [(-121.7876,37.217),(-121.7953,37.211)]
+Harvard Way [(-121.754,37.805),(-121.7508,37.806)]
+Harvest Road [(-121.8797,37.687),(-121.8807,37.691)]
+Harwood Ave [(-122.2471,37.497),(-122.2466,37.498)]
+Harwood Ave [(-122.25,37.489),(-122.2475,37.495)]
+Haskell St [(-122.2825,37.512),(-122.2805,37.516)]
+Hathaway Ave [(-122.1109,37.742),(-122.1105,37.739)]
+Havasu St [(-121.9209,37.887),(-121.9204,37.878)]
+Havenscourt Blvd [(-122.1891,37.634),(-122.1888,37.638),(-122.1882,37.643)]
+Haverhill Dr [(-122.1938,37.246),(-122.1915,37.242)]
+Hawkins St [(-121.969533,37.36702),(-121.9703,37.358)]
+Hawley St [(-122.1921,37.531),(-122.1915,37.526)]
+Hawthorne Ter [(-122.261,37.825),(-122.262,37.823)]
+Hayfield Road [(-121.8292,37.314),(-121.8281,37.295)]
+Hays St [(-122.1538,37.203),(-122.1533,37.194)]
+Hayward Blvd [(-122.0224,37.563),(-122.0179,37.544)]
+Hayward Blvd [(-122.0335,37.552),(-122.032996,37.55094)]
+Hayward Blvd [(-122.03757,37.5596),(-122.033997,37.55449)]
+Hayward Blvd [(-122.0383,37.556),(-122.0381,37.557)]
+Hayward Blvd [(-122.050043,37.58583),(-122.0484,37.574),(-122.047,37.556)]
+Hearst Ave [(-122.2551,37.757),(-122.2545,37.758)]
+Hearst Ave [(-122.2691,37.738),(-122.268,37.74)]
+Hearst Ave [(-122.2726,37.734),(-122.2715,37.735)]
+Hearst Ave [(-122.277,37.727),(-122.2748,37.731)]
+Hearst Ave [(-122.2858,37.714),(-122.2847,37.715)]
+Hearst Ave [(-122.2918,37.704),(-122.2887,37.709)]
+Hearst Ave [(-122.3027,37.682),(-122.3019,37.685)]
+Heartwood Dr [(-122.2006,37.341),(-122.1992,37.338)]
+Heathrow Ter [(-122.057185,37.55699),(-122.056911,37.55223)]
+Hebrides Ct [(-122.0343,37.529),(-122.034,37.531)]
+Hegenberger Exwy [(-122.1874,37.572),(-122.1891,37.56)]
+Hegenberger Exwy [(-122.1946,37.52),(-122.1947,37.497)]
+Hegenberger Road [(-122.1953,37.401),(-122.1953,37.404)]
+Hegenberger Road [(-122.1955,37.378),(-122.1954,37.385)]
+Heidelberg Dr [(-121.7692,37.638),(-121.7714,37.645)]
+Heidelberg Dr [(-121.7761,37.614),(-121.7751,37.614)]
+Heinz Ave [(-122.2953,37.527),(-122.2912,37.536)]
+Hellman St [(-122.1403,37.471),(-122.1406,37.464)]
+Helsinki Way [(-121.7753,37.659),(-121.7744,37.658)]
+Hemlock Ter [(-121.986875,37.25649),(-121.986744,37.25571)]
+Henry St [(-122.2706,37.857),(-122.2704,37.843)]
+Heritage Ter [(-121.997217,37.55076),(-121.997913,37.54319)]
+Herman Ave [(-121.7163,37.123),(-121.7164,37.142)]
+Hermes Ct [(-122.0786,37.516),(-122.0784,37.514)]
+Hermitage Ave [(-122.0542,37.384),(-122.0566,37.368)]
+Hermitage Ct [(-121.7295,37.263),(-121.729426,37.26819)]
+Hermitage Lane [(-122.0362,37.137),(-122.0354,37.136)]
+Hermosa Ave [(-122.2309,37.415),(-122.231,37.404),(-122.2301,37.404)]
+Herrier St [(-122.1943,37.006),(-122.1936,37.998)]
+Herrin Way [(-121.9098,37.888),(-121.91,37.893)]
+Hesperian Blvd [(-122.0878,37.182),(-122.0873,37.174)]
+Hesperian Blvd [(-122.088064,37.18684),(-122.087886,37.18358)]
+Hesperian Blvd [(-122.0916,37.245),(-122.0896,37.214)]
+Hesperian Blvd [(-122.097,37.333),(-122.0956,37.31),(-122.0946,37.293)]
+Hesperian Blvd [(-122.1079,37.513),(-122.1076,37.507)]
+Hesperian Blvd [(-122.1102,37.551),(-122.1091,37.534)]
+Hesperian Blvd [(-122.1132,37.6),(-122.1123,37.586)]
+Hesperian Blvd [(-122.1257,37.792),(-122.1251,37.781)]
+Hesperian Blvd [(-122.1287,37.989),(-122.1287,37.984)]
+Hesperian Blvd [(-122.1288,37.922),(-122.1288,37.913)]
+Hesse Dr [(-122.0782,37.208),(-122.0782,37.204)]
+Hetch Hetchy Aqueduct [(-121.635378,37.0634),(-121.630012,37.07482)]
+Hetch Hetchy Aqueduct [(-121.687586,37.92102),(-121.686938,37.92241)]
+Hetch Hetchy Aqueduct [(-121.74008,37.81072),(-121.739425,37.81211)]
+Hetch Hetchy Aqueduct [(-121.9355,37.477),(-121.9169,37.53)]
+Hetch Hetchy Aqueduct [(-121.9465,37.448),(-121.945888,37.4496)]
+Hetch Hetchy Aqueduct [(-121.94968,37.4388),(-121.9488,37.441)]
+Hetch Hetchy Aqueduct [(-121.9615,37.407),(-121.960853,37.40854)]
+Hetch Hetchy Aqueduct [(-122.0007,37.313),(-122.0005,37.313)]
+Hetch Hetchy Aqueduct [(-122.0233,37.291),(-122.020432,37.29388)]
+Hetch Hetchy Aqueduct [(-122.0255,37.283),(-122.0245,37.286)]
+Hetch Hetchy Aqueduct [(-122.039,37.25),(-122.0404,37.247)]
+Heyer Ave [(-122.0673,37.044),(-122.0657,37.044)]
+Hibiscus Ave [(-121.999801,37.80223),(-121.9993,37.8)]
+Hickory Lane [(-121.9163,37.102),(-121.91604,37.1072)]
+Hickory St [(-122.0524,37.214),(-122.0523,37.211)]
+Hidalgo Ct [(-121.9505,37.614),(-121.9512,37.608)]
+Hidden Lane [(-122.0553,37.757),(-122.05301,37.75242),(-122.0503,37.747)]
+Higgins Way [(-121.9433,37.392),(-121.9424,37.392)]
+High St [(-121.9601,37.381),(-121.95938,37.3774)]
+High St [(-122.1901,37.889),(-122.1899,37.891)]
+High St [(-122.199,37.845),(-122.1983,37.849)]
+High St [(-122.2007,37.837),(-122.1997,37.842)]
+High St [(-122.2151,37.712),(-122.2145,37.716)]
+High St [(-122.2233,37.647),(-122.2226,37.652)]
+High St [(-122.2281,37.605),(-122.2273,37.611),(-122.2267,37.618)]
+High St [(-122.2295,37.592),(-122.2288,37.597)]
+High St [(-122.2379,37.514),(-122.2367,37.526)]
+Highland Ave [(-122.2286,37.21),(-122.2285,37.2)]
+Hilgard Ave [(-122.2603,37.787),(-122.2585,37.789)]
+Hilgard Ave [(-122.2638,37.782),(-122.2624,37.783)]
+Hill Road [(-122.2498,37.881),(-122.2486,37.868)]
+Hillcrest Ave [(-121.7472,37.839),(-121.7473,37.834)]
+Hillcrest Ave [(-122.0492,37.591),(-122.0485,37.587)]
+Hillcroft Cir [(-122.2304,37.089),(-122.2301,37.093)]
+Hilldale Ave [(-122.2629,37.96),(-122.2624,37.956)]
+Hillegass Ave [(-122.2541,37.503),(-122.2543,37.513)]
+Hillegass Ave [(-122.2545,37.561),(-122.2541,37.539)]
+Hillegass Ave [(-122.2557,37.619),(-122.2554,37.601)]
+Hiller Dr [(-122.2275,37.551),(-122.2263,37.542)]
+Hiller Dr [(-122.228415,37.50849),(-122.2284,37.508)]
+Hillgirt Cir [(-122.2429,37.079),(-122.2424,37.076)]
+Hillside Ave [(-121.997183,37.84571),(-121.998118,37.83759),(-121.9983,37.836)]
+Hillside Ct [(-122.2345,37.231),(-122.2342,37.225)]
+Hillside St [(-122.1628,37.561),(-122.1625,37.553)]
+Hilltop Cres [(-122.2256,37.329),(-122.2246,37.319)]
+Hillview Ct [(-121.9178,37.841),(-121.9191,37.838)]
+Hillview Road [(-122.253,37.934),(-122.25,37.92)]
+Hilsadne Ter [(-122.046919,37.64183),(-122.04699,37.6443)]
+Hilton St [(-122.03,37.877),(-122.0305,37.864)]
+Hobart Ct [(-121.9108,37.709),(-121.9102,37.711)]
+Hochler Dr [(-121.9111,37.317),(-121.9099,37.305)]
+Hogan Pl [(-121.7696,37.507),(-121.7689,37.502)]
+Holiday St [(-122.0421,37.306),(-122.0418,37.304)]
+Holladay Ct [(-121.7773,37.842),(-121.778,37.841)]
+Holland Dr [(-121.9115,37.798),(-121.9118,37.801)]
+Holland Dr [(-121.9124,37.814),(-121.9126,37.821)]
+Holland St [(-122.2038,37.688),(-122.2016,37.677)]
+Hollis St [(-122.2851,37.314),(-122.2857,37.332)]
+Hollis St [(-122.2866,37.355),(-122.2869,37.362)]
+Hollis St [(-122.2885,37.397),(-122.289,37.414)]
+Hollis St [(-122.2894,37.428),(-122.2895,37.433)]
+Hollis St [(-122.2901,37.45),(-122.2903,37.458)]
+Hollis St [(-122.291,37.48),(-122.2913,37.49)]
+Holly St [(-122.1715,37.488),(-122.1704,37.472)]
+Holly St [(-122.1742,37.532),(-122.1737,37.526)]
+Holly St [(-122.2393,37.3),(-122.2395,37.297)]
+Hollyhock Dr [(-122.131813,37.99633),(-122.1306,37.997)]
+Holmes St [(-121.7784,37.776),(-121.778512,37.7704)]
+Holmes St [(-121.7789,37.751),(-121.7789,37.747)]
+Holmes St [(-121.7794,37.697),(-121.7794,37.67)]
+Holmes St [(-121.7816,37.566),(-121.7818,37.56)]
+Holt St [(-122.063,37.813),(-122.063508,37.80257)]
+Honeysuckle Road [(-121.7458,37.102),(-121.745,37.096)]
+Hooper St [(-121.955964,37.28079),(-121.95582,37.27507)]
+Hoover Ave [(-122.2088,37.116),(-122.2086,37.115)]
+Hop Ranch Road [(-122.0461,37.942),(-122.045974,37.92945)]
+Hopkins Ct [(-122.2829,37.819),(-122.2822,37.822)]
+Hopkins St [(-122.0778,37.184),(-122.0775,37.159)]
+Hopkins St [(-122.284,37.802),(-122.2834,37.805)]
+Hopyard Road [(-121.8828,37.674),(-121.8833,37.678)]
+Hopyard Road [(-121.9026,37.975),(-121.9033,37.985)]
+Horatio Way [(-122.0499,37.781),(-122.0505,37.774)]
+Hospital Dr [(-122.257,37.548),(-122.2559,37.549)]
+Hotchkiss St [(-121.9283,37.947),(-121.9287,37.958)]
+Howe Ct [(-121.9514,37.252),(-121.9544,37.25)]
+Howe St [(-122.2541,37.265),(-122.2537,37.267)]
+Hoyt St [(-121.9195,37.842),(-121.9184,37.824)]
+Hubbard Ave [(-122.1585,37.914),(-122.1602,37.914)]
+Huber Dr [(-122.0904,37.09),(-122.09042,37.0804),(-122.0901,37.074)]
+Hudson Lane [(-122.1483,37.14),(-122.149,37.137)]
+Hugh Way [(-122.0322,37.185),(-122.0317,37.18)]
+Hula Cir [(-122.0465,37.869),(-122.0468,37.875)]
+Humboldt Ave [(-122.2145,37.872),(-122.2133,37.879)]
+Hummingbird Road [(-121.8824,37.777),(-121.8853,37.773)]
+Hummingbird Road [(-121.8862,37.774),(-121.8871,37.774)]
+Hunter Ave [(-122.1824,37.312),(-122.1816,37.339)]
+Huntington St [(-122.1866,37.895),(-122.186,37.899)]
+Huntwood Ave [(-122.0531,37.093),(-122.0531,37.078)]
+Huntwood Ave [(-122.06,37.279),(-122.0598,37.273)]
+Huntwood Ave [(-122.0641,37.336),(-122.063367,37.32585),(-122.0628,37.318)]
+Huntwood Ave [(-122.0781,37.48),(-122.0774,37.473)]
+Hutton Ct [(-121.9826,37.274),(-121.9822,37.272)]
+Hyde St [(-122.2198,37.883),(-122.2171,37.872)]
+I St [(-121.7655,37.798),(-121.7652,37.791)]
+I St [(-121.7675,37.848),(-121.7682,37.857)]
+I- 205 ((-121.573292,37.41726),(-121.571644,37.42),(-121.563557,37.42641))
+I- 205 [(-121.572819,37.42107),(-121.571705,37.42168),(-121.563557,37.42641),(-121.560856,37.42885),(-121.559467,37.42946),(-121.559055,37.42939),(-121.558781,37.42992),(-121.556644,37.43214),(-121.5559,37.434)]
+I- 580 ((-121.628147,37.33089),(-121.605091,37.3605),(-121.589344,37.3946),(-121.58118,37.41062),(-121.573292,37.41726),(-121.571644,37.41779),(-121.55843,37.40406))
+I- 580 ((-121.628147,37.33089),(-121.629246,37.32464),(-121.644337,37.2411),(-121.654377,37.20798),(-121.654356,37.20723),(-121.654926,37.20531),(-121.658827,37.19552))
+I- 580 ((-121.727,37.074),(-121.7255,37.083),(-121.7234,37.092),(-121.723,37.095),(-121.721995,37.09859),(-121.7216,37.1),(-121.7211,37.102),(-121.7188,37.109))
+I- 580 ((-122.1752,37.826),(-122.177,37.833))
+I- 580 ((-122.2029,37.928),(-122.2036,37.933),(-122.2043,37.938))
+I- 580 ((-122.2535,37.196),(-122.2539,37.2))
+I- 580 ((-122.2744,37.262),(-122.2746,37.263),(-122.2774,37.27),(-122.278,37.271),(-122.2792,37.274),(-122.2806,37.275),(-122.2817,37.276),(-122.2828,37.276),(-122.2837,37.276))
+I- 580 ((-122.2837,37.276),(-122.2849,37.273),(-122.286,37.27),(-122.2868,37.269),(-122.2871,37.266),(-122.2882,37.265),(-122.2893,37.266))
+I- 580 [(-121.560856,37.42885),(-121.55843,37.40406),(-121.557655,37.39926),(-121.556,37.389)]
+I- 580 [(-121.628757,37.34287),(-121.600742,37.38407),(-121.588856,37.40124),(-121.580494,37.41451),(-121.572819,37.42107)]
+I- 580 [(-121.628757,37.34287),(-121.630009,37.33791),(-121.657688,37.20089),(-121.658827,37.19552),(-121.659626,37.19326),(-121.660816,37.18952)]
+I- 580 [(-121.664341,37.1822),(-121.662081,37.18693),(-121.661812,37.18746),(-121.660816,37.18952)]
+I- 580 [(-121.664341,37.1822),(-121.666965,37.18166),(-121.6697,37.185)]
+I- 580 [(-121.727,37.074),(-121.7229,37.093),(-121.722301,37.09522),(-121.721001,37.10005),(-121.7194,37.106),(-121.7188,37.109),(-121.7168,37.12),(-121.7163,37.123),(-121.7145,37.127),(-121.7096,37.148),(-121.707731,37.1568),(-121.7058,37.166),(-121.7055,37.168),(-121.7044,37.174),(-121.7038,37.172),(-121.7037,37.172),(-121.7027,37.175),(-121.7001,37.181),(-121.6957,37.191),(-121.6948,37.192),(-121.6897,37.204),(-121.6697,37.185)]
+I- 580 [(-121.727,37.074),(-121.7275,37.072),(-121.7331,37.046)]
+I- 580 [(-121.7705,37.013),(-121.769039,37.01504),(-121.7583,37.03),(-121.7557,37.03),(-121.754805,37.02966),(-121.750803,37.02812),(-121.75,37.02738),(-121.7489,37.026),(-121.7453,37.024),(-121.7438,37.024),(-121.7411,37.024),(-121.740416,37.02505),(-121.739035,37.02719),(-121.7379,37.028),(-121.736275,37.03278),(-121.7362,37.033),(-121.7358,37.034),(-121.7331,37.046)]
+I- 580 [(-121.8585,37.013),(-121.8521,37.011),(-121.8485,37.011),(-121.8463,37.011),(-121.8455,37.011),(-121.8416,37.011),(-121.8414,37.011),(-121.834001,37.0104),(-121.8292,37.01),(-121.8288,37.009),(-121.82332,37.00833),(-121.8206,37.008)]
+I- 580 [(-121.8585,37.013),(-121.863393,37.01213),(-121.8641,37.012)]
+I- 580 [(-121.9214,37.015),(-121.9189,37.02),(-121.918,37.02),(-121.9087,37.017),(-121.90609,37.01797),(-121.906,37.018)]
+I- 580 [(-121.9214,37.015),(-121.9217,37.014)]
+I- 580 [(-121.9322,37.989),(-121.9243,37.006),(-121.9217,37.014)]
+I- 580 [(-122.018,37.019),(-122.0009,37.032),(-121.9787,37.983),(-121.958,37.984),(-121.9571,37.986)]
+I- 580 [(-122.0911,37.906),(-122.09,37.908),(-122.0882,37.908),(-122.0856,37.909),(-122.078489,37.909),(-122.0726,37.909),(-122.0711,37.91),(-122.068287,37.91137),(-122.0649,37.914)]
+I- 580 [(-122.098,37.908),(-122.0965,37.904),(-122.095586,37.903),(-122.0953,37.903),(-122.0943,37.902),(-122.0938,37.903),(-122.093241,37.90351)]
+I- 580 [(-122.1108,37.023),(-122.1101,37.02),(-122.108103,37.00764),(-122.108,37.007),(-122.1069,37.998),(-122.1064,37.994),(-122.1053,37.982),(-122.1048,37.977),(-122.1032,37.958),(-122.1026,37.953),(-122.1013,37.938),(-122.0989,37.911),(-122.0984,37.91),(-122.098,37.908)]
+I- 580 [(-122.1306,37.157),(-122.1298,37.147),(-122.1277,37.122),(-122.126709,37.11468),(-122.1254,37.105),(-122.125,37.103),(-122.1237,37.096),(-122.1231,37.093),(-122.1214,37.082),(-122.1183,37.066),(-122.116,37.052),(-122.1153,37.048),(-122.1108,37.023)]
+I- 580 [(-122.1543,37.703),(-122.1535,37.694),(-122.1512,37.655),(-122.1475,37.603),(-122.1468,37.583),(-122.1472,37.569),(-122.149044,37.54874),(-122.1493,37.546),(-122.1501,37.532),(-122.1506,37.509),(-122.1495,37.482),(-122.1487,37.467),(-122.1477,37.447),(-122.1414,37.383),(-122.1404,37.376),(-122.1398,37.372),(-122.139,37.356),(-122.1388,37.353),(-122.1385,37.34),(-122.1382,37.33),(-122.1378,37.316)]
+I- 580 [(-122.1716,37.805),(-122.1703,37.799),(-122.170042,37.79758),(-122.169413,37.79411),(-122.167295,37.78244),(-122.166339,37.77717),(-122.165224,37.77112),(-122.1648,37.769)]
+I- 580 [(-122.1716,37.805),(-122.1728,37.811),(-122.1742,37.817)]
+I- 580 [(-122.177,37.833),(-122.1789,37.838)]
+I- 580 [(-122.2043,37.938),(-122.204734,37.94074),(-122.2062,37.95),(-122.207492,37.95608),(-122.2079,37.958),(-122.2085,37.962)]
+I- 580 [(-122.2197,37.99),(-122.22,37.99),(-122.222092,37.99523),(-122.2232,37.998),(-122.224146,37.99963),(-122.2261,37.003),(-122.2278,37.007),(-122.2302,37.026),(-122.2323,37.043),(-122.2344,37.059),(-122.235405,37.06427),(-122.2365,37.07)]
+I- 580 [(-122.2535,37.196),(-122.2533,37.196)]
+I- 580 [(-122.2539,37.2),(-122.2541,37.201)]
+I- 580 [(-122.2613,37.23),(-122.2601,37.22833),(-122.2611,37.231),(-122.2639,37.238),(-122.2646,37.241),(-122.2654,37.242)]
+I- 580 [(-122.2613,37.23),(-122.263448,37.23307),(-122.2647,37.23486),(-122.2655,37.236),(-122.2664,37.238)]
+I- 580 [(-122.2664,37.238),(-122.2675,37.243)]
+I- 580 [(-122.2675,37.243),(-122.2677,37.243),(-122.2681,37.244)]
+I- 580 [(-122.2679,37.248),(-122.2681,37.248)]
+I- 580 [(-122.2744,37.262),(-122.2734,37.259),(-122.2695,37.247),(-122.268532,37.2449),(-122.268237,37.24426),(-122.2681,37.244)]
+I- 580 [(-122.2893,37.266),(-122.2904,37.27),(-122.2909,37.273)]
+I- 580 [(-122.2901,37.274),(-122.2905,37.278),(-122.2921,37.286),(-122.2927,37.299)]
+I- 580 [(-122.2909,37.273),(-122.2918,37.279),(-122.2923,37.282)]
+I- 580 [(-122.2928,37.293),(-122.2931,37.301)]
+I- 580 Ramp ((-121.74,37.036),(-121.7393,37.033),(-121.7384,37.032))
+I- 580 Ramp ((-121.7843,37.996),(-121.784294,37.99539),(-121.7842,37.985))
+I- 580 Ramp ((-121.818,37.011),(-121.8187,37.014))
+I- 580 Ramp ((-121.8185,37.008),(-121.8187,37.001))
+I- 580 Ramp ((-121.9025,37.018),(-121.9037,37.022),(-121.9059,37.029))
+I- 580 Ramp ((-121.9335,37.987),(-121.9331,37.979))
+I- 580 Ramp ((-122.0206,37.007),(-122.0203,37.015))
+I- 580 Ramp ((-122.1202,37.085),(-122.1203,37.083))
+I- 580 Ramp ((-122.1522,37.526),(-122.1514,37.524))
+I- 580 Ramp ((-122.2541,37.201),(-122.254,37.205))
+I- 580 Ramp ((-122.2646,37.241),(-122.2653,37.244),(-122.266,37.245))
+I- 580 Ramp ((-122.2654,37.242),(-122.266,37.245),(-122.2671,37.245))
+I- 580 Ramp ((-122.2786,37.288),(-122.2798,37.286),(-122.2804,37.285),(-122.2814,37.282),(-122.2818,37.28))
+I- 580 Ramp ((-122.2796,37.289),(-122.2818,37.283),(-122.2822,37.281))
+I- 580 Ramp [(-121.657688,37.20089),(-121.658176,37.20089),(-121.659977,37.19715),(-121.660232,37.1965),(-121.661524,37.18996),(-121.662081,37.18693)]
+I- 580 Ramp [(-121.660816,37.18952),(-121.659428,37.19105),(-121.658115,37.19272),(-121.657078,37.19639),(-121.654926,37.20531)]
+I- 580 Ramp [(-121.7218,37.088),(-121.722301,37.09522)]
+I- 580 Ramp [(-121.7231,37.108),(-121.7211,37.102)]
+I- 580 Ramp [(-121.7232,37.103),(-121.7222,37.103),(-121.721995,37.09859)]
+I- 580 Ramp [(-121.7232,37.103),(-121.7234,37.092)]
+I- 580 Ramp [(-121.7255,37.083),(-121.7232,37.114)]
+I- 580 Ramp [(-121.727,37.074),(-121.7237,37.084),(-121.723311,37.0823),(-121.7221,37.077),(-121.7219,37.081),(-121.720744,37.09257),(-121.7194,37.106)]
+I- 580 Ramp [(-121.739,37.02),(-121.738298,37.02506),(-121.737988,37.02686),(-121.7379,37.028)]
+I- 580 Ramp [(-121.739,37.02),(-121.7402,37.015)]
+I- 580 Ramp [(-121.74,37.034),(-121.7409,37.034),(-121.7401,37.029),(-121.7384,37.032),(-121.7358,37.034)]
+I- 580 Ramp [(-121.7411,37.024),(-121.7401,37.018),(-121.7401,37.024)]
+I- 580 Ramp [(-121.7438,37.024),(-121.742961,37.02896),(-121.7416,37.037),(-121.74,37.039)]
+I- 580 Ramp [(-121.7743,37.006),(-121.7729,37.006),(-121.7705,37.013)]
+I- 580 Ramp [(-121.7743,37.006),(-121.7729,37.013),(-121.7705,37.013)]
+I- 580 Ramp [(-121.7865,37.995),(-121.7852,37.992),(-121.7842,37.985),(-121.7838,37.966),(-121.7834,37.957)]
+I- 580 Ramp [(-121.7891,37.998),(-121.7854,37.999),(-121.78441,37.9963),(-121.7843,37.996)]
+I- 580 Ramp [(-121.8195,37.007),(-121.8187,37.014),(-121.8179,37.015)]
+I- 580 Ramp [(-121.8206,37.008),(-121.8187,37.001),(-121.8179,37.005)]
+I- 580 Ramp [(-121.8454,37.01),(-121.8455,37.011)]
+I- 580 Ramp [(-121.8463,37.011),(-121.8496,37.025)]
+I- 580 Ramp [(-121.8521,37.011),(-121.8479,37.999),(-121.8476,37.999),(-121.8456,37.01),(-121.8455,37.011)]
+I- 580 Ramp [(-121.8521,37.011),(-121.8496,37.025)]
+I- 580 Ramp [(-121.866951,37.01385),(-121.871381,37.02608)]
+I- 580 Ramp [(-121.8695,37.013),(-121.8712,37.011),(-121.8717,37.001),(-121.8714,37.001)]
+I- 580 Ramp [(-121.8713,37.014),(-121.871352,37.02321)]
+I- 580 Ramp [(-121.8743,37.014),(-121.8722,37.999),(-121.8714,37.999)]
+I- 580 Ramp [(-121.9043,37.998),(-121.9036,37.013),(-121.902632,37.0174),(-121.9025,37.018)]
+I- 580 Ramp [(-121.906,37.018),(-121.9056,37.011)]
+I- 580 Ramp [(-121.906,37.018),(-121.905635,37.0239),(-121.9065,37.023)]
+I- 580 Ramp [(-121.906,37.018),(-121.906044,37.02416),(-121.906177,37.02331),(-121.906315,37.02115)]
+I- 580 Ramp [(-121.9068,37.027),(-121.9059,37.029),(-121.9078,37.04)]
+I- 580 Ramp [(-121.9087,37.017),(-121.9056,37.003),(-121.905,37.004)]
+I- 580 Ramp [(-121.9087,37.017),(-121.907194,37.02619),(-121.9071,37.029),(-121.9072,37.033)]
+I- 580 Ramp [(-121.9335,37.987),(-121.9341,37.991),(-121.9345,37.985)]
+I- 580 Ramp [(-121.9345,37.985),(-121.9341,37.978)]
+I- 580 Ramp [(-121.9364,37.986),(-121.9338,37.971),(-121.9331,37.979),(-121.9322,37.989)]
+I- 580 Ramp [(-121.9368,37.986),(-121.936483,37.98832),(-121.9353,37.997),(-121.93504,37.00035),(-121.9346,37.006),(-121.933764,37.00031),(-121.9333,37.997),(-121.9322,37.989)]
+I- 580 Ramp [(-121.9562,37.984),(-121.9565,37.98)]
+I- 580 Ramp [(-121.9571,37.986),(-121.9565,37.98)]
+I- 580 Ramp [(-121.958,37.984),(-121.9565,37.98)]
+I- 580 Ramp [(-122.0195,37.012),(-122.0184,37.009),(-122.018,37.019)]
+I- 580 Ramp [(-122.0195,37.012),(-122.0203,37.015),(-122.0212,37.02)]
+I- 580 Ramp [(-122.0531,37.932),(-122.0544,37.941)]
+I- 580 Ramp [(-122.0649,37.914),(-122.0618,37.916),(-122.0604,37.92)]
+I- 580 Ramp [(-122.0649,37.914),(-122.0639,37.92),(-122.063,37.923)]
+I- 580 Ramp [(-122.0884,37.911),(-122.0856,37.909)]
+I- 580 Ramp [(-122.093241,37.90351),(-122.09364,37.89634),(-122.093788,37.89212)]
+I- 580 Ramp [(-122.0934,37.896),(-122.09257,37.89961),(-122.0911,37.906)]
+I- 580 Ramp [(-122.0941,37.897),(-122.0943,37.902)]
+I- 580 Ramp [(-122.0953,37.903),(-122.0971,37.911),(-122.0943,37.902)]
+I- 580 Ramp [(-122.096,37.888),(-122.0962,37.891),(-122.0964,37.9)]
+I- 580 Ramp [(-122.101,37.898),(-122.1005,37.902),(-122.0989,37.911)]
+I- 580 Ramp [(-122.108,37.007),(-122.1093,37.02)]
+I- 580 Ramp [(-122.1086,37.003),(-122.1068,37.993),(-122.1066,37.992),(-122.1053,37.982)]
+I- 580 Ramp [(-122.1086,37.003),(-122.1103,37.018),(-122.1108,37.023),(-122.1093,37.02)]
+I- 580 Ramp [(-122.1206,37.088),(-122.1203,37.083),(-122.1183,37.066)]
+I- 580 Ramp [(-122.1215,37.075),(-122.1183,37.066)]
+I- 580 Ramp [(-122.1226,37.099),(-122.1238,37.102),(-122.1254,37.105)]
+I- 580 Ramp [(-122.1237,37.096),(-122.1234,37.09)]
+I- 580 Ramp [(-122.1311,37.177),(-122.1306,37.157)]
+I- 580 Ramp [(-122.1338,37.222),(-122.135,37.227)]
+I- 580 Ramp [(-122.1362,37.248),(-122.1374,37.249)]
+I- 580 Ramp [(-122.1371,37.279),(-122.1367,37.264)]
+I- 580 Ramp [(-122.1371,37.279),(-122.1381,37.262)]
+I- 580 Ramp [(-122.1378,37.316),(-122.1374,37.311),(-122.1373,37.307),(-122.1367,37.296)]
+I- 580 Ramp [(-122.1379,37.282),(-122.1371,37.279)]
+I- 580 Ramp [(-122.1391,37.367),(-122.1388,37.353)]
+I- 580 Ramp [(-122.1414,37.383),(-122.1407,37.376),(-122.1403,37.372),(-122.139,37.356)]
+I- 580 Ramp [(-122.1487,37.467),(-122.1476,37.454)]
+I- 580 Ramp [(-122.1493,37.546),(-122.1508,37.535)]
+I- 580 Ramp [(-122.1495,37.482),(-122.1489,37.452)]
+I- 580 Ramp [(-122.1521,37.529),(-122.1514,37.524),(-122.1506,37.509)]
+I- 580 Ramp [(-122.1521,37.682),(-122.151,37.659),(-122.1512,37.655)]
+I- 580 Ramp [(-122.1539,37.707),(-122.1535,37.694)]
+I- 580 Ramp [(-122.1553,37.706),(-122.1543,37.703)]
+I- 580 Ramp [(-122.1567,37.727),(-122.156,37.71)]
+I- 580 Ramp [(-122.1648,37.769),(-122.16472,37.7682),(-122.1638,37.759)]
+I- 580 Ramp [(-122.1716,37.805),(-122.1725,37.816)]
+I- 580 Ramp [(-122.1729,37.803),(-122.1716,37.801),(-122.1703,37.799)]
+I- 580 Ramp [(-122.1742,37.817),(-122.1752,37.822),(-122.177,37.833)]
+I- 580 Ramp [(-122.1764,37.839),(-122.1756,37.838),(-122.1743,37.833)]
+I- 580 Ramp [(-122.1773,37.839),(-122.1764,37.839)]
+I- 580 Ramp [(-122.1789,37.838),(-122.1773,37.839)]
+I- 580 Ramp [(-122.1823,37.838),(-122.182011,37.83754),(-122.180678,37.83541),(-122.1798,37.834),(-122.1798,37.838)]
+I- 580 Ramp [(-122.1873,37.838),(-122.1868,37.841),(-122.1866,37.844),(-122.1854,37.841),(-122.1847,37.84)]
+I- 580 Ramp [(-122.1895,37.844),(-122.1886,37.845)]
+I- 580 Ramp [(-122.1909,37.85),(-122.1892,37.848),(-122.1879,37.844)]
+I- 580 Ramp [(-122.1917,37.853),(-122.1927,37.863)]
+I- 580 Ramp [(-122.1924,37.854),(-122.189775,37.84425),(-122.1889,37.841)]
+I- 580 Ramp [(-122.1975,37.878),(-122.196458,37.87516),(-122.1964,37.875),(-122.1951,37.871),(-122.1942,37.869)]
+I- 580 Ramp [(-122.2017,37.923),(-122.2029,37.928)]
+I- 580 Ramp [(-122.2027,37.918),(-122.2037,37.93),(-122.2043,37.938)]
+I- 580 Ramp [(-122.2079,37.958),(-122.2099,37.962)]
+I- 580 Ramp [(-122.2085,37.962),(-122.209,37.966)]
+I- 580 Ramp [(-122.2158,37.985),(-122.2173,37.989),(-122.2178,37.991),(-122.218357,37.99071),(-122.219392,37.99016),(-122.2197,37.99)]
+I- 580 Ramp [(-122.2161,37.976),(-122.218,37.982),(-122.218481,37.98426),(-122.2197,37.99)]
+I- 580 Ramp [(-122.2271,37.001),(-122.2261,37.003)]
+I- 580 Ramp [(-122.2278,37.013),(-122.2261,37.003)]
+I- 580 Ramp [(-122.2365,37.07),(-122.2346,37.052),(-122.2338,37.044)]
+I- 580 Ramp [(-122.2428,37.096),(-122.2414,37.091),(-122.241182,37.09018),(-122.2406,37.088)]
+I- 580 Ramp [(-122.2453,37.095),(-122.2414,37.089)]
+I- 580 Ramp [(-122.249,37.121),(-122.2476,37.12)]
+I- 580 Ramp [(-122.2491,37.115),(-122.2489,37.113)]
+I- 580 Ramp [(-122.2494,37.125),(-122.2489,37.113)]
+I- 580 Ramp [(-122.2501,37.148),(-122.249485,37.14041),(-122.2484,37.127)]
+I- 580 Ramp [(-122.2529,37.197),(-122.2525,37.192),(-122.2517,37.174)]
+I- 580 Ramp [(-122.2535,37.192),(-122.253,37.182)]
+I- 580 Ramp [(-122.2535,37.192),(-122.2539,37.2)]
+I- 580 Ramp [(-122.2547,37.205),(-122.254,37.205),(-122.2538,37.202),(-122.2535,37.196)]
+I- 580 Ramp [(-122.2564,37.213),(-122.2542,37.199),(-122.2538,37.19)]
+I- 580 Ramp [(-122.2564,37.213),(-122.2544,37.197),(-122.2538,37.19)]
+I- 580 Ramp [(-122.2604,37.222),(-122.2613,37.23)]
+I- 580 Ramp [(-122.2639,37.238),(-122.2659,37.239),(-122.2664,37.238),(-122.2676,37.231),(-122.268,37.227)]
+I- 580 Ramp [(-122.267,37.261),(-122.2671,37.263)]
+I- 580 Ramp [(-122.2671,37.245),(-122.2673,37.248)]
+I- 580 Ramp [(-122.2671,37.245),(-122.2675,37.243)]
+I- 580 Ramp [(-122.2673,37.248),(-122.2676,37.25),(-122.2677,37.252)]
+I- 580 Ramp [(-122.2676,37.273),(-122.267231,37.28349),(-122.266927,37.29214)]
+I- 580 Ramp [(-122.2676,37.273),(-122.2679,37.264),(-122.2677,37.252)]
+I- 580 Ramp [(-122.2677,37.242),(-122.2675,37.243)]
+I- 580 Ramp [(-122.2677,37.242),(-122.2681,37.238),(-122.2678,37.233)]
+I- 580 Ramp [(-122.2677,37.242),(-122.2681,37.244)]
+I- 580 Ramp [(-122.2677,37.242),(-122.2683,37.243)]
+I- 580 Ramp [(-122.2677,37.252),(-122.267508,37.25368),(-122.267,37.261)]
+I- 580 Ramp [(-122.2683,37.243),(-122.2685,37.243)]
+I- 580 Ramp [(-122.268532,37.2449),(-122.2685,37.243)]
+I- 580 Ramp [(-122.2686,37.249),(-122.2682,37.245),(-122.2681,37.244)]
+I- 580 Ramp [(-122.2686,37.249),(-122.268532,37.2449)]
+I- 580 Ramp [(-122.2686,37.249),(-122.2701,37.255)]
+I- 580 Ramp [(-122.2695,37.247),(-122.2686,37.249),(-122.2677,37.252)]
+I- 580 Ramp [(-122.2695,37.247),(-122.2692,37.244),(-122.2684,37.234)]
+I- 580 Ramp [(-122.2716,37.259),(-122.2734,37.259)]
+I- 580 Ramp [(-122.2806,37.275),(-122.2818,37.28),(-122.2822,37.281),(-122.2837,37.276)]
+I- 580 Ramp [(-122.2913,37.278),(-122.2917,37.282)]
+I- 580 Ramp [(-122.2917,37.282),(-122.2921,37.286)]
+I- 580/I-680 Ramp ((-121.9207,37.988),(-121.9192,37.016))
+I- 580/I-680 Ramp ((-121.9218,37.021),(-121.9237,37.017))
+I- 580/I-680 Ramp [(-121.9213,37.005),(-121.9203,37.013),(-121.9192,37.016),(-121.918,37.02)]
+I- 580/I-680 Ramp [(-121.922017,37.02317),(-121.9212,37.021)]
+I- 580/I-680 Ramp [(-121.9225,37.034),(-121.9218,37.031),(-121.9213,37.026),(-121.919443,37.02043),(-121.921326,37.01739)]
+I- 580/I-680 Ramp [(-121.9243,37.006),(-121.9238,37.005),(-121.9225,37.008),(-121.92154,37.0104)]
+I- 580/I-680 Ramp [(-121.9243,37.006),(-121.924389,37.00779),(-121.9237,37.017),(-121.9231,37.022),(-121.9227,37.029),(-121.9229,37.039)]
+I- 680 ((-121.8706,37.038),(-121.8709,37.047))
+I- 680 ((-121.939,37.15),(-121.9387,37.145),(-121.9373,37.125),(-121.934242,37.07643),(-121.933886,37.0709),(-121.9337,37.068),(-121.933122,37.06139),(-121.932736,37.05698),(-121.93222,37.05108),(-121.931844,37.04678),(-121.930113,37.027),(-121.926829,37),(-121.9265,37.998),(-121.9217,37.96),(-121.9203,37.949),(-121.9184,37.934))
+I- 680 [(-121.8709,37.047),(-121.8831,37.366),(-121.8833,37.376)]
+I- 680 [(-121.8852,37.422),(-121.8874,37.444),(-121.8902,37.47),(-121.8905,37.472),(-121.8907,37.474),(-121.8985,37.545),(-121.8994,37.553),(-121.9007,37.565)]
+I- 680 [(-121.8867,37.732),(-121.8845,37.744),(-121.8818,37.756),(-121.8761,37.781),(-121.8712,37.857)]
+I- 680 [(-121.902447,37.64695),(-121.903435,37.65882)]
+I- 680 [(-121.9101,37.715),(-121.909801,37.70519),(-121.909562,37.69734),(-121.909493,37.69507),(-121.9094,37.692),(-121.909295,37.68862),(-121.909145,37.6838),(-121.909103,37.68245),(-121.908715,37.66995),(-121.908524,37.6638),(-121.9082,37.653)]
+I- 680 [(-121.9101,37.715),(-121.911269,37.74682),(-121.9119,37.764),(-121.9124,37.776),(-121.9174,37.905),(-121.9194,37.957),(-121.9207,37.988)]
+I- 680 [(-121.9139,37.562),(-121.9077,37.609)]
+I- 680 [(-121.9139,37.562),(-121.915,37.54),(-121.9156,37.532),(-121.9165,37.519),(-121.9174,37.504),(-121.918,37.493),(-121.92,37.438),(-121.9202,37.435),(-121.9233,37.404),(-121.9238,37.402)]
+I- 680 [(-121.9184,37.934),(-121.917,37.913),(-121.9122,37.83),(-121.9052,37.702)]
+I- 680 [(-121.92154,37.0104),(-121.9217,37.014)]
+I- 680 [(-121.9217,37.014),(-121.9218,37.021)]
+I- 680 [(-121.9229,37.039),(-121.9243,37.057),(-121.9286,37.106),(-121.929195,37.11329),(-121.929661,37.119),(-121.932027,37.148),(-121.93391,37.17107),(-121.9357,37.193),(-121.936421,37.20193),(-121.9378,37.219)]
+I- 680 [(-121.939,37.15),(-121.9418,37.195)]
+I- 680 Ramp ((-121.9007,37.565),(-121.9008,37.558))
+I- 680 Ramp ((-121.921,37.965),(-121.9198,37.96),(-121.9208,37.957),(-121.9199,37.951),(-121.9187,37.941))
+I- 680 Ramp ((-121.9247,37.932),(-121.9211,37.944),(-121.9201,37.944))
+I- 680 Ramp ((-121.9368,37.149),(-121.937,37.144))
+I- 680 Ramp ((-121.9372,37.335),(-121.938,37.32))
+I- 680 Ramp ((-121.9402,37.143),(-121.94,37.139))
+I- 680 Ramp ((-121.9418,37.314),(-121.9414,37.32))
+I- 680 Ramp ((-121.9422,37.314),(-121.9423,37.318))
+I- 680 Ramp [(-121.8698,37.01),(-121.8706,37.038)]
+I- 680 Ramp [(-121.8701,37.878),(-121.8681,37.881)]
+I- 680 Ramp [(-121.8703,37.891),(-121.8712,37.857),(-121.8678,37.875)]
+I- 680 Ramp [(-121.8709,37.924),(-121.8686,37.925)]
+I- 680 Ramp [(-121.8712,37.01),(-121.8709,37.047)]
+I- 680 Ramp [(-121.8818,37.756),(-121.8842,37.741),(-121.8867,37.732)]
+I- 680 Ramp [(-121.8833,37.376),(-121.8833,37.392),(-121.883,37.4),(-121.8835,37.402),(-121.8852,37.422)]
+I- 680 Ramp [(-121.8839,37.397),(-121.8847,37.394),(-121.884,37.399)]
+I- 680 Ramp [(-121.8985,37.545),(-121.9001,37.565),(-121.8999,37.571),(-121.9008,37.572),(-121.903,37.586)]
+I- 680 Ramp [(-121.8994,37.553),(-121.9008,37.558),(-121.9014,37.565)]
+I- 680 Ramp [(-121.9027,37.672),(-121.902847,37.6715),(-121.90161,37.64898)]
+I- 680 Ramp [(-121.905,37.699),(-121.9004,37.677),(-121.90161,37.64898)]
+I- 680 Ramp [(-121.9052,37.702),(-121.9047,37.667),(-121.903435,37.65882)]
+I- 680 Ramp [(-121.9077,37.609),(-121.9036,37.614)]
+I- 680 Ramp [(-121.9077,37.609),(-121.9053,37.625)]
+I- 680 Ramp [(-121.9167,37.5),(-121.9174,37.504)]
+I- 680 Ramp [(-121.92,37.438),(-121.9218,37.424),(-121.9238,37.408),(-121.9252,37.392)]
+I- 680 Ramp [(-121.921,37.965),(-121.922,37.966),(-121.9214,37.961)]
+I- 680 Ramp [(-121.9226,37.394),(-121.9232,37.392),(-121.9252,37.392)]
+I- 680 Ramp [(-121.9238,37.402),(-121.9234,37.395),(-121.923,37.399)]
+I- 680 Ramp [(-121.9265,37.998),(-121.9242,37.983),(-121.922383,37.9786),(-121.9198,37.975),(-121.9195,37.954),(-121.9187,37.941),(-121.9184,37.934)]
+I- 680 Ramp [(-121.9265,37.998),(-121.9249,37.98),(-121.9227,37.963),(-121.9221,37.959),(-121.9214,37.954),(-121.9206,37.947),(-121.9201,37.944),(-121.9184,37.934)]
+I- 680 Ramp [(-121.9373,37.148),(-121.937,37.144),(-121.9373,37.125)]
+I- 680 Ramp [(-121.9387,37.145),(-121.9376,37.147)]
+I- 680 Ramp [(-121.9394,37.144),(-121.9387,37.145)]
+I- 680 Ramp [(-121.9401,37.233),(-121.9378,37.219),(-121.937899,37.22791),(-121.938,37.237)]
+I- 680 Ramp [(-121.9406,37.142),(-121.94,37.139),(-121.9373,37.125)]
+I- 680 Ramp [(-121.9426,37.315),(-121.9423,37.318),(-121.9414,37.32),(-121.9394,37.324),(-121.938,37.32),(-121.9368,37.313)]
+I- 80 ((-122.2927,37.299),(-122.2928,37.293),(-122.2932,37.284))
+I- 80 ((-122.2931,37.301),(-122.2949,37.279))
+I- 80 ((-122.2937,37.277),(-122.3016,37.262))
+I- 80 ((-122.2949,37.279),(-122.2962,37.273))
+I- 80 ((-122.2962,37.273),(-122.3004,37.264))
+I- 80 ((-122.3004,37.264),(-122.3016,37.262),(-122.3051,37.254),(-122.3083,37.249))
+I- 80 ((-122.307,37.902),(-122.3074,37.896),(-122.307512,37.89327),(-122.3081,37.879),(-122.3077,37.861))
+I- 80 [(-122.2932,37.284),(-122.2934,37.28)]
+I- 80 [(-122.2981,37.492),(-122.2978,37.48),(-122.2976,37.473),(-122.2971,37.452),(-122.2962,37.413)]
+I- 80 [(-122.3023,37.644),(-122.2988,37.518)]
+I- 80 [(-122.3062,37.774),(-122.3038,37.695)]
+I- 80 [(-122.3065,37.935),(-122.3065,37.913),(-122.3067,37.905),(-122.307,37.902)]
+I- 80 [(-122.3075,37.828),(-122.3076,37.831)]
+I- 80 [(-122.3075,37.828),(-122.3077,37.822),(-122.3074,37.817),(-122.3069,37.801),(-122.3068,37.795)]
+I- 80 [(-122.308,37.98),(-122.3066,37.943),(-122.3056,37.912),(-122.3057,37.908),(-122.305805,37.90511),(-122.3061,37.897),(-122.3064,37.893),(-122.3073,37.881),(-122.3073,37.877),(-122.3077,37.861),(-122.3076,37.831)]
+I- 80 [(-122.3083,37.249),(-122.3281,37.216),(-122.348,37.175)]
+I- 80 Ramp ((-122.2944,37.491),(-122.2956,37.484),(-122.2965,37.478))
+I- 80 Ramp ((-122.2983,37.501),(-122.2979,37.499))
+I- 80 Ramp ((-122.3017,37.67),(-122.3022,37.662))
+I- 80 Ramp [(-122.2871,37.266),(-122.2901,37.274)]
+I- 80 Ramp [(-122.2883,37.247),(-122.2904,37.267),(-122.2909,37.273),(-122.2918,37.275),(-122.2937,37.277)]
+I- 80 Ramp [(-122.2885,37.254),(-122.2883,37.247)]
+I- 80 Ramp [(-122.2885,37.254),(-122.2893,37.266)]
+I- 80 Ramp [(-122.2918,37.279),(-122.2926,37.281)]
+I- 80 Ramp [(-122.2923,37.282),(-122.2926,37.283),(-122.2932,37.284)]
+I- 80 Ramp [(-122.2934,37.28),(-122.2926,37.281)]
+I- 80 Ramp [(-122.2934,37.28),(-122.2962,37.273)]
+I- 80 Ramp [(-122.2957,37.396),(-122.2946,37.384),(-122.2948,37.367)]
+I- 80 Ramp [(-122.2962,37.413),(-122.2959,37.382),(-122.2951,37.372)]
+I- 80 Ramp [(-122.2984,37.506),(-122.2977,37.502),(-122.2979,37.499),(-122.2956,37.489),(-122.2962,37.483),(-122.2965,37.478),(-122.2971,37.452)]
+I- 80 Ramp [(-122.2988,37.518),(-122.299,37.5),(-122.2994,37.488),(-122.2995,37.477),(-122.2971,37.452)]
+I- 80 Ramp [(-122.3038,37.695),(-122.3021,37.67),(-122.3022,37.662),(-122.3023,37.644)]
+I- 80 Ramp [(-122.3038,37.695),(-122.3036,37.674),(-122.3034,37.667),(-122.3033,37.661),(-122.3023,37.644)]
+I- 80 Ramp [(-122.3044,37.25),(-122.3051,37.254)]
+I- 80 Ramp [(-122.3044,37.25),(-122.3083,37.249)]
+I- 80 Ramp [(-122.3051,37.254),(-122.3083,37.249)]
+I- 80 Ramp [(-122.3051,37.91),(-122.3056,37.912)]
+I- 80 Ramp [(-122.3051,37.91),(-122.3057,37.908)]
+I- 80 Ramp [(-122.306,37.876),(-122.3066,37.869),(-122.3067,37.843),(-122.3069,37.818)]
+I- 80 Ramp [(-122.3068,37.795),(-122.306,37.783),(-122.3062,37.774)]
+I- 80 Ramp [(-122.3068,37.795),(-122.3069,37.782),(-122.3062,37.774)]
+I- 80 Ramp [(-122.3069,37.925),(-122.3065,37.935)]
+I- 80 Ramp [(-122.307,37.902),(-122.3073,37.881)]
+I- 80 Ramp [(-122.3072,37.916),(-122.3074,37.896)]
+I- 80 Ramp [(-122.307624,37.87781),(-122.307512,37.89327)]
+I- 80 Ramp [(-122.3078,37.93),(-122.3081,37.879)]
+I- 80 Ramp [(-122.3088,37.885),(-122.3076,37.831)]
+I- 880 ((-121.9669,37.075),(-121.9663,37.071),(-121.9656,37.065),(-121.9618,37.037),(-121.95689,37),(-121.948,37.933))
+I- 880 [(-121.9357,37.83),(-121.9356,37.826),(-121.9351,37.819),(-121.9349,37.813),(-121.9337,37.788),(-121.9327,37.767),(-121.9232,37.57),(-121.9229,37.563),(-121.9229,37.561),(-121.922487,37.55412)]
+I- 880 [(-121.937,37.852),(-121.9368,37.848)]
+I- 880 [(-121.948,37.933),(-121.9471,37.925),(-121.9467,37.923),(-121.946,37.918),(-121.9452,37.912),(-121.937,37.852)]
+I- 880 [(-121.9995,37.289),(-121.998758,37.28558),(-121.998013,37.282),(-121.996913,37.27613),(-121.9929,37.255),(-121.9919,37.252),(-121.991111,37.24795),(-121.990277,37.24367),(-121.989597,37.24018),(-121.9882,37.233),(-121.9871,37.229),(-121.9865,37.226),(-121.9848,37.216),(-121.982,37.196),(-121.9805,37.186),(-121.975936,37.14723),(-121.9712,37.107)]
+I- 880 [(-122.0219,37.466),(-122.0205,37.447),(-122.020331,37.44447),(-122.020008,37.43962),(-122.0195,37.432),(-122.0193,37.429),(-122.0164,37.393),(-122.010219,37.34771),(-122.0041,37.313)]
+I- 880 [(-122.0375,37.632),(-122.0359,37.619),(-122.0358,37.616),(-122.034514,37.60409),(-122.031876,37.57965),(-122.031193,37.57332),(-122.03016,37.56375),(-122.02943,37.55698),(-122.028689,37.54929),(-122.027833,37.53908),(-122.025979,37.51698),(-122.0238,37.491)]
+I- 880 [(-122.0469,37.774),(-122.0466,37.765),(-122.0465,37.761),(-122.045041,37.72234),(-122.0445,37.708),(-122.0426,37.686),(-122.041515,37.67425),(-122.0414,37.673),(-122.0398,37.656)]
+I- 880 [(-122.0612,37.003),(-122.0604,37.991),(-122.0596,37.982),(-122.0585,37.967),(-122.0583,37.961),(-122.0553,37.918),(-122.053635,37.89475),(-122.050759,37.8546),(-122.05,37.844),(-122.0485,37.817),(-122.0483,37.813),(-122.0482,37.811)]
+I- 880 [(-122.0666,37.085),(-122.0656,37.067),(-122.0653,37.062),(-122.0644,37.049),(-122.0635,37.036),(-122.0618,37.011)]
+I- 880 [(-122.0831,37.312),(-122.0819,37.296),(-122.081,37.285),(-122.0786,37.248),(-122.078,37.24),(-122.077642,37.23496),(-122.076983,37.22567),(-122.076599,37.22026),(-122.076229,37.21505),(-122.0758,37.209)]
+I- 880 [(-122.0978,37.528),(-122.096,37.496),(-122.0931,37.453),(-122.09277,37.4496),(-122.090189,37.41442),(-122.0896,37.405),(-122.085,37.34)]
+I- 880 [(-122.1048,37.638),(-122.1045,37.633),(-122.1042,37.63),(-122.1033,37.617),(-122.1029,37.61)]
+I- 880 [(-122.1365,37.902),(-122.1358,37.898),(-122.1333,37.881),(-122.1323,37.874),(-122.1311,37.866),(-122.1308,37.865),(-122.1307,37.864),(-122.1289,37.851),(-122.1277,37.843),(-122.1264,37.834),(-122.1231,37.812),(-122.1165,37.766),(-122.1104,37.72),(-122.109695,37.71094),(-122.109,37.702),(-122.108312,37.69168),(-122.1076,37.681)]
+I- 880 [(-122.1365,37.902),(-122.1376,37.91)]
+I- 880 [(-122.1755,37.185),(-122.1747,37.178),(-122.1742,37.173),(-122.1692,37.126),(-122.167792,37.11594),(-122.16757,37.11435),(-122.1671,37.111),(-122.1655,37.1),(-122.165169,37.09811),(-122.1641,37.092),(-122.1596,37.061),(-122.158381,37.05275),(-122.155991,37.03657),(-122.1531,37.017),(-122.1478,37.98),(-122.1407,37.932),(-122.1394,37.924),(-122.1389,37.92),(-122.1376,37.91)]
+I- 880 [(-122.1761,37.191),(-122.1757,37.187)]
+I- 880 [(-122.1947,37.394),(-122.194,37.385),(-122.1933,37.378),(-122.1882,37.32),(-122.1879,37.317),(-122.1877,37.315),(-122.1876,37.312),(-122.1873,37.309),(-122.1829,37.26),(-122.1818,37.249),(-122.1804,37.234),(-122.1802,37.231),(-122.1776,37.206),(-122.1768,37.197)]
+I- 880 [(-122.2068,37.534),(-122.2066,37.53),(-122.206,37.522),(-122.2005,37.46),(-122.1967,37.418),(-122.1959,37.407)]
+I- 880 [(-122.2214,37.711),(-122.2202,37.699),(-122.2199,37.695),(-122.219,37.682),(-122.2184,37.672),(-122.2173,37.652),(-122.2159,37.638),(-122.2144,37.616),(-122.2138,37.612),(-122.2135,37.609),(-122.212,37.592),(-122.2116,37.586),(-122.2111,37.581)]
+I- 880 [(-122.236,37.783),(-122.2352,37.767),(-122.2348,37.764),(-122.2339,37.758),(-122.2329,37.752),(-122.2323,37.748),(-122.2319,37.746),(-122.2316,37.746),(-122.2312,37.744),(-122.2301,37.743),(-122.2269,37.73),(-122.2267,37.73),(-122.222,37.713)]
+I- 880 [(-122.2707,37.975),(-122.2693,37.972),(-122.2681,37.966),(-122.267,37.962),(-122.2659,37.957),(-122.2648,37.952),(-122.2636,37.946),(-122.2625,37.935),(-122.2617,37.927),(-122.2607,37.921),(-122.2593,37.916),(-122.258,37.911),(-122.2536,37.898),(-122.2432,37.858),(-122.2408,37.845),(-122.2386,37.827),(-122.2374,37.811)]
+I- 880 [(-122.291,37.052),(-122.2898,37.042),(-122.2888,37.038),(-122.2878,37.036),(-122.2863,37.032)]
+I- 880 Ramp ((-121.9349,37.813),(-121.935,37.828))
+I- 880 Ramp ((-121.9351,37.819),(-121.935,37.828),(-121.9339,37.847))
+I- 880 Ramp ((-121.936,37.837),(-121.9351,37.835))
+I- 880 Ramp ((-121.9368,37.848),(-121.9362,37.835))
+I- 880 Ramp ((-121.9376,37.834),(-121.937,37.836))
+I- 880 Ramp ((-121.9477,37.91),(-121.9469,37.911),(-121.9463,37.911))
+I- 880 Ramp ((-121.9682,37.065),(-121.9686,37.066))
+I- 880 Ramp ((-122.0008,37.31),(-122.0002,37.308))
+I- 880 Ramp ((-122.0013,37.298),(-122.0013,37.287))
+I- 880 Ramp ((-122.0369,37.645),(-122.0364,37.646))
+I- 880 Ramp ((-122.0477,37.798),(-122.0466,37.789),(-122.0461,37.785))
+I- 880 Ramp ((-122.0477,37.798),(-122.0482,37.8))
+I- 880 Ramp ((-122.0585,37.967),(-122.0577,37.974),(-122.0548,37.966),(-122.055168,37.96828),(-122.0577,37.984))
+I- 880 Ramp ((-122.0919,37.465),(-122.0931,37.461))
+I- 880 Ramp ((-122.0932,37.439),(-122.0922,37.437),(-122.0915,37.427))
+I- 880 Ramp ((-122.1005,37.571),(-122.1003,37.565),(-122.0997,37.548))
+I- 880 Ramp ((-122.1286,37.836),(-122.1283,37.839))
+I- 880 Ramp ((-122.1335,37.901),(-122.1358,37.905))
+I- 880 Ramp ((-122.1378,37.923),(-122.1374,37.925))
+I- 880 Ramp ((-122.1757,37.187),(-122.1754,37.191))
+I- 880 Ramp ((-122.1863,37.322),(-122.187,37.322))
+I- 880 Ramp ((-122.1876,37.312),(-122.1877,37.309))
+I- 880 Ramp ((-122.2054,37.542),(-122.2057,37.533),(-122.2059,37.526))
+I- 880 Ramp ((-122.2091,37.532),(-122.2089,37.535),(-122.2072,37.535))
+I- 880 Ramp ((-122.2187,37.684),(-122.219,37.688),(-122.2194,37.697))
+I- 880 Ramp ((-122.2301,37.743),(-122.2316,37.751))
+I- 880 Ramp ((-122.2352,37.767),(-122.2356,37.768),(-122.236,37.768))
+I- 880 Ramp [(-121.9215,37.556),(-121.92179,37.55745),(-121.9229,37.563)]
+I- 880 Ramp [(-121.9232,37.57),(-121.9234,37.559),(-121.9229,37.561)]
+I- 880 Ramp [(-121.9335,37.851),(-121.9339,37.847),(-121.9351,37.835),(-121.9357,37.83)]
+I- 880 Ramp [(-121.9343,37.85),(-121.937,37.852),(-121.937,37.836),(-121.9362,37.835),(-121.9359,37.826),(-121.9349,37.813)]
+I- 880 Ramp [(-121.946,37.918),(-121.9463,37.911),(-121.9452,37.912)]
+I- 880 Ramp [(-121.9612,37.099),(-121.9615,37.09),(-121.9616,37.087)]
+I- 880 Ramp [(-121.9618,37.037),(-121.961998,37.05205),(-121.962212,37.06831),(-121.9623,37.075),(-121.9616,37.087)]
+I- 880 Ramp [(-121.9656,37.065),(-121.9678,37.061),(-121.9676,37.065)]
+I- 880 Ramp [(-121.9688,37.061),(-121.9686,37.066),(-121.9669,37.075)]
+I- 880 Ramp [(-121.9712,37.107),(-121.9643,37.087),(-121.9623,37.085)]
+I- 880 Ramp [(-121.986,37.239),(-121.9865,37.236),(-121.9861,37.234)]
+I- 880 Ramp [(-121.9865,37.226),(-121.9872,37.22)]
+I- 880 Ramp [(-121.9871,37.229),(-121.9863,37.232)]
+I- 880 Ramp [(-121.9885,37.211),(-121.9848,37.216)]
+I- 880 Ramp [(-122.0019,37.301),(-122.002,37.293)]
+I- 880 Ramp [(-122.0041,37.313),(-122.0018,37.315),(-122.0007,37.315),(-122.0005,37.313),(-122.0002,37.308),(-121.9995,37.289)]
+I- 880 Ramp [(-122.0041,37.313),(-122.0038,37.308),(-122.0039,37.284),(-122.0013,37.287),(-121.9995,37.289)]
+I- 880 Ramp [(-122.0226,37.474),(-122.0214,37.473),(-122.0219,37.466)]
+I- 880 Ramp [(-122.0236,37.488),(-122.0231,37.458),(-122.0227,37.458),(-122.0223,37.452),(-122.0205,37.447)]
+I- 880 Ramp [(-122.0238,37.491),(-122.0215,37.483),(-122.0211,37.477),(-122.0205,37.447)]
+I- 880 Ramp [(-122.0364,37.652),(-122.0364,37.646),(-122.03618,37.63412),(-122.036027,37.62586),(-122.0359,37.619)]
+I- 880 Ramp [(-122.0374,37.639),(-122.0375,37.632)]
+I- 880 Ramp [(-122.0383,37.64),(-122.0387,37.636),(-122.0387,37.633),(-122.038,37.632)]
+I- 880 Ramp [(-122.0392,37.65),(-122.0389,37.625),(-122.0391,37.617),(-122.036099,37.6161),(-122.0358,37.616)]
+I- 880 Ramp [(-122.0398,37.656),(-122.0361,37.656)]
+I- 880 Ramp [(-122.0482,37.811),(-122.0461,37.785),(-122.0453,37.778),(-122.045,37.775)]
+I- 880 Ramp [(-122.0485,37.817),(-122.0484,37.813),(-122.0482,37.8),(-122.0481,37.794),(-122.0478,37.781),(-122.0469,37.774)]
+I- 880 Ramp [(-122.059,37.982),(-122.0577,37.984),(-122.0612,37.003)]
+I- 880 Ramp [(-122.0604,37.991),(-122.06,37.983),(-122.0605,37.983)]
+I- 880 Ramp [(-122.0618,37.011),(-122.0631,37.982),(-122.0585,37.967)]
+I- 880 Ramp [(-122.0635,37.036),(-122.064,37.049),(-122.0648,37.069)]
+I- 880 Ramp [(-122.0642,37.073),(-122.0648,37.076),(-122.0666,37.085)]
+I- 880 Ramp [(-122.0653,37.062),(-122.0663,37.064)]
+I- 880 Ramp [(-122.0758,37.209),(-122.073269,37.18911),(-122.073,37.187),(-122.0723,37.183)]
+I- 880 Ramp [(-122.0758,37.209),(-122.075697,37.2063),(-122.075,37.188),(-122.0749,37.184)]
+I- 880 Ramp [(-122.0837,37.322),(-122.0822,37.316),(-122.0831,37.312)]
+I- 880 Ramp [(-122.0837,37.322),(-122.0843,37.316)]
+I- 880 Ramp [(-122.085,37.34),(-122.0801,37.316),(-122.081,37.285)]
+I- 880 Ramp [(-122.085,37.34),(-122.0866,37.316),(-122.0819,37.296)]
+I- 880 Ramp [(-122.0918,37.466),(-122.0896,37.405)]
+I- 880 Ramp [(-122.092,37.461),(-122.0923,37.457)]
+I- 880 Ramp [(-122.0931,37.453),(-122.0937,37.469),(-122.0931,37.461),(-122.0923,37.457)]
+I- 880 Ramp [(-122.096,37.496),(-122.0941,37.464),(-122.0943,37.421),(-122.0915,37.427),(-122.0896,37.405)]
+I- 880 Ramp [(-122.0985,37.574),(-122.0986,37.567),(-122.0978,37.528)]
+I- 880 Ramp [(-122.0993,37.571),(-122.0999,37.569)]
+I- 880 Ramp [(-122.1005,37.571),(-122.1009,37.568)]
+I- 880 Ramp [(-122.1022,37.597),(-122.102055,37.59178),(-122.1019,37.562)]
+I- 880 Ramp [(-122.1029,37.61),(-122.1013,37.587),(-122.0999,37.569)]
+I- 880 Ramp [(-122.1032,37.557),(-122.0997,37.548),(-122.0978,37.528)]
+I- 880 Ramp [(-122.1076,37.681),(-122.106243,37.66549),(-122.1062,37.665),(-122.106043,37.66186),(-122.105869,37.65838),(-122.1048,37.638)]
+I- 880 Ramp [(-122.1076,37.681),(-122.107021,37.6646),(-122.107,37.664),(-122.106808,37.66184),(-122.1048,37.638)]
+I- 880 Ramp [(-122.1287,37.842),(-122.1283,37.839),(-122.1264,37.834)]
+I- 880 Ramp [(-122.1289,37.861),(-122.1284,37.855),(-122.1277,37.843)]
+I- 880 Ramp [(-122.1296,37.865),(-122.1299,37.866),(-122.1323,37.874)]
+I- 880 Ramp [(-122.1318,37.865),(-122.1319,37.866),(-122.1333,37.881)]
+I- 880 Ramp [(-122.1376,37.91),(-122.1379,37.909),(-122.1376,37.907),(-122.1365,37.902)]
+I- 880 Ramp [(-122.1379,37.891),(-122.1383,37.897),(-122.1377,37.902)]
+I- 880 Ramp [(-122.1379,37.931),(-122.137597,37.92736),(-122.1374,37.925),(-122.1373,37.924),(-122.1369,37.914),(-122.1358,37.905),(-122.1365,37.908),(-122.1358,37.898)]
+I- 880 Ramp [(-122.1407,37.932),(-122.1397,37.923),(-122.1389,37.917),(-122.1383,37.913)]
+I- 880 Ramp [(-122.164,37.106),(-122.1653,37.113),(-122.1651,37.101)]
+I- 880 Ramp [(-122.164188,37.10514),(-122.1641,37.092)]
+I- 880 Ramp [(-122.1646,37.113),(-122.163,37.111)]
+I- 880 Ramp [(-122.1646,37.113),(-122.165458,37.11379),(-122.167792,37.11594)]
+I- 880 Ramp [(-122.165757,37.09059),(-122.165169,37.09811)]
+I- 880 Ramp [(-122.165757,37.09059),(-122.16635,37.09559)]
+I- 880 Ramp [(-122.1675,37.09),(-122.1666,37.089),(-122.165804,37.08972),(-122.1655,37.09),(-122.1641,37.092)]
+I- 880 Ramp [(-122.1675,37.09),(-122.16757,37.11435)]
+I- 880 Ramp [(-122.173,37.194),(-122.1742,37.173)]
+I- 880 Ramp [(-122.1763,37.184),(-122.1761,37.191)]
+I- 880 Ramp [(-122.1763,37.193),(-122.1754,37.191),(-122.1745,37.194),(-122.1743,37.192),(-122.1737,37.196),(-122.1725,37.198)]
+I- 880 Ramp [(-122.1768,37.197),(-122.1774,37.182),(-122.1769,37.18),(-122.1758,37.181),(-122.1747,37.178)]
+I- 880 Ramp [(-122.1868,37.32),(-122.187,37.322),(-122.1873,37.321),(-122.1877,37.319),(-122.1879,37.317)]
+I- 880 Ramp [(-122.1893,37.303),(-122.1888,37.305),(-122.1885,37.307),(-122.1877,37.309),(-122.1873,37.309)]
+I- 880 Ramp [(-122.1946,37.405),(-122.1942,37.411),(-122.1951,37.411)]
+I- 880 Ramp [(-122.1946,37.405),(-122.1947,37.394)]
+I- 880 Ramp [(-122.1952,37.418),(-122.1967,37.418)]
+I- 880 Ramp [(-122.1954,37.372),(-122.1957,37.378)]
+I- 880 Ramp [(-122.1959,37.407),(-122.1958,37.396),(-122.1953,37.396),(-122.1947,37.394)]
+I- 880 Ramp [(-122.2065,37.54),(-122.2066,37.535),(-122.2063,37.531),(-122.2059,37.526),(-122.206,37.522)]
+I- 880 Ramp [(-122.2092,37.535),(-122.2074,37.536),(-122.2072,37.535),(-122.2066,37.53)]
+I- 880 Ramp [(-122.2096,37.565),(-122.2079,37.549),(-122.2061,37.541)]
+I- 880 Ramp [(-122.2111,37.581),(-122.2108,37.535)]
+I- 880 Ramp [(-122.2176,37.65),(-122.2173,37.652)]
+I- 880 Ramp [(-122.2177,37.668),(-122.2168,37.653),(-122.2159,37.638)]
+I- 880 Ramp [(-122.218,37.659),(-122.2173,37.652)]
+I- 880 Ramp [(-122.2199,37.695),(-122.2194,37.697),(-122.2197,37.7),(-122.2202,37.699)]
+I- 880 Ramp [(-122.222,37.713),(-122.2212,37.705),(-122.2207,37.697),(-122.2208,37.694),(-122.2192,37.68),(-122.2184,37.672)]
+I- 880 Ramp [(-122.2301,37.743),(-122.2294,37.735)]
+I- 880 Ramp [(-122.2316,37.746),(-122.2316,37.751),(-122.2321,37.752),(-122.2329,37.752)]
+I- 880 Ramp [(-122.2325,37.743),(-122.2319,37.746)]
+I- 880 Ramp [(-122.2361,37.785),(-122.2356,37.781)]
+I- 880 Ramp [(-122.237,37.799),(-122.236,37.777),(-122.236,37.768),(-122.2357,37.764),(-122.235,37.754)]
+I- 880 Ramp [(-122.2372,37.802),(-122.23638,37.7996),(-122.2361,37.801),(-122.2357,37.801)]
+I- 880 Ramp [(-122.2374,37.811),(-122.2374,37.796)]
+I- 880 Ramp [(-122.2536,37.898),(-122.254,37.902)]
+I- 880 Ramp [(-122.2577,37.914),(-122.258,37.911)]
+I- 880 Ramp [(-122.2636,37.946),(-122.2646,37.954)]
+I- 880 Ramp [(-122.2636,37.946),(-122.2649,37.95),(-122.2662,37.954)]
+I- 880 Ramp [(-122.268,37.969),(-122.2693,37.972)]
+I- 880 Ramp [(-122.2685,37.962),(-122.2695,37.969),(-122.2707,37.975)]
+I- 880 Ramp [(-122.2729,37.986),(-122.2744,37.986)]
+I- 880 Ramp [(-122.274,37.993),(-122.2729,37.986)]
+I- 880 Ramp [(-122.2771,37.002),(-122.278,37)]
+I- 880 Ramp [(-122.2775,37.007),(-122.2769,37.001)]
+I- 880 Ramp [(-122.2849,37.028),(-122.2845,37.026),(-122.2837,37.021)]
+I- 880 Ramp [(-122.2863,37.032),(-122.285451,37.03094),(-122.284429,37.02966),(-122.2831,37.028),(-122.2823,37.031)]
+I- 880 Ramp [(-122.291454,37.064),(-122.291088,37.06103),(-122.2903,37.05),(-122.2897,37.044),(-122.2888,37.038)]
+I- 880 Ramp [(-122.2916,37.052),(-122.2898,37.04),(-122.2888,37.038)]
+I- 980 ((-122.268,37.227),(-122.2678,37.233),(-122.2675,37.243))
+I- 980 ((-122.2688,37.2),(-122.2684,37.215),(-122.268,37.227))
+I- 980 [(-122.2675,37.243),(-122.2674,37.246)]
+I- 980 [(-122.2681,37.248),(-122.2677,37.252)]
+I- 980 [(-122.268237,37.24426),(-122.2682,37.245)]
+I- 980 [(-122.2684,37.236),(-122.2683,37.243),(-122.268237,37.24426)]
+I- 980 [(-122.2684,37.236),(-122.2684,37.234),(-122.268817,37.22458),(-122.269027,37.21557),(-122.2691,37.213),(-122.2694,37.199),(-122.2693,37.194)]
+I- 980 [(-122.2688,37.2),(-122.2688,37.197),(-122.2689,37.191)]
+I- 980 [(-122.2699,37.159),(-122.2697,37.165),(-122.2695,37.17),(-122.2693,37.18),(-122.2689,37.191)]
+I- 980 [(-122.2701,37.163),(-122.2699,37.172),(-122.2693,37.192),(-122.2693,37.194)]
+I- 980 [(-122.2783,37.006),(-122.2787,37.014),(-122.2791,37.024),(-122.279124,37.0254),(-122.2782,37.053),(-122.2778,37.061),(-122.2773,37.068)]
+I- 980 [(-122.2789,37.009),(-122.2795,37.022),(-122.279648,37.02711),(-122.2787,37.055),(-122.2782,37.062),(-122.2775,37.075),(-122.2773,37.078),(-122.2763,37.094),(-122.2758,37.1),(-122.274904,37.11235),(-122.274857,37.1131),(-122.2748,37.114)]
+I- 980 Ramp [(-122.2688,37.197),(-122.2691,37.191),(-122.2693,37.18)]
+I- 980 Ramp [(-122.2688,37.206),(-122.2688,37.203),(-122.2684,37.215)]
+I- 980 Ramp [(-122.2691,37.213),(-122.269438,37.20709),(-122.2699,37.199),(-122.27,37.194),(-122.270194,37.18382),(-122.270352,37.17552),(-122.2704,37.173)]
+I- 980 Ramp [(-122.2693,37.194),(-122.2691,37.198)]
+I- 980 Ramp [(-122.2699,37.159),(-122.2701,37.163)]
+I- 980 Ramp [(-122.2739,37.117),(-122.2745,37.119),(-122.2733,37.129)]
+I- 980 Ramp [(-122.2773,37.068),(-122.2772,37.061)]
+I- 980 Ramp [(-122.2773,37.078),(-122.2766,37.095)]
+Idaho St [(-122.28,37.437),(-122.2801,37.446)]
+Iglesia Dr [(-121.946443,37.09438),(-121.946833,37.09285)]
+Independence Ave [(-121.865283,37.46138),(-121.8634,37.446)]
+Independence Dr [(-121.8683,37.521),(-121.868866,37.50763)]
+Independence Road [(-121.9474,37.172),(-121.9508,37.163)]
+Indian Road [(-122.2209,37.153),(-122.2216,37.142)]
+Indian Way [(-122.2066,37.398),(-122.2045,37.411)]
+Indian Creek [(-121.7642,37.246),(-121.7655,37.244)]
+Indian Creek Road [(-121.7751,37.798),(-121.7646,37.799)]
+Indian Creek Road [(-121.7889,37.843),(-121.8,37.791)]
+Indian Creek Road [(-121.863754,37.77499),(-121.8644,37.782)]
+Indian Joe Creek [(-121.8273,37.142),(-121.806,37.295)]
+Indian Rock Path [(-122.2717,37.919),(-122.2727,37.922)]
+Industrial Blvd [(-122.1091,37.328),(-122.1085,37.326)]
+Industrial Pkwy [(-122.055708,37.21892),(-122.0559,37.217)]
+Industrial Pkwy [(-122.0723,37.183),(-122.0731,37.184)]
+Industrial St [(-122.1834,37.424),(-122.1833,37.425)]
+Inglewood Dr [(-121.9086,37.877),(-121.9088,37.877)]
+Inglewood Dr [(-121.909484,37.87762),(-121.9099,37.878)]
+Inglewood St [(-122.0802,37.397),(-122.0823,37.397)]
+Inglewood Common [(-121.955843,37.36729),(-121.955147,37.36923)]
+Innsbruck St [(-121.7706,37.68),(-121.7708,37.668)]
+Inverness St [(-122.1521,37.882),(-122.1523,37.877)]
+Inverness Way [(-121.752134,37.92412),(-121.751744,37.92413)]
+Iroquois Ave [(-121.7876,37.891),(-121.7876,37.899)]
+Irvington Ave [(-121.9624,37.308),(-121.9661,37.307)]
+Isabel Ave [(-121.8047,37.63301),(-121.8047,37.63248)]
+Isherwood Way [(-122.0138,37.733),(-122.0156,37.702)]
+Island Dr [(-122.2329,37.463),(-122.2336,37.455)]
+Island Dr [(-122.2399,37.334),(-122.2394,37.34),(-122.2383,37.352)]
+Island Dr [(-122.2411,37.318),(-122.2406,37.325)]
+Island Pine Ct [(-122.069,37.31),(-122.0697,37.31)]
+Isle Royal St [(-121.9695,37.178),(-121.9686,37.15),(-121.9683,37.149)]
+Isola Ct [(-122.0551,37.815),(-122.0555,37.811)]
+Ithaca St [(-122.0318,37.09),(-122.0317,37.087)]
+Ivy Dr [(-122.2434,37.01),(-122.2413,37.015)]
+J St [(-121.9762,37.754),(-121.9767,37.743)]
+Jacaranda Ct [(-121.9431,37.49),(-121.9426,37.486)]
+Jacaranda Dr [(-122.0147,37.288),(-122.013,37.287)]
+Jackson Ave [(-121.7416,37.868),(-121.7416,37.862)]
+Jackson Ct [(-121.9377,37.336),(-121.9383,37.337)]
+Jackson St [(-122.0809,37.669),(-122.0804,37.677)]
+Jackson St [(-122.0838,37.614),(-122.0832,37.624)]
+Jackson St [(-122.0845,37.6),(-122.0842,37.606)]
+Jackson St [(-122.0981,37.368),(-122.0988,37.365)]
+Jackson St [(-122.2646,37.025),(-122.2641,37.034)]
+Jackson St [(-122.2668,37.991),(-122.2664,37.997)]
+Jackson St [(-122.2689,37.955),(-122.2685,37.962)]
+Jade Cir [(-121.919585,37.25699),(-121.918784,37.24417)]
+Jamaica Way [(-122.1785,37.971),(-122.1792,37.969)]
+James Ave [(-122.0634,37.055),(-122.0598,37.052)]
+Jamestown Road [(-121.947,37.165),(-121.9476,37.163)]
+Jamison Way [(-122.075555,37.98023),(-122.073719,37.98074)]
+Jaques Ct [(-122.052072,37.68998),(-122.050458,37.69474)]
+Jaquiline St [(-121.725054,37.80597),(-121.723604,37.80449)]
+Jarvis Ave [(-122.053935,37.43593),(-122.054359,37.43078)]
+Jarvis Ave [(-122.055,37.423),(-122.0552,37.42)]
+Jason Way [(-122.0044,37.584),(-122.0033,37.577)]
+Jayar Pl [(-122.0345,37.206),(-122.0337,37.209)]
+Jayne Ave [(-122.2566,37.13),(-122.2549,37.142)]
+Jaynes St [(-122.2789,37.779),(-122.2781,37.781)]
+Jean Ct [(-122.0755,37.809),(-122.0766,37.798)]
+Jean Dr [(-122.0739,37.78),(-122.0734,37.774)]
+Jean Dr [(-122.0766,37.798),(-122.0759,37.794)]
+Jean St [(-122.2477,37.18),(-122.2468,37.187),(-122.2459,37.196)]
+Jeffer St [(-122.0746,37.856),(-122.0739,37.86)]
+Jefferson Ave [(-122.2775,37.663),(-122.2774,37.645),(-122.2771,37.627)]
+Jefferson St [(-122.2749,37.049),(-122.2745,37.055)]
+Jensen Road [(-122.03781,37.03891),(-122.0377,37.04)]
+Jensen St [(-121.754,37.805),(-121.754,37.8)]
+Jerome Ave [(-121.9239,37.294),(-121.9239,37.287)]
+Jerome Ave [(-121.9247,37.314),(-121.9244,37.309)]
+Jerrold Road [(-121.6907,37.681),(-121.6908,37.653)]
+Jessica Cir [(-122.0472,37.76),(-122.0488,37.748)]
+Jewell Ct [(-122.2048,37.467),(-122.2043,37.452)]
+Joaquin Miller Road [(-122.1797,37.083),(-122.1778,37.051)]
+Joaquin Miller Road [(-122.1914,37.128),(-122.1862,37.119),(-122.1842,37.106)]
+Joaquin Murieta Ave [(-121.9958,37.208),(-121.9962,37.201)]
+John Dr [(-122.0939,37.925),(-122.0934,37.919)]
+Johnson Dr [(-121.9145,37.901),(-121.915,37.877)]
+Johnson Industrial Dr [(-121.9096,37.014),(-121.9172,37.016)]
+Jones St [(-122.2982,37.755),(-122.2971,37.757)]
+Jones St [(-122.301984,37.74755),(-122.3015,37.749)]
+Joseph Dr [(-122.0742,37.12),(-122.0732,37.109)]
+Joshua St [(-122.0686,37.455),(-122.0686,37.449)]
+Jovan Ter [(-122.046671,37.62206),(-122.046466,37.6181)]
+Joyce St [(-122.0792,37.604),(-122.0774,37.581)]
+Juana Ave [(-122.1417,37.262),(-122.1396,37.267)]
+Juana Ave [(-122.1481,37.243),(-122.1459,37.249)]
+June Ct [(-122.1041,37.76),(-122.1045,37.769)]
+June Ct [(-122.1786,37.316),(-122.178,37.314)]
+Juneau St [(-122.1814,37.068),(-122.1801,37.049)]
+Juniper Ave [(-122.0464,37.255),(-122.0476,37.251)]
+Juniper St [(-121.7823,37.897),(-121.7815,37.9)]
+Juniper St [(-122.1592,37.961),(-122.1591,37.955)]
+Junipero Com [(-121.9919,37.796),(-121.992,37.791)]
+Jupiter Ct [(-122.064459,37.84741),(-122.064255,37.84626)]
+Kains Ave [(-122.2923,37.758),(-122.2922,37.754)]
+Kains Ave [(-122.2949,37.828),(-122.2948,37.825)]
+Kains Ave [(-122.2992,37.983),(-122.2989,37.97),(-122.2984,37.953)]
+Kaiser Dr [(-122.067163,37.47821),(-122.060402,37.51961)]
+Kaiser Creek Road [(-122.0928,37.885),(-122.0918,37.802)]
+Kamp Dr [(-121.867789,37.82326),(-121.867753,37.8292)]
+Kansas Way [(-121.9115,37.71),(-121.912,37.706)]
+Kathy Way [(-121.7292,37.825),(-121.729061,37.82514)]
+Kato Road [(-121.9185,37.626),(-121.9181,37.627)]
+Kay Ave [(-122.0968,37.433),(-122.0968,37.427)]
+Kay Ave [(-122.0969,37.398),(-122.0971,37.389)]
+Kay Ave [(-122.097,37.461),(-122.0969,37.457)]
+Kay Ct [(-121.953,37.277),(-121.9535,37.277)]
+Kearney Ave [(-122.1981,37.124),(-122.1963,37.135)]
+Keeler Ave [(-122.2552,37.892),(-122.2549,37.882)]
+Keeler Ave [(-122.2578,37.906),(-122.2579,37.899)]
+Keith Ave [(-122.2603,37.894),(-122.26,37.893)]
+Keller Ave [(-122.1353,37.702),(-122.1345,37.705)]
+Keller Ave [(-122.154,37.723),(-122.1531,37.722)]
+Kelly St [(-122.0519,37.852),(-122.0503,37.856)]
+Kelly St [(-122.0583,37.842),(-122.058,37.842)]
+Kelso Road [(-121.584782,37.94979),(-121.585132,37.94971)]
+Kenilworth Ave [(-122.1501,37.376),(-122.1498,37.371)]
+Kenilworth Ave [(-122.151,37.393),(-122.1505,37.383)]
+Kenita Way [(-122.0508,37.944),(-122.0503,37.944)]
+Kenmore Ave [(-122.2378,37.173),(-122.2386,37.162)]
+Kenmore Ct [(-122.0713,37.965),(-122.0706,37.967)]
+Kennedy Ave [(-122.0201,37.832),(-122.0203,37.824)]
+Kennet St [(-122.0309,37.129),(-122.031,37.115)]
+Kent Ave [(-122.113,37.891),(-122.1131,37.887)]
+Kent Ave [(-122.113,37.937),(-122.113,37.929)]
+Kentucky Ave [(-122.2719,37.026),(-122.2706,37.013)]
+Kentwood Way [(-121.9235,37.841),(-121.926,37.838)]
+Kenwood Dr [(-122.054303,37.63614),(-122.054133,37.62981)]
+Keoncrest Dr [(-122.2842,37.77),(-122.2835,37.771)]
+Kerlin St [(-121.9986,37.469),(-121.9979,37.465)]
+Kerwin Ave [(-122.181,37.296),(-122.1807,37.295)]
+Key Ct [(-122.1246,37.545),(-122.1244,37.553)]
+Key Route Blvd [(-122.2921,37.91),(-122.292,37.908)]
+Keys Pl [(-122.0871,37.253),(-122.0875,37.252)]
+Kildare Road [(-122.0968,37.016),(-122.0959,37)]
+Kilkenny Pl [(-122.251002,37.40535),(-122.2508,37.403)]
+Kimberly Commons [(-121.737774,37.88769),(-121.737673,37.89024)]
+King St [(-122.2733,37.504),(-122.2732,37.496)]
+King St [(-122.2738,37.558),(-122.2737,37.55)]
+King Way [(-121.9176,37.133),(-121.9185,37.13)]
+Kings Ct [(-122.0695,37.764),(-122.0689,37.756)]
+Kingsland Ave [(-122.1957,37.743),(-122.1956,37.753)]
+Kingsley St [(-122.2311,37.042),(-122.2306,37.046)]
+Kirkcaldy St [(-121.859937,37.97443),(-121.8601,37.97441)]
+Kirkham St [(-122.289,37.122),(-122.2887,37.129)]
+Kit Lane [(-122.0237,37.124),(-122.0236,37.132)]
+Kitty Hawk Road [(-121.8048,37.797),(-121.8049,37.867)]
+Klamath St [(-121.9142,37.982),(-121.9145,37.978)]
+Klamath St [(-122.1832,37.035),(-122.1815,37.023)]
+Knapp St [(-122.0594,37.062),(-122.0593,37.049)]
+Knight Dr [(-122.089468,37.17681),(-122.089434,37.17337)]
+Knight St [(-122.18,37.291),(-122.1794,37.288)]
+Knoll Way [(-122.0864,37.848),(-122.0839,37.836)]
+Knowland Ave [(-122.1957,37.816),(-122.1948,37.823)]
+Kofman Ct [(-122.2498,37.422),(-122.2497,37.417)]
+Koford Road [(-122.1903,37.296),(-122.1884,37.286)]
+Kolln St [(-121.87,37.792),(-121.8705,37.796)]
+Koopmann Creek [(-121.9442,37.181),(-121.943465,37.17671)]
+Korbel St [(-122.0648,37.742),(-122.0655,37.723)]
+Kottinger Dr [(-121.8596,37.58),(-121.859,37.57)]
+Kramer St [(-122.1406,37.834),(-122.1416,37.826)]
+Kramer St [(-122.143,37.819),(-122.1435,37.812)]
+Krause St [(-121.8731,37.863),(-121.8719,37.865)]
+L St [(-121.7684,37.772),(-121.768,37.762)]
+L St [(-121.7694,37.795),(-121.7692,37.788)]
+La Cresta [(-122.2433,37.353),(-122.2425,37.349)]
+La Cresta Ave [(-122.2175,37.06),(-122.217,37.073)]
+La Loma Ave [(-122.2554,37.844),(-122.2559,37.841)]
+La Playa Dr [(-122.1039,37.545),(-122.101,37.493)]
+La Salle Ave [(-122.2191,37.176),(-122.2206,37.158)]
+La Salle Ave [(-122.2242,37.153),(-122.2255,37.155)]
+La Vereda Road [(-122.2562,37.801),(-122.2558,37.792)]
+Ladera Ct [(-121.9444,37.068),(-121.9429,37.07)]
+Lafayette Ave [(-122.0309,37.474),(-122.0328,37.458)]
+Lafayette Ave [(-122.0336,37.451),(-122.0352,37.438)]
+Lafayette Ave [(-122.0395,37.411),(-122.0418,37.398)]
+Lafayette Ave [(-122.1602,37.293),(-122.1597,37.287)]
+Lafayette St [(-122.2559,37.646),(-122.2553,37.654)]
+Laguna Ave [(-122.2062,37.027),(-122.2058,37.03)]
+Laguna Ave [(-122.2099,37.989),(-122.2089,37)]
+Laguna Dr [(-122.1128,37.418),(-122.1132,37.418)]
+Laiolo Road [(-121.9715,37.322),(-121.9707,37.318)]
+Lake Blvd [(-122.0381,37.533),(-122.0371,37.53)]
+Lake Blvd [(-122.0393,37.539),(-122.0387,37.537)]
+Lake Chabot [(-122.0753,37.378),(-122.0762,37.367)]
+Lake Chabot Road [(-122.0988,37.133),(-122.0977,37.13)]
+Lake Chabot Road [(-122.1061,37.171),(-122.1047,37.155)]
+Lake Chabot Road [(-122.1323,37.308),(-122.132,37.307)]
+Lake Mead Dr [(-122.0533,37.873),(-122.0523,37.858),(-122.0524,37.853)]
+Lake Ontario Dr [(-122.0554,37.863),(-122.0557,37.859)]
+Lake Ontario Dr [(-122.0562,37.852),(-122.0567,37.847)]
+Lake Pillsbury Dr [(-122.0561,37.906),(-122.0573,37.901)]
+Lakecrest Ct [(-122.0947,37.107),(-122.0939,37.103)]
+Lakehurst Cir [(-122.284729,37.89025),(-122.286096,37.90364)]
+Lakeridge Ave [(-122.048299,37.84349),(-122.048127,37.83401)]
+Lakeshore Ave [(-122.2586,37.99),(-122.2556,37.006)]
+Lakeview Ave [(-122.2225,37.219),(-122.2237,37.221)]
+Lakeview Blvd [(-121.9313,37.702),(-121.936,37.784)]
+Lakewood Ct [(-122.0261,37.472),(-122.0263,37.469)]
+Lakewood Dr [(-122.0288,37.48),(-122.0269,37.477)]
+Lakewood St [(-121.9189,37.763),(-121.9191,37.772)]
+Lakewood Way [(-122.0795,37.389),(-122.0793,37.366)]
+Lambaren Ave [(-121.7825,37.822),(-121.7816,37.821)]
+Lambeth Road [(-121.7686,37.942),(-121.7684,37.947)]
+Lanai Ct [(-122.0768,37.269),(-122.0768,37.26)]
+Landing Road [(-121.947,37.809),(-121.9444,37.82)]
+Landon Ave [(-121.9785,37.258),(-121.978,37.255)]
+Langmuir Lane [(-121.9199,37.19),(-121.9215,37.197)]
+Lansdown Ct [(-121.8659,37.949),(-121.8661,37.96)]
+Larchmont Isle [(-122.2667,37.654),(-122.2671,37.647)]
+Lark Way [(-122.0563,37.8),(-122.0553,37.797)]
+Larkspur Dr [(-121.7431,37.084),(-121.7435,37.09)]
+Larkspur St [(-122.0118,37.231),(-122.011,37.227)]
+Larmer Ct [(-122.2371,37.215),(-122.2365,37.212)]
+Las Palmas Ave [(-121.9521,37.547),(-121.9513,37.539)]
+Las Palmas Ct [(-121.944,37.502),(-121.9443,37.513)]
+Las Palmas Ct [(-121.950103,37.00582),(-121.949498,37.00835)]
+Las Positas Blvd [(-121.7988,37.889),(-121.7984,37.889)]
+Las Positas Blvd [(-121.8642,37.957),(-121.8645,37.955)]
+Las Positas Blvd [(-121.907779,37.79734),(-121.908481,37.79416)]
+Las Positas Blvd [(-121.9094,37.79),(-121.9099,37.787)]
+Las Positas Blvd [(-121.915,37.769),(-121.9187,37.759)]
+Las Positas Road [(-121.7548,37.025),(-121.75,37.01778)]
+Las Positas Road [(-121.764488,37.99199),(-121.75569,37.02022)]
+Las Positas Road [(-121.7726,37.976),(-121.76841,37.98426)]
+Lassen Road [(-121.7428,37.05),(-121.742211,37.05687)]
+Latham Lane [(-122.2572,37.943),(-122.2565,37.947)]
+Latham Walk [(-122.2575,37.941),(-122.2572,37.943)]
+Lauderdale Ave [(-122.0983,37.344),(-122.0977,37.334)]
+Laurel St [(-122.0483,37.265),(-122.0476,37.251)]
+Laurette Pl [(-122.0651,37.476),(-122.0646,37.479)]
+Lauriston Ct [(-122.2032,37.45),(-122.2034,37.46)]
+Laverne Ave [(-122.1938,37.731),(-122.1928,37.721)]
+Laverne Dr [(-122.1533,37.821),(-122.1532,37.814)]
+Laverne Dr [(-122.1564,37.852),(-122.1549,37.847)]
+Lawlor St [(-122.1573,37.533),(-122.1567,37.517)]
+Lawrence Dr [(-122.0779,37.133),(-122.0756,37.141)]
+Lawton Ave [(-122.2563,37.36),(-122.256,37.366)]
+Lee Ave [(-121.7614,37.878),(-121.7615,37.898)]
+Lee Ave [(-122.1507,37.298),(-122.1506,37.291)]
+Lee St [(-122.2561,37.115),(-122.2569,37.097)]
+Leigh St [(-121.9154,37.776),(-121.9153,37.774)]
+Leighton St [(-122.0805,37.628),(-122.0797,37.632)]
+Leimert Blvd [(-122.2059,37.169),(-122.2048,37.169)]
+Leland Way [(-121.7867,37.655),(-121.7859,37.656)]
+Lemke Pl [(-121.9882,37.261),(-121.98835,37.2595)]
+Leon Ct [(-122.0249,37.525),(-122.0255,37.521)]
+Leona St [(-122.1751,37.84),(-122.1739,37.836)]
+Leonard Dr [(-122.1731,37.185),(-122.1731,37.172),(-122.1725,37.165)]
+Leonardo Way [(-122.1073,37.577),(-122.108,37.575)]
+Leroy Ave [(-122.2598,37.819),(-122.259792,37.8182)]
+Leslie St [(-121.9639,37.412),(-121.9634,37.405)]
+Leslie St [(-121.9729,37.438),(-121.971866,37.43847)]
+Lessley Ave [(-122.0727,37.866),(-122.0718,37.867)]
+Levine Ct [(-122.0836,37.75),(-122.0831,37.753)]
+Lewelling Blvd [(-122.1118,37.868),(-122.1112,37.869)]
+Lewelling Blvd [(-122.1219,37.865),(-122.1178,37.866)]
+Lewelling Blvd [(-122.1555,37.793),(-122.1572,37.787)]
+Lewis Ave [(-122.143,37.359),(-122.1433,37.339)]
+Liberty St [(-121.9795,37.499),(-121.9785,37.495)]
+Liberty St [(-122.1019,37.934),(-122.1009,37.924)]
+Libra Ct [(-121.7389,37.179),(-121.7391,37.187)]
+Lilac Loop [(-122.0182,37.805),(-122.0184,37.798)]
+Lilac St [(-122.2014,37.799),(-122.2008,37.804)]
+Lillian Ave [(-122.1358,37.044),(-122.1337,37.063)]
+Lillian St [(-121.7308,37.829),(-121.7307,37.824)]
+Lilly St [(-122.078139,37.64103),(-122.0775,37.643)]
+Lincoln Ave [(-121.7449,37.849),(-121.745,37.832)]
+Lincoln Ave [(-122.1321,37.499),(-122.1349,37.499)]
+Lincoln Ave [(-122.2064,37.063),(-122.2059,37.065)]
+Lincoln Ave [(-122.2093,37.034),(-122.2087,37.041)]
+Lincoln Ave [(-122.251,37.712),(-122.25,37.707)]
+Lincoln Ave [(-122.2549,37.729),(-122.2541,37.726)]
+Lincoln Ave [(-122.2628,37.751),(-122.2615,37.751)]
+Lincoln Ave [(-122.2674,37.752),(-122.2667,37.753)]
+Lincoln Ave [(-122.2721,37.754),(-122.2699,37.753)]
+Lincoln Ave [(-122.2786,37.756),(-122.276,37.755)]
+Lincoln Way [(-122.1987,37.117),(-122.198,37.101)]
+Linda Way [(-121.8657,37.573),(-121.866,37.566)]
+Lindbergh Ave [(-121.8089,37.973),(-121.8072,37.973)]
+Lindbergh Ave [(-121.8151,37.972),(-121.8118,37.971),(-121.8098,37.973)]
+Lindemann Road [(-121.558002,37.00213),(-121.558002,37.00663)]
+Linden St [(-121.7733,37.876),(-121.772,37.879)]
+Linden St [(-121.7782,37.861),(-121.777,37.865),(-121.7757,37.868)]
+Linden St [(-122.0692,37.83),(-122.0678,37.833)]
+Linden St [(-122.2751,37.344),(-122.275,37.35)]
+Linden St [(-122.2832,37.096),(-122.2827,37.112)]
+Linden St [(-122.2842,37.067),(-122.2837,37.082)]
+Linden St [(-122.2854,37.035),(-122.2853,37.038)]
+Linden St [(-122.2867,37.998),(-122.2864,37.008)]
+Linmore Dr [(-121.9202,37.39),(-121.9191,37.388)]
+Lisbon Ave [(-121.7818,37.71),(-121.7808,37.71)]
+Little Foot Dr [(-121.9223,37.064),(-121.9228,37.064)]
+Livermore Ave [(-121.7509,37.715),(-121.750474,37.71229)]
+Livermore Ave [(-121.7553,37.744),(-121.7539,37.737)]
+Livermore Ave [(-121.7589,37.765),(-121.7584,37.763)]
+Livermore Ave [(-121.7662,37.811),(-121.7656,37.808)]
+Livermore Ave [(-121.7687,37.448),(-121.769,37.375)]
+Livermore Ave [(-121.7699,37.863),(-121.7703,37.874)]
+Livermore Ave [(-121.772719,37.99085),(-121.7728,37.001)]
+Livermore Commons [(-121.926632,37.3099),(-121.926508,37.30113)]
+Locksley Ave [(-122.2547,37.422),(-122.2534,37.438)]
+Lockwood Ave [(-121.9401,37.366),(-121.9408,37.364)]
+Lockwood St [(-122.1802,37.628),(-122.1778,37.601)]
+Locust St [(-121.7815,37.876),(-121.7791,37.881)]
+Locust St [(-122.0435,37.315),(-122.0435,37.312)]
+Locust St [(-122.1606,37.007),(-122.1593,37.987)]
+Locust St [(-122.1813,37.578),(-122.1807,37.572)]
+Logan Ct [(-122.0053,37.492),(-122.0061,37.484)]
+Logan Dr [(-121.9862,37.39),(-121.9856,37.384)]
+Logan Dr [(-121.9913,37.423),(-121.990341,37.41763)]
+Loma Dr [(-121.9202,37.39),(-121.9204,37.394)]
+Loma Vista Ave [(-122.1952,37.942),(-122.1933,37.952)]
+Lomitas Ave [(-121.773262,37.59041),(-121.77308,37.59042),(-121.772998,37.59043)]
+Lomitas Ave [(-121.7807,37.574),(-121.7804,37.586)]
+Longridge Road [(-122.2345,37.096),(-122.2317,37.099)]
+Longview Dr [(-121.904,37.486),(-121.9047,37.482)]
+Longview Dr [(-122.1277,37.257),(-122.1277,37.252)]
+Longwood Ct [(-122.1036,37.606),(-122.1038,37.603)]
+Lori Way [(-122.0446,37.845),(-122.0441,37.835)]
+Loro Pl [(-121.9447,37.577),(-121.9458,37.582)]
+Lorren Dr [(-121.9911,37.491),(-121.9926,37.473)]
+Los Banos St [(-122.102,37.914),(-122.1013,37.91)]
+Los Banos St [(-122.1064,37.965),(-122.1057,37.956)]
+Louise Lane [(-122.0749,37.764),(-122.0754,37.758)]
+Lowell St [(-122.275,37.384),(-122.2752,37.389)]
+Lowell St [(-122.2757,37.41),(-122.2759,37.417)]
+Lowry Road [(-122.052627,37.83339),(-122.0531,37.827),(-122.0538,37.818)]
+Lucot St [(-122.1091,37.746),(-122.1096,37.743)]
+Luna Ave [(-122.1238,37.117),(-122.123,37.124)]
+Lunar Way [(-122.0668,37.883),(-122.0669,37.877)]
+Lurene Dr [(-121.918603,37.24972),(-121.919428,37.24785)]
+Luzon Ct [(-121.9328,37.28),(-121.9321,37.279)]
+Lyman Road [(-122.2124,37.068),(-122.2105,37.093)]
+Lynde St [(-122.2195,37.912),(-122.2187,37.91),(-122.2173,37.908)]
+Lyndhurst St [(-122.1823,37.354),(-122.1807,37.348)]
+Lyra St [(-121.9186,37.766),(-121.9184,37.762)]
+M St [(-121.7731,37.842),(-121.7736,37.853)]
+M St [(-122.0851,37.754),(-122.0845,37.742)]
+Maar Ave [(-121.9573,37.693),(-121.9565,37.701)]
+Mabel Ave [(-122.0774,37.029),(-122.0728,37.029)]
+Mabel St [(-122.2841,37.591),(-122.284,37.583)]
+Mac Arthur Blvd [(-122.139,37.34),(-122.138812,37.34)]
+Mac Arthur Blvd [(-122.1394,37.356),(-122.139376,37.35503)]
+Mac Arthur Blvd [(-122.1407,37.372),(-122.14,37.364)]
+Mac Arthur Blvd [(-122.1495,37.412),(-122.1487,37.408)]
+Mac Arthur Blvd [(-122.1552,37.454),(-122.1541,37.446)]
+Mac Arthur Blvd [(-122.1605,37.553),(-122.1596,37.525)]
+Mac Arthur Blvd [(-122.1636,37.618),(-122.1629,37.612)]
+Mac Arthur Blvd [(-122.1697,37.68),(-122.1696,37.679)]
+Mac Arthur Blvd [(-122.185,37.757),(-122.1843,37.753)]
+Mac Arthur Blvd [(-122.1868,37.773),(-122.1861,37.767)]
+Mac Arthur Blvd [(-122.188,37.837),(-122.1871,37.833)]
+Mac Arthur Blvd [(-122.207,37.985),(-122.204,37.973)]
+Mac Arthur Blvd [(-122.2239,37.007),(-122.2229,37.005)]
+Mac Arthur Blvd [(-122.2258,37.01),(-122.2251,37.008)]
+Mac Arthur Blvd [(-122.2281,37.014),(-122.2278,37.013)]
+Mac Arthur Blvd [(-122.2328,37.039),(-122.2307,37.022)]
+Mac Arthur Blvd [(-122.2353,37.054),(-122.2338,37.044)]
+Mac Arthur Blvd [(-122.2405,37.072),(-122.2402,37.07)]
+Mac Arthur Blvd [(-122.252491,37.17473),(-122.253,37.182)]
+Mac Arthur Blvd [(-122.262,37.258),(-122.2631,37.26)]
+Mac Arthur Blvd [(-122.2673,37.272),(-122.2676,37.273)]
+Mackenzie Pl [(-122.0125,37.698),(-122.0115,37.715)]
+Maddux Dr [(-122.1829,37.343),(-122.1823,37.341)]
+Maddux Dr [(-122.1848,37.366),(-122.1848,37.36)]
+Madelaine Pl [(-122.0401,37.448),(-122.0395,37.448)]
+Madera Ave [(-122.19,37.814),(-122.1914,37.824)]
+Madison Ave [(-121.7414,37.777),(-121.7415,37.77)]
+Madison Ave [(-122.0628,37.078),(-122.063,37.071)]
+Madison St [(-122.267,37.962),(-122.2668,37.965)]
+Madison St [(-122.2686,37.937),(-122.2682,37.945)]
+Magee Ave [(-122.1991,37.928),(-122.1977,37.937)]
+Magnolia Cir [(-121.868186,37.81015),(-121.866849,37.80683)]
+Magnolia Dr [(-122.2313,37.296),(-122.2317,37.285)]
+Magnolia St [(-122.0361,37.306),(-122.0354,37.303)]
+Magnolia St [(-122.0409,37.333),(-122.0394,37.325)]
+Magnolia St [(-122.0971,37.5),(-122.0962,37.484)]
+Magnolia St [(-122.2864,37.104),(-122.286,37.115)]
+Magnolia St [(-122.2899,37.005),(-122.2894,37.019)]
+Main St [(-121.8743,37.615),(-121.8739,37.624)]
+Main St [(-121.8754,37.595),(-121.875,37.604)]
+Main St [(-121.8769,37.571),(-121.876403,37.57907)]
+Main St [(-121.8875,37.939),(-121.8907,37.936)]
+Main St [(-122.0817,37.729),(-122.0807,37.719)]
+Main St [(-122.0844,37.758),(-122.0836,37.75)]
+Main St [(-122.2907,37.832),(-122.2907,37.839)]
+Mairmont Dr [(-121.8725,37.8),(-121.8734,37.8)]
+Maitland Dr [(-122.2286,37.273),(-122.2277,37.265)]
+Majestic Ave [(-122.1784,37.793),(-122.1789,37.788)]
+Majestic Way [(-122.142593,37.98723),(-122.1423,37.98485)]
+Malabar Ave [(-122.0727,37.103),(-122.0711,37.103)]
+Malcolm Ave [(-122.1269,37.516),(-122.125,37.514)]
+Malcolm Ave [(-122.1366,37.469),(-122.1359,37.483)]
+Mallard Ct [(-122.0358,37.486),(-122.0367,37.492)]
+Malta Ct [(-122.1832,37.273),(-122.1823,37.278)]
+Manchester Road [(-122.1125,37.071),(-122.1116,37.071)]
+Manchester St [(-121.866725,37.98973),(-121.866734,37.98255)]
+Mandalay Road [(-122.2322,37.397),(-122.2321,37.403)]
+Manila Ave [(-122.2448,37.425),(-122.2437,37.428)]
+Mann Ave [(-122.0165,37.844),(-122.0171,37.83)]
+Manor Blvd [(-122.1402,37.912),(-122.1409,37.913)]
+Manor Blvd [(-122.1421,37.912),(-122.1426,37.912)]
+Manor Blvd [(-122.1452,37.911),(-122.1461,37.91)]
+Manor Blvd [(-122.1584,37.906),(-122.1597,37.905)]
+Manor Way [(-122.2853,37.857),(-122.2844,37.855)]
+Manter Road [(-122.0531,37.984),(-122.0522,37.98)]
+Mantilla Ave [(-122.0781,37.31),(-122.079,37.309)]
+Manzanita St [(-122.0188,37.263),(-122.0182,37.258)]
+Maple Ave [(-122.1987,37.024),(-122.197,37.035)]
+Maple Ave [(-122.2051,37.962),(-122.2047,37.965)]
+Maple Ave [(-122.21,37.909),(-122.2096,37.915)]
+Maple St [(-121.7638,37.824),(-121.7627,37.82)]
+Maple St [(-122.2888,37.796),(-122.2872,37.795)]
+Maple Leaf Dr [(-121.864615,37.81276),(-121.863868,37.79283)]
+Marabu Way [(-121.9503,37.454),(-121.9508,37.45)]
+Maraschino Ct [(-122.0249,37.842),(-122.0252,37.833)]
+Margery Dr [(-121.9766,37.338),(-121.9772,37.33)]
+Marianas [(-122.2426,37.344),(-122.243,37.339)]
+Marianas [(-122.244,37.327),(-122.244408,37.32305)]
+Marigold Ct [(-121.9177,37.748),(-121.9187,37.746)]
+Marigold Road [(-121.7383,37.123),(-121.7393,37.128)]
+Marin Ave [(-122.1044,37.614),(-122.1055,37.613)]
+Marin Ave [(-122.1101,37.608),(-122.1107,37.607)]
+Marin Ave [(-122.258,37.969),(-122.258,37.966)]
+Marin Ave [(-122.2741,37.894),(-122.272,37.901)]
+Marin Ave [(-122.2865,37.891),(-122.2856,37.895)]
+Marin Ave [(-122.2928,37.877),(-122.2925,37.877)]
+Marin Ave [(-122.2956,37.871),(-122.2948,37.873)]
+Marin Way [(-122.2443,37.898),(-122.2436,37.893)]
+Marina Blvd [(-122.163,37.111),(-122.164,37.106)]
+Marina Blvd [(-122.164188,37.10514),(-122.1651,37.101)]
+Marineview Dr [(-122.1261,37.21),(-122.1254,37.205)]
+Marineview Dr [(-122.1318,37.241),(-122.1305,37.239)]
+Maritime St [(-122.3072,37.156),(-122.3084,37.142)]
+Market Ave [(-122.0229,37.328),(-122.0244,37.312)]
+Market St [(-122.2727,37.333),(-122.2725,37.34)]
+Market St [(-122.2738,37.292),(-122.2736,37.301)]
+Market St [(-122.2741,37.426),(-122.2742,37.433)]
+Market St [(-122.2754,37.227),(-122.2751,37.235)]
+Market St [(-122.2794,37.109),(-122.2793,37.114)]
+Market St [(-122.2826,37.021),(-122.2825,37.023)]
+Marlboro Way [(-121.86612,37.9994),(-121.866,37.999)]
+Marlin Ct [(-122.044157,37.30406),(-122.044602,37.30052)]
+Marlow Dr [(-122.1375,37.346),(-122.1374,37.351)]
+Marne St [(-122.148,37.811),(-122.1476,37.805)]
+Mars Road [(-121.7834,37.586),(-121.7827,37.575)]
+Marsala Ct [(-121.848563,37.64116),(-121.847736,37.63501),(-121.847146,37.63392)]
+Marshall St [(-122.0637,37.99),(-122.0638,37.982)]
+Marshall Ter [(-122.026173,37.67648),(-122.026375,37.67346)]
+Martin Ave [(-121.8618,37.818),(-121.8618,37.82696),(-121.8618,37.848)]
+Martin Luther King Jr Way [(-122.2667,37.353),(-122.2666,37.357)]
+Martin Luther King Jr Way [(-122.2698,37.223),(-122.2697,37.229)]
+Martin Luther King Jr Way [(-122.2703,37.527),(-122.2702,37.517)]
+Martin Luther King Jr Way [(-122.2705,37.194),(-122.2704,37.199)]
+Martin Luther King Jr Way [(-122.2707,37.563),(-122.2706,37.545)]
+Martin Luther King Jr Way [(-122.2709,37.489),(-122.2708,37.481)]
+Martin Luther King Jr Way [(-122.2712,37.608),(-122.2711,37.599)]
+Martin Luther King Jr Way [(-122.272,37.146),(-122.2715,37.153)]
+Martin Luther King Jr Way [(-122.2787,37.014),(-122.2784,37.02),(-122.2779,37.027)]
+Martinez Dr [(-121.984,37.793),(-121.984,37.796)]
+Martinez St [(-122.159,37.191),(-122.1582,37.185)]
+Maryland Ave [(-122.2734,37.031),(-122.272,37.032)]
+Marylin Ave [(-121.7857,37.838),(-121.7833,37.837)]
+Masonic Ter [(-121.9512,37.123),(-121.9508,37.124)]
+Massachusetts St [(-122.0925,37.051),(-122.0909,37.056)]
+Mateo Ct [(-122.0266,37.659),(-122.0273,37.65)]
+Matthew Ct [(-121.8807,37.394),(-121.8783,37.395)]
+Mattos Dr [(-121.9983,37.542),(-121.9992,37.53)]
+Mattos Dr [(-122.0002,37.52),(-122.0005,37.513)]
+Mattos Dr [(-122.0005,37.502),(-122.000898,37.49683)]
+Mattox Road [(-122.0958,37.872),(-122.0957,37.876)]
+Maubert Ave [(-122.1114,37.009),(-122.1096,37.995)]
+Maud Ave [(-122.1387,37.25),(-122.1379,37.252)]
+Mauna Loa Park Dr [(-121.9723,37.191),(-121.972,37.196)]
+Mavis Ct [(-121.8584,37.606),(-121.8577,37.603)]
+Mavis Dr [(-121.86,37.628),(-121.8598,37.606)]
+Maxwelton Road [(-122.2248,37.31),(-122.2252,37.32)]
+May Ct [(-122.0589,37.323),(-122.0577,37.331)]
+May Road [(-122.0168,37.083),(-122.015,37.095)]
+Maya St [(-121.9148,37.792),(-121.9143,37.784)]
+Maybelle Ave [(-122.1957,37.878),(-122.1948,37.883)]
+Maybelle Way [(-122.1974,37.873),(-122.1971,37.874)]
+Mayfair Park Ave [(-121.9631,37.148),(-121.9629,37.143)]
+Mayhews Landing Road [(-122.0417,37.346),(-122.0424,37.341)]
+Mayhews Landing Road [(-122.0458,37.318),(-122.0468,37.309)]
+Mayhews Landing Road [(-122.0482,37.299),(-122.0489,37.295)]
+Mayport Cir [(-122.283588,37.88064),(-122.284724,37.87992)]
+Mayview Way [(-121.7869,37.637),(-121.7848,37.636)]
+Mc Clary Ave [(-122.1924,37.443),(-122.1917,37.448)]
+Mc Farlane Lane [(-122.0823,37.377),(-122.0863,37.374)]
+Mc Gee Ave [(-122.2755,37.611),(-122.2754,37.603)]
+Mc Gee Ave [(-122.2768,37.709),(-122.2768,37.702)]
+Mc Gee Ave [(-122.2772,37.746),(-122.2771,37.736)]
+Mc Millan St [(-122.2479,37.454),(-122.248,37.457)]
+McClure Ave [(-122.1431,37.001),(-122.1436,37.998)]
+McGlinchey Dr [(-121.7721,37.745),(-121.7722,37.729)]
+McKeown Ct [(-122.0676,37.681),(-122.0671,37.674)]
+McKeown Ter [(-121.992,37.825),(-121.992203,37.82275)]
+McSherry Way [(-122.235166,37.29199),(-122.234053,37.27635)]
+Meadowbrook Ave [(-122.0406,37.198),(-122.0389,37.183)]
+Meadowbrook Com [(-122.0043,37.608),(-122.0036,37.609)]
+Meadowlark Dr [(-122.0692,37.985),(-122.0692,37.973)]
+Mecartney Road [(-122.2473,37.374),(-122.2455,37.369)]
+Mecartney Road [(-122.24835,37.37769),(-122.247665,37.3761)]
+Medacino Ter [(-122.043321,37.65121),(-122.043914,37.64881)]
+Medallion Dr [(-122.0502,37.08),(-122.0502,37.059)]
+Medallion Dr [(-122.0502,37.929),(-122.0502,37.936)]
+Medellion Dr [(-122.050194,37.95738),(-122.050187,37.96407)]
+Medford Ave [(-122.1017,37.828),(-122.1015,37.829),(-122.1002,37.832)]
+Medlar Dr [(-122.0627,37.378),(-122.0625,37.375)]
+Meek Ave [(-122.0919,37.641),(-122.0897,37.642)]
+Meekland Ave [(-122.113,37.812),(-122.1128,37.809)]
+Meiggs St [(-121.9596,37.267),(-121.9593,37.255)]
+Melbourne Ave [(-122.0842,37.285),(-122.0837,37.269)]
+Melcher St [(-122.1792,37.21),(-122.1778,37.198)]
+Mellow Way [(-122.0387,37.846),(-122.0377,37.841)]
+Melrose Ave [(-122.2032,37.736),(-122.2029,37.733)]
+Melrose Ave [(-122.2075,37.756),(-122.2072,37.754)]
+Melrose Ave [(-122.2328,37.331),(-122.2319,37.335)]
+Mendenhall Road [(-121.681342,37.03354),(-121.684794,37.0443)]
+Mendenhall Road [(-122.134,37.677),(-122.1324,37.685)]
+Merced St [(-122.1708,37.102),(-122.1707,37.099)]
+Mercury St [(-122.0598,37.291),(-122.0595,37.284)]
+Merle Ct [(-122.1467,37.373),(-122.1467,37.366)]
+Merle Ct [(-122.1482,37.368),(-122.1479,37.358)]
+Merol Ave [(-121.9924,37.453),(-121.9918,37.45)]
+Merriewood Dr [(-122.2136,37.373),(-122.2128,37.389)]
+Merrill Ave [(-121.9205,37.807),(-121.9196,37.811)]
+Merrimac River St [(-122.032907,37.70666),(-122.0332,37.703)]
+Merritt Ave [(-122.2471,37.078),(-122.2446,37.078)]
+Merritt Ave [(-122.2495,37.053),(-122.2487,37.066)]
+Merritt Channel [(-122.2591,37.968),(-122.2606,37.942)]
+Mesa Ave [(-122.233,37.304),(-122.2322,37.291)]
+Meteor Dr [(-122.0713,37.868),(-122.0702,37.862)]
+Miami Ave [(-122.0812,37.234),(-122.0812,37.229)]
+Michael Ave [(-121.9605,37.208),(-121.961,37.206)]
+Michell Ct [(-121.7588,37.897),(-121.7583,37.893)]
+Michelle St [(-121.9691,37.461),(-121.968214,37.45771)]
+Middlefield Ave [(-121.9442,37.309),(-121.9426,37.311)]
+Midvale Ave [(-122.2016,37.933),(-122.2009,37.938)]
+Midway Road [(-121.572056,37.50049),(-121.574787,37.51247)]
+Milani Ave [(-122.0235,37.438),(-122.024,37.432)]
+Milburn Ter [(-122.058937,37.54931),(-122.058337,37.5373)]
+Mildred Ct [(-122.0002,37.388),(-121.9998,37.386)]
+Mildred Dr [(-121.9958,37.44),(-121.9964,37.433)]
+Mildred Dr [(-121.9973,37.421),(-121.9977,37.417)]
+Miles Ave [(-122.2599,37.373),(-122.2589,37.382)]
+Miller Ave [(-122.2322,37.844),(-122.2319,37.848)]
+Miller Ave [(-122.2353,37.806),(-122.235,37.809)]
+Miller Road [(-122.0902,37.645),(-122.0865,37.545)]
+Miller St [(-122.1627,37.048),(-122.1614,37.032)]
+Mills Ave [(-122.1222,37.917),(-122.1222,37.908)]
+Milmar Blvd [(-122.0785,37.108),(-122.0783,37.101)]
+Milvia St [(-122.2689,37.593),(-122.2687,37.585)]
+Milvia St [(-122.2692,37.637),(-122.2691,37.629)]
+Milvia St [(-122.2707,37.772),(-122.2707,37.763)]
+Milvia St [(-122.2711,37.817),(-122.2708,37.8),(-122.2707,37.781)]
+Minerva St [(-122.0652,37.282),(-122.066,37.28)]
+Mines Road [(-121.531666,37.83219),(-121.534092,37.94366)]
+Mines Road [(-121.536341,37),(-121.536966,37.00198)]
+Mines Road [(-121.570515,37.78068),(-121.568708,37.80183)]
+Mines Road [(-121.633014,37.95741),(-121.642288,37.98874)]
+Minivet Ct [(-121.8834,37.805),(-121.8838,37.811)]
+Minturn Ct [(-122.0545,37.944),(-122.0538,37.946)]
+Mira Vista Dr [(-122.0829,37.22),(-122.0827,37.213)]
+Mirador Ct [(-121.8644,37.556),(-121.8633,37.552)]
+Miramar Ave [(-122.1009,37.025),(-122.099089,37.03209)]
+Miramonte Ave [(-122.1052,37.986),(-122.1042,37.997)]
+Miranda St [(-122.0715,37.211),(-122.0717,37.206)]
+Miranda Way [(-121.7837,37.649),(-121.7807,37.648)]
+Mission Blvd [(-121.918886,37),(-121.9194,37.976),(-121.9198,37.975)]
+Mission Blvd [(-121.920783,37.37168),(-121.9204,37.367)]
+Mission Blvd [(-121.9214,37.961),(-121.9217,37.96)]
+Mission Blvd [(-121.924901,37.92912),(-121.9254,37.922)]
+Mission Blvd [(-121.928695,37.43767),(-121.925,37.417)]
+Mission Blvd [(-121.937204,37.48803),(-121.936483,37.48336)]
+Mission Blvd [(-121.9418,37.517),(-121.9389,37.499)]
+Mission Blvd [(-121.9449,37.536),(-121.9438,37.53)]
+Mission Blvd [(-121.949133,37.56339),(-121.948176,37.5572)]
+Mission Blvd [(-121.9562,37.607),(-121.9548,37.598)]
+Mission Blvd [(-121.9632,37.683),(-121.96315,37.6825)]
+Mission Blvd [(-121.971801,37.77041),(-121.9706,37.762)]
+Mission Blvd [(-121.9823,37.791),(-121.9765,37.788),(-121.9751,37.786)]
+Mission Blvd [(-122.0006,37.896),(-121.9989,37.88)]
+Mission Blvd [(-122.0221,37.084),(-122.021325,37.07762)]
+Mission Blvd [(-122.0641,37.491),(-122.062,37.464)]
+Mission Blvd [(-122.07,37.567),(-122.0692,37.555)]
+Mission Blvd [(-122.0798,37.688),(-122.0779,37.668)]
+Mission Blvd [(-122.0928,37.802),(-122.0919,37.796),(-122.0916,37.793)]
+Mission Blvd [(-122.0955,37.824),(-122.0947,37.817)]
+Mission Blvd [(-122.1031,37.882),(-122.1024,37.877)]
+Mission Ct [(-121.929057,37.90595),(-121.929602,37.90063)]
+Mission Dr [(-121.879,37.526),(-121.8788,37.526)]
+Mission Creek [(-121.9244,37.614),(-121.9238,37.608)]
+Mistflower Ave [(-122.0195,37.329),(-122.0199,37.323)]
+Mitchell Ave [(-122.1438,37.376),(-122.1439,37.374)]
+Mitchell St [(-122.2257,37.844),(-122.2249,37.852)]
+Moccasin St [(-122.0302,37.085),(-122.0297,37.078)]
+Mocine Ave [(-122.0751,37.497),(-122.0748,37.476)]
+Mockingbird Lane [(-122.0862,37.419),(-122.0861,37.412)]
+Mohr Ave [(-121.8495,37.819),(-121.8618,37.818)]
+Mohr Ave [(-121.877,37.819),(-121.8793,37.816),(-121.8799,37.819)]
+Mohr Dr [(-122.1132,37.466),(-122.113,37.46)]
+Molaka Cir [(-122.0476,37.863),(-122.0479,37.871)]
+Monaco Ave [(-122.0024,37.966),(-122.0023,37.967)]
+Monaco Ct [(-121.8762,37.544),(-121.8766,37.538)]
+Monika Lane [(-122.051987,37.88553),(-122.052536,37.88171)]
+Montcalm Ave [(-122.0394,37.373),(-122.04,37.369)]
+Monte Vista Dr [(-122.1127,37.4),(-122.1126,37.388)]
+Montecito Ave [(-122.2602,37.114),(-122.2589,37.122)]
+Montecito Cir [(-121.789,37.97),(-121.7883,37.967),(-121.7867,37.962)]
+Montecito Cir [(-121.789,37.97),(-121.78927,37.9709)]
+Montecito Dr [(-121.9899,37.743),(-121.9892,37.762)]
+Montego Bay [(-122.2491,37.389),(-122.2485,37.396)]
+Monterey Ave [(-122.2793,37.863),(-122.2799,37.857)]
+Monterey Blvd [(-122.1494,37.018),(-122.1486,37.013)]
+Monterey Blvd [(-122.189,37.987),(-122.1891,37.971)]
+Monterey Blvd [(-122.1931,37.068),(-122.1918,37.051)]
+Monterey Blvd [(-122.1971,37.103),(-122.1961,37.096)]
+Monterey Blvd [(-122.2029,37.167),(-122.2012,37.146)]
+Monterey Dr [(-121.909595,37.12732),(-121.910168,37.15444)]
+Montgomery Ave [(-122.0971,37.824),(-122.0955,37.811)]
+Montgomery Ave [(-122.0989,37.838),(-122.0977,37.829)]
+Montgomery St [(-122.0882,37.738),(-122.0875,37.727)]
+Montgomery St [(-122.2489,37.316),(-122.2471,37.33)]
+Montgomery St [(-122.2518,37.292),(-122.2504,37.305)]
+Monticello Ave [(-122.2,37.771),(-122.199,37.778)]
+Monticello St [(-122.0561,37.37),(-122.0554,37.374)]
+Mooney Ave [(-122.1234,37.972),(-122.1234,37.978)]
+Moonflower Way [(-121.7297,37.075),(-121.7322,37.071)]
+Moore Dr [(-122.1901,37.335),(-122.1884,37.351)]
+Moores Ave [(-122.0087,37.301),(-122.0094,37.292)]
+Moores Ave [(-122.0098,37.287),(-122.0102,37.281)]
+Moores Ave [(-122.0138,37.237),(-122.014,37.233)]
+Morada Ct [(-121.93541,37.49248),(-121.935229,37.49155)]
+Moraga Ave [(-122.2096,37.265),(-122.2088,37.254)]
+Moraga Ave [(-122.225,37.3),(-122.2243,37.3)]
+Moraga Ave [(-122.2447,37.288),(-122.2423,37.302)]
+Moraga Dr [(-121.7915,37.701),(-121.7906,37.705)]
+Morcom Pl [(-122.1901,37.826),(-122.1897,37.829)]
+Moreland Dr [(-122.0802,37.11),(-122.0793,37.109)]
+Morengo Way [(-121.9209,37.837),(-121.9203,37.84)]
+Mound St [(-122.2346,37.582),(-122.2339,37.588)]
+Mound St [(-122.2402,37.531),(-122.2395,37.537)]
+Mount Hamilton Ct [(-122.030097,37.11348),(-122.030412,37.11049)]
+Mountain Ave [(-122.2267,37.24),(-122.2261,37.231)]
+Mountain Ave [(-122.2291,37.256),(-122.2288,37.257)]
+Mountain Blvd [(-122.151,37.659),(-122.1484,37.64)]
+Mountain Blvd [(-122.1535,37.702),(-122.153,37.696)]
+Mountain Blvd [(-122.1765,37.857),(-122.177,37.85)]
+Mountain Blvd [(-122.1774,37.889),(-122.1771,37.887)]
+Mountain Blvd [(-122.1789,37.934),(-122.1787,37.93)]
+Mountain Blvd [(-122.2013,37.164),(-122.2004,37.15)]
+Mountain Blvd [(-122.2085,37.261),(-122.2088,37.254)]
+Mountain Blvd [(-122.211,37.308),(-122.2102,37.297)]
+Mountain Blvd [(-122.222,37.435),(-122.2204,37.436)]
+Mountain Blvd [(-122.2261,37.465),(-122.2241,37.451)]
+Mountaingate Way [(-122.1999,37.198),(-122.1994,37.193)]
+Mowry Ave [(-121.9745,37.659),(-121.975803,37.65155)]
+Mowry Ave [(-121.976381,37.64822),(-121.9769,37.646)]
+Mowry Ave [(-121.9807,37.567),(-121.9809,37.565)]
+Mowry Ave [(-121.9825,37.547),(-121.9838,37.532)]
+Mowry Ave [(-121.9896,37.459),(-121.9904,37.449)]
+Mowry Ave [(-122.0074,37.245),(-122.008,37.236)]
+Mowry Ave [(-122.0113,37.158),(-122.0129,37.124)]
+Mowry Slough [(-122.0393,37.918),(-122.0552,37.908)]
+Msn Creek Dr [(-121.9354,37.428),(-121.9344,37.427)]
+Mtn House Road [(-121.576862,37.78355),(-121.576389,37.80422)]
+Mtn House Road [(-121.579578,37.73807),(-121.578037,37.75356)]
+Mtn House Creek [(-121.558979,37.69672),(-121.558765,37.69123),(-121.564396,37.65095)]
+Mtn House Creek [(-121.570759,37.60189),(-121.566196,37.63889)]
+Mtn House Creek [(-121.577,37.50431),(-121.578144,37.49554)]
+Muir St [(-122.0761,37.529),(-122.0756,37.524)]
+Muir St [(-122.0787,37.562),(-122.0779,37.553)]
+Muirwood Dr [(-121.9162,37.824),(-121.9168,37.831)]
+Mulberry Pl [(-121.9221,37.234),(-121.9225,37.232)]
+Mulberry Pl [(-121.9238,37.249),(-121.9249,37.261)]
+Mulberry St [(-122.0363,37.332),(-122.034,37.32)]
+Murcia St [(-122.076,37.235),(-122.0758,37.23)]
+Murdell Lane [(-121.7977,37.619),(-121.797719,37.60774)]
+Murdell Lane [(-121.8001,37.746),(-121.7998,37.741)]
+Murdock Ct [(-122.1828,37.753),(-122.1832,37.756)]
+Murray St [(-122.2885,37.513),(-122.288,37.514)]
+Murrieta Blvd [(-121.7825,37.758),(-121.7816,37.758)]
+Murrieta Blvd [(-121.7847,37.94),(-121.7842,37.942)]
+Murrieta Blvd [(-121.7864,37.791),(-121.7866,37.797)]
+Murrieta Blvd [(-121.7865,37.934),(-121.7858,37.936)]
+Murrieta Blvd [(-121.788409,37.80906),(-121.7885,37.815)]
+Myers St [(-122.1511,37.423),(-122.151,37.415)]
+Myra Ct [(-122.108083,37.47375),(-122.108278,37.47677)]
+Myrtle St [(-122.0924,37.685),(-122.0919,37.676)]
+Myrtle St [(-122.2812,37.092),(-122.2805,37.108)]
+N St [(-121.7737,37.824),(-121.7739,37.829)]
+Nandina Ct [(-121.9274,37.299),(-121.9281,37.297)]
+Nansa Ct [(-121.9188,37.259),(-121.918711,37.25453)]
+Natalie Ave [(-121.9792,37.307),(-121.9799,37.296)]
+National Ave [(-122.1192,37.5),(-122.1281,37.489)]
+Navajo Ct [(-121.8779,37.901),(-121.8783,37.9)]
+Navy Roadway [(-122.3093,37.092),(-122.3123,37.109)]
+Neal St [(-121.8692,37.577),(-121.8683,37.574)]
+Nelson Ct [(-121.864163,37.4684),(-121.86436,37.4745)]
+Nelson Pl [(-122.0366,37.522),(-122.0368,37.519)]
+Nelson St [(-121.9822,37.362),(-121.9846,37.348)]
+Neptune Dr [(-122.1878,37.989),(-122.1853,37.958)]
+Neptune Dr [(-122.1881,37.032),(-122.1875,37.007)]
+Neptune Road [(-121.7885,37.607),(-121.7885,37.599)]
+Nevada St [(-122.0313,37.891),(-122.0317,37.882)]
+Nevada St [(-122.1821,37.37),(-122.1815,37.376)]
+New England Village Dr [(-122.058,37.237),(-122.0585,37.235)]
+Newark Blvd [(-122.0329,37.398),(-122.032,37.382)]
+Newark Blvd [(-122.0352,37.438),(-122.0341,37.423)]
+Newberry St [(-122.2671,37.568),(-122.2668,37.554)]
+Newcastle Lane [(-121.918,37.167),(-121.9191,37.182)]
+Newport St [(-122.1059,37.328),(-122.1054,37.315)]
+Nicol Ave [(-122.2174,37.943),(-122.2164,37.94)]
+Nicolet Ave [(-122.0174,37.696),(-122.0183,37.693)]
+Nicolet Ave [(-122.0238,37.615),(-122.0238,37.61)]
+Nicolet Ave [(-122.0264,37.574),(-122.0269,37.567)]
+Nicolet Ct [(-122.0242,37.641),(-122.0236,37.638)]
+Nielsen Lane [(-121.744,37.777),(-121.744,37.77)]
+Niles Blvd [(-121.9933,37.803),(-121.9929,37.803)]
+Niles Canyon Road [(-121.881448,37.92865),(-121.8812,37.929)]
+Niles Canyon Road [(-121.8954,37.94),(-121.89496,37.93963)]
+Niles Canyon Road [(-121.909,37.94),(-121.9034,37.957)]
+Niles Canyon Road [(-121.9292,37.955),(-121.925931,37.9815)]
+Niles Canyon Road [(-121.9333,37.913),(-121.9317,37.919)]
+Niles Canyon Road [(-121.9352,37.912),(-121.9346,37.91)]
+Nobhill Ct [(-122.0354,37.523),(-122.0353,37.517)]
+Norbridge Ave [(-122.084,37.911),(-122.0829,37.911)]
+Norene Way [(-122.1365,37.286),(-122.136,37.287)]
+Norfolk Road [(-122.2936,37.877),(-122.2937,37.859)]
+Norfolk Road [(-122.2937,37.848),(-122.2941,37.845)]
+Noria Ct [(-121.9319,37.288),(-121.9324,37.286)]
+Norissa Cir [(-122.0486,37.763),(-122.0484,37.77)]
+Normandie Ave [(-122.1902,37.783),(-122.1886,37.768)]
+Normandy Ct [(-122.0629,37.971),(-122.063,37.968)]
+Normandy Dr [(-122.0501,37.457),(-122.0506,37.451)]
+Norris Canyon Road [(-122.015,37.545),(-122.0103,37.541)]
+Norris Canyon Road [(-122.0329,37.332),(-122.0278,37.371)]
+North Blvd [(-122.1768,37.243),(-122.1775,37.242)]
+North Hill Ct [(-122.231,37.533),(-122.2325,37.541)]
+Northland Ter [(-122.059106,37.55559),(-122.05922,37.55498)]
+Northrup St [(-122.2141,37.371),(-122.215,37.381)]
+Northvale Road [(-122.2361,37.088),(-122.2335,37.083)]
+Northview Dr [(-122.0504,37.887),(-122.0511,37.892)]
+Northway Road [(-121.8857,37.748),(-121.8871,37.75)]
+Northwood Dr [(-122.2299,37.64),(-122.2291,37.638)]
+Northwood Dr [(-122.2317,37.629),(-122.2311,37.637)]
+Northwood Com [(-121.7885,37.815),(-121.788231,37.80962)]
+Norton Ave [(-122.1977,37.967),(-122.196855,37.97165)]
+Norwood Pl [(-121.7807,37.534),(-121.7811,37.534)]
+Nottingham Ct [(-122.0297,37.528),(-122.0303,37.522)]
+Nova Dr [(-122.2397,37.213),(-122.2394,37.214)]
+Novara Road [(-122.2377,37.47),(-122.2368,37.472)]
+Novato St [(-122.0628,37.731),(-122.0633,37.723)]
+Nula Way [(-122.0553,37.865),(-122.0552,37.855)]
+Nursery Ave [(-121.9897,37.802),(-121.9901,37.795)]
+O Connell Lane [(-121.9941,37.985),(-121.9917,37.974)]
+O Connell Lane [(-122.00235,37.9875),(-121.9964,37.995)]
+O Connell Lane [(-122.0038,37.972),(-122.0036,37.975)]
+Oak St [(-122.2383,37.713),(-122.238,37.719)]
+Oak St [(-122.2437,37.636),(-122.2431,37.644)]
+Oak St [(-122.265,37.877),(-122.264,37.879)]
+Oak St [(-122.2653,37.967),(-122.2649,37.974)]
+Oak St [(-122.2666,37.947),(-122.2664,37.95)]
+Oak Creek Ct [(-121.9153,37.737),(-121.9171,37.735)]
+Oak Hill Road [(-122.1409,37.647),(-122.1382,37.658)]
+Oak Knoll Blvd [(-122.151,37.559),(-122.1504,37.556)]
+Oakbrook Ct [(-121.865488,37.96653),(-121.8661,37.965)]
+Oakdale St [(-122.0648,37.767),(-122.0654,37.761)]
+Oakes Blvd [(-122.1523,37.316),(-122.1511,37.318)]
+Oakes Dr [(-122.034734,37.62423),(-122.0329,37.618)]
+Oakland Ave [(-121.8703,37.85305),(-121.8703,37.866)]
+Oakland Ave [(-122.2332,37.258),(-122.2319,37.262)]
+Oakland Ave [(-122.24,37.235),(-122.2393,37.237),(-122.2387,37.24)]
+Oakland Ave [(-122.2529,37.197),(-122.2521,37.203)]
+Oakland Ave [(-122.2583,37.162),(-122.2569,37.167)]
+Oakland Inner Harbor [(-122.2625,37.913),(-122.260016,37.89484)]
+Oakmont Ave [(-122.231698,37.17113),(-122.2321,37.168)]
+Oakport St [(-122.1975,37.422),(-122.1963,37.386)]
+Oakport St [(-122.2123,37.59),(-122.2117,37.585)]
+Oakridge Road [(-121.7646,37.841),(-121.768787,37.84142)]
+Oakridge Road [(-121.8182,37.93),(-121.8207,37.931)]
+Oakridge Road [(-121.8316,37.049),(-121.828382,37)]
+Oakview Dr [(-122.2079,37.123),(-122.2074,37.116)]
+Oakwood Dr [(-122.1989,37.424),(-122.198,37.437)]
+Ocaso Camino [(-121.9293,37.261),(-121.9303,37.252)]
+Occidental Road [(-122.1102,37.403),(-122.110496,37.40277)]
+Ocie Way [(-122.0966,37.605),(-122.097,37.603)]
+Octavia St [(-122.2026,37.906),(-122.2013,37.906)]
+Ogden Dr [(-121.978963,37.36863),(-121.9808,37.368)]
+Olazaba Ter [(-121.9176,37.247),(-121.917664,37.24323)]
+Old Bernal Ave [(-121.879084,37.58027),(-121.878944,37.57993)]
+Old Canyon Road [(-121.965,37.794),(-121.963,37.8)]
+Old Santa Rita Road [(-121.877505,37.9429),(-121.8776,37.96)]
+Old Tower Road [(-121.7722,37.729),(-121.7704,37.729)]
+Old Warm Springs Blvd [(-121.9482,37.092),(-121.9473,37.084)]
+Oleander Ave [(-122.2338,37.296),(-122.2346,37.295)]
+Oleander Ave [(-122.2358,37.302),(-122.236192,37.30424)]
+Oleander St [(-121.7471,37.126),(-121.7459,37.129)]
+Olive Ave [(-121.9326,37.36),(-121.9333,37.356)]
+Olive Ave [(-121.9458,37.34),(-121.9493,37.337)]
+Olive Ave [(-122.2462,37.219),(-122.2451,37.202)]
+Oliver Way [(-121.9665,37.739),(-121.966295,37.73644)]
+Olivina Ave [(-121.7791,37.829),(-121.7787,37.829)]
+Olympic Ct [(-122.0509,37.321),(-122.0518,37.31)]
+Olympus Ave [(-122.2512,37.834),(-122.2502,37.818)]
+Omak St [(-121.9209,37.064),(-121.9209,37.071)]
+Omar St [(-121.9719,37.221),(-121.9724,37.222)]
+Omar St [(-121.9825,37.255),(-121.9831,37.259)]
+Omega Ave [(-122.0678,37.988),(-122.0664,37.989)]
+Oneil Ave [(-122.073989,37.5886),(-122.073631,37.58413)]
+Oneil Ave [(-122.076754,37.62476),(-122.0745,37.595)]
+Onondaga Dr [(-121.9288,37.054),(-121.9282,37.048)]
+Onondaga Dr [(-121.9312,37.054),(-121.93,37.058)]
+Opal Way [(-121.7984,37.679),(-121.7975,37.679)]
+Orange Ave [(-122.0787,37.867),(-122.0789,37.842)]
+Orchard Ave [(-122.0858,37.555),(-122.0833,37.551)]
+Orchard Lane [(-122.2475,37.694),(-122.2467,37.692)]
+Orchid Dr [(-122.1416,37.098),(-122.1397,37.085)]
+Oregon St [(-122.2589,37.588),(-122.2565,37.591)]
+Oregon St [(-122.2794,37.561),(-122.2789,37.562)]
+Orin Dr [(-122.2623,37.133),(-122.2626,37.142)]
+Oriole Ave [(-121.7879,37.827),(-121.7879,37.851)]
+Oriole Ave [(-122.1209,37.051),(-122.1205,37.071)]
+Orleans Dr [(-122.046,37.393),(-122.0464,37.383)]
+Orleans Dr [(-122.0473,37.438),(-122.0463,37.412)]
+Orloff Dr [(-121.8659,37.767),(-121.865,37.765)]
+Orloff Dr [(-121.8677,37.768),(-121.8669,37.768)]
+Osgood Road [(-121.9371,37.071),(-121.9363,37.06)]
+Osgood Road [(-121.949,37.262),(-121.946,37.208)]
+Ostrander Road [(-122.2364,37.413),(-122.2356,37.429)]
+Otis Dr [(-122.2564,37.619),(-122.256,37.618)]
+Otis Dr [(-122.2605,37.627),(-122.2593,37.625)]
+Outlook Ave [(-122.1616,37.636),(-122.1612,37.632)]
+Outlook Ave [(-122.168,37.698),(-122.1678,37.697)]
+Outlook Ave [(-122.1743,37.75),(-122.1736,37.746)]
+Outlook Ave [(-122.1789,37.788),(-122.1786,37.787)]
+Outrigger Dr [(-122.1802,37.95),(-122.178449,37.9197)]
+Overacker Ave [(-121.9617,37.652),(-121.9614,37.648),(-121.9607,37.643)]
+Owl Ct [(-121.91654,37.19636),(-121.915693,37.19031)]
+Oxford Pl [(-121.7752,37.532),(-121.7747,37.535)]
+Oxford St [(-122.2651,37.702),(-122.2651,37.699)]
+Oxford St [(-122.2657,37.742),(-122.2656,37.734)]
+Oyster Pond Road [(-122.2408,37.407),(-122.2404,37.403)]
+Oyster Shoals [(-122.2372,37.394),(-122.2365,37.393)]
+Pacific Ave [(-121.754552,37.76177),(-121.753824,37.76164)]
+Pacific Ave [(-122.1632,37.171),(-122.1621,37.156),(-122.1613,37.15)]
+Pacific Ave [(-122.1661,37.214),(-122.1654,37.204)]
+Pacific Ave [(-122.2419,37.683),(-122.2408,37.678)]
+Pacific Ave [(-122.2535,37.735),(-122.2527,37.731)]
+Pacific St [(-122.0342,37.962),(-122.0371,37.962)]
+Pacific St [(-122.0544,37.294),(-122.0548,37.297)]
+Packet Landing Road [(-122.2376,37.471),(-122.2372,37.458)]
+Pagano Ct [(-122.1371,37.959),(-122.1379,37.954)]
+Pala Ave [(-122.2328,37.28),(-122.2319,37.283)]
+Palatka Lane [(-122.0915,37.35),(-122.0927,37.354)]
+Palm Ave [(-121.932,37.329),(-121.9319,37.319)]
+Palm Dr [(-122.0503,37.898),(-122.0501,37.897)]
+Palm Dr [(-122.052051,37.88751),(-122.0519,37.885)]
+Palmer Dr [(-121.86601,37.85873),(-121.865793,37.84778)]
+Palmer Dr [(-122.003005,37.52852),(-122.0026,37.527)]
+Palmetto Dr [(-122.0189,37.111),(-122.018,37.106)]
+Palo Verde Road [(-122.0242,37.959),(-122.023,37.955),(-122.0222,37.961)]
+Paloma Ave [(-122.2347,37.126),(-122.2353,37.136)]
+Palomares Road [(-121.9404,37.123),(-121.9407,37.11)]
+Palomares Road [(-121.9501,37.318),(-121.948,37.304)]
+Palomares Road [(-121.9683,37.439),(-121.9686,37.436)]
+Palomares Road [(-121.9976,37.676),(-121.997,37.674)]
+Palomares Road [(-122.0191,37.873),(-122.0056,37.744)]
+Palomino Dr [(-121.8536,37.59),(-121.852,37.589)]
+Pampas Ave [(-122.188335,37.87635),(-122.1878,37.872)]
+Panda Way [(-122.0668,37.773),(-122.0662,37.762)]
+Panitz St [(-122.0553,37.686),(-122.0553,37.679)]
+Panorama Trl [(-121.8996,37.291),(-121.9098,37.262)]
+Panorama Trl [(-121.9023,37.342),(-121.8995,37.303)]
+Panorama Trl [(-121.9096,37.37),(-121.9098,37.371)]
+Panorama Trl [(-121.9148,37.366),(-121.9141,37.356)]
+Panoramic Way [(-122.2454,37.695),(-122.2436,37.688)]
+Papago St [(-121.9155,37.826),(-121.9147,37.811)]
+Parada St [(-121.9949,37.166),(-121.9931,37.153)]
+Pardee Ave [(-121.9885,37.367),(-121.9893,37.359)]
+Pardee Dr [(-122.1991,37.286),(-122.1974,37.27)]
+Parish Ct [(-122.236048,37.33387),(-122.235406,37.3307)]
+Park Ave [(-122.2407,37.634),(-122.2404,37.638)]
+Park Ave [(-122.242,37.616),(-122.2416,37.621)]
+Park Ave [(-122.2461,37.565),(-122.2443,37.589)]
+Park Ave [(-122.2841,37.316),(-122.2851,37.314)]
+Park Blvd [(-122.2058,37.222),(-122.2047,37.23)]
+Park Blvd [(-122.218,37.087),(-122.2173,37.091)]
+Park Blvd [(-122.2312,37.051),(-122.2303,37.049)]
+Park Blvd [(-122.2387,37.027),(-122.2377,37.028),(-122.2362,37.031)]
+Park Blvd [(-122.2461,37.013),(-122.2457,37.013)]
+Park Lane [(-122.2309,37.163),(-122.2313,37.166)]
+Park St [(-121.7711,37.86),(-121.7699,37.863)]
+Park St [(-122.2377,37.686),(-122.237,37.695)]
+Park St [(-122.2491,37.541),(-122.247562,37.56407)]
+Park St [(-122.2825,37.544),(-122.282424,37.5421)]
+Park Way [(-122.0875,37.97),(-122.087418,37.96824)]
+Park Way [(-122.2339,37.288),(-122.2332,37.288)]
+Park Way [(-122.2366,37.289),(-122.2357,37.288)]
+Park Way [(-122.3038,37.798),(-122.3031,37.8)]
+Park Blvd Way [(-122.2303,37.049),(-122.2287,37.049)]
+Park Center Lane [(-121.9793,37.431),(-121.9798,37.425)]
+Parker Ave [(-122.1647,37.685),(-122.1643,37.686)]
+Parker Road [(-122.0876,37.181),(-122.0859,37.17)]
+Parker St [(-122.2664,37.623),(-122.2643,37.626)]
+Parkmeadow Dr [(-121.9319,37.062),(-121.9322,37.066)]
+Parkmeadow Dr [(-121.9324,37.099),(-121.9317,37.104)]
+Parkridge Dr [(-122.1438,37.884),(-122.1428,37.9)]
+Parkside Dr [(-121.886474,37.83325),(-121.8863,37.834)]
+Parkside Dr [(-121.8925,37.806),(-121.8916,37.809)]
+Parkside Dr [(-121.895065,37.79588),(-121.8949,37.797)]
+Parkside Dr [(-121.9836,37.598),(-121.9851,37.594),(-121.9861,37.592)]
+Parkside Dr [(-122.0475,37.603),(-122.0443,37.596)]
+Parkside Dr [(-122.0595,37.008),(-122.0592,37.012)]
+Parkview Road [(-122.0548,37.023),(-122.0548,37.02)]
+Parnassus Road [(-122.2525,37.814),(-122.2518,37.814)]
+Parsons Ct [(-122.082,37.056),(-122.0812,37.053)]
+Partridge Ave [(-122.1597,37.655),(-122.1584,37.652)]
+Partridge Com [(-121.7876,37.877),(-121.7876,37.882)]
+Paru St [(-122.2583,37.712),(-122.2574,37.725)]
+Paru St [(-122.2601,37.687),(-122.2594,37.695)]
+Pasatiempo St [(-121.7252,37.262),(-121.7252,37.27)]
+Paseo del Cajon [(-121.8901,37.699),(-121.889101,37.68411)]
+Paseo del Rio [(-122.1309,37.842),(-122.1332,37.836),(-122.134,37.835)]
+Paseo Grande [(-122.1197,37.83),(-122.1204,37.826)]
+Paseo Grande [(-122.1231,37.812),(-122.1234,37.809)]
+Paseo Largavista [(-122.1287,37.822),(-122.1281,37.811)]
+Paseo Padre Pkwy [(-121.9143,37.005),(-121.913522,37)]
+Paseo Padre Pkwy [(-121.9188,37.004),(-121.9177,37.004)]
+Paseo Padre Pkwy [(-121.9292,37.083),(-121.9295,37.088)]
+Paseo Padre Pkwy [(-121.9302,37.137),(-121.9298,37.146)]
+Paseo Padre Pkwy [(-121.9357,37.342),(-121.9327,37.318)]
+Paseo Padre Pkwy [(-121.9594,37.433),(-121.9569,37.419)]
+Paseo Padre Pkwy [(-121.960768,37.44248),(-121.9599,37.437)]
+Paseo Padre Pkwy [(-121.9643,37.462),(-121.9618,37.449)]
+Paseo Padre Pkwy [(-121.9885,37.578),(-121.9877,37.574)]
+Paseo Padre Pkwy [(-122.0021,37.639),(-121.996,37.628)]
+Paseo Padre Pkwy [(-122.0332,37.819),(-122.0307,37.809)]
+Paseo Padre Pkwy [(-122.0414,37.734),(-122.0445,37.708)]
+Paseo Padre Pkwy [(-122.045898,37.70023),(-122.047,37.695)]
+Paseo Padre Pkwy [(-122.049,37.684),(-122.05,37.67999)]
+Paseo Padre Pkwy [(-122.051153,37.67586),(-122.0517,37.674)]
+Paseo Santa Cruz [(-121.9052,37.65),(-121.904,37.65)]
+Paseo Santa Cruz [(-121.906145,37.66172),(-121.905945,37.65817)]
+Patterson Pass Road [(-121.556654,37.14753),(-121.556,37.148)]
+Patterson Pass Road [(-121.574131,37.07538),(-121.573093,37.09003)]
+Patterson Ranch Road [(-122.0702,37.545),(-122.0855,37.509),(-122.0902,37.515)]
+Paxton Ct [(-122.0092,37.388),(-122.0101,37.378)]
+Payne Ct [(-121.9133,37.841),(-121.9139,37.84)]
+Peach Ct [(-122.0477,37.295),(-122.0473,37.293)]
+Peach St [(-122.2339,37.527),(-122.2334,37.532),(-122.2328,37.537)]
+Peach St [(-122.2352,37.502),(-122.2354,37.514)]
+Peachtree Ave [(-122.0511,37.277),(-122.0517,37.275)]
+Peachtree Dr [(-122.091,37.209),(-122.0913,37.199)]
+Pearl St [(-122.2319,37.672),(-122.2316,37.676)]
+Pearl St [(-122.2337,37.651),(-122.2329,37.661)]
+Pearl St [(-122.2383,37.594),(-122.2366,37.615)]
+Pearl St [(-122.2551,37.179),(-122.2546,37.174)]
+Peary Ct [(-121.769,37.711),(-121.7686,37.706)]
+Pecan Ct [(-121.9126,37.743),(-121.9142,37.739)]
+Peladeau St [(-122.2888,37.383),(-122.2889,37.386)]
+Pelican Ct [(-121.7907,37.839),(-121.7902,37.832)]
+Penniman Ave [(-122.2018,37.863),(-122.2012,37.856)]
+Pennsylvania Ave [(-121.9854,37.532),(-121.9862,37.523)]
+Peony Ct [(-121.9798,37.204),(-121.9804,37.198)]
+Pepperdine St [(-122.1501,37.989),(-122.1488,37.98)]
+Peppertree Ct [(-121.9416,37.16),(-121.9412,37.163)]
+Peppertree Road [(-121.9421,37.127),(-121.94222,37.1302)]
+Peralta Ave [(-122.1585,37.293),(-122.1592,37.29)]
+Peralta Ave [(-122.1639,37.267),(-122.1653,37.27)]
+Peralta Blvd [(-121.9856,37.63),(-121.9867,37.627)]
+Peralta Blvd [(-122.0022,37.595),(-122.0045,37.591)]
+Peralta Blvd [(-122.0073,37.573),(-122.0082,37.562)]
+Peralta St [(-122.2832,37.243),(-122.2829,37.246)]
+Peralta St [(-122.2933,37.113),(-122.2926,37.116)]
+Peralta St [(-122.2943,37.103),(-122.2937,37.111)]
+Peralta St [(-122.2974,37.061),(-122.2973,37.063)]
+Peridot Dr [(-121.8024,37.648),(-121.8024,37.645)]
+Periwinkle Road [(-122.0451,37.301),(-122.044758,37.29844)]
+Perkins St [(-122.0185,37.617),(-122.0177,37.613)]
+Perkins St [(-122.2553,37.105),(-122.2557,37.096)]
+Perlata Creek [(-122.2027,37.941),(-122.2031,37.936)]
+Perlata Creek [(-122.2059,37.92),(-122.2092,37.906)]
+Perlata Creek [(-122.2133,37.891),(-122.214,37.885)]
+Perlata Creek [(-122.2158,37.753),(-122.2155,37.739)]
+Perlata Creek [(-122.21853,37.83163),(-122.2188,37.826)]
+Perry Road [(-122.0265,37.846),(-122.0256,37.844)]
+Pershing Dr [(-122.1618,37.278),(-122.1613,37.271),(-122.1609,37.266)]
+Pershing Dr [(-122.1633,37.299),(-122.1628,37.291)]
+Pershing Dr [(-122.165,37.321),(-122.1645,37.315)]
+Pershing Dr [(-122.1662,37.339),(-122.1658,37.333)]
+Pestana Pl [(-121.7578,37.84),(-121.757776,37.84644)]
+Peterman Ave [(-122.0945,37.392),(-122.0918,37.421)]
+Peters Ave [(-121.8769,37.6),(-121.8763,37.608)]
+Peters St [(-122.1287,37.068),(-122.128,37.073)]
+Peterson St [(-122.232,37.733),(-122.2316,37.739)]
+Peterson Way [(-122.0967,37.162),(-122.0952,37.159)]
+Petroleum St [(-122.3168,37.13),(-122.3177,37.13)]
+Petronave Dr [(-121.8344,37.469),(-121.83751,37.55738)]
+Pickering Ave [(-121.9596,37.722),(-121.958868,37.72618)]
+Pickering Ave [(-121.964,37.698),(-121.9637,37.7)]
+Pico Road [(-121.9165,37.524),(-121.9177,37.505)]
+Piedmont Ave [(-122.2511,37.644),(-122.251,37.624)]
+Piedmont Ave [(-122.2527,37.263),(-122.2521,37.268)]
+Piedmont Cres [(-122.2513,37.671),(-122.2503,37.661)]
+Pierce Ave [(-122.1683,37.186),(-122.1672,37.168)]
+Pierce St [(-122.3045,37.891),(-122.3042,37.884)]
+Pike Ct [(-121.9219,37.08),(-121.9224,37.079)]
+Pimlico Dr [(-121.8616,37.998),(-121.8618,37.008)]
+Pine St [(-121.7758,37.902),(-121.7746,37.906)]
+Pine St [(-121.7784,37.894),(-121.7776,37.897)]
+Pine St [(-121.7869,37.882),(-121.7864,37.883)]
+Pine St [(-121.9216,37.243),(-121.9226,37.235)]
+Pine St [(-122.303,37.074),(-122.3026,37.084)]
+Pine St [(-122.3034,37.063),(-122.3032,37.069)]
+Pine Needle Dr [(-122.2127,37.478),(-122.2125,37.473)]
+Pinewood Road [(-122.2219,37.424),(-122.2211,37.429)]
+Pinto Ct [(-122.0228,37.08),(-122.0224,37.073)]
+Piper St [(-122.216,37.39),(-122.2155,37.385)]
+Pippin St [(-122.1747,37.344),(-122.1734,37.332)]
+Pizarro Dr [(-122.025,37.556),(-122.026,37.544)]
+Placer Way [(-121.9789,37.415),(-121.9769,37.407)]
+Plata Way [(-121.9402,37.066),(-121.9394,37.069)]
+Pleasant Way [(-122.1653,37.311),(-122.1646,37.302)]
+Pleasant Hill Road [(-121.9264,37.854),(-121.9268,37.86)]
+Pleasant Valley Ct [(-122.2455,37.298),(-122.2439,37.305)]
+Pleasanton Ave [(-121.8782,37.636),(-121.8784,37.63)]
+Pleasanton Ave [(-121.8819,37.586),(-121.882761,37.5753)]
+Pleasanton Canal [(-121.886052,37.82228),(-121.8833,37.835)]
+Pleasanton Sunol Road [(-121.8765,37.987),(-121.8775,37.982)]
+Pleasanton Sunol Road [(-121.8851,37.39),(-121.8858,37.387)]
+Pleitner Ave [(-122.2098,37.946),(-122.2094,37.953)]
+Plomosa Road [(-121.9106,37.703),(-121.9102,37.696)]
+Plummer Creek [(-122.0778,37.095),(-122.0852,37.069)]
+Plymouth Ave [(-121.9369,37.422),(-121.9378,37.424)]
+Plymouth Dr [(-122.0798,37.132),(-122.0802,37.11)]
+Plymouth St [(-122.1643,37.425),(-122.1641,37.418)]
+Plymouth St [(-122.1661,37.454),(-122.1652,37.44)]
+Plymouth St [(-122.1763,37.593),(-122.1758,37.586)]
+Poda Ct [(-121.9321,37.308),(-121.9315,37.312)]
+Poinciana Pl [(-121.9946,37.341),(-121.994,37.337)]
+Point Eden Way [(-122.1208,37.255),(-122.1262,37.256)]
+Polaris Ave [(-122.064185,37.84562),(-122.0647,37.84)]
+Polk Way [(-121.745,37.867),(-121.745,37.858)]
+Polvorosa Ct [(-122.0178,37.594),(-122.0174,37.591)]
+Pomar Vista Ave [(-122.0989,37.958),(-122.0973,37.969)]
+Pomona Way [(-121.743614,37.84357),(-121.7427,37.839)]
+Ponderosa Dr [(-121.749629,37.12363),(-121.74967,37.11779)]
+Pontiac St [(-122.165,37.365),(-122.1647,37.359),(-122.1643,37.354)]
+Pontiac St [(-122.1665,37.383),(-122.166309,37.3779)]
+Poplar Ave [(-122.1018,37.704),(-122.098,37.721)]
+Poplar Path [(-122.2678,37.968),(-122.2687,37.967)]
+Port Sailwood Dr [(-122.0261,37.467),(-122.0265,37.463)]
+Portage Road [(-121.9241,37.092),(-121.9257,37.109)]
+Portal Ave [(-122.2281,37.148),(-122.2282,37.157)]
+Portland Ave [(-122.2861,37.949),(-122.2858,37.949)]
+Portland Ave [(-122.2945,37.945),(-122.2937,37.946)]
+Portofino Cir [(-122.123984,37.08583),(-122.124375,37.08809)]
+Portola Ave [(-121.7822,37.948),(-121.7806,37.94),(-121.7794,37.935)]
+Portola Ave [(-122.2723,37.696),(-122.2713,37.691)]
+Portola Dr [(-122.019284,37.56839),(-122.0199,37.563)]
+Portola Dr [(-122.1482,37.024),(-122.1488,37.021)]
+Portola Dr [(-122.1505,37.019),(-122.1516,37.016)]
+Portsmouth Ave [(-122.1023,37.262),(-122.1013,37.254)]
+Portsmouth Ave [(-122.1064,37.315),(-122.1064,37.308)]
+Portwood Ave [(-122.2322,37.76),(-122.2317,37.765)]
+Posen Ave [(-122.2828,37.848),(-122.2822,37.85)]
+Posey Loop [(-122.276,37.854),(-122.276,37.859)]
+Potawatami Dr [(-121.9238,37.063),(-121.924,37.056)]
+Powell St [(-122.2926,37.388),(-122.2937,37.387)]
+Powell St [(-122.3101,37.375),(-122.3108,37.375)]
+Prairie Dr [(-121.9124,37.667),(-121.9123,37.664)]
+Prarie Dr [(-121.9102,37.652),(-121.910115,37.65226)]
+Preda St [(-122.1686,37.244),(-122.1691,37.255)]
+Presley Way [(-122.2461,37.462),(-122.2462,37.465)]
+Preston Ct [(-121.733536,37.01447),(-121.733204,37.00955)]
+Preston Ct [(-121.9666,37.659),(-121.9652,37.668)]
+Prestwick Ave [(-122.0369,37.208),(-122.0363,37.202)]
+Prince Dr [(-121.9164,37.155),(-121.9185,37.145)]
+Prince St [(-122.2582,37.541),(-122.2568,37.543)]
+Prince St [(-122.2723,37.524),(-122.2713,37.525)]
+Prince St [(-122.2802,37.512),(-122.2794,37.514)]
+Princeton Ter [(-121.977476,37.61102),(-121.977871,37.61066)]
+Princeton Way [(-121.75,37.814),(-121.7472,37.814)]
+Proctor Ave [(-122.2222,37.364),(-122.2217,37.36)]
+Proctor Ave [(-122.2267,37.406),(-122.2251,37.386)]
+Proctor Road [(-122.0671,37.192),(-122.067,37.2)]
+Proctor Road [(-122.0761,37.177),(-122.073739,37.1724)]
+Prospect St [(-122.2492,37.699),(-122.249,37.695)]
+Prosperity Way [(-122.1033,37.031),(-122.1044,37.042)]
+Pueblo Dr [(-122.1748,37.269),(-122.1743,37.269)]
+Pueblo Creek [(-122.0958,37.203),(-122.0965,37.205)]
+Pueblo Serena [(-122.0958,37.222),(-122.0958,37.203)]
+Pueblo Spring [(-122.0964,37.238),(-122.0965,37.222)]
+Puerto Vallarta [(-121.8719,37.54),(-121.8728,37.528)]
+Pulaski Dr [(-122.0262,37.107),(-122.0254,37.1)]
+Pulsar Ave [(-121.7892,37.58955),(-121.7892,37.60011)]
+Purcell Pl [(-122.0249,37.718),(-122.0224,37.699)]
+Purdue St [(-122.1555,37.95),(-122.1565,37.949)]
+Pyramid St [(-121.768592,37.56164),(-121.768329,37.56198)]
+Quail Run Road [(-122.0448,37.808),(-122.0439,37.805)]
+Quebec Ave [(-122.1528,37.786),(-122.1535,37.785)]
+Quebec Common [(-122.051368,37.60756),(-122.05282,37.60102)]
+Queen Anne Dr [(-122.0751,37.883),(-122.077,37.865)]
+Quercus Ct [(-122.0267,37.59),(-122.0269,37.572)]
+Quigley Pl [(-122.1962,37.867),(-122.1957,37.861)]
+Quinn Lane [(-122.066405,37.74794),(-122.066039,37.73557)]
+Quintana Ct [(-121.9516,37.464),(-121.9522,37.463)]
+Quintana Way [(-121.9504,37.49),(-121.9508,37.489)]
+Rachelle St [(-121.7257,37.945),(-121.7257,37.932)]
+Racine St [(-122.2622,37.494),(-122.2624,37.501)]
+Racoon Hallow Ct [(-121.914577,37.63603),(-121.913452,37.63738)]
+Radele Ct [(-122.0101,37.363),(-122.0105,37.357)]
+Ragland St [(-122.109,37.877),(-122.1089,37.872)]
+Railroad Ave [(-121.7661,37.841),(-121.7654,37.842)]
+Railroad Ave [(-121.771533,37.82472),(-121.771,37.826)]
+Railroad Ave [(-121.779215,37.79798),(-121.779265,37.79635)]
+Railroad Ave [(-121.891,37.94),(-121.8924,37.941)]
+Railroad Ave [(-122.0245,37.013),(-122.0234,37.003),(-122.0223,37.993)]
+Railroad Ave [(-122.1835,37.394),(-122.1828,37.388)]
+Rainier Ave [(-121.8009,37.803),(-121.7999,37.804)]
+Ralston Com [(-121.9775,37.428),(-121.9771,37.432)]
+Ramona Ave [(-122.2391,37.291),(-122.2373,37.293)]
+Ramona Ave [(-122.244153,37.31499),(-122.243523,37.31109)]
+Rancho Arroyo Pkwy [(-121.9932,37.785),(-121.9929,37.774),(-121.9926,37.769)]
+Rancho Higuera Road [(-121.9112,37.96),(-121.9105,37.959)]
+Randall Ct [(-122.0749,37.976),(-122.0756,37.968)]
+Randicik Ct [(-121.863424,37.95818),(-121.863445,37.9654)]
+Randy St [(-122.1517,37.809),(-122.1523,37.807)]
+Ranker Pl [(-122.0725,37.4),(-122.0737,37.395)]
+Ranspot Dr [(-122.0972,37.999),(-122.0959,37)]
+Raymond Road [(-121.7462,37.306),(-121.7326,37.305)]
+Reading Ave [(-122.0779,37.874),(-122.0735,37.875)]
+Redbud Lane [(-122.0969,37.627),(-122.0978,37.627)]
+Redding Pl [(-122.1919,37.843),(-122.1921,37.841)]
+Redding St [(-122.191432,37.84242),(-122.1911,37.842)]
+Redding St [(-122.1943,37.858),(-122.1934,37.854)]
+Redding St [(-122.1978,37.901),(-122.1975,37.895)]
+Redwood Ct [(-121.9142,37.69),(-121.9144,37.696)]
+Redwood Road [(-122.0726,37.155),(-122.0726,37.139)]
+Redwood Road [(-122.0726,37.18079),(-122.0726,37.179)]
+Redwood Road [(-122.0726,37.909),(-122.0727,37.906)]
+Redwood Road [(-122.0727,37.955),(-122.0727,37.948)]
+Redwood Road [(-122.0736,37.393),(-122.075736,37.37634)]
+Redwood Road [(-122.1493,37.98),(-122.1437,37.001)]
+Redwood Road [(-122.174191,37.96191),(-122.174,37.966)]
+Redwood Road [(-122.1819,37.978),(-122.1811,37.968)]
+Redwood Road [(-122.1882,37.986),(-122.1877,37.986)]
+Redwood Creek [(-122.1366,37.968),(-122.1302,37.918)]
+Reed Ave [(-121.7581,37.516),(-121.7559,37.516)]
+Regailia Ave [(-121.86701,37.62944),(-121.867052,37.62182)]
+Regal Ave [(-122.0839,37.468),(-122.0857,37.449)]
+Regents Blvd [(-122.0662,37.779),(-122.066625,37.77216)]
+Regents Blvd [(-122.0673,37.759),(-122.0677,37.751)]
+Regents Blvd [(-122.0681,37.74),(-122.068,37.73)]
+Regents Blvd [(-122.068594,37.80778),(-122.0681,37.805)]
+Regents Blvd [(-122.077,37.865),(-122.0761,37.862)]
+Regional St [(-121.9328,37.029),(-121.9347,37.072)]
+Reinhardt Dr [(-122.1831,37.922),(-122.1828,37.918)]
+Renwick St [(-122.1989,37.797),(-122.1982,37.802)]
+Republic Ave [(-122.1688,37.046),(-122.1721,37.032)]
+Requa Road [(-122.2323,37.199),(-122.2293,37.21)]
+Revere Ave [(-122.03,37.129),(-122.0287,37.129)]
+Revere Ave [(-122.1347,37.359),(-122.134,37.362)]
+Reyes Dr [(-122.0791,37.873),(-122.0787,37.873)]
+Reynolds Dr [(-122.0036,37.671),(-122.002954,37.66809)]
+Rhine Way [(-121.84112,37.58518),(-121.840944,37.56363)]
+Rhododendron Dr [(-121.74419,37.09645),(-121.7445,37.105)]
+Ribera Ct [(-122.0292,37.644),(-122.0286,37.641)]
+Ricardo Ave [(-122.1176,37.761),(-122.1148,37.745)]
+Rich Ave [(-122.0351,37.309),(-122.0354,37.303)]
+Richardson Way [(-122.2263,37.224),(-122.225,37.22)]
+Richmond Ave [(-121.9969,37.452),(-121.9975,37.442)]
+Richmond Blvd [(-122.2584,37.211),(-122.2573,37.218)]
+Richmond Blvd [(-122.2592,37.195),(-122.2587,37.201)]
+Ridge Trl [(-121.8615,37.438),(-121.859211,37.42899)]
+Ridge Top Road [(-122.1538,37.164),(-122.1566,37.179)]
+Ridgeview Ter [(-121.970517,37.63559),(-121.970862,37.63332)]
+Ridgeway Ave [(-122.2513,37.286),(-122.2518,37.292)]
+Ridgeway Ave [(-122.2539,37.299),(-122.2548,37.302)]
+Ridgewood Dr [(-122.051305,37.59386),(-122.053772,37.58263)]
+Ridley Dr [(-121.9794,37.65),(-121.979,37.644)]
+Riley Dr [(-122.2999,37.858),(-122.299,37.86)]
+Rincon Ave [(-121.7824,37.828),(-121.7824,37.837)]
+Rispen Dr [(-122.2342,37.621),(-122.2326,37.634)]
+Riverdale Ct [(-121.9198,37.891),(-121.9201,37.893)]
+Riverside Ave [(-121.9758,37.74),(-121.9764,37.725)]
+Riviera Dr [(-122.0003,37.954),(-122.0003,37.957)]
+Riviera Dr [(-122.0003,37.96),(-122.0003,37.968)]
+Roberts Ave [(-121.9554,37.299),(-121.955,37.282)]
+Robertson Ave [(-122.0164,37.33),(-122.0173,37.318)]
+Robertson Ave [(-122.021,37.275),(-122.0213,37.27)]
+Robin St [(-121.9701,37.297),(-121.9698,37.287)]
+Robinson Dr [(-122.1825,37.096),(-122.1807,37.054)]
+Robledo Dr [(-122.1734,37.304),(-122.1706,37.281)]
+Roca Dr [(-122.0335,37.609),(-122.0314,37.599)]
+Rochelle Ave [(-122.0603,37.347),(-122.0594,37.329)]
+Rockford Road [(-122.0848,37.819),(-122.0842,37.814)]
+Rockingham Dr [(-121.8681,37.948),(-121.8689,37.944)]
+Rocklin Dr [(-122.070801,37.71701),(-122.0712,37.713)]
+Rocklin Dr [(-122.0719,37.698),(-122.0722,37.689)]
+Rockridge Blvd [(-122.242,37.457),(-122.2416,37.464)]
+Rockridge Blvd [(-122.2424,37.454),(-122.2417,37.453)]
+Rockrose Dr [(-122.0105,37.248),(-122.0114,37.252)]
+Rockwood Dr [(-122.0128,37.492),(-122.0109,37.482)]
+Rodney Com [(-121.9562,37.385),(-121.9555,37.382)]
+Rogers Ave [(-122.0044,37.43),(-122.0061,37.409)]
+Rolling Hills Dr [(-121.948386,37.07126),(-121.947082,37.07103)]
+Rolling Hills Dr [(-121.950806,37.10304),(-121.950378,37.11376)]
+Rollinghills Cir [(-121.945273,37.06404),(-121.945249,37.0671)]
+Romeo Pl [(-122.0562,37.692),(-122.0571,37.694)]
+Romey Lane [(-122.0603,37.825),(-122.0587,37.825)]
+Ronald Ct [(-121.9528,37.259),(-121.9537,37.259)]
+Ronda St [(-122.125,37.865),(-122.1251,37.856)]
+Roosevelt Pl [(-121.9918,37.267),(-121.9923,37.263)]
+Rosario Ct [(-122.110662,37.79847),(-122.109931,37.78596)]
+Rose Ave [(-121.8792,37.616),(-121.8838,37.656)]
+Rose Ave [(-122.244329,37.28237),(-122.2441,37.284),(-122.2438,37.287)]
+Rose Ave [(-122.2476,37.256),(-122.2469,37.268)]
+Rose Dr [(-122.1451,37.142),(-122.1445,37.138)]
+Rose St [(-121.757,37.84),(-121.757,37.831)]
+Rose St [(-122.2586,37.834),(-122.2575,37.836)]
+Rose St [(-122.2644,37.83),(-122.263462,37.83335)]
+Rose St [(-122.2696,37.82),(-122.2689,37.82)]
+Rose St [(-122.2843,37.782),(-122.2829,37.787)]
+Rose St [(-122.2882,37.769),(-122.287,37.771)]
+Rosedale Ct [(-121.9232,37.9),(-121.924,37.897)]
+Rosegate Ter [(-121.969628,37.63263),(-121.970152,37.638)]
+Roselli Dr [(-121.7848,37.636),(-121.7848,37.628)]
+Rosemary Ct [(-121.9314,37.088),(-121.9314,37.085)]
+Rosewood Ct [(-122.0622,37.37),(-122.0618,37.372)]
+Rosewood Common [(-121.964615,37.21789),(-121.964292,37.213)]
+Ross Cir [(-122.2466,37.502),(-122.2474,37.514)]
+Ross Gate Way [(-121.8794,37.845),(-121.8818,37.836)]
+Rothchild Ct [(-121.950257,37.08452),(-121.951086,37.08115)]
+Roundhill Dr [(-122.0376,37.542),(-122.0377,37.536)]
+Rousillon Ave [(-122.046,37.672),(-122.0451,37.674)]
+Roxanne St [(-121.7288,37.853),(-121.7287,37.849)]
+Roxbury Ave [(-122.1354,37.339),(-122.1346,37.336)]
+Roxbury Lane [(-122.0333,37.615),(-122.0329,37.618)]
+Roxbury Lane [(-122.0359,37.623),(-122.0356,37.619)]
+Royal Ann Dr [(-122.0267,37.889),(-122.0268,37.88)]
+Royal Ann Dr [(-122.0273,37.871),(-122.0278,37.862)]
+Royal Ann St [(-122.1696,37.367),(-122.1691,37.362)]
+Royal Ann St [(-122.1705,37.38),(-122.1702,37.376)]
+Royal Palm Dr [(-121.9945,37.315),(-121.9922,37.303)]
+Ruby Road [(-121.8029,37.688),(-121.8023,37.68)]
+Ruby St [(-122.0765,37.815),(-122.0751,37.802)]
+Rudsdale St [(-122.1855,37.552),(-122.1849,37.547)]
+Rugby Ave [(-122.274,37.045),(-122.2738,37.037)]
+Running Hills Ave [(-121.7262,37.22),(-121.7238,37.213)]
+Ruschin Dr [(-122.0397,37.459),(-122.0393,37.465)]
+Russell Ave [(-122.1694,37.819),(-122.1687,37.845)]
+Russell St [(-122.2661,37.569),(-122.2652,37.571)]
+Russell St [(-122.2695,37.564),(-122.2684,37.566)]
+Russell Way [(-122.08,37.771),(-122.0781,37.783)]
+Russet St [(-122.1758,37.361),(-122.1753,37.355)]
+Rutgers Way [(-121.7421,37.744),(-121.742,37.739)]
+Ruth Ct [(-122.1301,37.004),(-122.1288,37.002)]
+Ruth Way [(-121.7928,37.766),(-121.792,37.767)]
+Ruth Glen [(-121.9121,37.281),(-121.9124,37.278)]
+Ruus Road [(-122.0667,37.296),(-122.066,37.28)]
+Sabercat Road [(-121.9396,37.176),(-121.9388,37.165),(-121.937,37.159)]
+Sabercat Road [(-121.9453,37.266),(-121.944385,37.25156),(-121.9408,37.195)]
+Sable Pointe [(-122.2393,37.439),(-122.24,37.434)]
+Sable Pointe [(-122.241,37.424),(-122.2416,37.418)]
+Sacramento Ave [(-121.9861,37.44),(-121.9865,37.436)]
+Sacramento St [(-122.277606,37.50815),(-122.277616,37.50591)]
+Sacramento St [(-122.2799,37.606),(-122.2797,37.597)]
+Sacramento St [(-122.2813,37.703),(-122.2811,37.695)]
+Saddle Brook Dr [(-122.1478,37.909),(-122.1454,37.904),(-122.1451,37.888)]
+Saginaw Ct [(-121.8803,37.898),(-121.8806,37.901)]
+Saguare Com [(-121.9049,37.022),(-121.9043,37.017)]
+Sailway Dr [(-121.9673,37.495),(-121.9686,37.502)]
+Salem St [(-122.2794,37.361),(-122.2796,37.368)]
+Salem St [(-122.2832,37.474),(-122.2835,37.479)]
+Salinas Pl [(-122.041,37.689),(-122.0404,37.686)]
+Salisbury St [(-122.2162,37.863),(-122.2146,37.848)]
+Salton Sea Lane [(-122.0597,37.88),(-122.0591,37.869)]
+San Andreas Dr [(-122.0592,37.957),(-122.0585,37.954)]
+San Andreas Dr [(-122.0609,37.9),(-122.0614,37.895)]
+San Andreas Dr [(-122.0621,37.973),(-122.0614,37.972)]
+San Andreas Dr [(-122.0635,37.878),(-122.0648,37.891)]
+San Andreas Dr [(-122.0658,37.907),(-122.0661,37.914)]
+San Andreas Dr [(-122.0668,37.926),(-122.0672,37.931)]
+San Antonio Ave [(-122.2585,37.679),(-122.2566,37.672)]
+San Antonio St [(-122.0472,37.155),(-122.0477,37.108)]
+San Antonio Way [(-122.2323,37.891),(-122.2314,37.886)]
+San Antonio Creek [(-121.8722,37.759),(-121.8641,37.771)]
+San Antonio Reservoir [(-121.8487,37.728),(-121.8359,37.67)]
+San Bernardino Way [(-122.0621,37.936),(-122.0628,37.933)]
+San Carlos Ave [(-122.0912,37.941),(-122.0899,37.94)]
+San Carlos Ave [(-122.2886,37.931),(-122.2885,37.91)]
+San Carlos Walk [(-122.2087,37.795),(-122.208,37.789)]
+San Clemente St [(-122.0511,37.188),(-122.051,37.177)]
+San Franciscan Dr [(-122.0589,37.24),(-122.0582,37.246)]
+San Francisco Bay [(-122.108,37.032),(-122.1048,37.001)]
+San Francisco Bay [(-122.3115,37.814),(-122.3096,37.777)]
+San Francisco Bay [(-122.3176,37.669),(-122.3108,37.652)]
+San Jose Ave [(-122.2423,37.594),(-122.2409,37.586)]
+San Jose Ave [(-122.2455,37.609),(-122.2445,37.605)]
+San Jose Ave [(-122.2543,37.65),(-122.251,37.635)]
+San Jose Ct [(-122.0563,37.922),(-122.055864,37.91921)]
+San Juan St [(-122.2158,37.803),(-122.2149,37.795)]
+San Leandro Blvd [(-122.1557,37.174),(-122.1545,37.168)]
+San Leandro St [(-122.1703,37.323),(-122.1689,37.312)]
+San Leandro St [(-122.1717,37.335),(-122.1709,37.329)]
+San Leandro St [(-122.2195,37.721),(-122.2186,37.716)]
+San Leandro St [(-122.2251,37.748),(-122.2242,37.743)]
+San Leandro Bay [(-122.2241,37.553),(-122.2253,37.542)]
+San Leandro Creek [(-122.154489,37.27978),(-122.155,37.281)]
+San Leandro Creek [(-122.175792,37.24864),(-122.1807,37.247)]
+San Leandro Creek Canal [(-122.2081,37.409),(-122.2076,37.401)]
+San Lorenzo Ave [(-122.2861,37.93),(-122.2857,37.929)]
+San Lorenzo Creek [(-122.0544,37.907),(-122.0547,37.908)]
+San Lorenzo Creek [(-122.063257,37.85966),(-122.064271,37.85339)]
+San Lorenzo Creek [(-122.0741,37.799),(-122.0757,37.789)]
+San Lorenzo Creek [(-122.117293,37.85868),(-122.120139,37.85739)]
+San Lorenzo Creek [(-122.124957,37.853),(-122.1271,37.849)]
+San Lorenzo Creek [(-122.1539,37.747),(-122.1616,37.703)]
+San Luces Way [(-122.0672,37.963),(-122.0695,37.954)]
+San Luis Ct [(-121.8768,37.483),(-121.8766,37.474)]
+San Luis Ct [(-122.065,37.975),(-122.0657,37.973)]
+San Luis Road [(-122.271,37.959),(-122.2701,37.933)]
+San Marco Way [(-122.0666,37.95),(-122.0687,37.942)]
+San Mateo Road [(-122.2724,37.925),(-122.2728,37.928)]
+San Mateo Way [(-122.0609,37.926),(-122.061842,37.92391)]
+San Miguel Ave [(-122.0793,37.052),(-122.079224,37.0254)]
+San Miguel Ave [(-122.0801,37.927),(-122.08,37.911)]
+San Moreno Ct [(-121.954,37.576),(-121.9532,37.558)]
+San Pablo Ave [(-122.2718,37.084),(-122.2721,37.093)]
+San Pablo Ave [(-122.2797,37.326),(-122.2798,37.332)]
+San Pablo Ave [(-122.2822,37.405),(-122.2824,37.411)]
+San Pablo Ave [(-122.2834,37.442),(-122.2838,37.45)]
+San Pablo Ave [(-122.2859,37.518),(-122.2857,37.515)]
+San Pablo Ave [(-122.2864,37.537),(-122.2862,37.532)]
+San Pablo Ave [(-122.2922,37.725),(-122.2922,37.716),(-122.2918,37.704)]
+San Pablo Ave [(-122.2961,37.84),(-122.2959,37.832)]
+San Rafael St [(-122.1398,37.225),(-122.1395,37.219)]
+San Ramon Road [(-121.937288,37.09164),(-121.937307,37.0922)]
+San Sabana Road [(-121.9402,37.066),(-121.9402,37.095)]
+Sandalwood St [(-122.0347,37.493),(-122.0327,37.483)]
+Sandburg Way [(-122.0625,37.226),(-122.0625,37.203)]
+Sandelin Ave [(-122.1351,37.294),(-122.1343,37.298)]
+Sandringham Road [(-122.2139,37.183),(-122.2142,37.179)]
+Sandringham Road [(-122.2155,37.175),(-122.2163,37.165)]
+Sandy Road [(-122.0643,37.089),(-122.0645,37.086)]
+Sandy Hook Dr [(-122.0615,37.22),(-122.062,37.221)]
+Sanford St [(-122.1546,37.737),(-122.1543,37.728)]
+Sangamore St [(-122.1069,37.471),(-122.1076,37.468)]
+Santa Barbara Road [(-122.2679,37.928),(-122.2666,37.92)]
+Santa Clara Ave [(-122.2509,37.695),(-122.2479,37.682)]
+Santa Clara Ave [(-122.254714,37.71317),(-122.254,37.71)]
+Santa Clara Ave [(-122.2551,37.211),(-122.254,37.205)]
+Santa Clara Ave [(-122.2787,37.736),(-122.2761,37.736)]
+Santa Clara Ave [(-122.2791,37.994),(-122.2798,37.989)]
+Santa Clara St [(-122.0901,37.496),(-122.0885,37.485)]
+Santa Clara St [(-122.0923,37.51),(-122.0919,37.504)]
+Santa Clara St [(-122.1009,37.618),(-122.1006,37.613)]
+Santa Clara St [(-122.1012,37.667),(-122.1012,37.66389),(-122.1012,37.65718)]
+Santa Clara Way [(-121.7539,37.854),(-121.7508,37.856),(-121.75,37.856)]
+Santa Fe Ave [(-122.2899,37.817),(-122.2899,37.815)]
+Santa Maria Ave [(-122.0773,37),(-122.0773,37.98)]
+Santa Maria Dr [(-122.0633,37.965),(-122.0624,37.961)]
+Santa Paula [(-122.1607,37.848),(-122.1596,37.834)]
+Santa Ray Ave [(-122.2393,37.122),(-122.2381,37.113)]
+Santa Rita Road [(-121.872608,37.75282),(-121.8731,37.766)]
+Santa Rita Road [(-121.877461,37.9142),(-121.8775,37.922)]
+Santa Rita St [(-122.2065,37.804),(-122.2058,37.796)]
+Santa Rita St [(-122.2129,37.832),(-122.2104,37.839)]
+Santa Rosa St [(-122.1506,37.247),(-122.1502,37.238)]
+Santa Rosa St [(-122.1513,37.265),(-122.1509,37.256)]
+Santa Teresa [(-122.1576,37.826),(-122.1565,37.812),(-122.1553,37.797)]
+Santa Teresa Com [(-121.9505,37.564),(-121.9496,37.559)]
+Santee Road [(-122.0371,37.691),(-122.0376,37.685)]
+Santiago Road [(-122.176,37.962),(-122.1756,37.956)]
+Santo Ct [(-121.946172,37.10869),(-121.947136,37.10367)]
+Saratoga St [(-122.1024,37.997),(-122.1019,37.997)]
+Sarazen Ave [(-122.1539,37.58),(-122.1532,37.585)]
+Saroni Dr [(-122.1954,37.345),(-122.1944,37.347)]
+Saturn Dr [(-122.1283,37.145),(-122.1238,37.117)]
+Sausal Creek [(-122.2126,37.122),(-122.213944,37.10691)]
+Sausal Creek [(-122.215521,37.03532),(-122.216544,37.01827)]
+Sausal Creek [(-122.2175,37.985),(-122.218,37.982)]
+Sausal Creek [(-122.218,37.982),(-122.218834,37.9583)]
+Sausal Creek [(-122.219108,37.9487),(-122.2193,37.942),(-122.219513,37.93313)]
+Sausal Creek [(-122.2217,37.893),(-122.2221,37.884)]
+Sausal Creek [(-122.2228,37.862),(-122.2232,37.856)]
+Scarborough Dr [(-122.0362,37.568),(-122.0362,37.556)]
+Scarborough Dr [(-122.1993,37.214),(-122.1999,37.24),(-122.1994,37.241)]
+Scenic Ave [(-121.7262,37.171),(-121.7232,37.171)]
+Scenic Ave [(-121.7314,37.171),(-121.7305,37.17)]
+Scenic Ave [(-121.7414,37.166),(-121.73802,37.16978)]
+Scenic Ave [(-122.2282,37.278),(-122.2284,37.296)]
+Scenic Ave [(-122.2619,37.762),(-122.2617,37.749)]
+Scenicview Dr [(-122.1354,37.26),(-122.1325,37.252)]
+School St [(-122.1378,37.209),(-122.1369,37.199),(-122.1364,37.197)]
+School St [(-122.2116,37.945),(-122.2103,37.94)]
+School Way [(-122.0838,37.18),(-122.0833,37.177)]
+Schooner Hill [(-122.2263,37.56),(-122.2269,37.564)]
+Schuster Ave [(-122.0854,37.081),(-122.0854,37.074)]
+Scott Pl [(-122.0802,37.38),(-122.0815,37.381)]
+Scott Creek [(-121.8694,37.814),(-121.8694,37.803)]
+Scott Creek Road [(-121.8999,37.678),(-121.8975,37.685)]
+Scott Creek Road [(-121.9047,37.667),(-121.9034,37.67)]
+Scott Creek Road [(-121.9098,37.651),(-121.9086,37.655)]
+Sea Bridge Way [(-122.2364,37.426),(-122.2359,37.417)]
+Sea View Pkwy [(-122.242913,37.47248),(-122.2421,37.472),(-122.2411,37.471)]
+Sea View Pkwy [(-122.2499,37.466),(-122.2496,37.468)]
+Sea View Pkwy [(-122.253,37.455),(-122.2519,37.457)]
+Sea View Pkwy [(-122.2547,37.434),(-122.255,37.439)]
+Seal Rock Ter [(-122.033374,37.69186),(-122.0335,37.692),(-122.0338,37.694)]
+Seaver St [(-122.1016,37.427),(-122.1016,37.419)]
+Seaview Ave [(-122.0623,37.113),(-122.0599,37.111)]
+Seawall Dr [(-122.316154,37.63126),(-122.315489,37.60045)]
+Segovia Pl [(-121.9478,37.596),(-121.9489,37.585)]
+Selkirk St [(-121.9798,37.336),(-121.9796,37.331)]
+Seminary Ave [(-122.1772,37.796),(-122.1756,37.798)]
+Seminary Ave [(-122.1814,37.772),(-122.1807,37.778)]
+Seneca St [(-122.03,37.122),(-122.03,37.115)]
+Seneca Park Ave [(-121.9649,37.17),(-121.9653,37.167)]
+Senior Ave [(-122.2487,37.826),(-122.2473,37.827)]
+Sequoia Road [(-122.0013,37.617),(-122.001192,37.61346)]
+Sequoia Ter [(-121.997978,37.6571),(-121.997403,37.66696)]
+Seven Hills Road [(-122.0876,37.1),(-122.086344,37.1012),(-122.0855,37.102)]
+Severn Dr [(-122.0359,37.584),(-122.0368,37.571)]
+Severn Pl [(-122.0363,37.586),(-122.0367,37.581)]
+Sevilla Road [(-122.0829,37.809),(-122.0831,37.81)]
+Seville Pl [(-121.9514,37.522),(-121.9522,37.518)]
+Sextus Road [(-122.1938,37.33),(-122.1896,37.327)]
+Seymour Pl [(-122.0357,37.84),(-122.0367,37.837)]
+Shadow Ridge Dr [(-122.041,37.144),(-122.0411,37.162)]
+Shady Creek Road [(-121.913106,37.24701),(-121.914087,37.24475)]
+Shafer Creek [(-121.681036,37.12545),(-121.694801,37.25112)]
+Shafter Ave [(-122.2539,37.418),(-122.2527,37.434)]
+Shafter Ave [(-122.2569,37.383),(-122.2556,37.399)]
+Shafter Ave [(-122.2586,37.313),(-122.2583,37.323)]
+Shamrock Pl [(-121.929,37.24),(-121.9295,37.247)]
+Shannon Ave [(-121.94218,37.14137),(-121.9418,37.142)]
+Shasta St [(-121.802842,37.889),(-121.802801,37.8826)]
+Shattuck Ave [(-122.053676,37.61223),(-122.052619,37.61591)]
+Shattuck Ave [(-122.2633,37.339),(-122.2634,37.347)]
+Shattuck Ave [(-122.2648,37.468),(-122.2648,37.474)]
+Shattuck Ave [(-122.2675,37.712),(-122.2674,37.704)]
+Shattuck Ave [(-122.2683,37.776),(-122.2683,37.766)]
+Shattuck Ave [(-122.2686,37.904),(-122.2686,37.897)]
+Shaw St [(-122.1518,37.459),(-122.1511,37.455)]
+Sheffield Lane [(-122.0511,37.006),(-122.051292,37.00178)]
+Sheffield Road [(-122.1032,37.097),(-122.1026,37.095)]
+Sheffield Road [(-122.241,37.45),(-122.2413,37.446)]
+Sheffield Road [(-122.2421,37.44),(-122.2427,37.437)]
+Sheffield Road [(-122.2457,37.441),(-122.2448,37.44)]
+Sheila St [(-122.0453,37.852),(-122.045,37.847)]
+Sheldon St [(-122.146,37.455),(-122.1454,37.451)]
+Shepherd Ave [(-122.0707,37.383),(-122.0737,37.367)]
+Shepherd Canyon Road [(-122.1845,37.355),(-122.1835,37.355)]
+Shepherd Canyon Road [(-122.1988,37.302),(-122.196132,37.31137)]
+Shepherd Canyon Road [(-122.2052,37.245),(-122.2027,37.236)]
+Sheridan Road [(-121.901114,37.58914),(-121.899,37.574)]
+Sheridan Road [(-122.2279,37.425),(-122.2253,37.411),(-122.2223,37.377)]
+Sherman St [(-122.2628,37.781),(-122.2627,37.787)]
+Sherman St [(-122.263,37.695),(-122.262932,37.70799)]
+Sherman St [(-122.2631,37.667),(-122.2631,37.674)]
+Sherry Ct [(-121.7642,37.736),(-121.76437,37.73958)]
+Sherwood Lane [(-122.2474,37.467),(-122.246514,37.45985)]
+Shiela Way [(-122.0734,37.757),(-122.073,37.75)]
+Shinn St [(-121.9818,37.64),(-121.9817,37.636)]
+Shirley Ave [(-122.1157,37.717),(-122.117,37.712)]
+Shoreline Dr [(-122.2638,37.596),(-122.2607,37.584)]
+Shoreline Dr [(-122.2657,37.603),(-122.2648,37.6)]
+Shoreline Dr [(-122.2714,37.626),(-122.2691,37.616)]
+Shylock Dr [(-122.0488,37.748),(-122.0486,37.74)]
+Sidney Ave [(-122.1279,37.096),(-122.1274,37.1)]
+Sigourney Elysian Fields Dr [(-122.127563,37.66495),(-122.1275,37.649)]
+Sigourney Elysian Fields Dr [(-122.128,37.674),(-122.1277,37.667)]
+Silva Lane [(-122.237019,37.28132),(-122.235461,37.27888)]
+Silvergate Dr [(-121.941,37.097),(-121.9402,37.095)]
+Simm Ct [(-121.9757,37.15),(-121.9748,37.153)]
+Simmons St [(-122.1881,37.803),(-122.1874,37.805)]
+Sims Dr [(-122.21,37.207),(-122.2101,37.237)]
+Simson St [(-122.1693,37.737),(-122.1687,37.74)]
+Sinbad Creek [(-121.9177,37.343),(-121.9202,37.366)]
+Sinbad Creek [(-121.9498,37.613),(-121.9556,37.643)]
+Singing Hills Ave [(-121.7262,37.199),(-121.7237,37.206)]
+Singleton Ave [(-122.2896,37.87397),(-122.288161,37.87394)]
+Sioux Ct [(-121.926147,37.14374),(-121.9248,37.122)]
+Sioux Dr [(-121.928,37.142),(-121.926147,37.14374)]
+Siward Dr [(-122.043286,37.63282),(-122.043581,37.63639)]
+Siward Dr [(-122.044259,37.64617),(-122.044499,37.6493)]
+Siward Dr [(-122.046199,37.68158),(-122.046,37.672)]
+Siward Dr [(-122.047527,37.71764),(-122.0475,37.716)]
+Skylark Dr [(-122.0117,37.839),(-122.011,37.835)]
+Skylark Dr [(-122.0192,37.85),(-122.0186,37.848)]
+Skyline Blvd [(-122.1409,37.819),(-122.1307,37.73)]
+Skyline Blvd [(-122.144,37.851),(-122.1409,37.829)]
+Skyline Blvd [(-122.1701,37.988),(-122.167,37.982)]
+Skyline Blvd [(-122.1738,37.01),(-122.1714,37.996)]
+Skyline Blvd [(-122.1772,37.039),(-122.1774,37.037)]
+Skyline Blvd [(-122.1865,37.25),(-122.186,37.247)]
+Skyline Blvd [(-122.1885,37.275),(-122.1873,37.266)]
+Skyline Blvd [(-122.1965,37.405),(-122.190866,37.38122)]
+Skyline Blvd [(-122.2091,37.506),(-122.2076,37.506)]
+Skyline Dr [(-122.0277,37.5),(-122.0284,37.498)]
+Skyview Dr [(-122.1276,37.242),(-122.1274,37.244)]
+Skywest Dr [(-122.1161,37.62),(-122.1123,37.586)]
+Sleepy Hollow Ave [(-122.093,37.35),(-122.0927,37.343)]
+Sleepy Hollow Ave [(-122.1003,37.335),(-122.1008,37.332)]
+Sleepy Hollow Ave [(-122.1045,37.316),(-122.1054,37.315)]
+Slender Ct [(-122.0565,37.73),(-122.055,37.735)]
+Smalley Ave [(-122.095194,37.69714),(-122.0945,37.7)]
+Smith St [(-122.0727,37.965),(-122.0742,37.964)]
+Smith St [(-122.0759,37.963),(-122.0778,37.964)]
+Snake Road [(-122.206567,37.27603),(-122.2062,37.281),(-122.2057,37.292)]
+Snake Road [(-122.2066,37.331),(-122.2048,37.345)]
+Sodaville Ct [(-121.9266,37.029),(-121.9278,37.033)]
+Solano Ave [(-122.1131,37.735),(-122.1161,37.724)]
+Solano Ave [(-122.2829,37.912),(-122.2819,37.913)]
+Solano Ave [(-122.2871,37.91),(-122.2867,37.91)]
+Solano Ave [(-122.289,37.909),(-122.2885,37.91)]
+Solano Way [(-122.0773,37.921),(-122.0785,37.921)]
+Solano Way [(-122.2451,37.889),(-122.2443,37.885)]
+Solomon Lane [(-122.2372,37.299),(-122.2374,37.3)]
+Somerset Ave [(-122.0827,37.016),(-122.0811,37.016)]
+Somerset Ave [(-122.0893,37.006),(-122.0885,37.008)]
+Somerset Ave [(-122.0907,37.003),(-122.0899,37.005)]
+Somerset Ave [(-122.0936,37.994),(-122.0922,37.998)]
+Somerset Pl [(-122.0295,37.49),(-122.0299,37.485)]
+Somerset Pl [(-122.2732,37.949),(-122.27246,37.94689)]
+Sonoma Ave [(-122.2776,37.858),(-122.2765,37.856)]
+Sonoma Dr [(-121.8796,37.487),(-121.8799,37.488)]
+Soquel St [(-122.0664,37.711),(-122.0657,37.707)]
+Sora Com [(-122.043,37.743),(-122.0428,37.746)]
+Sorani Way [(-122.0785,37.172),(-122.0782,37.171)]
+Soto Road [(-122.0812,37.561),(-122.0798,37.545)]
+South Road [(-121.8941,37.283),(-121.894,37.279)]
+South Bay Aqueduct [(-121.6786,37.942),(-121.676067,37.8981)]
+South Dry Creek Branch [(-122.0168,37.083),(-122.0173,37.084)]
+South Fork Trout Creek [(-121.658085,37.99874),(-121.657591,37.02423)]
+South Front Road [(-121.7116,37.134),(-121.7092,37.145)]
+South Front Road [(-121.723953,37.0797),(-121.722,37.062)]
+South Front Road [(-121.738379,37.02183),(-121.737947,37.02335)]
+Southampton Ave [(-122.2745,37.948),(-122.2742,37.962)]
+Southern Pacific Railroad [(-121.558002,37.00663),(-121.576,37.136)]
+Southern Pacific Railroad [(-121.6695,37.391),(-121.666889,37.41337)]
+Southern Pacific Railroad [(-121.7674,37.843),(-121.7686,37.84)]
+Southern Pacific Railroad [(-122.3002,37.674),(-122.2999,37.661)]
+Southlake Com [(-121.9572,37.113),(-121.9565,37.116)]
+Southwick Ct [(-121.9441,37.185),(-121.9436,37.18)]
+Sp Railroad [(-121.7182,37.017),(-121.7162,37.025)]
+Sp Railroad [(-121.8204,37.746),(-121.8139,37.753)]
+Sp Railroad [(-121.8591,37.701),(-121.8586,37.705)]
+Sp Railroad [(-121.8699,37.631),(-121.8678,37.651)]
+Sp Railroad [(-121.87958,37.88603),(-121.880675,37.89396)]
+Sp Railroad [(-121.893564,37.99009),(-121.897,37.016)]
+Sp Railroad [(-121.8977,37.022),(-121.9022,37.054)]
+Sp Railroad [(-121.9271,37.788),(-121.9185,37.626)]
+Sp Railroad [(-121.950757,37.25243),(-121.9506,37.25)]
+Sp Railroad [(-121.9565,37.898),(-121.9562,37.9)]
+Sp Railroad [(-121.9684,37.715),(-121.9669,37.701),(-121.9655,37.69)]
+Sp Railroad [(-121.9736,37.616),(-121.9737,37.608)]
+Sp Railroad [(-121.993831,37.81669),(-121.992146,37.8107)]
+Sp Railroad [(-122.0257,37.349),(-122.0289,37.31)]
+Sp Railroad [(-122.0315,37.251),(-122.0292,37.24)]
+Sp Railroad [(-122.0321,37.271),(-122.0347,37.265)]
+Sp Railroad [(-122.0386,37.133),(-122.0335,37.089)]
+Sp Railroad [(-122.0414,37.268),(-122.042509,37.26338)]
+Sp Railroad [(-122.0553,37.212),(-122.0652,37.134),(-122.0654,37.131)]
+Sp Railroad [(-122.0594,37.75),(-122.0593,37.743)]
+Sp Railroad [(-122.0626,37.857),(-122.0616,37.845)]
+Sp Railroad [(-122.0734,37.001),(-122.0734,37.997)]
+Sp Railroad [(-122.076691,37.99914),(-122.075907,37.99243)]
+Sp Railroad [(-122.086,37.079),(-122.081,37.036)]
+Sp Railroad [(-122.0914,37.601),(-122.087,37.56),(-122.086408,37.5551)]
+Sp Railroad [(-122.10629,37.73042),(-122.1054,37.723)]
+Sp Railroad [(-122.1129,37.315),(-122.1125,37.311)]
+Sp Railroad [(-122.121,37.857),(-122.1187,37.837)]
+Sp Railroad [(-122.137,37.576),(-122.1327,37.53)]
+Sp Railroad [(-122.137792,37.003),(-122.1365,37.992),(-122.131257,37.94612)]
+Sp Railroad [(-122.172213,37.03399),(-122.1678,37.059)]
+Sp Railroad [(-122.1748,37.322),(-122.174,37.315)]
+Sp Railroad [(-122.1785,37.355),(-122.178,37.351)]
+Sp Railroad [(-122.17894,37.32386),(-122.179281,37.31827),(-122.1807,37.295)]
+Sp Railroad [(-122.1801,37.115),(-122.178,37.088)]
+Sp Railroad [(-122.1947,37.497),(-122.193328,37.4848)]
+Sp Railroad [(-122.2269,37.73),(-122.2272,37.726)]
+Sp Railroad [(-122.2281,37.761),(-122.2268,37.753)]
+Sp Railroad [(-122.2346,37.727),(-122.2343,37.726)]
+Sp Railroad [(-122.2411,37.85),(-122.2359,37.814)]
+Sp Railroad [(-122.2506,37.891),(-122.2501,37.889)]
+Sp Railroad [(-122.2724,37.946),(-122.2713,37.942)]
+Sp Railroad [(-122.2737,37.774),(-122.2731,37.765)]
+Sp Railroad [(-122.2744,37.802),(-122.2743,37.795)]
+Sp Railroad [(-122.275429,37.88474),(-122.2754,37.883)]
+Sp Railroad [(-122.2853,37.355),(-122.2847,37.334)]
+Sp Railroad [(-122.2864,37.393),(-122.2881,37.387)]
+Sp Railroad [(-122.2888,37.383),(-122.2893,37.38)]
+Sp Railroad [(-122.2898,37.349),(-122.2887,37.319),(-122.2883,37.307)]
+Sp Railroad [(-122.2939,37.484),(-122.2936,37.475)]
+Sp Railroad [(-122.2965,37.56),(-122.2959,37.545)]
+Sp Railroad [(-122.305229,37.83926),(-122.3049,37.822)]
+Sp Railroad [(-122.3086,37.087),(-122.31,37.085)]
+Spady St [(-121.9689,37.424),(-121.969174,37.41761)]
+Sparrow Dr [(-121.9331,37.139),(-121.9328,37.134)]
+Sparrow Road [(-122.0827,37.209),(-122.0826,37.203)]
+Spence Ave [(-121.9733,37.616),(-121.9728,37.619)]
+Spetti Dr [(-121.9684,37.665),(-121.9696,37.658)]
+Spinnaker Way [(-122.3138,37.694),(-122.3171,37.687)]
+Spoonbill Common [(-122.043662,37.66522),(-122.043425,37.66065)]
+Spring St [(-121.8702,37.62),(-121.871,37.621)]
+Springbrook Lane [(-122.057162,37.29952),(-122.056705,37.29151)]
+Springdale Ave [(-121.9196,37.845),(-121.92,37.854)]
+Springdale Ave [(-121.9229,37.884),(-121.9233,37.888)]
+Springfield St [(-122.1598,37.485),(-122.1597,37.477)]
+Springhouse Dr [(-121.883683,37.89587),(-121.880871,37.89125)]
+Springlake Dr [(-122.133113,37.93134),(-122.134,37.93)]
+Springtown Blvd [(-121.74,37.039),(-121.7407,37.059)]
+Springtown Blvd [(-121.743242,37.07568),(-121.745,37.088)]
+Spruce St [(-122.0506,37.327),(-122.0496,37.322)]
+Spruce St [(-122.0566,37.368),(-122.0565,37.361)]
+Spruce St [(-122.1578,37.994),(-122.1586,37.991)]
+Spruce St [(-122.2398,37.048),(-122.2393,37.056)]
+Spruce St [(-122.2659,37.849),(-122.2655,37.839)]
+Spruce St [(-122.2698,37.99),(-122.2693,37.981)]
+St Anthony Dr [(-121.939,37.474),(-121.9385,37.471)]
+St Charles St [(-122.2654,37.763),(-122.2653,37.773)]
+St Johns St [(-121.8764,37.643),(-121.8777,37.646)]
+St Matthew Dr [(-122.024,37.394),(-122.0234,37.391)]
+St Michael Cir [(-121.853648,37.6251),(-121.853779,37.61796)]
+Stacy St [(-122.1218,37.544),(-122.1199,37.532)]
+Stadium Way [(-121.758155,37.66715),(-121.757912,37.66728)]
+Stagecoach Road [(-121.921401,37.27049),(-121.9218,37.277)]
+Stalker Way [(-122.264,37.807),(-122.2672,37.816)]
+Stanford Ave [(-122.2764,37.438),(-122.2773,37.433)]
+Stanford Ave [(-122.2781,37.43),(-122.2802,37.42)]
+Stanford Way [(-121.7451,37.826),(-121.743925,37.82264)]
+Stanford Way [(-121.7473,37.828),(-121.7459,37.826)]
+Stanley Ave [(-121.9621,37.242),(-121.9632,37.24)]
+Stanley Ave [(-122.1504,37.485),(-122.15,37.478),(-122.1498,37.469)]
+Stanley Blvd [(-121.7971,37.769),(-121.7948,37.772)]
+Stanley Blvd [(-121.8705,37.659),(-121.871,37.66)]
+Stannage Ave [(-122.2939,37.844),(-122.2939,37.828)]
+Stannage Ave [(-122.297,37.939),(-122.2965,37.923)]
+Stanton Ave [(-122.0889,37.939),(-122.0885,37.928)]
+Stanton Ave [(-122.0953,37.027),(-122.0944,37.022)]
+Stanton Ave [(-122.100392,37.0697),(-122.099513,37.06052)]
+Stanton Hill Road [(-122.0935,37.963),(-122.0929,37.964)]
+Stanwood Ave [(-122.0839,37.416),(-122.0839,37.409)]
+Star View Ct [(-122.2251,37.516),(-122.2248,37.511)]
+Starflower Way [(-121.7298,37.099),(-121.7323,37.095)]
+Starling Dr [(-122.015929,37.82489),(-122.0171,37.83)]
+Starlite Way [(-121.9167,37.738),(-121.9162,37.745)]
+Starr Ct [(-121.9238,37.371),(-121.9246,37.373)]
+Starr Ct [(-121.9249,37.373),(-121.92514,37.3722)]
+Starr St [(-121.9213,37.378),(-121.9216,37.376)]
+Starview Dr [(-122.1248,37.197),(-122.1231,37.201)]
+Starward Dr [(-121.9356,37.103),(-121.936,37.109)]
+Starward Dr [(-121.9361,37.115),(-121.9363,37.128)]
+State Hwy 123 [(-122.3004,37.986),(-122.2998,37.969),(-122.2995,37.962),(-122.2992,37.952),(-122.299,37.942),(-122.2987,37.935),(-122.2984,37.924),(-122.2982,37.92),(-122.2976,37.904),(-122.297,37.88),(-122.2966,37.869),(-122.2959,37.848),(-122.2961,37.843)]
+State Hwy 13 [(-122.1797,37.943),(-122.179871,37.91849),(-122.18,37.9),(-122.179023,37.86615),(-122.1787,37.862),(-122.1781,37.851),(-122.1777,37.845),(-122.1773,37.839),(-122.177,37.833)]
+State Hwy 13 [(-122.1828,37.974),(-122.1799,37.948)]
+State Hwy 13 [(-122.2049,37.2),(-122.20328,37.17975),(-122.1989,37.125),(-122.198078,37.11641),(-122.1975,37.11)]
+State Hwy 13 [(-122.2213,37.388),(-122.218753,37.36402),(-122.2168,37.336),(-122.2163,37.328),(-122.2144,37.313),(-122.211744,37.28221),(-122.21,37.262),(-122.2087,37.244),(-122.207,37.224),(-122.2065,37.218),(-122.2058,37.209)]
+State Hwy 13 [(-122.2319,37.515),(-122.2316,37.511),(-122.2305,37.498),(-122.2296,37.489),(-122.2286,37.478),(-122.2244,37.427)]
+State Hwy 13 Ramp [(-122.1781,37.851),(-122.1782,37.847),(-122.1777,37.845)]
+State Hwy 13 Ramp [(-122.1799,37.948),(-122.1797,37.943)]
+State Hwy 13 Ramp [(-122.1819,37.978),(-122.1828,37.974)]
+State Hwy 13 Ramp [(-122.1835,37.98),(-122.1852,37.985)]
+State Hwy 13 Ramp [(-122.1854,37.996),(-122.1861,37.986)]
+State Hwy 13 Ramp [(-122.1937,37.078),(-122.1943,37.08)]
+State Hwy 13 Ramp [(-122.1942,37.084),(-122.1964,37.107)]
+State Hwy 13 Ramp [(-122.1975,37.11),(-122.1964,37.107)]
+State Hwy 13 Ramp [(-122.1989,37.125),(-122.1984,37.117),(-122.1981,37.112)]
+State Hwy 13 Ramp [(-122.2054,37.205),(-122.2048,37.209)]
+State Hwy 13 Ramp [(-122.2055,37.197),(-122.2049,37.2)]
+State Hwy 13 Ramp [(-122.2058,37.209),(-122.2049,37.211)]
+State Hwy 13 Ramp [(-122.2069,37.213),(-122.2072,37.216),(-122.2065,37.218)]
+State Hwy 13 Ramp [(-122.2073,37.221),(-122.207,37.224)]
+State Hwy 13 Ramp [(-122.2163,37.328),(-122.2149,37.321),(-122.2144,37.313)]
+State Hwy 13 Ramp [(-122.2168,37.336),(-122.2168,37.328)]
+State Hwy 13 Ramp [(-122.2232,37.41),(-122.2247,37.422)]
+State Hwy 13 Ramp [(-122.2244,37.427),(-122.223,37.414),(-122.2214,37.396),(-122.2213,37.388)]
+State Hwy 13 Ramp [(-122.2244,37.427),(-122.2247,37.422)]
+State Hwy 13 Ramp [(-122.2307,37.487),(-122.2286,37.478)]
+State Hwy 17 [(-122.3107,37.976),(-122.3078,37.93),(-122.3072,37.916),(-122.307,37.902)]
+State Hwy 17 Ramp ((-122.2877,37.234),(-122.2881,37.237))
+State Hwy 17 Ramp [(-122.2882,37.233),(-122.2881,37.237),(-122.2882,37.242),(-122.2883,37.247),(-122.2885,37.254)]
+State Hwy 17 Ramp [(-122.2899,37.128),(-122.2898,37.132),(-122.2897,37.142)]
+State Hwy 17 Ramp [(-122.291,37.113),(-122.2906,37.118)]
+State Hwy 17 Ramp [(-122.2915,37.083),(-122.29137,37.07065),(-122.290561,37.06224)]
+State Hwy 238 ((-122.098,37.908),(-122.0983,37.907),(-122.099,37.905),(-122.101,37.898),(-122.101535,37.89711),(-122.103173,37.89438),(-122.1046,37.892),(-122.106,37.89))
+State Hwy 238 [(-122.106,37.89),(-122.1061,37.89),(-122.1064,37.89),(-122.1073,37.889)]
+State Hwy 238 Ramp ((-122.1064,37.89),(-122.1067,37.899))
+State Hwy 238 Ramp ((-122.1288,37.892),(-122.1293,37.895))
+State Hwy 238 Ramp ((-122.1288,37.913),(-122.1294,37.917))
+State Hwy 238 Ramp [(-122.1073,37.889),(-122.1067,37.899),(-122.1056,37.899)]
+State Hwy 238 Ramp [(-122.1073,37.889),(-122.1068,37.881),(-122.106574,37.88354),(-122.106,37.89)]
+State Hwy 238 Ramp [(-122.1288,37.9),(-122.1293,37.895),(-122.1296,37.906)]
+State Hwy 238 Ramp [(-122.1288,37.922),(-122.1294,37.917),(-122.1296,37.906)]
+State Hwy 24 [(-122.2206,37.531),(-122.2204,37.536),(-122.2194,37.541),(-122.217,37.546),(-122.2151,37.551),(-122.2139,37.561),(-122.2121,37.577)]
+State Hwy 24 [(-122.2674,37.246),(-122.2673,37.248),(-122.267,37.261),(-122.2668,37.271),(-122.2663,37.298),(-122.2659,37.315),(-122.2655,37.336),(-122.265007,37.35882),(-122.264443,37.37286),(-122.2641,37.381),(-122.2638,37.388),(-122.2631,37.396),(-122.2617,37.405),(-122.2615,37.407),(-122.2605,37.412)]
+State Hwy 24 [(-122.2681,37.244),(-122.2679,37.248),(-122.2677,37.252)]
+State Hwy 24 Ramp ((-122.2191,37.537),(-122.2197,37.535))
+State Hwy 24 Ramp ((-122.2204,37.536),(-122.2211,37.533))
+State Hwy 24 Ramp [(-122.2194,37.541),(-122.2197,37.535),(-122.2197,37.531)]
+State Hwy 24 Ramp [(-122.2211,37.542),(-122.2211,37.533),(-122.2209,37.526)]
+State Hwy 24 Ramp [(-122.2218,37.517),(-122.2214,37.507)]
+State Hwy 24 Ramp [(-122.2224,37.497),(-122.2219,37.494),(-122.2215,37.492)]
+State Hwy 24 Ramp [(-122.2279,37.486),(-122.2284,37.485)]
+State Hwy 24 Ramp [(-122.2279,37.486),(-122.2305,37.498)]
+State Hwy 24 Ramp [(-122.2286,37.478),(-122.2284,37.485)]
+State Hwy 24 Ramp [(-122.2551,37.435),(-122.2568,37.436),(-122.2585,37.428),(-122.259722,37.42438),(-122.260592,37.4218),(-122.2612,37.42)]
+State Hwy 24 Ramp [(-122.257,37.433),(-122.258,37.427)]
+State Hwy 24 Ramp [(-122.2605,37.412),(-122.2601,37.412),(-122.2599,37.412),(-122.2586,37.413)]
+State Hwy 24 Ramp [(-122.2623,37.409),(-122.2614,37.411),(-122.260282,37.41626),(-122.258,37.427)]
+State Hwy 24 Ramp [(-122.2657,37.351),(-122.2665,37.36)]
+State Hwy 24 Ramp [(-122.266,37.348),(-122.2661,37.35),(-122.2668,37.35)]
+State Hwy 24 Ramp [(-122.2677,37.252),(-122.2674,37.258),(-122.2671,37.263),(-122.2672,37.267),(-122.2673,37.272)]
+State Hwy 84 [(-121.7673,37.82),(-121.7664,37.828),(-121.7654,37.835),(-121.765,37.837)]
+State Hwy 84 [(-121.9565,37.898),(-121.956589,37.89911),(-121.9569,37.903),(-121.956,37.91),(-121.9553,37.919)]
+State Hwy 84 [(-121.9725,37.749),(-121.972151,37.75144),(-121.9715,37.756),(-121.9706,37.762),(-121.9692,37.778),(-121.9673,37.793),(-121.9637,37.813),(-121.9637,37.854),(-121.9576,37.891)]
+State Hwy 84 [(-122.0484,37.539),(-122.0443,37.564),(-122.0423,37.577)]
+State Hwy 84 [(-122.0484,37.539),(-122.0514,37.52),(-122.051557,37.51906),(-122.0544,37.502)]
+State Hwy 84 [(-122.0671,37.426),(-122.0658,37.432)]
+State Hwy 84 [(-122.0671,37.426),(-122.07,37.402),(-122.074,37.37),(-122.0773,37.338)]
+State Hwy 84 Ramp [(-122.0544,37.502),(-122.052622,37.50902),(-122.0506,37.517),(-122.0484,37.539)]
+State Hwy 92 [(-122.1085,37.326),(-122.1095,37.322),(-122.1111,37.316),(-122.1119,37.313),(-122.1125,37.311),(-122.1131,37.308),(-122.1167,37.292),(-122.1187,37.285),(-122.12,37.28)]
+State Hwy 92 Ramp ((-122.1091,37.328),(-122.1101,37.332))
+State Hwy 92 Ramp [(-122.099,37.368),(-122.0994,37.363)]
+State Hwy 92 Ramp [(-122.1086,37.321),(-122.1089,37.315),(-122.1111,37.316)]
+State Hwy 92 Ramp [(-122.1091,37.328),(-122.1101,37.332),(-122.1101,37.327),(-122.1095,37.322)]
+State Hwy 92 Ramp [(-122.1167,37.292),(-122.1185,37.295),(-122.1191,37.298)]
+State Hwy 92 Ramp [(-122.1187,37.285),(-122.1186,37.292),(-122.1193,37.29)]
+State Hwy 92 Ramp [(-122.12,37.28),(-122.1207,37.269),(-122.1204,37.27)]
+State Hwy 92 Ramp [(-122.1204,37.267),(-122.123,37.271)]
+Staten Ave [(-122.2533,37.103),(-122.2534,37.094)]
+Stearns Ave [(-122.1564,37.533),(-122.1533,37.512)]
+Steele St [(-122.188,37.892),(-122.1874,37.886)]
+Steinmetz Way [(-122.1979,37.061),(-122.1976,37.051)]
+Stella St [(-122.14272,37.4824),(-122.1418,37.469)]
+Stenhammer Dr [(-121.9612,37.84),(-121.9607,37.826)]
+Sterne Pl [(-122.0318,37.838),(-122.0327,37.835)]
+Steuben Ct [(-121.9547,37.227),(-121.9553,37.225)]
+Stevens St [(-122.0718,37.999),(-122.0707,37.999)]
+Stevenson Blvd [(-121.9758,37.367),(-121.9782,37.334)]
+Stevenson Blvd [(-121.982964,37.27715),(-121.983567,37.27017)]
+Stewart Ave [(-121.9797,37.174),(-121.9821,37.149)]
+Stoakes Ave [(-122.161722,37.31574),(-122.16195,37.31467)]
+Stonedale Dr [(-121.9171,37.877),(-121.9173,37.882)]
+Stonehenge Road [(-122.037162,37.84679),(-122.0377,37.841)]
+Stoneridge Dr [(-121.894,37.919),(-121.8902,37.925)]
+Stoneridge Dr [(-121.9082,37.905),(-121.9089,37.904)]
+Stoneridge Mall Road [(-121.9274,37.926),(-121.925,37.925)]
+Stoneridge Mall Road [(-121.9287,37.963),(-121.9283,37.941)]
+Stonewall Ave [(-122.1076,37.568),(-122.1067,37.554)]
+Stony Brook [(-121.9429,37.244),(-121.9432,37.21)]
+Storer Ave [(-122.1944,37.852),(-122.1934,37.854)]
+Stow Ave [(-122.2508,37.042),(-122.2491,37.043)]
+Strang Ave [(-122.1087,37.034),(-122.1076,37.037)]
+Stratford Ave [(-121.9696,37.271),(-121.9732,37.254)]
+Stratton Common [(-121.983399,37.43226),(-121.983217,37.43097)]
+Stream [(-121.574573,37.54948),(-121.574039,37.57213)]
+Stream [(-121.648853,37.05723),(-121.651539,37.14924)]
+Strobridge Ave [(-122.0884,37.911),(-122.0882,37.908)]
+Strong Way [(-122.102446,37.04885),(-122.101424,37.05337)]
+Stuart St [(-122.2518,37.6),(-122.2507,37.601),(-122.2491,37.606)]
+Stuart St [(-122.2554,37.601),(-122.2541,37.602)]
+Suddard Ct [(-121.9057,37.823),(-121.9064,37.822)]
+Sueirro St [(-122.1113,37.628),(-122.1121,37.627)]
+Suffolk Way [(-121.8699,37.932),(-121.8732,37.92)]
+Sulphur Dr [(-122.0517,37.719),(-122.0506,37.715)]
+Sulphur Creek [(-122.058701,37.76216),(-122.0609,37.772)]
+Sulphur Creek [(-122.0655,37.766),(-122.0659,37.764)]
+Sumatra St [(-122.0743,37.277),(-122.0751,37.276)]
+Summit Road [(-122.2464,37.816),(-122.2447,37.82)]
+Summit Road [(-122.2479,37.874),(-122.2478,37.87)]
+Sun Valley Dr [(-122.1174,37.493),(-122.1173,37.488)]
+Sun Valley Dr [(-122.1191,37.47),(-122.1201,37.465)]
+Sundale Dr [(-121.976,37.481),(-121.9776,37.464)]
+Sundale Dr [(-121.982028,37.41595),(-121.98231,37.41254)]
+Sundale Dr [(-121.9836,37.315),(-121.9809,37.301)]
+Sundale Dr [(-121.9868,37.342),(-121.9867,37.338)]
+Sundance Dr [(-121.9113,37.988),(-121.9097,37.992)]
+Sundberg Ave [(-122.1667,37.119),(-122.1659,37.116)]
+Sunnybank Pl [(-122.051879,37.78503),(-122.052,37.782)]
+Sunnydale Ct [(-122.0429,37.203),(-122.0429,37.2)]
+Sunnyhills Road [(-122.2257,37.111),(-122.2246,37.114)]
+Sunnymere Ave [(-122.1681,37.776),(-122.1672,37.77)]
+Sunnymere Ave [(-122.1707,37.791),(-122.1699,37.786)]
+Sunnymere Ave [(-122.1729,37.803),(-122.1718,37.795)]
+Sunnyside Ave [(-122.2469,37.232),(-122.2453,37.221)]
+Sunnyside St [(-122.1564,37.386),(-122.1559,37.38)]
+Sunnyside St [(-122.1611,37.462),(-122.1604,37.453)]
+Sunol Blvd [(-121.8805,37.447),(-121.8807,37.422)]
+Sunol Road [(-122.1254,37.671),(-122.1254,37.666)]
+Sunol Ridge Trl [(-121.9419,37.455),(-121.9345,37.38)]
+Sunrise Dr [(-121.7347,37.066),(-121.7343,37.069)]
+Sunset Ave [(-122.2176,37.901),(-122.2173,37.908)]
+Sunset Blvd [(-122.0899,37.779),(-122.0888,37.788)]
+Sunset Blvd [(-122.0932,37.755),(-122.0921,37.761)]
+Sunset Blvd [(-122.0944,37.75),(-122.0941,37.751)]
+Sunset Trl [(-122.2375,37.574),(-122.2372,37.571)]
+Sunshine Pl [(-122.097403,37.11209),(-122.096541,37.11436)]
+Sunstream Lane [(-121.7345,37.059),(-121.7347,37.066)]
+Sunwood Dr [(-121.9332,37.113),(-121.9335,37.132)]
+Superior Ave [(-122.142,37.324),(-122.1422,37.315)]
+Superior Dr [(-121.7724,37.543),(-121.7727,37.543)]
+Surry Pl [(-122.0052,37.685),(-122.006,37.679)]
+Sussex Way [(-122.0657,37.227),(-122.0669,37.222),(-122.0673,37.219)]
+Suter St [(-122.2009,37.89),(-122.2007,37.888)]
+Suter St [(-122.2072,37.94),(-122.2061,37.931)]
+Sutter Dr [(-121.9893,37.359),(-121.987,37.35)]
+Sutter Dr [(-121.9951,37.377),(-121.9943,37.373)]
+Sutter Gate Ave [(-121.8838,37.866),(-121.8845,37.864)]
+Sutton Loop [(-121.9994,37.586),(-121.9981,37.576)]
+Swan Dr [(-121.7996,37.845),(-121.7991,37.834)]
+Sybil Ave [(-122.1443,37.213),(-122.1435,37.215)]
+Sycamore Ave [(-122.0759,37.633),(-122.0752,37.636)]
+Sycamore Ave [(-122.0826,37.609),(-122.0818,37.606)]
+Sycamore St [(-122.0361,37.294),(-122.0354,37.291)]
+Sycamore St [(-122.2715,37.16),(-122.2739,37.163)]
+Sydney Ave [(-121.7678,37.636),(-121.7669,37.636)]
+Sydney Cir [(-122.098211,37.07652),(-122.09723,37.06547)]
+Sylvan Glen [(-122.065238,37.685),(-122.065615,37.67879)]
+Sylvaner Dr [(-121.8475,37.61),(-121.8482,37.607)]
+Sylvaner Way [(-121.907301,37.67596),(-121.908111,37.67419)]
+Sylvester Dr [(-122.041,37.815),(-122.0405,37.812)]
+Sylvia Cir [(-121.860125,37.6435),(-121.8603,37.64325)]
+Tacchella Way [(-121.994785,37.62496),(-121.994311,37.61417)]
+Tahiti Lane [(-122.2411,37.343),(-122.240607,37.3499),(-122.2401,37.357)]
+Talbot Ave [(-122.2925,37.847),(-122.2925,37.843)]
+Talbot Ave [(-122.2967,37.989),(-122.2964,37.975),(-122.2959,37.959)]
+Tallahassee St [(-122.0989,37.352),(-122.1012,37.345)]
+Tamalpais Path [(-122.2596,37.866),(-122.2599,37.872)]
+Tamalpais Road [(-122.2593,37.836),(-122.2588,37.842)]
+Tamarack Dr [(-121.9207,37.159),(-121.922,37.155)]
+Tamarack Dr [(-121.9255,37.155),(-121.9266,37.156)]
+Tamarack Dr [(-121.9304,37.16),(-121.9313,37.163)]
+Tamarack Dr [(-121.932412,37.17516),(-121.9327,37.179)]
+Tamayo St [(-122.0184,37.732),(-122.019,37.729)]
+Tamayo St [(-122.0201,37.723),(-122.0204,37.72)]
+Tampa Ave [(-122.0747,37.327),(-122.0747,37.314),(-122.0746,37.308)]
+Tanager Dr [(-121.8775,37.831),(-121.8778,37.838)]
+Tanglewood Path [(-122.2431,37.628),(-122.2427,37.626)]
+Tarraville Creek [(-121.52544,37.95569),(-121.525089,37.948)]
+Tarraville Creek [(-121.528505,37.9837),(-121.528147,37.97997)]
+Tarraville Creek [(-121.53429,37.98943),(-121.532627,37.98722)]
+Tarraville Creek [(-121.536091,37.99721),(-121.536763,37)]
+Tarraville Creek Road [(-121.52582,37.96459),(-121.525715,37.96296)]
+Tarraville Creek Road [(-121.530642,37.9828),(-121.530608,37.98278)]
+Tartarian St [(-122.1774,37.35),(-122.1768,37.352)]
+Tassajara Road [(-121.8709,37.099),(-121.8713,37.048)]
+Tassajara Creek [(-121.87866,37.98898),(-121.8782,37.015)]
+Tassajara Creek [(-121.8862,37.901),(-121.8847,37.924)]
+Taurus Ave [(-122.2159,37.416),(-122.2128,37.389)]
+Taylor Ave [(-122.0547,37.245),(-122.0541,37.241),(-122.0535,37.237)]
+Taylor Ave [(-122.280873,37.72975),(-122.280388,37.72958)]
+Teakwood St [(-122.1109,37.58),(-122.1104,37.574)]
+Technology Dr [(-121.9539,37.109),(-121.9536,37.099)]
+Telegraph Ave [(-122.2581,37.635),(-122.2583,37.625)]
+Telegraph Ave [(-122.2585,37.616),(-122.2586,37.607)]
+Telegraph Ave [(-122.2589,37.588),(-122.2591,37.578)]
+Telegraph Ave [(-122.2594,37.547),(-122.2596,37.541)]
+Telegraph Ave [(-122.2614,37.411),(-122.2613,37.414)]
+Telegraph Ave [(-122.2618,37.384),(-122.261742,37.38806)]
+Telegraph Ave [(-122.2625,37.353),(-122.2623,37.361)]
+Telegraph Ave [(-122.2647,37.266),(-122.2647,37.274)]
+Telegraph Ave [(-122.2679,37.147),(-122.2678,37.149)]
+Telegraph Ave [(-122.2688,37.109),(-122.2686,37.114)]
+Telegraph Ave [(-122.2693,37.089),(-122.2691,37.096)]
+Telegraph Ave [(-122.2699,37.067),(-122.2697,37.074)]
+Temescal Cir [(-122.282639,37.36326),(-122.28206,37.3665)]
+Temescal Creek [(-122.284945,37.36017),(-122.2855,37.359)]
+Temescal Creek [(-122.2922,37.344),(-122.2937,37.341)]
+Tennyson Road [(-122.056941,37.35835),(-122.0562,37.36)]
+Tennyson Road [(-122.0605,37.352),(-122.0602,37.354)]
+Tennyson Road [(-122.062,37.345),(-122.0625,37.343)]
+Tennyson Road [(-122.0682,37.318),(-122.0685,37.317)]
+Tennyson Road [(-122.0891,37.317),(-122.0927,37.317)]
+Tennyson Road [(-122.1035,37.272),(-122.1041,37.268)]
+Terrace St [(-122.2539,37.299),(-122.2523,37.321)]
+Terrace Walk [(-122.2719,37.89),(-122.2705,37.884)]
+Tesla Road [(-121.570042,37.38752),(-121.573978,37.37661)]
+Tesla Road [(-121.588398,37.4085),(-121.596897,37.4397)]
+Tesla Road [(-121.613819,37.51806),(-121.62334,37.46389)]
+Tesla Road [(-121.646458,37.4545),(-121.646946,37.45847)]
+Tesla Road [(-121.6705,37.607),(-121.667684,37.55608)]
+Tesla Road [(-121.6774,37.632),(-121.675,37.633)]
+Tevis St [(-122.2011,37.621),(-122.2007,37.616)]
+Tevlin St [(-122.2866,37.83),(-122.2871,37.819)]
+Texas St [(-122.2087,37.942),(-122.2076,37.937)]
+Thackeray Ave [(-122.072,37.305),(-122.0715,37.298)]
+The Cir [(-122.2721,37.904),(-122.2718,37.905)]
+The Cres [(-122.2534,37.945),(-122.254,37.941)]
+The Alameda [(-122.2749,37.863),(-122.2744,37.855)]
+The Alameda [(-122.276,37.89),(-122.2759,37.882)]
+The Alameda [(-122.2765,37.916),(-122.2762,37.902)]
+The Alameda [(-122.2805,37.996),(-122.2808,37.988)]
+The South Crossways [(-122.2442,37.529),(-122.2443,37.522)]
+The Uplands [(-122.2477,37.545),(-122.246,37.543),(-122.245,37.538)]
+Theresa Way [(-121.7273,37.877),(-121.7272,37.868)]
+Theresa Way [(-121.7289,37.906),(-121.728,37.899)]
+Theta St [(-122.0181,37.565),(-122.018394,37.56324)]
+Theta St [(-122.0197,37.553),(-122.02,37.55)]
+Thomas Ave [(-122.2487,37.375),(-122.2479,37.39)]
+Thornhill Dr [(-122.204,37.39),(-122.201,37.389)]
+Thornhill Dr [(-122.2131,37.335),(-122.2112,37.35)]
+Thornton Ave [(-122.0036,37.671),(-122.0048,37.66)]
+Thornton Ave [(-122.0068,37.644),(-122.0083,37.63)]
+Thornton Ave [(-122.0127,37.583),(-122.0132,37.575)]
+Thornton Ave [(-122.0164,37.537),(-122.0175,37.523)]
+Thornton Ave [(-122.0211,37.477),(-122.0214,37.473)]
+Thornton Ave [(-122.0291,37.379),(-122.03,37.37)]
+Thornton Ave [(-122.0636,37.335),(-122.0626,37.303)]
+Thornton St [(-122.1505,37.215),(-122.1526,37.206)]
+Thornton St [(-122.1572,37.189),(-122.1576,37.188)]
+Thousand Oaks Blvd [(-122.2799,37.975),(-122.2779,37.972)]
+Thousand Oaks Blvd [(-122.283,37.972),(-122.2824,37.974)]
+Tidal Canal [(-122.2302,37.697),(-122.229,37.694)]
+Tidal Canal [(-122.2428,37.791),(-122.2386,37.745)]
+Tidewater Ave [(-122.2193,37.625),(-122.2174,37.609)]
+Tideway Dr [(-122.2877,37.692),(-122.2854,37.704)]
+Tiffin Road [(-122.2086,37.069),(-122.2076,37.07)]
+Tilgrim Way [(-122.0831,37.211),(-122.084,37.211)]
+Timber St [(-122.0175,37.38),(-122.0139,37.362)]
+Timbercreek Ter [(-121.9527,37.351),(-121.952496,37.35039)]
+Timpanogas Cir [(-121.963,37.752),(-121.962723,37.74924)]
+Tina Way [(-122.0322,37.185),(-122.0305,37.196)]
+Tinder Ct [(-122.0496,37.109),(-122.0488,37.12)]
+Tioga Ct [(-121.847804,37.6667),(-121.848197,37.66978)]
+Tissiack Way [(-121.920364,37),(-121.9208,37.995)]
+Toler Ave [(-122.1575,37.269),(-122.1593,37.261)]
+Tonopah Ct [(-121.914,37.684),(-121.9138,37.682)]
+Topawa Dr [(-121.9204,37.878),(-121.9197,37.88)]
+Toroges Creek [(-121.9217,37.808),(-121.922457,37.80485)]
+Toroges Creek [(-121.925,37.795),(-121.9268,37.789)]
+Torrano Ave [(-122.0685,37.547),(-122.0679,37.551)]
+Tory Way [(-121.9162,37.139),(-121.917,37.132)]
+Totterdell St [(-122.187,37.228),(-122.1864,37.224)]
+Touriga Dr [(-121.8512,37.611),(-121.851283,37.60893)]
+Touriga Dr [(-121.8512,37.644),(-121.8511,37.648)]
+Touriga Dr [(-121.8521,37.566),(-121.8523,37.559)]
+Towers St [(-122.1296,37.096),(-122.1286,37.089)]
+Toyon Pl [(-122.0469,37.951),(-122.0464,37.953)]
+Tozier St [(-122.0502,37.394),(-122.0497,37.39)]
+Trade Wind Lane [(-121.9887,37.294),(-121.9891,37.288)]
+Trafalgar Ave [(-122.1036,37.369),(-122.1043,37.375)]
+Treeview St [(-122.0379,37.267),(-122.0375,37.262)]
+Treeview St [(-122.039,37.282),(-122.0387,37.278)]
+Tremont St [(-122.267,37.503),(-122.2672,37.514)]
+Trenery Dr [(-121.866449,37.88045),(-121.866515,37.88044)]
+Trenton Dr [(-122.0655,37.155),(-122.0647,37.142)]
+Trestle Glen Road [(-122.221239,37.12059),(-122.2207,37.125),(-122.2201,37.133)]
+Trimingham Dr [(-121.871338,37.76396),(-121.872239,37.76736)]
+Trojan Ave [(-122.144,37.868),(-122.1446,37.867)]
+Trojan Ave [(-122.1467,37.864),(-122.1483,37.862)]
+Trojan Ave [(-122.1514,37.858),(-122.1522,37.857)]
+Trombas Ave [(-122.1428,37.217),(-122.1425,37.211)]
+Tropicana Way [(-122.0492,37.892),(-122.050057,37.88256),(-122.0513,37.874)]
+Truman Pl [(-121.9933,37.285),(-121.9938,37.279)]
+Tudor Road [(-122.1808,37.216),(-122.1801,37.216)]
+Tulare Ave [(-122.2811,37.889),(-122.2816,37.868)]
+Tule Lake Lane [(-122.0568,37.871),(-122.0559,37.866)]
+Tulip Ave [(-122.1901,37.881),(-122.1898,37.877)]
+Tumbleweed Ct [(-122.0715,37.906),(-122.0718,37.908)]
+Tunnel Road [(-122.2382,37.555),(-122.2374,37.553)]
+Tunnel Road [(-122.2405,37.574),(-122.2402,37.57)]
+Tunnel Road [(-122.2427,37.581),(-122.2414,37.579)]
+Tunnel Creek [(-121.610752,37.58239),(-121.624698,37.83019)]
+Tupelo Ter [(-122.059087,37.6113),(-122.057021,37.59942)]
+Tupelo Ter [(-122.061851,37.62675),(-122.0606,37.62)]
+Turban Ct [(-121.9737,37.141),(-121.9729,37.143)]
+Turnstone Ct [(-121.7967,37.812),(-121.7967,37.814)]
+Turquoise St [(-121.918671,37.20347),(-121.918562,37.20173)]
+Twain Ave [(-122.2574,37.904),(-122.2563,37.907)]
+Twin Peaks Ter [(-121.9785,37.427),(-121.9783,37.431)]
+Tyee Ct [(-122.084,37.918),(-122.084,37.913)]
+Tyler Lane [(-122.0674,37.157),(-122.066,37.146)]
+Tyler Pl [(-121.9973,37.293),(-121.9986,37.289)]
+Tyler St [(-122.2782,37.525),(-122.278041,37.52523)]
+Tyrrell Ave [(-122.0709,37.334),(-122.0708,37.329)]
+Tyrrell Ave [(-122.0751,37.441),(-122.0759,37.428)]
+Ulmeca Pl [(-121.9129,37.786),(-121.9125,37.779)]
+Underwood Ave [(-122.0823,37.477),(-122.0822,37.469)]
+Underwood Ave [(-122.1852,37.828),(-122.1846,37.809)]
+Union St [(-121.9578,37.334),(-121.9584,37.33)]
+Union St [(-122.2527,37.731),(-122.2521,37.739)]
+Union St [(-122.2555,37.689),(-122.2549,37.698)]
+Union St [(-122.2579,37.654),(-122.2573,37.663)]
+Union St [(-122.2839,37.21),(-122.2833,37.227)]
+Union St [(-122.2854,37.164),(-122.2852,37.178),(-122.2844,37.195)]
+Union St [(-122.2862,37.14),(-122.2858,37.152)]
+Union St [(-122.2881,37.092),(-122.2876,37.107)]
+Union St [(-122.2898,37.042),(-122.2897,37.044)]
+Union City Blvd [(-122.0674,37.657),(-122.0669,37.654)]
+Union City Blvd [(-122.078284,37.74531),(-122.078001,37.74022)]
+Union City Blvd [(-122.0791,37.76),(-122.0786,37.751)]
+Union City Blvd [(-122.0795,37.923),(-122.0797,37.907)]
+Union City Blvd [(-122.0797,37.883),(-122.0797,37.878)]
+Union City Blvd [(-122.0797,37.964),(-122.0797,37.959)]
+Union City Blvd [(-122.0805,37.986),(-122.0804,37.972)]
+University Ave [(-122.2711,37.718),(-122.27,37.719)]
+University Dr [(-122.0275,37.971),(-122.0264,37.97)]
+Upton Ave [(-122.1298,37.99),(-122.1297,37.984)]
+Urban St [(-121.9714,37.412),(-121.971,37.41)]
+Usher St [(-122.1276,37.885),(-122.1275,37.865)]
+Utah St [(-121.8591,37.68),(-121.8584,37.682)]
+Vaca Dr [(-121.9531,37.448),(-121.9531,37.442)]
+Val St [(-121.9739,37.325),(-121.9746,37.324)]
+Valita Dr [(-122.1417,37.192),(-122.141,37.195)]
+Valle Vista Ave [(-122.246,37.162),(-122.2461,37.168)]
+Vallecitos Road [(-121.7875,37.454),(-121.7856,37.463),(-121.7822,37.482)]
+Vallecitos Road [(-121.8177,37.142),(-121.836,37.053)]
+Vallecitos Road [(-121.8631,37.951),(-121.868,37.93)]
+Vallecitos Road [(-121.8699,37.916),(-121.8703,37.891)]
+Vallecitos Creek [(-121.8423,37.033),(-121.8379,37.094)]
+Vallejo St [(-121.9718,37.765),(-121.9719,37.769)]
+Vallejo St [(-122.2869,37.46),(-122.2872,37.466)]
+Valley Ave [(-121.8591,37.733),(-121.8628,37.76)]
+Valley Ave [(-121.8673,37.761),(-121.8677,37.76)]
+Valley Ave [(-121.887,37.767),(-121.8878,37.767),(-121.8888,37.767)]
+Valley Ave [(-121.8962,37.644),(-121.89632,37.6086),(-121.896359,37.59716)]
+Valley Dr [(-121.863815,37.76063),(-121.8644,37.761)]
+Valley Dr [(-121.8967,37.761),(-121.8982,37.746)]
+Valley Dr [(-121.897701,37.6866),(-121.8972,37.677),(-121.89692,37.66207)]
+Valley St [(-122.17,37.16),(-122.1707,37.158)]
+Valley Brook Ct [(-122.0479,37.871),(-122.0478,37.867)]
+Valley Trails Dr [(-121.9038,37.75),(-121.9029,37.754)]
+Valley Trails Dr [(-121.9066,37.776),(-121.9075,37.775),(-121.9084,37.774)]
+Valley Trails Dr [(-121.908,37.739),(-121.9075,37.739)]
+Valley Trails Dr [(-121.9098,37.756),(-121.91,37.753)]
+Valpey Park Ave [(-121.9748,37.188),(-121.9756,37.186)]
+Van Ave [(-122.1212,37.142),(-122.1206,37.128)]
+Van Buren Ave [(-122.2521,37.111),(-122.2512,37.113)]
+Van Mourik Ave [(-122.1718,37.795),(-122.1716,37.801)]
+Vancouver Way [(-121.7753,37.665),(-121.7742,37.666)]
+Vancouver Way [(-121.7775,37.667),(-121.7768,37.666)]
+Vanda Way [(-121.9989,37.807),(-121.9993,37.8)]
+Vanderbilt St [(-122.039,37.254),(-122.0386,37.243)]
+Vane Common [(-122.056732,37.54797),(-122.056633,37.54543)]
+Vargas Road [(-121.9161,37.512),(-121.9165,37.504)]
+Vasco Road [(-121.717311,37.99316),(-121.7174,37.971)]
+Vasco Road [(-121.7177,37.70674),(-121.7177,37.68228)]
+Vasco Road [(-121.7217,37.062),(-121.72,37.039)]
+Vasco Road [(-121.7229,37.093),(-121.7228,37.089)]
+Vasco Road [(-121.7231,37.108),(-121.7232,37.114)]
+Vasco Road [(-121.737,37.725),(-121.7282,37.489)]
+Vasquez Ct [(-122.0252,37.029),(-122.025392,37.02756)]
+Vassar Ave [(-122.1245,37.952),(-122.1246,37.945)]
+Vaughn Ave [(-121.7118,37.061),(-121.7074,37.08)]
+Vaughn Ave [(-122.0802,37.053),(-122.0801,37.026)]
+Vegas Ave [(-122.075906,37.8929),(-122.0726,37.894)]
+Ventura Ave [(-122.2832,37.864),(-122.283509,37.8555)]
+Vera Ave [(-122.1318,37.984),(-122.1317,37.977)]
+Verdemar Dr [(-122.2454,37.333),(-122.2453,37.335)]
+Vergil St [(-122.0685,37.886),(-122.0681,37.88)]
+Vermont St [(-122.2424,37.16),(-122.2415,37.168)]
+Vernetti Way [(-122.064379,37.90817),(-122.063821,37.90117)]
+Vernon Ave [(-122.0039,37.383),(-122.006,37.358)]
+Vernon St [(-122.2509,37.179),(-122.2503,37.184)]
+Verona Ave [(-121.7808,37.721),(-121.7801,37.721)]
+Versailles Ave [(-122.2383,37.577),(-122.2374,37.589)]
+Via Alamitos [(-122.1302,37.675),(-122.13,37.668)]
+Via Alamitos [(-122.1302,37.704),(-122.1303,37.697)]
+Via Amigos [(-122.1406,37.731),(-122.1424,37.722)]
+Via Annette [(-122.1389,37.65),(-122.1388,37.631)]
+Via Arriba [(-122.122,37.712),(-122.1216,37.702)]
+Via Arroyo [(-122.1256,37.835),(-122.1236,37.823)]
+Via Barrett [(-122.1495,37.736),(-122.1503,37.733)]
+Via Bellita [(-122.121893,37.82393),(-122.12257,37.81959)]
+Via Buena Vista [(-122.1366,37.652),(-122.1374,37.651)]
+Via Carmen [(-122.1401,37.674),(-122.1397,37.65)]
+Via Chiquita [(-122.1321,37.693),(-122.1319,37.681)]
+Via Chiquita [(-122.1322,37.708),(-122.1321,37.702)]
+Via Chiquita [(-122.1337,37.731),(-122.1333,37.724)]
+Via Cordoba [(-122.1246,37.853),(-122.1239,37.853)]
+Via de Los Cerros [(-121.901117,37.7212),(-121.900094,37.71647)]
+Via de Los Milagros [(-121.8982,37.746),(-121.8975,37.743)]
+Via del Prado [(-122.135,37.797),(-122.1339,37.78)]
+Via del Sol [(-121.7899,37.624),(-121.7901,37.615)]
+Via Diego [(-122.118617,37.81903),(-122.1194,37.814)]
+Via el Cerrito [(-122.1337,37.691),(-122.1336,37.676)]
+Via Enrico [(-122.1379,37.82),(-122.1389,37.818)]
+Via Enrico [(-122.139846,37.81379),(-122.1407,37.81)]
+Via Escondido [(-122.1409,37.747),(-122.141278,37.74521)]
+Via Escondido [(-122.142774,37.73812),(-122.143583,37.73429)]
+Via Esperanza [(-122.1273,37.676),(-122.1302,37.675)]
+Via Frances [(-122.1387,37.68),(-122.1386,37.666)]
+Via Granada [(-122.121893,37.82393),(-122.1217,37.822)]
+Via Harriet [(-122.1474,37.709),(-122.1473,37.691),(-122.1456,37.674)]
+Via Hermana [(-122.1433,37.791),(-122.1442,37.786)]
+Via la Jolla [(-122.1346,37.699),(-122.1345,37.691)]
+Via Lacqua [(-122.1463,37.734),(-122.1471,37.73)]
+Via Lucas [(-122.1381,37.71),(-122.1396,37.71)]
+Via Manzanas [(-122.1248,37.761),(-122.1265,37.753)]
+Via Matero [(-122.116,37.806),(-122.1175,37.797)]
+Via Montalvo [(-121.7867,37.962),(-121.7861,37.967)]
+Via Natal [(-122.1434,37.684),(-122.1441,37.68)]
+Via Natal [(-122.1449,37.677),(-122.1456,37.674)]
+Via Nueva [(-122.1461,37.743),(-122.1456,37.738)]
+Via Paro [(-122.129,37.78),(-122.1276,37.757)]
+Via Peralta [(-121.8941,37.729),(-121.8938,37.726)]
+Via Perdido [(-122.1295,37.741),(-122.1281,37.727)]
+Via Pinale [(-122.1333,37.793),(-122.1315,37.773)]
+Via Primero [(-122.1223,37.774),(-122.1211,37.755)]
+Via Redondo [(-122.141,37.7),(-122.1423,37.707)]
+Via Rodriguez [(-122.1234,37.809),(-122.1233,37.806)]
+Via Segundo [(-122.1207,37.778),(-122.1189,37.765)]
+Via Tovita [(-122.1336,37.64),(-122.1356,37.638)]
+Via Vega [(-122.1388,37.837),(-122.1389,37.818)]
+Via Verde [(-122.1165,37.788),(-122.1175,37.782)]
+Via Vista [(-122.1364,37.745),(-122.1391,37.735)]
+Via Zapata [(-121.9434,37.147),(-121.9441,37.161)]
+Vicente Pl [(-122.2329,37.572),(-122.232,37.578)]
+Vicente St [(-122.2598,37.427),(-122.2598,37.432)]
+Victor Ave [(-122.1914,37.972),(-122.1901,37.964)]
+Victor Ave [(-122.1922,37.987),(-122.1918,37.981)]
+Victoria Ave [(-122.0739,37.905),(-122.0759,37.903)]
+Victoria Lane [(-121.663807,37.54316),(-121.663944,37.56139)]
+Victoria Bay [(-122.2363,37.399),(-122.2365,37.393)]
+Victory Dr [(-122.1091,37.635),(-122.109281,37.6426)]
+View Dr [(-122.1364,37.276),(-122.1295,37.271)]
+View Crest Ct [(-122.165164,37.82551),(-122.164162,37.826)]
+Village Dr [(-122.0773,37.967),(-122.0766,37.967)]
+Village Pkwy [(-121.9256,37.098),(-121.9267,37.113)]
+Villareal Dr [(-122.019699,37.15004),(-122.018295,37.15912)]
+Villareal Dr [(-122.0204,37.147),(-122.0217,37.142)]
+Villareal Dr [(-122.022433,37.10266),(-122.023425,37.09294)]
+Vine St [(-121.8603,37.64036),(-121.8601,37.64044)]
+Vine St [(-122.2755,37.793),(-122.2743,37.794)]
+Vineyard Ave [(-121.8463,37.647),(-121.846448,37.64711)]
+Vineyard Ave [(-121.8524,37.648),(-121.8511,37.648)]
+Vineyard Ave [(-121.853754,37.648),(-121.853805,37.648)]
+Vineyard Road [(-121.919,37.056),(-121.9128,37.069)]
+Vineyard Road [(-122.0878,37.2),(-122.0877,37.191)]
+Virginia St [(-122.0748,37.47),(-122.0697,37.477)]
+Virginia St [(-122.2582,37.78),(-122.2565,37.782),(-122.2557,37.784)]
+Virginia St [(-122.2751,37.756),(-122.2739,37.759)]
+Virgo Road [(-122.2152,37.415),(-122.2146,37.435)]
+Vista Ct [(-121.755,37.84),(-121.7551,37.838)]
+Vista del Plaza Lane [(-122.0834,37.809),(-122.0829,37.804)]
+Vistamont Ave [(-122.265432,37.0356),(-122.2643,37.029)]
+Vivian St [(-122.0831,37.895),(-122.082,37.895)]
+Vomac Road [(-121.9367,37.166),(-121.937,37.173)]
+Wadsworth Ct [(-121.97,37.249),(-121.9708,37.241)]
+Wagner St [(-122.1254,37.959),(-122.1245,37.952)]
+Wagoner Dr [(-121.784,37.67),(-121.784,37.663)]
+Walker Ave [(-122.2437,37.153),(-122.2433,37.161)]
+Walker Pl [(-121.7692,37.537),(-121.7693,37.544)]
+Wall St [(-121.7904,37.776),(-121.7893,37.767)]
+Walnut Ave [(-121.9689,37.601),(-121.969899,37.59434)]
+Walnut Ave [(-121.9804,37.471),(-121.9817,37.456)]
+Walnut Ave [(-121.983083,37.44159),(-121.983361,37.43869)]
+Walnut St [(-121.769,37.877),(-121.7683,37.88)]
+Walnut St [(-121.774,37.863),(-121.7728,37.866)]
+Walnut St [(-122.0425,37.262),(-122.0417,37.246)]
+Walnut St [(-122.1913,37.734),(-122.1909,37.729)]
+Walnut St [(-122.1948,37.763),(-122.1923,37.742)]
+Walnut St [(-122.2442,37.682),(-122.2437,37.69)]
+Walnut St [(-122.2465,37.648),(-122.2459,37.658)]
+Walnut St [(-122.2489,37.613),(-122.2483,37.622)]
+Walnut St [(-122.2675,37.804),(-122.2672,37.785)]
+Walpert St [(-122.07476,37.6893),(-122.073488,37.69487)]
+Warbler Loop [(-122.0601,37.813),(-122.0597,37.793)]
+Ward St [(-122.2838,37.575),(-122.2827,37.575)]
+Ward Creek [(-122.0568,37.644),(-122.058701,37.64815)]
+Ward Creek [(-122.0717,37.679),(-122.077,37.657)]
+Ward Creek Branch [(-122.0615,37.62),(-122.069914,37.64417)]
+Warfield Ave [(-122.2403,37.16),(-122.2382,37.178)]
+Warfield Ave [(-122.2404,37.153),(-122.2402,37.157)]
+Warfield Ave [(-122.244,37.124),(-122.2435,37.13)]
+Warm Springs Blvd [(-121.9184,37.728),(-121.9168,37.703)]
+Warm Springs Blvd [(-121.9209,37.769),(-121.9198,37.751)]
+Warm Springs Blvd [(-121.9258,37.851),(-121.9247,37.833)]
+Warm Springs Blvd [(-121.933956,37),(-121.9343,37.97)]
+Warner Ave [(-122.0249,37.096),(-122.024,37.103)]
+Warner Ave [(-122.1579,37.477),(-122.1571,37.482)]
+Warren Ave [(-121.9285,37.866),(-121.9302,37.859)]
+Warren Ave [(-121.9314,37.855),(-121.933,37.849)]
+Warsaw Ave [(-121.7714,37.623),(-121.77064,37.6268)]
+Warwick Pl [(-122.0498,37.647),(-122.0487,37.634)]
+Warwick Road [(-122.0279,37.791),(-122.0282,37.787)]
+Warwick Road [(-122.029,37.777),(-122.0298,37.768)]
+Wasatch Dr [(-121.962,37.752),(-121.9623,37.756)]
+Washburn Dr [(-121.9877,37.739),(-121.9881,37.728)]
+Washington Ave [(-122.1378,37.897),(-122.1379,37.891)]
+Washington Ave [(-122.1379,37.959),(-122.1379,37.954)]
+Washington Ave [(-122.1491,37.154),(-122.149,37.151),(-122.1485,37.144)]
+Washington Ave [(-122.1536,37.222),(-122.1534,37.217)]
+Washington Ave [(-122.2912,37.932),(-122.2903,37.931)]
+Washington Ave [(-122.301,37.919),(-122.3001,37.921)]
+Washington Ave [(-122.3033,37.9),(-122.3023,37.905)]
+Washington Blvd [(-121.9418,37.314),(-121.9422,37.314)]
+Washington Blvd [(-121.9512,37.335),(-121.9522,37.335)]
+Washington Blvd [(-121.9557,37.328),(-121.9584,37.33)]
+Washington St [(-121.8615,37.684),(-121.8596,37.694)]
+Washington St [(-122.2355,37.537),(-122.2347,37.532)]
+Washington St [(-122.2378,37.553),(-122.237,37.548)]
+Washington St [(-122.2405,37.568),(-122.24,37.566)]
+Washington St [(-122.2772,37.965),(-122.2769,37.97)]
+Washo Dr [(-121.9213,37.117),(-121.9204,37.114)]
+Waterfall Isle [(-122.2699,37.668),(-122.2694,37.677)]
+Waterford Pl [(-122.0472,37.026),(-122.0473,37.021)]
+Watts St [(-122.2805,37.28),(-122.2804,37.285)]
+Waverly Way [(-121.7634,37.94),(-121.762632,37.94055)]
+Webb Ave [(-122.0975,37.89),(-122.0971,37.894)]
+Webster St [(-122.0593,37.39),(-122.0587,37.393)]
+Webster St [(-122.2474,37.563),(-122.2461,37.562)]
+Webster St [(-122.2595,37.33),(-122.2596,37.337)]
+Webster St [(-122.2599,37.305),(-122.2596,37.313)]
+Webster St [(-122.2643,37.145),(-122.2642,37.152)]
+Webster St [(-122.2691,37.026),(-122.2687,37.033)]
+Webster St [(-122.2703,37.005),(-122.2699,37.011)]
+Webster St [(-122.2724,37.97),(-122.272,37.977)]
+Webster St [(-122.2752,37.925),(-122.2746,37.936)]
+Webster St [(-122.276,37.775),(-122.276,37.785)]
+Webster St [(-122.276,37.809),(-122.276,37.841)]
+Webster St [(-122.276,37.903),(-122.276,37.913)]
+Welch Creek [(-121.8107,37.349),(-121.8012,37.397)]
+Welch Creek Road [(-121.7695,37.386),(-121.7737,37.413)]
+Welch Creek Road [(-121.7895,37.36),(-121.7717,37.218)]
+Welch Creek Road [(-121.844,37.37),(-121.8289,37.315)]
+Weld St [(-122.1827,37.64),(-122.181,37.623)]
+Welk Com [(-122.036356,37.86914),(-122.036265,37.8618)]
+Wellington Pl [(-122.0242,37.628),(-122.0227,37.632)]
+Wente St [(-121.7486,37.661),(-121.7486,37.65715)]
+Wentworth Ave [(-122.1997,37.698),(-122.1986,37.698)]
+Wesley Ave [(-122.2457,37.068),(-122.2446,37.078)]
+Wesley Ave [(-122.2482,37.056),(-122.2476,37.059)]
+West St [(-122.1066,37.48),(-122.108083,37.47375)]
+West St [(-122.2719,37.243),(-122.2717,37.251)]
+West St [(-122.2754,37.136),(-122.2751,37.141)]
+West St [(-122.276,37.124),(-122.2756,37.131)]
+West Loop Road [(-122.0576,37.604),(-122.0602,37.586)]
+Westbrook Pl [(-121.7787,37.565),(-121.7784,37.564)]
+Westchester St [(-122.031,37.135),(-122.0293,37.136)]
+Western Ave [(-122.1365,37.049),(-122.1358,37.044)]
+Western Blvd [(-122.1004,37.792),(-122.0983,37.778)]
+Western Blvd [(-122.1043,37.819),(-122.1023,37.805)]
+Western Pacific Railroad [(-121.628112,37.31406),(-121.628315,37.31524)]
+Western Pacific Railroad [(-121.6953,37.215),(-121.6955,37.223)]
+Western Pacific Railroad [(-121.95686,37.6508),(-121.9548,37.598)]
+Western Pacific Railroad [(-122.0302,37.963),(-122.0302,37.99)]
+Western Pacific Railroad Spur [(-122.0394,37.018),(-122.0394,37.961)]
+Westminster Dr [(-122.2409,37.406),(-122.2391,37.413)]
+Westover Dr [(-122.1985,37.286),(-122.1959,37.302)]
+Westview Pl [(-122.2309,37.588),(-122.2303,37.585)]
+Westwood Ave [(-122.0079,37.454),(-122.008033,37.45)]
+Westwood Pl [(-122.0773,37.36),(-122.078,37.362)]
+Whalebone Way [(-122.0592,37.244),(-122.059,37.242)]
+Whimbrel Road [(-122.0439,37.832),(-122.0432,37.828)]
+Whimbrel Road [(-122.045,37.842),(-122.0447,37.839)]
+Whipple Road [(-122.0532,37.059),(-122.0576,37.059)]
+Whipple Road [(-122.0676,37.055),(-122.0678,37.057),(-122.0752,37.057)]
+Whispering Pine Ct [(-122.06,37.222),(-122.0608,37.226)]
+Whitaker Ave [(-122.2555,37.909),(-122.2554,37.912)]
+Whitecap Way [(-121.991567,37.28508),(-121.99096,37.28222)]
+Whitlock Creek [(-121.74683,37.91276),(-121.733107,37)]
+Whitman St [(-122.0633,37.399),(-122.063,37.392)]
+Whitman St [(-122.072,37.54),(-122.0712,37.53)]
+Whitney Pl [(-121.9168,37.703),(-121.9188,37.695)]
+Wicks Blvd [(-122.1596,37.856),(-122.1578,37.833)]
+Wicks Blvd [(-122.163486,37.97094),(-122.1632,37.966),(-122.1624,37.936)]
+Wilbeam Ave [(-122.0759,37.936),(-122.076,37.932)]
+Wilbur St [(-122.21,37.039),(-122.2093,37.034)]
+Wild Current Way [(-122.1987,37.395),(-122.1993,37.402)]
+Wildcat Ct [(-121.94693,37.08767),(-121.947193,37.08295)]
+Wildcat Canyon Road [(-122.2628,37.035),(-122.264,37.041)]
+Wildcat Canyon Road [(-122.2658,37.046),(-122.264,37.041)]
+Wildwood Ave [(-122.2341,37.194),(-122.2328,37.199)]
+Wiley St [(-122.1553,37.93),(-122.1548,37.921),(-122.1546,37.916)]
+Willard Way [(-122.0871,37.169),(-122.0843,37.177)]
+Williams St [(-122.1568,37.179),(-122.1577,37.175)]
+Williams St [(-122.1634,37.151),(-122.164,37.148)]
+Williams St [(-122.1649,37.143),(-122.1655,37.141)]
+Willimet Way [(-122.0964,37.517),(-122.0949,37.493)]
+Willow Ave [(-122.1012,37.748),(-122.1002,37.754)]
+Willow Ave [(-122.16,37.961),(-122.1608,37.96)]
+Willow St [(-122.0519,37.279),(-122.0517,37.275)]
+Willow St [(-122.2459,37.711),(-122.2453,37.72)]
+Willow St [(-122.2494,37.661),(-122.2487,37.67)]
+Willow St [(-122.2516,37.627),(-122.251,37.635)]
+Willow St [(-122.2559,37.567),(-122.2551,37.58)]
+Willow St [(-122.2913,37.184),(-122.2901,37.198),(-122.2891,37.212)]
+Willow St [(-122.2998,37.077),(-122.2996,37.081)]
+Willow Walk [(-122.2364,37.572),(-122.23664,37.5678)]
+Willowood Dr [(-122.0126,37.498),(-122.0096,37.483)]
+Wilson Ave [(-122.0751,37.073),(-122.0727,37.07)]
+Wilson Cir [(-122.246849,37.81463),(-122.2468,37.815)]
+Winding Lane [(-121.931438,37.07849),(-121.9306,37.078)]
+Windmill Ct [(-121.9163,37.948),(-121.9167,37.944)]
+Windmill Lane [(-121.8688,37.531),(-121.8678,37.532)]
+Windsor Dr [(-122.1125,37.071),(-122.1105,37.048)]
+Windsor Dr [(-122.2303,37.673),(-122.2289,37.665)]
+Windsor Pl [(-121.7781,37.521),(-121.7781,37.525)]
+Windsor Way [(-121.7772,37.521),(-121.7762,37.521)]
+Wingate Way [(-122.0652,37.838),(-122.0643,37.846)]
+Winslow Ter [(-122.059185,37.53928),(-122.058347,37.5363)]
+Winsor Ave [(-122.2356,37.203),(-122.2351,37.203)]
+Winthrope St [(-122.1559,37.694),(-122.1548,37.678)]
+Winton Ave [(-122.1151,37.533),(-122.1165,37.532)]
+Winton Ave [(-122.1231,37.533),(-122.1231,37.53)]
+Wisconsin St [(-122.1927,37.945),(-122.1921,37.94)]
+Wisconsin St [(-122.1945,37.965),(-122.1939,37.959)]
+Wisconsin St [(-122.1994,37.017),(-122.1975,37.998),(-122.1971,37.994)]
+Wistaria Way [(-122.2251,37.182),(-122.2249,37.188)]
+Wisteria Dr [(-121.9362,37.373),(-121.9363,37.367)]
+Wisteria Dr [(-121.9369,37.398),(-121.9369,37.391)]
+Wisteria Way [(-121.7322,37.118),(-121.7332,37.114)]
+Wixon Dr [(-121.9613,37.199),(-121.9604,37.18)]
+Wolcott Dr [(-121.9568,37.393),(-121.9564,37.389)]
+Wood Dr [(-122.2194,37.28),(-122.2174,37.266)]
+Wood St [(-121.762,37.839),(-121.7611,37.834)]
+Wood St [(-122.2993,37.107),(-122.2988,37.113)]
+Wood St [(-122.302,37.06),(-122.3019,37.066)]
+Woodcrest Dr [(-121.9579,37.225),(-121.9589,37.224)]
+Woodhaven Way [(-122.208,37.417),(-122.2045,37.411)]
+Woodhue Ter [(-122.046987,37.6207),(-122.047212,37.62529)]
+Woodridge Dr [(-122.0631,37.836),(-122.0616,37.82)]
+Woodroe Ave [(-122.0536,37.855),(-122.0534,37.847)]
+Woodroe Ave [(-122.0539,37.888),(-122.054,37.891)]
+Woodruff Ave [(-122.2214,37.03),(-122.2206,37.057)]
+Woolsey St [(-122.2553,37.538),(-122.2545,37.538)]
+Wooster Ct [(-122.023499,37.15118),(-122.0237,37.147)]
+Worth St [(-122.1903,37.362),(-122.1903,37.365)]
+Worth St [(-122.1915,37.378),(-122.1916,37.379)]
+Wp Railroad [(-121.7675,37.848),(-121.765632,37.85375),(-121.7636,37.86)]
+Wp Railroad [(-121.7864,37.791),(-121.7761,37.822)]
+Wp Railroad [(-121.813721,37.75618),(-121.8049,37.765)]
+Wp Railroad [(-121.8346,37.733),(-121.8259,37.743)]
+Wp Railroad [(-121.874759,37.65728),(-121.8727,37.665)]
+Wp Railroad [(-121.8804,37.27),(-121.8806,37.272),(-121.88678,37.36528)]
+Wp Railroad [(-121.882149,37.57458),(-121.8813,37.585)]
+Wp Railroad [(-121.8861,37.94),(-121.8847,37.952)]
+Wp Railroad [(-121.9304,37.856),(-121.9268,37.789)]
+Wp Railroad [(-121.958612,37.67823),(-121.9581,37.67)]
+Wp Railroad [(-122.0223,37.959),(-122.0211,37.949)]
+Wp Railroad [(-122.0476,37.214),(-122.0473,37.196)]
+Wp Railroad [(-122.0755,37.589),(-122.0731,37.56)]
+Wp Railroad [(-122.1877,37.466),(-122.1834,37.43)]
+Wp Railroad [(-122.2043,37.608),(-122.203628,37.60237)]
+Wp Railroad [(-122.2145,37.694),(-122.2137,37.688)]
+Wp Railroad [(-122.2342,37.806),(-122.2295,37.777)]
+Wp Railroad [(-122.2434,37.865),(-122.241,37.851)]
+Wp Railroad [(-122.254,37.902),(-122.2506,37.891)]
+Wp Railroad [(-122.262,37.923),(-122.2607,37.921)]
+Wp Railroad [(-122.2693,37.949),(-122.2682,37.945)]
+Wrenn St [(-122.2063,37.117),(-122.2056,37.117)]
+Wyndham Pl [(-122.027,37.724),(-122.0286,37.73)]
+Xavier Way [(-121.7458,37.778),(-121.7459,37.77)]
+Xavier Common [(-122.049697,37.64509),(-122.050346,37.64313)]
+Yale Ave [(-122.1205,37.913),(-122.1205,37.907)]
+Yale Way [(-121.9443,37.098),(-121.9482,37.092)]
+Yampa Way [(-121.9117,37.641),(-121.9109,37.644)]
+Yellowstone Park Dr [(-121.9686,37.111),(-121.9705,37.117)]
+Yerba Buena Ave [(-122.2789,37.301),(-122.2843,37.29)]
+Yerba Buena Pl [(-121.9602,37.678),(-121.9595,37.683)]
+Yerba Buena St [(-121.960541,37.6819),(-121.9602,37.678)]
+Ygnacio Ave [(-122.2103,37.756),(-122.2098,37.754)]
+Yolo Ave [(-122.274,37.85),(-122.273,37.855)]
+York Dr [(-121.9212,37.098),(-121.9227,37.104)]
+York Dr [(-122.2415,37.283),(-122.2406,37.261)]
+Yorktown Road [(-121.9507,37.173),(-121.9513,37.172)]
+Yosemite Pl [(-121.8019,37.853),(-121.8024,37.855)]
+Yosemite Road [(-122.2767,37.968),(-122.2757,37.958)]
+Yosemite Way [(-122.1108,37.4803),(-122.109445,37.48415)]
+Zacate Ave [(-121.9575,37.646),(-121.9567,37.64)]
+Zacate Ave [(-121.9594,37.662),(-121.9589,37.654)]
+Zapata Ct [(-121.9456,37.171),(-121.94565,37.1705)]
+Zapotec Dr [(-121.9108,37.899),(-121.9097,37.898)]
+Zephyr Ave [(-122.0572,37.107),(-122.0556,37.107)]
+Zephyr Ave [(-122.0584,37.107),(-122.0609,37.107)]
+Ziegler Ave [(-122.1258,37.483),(-122.125,37.499)]
+Zircon Ter [(-122.051254,37.64038),(-122.051126,37.63696)]
+100th Ave [(-122.1657,37.429),(-122.1647,37.432)]
+100th Ave [(-122.1789,37.364),(-122.1785,37.367)]
+101st Ave [(-122.1595,37.438),(-122.1583,37.441)]
+104th Ave [(-122.1583,37.417),(-122.1573,37.423)]
+104th Ave [(-122.1612,37.411),(-122.1587,37.417)]
+104th Ave [(-122.1697,37.375),(-122.1681,37.383)]
+105th Ave [(-122.1774,37.325),(-122.1771,37.327)]
+106th Ave [(-122.158,37.404),(-122.156,37.409)]
+107th Ave [(-122.1555,37.403),(-122.1531,37.41)]
+108th Ave [(-122.1474,37.419),(-122.1468,37.423)]
+10th St [(-122.0211,37.969),(-122.0198,37.958)]
+10th St [(-122.0603,37.402),(-122.0593,37.39)]
+10th St [(-122.2541,37.924),(-122.2537,37.92)]
+10th St [(-122.2553,37.935),(-122.2545,37.927)]
+10th St [(-122.2675,37.002),(-122.2687,37.007)]
+10th St [(-122.2886,37.57),(-122.2883,37.56)]
+10th St [(-122.2916,37.662),(-122.291,37.644)]
+10th St [(-122.2918,37.084),(-122.292,37.085)]
+10th St [(-122.2945,37.75),(-122.2939,37.732)]
+11th Ave [(-122.2493,37.91),(-122.2483,37.916)]
+11th St [(-122.0206,37.952),(-122.0192,37.941),(-122.0185,37.934)]
+120 Canal [(-121.587482,37.88204),(-121.587833,37.8825)]
+12th Ave [(-122.2396,37.958),(-122.2385,37.965)]
+12th St [(-121.896,37.194),(-121.8932,37.194)]
+12th St [(-121.9026,37.204),(-121.900288,37.19771),(-121.896959,37.19973)]
+12th St [(-122.2163,37.718),(-122.2151,37.712)]
+12th St [(-122.2469,37.891),(-122.2459,37.882)]
+12th St [(-122.2566,37.974),(-122.256,37.971)]
+12th St [(-122.2611,37.996),(-122.262,37.998)]
+12th St [(-122.2644,37.006),(-122.2655,37.011)]
+12th St [(-122.2807,37.073),(-122.2796,37.068)]
+12th St [(-122.2889,37.094),(-122.29,37.094)]
+12th St [(-122.2926,37.103),(-122.2937,37.102)]
+12th St [(-122.2943,37.103),(-122.2955,37.109),(-122.2964,37.113)]
+136th Ave [(-122.1399,37.158),(-122.1371,37.18)]
+137th Ave [(-122.1366,37.176),(-122.1351,37.18)]
+13th Ave [(-122.235,37.984),(-122.2345,37.99)]
+13th Ave [(-122.2396,37.944),(-122.2387,37.95)]
+13th St [(-122.0242,37.962),(-122.0231,37.954)]
+13th St [(-122.0565,37.386),(-122.0554,37.374)]
+13th St [(-122.2628,37.009),(-122.2639,37.014)]
+140th Ave [(-122.1386,37.13),(-122.1374,37.14)]
+141st Ave [(-122.1368,37.136),(-122.1355,37.147)]
+142nd Ave [(-122.1348,37.142),(-122.1336,37.153)]
+143rd Ave [(-122.1408,37.077),(-122.1397,37.085)]
+145th Ave [(-122.1368,37.082),(-122.1359,37.09)]
+148th Ave [(-122.1284,37.119),(-122.1277,37.122)]
+14th Ave [(-122.2244,37.028),(-122.2242,37.036)]
+14th Ave [(-122.2245,37.036),(-122.2238,37.062)]
+14th Ave [(-122.2285,37.981),(-122.2283,37.988)]
+14th Ave [(-122.232,37.965),(-122.2293,37.975)]
+14th Ave [(-122.2342,37.957),(-122.2332,37.961)]
+14th Ave [(-122.2357,37.953),(-122.2354,37.953)]
+14th Ave [(-122.2408,37.923),(-122.2407,37.925)]
+14th Ave [(-122.2426,37.911),(-122.2419,37.916)]
+14th St [(-122.0238,37.949),(-122.0226,37.938)]
+14th St [(-122.1178,37.983),(-122.116,37.971)]
+14th St [(-122.1212,37.007),(-122.1206,37.002)]
+14th St [(-122.1241,37.027),(-122.1235,37.023),(-122.1226,37.017)]
+14th St [(-122.1316,37.081),(-122.1287,37.06)]
+14th St [(-122.1378,37.124),(-122.1376,37.123)]
+14th St [(-122.141,37.147),(-122.1397,37.138)]
+14th St [(-122.1457,37.181),(-122.145,37.176)]
+14th St [(-122.1562,37.262),(-122.1557,37.253)]
+14th St [(-122.1626,37.348),(-122.1621,37.344)]
+14th St [(-122.1655,37.387),(-122.165101,37.38001)]
+14th St [(-122.1682,37.422),(-122.168,37.419)]
+14th St [(-122.1709,37.459),(-122.1704,37.453)]
+14th St [(-122.1761,37.529),(-122.1757,37.524),(-122.1754,37.52)]
+14th St [(-122.1771,37.544),(-122.1768,37.54)]
+14th St [(-122.1845,37.581),(-122.1839,37.579)]
+14th St [(-122.1945,37.629),(-122.1936,37.625)]
+14th St [(-122.2128,37.717),(-122.2118,37.713)]
+14th St [(-122.2288,37.798),(-122.228,37.792)]
+14th St [(-122.2419,37.877),(-122.2408,37.871)]
+14th St [(-122.2659,37.03),(-122.2671,37.035)]
+14th St [(-122.2755,37.068),(-122.2741,37.063)]
+14th St [(-122.277,37.073),(-122.2765,37.071)]
+14th St [(-122.299,37.147),(-122.3,37.148)]
+150th Ave [(-122.1241,37.085),(-122.123984,37.08583)]
+150th Ave [(-122.1266,37.065),(-122.1258,37.071)]
+152nd Ave [(-122.1218,37.072),(-122.1215,37.075)]
+15th St [(-122.0564,37.416),(-122.056,37.411)]
+15th St [(-122.2264,37.794),(-122.2258,37.791),(-122.2251,37.789)]
+15th St [(-122.241,37.885),(-122.2401,37.879)]
+15th St [(-122.2472,37.923),(-122.2464,37.915)]
+15th St [(-122.2534,37.976),(-122.2525,37.969)]
+163rd Ave [(-122.1108,37.985),(-122.1096,37.995)]
+164th Ave [(-122.1068,37.993),(-122.1069,37.998)]
+164th Ave [(-122.1101,37.964),(-122.1096,37.968)]
+166th Ave [(-122.1008,37.99),(-122.1006,37.997)]
+167th Ave [(-122.1012,37.966),(-122.1006,37.973)]
+168th Ave [(-122.1046,37.934),(-122.1041,37.938)]
+16th Ave [(-122.2391,37.924),(-122.2387,37.928)]
+16th Ave [(-122.2422,37.892),(-122.2418,37.896)]
+16th Ave [(-122.2438,37.874),(-122.2438,37.876)]
+16th St [(-122.054,37.414),(-122.0534,37.405)]
+16th St [(-122.231,37.837),(-122.2306,37.834)]
+16th St [(-122.2699,37.067),(-122.2713,37.069)]
+16th St [(-122.2923,37.129),(-122.2926,37.132)]
+170th Ave [(-122.1029,37.924),(-122.1025,37.93)]
+170th Ave [(-122.1083,37.891),(-122.1075,37.893)]
+171st Ave [(-122.1042,37.908),(-122.1031,37.916)]
+173rd Ave [(-122.0984,37.935),(-122.0976,37.936)]
+17th Ave [(-122.238,37.918),(-122.2377,37.921)]
+17th St [(-122.1947,37.648),(-122.1934,37.637)]
+17th St [(-122.2385,37.895),(-122.2375,37.889)]
+17th St [(-122.2514,37.99),(-122.2507,37.982)]
+17th St [(-122.2607,37.043),(-122.2619,37.047)]
+17th St [(-122.2642,37.057),(-122.2655,37.059)]
+17th St [(-122.2743,37.087),(-122.2729,37.082)]
+17th St [(-122.2898,37.132),(-122.29,37.132)]
+17th St [(-122.2938,37.149),(-122.295,37.155)]
+18th Ave [(-122.2385,37.895),(-122.2381,37.899)]
+18th St [(-122.2205,37.811),(-122.219,37.806)]
+18th St [(-122.2424,37.924),(-122.2416,37.918)]
+18th St [(-122.2461,37.957),(-122.2453,37.95)]
+18th St [(-122.254,37.012),(-122.2535,37.01)]
+18th St [(-122.2775,37.106),(-122.2762,37.101)]
+18th St [(-122.2775,37.106),(-122.2794,37.109)]
+18th St [(-122.2803,37.116),(-122.2814,37.119)]
+19th Ave [(-122.2355,37.91),(-122.2351,37.914),(-122.2344,37.922)]
+19th Ave [(-122.2366,37.897),(-122.2359,37.905)]
+19th Ave [(-122.2386,37.877),(-122.2382,37.881)]
+19th Ave [(-122.2394,37.868),(-122.239,37.873)]
+19th Ave [(-122.2408,37.854),(-122.2406,37.856)]
+19th St [(-122.2488,37.994),(-122.2479,37.986)]
+19th St [(-122.2672,37.079),(-122.2685,37.084)]
+1st Ave [(-122.2567,37.992),(-122.2558,37.999)]
+1st St [(-121.7401,37.018),(-121.7401,37.024)]
+1st St [(-121.7402,37.015),(-121.7401,37.018)]
+1st St [(-121.7425,37.976),(-121.7417,37.986)]
+1st St [(-121.75508,37.89294),(-121.753581,37.90031)]
+1st St [(-121.7716,37.805),(-121.7702,37.809)]
+1st St [(-121.8728,37.596),(-121.8738,37.587)]
+20th Ave [(-122.238,37.867),(-122.2376,37.871)]
+20th St [(-122.2327,37.887),(-122.2308,37.876)]
+20th St [(-122.2433,37.963),(-122.2424,37.955)]
+20th St [(-122.2468,37.994),(-122.2459,37.985)]
+20th St [(-122.2912,37.163),(-122.2926,37.17)]
+21st Ave [(-122.2341,37.89),(-122.2337,37.893)]
+21st Ave [(-122.2368,37.859),(-122.2365,37.865)]
+21st Ave [(-122.2385,37.843),(-122.238,37.847)]
+21st St [(-122.243,37.975),(-122.2423,37.97)]
+21st St [(-122.2624,37.099),(-122.2643,37.102)]
+21st St [(-122.2688,37.109),(-122.2719,37.114)]
+22nd Ave [(-122.2343,37.871),(-122.2338,37.875)]
+22nd St [(-122.2227,37.854),(-122.222,37.851)]
+22nd St [(-122.265,37.114),(-122.2655,37.114)]
+22nd St [(-122.2756,37.131),(-122.2769,37.136)]
+23rd Ave [(-122.2272,37.914),(-122.2266,37.92)]
+23rd Ave [(-122.2298,37.892),(-122.2298,37.894)]
+23rd Ave [(-122.2339,37.841),(-122.233,37.849)]
+23rd Ave [(-122.2357,37.824),(-122.2356,37.825)]
+23rd Ave [(-122.2359,37.817),(-122.236,37.82)]
+23rd St [(-122.2258,37.878),(-122.2252,37.875)]
+23rd St [(-122.228,37.896),(-122.2271,37.89)]
+23rd St [(-122.2404,37.982),(-122.2395,37.974)]
+23rd St [(-122.2648,37.124),(-122.2658,37.125)]
+23rd Av Ovps [(-122.2356,37.768),(-122.235,37.754)]
+23rd Av Ovps [(-122.236,37.783),(-122.2356,37.768)]
+24th Ave [(-122.2285,37.873),(-122.2277,37.88),(-122.2271,37.89)]
+24th St [(-122.233,37.936),(-122.2309,37.923)]
+24th St [(-122.2352,37.95),(-122.235,37.948)]
+24th St [(-122.2868,37.183),(-122.2877,37.186)]
+24th St [(-122.2901,37.198),(-122.2914,37.204)]
+25th Ave [(-122.2222,37.94),(-122.222,37.942)]
+26th Ave [(-122.2252,37.875),(-122.2244,37.884)]
+26th St [(-122.2294,37.939),(-122.2289,37.936)]
+26th St [(-122.2739,37.171),(-122.2749,37.172)]
+26th St [(-122.2791,37.183),(-122.28,37.185)]
+27th Ave [(-122.234,37.773),(-122.2335,37.778)]
+27th St [(-122.2224,37.918),(-122.2216,37.912)]
+27th St [(-122.2243,37.928),(-122.2231,37.921)]
+27th St [(-122.2287,37.945),(-122.2278,37.939)]
+27th St [(-122.2625,37.151),(-122.2626,37.154)]
+27th St [(-122.2751,37.181),(-122.276,37.18)]
+28th St [(-122.2356,37.999),(-122.235,37.994)]
+28th St [(-122.2671,37.176),(-122.2684,37.179)]
+28th St [(-122.2754,37.192),(-122.2764,37.193)]
+29th St [(-122.2633,37.179),(-122.264,37.181)]
+29th St [(-122.266,37.183),(-122.2668,37.184)]
+29th St [(-122.2693,37.192),(-122.27,37.194)]
+29th St [(-122.2707,37.189),(-122.2733,37.194)]
+29th Av Ovps [(-122.2323,37.748),(-122.2321,37.752)]
+2nd St [(-121.9787,37.76),(-121.9775,37.757)]
+2nd St [(-121.9825,37.768),(-121.9806,37.763)]
+2nd St [(-121.986342,37.77609),(-121.9854,37.774),(-121.9842,37.772)]
+2nd St [(-122.0522,37.685),(-122.05205,37.6854)]
+2nd St [(-122.0553,37.679),(-122.0539,37.682)]
+2nd St [(-122.0697,37.696),(-122.0674,37.688)]
+2nd St [(-122.2781,37.975),(-122.2792,37.979)]
+30th St [(-122.2789,37.218),(-122.2801,37.22)]
+31st Ave [(-122.2259,37.771),(-122.2254,37.779)]
+31st St [(-122.2252,37.982),(-122.224,37.979)]
+31st St [(-122.2323,37.995),(-122.2303,37.992)]
+31st St [(-122.2728,37.212),(-122.2756,37.217)]
+32nd St [(-122.225,37.99),(-122.2244,37.988)]
+32nd St [(-122.2754,37.227),(-122.2765,37.228)]
+32nd St [(-122.2839,37.242),(-122.285,37.239)]
+32nd St [(-122.2893,37.23),(-122.2901,37.229)]
+33rd Ave [(-122.2235,37.776),(-122.2225,37.798)]
+34th Ave [(-122.2205,37.811),(-122.2196,37.829)]
+34th Ave [(-122.2232,37.755),(-122.2225,37.765)]
+34th Ave [(-122.2251,37.728),(-122.2246,37.737)]
+34th St [(-122.2637,37.223),(-122.2647,37.225)]
+35th Ave [(-122.1914,37.983),(-122.1909,37.988)]
+35th Ave [(-122.2005,37.929),(-122.1996,37.935)]
+35th Ave [(-122.214,37.85),(-122.2138,37.852)]
+35th Ave [(-122.2243,37.724),(-122.2238,37.731)]
+35th St [(-122.2685,37.243),(-122.2692,37.244)]
+35th St [(-122.2746,37.257),(-122.2779,37.266)]
+35th St [(-122.2783,37.266),(-122.2792,37.269)]
+36th Ave [(-122.2196,37.778),(-122.218,37.802)]
+36th Ave [(-122.2214,37.746),(-122.2206,37.755)]
+36th Ave [(-122.2221,37.737),(-122.222,37.738)]
+36th Ave [(-122.2233,37.721),(-122.223,37.725)]
+36th Ave [(-122.22414,37.70988),(-122.2238,37.716)]
+36th St [(-122.228,37.022),(-122.227,37.02)]
+36th St [(-122.2779,37.273),(-122.279,37.277)]
+37th Ave [(-122.2211,37.732),(-122.221,37.733)]
+37th St [(-122.2613,37.249),(-122.265,37.257)]
+37th St [(-122.265,37.26),(-122.2663,37.271)]
+37th St [(-122.2743,37.274),(-122.2768,37.278)]
+38th Ave [(-122.1963,37.907),(-122.1954,37.912)]
+38th Ave [(-122.2202,37.729),(-122.2197,37.734)]
+38th St [(-122.2204,37.029),(-122.2199,37.028)]
+38th St [(-122.2571,37.266),(-122.2583,37.268)]
+38th St [(-122.2587,37.268),(-122.2597,37.269)]
+39th Ave [(-122.2054,37.85),(-122.205,37.852)]
+39th Ave [(-122.2163,37.763),(-122.216,37.768)]
+3rd St [(-121.892355,37.95759),(-121.8908,37.957)]
+3rd St [(-122.2872,37.803),(-122.287191,37.8084)]
+3rd St [(-122.2873,37.767),(-122.2873,37.775)]
+3rd St [(-122.2874,37.739),(-122.2874,37.748)]
+3rd St [(-122.2894,37.019),(-122.2905,37.021)]
+3rd St [(-122.2972,37.034),(-122.2982,37.036)]
+40th Ave [(-122.2156,37.76),(-122.2152,37.764)]
+40th St [(-122.2585,37.286),(-122.2594,37.288)]
+41st Ave [(-122.214,37.75),(-122.2135,37.756)]
+41st Ave [(-122.2153,37.735),(-122.2151,37.738)]
+41st Ave [(-122.217,37.719),(-122.2169,37.721)]
+41st St [(-122.2562,37.29),(-122.2571,37.291)]
+41st St [(-122.2655,37.305),(-122.2653,37.314)]
+41st St [(-122.2671,37.308),(-122.2677,37.308)]
+41st St [(-122.2768,37.324),(-122.2773,37.325)]
+42nd Ave [(-122.2104,37.767),(-122.2097,37.773)]
+42nd St [(-122.2755,37.33),(-122.2765,37.332)]
+43rd St [(-122.2673,37.325),(-122.2698,37.329)]
+43rd St [(-122.2763,37.338),(-122.2768,37.339)]
+45th Ave [(-122.2088,37.749),(-122.208,37.758)]
+45th Ave [(-122.2118,37.713),(-122.2109,37.724)]
+45th St [(-122.2814,37.341),(-122.2825,37.339)]
+46th Ave [(-122.214,37.685),(-122.2137,37.688)]
+46th St [(-122.2669,37.345),(-122.2694,37.349)]
+47th Ave [(-122.2086,37.719),(-122.2082,37.723)]
+48th Ave [(-122.2059,37.736),(-122.2052,37.745)]
+48th St [(-122.2633,37.354),(-122.2643,37.356)]
+48th St [(-122.2782,37.373),(-122.2782,37.37224)]
+49th St [(-122.2545,37.348),(-122.2552,37.349)]
+4th Ave [(-122.2534,37.976),(-122.2524,37.983)]
+4th St [(-121.7778,37.76),(-121.7753,37.769),(-121.7741,37.772)]
+4th St [(-122.0775,37.831),(-122.0772,37.824)]
+4th St [(-122.2773,37.988),(-122.2784,37.993)]
+4th St [(-122.2853,37.738),(-122.2854,37.742)]
+4th St [(-122.3005,37.885),(-122.3006,37.871)]
+50th Ave [(-122.2059,37.718),(-122.2054,37.724)]
+50th Ave [(-122.2108,37.663),(-122.2096,37.675)]
+50th Ave [(-122.2135,37.638),(-122.2123,37.65)]
+51st Ave [(-122.2103,37.658),(-122.21,37.661)]
+52nd Ave [(-122.2096,37.653),(-122.2092,37.657)]
+52nd St [(-122.268,37.376),(-122.2683,37.376)]
+52nd St [(-122.2706,37.372),(-122.2728,37.369)]
+53rd Ave [(-122.2075,37.659),(-122.205,37.679)]
+54th Ave [(-122.2076,37.649),(-122.2068,37.654)]
+54th St [(-122.2733,37.386),(-122.275,37.384)]
+55th Ave [(-122.1886,37.768),(-122.1883,37.77)]
+55th Ave [(-122.197,37.709),(-122.1963,37.714)]
+55th Ave [(-122.1996,37.689),(-122.1991,37.695)]
+55th St [(-122.2641,37.408),(-122.2667,37.404)]
+55th St [(-122.2824,37.386),(-122.2834,37.384)]
+56th St [(-122.2692,37.409),(-122.2695,37.409)]
+56th St [(-122.2735,37.404),(-122.2743,37.403)]
+57th Ave [(-122.1933,37.711),(-122.1928,37.721)]
+57th Ave [(-122.1969,37.686),(-122.1964,37.689)]
+57th St [(-122.2609,37.433),(-122.262,37.435)]
+57th St [(-122.2661,37.428),(-122.2671,37.426)]
+57th St [(-122.274,37.418),(-122.2759,37.417)]
+57th St [(-122.2798,37.409),(-122.2822,37.405)]
+58th Ave [(-122.1876,37.749),(-122.1867,37.755)]
+58th St [(-122.2663,37.437),(-122.2673,37.435)]
+58th St [(-122.2701,37.437),(-122.272,37.435)]
+59th St [(-122.2587,37.456),(-122.2608,37.454)]
+59th St [(-122.2665,37.445),(-122.2675,37.445)]
+59th St [(-122.2863,37.42),(-122.2872,37.418)]
+5th Ave [(-122.2516,37.975),(-122.2507,37.982)]
+5th St [(-121.7638,37.795),(-121.7633,37.795)]
+5th St [(-121.7713,37.772),(-121.7701,37.775)]
+5th St [(-121.7737,37.765),(-121.7727,37.769)]
+5th St [(-121.7766,37.757),(-121.7751,37.761)]
+5th St [(-121.9082,37.114),(-121.9045,37.113)]
+5th St [(-122.0235,37.054),(-122.0224,37.045)]
+5th St [(-122.071,37.754),(-122.0707,37.749)]
+5th St [(-122.0732,37.8),(-122.0725,37.789)]
+5th St [(-122.2732,37.981),(-122.2744,37.986)]
+5th St [(-122.2756,37.991),(-122.2768,37.995)]
+5th St [(-122.278,37),(-122.2792,37.005),(-122.2803,37.009)]
+5th St [(-122.2815,37.766),(-122.2814,37.777)]
+5th St [(-122.2816,37.747),(-122.2816,37.757)]
+5th St [(-122.2901,37.036),(-122.292,37.04)]
+5th St [(-122.2933,37.041),(-122.2946,37.045)]
+5th St [(-122.296,37.615),(-122.2953,37.598)]
+5th St [(-122.2977,37.665),(-122.2972,37.651),(-122.2966,37.633)]
+5th St [(-122.2979,37.866),(-122.2979,37.87)]
+5th St [(-122.3011,37.775),(-122.3008,37.763)]
+60th Ave [(-122.1881,37.712),(-122.1868,37.722)]
+60th St [(-122.2606,37.469),(-122.2615,37.466)]
+60th St [(-122.2816,37.435),(-122.2831,37.432)]
+61st Ave [(-122.1973,37.643),(-122.1962,37.65)]
+61st St [(-122.2861,37.436),(-122.2877,37.433)]
+62nd Ave [(-122.1794,37.751),(-122.1787,37.755)]
+62nd Ave [(-122.1916,37.67),(-122.1911,37.673)]
+62nd Ave [(-122.1992,37.617),(-122.1982,37.623)]
+62nd St [(-122.254,37.494),(-122.2563,37.491)]
+62nd St [(-122.268,37.477),(-122.2685,37.476)]
+62nd St [(-122.2755,37.466),(-122.2749,37.467)]
+62nd St [(-122.2761,37.465),(-122.276639,37.4632),(-122.2767,37.463)]
+63rd Ave [(-122.1853,37.7),(-122.1844,37.706)]
+63rd St [(-122.2563,37.495),(-122.2576,37.493),(-122.2596,37.49)]
+63rd St [(-122.2604,37.49),(-122.2621,37.487)]
+63rd St [(-122.2622,37.492),(-122.2651,37.489)]
+63rd St [(-122.2708,37.481),(-122.269,37.484)]
+64th Ave [(-122.1758,37.76),(-122.1753,37.767)]
+64th Ave [(-122.188,37.673),(-122.1852,37.691)]
+64th Ave [(-122.1921,37.645),(-122.1911,37.652)]
+64th St [(-122.2869,37.46),(-122.2884,37.455)]
+64th St [(-122.2945,37.441),(-122.2962,37.439)]
+64th Av Pl [(-122.1784,37.734),(-122.1767,37.744)]
+65th St [(-122.2653,37.505),(-122.2661,37.504)]
+65th St [(-122.2847,37.481),(-122.2874,37.476)]
+66th Ave [(-122.2013,37.556),(-122.1997,37.564)]
+66th Ave [(-122.2054,37.542),(-122.2042,37.546)]
+67th St [(-122.2828,37.504),(-122.283232,37.5031)]
+67th St [(-122.28407,37.50135),(-122.2852,37.499)]
+67th St [(-122.2887,37.495),(-122.2913,37.49)]
+69th Ave [(-122.1927,37.582),(-122.1919,37.587)]
+69th Ave [(-122.1959,37.56),(-122.1954,37.563)]
+6th St [(-121.7634,37.785),(-121.7624,37.789)]
+6th St [(-122.0219,37.032),(-122.0208,37.022)]
+6th St [(-122.2691,37.975),(-122.2704,37.98)]
+6th St [(-122.274,37.993),(-122.2752,37.998)]
+6th St [(-122.2965,37.668),(-122.296,37.653)]
+6th St [(-122.2982,37.724),(-122.2977,37.705)]
+6th St [(-122.3012,37.813),(-122.3006,37.795)]
+6th St [(-122.3016,37.831),(-122.3016,37.826)]
+70 Canal [(-121.576176,37.91958),(-121.57645,37.91996)]
+70th Ave [(-122.187,37.611),(-122.1862,37.616)]
+71st Ave [(-122.1908,37.576),(-122.189,37.588)]
+71st Ave [(-122.195,37.548),(-122.1944,37.553)]
+73rd Ave [(-122.1746,37.664),(-122.1724,37.68)]
+73rd Ave [(-122.1768,37.651),(-122.1761,37.655)]
+73rd Ave [(-122.1802,37.628),(-122.1794,37.633)]
+73rd Ave [(-122.1837,37.606),(-122.1829,37.611)]
+73rd Ave [(-122.1915,37.554),(-122.1897,37.565)]
+74th Ave [(-122.175,37.653),(-122.174,37.66)]
+77th Ave [(-122.17,37.656),(-122.1682,37.668)]
+78th Ave [(-122.1697,37.652),(-122.1674,37.663)]
+78th Ave [(-122.1791,37.593),(-122.1784,37.597)]
+79th Ave [(-122.1686,37.639),(-122.1662,37.65)]
+7th Ave [(-122.2413,37.015),(-122.2406,37.021)]
+7th Ave [(-122.2459,37.985),(-122.2449,37.991)]
+7th Ave [(-122.2528,37.94),(-122.2518,37.947)]
+7th St [(-121.7606,37.79),(-121.7597,37.798)]
+7th St [(-121.7618,37.779),(-121.7615,37.783)]
+7th St [(-122.0047,37.916),(-122.0041,37.92)]
+7th St [(-122.0168,37.978),(-122.0149,37.974)]
+7th St [(-122.0674,37.771),(-122.0667,37.761)]
+7th St [(-122.0693,37.803),(-122.0687,37.793)]
+7th St [(-122.2362,37.753),(-122.235,37.754)]
+7th St [(-122.2759,37.009),(-122.2771,37.014)]
+7th St [(-122.2864,37.041),(-122.2875,37.043)]
+7th St [(-122.2903,37.511),(-122.2902,37.508)]
+7th St [(-122.2916,37.052),(-122.2926,37.055)]
+7th St [(-122.2918,37.562),(-122.2916,37.553)]
+7th St [(-122.2945,37.635),(-122.2938,37.62)]
+7th St [(-122.2996,37.797),(-122.299,37.779),(-122.2985,37.767)]
+7th St [(-122.3215,37.099),(-122.326,37.102)]
+80th Ave [(-122.1807,37.563),(-122.1793,37.57)]
+81st Ave [(-122.1912,37.493),(-122.191,37.495)]
+82nd Ave [(-122.1659,37.614),(-122.1653,37.619)]
+82nd Ave [(-122.1695,37.596),(-122.1681,37.603)]
+82nd Ave [(-122.1764,37.562),(-122.1747,37.57)]
+83rd Ave [(-122.1741,37.563),(-122.1724,37.571)]
+83rd Ave [(-122.1799,37.531),(-122.179,37.535)]
+84th Ave [(-122.1683,37.58),(-122.1665,37.589)]
+85th Ave [(-122.1677,37.573),(-122.166,37.581)]
+85th Ave [(-122.173,37.548),(-122.1713,37.556)]
+85th Ave [(-122.1763,37.533),(-122.1748,37.54)]
+85th Ave [(-122.1877,37.466),(-122.186,37.476)]
+87th Ave [(-122.1646,37.561),(-122.1643,37.563)]
+87th Ave [(-122.1698,37.536),(-122.1681,37.544)]
+87th Ave [(-122.1834,37.474),(-122.1814,37.484)]
+88th Ave [(-122.1728,37.514),(-122.1711,37.521)]
+89th Ave [(-122.1822,37.459),(-122.1803,37.471)]
+8th Ave [(-122.2489,37.952),(-122.248,37.958)]
+8th St [(-121.9083,37.161),(-121.9081,37.16)]
+8th St [(-122.2459,37.882),(-122.2456,37.879)]
+8th St [(-122.2546,37.914),(-122.2533,37.909)]
+8th St [(-122.2572,37.935),(-122.256523,37.92898)]
+8th St [(-122.2755,37.017),(-122.2766,37.022)]
+8th St [(-122.2955,37.709),(-122.2952,37.698)]
+8th St [(-122.296,37.748),(-122.296,37.733)]
+8th St [(-122.2969,37.751),(-122.2967,37.746)]
+90th Ave [(-122.1769,37.477),(-122.1753,37.485)]
+90th Ave [(-122.1798,37.464),(-122.1785,37.47)]
+92nd Ave [(-122.1759,37.464),(-122.1742,37.472)]
+94th Ave [(-122.1714,37.466),(-122.1704,37.472)]
+96th Ave [(-122.1621,37.493),(-122.161,37.498)]
+98th Ave [(-122.1568,37.498),(-122.1558,37.502)]
+98th Ave [(-122.1693,37.438),(-122.1682,37.444)]
+98th Ave [(-122.1767,37.401),(-122.1758,37.408)]
+98th Ave [(-122.1783,37.388),(-122.1773,37.396)]
+98th Ave [(-122.1791,37.382),(-122.1788,37.384)]
+98th Ave [(-122.1914,37.294),(-122.1904,37.298)]
+98th Ave [(-122.2001,37.258),(-122.1974,37.27)]
+99th Av Ct [(-122.1698,37.427),(-122.1694,37.422)]
+9th Ave [(-122.2491,37.938),(-122.248,37.945)]
+9th Ave [(-122.2516,37.922),(-122.2511,37.925)]
+9th St [(-122.2349,37.779),(-122.234,37.773)]
+9th St [(-122.27,37.725),(-122.27,37.734)]
+9th St [(-122.2899,37.576),(-122.2897,37.567)]
diff --git a/src/test/regress/data/stud_emp.data b/src/test/regress/data/stud_emp.data
new file mode 100644
index 0000000..4ad7566
--- /dev/null
+++ b/src/test/regress/data/stud_emp.data
@@ -0,0 +1,3 @@
+jeff 23 (8,7.7) 600 sharon 3.50000000000000000e+00 \N
+cim 30 (10.5,4.7) 400 \N 3.39999999999999990e+00 \N
+linda 19 (0.9,6.1) 100 \N 2.89999999999999990e+00 \N
diff --git a/src/test/regress/data/student.data b/src/test/regress/data/student.data
new file mode 100644
index 0000000..f7e29e4
--- /dev/null
+++ b/src/test/regress/data/student.data
@@ -0,0 +1,2 @@
+fred 28 (3.1,-1.5) 3.70000000000000020e+00
+larry 60 (21.8,4.9) 3.10000000000000010e+00
diff --git a/src/test/regress/data/tenk.data b/src/test/regress/data/tenk.data
new file mode 100644
index 0000000..c9064c9
--- /dev/null
+++ b/src/test/regress/data/tenk.data
@@ -0,0 +1,10000 @@
+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
+7695 23 1 3 5 15 95 695 1695 2695 7695 190 191 ZJAAAA XAAAAA VVVVxx
+3442 24 0 2 2 2 42 442 1442 3442 3442 84 85 KCAAAA YAAAAA AAAAxx
+5119 25 1 3 9 19 19 119 1119 119 5119 38 39 XOAAAA ZAAAAA HHHHxx
+646 26 0 2 6 6 46 646 646 646 646 92 93 WYAAAA ABAAAA OOOOxx
+9605 27 1 1 5 5 5 605 1605 4605 9605 10 11 LFAAAA BBAAAA VVVVxx
+263 28 1 3 3 3 63 263 263 263 263 126 127 DKAAAA CBAAAA AAAAxx
+3269 29 1 1 9 9 69 269 1269 3269 3269 138 139 TVAAAA DBAAAA HHHHxx
+1839 30 1 3 9 19 39 839 1839 1839 1839 78 79 TSAAAA EBAAAA OOOOxx
+9144 31 0 0 4 4 44 144 1144 4144 9144 88 89 SNAAAA FBAAAA VVVVxx
+2513 32 1 1 3 13 13 513 513 2513 2513 26 27 RSAAAA GBAAAA AAAAxx
+8850 33 0 2 0 10 50 850 850 3850 8850 100 101 KCAAAA HBAAAA HHHHxx
+236 34 0 0 6 16 36 236 236 236 236 72 73 CJAAAA IBAAAA OOOOxx
+3162 35 0 2 2 2 62 162 1162 3162 3162 124 125 QRAAAA JBAAAA VVVVxx
+4380 36 0 0 0 0 80 380 380 4380 4380 160 161 MMAAAA KBAAAA AAAAxx
+8095 37 1 3 5 15 95 95 95 3095 8095 190 191 JZAAAA LBAAAA HHHHxx
+209 38 1 1 9 9 9 209 209 209 209 18 19 BIAAAA MBAAAA OOOOxx
+3055 39 1 3 5 15 55 55 1055 3055 3055 110 111 NNAAAA NBAAAA VVVVxx
+6921 40 1 1 1 1 21 921 921 1921 6921 42 43 FGAAAA OBAAAA AAAAxx
+7046 41 0 2 6 6 46 46 1046 2046 7046 92 93 ALAAAA PBAAAA HHHHxx
+7912 42 0 0 2 12 12 912 1912 2912 7912 24 25 ISAAAA QBAAAA OOOOxx
+7267 43 1 3 7 7 67 267 1267 2267 7267 134 135 NTAAAA RBAAAA VVVVxx
+3599 44 1 3 9 19 99 599 1599 3599 3599 198 199 LIAAAA SBAAAA AAAAxx
+923 45 1 3 3 3 23 923 923 923 923 46 47 NJAAAA TBAAAA HHHHxx
+1437 46 1 1 7 17 37 437 1437 1437 1437 74 75 HDAAAA UBAAAA OOOOxx
+6439 47 1 3 9 19 39 439 439 1439 6439 78 79 RNAAAA VBAAAA VVVVxx
+6989 48 1 1 9 9 89 989 989 1989 6989 178 179 VIAAAA WBAAAA AAAAxx
+8798 49 0 2 8 18 98 798 798 3798 8798 196 197 KAAAAA XBAAAA HHHHxx
+5960 50 0 0 0 0 60 960 1960 960 5960 120 121 GVAAAA YBAAAA OOOOxx
+5832 51 0 0 2 12 32 832 1832 832 5832 64 65 IQAAAA ZBAAAA VVVVxx
+6066 52 0 2 6 6 66 66 66 1066 6066 132 133 IZAAAA ACAAAA AAAAxx
+322 53 0 2 2 2 22 322 322 322 322 44 45 KMAAAA BCAAAA HHHHxx
+8321 54 1 1 1 1 21 321 321 3321 8321 42 43 BIAAAA CCAAAA OOOOxx
+734 55 0 2 4 14 34 734 734 734 734 68 69 GCAAAA DCAAAA VVVVxx
+688 56 0 0 8 8 88 688 688 688 688 176 177 MAAAAA ECAAAA AAAAxx
+4212 57 0 0 2 12 12 212 212 4212 4212 24 25 AGAAAA FCAAAA HHHHxx
+9653 58 1 1 3 13 53 653 1653 4653 9653 106 107 HHAAAA GCAAAA OOOOxx
+2677 59 1 1 7 17 77 677 677 2677 2677 154 155 ZYAAAA HCAAAA VVVVxx
+5423 60 1 3 3 3 23 423 1423 423 5423 46 47 PAAAAA ICAAAA AAAAxx
+2592 61 0 0 2 12 92 592 592 2592 2592 184 185 SVAAAA JCAAAA HHHHxx
+3233 62 1 1 3 13 33 233 1233 3233 3233 66 67 JUAAAA KCAAAA OOOOxx
+5032 63 0 0 2 12 32 32 1032 32 5032 64 65 OLAAAA LCAAAA VVVVxx
+2525 64 1 1 5 5 25 525 525 2525 2525 50 51 DTAAAA MCAAAA AAAAxx
+4450 65 0 2 0 10 50 450 450 4450 4450 100 101 EPAAAA NCAAAA HHHHxx
+5778 66 0 2 8 18 78 778 1778 778 5778 156 157 GOAAAA OCAAAA OOOOxx
+5852 67 0 0 2 12 52 852 1852 852 5852 104 105 CRAAAA PCAAAA VVVVxx
+5404 68 0 0 4 4 4 404 1404 404 5404 8 9 WZAAAA QCAAAA AAAAxx
+6223 69 1 3 3 3 23 223 223 1223 6223 46 47 JFAAAA RCAAAA HHHHxx
+6133 70 1 1 3 13 33 133 133 1133 6133 66 67 XBAAAA SCAAAA OOOOxx
+9112 71 0 0 2 12 12 112 1112 4112 9112 24 25 MMAAAA TCAAAA VVVVxx
+7575 72 1 3 5 15 75 575 1575 2575 7575 150 151 JFAAAA UCAAAA AAAAxx
+7414 73 0 2 4 14 14 414 1414 2414 7414 28 29 EZAAAA VCAAAA HHHHxx
+9741 74 1 1 1 1 41 741 1741 4741 9741 82 83 RKAAAA WCAAAA OOOOxx
+3767 75 1 3 7 7 67 767 1767 3767 3767 134 135 XOAAAA XCAAAA VVVVxx
+9372 76 0 0 2 12 72 372 1372 4372 9372 144 145 MWAAAA YCAAAA AAAAxx
+8976 77 0 0 6 16 76 976 976 3976 8976 152 153 GHAAAA ZCAAAA HHHHxx
+4071 78 1 3 1 11 71 71 71 4071 4071 142 143 PAAAAA ADAAAA OOOOxx
+1311 79 1 3 1 11 11 311 1311 1311 1311 22 23 LYAAAA BDAAAA VVVVxx
+2604 80 0 0 4 4 4 604 604 2604 2604 8 9 EWAAAA CDAAAA AAAAxx
+8840 81 0 0 0 0 40 840 840 3840 8840 80 81 ACAAAA DDAAAA HHHHxx
+567 82 1 3 7 7 67 567 567 567 567 134 135 VVAAAA EDAAAA OOOOxx
+5215 83 1 3 5 15 15 215 1215 215 5215 30 31 PSAAAA FDAAAA VVVVxx
+5474 84 0 2 4 14 74 474 1474 474 5474 148 149 OCAAAA GDAAAA AAAAxx
+3906 85 0 2 6 6 6 906 1906 3906 3906 12 13 GUAAAA HDAAAA HHHHxx
+1769 86 1 1 9 9 69 769 1769 1769 1769 138 139 BQAAAA IDAAAA OOOOxx
+1454 87 0 2 4 14 54 454 1454 1454 1454 108 109 YDAAAA JDAAAA VVVVxx
+6877 88 1 1 7 17 77 877 877 1877 6877 154 155 NEAAAA KDAAAA AAAAxx
+6501 89 1 1 1 1 1 501 501 1501 6501 2 3 BQAAAA LDAAAA HHHHxx
+934 90 0 2 4 14 34 934 934 934 934 68 69 YJAAAA MDAAAA OOOOxx
+4075 91 1 3 5 15 75 75 75 4075 4075 150 151 TAAAAA NDAAAA VVVVxx
+3180 92 0 0 0 0 80 180 1180 3180 3180 160 161 ISAAAA ODAAAA AAAAxx
+7787 93 1 3 7 7 87 787 1787 2787 7787 174 175 NNAAAA PDAAAA HHHHxx
+6401 94 1 1 1 1 1 401 401 1401 6401 2 3 FMAAAA QDAAAA OOOOxx
+4244 95 0 0 4 4 44 244 244 4244 4244 88 89 GHAAAA RDAAAA VVVVxx
+4591 96 1 3 1 11 91 591 591 4591 4591 182 183 PUAAAA SDAAAA AAAAxx
+4113 97 1 1 3 13 13 113 113 4113 4113 26 27 FCAAAA TDAAAA HHHHxx
+5925 98 1 1 5 5 25 925 1925 925 5925 50 51 XTAAAA UDAAAA OOOOxx
+1987 99 1 3 7 7 87 987 1987 1987 1987 174 175 LYAAAA VDAAAA VVVVxx
+8248 100 0 0 8 8 48 248 248 3248 8248 96 97 GFAAAA WDAAAA AAAAxx
+4151 101 1 3 1 11 51 151 151 4151 4151 102 103 RDAAAA XDAAAA HHHHxx
+8670 102 0 2 0 10 70 670 670 3670 8670 140 141 MVAAAA YDAAAA OOOOxx
+6194 103 0 2 4 14 94 194 194 1194 6194 188 189 GEAAAA ZDAAAA VVVVxx
+88 104 0 0 8 8 88 88 88 88 88 176 177 KDAAAA AEAAAA AAAAxx
+4058 105 0 2 8 18 58 58 58 4058 4058 116 117 CAAAAA BEAAAA HHHHxx
+2742 106 0 2 2 2 42 742 742 2742 2742 84 85 MBAAAA CEAAAA OOOOxx
+8275 107 1 3 5 15 75 275 275 3275 8275 150 151 HGAAAA DEAAAA VVVVxx
+4258 108 0 2 8 18 58 258 258 4258 4258 116 117 UHAAAA EEAAAA AAAAxx
+6129 109 1 1 9 9 29 129 129 1129 6129 58 59 TBAAAA FEAAAA HHHHxx
+7243 110 1 3 3 3 43 243 1243 2243 7243 86 87 PSAAAA GEAAAA OOOOxx
+2392 111 0 0 2 12 92 392 392 2392 2392 184 185 AOAAAA HEAAAA VVVVxx
+9853 112 1 1 3 13 53 853 1853 4853 9853 106 107 ZOAAAA IEAAAA AAAAxx
+6064 113 0 0 4 4 64 64 64 1064 6064 128 129 GZAAAA JEAAAA HHHHxx
+4391 114 1 3 1 11 91 391 391 4391 4391 182 183 XMAAAA KEAAAA OOOOxx
+726 115 0 2 6 6 26 726 726 726 726 52 53 YBAAAA LEAAAA VVVVxx
+6957 116 1 1 7 17 57 957 957 1957 6957 114 115 PHAAAA MEAAAA AAAAxx
+3853 117 1 1 3 13 53 853 1853 3853 3853 106 107 FSAAAA NEAAAA HHHHxx
+4524 118 0 0 4 4 24 524 524 4524 4524 48 49 ASAAAA OEAAAA OOOOxx
+5330 119 0 2 0 10 30 330 1330 330 5330 60 61 AXAAAA PEAAAA VVVVxx
+6671 120 1 3 1 11 71 671 671 1671 6671 142 143 PWAAAA QEAAAA AAAAxx
+5314 121 0 2 4 14 14 314 1314 314 5314 28 29 KWAAAA REAAAA HHHHxx
+9202 122 0 2 2 2 2 202 1202 4202 9202 4 5 YPAAAA SEAAAA OOOOxx
+4596 123 0 0 6 16 96 596 596 4596 4596 192 193 UUAAAA TEAAAA VVVVxx
+8951 124 1 3 1 11 51 951 951 3951 8951 102 103 HGAAAA UEAAAA AAAAxx
+9902 125 0 2 2 2 2 902 1902 4902 9902 4 5 WQAAAA VEAAAA HHHHxx
+1440 126 0 0 0 0 40 440 1440 1440 1440 80 81 KDAAAA WEAAAA OOOOxx
+5339 127 1 3 9 19 39 339 1339 339 5339 78 79 JXAAAA XEAAAA VVVVxx
+3371 128 1 3 1 11 71 371 1371 3371 3371 142 143 RZAAAA YEAAAA AAAAxx
+4467 129 1 3 7 7 67 467 467 4467 4467 134 135 VPAAAA ZEAAAA HHHHxx
+6216 130 0 0 6 16 16 216 216 1216 6216 32 33 CFAAAA AFAAAA OOOOxx
+5364 131 0 0 4 4 64 364 1364 364 5364 128 129 IYAAAA BFAAAA VVVVxx
+7547 132 1 3 7 7 47 547 1547 2547 7547 94 95 HEAAAA CFAAAA AAAAxx
+4338 133 0 2 8 18 38 338 338 4338 4338 76 77 WKAAAA DFAAAA HHHHxx
+3481 134 1 1 1 1 81 481 1481 3481 3481 162 163 XDAAAA EFAAAA OOOOxx
+826 135 0 2 6 6 26 826 826 826 826 52 53 UFAAAA FFAAAA VVVVxx
+3647 136 1 3 7 7 47 647 1647 3647 3647 94 95 HKAAAA GFAAAA AAAAxx
+3337 137 1 1 7 17 37 337 1337 3337 3337 74 75 JYAAAA HFAAAA HHHHxx
+3591 138 1 3 1 11 91 591 1591 3591 3591 182 183 DIAAAA IFAAAA OOOOxx
+7192 139 0 0 2 12 92 192 1192 2192 7192 184 185 QQAAAA JFAAAA VVVVxx
+1078 140 0 2 8 18 78 78 1078 1078 1078 156 157 MPAAAA KFAAAA AAAAxx
+1310 141 0 2 0 10 10 310 1310 1310 1310 20 21 KYAAAA LFAAAA HHHHxx
+9642 142 0 2 2 2 42 642 1642 4642 9642 84 85 WGAAAA MFAAAA OOOOxx
+39 143 1 3 9 19 39 39 39 39 39 78 79 NBAAAA NFAAAA VVVVxx
+8682 144 0 2 2 2 82 682 682 3682 8682 164 165 YVAAAA OFAAAA AAAAxx
+1794 145 0 2 4 14 94 794 1794 1794 1794 188 189 ARAAAA PFAAAA HHHHxx
+5630 146 0 2 0 10 30 630 1630 630 5630 60 61 OIAAAA QFAAAA OOOOxx
+6748 147 0 0 8 8 48 748 748 1748 6748 96 97 OZAAAA RFAAAA VVVVxx
+3766 148 0 2 6 6 66 766 1766 3766 3766 132 133 WOAAAA SFAAAA AAAAxx
+6403 149 1 3 3 3 3 403 403 1403 6403 6 7 HMAAAA TFAAAA HHHHxx
+175 150 1 3 5 15 75 175 175 175 175 150 151 TGAAAA UFAAAA OOOOxx
+2179 151 1 3 9 19 79 179 179 2179 2179 158 159 VFAAAA VFAAAA VVVVxx
+7897 152 1 1 7 17 97 897 1897 2897 7897 194 195 TRAAAA WFAAAA AAAAxx
+2760 153 0 0 0 0 60 760 760 2760 2760 120 121 ECAAAA XFAAAA HHHHxx
+1675 154 1 3 5 15 75 675 1675 1675 1675 150 151 LMAAAA YFAAAA OOOOxx
+2564 155 0 0 4 4 64 564 564 2564 2564 128 129 QUAAAA ZFAAAA VVVVxx
+157 156 1 1 7 17 57 157 157 157 157 114 115 BGAAAA AGAAAA AAAAxx
+8779 157 1 3 9 19 79 779 779 3779 8779 158 159 RZAAAA BGAAAA HHHHxx
+9591 158 1 3 1 11 91 591 1591 4591 9591 182 183 XEAAAA CGAAAA OOOOxx
+8732 159 0 0 2 12 32 732 732 3732 8732 64 65 WXAAAA DGAAAA VVVVxx
+139 160 1 3 9 19 39 139 139 139 139 78 79 JFAAAA EGAAAA AAAAxx
+5372 161 0 0 2 12 72 372 1372 372 5372 144 145 QYAAAA FGAAAA HHHHxx
+1278 162 0 2 8 18 78 278 1278 1278 1278 156 157 EXAAAA GGAAAA OOOOxx
+4697 163 1 1 7 17 97 697 697 4697 4697 194 195 RYAAAA HGAAAA VVVVxx
+8610 164 0 2 0 10 10 610 610 3610 8610 20 21 ETAAAA IGAAAA AAAAxx
+8180 165 0 0 0 0 80 180 180 3180 8180 160 161 QCAAAA JGAAAA HHHHxx
+2399 166 1 3 9 19 99 399 399 2399 2399 198 199 HOAAAA KGAAAA OOOOxx
+615 167 1 3 5 15 15 615 615 615 615 30 31 RXAAAA LGAAAA VVVVxx
+7629 168 1 1 9 9 29 629 1629 2629 7629 58 59 LHAAAA MGAAAA AAAAxx
+7628 169 0 0 8 8 28 628 1628 2628 7628 56 57 KHAAAA NGAAAA HHHHxx
+4659 170 1 3 9 19 59 659 659 4659 4659 118 119 FXAAAA OGAAAA OOOOxx
+5865 171 1 1 5 5 65 865 1865 865 5865 130 131 PRAAAA PGAAAA VVVVxx
+3973 172 1 1 3 13 73 973 1973 3973 3973 146 147 VWAAAA QGAAAA AAAAxx
+552 173 0 0 2 12 52 552 552 552 552 104 105 GVAAAA RGAAAA HHHHxx
+708 174 0 0 8 8 8 708 708 708 708 16 17 GBAAAA SGAAAA OOOOxx
+3550 175 0 2 0 10 50 550 1550 3550 3550 100 101 OGAAAA TGAAAA VVVVxx
+5547 176 1 3 7 7 47 547 1547 547 5547 94 95 JFAAAA UGAAAA AAAAxx
+489 177 1 1 9 9 89 489 489 489 489 178 179 VSAAAA VGAAAA HHHHxx
+3794 178 0 2 4 14 94 794 1794 3794 3794 188 189 YPAAAA WGAAAA OOOOxx
+9479 179 1 3 9 19 79 479 1479 4479 9479 158 159 PAAAAA XGAAAA VVVVxx
+6435 180 1 3 5 15 35 435 435 1435 6435 70 71 NNAAAA YGAAAA AAAAxx
+5120 181 0 0 0 0 20 120 1120 120 5120 40 41 YOAAAA ZGAAAA HHHHxx
+3615 182 1 3 5 15 15 615 1615 3615 3615 30 31 BJAAAA AHAAAA OOOOxx
+8399 183 1 3 9 19 99 399 399 3399 8399 198 199 BLAAAA BHAAAA VVVVxx
+2155 184 1 3 5 15 55 155 155 2155 2155 110 111 XEAAAA CHAAAA AAAAxx
+6690 185 0 2 0 10 90 690 690 1690 6690 180 181 IXAAAA DHAAAA HHHHxx
+1683 186 1 3 3 3 83 683 1683 1683 1683 166 167 TMAAAA EHAAAA OOOOxx
+6302 187 0 2 2 2 2 302 302 1302 6302 4 5 KIAAAA FHAAAA VVVVxx
+516 188 0 0 6 16 16 516 516 516 516 32 33 WTAAAA GHAAAA AAAAxx
+3901 189 1 1 1 1 1 901 1901 3901 3901 2 3 BUAAAA HHAAAA HHHHxx
+6938 190 0 2 8 18 38 938 938 1938 6938 76 77 WGAAAA IHAAAA OOOOxx
+7484 191 0 0 4 4 84 484 1484 2484 7484 168 169 WBAAAA JHAAAA VVVVxx
+7424 192 0 0 4 4 24 424 1424 2424 7424 48 49 OZAAAA KHAAAA AAAAxx
+9410 193 0 2 0 10 10 410 1410 4410 9410 20 21 YXAAAA LHAAAA HHHHxx
+1714 194 0 2 4 14 14 714 1714 1714 1714 28 29 YNAAAA MHAAAA OOOOxx
+8278 195 0 2 8 18 78 278 278 3278 8278 156 157 KGAAAA NHAAAA VVVVxx
+3158 196 0 2 8 18 58 158 1158 3158 3158 116 117 MRAAAA OHAAAA AAAAxx
+2511 197 1 3 1 11 11 511 511 2511 2511 22 23 PSAAAA PHAAAA HHHHxx
+2912 198 0 0 2 12 12 912 912 2912 2912 24 25 AIAAAA QHAAAA OOOOxx
+2648 199 0 0 8 8 48 648 648 2648 2648 96 97 WXAAAA RHAAAA VVVVxx
+9385 200 1 1 5 5 85 385 1385 4385 9385 170 171 ZWAAAA SHAAAA AAAAxx
+7545 201 1 1 5 5 45 545 1545 2545 7545 90 91 FEAAAA THAAAA HHHHxx
+8407 202 1 3 7 7 7 407 407 3407 8407 14 15 JLAAAA UHAAAA OOOOxx
+5893 203 1 1 3 13 93 893 1893 893 5893 186 187 RSAAAA VHAAAA VVVVxx
+7049 204 1 1 9 9 49 49 1049 2049 7049 98 99 DLAAAA WHAAAA AAAAxx
+6812 205 0 0 2 12 12 812 812 1812 6812 24 25 ACAAAA XHAAAA HHHHxx
+3649 206 1 1 9 9 49 649 1649 3649 3649 98 99 JKAAAA YHAAAA OOOOxx
+9275 207 1 3 5 15 75 275 1275 4275 9275 150 151 TSAAAA ZHAAAA VVVVxx
+1179 208 1 3 9 19 79 179 1179 1179 1179 158 159 JTAAAA AIAAAA AAAAxx
+969 209 1 1 9 9 69 969 969 969 969 138 139 HLAAAA BIAAAA HHHHxx
+7920 210 0 0 0 0 20 920 1920 2920 7920 40 41 QSAAAA CIAAAA OOOOxx
+998 211 0 2 8 18 98 998 998 998 998 196 197 KMAAAA DIAAAA VVVVxx
+3958 212 0 2 8 18 58 958 1958 3958 3958 116 117 GWAAAA EIAAAA AAAAxx
+6052 213 0 0 2 12 52 52 52 1052 6052 104 105 UYAAAA FIAAAA HHHHxx
+8791 214 1 3 1 11 91 791 791 3791 8791 182 183 DAAAAA GIAAAA OOOOxx
+5191 215 1 3 1 11 91 191 1191 191 5191 182 183 RRAAAA HIAAAA VVVVxx
+4267 216 1 3 7 7 67 267 267 4267 4267 134 135 DIAAAA IIAAAA AAAAxx
+2829 217 1 1 9 9 29 829 829 2829 2829 58 59 VEAAAA JIAAAA HHHHxx
+6396 218 0 0 6 16 96 396 396 1396 6396 192 193 AMAAAA KIAAAA OOOOxx
+9413 219 1 1 3 13 13 413 1413 4413 9413 26 27 BYAAAA LIAAAA VVVVxx
+614 220 0 2 4 14 14 614 614 614 614 28 29 QXAAAA MIAAAA AAAAxx
+4660 221 0 0 0 0 60 660 660 4660 4660 120 121 GXAAAA NIAAAA HHHHxx
+8834 222 0 2 4 14 34 834 834 3834 8834 68 69 UBAAAA OIAAAA OOOOxx
+2767 223 1 3 7 7 67 767 767 2767 2767 134 135 LCAAAA PIAAAA VVVVxx
+2444 224 0 0 4 4 44 444 444 2444 2444 88 89 AQAAAA QIAAAA AAAAxx
+4129 225 1 1 9 9 29 129 129 4129 4129 58 59 VCAAAA RIAAAA HHHHxx
+3394 226 0 2 4 14 94 394 1394 3394 3394 188 189 OAAAAA SIAAAA OOOOxx
+2705 227 1 1 5 5 5 705 705 2705 2705 10 11 BAAAAA TIAAAA VVVVxx
+8499 228 1 3 9 19 99 499 499 3499 8499 198 199 XOAAAA UIAAAA AAAAxx
+8852 229 0 0 2 12 52 852 852 3852 8852 104 105 MCAAAA VIAAAA HHHHxx
+6174 230 0 2 4 14 74 174 174 1174 6174 148 149 MDAAAA WIAAAA OOOOxx
+750 231 0 2 0 10 50 750 750 750 750 100 101 WCAAAA XIAAAA VVVVxx
+8164 232 0 0 4 4 64 164 164 3164 8164 128 129 ACAAAA YIAAAA AAAAxx
+4930 233 0 2 0 10 30 930 930 4930 4930 60 61 QHAAAA ZIAAAA HHHHxx
+9904 234 0 0 4 4 4 904 1904 4904 9904 8 9 YQAAAA AJAAAA OOOOxx
+7378 235 0 2 8 18 78 378 1378 2378 7378 156 157 UXAAAA BJAAAA VVVVxx
+2927 236 1 3 7 7 27 927 927 2927 2927 54 55 PIAAAA CJAAAA AAAAxx
+7155 237 1 3 5 15 55 155 1155 2155 7155 110 111 FPAAAA DJAAAA HHHHxx
+1302 238 0 2 2 2 2 302 1302 1302 1302 4 5 CYAAAA EJAAAA OOOOxx
+5904 239 0 0 4 4 4 904 1904 904 5904 8 9 CTAAAA FJAAAA VVVVxx
+9687 240 1 3 7 7 87 687 1687 4687 9687 174 175 PIAAAA GJAAAA AAAAxx
+3553 241 1 1 3 13 53 553 1553 3553 3553 106 107 RGAAAA HJAAAA HHHHxx
+4447 242 1 3 7 7 47 447 447 4447 4447 94 95 BPAAAA IJAAAA OOOOxx
+6878 243 0 2 8 18 78 878 878 1878 6878 156 157 OEAAAA JJAAAA VVVVxx
+9470 244 0 2 0 10 70 470 1470 4470 9470 140 141 GAAAAA KJAAAA AAAAxx
+9735 245 1 3 5 15 35 735 1735 4735 9735 70 71 LKAAAA LJAAAA HHHHxx
+5967 246 1 3 7 7 67 967 1967 967 5967 134 135 NVAAAA MJAAAA OOOOxx
+6601 247 1 1 1 1 1 601 601 1601 6601 2 3 XTAAAA NJAAAA VVVVxx
+7631 248 1 3 1 11 31 631 1631 2631 7631 62 63 NHAAAA OJAAAA AAAAxx
+3559 249 1 3 9 19 59 559 1559 3559 3559 118 119 XGAAAA PJAAAA HHHHxx
+2247 250 1 3 7 7 47 247 247 2247 2247 94 95 LIAAAA QJAAAA OOOOxx
+9649 251 1 1 9 9 49 649 1649 4649 9649 98 99 DHAAAA RJAAAA VVVVxx
+808 252 0 0 8 8 8 808 808 808 808 16 17 CFAAAA SJAAAA AAAAxx
+240 253 0 0 0 0 40 240 240 240 240 80 81 GJAAAA TJAAAA HHHHxx
+5031 254 1 3 1 11 31 31 1031 31 5031 62 63 NLAAAA UJAAAA OOOOxx
+9563 255 1 3 3 3 63 563 1563 4563 9563 126 127 VDAAAA VJAAAA VVVVxx
+5656 256 0 0 6 16 56 656 1656 656 5656 112 113 OJAAAA WJAAAA AAAAxx
+3886 257 0 2 6 6 86 886 1886 3886 3886 172 173 MTAAAA XJAAAA HHHHxx
+2431 258 1 3 1 11 31 431 431 2431 2431 62 63 NPAAAA YJAAAA OOOOxx
+5560 259 0 0 0 0 60 560 1560 560 5560 120 121 WFAAAA ZJAAAA VVVVxx
+9065 260 1 1 5 5 65 65 1065 4065 9065 130 131 RKAAAA AKAAAA AAAAxx
+8130 261 0 2 0 10 30 130 130 3130 8130 60 61 SAAAAA BKAAAA HHHHxx
+4054 262 0 2 4 14 54 54 54 4054 4054 108 109 YZAAAA CKAAAA OOOOxx
+873 263 1 1 3 13 73 873 873 873 873 146 147 PHAAAA DKAAAA VVVVxx
+3092 264 0 0 2 12 92 92 1092 3092 3092 184 185 YOAAAA EKAAAA AAAAxx
+6697 265 1 1 7 17 97 697 697 1697 6697 194 195 PXAAAA FKAAAA HHHHxx
+2452 266 0 0 2 12 52 452 452 2452 2452 104 105 IQAAAA GKAAAA OOOOxx
+7867 267 1 3 7 7 67 867 1867 2867 7867 134 135 PQAAAA HKAAAA VVVVxx
+3753 268 1 1 3 13 53 753 1753 3753 3753 106 107 JOAAAA IKAAAA AAAAxx
+7834 269 0 2 4 14 34 834 1834 2834 7834 68 69 IPAAAA JKAAAA HHHHxx
+5846 270 0 2 6 6 46 846 1846 846 5846 92 93 WQAAAA KKAAAA OOOOxx
+7604 271 0 0 4 4 4 604 1604 2604 7604 8 9 MGAAAA LKAAAA VVVVxx
+3452 272 0 0 2 12 52 452 1452 3452 3452 104 105 UCAAAA MKAAAA AAAAxx
+4788 273 0 0 8 8 88 788 788 4788 4788 176 177 ECAAAA NKAAAA HHHHxx
+8600 274 0 0 0 0 0 600 600 3600 8600 0 1 USAAAA OKAAAA OOOOxx
+8511 275 1 3 1 11 11 511 511 3511 8511 22 23 JPAAAA PKAAAA VVVVxx
+4452 276 0 0 2 12 52 452 452 4452 4452 104 105 GPAAAA QKAAAA AAAAxx
+1709 277 1 1 9 9 9 709 1709 1709 1709 18 19 TNAAAA RKAAAA HHHHxx
+3440 278 0 0 0 0 40 440 1440 3440 3440 80 81 ICAAAA SKAAAA OOOOxx
+9188 279 0 0 8 8 88 188 1188 4188 9188 176 177 KPAAAA TKAAAA VVVVxx
+3058 280 0 2 8 18 58 58 1058 3058 3058 116 117 QNAAAA UKAAAA AAAAxx
+5821 281 1 1 1 1 21 821 1821 821 5821 42 43 XPAAAA VKAAAA HHHHxx
+3428 282 0 0 8 8 28 428 1428 3428 3428 56 57 WBAAAA WKAAAA OOOOxx
+3581 283 1 1 1 1 81 581 1581 3581 3581 162 163 THAAAA XKAAAA VVVVxx
+7523 284 1 3 3 3 23 523 1523 2523 7523 46 47 JDAAAA YKAAAA AAAAxx
+3131 285 1 3 1 11 31 131 1131 3131 3131 62 63 LQAAAA ZKAAAA HHHHxx
+2404 286 0 0 4 4 4 404 404 2404 2404 8 9 MOAAAA ALAAAA OOOOxx
+5453 287 1 1 3 13 53 453 1453 453 5453 106 107 TBAAAA BLAAAA VVVVxx
+1599 288 1 3 9 19 99 599 1599 1599 1599 198 199 NJAAAA CLAAAA AAAAxx
+7081 289 1 1 1 1 81 81 1081 2081 7081 162 163 JMAAAA DLAAAA HHHHxx
+1750 290 0 2 0 10 50 750 1750 1750 1750 100 101 IPAAAA ELAAAA OOOOxx
+5085 291 1 1 5 5 85 85 1085 85 5085 170 171 PNAAAA FLAAAA VVVVxx
+9777 292 1 1 7 17 77 777 1777 4777 9777 154 155 BMAAAA GLAAAA AAAAxx
+574 293 0 2 4 14 74 574 574 574 574 148 149 CWAAAA HLAAAA HHHHxx
+5984 294 0 0 4 4 84 984 1984 984 5984 168 169 EWAAAA ILAAAA OOOOxx
+7039 295 1 3 9 19 39 39 1039 2039 7039 78 79 TKAAAA JLAAAA VVVVxx
+7143 296 1 3 3 3 43 143 1143 2143 7143 86 87 TOAAAA KLAAAA AAAAxx
+5702 297 0 2 2 2 2 702 1702 702 5702 4 5 ILAAAA LLAAAA HHHHxx
+362 298 0 2 2 2 62 362 362 362 362 124 125 YNAAAA MLAAAA OOOOxx
+6997 299 1 1 7 17 97 997 997 1997 6997 194 195 DJAAAA NLAAAA VVVVxx
+2529 300 1 1 9 9 29 529 529 2529 2529 58 59 HTAAAA OLAAAA AAAAxx
+6319 301 1 3 9 19 19 319 319 1319 6319 38 39 BJAAAA PLAAAA HHHHxx
+954 302 0 2 4 14 54 954 954 954 954 108 109 SKAAAA QLAAAA OOOOxx
+3413 303 1 1 3 13 13 413 1413 3413 3413 26 27 HBAAAA RLAAAA VVVVxx
+9081 304 1 1 1 1 81 81 1081 4081 9081 162 163 HLAAAA SLAAAA AAAAxx
+5599 305 1 3 9 19 99 599 1599 599 5599 198 199 JHAAAA TLAAAA HHHHxx
+4772 306 0 0 2 12 72 772 772 4772 4772 144 145 OBAAAA ULAAAA OOOOxx
+1124 307 0 0 4 4 24 124 1124 1124 1124 48 49 GRAAAA VLAAAA VVVVxx
+7793 308 1 1 3 13 93 793 1793 2793 7793 186 187 TNAAAA WLAAAA AAAAxx
+4201 309 1 1 1 1 1 201 201 4201 4201 2 3 PFAAAA XLAAAA HHHHxx
+7015 310 1 3 5 15 15 15 1015 2015 7015 30 31 VJAAAA YLAAAA OOOOxx
+5936 311 0 0 6 16 36 936 1936 936 5936 72 73 IUAAAA ZLAAAA VVVVxx
+4625 312 1 1 5 5 25 625 625 4625 4625 50 51 XVAAAA AMAAAA AAAAxx
+4989 313 1 1 9 9 89 989 989 4989 4989 178 179 XJAAAA BMAAAA HHHHxx
+4949 314 1 1 9 9 49 949 949 4949 4949 98 99 JIAAAA CMAAAA OOOOxx
+6273 315 1 1 3 13 73 273 273 1273 6273 146 147 HHAAAA DMAAAA VVVVxx
+4478 316 0 2 8 18 78 478 478 4478 4478 156 157 GQAAAA EMAAAA AAAAxx
+8854 317 0 2 4 14 54 854 854 3854 8854 108 109 OCAAAA FMAAAA HHHHxx
+2105 318 1 1 5 5 5 105 105 2105 2105 10 11 ZCAAAA GMAAAA OOOOxx
+8345 319 1 1 5 5 45 345 345 3345 8345 90 91 ZIAAAA HMAAAA VVVVxx
+1941 320 1 1 1 1 41 941 1941 1941 1941 82 83 RWAAAA IMAAAA AAAAxx
+1765 321 1 1 5 5 65 765 1765 1765 1765 130 131 XPAAAA JMAAAA HHHHxx
+9592 322 0 0 2 12 92 592 1592 4592 9592 184 185 YEAAAA KMAAAA OOOOxx
+1694 323 0 2 4 14 94 694 1694 1694 1694 188 189 ENAAAA LMAAAA VVVVxx
+8940 324 0 0 0 0 40 940 940 3940 8940 80 81 WFAAAA MMAAAA AAAAxx
+7264 325 0 0 4 4 64 264 1264 2264 7264 128 129 KTAAAA NMAAAA HHHHxx
+4699 326 1 3 9 19 99 699 699 4699 4699 198 199 TYAAAA OMAAAA OOOOxx
+4541 327 1 1 1 1 41 541 541 4541 4541 82 83 RSAAAA PMAAAA VVVVxx
+5768 328 0 0 8 8 68 768 1768 768 5768 136 137 WNAAAA QMAAAA AAAAxx
+6183 329 1 3 3 3 83 183 183 1183 6183 166 167 VDAAAA RMAAAA HHHHxx
+7457 330 1 1 7 17 57 457 1457 2457 7457 114 115 VAAAAA SMAAAA OOOOxx
+7317 331 1 1 7 17 17 317 1317 2317 7317 34 35 LVAAAA TMAAAA VVVVxx
+1944 332 0 0 4 4 44 944 1944 1944 1944 88 89 UWAAAA UMAAAA AAAAxx
+665 333 1 1 5 5 65 665 665 665 665 130 131 PZAAAA VMAAAA HHHHxx
+5974 334 0 2 4 14 74 974 1974 974 5974 148 149 UVAAAA WMAAAA OOOOxx
+7370 335 0 2 0 10 70 370 1370 2370 7370 140 141 MXAAAA XMAAAA VVVVxx
+9196 336 0 0 6 16 96 196 1196 4196 9196 192 193 SPAAAA YMAAAA AAAAxx
+6796 337 0 0 6 16 96 796 796 1796 6796 192 193 KBAAAA ZMAAAA HHHHxx
+6180 338 0 0 0 0 80 180 180 1180 6180 160 161 SDAAAA ANAAAA OOOOxx
+8557 339 1 1 7 17 57 557 557 3557 8557 114 115 DRAAAA BNAAAA VVVVxx
+928 340 0 0 8 8 28 928 928 928 928 56 57 SJAAAA CNAAAA AAAAxx
+6275 341 1 3 5 15 75 275 275 1275 6275 150 151 JHAAAA DNAAAA HHHHxx
+409 342 1 1 9 9 9 409 409 409 409 18 19 TPAAAA ENAAAA OOOOxx
+6442 343 0 2 2 2 42 442 442 1442 6442 84 85 UNAAAA FNAAAA VVVVxx
+5889 344 1 1 9 9 89 889 1889 889 5889 178 179 NSAAAA GNAAAA AAAAxx
+5180 345 0 0 0 0 80 180 1180 180 5180 160 161 GRAAAA HNAAAA HHHHxx
+1629 346 1 1 9 9 29 629 1629 1629 1629 58 59 RKAAAA INAAAA OOOOxx
+6088 347 0 0 8 8 88 88 88 1088 6088 176 177 EAAAAA JNAAAA VVVVxx
+5598 348 0 2 8 18 98 598 1598 598 5598 196 197 IHAAAA KNAAAA AAAAxx
+1803 349 1 3 3 3 3 803 1803 1803 1803 6 7 JRAAAA LNAAAA HHHHxx
+2330 350 0 2 0 10 30 330 330 2330 2330 60 61 QLAAAA MNAAAA OOOOxx
+5901 351 1 1 1 1 1 901 1901 901 5901 2 3 ZSAAAA NNAAAA VVVVxx
+780 352 0 0 0 0 80 780 780 780 780 160 161 AEAAAA ONAAAA AAAAxx
+7171 353 1 3 1 11 71 171 1171 2171 7171 142 143 VPAAAA PNAAAA HHHHxx
+8778 354 0 2 8 18 78 778 778 3778 8778 156 157 QZAAAA QNAAAA OOOOxx
+6622 355 0 2 2 2 22 622 622 1622 6622 44 45 SUAAAA RNAAAA VVVVxx
+9938 356 0 2 8 18 38 938 1938 4938 9938 76 77 GSAAAA SNAAAA AAAAxx
+8254 357 0 2 4 14 54 254 254 3254 8254 108 109 MFAAAA TNAAAA HHHHxx
+1951 358 1 3 1 11 51 951 1951 1951 1951 102 103 BXAAAA UNAAAA OOOOxx
+1434 359 0 2 4 14 34 434 1434 1434 1434 68 69 EDAAAA VNAAAA VVVVxx
+7539 360 1 3 9 19 39 539 1539 2539 7539 78 79 ZDAAAA WNAAAA AAAAxx
+600 361 0 0 0 0 0 600 600 600 600 0 1 CXAAAA XNAAAA HHHHxx
+3122 362 0 2 2 2 22 122 1122 3122 3122 44 45 CQAAAA YNAAAA OOOOxx
+5704 363 0 0 4 4 4 704 1704 704 5704 8 9 KLAAAA ZNAAAA VVVVxx
+6300 364 0 0 0 0 0 300 300 1300 6300 0 1 IIAAAA AOAAAA AAAAxx
+4585 365 1 1 5 5 85 585 585 4585 4585 170 171 JUAAAA BOAAAA HHHHxx
+6313 366 1 1 3 13 13 313 313 1313 6313 26 27 VIAAAA COAAAA OOOOxx
+3154 367 0 2 4 14 54 154 1154 3154 3154 108 109 IRAAAA DOAAAA VVVVxx
+642 368 0 2 2 2 42 642 642 642 642 84 85 SYAAAA EOAAAA AAAAxx
+7736 369 0 0 6 16 36 736 1736 2736 7736 72 73 OLAAAA FOAAAA HHHHxx
+5087 370 1 3 7 7 87 87 1087 87 5087 174 175 RNAAAA GOAAAA OOOOxx
+5708 371 0 0 8 8 8 708 1708 708 5708 16 17 OLAAAA HOAAAA VVVVxx
+8169 372 1 1 9 9 69 169 169 3169 8169 138 139 FCAAAA IOAAAA AAAAxx
+9768 373 0 0 8 8 68 768 1768 4768 9768 136 137 SLAAAA JOAAAA HHHHxx
+3874 374 0 2 4 14 74 874 1874 3874 3874 148 149 ATAAAA KOAAAA OOOOxx
+6831 375 1 3 1 11 31 831 831 1831 6831 62 63 TCAAAA LOAAAA VVVVxx
+18 376 0 2 8 18 18 18 18 18 18 36 37 SAAAAA MOAAAA AAAAxx
+6375 377 1 3 5 15 75 375 375 1375 6375 150 151 FLAAAA NOAAAA HHHHxx
+7106 378 0 2 6 6 6 106 1106 2106 7106 12 13 INAAAA OOAAAA OOOOxx
+5926 379 0 2 6 6 26 926 1926 926 5926 52 53 YTAAAA POAAAA VVVVxx
+4956 380 0 0 6 16 56 956 956 4956 4956 112 113 QIAAAA QOAAAA AAAAxx
+7042 381 0 2 2 2 42 42 1042 2042 7042 84 85 WKAAAA ROAAAA HHHHxx
+6043 382 1 3 3 3 43 43 43 1043 6043 86 87 LYAAAA SOAAAA OOOOxx
+2084 383 0 0 4 4 84 84 84 2084 2084 168 169 ECAAAA TOAAAA VVVVxx
+6038 384 0 2 8 18 38 38 38 1038 6038 76 77 GYAAAA UOAAAA AAAAxx
+7253 385 1 1 3 13 53 253 1253 2253 7253 106 107 ZSAAAA VOAAAA HHHHxx
+2061 386 1 1 1 1 61 61 61 2061 2061 122 123 HBAAAA WOAAAA OOOOxx
+7800 387 0 0 0 0 0 800 1800 2800 7800 0 1 AOAAAA XOAAAA VVVVxx
+4970 388 0 2 0 10 70 970 970 4970 4970 140 141 EJAAAA YOAAAA AAAAxx
+8580 389 0 0 0 0 80 580 580 3580 8580 160 161 ASAAAA ZOAAAA HHHHxx
+9173 390 1 1 3 13 73 173 1173 4173 9173 146 147 VOAAAA APAAAA OOOOxx
+8558 391 0 2 8 18 58 558 558 3558 8558 116 117 ERAAAA BPAAAA VVVVxx
+3897 392 1 1 7 17 97 897 1897 3897 3897 194 195 XTAAAA CPAAAA AAAAxx
+5069 393 1 1 9 9 69 69 1069 69 5069 138 139 ZMAAAA DPAAAA HHHHxx
+2301 394 1 1 1 1 1 301 301 2301 2301 2 3 NKAAAA EPAAAA OOOOxx
+9863 395 1 3 3 3 63 863 1863 4863 9863 126 127 JPAAAA FPAAAA VVVVxx
+5733 396 1 1 3 13 33 733 1733 733 5733 66 67 NMAAAA GPAAAA AAAAxx
+2338 397 0 2 8 18 38 338 338 2338 2338 76 77 YLAAAA HPAAAA HHHHxx
+9639 398 1 3 9 19 39 639 1639 4639 9639 78 79 TGAAAA IPAAAA OOOOxx
+1139 399 1 3 9 19 39 139 1139 1139 1139 78 79 VRAAAA JPAAAA VVVVxx
+2293 400 1 1 3 13 93 293 293 2293 2293 186 187 FKAAAA KPAAAA AAAAxx
+6125 401 1 1 5 5 25 125 125 1125 6125 50 51 PBAAAA LPAAAA HHHHxx
+5374 402 0 2 4 14 74 374 1374 374 5374 148 149 SYAAAA MPAAAA OOOOxx
+7216 403 0 0 6 16 16 216 1216 2216 7216 32 33 ORAAAA NPAAAA VVVVxx
+2285 404 1 1 5 5 85 285 285 2285 2285 170 171 XJAAAA OPAAAA AAAAxx
+2387 405 1 3 7 7 87 387 387 2387 2387 174 175 VNAAAA PPAAAA HHHHxx
+5015 406 1 3 5 15 15 15 1015 15 5015 30 31 XKAAAA QPAAAA OOOOxx
+2087 407 1 3 7 7 87 87 87 2087 2087 174 175 HCAAAA RPAAAA VVVVxx
+4938 408 0 2 8 18 38 938 938 4938 4938 76 77 YHAAAA SPAAAA AAAAxx
+3635 409 1 3 5 15 35 635 1635 3635 3635 70 71 VJAAAA TPAAAA HHHHxx
+7737 410 1 1 7 17 37 737 1737 2737 7737 74 75 PLAAAA UPAAAA OOOOxx
+8056 411 0 0 6 16 56 56 56 3056 8056 112 113 WXAAAA VPAAAA VVVVxx
+4502 412 0 2 2 2 2 502 502 4502 4502 4 5 ERAAAA WPAAAA AAAAxx
+54 413 0 2 4 14 54 54 54 54 54 108 109 CCAAAA XPAAAA HHHHxx
+3182 414 0 2 2 2 82 182 1182 3182 3182 164 165 KSAAAA YPAAAA OOOOxx
+3718 415 0 2 8 18 18 718 1718 3718 3718 36 37 ANAAAA ZPAAAA VVVVxx
+3989 416 1 1 9 9 89 989 1989 3989 3989 178 179 LXAAAA AQAAAA AAAAxx
+8028 417 0 0 8 8 28 28 28 3028 8028 56 57 UWAAAA BQAAAA HHHHxx
+1426 418 0 2 6 6 26 426 1426 1426 1426 52 53 WCAAAA CQAAAA OOOOxx
+3801 419 1 1 1 1 1 801 1801 3801 3801 2 3 FQAAAA DQAAAA VVVVxx
+241 420 1 1 1 1 41 241 241 241 241 82 83 HJAAAA EQAAAA AAAAxx
+8000 421 0 0 0 0 0 0 0 3000 8000 0 1 SVAAAA FQAAAA HHHHxx
+8357 422 1 1 7 17 57 357 357 3357 8357 114 115 LJAAAA GQAAAA OOOOxx
+7548 423 0 0 8 8 48 548 1548 2548 7548 96 97 IEAAAA HQAAAA VVVVxx
+7307 424 1 3 7 7 7 307 1307 2307 7307 14 15 BVAAAA IQAAAA AAAAxx
+2275 425 1 3 5 15 75 275 275 2275 2275 150 151 NJAAAA JQAAAA HHHHxx
+2718 426 0 2 8 18 18 718 718 2718 2718 36 37 OAAAAA KQAAAA OOOOxx
+7068 427 0 0 8 8 68 68 1068 2068 7068 136 137 WLAAAA LQAAAA VVVVxx
+3181 428 1 1 1 1 81 181 1181 3181 3181 162 163 JSAAAA MQAAAA AAAAxx
+749 429 1 1 9 9 49 749 749 749 749 98 99 VCAAAA NQAAAA HHHHxx
+5195 430 1 3 5 15 95 195 1195 195 5195 190 191 VRAAAA OQAAAA OOOOxx
+6136 431 0 0 6 16 36 136 136 1136 6136 72 73 ACAAAA PQAAAA VVVVxx
+8012 432 0 0 2 12 12 12 12 3012 8012 24 25 EWAAAA QQAAAA AAAAxx
+3957 433 1 1 7 17 57 957 1957 3957 3957 114 115 FWAAAA RQAAAA HHHHxx
+3083 434 1 3 3 3 83 83 1083 3083 3083 166 167 POAAAA SQAAAA OOOOxx
+9997 435 1 1 7 17 97 997 1997 4997 9997 194 195 NUAAAA TQAAAA VVVVxx
+3299 436 1 3 9 19 99 299 1299 3299 3299 198 199 XWAAAA UQAAAA AAAAxx
+846 437 0 2 6 6 46 846 846 846 846 92 93 OGAAAA VQAAAA HHHHxx
+2985 438 1 1 5 5 85 985 985 2985 2985 170 171 VKAAAA WQAAAA OOOOxx
+9238 439 0 2 8 18 38 238 1238 4238 9238 76 77 IRAAAA XQAAAA VVVVxx
+1403 440 1 3 3 3 3 403 1403 1403 1403 6 7 ZBAAAA YQAAAA AAAAxx
+5563 441 1 3 3 3 63 563 1563 563 5563 126 127 ZFAAAA ZQAAAA HHHHxx
+7965 442 1 1 5 5 65 965 1965 2965 7965 130 131 JUAAAA ARAAAA OOOOxx
+4512 443 0 0 2 12 12 512 512 4512 4512 24 25 ORAAAA BRAAAA VVVVxx
+9730 444 0 2 0 10 30 730 1730 4730 9730 60 61 GKAAAA CRAAAA AAAAxx
+1129 445 1 1 9 9 29 129 1129 1129 1129 58 59 LRAAAA DRAAAA HHHHxx
+2624 446 0 0 4 4 24 624 624 2624 2624 48 49 YWAAAA ERAAAA OOOOxx
+8178 447 0 2 8 18 78 178 178 3178 8178 156 157 OCAAAA FRAAAA VVVVxx
+6468 448 0 0 8 8 68 468 468 1468 6468 136 137 UOAAAA GRAAAA AAAAxx
+3027 449 1 3 7 7 27 27 1027 3027 3027 54 55 LMAAAA HRAAAA HHHHxx
+3845 450 1 1 5 5 45 845 1845 3845 3845 90 91 XRAAAA IRAAAA OOOOxx
+786 451 0 2 6 6 86 786 786 786 786 172 173 GEAAAA JRAAAA VVVVxx
+4971 452 1 3 1 11 71 971 971 4971 4971 142 143 FJAAAA KRAAAA AAAAxx
+1542 453 0 2 2 2 42 542 1542 1542 1542 84 85 IHAAAA LRAAAA HHHHxx
+7967 454 1 3 7 7 67 967 1967 2967 7967 134 135 LUAAAA MRAAAA OOOOxx
+443 455 1 3 3 3 43 443 443 443 443 86 87 BRAAAA NRAAAA VVVVxx
+7318 456 0 2 8 18 18 318 1318 2318 7318 36 37 MVAAAA ORAAAA AAAAxx
+4913 457 1 1 3 13 13 913 913 4913 4913 26 27 ZGAAAA PRAAAA HHHHxx
+9466 458 0 2 6 6 66 466 1466 4466 9466 132 133 CAAAAA QRAAAA OOOOxx
+7866 459 0 2 6 6 66 866 1866 2866 7866 132 133 OQAAAA RRAAAA VVVVxx
+784 460 0 0 4 4 84 784 784 784 784 168 169 EEAAAA SRAAAA AAAAxx
+9040 461 0 0 0 0 40 40 1040 4040 9040 80 81 SJAAAA TRAAAA HHHHxx
+3954 462 0 2 4 14 54 954 1954 3954 3954 108 109 CWAAAA URAAAA OOOOxx
+4183 463 1 3 3 3 83 183 183 4183 4183 166 167 XEAAAA VRAAAA VVVVxx
+3608 464 0 0 8 8 8 608 1608 3608 3608 16 17 UIAAAA WRAAAA AAAAxx
+7630 465 0 2 0 10 30 630 1630 2630 7630 60 61 MHAAAA XRAAAA HHHHxx
+590 466 0 2 0 10 90 590 590 590 590 180 181 SWAAAA YRAAAA OOOOxx
+3453 467 1 1 3 13 53 453 1453 3453 3453 106 107 VCAAAA ZRAAAA VVVVxx
+7757 468 1 1 7 17 57 757 1757 2757 7757 114 115 JMAAAA ASAAAA AAAAxx
+7394 469 0 2 4 14 94 394 1394 2394 7394 188 189 KYAAAA BSAAAA HHHHxx
+396 470 0 0 6 16 96 396 396 396 396 192 193 GPAAAA CSAAAA OOOOxx
+7873 471 1 1 3 13 73 873 1873 2873 7873 146 147 VQAAAA DSAAAA VVVVxx
+1553 472 1 1 3 13 53 553 1553 1553 1553 106 107 THAAAA ESAAAA AAAAxx
+598 473 0 2 8 18 98 598 598 598 598 196 197 AXAAAA FSAAAA HHHHxx
+7191 474 1 3 1 11 91 191 1191 2191 7191 182 183 PQAAAA GSAAAA OOOOxx
+8116 475 0 0 6 16 16 116 116 3116 8116 32 33 EAAAAA HSAAAA VVVVxx
+2516 476 0 0 6 16 16 516 516 2516 2516 32 33 USAAAA ISAAAA AAAAxx
+7750 477 0 2 0 10 50 750 1750 2750 7750 100 101 CMAAAA JSAAAA HHHHxx
+6625 478 1 1 5 5 25 625 625 1625 6625 50 51 VUAAAA KSAAAA OOOOxx
+8838 479 0 2 8 18 38 838 838 3838 8838 76 77 YBAAAA LSAAAA VVVVxx
+4636 480 0 0 6 16 36 636 636 4636 4636 72 73 IWAAAA MSAAAA AAAAxx
+7627 481 1 3 7 7 27 627 1627 2627 7627 54 55 JHAAAA NSAAAA HHHHxx
+1690 482 0 2 0 10 90 690 1690 1690 1690 180 181 ANAAAA OSAAAA OOOOxx
+7071 483 1 3 1 11 71 71 1071 2071 7071 142 143 ZLAAAA PSAAAA VVVVxx
+2081 484 1 1 1 1 81 81 81 2081 2081 162 163 BCAAAA QSAAAA AAAAxx
+7138 485 0 2 8 18 38 138 1138 2138 7138 76 77 OOAAAA RSAAAA HHHHxx
+864 486 0 0 4 4 64 864 864 864 864 128 129 GHAAAA SSAAAA OOOOxx
+6392 487 0 0 2 12 92 392 392 1392 6392 184 185 WLAAAA TSAAAA VVVVxx
+7544 488 0 0 4 4 44 544 1544 2544 7544 88 89 EEAAAA USAAAA AAAAxx
+5438 489 0 2 8 18 38 438 1438 438 5438 76 77 EBAAAA VSAAAA HHHHxx
+7099 490 1 3 9 19 99 99 1099 2099 7099 198 199 BNAAAA WSAAAA OOOOxx
+5157 491 1 1 7 17 57 157 1157 157 5157 114 115 JQAAAA XSAAAA VVVVxx
+3391 492 1 3 1 11 91 391 1391 3391 3391 182 183 LAAAAA YSAAAA AAAAxx
+3805 493 1 1 5 5 5 805 1805 3805 3805 10 11 JQAAAA ZSAAAA HHHHxx
+2110 494 0 2 0 10 10 110 110 2110 2110 20 21 EDAAAA ATAAAA OOOOxx
+3176 495 0 0 6 16 76 176 1176 3176 3176 152 153 ESAAAA BTAAAA VVVVxx
+5918 496 0 2 8 18 18 918 1918 918 5918 36 37 QTAAAA CTAAAA AAAAxx
+1218 497 0 2 8 18 18 218 1218 1218 1218 36 37 WUAAAA DTAAAA HHHHxx
+6683 498 1 3 3 3 83 683 683 1683 6683 166 167 BXAAAA ETAAAA OOOOxx
+914 499 0 2 4 14 14 914 914 914 914 28 29 EJAAAA FTAAAA VVVVxx
+4737 500 1 1 7 17 37 737 737 4737 4737 74 75 FAAAAA GTAAAA AAAAxx
+7286 501 0 2 6 6 86 286 1286 2286 7286 172 173 GUAAAA HTAAAA HHHHxx
+9975 502 1 3 5 15 75 975 1975 4975 9975 150 151 RTAAAA ITAAAA OOOOxx
+8030 503 0 2 0 10 30 30 30 3030 8030 60 61 WWAAAA JTAAAA VVVVxx
+7364 504 0 0 4 4 64 364 1364 2364 7364 128 129 GXAAAA KTAAAA AAAAxx
+1389 505 1 1 9 9 89 389 1389 1389 1389 178 179 LBAAAA LTAAAA HHHHxx
+4025 506 1 1 5 5 25 25 25 4025 4025 50 51 VYAAAA MTAAAA OOOOxx
+4835 507 1 3 5 15 35 835 835 4835 4835 70 71 ZDAAAA NTAAAA VVVVxx
+8045 508 1 1 5 5 45 45 45 3045 8045 90 91 LXAAAA OTAAAA AAAAxx
+1864 509 0 0 4 4 64 864 1864 1864 1864 128 129 STAAAA PTAAAA HHHHxx
+3313 510 1 1 3 13 13 313 1313 3313 3313 26 27 LXAAAA QTAAAA OOOOxx
+2384 511 0 0 4 4 84 384 384 2384 2384 168 169 SNAAAA RTAAAA VVVVxx
+6115 512 1 3 5 15 15 115 115 1115 6115 30 31 FBAAAA STAAAA AAAAxx
+5705 513 1 1 5 5 5 705 1705 705 5705 10 11 LLAAAA TTAAAA HHHHxx
+9269 514 1 1 9 9 69 269 1269 4269 9269 138 139 NSAAAA UTAAAA OOOOxx
+3379 515 1 3 9 19 79 379 1379 3379 3379 158 159 ZZAAAA VTAAAA VVVVxx
+8205 516 1 1 5 5 5 205 205 3205 8205 10 11 PDAAAA WTAAAA AAAAxx
+6575 517 1 3 5 15 75 575 575 1575 6575 150 151 XSAAAA XTAAAA HHHHxx
+486 518 0 2 6 6 86 486 486 486 486 172 173 SSAAAA YTAAAA OOOOxx
+4894 519 0 2 4 14 94 894 894 4894 4894 188 189 GGAAAA ZTAAAA VVVVxx
+3090 520 0 2 0 10 90 90 1090 3090 3090 180 181 WOAAAA AUAAAA AAAAxx
+759 521 1 3 9 19 59 759 759 759 759 118 119 FDAAAA BUAAAA HHHHxx
+4864 522 0 0 4 4 64 864 864 4864 4864 128 129 CFAAAA CUAAAA OOOOxx
+4083 523 1 3 3 3 83 83 83 4083 4083 166 167 BBAAAA DUAAAA VVVVxx
+6918 524 0 2 8 18 18 918 918 1918 6918 36 37 CGAAAA EUAAAA AAAAxx
+8146 525 0 2 6 6 46 146 146 3146 8146 92 93 IBAAAA FUAAAA HHHHxx
+1523 526 1 3 3 3 23 523 1523 1523 1523 46 47 PGAAAA GUAAAA OOOOxx
+1591 527 1 3 1 11 91 591 1591 1591 1591 182 183 FJAAAA HUAAAA VVVVxx
+3343 528 1 3 3 3 43 343 1343 3343 3343 86 87 PYAAAA IUAAAA AAAAxx
+1391 529 1 3 1 11 91 391 1391 1391 1391 182 183 NBAAAA JUAAAA HHHHxx
+9963 530 1 3 3 3 63 963 1963 4963 9963 126 127 FTAAAA KUAAAA OOOOxx
+2423 531 1 3 3 3 23 423 423 2423 2423 46 47 FPAAAA LUAAAA VVVVxx
+1822 532 0 2 2 2 22 822 1822 1822 1822 44 45 CSAAAA MUAAAA AAAAxx
+8706 533 0 2 6 6 6 706 706 3706 8706 12 13 WWAAAA NUAAAA HHHHxx
+3001 534 1 1 1 1 1 1 1001 3001 3001 2 3 LLAAAA OUAAAA OOOOxx
+6707 535 1 3 7 7 7 707 707 1707 6707 14 15 ZXAAAA PUAAAA VVVVxx
+2121 536 1 1 1 1 21 121 121 2121 2121 42 43 PDAAAA QUAAAA AAAAxx
+5814 537 0 2 4 14 14 814 1814 814 5814 28 29 QPAAAA RUAAAA HHHHxx
+2659 538 1 3 9 19 59 659 659 2659 2659 118 119 HYAAAA SUAAAA OOOOxx
+2016 539 0 0 6 16 16 16 16 2016 2016 32 33 OZAAAA TUAAAA VVVVxx
+4286 540 0 2 6 6 86 286 286 4286 4286 172 173 WIAAAA UUAAAA AAAAxx
+9205 541 1 1 5 5 5 205 1205 4205 9205 10 11 BQAAAA VUAAAA HHHHxx
+3496 542 0 0 6 16 96 496 1496 3496 3496 192 193 MEAAAA WUAAAA OOOOxx
+5333 543 1 1 3 13 33 333 1333 333 5333 66 67 DXAAAA XUAAAA VVVVxx
+5571 544 1 3 1 11 71 571 1571 571 5571 142 143 HGAAAA YUAAAA AAAAxx
+1696 545 0 0 6 16 96 696 1696 1696 1696 192 193 GNAAAA ZUAAAA HHHHxx
+4871 546 1 3 1 11 71 871 871 4871 4871 142 143 JFAAAA AVAAAA OOOOxx
+4852 547 0 0 2 12 52 852 852 4852 4852 104 105 QEAAAA BVAAAA VVVVxx
+8483 548 1 3 3 3 83 483 483 3483 8483 166 167 HOAAAA CVAAAA AAAAxx
+1376 549 0 0 6 16 76 376 1376 1376 1376 152 153 YAAAAA DVAAAA HHHHxx
+5456 550 0 0 6 16 56 456 1456 456 5456 112 113 WBAAAA EVAAAA OOOOxx
+499 551 1 3 9 19 99 499 499 499 499 198 199 FTAAAA FVAAAA VVVVxx
+3463 552 1 3 3 3 63 463 1463 3463 3463 126 127 FDAAAA GVAAAA AAAAxx
+7426 553 0 2 6 6 26 426 1426 2426 7426 52 53 QZAAAA HVAAAA HHHHxx
+5341 554 1 1 1 1 41 341 1341 341 5341 82 83 LXAAAA IVAAAA OOOOxx
+9309 555 1 1 9 9 9 309 1309 4309 9309 18 19 BUAAAA JVAAAA VVVVxx
+2055 556 1 3 5 15 55 55 55 2055 2055 110 111 BBAAAA KVAAAA AAAAxx
+2199 557 1 3 9 19 99 199 199 2199 2199 198 199 PGAAAA LVAAAA HHHHxx
+7235 558 1 3 5 15 35 235 1235 2235 7235 70 71 HSAAAA MVAAAA OOOOxx
+8661 559 1 1 1 1 61 661 661 3661 8661 122 123 DVAAAA NVAAAA VVVVxx
+9494 560 0 2 4 14 94 494 1494 4494 9494 188 189 EBAAAA OVAAAA AAAAxx
+935 561 1 3 5 15 35 935 935 935 935 70 71 ZJAAAA PVAAAA HHHHxx
+7044 562 0 0 4 4 44 44 1044 2044 7044 88 89 YKAAAA QVAAAA OOOOxx
+1974 563 0 2 4 14 74 974 1974 1974 1974 148 149 YXAAAA RVAAAA VVVVxx
+9679 564 1 3 9 19 79 679 1679 4679 9679 158 159 HIAAAA SVAAAA AAAAxx
+9822 565 0 2 2 2 22 822 1822 4822 9822 44 45 UNAAAA TVAAAA HHHHxx
+4088 566 0 0 8 8 88 88 88 4088 4088 176 177 GBAAAA UVAAAA OOOOxx
+1749 567 1 1 9 9 49 749 1749 1749 1749 98 99 HPAAAA VVAAAA VVVVxx
+2116 568 0 0 6 16 16 116 116 2116 2116 32 33 KDAAAA WVAAAA AAAAxx
+976 569 0 0 6 16 76 976 976 976 976 152 153 OLAAAA XVAAAA HHHHxx
+8689 570 1 1 9 9 89 689 689 3689 8689 178 179 FWAAAA YVAAAA OOOOxx
+2563 571 1 3 3 3 63 563 563 2563 2563 126 127 PUAAAA ZVAAAA VVVVxx
+7195 572 1 3 5 15 95 195 1195 2195 7195 190 191 TQAAAA AWAAAA AAAAxx
+9985 573 1 1 5 5 85 985 1985 4985 9985 170 171 BUAAAA BWAAAA HHHHxx
+7699 574 1 3 9 19 99 699 1699 2699 7699 198 199 DKAAAA CWAAAA OOOOxx
+5311 575 1 3 1 11 11 311 1311 311 5311 22 23 HWAAAA DWAAAA VVVVxx
+295 576 1 3 5 15 95 295 295 295 295 190 191 JLAAAA EWAAAA AAAAxx
+8214 577 0 2 4 14 14 214 214 3214 8214 28 29 YDAAAA FWAAAA HHHHxx
+3275 578 1 3 5 15 75 275 1275 3275 3275 150 151 ZVAAAA GWAAAA OOOOxx
+9646 579 0 2 6 6 46 646 1646 4646 9646 92 93 AHAAAA HWAAAA VVVVxx
+1908 580 0 0 8 8 8 908 1908 1908 1908 16 17 KVAAAA IWAAAA AAAAxx
+3858 581 0 2 8 18 58 858 1858 3858 3858 116 117 KSAAAA JWAAAA HHHHxx
+9362 582 0 2 2 2 62 362 1362 4362 9362 124 125 CWAAAA KWAAAA OOOOxx
+9307 583 1 3 7 7 7 307 1307 4307 9307 14 15 ZTAAAA LWAAAA VVVVxx
+6124 584 0 0 4 4 24 124 124 1124 6124 48 49 OBAAAA MWAAAA AAAAxx
+2405 585 1 1 5 5 5 405 405 2405 2405 10 11 NOAAAA NWAAAA HHHHxx
+8422 586 0 2 2 2 22 422 422 3422 8422 44 45 YLAAAA OWAAAA OOOOxx
+393 587 1 1 3 13 93 393 393 393 393 186 187 DPAAAA PWAAAA VVVVxx
+8973 588 1 1 3 13 73 973 973 3973 8973 146 147 DHAAAA QWAAAA AAAAxx
+5171 589 1 3 1 11 71 171 1171 171 5171 142 143 XQAAAA RWAAAA HHHHxx
+4929 590 1 1 9 9 29 929 929 4929 4929 58 59 PHAAAA SWAAAA OOOOxx
+6935 591 1 3 5 15 35 935 935 1935 6935 70 71 TGAAAA TWAAAA VVVVxx
+8584 592 0 0 4 4 84 584 584 3584 8584 168 169 ESAAAA UWAAAA AAAAxx
+1035 593 1 3 5 15 35 35 1035 1035 1035 70 71 VNAAAA VWAAAA HHHHxx
+3734 594 0 2 4 14 34 734 1734 3734 3734 68 69 QNAAAA WWAAAA OOOOxx
+1458 595 0 2 8 18 58 458 1458 1458 1458 116 117 CEAAAA XWAAAA VVVVxx
+8746 596 0 2 6 6 46 746 746 3746 8746 92 93 KYAAAA YWAAAA AAAAxx
+1677 597 1 1 7 17 77 677 1677 1677 1677 154 155 NMAAAA ZWAAAA HHHHxx
+8502 598 0 2 2 2 2 502 502 3502 8502 4 5 APAAAA AXAAAA OOOOxx
+7752 599 0 0 2 12 52 752 1752 2752 7752 104 105 EMAAAA BXAAAA VVVVxx
+2556 600 0 0 6 16 56 556 556 2556 2556 112 113 IUAAAA CXAAAA AAAAxx
+6426 601 0 2 6 6 26 426 426 1426 6426 52 53 ENAAAA DXAAAA HHHHxx
+8420 602 0 0 0 0 20 420 420 3420 8420 40 41 WLAAAA EXAAAA OOOOxx
+4462 603 0 2 2 2 62 462 462 4462 4462 124 125 QPAAAA FXAAAA VVVVxx
+1378 604 0 2 8 18 78 378 1378 1378 1378 156 157 ABAAAA GXAAAA AAAAxx
+1387 605 1 3 7 7 87 387 1387 1387 1387 174 175 JBAAAA HXAAAA HHHHxx
+8094 606 0 2 4 14 94 94 94 3094 8094 188 189 IZAAAA IXAAAA OOOOxx
+7247 607 1 3 7 7 47 247 1247 2247 7247 94 95 TSAAAA JXAAAA VVVVxx
+4261 608 1 1 1 1 61 261 261 4261 4261 122 123 XHAAAA KXAAAA AAAAxx
+5029 609 1 1 9 9 29 29 1029 29 5029 58 59 LLAAAA LXAAAA HHHHxx
+3625 610 1 1 5 5 25 625 1625 3625 3625 50 51 LJAAAA MXAAAA OOOOxx
+8068 611 0 0 8 8 68 68 68 3068 8068 136 137 IYAAAA NXAAAA VVVVxx
+102 612 0 2 2 2 2 102 102 102 102 4 5 YDAAAA OXAAAA AAAAxx
+5596 613 0 0 6 16 96 596 1596 596 5596 192 193 GHAAAA PXAAAA HHHHxx
+5872 614 0 0 2 12 72 872 1872 872 5872 144 145 WRAAAA QXAAAA OOOOxx
+4742 615 0 2 2 2 42 742 742 4742 4742 84 85 KAAAAA RXAAAA VVVVxx
+2117 616 1 1 7 17 17 117 117 2117 2117 34 35 LDAAAA SXAAAA AAAAxx
+3945 617 1 1 5 5 45 945 1945 3945 3945 90 91 TVAAAA TXAAAA HHHHxx
+7483 618 1 3 3 3 83 483 1483 2483 7483 166 167 VBAAAA UXAAAA OOOOxx
+4455 619 1 3 5 15 55 455 455 4455 4455 110 111 JPAAAA VXAAAA VVVVxx
+609 620 1 1 9 9 9 609 609 609 609 18 19 LXAAAA WXAAAA AAAAxx
+9829 621 1 1 9 9 29 829 1829 4829 9829 58 59 BOAAAA XXAAAA HHHHxx
+4857 622 1 1 7 17 57 857 857 4857 4857 114 115 VEAAAA YXAAAA OOOOxx
+3314 623 0 2 4 14 14 314 1314 3314 3314 28 29 MXAAAA ZXAAAA VVVVxx
+5353 624 1 1 3 13 53 353 1353 353 5353 106 107 XXAAAA AYAAAA AAAAxx
+4909 625 1 1 9 9 9 909 909 4909 4909 18 19 VGAAAA BYAAAA HHHHxx
+7597 626 1 1 7 17 97 597 1597 2597 7597 194 195 FGAAAA CYAAAA OOOOxx
+2683 627 1 3 3 3 83 683 683 2683 2683 166 167 FZAAAA DYAAAA VVVVxx
+3223 628 1 3 3 3 23 223 1223 3223 3223 46 47 ZTAAAA EYAAAA AAAAxx
+5363 629 1 3 3 3 63 363 1363 363 5363 126 127 HYAAAA FYAAAA HHHHxx
+4578 630 0 2 8 18 78 578 578 4578 4578 156 157 CUAAAA GYAAAA OOOOxx
+5544 631 0 0 4 4 44 544 1544 544 5544 88 89 GFAAAA HYAAAA VVVVxx
+1589 632 1 1 9 9 89 589 1589 1589 1589 178 179 DJAAAA IYAAAA AAAAxx
+7412 633 0 0 2 12 12 412 1412 2412 7412 24 25 CZAAAA JYAAAA HHHHxx
+3803 634 1 3 3 3 3 803 1803 3803 3803 6 7 HQAAAA KYAAAA OOOOxx
+6179 635 1 3 9 19 79 179 179 1179 6179 158 159 RDAAAA LYAAAA VVVVxx
+5588 636 0 0 8 8 88 588 1588 588 5588 176 177 YGAAAA MYAAAA AAAAxx
+2134 637 0 2 4 14 34 134 134 2134 2134 68 69 CEAAAA NYAAAA HHHHxx
+4383 638 1 3 3 3 83 383 383 4383 4383 166 167 PMAAAA OYAAAA OOOOxx
+6995 639 1 3 5 15 95 995 995 1995 6995 190 191 BJAAAA PYAAAA VVVVxx
+6598 640 0 2 8 18 98 598 598 1598 6598 196 197 UTAAAA QYAAAA AAAAxx
+8731 641 1 3 1 11 31 731 731 3731 8731 62 63 VXAAAA RYAAAA HHHHxx
+7177 642 1 1 7 17 77 177 1177 2177 7177 154 155 BQAAAA SYAAAA OOOOxx
+6578 643 0 2 8 18 78 578 578 1578 6578 156 157 ATAAAA TYAAAA VVVVxx
+9393 644 1 1 3 13 93 393 1393 4393 9393 186 187 HXAAAA UYAAAA AAAAxx
+1276 645 0 0 6 16 76 276 1276 1276 1276 152 153 CXAAAA VYAAAA HHHHxx
+8766 646 0 2 6 6 66 766 766 3766 8766 132 133 EZAAAA WYAAAA OOOOxx
+1015 647 1 3 5 15 15 15 1015 1015 1015 30 31 BNAAAA XYAAAA VVVVxx
+4396 648 0 0 6 16 96 396 396 4396 4396 192 193 CNAAAA YYAAAA AAAAxx
+5564 649 0 0 4 4 64 564 1564 564 5564 128 129 AGAAAA ZYAAAA HHHHxx
+927 650 1 3 7 7 27 927 927 927 927 54 55 RJAAAA AZAAAA OOOOxx
+3306 651 0 2 6 6 6 306 1306 3306 3306 12 13 EXAAAA BZAAAA VVVVxx
+1615 652 1 3 5 15 15 615 1615 1615 1615 30 31 DKAAAA CZAAAA AAAAxx
+4550 653 0 2 0 10 50 550 550 4550 4550 100 101 ATAAAA DZAAAA HHHHxx
+2468 654 0 0 8 8 68 468 468 2468 2468 136 137 YQAAAA EZAAAA OOOOxx
+5336 655 0 0 6 16 36 336 1336 336 5336 72 73 GXAAAA FZAAAA VVVVxx
+4471 656 1 3 1 11 71 471 471 4471 4471 142 143 ZPAAAA GZAAAA AAAAxx
+8085 657 1 1 5 5 85 85 85 3085 8085 170 171 ZYAAAA HZAAAA HHHHxx
+540 658 0 0 0 0 40 540 540 540 540 80 81 UUAAAA IZAAAA OOOOxx
+5108 659 0 0 8 8 8 108 1108 108 5108 16 17 MOAAAA JZAAAA VVVVxx
+8015 660 1 3 5 15 15 15 15 3015 8015 30 31 HWAAAA KZAAAA AAAAxx
+2857 661 1 1 7 17 57 857 857 2857 2857 114 115 XFAAAA LZAAAA HHHHxx
+9472 662 0 0 2 12 72 472 1472 4472 9472 144 145 IAAAAA MZAAAA OOOOxx
+5666 663 0 2 6 6 66 666 1666 666 5666 132 133 YJAAAA NZAAAA VVVVxx
+3555 664 1 3 5 15 55 555 1555 3555 3555 110 111 TGAAAA OZAAAA AAAAxx
+378 665 0 2 8 18 78 378 378 378 378 156 157 OOAAAA PZAAAA HHHHxx
+4466 666 0 2 6 6 66 466 466 4466 4466 132 133 UPAAAA QZAAAA OOOOxx
+3247 667 1 3 7 7 47 247 1247 3247 3247 94 95 XUAAAA RZAAAA VVVVxx
+6570 668 0 2 0 10 70 570 570 1570 6570 140 141 SSAAAA SZAAAA AAAAxx
+5655 669 1 3 5 15 55 655 1655 655 5655 110 111 NJAAAA TZAAAA HHHHxx
+917 670 1 1 7 17 17 917 917 917 917 34 35 HJAAAA UZAAAA OOOOxx
+3637 671 1 1 7 17 37 637 1637 3637 3637 74 75 XJAAAA VZAAAA VVVVxx
+3668 672 0 0 8 8 68 668 1668 3668 3668 136 137 CLAAAA WZAAAA AAAAxx
+5644 673 0 0 4 4 44 644 1644 644 5644 88 89 CJAAAA XZAAAA HHHHxx
+8286 674 0 2 6 6 86 286 286 3286 8286 172 173 SGAAAA YZAAAA OOOOxx
+6896 675 0 0 6 16 96 896 896 1896 6896 192 193 GFAAAA ZZAAAA VVVVxx
+2870 676 0 2 0 10 70 870 870 2870 2870 140 141 KGAAAA AABAAA AAAAxx
+8041 677 1 1 1 1 41 41 41 3041 8041 82 83 HXAAAA BABAAA HHHHxx
+8137 678 1 1 7 17 37 137 137 3137 8137 74 75 ZAAAAA CABAAA OOOOxx
+4823 679 1 3 3 3 23 823 823 4823 4823 46 47 NDAAAA DABAAA VVVVxx
+2438 680 0 2 8 18 38 438 438 2438 2438 76 77 UPAAAA EABAAA AAAAxx
+6329 681 1 1 9 9 29 329 329 1329 6329 58 59 LJAAAA FABAAA HHHHxx
+623 682 1 3 3 3 23 623 623 623 623 46 47 ZXAAAA GABAAA OOOOxx
+1360 683 0 0 0 0 60 360 1360 1360 1360 120 121 IAAAAA HABAAA VVVVxx
+7987 684 1 3 7 7 87 987 1987 2987 7987 174 175 FVAAAA IABAAA AAAAxx
+9788 685 0 0 8 8 88 788 1788 4788 9788 176 177 MMAAAA JABAAA HHHHxx
+3212 686 0 0 2 12 12 212 1212 3212 3212 24 25 OTAAAA KABAAA OOOOxx
+2725 687 1 1 5 5 25 725 725 2725 2725 50 51 VAAAAA LABAAA VVVVxx
+7837 688 1 1 7 17 37 837 1837 2837 7837 74 75 LPAAAA MABAAA AAAAxx
+4746 689 0 2 6 6 46 746 746 4746 4746 92 93 OAAAAA NABAAA HHHHxx
+3986 690 0 2 6 6 86 986 1986 3986 3986 172 173 IXAAAA OABAAA OOOOxx
+9128 691 0 0 8 8 28 128 1128 4128 9128 56 57 CNAAAA PABAAA VVVVxx
+5044 692 0 0 4 4 44 44 1044 44 5044 88 89 AMAAAA QABAAA AAAAxx
+8132 693 0 0 2 12 32 132 132 3132 8132 64 65 UAAAAA RABAAA HHHHxx
+9992 694 0 0 2 12 92 992 1992 4992 9992 184 185 IUAAAA SABAAA OOOOxx
+8468 695 0 0 8 8 68 468 468 3468 8468 136 137 SNAAAA TABAAA VVVVxx
+6876 696 0 0 6 16 76 876 876 1876 6876 152 153 MEAAAA UABAAA AAAAxx
+3532 697 0 0 2 12 32 532 1532 3532 3532 64 65 WFAAAA VABAAA HHHHxx
+2140 698 0 0 0 0 40 140 140 2140 2140 80 81 IEAAAA WABAAA OOOOxx
+2183 699 1 3 3 3 83 183 183 2183 2183 166 167 ZFAAAA XABAAA VVVVxx
+9766 700 0 2 6 6 66 766 1766 4766 9766 132 133 QLAAAA YABAAA AAAAxx
+7943 701 1 3 3 3 43 943 1943 2943 7943 86 87 NTAAAA ZABAAA HHHHxx
+9243 702 1 3 3 3 43 243 1243 4243 9243 86 87 NRAAAA ABBAAA OOOOxx
+6241 703 1 1 1 1 41 241 241 1241 6241 82 83 BGAAAA BBBAAA VVVVxx
+9540 704 0 0 0 0 40 540 1540 4540 9540 80 81 YCAAAA CBBAAA AAAAxx
+7418 705 0 2 8 18 18 418 1418 2418 7418 36 37 IZAAAA DBBAAA HHHHxx
+1603 706 1 3 3 3 3 603 1603 1603 1603 6 7 RJAAAA EBBAAA OOOOxx
+8950 707 0 2 0 10 50 950 950 3950 8950 100 101 GGAAAA FBBAAA VVVVxx
+6933 708 1 1 3 13 33 933 933 1933 6933 66 67 RGAAAA GBBAAA AAAAxx
+2646 709 0 2 6 6 46 646 646 2646 2646 92 93 UXAAAA HBBAAA HHHHxx
+3447 710 1 3 7 7 47 447 1447 3447 3447 94 95 PCAAAA IBBAAA OOOOxx
+9957 711 1 1 7 17 57 957 1957 4957 9957 114 115 ZSAAAA JBBAAA VVVVxx
+4623 712 1 3 3 3 23 623 623 4623 4623 46 47 VVAAAA KBBAAA AAAAxx
+9058 713 0 2 8 18 58 58 1058 4058 9058 116 117 KKAAAA LBBAAA HHHHxx
+7361 714 1 1 1 1 61 361 1361 2361 7361 122 123 DXAAAA MBBAAA OOOOxx
+2489 715 1 1 9 9 89 489 489 2489 2489 178 179 TRAAAA NBBAAA VVVVxx
+7643 716 1 3 3 3 43 643 1643 2643 7643 86 87 ZHAAAA OBBAAA AAAAxx
+9166 717 0 2 6 6 66 166 1166 4166 9166 132 133 OOAAAA PBBAAA HHHHxx
+7789 718 1 1 9 9 89 789 1789 2789 7789 178 179 PNAAAA QBBAAA OOOOxx
+2332 719 0 0 2 12 32 332 332 2332 2332 64 65 SLAAAA RBBAAA VVVVxx
+1832 720 0 0 2 12 32 832 1832 1832 1832 64 65 MSAAAA SBBAAA AAAAxx
+8375 721 1 3 5 15 75 375 375 3375 8375 150 151 DKAAAA TBBAAA HHHHxx
+948 722 0 0 8 8 48 948 948 948 948 96 97 MKAAAA UBBAAA OOOOxx
+5613 723 1 1 3 13 13 613 1613 613 5613 26 27 XHAAAA VBBAAA VVVVxx
+6310 724 0 2 0 10 10 310 310 1310 6310 20 21 SIAAAA WBBAAA AAAAxx
+4254 725 0 2 4 14 54 254 254 4254 4254 108 109 QHAAAA XBBAAA HHHHxx
+4260 726 0 0 0 0 60 260 260 4260 4260 120 121 WHAAAA YBBAAA OOOOxx
+2060 727 0 0 0 0 60 60 60 2060 2060 120 121 GBAAAA ZBBAAA VVVVxx
+4831 728 1 3 1 11 31 831 831 4831 4831 62 63 VDAAAA ACBAAA AAAAxx
+6176 729 0 0 6 16 76 176 176 1176 6176 152 153 ODAAAA BCBAAA HHHHxx
+6688 730 0 0 8 8 88 688 688 1688 6688 176 177 GXAAAA CCBAAA OOOOxx
+5752 731 0 0 2 12 52 752 1752 752 5752 104 105 GNAAAA DCBAAA VVVVxx
+8714 732 0 2 4 14 14 714 714 3714 8714 28 29 EXAAAA ECBAAA AAAAxx
+6739 733 1 3 9 19 39 739 739 1739 6739 78 79 FZAAAA FCBAAA HHHHxx
+7066 734 0 2 6 6 66 66 1066 2066 7066 132 133 ULAAAA GCBAAA OOOOxx
+7250 735 0 2 0 10 50 250 1250 2250 7250 100 101 WSAAAA HCBAAA VVVVxx
+3161 736 1 1 1 1 61 161 1161 3161 3161 122 123 PRAAAA ICBAAA AAAAxx
+1411 737 1 3 1 11 11 411 1411 1411 1411 22 23 HCAAAA JCBAAA HHHHxx
+9301 738 1 1 1 1 1 301 1301 4301 9301 2 3 TTAAAA KCBAAA OOOOxx
+8324 739 0 0 4 4 24 324 324 3324 8324 48 49 EIAAAA LCBAAA VVVVxx
+9641 740 1 1 1 1 41 641 1641 4641 9641 82 83 VGAAAA MCBAAA AAAAxx
+7077 741 1 1 7 17 77 77 1077 2077 7077 154 155 FMAAAA NCBAAA HHHHxx
+9888 742 0 0 8 8 88 888 1888 4888 9888 176 177 IQAAAA OCBAAA OOOOxx
+9909 743 1 1 9 9 9 909 1909 4909 9909 18 19 DRAAAA PCBAAA VVVVxx
+2209 744 1 1 9 9 9 209 209 2209 2209 18 19 ZGAAAA QCBAAA AAAAxx
+6904 745 0 0 4 4 4 904 904 1904 6904 8 9 OFAAAA RCBAAA HHHHxx
+6608 746 0 0 8 8 8 608 608 1608 6608 16 17 EUAAAA SCBAAA OOOOxx
+8400 747 0 0 0 0 0 400 400 3400 8400 0 1 CLAAAA TCBAAA VVVVxx
+5124 748 0 0 4 4 24 124 1124 124 5124 48 49 CPAAAA UCBAAA AAAAxx
+5484 749 0 0 4 4 84 484 1484 484 5484 168 169 YCAAAA VCBAAA HHHHxx
+3575 750 1 3 5 15 75 575 1575 3575 3575 150 151 NHAAAA WCBAAA OOOOxx
+9723 751 1 3 3 3 23 723 1723 4723 9723 46 47 ZJAAAA XCBAAA VVVVxx
+360 752 0 0 0 0 60 360 360 360 360 120 121 WNAAAA YCBAAA AAAAxx
+1059 753 1 3 9 19 59 59 1059 1059 1059 118 119 TOAAAA ZCBAAA HHHHxx
+4941 754 1 1 1 1 41 941 941 4941 4941 82 83 BIAAAA ADBAAA OOOOxx
+2535 755 1 3 5 15 35 535 535 2535 2535 70 71 NTAAAA BDBAAA VVVVxx
+4119 756 1 3 9 19 19 119 119 4119 4119 38 39 LCAAAA CDBAAA AAAAxx
+3725 757 1 1 5 5 25 725 1725 3725 3725 50 51 HNAAAA DDBAAA HHHHxx
+4758 758 0 2 8 18 58 758 758 4758 4758 116 117 ABAAAA EDBAAA OOOOxx
+9593 759 1 1 3 13 93 593 1593 4593 9593 186 187 ZEAAAA FDBAAA VVVVxx
+4663 760 1 3 3 3 63 663 663 4663 4663 126 127 JXAAAA GDBAAA AAAAxx
+7734 761 0 2 4 14 34 734 1734 2734 7734 68 69 MLAAAA HDBAAA HHHHxx
+9156 762 0 0 6 16 56 156 1156 4156 9156 112 113 EOAAAA IDBAAA OOOOxx
+8120 763 0 0 0 0 20 120 120 3120 8120 40 41 IAAAAA JDBAAA VVVVxx
+4385 764 1 1 5 5 85 385 385 4385 4385 170 171 RMAAAA KDBAAA AAAAxx
+2926 765 0 2 6 6 26 926 926 2926 2926 52 53 OIAAAA LDBAAA HHHHxx
+4186 766 0 2 6 6 86 186 186 4186 4186 172 173 AFAAAA MDBAAA OOOOxx
+2508 767 0 0 8 8 8 508 508 2508 2508 16 17 MSAAAA NDBAAA VVVVxx
+4012 768 0 0 2 12 12 12 12 4012 4012 24 25 IYAAAA ODBAAA AAAAxx
+6266 769 0 2 6 6 66 266 266 1266 6266 132 133 AHAAAA PDBAAA HHHHxx
+3709 770 1 1 9 9 9 709 1709 3709 3709 18 19 RMAAAA QDBAAA OOOOxx
+7289 771 1 1 9 9 89 289 1289 2289 7289 178 179 JUAAAA RDBAAA VVVVxx
+8875 772 1 3 5 15 75 875 875 3875 8875 150 151 JDAAAA SDBAAA AAAAxx
+4412 773 0 0 2 12 12 412 412 4412 4412 24 25 SNAAAA TDBAAA HHHHxx
+3033 774 1 1 3 13 33 33 1033 3033 3033 66 67 RMAAAA UDBAAA OOOOxx
+1645 775 1 1 5 5 45 645 1645 1645 1645 90 91 HLAAAA VDBAAA VVVVxx
+3557 776 1 1 7 17 57 557 1557 3557 3557 114 115 VGAAAA WDBAAA AAAAxx
+6316 777 0 0 6 16 16 316 316 1316 6316 32 33 YIAAAA XDBAAA HHHHxx
+2054 778 0 2 4 14 54 54 54 2054 2054 108 109 ABAAAA YDBAAA OOOOxx
+7031 779 1 3 1 11 31 31 1031 2031 7031 62 63 LKAAAA ZDBAAA VVVVxx
+3405 780 1 1 5 5 5 405 1405 3405 3405 10 11 ZAAAAA AEBAAA AAAAxx
+5343 781 1 3 3 3 43 343 1343 343 5343 86 87 NXAAAA BEBAAA HHHHxx
+5240 782 0 0 0 0 40 240 1240 240 5240 80 81 OTAAAA CEBAAA OOOOxx
+9650 783 0 2 0 10 50 650 1650 4650 9650 100 101 EHAAAA DEBAAA VVVVxx
+3777 784 1 1 7 17 77 777 1777 3777 3777 154 155 HPAAAA EEBAAA AAAAxx
+9041 785 1 1 1 1 41 41 1041 4041 9041 82 83 TJAAAA FEBAAA HHHHxx
+6923 786 1 3 3 3 23 923 923 1923 6923 46 47 HGAAAA GEBAAA OOOOxx
+2977 787 1 1 7 17 77 977 977 2977 2977 154 155 NKAAAA HEBAAA VVVVxx
+5500 788 0 0 0 0 0 500 1500 500 5500 0 1 ODAAAA IEBAAA AAAAxx
+1044 789 0 0 4 4 44 44 1044 1044 1044 88 89 EOAAAA JEBAAA HHHHxx
+434 790 0 2 4 14 34 434 434 434 434 68 69 SQAAAA KEBAAA OOOOxx
+611 791 1 3 1 11 11 611 611 611 611 22 23 NXAAAA LEBAAA VVVVxx
+5760 792 0 0 0 0 60 760 1760 760 5760 120 121 ONAAAA MEBAAA AAAAxx
+2445 793 1 1 5 5 45 445 445 2445 2445 90 91 BQAAAA NEBAAA HHHHxx
+7098 794 0 2 8 18 98 98 1098 2098 7098 196 197 ANAAAA OEBAAA OOOOxx
+2188 795 0 0 8 8 88 188 188 2188 2188 176 177 EGAAAA PEBAAA VVVVxx
+4597 796 1 1 7 17 97 597 597 4597 4597 194 195 VUAAAA QEBAAA AAAAxx
+1913 797 1 1 3 13 13 913 1913 1913 1913 26 27 PVAAAA REBAAA HHHHxx
+8696 798 0 0 6 16 96 696 696 3696 8696 192 193 MWAAAA SEBAAA OOOOxx
+3332 799 0 0 2 12 32 332 1332 3332 3332 64 65 EYAAAA TEBAAA VVVVxx
+8760 800 0 0 0 0 60 760 760 3760 8760 120 121 YYAAAA UEBAAA AAAAxx
+3215 801 1 3 5 15 15 215 1215 3215 3215 30 31 RTAAAA VEBAAA HHHHxx
+1625 802 1 1 5 5 25 625 1625 1625 1625 50 51 NKAAAA WEBAAA OOOOxx
+4219 803 1 3 9 19 19 219 219 4219 4219 38 39 HGAAAA XEBAAA VVVVxx
+415 804 1 3 5 15 15 415 415 415 415 30 31 ZPAAAA YEBAAA AAAAxx
+4242 805 0 2 2 2 42 242 242 4242 4242 84 85 EHAAAA ZEBAAA HHHHxx
+8660 806 0 0 0 0 60 660 660 3660 8660 120 121 CVAAAA AFBAAA OOOOxx
+6525 807 1 1 5 5 25 525 525 1525 6525 50 51 ZQAAAA BFBAAA VVVVxx
+2141 808 1 1 1 1 41 141 141 2141 2141 82 83 JEAAAA CFBAAA AAAAxx
+5152 809 0 0 2 12 52 152 1152 152 5152 104 105 EQAAAA DFBAAA HHHHxx
+8560 810 0 0 0 0 60 560 560 3560 8560 120 121 GRAAAA EFBAAA OOOOxx
+9835 811 1 3 5 15 35 835 1835 4835 9835 70 71 HOAAAA FFBAAA VVVVxx
+2657 812 1 1 7 17 57 657 657 2657 2657 114 115 FYAAAA GFBAAA AAAAxx
+6085 813 1 1 5 5 85 85 85 1085 6085 170 171 BAAAAA HFBAAA HHHHxx
+6698 814 0 2 8 18 98 698 698 1698 6698 196 197 QXAAAA IFBAAA OOOOxx
+5421 815 1 1 1 1 21 421 1421 421 5421 42 43 NAAAAA JFBAAA VVVVxx
+6661 816 1 1 1 1 61 661 661 1661 6661 122 123 FWAAAA KFBAAA AAAAxx
+5645 817 1 1 5 5 45 645 1645 645 5645 90 91 DJAAAA LFBAAA HHHHxx
+1248 818 0 0 8 8 48 248 1248 1248 1248 96 97 AWAAAA MFBAAA OOOOxx
+5690 819 0 2 0 10 90 690 1690 690 5690 180 181 WKAAAA NFBAAA VVVVxx
+4762 820 0 2 2 2 62 762 762 4762 4762 124 125 EBAAAA OFBAAA AAAAxx
+1455 821 1 3 5 15 55 455 1455 1455 1455 110 111 ZDAAAA PFBAAA HHHHxx
+9846 822 0 2 6 6 46 846 1846 4846 9846 92 93 SOAAAA QFBAAA OOOOxx
+5295 823 1 3 5 15 95 295 1295 295 5295 190 191 RVAAAA RFBAAA VVVVxx
+2826 824 0 2 6 6 26 826 826 2826 2826 52 53 SEAAAA SFBAAA AAAAxx
+7496 825 0 0 6 16 96 496 1496 2496 7496 192 193 ICAAAA TFBAAA HHHHxx
+3024 826 0 0 4 4 24 24 1024 3024 3024 48 49 IMAAAA UFBAAA OOOOxx
+4945 827 1 1 5 5 45 945 945 4945 4945 90 91 FIAAAA VFBAAA VVVVxx
+4404 828 0 0 4 4 4 404 404 4404 4404 8 9 KNAAAA WFBAAA AAAAxx
+9302 829 0 2 2 2 2 302 1302 4302 9302 4 5 UTAAAA XFBAAA HHHHxx
+1286 830 0 2 6 6 86 286 1286 1286 1286 172 173 MXAAAA YFBAAA OOOOxx
+8435 831 1 3 5 15 35 435 435 3435 8435 70 71 LMAAAA ZFBAAA VVVVxx
+8969 832 1 1 9 9 69 969 969 3969 8969 138 139 ZGAAAA AGBAAA AAAAxx
+3302 833 0 2 2 2 2 302 1302 3302 3302 4 5 AXAAAA BGBAAA HHHHxx
+9753 834 1 1 3 13 53 753 1753 4753 9753 106 107 DLAAAA CGBAAA OOOOxx
+9374 835 0 2 4 14 74 374 1374 4374 9374 148 149 OWAAAA DGBAAA VVVVxx
+4907 836 1 3 7 7 7 907 907 4907 4907 14 15 TGAAAA EGBAAA AAAAxx
+1659 837 1 3 9 19 59 659 1659 1659 1659 118 119 VLAAAA FGBAAA HHHHxx
+5095 838 1 3 5 15 95 95 1095 95 5095 190 191 ZNAAAA GGBAAA OOOOxx
+9446 839 0 2 6 6 46 446 1446 4446 9446 92 93 IZAAAA HGBAAA VVVVxx
+8528 840 0 0 8 8 28 528 528 3528 8528 56 57 AQAAAA IGBAAA AAAAxx
+4890 841 0 2 0 10 90 890 890 4890 4890 180 181 CGAAAA JGBAAA HHHHxx
+1221 842 1 1 1 1 21 221 1221 1221 1221 42 43 ZUAAAA KGBAAA OOOOxx
+5583 843 1 3 3 3 83 583 1583 583 5583 166 167 TGAAAA LGBAAA VVVVxx
+7303 844 1 3 3 3 3 303 1303 2303 7303 6 7 XUAAAA MGBAAA AAAAxx
+406 845 0 2 6 6 6 406 406 406 406 12 13 QPAAAA NGBAAA HHHHxx
+7542 846 0 2 2 2 42 542 1542 2542 7542 84 85 CEAAAA OGBAAA OOOOxx
+9507 847 1 3 7 7 7 507 1507 4507 9507 14 15 RBAAAA PGBAAA VVVVxx
+9511 848 1 3 1 11 11 511 1511 4511 9511 22 23 VBAAAA QGBAAA AAAAxx
+1373 849 1 1 3 13 73 373 1373 1373 1373 146 147 VAAAAA RGBAAA HHHHxx
+6556 850 0 0 6 16 56 556 556 1556 6556 112 113 ESAAAA SGBAAA OOOOxx
+4117 851 1 1 7 17 17 117 117 4117 4117 34 35 JCAAAA TGBAAA VVVVxx
+7794 852 0 2 4 14 94 794 1794 2794 7794 188 189 UNAAAA UGBAAA AAAAxx
+7170 853 0 2 0 10 70 170 1170 2170 7170 140 141 UPAAAA VGBAAA HHHHxx
+5809 854 1 1 9 9 9 809 1809 809 5809 18 19 LPAAAA WGBAAA OOOOxx
+7828 855 0 0 8 8 28 828 1828 2828 7828 56 57 CPAAAA XGBAAA VVVVxx
+8046 856 0 2 6 6 46 46 46 3046 8046 92 93 MXAAAA YGBAAA AAAAxx
+4833 857 1 1 3 13 33 833 833 4833 4833 66 67 XDAAAA ZGBAAA HHHHxx
+2107 858 1 3 7 7 7 107 107 2107 2107 14 15 BDAAAA AHBAAA OOOOxx
+4276 859 0 0 6 16 76 276 276 4276 4276 152 153 MIAAAA BHBAAA VVVVxx
+9536 860 0 0 6 16 36 536 1536 4536 9536 72 73 UCAAAA CHBAAA AAAAxx
+5549 861 1 1 9 9 49 549 1549 549 5549 98 99 LFAAAA DHBAAA HHHHxx
+6427 862 1 3 7 7 27 427 427 1427 6427 54 55 FNAAAA EHBAAA OOOOxx
+1382 863 0 2 2 2 82 382 1382 1382 1382 164 165 EBAAAA FHBAAA VVVVxx
+3256 864 0 0 6 16 56 256 1256 3256 3256 112 113 GVAAAA GHBAAA AAAAxx
+3270 865 0 2 0 10 70 270 1270 3270 3270 140 141 UVAAAA HHBAAA HHHHxx
+4808 866 0 0 8 8 8 808 808 4808 4808 16 17 YCAAAA IHBAAA OOOOxx
+7938 867 0 2 8 18 38 938 1938 2938 7938 76 77 ITAAAA JHBAAA VVVVxx
+4405 868 1 1 5 5 5 405 405 4405 4405 10 11 LNAAAA KHBAAA AAAAxx
+2264 869 0 0 4 4 64 264 264 2264 2264 128 129 CJAAAA LHBAAA HHHHxx
+80 870 0 0 0 0 80 80 80 80 80 160 161 CDAAAA MHBAAA OOOOxx
+320 871 0 0 0 0 20 320 320 320 320 40 41 IMAAAA NHBAAA VVVVxx
+2383 872 1 3 3 3 83 383 383 2383 2383 166 167 RNAAAA OHBAAA AAAAxx
+3146 873 0 2 6 6 46 146 1146 3146 3146 92 93 ARAAAA PHBAAA HHHHxx
+6911 874 1 3 1 11 11 911 911 1911 6911 22 23 VFAAAA QHBAAA OOOOxx
+7377 875 1 1 7 17 77 377 1377 2377 7377 154 155 TXAAAA RHBAAA VVVVxx
+9965 876 1 1 5 5 65 965 1965 4965 9965 130 131 HTAAAA SHBAAA AAAAxx
+8361 877 1 1 1 1 61 361 361 3361 8361 122 123 PJAAAA THBAAA HHHHxx
+9417 878 1 1 7 17 17 417 1417 4417 9417 34 35 FYAAAA UHBAAA OOOOxx
+2483 879 1 3 3 3 83 483 483 2483 2483 166 167 NRAAAA VHBAAA VVVVxx
+9843 880 1 3 3 3 43 843 1843 4843 9843 86 87 POAAAA WHBAAA AAAAxx
+6395 881 1 3 5 15 95 395 395 1395 6395 190 191 ZLAAAA XHBAAA HHHHxx
+6444 882 0 0 4 4 44 444 444 1444 6444 88 89 WNAAAA YHBAAA OOOOxx
+1820 883 0 0 0 0 20 820 1820 1820 1820 40 41 ASAAAA ZHBAAA VVVVxx
+2768 884 0 0 8 8 68 768 768 2768 2768 136 137 MCAAAA AIBAAA AAAAxx
+5413 885 1 1 3 13 13 413 1413 413 5413 26 27 FAAAAA BIBAAA HHHHxx
+2923 886 1 3 3 3 23 923 923 2923 2923 46 47 LIAAAA CIBAAA OOOOxx
+5286 887 0 2 6 6 86 286 1286 286 5286 172 173 IVAAAA DIBAAA VVVVxx
+6126 888 0 2 6 6 26 126 126 1126 6126 52 53 QBAAAA EIBAAA AAAAxx
+8343 889 1 3 3 3 43 343 343 3343 8343 86 87 XIAAAA FIBAAA HHHHxx
+6010 890 0 2 0 10 10 10 10 1010 6010 20 21 EXAAAA GIBAAA OOOOxx
+4177 891 1 1 7 17 77 177 177 4177 4177 154 155 REAAAA HIBAAA VVVVxx
+5808 892 0 0 8 8 8 808 1808 808 5808 16 17 KPAAAA IIBAAA AAAAxx
+4859 893 1 3 9 19 59 859 859 4859 4859 118 119 XEAAAA JIBAAA HHHHxx
+9252 894 0 0 2 12 52 252 1252 4252 9252 104 105 WRAAAA KIBAAA OOOOxx
+2941 895 1 1 1 1 41 941 941 2941 2941 82 83 DJAAAA LIBAAA VVVVxx
+8693 896 1 1 3 13 93 693 693 3693 8693 186 187 JWAAAA MIBAAA AAAAxx
+4432 897 0 0 2 12 32 432 432 4432 4432 64 65 MOAAAA NIBAAA HHHHxx
+2371 898 1 3 1 11 71 371 371 2371 2371 142 143 FNAAAA OIBAAA OOOOxx
+7546 899 0 2 6 6 46 546 1546 2546 7546 92 93 GEAAAA PIBAAA VVVVxx
+1369 900 1 1 9 9 69 369 1369 1369 1369 138 139 RAAAAA QIBAAA AAAAxx
+4687 901 1 3 7 7 87 687 687 4687 4687 174 175 HYAAAA RIBAAA HHHHxx
+8941 902 1 1 1 1 41 941 941 3941 8941 82 83 XFAAAA SIBAAA OOOOxx
+226 903 0 2 6 6 26 226 226 226 226 52 53 SIAAAA TIBAAA VVVVxx
+3493 904 1 1 3 13 93 493 1493 3493 3493 186 187 JEAAAA UIBAAA AAAAxx
+6433 905 1 1 3 13 33 433 433 1433 6433 66 67 LNAAAA VIBAAA HHHHxx
+9189 906 1 1 9 9 89 189 1189 4189 9189 178 179 LPAAAA WIBAAA OOOOxx
+6027 907 1 3 7 7 27 27 27 1027 6027 54 55 VXAAAA XIBAAA VVVVxx
+4615 908 1 3 5 15 15 615 615 4615 4615 30 31 NVAAAA YIBAAA AAAAxx
+5320 909 0 0 0 0 20 320 1320 320 5320 40 41 QWAAAA ZIBAAA HHHHxx
+7002 910 0 2 2 2 2 2 1002 2002 7002 4 5 IJAAAA AJBAAA OOOOxx
+7367 911 1 3 7 7 67 367 1367 2367 7367 134 135 JXAAAA BJBAAA VVVVxx
+289 912 1 1 9 9 89 289 289 289 289 178 179 DLAAAA CJBAAA AAAAxx
+407 913 1 3 7 7 7 407 407 407 407 14 15 RPAAAA DJBAAA HHHHxx
+504 914 0 0 4 4 4 504 504 504 504 8 9 KTAAAA EJBAAA OOOOxx
+8301 915 1 1 1 1 1 301 301 3301 8301 2 3 HHAAAA FJBAAA VVVVxx
+1396 916 0 0 6 16 96 396 1396 1396 1396 192 193 SBAAAA GJBAAA AAAAxx
+4794 917 0 2 4 14 94 794 794 4794 4794 188 189 KCAAAA HJBAAA HHHHxx
+6400 918 0 0 0 0 0 400 400 1400 6400 0 1 EMAAAA IJBAAA OOOOxx
+1275 919 1 3 5 15 75 275 1275 1275 1275 150 151 BXAAAA JJBAAA VVVVxx
+5797 920 1 1 7 17 97 797 1797 797 5797 194 195 ZOAAAA KJBAAA AAAAxx
+2221 921 1 1 1 1 21 221 221 2221 2221 42 43 LHAAAA LJBAAA HHHHxx
+2504 922 0 0 4 4 4 504 504 2504 2504 8 9 ISAAAA MJBAAA OOOOxx
+2143 923 1 3 3 3 43 143 143 2143 2143 86 87 LEAAAA NJBAAA VVVVxx
+1083 924 1 3 3 3 83 83 1083 1083 1083 166 167 RPAAAA OJBAAA AAAAxx
+6148 925 0 0 8 8 48 148 148 1148 6148 96 97 MCAAAA PJBAAA HHHHxx
+3612 926 0 0 2 12 12 612 1612 3612 3612 24 25 YIAAAA QJBAAA OOOOxx
+9499 927 1 3 9 19 99 499 1499 4499 9499 198 199 JBAAAA RJBAAA VVVVxx
+5773 928 1 1 3 13 73 773 1773 773 5773 146 147 BOAAAA SJBAAA AAAAxx
+1014 929 0 2 4 14 14 14 1014 1014 1014 28 29 ANAAAA TJBAAA HHHHxx
+1427 930 1 3 7 7 27 427 1427 1427 1427 54 55 XCAAAA UJBAAA OOOOxx
+6770 931 0 2 0 10 70 770 770 1770 6770 140 141 KAAAAA VJBAAA VVVVxx
+9042 932 0 2 2 2 42 42 1042 4042 9042 84 85 UJAAAA WJBAAA AAAAxx
+9892 933 0 0 2 12 92 892 1892 4892 9892 184 185 MQAAAA XJBAAA HHHHxx
+1771 934 1 3 1 11 71 771 1771 1771 1771 142 143 DQAAAA YJBAAA OOOOxx
+7392 935 0 0 2 12 92 392 1392 2392 7392 184 185 IYAAAA ZJBAAA VVVVxx
+4465 936 1 1 5 5 65 465 465 4465 4465 130 131 TPAAAA AKBAAA AAAAxx
+278 937 0 2 8 18 78 278 278 278 278 156 157 SKAAAA BKBAAA HHHHxx
+7776 938 0 0 6 16 76 776 1776 2776 7776 152 153 CNAAAA CKBAAA OOOOxx
+3763 939 1 3 3 3 63 763 1763 3763 3763 126 127 TOAAAA DKBAAA VVVVxx
+7503 940 1 3 3 3 3 503 1503 2503 7503 6 7 PCAAAA EKBAAA AAAAxx
+3793 941 1 1 3 13 93 793 1793 3793 3793 186 187 XPAAAA FKBAAA HHHHxx
+6510 942 0 2 0 10 10 510 510 1510 6510 20 21 KQAAAA GKBAAA OOOOxx
+7641 943 1 1 1 1 41 641 1641 2641 7641 82 83 XHAAAA HKBAAA VVVVxx
+3228 944 0 0 8 8 28 228 1228 3228 3228 56 57 EUAAAA IKBAAA AAAAxx
+194 945 0 2 4 14 94 194 194 194 194 188 189 MHAAAA JKBAAA HHHHxx
+8555 946 1 3 5 15 55 555 555 3555 8555 110 111 BRAAAA KKBAAA OOOOxx
+4997 947 1 1 7 17 97 997 997 4997 4997 194 195 FKAAAA LKBAAA VVVVxx
+8687 948 1 3 7 7 87 687 687 3687 8687 174 175 DWAAAA MKBAAA AAAAxx
+6632 949 0 0 2 12 32 632 632 1632 6632 64 65 CVAAAA NKBAAA HHHHxx
+9607 950 1 3 7 7 7 607 1607 4607 9607 14 15 NFAAAA OKBAAA OOOOxx
+6201 951 1 1 1 1 1 201 201 1201 6201 2 3 NEAAAA PKBAAA VVVVxx
+857 952 1 1 7 17 57 857 857 857 857 114 115 ZGAAAA QKBAAA AAAAxx
+5623 953 1 3 3 3 23 623 1623 623 5623 46 47 HIAAAA RKBAAA HHHHxx
+5979 954 1 3 9 19 79 979 1979 979 5979 158 159 ZVAAAA SKBAAA OOOOxx
+2201 955 1 1 1 1 1 201 201 2201 2201 2 3 RGAAAA TKBAAA VVVVxx
+3166 956 0 2 6 6 66 166 1166 3166 3166 132 133 URAAAA UKBAAA AAAAxx
+6249 957 1 1 9 9 49 249 249 1249 6249 98 99 JGAAAA VKBAAA HHHHxx
+3271 958 1 3 1 11 71 271 1271 3271 3271 142 143 VVAAAA WKBAAA OOOOxx
+7777 959 1 1 7 17 77 777 1777 2777 7777 154 155 DNAAAA XKBAAA VVVVxx
+6732 960 0 0 2 12 32 732 732 1732 6732 64 65 YYAAAA YKBAAA AAAAxx
+6297 961 1 1 7 17 97 297 297 1297 6297 194 195 FIAAAA ZKBAAA HHHHxx
+5685 962 1 1 5 5 85 685 1685 685 5685 170 171 RKAAAA ALBAAA OOOOxx
+9931 963 1 3 1 11 31 931 1931 4931 9931 62 63 ZRAAAA BLBAAA VVVVxx
+7485 964 1 1 5 5 85 485 1485 2485 7485 170 171 XBAAAA CLBAAA AAAAxx
+386 965 0 2 6 6 86 386 386 386 386 172 173 WOAAAA DLBAAA HHHHxx
+8204 966 0 0 4 4 4 204 204 3204 8204 8 9 ODAAAA ELBAAA OOOOxx
+3606 967 0 2 6 6 6 606 1606 3606 3606 12 13 SIAAAA FLBAAA VVVVxx
+1692 968 0 0 2 12 92 692 1692 1692 1692 184 185 CNAAAA GLBAAA AAAAxx
+3002 969 0 2 2 2 2 2 1002 3002 3002 4 5 MLAAAA HLBAAA HHHHxx
+9676 970 0 0 6 16 76 676 1676 4676 9676 152 153 EIAAAA ILBAAA OOOOxx
+915 971 1 3 5 15 15 915 915 915 915 30 31 FJAAAA JLBAAA VVVVxx
+7706 972 0 2 6 6 6 706 1706 2706 7706 12 13 KKAAAA KLBAAA AAAAxx
+6080 973 0 0 0 0 80 80 80 1080 6080 160 161 WZAAAA LLBAAA HHHHxx
+1860 974 0 0 0 0 60 860 1860 1860 1860 120 121 OTAAAA MLBAAA OOOOxx
+1444 975 0 0 4 4 44 444 1444 1444 1444 88 89 ODAAAA NLBAAA VVVVxx
+7208 976 0 0 8 8 8 208 1208 2208 7208 16 17 GRAAAA OLBAAA AAAAxx
+8554 977 0 2 4 14 54 554 554 3554 8554 108 109 ARAAAA PLBAAA HHHHxx
+2028 978 0 0 8 8 28 28 28 2028 2028 56 57 AAAAAA QLBAAA OOOOxx
+9893 979 1 1 3 13 93 893 1893 4893 9893 186 187 NQAAAA RLBAAA VVVVxx
+4740 980 0 0 0 0 40 740 740 4740 4740 80 81 IAAAAA SLBAAA AAAAxx
+6186 981 0 2 6 6 86 186 186 1186 6186 172 173 YDAAAA TLBAAA HHHHxx
+6357 982 1 1 7 17 57 357 357 1357 6357 114 115 NKAAAA ULBAAA OOOOxx
+3699 983 1 3 9 19 99 699 1699 3699 3699 198 199 HMAAAA VLBAAA VVVVxx
+7620 984 0 0 0 0 20 620 1620 2620 7620 40 41 CHAAAA WLBAAA AAAAxx
+921 985 1 1 1 1 21 921 921 921 921 42 43 LJAAAA XLBAAA HHHHxx
+5506 986 0 2 6 6 6 506 1506 506 5506 12 13 UDAAAA YLBAAA OOOOxx
+8851 987 1 3 1 11 51 851 851 3851 8851 102 103 LCAAAA ZLBAAA VVVVxx
+3205 988 1 1 5 5 5 205 1205 3205 3205 10 11 HTAAAA AMBAAA AAAAxx
+1956 989 0 0 6 16 56 956 1956 1956 1956 112 113 GXAAAA BMBAAA HHHHxx
+6272 990 0 0 2 12 72 272 272 1272 6272 144 145 GHAAAA CMBAAA OOOOxx
+1509 991 1 1 9 9 9 509 1509 1509 1509 18 19 BGAAAA DMBAAA VVVVxx
+53 992 1 1 3 13 53 53 53 53 53 106 107 BCAAAA EMBAAA AAAAxx
+213 993 1 1 3 13 13 213 213 213 213 26 27 FIAAAA FMBAAA HHHHxx
+4924 994 0 0 4 4 24 924 924 4924 4924 48 49 KHAAAA GMBAAA OOOOxx
+2097 995 1 1 7 17 97 97 97 2097 2097 194 195 RCAAAA HMBAAA VVVVxx
+4607 996 1 3 7 7 7 607 607 4607 4607 14 15 FVAAAA IMBAAA AAAAxx
+1582 997 0 2 2 2 82 582 1582 1582 1582 164 165 WIAAAA JMBAAA HHHHxx
+6643 998 1 3 3 3 43 643 643 1643 6643 86 87 NVAAAA KMBAAA OOOOxx
+2238 999 0 2 8 18 38 238 238 2238 2238 76 77 CIAAAA LMBAAA VVVVxx
+2942 1000 0 2 2 2 42 942 942 2942 2942 84 85 EJAAAA MMBAAA AAAAxx
+1655 1001 1 3 5 15 55 655 1655 1655 1655 110 111 RLAAAA NMBAAA HHHHxx
+3226 1002 0 2 6 6 26 226 1226 3226 3226 52 53 CUAAAA OMBAAA OOOOxx
+4263 1003 1 3 3 3 63 263 263 4263 4263 126 127 ZHAAAA PMBAAA VVVVxx
+960 1004 0 0 0 0 60 960 960 960 960 120 121 YKAAAA QMBAAA AAAAxx
+1213 1005 1 1 3 13 13 213 1213 1213 1213 26 27 RUAAAA RMBAAA HHHHxx
+1845 1006 1 1 5 5 45 845 1845 1845 1845 90 91 ZSAAAA SMBAAA OOOOxx
+6944 1007 0 0 4 4 44 944 944 1944 6944 88 89 CHAAAA TMBAAA VVVVxx
+5284 1008 0 0 4 4 84 284 1284 284 5284 168 169 GVAAAA UMBAAA AAAAxx
+188 1009 0 0 8 8 88 188 188 188 188 176 177 GHAAAA VMBAAA HHHHxx
+748 1010 0 0 8 8 48 748 748 748 748 96 97 UCAAAA WMBAAA OOOOxx
+2226 1011 0 2 6 6 26 226 226 2226 2226 52 53 QHAAAA XMBAAA VVVVxx
+7342 1012 0 2 2 2 42 342 1342 2342 7342 84 85 KWAAAA YMBAAA AAAAxx
+6120 1013 0 0 0 0 20 120 120 1120 6120 40 41 KBAAAA ZMBAAA HHHHxx
+536 1014 0 0 6 16 36 536 536 536 536 72 73 QUAAAA ANBAAA OOOOxx
+3239 1015 1 3 9 19 39 239 1239 3239 3239 78 79 PUAAAA BNBAAA VVVVxx
+2832 1016 0 0 2 12 32 832 832 2832 2832 64 65 YEAAAA CNBAAA AAAAxx
+5296 1017 0 0 6 16 96 296 1296 296 5296 192 193 SVAAAA DNBAAA HHHHxx
+5795 1018 1 3 5 15 95 795 1795 795 5795 190 191 XOAAAA ENBAAA OOOOxx
+6290 1019 0 2 0 10 90 290 290 1290 6290 180 181 YHAAAA FNBAAA VVVVxx
+4916 1020 0 0 6 16 16 916 916 4916 4916 32 33 CHAAAA GNBAAA AAAAxx
+8366 1021 0 2 6 6 66 366 366 3366 8366 132 133 UJAAAA HNBAAA HHHHxx
+4248 1022 0 0 8 8 48 248 248 4248 4248 96 97 KHAAAA INBAAA OOOOxx
+6460 1023 0 0 0 0 60 460 460 1460 6460 120 121 MOAAAA JNBAAA VVVVxx
+9296 1024 0 0 6 16 96 296 1296 4296 9296 192 193 OTAAAA KNBAAA AAAAxx
+3486 1025 0 2 6 6 86 486 1486 3486 3486 172 173 CEAAAA LNBAAA HHHHxx
+5664 1026 0 0 4 4 64 664 1664 664 5664 128 129 WJAAAA MNBAAA OOOOxx
+7624 1027 0 0 4 4 24 624 1624 2624 7624 48 49 GHAAAA NNBAAA VVVVxx
+2790 1028 0 2 0 10 90 790 790 2790 2790 180 181 IDAAAA ONBAAA AAAAxx
+682 1029 0 2 2 2 82 682 682 682 682 164 165 GAAAAA PNBAAA HHHHxx
+6412 1030 0 0 2 12 12 412 412 1412 6412 24 25 QMAAAA QNBAAA OOOOxx
+6882 1031 0 2 2 2 82 882 882 1882 6882 164 165 SEAAAA RNBAAA VVVVxx
+1332 1032 0 0 2 12 32 332 1332 1332 1332 64 65 GZAAAA SNBAAA AAAAxx
+4911 1033 1 3 1 11 11 911 911 4911 4911 22 23 XGAAAA TNBAAA HHHHxx
+3528 1034 0 0 8 8 28 528 1528 3528 3528 56 57 SFAAAA UNBAAA OOOOxx
+271 1035 1 3 1 11 71 271 271 271 271 142 143 LKAAAA VNBAAA VVVVxx
+7007 1036 1 3 7 7 7 7 1007 2007 7007 14 15 NJAAAA WNBAAA AAAAxx
+2198 1037 0 2 8 18 98 198 198 2198 2198 196 197 OGAAAA XNBAAA HHHHxx
+4266 1038 0 2 6 6 66 266 266 4266 4266 132 133 CIAAAA YNBAAA OOOOxx
+9867 1039 1 3 7 7 67 867 1867 4867 9867 134 135 NPAAAA ZNBAAA VVVVxx
+7602 1040 0 2 2 2 2 602 1602 2602 7602 4 5 KGAAAA AOBAAA AAAAxx
+7521 1041 1 1 1 1 21 521 1521 2521 7521 42 43 HDAAAA BOBAAA HHHHxx
+7200 1042 0 0 0 0 0 200 1200 2200 7200 0 1 YQAAAA COBAAA OOOOxx
+4816 1043 0 0 6 16 16 816 816 4816 4816 32 33 GDAAAA DOBAAA VVVVxx
+1669 1044 1 1 9 9 69 669 1669 1669 1669 138 139 FMAAAA EOBAAA AAAAxx
+4764 1045 0 0 4 4 64 764 764 4764 4764 128 129 GBAAAA FOBAAA HHHHxx
+7393 1046 1 1 3 13 93 393 1393 2393 7393 186 187 JYAAAA GOBAAA OOOOxx
+7434 1047 0 2 4 14 34 434 1434 2434 7434 68 69 YZAAAA HOBAAA VVVVxx
+9079 1048 1 3 9 19 79 79 1079 4079 9079 158 159 FLAAAA IOBAAA AAAAxx
+9668 1049 0 0 8 8 68 668 1668 4668 9668 136 137 WHAAAA JOBAAA HHHHxx
+7184 1050 0 0 4 4 84 184 1184 2184 7184 168 169 IQAAAA KOBAAA OOOOxx
+7347 1051 1 3 7 7 47 347 1347 2347 7347 94 95 PWAAAA LOBAAA VVVVxx
+951 1052 1 3 1 11 51 951 951 951 951 102 103 PKAAAA MOBAAA AAAAxx
+4513 1053 1 1 3 13 13 513 513 4513 4513 26 27 PRAAAA NOBAAA HHHHxx
+2692 1054 0 0 2 12 92 692 692 2692 2692 184 185 OZAAAA OOBAAA OOOOxx
+9930 1055 0 2 0 10 30 930 1930 4930 9930 60 61 YRAAAA POBAAA VVVVxx
+4516 1056 0 0 6 16 16 516 516 4516 4516 32 33 SRAAAA QOBAAA AAAAxx
+1592 1057 0 0 2 12 92 592 1592 1592 1592 184 185 GJAAAA ROBAAA HHHHxx
+6312 1058 0 0 2 12 12 312 312 1312 6312 24 25 UIAAAA SOBAAA OOOOxx
+185 1059 1 1 5 5 85 185 185 185 185 170 171 DHAAAA TOBAAA VVVVxx
+1848 1060 0 0 8 8 48 848 1848 1848 1848 96 97 CTAAAA UOBAAA AAAAxx
+5844 1061 0 0 4 4 44 844 1844 844 5844 88 89 UQAAAA VOBAAA HHHHxx
+1666 1062 0 2 6 6 66 666 1666 1666 1666 132 133 CMAAAA WOBAAA OOOOxx
+5864 1063 0 0 4 4 64 864 1864 864 5864 128 129 ORAAAA XOBAAA VVVVxx
+1004 1064 0 0 4 4 4 4 1004 1004 1004 8 9 QMAAAA YOBAAA AAAAxx
+1758 1065 0 2 8 18 58 758 1758 1758 1758 116 117 QPAAAA ZOBAAA HHHHxx
+8823 1066 1 3 3 3 23 823 823 3823 8823 46 47 JBAAAA APBAAA OOOOxx
+129 1067 1 1 9 9 29 129 129 129 129 58 59 ZEAAAA BPBAAA VVVVxx
+5703 1068 1 3 3 3 3 703 1703 703 5703 6 7 JLAAAA CPBAAA AAAAxx
+3331 1069 1 3 1 11 31 331 1331 3331 3331 62 63 DYAAAA DPBAAA HHHHxx
+5791 1070 1 3 1 11 91 791 1791 791 5791 182 183 TOAAAA EPBAAA OOOOxx
+4421 1071 1 1 1 1 21 421 421 4421 4421 42 43 BOAAAA FPBAAA VVVVxx
+9740 1072 0 0 0 0 40 740 1740 4740 9740 80 81 QKAAAA GPBAAA AAAAxx
+798 1073 0 2 8 18 98 798 798 798 798 196 197 SEAAAA HPBAAA HHHHxx
+571 1074 1 3 1 11 71 571 571 571 571 142 143 ZVAAAA IPBAAA OOOOxx
+7084 1075 0 0 4 4 84 84 1084 2084 7084 168 169 MMAAAA JPBAAA VVVVxx
+650 1076 0 2 0 10 50 650 650 650 650 100 101 AZAAAA KPBAAA AAAAxx
+1467 1077 1 3 7 7 67 467 1467 1467 1467 134 135 LEAAAA LPBAAA HHHHxx
+5446 1078 0 2 6 6 46 446 1446 446 5446 92 93 MBAAAA MPBAAA OOOOxx
+830 1079 0 2 0 10 30 830 830 830 830 60 61 YFAAAA NPBAAA VVVVxx
+5516 1080 0 0 6 16 16 516 1516 516 5516 32 33 EEAAAA OPBAAA AAAAxx
+8520 1081 0 0 0 0 20 520 520 3520 8520 40 41 SPAAAA PPBAAA HHHHxx
+1152 1082 0 0 2 12 52 152 1152 1152 1152 104 105 ISAAAA QPBAAA OOOOxx
+862 1083 0 2 2 2 62 862 862 862 862 124 125 EHAAAA RPBAAA VVVVxx
+454 1084 0 2 4 14 54 454 454 454 454 108 109 MRAAAA SPBAAA AAAAxx
+9956 1085 0 0 6 16 56 956 1956 4956 9956 112 113 YSAAAA TPBAAA HHHHxx
+1654 1086 0 2 4 14 54 654 1654 1654 1654 108 109 QLAAAA UPBAAA OOOOxx
+257 1087 1 1 7 17 57 257 257 257 257 114 115 XJAAAA VPBAAA VVVVxx
+5469 1088 1 1 9 9 69 469 1469 469 5469 138 139 JCAAAA WPBAAA AAAAxx
+9075 1089 1 3 5 15 75 75 1075 4075 9075 150 151 BLAAAA XPBAAA HHHHxx
+7799 1090 1 3 9 19 99 799 1799 2799 7799 198 199 ZNAAAA YPBAAA OOOOxx
+2001 1091 1 1 1 1 1 1 1 2001 2001 2 3 ZYAAAA ZPBAAA VVVVxx
+9786 1092 0 2 6 6 86 786 1786 4786 9786 172 173 KMAAAA AQBAAA AAAAxx
+7281 1093 1 1 1 1 81 281 1281 2281 7281 162 163 BUAAAA BQBAAA HHHHxx
+5137 1094 1 1 7 17 37 137 1137 137 5137 74 75 PPAAAA CQBAAA OOOOxx
+4053 1095 1 1 3 13 53 53 53 4053 4053 106 107 XZAAAA DQBAAA VVVVxx
+7911 1096 1 3 1 11 11 911 1911 2911 7911 22 23 HSAAAA EQBAAA AAAAxx
+4298 1097 0 2 8 18 98 298 298 4298 4298 196 197 IJAAAA FQBAAA HHHHxx
+4805 1098 1 1 5 5 5 805 805 4805 4805 10 11 VCAAAA GQBAAA OOOOxx
+9038 1099 0 2 8 18 38 38 1038 4038 9038 76 77 QJAAAA HQBAAA VVVVxx
+8023 1100 1 3 3 3 23 23 23 3023 8023 46 47 PWAAAA IQBAAA AAAAxx
+6595 1101 1 3 5 15 95 595 595 1595 6595 190 191 RTAAAA JQBAAA HHHHxx
+9831 1102 1 3 1 11 31 831 1831 4831 9831 62 63 DOAAAA KQBAAA OOOOxx
+788 1103 0 0 8 8 88 788 788 788 788 176 177 IEAAAA LQBAAA VVVVxx
+902 1104 0 2 2 2 2 902 902 902 902 4 5 SIAAAA MQBAAA AAAAxx
+9137 1105 1 1 7 17 37 137 1137 4137 9137 74 75 LNAAAA NQBAAA HHHHxx
+1744 1106 0 0 4 4 44 744 1744 1744 1744 88 89 CPAAAA OQBAAA OOOOxx
+7285 1107 1 1 5 5 85 285 1285 2285 7285 170 171 FUAAAA PQBAAA VVVVxx
+7006 1108 0 2 6 6 6 6 1006 2006 7006 12 13 MJAAAA QQBAAA AAAAxx
+9236 1109 0 0 6 16 36 236 1236 4236 9236 72 73 GRAAAA RQBAAA HHHHxx
+5472 1110 0 0 2 12 72 472 1472 472 5472 144 145 MCAAAA SQBAAA OOOOxx
+7975 1111 1 3 5 15 75 975 1975 2975 7975 150 151 TUAAAA TQBAAA VVVVxx
+4181 1112 1 1 1 1 81 181 181 4181 4181 162 163 VEAAAA UQBAAA AAAAxx
+7677 1113 1 1 7 17 77 677 1677 2677 7677 154 155 HJAAAA VQBAAA HHHHxx
+35 1114 1 3 5 15 35 35 35 35 35 70 71 JBAAAA WQBAAA OOOOxx
+6813 1115 1 1 3 13 13 813 813 1813 6813 26 27 BCAAAA XQBAAA VVVVxx
+6618 1116 0 2 8 18 18 618 618 1618 6618 36 37 OUAAAA YQBAAA AAAAxx
+8069 1117 1 1 9 9 69 69 69 3069 8069 138 139 JYAAAA ZQBAAA HHHHxx
+3071 1118 1 3 1 11 71 71 1071 3071 3071 142 143 DOAAAA ARBAAA OOOOxx
+4390 1119 0 2 0 10 90 390 390 4390 4390 180 181 WMAAAA BRBAAA VVVVxx
+7764 1120 0 0 4 4 64 764 1764 2764 7764 128 129 QMAAAA CRBAAA AAAAxx
+8163 1121 1 3 3 3 63 163 163 3163 8163 126 127 ZBAAAA DRBAAA HHHHxx
+1961 1122 1 1 1 1 61 961 1961 1961 1961 122 123 LXAAAA ERBAAA OOOOxx
+1103 1123 1 3 3 3 3 103 1103 1103 1103 6 7 LQAAAA FRBAAA VVVVxx
+5486 1124 0 2 6 6 86 486 1486 486 5486 172 173 ADAAAA GRBAAA AAAAxx
+9513 1125 1 1 3 13 13 513 1513 4513 9513 26 27 XBAAAA HRBAAA HHHHxx
+7311 1126 1 3 1 11 11 311 1311 2311 7311 22 23 FVAAAA IRBAAA OOOOxx
+4144 1127 0 0 4 4 44 144 144 4144 4144 88 89 KDAAAA JRBAAA VVVVxx
+7901 1128 1 1 1 1 1 901 1901 2901 7901 2 3 XRAAAA KRBAAA AAAAxx
+4629 1129 1 1 9 9 29 629 629 4629 4629 58 59 BWAAAA LRBAAA HHHHxx
+6858 1130 0 2 8 18 58 858 858 1858 6858 116 117 UDAAAA MRBAAA OOOOxx
+125 1131 1 1 5 5 25 125 125 125 125 50 51 VEAAAA NRBAAA VVVVxx
+3834 1132 0 2 4 14 34 834 1834 3834 3834 68 69 MRAAAA ORBAAA AAAAxx
+8155 1133 1 3 5 15 55 155 155 3155 8155 110 111 RBAAAA PRBAAA HHHHxx
+8230 1134 0 2 0 10 30 230 230 3230 8230 60 61 OEAAAA QRBAAA OOOOxx
+744 1135 0 0 4 4 44 744 744 744 744 88 89 QCAAAA RRBAAA VVVVxx
+357 1136 1 1 7 17 57 357 357 357 357 114 115 TNAAAA SRBAAA AAAAxx
+2159 1137 1 3 9 19 59 159 159 2159 2159 118 119 BFAAAA TRBAAA HHHHxx
+8559 1138 1 3 9 19 59 559 559 3559 8559 118 119 FRAAAA URBAAA OOOOxx
+6866 1139 0 2 6 6 66 866 866 1866 6866 132 133 CEAAAA VRBAAA VVVVxx
+3863 1140 1 3 3 3 63 863 1863 3863 3863 126 127 PSAAAA WRBAAA AAAAxx
+4193 1141 1 1 3 13 93 193 193 4193 4193 186 187 HFAAAA XRBAAA HHHHxx
+3277 1142 1 1 7 17 77 277 1277 3277 3277 154 155 BWAAAA YRBAAA OOOOxx
+5577 1143 1 1 7 17 77 577 1577 577 5577 154 155 NGAAAA ZRBAAA VVVVxx
+9503 1144 1 3 3 3 3 503 1503 4503 9503 6 7 NBAAAA ASBAAA AAAAxx
+7642 1145 0 2 2 2 42 642 1642 2642 7642 84 85 YHAAAA BSBAAA HHHHxx
+6197 1146 1 1 7 17 97 197 197 1197 6197 194 195 JEAAAA CSBAAA OOOOxx
+8995 1147 1 3 5 15 95 995 995 3995 8995 190 191 ZHAAAA DSBAAA VVVVxx
+440 1148 0 0 0 0 40 440 440 440 440 80 81 YQAAAA ESBAAA AAAAxx
+8418 1149 0 2 8 18 18 418 418 3418 8418 36 37 ULAAAA FSBAAA HHHHxx
+8531 1150 1 3 1 11 31 531 531 3531 8531 62 63 DQAAAA GSBAAA OOOOxx
+3790 1151 0 2 0 10 90 790 1790 3790 3790 180 181 UPAAAA HSBAAA VVVVxx
+7610 1152 0 2 0 10 10 610 1610 2610 7610 20 21 SGAAAA ISBAAA AAAAxx
+1252 1153 0 0 2 12 52 252 1252 1252 1252 104 105 EWAAAA JSBAAA HHHHxx
+7559 1154 1 3 9 19 59 559 1559 2559 7559 118 119 TEAAAA KSBAAA OOOOxx
+9945 1155 1 1 5 5 45 945 1945 4945 9945 90 91 NSAAAA LSBAAA VVVVxx
+9023 1156 1 3 3 3 23 23 1023 4023 9023 46 47 BJAAAA MSBAAA AAAAxx
+3516 1157 0 0 6 16 16 516 1516 3516 3516 32 33 GFAAAA NSBAAA HHHHxx
+4671 1158 1 3 1 11 71 671 671 4671 4671 142 143 RXAAAA OSBAAA OOOOxx
+1465 1159 1 1 5 5 65 465 1465 1465 1465 130 131 JEAAAA PSBAAA VVVVxx
+9515 1160 1 3 5 15 15 515 1515 4515 9515 30 31 ZBAAAA QSBAAA AAAAxx
+3242 1161 0 2 2 2 42 242 1242 3242 3242 84 85 SUAAAA RSBAAA HHHHxx
+1732 1162 0 0 2 12 32 732 1732 1732 1732 64 65 QOAAAA SSBAAA OOOOxx
+1678 1163 0 2 8 18 78 678 1678 1678 1678 156 157 OMAAAA TSBAAA VVVVxx
+1464 1164 0 0 4 4 64 464 1464 1464 1464 128 129 IEAAAA USBAAA AAAAxx
+6546 1165 0 2 6 6 46 546 546 1546 6546 92 93 URAAAA VSBAAA HHHHxx
+4448 1166 0 0 8 8 48 448 448 4448 4448 96 97 CPAAAA WSBAAA OOOOxx
+9847 1167 1 3 7 7 47 847 1847 4847 9847 94 95 TOAAAA XSBAAA VVVVxx
+8264 1168 0 0 4 4 64 264 264 3264 8264 128 129 WFAAAA YSBAAA AAAAxx
+1620 1169 0 0 0 0 20 620 1620 1620 1620 40 41 IKAAAA ZSBAAA HHHHxx
+9388 1170 0 0 8 8 88 388 1388 4388 9388 176 177 CXAAAA ATBAAA OOOOxx
+6445 1171 1 1 5 5 45 445 445 1445 6445 90 91 XNAAAA BTBAAA VVVVxx
+4789 1172 1 1 9 9 89 789 789 4789 4789 178 179 FCAAAA CTBAAA AAAAxx
+1562 1173 0 2 2 2 62 562 1562 1562 1562 124 125 CIAAAA DTBAAA HHHHxx
+7305 1174 1 1 5 5 5 305 1305 2305 7305 10 11 ZUAAAA ETBAAA OOOOxx
+6344 1175 0 0 4 4 44 344 344 1344 6344 88 89 AKAAAA FTBAAA VVVVxx
+5130 1176 0 2 0 10 30 130 1130 130 5130 60 61 IPAAAA GTBAAA AAAAxx
+3284 1177 0 0 4 4 84 284 1284 3284 3284 168 169 IWAAAA HTBAAA HHHHxx
+6346 1178 0 2 6 6 46 346 346 1346 6346 92 93 CKAAAA ITBAAA OOOOxx
+1061 1179 1 1 1 1 61 61 1061 1061 1061 122 123 VOAAAA JTBAAA VVVVxx
+872 1180 0 0 2 12 72 872 872 872 872 144 145 OHAAAA KTBAAA AAAAxx
+123 1181 1 3 3 3 23 123 123 123 123 46 47 TEAAAA LTBAAA HHHHxx
+7903 1182 1 3 3 3 3 903 1903 2903 7903 6 7 ZRAAAA MTBAAA OOOOxx
+560 1183 0 0 0 0 60 560 560 560 560 120 121 OVAAAA NTBAAA VVVVxx
+4446 1184 0 2 6 6 46 446 446 4446 4446 92 93 APAAAA OTBAAA AAAAxx
+3909 1185 1 1 9 9 9 909 1909 3909 3909 18 19 JUAAAA PTBAAA HHHHxx
+669 1186 1 1 9 9 69 669 669 669 669 138 139 TZAAAA QTBAAA OOOOxx
+7843 1187 1 3 3 3 43 843 1843 2843 7843 86 87 RPAAAA RTBAAA VVVVxx
+2546 1188 0 2 6 6 46 546 546 2546 2546 92 93 YTAAAA STBAAA AAAAxx
+6757 1189 1 1 7 17 57 757 757 1757 6757 114 115 XZAAAA TTBAAA HHHHxx
+466 1190 0 2 6 6 66 466 466 466 466 132 133 YRAAAA UTBAAA OOOOxx
+5556 1191 0 0 6 16 56 556 1556 556 5556 112 113 SFAAAA VTBAAA VVVVxx
+7196 1192 0 0 6 16 96 196 1196 2196 7196 192 193 UQAAAA WTBAAA AAAAxx
+2947 1193 1 3 7 7 47 947 947 2947 2947 94 95 JJAAAA XTBAAA HHHHxx
+6493 1194 1 1 3 13 93 493 493 1493 6493 186 187 TPAAAA YTBAAA OOOOxx
+7203 1195 1 3 3 3 3 203 1203 2203 7203 6 7 BRAAAA ZTBAAA VVVVxx
+3716 1196 0 0 6 16 16 716 1716 3716 3716 32 33 YMAAAA AUBAAA AAAAxx
+8058 1197 0 2 8 18 58 58 58 3058 8058 116 117 YXAAAA BUBAAA HHHHxx
+433 1198 1 1 3 13 33 433 433 433 433 66 67 RQAAAA CUBAAA OOOOxx
+7649 1199 1 1 9 9 49 649 1649 2649 7649 98 99 FIAAAA DUBAAA VVVVxx
+6966 1200 0 2 6 6 66 966 966 1966 6966 132 133 YHAAAA EUBAAA AAAAxx
+553 1201 1 1 3 13 53 553 553 553 553 106 107 HVAAAA FUBAAA HHHHxx
+3677 1202 1 1 7 17 77 677 1677 3677 3677 154 155 LLAAAA GUBAAA OOOOxx
+2344 1203 0 0 4 4 44 344 344 2344 2344 88 89 EMAAAA HUBAAA VVVVxx
+7439 1204 1 3 9 19 39 439 1439 2439 7439 78 79 DAAAAA IUBAAA AAAAxx
+3910 1205 0 2 0 10 10 910 1910 3910 3910 20 21 KUAAAA JUBAAA HHHHxx
+3638 1206 0 2 8 18 38 638 1638 3638 3638 76 77 YJAAAA KUBAAA OOOOxx
+6637 1207 1 1 7 17 37 637 637 1637 6637 74 75 HVAAAA LUBAAA VVVVxx
+4438 1208 0 2 8 18 38 438 438 4438 4438 76 77 SOAAAA MUBAAA AAAAxx
+171 1209 1 3 1 11 71 171 171 171 171 142 143 PGAAAA NUBAAA HHHHxx
+310 1210 0 2 0 10 10 310 310 310 310 20 21 YLAAAA OUBAAA OOOOxx
+2714 1211 0 2 4 14 14 714 714 2714 2714 28 29 KAAAAA PUBAAA VVVVxx
+5199 1212 1 3 9 19 99 199 1199 199 5199 198 199 ZRAAAA QUBAAA AAAAxx
+8005 1213 1 1 5 5 5 5 5 3005 8005 10 11 XVAAAA RUBAAA HHHHxx
+3188 1214 0 0 8 8 88 188 1188 3188 3188 176 177 QSAAAA SUBAAA OOOOxx
+1518 1215 0 2 8 18 18 518 1518 1518 1518 36 37 KGAAAA TUBAAA VVVVxx
+6760 1216 0 0 0 0 60 760 760 1760 6760 120 121 AAAAAA UUBAAA AAAAxx
+9373 1217 1 1 3 13 73 373 1373 4373 9373 146 147 NWAAAA VUBAAA HHHHxx
+1938 1218 0 2 8 18 38 938 1938 1938 1938 76 77 OWAAAA WUBAAA OOOOxx
+2865 1219 1 1 5 5 65 865 865 2865 2865 130 131 FGAAAA XUBAAA VVVVxx
+3203 1220 1 3 3 3 3 203 1203 3203 3203 6 7 FTAAAA YUBAAA AAAAxx
+6025 1221 1 1 5 5 25 25 25 1025 6025 50 51 TXAAAA ZUBAAA HHHHxx
+8684 1222 0 0 4 4 84 684 684 3684 8684 168 169 AWAAAA AVBAAA OOOOxx
+7732 1223 0 0 2 12 32 732 1732 2732 7732 64 65 KLAAAA BVBAAA VVVVxx
+3218 1224 0 2 8 18 18 218 1218 3218 3218 36 37 UTAAAA CVBAAA AAAAxx
+525 1225 1 1 5 5 25 525 525 525 525 50 51 FUAAAA DVBAAA HHHHxx
+601 1226 1 1 1 1 1 601 601 601 601 2 3 DXAAAA EVBAAA OOOOxx
+6091 1227 1 3 1 11 91 91 91 1091 6091 182 183 HAAAAA FVBAAA VVVVxx
+4498 1228 0 2 8 18 98 498 498 4498 4498 196 197 ARAAAA GVBAAA AAAAxx
+8192 1229 0 0 2 12 92 192 192 3192 8192 184 185 CDAAAA HVBAAA HHHHxx
+8006 1230 0 2 6 6 6 6 6 3006 8006 12 13 YVAAAA IVBAAA OOOOxx
+6157 1231 1 1 7 17 57 157 157 1157 6157 114 115 VCAAAA JVBAAA VVVVxx
+312 1232 0 0 2 12 12 312 312 312 312 24 25 AMAAAA KVBAAA AAAAxx
+8652 1233 0 0 2 12 52 652 652 3652 8652 104 105 UUAAAA LVBAAA HHHHxx
+2787 1234 1 3 7 7 87 787 787 2787 2787 174 175 FDAAAA MVBAAA OOOOxx
+1782 1235 0 2 2 2 82 782 1782 1782 1782 164 165 OQAAAA NVBAAA VVVVxx
+23 1236 1 3 3 3 23 23 23 23 23 46 47 XAAAAA OVBAAA AAAAxx
+1206 1237 0 2 6 6 6 206 1206 1206 1206 12 13 KUAAAA PVBAAA HHHHxx
+1076 1238 0 0 6 16 76 76 1076 1076 1076 152 153 KPAAAA QVBAAA OOOOxx
+5379 1239 1 3 9 19 79 379 1379 379 5379 158 159 XYAAAA RVBAAA VVVVxx
+2047 1240 1 3 7 7 47 47 47 2047 2047 94 95 TAAAAA SVBAAA AAAAxx
+6262 1241 0 2 2 2 62 262 262 1262 6262 124 125 WGAAAA TVBAAA HHHHxx
+1840 1242 0 0 0 0 40 840 1840 1840 1840 80 81 USAAAA UVBAAA OOOOxx
+2106 1243 0 2 6 6 6 106 106 2106 2106 12 13 ADAAAA VVBAAA VVVVxx
+1307 1244 1 3 7 7 7 307 1307 1307 1307 14 15 HYAAAA WVBAAA AAAAxx
+735 1245 1 3 5 15 35 735 735 735 735 70 71 HCAAAA XVBAAA HHHHxx
+3657 1246 1 1 7 17 57 657 1657 3657 3657 114 115 RKAAAA YVBAAA OOOOxx
+3006 1247 0 2 6 6 6 6 1006 3006 3006 12 13 QLAAAA ZVBAAA VVVVxx
+1538 1248 0 2 8 18 38 538 1538 1538 1538 76 77 EHAAAA AWBAAA AAAAxx
+6098 1249 0 2 8 18 98 98 98 1098 6098 196 197 OAAAAA BWBAAA HHHHxx
+5267 1250 1 3 7 7 67 267 1267 267 5267 134 135 PUAAAA CWBAAA OOOOxx
+9757 1251 1 1 7 17 57 757 1757 4757 9757 114 115 HLAAAA DWBAAA VVVVxx
+1236 1252 0 0 6 16 36 236 1236 1236 1236 72 73 OVAAAA EWBAAA AAAAxx
+83 1253 1 3 3 3 83 83 83 83 83 166 167 FDAAAA FWBAAA HHHHxx
+9227 1254 1 3 7 7 27 227 1227 4227 9227 54 55 XQAAAA GWBAAA OOOOxx
+8772 1255 0 0 2 12 72 772 772 3772 8772 144 145 KZAAAA HWBAAA VVVVxx
+8822 1256 0 2 2 2 22 822 822 3822 8822 44 45 IBAAAA IWBAAA AAAAxx
+7167 1257 1 3 7 7 67 167 1167 2167 7167 134 135 RPAAAA JWBAAA HHHHxx
+6909 1258 1 1 9 9 9 909 909 1909 6909 18 19 TFAAAA KWBAAA OOOOxx
+1439 1259 1 3 9 19 39 439 1439 1439 1439 78 79 JDAAAA LWBAAA VVVVxx
+2370 1260 0 2 0 10 70 370 370 2370 2370 140 141 ENAAAA MWBAAA AAAAxx
+4577 1261 1 1 7 17 77 577 577 4577 4577 154 155 BUAAAA NWBAAA HHHHxx
+2575 1262 1 3 5 15 75 575 575 2575 2575 150 151 BVAAAA OWBAAA OOOOxx
+2795 1263 1 3 5 15 95 795 795 2795 2795 190 191 NDAAAA PWBAAA VVVVxx
+5520 1264 0 0 0 0 20 520 1520 520 5520 40 41 IEAAAA QWBAAA AAAAxx
+382 1265 0 2 2 2 82 382 382 382 382 164 165 SOAAAA RWBAAA HHHHxx
+6335 1266 1 3 5 15 35 335 335 1335 6335 70 71 RJAAAA SWBAAA OOOOxx
+8430 1267 0 2 0 10 30 430 430 3430 8430 60 61 GMAAAA TWBAAA VVVVxx
+4131 1268 1 3 1 11 31 131 131 4131 4131 62 63 XCAAAA UWBAAA AAAAxx
+9332 1269 0 0 2 12 32 332 1332 4332 9332 64 65 YUAAAA VWBAAA HHHHxx
+293 1270 1 1 3 13 93 293 293 293 293 186 187 HLAAAA WWBAAA OOOOxx
+2276 1271 0 0 6 16 76 276 276 2276 2276 152 153 OJAAAA XWBAAA VVVVxx
+5687 1272 1 3 7 7 87 687 1687 687 5687 174 175 TKAAAA YWBAAA AAAAxx
+5862 1273 0 2 2 2 62 862 1862 862 5862 124 125 MRAAAA ZWBAAA HHHHxx
+5073 1274 1 1 3 13 73 73 1073 73 5073 146 147 DNAAAA AXBAAA OOOOxx
+4170 1275 0 2 0 10 70 170 170 4170 4170 140 141 KEAAAA BXBAAA VVVVxx
+5039 1276 1 3 9 19 39 39 1039 39 5039 78 79 VLAAAA CXBAAA AAAAxx
+3294 1277 0 2 4 14 94 294 1294 3294 3294 188 189 SWAAAA DXBAAA HHHHxx
+6015 1278 1 3 5 15 15 15 15 1015 6015 30 31 JXAAAA EXBAAA OOOOxx
+9015 1279 1 3 5 15 15 15 1015 4015 9015 30 31 TIAAAA FXBAAA VVVVxx
+9785 1280 1 1 5 5 85 785 1785 4785 9785 170 171 JMAAAA GXBAAA AAAAxx
+4312 1281 0 0 2 12 12 312 312 4312 4312 24 25 WJAAAA HXBAAA HHHHxx
+6343 1282 1 3 3 3 43 343 343 1343 6343 86 87 ZJAAAA IXBAAA OOOOxx
+2161 1283 1 1 1 1 61 161 161 2161 2161 122 123 DFAAAA JXBAAA VVVVxx
+4490 1284 0 2 0 10 90 490 490 4490 4490 180 181 SQAAAA KXBAAA AAAAxx
+4454 1285 0 2 4 14 54 454 454 4454 4454 108 109 IPAAAA LXBAAA HHHHxx
+7647 1286 1 3 7 7 47 647 1647 2647 7647 94 95 DIAAAA MXBAAA OOOOxx
+1028 1287 0 0 8 8 28 28 1028 1028 1028 56 57 ONAAAA NXBAAA VVVVxx
+2965 1288 1 1 5 5 65 965 965 2965 2965 130 131 BKAAAA OXBAAA AAAAxx
+9900 1289 0 0 0 0 0 900 1900 4900 9900 0 1 UQAAAA PXBAAA HHHHxx
+5509 1290 1 1 9 9 9 509 1509 509 5509 18 19 XDAAAA QXBAAA OOOOxx
+7751 1291 1 3 1 11 51 751 1751 2751 7751 102 103 DMAAAA RXBAAA VVVVxx
+9594 1292 0 2 4 14 94 594 1594 4594 9594 188 189 AFAAAA SXBAAA AAAAxx
+7632 1293 0 0 2 12 32 632 1632 2632 7632 64 65 OHAAAA TXBAAA HHHHxx
+6528 1294 0 0 8 8 28 528 528 1528 6528 56 57 CRAAAA UXBAAA OOOOxx
+1041 1295 1 1 1 1 41 41 1041 1041 1041 82 83 BOAAAA VXBAAA VVVVxx
+1534 1296 0 2 4 14 34 534 1534 1534 1534 68 69 AHAAAA WXBAAA AAAAxx
+4229 1297 1 1 9 9 29 229 229 4229 4229 58 59 RGAAAA XXBAAA HHHHxx
+84 1298 0 0 4 4 84 84 84 84 84 168 169 GDAAAA YXBAAA OOOOxx
+2189 1299 1 1 9 9 89 189 189 2189 2189 178 179 FGAAAA ZXBAAA VVVVxx
+7566 1300 0 2 6 6 66 566 1566 2566 7566 132 133 AFAAAA AYBAAA AAAAxx
+707 1301 1 3 7 7 7 707 707 707 707 14 15 FBAAAA BYBAAA HHHHxx
+581 1302 1 1 1 1 81 581 581 581 581 162 163 JWAAAA CYBAAA OOOOxx
+6753 1303 1 1 3 13 53 753 753 1753 6753 106 107 TZAAAA DYBAAA VVVVxx
+8604 1304 0 0 4 4 4 604 604 3604 8604 8 9 YSAAAA EYBAAA AAAAxx
+373 1305 1 1 3 13 73 373 373 373 373 146 147 JOAAAA FYBAAA HHHHxx
+9635 1306 1 3 5 15 35 635 1635 4635 9635 70 71 PGAAAA GYBAAA OOOOxx
+9277 1307 1 1 7 17 77 277 1277 4277 9277 154 155 VSAAAA HYBAAA VVVVxx
+7117 1308 1 1 7 17 17 117 1117 2117 7117 34 35 TNAAAA IYBAAA AAAAxx
+8564 1309 0 0 4 4 64 564 564 3564 8564 128 129 KRAAAA JYBAAA HHHHxx
+1697 1310 1 1 7 17 97 697 1697 1697 1697 194 195 HNAAAA KYBAAA OOOOxx
+7840 1311 0 0 0 0 40 840 1840 2840 7840 80 81 OPAAAA LYBAAA VVVVxx
+3646 1312 0 2 6 6 46 646 1646 3646 3646 92 93 GKAAAA MYBAAA AAAAxx
+368 1313 0 0 8 8 68 368 368 368 368 136 137 EOAAAA NYBAAA HHHHxx
+4797 1314 1 1 7 17 97 797 797 4797 4797 194 195 NCAAAA OYBAAA OOOOxx
+5300 1315 0 0 0 0 0 300 1300 300 5300 0 1 WVAAAA PYBAAA VVVVxx
+7664 1316 0 0 4 4 64 664 1664 2664 7664 128 129 UIAAAA QYBAAA AAAAxx
+1466 1317 0 2 6 6 66 466 1466 1466 1466 132 133 KEAAAA RYBAAA HHHHxx
+2477 1318 1 1 7 17 77 477 477 2477 2477 154 155 HRAAAA SYBAAA OOOOxx
+2036 1319 0 0 6 16 36 36 36 2036 2036 72 73 IAAAAA TYBAAA VVVVxx
+3624 1320 0 0 4 4 24 624 1624 3624 3624 48 49 KJAAAA UYBAAA AAAAxx
+5099 1321 1 3 9 19 99 99 1099 99 5099 198 199 DOAAAA VYBAAA HHHHxx
+1308 1322 0 0 8 8 8 308 1308 1308 1308 16 17 IYAAAA WYBAAA OOOOxx
+3704 1323 0 0 4 4 4 704 1704 3704 3704 8 9 MMAAAA XYBAAA VVVVxx
+2451 1324 1 3 1 11 51 451 451 2451 2451 102 103 HQAAAA YYBAAA AAAAxx
+4898 1325 0 2 8 18 98 898 898 4898 4898 196 197 KGAAAA ZYBAAA HHHHxx
+4959 1326 1 3 9 19 59 959 959 4959 4959 118 119 TIAAAA AZBAAA OOOOxx
+5942 1327 0 2 2 2 42 942 1942 942 5942 84 85 OUAAAA BZBAAA VVVVxx
+2425 1328 1 1 5 5 25 425 425 2425 2425 50 51 HPAAAA CZBAAA AAAAxx
+7760 1329 0 0 0 0 60 760 1760 2760 7760 120 121 MMAAAA DZBAAA HHHHxx
+6294 1330 0 2 4 14 94 294 294 1294 6294 188 189 CIAAAA EZBAAA OOOOxx
+6785 1331 1 1 5 5 85 785 785 1785 6785 170 171 ZAAAAA FZBAAA VVVVxx
+3542 1332 0 2 2 2 42 542 1542 3542 3542 84 85 GGAAAA GZBAAA AAAAxx
+1809 1333 1 1 9 9 9 809 1809 1809 1809 18 19 PRAAAA HZBAAA HHHHxx
+130 1334 0 2 0 10 30 130 130 130 130 60 61 AFAAAA IZBAAA OOOOxx
+8672 1335 0 0 2 12 72 672 672 3672 8672 144 145 OVAAAA JZBAAA VVVVxx
+2125 1336 1 1 5 5 25 125 125 2125 2125 50 51 TDAAAA KZBAAA AAAAxx
+7683 1337 1 3 3 3 83 683 1683 2683 7683 166 167 NJAAAA LZBAAA HHHHxx
+7842 1338 0 2 2 2 42 842 1842 2842 7842 84 85 QPAAAA MZBAAA OOOOxx
+9584 1339 0 0 4 4 84 584 1584 4584 9584 168 169 QEAAAA NZBAAA VVVVxx
+7963 1340 1 3 3 3 63 963 1963 2963 7963 126 127 HUAAAA OZBAAA AAAAxx
+8581 1341 1 1 1 1 81 581 581 3581 8581 162 163 BSAAAA PZBAAA HHHHxx
+2135 1342 1 3 5 15 35 135 135 2135 2135 70 71 DEAAAA QZBAAA OOOOxx
+7352 1343 0 0 2 12 52 352 1352 2352 7352 104 105 UWAAAA RZBAAA VVVVxx
+5789 1344 1 1 9 9 89 789 1789 789 5789 178 179 ROAAAA SZBAAA AAAAxx
+8490 1345 0 2 0 10 90 490 490 3490 8490 180 181 OOAAAA TZBAAA HHHHxx
+2145 1346 1 1 5 5 45 145 145 2145 2145 90 91 NEAAAA UZBAAA OOOOxx
+7021 1347 1 1 1 1 21 21 1021 2021 7021 42 43 BKAAAA VZBAAA VVVVxx
+3736 1348 0 0 6 16 36 736 1736 3736 3736 72 73 SNAAAA WZBAAA AAAAxx
+7396 1349 0 0 6 16 96 396 1396 2396 7396 192 193 MYAAAA XZBAAA HHHHxx
+6334 1350 0 2 4 14 34 334 334 1334 6334 68 69 QJAAAA YZBAAA OOOOxx
+5461 1351 1 1 1 1 61 461 1461 461 5461 122 123 BCAAAA ZZBAAA VVVVxx
+5337 1352 1 1 7 17 37 337 1337 337 5337 74 75 HXAAAA AACAAA AAAAxx
+7440 1353 0 0 0 0 40 440 1440 2440 7440 80 81 EAAAAA BACAAA HHHHxx
+6879 1354 1 3 9 19 79 879 879 1879 6879 158 159 PEAAAA CACAAA OOOOxx
+2432 1355 0 0 2 12 32 432 432 2432 2432 64 65 OPAAAA DACAAA VVVVxx
+8529 1356 1 1 9 9 29 529 529 3529 8529 58 59 BQAAAA EACAAA AAAAxx
+7859 1357 1 3 9 19 59 859 1859 2859 7859 118 119 HQAAAA FACAAA HHHHxx
+15 1358 1 3 5 15 15 15 15 15 15 30 31 PAAAAA GACAAA OOOOxx
+7475 1359 1 3 5 15 75 475 1475 2475 7475 150 151 NBAAAA HACAAA VVVVxx
+717 1360 1 1 7 17 17 717 717 717 717 34 35 PBAAAA IACAAA AAAAxx
+250 1361 0 2 0 10 50 250 250 250 250 100 101 QJAAAA JACAAA HHHHxx
+4700 1362 0 0 0 0 0 700 700 4700 4700 0 1 UYAAAA KACAAA OOOOxx
+7510 1363 0 2 0 10 10 510 1510 2510 7510 20 21 WCAAAA LACAAA VVVVxx
+4562 1364 0 2 2 2 62 562 562 4562 4562 124 125 MTAAAA MACAAA AAAAxx
+8075 1365 1 3 5 15 75 75 75 3075 8075 150 151 PYAAAA NACAAA HHHHxx
+871 1366 1 3 1 11 71 871 871 871 871 142 143 NHAAAA OACAAA OOOOxx
+7161 1367 1 1 1 1 61 161 1161 2161 7161 122 123 LPAAAA PACAAA VVVVxx
+9109 1368 1 1 9 9 9 109 1109 4109 9109 18 19 JMAAAA QACAAA AAAAxx
+8675 1369 1 3 5 15 75 675 675 3675 8675 150 151 RVAAAA RACAAA HHHHxx
+1025 1370 1 1 5 5 25 25 1025 1025 1025 50 51 LNAAAA SACAAA OOOOxx
+4065 1371 1 1 5 5 65 65 65 4065 4065 130 131 JAAAAA TACAAA VVVVxx
+3511 1372 1 3 1 11 11 511 1511 3511 3511 22 23 BFAAAA UACAAA AAAAxx
+9840 1373 0 0 0 0 40 840 1840 4840 9840 80 81 MOAAAA VACAAA HHHHxx
+7495 1374 1 3 5 15 95 495 1495 2495 7495 190 191 HCAAAA WACAAA OOOOxx
+55 1375 1 3 5 15 55 55 55 55 55 110 111 DCAAAA XACAAA VVVVxx
+6151 1376 1 3 1 11 51 151 151 1151 6151 102 103 PCAAAA YACAAA AAAAxx
+2512 1377 0 0 2 12 12 512 512 2512 2512 24 25 QSAAAA ZACAAA HHHHxx
+5881 1378 1 1 1 1 81 881 1881 881 5881 162 163 FSAAAA ABCAAA OOOOxx
+1442 1379 0 2 2 2 42 442 1442 1442 1442 84 85 MDAAAA BBCAAA VVVVxx
+1270 1380 0 2 0 10 70 270 1270 1270 1270 140 141 WWAAAA CBCAAA AAAAxx
+959 1381 1 3 9 19 59 959 959 959 959 118 119 XKAAAA DBCAAA HHHHxx
+8251 1382 1 3 1 11 51 251 251 3251 8251 102 103 JFAAAA EBCAAA OOOOxx
+3051 1383 1 3 1 11 51 51 1051 3051 3051 102 103 JNAAAA FBCAAA VVVVxx
+5052 1384 0 0 2 12 52 52 1052 52 5052 104 105 IMAAAA GBCAAA AAAAxx
+1863 1385 1 3 3 3 63 863 1863 1863 1863 126 127 RTAAAA HBCAAA HHHHxx
+344 1386 0 0 4 4 44 344 344 344 344 88 89 GNAAAA IBCAAA OOOOxx
+3590 1387 0 2 0 10 90 590 1590 3590 3590 180 181 CIAAAA JBCAAA VVVVxx
+4223 1388 1 3 3 3 23 223 223 4223 4223 46 47 LGAAAA KBCAAA AAAAxx
+2284 1389 0 0 4 4 84 284 284 2284 2284 168 169 WJAAAA LBCAAA HHHHxx
+9425 1390 1 1 5 5 25 425 1425 4425 9425 50 51 NYAAAA MBCAAA OOOOxx
+6221 1391 1 1 1 1 21 221 221 1221 6221 42 43 HFAAAA NBCAAA VVVVxx
+195 1392 1 3 5 15 95 195 195 195 195 190 191 NHAAAA OBCAAA AAAAxx
+1517 1393 1 1 7 17 17 517 1517 1517 1517 34 35 JGAAAA PBCAAA HHHHxx
+3791 1394 1 3 1 11 91 791 1791 3791 3791 182 183 VPAAAA QBCAAA OOOOxx
+572 1395 0 0 2 12 72 572 572 572 572 144 145 AWAAAA RBCAAA VVVVxx
+46 1396 0 2 6 6 46 46 46 46 46 92 93 UBAAAA SBCAAA AAAAxx
+9451 1397 1 3 1 11 51 451 1451 4451 9451 102 103 NZAAAA TBCAAA HHHHxx
+3359 1398 1 3 9 19 59 359 1359 3359 3359 118 119 FZAAAA UBCAAA OOOOxx
+8867 1399 1 3 7 7 67 867 867 3867 8867 134 135 BDAAAA VBCAAA VVVVxx
+674 1400 0 2 4 14 74 674 674 674 674 148 149 YZAAAA WBCAAA AAAAxx
+2674 1401 0 2 4 14 74 674 674 2674 2674 148 149 WYAAAA XBCAAA HHHHxx
+6523 1402 1 3 3 3 23 523 523 1523 6523 46 47 XQAAAA YBCAAA OOOOxx
+6210 1403 0 2 0 10 10 210 210 1210 6210 20 21 WEAAAA ZBCAAA VVVVxx
+7564 1404 0 0 4 4 64 564 1564 2564 7564 128 129 YEAAAA ACCAAA AAAAxx
+4776 1405 0 0 6 16 76 776 776 4776 4776 152 153 SBAAAA BCCAAA HHHHxx
+2993 1406 1 1 3 13 93 993 993 2993 2993 186 187 DLAAAA CCCAAA OOOOxx
+2969 1407 1 1 9 9 69 969 969 2969 2969 138 139 FKAAAA DCCAAA VVVVxx
+1762 1408 0 2 2 2 62 762 1762 1762 1762 124 125 UPAAAA ECCAAA AAAAxx
+685 1409 1 1 5 5 85 685 685 685 685 170 171 JAAAAA FCCAAA HHHHxx
+5312 1410 0 0 2 12 12 312 1312 312 5312 24 25 IWAAAA GCCAAA OOOOxx
+3264 1411 0 0 4 4 64 264 1264 3264 3264 128 129 OVAAAA HCCAAA VVVVxx
+7008 1412 0 0 8 8 8 8 1008 2008 7008 16 17 OJAAAA ICCAAA AAAAxx
+5167 1413 1 3 7 7 67 167 1167 167 5167 134 135 TQAAAA JCCAAA HHHHxx
+3060 1414 0 0 0 0 60 60 1060 3060 3060 120 121 SNAAAA KCCAAA OOOOxx
+1752 1415 0 0 2 12 52 752 1752 1752 1752 104 105 KPAAAA LCCAAA VVVVxx
+1016 1416 0 0 6 16 16 16 1016 1016 1016 32 33 CNAAAA MCCAAA AAAAxx
+7365 1417 1 1 5 5 65 365 1365 2365 7365 130 131 HXAAAA NCCAAA HHHHxx
+4358 1418 0 2 8 18 58 358 358 4358 4358 116 117 QLAAAA OCCAAA OOOOxx
+2819 1419 1 3 9 19 19 819 819 2819 2819 38 39 LEAAAA PCCAAA VVVVxx
+6727 1420 1 3 7 7 27 727 727 1727 6727 54 55 TYAAAA QCCAAA AAAAxx
+1459 1421 1 3 9 19 59 459 1459 1459 1459 118 119 DEAAAA RCCAAA HHHHxx
+1708 1422 0 0 8 8 8 708 1708 1708 1708 16 17 SNAAAA SCCAAA OOOOxx
+471 1423 1 3 1 11 71 471 471 471 471 142 143 DSAAAA TCCAAA VVVVxx
+387 1424 1 3 7 7 87 387 387 387 387 174 175 XOAAAA UCCAAA AAAAxx
+1166 1425 0 2 6 6 66 166 1166 1166 1166 132 133 WSAAAA VCCAAA HHHHxx
+2400 1426 0 0 0 0 0 400 400 2400 2400 0 1 IOAAAA WCCAAA OOOOxx
+3584 1427 0 0 4 4 84 584 1584 3584 3584 168 169 WHAAAA XCCAAA VVVVxx
+6423 1428 1 3 3 3 23 423 423 1423 6423 46 47 BNAAAA YCCAAA AAAAxx
+9520 1429 0 0 0 0 20 520 1520 4520 9520 40 41 ECAAAA ZCCAAA HHHHxx
+8080 1430 0 0 0 0 80 80 80 3080 8080 160 161 UYAAAA ADCAAA OOOOxx
+5709 1431 1 1 9 9 9 709 1709 709 5709 18 19 PLAAAA BDCAAA VVVVxx
+1131 1432 1 3 1 11 31 131 1131 1131 1131 62 63 NRAAAA CDCAAA AAAAxx
+8562 1433 0 2 2 2 62 562 562 3562 8562 124 125 IRAAAA DDCAAA HHHHxx
+5766 1434 0 2 6 6 66 766 1766 766 5766 132 133 UNAAAA EDCAAA OOOOxx
+245 1435 1 1 5 5 45 245 245 245 245 90 91 LJAAAA FDCAAA VVVVxx
+9869 1436 1 1 9 9 69 869 1869 4869 9869 138 139 PPAAAA GDCAAA AAAAxx
+3533 1437 1 1 3 13 33 533 1533 3533 3533 66 67 XFAAAA HDCAAA HHHHxx
+5109 1438 1 1 9 9 9 109 1109 109 5109 18 19 NOAAAA IDCAAA OOOOxx
+977 1439 1 1 7 17 77 977 977 977 977 154 155 PLAAAA JDCAAA VVVVxx
+1651 1440 1 3 1 11 51 651 1651 1651 1651 102 103 NLAAAA KDCAAA AAAAxx
+1357 1441 1 1 7 17 57 357 1357 1357 1357 114 115 FAAAAA LDCAAA HHHHxx
+9087 1442 1 3 7 7 87 87 1087 4087 9087 174 175 NLAAAA MDCAAA OOOOxx
+3399 1443 1 3 9 19 99 399 1399 3399 3399 198 199 TAAAAA NDCAAA VVVVxx
+7543 1444 1 3 3 3 43 543 1543 2543 7543 86 87 DEAAAA ODCAAA AAAAxx
+2469 1445 1 1 9 9 69 469 469 2469 2469 138 139 ZQAAAA PDCAAA HHHHxx
+8305 1446 1 1 5 5 5 305 305 3305 8305 10 11 LHAAAA QDCAAA OOOOxx
+3265 1447 1 1 5 5 65 265 1265 3265 3265 130 131 PVAAAA RDCAAA VVVVxx
+9977 1448 1 1 7 17 77 977 1977 4977 9977 154 155 TTAAAA SDCAAA AAAAxx
+3961 1449 1 1 1 1 61 961 1961 3961 3961 122 123 JWAAAA TDCAAA HHHHxx
+4952 1450 0 0 2 12 52 952 952 4952 4952 104 105 MIAAAA UDCAAA OOOOxx
+5173 1451 1 1 3 13 73 173 1173 173 5173 146 147 ZQAAAA VDCAAA VVVVxx
+860 1452 0 0 0 0 60 860 860 860 860 120 121 CHAAAA WDCAAA AAAAxx
+4523 1453 1 3 3 3 23 523 523 4523 4523 46 47 ZRAAAA XDCAAA HHHHxx
+2361 1454 1 1 1 1 61 361 361 2361 2361 122 123 VMAAAA YDCAAA OOOOxx
+7877 1455 1 1 7 17 77 877 1877 2877 7877 154 155 ZQAAAA ZDCAAA VVVVxx
+3422 1456 0 2 2 2 22 422 1422 3422 3422 44 45 QBAAAA AECAAA AAAAxx
+5781 1457 1 1 1 1 81 781 1781 781 5781 162 163 JOAAAA BECAAA HHHHxx
+4752 1458 0 0 2 12 52 752 752 4752 4752 104 105 UAAAAA CECAAA OOOOxx
+1786 1459 0 2 6 6 86 786 1786 1786 1786 172 173 SQAAAA DECAAA VVVVxx
+1892 1460 0 0 2 12 92 892 1892 1892 1892 184 185 UUAAAA EECAAA AAAAxx
+6389 1461 1 1 9 9 89 389 389 1389 6389 178 179 TLAAAA FECAAA HHHHxx
+8644 1462 0 0 4 4 44 644 644 3644 8644 88 89 MUAAAA GECAAA OOOOxx
+9056 1463 0 0 6 16 56 56 1056 4056 9056 112 113 IKAAAA HECAAA VVVVxx
+1423 1464 1 3 3 3 23 423 1423 1423 1423 46 47 TCAAAA IECAAA AAAAxx
+4901 1465 1 1 1 1 1 901 901 4901 4901 2 3 NGAAAA JECAAA HHHHxx
+3859 1466 1 3 9 19 59 859 1859 3859 3859 118 119 LSAAAA KECAAA OOOOxx
+2324 1467 0 0 4 4 24 324 324 2324 2324 48 49 KLAAAA LECAAA VVVVxx
+8101 1468 1 1 1 1 1 101 101 3101 8101 2 3 PZAAAA MECAAA AAAAxx
+8016 1469 0 0 6 16 16 16 16 3016 8016 32 33 IWAAAA NECAAA HHHHxx
+5826 1470 0 2 6 6 26 826 1826 826 5826 52 53 CQAAAA OECAAA OOOOxx
+8266 1471 0 2 6 6 66 266 266 3266 8266 132 133 YFAAAA PECAAA VVVVxx
+7558 1472 0 2 8 18 58 558 1558 2558 7558 116 117 SEAAAA QECAAA AAAAxx
+6976 1473 0 0 6 16 76 976 976 1976 6976 152 153 IIAAAA RECAAA HHHHxx
+222 1474 0 2 2 2 22 222 222 222 222 44 45 OIAAAA SECAAA OOOOxx
+1624 1475 0 0 4 4 24 624 1624 1624 1624 48 49 MKAAAA TECAAA VVVVxx
+1250 1476 0 2 0 10 50 250 1250 1250 1250 100 101 CWAAAA UECAAA AAAAxx
+1621 1477 1 1 1 1 21 621 1621 1621 1621 42 43 JKAAAA VECAAA HHHHxx
+2350 1478 0 2 0 10 50 350 350 2350 2350 100 101 KMAAAA WECAAA OOOOxx
+5239 1479 1 3 9 19 39 239 1239 239 5239 78 79 NTAAAA XECAAA VVVVxx
+6681 1480 1 1 1 1 81 681 681 1681 6681 162 163 ZWAAAA YECAAA AAAAxx
+4983 1481 1 3 3 3 83 983 983 4983 4983 166 167 RJAAAA ZECAAA HHHHxx
+7149 1482 1 1 9 9 49 149 1149 2149 7149 98 99 ZOAAAA AFCAAA OOOOxx
+3502 1483 0 2 2 2 2 502 1502 3502 3502 4 5 SEAAAA BFCAAA VVVVxx
+3133 1484 1 1 3 13 33 133 1133 3133 3133 66 67 NQAAAA CFCAAA AAAAxx
+8342 1485 0 2 2 2 42 342 342 3342 8342 84 85 WIAAAA DFCAAA HHHHxx
+3041 1486 1 1 1 1 41 41 1041 3041 3041 82 83 ZMAAAA EFCAAA OOOOxx
+5383 1487 1 3 3 3 83 383 1383 383 5383 166 167 BZAAAA FFCAAA VVVVxx
+3916 1488 0 0 6 16 16 916 1916 3916 3916 32 33 QUAAAA GFCAAA AAAAxx
+1438 1489 0 2 8 18 38 438 1438 1438 1438 76 77 IDAAAA HFCAAA HHHHxx
+9408 1490 0 0 8 8 8 408 1408 4408 9408 16 17 WXAAAA IFCAAA OOOOxx
+5783 1491 1 3 3 3 83 783 1783 783 5783 166 167 LOAAAA JFCAAA VVVVxx
+683 1492 1 3 3 3 83 683 683 683 683 166 167 HAAAAA KFCAAA AAAAxx
+9381 1493 1 1 1 1 81 381 1381 4381 9381 162 163 VWAAAA LFCAAA HHHHxx
+5676 1494 0 0 6 16 76 676 1676 676 5676 152 153 IKAAAA MFCAAA OOOOxx
+3224 1495 0 0 4 4 24 224 1224 3224 3224 48 49 AUAAAA NFCAAA VVVVxx
+8332 1496 0 0 2 12 32 332 332 3332 8332 64 65 MIAAAA OFCAAA AAAAxx
+3372 1497 0 0 2 12 72 372 1372 3372 3372 144 145 SZAAAA PFCAAA HHHHxx
+7436 1498 0 0 6 16 36 436 1436 2436 7436 72 73 AAAAAA QFCAAA OOOOxx
+5010 1499 0 2 0 10 10 10 1010 10 5010 20 21 SKAAAA RFCAAA VVVVxx
+7256 1500 0 0 6 16 56 256 1256 2256 7256 112 113 CTAAAA SFCAAA AAAAxx
+961 1501 1 1 1 1 61 961 961 961 961 122 123 ZKAAAA TFCAAA HHHHxx
+4182 1502 0 2 2 2 82 182 182 4182 4182 164 165 WEAAAA UFCAAA OOOOxx
+639 1503 1 3 9 19 39 639 639 639 639 78 79 PYAAAA VFCAAA VVVVxx
+8836 1504 0 0 6 16 36 836 836 3836 8836 72 73 WBAAAA WFCAAA AAAAxx
+8705 1505 1 1 5 5 5 705 705 3705 8705 10 11 VWAAAA XFCAAA HHHHxx
+32 1506 0 0 2 12 32 32 32 32 32 64 65 GBAAAA YFCAAA OOOOxx
+7913 1507 1 1 3 13 13 913 1913 2913 7913 26 27 JSAAAA ZFCAAA VVVVxx
+229 1508 1 1 9 9 29 229 229 229 229 58 59 VIAAAA AGCAAA AAAAxx
+2393 1509 1 1 3 13 93 393 393 2393 2393 186 187 BOAAAA BGCAAA HHHHxx
+2815 1510 1 3 5 15 15 815 815 2815 2815 30 31 HEAAAA CGCAAA OOOOxx
+4858 1511 0 2 8 18 58 858 858 4858 4858 116 117 WEAAAA DGCAAA VVVVxx
+6283 1512 1 3 3 3 83 283 283 1283 6283 166 167 RHAAAA EGCAAA AAAAxx
+4147 1513 1 3 7 7 47 147 147 4147 4147 94 95 NDAAAA FGCAAA HHHHxx
+6801 1514 1 1 1 1 1 801 801 1801 6801 2 3 PBAAAA GGCAAA OOOOxx
+1011 1515 1 3 1 11 11 11 1011 1011 1011 22 23 XMAAAA HGCAAA VVVVxx
+2527 1516 1 3 7 7 27 527 527 2527 2527 54 55 FTAAAA IGCAAA AAAAxx
+381 1517 1 1 1 1 81 381 381 381 381 162 163 ROAAAA JGCAAA HHHHxx
+3366 1518 0 2 6 6 66 366 1366 3366 3366 132 133 MZAAAA KGCAAA OOOOxx
+9636 1519 0 0 6 16 36 636 1636 4636 9636 72 73 QGAAAA LGCAAA VVVVxx
+2239 1520 1 3 9 19 39 239 239 2239 2239 78 79 DIAAAA MGCAAA AAAAxx
+5911 1521 1 3 1 11 11 911 1911 911 5911 22 23 JTAAAA NGCAAA HHHHxx
+449 1522 1 1 9 9 49 449 449 449 449 98 99 HRAAAA OGCAAA OOOOxx
+5118 1523 0 2 8 18 18 118 1118 118 5118 36 37 WOAAAA PGCAAA VVVVxx
+7684 1524 0 0 4 4 84 684 1684 2684 7684 168 169 OJAAAA QGCAAA AAAAxx
+804 1525 0 0 4 4 4 804 804 804 804 8 9 YEAAAA RGCAAA HHHHxx
+8378 1526 0 2 8 18 78 378 378 3378 8378 156 157 GKAAAA SGCAAA OOOOxx
+9855 1527 1 3 5 15 55 855 1855 4855 9855 110 111 BPAAAA TGCAAA VVVVxx
+1995 1528 1 3 5 15 95 995 1995 1995 1995 190 191 TYAAAA UGCAAA AAAAxx
+1979 1529 1 3 9 19 79 979 1979 1979 1979 158 159 DYAAAA VGCAAA HHHHxx
+4510 1530 0 2 0 10 10 510 510 4510 4510 20 21 MRAAAA WGCAAA OOOOxx
+3792 1531 0 0 2 12 92 792 1792 3792 3792 184 185 WPAAAA XGCAAA VVVVxx
+3541 1532 1 1 1 1 41 541 1541 3541 3541 82 83 FGAAAA YGCAAA AAAAxx
+8847 1533 1 3 7 7 47 847 847 3847 8847 94 95 HCAAAA ZGCAAA HHHHxx
+1336 1534 0 0 6 16 36 336 1336 1336 1336 72 73 KZAAAA AHCAAA OOOOxx
+6780 1535 0 0 0 0 80 780 780 1780 6780 160 161 UAAAAA BHCAAA VVVVxx
+8711 1536 1 3 1 11 11 711 711 3711 8711 22 23 BXAAAA CHCAAA AAAAxx
+7839 1537 1 3 9 19 39 839 1839 2839 7839 78 79 NPAAAA DHCAAA HHHHxx
+677 1538 1 1 7 17 77 677 677 677 677 154 155 BAAAAA EHCAAA OOOOxx
+1574 1539 0 2 4 14 74 574 1574 1574 1574 148 149 OIAAAA FHCAAA VVVVxx
+2905 1540 1 1 5 5 5 905 905 2905 2905 10 11 THAAAA GHCAAA AAAAxx
+1879 1541 1 3 9 19 79 879 1879 1879 1879 158 159 HUAAAA HHCAAA HHHHxx
+7820 1542 0 0 0 0 20 820 1820 2820 7820 40 41 UOAAAA IHCAAA OOOOxx
+4308 1543 0 0 8 8 8 308 308 4308 4308 16 17 SJAAAA JHCAAA VVVVxx
+4474 1544 0 2 4 14 74 474 474 4474 4474 148 149 CQAAAA KHCAAA AAAAxx
+6985 1545 1 1 5 5 85 985 985 1985 6985 170 171 RIAAAA LHCAAA HHHHxx
+6929 1546 1 1 9 9 29 929 929 1929 6929 58 59 NGAAAA MHCAAA OOOOxx
+777 1547 1 1 7 17 77 777 777 777 777 154 155 XDAAAA NHCAAA VVVVxx
+8271 1548 1 3 1 11 71 271 271 3271 8271 142 143 DGAAAA OHCAAA AAAAxx
+2389 1549 1 1 9 9 89 389 389 2389 2389 178 179 XNAAAA PHCAAA HHHHxx
+946 1550 0 2 6 6 46 946 946 946 946 92 93 KKAAAA QHCAAA OOOOxx
+9682 1551 0 2 2 2 82 682 1682 4682 9682 164 165 KIAAAA RHCAAA VVVVxx
+8722 1552 0 2 2 2 22 722 722 3722 8722 44 45 MXAAAA SHCAAA AAAAxx
+470 1553 0 2 0 10 70 470 470 470 470 140 141 CSAAAA THCAAA HHHHxx
+7425 1554 1 1 5 5 25 425 1425 2425 7425 50 51 PZAAAA UHCAAA OOOOxx
+2372 1555 0 0 2 12 72 372 372 2372 2372 144 145 GNAAAA VHCAAA VVVVxx
+508 1556 0 0 8 8 8 508 508 508 508 16 17 OTAAAA WHCAAA AAAAxx
+163 1557 1 3 3 3 63 163 163 163 163 126 127 HGAAAA XHCAAA HHHHxx
+6579 1558 1 3 9 19 79 579 579 1579 6579 158 159 BTAAAA YHCAAA OOOOxx
+2355 1559 1 3 5 15 55 355 355 2355 2355 110 111 PMAAAA ZHCAAA VVVVxx
+70 1560 0 2 0 10 70 70 70 70 70 140 141 SCAAAA AICAAA AAAAxx
+651 1561 1 3 1 11 51 651 651 651 651 102 103 BZAAAA BICAAA HHHHxx
+4436 1562 0 0 6 16 36 436 436 4436 4436 72 73 QOAAAA CICAAA OOOOxx
+4240 1563 0 0 0 0 40 240 240 4240 4240 80 81 CHAAAA DICAAA VVVVxx
+2722 1564 0 2 2 2 22 722 722 2722 2722 44 45 SAAAAA EICAAA AAAAxx
+8937 1565 1 1 7 17 37 937 937 3937 8937 74 75 TFAAAA FICAAA HHHHxx
+8364 1566 0 0 4 4 64 364 364 3364 8364 128 129 SJAAAA GICAAA OOOOxx
+8317 1567 1 1 7 17 17 317 317 3317 8317 34 35 XHAAAA HICAAA VVVVxx
+8872 1568 0 0 2 12 72 872 872 3872 8872 144 145 GDAAAA IICAAA AAAAxx
+5512 1569 0 0 2 12 12 512 1512 512 5512 24 25 AEAAAA JICAAA HHHHxx
+6651 1570 1 3 1 11 51 651 651 1651 6651 102 103 VVAAAA KICAAA OOOOxx
+5976 1571 0 0 6 16 76 976 1976 976 5976 152 153 WVAAAA LICAAA VVVVxx
+3301 1572 1 1 1 1 1 301 1301 3301 3301 2 3 ZWAAAA MICAAA AAAAxx
+6784 1573 0 0 4 4 84 784 784 1784 6784 168 169 YAAAAA NICAAA HHHHxx
+573 1574 1 1 3 13 73 573 573 573 573 146 147 BWAAAA OICAAA OOOOxx
+3015 1575 1 3 5 15 15 15 1015 3015 3015 30 31 ZLAAAA PICAAA VVVVxx
+8245 1576 1 1 5 5 45 245 245 3245 8245 90 91 DFAAAA QICAAA AAAAxx
+5251 1577 1 3 1 11 51 251 1251 251 5251 102 103 ZTAAAA RICAAA HHHHxx
+2281 1578 1 1 1 1 81 281 281 2281 2281 162 163 TJAAAA SICAAA OOOOxx
+518 1579 0 2 8 18 18 518 518 518 518 36 37 YTAAAA TICAAA VVVVxx
+9839 1580 1 3 9 19 39 839 1839 4839 9839 78 79 LOAAAA UICAAA AAAAxx
+4526 1581 0 2 6 6 26 526 526 4526 4526 52 53 CSAAAA VICAAA HHHHxx
+1261 1582 1 1 1 1 61 261 1261 1261 1261 122 123 NWAAAA WICAAA OOOOxx
+4259 1583 1 3 9 19 59 259 259 4259 4259 118 119 VHAAAA XICAAA VVVVxx
+9098 1584 0 2 8 18 98 98 1098 4098 9098 196 197 YLAAAA YICAAA AAAAxx
+6037 1585 1 1 7 17 37 37 37 1037 6037 74 75 FYAAAA ZICAAA HHHHxx
+4284 1586 0 0 4 4 84 284 284 4284 4284 168 169 UIAAAA AJCAAA OOOOxx
+3267 1587 1 3 7 7 67 267 1267 3267 3267 134 135 RVAAAA BJCAAA VVVVxx
+5908 1588 0 0 8 8 8 908 1908 908 5908 16 17 GTAAAA CJCAAA AAAAxx
+1549 1589 1 1 9 9 49 549 1549 1549 1549 98 99 PHAAAA DJCAAA HHHHxx
+8736 1590 0 0 6 16 36 736 736 3736 8736 72 73 AYAAAA EJCAAA OOOOxx
+2008 1591 0 0 8 8 8 8 8 2008 2008 16 17 GZAAAA FJCAAA VVVVxx
+548 1592 0 0 8 8 48 548 548 548 548 96 97 CVAAAA GJCAAA AAAAxx
+8846 1593 0 2 6 6 46 846 846 3846 8846 92 93 GCAAAA HJCAAA HHHHxx
+8374 1594 0 2 4 14 74 374 374 3374 8374 148 149 CKAAAA IJCAAA OOOOxx
+7986 1595 0 2 6 6 86 986 1986 2986 7986 172 173 EVAAAA JJCAAA VVVVxx
+6819 1596 1 3 9 19 19 819 819 1819 6819 38 39 HCAAAA KJCAAA AAAAxx
+4418 1597 0 2 8 18 18 418 418 4418 4418 36 37 YNAAAA LJCAAA HHHHxx
+833 1598 1 1 3 13 33 833 833 833 833 66 67 BGAAAA MJCAAA OOOOxx
+4416 1599 0 0 6 16 16 416 416 4416 4416 32 33 WNAAAA NJCAAA VVVVxx
+4902 1600 0 2 2 2 2 902 902 4902 4902 4 5 OGAAAA OJCAAA AAAAxx
+6828 1601 0 0 8 8 28 828 828 1828 6828 56 57 QCAAAA PJCAAA HHHHxx
+1118 1602 0 2 8 18 18 118 1118 1118 1118 36 37 ARAAAA QJCAAA OOOOxx
+9993 1603 1 1 3 13 93 993 1993 4993 9993 186 187 JUAAAA RJCAAA VVVVxx
+1430 1604 0 2 0 10 30 430 1430 1430 1430 60 61 ADAAAA SJCAAA AAAAxx
+5670 1605 0 2 0 10 70 670 1670 670 5670 140 141 CKAAAA TJCAAA HHHHxx
+5424 1606 0 0 4 4 24 424 1424 424 5424 48 49 QAAAAA UJCAAA OOOOxx
+5561 1607 1 1 1 1 61 561 1561 561 5561 122 123 XFAAAA VJCAAA VVVVxx
+2027 1608 1 3 7 7 27 27 27 2027 2027 54 55 ZZAAAA WJCAAA AAAAxx
+6924 1609 0 0 4 4 24 924 924 1924 6924 48 49 IGAAAA XJCAAA HHHHxx
+5946 1610 0 2 6 6 46 946 1946 946 5946 92 93 SUAAAA YJCAAA OOOOxx
+4294 1611 0 2 4 14 94 294 294 4294 4294 188 189 EJAAAA ZJCAAA VVVVxx
+2936 1612 0 0 6 16 36 936 936 2936 2936 72 73 YIAAAA AKCAAA AAAAxx
+3855 1613 1 3 5 15 55 855 1855 3855 3855 110 111 HSAAAA BKCAAA HHHHxx
+455 1614 1 3 5 15 55 455 455 455 455 110 111 NRAAAA CKCAAA OOOOxx
+2918 1615 0 2 8 18 18 918 918 2918 2918 36 37 GIAAAA DKCAAA VVVVxx
+448 1616 0 0 8 8 48 448 448 448 448 96 97 GRAAAA EKCAAA AAAAxx
+2149 1617 1 1 9 9 49 149 149 2149 2149 98 99 REAAAA FKCAAA HHHHxx
+8890 1618 0 2 0 10 90 890 890 3890 8890 180 181 YDAAAA GKCAAA OOOOxx
+8919 1619 1 3 9 19 19 919 919 3919 8919 38 39 BFAAAA HKCAAA VVVVxx
+4957 1620 1 1 7 17 57 957 957 4957 4957 114 115 RIAAAA IKCAAA AAAAxx
+4 1621 0 0 4 4 4 4 4 4 4 8 9 EAAAAA JKCAAA HHHHxx
+4837 1622 1 1 7 17 37 837 837 4837 4837 74 75 BEAAAA KKCAAA OOOOxx
+3976 1623 0 0 6 16 76 976 1976 3976 3976 152 153 YWAAAA LKCAAA VVVVxx
+9459 1624 1 3 9 19 59 459 1459 4459 9459 118 119 VZAAAA MKCAAA AAAAxx
+7097 1625 1 1 7 17 97 97 1097 2097 7097 194 195 ZMAAAA NKCAAA HHHHxx
+9226 1626 0 2 6 6 26 226 1226 4226 9226 52 53 WQAAAA OKCAAA OOOOxx
+5803 1627 1 3 3 3 3 803 1803 803 5803 6 7 FPAAAA PKCAAA VVVVxx
+21 1628 1 1 1 1 21 21 21 21 21 42 43 VAAAAA QKCAAA AAAAxx
+5275 1629 1 3 5 15 75 275 1275 275 5275 150 151 XUAAAA RKCAAA HHHHxx
+3488 1630 0 0 8 8 88 488 1488 3488 3488 176 177 EEAAAA SKCAAA OOOOxx
+1595 1631 1 3 5 15 95 595 1595 1595 1595 190 191 JJAAAA TKCAAA VVVVxx
+5212 1632 0 0 2 12 12 212 1212 212 5212 24 25 MSAAAA UKCAAA AAAAxx
+6574 1633 0 2 4 14 74 574 574 1574 6574 148 149 WSAAAA VKCAAA HHHHxx
+7524 1634 0 0 4 4 24 524 1524 2524 7524 48 49 KDAAAA WKCAAA OOOOxx
+6100 1635 0 0 0 0 0 100 100 1100 6100 0 1 QAAAAA XKCAAA VVVVxx
+1198 1636 0 2 8 18 98 198 1198 1198 1198 196 197 CUAAAA YKCAAA AAAAxx
+7345 1637 1 1 5 5 45 345 1345 2345 7345 90 91 NWAAAA ZKCAAA HHHHxx
+5020 1638 0 0 0 0 20 20 1020 20 5020 40 41 CLAAAA ALCAAA OOOOxx
+6925 1639 1 1 5 5 25 925 925 1925 6925 50 51 JGAAAA BLCAAA VVVVxx
+8915 1640 1 3 5 15 15 915 915 3915 8915 30 31 XEAAAA CLCAAA AAAAxx
+3088 1641 0 0 8 8 88 88 1088 3088 3088 176 177 UOAAAA DLCAAA HHHHxx
+4828 1642 0 0 8 8 28 828 828 4828 4828 56 57 SDAAAA ELCAAA OOOOxx
+7276 1643 0 0 6 16 76 276 1276 2276 7276 152 153 WTAAAA FLCAAA VVVVxx
+299 1644 1 3 9 19 99 299 299 299 299 198 199 NLAAAA GLCAAA AAAAxx
+76 1645 0 0 6 16 76 76 76 76 76 152 153 YCAAAA HLCAAA HHHHxx
+8458 1646 0 2 8 18 58 458 458 3458 8458 116 117 INAAAA ILCAAA OOOOxx
+7207 1647 1 3 7 7 7 207 1207 2207 7207 14 15 FRAAAA JLCAAA VVVVxx
+5585 1648 1 1 5 5 85 585 1585 585 5585 170 171 VGAAAA KLCAAA AAAAxx
+3234 1649 0 2 4 14 34 234 1234 3234 3234 68 69 KUAAAA LLCAAA HHHHxx
+8001 1650 1 1 1 1 1 1 1 3001 8001 2 3 TVAAAA MLCAAA OOOOxx
+1319 1651 1 3 9 19 19 319 1319 1319 1319 38 39 TYAAAA NLCAAA VVVVxx
+6342 1652 0 2 2 2 42 342 342 1342 6342 84 85 YJAAAA OLCAAA AAAAxx
+9199 1653 1 3 9 19 99 199 1199 4199 9199 198 199 VPAAAA PLCAAA HHHHxx
+5696 1654 0 0 6 16 96 696 1696 696 5696 192 193 CLAAAA QLCAAA OOOOxx
+2562 1655 0 2 2 2 62 562 562 2562 2562 124 125 OUAAAA RLCAAA VVVVxx
+4226 1656 0 2 6 6 26 226 226 4226 4226 52 53 OGAAAA SLCAAA AAAAxx
+1184 1657 0 0 4 4 84 184 1184 1184 1184 168 169 OTAAAA TLCAAA HHHHxx
+5807 1658 1 3 7 7 7 807 1807 807 5807 14 15 JPAAAA ULCAAA OOOOxx
+1890 1659 0 2 0 10 90 890 1890 1890 1890 180 181 SUAAAA VLCAAA VVVVxx
+451 1660 1 3 1 11 51 451 451 451 451 102 103 JRAAAA WLCAAA AAAAxx
+1049 1661 1 1 9 9 49 49 1049 1049 1049 98 99 JOAAAA XLCAAA HHHHxx
+5272 1662 0 0 2 12 72 272 1272 272 5272 144 145 UUAAAA YLCAAA OOOOxx
+4588 1663 0 0 8 8 88 588 588 4588 4588 176 177 MUAAAA ZLCAAA VVVVxx
+5213 1664 1 1 3 13 13 213 1213 213 5213 26 27 NSAAAA AMCAAA AAAAxx
+9543 1665 1 3 3 3 43 543 1543 4543 9543 86 87 BDAAAA BMCAAA HHHHxx
+6318 1666 0 2 8 18 18 318 318 1318 6318 36 37 AJAAAA CMCAAA OOOOxx
+7992 1667 0 0 2 12 92 992 1992 2992 7992 184 185 KVAAAA DMCAAA VVVVxx
+4619 1668 1 3 9 19 19 619 619 4619 4619 38 39 RVAAAA EMCAAA AAAAxx
+7189 1669 1 1 9 9 89 189 1189 2189 7189 178 179 NQAAAA FMCAAA HHHHxx
+2178 1670 0 2 8 18 78 178 178 2178 2178 156 157 UFAAAA GMCAAA OOOOxx
+4928 1671 0 0 8 8 28 928 928 4928 4928 56 57 OHAAAA HMCAAA VVVVxx
+3966 1672 0 2 6 6 66 966 1966 3966 3966 132 133 OWAAAA IMCAAA AAAAxx
+9790 1673 0 2 0 10 90 790 1790 4790 9790 180 181 OMAAAA JMCAAA HHHHxx
+9150 1674 0 2 0 10 50 150 1150 4150 9150 100 101 YNAAAA KMCAAA OOOOxx
+313 1675 1 1 3 13 13 313 313 313 313 26 27 BMAAAA LMCAAA VVVVxx
+1614 1676 0 2 4 14 14 614 1614 1614 1614 28 29 CKAAAA MMCAAA AAAAxx
+1581 1677 1 1 1 1 81 581 1581 1581 1581 162 163 VIAAAA NMCAAA HHHHxx
+3674 1678 0 2 4 14 74 674 1674 3674 3674 148 149 ILAAAA OMCAAA OOOOxx
+3444 1679 0 0 4 4 44 444 1444 3444 3444 88 89 MCAAAA PMCAAA VVVVxx
+1050 1680 0 2 0 10 50 50 1050 1050 1050 100 101 KOAAAA QMCAAA AAAAxx
+8241 1681 1 1 1 1 41 241 241 3241 8241 82 83 ZEAAAA RMCAAA HHHHxx
+3382 1682 0 2 2 2 82 382 1382 3382 3382 164 165 CAAAAA SMCAAA OOOOxx
+7105 1683 1 1 5 5 5 105 1105 2105 7105 10 11 HNAAAA TMCAAA VVVVxx
+2957 1684 1 1 7 17 57 957 957 2957 2957 114 115 TJAAAA UMCAAA AAAAxx
+6162 1685 0 2 2 2 62 162 162 1162 6162 124 125 ADAAAA VMCAAA HHHHxx
+5150 1686 0 2 0 10 50 150 1150 150 5150 100 101 CQAAAA WMCAAA OOOOxx
+2622 1687 0 2 2 2 22 622 622 2622 2622 44 45 WWAAAA XMCAAA VVVVxx
+2240 1688 0 0 0 0 40 240 240 2240 2240 80 81 EIAAAA YMCAAA AAAAxx
+8880 1689 0 0 0 0 80 880 880 3880 8880 160 161 ODAAAA ZMCAAA HHHHxx
+9250 1690 0 2 0 10 50 250 1250 4250 9250 100 101 URAAAA ANCAAA OOOOxx
+7010 1691 0 2 0 10 10 10 1010 2010 7010 20 21 QJAAAA BNCAAA VVVVxx
+1098 1692 0 2 8 18 98 98 1098 1098 1098 196 197 GQAAAA CNCAAA AAAAxx
+648 1693 0 0 8 8 48 648 648 648 648 96 97 YYAAAA DNCAAA HHHHxx
+5536 1694 0 0 6 16 36 536 1536 536 5536 72 73 YEAAAA ENCAAA OOOOxx
+7858 1695 0 2 8 18 58 858 1858 2858 7858 116 117 GQAAAA FNCAAA VVVVxx
+7053 1696 1 1 3 13 53 53 1053 2053 7053 106 107 HLAAAA GNCAAA AAAAxx
+8681 1697 1 1 1 1 81 681 681 3681 8681 162 163 XVAAAA HNCAAA HHHHxx
+8832 1698 0 0 2 12 32 832 832 3832 8832 64 65 SBAAAA INCAAA OOOOxx
+6836 1699 0 0 6 16 36 836 836 1836 6836 72 73 YCAAAA JNCAAA VVVVxx
+4856 1700 0 0 6 16 56 856 856 4856 4856 112 113 UEAAAA KNCAAA AAAAxx
+345 1701 1 1 5 5 45 345 345 345 345 90 91 HNAAAA LNCAAA HHHHxx
+6559 1702 1 3 9 19 59 559 559 1559 6559 118 119 HSAAAA MNCAAA OOOOxx
+3017 1703 1 1 7 17 17 17 1017 3017 3017 34 35 BMAAAA NNCAAA VVVVxx
+4176 1704 0 0 6 16 76 176 176 4176 4176 152 153 QEAAAA ONCAAA AAAAxx
+2839 1705 1 3 9 19 39 839 839 2839 2839 78 79 FFAAAA PNCAAA HHHHxx
+6065 1706 1 1 5 5 65 65 65 1065 6065 130 131 HZAAAA QNCAAA OOOOxx
+7360 1707 0 0 0 0 60 360 1360 2360 7360 120 121 CXAAAA RNCAAA VVVVxx
+9527 1708 1 3 7 7 27 527 1527 4527 9527 54 55 LCAAAA SNCAAA AAAAxx
+8849 1709 1 1 9 9 49 849 849 3849 8849 98 99 JCAAAA TNCAAA HHHHxx
+7274 1710 0 2 4 14 74 274 1274 2274 7274 148 149 UTAAAA UNCAAA OOOOxx
+4368 1711 0 0 8 8 68 368 368 4368 4368 136 137 AMAAAA VNCAAA VVVVxx
+2488 1712 0 0 8 8 88 488 488 2488 2488 176 177 SRAAAA WNCAAA AAAAxx
+4674 1713 0 2 4 14 74 674 674 4674 4674 148 149 UXAAAA XNCAAA HHHHxx
+365 1714 1 1 5 5 65 365 365 365 365 130 131 BOAAAA YNCAAA OOOOxx
+5897 1715 1 1 7 17 97 897 1897 897 5897 194 195 VSAAAA ZNCAAA VVVVxx
+8918 1716 0 2 8 18 18 918 918 3918 8918 36 37 AFAAAA AOCAAA AAAAxx
+1988 1717 0 0 8 8 88 988 1988 1988 1988 176 177 MYAAAA BOCAAA HHHHxx
+1210 1718 0 2 0 10 10 210 1210 1210 1210 20 21 OUAAAA COCAAA OOOOxx
+2945 1719 1 1 5 5 45 945 945 2945 2945 90 91 HJAAAA DOCAAA VVVVxx
+555 1720 1 3 5 15 55 555 555 555 555 110 111 JVAAAA EOCAAA AAAAxx
+9615 1721 1 3 5 15 15 615 1615 4615 9615 30 31 VFAAAA FOCAAA HHHHxx
+9939 1722 1 3 9 19 39 939 1939 4939 9939 78 79 HSAAAA GOCAAA OOOOxx
+1216 1723 0 0 6 16 16 216 1216 1216 1216 32 33 UUAAAA HOCAAA VVVVxx
+745 1724 1 1 5 5 45 745 745 745 745 90 91 RCAAAA IOCAAA AAAAxx
+3326 1725 0 2 6 6 26 326 1326 3326 3326 52 53 YXAAAA JOCAAA HHHHxx
+953 1726 1 1 3 13 53 953 953 953 953 106 107 RKAAAA KOCAAA OOOOxx
+444 1727 0 0 4 4 44 444 444 444 444 88 89 CRAAAA LOCAAA VVVVxx
+280 1728 0 0 0 0 80 280 280 280 280 160 161 UKAAAA MOCAAA AAAAxx
+3707 1729 1 3 7 7 7 707 1707 3707 3707 14 15 PMAAAA NOCAAA HHHHxx
+1351 1730 1 3 1 11 51 351 1351 1351 1351 102 103 ZZAAAA OOCAAA OOOOxx
+1280 1731 0 0 0 0 80 280 1280 1280 1280 160 161 GXAAAA POCAAA VVVVxx
+628 1732 0 0 8 8 28 628 628 628 628 56 57 EYAAAA QOCAAA AAAAxx
+6198 1733 0 2 8 18 98 198 198 1198 6198 196 197 KEAAAA ROCAAA HHHHxx
+1957 1734 1 1 7 17 57 957 1957 1957 1957 114 115 HXAAAA SOCAAA OOOOxx
+9241 1735 1 1 1 1 41 241 1241 4241 9241 82 83 LRAAAA TOCAAA VVVVxx
+303 1736 1 3 3 3 3 303 303 303 303 6 7 RLAAAA UOCAAA AAAAxx
+1945 1737 1 1 5 5 45 945 1945 1945 1945 90 91 VWAAAA VOCAAA HHHHxx
+3634 1738 0 2 4 14 34 634 1634 3634 3634 68 69 UJAAAA WOCAAA OOOOxx
+4768 1739 0 0 8 8 68 768 768 4768 4768 136 137 KBAAAA XOCAAA VVVVxx
+9262 1740 0 2 2 2 62 262 1262 4262 9262 124 125 GSAAAA YOCAAA AAAAxx
+2610 1741 0 2 0 10 10 610 610 2610 2610 20 21 KWAAAA ZOCAAA HHHHxx
+6640 1742 0 0 0 0 40 640 640 1640 6640 80 81 KVAAAA APCAAA OOOOxx
+3338 1743 0 2 8 18 38 338 1338 3338 3338 76 77 KYAAAA BPCAAA VVVVxx
+6560 1744 0 0 0 0 60 560 560 1560 6560 120 121 ISAAAA CPCAAA AAAAxx
+5986 1745 0 2 6 6 86 986 1986 986 5986 172 173 GWAAAA DPCAAA HHHHxx
+2970 1746 0 2 0 10 70 970 970 2970 2970 140 141 GKAAAA EPCAAA OOOOxx
+4731 1747 1 3 1 11 31 731 731 4731 4731 62 63 ZZAAAA FPCAAA VVVVxx
+9486 1748 0 2 6 6 86 486 1486 4486 9486 172 173 WAAAAA GPCAAA AAAAxx
+7204 1749 0 0 4 4 4 204 1204 2204 7204 8 9 CRAAAA HPCAAA HHHHxx
+6685 1750 1 1 5 5 85 685 685 1685 6685 170 171 DXAAAA IPCAAA OOOOxx
+6852 1751 0 0 2 12 52 852 852 1852 6852 104 105 ODAAAA JPCAAA VVVVxx
+2325 1752 1 1 5 5 25 325 325 2325 2325 50 51 LLAAAA KPCAAA AAAAxx
+1063 1753 1 3 3 3 63 63 1063 1063 1063 126 127 XOAAAA LPCAAA HHHHxx
+6810 1754 0 2 0 10 10 810 810 1810 6810 20 21 YBAAAA MPCAAA OOOOxx
+7718 1755 0 2 8 18 18 718 1718 2718 7718 36 37 WKAAAA NPCAAA VVVVxx
+1680 1756 0 0 0 0 80 680 1680 1680 1680 160 161 QMAAAA OPCAAA AAAAxx
+7402 1757 0 2 2 2 2 402 1402 2402 7402 4 5 SYAAAA PPCAAA HHHHxx
+4134 1758 0 2 4 14 34 134 134 4134 4134 68 69 ADAAAA QPCAAA OOOOxx
+8232 1759 0 0 2 12 32 232 232 3232 8232 64 65 QEAAAA RPCAAA VVVVxx
+6682 1760 0 2 2 2 82 682 682 1682 6682 164 165 AXAAAA SPCAAA AAAAxx
+7952 1761 0 0 2 12 52 952 1952 2952 7952 104 105 WTAAAA TPCAAA HHHHxx
+5943 1762 1 3 3 3 43 943 1943 943 5943 86 87 PUAAAA UPCAAA OOOOxx
+5394 1763 0 2 4 14 94 394 1394 394 5394 188 189 MZAAAA VPCAAA VVVVxx
+6554 1764 0 2 4 14 54 554 554 1554 6554 108 109 CSAAAA WPCAAA AAAAxx
+8186 1765 0 2 6 6 86 186 186 3186 8186 172 173 WCAAAA XPCAAA HHHHxx
+199 1766 1 3 9 19 99 199 199 199 199 198 199 RHAAAA YPCAAA OOOOxx
+3386 1767 0 2 6 6 86 386 1386 3386 3386 172 173 GAAAAA ZPCAAA VVVVxx
+8974 1768 0 2 4 14 74 974 974 3974 8974 148 149 EHAAAA AQCAAA AAAAxx
+8140 1769 0 0 0 0 40 140 140 3140 8140 80 81 CBAAAA BQCAAA HHHHxx
+3723 1770 1 3 3 3 23 723 1723 3723 3723 46 47 FNAAAA CQCAAA OOOOxx
+8827 1771 1 3 7 7 27 827 827 3827 8827 54 55 NBAAAA DQCAAA VVVVxx
+1998 1772 0 2 8 18 98 998 1998 1998 1998 196 197 WYAAAA EQCAAA AAAAxx
+879 1773 1 3 9 19 79 879 879 879 879 158 159 VHAAAA FQCAAA HHHHxx
+892 1774 0 0 2 12 92 892 892 892 892 184 185 IIAAAA GQCAAA OOOOxx
+9468 1775 0 0 8 8 68 468 1468 4468 9468 136 137 EAAAAA HQCAAA VVVVxx
+3797 1776 1 1 7 17 97 797 1797 3797 3797 194 195 BQAAAA IQCAAA AAAAxx
+8379 1777 1 3 9 19 79 379 379 3379 8379 158 159 HKAAAA JQCAAA HHHHxx
+2817 1778 1 1 7 17 17 817 817 2817 2817 34 35 JEAAAA KQCAAA OOOOxx
+789 1779 1 1 9 9 89 789 789 789 789 178 179 JEAAAA LQCAAA VVVVxx
+3871 1780 1 3 1 11 71 871 1871 3871 3871 142 143 XSAAAA MQCAAA AAAAxx
+7931 1781 1 3 1 11 31 931 1931 2931 7931 62 63 BTAAAA NQCAAA HHHHxx
+3636 1782 0 0 6 16 36 636 1636 3636 3636 72 73 WJAAAA OQCAAA OOOOxx
+699 1783 1 3 9 19 99 699 699 699 699 198 199 XAAAAA PQCAAA VVVVxx
+6850 1784 0 2 0 10 50 850 850 1850 6850 100 101 MDAAAA QQCAAA AAAAxx
+6394 1785 0 2 4 14 94 394 394 1394 6394 188 189 YLAAAA RQCAAA HHHHxx
+3475 1786 1 3 5 15 75 475 1475 3475 3475 150 151 RDAAAA SQCAAA OOOOxx
+3026 1787 0 2 6 6 26 26 1026 3026 3026 52 53 KMAAAA TQCAAA VVVVxx
+876 1788 0 0 6 16 76 876 876 876 876 152 153 SHAAAA UQCAAA AAAAxx
+1992 1789 0 0 2 12 92 992 1992 1992 1992 184 185 QYAAAA VQCAAA HHHHxx
+3079 1790 1 3 9 19 79 79 1079 3079 3079 158 159 LOAAAA WQCAAA OOOOxx
+8128 1791 0 0 8 8 28 128 128 3128 8128 56 57 QAAAAA XQCAAA VVVVxx
+8123 1792 1 3 3 3 23 123 123 3123 8123 46 47 LAAAAA YQCAAA AAAAxx
+3285 1793 1 1 5 5 85 285 1285 3285 3285 170 171 JWAAAA ZQCAAA HHHHxx
+9315 1794 1 3 5 15 15 315 1315 4315 9315 30 31 HUAAAA ARCAAA OOOOxx
+9862 1795 0 2 2 2 62 862 1862 4862 9862 124 125 IPAAAA BRCAAA VVVVxx
+2764 1796 0 0 4 4 64 764 764 2764 2764 128 129 ICAAAA CRCAAA AAAAxx
+3544 1797 0 0 4 4 44 544 1544 3544 3544 88 89 IGAAAA DRCAAA HHHHxx
+7747 1798 1 3 7 7 47 747 1747 2747 7747 94 95 ZLAAAA ERCAAA OOOOxx
+7725 1799 1 1 5 5 25 725 1725 2725 7725 50 51 DLAAAA FRCAAA VVVVxx
+2449 1800 1 1 9 9 49 449 449 2449 2449 98 99 FQAAAA GRCAAA AAAAxx
+8967 1801 1 3 7 7 67 967 967 3967 8967 134 135 XGAAAA HRCAAA HHHHxx
+7371 1802 1 3 1 11 71 371 1371 2371 7371 142 143 NXAAAA IRCAAA OOOOxx
+2158 1803 0 2 8 18 58 158 158 2158 2158 116 117 AFAAAA JRCAAA VVVVxx
+5590 1804 0 2 0 10 90 590 1590 590 5590 180 181 AHAAAA KRCAAA AAAAxx
+8072 1805 0 0 2 12 72 72 72 3072 8072 144 145 MYAAAA LRCAAA HHHHxx
+1971 1806 1 3 1 11 71 971 1971 1971 1971 142 143 VXAAAA MRCAAA OOOOxx
+772 1807 0 0 2 12 72 772 772 772 772 144 145 SDAAAA NRCAAA VVVVxx
+3433 1808 1 1 3 13 33 433 1433 3433 3433 66 67 BCAAAA ORCAAA AAAAxx
+8419 1809 1 3 9 19 19 419 419 3419 8419 38 39 VLAAAA PRCAAA HHHHxx
+1493 1810 1 1 3 13 93 493 1493 1493 1493 186 187 LFAAAA QRCAAA OOOOxx
+2584 1811 0 0 4 4 84 584 584 2584 2584 168 169 KVAAAA RRCAAA VVVVxx
+9502 1812 0 2 2 2 2 502 1502 4502 9502 4 5 MBAAAA SRCAAA AAAAxx
+4673 1813 1 1 3 13 73 673 673 4673 4673 146 147 TXAAAA TRCAAA HHHHxx
+7403 1814 1 3 3 3 3 403 1403 2403 7403 6 7 TYAAAA URCAAA OOOOxx
+7103 1815 1 3 3 3 3 103 1103 2103 7103 6 7 FNAAAA VRCAAA VVVVxx
+7026 1816 0 2 6 6 26 26 1026 2026 7026 52 53 GKAAAA WRCAAA AAAAxx
+8574 1817 0 2 4 14 74 574 574 3574 8574 148 149 URAAAA XRCAAA HHHHxx
+1366 1818 0 2 6 6 66 366 1366 1366 1366 132 133 OAAAAA YRCAAA OOOOxx
+5787 1819 1 3 7 7 87 787 1787 787 5787 174 175 POAAAA ZRCAAA VVVVxx
+2552 1820 0 0 2 12 52 552 552 2552 2552 104 105 EUAAAA ASCAAA AAAAxx
+4557 1821 1 1 7 17 57 557 557 4557 4557 114 115 HTAAAA BSCAAA HHHHxx
+3237 1822 1 1 7 17 37 237 1237 3237 3237 74 75 NUAAAA CSCAAA OOOOxx
+6901 1823 1 1 1 1 1 901 901 1901 6901 2 3 LFAAAA DSCAAA VVVVxx
+7708 1824 0 0 8 8 8 708 1708 2708 7708 16 17 MKAAAA ESCAAA AAAAxx
+2011 1825 1 3 1 11 11 11 11 2011 2011 22 23 JZAAAA FSCAAA HHHHxx
+9455 1826 1 3 5 15 55 455 1455 4455 9455 110 111 RZAAAA GSCAAA OOOOxx
+5228 1827 0 0 8 8 28 228 1228 228 5228 56 57 CTAAAA HSCAAA VVVVxx
+4043 1828 1 3 3 3 43 43 43 4043 4043 86 87 NZAAAA ISCAAA AAAAxx
+8242 1829 0 2 2 2 42 242 242 3242 8242 84 85 AFAAAA JSCAAA HHHHxx
+6351 1830 1 3 1 11 51 351 351 1351 6351 102 103 HKAAAA KSCAAA OOOOxx
+5899 1831 1 3 9 19 99 899 1899 899 5899 198 199 XSAAAA LSCAAA VVVVxx
+4849 1832 1 1 9 9 49 849 849 4849 4849 98 99 NEAAAA MSCAAA AAAAxx
+9583 1833 1 3 3 3 83 583 1583 4583 9583 166 167 PEAAAA NSCAAA HHHHxx
+4994 1834 0 2 4 14 94 994 994 4994 4994 188 189 CKAAAA OSCAAA OOOOxx
+9787 1835 1 3 7 7 87 787 1787 4787 9787 174 175 LMAAAA PSCAAA VVVVxx
+243 1836 1 3 3 3 43 243 243 243 243 86 87 JJAAAA QSCAAA AAAAxx
+3931 1837 1 3 1 11 31 931 1931 3931 3931 62 63 FVAAAA RSCAAA HHHHxx
+5945 1838 1 1 5 5 45 945 1945 945 5945 90 91 RUAAAA SSCAAA OOOOxx
+1325 1839 1 1 5 5 25 325 1325 1325 1325 50 51 ZYAAAA TSCAAA VVVVxx
+4142 1840 0 2 2 2 42 142 142 4142 4142 84 85 IDAAAA USCAAA AAAAxx
+1963 1841 1 3 3 3 63 963 1963 1963 1963 126 127 NXAAAA VSCAAA HHHHxx
+7041 1842 1 1 1 1 41 41 1041 2041 7041 82 83 VKAAAA WSCAAA OOOOxx
+3074 1843 0 2 4 14 74 74 1074 3074 3074 148 149 GOAAAA XSCAAA VVVVxx
+3290 1844 0 2 0 10 90 290 1290 3290 3290 180 181 OWAAAA YSCAAA AAAAxx
+4146 1845 0 2 6 6 46 146 146 4146 4146 92 93 MDAAAA ZSCAAA HHHHxx
+3832 1846 0 0 2 12 32 832 1832 3832 3832 64 65 KRAAAA ATCAAA OOOOxx
+2217 1847 1 1 7 17 17 217 217 2217 2217 34 35 HHAAAA BTCAAA VVVVxx
+635 1848 1 3 5 15 35 635 635 635 635 70 71 LYAAAA CTCAAA AAAAxx
+6967 1849 1 3 7 7 67 967 967 1967 6967 134 135 ZHAAAA DTCAAA HHHHxx
+3522 1850 0 2 2 2 22 522 1522 3522 3522 44 45 MFAAAA ETCAAA OOOOxx
+2471 1851 1 3 1 11 71 471 471 2471 2471 142 143 BRAAAA FTCAAA VVVVxx
+4236 1852 0 0 6 16 36 236 236 4236 4236 72 73 YGAAAA GTCAAA AAAAxx
+853 1853 1 1 3 13 53 853 853 853 853 106 107 VGAAAA HTCAAA HHHHxx
+3754 1854 0 2 4 14 54 754 1754 3754 3754 108 109 KOAAAA ITCAAA OOOOxx
+796 1855 0 0 6 16 96 796 796 796 796 192 193 QEAAAA JTCAAA VVVVxx
+4640 1856 0 0 0 0 40 640 640 4640 4640 80 81 MWAAAA KTCAAA AAAAxx
+9496 1857 0 0 6 16 96 496 1496 4496 9496 192 193 GBAAAA LTCAAA HHHHxx
+6873 1858 1 1 3 13 73 873 873 1873 6873 146 147 JEAAAA MTCAAA OOOOxx
+4632 1859 0 0 2 12 32 632 632 4632 4632 64 65 EWAAAA NTCAAA VVVVxx
+5758 1860 0 2 8 18 58 758 1758 758 5758 116 117 MNAAAA OTCAAA AAAAxx
+6514 1861 0 2 4 14 14 514 514 1514 6514 28 29 OQAAAA PTCAAA HHHHxx
+9510 1862 0 2 0 10 10 510 1510 4510 9510 20 21 UBAAAA QTCAAA OOOOxx
+8411 1863 1 3 1 11 11 411 411 3411 8411 22 23 NLAAAA RTCAAA VVVVxx
+7762 1864 0 2 2 2 62 762 1762 2762 7762 124 125 OMAAAA STCAAA AAAAxx
+2225 1865 1 1 5 5 25 225 225 2225 2225 50 51 PHAAAA TTCAAA HHHHxx
+4373 1866 1 1 3 13 73 373 373 4373 4373 146 147 FMAAAA UTCAAA OOOOxx
+7326 1867 0 2 6 6 26 326 1326 2326 7326 52 53 UVAAAA VTCAAA VVVVxx
+8651 1868 1 3 1 11 51 651 651 3651 8651 102 103 TUAAAA WTCAAA AAAAxx
+9825 1869 1 1 5 5 25 825 1825 4825 9825 50 51 XNAAAA XTCAAA HHHHxx
+2988 1870 0 0 8 8 88 988 988 2988 2988 176 177 YKAAAA YTCAAA OOOOxx
+8138 1871 0 2 8 18 38 138 138 3138 8138 76 77 ABAAAA ZTCAAA VVVVxx
+7792 1872 0 0 2 12 92 792 1792 2792 7792 184 185 SNAAAA AUCAAA AAAAxx
+1232 1873 0 0 2 12 32 232 1232 1232 1232 64 65 KVAAAA BUCAAA HHHHxx
+8221 1874 1 1 1 1 21 221 221 3221 8221 42 43 FEAAAA CUCAAA OOOOxx
+4044 1875 0 0 4 4 44 44 44 4044 4044 88 89 OZAAAA DUCAAA VVVVxx
+1204 1876 0 0 4 4 4 204 1204 1204 1204 8 9 IUAAAA EUCAAA AAAAxx
+5145 1877 1 1 5 5 45 145 1145 145 5145 90 91 XPAAAA FUCAAA HHHHxx
+7791 1878 1 3 1 11 91 791 1791 2791 7791 182 183 RNAAAA GUCAAA OOOOxx
+8270 1879 0 2 0 10 70 270 270 3270 8270 140 141 CGAAAA HUCAAA VVVVxx
+9427 1880 1 3 7 7 27 427 1427 4427 9427 54 55 PYAAAA IUCAAA AAAAxx
+2152 1881 0 0 2 12 52 152 152 2152 2152 104 105 UEAAAA JUCAAA HHHHxx
+7790 1882 0 2 0 10 90 790 1790 2790 7790 180 181 QNAAAA KUCAAA OOOOxx
+5301 1883 1 1 1 1 1 301 1301 301 5301 2 3 XVAAAA LUCAAA VVVVxx
+626 1884 0 2 6 6 26 626 626 626 626 52 53 CYAAAA MUCAAA AAAAxx
+260 1885 0 0 0 0 60 260 260 260 260 120 121 AKAAAA NUCAAA HHHHxx
+4369 1886 1 1 9 9 69 369 369 4369 4369 138 139 BMAAAA OUCAAA OOOOxx
+5457 1887 1 1 7 17 57 457 1457 457 5457 114 115 XBAAAA PUCAAA VVVVxx
+3468 1888 0 0 8 8 68 468 1468 3468 3468 136 137 KDAAAA QUCAAA AAAAxx
+2257 1889 1 1 7 17 57 257 257 2257 2257 114 115 VIAAAA RUCAAA HHHHxx
+9318 1890 0 2 8 18 18 318 1318 4318 9318 36 37 KUAAAA SUCAAA OOOOxx
+8762 1891 0 2 2 2 62 762 762 3762 8762 124 125 AZAAAA TUCAAA VVVVxx
+9153 1892 1 1 3 13 53 153 1153 4153 9153 106 107 BOAAAA UUCAAA AAAAxx
+9220 1893 0 0 0 0 20 220 1220 4220 9220 40 41 QQAAAA VUCAAA HHHHxx
+8003 1894 1 3 3 3 3 3 3 3003 8003 6 7 VVAAAA WUCAAA OOOOxx
+7257 1895 1 1 7 17 57 257 1257 2257 7257 114 115 DTAAAA XUCAAA VVVVxx
+3930 1896 0 2 0 10 30 930 1930 3930 3930 60 61 EVAAAA YUCAAA AAAAxx
+2976 1897 0 0 6 16 76 976 976 2976 2976 152 153 MKAAAA ZUCAAA HHHHxx
+2531 1898 1 3 1 11 31 531 531 2531 2531 62 63 JTAAAA AVCAAA OOOOxx
+2250 1899 0 2 0 10 50 250 250 2250 2250 100 101 OIAAAA BVCAAA VVVVxx
+8549 1900 1 1 9 9 49 549 549 3549 8549 98 99 VQAAAA CVCAAA AAAAxx
+7197 1901 1 1 7 17 97 197 1197 2197 7197 194 195 VQAAAA DVCAAA HHHHxx
+5916 1902 0 0 6 16 16 916 1916 916 5916 32 33 OTAAAA EVCAAA OOOOxx
+5287 1903 1 3 7 7 87 287 1287 287 5287 174 175 JVAAAA FVCAAA VVVVxx
+9095 1904 1 3 5 15 95 95 1095 4095 9095 190 191 VLAAAA GVCAAA AAAAxx
+7137 1905 1 1 7 17 37 137 1137 2137 7137 74 75 NOAAAA HVCAAA HHHHxx
+7902 1906 0 2 2 2 2 902 1902 2902 7902 4 5 YRAAAA IVCAAA OOOOxx
+7598 1907 0 2 8 18 98 598 1598 2598 7598 196 197 GGAAAA JVCAAA VVVVxx
+5652 1908 0 0 2 12 52 652 1652 652 5652 104 105 KJAAAA KVCAAA AAAAxx
+2017 1909 1 1 7 17 17 17 17 2017 2017 34 35 PZAAAA LVCAAA HHHHxx
+7255 1910 1 3 5 15 55 255 1255 2255 7255 110 111 BTAAAA MVCAAA OOOOxx
+7999 1911 1 3 9 19 99 999 1999 2999 7999 198 199 RVAAAA NVCAAA VVVVxx
+5388 1912 0 0 8 8 88 388 1388 388 5388 176 177 GZAAAA OVCAAA AAAAxx
+8754 1913 0 2 4 14 54 754 754 3754 8754 108 109 SYAAAA PVCAAA HHHHxx
+5415 1914 1 3 5 15 15 415 1415 415 5415 30 31 HAAAAA QVCAAA OOOOxx
+8861 1915 1 1 1 1 61 861 861 3861 8861 122 123 VCAAAA RVCAAA VVVVxx
+2874 1916 0 2 4 14 74 874 874 2874 2874 148 149 OGAAAA SVCAAA AAAAxx
+9910 1917 0 2 0 10 10 910 1910 4910 9910 20 21 ERAAAA TVCAAA HHHHxx
+5178 1918 0 2 8 18 78 178 1178 178 5178 156 157 ERAAAA UVCAAA OOOOxx
+5698 1919 0 2 8 18 98 698 1698 698 5698 196 197 ELAAAA VVCAAA VVVVxx
+8500 1920 0 0 0 0 0 500 500 3500 8500 0 1 YOAAAA WVCAAA AAAAxx
+1814 1921 0 2 4 14 14 814 1814 1814 1814 28 29 URAAAA XVCAAA HHHHxx
+4968 1922 0 0 8 8 68 968 968 4968 4968 136 137 CJAAAA YVCAAA OOOOxx
+2642 1923 0 2 2 2 42 642 642 2642 2642 84 85 QXAAAA ZVCAAA VVVVxx
+1578 1924 0 2 8 18 78 578 1578 1578 1578 156 157 SIAAAA AWCAAA AAAAxx
+4774 1925 0 2 4 14 74 774 774 4774 4774 148 149 QBAAAA BWCAAA HHHHxx
+7062 1926 0 2 2 2 62 62 1062 2062 7062 124 125 QLAAAA CWCAAA OOOOxx
+5381 1927 1 1 1 1 81 381 1381 381 5381 162 163 ZYAAAA DWCAAA VVVVxx
+7985 1928 1 1 5 5 85 985 1985 2985 7985 170 171 DVAAAA EWCAAA AAAAxx
+3850 1929 0 2 0 10 50 850 1850 3850 3850 100 101 CSAAAA FWCAAA HHHHxx
+5624 1930 0 0 4 4 24 624 1624 624 5624 48 49 IIAAAA GWCAAA OOOOxx
+8948 1931 0 0 8 8 48 948 948 3948 8948 96 97 EGAAAA HWCAAA VVVVxx
+995 1932 1 3 5 15 95 995 995 995 995 190 191 HMAAAA IWCAAA AAAAxx
+5058 1933 0 2 8 18 58 58 1058 58 5058 116 117 OMAAAA JWCAAA HHHHxx
+9670 1934 0 2 0 10 70 670 1670 4670 9670 140 141 YHAAAA KWCAAA OOOOxx
+3115 1935 1 3 5 15 15 115 1115 3115 3115 30 31 VPAAAA LWCAAA VVVVxx
+4935 1936 1 3 5 15 35 935 935 4935 4935 70 71 VHAAAA MWCAAA AAAAxx
+4735 1937 1 3 5 15 35 735 735 4735 4735 70 71 DAAAAA NWCAAA HHHHxx
+1348 1938 0 0 8 8 48 348 1348 1348 1348 96 97 WZAAAA OWCAAA OOOOxx
+2380 1939 0 0 0 0 80 380 380 2380 2380 160 161 ONAAAA PWCAAA VVVVxx
+4246 1940 0 2 6 6 46 246 246 4246 4246 92 93 IHAAAA QWCAAA AAAAxx
+522 1941 0 2 2 2 22 522 522 522 522 44 45 CUAAAA RWCAAA HHHHxx
+1701 1942 1 1 1 1 1 701 1701 1701 1701 2 3 LNAAAA SWCAAA OOOOxx
+9709 1943 1 1 9 9 9 709 1709 4709 9709 18 19 LJAAAA TWCAAA VVVVxx
+8829 1944 1 1 9 9 29 829 829 3829 8829 58 59 PBAAAA UWCAAA AAAAxx
+7936 1945 0 0 6 16 36 936 1936 2936 7936 72 73 GTAAAA VWCAAA HHHHxx
+8474 1946 0 2 4 14 74 474 474 3474 8474 148 149 YNAAAA WWCAAA OOOOxx
+4676 1947 0 0 6 16 76 676 676 4676 4676 152 153 WXAAAA XWCAAA VVVVxx
+6303 1948 1 3 3 3 3 303 303 1303 6303 6 7 LIAAAA YWCAAA AAAAxx
+3485 1949 1 1 5 5 85 485 1485 3485 3485 170 171 BEAAAA ZWCAAA HHHHxx
+2695 1950 1 3 5 15 95 695 695 2695 2695 190 191 RZAAAA AXCAAA OOOOxx
+8830 1951 0 2 0 10 30 830 830 3830 8830 60 61 QBAAAA BXCAAA VVVVxx
+898 1952 0 2 8 18 98 898 898 898 898 196 197 OIAAAA CXCAAA AAAAxx
+7268 1953 0 0 8 8 68 268 1268 2268 7268 136 137 OTAAAA DXCAAA HHHHxx
+6568 1954 0 0 8 8 68 568 568 1568 6568 136 137 QSAAAA EXCAAA OOOOxx
+9724 1955 0 0 4 4 24 724 1724 4724 9724 48 49 AKAAAA FXCAAA VVVVxx
+3329 1956 1 1 9 9 29 329 1329 3329 3329 58 59 BYAAAA GXCAAA AAAAxx
+9860 1957 0 0 0 0 60 860 1860 4860 9860 120 121 GPAAAA HXCAAA HHHHxx
+6833 1958 1 1 3 13 33 833 833 1833 6833 66 67 VCAAAA IXCAAA OOOOxx
+5956 1959 0 0 6 16 56 956 1956 956 5956 112 113 CVAAAA JXCAAA VVVVxx
+3963 1960 1 3 3 3 63 963 1963 3963 3963 126 127 LWAAAA KXCAAA AAAAxx
+883 1961 1 3 3 3 83 883 883 883 883 166 167 ZHAAAA LXCAAA HHHHxx
+2761 1962 1 1 1 1 61 761 761 2761 2761 122 123 FCAAAA MXCAAA OOOOxx
+4644 1963 0 0 4 4 44 644 644 4644 4644 88 89 QWAAAA NXCAAA VVVVxx
+1358 1964 0 2 8 18 58 358 1358 1358 1358 116 117 GAAAAA OXCAAA AAAAxx
+2049 1965 1 1 9 9 49 49 49 2049 2049 98 99 VAAAAA PXCAAA HHHHxx
+2193 1966 1 1 3 13 93 193 193 2193 2193 186 187 JGAAAA QXCAAA OOOOxx
+9435 1967 1 3 5 15 35 435 1435 4435 9435 70 71 XYAAAA RXCAAA VVVVxx
+5890 1968 0 2 0 10 90 890 1890 890 5890 180 181 OSAAAA SXCAAA AAAAxx
+8149 1969 1 1 9 9 49 149 149 3149 8149 98 99 LBAAAA TXCAAA HHHHxx
+423 1970 1 3 3 3 23 423 423 423 423 46 47 HQAAAA UXCAAA OOOOxx
+7980 1971 0 0 0 0 80 980 1980 2980 7980 160 161 YUAAAA VXCAAA VVVVxx
+9019 1972 1 3 9 19 19 19 1019 4019 9019 38 39 XIAAAA WXCAAA AAAAxx
+1647 1973 1 3 7 7 47 647 1647 1647 1647 94 95 JLAAAA XXCAAA HHHHxx
+9495 1974 1 3 5 15 95 495 1495 4495 9495 190 191 FBAAAA YXCAAA OOOOxx
+3904 1975 0 0 4 4 4 904 1904 3904 3904 8 9 EUAAAA ZXCAAA VVVVxx
+5838 1976 0 2 8 18 38 838 1838 838 5838 76 77 OQAAAA AYCAAA AAAAxx
+3866 1977 0 2 6 6 66 866 1866 3866 3866 132 133 SSAAAA BYCAAA HHHHxx
+3093 1978 1 1 3 13 93 93 1093 3093 3093 186 187 ZOAAAA CYCAAA OOOOxx
+9666 1979 0 2 6 6 66 666 1666 4666 9666 132 133 UHAAAA DYCAAA VVVVxx
+1246 1980 0 2 6 6 46 246 1246 1246 1246 92 93 YVAAAA EYCAAA AAAAxx
+9759 1981 1 3 9 19 59 759 1759 4759 9759 118 119 JLAAAA FYCAAA HHHHxx
+7174 1982 0 2 4 14 74 174 1174 2174 7174 148 149 YPAAAA GYCAAA OOOOxx
+7678 1983 0 2 8 18 78 678 1678 2678 7678 156 157 IJAAAA HYCAAA VVVVxx
+3004 1984 0 0 4 4 4 4 1004 3004 3004 8 9 OLAAAA IYCAAA AAAAxx
+5607 1985 1 3 7 7 7 607 1607 607 5607 14 15 RHAAAA JYCAAA HHHHxx
+8510 1986 0 2 0 10 10 510 510 3510 8510 20 21 IPAAAA KYCAAA OOOOxx
+1483 1987 1 3 3 3 83 483 1483 1483 1483 166 167 BFAAAA LYCAAA VVVVxx
+2915 1988 1 3 5 15 15 915 915 2915 2915 30 31 DIAAAA MYCAAA AAAAxx
+1548 1989 0 0 8 8 48 548 1548 1548 1548 96 97 OHAAAA NYCAAA HHHHxx
+5767 1990 1 3 7 7 67 767 1767 767 5767 134 135 VNAAAA OYCAAA OOOOxx
+3214 1991 0 2 4 14 14 214 1214 3214 3214 28 29 QTAAAA PYCAAA VVVVxx
+8663 1992 1 3 3 3 63 663 663 3663 8663 126 127 FVAAAA QYCAAA AAAAxx
+5425 1993 1 1 5 5 25 425 1425 425 5425 50 51 RAAAAA RYCAAA HHHHxx
+8530 1994 0 2 0 10 30 530 530 3530 8530 60 61 CQAAAA SYCAAA OOOOxx
+821 1995 1 1 1 1 21 821 821 821 821 42 43 PFAAAA TYCAAA VVVVxx
+8816 1996 0 0 6 16 16 816 816 3816 8816 32 33 CBAAAA UYCAAA AAAAxx
+9367 1997 1 3 7 7 67 367 1367 4367 9367 134 135 HWAAAA VYCAAA HHHHxx
+4138 1998 0 2 8 18 38 138 138 4138 4138 76 77 EDAAAA WYCAAA OOOOxx
+94 1999 0 2 4 14 94 94 94 94 94 188 189 QDAAAA XYCAAA VVVVxx
+1858 2000 0 2 8 18 58 858 1858 1858 1858 116 117 MTAAAA YYCAAA AAAAxx
+5513 2001 1 1 3 13 13 513 1513 513 5513 26 27 BEAAAA ZYCAAA HHHHxx
+9620 2002 0 0 0 0 20 620 1620 4620 9620 40 41 AGAAAA AZCAAA OOOOxx
+4770 2003 0 2 0 10 70 770 770 4770 4770 140 141 MBAAAA BZCAAA VVVVxx
+5193 2004 1 1 3 13 93 193 1193 193 5193 186 187 TRAAAA CZCAAA AAAAxx
+198 2005 0 2 8 18 98 198 198 198 198 196 197 QHAAAA DZCAAA HHHHxx
+417 2006 1 1 7 17 17 417 417 417 417 34 35 BQAAAA EZCAAA OOOOxx
+173 2007 1 1 3 13 73 173 173 173 173 146 147 RGAAAA FZCAAA VVVVxx
+6248 2008 0 0 8 8 48 248 248 1248 6248 96 97 IGAAAA GZCAAA AAAAxx
+302 2009 0 2 2 2 2 302 302 302 302 4 5 QLAAAA HZCAAA HHHHxx
+8983 2010 1 3 3 3 83 983 983 3983 8983 166 167 NHAAAA IZCAAA OOOOxx
+4840 2011 0 0 0 0 40 840 840 4840 4840 80 81 EEAAAA JZCAAA VVVVxx
+2876 2012 0 0 6 16 76 876 876 2876 2876 152 153 QGAAAA KZCAAA AAAAxx
+5841 2013 1 1 1 1 41 841 1841 841 5841 82 83 RQAAAA LZCAAA HHHHxx
+2766 2014 0 2 6 6 66 766 766 2766 2766 132 133 KCAAAA MZCAAA OOOOxx
+9482 2015 0 2 2 2 82 482 1482 4482 9482 164 165 SAAAAA NZCAAA VVVVxx
+5335 2016 1 3 5 15 35 335 1335 335 5335 70 71 FXAAAA OZCAAA AAAAxx
+1502 2017 0 2 2 2 2 502 1502 1502 1502 4 5 UFAAAA PZCAAA HHHHxx
+9291 2018 1 3 1 11 91 291 1291 4291 9291 182 183 JTAAAA QZCAAA OOOOxx
+8655 2019 1 3 5 15 55 655 655 3655 8655 110 111 XUAAAA RZCAAA VVVVxx
+1687 2020 1 3 7 7 87 687 1687 1687 1687 174 175 XMAAAA SZCAAA AAAAxx
+8171 2021 1 3 1 11 71 171 171 3171 8171 142 143 HCAAAA TZCAAA HHHHxx
+5699 2022 1 3 9 19 99 699 1699 699 5699 198 199 FLAAAA UZCAAA OOOOxx
+1462 2023 0 2 2 2 62 462 1462 1462 1462 124 125 GEAAAA VZCAAA VVVVxx
+608 2024 0 0 8 8 8 608 608 608 608 16 17 KXAAAA WZCAAA AAAAxx
+6860 2025 0 0 0 0 60 860 860 1860 6860 120 121 WDAAAA XZCAAA HHHHxx
+6063 2026 1 3 3 3 63 63 63 1063 6063 126 127 FZAAAA YZCAAA OOOOxx
+1422 2027 0 2 2 2 22 422 1422 1422 1422 44 45 SCAAAA ZZCAAA VVVVxx
+1932 2028 0 0 2 12 32 932 1932 1932 1932 64 65 IWAAAA AADAAA AAAAxx
+5065 2029 1 1 5 5 65 65 1065 65 5065 130 131 VMAAAA BADAAA HHHHxx
+432 2030 0 0 2 12 32 432 432 432 432 64 65 QQAAAA CADAAA OOOOxx
+4680 2031 0 0 0 0 80 680 680 4680 4680 160 161 AYAAAA DADAAA VVVVxx
+8172 2032 0 0 2 12 72 172 172 3172 8172 144 145 ICAAAA EADAAA AAAAxx
+8668 2033 0 0 8 8 68 668 668 3668 8668 136 137 KVAAAA FADAAA HHHHxx
+256 2034 0 0 6 16 56 256 256 256 256 112 113 WJAAAA GADAAA OOOOxx
+2500 2035 0 0 0 0 0 500 500 2500 2500 0 1 ESAAAA HADAAA VVVVxx
+274 2036 0 2 4 14 74 274 274 274 274 148 149 OKAAAA IADAAA AAAAxx
+5907 2037 1 3 7 7 7 907 1907 907 5907 14 15 FTAAAA JADAAA HHHHxx
+8587 2038 1 3 7 7 87 587 587 3587 8587 174 175 HSAAAA KADAAA OOOOxx
+9942 2039 0 2 2 2 42 942 1942 4942 9942 84 85 KSAAAA LADAAA VVVVxx
+116 2040 0 0 6 16 16 116 116 116 116 32 33 MEAAAA MADAAA AAAAxx
+7134 2041 0 2 4 14 34 134 1134 2134 7134 68 69 KOAAAA NADAAA HHHHxx
+9002 2042 0 2 2 2 2 2 1002 4002 9002 4 5 GIAAAA OADAAA OOOOxx
+1209 2043 1 1 9 9 9 209 1209 1209 1209 18 19 NUAAAA PADAAA VVVVxx
+9983 2044 1 3 3 3 83 983 1983 4983 9983 166 167 ZTAAAA QADAAA AAAAxx
+1761 2045 1 1 1 1 61 761 1761 1761 1761 122 123 TPAAAA RADAAA HHHHxx
+7723 2046 1 3 3 3 23 723 1723 2723 7723 46 47 BLAAAA SADAAA OOOOxx
+6518 2047 0 2 8 18 18 518 518 1518 6518 36 37 SQAAAA TADAAA VVVVxx
+1372 2048 0 0 2 12 72 372 1372 1372 1372 144 145 UAAAAA UADAAA AAAAxx
+3587 2049 1 3 7 7 87 587 1587 3587 3587 174 175 ZHAAAA VADAAA HHHHxx
+5323 2050 1 3 3 3 23 323 1323 323 5323 46 47 TWAAAA WADAAA OOOOxx
+5902 2051 0 2 2 2 2 902 1902 902 5902 4 5 ATAAAA XADAAA VVVVxx
+3749 2052 1 1 9 9 49 749 1749 3749 3749 98 99 FOAAAA YADAAA AAAAxx
+5965 2053 1 1 5 5 65 965 1965 965 5965 130 131 LVAAAA ZADAAA HHHHxx
+663 2054 1 3 3 3 63 663 663 663 663 126 127 NZAAAA ABDAAA OOOOxx
+36 2055 0 0 6 16 36 36 36 36 36 72 73 KBAAAA BBDAAA VVVVxx
+9782 2056 0 2 2 2 82 782 1782 4782 9782 164 165 GMAAAA CBDAAA AAAAxx
+5412 2057 0 0 2 12 12 412 1412 412 5412 24 25 EAAAAA DBDAAA HHHHxx
+9961 2058 1 1 1 1 61 961 1961 4961 9961 122 123 DTAAAA EBDAAA OOOOxx
+6492 2059 0 0 2 12 92 492 492 1492 6492 184 185 SPAAAA FBDAAA VVVVxx
+4234 2060 0 2 4 14 34 234 234 4234 4234 68 69 WGAAAA GBDAAA AAAAxx
+4922 2061 0 2 2 2 22 922 922 4922 4922 44 45 IHAAAA HBDAAA HHHHxx
+6166 2062 0 2 6 6 66 166 166 1166 6166 132 133 EDAAAA IBDAAA OOOOxx
+7019 2063 1 3 9 19 19 19 1019 2019 7019 38 39 ZJAAAA JBDAAA VVVVxx
+7805 2064 1 1 5 5 5 805 1805 2805 7805 10 11 FOAAAA KBDAAA AAAAxx
+9808 2065 0 0 8 8 8 808 1808 4808 9808 16 17 GNAAAA LBDAAA HHHHxx
+2550 2066 0 2 0 10 50 550 550 2550 2550 100 101 CUAAAA MBDAAA OOOOxx
+8626 2067 0 2 6 6 26 626 626 3626 8626 52 53 UTAAAA NBDAAA VVVVxx
+5649 2068 1 1 9 9 49 649 1649 649 5649 98 99 HJAAAA OBDAAA AAAAxx
+3117 2069 1 1 7 17 17 117 1117 3117 3117 34 35 XPAAAA PBDAAA HHHHxx
+866 2070 0 2 6 6 66 866 866 866 866 132 133 IHAAAA QBDAAA OOOOxx
+2323 2071 1 3 3 3 23 323 323 2323 2323 46 47 JLAAAA RBDAAA VVVVxx
+5132 2072 0 0 2 12 32 132 1132 132 5132 64 65 KPAAAA SBDAAA AAAAxx
+9222 2073 0 2 2 2 22 222 1222 4222 9222 44 45 SQAAAA TBDAAA HHHHxx
+3934 2074 0 2 4 14 34 934 1934 3934 3934 68 69 IVAAAA UBDAAA OOOOxx
+4845 2075 1 1 5 5 45 845 845 4845 4845 90 91 JEAAAA VBDAAA VVVVxx
+7714 2076 0 2 4 14 14 714 1714 2714 7714 28 29 SKAAAA WBDAAA AAAAxx
+9818 2077 0 2 8 18 18 818 1818 4818 9818 36 37 QNAAAA XBDAAA HHHHxx
+2219 2078 1 3 9 19 19 219 219 2219 2219 38 39 JHAAAA YBDAAA OOOOxx
+6573 2079 1 1 3 13 73 573 573 1573 6573 146 147 VSAAAA ZBDAAA VVVVxx
+4555 2080 1 3 5 15 55 555 555 4555 4555 110 111 FTAAAA ACDAAA AAAAxx
+7306 2081 0 2 6 6 6 306 1306 2306 7306 12 13 AVAAAA BCDAAA HHHHxx
+9313 2082 1 1 3 13 13 313 1313 4313 9313 26 27 FUAAAA CCDAAA OOOOxx
+3924 2083 0 0 4 4 24 924 1924 3924 3924 48 49 YUAAAA DCDAAA VVVVxx
+5176 2084 0 0 6 16 76 176 1176 176 5176 152 153 CRAAAA ECDAAA AAAAxx
+9767 2085 1 3 7 7 67 767 1767 4767 9767 134 135 RLAAAA FCDAAA HHHHxx
+905 2086 1 1 5 5 5 905 905 905 905 10 11 VIAAAA GCDAAA OOOOxx
+8037 2087 1 1 7 17 37 37 37 3037 8037 74 75 DXAAAA HCDAAA VVVVxx
+8133 2088 1 1 3 13 33 133 133 3133 8133 66 67 VAAAAA ICDAAA AAAAxx
+2954 2089 0 2 4 14 54 954 954 2954 2954 108 109 QJAAAA JCDAAA HHHHxx
+7262 2090 0 2 2 2 62 262 1262 2262 7262 124 125 ITAAAA KCDAAA OOOOxx
+8768 2091 0 0 8 8 68 768 768 3768 8768 136 137 GZAAAA LCDAAA VVVVxx
+6953 2092 1 1 3 13 53 953 953 1953 6953 106 107 LHAAAA MCDAAA AAAAxx
+1984 2093 0 0 4 4 84 984 1984 1984 1984 168 169 IYAAAA NCDAAA HHHHxx
+9348 2094 0 0 8 8 48 348 1348 4348 9348 96 97 OVAAAA OCDAAA OOOOxx
+7769 2095 1 1 9 9 69 769 1769 2769 7769 138 139 VMAAAA PCDAAA VVVVxx
+2994 2096 0 2 4 14 94 994 994 2994 2994 188 189 ELAAAA QCDAAA AAAAxx
+5938 2097 0 2 8 18 38 938 1938 938 5938 76 77 KUAAAA RCDAAA HHHHxx
+556 2098 0 0 6 16 56 556 556 556 556 112 113 KVAAAA SCDAAA OOOOxx
+2577 2099 1 1 7 17 77 577 577 2577 2577 154 155 DVAAAA TCDAAA VVVVxx
+8733 2100 1 1 3 13 33 733 733 3733 8733 66 67 XXAAAA UCDAAA AAAAxx
+3108 2101 0 0 8 8 8 108 1108 3108 3108 16 17 OPAAAA VCDAAA HHHHxx
+4166 2102 0 2 6 6 66 166 166 4166 4166 132 133 GEAAAA WCDAAA OOOOxx
+3170 2103 0 2 0 10 70 170 1170 3170 3170 140 141 YRAAAA XCDAAA VVVVxx
+8118 2104 0 2 8 18 18 118 118 3118 8118 36 37 GAAAAA YCDAAA AAAAxx
+8454 2105 0 2 4 14 54 454 454 3454 8454 108 109 ENAAAA ZCDAAA HHHHxx
+5338 2106 0 2 8 18 38 338 1338 338 5338 76 77 IXAAAA ADDAAA OOOOxx
+402 2107 0 2 2 2 2 402 402 402 402 4 5 MPAAAA BDDAAA VVVVxx
+5673 2108 1 1 3 13 73 673 1673 673 5673 146 147 FKAAAA CDDAAA AAAAxx
+4324 2109 0 0 4 4 24 324 324 4324 4324 48 49 IKAAAA DDDAAA HHHHxx
+1943 2110 1 3 3 3 43 943 1943 1943 1943 86 87 TWAAAA EDDAAA OOOOxx
+7703 2111 1 3 3 3 3 703 1703 2703 7703 6 7 HKAAAA FDDAAA VVVVxx
+7180 2112 0 0 0 0 80 180 1180 2180 7180 160 161 EQAAAA GDDAAA AAAAxx
+5478 2113 0 2 8 18 78 478 1478 478 5478 156 157 SCAAAA HDDAAA HHHHxx
+5775 2114 1 3 5 15 75 775 1775 775 5775 150 151 DOAAAA IDDAAA OOOOxx
+6952 2115 0 0 2 12 52 952 952 1952 6952 104 105 KHAAAA JDDAAA VVVVxx
+9022 2116 0 2 2 2 22 22 1022 4022 9022 44 45 AJAAAA KDDAAA AAAAxx
+547 2117 1 3 7 7 47 547 547 547 547 94 95 BVAAAA LDDAAA HHHHxx
+5877 2118 1 1 7 17 77 877 1877 877 5877 154 155 BSAAAA MDDAAA OOOOxx
+9580 2119 0 0 0 0 80 580 1580 4580 9580 160 161 MEAAAA NDDAAA VVVVxx
+6094 2120 0 2 4 14 94 94 94 1094 6094 188 189 KAAAAA ODDAAA AAAAxx
+3398 2121 0 2 8 18 98 398 1398 3398 3398 196 197 SAAAAA PDDAAA HHHHxx
+4574 2122 0 2 4 14 74 574 574 4574 4574 148 149 YTAAAA QDDAAA OOOOxx
+3675 2123 1 3 5 15 75 675 1675 3675 3675 150 151 JLAAAA RDDAAA VVVVxx
+6413 2124 1 1 3 13 13 413 413 1413 6413 26 27 RMAAAA SDDAAA AAAAxx
+9851 2125 1 3 1 11 51 851 1851 4851 9851 102 103 XOAAAA TDDAAA HHHHxx
+126 2126 0 2 6 6 26 126 126 126 126 52 53 WEAAAA UDDAAA OOOOxx
+6803 2127 1 3 3 3 3 803 803 1803 6803 6 7 RBAAAA VDDAAA VVVVxx
+6949 2128 1 1 9 9 49 949 949 1949 6949 98 99 HHAAAA WDDAAA AAAAxx
+115 2129 1 3 5 15 15 115 115 115 115 30 31 LEAAAA XDDAAA HHHHxx
+4165 2130 1 1 5 5 65 165 165 4165 4165 130 131 FEAAAA YDDAAA OOOOxx
+201 2131 1 1 1 1 1 201 201 201 201 2 3 THAAAA ZDDAAA VVVVxx
+9324 2132 0 0 4 4 24 324 1324 4324 9324 48 49 QUAAAA AEDAAA AAAAxx
+6562 2133 0 2 2 2 62 562 562 1562 6562 124 125 KSAAAA BEDAAA HHHHxx
+1917 2134 1 1 7 17 17 917 1917 1917 1917 34 35 TVAAAA CEDAAA OOOOxx
+558 2135 0 2 8 18 58 558 558 558 558 116 117 MVAAAA DEDAAA VVVVxx
+8515 2136 1 3 5 15 15 515 515 3515 8515 30 31 NPAAAA EEDAAA AAAAxx
+6321 2137 1 1 1 1 21 321 321 1321 6321 42 43 DJAAAA FEDAAA HHHHxx
+6892 2138 0 0 2 12 92 892 892 1892 6892 184 185 CFAAAA GEDAAA OOOOxx
+1001 2139 1 1 1 1 1 1 1001 1001 1001 2 3 NMAAAA HEDAAA VVVVxx
+2858 2140 0 2 8 18 58 858 858 2858 2858 116 117 YFAAAA IEDAAA AAAAxx
+2434 2141 0 2 4 14 34 434 434 2434 2434 68 69 QPAAAA JEDAAA HHHHxx
+4460 2142 0 0 0 0 60 460 460 4460 4460 120 121 OPAAAA KEDAAA OOOOxx
+5447 2143 1 3 7 7 47 447 1447 447 5447 94 95 NBAAAA LEDAAA VVVVxx
+3799 2144 1 3 9 19 99 799 1799 3799 3799 198 199 DQAAAA MEDAAA AAAAxx
+4310 2145 0 2 0 10 10 310 310 4310 4310 20 21 UJAAAA NEDAAA HHHHxx
+405 2146 1 1 5 5 5 405 405 405 405 10 11 PPAAAA OEDAAA OOOOxx
+4573 2147 1 1 3 13 73 573 573 4573 4573 146 147 XTAAAA PEDAAA VVVVxx
+706 2148 0 2 6 6 6 706 706 706 706 12 13 EBAAAA QEDAAA AAAAxx
+7619 2149 1 3 9 19 19 619 1619 2619 7619 38 39 BHAAAA REDAAA HHHHxx
+7959 2150 1 3 9 19 59 959 1959 2959 7959 118 119 DUAAAA SEDAAA OOOOxx
+6712 2151 0 0 2 12 12 712 712 1712 6712 24 25 EYAAAA TEDAAA VVVVxx
+6959 2152 1 3 9 19 59 959 959 1959 6959 118 119 RHAAAA UEDAAA AAAAxx
+9791 2153 1 3 1 11 91 791 1791 4791 9791 182 183 PMAAAA VEDAAA HHHHxx
+2112 2154 0 0 2 12 12 112 112 2112 2112 24 25 GDAAAA WEDAAA OOOOxx
+9114 2155 0 2 4 14 14 114 1114 4114 9114 28 29 OMAAAA XEDAAA VVVVxx
+3506 2156 0 2 6 6 6 506 1506 3506 3506 12 13 WEAAAA YEDAAA AAAAxx
+5002 2157 0 2 2 2 2 2 1002 2 5002 4 5 KKAAAA ZEDAAA HHHHxx
+3518 2158 0 2 8 18 18 518 1518 3518 3518 36 37 IFAAAA AFDAAA OOOOxx
+602 2159 0 2 2 2 2 602 602 602 602 4 5 EXAAAA BFDAAA VVVVxx
+9060 2160 0 0 0 0 60 60 1060 4060 9060 120 121 MKAAAA CFDAAA AAAAxx
+3292 2161 0 0 2 12 92 292 1292 3292 3292 184 185 QWAAAA DFDAAA HHHHxx
+77 2162 1 1 7 17 77 77 77 77 77 154 155 ZCAAAA EFDAAA OOOOxx
+1420 2163 0 0 0 0 20 420 1420 1420 1420 40 41 QCAAAA FFDAAA VVVVxx
+6001 2164 1 1 1 1 1 1 1 1001 6001 2 3 VWAAAA GFDAAA AAAAxx
+7477 2165 1 1 7 17 77 477 1477 2477 7477 154 155 PBAAAA HFDAAA HHHHxx
+6655 2166 1 3 5 15 55 655 655 1655 6655 110 111 ZVAAAA IFDAAA OOOOxx
+7845 2167 1 1 5 5 45 845 1845 2845 7845 90 91 TPAAAA JFDAAA VVVVxx
+8484 2168 0 0 4 4 84 484 484 3484 8484 168 169 IOAAAA KFDAAA AAAAxx
+4345 2169 1 1 5 5 45 345 345 4345 4345 90 91 DLAAAA LFDAAA HHHHxx
+4250 2170 0 2 0 10 50 250 250 4250 4250 100 101 MHAAAA MFDAAA OOOOxx
+2391 2171 1 3 1 11 91 391 391 2391 2391 182 183 ZNAAAA NFDAAA VVVVxx
+6884 2172 0 0 4 4 84 884 884 1884 6884 168 169 UEAAAA OFDAAA AAAAxx
+7270 2173 0 2 0 10 70 270 1270 2270 7270 140 141 QTAAAA PFDAAA HHHHxx
+2499 2174 1 3 9 19 99 499 499 2499 2499 198 199 DSAAAA QFDAAA OOOOxx
+7312 2175 0 0 2 12 12 312 1312 2312 7312 24 25 GVAAAA RFDAAA VVVVxx
+7113 2176 1 1 3 13 13 113 1113 2113 7113 26 27 PNAAAA SFDAAA AAAAxx
+6695 2177 1 3 5 15 95 695 695 1695 6695 190 191 NXAAAA TFDAAA HHHHxx
+6521 2178 1 1 1 1 21 521 521 1521 6521 42 43 VQAAAA UFDAAA OOOOxx
+272 2179 0 0 2 12 72 272 272 272 272 144 145 MKAAAA VFDAAA VVVVxx
+9976 2180 0 0 6 16 76 976 1976 4976 9976 152 153 STAAAA WFDAAA AAAAxx
+992 2181 0 0 2 12 92 992 992 992 992 184 185 EMAAAA XFDAAA HHHHxx
+6158 2182 0 2 8 18 58 158 158 1158 6158 116 117 WCAAAA YFDAAA OOOOxx
+3281 2183 1 1 1 1 81 281 1281 3281 3281 162 163 FWAAAA ZFDAAA VVVVxx
+7446 2184 0 2 6 6 46 446 1446 2446 7446 92 93 KAAAAA AGDAAA AAAAxx
+4679 2185 1 3 9 19 79 679 679 4679 4679 158 159 ZXAAAA BGDAAA HHHHxx
+5203 2186 1 3 3 3 3 203 1203 203 5203 6 7 DSAAAA CGDAAA OOOOxx
+9874 2187 0 2 4 14 74 874 1874 4874 9874 148 149 UPAAAA DGDAAA VVVVxx
+8371 2188 1 3 1 11 71 371 371 3371 8371 142 143 ZJAAAA EGDAAA AAAAxx
+9086 2189 0 2 6 6 86 86 1086 4086 9086 172 173 MLAAAA FGDAAA HHHHxx
+430 2190 0 2 0 10 30 430 430 430 430 60 61 OQAAAA GGDAAA OOOOxx
+8749 2191 1 1 9 9 49 749 749 3749 8749 98 99 NYAAAA HGDAAA VVVVxx
+577 2192 1 1 7 17 77 577 577 577 577 154 155 FWAAAA IGDAAA AAAAxx
+4884 2193 0 0 4 4 84 884 884 4884 4884 168 169 WFAAAA JGDAAA HHHHxx
+3421 2194 1 1 1 1 21 421 1421 3421 3421 42 43 PBAAAA KGDAAA OOOOxx
+2812 2195 0 0 2 12 12 812 812 2812 2812 24 25 EEAAAA LGDAAA VVVVxx
+5958 2196 0 2 8 18 58 958 1958 958 5958 116 117 EVAAAA MGDAAA AAAAxx
+9901 2197 1 1 1 1 1 901 1901 4901 9901 2 3 VQAAAA NGDAAA HHHHxx
+8478 2198 0 2 8 18 78 478 478 3478 8478 156 157 COAAAA OGDAAA OOOOxx
+6545 2199 1 1 5 5 45 545 545 1545 6545 90 91 TRAAAA PGDAAA VVVVxx
+1479 2200 1 3 9 19 79 479 1479 1479 1479 158 159 XEAAAA QGDAAA AAAAxx
+1046 2201 0 2 6 6 46 46 1046 1046 1046 92 93 GOAAAA RGDAAA HHHHxx
+6372 2202 0 0 2 12 72 372 372 1372 6372 144 145 CLAAAA SGDAAA OOOOxx
+8206 2203 0 2 6 6 6 206 206 3206 8206 12 13 QDAAAA TGDAAA VVVVxx
+9544 2204 0 0 4 4 44 544 1544 4544 9544 88 89 CDAAAA UGDAAA AAAAxx
+9287 2205 1 3 7 7 87 287 1287 4287 9287 174 175 FTAAAA VGDAAA HHHHxx
+6786 2206 0 2 6 6 86 786 786 1786 6786 172 173 ABAAAA WGDAAA OOOOxx
+6511 2207 1 3 1 11 11 511 511 1511 6511 22 23 LQAAAA XGDAAA VVVVxx
+603 2208 1 3 3 3 3 603 603 603 603 6 7 FXAAAA YGDAAA AAAAxx
+2022 2209 0 2 2 2 22 22 22 2022 2022 44 45 UZAAAA ZGDAAA HHHHxx
+2086 2210 0 2 6 6 86 86 86 2086 2086 172 173 GCAAAA AHDAAA OOOOxx
+1969 2211 1 1 9 9 69 969 1969 1969 1969 138 139 TXAAAA BHDAAA VVVVxx
+4841 2212 1 1 1 1 41 841 841 4841 4841 82 83 FEAAAA CHDAAA AAAAxx
+5845 2213 1 1 5 5 45 845 1845 845 5845 90 91 VQAAAA DHDAAA HHHHxx
+4635 2214 1 3 5 15 35 635 635 4635 4635 70 71 HWAAAA EHDAAA OOOOxx
+4658 2215 0 2 8 18 58 658 658 4658 4658 116 117 EXAAAA FHDAAA VVVVxx
+2896 2216 0 0 6 16 96 896 896 2896 2896 192 193 KHAAAA GHDAAA AAAAxx
+5179 2217 1 3 9 19 79 179 1179 179 5179 158 159 FRAAAA HHDAAA HHHHxx
+8667 2218 1 3 7 7 67 667 667 3667 8667 134 135 JVAAAA IHDAAA OOOOxx
+7294 2219 0 2 4 14 94 294 1294 2294 7294 188 189 OUAAAA JHDAAA VVVVxx
+3706 2220 0 2 6 6 6 706 1706 3706 3706 12 13 OMAAAA KHDAAA AAAAxx
+8389 2221 1 1 9 9 89 389 389 3389 8389 178 179 RKAAAA LHDAAA HHHHxx
+2486 2222 0 2 6 6 86 486 486 2486 2486 172 173 QRAAAA MHDAAA OOOOxx
+8743 2223 1 3 3 3 43 743 743 3743 8743 86 87 HYAAAA NHDAAA VVVVxx
+2777 2224 1 1 7 17 77 777 777 2777 2777 154 155 VCAAAA OHDAAA AAAAxx
+2113 2225 1 1 3 13 13 113 113 2113 2113 26 27 HDAAAA PHDAAA HHHHxx
+2076 2226 0 0 6 16 76 76 76 2076 2076 152 153 WBAAAA QHDAAA OOOOxx
+2300 2227 0 0 0 0 0 300 300 2300 2300 0 1 MKAAAA RHDAAA VVVVxx
+6894 2228 0 2 4 14 94 894 894 1894 6894 188 189 EFAAAA SHDAAA AAAAxx
+6939 2229 1 3 9 19 39 939 939 1939 6939 78 79 XGAAAA THDAAA HHHHxx
+446 2230 0 2 6 6 46 446 446 446 446 92 93 ERAAAA UHDAAA OOOOxx
+6218 2231 0 2 8 18 18 218 218 1218 6218 36 37 EFAAAA VHDAAA VVVVxx
+1295 2232 1 3 5 15 95 295 1295 1295 1295 190 191 VXAAAA WHDAAA AAAAxx
+5135 2233 1 3 5 15 35 135 1135 135 5135 70 71 NPAAAA XHDAAA HHHHxx
+8122 2234 0 2 2 2 22 122 122 3122 8122 44 45 KAAAAA YHDAAA OOOOxx
+316 2235 0 0 6 16 16 316 316 316 316 32 33 EMAAAA ZHDAAA VVVVxx
+514 2236 0 2 4 14 14 514 514 514 514 28 29 UTAAAA AIDAAA AAAAxx
+7970 2237 0 2 0 10 70 970 1970 2970 7970 140 141 OUAAAA BIDAAA HHHHxx
+9350 2238 0 2 0 10 50 350 1350 4350 9350 100 101 QVAAAA CIDAAA OOOOxx
+3700 2239 0 0 0 0 0 700 1700 3700 3700 0 1 IMAAAA DIDAAA VVVVxx
+582 2240 0 2 2 2 82 582 582 582 582 164 165 KWAAAA EIDAAA AAAAxx
+9722 2241 0 2 2 2 22 722 1722 4722 9722 44 45 YJAAAA FIDAAA HHHHxx
+7398 2242 0 2 8 18 98 398 1398 2398 7398 196 197 OYAAAA GIDAAA OOOOxx
+2265 2243 1 1 5 5 65 265 265 2265 2265 130 131 DJAAAA HIDAAA VVVVxx
+3049 2244 1 1 9 9 49 49 1049 3049 3049 98 99 HNAAAA IIDAAA AAAAxx
+9121 2245 1 1 1 1 21 121 1121 4121 9121 42 43 VMAAAA JIDAAA HHHHxx
+4275 2246 1 3 5 15 75 275 275 4275 4275 150 151 LIAAAA KIDAAA OOOOxx
+6567 2247 1 3 7 7 67 567 567 1567 6567 134 135 PSAAAA LIDAAA VVVVxx
+6755 2248 1 3 5 15 55 755 755 1755 6755 110 111 VZAAAA MIDAAA AAAAxx
+4535 2249 1 3 5 15 35 535 535 4535 4535 70 71 LSAAAA NIDAAA HHHHxx
+7968 2250 0 0 8 8 68 968 1968 2968 7968 136 137 MUAAAA OIDAAA OOOOxx
+3412 2251 0 0 2 12 12 412 1412 3412 3412 24 25 GBAAAA PIDAAA VVVVxx
+6112 2252 0 0 2 12 12 112 112 1112 6112 24 25 CBAAAA QIDAAA AAAAxx
+6805 2253 1 1 5 5 5 805 805 1805 6805 10 11 TBAAAA RIDAAA HHHHxx
+2880 2254 0 0 0 0 80 880 880 2880 2880 160 161 UGAAAA SIDAAA OOOOxx
+7710 2255 0 2 0 10 10 710 1710 2710 7710 20 21 OKAAAA TIDAAA VVVVxx
+7949 2256 1 1 9 9 49 949 1949 2949 7949 98 99 TTAAAA UIDAAA AAAAxx
+7043 2257 1 3 3 3 43 43 1043 2043 7043 86 87 XKAAAA VIDAAA HHHHxx
+9012 2258 0 0 2 12 12 12 1012 4012 9012 24 25 QIAAAA WIDAAA OOOOxx
+878 2259 0 2 8 18 78 878 878 878 878 156 157 UHAAAA XIDAAA VVVVxx
+7930 2260 0 2 0 10 30 930 1930 2930 7930 60 61 ATAAAA YIDAAA AAAAxx
+667 2261 1 3 7 7 67 667 667 667 667 134 135 RZAAAA ZIDAAA HHHHxx
+1905 2262 1 1 5 5 5 905 1905 1905 1905 10 11 HVAAAA AJDAAA OOOOxx
+4958 2263 0 2 8 18 58 958 958 4958 4958 116 117 SIAAAA BJDAAA VVVVxx
+2973 2264 1 1 3 13 73 973 973 2973 2973 146 147 JKAAAA CJDAAA AAAAxx
+3631 2265 1 3 1 11 31 631 1631 3631 3631 62 63 RJAAAA DJDAAA HHHHxx
+5868 2266 0 0 8 8 68 868 1868 868 5868 136 137 SRAAAA EJDAAA OOOOxx
+2873 2267 1 1 3 13 73 873 873 2873 2873 146 147 NGAAAA FJDAAA VVVVxx
+6941 2268 1 1 1 1 41 941 941 1941 6941 82 83 ZGAAAA GJDAAA AAAAxx
+6384 2269 0 0 4 4 84 384 384 1384 6384 168 169 OLAAAA HJDAAA HHHHxx
+3806 2270 0 2 6 6 6 806 1806 3806 3806 12 13 KQAAAA IJDAAA OOOOxx
+5079 2271 1 3 9 19 79 79 1079 79 5079 158 159 JNAAAA JJDAAA VVVVxx
+1970 2272 0 2 0 10 70 970 1970 1970 1970 140 141 UXAAAA KJDAAA AAAAxx
+7810 2273 0 2 0 10 10 810 1810 2810 7810 20 21 KOAAAA LJDAAA HHHHxx
+4639 2274 1 3 9 19 39 639 639 4639 4639 78 79 LWAAAA MJDAAA OOOOxx
+6527 2275 1 3 7 7 27 527 527 1527 6527 54 55 BRAAAA NJDAAA VVVVxx
+8079 2276 1 3 9 19 79 79 79 3079 8079 158 159 TYAAAA OJDAAA AAAAxx
+2740 2277 0 0 0 0 40 740 740 2740 2740 80 81 KBAAAA PJDAAA HHHHxx
+2337 2278 1 1 7 17 37 337 337 2337 2337 74 75 XLAAAA QJDAAA OOOOxx
+6670 2279 0 2 0 10 70 670 670 1670 6670 140 141 OWAAAA RJDAAA VVVVxx
+2345 2280 1 1 5 5 45 345 345 2345 2345 90 91 FMAAAA SJDAAA AAAAxx
+401 2281 1 1 1 1 1 401 401 401 401 2 3 LPAAAA TJDAAA HHHHxx
+2704 2282 0 0 4 4 4 704 704 2704 2704 8 9 AAAAAA UJDAAA OOOOxx
+5530 2283 0 2 0 10 30 530 1530 530 5530 60 61 SEAAAA VJDAAA VVVVxx
+51 2284 1 3 1 11 51 51 51 51 51 102 103 ZBAAAA WJDAAA AAAAxx
+4282 2285 0 2 2 2 82 282 282 4282 4282 164 165 SIAAAA XJDAAA HHHHxx
+7336 2286 0 0 6 16 36 336 1336 2336 7336 72 73 EWAAAA YJDAAA OOOOxx
+8320 2287 0 0 0 0 20 320 320 3320 8320 40 41 AIAAAA ZJDAAA VVVVxx
+7772 2288 0 0 2 12 72 772 1772 2772 7772 144 145 YMAAAA AKDAAA AAAAxx
+1894 2289 0 2 4 14 94 894 1894 1894 1894 188 189 WUAAAA BKDAAA HHHHxx
+2320 2290 0 0 0 0 20 320 320 2320 2320 40 41 GLAAAA CKDAAA OOOOxx
+6232 2291 0 0 2 12 32 232 232 1232 6232 64 65 SFAAAA DKDAAA VVVVxx
+2833 2292 1 1 3 13 33 833 833 2833 2833 66 67 ZEAAAA EKDAAA AAAAxx
+8265 2293 1 1 5 5 65 265 265 3265 8265 130 131 XFAAAA FKDAAA HHHHxx
+4589 2294 1 1 9 9 89 589 589 4589 4589 178 179 NUAAAA GKDAAA OOOOxx
+8182 2295 0 2 2 2 82 182 182 3182 8182 164 165 SCAAAA HKDAAA VVVVxx
+8337 2296 1 1 7 17 37 337 337 3337 8337 74 75 RIAAAA IKDAAA AAAAxx
+8210 2297 0 2 0 10 10 210 210 3210 8210 20 21 UDAAAA JKDAAA HHHHxx
+1406 2298 0 2 6 6 6 406 1406 1406 1406 12 13 CCAAAA KKDAAA OOOOxx
+4463 2299 1 3 3 3 63 463 463 4463 4463 126 127 RPAAAA LKDAAA VVVVxx
+4347 2300 1 3 7 7 47 347 347 4347 4347 94 95 FLAAAA MKDAAA AAAAxx
+181 2301 1 1 1 1 81 181 181 181 181 162 163 ZGAAAA NKDAAA HHHHxx
+9986 2302 0 2 6 6 86 986 1986 4986 9986 172 173 CUAAAA OKDAAA OOOOxx
+661 2303 1 1 1 1 61 661 661 661 661 122 123 LZAAAA PKDAAA VVVVxx
+4105 2304 1 1 5 5 5 105 105 4105 4105 10 11 XBAAAA QKDAAA AAAAxx
+2187 2305 1 3 7 7 87 187 187 2187 2187 174 175 DGAAAA RKDAAA HHHHxx
+1628 2306 0 0 8 8 28 628 1628 1628 1628 56 57 QKAAAA SKDAAA OOOOxx
+3119 2307 1 3 9 19 19 119 1119 3119 3119 38 39 ZPAAAA TKDAAA VVVVxx
+6804 2308 0 0 4 4 4 804 804 1804 6804 8 9 SBAAAA UKDAAA AAAAxx
+9918 2309 0 2 8 18 18 918 1918 4918 9918 36 37 MRAAAA VKDAAA HHHHxx
+8916 2310 0 0 6 16 16 916 916 3916 8916 32 33 YEAAAA WKDAAA OOOOxx
+6057 2311 1 1 7 17 57 57 57 1057 6057 114 115 ZYAAAA XKDAAA VVVVxx
+3622 2312 0 2 2 2 22 622 1622 3622 3622 44 45 IJAAAA YKDAAA AAAAxx
+9168 2313 0 0 8 8 68 168 1168 4168 9168 136 137 QOAAAA ZKDAAA HHHHxx
+3720 2314 0 0 0 0 20 720 1720 3720 3720 40 41 CNAAAA ALDAAA OOOOxx
+9927 2315 1 3 7 7 27 927 1927 4927 9927 54 55 VRAAAA BLDAAA VVVVxx
+5616 2316 0 0 6 16 16 616 1616 616 5616 32 33 AIAAAA CLDAAA AAAAxx
+5210 2317 0 2 0 10 10 210 1210 210 5210 20 21 KSAAAA DLDAAA HHHHxx
+636 2318 0 0 6 16 36 636 636 636 636 72 73 MYAAAA ELDAAA OOOOxx
+9936 2319 0 0 6 16 36 936 1936 4936 9936 72 73 ESAAAA FLDAAA VVVVxx
+2316 2320 0 0 6 16 16 316 316 2316 2316 32 33 CLAAAA GLDAAA AAAAxx
+4363 2321 1 3 3 3 63 363 363 4363 4363 126 127 VLAAAA HLDAAA HHHHxx
+7657 2322 1 1 7 17 57 657 1657 2657 7657 114 115 NIAAAA ILDAAA OOOOxx
+697 2323 1 1 7 17 97 697 697 697 697 194 195 VAAAAA JLDAAA VVVVxx
+912 2324 0 0 2 12 12 912 912 912 912 24 25 CJAAAA KLDAAA AAAAxx
+8806 2325 0 2 6 6 6 806 806 3806 8806 12 13 SAAAAA LLDAAA HHHHxx
+9698 2326 0 2 8 18 98 698 1698 4698 9698 196 197 AJAAAA MLDAAA OOOOxx
+6191 2327 1 3 1 11 91 191 191 1191 6191 182 183 DEAAAA NLDAAA VVVVxx
+1188 2328 0 0 8 8 88 188 1188 1188 1188 176 177 STAAAA OLDAAA AAAAxx
+7676 2329 0 0 6 16 76 676 1676 2676 7676 152 153 GJAAAA PLDAAA HHHHxx
+7073 2330 1 1 3 13 73 73 1073 2073 7073 146 147 BMAAAA QLDAAA OOOOxx
+8019 2331 1 3 9 19 19 19 19 3019 8019 38 39 LWAAAA RLDAAA VVVVxx
+4726 2332 0 2 6 6 26 726 726 4726 4726 52 53 UZAAAA SLDAAA AAAAxx
+4648 2333 0 0 8 8 48 648 648 4648 4648 96 97 UWAAAA TLDAAA HHHHxx
+3227 2334 1 3 7 7 27 227 1227 3227 3227 54 55 DUAAAA ULDAAA OOOOxx
+7232 2335 0 0 2 12 32 232 1232 2232 7232 64 65 ESAAAA VLDAAA VVVVxx
+9761 2336 1 1 1 1 61 761 1761 4761 9761 122 123 LLAAAA WLDAAA AAAAxx
+3105 2337 1 1 5 5 5 105 1105 3105 3105 10 11 LPAAAA XLDAAA HHHHxx
+5266 2338 0 2 6 6 66 266 1266 266 5266 132 133 OUAAAA YLDAAA OOOOxx
+6788 2339 0 0 8 8 88 788 788 1788 6788 176 177 CBAAAA ZLDAAA VVVVxx
+2442 2340 0 2 2 2 42 442 442 2442 2442 84 85 YPAAAA AMDAAA AAAAxx
+8198 2341 0 2 8 18 98 198 198 3198 8198 196 197 IDAAAA BMDAAA HHHHxx
+5806 2342 0 2 6 6 6 806 1806 806 5806 12 13 IPAAAA CMDAAA OOOOxx
+8928 2343 0 0 8 8 28 928 928 3928 8928 56 57 KFAAAA DMDAAA VVVVxx
+1657 2344 1 1 7 17 57 657 1657 1657 1657 114 115 TLAAAA EMDAAA AAAAxx
+9164 2345 0 0 4 4 64 164 1164 4164 9164 128 129 MOAAAA FMDAAA HHHHxx
+1851 2346 1 3 1 11 51 851 1851 1851 1851 102 103 FTAAAA GMDAAA OOOOxx
+4744 2347 0 0 4 4 44 744 744 4744 4744 88 89 MAAAAA HMDAAA VVVVxx
+8055 2348 1 3 5 15 55 55 55 3055 8055 110 111 VXAAAA IMDAAA AAAAxx
+1533 2349 1 1 3 13 33 533 1533 1533 1533 66 67 ZGAAAA JMDAAA HHHHxx
+1260 2350 0 0 0 0 60 260 1260 1260 1260 120 121 MWAAAA KMDAAA OOOOxx
+1290 2351 0 2 0 10 90 290 1290 1290 1290 180 181 QXAAAA LMDAAA VVVVxx
+297 2352 1 1 7 17 97 297 297 297 297 194 195 LLAAAA MMDAAA AAAAxx
+4145 2353 1 1 5 5 45 145 145 4145 4145 90 91 LDAAAA NMDAAA HHHHxx
+863 2354 1 3 3 3 63 863 863 863 863 126 127 FHAAAA OMDAAA OOOOxx
+3423 2355 1 3 3 3 23 423 1423 3423 3423 46 47 RBAAAA PMDAAA VVVVxx
+8750 2356 0 2 0 10 50 750 750 3750 8750 100 101 OYAAAA QMDAAA AAAAxx
+3546 2357 0 2 6 6 46 546 1546 3546 3546 92 93 KGAAAA RMDAAA HHHHxx
+3678 2358 0 2 8 18 78 678 1678 3678 3678 156 157 MLAAAA SMDAAA OOOOxx
+5313 2359 1 1 3 13 13 313 1313 313 5313 26 27 JWAAAA TMDAAA VVVVxx
+6233 2360 1 1 3 13 33 233 233 1233 6233 66 67 TFAAAA UMDAAA AAAAxx
+5802 2361 0 2 2 2 2 802 1802 802 5802 4 5 EPAAAA VMDAAA HHHHxx
+7059 2362 1 3 9 19 59 59 1059 2059 7059 118 119 NLAAAA WMDAAA OOOOxx
+6481 2363 1 1 1 1 81 481 481 1481 6481 162 163 HPAAAA XMDAAA VVVVxx
+1596 2364 0 0 6 16 96 596 1596 1596 1596 192 193 KJAAAA YMDAAA AAAAxx
+8181 2365 1 1 1 1 81 181 181 3181 8181 162 163 RCAAAA ZMDAAA HHHHxx
+5368 2366 0 0 8 8 68 368 1368 368 5368 136 137 MYAAAA ANDAAA OOOOxx
+9416 2367 0 0 6 16 16 416 1416 4416 9416 32 33 EYAAAA BNDAAA VVVVxx
+9521 2368 1 1 1 1 21 521 1521 4521 9521 42 43 FCAAAA CNDAAA AAAAxx
+1042 2369 0 2 2 2 42 42 1042 1042 1042 84 85 COAAAA DNDAAA HHHHxx
+4503 2370 1 3 3 3 3 503 503 4503 4503 6 7 FRAAAA ENDAAA OOOOxx
+3023 2371 1 3 3 3 23 23 1023 3023 3023 46 47 HMAAAA FNDAAA VVVVxx
+1976 2372 0 0 6 16 76 976 1976 1976 1976 152 153 AYAAAA GNDAAA AAAAxx
+5610 2373 0 2 0 10 10 610 1610 610 5610 20 21 UHAAAA HNDAAA HHHHxx
+7410 2374 0 2 0 10 10 410 1410 2410 7410 20 21 AZAAAA INDAAA OOOOxx
+7872 2375 0 0 2 12 72 872 1872 2872 7872 144 145 UQAAAA JNDAAA VVVVxx
+8591 2376 1 3 1 11 91 591 591 3591 8591 182 183 LSAAAA KNDAAA AAAAxx
+1804 2377 0 0 4 4 4 804 1804 1804 1804 8 9 KRAAAA LNDAAA HHHHxx
+5299 2378 1 3 9 19 99 299 1299 299 5299 198 199 VVAAAA MNDAAA OOOOxx
+4695 2379 1 3 5 15 95 695 695 4695 4695 190 191 PYAAAA NNDAAA VVVVxx
+2672 2380 0 0 2 12 72 672 672 2672 2672 144 145 UYAAAA ONDAAA AAAAxx
+585 2381 1 1 5 5 85 585 585 585 585 170 171 NWAAAA PNDAAA HHHHxx
+8622 2382 0 2 2 2 22 622 622 3622 8622 44 45 QTAAAA QNDAAA OOOOxx
+3780 2383 0 0 0 0 80 780 1780 3780 3780 160 161 KPAAAA RNDAAA VVVVxx
+7941 2384 1 1 1 1 41 941 1941 2941 7941 82 83 LTAAAA SNDAAA AAAAxx
+3305 2385 1 1 5 5 5 305 1305 3305 3305 10 11 DXAAAA TNDAAA HHHHxx
+8653 2386 1 1 3 13 53 653 653 3653 8653 106 107 VUAAAA UNDAAA OOOOxx
+5756 2387 0 0 6 16 56 756 1756 756 5756 112 113 KNAAAA VNDAAA VVVVxx
+576 2388 0 0 6 16 76 576 576 576 576 152 153 EWAAAA WNDAAA AAAAxx
+1915 2389 1 3 5 15 15 915 1915 1915 1915 30 31 RVAAAA XNDAAA HHHHxx
+4627 2390 1 3 7 7 27 627 627 4627 4627 54 55 ZVAAAA YNDAAA OOOOxx
+920 2391 0 0 0 0 20 920 920 920 920 40 41 KJAAAA ZNDAAA VVVVxx
+2537 2392 1 1 7 17 37 537 537 2537 2537 74 75 PTAAAA AODAAA AAAAxx
+50 2393 0 2 0 10 50 50 50 50 50 100 101 YBAAAA BODAAA HHHHxx
+1313 2394 1 1 3 13 13 313 1313 1313 1313 26 27 NYAAAA CODAAA OOOOxx
+8542 2395 0 2 2 2 42 542 542 3542 8542 84 85 OQAAAA DODAAA VVVVxx
+6428 2396 0 0 8 8 28 428 428 1428 6428 56 57 GNAAAA EODAAA AAAAxx
+4351 2397 1 3 1 11 51 351 351 4351 4351 102 103 JLAAAA FODAAA HHHHxx
+2050 2398 0 2 0 10 50 50 50 2050 2050 100 101 WAAAAA GODAAA OOOOxx
+5162 2399 0 2 2 2 62 162 1162 162 5162 124 125 OQAAAA HODAAA VVVVxx
+8229 2400 1 1 9 9 29 229 229 3229 8229 58 59 NEAAAA IODAAA AAAAxx
+7782 2401 0 2 2 2 82 782 1782 2782 7782 164 165 INAAAA JODAAA HHHHxx
+1563 2402 1 3 3 3 63 563 1563 1563 1563 126 127 DIAAAA KODAAA OOOOxx
+267 2403 1 3 7 7 67 267 267 267 267 134 135 HKAAAA LODAAA VVVVxx
+5138 2404 0 2 8 18 38 138 1138 138 5138 76 77 QPAAAA MODAAA AAAAxx
+7022 2405 0 2 2 2 22 22 1022 2022 7022 44 45 CKAAAA NODAAA HHHHxx
+6705 2406 1 1 5 5 5 705 705 1705 6705 10 11 XXAAAA OODAAA OOOOxx
+6190 2407 0 2 0 10 90 190 190 1190 6190 180 181 CEAAAA PODAAA VVVVxx
+8226 2408 0 2 6 6 26 226 226 3226 8226 52 53 KEAAAA QODAAA AAAAxx
+8882 2409 0 2 2 2 82 882 882 3882 8882 164 165 QDAAAA RODAAA HHHHxx
+5181 2410 1 1 1 1 81 181 1181 181 5181 162 163 HRAAAA SODAAA OOOOxx
+4598 2411 0 2 8 18 98 598 598 4598 4598 196 197 WUAAAA TODAAA VVVVxx
+4882 2412 0 2 2 2 82 882 882 4882 4882 164 165 UFAAAA UODAAA AAAAxx
+7490 2413 0 2 0 10 90 490 1490 2490 7490 180 181 CCAAAA VODAAA HHHHxx
+5224 2414 0 0 4 4 24 224 1224 224 5224 48 49 YSAAAA WODAAA OOOOxx
+2174 2415 0 2 4 14 74 174 174 2174 2174 148 149 QFAAAA XODAAA VVVVxx
+3059 2416 1 3 9 19 59 59 1059 3059 3059 118 119 RNAAAA YODAAA AAAAxx
+8790 2417 0 2 0 10 90 790 790 3790 8790 180 181 CAAAAA ZODAAA HHHHxx
+2222 2418 0 2 2 2 22 222 222 2222 2222 44 45 MHAAAA APDAAA OOOOxx
+5473 2419 1 1 3 13 73 473 1473 473 5473 146 147 NCAAAA BPDAAA VVVVxx
+937 2420 1 1 7 17 37 937 937 937 937 74 75 BKAAAA CPDAAA AAAAxx
+2975 2421 1 3 5 15 75 975 975 2975 2975 150 151 LKAAAA DPDAAA HHHHxx
+9569 2422 1 1 9 9 69 569 1569 4569 9569 138 139 BEAAAA EPDAAA OOOOxx
+3456 2423 0 0 6 16 56 456 1456 3456 3456 112 113 YCAAAA FPDAAA VVVVxx
+6657 2424 1 1 7 17 57 657 657 1657 6657 114 115 BWAAAA GPDAAA AAAAxx
+3776 2425 0 0 6 16 76 776 1776 3776 3776 152 153 GPAAAA HPDAAA HHHHxx
+6072 2426 0 0 2 12 72 72 72 1072 6072 144 145 OZAAAA IPDAAA OOOOxx
+8129 2427 1 1 9 9 29 129 129 3129 8129 58 59 RAAAAA JPDAAA VVVVxx
+1085 2428 1 1 5 5 85 85 1085 1085 1085 170 171 TPAAAA KPDAAA AAAAxx
+2079 2429 1 3 9 19 79 79 79 2079 2079 158 159 ZBAAAA LPDAAA HHHHxx
+1200 2430 0 0 0 0 0 200 1200 1200 1200 0 1 EUAAAA MPDAAA OOOOxx
+3276 2431 0 0 6 16 76 276 1276 3276 3276 152 153 AWAAAA NPDAAA VVVVxx
+2608 2432 0 0 8 8 8 608 608 2608 2608 16 17 IWAAAA OPDAAA AAAAxx
+702 2433 0 2 2 2 2 702 702 702 702 4 5 ABAAAA PPDAAA HHHHxx
+5750 2434 0 2 0 10 50 750 1750 750 5750 100 101 ENAAAA QPDAAA OOOOxx
+2776 2435 0 0 6 16 76 776 776 2776 2776 152 153 UCAAAA RPDAAA VVVVxx
+9151 2436 1 3 1 11 51 151 1151 4151 9151 102 103 ZNAAAA SPDAAA AAAAxx
+3282 2437 0 2 2 2 82 282 1282 3282 3282 164 165 GWAAAA TPDAAA HHHHxx
+408 2438 0 0 8 8 8 408 408 408 408 16 17 SPAAAA UPDAAA OOOOxx
+3473 2439 1 1 3 13 73 473 1473 3473 3473 146 147 PDAAAA VPDAAA VVVVxx
+7095 2440 1 3 5 15 95 95 1095 2095 7095 190 191 XMAAAA WPDAAA AAAAxx
+3288 2441 0 0 8 8 88 288 1288 3288 3288 176 177 MWAAAA XPDAAA HHHHxx
+8215 2442 1 3 5 15 15 215 215 3215 8215 30 31 ZDAAAA YPDAAA OOOOxx
+6244 2443 0 0 4 4 44 244 244 1244 6244 88 89 EGAAAA ZPDAAA VVVVxx
+8440 2444 0 0 0 0 40 440 440 3440 8440 80 81 QMAAAA AQDAAA AAAAxx
+3800 2445 0 0 0 0 0 800 1800 3800 3800 0 1 EQAAAA BQDAAA HHHHxx
+7279 2446 1 3 9 19 79 279 1279 2279 7279 158 159 ZTAAAA CQDAAA OOOOxx
+9206 2447 0 2 6 6 6 206 1206 4206 9206 12 13 CQAAAA DQDAAA VVVVxx
+6465 2448 1 1 5 5 65 465 465 1465 6465 130 131 ROAAAA EQDAAA AAAAxx
+4127 2449 1 3 7 7 27 127 127 4127 4127 54 55 TCAAAA FQDAAA HHHHxx
+7463 2450 1 3 3 3 63 463 1463 2463 7463 126 127 BBAAAA GQDAAA OOOOxx
+5117 2451 1 1 7 17 17 117 1117 117 5117 34 35 VOAAAA HQDAAA VVVVxx
+4715 2452 1 3 5 15 15 715 715 4715 4715 30 31 JZAAAA IQDAAA AAAAxx
+2010 2453 0 2 0 10 10 10 10 2010 2010 20 21 IZAAAA JQDAAA HHHHxx
+6486 2454 0 2 6 6 86 486 486 1486 6486 172 173 MPAAAA KQDAAA OOOOxx
+6434 2455 0 2 4 14 34 434 434 1434 6434 68 69 MNAAAA LQDAAA VVVVxx
+2151 2456 1 3 1 11 51 151 151 2151 2151 102 103 TEAAAA MQDAAA AAAAxx
+4821 2457 1 1 1 1 21 821 821 4821 4821 42 43 LDAAAA NQDAAA HHHHxx
+6507 2458 1 3 7 7 7 507 507 1507 6507 14 15 HQAAAA OQDAAA OOOOxx
+8741 2459 1 1 1 1 41 741 741 3741 8741 82 83 FYAAAA PQDAAA VVVVxx
+6846 2460 0 2 6 6 46 846 846 1846 6846 92 93 IDAAAA QQDAAA AAAAxx
+4525 2461 1 1 5 5 25 525 525 4525 4525 50 51 BSAAAA RQDAAA HHHHxx
+8299 2462 1 3 9 19 99 299 299 3299 8299 198 199 FHAAAA SQDAAA OOOOxx
+5465 2463 1 1 5 5 65 465 1465 465 5465 130 131 FCAAAA TQDAAA VVVVxx
+7206 2464 0 2 6 6 6 206 1206 2206 7206 12 13 ERAAAA UQDAAA AAAAxx
+2616 2465 0 0 6 16 16 616 616 2616 2616 32 33 QWAAAA VQDAAA HHHHxx
+4440 2466 0 0 0 0 40 440 440 4440 4440 80 81 UOAAAA WQDAAA OOOOxx
+6109 2467 1 1 9 9 9 109 109 1109 6109 18 19 ZAAAAA XQDAAA VVVVxx
+7905 2468 1 1 5 5 5 905 1905 2905 7905 10 11 BSAAAA YQDAAA AAAAxx
+6498 2469 0 2 8 18 98 498 498 1498 6498 196 197 YPAAAA ZQDAAA HHHHxx
+2034 2470 0 2 4 14 34 34 34 2034 2034 68 69 GAAAAA ARDAAA OOOOxx
+7693 2471 1 1 3 13 93 693 1693 2693 7693 186 187 XJAAAA BRDAAA VVVVxx
+7511 2472 1 3 1 11 11 511 1511 2511 7511 22 23 XCAAAA CRDAAA AAAAxx
+7531 2473 1 3 1 11 31 531 1531 2531 7531 62 63 RDAAAA DRDAAA HHHHxx
+6869 2474 1 1 9 9 69 869 869 1869 6869 138 139 FEAAAA ERDAAA OOOOxx
+2763 2475 1 3 3 3 63 763 763 2763 2763 126 127 HCAAAA FRDAAA VVVVxx
+575 2476 1 3 5 15 75 575 575 575 575 150 151 DWAAAA GRDAAA AAAAxx
+8953 2477 1 1 3 13 53 953 953 3953 8953 106 107 JGAAAA HRDAAA HHHHxx
+5833 2478 1 1 3 13 33 833 1833 833 5833 66 67 JQAAAA IRDAAA OOOOxx
+9035 2479 1 3 5 15 35 35 1035 4035 9035 70 71 NJAAAA JRDAAA VVVVxx
+9123 2480 1 3 3 3 23 123 1123 4123 9123 46 47 XMAAAA KRDAAA AAAAxx
+206 2481 0 2 6 6 6 206 206 206 206 12 13 YHAAAA LRDAAA HHHHxx
+4155 2482 1 3 5 15 55 155 155 4155 4155 110 111 VDAAAA MRDAAA OOOOxx
+532 2483 0 0 2 12 32 532 532 532 532 64 65 MUAAAA NRDAAA VVVVxx
+1370 2484 0 2 0 10 70 370 1370 1370 1370 140 141 SAAAAA ORDAAA AAAAxx
+7656 2485 0 0 6 16 56 656 1656 2656 7656 112 113 MIAAAA PRDAAA HHHHxx
+7735 2486 1 3 5 15 35 735 1735 2735 7735 70 71 NLAAAA QRDAAA OOOOxx
+2118 2487 0 2 8 18 18 118 118 2118 2118 36 37 MDAAAA RRDAAA VVVVxx
+6914 2488 0 2 4 14 14 914 914 1914 6914 28 29 YFAAAA SRDAAA AAAAxx
+6277 2489 1 1 7 17 77 277 277 1277 6277 154 155 LHAAAA TRDAAA HHHHxx
+6347 2490 1 3 7 7 47 347 347 1347 6347 94 95 DKAAAA URDAAA OOOOxx
+4030 2491 0 2 0 10 30 30 30 4030 4030 60 61 AZAAAA VRDAAA VVVVxx
+9673 2492 1 1 3 13 73 673 1673 4673 9673 146 147 BIAAAA WRDAAA AAAAxx
+2015 2493 1 3 5 15 15 15 15 2015 2015 30 31 NZAAAA XRDAAA HHHHxx
+1317 2494 1 1 7 17 17 317 1317 1317 1317 34 35 RYAAAA YRDAAA OOOOxx
+404 2495 0 0 4 4 4 404 404 404 404 8 9 OPAAAA ZRDAAA VVVVxx
+1604 2496 0 0 4 4 4 604 1604 1604 1604 8 9 SJAAAA ASDAAA AAAAxx
+1912 2497 0 0 2 12 12 912 1912 1912 1912 24 25 OVAAAA BSDAAA HHHHxx
+5727 2498 1 3 7 7 27 727 1727 727 5727 54 55 HMAAAA CSDAAA OOOOxx
+4538 2499 0 2 8 18 38 538 538 4538 4538 76 77 OSAAAA DSDAAA VVVVxx
+6868 2500 0 0 8 8 68 868 868 1868 6868 136 137 EEAAAA ESDAAA AAAAxx
+9801 2501 1 1 1 1 1 801 1801 4801 9801 2 3 ZMAAAA FSDAAA HHHHxx
+1781 2502 1 1 1 1 81 781 1781 1781 1781 162 163 NQAAAA GSDAAA OOOOxx
+7061 2503 1 1 1 1 61 61 1061 2061 7061 122 123 PLAAAA HSDAAA VVVVxx
+2412 2504 0 0 2 12 12 412 412 2412 2412 24 25 UOAAAA ISDAAA AAAAxx
+9191 2505 1 3 1 11 91 191 1191 4191 9191 182 183 NPAAAA JSDAAA HHHHxx
+1958 2506 0 2 8 18 58 958 1958 1958 1958 116 117 IXAAAA KSDAAA OOOOxx
+2203 2507 1 3 3 3 3 203 203 2203 2203 6 7 TGAAAA LSDAAA VVVVxx
+9104 2508 0 0 4 4 4 104 1104 4104 9104 8 9 EMAAAA MSDAAA AAAAxx
+3837 2509 1 1 7 17 37 837 1837 3837 3837 74 75 PRAAAA NSDAAA HHHHxx
+7055 2510 1 3 5 15 55 55 1055 2055 7055 110 111 JLAAAA OSDAAA OOOOxx
+4612 2511 0 0 2 12 12 612 612 4612 4612 24 25 KVAAAA PSDAAA VVVVxx
+6420 2512 0 0 0 0 20 420 420 1420 6420 40 41 YMAAAA QSDAAA AAAAxx
+613 2513 1 1 3 13 13 613 613 613 613 26 27 PXAAAA RSDAAA HHHHxx
+1691 2514 1 3 1 11 91 691 1691 1691 1691 182 183 BNAAAA SSDAAA OOOOxx
+33 2515 1 1 3 13 33 33 33 33 33 66 67 HBAAAA TSDAAA VVVVxx
+875 2516 1 3 5 15 75 875 875 875 875 150 151 RHAAAA USDAAA AAAAxx
+9030 2517 0 2 0 10 30 30 1030 4030 9030 60 61 IJAAAA VSDAAA HHHHxx
+4285 2518 1 1 5 5 85 285 285 4285 4285 170 171 VIAAAA WSDAAA OOOOxx
+6236 2519 0 0 6 16 36 236 236 1236 6236 72 73 WFAAAA XSDAAA VVVVxx
+4702 2520 0 2 2 2 2 702 702 4702 4702 4 5 WYAAAA YSDAAA AAAAxx
+3441 2521 1 1 1 1 41 441 1441 3441 3441 82 83 JCAAAA ZSDAAA HHHHxx
+2150 2522 0 2 0 10 50 150 150 2150 2150 100 101 SEAAAA ATDAAA OOOOxx
+1852 2523 0 0 2 12 52 852 1852 1852 1852 104 105 GTAAAA BTDAAA VVVVxx
+7713 2524 1 1 3 13 13 713 1713 2713 7713 26 27 RKAAAA CTDAAA AAAAxx
+6849 2525 1 1 9 9 49 849 849 1849 6849 98 99 LDAAAA DTDAAA HHHHxx
+3425 2526 1 1 5 5 25 425 1425 3425 3425 50 51 TBAAAA ETDAAA OOOOxx
+4681 2527 1 1 1 1 81 681 681 4681 4681 162 163 BYAAAA FTDAAA VVVVxx
+1134 2528 0 2 4 14 34 134 1134 1134 1134 68 69 QRAAAA GTDAAA AAAAxx
+7462 2529 0 2 2 2 62 462 1462 2462 7462 124 125 ABAAAA HTDAAA HHHHxx
+2148 2530 0 0 8 8 48 148 148 2148 2148 96 97 QEAAAA ITDAAA OOOOxx
+5921 2531 1 1 1 1 21 921 1921 921 5921 42 43 TTAAAA JTDAAA VVVVxx
+118 2532 0 2 8 18 18 118 118 118 118 36 37 OEAAAA KTDAAA AAAAxx
+3065 2533 1 1 5 5 65 65 1065 3065 3065 130 131 XNAAAA LTDAAA HHHHxx
+6590 2534 0 2 0 10 90 590 590 1590 6590 180 181 MTAAAA MTDAAA OOOOxx
+4993 2535 1 1 3 13 93 993 993 4993 4993 186 187 BKAAAA NTDAAA VVVVxx
+6818 2536 0 2 8 18 18 818 818 1818 6818 36 37 GCAAAA OTDAAA AAAAxx
+1449 2537 1 1 9 9 49 449 1449 1449 1449 98 99 TDAAAA PTDAAA HHHHxx
+2039 2538 1 3 9 19 39 39 39 2039 2039 78 79 LAAAAA QTDAAA OOOOxx
+2524 2539 0 0 4 4 24 524 524 2524 2524 48 49 CTAAAA RTDAAA VVVVxx
+1481 2540 1 1 1 1 81 481 1481 1481 1481 162 163 ZEAAAA STDAAA AAAAxx
+6984 2541 0 0 4 4 84 984 984 1984 6984 168 169 QIAAAA TTDAAA HHHHxx
+3960 2542 0 0 0 0 60 960 1960 3960 3960 120 121 IWAAAA UTDAAA OOOOxx
+1983 2543 1 3 3 3 83 983 1983 1983 1983 166 167 HYAAAA VTDAAA VVVVxx
+6379 2544 1 3 9 19 79 379 379 1379 6379 158 159 JLAAAA WTDAAA AAAAxx
+8975 2545 1 3 5 15 75 975 975 3975 8975 150 151 FHAAAA XTDAAA HHHHxx
+1102 2546 0 2 2 2 2 102 1102 1102 1102 4 5 KQAAAA YTDAAA OOOOxx
+2517 2547 1 1 7 17 17 517 517 2517 2517 34 35 VSAAAA ZTDAAA VVVVxx
+712 2548 0 0 2 12 12 712 712 712 712 24 25 KBAAAA AUDAAA AAAAxx
+5419 2549 1 3 9 19 19 419 1419 419 5419 38 39 LAAAAA BUDAAA HHHHxx
+723 2550 1 3 3 3 23 723 723 723 723 46 47 VBAAAA CUDAAA OOOOxx
+8057 2551 1 1 7 17 57 57 57 3057 8057 114 115 XXAAAA DUDAAA VVVVxx
+7471 2552 1 3 1 11 71 471 1471 2471 7471 142 143 JBAAAA EUDAAA AAAAxx
+8855 2553 1 3 5 15 55 855 855 3855 8855 110 111 PCAAAA FUDAAA HHHHxx
+5074 2554 0 2 4 14 74 74 1074 74 5074 148 149 ENAAAA GUDAAA OOOOxx
+7139 2555 1 3 9 19 39 139 1139 2139 7139 78 79 POAAAA HUDAAA VVVVxx
+3833 2556 1 1 3 13 33 833 1833 3833 3833 66 67 LRAAAA IUDAAA AAAAxx
+5186 2557 0 2 6 6 86 186 1186 186 5186 172 173 MRAAAA JUDAAA HHHHxx
+9436 2558 0 0 6 16 36 436 1436 4436 9436 72 73 YYAAAA KUDAAA OOOOxx
+8859 2559 1 3 9 19 59 859 859 3859 8859 118 119 TCAAAA LUDAAA VVVVxx
+6943 2560 1 3 3 3 43 943 943 1943 6943 86 87 BHAAAA MUDAAA AAAAxx
+2315 2561 1 3 5 15 15 315 315 2315 2315 30 31 BLAAAA NUDAAA HHHHxx
+1394 2562 0 2 4 14 94 394 1394 1394 1394 188 189 QBAAAA OUDAAA OOOOxx
+8863 2563 1 3 3 3 63 863 863 3863 8863 126 127 XCAAAA PUDAAA VVVVxx
+8812 2564 0 0 2 12 12 812 812 3812 8812 24 25 YAAAAA QUDAAA AAAAxx
+7498 2565 0 2 8 18 98 498 1498 2498 7498 196 197 KCAAAA RUDAAA HHHHxx
+8962 2566 0 2 2 2 62 962 962 3962 8962 124 125 SGAAAA SUDAAA OOOOxx
+2533 2567 1 1 3 13 33 533 533 2533 2533 66 67 LTAAAA TUDAAA VVVVxx
+8188 2568 0 0 8 8 88 188 188 3188 8188 176 177 YCAAAA UUDAAA AAAAxx
+6137 2569 1 1 7 17 37 137 137 1137 6137 74 75 BCAAAA VUDAAA HHHHxx
+974 2570 0 2 4 14 74 974 974 974 974 148 149 MLAAAA WUDAAA OOOOxx
+2751 2571 1 3 1 11 51 751 751 2751 2751 102 103 VBAAAA XUDAAA VVVVxx
+4975 2572 1 3 5 15 75 975 975 4975 4975 150 151 JJAAAA YUDAAA AAAAxx
+3411 2573 1 3 1 11 11 411 1411 3411 3411 22 23 FBAAAA ZUDAAA HHHHxx
+3143 2574 1 3 3 3 43 143 1143 3143 3143 86 87 XQAAAA AVDAAA OOOOxx
+8011 2575 1 3 1 11 11 11 11 3011 8011 22 23 DWAAAA BVDAAA VVVVxx
+988 2576 0 0 8 8 88 988 988 988 988 176 177 AMAAAA CVDAAA AAAAxx
+4289 2577 1 1 9 9 89 289 289 4289 4289 178 179 ZIAAAA DVDAAA HHHHxx
+8105 2578 1 1 5 5 5 105 105 3105 8105 10 11 TZAAAA EVDAAA OOOOxx
+9885 2579 1 1 5 5 85 885 1885 4885 9885 170 171 FQAAAA FVDAAA VVVVxx
+1002 2580 0 2 2 2 2 2 1002 1002 1002 4 5 OMAAAA GVDAAA AAAAxx
+5827 2581 1 3 7 7 27 827 1827 827 5827 54 55 DQAAAA HVDAAA HHHHxx
+1228 2582 0 0 8 8 28 228 1228 1228 1228 56 57 GVAAAA IVDAAA OOOOxx
+6352 2583 0 0 2 12 52 352 352 1352 6352 104 105 IKAAAA JVDAAA VVVVxx
+8868 2584 0 0 8 8 68 868 868 3868 8868 136 137 CDAAAA KVDAAA AAAAxx
+3643 2585 1 3 3 3 43 643 1643 3643 3643 86 87 DKAAAA LVDAAA HHHHxx
+1468 2586 0 0 8 8 68 468 1468 1468 1468 136 137 MEAAAA MVDAAA OOOOxx
+8415 2587 1 3 5 15 15 415 415 3415 8415 30 31 RLAAAA NVDAAA VVVVxx
+9631 2588 1 3 1 11 31 631 1631 4631 9631 62 63 LGAAAA OVDAAA AAAAxx
+7408 2589 0 0 8 8 8 408 1408 2408 7408 16 17 YYAAAA PVDAAA HHHHxx
+1934 2590 0 2 4 14 34 934 1934 1934 1934 68 69 KWAAAA QVDAAA OOOOxx
+996 2591 0 0 6 16 96 996 996 996 996 192 193 IMAAAA RVDAAA VVVVxx
+8027 2592 1 3 7 7 27 27 27 3027 8027 54 55 TWAAAA SVDAAA AAAAxx
+8464 2593 0 0 4 4 64 464 464 3464 8464 128 129 ONAAAA TVDAAA HHHHxx
+5007 2594 1 3 7 7 7 7 1007 7 5007 14 15 PKAAAA UVDAAA OOOOxx
+8356 2595 0 0 6 16 56 356 356 3356 8356 112 113 KJAAAA VVDAAA VVVVxx
+4579 2596 1 3 9 19 79 579 579 4579 4579 158 159 DUAAAA WVDAAA AAAAxx
+8513 2597 1 1 3 13 13 513 513 3513 8513 26 27 LPAAAA XVDAAA HHHHxx
+383 2598 1 3 3 3 83 383 383 383 383 166 167 TOAAAA YVDAAA OOOOxx
+9304 2599 0 0 4 4 4 304 1304 4304 9304 8 9 WTAAAA ZVDAAA VVVVxx
+7224 2600 0 0 4 4 24 224 1224 2224 7224 48 49 WRAAAA AWDAAA AAAAxx
+6023 2601 1 3 3 3 23 23 23 1023 6023 46 47 RXAAAA BWDAAA HHHHxx
+2746 2602 0 2 6 6 46 746 746 2746 2746 92 93 QBAAAA CWDAAA OOOOxx
+137 2603 1 1 7 17 37 137 137 137 137 74 75 HFAAAA DWDAAA VVVVxx
+9441 2604 1 1 1 1 41 441 1441 4441 9441 82 83 DZAAAA EWDAAA AAAAxx
+3690 2605 0 2 0 10 90 690 1690 3690 3690 180 181 YLAAAA FWDAAA HHHHxx
+913 2606 1 1 3 13 13 913 913 913 913 26 27 DJAAAA GWDAAA OOOOxx
+1768 2607 0 0 8 8 68 768 1768 1768 1768 136 137 AQAAAA HWDAAA VVVVxx
+8492 2608 0 0 2 12 92 492 492 3492 8492 184 185 QOAAAA IWDAAA AAAAxx
+8083 2609 1 3 3 3 83 83 83 3083 8083 166 167 XYAAAA JWDAAA HHHHxx
+4609 2610 1 1 9 9 9 609 609 4609 4609 18 19 HVAAAA KWDAAA OOOOxx
+7520 2611 0 0 0 0 20 520 1520 2520 7520 40 41 GDAAAA LWDAAA VVVVxx
+4231 2612 1 3 1 11 31 231 231 4231 4231 62 63 TGAAAA MWDAAA AAAAxx
+6022 2613 0 2 2 2 22 22 22 1022 6022 44 45 QXAAAA NWDAAA HHHHxx
+9784 2614 0 0 4 4 84 784 1784 4784 9784 168 169 IMAAAA OWDAAA OOOOxx
+1343 2615 1 3 3 3 43 343 1343 1343 1343 86 87 RZAAAA PWDAAA VVVVxx
+7549 2616 1 1 9 9 49 549 1549 2549 7549 98 99 JEAAAA QWDAAA AAAAxx
+269 2617 1 1 9 9 69 269 269 269 269 138 139 JKAAAA RWDAAA HHHHxx
+1069 2618 1 1 9 9 69 69 1069 1069 1069 138 139 DPAAAA SWDAAA OOOOxx
+4610 2619 0 2 0 10 10 610 610 4610 4610 20 21 IVAAAA TWDAAA VVVVxx
+482 2620 0 2 2 2 82 482 482 482 482 164 165 OSAAAA UWDAAA AAAAxx
+3025 2621 1 1 5 5 25 25 1025 3025 3025 50 51 JMAAAA VWDAAA HHHHxx
+7914 2622 0 2 4 14 14 914 1914 2914 7914 28 29 KSAAAA WWDAAA OOOOxx
+3198 2623 0 2 8 18 98 198 1198 3198 3198 196 197 ATAAAA XWDAAA VVVVxx
+1187 2624 1 3 7 7 87 187 1187 1187 1187 174 175 RTAAAA YWDAAA AAAAxx
+4707 2625 1 3 7 7 7 707 707 4707 4707 14 15 BZAAAA ZWDAAA HHHHxx
+8279 2626 1 3 9 19 79 279 279 3279 8279 158 159 LGAAAA AXDAAA OOOOxx
+6127 2627 1 3 7 7 27 127 127 1127 6127 54 55 RBAAAA BXDAAA VVVVxx
+1305 2628 1 1 5 5 5 305 1305 1305 1305 10 11 FYAAAA CXDAAA AAAAxx
+4804 2629 0 0 4 4 4 804 804 4804 4804 8 9 UCAAAA DXDAAA HHHHxx
+6069 2630 1 1 9 9 69 69 69 1069 6069 138 139 LZAAAA EXDAAA OOOOxx
+9229 2631 1 1 9 9 29 229 1229 4229 9229 58 59 ZQAAAA FXDAAA VVVVxx
+4703 2632 1 3 3 3 3 703 703 4703 4703 6 7 XYAAAA GXDAAA AAAAxx
+6410 2633 0 2 0 10 10 410 410 1410 6410 20 21 OMAAAA HXDAAA HHHHxx
+944 2634 0 0 4 4 44 944 944 944 944 88 89 IKAAAA IXDAAA OOOOxx
+3744 2635 0 0 4 4 44 744 1744 3744 3744 88 89 AOAAAA JXDAAA VVVVxx
+1127 2636 1 3 7 7 27 127 1127 1127 1127 54 55 JRAAAA KXDAAA AAAAxx
+6693 2637 1 1 3 13 93 693 693 1693 6693 186 187 LXAAAA LXDAAA HHHHxx
+583 2638 1 3 3 3 83 583 583 583 583 166 167 LWAAAA MXDAAA OOOOxx
+2684 2639 0 0 4 4 84 684 684 2684 2684 168 169 GZAAAA NXDAAA VVVVxx
+6192 2640 0 0 2 12 92 192 192 1192 6192 184 185 EEAAAA OXDAAA AAAAxx
+4157 2641 1 1 7 17 57 157 157 4157 4157 114 115 XDAAAA PXDAAA HHHHxx
+6470 2642 0 2 0 10 70 470 470 1470 6470 140 141 WOAAAA QXDAAA OOOOxx
+8965 2643 1 1 5 5 65 965 965 3965 8965 130 131 VGAAAA RXDAAA VVVVxx
+1433 2644 1 1 3 13 33 433 1433 1433 1433 66 67 DDAAAA SXDAAA AAAAxx
+4570 2645 0 2 0 10 70 570 570 4570 4570 140 141 UTAAAA TXDAAA HHHHxx
+1806 2646 0 2 6 6 6 806 1806 1806 1806 12 13 MRAAAA UXDAAA OOOOxx
+1230 2647 0 2 0 10 30 230 1230 1230 1230 60 61 IVAAAA VXDAAA VVVVxx
+2283 2648 1 3 3 3 83 283 283 2283 2283 166 167 VJAAAA WXDAAA AAAAxx
+6456 2649 0 0 6 16 56 456 456 1456 6456 112 113 IOAAAA XXDAAA HHHHxx
+7427 2650 1 3 7 7 27 427 1427 2427 7427 54 55 RZAAAA YXDAAA OOOOxx
+8310 2651 0 2 0 10 10 310 310 3310 8310 20 21 QHAAAA ZXDAAA VVVVxx
+8103 2652 1 3 3 3 3 103 103 3103 8103 6 7 RZAAAA AYDAAA AAAAxx
+3947 2653 1 3 7 7 47 947 1947 3947 3947 94 95 VVAAAA BYDAAA HHHHxx
+3414 2654 0 2 4 14 14 414 1414 3414 3414 28 29 IBAAAA CYDAAA OOOOxx
+2043 2655 1 3 3 3 43 43 43 2043 2043 86 87 PAAAAA DYDAAA VVVVxx
+4393 2656 1 1 3 13 93 393 393 4393 4393 186 187 ZMAAAA EYDAAA AAAAxx
+6664 2657 0 0 4 4 64 664 664 1664 6664 128 129 IWAAAA FYDAAA HHHHxx
+4545 2658 1 1 5 5 45 545 545 4545 4545 90 91 VSAAAA GYDAAA OOOOxx
+7637 2659 1 1 7 17 37 637 1637 2637 7637 74 75 THAAAA HYDAAA VVVVxx
+1359 2660 1 3 9 19 59 359 1359 1359 1359 118 119 HAAAAA IYDAAA AAAAxx
+5018 2661 0 2 8 18 18 18 1018 18 5018 36 37 ALAAAA JYDAAA HHHHxx
+987 2662 1 3 7 7 87 987 987 987 987 174 175 ZLAAAA KYDAAA OOOOxx
+1320 2663 0 0 0 0 20 320 1320 1320 1320 40 41 UYAAAA LYDAAA VVVVxx
+9311 2664 1 3 1 11 11 311 1311 4311 9311 22 23 DUAAAA MYDAAA AAAAxx
+7993 2665 1 1 3 13 93 993 1993 2993 7993 186 187 LVAAAA NYDAAA HHHHxx
+7588 2666 0 0 8 8 88 588 1588 2588 7588 176 177 WFAAAA OYDAAA OOOOxx
+5983 2667 1 3 3 3 83 983 1983 983 5983 166 167 DWAAAA PYDAAA VVVVxx
+4070 2668 0 2 0 10 70 70 70 4070 4070 140 141 OAAAAA QYDAAA AAAAxx
+8349 2669 1 1 9 9 49 349 349 3349 8349 98 99 DJAAAA RYDAAA HHHHxx
+3810 2670 0 2 0 10 10 810 1810 3810 3810 20 21 OQAAAA SYDAAA OOOOxx
+6948 2671 0 0 8 8 48 948 948 1948 6948 96 97 GHAAAA TYDAAA VVVVxx
+7153 2672 1 1 3 13 53 153 1153 2153 7153 106 107 DPAAAA UYDAAA AAAAxx
+5371 2673 1 3 1 11 71 371 1371 371 5371 142 143 PYAAAA VYDAAA HHHHxx
+8316 2674 0 0 6 16 16 316 316 3316 8316 32 33 WHAAAA WYDAAA OOOOxx
+5903 2675 1 3 3 3 3 903 1903 903 5903 6 7 BTAAAA XYDAAA VVVVxx
+6718 2676 0 2 8 18 18 718 718 1718 6718 36 37 KYAAAA YYDAAA AAAAxx
+4759 2677 1 3 9 19 59 759 759 4759 4759 118 119 BBAAAA ZYDAAA HHHHxx
+2555 2678 1 3 5 15 55 555 555 2555 2555 110 111 HUAAAA AZDAAA OOOOxx
+3457 2679 1 1 7 17 57 457 1457 3457 3457 114 115 ZCAAAA BZDAAA VVVVxx
+9626 2680 0 2 6 6 26 626 1626 4626 9626 52 53 GGAAAA CZDAAA AAAAxx
+2570 2681 0 2 0 10 70 570 570 2570 2570 140 141 WUAAAA DZDAAA HHHHxx
+7964 2682 0 0 4 4 64 964 1964 2964 7964 128 129 IUAAAA EZDAAA OOOOxx
+1543 2683 1 3 3 3 43 543 1543 1543 1543 86 87 JHAAAA FZDAAA VVVVxx
+929 2684 1 1 9 9 29 929 929 929 929 58 59 TJAAAA GZDAAA AAAAxx
+9244 2685 0 0 4 4 44 244 1244 4244 9244 88 89 ORAAAA HZDAAA HHHHxx
+9210 2686 0 2 0 10 10 210 1210 4210 9210 20 21 GQAAAA IZDAAA OOOOxx
+8334 2687 0 2 4 14 34 334 334 3334 8334 68 69 OIAAAA JZDAAA VVVVxx
+9310 2688 0 2 0 10 10 310 1310 4310 9310 20 21 CUAAAA KZDAAA AAAAxx
+5024 2689 0 0 4 4 24 24 1024 24 5024 48 49 GLAAAA LZDAAA HHHHxx
+8794 2690 0 2 4 14 94 794 794 3794 8794 188 189 GAAAAA MZDAAA OOOOxx
+4091 2691 1 3 1 11 91 91 91 4091 4091 182 183 JBAAAA NZDAAA VVVVxx
+649 2692 1 1 9 9 49 649 649 649 649 98 99 ZYAAAA OZDAAA AAAAxx
+8505 2693 1 1 5 5 5 505 505 3505 8505 10 11 DPAAAA PZDAAA HHHHxx
+6652 2694 0 0 2 12 52 652 652 1652 6652 104 105 WVAAAA QZDAAA OOOOxx
+8945 2695 1 1 5 5 45 945 945 3945 8945 90 91 BGAAAA RZDAAA VVVVxx
+2095 2696 1 3 5 15 95 95 95 2095 2095 190 191 PCAAAA SZDAAA AAAAxx
+8676 2697 0 0 6 16 76 676 676 3676 8676 152 153 SVAAAA TZDAAA HHHHxx
+3994 2698 0 2 4 14 94 994 1994 3994 3994 188 189 QXAAAA UZDAAA OOOOxx
+2859 2699 1 3 9 19 59 859 859 2859 2859 118 119 ZFAAAA VZDAAA VVVVxx
+5403 2700 1 3 3 3 3 403 1403 403 5403 6 7 VZAAAA WZDAAA AAAAxx
+3254 2701 0 2 4 14 54 254 1254 3254 3254 108 109 EVAAAA XZDAAA HHHHxx
+7339 2702 1 3 9 19 39 339 1339 2339 7339 78 79 HWAAAA YZDAAA OOOOxx
+7220 2703 0 0 0 0 20 220 1220 2220 7220 40 41 SRAAAA ZZDAAA VVVVxx
+4154 2704 0 2 4 14 54 154 154 4154 4154 108 109 UDAAAA AAEAAA AAAAxx
+7570 2705 0 2 0 10 70 570 1570 2570 7570 140 141 EFAAAA BAEAAA HHHHxx
+2576 2706 0 0 6 16 76 576 576 2576 2576 152 153 CVAAAA CAEAAA OOOOxx
+5764 2707 0 0 4 4 64 764 1764 764 5764 128 129 SNAAAA DAEAAA VVVVxx
+4314 2708 0 2 4 14 14 314 314 4314 4314 28 29 YJAAAA EAEAAA AAAAxx
+2274 2709 0 2 4 14 74 274 274 2274 2274 148 149 MJAAAA FAEAAA HHHHxx
+9756 2710 0 0 6 16 56 756 1756 4756 9756 112 113 GLAAAA GAEAAA OOOOxx
+8274 2711 0 2 4 14 74 274 274 3274 8274 148 149 GGAAAA HAEAAA VVVVxx
+1289 2712 1 1 9 9 89 289 1289 1289 1289 178 179 PXAAAA IAEAAA AAAAxx
+7335 2713 1 3 5 15 35 335 1335 2335 7335 70 71 DWAAAA JAEAAA HHHHxx
+5351 2714 1 3 1 11 51 351 1351 351 5351 102 103 VXAAAA KAEAAA OOOOxx
+8978 2715 0 2 8 18 78 978 978 3978 8978 156 157 IHAAAA LAEAAA VVVVxx
+2 2716 0 2 2 2 2 2 2 2 2 4 5 CAAAAA MAEAAA AAAAxx
+8906 2717 0 2 6 6 6 906 906 3906 8906 12 13 OEAAAA NAEAAA HHHHxx
+6388 2718 0 0 8 8 88 388 388 1388 6388 176 177 SLAAAA OAEAAA OOOOxx
+5675 2719 1 3 5 15 75 675 1675 675 5675 150 151 HKAAAA PAEAAA VVVVxx
+255 2720 1 3 5 15 55 255 255 255 255 110 111 VJAAAA QAEAAA AAAAxx
+9538 2721 0 2 8 18 38 538 1538 4538 9538 76 77 WCAAAA RAEAAA HHHHxx
+1480 2722 0 0 0 0 80 480 1480 1480 1480 160 161 YEAAAA SAEAAA OOOOxx
+4015 2723 1 3 5 15 15 15 15 4015 4015 30 31 LYAAAA TAEAAA VVVVxx
+5166 2724 0 2 6 6 66 166 1166 166 5166 132 133 SQAAAA UAEAAA AAAAxx
+91 2725 1 3 1 11 91 91 91 91 91 182 183 NDAAAA VAEAAA HHHHxx
+2958 2726 0 2 8 18 58 958 958 2958 2958 116 117 UJAAAA WAEAAA OOOOxx
+9131 2727 1 3 1 11 31 131 1131 4131 9131 62 63 FNAAAA XAEAAA VVVVxx
+3944 2728 0 0 4 4 44 944 1944 3944 3944 88 89 SVAAAA YAEAAA AAAAxx
+4514 2729 0 2 4 14 14 514 514 4514 4514 28 29 QRAAAA ZAEAAA HHHHxx
+5661 2730 1 1 1 1 61 661 1661 661 5661 122 123 TJAAAA ABEAAA OOOOxx
+8724 2731 0 0 4 4 24 724 724 3724 8724 48 49 OXAAAA BBEAAA VVVVxx
+6408 2732 0 0 8 8 8 408 408 1408 6408 16 17 MMAAAA CBEAAA AAAAxx
+5013 2733 1 1 3 13 13 13 1013 13 5013 26 27 VKAAAA DBEAAA HHHHxx
+6156 2734 0 0 6 16 56 156 156 1156 6156 112 113 UCAAAA EBEAAA OOOOxx
+7350 2735 0 2 0 10 50 350 1350 2350 7350 100 101 SWAAAA FBEAAA VVVVxx
+9858 2736 0 2 8 18 58 858 1858 4858 9858 116 117 EPAAAA GBEAAA AAAAxx
+895 2737 1 3 5 15 95 895 895 895 895 190 191 LIAAAA HBEAAA HHHHxx
+8368 2738 0 0 8 8 68 368 368 3368 8368 136 137 WJAAAA IBEAAA OOOOxx
+179 2739 1 3 9 19 79 179 179 179 179 158 159 XGAAAA JBEAAA VVVVxx
+4048 2740 0 0 8 8 48 48 48 4048 4048 96 97 SZAAAA KBEAAA AAAAxx
+3073 2741 1 1 3 13 73 73 1073 3073 3073 146 147 FOAAAA LBEAAA HHHHxx
+321 2742 1 1 1 1 21 321 321 321 321 42 43 JMAAAA MBEAAA OOOOxx
+5352 2743 0 0 2 12 52 352 1352 352 5352 104 105 WXAAAA NBEAAA VVVVxx
+1940 2744 0 0 0 0 40 940 1940 1940 1940 80 81 QWAAAA OBEAAA AAAAxx
+8803 2745 1 3 3 3 3 803 803 3803 8803 6 7 PAAAAA PBEAAA HHHHxx
+791 2746 1 3 1 11 91 791 791 791 791 182 183 LEAAAA QBEAAA OOOOxx
+9809 2747 1 1 9 9 9 809 1809 4809 9809 18 19 HNAAAA RBEAAA VVVVxx
+5519 2748 1 3 9 19 19 519 1519 519 5519 38 39 HEAAAA SBEAAA AAAAxx
+7420 2749 0 0 0 0 20 420 1420 2420 7420 40 41 KZAAAA TBEAAA HHHHxx
+7541 2750 1 1 1 1 41 541 1541 2541 7541 82 83 BEAAAA UBEAAA OOOOxx
+6538 2751 0 2 8 18 38 538 538 1538 6538 76 77 MRAAAA VBEAAA VVVVxx
+710 2752 0 2 0 10 10 710 710 710 710 20 21 IBAAAA WBEAAA AAAAxx
+9488 2753 0 0 8 8 88 488 1488 4488 9488 176 177 YAAAAA XBEAAA HHHHxx
+3135 2754 1 3 5 15 35 135 1135 3135 3135 70 71 PQAAAA YBEAAA OOOOxx
+4273 2755 1 1 3 13 73 273 273 4273 4273 146 147 JIAAAA ZBEAAA VVVVxx
+629 2756 1 1 9 9 29 629 629 629 629 58 59 FYAAAA ACEAAA AAAAxx
+9167 2757 1 3 7 7 67 167 1167 4167 9167 134 135 POAAAA BCEAAA HHHHxx
+751 2758 1 3 1 11 51 751 751 751 751 102 103 XCAAAA CCEAAA OOOOxx
+1126 2759 0 2 6 6 26 126 1126 1126 1126 52 53 IRAAAA DCEAAA VVVVxx
+3724 2760 0 0 4 4 24 724 1724 3724 3724 48 49 GNAAAA ECEAAA AAAAxx
+1789 2761 1 1 9 9 89 789 1789 1789 1789 178 179 VQAAAA FCEAAA HHHHxx
+792 2762 0 0 2 12 92 792 792 792 792 184 185 MEAAAA GCEAAA OOOOxx
+2771 2763 1 3 1 11 71 771 771 2771 2771 142 143 PCAAAA HCEAAA VVVVxx
+4313 2764 1 1 3 13 13 313 313 4313 4313 26 27 XJAAAA ICEAAA AAAAxx
+9312 2765 0 0 2 12 12 312 1312 4312 9312 24 25 EUAAAA JCEAAA HHHHxx
+955 2766 1 3 5 15 55 955 955 955 955 110 111 TKAAAA KCEAAA OOOOxx
+6382 2767 0 2 2 2 82 382 382 1382 6382 164 165 MLAAAA LCEAAA VVVVxx
+7875 2768 1 3 5 15 75 875 1875 2875 7875 150 151 XQAAAA MCEAAA AAAAxx
+7491 2769 1 3 1 11 91 491 1491 2491 7491 182 183 DCAAAA NCEAAA HHHHxx
+8193 2770 1 1 3 13 93 193 193 3193 8193 186 187 DDAAAA OCEAAA OOOOxx
+968 2771 0 0 8 8 68 968 968 968 968 136 137 GLAAAA PCEAAA VVVVxx
+4951 2772 1 3 1 11 51 951 951 4951 4951 102 103 LIAAAA QCEAAA AAAAxx
+2204 2773 0 0 4 4 4 204 204 2204 2204 8 9 UGAAAA RCEAAA HHHHxx
+2066 2774 0 2 6 6 66 66 66 2066 2066 132 133 MBAAAA SCEAAA OOOOxx
+2631 2775 1 3 1 11 31 631 631 2631 2631 62 63 FXAAAA TCEAAA VVVVxx
+8947 2776 1 3 7 7 47 947 947 3947 8947 94 95 DGAAAA UCEAAA AAAAxx
+8033 2777 1 1 3 13 33 33 33 3033 8033 66 67 ZWAAAA VCEAAA HHHHxx
+6264 2778 0 0 4 4 64 264 264 1264 6264 128 129 YGAAAA WCEAAA OOOOxx
+7778 2779 0 2 8 18 78 778 1778 2778 7778 156 157 ENAAAA XCEAAA VVVVxx
+9701 2780 1 1 1 1 1 701 1701 4701 9701 2 3 DJAAAA YCEAAA AAAAxx
+5091 2781 1 3 1 11 91 91 1091 91 5091 182 183 VNAAAA ZCEAAA HHHHxx
+7577 2782 1 1 7 17 77 577 1577 2577 7577 154 155 LFAAAA ADEAAA OOOOxx
+3345 2783 1 1 5 5 45 345 1345 3345 3345 90 91 RYAAAA BDEAAA VVVVxx
+7329 2784 1 1 9 9 29 329 1329 2329 7329 58 59 XVAAAA CDEAAA AAAAxx
+7551 2785 1 3 1 11 51 551 1551 2551 7551 102 103 LEAAAA DDEAAA HHHHxx
+6207 2786 1 3 7 7 7 207 207 1207 6207 14 15 TEAAAA EDEAAA OOOOxx
+8664 2787 0 0 4 4 64 664 664 3664 8664 128 129 GVAAAA FDEAAA VVVVxx
+8394 2788 0 2 4 14 94 394 394 3394 8394 188 189 WKAAAA GDEAAA AAAAxx
+7324 2789 0 0 4 4 24 324 1324 2324 7324 48 49 SVAAAA HDEAAA HHHHxx
+2713 2790 1 1 3 13 13 713 713 2713 2713 26 27 JAAAAA IDEAAA OOOOxx
+2230 2791 0 2 0 10 30 230 230 2230 2230 60 61 UHAAAA JDEAAA VVVVxx
+9211 2792 1 3 1 11 11 211 1211 4211 9211 22 23 HQAAAA KDEAAA AAAAxx
+1296 2793 0 0 6 16 96 296 1296 1296 1296 192 193 WXAAAA LDEAAA HHHHxx
+8104 2794 0 0 4 4 4 104 104 3104 8104 8 9 SZAAAA MDEAAA OOOOxx
+6916 2795 0 0 6 16 16 916 916 1916 6916 32 33 AGAAAA NDEAAA VVVVxx
+2208 2796 0 0 8 8 8 208 208 2208 2208 16 17 YGAAAA ODEAAA AAAAxx
+3935 2797 1 3 5 15 35 935 1935 3935 3935 70 71 JVAAAA PDEAAA HHHHxx
+7814 2798 0 2 4 14 14 814 1814 2814 7814 28 29 OOAAAA QDEAAA OOOOxx
+6508 2799 0 0 8 8 8 508 508 1508 6508 16 17 IQAAAA RDEAAA VVVVxx
+1703 2800 1 3 3 3 3 703 1703 1703 1703 6 7 NNAAAA SDEAAA AAAAxx
+5640 2801 0 0 0 0 40 640 1640 640 5640 80 81 YIAAAA TDEAAA HHHHxx
+6417 2802 1 1 7 17 17 417 417 1417 6417 34 35 VMAAAA UDEAAA OOOOxx
+1713 2803 1 1 3 13 13 713 1713 1713 1713 26 27 XNAAAA VDEAAA VVVVxx
+5309 2804 1 1 9 9 9 309 1309 309 5309 18 19 FWAAAA WDEAAA AAAAxx
+4364 2805 0 0 4 4 64 364 364 4364 4364 128 129 WLAAAA XDEAAA HHHHxx
+619 2806 1 3 9 19 19 619 619 619 619 38 39 VXAAAA YDEAAA OOOOxx
+9498 2807 0 2 8 18 98 498 1498 4498 9498 196 197 IBAAAA ZDEAAA VVVVxx
+2804 2808 0 0 4 4 4 804 804 2804 2804 8 9 WDAAAA AEEAAA AAAAxx
+2220 2809 0 0 0 0 20 220 220 2220 2220 40 41 KHAAAA BEEAAA HHHHxx
+9542 2810 0 2 2 2 42 542 1542 4542 9542 84 85 ADAAAA CEEAAA OOOOxx
+3349 2811 1 1 9 9 49 349 1349 3349 3349 98 99 VYAAAA DEEAAA VVVVxx
+9198 2812 0 2 8 18 98 198 1198 4198 9198 196 197 UPAAAA EEEAAA AAAAxx
+2727 2813 1 3 7 7 27 727 727 2727 2727 54 55 XAAAAA FEEAAA HHHHxx
+3768 2814 0 0 8 8 68 768 1768 3768 3768 136 137 YOAAAA GEEAAA OOOOxx
+2334 2815 0 2 4 14 34 334 334 2334 2334 68 69 ULAAAA HEEAAA VVVVxx
+7770 2816 0 2 0 10 70 770 1770 2770 7770 140 141 WMAAAA IEEAAA AAAAxx
+5963 2817 1 3 3 3 63 963 1963 963 5963 126 127 JVAAAA JEEAAA HHHHxx
+4732 2818 0 0 2 12 32 732 732 4732 4732 64 65 AAAAAA KEEAAA OOOOxx
+2448 2819 0 0 8 8 48 448 448 2448 2448 96 97 EQAAAA LEEAAA VVVVxx
+5998 2820 0 2 8 18 98 998 1998 998 5998 196 197 SWAAAA MEEAAA AAAAxx
+8577 2821 1 1 7 17 77 577 577 3577 8577 154 155 XRAAAA NEEAAA HHHHxx
+266 2822 0 2 6 6 66 266 266 266 266 132 133 GKAAAA OEEAAA OOOOxx
+2169 2823 1 1 9 9 69 169 169 2169 2169 138 139 LFAAAA PEEAAA VVVVxx
+8228 2824 0 0 8 8 28 228 228 3228 8228 56 57 MEAAAA QEEAAA AAAAxx
+4813 2825 1 1 3 13 13 813 813 4813 4813 26 27 DDAAAA REEAAA HHHHxx
+2769 2826 1 1 9 9 69 769 769 2769 2769 138 139 NCAAAA SEEAAA OOOOxx
+8382 2827 0 2 2 2 82 382 382 3382 8382 164 165 KKAAAA TEEAAA VVVVxx
+1717 2828 1 1 7 17 17 717 1717 1717 1717 34 35 BOAAAA UEEAAA AAAAxx
+7178 2829 0 2 8 18 78 178 1178 2178 7178 156 157 CQAAAA VEEAAA HHHHxx
+9547 2830 1 3 7 7 47 547 1547 4547 9547 94 95 FDAAAA WEEAAA OOOOxx
+8187 2831 1 3 7 7 87 187 187 3187 8187 174 175 XCAAAA XEEAAA VVVVxx
+3168 2832 0 0 8 8 68 168 1168 3168 3168 136 137 WRAAAA YEEAAA AAAAxx
+2180 2833 0 0 0 0 80 180 180 2180 2180 160 161 WFAAAA ZEEAAA HHHHxx
+859 2834 1 3 9 19 59 859 859 859 859 118 119 BHAAAA AFEAAA OOOOxx
+1554 2835 0 2 4 14 54 554 1554 1554 1554 108 109 UHAAAA BFEAAA VVVVxx
+3567 2836 1 3 7 7 67 567 1567 3567 3567 134 135 FHAAAA CFEAAA AAAAxx
+5985 2837 1 1 5 5 85 985 1985 985 5985 170 171 FWAAAA DFEAAA HHHHxx
+1 2838 1 1 1 1 1 1 1 1 1 2 3 BAAAAA EFEAAA OOOOxx
+5937 2839 1 1 7 17 37 937 1937 937 5937 74 75 JUAAAA FFEAAA VVVVxx
+7594 2840 0 2 4 14 94 594 1594 2594 7594 188 189 CGAAAA GFEAAA AAAAxx
+3783 2841 1 3 3 3 83 783 1783 3783 3783 166 167 NPAAAA HFEAAA HHHHxx
+6841 2842 1 1 1 1 41 841 841 1841 6841 82 83 DDAAAA IFEAAA OOOOxx
+9694 2843 0 2 4 14 94 694 1694 4694 9694 188 189 WIAAAA JFEAAA VVVVxx
+4322 2844 0 2 2 2 22 322 322 4322 4322 44 45 GKAAAA KFEAAA AAAAxx
+6012 2845 0 0 2 12 12 12 12 1012 6012 24 25 GXAAAA LFEAAA HHHHxx
+108 2846 0 0 8 8 8 108 108 108 108 16 17 EEAAAA MFEAAA OOOOxx
+3396 2847 0 0 6 16 96 396 1396 3396 3396 192 193 QAAAAA NFEAAA VVVVxx
+8643 2848 1 3 3 3 43 643 643 3643 8643 86 87 LUAAAA OFEAAA AAAAxx
+6087 2849 1 3 7 7 87 87 87 1087 6087 174 175 DAAAAA PFEAAA HHHHxx
+2629 2850 1 1 9 9 29 629 629 2629 2629 58 59 DXAAAA QFEAAA OOOOxx
+3009 2851 1 1 9 9 9 9 1009 3009 3009 18 19 TLAAAA RFEAAA VVVVxx
+438 2852 0 2 8 18 38 438 438 438 438 76 77 WQAAAA SFEAAA AAAAxx
+2480 2853 0 0 0 0 80 480 480 2480 2480 160 161 KRAAAA TFEAAA HHHHxx
+936 2854 0 0 6 16 36 936 936 936 936 72 73 AKAAAA UFEAAA OOOOxx
+6 2855 0 2 6 6 6 6 6 6 6 12 13 GAAAAA VFEAAA VVVVxx
+768 2856 0 0 8 8 68 768 768 768 768 136 137 ODAAAA WFEAAA AAAAxx
+1564 2857 0 0 4 4 64 564 1564 1564 1564 128 129 EIAAAA XFEAAA HHHHxx
+3236 2858 0 0 6 16 36 236 1236 3236 3236 72 73 MUAAAA YFEAAA OOOOxx
+3932 2859 0 0 2 12 32 932 1932 3932 3932 64 65 GVAAAA ZFEAAA VVVVxx
+8914 2860 0 2 4 14 14 914 914 3914 8914 28 29 WEAAAA AGEAAA AAAAxx
+119 2861 1 3 9 19 19 119 119 119 119 38 39 PEAAAA BGEAAA HHHHxx
+6034 2862 0 2 4 14 34 34 34 1034 6034 68 69 CYAAAA CGEAAA OOOOxx
+5384 2863 0 0 4 4 84 384 1384 384 5384 168 169 CZAAAA DGEAAA VVVVxx
+6885 2864 1 1 5 5 85 885 885 1885 6885 170 171 VEAAAA EGEAAA AAAAxx
+232 2865 0 0 2 12 32 232 232 232 232 64 65 YIAAAA FGEAAA HHHHxx
+1293 2866 1 1 3 13 93 293 1293 1293 1293 186 187 TXAAAA GGEAAA OOOOxx
+9204 2867 0 0 4 4 4 204 1204 4204 9204 8 9 AQAAAA HGEAAA VVVVxx
+527 2868 1 3 7 7 27 527 527 527 527 54 55 HUAAAA IGEAAA AAAAxx
+6539 2869 1 3 9 19 39 539 539 1539 6539 78 79 NRAAAA JGEAAA HHHHxx
+3679 2870 1 3 9 19 79 679 1679 3679 3679 158 159 NLAAAA KGEAAA OOOOxx
+8282 2871 0 2 2 2 82 282 282 3282 8282 164 165 OGAAAA LGEAAA VVVVxx
+5027 2872 1 3 7 7 27 27 1027 27 5027 54 55 JLAAAA MGEAAA AAAAxx
+7694 2873 0 2 4 14 94 694 1694 2694 7694 188 189 YJAAAA NGEAAA HHHHxx
+473 2874 1 1 3 13 73 473 473 473 473 146 147 FSAAAA OGEAAA OOOOxx
+6325 2875 1 1 5 5 25 325 325 1325 6325 50 51 HJAAAA PGEAAA VVVVxx
+8761 2876 1 1 1 1 61 761 761 3761 8761 122 123 ZYAAAA QGEAAA AAAAxx
+6184 2877 0 0 4 4 84 184 184 1184 6184 168 169 WDAAAA RGEAAA HHHHxx
+419 2878 1 3 9 19 19 419 419 419 419 38 39 DQAAAA SGEAAA OOOOxx
+6111 2879 1 3 1 11 11 111 111 1111 6111 22 23 BBAAAA TGEAAA VVVVxx
+3836 2880 0 0 6 16 36 836 1836 3836 3836 72 73 ORAAAA UGEAAA AAAAxx
+4086 2881 0 2 6 6 86 86 86 4086 4086 172 173 EBAAAA VGEAAA HHHHxx
+5818 2882 0 2 8 18 18 818 1818 818 5818 36 37 UPAAAA WGEAAA OOOOxx
+4528 2883 0 0 8 8 28 528 528 4528 4528 56 57 ESAAAA XGEAAA VVVVxx
+7199 2884 1 3 9 19 99 199 1199 2199 7199 198 199 XQAAAA YGEAAA AAAAxx
+1847 2885 1 3 7 7 47 847 1847 1847 1847 94 95 BTAAAA ZGEAAA HHHHxx
+2875 2886 1 3 5 15 75 875 875 2875 2875 150 151 PGAAAA AHEAAA OOOOxx
+2872 2887 0 0 2 12 72 872 872 2872 2872 144 145 MGAAAA BHEAAA VVVVxx
+3972 2888 0 0 2 12 72 972 1972 3972 3972 144 145 UWAAAA CHEAAA AAAAxx
+7590 2889 0 2 0 10 90 590 1590 2590 7590 180 181 YFAAAA DHEAAA HHHHxx
+1914 2890 0 2 4 14 14 914 1914 1914 1914 28 29 QVAAAA EHEAAA OOOOxx
+1658 2891 0 2 8 18 58 658 1658 1658 1658 116 117 ULAAAA FHEAAA VVVVxx
+2126 2892 0 2 6 6 26 126 126 2126 2126 52 53 UDAAAA GHEAAA AAAAxx
+645 2893 1 1 5 5 45 645 645 645 645 90 91 VYAAAA HHEAAA HHHHxx
+6636 2894 0 0 6 16 36 636 636 1636 6636 72 73 GVAAAA IHEAAA OOOOxx
+1469 2895 1 1 9 9 69 469 1469 1469 1469 138 139 NEAAAA JHEAAA VVVVxx
+1377 2896 1 1 7 17 77 377 1377 1377 1377 154 155 ZAAAAA KHEAAA AAAAxx
+8425 2897 1 1 5 5 25 425 425 3425 8425 50 51 BMAAAA LHEAAA HHHHxx
+9300 2898 0 0 0 0 0 300 1300 4300 9300 0 1 STAAAA MHEAAA OOOOxx
+5355 2899 1 3 5 15 55 355 1355 355 5355 110 111 ZXAAAA NHEAAA VVVVxx
+840 2900 0 0 0 0 40 840 840 840 840 80 81 IGAAAA OHEAAA AAAAxx
+5185 2901 1 1 5 5 85 185 1185 185 5185 170 171 LRAAAA PHEAAA HHHHxx
+6467 2902 1 3 7 7 67 467 467 1467 6467 134 135 TOAAAA QHEAAA OOOOxx
+58 2903 0 2 8 18 58 58 58 58 58 116 117 GCAAAA RHEAAA VVVVxx
+5051 2904 1 3 1 11 51 51 1051 51 5051 102 103 HMAAAA SHEAAA AAAAxx
+8901 2905 1 1 1 1 1 901 901 3901 8901 2 3 JEAAAA THEAAA HHHHxx
+1550 2906 0 2 0 10 50 550 1550 1550 1550 100 101 QHAAAA UHEAAA OOOOxx
+1698 2907 0 2 8 18 98 698 1698 1698 1698 196 197 INAAAA VHEAAA VVVVxx
+802 2908 0 2 2 2 2 802 802 802 802 4 5 WEAAAA WHEAAA AAAAxx
+2440 2909 0 0 0 0 40 440 440 2440 2440 80 81 WPAAAA XHEAAA HHHHxx
+2260 2910 0 0 0 0 60 260 260 2260 2260 120 121 YIAAAA YHEAAA OOOOxx
+8218 2911 0 2 8 18 18 218 218 3218 8218 36 37 CEAAAA ZHEAAA VVVVxx
+5144 2912 0 0 4 4 44 144 1144 144 5144 88 89 WPAAAA AIEAAA AAAAxx
+4822 2913 0 2 2 2 22 822 822 4822 4822 44 45 MDAAAA BIEAAA HHHHxx
+9476 2914 0 0 6 16 76 476 1476 4476 9476 152 153 MAAAAA CIEAAA OOOOxx
+7535 2915 1 3 5 15 35 535 1535 2535 7535 70 71 VDAAAA DIEAAA VVVVxx
+8738 2916 0 2 8 18 38 738 738 3738 8738 76 77 CYAAAA EIEAAA AAAAxx
+7946 2917 0 2 6 6 46 946 1946 2946 7946 92 93 QTAAAA FIEAAA HHHHxx
+8143 2918 1 3 3 3 43 143 143 3143 8143 86 87 FBAAAA GIEAAA OOOOxx
+2623 2919 1 3 3 3 23 623 623 2623 2623 46 47 XWAAAA HIEAAA VVVVxx
+5209 2920 1 1 9 9 9 209 1209 209 5209 18 19 JSAAAA IIEAAA AAAAxx
+7674 2921 0 2 4 14 74 674 1674 2674 7674 148 149 EJAAAA JIEAAA HHHHxx
+1135 2922 1 3 5 15 35 135 1135 1135 1135 70 71 RRAAAA KIEAAA OOOOxx
+424 2923 0 0 4 4 24 424 424 424 424 48 49 IQAAAA LIEAAA VVVVxx
+942 2924 0 2 2 2 42 942 942 942 942 84 85 GKAAAA MIEAAA AAAAxx
+7813 2925 1 1 3 13 13 813 1813 2813 7813 26 27 NOAAAA NIEAAA HHHHxx
+3539 2926 1 3 9 19 39 539 1539 3539 3539 78 79 DGAAAA OIEAAA OOOOxx
+2909 2927 1 1 9 9 9 909 909 2909 2909 18 19 XHAAAA PIEAAA VVVVxx
+3748 2928 0 0 8 8 48 748 1748 3748 3748 96 97 EOAAAA QIEAAA AAAAxx
+2996 2929 0 0 6 16 96 996 996 2996 2996 192 193 GLAAAA RIEAAA HHHHxx
+1869 2930 1 1 9 9 69 869 1869 1869 1869 138 139 XTAAAA SIEAAA OOOOxx
+8151 2931 1 3 1 11 51 151 151 3151 8151 102 103 NBAAAA TIEAAA VVVVxx
+6361 2932 1 1 1 1 61 361 361 1361 6361 122 123 RKAAAA UIEAAA AAAAxx
+5568 2933 0 0 8 8 68 568 1568 568 5568 136 137 EGAAAA VIEAAA HHHHxx
+2796 2934 0 0 6 16 96 796 796 2796 2796 192 193 ODAAAA WIEAAA OOOOxx
+8489 2935 1 1 9 9 89 489 489 3489 8489 178 179 NOAAAA XIEAAA VVVVxx
+9183 2936 1 3 3 3 83 183 1183 4183 9183 166 167 FPAAAA YIEAAA AAAAxx
+8227 2937 1 3 7 7 27 227 227 3227 8227 54 55 LEAAAA ZIEAAA HHHHxx
+1844 2938 0 0 4 4 44 844 1844 1844 1844 88 89 YSAAAA AJEAAA OOOOxx
+3975 2939 1 3 5 15 75 975 1975 3975 3975 150 151 XWAAAA BJEAAA VVVVxx
+6490 2940 0 2 0 10 90 490 490 1490 6490 180 181 QPAAAA CJEAAA AAAAxx
+8303 2941 1 3 3 3 3 303 303 3303 8303 6 7 JHAAAA DJEAAA HHHHxx
+7334 2942 0 2 4 14 34 334 1334 2334 7334 68 69 CWAAAA EJEAAA OOOOxx
+2382 2943 0 2 2 2 82 382 382 2382 2382 164 165 QNAAAA FJEAAA VVVVxx
+177 2944 1 1 7 17 77 177 177 177 177 154 155 VGAAAA GJEAAA AAAAxx
+8117 2945 1 1 7 17 17 117 117 3117 8117 34 35 FAAAAA HJEAAA HHHHxx
+5485 2946 1 1 5 5 85 485 1485 485 5485 170 171 ZCAAAA IJEAAA OOOOxx
+6544 2947 0 0 4 4 44 544 544 1544 6544 88 89 SRAAAA JJEAAA VVVVxx
+8517 2948 1 1 7 17 17 517 517 3517 8517 34 35 PPAAAA KJEAAA AAAAxx
+2252 2949 0 0 2 12 52 252 252 2252 2252 104 105 QIAAAA LJEAAA HHHHxx
+4480 2950 0 0 0 0 80 480 480 4480 4480 160 161 IQAAAA MJEAAA OOOOxx
+4785 2951 1 1 5 5 85 785 785 4785 4785 170 171 BCAAAA NJEAAA VVVVxx
+9700 2952 0 0 0 0 0 700 1700 4700 9700 0 1 CJAAAA OJEAAA AAAAxx
+2122 2953 0 2 2 2 22 122 122 2122 2122 44 45 QDAAAA PJEAAA HHHHxx
+8783 2954 1 3 3 3 83 783 783 3783 8783 166 167 VZAAAA QJEAAA OOOOxx
+1453 2955 1 1 3 13 53 453 1453 1453 1453 106 107 XDAAAA RJEAAA VVVVxx
+3908 2956 0 0 8 8 8 908 1908 3908 3908 16 17 IUAAAA SJEAAA AAAAxx
+7707 2957 1 3 7 7 7 707 1707 2707 7707 14 15 LKAAAA TJEAAA HHHHxx
+9049 2958 1 1 9 9 49 49 1049 4049 9049 98 99 BKAAAA UJEAAA OOOOxx
+654 2959 0 2 4 14 54 654 654 654 654 108 109 EZAAAA VJEAAA VVVVxx
+3336 2960 0 0 6 16 36 336 1336 3336 3336 72 73 IYAAAA WJEAAA AAAAxx
+622 2961 0 2 2 2 22 622 622 622 622 44 45 YXAAAA XJEAAA HHHHxx
+8398 2962 0 2 8 18 98 398 398 3398 8398 196 197 ALAAAA YJEAAA OOOOxx
+9193 2963 1 1 3 13 93 193 1193 4193 9193 186 187 PPAAAA ZJEAAA VVVVxx
+7896 2964 0 0 6 16 96 896 1896 2896 7896 192 193 SRAAAA AKEAAA AAAAxx
+9798 2965 0 2 8 18 98 798 1798 4798 9798 196 197 WMAAAA BKEAAA HHHHxx
+2881 2966 1 1 1 1 81 881 881 2881 2881 162 163 VGAAAA CKEAAA OOOOxx
+672 2967 0 0 2 12 72 672 672 672 672 144 145 WZAAAA DKEAAA VVVVxx
+6743 2968 1 3 3 3 43 743 743 1743 6743 86 87 JZAAAA EKEAAA AAAAxx
+8935 2969 1 3 5 15 35 935 935 3935 8935 70 71 RFAAAA FKEAAA HHHHxx
+2426 2970 0 2 6 6 26 426 426 2426 2426 52 53 IPAAAA GKEAAA OOOOxx
+722 2971 0 2 2 2 22 722 722 722 722 44 45 UBAAAA HKEAAA VVVVxx
+5088 2972 0 0 8 8 88 88 1088 88 5088 176 177 SNAAAA IKEAAA AAAAxx
+8677 2973 1 1 7 17 77 677 677 3677 8677 154 155 TVAAAA JKEAAA HHHHxx
+6963 2974 1 3 3 3 63 963 963 1963 6963 126 127 VHAAAA KKEAAA OOOOxx
+1653 2975 1 1 3 13 53 653 1653 1653 1653 106 107 PLAAAA LKEAAA VVVVxx
+7295 2976 1 3 5 15 95 295 1295 2295 7295 190 191 PUAAAA MKEAAA AAAAxx
+6675 2977 1 3 5 15 75 675 675 1675 6675 150 151 TWAAAA NKEAAA HHHHxx
+7183 2978 1 3 3 3 83 183 1183 2183 7183 166 167 HQAAAA OKEAAA OOOOxx
+4378 2979 0 2 8 18 78 378 378 4378 4378 156 157 KMAAAA PKEAAA VVVVxx
+2157 2980 1 1 7 17 57 157 157 2157 2157 114 115 ZEAAAA QKEAAA AAAAxx
+2621 2981 1 1 1 1 21 621 621 2621 2621 42 43 VWAAAA RKEAAA HHHHxx
+9278 2982 0 2 8 18 78 278 1278 4278 9278 156 157 WSAAAA SKEAAA OOOOxx
+79 2983 1 3 9 19 79 79 79 79 79 158 159 BDAAAA TKEAAA VVVVxx
+7358 2984 0 2 8 18 58 358 1358 2358 7358 116 117 AXAAAA UKEAAA AAAAxx
+3589 2985 1 1 9 9 89 589 1589 3589 3589 178 179 BIAAAA VKEAAA HHHHxx
+1254 2986 0 2 4 14 54 254 1254 1254 1254 108 109 GWAAAA WKEAAA OOOOxx
+3490 2987 0 2 0 10 90 490 1490 3490 3490 180 181 GEAAAA XKEAAA VVVVxx
+7533 2988 1 1 3 13 33 533 1533 2533 7533 66 67 TDAAAA YKEAAA AAAAxx
+2800 2989 0 0 0 0 0 800 800 2800 2800 0 1 SDAAAA ZKEAAA HHHHxx
+351 2990 1 3 1 11 51 351 351 351 351 102 103 NNAAAA ALEAAA OOOOxx
+4359 2991 1 3 9 19 59 359 359 4359 4359 118 119 RLAAAA BLEAAA VVVVxx
+5788 2992 0 0 8 8 88 788 1788 788 5788 176 177 QOAAAA CLEAAA AAAAxx
+5521 2993 1 1 1 1 21 521 1521 521 5521 42 43 JEAAAA DLEAAA HHHHxx
+3351 2994 1 3 1 11 51 351 1351 3351 3351 102 103 XYAAAA ELEAAA OOOOxx
+5129 2995 1 1 9 9 29 129 1129 129 5129 58 59 HPAAAA FLEAAA VVVVxx
+315 2996 1 3 5 15 15 315 315 315 315 30 31 DMAAAA GLEAAA AAAAxx
+7552 2997 0 0 2 12 52 552 1552 2552 7552 104 105 MEAAAA HLEAAA HHHHxx
+9176 2998 0 0 6 16 76 176 1176 4176 9176 152 153 YOAAAA ILEAAA OOOOxx
+7458 2999 0 2 8 18 58 458 1458 2458 7458 116 117 WAAAAA JLEAAA VVVVxx
+279 3000 1 3 9 19 79 279 279 279 279 158 159 TKAAAA KLEAAA AAAAxx
+738 3001 0 2 8 18 38 738 738 738 738 76 77 KCAAAA LLEAAA HHHHxx
+2557 3002 1 1 7 17 57 557 557 2557 2557 114 115 JUAAAA MLEAAA OOOOxx
+9395 3003 1 3 5 15 95 395 1395 4395 9395 190 191 JXAAAA NLEAAA VVVVxx
+7214 3004 0 2 4 14 14 214 1214 2214 7214 28 29 MRAAAA OLEAAA AAAAxx
+6354 3005 0 2 4 14 54 354 354 1354 6354 108 109 KKAAAA PLEAAA HHHHxx
+4799 3006 1 3 9 19 99 799 799 4799 4799 198 199 PCAAAA QLEAAA OOOOxx
+1231 3007 1 3 1 11 31 231 1231 1231 1231 62 63 JVAAAA RLEAAA VVVVxx
+5252 3008 0 0 2 12 52 252 1252 252 5252 104 105 AUAAAA SLEAAA AAAAxx
+5250 3009 0 2 0 10 50 250 1250 250 5250 100 101 YTAAAA TLEAAA HHHHxx
+9319 3010 1 3 9 19 19 319 1319 4319 9319 38 39 LUAAAA ULEAAA OOOOxx
+1724 3011 0 0 4 4 24 724 1724 1724 1724 48 49 IOAAAA VLEAAA VVVVxx
+7947 3012 1 3 7 7 47 947 1947 2947 7947 94 95 RTAAAA WLEAAA AAAAxx
+1105 3013 1 1 5 5 5 105 1105 1105 1105 10 11 NQAAAA XLEAAA HHHHxx
+1417 3014 1 1 7 17 17 417 1417 1417 1417 34 35 NCAAAA YLEAAA OOOOxx
+7101 3015 1 1 1 1 1 101 1101 2101 7101 2 3 DNAAAA ZLEAAA VVVVxx
+1088 3016 0 0 8 8 88 88 1088 1088 1088 176 177 WPAAAA AMEAAA AAAAxx
+979 3017 1 3 9 19 79 979 979 979 979 158 159 RLAAAA BMEAAA HHHHxx
+7589 3018 1 1 9 9 89 589 1589 2589 7589 178 179 XFAAAA CMEAAA OOOOxx
+8952 3019 0 0 2 12 52 952 952 3952 8952 104 105 IGAAAA DMEAAA VVVVxx
+2864 3020 0 0 4 4 64 864 864 2864 2864 128 129 EGAAAA EMEAAA AAAAxx
+234 3021 0 2 4 14 34 234 234 234 234 68 69 AJAAAA FMEAAA HHHHxx
+7231 3022 1 3 1 11 31 231 1231 2231 7231 62 63 DSAAAA GMEAAA OOOOxx
+6792 3023 0 0 2 12 92 792 792 1792 6792 184 185 GBAAAA HMEAAA VVVVxx
+4311 3024 1 3 1 11 11 311 311 4311 4311 22 23 VJAAAA IMEAAA AAAAxx
+3374 3025 0 2 4 14 74 374 1374 3374 3374 148 149 UZAAAA JMEAAA HHHHxx
+3367 3026 1 3 7 7 67 367 1367 3367 3367 134 135 NZAAAA KMEAAA OOOOxx
+2598 3027 0 2 8 18 98 598 598 2598 2598 196 197 YVAAAA LMEAAA VVVVxx
+1033 3028 1 1 3 13 33 33 1033 1033 1033 66 67 TNAAAA MMEAAA AAAAxx
+7803 3029 1 3 3 3 3 803 1803 2803 7803 6 7 DOAAAA NMEAAA HHHHxx
+3870 3030 0 2 0 10 70 870 1870 3870 3870 140 141 WSAAAA OMEAAA OOOOxx
+4962 3031 0 2 2 2 62 962 962 4962 4962 124 125 WIAAAA PMEAAA VVVVxx
+4842 3032 0 2 2 2 42 842 842 4842 4842 84 85 GEAAAA QMEAAA AAAAxx
+8814 3033 0 2 4 14 14 814 814 3814 8814 28 29 ABAAAA RMEAAA HHHHxx
+3429 3034 1 1 9 9 29 429 1429 3429 3429 58 59 XBAAAA SMEAAA OOOOxx
+6550 3035 0 2 0 10 50 550 550 1550 6550 100 101 YRAAAA TMEAAA VVVVxx
+6317 3036 1 1 7 17 17 317 317 1317 6317 34 35 ZIAAAA UMEAAA AAAAxx
+5023 3037 1 3 3 3 23 23 1023 23 5023 46 47 FLAAAA VMEAAA HHHHxx
+5825 3038 1 1 5 5 25 825 1825 825 5825 50 51 BQAAAA WMEAAA OOOOxx
+5297 3039 1 1 7 17 97 297 1297 297 5297 194 195 TVAAAA XMEAAA VVVVxx
+8764 3040 0 0 4 4 64 764 764 3764 8764 128 129 CZAAAA YMEAAA AAAAxx
+5084 3041 0 0 4 4 84 84 1084 84 5084 168 169 ONAAAA ZMEAAA HHHHxx
+6808 3042 0 0 8 8 8 808 808 1808 6808 16 17 WBAAAA ANEAAA OOOOxx
+1780 3043 0 0 0 0 80 780 1780 1780 1780 160 161 MQAAAA BNEAAA VVVVxx
+4092 3044 0 0 2 12 92 92 92 4092 4092 184 185 KBAAAA CNEAAA AAAAxx
+3618 3045 0 2 8 18 18 618 1618 3618 3618 36 37 EJAAAA DNEAAA HHHHxx
+7299 3046 1 3 9 19 99 299 1299 2299 7299 198 199 TUAAAA ENEAAA OOOOxx
+8544 3047 0 0 4 4 44 544 544 3544 8544 88 89 QQAAAA FNEAAA VVVVxx
+2359 3048 1 3 9 19 59 359 359 2359 2359 118 119 TMAAAA GNEAAA AAAAxx
+1939 3049 1 3 9 19 39 939 1939 1939 1939 78 79 PWAAAA HNEAAA HHHHxx
+5834 3050 0 2 4 14 34 834 1834 834 5834 68 69 KQAAAA INEAAA OOOOxx
+1997 3051 1 1 7 17 97 997 1997 1997 1997 194 195 VYAAAA JNEAAA VVVVxx
+7917 3052 1 1 7 17 17 917 1917 2917 7917 34 35 NSAAAA KNEAAA AAAAxx
+2098 3053 0 2 8 18 98 98 98 2098 2098 196 197 SCAAAA LNEAAA HHHHxx
+7576 3054 0 0 6 16 76 576 1576 2576 7576 152 153 KFAAAA MNEAAA OOOOxx
+376 3055 0 0 6 16 76 376 376 376 376 152 153 MOAAAA NNEAAA VVVVxx
+8535 3056 1 3 5 15 35 535 535 3535 8535 70 71 HQAAAA ONEAAA AAAAxx
+5659 3057 1 3 9 19 59 659 1659 659 5659 118 119 RJAAAA PNEAAA HHHHxx
+2786 3058 0 2 6 6 86 786 786 2786 2786 172 173 EDAAAA QNEAAA OOOOxx
+8820 3059 0 0 0 0 20 820 820 3820 8820 40 41 GBAAAA RNEAAA VVVVxx
+1229 3060 1 1 9 9 29 229 1229 1229 1229 58 59 HVAAAA SNEAAA AAAAxx
+9321 3061 1 1 1 1 21 321 1321 4321 9321 42 43 NUAAAA TNEAAA HHHHxx
+7662 3062 0 2 2 2 62 662 1662 2662 7662 124 125 SIAAAA UNEAAA OOOOxx
+5535 3063 1 3 5 15 35 535 1535 535 5535 70 71 XEAAAA VNEAAA VVVVxx
+4889 3064 1 1 9 9 89 889 889 4889 4889 178 179 BGAAAA WNEAAA AAAAxx
+8259 3065 1 3 9 19 59 259 259 3259 8259 118 119 RFAAAA XNEAAA HHHHxx
+6789 3066 1 1 9 9 89 789 789 1789 6789 178 179 DBAAAA YNEAAA OOOOxx
+5411 3067 1 3 1 11 11 411 1411 411 5411 22 23 DAAAAA ZNEAAA VVVVxx
+6992 3068 0 0 2 12 92 992 992 1992 6992 184 185 YIAAAA AOEAAA AAAAxx
+7698 3069 0 2 8 18 98 698 1698 2698 7698 196 197 CKAAAA BOEAAA HHHHxx
+2342 3070 0 2 2 2 42 342 342 2342 2342 84 85 CMAAAA COEAAA OOOOxx
+1501 3071 1 1 1 1 1 501 1501 1501 1501 2 3 TFAAAA DOEAAA VVVVxx
+6322 3072 0 2 2 2 22 322 322 1322 6322 44 45 EJAAAA EOEAAA AAAAxx
+9861 3073 1 1 1 1 61 861 1861 4861 9861 122 123 HPAAAA FOEAAA HHHHxx
+9802 3074 0 2 2 2 2 802 1802 4802 9802 4 5 ANAAAA GOEAAA OOOOxx
+4750 3075 0 2 0 10 50 750 750 4750 4750 100 101 SAAAAA HOEAAA VVVVxx
+5855 3076 1 3 5 15 55 855 1855 855 5855 110 111 FRAAAA IOEAAA AAAAxx
+4304 3077 0 0 4 4 4 304 304 4304 4304 8 9 OJAAAA JOEAAA HHHHxx
+2605 3078 1 1 5 5 5 605 605 2605 2605 10 11 FWAAAA KOEAAA OOOOxx
+1802 3079 0 2 2 2 2 802 1802 1802 1802 4 5 IRAAAA LOEAAA VVVVxx
+9368 3080 0 0 8 8 68 368 1368 4368 9368 136 137 IWAAAA MOEAAA AAAAxx
+7107 3081 1 3 7 7 7 107 1107 2107 7107 14 15 JNAAAA NOEAAA HHHHxx
+8895 3082 1 3 5 15 95 895 895 3895 8895 190 191 DEAAAA OOEAAA OOOOxx
+3750 3083 0 2 0 10 50 750 1750 3750 3750 100 101 GOAAAA POEAAA VVVVxx
+8934 3084 0 2 4 14 34 934 934 3934 8934 68 69 QFAAAA QOEAAA AAAAxx
+9464 3085 0 0 4 4 64 464 1464 4464 9464 128 129 AAAAAA ROEAAA HHHHxx
+1928 3086 0 0 8 8 28 928 1928 1928 1928 56 57 EWAAAA SOEAAA OOOOxx
+3196 3087 0 0 6 16 96 196 1196 3196 3196 192 193 YSAAAA TOEAAA VVVVxx
+5256 3088 0 0 6 16 56 256 1256 256 5256 112 113 EUAAAA UOEAAA AAAAxx
+7119 3089 1 3 9 19 19 119 1119 2119 7119 38 39 VNAAAA VOEAAA HHHHxx
+4495 3090 1 3 5 15 95 495 495 4495 4495 190 191 XQAAAA WOEAAA OOOOxx
+9292 3091 0 0 2 12 92 292 1292 4292 9292 184 185 KTAAAA XOEAAA VVVVxx
+1617 3092 1 1 7 17 17 617 1617 1617 1617 34 35 FKAAAA YOEAAA AAAAxx
+481 3093 1 1 1 1 81 481 481 481 481 162 163 NSAAAA ZOEAAA HHHHxx
+56 3094 0 0 6 16 56 56 56 56 56 112 113 ECAAAA APEAAA OOOOxx
+9120 3095 0 0 0 0 20 120 1120 4120 9120 40 41 UMAAAA BPEAAA VVVVxx
+1306 3096 0 2 6 6 6 306 1306 1306 1306 12 13 GYAAAA CPEAAA AAAAxx
+7773 3097 1 1 3 13 73 773 1773 2773 7773 146 147 ZMAAAA DPEAAA HHHHxx
+4863 3098 1 3 3 3 63 863 863 4863 4863 126 127 BFAAAA EPEAAA OOOOxx
+1114 3099 0 2 4 14 14 114 1114 1114 1114 28 29 WQAAAA FPEAAA VVVVxx
+8124 3100 0 0 4 4 24 124 124 3124 8124 48 49 MAAAAA GPEAAA AAAAxx
+6254 3101 0 2 4 14 54 254 254 1254 6254 108 109 OGAAAA HPEAAA HHHHxx
+8109 3102 1 1 9 9 9 109 109 3109 8109 18 19 XZAAAA IPEAAA OOOOxx
+1747 3103 1 3 7 7 47 747 1747 1747 1747 94 95 FPAAAA JPEAAA VVVVxx
+6185 3104 1 1 5 5 85 185 185 1185 6185 170 171 XDAAAA KPEAAA AAAAxx
+3388 3105 0 0 8 8 88 388 1388 3388 3388 176 177 IAAAAA LPEAAA HHHHxx
+4905 3106 1 1 5 5 5 905 905 4905 4905 10 11 RGAAAA MPEAAA OOOOxx
+5728 3107 0 0 8 8 28 728 1728 728 5728 56 57 IMAAAA NPEAAA VVVVxx
+7507 3108 1 3 7 7 7 507 1507 2507 7507 14 15 TCAAAA OPEAAA AAAAxx
+5662 3109 0 2 2 2 62 662 1662 662 5662 124 125 UJAAAA PPEAAA HHHHxx
+1686 3110 0 2 6 6 86 686 1686 1686 1686 172 173 WMAAAA QPEAAA OOOOxx
+5202 3111 0 2 2 2 2 202 1202 202 5202 4 5 CSAAAA RPEAAA VVVVxx
+6905 3112 1 1 5 5 5 905 905 1905 6905 10 11 PFAAAA SPEAAA AAAAxx
+9577 3113 1 1 7 17 77 577 1577 4577 9577 154 155 JEAAAA TPEAAA HHHHxx
+7194 3114 0 2 4 14 94 194 1194 2194 7194 188 189 SQAAAA UPEAAA OOOOxx
+7016 3115 0 0 6 16 16 16 1016 2016 7016 32 33 WJAAAA VPEAAA VVVVxx
+8905 3116 1 1 5 5 5 905 905 3905 8905 10 11 NEAAAA WPEAAA AAAAxx
+3419 3117 1 3 9 19 19 419 1419 3419 3419 38 39 NBAAAA XPEAAA HHHHxx
+6881 3118 1 1 1 1 81 881 881 1881 6881 162 163 REAAAA YPEAAA OOOOxx
+8370 3119 0 2 0 10 70 370 370 3370 8370 140 141 YJAAAA ZPEAAA VVVVxx
+6117 3120 1 1 7 17 17 117 117 1117 6117 34 35 HBAAAA AQEAAA AAAAxx
+1636 3121 0 0 6 16 36 636 1636 1636 1636 72 73 YKAAAA BQEAAA HHHHxx
+6857 3122 1 1 7 17 57 857 857 1857 6857 114 115 TDAAAA CQEAAA OOOOxx
+7163 3123 1 3 3 3 63 163 1163 2163 7163 126 127 NPAAAA DQEAAA VVVVxx
+5040 3124 0 0 0 0 40 40 1040 40 5040 80 81 WLAAAA EQEAAA AAAAxx
+6263 3125 1 3 3 3 63 263 263 1263 6263 126 127 XGAAAA FQEAAA HHHHxx
+4809 3126 1 1 9 9 9 809 809 4809 4809 18 19 ZCAAAA GQEAAA OOOOxx
+900 3127 0 0 0 0 0 900 900 900 900 0 1 QIAAAA HQEAAA VVVVxx
+3199 3128 1 3 9 19 99 199 1199 3199 3199 198 199 BTAAAA IQEAAA AAAAxx
+4156 3129 0 0 6 16 56 156 156 4156 4156 112 113 WDAAAA JQEAAA HHHHxx
+3501 3130 1 1 1 1 1 501 1501 3501 3501 2 3 REAAAA KQEAAA OOOOxx
+164 3131 0 0 4 4 64 164 164 164 164 128 129 IGAAAA LQEAAA VVVVxx
+9548 3132 0 0 8 8 48 548 1548 4548 9548 96 97 GDAAAA MQEAAA AAAAxx
+1149 3133 1 1 9 9 49 149 1149 1149 1149 98 99 FSAAAA NQEAAA HHHHxx
+1962 3134 0 2 2 2 62 962 1962 1962 1962 124 125 MXAAAA OQEAAA OOOOxx
+4072 3135 0 0 2 12 72 72 72 4072 4072 144 145 QAAAAA PQEAAA VVVVxx
+4280 3136 0 0 0 0 80 280 280 4280 4280 160 161 QIAAAA QQEAAA AAAAxx
+1398 3137 0 2 8 18 98 398 1398 1398 1398 196 197 UBAAAA RQEAAA HHHHxx
+725 3138 1 1 5 5 25 725 725 725 725 50 51 XBAAAA SQEAAA OOOOxx
+3988 3139 0 0 8 8 88 988 1988 3988 3988 176 177 KXAAAA TQEAAA VVVVxx
+5059 3140 1 3 9 19 59 59 1059 59 5059 118 119 PMAAAA UQEAAA AAAAxx
+2632 3141 0 0 2 12 32 632 632 2632 2632 64 65 GXAAAA VQEAAA HHHHxx
+1909 3142 1 1 9 9 9 909 1909 1909 1909 18 19 LVAAAA WQEAAA OOOOxx
+6827 3143 1 3 7 7 27 827 827 1827 6827 54 55 PCAAAA XQEAAA VVVVxx
+8156 3144 0 0 6 16 56 156 156 3156 8156 112 113 SBAAAA YQEAAA AAAAxx
+1192 3145 0 0 2 12 92 192 1192 1192 1192 184 185 WTAAAA ZQEAAA HHHHxx
+9545 3146 1 1 5 5 45 545 1545 4545 9545 90 91 DDAAAA AREAAA OOOOxx
+2249 3147 1 1 9 9 49 249 249 2249 2249 98 99 NIAAAA BREAAA VVVVxx
+5580 3148 0 0 0 0 80 580 1580 580 5580 160 161 QGAAAA CREAAA AAAAxx
+8403 3149 1 3 3 3 3 403 403 3403 8403 6 7 FLAAAA DREAAA HHHHxx
+4024 3150 0 0 4 4 24 24 24 4024 4024 48 49 UYAAAA EREAAA OOOOxx
+1866 3151 0 2 6 6 66 866 1866 1866 1866 132 133 UTAAAA FREAAA VVVVxx
+9251 3152 1 3 1 11 51 251 1251 4251 9251 102 103 VRAAAA GREAAA AAAAxx
+9979 3153 1 3 9 19 79 979 1979 4979 9979 158 159 VTAAAA HREAAA HHHHxx
+9899 3154 1 3 9 19 99 899 1899 4899 9899 198 199 TQAAAA IREAAA OOOOxx
+2540 3155 0 0 0 0 40 540 540 2540 2540 80 81 STAAAA JREAAA VVVVxx
+8957 3156 1 1 7 17 57 957 957 3957 8957 114 115 NGAAAA KREAAA AAAAxx
+7702 3157 0 2 2 2 2 702 1702 2702 7702 4 5 GKAAAA LREAAA HHHHxx
+4211 3158 1 3 1 11 11 211 211 4211 4211 22 23 ZFAAAA MREAAA OOOOxx
+6684 3159 0 0 4 4 84 684 684 1684 6684 168 169 CXAAAA NREAAA VVVVxx
+3883 3160 1 3 3 3 83 883 1883 3883 3883 166 167 JTAAAA OREAAA AAAAxx
+3531 3161 1 3 1 11 31 531 1531 3531 3531 62 63 VFAAAA PREAAA HHHHxx
+9178 3162 0 2 8 18 78 178 1178 4178 9178 156 157 APAAAA QREAAA OOOOxx
+3389 3163 1 1 9 9 89 389 1389 3389 3389 178 179 JAAAAA RREAAA VVVVxx
+7874 3164 0 2 4 14 74 874 1874 2874 7874 148 149 WQAAAA SREAAA AAAAxx
+4522 3165 0 2 2 2 22 522 522 4522 4522 44 45 YRAAAA TREAAA HHHHxx
+9399 3166 1 3 9 19 99 399 1399 4399 9399 198 199 NXAAAA UREAAA OOOOxx
+9083 3167 1 3 3 3 83 83 1083 4083 9083 166 167 JLAAAA VREAAA VVVVxx
+1530 3168 0 2 0 10 30 530 1530 1530 1530 60 61 WGAAAA WREAAA AAAAxx
+2360 3169 0 0 0 0 60 360 360 2360 2360 120 121 UMAAAA XREAAA HHHHxx
+4908 3170 0 0 8 8 8 908 908 4908 4908 16 17 UGAAAA YREAAA OOOOxx
+4628 3171 0 0 8 8 28 628 628 4628 4628 56 57 AWAAAA ZREAAA VVVVxx
+3889 3172 1 1 9 9 89 889 1889 3889 3889 178 179 PTAAAA ASEAAA AAAAxx
+1331 3173 1 3 1 11 31 331 1331 1331 1331 62 63 FZAAAA BSEAAA HHHHxx
+1942 3174 0 2 2 2 42 942 1942 1942 1942 84 85 SWAAAA CSEAAA OOOOxx
+4734 3175 0 2 4 14 34 734 734 4734 4734 68 69 CAAAAA DSEAAA VVVVxx
+8386 3176 0 2 6 6 86 386 386 3386 8386 172 173 OKAAAA ESEAAA AAAAxx
+3586 3177 0 2 6 6 86 586 1586 3586 3586 172 173 YHAAAA FSEAAA HHHHxx
+2354 3178 0 2 4 14 54 354 354 2354 2354 108 109 OMAAAA GSEAAA OOOOxx
+7108 3179 0 0 8 8 8 108 1108 2108 7108 16 17 KNAAAA HSEAAA VVVVxx
+1857 3180 1 1 7 17 57 857 1857 1857 1857 114 115 LTAAAA ISEAAA AAAAxx
+2544 3181 0 0 4 4 44 544 544 2544 2544 88 89 WTAAAA JSEAAA HHHHxx
+819 3182 1 3 9 19 19 819 819 819 819 38 39 NFAAAA KSEAAA OOOOxx
+2878 3183 0 2 8 18 78 878 878 2878 2878 156 157 SGAAAA LSEAAA VVVVxx
+1772 3184 0 0 2 12 72 772 1772 1772 1772 144 145 EQAAAA MSEAAA AAAAxx
+354 3185 0 2 4 14 54 354 354 354 354 108 109 QNAAAA NSEAAA HHHHxx
+3259 3186 1 3 9 19 59 259 1259 3259 3259 118 119 JVAAAA OSEAAA OOOOxx
+2170 3187 0 2 0 10 70 170 170 2170 2170 140 141 MFAAAA PSEAAA VVVVxx
+1190 3188 0 2 0 10 90 190 1190 1190 1190 180 181 UTAAAA QSEAAA AAAAxx
+3607 3189 1 3 7 7 7 607 1607 3607 3607 14 15 TIAAAA RSEAAA HHHHxx
+4661 3190 1 1 1 1 61 661 661 4661 4661 122 123 HXAAAA SSEAAA OOOOxx
+1796 3191 0 0 6 16 96 796 1796 1796 1796 192 193 CRAAAA TSEAAA VVVVxx
+1561 3192 1 1 1 1 61 561 1561 1561 1561 122 123 BIAAAA USEAAA AAAAxx
+4336 3193 0 0 6 16 36 336 336 4336 4336 72 73 UKAAAA VSEAAA HHHHxx
+7550 3194 0 2 0 10 50 550 1550 2550 7550 100 101 KEAAAA WSEAAA OOOOxx
+3238 3195 0 2 8 18 38 238 1238 3238 3238 76 77 OUAAAA XSEAAA VVVVxx
+9870 3196 0 2 0 10 70 870 1870 4870 9870 140 141 QPAAAA YSEAAA AAAAxx
+6502 3197 0 2 2 2 2 502 502 1502 6502 4 5 CQAAAA ZSEAAA HHHHxx
+3903 3198 1 3 3 3 3 903 1903 3903 3903 6 7 DUAAAA ATEAAA OOOOxx
+2869 3199 1 1 9 9 69 869 869 2869 2869 138 139 JGAAAA BTEAAA VVVVxx
+5072 3200 0 0 2 12 72 72 1072 72 5072 144 145 CNAAAA CTEAAA AAAAxx
+1201 3201 1 1 1 1 1 201 1201 1201 1201 2 3 FUAAAA DTEAAA HHHHxx
+6245 3202 1 1 5 5 45 245 245 1245 6245 90 91 FGAAAA ETEAAA OOOOxx
+1402 3203 0 2 2 2 2 402 1402 1402 1402 4 5 YBAAAA FTEAAA VVVVxx
+2594 3204 0 2 4 14 94 594 594 2594 2594 188 189 UVAAAA GTEAAA AAAAxx
+9171 3205 1 3 1 11 71 171 1171 4171 9171 142 143 TOAAAA HTEAAA HHHHxx
+2620 3206 0 0 0 0 20 620 620 2620 2620 40 41 UWAAAA ITEAAA OOOOxx
+6309 3207 1 1 9 9 9 309 309 1309 6309 18 19 RIAAAA JTEAAA VVVVxx
+1285 3208 1 1 5 5 85 285 1285 1285 1285 170 171 LXAAAA KTEAAA AAAAxx
+5466 3209 0 2 6 6 66 466 1466 466 5466 132 133 GCAAAA LTEAAA HHHHxx
+168 3210 0 0 8 8 68 168 168 168 168 136 137 MGAAAA MTEAAA OOOOxx
+1410 3211 0 2 0 10 10 410 1410 1410 1410 20 21 GCAAAA NTEAAA VVVVxx
+6332 3212 0 0 2 12 32 332 332 1332 6332 64 65 OJAAAA OTEAAA AAAAxx
+9530 3213 0 2 0 10 30 530 1530 4530 9530 60 61 OCAAAA PTEAAA HHHHxx
+7749 3214 1 1 9 9 49 749 1749 2749 7749 98 99 BMAAAA QTEAAA OOOOxx
+3656 3215 0 0 6 16 56 656 1656 3656 3656 112 113 QKAAAA RTEAAA VVVVxx
+37 3216 1 1 7 17 37 37 37 37 37 74 75 LBAAAA STEAAA AAAAxx
+2744 3217 0 0 4 4 44 744 744 2744 2744 88 89 OBAAAA TTEAAA HHHHxx
+4206 3218 0 2 6 6 6 206 206 4206 4206 12 13 UFAAAA UTEAAA OOOOxx
+1846 3219 0 2 6 6 46 846 1846 1846 1846 92 93 ATAAAA VTEAAA VVVVxx
+9913 3220 1 1 3 13 13 913 1913 4913 9913 26 27 HRAAAA WTEAAA AAAAxx
+4078 3221 0 2 8 18 78 78 78 4078 4078 156 157 WAAAAA XTEAAA HHHHxx
+2080 3222 0 0 0 0 80 80 80 2080 2080 160 161 ACAAAA YTEAAA OOOOxx
+4169 3223 1 1 9 9 69 169 169 4169 4169 138 139 JEAAAA ZTEAAA VVVVxx
+2070 3224 0 2 0 10 70 70 70 2070 2070 140 141 QBAAAA AUEAAA AAAAxx
+4500 3225 0 0 0 0 0 500 500 4500 4500 0 1 CRAAAA BUEAAA HHHHxx
+4123 3226 1 3 3 3 23 123 123 4123 4123 46 47 PCAAAA CUEAAA OOOOxx
+5594 3227 0 2 4 14 94 594 1594 594 5594 188 189 EHAAAA DUEAAA VVVVxx
+9941 3228 1 1 1 1 41 941 1941 4941 9941 82 83 JSAAAA EUEAAA AAAAxx
+7154 3229 0 2 4 14 54 154 1154 2154 7154 108 109 EPAAAA FUEAAA HHHHxx
+8340 3230 0 0 0 0 40 340 340 3340 8340 80 81 UIAAAA GUEAAA OOOOxx
+7110 3231 0 2 0 10 10 110 1110 2110 7110 20 21 MNAAAA HUEAAA VVVVxx
+7795 3232 1 3 5 15 95 795 1795 2795 7795 190 191 VNAAAA IUEAAA AAAAxx
+132 3233 0 0 2 12 32 132 132 132 132 64 65 CFAAAA JUEAAA HHHHxx
+4603 3234 1 3 3 3 3 603 603 4603 4603 6 7 BVAAAA KUEAAA OOOOxx
+9720 3235 0 0 0 0 20 720 1720 4720 9720 40 41 WJAAAA LUEAAA VVVVxx
+1460 3236 0 0 0 0 60 460 1460 1460 1460 120 121 EEAAAA MUEAAA AAAAxx
+4677 3237 1 1 7 17 77 677 677 4677 4677 154 155 XXAAAA NUEAAA HHHHxx
+9272 3238 0 0 2 12 72 272 1272 4272 9272 144 145 QSAAAA OUEAAA OOOOxx
+2279 3239 1 3 9 19 79 279 279 2279 2279 158 159 RJAAAA PUEAAA VVVVxx
+4587 3240 1 3 7 7 87 587 587 4587 4587 174 175 LUAAAA QUEAAA AAAAxx
+2244 3241 0 0 4 4 44 244 244 2244 2244 88 89 IIAAAA RUEAAA HHHHxx
+742 3242 0 2 2 2 42 742 742 742 742 84 85 OCAAAA SUEAAA OOOOxx
+4426 3243 0 2 6 6 26 426 426 4426 4426 52 53 GOAAAA TUEAAA VVVVxx
+4571 3244 1 3 1 11 71 571 571 4571 4571 142 143 VTAAAA UUEAAA AAAAxx
+4775 3245 1 3 5 15 75 775 775 4775 4775 150 151 RBAAAA VUEAAA HHHHxx
+24 3246 0 0 4 4 24 24 24 24 24 48 49 YAAAAA WUEAAA OOOOxx
+4175 3247 1 3 5 15 75 175 175 4175 4175 150 151 PEAAAA XUEAAA VVVVxx
+9877 3248 1 1 7 17 77 877 1877 4877 9877 154 155 XPAAAA YUEAAA AAAAxx
+7271 3249 1 3 1 11 71 271 1271 2271 7271 142 143 RTAAAA ZUEAAA HHHHxx
+5468 3250 0 0 8 8 68 468 1468 468 5468 136 137 ICAAAA AVEAAA OOOOxx
+6106 3251 0 2 6 6 6 106 106 1106 6106 12 13 WAAAAA BVEAAA VVVVxx
+9005 3252 1 1 5 5 5 5 1005 4005 9005 10 11 JIAAAA CVEAAA AAAAxx
+109 3253 1 1 9 9 9 109 109 109 109 18 19 FEAAAA DVEAAA HHHHxx
+6365 3254 1 1 5 5 65 365 365 1365 6365 130 131 VKAAAA EVEAAA OOOOxx
+7437 3255 1 1 7 17 37 437 1437 2437 7437 74 75 BAAAAA FVEAAA VVVVxx
+7979 3256 1 3 9 19 79 979 1979 2979 7979 158 159 XUAAAA GVEAAA AAAAxx
+6050 3257 0 2 0 10 50 50 50 1050 6050 100 101 SYAAAA HVEAAA HHHHxx
+2853 3258 1 1 3 13 53 853 853 2853 2853 106 107 TFAAAA IVEAAA OOOOxx
+7603 3259 1 3 3 3 3 603 1603 2603 7603 6 7 LGAAAA JVEAAA VVVVxx
+483 3260 1 3 3 3 83 483 483 483 483 166 167 PSAAAA KVEAAA AAAAxx
+5994 3261 0 2 4 14 94 994 1994 994 5994 188 189 OWAAAA LVEAAA HHHHxx
+6708 3262 0 0 8 8 8 708 708 1708 6708 16 17 AYAAAA MVEAAA OOOOxx
+5090 3263 0 2 0 10 90 90 1090 90 5090 180 181 UNAAAA NVEAAA VVVVxx
+4608 3264 0 0 8 8 8 608 608 4608 4608 16 17 GVAAAA OVEAAA AAAAxx
+4551 3265 1 3 1 11 51 551 551 4551 4551 102 103 BTAAAA PVEAAA HHHHxx
+5437 3266 1 1 7 17 37 437 1437 437 5437 74 75 DBAAAA QVEAAA OOOOxx
+4130 3267 0 2 0 10 30 130 130 4130 4130 60 61 WCAAAA RVEAAA VVVVxx
+6363 3268 1 3 3 3 63 363 363 1363 6363 126 127 TKAAAA SVEAAA AAAAxx
+1499 3269 1 3 9 19 99 499 1499 1499 1499 198 199 RFAAAA TVEAAA HHHHxx
+384 3270 0 0 4 4 84 384 384 384 384 168 169 UOAAAA UVEAAA OOOOxx
+2266 3271 0 2 6 6 66 266 266 2266 2266 132 133 EJAAAA VVEAAA VVVVxx
+6018 3272 0 2 8 18 18 18 18 1018 6018 36 37 MXAAAA WVEAAA AAAAxx
+7915 3273 1 3 5 15 15 915 1915 2915 7915 30 31 LSAAAA XVEAAA HHHHxx
+6167 3274 1 3 7 7 67 167 167 1167 6167 134 135 FDAAAA YVEAAA OOOOxx
+9988 3275 0 0 8 8 88 988 1988 4988 9988 176 177 EUAAAA ZVEAAA VVVVxx
+6599 3276 1 3 9 19 99 599 599 1599 6599 198 199 VTAAAA AWEAAA AAAAxx
+1693 3277 1 1 3 13 93 693 1693 1693 1693 186 187 DNAAAA BWEAAA HHHHxx
+5971 3278 1 3 1 11 71 971 1971 971 5971 142 143 RVAAAA CWEAAA OOOOxx
+8470 3279 0 2 0 10 70 470 470 3470 8470 140 141 UNAAAA DWEAAA VVVVxx
+2807 3280 1 3 7 7 7 807 807 2807 2807 14 15 ZDAAAA EWEAAA AAAAxx
+1120 3281 0 0 0 0 20 120 1120 1120 1120 40 41 CRAAAA FWEAAA HHHHxx
+5924 3282 0 0 4 4 24 924 1924 924 5924 48 49 WTAAAA GWEAAA OOOOxx
+9025 3283 1 1 5 5 25 25 1025 4025 9025 50 51 DJAAAA HWEAAA VVVVxx
+9454 3284 0 2 4 14 54 454 1454 4454 9454 108 109 QZAAAA IWEAAA AAAAxx
+2259 3285 1 3 9 19 59 259 259 2259 2259 118 119 XIAAAA JWEAAA HHHHxx
+5249 3286 1 1 9 9 49 249 1249 249 5249 98 99 XTAAAA KWEAAA OOOOxx
+6350 3287 0 2 0 10 50 350 350 1350 6350 100 101 GKAAAA LWEAAA VVVVxx
+2930 3288 0 2 0 10 30 930 930 2930 2930 60 61 SIAAAA MWEAAA AAAAxx
+6055 3289 1 3 5 15 55 55 55 1055 6055 110 111 XYAAAA NWEAAA HHHHxx
+7691 3290 1 3 1 11 91 691 1691 2691 7691 182 183 VJAAAA OWEAAA OOOOxx
+1573 3291 1 1 3 13 73 573 1573 1573 1573 146 147 NIAAAA PWEAAA VVVVxx
+9943 3292 1 3 3 3 43 943 1943 4943 9943 86 87 LSAAAA QWEAAA AAAAxx
+3085 3293 1 1 5 5 85 85 1085 3085 3085 170 171 ROAAAA RWEAAA HHHHxx
+5928 3294 0 0 8 8 28 928 1928 928 5928 56 57 AUAAAA SWEAAA OOOOxx
+887 3295 1 3 7 7 87 887 887 887 887 174 175 DIAAAA TWEAAA VVVVxx
+4630 3296 0 2 0 10 30 630 630 4630 4630 60 61 CWAAAA UWEAAA AAAAxx
+9827 3297 1 3 7 7 27 827 1827 4827 9827 54 55 ZNAAAA VWEAAA HHHHxx
+8926 3298 0 2 6 6 26 926 926 3926 8926 52 53 IFAAAA WWEAAA OOOOxx
+5726 3299 0 2 6 6 26 726 1726 726 5726 52 53 GMAAAA XWEAAA VVVVxx
+1569 3300 1 1 9 9 69 569 1569 1569 1569 138 139 JIAAAA YWEAAA AAAAxx
+8074 3301 0 2 4 14 74 74 74 3074 8074 148 149 OYAAAA ZWEAAA HHHHxx
+7909 3302 1 1 9 9 9 909 1909 2909 7909 18 19 FSAAAA AXEAAA OOOOxx
+8367 3303 1 3 7 7 67 367 367 3367 8367 134 135 VJAAAA BXEAAA VVVVxx
+7217 3304 1 1 7 17 17 217 1217 2217 7217 34 35 PRAAAA CXEAAA AAAAxx
+5254 3305 0 2 4 14 54 254 1254 254 5254 108 109 CUAAAA DXEAAA HHHHxx
+1181 3306 1 1 1 1 81 181 1181 1181 1181 162 163 LTAAAA EXEAAA OOOOxx
+6907 3307 1 3 7 7 7 907 907 1907 6907 14 15 RFAAAA FXEAAA VVVVxx
+5508 3308 0 0 8 8 8 508 1508 508 5508 16 17 WDAAAA GXEAAA AAAAxx
+4782 3309 0 2 2 2 82 782 782 4782 4782 164 165 YBAAAA HXEAAA HHHHxx
+793 3310 1 1 3 13 93 793 793 793 793 186 187 NEAAAA IXEAAA OOOOxx
+5740 3311 0 0 0 0 40 740 1740 740 5740 80 81 UMAAAA JXEAAA VVVVxx
+3107 3312 1 3 7 7 7 107 1107 3107 3107 14 15 NPAAAA KXEAAA AAAAxx
+1197 3313 1 1 7 17 97 197 1197 1197 1197 194 195 BUAAAA LXEAAA HHHHxx
+4376 3314 0 0 6 16 76 376 376 4376 4376 152 153 IMAAAA MXEAAA OOOOxx
+6226 3315 0 2 6 6 26 226 226 1226 6226 52 53 MFAAAA NXEAAA VVVVxx
+5033 3316 1 1 3 13 33 33 1033 33 5033 66 67 PLAAAA OXEAAA AAAAxx
+5494 3317 0 2 4 14 94 494 1494 494 5494 188 189 IDAAAA PXEAAA HHHHxx
+3244 3318 0 0 4 4 44 244 1244 3244 3244 88 89 UUAAAA QXEAAA OOOOxx
+7670 3319 0 2 0 10 70 670 1670 2670 7670 140 141 AJAAAA RXEAAA VVVVxx
+9273 3320 1 1 3 13 73 273 1273 4273 9273 146 147 RSAAAA SXEAAA AAAAxx
+5248 3321 0 0 8 8 48 248 1248 248 5248 96 97 WTAAAA TXEAAA HHHHxx
+3381 3322 1 1 1 1 81 381 1381 3381 3381 162 163 BAAAAA UXEAAA OOOOxx
+4136 3323 0 0 6 16 36 136 136 4136 4136 72 73 CDAAAA VXEAAA VVVVxx
+4163 3324 1 3 3 3 63 163 163 4163 4163 126 127 DEAAAA WXEAAA AAAAxx
+4270 3325 0 2 0 10 70 270 270 4270 4270 140 141 GIAAAA XXEAAA HHHHxx
+1729 3326 1 1 9 9 29 729 1729 1729 1729 58 59 NOAAAA YXEAAA OOOOxx
+2778 3327 0 2 8 18 78 778 778 2778 2778 156 157 WCAAAA ZXEAAA VVVVxx
+5082 3328 0 2 2 2 82 82 1082 82 5082 164 165 MNAAAA AYEAAA AAAAxx
+870 3329 0 2 0 10 70 870 870 870 870 140 141 MHAAAA BYEAAA HHHHxx
+4192 3330 0 0 2 12 92 192 192 4192 4192 184 185 GFAAAA CYEAAA OOOOxx
+308 3331 0 0 8 8 8 308 308 308 308 16 17 WLAAAA DYEAAA VVVVxx
+6783 3332 1 3 3 3 83 783 783 1783 6783 166 167 XAAAAA EYEAAA AAAAxx
+7611 3333 1 3 1 11 11 611 1611 2611 7611 22 23 TGAAAA FYEAAA HHHHxx
+4221 3334 1 1 1 1 21 221 221 4221 4221 42 43 JGAAAA GYEAAA OOOOxx
+6353 3335 1 1 3 13 53 353 353 1353 6353 106 107 JKAAAA HYEAAA VVVVxx
+1830 3336 0 2 0 10 30 830 1830 1830 1830 60 61 KSAAAA IYEAAA AAAAxx
+2437 3337 1 1 7 17 37 437 437 2437 2437 74 75 TPAAAA JYEAAA HHHHxx
+3360 3338 0 0 0 0 60 360 1360 3360 3360 120 121 GZAAAA KYEAAA OOOOxx
+1829 3339 1 1 9 9 29 829 1829 1829 1829 58 59 JSAAAA LYEAAA VVVVxx
+9475 3340 1 3 5 15 75 475 1475 4475 9475 150 151 LAAAAA MYEAAA AAAAxx
+4566 3341 0 2 6 6 66 566 566 4566 4566 132 133 QTAAAA NYEAAA HHHHxx
+9944 3342 0 0 4 4 44 944 1944 4944 9944 88 89 MSAAAA OYEAAA OOOOxx
+6054 3343 0 2 4 14 54 54 54 1054 6054 108 109 WYAAAA PYEAAA VVVVxx
+4722 3344 0 2 2 2 22 722 722 4722 4722 44 45 QZAAAA QYEAAA AAAAxx
+2779 3345 1 3 9 19 79 779 779 2779 2779 158 159 XCAAAA RYEAAA HHHHxx
+8051 3346 1 3 1 11 51 51 51 3051 8051 102 103 RXAAAA SYEAAA OOOOxx
+9671 3347 1 3 1 11 71 671 1671 4671 9671 142 143 ZHAAAA TYEAAA VVVVxx
+6084 3348 0 0 4 4 84 84 84 1084 6084 168 169 AAAAAA UYEAAA AAAAxx
+3729 3349 1 1 9 9 29 729 1729 3729 3729 58 59 LNAAAA VYEAAA HHHHxx
+6627 3350 1 3 7 7 27 627 627 1627 6627 54 55 XUAAAA WYEAAA OOOOxx
+4769 3351 1 1 9 9 69 769 769 4769 4769 138 139 LBAAAA XYEAAA VVVVxx
+2224 3352 0 0 4 4 24 224 224 2224 2224 48 49 OHAAAA YYEAAA AAAAxx
+1404 3353 0 0 4 4 4 404 1404 1404 1404 8 9 ACAAAA ZYEAAA HHHHxx
+8532 3354 0 0 2 12 32 532 532 3532 8532 64 65 EQAAAA AZEAAA OOOOxx
+6759 3355 1 3 9 19 59 759 759 1759 6759 118 119 ZZAAAA BZEAAA VVVVxx
+6404 3356 0 0 4 4 4 404 404 1404 6404 8 9 IMAAAA CZEAAA AAAAxx
+3144 3357 0 0 4 4 44 144 1144 3144 3144 88 89 YQAAAA DZEAAA HHHHxx
+973 3358 1 1 3 13 73 973 973 973 973 146 147 LLAAAA EZEAAA OOOOxx
+9789 3359 1 1 9 9 89 789 1789 4789 9789 178 179 NMAAAA FZEAAA VVVVxx
+6181 3360 1 1 1 1 81 181 181 1181 6181 162 163 TDAAAA GZEAAA AAAAxx
+1519 3361 1 3 9 19 19 519 1519 1519 1519 38 39 LGAAAA HZEAAA HHHHxx
+9729 3362 1 1 9 9 29 729 1729 4729 9729 58 59 FKAAAA IZEAAA OOOOxx
+8167 3363 1 3 7 7 67 167 167 3167 8167 134 135 DCAAAA JZEAAA VVVVxx
+3830 3364 0 2 0 10 30 830 1830 3830 3830 60 61 IRAAAA KZEAAA AAAAxx
+6286 3365 0 2 6 6 86 286 286 1286 6286 172 173 UHAAAA LZEAAA HHHHxx
+3047 3366 1 3 7 7 47 47 1047 3047 3047 94 95 FNAAAA MZEAAA OOOOxx
+3183 3367 1 3 3 3 83 183 1183 3183 3183 166 167 LSAAAA NZEAAA VVVVxx
+6687 3368 1 3 7 7 87 687 687 1687 6687 174 175 FXAAAA OZEAAA AAAAxx
+2783 3369 1 3 3 3 83 783 783 2783 2783 166 167 BDAAAA PZEAAA HHHHxx
+9920 3370 0 0 0 0 20 920 1920 4920 9920 40 41 ORAAAA QZEAAA OOOOxx
+4847 3371 1 3 7 7 47 847 847 4847 4847 94 95 LEAAAA RZEAAA VVVVxx
+3645 3372 1 1 5 5 45 645 1645 3645 3645 90 91 FKAAAA SZEAAA AAAAxx
+7406 3373 0 2 6 6 6 406 1406 2406 7406 12 13 WYAAAA TZEAAA HHHHxx
+6003 3374 1 3 3 3 3 3 3 1003 6003 6 7 XWAAAA UZEAAA OOOOxx
+3408 3375 0 0 8 8 8 408 1408 3408 3408 16 17 CBAAAA VZEAAA VVVVxx
+4243 3376 1 3 3 3 43 243 243 4243 4243 86 87 FHAAAA WZEAAA AAAAxx
+1622 3377 0 2 2 2 22 622 1622 1622 1622 44 45 KKAAAA XZEAAA HHHHxx
+5319 3378 1 3 9 19 19 319 1319 319 5319 38 39 PWAAAA YZEAAA OOOOxx
+4033 3379 1 1 3 13 33 33 33 4033 4033 66 67 DZAAAA ZZEAAA VVVVxx
+8573 3380 1 1 3 13 73 573 573 3573 8573 146 147 TRAAAA AAFAAA AAAAxx
+8404 3381 0 0 4 4 4 404 404 3404 8404 8 9 GLAAAA BAFAAA HHHHxx
+6993 3382 1 1 3 13 93 993 993 1993 6993 186 187 ZIAAAA CAFAAA OOOOxx
+660 3383 0 0 0 0 60 660 660 660 660 120 121 KZAAAA DAFAAA VVVVxx
+1136 3384 0 0 6 16 36 136 1136 1136 1136 72 73 SRAAAA EAFAAA AAAAxx
+3393 3385 1 1 3 13 93 393 1393 3393 3393 186 187 NAAAAA FAFAAA HHHHxx
+9743 3386 1 3 3 3 43 743 1743 4743 9743 86 87 TKAAAA GAFAAA OOOOxx
+9705 3387 1 1 5 5 5 705 1705 4705 9705 10 11 HJAAAA HAFAAA VVVVxx
+6960 3388 0 0 0 0 60 960 960 1960 6960 120 121 SHAAAA IAFAAA AAAAxx
+2753 3389 1 1 3 13 53 753 753 2753 2753 106 107 XBAAAA JAFAAA HHHHxx
+906 3390 0 2 6 6 6 906 906 906 906 12 13 WIAAAA KAFAAA OOOOxx
+999 3391 1 3 9 19 99 999 999 999 999 198 199 LMAAAA LAFAAA VVVVxx
+6927 3392 1 3 7 7 27 927 927 1927 6927 54 55 LGAAAA MAFAAA AAAAxx
+4846 3393 0 2 6 6 46 846 846 4846 4846 92 93 KEAAAA NAFAAA HHHHxx
+676 3394 0 0 6 16 76 676 676 676 676 152 153 AAAAAA OAFAAA OOOOxx
+8612 3395 0 0 2 12 12 612 612 3612 8612 24 25 GTAAAA PAFAAA VVVVxx
+4111 3396 1 3 1 11 11 111 111 4111 4111 22 23 DCAAAA QAFAAA AAAAxx
+9994 3397 0 2 4 14 94 994 1994 4994 9994 188 189 KUAAAA RAFAAA HHHHxx
+4399 3398 1 3 9 19 99 399 399 4399 4399 198 199 FNAAAA SAFAAA OOOOxx
+4464 3399 0 0 4 4 64 464 464 4464 4464 128 129 SPAAAA TAFAAA VVVVxx
+7316 3400 0 0 6 16 16 316 1316 2316 7316 32 33 KVAAAA UAFAAA AAAAxx
+8982 3401 0 2 2 2 82 982 982 3982 8982 164 165 MHAAAA VAFAAA HHHHxx
+1871 3402 1 3 1 11 71 871 1871 1871 1871 142 143 ZTAAAA WAFAAA OOOOxx
+4082 3403 0 2 2 2 82 82 82 4082 4082 164 165 ABAAAA XAFAAA VVVVxx
+3949 3404 1 1 9 9 49 949 1949 3949 3949 98 99 XVAAAA YAFAAA AAAAxx
+9352 3405 0 0 2 12 52 352 1352 4352 9352 104 105 SVAAAA ZAFAAA HHHHxx
+9638 3406 0 2 8 18 38 638 1638 4638 9638 76 77 SGAAAA ABFAAA OOOOxx
+8177 3407 1 1 7 17 77 177 177 3177 8177 154 155 NCAAAA BBFAAA VVVVxx
+3499 3408 1 3 9 19 99 499 1499 3499 3499 198 199 PEAAAA CBFAAA AAAAxx
+4233 3409 1 1 3 13 33 233 233 4233 4233 66 67 VGAAAA DBFAAA HHHHxx
+1953 3410 1 1 3 13 53 953 1953 1953 1953 106 107 DXAAAA EBFAAA OOOOxx
+7372 3411 0 0 2 12 72 372 1372 2372 7372 144 145 OXAAAA FBFAAA VVVVxx
+5127 3412 1 3 7 7 27 127 1127 127 5127 54 55 FPAAAA GBFAAA AAAAxx
+4384 3413 0 0 4 4 84 384 384 4384 4384 168 169 QMAAAA HBFAAA HHHHxx
+9964 3414 0 0 4 4 64 964 1964 4964 9964 128 129 GTAAAA IBFAAA OOOOxx
+5392 3415 0 0 2 12 92 392 1392 392 5392 184 185 KZAAAA JBFAAA VVVVxx
+616 3416 0 0 6 16 16 616 616 616 616 32 33 SXAAAA KBFAAA AAAAxx
+591 3417 1 3 1 11 91 591 591 591 591 182 183 TWAAAA LBFAAA HHHHxx
+6422 3418 0 2 2 2 22 422 422 1422 6422 44 45 ANAAAA MBFAAA OOOOxx
+6551 3419 1 3 1 11 51 551 551 1551 6551 102 103 ZRAAAA NBFAAA VVVVxx
+9286 3420 0 2 6 6 86 286 1286 4286 9286 172 173 ETAAAA OBFAAA AAAAxx
+3817 3421 1 1 7 17 17 817 1817 3817 3817 34 35 VQAAAA PBFAAA HHHHxx
+7717 3422 1 1 7 17 17 717 1717 2717 7717 34 35 VKAAAA QBFAAA OOOOxx
+8718 3423 0 2 8 18 18 718 718 3718 8718 36 37 IXAAAA RBFAAA VVVVxx
+8608 3424 0 0 8 8 8 608 608 3608 8608 16 17 CTAAAA SBFAAA AAAAxx
+2242 3425 0 2 2 2 42 242 242 2242 2242 84 85 GIAAAA TBFAAA HHHHxx
+4811 3426 1 3 1 11 11 811 811 4811 4811 22 23 BDAAAA UBFAAA OOOOxx
+6838 3427 0 2 8 18 38 838 838 1838 6838 76 77 ADAAAA VBFAAA VVVVxx
+787 3428 1 3 7 7 87 787 787 787 787 174 175 HEAAAA WBFAAA AAAAxx
+7940 3429 0 0 0 0 40 940 1940 2940 7940 80 81 KTAAAA XBFAAA HHHHxx
+336 3430 0 0 6 16 36 336 336 336 336 72 73 YMAAAA YBFAAA OOOOxx
+9859 3431 1 3 9 19 59 859 1859 4859 9859 118 119 FPAAAA ZBFAAA VVVVxx
+3864 3432 0 0 4 4 64 864 1864 3864 3864 128 129 QSAAAA ACFAAA AAAAxx
+7162 3433 0 2 2 2 62 162 1162 2162 7162 124 125 MPAAAA BCFAAA HHHHxx
+2071 3434 1 3 1 11 71 71 71 2071 2071 142 143 RBAAAA CCFAAA OOOOxx
+7469 3435 1 1 9 9 69 469 1469 2469 7469 138 139 HBAAAA DCFAAA VVVVxx
+2917 3436 1 1 7 17 17 917 917 2917 2917 34 35 FIAAAA ECFAAA AAAAxx
+7486 3437 0 2 6 6 86 486 1486 2486 7486 172 173 YBAAAA FCFAAA HHHHxx
+3355 3438 1 3 5 15 55 355 1355 3355 3355 110 111 BZAAAA GCFAAA OOOOxx
+6998 3439 0 2 8 18 98 998 998 1998 6998 196 197 EJAAAA HCFAAA VVVVxx
+5498 3440 0 2 8 18 98 498 1498 498 5498 196 197 MDAAAA ICFAAA AAAAxx
+5113 3441 1 1 3 13 13 113 1113 113 5113 26 27 ROAAAA JCFAAA HHHHxx
+2846 3442 0 2 6 6 46 846 846 2846 2846 92 93 MFAAAA KCFAAA OOOOxx
+6834 3443 0 2 4 14 34 834 834 1834 6834 68 69 WCAAAA LCFAAA VVVVxx
+8925 3444 1 1 5 5 25 925 925 3925 8925 50 51 HFAAAA MCFAAA AAAAxx
+2757 3445 1 1 7 17 57 757 757 2757 2757 114 115 BCAAAA NCFAAA HHHHxx
+2775 3446 1 3 5 15 75 775 775 2775 2775 150 151 TCAAAA OCFAAA OOOOxx
+6182 3447 0 2 2 2 82 182 182 1182 6182 164 165 UDAAAA PCFAAA VVVVxx
+4488 3448 0 0 8 8 88 488 488 4488 4488 176 177 QQAAAA QCFAAA AAAAxx
+8523 3449 1 3 3 3 23 523 523 3523 8523 46 47 VPAAAA RCFAAA HHHHxx
+52 3450 0 0 2 12 52 52 52 52 52 104 105 ACAAAA SCFAAA OOOOxx
+7251 3451 1 3 1 11 51 251 1251 2251 7251 102 103 XSAAAA TCFAAA VVVVxx
+6130 3452 0 2 0 10 30 130 130 1130 6130 60 61 UBAAAA UCFAAA AAAAxx
+205 3453 1 1 5 5 5 205 205 205 205 10 11 XHAAAA VCFAAA HHHHxx
+1186 3454 0 2 6 6 86 186 1186 1186 1186 172 173 QTAAAA WCFAAA OOOOxx
+1738 3455 0 2 8 18 38 738 1738 1738 1738 76 77 WOAAAA XCFAAA VVVVxx
+9485 3456 1 1 5 5 85 485 1485 4485 9485 170 171 VAAAAA YCFAAA AAAAxx
+4235 3457 1 3 5 15 35 235 235 4235 4235 70 71 XGAAAA ZCFAAA HHHHxx
+7891 3458 1 3 1 11 91 891 1891 2891 7891 182 183 NRAAAA ADFAAA OOOOxx
+4960 3459 0 0 0 0 60 960 960 4960 4960 120 121 UIAAAA BDFAAA VVVVxx
+8911 3460 1 3 1 11 11 911 911 3911 8911 22 23 TEAAAA CDFAAA AAAAxx
+1219 3461 1 3 9 19 19 219 1219 1219 1219 38 39 XUAAAA DDFAAA HHHHxx
+9652 3462 0 0 2 12 52 652 1652 4652 9652 104 105 GHAAAA EDFAAA OOOOxx
+9715 3463 1 3 5 15 15 715 1715 4715 9715 30 31 RJAAAA FDFAAA VVVVxx
+6629 3464 1 1 9 9 29 629 629 1629 6629 58 59 ZUAAAA GDFAAA AAAAxx
+700 3465 0 0 0 0 0 700 700 700 700 0 1 YAAAAA HDFAAA HHHHxx
+9819 3466 1 3 9 19 19 819 1819 4819 9819 38 39 RNAAAA IDFAAA OOOOxx
+5188 3467 0 0 8 8 88 188 1188 188 5188 176 177 ORAAAA JDFAAA VVVVxx
+5367 3468 1 3 7 7 67 367 1367 367 5367 134 135 LYAAAA KDFAAA AAAAxx
+6447 3469 1 3 7 7 47 447 447 1447 6447 94 95 ZNAAAA LDFAAA HHHHxx
+720 3470 0 0 0 0 20 720 720 720 720 40 41 SBAAAA MDFAAA OOOOxx
+9157 3471 1 1 7 17 57 157 1157 4157 9157 114 115 FOAAAA NDFAAA VVVVxx
+1082 3472 0 2 2 2 82 82 1082 1082 1082 164 165 QPAAAA ODFAAA AAAAxx
+3179 3473 1 3 9 19 79 179 1179 3179 3179 158 159 HSAAAA PDFAAA HHHHxx
+4818 3474 0 2 8 18 18 818 818 4818 4818 36 37 IDAAAA QDFAAA OOOOxx
+7607 3475 1 3 7 7 7 607 1607 2607 7607 14 15 PGAAAA RDFAAA VVVVxx
+2352 3476 0 0 2 12 52 352 352 2352 2352 104 105 MMAAAA SDFAAA AAAAxx
+1170 3477 0 2 0 10 70 170 1170 1170 1170 140 141 ATAAAA TDFAAA HHHHxx
+4269 3478 1 1 9 9 69 269 269 4269 4269 138 139 FIAAAA UDFAAA OOOOxx
+8767 3479 1 3 7 7 67 767 767 3767 8767 134 135 FZAAAA VDFAAA VVVVxx
+3984 3480 0 0 4 4 84 984 1984 3984 3984 168 169 GXAAAA WDFAAA AAAAxx
+3190 3481 0 2 0 10 90 190 1190 3190 3190 180 181 SSAAAA XDFAAA HHHHxx
+7456 3482 0 0 6 16 56 456 1456 2456 7456 112 113 UAAAAA YDFAAA OOOOxx
+4348 3483 0 0 8 8 48 348 348 4348 4348 96 97 GLAAAA ZDFAAA VVVVxx
+3150 3484 0 2 0 10 50 150 1150 3150 3150 100 101 ERAAAA AEFAAA AAAAxx
+8780 3485 0 0 0 0 80 780 780 3780 8780 160 161 SZAAAA BEFAAA HHHHxx
+2553 3486 1 1 3 13 53 553 553 2553 2553 106 107 FUAAAA CEFAAA OOOOxx
+7526 3487 0 2 6 6 26 526 1526 2526 7526 52 53 MDAAAA DEFAAA VVVVxx
+2031 3488 1 3 1 11 31 31 31 2031 2031 62 63 DAAAAA EEFAAA AAAAxx
+8793 3489 1 1 3 13 93 793 793 3793 8793 186 187 FAAAAA FEFAAA HHHHxx
+1122 3490 0 2 2 2 22 122 1122 1122 1122 44 45 ERAAAA GEFAAA OOOOxx
+1855 3491 1 3 5 15 55 855 1855 1855 1855 110 111 JTAAAA HEFAAA VVVVxx
+6613 3492 1 1 3 13 13 613 613 1613 6613 26 27 JUAAAA IEFAAA AAAAxx
+3231 3493 1 3 1 11 31 231 1231 3231 3231 62 63 HUAAAA JEFAAA HHHHxx
+9101 3494 1 1 1 1 1 101 1101 4101 9101 2 3 BMAAAA KEFAAA OOOOxx
+4937 3495 1 1 7 17 37 937 937 4937 4937 74 75 XHAAAA LEFAAA VVVVxx
+666 3496 0 2 6 6 66 666 666 666 666 132 133 QZAAAA MEFAAA AAAAxx
+8943 3497 1 3 3 3 43 943 943 3943 8943 86 87 ZFAAAA NEFAAA HHHHxx
+6164 3498 0 0 4 4 64 164 164 1164 6164 128 129 CDAAAA OEFAAA OOOOxx
+1081 3499 1 1 1 1 81 81 1081 1081 1081 162 163 PPAAAA PEFAAA VVVVxx
+210 3500 0 2 0 10 10 210 210 210 210 20 21 CIAAAA QEFAAA AAAAxx
+6024 3501 0 0 4 4 24 24 24 1024 6024 48 49 SXAAAA REFAAA HHHHxx
+5715 3502 1 3 5 15 15 715 1715 715 5715 30 31 VLAAAA SEFAAA OOOOxx
+8938 3503 0 2 8 18 38 938 938 3938 8938 76 77 UFAAAA TEFAAA VVVVxx
+1326 3504 0 2 6 6 26 326 1326 1326 1326 52 53 AZAAAA UEFAAA AAAAxx
+7111 3505 1 3 1 11 11 111 1111 2111 7111 22 23 NNAAAA VEFAAA HHHHxx
+757 3506 1 1 7 17 57 757 757 757 757 114 115 DDAAAA WEFAAA OOOOxx
+8933 3507 1 1 3 13 33 933 933 3933 8933 66 67 PFAAAA XEFAAA VVVVxx
+6495 3508 1 3 5 15 95 495 495 1495 6495 190 191 VPAAAA YEFAAA AAAAxx
+3134 3509 0 2 4 14 34 134 1134 3134 3134 68 69 OQAAAA ZEFAAA HHHHxx
+1304 3510 0 0 4 4 4 304 1304 1304 1304 8 9 EYAAAA AFFAAA OOOOxx
+1835 3511 1 3 5 15 35 835 1835 1835 1835 70 71 PSAAAA BFFAAA VVVVxx
+7275 3512 1 3 5 15 75 275 1275 2275 7275 150 151 VTAAAA CFFAAA AAAAxx
+7337 3513 1 1 7 17 37 337 1337 2337 7337 74 75 FWAAAA DFFAAA HHHHxx
+1282 3514 0 2 2 2 82 282 1282 1282 1282 164 165 IXAAAA EFFAAA OOOOxx
+6566 3515 0 2 6 6 66 566 566 1566 6566 132 133 OSAAAA FFFAAA VVVVxx
+3786 3516 0 2 6 6 86 786 1786 3786 3786 172 173 QPAAAA GFFAAA AAAAxx
+5741 3517 1 1 1 1 41 741 1741 741 5741 82 83 VMAAAA HFFAAA HHHHxx
+6076 3518 0 0 6 16 76 76 76 1076 6076 152 153 SZAAAA IFFAAA OOOOxx
+9998 3519 0 2 8 18 98 998 1998 4998 9998 196 197 OUAAAA JFFAAA VVVVxx
+6268 3520 0 0 8 8 68 268 268 1268 6268 136 137 CHAAAA KFFAAA AAAAxx
+9647 3521 1 3 7 7 47 647 1647 4647 9647 94 95 BHAAAA LFFAAA HHHHxx
+4877 3522 1 1 7 17 77 877 877 4877 4877 154 155 PFAAAA MFFAAA OOOOxx
+2652 3523 0 0 2 12 52 652 652 2652 2652 104 105 AYAAAA NFFAAA VVVVxx
+1247 3524 1 3 7 7 47 247 1247 1247 1247 94 95 ZVAAAA OFFAAA AAAAxx
+2721 3525 1 1 1 1 21 721 721 2721 2721 42 43 RAAAAA PFFAAA HHHHxx
+5968 3526 0 0 8 8 68 968 1968 968 5968 136 137 OVAAAA QFFAAA OOOOxx
+9570 3527 0 2 0 10 70 570 1570 4570 9570 140 141 CEAAAA RFFAAA VVVVxx
+6425 3528 1 1 5 5 25 425 425 1425 6425 50 51 DNAAAA SFFAAA AAAAxx
+5451 3529 1 3 1 11 51 451 1451 451 5451 102 103 RBAAAA TFFAAA HHHHxx
+5668 3530 0 0 8 8 68 668 1668 668 5668 136 137 AKAAAA UFFAAA OOOOxx
+9493 3531 1 1 3 13 93 493 1493 4493 9493 186 187 DBAAAA VFFAAA VVVVxx
+7973 3532 1 1 3 13 73 973 1973 2973 7973 146 147 RUAAAA WFFAAA AAAAxx
+8250 3533 0 2 0 10 50 250 250 3250 8250 100 101 IFAAAA XFFAAA HHHHxx
+82 3534 0 2 2 2 82 82 82 82 82 164 165 EDAAAA YFFAAA OOOOxx
+6258 3535 0 2 8 18 58 258 258 1258 6258 116 117 SGAAAA ZFFAAA VVVVxx
+9978 3536 0 2 8 18 78 978 1978 4978 9978 156 157 UTAAAA AGFAAA AAAAxx
+6930 3537 0 2 0 10 30 930 930 1930 6930 60 61 OGAAAA BGFAAA HHHHxx
+3746 3538 0 2 6 6 46 746 1746 3746 3746 92 93 COAAAA CGFAAA OOOOxx
+7065 3539 1 1 5 5 65 65 1065 2065 7065 130 131 TLAAAA DGFAAA VVVVxx
+4281 3540 1 1 1 1 81 281 281 4281 4281 162 163 RIAAAA EGFAAA AAAAxx
+4367 3541 1 3 7 7 67 367 367 4367 4367 134 135 ZLAAAA FGFAAA HHHHxx
+9526 3542 0 2 6 6 26 526 1526 4526 9526 52 53 KCAAAA GGFAAA OOOOxx
+5880 3543 0 0 0 0 80 880 1880 880 5880 160 161 ESAAAA HGFAAA VVVVxx
+8480 3544 0 0 0 0 80 480 480 3480 8480 160 161 EOAAAA IGFAAA AAAAxx
+2476 3545 0 0 6 16 76 476 476 2476 2476 152 153 GRAAAA JGFAAA HHHHxx
+9074 3546 0 2 4 14 74 74 1074 4074 9074 148 149 ALAAAA KGFAAA OOOOxx
+4830 3547 0 2 0 10 30 830 830 4830 4830 60 61 UDAAAA LGFAAA VVVVxx
+3207 3548 1 3 7 7 7 207 1207 3207 3207 14 15 JTAAAA MGFAAA AAAAxx
+7894 3549 0 2 4 14 94 894 1894 2894 7894 188 189 QRAAAA NGFAAA HHHHxx
+3860 3550 0 0 0 0 60 860 1860 3860 3860 120 121 MSAAAA OGFAAA OOOOxx
+5293 3551 1 1 3 13 93 293 1293 293 5293 186 187 PVAAAA PGFAAA VVVVxx
+6895 3552 1 3 5 15 95 895 895 1895 6895 190 191 FFAAAA QGFAAA AAAAxx
+9908 3553 0 0 8 8 8 908 1908 4908 9908 16 17 CRAAAA RGFAAA HHHHxx
+9247 3554 1 3 7 7 47 247 1247 4247 9247 94 95 RRAAAA SGFAAA OOOOxx
+8110 3555 0 2 0 10 10 110 110 3110 8110 20 21 YZAAAA TGFAAA VVVVxx
+4716 3556 0 0 6 16 16 716 716 4716 4716 32 33 KZAAAA UGFAAA AAAAxx
+4979 3557 1 3 9 19 79 979 979 4979 4979 158 159 NJAAAA VGFAAA HHHHxx
+5280 3558 0 0 0 0 80 280 1280 280 5280 160 161 CVAAAA WGFAAA OOOOxx
+8326 3559 0 2 6 6 26 326 326 3326 8326 52 53 GIAAAA XGFAAA VVVVxx
+5572 3560 0 0 2 12 72 572 1572 572 5572 144 145 IGAAAA YGFAAA AAAAxx
+4665 3561 1 1 5 5 65 665 665 4665 4665 130 131 LXAAAA ZGFAAA HHHHxx
+3665 3562 1 1 5 5 65 665 1665 3665 3665 130 131 ZKAAAA AHFAAA OOOOxx
+6744 3563 0 0 4 4 44 744 744 1744 6744 88 89 KZAAAA BHFAAA VVVVxx
+1897 3564 1 1 7 17 97 897 1897 1897 1897 194 195 ZUAAAA CHFAAA AAAAxx
+1220 3565 0 0 0 0 20 220 1220 1220 1220 40 41 YUAAAA DHFAAA HHHHxx
+2614 3566 0 2 4 14 14 614 614 2614 2614 28 29 OWAAAA EHFAAA OOOOxx
+8509 3567 1 1 9 9 9 509 509 3509 8509 18 19 HPAAAA FHFAAA VVVVxx
+8521 3568 1 1 1 1 21 521 521 3521 8521 42 43 TPAAAA GHFAAA AAAAxx
+4121 3569 1 1 1 1 21 121 121 4121 4121 42 43 NCAAAA HHFAAA HHHHxx
+9663 3570 1 3 3 3 63 663 1663 4663 9663 126 127 RHAAAA IHFAAA OOOOxx
+2346 3571 0 2 6 6 46 346 346 2346 2346 92 93 GMAAAA JHFAAA VVVVxx
+3370 3572 0 2 0 10 70 370 1370 3370 3370 140 141 QZAAAA KHFAAA AAAAxx
+1498 3573 0 2 8 18 98 498 1498 1498 1498 196 197 QFAAAA LHFAAA HHHHxx
+7422 3574 0 2 2 2 22 422 1422 2422 7422 44 45 MZAAAA MHFAAA OOOOxx
+3472 3575 0 0 2 12 72 472 1472 3472 3472 144 145 ODAAAA NHFAAA VVVVxx
+4126 3576 0 2 6 6 26 126 126 4126 4126 52 53 SCAAAA OHFAAA AAAAxx
+4494 3577 0 2 4 14 94 494 494 4494 4494 188 189 WQAAAA PHFAAA HHHHxx
+6323 3578 1 3 3 3 23 323 323 1323 6323 46 47 FJAAAA QHFAAA OOOOxx
+2823 3579 1 3 3 3 23 823 823 2823 2823 46 47 PEAAAA RHFAAA VVVVxx
+8596 3580 0 0 6 16 96 596 596 3596 8596 192 193 QSAAAA SHFAAA AAAAxx
+6642 3581 0 2 2 2 42 642 642 1642 6642 84 85 MVAAAA THFAAA HHHHxx
+9276 3582 0 0 6 16 76 276 1276 4276 9276 152 153 USAAAA UHFAAA OOOOxx
+4148 3583 0 0 8 8 48 148 148 4148 4148 96 97 ODAAAA VHFAAA VVVVxx
+9770 3584 0 2 0 10 70 770 1770 4770 9770 140 141 ULAAAA WHFAAA AAAAxx
+9812 3585 0 0 2 12 12 812 1812 4812 9812 24 25 KNAAAA XHFAAA HHHHxx
+4419 3586 1 3 9 19 19 419 419 4419 4419 38 39 ZNAAAA YHFAAA OOOOxx
+3802 3587 0 2 2 2 2 802 1802 3802 3802 4 5 GQAAAA ZHFAAA VVVVxx
+3210 3588 0 2 0 10 10 210 1210 3210 3210 20 21 MTAAAA AIFAAA AAAAxx
+6794 3589 0 2 4 14 94 794 794 1794 6794 188 189 IBAAAA BIFAAA HHHHxx
+242 3590 0 2 2 2 42 242 242 242 242 84 85 IJAAAA CIFAAA OOOOxx
+962 3591 0 2 2 2 62 962 962 962 962 124 125 ALAAAA DIFAAA VVVVxx
+7151 3592 1 3 1 11 51 151 1151 2151 7151 102 103 BPAAAA EIFAAA AAAAxx
+9440 3593 0 0 0 0 40 440 1440 4440 9440 80 81 CZAAAA FIFAAA HHHHxx
+721 3594 1 1 1 1 21 721 721 721 721 42 43 TBAAAA GIFAAA OOOOxx
+2119 3595 1 3 9 19 19 119 119 2119 2119 38 39 NDAAAA HIFAAA VVVVxx
+9883 3596 1 3 3 3 83 883 1883 4883 9883 166 167 DQAAAA IIFAAA AAAAxx
+5071 3597 1 3 1 11 71 71 1071 71 5071 142 143 BNAAAA JIFAAA HHHHxx
+8239 3598 1 3 9 19 39 239 239 3239 8239 78 79 XEAAAA KIFAAA OOOOxx
+7451 3599 1 3 1 11 51 451 1451 2451 7451 102 103 PAAAAA LIFAAA VVVVxx
+9517 3600 1 1 7 17 17 517 1517 4517 9517 34 35 BCAAAA MIFAAA AAAAxx
+9180 3601 0 0 0 0 80 180 1180 4180 9180 160 161 CPAAAA NIFAAA HHHHxx
+9327 3602 1 3 7 7 27 327 1327 4327 9327 54 55 TUAAAA OIFAAA OOOOxx
+5462 3603 0 2 2 2 62 462 1462 462 5462 124 125 CCAAAA PIFAAA VVVVxx
+8306 3604 0 2 6 6 6 306 306 3306 8306 12 13 MHAAAA QIFAAA AAAAxx
+6234 3605 0 2 4 14 34 234 234 1234 6234 68 69 UFAAAA RIFAAA HHHHxx
+8771 3606 1 3 1 11 71 771 771 3771 8771 142 143 JZAAAA SIFAAA OOOOxx
+5853 3607 1 1 3 13 53 853 1853 853 5853 106 107 DRAAAA TIFAAA VVVVxx
+8373 3608 1 1 3 13 73 373 373 3373 8373 146 147 BKAAAA UIFAAA AAAAxx
+5017 3609 1 1 7 17 17 17 1017 17 5017 34 35 ZKAAAA VIFAAA HHHHxx
+8025 3610 1 1 5 5 25 25 25 3025 8025 50 51 RWAAAA WIFAAA OOOOxx
+2526 3611 0 2 6 6 26 526 526 2526 2526 52 53 ETAAAA XIFAAA VVVVxx
+7419 3612 1 3 9 19 19 419 1419 2419 7419 38 39 JZAAAA YIFAAA AAAAxx
+4572 3613 0 0 2 12 72 572 572 4572 4572 144 145 WTAAAA ZIFAAA HHHHxx
+7744 3614 0 0 4 4 44 744 1744 2744 7744 88 89 WLAAAA AJFAAA OOOOxx
+8825 3615 1 1 5 5 25 825 825 3825 8825 50 51 LBAAAA BJFAAA VVVVxx
+6067 3616 1 3 7 7 67 67 67 1067 6067 134 135 JZAAAA CJFAAA AAAAxx
+3291 3617 1 3 1 11 91 291 1291 3291 3291 182 183 PWAAAA DJFAAA HHHHxx
+7115 3618 1 3 5 15 15 115 1115 2115 7115 30 31 RNAAAA EJFAAA OOOOxx
+2626 3619 0 2 6 6 26 626 626 2626 2626 52 53 AXAAAA FJFAAA VVVVxx
+4109 3620 1 1 9 9 9 109 109 4109 4109 18 19 BCAAAA GJFAAA AAAAxx
+4056 3621 0 0 6 16 56 56 56 4056 4056 112 113 AAAAAA HJFAAA HHHHxx
+6811 3622 1 3 1 11 11 811 811 1811 6811 22 23 ZBAAAA IJFAAA OOOOxx
+680 3623 0 0 0 0 80 680 680 680 680 160 161 EAAAAA JJFAAA VVVVxx
+474 3624 0 2 4 14 74 474 474 474 474 148 149 GSAAAA KJFAAA AAAAxx
+9294 3625 0 2 4 14 94 294 1294 4294 9294 188 189 MTAAAA LJFAAA HHHHxx
+7555 3626 1 3 5 15 55 555 1555 2555 7555 110 111 PEAAAA MJFAAA OOOOxx
+8076 3627 0 0 6 16 76 76 76 3076 8076 152 153 QYAAAA NJFAAA VVVVxx
+3840 3628 0 0 0 0 40 840 1840 3840 3840 80 81 SRAAAA OJFAAA AAAAxx
+5955 3629 1 3 5 15 55 955 1955 955 5955 110 111 BVAAAA PJFAAA HHHHxx
+994 3630 0 2 4 14 94 994 994 994 994 188 189 GMAAAA QJFAAA OOOOxx
+2089 3631 1 1 9 9 89 89 89 2089 2089 178 179 JCAAAA RJFAAA VVVVxx
+869 3632 1 1 9 9 69 869 869 869 869 138 139 LHAAAA SJFAAA AAAAxx
+1223 3633 1 3 3 3 23 223 1223 1223 1223 46 47 BVAAAA TJFAAA HHHHxx
+1514 3634 0 2 4 14 14 514 1514 1514 1514 28 29 GGAAAA UJFAAA OOOOxx
+4891 3635 1 3 1 11 91 891 891 4891 4891 182 183 DGAAAA VJFAAA VVVVxx
+4190 3636 0 2 0 10 90 190 190 4190 4190 180 181 EFAAAA WJFAAA AAAAxx
+4377 3637 1 1 7 17 77 377 377 4377 4377 154 155 JMAAAA XJFAAA HHHHxx
+9195 3638 1 3 5 15 95 195 1195 4195 9195 190 191 RPAAAA YJFAAA OOOOxx
+3827 3639 1 3 7 7 27 827 1827 3827 3827 54 55 FRAAAA ZJFAAA VVVVxx
+7386 3640 0 2 6 6 86 386 1386 2386 7386 172 173 CYAAAA AKFAAA AAAAxx
+6665 3641 1 1 5 5 65 665 665 1665 6665 130 131 JWAAAA BKFAAA HHHHxx
+7514 3642 0 2 4 14 14 514 1514 2514 7514 28 29 ADAAAA CKFAAA OOOOxx
+6431 3643 1 3 1 11 31 431 431 1431 6431 62 63 JNAAAA DKFAAA VVVVxx
+3251 3644 1 3 1 11 51 251 1251 3251 3251 102 103 BVAAAA EKFAAA AAAAxx
+8439 3645 1 3 9 19 39 439 439 3439 8439 78 79 PMAAAA FKFAAA HHHHxx
+831 3646 1 3 1 11 31 831 831 831 831 62 63 ZFAAAA GKFAAA OOOOxx
+8485 3647 1 1 5 5 85 485 485 3485 8485 170 171 JOAAAA HKFAAA VVVVxx
+7314 3648 0 2 4 14 14 314 1314 2314 7314 28 29 IVAAAA IKFAAA AAAAxx
+3044 3649 0 0 4 4 44 44 1044 3044 3044 88 89 CNAAAA JKFAAA HHHHxx
+4283 3650 1 3 3 3 83 283 283 4283 4283 166 167 TIAAAA KKFAAA OOOOxx
+298 3651 0 2 8 18 98 298 298 298 298 196 197 MLAAAA LKFAAA VVVVxx
+7114 3652 0 2 4 14 14 114 1114 2114 7114 28 29 QNAAAA MKFAAA AAAAxx
+9664 3653 0 0 4 4 64 664 1664 4664 9664 128 129 SHAAAA NKFAAA HHHHxx
+5315 3654 1 3 5 15 15 315 1315 315 5315 30 31 LWAAAA OKFAAA OOOOxx
+2164 3655 0 0 4 4 64 164 164 2164 2164 128 129 GFAAAA PKFAAA VVVVxx
+3390 3656 0 2 0 10 90 390 1390 3390 3390 180 181 KAAAAA QKFAAA AAAAxx
+836 3657 0 0 6 16 36 836 836 836 836 72 73 EGAAAA RKFAAA HHHHxx
+3316 3658 0 0 6 16 16 316 1316 3316 3316 32 33 OXAAAA SKFAAA OOOOxx
+1284 3659 0 0 4 4 84 284 1284 1284 1284 168 169 KXAAAA TKFAAA VVVVxx
+2497 3660 1 1 7 17 97 497 497 2497 2497 194 195 BSAAAA UKFAAA AAAAxx
+1374 3661 0 2 4 14 74 374 1374 1374 1374 148 149 WAAAAA VKFAAA HHHHxx
+9525 3662 1 1 5 5 25 525 1525 4525 9525 50 51 JCAAAA WKFAAA OOOOxx
+2911 3663 1 3 1 11 11 911 911 2911 2911 22 23 ZHAAAA XKFAAA VVVVxx
+9686 3664 0 2 6 6 86 686 1686 4686 9686 172 173 OIAAAA YKFAAA AAAAxx
+584 3665 0 0 4 4 84 584 584 584 584 168 169 MWAAAA ZKFAAA HHHHxx
+5653 3666 1 1 3 13 53 653 1653 653 5653 106 107 LJAAAA ALFAAA OOOOxx
+4986 3667 0 2 6 6 86 986 986 4986 4986 172 173 UJAAAA BLFAAA VVVVxx
+6049 3668 1 1 9 9 49 49 49 1049 6049 98 99 RYAAAA CLFAAA AAAAxx
+9891 3669 1 3 1 11 91 891 1891 4891 9891 182 183 LQAAAA DLFAAA HHHHxx
+8809 3670 1 1 9 9 9 809 809 3809 8809 18 19 VAAAAA ELFAAA OOOOxx
+8598 3671 0 2 8 18 98 598 598 3598 8598 196 197 SSAAAA FLFAAA VVVVxx
+2573 3672 1 1 3 13 73 573 573 2573 2573 146 147 ZUAAAA GLFAAA AAAAxx
+6864 3673 0 0 4 4 64 864 864 1864 6864 128 129 AEAAAA HLFAAA HHHHxx
+7932 3674 0 0 2 12 32 932 1932 2932 7932 64 65 CTAAAA ILFAAA OOOOxx
+6605 3675 1 1 5 5 5 605 605 1605 6605 10 11 BUAAAA JLFAAA VVVVxx
+9500 3676 0 0 0 0 0 500 1500 4500 9500 0 1 KBAAAA KLFAAA AAAAxx
+8742 3677 0 2 2 2 42 742 742 3742 8742 84 85 GYAAAA LLFAAA HHHHxx
+9815 3678 1 3 5 15 15 815 1815 4815 9815 30 31 NNAAAA MLFAAA OOOOxx
+3319 3679 1 3 9 19 19 319 1319 3319 3319 38 39 RXAAAA NLFAAA VVVVxx
+184 3680 0 0 4 4 84 184 184 184 184 168 169 CHAAAA OLFAAA AAAAxx
+8886 3681 0 2 6 6 86 886 886 3886 8886 172 173 UDAAAA PLFAAA HHHHxx
+7050 3682 0 2 0 10 50 50 1050 2050 7050 100 101 ELAAAA QLFAAA OOOOxx
+9781 3683 1 1 1 1 81 781 1781 4781 9781 162 163 FMAAAA RLFAAA VVVVxx
+2443 3684 1 3 3 3 43 443 443 2443 2443 86 87 ZPAAAA SLFAAA AAAAxx
+1160 3685 0 0 0 0 60 160 1160 1160 1160 120 121 QSAAAA TLFAAA HHHHxx
+4600 3686 0 0 0 0 0 600 600 4600 4600 0 1 YUAAAA ULFAAA OOOOxx
+813 3687 1 1 3 13 13 813 813 813 813 26 27 HFAAAA VLFAAA VVVVxx
+5078 3688 0 2 8 18 78 78 1078 78 5078 156 157 INAAAA WLFAAA AAAAxx
+9008 3689 0 0 8 8 8 8 1008 4008 9008 16 17 MIAAAA XLFAAA HHHHxx
+9016 3690 0 0 6 16 16 16 1016 4016 9016 32 33 UIAAAA YLFAAA OOOOxx
+2747 3691 1 3 7 7 47 747 747 2747 2747 94 95 RBAAAA ZLFAAA VVVVxx
+3106 3692 0 2 6 6 6 106 1106 3106 3106 12 13 MPAAAA AMFAAA AAAAxx
+8235 3693 1 3 5 15 35 235 235 3235 8235 70 71 TEAAAA BMFAAA HHHHxx
+5582 3694 0 2 2 2 82 582 1582 582 5582 164 165 SGAAAA CMFAAA OOOOxx
+4334 3695 0 2 4 14 34 334 334 4334 4334 68 69 SKAAAA DMFAAA VVVVxx
+1612 3696 0 0 2 12 12 612 1612 1612 1612 24 25 AKAAAA EMFAAA AAAAxx
+5650 3697 0 2 0 10 50 650 1650 650 5650 100 101 IJAAAA FMFAAA HHHHxx
+6086 3698 0 2 6 6 86 86 86 1086 6086 172 173 CAAAAA GMFAAA OOOOxx
+9667 3699 1 3 7 7 67 667 1667 4667 9667 134 135 VHAAAA HMFAAA VVVVxx
+4215 3700 1 3 5 15 15 215 215 4215 4215 30 31 DGAAAA IMFAAA AAAAxx
+8553 3701 1 1 3 13 53 553 553 3553 8553 106 107 ZQAAAA JMFAAA HHHHxx
+9066 3702 0 2 6 6 66 66 1066 4066 9066 132 133 SKAAAA KMFAAA OOOOxx
+1092 3703 0 0 2 12 92 92 1092 1092 1092 184 185 AQAAAA LMFAAA VVVVxx
+2848 3704 0 0 8 8 48 848 848 2848 2848 96 97 OFAAAA MMFAAA AAAAxx
+2765 3705 1 1 5 5 65 765 765 2765 2765 130 131 JCAAAA NMFAAA HHHHxx
+6513 3706 1 1 3 13 13 513 513 1513 6513 26 27 NQAAAA OMFAAA OOOOxx
+6541 3707 1 1 1 1 41 541 541 1541 6541 82 83 PRAAAA PMFAAA VVVVxx
+9617 3708 1 1 7 17 17 617 1617 4617 9617 34 35 XFAAAA QMFAAA AAAAxx
+5870 3709 0 2 0 10 70 870 1870 870 5870 140 141 URAAAA RMFAAA HHHHxx
+8811 3710 1 3 1 11 11 811 811 3811 8811 22 23 XAAAAA SMFAAA OOOOxx
+4529 3711 1 1 9 9 29 529 529 4529 4529 58 59 FSAAAA TMFAAA VVVVxx
+161 3712 1 1 1 1 61 161 161 161 161 122 123 FGAAAA UMFAAA AAAAxx
+641 3713 1 1 1 1 41 641 641 641 641 82 83 RYAAAA VMFAAA HHHHxx
+4767 3714 1 3 7 7 67 767 767 4767 4767 134 135 JBAAAA WMFAAA OOOOxx
+6293 3715 1 1 3 13 93 293 293 1293 6293 186 187 BIAAAA XMFAAA VVVVxx
+3816 3716 0 0 6 16 16 816 1816 3816 3816 32 33 UQAAAA YMFAAA AAAAxx
+4748 3717 0 0 8 8 48 748 748 4748 4748 96 97 QAAAAA ZMFAAA HHHHxx
+9924 3718 0 0 4 4 24 924 1924 4924 9924 48 49 SRAAAA ANFAAA OOOOxx
+6716 3719 0 0 6 16 16 716 716 1716 6716 32 33 IYAAAA BNFAAA VVVVxx
+8828 3720 0 0 8 8 28 828 828 3828 8828 56 57 OBAAAA CNFAAA AAAAxx
+4967 3721 1 3 7 7 67 967 967 4967 4967 134 135 BJAAAA DNFAAA HHHHxx
+9680 3722 0 0 0 0 80 680 1680 4680 9680 160 161 IIAAAA ENFAAA OOOOxx
+2784 3723 0 0 4 4 84 784 784 2784 2784 168 169 CDAAAA FNFAAA VVVVxx
+2882 3724 0 2 2 2 82 882 882 2882 2882 164 165 WGAAAA GNFAAA AAAAxx
+3641 3725 1 1 1 1 41 641 1641 3641 3641 82 83 BKAAAA HNFAAA HHHHxx
+5537 3726 1 1 7 17 37 537 1537 537 5537 74 75 ZEAAAA INFAAA OOOOxx
+820 3727 0 0 0 0 20 820 820 820 820 40 41 OFAAAA JNFAAA VVVVxx
+5847 3728 1 3 7 7 47 847 1847 847 5847 94 95 XQAAAA KNFAAA AAAAxx
+566 3729 0 2 6 6 66 566 566 566 566 132 133 UVAAAA LNFAAA HHHHxx
+2246 3730 0 2 6 6 46 246 246 2246 2246 92 93 KIAAAA MNFAAA OOOOxx
+6680 3731 0 0 0 0 80 680 680 1680 6680 160 161 YWAAAA NNFAAA VVVVxx
+2014 3732 0 2 4 14 14 14 14 2014 2014 28 29 MZAAAA ONFAAA AAAAxx
+8355 3733 1 3 5 15 55 355 355 3355 8355 110 111 JJAAAA PNFAAA HHHHxx
+1610 3734 0 2 0 10 10 610 1610 1610 1610 20 21 YJAAAA QNFAAA OOOOxx
+9719 3735 1 3 9 19 19 719 1719 4719 9719 38 39 VJAAAA RNFAAA VVVVxx
+8498 3736 0 2 8 18 98 498 498 3498 8498 196 197 WOAAAA SNFAAA AAAAxx
+5883 3737 1 3 3 3 83 883 1883 883 5883 166 167 HSAAAA TNFAAA HHHHxx
+7380 3738 0 0 0 0 80 380 1380 2380 7380 160 161 WXAAAA UNFAAA OOOOxx
+8865 3739 1 1 5 5 65 865 865 3865 8865 130 131 ZCAAAA VNFAAA VVVVxx
+4743 3740 1 3 3 3 43 743 743 4743 4743 86 87 LAAAAA WNFAAA AAAAxx
+5086 3741 0 2 6 6 86 86 1086 86 5086 172 173 QNAAAA XNFAAA HHHHxx
+2739 3742 1 3 9 19 39 739 739 2739 2739 78 79 JBAAAA YNFAAA OOOOxx
+9375 3743 1 3 5 15 75 375 1375 4375 9375 150 151 PWAAAA ZNFAAA VVVVxx
+7876 3744 0 0 6 16 76 876 1876 2876 7876 152 153 YQAAAA AOFAAA AAAAxx
+453 3745 1 1 3 13 53 453 453 453 453 106 107 LRAAAA BOFAAA HHHHxx
+6987 3746 1 3 7 7 87 987 987 1987 6987 174 175 TIAAAA COFAAA OOOOxx
+2860 3747 0 0 0 0 60 860 860 2860 2860 120 121 AGAAAA DOFAAA VVVVxx
+8372 3748 0 0 2 12 72 372 372 3372 8372 144 145 AKAAAA EOFAAA AAAAxx
+2048 3749 0 0 8 8 48 48 48 2048 2048 96 97 UAAAAA FOFAAA HHHHxx
+9231 3750 1 3 1 11 31 231 1231 4231 9231 62 63 BRAAAA GOFAAA OOOOxx
+634 3751 0 2 4 14 34 634 634 634 634 68 69 KYAAAA HOFAAA VVVVxx
+3998 3752 0 2 8 18 98 998 1998 3998 3998 196 197 UXAAAA IOFAAA AAAAxx
+4728 3753 0 0 8 8 28 728 728 4728 4728 56 57 WZAAAA JOFAAA HHHHxx
+579 3754 1 3 9 19 79 579 579 579 579 158 159 HWAAAA KOFAAA OOOOxx
+815 3755 1 3 5 15 15 815 815 815 815 30 31 JFAAAA LOFAAA VVVVxx
+1009 3756 1 1 9 9 9 9 1009 1009 1009 18 19 VMAAAA MOFAAA AAAAxx
+6596 3757 0 0 6 16 96 596 596 1596 6596 192 193 STAAAA NOFAAA HHHHxx
+2793 3758 1 1 3 13 93 793 793 2793 2793 186 187 LDAAAA OOFAAA OOOOxx
+9589 3759 1 1 9 9 89 589 1589 4589 9589 178 179 VEAAAA POFAAA VVVVxx
+2794 3760 0 2 4 14 94 794 794 2794 2794 188 189 MDAAAA QOFAAA AAAAxx
+2551 3761 1 3 1 11 51 551 551 2551 2551 102 103 DUAAAA ROFAAA HHHHxx
+1588 3762 0 0 8 8 88 588 1588 1588 1588 176 177 CJAAAA SOFAAA OOOOxx
+4443 3763 1 3 3 3 43 443 443 4443 4443 86 87 XOAAAA TOFAAA VVVVxx
+5009 3764 1 1 9 9 9 9 1009 9 5009 18 19 RKAAAA UOFAAA AAAAxx
+4287 3765 1 3 7 7 87 287 287 4287 4287 174 175 XIAAAA VOFAAA HHHHxx
+2167 3766 1 3 7 7 67 167 167 2167 2167 134 135 JFAAAA WOFAAA OOOOxx
+2290 3767 0 2 0 10 90 290 290 2290 2290 180 181 CKAAAA XOFAAA VVVVxx
+7225 3768 1 1 5 5 25 225 1225 2225 7225 50 51 XRAAAA YOFAAA AAAAxx
+8992 3769 0 0 2 12 92 992 992 3992 8992 184 185 WHAAAA ZOFAAA HHHHxx
+1540 3770 0 0 0 0 40 540 1540 1540 1540 80 81 GHAAAA APFAAA OOOOxx
+2029 3771 1 1 9 9 29 29 29 2029 2029 58 59 BAAAAA BPFAAA VVVVxx
+2855 3772 1 3 5 15 55 855 855 2855 2855 110 111 VFAAAA CPFAAA AAAAxx
+3534 3773 0 2 4 14 34 534 1534 3534 3534 68 69 YFAAAA DPFAAA HHHHxx
+8078 3774 0 2 8 18 78 78 78 3078 8078 156 157 SYAAAA EPFAAA OOOOxx
+9778 3775 0 2 8 18 78 778 1778 4778 9778 156 157 CMAAAA FPFAAA VVVVxx
+3543 3776 1 3 3 3 43 543 1543 3543 3543 86 87 HGAAAA GPFAAA AAAAxx
+4778 3777 0 2 8 18 78 778 778 4778 4778 156 157 UBAAAA HPFAAA HHHHxx
+8931 3778 1 3 1 11 31 931 931 3931 8931 62 63 NFAAAA IPFAAA OOOOxx
+557 3779 1 1 7 17 57 557 557 557 557 114 115 LVAAAA JPFAAA VVVVxx
+5546 3780 0 2 6 6 46 546 1546 546 5546 92 93 IFAAAA KPFAAA AAAAxx
+7527 3781 1 3 7 7 27 527 1527 2527 7527 54 55 NDAAAA LPFAAA HHHHxx
+5000 3782 0 0 0 0 0 0 1000 0 5000 0 1 IKAAAA MPFAAA OOOOxx
+7587 3783 1 3 7 7 87 587 1587 2587 7587 174 175 VFAAAA NPFAAA VVVVxx
+3014 3784 0 2 4 14 14 14 1014 3014 3014 28 29 YLAAAA OPFAAA AAAAxx
+5276 3785 0 0 6 16 76 276 1276 276 5276 152 153 YUAAAA PPFAAA HHHHxx
+6457 3786 1 1 7 17 57 457 457 1457 6457 114 115 JOAAAA QPFAAA OOOOxx
+389 3787 1 1 9 9 89 389 389 389 389 178 179 ZOAAAA RPFAAA VVVVxx
+7104 3788 0 0 4 4 4 104 1104 2104 7104 8 9 GNAAAA SPFAAA AAAAxx
+9995 3789 1 3 5 15 95 995 1995 4995 9995 190 191 LUAAAA TPFAAA HHHHxx
+7368 3790 0 0 8 8 68 368 1368 2368 7368 136 137 KXAAAA UPFAAA OOOOxx
+3258 3791 0 2 8 18 58 258 1258 3258 3258 116 117 IVAAAA VPFAAA VVVVxx
+9208 3792 0 0 8 8 8 208 1208 4208 9208 16 17 EQAAAA WPFAAA AAAAxx
+2396 3793 0 0 6 16 96 396 396 2396 2396 192 193 EOAAAA XPFAAA HHHHxx
+1715 3794 1 3 5 15 15 715 1715 1715 1715 30 31 ZNAAAA YPFAAA OOOOxx
+1240 3795 0 0 0 0 40 240 1240 1240 1240 80 81 SVAAAA ZPFAAA VVVVxx
+1952 3796 0 0 2 12 52 952 1952 1952 1952 104 105 CXAAAA AQFAAA AAAAxx
+4403 3797 1 3 3 3 3 403 403 4403 4403 6 7 JNAAAA BQFAAA HHHHxx
+6333 3798 1 1 3 13 33 333 333 1333 6333 66 67 PJAAAA CQFAAA OOOOxx
+2492 3799 0 0 2 12 92 492 492 2492 2492 184 185 WRAAAA DQFAAA VVVVxx
+6543 3800 1 3 3 3 43 543 543 1543 6543 86 87 RRAAAA EQFAAA AAAAxx
+5548 3801 0 0 8 8 48 548 1548 548 5548 96 97 KFAAAA FQFAAA HHHHxx
+3458 3802 0 2 8 18 58 458 1458 3458 3458 116 117 ADAAAA GQFAAA OOOOxx
+2588 3803 0 0 8 8 88 588 588 2588 2588 176 177 OVAAAA HQFAAA VVVVxx
+1364 3804 0 0 4 4 64 364 1364 1364 1364 128 129 MAAAAA IQFAAA AAAAxx
+9856 3805 0 0 6 16 56 856 1856 4856 9856 112 113 CPAAAA JQFAAA HHHHxx
+4964 3806 0 0 4 4 64 964 964 4964 4964 128 129 YIAAAA KQFAAA OOOOxx
+773 3807 1 1 3 13 73 773 773 773 773 146 147 TDAAAA LQFAAA VVVVxx
+6402 3808 0 2 2 2 2 402 402 1402 6402 4 5 GMAAAA MQFAAA AAAAxx
+7213 3809 1 1 3 13 13 213 1213 2213 7213 26 27 LRAAAA NQFAAA HHHHxx
+3385 3810 1 1 5 5 85 385 1385 3385 3385 170 171 FAAAAA OQFAAA OOOOxx
+6005 3811 1 1 5 5 5 5 5 1005 6005 10 11 ZWAAAA PQFAAA VVVVxx
+9346 3812 0 2 6 6 46 346 1346 4346 9346 92 93 MVAAAA QQFAAA AAAAxx
+1831 3813 1 3 1 11 31 831 1831 1831 1831 62 63 LSAAAA RQFAAA HHHHxx
+5406 3814 0 2 6 6 6 406 1406 406 5406 12 13 YZAAAA SQFAAA OOOOxx
+2154 3815 0 2 4 14 54 154 154 2154 2154 108 109 WEAAAA TQFAAA VVVVxx
+3721 3816 1 1 1 1 21 721 1721 3721 3721 42 43 DNAAAA UQFAAA AAAAxx
+2889 3817 1 1 9 9 89 889 889 2889 2889 178 179 DHAAAA VQFAAA HHHHxx
+4410 3818 0 2 0 10 10 410 410 4410 4410 20 21 QNAAAA WQFAAA OOOOxx
+7102 3819 0 2 2 2 2 102 1102 2102 7102 4 5 ENAAAA XQFAAA VVVVxx
+4057 3820 1 1 7 17 57 57 57 4057 4057 114 115 BAAAAA YQFAAA AAAAxx
+9780 3821 0 0 0 0 80 780 1780 4780 9780 160 161 EMAAAA ZQFAAA HHHHxx
+9481 3822 1 1 1 1 81 481 1481 4481 9481 162 163 RAAAAA ARFAAA OOOOxx
+2366 3823 0 2 6 6 66 366 366 2366 2366 132 133 ANAAAA BRFAAA VVVVxx
+2708 3824 0 0 8 8 8 708 708 2708 2708 16 17 EAAAAA CRFAAA AAAAxx
+7399 3825 1 3 9 19 99 399 1399 2399 7399 198 199 PYAAAA DRFAAA HHHHxx
+5234 3826 0 2 4 14 34 234 1234 234 5234 68 69 ITAAAA ERFAAA OOOOxx
+1843 3827 1 3 3 3 43 843 1843 1843 1843 86 87 XSAAAA FRFAAA VVVVxx
+1006 3828 0 2 6 6 6 6 1006 1006 1006 12 13 SMAAAA GRFAAA AAAAxx
+7696 3829 0 0 6 16 96 696 1696 2696 7696 192 193 AKAAAA HRFAAA HHHHxx
+6411 3830 1 3 1 11 11 411 411 1411 6411 22 23 PMAAAA IRFAAA OOOOxx
+3913 3831 1 1 3 13 13 913 1913 3913 3913 26 27 NUAAAA JRFAAA VVVVxx
+2538 3832 0 2 8 18 38 538 538 2538 2538 76 77 QTAAAA KRFAAA AAAAxx
+3019 3833 1 3 9 19 19 19 1019 3019 3019 38 39 DMAAAA LRFAAA HHHHxx
+107 3834 1 3 7 7 7 107 107 107 107 14 15 DEAAAA MRFAAA OOOOxx
+427 3835 1 3 7 7 27 427 427 427 427 54 55 LQAAAA NRFAAA VVVVxx
+9849 3836 1 1 9 9 49 849 1849 4849 9849 98 99 VOAAAA ORFAAA AAAAxx
+4195 3837 1 3 5 15 95 195 195 4195 4195 190 191 JFAAAA PRFAAA HHHHxx
+9215 3838 1 3 5 15 15 215 1215 4215 9215 30 31 LQAAAA QRFAAA OOOOxx
+3165 3839 1 1 5 5 65 165 1165 3165 3165 130 131 TRAAAA RRFAAA VVVVxx
+3280 3840 0 0 0 0 80 280 1280 3280 3280 160 161 EWAAAA SRFAAA AAAAxx
+4477 3841 1 1 7 17 77 477 477 4477 4477 154 155 FQAAAA TRFAAA HHHHxx
+5885 3842 1 1 5 5 85 885 1885 885 5885 170 171 JSAAAA URFAAA OOOOxx
+3311 3843 1 3 1 11 11 311 1311 3311 3311 22 23 JXAAAA VRFAAA VVVVxx
+6453 3844 1 1 3 13 53 453 453 1453 6453 106 107 FOAAAA WRFAAA AAAAxx
+8527 3845 1 3 7 7 27 527 527 3527 8527 54 55 ZPAAAA XRFAAA HHHHxx
+1921 3846 1 1 1 1 21 921 1921 1921 1921 42 43 XVAAAA YRFAAA OOOOxx
+2427 3847 1 3 7 7 27 427 427 2427 2427 54 55 JPAAAA ZRFAAA VVVVxx
+3691 3848 1 3 1 11 91 691 1691 3691 3691 182 183 ZLAAAA ASFAAA AAAAxx
+3882 3849 0 2 2 2 82 882 1882 3882 3882 164 165 ITAAAA BSFAAA HHHHxx
+562 3850 0 2 2 2 62 562 562 562 562 124 125 QVAAAA CSFAAA OOOOxx
+377 3851 1 1 7 17 77 377 377 377 377 154 155 NOAAAA DSFAAA VVVVxx
+1497 3852 1 1 7 17 97 497 1497 1497 1497 194 195 PFAAAA ESFAAA AAAAxx
+4453 3853 1 1 3 13 53 453 453 4453 4453 106 107 HPAAAA FSFAAA HHHHxx
+4678 3854 0 2 8 18 78 678 678 4678 4678 156 157 YXAAAA GSFAAA OOOOxx
+2234 3855 0 2 4 14 34 234 234 2234 2234 68 69 YHAAAA HSFAAA VVVVxx
+1073 3856 1 1 3 13 73 73 1073 1073 1073 146 147 HPAAAA ISFAAA AAAAxx
+6479 3857 1 3 9 19 79 479 479 1479 6479 158 159 FPAAAA JSFAAA HHHHxx
+5665 3858 1 1 5 5 65 665 1665 665 5665 130 131 XJAAAA KSFAAA OOOOxx
+586 3859 0 2 6 6 86 586 586 586 586 172 173 OWAAAA LSFAAA VVVVxx
+1584 3860 0 0 4 4 84 584 1584 1584 1584 168 169 YIAAAA MSFAAA AAAAxx
+2574 3861 0 2 4 14 74 574 574 2574 2574 148 149 AVAAAA NSFAAA HHHHxx
+9833 3862 1 1 3 13 33 833 1833 4833 9833 66 67 FOAAAA OSFAAA OOOOxx
+6726 3863 0 2 6 6 26 726 726 1726 6726 52 53 SYAAAA PSFAAA VVVVxx
+8497 3864 1 1 7 17 97 497 497 3497 8497 194 195 VOAAAA QSFAAA AAAAxx
+2914 3865 0 2 4 14 14 914 914 2914 2914 28 29 CIAAAA RSFAAA HHHHxx
+8586 3866 0 2 6 6 86 586 586 3586 8586 172 173 GSAAAA SSFAAA OOOOxx
+6973 3867 1 1 3 13 73 973 973 1973 6973 146 147 FIAAAA TSFAAA VVVVxx
+1322 3868 0 2 2 2 22 322 1322 1322 1322 44 45 WYAAAA USFAAA AAAAxx
+5242 3869 0 2 2 2 42 242 1242 242 5242 84 85 QTAAAA VSFAAA HHHHxx
+5581 3870 1 1 1 1 81 581 1581 581 5581 162 163 RGAAAA WSFAAA OOOOxx
+1365 3871 1 1 5 5 65 365 1365 1365 1365 130 131 NAAAAA XSFAAA VVVVxx
+2818 3872 0 2 8 18 18 818 818 2818 2818 36 37 KEAAAA YSFAAA AAAAxx
+3758 3873 0 2 8 18 58 758 1758 3758 3758 116 117 OOAAAA ZSFAAA HHHHxx
+2665 3874 1 1 5 5 65 665 665 2665 2665 130 131 NYAAAA ATFAAA OOOOxx
+9823 3875 1 3 3 3 23 823 1823 4823 9823 46 47 VNAAAA BTFAAA VVVVxx
+7057 3876 1 1 7 17 57 57 1057 2057 7057 114 115 LLAAAA CTFAAA AAAAxx
+543 3877 1 3 3 3 43 543 543 543 543 86 87 XUAAAA DTFAAA HHHHxx
+4008 3878 0 0 8 8 8 8 8 4008 4008 16 17 EYAAAA ETFAAA OOOOxx
+4397 3879 1 1 7 17 97 397 397 4397 4397 194 195 DNAAAA FTFAAA VVVVxx
+8533 3880 1 1 3 13 33 533 533 3533 8533 66 67 FQAAAA GTFAAA AAAAxx
+9728 3881 0 0 8 8 28 728 1728 4728 9728 56 57 EKAAAA HTFAAA HHHHxx
+5198 3882 0 2 8 18 98 198 1198 198 5198 196 197 YRAAAA ITFAAA OOOOxx
+5036 3883 0 0 6 16 36 36 1036 36 5036 72 73 SLAAAA JTFAAA VVVVxx
+4394 3884 0 2 4 14 94 394 394 4394 4394 188 189 ANAAAA KTFAAA AAAAxx
+9633 3885 1 1 3 13 33 633 1633 4633 9633 66 67 NGAAAA LTFAAA HHHHxx
+3339 3886 1 3 9 19 39 339 1339 3339 3339 78 79 LYAAAA MTFAAA OOOOxx
+9529 3887 1 1 9 9 29 529 1529 4529 9529 58 59 NCAAAA NTFAAA VVVVxx
+4780 3888 0 0 0 0 80 780 780 4780 4780 160 161 WBAAAA OTFAAA AAAAxx
+4862 3889 0 2 2 2 62 862 862 4862 4862 124 125 AFAAAA PTFAAA HHHHxx
+8152 3890 0 0 2 12 52 152 152 3152 8152 104 105 OBAAAA QTFAAA OOOOxx
+9330 3891 0 2 0 10 30 330 1330 4330 9330 60 61 WUAAAA RTFAAA VVVVxx
+4362 3892 0 2 2 2 62 362 362 4362 4362 124 125 ULAAAA STFAAA AAAAxx
+4688 3893 0 0 8 8 88 688 688 4688 4688 176 177 IYAAAA TTFAAA HHHHxx
+1903 3894 1 3 3 3 3 903 1903 1903 1903 6 7 FVAAAA UTFAAA OOOOxx
+9027 3895 1 3 7 7 27 27 1027 4027 9027 54 55 FJAAAA VTFAAA VVVVxx
+5385 3896 1 1 5 5 85 385 1385 385 5385 170 171 DZAAAA WTFAAA AAAAxx
+9854 3897 0 2 4 14 54 854 1854 4854 9854 108 109 APAAAA XTFAAA HHHHxx
+9033 3898 1 1 3 13 33 33 1033 4033 9033 66 67 LJAAAA YTFAAA OOOOxx
+3185 3899 1 1 5 5 85 185 1185 3185 3185 170 171 NSAAAA ZTFAAA VVVVxx
+2618 3900 0 2 8 18 18 618 618 2618 2618 36 37 SWAAAA AUFAAA AAAAxx
+371 3901 1 3 1 11 71 371 371 371 371 142 143 HOAAAA BUFAAA HHHHxx
+3697 3902 1 1 7 17 97 697 1697 3697 3697 194 195 FMAAAA CUFAAA OOOOxx
+1682 3903 0 2 2 2 82 682 1682 1682 1682 164 165 SMAAAA DUFAAA VVVVxx
+3333 3904 1 1 3 13 33 333 1333 3333 3333 66 67 FYAAAA EUFAAA AAAAxx
+1722 3905 0 2 2 2 22 722 1722 1722 1722 44 45 GOAAAA FUFAAA HHHHxx
+2009 3906 1 1 9 9 9 9 9 2009 2009 18 19 HZAAAA GUFAAA OOOOxx
+3517 3907 1 1 7 17 17 517 1517 3517 3517 34 35 HFAAAA HUFAAA VVVVxx
+7640 3908 0 0 0 0 40 640 1640 2640 7640 80 81 WHAAAA IUFAAA AAAAxx
+259 3909 1 3 9 19 59 259 259 259 259 118 119 ZJAAAA JUFAAA HHHHxx
+1400 3910 0 0 0 0 0 400 1400 1400 1400 0 1 WBAAAA KUFAAA OOOOxx
+6663 3911 1 3 3 3 63 663 663 1663 6663 126 127 HWAAAA LUFAAA VVVVxx
+1576 3912 0 0 6 16 76 576 1576 1576 1576 152 153 QIAAAA MUFAAA AAAAxx
+8843 3913 1 3 3 3 43 843 843 3843 8843 86 87 DCAAAA NUFAAA HHHHxx
+9474 3914 0 2 4 14 74 474 1474 4474 9474 148 149 KAAAAA OUFAAA OOOOxx
+1597 3915 1 1 7 17 97 597 1597 1597 1597 194 195 LJAAAA PUFAAA VVVVxx
+1143 3916 1 3 3 3 43 143 1143 1143 1143 86 87 ZRAAAA QUFAAA AAAAxx
+4162 3917 0 2 2 2 62 162 162 4162 4162 124 125 CEAAAA RUFAAA HHHHxx
+1301 3918 1 1 1 1 1 301 1301 1301 1301 2 3 BYAAAA SUFAAA OOOOxx
+2935 3919 1 3 5 15 35 935 935 2935 2935 70 71 XIAAAA TUFAAA VVVVxx
+886 3920 0 2 6 6 86 886 886 886 886 172 173 CIAAAA UUFAAA AAAAxx
+1661 3921 1 1 1 1 61 661 1661 1661 1661 122 123 XLAAAA VUFAAA HHHHxx
+1026 3922 0 2 6 6 26 26 1026 1026 1026 52 53 MNAAAA WUFAAA OOOOxx
+7034 3923 0 2 4 14 34 34 1034 2034 7034 68 69 OKAAAA XUFAAA VVVVxx
+2305 3924 1 1 5 5 5 305 305 2305 2305 10 11 RKAAAA YUFAAA AAAAxx
+1725 3925 1 1 5 5 25 725 1725 1725 1725 50 51 JOAAAA ZUFAAA HHHHxx
+909 3926 1 1 9 9 9 909 909 909 909 18 19 ZIAAAA AVFAAA OOOOxx
+9906 3927 0 2 6 6 6 906 1906 4906 9906 12 13 ARAAAA BVFAAA VVVVxx
+3309 3928 1 1 9 9 9 309 1309 3309 3309 18 19 HXAAAA CVFAAA AAAAxx
+515 3929 1 3 5 15 15 515 515 515 515 30 31 VTAAAA DVFAAA HHHHxx
+932 3930 0 0 2 12 32 932 932 932 932 64 65 WJAAAA EVFAAA OOOOxx
+8144 3931 0 0 4 4 44 144 144 3144 8144 88 89 GBAAAA FVFAAA VVVVxx
+5592 3932 0 0 2 12 92 592 1592 592 5592 184 185 CHAAAA GVFAAA AAAAxx
+4003 3933 1 3 3 3 3 3 3 4003 4003 6 7 ZXAAAA HVFAAA HHHHxx
+9566 3934 0 2 6 6 66 566 1566 4566 9566 132 133 YDAAAA IVFAAA OOOOxx
+4556 3935 0 0 6 16 56 556 556 4556 4556 112 113 GTAAAA JVFAAA VVVVxx
+268 3936 0 0 8 8 68 268 268 268 268 136 137 IKAAAA KVFAAA AAAAxx
+8107 3937 1 3 7 7 7 107 107 3107 8107 14 15 VZAAAA LVFAAA HHHHxx
+5816 3938 0 0 6 16 16 816 1816 816 5816 32 33 SPAAAA MVFAAA OOOOxx
+8597 3939 1 1 7 17 97 597 597 3597 8597 194 195 RSAAAA NVFAAA VVVVxx
+9611 3940 1 3 1 11 11 611 1611 4611 9611 22 23 RFAAAA OVFAAA AAAAxx
+8070 3941 0 2 0 10 70 70 70 3070 8070 140 141 KYAAAA PVFAAA HHHHxx
+6040 3942 0 0 0 0 40 40 40 1040 6040 80 81 IYAAAA QVFAAA OOOOxx
+3184 3943 0 0 4 4 84 184 1184 3184 3184 168 169 MSAAAA RVFAAA VVVVxx
+9656 3944 0 0 6 16 56 656 1656 4656 9656 112 113 KHAAAA SVFAAA AAAAxx
+1577 3945 1 1 7 17 77 577 1577 1577 1577 154 155 RIAAAA TVFAAA HHHHxx
+1805 3946 1 1 5 5 5 805 1805 1805 1805 10 11 LRAAAA UVFAAA OOOOxx
+8268 3947 0 0 8 8 68 268 268 3268 8268 136 137 AGAAAA VVFAAA VVVVxx
+3489 3948 1 1 9 9 89 489 1489 3489 3489 178 179 FEAAAA WVFAAA AAAAxx
+4564 3949 0 0 4 4 64 564 564 4564 4564 128 129 OTAAAA XVFAAA HHHHxx
+4006 3950 0 2 6 6 6 6 6 4006 4006 12 13 CYAAAA YVFAAA OOOOxx
+8466 3951 0 2 6 6 66 466 466 3466 8466 132 133 QNAAAA ZVFAAA VVVVxx
+938 3952 0 2 8 18 38 938 938 938 938 76 77 CKAAAA AWFAAA AAAAxx
+5944 3953 0 0 4 4 44 944 1944 944 5944 88 89 QUAAAA BWFAAA HHHHxx
+8363 3954 1 3 3 3 63 363 363 3363 8363 126 127 RJAAAA CWFAAA OOOOxx
+5348 3955 0 0 8 8 48 348 1348 348 5348 96 97 SXAAAA DWFAAA VVVVxx
+71 3956 1 3 1 11 71 71 71 71 71 142 143 TCAAAA EWFAAA AAAAxx
+3620 3957 0 0 0 0 20 620 1620 3620 3620 40 41 GJAAAA FWFAAA HHHHxx
+3230 3958 0 2 0 10 30 230 1230 3230 3230 60 61 GUAAAA GWFAAA OOOOxx
+6132 3959 0 0 2 12 32 132 132 1132 6132 64 65 WBAAAA HWFAAA VVVVxx
+6143 3960 1 3 3 3 43 143 143 1143 6143 86 87 HCAAAA IWFAAA AAAAxx
+8781 3961 1 1 1 1 81 781 781 3781 8781 162 163 TZAAAA JWFAAA HHHHxx
+5522 3962 0 2 2 2 22 522 1522 522 5522 44 45 KEAAAA KWFAAA OOOOxx
+6320 3963 0 0 0 0 20 320 320 1320 6320 40 41 CJAAAA LWFAAA VVVVxx
+3923 3964 1 3 3 3 23 923 1923 3923 3923 46 47 XUAAAA MWFAAA AAAAxx
+2207 3965 1 3 7 7 7 207 207 2207 2207 14 15 XGAAAA NWFAAA HHHHxx
+966 3966 0 2 6 6 66 966 966 966 966 132 133 ELAAAA OWFAAA OOOOxx
+9020 3967 0 0 0 0 20 20 1020 4020 9020 40 41 YIAAAA PWFAAA VVVVxx
+4616 3968 0 0 6 16 16 616 616 4616 4616 32 33 OVAAAA QWFAAA AAAAxx
+8289 3969 1 1 9 9 89 289 289 3289 8289 178 179 VGAAAA RWFAAA HHHHxx
+5796 3970 0 0 6 16 96 796 1796 796 5796 192 193 YOAAAA SWFAAA OOOOxx
+9259 3971 1 3 9 19 59 259 1259 4259 9259 118 119 DSAAAA TWFAAA VVVVxx
+3710 3972 0 2 0 10 10 710 1710 3710 3710 20 21 SMAAAA UWFAAA AAAAxx
+251 3973 1 3 1 11 51 251 251 251 251 102 103 RJAAAA VWFAAA HHHHxx
+7669 3974 1 1 9 9 69 669 1669 2669 7669 138 139 ZIAAAA WWFAAA OOOOxx
+6304 3975 0 0 4 4 4 304 304 1304 6304 8 9 MIAAAA XWFAAA VVVVxx
+6454 3976 0 2 4 14 54 454 454 1454 6454 108 109 GOAAAA YWFAAA AAAAxx
+1489 3977 1 1 9 9 89 489 1489 1489 1489 178 179 HFAAAA ZWFAAA HHHHxx
+715 3978 1 3 5 15 15 715 715 715 715 30 31 NBAAAA AXFAAA OOOOxx
+4319 3979 1 3 9 19 19 319 319 4319 4319 38 39 DKAAAA BXFAAA VVVVxx
+7112 3980 0 0 2 12 12 112 1112 2112 7112 24 25 ONAAAA CXFAAA AAAAxx
+3726 3981 0 2 6 6 26 726 1726 3726 3726 52 53 INAAAA DXFAAA HHHHxx
+7727 3982 1 3 7 7 27 727 1727 2727 7727 54 55 FLAAAA EXFAAA OOOOxx
+8387 3983 1 3 7 7 87 387 387 3387 8387 174 175 PKAAAA FXFAAA VVVVxx
+6555 3984 1 3 5 15 55 555 555 1555 6555 110 111 DSAAAA GXFAAA AAAAxx
+1148 3985 0 0 8 8 48 148 1148 1148 1148 96 97 ESAAAA HXFAAA HHHHxx
+9000 3986 0 0 0 0 0 0 1000 4000 9000 0 1 EIAAAA IXFAAA OOOOxx
+5278 3987 0 2 8 18 78 278 1278 278 5278 156 157 AVAAAA JXFAAA VVVVxx
+2388 3988 0 0 8 8 88 388 388 2388 2388 176 177 WNAAAA KXFAAA AAAAxx
+7984 3989 0 0 4 4 84 984 1984 2984 7984 168 169 CVAAAA LXFAAA HHHHxx
+881 3990 1 1 1 1 81 881 881 881 881 162 163 XHAAAA MXFAAA OOOOxx
+6830 3991 0 2 0 10 30 830 830 1830 6830 60 61 SCAAAA NXFAAA VVVVxx
+7056 3992 0 0 6 16 56 56 1056 2056 7056 112 113 KLAAAA OXFAAA AAAAxx
+7581 3993 1 1 1 1 81 581 1581 2581 7581 162 163 PFAAAA PXFAAA HHHHxx
+5214 3994 0 2 4 14 14 214 1214 214 5214 28 29 OSAAAA QXFAAA OOOOxx
+2505 3995 1 1 5 5 5 505 505 2505 2505 10 11 JSAAAA RXFAAA VVVVxx
+5112 3996 0 0 2 12 12 112 1112 112 5112 24 25 QOAAAA SXFAAA AAAAxx
+9884 3997 0 0 4 4 84 884 1884 4884 9884 168 169 EQAAAA TXFAAA HHHHxx
+8040 3998 0 0 0 0 40 40 40 3040 8040 80 81 GXAAAA UXFAAA OOOOxx
+7033 3999 1 1 3 13 33 33 1033 2033 7033 66 67 NKAAAA VXFAAA VVVVxx
+9343 4000 1 3 3 3 43 343 1343 4343 9343 86 87 JVAAAA WXFAAA AAAAxx
+2931 4001 1 3 1 11 31 931 931 2931 2931 62 63 TIAAAA XXFAAA HHHHxx
+9024 4002 0 0 4 4 24 24 1024 4024 9024 48 49 CJAAAA YXFAAA OOOOxx
+6485 4003 1 1 5 5 85 485 485 1485 6485 170 171 LPAAAA ZXFAAA VVVVxx
+3465 4004 1 1 5 5 65 465 1465 3465 3465 130 131 HDAAAA AYFAAA AAAAxx
+3357 4005 1 1 7 17 57 357 1357 3357 3357 114 115 DZAAAA BYFAAA HHHHxx
+2929 4006 1 1 9 9 29 929 929 2929 2929 58 59 RIAAAA CYFAAA OOOOxx
+3086 4007 0 2 6 6 86 86 1086 3086 3086 172 173 SOAAAA DYFAAA VVVVxx
+8897 4008 1 1 7 17 97 897 897 3897 8897 194 195 FEAAAA EYFAAA AAAAxx
+9688 4009 0 0 8 8 88 688 1688 4688 9688 176 177 QIAAAA FYFAAA HHHHxx
+6522 4010 0 2 2 2 22 522 522 1522 6522 44 45 WQAAAA GYFAAA OOOOxx
+3241 4011 1 1 1 1 41 241 1241 3241 3241 82 83 RUAAAA HYFAAA VVVVxx
+8770 4012 0 2 0 10 70 770 770 3770 8770 140 141 IZAAAA IYFAAA AAAAxx
+2884 4013 0 0 4 4 84 884 884 2884 2884 168 169 YGAAAA JYFAAA HHHHxx
+9579 4014 1 3 9 19 79 579 1579 4579 9579 158 159 LEAAAA KYFAAA OOOOxx
+3125 4015 1 1 5 5 25 125 1125 3125 3125 50 51 FQAAAA LYFAAA VVVVxx
+4604 4016 0 0 4 4 4 604 604 4604 4604 8 9 CVAAAA MYFAAA AAAAxx
+2682 4017 0 2 2 2 82 682 682 2682 2682 164 165 EZAAAA NYFAAA HHHHxx
+254 4018 0 2 4 14 54 254 254 254 254 108 109 UJAAAA OYFAAA OOOOxx
+6569 4019 1 1 9 9 69 569 569 1569 6569 138 139 RSAAAA PYFAAA VVVVxx
+2686 4020 0 2 6 6 86 686 686 2686 2686 172 173 IZAAAA QYFAAA AAAAxx
+2123 4021 1 3 3 3 23 123 123 2123 2123 46 47 RDAAAA RYFAAA HHHHxx
+1745 4022 1 1 5 5 45 745 1745 1745 1745 90 91 DPAAAA SYFAAA OOOOxx
+247 4023 1 3 7 7 47 247 247 247 247 94 95 NJAAAA TYFAAA VVVVxx
+5800 4024 0 0 0 0 0 800 1800 800 5800 0 1 CPAAAA UYFAAA AAAAxx
+1121 4025 1 1 1 1 21 121 1121 1121 1121 42 43 DRAAAA VYFAAA HHHHxx
+8893 4026 1 1 3 13 93 893 893 3893 8893 186 187 BEAAAA WYFAAA OOOOxx
+7819 4027 1 3 9 19 19 819 1819 2819 7819 38 39 TOAAAA XYFAAA VVVVxx
+1339 4028 1 3 9 19 39 339 1339 1339 1339 78 79 NZAAAA YYFAAA AAAAxx
+5680 4029 0 0 0 0 80 680 1680 680 5680 160 161 MKAAAA ZYFAAA HHHHxx
+5093 4030 1 1 3 13 93 93 1093 93 5093 186 187 XNAAAA AZFAAA OOOOxx
+3508 4031 0 0 8 8 8 508 1508 3508 3508 16 17 YEAAAA BZFAAA VVVVxx
+933 4032 1 1 3 13 33 933 933 933 933 66 67 XJAAAA CZFAAA AAAAxx
+1106 4033 0 2 6 6 6 106 1106 1106 1106 12 13 OQAAAA DZFAAA HHHHxx
+4386 4034 0 2 6 6 86 386 386 4386 4386 172 173 SMAAAA EZFAAA OOOOxx
+5895 4035 1 3 5 15 95 895 1895 895 5895 190 191 TSAAAA FZFAAA VVVVxx
+2980 4036 0 0 0 0 80 980 980 2980 2980 160 161 QKAAAA GZFAAA AAAAxx
+4400 4037 0 0 0 0 0 400 400 4400 4400 0 1 GNAAAA HZFAAA HHHHxx
+7433 4038 1 1 3 13 33 433 1433 2433 7433 66 67 XZAAAA IZFAAA OOOOxx
+6110 4039 0 2 0 10 10 110 110 1110 6110 20 21 ABAAAA JZFAAA VVVVxx
+867 4040 1 3 7 7 67 867 867 867 867 134 135 JHAAAA KZFAAA AAAAxx
+5292 4041 0 0 2 12 92 292 1292 292 5292 184 185 OVAAAA LZFAAA HHHHxx
+3926 4042 0 2 6 6 26 926 1926 3926 3926 52 53 AVAAAA MZFAAA OOOOxx
+1107 4043 1 3 7 7 7 107 1107 1107 1107 14 15 PQAAAA NZFAAA VVVVxx
+7355 4044 1 3 5 15 55 355 1355 2355 7355 110 111 XWAAAA OZFAAA AAAAxx
+4689 4045 1 1 9 9 89 689 689 4689 4689 178 179 JYAAAA PZFAAA HHHHxx
+4872 4046 0 0 2 12 72 872 872 4872 4872 144 145 KFAAAA QZFAAA OOOOxx
+7821 4047 1 1 1 1 21 821 1821 2821 7821 42 43 VOAAAA RZFAAA VVVVxx
+7277 4048 1 1 7 17 77 277 1277 2277 7277 154 155 XTAAAA SZFAAA AAAAxx
+3268 4049 0 0 8 8 68 268 1268 3268 3268 136 137 SVAAAA TZFAAA HHHHxx
+8877 4050 1 1 7 17 77 877 877 3877 8877 154 155 LDAAAA UZFAAA OOOOxx
+343 4051 1 3 3 3 43 343 343 343 343 86 87 FNAAAA VZFAAA VVVVxx
+621 4052 1 1 1 1 21 621 621 621 621 42 43 XXAAAA WZFAAA AAAAxx
+5429 4053 1 1 9 9 29 429 1429 429 5429 58 59 VAAAAA XZFAAA HHHHxx
+392 4054 0 0 2 12 92 392 392 392 392 184 185 CPAAAA YZFAAA OOOOxx
+6004 4055 0 0 4 4 4 4 4 1004 6004 8 9 YWAAAA ZZFAAA VVVVxx
+6377 4056 1 1 7 17 77 377 377 1377 6377 154 155 HLAAAA AAGAAA AAAAxx
+3037 4057 1 1 7 17 37 37 1037 3037 3037 74 75 VMAAAA BAGAAA HHHHxx
+3514 4058 0 2 4 14 14 514 1514 3514 3514 28 29 EFAAAA CAGAAA OOOOxx
+8740 4059 0 0 0 0 40 740 740 3740 8740 80 81 EYAAAA DAGAAA VVVVxx
+3877 4060 1 1 7 17 77 877 1877 3877 3877 154 155 DTAAAA EAGAAA AAAAxx
+5731 4061 1 3 1 11 31 731 1731 731 5731 62 63 LMAAAA FAGAAA HHHHxx
+6407 4062 1 3 7 7 7 407 407 1407 6407 14 15 LMAAAA GAGAAA OOOOxx
+2044 4063 0 0 4 4 44 44 44 2044 2044 88 89 QAAAAA HAGAAA VVVVxx
+7362 4064 0 2 2 2 62 362 1362 2362 7362 124 125 EXAAAA IAGAAA AAAAxx
+5458 4065 0 2 8 18 58 458 1458 458 5458 116 117 YBAAAA JAGAAA HHHHxx
+6437 4066 1 1 7 17 37 437 437 1437 6437 74 75 PNAAAA KAGAAA OOOOxx
+1051 4067 1 3 1 11 51 51 1051 1051 1051 102 103 LOAAAA LAGAAA VVVVxx
+1203 4068 1 3 3 3 3 203 1203 1203 1203 6 7 HUAAAA MAGAAA AAAAxx
+2176 4069 0 0 6 16 76 176 176 2176 2176 152 153 SFAAAA NAGAAA HHHHxx
+8997 4070 1 1 7 17 97 997 997 3997 8997 194 195 BIAAAA OAGAAA OOOOxx
+6378 4071 0 2 8 18 78 378 378 1378 6378 156 157 ILAAAA PAGAAA VVVVxx
+6006 4072 0 2 6 6 6 6 6 1006 6006 12 13 AXAAAA QAGAAA AAAAxx
+2308 4073 0 0 8 8 8 308 308 2308 2308 16 17 UKAAAA RAGAAA HHHHxx
+625 4074 1 1 5 5 25 625 625 625 625 50 51 BYAAAA SAGAAA OOOOxx
+7298 4075 0 2 8 18 98 298 1298 2298 7298 196 197 SUAAAA TAGAAA VVVVxx
+5575 4076 1 3 5 15 75 575 1575 575 5575 150 151 LGAAAA UAGAAA AAAAxx
+3565 4077 1 1 5 5 65 565 1565 3565 3565 130 131 DHAAAA VAGAAA HHHHxx
+47 4078 1 3 7 7 47 47 47 47 47 94 95 VBAAAA WAGAAA OOOOxx
+2413 4079 1 1 3 13 13 413 413 2413 2413 26 27 VOAAAA XAGAAA VVVVxx
+2153 4080 1 1 3 13 53 153 153 2153 2153 106 107 VEAAAA YAGAAA AAAAxx
+752 4081 0 0 2 12 52 752 752 752 752 104 105 YCAAAA ZAGAAA HHHHxx
+4095 4082 1 3 5 15 95 95 95 4095 4095 190 191 NBAAAA ABGAAA OOOOxx
+2518 4083 0 2 8 18 18 518 518 2518 2518 36 37 WSAAAA BBGAAA VVVVxx
+3681 4084 1 1 1 1 81 681 1681 3681 3681 162 163 PLAAAA CBGAAA AAAAxx
+4213 4085 1 1 3 13 13 213 213 4213 4213 26 27 BGAAAA DBGAAA HHHHxx
+2615 4086 1 3 5 15 15 615 615 2615 2615 30 31 PWAAAA EBGAAA OOOOxx
+1471 4087 1 3 1 11 71 471 1471 1471 1471 142 143 PEAAAA FBGAAA VVVVxx
+7315 4088 1 3 5 15 15 315 1315 2315 7315 30 31 JVAAAA GBGAAA AAAAxx
+6013 4089 1 1 3 13 13 13 13 1013 6013 26 27 HXAAAA HBGAAA HHHHxx
+3077 4090 1 1 7 17 77 77 1077 3077 3077 154 155 JOAAAA IBGAAA OOOOxx
+2190 4091 0 2 0 10 90 190 190 2190 2190 180 181 GGAAAA JBGAAA VVVVxx
+528 4092 0 0 8 8 28 528 528 528 528 56 57 IUAAAA KBGAAA AAAAxx
+9508 4093 0 0 8 8 8 508 1508 4508 9508 16 17 SBAAAA LBGAAA HHHHxx
+2473 4094 1 1 3 13 73 473 473 2473 2473 146 147 DRAAAA MBGAAA OOOOxx
+167 4095 1 3 7 7 67 167 167 167 167 134 135 LGAAAA NBGAAA VVVVxx
+8448 4096 0 0 8 8 48 448 448 3448 8448 96 97 YMAAAA OBGAAA AAAAxx
+7538 4097 0 2 8 18 38 538 1538 2538 7538 76 77 YDAAAA PBGAAA HHHHxx
+7638 4098 0 2 8 18 38 638 1638 2638 7638 76 77 UHAAAA QBGAAA OOOOxx
+4328 4099 0 0 8 8 28 328 328 4328 4328 56 57 MKAAAA RBGAAA VVVVxx
+3812 4100 0 0 2 12 12 812 1812 3812 3812 24 25 QQAAAA SBGAAA AAAAxx
+2879 4101 1 3 9 19 79 879 879 2879 2879 158 159 TGAAAA TBGAAA HHHHxx
+4741 4102 1 1 1 1 41 741 741 4741 4741 82 83 JAAAAA UBGAAA OOOOxx
+9155 4103 1 3 5 15 55 155 1155 4155 9155 110 111 DOAAAA VBGAAA VVVVxx
+5151 4104 1 3 1 11 51 151 1151 151 5151 102 103 DQAAAA WBGAAA AAAAxx
+5591 4105 1 3 1 11 91 591 1591 591 5591 182 183 BHAAAA XBGAAA HHHHxx
+1034 4106 0 2 4 14 34 34 1034 1034 1034 68 69 UNAAAA YBGAAA OOOOxx
+765 4107 1 1 5 5 65 765 765 765 765 130 131 LDAAAA ZBGAAA VVVVxx
+2664 4108 0 0 4 4 64 664 664 2664 2664 128 129 MYAAAA ACGAAA AAAAxx
+6854 4109 0 2 4 14 54 854 854 1854 6854 108 109 QDAAAA BCGAAA HHHHxx
+8263 4110 1 3 3 3 63 263 263 3263 8263 126 127 VFAAAA CCGAAA OOOOxx
+8658 4111 0 2 8 18 58 658 658 3658 8658 116 117 AVAAAA DCGAAA VVVVxx
+587 4112 1 3 7 7 87 587 587 587 587 174 175 PWAAAA ECGAAA AAAAxx
+4553 4113 1 1 3 13 53 553 553 4553 4553 106 107 DTAAAA FCGAAA HHHHxx
+1368 4114 0 0 8 8 68 368 1368 1368 1368 136 137 QAAAAA GCGAAA OOOOxx
+1718 4115 0 2 8 18 18 718 1718 1718 1718 36 37 COAAAA HCGAAA VVVVxx
+140 4116 0 0 0 0 40 140 140 140 140 80 81 KFAAAA ICGAAA AAAAxx
+8341 4117 1 1 1 1 41 341 341 3341 8341 82 83 VIAAAA JCGAAA HHHHxx
+72 4118 0 0 2 12 72 72 72 72 72 144 145 UCAAAA KCGAAA OOOOxx
+6589 4119 1 1 9 9 89 589 589 1589 6589 178 179 LTAAAA LCGAAA VVVVxx
+2024 4120 0 0 4 4 24 24 24 2024 2024 48 49 WZAAAA MCGAAA AAAAxx
+8024 4121 0 0 4 4 24 24 24 3024 8024 48 49 QWAAAA NCGAAA HHHHxx
+9564 4122 0 0 4 4 64 564 1564 4564 9564 128 129 WDAAAA OCGAAA OOOOxx
+8625 4123 1 1 5 5 25 625 625 3625 8625 50 51 TTAAAA PCGAAA VVVVxx
+2680 4124 0 0 0 0 80 680 680 2680 2680 160 161 CZAAAA QCGAAA AAAAxx
+4323 4125 1 3 3 3 23 323 323 4323 4323 46 47 HKAAAA RCGAAA HHHHxx
+8981 4126 1 1 1 1 81 981 981 3981 8981 162 163 LHAAAA SCGAAA OOOOxx
+8909 4127 1 1 9 9 9 909 909 3909 8909 18 19 REAAAA TCGAAA VVVVxx
+5288 4128 0 0 8 8 88 288 1288 288 5288 176 177 KVAAAA UCGAAA AAAAxx
+2057 4129 1 1 7 17 57 57 57 2057 2057 114 115 DBAAAA VCGAAA HHHHxx
+5931 4130 1 3 1 11 31 931 1931 931 5931 62 63 DUAAAA WCGAAA OOOOxx
+9794 4131 0 2 4 14 94 794 1794 4794 9794 188 189 SMAAAA XCGAAA VVVVxx
+1012 4132 0 0 2 12 12 12 1012 1012 1012 24 25 YMAAAA YCGAAA AAAAxx
+5496 4133 0 0 6 16 96 496 1496 496 5496 192 193 KDAAAA ZCGAAA HHHHxx
+9182 4134 0 2 2 2 82 182 1182 4182 9182 164 165 EPAAAA ADGAAA OOOOxx
+5258 4135 0 2 8 18 58 258 1258 258 5258 116 117 GUAAAA BDGAAA VVVVxx
+3050 4136 0 2 0 10 50 50 1050 3050 3050 100 101 INAAAA CDGAAA AAAAxx
+2083 4137 1 3 3 3 83 83 83 2083 2083 166 167 DCAAAA DDGAAA HHHHxx
+3069 4138 1 1 9 9 69 69 1069 3069 3069 138 139 BOAAAA EDGAAA OOOOxx
+8459 4139 1 3 9 19 59 459 459 3459 8459 118 119 JNAAAA FDGAAA VVVVxx
+169 4140 1 1 9 9 69 169 169 169 169 138 139 NGAAAA GDGAAA AAAAxx
+4379 4141 1 3 9 19 79 379 379 4379 4379 158 159 LMAAAA HDGAAA HHHHxx
+5126 4142 0 2 6 6 26 126 1126 126 5126 52 53 EPAAAA IDGAAA OOOOxx
+1415 4143 1 3 5 15 15 415 1415 1415 1415 30 31 LCAAAA JDGAAA VVVVxx
+1163 4144 1 3 3 3 63 163 1163 1163 1163 126 127 TSAAAA KDGAAA AAAAxx
+3500 4145 0 0 0 0 0 500 1500 3500 3500 0 1 QEAAAA LDGAAA HHHHxx
+7202 4146 0 2 2 2 2 202 1202 2202 7202 4 5 ARAAAA MDGAAA OOOOxx
+747 4147 1 3 7 7 47 747 747 747 747 94 95 TCAAAA NDGAAA VVVVxx
+9264 4148 0 0 4 4 64 264 1264 4264 9264 128 129 ISAAAA ODGAAA AAAAxx
+8548 4149 0 0 8 8 48 548 548 3548 8548 96 97 UQAAAA PDGAAA HHHHxx
+4228 4150 0 0 8 8 28 228 228 4228 4228 56 57 QGAAAA QDGAAA OOOOxx
+7122 4151 0 2 2 2 22 122 1122 2122 7122 44 45 YNAAAA RDGAAA VVVVxx
+3395 4152 1 3 5 15 95 395 1395 3395 3395 190 191 PAAAAA SDGAAA AAAAxx
+5674 4153 0 2 4 14 74 674 1674 674 5674 148 149 GKAAAA TDGAAA HHHHxx
+7293 4154 1 1 3 13 93 293 1293 2293 7293 186 187 NUAAAA UDGAAA OOOOxx
+737 4155 1 1 7 17 37 737 737 737 737 74 75 JCAAAA VDGAAA VVVVxx
+9595 4156 1 3 5 15 95 595 1595 4595 9595 190 191 BFAAAA WDGAAA AAAAxx
+594 4157 0 2 4 14 94 594 594 594 594 188 189 WWAAAA XDGAAA HHHHxx
+5322 4158 0 2 2 2 22 322 1322 322 5322 44 45 SWAAAA YDGAAA OOOOxx
+2933 4159 1 1 3 13 33 933 933 2933 2933 66 67 VIAAAA ZDGAAA VVVVxx
+4955 4160 1 3 5 15 55 955 955 4955 4955 110 111 PIAAAA AEGAAA AAAAxx
+4073 4161 1 1 3 13 73 73 73 4073 4073 146 147 RAAAAA BEGAAA HHHHxx
+7249 4162 1 1 9 9 49 249 1249 2249 7249 98 99 VSAAAA CEGAAA OOOOxx
+192 4163 0 0 2 12 92 192 192 192 192 184 185 KHAAAA DEGAAA VVVVxx
+2617 4164 1 1 7 17 17 617 617 2617 2617 34 35 RWAAAA EEGAAA AAAAxx
+7409 4165 1 1 9 9 9 409 1409 2409 7409 18 19 ZYAAAA FEGAAA HHHHxx
+4903 4166 1 3 3 3 3 903 903 4903 4903 6 7 PGAAAA GEGAAA OOOOxx
+9797 4167 1 1 7 17 97 797 1797 4797 9797 194 195 VMAAAA HEGAAA VVVVxx
+9919 4168 1 3 9 19 19 919 1919 4919 9919 38 39 NRAAAA IEGAAA AAAAxx
+1878 4169 0 2 8 18 78 878 1878 1878 1878 156 157 GUAAAA JEGAAA HHHHxx
+4851 4170 1 3 1 11 51 851 851 4851 4851 102 103 PEAAAA KEGAAA OOOOxx
+5514 4171 0 2 4 14 14 514 1514 514 5514 28 29 CEAAAA LEGAAA VVVVxx
+2582 4172 0 2 2 2 82 582 582 2582 2582 164 165 IVAAAA MEGAAA AAAAxx
+3564 4173 0 0 4 4 64 564 1564 3564 3564 128 129 CHAAAA NEGAAA HHHHxx
+7085 4174 1 1 5 5 85 85 1085 2085 7085 170 171 NMAAAA OEGAAA OOOOxx
+3619 4175 1 3 9 19 19 619 1619 3619 3619 38 39 FJAAAA PEGAAA VVVVxx
+261 4176 1 1 1 1 61 261 261 261 261 122 123 BKAAAA QEGAAA AAAAxx
+7338 4177 0 2 8 18 38 338 1338 2338 7338 76 77 GWAAAA REGAAA HHHHxx
+4251 4178 1 3 1 11 51 251 251 4251 4251 102 103 NHAAAA SEGAAA OOOOxx
+5360 4179 0 0 0 0 60 360 1360 360 5360 120 121 EYAAAA TEGAAA VVVVxx
+5678 4180 0 2 8 18 78 678 1678 678 5678 156 157 KKAAAA UEGAAA AAAAxx
+9162 4181 0 2 2 2 62 162 1162 4162 9162 124 125 KOAAAA VEGAAA HHHHxx
+5920 4182 0 0 0 0 20 920 1920 920 5920 40 41 STAAAA WEGAAA OOOOxx
+7156 4183 0 0 6 16 56 156 1156 2156 7156 112 113 GPAAAA XEGAAA VVVVxx
+4271 4184 1 3 1 11 71 271 271 4271 4271 142 143 HIAAAA YEGAAA AAAAxx
+4698 4185 0 2 8 18 98 698 698 4698 4698 196 197 SYAAAA ZEGAAA HHHHxx
+1572 4186 0 0 2 12 72 572 1572 1572 1572 144 145 MIAAAA AFGAAA OOOOxx
+6974 4187 0 2 4 14 74 974 974 1974 6974 148 149 GIAAAA BFGAAA VVVVxx
+4291 4188 1 3 1 11 91 291 291 4291 4291 182 183 BJAAAA CFGAAA AAAAxx
+4036 4189 0 0 6 16 36 36 36 4036 4036 72 73 GZAAAA DFGAAA HHHHxx
+7473 4190 1 1 3 13 73 473 1473 2473 7473 146 147 LBAAAA EFGAAA OOOOxx
+4786 4191 0 2 6 6 86 786 786 4786 4786 172 173 CCAAAA FFGAAA VVVVxx
+2662 4192 0 2 2 2 62 662 662 2662 2662 124 125 KYAAAA GFGAAA AAAAxx
+916 4193 0 0 6 16 16 916 916 916 916 32 33 GJAAAA HFGAAA HHHHxx
+668 4194 0 0 8 8 68 668 668 668 668 136 137 SZAAAA IFGAAA OOOOxx
+4874 4195 0 2 4 14 74 874 874 4874 4874 148 149 MFAAAA JFGAAA VVVVxx
+3752 4196 0 0 2 12 52 752 1752 3752 3752 104 105 IOAAAA KFGAAA AAAAxx
+4865 4197 1 1 5 5 65 865 865 4865 4865 130 131 DFAAAA LFGAAA HHHHxx
+7052 4198 0 0 2 12 52 52 1052 2052 7052 104 105 GLAAAA MFGAAA OOOOxx
+5712 4199 0 0 2 12 12 712 1712 712 5712 24 25 SLAAAA NFGAAA VVVVxx
+31 4200 1 3 1 11 31 31 31 31 31 62 63 FBAAAA OFGAAA AAAAxx
+4944 4201 0 0 4 4 44 944 944 4944 4944 88 89 EIAAAA PFGAAA HHHHxx
+1435 4202 1 3 5 15 35 435 1435 1435 1435 70 71 FDAAAA QFGAAA OOOOxx
+501 4203 1 1 1 1 1 501 501 501 501 2 3 HTAAAA RFGAAA VVVVxx
+9401 4204 1 1 1 1 1 401 1401 4401 9401 2 3 PXAAAA SFGAAA AAAAxx
+5014 4205 0 2 4 14 14 14 1014 14 5014 28 29 WKAAAA TFGAAA HHHHxx
+9125 4206 1 1 5 5 25 125 1125 4125 9125 50 51 ZMAAAA UFGAAA OOOOxx
+6144 4207 0 0 4 4 44 144 144 1144 6144 88 89 ICAAAA VFGAAA VVVVxx
+1743 4208 1 3 3 3 43 743 1743 1743 1743 86 87 BPAAAA WFGAAA AAAAxx
+4316 4209 0 0 6 16 16 316 316 4316 4316 32 33 AKAAAA XFGAAA HHHHxx
+8212 4210 0 0 2 12 12 212 212 3212 8212 24 25 WDAAAA YFGAAA OOOOxx
+7344 4211 0 0 4 4 44 344 1344 2344 7344 88 89 MWAAAA ZFGAAA VVVVxx
+2051 4212 1 3 1 11 51 51 51 2051 2051 102 103 XAAAAA AGGAAA AAAAxx
+8131 4213 1 3 1 11 31 131 131 3131 8131 62 63 TAAAAA BGGAAA HHHHxx
+7023 4214 1 3 3 3 23 23 1023 2023 7023 46 47 DKAAAA CGGAAA OOOOxx
+9674 4215 0 2 4 14 74 674 1674 4674 9674 148 149 CIAAAA DGGAAA VVVVxx
+4984 4216 0 0 4 4 84 984 984 4984 4984 168 169 SJAAAA EGGAAA AAAAxx
+111 4217 1 3 1 11 11 111 111 111 111 22 23 HEAAAA FGGAAA HHHHxx
+2296 4218 0 0 6 16 96 296 296 2296 2296 192 193 IKAAAA GGGAAA OOOOxx
+5025 4219 1 1 5 5 25 25 1025 25 5025 50 51 HLAAAA HGGAAA VVVVxx
+1756 4220 0 0 6 16 56 756 1756 1756 1756 112 113 OPAAAA IGGAAA AAAAxx
+2885 4221 1 1 5 5 85 885 885 2885 2885 170 171 ZGAAAA JGGAAA HHHHxx
+2541 4222 1 1 1 1 41 541 541 2541 2541 82 83 TTAAAA KGGAAA OOOOxx
+1919 4223 1 3 9 19 19 919 1919 1919 1919 38 39 VVAAAA LGGAAA VVVVxx
+6496 4224 0 0 6 16 96 496 496 1496 6496 192 193 WPAAAA MGGAAA AAAAxx
+6103 4225 1 3 3 3 3 103 103 1103 6103 6 7 TAAAAA NGGAAA HHHHxx
+98 4226 0 2 8 18 98 98 98 98 98 196 197 UDAAAA OGGAAA OOOOxx
+3727 4227 1 3 7 7 27 727 1727 3727 3727 54 55 JNAAAA PGGAAA VVVVxx
+689 4228 1 1 9 9 89 689 689 689 689 178 179 NAAAAA QGGAAA AAAAxx
+7181 4229 1 1 1 1 81 181 1181 2181 7181 162 163 FQAAAA RGGAAA HHHHxx
+8447 4230 1 3 7 7 47 447 447 3447 8447 94 95 XMAAAA SGGAAA OOOOxx
+4569 4231 1 1 9 9 69 569 569 4569 4569 138 139 TTAAAA TGGAAA VVVVxx
+8844 4232 0 0 4 4 44 844 844 3844 8844 88 89 ECAAAA UGGAAA AAAAxx
+2436 4233 0 0 6 16 36 436 436 2436 2436 72 73 SPAAAA VGGAAA HHHHxx
+391 4234 1 3 1 11 91 391 391 391 391 182 183 BPAAAA WGGAAA OOOOxx
+3035 4235 1 3 5 15 35 35 1035 3035 3035 70 71 TMAAAA XGGAAA VVVVxx
+7583 4236 1 3 3 3 83 583 1583 2583 7583 166 167 RFAAAA YGGAAA AAAAxx
+1145 4237 1 1 5 5 45 145 1145 1145 1145 90 91 BSAAAA ZGGAAA HHHHxx
+93 4238 1 1 3 13 93 93 93 93 93 186 187 PDAAAA AHGAAA OOOOxx
+8896 4239 0 0 6 16 96 896 896 3896 8896 192 193 EEAAAA BHGAAA VVVVxx
+6719 4240 1 3 9 19 19 719 719 1719 6719 38 39 LYAAAA CHGAAA AAAAxx
+7728 4241 0 0 8 8 28 728 1728 2728 7728 56 57 GLAAAA DHGAAA HHHHxx
+1349 4242 1 1 9 9 49 349 1349 1349 1349 98 99 XZAAAA EHGAAA OOOOxx
+5349 4243 1 1 9 9 49 349 1349 349 5349 98 99 TXAAAA FHGAAA VVVVxx
+3040 4244 0 0 0 0 40 40 1040 3040 3040 80 81 YMAAAA GHGAAA AAAAxx
+2414 4245 0 2 4 14 14 414 414 2414 2414 28 29 WOAAAA HHGAAA HHHHxx
+5122 4246 0 2 2 2 22 122 1122 122 5122 44 45 APAAAA IHGAAA OOOOxx
+9553 4247 1 1 3 13 53 553 1553 4553 9553 106 107 LDAAAA JHGAAA VVVVxx
+5987 4248 1 3 7 7 87 987 1987 987 5987 174 175 HWAAAA KHGAAA AAAAxx
+5939 4249 1 3 9 19 39 939 1939 939 5939 78 79 LUAAAA LHGAAA HHHHxx
+3525 4250 1 1 5 5 25 525 1525 3525 3525 50 51 PFAAAA MHGAAA OOOOxx
+1371 4251 1 3 1 11 71 371 1371 1371 1371 142 143 TAAAAA NHGAAA VVVVxx
+618 4252 0 2 8 18 18 618 618 618 618 36 37 UXAAAA OHGAAA AAAAxx
+6529 4253 1 1 9 9 29 529 529 1529 6529 58 59 DRAAAA PHGAAA HHHHxx
+4010 4254 0 2 0 10 10 10 10 4010 4010 20 21 GYAAAA QHGAAA OOOOxx
+328 4255 0 0 8 8 28 328 328 328 328 56 57 QMAAAA RHGAAA VVVVxx
+6121 4256 1 1 1 1 21 121 121 1121 6121 42 43 LBAAAA SHGAAA AAAAxx
+3505 4257 1 1 5 5 5 505 1505 3505 3505 10 11 VEAAAA THGAAA HHHHxx
+2033 4258 1 1 3 13 33 33 33 2033 2033 66 67 FAAAAA UHGAAA OOOOxx
+4724 4259 0 0 4 4 24 724 724 4724 4724 48 49 SZAAAA VHGAAA VVVVxx
+8717 4260 1 1 7 17 17 717 717 3717 8717 34 35 HXAAAA WHGAAA AAAAxx
+5639 4261 1 3 9 19 39 639 1639 639 5639 78 79 XIAAAA XHGAAA HHHHxx
+3448 4262 0 0 8 8 48 448 1448 3448 3448 96 97 QCAAAA YHGAAA OOOOxx
+2919 4263 1 3 9 19 19 919 919 2919 2919 38 39 HIAAAA ZHGAAA VVVVxx
+3417 4264 1 1 7 17 17 417 1417 3417 3417 34 35 LBAAAA AIGAAA AAAAxx
+943 4265 1 3 3 3 43 943 943 943 943 86 87 HKAAAA BIGAAA HHHHxx
+775 4266 1 3 5 15 75 775 775 775 775 150 151 VDAAAA CIGAAA OOOOxx
+2333 4267 1 1 3 13 33 333 333 2333 2333 66 67 TLAAAA DIGAAA VVVVxx
+4801 4268 1 1 1 1 1 801 801 4801 4801 2 3 RCAAAA EIGAAA AAAAxx
+7169 4269 1 1 9 9 69 169 1169 2169 7169 138 139 TPAAAA FIGAAA HHHHxx
+2840 4270 0 0 0 0 40 840 840 2840 2840 80 81 GFAAAA GIGAAA OOOOxx
+9034 4271 0 2 4 14 34 34 1034 4034 9034 68 69 MJAAAA HIGAAA VVVVxx
+6154 4272 0 2 4 14 54 154 154 1154 6154 108 109 SCAAAA IIGAAA AAAAxx
+1412 4273 0 0 2 12 12 412 1412 1412 1412 24 25 ICAAAA JIGAAA HHHHxx
+2263 4274 1 3 3 3 63 263 263 2263 2263 126 127 BJAAAA KIGAAA OOOOxx
+7118 4275 0 2 8 18 18 118 1118 2118 7118 36 37 UNAAAA LIGAAA VVVVxx
+1526 4276 0 2 6 6 26 526 1526 1526 1526 52 53 SGAAAA MIGAAA AAAAxx
+491 4277 1 3 1 11 91 491 491 491 491 182 183 XSAAAA NIGAAA HHHHxx
+9732 4278 0 0 2 12 32 732 1732 4732 9732 64 65 IKAAAA OIGAAA OOOOxx
+7067 4279 1 3 7 7 67 67 1067 2067 7067 134 135 VLAAAA PIGAAA VVVVxx
+212 4280 0 0 2 12 12 212 212 212 212 24 25 EIAAAA QIGAAA AAAAxx
+1955 4281 1 3 5 15 55 955 1955 1955 1955 110 111 FXAAAA RIGAAA HHHHxx
+3303 4282 1 3 3 3 3 303 1303 3303 3303 6 7 BXAAAA SIGAAA OOOOxx
+2715 4283 1 3 5 15 15 715 715 2715 2715 30 31 LAAAAA TIGAAA VVVVxx
+8168 4284 0 0 8 8 68 168 168 3168 8168 136 137 ECAAAA UIGAAA AAAAxx
+6799 4285 1 3 9 19 99 799 799 1799 6799 198 199 NBAAAA VIGAAA HHHHxx
+5080 4286 0 0 0 0 80 80 1080 80 5080 160 161 KNAAAA WIGAAA OOOOxx
+4939 4287 1 3 9 19 39 939 939 4939 4939 78 79 ZHAAAA XIGAAA VVVVxx
+6604 4288 0 0 4 4 4 604 604 1604 6604 8 9 AUAAAA YIGAAA AAAAxx
+6531 4289 1 3 1 11 31 531 531 1531 6531 62 63 FRAAAA ZIGAAA HHHHxx
+9948 4290 0 0 8 8 48 948 1948 4948 9948 96 97 QSAAAA AJGAAA OOOOxx
+7923 4291 1 3 3 3 23 923 1923 2923 7923 46 47 TSAAAA BJGAAA VVVVxx
+9905 4292 1 1 5 5 5 905 1905 4905 9905 10 11 ZQAAAA CJGAAA AAAAxx
+340 4293 0 0 0 0 40 340 340 340 340 80 81 CNAAAA DJGAAA HHHHxx
+1721 4294 1 1 1 1 21 721 1721 1721 1721 42 43 FOAAAA EJGAAA OOOOxx
+9047 4295 1 3 7 7 47 47 1047 4047 9047 94 95 ZJAAAA FJGAAA VVVVxx
+4723 4296 1 3 3 3 23 723 723 4723 4723 46 47 RZAAAA GJGAAA AAAAxx
+5748 4297 0 0 8 8 48 748 1748 748 5748 96 97 CNAAAA HJGAAA HHHHxx
+6845 4298 1 1 5 5 45 845 845 1845 6845 90 91 HDAAAA IJGAAA OOOOxx
+1556 4299 0 0 6 16 56 556 1556 1556 1556 112 113 WHAAAA JJGAAA VVVVxx
+9505 4300 1 1 5 5 5 505 1505 4505 9505 10 11 PBAAAA KJGAAA AAAAxx
+3573 4301 1 1 3 13 73 573 1573 3573 3573 146 147 LHAAAA LJGAAA HHHHxx
+3785 4302 1 1 5 5 85 785 1785 3785 3785 170 171 PPAAAA MJGAAA OOOOxx
+2772 4303 0 0 2 12 72 772 772 2772 2772 144 145 QCAAAA NJGAAA VVVVxx
+7282 4304 0 2 2 2 82 282 1282 2282 7282 164 165 CUAAAA OJGAAA AAAAxx
+8106 4305 0 2 6 6 6 106 106 3106 8106 12 13 UZAAAA PJGAAA HHHHxx
+2847 4306 1 3 7 7 47 847 847 2847 2847 94 95 NFAAAA QJGAAA OOOOxx
+9803 4307 1 3 3 3 3 803 1803 4803 9803 6 7 BNAAAA RJGAAA VVVVxx
+7719 4308 1 3 9 19 19 719 1719 2719 7719 38 39 XKAAAA SJGAAA AAAAxx
+4649 4309 1 1 9 9 49 649 649 4649 4649 98 99 VWAAAA TJGAAA HHHHxx
+6196 4310 0 0 6 16 96 196 196 1196 6196 192 193 IEAAAA UJGAAA OOOOxx
+6026 4311 0 2 6 6 26 26 26 1026 6026 52 53 UXAAAA VJGAAA VVVVxx
+1646 4312 0 2 6 6 46 646 1646 1646 1646 92 93 ILAAAA WJGAAA AAAAxx
+6526 4313 0 2 6 6 26 526 526 1526 6526 52 53 ARAAAA XJGAAA HHHHxx
+5110 4314 0 2 0 10 10 110 1110 110 5110 20 21 OOAAAA YJGAAA OOOOxx
+3946 4315 0 2 6 6 46 946 1946 3946 3946 92 93 UVAAAA ZJGAAA VVVVxx
+445 4316 1 1 5 5 45 445 445 445 445 90 91 DRAAAA AKGAAA AAAAxx
+3249 4317 1 1 9 9 49 249 1249 3249 3249 98 99 ZUAAAA BKGAAA HHHHxx
+2501 4318 1 1 1 1 1 501 501 2501 2501 2 3 FSAAAA CKGAAA OOOOxx
+3243 4319 1 3 3 3 43 243 1243 3243 3243 86 87 TUAAAA DKGAAA VVVVxx
+4701 4320 1 1 1 1 1 701 701 4701 4701 2 3 VYAAAA EKGAAA AAAAxx
+472 4321 0 0 2 12 72 472 472 472 472 144 145 ESAAAA FKGAAA HHHHxx
+3356 4322 0 0 6 16 56 356 1356 3356 3356 112 113 CZAAAA GKGAAA OOOOxx
+9967 4323 1 3 7 7 67 967 1967 4967 9967 134 135 JTAAAA HKGAAA VVVVxx
+4292 4324 0 0 2 12 92 292 292 4292 4292 184 185 CJAAAA IKGAAA AAAAxx
+7005 4325 1 1 5 5 5 5 1005 2005 7005 10 11 LJAAAA JKGAAA HHHHxx
+6267 4326 1 3 7 7 67 267 267 1267 6267 134 135 BHAAAA KKGAAA OOOOxx
+6678 4327 0 2 8 18 78 678 678 1678 6678 156 157 WWAAAA LKGAAA VVVVxx
+6083 4328 1 3 3 3 83 83 83 1083 6083 166 167 ZZAAAA MKGAAA AAAAxx
+760 4329 0 0 0 0 60 760 760 760 760 120 121 GDAAAA NKGAAA HHHHxx
+7833 4330 1 1 3 13 33 833 1833 2833 7833 66 67 HPAAAA OKGAAA OOOOxx
+2877 4331 1 1 7 17 77 877 877 2877 2877 154 155 RGAAAA PKGAAA VVVVxx
+8810 4332 0 2 0 10 10 810 810 3810 8810 20 21 WAAAAA QKGAAA AAAAxx
+1560 4333 0 0 0 0 60 560 1560 1560 1560 120 121 AIAAAA RKGAAA HHHHxx
+1367 4334 1 3 7 7 67 367 1367 1367 1367 134 135 PAAAAA SKGAAA OOOOxx
+8756 4335 0 0 6 16 56 756 756 3756 8756 112 113 UYAAAA TKGAAA VVVVxx
+1346 4336 0 2 6 6 46 346 1346 1346 1346 92 93 UZAAAA UKGAAA AAAAxx
+6449 4337 1 1 9 9 49 449 449 1449 6449 98 99 BOAAAA VKGAAA HHHHxx
+6658 4338 0 2 8 18 58 658 658 1658 6658 116 117 CWAAAA WKGAAA OOOOxx
+6745 4339 1 1 5 5 45 745 745 1745 6745 90 91 LZAAAA XKGAAA VVVVxx
+4866 4340 0 2 6 6 66 866 866 4866 4866 132 133 EFAAAA YKGAAA AAAAxx
+14 4341 0 2 4 14 14 14 14 14 14 28 29 OAAAAA ZKGAAA HHHHxx
+4506 4342 0 2 6 6 6 506 506 4506 4506 12 13 IRAAAA ALGAAA OOOOxx
+1923 4343 1 3 3 3 23 923 1923 1923 1923 46 47 ZVAAAA BLGAAA VVVVxx
+8365 4344 1 1 5 5 65 365 365 3365 8365 130 131 TJAAAA CLGAAA AAAAxx
+1279 4345 1 3 9 19 79 279 1279 1279 1279 158 159 FXAAAA DLGAAA HHHHxx
+7666 4346 0 2 6 6 66 666 1666 2666 7666 132 133 WIAAAA ELGAAA OOOOxx
+7404 4347 0 0 4 4 4 404 1404 2404 7404 8 9 UYAAAA FLGAAA VVVVxx
+65 4348 1 1 5 5 65 65 65 65 65 130 131 NCAAAA GLGAAA AAAAxx
+5820 4349 0 0 0 0 20 820 1820 820 5820 40 41 WPAAAA HLGAAA HHHHxx
+459 4350 1 3 9 19 59 459 459 459 459 118 119 RRAAAA ILGAAA OOOOxx
+4787 4351 1 3 7 7 87 787 787 4787 4787 174 175 DCAAAA JLGAAA VVVVxx
+5631 4352 1 3 1 11 31 631 1631 631 5631 62 63 PIAAAA KLGAAA AAAAxx
+9717 4353 1 1 7 17 17 717 1717 4717 9717 34 35 TJAAAA LLGAAA HHHHxx
+2560 4354 0 0 0 0 60 560 560 2560 2560 120 121 MUAAAA MLGAAA OOOOxx
+8295 4355 1 3 5 15 95 295 295 3295 8295 190 191 BHAAAA NLGAAA VVVVxx
+3596 4356 0 0 6 16 96 596 1596 3596 3596 192 193 IIAAAA OLGAAA AAAAxx
+2023 4357 1 3 3 3 23 23 23 2023 2023 46 47 VZAAAA PLGAAA HHHHxx
+5055 4358 1 3 5 15 55 55 1055 55 5055 110 111 LMAAAA QLGAAA OOOOxx
+763 4359 1 3 3 3 63 763 763 763 763 126 127 JDAAAA RLGAAA VVVVxx
+6733 4360 1 1 3 13 33 733 733 1733 6733 66 67 ZYAAAA SLGAAA AAAAxx
+9266 4361 0 2 6 6 66 266 1266 4266 9266 132 133 KSAAAA TLGAAA HHHHxx
+4479 4362 1 3 9 19 79 479 479 4479 4479 158 159 HQAAAA ULGAAA OOOOxx
+1816 4363 0 0 6 16 16 816 1816 1816 1816 32 33 WRAAAA VLGAAA VVVVxx
+899 4364 1 3 9 19 99 899 899 899 899 198 199 PIAAAA WLGAAA AAAAxx
+230 4365 0 2 0 10 30 230 230 230 230 60 61 WIAAAA XLGAAA HHHHxx
+5362 4366 0 2 2 2 62 362 1362 362 5362 124 125 GYAAAA YLGAAA OOOOxx
+1609 4367 1 1 9 9 9 609 1609 1609 1609 18 19 XJAAAA ZLGAAA VVVVxx
+6750 4368 0 2 0 10 50 750 750 1750 6750 100 101 QZAAAA AMGAAA AAAAxx
+9704 4369 0 0 4 4 4 704 1704 4704 9704 8 9 GJAAAA BMGAAA HHHHxx
+3991 4370 1 3 1 11 91 991 1991 3991 3991 182 183 NXAAAA CMGAAA OOOOxx
+3959 4371 1 3 9 19 59 959 1959 3959 3959 118 119 HWAAAA DMGAAA VVVVxx
+9021 4372 1 1 1 1 21 21 1021 4021 9021 42 43 ZIAAAA EMGAAA AAAAxx
+7585 4373 1 1 5 5 85 585 1585 2585 7585 170 171 TFAAAA FMGAAA HHHHxx
+7083 4374 1 3 3 3 83 83 1083 2083 7083 166 167 LMAAAA GMGAAA OOOOxx
+7688 4375 0 0 8 8 88 688 1688 2688 7688 176 177 SJAAAA HMGAAA VVVVxx
+2673 4376 1 1 3 13 73 673 673 2673 2673 146 147 VYAAAA IMGAAA AAAAxx
+3554 4377 0 2 4 14 54 554 1554 3554 3554 108 109 SGAAAA JMGAAA HHHHxx
+7416 4378 0 0 6 16 16 416 1416 2416 7416 32 33 GZAAAA KMGAAA OOOOxx
+5672 4379 0 0 2 12 72 672 1672 672 5672 144 145 EKAAAA LMGAAA VVVVxx
+1355 4380 1 3 5 15 55 355 1355 1355 1355 110 111 DAAAAA MMGAAA AAAAxx
+3149 4381 1 1 9 9 49 149 1149 3149 3149 98 99 DRAAAA NMGAAA HHHHxx
+5811 4382 1 3 1 11 11 811 1811 811 5811 22 23 NPAAAA OMGAAA OOOOxx
+3759 4383 1 3 9 19 59 759 1759 3759 3759 118 119 POAAAA PMGAAA VVVVxx
+5634 4384 0 2 4 14 34 634 1634 634 5634 68 69 SIAAAA QMGAAA AAAAxx
+8617 4385 1 1 7 17 17 617 617 3617 8617 34 35 LTAAAA RMGAAA HHHHxx
+8949 4386 1 1 9 9 49 949 949 3949 8949 98 99 FGAAAA SMGAAA OOOOxx
+3964 4387 0 0 4 4 64 964 1964 3964 3964 128 129 MWAAAA TMGAAA VVVVxx
+3852 4388 0 0 2 12 52 852 1852 3852 3852 104 105 ESAAAA UMGAAA AAAAxx
+1555 4389 1 3 5 15 55 555 1555 1555 1555 110 111 VHAAAA VMGAAA HHHHxx
+6536 4390 0 0 6 16 36 536 536 1536 6536 72 73 KRAAAA WMGAAA OOOOxx
+4779 4391 1 3 9 19 79 779 779 4779 4779 158 159 VBAAAA XMGAAA VVVVxx
+1893 4392 1 1 3 13 93 893 1893 1893 1893 186 187 VUAAAA YMGAAA AAAAxx
+9358 4393 0 2 8 18 58 358 1358 4358 9358 116 117 YVAAAA ZMGAAA HHHHxx
+7438 4394 0 2 8 18 38 438 1438 2438 7438 76 77 CAAAAA ANGAAA OOOOxx
+941 4395 1 1 1 1 41 941 941 941 941 82 83 FKAAAA BNGAAA VVVVxx
+4844 4396 0 0 4 4 44 844 844 4844 4844 88 89 IEAAAA CNGAAA AAAAxx
+4745 4397 1 1 5 5 45 745 745 4745 4745 90 91 NAAAAA DNGAAA HHHHxx
+1017 4398 1 1 7 17 17 17 1017 1017 1017 34 35 DNAAAA ENGAAA OOOOxx
+327 4399 1 3 7 7 27 327 327 327 327 54 55 PMAAAA FNGAAA VVVVxx
+3152 4400 0 0 2 12 52 152 1152 3152 3152 104 105 GRAAAA GNGAAA AAAAxx
+4711 4401 1 3 1 11 11 711 711 4711 4711 22 23 FZAAAA HNGAAA HHHHxx
+141 4402 1 1 1 1 41 141 141 141 141 82 83 LFAAAA INGAAA OOOOxx
+1303 4403 1 3 3 3 3 303 1303 1303 1303 6 7 DYAAAA JNGAAA VVVVxx
+8873 4404 1 1 3 13 73 873 873 3873 8873 146 147 HDAAAA KNGAAA AAAAxx
+8481 4405 1 1 1 1 81 481 481 3481 8481 162 163 FOAAAA LNGAAA HHHHxx
+5445 4406 1 1 5 5 45 445 1445 445 5445 90 91 LBAAAA MNGAAA OOOOxx
+7868 4407 0 0 8 8 68 868 1868 2868 7868 136 137 QQAAAA NNGAAA VVVVxx
+6722 4408 0 2 2 2 22 722 722 1722 6722 44 45 OYAAAA ONGAAA AAAAxx
+6628 4409 0 0 8 8 28 628 628 1628 6628 56 57 YUAAAA PNGAAA HHHHxx
+7738 4410 0 2 8 18 38 738 1738 2738 7738 76 77 QLAAAA QNGAAA OOOOxx
+1018 4411 0 2 8 18 18 18 1018 1018 1018 36 37 ENAAAA RNGAAA VVVVxx
+3296 4412 0 0 6 16 96 296 1296 3296 3296 192 193 UWAAAA SNGAAA AAAAxx
+1946 4413 0 2 6 6 46 946 1946 1946 1946 92 93 WWAAAA TNGAAA HHHHxx
+6603 4414 1 3 3 3 3 603 603 1603 6603 6 7 ZTAAAA UNGAAA OOOOxx
+3562 4415 0 2 2 2 62 562 1562 3562 3562 124 125 AHAAAA VNGAAA VVVVxx
+1147 4416 1 3 7 7 47 147 1147 1147 1147 94 95 DSAAAA WNGAAA AAAAxx
+6031 4417 1 3 1 11 31 31 31 1031 6031 62 63 ZXAAAA XNGAAA HHHHxx
+6484 4418 0 0 4 4 84 484 484 1484 6484 168 169 KPAAAA YNGAAA OOOOxx
+496 4419 0 0 6 16 96 496 496 496 496 192 193 CTAAAA ZNGAAA VVVVxx
+4563 4420 1 3 3 3 63 563 563 4563 4563 126 127 NTAAAA AOGAAA AAAAxx
+1037 4421 1 1 7 17 37 37 1037 1037 1037 74 75 XNAAAA BOGAAA HHHHxx
+9672 4422 0 0 2 12 72 672 1672 4672 9672 144 145 AIAAAA COGAAA OOOOxx
+9053 4423 1 1 3 13 53 53 1053 4053 9053 106 107 FKAAAA DOGAAA VVVVxx
+2523 4424 1 3 3 3 23 523 523 2523 2523 46 47 BTAAAA EOGAAA AAAAxx
+8519 4425 1 3 9 19 19 519 519 3519 8519 38 39 RPAAAA FOGAAA HHHHxx
+8190 4426 0 2 0 10 90 190 190 3190 8190 180 181 ADAAAA GOGAAA OOOOxx
+2068 4427 0 0 8 8 68 68 68 2068 2068 136 137 OBAAAA HOGAAA VVVVxx
+8569 4428 1 1 9 9 69 569 569 3569 8569 138 139 PRAAAA IOGAAA AAAAxx
+6535 4429 1 3 5 15 35 535 535 1535 6535 70 71 JRAAAA JOGAAA HHHHxx
+1810 4430 0 2 0 10 10 810 1810 1810 1810 20 21 QRAAAA KOGAAA OOOOxx
+3099 4431 1 3 9 19 99 99 1099 3099 3099 198 199 FPAAAA LOGAAA VVVVxx
+7466 4432 0 2 6 6 66 466 1466 2466 7466 132 133 EBAAAA MOGAAA AAAAxx
+4017 4433 1 1 7 17 17 17 17 4017 4017 34 35 NYAAAA NOGAAA HHHHxx
+1097 4434 1 1 7 17 97 97 1097 1097 1097 194 195 FQAAAA OOGAAA OOOOxx
+7686 4435 0 2 6 6 86 686 1686 2686 7686 172 173 QJAAAA POGAAA VVVVxx
+6742 4436 0 2 2 2 42 742 742 1742 6742 84 85 IZAAAA QOGAAA AAAAxx
+5966 4437 0 2 6 6 66 966 1966 966 5966 132 133 MVAAAA ROGAAA HHHHxx
+3632 4438 0 0 2 12 32 632 1632 3632 3632 64 65 SJAAAA SOGAAA OOOOxx
+8837 4439 1 1 7 17 37 837 837 3837 8837 74 75 XBAAAA TOGAAA VVVVxx
+1667 4440 1 3 7 7 67 667 1667 1667 1667 134 135 DMAAAA UOGAAA AAAAxx
+8833 4441 1 1 3 13 33 833 833 3833 8833 66 67 TBAAAA VOGAAA HHHHxx
+9805 4442 1 1 5 5 5 805 1805 4805 9805 10 11 DNAAAA WOGAAA OOOOxx
+3650 4443 0 2 0 10 50 650 1650 3650 3650 100 101 KKAAAA XOGAAA VVVVxx
+2237 4444 1 1 7 17 37 237 237 2237 2237 74 75 BIAAAA YOGAAA AAAAxx
+9980 4445 0 0 0 0 80 980 1980 4980 9980 160 161 WTAAAA ZOGAAA HHHHxx
+2861 4446 1 1 1 1 61 861 861 2861 2861 122 123 BGAAAA APGAAA OOOOxx
+1334 4447 0 2 4 14 34 334 1334 1334 1334 68 69 IZAAAA BPGAAA VVVVxx
+842 4448 0 2 2 2 42 842 842 842 842 84 85 KGAAAA CPGAAA AAAAxx
+1116 4449 0 0 6 16 16 116 1116 1116 1116 32 33 YQAAAA DPGAAA HHHHxx
+4055 4450 1 3 5 15 55 55 55 4055 4055 110 111 ZZAAAA EPGAAA OOOOxx
+3842 4451 0 2 2 2 42 842 1842 3842 3842 84 85 URAAAA FPGAAA VVVVxx
+1886 4452 0 2 6 6 86 886 1886 1886 1886 172 173 OUAAAA GPGAAA AAAAxx
+8589 4453 1 1 9 9 89 589 589 3589 8589 178 179 JSAAAA HPGAAA HHHHxx
+5873 4454 1 1 3 13 73 873 1873 873 5873 146 147 XRAAAA IPGAAA OOOOxx
+7711 4455 1 3 1 11 11 711 1711 2711 7711 22 23 PKAAAA JPGAAA VVVVxx
+911 4456 1 3 1 11 11 911 911 911 911 22 23 BJAAAA KPGAAA AAAAxx
+5837 4457 1 1 7 17 37 837 1837 837 5837 74 75 NQAAAA LPGAAA HHHHxx
+897 4458 1 1 7 17 97 897 897 897 897 194 195 NIAAAA MPGAAA OOOOxx
+4299 4459 1 3 9 19 99 299 299 4299 4299 198 199 JJAAAA NPGAAA VVVVxx
+7774 4460 0 2 4 14 74 774 1774 2774 7774 148 149 ANAAAA OPGAAA AAAAxx
+7832 4461 0 0 2 12 32 832 1832 2832 7832 64 65 GPAAAA PPGAAA HHHHxx
+9915 4462 1 3 5 15 15 915 1915 4915 9915 30 31 JRAAAA QPGAAA OOOOxx
+9 4463 1 1 9 9 9 9 9 9 9 18 19 JAAAAA RPGAAA VVVVxx
+9675 4464 1 3 5 15 75 675 1675 4675 9675 150 151 DIAAAA SPGAAA AAAAxx
+7953 4465 1 1 3 13 53 953 1953 2953 7953 106 107 XTAAAA TPGAAA HHHHxx
+8912 4466 0 0 2 12 12 912 912 3912 8912 24 25 UEAAAA UPGAAA OOOOxx
+4188 4467 0 0 8 8 88 188 188 4188 4188 176 177 CFAAAA VPGAAA VVVVxx
+8446 4468 0 2 6 6 46 446 446 3446 8446 92 93 WMAAAA WPGAAA AAAAxx
+1600 4469 0 0 0 0 0 600 1600 1600 1600 0 1 OJAAAA XPGAAA HHHHxx
+43 4470 1 3 3 3 43 43 43 43 43 86 87 RBAAAA YPGAAA OOOOxx
+544 4471 0 0 4 4 44 544 544 544 544 88 89 YUAAAA ZPGAAA VVVVxx
+6977 4472 1 1 7 17 77 977 977 1977 6977 154 155 JIAAAA AQGAAA AAAAxx
+3191 4473 1 3 1 11 91 191 1191 3191 3191 182 183 TSAAAA BQGAAA HHHHxx
+418 4474 0 2 8 18 18 418 418 418 418 36 37 CQAAAA CQGAAA OOOOxx
+3142 4475 0 2 2 2 42 142 1142 3142 3142 84 85 WQAAAA DQGAAA VVVVxx
+5042 4476 0 2 2 2 42 42 1042 42 5042 84 85 YLAAAA EQGAAA AAAAxx
+2194 4477 0 2 4 14 94 194 194 2194 2194 188 189 KGAAAA FQGAAA HHHHxx
+2397 4478 1 1 7 17 97 397 397 2397 2397 194 195 FOAAAA GQGAAA OOOOxx
+4684 4479 0 0 4 4 84 684 684 4684 4684 168 169 EYAAAA HQGAAA VVVVxx
+34 4480 0 2 4 14 34 34 34 34 34 68 69 IBAAAA IQGAAA AAAAxx
+3844 4481 0 0 4 4 44 844 1844 3844 3844 88 89 WRAAAA JQGAAA HHHHxx
+7824 4482 0 0 4 4 24 824 1824 2824 7824 48 49 YOAAAA KQGAAA OOOOxx
+6177 4483 1 1 7 17 77 177 177 1177 6177 154 155 PDAAAA LQGAAA VVVVxx
+9657 4484 1 1 7 17 57 657 1657 4657 9657 114 115 LHAAAA MQGAAA AAAAxx
+4546 4485 0 2 6 6 46 546 546 4546 4546 92 93 WSAAAA NQGAAA HHHHxx
+599 4486 1 3 9 19 99 599 599 599 599 198 199 BXAAAA OQGAAA OOOOxx
+153 4487 1 1 3 13 53 153 153 153 153 106 107 XFAAAA PQGAAA VVVVxx
+6910 4488 0 2 0 10 10 910 910 1910 6910 20 21 UFAAAA QQGAAA AAAAxx
+4408 4489 0 0 8 8 8 408 408 4408 4408 16 17 ONAAAA RQGAAA HHHHxx
+1164 4490 0 0 4 4 64 164 1164 1164 1164 128 129 USAAAA SQGAAA OOOOxx
+6469 4491 1 1 9 9 69 469 469 1469 6469 138 139 VOAAAA TQGAAA VVVVxx
+5996 4492 0 0 6 16 96 996 1996 996 5996 192 193 QWAAAA UQGAAA AAAAxx
+2639 4493 1 3 9 19 39 639 639 2639 2639 78 79 NXAAAA VQGAAA HHHHxx
+2678 4494 0 2 8 18 78 678 678 2678 2678 156 157 AZAAAA WQGAAA OOOOxx
+8392 4495 0 0 2 12 92 392 392 3392 8392 184 185 UKAAAA XQGAAA VVVVxx
+1386 4496 0 2 6 6 86 386 1386 1386 1386 172 173 IBAAAA YQGAAA AAAAxx
+5125 4497 1 1 5 5 25 125 1125 125 5125 50 51 DPAAAA ZQGAAA HHHHxx
+8453 4498 1 1 3 13 53 453 453 3453 8453 106 107 DNAAAA ARGAAA OOOOxx
+2369 4499 1 1 9 9 69 369 369 2369 2369 138 139 DNAAAA BRGAAA VVVVxx
+1608 4500 0 0 8 8 8 608 1608 1608 1608 16 17 WJAAAA CRGAAA AAAAxx
+3781 4501 1 1 1 1 81 781 1781 3781 3781 162 163 LPAAAA DRGAAA HHHHxx
+903 4502 1 3 3 3 3 903 903 903 903 6 7 TIAAAA ERGAAA OOOOxx
+2099 4503 1 3 9 19 99 99 99 2099 2099 198 199 TCAAAA FRGAAA VVVVxx
+538 4504 0 2 8 18 38 538 538 538 538 76 77 SUAAAA GRGAAA AAAAxx
+9177 4505 1 1 7 17 77 177 1177 4177 9177 154 155 ZOAAAA HRGAAA HHHHxx
+420 4506 0 0 0 0 20 420 420 420 420 40 41 EQAAAA IRGAAA OOOOxx
+9080 4507 0 0 0 0 80 80 1080 4080 9080 160 161 GLAAAA JRGAAA VVVVxx
+2630 4508 0 2 0 10 30 630 630 2630 2630 60 61 EXAAAA KRGAAA AAAAxx
+5978 4509 0 2 8 18 78 978 1978 978 5978 156 157 YVAAAA LRGAAA HHHHxx
+9239 4510 1 3 9 19 39 239 1239 4239 9239 78 79 JRAAAA MRGAAA OOOOxx
+4372 4511 0 0 2 12 72 372 372 4372 4372 144 145 EMAAAA NRGAAA VVVVxx
+4357 4512 1 1 7 17 57 357 357 4357 4357 114 115 PLAAAA ORGAAA AAAAxx
+9857 4513 1 1 7 17 57 857 1857 4857 9857 114 115 DPAAAA PRGAAA HHHHxx
+7933 4514 1 1 3 13 33 933 1933 2933 7933 66 67 DTAAAA QRGAAA OOOOxx
+9574 4515 0 2 4 14 74 574 1574 4574 9574 148 149 GEAAAA RRGAAA VVVVxx
+8294 4516 0 2 4 14 94 294 294 3294 8294 188 189 AHAAAA SRGAAA AAAAxx
+627 4517 1 3 7 7 27 627 627 627 627 54 55 DYAAAA TRGAAA HHHHxx
+3229 4518 1 1 9 9 29 229 1229 3229 3229 58 59 FUAAAA URGAAA OOOOxx
+3163 4519 1 3 3 3 63 163 1163 3163 3163 126 127 RRAAAA VRGAAA VVVVxx
+7349 4520 1 1 9 9 49 349 1349 2349 7349 98 99 RWAAAA WRGAAA AAAAxx
+6889 4521 1 1 9 9 89 889 889 1889 6889 178 179 ZEAAAA XRGAAA HHHHxx
+2101 4522 1 1 1 1 1 101 101 2101 2101 2 3 VCAAAA YRGAAA OOOOxx
+6476 4523 0 0 6 16 76 476 476 1476 6476 152 153 CPAAAA ZRGAAA VVVVxx
+6765 4524 1 1 5 5 65 765 765 1765 6765 130 131 FAAAAA ASGAAA AAAAxx
+4204 4525 0 0 4 4 4 204 204 4204 4204 8 9 SFAAAA BSGAAA HHHHxx
+5915 4526 1 3 5 15 15 915 1915 915 5915 30 31 NTAAAA CSGAAA OOOOxx
+2318 4527 0 2 8 18 18 318 318 2318 2318 36 37 ELAAAA DSGAAA VVVVxx
+294 4528 0 2 4 14 94 294 294 294 294 188 189 ILAAAA ESGAAA AAAAxx
+5245 4529 1 1 5 5 45 245 1245 245 5245 90 91 TTAAAA FSGAAA HHHHxx
+4481 4530 1 1 1 1 81 481 481 4481 4481 162 163 JQAAAA GSGAAA OOOOxx
+7754 4531 0 2 4 14 54 754 1754 2754 7754 108 109 GMAAAA HSGAAA VVVVxx
+8494 4532 0 2 4 14 94 494 494 3494 8494 188 189 SOAAAA ISGAAA AAAAxx
+4014 4533 0 2 4 14 14 14 14 4014 4014 28 29 KYAAAA JSGAAA HHHHxx
+2197 4534 1 1 7 17 97 197 197 2197 2197 194 195 NGAAAA KSGAAA OOOOxx
+1297 4535 1 1 7 17 97 297 1297 1297 1297 194 195 XXAAAA LSGAAA VVVVxx
+1066 4536 0 2 6 6 66 66 1066 1066 1066 132 133 APAAAA MSGAAA AAAAxx
+5710 4537 0 2 0 10 10 710 1710 710 5710 20 21 QLAAAA NSGAAA HHHHxx
+4100 4538 0 0 0 0 0 100 100 4100 4100 0 1 SBAAAA OSGAAA OOOOxx
+7356 4539 0 0 6 16 56 356 1356 2356 7356 112 113 YWAAAA PSGAAA VVVVxx
+7658 4540 0 2 8 18 58 658 1658 2658 7658 116 117 OIAAAA QSGAAA AAAAxx
+3666 4541 0 2 6 6 66 666 1666 3666 3666 132 133 ALAAAA RSGAAA HHHHxx
+9713 4542 1 1 3 13 13 713 1713 4713 9713 26 27 PJAAAA SSGAAA OOOOxx
+691 4543 1 3 1 11 91 691 691 691 691 182 183 PAAAAA TSGAAA VVVVxx
+3112 4544 0 0 2 12 12 112 1112 3112 3112 24 25 SPAAAA USGAAA AAAAxx
+6035 4545 1 3 5 15 35 35 35 1035 6035 70 71 DYAAAA VSGAAA HHHHxx
+8353 4546 1 1 3 13 53 353 353 3353 8353 106 107 HJAAAA WSGAAA OOOOxx
+5679 4547 1 3 9 19 79 679 1679 679 5679 158 159 LKAAAA XSGAAA VVVVxx
+2124 4548 0 0 4 4 24 124 124 2124 2124 48 49 SDAAAA YSGAAA AAAAxx
+4714 4549 0 2 4 14 14 714 714 4714 4714 28 29 IZAAAA ZSGAAA HHHHxx
+9048 4550 0 0 8 8 48 48 1048 4048 9048 96 97 AKAAAA ATGAAA OOOOxx
+7692 4551 0 0 2 12 92 692 1692 2692 7692 184 185 WJAAAA BTGAAA VVVVxx
+4542 4552 0 2 2 2 42 542 542 4542 4542 84 85 SSAAAA CTGAAA AAAAxx
+8737 4553 1 1 7 17 37 737 737 3737 8737 74 75 BYAAAA DTGAAA HHHHxx
+4977 4554 1 1 7 17 77 977 977 4977 4977 154 155 LJAAAA ETGAAA OOOOxx
+9349 4555 1 1 9 9 49 349 1349 4349 9349 98 99 PVAAAA FTGAAA VVVVxx
+731 4556 1 3 1 11 31 731 731 731 731 62 63 DCAAAA GTGAAA AAAAxx
+1788 4557 0 0 8 8 88 788 1788 1788 1788 176 177 UQAAAA HTGAAA HHHHxx
+7830 4558 0 2 0 10 30 830 1830 2830 7830 60 61 EPAAAA ITGAAA OOOOxx
+3977 4559 1 1 7 17 77 977 1977 3977 3977 154 155 ZWAAAA JTGAAA VVVVxx
+2421 4560 1 1 1 1 21 421 421 2421 2421 42 43 DPAAAA KTGAAA AAAAxx
+5891 4561 1 3 1 11 91 891 1891 891 5891 182 183 PSAAAA LTGAAA HHHHxx
+1111 4562 1 3 1 11 11 111 1111 1111 1111 22 23 TQAAAA MTGAAA OOOOxx
+9224 4563 0 0 4 4 24 224 1224 4224 9224 48 49 UQAAAA NTGAAA VVVVxx
+9872 4564 0 0 2 12 72 872 1872 4872 9872 144 145 SPAAAA OTGAAA AAAAxx
+2433 4565 1 1 3 13 33 433 433 2433 2433 66 67 PPAAAA PTGAAA HHHHxx
+1491 4566 1 3 1 11 91 491 1491 1491 1491 182 183 JFAAAA QTGAAA OOOOxx
+6653 4567 1 1 3 13 53 653 653 1653 6653 106 107 XVAAAA RTGAAA VVVVxx
+1907 4568 1 3 7 7 7 907 1907 1907 1907 14 15 JVAAAA STGAAA AAAAxx
+889 4569 1 1 9 9 89 889 889 889 889 178 179 FIAAAA TTGAAA HHHHxx
+561 4570 1 1 1 1 61 561 561 561 561 122 123 PVAAAA UTGAAA OOOOxx
+7415 4571 1 3 5 15 15 415 1415 2415 7415 30 31 FZAAAA VTGAAA VVVVxx
+2703 4572 1 3 3 3 3 703 703 2703 2703 6 7 ZZAAAA WTGAAA AAAAxx
+2561 4573 1 1 1 1 61 561 561 2561 2561 122 123 NUAAAA XTGAAA HHHHxx
+1257 4574 1 1 7 17 57 257 1257 1257 1257 114 115 JWAAAA YTGAAA OOOOxx
+2390 4575 0 2 0 10 90 390 390 2390 2390 180 181 YNAAAA ZTGAAA VVVVxx
+3915 4576 1 3 5 15 15 915 1915 3915 3915 30 31 PUAAAA AUGAAA AAAAxx
+8476 4577 0 0 6 16 76 476 476 3476 8476 152 153 AOAAAA BUGAAA HHHHxx
+607 4578 1 3 7 7 7 607 607 607 607 14 15 JXAAAA CUGAAA OOOOxx
+3891 4579 1 3 1 11 91 891 1891 3891 3891 182 183 RTAAAA DUGAAA VVVVxx
+7269 4580 1 1 9 9 69 269 1269 2269 7269 138 139 PTAAAA EUGAAA AAAAxx
+9537 4581 1 1 7 17 37 537 1537 4537 9537 74 75 VCAAAA FUGAAA HHHHxx
+8518 4582 0 2 8 18 18 518 518 3518 8518 36 37 QPAAAA GUGAAA OOOOxx
+5221 4583 1 1 1 1 21 221 1221 221 5221 42 43 VSAAAA HUGAAA VVVVxx
+3274 4584 0 2 4 14 74 274 1274 3274 3274 148 149 YVAAAA IUGAAA AAAAxx
+6677 4585 1 1 7 17 77 677 677 1677 6677 154 155 VWAAAA JUGAAA HHHHxx
+3114 4586 0 2 4 14 14 114 1114 3114 3114 28 29 UPAAAA KUGAAA OOOOxx
+1966 4587 0 2 6 6 66 966 1966 1966 1966 132 133 QXAAAA LUGAAA VVVVxx
+5941 4588 1 1 1 1 41 941 1941 941 5941 82 83 NUAAAA MUGAAA AAAAxx
+9463 4589 1 3 3 3 63 463 1463 4463 9463 126 127 ZZAAAA NUGAAA HHHHxx
+8966 4590 0 2 6 6 66 966 966 3966 8966 132 133 WGAAAA OUGAAA OOOOxx
+4402 4591 0 2 2 2 2 402 402 4402 4402 4 5 INAAAA PUGAAA VVVVxx
+3364 4592 0 0 4 4 64 364 1364 3364 3364 128 129 KZAAAA QUGAAA AAAAxx
+3698 4593 0 2 8 18 98 698 1698 3698 3698 196 197 GMAAAA RUGAAA HHHHxx
+4651 4594 1 3 1 11 51 651 651 4651 4651 102 103 XWAAAA SUGAAA OOOOxx
+2127 4595 1 3 7 7 27 127 127 2127 2127 54 55 VDAAAA TUGAAA VVVVxx
+3614 4596 0 2 4 14 14 614 1614 3614 3614 28 29 AJAAAA UUGAAA AAAAxx
+5430 4597 0 2 0 10 30 430 1430 430 5430 60 61 WAAAAA VUGAAA HHHHxx
+3361 4598 1 1 1 1 61 361 1361 3361 3361 122 123 HZAAAA WUGAAA OOOOxx
+4798 4599 0 2 8 18 98 798 798 4798 4798 196 197 OCAAAA XUGAAA VVVVxx
+8269 4600 1 1 9 9 69 269 269 3269 8269 138 139 BGAAAA YUGAAA AAAAxx
+6458 4601 0 2 8 18 58 458 458 1458 6458 116 117 KOAAAA ZUGAAA HHHHxx
+3358 4602 0 2 8 18 58 358 1358 3358 3358 116 117 EZAAAA AVGAAA OOOOxx
+5898 4603 0 2 8 18 98 898 1898 898 5898 196 197 WSAAAA BVGAAA VVVVxx
+1880 4604 0 0 0 0 80 880 1880 1880 1880 160 161 IUAAAA CVGAAA AAAAxx
+782 4605 0 2 2 2 82 782 782 782 782 164 165 CEAAAA DVGAAA HHHHxx
+3102 4606 0 2 2 2 2 102 1102 3102 3102 4 5 IPAAAA EVGAAA OOOOxx
+6366 4607 0 2 6 6 66 366 366 1366 6366 132 133 WKAAAA FVGAAA VVVVxx
+399 4608 1 3 9 19 99 399 399 399 399 198 199 JPAAAA GVGAAA AAAAxx
+6773 4609 1 1 3 13 73 773 773 1773 6773 146 147 NAAAAA HVGAAA HHHHxx
+7942 4610 0 2 2 2 42 942 1942 2942 7942 84 85 MTAAAA IVGAAA OOOOxx
+6274 4611 0 2 4 14 74 274 274 1274 6274 148 149 IHAAAA JVGAAA VVVVxx
+7447 4612 1 3 7 7 47 447 1447 2447 7447 94 95 LAAAAA KVGAAA AAAAxx
+7648 4613 0 0 8 8 48 648 1648 2648 7648 96 97 EIAAAA LVGAAA HHHHxx
+3997 4614 1 1 7 17 97 997 1997 3997 3997 194 195 TXAAAA MVGAAA OOOOxx
+1759 4615 1 3 9 19 59 759 1759 1759 1759 118 119 RPAAAA NVGAAA VVVVxx
+1785 4616 1 1 5 5 85 785 1785 1785 1785 170 171 RQAAAA OVGAAA AAAAxx
+8930 4617 0 2 0 10 30 930 930 3930 8930 60 61 MFAAAA PVGAAA HHHHxx
+7595 4618 1 3 5 15 95 595 1595 2595 7595 190 191 DGAAAA QVGAAA OOOOxx
+6752 4619 0 0 2 12 52 752 752 1752 6752 104 105 SZAAAA RVGAAA VVVVxx
+5635 4620 1 3 5 15 35 635 1635 635 5635 70 71 TIAAAA SVGAAA AAAAxx
+1579 4621 1 3 9 19 79 579 1579 1579 1579 158 159 TIAAAA TVGAAA HHHHxx
+7743 4622 1 3 3 3 43 743 1743 2743 7743 86 87 VLAAAA UVGAAA OOOOxx
+5856 4623 0 0 6 16 56 856 1856 856 5856 112 113 GRAAAA VVGAAA VVVVxx
+7273 4624 1 1 3 13 73 273 1273 2273 7273 146 147 TTAAAA WVGAAA AAAAxx
+1399 4625 1 3 9 19 99 399 1399 1399 1399 198 199 VBAAAA XVGAAA HHHHxx
+3694 4626 0 2 4 14 94 694 1694 3694 3694 188 189 CMAAAA YVGAAA OOOOxx
+2782 4627 0 2 2 2 82 782 782 2782 2782 164 165 ADAAAA ZVGAAA VVVVxx
+6951 4628 1 3 1 11 51 951 951 1951 6951 102 103 JHAAAA AWGAAA AAAAxx
+6053 4629 1 1 3 13 53 53 53 1053 6053 106 107 VYAAAA BWGAAA HHHHxx
+1753 4630 1 1 3 13 53 753 1753 1753 1753 106 107 LPAAAA CWGAAA OOOOxx
+3985 4631 1 1 5 5 85 985 1985 3985 3985 170 171 HXAAAA DWGAAA VVVVxx
+6159 4632 1 3 9 19 59 159 159 1159 6159 118 119 XCAAAA EWGAAA AAAAxx
+6250 4633 0 2 0 10 50 250 250 1250 6250 100 101 KGAAAA FWGAAA HHHHxx
+6240 4634 0 0 0 0 40 240 240 1240 6240 80 81 AGAAAA GWGAAA OOOOxx
+6571 4635 1 3 1 11 71 571 571 1571 6571 142 143 TSAAAA HWGAAA VVVVxx
+8624 4636 0 0 4 4 24 624 624 3624 8624 48 49 STAAAA IWGAAA AAAAxx
+9718 4637 0 2 8 18 18 718 1718 4718 9718 36 37 UJAAAA JWGAAA HHHHxx
+5529 4638 1 1 9 9 29 529 1529 529 5529 58 59 REAAAA KWGAAA OOOOxx
+7089 4639 1 1 9 9 89 89 1089 2089 7089 178 179 RMAAAA LWGAAA VVVVxx
+5488 4640 0 0 8 8 88 488 1488 488 5488 176 177 CDAAAA MWGAAA AAAAxx
+5444 4641 0 0 4 4 44 444 1444 444 5444 88 89 KBAAAA NWGAAA HHHHxx
+4899 4642 1 3 9 19 99 899 899 4899 4899 198 199 LGAAAA OWGAAA OOOOxx
+7928 4643 0 0 8 8 28 928 1928 2928 7928 56 57 YSAAAA PWGAAA VVVVxx
+4736 4644 0 0 6 16 36 736 736 4736 4736 72 73 EAAAAA QWGAAA AAAAxx
+4317 4645 1 1 7 17 17 317 317 4317 4317 34 35 BKAAAA RWGAAA HHHHxx
+1174 4646 0 2 4 14 74 174 1174 1174 1174 148 149 ETAAAA SWGAAA OOOOxx
+6138 4647 0 2 8 18 38 138 138 1138 6138 76 77 CCAAAA TWGAAA VVVVxx
+3943 4648 1 3 3 3 43 943 1943 3943 3943 86 87 RVAAAA UWGAAA AAAAxx
+1545 4649 1 1 5 5 45 545 1545 1545 1545 90 91 LHAAAA VWGAAA HHHHxx
+6867 4650 1 3 7 7 67 867 867 1867 6867 134 135 DEAAAA WWGAAA OOOOxx
+6832 4651 0 0 2 12 32 832 832 1832 6832 64 65 UCAAAA XWGAAA VVVVxx
+2987 4652 1 3 7 7 87 987 987 2987 2987 174 175 XKAAAA YWGAAA AAAAxx
+5169 4653 1 1 9 9 69 169 1169 169 5169 138 139 VQAAAA ZWGAAA HHHHxx
+8998 4654 0 2 8 18 98 998 998 3998 8998 196 197 CIAAAA AXGAAA OOOOxx
+9347 4655 1 3 7 7 47 347 1347 4347 9347 94 95 NVAAAA BXGAAA VVVVxx
+4800 4656 0 0 0 0 0 800 800 4800 4800 0 1 QCAAAA CXGAAA AAAAxx
+4200 4657 0 0 0 0 0 200 200 4200 4200 0 1 OFAAAA DXGAAA HHHHxx
+4046 4658 0 2 6 6 46 46 46 4046 4046 92 93 QZAAAA EXGAAA OOOOxx
+7142 4659 0 2 2 2 42 142 1142 2142 7142 84 85 SOAAAA FXGAAA VVVVxx
+2733 4660 1 1 3 13 33 733 733 2733 2733 66 67 DBAAAA GXGAAA AAAAxx
+1568 4661 0 0 8 8 68 568 1568 1568 1568 136 137 IIAAAA HXGAAA HHHHxx
+5105 4662 1 1 5 5 5 105 1105 105 5105 10 11 JOAAAA IXGAAA OOOOxx
+9115 4663 1 3 5 15 15 115 1115 4115 9115 30 31 PMAAAA JXGAAA VVVVxx
+6475 4664 1 3 5 15 75 475 475 1475 6475 150 151 BPAAAA KXGAAA AAAAxx
+3796 4665 0 0 6 16 96 796 1796 3796 3796 192 193 AQAAAA LXGAAA HHHHxx
+5410 4666 0 2 0 10 10 410 1410 410 5410 20 21 CAAAAA MXGAAA OOOOxx
+4023 4667 1 3 3 3 23 23 23 4023 4023 46 47 TYAAAA NXGAAA VVVVxx
+8904 4668 0 0 4 4 4 904 904 3904 8904 8 9 MEAAAA OXGAAA AAAAxx
+450 4669 0 2 0 10 50 450 450 450 450 100 101 IRAAAA PXGAAA HHHHxx
+8087 4670 1 3 7 7 87 87 87 3087 8087 174 175 BZAAAA QXGAAA OOOOxx
+6478 4671 0 2 8 18 78 478 478 1478 6478 156 157 EPAAAA RXGAAA VVVVxx
+2696 4672 0 0 6 16 96 696 696 2696 2696 192 193 SZAAAA SXGAAA AAAAxx
+1792 4673 0 0 2 12 92 792 1792 1792 1792 184 185 YQAAAA TXGAAA HHHHxx
+9699 4674 1 3 9 19 99 699 1699 4699 9699 198 199 BJAAAA UXGAAA OOOOxx
+9160 4675 0 0 0 0 60 160 1160 4160 9160 120 121 IOAAAA VXGAAA VVVVxx
+9989 4676 1 1 9 9 89 989 1989 4989 9989 178 179 FUAAAA WXGAAA AAAAxx
+9568 4677 0 0 8 8 68 568 1568 4568 9568 136 137 AEAAAA XXGAAA HHHHxx
+487 4678 1 3 7 7 87 487 487 487 487 174 175 TSAAAA YXGAAA OOOOxx
+7863 4679 1 3 3 3 63 863 1863 2863 7863 126 127 LQAAAA ZXGAAA VVVVxx
+1884 4680 0 0 4 4 84 884 1884 1884 1884 168 169 MUAAAA AYGAAA AAAAxx
+2651 4681 1 3 1 11 51 651 651 2651 2651 102 103 ZXAAAA BYGAAA HHHHxx
+8285 4682 1 1 5 5 85 285 285 3285 8285 170 171 RGAAAA CYGAAA OOOOxx
+3927 4683 1 3 7 7 27 927 1927 3927 3927 54 55 BVAAAA DYGAAA VVVVxx
+4076 4684 0 0 6 16 76 76 76 4076 4076 152 153 UAAAAA EYGAAA AAAAxx
+6149 4685 1 1 9 9 49 149 149 1149 6149 98 99 NCAAAA FYGAAA HHHHxx
+6581 4686 1 1 1 1 81 581 581 1581 6581 162 163 DTAAAA GYGAAA OOOOxx
+8293 4687 1 1 3 13 93 293 293 3293 8293 186 187 ZGAAAA HYGAAA VVVVxx
+7665 4688 1 1 5 5 65 665 1665 2665 7665 130 131 VIAAAA IYGAAA AAAAxx
+4435 4689 1 3 5 15 35 435 435 4435 4435 70 71 POAAAA JYGAAA HHHHxx
+1271 4690 1 3 1 11 71 271 1271 1271 1271 142 143 XWAAAA KYGAAA OOOOxx
+3928 4691 0 0 8 8 28 928 1928 3928 3928 56 57 CVAAAA LYGAAA VVVVxx
+7045 4692 1 1 5 5 45 45 1045 2045 7045 90 91 ZKAAAA MYGAAA AAAAxx
+4943 4693 1 3 3 3 43 943 943 4943 4943 86 87 DIAAAA NYGAAA HHHHxx
+8473 4694 1 1 3 13 73 473 473 3473 8473 146 147 XNAAAA OYGAAA OOOOxx
+1707 4695 1 3 7 7 7 707 1707 1707 1707 14 15 RNAAAA PYGAAA VVVVxx
+7509 4696 1 1 9 9 9 509 1509 2509 7509 18 19 VCAAAA QYGAAA AAAAxx
+1593 4697 1 1 3 13 93 593 1593 1593 1593 186 187 HJAAAA RYGAAA HHHHxx
+9281 4698 1 1 1 1 81 281 1281 4281 9281 162 163 ZSAAAA SYGAAA OOOOxx
+8986 4699 0 2 6 6 86 986 986 3986 8986 172 173 QHAAAA TYGAAA VVVVxx
+3740 4700 0 0 0 0 40 740 1740 3740 3740 80 81 WNAAAA UYGAAA AAAAxx
+9265 4701 1 1 5 5 65 265 1265 4265 9265 130 131 JSAAAA VYGAAA HHHHxx
+1510 4702 0 2 0 10 10 510 1510 1510 1510 20 21 CGAAAA WYGAAA OOOOxx
+3022 4703 0 2 2 2 22 22 1022 3022 3022 44 45 GMAAAA XYGAAA VVVVxx
+9014 4704 0 2 4 14 14 14 1014 4014 9014 28 29 SIAAAA YYGAAA AAAAxx
+6816 4705 0 0 6 16 16 816 816 1816 6816 32 33 ECAAAA ZYGAAA HHHHxx
+5518 4706 0 2 8 18 18 518 1518 518 5518 36 37 GEAAAA AZGAAA OOOOxx
+4451 4707 1 3 1 11 51 451 451 4451 4451 102 103 FPAAAA BZGAAA VVVVxx
+8747 4708 1 3 7 7 47 747 747 3747 8747 94 95 LYAAAA CZGAAA AAAAxx
+4646 4709 0 2 6 6 46 646 646 4646 4646 92 93 SWAAAA DZGAAA HHHHxx
+7296 4710 0 0 6 16 96 296 1296 2296 7296 192 193 QUAAAA EZGAAA OOOOxx
+9644 4711 0 0 4 4 44 644 1644 4644 9644 88 89 YGAAAA FZGAAA VVVVxx
+5977 4712 1 1 7 17 77 977 1977 977 5977 154 155 XVAAAA GZGAAA AAAAxx
+6270 4713 0 2 0 10 70 270 270 1270 6270 140 141 EHAAAA HZGAAA HHHHxx
+5578 4714 0 2 8 18 78 578 1578 578 5578 156 157 OGAAAA IZGAAA OOOOxx
+2465 4715 1 1 5 5 65 465 465 2465 2465 130 131 VQAAAA JZGAAA VVVVxx
+6436 4716 0 0 6 16 36 436 436 1436 6436 72 73 ONAAAA KZGAAA AAAAxx
+8089 4717 1 1 9 9 89 89 89 3089 8089 178 179 DZAAAA LZGAAA HHHHxx
+2409 4718 1 1 9 9 9 409 409 2409 2409 18 19 ROAAAA MZGAAA OOOOxx
+284 4719 0 0 4 4 84 284 284 284 284 168 169 YKAAAA NZGAAA VVVVxx
+5576 4720 0 0 6 16 76 576 1576 576 5576 152 153 MGAAAA OZGAAA AAAAxx
+6534 4721 0 2 4 14 34 534 534 1534 6534 68 69 IRAAAA PZGAAA HHHHxx
+8848 4722 0 0 8 8 48 848 848 3848 8848 96 97 ICAAAA QZGAAA OOOOxx
+4305 4723 1 1 5 5 5 305 305 4305 4305 10 11 PJAAAA RZGAAA VVVVxx
+5574 4724 0 2 4 14 74 574 1574 574 5574 148 149 KGAAAA SZGAAA AAAAxx
+596 4725 0 0 6 16 96 596 596 596 596 192 193 YWAAAA TZGAAA HHHHxx
+1253 4726 1 1 3 13 53 253 1253 1253 1253 106 107 FWAAAA UZGAAA OOOOxx
+521 4727 1 1 1 1 21 521 521 521 521 42 43 BUAAAA VZGAAA VVVVxx
+8739 4728 1 3 9 19 39 739 739 3739 8739 78 79 DYAAAA WZGAAA AAAAxx
+908 4729 0 0 8 8 8 908 908 908 908 16 17 YIAAAA XZGAAA HHHHxx
+6937 4730 1 1 7 17 37 937 937 1937 6937 74 75 VGAAAA YZGAAA OOOOxx
+4515 4731 1 3 5 15 15 515 515 4515 4515 30 31 RRAAAA ZZGAAA VVVVxx
+8630 4732 0 2 0 10 30 630 630 3630 8630 60 61 YTAAAA AAHAAA AAAAxx
+7518 4733 0 2 8 18 18 518 1518 2518 7518 36 37 EDAAAA BAHAAA HHHHxx
+8300 4734 0 0 0 0 0 300 300 3300 8300 0 1 GHAAAA CAHAAA OOOOxx
+8434 4735 0 2 4 14 34 434 434 3434 8434 68 69 KMAAAA DAHAAA VVVVxx
+6000 4736 0 0 0 0 0 0 0 1000 6000 0 1 UWAAAA EAHAAA AAAAxx
+4508 4737 0 0 8 8 8 508 508 4508 4508 16 17 KRAAAA FAHAAA HHHHxx
+7861 4738 1 1 1 1 61 861 1861 2861 7861 122 123 JQAAAA GAHAAA OOOOxx
+5953 4739 1 1 3 13 53 953 1953 953 5953 106 107 ZUAAAA HAHAAA VVVVxx
+5063 4740 1 3 3 3 63 63 1063 63 5063 126 127 TMAAAA IAHAAA AAAAxx
+4501 4741 1 1 1 1 1 501 501 4501 4501 2 3 DRAAAA JAHAAA HHHHxx
+7092 4742 0 0 2 12 92 92 1092 2092 7092 184 185 UMAAAA KAHAAA OOOOxx
+4388 4743 0 0 8 8 88 388 388 4388 4388 176 177 UMAAAA LAHAAA VVVVxx
+1826 4744 0 2 6 6 26 826 1826 1826 1826 52 53 GSAAAA MAHAAA AAAAxx
+568 4745 0 0 8 8 68 568 568 568 568 136 137 WVAAAA NAHAAA HHHHxx
+8184 4746 0 0 4 4 84 184 184 3184 8184 168 169 UCAAAA OAHAAA OOOOxx
+4268 4747 0 0 8 8 68 268 268 4268 4268 136 137 EIAAAA PAHAAA VVVVxx
+5798 4748 0 2 8 18 98 798 1798 798 5798 196 197 APAAAA QAHAAA AAAAxx
+5190 4749 0 2 0 10 90 190 1190 190 5190 180 181 QRAAAA RAHAAA HHHHxx
+1298 4750 0 2 8 18 98 298 1298 1298 1298 196 197 YXAAAA SAHAAA OOOOxx
+4035 4751 1 3 5 15 35 35 35 4035 4035 70 71 FZAAAA TAHAAA VVVVxx
+4504 4752 0 0 4 4 4 504 504 4504 4504 8 9 GRAAAA UAHAAA AAAAxx
+5992 4753 0 0 2 12 92 992 1992 992 5992 184 185 MWAAAA VAHAAA HHHHxx
+770 4754 0 2 0 10 70 770 770 770 770 140 141 QDAAAA WAHAAA OOOOxx
+7502 4755 0 2 2 2 2 502 1502 2502 7502 4 5 OCAAAA XAHAAA VVVVxx
+824 4756 0 0 4 4 24 824 824 824 824 48 49 SFAAAA YAHAAA AAAAxx
+7716 4757 0 0 6 16 16 716 1716 2716 7716 32 33 UKAAAA ZAHAAA HHHHxx
+5749 4758 1 1 9 9 49 749 1749 749 5749 98 99 DNAAAA ABHAAA OOOOxx
+9814 4759 0 2 4 14 14 814 1814 4814 9814 28 29 MNAAAA BBHAAA VVVVxx
+350 4760 0 2 0 10 50 350 350 350 350 100 101 MNAAAA CBHAAA AAAAxx
+1390 4761 0 2 0 10 90 390 1390 1390 1390 180 181 MBAAAA DBHAAA HHHHxx
+6994 4762 0 2 4 14 94 994 994 1994 6994 188 189 AJAAAA EBHAAA OOOOxx
+3629 4763 1 1 9 9 29 629 1629 3629 3629 58 59 PJAAAA FBHAAA VVVVxx
+9937 4764 1 1 7 17 37 937 1937 4937 9937 74 75 FSAAAA GBHAAA AAAAxx
+5285 4765 1 1 5 5 85 285 1285 285 5285 170 171 HVAAAA HBHAAA HHHHxx
+3157 4766 1 1 7 17 57 157 1157 3157 3157 114 115 LRAAAA IBHAAA OOOOxx
+9549 4767 1 1 9 9 49 549 1549 4549 9549 98 99 HDAAAA JBHAAA VVVVxx
+4118 4768 0 2 8 18 18 118 118 4118 4118 36 37 KCAAAA KBHAAA AAAAxx
+756 4769 0 0 6 16 56 756 756 756 756 112 113 CDAAAA LBHAAA HHHHxx
+5964 4770 0 0 4 4 64 964 1964 964 5964 128 129 KVAAAA MBHAAA OOOOxx
+7701 4771 1 1 1 1 1 701 1701 2701 7701 2 3 FKAAAA NBHAAA VVVVxx
+1242 4772 0 2 2 2 42 242 1242 1242 1242 84 85 UVAAAA OBHAAA AAAAxx
+7890 4773 0 2 0 10 90 890 1890 2890 7890 180 181 MRAAAA PBHAAA HHHHxx
+1991 4774 1 3 1 11 91 991 1991 1991 1991 182 183 PYAAAA QBHAAA OOOOxx
+110 4775 0 2 0 10 10 110 110 110 110 20 21 GEAAAA RBHAAA VVVVxx
+9334 4776 0 2 4 14 34 334 1334 4334 9334 68 69 AVAAAA SBHAAA AAAAxx
+6231 4777 1 3 1 11 31 231 231 1231 6231 62 63 RFAAAA TBHAAA HHHHxx
+9871 4778 1 3 1 11 71 871 1871 4871 9871 142 143 RPAAAA UBHAAA OOOOxx
+9471 4779 1 3 1 11 71 471 1471 4471 9471 142 143 HAAAAA VBHAAA VVVVxx
+2697 4780 1 1 7 17 97 697 697 2697 2697 194 195 TZAAAA WBHAAA AAAAxx
+4761 4781 1 1 1 1 61 761 761 4761 4761 122 123 DBAAAA XBHAAA HHHHxx
+8493 4782 1 1 3 13 93 493 493 3493 8493 186 187 ROAAAA YBHAAA OOOOxx
+1045 4783 1 1 5 5 45 45 1045 1045 1045 90 91 FOAAAA ZBHAAA VVVVxx
+3403 4784 1 3 3 3 3 403 1403 3403 3403 6 7 XAAAAA ACHAAA AAAAxx
+9412 4785 0 0 2 12 12 412 1412 4412 9412 24 25 AYAAAA BCHAAA HHHHxx
+7652 4786 0 0 2 12 52 652 1652 2652 7652 104 105 IIAAAA CCHAAA OOOOxx
+5866 4787 0 2 6 6 66 866 1866 866 5866 132 133 QRAAAA DCHAAA VVVVxx
+6942 4788 0 2 2 2 42 942 942 1942 6942 84 85 AHAAAA ECHAAA AAAAxx
+9353 4789 1 1 3 13 53 353 1353 4353 9353 106 107 TVAAAA FCHAAA HHHHxx
+2600 4790 0 0 0 0 0 600 600 2600 2600 0 1 AWAAAA GCHAAA OOOOxx
+6971 4791 1 3 1 11 71 971 971 1971 6971 142 143 DIAAAA HCHAAA VVVVxx
+5391 4792 1 3 1 11 91 391 1391 391 5391 182 183 JZAAAA ICHAAA AAAAxx
+7654 4793 0 2 4 14 54 654 1654 2654 7654 108 109 KIAAAA JCHAAA HHHHxx
+1797 4794 1 1 7 17 97 797 1797 1797 1797 194 195 DRAAAA KCHAAA OOOOxx
+4530 4795 0 2 0 10 30 530 530 4530 4530 60 61 GSAAAA LCHAAA VVVVxx
+3130 4796 0 2 0 10 30 130 1130 3130 3130 60 61 KQAAAA MCHAAA AAAAxx
+9442 4797 0 2 2 2 42 442 1442 4442 9442 84 85 EZAAAA NCHAAA HHHHxx
+6659 4798 1 3 9 19 59 659 659 1659 6659 118 119 DWAAAA OCHAAA OOOOxx
+9714 4799 0 2 4 14 14 714 1714 4714 9714 28 29 QJAAAA PCHAAA VVVVxx
+3660 4800 0 0 0 0 60 660 1660 3660 3660 120 121 UKAAAA QCHAAA AAAAxx
+1906 4801 0 2 6 6 6 906 1906 1906 1906 12 13 IVAAAA RCHAAA HHHHxx
+7927 4802 1 3 7 7 27 927 1927 2927 7927 54 55 XSAAAA SCHAAA OOOOxx
+1767 4803 1 3 7 7 67 767 1767 1767 1767 134 135 ZPAAAA TCHAAA VVVVxx
+5523 4804 1 3 3 3 23 523 1523 523 5523 46 47 LEAAAA UCHAAA AAAAxx
+9289 4805 1 1 9 9 89 289 1289 4289 9289 178 179 HTAAAA VCHAAA HHHHxx
+2717 4806 1 1 7 17 17 717 717 2717 2717 34 35 NAAAAA WCHAAA OOOOxx
+4099 4807 1 3 9 19 99 99 99 4099 4099 198 199 RBAAAA XCHAAA VVVVxx
+4387 4808 1 3 7 7 87 387 387 4387 4387 174 175 TMAAAA YCHAAA AAAAxx
+8864 4809 0 0 4 4 64 864 864 3864 8864 128 129 YCAAAA ZCHAAA HHHHxx
+1774 4810 0 2 4 14 74 774 1774 1774 1774 148 149 GQAAAA ADHAAA OOOOxx
+6292 4811 0 0 2 12 92 292 292 1292 6292 184 185 AIAAAA BDHAAA VVVVxx
+847 4812 1 3 7 7 47 847 847 847 847 94 95 PGAAAA CDHAAA AAAAxx
+5954 4813 0 2 4 14 54 954 1954 954 5954 108 109 AVAAAA DDHAAA HHHHxx
+8032 4814 0 0 2 12 32 32 32 3032 8032 64 65 YWAAAA EDHAAA OOOOxx
+3295 4815 1 3 5 15 95 295 1295 3295 3295 190 191 TWAAAA FDHAAA VVVVxx
+8984 4816 0 0 4 4 84 984 984 3984 8984 168 169 OHAAAA GDHAAA AAAAxx
+7809 4817 1 1 9 9 9 809 1809 2809 7809 18 19 JOAAAA HDHAAA HHHHxx
+1670 4818 0 2 0 10 70 670 1670 1670 1670 140 141 GMAAAA IDHAAA OOOOxx
+7733 4819 1 1 3 13 33 733 1733 2733 7733 66 67 LLAAAA JDHAAA VVVVxx
+6187 4820 1 3 7 7 87 187 187 1187 6187 174 175 ZDAAAA KDHAAA AAAAxx
+9326 4821 0 2 6 6 26 326 1326 4326 9326 52 53 SUAAAA LDHAAA HHHHxx
+2493 4822 1 1 3 13 93 493 493 2493 2493 186 187 XRAAAA MDHAAA OOOOxx
+9512 4823 0 0 2 12 12 512 1512 4512 9512 24 25 WBAAAA NDHAAA VVVVxx
+4342 4824 0 2 2 2 42 342 342 4342 4342 84 85 ALAAAA ODHAAA AAAAxx
+5350 4825 0 2 0 10 50 350 1350 350 5350 100 101 UXAAAA PDHAAA HHHHxx
+6009 4826 1 1 9 9 9 9 9 1009 6009 18 19 DXAAAA QDHAAA OOOOxx
+1208 4827 0 0 8 8 8 208 1208 1208 1208 16 17 MUAAAA RDHAAA VVVVxx
+7014 4828 0 2 4 14 14 14 1014 2014 7014 28 29 UJAAAA SDHAAA AAAAxx
+2967 4829 1 3 7 7 67 967 967 2967 2967 134 135 DKAAAA TDHAAA HHHHxx
+5831 4830 1 3 1 11 31 831 1831 831 5831 62 63 HQAAAA UDHAAA OOOOxx
+3097 4831 1 1 7 17 97 97 1097 3097 3097 194 195 DPAAAA VDHAAA VVVVxx
+1528 4832 0 0 8 8 28 528 1528 1528 1528 56 57 UGAAAA WDHAAA AAAAxx
+6429 4833 1 1 9 9 29 429 429 1429 6429 58 59 HNAAAA XDHAAA HHHHxx
+7320 4834 0 0 0 0 20 320 1320 2320 7320 40 41 OVAAAA YDHAAA OOOOxx
+844 4835 0 0 4 4 44 844 844 844 844 88 89 MGAAAA ZDHAAA VVVVxx
+7054 4836 0 2 4 14 54 54 1054 2054 7054 108 109 ILAAAA AEHAAA AAAAxx
+1643 4837 1 3 3 3 43 643 1643 1643 1643 86 87 FLAAAA BEHAAA HHHHxx
+7626 4838 0 2 6 6 26 626 1626 2626 7626 52 53 IHAAAA CEHAAA OOOOxx
+8728 4839 0 0 8 8 28 728 728 3728 8728 56 57 SXAAAA DEHAAA VVVVxx
+8277 4840 1 1 7 17 77 277 277 3277 8277 154 155 JGAAAA EEHAAA AAAAxx
+189 4841 1 1 9 9 89 189 189 189 189 178 179 HHAAAA FEHAAA HHHHxx
+3717 4842 1 1 7 17 17 717 1717 3717 3717 34 35 ZMAAAA GEHAAA OOOOxx
+1020 4843 0 0 0 0 20 20 1020 1020 1020 40 41 GNAAAA HEHAAA VVVVxx
+9234 4844 0 2 4 14 34 234 1234 4234 9234 68 69 ERAAAA IEHAAA AAAAxx
+9541 4845 1 1 1 1 41 541 1541 4541 9541 82 83 ZCAAAA JEHAAA HHHHxx
+380 4846 0 0 0 0 80 380 380 380 380 160 161 QOAAAA KEHAAA OOOOxx
+397 4847 1 1 7 17 97 397 397 397 397 194 195 HPAAAA LEHAAA VVVVxx
+835 4848 1 3 5 15 35 835 835 835 835 70 71 DGAAAA MEHAAA AAAAxx
+347 4849 1 3 7 7 47 347 347 347 347 94 95 JNAAAA NEHAAA HHHHxx
+2490 4850 0 2 0 10 90 490 490 2490 2490 180 181 URAAAA OEHAAA OOOOxx
+605 4851 1 1 5 5 5 605 605 605 605 10 11 HXAAAA PEHAAA VVVVxx
+7960 4852 0 0 0 0 60 960 1960 2960 7960 120 121 EUAAAA QEHAAA AAAAxx
+9681 4853 1 1 1 1 81 681 1681 4681 9681 162 163 JIAAAA REHAAA HHHHxx
+5753 4854 1 1 3 13 53 753 1753 753 5753 106 107 HNAAAA SEHAAA OOOOxx
+1676 4855 0 0 6 16 76 676 1676 1676 1676 152 153 MMAAAA TEHAAA VVVVxx
+5533 4856 1 1 3 13 33 533 1533 533 5533 66 67 VEAAAA UEHAAA AAAAxx
+8958 4857 0 2 8 18 58 958 958 3958 8958 116 117 OGAAAA VEHAAA HHHHxx
+664 4858 0 0 4 4 64 664 664 664 664 128 129 OZAAAA WEHAAA OOOOxx
+3005 4859 1 1 5 5 5 5 1005 3005 3005 10 11 PLAAAA XEHAAA VVVVxx
+8576 4860 0 0 6 16 76 576 576 3576 8576 152 153 WRAAAA YEHAAA AAAAxx
+7304 4861 0 0 4 4 4 304 1304 2304 7304 8 9 YUAAAA ZEHAAA HHHHxx
+3375 4862 1 3 5 15 75 375 1375 3375 3375 150 151 VZAAAA AFHAAA OOOOxx
+6336 4863 0 0 6 16 36 336 336 1336 6336 72 73 SJAAAA BFHAAA VVVVxx
+1392 4864 0 0 2 12 92 392 1392 1392 1392 184 185 OBAAAA CFHAAA AAAAxx
+2925 4865 1 1 5 5 25 925 925 2925 2925 50 51 NIAAAA DFHAAA HHHHxx
+1217 4866 1 1 7 17 17 217 1217 1217 1217 34 35 VUAAAA EFHAAA OOOOxx
+3714 4867 0 2 4 14 14 714 1714 3714 3714 28 29 WMAAAA FFHAAA VVVVxx
+2120 4868 0 0 0 0 20 120 120 2120 2120 40 41 ODAAAA GFHAAA AAAAxx
+2845 4869 1 1 5 5 45 845 845 2845 2845 90 91 LFAAAA HFHAAA HHHHxx
+3865 4870 1 1 5 5 65 865 1865 3865 3865 130 131 RSAAAA IFHAAA OOOOxx
+124 4871 0 0 4 4 24 124 124 124 124 48 49 UEAAAA JFHAAA VVVVxx
+865 4872 1 1 5 5 65 865 865 865 865 130 131 HHAAAA KFHAAA AAAAxx
+9361 4873 1 1 1 1 61 361 1361 4361 9361 122 123 BWAAAA LFHAAA HHHHxx
+6338 4874 0 2 8 18 38 338 338 1338 6338 76 77 UJAAAA MFHAAA OOOOxx
+7330 4875 0 2 0 10 30 330 1330 2330 7330 60 61 YVAAAA NFHAAA VVVVxx
+513 4876 1 1 3 13 13 513 513 513 513 26 27 TTAAAA OFHAAA AAAAxx
+5001 4877 1 1 1 1 1 1 1001 1 5001 2 3 JKAAAA PFHAAA HHHHxx
+549 4878 1 1 9 9 49 549 549 549 549 98 99 DVAAAA QFHAAA OOOOxx
+1808 4879 0 0 8 8 8 808 1808 1808 1808 16 17 ORAAAA RFHAAA VVVVxx
+7168 4880 0 0 8 8 68 168 1168 2168 7168 136 137 SPAAAA SFHAAA AAAAxx
+9878 4881 0 2 8 18 78 878 1878 4878 9878 156 157 YPAAAA TFHAAA HHHHxx
+233 4882 1 1 3 13 33 233 233 233 233 66 67 ZIAAAA UFHAAA OOOOxx
+4262 4883 0 2 2 2 62 262 262 4262 4262 124 125 YHAAAA VFHAAA VVVVxx
+7998 4884 0 2 8 18 98 998 1998 2998 7998 196 197 QVAAAA WFHAAA AAAAxx
+2419 4885 1 3 9 19 19 419 419 2419 2419 38 39 BPAAAA XFHAAA HHHHxx
+9960 4886 0 0 0 0 60 960 1960 4960 9960 120 121 CTAAAA YFHAAA OOOOxx
+3523 4887 1 3 3 3 23 523 1523 3523 3523 46 47 NFAAAA ZFHAAA VVVVxx
+5440 4888 0 0 0 0 40 440 1440 440 5440 80 81 GBAAAA AGHAAA AAAAxx
+3030 4889 0 2 0 10 30 30 1030 3030 3030 60 61 OMAAAA BGHAAA HHHHxx
+2745 4890 1 1 5 5 45 745 745 2745 2745 90 91 PBAAAA CGHAAA OOOOxx
+7175 4891 1 3 5 15 75 175 1175 2175 7175 150 151 ZPAAAA DGHAAA VVVVxx
+640 4892 0 0 0 0 40 640 640 640 640 80 81 QYAAAA EGHAAA AAAAxx
+1798 4893 0 2 8 18 98 798 1798 1798 1798 196 197 ERAAAA FGHAAA HHHHxx
+7499 4894 1 3 9 19 99 499 1499 2499 7499 198 199 LCAAAA GGHAAA OOOOxx
+1924 4895 0 0 4 4 24 924 1924 1924 1924 48 49 AWAAAA HGHAAA VVVVxx
+1327 4896 1 3 7 7 27 327 1327 1327 1327 54 55 BZAAAA IGHAAA AAAAxx
+73 4897 1 1 3 13 73 73 73 73 73 146 147 VCAAAA JGHAAA HHHHxx
+9558 4898 0 2 8 18 58 558 1558 4558 9558 116 117 QDAAAA KGHAAA OOOOxx
+818 4899 0 2 8 18 18 818 818 818 818 36 37 MFAAAA LGHAAA VVVVxx
+9916 4900 0 0 6 16 16 916 1916 4916 9916 32 33 KRAAAA MGHAAA AAAAxx
+2978 4901 0 2 8 18 78 978 978 2978 2978 156 157 OKAAAA NGHAAA HHHHxx
+8469 4902 1 1 9 9 69 469 469 3469 8469 138 139 TNAAAA OGHAAA OOOOxx
+9845 4903 1 1 5 5 45 845 1845 4845 9845 90 91 ROAAAA PGHAAA VVVVxx
+2326 4904 0 2 6 6 26 326 326 2326 2326 52 53 MLAAAA QGHAAA AAAAxx
+4032 4905 0 0 2 12 32 32 32 4032 4032 64 65 CZAAAA RGHAAA HHHHxx
+5604 4906 0 0 4 4 4 604 1604 604 5604 8 9 OHAAAA SGHAAA OOOOxx
+9610 4907 0 2 0 10 10 610 1610 4610 9610 20 21 QFAAAA TGHAAA VVVVxx
+5101 4908 1 1 1 1 1 101 1101 101 5101 2 3 FOAAAA UGHAAA AAAAxx
+7246 4909 0 2 6 6 46 246 1246 2246 7246 92 93 SSAAAA VGHAAA HHHHxx
+1292 4910 0 0 2 12 92 292 1292 1292 1292 184 185 SXAAAA WGHAAA OOOOxx
+6235 4911 1 3 5 15 35 235 235 1235 6235 70 71 VFAAAA XGHAAA VVVVxx
+1733 4912 1 1 3 13 33 733 1733 1733 1733 66 67 ROAAAA YGHAAA AAAAxx
+4647 4913 1 3 7 7 47 647 647 4647 4647 94 95 TWAAAA ZGHAAA HHHHxx
+258 4914 0 2 8 18 58 258 258 258 258 116 117 YJAAAA AHHAAA OOOOxx
+8438 4915 0 2 8 18 38 438 438 3438 8438 76 77 OMAAAA BHHAAA VVVVxx
+7869 4916 1 1 9 9 69 869 1869 2869 7869 138 139 RQAAAA CHHAAA AAAAxx
+9691 4917 1 3 1 11 91 691 1691 4691 9691 182 183 TIAAAA DHHAAA HHHHxx
+5422 4918 0 2 2 2 22 422 1422 422 5422 44 45 OAAAAA EHHAAA OOOOxx
+9630 4919 0 2 0 10 30 630 1630 4630 9630 60 61 KGAAAA FHHAAA VVVVxx
+4439 4920 1 3 9 19 39 439 439 4439 4439 78 79 TOAAAA GHHAAA AAAAxx
+3140 4921 0 0 0 0 40 140 1140 3140 3140 80 81 UQAAAA HHHAAA HHHHxx
+9111 4922 1 3 1 11 11 111 1111 4111 9111 22 23 LMAAAA IHHAAA OOOOxx
+4606 4923 0 2 6 6 6 606 606 4606 4606 12 13 EVAAAA JHHAAA VVVVxx
+8620 4924 0 0 0 0 20 620 620 3620 8620 40 41 OTAAAA KHHAAA AAAAxx
+7849 4925 1 1 9 9 49 849 1849 2849 7849 98 99 XPAAAA LHHAAA HHHHxx
+346 4926 0 2 6 6 46 346 346 346 346 92 93 INAAAA MHHAAA OOOOxx
+9528 4927 0 0 8 8 28 528 1528 4528 9528 56 57 MCAAAA NHHAAA VVVVxx
+1811 4928 1 3 1 11 11 811 1811 1811 1811 22 23 RRAAAA OHHAAA AAAAxx
+6068 4929 0 0 8 8 68 68 68 1068 6068 136 137 KZAAAA PHHAAA HHHHxx
+6260 4930 0 0 0 0 60 260 260 1260 6260 120 121 UGAAAA QHHAAA OOOOxx
+5909 4931 1 1 9 9 9 909 1909 909 5909 18 19 HTAAAA RHHAAA VVVVxx
+4518 4932 0 2 8 18 18 518 518 4518 4518 36 37 URAAAA SHHAAA AAAAxx
+7530 4933 0 2 0 10 30 530 1530 2530 7530 60 61 QDAAAA THHAAA HHHHxx
+3900 4934 0 0 0 0 0 900 1900 3900 3900 0 1 AUAAAA UHHAAA OOOOxx
+3969 4935 1 1 9 9 69 969 1969 3969 3969 138 139 RWAAAA VHHAAA VVVVxx
+8690 4936 0 2 0 10 90 690 690 3690 8690 180 181 GWAAAA WHHAAA AAAAxx
+5532 4937 0 0 2 12 32 532 1532 532 5532 64 65 UEAAAA XHHAAA HHHHxx
+5989 4938 1 1 9 9 89 989 1989 989 5989 178 179 JWAAAA YHHAAA OOOOxx
+1870 4939 0 2 0 10 70 870 1870 1870 1870 140 141 YTAAAA ZHHAAA VVVVxx
+1113 4940 1 1 3 13 13 113 1113 1113 1113 26 27 VQAAAA AIHAAA AAAAxx
+5155 4941 1 3 5 15 55 155 1155 155 5155 110 111 HQAAAA BIHAAA HHHHxx
+7460 4942 0 0 0 0 60 460 1460 2460 7460 120 121 YAAAAA CIHAAA OOOOxx
+6217 4943 1 1 7 17 17 217 217 1217 6217 34 35 DFAAAA DIHAAA VVVVxx
+8333 4944 1 1 3 13 33 333 333 3333 8333 66 67 NIAAAA EIHAAA AAAAxx
+6341 4945 1 1 1 1 41 341 341 1341 6341 82 83 XJAAAA FIHAAA HHHHxx
+6230 4946 0 2 0 10 30 230 230 1230 6230 60 61 QFAAAA GIHAAA OOOOxx
+6902 4947 0 2 2 2 2 902 902 1902 6902 4 5 MFAAAA HIHAAA VVVVxx
+670 4948 0 2 0 10 70 670 670 670 670 140 141 UZAAAA IIHAAA AAAAxx
+805 4949 1 1 5 5 5 805 805 805 805 10 11 ZEAAAA JIHAAA HHHHxx
+1340 4950 0 0 0 0 40 340 1340 1340 1340 80 81 OZAAAA KIHAAA OOOOxx
+8649 4951 1 1 9 9 49 649 649 3649 8649 98 99 RUAAAA LIHAAA VVVVxx
+3887 4952 1 3 7 7 87 887 1887 3887 3887 174 175 NTAAAA MIHAAA AAAAxx
+5400 4953 0 0 0 0 0 400 1400 400 5400 0 1 SZAAAA NIHAAA HHHHxx
+4354 4954 0 2 4 14 54 354 354 4354 4354 108 109 MLAAAA OIHAAA OOOOxx
+950 4955 0 2 0 10 50 950 950 950 950 100 101 OKAAAA PIHAAA VVVVxx
+1544 4956 0 0 4 4 44 544 1544 1544 1544 88 89 KHAAAA QIHAAA AAAAxx
+3898 4957 0 2 8 18 98 898 1898 3898 3898 196 197 YTAAAA RIHAAA HHHHxx
+8038 4958 0 2 8 18 38 38 38 3038 8038 76 77 EXAAAA SIHAAA OOOOxx
+1095 4959 1 3 5 15 95 95 1095 1095 1095 190 191 DQAAAA TIHAAA VVVVxx
+1748 4960 0 0 8 8 48 748 1748 1748 1748 96 97 GPAAAA UIHAAA AAAAxx
+9154 4961 0 2 4 14 54 154 1154 4154 9154 108 109 COAAAA VIHAAA HHHHxx
+2182 4962 0 2 2 2 82 182 182 2182 2182 164 165 YFAAAA WIHAAA OOOOxx
+6797 4963 1 1 7 17 97 797 797 1797 6797 194 195 LBAAAA XIHAAA VVVVxx
+9149 4964 1 1 9 9 49 149 1149 4149 9149 98 99 XNAAAA YIHAAA AAAAxx
+7351 4965 1 3 1 11 51 351 1351 2351 7351 102 103 TWAAAA ZIHAAA HHHHxx
+2820 4966 0 0 0 0 20 820 820 2820 2820 40 41 MEAAAA AJHAAA OOOOxx
+9696 4967 0 0 6 16 96 696 1696 4696 9696 192 193 YIAAAA BJHAAA VVVVxx
+253 4968 1 1 3 13 53 253 253 253 253 106 107 TJAAAA CJHAAA AAAAxx
+3600 4969 0 0 0 0 0 600 1600 3600 3600 0 1 MIAAAA DJHAAA HHHHxx
+3892 4970 0 0 2 12 92 892 1892 3892 3892 184 185 STAAAA EJHAAA OOOOxx
+231 4971 1 3 1 11 31 231 231 231 231 62 63 XIAAAA FJHAAA VVVVxx
+8331 4972 1 3 1 11 31 331 331 3331 8331 62 63 LIAAAA GJHAAA AAAAxx
+403 4973 1 3 3 3 3 403 403 403 403 6 7 NPAAAA HJHAAA HHHHxx
+8642 4974 0 2 2 2 42 642 642 3642 8642 84 85 KUAAAA IJHAAA OOOOxx
+3118 4975 0 2 8 18 18 118 1118 3118 3118 36 37 YPAAAA JJHAAA VVVVxx
+3835 4976 1 3 5 15 35 835 1835 3835 3835 70 71 NRAAAA KJHAAA AAAAxx
+1117 4977 1 1 7 17 17 117 1117 1117 1117 34 35 ZQAAAA LJHAAA HHHHxx
+7024 4978 0 0 4 4 24 24 1024 2024 7024 48 49 EKAAAA MJHAAA OOOOxx
+2636 4979 0 0 6 16 36 636 636 2636 2636 72 73 KXAAAA NJHAAA VVVVxx
+3778 4980 0 2 8 18 78 778 1778 3778 3778 156 157 IPAAAA OJHAAA AAAAxx
+2003 4981 1 3 3 3 3 3 3 2003 2003 6 7 BZAAAA PJHAAA HHHHxx
+5717 4982 1 1 7 17 17 717 1717 717 5717 34 35 XLAAAA QJHAAA OOOOxx
+4869 4983 1 1 9 9 69 869 869 4869 4869 138 139 HFAAAA RJHAAA VVVVxx
+8921 4984 1 1 1 1 21 921 921 3921 8921 42 43 DFAAAA SJHAAA AAAAxx
+888 4985 0 0 8 8 88 888 888 888 888 176 177 EIAAAA TJHAAA HHHHxx
+7599 4986 1 3 9 19 99 599 1599 2599 7599 198 199 HGAAAA UJHAAA OOOOxx
+8621 4987 1 1 1 1 21 621 621 3621 8621 42 43 PTAAAA VJHAAA VVVVxx
+811 4988 1 3 1 11 11 811 811 811 811 22 23 FFAAAA WJHAAA AAAAxx
+9147 4989 1 3 7 7 47 147 1147 4147 9147 94 95 VNAAAA XJHAAA HHHHxx
+1413 4990 1 1 3 13 13 413 1413 1413 1413 26 27 JCAAAA YJHAAA OOOOxx
+5232 4991 0 0 2 12 32 232 1232 232 5232 64 65 GTAAAA ZJHAAA VVVVxx
+5912 4992 0 0 2 12 12 912 1912 912 5912 24 25 KTAAAA AKHAAA AAAAxx
+3418 4993 0 2 8 18 18 418 1418 3418 3418 36 37 MBAAAA BKHAAA HHHHxx
+3912 4994 0 0 2 12 12 912 1912 3912 3912 24 25 MUAAAA CKHAAA OOOOxx
+9576 4995 0 0 6 16 76 576 1576 4576 9576 152 153 IEAAAA DKHAAA VVVVxx
+4225 4996 1 1 5 5 25 225 225 4225 4225 50 51 NGAAAA EKHAAA AAAAxx
+8222 4997 0 2 2 2 22 222 222 3222 8222 44 45 GEAAAA FKHAAA HHHHxx
+7013 4998 1 1 3 13 13 13 1013 2013 7013 26 27 TJAAAA GKHAAA OOOOxx
+7037 4999 1 1 7 17 37 37 1037 2037 7037 74 75 RKAAAA HKHAAA VVVVxx
+1205 5000 1 1 5 5 5 205 1205 1205 1205 10 11 JUAAAA IKHAAA AAAAxx
+8114 5001 0 2 4 14 14 114 114 3114 8114 28 29 CAAAAA JKHAAA HHHHxx
+6585 5002 1 1 5 5 85 585 585 1585 6585 170 171 HTAAAA KKHAAA OOOOxx
+155 5003 1 3 5 15 55 155 155 155 155 110 111 ZFAAAA LKHAAA VVVVxx
+2841 5004 1 1 1 1 41 841 841 2841 2841 82 83 HFAAAA MKHAAA AAAAxx
+1996 5005 0 0 6 16 96 996 1996 1996 1996 192 193 UYAAAA NKHAAA HHHHxx
+4948 5006 0 0 8 8 48 948 948 4948 4948 96 97 IIAAAA OKHAAA OOOOxx
+3304 5007 0 0 4 4 4 304 1304 3304 3304 8 9 CXAAAA PKHAAA VVVVxx
+5684 5008 0 0 4 4 84 684 1684 684 5684 168 169 QKAAAA QKHAAA AAAAxx
+6962 5009 0 2 2 2 62 962 962 1962 6962 124 125 UHAAAA RKHAAA HHHHxx
+8691 5010 1 3 1 11 91 691 691 3691 8691 182 183 HWAAAA SKHAAA OOOOxx
+8501 5011 1 1 1 1 1 501 501 3501 8501 2 3 ZOAAAA TKHAAA VVVVxx
+4783 5012 1 3 3 3 83 783 783 4783 4783 166 167 ZBAAAA UKHAAA AAAAxx
+3762 5013 0 2 2 2 62 762 1762 3762 3762 124 125 SOAAAA VKHAAA HHHHxx
+4534 5014 0 2 4 14 34 534 534 4534 4534 68 69 KSAAAA WKHAAA OOOOxx
+4999 5015 1 3 9 19 99 999 999 4999 4999 198 199 HKAAAA XKHAAA VVVVxx
+4618 5016 0 2 8 18 18 618 618 4618 4618 36 37 QVAAAA YKHAAA AAAAxx
+4220 5017 0 0 0 0 20 220 220 4220 4220 40 41 IGAAAA ZKHAAA HHHHxx
+3384 5018 0 0 4 4 84 384 1384 3384 3384 168 169 EAAAAA ALHAAA OOOOxx
+3036 5019 0 0 6 16 36 36 1036 3036 3036 72 73 UMAAAA BLHAAA VVVVxx
+545 5020 1 1 5 5 45 545 545 545 545 90 91 ZUAAAA CLHAAA AAAAxx
+9946 5021 0 2 6 6 46 946 1946 4946 9946 92 93 OSAAAA DLHAAA HHHHxx
+1985 5022 1 1 5 5 85 985 1985 1985 1985 170 171 JYAAAA ELHAAA OOOOxx
+2310 5023 0 2 0 10 10 310 310 2310 2310 20 21 WKAAAA FLHAAA VVVVxx
+6563 5024 1 3 3 3 63 563 563 1563 6563 126 127 LSAAAA GLHAAA AAAAxx
+4886 5025 0 2 6 6 86 886 886 4886 4886 172 173 YFAAAA HLHAAA HHHHxx
+9359 5026 1 3 9 19 59 359 1359 4359 9359 118 119 ZVAAAA ILHAAA OOOOxx
+400 5027 0 0 0 0 0 400 400 400 400 0 1 KPAAAA JLHAAA VVVVxx
+9742 5028 0 2 2 2 42 742 1742 4742 9742 84 85 SKAAAA KLHAAA AAAAxx
+6736 5029 0 0 6 16 36 736 736 1736 6736 72 73 CZAAAA LLHAAA HHHHxx
+8166 5030 0 2 6 6 66 166 166 3166 8166 132 133 CCAAAA MLHAAA OOOOxx
+861 5031 1 1 1 1 61 861 861 861 861 122 123 DHAAAA NLHAAA VVVVxx
+7492 5032 0 0 2 12 92 492 1492 2492 7492 184 185 ECAAAA OLHAAA AAAAxx
+1155 5033 1 3 5 15 55 155 1155 1155 1155 110 111 LSAAAA PLHAAA HHHHxx
+9769 5034 1 1 9 9 69 769 1769 4769 9769 138 139 TLAAAA QLHAAA OOOOxx
+6843 5035 1 3 3 3 43 843 843 1843 6843 86 87 FDAAAA RLHAAA VVVVxx
+5625 5036 1 1 5 5 25 625 1625 625 5625 50 51 JIAAAA SLHAAA AAAAxx
+1910 5037 0 2 0 10 10 910 1910 1910 1910 20 21 MVAAAA TLHAAA HHHHxx
+9796 5038 0 0 6 16 96 796 1796 4796 9796 192 193 UMAAAA ULHAAA OOOOxx
+6950 5039 0 2 0 10 50 950 950 1950 6950 100 101 IHAAAA VLHAAA VVVVxx
+3084 5040 0 0 4 4 84 84 1084 3084 3084 168 169 QOAAAA WLHAAA AAAAxx
+2959 5041 1 3 9 19 59 959 959 2959 2959 118 119 VJAAAA XLHAAA HHHHxx
+2093 5042 1 1 3 13 93 93 93 2093 2093 186 187 NCAAAA YLHAAA OOOOxx
+2738 5043 0 2 8 18 38 738 738 2738 2738 76 77 IBAAAA ZLHAAA VVVVxx
+6406 5044 0 2 6 6 6 406 406 1406 6406 12 13 KMAAAA AMHAAA AAAAxx
+9082 5045 0 2 2 2 82 82 1082 4082 9082 164 165 ILAAAA BMHAAA HHHHxx
+8568 5046 0 0 8 8 68 568 568 3568 8568 136 137 ORAAAA CMHAAA OOOOxx
+3566 5047 0 2 6 6 66 566 1566 3566 3566 132 133 EHAAAA DMHAAA VVVVxx
+3016 5048 0 0 6 16 16 16 1016 3016 3016 32 33 AMAAAA EMHAAA AAAAxx
+1207 5049 1 3 7 7 7 207 1207 1207 1207 14 15 LUAAAA FMHAAA HHHHxx
+4045 5050 1 1 5 5 45 45 45 4045 4045 90 91 PZAAAA GMHAAA OOOOxx
+4173 5051 1 1 3 13 73 173 173 4173 4173 146 147 NEAAAA HMHAAA VVVVxx
+3939 5052 1 3 9 19 39 939 1939 3939 3939 78 79 NVAAAA IMHAAA AAAAxx
+9683 5053 1 3 3 3 83 683 1683 4683 9683 166 167 LIAAAA JMHAAA HHHHxx
+1684 5054 0 0 4 4 84 684 1684 1684 1684 168 169 UMAAAA KMHAAA OOOOxx
+9271 5055 1 3 1 11 71 271 1271 4271 9271 142 143 PSAAAA LMHAAA VVVVxx
+9317 5056 1 1 7 17 17 317 1317 4317 9317 34 35 JUAAAA MMHAAA AAAAxx
+5793 5057 1 1 3 13 93 793 1793 793 5793 186 187 VOAAAA NMHAAA HHHHxx
+352 5058 0 0 2 12 52 352 352 352 352 104 105 ONAAAA OMHAAA OOOOxx
+7328 5059 0 0 8 8 28 328 1328 2328 7328 56 57 WVAAAA PMHAAA VVVVxx
+4582 5060 0 2 2 2 82 582 582 4582 4582 164 165 GUAAAA QMHAAA AAAAxx
+7413 5061 1 1 3 13 13 413 1413 2413 7413 26 27 DZAAAA RMHAAA HHHHxx
+6772 5062 0 0 2 12 72 772 772 1772 6772 144 145 MAAAAA SMHAAA OOOOxx
+4973 5063 1 1 3 13 73 973 973 4973 4973 146 147 HJAAAA TMHAAA VVVVxx
+7480 5064 0 0 0 0 80 480 1480 2480 7480 160 161 SBAAAA UMHAAA AAAAxx
+5555 5065 1 3 5 15 55 555 1555 555 5555 110 111 RFAAAA VMHAAA HHHHxx
+4227 5066 1 3 7 7 27 227 227 4227 4227 54 55 PGAAAA WMHAAA OOOOxx
+4153 5067 1 1 3 13 53 153 153 4153 4153 106 107 TDAAAA XMHAAA VVVVxx
+4601 5068 1 1 1 1 1 601 601 4601 4601 2 3 ZUAAAA YMHAAA AAAAxx
+3782 5069 0 2 2 2 82 782 1782 3782 3782 164 165 MPAAAA ZMHAAA HHHHxx
+3872 5070 0 0 2 12 72 872 1872 3872 3872 144 145 YSAAAA ANHAAA OOOOxx
+893 5071 1 1 3 13 93 893 893 893 893 186 187 JIAAAA BNHAAA VVVVxx
+2430 5072 0 2 0 10 30 430 430 2430 2430 60 61 MPAAAA CNHAAA AAAAxx
+2591 5073 1 3 1 11 91 591 591 2591 2591 182 183 RVAAAA DNHAAA HHHHxx
+264 5074 0 0 4 4 64 264 264 264 264 128 129 EKAAAA ENHAAA OOOOxx
+6238 5075 0 2 8 18 38 238 238 1238 6238 76 77 YFAAAA FNHAAA VVVVxx
+633 5076 1 1 3 13 33 633 633 633 633 66 67 JYAAAA GNHAAA AAAAxx
+1029 5077 1 1 9 9 29 29 1029 1029 1029 58 59 PNAAAA HNHAAA HHHHxx
+5934 5078 0 2 4 14 34 934 1934 934 5934 68 69 GUAAAA INHAAA OOOOxx
+8694 5079 0 2 4 14 94 694 694 3694 8694 188 189 KWAAAA JNHAAA VVVVxx
+7401 5080 1 1 1 1 1 401 1401 2401 7401 2 3 RYAAAA KNHAAA AAAAxx
+1165 5081 1 1 5 5 65 165 1165 1165 1165 130 131 VSAAAA LNHAAA HHHHxx
+9438 5082 0 2 8 18 38 438 1438 4438 9438 76 77 AZAAAA MNHAAA OOOOxx
+4790 5083 0 2 0 10 90 790 790 4790 4790 180 181 GCAAAA NNHAAA VVVVxx
+4531 5084 1 3 1 11 31 531 531 4531 4531 62 63 HSAAAA ONHAAA AAAAxx
+6099 5085 1 3 9 19 99 99 99 1099 6099 198 199 PAAAAA PNHAAA HHHHxx
+8236 5086 0 0 6 16 36 236 236 3236 8236 72 73 UEAAAA QNHAAA OOOOxx
+8551 5087 1 3 1 11 51 551 551 3551 8551 102 103 XQAAAA RNHAAA VVVVxx
+3128 5088 0 0 8 8 28 128 1128 3128 3128 56 57 IQAAAA SNHAAA AAAAxx
+3504 5089 0 0 4 4 4 504 1504 3504 3504 8 9 UEAAAA TNHAAA HHHHxx
+9071 5090 1 3 1 11 71 71 1071 4071 9071 142 143 XKAAAA UNHAAA OOOOxx
+5930 5091 0 2 0 10 30 930 1930 930 5930 60 61 CUAAAA VNHAAA VVVVxx
+6825 5092 1 1 5 5 25 825 825 1825 6825 50 51 NCAAAA WNHAAA AAAAxx
+2218 5093 0 2 8 18 18 218 218 2218 2218 36 37 IHAAAA XNHAAA HHHHxx
+3604 5094 0 0 4 4 4 604 1604 3604 3604 8 9 QIAAAA YNHAAA OOOOxx
+5761 5095 1 1 1 1 61 761 1761 761 5761 122 123 PNAAAA ZNHAAA VVVVxx
+5414 5096 0 2 4 14 14 414 1414 414 5414 28 29 GAAAAA AOHAAA AAAAxx
+5892 5097 0 0 2 12 92 892 1892 892 5892 184 185 QSAAAA BOHAAA HHHHxx
+4080 5098 0 0 0 0 80 80 80 4080 4080 160 161 YAAAAA COHAAA OOOOxx
+8018 5099 0 2 8 18 18 18 18 3018 8018 36 37 KWAAAA DOHAAA VVVVxx
+1757 5100 1 1 7 17 57 757 1757 1757 1757 114 115 PPAAAA EOHAAA AAAAxx
+5854 5101 0 2 4 14 54 854 1854 854 5854 108 109 ERAAAA FOHAAA HHHHxx
+1335 5102 1 3 5 15 35 335 1335 1335 1335 70 71 JZAAAA GOHAAA OOOOxx
+3811 5103 1 3 1 11 11 811 1811 3811 3811 22 23 PQAAAA HOHAAA VVVVxx
+9917 5104 1 1 7 17 17 917 1917 4917 9917 34 35 LRAAAA IOHAAA AAAAxx
+5947 5105 1 3 7 7 47 947 1947 947 5947 94 95 TUAAAA JOHAAA HHHHxx
+7263 5106 1 3 3 3 63 263 1263 2263 7263 126 127 JTAAAA KOHAAA OOOOxx
+1730 5107 0 2 0 10 30 730 1730 1730 1730 60 61 OOAAAA LOHAAA VVVVxx
+5747 5108 1 3 7 7 47 747 1747 747 5747 94 95 BNAAAA MOHAAA AAAAxx
+3876 5109 0 0 6 16 76 876 1876 3876 3876 152 153 CTAAAA NOHAAA HHHHxx
+2762 5110 0 2 2 2 62 762 762 2762 2762 124 125 GCAAAA OOHAAA OOOOxx
+7613 5111 1 1 3 13 13 613 1613 2613 7613 26 27 VGAAAA POHAAA VVVVxx
+152 5112 0 0 2 12 52 152 152 152 152 104 105 WFAAAA QOHAAA AAAAxx
+3941 5113 1 1 1 1 41 941 1941 3941 3941 82 83 PVAAAA ROHAAA HHHHxx
+5614 5114 0 2 4 14 14 614 1614 614 5614 28 29 YHAAAA SOHAAA OOOOxx
+9279 5115 1 3 9 19 79 279 1279 4279 9279 158 159 XSAAAA TOHAAA VVVVxx
+3048 5116 0 0 8 8 48 48 1048 3048 3048 96 97 GNAAAA UOHAAA AAAAxx
+6152 5117 0 0 2 12 52 152 152 1152 6152 104 105 QCAAAA VOHAAA HHHHxx
+5481 5118 1 1 1 1 81 481 1481 481 5481 162 163 VCAAAA WOHAAA OOOOxx
+4675 5119 1 3 5 15 75 675 675 4675 4675 150 151 VXAAAA XOHAAA VVVVxx
+3334 5120 0 2 4 14 34 334 1334 3334 3334 68 69 GYAAAA YOHAAA AAAAxx
+4691 5121 1 3 1 11 91 691 691 4691 4691 182 183 LYAAAA ZOHAAA HHHHxx
+803 5122 1 3 3 3 3 803 803 803 803 6 7 XEAAAA APHAAA OOOOxx
+5409 5123 1 1 9 9 9 409 1409 409 5409 18 19 BAAAAA BPHAAA VVVVxx
+1054 5124 0 2 4 14 54 54 1054 1054 1054 108 109 OOAAAA CPHAAA AAAAxx
+103 5125 1 3 3 3 3 103 103 103 103 6 7 ZDAAAA DPHAAA HHHHxx
+8565 5126 1 1 5 5 65 565 565 3565 8565 130 131 LRAAAA EPHAAA OOOOxx
+4666 5127 0 2 6 6 66 666 666 4666 4666 132 133 MXAAAA FPHAAA VVVVxx
+6634 5128 0 2 4 14 34 634 634 1634 6634 68 69 EVAAAA GPHAAA AAAAxx
+5538 5129 0 2 8 18 38 538 1538 538 5538 76 77 AFAAAA HPHAAA HHHHxx
+3789 5130 1 1 9 9 89 789 1789 3789 3789 178 179 TPAAAA IPHAAA OOOOxx
+4641 5131 1 1 1 1 41 641 641 4641 4641 82 83 NWAAAA JPHAAA VVVVxx
+2458 5132 0 2 8 18 58 458 458 2458 2458 116 117 OQAAAA KPHAAA AAAAxx
+5667 5133 1 3 7 7 67 667 1667 667 5667 134 135 ZJAAAA LPHAAA HHHHxx
+6524 5134 0 0 4 4 24 524 524 1524 6524 48 49 YQAAAA MPHAAA OOOOxx
+9179 5135 1 3 9 19 79 179 1179 4179 9179 158 159 BPAAAA NPHAAA VVVVxx
+6358 5136 0 2 8 18 58 358 358 1358 6358 116 117 OKAAAA OPHAAA AAAAxx
+6668 5137 0 0 8 8 68 668 668 1668 6668 136 137 MWAAAA PPHAAA HHHHxx
+6414 5138 0 2 4 14 14 414 414 1414 6414 28 29 SMAAAA QPHAAA OOOOxx
+2813 5139 1 1 3 13 13 813 813 2813 2813 26 27 FEAAAA RPHAAA VVVVxx
+8927 5140 1 3 7 7 27 927 927 3927 8927 54 55 JFAAAA SPHAAA AAAAxx
+8695 5141 1 3 5 15 95 695 695 3695 8695 190 191 LWAAAA TPHAAA HHHHxx
+363 5142 1 3 3 3 63 363 363 363 363 126 127 ZNAAAA UPHAAA OOOOxx
+9966 5143 0 2 6 6 66 966 1966 4966 9966 132 133 ITAAAA VPHAAA VVVVxx
+1323 5144 1 3 3 3 23 323 1323 1323 1323 46 47 XYAAAA WPHAAA AAAAxx
+8211 5145 1 3 1 11 11 211 211 3211 8211 22 23 VDAAAA XPHAAA HHHHxx
+4375 5146 1 3 5 15 75 375 375 4375 4375 150 151 HMAAAA YPHAAA OOOOxx
+3257 5147 1 1 7 17 57 257 1257 3257 3257 114 115 HVAAAA ZPHAAA VVVVxx
+6239 5148 1 3 9 19 39 239 239 1239 6239 78 79 ZFAAAA AQHAAA AAAAxx
+3602 5149 0 2 2 2 2 602 1602 3602 3602 4 5 OIAAAA BQHAAA HHHHxx
+9830 5150 0 2 0 10 30 830 1830 4830 9830 60 61 COAAAA CQHAAA OOOOxx
+7826 5151 0 2 6 6 26 826 1826 2826 7826 52 53 APAAAA DQHAAA VVVVxx
+2108 5152 0 0 8 8 8 108 108 2108 2108 16 17 CDAAAA EQHAAA AAAAxx
+7245 5153 1 1 5 5 45 245 1245 2245 7245 90 91 RSAAAA FQHAAA HHHHxx
+8330 5154 0 2 0 10 30 330 330 3330 8330 60 61 KIAAAA GQHAAA OOOOxx
+7441 5155 1 1 1 1 41 441 1441 2441 7441 82 83 FAAAAA HQHAAA VVVVxx
+9848 5156 0 0 8 8 48 848 1848 4848 9848 96 97 UOAAAA IQHAAA AAAAxx
+1226 5157 0 2 6 6 26 226 1226 1226 1226 52 53 EVAAAA JQHAAA HHHHxx
+414 5158 0 2 4 14 14 414 414 414 414 28 29 YPAAAA KQHAAA OOOOxx
+1273 5159 1 1 3 13 73 273 1273 1273 1273 146 147 ZWAAAA LQHAAA VVVVxx
+9866 5160 0 2 6 6 66 866 1866 4866 9866 132 133 MPAAAA MQHAAA AAAAxx
+4633 5161 1 1 3 13 33 633 633 4633 4633 66 67 FWAAAA NQHAAA HHHHxx
+8727 5162 1 3 7 7 27 727 727 3727 8727 54 55 RXAAAA OQHAAA OOOOxx
+5308 5163 0 0 8 8 8 308 1308 308 5308 16 17 EWAAAA PQHAAA VVVVxx
+1395 5164 1 3 5 15 95 395 1395 1395 1395 190 191 RBAAAA QQHAAA AAAAxx
+1825 5165 1 1 5 5 25 825 1825 1825 1825 50 51 FSAAAA RQHAAA HHHHxx
+7606 5166 0 2 6 6 6 606 1606 2606 7606 12 13 OGAAAA SQHAAA OOOOxx
+9390 5167 0 2 0 10 90 390 1390 4390 9390 180 181 EXAAAA TQHAAA VVVVxx
+2376 5168 0 0 6 16 76 376 376 2376 2376 152 153 KNAAAA UQHAAA AAAAxx
+2377 5169 1 1 7 17 77 377 377 2377 2377 154 155 LNAAAA VQHAAA HHHHxx
+5346 5170 0 2 6 6 46 346 1346 346 5346 92 93 QXAAAA WQHAAA OOOOxx
+4140 5171 0 0 0 0 40 140 140 4140 4140 80 81 GDAAAA XQHAAA VVVVxx
+6032 5172 0 0 2 12 32 32 32 1032 6032 64 65 AYAAAA YQHAAA AAAAxx
+9453 5173 1 1 3 13 53 453 1453 4453 9453 106 107 PZAAAA ZQHAAA HHHHxx
+9297 5174 1 1 7 17 97 297 1297 4297 9297 194 195 PTAAAA ARHAAA OOOOxx
+6455 5175 1 3 5 15 55 455 455 1455 6455 110 111 HOAAAA BRHAAA VVVVxx
+4458 5176 0 2 8 18 58 458 458 4458 4458 116 117 MPAAAA CRHAAA AAAAxx
+9516 5177 0 0 6 16 16 516 1516 4516 9516 32 33 ACAAAA DRHAAA HHHHxx
+6211 5178 1 3 1 11 11 211 211 1211 6211 22 23 XEAAAA ERHAAA OOOOxx
+526 5179 0 2 6 6 26 526 526 526 526 52 53 GUAAAA FRHAAA VVVVxx
+3570 5180 0 2 0 10 70 570 1570 3570 3570 140 141 IHAAAA GRHAAA AAAAxx
+4885 5181 1 1 5 5 85 885 885 4885 4885 170 171 XFAAAA HRHAAA HHHHxx
+6390 5182 0 2 0 10 90 390 390 1390 6390 180 181 ULAAAA IRHAAA OOOOxx
+1606 5183 0 2 6 6 6 606 1606 1606 1606 12 13 UJAAAA JRHAAA VVVVxx
+7850 5184 0 2 0 10 50 850 1850 2850 7850 100 101 YPAAAA KRHAAA AAAAxx
+3315 5185 1 3 5 15 15 315 1315 3315 3315 30 31 NXAAAA LRHAAA HHHHxx
+8322 5186 0 2 2 2 22 322 322 3322 8322 44 45 CIAAAA MRHAAA OOOOxx
+3703 5187 1 3 3 3 3 703 1703 3703 3703 6 7 LMAAAA NRHAAA VVVVxx
+9489 5188 1 1 9 9 89 489 1489 4489 9489 178 179 ZAAAAA ORHAAA AAAAxx
+6104 5189 0 0 4 4 4 104 104 1104 6104 8 9 UAAAAA PRHAAA HHHHxx
+3067 5190 1 3 7 7 67 67 1067 3067 3067 134 135 ZNAAAA QRHAAA OOOOxx
+2521 5191 1 1 1 1 21 521 521 2521 2521 42 43 ZSAAAA RRHAAA VVVVxx
+2581 5192 1 1 1 1 81 581 581 2581 2581 162 163 HVAAAA SRHAAA AAAAxx
+595 5193 1 3 5 15 95 595 595 595 595 190 191 XWAAAA TRHAAA HHHHxx
+8291 5194 1 3 1 11 91 291 291 3291 8291 182 183 XGAAAA URHAAA OOOOxx
+1727 5195 1 3 7 7 27 727 1727 1727 1727 54 55 LOAAAA VRHAAA VVVVxx
+6847 5196 1 3 7 7 47 847 847 1847 6847 94 95 JDAAAA WRHAAA AAAAxx
+7494 5197 0 2 4 14 94 494 1494 2494 7494 188 189 GCAAAA XRHAAA HHHHxx
+7093 5198 1 1 3 13 93 93 1093 2093 7093 186 187 VMAAAA YRHAAA OOOOxx
+7357 5199 1 1 7 17 57 357 1357 2357 7357 114 115 ZWAAAA ZRHAAA VVVVxx
+620 5200 0 0 0 0 20 620 620 620 620 40 41 WXAAAA ASHAAA AAAAxx
+2460 5201 0 0 0 0 60 460 460 2460 2460 120 121 QQAAAA BSHAAA HHHHxx
+1598 5202 0 2 8 18 98 598 1598 1598 1598 196 197 MJAAAA CSHAAA OOOOxx
+4112 5203 0 0 2 12 12 112 112 4112 4112 24 25 ECAAAA DSHAAA VVVVxx
+2956 5204 0 0 6 16 56 956 956 2956 2956 112 113 SJAAAA ESHAAA AAAAxx
+3193 5205 1 1 3 13 93 193 1193 3193 3193 186 187 VSAAAA FSHAAA HHHHxx
+6356 5206 0 0 6 16 56 356 356 1356 6356 112 113 MKAAAA GSHAAA OOOOxx
+730 5207 0 2 0 10 30 730 730 730 730 60 61 CCAAAA HSHAAA VVVVxx
+8826 5208 0 2 6 6 26 826 826 3826 8826 52 53 MBAAAA ISHAAA AAAAxx
+9036 5209 0 0 6 16 36 36 1036 4036 9036 72 73 OJAAAA JSHAAA HHHHxx
+2085 5210 1 1 5 5 85 85 85 2085 2085 170 171 FCAAAA KSHAAA OOOOxx
+9007 5211 1 3 7 7 7 7 1007 4007 9007 14 15 LIAAAA LSHAAA VVVVxx
+6047 5212 1 3 7 7 47 47 47 1047 6047 94 95 PYAAAA MSHAAA AAAAxx
+3953 5213 1 1 3 13 53 953 1953 3953 3953 106 107 BWAAAA NSHAAA HHHHxx
+1214 5214 0 2 4 14 14 214 1214 1214 1214 28 29 SUAAAA OSHAAA OOOOxx
+4814 5215 0 2 4 14 14 814 814 4814 4814 28 29 EDAAAA PSHAAA VVVVxx
+5738 5216 0 2 8 18 38 738 1738 738 5738 76 77 SMAAAA QSHAAA AAAAxx
+7176 5217 0 0 6 16 76 176 1176 2176 7176 152 153 AQAAAA RSHAAA HHHHxx
+3609 5218 1 1 9 9 9 609 1609 3609 3609 18 19 VIAAAA SSHAAA OOOOxx
+592 5219 0 0 2 12 92 592 592 592 592 184 185 UWAAAA TSHAAA VVVVxx
+9391 5220 1 3 1 11 91 391 1391 4391 9391 182 183 FXAAAA USHAAA AAAAxx
+5345 5221 1 1 5 5 45 345 1345 345 5345 90 91 PXAAAA VSHAAA HHHHxx
+1171 5222 1 3 1 11 71 171 1171 1171 1171 142 143 BTAAAA WSHAAA OOOOxx
+7238 5223 0 2 8 18 38 238 1238 2238 7238 76 77 KSAAAA XSHAAA VVVVxx
+7561 5224 1 1 1 1 61 561 1561 2561 7561 122 123 VEAAAA YSHAAA AAAAxx
+5876 5225 0 0 6 16 76 876 1876 876 5876 152 153 ASAAAA ZSHAAA HHHHxx
+6611 5226 1 3 1 11 11 611 611 1611 6611 22 23 HUAAAA ATHAAA OOOOxx
+7300 5227 0 0 0 0 0 300 1300 2300 7300 0 1 UUAAAA BTHAAA VVVVxx
+1506 5228 0 2 6 6 6 506 1506 1506 1506 12 13 YFAAAA CTHAAA AAAAxx
+1153 5229 1 1 3 13 53 153 1153 1153 1153 106 107 JSAAAA DTHAAA HHHHxx
+3831 5230 1 3 1 11 31 831 1831 3831 3831 62 63 JRAAAA ETHAAA OOOOxx
+9255 5231 1 3 5 15 55 255 1255 4255 9255 110 111 ZRAAAA FTHAAA VVVVxx
+1841 5232 1 1 1 1 41 841 1841 1841 1841 82 83 VSAAAA GTHAAA AAAAxx
+5075 5233 1 3 5 15 75 75 1075 75 5075 150 151 FNAAAA HTHAAA HHHHxx
+101 5234 1 1 1 1 1 101 101 101 101 2 3 XDAAAA ITHAAA OOOOxx
+2627 5235 1 3 7 7 27 627 627 2627 2627 54 55 BXAAAA JTHAAA VVVVxx
+7078 5236 0 2 8 18 78 78 1078 2078 7078 156 157 GMAAAA KTHAAA AAAAxx
+2850 5237 0 2 0 10 50 850 850 2850 2850 100 101 QFAAAA LTHAAA HHHHxx
+8703 5238 1 3 3 3 3 703 703 3703 8703 6 7 TWAAAA MTHAAA OOOOxx
+4101 5239 1 1 1 1 1 101 101 4101 4101 2 3 TBAAAA NTHAAA VVVVxx
+318 5240 0 2 8 18 18 318 318 318 318 36 37 GMAAAA OTHAAA AAAAxx
+6452 5241 0 0 2 12 52 452 452 1452 6452 104 105 EOAAAA PTHAAA HHHHxx
+5558 5242 0 2 8 18 58 558 1558 558 5558 116 117 UFAAAA QTHAAA OOOOxx
+3127 5243 1 3 7 7 27 127 1127 3127 3127 54 55 HQAAAA RTHAAA VVVVxx
+535 5244 1 3 5 15 35 535 535 535 535 70 71 PUAAAA STHAAA AAAAxx
+270 5245 0 2 0 10 70 270 270 270 270 140 141 KKAAAA TTHAAA HHHHxx
+4038 5246 0 2 8 18 38 38 38 4038 4038 76 77 IZAAAA UTHAAA OOOOxx
+3404 5247 0 0 4 4 4 404 1404 3404 3404 8 9 YAAAAA VTHAAA VVVVxx
+2374 5248 0 2 4 14 74 374 374 2374 2374 148 149 INAAAA WTHAAA AAAAxx
+6446 5249 0 2 6 6 46 446 446 1446 6446 92 93 YNAAAA XTHAAA HHHHxx
+7758 5250 0 2 8 18 58 758 1758 2758 7758 116 117 KMAAAA YTHAAA OOOOxx
+356 5251 0 0 6 16 56 356 356 356 356 112 113 SNAAAA ZTHAAA VVVVxx
+9197 5252 1 1 7 17 97 197 1197 4197 9197 194 195 TPAAAA AUHAAA AAAAxx
+9765 5253 1 1 5 5 65 765 1765 4765 9765 130 131 PLAAAA BUHAAA HHHHxx
+4974 5254 0 2 4 14 74 974 974 4974 4974 148 149 IJAAAA CUHAAA OOOOxx
+442 5255 0 2 2 2 42 442 442 442 442 84 85 ARAAAA DUHAAA VVVVxx
+4349 5256 1 1 9 9 49 349 349 4349 4349 98 99 HLAAAA EUHAAA AAAAxx
+6119 5257 1 3 9 19 19 119 119 1119 6119 38 39 JBAAAA FUHAAA HHHHxx
+7574 5258 0 2 4 14 74 574 1574 2574 7574 148 149 IFAAAA GUHAAA OOOOxx
+4445 5259 1 1 5 5 45 445 445 4445 4445 90 91 ZOAAAA HUHAAA VVVVxx
+940 5260 0 0 0 0 40 940 940 940 940 80 81 EKAAAA IUHAAA AAAAxx
+1875 5261 1 3 5 15 75 875 1875 1875 1875 150 151 DUAAAA JUHAAA HHHHxx
+5951 5262 1 3 1 11 51 951 1951 951 5951 102 103 XUAAAA KUHAAA OOOOxx
+9132 5263 0 0 2 12 32 132 1132 4132 9132 64 65 GNAAAA LUHAAA VVVVxx
+6913 5264 1 1 3 13 13 913 913 1913 6913 26 27 XFAAAA MUHAAA AAAAxx
+3308 5265 0 0 8 8 8 308 1308 3308 3308 16 17 GXAAAA NUHAAA HHHHxx
+7553 5266 1 1 3 13 53 553 1553 2553 7553 106 107 NEAAAA OUHAAA OOOOxx
+2138 5267 0 2 8 18 38 138 138 2138 2138 76 77 GEAAAA PUHAAA VVVVxx
+6252 5268 0 0 2 12 52 252 252 1252 6252 104 105 MGAAAA QUHAAA AAAAxx
+2171 5269 1 3 1 11 71 171 171 2171 2171 142 143 NFAAAA RUHAAA HHHHxx
+4159 5270 1 3 9 19 59 159 159 4159 4159 118 119 ZDAAAA SUHAAA OOOOxx
+2401 5271 1 1 1 1 1 401 401 2401 2401 2 3 JOAAAA TUHAAA VVVVxx
+6553 5272 1 1 3 13 53 553 553 1553 6553 106 107 BSAAAA UUHAAA AAAAxx
+5217 5273 1 1 7 17 17 217 1217 217 5217 34 35 RSAAAA VUHAAA HHHHxx
+1405 5274 1 1 5 5 5 405 1405 1405 1405 10 11 BCAAAA WUHAAA OOOOxx
+1494 5275 0 2 4 14 94 494 1494 1494 1494 188 189 MFAAAA XUHAAA VVVVxx
+5553 5276 1 1 3 13 53 553 1553 553 5553 106 107 PFAAAA YUHAAA AAAAxx
+8296 5277 0 0 6 16 96 296 296 3296 8296 192 193 CHAAAA ZUHAAA HHHHxx
+6565 5278 1 1 5 5 65 565 565 1565 6565 130 131 NSAAAA AVHAAA OOOOxx
+817 5279 1 1 7 17 17 817 817 817 817 34 35 LFAAAA BVHAAA VVVVxx
+6947 5280 1 3 7 7 47 947 947 1947 6947 94 95 FHAAAA CVHAAA AAAAxx
+4184 5281 0 0 4 4 84 184 184 4184 4184 168 169 YEAAAA DVHAAA HHHHxx
+6577 5282 1 1 7 17 77 577 577 1577 6577 154 155 ZSAAAA EVHAAA OOOOxx
+6424 5283 0 0 4 4 24 424 424 1424 6424 48 49 CNAAAA FVHAAA VVVVxx
+2482 5284 0 2 2 2 82 482 482 2482 2482 164 165 MRAAAA GVHAAA AAAAxx
+6874 5285 0 2 4 14 74 874 874 1874 6874 148 149 KEAAAA HVHAAA HHHHxx
+7601 5286 1 1 1 1 1 601 1601 2601 7601 2 3 JGAAAA IVHAAA OOOOxx
+4552 5287 0 0 2 12 52 552 552 4552 4552 104 105 CTAAAA JVHAAA VVVVxx
+8406 5288 0 2 6 6 6 406 406 3406 8406 12 13 ILAAAA KVHAAA AAAAxx
+2924 5289 0 0 4 4 24 924 924 2924 2924 48 49 MIAAAA LVHAAA HHHHxx
+8255 5290 1 3 5 15 55 255 255 3255 8255 110 111 NFAAAA MVHAAA OOOOxx
+4920 5291 0 0 0 0 20 920 920 4920 4920 40 41 GHAAAA NVHAAA VVVVxx
+228 5292 0 0 8 8 28 228 228 228 228 56 57 UIAAAA OVHAAA AAAAxx
+9431 5293 1 3 1 11 31 431 1431 4431 9431 62 63 TYAAAA PVHAAA HHHHxx
+4021 5294 1 1 1 1 21 21 21 4021 4021 42 43 RYAAAA QVHAAA OOOOxx
+2966 5295 0 2 6 6 66 966 966 2966 2966 132 133 CKAAAA RVHAAA VVVVxx
+2862 5296 0 2 2 2 62 862 862 2862 2862 124 125 CGAAAA SVHAAA AAAAxx
+4303 5297 1 3 3 3 3 303 303 4303 4303 6 7 NJAAAA TVHAAA HHHHxx
+9643 5298 1 3 3 3 43 643 1643 4643 9643 86 87 XGAAAA UVHAAA OOOOxx
+3008 5299 0 0 8 8 8 8 1008 3008 3008 16 17 SLAAAA VVHAAA VVVVxx
+7476 5300 0 0 6 16 76 476 1476 2476 7476 152 153 OBAAAA WVHAAA AAAAxx
+3686 5301 0 2 6 6 86 686 1686 3686 3686 172 173 ULAAAA XVHAAA HHHHxx
+9051 5302 1 3 1 11 51 51 1051 4051 9051 102 103 DKAAAA YVHAAA OOOOxx
+6592 5303 0 0 2 12 92 592 592 1592 6592 184 185 OTAAAA ZVHAAA VVVVxx
+924 5304 0 0 4 4 24 924 924 924 924 48 49 OJAAAA AWHAAA AAAAxx
+4406 5305 0 2 6 6 6 406 406 4406 4406 12 13 MNAAAA BWHAAA HHHHxx
+5233 5306 1 1 3 13 33 233 1233 233 5233 66 67 HTAAAA CWHAAA OOOOxx
+8881 5307 1 1 1 1 81 881 881 3881 8881 162 163 PDAAAA DWHAAA VVVVxx
+2212 5308 0 0 2 12 12 212 212 2212 2212 24 25 CHAAAA EWHAAA AAAAxx
+5804 5309 0 0 4 4 4 804 1804 804 5804 8 9 GPAAAA FWHAAA HHHHxx
+2990 5310 0 2 0 10 90 990 990 2990 2990 180 181 ALAAAA GWHAAA OOOOxx
+4069 5311 1 1 9 9 69 69 69 4069 4069 138 139 NAAAAA HWHAAA VVVVxx
+5380 5312 0 0 0 0 80 380 1380 380 5380 160 161 YYAAAA IWHAAA AAAAxx
+5016 5313 0 0 6 16 16 16 1016 16 5016 32 33 YKAAAA JWHAAA HHHHxx
+5056 5314 0 0 6 16 56 56 1056 56 5056 112 113 MMAAAA KWHAAA OOOOxx
+3732 5315 0 0 2 12 32 732 1732 3732 3732 64 65 ONAAAA LWHAAA VVVVxx
+5527 5316 1 3 7 7 27 527 1527 527 5527 54 55 PEAAAA MWHAAA AAAAxx
+1151 5317 1 3 1 11 51 151 1151 1151 1151 102 103 HSAAAA NWHAAA HHHHxx
+7900 5318 0 0 0 0 0 900 1900 2900 7900 0 1 WRAAAA OWHAAA OOOOxx
+1660 5319 0 0 0 0 60 660 1660 1660 1660 120 121 WLAAAA PWHAAA VVVVxx
+8064 5320 0 0 4 4 64 64 64 3064 8064 128 129 EYAAAA QWHAAA AAAAxx
+8240 5321 0 0 0 0 40 240 240 3240 8240 80 81 YEAAAA RWHAAA HHHHxx
+413 5322 1 1 3 13 13 413 413 413 413 26 27 XPAAAA SWHAAA OOOOxx
+8311 5323 1 3 1 11 11 311 311 3311 8311 22 23 RHAAAA TWHAAA VVVVxx
+1065 5324 1 1 5 5 65 65 1065 1065 1065 130 131 ZOAAAA UWHAAA AAAAxx
+2741 5325 1 1 1 1 41 741 741 2741 2741 82 83 LBAAAA VWHAAA HHHHxx
+5306 5326 0 2 6 6 6 306 1306 306 5306 12 13 CWAAAA WWHAAA OOOOxx
+5464 5327 0 0 4 4 64 464 1464 464 5464 128 129 ECAAAA XWHAAA VVVVxx
+4237 5328 1 1 7 17 37 237 237 4237 4237 74 75 ZGAAAA YWHAAA AAAAxx
+3822 5329 0 2 2 2 22 822 1822 3822 3822 44 45 ARAAAA ZWHAAA HHHHxx
+2548 5330 0 0 8 8 48 548 548 2548 2548 96 97 AUAAAA AXHAAA OOOOxx
+2688 5331 0 0 8 8 88 688 688 2688 2688 176 177 KZAAAA BXHAAA VVVVxx
+8061 5332 1 1 1 1 61 61 61 3061 8061 122 123 BYAAAA CXHAAA AAAAxx
+9340 5333 0 0 0 0 40 340 1340 4340 9340 80 81 GVAAAA DXHAAA HHHHxx
+4031 5334 1 3 1 11 31 31 31 4031 4031 62 63 BZAAAA EXHAAA OOOOxx
+2635 5335 1 3 5 15 35 635 635 2635 2635 70 71 JXAAAA FXHAAA VVVVxx
+809 5336 1 1 9 9 9 809 809 809 809 18 19 DFAAAA GXHAAA AAAAxx
+3209 5337 1 1 9 9 9 209 1209 3209 3209 18 19 LTAAAA HXHAAA HHHHxx
+3825 5338 1 1 5 5 25 825 1825 3825 3825 50 51 DRAAAA IXHAAA OOOOxx
+1448 5339 0 0 8 8 48 448 1448 1448 1448 96 97 SDAAAA JXHAAA VVVVxx
+9077 5340 1 1 7 17 77 77 1077 4077 9077 154 155 DLAAAA KXHAAA AAAAxx
+3730 5341 0 2 0 10 30 730 1730 3730 3730 60 61 MNAAAA LXHAAA HHHHxx
+9596 5342 0 0 6 16 96 596 1596 4596 9596 192 193 CFAAAA MXHAAA OOOOxx
+3563 5343 1 3 3 3 63 563 1563 3563 3563 126 127 BHAAAA NXHAAA VVVVxx
+4116 5344 0 0 6 16 16 116 116 4116 4116 32 33 ICAAAA OXHAAA AAAAxx
+4825 5345 1 1 5 5 25 825 825 4825 4825 50 51 PDAAAA PXHAAA HHHHxx
+8376 5346 0 0 6 16 76 376 376 3376 8376 152 153 EKAAAA QXHAAA OOOOxx
+3917 5347 1 1 7 17 17 917 1917 3917 3917 34 35 RUAAAA RXHAAA VVVVxx
+4407 5348 1 3 7 7 7 407 407 4407 4407 14 15 NNAAAA SXHAAA AAAAxx
+8202 5349 0 2 2 2 2 202 202 3202 8202 4 5 MDAAAA TXHAAA HHHHxx
+7675 5350 1 3 5 15 75 675 1675 2675 7675 150 151 FJAAAA UXHAAA OOOOxx
+4104 5351 0 0 4 4 4 104 104 4104 4104 8 9 WBAAAA VXHAAA VVVVxx
+9225 5352 1 1 5 5 25 225 1225 4225 9225 50 51 VQAAAA WXHAAA AAAAxx
+2834 5353 0 2 4 14 34 834 834 2834 2834 68 69 AFAAAA XXHAAA HHHHxx
+1227 5354 1 3 7 7 27 227 1227 1227 1227 54 55 FVAAAA YXHAAA OOOOxx
+3383 5355 1 3 3 3 83 383 1383 3383 3383 166 167 DAAAAA ZXHAAA VVVVxx
+67 5356 1 3 7 7 67 67 67 67 67 134 135 PCAAAA AYHAAA AAAAxx
+1751 5357 1 3 1 11 51 751 1751 1751 1751 102 103 JPAAAA BYHAAA HHHHxx
+8054 5358 0 2 4 14 54 54 54 3054 8054 108 109 UXAAAA CYHAAA OOOOxx
+8571 5359 1 3 1 11 71 571 571 3571 8571 142 143 RRAAAA DYHAAA VVVVxx
+2466 5360 0 2 6 6 66 466 466 2466 2466 132 133 WQAAAA EYHAAA AAAAxx
+9405 5361 1 1 5 5 5 405 1405 4405 9405 10 11 TXAAAA FYHAAA HHHHxx
+6883 5362 1 3 3 3 83 883 883 1883 6883 166 167 TEAAAA GYHAAA OOOOxx
+4301 5363 1 1 1 1 1 301 301 4301 4301 2 3 LJAAAA HYHAAA VVVVxx
+3705 5364 1 1 5 5 5 705 1705 3705 3705 10 11 NMAAAA IYHAAA AAAAxx
+5420 5365 0 0 0 0 20 420 1420 420 5420 40 41 MAAAAA JYHAAA HHHHxx
+3692 5366 0 0 2 12 92 692 1692 3692 3692 184 185 AMAAAA KYHAAA OOOOxx
+6851 5367 1 3 1 11 51 851 851 1851 6851 102 103 NDAAAA LYHAAA VVVVxx
+9363 5368 1 3 3 3 63 363 1363 4363 9363 126 127 DWAAAA MYHAAA AAAAxx
+2269 5369 1 1 9 9 69 269 269 2269 2269 138 139 HJAAAA NYHAAA HHHHxx
+4918 5370 0 2 8 18 18 918 918 4918 4918 36 37 EHAAAA OYHAAA OOOOxx
+4297 5371 1 1 7 17 97 297 297 4297 4297 194 195 HJAAAA PYHAAA VVVVxx
+1836 5372 0 0 6 16 36 836 1836 1836 1836 72 73 QSAAAA QYHAAA AAAAxx
+237 5373 1 1 7 17 37 237 237 237 237 74 75 DJAAAA RYHAAA HHHHxx
+6131 5374 1 3 1 11 31 131 131 1131 6131 62 63 VBAAAA SYHAAA OOOOxx
+3174 5375 0 2 4 14 74 174 1174 3174 3174 148 149 CSAAAA TYHAAA VVVVxx
+9987 5376 1 3 7 7 87 987 1987 4987 9987 174 175 DUAAAA UYHAAA AAAAxx
+3630 5377 0 2 0 10 30 630 1630 3630 3630 60 61 QJAAAA VYHAAA HHHHxx
+2899 5378 1 3 9 19 99 899 899 2899 2899 198 199 NHAAAA WYHAAA OOOOxx
+4079 5379 1 3 9 19 79 79 79 4079 4079 158 159 XAAAAA XYHAAA VVVVxx
+5049 5380 1 1 9 9 49 49 1049 49 5049 98 99 FMAAAA YYHAAA AAAAxx
+2963 5381 1 3 3 3 63 963 963 2963 2963 126 127 ZJAAAA ZYHAAA HHHHxx
+3962 5382 0 2 2 2 62 962 1962 3962 3962 124 125 KWAAAA AZHAAA OOOOxx
+7921 5383 1 1 1 1 21 921 1921 2921 7921 42 43 RSAAAA BZHAAA VVVVxx
+3967 5384 1 3 7 7 67 967 1967 3967 3967 134 135 PWAAAA CZHAAA AAAAxx
+2752 5385 0 0 2 12 52 752 752 2752 2752 104 105 WBAAAA DZHAAA HHHHxx
+7944 5386 0 0 4 4 44 944 1944 2944 7944 88 89 OTAAAA EZHAAA OOOOxx
+2205 5387 1 1 5 5 5 205 205 2205 2205 10 11 VGAAAA FZHAAA VVVVxx
+5035 5388 1 3 5 15 35 35 1035 35 5035 70 71 RLAAAA GZHAAA AAAAxx
+1425 5389 1 1 5 5 25 425 1425 1425 1425 50 51 VCAAAA HZHAAA HHHHxx
+832 5390 0 0 2 12 32 832 832 832 832 64 65 AGAAAA IZHAAA OOOOxx
+1447 5391 1 3 7 7 47 447 1447 1447 1447 94 95 RDAAAA JZHAAA VVVVxx
+6108 5392 0 0 8 8 8 108 108 1108 6108 16 17 YAAAAA KZHAAA AAAAxx
+4936 5393 0 0 6 16 36 936 936 4936 4936 72 73 WHAAAA LZHAAA HHHHxx
+7704 5394 0 0 4 4 4 704 1704 2704 7704 8 9 IKAAAA MZHAAA OOOOxx
+142 5395 0 2 2 2 42 142 142 142 142 84 85 MFAAAA NZHAAA VVVVxx
+4272 5396 0 0 2 12 72 272 272 4272 4272 144 145 IIAAAA OZHAAA AAAAxx
+7667 5397 1 3 7 7 67 667 1667 2667 7667 134 135 XIAAAA PZHAAA HHHHxx
+366 5398 0 2 6 6 66 366 366 366 366 132 133 COAAAA QZHAAA OOOOxx
+8866 5399 0 2 6 6 66 866 866 3866 8866 132 133 ADAAAA RZHAAA VVVVxx
+7712 5400 0 0 2 12 12 712 1712 2712 7712 24 25 QKAAAA SZHAAA AAAAxx
+3880 5401 0 0 0 0 80 880 1880 3880 3880 160 161 GTAAAA TZHAAA HHHHxx
+4631 5402 1 3 1 11 31 631 631 4631 4631 62 63 DWAAAA UZHAAA OOOOxx
+2789 5403 1 1 9 9 89 789 789 2789 2789 178 179 HDAAAA VZHAAA VVVVxx
+7720 5404 0 0 0 0 20 720 1720 2720 7720 40 41 YKAAAA WZHAAA AAAAxx
+7618 5405 0 2 8 18 18 618 1618 2618 7618 36 37 AHAAAA XZHAAA HHHHxx
+4990 5406 0 2 0 10 90 990 990 4990 4990 180 181 YJAAAA YZHAAA OOOOxx
+7918 5407 0 2 8 18 18 918 1918 2918 7918 36 37 OSAAAA ZZHAAA VVVVxx
+5067 5408 1 3 7 7 67 67 1067 67 5067 134 135 XMAAAA AAIAAA AAAAxx
+6370 5409 0 2 0 10 70 370 370 1370 6370 140 141 ALAAAA BAIAAA HHHHxx
+2268 5410 0 0 8 8 68 268 268 2268 2268 136 137 GJAAAA CAIAAA OOOOxx
+1949 5411 1 1 9 9 49 949 1949 1949 1949 98 99 ZWAAAA DAIAAA VVVVxx
+5503 5412 1 3 3 3 3 503 1503 503 5503 6 7 RDAAAA EAIAAA AAAAxx
+9951 5413 1 3 1 11 51 951 1951 4951 9951 102 103 TSAAAA FAIAAA HHHHxx
+6823 5414 1 3 3 3 23 823 823 1823 6823 46 47 LCAAAA GAIAAA OOOOxx
+6287 5415 1 3 7 7 87 287 287 1287 6287 174 175 VHAAAA HAIAAA VVVVxx
+6016 5416 0 0 6 16 16 16 16 1016 6016 32 33 KXAAAA IAIAAA AAAAxx
+1977 5417 1 1 7 17 77 977 1977 1977 1977 154 155 BYAAAA JAIAAA HHHHxx
+8579 5418 1 3 9 19 79 579 579 3579 8579 158 159 ZRAAAA KAIAAA OOOOxx
+6204 5419 0 0 4 4 4 204 204 1204 6204 8 9 QEAAAA LAIAAA VVVVxx
+9764 5420 0 0 4 4 64 764 1764 4764 9764 128 129 OLAAAA MAIAAA AAAAxx
+2005 5421 1 1 5 5 5 5 5 2005 2005 10 11 DZAAAA NAIAAA HHHHxx
+1648 5422 0 0 8 8 48 648 1648 1648 1648 96 97 KLAAAA OAIAAA OOOOxx
+2457 5423 1 1 7 17 57 457 457 2457 2457 114 115 NQAAAA PAIAAA VVVVxx
+2698 5424 0 2 8 18 98 698 698 2698 2698 196 197 UZAAAA QAIAAA AAAAxx
+7730 5425 0 2 0 10 30 730 1730 2730 7730 60 61 ILAAAA RAIAAA HHHHxx
+7287 5426 1 3 7 7 87 287 1287 2287 7287 174 175 HUAAAA SAIAAA OOOOxx
+2937 5427 1 1 7 17 37 937 937 2937 2937 74 75 ZIAAAA TAIAAA VVVVxx
+6824 5428 0 0 4 4 24 824 824 1824 6824 48 49 MCAAAA UAIAAA AAAAxx
+9256 5429 0 0 6 16 56 256 1256 4256 9256 112 113 ASAAAA VAIAAA HHHHxx
+4810 5430 0 2 0 10 10 810 810 4810 4810 20 21 ADAAAA WAIAAA OOOOxx
+3869 5431 1 1 9 9 69 869 1869 3869 3869 138 139 VSAAAA XAIAAA VVVVxx
+1993 5432 1 1 3 13 93 993 1993 1993 1993 186 187 RYAAAA YAIAAA AAAAxx
+6048 5433 0 0 8 8 48 48 48 1048 6048 96 97 QYAAAA ZAIAAA HHHHxx
+6922 5434 0 2 2 2 22 922 922 1922 6922 44 45 GGAAAA ABIAAA OOOOxx
+8 5435 0 0 8 8 8 8 8 8 8 16 17 IAAAAA BBIAAA VVVVxx
+6706 5436 0 2 6 6 6 706 706 1706 6706 12 13 YXAAAA CBIAAA AAAAxx
+9159 5437 1 3 9 19 59 159 1159 4159 9159 118 119 HOAAAA DBIAAA HHHHxx
+7020 5438 0 0 0 0 20 20 1020 2020 7020 40 41 AKAAAA EBIAAA OOOOxx
+767 5439 1 3 7 7 67 767 767 767 767 134 135 NDAAAA FBIAAA VVVVxx
+8602 5440 0 2 2 2 2 602 602 3602 8602 4 5 WSAAAA GBIAAA AAAAxx
+4442 5441 0 2 2 2 42 442 442 4442 4442 84 85 WOAAAA HBIAAA HHHHxx
+2040 5442 0 0 0 0 40 40 40 2040 2040 80 81 MAAAAA IBIAAA OOOOxx
+5493 5443 1 1 3 13 93 493 1493 493 5493 186 187 HDAAAA JBIAAA VVVVxx
+275 5444 1 3 5 15 75 275 275 275 275 150 151 PKAAAA KBIAAA AAAAxx
+8876 5445 0 0 6 16 76 876 876 3876 8876 152 153 KDAAAA LBIAAA HHHHxx
+7381 5446 1 1 1 1 81 381 1381 2381 7381 162 163 XXAAAA MBIAAA OOOOxx
+1827 5447 1 3 7 7 27 827 1827 1827 1827 54 55 HSAAAA NBIAAA VVVVxx
+3537 5448 1 1 7 17 37 537 1537 3537 3537 74 75 BGAAAA OBIAAA AAAAxx
+6978 5449 0 2 8 18 78 978 978 1978 6978 156 157 KIAAAA PBIAAA HHHHxx
+6160 5450 0 0 0 0 60 160 160 1160 6160 120 121 YCAAAA QBIAAA OOOOxx
+9219 5451 1 3 9 19 19 219 1219 4219 9219 38 39 PQAAAA RBIAAA VVVVxx
+5034 5452 0 2 4 14 34 34 1034 34 5034 68 69 QLAAAA SBIAAA AAAAxx
+8463 5453 1 3 3 3 63 463 463 3463 8463 126 127 NNAAAA TBIAAA HHHHxx
+2038 5454 0 2 8 18 38 38 38 2038 2038 76 77 KAAAAA UBIAAA OOOOxx
+9562 5455 0 2 2 2 62 562 1562 4562 9562 124 125 UDAAAA VBIAAA VVVVxx
+2687 5456 1 3 7 7 87 687 687 2687 2687 174 175 JZAAAA WBIAAA AAAAxx
+5092 5457 0 0 2 12 92 92 1092 92 5092 184 185 WNAAAA XBIAAA HHHHxx
+539 5458 1 3 9 19 39 539 539 539 539 78 79 TUAAAA YBIAAA OOOOxx
+2139 5459 1 3 9 19 39 139 139 2139 2139 78 79 HEAAAA ZBIAAA VVVVxx
+9221 5460 1 1 1 1 21 221 1221 4221 9221 42 43 RQAAAA ACIAAA AAAAxx
+965 5461 1 1 5 5 65 965 965 965 965 130 131 DLAAAA BCIAAA HHHHxx
+6051 5462 1 3 1 11 51 51 51 1051 6051 102 103 TYAAAA CCIAAA OOOOxx
+5822 5463 0 2 2 2 22 822 1822 822 5822 44 45 YPAAAA DCIAAA VVVVxx
+6397 5464 1 1 7 17 97 397 397 1397 6397 194 195 BMAAAA ECIAAA AAAAxx
+2375 5465 1 3 5 15 75 375 375 2375 2375 150 151 JNAAAA FCIAAA HHHHxx
+9415 5466 1 3 5 15 15 415 1415 4415 9415 30 31 DYAAAA GCIAAA OOOOxx
+6552 5467 0 0 2 12 52 552 552 1552 6552 104 105 ASAAAA HCIAAA VVVVxx
+2248 5468 0 0 8 8 48 248 248 2248 2248 96 97 MIAAAA ICIAAA AAAAxx
+2611 5469 1 3 1 11 11 611 611 2611 2611 22 23 LWAAAA JCIAAA HHHHxx
+9609 5470 1 1 9 9 9 609 1609 4609 9609 18 19 PFAAAA KCIAAA OOOOxx
+2132 5471 0 0 2 12 32 132 132 2132 2132 64 65 AEAAAA LCIAAA VVVVxx
+8452 5472 0 0 2 12 52 452 452 3452 8452 104 105 CNAAAA MCIAAA AAAAxx
+9407 5473 1 3 7 7 7 407 1407 4407 9407 14 15 VXAAAA NCIAAA HHHHxx
+2814 5474 0 2 4 14 14 814 814 2814 2814 28 29 GEAAAA OCIAAA OOOOxx
+1889 5475 1 1 9 9 89 889 1889 1889 1889 178 179 RUAAAA PCIAAA VVVVxx
+7489 5476 1 1 9 9 89 489 1489 2489 7489 178 179 BCAAAA QCIAAA AAAAxx
+2255 5477 1 3 5 15 55 255 255 2255 2255 110 111 TIAAAA RCIAAA HHHHxx
+3380 5478 0 0 0 0 80 380 1380 3380 3380 160 161 AAAAAA SCIAAA OOOOxx
+1167 5479 1 3 7 7 67 167 1167 1167 1167 134 135 XSAAAA TCIAAA VVVVxx
+5369 5480 1 1 9 9 69 369 1369 369 5369 138 139 NYAAAA UCIAAA AAAAxx
+2378 5481 0 2 8 18 78 378 378 2378 2378 156 157 MNAAAA VCIAAA HHHHxx
+8315 5482 1 3 5 15 15 315 315 3315 8315 30 31 VHAAAA WCIAAA OOOOxx
+2934 5483 0 2 4 14 34 934 934 2934 2934 68 69 WIAAAA XCIAAA VVVVxx
+7924 5484 0 0 4 4 24 924 1924 2924 7924 48 49 USAAAA YCIAAA AAAAxx
+2867 5485 1 3 7 7 67 867 867 2867 2867 134 135 HGAAAA ZCIAAA HHHHxx
+9141 5486 1 1 1 1 41 141 1141 4141 9141 82 83 PNAAAA ADIAAA OOOOxx
+3613 5487 1 1 3 13 13 613 1613 3613 3613 26 27 ZIAAAA BDIAAA VVVVxx
+2461 5488 1 1 1 1 61 461 461 2461 2461 122 123 RQAAAA CDIAAA AAAAxx
+4567 5489 1 3 7 7 67 567 567 4567 4567 134 135 RTAAAA DDIAAA HHHHxx
+2906 5490 0 2 6 6 6 906 906 2906 2906 12 13 UHAAAA EDIAAA OOOOxx
+4848 5491 0 0 8 8 48 848 848 4848 4848 96 97 MEAAAA FDIAAA VVVVxx
+6614 5492 0 2 4 14 14 614 614 1614 6614 28 29 KUAAAA GDIAAA AAAAxx
+6200 5493 0 0 0 0 0 200 200 1200 6200 0 1 MEAAAA HDIAAA HHHHxx
+7895 5494 1 3 5 15 95 895 1895 2895 7895 190 191 RRAAAA IDIAAA OOOOxx
+6829 5495 1 1 9 9 29 829 829 1829 6829 58 59 RCAAAA JDIAAA VVVVxx
+4087 5496 1 3 7 7 87 87 87 4087 4087 174 175 FBAAAA KDIAAA AAAAxx
+8787 5497 1 3 7 7 87 787 787 3787 8787 174 175 ZZAAAA LDIAAA HHHHxx
+3322 5498 0 2 2 2 22 322 1322 3322 3322 44 45 UXAAAA MDIAAA OOOOxx
+9091 5499 1 3 1 11 91 91 1091 4091 9091 182 183 RLAAAA NDIAAA VVVVxx
+5268 5500 0 0 8 8 68 268 1268 268 5268 136 137 QUAAAA ODIAAA AAAAxx
+2719 5501 1 3 9 19 19 719 719 2719 2719 38 39 PAAAAA PDIAAA HHHHxx
+30 5502 0 2 0 10 30 30 30 30 30 60 61 EBAAAA QDIAAA OOOOxx
+1975 5503 1 3 5 15 75 975 1975 1975 1975 150 151 ZXAAAA RDIAAA VVVVxx
+2641 5504 1 1 1 1 41 641 641 2641 2641 82 83 PXAAAA SDIAAA AAAAxx
+8616 5505 0 0 6 16 16 616 616 3616 8616 32 33 KTAAAA TDIAAA HHHHxx
+5980 5506 0 0 0 0 80 980 1980 980 5980 160 161 AWAAAA UDIAAA OOOOxx
+5170 5507 0 2 0 10 70 170 1170 170 5170 140 141 WQAAAA VDIAAA VVVVxx
+1960 5508 0 0 0 0 60 960 1960 1960 1960 120 121 KXAAAA WDIAAA AAAAxx
+8141 5509 1 1 1 1 41 141 141 3141 8141 82 83 DBAAAA XDIAAA HHHHxx
+6692 5510 0 0 2 12 92 692 692 1692 6692 184 185 KXAAAA YDIAAA OOOOxx
+7621 5511 1 1 1 1 21 621 1621 2621 7621 42 43 DHAAAA ZDIAAA VVVVxx
+3890 5512 0 2 0 10 90 890 1890 3890 3890 180 181 QTAAAA AEIAAA AAAAxx
+4300 5513 0 0 0 0 0 300 300 4300 4300 0 1 KJAAAA BEIAAA HHHHxx
+736 5514 0 0 6 16 36 736 736 736 736 72 73 ICAAAA CEIAAA OOOOxx
+6626 5515 0 2 6 6 26 626 626 1626 6626 52 53 WUAAAA DEIAAA VVVVxx
+1800 5516 0 0 0 0 0 800 1800 1800 1800 0 1 GRAAAA EEIAAA AAAAxx
+3430 5517 0 2 0 10 30 430 1430 3430 3430 60 61 YBAAAA FEIAAA HHHHxx
+9519 5518 1 3 9 19 19 519 1519 4519 9519 38 39 DCAAAA GEIAAA OOOOxx
+5111 5519 1 3 1 11 11 111 1111 111 5111 22 23 POAAAA HEIAAA VVVVxx
+6915 5520 1 3 5 15 15 915 915 1915 6915 30 31 ZFAAAA IEIAAA AAAAxx
+9246 5521 0 2 6 6 46 246 1246 4246 9246 92 93 QRAAAA JEIAAA HHHHxx
+5141 5522 1 1 1 1 41 141 1141 141 5141 82 83 TPAAAA KEIAAA OOOOxx
+5922 5523 0 2 2 2 22 922 1922 922 5922 44 45 UTAAAA LEIAAA VVVVxx
+3087 5524 1 3 7 7 87 87 1087 3087 3087 174 175 TOAAAA MEIAAA AAAAxx
+1859 5525 1 3 9 19 59 859 1859 1859 1859 118 119 NTAAAA NEIAAA HHHHxx
+8482 5526 0 2 2 2 82 482 482 3482 8482 164 165 GOAAAA OEIAAA OOOOxx
+8414 5527 0 2 4 14 14 414 414 3414 8414 28 29 QLAAAA PEIAAA VVVVxx
+6662 5528 0 2 2 2 62 662 662 1662 6662 124 125 GWAAAA QEIAAA AAAAxx
+8614 5529 0 2 4 14 14 614 614 3614 8614 28 29 ITAAAA REIAAA HHHHxx
+42 5530 0 2 2 2 42 42 42 42 42 84 85 QBAAAA SEIAAA OOOOxx
+7582 5531 0 2 2 2 82 582 1582 2582 7582 164 165 QFAAAA TEIAAA VVVVxx
+8183 5532 1 3 3 3 83 183 183 3183 8183 166 167 TCAAAA UEIAAA AAAAxx
+1299 5533 1 3 9 19 99 299 1299 1299 1299 198 199 ZXAAAA VEIAAA HHHHxx
+7004 5534 0 0 4 4 4 4 1004 2004 7004 8 9 KJAAAA WEIAAA OOOOxx
+3298 5535 0 2 8 18 98 298 1298 3298 3298 196 197 WWAAAA XEIAAA VVVVxx
+7884 5536 0 0 4 4 84 884 1884 2884 7884 168 169 GRAAAA YEIAAA AAAAxx
+4191 5537 1 3 1 11 91 191 191 4191 4191 182 183 FFAAAA ZEIAAA HHHHxx
+7346 5538 0 2 6 6 46 346 1346 2346 7346 92 93 OWAAAA AFIAAA OOOOxx
+7989 5539 1 1 9 9 89 989 1989 2989 7989 178 179 HVAAAA BFIAAA VVVVxx
+5719 5540 1 3 9 19 19 719 1719 719 5719 38 39 ZLAAAA CFIAAA AAAAxx
+800 5541 0 0 0 0 0 800 800 800 800 0 1 UEAAAA DFIAAA HHHHxx
+6509 5542 1 1 9 9 9 509 509 1509 6509 18 19 JQAAAA EFIAAA OOOOxx
+4672 5543 0 0 2 12 72 672 672 4672 4672 144 145 SXAAAA FFIAAA VVVVxx
+4434 5544 0 2 4 14 34 434 434 4434 4434 68 69 OOAAAA GFIAAA AAAAxx
+8309 5545 1 1 9 9 9 309 309 3309 8309 18 19 PHAAAA HFIAAA HHHHxx
+5134 5546 0 2 4 14 34 134 1134 134 5134 68 69 MPAAAA IFIAAA OOOOxx
+5153 5547 1 1 3 13 53 153 1153 153 5153 106 107 FQAAAA JFIAAA VVVVxx
+1522 5548 0 2 2 2 22 522 1522 1522 1522 44 45 OGAAAA KFIAAA AAAAxx
+8629 5549 1 1 9 9 29 629 629 3629 8629 58 59 XTAAAA LFIAAA HHHHxx
+4549 5550 1 1 9 9 49 549 549 4549 4549 98 99 ZSAAAA MFIAAA OOOOxx
+9506 5551 0 2 6 6 6 506 1506 4506 9506 12 13 QBAAAA NFIAAA VVVVxx
+6542 5552 0 2 2 2 42 542 542 1542 6542 84 85 QRAAAA OFIAAA AAAAxx
+2579 5553 1 3 9 19 79 579 579 2579 2579 158 159 FVAAAA PFIAAA HHHHxx
+4664 5554 0 0 4 4 64 664 664 4664 4664 128 129 KXAAAA QFIAAA OOOOxx
+696 5555 0 0 6 16 96 696 696 696 696 192 193 UAAAAA RFIAAA VVVVxx
+7950 5556 0 2 0 10 50 950 1950 2950 7950 100 101 UTAAAA SFIAAA AAAAxx
+5 5557 1 1 5 5 5 5 5 5 5 10 11 FAAAAA TFIAAA HHHHxx
+7806 5558 0 2 6 6 6 806 1806 2806 7806 12 13 GOAAAA UFIAAA OOOOxx
+2770 5559 0 2 0 10 70 770 770 2770 2770 140 141 OCAAAA VFIAAA VVVVxx
+1344 5560 0 0 4 4 44 344 1344 1344 1344 88 89 SZAAAA WFIAAA AAAAxx
+511 5561 1 3 1 11 11 511 511 511 511 22 23 RTAAAA XFIAAA HHHHxx
+9070 5562 0 2 0 10 70 70 1070 4070 9070 140 141 WKAAAA YFIAAA OOOOxx
+2961 5563 1 1 1 1 61 961 961 2961 2961 122 123 XJAAAA ZFIAAA VVVVxx
+8031 5564 1 3 1 11 31 31 31 3031 8031 62 63 XWAAAA AGIAAA AAAAxx
+326 5565 0 2 6 6 26 326 326 326 326 52 53 OMAAAA BGIAAA HHHHxx
+183 5566 1 3 3 3 83 183 183 183 183 166 167 BHAAAA CGIAAA OOOOxx
+5917 5567 1 1 7 17 17 917 1917 917 5917 34 35 PTAAAA DGIAAA VVVVxx
+8256 5568 0 0 6 16 56 256 256 3256 8256 112 113 OFAAAA EGIAAA AAAAxx
+7889 5569 1 1 9 9 89 889 1889 2889 7889 178 179 LRAAAA FGIAAA HHHHxx
+9029 5570 1 1 9 9 29 29 1029 4029 9029 58 59 HJAAAA GGIAAA OOOOxx
+1316 5571 0 0 6 16 16 316 1316 1316 1316 32 33 QYAAAA HGIAAA VVVVxx
+7442 5572 0 2 2 2 42 442 1442 2442 7442 84 85 GAAAAA IGIAAA AAAAxx
+2810 5573 0 2 0 10 10 810 810 2810 2810 20 21 CEAAAA JGIAAA HHHHxx
+20 5574 0 0 0 0 20 20 20 20 20 40 41 UAAAAA KGIAAA OOOOxx
+2306 5575 0 2 6 6 6 306 306 2306 2306 12 13 SKAAAA LGIAAA VVVVxx
+4694 5576 0 2 4 14 94 694 694 4694 4694 188 189 OYAAAA MGIAAA AAAAxx
+9710 5577 0 2 0 10 10 710 1710 4710 9710 20 21 MJAAAA NGIAAA HHHHxx
+1791 5578 1 3 1 11 91 791 1791 1791 1791 182 183 XQAAAA OGIAAA OOOOxx
+6730 5579 0 2 0 10 30 730 730 1730 6730 60 61 WYAAAA PGIAAA VVVVxx
+359 5580 1 3 9 19 59 359 359 359 359 118 119 VNAAAA QGIAAA AAAAxx
+8097 5581 1 1 7 17 97 97 97 3097 8097 194 195 LZAAAA RGIAAA HHHHxx
+6147 5582 1 3 7 7 47 147 147 1147 6147 94 95 LCAAAA SGIAAA OOOOxx
+643 5583 1 3 3 3 43 643 643 643 643 86 87 TYAAAA TGIAAA VVVVxx
+698 5584 0 2 8 18 98 698 698 698 698 196 197 WAAAAA UGIAAA AAAAxx
+3881 5585 1 1 1 1 81 881 1881 3881 3881 162 163 HTAAAA VGIAAA HHHHxx
+7600 5586 0 0 0 0 0 600 1600 2600 7600 0 1 IGAAAA WGIAAA OOOOxx
+1583 5587 1 3 3 3 83 583 1583 1583 1583 166 167 XIAAAA XGIAAA VVVVxx
+9612 5588 0 0 2 12 12 612 1612 4612 9612 24 25 SFAAAA YGIAAA AAAAxx
+1032 5589 0 0 2 12 32 32 1032 1032 1032 64 65 SNAAAA ZGIAAA HHHHxx
+4834 5590 0 2 4 14 34 834 834 4834 4834 68 69 YDAAAA AHIAAA OOOOxx
+5076 5591 0 0 6 16 76 76 1076 76 5076 152 153 GNAAAA BHIAAA VVVVxx
+3070 5592 0 2 0 10 70 70 1070 3070 3070 140 141 COAAAA CHIAAA AAAAxx
+1421 5593 1 1 1 1 21 421 1421 1421 1421 42 43 RCAAAA DHIAAA HHHHxx
+8970 5594 0 2 0 10 70 970 970 3970 8970 140 141 AHAAAA EHIAAA OOOOxx
+6271 5595 1 3 1 11 71 271 271 1271 6271 142 143 FHAAAA FHIAAA VVVVxx
+8547 5596 1 3 7 7 47 547 547 3547 8547 94 95 TQAAAA GHIAAA AAAAxx
+1259 5597 1 3 9 19 59 259 1259 1259 1259 118 119 LWAAAA HHIAAA HHHHxx
+8328 5598 0 0 8 8 28 328 328 3328 8328 56 57 IIAAAA IHIAAA OOOOxx
+1503 5599 1 3 3 3 3 503 1503 1503 1503 6 7 VFAAAA JHIAAA VVVVxx
+2253 5600 1 1 3 13 53 253 253 2253 2253 106 107 RIAAAA KHIAAA AAAAxx
+7449 5601 1 1 9 9 49 449 1449 2449 7449 98 99 NAAAAA LHIAAA HHHHxx
+3579 5602 1 3 9 19 79 579 1579 3579 3579 158 159 RHAAAA MHIAAA OOOOxx
+1585 5603 1 1 5 5 85 585 1585 1585 1585 170 171 ZIAAAA NHIAAA VVVVxx
+5543 5604 1 3 3 3 43 543 1543 543 5543 86 87 FFAAAA OHIAAA AAAAxx
+8627 5605 1 3 7 7 27 627 627 3627 8627 54 55 VTAAAA PHIAAA HHHHxx
+8618 5606 0 2 8 18 18 618 618 3618 8618 36 37 MTAAAA QHIAAA OOOOxx
+1911 5607 1 3 1 11 11 911 1911 1911 1911 22 23 NVAAAA RHIAAA VVVVxx
+2758 5608 0 2 8 18 58 758 758 2758 2758 116 117 CCAAAA SHIAAA AAAAxx
+5744 5609 0 0 4 4 44 744 1744 744 5744 88 89 YMAAAA THIAAA HHHHxx
+4976 5610 0 0 6 16 76 976 976 4976 4976 152 153 KJAAAA UHIAAA OOOOxx
+6380 5611 0 0 0 0 80 380 380 1380 6380 160 161 KLAAAA VHIAAA VVVVxx
+1937 5612 1 1 7 17 37 937 1937 1937 1937 74 75 NWAAAA WHIAAA AAAAxx
+9903 5613 1 3 3 3 3 903 1903 4903 9903 6 7 XQAAAA XHIAAA HHHHxx
+4409 5614 1 1 9 9 9 409 409 4409 4409 18 19 PNAAAA YHIAAA OOOOxx
+4133 5615 1 1 3 13 33 133 133 4133 4133 66 67 ZCAAAA ZHIAAA VVVVxx
+5263 5616 1 3 3 3 63 263 1263 263 5263 126 127 LUAAAA AIIAAA AAAAxx
+7888 5617 0 0 8 8 88 888 1888 2888 7888 176 177 KRAAAA BIIAAA HHHHxx
+6060 5618 0 0 0 0 60 60 60 1060 6060 120 121 CZAAAA CIIAAA OOOOxx
+2522 5619 0 2 2 2 22 522 522 2522 2522 44 45 ATAAAA DIIAAA VVVVxx
+5550 5620 0 2 0 10 50 550 1550 550 5550 100 101 MFAAAA EIIAAA AAAAxx
+9396 5621 0 0 6 16 96 396 1396 4396 9396 192 193 KXAAAA FIIAAA HHHHxx
+176 5622 0 0 6 16 76 176 176 176 176 152 153 UGAAAA GIIAAA OOOOxx
+5148 5623 0 0 8 8 48 148 1148 148 5148 96 97 AQAAAA HIIAAA VVVVxx
+6691 5624 1 3 1 11 91 691 691 1691 6691 182 183 JXAAAA IIIAAA AAAAxx
+4652 5625 0 0 2 12 52 652 652 4652 4652 104 105 YWAAAA JIIAAA HHHHxx
+5096 5626 0 0 6 16 96 96 1096 96 5096 192 193 AOAAAA KIIAAA OOOOxx
+2408 5627 0 0 8 8 8 408 408 2408 2408 16 17 QOAAAA LIIAAA VVVVxx
+7322 5628 0 2 2 2 22 322 1322 2322 7322 44 45 QVAAAA MIIAAA AAAAxx
+6782 5629 0 2 2 2 82 782 782 1782 6782 164 165 WAAAAA NIIAAA HHHHxx
+4642 5630 0 2 2 2 42 642 642 4642 4642 84 85 OWAAAA OIIAAA OOOOxx
+5427 5631 1 3 7 7 27 427 1427 427 5427 54 55 TAAAAA PIIAAA VVVVxx
+4461 5632 1 1 1 1 61 461 461 4461 4461 122 123 PPAAAA QIIAAA AAAAxx
+8416 5633 0 0 6 16 16 416 416 3416 8416 32 33 SLAAAA RIIAAA HHHHxx
+2593 5634 1 1 3 13 93 593 593 2593 2593 186 187 TVAAAA SIIAAA OOOOxx
+6202 5635 0 2 2 2 2 202 202 1202 6202 4 5 OEAAAA TIIAAA VVVVxx
+3826 5636 0 2 6 6 26 826 1826 3826 3826 52 53 ERAAAA UIIAAA AAAAxx
+4417 5637 1 1 7 17 17 417 417 4417 4417 34 35 XNAAAA VIIAAA HHHHxx
+7871 5638 1 3 1 11 71 871 1871 2871 7871 142 143 TQAAAA WIIAAA OOOOxx
+5622 5639 0 2 2 2 22 622 1622 622 5622 44 45 GIAAAA XIIAAA VVVVxx
+3010 5640 0 2 0 10 10 10 1010 3010 3010 20 21 ULAAAA YIIAAA AAAAxx
+3407 5641 1 3 7 7 7 407 1407 3407 3407 14 15 BBAAAA ZIIAAA HHHHxx
+1274 5642 0 2 4 14 74 274 1274 1274 1274 148 149 AXAAAA AJIAAA OOOOxx
+2828 5643 0 0 8 8 28 828 828 2828 2828 56 57 UEAAAA BJIAAA VVVVxx
+3427 5644 1 3 7 7 27 427 1427 3427 3427 54 55 VBAAAA CJIAAA AAAAxx
+612 5645 0 0 2 12 12 612 612 612 612 24 25 OXAAAA DJIAAA HHHHxx
+8729 5646 1 1 9 9 29 729 729 3729 8729 58 59 TXAAAA EJIAAA OOOOxx
+1239 5647 1 3 9 19 39 239 1239 1239 1239 78 79 RVAAAA FJIAAA VVVVxx
+8990 5648 0 2 0 10 90 990 990 3990 8990 180 181 UHAAAA GJIAAA AAAAxx
+5609 5649 1 1 9 9 9 609 1609 609 5609 18 19 THAAAA HJIAAA HHHHxx
+4441 5650 1 1 1 1 41 441 441 4441 4441 82 83 VOAAAA IJIAAA OOOOxx
+9078 5651 0 2 8 18 78 78 1078 4078 9078 156 157 ELAAAA JJIAAA VVVVxx
+6699 5652 1 3 9 19 99 699 699 1699 6699 198 199 RXAAAA KJIAAA AAAAxx
+8390 5653 0 2 0 10 90 390 390 3390 8390 180 181 SKAAAA LJIAAA HHHHxx
+5455 5654 1 3 5 15 55 455 1455 455 5455 110 111 VBAAAA MJIAAA OOOOxx
+7537 5655 1 1 7 17 37 537 1537 2537 7537 74 75 XDAAAA NJIAAA VVVVxx
+4669 5656 1 1 9 9 69 669 669 4669 4669 138 139 PXAAAA OJIAAA AAAAxx
+5534 5657 0 2 4 14 34 534 1534 534 5534 68 69 WEAAAA PJIAAA HHHHxx
+1920 5658 0 0 0 0 20 920 1920 1920 1920 40 41 WVAAAA QJIAAA OOOOxx
+9465 5659 1 1 5 5 65 465 1465 4465 9465 130 131 BAAAAA RJIAAA VVVVxx
+4897 5660 1 1 7 17 97 897 897 4897 4897 194 195 JGAAAA SJIAAA AAAAxx
+1990 5661 0 2 0 10 90 990 1990 1990 1990 180 181 OYAAAA TJIAAA HHHHxx
+7148 5662 0 0 8 8 48 148 1148 2148 7148 96 97 YOAAAA UJIAAA OOOOxx
+533 5663 1 1 3 13 33 533 533 533 533 66 67 NUAAAA VJIAAA VVVVxx
+4339 5664 1 3 9 19 39 339 339 4339 4339 78 79 XKAAAA WJIAAA AAAAxx
+6450 5665 0 2 0 10 50 450 450 1450 6450 100 101 COAAAA XJIAAA HHHHxx
+9627 5666 1 3 7 7 27 627 1627 4627 9627 54 55 HGAAAA YJIAAA OOOOxx
+5539 5667 1 3 9 19 39 539 1539 539 5539 78 79 BFAAAA ZJIAAA VVVVxx
+6758 5668 0 2 8 18 58 758 758 1758 6758 116 117 YZAAAA AKIAAA AAAAxx
+3435 5669 1 3 5 15 35 435 1435 3435 3435 70 71 DCAAAA BKIAAA HHHHxx
+4350 5670 0 2 0 10 50 350 350 4350 4350 100 101 ILAAAA CKIAAA OOOOxx
+9088 5671 0 0 8 8 88 88 1088 4088 9088 176 177 OLAAAA DKIAAA VVVVxx
+6368 5672 0 0 8 8 68 368 368 1368 6368 136 137 YKAAAA EKIAAA AAAAxx
+6337 5673 1 1 7 17 37 337 337 1337 6337 74 75 TJAAAA FKIAAA HHHHxx
+4361 5674 1 1 1 1 61 361 361 4361 4361 122 123 TLAAAA GKIAAA OOOOxx
+1719 5675 1 3 9 19 19 719 1719 1719 1719 38 39 DOAAAA HKIAAA VVVVxx
+3109 5676 1 1 9 9 9 109 1109 3109 3109 18 19 PPAAAA IKIAAA AAAAxx
+7135 5677 1 3 5 15 35 135 1135 2135 7135 70 71 LOAAAA JKIAAA HHHHxx
+1964 5678 0 0 4 4 64 964 1964 1964 1964 128 129 OXAAAA KKIAAA OOOOxx
+3 5679 1 3 3 3 3 3 3 3 3 6 7 DAAAAA LKIAAA VVVVxx
+1868 5680 0 0 8 8 68 868 1868 1868 1868 136 137 WTAAAA MKIAAA AAAAxx
+5182 5681 0 2 2 2 82 182 1182 182 5182 164 165 IRAAAA NKIAAA HHHHxx
+7567 5682 1 3 7 7 67 567 1567 2567 7567 134 135 BFAAAA OKIAAA OOOOxx
+3676 5683 0 0 6 16 76 676 1676 3676 3676 152 153 KLAAAA PKIAAA VVVVxx
+9382 5684 0 2 2 2 82 382 1382 4382 9382 164 165 WWAAAA QKIAAA AAAAxx
+8645 5685 1 1 5 5 45 645 645 3645 8645 90 91 NUAAAA RKIAAA HHHHxx
+2018 5686 0 2 8 18 18 18 18 2018 2018 36 37 QZAAAA SKIAAA OOOOxx
+217 5687 1 1 7 17 17 217 217 217 217 34 35 JIAAAA TKIAAA VVVVxx
+6793 5688 1 1 3 13 93 793 793 1793 6793 186 187 HBAAAA UKIAAA AAAAxx
+7280 5689 0 0 0 0 80 280 1280 2280 7280 160 161 AUAAAA VKIAAA HHHHxx
+2168 5690 0 0 8 8 68 168 168 2168 2168 136 137 KFAAAA WKIAAA OOOOxx
+5259 5691 1 3 9 19 59 259 1259 259 5259 118 119 HUAAAA XKIAAA VVVVxx
+6019 5692 1 3 9 19 19 19 19 1019 6019 38 39 NXAAAA YKIAAA AAAAxx
+877 5693 1 1 7 17 77 877 877 877 877 154 155 THAAAA ZKIAAA HHHHxx
+4961 5694 1 1 1 1 61 961 961 4961 4961 122 123 VIAAAA ALIAAA OOOOxx
+1873 5695 1 1 3 13 73 873 1873 1873 1873 146 147 BUAAAA BLIAAA VVVVxx
+13 5696 1 1 3 13 13 13 13 13 13 26 27 NAAAAA CLIAAA AAAAxx
+1537 5697 1 1 7 17 37 537 1537 1537 1537 74 75 DHAAAA DLIAAA HHHHxx
+3129 5698 1 1 9 9 29 129 1129 3129 3129 58 59 JQAAAA ELIAAA OOOOxx
+6473 5699 1 1 3 13 73 473 473 1473 6473 146 147 ZOAAAA FLIAAA VVVVxx
+7865 5700 1 1 5 5 65 865 1865 2865 7865 130 131 NQAAAA GLIAAA AAAAxx
+7822 5701 0 2 2 2 22 822 1822 2822 7822 44 45 WOAAAA HLIAAA HHHHxx
+239 5702 1 3 9 19 39 239 239 239 239 78 79 FJAAAA ILIAAA OOOOxx
+2062 5703 0 2 2 2 62 62 62 2062 2062 124 125 IBAAAA JLIAAA VVVVxx
+762 5704 0 2 2 2 62 762 762 762 762 124 125 IDAAAA KLIAAA AAAAxx
+3764 5705 0 0 4 4 64 764 1764 3764 3764 128 129 UOAAAA LLIAAA HHHHxx
+465 5706 1 1 5 5 65 465 465 465 465 130 131 XRAAAA MLIAAA OOOOxx
+2587 5707 1 3 7 7 87 587 587 2587 2587 174 175 NVAAAA NLIAAA VVVVxx
+8402 5708 0 2 2 2 2 402 402 3402 8402 4 5 ELAAAA OLIAAA AAAAxx
+1055 5709 1 3 5 15 55 55 1055 1055 1055 110 111 POAAAA PLIAAA HHHHxx
+3072 5710 0 0 2 12 72 72 1072 3072 3072 144 145 EOAAAA QLIAAA OOOOxx
+7359 5711 1 3 9 19 59 359 1359 2359 7359 118 119 BXAAAA RLIAAA VVVVxx
+6558 5712 0 2 8 18 58 558 558 1558 6558 116 117 GSAAAA SLIAAA AAAAxx
+48 5713 0 0 8 8 48 48 48 48 48 96 97 WBAAAA TLIAAA HHHHxx
+5382 5714 0 2 2 2 82 382 1382 382 5382 164 165 AZAAAA ULIAAA OOOOxx
+947 5715 1 3 7 7 47 947 947 947 947 94 95 LKAAAA VLIAAA VVVVxx
+2644 5716 0 0 4 4 44 644 644 2644 2644 88 89 SXAAAA WLIAAA AAAAxx
+7516 5717 0 0 6 16 16 516 1516 2516 7516 32 33 CDAAAA XLIAAA HHHHxx
+2362 5718 0 2 2 2 62 362 362 2362 2362 124 125 WMAAAA YLIAAA OOOOxx
+839 5719 1 3 9 19 39 839 839 839 839 78 79 HGAAAA ZLIAAA VVVVxx
+2216 5720 0 0 6 16 16 216 216 2216 2216 32 33 GHAAAA AMIAAA AAAAxx
+7673 5721 1 1 3 13 73 673 1673 2673 7673 146 147 DJAAAA BMIAAA HHHHxx
+8173 5722 1 1 3 13 73 173 173 3173 8173 146 147 JCAAAA CMIAAA OOOOxx
+1630 5723 0 2 0 10 30 630 1630 1630 1630 60 61 SKAAAA DMIAAA VVVVxx
+9057 5724 1 1 7 17 57 57 1057 4057 9057 114 115 JKAAAA EMIAAA AAAAxx
+4392 5725 0 0 2 12 92 392 392 4392 4392 184 185 YMAAAA FMIAAA HHHHxx
+3695 5726 1 3 5 15 95 695 1695 3695 3695 190 191 DMAAAA GMIAAA OOOOxx
+5751 5727 1 3 1 11 51 751 1751 751 5751 102 103 FNAAAA HMIAAA VVVVxx
+5745 5728 1 1 5 5 45 745 1745 745 5745 90 91 ZMAAAA IMIAAA AAAAxx
+7945 5729 1 1 5 5 45 945 1945 2945 7945 90 91 PTAAAA JMIAAA HHHHxx
+5174 5730 0 2 4 14 74 174 1174 174 5174 148 149 ARAAAA KMIAAA OOOOxx
+3829 5731 1 1 9 9 29 829 1829 3829 3829 58 59 HRAAAA LMIAAA VVVVxx
+3317 5732 1 1 7 17 17 317 1317 3317 3317 34 35 PXAAAA MMIAAA AAAAxx
+4253 5733 1 1 3 13 53 253 253 4253 4253 106 107 PHAAAA NMIAAA HHHHxx
+1291 5734 1 3 1 11 91 291 1291 1291 1291 182 183 RXAAAA OMIAAA OOOOxx
+3266 5735 0 2 6 6 66 266 1266 3266 3266 132 133 QVAAAA PMIAAA VVVVxx
+2939 5736 1 3 9 19 39 939 939 2939 2939 78 79 BJAAAA QMIAAA AAAAxx
+2755 5737 1 3 5 15 55 755 755 2755 2755 110 111 ZBAAAA RMIAAA HHHHxx
+6844 5738 0 0 4 4 44 844 844 1844 6844 88 89 GDAAAA SMIAAA OOOOxx
+8594 5739 0 2 4 14 94 594 594 3594 8594 188 189 OSAAAA TMIAAA VVVVxx
+704 5740 0 0 4 4 4 704 704 704 704 8 9 CBAAAA UMIAAA AAAAxx
+1681 5741 1 1 1 1 81 681 1681 1681 1681 162 163 RMAAAA VMIAAA HHHHxx
+364 5742 0 0 4 4 64 364 364 364 364 128 129 AOAAAA WMIAAA OOOOxx
+2928 5743 0 0 8 8 28 928 928 2928 2928 56 57 QIAAAA XMIAAA VVVVxx
+117 5744 1 1 7 17 17 117 117 117 117 34 35 NEAAAA YMIAAA AAAAxx
+96 5745 0 0 6 16 96 96 96 96 96 192 193 SDAAAA ZMIAAA HHHHxx
+7796 5746 0 0 6 16 96 796 1796 2796 7796 192 193 WNAAAA ANIAAA OOOOxx
+3101 5747 1 1 1 1 1 101 1101 3101 3101 2 3 HPAAAA BNIAAA VVVVxx
+3397 5748 1 1 7 17 97 397 1397 3397 3397 194 195 RAAAAA CNIAAA AAAAxx
+1605 5749 1 1 5 5 5 605 1605 1605 1605 10 11 TJAAAA DNIAAA HHHHxx
+4881 5750 1 1 1 1 81 881 881 4881 4881 162 163 TFAAAA ENIAAA OOOOxx
+4521 5751 1 1 1 1 21 521 521 4521 4521 42 43 XRAAAA FNIAAA VVVVxx
+6430 5752 0 2 0 10 30 430 430 1430 6430 60 61 INAAAA GNIAAA AAAAxx
+282 5753 0 2 2 2 82 282 282 282 282 164 165 WKAAAA HNIAAA HHHHxx
+9645 5754 1 1 5 5 45 645 1645 4645 9645 90 91 ZGAAAA INIAAA OOOOxx
+8946 5755 0 2 6 6 46 946 946 3946 8946 92 93 CGAAAA JNIAAA VVVVxx
+5064 5756 0 0 4 4 64 64 1064 64 5064 128 129 UMAAAA KNIAAA AAAAxx
+7470 5757 0 2 0 10 70 470 1470 2470 7470 140 141 IBAAAA LNIAAA HHHHxx
+5886 5758 0 2 6 6 86 886 1886 886 5886 172 173 KSAAAA MNIAAA OOOOxx
+6280 5759 0 0 0 0 80 280 280 1280 6280 160 161 OHAAAA NNIAAA VVVVxx
+5247 5760 1 3 7 7 47 247 1247 247 5247 94 95 VTAAAA ONIAAA AAAAxx
+412 5761 0 0 2 12 12 412 412 412 412 24 25 WPAAAA PNIAAA HHHHxx
+5342 5762 0 2 2 2 42 342 1342 342 5342 84 85 MXAAAA QNIAAA OOOOxx
+2271 5763 1 3 1 11 71 271 271 2271 2271 142 143 JJAAAA RNIAAA VVVVxx
+849 5764 1 1 9 9 49 849 849 849 849 98 99 RGAAAA SNIAAA AAAAxx
+1885 5765 1 1 5 5 85 885 1885 1885 1885 170 171 NUAAAA TNIAAA HHHHxx
+5620 5766 0 0 0 0 20 620 1620 620 5620 40 41 EIAAAA UNIAAA OOOOxx
+7079 5767 1 3 9 19 79 79 1079 2079 7079 158 159 HMAAAA VNIAAA VVVVxx
+5819 5768 1 3 9 19 19 819 1819 819 5819 38 39 VPAAAA WNIAAA AAAAxx
+7497 5769 1 1 7 17 97 497 1497 2497 7497 194 195 JCAAAA XNIAAA HHHHxx
+5993 5770 1 1 3 13 93 993 1993 993 5993 186 187 NWAAAA YNIAAA OOOOxx
+3739 5771 1 3 9 19 39 739 1739 3739 3739 78 79 VNAAAA ZNIAAA VVVVxx
+6296 5772 0 0 6 16 96 296 296 1296 6296 192 193 EIAAAA AOIAAA AAAAxx
+2716 5773 0 0 6 16 16 716 716 2716 2716 32 33 MAAAAA BOIAAA HHHHxx
+1130 5774 0 2 0 10 30 130 1130 1130 1130 60 61 MRAAAA COIAAA OOOOxx
+5593 5775 1 1 3 13 93 593 1593 593 5593 186 187 DHAAAA DOIAAA VVVVxx
+6972 5776 0 0 2 12 72 972 972 1972 6972 144 145 EIAAAA EOIAAA AAAAxx
+8360 5777 0 0 0 0 60 360 360 3360 8360 120 121 OJAAAA FOIAAA HHHHxx
+6448 5778 0 0 8 8 48 448 448 1448 6448 96 97 AOAAAA GOIAAA OOOOxx
+3689 5779 1 1 9 9 89 689 1689 3689 3689 178 179 XLAAAA HOIAAA VVVVxx
+7951 5780 1 3 1 11 51 951 1951 2951 7951 102 103 VTAAAA IOIAAA AAAAxx
+2974 5781 0 2 4 14 74 974 974 2974 2974 148 149 KKAAAA JOIAAA HHHHxx
+6600 5782 0 0 0 0 0 600 600 1600 6600 0 1 WTAAAA KOIAAA OOOOxx
+4662 5783 0 2 2 2 62 662 662 4662 4662 124 125 IXAAAA LOIAAA VVVVxx
+4765 5784 1 1 5 5 65 765 765 4765 4765 130 131 HBAAAA MOIAAA AAAAxx
+355 5785 1 3 5 15 55 355 355 355 355 110 111 RNAAAA NOIAAA HHHHxx
+6228 5786 0 0 8 8 28 228 228 1228 6228 56 57 OFAAAA OOIAAA OOOOxx
+964 5787 0 0 4 4 64 964 964 964 964 128 129 CLAAAA POIAAA VVVVxx
+3082 5788 0 2 2 2 82 82 1082 3082 3082 164 165 OOAAAA QOIAAA AAAAxx
+7028 5789 0 0 8 8 28 28 1028 2028 7028 56 57 IKAAAA ROIAAA HHHHxx
+4505 5790 1 1 5 5 5 505 505 4505 4505 10 11 HRAAAA SOIAAA OOOOxx
+8961 5791 1 1 1 1 61 961 961 3961 8961 122 123 RGAAAA TOIAAA VVVVxx
+9571 5792 1 3 1 11 71 571 1571 4571 9571 142 143 DEAAAA UOIAAA AAAAxx
+9394 5793 0 2 4 14 94 394 1394 4394 9394 188 189 IXAAAA VOIAAA HHHHxx
+4245 5794 1 1 5 5 45 245 245 4245 4245 90 91 HHAAAA WOIAAA OOOOxx
+7560 5795 0 0 0 0 60 560 1560 2560 7560 120 121 UEAAAA XOIAAA VVVVxx
+2907 5796 1 3 7 7 7 907 907 2907 2907 14 15 VHAAAA YOIAAA AAAAxx
+7817 5797 1 1 7 17 17 817 1817 2817 7817 34 35 ROAAAA ZOIAAA HHHHxx
+5408 5798 0 0 8 8 8 408 1408 408 5408 16 17 AAAAAA APIAAA OOOOxx
+8092 5799 0 0 2 12 92 92 92 3092 8092 184 185 GZAAAA BPIAAA VVVVxx
+1309 5800 1 1 9 9 9 309 1309 1309 1309 18 19 JYAAAA CPIAAA AAAAxx
+6673 5801 1 1 3 13 73 673 673 1673 6673 146 147 RWAAAA DPIAAA HHHHxx
+1245 5802 1 1 5 5 45 245 1245 1245 1245 90 91 XVAAAA EPIAAA OOOOxx
+6790 5803 0 2 0 10 90 790 790 1790 6790 180 181 EBAAAA FPIAAA VVVVxx
+8380 5804 0 0 0 0 80 380 380 3380 8380 160 161 IKAAAA GPIAAA AAAAxx
+5786 5805 0 2 6 6 86 786 1786 786 5786 172 173 OOAAAA HPIAAA HHHHxx
+9590 5806 0 2 0 10 90 590 1590 4590 9590 180 181 WEAAAA IPIAAA OOOOxx
+5763 5807 1 3 3 3 63 763 1763 763 5763 126 127 RNAAAA JPIAAA VVVVxx
+1345 5808 1 1 5 5 45 345 1345 1345 1345 90 91 TZAAAA KPIAAA AAAAxx
+3480 5809 0 0 0 0 80 480 1480 3480 3480 160 161 WDAAAA LPIAAA HHHHxx
+7864 5810 0 0 4 4 64 864 1864 2864 7864 128 129 MQAAAA MPIAAA OOOOxx
+4853 5811 1 1 3 13 53 853 853 4853 4853 106 107 REAAAA NPIAAA VVVVxx
+1445 5812 1 1 5 5 45 445 1445 1445 1445 90 91 PDAAAA OPIAAA AAAAxx
+170 5813 0 2 0 10 70 170 170 170 170 140 141 OGAAAA PPIAAA HHHHxx
+7348 5814 0 0 8 8 48 348 1348 2348 7348 96 97 QWAAAA QPIAAA OOOOxx
+3920 5815 0 0 0 0 20 920 1920 3920 3920 40 41 UUAAAA RPIAAA VVVVxx
+3307 5816 1 3 7 7 7 307 1307 3307 3307 14 15 FXAAAA SPIAAA AAAAxx
+4584 5817 0 0 4 4 84 584 584 4584 4584 168 169 IUAAAA TPIAAA HHHHxx
+3344 5818 0 0 4 4 44 344 1344 3344 3344 88 89 QYAAAA UPIAAA OOOOxx
+4360 5819 0 0 0 0 60 360 360 4360 4360 120 121 SLAAAA VPIAAA VVVVxx
+8757 5820 1 1 7 17 57 757 757 3757 8757 114 115 VYAAAA WPIAAA AAAAxx
+4315 5821 1 3 5 15 15 315 315 4315 4315 30 31 ZJAAAA XPIAAA HHHHxx
+5243 5822 1 3 3 3 43 243 1243 243 5243 86 87 RTAAAA YPIAAA OOOOxx
+8550 5823 0 2 0 10 50 550 550 3550 8550 100 101 WQAAAA ZPIAAA VVVVxx
+159 5824 1 3 9 19 59 159 159 159 159 118 119 DGAAAA AQIAAA AAAAxx
+4710 5825 0 2 0 10 10 710 710 4710 4710 20 21 EZAAAA BQIAAA HHHHxx
+7179 5826 1 3 9 19 79 179 1179 2179 7179 158 159 DQAAAA CQIAAA OOOOxx
+2509 5827 1 1 9 9 9 509 509 2509 2509 18 19 NSAAAA DQIAAA VVVVxx
+6981 5828 1 1 1 1 81 981 981 1981 6981 162 163 NIAAAA EQIAAA AAAAxx
+5060 5829 0 0 0 0 60 60 1060 60 5060 120 121 QMAAAA FQIAAA HHHHxx
+5601 5830 1 1 1 1 1 601 1601 601 5601 2 3 LHAAAA GQIAAA OOOOxx
+703 5831 1 3 3 3 3 703 703 703 703 6 7 BBAAAA HQIAAA VVVVxx
+8719 5832 1 3 9 19 19 719 719 3719 8719 38 39 JXAAAA IQIAAA AAAAxx
+1570 5833 0 2 0 10 70 570 1570 1570 1570 140 141 KIAAAA JQIAAA HHHHxx
+1036 5834 0 0 6 16 36 36 1036 1036 1036 72 73 WNAAAA KQIAAA OOOOxx
+6703 5835 1 3 3 3 3 703 703 1703 6703 6 7 VXAAAA LQIAAA VVVVxx
+252 5836 0 0 2 12 52 252 252 252 252 104 105 SJAAAA MQIAAA AAAAxx
+631 5837 1 3 1 11 31 631 631 631 631 62 63 HYAAAA NQIAAA HHHHxx
+5098 5838 0 2 8 18 98 98 1098 98 5098 196 197 COAAAA OQIAAA OOOOxx
+8346 5839 0 2 6 6 46 346 346 3346 8346 92 93 AJAAAA PQIAAA VVVVxx
+4910 5840 0 2 0 10 10 910 910 4910 4910 20 21 WGAAAA QQIAAA AAAAxx
+559 5841 1 3 9 19 59 559 559 559 559 118 119 NVAAAA RQIAAA HHHHxx
+1477 5842 1 1 7 17 77 477 1477 1477 1477 154 155 VEAAAA SQIAAA OOOOxx
+5115 5843 1 3 5 15 15 115 1115 115 5115 30 31 TOAAAA TQIAAA VVVVxx
+8784 5844 0 0 4 4 84 784 784 3784 8784 168 169 WZAAAA UQIAAA AAAAxx
+4422 5845 0 2 2 2 22 422 422 4422 4422 44 45 COAAAA VQIAAA HHHHxx
+2702 5846 0 2 2 2 2 702 702 2702 2702 4 5 YZAAAA WQIAAA OOOOxx
+9599 5847 1 3 9 19 99 599 1599 4599 9599 198 199 FFAAAA XQIAAA VVVVxx
+2463 5848 1 3 3 3 63 463 463 2463 2463 126 127 TQAAAA YQIAAA AAAAxx
+498 5849 0 2 8 18 98 498 498 498 498 196 197 ETAAAA ZQIAAA HHHHxx
+494 5850 0 2 4 14 94 494 494 494 494 188 189 ATAAAA ARIAAA OOOOxx
+8632 5851 0 0 2 12 32 632 632 3632 8632 64 65 AUAAAA BRIAAA VVVVxx
+3449 5852 1 1 9 9 49 449 1449 3449 3449 98 99 RCAAAA CRIAAA AAAAxx
+5888 5853 0 0 8 8 88 888 1888 888 5888 176 177 MSAAAA DRIAAA HHHHxx
+2211 5854 1 3 1 11 11 211 211 2211 2211 22 23 BHAAAA ERIAAA OOOOxx
+2835 5855 1 3 5 15 35 835 835 2835 2835 70 71 BFAAAA FRIAAA VVVVxx
+4196 5856 0 0 6 16 96 196 196 4196 4196 192 193 KFAAAA GRIAAA AAAAxx
+2177 5857 1 1 7 17 77 177 177 2177 2177 154 155 TFAAAA HRIAAA HHHHxx
+1959 5858 1 3 9 19 59 959 1959 1959 1959 118 119 JXAAAA IRIAAA OOOOxx
+5172 5859 0 0 2 12 72 172 1172 172 5172 144 145 YQAAAA JRIAAA VVVVxx
+7898 5860 0 2 8 18 98 898 1898 2898 7898 196 197 URAAAA KRIAAA AAAAxx
+5729 5861 1 1 9 9 29 729 1729 729 5729 58 59 JMAAAA LRIAAA HHHHxx
+469 5862 1 1 9 9 69 469 469 469 469 138 139 BSAAAA MRIAAA OOOOxx
+4456 5863 0 0 6 16 56 456 456 4456 4456 112 113 KPAAAA NRIAAA VVVVxx
+3578 5864 0 2 8 18 78 578 1578 3578 3578 156 157 QHAAAA ORIAAA AAAAxx
+8623 5865 1 3 3 3 23 623 623 3623 8623 46 47 RTAAAA PRIAAA HHHHxx
+6749 5866 1 1 9 9 49 749 749 1749 6749 98 99 PZAAAA QRIAAA OOOOxx
+6735 5867 1 3 5 15 35 735 735 1735 6735 70 71 BZAAAA RRIAAA VVVVxx
+5197 5868 1 1 7 17 97 197 1197 197 5197 194 195 XRAAAA SRIAAA AAAAxx
+2067 5869 1 3 7 7 67 67 67 2067 2067 134 135 NBAAAA TRIAAA HHHHxx
+5600 5870 0 0 0 0 0 600 1600 600 5600 0 1 KHAAAA URIAAA OOOOxx
+7741 5871 1 1 1 1 41 741 1741 2741 7741 82 83 TLAAAA VRIAAA VVVVxx
+9925 5872 1 1 5 5 25 925 1925 4925 9925 50 51 TRAAAA WRIAAA AAAAxx
+9685 5873 1 1 5 5 85 685 1685 4685 9685 170 171 NIAAAA XRIAAA HHHHxx
+7622 5874 0 2 2 2 22 622 1622 2622 7622 44 45 EHAAAA YRIAAA OOOOxx
+6859 5875 1 3 9 19 59 859 859 1859 6859 118 119 VDAAAA ZRIAAA VVVVxx
+3094 5876 0 2 4 14 94 94 1094 3094 3094 188 189 APAAAA ASIAAA AAAAxx
+2628 5877 0 0 8 8 28 628 628 2628 2628 56 57 CXAAAA BSIAAA HHHHxx
+40 5878 0 0 0 0 40 40 40 40 40 80 81 OBAAAA CSIAAA OOOOxx
+1644 5879 0 0 4 4 44 644 1644 1644 1644 88 89 GLAAAA DSIAAA VVVVxx
+588 5880 0 0 8 8 88 588 588 588 588 176 177 QWAAAA ESIAAA AAAAxx
+7522 5881 0 2 2 2 22 522 1522 2522 7522 44 45 IDAAAA FSIAAA HHHHxx
+162 5882 0 2 2 2 62 162 162 162 162 124 125 GGAAAA GSIAAA OOOOxx
+3610 5883 0 2 0 10 10 610 1610 3610 3610 20 21 WIAAAA HSIAAA VVVVxx
+3561 5884 1 1 1 1 61 561 1561 3561 3561 122 123 ZGAAAA ISIAAA AAAAxx
+8185 5885 1 1 5 5 85 185 185 3185 8185 170 171 VCAAAA JSIAAA HHHHxx
+7237 5886 1 1 7 17 37 237 1237 2237 7237 74 75 JSAAAA KSIAAA OOOOxx
+4592 5887 0 0 2 12 92 592 592 4592 4592 184 185 QUAAAA LSIAAA VVVVxx
+7082 5888 0 2 2 2 82 82 1082 2082 7082 164 165 KMAAAA MSIAAA AAAAxx
+4719 5889 1 3 9 19 19 719 719 4719 4719 38 39 NZAAAA NSIAAA HHHHxx
+3879 5890 1 3 9 19 79 879 1879 3879 3879 158 159 FTAAAA OSIAAA OOOOxx
+1662 5891 0 2 2 2 62 662 1662 1662 1662 124 125 YLAAAA PSIAAA VVVVxx
+3995 5892 1 3 5 15 95 995 1995 3995 3995 190 191 RXAAAA QSIAAA AAAAxx
+5828 5893 0 0 8 8 28 828 1828 828 5828 56 57 EQAAAA RSIAAA HHHHxx
+4197 5894 1 1 7 17 97 197 197 4197 4197 194 195 LFAAAA SSIAAA OOOOxx
+5146 5895 0 2 6 6 46 146 1146 146 5146 92 93 YPAAAA TSIAAA VVVVxx
+753 5896 1 1 3 13 53 753 753 753 753 106 107 ZCAAAA USIAAA AAAAxx
+7064 5897 0 0 4 4 64 64 1064 2064 7064 128 129 SLAAAA VSIAAA HHHHxx
+1312 5898 0 0 2 12 12 312 1312 1312 1312 24 25 MYAAAA WSIAAA OOOOxx
+5573 5899 1 1 3 13 73 573 1573 573 5573 146 147 JGAAAA XSIAAA VVVVxx
+7634 5900 0 2 4 14 34 634 1634 2634 7634 68 69 QHAAAA YSIAAA AAAAxx
+2459 5901 1 3 9 19 59 459 459 2459 2459 118 119 PQAAAA ZSIAAA HHHHxx
+8636 5902 0 0 6 16 36 636 636 3636 8636 72 73 EUAAAA ATIAAA OOOOxx
+5318 5903 0 2 8 18 18 318 1318 318 5318 36 37 OWAAAA BTIAAA VVVVxx
+1064 5904 0 0 4 4 64 64 1064 1064 1064 128 129 YOAAAA CTIAAA AAAAxx
+9779 5905 1 3 9 19 79 779 1779 4779 9779 158 159 DMAAAA DTIAAA HHHHxx
+6512 5906 0 0 2 12 12 512 512 1512 6512 24 25 MQAAAA ETIAAA OOOOxx
+3572 5907 0 0 2 12 72 572 1572 3572 3572 144 145 KHAAAA FTIAAA VVVVxx
+816 5908 0 0 6 16 16 816 816 816 816 32 33 KFAAAA GTIAAA AAAAxx
+3978 5909 0 2 8 18 78 978 1978 3978 3978 156 157 AXAAAA HTIAAA HHHHxx
+5390 5910 0 2 0 10 90 390 1390 390 5390 180 181 IZAAAA ITIAAA OOOOxx
+4685 5911 1 1 5 5 85 685 685 4685 4685 170 171 FYAAAA JTIAAA VVVVxx
+3003 5912 1 3 3 3 3 3 1003 3003 3003 6 7 NLAAAA KTIAAA AAAAxx
+2638 5913 0 2 8 18 38 638 638 2638 2638 76 77 MXAAAA LTIAAA HHHHxx
+9716 5914 0 0 6 16 16 716 1716 4716 9716 32 33 SJAAAA MTIAAA OOOOxx
+9598 5915 0 2 8 18 98 598 1598 4598 9598 196 197 EFAAAA NTIAAA VVVVxx
+9501 5916 1 1 1 1 1 501 1501 4501 9501 2 3 LBAAAA OTIAAA AAAAxx
+1704 5917 0 0 4 4 4 704 1704 1704 1704 8 9 ONAAAA PTIAAA HHHHxx
+8609 5918 1 1 9 9 9 609 609 3609 8609 18 19 DTAAAA QTIAAA OOOOxx
+5211 5919 1 3 1 11 11 211 1211 211 5211 22 23 LSAAAA RTIAAA VVVVxx
+3605 5920 1 1 5 5 5 605 1605 3605 3605 10 11 RIAAAA STIAAA AAAAxx
+8730 5921 0 2 0 10 30 730 730 3730 8730 60 61 UXAAAA TTIAAA HHHHxx
+4208 5922 0 0 8 8 8 208 208 4208 4208 16 17 WFAAAA UTIAAA OOOOxx
+7784 5923 0 0 4 4 84 784 1784 2784 7784 168 169 KNAAAA VTIAAA VVVVxx
+7501 5924 1 1 1 1 1 501 1501 2501 7501 2 3 NCAAAA WTIAAA AAAAxx
+7862 5925 0 2 2 2 62 862 1862 2862 7862 124 125 KQAAAA XTIAAA HHHHxx
+8922 5926 0 2 2 2 22 922 922 3922 8922 44 45 EFAAAA YTIAAA OOOOxx
+3857 5927 1 1 7 17 57 857 1857 3857 3857 114 115 JSAAAA ZTIAAA VVVVxx
+6393 5928 1 1 3 13 93 393 393 1393 6393 186 187 XLAAAA AUIAAA AAAAxx
+506 5929 0 2 6 6 6 506 506 506 506 12 13 MTAAAA BUIAAA HHHHxx
+4232 5930 0 0 2 12 32 232 232 4232 4232 64 65 UGAAAA CUIAAA OOOOxx
+8991 5931 1 3 1 11 91 991 991 3991 8991 182 183 VHAAAA DUIAAA VVVVxx
+8578 5932 0 2 8 18 78 578 578 3578 8578 156 157 YRAAAA EUIAAA AAAAxx
+3235 5933 1 3 5 15 35 235 1235 3235 3235 70 71 LUAAAA FUIAAA HHHHxx
+963 5934 1 3 3 3 63 963 963 963 963 126 127 BLAAAA GUIAAA OOOOxx
+113 5935 1 1 3 13 13 113 113 113 113 26 27 JEAAAA HUIAAA VVVVxx
+8234 5936 0 2 4 14 34 234 234 3234 8234 68 69 SEAAAA IUIAAA AAAAxx
+2613 5937 1 1 3 13 13 613 613 2613 2613 26 27 NWAAAA JUIAAA HHHHxx
+5540 5938 0 0 0 0 40 540 1540 540 5540 80 81 CFAAAA KUIAAA OOOOxx
+9727 5939 1 3 7 7 27 727 1727 4727 9727 54 55 DKAAAA LUIAAA VVVVxx
+2229 5940 1 1 9 9 29 229 229 2229 2229 58 59 THAAAA MUIAAA AAAAxx
+6242 5941 0 2 2 2 42 242 242 1242 6242 84 85 CGAAAA NUIAAA HHHHxx
+2502 5942 0 2 2 2 2 502 502 2502 2502 4 5 GSAAAA OUIAAA OOOOxx
+6212 5943 0 0 2 12 12 212 212 1212 6212 24 25 YEAAAA PUIAAA VVVVxx
+3495 5944 1 3 5 15 95 495 1495 3495 3495 190 191 LEAAAA QUIAAA AAAAxx
+2364 5945 0 0 4 4 64 364 364 2364 2364 128 129 YMAAAA RUIAAA HHHHxx
+6777 5946 1 1 7 17 77 777 777 1777 6777 154 155 RAAAAA SUIAAA OOOOxx
+9811 5947 1 3 1 11 11 811 1811 4811 9811 22 23 JNAAAA TUIAAA VVVVxx
+1450 5948 0 2 0 10 50 450 1450 1450 1450 100 101 UDAAAA UUIAAA AAAAxx
+5008 5949 0 0 8 8 8 8 1008 8 5008 16 17 QKAAAA VUIAAA HHHHxx
+1318 5950 0 2 8 18 18 318 1318 1318 1318 36 37 SYAAAA WUIAAA OOOOxx
+3373 5951 1 1 3 13 73 373 1373 3373 3373 146 147 TZAAAA XUIAAA VVVVxx
+398 5952 0 2 8 18 98 398 398 398 398 196 197 IPAAAA YUIAAA AAAAxx
+3804 5953 0 0 4 4 4 804 1804 3804 3804 8 9 IQAAAA ZUIAAA HHHHxx
+9148 5954 0 0 8 8 48 148 1148 4148 9148 96 97 WNAAAA AVIAAA OOOOxx
+4382 5955 0 2 2 2 82 382 382 4382 4382 164 165 OMAAAA BVIAAA VVVVxx
+4026 5956 0 2 6 6 26 26 26 4026 4026 52 53 WYAAAA CVIAAA AAAAxx
+7804 5957 0 0 4 4 4 804 1804 2804 7804 8 9 EOAAAA DVIAAA HHHHxx
+6839 5958 1 3 9 19 39 839 839 1839 6839 78 79 BDAAAA EVIAAA OOOOxx
+3756 5959 0 0 6 16 56 756 1756 3756 3756 112 113 MOAAAA FVIAAA VVVVxx
+6734 5960 0 2 4 14 34 734 734 1734 6734 68 69 AZAAAA GVIAAA AAAAxx
+2228 5961 0 0 8 8 28 228 228 2228 2228 56 57 SHAAAA HVIAAA HHHHxx
+3273 5962 1 1 3 13 73 273 1273 3273 3273 146 147 XVAAAA IVIAAA OOOOxx
+3708 5963 0 0 8 8 8 708 1708 3708 3708 16 17 QMAAAA JVIAAA VVVVxx
+4320 5964 0 0 0 0 20 320 320 4320 4320 40 41 EKAAAA KVIAAA AAAAxx
+74 5965 0 2 4 14 74 74 74 74 74 148 149 WCAAAA LVIAAA HHHHxx
+2520 5966 0 0 0 0 20 520 520 2520 2520 40 41 YSAAAA MVIAAA OOOOxx
+9619 5967 1 3 9 19 19 619 1619 4619 9619 38 39 ZFAAAA NVIAAA VVVVxx
+1801 5968 1 1 1 1 1 801 1801 1801 1801 2 3 HRAAAA OVIAAA AAAAxx
+6399 5969 1 3 9 19 99 399 399 1399 6399 198 199 DMAAAA PVIAAA HHHHxx
+8313 5970 1 1 3 13 13 313 313 3313 8313 26 27 THAAAA QVIAAA OOOOxx
+7003 5971 1 3 3 3 3 3 1003 2003 7003 6 7 JJAAAA RVIAAA VVVVxx
+329 5972 1 1 9 9 29 329 329 329 329 58 59 RMAAAA SVIAAA AAAAxx
+9090 5973 0 2 0 10 90 90 1090 4090 9090 180 181 QLAAAA TVIAAA HHHHxx
+2299 5974 1 3 9 19 99 299 299 2299 2299 198 199 LKAAAA UVIAAA OOOOxx
+3925 5975 1 1 5 5 25 925 1925 3925 3925 50 51 ZUAAAA VVIAAA VVVVxx
+8145 5976 1 1 5 5 45 145 145 3145 8145 90 91 HBAAAA WVIAAA AAAAxx
+8561 5977 1 1 1 1 61 561 561 3561 8561 122 123 HRAAAA XVIAAA HHHHxx
+2797 5978 1 1 7 17 97 797 797 2797 2797 194 195 PDAAAA YVIAAA OOOOxx
+1451 5979 1 3 1 11 51 451 1451 1451 1451 102 103 VDAAAA ZVIAAA VVVVxx
+7977 5980 1 1 7 17 77 977 1977 2977 7977 154 155 VUAAAA AWIAAA AAAAxx
+112 5981 0 0 2 12 12 112 112 112 112 24 25 IEAAAA BWIAAA HHHHxx
+5265 5982 1 1 5 5 65 265 1265 265 5265 130 131 NUAAAA CWIAAA OOOOxx
+3819 5983 1 3 9 19 19 819 1819 3819 3819 38 39 XQAAAA DWIAAA VVVVxx
+3648 5984 0 0 8 8 48 648 1648 3648 3648 96 97 IKAAAA EWIAAA AAAAxx
+6306 5985 0 2 6 6 6 306 306 1306 6306 12 13 OIAAAA FWIAAA HHHHxx
+2385 5986 1 1 5 5 85 385 385 2385 2385 170 171 TNAAAA GWIAAA OOOOxx
+9084 5987 0 0 4 4 84 84 1084 4084 9084 168 169 KLAAAA HWIAAA VVVVxx
+4499 5988 1 3 9 19 99 499 499 4499 4499 198 199 BRAAAA IWIAAA AAAAxx
+1154 5989 0 2 4 14 54 154 1154 1154 1154 108 109 KSAAAA JWIAAA HHHHxx
+6800 5990 0 0 0 0 0 800 800 1800 6800 0 1 OBAAAA KWIAAA OOOOxx
+8049 5991 1 1 9 9 49 49 49 3049 8049 98 99 PXAAAA LWIAAA VVVVxx
+3733 5992 1 1 3 13 33 733 1733 3733 3733 66 67 PNAAAA MWIAAA AAAAxx
+8496 5993 0 0 6 16 96 496 496 3496 8496 192 193 UOAAAA NWIAAA HHHHxx
+9952 5994 0 0 2 12 52 952 1952 4952 9952 104 105 USAAAA OWIAAA OOOOxx
+9792 5995 0 0 2 12 92 792 1792 4792 9792 184 185 QMAAAA PWIAAA VVVVxx
+5081 5996 1 1 1 1 81 81 1081 81 5081 162 163 LNAAAA QWIAAA AAAAxx
+7908 5997 0 0 8 8 8 908 1908 2908 7908 16 17 ESAAAA RWIAAA HHHHxx
+5398 5998 0 2 8 18 98 398 1398 398 5398 196 197 QZAAAA SWIAAA OOOOxx
+8423 5999 1 3 3 3 23 423 423 3423 8423 46 47 ZLAAAA TWIAAA VVVVxx
+3362 6000 0 2 2 2 62 362 1362 3362 3362 124 125 IZAAAA UWIAAA AAAAxx
+7767 6001 1 3 7 7 67 767 1767 2767 7767 134 135 TMAAAA VWIAAA HHHHxx
+7063 6002 1 3 3 3 63 63 1063 2063 7063 126 127 RLAAAA WWIAAA OOOOxx
+8350 6003 0 2 0 10 50 350 350 3350 8350 100 101 EJAAAA XWIAAA VVVVxx
+6779 6004 1 3 9 19 79 779 779 1779 6779 158 159 TAAAAA YWIAAA AAAAxx
+5742 6005 0 2 2 2 42 742 1742 742 5742 84 85 WMAAAA ZWIAAA HHHHxx
+9045 6006 1 1 5 5 45 45 1045 4045 9045 90 91 XJAAAA AXIAAA OOOOxx
+8792 6007 0 0 2 12 92 792 792 3792 8792 184 185 EAAAAA BXIAAA VVVVxx
+8160 6008 0 0 0 0 60 160 160 3160 8160 120 121 WBAAAA CXIAAA AAAAxx
+3061 6009 1 1 1 1 61 61 1061 3061 3061 122 123 TNAAAA DXIAAA HHHHxx
+4721 6010 1 1 1 1 21 721 721 4721 4721 42 43 PZAAAA EXIAAA OOOOxx
+9817 6011 1 1 7 17 17 817 1817 4817 9817 34 35 PNAAAA FXIAAA VVVVxx
+9257 6012 1 1 7 17 57 257 1257 4257 9257 114 115 BSAAAA GXIAAA AAAAxx
+7779 6013 1 3 9 19 79 779 1779 2779 7779 158 159 FNAAAA HXIAAA HHHHxx
+2663 6014 1 3 3 3 63 663 663 2663 2663 126 127 LYAAAA IXIAAA OOOOxx
+3885 6015 1 1 5 5 85 885 1885 3885 3885 170 171 LTAAAA JXIAAA VVVVxx
+9469 6016 1 1 9 9 69 469 1469 4469 9469 138 139 FAAAAA KXIAAA AAAAxx
+6766 6017 0 2 6 6 66 766 766 1766 6766 132 133 GAAAAA LXIAAA HHHHxx
+7173 6018 1 1 3 13 73 173 1173 2173 7173 146 147 XPAAAA MXIAAA OOOOxx
+4709 6019 1 1 9 9 9 709 709 4709 4709 18 19 DZAAAA NXIAAA VVVVxx
+4210 6020 0 2 0 10 10 210 210 4210 4210 20 21 YFAAAA OXIAAA AAAAxx
+3715 6021 1 3 5 15 15 715 1715 3715 3715 30 31 XMAAAA PXIAAA HHHHxx
+5089 6022 1 1 9 9 89 89 1089 89 5089 178 179 TNAAAA QXIAAA OOOOxx
+1639 6023 1 3 9 19 39 639 1639 1639 1639 78 79 BLAAAA RXIAAA VVVVxx
+5757 6024 1 1 7 17 57 757 1757 757 5757 114 115 LNAAAA SXIAAA AAAAxx
+3545 6025 1 1 5 5 45 545 1545 3545 3545 90 91 JGAAAA TXIAAA HHHHxx
+709 6026 1 1 9 9 9 709 709 709 709 18 19 HBAAAA UXIAAA OOOOxx
+6519 6027 1 3 9 19 19 519 519 1519 6519 38 39 TQAAAA VXIAAA VVVVxx
+4341 6028 1 1 1 1 41 341 341 4341 4341 82 83 ZKAAAA WXIAAA AAAAxx
+2381 6029 1 1 1 1 81 381 381 2381 2381 162 163 PNAAAA XXIAAA HHHHxx
+7215 6030 1 3 5 15 15 215 1215 2215 7215 30 31 NRAAAA YXIAAA OOOOxx
+9323 6031 1 3 3 3 23 323 1323 4323 9323 46 47 PUAAAA ZXIAAA VVVVxx
+3593 6032 1 1 3 13 93 593 1593 3593 3593 186 187 FIAAAA AYIAAA AAAAxx
+3123 6033 1 3 3 3 23 123 1123 3123 3123 46 47 DQAAAA BYIAAA HHHHxx
+8673 6034 1 1 3 13 73 673 673 3673 8673 146 147 PVAAAA CYIAAA OOOOxx
+5094 6035 0 2 4 14 94 94 1094 94 5094 188 189 YNAAAA DYIAAA VVVVxx
+6477 6036 1 1 7 17 77 477 477 1477 6477 154 155 DPAAAA EYIAAA AAAAxx
+9734 6037 0 2 4 14 34 734 1734 4734 9734 68 69 KKAAAA FYIAAA HHHHxx
+2998 6038 0 2 8 18 98 998 998 2998 2998 196 197 ILAAAA GYIAAA OOOOxx
+7807 6039 1 3 7 7 7 807 1807 2807 7807 14 15 HOAAAA HYIAAA VVVVxx
+5739 6040 1 3 9 19 39 739 1739 739 5739 78 79 TMAAAA IYIAAA AAAAxx
+138 6041 0 2 8 18 38 138 138 138 138 76 77 IFAAAA JYIAAA HHHHxx
+2403 6042 1 3 3 3 3 403 403 2403 2403 6 7 LOAAAA KYIAAA OOOOxx
+2484 6043 0 0 4 4 84 484 484 2484 2484 168 169 ORAAAA LYIAAA VVVVxx
+2805 6044 1 1 5 5 5 805 805 2805 2805 10 11 XDAAAA MYIAAA AAAAxx
+5189 6045 1 1 9 9 89 189 1189 189 5189 178 179 PRAAAA NYIAAA HHHHxx
+8336 6046 0 0 6 16 36 336 336 3336 8336 72 73 QIAAAA OYIAAA OOOOxx
+5241 6047 1 1 1 1 41 241 1241 241 5241 82 83 PTAAAA PYIAAA VVVVxx
+2612 6048 0 0 2 12 12 612 612 2612 2612 24 25 MWAAAA QYIAAA AAAAxx
+2571 6049 1 3 1 11 71 571 571 2571 2571 142 143 XUAAAA RYIAAA HHHHxx
+926 6050 0 2 6 6 26 926 926 926 926 52 53 QJAAAA SYIAAA OOOOxx
+337 6051 1 1 7 17 37 337 337 337 337 74 75 ZMAAAA TYIAAA VVVVxx
+2821 6052 1 1 1 1 21 821 821 2821 2821 42 43 NEAAAA UYIAAA AAAAxx
+2658 6053 0 2 8 18 58 658 658 2658 2658 116 117 GYAAAA VYIAAA HHHHxx
+9054 6054 0 2 4 14 54 54 1054 4054 9054 108 109 GKAAAA WYIAAA OOOOxx
+5492 6055 0 0 2 12 92 492 1492 492 5492 184 185 GDAAAA XYIAAA VVVVxx
+7313 6056 1 1 3 13 13 313 1313 2313 7313 26 27 HVAAAA YYIAAA AAAAxx
+75 6057 1 3 5 15 75 75 75 75 75 150 151 XCAAAA ZYIAAA HHHHxx
+5489 6058 1 1 9 9 89 489 1489 489 5489 178 179 DDAAAA AZIAAA OOOOxx
+8413 6059 1 1 3 13 13 413 413 3413 8413 26 27 PLAAAA BZIAAA VVVVxx
+3693 6060 1 1 3 13 93 693 1693 3693 3693 186 187 BMAAAA CZIAAA AAAAxx
+9820 6061 0 0 0 0 20 820 1820 4820 9820 40 41 SNAAAA DZIAAA HHHHxx
+8157 6062 1 1 7 17 57 157 157 3157 8157 114 115 TBAAAA EZIAAA OOOOxx
+4161 6063 1 1 1 1 61 161 161 4161 4161 122 123 BEAAAA FZIAAA VVVVxx
+8339 6064 1 3 9 19 39 339 339 3339 8339 78 79 TIAAAA GZIAAA AAAAxx
+4141 6065 1 1 1 1 41 141 141 4141 4141 82 83 HDAAAA HZIAAA HHHHxx
+9001 6066 1 1 1 1 1 1 1001 4001 9001 2 3 FIAAAA IZIAAA OOOOxx
+8247 6067 1 3 7 7 47 247 247 3247 8247 94 95 FFAAAA JZIAAA VVVVxx
+1182 6068 0 2 2 2 82 182 1182 1182 1182 164 165 MTAAAA KZIAAA AAAAxx
+9876 6069 0 0 6 16 76 876 1876 4876 9876 152 153 WPAAAA LZIAAA HHHHxx
+4302 6070 0 2 2 2 2 302 302 4302 4302 4 5 MJAAAA MZIAAA OOOOxx
+6674 6071 0 2 4 14 74 674 674 1674 6674 148 149 SWAAAA NZIAAA VVVVxx
+4214 6072 0 2 4 14 14 214 214 4214 4214 28 29 CGAAAA OZIAAA AAAAxx
+5584 6073 0 0 4 4 84 584 1584 584 5584 168 169 UGAAAA PZIAAA HHHHxx
+265 6074 1 1 5 5 65 265 265 265 265 130 131 FKAAAA QZIAAA OOOOxx
+9207 6075 1 3 7 7 7 207 1207 4207 9207 14 15 DQAAAA RZIAAA VVVVxx
+9434 6076 0 2 4 14 34 434 1434 4434 9434 68 69 WYAAAA SZIAAA AAAAxx
+2921 6077 1 1 1 1 21 921 921 2921 2921 42 43 JIAAAA TZIAAA HHHHxx
+9355 6078 1 3 5 15 55 355 1355 4355 9355 110 111 VVAAAA UZIAAA OOOOxx
+8538 6079 0 2 8 18 38 538 538 3538 8538 76 77 KQAAAA VZIAAA VVVVxx
+4559 6080 1 3 9 19 59 559 559 4559 4559 118 119 JTAAAA WZIAAA AAAAxx
+9175 6081 1 3 5 15 75 175 1175 4175 9175 150 151 XOAAAA XZIAAA HHHHxx
+4489 6082 1 1 9 9 89 489 489 4489 4489 178 179 RQAAAA YZIAAA OOOOxx
+1485 6083 1 1 5 5 85 485 1485 1485 1485 170 171 DFAAAA ZZIAAA VVVVxx
+8853 6084 1 1 3 13 53 853 853 3853 8853 106 107 NCAAAA AAJAAA AAAAxx
+9143 6085 1 3 3 3 43 143 1143 4143 9143 86 87 RNAAAA BAJAAA HHHHxx
+9551 6086 1 3 1 11 51 551 1551 4551 9551 102 103 JDAAAA CAJAAA OOOOxx
+49 6087 1 1 9 9 49 49 49 49 49 98 99 XBAAAA DAJAAA VVVVxx
+8351 6088 1 3 1 11 51 351 351 3351 8351 102 103 FJAAAA EAJAAA AAAAxx
+9748 6089 0 0 8 8 48 748 1748 4748 9748 96 97 YKAAAA FAJAAA HHHHxx
+4536 6090 0 0 6 16 36 536 536 4536 4536 72 73 MSAAAA GAJAAA OOOOxx
+930 6091 0 2 0 10 30 930 930 930 930 60 61 UJAAAA HAJAAA VVVVxx
+2206 6092 0 2 6 6 6 206 206 2206 2206 12 13 WGAAAA IAJAAA AAAAxx
+8004 6093 0 0 4 4 4 4 4 3004 8004 8 9 WVAAAA JAJAAA HHHHxx
+219 6094 1 3 9 19 19 219 219 219 219 38 39 LIAAAA KAJAAA OOOOxx
+2724 6095 0 0 4 4 24 724 724 2724 2724 48 49 UAAAAA LAJAAA VVVVxx
+4868 6096 0 0 8 8 68 868 868 4868 4868 136 137 GFAAAA MAJAAA AAAAxx
+5952 6097 0 0 2 12 52 952 1952 952 5952 104 105 YUAAAA NAJAAA HHHHxx
+2094 6098 0 2 4 14 94 94 94 2094 2094 188 189 OCAAAA OAJAAA OOOOxx
+5707 6099 1 3 7 7 7 707 1707 707 5707 14 15 NLAAAA PAJAAA VVVVxx
+5200 6100 0 0 0 0 0 200 1200 200 5200 0 1 ASAAAA QAJAAA AAAAxx
+967 6101 1 3 7 7 67 967 967 967 967 134 135 FLAAAA RAJAAA HHHHxx
+1982 6102 0 2 2 2 82 982 1982 1982 1982 164 165 GYAAAA SAJAAA OOOOxx
+3410 6103 0 2 0 10 10 410 1410 3410 3410 20 21 EBAAAA TAJAAA VVVVxx
+174 6104 0 2 4 14 74 174 174 174 174 148 149 SGAAAA UAJAAA AAAAxx
+9217 6105 1 1 7 17 17 217 1217 4217 9217 34 35 NQAAAA VAJAAA HHHHxx
+9103 6106 1 3 3 3 3 103 1103 4103 9103 6 7 DMAAAA WAJAAA OOOOxx
+868 6107 0 0 8 8 68 868 868 868 868 136 137 KHAAAA XAJAAA VVVVxx
+8261 6108 1 1 1 1 61 261 261 3261 8261 122 123 TFAAAA YAJAAA AAAAxx
+2720 6109 0 0 0 0 20 720 720 2720 2720 40 41 QAAAAA ZAJAAA HHHHxx
+2999 6110 1 3 9 19 99 999 999 2999 2999 198 199 JLAAAA ABJAAA OOOOxx
+769 6111 1 1 9 9 69 769 769 769 769 138 139 PDAAAA BBJAAA VVVVxx
+4533 6112 1 1 3 13 33 533 533 4533 4533 66 67 JSAAAA CBJAAA AAAAxx
+2030 6113 0 2 0 10 30 30 30 2030 2030 60 61 CAAAAA DBJAAA HHHHxx
+5824 6114 0 0 4 4 24 824 1824 824 5824 48 49 AQAAAA EBJAAA OOOOxx
+2328 6115 0 0 8 8 28 328 328 2328 2328 56 57 OLAAAA FBJAAA VVVVxx
+9970 6116 0 2 0 10 70 970 1970 4970 9970 140 141 MTAAAA GBJAAA AAAAxx
+3192 6117 0 0 2 12 92 192 1192 3192 3192 184 185 USAAAA HBJAAA HHHHxx
+3387 6118 1 3 7 7 87 387 1387 3387 3387 174 175 HAAAAA IBJAAA OOOOxx
+1936 6119 0 0 6 16 36 936 1936 1936 1936 72 73 MWAAAA JBJAAA VVVVxx
+6934 6120 0 2 4 14 34 934 934 1934 6934 68 69 SGAAAA KBJAAA AAAAxx
+5615 6121 1 3 5 15 15 615 1615 615 5615 30 31 ZHAAAA LBJAAA HHHHxx
+2241 6122 1 1 1 1 41 241 241 2241 2241 82 83 FIAAAA MBJAAA OOOOxx
+1842 6123 0 2 2 2 42 842 1842 1842 1842 84 85 WSAAAA NBJAAA VVVVxx
+8044 6124 0 0 4 4 44 44 44 3044 8044 88 89 KXAAAA OBJAAA AAAAxx
+8902 6125 0 2 2 2 2 902 902 3902 8902 4 5 KEAAAA PBJAAA HHHHxx
+4519 6126 1 3 9 19 19 519 519 4519 4519 38 39 VRAAAA QBJAAA OOOOxx
+492 6127 0 0 2 12 92 492 492 492 492 184 185 YSAAAA RBJAAA VVVVxx
+2694 6128 0 2 4 14 94 694 694 2694 2694 188 189 QZAAAA SBJAAA AAAAxx
+5861 6129 1 1 1 1 61 861 1861 861 5861 122 123 LRAAAA TBJAAA HHHHxx
+2104 6130 0 0 4 4 4 104 104 2104 2104 8 9 YCAAAA UBJAAA OOOOxx
+5376 6131 0 0 6 16 76 376 1376 376 5376 152 153 UYAAAA VBJAAA VVVVxx
+3147 6132 1 3 7 7 47 147 1147 3147 3147 94 95 BRAAAA WBJAAA AAAAxx
+9880 6133 0 0 0 0 80 880 1880 4880 9880 160 161 AQAAAA XBJAAA HHHHxx
+6171 6134 1 3 1 11 71 171 171 1171 6171 142 143 JDAAAA YBJAAA OOOOxx
+1850 6135 0 2 0 10 50 850 1850 1850 1850 100 101 ETAAAA ZBJAAA VVVVxx
+1775 6136 1 3 5 15 75 775 1775 1775 1775 150 151 HQAAAA ACJAAA AAAAxx
+9261 6137 1 1 1 1 61 261 1261 4261 9261 122 123 FSAAAA BCJAAA HHHHxx
+9648 6138 0 0 8 8 48 648 1648 4648 9648 96 97 CHAAAA CCJAAA OOOOxx
+7846 6139 0 2 6 6 46 846 1846 2846 7846 92 93 UPAAAA DCJAAA VVVVxx
+1446 6140 0 2 6 6 46 446 1446 1446 1446 92 93 QDAAAA ECJAAA AAAAxx
+3139 6141 1 3 9 19 39 139 1139 3139 3139 78 79 TQAAAA FCJAAA HHHHxx
+6142 6142 0 2 2 2 42 142 142 1142 6142 84 85 GCAAAA GCJAAA OOOOxx
+5812 6143 0 0 2 12 12 812 1812 812 5812 24 25 OPAAAA HCJAAA VVVVxx
+6728 6144 0 0 8 8 28 728 728 1728 6728 56 57 UYAAAA ICJAAA AAAAxx
+4428 6145 0 0 8 8 28 428 428 4428 4428 56 57 IOAAAA JCJAAA HHHHxx
+502 6146 0 2 2 2 2 502 502 502 502 4 5 ITAAAA KCJAAA OOOOxx
+2363 6147 1 3 3 3 63 363 363 2363 2363 126 127 XMAAAA LCJAAA VVVVxx
+3808 6148 0 0 8 8 8 808 1808 3808 3808 16 17 MQAAAA MCJAAA AAAAxx
+1010 6149 0 2 0 10 10 10 1010 1010 1010 20 21 WMAAAA NCJAAA HHHHxx
+9565 6150 1 1 5 5 65 565 1565 4565 9565 130 131 XDAAAA OCJAAA OOOOxx
+1587 6151 1 3 7 7 87 587 1587 1587 1587 174 175 BJAAAA PCJAAA VVVVxx
+1474 6152 0 2 4 14 74 474 1474 1474 1474 148 149 SEAAAA QCJAAA AAAAxx
+6215 6153 1 3 5 15 15 215 215 1215 6215 30 31 BFAAAA RCJAAA HHHHxx
+2395 6154 1 3 5 15 95 395 395 2395 2395 190 191 DOAAAA SCJAAA OOOOxx
+8753 6155 1 1 3 13 53 753 753 3753 8753 106 107 RYAAAA TCJAAA VVVVxx
+2446 6156 0 2 6 6 46 446 446 2446 2446 92 93 CQAAAA UCJAAA AAAAxx
+60 6157 0 0 0 0 60 60 60 60 60 120 121 ICAAAA VCJAAA HHHHxx
+982 6158 0 2 2 2 82 982 982 982 982 164 165 ULAAAA WCJAAA OOOOxx
+6489 6159 1 1 9 9 89 489 489 1489 6489 178 179 PPAAAA XCJAAA VVVVxx
+5334 6160 0 2 4 14 34 334 1334 334 5334 68 69 EXAAAA YCJAAA AAAAxx
+8540 6161 0 0 0 0 40 540 540 3540 8540 80 81 MQAAAA ZCJAAA HHHHxx
+490 6162 0 2 0 10 90 490 490 490 490 180 181 WSAAAA ADJAAA OOOOxx
+6763 6163 1 3 3 3 63 763 763 1763 6763 126 127 DAAAAA BDJAAA VVVVxx
+8273 6164 1 1 3 13 73 273 273 3273 8273 146 147 FGAAAA CDJAAA AAAAxx
+8327 6165 1 3 7 7 27 327 327 3327 8327 54 55 HIAAAA DDJAAA HHHHxx
+8541 6166 1 1 1 1 41 541 541 3541 8541 82 83 NQAAAA EDJAAA OOOOxx
+3459 6167 1 3 9 19 59 459 1459 3459 3459 118 119 BDAAAA FDJAAA VVVVxx
+5557 6168 1 1 7 17 57 557 1557 557 5557 114 115 TFAAAA GDJAAA AAAAxx
+158 6169 0 2 8 18 58 158 158 158 158 116 117 CGAAAA HDJAAA HHHHxx
+1741 6170 1 1 1 1 41 741 1741 1741 1741 82 83 ZOAAAA IDJAAA OOOOxx
+8385 6171 1 1 5 5 85 385 385 3385 8385 170 171 NKAAAA JDJAAA VVVVxx
+617 6172 1 1 7 17 17 617 617 617 617 34 35 TXAAAA KDJAAA AAAAxx
+3560 6173 0 0 0 0 60 560 1560 3560 3560 120 121 YGAAAA LDJAAA HHHHxx
+5216 6174 0 0 6 16 16 216 1216 216 5216 32 33 QSAAAA MDJAAA OOOOxx
+8443 6175 1 3 3 3 43 443 443 3443 8443 86 87 TMAAAA NDJAAA VVVVxx
+2700 6176 0 0 0 0 0 700 700 2700 2700 0 1 WZAAAA ODJAAA AAAAxx
+3661 6177 1 1 1 1 61 661 1661 3661 3661 122 123 VKAAAA PDJAAA HHHHxx
+4875 6178 1 3 5 15 75 875 875 4875 4875 150 151 NFAAAA QDJAAA OOOOxx
+6721 6179 1 1 1 1 21 721 721 1721 6721 42 43 NYAAAA RDJAAA VVVVxx
+3659 6180 1 3 9 19 59 659 1659 3659 3659 118 119 TKAAAA SDJAAA AAAAxx
+8944 6181 0 0 4 4 44 944 944 3944 8944 88 89 AGAAAA TDJAAA HHHHxx
+9133 6182 1 1 3 13 33 133 1133 4133 9133 66 67 HNAAAA UDJAAA OOOOxx
+9882 6183 0 2 2 2 82 882 1882 4882 9882 164 165 CQAAAA VDJAAA VVVVxx
+2102 6184 0 2 2 2 2 102 102 2102 2102 4 5 WCAAAA WDJAAA AAAAxx
+9445 6185 1 1 5 5 45 445 1445 4445 9445 90 91 HZAAAA XDJAAA HHHHxx
+5559 6186 1 3 9 19 59 559 1559 559 5559 118 119 VFAAAA YDJAAA OOOOxx
+6096 6187 0 0 6 16 96 96 96 1096 6096 192 193 MAAAAA ZDJAAA VVVVxx
+9336 6188 0 0 6 16 36 336 1336 4336 9336 72 73 CVAAAA AEJAAA AAAAxx
+2162 6189 0 2 2 2 62 162 162 2162 2162 124 125 EFAAAA BEJAAA HHHHxx
+7459 6190 1 3 9 19 59 459 1459 2459 7459 118 119 XAAAAA CEJAAA OOOOxx
+3248 6191 0 0 8 8 48 248 1248 3248 3248 96 97 YUAAAA DEJAAA VVVVxx
+9539 6192 1 3 9 19 39 539 1539 4539 9539 78 79 XCAAAA EEJAAA AAAAxx
+4449 6193 1 1 9 9 49 449 449 4449 4449 98 99 DPAAAA FEJAAA HHHHxx
+2809 6194 1 1 9 9 9 809 809 2809 2809 18 19 BEAAAA GEJAAA OOOOxx
+7058 6195 0 2 8 18 58 58 1058 2058 7058 116 117 MLAAAA HEJAAA VVVVxx
+3512 6196 0 0 2 12 12 512 1512 3512 3512 24 25 CFAAAA IEJAAA AAAAxx
+2802 6197 0 2 2 2 2 802 802 2802 2802 4 5 UDAAAA JEJAAA HHHHxx
+6289 6198 1 1 9 9 89 289 289 1289 6289 178 179 XHAAAA KEJAAA OOOOxx
+1947 6199 1 3 7 7 47 947 1947 1947 1947 94 95 XWAAAA LEJAAA VVVVxx
+9572 6200 0 0 2 12 72 572 1572 4572 9572 144 145 EEAAAA MEJAAA AAAAxx
+2356 6201 0 0 6 16 56 356 356 2356 2356 112 113 QMAAAA NEJAAA HHHHxx
+3039 6202 1 3 9 19 39 39 1039 3039 3039 78 79 XMAAAA OEJAAA OOOOxx
+9452 6203 0 0 2 12 52 452 1452 4452 9452 104 105 OZAAAA PEJAAA VVVVxx
+6328 6204 0 0 8 8 28 328 328 1328 6328 56 57 KJAAAA QEJAAA AAAAxx
+7661 6205 1 1 1 1 61 661 1661 2661 7661 122 123 RIAAAA REJAAA HHHHxx
+2566 6206 0 2 6 6 66 566 566 2566 2566 132 133 SUAAAA SEJAAA OOOOxx
+6095 6207 1 3 5 15 95 95 95 1095 6095 190 191 LAAAAA TEJAAA VVVVxx
+6367 6208 1 3 7 7 67 367 367 1367 6367 134 135 XKAAAA UEJAAA AAAAxx
+3368 6209 0 0 8 8 68 368 1368 3368 3368 136 137 OZAAAA VEJAAA HHHHxx
+5567 6210 1 3 7 7 67 567 1567 567 5567 134 135 DGAAAA WEJAAA OOOOxx
+9834 6211 0 2 4 14 34 834 1834 4834 9834 68 69 GOAAAA XEJAAA VVVVxx
+9695 6212 1 3 5 15 95 695 1695 4695 9695 190 191 XIAAAA YEJAAA AAAAxx
+7291 6213 1 3 1 11 91 291 1291 2291 7291 182 183 LUAAAA ZEJAAA HHHHxx
+4806 6214 0 2 6 6 6 806 806 4806 4806 12 13 WCAAAA AFJAAA OOOOxx
+2000 6215 0 0 0 0 0 0 0 2000 2000 0 1 YYAAAA BFJAAA VVVVxx
+6817 6216 1 1 7 17 17 817 817 1817 6817 34 35 FCAAAA CFJAAA AAAAxx
+8487 6217 1 3 7 7 87 487 487 3487 8487 174 175 LOAAAA DFJAAA HHHHxx
+3245 6218 1 1 5 5 45 245 1245 3245 3245 90 91 VUAAAA EFJAAA OOOOxx
+632 6219 0 0 2 12 32 632 632 632 632 64 65 IYAAAA FFJAAA VVVVxx
+8067 6220 1 3 7 7 67 67 67 3067 8067 134 135 HYAAAA GFJAAA AAAAxx
+7140 6221 0 0 0 0 40 140 1140 2140 7140 80 81 QOAAAA HFJAAA HHHHxx
+6802 6222 0 2 2 2 2 802 802 1802 6802 4 5 QBAAAA IFJAAA OOOOxx
+3980 6223 0 0 0 0 80 980 1980 3980 3980 160 161 CXAAAA JFJAAA VVVVxx
+1321 6224 1 1 1 1 21 321 1321 1321 1321 42 43 VYAAAA KFJAAA AAAAxx
+2273 6225 1 1 3 13 73 273 273 2273 2273 146 147 LJAAAA LFJAAA HHHHxx
+6787 6226 1 3 7 7 87 787 787 1787 6787 174 175 BBAAAA MFJAAA OOOOxx
+9480 6227 0 0 0 0 80 480 1480 4480 9480 160 161 QAAAAA NFJAAA VVVVxx
+9404 6228 0 0 4 4 4 404 1404 4404 9404 8 9 SXAAAA OFJAAA AAAAxx
+3914 6229 0 2 4 14 14 914 1914 3914 3914 28 29 OUAAAA PFJAAA HHHHxx
+5507 6230 1 3 7 7 7 507 1507 507 5507 14 15 VDAAAA QFJAAA OOOOxx
+1813 6231 1 1 3 13 13 813 1813 1813 1813 26 27 TRAAAA RFJAAA VVVVxx
+1999 6232 1 3 9 19 99 999 1999 1999 1999 198 199 XYAAAA SFJAAA AAAAxx
+3848 6233 0 0 8 8 48 848 1848 3848 3848 96 97 ASAAAA TFJAAA HHHHxx
+9693 6234 1 1 3 13 93 693 1693 4693 9693 186 187 VIAAAA UFJAAA OOOOxx
+1353 6235 1 1 3 13 53 353 1353 1353 1353 106 107 BAAAAA VFJAAA VVVVxx
+7218 6236 0 2 8 18 18 218 1218 2218 7218 36 37 QRAAAA WFJAAA AAAAxx
+8223 6237 1 3 3 3 23 223 223 3223 8223 46 47 HEAAAA XFJAAA HHHHxx
+9982 6238 0 2 2 2 82 982 1982 4982 9982 164 165 YTAAAA YFJAAA OOOOxx
+8799 6239 1 3 9 19 99 799 799 3799 8799 198 199 LAAAAA ZFJAAA VVVVxx
+8929 6240 1 1 9 9 29 929 929 3929 8929 58 59 LFAAAA AGJAAA AAAAxx
+4626 6241 0 2 6 6 26 626 626 4626 4626 52 53 YVAAAA BGJAAA HHHHxx
+7958 6242 0 2 8 18 58 958 1958 2958 7958 116 117 CUAAAA CGJAAA OOOOxx
+3743 6243 1 3 3 3 43 743 1743 3743 3743 86 87 ZNAAAA DGJAAA VVVVxx
+8165 6244 1 1 5 5 65 165 165 3165 8165 130 131 BCAAAA EGJAAA AAAAxx
+7899 6245 1 3 9 19 99 899 1899 2899 7899 198 199 VRAAAA FGJAAA HHHHxx
+8698 6246 0 2 8 18 98 698 698 3698 8698 196 197 OWAAAA GGJAAA OOOOxx
+9270 6247 0 2 0 10 70 270 1270 4270 9270 140 141 OSAAAA HGJAAA VVVVxx
+6348 6248 0 0 8 8 48 348 348 1348 6348 96 97 EKAAAA IGJAAA AAAAxx
+6999 6249 1 3 9 19 99 999 999 1999 6999 198 199 FJAAAA JGJAAA HHHHxx
+8467 6250 1 3 7 7 67 467 467 3467 8467 134 135 RNAAAA KGJAAA OOOOxx
+3907 6251 1 3 7 7 7 907 1907 3907 3907 14 15 HUAAAA LGJAAA VVVVxx
+4738 6252 0 2 8 18 38 738 738 4738 4738 76 77 GAAAAA MGJAAA AAAAxx
+248 6253 0 0 8 8 48 248 248 248 248 96 97 OJAAAA NGJAAA HHHHxx
+8769 6254 1 1 9 9 69 769 769 3769 8769 138 139 HZAAAA OGJAAA OOOOxx
+9922 6255 0 2 2 2 22 922 1922 4922 9922 44 45 QRAAAA PGJAAA VVVVxx
+778 6256 0 2 8 18 78 778 778 778 778 156 157 YDAAAA QGJAAA AAAAxx
+1233 6257 1 1 3 13 33 233 1233 1233 1233 66 67 LVAAAA RGJAAA HHHHxx
+1183 6258 1 3 3 3 83 183 1183 1183 1183 166 167 NTAAAA SGJAAA OOOOxx
+2838 6259 0 2 8 18 38 838 838 2838 2838 76 77 EFAAAA TGJAAA VVVVxx
+3096 6260 0 0 6 16 96 96 1096 3096 3096 192 193 CPAAAA UGJAAA AAAAxx
+8566 6261 0 2 6 6 66 566 566 3566 8566 132 133 MRAAAA VGJAAA HHHHxx
+7635 6262 1 3 5 15 35 635 1635 2635 7635 70 71 RHAAAA WGJAAA OOOOxx
+5428 6263 0 0 8 8 28 428 1428 428 5428 56 57 UAAAAA XGJAAA VVVVxx
+7430 6264 0 2 0 10 30 430 1430 2430 7430 60 61 UZAAAA YGJAAA AAAAxx
+7210 6265 0 2 0 10 10 210 1210 2210 7210 20 21 IRAAAA ZGJAAA HHHHxx
+4485 6266 1 1 5 5 85 485 485 4485 4485 170 171 NQAAAA AHJAAA OOOOxx
+9623 6267 1 3 3 3 23 623 1623 4623 9623 46 47 DGAAAA BHJAAA VVVVxx
+3670 6268 0 2 0 10 70 670 1670 3670 3670 140 141 ELAAAA CHJAAA AAAAxx
+1575 6269 1 3 5 15 75 575 1575 1575 1575 150 151 PIAAAA DHJAAA HHHHxx
+5874 6270 0 2 4 14 74 874 1874 874 5874 148 149 YRAAAA EHJAAA OOOOxx
+673 6271 1 1 3 13 73 673 673 673 673 146 147 XZAAAA FHJAAA VVVVxx
+9712 6272 0 0 2 12 12 712 1712 4712 9712 24 25 OJAAAA GHJAAA AAAAxx
+7729 6273 1 1 9 9 29 729 1729 2729 7729 58 59 HLAAAA HHJAAA HHHHxx
+4318 6274 0 2 8 18 18 318 318 4318 4318 36 37 CKAAAA IHJAAA OOOOxx
+4143 6275 1 3 3 3 43 143 143 4143 4143 86 87 JDAAAA JHJAAA VVVVxx
+4932 6276 0 0 2 12 32 932 932 4932 4932 64 65 SHAAAA KHJAAA AAAAxx
+5835 6277 1 3 5 15 35 835 1835 835 5835 70 71 LQAAAA LHJAAA HHHHxx
+4966 6278 0 2 6 6 66 966 966 4966 4966 132 133 AJAAAA MHJAAA OOOOxx
+6711 6279 1 3 1 11 11 711 711 1711 6711 22 23 DYAAAA NHJAAA VVVVxx
+3990 6280 0 2 0 10 90 990 1990 3990 3990 180 181 MXAAAA OHJAAA AAAAxx
+990 6281 0 2 0 10 90 990 990 990 990 180 181 CMAAAA PHJAAA HHHHxx
+220 6282 0 0 0 0 20 220 220 220 220 40 41 MIAAAA QHJAAA OOOOxx
+5693 6283 1 1 3 13 93 693 1693 693 5693 186 187 ZKAAAA RHJAAA VVVVxx
+3662 6284 0 2 2 2 62 662 1662 3662 3662 124 125 WKAAAA SHJAAA AAAAxx
+7844 6285 0 0 4 4 44 844 1844 2844 7844 88 89 SPAAAA THJAAA HHHHxx
+5515 6286 1 3 5 15 15 515 1515 515 5515 30 31 DEAAAA UHJAAA OOOOxx
+5551 6287 1 3 1 11 51 551 1551 551 5551 102 103 NFAAAA VHJAAA VVVVxx
+2358 6288 0 2 8 18 58 358 358 2358 2358 116 117 SMAAAA WHJAAA AAAAxx
+8977 6289 1 1 7 17 77 977 977 3977 8977 154 155 HHAAAA XHJAAA HHHHxx
+7040 6290 0 0 0 0 40 40 1040 2040 7040 80 81 UKAAAA YHJAAA OOOOxx
+105 6291 1 1 5 5 5 105 105 105 105 10 11 BEAAAA ZHJAAA VVVVxx
+4496 6292 0 0 6 16 96 496 496 4496 4496 192 193 YQAAAA AIJAAA AAAAxx
+2254 6293 0 2 4 14 54 254 254 2254 2254 108 109 SIAAAA BIJAAA HHHHxx
+411 6294 1 3 1 11 11 411 411 411 411 22 23 VPAAAA CIJAAA OOOOxx
+2373 6295 1 1 3 13 73 373 373 2373 2373 146 147 HNAAAA DIJAAA VVVVxx
+3477 6296 1 1 7 17 77 477 1477 3477 3477 154 155 TDAAAA EIJAAA AAAAxx
+8964 6297 0 0 4 4 64 964 964 3964 8964 128 129 UGAAAA FIJAAA HHHHxx
+8471 6298 1 3 1 11 71 471 471 3471 8471 142 143 VNAAAA GIJAAA OOOOxx
+5776 6299 0 0 6 16 76 776 1776 776 5776 152 153 EOAAAA HIJAAA VVVVxx
+9921 6300 1 1 1 1 21 921 1921 4921 9921 42 43 PRAAAA IIJAAA AAAAxx
+7816 6301 0 0 6 16 16 816 1816 2816 7816 32 33 QOAAAA JIJAAA HHHHxx
+2439 6302 1 3 9 19 39 439 439 2439 2439 78 79 VPAAAA KIJAAA OOOOxx
+9298 6303 0 2 8 18 98 298 1298 4298 9298 196 197 QTAAAA LIJAAA VVVVxx
+9424 6304 0 0 4 4 24 424 1424 4424 9424 48 49 MYAAAA MIJAAA AAAAxx
+3252 6305 0 0 2 12 52 252 1252 3252 3252 104 105 CVAAAA NIJAAA HHHHxx
+1401 6306 1 1 1 1 1 401 1401 1401 1401 2 3 XBAAAA OIJAAA OOOOxx
+9632 6307 0 0 2 12 32 632 1632 4632 9632 64 65 MGAAAA PIJAAA VVVVxx
+370 6308 0 2 0 10 70 370 370 370 370 140 141 GOAAAA QIJAAA AAAAxx
+728 6309 0 0 8 8 28 728 728 728 728 56 57 ACAAAA RIJAAA HHHHxx
+2888 6310 0 0 8 8 88 888 888 2888 2888 176 177 CHAAAA SIJAAA OOOOxx
+1441 6311 1 1 1 1 41 441 1441 1441 1441 82 83 LDAAAA TIJAAA VVVVxx
+8308 6312 0 0 8 8 8 308 308 3308 8308 16 17 OHAAAA UIJAAA AAAAxx
+2165 6313 1 1 5 5 65 165 165 2165 2165 130 131 HFAAAA VIJAAA HHHHxx
+6359 6314 1 3 9 19 59 359 359 1359 6359 118 119 PKAAAA WIJAAA OOOOxx
+9637 6315 1 1 7 17 37 637 1637 4637 9637 74 75 RGAAAA XIJAAA VVVVxx
+5208 6316 0 0 8 8 8 208 1208 208 5208 16 17 ISAAAA YIJAAA AAAAxx
+4705 6317 1 1 5 5 5 705 705 4705 4705 10 11 ZYAAAA ZIJAAA HHHHxx
+2341 6318 1 1 1 1 41 341 341 2341 2341 82 83 BMAAAA AJJAAA OOOOxx
+8539 6319 1 3 9 19 39 539 539 3539 8539 78 79 LQAAAA BJJAAA VVVVxx
+7528 6320 0 0 8 8 28 528 1528 2528 7528 56 57 ODAAAA CJJAAA AAAAxx
+7969 6321 1 1 9 9 69 969 1969 2969 7969 138 139 NUAAAA DJJAAA HHHHxx
+6381 6322 1 1 1 1 81 381 381 1381 6381 162 163 LLAAAA EJJAAA OOOOxx
+4906 6323 0 2 6 6 6 906 906 4906 4906 12 13 SGAAAA FJJAAA VVVVxx
+8697 6324 1 1 7 17 97 697 697 3697 8697 194 195 NWAAAA GJJAAA AAAAxx
+6301 6325 1 1 1 1 1 301 301 1301 6301 2 3 JIAAAA HJJAAA HHHHxx
+7554 6326 0 2 4 14 54 554 1554 2554 7554 108 109 OEAAAA IJJAAA OOOOxx
+5107 6327 1 3 7 7 7 107 1107 107 5107 14 15 LOAAAA JJJAAA VVVVxx
+5046 6328 0 2 6 6 46 46 1046 46 5046 92 93 CMAAAA KJJAAA AAAAxx
+4063 6329 1 3 3 3 63 63 63 4063 4063 126 127 HAAAAA LJJAAA HHHHxx
+7580 6330 0 0 0 0 80 580 1580 2580 7580 160 161 OFAAAA MJJAAA OOOOxx
+2245 6331 1 1 5 5 45 245 245 2245 2245 90 91 JIAAAA NJJAAA VVVVxx
+3711 6332 1 3 1 11 11 711 1711 3711 3711 22 23 TMAAAA OJJAAA AAAAxx
+3220 6333 0 0 0 0 20 220 1220 3220 3220 40 41 WTAAAA PJJAAA HHHHxx
+6463 6334 1 3 3 3 63 463 463 1463 6463 126 127 POAAAA QJJAAA OOOOxx
+8196 6335 0 0 6 16 96 196 196 3196 8196 192 193 GDAAAA RJJAAA VVVVxx
+9875 6336 1 3 5 15 75 875 1875 4875 9875 150 151 VPAAAA SJJAAA AAAAxx
+1333 6337 1 1 3 13 33 333 1333 1333 1333 66 67 HZAAAA TJJAAA HHHHxx
+7880 6338 0 0 0 0 80 880 1880 2880 7880 160 161 CRAAAA UJJAAA OOOOxx
+2322 6339 0 2 2 2 22 322 322 2322 2322 44 45 ILAAAA VJJAAA VVVVxx
+2163 6340 1 3 3 3 63 163 163 2163 2163 126 127 FFAAAA WJJAAA AAAAxx
+421 6341 1 1 1 1 21 421 421 421 421 42 43 FQAAAA XJJAAA HHHHxx
+2042 6342 0 2 2 2 42 42 42 2042 2042 84 85 OAAAAA YJJAAA OOOOxx
+1424 6343 0 0 4 4 24 424 1424 1424 1424 48 49 UCAAAA ZJJAAA VVVVxx
+7870 6344 0 2 0 10 70 870 1870 2870 7870 140 141 SQAAAA AKJAAA AAAAxx
+2653 6345 1 1 3 13 53 653 653 2653 2653 106 107 BYAAAA BKJAAA HHHHxx
+4216 6346 0 0 6 16 16 216 216 4216 4216 32 33 EGAAAA CKJAAA OOOOxx
+1515 6347 1 3 5 15 15 515 1515 1515 1515 30 31 HGAAAA DKJAAA VVVVxx
+7860 6348 0 0 0 0 60 860 1860 2860 7860 120 121 IQAAAA EKJAAA AAAAxx
+2984 6349 0 0 4 4 84 984 984 2984 2984 168 169 UKAAAA FKJAAA HHHHxx
+6269 6350 1 1 9 9 69 269 269 1269 6269 138 139 DHAAAA GKJAAA OOOOxx
+2609 6351 1 1 9 9 9 609 609 2609 2609 18 19 JWAAAA HKJAAA VVVVxx
+3671 6352 1 3 1 11 71 671 1671 3671 3671 142 143 FLAAAA IKJAAA AAAAxx
+4544 6353 0 0 4 4 44 544 544 4544 4544 88 89 USAAAA JKJAAA HHHHxx
+4668 6354 0 0 8 8 68 668 668 4668 4668 136 137 OXAAAA KKJAAA OOOOxx
+2565 6355 1 1 5 5 65 565 565 2565 2565 130 131 RUAAAA LKJAAA VVVVxx
+3126 6356 0 2 6 6 26 126 1126 3126 3126 52 53 GQAAAA MKJAAA AAAAxx
+7573 6357 1 1 3 13 73 573 1573 2573 7573 146 147 HFAAAA NKJAAA HHHHxx
+1476 6358 0 0 6 16 76 476 1476 1476 1476 152 153 UEAAAA OKJAAA OOOOxx
+2146 6359 0 2 6 6 46 146 146 2146 2146 92 93 OEAAAA PKJAAA VVVVxx
+9990 6360 0 2 0 10 90 990 1990 4990 9990 180 181 GUAAAA QKJAAA AAAAxx
+2530 6361 0 2 0 10 30 530 530 2530 2530 60 61 ITAAAA RKJAAA HHHHxx
+9288 6362 0 0 8 8 88 288 1288 4288 9288 176 177 GTAAAA SKJAAA OOOOxx
+9755 6363 1 3 5 15 55 755 1755 4755 9755 110 111 FLAAAA TKJAAA VVVVxx
+5305 6364 1 1 5 5 5 305 1305 305 5305 10 11 BWAAAA UKJAAA AAAAxx
+2495 6365 1 3 5 15 95 495 495 2495 2495 190 191 ZRAAAA VKJAAA HHHHxx
+5443 6366 1 3 3 3 43 443 1443 443 5443 86 87 JBAAAA WKJAAA OOOOxx
+1930 6367 0 2 0 10 30 930 1930 1930 1930 60 61 GWAAAA XKJAAA VVVVxx
+9134 6368 0 2 4 14 34 134 1134 4134 9134 68 69 INAAAA YKJAAA AAAAxx
+2844 6369 0 0 4 4 44 844 844 2844 2844 88 89 KFAAAA ZKJAAA HHHHxx
+896 6370 0 0 6 16 96 896 896 896 896 192 193 MIAAAA ALJAAA OOOOxx
+1330 6371 0 2 0 10 30 330 1330 1330 1330 60 61 EZAAAA BLJAAA VVVVxx
+8980 6372 0 0 0 0 80 980 980 3980 8980 160 161 KHAAAA CLJAAA AAAAxx
+5940 6373 0 0 0 0 40 940 1940 940 5940 80 81 MUAAAA DLJAAA HHHHxx
+6494 6374 0 2 4 14 94 494 494 1494 6494 188 189 UPAAAA ELJAAA OOOOxx
+165 6375 1 1 5 5 65 165 165 165 165 130 131 JGAAAA FLJAAA VVVVxx
+2510 6376 0 2 0 10 10 510 510 2510 2510 20 21 OSAAAA GLJAAA AAAAxx
+9950 6377 0 2 0 10 50 950 1950 4950 9950 100 101 SSAAAA HLJAAA HHHHxx
+3854 6378 0 2 4 14 54 854 1854 3854 3854 108 109 GSAAAA ILJAAA OOOOxx
+7493 6379 1 1 3 13 93 493 1493 2493 7493 186 187 FCAAAA JLJAAA VVVVxx
+4124 6380 0 0 4 4 24 124 124 4124 4124 48 49 QCAAAA KLJAAA AAAAxx
+8563 6381 1 3 3 3 63 563 563 3563 8563 126 127 JRAAAA LLJAAA HHHHxx
+8735 6382 1 3 5 15 35 735 735 3735 8735 70 71 ZXAAAA MLJAAA OOOOxx
+9046 6383 0 2 6 6 46 46 1046 4046 9046 92 93 YJAAAA NLJAAA VVVVxx
+1754 6384 0 2 4 14 54 754 1754 1754 1754 108 109 MPAAAA OLJAAA AAAAxx
+6954 6385 0 2 4 14 54 954 954 1954 6954 108 109 MHAAAA PLJAAA HHHHxx
+4953 6386 1 1 3 13 53 953 953 4953 4953 106 107 NIAAAA QLJAAA OOOOxx
+8142 6387 0 2 2 2 42 142 142 3142 8142 84 85 EBAAAA RLJAAA VVVVxx
+9661 6388 1 1 1 1 61 661 1661 4661 9661 122 123 PHAAAA SLJAAA AAAAxx
+6415 6389 1 3 5 15 15 415 415 1415 6415 30 31 TMAAAA TLJAAA HHHHxx
+5782 6390 0 2 2 2 82 782 1782 782 5782 164 165 KOAAAA ULJAAA OOOOxx
+7721 6391 1 1 1 1 21 721 1721 2721 7721 42 43 ZKAAAA VLJAAA VVVVxx
+580 6392 0 0 0 0 80 580 580 580 580 160 161 IWAAAA WLJAAA AAAAxx
+3784 6393 0 0 4 4 84 784 1784 3784 3784 168 169 OPAAAA XLJAAA HHHHxx
+9810 6394 0 2 0 10 10 810 1810 4810 9810 20 21 INAAAA YLJAAA OOOOxx
+8488 6395 0 0 8 8 88 488 488 3488 8488 176 177 MOAAAA ZLJAAA VVVVxx
+6214 6396 0 2 4 14 14 214 214 1214 6214 28 29 AFAAAA AMJAAA AAAAxx
+9433 6397 1 1 3 13 33 433 1433 4433 9433 66 67 VYAAAA BMJAAA HHHHxx
+9959 6398 1 3 9 19 59 959 1959 4959 9959 118 119 BTAAAA CMJAAA OOOOxx
+554 6399 0 2 4 14 54 554 554 554 554 108 109 IVAAAA DMJAAA VVVVxx
+6646 6400 0 2 6 6 46 646 646 1646 6646 92 93 QVAAAA EMJAAA AAAAxx
+1138 6401 0 2 8 18 38 138 1138 1138 1138 76 77 URAAAA FMJAAA HHHHxx
+9331 6402 1 3 1 11 31 331 1331 4331 9331 62 63 XUAAAA GMJAAA OOOOxx
+7331 6403 1 3 1 11 31 331 1331 2331 7331 62 63 ZVAAAA HMJAAA VVVVxx
+3482 6404 0 2 2 2 82 482 1482 3482 3482 164 165 YDAAAA IMJAAA AAAAxx
+3795 6405 1 3 5 15 95 795 1795 3795 3795 190 191 ZPAAAA JMJAAA HHHHxx
+2441 6406 1 1 1 1 41 441 441 2441 2441 82 83 XPAAAA KMJAAA OOOOxx
+5229 6407 1 1 9 9 29 229 1229 229 5229 58 59 DTAAAA LMJAAA VVVVxx
+7012 6408 0 0 2 12 12 12 1012 2012 7012 24 25 SJAAAA MMJAAA AAAAxx
+7036 6409 0 0 6 16 36 36 1036 2036 7036 72 73 QKAAAA NMJAAA HHHHxx
+8243 6410 1 3 3 3 43 243 243 3243 8243 86 87 BFAAAA OMJAAA OOOOxx
+9320 6411 0 0 0 0 20 320 1320 4320 9320 40 41 MUAAAA PMJAAA VVVVxx
+4693 6412 1 1 3 13 93 693 693 4693 4693 186 187 NYAAAA QMJAAA AAAAxx
+6741 6413 1 1 1 1 41 741 741 1741 6741 82 83 HZAAAA RMJAAA HHHHxx
+2997 6414 1 1 7 17 97 997 997 2997 2997 194 195 HLAAAA SMJAAA OOOOxx
+4838 6415 0 2 8 18 38 838 838 4838 4838 76 77 CEAAAA TMJAAA VVVVxx
+6945 6416 1 1 5 5 45 945 945 1945 6945 90 91 DHAAAA UMJAAA AAAAxx
+8253 6417 1 1 3 13 53 253 253 3253 8253 106 107 LFAAAA VMJAAA HHHHxx
+8989 6418 1 1 9 9 89 989 989 3989 8989 178 179 THAAAA WMJAAA OOOOxx
+2640 6419 0 0 0 0 40 640 640 2640 2640 80 81 OXAAAA XMJAAA VVVVxx
+5647 6420 1 3 7 7 47 647 1647 647 5647 94 95 FJAAAA YMJAAA AAAAxx
+7186 6421 0 2 6 6 86 186 1186 2186 7186 172 173 KQAAAA ZMJAAA HHHHxx
+3278 6422 0 2 8 18 78 278 1278 3278 3278 156 157 CWAAAA ANJAAA OOOOxx
+8546 6423 0 2 6 6 46 546 546 3546 8546 92 93 SQAAAA BNJAAA VVVVxx
+8297 6424 1 1 7 17 97 297 297 3297 8297 194 195 DHAAAA CNJAAA AAAAxx
+9534 6425 0 2 4 14 34 534 1534 4534 9534 68 69 SCAAAA DNJAAA HHHHxx
+9618 6426 0 2 8 18 18 618 1618 4618 9618 36 37 YFAAAA ENJAAA OOOOxx
+8839 6427 1 3 9 19 39 839 839 3839 8839 78 79 ZBAAAA FNJAAA VVVVxx
+7605 6428 1 1 5 5 5 605 1605 2605 7605 10 11 NGAAAA GNJAAA AAAAxx
+6421 6429 1 1 1 1 21 421 421 1421 6421 42 43 ZMAAAA HNJAAA HHHHxx
+3582 6430 0 2 2 2 82 582 1582 3582 3582 164 165 UHAAAA INJAAA OOOOxx
+485 6431 1 1 5 5 85 485 485 485 485 170 171 RSAAAA JNJAAA VVVVxx
+1925 6432 1 1 5 5 25 925 1925 1925 1925 50 51 BWAAAA KNJAAA AAAAxx
+4296 6433 0 0 6 16 96 296 296 4296 4296 192 193 GJAAAA LNJAAA HHHHxx
+8874 6434 0 2 4 14 74 874 874 3874 8874 148 149 IDAAAA MNJAAA OOOOxx
+1443 6435 1 3 3 3 43 443 1443 1443 1443 86 87 NDAAAA NNJAAA VVVVxx
+4239 6436 1 3 9 19 39 239 239 4239 4239 78 79 BHAAAA ONJAAA AAAAxx
+9760 6437 0 0 0 0 60 760 1760 4760 9760 120 121 KLAAAA PNJAAA HHHHxx
+136 6438 0 0 6 16 36 136 136 136 136 72 73 GFAAAA QNJAAA OOOOxx
+6472 6439 0 0 2 12 72 472 472 1472 6472 144 145 YOAAAA RNJAAA VVVVxx
+4896 6440 0 0 6 16 96 896 896 4896 4896 192 193 IGAAAA SNJAAA AAAAxx
+9028 6441 0 0 8 8 28 28 1028 4028 9028 56 57 GJAAAA TNJAAA HHHHxx
+8354 6442 0 2 4 14 54 354 354 3354 8354 108 109 IJAAAA UNJAAA OOOOxx
+8648 6443 0 0 8 8 48 648 648 3648 8648 96 97 QUAAAA VNJAAA VVVVxx
+918 6444 0 2 8 18 18 918 918 918 918 36 37 IJAAAA WNJAAA AAAAxx
+6606 6445 0 2 6 6 6 606 606 1606 6606 12 13 CUAAAA XNJAAA HHHHxx
+2462 6446 0 2 2 2 62 462 462 2462 2462 124 125 SQAAAA YNJAAA OOOOxx
+7536 6447 0 0 6 16 36 536 1536 2536 7536 72 73 WDAAAA ZNJAAA VVVVxx
+1700 6448 0 0 0 0 0 700 1700 1700 1700 0 1 KNAAAA AOJAAA AAAAxx
+6740 6449 0 0 0 0 40 740 740 1740 6740 80 81 GZAAAA BOJAAA HHHHxx
+28 6450 0 0 8 8 28 28 28 28 28 56 57 CBAAAA COJAAA OOOOxx
+6044 6451 0 0 4 4 44 44 44 1044 6044 88 89 MYAAAA DOJAAA VVVVxx
+5053 6452 1 1 3 13 53 53 1053 53 5053 106 107 JMAAAA EOJAAA AAAAxx
+4832 6453 0 0 2 12 32 832 832 4832 4832 64 65 WDAAAA FOJAAA HHHHxx
+9145 6454 1 1 5 5 45 145 1145 4145 9145 90 91 TNAAAA GOJAAA OOOOxx
+5482 6455 0 2 2 2 82 482 1482 482 5482 164 165 WCAAAA HOJAAA VVVVxx
+7644 6456 0 0 4 4 44 644 1644 2644 7644 88 89 AIAAAA IOJAAA AAAAxx
+2128 6457 0 0 8 8 28 128 128 2128 2128 56 57 WDAAAA JOJAAA HHHHxx
+6583 6458 1 3 3 3 83 583 583 1583 6583 166 167 FTAAAA KOJAAA OOOOxx
+4224 6459 0 0 4 4 24 224 224 4224 4224 48 49 MGAAAA LOJAAA VVVVxx
+5253 6460 1 1 3 13 53 253 1253 253 5253 106 107 BUAAAA MOJAAA AAAAxx
+8219 6461 1 3 9 19 19 219 219 3219 8219 38 39 DEAAAA NOJAAA HHHHxx
+8113 6462 1 1 3 13 13 113 113 3113 8113 26 27 BAAAAA OOJAAA OOOOxx
+3616 6463 0 0 6 16 16 616 1616 3616 3616 32 33 CJAAAA POJAAA VVVVxx
+1361 6464 1 1 1 1 61 361 1361 1361 1361 122 123 JAAAAA QOJAAA AAAAxx
+949 6465 1 1 9 9 49 949 949 949 949 98 99 NKAAAA ROJAAA HHHHxx
+8582 6466 0 2 2 2 82 582 582 3582 8582 164 165 CSAAAA SOJAAA OOOOxx
+5104 6467 0 0 4 4 4 104 1104 104 5104 8 9 IOAAAA TOJAAA VVVVxx
+6146 6468 0 2 6 6 46 146 146 1146 6146 92 93 KCAAAA UOJAAA AAAAxx
+7681 6469 1 1 1 1 81 681 1681 2681 7681 162 163 LJAAAA VOJAAA HHHHxx
+1904 6470 0 0 4 4 4 904 1904 1904 1904 8 9 GVAAAA WOJAAA OOOOxx
+1989 6471 1 1 9 9 89 989 1989 1989 1989 178 179 NYAAAA XOJAAA VVVVxx
+4179 6472 1 3 9 19 79 179 179 4179 4179 158 159 TEAAAA YOJAAA AAAAxx
+1739 6473 1 3 9 19 39 739 1739 1739 1739 78 79 XOAAAA ZOJAAA HHHHxx
+2447 6474 1 3 7 7 47 447 447 2447 2447 94 95 DQAAAA APJAAA OOOOxx
+3029 6475 1 1 9 9 29 29 1029 3029 3029 58 59 NMAAAA BPJAAA VVVVxx
+9783 6476 1 3 3 3 83 783 1783 4783 9783 166 167 HMAAAA CPJAAA AAAAxx
+8381 6477 1 1 1 1 81 381 381 3381 8381 162 163 JKAAAA DPJAAA HHHHxx
+8755 6478 1 3 5 15 55 755 755 3755 8755 110 111 TYAAAA EPJAAA OOOOxx
+8384 6479 0 0 4 4 84 384 384 3384 8384 168 169 MKAAAA FPJAAA VVVVxx
+7655 6480 1 3 5 15 55 655 1655 2655 7655 110 111 LIAAAA GPJAAA AAAAxx
+4766 6481 0 2 6 6 66 766 766 4766 4766 132 133 IBAAAA HPJAAA HHHHxx
+3324 6482 0 0 4 4 24 324 1324 3324 3324 48 49 WXAAAA IPJAAA OOOOxx
+5022 6483 0 2 2 2 22 22 1022 22 5022 44 45 ELAAAA JPJAAA VVVVxx
+2856 6484 0 0 6 16 56 856 856 2856 2856 112 113 WFAAAA KPJAAA AAAAxx
+6503 6485 1 3 3 3 3 503 503 1503 6503 6 7 DQAAAA LPJAAA HHHHxx
+6872 6486 0 0 2 12 72 872 872 1872 6872 144 145 IEAAAA MPJAAA OOOOxx
+1663 6487 1 3 3 3 63 663 1663 1663 1663 126 127 ZLAAAA NPJAAA VVVVxx
+6964 6488 0 0 4 4 64 964 964 1964 6964 128 129 WHAAAA OPJAAA AAAAxx
+4622 6489 0 2 2 2 22 622 622 4622 4622 44 45 UVAAAA PPJAAA HHHHxx
+6089 6490 1 1 9 9 89 89 89 1089 6089 178 179 FAAAAA QPJAAA OOOOxx
+8567 6491 1 3 7 7 67 567 567 3567 8567 134 135 NRAAAA RPJAAA VVVVxx
+597 6492 1 1 7 17 97 597 597 597 597 194 195 ZWAAAA SPJAAA AAAAxx
+4222 6493 0 2 2 2 22 222 222 4222 4222 44 45 KGAAAA TPJAAA HHHHxx
+9322 6494 0 2 2 2 22 322 1322 4322 9322 44 45 OUAAAA UPJAAA OOOOxx
+624 6495 0 0 4 4 24 624 624 624 624 48 49 AYAAAA VPJAAA VVVVxx
+4329 6496 1 1 9 9 29 329 329 4329 4329 58 59 NKAAAA WPJAAA AAAAxx
+6781 6497 1 1 1 1 81 781 781 1781 6781 162 163 VAAAAA XPJAAA HHHHxx
+1673 6498 1 1 3 13 73 673 1673 1673 1673 146 147 JMAAAA YPJAAA OOOOxx
+6633 6499 1 1 3 13 33 633 633 1633 6633 66 67 DVAAAA ZPJAAA VVVVxx
+2569 6500 1 1 9 9 69 569 569 2569 2569 138 139 VUAAAA AQJAAA AAAAxx
+4995 6501 1 3 5 15 95 995 995 4995 4995 190 191 DKAAAA BQJAAA HHHHxx
+2749 6502 1 1 9 9 49 749 749 2749 2749 98 99 TBAAAA CQJAAA OOOOxx
+9044 6503 0 0 4 4 44 44 1044 4044 9044 88 89 WJAAAA DQJAAA VVVVxx
+5823 6504 1 3 3 3 23 823 1823 823 5823 46 47 ZPAAAA EQJAAA AAAAxx
+9366 6505 0 2 6 6 66 366 1366 4366 9366 132 133 GWAAAA FQJAAA HHHHxx
+1169 6506 1 1 9 9 69 169 1169 1169 1169 138 139 ZSAAAA GQJAAA OOOOxx
+1300 6507 0 0 0 0 0 300 1300 1300 1300 0 1 AYAAAA HQJAAA VVVVxx
+9973 6508 1 1 3 13 73 973 1973 4973 9973 146 147 PTAAAA IQJAAA AAAAxx
+2092 6509 0 0 2 12 92 92 92 2092 2092 184 185 MCAAAA JQJAAA HHHHxx
+9776 6510 0 0 6 16 76 776 1776 4776 9776 152 153 AMAAAA KQJAAA OOOOxx
+7612 6511 0 0 2 12 12 612 1612 2612 7612 24 25 UGAAAA LQJAAA VVVVxx
+7190 6512 0 2 0 10 90 190 1190 2190 7190 180 181 OQAAAA MQJAAA AAAAxx
+5147 6513 1 3 7 7 47 147 1147 147 5147 94 95 ZPAAAA NQJAAA HHHHxx
+3722 6514 0 2 2 2 22 722 1722 3722 3722 44 45 ENAAAA OQJAAA OOOOxx
+5858 6515 0 2 8 18 58 858 1858 858 5858 116 117 IRAAAA PQJAAA VVVVxx
+3204 6516 0 0 4 4 4 204 1204 3204 3204 8 9 GTAAAA QQJAAA AAAAxx
+8994 6517 0 2 4 14 94 994 994 3994 8994 188 189 YHAAAA RQJAAA HHHHxx
+7478 6518 0 2 8 18 78 478 1478 2478 7478 156 157 QBAAAA SQJAAA OOOOxx
+9624 6519 0 0 4 4 24 624 1624 4624 9624 48 49 EGAAAA TQJAAA VVVVxx
+6639 6520 1 3 9 19 39 639 639 1639 6639 78 79 JVAAAA UQJAAA AAAAxx
+369 6521 1 1 9 9 69 369 369 369 369 138 139 FOAAAA VQJAAA HHHHxx
+7766 6522 0 2 6 6 66 766 1766 2766 7766 132 133 SMAAAA WQJAAA OOOOxx
+4094 6523 0 2 4 14 94 94 94 4094 4094 188 189 MBAAAA XQJAAA VVVVxx
+9556 6524 0 0 6 16 56 556 1556 4556 9556 112 113 ODAAAA YQJAAA AAAAxx
+4887 6525 1 3 7 7 87 887 887 4887 4887 174 175 ZFAAAA ZQJAAA HHHHxx
+2321 6526 1 1 1 1 21 321 321 2321 2321 42 43 HLAAAA ARJAAA OOOOxx
+9201 6527 1 1 1 1 1 201 1201 4201 9201 2 3 XPAAAA BRJAAA VVVVxx
+1627 6528 1 3 7 7 27 627 1627 1627 1627 54 55 PKAAAA CRJAAA AAAAxx
+150 6529 0 2 0 10 50 150 150 150 150 100 101 UFAAAA DRJAAA HHHHxx
+8010 6530 0 2 0 10 10 10 10 3010 8010 20 21 CWAAAA ERJAAA OOOOxx
+8026 6531 0 2 6 6 26 26 26 3026 8026 52 53 SWAAAA FRJAAA VVVVxx
+5495 6532 1 3 5 15 95 495 1495 495 5495 190 191 JDAAAA GRJAAA AAAAxx
+6213 6533 1 1 3 13 13 213 213 1213 6213 26 27 ZEAAAA HRJAAA HHHHxx
+6464 6534 0 0 4 4 64 464 464 1464 6464 128 129 QOAAAA IRJAAA OOOOxx
+1158 6535 0 2 8 18 58 158 1158 1158 1158 116 117 OSAAAA JRJAAA VVVVxx
+8669 6536 1 1 9 9 69 669 669 3669 8669 138 139 LVAAAA KRJAAA AAAAxx
+3225 6537 1 1 5 5 25 225 1225 3225 3225 50 51 BUAAAA LRJAAA HHHHxx
+1294 6538 0 2 4 14 94 294 1294 1294 1294 188 189 UXAAAA MRJAAA OOOOxx
+2166 6539 0 2 6 6 66 166 166 2166 2166 132 133 IFAAAA NRJAAA VVVVxx
+9328 6540 0 0 8 8 28 328 1328 4328 9328 56 57 UUAAAA ORJAAA AAAAxx
+8431 6541 1 3 1 11 31 431 431 3431 8431 62 63 HMAAAA PRJAAA HHHHxx
+7100 6542 0 0 0 0 0 100 1100 2100 7100 0 1 CNAAAA QRJAAA OOOOxx
+8126 6543 0 2 6 6 26 126 126 3126 8126 52 53 OAAAAA RRJAAA VVVVxx
+2185 6544 1 1 5 5 85 185 185 2185 2185 170 171 BGAAAA SRJAAA AAAAxx
+5697 6545 1 1 7 17 97 697 1697 697 5697 194 195 DLAAAA TRJAAA HHHHxx
+5531 6546 1 3 1 11 31 531 1531 531 5531 62 63 TEAAAA URJAAA OOOOxx
+3020 6547 0 0 0 0 20 20 1020 3020 3020 40 41 EMAAAA VRJAAA VVVVxx
+3076 6548 0 0 6 16 76 76 1076 3076 3076 152 153 IOAAAA WRJAAA AAAAxx
+9228 6549 0 0 8 8 28 228 1228 4228 9228 56 57 YQAAAA XRJAAA HHHHxx
+1734 6550 0 2 4 14 34 734 1734 1734 1734 68 69 SOAAAA YRJAAA OOOOxx
+7616 6551 0 0 6 16 16 616 1616 2616 7616 32 33 YGAAAA ZRJAAA VVVVxx
+9059 6552 1 3 9 19 59 59 1059 4059 9059 118 119 LKAAAA ASJAAA AAAAxx
+323 6553 1 3 3 3 23 323 323 323 323 46 47 LMAAAA BSJAAA HHHHxx
+1283 6554 1 3 3 3 83 283 1283 1283 1283 166 167 JXAAAA CSJAAA OOOOxx
+9535 6555 1 3 5 15 35 535 1535 4535 9535 70 71 TCAAAA DSJAAA VVVVxx
+2580 6556 0 0 0 0 80 580 580 2580 2580 160 161 GVAAAA ESJAAA AAAAxx
+7633 6557 1 1 3 13 33 633 1633 2633 7633 66 67 PHAAAA FSJAAA HHHHxx
+9497 6558 1 1 7 17 97 497 1497 4497 9497 194 195 HBAAAA GSJAAA OOOOxx
+9842 6559 0 2 2 2 42 842 1842 4842 9842 84 85 OOAAAA HSJAAA VVVVxx
+3426 6560 0 2 6 6 26 426 1426 3426 3426 52 53 UBAAAA ISJAAA AAAAxx
+7650 6561 0 2 0 10 50 650 1650 2650 7650 100 101 GIAAAA JSJAAA HHHHxx
+9935 6562 1 3 5 15 35 935 1935 4935 9935 70 71 DSAAAA KSJAAA OOOOxx
+9354 6563 0 2 4 14 54 354 1354 4354 9354 108 109 UVAAAA LSJAAA VVVVxx
+5569 6564 1 1 9 9 69 569 1569 569 5569 138 139 FGAAAA MSJAAA AAAAxx
+5765 6565 1 1 5 5 65 765 1765 765 5765 130 131 TNAAAA NSJAAA HHHHxx
+7283 6566 1 3 3 3 83 283 1283 2283 7283 166 167 DUAAAA OSJAAA OOOOxx
+1068 6567 0 0 8 8 68 68 1068 1068 1068 136 137 CPAAAA PSJAAA VVVVxx
+1641 6568 1 1 1 1 41 641 1641 1641 1641 82 83 DLAAAA QSJAAA AAAAxx
+1688 6569 0 0 8 8 88 688 1688 1688 1688 176 177 YMAAAA RSJAAA HHHHxx
+1133 6570 1 1 3 13 33 133 1133 1133 1133 66 67 PRAAAA SSJAAA OOOOxx
+4493 6571 1 1 3 13 93 493 493 4493 4493 186 187 VQAAAA TSJAAA VVVVxx
+3354 6572 0 2 4 14 54 354 1354 3354 3354 108 109 AZAAAA USJAAA AAAAxx
+4029 6573 1 1 9 9 29 29 29 4029 4029 58 59 ZYAAAA VSJAAA HHHHxx
+6704 6574 0 0 4 4 4 704 704 1704 6704 8 9 WXAAAA WSJAAA OOOOxx
+3221 6575 1 1 1 1 21 221 1221 3221 3221 42 43 XTAAAA XSJAAA VVVVxx
+9432 6576 0 0 2 12 32 432 1432 4432 9432 64 65 UYAAAA YSJAAA AAAAxx
+6990 6577 0 2 0 10 90 990 990 1990 6990 180 181 WIAAAA ZSJAAA HHHHxx
+1760 6578 0 0 0 0 60 760 1760 1760 1760 120 121 SPAAAA ATJAAA OOOOxx
+4754 6579 0 2 4 14 54 754 754 4754 4754 108 109 WAAAAA BTJAAA VVVVxx
+7724 6580 0 0 4 4 24 724 1724 2724 7724 48 49 CLAAAA CTJAAA AAAAxx
+9487 6581 1 3 7 7 87 487 1487 4487 9487 174 175 XAAAAA DTJAAA HHHHxx
+166 6582 0 2 6 6 66 166 166 166 166 132 133 KGAAAA ETJAAA OOOOxx
+5479 6583 1 3 9 19 79 479 1479 479 5479 158 159 TCAAAA FTJAAA VVVVxx
+8744 6584 0 0 4 4 44 744 744 3744 8744 88 89 IYAAAA GTJAAA AAAAxx
+5746 6585 0 2 6 6 46 746 1746 746 5746 92 93 ANAAAA HTJAAA HHHHxx
+907 6586 1 3 7 7 7 907 907 907 907 14 15 XIAAAA ITJAAA OOOOxx
+3968 6587 0 0 8 8 68 968 1968 3968 3968 136 137 QWAAAA JTJAAA VVVVxx
+5721 6588 1 1 1 1 21 721 1721 721 5721 42 43 BMAAAA KTJAAA AAAAxx
+6738 6589 0 2 8 18 38 738 738 1738 6738 76 77 EZAAAA LTJAAA HHHHxx
+4097 6590 1 1 7 17 97 97 97 4097 4097 194 195 PBAAAA MTJAAA OOOOxx
+8456 6591 0 0 6 16 56 456 456 3456 8456 112 113 GNAAAA NTJAAA VVVVxx
+1269 6592 1 1 9 9 69 269 1269 1269 1269 138 139 VWAAAA OTJAAA AAAAxx
+7997 6593 1 1 7 17 97 997 1997 2997 7997 194 195 PVAAAA PTJAAA HHHHxx
+9457 6594 1 1 7 17 57 457 1457 4457 9457 114 115 TZAAAA QTJAAA OOOOxx
+1159 6595 1 3 9 19 59 159 1159 1159 1159 118 119 PSAAAA RTJAAA VVVVxx
+1631 6596 1 3 1 11 31 631 1631 1631 1631 62 63 TKAAAA STJAAA AAAAxx
+2019 6597 1 3 9 19 19 19 19 2019 2019 38 39 RZAAAA TTJAAA HHHHxx
+3186 6598 0 2 6 6 86 186 1186 3186 3186 172 173 OSAAAA UTJAAA OOOOxx
+5587 6599 1 3 7 7 87 587 1587 587 5587 174 175 XGAAAA VTJAAA VVVVxx
+9172 6600 0 0 2 12 72 172 1172 4172 9172 144 145 UOAAAA WTJAAA AAAAxx
+5589 6601 1 1 9 9 89 589 1589 589 5589 178 179 ZGAAAA XTJAAA HHHHxx
+5103 6602 1 3 3 3 3 103 1103 103 5103 6 7 HOAAAA YTJAAA OOOOxx
+3177 6603 1 1 7 17 77 177 1177 3177 3177 154 155 FSAAAA ZTJAAA VVVVxx
+8887 6604 1 3 7 7 87 887 887 3887 8887 174 175 VDAAAA AUJAAA AAAAxx
+12 6605 0 0 2 12 12 12 12 12 12 24 25 MAAAAA BUJAAA HHHHxx
+8575 6606 1 3 5 15 75 575 575 3575 8575 150 151 VRAAAA CUJAAA OOOOxx
+4335 6607 1 3 5 15 35 335 335 4335 4335 70 71 TKAAAA DUJAAA VVVVxx
+4581 6608 1 1 1 1 81 581 581 4581 4581 162 163 FUAAAA EUJAAA AAAAxx
+4444 6609 0 0 4 4 44 444 444 4444 4444 88 89 YOAAAA FUJAAA HHHHxx
+7978 6610 0 2 8 18 78 978 1978 2978 7978 156 157 WUAAAA GUJAAA OOOOxx
+3081 6611 1 1 1 1 81 81 1081 3081 3081 162 163 NOAAAA HUJAAA VVVVxx
+4059 6612 1 3 9 19 59 59 59 4059 4059 118 119 DAAAAA IUJAAA AAAAxx
+5711 6613 1 3 1 11 11 711 1711 711 5711 22 23 RLAAAA JUJAAA HHHHxx
+7069 6614 1 1 9 9 69 69 1069 2069 7069 138 139 XLAAAA KUJAAA OOOOxx
+6150 6615 0 2 0 10 50 150 150 1150 6150 100 101 OCAAAA LUJAAA VVVVxx
+9550 6616 0 2 0 10 50 550 1550 4550 9550 100 101 IDAAAA MUJAAA AAAAxx
+7087 6617 1 3 7 7 87 87 1087 2087 7087 174 175 PMAAAA NUJAAA HHHHxx
+9557 6618 1 1 7 17 57 557 1557 4557 9557 114 115 PDAAAA OUJAAA OOOOxx
+7856 6619 0 0 6 16 56 856 1856 2856 7856 112 113 EQAAAA PUJAAA VVVVxx
+1115 6620 1 3 5 15 15 115 1115 1115 1115 30 31 XQAAAA QUJAAA AAAAxx
+1086 6621 0 2 6 6 86 86 1086 1086 1086 172 173 UPAAAA RUJAAA HHHHxx
+5048 6622 0 0 8 8 48 48 1048 48 5048 96 97 EMAAAA SUJAAA OOOOxx
+5168 6623 0 0 8 8 68 168 1168 168 5168 136 137 UQAAAA TUJAAA VVVVxx
+6029 6624 1 1 9 9 29 29 29 1029 6029 58 59 XXAAAA UUJAAA AAAAxx
+546 6625 0 2 6 6 46 546 546 546 546 92 93 AVAAAA VUJAAA HHHHxx
+2908 6626 0 0 8 8 8 908 908 2908 2908 16 17 WHAAAA WUJAAA OOOOxx
+779 6627 1 3 9 19 79 779 779 779 779 158 159 ZDAAAA XUJAAA VVVVxx
+4202 6628 0 2 2 2 2 202 202 4202 4202 4 5 QFAAAA YUJAAA AAAAxx
+9984 6629 0 0 4 4 84 984 1984 4984 9984 168 169 AUAAAA ZUJAAA HHHHxx
+4730 6630 0 2 0 10 30 730 730 4730 4730 60 61 YZAAAA AVJAAA OOOOxx
+6517 6631 1 1 7 17 17 517 517 1517 6517 34 35 RQAAAA BVJAAA VVVVxx
+8410 6632 0 2 0 10 10 410 410 3410 8410 20 21 MLAAAA CVJAAA AAAAxx
+4793 6633 1 1 3 13 93 793 793 4793 4793 186 187 JCAAAA DVJAAA HHHHxx
+3431 6634 1 3 1 11 31 431 1431 3431 3431 62 63 ZBAAAA EVJAAA OOOOxx
+2481 6635 1 1 1 1 81 481 481 2481 2481 162 163 LRAAAA FVJAAA VVVVxx
+3905 6636 1 1 5 5 5 905 1905 3905 3905 10 11 FUAAAA GVJAAA AAAAxx
+8807 6637 1 3 7 7 7 807 807 3807 8807 14 15 TAAAAA HVJAAA HHHHxx
+2660 6638 0 0 0 0 60 660 660 2660 2660 120 121 IYAAAA IVJAAA OOOOxx
+4985 6639 1 1 5 5 85 985 985 4985 4985 170 171 TJAAAA JVJAAA VVVVxx
+3080 6640 0 0 0 0 80 80 1080 3080 3080 160 161 MOAAAA KVJAAA AAAAxx
+1090 6641 0 2 0 10 90 90 1090 1090 1090 180 181 YPAAAA LVJAAA HHHHxx
+6917 6642 1 1 7 17 17 917 917 1917 6917 34 35 BGAAAA MVJAAA OOOOxx
+5177 6643 1 1 7 17 77 177 1177 177 5177 154 155 DRAAAA NVJAAA VVVVxx
+2729 6644 1 1 9 9 29 729 729 2729 2729 58 59 ZAAAAA OVJAAA AAAAxx
+9706 6645 0 2 6 6 6 706 1706 4706 9706 12 13 IJAAAA PVJAAA HHHHxx
+9929 6646 1 1 9 9 29 929 1929 4929 9929 58 59 XRAAAA QVJAAA OOOOxx
+1547 6647 1 3 7 7 47 547 1547 1547 1547 94 95 NHAAAA RVJAAA VVVVxx
+2798 6648 0 2 8 18 98 798 798 2798 2798 196 197 QDAAAA SVJAAA AAAAxx
+4420 6649 0 0 0 0 20 420 420 4420 4420 40 41 AOAAAA TVJAAA HHHHxx
+6771 6650 1 3 1 11 71 771 771 1771 6771 142 143 LAAAAA UVJAAA OOOOxx
+2004 6651 0 0 4 4 4 4 4 2004 2004 8 9 CZAAAA VVJAAA VVVVxx
+8686 6652 0 2 6 6 86 686 686 3686 8686 172 173 CWAAAA WVJAAA AAAAxx
+3663 6653 1 3 3 3 63 663 1663 3663 3663 126 127 XKAAAA XVJAAA HHHHxx
+806 6654 0 2 6 6 6 806 806 806 806 12 13 AFAAAA YVJAAA OOOOxx
+4309 6655 1 1 9 9 9 309 309 4309 4309 18 19 TJAAAA ZVJAAA VVVVxx
+7443 6656 1 3 3 3 43 443 1443 2443 7443 86 87 HAAAAA AWJAAA AAAAxx
+5779 6657 1 3 9 19 79 779 1779 779 5779 158 159 HOAAAA BWJAAA HHHHxx
+8821 6658 1 1 1 1 21 821 821 3821 8821 42 43 HBAAAA CWJAAA OOOOxx
+4198 6659 0 2 8 18 98 198 198 4198 4198 196 197 MFAAAA DWJAAA VVVVxx
+8115 6660 1 3 5 15 15 115 115 3115 8115 30 31 DAAAAA EWJAAA AAAAxx
+9554 6661 0 2 4 14 54 554 1554 4554 9554 108 109 MDAAAA FWJAAA HHHHxx
+8956 6662 0 0 6 16 56 956 956 3956 8956 112 113 MGAAAA GWJAAA OOOOxx
+4733 6663 1 1 3 13 33 733 733 4733 4733 66 67 BAAAAA HWJAAA VVVVxx
+5417 6664 1 1 7 17 17 417 1417 417 5417 34 35 JAAAAA IWJAAA AAAAxx
+4792 6665 0 0 2 12 92 792 792 4792 4792 184 185 ICAAAA JWJAAA HHHHxx
+462 6666 0 2 2 2 62 462 462 462 462 124 125 URAAAA KWJAAA OOOOxx
+3687 6667 1 3 7 7 87 687 1687 3687 3687 174 175 VLAAAA LWJAAA VVVVxx
+2013 6668 1 1 3 13 13 13 13 2013 2013 26 27 LZAAAA MWJAAA AAAAxx
+5386 6669 0 2 6 6 86 386 1386 386 5386 172 173 EZAAAA NWJAAA HHHHxx
+2816 6670 0 0 6 16 16 816 816 2816 2816 32 33 IEAAAA OWJAAA OOOOxx
+7827 6671 1 3 7 7 27 827 1827 2827 7827 54 55 BPAAAA PWJAAA VVVVxx
+5077 6672 1 1 7 17 77 77 1077 77 5077 154 155 HNAAAA QWJAAA AAAAxx
+6039 6673 1 3 9 19 39 39 39 1039 6039 78 79 HYAAAA RWJAAA HHHHxx
+215 6674 1 3 5 15 15 215 215 215 215 30 31 HIAAAA SWJAAA OOOOxx
+855 6675 1 3 5 15 55 855 855 855 855 110 111 XGAAAA TWJAAA VVVVxx
+9692 6676 0 0 2 12 92 692 1692 4692 9692 184 185 UIAAAA UWJAAA AAAAxx
+8391 6677 1 3 1 11 91 391 391 3391 8391 182 183 TKAAAA VWJAAA HHHHxx
+8424 6678 0 0 4 4 24 424 424 3424 8424 48 49 AMAAAA WWJAAA OOOOxx
+6331 6679 1 3 1 11 31 331 331 1331 6331 62 63 NJAAAA XWJAAA VVVVxx
+6561 6680 1 1 1 1 61 561 561 1561 6561 122 123 JSAAAA YWJAAA AAAAxx
+8955 6681 1 3 5 15 55 955 955 3955 8955 110 111 LGAAAA ZWJAAA HHHHxx
+1764 6682 0 0 4 4 64 764 1764 1764 1764 128 129 WPAAAA AXJAAA OOOOxx
+6623 6683 1 3 3 3 23 623 623 1623 6623 46 47 TUAAAA BXJAAA VVVVxx
+2900 6684 0 0 0 0 0 900 900 2900 2900 0 1 OHAAAA CXJAAA AAAAxx
+7048 6685 0 0 8 8 48 48 1048 2048 7048 96 97 CLAAAA DXJAAA HHHHxx
+3843 6686 1 3 3 3 43 843 1843 3843 3843 86 87 VRAAAA EXJAAA OOOOxx
+4855 6687 1 3 5 15 55 855 855 4855 4855 110 111 TEAAAA FXJAAA VVVVxx
+7383 6688 1 3 3 3 83 383 1383 2383 7383 166 167 ZXAAAA GXJAAA AAAAxx
+7765 6689 1 1 5 5 65 765 1765 2765 7765 130 131 RMAAAA HXJAAA HHHHxx
+1125 6690 1 1 5 5 25 125 1125 1125 1125 50 51 HRAAAA IXJAAA OOOOxx
+755 6691 1 3 5 15 55 755 755 755 755 110 111 BDAAAA JXJAAA VVVVxx
+2995 6692 1 3 5 15 95 995 995 2995 2995 190 191 FLAAAA KXJAAA AAAAxx
+8907 6693 1 3 7 7 7 907 907 3907 8907 14 15 PEAAAA LXJAAA HHHHxx
+9357 6694 1 1 7 17 57 357 1357 4357 9357 114 115 XVAAAA MXJAAA OOOOxx
+4469 6695 1 1 9 9 69 469 469 4469 4469 138 139 XPAAAA NXJAAA VVVVxx
+2147 6696 1 3 7 7 47 147 147 2147 2147 94 95 PEAAAA OXJAAA AAAAxx
+2952 6697 0 0 2 12 52 952 952 2952 2952 104 105 OJAAAA PXJAAA HHHHxx
+1324 6698 0 0 4 4 24 324 1324 1324 1324 48 49 YYAAAA QXJAAA OOOOxx
+1173 6699 1 1 3 13 73 173 1173 1173 1173 146 147 DTAAAA RXJAAA VVVVxx
+3169 6700 1 1 9 9 69 169 1169 3169 3169 138 139 XRAAAA SXJAAA AAAAxx
+5149 6701 1 1 9 9 49 149 1149 149 5149 98 99 BQAAAA TXJAAA HHHHxx
+9660 6702 0 0 0 0 60 660 1660 4660 9660 120 121 OHAAAA UXJAAA OOOOxx
+3446 6703 0 2 6 6 46 446 1446 3446 3446 92 93 OCAAAA VXJAAA VVVVxx
+6988 6704 0 0 8 8 88 988 988 1988 6988 176 177 UIAAAA WXJAAA AAAAxx
+5829 6705 1 1 9 9 29 829 1829 829 5829 58 59 FQAAAA XXJAAA HHHHxx
+7166 6706 0 2 6 6 66 166 1166 2166 7166 132 133 QPAAAA YXJAAA OOOOxx
+3940 6707 0 0 0 0 40 940 1940 3940 3940 80 81 OVAAAA ZXJAAA VVVVxx
+2645 6708 1 1 5 5 45 645 645 2645 2645 90 91 TXAAAA AYJAAA AAAAxx
+478 6709 0 2 8 18 78 478 478 478 478 156 157 KSAAAA BYJAAA HHHHxx
+1156 6710 0 0 6 16 56 156 1156 1156 1156 112 113 MSAAAA CYJAAA OOOOxx
+2731 6711 1 3 1 11 31 731 731 2731 2731 62 63 BBAAAA DYJAAA VVVVxx
+5637 6712 1 1 7 17 37 637 1637 637 5637 74 75 VIAAAA EYJAAA AAAAxx
+7517 6713 1 1 7 17 17 517 1517 2517 7517 34 35 DDAAAA FYJAAA HHHHxx
+5331 6714 1 3 1 11 31 331 1331 331 5331 62 63 BXAAAA GYJAAA OOOOxx
+9640 6715 0 0 0 0 40 640 1640 4640 9640 80 81 UGAAAA HYJAAA VVVVxx
+4108 6716 0 0 8 8 8 108 108 4108 4108 16 17 ACAAAA IYJAAA AAAAxx
+1087 6717 1 3 7 7 87 87 1087 1087 1087 174 175 VPAAAA JYJAAA HHHHxx
+8017 6718 1 1 7 17 17 17 17 3017 8017 34 35 JWAAAA KYJAAA OOOOxx
+8795 6719 1 3 5 15 95 795 795 3795 8795 190 191 HAAAAA LYJAAA VVVVxx
+7060 6720 0 0 0 0 60 60 1060 2060 7060 120 121 OLAAAA MYJAAA AAAAxx
+9450 6721 0 2 0 10 50 450 1450 4450 9450 100 101 MZAAAA NYJAAA HHHHxx
+390 6722 0 2 0 10 90 390 390 390 390 180 181 APAAAA OYJAAA OOOOxx
+66 6723 0 2 6 6 66 66 66 66 66 132 133 OCAAAA PYJAAA VVVVxx
+8789 6724 1 1 9 9 89 789 789 3789 8789 178 179 BAAAAA QYJAAA AAAAxx
+9260 6725 0 0 0 0 60 260 1260 4260 9260 120 121 ESAAAA RYJAAA HHHHxx
+6679 6726 1 3 9 19 79 679 679 1679 6679 158 159 XWAAAA SYJAAA OOOOxx
+9052 6727 0 0 2 12 52 52 1052 4052 9052 104 105 EKAAAA TYJAAA VVVVxx
+9561 6728 1 1 1 1 61 561 1561 4561 9561 122 123 TDAAAA UYJAAA AAAAxx
+9725 6729 1 1 5 5 25 725 1725 4725 9725 50 51 BKAAAA VYJAAA HHHHxx
+6298 6730 0 2 8 18 98 298 298 1298 6298 196 197 GIAAAA WYJAAA OOOOxx
+8654 6731 0 2 4 14 54 654 654 3654 8654 108 109 WUAAAA XYJAAA VVVVxx
+8725 6732 1 1 5 5 25 725 725 3725 8725 50 51 PXAAAA YYJAAA AAAAxx
+9377 6733 1 1 7 17 77 377 1377 4377 9377 154 155 RWAAAA ZYJAAA HHHHxx
+3807 6734 1 3 7 7 7 807 1807 3807 3807 14 15 LQAAAA AZJAAA OOOOxx
+8048 6735 0 0 8 8 48 48 48 3048 8048 96 97 OXAAAA BZJAAA VVVVxx
+764 6736 0 0 4 4 64 764 764 764 764 128 129 KDAAAA CZJAAA AAAAxx
+9702 6737 0 2 2 2 2 702 1702 4702 9702 4 5 EJAAAA DZJAAA HHHHxx
+8060 6738 0 0 0 0 60 60 60 3060 8060 120 121 AYAAAA EZJAAA OOOOxx
+6371 6739 1 3 1 11 71 371 371 1371 6371 142 143 BLAAAA FZJAAA VVVVxx
+5237 6740 1 1 7 17 37 237 1237 237 5237 74 75 LTAAAA GZJAAA AAAAxx
+743 6741 1 3 3 3 43 743 743 743 743 86 87 PCAAAA HZJAAA HHHHxx
+7395 6742 1 3 5 15 95 395 1395 2395 7395 190 191 LYAAAA IZJAAA OOOOxx
+3365 6743 1 1 5 5 65 365 1365 3365 3365 130 131 LZAAAA JZJAAA VVVVxx
+6667 6744 1 3 7 7 67 667 667 1667 6667 134 135 LWAAAA KZJAAA AAAAxx
+3445 6745 1 1 5 5 45 445 1445 3445 3445 90 91 NCAAAA LZJAAA HHHHxx
+4019 6746 1 3 9 19 19 19 19 4019 4019 38 39 PYAAAA MZJAAA OOOOxx
+7035 6747 1 3 5 15 35 35 1035 2035 7035 70 71 PKAAAA NZJAAA VVVVxx
+5274 6748 0 2 4 14 74 274 1274 274 5274 148 149 WUAAAA OZJAAA AAAAxx
+519 6749 1 3 9 19 19 519 519 519 519 38 39 ZTAAAA PZJAAA HHHHxx
+2801 6750 1 1 1 1 1 801 801 2801 2801 2 3 TDAAAA QZJAAA OOOOxx
+3320 6751 0 0 0 0 20 320 1320 3320 3320 40 41 SXAAAA RZJAAA VVVVxx
+3153 6752 1 1 3 13 53 153 1153 3153 3153 106 107 HRAAAA SZJAAA AAAAxx
+7680 6753 0 0 0 0 80 680 1680 2680 7680 160 161 KJAAAA TZJAAA HHHHxx
+8942 6754 0 2 2 2 42 942 942 3942 8942 84 85 YFAAAA UZJAAA OOOOxx
+3195 6755 1 3 5 15 95 195 1195 3195 3195 190 191 XSAAAA VZJAAA VVVVxx
+2287 6756 1 3 7 7 87 287 287 2287 2287 174 175 ZJAAAA WZJAAA AAAAxx
+8325 6757 1 1 5 5 25 325 325 3325 8325 50 51 FIAAAA XZJAAA HHHHxx
+2603 6758 1 3 3 3 3 603 603 2603 2603 6 7 DWAAAA YZJAAA OOOOxx
+5871 6759 1 3 1 11 71 871 1871 871 5871 142 143 VRAAAA ZZJAAA VVVVxx
+1773 6760 1 1 3 13 73 773 1773 1773 1773 146 147 FQAAAA AAKAAA AAAAxx
+3323 6761 1 3 3 3 23 323 1323 3323 3323 46 47 VXAAAA BAKAAA HHHHxx
+2053 6762 1 1 3 13 53 53 53 2053 2053 106 107 ZAAAAA CAKAAA OOOOxx
+4062 6763 0 2 2 2 62 62 62 4062 4062 124 125 GAAAAA DAKAAA VVVVxx
+4611 6764 1 3 1 11 11 611 611 4611 4611 22 23 JVAAAA EAKAAA AAAAxx
+3451 6765 1 3 1 11 51 451 1451 3451 3451 102 103 TCAAAA FAKAAA HHHHxx
+1819 6766 1 3 9 19 19 819 1819 1819 1819 38 39 ZRAAAA GAKAAA OOOOxx
+9806 6767 0 2 6 6 6 806 1806 4806 9806 12 13 ENAAAA HAKAAA VVVVxx
+6619 6768 1 3 9 19 19 619 619 1619 6619 38 39 PUAAAA IAKAAA AAAAxx
+1031 6769 1 3 1 11 31 31 1031 1031 1031 62 63 RNAAAA JAKAAA HHHHxx
+1865 6770 1 1 5 5 65 865 1865 1865 1865 130 131 TTAAAA KAKAAA OOOOxx
+6282 6771 0 2 2 2 82 282 282 1282 6282 164 165 QHAAAA LAKAAA VVVVxx
+1178 6772 0 2 8 18 78 178 1178 1178 1178 156 157 ITAAAA MAKAAA AAAAxx
+8007 6773 1 3 7 7 7 7 7 3007 8007 14 15 ZVAAAA NAKAAA HHHHxx
+9126 6774 0 2 6 6 26 126 1126 4126 9126 52 53 ANAAAA OAKAAA OOOOxx
+9113 6775 1 1 3 13 13 113 1113 4113 9113 26 27 NMAAAA PAKAAA VVVVxx
+537 6776 1 1 7 17 37 537 537 537 537 74 75 RUAAAA QAKAAA AAAAxx
+6208 6777 0 0 8 8 8 208 208 1208 6208 16 17 UEAAAA RAKAAA HHHHxx
+1626 6778 0 2 6 6 26 626 1626 1626 1626 52 53 OKAAAA SAKAAA OOOOxx
+7188 6779 0 0 8 8 88 188 1188 2188 7188 176 177 MQAAAA TAKAAA VVVVxx
+9216 6780 0 0 6 16 16 216 1216 4216 9216 32 33 MQAAAA UAKAAA AAAAxx
+6134 6781 0 2 4 14 34 134 134 1134 6134 68 69 YBAAAA VAKAAA HHHHxx
+2074 6782 0 2 4 14 74 74 74 2074 2074 148 149 UBAAAA WAKAAA OOOOxx
+6369 6783 1 1 9 9 69 369 369 1369 6369 138 139 ZKAAAA XAKAAA VVVVxx
+9306 6784 0 2 6 6 6 306 1306 4306 9306 12 13 YTAAAA YAKAAA AAAAxx
+3155 6785 1 3 5 15 55 155 1155 3155 3155 110 111 JRAAAA ZAKAAA HHHHxx
+3611 6786 1 3 1 11 11 611 1611 3611 3611 22 23 XIAAAA ABKAAA OOOOxx
+6530 6787 0 2 0 10 30 530 530 1530 6530 60 61 ERAAAA BBKAAA VVVVxx
+6979 6788 1 3 9 19 79 979 979 1979 6979 158 159 LIAAAA CBKAAA AAAAxx
+9129 6789 1 1 9 9 29 129 1129 4129 9129 58 59 DNAAAA DBKAAA HHHHxx
+8013 6790 1 1 3 13 13 13 13 3013 8013 26 27 FWAAAA EBKAAA OOOOxx
+6926 6791 0 2 6 6 26 926 926 1926 6926 52 53 KGAAAA FBKAAA VVVVxx
+1877 6792 1 1 7 17 77 877 1877 1877 1877 154 155 FUAAAA GBKAAA AAAAxx
+1882 6793 0 2 2 2 82 882 1882 1882 1882 164 165 KUAAAA HBKAAA HHHHxx
+6720 6794 0 0 0 0 20 720 720 1720 6720 40 41 MYAAAA IBKAAA OOOOxx
+690 6795 0 2 0 10 90 690 690 690 690 180 181 OAAAAA JBKAAA VVVVxx
+143 6796 1 3 3 3 43 143 143 143 143 86 87 NFAAAA KBKAAA AAAAxx
+7241 6797 1 1 1 1 41 241 1241 2241 7241 82 83 NSAAAA LBKAAA HHHHxx
+6461 6798 1 1 1 1 61 461 461 1461 6461 122 123 NOAAAA MBKAAA OOOOxx
+2258 6799 0 2 8 18 58 258 258 2258 2258 116 117 WIAAAA NBKAAA VVVVxx
+2280 6800 0 0 0 0 80 280 280 2280 2280 160 161 SJAAAA OBKAAA AAAAxx
+7556 6801 0 0 6 16 56 556 1556 2556 7556 112 113 QEAAAA PBKAAA HHHHxx
+1038 6802 0 2 8 18 38 38 1038 1038 1038 76 77 YNAAAA QBKAAA OOOOxx
+2634 6803 0 2 4 14 34 634 634 2634 2634 68 69 IXAAAA RBKAAA VVVVxx
+7847 6804 1 3 7 7 47 847 1847 2847 7847 94 95 VPAAAA SBKAAA AAAAxx
+4415 6805 1 3 5 15 15 415 415 4415 4415 30 31 VNAAAA TBKAAA HHHHxx
+1933 6806 1 1 3 13 33 933 1933 1933 1933 66 67 JWAAAA UBKAAA OOOOxx
+8034 6807 0 2 4 14 34 34 34 3034 8034 68 69 AXAAAA VBKAAA VVVVxx
+9233 6808 1 1 3 13 33 233 1233 4233 9233 66 67 DRAAAA WBKAAA AAAAxx
+6572 6809 0 0 2 12 72 572 572 1572 6572 144 145 USAAAA XBKAAA HHHHxx
+1586 6810 0 2 6 6 86 586 1586 1586 1586 172 173 AJAAAA YBKAAA OOOOxx
+8512 6811 0 0 2 12 12 512 512 3512 8512 24 25 KPAAAA ZBKAAA VVVVxx
+7421 6812 1 1 1 1 21 421 1421 2421 7421 42 43 LZAAAA ACKAAA AAAAxx
+503 6813 1 3 3 3 3 503 503 503 503 6 7 JTAAAA BCKAAA HHHHxx
+5332 6814 0 0 2 12 32 332 1332 332 5332 64 65 CXAAAA CCKAAA OOOOxx
+2602 6815 0 2 2 2 2 602 602 2602 2602 4 5 CWAAAA DCKAAA VVVVxx
+2902 6816 0 2 2 2 2 902 902 2902 2902 4 5 QHAAAA ECKAAA AAAAxx
+2979 6817 1 3 9 19 79 979 979 2979 2979 158 159 PKAAAA FCKAAA HHHHxx
+1431 6818 1 3 1 11 31 431 1431 1431 1431 62 63 BDAAAA GCKAAA OOOOxx
+8639 6819 1 3 9 19 39 639 639 3639 8639 78 79 HUAAAA HCKAAA VVVVxx
+4218 6820 0 2 8 18 18 218 218 4218 4218 36 37 GGAAAA ICKAAA AAAAxx
+7453 6821 1 1 3 13 53 453 1453 2453 7453 106 107 RAAAAA JCKAAA HHHHxx
+5448 6822 0 0 8 8 48 448 1448 448 5448 96 97 OBAAAA KCKAAA OOOOxx
+6768 6823 0 0 8 8 68 768 768 1768 6768 136 137 IAAAAA LCKAAA VVVVxx
+3104 6824 0 0 4 4 4 104 1104 3104 3104 8 9 KPAAAA MCKAAA AAAAxx
+2297 6825 1 1 7 17 97 297 297 2297 2297 194 195 JKAAAA NCKAAA HHHHxx
+7994 6826 0 2 4 14 94 994 1994 2994 7994 188 189 MVAAAA OCKAAA OOOOxx
+550 6827 0 2 0 10 50 550 550 550 550 100 101 EVAAAA PCKAAA VVVVxx
+4777 6828 1 1 7 17 77 777 777 4777 4777 154 155 TBAAAA QCKAAA AAAAxx
+5962 6829 0 2 2 2 62 962 1962 962 5962 124 125 IVAAAA RCKAAA HHHHxx
+1763 6830 1 3 3 3 63 763 1763 1763 1763 126 127 VPAAAA SCKAAA OOOOxx
+3654 6831 0 2 4 14 54 654 1654 3654 3654 108 109 OKAAAA TCKAAA VVVVxx
+4106 6832 0 2 6 6 6 106 106 4106 4106 12 13 YBAAAA UCKAAA AAAAxx
+5156 6833 0 0 6 16 56 156 1156 156 5156 112 113 IQAAAA VCKAAA HHHHxx
+422 6834 0 2 2 2 22 422 422 422 422 44 45 GQAAAA WCKAAA OOOOxx
+5011 6835 1 3 1 11 11 11 1011 11 5011 22 23 TKAAAA XCKAAA VVVVxx
+218 6836 0 2 8 18 18 218 218 218 218 36 37 KIAAAA YCKAAA AAAAxx
+9762 6837 0 2 2 2 62 762 1762 4762 9762 124 125 MLAAAA ZCKAAA HHHHxx
+6074 6838 0 2 4 14 74 74 74 1074 6074 148 149 QZAAAA ADKAAA OOOOxx
+4060 6839 0 0 0 0 60 60 60 4060 4060 120 121 EAAAAA BDKAAA VVVVxx
+8680 6840 0 0 0 0 80 680 680 3680 8680 160 161 WVAAAA CDKAAA AAAAxx
+5863 6841 1 3 3 3 63 863 1863 863 5863 126 127 NRAAAA DDKAAA HHHHxx
+8042 6842 0 2 2 2 42 42 42 3042 8042 84 85 IXAAAA EDKAAA OOOOxx
+2964 6843 0 0 4 4 64 964 964 2964 2964 128 129 AKAAAA FDKAAA VVVVxx
+6931 6844 1 3 1 11 31 931 931 1931 6931 62 63 PGAAAA GDKAAA AAAAxx
+6715 6845 1 3 5 15 15 715 715 1715 6715 30 31 HYAAAA HDKAAA HHHHxx
+5859 6846 1 3 9 19 59 859 1859 859 5859 118 119 JRAAAA IDKAAA OOOOxx
+6173 6847 1 1 3 13 73 173 173 1173 6173 146 147 LDAAAA JDKAAA VVVVxx
+7788 6848 0 0 8 8 88 788 1788 2788 7788 176 177 ONAAAA KDKAAA AAAAxx
+9370 6849 0 2 0 10 70 370 1370 4370 9370 140 141 KWAAAA LDKAAA HHHHxx
+3038 6850 0 2 8 18 38 38 1038 3038 3038 76 77 WMAAAA MDKAAA OOOOxx
+6483 6851 1 3 3 3 83 483 483 1483 6483 166 167 JPAAAA NDKAAA VVVVxx
+7534 6852 0 2 4 14 34 534 1534 2534 7534 68 69 UDAAAA ODKAAA AAAAxx
+5769 6853 1 1 9 9 69 769 1769 769 5769 138 139 XNAAAA PDKAAA HHHHxx
+9152 6854 0 0 2 12 52 152 1152 4152 9152 104 105 AOAAAA QDKAAA OOOOxx
+6251 6855 1 3 1 11 51 251 251 1251 6251 102 103 LGAAAA RDKAAA VVVVxx
+9209 6856 1 1 9 9 9 209 1209 4209 9209 18 19 FQAAAA SDKAAA AAAAxx
+5365 6857 1 1 5 5 65 365 1365 365 5365 130 131 JYAAAA TDKAAA HHHHxx
+509 6858 1 1 9 9 9 509 509 509 509 18 19 PTAAAA UDKAAA OOOOxx
+3132 6859 0 0 2 12 32 132 1132 3132 3132 64 65 MQAAAA VDKAAA VVVVxx
+5373 6860 1 1 3 13 73 373 1373 373 5373 146 147 RYAAAA WDKAAA AAAAxx
+4247 6861 1 3 7 7 47 247 247 4247 4247 94 95 JHAAAA XDKAAA HHHHxx
+3491 6862 1 3 1 11 91 491 1491 3491 3491 182 183 HEAAAA YDKAAA OOOOxx
+495 6863 1 3 5 15 95 495 495 495 495 190 191 BTAAAA ZDKAAA VVVVxx
+1594 6864 0 2 4 14 94 594 1594 1594 1594 188 189 IJAAAA AEKAAA AAAAxx
+2243 6865 1 3 3 3 43 243 243 2243 2243 86 87 HIAAAA BEKAAA HHHHxx
+7780 6866 0 0 0 0 80 780 1780 2780 7780 160 161 GNAAAA CEKAAA OOOOxx
+5632 6867 0 0 2 12 32 632 1632 632 5632 64 65 QIAAAA DEKAAA VVVVxx
+2679 6868 1 3 9 19 79 679 679 2679 2679 158 159 BZAAAA EEKAAA AAAAxx
+1354 6869 0 2 4 14 54 354 1354 1354 1354 108 109 CAAAAA FEKAAA HHHHxx
+180 6870 0 0 0 0 80 180 180 180 180 160 161 YGAAAA GEKAAA OOOOxx
+7017 6871 1 1 7 17 17 17 1017 2017 7017 34 35 XJAAAA HEKAAA VVVVxx
+1867 6872 1 3 7 7 67 867 1867 1867 1867 134 135 VTAAAA IEKAAA AAAAxx
+2213 6873 1 1 3 13 13 213 213 2213 2213 26 27 DHAAAA JEKAAA HHHHxx
+8773 6874 1 1 3 13 73 773 773 3773 8773 146 147 LZAAAA KEKAAA OOOOxx
+1784 6875 0 0 4 4 84 784 1784 1784 1784 168 169 QQAAAA LEKAAA VVVVxx
+5961 6876 1 1 1 1 61 961 1961 961 5961 122 123 HVAAAA MEKAAA AAAAxx
+8801 6877 1 1 1 1 1 801 801 3801 8801 2 3 NAAAAA NEKAAA HHHHxx
+4860 6878 0 0 0 0 60 860 860 4860 4860 120 121 YEAAAA OEKAAA OOOOxx
+2214 6879 0 2 4 14 14 214 214 2214 2214 28 29 EHAAAA PEKAAA VVVVxx
+1735 6880 1 3 5 15 35 735 1735 1735 1735 70 71 TOAAAA QEKAAA AAAAxx
+578 6881 0 2 8 18 78 578 578 578 578 156 157 GWAAAA REKAAA HHHHxx
+7853 6882 1 1 3 13 53 853 1853 2853 7853 106 107 BQAAAA SEKAAA OOOOxx
+2215 6883 1 3 5 15 15 215 215 2215 2215 30 31 FHAAAA TEKAAA VVVVxx
+4704 6884 0 0 4 4 4 704 704 4704 4704 8 9 YYAAAA UEKAAA AAAAxx
+9379 6885 1 3 9 19 79 379 1379 4379 9379 158 159 TWAAAA VEKAAA HHHHxx
+9745 6886 1 1 5 5 45 745 1745 4745 9745 90 91 VKAAAA WEKAAA OOOOxx
+5636 6887 0 0 6 16 36 636 1636 636 5636 72 73 UIAAAA XEKAAA VVVVxx
+4548 6888 0 0 8 8 48 548 548 4548 4548 96 97 YSAAAA YEKAAA AAAAxx
+6537 6889 1 1 7 17 37 537 537 1537 6537 74 75 LRAAAA ZEKAAA HHHHxx
+7748 6890 0 0 8 8 48 748 1748 2748 7748 96 97 AMAAAA AFKAAA OOOOxx
+687 6891 1 3 7 7 87 687 687 687 687 174 175 LAAAAA BFKAAA VVVVxx
+1243 6892 1 3 3 3 43 243 1243 1243 1243 86 87 VVAAAA CFKAAA AAAAxx
+852 6893 0 0 2 12 52 852 852 852 852 104 105 UGAAAA DFKAAA HHHHxx
+785 6894 1 1 5 5 85 785 785 785 785 170 171 FEAAAA EFKAAA OOOOxx
+2002 6895 0 2 2 2 2 2 2 2002 2002 4 5 AZAAAA FFKAAA VVVVxx
+2748 6896 0 0 8 8 48 748 748 2748 2748 96 97 SBAAAA GFKAAA AAAAxx
+6075 6897 1 3 5 15 75 75 75 1075 6075 150 151 RZAAAA HFKAAA HHHHxx
+7029 6898 1 1 9 9 29 29 1029 2029 7029 58 59 JKAAAA IFKAAA OOOOxx
+7474 6899 0 2 4 14 74 474 1474 2474 7474 148 149 MBAAAA JFKAAA VVVVxx
+7755 6900 1 3 5 15 55 755 1755 2755 7755 110 111 HMAAAA KFKAAA AAAAxx
+1456 6901 0 0 6 16 56 456 1456 1456 1456 112 113 AEAAAA LFKAAA HHHHxx
+2808 6902 0 0 8 8 8 808 808 2808 2808 16 17 AEAAAA MFKAAA OOOOxx
+4089 6903 1 1 9 9 89 89 89 4089 4089 178 179 HBAAAA NFKAAA VVVVxx
+4718 6904 0 2 8 18 18 718 718 4718 4718 36 37 MZAAAA OFKAAA AAAAxx
+910 6905 0 2 0 10 10 910 910 910 910 20 21 AJAAAA PFKAAA HHHHxx
+2868 6906 0 0 8 8 68 868 868 2868 2868 136 137 IGAAAA QFKAAA OOOOxx
+2103 6907 1 3 3 3 3 103 103 2103 2103 6 7 XCAAAA RFKAAA VVVVxx
+2407 6908 1 3 7 7 7 407 407 2407 2407 14 15 POAAAA SFKAAA AAAAxx
+4353 6909 1 1 3 13 53 353 353 4353 4353 106 107 LLAAAA TFKAAA HHHHxx
+7988 6910 0 0 8 8 88 988 1988 2988 7988 176 177 GVAAAA UFKAAA OOOOxx
+2750 6911 0 2 0 10 50 750 750 2750 2750 100 101 UBAAAA VFKAAA VVVVxx
+2006 6912 0 2 6 6 6 6 6 2006 2006 12 13 EZAAAA WFKAAA AAAAxx
+4617 6913 1 1 7 17 17 617 617 4617 4617 34 35 PVAAAA XFKAAA HHHHxx
+1251 6914 1 3 1 11 51 251 1251 1251 1251 102 103 DWAAAA YFKAAA OOOOxx
+4590 6915 0 2 0 10 90 590 590 4590 4590 180 181 OUAAAA ZFKAAA VVVVxx
+1144 6916 0 0 4 4 44 144 1144 1144 1144 88 89 ASAAAA AGKAAA AAAAxx
+7131 6917 1 3 1 11 31 131 1131 2131 7131 62 63 HOAAAA BGKAAA HHHHxx
+95 6918 1 3 5 15 95 95 95 95 95 190 191 RDAAAA CGKAAA OOOOxx
+4827 6919 1 3 7 7 27 827 827 4827 4827 54 55 RDAAAA DGKAAA VVVVxx
+4307 6920 1 3 7 7 7 307 307 4307 4307 14 15 RJAAAA EGKAAA AAAAxx
+1505 6921 1 1 5 5 5 505 1505 1505 1505 10 11 XFAAAA FGKAAA HHHHxx
+8191 6922 1 3 1 11 91 191 191 3191 8191 182 183 BDAAAA GGKAAA OOOOxx
+5037 6923 1 1 7 17 37 37 1037 37 5037 74 75 TLAAAA HGKAAA VVVVxx
+7363 6924 1 3 3 3 63 363 1363 2363 7363 126 127 FXAAAA IGKAAA AAAAxx
+8427 6925 1 3 7 7 27 427 427 3427 8427 54 55 DMAAAA JGKAAA HHHHxx
+5231 6926 1 3 1 11 31 231 1231 231 5231 62 63 FTAAAA KGKAAA OOOOxx
+2943 6927 1 3 3 3 43 943 943 2943 2943 86 87 FJAAAA LGKAAA VVVVxx
+4624 6928 0 0 4 4 24 624 624 4624 4624 48 49 WVAAAA MGKAAA AAAAxx
+2020 6929 0 0 0 0 20 20 20 2020 2020 40 41 SZAAAA NGKAAA HHHHxx
+6155 6930 1 3 5 15 55 155 155 1155 6155 110 111 TCAAAA OGKAAA OOOOxx
+4381 6931 1 1 1 1 81 381 381 4381 4381 162 163 NMAAAA PGKAAA VVVVxx
+1057 6932 1 1 7 17 57 57 1057 1057 1057 114 115 ROAAAA QGKAAA AAAAxx
+9010 6933 0 2 0 10 10 10 1010 4010 9010 20 21 OIAAAA RGKAAA HHHHxx
+4947 6934 1 3 7 7 47 947 947 4947 4947 94 95 HIAAAA SGKAAA OOOOxx
+335 6935 1 3 5 15 35 335 335 335 335 70 71 XMAAAA TGKAAA VVVVxx
+6890 6936 0 2 0 10 90 890 890 1890 6890 180 181 AFAAAA UGKAAA AAAAxx
+5070 6937 0 2 0 10 70 70 1070 70 5070 140 141 ANAAAA VGKAAA HHHHxx
+5270 6938 0 2 0 10 70 270 1270 270 5270 140 141 SUAAAA WGKAAA OOOOxx
+8657 6939 1 1 7 17 57 657 657 3657 8657 114 115 ZUAAAA XGKAAA VVVVxx
+7625 6940 1 1 5 5 25 625 1625 2625 7625 50 51 HHAAAA YGKAAA AAAAxx
+5759 6941 1 3 9 19 59 759 1759 759 5759 118 119 NNAAAA ZGKAAA HHHHxx
+9483 6942 1 3 3 3 83 483 1483 4483 9483 166 167 TAAAAA AHKAAA OOOOxx
+8304 6943 0 0 4 4 4 304 304 3304 8304 8 9 KHAAAA BHKAAA VVVVxx
+296 6944 0 0 6 16 96 296 296 296 296 192 193 KLAAAA CHKAAA AAAAxx
+1176 6945 0 0 6 16 76 176 1176 1176 1176 152 153 GTAAAA DHKAAA HHHHxx
+2069 6946 1 1 9 9 69 69 69 2069 2069 138 139 PBAAAA EHKAAA OOOOxx
+1531 6947 1 3 1 11 31 531 1531 1531 1531 62 63 XGAAAA FHKAAA VVVVxx
+5329 6948 1 1 9 9 29 329 1329 329 5329 58 59 ZWAAAA GHKAAA AAAAxx
+3702 6949 0 2 2 2 2 702 1702 3702 3702 4 5 KMAAAA HHKAAA HHHHxx
+6520 6950 0 0 0 0 20 520 520 1520 6520 40 41 UQAAAA IHKAAA OOOOxx
+7310 6951 0 2 0 10 10 310 1310 2310 7310 20 21 EVAAAA JHKAAA VVVVxx
+1175 6952 1 3 5 15 75 175 1175 1175 1175 150 151 FTAAAA KHKAAA AAAAxx
+9107 6953 1 3 7 7 7 107 1107 4107 9107 14 15 HMAAAA LHKAAA HHHHxx
+2737 6954 1 1 7 17 37 737 737 2737 2737 74 75 HBAAAA MHKAAA OOOOxx
+3437 6955 1 1 7 17 37 437 1437 3437 3437 74 75 FCAAAA NHKAAA VVVVxx
+281 6956 1 1 1 1 81 281 281 281 281 162 163 VKAAAA OHKAAA AAAAxx
+6676 6957 0 0 6 16 76 676 676 1676 6676 152 153 UWAAAA PHKAAA HHHHxx
+145 6958 1 1 5 5 45 145 145 145 145 90 91 PFAAAA QHKAAA OOOOxx
+3172 6959 0 0 2 12 72 172 1172 3172 3172 144 145 ASAAAA RHKAAA VVVVxx
+4049 6960 1 1 9 9 49 49 49 4049 4049 98 99 TZAAAA SHKAAA AAAAxx
+6042 6961 0 2 2 2 42 42 42 1042 6042 84 85 KYAAAA THKAAA HHHHxx
+9122 6962 0 2 2 2 22 122 1122 4122 9122 44 45 WMAAAA UHKAAA OOOOxx
+7244 6963 0 0 4 4 44 244 1244 2244 7244 88 89 QSAAAA VHKAAA VVVVxx
+5361 6964 1 1 1 1 61 361 1361 361 5361 122 123 FYAAAA WHKAAA AAAAxx
+8647 6965 1 3 7 7 47 647 647 3647 8647 94 95 PUAAAA XHKAAA HHHHxx
+7956 6966 0 0 6 16 56 956 1956 2956 7956 112 113 AUAAAA YHKAAA OOOOxx
+7812 6967 0 0 2 12 12 812 1812 2812 7812 24 25 MOAAAA ZHKAAA VVVVxx
+570 6968 0 2 0 10 70 570 570 570 570 140 141 YVAAAA AIKAAA AAAAxx
+4115 6969 1 3 5 15 15 115 115 4115 4115 30 31 HCAAAA BIKAAA HHHHxx
+1856 6970 0 0 6 16 56 856 1856 1856 1856 112 113 KTAAAA CIKAAA OOOOxx
+9582 6971 0 2 2 2 82 582 1582 4582 9582 164 165 OEAAAA DIKAAA VVVVxx
+2025 6972 1 1 5 5 25 25 25 2025 2025 50 51 XZAAAA EIKAAA AAAAxx
+986 6973 0 2 6 6 86 986 986 986 986 172 173 YLAAAA FIKAAA HHHHxx
+8358 6974 0 2 8 18 58 358 358 3358 8358 116 117 MJAAAA GIKAAA OOOOxx
+510 6975 0 2 0 10 10 510 510 510 510 20 21 QTAAAA HIKAAA VVVVxx
+6101 6976 1 1 1 1 1 101 101 1101 6101 2 3 RAAAAA IIKAAA AAAAxx
+4167 6977 1 3 7 7 67 167 167 4167 4167 134 135 HEAAAA JIKAAA HHHHxx
+6139 6978 1 3 9 19 39 139 139 1139 6139 78 79 DCAAAA KIKAAA OOOOxx
+6912 6979 0 0 2 12 12 912 912 1912 6912 24 25 WFAAAA LIKAAA VVVVxx
+339 6980 1 3 9 19 39 339 339 339 339 78 79 BNAAAA MIKAAA AAAAxx
+8759 6981 1 3 9 19 59 759 759 3759 8759 118 119 XYAAAA NIKAAA HHHHxx
+246 6982 0 2 6 6 46 246 246 246 246 92 93 MJAAAA OIKAAA OOOOxx
+2831 6983 1 3 1 11 31 831 831 2831 2831 62 63 XEAAAA PIKAAA VVVVxx
+2327 6984 1 3 7 7 27 327 327 2327 2327 54 55 NLAAAA QIKAAA AAAAxx
+7001 6985 1 1 1 1 1 1 1001 2001 7001 2 3 HJAAAA RIKAAA HHHHxx
+4398 6986 0 2 8 18 98 398 398 4398 4398 196 197 ENAAAA SIKAAA OOOOxx
+1495 6987 1 3 5 15 95 495 1495 1495 1495 190 191 NFAAAA TIKAAA VVVVxx
+8522 6988 0 2 2 2 22 522 522 3522 8522 44 45 UPAAAA UIKAAA AAAAxx
+7090 6989 0 2 0 10 90 90 1090 2090 7090 180 181 SMAAAA VIKAAA HHHHxx
+8457 6990 1 1 7 17 57 457 457 3457 8457 114 115 HNAAAA WIKAAA OOOOxx
+4238 6991 0 2 8 18 38 238 238 4238 4238 76 77 AHAAAA XIKAAA VVVVxx
+6791 6992 1 3 1 11 91 791 791 1791 6791 182 183 FBAAAA YIKAAA AAAAxx
+1342 6993 0 2 2 2 42 342 1342 1342 1342 84 85 QZAAAA ZIKAAA HHHHxx
+4580 6994 0 0 0 0 80 580 580 4580 4580 160 161 EUAAAA AJKAAA OOOOxx
+1475 6995 1 3 5 15 75 475 1475 1475 1475 150 151 TEAAAA BJKAAA VVVVxx
+9184 6996 0 0 4 4 84 184 1184 4184 9184 168 169 GPAAAA CJKAAA AAAAxx
+1189 6997 1 1 9 9 89 189 1189 1189 1189 178 179 TTAAAA DJKAAA HHHHxx
+638 6998 0 2 8 18 38 638 638 638 638 76 77 OYAAAA EJKAAA OOOOxx
+5867 6999 1 3 7 7 67 867 1867 867 5867 134 135 RRAAAA FJKAAA VVVVxx
+9911 7000 1 3 1 11 11 911 1911 4911 9911 22 23 FRAAAA GJKAAA AAAAxx
+8147 7001 1 3 7 7 47 147 147 3147 8147 94 95 JBAAAA HJKAAA HHHHxx
+4492 7002 0 0 2 12 92 492 492 4492 4492 184 185 UQAAAA IJKAAA OOOOxx
+385 7003 1 1 5 5 85 385 385 385 385 170 171 VOAAAA JJKAAA VVVVxx
+5235 7004 1 3 5 15 35 235 1235 235 5235 70 71 JTAAAA KJKAAA AAAAxx
+4812 7005 0 0 2 12 12 812 812 4812 4812 24 25 CDAAAA LJKAAA HHHHxx
+9807 7006 1 3 7 7 7 807 1807 4807 9807 14 15 FNAAAA MJKAAA OOOOxx
+9588 7007 0 0 8 8 88 588 1588 4588 9588 176 177 UEAAAA NJKAAA VVVVxx
+9832 7008 0 0 2 12 32 832 1832 4832 9832 64 65 EOAAAA OJKAAA AAAAxx
+3757 7009 1 1 7 17 57 757 1757 3757 3757 114 115 NOAAAA PJKAAA HHHHxx
+9703 7010 1 3 3 3 3 703 1703 4703 9703 6 7 FJAAAA QJKAAA OOOOxx
+1022 7011 0 2 2 2 22 22 1022 1022 1022 44 45 INAAAA RJKAAA VVVVxx
+5165 7012 1 1 5 5 65 165 1165 165 5165 130 131 RQAAAA SJKAAA AAAAxx
+7129 7013 1 1 9 9 29 129 1129 2129 7129 58 59 FOAAAA TJKAAA HHHHxx
+4164 7014 0 0 4 4 64 164 164 4164 4164 128 129 EEAAAA UJKAAA OOOOxx
+7239 7015 1 3 9 19 39 239 1239 2239 7239 78 79 LSAAAA VJKAAA VVVVxx
+523 7016 1 3 3 3 23 523 523 523 523 46 47 DUAAAA WJKAAA AAAAxx
+4670 7017 0 2 0 10 70 670 670 4670 4670 140 141 QXAAAA XJKAAA HHHHxx
+8503 7018 1 3 3 3 3 503 503 3503 8503 6 7 BPAAAA YJKAAA OOOOxx
+714 7019 0 2 4 14 14 714 714 714 714 28 29 MBAAAA ZJKAAA VVVVxx
+1350 7020 0 2 0 10 50 350 1350 1350 1350 100 101 YZAAAA AKKAAA AAAAxx
+8318 7021 0 2 8 18 18 318 318 3318 8318 36 37 YHAAAA BKKAAA HHHHxx
+1834 7022 0 2 4 14 34 834 1834 1834 1834 68 69 OSAAAA CKKAAA OOOOxx
+4306 7023 0 2 6 6 6 306 306 4306 4306 12 13 QJAAAA DKKAAA VVVVxx
+8543 7024 1 3 3 3 43 543 543 3543 8543 86 87 PQAAAA EKKAAA AAAAxx
+9397 7025 1 1 7 17 97 397 1397 4397 9397 194 195 LXAAAA FKKAAA HHHHxx
+3145 7026 1 1 5 5 45 145 1145 3145 3145 90 91 ZQAAAA GKKAAA OOOOxx
+3942 7027 0 2 2 2 42 942 1942 3942 3942 84 85 QVAAAA HKKAAA VVVVxx
+8583 7028 1 3 3 3 83 583 583 3583 8583 166 167 DSAAAA IKKAAA AAAAxx
+8073 7029 1 1 3 13 73 73 73 3073 8073 146 147 NYAAAA JKKAAA HHHHxx
+4940 7030 0 0 0 0 40 940 940 4940 4940 80 81 AIAAAA KKKAAA OOOOxx
+9573 7031 1 1 3 13 73 573 1573 4573 9573 146 147 FEAAAA LKKAAA VVVVxx
+5325 7032 1 1 5 5 25 325 1325 325 5325 50 51 VWAAAA MKKAAA AAAAxx
+1833 7033 1 1 3 13 33 833 1833 1833 1833 66 67 NSAAAA NKKAAA HHHHxx
+1337 7034 1 1 7 17 37 337 1337 1337 1337 74 75 LZAAAA OKKAAA OOOOxx
+9749 7035 1 1 9 9 49 749 1749 4749 9749 98 99 ZKAAAA PKKAAA VVVVxx
+7505 7036 1 1 5 5 5 505 1505 2505 7505 10 11 RCAAAA QKKAAA AAAAxx
+9731 7037 1 3 1 11 31 731 1731 4731 9731 62 63 HKAAAA RKKAAA HHHHxx
+4098 7038 0 2 8 18 98 98 98 4098 4098 196 197 QBAAAA SKKAAA OOOOxx
+1418 7039 0 2 8 18 18 418 1418 1418 1418 36 37 OCAAAA TKKAAA VVVVxx
+63 7040 1 3 3 3 63 63 63 63 63 126 127 LCAAAA UKKAAA AAAAxx
+9889 7041 1 1 9 9 89 889 1889 4889 9889 178 179 JQAAAA VKKAAA HHHHxx
+2871 7042 1 3 1 11 71 871 871 2871 2871 142 143 LGAAAA WKKAAA OOOOxx
+1003 7043 1 3 3 3 3 3 1003 1003 1003 6 7 PMAAAA XKKAAA VVVVxx
+8796 7044 0 0 6 16 96 796 796 3796 8796 192 193 IAAAAA YKKAAA AAAAxx
+22 7045 0 2 2 2 22 22 22 22 22 44 45 WAAAAA ZKKAAA HHHHxx
+8244 7046 0 0 4 4 44 244 244 3244 8244 88 89 CFAAAA ALKAAA OOOOxx
+2282 7047 0 2 2 2 82 282 282 2282 2282 164 165 UJAAAA BLKAAA VVVVxx
+3487 7048 1 3 7 7 87 487 1487 3487 3487 174 175 DEAAAA CLKAAA AAAAxx
+8633 7049 1 1 3 13 33 633 633 3633 8633 66 67 BUAAAA DLKAAA HHHHxx
+6418 7050 0 2 8 18 18 418 418 1418 6418 36 37 WMAAAA ELKAAA OOOOxx
+4682 7051 0 2 2 2 82 682 682 4682 4682 164 165 CYAAAA FLKAAA VVVVxx
+4103 7052 1 3 3 3 3 103 103 4103 4103 6 7 VBAAAA GLKAAA AAAAxx
+6256 7053 0 0 6 16 56 256 256 1256 6256 112 113 QGAAAA HLKAAA HHHHxx
+4040 7054 0 0 0 0 40 40 40 4040 4040 80 81 KZAAAA ILKAAA OOOOxx
+9342 7055 0 2 2 2 42 342 1342 4342 9342 84 85 IVAAAA JLKAAA VVVVxx
+9969 7056 1 1 9 9 69 969 1969 4969 9969 138 139 LTAAAA KLKAAA AAAAxx
+223 7057 1 3 3 3 23 223 223 223 223 46 47 PIAAAA LLKAAA HHHHxx
+4593 7058 1 1 3 13 93 593 593 4593 4593 186 187 RUAAAA MLKAAA OOOOxx
+44 7059 0 0 4 4 44 44 44 44 44 88 89 SBAAAA NLKAAA VVVVxx
+3513 7060 1 1 3 13 13 513 1513 3513 3513 26 27 DFAAAA OLKAAA AAAAxx
+5771 7061 1 3 1 11 71 771 1771 771 5771 142 143 ZNAAAA PLKAAA HHHHxx
+5083 7062 1 3 3 3 83 83 1083 83 5083 166 167 NNAAAA QLKAAA OOOOxx
+3839 7063 1 3 9 19 39 839 1839 3839 3839 78 79 RRAAAA RLKAAA VVVVxx
+2986 7064 0 2 6 6 86 986 986 2986 2986 172 173 WKAAAA SLKAAA AAAAxx
+2200 7065 0 0 0 0 0 200 200 2200 2200 0 1 QGAAAA TLKAAA HHHHxx
+197 7066 1 1 7 17 97 197 197 197 197 194 195 PHAAAA ULKAAA OOOOxx
+7455 7067 1 3 5 15 55 455 1455 2455 7455 110 111 TAAAAA VLKAAA VVVVxx
+1379 7068 1 3 9 19 79 379 1379 1379 1379 158 159 BBAAAA WLKAAA AAAAxx
+4356 7069 0 0 6 16 56 356 356 4356 4356 112 113 OLAAAA XLKAAA HHHHxx
+6888 7070 0 0 8 8 88 888 888 1888 6888 176 177 YEAAAA YLKAAA OOOOxx
+9139 7071 1 3 9 19 39 139 1139 4139 9139 78 79 NNAAAA ZLKAAA VVVVxx
+7682 7072 0 2 2 2 82 682 1682 2682 7682 164 165 MJAAAA AMKAAA AAAAxx
+4873 7073 1 1 3 13 73 873 873 4873 4873 146 147 LFAAAA BMKAAA HHHHxx
+783 7074 1 3 3 3 83 783 783 783 783 166 167 DEAAAA CMKAAA OOOOxx
+6071 7075 1 3 1 11 71 71 71 1071 6071 142 143 NZAAAA DMKAAA VVVVxx
+5160 7076 0 0 0 0 60 160 1160 160 5160 120 121 MQAAAA EMKAAA AAAAxx
+2291 7077 1 3 1 11 91 291 291 2291 2291 182 183 DKAAAA FMKAAA HHHHxx
+187 7078 1 3 7 7 87 187 187 187 187 174 175 FHAAAA GMKAAA OOOOxx
+7786 7079 0 2 6 6 86 786 1786 2786 7786 172 173 MNAAAA HMKAAA VVVVxx
+3432 7080 0 0 2 12 32 432 1432 3432 3432 64 65 ACAAAA IMKAAA AAAAxx
+5450 7081 0 2 0 10 50 450 1450 450 5450 100 101 QBAAAA JMKAAA HHHHxx
+2699 7082 1 3 9 19 99 699 699 2699 2699 198 199 VZAAAA KMKAAA OOOOxx
+692 7083 0 0 2 12 92 692 692 692 692 184 185 QAAAAA LMKAAA VVVVxx
+6081 7084 1 1 1 1 81 81 81 1081 6081 162 163 XZAAAA MMKAAA AAAAxx
+4829 7085 1 1 9 9 29 829 829 4829 4829 58 59 TDAAAA NMKAAA HHHHxx
+238 7086 0 2 8 18 38 238 238 238 238 76 77 EJAAAA OMKAAA OOOOxx
+9100 7087 0 0 0 0 0 100 1100 4100 9100 0 1 AMAAAA PMKAAA VVVVxx
+1968 7088 0 0 8 8 68 968 1968 1968 1968 136 137 SXAAAA QMKAAA AAAAxx
+1872 7089 0 0 2 12 72 872 1872 1872 1872 144 145 AUAAAA RMKAAA HHHHxx
+7051 7090 1 3 1 11 51 51 1051 2051 7051 102 103 FLAAAA SMKAAA OOOOxx
+2743 7091 1 3 3 3 43 743 743 2743 2743 86 87 NBAAAA TMKAAA VVVVxx
+1237 7092 1 1 7 17 37 237 1237 1237 1237 74 75 PVAAAA UMKAAA AAAAxx
+3052 7093 0 0 2 12 52 52 1052 3052 3052 104 105 KNAAAA VMKAAA HHHHxx
+8021 7094 1 1 1 1 21 21 21 3021 8021 42 43 NWAAAA WMKAAA OOOOxx
+657 7095 1 1 7 17 57 657 657 657 657 114 115 HZAAAA XMKAAA VVVVxx
+2236 7096 0 0 6 16 36 236 236 2236 2236 72 73 AIAAAA YMKAAA AAAAxx
+7011 7097 1 3 1 11 11 11 1011 2011 7011 22 23 RJAAAA ZMKAAA HHHHxx
+4067 7098 1 3 7 7 67 67 67 4067 4067 134 135 LAAAAA ANKAAA OOOOxx
+9449 7099 1 1 9 9 49 449 1449 4449 9449 98 99 LZAAAA BNKAAA VVVVxx
+7428 7100 0 0 8 8 28 428 1428 2428 7428 56 57 SZAAAA CNKAAA AAAAxx
+1272 7101 0 0 2 12 72 272 1272 1272 1272 144 145 YWAAAA DNKAAA HHHHxx
+6897 7102 1 1 7 17 97 897 897 1897 6897 194 195 HFAAAA ENKAAA OOOOxx
+5839 7103 1 3 9 19 39 839 1839 839 5839 78 79 PQAAAA FNKAAA VVVVxx
+6835 7104 1 3 5 15 35 835 835 1835 6835 70 71 XCAAAA GNKAAA AAAAxx
+1887 7105 1 3 7 7 87 887 1887 1887 1887 174 175 PUAAAA HNKAAA HHHHxx
+1551 7106 1 3 1 11 51 551 1551 1551 1551 102 103 RHAAAA INKAAA OOOOxx
+4667 7107 1 3 7 7 67 667 667 4667 4667 134 135 NXAAAA JNKAAA VVVVxx
+9603 7108 1 3 3 3 3 603 1603 4603 9603 6 7 JFAAAA KNKAAA AAAAxx
+4332 7109 0 0 2 12 32 332 332 4332 4332 64 65 QKAAAA LNKAAA HHHHxx
+5681 7110 1 1 1 1 81 681 1681 681 5681 162 163 NKAAAA MNKAAA OOOOxx
+8062 7111 0 2 2 2 62 62 62 3062 8062 124 125 CYAAAA NNKAAA VVVVxx
+2302 7112 0 2 2 2 2 302 302 2302 2302 4 5 OKAAAA ONKAAA AAAAxx
+2825 7113 1 1 5 5 25 825 825 2825 2825 50 51 REAAAA PNKAAA HHHHxx
+4527 7114 1 3 7 7 27 527 527 4527 4527 54 55 DSAAAA QNKAAA OOOOxx
+4230 7115 0 2 0 10 30 230 230 4230 4230 60 61 SGAAAA RNKAAA VVVVxx
+3053 7116 1 1 3 13 53 53 1053 3053 3053 106 107 LNAAAA SNKAAA AAAAxx
+983 7117 1 3 3 3 83 983 983 983 983 166 167 VLAAAA TNKAAA HHHHxx
+9458 7118 0 2 8 18 58 458 1458 4458 9458 116 117 UZAAAA UNKAAA OOOOxx
+4128 7119 0 0 8 8 28 128 128 4128 4128 56 57 UCAAAA VNKAAA VVVVxx
+425 7120 1 1 5 5 25 425 425 425 425 50 51 JQAAAA WNKAAA AAAAxx
+3911 7121 1 3 1 11 11 911 1911 3911 3911 22 23 LUAAAA XNKAAA HHHHxx
+6607 7122 1 3 7 7 7 607 607 1607 6607 14 15 DUAAAA YNKAAA OOOOxx
+5431 7123 1 3 1 11 31 431 1431 431 5431 62 63 XAAAAA ZNKAAA VVVVxx
+6330 7124 0 2 0 10 30 330 330 1330 6330 60 61 MJAAAA AOKAAA AAAAxx
+3592 7125 0 0 2 12 92 592 1592 3592 3592 184 185 EIAAAA BOKAAA HHHHxx
+154 7126 0 2 4 14 54 154 154 154 154 108 109 YFAAAA COKAAA OOOOxx
+9879 7127 1 3 9 19 79 879 1879 4879 9879 158 159 ZPAAAA DOKAAA VVVVxx
+3202 7128 0 2 2 2 2 202 1202 3202 3202 4 5 ETAAAA EOKAAA AAAAxx
+3056 7129 0 0 6 16 56 56 1056 3056 3056 112 113 ONAAAA FOKAAA HHHHxx
+9890 7130 0 2 0 10 90 890 1890 4890 9890 180 181 KQAAAA GOKAAA OOOOxx
+5840 7131 0 0 0 0 40 840 1840 840 5840 80 81 QQAAAA HOKAAA VVVVxx
+9804 7132 0 0 4 4 4 804 1804 4804 9804 8 9 CNAAAA IOKAAA AAAAxx
+681 7133 1 1 1 1 81 681 681 681 681 162 163 FAAAAA JOKAAA HHHHxx
+3443 7134 1 3 3 3 43 443 1443 3443 3443 86 87 LCAAAA KOKAAA OOOOxx
+8088 7135 0 0 8 8 88 88 88 3088 8088 176 177 CZAAAA LOKAAA VVVVxx
+9447 7136 1 3 7 7 47 447 1447 4447 9447 94 95 JZAAAA MOKAAA AAAAxx
+1490 7137 0 2 0 10 90 490 1490 1490 1490 180 181 IFAAAA NOKAAA HHHHxx
+3684 7138 0 0 4 4 84 684 1684 3684 3684 168 169 SLAAAA OOKAAA OOOOxx
+3113 7139 1 1 3 13 13 113 1113 3113 3113 26 27 TPAAAA POKAAA VVVVxx
+9004 7140 0 0 4 4 4 4 1004 4004 9004 8 9 IIAAAA QOKAAA AAAAxx
+7147 7141 1 3 7 7 47 147 1147 2147 7147 94 95 XOAAAA ROKAAA HHHHxx
+7571 7142 1 3 1 11 71 571 1571 2571 7571 142 143 FFAAAA SOKAAA OOOOxx
+5545 7143 1 1 5 5 45 545 1545 545 5545 90 91 HFAAAA TOKAAA VVVVxx
+4558 7144 0 2 8 18 58 558 558 4558 4558 116 117 ITAAAA UOKAAA AAAAxx
+6206 7145 0 2 6 6 6 206 206 1206 6206 12 13 SEAAAA VOKAAA HHHHxx
+5695 7146 1 3 5 15 95 695 1695 695 5695 190 191 BLAAAA WOKAAA OOOOxx
+9600 7147 0 0 0 0 0 600 1600 4600 9600 0 1 GFAAAA XOKAAA VVVVxx
+5432 7148 0 0 2 12 32 432 1432 432 5432 64 65 YAAAAA YOKAAA AAAAxx
+9299 7149 1 3 9 19 99 299 1299 4299 9299 198 199 RTAAAA ZOKAAA HHHHxx
+2386 7150 0 2 6 6 86 386 386 2386 2386 172 173 UNAAAA APKAAA OOOOxx
+2046 7151 0 2 6 6 46 46 46 2046 2046 92 93 SAAAAA BPKAAA VVVVxx
+3293 7152 1 1 3 13 93 293 1293 3293 3293 186 187 RWAAAA CPKAAA AAAAxx
+3046 7153 0 2 6 6 46 46 1046 3046 3046 92 93 ENAAAA DPKAAA HHHHxx
+214 7154 0 2 4 14 14 214 214 214 214 28 29 GIAAAA EPKAAA OOOOxx
+7893 7155 1 1 3 13 93 893 1893 2893 7893 186 187 PRAAAA FPKAAA VVVVxx
+891 7156 1 3 1 11 91 891 891 891 891 182 183 HIAAAA GPKAAA AAAAxx
+6499 7157 1 3 9 19 99 499 499 1499 6499 198 199 ZPAAAA HPKAAA HHHHxx
+5003 7158 1 3 3 3 3 3 1003 3 5003 6 7 LKAAAA IPKAAA OOOOxx
+6487 7159 1 3 7 7 87 487 487 1487 6487 174 175 NPAAAA JPKAAA VVVVxx
+9403 7160 1 3 3 3 3 403 1403 4403 9403 6 7 RXAAAA KPKAAA AAAAxx
+945 7161 1 1 5 5 45 945 945 945 945 90 91 JKAAAA LPKAAA HHHHxx
+6713 7162 1 1 3 13 13 713 713 1713 6713 26 27 FYAAAA MPKAAA OOOOxx
+9928 7163 0 0 8 8 28 928 1928 4928 9928 56 57 WRAAAA NPKAAA VVVVxx
+8585 7164 1 1 5 5 85 585 585 3585 8585 170 171 FSAAAA OPKAAA AAAAxx
+4004 7165 0 0 4 4 4 4 4 4004 4004 8 9 AYAAAA PPKAAA HHHHxx
+2528 7166 0 0 8 8 28 528 528 2528 2528 56 57 GTAAAA QPKAAA OOOOxx
+3350 7167 0 2 0 10 50 350 1350 3350 3350 100 101 WYAAAA RPKAAA VVVVxx
+2160 7168 0 0 0 0 60 160 160 2160 2160 120 121 CFAAAA SPKAAA AAAAxx
+1521 7169 1 1 1 1 21 521 1521 1521 1521 42 43 NGAAAA TPKAAA HHHHxx
+5660 7170 0 0 0 0 60 660 1660 660 5660 120 121 SJAAAA UPKAAA OOOOxx
+5755 7171 1 3 5 15 55 755 1755 755 5755 110 111 JNAAAA VPKAAA VVVVxx
+7614 7172 0 2 4 14 14 614 1614 2614 7614 28 29 WGAAAA WPKAAA AAAAxx
+3121 7173 1 1 1 1 21 121 1121 3121 3121 42 43 BQAAAA XPKAAA HHHHxx
+2735 7174 1 3 5 15 35 735 735 2735 2735 70 71 FBAAAA YPKAAA OOOOxx
+7506 7175 0 2 6 6 6 506 1506 2506 7506 12 13 SCAAAA ZPKAAA VVVVxx
+2693 7176 1 1 3 13 93 693 693 2693 2693 186 187 PZAAAA AQKAAA AAAAxx
+2892 7177 0 0 2 12 92 892 892 2892 2892 184 185 GHAAAA BQKAAA HHHHxx
+3310 7178 0 2 0 10 10 310 1310 3310 3310 20 21 IXAAAA CQKAAA OOOOxx
+3484 7179 0 0 4 4 84 484 1484 3484 3484 168 169 AEAAAA DQKAAA VVVVxx
+9733 7180 1 1 3 13 33 733 1733 4733 9733 66 67 JKAAAA EQKAAA AAAAxx
+29 7181 1 1 9 9 29 29 29 29 29 58 59 DBAAAA FQKAAA HHHHxx
+9013 7182 1 1 3 13 13 13 1013 4013 9013 26 27 RIAAAA GQKAAA OOOOxx
+3847 7183 1 3 7 7 47 847 1847 3847 3847 94 95 ZRAAAA HQKAAA VVVVxx
+6724 7184 0 0 4 4 24 724 724 1724 6724 48 49 QYAAAA IQKAAA AAAAxx
+2559 7185 1 3 9 19 59 559 559 2559 2559 118 119 LUAAAA JQKAAA HHHHxx
+5326 7186 0 2 6 6 26 326 1326 326 5326 52 53 WWAAAA KQKAAA OOOOxx
+4802 7187 0 2 2 2 2 802 802 4802 4802 4 5 SCAAAA LQKAAA VVVVxx
+131 7188 1 3 1 11 31 131 131 131 131 62 63 BFAAAA MQKAAA AAAAxx
+1634 7189 0 2 4 14 34 634 1634 1634 1634 68 69 WKAAAA NQKAAA HHHHxx
+919 7190 1 3 9 19 19 919 919 919 919 38 39 JJAAAA OQKAAA OOOOxx
+9575 7191 1 3 5 15 75 575 1575 4575 9575 150 151 HEAAAA PQKAAA VVVVxx
+1256 7192 0 0 6 16 56 256 1256 1256 1256 112 113 IWAAAA QQKAAA AAAAxx
+9428 7193 0 0 8 8 28 428 1428 4428 9428 56 57 QYAAAA RQKAAA HHHHxx
+5121 7194 1 1 1 1 21 121 1121 121 5121 42 43 ZOAAAA SQKAAA OOOOxx
+6584 7195 0 0 4 4 84 584 584 1584 6584 168 169 GTAAAA TQKAAA VVVVxx
+7193 7196 1 1 3 13 93 193 1193 2193 7193 186 187 RQAAAA UQKAAA AAAAxx
+4047 7197 1 3 7 7 47 47 47 4047 4047 94 95 RZAAAA VQKAAA HHHHxx
+104 7198 0 0 4 4 4 104 104 104 104 8 9 AEAAAA WQKAAA OOOOxx
+1527 7199 1 3 7 7 27 527 1527 1527 1527 54 55 TGAAAA XQKAAA VVVVxx
+3460 7200 0 0 0 0 60 460 1460 3460 3460 120 121 CDAAAA YQKAAA AAAAxx
+8526 7201 0 2 6 6 26 526 526 3526 8526 52 53 YPAAAA ZQKAAA HHHHxx
+8959 7202 1 3 9 19 59 959 959 3959 8959 118 119 PGAAAA ARKAAA OOOOxx
+3633 7203 1 1 3 13 33 633 1633 3633 3633 66 67 TJAAAA BRKAAA VVVVxx
+1799 7204 1 3 9 19 99 799 1799 1799 1799 198 199 FRAAAA CRKAAA AAAAxx
+461 7205 1 1 1 1 61 461 461 461 461 122 123 TRAAAA DRKAAA HHHHxx
+718 7206 0 2 8 18 18 718 718 718 718 36 37 QBAAAA ERKAAA OOOOxx
+3219 7207 1 3 9 19 19 219 1219 3219 3219 38 39 VTAAAA FRKAAA VVVVxx
+3494 7208 0 2 4 14 94 494 1494 3494 3494 188 189 KEAAAA GRKAAA AAAAxx
+9402 7209 0 2 2 2 2 402 1402 4402 9402 4 5 QXAAAA HRKAAA HHHHxx
+7983 7210 1 3 3 3 83 983 1983 2983 7983 166 167 BVAAAA IRKAAA OOOOxx
+7919 7211 1 3 9 19 19 919 1919 2919 7919 38 39 PSAAAA JRKAAA VVVVxx
+8036 7212 0 0 6 16 36 36 36 3036 8036 72 73 CXAAAA KRKAAA AAAAxx
+5164 7213 0 0 4 4 64 164 1164 164 5164 128 129 QQAAAA LRKAAA HHHHxx
+4160 7214 0 0 0 0 60 160 160 4160 4160 120 121 AEAAAA MRKAAA OOOOxx
+5370 7215 0 2 0 10 70 370 1370 370 5370 140 141 OYAAAA NRKAAA VVVVxx
+5347 7216 1 3 7 7 47 347 1347 347 5347 94 95 RXAAAA ORKAAA AAAAxx
+7109 7217 1 1 9 9 9 109 1109 2109 7109 18 19 LNAAAA PRKAAA HHHHxx
+4826 7218 0 2 6 6 26 826 826 4826 4826 52 53 QDAAAA QRKAAA OOOOxx
+1338 7219 0 2 8 18 38 338 1338 1338 1338 76 77 MZAAAA RRKAAA VVVVxx
+2711 7220 1 3 1 11 11 711 711 2711 2711 22 23 HAAAAA SRKAAA AAAAxx
+6299 7221 1 3 9 19 99 299 299 1299 6299 198 199 HIAAAA TRKAAA HHHHxx
+1616 7222 0 0 6 16 16 616 1616 1616 1616 32 33 EKAAAA URKAAA OOOOxx
+7519 7223 1 3 9 19 19 519 1519 2519 7519 38 39 FDAAAA VRKAAA VVVVxx
+1262 7224 0 2 2 2 62 262 1262 1262 1262 124 125 OWAAAA WRKAAA AAAAxx
+7228 7225 0 0 8 8 28 228 1228 2228 7228 56 57 ASAAAA XRKAAA HHHHxx
+7892 7226 0 0 2 12 92 892 1892 2892 7892 184 185 ORAAAA YRKAAA OOOOxx
+7929 7227 1 1 9 9 29 929 1929 2929 7929 58 59 ZSAAAA ZRKAAA VVVVxx
+7705 7228 1 1 5 5 5 705 1705 2705 7705 10 11 JKAAAA ASKAAA AAAAxx
+3111 7229 1 3 1 11 11 111 1111 3111 3111 22 23 RPAAAA BSKAAA HHHHxx
+3066 7230 0 2 6 6 66 66 1066 3066 3066 132 133 YNAAAA CSKAAA OOOOxx
+9559 7231 1 3 9 19 59 559 1559 4559 9559 118 119 RDAAAA DSKAAA VVVVxx
+3787 7232 1 3 7 7 87 787 1787 3787 3787 174 175 RPAAAA ESKAAA AAAAxx
+8710 7233 0 2 0 10 10 710 710 3710 8710 20 21 AXAAAA FSKAAA HHHHxx
+4870 7234 0 2 0 10 70 870 870 4870 4870 140 141 IFAAAA GSKAAA OOOOxx
+1883 7235 1 3 3 3 83 883 1883 1883 1883 166 167 LUAAAA HSKAAA VVVVxx
+9689 7236 1 1 9 9 89 689 1689 4689 9689 178 179 RIAAAA ISKAAA AAAAxx
+9491 7237 1 3 1 11 91 491 1491 4491 9491 182 183 BBAAAA JSKAAA HHHHxx
+2035 7238 1 3 5 15 35 35 35 2035 2035 70 71 HAAAAA KSKAAA OOOOxx
+655 7239 1 3 5 15 55 655 655 655 655 110 111 FZAAAA LSKAAA VVVVxx
+6305 7240 1 1 5 5 5 305 305 1305 6305 10 11 NIAAAA MSKAAA AAAAxx
+9423 7241 1 3 3 3 23 423 1423 4423 9423 46 47 LYAAAA NSKAAA HHHHxx
+283 7242 1 3 3 3 83 283 283 283 283 166 167 XKAAAA OSKAAA OOOOxx
+2607 7243 1 3 7 7 7 607 607 2607 2607 14 15 HWAAAA PSKAAA VVVVxx
+7740 7244 0 0 0 0 40 740 1740 2740 7740 80 81 SLAAAA QSKAAA AAAAxx
+6956 7245 0 0 6 16 56 956 956 1956 6956 112 113 OHAAAA RSKAAA HHHHxx
+884 7246 0 0 4 4 84 884 884 884 884 168 169 AIAAAA SSKAAA OOOOxx
+5730 7247 0 2 0 10 30 730 1730 730 5730 60 61 KMAAAA TSKAAA VVVVxx
+3438 7248 0 2 8 18 38 438 1438 3438 3438 76 77 GCAAAA USKAAA AAAAxx
+3250 7249 0 2 0 10 50 250 1250 3250 3250 100 101 AVAAAA VSKAAA HHHHxx
+5470 7250 0 2 0 10 70 470 1470 470 5470 140 141 KCAAAA WSKAAA OOOOxx
+2037 7251 1 1 7 17 37 37 37 2037 2037 74 75 JAAAAA XSKAAA VVVVxx
+6593 7252 1 1 3 13 93 593 593 1593 6593 186 187 PTAAAA YSKAAA AAAAxx
+3893 7253 1 1 3 13 93 893 1893 3893 3893 186 187 TTAAAA ZSKAAA HHHHxx
+3200 7254 0 0 0 0 0 200 1200 3200 3200 0 1 CTAAAA ATKAAA OOOOxx
+7125 7255 1 1 5 5 25 125 1125 2125 7125 50 51 BOAAAA BTKAAA VVVVxx
+2295 7256 1 3 5 15 95 295 295 2295 2295 190 191 HKAAAA CTKAAA AAAAxx
+2056 7257 0 0 6 16 56 56 56 2056 2056 112 113 CBAAAA DTKAAA HHHHxx
+2962 7258 0 2 2 2 62 962 962 2962 2962 124 125 YJAAAA ETKAAA OOOOxx
+993 7259 1 1 3 13 93 993 993 993 993 186 187 FMAAAA FTKAAA VVVVxx
+9127 7260 1 3 7 7 27 127 1127 4127 9127 54 55 BNAAAA GTKAAA AAAAxx
+2075 7261 1 3 5 15 75 75 75 2075 2075 150 151 VBAAAA HTKAAA HHHHxx
+9338 7262 0 2 8 18 38 338 1338 4338 9338 76 77 EVAAAA ITKAAA OOOOxx
+8100 7263 0 0 0 0 0 100 100 3100 8100 0 1 OZAAAA JTKAAA VVVVxx
+5047 7264 1 3 7 7 47 47 1047 47 5047 94 95 DMAAAA KTKAAA AAAAxx
+7032 7265 0 0 2 12 32 32 1032 2032 7032 64 65 MKAAAA LTKAAA HHHHxx
+6374 7266 0 2 4 14 74 374 374 1374 6374 148 149 ELAAAA MTKAAA OOOOxx
+4137 7267 1 1 7 17 37 137 137 4137 4137 74 75 DDAAAA NTKAAA VVVVxx
+7132 7268 0 0 2 12 32 132 1132 2132 7132 64 65 IOAAAA OTKAAA AAAAxx
+3064 7269 0 0 4 4 64 64 1064 3064 3064 128 129 WNAAAA PTKAAA HHHHxx
+3621 7270 1 1 1 1 21 621 1621 3621 3621 42 43 HJAAAA QTKAAA OOOOxx
+6199 7271 1 3 9 19 99 199 199 1199 6199 198 199 LEAAAA RTKAAA VVVVxx
+4926 7272 0 2 6 6 26 926 926 4926 4926 52 53 MHAAAA STKAAA AAAAxx
+8035 7273 1 3 5 15 35 35 35 3035 8035 70 71 BXAAAA TTKAAA HHHHxx
+2195 7274 1 3 5 15 95 195 195 2195 2195 190 191 LGAAAA UTKAAA OOOOxx
+5366 7275 0 2 6 6 66 366 1366 366 5366 132 133 KYAAAA VTKAAA VVVVxx
+3478 7276 0 2 8 18 78 478 1478 3478 3478 156 157 UDAAAA WTKAAA AAAAxx
+1926 7277 0 2 6 6 26 926 1926 1926 1926 52 53 CWAAAA XTKAAA HHHHxx
+7265 7278 1 1 5 5 65 265 1265 2265 7265 130 131 LTAAAA YTKAAA OOOOxx
+7668 7279 0 0 8 8 68 668 1668 2668 7668 136 137 YIAAAA ZTKAAA VVVVxx
+3335 7280 1 3 5 15 35 335 1335 3335 3335 70 71 HYAAAA AUKAAA AAAAxx
+7660 7281 0 0 0 0 60 660 1660 2660 7660 120 121 QIAAAA BUKAAA HHHHxx
+9604 7282 0 0 4 4 4 604 1604 4604 9604 8 9 KFAAAA CUKAAA OOOOxx
+7301 7283 1 1 1 1 1 301 1301 2301 7301 2 3 VUAAAA DUKAAA VVVVxx
+4475 7284 1 3 5 15 75 475 475 4475 4475 150 151 DQAAAA EUKAAA AAAAxx
+9954 7285 0 2 4 14 54 954 1954 4954 9954 108 109 WSAAAA FUKAAA HHHHxx
+5723 7286 1 3 3 3 23 723 1723 723 5723 46 47 DMAAAA GUKAAA OOOOxx
+2669 7287 1 1 9 9 69 669 669 2669 2669 138 139 RYAAAA HUKAAA VVVVxx
+1685 7288 1 1 5 5 85 685 1685 1685 1685 170 171 VMAAAA IUKAAA AAAAxx
+2233 7289 1 1 3 13 33 233 233 2233 2233 66 67 XHAAAA JUKAAA HHHHxx
+8111 7290 1 3 1 11 11 111 111 3111 8111 22 23 ZZAAAA KUKAAA OOOOxx
+7685 7291 1 1 5 5 85 685 1685 2685 7685 170 171 PJAAAA LUKAAA VVVVxx
+3773 7292 1 1 3 13 73 773 1773 3773 3773 146 147 DPAAAA MUKAAA AAAAxx
+7172 7293 0 0 2 12 72 172 1172 2172 7172 144 145 WPAAAA NUKAAA HHHHxx
+1740 7294 0 0 0 0 40 740 1740 1740 1740 80 81 YOAAAA OUKAAA OOOOxx
+5416 7295 0 0 6 16 16 416 1416 416 5416 32 33 IAAAAA PUKAAA VVVVxx
+1823 7296 1 3 3 3 23 823 1823 1823 1823 46 47 DSAAAA QUKAAA AAAAxx
+1668 7297 0 0 8 8 68 668 1668 1668 1668 136 137 EMAAAA RUKAAA HHHHxx
+1795 7298 1 3 5 15 95 795 1795 1795 1795 190 191 BRAAAA SUKAAA OOOOxx
+8599 7299 1 3 9 19 99 599 599 3599 8599 198 199 TSAAAA TUKAAA VVVVxx
+5542 7300 0 2 2 2 42 542 1542 542 5542 84 85 EFAAAA UUKAAA AAAAxx
+5658 7301 0 2 8 18 58 658 1658 658 5658 116 117 QJAAAA VUKAAA HHHHxx
+9824 7302 0 0 4 4 24 824 1824 4824 9824 48 49 WNAAAA WUKAAA OOOOxx
+19 7303 1 3 9 19 19 19 19 19 19 38 39 TAAAAA XUKAAA VVVVxx
+9344 7304 0 0 4 4 44 344 1344 4344 9344 88 89 KVAAAA YUKAAA AAAAxx
+5900 7305 0 0 0 0 0 900 1900 900 5900 0 1 YSAAAA ZUKAAA HHHHxx
+7818 7306 0 2 8 18 18 818 1818 2818 7818 36 37 SOAAAA AVKAAA OOOOxx
+8377 7307 1 1 7 17 77 377 377 3377 8377 154 155 FKAAAA BVKAAA VVVVxx
+6886 7308 0 2 6 6 86 886 886 1886 6886 172 173 WEAAAA CVKAAA AAAAxx
+3201 7309 1 1 1 1 1 201 1201 3201 3201 2 3 DTAAAA DVKAAA HHHHxx
+87 7310 1 3 7 7 87 87 87 87 87 174 175 JDAAAA EVKAAA OOOOxx
+1089 7311 1 1 9 9 89 89 1089 1089 1089 178 179 XPAAAA FVKAAA VVVVxx
+3948 7312 0 0 8 8 48 948 1948 3948 3948 96 97 WVAAAA GVKAAA AAAAxx
+6383 7313 1 3 3 3 83 383 383 1383 6383 166 167 NLAAAA HVKAAA HHHHxx
+837 7314 1 1 7 17 37 837 837 837 837 74 75 FGAAAA IVKAAA OOOOxx
+6285 7315 1 1 5 5 85 285 285 1285 6285 170 171 THAAAA JVKAAA VVVVxx
+78 7316 0 2 8 18 78 78 78 78 78 156 157 ADAAAA KVKAAA AAAAxx
+4389 7317 1 1 9 9 89 389 389 4389 4389 178 179 VMAAAA LVKAAA HHHHxx
+4795 7318 1 3 5 15 95 795 795 4795 4795 190 191 LCAAAA MVKAAA OOOOxx
+9369 7319 1 1 9 9 69 369 1369 4369 9369 138 139 JWAAAA NVKAAA VVVVxx
+69 7320 1 1 9 9 69 69 69 69 69 138 139 RCAAAA OVKAAA AAAAxx
+7689 7321 1 1 9 9 89 689 1689 2689 7689 178 179 TJAAAA PVKAAA HHHHxx
+5642 7322 0 2 2 2 42 642 1642 642 5642 84 85 AJAAAA QVKAAA OOOOxx
+2348 7323 0 0 8 8 48 348 348 2348 2348 96 97 IMAAAA RVKAAA VVVVxx
+9308 7324 0 0 8 8 8 308 1308 4308 9308 16 17 AUAAAA SVKAAA AAAAxx
+9093 7325 1 1 3 13 93 93 1093 4093 9093 186 187 TLAAAA TVKAAA HHHHxx
+1199 7326 1 3 9 19 99 199 1199 1199 1199 198 199 DUAAAA UVKAAA OOOOxx
+307 7327 1 3 7 7 7 307 307 307 307 14 15 VLAAAA VVKAAA VVVVxx
+3814 7328 0 2 4 14 14 814 1814 3814 3814 28 29 SQAAAA WVKAAA AAAAxx
+8817 7329 1 1 7 17 17 817 817 3817 8817 34 35 DBAAAA XVKAAA HHHHxx
+2329 7330 1 1 9 9 29 329 329 2329 2329 58 59 PLAAAA YVKAAA OOOOxx
+2932 7331 0 0 2 12 32 932 932 2932 2932 64 65 UIAAAA ZVKAAA VVVVxx
+1986 7332 0 2 6 6 86 986 1986 1986 1986 172 173 KYAAAA AWKAAA AAAAxx
+5279 7333 1 3 9 19 79 279 1279 279 5279 158 159 BVAAAA BWKAAA HHHHxx
+5357 7334 1 1 7 17 57 357 1357 357 5357 114 115 BYAAAA CWKAAA OOOOxx
+6778 7335 0 2 8 18 78 778 778 1778 6778 156 157 SAAAAA DWKAAA VVVVxx
+2773 7336 1 1 3 13 73 773 773 2773 2773 146 147 RCAAAA EWKAAA AAAAxx
+244 7337 0 0 4 4 44 244 244 244 244 88 89 KJAAAA FWKAAA HHHHxx
+6900 7338 0 0 0 0 0 900 900 1900 6900 0 1 KFAAAA GWKAAA OOOOxx
+4739 7339 1 3 9 19 39 739 739 4739 4739 78 79 HAAAAA HWKAAA VVVVxx
+3217 7340 1 1 7 17 17 217 1217 3217 3217 34 35 TTAAAA IWKAAA AAAAxx
+7563 7341 1 3 3 3 63 563 1563 2563 7563 126 127 XEAAAA JWKAAA HHHHxx
+1807 7342 1 3 7 7 7 807 1807 1807 1807 14 15 NRAAAA KWKAAA OOOOxx
+4199 7343 1 3 9 19 99 199 199 4199 4199 198 199 NFAAAA LWKAAA VVVVxx
+1077 7344 1 1 7 17 77 77 1077 1077 1077 154 155 LPAAAA MWKAAA AAAAxx
+8348 7345 0 0 8 8 48 348 348 3348 8348 96 97 CJAAAA NWKAAA HHHHxx
+841 7346 1 1 1 1 41 841 841 841 841 82 83 JGAAAA OWKAAA OOOOxx
+8154 7347 0 2 4 14 54 154 154 3154 8154 108 109 QBAAAA PWKAAA VVVVxx
+5261 7348 1 1 1 1 61 261 1261 261 5261 122 123 JUAAAA QWKAAA AAAAxx
+1950 7349 0 2 0 10 50 950 1950 1950 1950 100 101 AXAAAA RWKAAA HHHHxx
+8472 7350 0 0 2 12 72 472 472 3472 8472 144 145 WNAAAA SWKAAA OOOOxx
+8745 7351 1 1 5 5 45 745 745 3745 8745 90 91 JYAAAA TWKAAA VVVVxx
+8715 7352 1 3 5 15 15 715 715 3715 8715 30 31 FXAAAA UWKAAA AAAAxx
+9708 7353 0 0 8 8 8 708 1708 4708 9708 16 17 KJAAAA VWKAAA HHHHxx
+5860 7354 0 0 0 0 60 860 1860 860 5860 120 121 KRAAAA WWKAAA OOOOxx
+9142 7355 0 2 2 2 42 142 1142 4142 9142 84 85 QNAAAA XWKAAA VVVVxx
+6582 7356 0 2 2 2 82 582 582 1582 6582 164 165 ETAAAA YWKAAA AAAAxx
+1255 7357 1 3 5 15 55 255 1255 1255 1255 110 111 HWAAAA ZWKAAA HHHHxx
+6459 7358 1 3 9 19 59 459 459 1459 6459 118 119 LOAAAA AXKAAA OOOOxx
+6327 7359 1 3 7 7 27 327 327 1327 6327 54 55 JJAAAA BXKAAA VVVVxx
+4692 7360 0 0 2 12 92 692 692 4692 4692 184 185 MYAAAA CXKAAA AAAAxx
+3772 7361 0 0 2 12 72 772 1772 3772 3772 144 145 CPAAAA DXKAAA HHHHxx
+4203 7362 1 3 3 3 3 203 203 4203 4203 6 7 RFAAAA EXKAAA OOOOxx
+2946 7363 0 2 6 6 46 946 946 2946 2946 92 93 IJAAAA FXKAAA VVVVxx
+3524 7364 0 0 4 4 24 524 1524 3524 3524 48 49 OFAAAA GXKAAA AAAAxx
+8409 7365 1 1 9 9 9 409 409 3409 8409 18 19 LLAAAA HXKAAA HHHHxx
+1824 7366 0 0 4 4 24 824 1824 1824 1824 48 49 ESAAAA IXKAAA OOOOxx
+4637 7367 1 1 7 17 37 637 637 4637 4637 74 75 JWAAAA JXKAAA VVVVxx
+589 7368 1 1 9 9 89 589 589 589 589 178 179 RWAAAA KXKAAA AAAAxx
+484 7369 0 0 4 4 84 484 484 484 484 168 169 QSAAAA LXKAAA HHHHxx
+8963 7370 1 3 3 3 63 963 963 3963 8963 126 127 TGAAAA MXKAAA OOOOxx
+5502 7371 0 2 2 2 2 502 1502 502 5502 4 5 QDAAAA NXKAAA VVVVxx
+6982 7372 0 2 2 2 82 982 982 1982 6982 164 165 OIAAAA OXKAAA AAAAxx
+8029 7373 1 1 9 9 29 29 29 3029 8029 58 59 VWAAAA PXKAAA HHHHxx
+4395 7374 1 3 5 15 95 395 395 4395 4395 190 191 BNAAAA QXKAAA OOOOxx
+2595 7375 1 3 5 15 95 595 595 2595 2595 190 191 VVAAAA RXKAAA VVVVxx
+2133 7376 1 1 3 13 33 133 133 2133 2133 66 67 BEAAAA SXKAAA AAAAxx
+1414 7377 0 2 4 14 14 414 1414 1414 1414 28 29 KCAAAA TXKAAA HHHHxx
+8201 7378 1 1 1 1 1 201 201 3201 8201 2 3 LDAAAA UXKAAA OOOOxx
+4706 7379 0 2 6 6 6 706 706 4706 4706 12 13 AZAAAA VXKAAA VVVVxx
+5310 7380 0 2 0 10 10 310 1310 310 5310 20 21 GWAAAA WXKAAA AAAAxx
+7333 7381 1 1 3 13 33 333 1333 2333 7333 66 67 BWAAAA XXKAAA HHHHxx
+9420 7382 0 0 0 0 20 420 1420 4420 9420 40 41 IYAAAA YXKAAA OOOOxx
+1383 7383 1 3 3 3 83 383 1383 1383 1383 166 167 FBAAAA ZXKAAA VVVVxx
+6225 7384 1 1 5 5 25 225 225 1225 6225 50 51 LFAAAA AYKAAA AAAAxx
+2064 7385 0 0 4 4 64 64 64 2064 2064 128 129 KBAAAA BYKAAA HHHHxx
+6700 7386 0 0 0 0 0 700 700 1700 6700 0 1 SXAAAA CYKAAA OOOOxx
+1352 7387 0 0 2 12 52 352 1352 1352 1352 104 105 AAAAAA DYKAAA VVVVxx
+4249 7388 1 1 9 9 49 249 249 4249 4249 98 99 LHAAAA EYKAAA AAAAxx
+9429 7389 1 1 9 9 29 429 1429 4429 9429 58 59 RYAAAA FYKAAA HHHHxx
+8090 7390 0 2 0 10 90 90 90 3090 8090 180 181 EZAAAA GYKAAA OOOOxx
+5378 7391 0 2 8 18 78 378 1378 378 5378 156 157 WYAAAA HYKAAA VVVVxx
+9085 7392 1 1 5 5 85 85 1085 4085 9085 170 171 LLAAAA IYKAAA AAAAxx
+7468 7393 0 0 8 8 68 468 1468 2468 7468 136 137 GBAAAA JYKAAA HHHHxx
+9955 7394 1 3 5 15 55 955 1955 4955 9955 110 111 XSAAAA KYKAAA OOOOxx
+8692 7395 0 0 2 12 92 692 692 3692 8692 184 185 IWAAAA LYKAAA VVVVxx
+1463 7396 1 3 3 3 63 463 1463 1463 1463 126 127 HEAAAA MYKAAA AAAAxx
+3577 7397 1 1 7 17 77 577 1577 3577 3577 154 155 PHAAAA NYKAAA HHHHxx
+5654 7398 0 2 4 14 54 654 1654 654 5654 108 109 MJAAAA OYKAAA OOOOxx
+7955 7399 1 3 5 15 55 955 1955 2955 7955 110 111 ZTAAAA PYKAAA VVVVxx
+4843 7400 1 3 3 3 43 843 843 4843 4843 86 87 HEAAAA QYKAAA AAAAxx
+1776 7401 0 0 6 16 76 776 1776 1776 1776 152 153 IQAAAA RYKAAA HHHHxx
+2223 7402 1 3 3 3 23 223 223 2223 2223 46 47 NHAAAA SYKAAA OOOOxx
+8442 7403 0 2 2 2 42 442 442 3442 8442 84 85 SMAAAA TYKAAA VVVVxx
+9738 7404 0 2 8 18 38 738 1738 4738 9738 76 77 OKAAAA UYKAAA AAAAxx
+4867 7405 1 3 7 7 67 867 867 4867 4867 134 135 FFAAAA VYKAAA HHHHxx
+2983 7406 1 3 3 3 83 983 983 2983 2983 166 167 TKAAAA WYKAAA OOOOxx
+3300 7407 0 0 0 0 0 300 1300 3300 3300 0 1 YWAAAA XYKAAA VVVVxx
+3815 7408 1 3 5 15 15 815 1815 3815 3815 30 31 TQAAAA YYKAAA AAAAxx
+1779 7409 1 3 9 19 79 779 1779 1779 1779 158 159 LQAAAA ZYKAAA HHHHxx
+1123 7410 1 3 3 3 23 123 1123 1123 1123 46 47 FRAAAA AZKAAA OOOOxx
+4824 7411 0 0 4 4 24 824 824 4824 4824 48 49 ODAAAA BZKAAA VVVVxx
+5407 7412 1 3 7 7 7 407 1407 407 5407 14 15 ZZAAAA CZKAAA AAAAxx
+5123 7413 1 3 3 3 23 123 1123 123 5123 46 47 BPAAAA DZKAAA HHHHxx
+2515 7414 1 3 5 15 15 515 515 2515 2515 30 31 TSAAAA EZKAAA OOOOxx
+4781 7415 1 1 1 1 81 781 781 4781 4781 162 163 XBAAAA FZKAAA VVVVxx
+7831 7416 1 3 1 11 31 831 1831 2831 7831 62 63 FPAAAA GZKAAA AAAAxx
+6946 7417 0 2 6 6 46 946 946 1946 6946 92 93 EHAAAA HZKAAA HHHHxx
+1215 7418 1 3 5 15 15 215 1215 1215 1215 30 31 TUAAAA IZKAAA OOOOxx
+7783 7419 1 3 3 3 83 783 1783 2783 7783 166 167 JNAAAA JZKAAA VVVVxx
+4532 7420 0 0 2 12 32 532 532 4532 4532 64 65 ISAAAA KZKAAA AAAAxx
+9068 7421 0 0 8 8 68 68 1068 4068 9068 136 137 UKAAAA LZKAAA HHHHxx
+7030 7422 0 2 0 10 30 30 1030 2030 7030 60 61 KKAAAA MZKAAA OOOOxx
+436 7423 0 0 6 16 36 436 436 436 436 72 73 UQAAAA NZKAAA VVVVxx
+6549 7424 1 1 9 9 49 549 549 1549 6549 98 99 XRAAAA OZKAAA AAAAxx
+3348 7425 0 0 8 8 48 348 1348 3348 3348 96 97 UYAAAA PZKAAA HHHHxx
+6229 7426 1 1 9 9 29 229 229 1229 6229 58 59 PFAAAA QZKAAA OOOOxx
+3933 7427 1 1 3 13 33 933 1933 3933 3933 66 67 HVAAAA RZKAAA VVVVxx
+1876 7428 0 0 6 16 76 876 1876 1876 1876 152 153 EUAAAA SZKAAA AAAAxx
+8920 7429 0 0 0 0 20 920 920 3920 8920 40 41 CFAAAA TZKAAA HHHHxx
+7926 7430 0 2 6 6 26 926 1926 2926 7926 52 53 WSAAAA UZKAAA OOOOxx
+8805 7431 1 1 5 5 5 805 805 3805 8805 10 11 RAAAAA VZKAAA VVVVxx
+6729 7432 1 1 9 9 29 729 729 1729 6729 58 59 VYAAAA WZKAAA AAAAxx
+7397 7433 1 1 7 17 97 397 1397 2397 7397 194 195 NYAAAA XZKAAA HHHHxx
+9303 7434 1 3 3 3 3 303 1303 4303 9303 6 7 VTAAAA YZKAAA OOOOxx
+4255 7435 1 3 5 15 55 255 255 4255 4255 110 111 RHAAAA ZZKAAA VVVVxx
+7229 7436 1 1 9 9 29 229 1229 2229 7229 58 59 BSAAAA AALAAA AAAAxx
+854 7437 0 2 4 14 54 854 854 854 854 108 109 WGAAAA BALAAA HHHHxx
+6723 7438 1 3 3 3 23 723 723 1723 6723 46 47 PYAAAA CALAAA OOOOxx
+9597 7439 1 1 7 17 97 597 1597 4597 9597 194 195 DFAAAA DALAAA VVVVxx
+6532 7440 0 0 2 12 32 532 532 1532 6532 64 65 GRAAAA EALAAA AAAAxx
+2910 7441 0 2 0 10 10 910 910 2910 2910 20 21 YHAAAA FALAAA HHHHxx
+6717 7442 1 1 7 17 17 717 717 1717 6717 34 35 JYAAAA GALAAA OOOOxx
+1790 7443 0 2 0 10 90 790 1790 1790 1790 180 181 WQAAAA HALAAA VVVVxx
+3761 7444 1 1 1 1 61 761 1761 3761 3761 122 123 ROAAAA IALAAA AAAAxx
+1565 7445 1 1 5 5 65 565 1565 1565 1565 130 131 FIAAAA JALAAA HHHHxx
+6205 7446 1 1 5 5 5 205 205 1205 6205 10 11 REAAAA KALAAA OOOOxx
+2726 7447 0 2 6 6 26 726 726 2726 2726 52 53 WAAAAA LALAAA VVVVxx
+799 7448 1 3 9 19 99 799 799 799 799 198 199 TEAAAA MALAAA AAAAxx
+3540 7449 0 0 0 0 40 540 1540 3540 3540 80 81 EGAAAA NALAAA HHHHxx
+5878 7450 0 2 8 18 78 878 1878 878 5878 156 157 CSAAAA OALAAA OOOOxx
+2542 7451 0 2 2 2 42 542 542 2542 2542 84 85 UTAAAA PALAAA VVVVxx
+4888 7452 0 0 8 8 88 888 888 4888 4888 176 177 AGAAAA QALAAA AAAAxx
+5290 7453 0 2 0 10 90 290 1290 290 5290 180 181 MVAAAA RALAAA HHHHxx
+7995 7454 1 3 5 15 95 995 1995 2995 7995 190 191 NVAAAA SALAAA OOOOxx
+3519 7455 1 3 9 19 19 519 1519 3519 3519 38 39 JFAAAA TALAAA VVVVxx
+3571 7456 1 3 1 11 71 571 1571 3571 3571 142 143 JHAAAA UALAAA AAAAxx
+7854 7457 0 2 4 14 54 854 1854 2854 7854 108 109 CQAAAA VALAAA HHHHxx
+5184 7458 0 0 4 4 84 184 1184 184 5184 168 169 KRAAAA WALAAA OOOOxx
+3498 7459 0 2 8 18 98 498 1498 3498 3498 196 197 OEAAAA XALAAA VVVVxx
+1264 7460 0 0 4 4 64 264 1264 1264 1264 128 129 QWAAAA YALAAA AAAAxx
+3159 7461 1 3 9 19 59 159 1159 3159 3159 118 119 NRAAAA ZALAAA HHHHxx
+5480 7462 0 0 0 0 80 480 1480 480 5480 160 161 UCAAAA ABLAAA OOOOxx
+1706 7463 0 2 6 6 6 706 1706 1706 1706 12 13 QNAAAA BBLAAA VVVVxx
+4540 7464 0 0 0 0 40 540 540 4540 4540 80 81 QSAAAA CBLAAA AAAAxx
+2799 7465 1 3 9 19 99 799 799 2799 2799 198 199 RDAAAA DBLAAA HHHHxx
+7389 7466 1 1 9 9 89 389 1389 2389 7389 178 179 FYAAAA EBLAAA OOOOxx
+5565 7467 1 1 5 5 65 565 1565 565 5565 130 131 BGAAAA FBLAAA VVVVxx
+3896 7468 0 0 6 16 96 896 1896 3896 3896 192 193 WTAAAA GBLAAA AAAAxx
+2100 7469 0 0 0 0 0 100 100 2100 2100 0 1 UCAAAA HBLAAA HHHHxx
+3507 7470 1 3 7 7 7 507 1507 3507 3507 14 15 XEAAAA IBLAAA OOOOxx
+7971 7471 1 3 1 11 71 971 1971 2971 7971 142 143 PUAAAA JBLAAA VVVVxx
+2312 7472 0 0 2 12 12 312 312 2312 2312 24 25 YKAAAA KBLAAA AAAAxx
+2494 7473 0 2 4 14 94 494 494 2494 2494 188 189 YRAAAA LBLAAA HHHHxx
+2474 7474 0 2 4 14 74 474 474 2474 2474 148 149 ERAAAA MBLAAA OOOOxx
+3136 7475 0 0 6 16 36 136 1136 3136 3136 72 73 QQAAAA NBLAAA VVVVxx
+7242 7476 0 2 2 2 42 242 1242 2242 7242 84 85 OSAAAA OBLAAA AAAAxx
+9430 7477 0 2 0 10 30 430 1430 4430 9430 60 61 SYAAAA PBLAAA HHHHxx
+1052 7478 0 0 2 12 52 52 1052 1052 1052 104 105 MOAAAA QBLAAA OOOOxx
+4172 7479 0 0 2 12 72 172 172 4172 4172 144 145 MEAAAA RBLAAA VVVVxx
+970 7480 0 2 0 10 70 970 970 970 970 140 141 ILAAAA SBLAAA AAAAxx
+882 7481 0 2 2 2 82 882 882 882 882 164 165 YHAAAA TBLAAA HHHHxx
+9799 7482 1 3 9 19 99 799 1799 4799 9799 198 199 XMAAAA UBLAAA OOOOxx
+5850 7483 0 2 0 10 50 850 1850 850 5850 100 101 ARAAAA VBLAAA VVVVxx
+9473 7484 1 1 3 13 73 473 1473 4473 9473 146 147 JAAAAA WBLAAA AAAAxx
+8635 7485 1 3 5 15 35 635 635 3635 8635 70 71 DUAAAA XBLAAA HHHHxx
+2349 7486 1 1 9 9 49 349 349 2349 2349 98 99 JMAAAA YBLAAA OOOOxx
+2270 7487 0 2 0 10 70 270 270 2270 2270 140 141 IJAAAA ZBLAAA VVVVxx
+7887 7488 1 3 7 7 87 887 1887 2887 7887 174 175 JRAAAA ACLAAA AAAAxx
+3091 7489 1 3 1 11 91 91 1091 3091 3091 182 183 XOAAAA BCLAAA HHHHxx
+3728 7490 0 0 8 8 28 728 1728 3728 3728 56 57 KNAAAA CCLAAA OOOOxx
+3658 7491 0 2 8 18 58 658 1658 3658 3658 116 117 SKAAAA DCLAAA VVVVxx
+5975 7492 1 3 5 15 75 975 1975 975 5975 150 151 VVAAAA ECLAAA AAAAxx
+332 7493 0 0 2 12 32 332 332 332 332 64 65 UMAAAA FCLAAA HHHHxx
+7990 7494 0 2 0 10 90 990 1990 2990 7990 180 181 IVAAAA GCLAAA OOOOxx
+8688 7495 0 0 8 8 88 688 688 3688 8688 176 177 EWAAAA HCLAAA VVVVxx
+9601 7496 1 1 1 1 1 601 1601 4601 9601 2 3 HFAAAA ICLAAA AAAAxx
+8401 7497 1 1 1 1 1 401 401 3401 8401 2 3 DLAAAA JCLAAA HHHHxx
+8093 7498 1 1 3 13 93 93 93 3093 8093 186 187 HZAAAA KCLAAA OOOOxx
+4278 7499 0 2 8 18 78 278 278 4278 4278 156 157 OIAAAA LCLAAA VVVVxx
+5467 7500 1 3 7 7 67 467 1467 467 5467 134 135 HCAAAA MCLAAA AAAAxx
+3137 7501 1 1 7 17 37 137 1137 3137 3137 74 75 RQAAAA NCLAAA HHHHxx
+204 7502 0 0 4 4 4 204 204 204 204 8 9 WHAAAA OCLAAA OOOOxx
+8224 7503 0 0 4 4 24 224 224 3224 8224 48 49 IEAAAA PCLAAA VVVVxx
+2944 7504 0 0 4 4 44 944 944 2944 2944 88 89 GJAAAA QCLAAA AAAAxx
+7593 7505 1 1 3 13 93 593 1593 2593 7593 186 187 BGAAAA RCLAAA HHHHxx
+814 7506 0 2 4 14 14 814 814 814 814 28 29 IFAAAA SCLAAA OOOOxx
+8047 7507 1 3 7 7 47 47 47 3047 8047 94 95 NXAAAA TCLAAA VVVVxx
+7802 7508 0 2 2 2 2 802 1802 2802 7802 4 5 COAAAA UCLAAA AAAAxx
+901 7509 1 1 1 1 1 901 901 901 901 2 3 RIAAAA VCLAAA HHHHxx
+6168 7510 0 0 8 8 68 168 168 1168 6168 136 137 GDAAAA WCLAAA OOOOxx
+2950 7511 0 2 0 10 50 950 950 2950 2950 100 101 MJAAAA XCLAAA VVVVxx
+5393 7512 1 1 3 13 93 393 1393 393 5393 186 187 LZAAAA YCLAAA AAAAxx
+3585 7513 1 1 5 5 85 585 1585 3585 3585 170 171 XHAAAA ZCLAAA HHHHxx
+9392 7514 0 0 2 12 92 392 1392 4392 9392 184 185 GXAAAA ADLAAA OOOOxx
+8314 7515 0 2 4 14 14 314 314 3314 8314 28 29 UHAAAA BDLAAA VVVVxx
+9972 7516 0 0 2 12 72 972 1972 4972 9972 144 145 OTAAAA CDLAAA AAAAxx
+9130 7517 0 2 0 10 30 130 1130 4130 9130 60 61 ENAAAA DDLAAA HHHHxx
+975 7518 1 3 5 15 75 975 975 975 975 150 151 NLAAAA EDLAAA OOOOxx
+5720 7519 0 0 0 0 20 720 1720 720 5720 40 41 AMAAAA FDLAAA VVVVxx
+3769 7520 1 1 9 9 69 769 1769 3769 3769 138 139 ZOAAAA GDLAAA AAAAxx
+5303 7521 1 3 3 3 3 303 1303 303 5303 6 7 ZVAAAA HDLAAA HHHHxx
+6564 7522 0 0 4 4 64 564 564 1564 6564 128 129 MSAAAA IDLAAA OOOOxx
+7855 7523 1 3 5 15 55 855 1855 2855 7855 110 111 DQAAAA JDLAAA VVVVxx
+8153 7524 1 1 3 13 53 153 153 3153 8153 106 107 PBAAAA KDLAAA AAAAxx
+2292 7525 0 0 2 12 92 292 292 2292 2292 184 185 EKAAAA LDLAAA HHHHxx
+3156 7526 0 0 6 16 56 156 1156 3156 3156 112 113 KRAAAA MDLAAA OOOOxx
+6580 7527 0 0 0 0 80 580 580 1580 6580 160 161 CTAAAA NDLAAA VVVVxx
+5324 7528 0 0 4 4 24 324 1324 324 5324 48 49 UWAAAA ODLAAA AAAAxx
+8871 7529 1 3 1 11 71 871 871 3871 8871 142 143 FDAAAA PDLAAA HHHHxx
+2543 7530 1 3 3 3 43 543 543 2543 2543 86 87 VTAAAA QDLAAA OOOOxx
+7857 7531 1 1 7 17 57 857 1857 2857 7857 114 115 FQAAAA RDLAAA VVVVxx
+4084 7532 0 0 4 4 84 84 84 4084 4084 168 169 CBAAAA SDLAAA AAAAxx
+9887 7533 1 3 7 7 87 887 1887 4887 9887 174 175 HQAAAA TDLAAA HHHHxx
+6940 7534 0 0 0 0 40 940 940 1940 6940 80 81 YGAAAA UDLAAA OOOOxx
+3415 7535 1 3 5 15 15 415 1415 3415 3415 30 31 JBAAAA VDLAAA VVVVxx
+5012 7536 0 0 2 12 12 12 1012 12 5012 24 25 UKAAAA WDLAAA AAAAxx
+3187 7537 1 3 7 7 87 187 1187 3187 3187 174 175 PSAAAA XDLAAA HHHHxx
+8556 7538 0 0 6 16 56 556 556 3556 8556 112 113 CRAAAA YDLAAA OOOOxx
+7966 7539 0 2 6 6 66 966 1966 2966 7966 132 133 KUAAAA ZDLAAA VVVVxx
+7481 7540 1 1 1 1 81 481 1481 2481 7481 162 163 TBAAAA AELAAA AAAAxx
+8524 7541 0 0 4 4 24 524 524 3524 8524 48 49 WPAAAA BELAAA HHHHxx
+3021 7542 1 1 1 1 21 21 1021 3021 3021 42 43 FMAAAA CELAAA OOOOxx
+6045 7543 1 1 5 5 45 45 45 1045 6045 90 91 NYAAAA DELAAA VVVVxx
+8022 7544 0 2 2 2 22 22 22 3022 8022 44 45 OWAAAA EELAAA AAAAxx
+3626 7545 0 2 6 6 26 626 1626 3626 3626 52 53 MJAAAA FELAAA HHHHxx
+1030 7546 0 2 0 10 30 30 1030 1030 1030 60 61 QNAAAA GELAAA OOOOxx
+8903 7547 1 3 3 3 3 903 903 3903 8903 6 7 LEAAAA HELAAA VVVVxx
+7488 7548 0 0 8 8 88 488 1488 2488 7488 176 177 ACAAAA IELAAA AAAAxx
+9293 7549 1 1 3 13 93 293 1293 4293 9293 186 187 LTAAAA JELAAA HHHHxx
+4586 7550 0 2 6 6 86 586 586 4586 4586 172 173 KUAAAA KELAAA OOOOxx
+9282 7551 0 2 2 2 82 282 1282 4282 9282 164 165 ATAAAA LELAAA VVVVxx
+1948 7552 0 0 8 8 48 948 1948 1948 1948 96 97 YWAAAA MELAAA AAAAxx
+2534 7553 0 2 4 14 34 534 534 2534 2534 68 69 MTAAAA NELAAA HHHHxx
+1150 7554 0 2 0 10 50 150 1150 1150 1150 100 101 GSAAAA OELAAA OOOOxx
+4931 7555 1 3 1 11 31 931 931 4931 4931 62 63 RHAAAA PELAAA VVVVxx
+2866 7556 0 2 6 6 66 866 866 2866 2866 132 133 GGAAAA QELAAA AAAAxx
+6172 7557 0 0 2 12 72 172 172 1172 6172 144 145 KDAAAA RELAAA HHHHxx
+4819 7558 1 3 9 19 19 819 819 4819 4819 38 39 JDAAAA SELAAA OOOOxx
+569 7559 1 1 9 9 69 569 569 569 569 138 139 XVAAAA TELAAA VVVVxx
+1146 7560 0 2 6 6 46 146 1146 1146 1146 92 93 CSAAAA UELAAA AAAAxx
+3062 7561 0 2 2 2 62 62 1062 3062 3062 124 125 UNAAAA VELAAA HHHHxx
+7690 7562 0 2 0 10 90 690 1690 2690 7690 180 181 UJAAAA WELAAA OOOOxx
+8611 7563 1 3 1 11 11 611 611 3611 8611 22 23 FTAAAA XELAAA VVVVxx
+1142 7564 0 2 2 2 42 142 1142 1142 1142 84 85 YRAAAA YELAAA AAAAxx
+1193 7565 1 1 3 13 93 193 1193 1193 1193 186 187 XTAAAA ZELAAA HHHHxx
+2507 7566 1 3 7 7 7 507 507 2507 2507 14 15 LSAAAA AFLAAA OOOOxx
+1043 7567 1 3 3 3 43 43 1043 1043 1043 86 87 DOAAAA BFLAAA VVVVxx
+7472 7568 0 0 2 12 72 472 1472 2472 7472 144 145 KBAAAA CFLAAA AAAAxx
+1817 7569 1 1 7 17 17 817 1817 1817 1817 34 35 XRAAAA DFLAAA HHHHxx
+3868 7570 0 0 8 8 68 868 1868 3868 3868 136 137 USAAAA EFLAAA OOOOxx
+9031 7571 1 3 1 11 31 31 1031 4031 9031 62 63 JJAAAA FFLAAA VVVVxx
+7254 7572 0 2 4 14 54 254 1254 2254 7254 108 109 ATAAAA GFLAAA AAAAxx
+5030 7573 0 2 0 10 30 30 1030 30 5030 60 61 MLAAAA HFLAAA HHHHxx
+6594 7574 0 2 4 14 94 594 594 1594 6594 188 189 QTAAAA IFLAAA OOOOxx
+6862 7575 0 2 2 2 62 862 862 1862 6862 124 125 YDAAAA JFLAAA VVVVxx
+1994 7576 0 2 4 14 94 994 1994 1994 1994 188 189 SYAAAA KFLAAA AAAAxx
+9017 7577 1 1 7 17 17 17 1017 4017 9017 34 35 VIAAAA LFLAAA HHHHxx
+5716 7578 0 0 6 16 16 716 1716 716 5716 32 33 WLAAAA MFLAAA OOOOxx
+1900 7579 0 0 0 0 0 900 1900 1900 1900 0 1 CVAAAA NFLAAA VVVVxx
+120 7580 0 0 0 0 20 120 120 120 120 40 41 QEAAAA OFLAAA AAAAxx
+9003 7581 1 3 3 3 3 3 1003 4003 9003 6 7 HIAAAA PFLAAA HHHHxx
+4178 7582 0 2 8 18 78 178 178 4178 4178 156 157 SEAAAA QFLAAA OOOOxx
+8777 7583 1 1 7 17 77 777 777 3777 8777 154 155 PZAAAA RFLAAA VVVVxx
+3653 7584 1 1 3 13 53 653 1653 3653 3653 106 107 NKAAAA SFLAAA AAAAxx
+1137 7585 1 1 7 17 37 137 1137 1137 1137 74 75 TRAAAA TFLAAA HHHHxx
+6362 7586 0 2 2 2 62 362 362 1362 6362 124 125 SKAAAA UFLAAA OOOOxx
+8537 7587 1 1 7 17 37 537 537 3537 8537 74 75 JQAAAA VFLAAA VVVVxx
+1590 7588 0 2 0 10 90 590 1590 1590 1590 180 181 EJAAAA WFLAAA AAAAxx
+374 7589 0 2 4 14 74 374 374 374 374 148 149 KOAAAA XFLAAA HHHHxx
+2597 7590 1 1 7 17 97 597 597 2597 2597 194 195 XVAAAA YFLAAA OOOOxx
+8071 7591 1 3 1 11 71 71 71 3071 8071 142 143 LYAAAA ZFLAAA VVVVxx
+9009 7592 1 1 9 9 9 9 1009 4009 9009 18 19 NIAAAA AGLAAA AAAAxx
+1978 7593 0 2 8 18 78 978 1978 1978 1978 156 157 CYAAAA BGLAAA HHHHxx
+1541 7594 1 1 1 1 41 541 1541 1541 1541 82 83 HHAAAA CGLAAA OOOOxx
+4998 7595 0 2 8 18 98 998 998 4998 4998 196 197 GKAAAA DGLAAA VVVVxx
+1649 7596 1 1 9 9 49 649 1649 1649 1649 98 99 LLAAAA EGLAAA AAAAxx
+5426 7597 0 2 6 6 26 426 1426 426 5426 52 53 SAAAAA FGLAAA HHHHxx
+1492 7598 0 0 2 12 92 492 1492 1492 1492 184 185 KFAAAA GGLAAA OOOOxx
+9622 7599 0 2 2 2 22 622 1622 4622 9622 44 45 CGAAAA HGLAAA VVVVxx
+701 7600 1 1 1 1 1 701 701 701 701 2 3 ZAAAAA IGLAAA AAAAxx
+2781 7601 1 1 1 1 81 781 781 2781 2781 162 163 ZCAAAA JGLAAA HHHHxx
+3982 7602 0 2 2 2 82 982 1982 3982 3982 164 165 EXAAAA KGLAAA OOOOxx
+7259 7603 1 3 9 19 59 259 1259 2259 7259 118 119 FTAAAA LGLAAA VVVVxx
+9868 7604 0 0 8 8 68 868 1868 4868 9868 136 137 OPAAAA MGLAAA AAAAxx
+564 7605 0 0 4 4 64 564 564 564 564 128 129 SVAAAA NGLAAA HHHHxx
+6315 7606 1 3 5 15 15 315 315 1315 6315 30 31 XIAAAA OGLAAA OOOOxx
+9092 7607 0 0 2 12 92 92 1092 4092 9092 184 185 SLAAAA PGLAAA VVVVxx
+8237 7608 1 1 7 17 37 237 237 3237 8237 74 75 VEAAAA QGLAAA AAAAxx
+1513 7609 1 1 3 13 13 513 1513 1513 1513 26 27 FGAAAA RGLAAA HHHHxx
+1922 7610 0 2 2 2 22 922 1922 1922 1922 44 45 YVAAAA SGLAAA OOOOxx
+5396 7611 0 0 6 16 96 396 1396 396 5396 192 193 OZAAAA TGLAAA VVVVxx
+2485 7612 1 1 5 5 85 485 485 2485 2485 170 171 PRAAAA UGLAAA AAAAxx
+5774 7613 0 2 4 14 74 774 1774 774 5774 148 149 COAAAA VGLAAA HHHHxx
+3983 7614 1 3 3 3 83 983 1983 3983 3983 166 167 FXAAAA WGLAAA OOOOxx
+221 7615 1 1 1 1 21 221 221 221 221 42 43 NIAAAA XGLAAA VVVVxx
+8662 7616 0 2 2 2 62 662 662 3662 8662 124 125 EVAAAA YGLAAA AAAAxx
+2456 7617 0 0 6 16 56 456 456 2456 2456 112 113 MQAAAA ZGLAAA HHHHxx
+9736 7618 0 0 6 16 36 736 1736 4736 9736 72 73 MKAAAA AHLAAA OOOOxx
+8936 7619 0 0 6 16 36 936 936 3936 8936 72 73 SFAAAA BHLAAA VVVVxx
+5395 7620 1 3 5 15 95 395 1395 395 5395 190 191 NZAAAA CHLAAA AAAAxx
+9523 7621 1 3 3 3 23 523 1523 4523 9523 46 47 HCAAAA DHLAAA HHHHxx
+6980 7622 0 0 0 0 80 980 980 1980 6980 160 161 MIAAAA EHLAAA OOOOxx
+2091 7623 1 3 1 11 91 91 91 2091 2091 182 183 LCAAAA FHLAAA VVVVxx
+6807 7624 1 3 7 7 7 807 807 1807 6807 14 15 VBAAAA GHLAAA AAAAxx
+8818 7625 0 2 8 18 18 818 818 3818 8818 36 37 EBAAAA HHLAAA HHHHxx
+5298 7626 0 2 8 18 98 298 1298 298 5298 196 197 UVAAAA IHLAAA OOOOxx
+1726 7627 0 2 6 6 26 726 1726 1726 1726 52 53 KOAAAA JHLAAA VVVVxx
+3878 7628 0 2 8 18 78 878 1878 3878 3878 156 157 ETAAAA KHLAAA AAAAxx
+8700 7629 0 0 0 0 0 700 700 3700 8700 0 1 QWAAAA LHLAAA HHHHxx
+5201 7630 1 1 1 1 1 201 1201 201 5201 2 3 BSAAAA MHLAAA OOOOxx
+3936 7631 0 0 6 16 36 936 1936 3936 3936 72 73 KVAAAA NHLAAA VVVVxx
+776 7632 0 0 6 16 76 776 776 776 776 152 153 WDAAAA OHLAAA AAAAxx
+5302 7633 0 2 2 2 2 302 1302 302 5302 4 5 YVAAAA PHLAAA HHHHxx
+3595 7634 1 3 5 15 95 595 1595 3595 3595 190 191 HIAAAA QHLAAA OOOOxx
+9061 7635 1 1 1 1 61 61 1061 4061 9061 122 123 NKAAAA RHLAAA VVVVxx
+6261 7636 1 1 1 1 61 261 261 1261 6261 122 123 VGAAAA SHLAAA AAAAxx
+8878 7637 0 2 8 18 78 878 878 3878 8878 156 157 MDAAAA THLAAA HHHHxx
+3312 7638 0 0 2 12 12 312 1312 3312 3312 24 25 KXAAAA UHLAAA OOOOxx
+9422 7639 0 2 2 2 22 422 1422 4422 9422 44 45 KYAAAA VHLAAA VVVVxx
+7321 7640 1 1 1 1 21 321 1321 2321 7321 42 43 PVAAAA WHLAAA AAAAxx
+3813 7641 1 1 3 13 13 813 1813 3813 3813 26 27 RQAAAA XHLAAA HHHHxx
+5848 7642 0 0 8 8 48 848 1848 848 5848 96 97 YQAAAA YHLAAA OOOOxx
+3535 7643 1 3 5 15 35 535 1535 3535 3535 70 71 ZFAAAA ZHLAAA VVVVxx
+1040 7644 0 0 0 0 40 40 1040 1040 1040 80 81 AOAAAA AILAAA AAAAxx
+8572 7645 0 0 2 12 72 572 572 3572 8572 144 145 SRAAAA BILAAA HHHHxx
+5435 7646 1 3 5 15 35 435 1435 435 5435 70 71 BBAAAA CILAAA OOOOxx
+8199 7647 1 3 9 19 99 199 199 3199 8199 198 199 JDAAAA DILAAA VVVVxx
+8775 7648 1 3 5 15 75 775 775 3775 8775 150 151 NZAAAA EILAAA AAAAxx
+7722 7649 0 2 2 2 22 722 1722 2722 7722 44 45 ALAAAA FILAAA HHHHxx
+3549 7650 1 1 9 9 49 549 1549 3549 3549 98 99 NGAAAA GILAAA OOOOxx
+2578 7651 0 2 8 18 78 578 578 2578 2578 156 157 EVAAAA HILAAA VVVVxx
+1695 7652 1 3 5 15 95 695 1695 1695 1695 190 191 FNAAAA IILAAA AAAAxx
+1902 7653 0 2 2 2 2 902 1902 1902 1902 4 5 EVAAAA JILAAA HHHHxx
+6058 7654 0 2 8 18 58 58 58 1058 6058 116 117 AZAAAA KILAAA OOOOxx
+6591 7655 1 3 1 11 91 591 591 1591 6591 182 183 NTAAAA LILAAA VVVVxx
+7962 7656 0 2 2 2 62 962 1962 2962 7962 124 125 GUAAAA MILAAA AAAAxx
+5612 7657 0 0 2 12 12 612 1612 612 5612 24 25 WHAAAA NILAAA HHHHxx
+3341 7658 1 1 1 1 41 341 1341 3341 3341 82 83 NYAAAA OILAAA OOOOxx
+5460 7659 0 0 0 0 60 460 1460 460 5460 120 121 ACAAAA PILAAA VVVVxx
+2368 7660 0 0 8 8 68 368 368 2368 2368 136 137 CNAAAA QILAAA AAAAxx
+8646 7661 0 2 6 6 46 646 646 3646 8646 92 93 OUAAAA RILAAA HHHHxx
+4987 7662 1 3 7 7 87 987 987 4987 4987 174 175 VJAAAA SILAAA OOOOxx
+9018 7663 0 2 8 18 18 18 1018 4018 9018 36 37 WIAAAA TILAAA VVVVxx
+8685 7664 1 1 5 5 85 685 685 3685 8685 170 171 BWAAAA UILAAA AAAAxx
+694 7665 0 2 4 14 94 694 694 694 694 188 189 SAAAAA VILAAA HHHHxx
+2012 7666 0 0 2 12 12 12 12 2012 2012 24 25 KZAAAA WILAAA OOOOxx
+2417 7667 1 1 7 17 17 417 417 2417 2417 34 35 ZOAAAA XILAAA VVVVxx
+4022 7668 0 2 2 2 22 22 22 4022 4022 44 45 SYAAAA YILAAA AAAAxx
+5935 7669 1 3 5 15 35 935 1935 935 5935 70 71 HUAAAA ZILAAA HHHHxx
+1656 7670 0 0 6 16 56 656 1656 1656 1656 112 113 SLAAAA AJLAAA OOOOxx
+6195 7671 1 3 5 15 95 195 195 1195 6195 190 191 HEAAAA BJLAAA VVVVxx
+3057 7672 1 1 7 17 57 57 1057 3057 3057 114 115 PNAAAA CJLAAA AAAAxx
+2852 7673 0 0 2 12 52 852 852 2852 2852 104 105 SFAAAA DJLAAA HHHHxx
+4634 7674 0 2 4 14 34 634 634 4634 4634 68 69 GWAAAA EJLAAA OOOOxx
+1689 7675 1 1 9 9 89 689 1689 1689 1689 178 179 ZMAAAA FJLAAA VVVVxx
+4102 7676 0 2 2 2 2 102 102 4102 4102 4 5 UBAAAA GJLAAA AAAAxx
+3287 7677 1 3 7 7 87 287 1287 3287 3287 174 175 LWAAAA HJLAAA HHHHxx
+5246 7678 0 2 6 6 46 246 1246 246 5246 92 93 UTAAAA IJLAAA OOOOxx
+7450 7679 0 2 0 10 50 450 1450 2450 7450 100 101 OAAAAA JJLAAA VVVVxx
+6548 7680 0 0 8 8 48 548 548 1548 6548 96 97 WRAAAA KJLAAA AAAAxx
+379 7681 1 3 9 19 79 379 379 379 379 158 159 POAAAA LJLAAA HHHHxx
+7435 7682 1 3 5 15 35 435 1435 2435 7435 70 71 ZZAAAA MJLAAA OOOOxx
+2041 7683 1 1 1 1 41 41 41 2041 2041 82 83 NAAAAA NJLAAA VVVVxx
+8462 7684 0 2 2 2 62 462 462 3462 8462 124 125 MNAAAA OJLAAA AAAAxx
+9076 7685 0 0 6 16 76 76 1076 4076 9076 152 153 CLAAAA PJLAAA HHHHxx
+761 7686 1 1 1 1 61 761 761 761 761 122 123 HDAAAA QJLAAA OOOOxx
+795 7687 1 3 5 15 95 795 795 795 795 190 191 PEAAAA RJLAAA VVVVxx
+1671 7688 1 3 1 11 71 671 1671 1671 1671 142 143 HMAAAA SJLAAA AAAAxx
+695 7689 1 3 5 15 95 695 695 695 695 190 191 TAAAAA TJLAAA HHHHxx
+4981 7690 1 1 1 1 81 981 981 4981 4981 162 163 PJAAAA UJLAAA OOOOxx
+1211 7691 1 3 1 11 11 211 1211 1211 1211 22 23 PUAAAA VJLAAA VVVVxx
+5914 7692 0 2 4 14 14 914 1914 914 5914 28 29 MTAAAA WJLAAA AAAAxx
+9356 7693 0 0 6 16 56 356 1356 4356 9356 112 113 WVAAAA XJLAAA HHHHxx
+1500 7694 0 0 0 0 0 500 1500 1500 1500 0 1 SFAAAA YJLAAA OOOOxx
+3353 7695 1 1 3 13 53 353 1353 3353 3353 106 107 ZYAAAA ZJLAAA VVVVxx
+1060 7696 0 0 0 0 60 60 1060 1060 1060 120 121 UOAAAA AKLAAA AAAAxx
+7910 7697 0 2 0 10 10 910 1910 2910 7910 20 21 GSAAAA BKLAAA HHHHxx
+1329 7698 1 1 9 9 29 329 1329 1329 1329 58 59 DZAAAA CKLAAA OOOOxx
+6011 7699 1 3 1 11 11 11 11 1011 6011 22 23 FXAAAA DKLAAA VVVVxx
+7146 7700 0 2 6 6 46 146 1146 2146 7146 92 93 WOAAAA EKLAAA AAAAxx
+4602 7701 0 2 2 2 2 602 602 4602 4602 4 5 AVAAAA FKLAAA HHHHxx
+6751 7702 1 3 1 11 51 751 751 1751 6751 102 103 RZAAAA GKLAAA OOOOxx
+2666 7703 0 2 6 6 66 666 666 2666 2666 132 133 OYAAAA HKLAAA VVVVxx
+2785 7704 1 1 5 5 85 785 785 2785 2785 170 171 DDAAAA IKLAAA AAAAxx
+5851 7705 1 3 1 11 51 851 1851 851 5851 102 103 BRAAAA JKLAAA HHHHxx
+2435 7706 1 3 5 15 35 435 435 2435 2435 70 71 RPAAAA KKLAAA OOOOxx
+7429 7707 1 1 9 9 29 429 1429 2429 7429 58 59 TZAAAA LKLAAA VVVVxx
+4241 7708 1 1 1 1 41 241 241 4241 4241 82 83 DHAAAA MKLAAA AAAAxx
+5691 7709 1 3 1 11 91 691 1691 691 5691 182 183 XKAAAA NKLAAA HHHHxx
+7731 7710 1 3 1 11 31 731 1731 2731 7731 62 63 JLAAAA OKLAAA OOOOxx
+249 7711 1 1 9 9 49 249 249 249 249 98 99 PJAAAA PKLAAA VVVVxx
+1731 7712 1 3 1 11 31 731 1731 1731 1731 62 63 POAAAA QKLAAA AAAAxx
+8716 7713 0 0 6 16 16 716 716 3716 8716 32 33 GXAAAA RKLAAA HHHHxx
+2670 7714 0 2 0 10 70 670 670 2670 2670 140 141 SYAAAA SKLAAA OOOOxx
+4654 7715 0 2 4 14 54 654 654 4654 4654 108 109 AXAAAA TKLAAA VVVVxx
+1027 7716 1 3 7 7 27 27 1027 1027 1027 54 55 NNAAAA UKLAAA AAAAxx
+1099 7717 1 3 9 19 99 99 1099 1099 1099 198 199 HQAAAA VKLAAA HHHHxx
+3617 7718 1 1 7 17 17 617 1617 3617 3617 34 35 DJAAAA WKLAAA OOOOxx
+4330 7719 0 2 0 10 30 330 330 4330 4330 60 61 OKAAAA XKLAAA VVVVxx
+9750 7720 0 2 0 10 50 750 1750 4750 9750 100 101 ALAAAA YKLAAA AAAAxx
+467 7721 1 3 7 7 67 467 467 467 467 134 135 ZRAAAA ZKLAAA HHHHxx
+8525 7722 1 1 5 5 25 525 525 3525 8525 50 51 XPAAAA ALLAAA OOOOxx
+5990 7723 0 2 0 10 90 990 1990 990 5990 180 181 KWAAAA BLLAAA VVVVxx
+4839 7724 1 3 9 19 39 839 839 4839 4839 78 79 DEAAAA CLLAAA AAAAxx
+9914 7725 0 2 4 14 14 914 1914 4914 9914 28 29 IRAAAA DLLAAA HHHHxx
+7047 7726 1 3 7 7 47 47 1047 2047 7047 94 95 BLAAAA ELLAAA OOOOxx
+874 7727 0 2 4 14 74 874 874 874 874 148 149 QHAAAA FLLAAA VVVVxx
+6061 7728 1 1 1 1 61 61 61 1061 6061 122 123 DZAAAA GLLAAA AAAAxx
+5491 7729 1 3 1 11 91 491 1491 491 5491 182 183 FDAAAA HLLAAA HHHHxx
+4344 7730 0 0 4 4 44 344 344 4344 4344 88 89 CLAAAA ILLAAA OOOOxx
+1281 7731 1 1 1 1 81 281 1281 1281 1281 162 163 HXAAAA JLLAAA VVVVxx
+3597 7732 1 1 7 17 97 597 1597 3597 3597 194 195 JIAAAA KLLAAA AAAAxx
+4992 7733 0 0 2 12 92 992 992 4992 4992 184 185 AKAAAA LLLAAA HHHHxx
+3849 7734 1 1 9 9 49 849 1849 3849 3849 98 99 BSAAAA MLLAAA OOOOxx
+2655 7735 1 3 5 15 55 655 655 2655 2655 110 111 DYAAAA NLLAAA VVVVxx
+147 7736 1 3 7 7 47 147 147 147 147 94 95 RFAAAA OLLAAA AAAAxx
+9110 7737 0 2 0 10 10 110 1110 4110 9110 20 21 KMAAAA PLLAAA HHHHxx
+1637 7738 1 1 7 17 37 637 1637 1637 1637 74 75 ZKAAAA QLLAAA OOOOxx
+9826 7739 0 2 6 6 26 826 1826 4826 9826 52 53 YNAAAA RLLAAA VVVVxx
+5957 7740 1 1 7 17 57 957 1957 957 5957 114 115 DVAAAA SLLAAA AAAAxx
+6932 7741 0 0 2 12 32 932 932 1932 6932 64 65 QGAAAA TLLAAA HHHHxx
+9684 7742 0 0 4 4 84 684 1684 4684 9684 168 169 MIAAAA ULLAAA OOOOxx
+4653 7743 1 1 3 13 53 653 653 4653 4653 106 107 ZWAAAA VLLAAA VVVVxx
+8065 7744 1 1 5 5 65 65 65 3065 8065 130 131 FYAAAA WLLAAA AAAAxx
+1202 7745 0 2 2 2 2 202 1202 1202 1202 4 5 GUAAAA XLLAAA HHHHxx
+9214 7746 0 2 4 14 14 214 1214 4214 9214 28 29 KQAAAA YLLAAA OOOOxx
+196 7747 0 0 6 16 96 196 196 196 196 192 193 OHAAAA ZLLAAA VVVVxx
+4486 7748 0 2 6 6 86 486 486 4486 4486 172 173 OQAAAA AMLAAA AAAAxx
+2585 7749 1 1 5 5 85 585 585 2585 2585 170 171 LVAAAA BMLAAA HHHHxx
+2464 7750 0 0 4 4 64 464 464 2464 2464 128 129 UQAAAA CMLAAA OOOOxx
+3467 7751 1 3 7 7 67 467 1467 3467 3467 134 135 JDAAAA DMLAAA VVVVxx
+9295 7752 1 3 5 15 95 295 1295 4295 9295 190 191 NTAAAA EMLAAA AAAAxx
+517 7753 1 1 7 17 17 517 517 517 517 34 35 XTAAAA FMLAAA HHHHxx
+6870 7754 0 2 0 10 70 870 870 1870 6870 140 141 GEAAAA GMLAAA OOOOxx
+5732 7755 0 0 2 12 32 732 1732 732 5732 64 65 MMAAAA HMLAAA VVVVxx
+9376 7756 0 0 6 16 76 376 1376 4376 9376 152 153 QWAAAA IMLAAA AAAAxx
+838 7757 0 2 8 18 38 838 838 838 838 76 77 GGAAAA JMLAAA HHHHxx
+9254 7758 0 2 4 14 54 254 1254 4254 9254 108 109 YRAAAA KMLAAA OOOOxx
+8879 7759 1 3 9 19 79 879 879 3879 8879 158 159 NDAAAA LMLAAA VVVVxx
+6281 7760 1 1 1 1 81 281 281 1281 6281 162 163 PHAAAA MMLAAA AAAAxx
+8216 7761 0 0 6 16 16 216 216 3216 8216 32 33 AEAAAA NMLAAA HHHHxx
+9213 7762 1 1 3 13 13 213 1213 4213 9213 26 27 JQAAAA OMLAAA OOOOxx
+7234 7763 0 2 4 14 34 234 1234 2234 7234 68 69 GSAAAA PMLAAA VVVVxx
+5692 7764 0 0 2 12 92 692 1692 692 5692 184 185 YKAAAA QMLAAA AAAAxx
+693 7765 1 1 3 13 93 693 693 693 693 186 187 RAAAAA RMLAAA HHHHxx
+9050 7766 0 2 0 10 50 50 1050 4050 9050 100 101 CKAAAA SMLAAA OOOOxx
+3623 7767 1 3 3 3 23 623 1623 3623 3623 46 47 JJAAAA TMLAAA VVVVxx
+2130 7768 0 2 0 10 30 130 130 2130 2130 60 61 YDAAAA UMLAAA AAAAxx
+2514 7769 0 2 4 14 14 514 514 2514 2514 28 29 SSAAAA VMLAAA HHHHxx
+1812 7770 0 0 2 12 12 812 1812 1812 1812 24 25 SRAAAA WMLAAA OOOOxx
+9037 7771 1 1 7 17 37 37 1037 4037 9037 74 75 PJAAAA XMLAAA VVVVxx
+5054 7772 0 2 4 14 54 54 1054 54 5054 108 109 KMAAAA YMLAAA AAAAxx
+7801 7773 1 1 1 1 1 801 1801 2801 7801 2 3 BOAAAA ZMLAAA HHHHxx
+7939 7774 1 3 9 19 39 939 1939 2939 7939 78 79 JTAAAA ANLAAA OOOOxx
+7374 7775 0 2 4 14 74 374 1374 2374 7374 148 149 QXAAAA BNLAAA VVVVxx
+1058 7776 0 2 8 18 58 58 1058 1058 1058 116 117 SOAAAA CNLAAA AAAAxx
+1972 7777 0 0 2 12 72 972 1972 1972 1972 144 145 WXAAAA DNLAAA HHHHxx
+3741 7778 1 1 1 1 41 741 1741 3741 3741 82 83 XNAAAA ENLAAA OOOOxx
+2227 7779 1 3 7 7 27 227 227 2227 2227 54 55 RHAAAA FNLAAA VVVVxx
+304 7780 0 0 4 4 4 304 304 304 304 8 9 SLAAAA GNLAAA AAAAxx
+4914 7781 0 2 4 14 14 914 914 4914 4914 28 29 AHAAAA HNLAAA HHHHxx
+2428 7782 0 0 8 8 28 428 428 2428 2428 56 57 KPAAAA INLAAA OOOOxx
+6660 7783 0 0 0 0 60 660 660 1660 6660 120 121 EWAAAA JNLAAA VVVVxx
+2676 7784 0 0 6 16 76 676 676 2676 2676 152 153 YYAAAA KNLAAA AAAAxx
+2454 7785 0 2 4 14 54 454 454 2454 2454 108 109 KQAAAA LNLAAA HHHHxx
+3798 7786 0 2 8 18 98 798 1798 3798 3798 196 197 CQAAAA MNLAAA OOOOxx
+1341 7787 1 1 1 1 41 341 1341 1341 1341 82 83 PZAAAA NNLAAA VVVVxx
+1611 7788 1 3 1 11 11 611 1611 1611 1611 22 23 ZJAAAA ONLAAA AAAAxx
+2681 7789 1 1 1 1 81 681 681 2681 2681 162 163 DZAAAA PNLAAA HHHHxx
+7292 7790 0 0 2 12 92 292 1292 2292 7292 184 185 MUAAAA QNLAAA OOOOxx
+7775 7791 1 3 5 15 75 775 1775 2775 7775 150 151 BNAAAA RNLAAA VVVVxx
+794 7792 0 2 4 14 94 794 794 794 794 188 189 OEAAAA SNLAAA AAAAxx
+8709 7793 1 1 9 9 9 709 709 3709 8709 18 19 ZWAAAA TNLAAA HHHHxx
+1901 7794 1 1 1 1 1 901 1901 1901 1901 2 3 DVAAAA UNLAAA OOOOxx
+3089 7795 1 1 9 9 89 89 1089 3089 3089 178 179 VOAAAA VNLAAA VVVVxx
+7797 7796 1 1 7 17 97 797 1797 2797 7797 194 195 XNAAAA WNLAAA AAAAxx
+6070 7797 0 2 0 10 70 70 70 1070 6070 140 141 MZAAAA XNLAAA HHHHxx
+2191 7798 1 3 1 11 91 191 191 2191 2191 182 183 HGAAAA YNLAAA OOOOxx
+3497 7799 1 1 7 17 97 497 1497 3497 3497 194 195 NEAAAA ZNLAAA VVVVxx
+8302 7800 0 2 2 2 2 302 302 3302 8302 4 5 IHAAAA AOLAAA AAAAxx
+4365 7801 1 1 5 5 65 365 365 4365 4365 130 131 XLAAAA BOLAAA HHHHxx
+3588 7802 0 0 8 8 88 588 1588 3588 3588 176 177 AIAAAA COLAAA OOOOxx
+8292 7803 0 0 2 12 92 292 292 3292 8292 184 185 YGAAAA DOLAAA VVVVxx
+4696 7804 0 0 6 16 96 696 696 4696 4696 192 193 QYAAAA EOLAAA AAAAxx
+5641 7805 1 1 1 1 41 641 1641 641 5641 82 83 ZIAAAA FOLAAA HHHHxx
+9386 7806 0 2 6 6 86 386 1386 4386 9386 172 173 AXAAAA GOLAAA OOOOxx
+507 7807 1 3 7 7 7 507 507 507 507 14 15 NTAAAA HOLAAA VVVVxx
+7201 7808 1 1 1 1 1 201 1201 2201 7201 2 3 ZQAAAA IOLAAA AAAAxx
+7785 7809 1 1 5 5 85 785 1785 2785 7785 170 171 LNAAAA JOLAAA HHHHxx
+463 7810 1 3 3 3 63 463 463 463 463 126 127 VRAAAA KOLAAA OOOOxx
+6656 7811 0 0 6 16 56 656 656 1656 6656 112 113 AWAAAA LOLAAA VVVVxx
+807 7812 1 3 7 7 7 807 807 807 807 14 15 BFAAAA MOLAAA AAAAxx
+7278 7813 0 2 8 18 78 278 1278 2278 7278 156 157 YTAAAA NOLAAA HHHHxx
+6237 7814 1 1 7 17 37 237 237 1237 6237 74 75 XFAAAA OOLAAA OOOOxx
+7671 7815 1 3 1 11 71 671 1671 2671 7671 142 143 BJAAAA POLAAA VVVVxx
+2235 7816 1 3 5 15 35 235 235 2235 2235 70 71 ZHAAAA QOLAAA AAAAxx
+4042 7817 0 2 2 2 42 42 42 4042 4042 84 85 MZAAAA ROLAAA HHHHxx
+5273 7818 1 1 3 13 73 273 1273 273 5273 146 147 VUAAAA SOLAAA OOOOxx
+7557 7819 1 1 7 17 57 557 1557 2557 7557 114 115 REAAAA TOLAAA VVVVxx
+4007 7820 1 3 7 7 7 7 7 4007 4007 14 15 DYAAAA UOLAAA AAAAxx
+1428 7821 0 0 8 8 28 428 1428 1428 1428 56 57 YCAAAA VOLAAA HHHHxx
+9739 7822 1 3 9 19 39 739 1739 4739 9739 78 79 PKAAAA WOLAAA OOOOxx
+7836 7823 0 0 6 16 36 836 1836 2836 7836 72 73 KPAAAA XOLAAA VVVVxx
+1777 7824 1 1 7 17 77 777 1777 1777 1777 154 155 JQAAAA YOLAAA AAAAxx
+5192 7825 0 0 2 12 92 192 1192 192 5192 184 185 SRAAAA ZOLAAA HHHHxx
+7236 7826 0 0 6 16 36 236 1236 2236 7236 72 73 ISAAAA APLAAA OOOOxx
+1623 7827 1 3 3 3 23 623 1623 1623 1623 46 47 LKAAAA BPLAAA VVVVxx
+8288 7828 0 0 8 8 88 288 288 3288 8288 176 177 UGAAAA CPLAAA AAAAxx
+2827 7829 1 3 7 7 27 827 827 2827 2827 54 55 TEAAAA DPLAAA HHHHxx
+458 7830 0 2 8 18 58 458 458 458 458 116 117 QRAAAA EPLAAA OOOOxx
+1818 7831 0 2 8 18 18 818 1818 1818 1818 36 37 YRAAAA FPLAAA VVVVxx
+6837 7832 1 1 7 17 37 837 837 1837 6837 74 75 ZCAAAA GPLAAA AAAAxx
+7825 7833 1 1 5 5 25 825 1825 2825 7825 50 51 ZOAAAA HPLAAA HHHHxx
+9146 7834 0 2 6 6 46 146 1146 4146 9146 92 93 UNAAAA IPLAAA OOOOxx
+8451 7835 1 3 1 11 51 451 451 3451 8451 102 103 BNAAAA JPLAAA VVVVxx
+6438 7836 0 2 8 18 38 438 438 1438 6438 76 77 QNAAAA KPLAAA AAAAxx
+4020 7837 0 0 0 0 20 20 20 4020 4020 40 41 QYAAAA LPLAAA HHHHxx
+4068 7838 0 0 8 8 68 68 68 4068 4068 136 137 MAAAAA MPLAAA OOOOxx
+2411 7839 1 3 1 11 11 411 411 2411 2411 22 23 TOAAAA NPLAAA VVVVxx
+6222 7840 0 2 2 2 22 222 222 1222 6222 44 45 IFAAAA OPLAAA AAAAxx
+3164 7841 0 0 4 4 64 164 1164 3164 3164 128 129 SRAAAA PPLAAA HHHHxx
+311 7842 1 3 1 11 11 311 311 311 311 22 23 ZLAAAA QPLAAA OOOOxx
+5683 7843 1 3 3 3 83 683 1683 683 5683 166 167 PKAAAA RPLAAA VVVVxx
+3993 7844 1 1 3 13 93 993 1993 3993 3993 186 187 PXAAAA SPLAAA AAAAxx
+9897 7845 1 1 7 17 97 897 1897 4897 9897 194 195 RQAAAA TPLAAA HHHHxx
+6609 7846 1 1 9 9 9 609 609 1609 6609 18 19 FUAAAA UPLAAA OOOOxx
+1362 7847 0 2 2 2 62 362 1362 1362 1362 124 125 KAAAAA VPLAAA VVVVxx
+3918 7848 0 2 8 18 18 918 1918 3918 3918 36 37 SUAAAA WPLAAA AAAAxx
+7376 7849 0 0 6 16 76 376 1376 2376 7376 152 153 SXAAAA XPLAAA HHHHxx
+6996 7850 0 0 6 16 96 996 996 1996 6996 192 193 CJAAAA YPLAAA OOOOxx
+9567 7851 1 3 7 7 67 567 1567 4567 9567 134 135 ZDAAAA ZPLAAA VVVVxx
+7525 7852 1 1 5 5 25 525 1525 2525 7525 50 51 LDAAAA AQLAAA AAAAxx
+9069 7853 1 1 9 9 69 69 1069 4069 9069 138 139 VKAAAA BQLAAA HHHHxx
+9999 7854 1 3 9 19 99 999 1999 4999 9999 198 199 PUAAAA CQLAAA OOOOxx
+9237 7855 1 1 7 17 37 237 1237 4237 9237 74 75 HRAAAA DQLAAA VVVVxx
+8441 7856 1 1 1 1 41 441 441 3441 8441 82 83 RMAAAA EQLAAA AAAAxx
+6769 7857 1 1 9 9 69 769 769 1769 6769 138 139 JAAAAA FQLAAA HHHHxx
+6073 7858 1 1 3 13 73 73 73 1073 6073 146 147 PZAAAA GQLAAA OOOOxx
+1091 7859 1 3 1 11 91 91 1091 1091 1091 182 183 ZPAAAA HQLAAA VVVVxx
+9886 7860 0 2 6 6 86 886 1886 4886 9886 172 173 GQAAAA IQLAAA AAAAxx
+3971 7861 1 3 1 11 71 971 1971 3971 3971 142 143 TWAAAA JQLAAA HHHHxx
+4621 7862 1 1 1 1 21 621 621 4621 4621 42 43 TVAAAA KQLAAA OOOOxx
+3120 7863 0 0 0 0 20 120 1120 3120 3120 40 41 AQAAAA LQLAAA VVVVxx
+9773 7864 1 1 3 13 73 773 1773 4773 9773 146 147 XLAAAA MQLAAA AAAAxx
+8712 7865 0 0 2 12 12 712 712 3712 8712 24 25 CXAAAA NQLAAA HHHHxx
+801 7866 1 1 1 1 1 801 801 801 801 2 3 VEAAAA OQLAAA OOOOxx
+9478 7867 0 2 8 18 78 478 1478 4478 9478 156 157 OAAAAA PQLAAA VVVVxx
+3466 7868 0 2 6 6 66 466 1466 3466 3466 132 133 IDAAAA QQLAAA AAAAxx
+6326 7869 0 2 6 6 26 326 326 1326 6326 52 53 IJAAAA RQLAAA HHHHxx
+1723 7870 1 3 3 3 23 723 1723 1723 1723 46 47 HOAAAA SQLAAA OOOOxx
+4978 7871 0 2 8 18 78 978 978 4978 4978 156 157 MJAAAA TQLAAA VVVVxx
+2311 7872 1 3 1 11 11 311 311 2311 2311 22 23 XKAAAA UQLAAA AAAAxx
+9532 7873 0 0 2 12 32 532 1532 4532 9532 64 65 QCAAAA VQLAAA HHHHxx
+3680 7874 0 0 0 0 80 680 1680 3680 3680 160 161 OLAAAA WQLAAA OOOOxx
+1244 7875 0 0 4 4 44 244 1244 1244 1244 88 89 WVAAAA XQLAAA VVVVxx
+3821 7876 1 1 1 1 21 821 1821 3821 3821 42 43 ZQAAAA YQLAAA AAAAxx
+9586 7877 0 2 6 6 86 586 1586 4586 9586 172 173 SEAAAA ZQLAAA HHHHxx
+3894 7878 0 2 4 14 94 894 1894 3894 3894 188 189 UTAAAA ARLAAA OOOOxx
+6169 7879 1 1 9 9 69 169 169 1169 6169 138 139 HDAAAA BRLAAA VVVVxx
+5919 7880 1 3 9 19 19 919 1919 919 5919 38 39 RTAAAA CRLAAA AAAAxx
+4187 7881 1 3 7 7 87 187 187 4187 4187 174 175 BFAAAA DRLAAA HHHHxx
+5477 7882 1 1 7 17 77 477 1477 477 5477 154 155 RCAAAA ERLAAA OOOOxx
+2806 7883 0 2 6 6 6 806 806 2806 2806 12 13 YDAAAA FRLAAA VVVVxx
+8158 7884 0 2 8 18 58 158 158 3158 8158 116 117 UBAAAA GRLAAA AAAAxx
+7130 7885 0 2 0 10 30 130 1130 2130 7130 60 61 GOAAAA HRLAAA HHHHxx
+7133 7886 1 1 3 13 33 133 1133 2133 7133 66 67 JOAAAA IRLAAA OOOOxx
+6033 7887 1 1 3 13 33 33 33 1033 6033 66 67 BYAAAA JRLAAA VVVVxx
+2415 7888 1 3 5 15 15 415 415 2415 2415 30 31 XOAAAA KRLAAA AAAAxx
+8091 7889 1 3 1 11 91 91 91 3091 8091 182 183 FZAAAA LRLAAA HHHHxx
+8347 7890 1 3 7 7 47 347 347 3347 8347 94 95 BJAAAA MRLAAA OOOOxx
+7879 7891 1 3 9 19 79 879 1879 2879 7879 158 159 BRAAAA NRLAAA VVVVxx
+9360 7892 0 0 0 0 60 360 1360 4360 9360 120 121 AWAAAA ORLAAA AAAAxx
+3369 7893 1 1 9 9 69 369 1369 3369 3369 138 139 PZAAAA PRLAAA HHHHxx
+8536 7894 0 0 6 16 36 536 536 3536 8536 72 73 IQAAAA QRLAAA OOOOxx
+8628 7895 0 0 8 8 28 628 628 3628 8628 56 57 WTAAAA RRLAAA VVVVxx
+1580 7896 0 0 0 0 80 580 1580 1580 1580 160 161 UIAAAA SRLAAA AAAAxx
+705 7897 1 1 5 5 5 705 705 705 705 10 11 DBAAAA TRLAAA HHHHxx
+4650 7898 0 2 0 10 50 650 650 4650 4650 100 101 WWAAAA URLAAA OOOOxx
+9165 7899 1 1 5 5 65 165 1165 4165 9165 130 131 NOAAAA VRLAAA VVVVxx
+4820 7900 0 0 0 0 20 820 820 4820 4820 40 41 KDAAAA WRLAAA AAAAxx
+3538 7901 0 2 8 18 38 538 1538 3538 3538 76 77 CGAAAA XRLAAA HHHHxx
+9947 7902 1 3 7 7 47 947 1947 4947 9947 94 95 PSAAAA YRLAAA OOOOxx
+4954 7903 0 2 4 14 54 954 954 4954 4954 108 109 OIAAAA ZRLAAA VVVVxx
+1104 7904 0 0 4 4 4 104 1104 1104 1104 8 9 MQAAAA ASLAAA AAAAxx
+8455 7905 1 3 5 15 55 455 455 3455 8455 110 111 FNAAAA BSLAAA HHHHxx
+8307 7906 1 3 7 7 7 307 307 3307 8307 14 15 NHAAAA CSLAAA OOOOxx
+9203 7907 1 3 3 3 3 203 1203 4203 9203 6 7 ZPAAAA DSLAAA VVVVxx
+7565 7908 1 1 5 5 65 565 1565 2565 7565 130 131 ZEAAAA ESLAAA AAAAxx
+7745 7909 1 1 5 5 45 745 1745 2745 7745 90 91 XLAAAA FSLAAA HHHHxx
+1787 7910 1 3 7 7 87 787 1787 1787 1787 174 175 TQAAAA GSLAAA OOOOxx
+4861 7911 1 1 1 1 61 861 861 4861 4861 122 123 ZEAAAA HSLAAA VVVVxx
+5183 7912 1 3 3 3 83 183 1183 183 5183 166 167 JRAAAA ISLAAA AAAAxx
+529 7913 1 1 9 9 29 529 529 529 529 58 59 JUAAAA JSLAAA HHHHxx
+2470 7914 0 2 0 10 70 470 470 2470 2470 140 141 ARAAAA KSLAAA OOOOxx
+1267 7915 1 3 7 7 67 267 1267 1267 1267 134 135 TWAAAA LSLAAA VVVVxx
+2059 7916 1 3 9 19 59 59 59 2059 2059 118 119 FBAAAA MSLAAA AAAAxx
+1862 7917 0 2 2 2 62 862 1862 1862 1862 124 125 QTAAAA NSLAAA HHHHxx
+7382 7918 0 2 2 2 82 382 1382 2382 7382 164 165 YXAAAA OSLAAA OOOOxx
+4796 7919 0 0 6 16 96 796 796 4796 4796 192 193 MCAAAA PSLAAA VVVVxx
+2331 7920 1 3 1 11 31 331 331 2331 2331 62 63 RLAAAA QSLAAA AAAAxx
+8870 7921 0 2 0 10 70 870 870 3870 8870 140 141 EDAAAA RSLAAA HHHHxx
+9581 7922 1 1 1 1 81 581 1581 4581 9581 162 163 NEAAAA SSLAAA OOOOxx
+9063 7923 1 3 3 3 63 63 1063 4063 9063 126 127 PKAAAA TSLAAA VVVVxx
+2192 7924 0 0 2 12 92 192 192 2192 2192 184 185 IGAAAA USLAAA AAAAxx
+6466 7925 0 2 6 6 66 466 466 1466 6466 132 133 SOAAAA VSLAAA HHHHxx
+7096 7926 0 0 6 16 96 96 1096 2096 7096 192 193 YMAAAA WSLAAA OOOOxx
+6257 7927 1 1 7 17 57 257 257 1257 6257 114 115 RGAAAA XSLAAA VVVVxx
+7009 7928 1 1 9 9 9 9 1009 2009 7009 18 19 PJAAAA YSLAAA AAAAxx
+8136 7929 0 0 6 16 36 136 136 3136 8136 72 73 YAAAAA ZSLAAA HHHHxx
+1854 7930 0 2 4 14 54 854 1854 1854 1854 108 109 ITAAAA ATLAAA OOOOxx
+3644 7931 0 0 4 4 44 644 1644 3644 3644 88 89 EKAAAA BTLAAA VVVVxx
+4437 7932 1 1 7 17 37 437 437 4437 4437 74 75 ROAAAA CTLAAA AAAAxx
+7209 7933 1 1 9 9 9 209 1209 2209 7209 18 19 HRAAAA DTLAAA HHHHxx
+1516 7934 0 0 6 16 16 516 1516 1516 1516 32 33 IGAAAA ETLAAA OOOOxx
+822 7935 0 2 2 2 22 822 822 822 822 44 45 QFAAAA FTLAAA VVVVxx
+1778 7936 0 2 8 18 78 778 1778 1778 1778 156 157 KQAAAA GTLAAA AAAAxx
+8161 7937 1 1 1 1 61 161 161 3161 8161 122 123 XBAAAA HTLAAA HHHHxx
+6030 7938 0 2 0 10 30 30 30 1030 6030 60 61 YXAAAA ITLAAA OOOOxx
+3515 7939 1 3 5 15 15 515 1515 3515 3515 30 31 FFAAAA JTLAAA VVVVxx
+1702 7940 0 2 2 2 2 702 1702 1702 1702 4 5 MNAAAA KTLAAA AAAAxx
+2671 7941 1 3 1 11 71 671 671 2671 2671 142 143 TYAAAA LTLAAA HHHHxx
+7623 7942 1 3 3 3 23 623 1623 2623 7623 46 47 FHAAAA MTLAAA OOOOxx
+9828 7943 0 0 8 8 28 828 1828 4828 9828 56 57 AOAAAA NTLAAA VVVVxx
+1888 7944 0 0 8 8 88 888 1888 1888 1888 176 177 QUAAAA OTLAAA AAAAxx
+4520 7945 0 0 0 0 20 520 520 4520 4520 40 41 WRAAAA PTLAAA HHHHxx
+3461 7946 1 1 1 1 61 461 1461 3461 3461 122 123 DDAAAA QTLAAA OOOOxx
+1488 7947 0 0 8 8 88 488 1488 1488 1488 176 177 GFAAAA RTLAAA VVVVxx
+7753 7948 1 1 3 13 53 753 1753 2753 7753 106 107 FMAAAA STLAAA AAAAxx
+5525 7949 1 1 5 5 25 525 1525 525 5525 50 51 NEAAAA TTLAAA HHHHxx
+5220 7950 0 0 0 0 20 220 1220 220 5220 40 41 USAAAA UTLAAA OOOOxx
+305 7951 1 1 5 5 5 305 305 305 305 10 11 TLAAAA VTLAAA VVVVxx
+7883 7952 1 3 3 3 83 883 1883 2883 7883 166 167 FRAAAA WTLAAA AAAAxx
+1222 7953 0 2 2 2 22 222 1222 1222 1222 44 45 AVAAAA XTLAAA HHHHxx
+8552 7954 0 0 2 12 52 552 552 3552 8552 104 105 YQAAAA YTLAAA OOOOxx
+6097 7955 1 1 7 17 97 97 97 1097 6097 194 195 NAAAAA ZTLAAA VVVVxx
+2298 7956 0 2 8 18 98 298 298 2298 2298 196 197 KKAAAA AULAAA AAAAxx
+956 7957 0 0 6 16 56 956 956 956 956 112 113 UKAAAA BULAAA HHHHxx
+9351 7958 1 3 1 11 51 351 1351 4351 9351 102 103 RVAAAA CULAAA OOOOxx
+6669 7959 1 1 9 9 69 669 669 1669 6669 138 139 NWAAAA DULAAA VVVVxx
+9383 7960 1 3 3 3 83 383 1383 4383 9383 166 167 XWAAAA EULAAA AAAAxx
+1607 7961 1 3 7 7 7 607 1607 1607 1607 14 15 VJAAAA FULAAA HHHHxx
+812 7962 0 0 2 12 12 812 812 812 812 24 25 GFAAAA GULAAA OOOOxx
+2109 7963 1 1 9 9 9 109 109 2109 2109 18 19 DDAAAA HULAAA VVVVxx
+207 7964 1 3 7 7 7 207 207 207 207 14 15 ZHAAAA IULAAA AAAAxx
+7124 7965 0 0 4 4 24 124 1124 2124 7124 48 49 AOAAAA JULAAA HHHHxx
+9333 7966 1 1 3 13 33 333 1333 4333 9333 66 67 ZUAAAA KULAAA OOOOxx
+3262 7967 0 2 2 2 62 262 1262 3262 3262 124 125 MVAAAA LULAAA VVVVxx
+1070 7968 0 2 0 10 70 70 1070 1070 1070 140 141 EPAAAA MULAAA AAAAxx
+7579 7969 1 3 9 19 79 579 1579 2579 7579 158 159 NFAAAA NULAAA HHHHxx
+9283 7970 1 3 3 3 83 283 1283 4283 9283 166 167 BTAAAA OULAAA OOOOxx
+4917 7971 1 1 7 17 17 917 917 4917 4917 34 35 DHAAAA PULAAA VVVVxx
+1328 7972 0 0 8 8 28 328 1328 1328 1328 56 57 CZAAAA QULAAA AAAAxx
+3042 7973 0 2 2 2 42 42 1042 3042 3042 84 85 ANAAAA RULAAA HHHHxx
+8352 7974 0 0 2 12 52 352 352 3352 8352 104 105 GJAAAA SULAAA OOOOxx
+2710 7975 0 2 0 10 10 710 710 2710 2710 20 21 GAAAAA TULAAA VVVVxx
+3330 7976 0 2 0 10 30 330 1330 3330 3330 60 61 CYAAAA UULAAA AAAAxx
+2822 7977 0 2 2 2 22 822 822 2822 2822 44 45 OEAAAA VULAAA HHHHxx
+5627 7978 1 3 7 7 27 627 1627 627 5627 54 55 LIAAAA WULAAA OOOOxx
+7848 7979 0 0 8 8 48 848 1848 2848 7848 96 97 WPAAAA XULAAA VVVVxx
+7384 7980 0 0 4 4 84 384 1384 2384 7384 168 169 AYAAAA YULAAA AAAAxx
+727 7981 1 3 7 7 27 727 727 727 727 54 55 ZBAAAA ZULAAA HHHHxx
+9926 7982 0 2 6 6 26 926 1926 4926 9926 52 53 URAAAA AVLAAA OOOOxx
+2647 7983 1 3 7 7 47 647 647 2647 2647 94 95 VXAAAA BVLAAA VVVVxx
+6416 7984 0 0 6 16 16 416 416 1416 6416 32 33 UMAAAA CVLAAA AAAAxx
+8751 7985 1 3 1 11 51 751 751 3751 8751 102 103 PYAAAA DVLAAA HHHHxx
+6515 7986 1 3 5 15 15 515 515 1515 6515 30 31 PQAAAA EVLAAA OOOOxx
+2472 7987 0 0 2 12 72 472 472 2472 2472 144 145 CRAAAA FVLAAA VVVVxx
+7205 7988 1 1 5 5 5 205 1205 2205 7205 10 11 DRAAAA GVLAAA AAAAxx
+9654 7989 0 2 4 14 54 654 1654 4654 9654 108 109 IHAAAA HVLAAA HHHHxx
+5646 7990 0 2 6 6 46 646 1646 646 5646 92 93 EJAAAA IVLAAA OOOOxx
+4217 7991 1 1 7 17 17 217 217 4217 4217 34 35 FGAAAA JVLAAA VVVVxx
+4484 7992 0 0 4 4 84 484 484 4484 4484 168 169 MQAAAA KVLAAA AAAAxx
+6654 7993 0 2 4 14 54 654 654 1654 6654 108 109 YVAAAA LVLAAA HHHHxx
+4876 7994 0 0 6 16 76 876 876 4876 4876 152 153 OFAAAA MVLAAA OOOOxx
+9690 7995 0 2 0 10 90 690 1690 4690 9690 180 181 SIAAAA NVLAAA VVVVxx
+2453 7996 1 1 3 13 53 453 453 2453 2453 106 107 JQAAAA OVLAAA AAAAxx
+829 7997 1 1 9 9 29 829 829 829 829 58 59 XFAAAA PVLAAA HHHHxx
+2547 7998 1 3 7 7 47 547 547 2547 2547 94 95 ZTAAAA QVLAAA OOOOxx
+9726 7999 0 2 6 6 26 726 1726 4726 9726 52 53 CKAAAA RVLAAA VVVVxx
+9267 8000 1 3 7 7 67 267 1267 4267 9267 134 135 LSAAAA SVLAAA AAAAxx
+7448 8001 0 0 8 8 48 448 1448 2448 7448 96 97 MAAAAA TVLAAA HHHHxx
+610 8002 0 2 0 10 10 610 610 610 610 20 21 MXAAAA UVLAAA OOOOxx
+2791 8003 1 3 1 11 91 791 791 2791 2791 182 183 JDAAAA VVLAAA VVVVxx
+3651 8004 1 3 1 11 51 651 1651 3651 3651 102 103 LKAAAA WVLAAA AAAAxx
+5206 8005 0 2 6 6 6 206 1206 206 5206 12 13 GSAAAA XVLAAA HHHHxx
+8774 8006 0 2 4 14 74 774 774 3774 8774 148 149 MZAAAA YVLAAA OOOOxx
+4753 8007 1 1 3 13 53 753 753 4753 4753 106 107 VAAAAA ZVLAAA VVVVxx
+4755 8008 1 3 5 15 55 755 755 4755 4755 110 111 XAAAAA AWLAAA AAAAxx
+686 8009 0 2 6 6 86 686 686 686 686 172 173 KAAAAA BWLAAA HHHHxx
+8281 8010 1 1 1 1 81 281 281 3281 8281 162 163 NGAAAA CWLAAA OOOOxx
+2058 8011 0 2 8 18 58 58 58 2058 2058 116 117 EBAAAA DWLAAA VVVVxx
+8900 8012 0 0 0 0 0 900 900 3900 8900 0 1 IEAAAA EWLAAA AAAAxx
+8588 8013 0 0 8 8 88 588 588 3588 8588 176 177 ISAAAA FWLAAA HHHHxx
+2904 8014 0 0 4 4 4 904 904 2904 2904 8 9 SHAAAA GWLAAA OOOOxx
+8917 8015 1 1 7 17 17 917 917 3917 8917 34 35 ZEAAAA HWLAAA VVVVxx
+9026 8016 0 2 6 6 26 26 1026 4026 9026 52 53 EJAAAA IWLAAA AAAAxx
+2416 8017 0 0 6 16 16 416 416 2416 2416 32 33 YOAAAA JWLAAA HHHHxx
+1053 8018 1 1 3 13 53 53 1053 1053 1053 106 107 NOAAAA KWLAAA OOOOxx
+7141 8019 1 1 1 1 41 141 1141 2141 7141 82 83 ROAAAA LWLAAA VVVVxx
+9771 8020 1 3 1 11 71 771 1771 4771 9771 142 143 VLAAAA MWLAAA AAAAxx
+2774 8021 0 2 4 14 74 774 774 2774 2774 148 149 SCAAAA NWLAAA HHHHxx
+3213 8022 1 1 3 13 13 213 1213 3213 3213 26 27 PTAAAA OWLAAA OOOOxx
+5694 8023 0 2 4 14 94 694 1694 694 5694 188 189 ALAAAA PWLAAA VVVVxx
+6631 8024 1 3 1 11 31 631 631 1631 6631 62 63 BVAAAA QWLAAA AAAAxx
+6638 8025 0 2 8 18 38 638 638 1638 6638 76 77 IVAAAA RWLAAA HHHHxx
+7407 8026 1 3 7 7 7 407 1407 2407 7407 14 15 XYAAAA SWLAAA OOOOxx
+8972 8027 0 0 2 12 72 972 972 3972 8972 144 145 CHAAAA TWLAAA VVVVxx
+2202 8028 0 2 2 2 2 202 202 2202 2202 4 5 SGAAAA UWLAAA AAAAxx
+6135 8029 1 3 5 15 35 135 135 1135 6135 70 71 ZBAAAA VWLAAA HHHHxx
+5043 8030 1 3 3 3 43 43 1043 43 5043 86 87 ZLAAAA WWLAAA OOOOxx
+5163 8031 1 3 3 3 63 163 1163 163 5163 126 127 PQAAAA XWLAAA VVVVxx
+1191 8032 1 3 1 11 91 191 1191 1191 1191 182 183 VTAAAA YWLAAA AAAAxx
+6576 8033 0 0 6 16 76 576 576 1576 6576 152 153 YSAAAA ZWLAAA HHHHxx
+3455 8034 1 3 5 15 55 455 1455 3455 3455 110 111 XCAAAA AXLAAA OOOOxx
+3688 8035 0 0 8 8 88 688 1688 3688 3688 176 177 WLAAAA BXLAAA VVVVxx
+4982 8036 0 2 2 2 82 982 982 4982 4982 164 165 QJAAAA CXLAAA AAAAxx
+4180 8037 0 0 0 0 80 180 180 4180 4180 160 161 UEAAAA DXLAAA HHHHxx
+4708 8038 0 0 8 8 8 708 708 4708 4708 16 17 CZAAAA EXLAAA OOOOxx
+1241 8039 1 1 1 1 41 241 1241 1241 1241 82 83 TVAAAA FXLAAA VVVVxx
+4921 8040 1 1 1 1 21 921 921 4921 4921 42 43 HHAAAA GXLAAA AAAAxx
+3197 8041 1 1 7 17 97 197 1197 3197 3197 194 195 ZSAAAA HXLAAA HHHHxx
+8225 8042 1 1 5 5 25 225 225 3225 8225 50 51 JEAAAA IXLAAA OOOOxx
+5913 8043 1 1 3 13 13 913 1913 913 5913 26 27 LTAAAA JXLAAA VVVVxx
+6387 8044 1 3 7 7 87 387 387 1387 6387 174 175 RLAAAA KXLAAA AAAAxx
+2706 8045 0 2 6 6 6 706 706 2706 2706 12 13 CAAAAA LXLAAA HHHHxx
+1461 8046 1 1 1 1 61 461 1461 1461 1461 122 123 FEAAAA MXLAAA OOOOxx
+7646 8047 0 2 6 6 46 646 1646 2646 7646 92 93 CIAAAA NXLAAA VVVVxx
+8066 8048 0 2 6 6 66 66 66 3066 8066 132 133 GYAAAA OXLAAA AAAAxx
+4171 8049 1 3 1 11 71 171 171 4171 4171 142 143 LEAAAA PXLAAA HHHHxx
+8008 8050 0 0 8 8 8 8 8 3008 8008 16 17 AWAAAA QXLAAA OOOOxx
+2088 8051 0 0 8 8 88 88 88 2088 2088 176 177 ICAAAA RXLAAA VVVVxx
+7907 8052 1 3 7 7 7 907 1907 2907 7907 14 15 DSAAAA SXLAAA AAAAxx
+2429 8053 1 1 9 9 29 429 429 2429 2429 58 59 LPAAAA TXLAAA HHHHxx
+9629 8054 1 1 9 9 29 629 1629 4629 9629 58 59 JGAAAA UXLAAA OOOOxx
+1470 8055 0 2 0 10 70 470 1470 1470 1470 140 141 OEAAAA VXLAAA VVVVxx
+4346 8056 0 2 6 6 46 346 346 4346 4346 92 93 ELAAAA WXLAAA AAAAxx
+7219 8057 1 3 9 19 19 219 1219 2219 7219 38 39 RRAAAA XXLAAA HHHHxx
+1185 8058 1 1 5 5 85 185 1185 1185 1185 170 171 PTAAAA YXLAAA OOOOxx
+8776 8059 0 0 6 16 76 776 776 3776 8776 152 153 OZAAAA ZXLAAA VVVVxx
+684 8060 0 0 4 4 84 684 684 684 684 168 169 IAAAAA AYLAAA AAAAxx
+2343 8061 1 3 3 3 43 343 343 2343 2343 86 87 DMAAAA BYLAAA HHHHxx
+4470 8062 0 2 0 10 70 470 470 4470 4470 140 141 YPAAAA CYLAAA OOOOxx
+5116 8063 0 0 6 16 16 116 1116 116 5116 32 33 UOAAAA DYLAAA VVVVxx
+1746 8064 0 2 6 6 46 746 1746 1746 1746 92 93 EPAAAA EYLAAA AAAAxx
+3216 8065 0 0 6 16 16 216 1216 3216 3216 32 33 STAAAA FYLAAA HHHHxx
+4594 8066 0 2 4 14 94 594 594 4594 4594 188 189 SUAAAA GYLAAA OOOOxx
+3013 8067 1 1 3 13 13 13 1013 3013 3013 26 27 XLAAAA HYLAAA VVVVxx
+2307 8068 1 3 7 7 7 307 307 2307 2307 14 15 TKAAAA IYLAAA AAAAxx
+7663 8069 1 3 3 3 63 663 1663 2663 7663 126 127 TIAAAA JYLAAA HHHHxx
+8504 8070 0 0 4 4 4 504 504 3504 8504 8 9 CPAAAA KYLAAA OOOOxx
+3683 8071 1 3 3 3 83 683 1683 3683 3683 166 167 RLAAAA LYLAAA VVVVxx
+144 8072 0 0 4 4 44 144 144 144 144 88 89 OFAAAA MYLAAA AAAAxx
+203 8073 1 3 3 3 3 203 203 203 203 6 7 VHAAAA NYLAAA HHHHxx
+5255 8074 1 3 5 15 55 255 1255 255 5255 110 111 DUAAAA OYLAAA OOOOxx
+4150 8075 0 2 0 10 50 150 150 4150 4150 100 101 QDAAAA PYLAAA VVVVxx
+5701 8076 1 1 1 1 1 701 1701 701 5701 2 3 HLAAAA QYLAAA AAAAxx
+7400 8077 0 0 0 0 0 400 1400 2400 7400 0 1 QYAAAA RYLAAA HHHHxx
+8203 8078 1 3 3 3 3 203 203 3203 8203 6 7 NDAAAA SYLAAA OOOOxx
+637 8079 1 1 7 17 37 637 637 637 637 74 75 NYAAAA TYLAAA VVVVxx
+2898 8080 0 2 8 18 98 898 898 2898 2898 196 197 MHAAAA UYLAAA AAAAxx
+1110 8081 0 2 0 10 10 110 1110 1110 1110 20 21 SQAAAA VYLAAA HHHHxx
+6255 8082 1 3 5 15 55 255 255 1255 6255 110 111 PGAAAA WYLAAA OOOOxx
+1071 8083 1 3 1 11 71 71 1071 1071 1071 142 143 FPAAAA XYLAAA VVVVxx
+541 8084 1 1 1 1 41 541 541 541 541 82 83 VUAAAA YYLAAA AAAAxx
+8077 8085 1 1 7 17 77 77 77 3077 8077 154 155 RYAAAA ZYLAAA HHHHxx
+6809 8086 1 1 9 9 9 809 809 1809 6809 18 19 XBAAAA AZLAAA OOOOxx
+4749 8087 1 1 9 9 49 749 749 4749 4749 98 99 RAAAAA BZLAAA VVVVxx
+2886 8088 0 2 6 6 86 886 886 2886 2886 172 173 AHAAAA CZLAAA AAAAxx
+5510 8089 0 2 0 10 10 510 1510 510 5510 20 21 YDAAAA DZLAAA HHHHxx
+713 8090 1 1 3 13 13 713 713 713 713 26 27 LBAAAA EZLAAA OOOOxx
+8388 8091 0 0 8 8 88 388 388 3388 8388 176 177 QKAAAA FZLAAA VVVVxx
+9524 8092 0 0 4 4 24 524 1524 4524 9524 48 49 ICAAAA GZLAAA AAAAxx
+9949 8093 1 1 9 9 49 949 1949 4949 9949 98 99 RSAAAA HZLAAA HHHHxx
+885 8094 1 1 5 5 85 885 885 885 885 170 171 BIAAAA IZLAAA OOOOxx
+8699 8095 1 3 9 19 99 699 699 3699 8699 198 199 PWAAAA JZLAAA VVVVxx
+2232 8096 0 0 2 12 32 232 232 2232 2232 64 65 WHAAAA KZLAAA AAAAxx
+5142 8097 0 2 2 2 42 142 1142 142 5142 84 85 UPAAAA LZLAAA HHHHxx
+8891 8098 1 3 1 11 91 891 891 3891 8891 182 183 ZDAAAA MZLAAA OOOOxx
+1881 8099 1 1 1 1 81 881 1881 1881 1881 162 163 JUAAAA NZLAAA VVVVxx
+3751 8100 1 3 1 11 51 751 1751 3751 3751 102 103 HOAAAA OZLAAA AAAAxx
+1896 8101 0 0 6 16 96 896 1896 1896 1896 192 193 YUAAAA PZLAAA HHHHxx
+8258 8102 0 2 8 18 58 258 258 3258 8258 116 117 QFAAAA QZLAAA OOOOxx
+3820 8103 0 0 0 0 20 820 1820 3820 3820 40 41 YQAAAA RZLAAA VVVVxx
+6617 8104 1 1 7 17 17 617 617 1617 6617 34 35 NUAAAA SZLAAA AAAAxx
+5100 8105 0 0 0 0 0 100 1100 100 5100 0 1 EOAAAA TZLAAA HHHHxx
+4277 8106 1 1 7 17 77 277 277 4277 4277 154 155 NIAAAA UZLAAA OOOOxx
+2498 8107 0 2 8 18 98 498 498 2498 2498 196 197 CSAAAA VZLAAA VVVVxx
+4343 8108 1 3 3 3 43 343 343 4343 4343 86 87 BLAAAA WZLAAA AAAAxx
+8319 8109 1 3 9 19 19 319 319 3319 8319 38 39 ZHAAAA XZLAAA HHHHxx
+4803 8110 1 3 3 3 3 803 803 4803 4803 6 7 TCAAAA YZLAAA OOOOxx
+3100 8111 0 0 0 0 0 100 1100 3100 3100 0 1 GPAAAA ZZLAAA VVVVxx
+428 8112 0 0 8 8 28 428 428 428 428 56 57 MQAAAA AAMAAA AAAAxx
+2811 8113 1 3 1 11 11 811 811 2811 2811 22 23 DEAAAA BAMAAA HHHHxx
+2989 8114 1 1 9 9 89 989 989 2989 2989 178 179 ZKAAAA CAMAAA OOOOxx
+1100 8115 0 0 0 0 0 100 1100 1100 1100 0 1 IQAAAA DAMAAA VVVVxx
+6586 8116 0 2 6 6 86 586 586 1586 6586 172 173 ITAAAA EAMAAA AAAAxx
+3124 8117 0 0 4 4 24 124 1124 3124 3124 48 49 EQAAAA FAMAAA HHHHxx
+1635 8118 1 3 5 15 35 635 1635 1635 1635 70 71 XKAAAA GAMAAA OOOOxx
+3888 8119 0 0 8 8 88 888 1888 3888 3888 176 177 OTAAAA HAMAAA VVVVxx
+8369 8120 1 1 9 9 69 369 369 3369 8369 138 139 XJAAAA IAMAAA AAAAxx
+3148 8121 0 0 8 8 48 148 1148 3148 3148 96 97 CRAAAA JAMAAA HHHHxx
+2842 8122 0 2 2 2 42 842 842 2842 2842 84 85 IFAAAA KAMAAA OOOOxx
+4965 8123 1 1 5 5 65 965 965 4965 4965 130 131 ZIAAAA LAMAAA VVVVxx
+3742 8124 0 2 2 2 42 742 1742 3742 3742 84 85 YNAAAA MAMAAA AAAAxx
+5196 8125 0 0 6 16 96 196 1196 196 5196 192 193 WRAAAA NAMAAA HHHHxx
+9105 8126 1 1 5 5 5 105 1105 4105 9105 10 11 FMAAAA OAMAAA OOOOxx
+6806 8127 0 2 6 6 6 806 806 1806 6806 12 13 UBAAAA PAMAAA VVVVxx
+5849 8128 1 1 9 9 49 849 1849 849 5849 98 99 ZQAAAA QAMAAA AAAAxx
+6504 8129 0 0 4 4 4 504 504 1504 6504 8 9 EQAAAA RAMAAA HHHHxx
+9841 8130 1 1 1 1 41 841 1841 4841 9841 82 83 NOAAAA SAMAAA OOOOxx
+457 8131 1 1 7 17 57 457 457 457 457 114 115 PRAAAA TAMAAA VVVVxx
+8856 8132 0 0 6 16 56 856 856 3856 8856 112 113 QCAAAA UAMAAA AAAAxx
+8043 8133 1 3 3 3 43 43 43 3043 8043 86 87 JXAAAA VAMAAA HHHHxx
+5933 8134 1 1 3 13 33 933 1933 933 5933 66 67 FUAAAA WAMAAA OOOOxx
+5725 8135 1 1 5 5 25 725 1725 725 5725 50 51 FMAAAA XAMAAA VVVVxx
+8607 8136 1 3 7 7 7 607 607 3607 8607 14 15 BTAAAA YAMAAA AAAAxx
+9280 8137 0 0 0 0 80 280 1280 4280 9280 160 161 YSAAAA ZAMAAA HHHHxx
+6017 8138 1 1 7 17 17 17 17 1017 6017 34 35 LXAAAA ABMAAA OOOOxx
+4946 8139 0 2 6 6 46 946 946 4946 4946 92 93 GIAAAA BBMAAA VVVVxx
+7373 8140 1 1 3 13 73 373 1373 2373 7373 146 147 PXAAAA CBMAAA AAAAxx
+8096 8141 0 0 6 16 96 96 96 3096 8096 192 193 KZAAAA DBMAAA HHHHxx
+3178 8142 0 2 8 18 78 178 1178 3178 3178 156 157 GSAAAA EBMAAA OOOOxx
+1849 8143 1 1 9 9 49 849 1849 1849 1849 98 99 DTAAAA FBMAAA VVVVxx
+8813 8144 1 1 3 13 13 813 813 3813 8813 26 27 ZAAAAA GBMAAA AAAAxx
+460 8145 0 0 0 0 60 460 460 460 460 120 121 SRAAAA HBMAAA HHHHxx
+7756 8146 0 0 6 16 56 756 1756 2756 7756 112 113 IMAAAA IBMAAA OOOOxx
+4425 8147 1 1 5 5 25 425 425 4425 4425 50 51 FOAAAA JBMAAA VVVVxx
+1602 8148 0 2 2 2 2 602 1602 1602 1602 4 5 QJAAAA KBMAAA AAAAxx
+5981 8149 1 1 1 1 81 981 1981 981 5981 162 163 BWAAAA LBMAAA HHHHxx
+8139 8150 1 3 9 19 39 139 139 3139 8139 78 79 BBAAAA MBMAAA OOOOxx
+754 8151 0 2 4 14 54 754 754 754 754 108 109 ADAAAA NBMAAA VVVVxx
+26 8152 0 2 6 6 26 26 26 26 26 52 53 ABAAAA OBMAAA AAAAxx
+106 8153 0 2 6 6 6 106 106 106 106 12 13 CEAAAA PBMAAA HHHHxx
+7465 8154 1 1 5 5 65 465 1465 2465 7465 130 131 DBAAAA QBMAAA OOOOxx
+1048 8155 0 0 8 8 48 48 1048 1048 1048 96 97 IOAAAA RBMAAA VVVVxx
+2303 8156 1 3 3 3 3 303 303 2303 2303 6 7 PKAAAA SBMAAA AAAAxx
+5794 8157 0 2 4 14 94 794 1794 794 5794 188 189 WOAAAA TBMAAA HHHHxx
+3321 8158 1 1 1 1 21 321 1321 3321 3321 42 43 TXAAAA UBMAAA OOOOxx
+6122 8159 0 2 2 2 22 122 122 1122 6122 44 45 MBAAAA VBMAAA VVVVxx
+6474 8160 0 2 4 14 74 474 474 1474 6474 148 149 APAAAA WBMAAA AAAAxx
+827 8161 1 3 7 7 27 827 827 827 827 54 55 VFAAAA XBMAAA HHHHxx
+6616 8162 0 0 6 16 16 616 616 1616 6616 32 33 MUAAAA YBMAAA OOOOxx
+2131 8163 1 3 1 11 31 131 131 2131 2131 62 63 ZDAAAA ZBMAAA VVVVxx
+5483 8164 1 3 3 3 83 483 1483 483 5483 166 167 XCAAAA ACMAAA AAAAxx
+606 8165 0 2 6 6 6 606 606 606 606 12 13 IXAAAA BCMAAA HHHHxx
+922 8166 0 2 2 2 22 922 922 922 922 44 45 MJAAAA CCMAAA OOOOxx
+8475 8167 1 3 5 15 75 475 475 3475 8475 150 151 ZNAAAA DCMAAA VVVVxx
+7645 8168 1 1 5 5 45 645 1645 2645 7645 90 91 BIAAAA ECMAAA AAAAxx
+5097 8169 1 1 7 17 97 97 1097 97 5097 194 195 BOAAAA FCMAAA HHHHxx
+5377 8170 1 1 7 17 77 377 1377 377 5377 154 155 VYAAAA GCMAAA OOOOxx
+6116 8171 0 0 6 16 16 116 116 1116 6116 32 33 GBAAAA HCMAAA VVVVxx
+8674 8172 0 2 4 14 74 674 674 3674 8674 148 149 QVAAAA ICMAAA AAAAxx
+8063 8173 1 3 3 3 63 63 63 3063 8063 126 127 DYAAAA JCMAAA HHHHxx
+5271 8174 1 3 1 11 71 271 1271 271 5271 142 143 TUAAAA KCMAAA OOOOxx
+1619 8175 1 3 9 19 19 619 1619 1619 1619 38 39 HKAAAA LCMAAA VVVVxx
+6419 8176 1 3 9 19 19 419 419 1419 6419 38 39 XMAAAA MCMAAA AAAAxx
+7651 8177 1 3 1 11 51 651 1651 2651 7651 102 103 HIAAAA NCMAAA HHHHxx
+2897 8178 1 1 7 17 97 897 897 2897 2897 194 195 LHAAAA OCMAAA OOOOxx
+8148 8179 0 0 8 8 48 148 148 3148 8148 96 97 KBAAAA PCMAAA VVVVxx
+7461 8180 1 1 1 1 61 461 1461 2461 7461 122 123 ZAAAAA QCMAAA AAAAxx
+9186 8181 0 2 6 6 86 186 1186 4186 9186 172 173 IPAAAA RCMAAA HHHHxx
+7127 8182 1 3 7 7 27 127 1127 2127 7127 54 55 DOAAAA SCMAAA OOOOxx
+8233 8183 1 1 3 13 33 233 233 3233 8233 66 67 REAAAA TCMAAA VVVVxx
+9651 8184 1 3 1 11 51 651 1651 4651 9651 102 103 FHAAAA UCMAAA AAAAxx
+6746 8185 0 2 6 6 46 746 746 1746 6746 92 93 MZAAAA VCMAAA HHHHxx
+7835 8186 1 3 5 15 35 835 1835 2835 7835 70 71 JPAAAA WCMAAA OOOOxx
+8815 8187 1 3 5 15 15 815 815 3815 8815 30 31 BBAAAA XCMAAA VVVVxx
+6398 8188 0 2 8 18 98 398 398 1398 6398 196 197 CMAAAA YCMAAA AAAAxx
+5344 8189 0 0 4 4 44 344 1344 344 5344 88 89 OXAAAA ZCMAAA HHHHxx
+8209 8190 1 1 9 9 9 209 209 3209 8209 18 19 TDAAAA ADMAAA OOOOxx
+8444 8191 0 0 4 4 44 444 444 3444 8444 88 89 UMAAAA BDMAAA VVVVxx
+5669 8192 1 1 9 9 69 669 1669 669 5669 138 139 BKAAAA CDMAAA AAAAxx
+2455 8193 1 3 5 15 55 455 455 2455 2455 110 111 LQAAAA DDMAAA HHHHxx
+6767 8194 1 3 7 7 67 767 767 1767 6767 134 135 HAAAAA EDMAAA OOOOxx
+135 8195 1 3 5 15 35 135 135 135 135 70 71 FFAAAA FDMAAA VVVVxx
+3503 8196 1 3 3 3 3 503 1503 3503 3503 6 7 TEAAAA GDMAAA AAAAxx
+6102 8197 0 2 2 2 2 102 102 1102 6102 4 5 SAAAAA HDMAAA HHHHxx
+7136 8198 0 0 6 16 36 136 1136 2136 7136 72 73 MOAAAA IDMAAA OOOOxx
+4933 8199 1 1 3 13 33 933 933 4933 4933 66 67 THAAAA JDMAAA VVVVxx
+8804 8200 0 0 4 4 4 804 804 3804 8804 8 9 QAAAAA KDMAAA AAAAxx
+3760 8201 0 0 0 0 60 760 1760 3760 3760 120 121 QOAAAA LDMAAA HHHHxx
+8603 8202 1 3 3 3 3 603 603 3603 8603 6 7 XSAAAA MDMAAA OOOOxx
+7411 8203 1 3 1 11 11 411 1411 2411 7411 22 23 BZAAAA NDMAAA VVVVxx
+834 8204 0 2 4 14 34 834 834 834 834 68 69 CGAAAA ODMAAA AAAAxx
+7385 8205 1 1 5 5 85 385 1385 2385 7385 170 171 BYAAAA PDMAAA HHHHxx
+3696 8206 0 0 6 16 96 696 1696 3696 3696 192 193 EMAAAA QDMAAA OOOOxx
+8720 8207 0 0 0 0 20 720 720 3720 8720 40 41 KXAAAA RDMAAA VVVVxx
+4539 8208 1 3 9 19 39 539 539 4539 4539 78 79 PSAAAA SDMAAA AAAAxx
+9837 8209 1 1 7 17 37 837 1837 4837 9837 74 75 JOAAAA TDMAAA HHHHxx
+8595 8210 1 3 5 15 95 595 595 3595 8595 190 191 PSAAAA UDMAAA OOOOxx
+3673 8211 1 1 3 13 73 673 1673 3673 3673 146 147 HLAAAA VDMAAA VVVVxx
+475 8212 1 3 5 15 75 475 475 475 475 150 151 HSAAAA WDMAAA AAAAxx
+2256 8213 0 0 6 16 56 256 256 2256 2256 112 113 UIAAAA XDMAAA HHHHxx
+6349 8214 1 1 9 9 49 349 349 1349 6349 98 99 FKAAAA YDMAAA OOOOxx
+9968 8215 0 0 8 8 68 968 1968 4968 9968 136 137 KTAAAA ZDMAAA VVVVxx
+7261 8216 1 1 1 1 61 261 1261 2261 7261 122 123 HTAAAA AEMAAA AAAAxx
+5799 8217 1 3 9 19 99 799 1799 799 5799 198 199 BPAAAA BEMAAA HHHHxx
+8159 8218 1 3 9 19 59 159 159 3159 8159 118 119 VBAAAA CEMAAA OOOOxx
+92 8219 0 0 2 12 92 92 92 92 92 184 185 ODAAAA DEMAAA VVVVxx
+5927 8220 1 3 7 7 27 927 1927 927 5927 54 55 ZTAAAA EEMAAA AAAAxx
+7925 8221 1 1 5 5 25 925 1925 2925 7925 50 51 VSAAAA FEMAAA HHHHxx
+5836 8222 0 0 6 16 36 836 1836 836 5836 72 73 MQAAAA GEMAAA OOOOxx
+7935 8223 1 3 5 15 35 935 1935 2935 7935 70 71 FTAAAA HEMAAA VVVVxx
+5505 8224 1 1 5 5 5 505 1505 505 5505 10 11 TDAAAA IEMAAA AAAAxx
+5882 8225 0 2 2 2 82 882 1882 882 5882 164 165 GSAAAA JEMAAA HHHHxx
+4411 8226 1 3 1 11 11 411 411 4411 4411 22 23 RNAAAA KEMAAA OOOOxx
+64 8227 0 0 4 4 64 64 64 64 64 128 129 MCAAAA LEMAAA VVVVxx
+2851 8228 1 3 1 11 51 851 851 2851 2851 102 103 RFAAAA MEMAAA AAAAxx
+1665 8229 1 1 5 5 65 665 1665 1665 1665 130 131 BMAAAA NEMAAA HHHHxx
+2895 8230 1 3 5 15 95 895 895 2895 2895 190 191 JHAAAA OEMAAA OOOOxx
+2210 8231 0 2 0 10 10 210 210 2210 2210 20 21 AHAAAA PEMAAA VVVVxx
+9873 8232 1 1 3 13 73 873 1873 4873 9873 146 147 TPAAAA QEMAAA AAAAxx
+5402 8233 0 2 2 2 2 402 1402 402 5402 4 5 UZAAAA REMAAA HHHHxx
+285 8234 1 1 5 5 85 285 285 285 285 170 171 ZKAAAA SEMAAA OOOOxx
+8545 8235 1 1 5 5 45 545 545 3545 8545 90 91 RQAAAA TEMAAA VVVVxx
+5328 8236 0 0 8 8 28 328 1328 328 5328 56 57 YWAAAA UEMAAA AAAAxx
+733 8237 1 1 3 13 33 733 733 733 733 66 67 FCAAAA VEMAAA HHHHxx
+7726 8238 0 2 6 6 26 726 1726 2726 7726 52 53 ELAAAA WEMAAA OOOOxx
+5418 8239 0 2 8 18 18 418 1418 418 5418 36 37 KAAAAA XEMAAA VVVVxx
+7761 8240 1 1 1 1 61 761 1761 2761 7761 122 123 NMAAAA YEMAAA AAAAxx
+9263 8241 1 3 3 3 63 263 1263 4263 9263 126 127 HSAAAA ZEMAAA HHHHxx
+5579 8242 1 3 9 19 79 579 1579 579 5579 158 159 PGAAAA AFMAAA OOOOxx
+5434 8243 0 2 4 14 34 434 1434 434 5434 68 69 ABAAAA BFMAAA VVVVxx
+5230 8244 0 2 0 10 30 230 1230 230 5230 60 61 ETAAAA CFMAAA AAAAxx
+9981 8245 1 1 1 1 81 981 1981 4981 9981 162 163 XTAAAA DFMAAA HHHHxx
+5830 8246 0 2 0 10 30 830 1830 830 5830 60 61 GQAAAA EFMAAA OOOOxx
+128 8247 0 0 8 8 28 128 128 128 128 56 57 YEAAAA FFMAAA VVVVxx
+2734 8248 0 2 4 14 34 734 734 2734 2734 68 69 EBAAAA GFMAAA AAAAxx
+4537 8249 1 1 7 17 37 537 537 4537 4537 74 75 NSAAAA HFMAAA HHHHxx
+3899 8250 1 3 9 19 99 899 1899 3899 3899 198 199 ZTAAAA IFMAAA OOOOxx
+1000 8251 0 0 0 0 0 0 1000 1000 1000 0 1 MMAAAA JFMAAA VVVVxx
+9896 8252 0 0 6 16 96 896 1896 4896 9896 192 193 QQAAAA KFMAAA AAAAxx
+3640 8253 0 0 0 0 40 640 1640 3640 3640 80 81 AKAAAA LFMAAA HHHHxx
+2568 8254 0 0 8 8 68 568 568 2568 2568 136 137 UUAAAA MFMAAA OOOOxx
+2026 8255 0 2 6 6 26 26 26 2026 2026 52 53 YZAAAA NFMAAA VVVVxx
+3955 8256 1 3 5 15 55 955 1955 3955 3955 110 111 DWAAAA OFMAAA AAAAxx
+7152 8257 0 0 2 12 52 152 1152 2152 7152 104 105 CPAAAA PFMAAA HHHHxx
+2402 8258 0 2 2 2 2 402 402 2402 2402 4 5 KOAAAA QFMAAA OOOOxx
+9522 8259 0 2 2 2 22 522 1522 4522 9522 44 45 GCAAAA RFMAAA VVVVxx
+4011 8260 1 3 1 11 11 11 11 4011 4011 22 23 HYAAAA SFMAAA AAAAxx
+3297 8261 1 1 7 17 97 297 1297 3297 3297 194 195 VWAAAA TFMAAA HHHHxx
+4915 8262 1 3 5 15 15 915 915 4915 4915 30 31 BHAAAA UFMAAA OOOOxx
+5397 8263 1 1 7 17 97 397 1397 397 5397 194 195 PZAAAA VFMAAA VVVVxx
+5454 8264 0 2 4 14 54 454 1454 454 5454 108 109 UBAAAA WFMAAA AAAAxx
+4568 8265 0 0 8 8 68 568 568 4568 4568 136 137 STAAAA XFMAAA HHHHxx
+5875 8266 1 3 5 15 75 875 1875 875 5875 150 151 ZRAAAA YFMAAA OOOOxx
+3642 8267 0 2 2 2 42 642 1642 3642 3642 84 85 CKAAAA ZFMAAA VVVVxx
+8506 8268 0 2 6 6 6 506 506 3506 8506 12 13 EPAAAA AGMAAA AAAAxx
+9621 8269 1 1 1 1 21 621 1621 4621 9621 42 43 BGAAAA BGMAAA HHHHxx
+7739 8270 1 3 9 19 39 739 1739 2739 7739 78 79 RLAAAA CGMAAA OOOOxx
+3987 8271 1 3 7 7 87 987 1987 3987 3987 174 175 JXAAAA DGMAAA VVVVxx
+2090 8272 0 2 0 10 90 90 90 2090 2090 180 181 KCAAAA EGMAAA AAAAxx
+3838 8273 0 2 8 18 38 838 1838 3838 3838 76 77 QRAAAA FGMAAA HHHHxx
+17 8274 1 1 7 17 17 17 17 17 17 34 35 RAAAAA GGMAAA OOOOxx
+3406 8275 0 2 6 6 6 406 1406 3406 3406 12 13 ABAAAA HGMAAA VVVVxx
+8312 8276 0 0 2 12 12 312 312 3312 8312 24 25 SHAAAA IGMAAA AAAAxx
+4034 8277 0 2 4 14 34 34 34 4034 4034 68 69 EZAAAA JGMAAA HHHHxx
+1535 8278 1 3 5 15 35 535 1535 1535 1535 70 71 BHAAAA KGMAAA OOOOxx
+7198 8279 0 2 8 18 98 198 1198 2198 7198 196 197 WQAAAA LGMAAA VVVVxx
+8885 8280 1 1 5 5 85 885 885 3885 8885 170 171 TDAAAA MGMAAA AAAAxx
+4081 8281 1 1 1 1 81 81 81 4081 4081 162 163 ZAAAAA NGMAAA HHHHxx
+980 8282 0 0 0 0 80 980 980 980 980 160 161 SLAAAA OGMAAA OOOOxx
+551 8283 1 3 1 11 51 551 551 551 551 102 103 FVAAAA PGMAAA VVVVxx
+7746 8284 0 2 6 6 46 746 1746 2746 7746 92 93 YLAAAA QGMAAA AAAAxx
+4756 8285 0 0 6 16 56 756 756 4756 4756 112 113 YAAAAA RGMAAA HHHHxx
+3655 8286 1 3 5 15 55 655 1655 3655 3655 110 111 PKAAAA SGMAAA OOOOxx
+7075 8287 1 3 5 15 75 75 1075 2075 7075 150 151 DMAAAA TGMAAA VVVVxx
+3950 8288 0 2 0 10 50 950 1950 3950 3950 100 101 YVAAAA UGMAAA AAAAxx
+2314 8289 0 2 4 14 14 314 314 2314 2314 28 29 ALAAAA VGMAAA HHHHxx
+8432 8290 0 0 2 12 32 432 432 3432 8432 64 65 IMAAAA WGMAAA OOOOxx
+62 8291 0 2 2 2 62 62 62 62 62 124 125 KCAAAA XGMAAA VVVVxx
+6920 8292 0 0 0 0 20 920 920 1920 6920 40 41 EGAAAA YGMAAA AAAAxx
+4077 8293 1 1 7 17 77 77 77 4077 4077 154 155 VAAAAA ZGMAAA HHHHxx
+9118 8294 0 2 8 18 18 118 1118 4118 9118 36 37 SMAAAA AHMAAA OOOOxx
+5375 8295 1 3 5 15 75 375 1375 375 5375 150 151 TYAAAA BHMAAA VVVVxx
+178 8296 0 2 8 18 78 178 178 178 178 156 157 WGAAAA CHMAAA AAAAxx
+1079 8297 1 3 9 19 79 79 1079 1079 1079 158 159 NPAAAA DHMAAA HHHHxx
+4279 8298 1 3 9 19 79 279 279 4279 4279 158 159 PIAAAA EHMAAA OOOOxx
+8436 8299 0 0 6 16 36 436 436 3436 8436 72 73 MMAAAA FHMAAA VVVVxx
+1931 8300 1 3 1 11 31 931 1931 1931 1931 62 63 HWAAAA GHMAAA AAAAxx
+2096 8301 0 0 6 16 96 96 96 2096 2096 192 193 QCAAAA HHMAAA HHHHxx
+1638 8302 0 2 8 18 38 638 1638 1638 1638 76 77 ALAAAA IHMAAA OOOOxx
+2788 8303 0 0 8 8 88 788 788 2788 2788 176 177 GDAAAA JHMAAA VVVVxx
+4751 8304 1 3 1 11 51 751 751 4751 4751 102 103 TAAAAA KHMAAA AAAAxx
+8824 8305 0 0 4 4 24 824 824 3824 8824 48 49 KBAAAA LHMAAA HHHHxx
+3098 8306 0 2 8 18 98 98 1098 3098 3098 196 197 EPAAAA MHMAAA OOOOxx
+4497 8307 1 1 7 17 97 497 497 4497 4497 194 195 ZQAAAA NHMAAA VVVVxx
+5223 8308 1 3 3 3 23 223 1223 223 5223 46 47 XSAAAA OHMAAA AAAAxx
+9212 8309 0 0 2 12 12 212 1212 4212 9212 24 25 IQAAAA PHMAAA HHHHxx
+4265 8310 1 1 5 5 65 265 265 4265 4265 130 131 BIAAAA QHMAAA OOOOxx
+6898 8311 0 2 8 18 98 898 898 1898 6898 196 197 IFAAAA RHMAAA VVVVxx
+8808 8312 0 0 8 8 8 808 808 3808 8808 16 17 UAAAAA SHMAAA AAAAxx
+5629 8313 1 1 9 9 29 629 1629 629 5629 58 59 NIAAAA THMAAA HHHHxx
+3779 8314 1 3 9 19 79 779 1779 3779 3779 158 159 JPAAAA UHMAAA OOOOxx
+4972 8315 0 0 2 12 72 972 972 4972 4972 144 145 GJAAAA VHMAAA VVVVxx
+4511 8316 1 3 1 11 11 511 511 4511 4511 22 23 NRAAAA WHMAAA AAAAxx
+6761 8317 1 1 1 1 61 761 761 1761 6761 122 123 BAAAAA XHMAAA HHHHxx
+2335 8318 1 3 5 15 35 335 335 2335 2335 70 71 VLAAAA YHMAAA OOOOxx
+732 8319 0 0 2 12 32 732 732 732 732 64 65 ECAAAA ZHMAAA VVVVxx
+4757 8320 1 1 7 17 57 757 757 4757 4757 114 115 ZAAAAA AIMAAA AAAAxx
+6624 8321 0 0 4 4 24 624 624 1624 6624 48 49 UUAAAA BIMAAA HHHHxx
+5869 8322 1 1 9 9 69 869 1869 869 5869 138 139 TRAAAA CIMAAA OOOOxx
+5842 8323 0 2 2 2 42 842 1842 842 5842 84 85 SQAAAA DIMAAA VVVVxx
+5735 8324 1 3 5 15 35 735 1735 735 5735 70 71 PMAAAA EIMAAA AAAAxx
+8276 8325 0 0 6 16 76 276 276 3276 8276 152 153 IGAAAA FIMAAA HHHHxx
+7227 8326 1 3 7 7 27 227 1227 2227 7227 54 55 ZRAAAA GIMAAA OOOOxx
+4923 8327 1 3 3 3 23 923 923 4923 4923 46 47 JHAAAA HIMAAA VVVVxx
+9135 8328 1 3 5 15 35 135 1135 4135 9135 70 71 JNAAAA IIMAAA AAAAxx
+5813 8329 1 1 3 13 13 813 1813 813 5813 26 27 PPAAAA JIMAAA HHHHxx
+9697 8330 1 1 7 17 97 697 1697 4697 9697 194 195 ZIAAAA KIMAAA OOOOxx
+3222 8331 0 2 2 2 22 222 1222 3222 3222 44 45 YTAAAA LIMAAA VVVVxx
+2394 8332 0 2 4 14 94 394 394 2394 2394 188 189 COAAAA MIMAAA AAAAxx
+5784 8333 0 0 4 4 84 784 1784 784 5784 168 169 MOAAAA NIMAAA HHHHxx
+3652 8334 0 0 2 12 52 652 1652 3652 3652 104 105 MKAAAA OIMAAA OOOOxx
+8175 8335 1 3 5 15 75 175 175 3175 8175 150 151 LCAAAA PIMAAA VVVVxx
+7568 8336 0 0 8 8 68 568 1568 2568 7568 136 137 CFAAAA QIMAAA AAAAxx
+6645 8337 1 1 5 5 45 645 645 1645 6645 90 91 PVAAAA RIMAAA HHHHxx
+8176 8338 0 0 6 16 76 176 176 3176 8176 152 153 MCAAAA SIMAAA OOOOxx
+530 8339 0 2 0 10 30 530 530 530 530 60 61 KUAAAA TIMAAA VVVVxx
+5439 8340 1 3 9 19 39 439 1439 439 5439 78 79 FBAAAA UIMAAA AAAAxx
+61 8341 1 1 1 1 61 61 61 61 61 122 123 JCAAAA VIMAAA HHHHxx
+3951 8342 1 3 1 11 51 951 1951 3951 3951 102 103 ZVAAAA WIMAAA OOOOxx
+5283 8343 1 3 3 3 83 283 1283 283 5283 166 167 FVAAAA XIMAAA VVVVxx
+7226 8344 0 2 6 6 26 226 1226 2226 7226 52 53 YRAAAA YIMAAA AAAAxx
+1954 8345 0 2 4 14 54 954 1954 1954 1954 108 109 EXAAAA ZIMAAA HHHHxx
+334 8346 0 2 4 14 34 334 334 334 334 68 69 WMAAAA AJMAAA OOOOxx
+3921 8347 1 1 1 1 21 921 1921 3921 3921 42 43 VUAAAA BJMAAA VVVVxx
+6276 8348 0 0 6 16 76 276 276 1276 6276 152 153 KHAAAA CJMAAA AAAAxx
+3378 8349 0 2 8 18 78 378 1378 3378 3378 156 157 YZAAAA DJMAAA HHHHxx
+5236 8350 0 0 6 16 36 236 1236 236 5236 72 73 KTAAAA EJMAAA OOOOxx
+7781 8351 1 1 1 1 81 781 1781 2781 7781 162 163 HNAAAA FJMAAA VVVVxx
+8601 8352 1 1 1 1 1 601 601 3601 8601 2 3 VSAAAA GJMAAA AAAAxx
+1473 8353 1 1 3 13 73 473 1473 1473 1473 146 147 REAAAA HJMAAA HHHHxx
+3246 8354 0 2 6 6 46 246 1246 3246 3246 92 93 WUAAAA IJMAAA OOOOxx
+3601 8355 1 1 1 1 1 601 1601 3601 3601 2 3 NIAAAA JJMAAA VVVVxx
+6861 8356 1 1 1 1 61 861 861 1861 6861 122 123 XDAAAA KJMAAA AAAAxx
+9032 8357 0 0 2 12 32 32 1032 4032 9032 64 65 KJAAAA LJMAAA HHHHxx
+216 8358 0 0 6 16 16 216 216 216 216 32 33 IIAAAA MJMAAA OOOOxx
+3824 8359 0 0 4 4 24 824 1824 3824 3824 48 49 CRAAAA NJMAAA VVVVxx
+8486 8360 0 2 6 6 86 486 486 3486 8486 172 173 KOAAAA OJMAAA AAAAxx
+276 8361 0 0 6 16 76 276 276 276 276 152 153 QKAAAA PJMAAA HHHHxx
+1838 8362 0 2 8 18 38 838 1838 1838 1838 76 77 SSAAAA QJMAAA OOOOxx
+6175 8363 1 3 5 15 75 175 175 1175 6175 150 151 NDAAAA RJMAAA VVVVxx
+3719 8364 1 3 9 19 19 719 1719 3719 3719 38 39 BNAAAA SJMAAA AAAAxx
+6958 8365 0 2 8 18 58 958 958 1958 6958 116 117 QHAAAA TJMAAA HHHHxx
+6822 8366 0 2 2 2 22 822 822 1822 6822 44 45 KCAAAA UJMAAA OOOOxx
+3318 8367 0 2 8 18 18 318 1318 3318 3318 36 37 QXAAAA VJMAAA VVVVxx
+7222 8368 0 2 2 2 22 222 1222 2222 7222 44 45 URAAAA WJMAAA AAAAxx
+85 8369 1 1 5 5 85 85 85 85 85 170 171 HDAAAA XJMAAA HHHHxx
+5158 8370 0 2 8 18 58 158 1158 158 5158 116 117 KQAAAA YJMAAA OOOOxx
+6360 8371 0 0 0 0 60 360 360 1360 6360 120 121 QKAAAA ZJMAAA VVVVxx
+2599 8372 1 3 9 19 99 599 599 2599 2599 198 199 ZVAAAA AKMAAA AAAAxx
+4002 8373 0 2 2 2 2 2 2 4002 4002 4 5 YXAAAA BKMAAA HHHHxx
+6597 8374 1 1 7 17 97 597 597 1597 6597 194 195 TTAAAA CKMAAA OOOOxx
+5762 8375 0 2 2 2 62 762 1762 762 5762 124 125 QNAAAA DKMAAA VVVVxx
+8383 8376 1 3 3 3 83 383 383 3383 8383 166 167 LKAAAA EKMAAA AAAAxx
+4686 8377 0 2 6 6 86 686 686 4686 4686 172 173 GYAAAA FKMAAA HHHHxx
+5972 8378 0 0 2 12 72 972 1972 972 5972 144 145 SVAAAA GKMAAA OOOOxx
+1432 8379 0 0 2 12 32 432 1432 1432 1432 64 65 CDAAAA HKMAAA VVVVxx
+1601 8380 1 1 1 1 1 601 1601 1601 1601 2 3 PJAAAA IKMAAA AAAAxx
+3012 8381 0 0 2 12 12 12 1012 3012 3012 24 25 WLAAAA JKMAAA HHHHxx
+9345 8382 1 1 5 5 45 345 1345 4345 9345 90 91 LVAAAA KKMAAA OOOOxx
+8869 8383 1 1 9 9 69 869 869 3869 8869 138 139 DDAAAA LKMAAA VVVVxx
+6612 8384 0 0 2 12 12 612 612 1612 6612 24 25 IUAAAA MKMAAA AAAAxx
+262 8385 0 2 2 2 62 262 262 262 262 124 125 CKAAAA NKMAAA HHHHxx
+300 8386 0 0 0 0 0 300 300 300 300 0 1 OLAAAA OKMAAA OOOOxx
+3045 8387 1 1 5 5 45 45 1045 3045 3045 90 91 DNAAAA PKMAAA VVVVxx
+7252 8388 0 0 2 12 52 252 1252 2252 7252 104 105 YSAAAA QKMAAA AAAAxx
+9099 8389 1 3 9 19 99 99 1099 4099 9099 198 199 ZLAAAA RKMAAA HHHHxx
+9006 8390 0 2 6 6 6 6 1006 4006 9006 12 13 KIAAAA SKMAAA OOOOxx
+3078 8391 0 2 8 18 78 78 1078 3078 3078 156 157 KOAAAA TKMAAA VVVVxx
+5159 8392 1 3 9 19 59 159 1159 159 5159 118 119 LQAAAA UKMAAA AAAAxx
+9329 8393 1 1 9 9 29 329 1329 4329 9329 58 59 VUAAAA VKMAAA HHHHxx
+1393 8394 1 1 3 13 93 393 1393 1393 1393 186 187 PBAAAA WKMAAA OOOOxx
+5894 8395 0 2 4 14 94 894 1894 894 5894 188 189 SSAAAA XKMAAA VVVVxx
+11 8396 1 3 1 11 11 11 11 11 11 22 23 LAAAAA YKMAAA AAAAxx
+5606 8397 0 2 6 6 6 606 1606 606 5606 12 13 QHAAAA ZKMAAA HHHHxx
+5541 8398 1 1 1 1 41 541 1541 541 5541 82 83 DFAAAA ALMAAA OOOOxx
+2689 8399 1 1 9 9 89 689 689 2689 2689 178 179 LZAAAA BLMAAA VVVVxx
+1023 8400 1 3 3 3 23 23 1023 1023 1023 46 47 JNAAAA CLMAAA AAAAxx
+8134 8401 0 2 4 14 34 134 134 3134 8134 68 69 WAAAAA DLMAAA HHHHxx
+5923 8402 1 3 3 3 23 923 1923 923 5923 46 47 VTAAAA ELMAAA OOOOxx
+6056 8403 0 0 6 16 56 56 56 1056 6056 112 113 YYAAAA FLMAAA VVVVxx
+653 8404 1 1 3 13 53 653 653 653 653 106 107 DZAAAA GLMAAA AAAAxx
+367 8405 1 3 7 7 67 367 367 367 367 134 135 DOAAAA HLMAAA HHHHxx
+1828 8406 0 0 8 8 28 828 1828 1828 1828 56 57 ISAAAA ILMAAA OOOOxx
+6506 8407 0 2 6 6 6 506 506 1506 6506 12 13 GQAAAA JLMAAA VVVVxx
+5772 8408 0 0 2 12 72 772 1772 772 5772 144 145 AOAAAA KLMAAA AAAAxx
+8052 8409 0 0 2 12 52 52 52 3052 8052 104 105 SXAAAA LLMAAA HHHHxx
+2633 8410 1 1 3 13 33 633 633 2633 2633 66 67 HXAAAA MLMAAA OOOOxx
+4878 8411 0 2 8 18 78 878 878 4878 4878 156 157 QFAAAA NLMAAA VVVVxx
+5621 8412 1 1 1 1 21 621 1621 621 5621 42 43 FIAAAA OLMAAA AAAAxx
+41 8413 1 1 1 1 41 41 41 41 41 82 83 PBAAAA PLMAAA HHHHxx
+4613 8414 1 1 3 13 13 613 613 4613 4613 26 27 LVAAAA QLMAAA OOOOxx
+9389 8415 1 1 9 9 89 389 1389 4389 9389 178 179 DXAAAA RLMAAA VVVVxx
+9414 8416 0 2 4 14 14 414 1414 4414 9414 28 29 CYAAAA SLMAAA AAAAxx
+3583 8417 1 3 3 3 83 583 1583 3583 3583 166 167 VHAAAA TLMAAA HHHHxx
+3454 8418 0 2 4 14 54 454 1454 3454 3454 108 109 WCAAAA ULMAAA OOOOxx
+719 8419 1 3 9 19 19 719 719 719 719 38 39 RBAAAA VLMAAA VVVVxx
+6188 8420 0 0 8 8 88 188 188 1188 6188 176 177 AEAAAA WLMAAA AAAAxx
+2288 8421 0 0 8 8 88 288 288 2288 2288 176 177 AKAAAA XLMAAA HHHHxx
+1287 8422 1 3 7 7 87 287 1287 1287 1287 174 175 NXAAAA YLMAAA OOOOxx
+1397 8423 1 1 7 17 97 397 1397 1397 1397 194 195 TBAAAA ZLMAAA VVVVxx
+7763 8424 1 3 3 3 63 763 1763 2763 7763 126 127 PMAAAA AMMAAA AAAAxx
+5194 8425 0 2 4 14 94 194 1194 194 5194 188 189 URAAAA BMMAAA HHHHxx
+3167 8426 1 3 7 7 67 167 1167 3167 3167 134 135 VRAAAA CMMAAA OOOOxx
+9218 8427 0 2 8 18 18 218 1218 4218 9218 36 37 OQAAAA DMMAAA VVVVxx
+2065 8428 1 1 5 5 65 65 65 2065 2065 130 131 LBAAAA EMMAAA AAAAxx
+9669 8429 1 1 9 9 69 669 1669 4669 9669 138 139 XHAAAA FMMAAA HHHHxx
+146 8430 0 2 6 6 46 146 146 146 146 92 93 QFAAAA GMMAAA OOOOxx
+6141 8431 1 1 1 1 41 141 141 1141 6141 82 83 FCAAAA HMMAAA VVVVxx
+2843 8432 1 3 3 3 43 843 843 2843 2843 86 87 JFAAAA IMMAAA AAAAxx
+7934 8433 0 2 4 14 34 934 1934 2934 7934 68 69 ETAAAA JMMAAA HHHHxx
+2536 8434 0 0 6 16 36 536 536 2536 2536 72 73 OTAAAA KMMAAA OOOOxx
+7088 8435 0 0 8 8 88 88 1088 2088 7088 176 177 QMAAAA LMMAAA VVVVxx
+2519 8436 1 3 9 19 19 519 519 2519 2519 38 39 XSAAAA MMMAAA AAAAxx
+6650 8437 0 2 0 10 50 650 650 1650 6650 100 101 UVAAAA NMMAAA HHHHxx
+3007 8438 1 3 7 7 7 7 1007 3007 3007 14 15 RLAAAA OMMAAA OOOOxx
+4507 8439 1 3 7 7 7 507 507 4507 4507 14 15 JRAAAA PMMAAA VVVVxx
+4892 8440 0 0 2 12 92 892 892 4892 4892 184 185 EGAAAA QMMAAA AAAAxx
+7159 8441 1 3 9 19 59 159 1159 2159 7159 118 119 JPAAAA RMMAAA HHHHxx
+3171 8442 1 3 1 11 71 171 1171 3171 3171 142 143 ZRAAAA SMMAAA OOOOxx
+1080 8443 0 0 0 0 80 80 1080 1080 1080 160 161 OPAAAA TMMAAA VVVVxx
+7248 8444 0 0 8 8 48 248 1248 2248 7248 96 97 USAAAA UMMAAA AAAAxx
+7230 8445 0 2 0 10 30 230 1230 2230 7230 60 61 CSAAAA VMMAAA HHHHxx
+3823 8446 1 3 3 3 23 823 1823 3823 3823 46 47 BRAAAA WMMAAA OOOOxx
+5517 8447 1 1 7 17 17 517 1517 517 5517 34 35 FEAAAA XMMAAA VVVVxx
+1482 8448 0 2 2 2 82 482 1482 1482 1482 164 165 AFAAAA YMMAAA AAAAxx
+9953 8449 1 1 3 13 53 953 1953 4953 9953 106 107 VSAAAA ZMMAAA HHHHxx
+2754 8450 0 2 4 14 54 754 754 2754 2754 108 109 YBAAAA ANMAAA OOOOxx
+3875 8451 1 3 5 15 75 875 1875 3875 3875 150 151 BTAAAA BNMAAA VVVVxx
+9800 8452 0 0 0 0 0 800 1800 4800 9800 0 1 YMAAAA CNMAAA AAAAxx
+8819 8453 1 3 9 19 19 819 819 3819 8819 38 39 FBAAAA DNMAAA HHHHxx
+8267 8454 1 3 7 7 67 267 267 3267 8267 134 135 ZFAAAA ENMAAA OOOOxx
+520 8455 0 0 0 0 20 520 520 520 520 40 41 AUAAAA FNMAAA VVVVxx
+5770 8456 0 2 0 10 70 770 1770 770 5770 140 141 YNAAAA GNMAAA AAAAxx
+2114 8457 0 2 4 14 14 114 114 2114 2114 28 29 IDAAAA HNMAAA HHHHxx
+5045 8458 1 1 5 5 45 45 1045 45 5045 90 91 BMAAAA INMAAA OOOOxx
+1094 8459 0 2 4 14 94 94 1094 1094 1094 188 189 CQAAAA JNMAAA VVVVxx
+8786 8460 0 2 6 6 86 786 786 3786 8786 172 173 YZAAAA KNMAAA AAAAxx
+353 8461 1 1 3 13 53 353 353 353 353 106 107 PNAAAA LNMAAA HHHHxx
+290 8462 0 2 0 10 90 290 290 290 290 180 181 ELAAAA MNMAAA OOOOxx
+3376 8463 0 0 6 16 76 376 1376 3376 3376 152 153 WZAAAA NNMAAA VVVVxx
+9305 8464 1 1 5 5 5 305 1305 4305 9305 10 11 XTAAAA ONMAAA AAAAxx
+186 8465 0 2 6 6 86 186 186 186 186 172 173 EHAAAA PNMAAA HHHHxx
+4817 8466 1 1 7 17 17 817 817 4817 4817 34 35 HDAAAA QNMAAA OOOOxx
+4638 8467 0 2 8 18 38 638 638 4638 4638 76 77 KWAAAA RNMAAA VVVVxx
+3558 8468 0 2 8 18 58 558 1558 3558 3558 116 117 WGAAAA SNMAAA AAAAxx
+9285 8469 1 1 5 5 85 285 1285 4285 9285 170 171 DTAAAA TNMAAA HHHHxx
+848 8470 0 0 8 8 48 848 848 848 848 96 97 QGAAAA UNMAAA OOOOxx
+8923 8471 1 3 3 3 23 923 923 3923 8923 46 47 FFAAAA VNMAAA VVVVxx
+6826 8472 0 2 6 6 26 826 826 1826 6826 52 53 OCAAAA WNMAAA AAAAxx
+5187 8473 1 3 7 7 87 187 1187 187 5187 174 175 NRAAAA XNMAAA HHHHxx
+2398 8474 0 2 8 18 98 398 398 2398 2398 196 197 GOAAAA YNMAAA OOOOxx
+7653 8475 1 1 3 13 53 653 1653 2653 7653 106 107 JIAAAA ZNMAAA VVVVxx
+8835 8476 1 3 5 15 35 835 835 3835 8835 70 71 VBAAAA AOMAAA AAAAxx
+5736 8477 0 0 6 16 36 736 1736 736 5736 72 73 QMAAAA BOMAAA HHHHxx
+1238 8478 0 2 8 18 38 238 1238 1238 1238 76 77 QVAAAA COMAAA OOOOxx
+6021 8479 1 1 1 1 21 21 21 1021 6021 42 43 PXAAAA DOMAAA VVVVxx
+6815 8480 1 3 5 15 15 815 815 1815 6815 30 31 DCAAAA EOMAAA AAAAxx
+2549 8481 1 1 9 9 49 549 549 2549 2549 98 99 BUAAAA FOMAAA HHHHxx
+5657 8482 1 1 7 17 57 657 1657 657 5657 114 115 PJAAAA GOMAAA OOOOxx
+6855 8483 1 3 5 15 55 855 855 1855 6855 110 111 RDAAAA HOMAAA VVVVxx
+1225 8484 1 1 5 5 25 225 1225 1225 1225 50 51 DVAAAA IOMAAA AAAAxx
+7452 8485 0 0 2 12 52 452 1452 2452 7452 104 105 QAAAAA JOMAAA HHHHxx
+2479 8486 1 3 9 19 79 479 479 2479 2479 158 159 JRAAAA KOMAAA OOOOxx
+7974 8487 0 2 4 14 74 974 1974 2974 7974 148 149 SUAAAA LOMAAA VVVVxx
+1212 8488 0 0 2 12 12 212 1212 1212 1212 24 25 QUAAAA MOMAAA AAAAxx
+8883 8489 1 3 3 3 83 883 883 3883 8883 166 167 RDAAAA NOMAAA HHHHxx
+8150 8490 0 2 0 10 50 150 150 3150 8150 100 101 MBAAAA OOMAAA OOOOxx
+3392 8491 0 0 2 12 92 392 1392 3392 3392 184 185 MAAAAA POMAAA VVVVxx
+6774 8492 0 2 4 14 74 774 774 1774 6774 148 149 OAAAAA QOMAAA AAAAxx
+904 8493 0 0 4 4 4 904 904 904 904 8 9 UIAAAA ROMAAA HHHHxx
+5068 8494 0 0 8 8 68 68 1068 68 5068 136 137 YMAAAA SOMAAA OOOOxx
+9339 8495 1 3 9 19 39 339 1339 4339 9339 78 79 FVAAAA TOMAAA VVVVxx
+1062 8496 0 2 2 2 62 62 1062 1062 1062 124 125 WOAAAA UOMAAA AAAAxx
+3841 8497 1 1 1 1 41 841 1841 3841 3841 82 83 TRAAAA VOMAAA HHHHxx
+8924 8498 0 0 4 4 24 924 924 3924 8924 48 49 GFAAAA WOMAAA OOOOxx
+9795 8499 1 3 5 15 95 795 1795 4795 9795 190 191 TMAAAA XOMAAA VVVVxx
+3981 8500 1 1 1 1 81 981 1981 3981 3981 162 163 DXAAAA YOMAAA AAAAxx
+4290 8501 0 2 0 10 90 290 290 4290 4290 180 181 AJAAAA ZOMAAA HHHHxx
+1067 8502 1 3 7 7 67 67 1067 1067 1067 134 135 BPAAAA APMAAA OOOOxx
+8679 8503 1 3 9 19 79 679 679 3679 8679 158 159 VVAAAA BPMAAA VVVVxx
+2894 8504 0 2 4 14 94 894 894 2894 2894 188 189 IHAAAA CPMAAA AAAAxx
+9248 8505 0 0 8 8 48 248 1248 4248 9248 96 97 SRAAAA DPMAAA HHHHxx
+1072 8506 0 0 2 12 72 72 1072 1072 1072 144 145 GPAAAA EPMAAA OOOOxx
+3510 8507 0 2 0 10 10 510 1510 3510 3510 20 21 AFAAAA FPMAAA VVVVxx
+6871 8508 1 3 1 11 71 871 871 1871 6871 142 143 HEAAAA GPMAAA AAAAxx
+8701 8509 1 1 1 1 1 701 701 3701 8701 2 3 RWAAAA HPMAAA HHHHxx
+8170 8510 0 2 0 10 70 170 170 3170 8170 140 141 GCAAAA IPMAAA OOOOxx
+2730 8511 0 2 0 10 30 730 730 2730 2730 60 61 ABAAAA JPMAAA VVVVxx
+2668 8512 0 0 8 8 68 668 668 2668 2668 136 137 QYAAAA KPMAAA AAAAxx
+8723 8513 1 3 3 3 23 723 723 3723 8723 46 47 NXAAAA LPMAAA HHHHxx
+3439 8514 1 3 9 19 39 439 1439 3439 3439 78 79 HCAAAA MPMAAA OOOOxx
+6219 8515 1 3 9 19 19 219 219 1219 6219 38 39 FFAAAA NPMAAA VVVVxx
+4264 8516 0 0 4 4 64 264 264 4264 4264 128 129 AIAAAA OPMAAA AAAAxx
+3929 8517 1 1 9 9 29 929 1929 3929 3929 58 59 DVAAAA PPMAAA HHHHxx
+7 8518 1 3 7 7 7 7 7 7 7 14 15 HAAAAA QPMAAA OOOOxx
+3737 8519 1 1 7 17 37 737 1737 3737 3737 74 75 TNAAAA RPMAAA VVVVxx
+358 8520 0 2 8 18 58 358 358 358 358 116 117 UNAAAA SPMAAA AAAAxx
+5128 8521 0 0 8 8 28 128 1128 128 5128 56 57 GPAAAA TPMAAA HHHHxx
+7353 8522 1 1 3 13 53 353 1353 2353 7353 106 107 VWAAAA UPMAAA OOOOxx
+8758 8523 0 2 8 18 58 758 758 3758 8758 116 117 WYAAAA VPMAAA VVVVxx
+7284 8524 0 0 4 4 84 284 1284 2284 7284 168 169 EUAAAA WPMAAA AAAAxx
+4037 8525 1 1 7 17 37 37 37 4037 4037 74 75 HZAAAA XPMAAA HHHHxx
+435 8526 1 3 5 15 35 435 435 435 435 70 71 TQAAAA YPMAAA OOOOxx
+3580 8527 0 0 0 0 80 580 1580 3580 3580 160 161 SHAAAA ZPMAAA VVVVxx
+4554 8528 0 2 4 14 54 554 554 4554 4554 108 109 ETAAAA AQMAAA AAAAxx
+4337 8529 1 1 7 17 37 337 337 4337 4337 74 75 VKAAAA BQMAAA HHHHxx
+512 8530 0 0 2 12 12 512 512 512 512 24 25 STAAAA CQMAAA OOOOxx
+2032 8531 0 0 2 12 32 32 32 2032 2032 64 65 EAAAAA DQMAAA VVVVxx
+1755 8532 1 3 5 15 55 755 1755 1755 1755 110 111 NPAAAA EQMAAA AAAAxx
+9923 8533 1 3 3 3 23 923 1923 4923 9923 46 47 RRAAAA FQMAAA HHHHxx
+3747 8534 1 3 7 7 47 747 1747 3747 3747 94 95 DOAAAA GQMAAA OOOOxx
+27 8535 1 3 7 7 27 27 27 27 27 54 55 BBAAAA HQMAAA VVVVxx
+3075 8536 1 3 5 15 75 75 1075 3075 3075 150 151 HOAAAA IQMAAA AAAAxx
+6259 8537 1 3 9 19 59 259 259 1259 6259 118 119 TGAAAA JQMAAA HHHHxx
+2940 8538 0 0 0 0 40 940 940 2940 2940 80 81 CJAAAA KQMAAA OOOOxx
+5724 8539 0 0 4 4 24 724 1724 724 5724 48 49 EMAAAA LQMAAA VVVVxx
+5638 8540 0 2 8 18 38 638 1638 638 5638 76 77 WIAAAA MQMAAA AAAAxx
+479 8541 1 3 9 19 79 479 479 479 479 158 159 LSAAAA NQMAAA HHHHxx
+4125 8542 1 1 5 5 25 125 125 4125 4125 50 51 RCAAAA OQMAAA OOOOxx
+1525 8543 1 1 5 5 25 525 1525 1525 1525 50 51 RGAAAA PQMAAA VVVVxx
+7529 8544 1 1 9 9 29 529 1529 2529 7529 58 59 PDAAAA QQMAAA AAAAxx
+931 8545 1 3 1 11 31 931 931 931 931 62 63 VJAAAA RQMAAA HHHHxx
+5175 8546 1 3 5 15 75 175 1175 175 5175 150 151 BRAAAA SQMAAA OOOOxx
+6798 8547 0 2 8 18 98 798 798 1798 6798 196 197 MBAAAA TQMAAA VVVVxx
+2111 8548 1 3 1 11 11 111 111 2111 2111 22 23 FDAAAA UQMAAA AAAAxx
+6145 8549 1 1 5 5 45 145 145 1145 6145 90 91 JCAAAA VQMAAA HHHHxx
+4712 8550 0 0 2 12 12 712 712 4712 4712 24 25 GZAAAA WQMAAA OOOOxx
+3110 8551 0 2 0 10 10 110 1110 3110 3110 20 21 QPAAAA XQMAAA VVVVxx
+97 8552 1 1 7 17 97 97 97 97 97 194 195 TDAAAA YQMAAA AAAAxx
+758 8553 0 2 8 18 58 758 758 758 758 116 117 EDAAAA ZQMAAA HHHHxx
+1895 8554 1 3 5 15 95 895 1895 1895 1895 190 191 XUAAAA ARMAAA OOOOxx
+5289 8555 1 1 9 9 89 289 1289 289 5289 178 179 LVAAAA BRMAAA VVVVxx
+5026 8556 0 2 6 6 26 26 1026 26 5026 52 53 ILAAAA CRMAAA AAAAxx
+4725 8557 1 1 5 5 25 725 725 4725 4725 50 51 TZAAAA DRMAAA HHHHxx
+1679 8558 1 3 9 19 79 679 1679 1679 1679 158 159 PMAAAA ERMAAA OOOOxx
+4433 8559 1 1 3 13 33 433 433 4433 4433 66 67 NOAAAA FRMAAA VVVVxx
+5340 8560 0 0 0 0 40 340 1340 340 5340 80 81 KXAAAA GRMAAA AAAAxx
+6340 8561 0 0 0 0 40 340 340 1340 6340 80 81 WJAAAA HRMAAA HHHHxx
+3261 8562 1 1 1 1 61 261 1261 3261 3261 122 123 LVAAAA IRMAAA OOOOxx
+8108 8563 0 0 8 8 8 108 108 3108 8108 16 17 WZAAAA JRMAAA VVVVxx
+8785 8564 1 1 5 5 85 785 785 3785 8785 170 171 XZAAAA KRMAAA AAAAxx
+7391 8565 1 3 1 11 91 391 1391 2391 7391 182 183 HYAAAA LRMAAA HHHHxx
+1496 8566 0 0 6 16 96 496 1496 1496 1496 192 193 OFAAAA MRMAAA OOOOxx
+1484 8567 0 0 4 4 84 484 1484 1484 1484 168 169 CFAAAA NRMAAA VVVVxx
+5884 8568 0 0 4 4 84 884 1884 884 5884 168 169 ISAAAA ORMAAA AAAAxx
+342 8569 0 2 2 2 42 342 342 342 342 84 85 ENAAAA PRMAAA HHHHxx
+7659 8570 1 3 9 19 59 659 1659 2659 7659 118 119 PIAAAA QRMAAA OOOOxx
+6635 8571 1 3 5 15 35 635 635 1635 6635 70 71 FVAAAA RRMAAA VVVVxx
+8507 8572 1 3 7 7 7 507 507 3507 8507 14 15 FPAAAA SRMAAA AAAAxx
+2583 8573 1 3 3 3 83 583 583 2583 2583 166 167 JVAAAA TRMAAA HHHHxx
+6533 8574 1 1 3 13 33 533 533 1533 6533 66 67 HRAAAA URMAAA OOOOxx
+5879 8575 1 3 9 19 79 879 1879 879 5879 158 159 DSAAAA VRMAAA VVVVxx
+5511 8576 1 3 1 11 11 511 1511 511 5511 22 23 ZDAAAA WRMAAA AAAAxx
+3682 8577 0 2 2 2 82 682 1682 3682 3682 164 165 QLAAAA XRMAAA HHHHxx
+7182 8578 0 2 2 2 82 182 1182 2182 7182 164 165 GQAAAA YRMAAA OOOOxx
+1409 8579 1 1 9 9 9 409 1409 1409 1409 18 19 FCAAAA ZRMAAA VVVVxx
+3363 8580 1 3 3 3 63 363 1363 3363 3363 126 127 JZAAAA ASMAAA AAAAxx
+729 8581 1 1 9 9 29 729 729 729 729 58 59 BCAAAA BSMAAA HHHHxx
+5857 8582 1 1 7 17 57 857 1857 857 5857 114 115 HRAAAA CSMAAA OOOOxx
+235 8583 1 3 5 15 35 235 235 235 235 70 71 BJAAAA DSMAAA VVVVxx
+193 8584 1 1 3 13 93 193 193 193 193 186 187 LHAAAA ESMAAA AAAAxx
+5586 8585 0 2 6 6 86 586 1586 586 5586 172 173 WGAAAA FSMAAA HHHHxx
+6203 8586 1 3 3 3 3 203 203 1203 6203 6 7 PEAAAA GSMAAA OOOOxx
+6795 8587 1 3 5 15 95 795 795 1795 6795 190 191 JBAAAA HSMAAA VVVVxx
+3211 8588 1 3 1 11 11 211 1211 3211 3211 22 23 NTAAAA ISMAAA AAAAxx
+9763 8589 1 3 3 3 63 763 1763 4763 9763 126 127 NLAAAA JSMAAA HHHHxx
+9043 8590 1 3 3 3 43 43 1043 4043 9043 86 87 VJAAAA KSMAAA OOOOxx
+2854 8591 0 2 4 14 54 854 854 2854 2854 108 109 UFAAAA LSMAAA VVVVxx
+565 8592 1 1 5 5 65 565 565 565 565 130 131 TVAAAA MSMAAA AAAAxx
+9284 8593 0 0 4 4 84 284 1284 4284 9284 168 169 CTAAAA NSMAAA HHHHxx
+7886 8594 0 2 6 6 86 886 1886 2886 7886 172 173 IRAAAA OSMAAA OOOOxx
+122 8595 0 2 2 2 22 122 122 122 122 44 45 SEAAAA PSMAAA VVVVxx
+4934 8596 0 2 4 14 34 934 934 4934 4934 68 69 UHAAAA QSMAAA AAAAxx
+1766 8597 0 2 6 6 66 766 1766 1766 1766 132 133 YPAAAA RSMAAA HHHHxx
+2554 8598 0 2 4 14 54 554 554 2554 2554 108 109 GUAAAA SSMAAA OOOOxx
+488 8599 0 0 8 8 88 488 488 488 488 176 177 USAAAA TSMAAA VVVVxx
+825 8600 1 1 5 5 25 825 825 825 825 50 51 TFAAAA USMAAA AAAAxx
+678 8601 0 2 8 18 78 678 678 678 678 156 157 CAAAAA VSMAAA HHHHxx
+4543 8602 1 3 3 3 43 543 543 4543 4543 86 87 TSAAAA WSMAAA OOOOxx
+1699 8603 1 3 9 19 99 699 1699 1699 1699 198 199 JNAAAA XSMAAA VVVVxx
+3771 8604 1 3 1 11 71 771 1771 3771 3771 142 143 BPAAAA YSMAAA AAAAxx
+1234 8605 0 2 4 14 34 234 1234 1234 1234 68 69 MVAAAA ZSMAAA HHHHxx
+4152 8606 0 0 2 12 52 152 152 4152 4152 104 105 SDAAAA ATMAAA OOOOxx
+1632 8607 0 0 2 12 32 632 1632 1632 1632 64 65 UKAAAA BTMAAA VVVVxx
+4988 8608 0 0 8 8 88 988 988 4988 4988 176 177 WJAAAA CTMAAA AAAAxx
+1980 8609 0 0 0 0 80 980 1980 1980 1980 160 161 EYAAAA DTMAAA HHHHxx
+7479 8610 1 3 9 19 79 479 1479 2479 7479 158 159 RBAAAA ETMAAA OOOOxx
+2586 8611 0 2 6 6 86 586 586 2586 2586 172 173 MVAAAA FTMAAA VVVVxx
+5433 8612 1 1 3 13 33 433 1433 433 5433 66 67 ZAAAAA GTMAAA AAAAxx
+2261 8613 1 1 1 1 61 261 261 2261 2261 122 123 ZIAAAA HTMAAA HHHHxx
+1180 8614 0 0 0 0 80 180 1180 1180 1180 160 161 KTAAAA ITMAAA OOOOxx
+3938 8615 0 2 8 18 38 938 1938 3938 3938 76 77 MVAAAA JTMAAA VVVVxx
+6714 8616 0 2 4 14 14 714 714 1714 6714 28 29 GYAAAA KTMAAA AAAAxx
+2890 8617 0 2 0 10 90 890 890 2890 2890 180 181 EHAAAA LTMAAA HHHHxx
+7379 8618 1 3 9 19 79 379 1379 2379 7379 158 159 VXAAAA MTMAAA OOOOxx
+5896 8619 0 0 6 16 96 896 1896 896 5896 192 193 USAAAA NTMAAA VVVVxx
+5949 8620 1 1 9 9 49 949 1949 949 5949 98 99 VUAAAA OTMAAA AAAAxx
+3194 8621 0 2 4 14 94 194 1194 3194 3194 188 189 WSAAAA PTMAAA HHHHxx
+9325 8622 1 1 5 5 25 325 1325 4325 9325 50 51 RUAAAA QTMAAA OOOOxx
+9531 8623 1 3 1 11 31 531 1531 4531 9531 62 63 PCAAAA RTMAAA VVVVxx
+711 8624 1 3 1 11 11 711 711 711 711 22 23 JBAAAA STMAAA AAAAxx
+2450 8625 0 2 0 10 50 450 450 2450 2450 100 101 GQAAAA TTMAAA HHHHxx
+1929 8626 1 1 9 9 29 929 1929 1929 1929 58 59 FWAAAA UTMAAA OOOOxx
+6165 8627 1 1 5 5 65 165 165 1165 6165 130 131 DDAAAA VTMAAA VVVVxx
+4050 8628 0 2 0 10 50 50 50 4050 4050 100 101 UZAAAA WTMAAA AAAAxx
+9011 8629 1 3 1 11 11 11 1011 4011 9011 22 23 PIAAAA XTMAAA HHHHxx
+7916 8630 0 0 6 16 16 916 1916 2916 7916 32 33 MSAAAA YTMAAA OOOOxx
+9136 8631 0 0 6 16 36 136 1136 4136 9136 72 73 KNAAAA ZTMAAA VVVVxx
+8782 8632 0 2 2 2 82 782 782 3782 8782 164 165 UZAAAA AUMAAA AAAAxx
+8491 8633 1 3 1 11 91 491 491 3491 8491 182 183 POAAAA BUMAAA HHHHxx
+5114 8634 0 2 4 14 14 114 1114 114 5114 28 29 SOAAAA CUMAAA OOOOxx
+5815 8635 1 3 5 15 15 815 1815 815 5815 30 31 RPAAAA DUMAAA VVVVxx
+5628 8636 0 0 8 8 28 628 1628 628 5628 56 57 MIAAAA EUMAAA AAAAxx
+810 8637 0 2 0 10 10 810 810 810 810 20 21 EFAAAA FUMAAA HHHHxx
+6178 8638 0 2 8 18 78 178 178 1178 6178 156 157 QDAAAA GUMAAA OOOOxx
+2619 8639 1 3 9 19 19 619 619 2619 2619 38 39 TWAAAA HUMAAA VVVVxx
+3340 8640 0 0 0 0 40 340 1340 3340 3340 80 81 MYAAAA IUMAAA AAAAxx
+2491 8641 1 3 1 11 91 491 491 2491 2491 182 183 VRAAAA JUMAAA HHHHxx
+3574 8642 0 2 4 14 74 574 1574 3574 3574 148 149 MHAAAA KUMAAA OOOOxx
+6754 8643 0 2 4 14 54 754 754 1754 6754 108 109 UZAAAA LUMAAA VVVVxx
+1566 8644 0 2 6 6 66 566 1566 1566 1566 132 133 GIAAAA MUMAAA AAAAxx
+9174 8645 0 2 4 14 74 174 1174 4174 9174 148 149 WOAAAA NUMAAA HHHHxx
+1520 8646 0 0 0 0 20 520 1520 1520 1520 40 41 MGAAAA OUMAAA OOOOxx
+2691 8647 1 3 1 11 91 691 691 2691 2691 182 183 NZAAAA PUMAAA VVVVxx
+6961 8648 1 1 1 1 61 961 961 1961 6961 122 123 THAAAA QUMAAA AAAAxx
+5722 8649 0 2 2 2 22 722 1722 722 5722 44 45 CMAAAA RUMAAA HHHHxx
+9707 8650 1 3 7 7 7 707 1707 4707 9707 14 15 JJAAAA SUMAAA OOOOxx
+2891 8651 1 3 1 11 91 891 891 2891 2891 182 183 FHAAAA TUMAAA VVVVxx
+341 8652 1 1 1 1 41 341 341 341 341 82 83 DNAAAA UUMAAA AAAAxx
+4690 8653 0 2 0 10 90 690 690 4690 4690 180 181 KYAAAA VUMAAA HHHHxx
+7841 8654 1 1 1 1 41 841 1841 2841 7841 82 83 PPAAAA WUMAAA OOOOxx
+6615 8655 1 3 5 15 15 615 615 1615 6615 30 31 LUAAAA XUMAAA VVVVxx
+9169 8656 1 1 9 9 69 169 1169 4169 9169 138 139 ROAAAA YUMAAA AAAAxx
+6689 8657 1 1 9 9 89 689 689 1689 6689 178 179 HXAAAA ZUMAAA HHHHxx
+8721 8658 1 1 1 1 21 721 721 3721 8721 42 43 LXAAAA AVMAAA OOOOxx
+7508 8659 0 0 8 8 8 508 1508 2508 7508 16 17 UCAAAA BVMAAA VVVVxx
+8631 8660 1 3 1 11 31 631 631 3631 8631 62 63 ZTAAAA CVMAAA AAAAxx
+480 8661 0 0 0 0 80 480 480 480 480 160 161 MSAAAA DVMAAA HHHHxx
+7094 8662 0 2 4 14 94 94 1094 2094 7094 188 189 WMAAAA EVMAAA OOOOxx
+319 8663 1 3 9 19 19 319 319 319 319 38 39 HMAAAA FVMAAA VVVVxx
+9421 8664 1 1 1 1 21 421 1421 4421 9421 42 43 JYAAAA GVMAAA AAAAxx
+4352 8665 0 0 2 12 52 352 352 4352 4352 104 105 KLAAAA HVMAAA HHHHxx
+5019 8666 1 3 9 19 19 19 1019 19 5019 38 39 BLAAAA IVMAAA OOOOxx
+3956 8667 0 0 6 16 56 956 1956 3956 3956 112 113 EWAAAA JVMAAA VVVVxx
+114 8668 0 2 4 14 14 114 114 114 114 28 29 KEAAAA KVMAAA AAAAxx
+1196 8669 0 0 6 16 96 196 1196 1196 1196 192 193 AUAAAA LVMAAA HHHHxx
+1407 8670 1 3 7 7 7 407 1407 1407 1407 14 15 DCAAAA MVMAAA OOOOxx
+7432 8671 0 0 2 12 32 432 1432 2432 7432 64 65 WZAAAA NVMAAA VVVVxx
+3141 8672 1 1 1 1 41 141 1141 3141 3141 82 83 VQAAAA OVMAAA AAAAxx
+2073 8673 1 1 3 13 73 73 73 2073 2073 146 147 TBAAAA PVMAAA HHHHxx
+3400 8674 0 0 0 0 0 400 1400 3400 3400 0 1 UAAAAA QVMAAA OOOOxx
+505 8675 1 1 5 5 5 505 505 505 505 10 11 LTAAAA RVMAAA VVVVxx
+1263 8676 1 3 3 3 63 263 1263 1263 1263 126 127 PWAAAA SVMAAA AAAAxx
+190 8677 0 2 0 10 90 190 190 190 190 180 181 IHAAAA TVMAAA HHHHxx
+6686 8678 0 2 6 6 86 686 686 1686 6686 172 173 EXAAAA UVMAAA OOOOxx
+9821 8679 1 1 1 1 21 821 1821 4821 9821 42 43 TNAAAA VVMAAA VVVVxx
+1119 8680 1 3 9 19 19 119 1119 1119 1119 38 39 BRAAAA WVMAAA AAAAxx
+2955 8681 1 3 5 15 55 955 955 2955 2955 110 111 RJAAAA XVMAAA HHHHxx
+224 8682 0 0 4 4 24 224 224 224 224 48 49 QIAAAA YVMAAA OOOOxx
+7562 8683 0 2 2 2 62 562 1562 2562 7562 124 125 WEAAAA ZVMAAA VVVVxx
+8845 8684 1 1 5 5 45 845 845 3845 8845 90 91 FCAAAA AWMAAA AAAAxx
+5405 8685 1 1 5 5 5 405 1405 405 5405 10 11 XZAAAA BWMAAA HHHHxx
+9192 8686 0 0 2 12 92 192 1192 4192 9192 184 185 OPAAAA CWMAAA OOOOxx
+4927 8687 1 3 7 7 27 927 927 4927 4927 54 55 NHAAAA DWMAAA VVVVxx
+997 8688 1 1 7 17 97 997 997 997 997 194 195 JMAAAA EWMAAA AAAAxx
+989 8689 1 1 9 9 89 989 989 989 989 178 179 BMAAAA FWMAAA HHHHxx
+7258 8690 0 2 8 18 58 258 1258 2258 7258 116 117 ETAAAA GWMAAA OOOOxx
+6899 8691 1 3 9 19 99 899 899 1899 6899 198 199 JFAAAA HWMAAA VVVVxx
+1770 8692 0 2 0 10 70 770 1770 1770 1770 140 141 CQAAAA IWMAAA AAAAxx
+4423 8693 1 3 3 3 23 423 423 4423 4423 46 47 DOAAAA JWMAAA HHHHxx
+5671 8694 1 3 1 11 71 671 1671 671 5671 142 143 DKAAAA KWMAAA OOOOxx
+8393 8695 1 1 3 13 93 393 393 3393 8393 186 187 VKAAAA LWMAAA VVVVxx
+4355 8696 1 3 5 15 55 355 355 4355 4355 110 111 NLAAAA MWMAAA AAAAxx
+3919 8697 1 3 9 19 19 919 1919 3919 3919 38 39 TUAAAA NWMAAA HHHHxx
+338 8698 0 2 8 18 38 338 338 338 338 76 77 ANAAAA OWMAAA OOOOxx
+5790 8699 0 2 0 10 90 790 1790 790 5790 180 181 SOAAAA PWMAAA VVVVxx
+1452 8700 0 0 2 12 52 452 1452 1452 1452 104 105 WDAAAA QWMAAA AAAAxx
+939 8701 1 3 9 19 39 939 939 939 939 78 79 DKAAAA RWMAAA HHHHxx
+8913 8702 1 1 3 13 13 913 913 3913 8913 26 27 VEAAAA SWMAAA OOOOxx
+7157 8703 1 1 7 17 57 157 1157 2157 7157 114 115 HPAAAA TWMAAA VVVVxx
+7240 8704 0 0 0 0 40 240 1240 2240 7240 80 81 MSAAAA UWMAAA AAAAxx
+3492 8705 0 0 2 12 92 492 1492 3492 3492 184 185 IEAAAA VWMAAA HHHHxx
+3464 8706 0 0 4 4 64 464 1464 3464 3464 128 129 GDAAAA WWMAAA OOOOxx
+388 8707 0 0 8 8 88 388 388 388 388 176 177 YOAAAA XWMAAA VVVVxx
+4135 8708 1 3 5 15 35 135 135 4135 4135 70 71 BDAAAA YWMAAA AAAAxx
+1194 8709 0 2 4 14 94 194 1194 1194 1194 188 189 YTAAAA ZWMAAA HHHHxx
+5476 8710 0 0 6 16 76 476 1476 476 5476 152 153 QCAAAA AXMAAA OOOOxx
+9844 8711 0 0 4 4 44 844 1844 4844 9844 88 89 QOAAAA BXMAAA VVVVxx
+9364 8712 0 0 4 4 64 364 1364 4364 9364 128 129 EWAAAA CXMAAA AAAAxx
+5238 8713 0 2 8 18 38 238 1238 238 5238 76 77 MTAAAA DXMAAA HHHHxx
+3712 8714 0 0 2 12 12 712 1712 3712 3712 24 25 UMAAAA EXMAAA OOOOxx
+6189 8715 1 1 9 9 89 189 189 1189 6189 178 179 BEAAAA FXMAAA VVVVxx
+5257 8716 1 1 7 17 57 257 1257 257 5257 114 115 FUAAAA GXMAAA AAAAxx
+81 8717 1 1 1 1 81 81 81 81 81 162 163 DDAAAA HXMAAA HHHHxx
+3289 8718 1 1 9 9 89 289 1289 3289 3289 178 179 NWAAAA IXMAAA OOOOxx
+1177 8719 1 1 7 17 77 177 1177 1177 1177 154 155 HTAAAA JXMAAA VVVVxx
+5038 8720 0 2 8 18 38 38 1038 38 5038 76 77 ULAAAA KXMAAA AAAAxx
+325 8721 1 1 5 5 25 325 325 325 325 50 51 NMAAAA LXMAAA HHHHxx
+7221 8722 1 1 1 1 21 221 1221 2221 7221 42 43 TRAAAA MXMAAA OOOOxx
+7123 8723 1 3 3 3 23 123 1123 2123 7123 46 47 ZNAAAA NXMAAA VVVVxx
+6364 8724 0 0 4 4 64 364 364 1364 6364 128 129 UKAAAA OXMAAA AAAAxx
+4468 8725 0 0 8 8 68 468 468 4468 4468 136 137 WPAAAA PXMAAA HHHHxx
+9185 8726 1 1 5 5 85 185 1185 4185 9185 170 171 HPAAAA QXMAAA OOOOxx
+4158 8727 0 2 8 18 58 158 158 4158 4158 116 117 YDAAAA RXMAAA VVVVxx
+9439 8728 1 3 9 19 39 439 1439 4439 9439 78 79 BZAAAA SXMAAA AAAAxx
+7759 8729 1 3 9 19 59 759 1759 2759 7759 118 119 LMAAAA TXMAAA HHHHxx
+3325 8730 1 1 5 5 25 325 1325 3325 3325 50 51 XXAAAA UXMAAA OOOOxx
+7991 8731 1 3 1 11 91 991 1991 2991 7991 182 183 JVAAAA VXMAAA VVVVxx
+1650 8732 0 2 0 10 50 650 1650 1650 1650 100 101 MLAAAA WXMAAA AAAAxx
+8395 8733 1 3 5 15 95 395 395 3395 8395 190 191 XKAAAA XXMAAA HHHHxx
+286 8734 0 2 6 6 86 286 286 286 286 172 173 ALAAAA YXMAAA OOOOxx
+1507 8735 1 3 7 7 7 507 1507 1507 1507 14 15 ZFAAAA ZXMAAA VVVVxx
+4122 8736 0 2 2 2 22 122 122 4122 4122 44 45 OCAAAA AYMAAA AAAAxx
+2625 8737 1 1 5 5 25 625 625 2625 2625 50 51 ZWAAAA BYMAAA HHHHxx
+1140 8738 0 0 0 0 40 140 1140 1140 1140 80 81 WRAAAA CYMAAA OOOOxx
+5262 8739 0 2 2 2 62 262 1262 262 5262 124 125 KUAAAA DYMAAA VVVVxx
+4919 8740 1 3 9 19 19 919 919 4919 4919 38 39 FHAAAA EYMAAA AAAAxx
+7266 8741 0 2 6 6 66 266 1266 2266 7266 132 133 MTAAAA FYMAAA HHHHxx
+630 8742 0 2 0 10 30 630 630 630 630 60 61 GYAAAA GYMAAA OOOOxx
+2129 8743 1 1 9 9 29 129 129 2129 2129 58 59 XDAAAA HYMAAA VVVVxx
+9552 8744 0 0 2 12 52 552 1552 4552 9552 104 105 KDAAAA IYMAAA AAAAxx
+3018 8745 0 2 8 18 18 18 1018 3018 3018 36 37 CMAAAA JYMAAA HHHHxx
+7145 8746 1 1 5 5 45 145 1145 2145 7145 90 91 VOAAAA KYMAAA OOOOxx
+1633 8747 1 1 3 13 33 633 1633 1633 1633 66 67 VKAAAA LYMAAA VVVVxx
+7957 8748 1 1 7 17 57 957 1957 2957 7957 114 115 BUAAAA MYMAAA AAAAxx
+774 8749 0 2 4 14 74 774 774 774 774 148 149 UDAAAA NYMAAA HHHHxx
+9371 8750 1 3 1 11 71 371 1371 4371 9371 142 143 LWAAAA OYMAAA OOOOxx
+6007 8751 1 3 7 7 7 7 7 1007 6007 14 15 BXAAAA PYMAAA VVVVxx
+5277 8752 1 1 7 17 77 277 1277 277 5277 154 155 ZUAAAA QYMAAA AAAAxx
+9426 8753 0 2 6 6 26 426 1426 4426 9426 52 53 OYAAAA RYMAAA HHHHxx
+9190 8754 0 2 0 10 90 190 1190 4190 9190 180 181 MPAAAA SYMAAA OOOOxx
+8996 8755 0 0 6 16 96 996 996 3996 8996 192 193 AIAAAA TYMAAA VVVVxx
+3409 8756 1 1 9 9 9 409 1409 3409 3409 18 19 DBAAAA UYMAAA AAAAxx
+7212 8757 0 0 2 12 12 212 1212 2212 7212 24 25 KRAAAA VYMAAA HHHHxx
+416 8758 0 0 6 16 16 416 416 416 416 32 33 AQAAAA WYMAAA OOOOxx
+7211 8759 1 3 1 11 11 211 1211 2211 7211 22 23 JRAAAA XYMAAA VVVVxx
+7454 8760 0 2 4 14 54 454 1454 2454 7454 108 109 SAAAAA YYMAAA AAAAxx
+8417 8761 1 1 7 17 17 417 417 3417 8417 34 35 TLAAAA ZYMAAA HHHHxx
+5562 8762 0 2 2 2 62 562 1562 562 5562 124 125 YFAAAA AZMAAA OOOOxx
+4996 8763 0 0 6 16 96 996 996 4996 4996 192 193 EKAAAA BZMAAA VVVVxx
+5718 8764 0 2 8 18 18 718 1718 718 5718 36 37 YLAAAA CZMAAA AAAAxx
+7838 8765 0 2 8 18 38 838 1838 2838 7838 76 77 MPAAAA DZMAAA HHHHxx
+7715 8766 1 3 5 15 15 715 1715 2715 7715 30 31 TKAAAA EZMAAA OOOOxx
+2780 8767 0 0 0 0 80 780 780 2780 2780 160 161 YCAAAA FZMAAA VVVVxx
+1013 8768 1 1 3 13 13 13 1013 1013 1013 26 27 ZMAAAA GZMAAA AAAAxx
+8465 8769 1 1 5 5 65 465 465 3465 8465 130 131 PNAAAA HZMAAA HHHHxx
+7976 8770 0 0 6 16 76 976 1976 2976 7976 152 153 UUAAAA IZMAAA OOOOxx
+7150 8771 0 2 0 10 50 150 1150 2150 7150 100 101 APAAAA JZMAAA VVVVxx
+6471 8772 1 3 1 11 71 471 471 1471 6471 142 143 XOAAAA KZMAAA AAAAxx
+1927 8773 1 3 7 7 27 927 1927 1927 1927 54 55 DWAAAA LZMAAA HHHHxx
+227 8774 1 3 7 7 27 227 227 227 227 54 55 TIAAAA MZMAAA OOOOxx
+6462 8775 0 2 2 2 62 462 462 1462 6462 124 125 OOAAAA NZMAAA VVVVxx
+5227 8776 1 3 7 7 27 227 1227 227 5227 54 55 BTAAAA OZMAAA AAAAxx
+1074 8777 0 2 4 14 74 74 1074 1074 1074 148 149 IPAAAA PZMAAA HHHHxx
+9448 8778 0 0 8 8 48 448 1448 4448 9448 96 97 KZAAAA QZMAAA OOOOxx
+4459 8779 1 3 9 19 59 459 459 4459 4459 118 119 NPAAAA RZMAAA VVVVxx
+2478 8780 0 2 8 18 78 478 478 2478 2478 156 157 IRAAAA SZMAAA AAAAxx
+5005 8781 1 1 5 5 5 5 1005 5 5005 10 11 NKAAAA TZMAAA HHHHxx
+2418 8782 0 2 8 18 18 418 418 2418 2418 36 37 APAAAA UZMAAA OOOOxx
+6991 8783 1 3 1 11 91 991 991 1991 6991 182 183 XIAAAA VZMAAA VVVVxx
+4729 8784 1 1 9 9 29 729 729 4729 4729 58 59 XZAAAA WZMAAA AAAAxx
+3548 8785 0 0 8 8 48 548 1548 3548 3548 96 97 MGAAAA XZMAAA HHHHxx
+9616 8786 0 0 6 16 16 616 1616 4616 9616 32 33 WFAAAA YZMAAA OOOOxx
+2901 8787 1 1 1 1 1 901 901 2901 2901 2 3 PHAAAA ZZMAAA VVVVxx
+10 8788 0 2 0 10 10 10 10 10 10 20 21 KAAAAA AANAAA AAAAxx
+2637 8789 1 1 7 17 37 637 637 2637 2637 74 75 LXAAAA BANAAA HHHHxx
+6747 8790 1 3 7 7 47 747 747 1747 6747 94 95 NZAAAA CANAAA OOOOxx
+797 8791 1 1 7 17 97 797 797 797 797 194 195 REAAAA DANAAA VVVVxx
+7609 8792 1 1 9 9 9 609 1609 2609 7609 18 19 RGAAAA EANAAA AAAAxx
+8290 8793 0 2 0 10 90 290 290 3290 8290 180 181 WGAAAA FANAAA HHHHxx
+8765 8794 1 1 5 5 65 765 765 3765 8765 130 131 DZAAAA GANAAA OOOOxx
+8053 8795 1 1 3 13 53 53 53 3053 8053 106 107 TXAAAA HANAAA VVVVxx
+5602 8796 0 2 2 2 2 602 1602 602 5602 4 5 MHAAAA IANAAA AAAAxx
+3672 8797 0 0 2 12 72 672 1672 3672 3672 144 145 GLAAAA JANAAA HHHHxx
+7513 8798 1 1 3 13 13 513 1513 2513 7513 26 27 ZCAAAA KANAAA OOOOxx
+3462 8799 0 2 2 2 62 462 1462 3462 3462 124 125 EDAAAA LANAAA VVVVxx
+4457 8800 1 1 7 17 57 457 457 4457 4457 114 115 LPAAAA MANAAA AAAAxx
+6547 8801 1 3 7 7 47 547 547 1547 6547 94 95 VRAAAA NANAAA HHHHxx
+7417 8802 1 1 7 17 17 417 1417 2417 7417 34 35 HZAAAA OANAAA OOOOxx
+8641 8803 1 1 1 1 41 641 641 3641 8641 82 83 JUAAAA PANAAA VVVVxx
+149 8804 1 1 9 9 49 149 149 149 149 98 99 TFAAAA QANAAA AAAAxx
+5041 8805 1 1 1 1 41 41 1041 41 5041 82 83 XLAAAA RANAAA HHHHxx
+9232 8806 0 0 2 12 32 232 1232 4232 9232 64 65 CRAAAA SANAAA OOOOxx
+3603 8807 1 3 3 3 3 603 1603 3603 3603 6 7 PIAAAA TANAAA VVVVxx
+2792 8808 0 0 2 12 92 792 792 2792 2792 184 185 KDAAAA UANAAA AAAAxx
+6620 8809 0 0 0 0 20 620 620 1620 6620 40 41 QUAAAA VANAAA HHHHxx
+4000 8810 0 0 0 0 0 0 0 4000 4000 0 1 WXAAAA WANAAA OOOOxx
+659 8811 1 3 9 19 59 659 659 659 659 118 119 JZAAAA XANAAA VVVVxx
+8174 8812 0 2 4 14 74 174 174 3174 8174 148 149 KCAAAA YANAAA AAAAxx
+4599 8813 1 3 9 19 99 599 599 4599 4599 198 199 XUAAAA ZANAAA HHHHxx
+7851 8814 1 3 1 11 51 851 1851 2851 7851 102 103 ZPAAAA ABNAAA OOOOxx
+6284 8815 0 0 4 4 84 284 284 1284 6284 168 169 SHAAAA BBNAAA VVVVxx
+7116 8816 0 0 6 16 16 116 1116 2116 7116 32 33 SNAAAA CBNAAA AAAAxx
+5595 8817 1 3 5 15 95 595 1595 595 5595 190 191 FHAAAA DBNAAA HHHHxx
+2903 8818 1 3 3 3 3 903 903 2903 2903 6 7 RHAAAA EBNAAA OOOOxx
+5948 8819 0 0 8 8 48 948 1948 948 5948 96 97 UUAAAA FBNAAA VVVVxx
+225 8820 1 1 5 5 25 225 225 225 225 50 51 RIAAAA GBNAAA AAAAxx
+524 8821 0 0 4 4 24 524 524 524 524 48 49 EUAAAA HBNAAA HHHHxx
+7639 8822 1 3 9 19 39 639 1639 2639 7639 78 79 VHAAAA IBNAAA OOOOxx
+7297 8823 1 1 7 17 97 297 1297 2297 7297 194 195 RUAAAA JBNAAA VVVVxx
+2606 8824 0 2 6 6 6 606 606 2606 2606 12 13 GWAAAA KBNAAA AAAAxx
+4771 8825 1 3 1 11 71 771 771 4771 4771 142 143 NBAAAA LBNAAA HHHHxx
+8162 8826 0 2 2 2 62 162 162 3162 8162 124 125 YBAAAA MBNAAA OOOOxx
+8999 8827 1 3 9 19 99 999 999 3999 8999 198 199 DIAAAA NBNAAA VVVVxx
+2309 8828 1 1 9 9 9 309 309 2309 2309 18 19 VKAAAA OBNAAA AAAAxx
+3594 8829 0 2 4 14 94 594 1594 3594 3594 188 189 GIAAAA PBNAAA HHHHxx
+6092 8830 0 0 2 12 92 92 92 1092 6092 184 185 IAAAAA QBNAAA OOOOxx
+7467 8831 1 3 7 7 67 467 1467 2467 7467 134 135 FBAAAA RBNAAA VVVVxx
+6986 8832 0 2 6 6 86 986 986 1986 6986 172 173 SIAAAA SBNAAA AAAAxx
+9898 8833 0 2 8 18 98 898 1898 4898 9898 196 197 SQAAAA TBNAAA HHHHxx
+9578 8834 0 2 8 18 78 578 1578 4578 9578 156 157 KEAAAA UBNAAA OOOOxx
+156 8835 0 0 6 16 56 156 156 156 156 112 113 AGAAAA VBNAAA VVVVxx
+5810 8836 0 2 0 10 10 810 1810 810 5810 20 21 MPAAAA WBNAAA AAAAxx
+790 8837 0 2 0 10 90 790 790 790 790 180 181 KEAAAA XBNAAA HHHHxx
+6840 8838 0 0 0 0 40 840 840 1840 6840 80 81 CDAAAA YBNAAA OOOOxx
+6725 8839 1 1 5 5 25 725 725 1725 6725 50 51 RYAAAA ZBNAAA VVVVxx
+5528 8840 0 0 8 8 28 528 1528 528 5528 56 57 QEAAAA ACNAAA AAAAxx
+4120 8841 0 0 0 0 20 120 120 4120 4120 40 41 MCAAAA BCNAAA HHHHxx
+6694 8842 0 2 4 14 94 694 694 1694 6694 188 189 MXAAAA CCNAAA OOOOxx
+3552 8843 0 0 2 12 52 552 1552 3552 3552 104 105 QGAAAA DCNAAA VVVVxx
+1478 8844 0 2 8 18 78 478 1478 1478 1478 156 157 WEAAAA ECNAAA AAAAxx
+8084 8845 0 0 4 4 84 84 84 3084 8084 168 169 YYAAAA FCNAAA HHHHxx
+7578 8846 0 2 8 18 78 578 1578 2578 7578 156 157 MFAAAA GCNAAA OOOOxx
+6314 8847 0 2 4 14 14 314 314 1314 6314 28 29 WIAAAA HCNAAA VVVVxx
+6123 8848 1 3 3 3 23 123 123 1123 6123 46 47 NBAAAA ICNAAA AAAAxx
+9443 8849 1 3 3 3 43 443 1443 4443 9443 86 87 FZAAAA JCNAAA HHHHxx
+9628 8850 0 0 8 8 28 628 1628 4628 9628 56 57 IGAAAA KCNAAA OOOOxx
+8508 8851 0 0 8 8 8 508 508 3508 8508 16 17 GPAAAA LCNAAA VVVVxx
+5552 8852 0 0 2 12 52 552 1552 552 5552 104 105 OFAAAA MCNAAA AAAAxx
+5327 8853 1 3 7 7 27 327 1327 327 5327 54 55 XWAAAA NCNAAA HHHHxx
+7771 8854 1 3 1 11 71 771 1771 2771 7771 142 143 XMAAAA OCNAAA OOOOxx
+8932 8855 0 0 2 12 32 932 932 3932 8932 64 65 OFAAAA PCNAAA VVVVxx
+3526 8856 0 2 6 6 26 526 1526 3526 3526 52 53 QFAAAA QCNAAA AAAAxx
+4340 8857 0 0 0 0 40 340 340 4340 4340 80 81 YKAAAA RCNAAA HHHHxx
+9419 8858 1 3 9 19 19 419 1419 4419 9419 38 39 HYAAAA SCNAAA OOOOxx
+8421 8859 1 1 1 1 21 421 421 3421 8421 42 43 XLAAAA TCNAAA VVVVxx
+7431 8860 1 3 1 11 31 431 1431 2431 7431 62 63 VZAAAA UCNAAA AAAAxx
+172 8861 0 0 2 12 72 172 172 172 172 144 145 QGAAAA VCNAAA HHHHxx
+3279 8862 1 3 9 19 79 279 1279 3279 3279 158 159 DWAAAA WCNAAA OOOOxx
+1508 8863 0 0 8 8 8 508 1508 1508 1508 16 17 AGAAAA XCNAAA VVVVxx
+7091 8864 1 3 1 11 91 91 1091 2091 7091 182 183 TMAAAA YCNAAA AAAAxx
+1419 8865 1 3 9 19 19 419 1419 1419 1419 38 39 PCAAAA ZCNAAA HHHHxx
+3032 8866 0 0 2 12 32 32 1032 3032 3032 64 65 QMAAAA ADNAAA OOOOxx
+8683 8867 1 3 3 3 83 683 683 3683 8683 166 167 ZVAAAA BDNAAA VVVVxx
+4763 8868 1 3 3 3 63 763 763 4763 4763 126 127 FBAAAA CDNAAA AAAAxx
+4424 8869 0 0 4 4 24 424 424 4424 4424 48 49 EOAAAA DDNAAA HHHHxx
+8640 8870 0 0 0 0 40 640 640 3640 8640 80 81 IUAAAA EDNAAA OOOOxx
+7187 8871 1 3 7 7 87 187 1187 2187 7187 174 175 LQAAAA FDNAAA VVVVxx
+6247 8872 1 3 7 7 47 247 247 1247 6247 94 95 HGAAAA GDNAAA AAAAxx
+7340 8873 0 0 0 0 40 340 1340 2340 7340 80 81 IWAAAA HDNAAA HHHHxx
+182 8874 0 2 2 2 82 182 182 182 182 164 165 AHAAAA IDNAAA OOOOxx
+2948 8875 0 0 8 8 48 948 948 2948 2948 96 97 KJAAAA JDNAAA VVVVxx
+9462 8876 0 2 2 2 62 462 1462 4462 9462 124 125 YZAAAA KDNAAA AAAAxx
+5997 8877 1 1 7 17 97 997 1997 997 5997 194 195 RWAAAA LDNAAA HHHHxx
+5608 8878 0 0 8 8 8 608 1608 608 5608 16 17 SHAAAA MDNAAA OOOOxx
+1472 8879 0 0 2 12 72 472 1472 1472 1472 144 145 QEAAAA NDNAAA VVVVxx
+277 8880 1 1 7 17 77 277 277 277 277 154 155 RKAAAA ODNAAA AAAAxx
+4807 8881 1 3 7 7 7 807 807 4807 4807 14 15 XCAAAA PDNAAA HHHHxx
+4969 8882 1 1 9 9 69 969 969 4969 4969 138 139 DJAAAA QDNAAA OOOOxx
+5611 8883 1 3 1 11 11 611 1611 611 5611 22 23 VHAAAA RDNAAA VVVVxx
+372 8884 0 0 2 12 72 372 372 372 372 144 145 IOAAAA SDNAAA AAAAxx
+6666 8885 0 2 6 6 66 666 666 1666 6666 132 133 KWAAAA TDNAAA HHHHxx
+476 8886 0 0 6 16 76 476 476 476 476 152 153 ISAAAA UDNAAA OOOOxx
+5225 8887 1 1 5 5 25 225 1225 225 5225 50 51 ZSAAAA VDNAAA VVVVxx
+5143 8888 1 3 3 3 43 143 1143 143 5143 86 87 VPAAAA WDNAAA AAAAxx
+1853 8889 1 1 3 13 53 853 1853 1853 1853 106 107 HTAAAA XDNAAA HHHHxx
+675 8890 1 3 5 15 75 675 675 675 675 150 151 ZZAAAA YDNAAA OOOOxx
+5643 8891 1 3 3 3 43 643 1643 643 5643 86 87 BJAAAA ZDNAAA VVVVxx
+5317 8892 1 1 7 17 17 317 1317 317 5317 34 35 NWAAAA AENAAA AAAAxx
+8102 8893 0 2 2 2 2 102 102 3102 8102 4 5 QZAAAA BENAAA HHHHxx
+978 8894 0 2 8 18 78 978 978 978 978 156 157 QLAAAA CENAAA OOOOxx
+4620 8895 0 0 0 0 20 620 620 4620 4620 40 41 SVAAAA DENAAA VVVVxx
+151 8896 1 3 1 11 51 151 151 151 151 102 103 VFAAAA EENAAA AAAAxx
+972 8897 0 0 2 12 72 972 972 972 972 144 145 KLAAAA FENAAA HHHHxx
+6820 8898 0 0 0 0 20 820 820 1820 6820 40 41 ICAAAA GENAAA OOOOxx
+7387 8899 1 3 7 7 87 387 1387 2387 7387 174 175 DYAAAA HENAAA VVVVxx
+9634 8900 0 2 4 14 34 634 1634 4634 9634 68 69 OGAAAA IENAAA AAAAxx
+6308 8901 0 0 8 8 8 308 308 1308 6308 16 17 QIAAAA JENAAA HHHHxx
+8323 8902 1 3 3 3 23 323 323 3323 8323 46 47 DIAAAA KENAAA OOOOxx
+6672 8903 0 0 2 12 72 672 672 1672 6672 144 145 QWAAAA LENAAA VVVVxx
+8283 8904 1 3 3 3 83 283 283 3283 8283 166 167 PGAAAA MENAAA AAAAxx
+7996 8905 0 0 6 16 96 996 1996 2996 7996 192 193 OVAAAA NENAAA HHHHxx
+6488 8906 0 0 8 8 88 488 488 1488 6488 176 177 OPAAAA OENAAA OOOOxx
+2365 8907 1 1 5 5 65 365 365 2365 2365 130 131 ZMAAAA PENAAA VVVVxx
+9746 8908 0 2 6 6 46 746 1746 4746 9746 92 93 WKAAAA QENAAA AAAAxx
+8605 8909 1 1 5 5 5 605 605 3605 8605 10 11 ZSAAAA RENAAA HHHHxx
+3342 8910 0 2 2 2 42 342 1342 3342 3342 84 85 OYAAAA SENAAA OOOOxx
+8429 8911 1 1 9 9 29 429 429 3429 8429 58 59 FMAAAA TENAAA VVVVxx
+1162 8912 0 2 2 2 62 162 1162 1162 1162 124 125 SSAAAA UENAAA AAAAxx
+531 8913 1 3 1 11 31 531 531 531 531 62 63 LUAAAA VENAAA HHHHxx
+8408 8914 0 0 8 8 8 408 408 3408 8408 16 17 KLAAAA WENAAA OOOOxx
+8862 8915 0 2 2 2 62 862 862 3862 8862 124 125 WCAAAA XENAAA VVVVxx
+5843 8916 1 3 3 3 43 843 1843 843 5843 86 87 TQAAAA YENAAA AAAAxx
+8704 8917 0 0 4 4 4 704 704 3704 8704 8 9 UWAAAA ZENAAA HHHHxx
+7070 8918 0 2 0 10 70 70 1070 2070 7070 140 141 YLAAAA AFNAAA OOOOxx
+9119 8919 1 3 9 19 19 119 1119 4119 9119 38 39 TMAAAA BFNAAA VVVVxx
+8344 8920 0 0 4 4 44 344 344 3344 8344 88 89 YIAAAA CFNAAA AAAAxx
+8979 8921 1 3 9 19 79 979 979 3979 8979 158 159 JHAAAA DFNAAA HHHHxx
+2971 8922 1 3 1 11 71 971 971 2971 2971 142 143 HKAAAA EFNAAA OOOOxx
+7700 8923 0 0 0 0 0 700 1700 2700 7700 0 1 EKAAAA FFNAAA VVVVxx
+8280 8924 0 0 0 0 80 280 280 3280 8280 160 161 MGAAAA GFNAAA AAAAxx
+9096 8925 0 0 6 16 96 96 1096 4096 9096 192 193 WLAAAA HFNAAA HHHHxx
+99 8926 1 3 9 19 99 99 99 99 99 198 199 VDAAAA IFNAAA OOOOxx
+6696 8927 0 0 6 16 96 696 696 1696 6696 192 193 OXAAAA JFNAAA VVVVxx
+9490 8928 0 2 0 10 90 490 1490 4490 9490 180 181 ABAAAA KFNAAA AAAAxx
+9073 8929 1 1 3 13 73 73 1073 4073 9073 146 147 ZKAAAA LFNAAA HHHHxx
+1861 8930 1 1 1 1 61 861 1861 1861 1861 122 123 PTAAAA MFNAAA OOOOxx
+4413 8931 1 1 3 13 13 413 413 4413 4413 26 27 TNAAAA NFNAAA VVVVxx
+6002 8932 0 2 2 2 2 2 2 1002 6002 4 5 WWAAAA OFNAAA AAAAxx
+439 8933 1 3 9 19 39 439 439 439 439 78 79 XQAAAA PFNAAA HHHHxx
+5449 8934 1 1 9 9 49 449 1449 449 5449 98 99 PBAAAA QFNAAA OOOOxx
+9737 8935 1 1 7 17 37 737 1737 4737 9737 74 75 NKAAAA RFNAAA VVVVxx
+1898 8936 0 2 8 18 98 898 1898 1898 1898 196 197 AVAAAA SFNAAA AAAAxx
+4189 8937 1 1 9 9 89 189 189 4189 4189 178 179 DFAAAA TFNAAA HHHHxx
+1408 8938 0 0 8 8 8 408 1408 1408 1408 16 17 ECAAAA UFNAAA OOOOxx
+394 8939 0 2 4 14 94 394 394 394 394 188 189 EPAAAA VFNAAA VVVVxx
+1935 8940 1 3 5 15 35 935 1935 1935 1935 70 71 LWAAAA WFNAAA AAAAxx
+3965 8941 1 1 5 5 65 965 1965 3965 3965 130 131 NWAAAA XFNAAA HHHHxx
+6821 8942 1 1 1 1 21 821 821 1821 6821 42 43 JCAAAA YFNAAA OOOOxx
+349 8943 1 1 9 9 49 349 349 349 349 98 99 LNAAAA ZFNAAA VVVVxx
+8428 8944 0 0 8 8 28 428 428 3428 8428 56 57 EMAAAA AGNAAA AAAAxx
+8200 8945 0 0 0 0 0 200 200 3200 8200 0 1 KDAAAA BGNAAA HHHHxx
+1737 8946 1 1 7 17 37 737 1737 1737 1737 74 75 VOAAAA CGNAAA OOOOxx
+6516 8947 0 0 6 16 16 516 516 1516 6516 32 33 QQAAAA DGNAAA VVVVxx
+5441 8948 1 1 1 1 41 441 1441 441 5441 82 83 HBAAAA EGNAAA AAAAxx
+5999 8949 1 3 9 19 99 999 1999 999 5999 198 199 TWAAAA FGNAAA HHHHxx
+1539 8950 1 3 9 19 39 539 1539 1539 1539 78 79 FHAAAA GGNAAA OOOOxx
+9067 8951 1 3 7 7 67 67 1067 4067 9067 134 135 TKAAAA HGNAAA VVVVxx
+4061 8952 1 1 1 1 61 61 61 4061 4061 122 123 FAAAAA IGNAAA AAAAxx
+1642 8953 0 2 2 2 42 642 1642 1642 1642 84 85 ELAAAA JGNAAA HHHHxx
+4657 8954 1 1 7 17 57 657 657 4657 4657 114 115 DXAAAA KGNAAA OOOOxx
+9934 8955 0 2 4 14 34 934 1934 4934 9934 68 69 CSAAAA LGNAAA VVVVxx
+6385 8956 1 1 5 5 85 385 385 1385 6385 170 171 PLAAAA MGNAAA AAAAxx
+6775 8957 1 3 5 15 75 775 775 1775 6775 150 151 PAAAAA NGNAAA HHHHxx
+3873 8958 1 1 3 13 73 873 1873 3873 3873 146 147 ZSAAAA OGNAAA OOOOxx
+3862 8959 0 2 2 2 62 862 1862 3862 3862 124 125 OSAAAA PGNAAA VVVVxx
+1224 8960 0 0 4 4 24 224 1224 1224 1224 48 49 CVAAAA QGNAAA AAAAxx
+4483 8961 1 3 3 3 83 483 483 4483 4483 166 167 LQAAAA RGNAAA HHHHxx
+3685 8962 1 1 5 5 85 685 1685 3685 3685 170 171 TLAAAA SGNAAA OOOOxx
+6082 8963 0 2 2 2 82 82 82 1082 6082 164 165 YZAAAA TGNAAA VVVVxx
+7798 8964 0 2 8 18 98 798 1798 2798 7798 196 197 YNAAAA UGNAAA AAAAxx
+9039 8965 1 3 9 19 39 39 1039 4039 9039 78 79 RJAAAA VGNAAA HHHHxx
+985 8966 1 1 5 5 85 985 985 985 985 170 171 XLAAAA WGNAAA OOOOxx
+5389 8967 1 1 9 9 89 389 1389 389 5389 178 179 HZAAAA XGNAAA VVVVxx
+1716 8968 0 0 6 16 16 716 1716 1716 1716 32 33 AOAAAA YGNAAA AAAAxx
+4209 8969 1 1 9 9 9 209 209 4209 4209 18 19 XFAAAA ZGNAAA HHHHxx
+746 8970 0 2 6 6 46 746 746 746 746 92 93 SCAAAA AHNAAA OOOOxx
+6295 8971 1 3 5 15 95 295 295 1295 6295 190 191 DIAAAA BHNAAA VVVVxx
+9754 8972 0 2 4 14 54 754 1754 4754 9754 108 109 ELAAAA CHNAAA AAAAxx
+2336 8973 0 0 6 16 36 336 336 2336 2336 72 73 WLAAAA DHNAAA HHHHxx
+3701 8974 1 1 1 1 1 701 1701 3701 3701 2 3 JMAAAA EHNAAA OOOOxx
+3551 8975 1 3 1 11 51 551 1551 3551 3551 102 103 PGAAAA FHNAAA VVVVxx
+8516 8976 0 0 6 16 16 516 516 3516 8516 32 33 OPAAAA GHNAAA AAAAxx
+9290 8977 0 2 0 10 90 290 1290 4290 9290 180 181 ITAAAA HHNAAA HHHHxx
+5686 8978 0 2 6 6 86 686 1686 686 5686 172 173 SKAAAA IHNAAA OOOOxx
+2893 8979 1 1 3 13 93 893 893 2893 2893 186 187 HHAAAA JHNAAA VVVVxx
+6279 8980 1 3 9 19 79 279 279 1279 6279 158 159 NHAAAA KHNAAA AAAAxx
+2278 8981 0 2 8 18 78 278 278 2278 2278 156 157 QJAAAA LHNAAA HHHHxx
+1618 8982 0 2 8 18 18 618 1618 1618 1618 36 37 GKAAAA MHNAAA OOOOxx
+3450 8983 0 2 0 10 50 450 1450 3450 3450 100 101 SCAAAA NHNAAA VVVVxx
+8857 8984 1 1 7 17 57 857 857 3857 8857 114 115 RCAAAA OHNAAA AAAAxx
+1005 8985 1 1 5 5 5 5 1005 1005 1005 10 11 RMAAAA PHNAAA HHHHxx
+4727 8986 1 3 7 7 27 727 727 4727 4727 54 55 VZAAAA QHNAAA OOOOxx
+7617 8987 1 1 7 17 17 617 1617 2617 7617 34 35 ZGAAAA RHNAAA VVVVxx
+2021 8988 1 1 1 1 21 21 21 2021 2021 42 43 TZAAAA SHNAAA AAAAxx
+9124 8989 0 0 4 4 24 124 1124 4124 9124 48 49 YMAAAA THNAAA HHHHxx
+3175 8990 1 3 5 15 75 175 1175 3175 3175 150 151 DSAAAA UHNAAA OOOOxx
+2949 8991 1 1 9 9 49 949 949 2949 2949 98 99 LJAAAA VHNAAA VVVVxx
+2424 8992 0 0 4 4 24 424 424 2424 2424 48 49 GPAAAA WHNAAA AAAAxx
+4791 8993 1 3 1 11 91 791 791 4791 4791 182 183 HCAAAA XHNAAA HHHHxx
+7500 8994 0 0 0 0 0 500 1500 2500 7500 0 1 MCAAAA YHNAAA OOOOxx
+4893 8995 1 1 3 13 93 893 893 4893 4893 186 187 FGAAAA ZHNAAA VVVVxx
+121 8996 1 1 1 1 21 121 121 121 121 42 43 REAAAA AINAAA AAAAxx
+1965 8997 1 1 5 5 65 965 1965 1965 1965 130 131 PXAAAA BINAAA HHHHxx
+2972 8998 0 0 2 12 72 972 972 2972 2972 144 145 IKAAAA CINAAA OOOOxx
+662 8999 0 2 2 2 62 662 662 662 662 124 125 MZAAAA DINAAA VVVVxx
+7074 9000 0 2 4 14 74 74 1074 2074 7074 148 149 CMAAAA EINAAA AAAAxx
+981 9001 1 1 1 1 81 981 981 981 981 162 163 TLAAAA FINAAA HHHHxx
+3520 9002 0 0 0 0 20 520 1520 3520 3520 40 41 KFAAAA GINAAA OOOOxx
+6540 9003 0 0 0 0 40 540 540 1540 6540 80 81 ORAAAA HINAAA VVVVxx
+6648 9004 0 0 8 8 48 648 648 1648 6648 96 97 SVAAAA IINAAA AAAAxx
+7076 9005 0 0 6 16 76 76 1076 2076 7076 152 153 EMAAAA JINAAA HHHHxx
+6919 9006 1 3 9 19 19 919 919 1919 6919 38 39 DGAAAA KINAAA OOOOxx
+1108 9007 0 0 8 8 8 108 1108 1108 1108 16 17 QQAAAA LINAAA VVVVxx
+317 9008 1 1 7 17 17 317 317 317 317 34 35 FMAAAA MINAAA AAAAxx
+3483 9009 1 3 3 3 83 483 1483 3483 3483 166 167 ZDAAAA NINAAA HHHHxx
+6764 9010 0 0 4 4 64 764 764 1764 6764 128 129 EAAAAA OINAAA OOOOxx
+1235 9011 1 3 5 15 35 235 1235 1235 1235 70 71 NVAAAA PINAAA VVVVxx
+7121 9012 1 1 1 1 21 121 1121 2121 7121 42 43 XNAAAA QINAAA AAAAxx
+426 9013 0 2 6 6 26 426 426 426 426 52 53 KQAAAA RINAAA HHHHxx
+6880 9014 0 0 0 0 80 880 880 1880 6880 160 161 QEAAAA SINAAA OOOOxx
+5401 9015 1 1 1 1 1 401 1401 401 5401 2 3 TZAAAA TINAAA VVVVxx
+7323 9016 1 3 3 3 23 323 1323 2323 7323 46 47 RVAAAA UINAAA AAAAxx
+9751 9017 1 3 1 11 51 751 1751 4751 9751 102 103 BLAAAA VINAAA HHHHxx
+3436 9018 0 0 6 16 36 436 1436 3436 3436 72 73 ECAAAA WINAAA OOOOxx
+7319 9019 1 3 9 19 19 319 1319 2319 7319 38 39 NVAAAA XINAAA VVVVxx
+7882 9020 0 2 2 2 82 882 1882 2882 7882 164 165 ERAAAA YINAAA AAAAxx
+8260 9021 0 0 0 0 60 260 260 3260 8260 120 121 SFAAAA ZINAAA HHHHxx
+9758 9022 0 2 8 18 58 758 1758 4758 9758 116 117 ILAAAA AJNAAA OOOOxx
+4205 9023 1 1 5 5 5 205 205 4205 4205 10 11 TFAAAA BJNAAA VVVVxx
+8884 9024 0 0 4 4 84 884 884 3884 8884 168 169 SDAAAA CJNAAA AAAAxx
+1112 9025 0 0 2 12 12 112 1112 1112 1112 24 25 UQAAAA DJNAAA HHHHxx
+2186 9026 0 2 6 6 86 186 186 2186 2186 172 173 CGAAAA EJNAAA OOOOxx
+8666 9027 0 2 6 6 66 666 666 3666 8666 132 133 IVAAAA FJNAAA VVVVxx
+4325 9028 1 1 5 5 25 325 325 4325 4325 50 51 JKAAAA GJNAAA AAAAxx
+4912 9029 0 0 2 12 12 912 912 4912 4912 24 25 YGAAAA HJNAAA HHHHxx
+6497 9030 1 1 7 17 97 497 497 1497 6497 194 195 XPAAAA IJNAAA OOOOxx
+9072 9031 0 0 2 12 72 72 1072 4072 9072 144 145 YKAAAA JJNAAA VVVVxx
+8899 9032 1 3 9 19 99 899 899 3899 8899 198 199 HEAAAA KJNAAA AAAAxx
+5619 9033 1 3 9 19 19 619 1619 619 5619 38 39 DIAAAA LJNAAA HHHHxx
+4110 9034 0 2 0 10 10 110 110 4110 4110 20 21 CCAAAA MJNAAA OOOOxx
+7025 9035 1 1 5 5 25 25 1025 2025 7025 50 51 FKAAAA NJNAAA VVVVxx
+5605 9036 1 1 5 5 5 605 1605 605 5605 10 11 PHAAAA OJNAAA AAAAxx
+2572 9037 0 0 2 12 72 572 572 2572 2572 144 145 YUAAAA PJNAAA HHHHxx
+3895 9038 1 3 5 15 95 895 1895 3895 3895 190 191 VTAAAA QJNAAA OOOOxx
+9138 9039 0 2 8 18 38 138 1138 4138 9138 76 77 MNAAAA RJNAAA VVVVxx
+4713 9040 1 1 3 13 13 713 713 4713 4713 26 27 HZAAAA SJNAAA AAAAxx
+6079 9041 1 3 9 19 79 79 79 1079 6079 158 159 VZAAAA TJNAAA HHHHxx
+8898 9042 0 2 8 18 98 898 898 3898 8898 196 197 GEAAAA UJNAAA OOOOxx
+2650 9043 0 2 0 10 50 650 650 2650 2650 100 101 YXAAAA VJNAAA VVVVxx
+5316 9044 0 0 6 16 16 316 1316 316 5316 32 33 MWAAAA WJNAAA AAAAxx
+5133 9045 1 1 3 13 33 133 1133 133 5133 66 67 LPAAAA XJNAAA HHHHxx
+2184 9046 0 0 4 4 84 184 184 2184 2184 168 169 AGAAAA YJNAAA OOOOxx
+2728 9047 0 0 8 8 28 728 728 2728 2728 56 57 YAAAAA ZJNAAA VVVVxx
+6737 9048 1 1 7 17 37 737 737 1737 6737 74 75 DZAAAA AKNAAA AAAAxx
+1128 9049 0 0 8 8 28 128 1128 1128 1128 56 57 KRAAAA BKNAAA HHHHxx
+9662 9050 0 2 2 2 62 662 1662 4662 9662 124 125 QHAAAA CKNAAA OOOOxx
+9384 9051 0 0 4 4 84 384 1384 4384 9384 168 169 YWAAAA DKNAAA VVVVxx
+4576 9052 0 0 6 16 76 576 576 4576 4576 152 153 AUAAAA EKNAAA AAAAxx
+9613 9053 1 1 3 13 13 613 1613 4613 9613 26 27 TFAAAA FKNAAA HHHHxx
+4001 9054 1 1 1 1 1 1 1 4001 4001 2 3 XXAAAA GKNAAA OOOOxx
+3628 9055 0 0 8 8 28 628 1628 3628 3628 56 57 OJAAAA HKNAAA VVVVxx
+6968 9056 0 0 8 8 68 968 968 1968 6968 136 137 AIAAAA IKNAAA AAAAxx
+6491 9057 1 3 1 11 91 491 491 1491 6491 182 183 RPAAAA JKNAAA HHHHxx
+1265 9058 1 1 5 5 65 265 1265 1265 1265 130 131 RWAAAA KKNAAA OOOOxx
+6128 9059 0 0 8 8 28 128 128 1128 6128 56 57 SBAAAA LKNAAA VVVVxx
+4274 9060 0 2 4 14 74 274 274 4274 4274 148 149 KIAAAA MKNAAA AAAAxx
+3598 9061 0 2 8 18 98 598 1598 3598 3598 196 197 KIAAAA NKNAAA HHHHxx
+7961 9062 1 1 1 1 61 961 1961 2961 7961 122 123 FUAAAA OKNAAA OOOOxx
+2643 9063 1 3 3 3 43 643 643 2643 2643 86 87 RXAAAA PKNAAA VVVVxx
+4547 9064 1 3 7 7 47 547 547 4547 4547 94 95 XSAAAA QKNAAA AAAAxx
+3568 9065 0 0 8 8 68 568 1568 3568 3568 136 137 GHAAAA RKNAAA HHHHxx
+8954 9066 0 2 4 14 54 954 954 3954 8954 108 109 KGAAAA SKNAAA OOOOxx
+8802 9067 0 2 2 2 2 802 802 3802 8802 4 5 OAAAAA TKNAAA VVVVxx
+7829 9068 1 1 9 9 29 829 1829 2829 7829 58 59 DPAAAA UKNAAA AAAAxx
+1008 9069 0 0 8 8 8 8 1008 1008 1008 16 17 UMAAAA VKNAAA HHHHxx
+3627 9070 1 3 7 7 27 627 1627 3627 3627 54 55 NJAAAA WKNAAA OOOOxx
+3999 9071 1 3 9 19 99 999 1999 3999 3999 198 199 VXAAAA XKNAAA VVVVxx
+7697 9072 1 1 7 17 97 697 1697 2697 7697 194 195 BKAAAA YKNAAA AAAAxx
+9380 9073 0 0 0 0 80 380 1380 4380 9380 160 161 UWAAAA ZKNAAA HHHHxx
+2707 9074 1 3 7 7 7 707 707 2707 2707 14 15 DAAAAA ALNAAA OOOOxx
+4430 9075 0 2 0 10 30 430 430 4430 4430 60 61 KOAAAA BLNAAA VVVVxx
+6440 9076 0 0 0 0 40 440 440 1440 6440 80 81 SNAAAA CLNAAA AAAAxx
+9958 9077 0 2 8 18 58 958 1958 4958 9958 116 117 ATAAAA DLNAAA HHHHxx
+7592 9078 0 0 2 12 92 592 1592 2592 7592 184 185 AGAAAA ELNAAA OOOOxx
+7852 9079 0 0 2 12 52 852 1852 2852 7852 104 105 AQAAAA FLNAAA VVVVxx
+9253 9080 1 1 3 13 53 253 1253 4253 9253 106 107 XRAAAA GLNAAA AAAAxx
+5910 9081 0 2 0 10 10 910 1910 910 5910 20 21 ITAAAA HLNAAA HHHHxx
+7487 9082 1 3 7 7 87 487 1487 2487 7487 174 175 ZBAAAA ILNAAA OOOOxx
+6324 9083 0 0 4 4 24 324 324 1324 6324 48 49 GJAAAA JLNAAA VVVVxx
+5792 9084 0 0 2 12 92 792 1792 792 5792 184 185 UOAAAA KLNAAA AAAAxx
+7390 9085 0 2 0 10 90 390 1390 2390 7390 180 181 GYAAAA LLNAAA HHHHxx
+8534 9086 0 2 4 14 34 534 534 3534 8534 68 69 GQAAAA MLNAAA OOOOxx
+2690 9087 0 2 0 10 90 690 690 2690 2690 180 181 MZAAAA NLNAAA VVVVxx
+3992 9088 0 0 2 12 92 992 1992 3992 3992 184 185 OXAAAA OLNAAA AAAAxx
+6928 9089 0 0 8 8 28 928 928 1928 6928 56 57 MGAAAA PLNAAA HHHHxx
+7815 9090 1 3 5 15 15 815 1815 2815 7815 30 31 POAAAA QLNAAA OOOOxx
+9477 9091 1 1 7 17 77 477 1477 4477 9477 154 155 NAAAAA RLNAAA VVVVxx
+497 9092 1 1 7 17 97 497 497 497 497 194 195 DTAAAA SLNAAA AAAAxx
+7532 9093 0 0 2 12 32 532 1532 2532 7532 64 65 SDAAAA TLNAAA HHHHxx
+9838 9094 0 2 8 18 38 838 1838 4838 9838 76 77 KOAAAA ULNAAA OOOOxx
+1557 9095 1 1 7 17 57 557 1557 1557 1557 114 115 XHAAAA VLNAAA VVVVxx
+2467 9096 1 3 7 7 67 467 467 2467 2467 134 135 XQAAAA WLNAAA AAAAxx
+2367 9097 1 3 7 7 67 367 367 2367 2367 134 135 BNAAAA XLNAAA HHHHxx
+5677 9098 1 1 7 17 77 677 1677 677 5677 154 155 JKAAAA YLNAAA OOOOxx
+6193 9099 1 1 3 13 93 193 193 1193 6193 186 187 FEAAAA ZLNAAA VVVVxx
+7126 9100 0 2 6 6 26 126 1126 2126 7126 52 53 COAAAA AMNAAA AAAAxx
+5264 9101 0 0 4 4 64 264 1264 264 5264 128 129 MUAAAA BMNAAA HHHHxx
+850 9102 0 2 0 10 50 850 850 850 850 100 101 SGAAAA CMNAAA OOOOxx
+4854 9103 0 2 4 14 54 854 854 4854 4854 108 109 SEAAAA DMNAAA VVVVxx
+4414 9104 0 2 4 14 14 414 414 4414 4414 28 29 UNAAAA EMNAAA AAAAxx
+8971 9105 1 3 1 11 71 971 971 3971 8971 142 143 BHAAAA FMNAAA HHHHxx
+9240 9106 0 0 0 0 40 240 1240 4240 9240 80 81 KRAAAA GMNAAA OOOOxx
+7341 9107 1 1 1 1 41 341 1341 2341 7341 82 83 JWAAAA HMNAAA VVVVxx
+3151 9108 1 3 1 11 51 151 1151 3151 3151 102 103 FRAAAA IMNAAA AAAAxx
+1742 9109 0 2 2 2 42 742 1742 1742 1742 84 85 APAAAA JMNAAA HHHHxx
+1347 9110 1 3 7 7 47 347 1347 1347 1347 94 95 VZAAAA KMNAAA OOOOxx
+9418 9111 0 2 8 18 18 418 1418 4418 9418 36 37 GYAAAA LMNAAA VVVVxx
+5452 9112 0 0 2 12 52 452 1452 452 5452 104 105 SBAAAA MMNAAA AAAAxx
+8637 9113 1 1 7 17 37 637 637 3637 8637 74 75 FUAAAA NMNAAA HHHHxx
+8287 9114 1 3 7 7 87 287 287 3287 8287 174 175 TGAAAA OMNAAA OOOOxx
+9865 9115 1 1 5 5 65 865 1865 4865 9865 130 131 LPAAAA PMNAAA VVVVxx
+1664 9116 0 0 4 4 64 664 1664 1664 1664 128 129 AMAAAA QMNAAA AAAAxx
+9933 9117 1 1 3 13 33 933 1933 4933 9933 66 67 BSAAAA RMNAAA HHHHxx
+3416 9118 0 0 6 16 16 416 1416 3416 3416 32 33 KBAAAA SMNAAA OOOOxx
+7981 9119 1 1 1 1 81 981 1981 2981 7981 162 163 ZUAAAA TMNAAA VVVVxx
+1981 9120 1 1 1 1 81 981 1981 1981 1981 162 163 FYAAAA UMNAAA AAAAxx
+441 9121 1 1 1 1 41 441 441 441 441 82 83 ZQAAAA VMNAAA HHHHxx
+1380 9122 0 0 0 0 80 380 1380 1380 1380 160 161 CBAAAA WMNAAA OOOOxx
+7325 9123 1 1 5 5 25 325 1325 2325 7325 50 51 TVAAAA XMNAAA VVVVxx
+5682 9124 0 2 2 2 82 682 1682 682 5682 164 165 OKAAAA YMNAAA AAAAxx
+1024 9125 0 0 4 4 24 24 1024 1024 1024 48 49 KNAAAA ZMNAAA HHHHxx
+1096 9126 0 0 6 16 96 96 1096 1096 1096 192 193 EQAAAA ANNAAA OOOOxx
+4717 9127 1 1 7 17 17 717 717 4717 4717 34 35 LZAAAA BNNAAA VVVVxx
+7948 9128 0 0 8 8 48 948 1948 2948 7948 96 97 STAAAA CNNAAA AAAAxx
+4074 9129 0 2 4 14 74 74 74 4074 4074 148 149 SAAAAA DNNAAA HHHHxx
+211 9130 1 3 1 11 11 211 211 211 211 22 23 DIAAAA ENNAAA OOOOxx
+8993 9131 1 1 3 13 93 993 993 3993 8993 186 187 XHAAAA FNNAAA VVVVxx
+4509 9132 1 1 9 9 9 509 509 4509 4509 18 19 LRAAAA GNNAAA AAAAxx
+823 9133 1 3 3 3 23 823 823 823 823 46 47 RFAAAA HNNAAA HHHHxx
+4747 9134 1 3 7 7 47 747 747 4747 4747 94 95 PAAAAA INNAAA OOOOxx
+6955 9135 1 3 5 15 55 955 955 1955 6955 110 111 NHAAAA JNNAAA VVVVxx
+7922 9136 0 2 2 2 22 922 1922 2922 7922 44 45 SSAAAA KNNAAA AAAAxx
+6936 9137 0 0 6 16 36 936 936 1936 6936 72 73 UGAAAA LNNAAA HHHHxx
+1546 9138 0 2 6 6 46 546 1546 1546 1546 92 93 MHAAAA MNNAAA OOOOxx
+9836 9139 0 0 6 16 36 836 1836 4836 9836 72 73 IOAAAA NNNAAA VVVVxx
+5626 9140 0 2 6 6 26 626 1626 626 5626 52 53 KIAAAA ONNAAA AAAAxx
+4879 9141 1 3 9 19 79 879 879 4879 4879 158 159 RFAAAA PNNAAA HHHHxx
+8590 9142 0 2 0 10 90 590 590 3590 8590 180 181 KSAAAA QNNAAA OOOOxx
+8842 9143 0 2 2 2 42 842 842 3842 8842 84 85 CCAAAA RNNAAA VVVVxx
+6505 9144 1 1 5 5 5 505 505 1505 6505 10 11 FQAAAA SNNAAA AAAAxx
+2803 9145 1 3 3 3 3 803 803 2803 2803 6 7 VDAAAA TNNAAA HHHHxx
+9258 9146 0 2 8 18 58 258 1258 4258 9258 116 117 CSAAAA UNNAAA OOOOxx
+741 9147 1 1 1 1 41 741 741 741 741 82 83 NCAAAA VNNAAA VVVVxx
+1457 9148 1 1 7 17 57 457 1457 1457 1457 114 115 BEAAAA WNNAAA AAAAxx
+5777 9149 1 1 7 17 77 777 1777 777 5777 154 155 FOAAAA XNNAAA HHHHxx
+2883 9150 1 3 3 3 83 883 883 2883 2883 166 167 XGAAAA YNNAAA OOOOxx
+6610 9151 0 2 0 10 10 610 610 1610 6610 20 21 GUAAAA ZNNAAA VVVVxx
+4331 9152 1 3 1 11 31 331 331 4331 4331 62 63 PKAAAA AONAAA AAAAxx
+2712 9153 0 0 2 12 12 712 712 2712 2712 24 25 IAAAAA BONAAA HHHHxx
+9268 9154 0 0 8 8 68 268 1268 4268 9268 136 137 MSAAAA CONAAA OOOOxx
+410 9155 0 2 0 10 10 410 410 410 410 20 21 UPAAAA DONAAA VVVVxx
+9411 9156 1 3 1 11 11 411 1411 4411 9411 22 23 ZXAAAA EONAAA AAAAxx
+4683 9157 1 3 3 3 83 683 683 4683 4683 166 167 DYAAAA FONAAA HHHHxx
+7072 9158 0 0 2 12 72 72 1072 2072 7072 144 145 AMAAAA GONAAA OOOOxx
+5050 9159 0 2 0 10 50 50 1050 50 5050 100 101 GMAAAA HONAAA VVVVxx
+5932 9160 0 0 2 12 32 932 1932 932 5932 64 65 EUAAAA IONAAA AAAAxx
+2756 9161 0 0 6 16 56 756 756 2756 2756 112 113 ACAAAA JONAAA HHHHxx
+9813 9162 1 1 3 13 13 813 1813 4813 9813 26 27 LNAAAA KONAAA OOOOxx
+7388 9163 0 0 8 8 88 388 1388 2388 7388 176 177 EYAAAA LONAAA VVVVxx
+2596 9164 0 0 6 16 96 596 596 2596 2596 192 193 WVAAAA MONAAA AAAAxx
+5102 9165 0 2 2 2 2 102 1102 102 5102 4 5 GOAAAA NONAAA HHHHxx
+208 9166 0 0 8 8 8 208 208 208 208 16 17 AIAAAA OONAAA OOOOxx
+86 9167 0 2 6 6 86 86 86 86 86 172 173 IDAAAA PONAAA VVVVxx
+8127 9168 1 3 7 7 27 127 127 3127 8127 54 55 PAAAAA QONAAA AAAAxx
+5154 9169 0 2 4 14 54 154 1154 154 5154 108 109 GQAAAA RONAAA HHHHxx
+4491 9170 1 3 1 11 91 491 491 4491 4491 182 183 TQAAAA SONAAA OOOOxx
+7423 9171 1 3 3 3 23 423 1423 2423 7423 46 47 NZAAAA TONAAA VVVVxx
+6441 9172 1 1 1 1 41 441 441 1441 6441 82 83 TNAAAA UONAAA AAAAxx
+2920 9173 0 0 0 0 20 920 920 2920 2920 40 41 IIAAAA VONAAA HHHHxx
+6386 9174 0 2 6 6 86 386 386 1386 6386 172 173 QLAAAA WONAAA OOOOxx
+9744 9175 0 0 4 4 44 744 1744 4744 9744 88 89 UKAAAA XONAAA VVVVxx
+2667 9176 1 3 7 7 67 667 667 2667 2667 134 135 PYAAAA YONAAA AAAAxx
+5754 9177 0 2 4 14 54 754 1754 754 5754 108 109 INAAAA ZONAAA HHHHxx
+4645 9178 1 1 5 5 45 645 645 4645 4645 90 91 RWAAAA APNAAA OOOOxx
+4327 9179 1 3 7 7 27 327 327 4327 4327 54 55 LKAAAA BPNAAA VVVVxx
+843 9180 1 3 3 3 43 843 843 843 843 86 87 LGAAAA CPNAAA AAAAxx
+4085 9181 1 1 5 5 85 85 85 4085 4085 170 171 DBAAAA DPNAAA HHHHxx
+2849 9182 1 1 9 9 49 849 849 2849 2849 98 99 PFAAAA EPNAAA OOOOxx
+5734 9183 0 2 4 14 34 734 1734 734 5734 68 69 OMAAAA FPNAAA VVVVxx
+5307 9184 1 3 7 7 7 307 1307 307 5307 14 15 DWAAAA GPNAAA AAAAxx
+8433 9185 1 1 3 13 33 433 433 3433 8433 66 67 JMAAAA HPNAAA HHHHxx
+3031 9186 1 3 1 11 31 31 1031 3031 3031 62 63 PMAAAA IPNAAA OOOOxx
+5714 9187 0 2 4 14 14 714 1714 714 5714 28 29 ULAAAA JPNAAA VVVVxx
+5969 9188 1 1 9 9 69 969 1969 969 5969 138 139 PVAAAA KPNAAA AAAAxx
+2532 9189 0 0 2 12 32 532 532 2532 2532 64 65 KTAAAA LPNAAA HHHHxx
+5219 9190 1 3 9 19 19 219 1219 219 5219 38 39 TSAAAA MPNAAA OOOOxx
+7343 9191 1 3 3 3 43 343 1343 2343 7343 86 87 LWAAAA NPNAAA VVVVxx
+9089 9192 1 1 9 9 89 89 1089 4089 9089 178 179 PLAAAA OPNAAA AAAAxx
+9337 9193 1 1 7 17 37 337 1337 4337 9337 74 75 DVAAAA PPNAAA HHHHxx
+5131 9194 1 3 1 11 31 131 1131 131 5131 62 63 JPAAAA QPNAAA OOOOxx
+6253 9195 1 1 3 13 53 253 253 1253 6253 106 107 NGAAAA RPNAAA VVVVxx
+5140 9196 0 0 0 0 40 140 1140 140 5140 80 81 SPAAAA SPNAAA AAAAxx
+2953 9197 1 1 3 13 53 953 953 2953 2953 106 107 PJAAAA TPNAAA HHHHxx
+4293 9198 1 1 3 13 93 293 293 4293 4293 186 187 DJAAAA UPNAAA OOOOxx
+9974 9199 0 2 4 14 74 974 1974 4974 9974 148 149 QTAAAA VPNAAA VVVVxx
+5061 9200 1 1 1 1 61 61 1061 61 5061 122 123 RMAAAA WPNAAA AAAAxx
+8570 9201 0 2 0 10 70 570 570 3570 8570 140 141 QRAAAA XPNAAA HHHHxx
+9504 9202 0 0 4 4 4 504 1504 4504 9504 8 9 OBAAAA YPNAAA OOOOxx
+604 9203 0 0 4 4 4 604 604 604 604 8 9 GXAAAA ZPNAAA VVVVxx
+4991 9204 1 3 1 11 91 991 991 4991 4991 182 183 ZJAAAA AQNAAA AAAAxx
+880 9205 0 0 0 0 80 880 880 880 880 160 161 WHAAAA BQNAAA HHHHxx
+3861 9206 1 1 1 1 61 861 1861 3861 3861 122 123 NSAAAA CQNAAA OOOOxx
+8262 9207 0 2 2 2 62 262 262 3262 8262 124 125 UFAAAA DQNAAA VVVVxx
+5689 9208 1 1 9 9 89 689 1689 689 5689 178 179 VKAAAA EQNAAA AAAAxx
+1793 9209 1 1 3 13 93 793 1793 1793 1793 186 187 ZQAAAA FQNAAA HHHHxx
+2661 9210 1 1 1 1 61 661 661 2661 2661 122 123 JYAAAA GQNAAA OOOOxx
+7954 9211 0 2 4 14 54 954 1954 2954 7954 108 109 YTAAAA HQNAAA VVVVxx
+1874 9212 0 2 4 14 74 874 1874 1874 1874 148 149 CUAAAA IQNAAA AAAAxx
+2982 9213 0 2 2 2 82 982 982 2982 2982 164 165 SKAAAA JQNAAA HHHHxx
+331 9214 1 3 1 11 31 331 331 331 331 62 63 TMAAAA KQNAAA OOOOxx
+5021 9215 1 1 1 1 21 21 1021 21 5021 42 43 DLAAAA LQNAAA VVVVxx
+9894 9216 0 2 4 14 94 894 1894 4894 9894 188 189 OQAAAA MQNAAA AAAAxx
+7709 9217 1 1 9 9 9 709 1709 2709 7709 18 19 NKAAAA NQNAAA HHHHxx
+4980 9218 0 0 0 0 80 980 980 4980 4980 160 161 OJAAAA OQNAAA OOOOxx
+8249 9219 1 1 9 9 49 249 249 3249 8249 98 99 HFAAAA PQNAAA VVVVxx
+7120 9220 0 0 0 0 20 120 1120 2120 7120 40 41 WNAAAA QQNAAA AAAAxx
+7464 9221 0 0 4 4 64 464 1464 2464 7464 128 129 CBAAAA RQNAAA HHHHxx
+8086 9222 0 2 6 6 86 86 86 3086 8086 172 173 AZAAAA SQNAAA OOOOxx
+3509 9223 1 1 9 9 9 509 1509 3509 3509 18 19 ZEAAAA TQNAAA VVVVxx
+3902 9224 0 2 2 2 2 902 1902 3902 3902 4 5 CUAAAA UQNAAA AAAAxx
+9907 9225 1 3 7 7 7 907 1907 4907 9907 14 15 BRAAAA VQNAAA HHHHxx
+6278 9226 0 2 8 18 78 278 278 1278 6278 156 157 MHAAAA WQNAAA OOOOxx
+9316 9227 0 0 6 16 16 316 1316 4316 9316 32 33 IUAAAA XQNAAA VVVVxx
+2824 9228 0 0 4 4 24 824 824 2824 2824 48 49 QEAAAA YQNAAA AAAAxx
+1558 9229 0 2 8 18 58 558 1558 1558 1558 116 117 YHAAAA ZQNAAA HHHHxx
+5436 9230 0 0 6 16 36 436 1436 436 5436 72 73 CBAAAA ARNAAA OOOOxx
+1161 9231 1 1 1 1 61 161 1161 1161 1161 122 123 RSAAAA BRNAAA VVVVxx
+7569 9232 1 1 9 9 69 569 1569 2569 7569 138 139 DFAAAA CRNAAA AAAAxx
+9614 9233 0 2 4 14 14 614 1614 4614 9614 28 29 UFAAAA DRNAAA HHHHxx
+6970 9234 0 2 0 10 70 970 970 1970 6970 140 141 CIAAAA ERNAAA OOOOxx
+2422 9235 0 2 2 2 22 422 422 2422 2422 44 45 EPAAAA FRNAAA VVVVxx
+8860 9236 0 0 0 0 60 860 860 3860 8860 120 121 UCAAAA GRNAAA AAAAxx
+9912 9237 0 0 2 12 12 912 1912 4912 9912 24 25 GRAAAA HRNAAA HHHHxx
+1109 9238 1 1 9 9 9 109 1109 1109 1109 18 19 RQAAAA IRNAAA OOOOxx
+3286 9239 0 2 6 6 86 286 1286 3286 3286 172 173 KWAAAA JRNAAA VVVVxx
+2277 9240 1 1 7 17 77 277 277 2277 2277 154 155 PJAAAA KRNAAA AAAAxx
+8656 9241 0 0 6 16 56 656 656 3656 8656 112 113 YUAAAA LRNAAA HHHHxx
+4656 9242 0 0 6 16 56 656 656 4656 4656 112 113 CXAAAA MRNAAA OOOOxx
+6965 9243 1 1 5 5 65 965 965 1965 6965 130 131 XHAAAA NRNAAA VVVVxx
+7591 9244 1 3 1 11 91 591 1591 2591 7591 182 183 ZFAAAA ORNAAA AAAAxx
+4883 9245 1 3 3 3 83 883 883 4883 4883 166 167 VFAAAA PRNAAA HHHHxx
+452 9246 0 0 2 12 52 452 452 452 452 104 105 KRAAAA QRNAAA OOOOxx
+4018 9247 0 2 8 18 18 18 18 4018 4018 36 37 OYAAAA RRNAAA VVVVxx
+4066 9248 0 2 6 6 66 66 66 4066 4066 132 133 KAAAAA SRNAAA AAAAxx
+6480 9249 0 0 0 0 80 480 480 1480 6480 160 161 GPAAAA TRNAAA HHHHxx
+8634 9250 0 2 4 14 34 634 634 3634 8634 68 69 CUAAAA URNAAA OOOOxx
+9387 9251 1 3 7 7 87 387 1387 4387 9387 174 175 BXAAAA VRNAAA VVVVxx
+3476 9252 0 0 6 16 76 476 1476 3476 3476 152 153 SDAAAA WRNAAA AAAAxx
+5995 9253 1 3 5 15 95 995 1995 995 5995 190 191 PWAAAA XRNAAA HHHHxx
+9677 9254 1 1 7 17 77 677 1677 4677 9677 154 155 FIAAAA YRNAAA OOOOxx
+3884 9255 0 0 4 4 84 884 1884 3884 3884 168 169 KTAAAA ZRNAAA VVVVxx
+6500 9256 0 0 0 0 0 500 500 1500 6500 0 1 AQAAAA ASNAAA AAAAxx
+7972 9257 0 0 2 12 72 972 1972 2972 7972 144 145 QUAAAA BSNAAA HHHHxx
+5281 9258 1 1 1 1 81 281 1281 281 5281 162 163 DVAAAA CSNAAA OOOOxx
+1288 9259 0 0 8 8 88 288 1288 1288 1288 176 177 OXAAAA DSNAAA VVVVxx
+4366 9260 0 2 6 6 66 366 366 4366 4366 132 133 YLAAAA ESNAAA AAAAxx
+6557 9261 1 1 7 17 57 557 557 1557 6557 114 115 FSAAAA FSNAAA HHHHxx
+7086 9262 0 2 6 6 86 86 1086 2086 7086 172 173 OMAAAA GSNAAA OOOOxx
+6588 9263 0 0 8 8 88 588 588 1588 6588 176 177 KTAAAA HSNAAA VVVVxx
+9062 9264 0 2 2 2 62 62 1062 4062 9062 124 125 OKAAAA ISNAAA AAAAxx
+9230 9265 0 2 0 10 30 230 1230 4230 9230 60 61 ARAAAA JSNAAA HHHHxx
+7672 9266 0 0 2 12 72 672 1672 2672 7672 144 145 CJAAAA KSNAAA OOOOxx
+5204 9267 0 0 4 4 4 204 1204 204 5204 8 9 ESAAAA LSNAAA VVVVxx
+2836 9268 0 0 6 16 36 836 836 2836 2836 72 73 CFAAAA MSNAAA AAAAxx
+7165 9269 1 1 5 5 65 165 1165 2165 7165 130 131 PPAAAA NSNAAA HHHHxx
+971 9270 1 3 1 11 71 971 971 971 971 142 143 JLAAAA OSNAAA OOOOxx
+3851 9271 1 3 1 11 51 851 1851 3851 3851 102 103 DSAAAA PSNAAA VVVVxx
+8593 9272 1 1 3 13 93 593 593 3593 8593 186 187 NSAAAA QSNAAA AAAAxx
+7742 9273 0 2 2 2 42 742 1742 2742 7742 84 85 ULAAAA RSNAAA HHHHxx
+2887 9274 1 3 7 7 87 887 887 2887 2887 174 175 BHAAAA SSNAAA OOOOxx
+8479 9275 1 3 9 19 79 479 479 3479 8479 158 159 DOAAAA TSNAAA VVVVxx
+9514 9276 0 2 4 14 14 514 1514 4514 9514 28 29 YBAAAA USNAAA AAAAxx
+273 9277 1 1 3 13 73 273 273 273 273 146 147 NKAAAA VSNAAA HHHHxx
+2938 9278 0 2 8 18 38 938 938 2938 2938 76 77 AJAAAA WSNAAA OOOOxx
+9793 9279 1 1 3 13 93 793 1793 4793 9793 186 187 RMAAAA XSNAAA VVVVxx
+8050 9280 0 2 0 10 50 50 50 3050 8050 100 101 QXAAAA YSNAAA AAAAxx
+6702 9281 0 2 2 2 2 702 702 1702 6702 4 5 UXAAAA ZSNAAA HHHHxx
+7290 9282 0 2 0 10 90 290 1290 2290 7290 180 181 KUAAAA ATNAAA OOOOxx
+1837 9283 1 1 7 17 37 837 1837 1837 1837 74 75 RSAAAA BTNAAA VVVVxx
+3206 9284 0 2 6 6 6 206 1206 3206 3206 12 13 ITAAAA CTNAAA AAAAxx
+4925 9285 1 1 5 5 25 925 925 4925 4925 50 51 LHAAAA DTNAAA HHHHxx
+5066 9286 0 2 6 6 66 66 1066 66 5066 132 133 WMAAAA ETNAAA OOOOxx
+3401 9287 1 1 1 1 1 401 1401 3401 3401 2 3 VAAAAA FTNAAA VVVVxx
+3474 9288 0 2 4 14 74 474 1474 3474 3474 148 149 QDAAAA GTNAAA AAAAxx
+57 9289 1 1 7 17 57 57 57 57 57 114 115 FCAAAA HTNAAA HHHHxx
+2082 9290 0 2 2 2 82 82 82 2082 2082 164 165 CCAAAA ITNAAA OOOOxx
+100 9291 0 0 0 0 0 100 100 100 100 0 1 WDAAAA JTNAAA VVVVxx
+9665 9292 1 1 5 5 65 665 1665 4665 9665 130 131 THAAAA KTNAAA AAAAxx
+8284 9293 0 0 4 4 84 284 284 3284 8284 168 169 QGAAAA LTNAAA HHHHxx
+958 9294 0 2 8 18 58 958 958 958 958 116 117 WKAAAA MTNAAA OOOOxx
+5282 9295 0 2 2 2 82 282 1282 282 5282 164 165 EVAAAA NTNAAA VVVVxx
+4257 9296 1 1 7 17 57 257 257 4257 4257 114 115 THAAAA OTNAAA AAAAxx
+3160 9297 0 0 0 0 60 160 1160 3160 3160 120 121 ORAAAA PTNAAA HHHHxx
+8449 9298 1 1 9 9 49 449 449 3449 8449 98 99 ZMAAAA QTNAAA OOOOxx
+500 9299 0 0 0 0 0 500 500 500 500 0 1 GTAAAA RTNAAA VVVVxx
+6432 9300 0 0 2 12 32 432 432 1432 6432 64 65 KNAAAA STNAAA AAAAxx
+6220 9301 0 0 0 0 20 220 220 1220 6220 40 41 GFAAAA TTNAAA HHHHxx
+7233 9302 1 1 3 13 33 233 1233 2233 7233 66 67 FSAAAA UTNAAA OOOOxx
+2723 9303 1 3 3 3 23 723 723 2723 2723 46 47 TAAAAA VTNAAA VVVVxx
+1899 9304 1 3 9 19 99 899 1899 1899 1899 198 199 BVAAAA WTNAAA AAAAxx
+7158 9305 0 2 8 18 58 158 1158 2158 7158 116 117 IPAAAA XTNAAA HHHHxx
+202 9306 0 2 2 2 2 202 202 202 202 4 5 UHAAAA YTNAAA OOOOxx
+2286 9307 0 2 6 6 86 286 286 2286 2286 172 173 YJAAAA ZTNAAA VVVVxx
+5356 9308 0 0 6 16 56 356 1356 356 5356 112 113 AYAAAA AUNAAA AAAAxx
+3809 9309 1 1 9 9 9 809 1809 3809 3809 18 19 NQAAAA BUNAAA HHHHxx
+3979 9310 1 3 9 19 79 979 1979 3979 3979 158 159 BXAAAA CUNAAA OOOOxx
+8359 9311 1 3 9 19 59 359 359 3359 8359 118 119 NJAAAA DUNAAA VVVVxx
+3479 9312 1 3 9 19 79 479 1479 3479 3479 158 159 VDAAAA EUNAAA AAAAxx
+4895 9313 1 3 5 15 95 895 895 4895 4895 190 191 HGAAAA FUNAAA HHHHxx
+6059 9314 1 3 9 19 59 59 59 1059 6059 118 119 BZAAAA GUNAAA OOOOxx
+9560 9315 0 0 0 0 60 560 1560 4560 9560 120 121 SDAAAA HUNAAA VVVVxx
+6756 9316 0 0 6 16 56 756 756 1756 6756 112 113 WZAAAA IUNAAA AAAAxx
+7504 9317 0 0 4 4 4 504 1504 2504 7504 8 9 QCAAAA JUNAAA HHHHxx
+6762 9318 0 2 2 2 62 762 762 1762 6762 124 125 CAAAAA KUNAAA OOOOxx
+5304 9319 0 0 4 4 4 304 1304 304 5304 8 9 AWAAAA LUNAAA VVVVxx
+9533 9320 1 1 3 13 33 533 1533 4533 9533 66 67 RCAAAA MUNAAA AAAAxx
+6649 9321 1 1 9 9 49 649 649 1649 6649 98 99 TVAAAA NUNAAA HHHHxx
+38 9322 0 2 8 18 38 38 38 38 38 76 77 MBAAAA OUNAAA OOOOxx
+5713 9323 1 1 3 13 13 713 1713 713 5713 26 27 TLAAAA PUNAAA VVVVxx
+3000 9324 0 0 0 0 0 0 1000 3000 3000 0 1 KLAAAA QUNAAA AAAAxx
+3738 9325 0 2 8 18 38 738 1738 3738 3738 76 77 UNAAAA RUNAAA HHHHxx
+3327 9326 1 3 7 7 27 327 1327 3327 3327 54 55 ZXAAAA SUNAAA OOOOxx
+3922 9327 0 2 2 2 22 922 1922 3922 3922 44 45 WUAAAA TUNAAA VVVVxx
+9245 9328 1 1 5 5 45 245 1245 4245 9245 90 91 PRAAAA UUNAAA AAAAxx
+2172 9329 0 0 2 12 72 172 172 2172 2172 144 145 OFAAAA VUNAAA HHHHxx
+7128 9330 0 0 8 8 28 128 1128 2128 7128 56 57 EOAAAA WUNAAA OOOOxx
+1195 9331 1 3 5 15 95 195 1195 1195 1195 190 191 ZTAAAA XUNAAA VVVVxx
+8445 9332 1 1 5 5 45 445 445 3445 8445 90 91 VMAAAA YUNAAA AAAAxx
+8638 9333 0 2 8 18 38 638 638 3638 8638 76 77 GUAAAA ZUNAAA HHHHxx
+1249 9334 1 1 9 9 49 249 1249 1249 1249 98 99 BWAAAA AVNAAA OOOOxx
+8659 9335 1 3 9 19 59 659 659 3659 8659 118 119 BVAAAA BVNAAA VVVVxx
+3556 9336 0 0 6 16 56 556 1556 3556 3556 112 113 UGAAAA CVNAAA AAAAxx
+3347 9337 1 3 7 7 47 347 1347 3347 3347 94 95 TYAAAA DVNAAA HHHHxx
+3260 9338 0 0 0 0 60 260 1260 3260 3260 120 121 KVAAAA EVNAAA OOOOxx
+5139 9339 1 3 9 19 39 139 1139 139 5139 78 79 RPAAAA FVNAAA VVVVxx
+9991 9340 1 3 1 11 91 991 1991 4991 9991 182 183 HUAAAA GVNAAA AAAAxx
+5499 9341 1 3 9 19 99 499 1499 499 5499 198 199 NDAAAA HVNAAA HHHHxx
+8082 9342 0 2 2 2 82 82 82 3082 8082 164 165 WYAAAA IVNAAA OOOOxx
+1640 9343 0 0 0 0 40 640 1640 1640 1640 80 81 CLAAAA JVNAAA VVVVxx
+8726 9344 0 2 6 6 26 726 726 3726 8726 52 53 QXAAAA KVNAAA AAAAxx
+2339 9345 1 3 9 19 39 339 339 2339 2339 78 79 ZLAAAA LVNAAA HHHHxx
+2601 9346 1 1 1 1 1 601 601 2601 2601 2 3 BWAAAA MVNAAA OOOOxx
+9940 9347 0 0 0 0 40 940 1940 4940 9940 80 81 ISAAAA NVNAAA VVVVxx
+4185 9348 1 1 5 5 85 185 185 4185 4185 170 171 ZEAAAA OVNAAA AAAAxx
+9546 9349 0 2 6 6 46 546 1546 4546 9546 92 93 EDAAAA PVNAAA HHHHxx
+5218 9350 0 2 8 18 18 218 1218 218 5218 36 37 SSAAAA QVNAAA OOOOxx
+4374 9351 0 2 4 14 74 374 374 4374 4374 148 149 GMAAAA RVNAAA VVVVxx
+288 9352 0 0 8 8 88 288 288 288 288 176 177 CLAAAA SVNAAA AAAAxx
+7445 9353 1 1 5 5 45 445 1445 2445 7445 90 91 JAAAAA TVNAAA HHHHxx
+1710 9354 0 2 0 10 10 710 1710 1710 1710 20 21 UNAAAA UVNAAA OOOOxx
+6409 9355 1 1 9 9 9 409 409 1409 6409 18 19 NMAAAA VVNAAA VVVVxx
+7982 9356 0 2 2 2 82 982 1982 2982 7982 164 165 AVAAAA WVNAAA AAAAxx
+4950 9357 0 2 0 10 50 950 950 4950 4950 100 101 KIAAAA XVNAAA HHHHxx
+9242 9358 0 2 2 2 42 242 1242 4242 9242 84 85 MRAAAA YVNAAA OOOOxx
+3272 9359 0 0 2 12 72 272 1272 3272 3272 144 145 WVAAAA ZVNAAA VVVVxx
+739 9360 1 3 9 19 39 739 739 739 739 78 79 LCAAAA AWNAAA AAAAxx
+5526 9361 0 2 6 6 26 526 1526 526 5526 52 53 OEAAAA BWNAAA HHHHxx
+8189 9362 1 1 9 9 89 189 189 3189 8189 178 179 ZCAAAA CWNAAA OOOOxx
+9106 9363 0 2 6 6 6 106 1106 4106 9106 12 13 GMAAAA DWNAAA VVVVxx
+9775 9364 1 3 5 15 75 775 1775 4775 9775 150 151 ZLAAAA EWNAAA AAAAxx
+4643 9365 1 3 3 3 43 643 643 4643 4643 86 87 PWAAAA FWNAAA HHHHxx
+8396 9366 0 0 6 16 96 396 396 3396 8396 192 193 YKAAAA GWNAAA OOOOxx
+3255 9367 1 3 5 15 55 255 1255 3255 3255 110 111 FVAAAA HWNAAA VVVVxx
+301 9368 1 1 1 1 1 301 301 301 301 2 3 PLAAAA IWNAAA AAAAxx
+6014 9369 0 2 4 14 14 14 14 1014 6014 28 29 IXAAAA JWNAAA HHHHxx
+6046 9370 0 2 6 6 46 46 46 1046 6046 92 93 OYAAAA KWNAAA OOOOxx
+984 9371 0 0 4 4 84 984 984 984 984 168 169 WLAAAA LWNAAA VVVVxx
+2420 9372 0 0 0 0 20 420 420 2420 2420 40 41 CPAAAA MWNAAA AAAAxx
+2922 9373 0 2 2 2 22 922 922 2922 2922 44 45 KIAAAA NWNAAA HHHHxx
+2317 9374 1 1 7 17 17 317 317 2317 2317 34 35 DLAAAA OWNAAA OOOOxx
+7332 9375 0 0 2 12 32 332 1332 2332 7332 64 65 AWAAAA PWNAAA VVVVxx
+6451 9376 1 3 1 11 51 451 451 1451 6451 102 103 DOAAAA QWNAAA AAAAxx
+2589 9377 1 1 9 9 89 589 589 2589 2589 178 179 PVAAAA RWNAAA HHHHxx
+4333 9378 1 1 3 13 33 333 333 4333 4333 66 67 RKAAAA SWNAAA OOOOxx
+8650 9379 0 2 0 10 50 650 650 3650 8650 100 101 SUAAAA TWNAAA VVVVxx
+6856 9380 0 0 6 16 56 856 856 1856 6856 112 113 SDAAAA UWNAAA AAAAxx
+4194 9381 0 2 4 14 94 194 194 4194 4194 188 189 IFAAAA VWNAAA HHHHxx
+6246 9382 0 2 6 6 46 246 246 1246 6246 92 93 GGAAAA WWNAAA OOOOxx
+4371 9383 1 3 1 11 71 371 371 4371 4371 142 143 DMAAAA XWNAAA VVVVxx
+1388 9384 0 0 8 8 88 388 1388 1388 1388 176 177 KBAAAA YWNAAA AAAAxx
+1056 9385 0 0 6 16 56 56 1056 1056 1056 112 113 QOAAAA ZWNAAA HHHHxx
+6041 9386 1 1 1 1 41 41 41 1041 6041 82 83 JYAAAA AXNAAA OOOOxx
+6153 9387 1 1 3 13 53 153 153 1153 6153 106 107 RCAAAA BXNAAA VVVVxx
+8450 9388 0 2 0 10 50 450 450 3450 8450 100 101 ANAAAA CXNAAA AAAAxx
+3469 9389 1 1 9 9 69 469 1469 3469 3469 138 139 LDAAAA DXNAAA HHHHxx
+5226 9390 0 2 6 6 26 226 1226 226 5226 52 53 ATAAAA EXNAAA OOOOxx
+8112 9391 0 0 2 12 12 112 112 3112 8112 24 25 AAAAAA FXNAAA VVVVxx
+647 9392 1 3 7 7 47 647 647 647 647 94 95 XYAAAA GXNAAA AAAAxx
+2567 9393 1 3 7 7 67 567 567 2567 2567 134 135 TUAAAA HXNAAA HHHHxx
+9064 9394 0 0 4 4 64 64 1064 4064 9064 128 129 QKAAAA IXNAAA OOOOxx
+5161 9395 1 1 1 1 61 161 1161 161 5161 122 123 NQAAAA JXNAAA VVVVxx
+5260 9396 0 0 0 0 60 260 1260 260 5260 120 121 IUAAAA KXNAAA AAAAxx
+8988 9397 0 0 8 8 88 988 988 3988 8988 176 177 SHAAAA LXNAAA HHHHxx
+9678 9398 0 2 8 18 78 678 1678 4678 9678 156 157 GIAAAA MXNAAA OOOOxx
+6853 9399 1 1 3 13 53 853 853 1853 6853 106 107 PDAAAA NXNAAA VVVVxx
+5294 9400 0 2 4 14 94 294 1294 294 5294 188 189 QVAAAA OXNAAA AAAAxx
+9864 9401 0 0 4 4 64 864 1864 4864 9864 128 129 KPAAAA PXNAAA HHHHxx
+8702 9402 0 2 2 2 2 702 702 3702 8702 4 5 SWAAAA QXNAAA OOOOxx
+1132 9403 0 0 2 12 32 132 1132 1132 1132 64 65 ORAAAA RXNAAA VVVVxx
+1524 9404 0 0 4 4 24 524 1524 1524 1524 48 49 QGAAAA SXNAAA AAAAxx
+4560 9405 0 0 0 0 60 560 560 4560 4560 120 121 KTAAAA TXNAAA HHHHxx
+2137 9406 1 1 7 17 37 137 137 2137 2137 74 75 FEAAAA UXNAAA OOOOxx
+3283 9407 1 3 3 3 83 283 1283 3283 3283 166 167 HWAAAA VXNAAA VVVVxx
+3377 9408 1 1 7 17 77 377 1377 3377 3377 154 155 XZAAAA WXNAAA AAAAxx
+2267 9409 1 3 7 7 67 267 267 2267 2267 134 135 FJAAAA XXNAAA HHHHxx
+8987 9410 1 3 7 7 87 987 987 3987 8987 174 175 RHAAAA YXNAAA OOOOxx
+6709 9411 1 1 9 9 9 709 709 1709 6709 18 19 BYAAAA ZXNAAA VVVVxx
+8059 9412 1 3 9 19 59 59 59 3059 8059 118 119 ZXAAAA AYNAAA AAAAxx
+3402 9413 0 2 2 2 2 402 1402 3402 3402 4 5 WAAAAA BYNAAA HHHHxx
+6443 9414 1 3 3 3 43 443 443 1443 6443 86 87 VNAAAA CYNAAA OOOOxx
+8858 9415 0 2 8 18 58 858 858 3858 8858 116 117 SCAAAA DYNAAA VVVVxx
+3974 9416 0 2 4 14 74 974 1974 3974 3974 148 149 WWAAAA EYNAAA AAAAxx
+3521 9417 1 1 1 1 21 521 1521 3521 3521 42 43 LFAAAA FYNAAA HHHHxx
+9509 9418 1 1 9 9 9 509 1509 4509 9509 18 19 TBAAAA GYNAAA OOOOxx
+5442 9419 0 2 2 2 42 442 1442 442 5442 84 85 IBAAAA HYNAAA VVVVxx
+8968 9420 0 0 8 8 68 968 968 3968 8968 136 137 YGAAAA IYNAAA AAAAxx
+333 9421 1 1 3 13 33 333 333 333 333 66 67 VMAAAA JYNAAA HHHHxx
+952 9422 0 0 2 12 52 952 952 952 952 104 105 QKAAAA KYNAAA OOOOxx
+7482 9423 0 2 2 2 82 482 1482 2482 7482 164 165 UBAAAA LYNAAA VVVVxx
+1486 9424 0 2 6 6 86 486 1486 1486 1486 172 173 EFAAAA MYNAAA AAAAxx
+1815 9425 1 3 5 15 15 815 1815 1815 1815 30 31 VRAAAA NYNAAA HHHHxx
+7937 9426 1 1 7 17 37 937 1937 2937 7937 74 75 HTAAAA OYNAAA OOOOxx
+1436 9427 0 0 6 16 36 436 1436 1436 1436 72 73 GDAAAA PYNAAA VVVVxx
+3470 9428 0 2 0 10 70 470 1470 3470 3470 140 141 MDAAAA QYNAAA AAAAxx
+8195 9429 1 3 5 15 95 195 195 3195 8195 190 191 FDAAAA RYNAAA HHHHxx
+6906 9430 0 2 6 6 6 906 906 1906 6906 12 13 QFAAAA SYNAAA OOOOxx
+2539 9431 1 3 9 19 39 539 539 2539 2539 78 79 RTAAAA TYNAAA VVVVxx
+5988 9432 0 0 8 8 88 988 1988 988 5988 176 177 IWAAAA UYNAAA AAAAxx
+8908 9433 0 0 8 8 8 908 908 3908 8908 16 17 QEAAAA VYNAAA HHHHxx
+2319 9434 1 3 9 19 19 319 319 2319 2319 38 39 FLAAAA WYNAAA OOOOxx
+3263 9435 1 3 3 3 63 263 1263 3263 3263 126 127 NVAAAA XYNAAA VVVVxx
+4039 9436 1 3 9 19 39 39 39 4039 4039 78 79 JZAAAA YYNAAA AAAAxx
+6373 9437 1 1 3 13 73 373 373 1373 6373 146 147 DLAAAA ZYNAAA HHHHxx
+1168 9438 0 0 8 8 68 168 1168 1168 1168 136 137 YSAAAA AZNAAA OOOOxx
+8338 9439 0 2 8 18 38 338 338 3338 8338 76 77 SIAAAA BZNAAA VVVVxx
+1172 9440 0 0 2 12 72 172 1172 1172 1172 144 145 CTAAAA CZNAAA AAAAxx
+200 9441 0 0 0 0 0 200 200 200 200 0 1 SHAAAA DZNAAA HHHHxx
+6355 9442 1 3 5 15 55 355 355 1355 6355 110 111 LKAAAA EZNAAA OOOOxx
+7768 9443 0 0 8 8 68 768 1768 2768 7768 136 137 UMAAAA FZNAAA VVVVxx
+25 9444 1 1 5 5 25 25 25 25 25 50 51 ZAAAAA GZNAAA AAAAxx
+7144 9445 0 0 4 4 44 144 1144 2144 7144 88 89 UOAAAA HZNAAA HHHHxx
+8671 9446 1 3 1 11 71 671 671 3671 8671 142 143 NVAAAA IZNAAA OOOOxx
+9163 9447 1 3 3 3 63 163 1163 4163 9163 126 127 LOAAAA JZNAAA VVVVxx
+8889 9448 1 1 9 9 89 889 889 3889 8889 178 179 XDAAAA KZNAAA AAAAxx
+5950 9449 0 2 0 10 50 950 1950 950 5950 100 101 WUAAAA LZNAAA HHHHxx
+6163 9450 1 3 3 3 63 163 163 1163 6163 126 127 BDAAAA MZNAAA OOOOxx
+8119 9451 1 3 9 19 19 119 119 3119 8119 38 39 HAAAAA NZNAAA VVVVxx
+1416 9452 0 0 6 16 16 416 1416 1416 1416 32 33 MCAAAA OZNAAA AAAAxx
+4132 9453 0 0 2 12 32 132 132 4132 4132 64 65 YCAAAA PZNAAA HHHHxx
+2294 9454 0 2 4 14 94 294 294 2294 2294 188 189 GKAAAA QZNAAA OOOOxx
+9094 9455 0 2 4 14 94 94 1094 4094 9094 188 189 ULAAAA RZNAAA VVVVxx
+4168 9456 0 0 8 8 68 168 168 4168 4168 136 137 IEAAAA SZNAAA AAAAxx
+9108 9457 0 0 8 8 8 108 1108 4108 9108 16 17 IMAAAA TZNAAA HHHHxx
+5706 9458 0 2 6 6 6 706 1706 706 5706 12 13 MLAAAA UZNAAA OOOOxx
+2231 9459 1 3 1 11 31 231 231 2231 2231 62 63 VHAAAA VZNAAA VVVVxx
+2173 9460 1 1 3 13 73 173 173 2173 2173 146 147 PFAAAA WZNAAA AAAAxx
+90 9461 0 2 0 10 90 90 90 90 90 180 181 MDAAAA XZNAAA HHHHxx
+9996 9462 0 0 6 16 96 996 1996 4996 9996 192 193 MUAAAA YZNAAA OOOOxx
+330 9463 0 2 0 10 30 330 330 330 330 60 61 SMAAAA ZZNAAA VVVVxx
+2052 9464 0 0 2 12 52 52 52 2052 2052 104 105 YAAAAA AAOAAA AAAAxx
+1093 9465 1 1 3 13 93 93 1093 1093 1093 186 187 BQAAAA BAOAAA HHHHxx
+5817 9466 1 1 7 17 17 817 1817 817 5817 34 35 TPAAAA CAOAAA OOOOxx
+1559 9467 1 3 9 19 59 559 1559 1559 1559 118 119 ZHAAAA DAOAAA VVVVxx
+8405 9468 1 1 5 5 5 405 405 3405 8405 10 11 HLAAAA EAOAAA AAAAxx
+9962 9469 0 2 2 2 62 962 1962 4962 9962 124 125 ETAAAA FAOAAA HHHHxx
+9461 9470 1 1 1 1 61 461 1461 4461 9461 122 123 XZAAAA GAOAAA OOOOxx
+3028 9471 0 0 8 8 28 28 1028 3028 3028 56 57 MMAAAA HAOAAA VVVVxx
+6814 9472 0 2 4 14 14 814 814 1814 6814 28 29 CCAAAA IAOAAA AAAAxx
+9587 9473 1 3 7 7 87 587 1587 4587 9587 174 175 TEAAAA JAOAAA HHHHxx
+6863 9474 1 3 3 3 63 863 863 1863 6863 126 127 ZDAAAA KAOAAA OOOOxx
+4963 9475 1 3 3 3 63 963 963 4963 4963 126 127 XIAAAA LAOAAA VVVVxx
+7811 9476 1 3 1 11 11 811 1811 2811 7811 22 23 LOAAAA MAOAAA AAAAxx
+7608 9477 0 0 8 8 8 608 1608 2608 7608 16 17 QGAAAA NAOAAA HHHHxx
+5321 9478 1 1 1 1 21 321 1321 321 5321 42 43 RWAAAA OAOAAA OOOOxx
+9971 9479 1 3 1 11 71 971 1971 4971 9971 142 143 NTAAAA PAOAAA VVVVxx
+6161 9480 1 1 1 1 61 161 161 1161 6161 122 123 ZCAAAA QAOAAA AAAAxx
+2181 9481 1 1 1 1 81 181 181 2181 2181 162 163 XFAAAA RAOAAA HHHHxx
+3828 9482 0 0 8 8 28 828 1828 3828 3828 56 57 GRAAAA SAOAAA OOOOxx
+348 9483 0 0 8 8 48 348 348 348 348 96 97 KNAAAA TAOAAA VVVVxx
+5459 9484 1 3 9 19 59 459 1459 459 5459 118 119 ZBAAAA UAOAAA AAAAxx
+9406 9485 0 2 6 6 6 406 1406 4406 9406 12 13 UXAAAA VAOAAA HHHHxx
+9852 9486 0 0 2 12 52 852 1852 4852 9852 104 105 YOAAAA WAOAAA OOOOxx
+3095 9487 1 3 5 15 95 95 1095 3095 3095 190 191 BPAAAA XAOAAA VVVVxx
+5597 9488 1 1 7 17 97 597 1597 597 5597 194 195 HHAAAA YAOAAA AAAAxx
+8841 9489 1 1 1 1 41 841 841 3841 8841 82 83 BCAAAA ZAOAAA HHHHxx
+3536 9490 0 0 6 16 36 536 1536 3536 3536 72 73 AGAAAA ABOAAA OOOOxx
+4009 9491 1 1 9 9 9 9 9 4009 4009 18 19 FYAAAA BBOAAA VVVVxx
+7366 9492 0 2 6 6 66 366 1366 2366 7366 132 133 IXAAAA CBOAAA AAAAxx
+7327 9493 1 3 7 7 27 327 1327 2327 7327 54 55 VVAAAA DBOAAA HHHHxx
+1613 9494 1 1 3 13 13 613 1613 1613 1613 26 27 BKAAAA EBOAAA OOOOxx
+8619 9495 1 3 9 19 19 619 619 3619 8619 38 39 NTAAAA FBOAAA VVVVxx
+4880 9496 0 0 0 0 80 880 880 4880 4880 160 161 SFAAAA GBOAAA AAAAxx
+1552 9497 0 0 2 12 52 552 1552 1552 1552 104 105 SHAAAA HBOAAA HHHHxx
+7636 9498 0 0 6 16 36 636 1636 2636 7636 72 73 SHAAAA IBOAAA OOOOxx
+8397 9499 1 1 7 17 97 397 397 3397 8397 194 195 ZKAAAA JBOAAA VVVVxx
+6224 9500 0 0 4 4 24 224 224 1224 6224 48 49 KFAAAA KBOAAA AAAAxx
+9102 9501 0 2 2 2 2 102 1102 4102 9102 4 5 CMAAAA LBOAAA HHHHxx
+7906 9502 0 2 6 6 6 906 1906 2906 7906 12 13 CSAAAA MBOAAA OOOOxx
+9467 9503 1 3 7 7 67 467 1467 4467 9467 134 135 DAAAAA NBOAAA VVVVxx
+828 9504 0 0 8 8 28 828 828 828 828 56 57 WFAAAA OBOAAA AAAAxx
+9585 9505 1 1 5 5 85 585 1585 4585 9585 170 171 REAAAA PBOAAA HHHHxx
+925 9506 1 1 5 5 25 925 925 925 925 50 51 PJAAAA QBOAAA OOOOxx
+7375 9507 1 3 5 15 75 375 1375 2375 7375 150 151 RXAAAA RBOAAA VVVVxx
+4027 9508 1 3 7 7 27 27 27 4027 4027 54 55 XYAAAA SBOAAA AAAAxx
+766 9509 0 2 6 6 66 766 766 766 766 132 133 MDAAAA TBOAAA HHHHxx
+5633 9510 1 1 3 13 33 633 1633 633 5633 66 67 RIAAAA UBOAAA OOOOxx
+5648 9511 0 0 8 8 48 648 1648 648 5648 96 97 GJAAAA VBOAAA VVVVxx
+148 9512 0 0 8 8 48 148 148 148 148 96 97 SFAAAA WBOAAA AAAAxx
+2072 9513 0 0 2 12 72 72 72 2072 2072 144 145 SBAAAA XBOAAA HHHHxx
+431 9514 1 3 1 11 31 431 431 431 431 62 63 PQAAAA YBOAAA OOOOxx
+1711 9515 1 3 1 11 11 711 1711 1711 1711 22 23 VNAAAA ZBOAAA VVVVxx
+9378 9516 0 2 8 18 78 378 1378 4378 9378 156 157 SWAAAA ACOAAA AAAAxx
+6776 9517 0 0 6 16 76 776 776 1776 6776 152 153 QAAAAA BCOAAA HHHHxx
+6842 9518 0 2 2 2 42 842 842 1842 6842 84 85 EDAAAA CCOAAA OOOOxx
+2656 9519 0 0 6 16 56 656 656 2656 2656 112 113 EYAAAA DCOAAA VVVVxx
+3116 9520 0 0 6 16 16 116 1116 3116 3116 32 33 WPAAAA ECOAAA AAAAxx
+7904 9521 0 0 4 4 4 904 1904 2904 7904 8 9 ASAAAA FCOAAA HHHHxx
+3529 9522 1 1 9 9 29 529 1529 3529 3529 58 59 TFAAAA GCOAAA OOOOxx
+3240 9523 0 0 0 0 40 240 1240 3240 3240 80 81 QUAAAA HCOAAA VVVVxx
+5801 9524 1 1 1 1 1 801 1801 801 5801 2 3 DPAAAA ICOAAA AAAAxx
+4090 9525 0 2 0 10 90 90 90 4090 4090 180 181 IBAAAA JCOAAA HHHHxx
+7687 9526 1 3 7 7 87 687 1687 2687 7687 174 175 RJAAAA KCOAAA OOOOxx
+9711 9527 1 3 1 11 11 711 1711 4711 9711 22 23 NJAAAA LCOAAA VVVVxx
+4760 9528 0 0 0 0 60 760 760 4760 4760 120 121 CBAAAA MCOAAA AAAAxx
+5524 9529 0 0 4 4 24 524 1524 524 5524 48 49 MEAAAA NCOAAA HHHHxx
+2251 9530 1 3 1 11 51 251 251 2251 2251 102 103 PIAAAA OCOAAA OOOOxx
+1511 9531 1 3 1 11 11 511 1511 1511 1511 22 23 DGAAAA PCOAAA VVVVxx
+5991 9532 1 3 1 11 91 991 1991 991 5991 182 183 LWAAAA QCOAAA AAAAxx
+7808 9533 0 0 8 8 8 808 1808 2808 7808 16 17 IOAAAA RCOAAA HHHHxx
+8708 9534 0 0 8 8 8 708 708 3708 8708 16 17 YWAAAA SCOAAA OOOOxx
+8939 9535 1 3 9 19 39 939 939 3939 8939 78 79 VFAAAA TCOAAA VVVVxx
+4295 9536 1 3 5 15 95 295 295 4295 4295 190 191 FJAAAA UCOAAA AAAAxx
+5905 9537 1 1 5 5 5 905 1905 905 5905 10 11 DTAAAA VCOAAA HHHHxx
+2649 9538 1 1 9 9 49 649 649 2649 2649 98 99 XXAAAA WCOAAA OOOOxx
+2347 9539 1 3 7 7 47 347 347 2347 2347 94 95 HMAAAA XCOAAA VVVVxx
+6339 9540 1 3 9 19 39 339 339 1339 6339 78 79 VJAAAA YCOAAA AAAAxx
+292 9541 0 0 2 12 92 292 292 292 292 184 185 GLAAAA ZCOAAA HHHHxx
+9314 9542 0 2 4 14 14 314 1314 4314 9314 28 29 GUAAAA ADOAAA OOOOxx
+6893 9543 1 1 3 13 93 893 893 1893 6893 186 187 DFAAAA BDOAAA VVVVxx
+3970 9544 0 2 0 10 70 970 1970 3970 3970 140 141 SWAAAA CDOAAA AAAAxx
+1652 9545 0 0 2 12 52 652 1652 1652 1652 104 105 OLAAAA DDOAAA HHHHxx
+4326 9546 0 2 6 6 26 326 326 4326 4326 52 53 KKAAAA EDOAAA OOOOxx
+7881 9547 1 1 1 1 81 881 1881 2881 7881 162 163 DRAAAA FDOAAA VVVVxx
+5291 9548 1 3 1 11 91 291 1291 291 5291 182 183 NVAAAA GDOAAA AAAAxx
+957 9549 1 1 7 17 57 957 957 957 957 114 115 VKAAAA HDOAAA HHHHxx
+2313 9550 1 1 3 13 13 313 313 2313 2313 26 27 ZKAAAA IDOAAA OOOOxx
+5463 9551 1 3 3 3 63 463 1463 463 5463 126 127 DCAAAA JDOAAA VVVVxx
+1268 9552 0 0 8 8 68 268 1268 1268 1268 136 137 UWAAAA KDOAAA AAAAxx
+5028 9553 0 0 8 8 28 28 1028 28 5028 56 57 KLAAAA LDOAAA HHHHxx
+656 9554 0 0 6 16 56 656 656 656 656 112 113 GZAAAA MDOAAA OOOOxx
+9274 9555 0 2 4 14 74 274 1274 4274 9274 148 149 SSAAAA NDOAAA VVVVxx
+8217 9556 1 1 7 17 17 217 217 3217 8217 34 35 BEAAAA ODOAAA AAAAxx
+2175 9557 1 3 5 15 75 175 175 2175 2175 150 151 RFAAAA PDOAAA HHHHxx
+6028 9558 0 0 8 8 28 28 28 1028 6028 56 57 WXAAAA QDOAAA OOOOxx
+7584 9559 0 0 4 4 84 584 1584 2584 7584 168 169 SFAAAA RDOAAA VVVVxx
+4114 9560 0 2 4 14 14 114 114 4114 4114 28 29 GCAAAA SDOAAA AAAAxx
+8894 9561 0 2 4 14 94 894 894 3894 8894 188 189 CEAAAA TDOAAA HHHHxx
+781 9562 1 1 1 1 81 781 781 781 781 162 163 BEAAAA UDOAAA OOOOxx
+133 9563 1 1 3 13 33 133 133 133 133 66 67 DFAAAA VDOAAA VVVVxx
+7572 9564 0 0 2 12 72 572 1572 2572 7572 144 145 GFAAAA WDOAAA AAAAxx
+8514 9565 0 2 4 14 14 514 514 3514 8514 28 29 MPAAAA XDOAAA HHHHxx
+3352 9566 0 0 2 12 52 352 1352 3352 3352 104 105 YYAAAA YDOAAA OOOOxx
+8098 9567 0 2 8 18 98 98 98 3098 8098 196 197 MZAAAA ZDOAAA VVVVxx
+9116 9568 0 0 6 16 16 116 1116 4116 9116 32 33 QMAAAA AEOAAA AAAAxx
+9444 9569 0 0 4 4 44 444 1444 4444 9444 88 89 GZAAAA BEOAAA HHHHxx
+2590 9570 0 2 0 10 90 590 590 2590 2590 180 181 QVAAAA CEOAAA OOOOxx
+7302 9571 0 2 2 2 2 302 1302 2302 7302 4 5 WUAAAA DEOAAA VVVVxx
+7444 9572 0 0 4 4 44 444 1444 2444 7444 88 89 IAAAAA EEOAAA AAAAxx
+8748 9573 0 0 8 8 48 748 748 3748 8748 96 97 MYAAAA FEOAAA HHHHxx
+7615 9574 1 3 5 15 15 615 1615 2615 7615 30 31 XGAAAA GEOAAA OOOOxx
+6090 9575 0 2 0 10 90 90 90 1090 6090 180 181 GAAAAA HEOAAA VVVVxx
+1529 9576 1 1 9 9 29 529 1529 1529 1529 58 59 VGAAAA IEOAAA AAAAxx
+9398 9577 0 2 8 18 98 398 1398 4398 9398 196 197 MXAAAA JEOAAA HHHHxx
+6114 9578 0 2 4 14 14 114 114 1114 6114 28 29 EBAAAA KEOAAA OOOOxx
+2736 9579 0 0 6 16 36 736 736 2736 2736 72 73 GBAAAA LEOAAA VVVVxx
+468 9580 0 0 8 8 68 468 468 468 468 136 137 ASAAAA MEOAAA AAAAxx
+1487 9581 1 3 7 7 87 487 1487 1487 1487 174 175 FFAAAA NEOAAA HHHHxx
+4784 9582 0 0 4 4 84 784 784 4784 4784 168 169 ACAAAA OEOAAA OOOOxx
+6731 9583 1 3 1 11 31 731 731 1731 6731 62 63 XYAAAA PEOAAA VVVVxx
+3328 9584 0 0 8 8 28 328 1328 3328 3328 56 57 AYAAAA QEOAAA AAAAxx
+6891 9585 1 3 1 11 91 891 891 1891 6891 182 183 BFAAAA REOAAA HHHHxx
+8039 9586 1 3 9 19 39 39 39 3039 8039 78 79 FXAAAA SEOAAA OOOOxx
+4064 9587 0 0 4 4 64 64 64 4064 4064 128 129 IAAAAA TEOAAA VVVVxx
+542 9588 0 2 2 2 42 542 542 542 542 84 85 WUAAAA UEOAAA AAAAxx
+1039 9589 1 3 9 19 39 39 1039 1039 1039 78 79 ZNAAAA VEOAAA HHHHxx
+5603 9590 1 3 3 3 3 603 1603 603 5603 6 7 NHAAAA WEOAAA OOOOxx
+6641 9591 1 1 1 1 41 641 641 1641 6641 82 83 LVAAAA XEOAAA VVVVxx
+6307 9592 1 3 7 7 7 307 307 1307 6307 14 15 PIAAAA YEOAAA AAAAxx
+5354 9593 0 2 4 14 54 354 1354 354 5354 108 109 YXAAAA ZEOAAA HHHHxx
+7878 9594 0 2 8 18 78 878 1878 2878 7878 156 157 ARAAAA AFOAAA OOOOxx
+6391 9595 1 3 1 11 91 391 391 1391 6391 182 183 VLAAAA BFOAAA VVVVxx
+4575 9596 1 3 5 15 75 575 575 4575 4575 150 151 ZTAAAA CFOAAA AAAAxx
+6644 9597 0 0 4 4 44 644 644 1644 6644 88 89 OVAAAA DFOAAA HHHHxx
+5207 9598 1 3 7 7 7 207 1207 207 5207 14 15 HSAAAA EFOAAA OOOOxx
+1736 9599 0 0 6 16 36 736 1736 1736 1736 72 73 UOAAAA FFOAAA VVVVxx
+3547 9600 1 3 7 7 47 547 1547 3547 3547 94 95 LGAAAA GFOAAA AAAAxx
+6647 9601 1 3 7 7 47 647 647 1647 6647 94 95 RVAAAA HFOAAA HHHHxx
+4107 9602 1 3 7 7 7 107 107 4107 4107 14 15 ZBAAAA IFOAAA OOOOxx
+8125 9603 1 1 5 5 25 125 125 3125 8125 50 51 NAAAAA JFOAAA VVVVxx
+9223 9604 1 3 3 3 23 223 1223 4223 9223 46 47 TQAAAA KFOAAA AAAAxx
+6903 9605 1 3 3 3 3 903 903 1903 6903 6 7 NFAAAA LFOAAA HHHHxx
+3639 9606 1 3 9 19 39 639 1639 3639 3639 78 79 ZJAAAA MFOAAA OOOOxx
+9606 9607 0 2 6 6 6 606 1606 4606 9606 12 13 MFAAAA NFOAAA VVVVxx
+3232 9608 0 0 2 12 32 232 1232 3232 3232 64 65 IUAAAA OFOAAA AAAAxx
+2063 9609 1 3 3 3 63 63 63 2063 2063 126 127 JBAAAA PFOAAA HHHHxx
+3731 9610 1 3 1 11 31 731 1731 3731 3731 62 63 NNAAAA QFOAAA OOOOxx
+2558 9611 0 2 8 18 58 558 558 2558 2558 116 117 KUAAAA RFOAAA VVVVxx
+2357 9612 1 1 7 17 57 357 357 2357 2357 114 115 RMAAAA SFOAAA AAAAxx
+6008 9613 0 0 8 8 8 8 8 1008 6008 16 17 CXAAAA TFOAAA HHHHxx
+8246 9614 0 2 6 6 46 246 246 3246 8246 92 93 EFAAAA UFOAAA OOOOxx
+8220 9615 0 0 0 0 20 220 220 3220 8220 40 41 EEAAAA VFOAAA VVVVxx
+1075 9616 1 3 5 15 75 75 1075 1075 1075 150 151 JPAAAA WFOAAA AAAAxx
+2410 9617 0 2 0 10 10 410 410 2410 2410 20 21 SOAAAA XFOAAA HHHHxx
+3253 9618 1 1 3 13 53 253 1253 3253 3253 106 107 DVAAAA YFOAAA OOOOxx
+4370 9619 0 2 0 10 70 370 370 4370 4370 140 141 CMAAAA ZFOAAA VVVVxx
+8426 9620 0 2 6 6 26 426 426 3426 8426 52 53 CMAAAA AGOAAA AAAAxx
+2262 9621 0 2 2 2 62 262 262 2262 2262 124 125 AJAAAA BGOAAA HHHHxx
+4149 9622 1 1 9 9 49 149 149 4149 4149 98 99 PDAAAA CGOAAA OOOOxx
+2732 9623 0 0 2 12 32 732 732 2732 2732 64 65 CBAAAA DGOAAA VVVVxx
+8606 9624 0 2 6 6 6 606 606 3606 8606 12 13 ATAAAA EGOAAA AAAAxx
+6311 9625 1 3 1 11 11 311 311 1311 6311 22 23 TIAAAA FGOAAA HHHHxx
+7223 9626 1 3 3 3 23 223 1223 2223 7223 46 47 VRAAAA GGOAAA OOOOxx
+3054 9627 0 2 4 14 54 54 1054 3054 3054 108 109 MNAAAA HGOAAA VVVVxx
+3952 9628 0 0 2 12 52 952 1952 3952 3952 104 105 AWAAAA IGOAAA AAAAxx
+8252 9629 0 0 2 12 52 252 252 3252 8252 104 105 KFAAAA JGOAAA HHHHxx
+6020 9630 0 0 0 0 20 20 20 1020 6020 40 41 OXAAAA KGOAAA OOOOxx
+3846 9631 0 2 6 6 46 846 1846 3846 3846 92 93 YRAAAA LGOAAA VVVVxx
+3755 9632 1 3 5 15 55 755 1755 3755 3755 110 111 LOAAAA MGOAAA AAAAxx
+3765 9633 1 1 5 5 65 765 1765 3765 3765 130 131 VOAAAA NGOAAA HHHHxx
+3434 9634 0 2 4 14 34 434 1434 3434 3434 68 69 CCAAAA OGOAAA OOOOxx
+1381 9635 1 1 1 1 81 381 1381 1381 1381 162 163 DBAAAA PGOAAA VVVVxx
+287 9636 1 3 7 7 87 287 287 287 287 174 175 BLAAAA QGOAAA AAAAxx
+4476 9637 0 0 6 16 76 476 476 4476 4476 152 153 EQAAAA RGOAAA HHHHxx
+2916 9638 0 0 6 16 16 916 916 2916 2916 32 33 EIAAAA SGOAAA OOOOxx
+4517 9639 1 1 7 17 17 517 517 4517 4517 34 35 TRAAAA TGOAAA VVVVxx
+4561 9640 1 1 1 1 61 561 561 4561 4561 122 123 LTAAAA UGOAAA AAAAxx
+5106 9641 0 2 6 6 6 106 1106 106 5106 12 13 KOAAAA VGOAAA HHHHxx
+2077 9642 1 1 7 17 77 77 77 2077 2077 154 155 XBAAAA WGOAAA OOOOxx
+5269 9643 1 1 9 9 69 269 1269 269 5269 138 139 RUAAAA XGOAAA VVVVxx
+5688 9644 0 0 8 8 88 688 1688 688 5688 176 177 UKAAAA YGOAAA AAAAxx
+8831 9645 1 3 1 11 31 831 831 3831 8831 62 63 RBAAAA ZGOAAA HHHHxx
+3867 9646 1 3 7 7 67 867 1867 3867 3867 134 135 TSAAAA AHOAAA OOOOxx
+6062 9647 0 2 2 2 62 62 62 1062 6062 124 125 EZAAAA BHOAAA VVVVxx
+8460 9648 0 0 0 0 60 460 460 3460 8460 120 121 KNAAAA CHOAAA AAAAxx
+3138 9649 0 2 8 18 38 138 1138 3138 3138 76 77 SQAAAA DHOAAA HHHHxx
+3173 9650 1 1 3 13 73 173 1173 3173 3173 146 147 BSAAAA EHOAAA OOOOxx
+7018 9651 0 2 8 18 18 18 1018 2018 7018 36 37 YJAAAA FHOAAA VVVVxx
+4836 9652 0 0 6 16 36 836 836 4836 4836 72 73 AEAAAA GHOAAA AAAAxx
+1007 9653 1 3 7 7 7 7 1007 1007 1007 14 15 TMAAAA HHOAAA HHHHxx
+658 9654 0 2 8 18 58 658 658 658 658 116 117 IZAAAA IHOAAA OOOOxx
+5205 9655 1 1 5 5 5 205 1205 205 5205 10 11 FSAAAA JHOAAA VVVVxx
+5805 9656 1 1 5 5 5 805 1805 805 5805 10 11 HPAAAA KHOAAA AAAAxx
+5959 9657 1 3 9 19 59 959 1959 959 5959 118 119 FVAAAA LHOAAA HHHHxx
+2863 9658 1 3 3 3 63 863 863 2863 2863 126 127 DGAAAA MHOAAA OOOOxx
+7272 9659 0 0 2 12 72 272 1272 2272 7272 144 145 STAAAA NHOAAA VVVVxx
+8437 9660 1 1 7 17 37 437 437 3437 8437 74 75 NMAAAA OHOAAA AAAAxx
+4900 9661 0 0 0 0 0 900 900 4900 4900 0 1 MGAAAA PHOAAA HHHHxx
+890 9662 0 2 0 10 90 890 890 890 890 180 181 GIAAAA QHOAAA OOOOxx
+3530 9663 0 2 0 10 30 530 1530 3530 3530 60 61 UFAAAA RHOAAA VVVVxx
+6209 9664 1 1 9 9 9 209 209 1209 6209 18 19 VEAAAA SHOAAA AAAAxx
+4595 9665 1 3 5 15 95 595 595 4595 4595 190 191 TUAAAA THOAAA HHHHxx
+5982 9666 0 2 2 2 82 982 1982 982 5982 164 165 CWAAAA UHOAAA OOOOxx
+1101 9667 1 1 1 1 1 101 1101 1101 1101 2 3 JQAAAA VHOAAA VVVVxx
+9555 9668 1 3 5 15 55 555 1555 4555 9555 110 111 NDAAAA WHOAAA AAAAxx
+1918 9669 0 2 8 18 18 918 1918 1918 1918 36 37 UVAAAA XHOAAA HHHHxx
+3527 9670 1 3 7 7 27 527 1527 3527 3527 54 55 RFAAAA YHOAAA OOOOxx
+7309 9671 1 1 9 9 9 309 1309 2309 7309 18 19 DVAAAA ZHOAAA VVVVxx
+8213 9672 1 1 3 13 13 213 213 3213 8213 26 27 XDAAAA AIOAAA AAAAxx
+306 9673 0 2 6 6 6 306 306 306 306 12 13 ULAAAA BIOAAA HHHHxx
+845 9674 1 1 5 5 45 845 845 845 845 90 91 NGAAAA CIOAAA OOOOxx
+16 9675 0 0 6 16 16 16 16 16 16 32 33 QAAAAA DIOAAA VVVVxx
+437 9676 1 1 7 17 37 437 437 437 437 74 75 VQAAAA EIOAAA AAAAxx
+9518 9677 0 2 8 18 18 518 1518 4518 9518 36 37 CCAAAA FIOAAA HHHHxx
+2142 9678 0 2 2 2 42 142 142 2142 2142 84 85 KEAAAA GIOAAA OOOOxx
+8121 9679 1 1 1 1 21 121 121 3121 8121 42 43 JAAAAA HIOAAA VVVVxx
+7354 9680 0 2 4 14 54 354 1354 2354 7354 108 109 WWAAAA IIOAAA AAAAxx
+1720 9681 0 0 0 0 20 720 1720 1720 1720 40 41 EOAAAA JIOAAA HHHHxx
+6078 9682 0 2 8 18 78 78 78 1078 6078 156 157 UZAAAA KIOAAA OOOOxx
+5929 9683 1 1 9 9 29 929 1929 929 5929 58 59 BUAAAA LIOAAA VVVVxx
+3856 9684 0 0 6 16 56 856 1856 3856 3856 112 113 ISAAAA MIOAAA AAAAxx
+3424 9685 0 0 4 4 24 424 1424 3424 3424 48 49 SBAAAA NIOAAA HHHHxx
+1712 9686 0 0 2 12 12 712 1712 1712 1712 24 25 WNAAAA OIOAAA OOOOxx
+2340 9687 0 0 0 0 40 340 340 2340 2340 80 81 AMAAAA PIOAAA VVVVxx
+5570 9688 0 2 0 10 70 570 1570 570 5570 140 141 GGAAAA QIOAAA AAAAxx
+8734 9689 0 2 4 14 34 734 734 3734 8734 68 69 YXAAAA RIOAAA HHHHxx
+6077 9690 1 1 7 17 77 77 77 1077 6077 154 155 TZAAAA SIOAAA OOOOxx
+2960 9691 0 0 0 0 60 960 960 2960 2960 120 121 WJAAAA TIOAAA VVVVxx
+5062 9692 0 2 2 2 62 62 1062 62 5062 124 125 SMAAAA UIOAAA AAAAxx
+1532 9693 0 0 2 12 32 532 1532 1532 1532 64 65 YGAAAA VIOAAA HHHHxx
+8298 9694 0 2 8 18 98 298 298 3298 8298 196 197 EHAAAA WIOAAA OOOOxx
+2496 9695 0 0 6 16 96 496 496 2496 2496 192 193 ASAAAA XIOAAA VVVVxx
+8412 9696 0 0 2 12 12 412 412 3412 8412 24 25 OLAAAA YIOAAA AAAAxx
+724 9697 0 0 4 4 24 724 724 724 724 48 49 WBAAAA ZIOAAA HHHHxx
+1019 9698 1 3 9 19 19 19 1019 1019 1019 38 39 FNAAAA AJOAAA OOOOxx
+6265 9699 1 1 5 5 65 265 265 1265 6265 130 131 ZGAAAA BJOAAA VVVVxx
+740 9700 0 0 0 0 40 740 740 740 740 80 81 MCAAAA CJOAAA AAAAxx
+8495 9701 1 3 5 15 95 495 495 3495 8495 190 191 TOAAAA DJOAAA HHHHxx
+6983 9702 1 3 3 3 83 983 983 1983 6983 166 167 PIAAAA EJOAAA OOOOxx
+991 9703 1 3 1 11 91 991 991 991 991 182 183 DMAAAA FJOAAA VVVVxx
+3189 9704 1 1 9 9 89 189 1189 3189 3189 178 179 RSAAAA GJOAAA AAAAxx
+4487 9705 1 3 7 7 87 487 487 4487 4487 174 175 PQAAAA HJOAAA HHHHxx
+5554 9706 0 2 4 14 54 554 1554 554 5554 108 109 QFAAAA IJOAAA OOOOxx
+1258 9707 0 2 8 18 58 258 1258 1258 1258 116 117 KWAAAA JJOAAA VVVVxx
+5359 9708 1 3 9 19 59 359 1359 359 5359 118 119 DYAAAA KJOAAA AAAAxx
+2709 9709 1 1 9 9 9 709 709 2709 2709 18 19 FAAAAA LJOAAA HHHHxx
+361 9710 1 1 1 1 61 361 361 361 361 122 123 XNAAAA MJOAAA OOOOxx
+4028 9711 0 0 8 8 28 28 28 4028 4028 56 57 YYAAAA NJOAAA VVVVxx
+3735 9712 1 3 5 15 35 735 1735 3735 3735 70 71 RNAAAA OJOAAA AAAAxx
+4427 9713 1 3 7 7 27 427 427 4427 4427 54 55 HOAAAA PJOAAA HHHHxx
+7540 9714 0 0 0 0 40 540 1540 2540 7540 80 81 AEAAAA QJOAAA OOOOxx
+3569 9715 1 1 9 9 69 569 1569 3569 3569 138 139 HHAAAA RJOAAA VVVVxx
+1916 9716 0 0 6 16 16 916 1916 1916 1916 32 33 SVAAAA SJOAAA AAAAxx
+7596 9717 0 0 6 16 96 596 1596 2596 7596 192 193 EGAAAA TJOAAA HHHHxx
+9721 9718 1 1 1 1 21 721 1721 4721 9721 42 43 XJAAAA UJOAAA OOOOxx
+4429 9719 1 1 9 9 29 429 429 4429 4429 58 59 JOAAAA VJOAAA VVVVxx
+3471 9720 1 3 1 11 71 471 1471 3471 3471 142 143 NDAAAA WJOAAA AAAAxx
+1157 9721 1 1 7 17 57 157 1157 1157 1157 114 115 NSAAAA XJOAAA HHHHxx
+5700 9722 0 0 0 0 0 700 1700 700 5700 0 1 GLAAAA YJOAAA OOOOxx
+4431 9723 1 3 1 11 31 431 431 4431 4431 62 63 LOAAAA ZJOAAA VVVVxx
+9409 9724 1 1 9 9 9 409 1409 4409 9409 18 19 XXAAAA AKOAAA AAAAxx
+8752 9725 0 0 2 12 52 752 752 3752 8752 104 105 QYAAAA BKOAAA HHHHxx
+9484 9726 0 0 4 4 84 484 1484 4484 9484 168 169 UAAAAA CKOAAA OOOOxx
+1266 9727 0 2 6 6 66 266 1266 1266 1266 132 133 SWAAAA DKOAAA VVVVxx
+9097 9728 1 1 7 17 97 97 1097 4097 9097 194 195 XLAAAA EKOAAA AAAAxx
+3068 9729 0 0 8 8 68 68 1068 3068 3068 136 137 AOAAAA FKOAAA HHHHxx
+5490 9730 0 2 0 10 90 490 1490 490 5490 180 181 EDAAAA GKOAAA OOOOxx
+1375 9731 1 3 5 15 75 375 1375 1375 1375 150 151 XAAAAA HKOAAA VVVVxx
+2487 9732 1 3 7 7 87 487 487 2487 2487 174 175 RRAAAA IKOAAA AAAAxx
+1705 9733 1 1 5 5 5 705 1705 1705 1705 10 11 PNAAAA JKOAAA HHHHxx
+1571 9734 1 3 1 11 71 571 1571 1571 1571 142 143 LIAAAA KKOAAA OOOOxx
+4005 9735 1 1 5 5 5 5 5 4005 4005 10 11 BYAAAA LKOAAA VVVVxx
+5497 9736 1 1 7 17 97 497 1497 497 5497 194 195 LDAAAA MKOAAA AAAAxx
+2144 9737 0 0 4 4 44 144 144 2144 2144 88 89 MEAAAA NKOAAA HHHHxx
+4052 9738 0 0 2 12 52 52 52 4052 4052 104 105 WZAAAA OKOAAA OOOOxx
+4942 9739 0 2 2 2 42 942 942 4942 4942 84 85 CIAAAA PKOAAA VVVVxx
+5504 9740 0 0 4 4 4 504 1504 504 5504 8 9 SDAAAA QKOAAA AAAAxx
+2913 9741 1 1 3 13 13 913 913 2913 2913 26 27 BIAAAA RKOAAA HHHHxx
+5617 9742 1 1 7 17 17 617 1617 617 5617 34 35 BIAAAA SKOAAA OOOOxx
+8179 9743 1 3 9 19 79 179 179 3179 8179 158 159 PCAAAA TKOAAA VVVVxx
+9437 9744 1 1 7 17 37 437 1437 4437 9437 74 75 ZYAAAA UKOAAA AAAAxx
+1821 9745 1 1 1 1 21 821 1821 1821 1821 42 43 BSAAAA VKOAAA HHHHxx
+5737 9746 1 1 7 17 37 737 1737 737 5737 74 75 RMAAAA WKOAAA OOOOxx
+4207 9747 1 3 7 7 7 207 207 4207 4207 14 15 VFAAAA XKOAAA VVVVxx
+4815 9748 1 3 5 15 15 815 815 4815 4815 30 31 FDAAAA YKOAAA AAAAxx
+8707 9749 1 3 7 7 7 707 707 3707 8707 14 15 XWAAAA ZKOAAA HHHHxx
+5970 9750 0 2 0 10 70 970 1970 970 5970 140 141 QVAAAA ALOAAA OOOOxx
+5501 9751 1 1 1 1 1 501 1501 501 5501 2 3 PDAAAA BLOAAA VVVVxx
+4013 9752 1 1 3 13 13 13 13 4013 4013 26 27 JYAAAA CLOAAA AAAAxx
+9235 9753 1 3 5 15 35 235 1235 4235 9235 70 71 FRAAAA DLOAAA HHHHxx
+2503 9754 1 3 3 3 3 503 503 2503 2503 6 7 HSAAAA ELOAAA OOOOxx
+9181 9755 1 1 1 1 81 181 1181 4181 9181 162 163 DPAAAA FLOAAA VVVVxx
+2289 9756 1 1 9 9 89 289 289 2289 2289 178 179 BKAAAA GLOAAA AAAAxx
+4256 9757 0 0 6 16 56 256 256 4256 4256 112 113 SHAAAA HLOAAA HHHHxx
+191 9758 1 3 1 11 91 191 191 191 191 182 183 JHAAAA ILOAAA OOOOxx
+9655 9759 1 3 5 15 55 655 1655 4655 9655 110 111 JHAAAA JLOAAA VVVVxx
+8615 9760 1 3 5 15 15 615 615 3615 8615 30 31 JTAAAA KLOAAA AAAAxx
+3011 9761 1 3 1 11 11 11 1011 3011 3011 22 23 VLAAAA LLOAAA HHHHxx
+6376 9762 0 0 6 16 76 376 376 1376 6376 152 153 GLAAAA MLOAAA OOOOxx
+68 9763 0 0 8 8 68 68 68 68 68 136 137 QCAAAA NLOAAA VVVVxx
+4720 9764 0 0 0 0 20 720 720 4720 4720 40 41 OZAAAA OLOAAA AAAAxx
+6848 9765 0 0 8 8 48 848 848 1848 6848 96 97 KDAAAA PLOAAA HHHHxx
+456 9766 0 0 6 16 56 456 456 456 456 112 113 ORAAAA QLOAAA OOOOxx
+5887 9767 1 3 7 7 87 887 1887 887 5887 174 175 LSAAAA RLOAAA VVVVxx
+9249 9768 1 1 9 9 49 249 1249 4249 9249 98 99 TRAAAA SLOAAA AAAAxx
+4041 9769 1 1 1 1 41 41 41 4041 4041 82 83 LZAAAA TLOAAA HHHHxx
+2304 9770 0 0 4 4 4 304 304 2304 2304 8 9 QKAAAA ULOAAA OOOOxx
+8763 9771 1 3 3 3 63 763 763 3763 8763 126 127 BZAAAA VLOAAA VVVVxx
+2115 9772 1 3 5 15 15 115 115 2115 2115 30 31 JDAAAA WLOAAA AAAAxx
+8014 9773 0 2 4 14 14 14 14 3014 8014 28 29 GWAAAA XLOAAA HHHHxx
+9895 9774 1 3 5 15 95 895 1895 4895 9895 190 191 PQAAAA YLOAAA OOOOxx
+671 9775 1 3 1 11 71 671 671 671 671 142 143 VZAAAA ZLOAAA VVVVxx
+3774 9776 0 2 4 14 74 774 1774 3774 3774 148 149 EPAAAA AMOAAA AAAAxx
+134 9777 0 2 4 14 34 134 134 134 134 68 69 EFAAAA BMOAAA HHHHxx
+534 9778 0 2 4 14 34 534 534 534 534 68 69 OUAAAA CMOAAA OOOOxx
+7308 9779 0 0 8 8 8 308 1308 2308 7308 16 17 CVAAAA DMOAAA VVVVxx
+5244 9780 0 0 4 4 44 244 1244 244 5244 88 89 STAAAA EMOAAA AAAAxx
+1512 9781 0 0 2 12 12 512 1512 1512 1512 24 25 EGAAAA FMOAAA HHHHxx
+8960 9782 0 0 0 0 60 960 960 3960 8960 120 121 QGAAAA GMOAAA OOOOxx
+6602 9783 0 2 2 2 2 602 602 1602 6602 4 5 YTAAAA HMOAAA VVVVxx
+593 9784 1 1 3 13 93 593 593 593 593 186 187 VWAAAA IMOAAA AAAAxx
+2353 9785 1 1 3 13 53 353 353 2353 2353 106 107 NMAAAA JMOAAA HHHHxx
+4139 9786 1 3 9 19 39 139 139 4139 4139 78 79 FDAAAA KMOAAA OOOOxx
+3063 9787 1 3 3 3 63 63 1063 3063 3063 126 127 VNAAAA LMOAAA VVVVxx
+652 9788 0 0 2 12 52 652 652 652 652 104 105 CZAAAA MMOAAA AAAAxx
+7405 9789 1 1 5 5 5 405 1405 2405 7405 10 11 VYAAAA NMOAAA HHHHxx
+3034 9790 0 2 4 14 34 34 1034 3034 3034 68 69 SMAAAA OMOAAA OOOOxx
+4614 9791 0 2 4 14 14 614 614 4614 4614 28 29 MVAAAA PMOAAA VVVVxx
+2351 9792 1 3 1 11 51 351 351 2351 2351 102 103 LMAAAA QMOAAA AAAAxx
+8208 9793 0 0 8 8 8 208 208 3208 8208 16 17 SDAAAA RMOAAA HHHHxx
+5475 9794 1 3 5 15 75 475 1475 475 5475 150 151 PCAAAA SMOAAA OOOOxx
+6875 9795 1 3 5 15 75 875 875 1875 6875 150 151 LEAAAA TMOAAA VVVVxx
+563 9796 1 3 3 3 63 563 563 563 563 126 127 RVAAAA UMOAAA AAAAxx
+3346 9797 0 2 6 6 46 346 1346 3346 3346 92 93 SYAAAA VMOAAA HHHHxx
+291 9798 1 3 1 11 91 291 291 291 291 182 183 FLAAAA WMOAAA OOOOxx
+6345 9799 1 1 5 5 45 345 345 1345 6345 90 91 BKAAAA XMOAAA VVVVxx
+8099 9800 1 3 9 19 99 99 99 3099 8099 198 199 NZAAAA YMOAAA AAAAxx
+2078 9801 0 2 8 18 78 78 78 2078 2078 156 157 YBAAAA ZMOAAA HHHHxx
+8238 9802 0 2 8 18 38 238 238 3238 8238 76 77 WEAAAA ANOAAA OOOOxx
+4482 9803 0 2 2 2 82 482 482 4482 4482 164 165 KQAAAA BNOAAA VVVVxx
+716 9804 0 0 6 16 16 716 716 716 716 32 33 OBAAAA CNOAAA AAAAxx
+7288 9805 0 0 8 8 88 288 1288 2288 7288 176 177 IUAAAA DNOAAA HHHHxx
+5906 9806 0 2 6 6 6 906 1906 906 5906 12 13 ETAAAA ENOAAA OOOOxx
+5618 9807 0 2 8 18 18 618 1618 618 5618 36 37 CIAAAA FNOAAA VVVVxx
+1141 9808 1 1 1 1 41 141 1141 1141 1141 82 83 XRAAAA GNOAAA AAAAxx
+8231 9809 1 3 1 11 31 231 231 3231 8231 62 63 PEAAAA HNOAAA HHHHxx
+3713 9810 1 1 3 13 13 713 1713 3713 3713 26 27 VMAAAA INOAAA OOOOxx
+9158 9811 0 2 8 18 58 158 1158 4158 9158 116 117 GOAAAA JNOAAA VVVVxx
+4051 9812 1 3 1 11 51 51 51 4051 4051 102 103 VZAAAA KNOAAA AAAAxx
+1973 9813 1 1 3 13 73 973 1973 1973 1973 146 147 XXAAAA LNOAAA HHHHxx
+6710 9814 0 2 0 10 10 710 710 1710 6710 20 21 CYAAAA MNOAAA OOOOxx
+1021 9815 1 1 1 1 21 21 1021 1021 1021 42 43 HNAAAA NNOAAA VVVVxx
+2196 9816 0 0 6 16 96 196 196 2196 2196 192 193 MGAAAA ONOAAA AAAAxx
+8335 9817 1 3 5 15 35 335 335 3335 8335 70 71 PIAAAA PNOAAA HHHHxx
+2272 9818 0 0 2 12 72 272 272 2272 2272 144 145 KJAAAA QNOAAA OOOOxx
+3818 9819 0 2 8 18 18 818 1818 3818 3818 36 37 WQAAAA RNOAAA VVVVxx
+679 9820 1 3 9 19 79 679 679 679 679 158 159 DAAAAA SNOAAA AAAAxx
+7512 9821 0 0 2 12 12 512 1512 2512 7512 24 25 YCAAAA TNOAAA HHHHxx
+493 9822 1 1 3 13 93 493 493 493 493 186 187 ZSAAAA UNOAAA OOOOxx
+5663 9823 1 3 3 3 63 663 1663 663 5663 126 127 VJAAAA VNOAAA VVVVxx
+4655 9824 1 3 5 15 55 655 655 4655 4655 110 111 BXAAAA WNOAAA AAAAxx
+3996 9825 0 0 6 16 96 996 1996 3996 3996 192 193 SXAAAA XNOAAA HHHHxx
+8797 9826 1 1 7 17 97 797 797 3797 8797 194 195 JAAAAA YNOAAA OOOOxx
+2991 9827 1 3 1 11 91 991 991 2991 2991 182 183 BLAAAA ZNOAAA VVVVxx
+7038 9828 0 2 8 18 38 38 1038 2038 7038 76 77 SKAAAA AOOAAA AAAAxx
+4174 9829 0 2 4 14 74 174 174 4174 4174 148 149 OEAAAA BOOAAA HHHHxx
+6908 9830 0 0 8 8 8 908 908 1908 6908 16 17 SFAAAA COOAAA OOOOxx
+8477 9831 1 1 7 17 77 477 477 3477 8477 154 155 BOAAAA DOOAAA VVVVxx
+3576 9832 0 0 6 16 76 576 1576 3576 3576 152 153 OHAAAA EOOAAA AAAAxx
+2685 9833 1 1 5 5 85 685 685 2685 2685 170 171 HZAAAA FOOAAA HHHHxx
+9161 9834 1 1 1 1 61 161 1161 4161 9161 122 123 JOAAAA GOOAAA OOOOxx
+2951 9835 1 3 1 11 51 951 951 2951 2951 102 103 NJAAAA HOOAAA VVVVxx
+8362 9836 0 2 2 2 62 362 362 3362 8362 124 125 QJAAAA IOOAAA AAAAxx
+2379 9837 1 3 9 19 79 379 379 2379 2379 158 159 NNAAAA JOOAAA HHHHxx
+1277 9838 1 1 7 17 77 277 1277 1277 1277 154 155 DXAAAA KOOAAA OOOOxx
+1728 9839 0 0 8 8 28 728 1728 1728 1728 56 57 MOAAAA LOOAAA VVVVxx
+9816 9840 0 0 6 16 16 816 1816 4816 9816 32 33 ONAAAA MOOAAA AAAAxx
+6288 9841 0 0 8 8 88 288 288 1288 6288 176 177 WHAAAA NOOAAA HHHHxx
+8985 9842 1 1 5 5 85 985 985 3985 8985 170 171 PHAAAA OOOAAA OOOOxx
+771 9843 1 3 1 11 71 771 771 771 771 142 143 RDAAAA POOAAA VVVVxx
+464 9844 0 0 4 4 64 464 464 464 464 128 129 WRAAAA QOOAAA AAAAxx
+9625 9845 1 1 5 5 25 625 1625 4625 9625 50 51 FGAAAA ROOAAA HHHHxx
+9608 9846 0 0 8 8 8 608 1608 4608 9608 16 17 OFAAAA SOOAAA OOOOxx
+9170 9847 0 2 0 10 70 170 1170 4170 9170 140 141 SOAAAA TOOAAA VVVVxx
+9658 9848 0 2 8 18 58 658 1658 4658 9658 116 117 MHAAAA UOOAAA AAAAxx
+7515 9849 1 3 5 15 15 515 1515 2515 7515 30 31 BDAAAA VOOAAA HHHHxx
+9400 9850 0 0 0 0 0 400 1400 4400 9400 0 1 OXAAAA WOOAAA OOOOxx
+2045 9851 1 1 5 5 45 45 45 2045 2045 90 91 RAAAAA XOOAAA VVVVxx
+324 9852 0 0 4 4 24 324 324 324 324 48 49 MMAAAA YOOAAA AAAAxx
+4252 9853 0 0 2 12 52 252 252 4252 4252 104 105 OHAAAA ZOOAAA HHHHxx
+8329 9854 1 1 9 9 29 329 329 3329 8329 58 59 JIAAAA APOAAA OOOOxx
+4472 9855 0 0 2 12 72 472 472 4472 4472 144 145 AQAAAA BPOAAA VVVVxx
+1047 9856 1 3 7 7 47 47 1047 1047 1047 94 95 HOAAAA CPOAAA AAAAxx
+9341 9857 1 1 1 1 41 341 1341 4341 9341 82 83 HVAAAA DPOAAA HHHHxx
+7000 9858 0 0 0 0 0 0 1000 2000 7000 0 1 GJAAAA EPOAAA OOOOxx
+1429 9859 1 1 9 9 29 429 1429 1429 1429 58 59 ZCAAAA FPOAAA VVVVxx
+2701 9860 1 1 1 1 1 701 701 2701 2701 2 3 XZAAAA GPOAAA AAAAxx
+6630 9861 0 2 0 10 30 630 630 1630 6630 60 61 AVAAAA HPOAAA HHHHxx
+3669 9862 1 1 9 9 69 669 1669 3669 3669 138 139 DLAAAA IPOAAA OOOOxx
+8613 9863 1 1 3 13 13 613 613 3613 8613 26 27 HTAAAA JPOAAA VVVVxx
+7080 9864 0 0 0 0 80 80 1080 2080 7080 160 161 IMAAAA KPOAAA AAAAxx
+8788 9865 0 0 8 8 88 788 788 3788 8788 176 177 AAAAAA LPOAAA HHHHxx
+6291 9866 1 3 1 11 91 291 291 1291 6291 182 183 ZHAAAA MPOAAA OOOOxx
+7885 9867 1 1 5 5 85 885 1885 2885 7885 170 171 HRAAAA NPOAAA VVVVxx
+7160 9868 0 0 0 0 60 160 1160 2160 7160 120 121 KPAAAA OPOAAA AAAAxx
+6140 9869 0 0 0 0 40 140 140 1140 6140 80 81 ECAAAA PPOAAA HHHHxx
+9881 9870 1 1 1 1 81 881 1881 4881 9881 162 163 BQAAAA QPOAAA OOOOxx
+9140 9871 0 0 0 0 40 140 1140 4140 9140 80 81 ONAAAA RPOAAA VVVVxx
+644 9872 0 0 4 4 44 644 644 644 644 88 89 UYAAAA SPOAAA AAAAxx
+3667 9873 1 3 7 7 67 667 1667 3667 3667 134 135 BLAAAA TPOAAA HHHHxx
+2675 9874 1 3 5 15 75 675 675 2675 2675 150 151 XYAAAA UPOAAA OOOOxx
+9492 9875 0 0 2 12 92 492 1492 4492 9492 184 185 CBAAAA VPOAAA VVVVxx
+5004 9876 0 0 4 4 4 4 1004 4 5004 8 9 MKAAAA WPOAAA AAAAxx
+9456 9877 0 0 6 16 56 456 1456 4456 9456 112 113 SZAAAA XPOAAA HHHHxx
+8197 9878 1 1 7 17 97 197 197 3197 8197 194 195 HDAAAA YPOAAA OOOOxx
+2837 9879 1 1 7 17 37 837 837 2837 2837 74 75 DFAAAA ZPOAAA VVVVxx
+127 9880 1 3 7 7 27 127 127 127 127 54 55 XEAAAA AQOAAA AAAAxx
+9772 9881 0 0 2 12 72 772 1772 4772 9772 144 145 WLAAAA BQOAAA HHHHxx
+5743 9882 1 3 3 3 43 743 1743 743 5743 86 87 XMAAAA CQOAAA OOOOxx
+2007 9883 1 3 7 7 7 7 7 2007 2007 14 15 FZAAAA DQOAAA VVVVxx
+7586 9884 0 2 6 6 86 586 1586 2586 7586 172 173 UFAAAA EQOAAA AAAAxx
+45 9885 1 1 5 5 45 45 45 45 45 90 91 TBAAAA FQOAAA HHHHxx
+6482 9886 0 2 2 2 82 482 482 1482 6482 164 165 IPAAAA GQOAAA OOOOxx
+4565 9887 1 1 5 5 65 565 565 4565 4565 130 131 PTAAAA HQOAAA VVVVxx
+6975 9888 1 3 5 15 75 975 975 1975 6975 150 151 HIAAAA IQOAAA AAAAxx
+7260 9889 0 0 0 0 60 260 1260 2260 7260 120 121 GTAAAA JQOAAA HHHHxx
+2830 9890 0 2 0 10 30 830 830 2830 2830 60 61 WEAAAA KQOAAA OOOOxx
+9365 9891 1 1 5 5 65 365 1365 4365 9365 130 131 FWAAAA LQOAAA VVVVxx
+8207 9892 1 3 7 7 7 207 207 3207 8207 14 15 RDAAAA MQOAAA AAAAxx
+2506 9893 0 2 6 6 6 506 506 2506 2506 12 13 KSAAAA NQOAAA HHHHxx
+8081 9894 1 1 1 1 81 81 81 3081 8081 162 163 VYAAAA OQOAAA OOOOxx
+8678 9895 0 2 8 18 78 678 678 3678 8678 156 157 UVAAAA PQOAAA VVVVxx
+9932 9896 0 0 2 12 32 932 1932 4932 9932 64 65 ASAAAA QQOAAA AAAAxx
+447 9897 1 3 7 7 47 447 447 447 447 94 95 FRAAAA RQOAAA HHHHxx
+9187 9898 1 3 7 7 87 187 1187 4187 9187 174 175 JPAAAA SQOAAA OOOOxx
+89 9899 1 1 9 9 89 89 89 89 89 178 179 LDAAAA TQOAAA VVVVxx
+7027 9900 1 3 7 7 27 27 1027 2027 7027 54 55 HKAAAA UQOAAA AAAAxx
+1536 9901 0 0 6 16 36 536 1536 1536 1536 72 73 CHAAAA VQOAAA HHHHxx
+160 9902 0 0 0 0 60 160 160 160 160 120 121 EGAAAA WQOAAA OOOOxx
+7679 9903 1 3 9 19 79 679 1679 2679 7679 158 159 JJAAAA XQOAAA VVVVxx
+5973 9904 1 1 3 13 73 973 1973 973 5973 146 147 TVAAAA YQOAAA AAAAxx
+4401 9905 1 1 1 1 1 401 401 4401 4401 2 3 HNAAAA ZQOAAA HHHHxx
+395 9906 1 3 5 15 95 395 395 395 395 190 191 FPAAAA AROAAA OOOOxx
+4904 9907 0 0 4 4 4 904 904 4904 4904 8 9 QGAAAA BROAAA VVVVxx
+2759 9908 1 3 9 19 59 759 759 2759 2759 118 119 DCAAAA CROAAA AAAAxx
+8713 9909 1 1 3 13 13 713 713 3713 8713 26 27 DXAAAA DROAAA HHHHxx
+3770 9910 0 2 0 10 70 770 1770 3770 3770 140 141 APAAAA EROAAA OOOOxx
+8272 9911 0 0 2 12 72 272 272 3272 8272 144 145 EGAAAA FROAAA VVVVxx
+5358 9912 0 2 8 18 58 358 1358 358 5358 116 117 CYAAAA GROAAA AAAAxx
+9747 9913 1 3 7 7 47 747 1747 4747 9747 94 95 XKAAAA HROAAA HHHHxx
+1567 9914 1 3 7 7 67 567 1567 1567 1567 134 135 HIAAAA IROAAA OOOOxx
+2136 9915 0 0 6 16 36 136 136 2136 2136 72 73 EEAAAA JROAAA VVVVxx
+314 9916 0 2 4 14 14 314 314 314 314 28 29 CMAAAA KROAAA AAAAxx
+4583 9917 1 3 3 3 83 583 583 4583 4583 166 167 HUAAAA LROAAA HHHHxx
+375 9918 1 3 5 15 75 375 375 375 375 150 151 LOAAAA MROAAA OOOOxx
+5566 9919 0 2 6 6 66 566 1566 566 5566 132 133 CGAAAA NROAAA VVVVxx
+6865 9920 1 1 5 5 65 865 865 1865 6865 130 131 BEAAAA OROAAA AAAAxx
+894 9921 0 2 4 14 94 894 894 894 894 188 189 KIAAAA PROAAA HHHHxx
+5399 9922 1 3 9 19 99 399 1399 399 5399 198 199 RZAAAA QROAAA OOOOxx
+1385 9923 1 1 5 5 85 385 1385 1385 1385 170 171 HBAAAA RROAAA VVVVxx
+2156 9924 0 0 6 16 56 156 156 2156 2156 112 113 YEAAAA SROAAA AAAAxx
+9659 9925 1 3 9 19 59 659 1659 4659 9659 118 119 NHAAAA TROAAA HHHHxx
+477 9926 1 1 7 17 77 477 477 477 477 154 155 JSAAAA UROAAA OOOOxx
+8194 9927 0 2 4 14 94 194 194 3194 8194 188 189 EDAAAA VROAAA VVVVxx
+3937 9928 1 1 7 17 37 937 1937 3937 3937 74 75 LVAAAA WROAAA AAAAxx
+3745 9929 1 1 5 5 45 745 1745 3745 3745 90 91 BOAAAA XROAAA HHHHxx
+4096 9930 0 0 6 16 96 96 96 4096 4096 192 193 OBAAAA YROAAA OOOOxx
+5487 9931 1 3 7 7 87 487 1487 487 5487 174 175 BDAAAA ZROAAA VVVVxx
+2475 9932 1 3 5 15 75 475 475 2475 2475 150 151 FRAAAA ASOAAA AAAAxx
+6105 9933 1 1 5 5 5 105 105 1105 6105 10 11 VAAAAA BSOAAA HHHHxx
+6036 9934 0 0 6 16 36 36 36 1036 6036 72 73 EYAAAA CSOAAA OOOOxx
+1315 9935 1 3 5 15 15 315 1315 1315 1315 30 31 PYAAAA DSOAAA VVVVxx
+4473 9936 1 1 3 13 73 473 473 4473 4473 146 147 BQAAAA ESOAAA AAAAxx
+4016 9937 0 0 6 16 16 16 16 4016 4016 32 33 MYAAAA FSOAAA HHHHxx
+8135 9938 1 3 5 15 35 135 135 3135 8135 70 71 XAAAAA GSOAAA OOOOxx
+8892 9939 0 0 2 12 92 892 892 3892 8892 184 185 AEAAAA HSOAAA VVVVxx
+4850 9940 0 2 0 10 50 850 850 4850 4850 100 101 OEAAAA ISOAAA AAAAxx
+2545 9941 1 1 5 5 45 545 545 2545 2545 90 91 XTAAAA JSOAAA HHHHxx
+3788 9942 0 0 8 8 88 788 1788 3788 3788 176 177 SPAAAA KSOAAA OOOOxx
+1672 9943 0 0 2 12 72 672 1672 1672 1672 144 145 IMAAAA LSOAAA VVVVxx
+3664 9944 0 0 4 4 64 664 1664 3664 3664 128 129 YKAAAA MSOAAA AAAAxx
+3775 9945 1 3 5 15 75 775 1775 3775 3775 150 151 FPAAAA NSOAAA HHHHxx
+3103 9946 1 3 3 3 3 103 1103 3103 3103 6 7 JPAAAA OSOAAA OOOOxx
+9335 9947 1 3 5 15 35 335 1335 4335 9335 70 71 BVAAAA PSOAAA VVVVxx
+9200 9948 0 0 0 0 0 200 1200 4200 9200 0 1 WPAAAA QSOAAA AAAAxx
+8665 9949 1 1 5 5 65 665 665 3665 8665 130 131 HVAAAA RSOAAA HHHHxx
+1356 9950 0 0 6 16 56 356 1356 1356 1356 112 113 EAAAAA SSOAAA OOOOxx
+6118 9951 0 2 8 18 18 118 118 1118 6118 36 37 IBAAAA TSOAAA VVVVxx
+4605 9952 1 1 5 5 5 605 605 4605 4605 10 11 DVAAAA USOAAA AAAAxx
+5651 9953 1 3 1 11 51 651 1651 651 5651 102 103 JJAAAA VSOAAA HHHHxx
+9055 9954 1 3 5 15 55 55 1055 4055 9055 110 111 HKAAAA WSOAAA OOOOxx
+8461 9955 1 1 1 1 61 461 461 3461 8461 122 123 LNAAAA XSOAAA VVVVxx
+6107 9956 1 3 7 7 7 107 107 1107 6107 14 15 XAAAAA YSOAAA AAAAxx
+1967 9957 1 3 7 7 67 967 1967 1967 1967 134 135 RXAAAA ZSOAAA HHHHxx
+8910 9958 0 2 0 10 10 910 910 3910 8910 20 21 SEAAAA ATOAAA OOOOxx
+8257 9959 1 1 7 17 57 257 257 3257 8257 114 115 PFAAAA BTOAAA VVVVxx
+851 9960 1 3 1 11 51 851 851 851 851 102 103 TGAAAA CTOAAA AAAAxx
+7823 9961 1 3 3 3 23 823 1823 2823 7823 46 47 XOAAAA DTOAAA HHHHxx
+3208 9962 0 0 8 8 8 208 1208 3208 3208 16 17 KTAAAA ETOAAA OOOOxx
+856 9963 0 0 6 16 56 856 856 856 856 112 113 YGAAAA FTOAAA VVVVxx
+2654 9964 0 2 4 14 54 654 654 2654 2654 108 109 CYAAAA GTOAAA AAAAxx
+7185 9965 1 1 5 5 85 185 1185 2185 7185 170 171 JQAAAA HTOAAA HHHHxx
+309 9966 1 1 9 9 9 309 309 309 309 18 19 XLAAAA ITOAAA OOOOxx
+9752 9967 0 0 2 12 52 752 1752 4752 9752 104 105 CLAAAA JTOAAA VVVVxx
+6405 9968 1 1 5 5 5 405 405 1405 6405 10 11 JMAAAA KTOAAA AAAAxx
+6113 9969 1 1 3 13 13 113 113 1113 6113 26 27 DBAAAA LTOAAA HHHHxx
+9774 9970 0 2 4 14 74 774 1774 4774 9774 148 149 YLAAAA MTOAAA OOOOxx
+1674 9971 0 2 4 14 74 674 1674 1674 1674 148 149 KMAAAA NTOAAA VVVVxx
+9602 9972 0 2 2 2 2 602 1602 4602 9602 4 5 IFAAAA OTOAAA AAAAxx
+1363 9973 1 3 3 3 63 363 1363 1363 1363 126 127 LAAAAA PTOAAA HHHHxx
+6887 9974 1 3 7 7 87 887 887 1887 6887 174 175 XEAAAA QTOAAA OOOOxx
+6170 9975 0 2 0 10 70 170 170 1170 6170 140 141 IDAAAA RTOAAA VVVVxx
+8888 9976 0 0 8 8 88 888 888 3888 8888 176 177 WDAAAA STOAAA AAAAxx
+2981 9977 1 1 1 1 81 981 981 2981 2981 162 163 RKAAAA TTOAAA HHHHxx
+7369 9978 1 1 9 9 69 369 1369 2369 7369 138 139 LXAAAA UTOAAA OOOOxx
+6227 9979 1 3 7 7 27 227 227 1227 6227 54 55 NFAAAA VTOAAA VVVVxx
+8002 9980 0 2 2 2 2 2 2 3002 8002 4 5 UVAAAA WTOAAA AAAAxx
+4288 9981 0 0 8 8 88 288 288 4288 4288 176 177 YIAAAA XTOAAA HHHHxx
+5136 9982 0 0 6 16 36 136 1136 136 5136 72 73 OPAAAA YTOAAA OOOOxx
+1084 9983 0 0 4 4 84 84 1084 1084 1084 168 169 SPAAAA ZTOAAA VVVVxx
+9117 9984 1 1 7 17 17 117 1117 4117 9117 34 35 RMAAAA AUOAAA AAAAxx
+2406 9985 0 2 6 6 6 406 406 2406 2406 12 13 OOAAAA BUOAAA HHHHxx
+1384 9986 0 0 4 4 84 384 1384 1384 1384 168 169 GBAAAA CUOAAA OOOOxx
+9194 9987 0 2 4 14 94 194 1194 4194 9194 188 189 QPAAAA DUOAAA VVVVxx
+858 9988 0 2 8 18 58 858 858 858 858 116 117 AHAAAA EUOAAA AAAAxx
+8592 9989 0 0 2 12 92 592 592 3592 8592 184 185 MSAAAA FUOAAA HHHHxx
+4773 9990 1 1 3 13 73 773 773 4773 4773 146 147 PBAAAA GUOAAA OOOOxx
+4093 9991 1 1 3 13 93 93 93 4093 4093 186 187 LBAAAA HUOAAA VVVVxx
+6587 9992 1 3 7 7 87 587 587 1587 6587 174 175 JTAAAA IUOAAA AAAAxx
+6093 9993 1 1 3 13 93 93 93 1093 6093 186 187 JAAAAA JUOAAA HHHHxx
+429 9994 1 1 9 9 29 429 429 429 429 58 59 NQAAAA KUOAAA OOOOxx
+5780 9995 0 0 0 0 80 780 1780 780 5780 160 161 IOAAAA LUOAAA VVVVxx
+1783 9996 1 3 3 3 83 783 1783 1783 1783 166 167 PQAAAA MUOAAA AAAAxx
+2992 9997 0 0 2 12 92 992 992 2992 2992 184 185 CLAAAA NUOAAA HHHHxx
+0 9998 0 0 0 0 0 0 0 0 0 0 1 AAAAAA OUOAAA OOOOxx
+2968 9999 0 0 8 8 68 968 968 2968 2968 136 137 EKAAAA PUOAAA VVVVxx
diff --git a/src/test/regress/data/tsearch.data b/src/test/regress/data/tsearch.data
new file mode 100644
index 0000000..a60f39b
--- /dev/null
+++ b/src/test/regress/data/tsearch.data
@@ -0,0 +1,508 @@
+\n
+\n
+\n
+\n
+\n
+\n
+\n
+\n
+\n i8 hy qo xa jl wr le l5 ja jx zf ro vw wd wa cc mm wh fn yd td l8 ec rv th oc ix ir sm y4 gh pr qg ue cx ww zv c9 zv tx eo f5 gd km b9 wb rm ym yl xj u7 xz uk iq tm ux di if uc hc ge
+\n gr ty ph jh po wa iw ag wq r3 yd ow rb ip et ej yl a9 dk pu y6 su ov hf xe qe sd qr zt kp ml ea tp pg dq e3 s3 hh gn hz j7 hb qs qd v0 v4 w0 nu ee wk ez un rd sz wx e7 pn yf gh uh ki kx rb qv f1 bh sr yj ry r2
+\n q1 q8 wp w9 vs ww rq de qt wo qp sa rv mc sn u8 yl
+\n hv ra sa fr qs ps 4w z5 ls wt ad wy q6 zg bd vt wa e4 ft w7 ld es yg et ic pm sw ja qv ov jm ma b3 wu wi qy ug hs wh ex rt tj en ur e2 ut gv as ui dy qy du qo gv cy lx kw xm fl x2 hd ny nu hh dt wg wh rs wb wz yy yu tj ha ak rw sw io h1 ux ku v6 wc qa rv xb s8 qd f2 zo k2 ew w4 yh yu yi
+\n rs tt gp qh wt q6 lg zh vr b8 uy uu lh px jm ww qe xu fp fd rs qu ki dr fn gq gw jv oq zt 2r lc ke wg l9 x3 x5 7g vs ar e7 u2 s8 t0 av dj kl nm u2 zp gf yw ee oc tw a1
+\n qs uz wr gq q9 rl e0 pe dj a9 hp qw aw er kq pp uu pl zo wp fr r6 ej pv u5 hh av lw ko qc pn qj ez n8 wn eu tq
+\n po h9 rd qs hr la u0 um me wp 0p rl mv rc ab r0 fe fj fk qn jh iy cn lb bl ln b5 ll yg yh qt qp uz od dq as gn cr qa wa cu fy zy vo xk eq vg mr ns yy t7 yi op th yo ov pv em tc hg az io s5 ct os wu lq dr mp hk si gx
+\n hm k5 pw a5 qh nb q3 ql wr wt z7 oz wu wh kv q8 c3 mt mg hb a3 rz pz uo y1 rb av us ek dz q0 d3 qw j2 ls wy qq jf ng eo gl ed ix em he qt du hp jc f2 m9 qp hb l4 gy zf l6 qr dn cp x1 oh qk kk s3 hy wg zs ot wj sl oz ie e9 ay it u5 ai hm gh py hz qk ki h8 ja zu qb ei vc qj hg ev h6 yh u0 tb id
+\n qg d1 bt c5 r3 iv g6 d7 rc ml gk uh yn y0 zo uh qd wh ib uo u4 om qg ql yz
+\n hb a3 q5 pl yj lo qy ki sy fo rj kk zq dl wn 7a zi wn wm yr w3 tv r1
+\n ft k6 iz qn qj q2 q3 bl zd av ro wo lk tg ea ew ed y1 ia yl ic g6 po aw sc zm qn gl wq qw zr jp wt j5 gs vt qt yc rr op yw tl ye hr i8 tb uu j0 xd lz vu nl qd fu wg pf wj bt ee wh t2 tp sz um oo tg ha u4 f5 sw pq pr ju qk mh ki zb vj ob cx df hj ef cj q6 u9 tv rv o4 sy ru fq ir
+\n ps ko uk tz vv um t9 uk k2 ja o6 ob
+\n qs nb gh ld q7 jc sp el w0 py qx i2 qe la rl qw tu ti dq ue iv oi wa qr ed t3 fg oa of rr fv qz xn wu wq te hx
+\n yb ty pq az fi qg qn la bu ji lg wg q8 mi cv rl up lg om oq ym pv in aq gg js ha on ww qr bj vn pv he b5 mh qe cc mk qt rb eu qy rw tr qo ec op sn oh e2 ao iv e4 hy dt s6 qt p1 hb ih qs wg x1 bd l1 t1 ro r9 uv wb aw gu os t0 ah e0 s0 hj pe or qj zz ql fd ks qv bq qm bg ec ry oj u8 u0 yj ru r1 yx o7
+\n z4 wr qz cg nq ir bb gb w7 e5 zc pj e9 px uo fp ts aq db q9 iy qe zv xu a9 l1 mb qw tc qu fi hw ur de e4 hk lj wo wf fi ep rl wh vh ek vp oi sv rh ay hj px aa er tv do ir
+\n tr o9 gb tt pp qa qs a5 ps rf q1 kj by ub ru ox co o8 ny wp wa ws rd kk b1 zc rl rz uo ts ig fh db qm q0 bg rr fu ld lr wb en nd cw vr hy rn qr en em au p8 so oh ut hz gq wp ow be ky wj dw t1 pl er wc ot na r9 wl ou un um wx iq sc e8 sn re rr f7 hz h4 ce wz qx wx kp px tl tx ai wq hf ec 6u rz og yt ok yy yp
+\n sa pp a7 qm qh of je qj lo ph wt h0 ji cg z8 2v xs zl mo ik hm on tu d8 av ot pn iv ez ja qn pq wy 7r mq qu p1 tu p6 ti ur pj uy ui qo i9 qa nj xm s1 ya fb 7j ro wn t6 wz yu iq yi go en pb aj f5 hf ug uh hk av pr wl wz im ja v9 u2 ks it br wv wn se ia o5 ox ei r2 ig aj sp
+\n sa tn z8 ew uo eh g8 zt wy 27 ff uh te en pd eh hv 2e wh ty oi sw xx 2p qs mx wb q3 rl eq aa eu
+\n d4 ef ta zq j2 em c0 vv wf kj dw uk ql y9 rn
+\n sq nm kl w8 ur kz c1 pc y1 g4 oi jv wr zy ew by se ec yn ti gq gt rd l5 ej yp tk da qz qx ir wm on q2 to ew
+\n rd gu z2 kj qk bl 6d wy nw xq iu 8t ri uc kq nx ql oa vi kd o6
+\n ra gr he wy q0 ow ti ia pb ha qr lv ms qu pu qw qr ml qt ep sv i5 of fm oe nl xh x1 xz u4 ha ao fc ug pw nh n9 qv kh vx uq w1 u0 ei if
+\n q1 d2 qz zd jd qb wj nt ah mj ea ed y1 et fj qe en b8 ty iv ht fv tn tm sg jb ky ai en us tl ud iu zj ql u1 ci ru iw tw
+\n fr ub h9 pd ub jk vh z6 wu wh wp 5z yt w9 w0 uy om tl rc r6 ax d7 et y2 tw dz se vf ii m3 lf b4 jf vr qw qy uf es qp en tl to ye ue ph e3 uy i0 jl pz oe qo zp wp ft ka zf qd wd kr qf l9 mm wf qx ef t3 x8 ex rg ev s8 ys it da rw al hn tc f6 fv nd nc ad fj nr x0 bx yq ti rx ok tb hx o8 dp
+\n o0 jq un xu q8 wo qq gg ta oj ec az dl bl wb
+\n o9 ij pq gu gp nv qk gg la q4 nw bo z8 9a iw wu q8 eh wi nt jk ut ys c1 r5 up y1 yl py oy ht gd td db qn cz qw lp re c7 dh j5 ia bz dj qr qt wd wf qi rt sv ul uz tl ta yr e4 tm sg pc jv hc hv lc xg xm br vf r8 na wl ou td wc up rj s8 e8 ir ys ii qk p0 lt ho wb x8 bv lw w1 rz ew aa rv ry gx o8
+\n tt hn gn un db fu uq qf d4 q3 pp ji lf wu bx q8 hx kb ny t5 bn hb ex yf ef yj g1 g2 to yk g3 ej sk hy dv qc gj qv sy bg wr na wy bx z0 rc rm ml ug te qp i5 ue oj s4 im oq qt gx sa gt l4 sv at v3 bq mv wd x3 80 x8 aq xk rg yp en gs us dq ak tz al tx o2 dg f9 kv or h4 jy k1 jo h8 kp lt os kh as tn eu ul tm su an tw sp
+\n za yi pe sh pv y4 y5 hy th jg qy qt ke ti ue qk yy ie cq wl p0 lw mf er w5
+\n k9 bt xu kc me is o5 z9 kb gv ur rc oe sk qn ve wi mm rn eu to ue uy qa xf by t1 td t7 aw up yf pr dk cg zr sc 3d at rw ec rl st zo rn do
+\n o9 z5 wy vi ya ea ee fo gf va ov ww rr wr lb ro qq vr gj nw ru ym iv s4 hu tm wo wp zs br fs wg ej du y1 yt yu e7 eb em dd pq v7 cr um ae oz 0z kc tq rw zl rt wb y9 xv tm tq di eo te gc
+\n tt un qs qn a7 qh je qj k0 o1 wr q6 wy ab q9 qm wr ea er eh pi hi sc hs m6 w1 bv lo zr tn yk ep op es ve xx sb ux hg sa gq qp wd n2 zh wf xf wj y3 wl e7 os u4 on ip kn ko qp s7 ly zn ba wu u4 kh f4 zo y9 q6 oh iw tq
+\n qa a4 gu a7 cp z1 he ma q7 lu dp w7 ea rc ee d8 y4 tw ez im ae bv ii qe vb zt lc lv wm ro lk qr hp re tw yv es fp as zu oe qu qi bp wg cp p7 v4 ek rd wc ar rj tj e8 od e0 pm h2 h4 in qf wu wi 19 bj rl rc ee yj et tw ep
+\n gv qd kj cd t3 c3 ih ws rg mc rx lh fd g8 gh cc vw b7 qe at j7 qo ws wg oy t6 t9 go eb e8 us u5 rq oe zj jy oz cj wb be ei pm og se w4 yu xw su yx if
+\n o9 ub rd hw gs z3 ql nq ru wg jc 1t kv mr zm ah dd jk w8 ej aq ig y8 pp fj li wq jj cc qr no wy wu en bx yr qy oo es fy pd tk ix ph yr sf vx pn p2 jq fs ed oy yk os ie s9 u5 ak ud gd uf kb xc u1 xm eu xw 19 wn vh w1 to ee er aa rb rn ru an r1 ei
+\n se kl 7h b6 xs ym tp an ta qb gn uo pt xi cl qp qy op vr ym ri ti tl i5 e1 e4 i9 ff i5 qp jx ht ql uo en pe ku h7 iw wn w4 ey ia si
+\n ql xt wi k6 ew sf eg up eh oy sq ja g9 i3 qe cv l1 qq bv w2 la eu wg ec ef oh fs tb pc xd qs nl qu fn dy oi iu yf re fc hj hk xv zn zz w1 ew
+\n po al hm qk jt cd ju nm li rs w9 ev ut ea 2f r4 d6 ey im pa nu wr m4 is bc xz w3 eu tb ha ft p4 ti to hr dy af i6 iz r4 jb x7 wj xg na rf gi at pn gd re wq qz ze bo wc vz sm zo my ye u7 oh dk w5 is yx tw fe dp
+\n jl za gk cm wu vq jc zc iu mb oe fo fp ic sc 2l hy qr eb p5 pf dq pa fy lc td sz oo aw u1 rj fl tz nx aq xx oz xb 55 y0
+\n uq wr lh jv ri i7 ss qo gy bt s3 u1 dy ox hg it
+\n ps hr lf jx bn qq up eh ab yl pn jg ng bz gd qr yw i9 j8 zi 3v oz at hd cx oj u9 rt uz ro ov
+\n sq ga ny se cj id rg r3 pk kv ee sh ek dk sz pp q0 mn az kp ei qi ry em ph p9 gw hc m0 cp ea mn yf t1 5y wx ol e6 ec u2 e7 uh uj uk av ql lw qx zr qv mw qg cq ww wb pw tu w2 mf ut gk af yo ie ob
+\n hn um a6 q7 af du r4 up tp ej sk lo le m8 rp eu ei qi ky op of tp ur oj hu tb dy qu gt tf oz wc s7 e7 ua pw ax nb wx wy fj wn 18 wv es yq ok w4 uz yx yc
+\n pa qg qh q4 fv qz kx q6 cp gb c6 pr eh id in qw we bk wn qq b6 qy qu es ic s1 og gn wp op qf ic ro os yp rj fj ag oc ay da fv wl qp f1 yx n7 ea w2 ly yj iq iw rm o5
+\n o9 ps d3 lp wr qc md e5 rk w0 pm gx lf ku qt qp to tc pk fb tb qi lh nt yd vt ot ra tg gd zx wx vj rq cr hm ma jp vg u8 rt ei it
+\n dx dv h9 rf qf uw a8 qh uv k3 ri is yr r3 eq uu tz yn y6 qc ps jf wq xe wx lc qr j4 ku xx nb 4z sr tr uq p6 uz of i6 s1 fs pj tc hu qu hz f1 hp lj s4 qx tg yp gs ob tz ds sw pm ug hm ip ql le vl wq tb xv eq w2 yg w4 st o6
+\n qd q4 pa z6 qz ia 70 r3 mb iu es r5 gh t9 cj vz qw mb ko vt qr qt gh qo ty eb kq n1 xb ef rp ek gu rg s7 rj sn ai hg o1 uj pr jt fg v0 tq tx ww bj bm ct w1 zi rn ox iw ri
+\n al rd w8 vp yd yk r0 pi po se sr qa l0 qk ir e9 hm kc rz aa w6
+\n un pq qd a8 z2 qk z5 ws bi xy qx wg wp t4 mj gv qm rg c6 w7 w9 es y1 g2 ej yz gg qc qn wq qw m9 wx qe kr 27 fp fq m7 xp 3p qr rr tr ij il eh au s1 uc fx ut qu sj j8 j9 ya nr rz wg wh eg x8 sl t7 yu vf ay ds ap re dh qg qh qj hz qk zz qx k3 cy iq ox qv eu nx n6 6r lq n0 y0 uq tb sy iw fm an
+\n yv dc qs gm q2 cv ok wt b2 cj wu mr zj kn e5 iu pz r8 pe fp ot tq a9 y5 sz ez cl wq qq wv a7 ln ky jd qe qr yx rm qi ea ln te y9 ev en eh iv tx e3 as tn j8 wf xh co fl nc wk xz es rx ee wh ub aq u1 ar e7 up it iu o2 wl ko jo cu pc wo al hm uq rn ul yz ro
+\n pw na wu jd yf oe qr xr sk wa hw ql wg x6 s9 u7 am
+\n uv tr ub k7 qg he u6 jt gs z3 by tn bi av z7 jc ck q7 2n ny cx km mk rf pj xi lh sf up yj to ia ab tq fq pm fd qc qv ps su qw fu xu cm zb bc qr qt tn ei rw gl p1 xi qo tt ed ef ri iz yw oh tc uy tv as qu l4 qr t4 wx e5 ae op oa em tz gd dq rw ug dr ux qj be ko cg nl je aj xw q1 vv ax rl w2 yt aa u0 eu ah
+\n dc ph sq jt ql un q5 cg lk w9 ur uy pz uo sx qv qq cc ln fu ym ho su pn qa bq pd wj wj yk ou wl rk o2 pt uc km ja wm ry rm ob
+\n gb pw qf we q3 ls q4 sy bl lg q8 t3 wl rg ed io ef if oi hp lo kw wy qw ei yz rt es p6 fp hi qo bn qw wg cy np uv yy oa uo ir of em ug x9 qh nj n8 ea u8 er w6
+\n ij dg cd lw gk wu zl dd eb eq sg ia am in wq xt nk wr xj qq p5 pd pk as sd fn lj jw fk l9 nt wl oo fj sb u4 gs fx hg o1 dr fb hj h8 xc yq ch er e2 aa af ah ob
+\n a2 o0 hn pd iz hw jg q1 jl qz ip le me wi bb r3 z7 g1 eh td sw g9 qq c9 vy ud qo es ec tj uw dq ur hj dy oe zp lk l5 fl wj ys t2 ej t4 ek rs sl yu oa u3 gd pm rw h1 pr h2 py wl 2p s7 wq 6r mi 10 ox o6
+\n i3 qw ee ur cy nx r2 wj t2 ub ir aj cl qm u0 oz
+\n qd qn un qz xy nq an kg hc c6 w8 93 eq ts g9 wy mg w3 rb 3f wf rw kt op es ef at em s6 pc wg bw x1 xl wg hl yk yo eb ud hm hl py wb u4 zp bj bm se sr sy ox am
+\n rc ix qs ls qy at ut pk yo ys ec hs lq xv ks
+\n yb al zf ws cn ac ih th ww vb kt b3 xo qe qi te ea p8 tn qd ci ix xk pk bg rc tl f4 wb rb ru
+\n iy qd a5 jq jw qh sw fv oz cj hc qq ya ee yn pr av or us iv fa qb q9 bh ns d0 qe i1 b0 fh qy qu qi ry os ul hq ri ix e1 ao p0 qt sf qi uh ll ko lx nz sg jz hq sh p8 x3 wg rd sx yo yp u3 pv rq ds tc rr wx lr xb wn ep hh bk yw q6 og yr yg si tq do if
+\n hv qa qf jg he q1 kj qz bh lr kn rj th kz ef eh av pp i1 ar gl ur lr bz xp yr ze qt tn es fl hw s5 qa ed t4 wz sx rg sv e9 fz hf al h1 av bg ym ee yg
+\n k8 nn jy q4 wd lf xu q9 a1 4v yd mb r6 yh pb ta g6 dn d3 pl j1 jk wc cn wy 26 rr te ti fa e4 uy fb gr hb kd lc qf p5 wh au fa iv xo hf ot eg ra wv tp ec yo ah iu pw hj ac h3 py k2 u1 wb rl rz yt er w6 ru af yo ep
+\n qd uq qh qm q3 vg qc c5 rd vp ut eq on yn ii xp up r8 d0 sz qx ue pl lx qe wr qr lm nh qt ha qo ki ri e2 tx iv ao s3 ow kp xf rh ya r2 rk cw nt by wd j8 t1 hk y1 ns t6 wc ev sq rq yf ux aw ch qs u2 zn sm rt wb bk yq dh 8w w3 rc yg o3 yi ox ov ir
+\n u0 q7 qb ml or nu b5 1l xb tr tp in qt hz so v6 dq o2 qh wl nb rv fw
+\n ss jr zf zh xt oy hy aw y8 js ob wq ny or vy fi en tb qi j9 gt ib ot oy rd e5 y6 tg th pt gq wz rt rl ew fm ie ri ir ro ah
+\n o0 qj h9 wy ee g9 gk jd fg qt 3d fu ru iz tl fd tv ad hl wp oo wf nb ez sv tl f4 dr oy rp
+\n ak il k6 qh q2 vd k3 zd bo lj k7 km 5c ut rz yd up ua is r0 qn zq wq j1 qe cv pw fu md bw yw qq ra rw qu ex ik at y0 ru ti yw fz ic ao ow gm jc i7 nf p4 fj xg kr br xk bs mb pk hl wl ta ez sv e9 us om rw ap gq wl k2 qz h8 gu kf et ru tq ag uz rp
+\n yb az dd fu rf hw qg we u9 o3 q5 q6 ag c2 o7 wa kh w8 vo mc yg tu ua uh ta tw ih hu fj su bg ww bh kw ry ru wy ky wu wi fw 20 b9 qo ik oa ev hw s1 e1 e3 fc uu s5 tn qy hz jc do ou jq gb kf pf xl x3 yv lz iq eb e8 os sn fx dw qg ql wc ka n8 gf ly se tv yk di si o7 r2 rp
+\n il mj vi sd ia y6 wq rm p5 ux ho nr ef ej wq iq fn
+\n ft cs uo io er ic tw ig mm c9 xk ab ze uw i5 s1 e4 pl ui f2 lj p4 sf x4 kz ej ez eb ov of rw dy av qh f0 h5 ki qx cx eb og gk oz uc
+\n ul io zd kn w9 y3 wt qq wp jl i9 jk ca h5 wx wb tm do
+\n iy hv cs a2 ee yz y6 gk kq em qy uq ts w0 rq rr vt pb nc q5
+\n qn q3 vt vu yk ej fp tw zm qq qy y9 hh wo wg rh ep x5 wk mr el l9 av hz w5
+\n hq qz wy cx rh ur w9 e8 r4 fq im fj gj dm qn gl jn iz l1 yh mz rw e2 qo wh nt wk zw t7 e5 iq fh eb sn ud az uv fh sv dq q1 ku zs eb ue xq rn o6 do
+\n ub lo sq wr d1 mt o7 ts t5 rd xe iu yg ot gg se pp qc js lu xt j3 j4 wt pc vz 5o yr qw zw qr eu db sy eb em fo i0 ad gw m9 ig ih lc od n4 pg rx bi ni kq wl aw e7 az jo mk bo wb ei mi ep wb eq di do
+\n q1 ub xt db wt ws ik pl ee or to ej ic is fr jk ls c9 qq yg qt eo rw tp p8 dy pz gm hz or xs bt x8 t4 t8 s7 oj lt wv vx u7 w4 et ox yo
+\n po o9 ih dx qa rf qf pd d2 kl ad lh kb bd qm bb b1 z8 ew d6 yg d7 ym ti eh ic iv oi y6 sz dx qn ut qm gz pj zw jj 4d bk wb lm xb ke yx oo qp yb yn en fo yw fp e4 aa fd jz qu gw qa zs nl v9 wf qt qi vg ni wx hk 9f sz tg t0 ga de re io av h2 jt x0 h4 wx wc fg rb rn nc yz iy zp ds ep zw pr xv rz yh yk zp do hc ep
+\n hb ty z2 qz qz zh gw mg kb ve zz ti tp py el jp tg qc ar qv gx la qr cn lr nd ng ve qt 6g ml op pd uq uw eh i8 uy dt ho j8 wp wd qe xm w0 x4 qk el e9 pb sm pn tc gt ce oj jr mi ds wb ym ew u8
+\n ij yb hn u7 cd gj co dp lp b2 r5 ed ti pn qx g0 jb jn jj we bl ri ot pi rb yc sv ty oh ph hh e4 hy sd wp ll ft l7 wh ca ys wf wb t7 sv uo sb sn ha pb sw de un qc bz wo en as tb eu af eo
+\n d2 k0 wr q4 q5 c2 sj iv pm g8 m1 l1 5s ij aa lb xm vf ej ta ar th od sm cw gy bu qd q1 u8 ry rn
+\n qa ux q3 mj ex yu zx rk gi rl ya is py am tw ja js db ps dn qb qn gn lc pe qq vr qr eo qi ec oa ev uz yq of in ho qo jj jk wk wd zp wf lz t8 tk ha pv fz pn ug o2 pe uk kv gq v7 oi qv wv dj tv fn fw
+\n dx a3 k5 um uq jd og nn q5 qx cu wp rd ws d6 px ac oe rb up tp ej ek ih ff qc gj qm xk b4 dz jg sq jh eu yx eo re es ul yw tp i6 pj ho qi qf sn og xo yv pk wj wb go ar uo eb ir iy pq uh qg h6 vt wv sn n0 rx af uz hx eo
+\n yv ub ty gn gu fu dm ca q2 d4 cn ad iw k6 bf zl zz 2o w7 uo ee yk ix g3 am fw oi jo se ha vs qn iy qq 24 bl j6 g4 cw jv 1l ei qy ke j4 qi ep of ao hh tb gm sh lh vc uf vu wd p6 xm qt kh rk l9 s4 wh mr t4 oi rf iq op ox u4 e9 fk u5 it re uk f0 kb nd qk ce jp lr cy js qd qb sb tq n7 n8 ed ue tn ox o6 id r2 it
+\n qa pa jd qn qg jt gh q5 lg ag qv ah qn vr da rh w7 b2 rz rx d6 d7 eg eh yl a9 ek dl tw sc hp ha su gz lo qe le ns kt qy qi 1h kp mz qu es yb yn p6 eh fs ok as im dy px gq qp qs l6 iv rl zw dr 4r hi wj rp t6 go s8 e8 at e9 f3 ak dg f9 qh pt dz ww rv wb oc pv be wq cs q1 xr xx eq yr u9 sr tb yl tq if hc ig
+\n a5 co dh bt lw ck lh w7 3e mp r3 rz yf yh uh eh td y8 fg pa ar va dm su q9 d5 qw re vh he jc 1g ib xz qq qw yg vt rn rb cb ry ym em i7 hr ff f2 qp rd lx wg lb kh va jv qi xd wh wc el un sz tf gu oz ae e9 e0 iu dr io dt fb dh jo um wx s5 oa kx ly rn oc zy f3 hb tt wb u9 oz hx if ig
+\n ak o0 qd q7 eq g1 y2 pt dk g8 qb vs qe dh 5i pt yh qo ul tp oj sp oq di uh zg xn rx tp tf ie f6 cg rv zm xw zq 5f md sr yk ru ro
+\n a2 tt ub rs ij ml ow pe el gd va ue zm sa pq lc yw qi qw lv ep qo uj ym tl ye hj s6 uf qp 82 fk y1 wl oi t8 fk pb tx o1 sk lm oo xv n2 ad fk n6 dp on q6 rv
+\n qf jf kk nm oz q7 b2 xo fw kj rh ua oe yl gh vd qe gn wb pt wi z0 se gj 48 of i5 oh so hz wp ae wg nc kg xf ev pv ov au iy az f7 qb q5 eq yr tv yy ol ry o4 oc di ep
+\n po o9 dc a5 jd z1 sq ws b7 ti r9 sl ez aw tg zm si ng qe ky b5 pp eb od jl ff oe ce qp gy yv qk r4 xf kw iw sn tx gg uh cq ql qa 2s mt eq rb dp
+\n qs qz cd dl se q0 lv eu yi rw qo uh uj ul en tx wo qd e6 pv gg je zx kp qc q3 ye en
+\n un qs qh se ws lf so eq yf ef y3 g4 zb hs q0 no qw j2 y0 uu fb di f1 kq oa ul t3 ot fh ak yf fv dt f8 jo sx wx at wn cs lq zc
+\n ub qa qs ik pw uq a6 pd dm d1 qm d2 qk cv zd bi wd ne ah qb kg kh ij 1p rk w9 wt r5 d5 px uf eh yk oy pm i2 hp st qn si qm zw we ls px lr ri qr sr db hp qu xk fy os eg en uc ur i7 sa hp vn qs kw dn od rh xj w9 wk ph ap yh el oi oo e7 gp ay s0 f4 gf az jy qk ql qx 1k v9 qc jq zy n5 kg hd ww wv bj hj ur er rn ry eo o7
+\n df a6 dn je ql no q6 ox wo zl bn rh ya mv e0 yn pr gd pi y8 i4 c7 g1 j6 wo rv eu xg eo c0 yx ea sv os wp qw wl ou un t6 u2 os of f6 dt f0 jt wc ja ae qv rm ds pq y7 qk ck aa ux
+\n db iz jk zd wy wh c3 zk 2o rj hw vp on ed ac to g2 r0 id ta th qb dm pj m8 np oe pu bb tc gh ml rq uf tu eh ye tx gv pk jv j8 lk xs kd fi mx be wd t3 mr wk wl td eb ie tz dw rw pm re fb dj h6 ql wz wx qx qv u3 vz xe ex 2w ty ew xm oz an
+\n ty a6 we wi ro lj bn rh r5 g4 aw jd q0 gz xy m6 wu qq et oo ex qp tr y0 fi au e3 oj gm px lg wk tu ek tg u2 ov em dg uk nd qj cy hp wq mi bj q4 ia fm r1 ei ie ux
+\n tr qg h7 qk jl kc jr am mo w8 e8 td gl kw sd jo qr vl gs qe b9 mm fh eo uh ft ik e2 i0 uu ff qu f2 jq v2 wg kg ek aq wm yi yo s8 e8 sq ab cw wt ck pb pn xl bj yq wm ew xq su r2
+\n qk wa q6 jj ws ut gd gf ly ec pj sa pd wl e5 wc da kx zk zz zb wv rm te
+\n jq uz nv ql as jx z8 q7 o7 yt rl ea e0 ym y2 pr ia sz sq sr qq qr vk oe pe lr bl ll rm yx y0 eg ti e1 ue uu ui jx zd oq kd rg lv lb r1 fx ro me ts ay f6 fc io qg py qj qk qs ky qh y9 ok o4 am
+\n qh dl jt wy a2 yk y2 i4 zq kq we bb dg m6 qq zw rt ta tc ff xs xd qf g0 1d yg du wz iy sw tc dd hj hk mh ov zi wn hk ee yj af
+\n ra ak uw q3 cb ji wy fw gw t4 mu ts qw ww rj vo rl yd ug d9 gj i3 zw qw wy md qq bv rn qy pd tl ic p9 hr dr hh ui sf f1 i6 ws cy es ef t5 kr ek oo t7 ec e8 u4 od dq ji ch jr zm jy q1 zp yq 2e og yo tm
+\n tr tt qa qd jf pg qh jr sw ao q3 qz za wt js bl vw q9 ws uu w0 ya pl yf rx ee tu r7 gg dv it lo ww up js qq qe lz eu qy rr yb ri ay ye ta tv im sh ss uk qd qf bw ro sl wl t5 e5 um th ha fx re ii fv je hk ot cq km h8 ks bk vl qn xe te tt rl u7 iq ry ag dp o8
+\n sa z1 qj q2 nn wr z5 mq xu q7 gv t6 w7 r4 c1 mb sd ed ym ot ta ht ts tw gd tf g8 se ar gh fh qv qn zm hs qw qe oq wc xj vz xl wo rn i2 sr rq at yq uw s1 tx hy fm pc wo hv gy vu wd lc ul p8 wk wk el oz oa rh gp pn gf fx fc f7 rr dy x9 uk f0 py wl v7 cr ch qs wv nz lv lu 5o xe ym ly er yi ia gl ox r1 dp
+\n uv qd hm qf gp k9 kj we lf bs ej 2i el wl t7 rj w0 rz yf ys r8 tp py tq tw dl im qc db qq sd ry c9 oe if qw aw qu uh tt p5 p7 p8 oj zi oe qu qi lk j9 sk zs ka lc wh wk zq mq vh t2 ej r9 mr ez t6 e5 op rk ga pb dq ap f9 py qk qz wc kd pv bd sm dr u7 mf o3 yk di r2
+\n po a3 uw q5 q7 ck kb zj td zz yf jd wq xh ld qr w4 p1 ij fu tp qq sv y2 yt t6 e5 op dw iu pw jp ka qv 4u qf rm vb w6
+\n fy a6 qg cs z3 ql dc jz wy me cj o6 ba kv wp w8 ea r5 uo fw ib ig g7 gg sy bg qr cb cq ro xl xv ex tt ru pd hg im oq gq ao rl pl aq sz t7 e6 os uf ug gg pr ql qz vt mj px wb ci qf ov be bg ww mp mi rz u7 w3 ei yc
+\n gh cm ca rg uy pm y7 g8 lx yc qi re uh yn uq eh tz ph wo cr sv fp kh sl oi sx ov ga iu h1 je fd rv qv wi jy yy ry o4 tq si
+\n qd iz qh q3 cg lf wy xa ez eq om ug eg yj fo fp yz qx qe wb or jg xb c9 p4 tj y0 iz tc oj tb i6 p1 ka zf qe yp wj mv ra ez rd uh pt zl lm sz wc lr bq oc zq sr af gl ei ux it
+\n a2 qa h9 qh q3 fn kx ve wz us sj yz fd g7 vh c9 xq xj ln tz wp wf kg kk by vf j9 5y un yi e6 rh sn tx hj kn rb
+\n un qs fi qm jk js bd o8 bx vt eq ya xp yg pz ym dj fp tp ta oy dl qc cx qq m1 rt wn d0 wm yr qw aq qt tb ha p1 uk yn ef tj gv im sd hk pz jx zi wa wf ba l0 wd mq ej wv t5 ek iq pv ov f3 em ak rq hn hh f7 uk qh ot ju ng ji h7 wz cr v9 bj bk rc er ia is iw ei id ov pu hc aj
+\n pw gm qd jf u6 z3 jl q4 wt bi lr id wa wz w8 ya ev ew r6 yn ee io r8 ip ej td im si j2 jo d7 m4 pv iv yg qi il ti i6 ta ib ap fb hz wd vu wf wh kt kh og kk nm rp ti ek ns t7 y5 wc ae ir pv hf ub wc ho wb wn mi rn w6 yk tm te rp
+\n o9 un h0 a6 pf iq xi tg w8 z7 r5 om oq eg or y4 sr fh zv vw zq tc ws rq db rw eo ym tl fv i9 pz jx j7 hx oi qf x2 l9 x3 qj by to el sx ys yd ao az uj hl dl tj nz wn kg kh on wv w1 w5 gl ei
+\n gv az ql nm rc r7 yl ja zn q9 xw no iz qt pk x4 l4 tg u3 of zk wc go qb mo eq u7 tv rm ig fe
+\n o0 ik qd um qg k9 mb bu wy bx ny ws hm ea mb iu pe eg ey sh uh g2 ic iv aq td qx ja qb ha 2j lp xr wc vl wn wi sl tx qt rq ec vw of uw sm ic qy j7 ns hb 2q kw wf vp 1f x1 5c zs y1 rg tg oz e7 fh eb ie up e0 ap ve wq zz cr wz h8 wv go ly fk az pr rz h4 ew w2 ok w5 ia si ro am
+\n dv d2 cd qc zt 25 xp wd te es sh eo wn f4 wo tv oc
+\n uv qd qn hr bj b1 mw lg io sh lo qq xh m6 28 rn xx p7 im qt jn jw bm qf r1 mn ny ed em ii dd wn cz ds vc wb hh q4 yw ur rt ie o6 ux r2
+\n rs dh z1 jr cb vq r3 eq om y1 sg r9 if tw qb qn m2 vy dc b0 ik fa ib aa jz qu sg qs 1s be of w9 oz sv t9 oc pv rw dd o2 dh lq ka lu 1m qk q4 y0 ye yq w2 w6 si ob it
+\n o9 db iz fi qn qj mw 3v wp li e3 km zz yy mo ya rz mv yg r7 yh pc or r9 pm a0 td ih db lt pg jf gl re ww qw m4 j5 xi wi yd nh yg qr rm et ey ug re rr y9 y0 sb tu od ay of iv oh i8 ok uu sf gq lg wa nn uj qs cu kd xb wg cs 3k yv hk pn ii hh cw km wz wx wc n3 jr wm qn bd zo mo jo wm q6 w3 rt an hx ah am uc dp
+\n ps pt vq kc bs vu vo xu ee ib hv wj x3 nu ud yf qh wb lb gz
+\n ra o9 qs ty rd ps db pu qj u6 k9 nb qk oj ql wt jx bo ri xo o0 mk rh bm mj ut mb rc yn et yl pm ih i1 g9 qm xq oq bj wt jp xu pt bc eo ep qi ky sv uk fy ri i5 im dy hk ui tm f2 uf ug qd kf nc s3 fs 12 t2 ro du wk ek yt ej t7 s7 oc ay s9 pv fl s0 gf tx fc ac py v5 qk ce qz cr jp ck hp u3 zm xr ii yv ea cf hj ye w3 tp do tw ux
+\n o9 qa ik wt kc q8 wk sp yy w8 w0 ys ea om tu yz pn fe ae g9 ps g0 i4 qb fk qn qm ut j1 d6 4d cb vj vk xy j5 be wi ve qq gf qr j3 ug qo p4 sm s2 ut fd pl qt jz ui qy qu qa nk fj iz xh wk iv qz fb ro x6 ti sl rs hc oi wm us ai dw o1 hh ab qh qj ju ng wl zr vk tw 5a vc hk md yr u7 w2 yt tn eu ul ah
+\n uv ra qs ty jh q4 o4 bc vr o9 rg jz mc r4 ui g1 ey g3 sj am sq fj qn it xt ln dl jh b9 g8 dv qt yz ea ue ss w9 wj kk bi ym tg t0 ob ys iu uj qk nf v6 nj ox qb wy mw 1n pq eq w1 rx rp ge
+\n a2 pa e4 xy yd sj vs jj xj lm qy qp ri ux p8 pj tv xs wd wf oz gd sw rw uj uk qj k1 xx um eu bx my em ey
+\n az h0 qz iq bl kb xp yf y8 qn rv hx oc re gt k2 bo qg cf rl
+\n yv uj d2 mq hx ws w7 mv yn r8 ab an ae jn xw al up be qr zr ep re qo ec ur ap hp pn wp i9 rf wf vo qk t8 eb yd uk kv ww wx wt ox kh mi eq yj oz fn ie am rp
+\n ik df qg jg k9 wy kc ro wi ve bb rl ew io or eh sq oi qc qe d7 m4 pu gd db oo yv yq ix eh fl pg ib hu pl cr fr xd cy ke mx yh wk ag hf hk qg sj we mz gp u4 ak ma rz rv af ox di yx ob
+\n hn pa pw qd il qh q1 z3 wr t3 wo ws vu uu ld pe fo dj ot dx pp vl qq rr ls j4 fs dl ve c6 rq ln xk ec rt ty ik y0 tu yq fz s5 sd sh jn wa uj ws lx qr ca rz wd nu ek yy yi y6 uo os up f4 fz qk h8 qc wr at 18 ca ww rv sy ox o6
+\n dv wu wo uo m2 we rp b6 qe ik e1 bq w8 x5 ez fh u4 iy jy wu li f5 u8 w3 yl te
+\n sa ds qd no q5 ra jd qo ru r7 uo ar ud on ak fv dg wl qx qv ye yl ep ge
+\n ss rf qn bu gj rj uo yz tf m1 kw zr oo y0 pf tc dy qu v6 xh t4 oo um df je qh dz v8 ho wn wo 0w dj rv o3 du ro
+\n ij uj k7 me lg ih hv ws rj pl sd uo y1 yk d0 pt y4 g4 ou tw sq td fj ha qm qq 4a kw d7 xy m5 bx c9 yx nw tr qo uj fu en p6 s1 ht tb qo zp kq x2 wk wj wk yt wz sz ae iw ay fk ao ug pq qg k1 ql xx qc cl qk 56 bn oj yt et ut uy tw ir yc
+\n k5 pg cp z5 wr no zd tk ej an qx gj i3 su we up 3q yq fx ib tv qp ik wj yf u1 os rk jt qo qx n9 w1 rb
+\n k9 uv gs wr 3b mh km bm we w9 es or yk r0 g5 aq gf nq qv ll m5 yd zq qt qp sv ed p5 of eh i7 pz hl sg jn wa m0 nm kf w8 wj de e7 ar iy pn ly wn fx w3 rb ey am
+\n o9 d2 vg gk ex rf rc hy qm j4 ga qw rm ls yl cm en tl tp fp tb i3 qy qo j9 vn zf wf qg mb kj qi jb mq wl rj s8 lw um zt wb f4 xw f9
+\n ra go ls qx wi c6 b0 rw g1 yz fe g8 ow qq ra mz ex oa fu iz tl uc e1 p6 x1 tf rh tk fz ap hl qh k3 xb mw zm yb yw q5 aa rp
+\n yu qx sc xe j2 oq gs i6 i9 l0
+\n yv ss tt gu fi qj bt ql ls io nw gk hl up zv gl ni xt wy dz qe ud nw rw qu uw e4 qy px qf zw za ty ek t7 pv dg ho wn uq rx yx ep
+\n ga 1w ld wy o7 xr pk r9 g6 hu jg lx sd no xt wr zy ku l2 nw 9r rt i5 to tp tc s6 f1 ud ko xb rj qy es t0 f4 fx ii rr hm hj fb ji oi n1 vk ci 9e mt yc 2r tv gk yp ux
+\n hb hn k0 wy m4 w7 rc ts y6 j3 qe ve qy rt so di qo dp lk xf mq wl em f5 pr wl wn 3k ew yt w4 ri
+\n qa ss lq wr bx t3 r5 ed eg sx dn we 7n ra qe b9 rm wd rw eo oa ri e1 e2 ut ap hu qo ws uz ai tz nl cu wq ln wn ie aj
+\n yb rs un hm dg qm qk ao mw fn kv ur uo pj e9 sf ia tp tw a0 td sx fg su xq m1 om na vk wy xk em l1 z0 nh b0 mz qy p2 ru au iv p9 pz ug lz xn wf xg fk zu wj wd u1 e9 tl ak hf sw o1 pw dt gq v5 lm h7 nn nl wq uq zt o3 ad ry id ig
+\n a3 pp dv qs gn u6 jy kk io dt wt ck rg ua yd ya yh ax ac y1 pe pc pv fp fw dc qv zb dn q0 ju jj m1 ui lx qe qr t9 ja he wi vw m7 l1 jn qe wa qt xg rq qy yi qu p3 yb ed en tz so s3 tb ho fm px gw zo lx wf sm mc dq wj yg l4 uv yk tp t5 wz ol fk f4 pe kc dj wz qb zm wi br zk ww ty 6i vm eb lt eq w2 ey yp yz o6 ei
+\n fu ga io nt wp jz yf rv oe eh pt dz ih sx qx g0 qm hf xe lz gc d5 bh 2z cn d9 1r sz li bv qe 6d bb er xv yx p2 ea tj p5 ay uq dq pg oh qt s6 px sh ko qa nn oa bq cs kk hh cr wg tu y4 t6 e5 oz th sn ov u5 dw qg dh uk n1 zr qv 3d n4 yx xe wv eb h4 yo ro hx o8 rp
+\n ra gu hm a7 jw qm qh jr gs k0 ql xt q5 dt ru wy k5 wh fw lu kb am m6 bx vy qq ev rk 2s mc yd es io pc pv g2 ek tq tw in ih ae qc d5 ui qe wt qq m8 vr nb ee hu rw rr tt ed fi em e1 e3 hh hi sh zp qo wp l4 ws qf qf pg eg to un gu u1 t9 ox e7 u4 od ds de hh py ql h7 gy js vz gf y8 uq se do ro rp
+\n qs qg gk ta bf r3 hw r7 r9 sh ua g3 sq td g7 ha lu qw xr wy wu rz ko bb i6 uy as di qi za hv jw rf 1f 2u va ap qi rc du wk yt t7 u2 ob re ax v8 cg qb wy wn kg pn yi rn ru
+\n gr ra jq qf go ga jh gs q3 tm q8 k7 o8 mj ym er ip ua ej hy i1 dv qb vg cv m5 wy xk g5 wi ng w3 3w ud rw ug ep hq ta fc fd aa i5 hx qp wp v7 qs l6 l9 l0 jn ty t6 ie rj tk od ys on pq tc zl nh qc xv wc cu ks ei lm vm cj yi ad r1 si sp
+\n qa hm a3 ac q9 na rj if qw rr vw tj ib su qu wo dp j0 wf pf 2y wk ym ra wb ae ga gs f8 gq im ar pb ec f9 yu rm
+\n t8 ej an y4 td ez ln z9 lj qy sm uw dq us cp nu tg vn
+\n qa ds k5 hw k8 k0 ql hl 1r wi fw c6 w7 mz rj xy r4 e0 ym yh eg r0 us fs ib oi qv q0 ww lp gm ln bx nc qi l1 qe wg ea qo eb tk eg to ur jc oe dp hv wa 2q nk at rg wf wg ca xk jn pk yg er ot uv wb aq ol wx e6 ev sv uo vf eb ah ud da pe qg jr kn ju ng ae wv n3 iw ly kf cl pb wv tt vn eb vm u7 ew aa w6 rm gx r2 o8
+\n q4 q6 vk d6 eg pc pv r0 tw i3 q0 we tw sm e4 ow sn kg up hm qx zv nz wm u9 ul ri do
+\n po uv dc qs qd hm qg q2 jj sw kl me q8 xa wa xf z5 yd r4 rq sf px ti ia r9 yl dj dk ek qx i2 sr qv qb lb wi nf wu qe tv fh qo rr yb fy eb ri ai ok qt pc ud qi qa ws qs lc zh nc x2 cs t2 di ke wk sz oc yp s9 ys ai ln wz cg wc wv os qb f2 ec y8 dg wm rz yr ee rn sy du su eu fm ei o6 dp hc
+\n ft qj q6 wq up ut lg er uw db ll ws of og e4 1i r0 wx fh th vf re hm zi
+\n a3 ty pw ph cg uo r7 oi q0 lb c0 vl xx mh hu b9 qy tt sv p5 eh to hh ow tm oe si sk oi gt kq cu vi j5 wf el tf yu u3 ya uj dy qh ql ct wc el y0 o3 o6 if ge
+\n a3 dd by wt lf 2v bl 7c bn cf yo go yf ii et ey yl aq aw g8 ho i3 qb dn qn lu vf vg 2k le ml wy t0 4h xk qw b7 bb eu xr qu tt y0 os sn tl pf og tz tx pl ss us xd cu oa qd xn ke qf vp kh ny wk r8 ej rd t7 sc e6 rh ud tx al gg re hj ux qj gw xx zv xm iy ca vb yw en oh u9 aa w5 w6 ul oc an uc
+\n qa un dn hq hw d1 jr jy kk kl wt kz zf z7 cm q7 me xp wp mj rh ue e7 ys rz eq ew ed xp ee yj y1 to fw aq po i2 jn li on m2 na vq wu ck er yu db yi gl ty eh uw fp tx e2 fs uu sf jx oe jb qp cy bn qd wg 3z nu j9 mr t6 gp pv ha tl ai fx uf fc kx qh gw xb zt qv qb ir cq vb y9 ct ol fn ah hx sp
+\n db wt cm ch jc wi dd ys on td po y8 q0 wq kt eu tc tv or fr s3 na e7 uf gg re f5 tt aa tb ie
+\n a5 jw qh q1 qj oj xy my b6 es yg yl y5 zm pv qw qt qo ea ri ao in s4 gv i0 ad lh wa qf gm rk vs oy r0 ez ab lm qs qh ry ox
+\n ga ca z6 nr wo rg bm vu uu rj e0 ui io pe eh d9 ab tw fe tf fk wy ln md rk sk qq qw hy kp dc qy y0 p5 p6 p7 ic pg e4 jb ge wp qa bn xf ks zi oy e5 um wx ie yp fv je ng oj ja v9 bp qv er an pu
+\n a4 jw a8 o1 q3 un gh le nq q8 ig rg rl ea io er tu fp dk hy sq ae lo qq wt wy 5i xj cw dz oo qo yn ty y9 y0 sb ef tj uw ta ur i8 tb oq af hz qi wo sk zs qs vi wf kt nb y1 oy wk r0 ol ex ec tg t9 eb ap fv qk ji cr s6 et xq bf ep mi ax rt yu iq af am yc
+\n gr uv hv tr a3 qs lo u7 jy qz kl z6 gk gz ag kn rg k0 w7 wr rl pj ii yh up ac ot d0 g6 td y6 fr se pp dc g9 cl gx qw pl m1 ii qr vz oy nf eu eo p1 os y0 ri ix au uc ai fx p9 nm jj lz pa kw ul rg gw qh 4m eo qc l5 rp oy ej un yy yu t8 sv ud fx ac dy av gq qj ve sl bu th u3 rn nz n5 zm yz bd tx el ex n9 es rt rz rx ol sy rn yo
+\n ph cb jx wu ib vb ih ty oy tl vu
+\n df qd k7 z2 q2 ju jz zf cm mw yr gu rx yh ym ef pc qx jd q0 ow pw wt rj xo mf xl qq qw ud tw ku ik oa od ti hk f1 xs qd wf dm s2 ph xo ou sx ae iw t9 eb u3 rk ak hf dw ax oe zl zz wm sm el cx cw lq za tu yw rx yu rn fn yi ei
+\n a6 ql wr jx z7 wu xi ym fo if a0 dv ww lx zv dk tu sn hh ff hu zo ws rf wf aa ni kq uv t7 um go e8 ob sm tz hl uc zz ol lr kc n6 bk ry if
+\n qs gm tn rp iu pi qe ec to l1 wh ra wl it kx fd vx q1 ri
+\n gu pw qg we d4 ws q4 cn q5 me qv zj zl ex wr xo yg r7 eg et ey us iv po aw se cx az lb nz nc qq ew rs rq yx ep tj uq eh fz hg gv jc di wp sa nc ya cs fv qz ti wn aw e7 ox u6 pn re o2 hm fv qg hl dl v9 qv tz 6y rl ye rx ur tn eo
+\n gr qj z6 ld tm jw hc ed y5 se ke ht tn jb 12 yt ek ao io wv ew ey fm tw ir
+\n gv fr ak o0 gb rd dv gu qf qg qh jg ux qj ph k0 oj wa jz bi ja eg c1 fe qn b5 rs rg b1 vo z7 us d5 r5 ii tu yh y1 or ek sl pm hy dc th sy ww ze vb wt m6 iv mj qe 6f qt gk tq ru yq au ap dr hh qy sf qa ik kt wd rz ej t3 ot ej ub wx oz th s7 t0 ag ga pv em fz sw o1 ip qh nd h5 et ho cu yz tq wq wn et tn yo si ov a1
+\n ij rs rd qd pd qg qh z4 ql ip nq q5 xu bz lh o7 my 3w xe ws 2p w9 rk es er d8 pu y6 qc gl bv qa rv qt rb os ru fn qy qu hx or wa qs at zg mx xo bg yh ec os eb hd rw dw ip vw ki ok qx cu wb sn wm yj o3 tm ei ah
+\n tr rd pq qd um qj u7 q3 cf db k4 gl mr gw c3 bs k8 vi 4v kz cg rz et ey tp fq y5 el gd dx qx hp mn cz wq xh m4 av t0 vz m6 qw tv rq ei il sb tk eg uq tc wo qo zs rd nx 2y fo j5 l0 l1 hy vy t3 t4 yt va y5 rg e7 uo ox at ir ys hd uf fx re rr ac kc cq qk cw h6 kp xn zu bd cz ca pn pq w1 rx vc w6 yo is fw ir ov
+\n ra ij a3 qs qn jf qm nv cs cv kz q5 um q7 q8 km ya ys rx yn d8 sh pt fe se js ue rk m7 wp et ei qy to tz s4 af 3i lc wk ej hc ex t7 oc sm s0 tl fx re fb jr jp qc kc jr cc w3 yl oc ob ep
+\n sa yb qn k8 lf d1 c3 wp vr wl yd iu kb sz g7 mn jm lz sd m3 lv qq j1 ex qo ry ru em pk i3 hi rf fk nc wd vu yt td sc tg s9 tz tx dh x9 qh ku dz my yr w3 oj se ei gz tq hx ah fe
+\n w9 rl rc or fq a9 pp db gj hs lc qr ec p4 ph hb x1 ez u5 qx ea 6t tn
+\n a3 qa dd qf qn qm qj vj wi ag wo ig e3 wz r6 d7 ax pe rb ey r9 is ot tq oy if hy se qx ar qb vd qw qe np xy nd wi in gj qu y9 ev ti tb qt px ud wo ll cy wd hw kh fp wk wg wh ym vo ub rd t7 iq yo ox eb yp ys au u6 rq ii io pe qh nz vl be n8 wv hk og rc er yu u0 rn yl is do eo
+\n dd nn oc el yu tl rc rv r7 y2 hi qc qm wu cq qw xc kp tr fu ib zi qu wp vi ci qj nu zw t1 wl fh ev os f4 f6 f7 cd zc qx zy wu bs qn u0
+\n o9 gr fu a7 qk xu q7 wp el yu fp ou y5 pm pp qm jm st op uc fx tn hl zs kp bq p7 hi ys qj ki qc qa n6 oj ey w6 yk si
+\n q7 xo sr he uu sd s6 gy ws iz fk sw al v6 lq fh ie oh uz pu
+\n of ch zj rk rx rc g8 i1 jk tv ul fi e1 ic sp in jl jv j7 nm rp r8 go hf wx tb oz tw it
+\n se kc tj rx yh eh td pa zb qv c8 j5 ri eq b9 rm ik ev ul ti p6 en ok tn wp jm ws ke br wj rp en gd rq f6 ac ab zc rz ew tb ro
+\n qd wr d6 i3 j1 ww if qt yn fd e4 qf j5 yh t8 u1 ev qc wv pw u7 oj ok yz tw o8
+\n un pa jd qh qm dz pi z3 ny gs k0 wt xy z6 cj k5 gl bz d1 fq ye yr rh t7 ot if g6 im pa ps fk zw lx kr lv na wu vw eo cm te qo tr ec ty sb y0 i5 to ye so tc i5 sg ct qs sc ws qr xj 2u n5 rl cw dw ys qj yn qc y1 sl t9 ox sv s8 ya s0 tl ys rw tx fx ds rr cq cd ql qa au qg vg rx yt iq tn yl uz ei si r2 ob
+\n rs gm qk gg m3 rj eq mv yf sk gx ve eh iv i7 n3 pb uf gh uj tg ox ww bg oz su o6
+\n ih a3 uj rd qs df h0 jd d2 kj q2 ap wr ol nw bz q7 fq ir ra w0 eq ya r6 d6 eg ej pn py pp sr qb jb wq ni xe we lm be xo w2 qo mj qr hp tr qo qp ef of yw ai e2 i8 fb tm do dp i8 l4 wd p5 sn pf gr vs rx kz vh t2 wj ot ar t9 at ir dw qj nc cw fd sx qc mz lt pv br wv dr q3 yq vn ye yw dk oh rc w3 yt se ov ge
+\n ds h0 jw he qh jr jt ql me na ah xa tf wt pj pk om 97 rc yg ym oe yj eg dl fe sz g9 lo qq qw wx rr c8 ns vq m7 xl gs vr qw qr kq qi qo eb tk ue dw e2 i8 i9 hy hh qt f1 pc vv qs bn ij i0 uj xh wk qz ns ej un oi yi rh od ha tl re tc o1 uh ac ip qj we qh lq eb w3 w4 ia oz eu ri uz ep
+\n fr ij dl qk z3 qz wt z9 gq mr wo zz rd dd rz ee sw pp g0 sy vg ww iu pz uo cb t9 ld qr ei yx rw es ts zi wp wd gw wj hf r4 tt x8 wl t6 hc gp eb aj ai iu o2 nh qv ey kg dp wq f5 rt cg yw sr tb gj rb fm ro ah ig
+\n ss ux q4 ji xa mj mi ld rl pj r4 rx yg ti ix a9 ig gj j1 ww ii qe j3 mz vl qq ye m8 b8 yl qp ik ki eg uq fi ok fb oq fm sf oe hp v4 nk wg mx kt vs j8 yn wc wj ot td wn iw os u4 tl u5 rq de io x9 dl ql n2 ji wb if hx
+\n iy jk ql q4 wt kz fb q7 vy w8 ur ax uf ym yl py ou dl pm in sq ho j1 qr ls j6 ic sl ko xq jm qe qr qt yc es ry pf he i8 s3 pj tm oe qo lk wp j9 nb yd bu rs e5 yi ar rh ga ud al hh oe wx s6 do kv be pm w5 fn ey du do pu
+\n po rd qs il rf uq of jf nf ih w8 b0 ur pl us ed tz tu ef rb sj tf ff i1 pa dv ue fk m2 qr wt j5 c0 vw xo b7 p7 qt zi wo gt qa oo qd bt nt zq x8 ou e5 u2 fj s0 yd sw re cd qx jp wc ja ga jt bh hm eq rv hx gx
+\n iy ij pa gy qd qg he d2 qk d4 qz q5 nw wu am qm ft w7 rq yn ef oe pv ek fq sk y4 ts am fd qx q9 jf ju wy lm zw wp er sy qu oa ta jl ss gq fe wa p2 kq ws 2w dn xz t2 ej rp rs yy e6 iq ag e0 u5 tx dd pq fv jr qj ku oj ql wz fd s5 nj qx zt 2d qb nx pm ce f9 w3 er tw rp aj it
+\n ss qa gm dm qk c1 jd k0 t8 mk rk tk om yn tz r7 px ac av ot ts if fw ez y6 se qb ha su qn cl wx we qr zt kr mj rc qo es tj ym iz tk i6 fa i7 s3 pl jx du qu j0 ws v2 wj ys yv wd s4 wf nm dt wv ub ez ta wx e0 fz al ap qg wr ar wb fj n4 cz qg wq fc mp yq ev se eu am gx dp te
+\n a2 hv yb nv h7 jt lw xt lu wp yr rg 2o 93 uo pr ej ez jb lz ww az oq bk b5 wi qw rq hs te ea es ed fu ti uz fd tm jc do qi j9 zs j0 wd xv iz hr wx el ns oi t8 sc t9 sb fk hg cd rb mz wn 4i ov ln yr oz tm ro o8
+\n iy pi jt kz st tm rh ya b2 om ef eh tp el in sc qc g0 ps zq nu pq j3 oe a7 ja js ng tc qe pp eo em fc s3 hh i9 jl qy i8 lk wa ae 1p vh ox rk em hf dd jt rn tq is oc o6 so pi
+\n qs gm qn gw qb cx w8 ur yp up uy ek ez ar sy qb hd bx rl qt yi nw tt eb fl eh pg oh ib qy qi bp jz lf eo ph wh oy y5 om az tc ab wv wb kg ww eq ok aa uy w6 ag ig pi
+\n ra a5 db co qn d4 bu qz kx me nr q8 my lp t8 gu rk yn et y1 ej g7 yq d0 j6 b6 qq rn kw ei yc uq e2 s3 oj s6 jl kf rl ny wg mw t2 co el yy ez eb e0 al qg km k3 n2 zr tk qb n5 n7 et tm ul
+\n po uv a2 o0 rd hq dh hw a8 d4 wi z2 vt ww kb d7 pv tq fa ta dl oi y6 im ff ae qv sy si wq pq bn b3 lm b5 wi ku qu ru ul ri tx fb ss or sk wp qd w7 kh nn es hy wh rp um sx e6 rh rk pn sq rr hm dt ip dh pt wl h8 qc vj ly bq zn gd wn q4 hj yq xb mf ok tm ge
+\n ub pa fy qf dh qj q4 wt mw cm k5 gw kb el w7 w8 mx ya ii dj dk gd dc gh st qb iu jk qr bz vz ab b5 mf pu qe xd nq eo yb pd i6 ue dq e1 qo wo sp 1o n1 4v at qf fi of xj rj dq ew nm x5 wh na ub e5 um sb ob em pb re ip x9 h7 zv xm bw 1v mp zr w3 xm ee yg rv rt ia is ro ep
+\n cm bp hc rx y4 sr q9 jj rt qo uk ev to ff so bg eg y4 l0 go os ay tx qh hl qc wb
+\n z3 nn o9 xf fs gd g8 ns ec p0 tb wf uv iw jt wr dq bj u7 e2
+\n da td ta tw tf tt ay dq sf gi ae rl e1 gk af dp
+\n un fi dx wt m5 vo ys j3 i5 ad nr wj mn tg ox bs ia
+\n hv yv qa qf dg qj do ek w0 is sl ez sr i2 ww we rl vr qw y9 tu p5 uc hj i6 ud ws l5 qf xh kh lg wf wj uv tf t7 e7 dt qz ka xn cx xe fn it
+\n iy yv rs qg uw oh q3 lr vq bz ab zm wa ds b1 w9 rl rz uy wy om uo ef fo py tw fe qx i2 qc qb qn ww vg ke wr j5 j6 oy qq ng sl qw mj yh xf yk xg qu te p2 ft y9 uk ym uq so fx ff fn qy du f1 na lh wo qo ge sk v1 wg mc dq wj iv 1h pk 3s ej oy ek td ex ae yi t9 go e8 rk tz ud rq ax hz dk qz kf wm yq cy w4 h6 rt ry tn r1 gz ux pi
+\n d2 we aa cb o3 xi tu ti gd wq pl xg wc lm de e4 sj hc ic wc ra wc go o1 dd ip wl in wx js tv rx yi yc
+\n ty um hq co ux ql q6 wi bc kn q0 r7 yz ib pm g7 po qv re we bh 8j ru xo ra eu ud qy uh ec ty ry yn hw sm e1 pg p0 dr qy oe lc x1 kt xz pl t4 el t5 ex sn us dq rq ao f8 pr md ql v7 v0 n7 kh vn wm u7 cz et w6 gl yo ei di tw
+\n ub jg ph q1 q2 d4 q4 qz kl ld cn ji z9 ro ek gb w7 rh pk ea ax yj pv ot yl an sl y5 po im i1 zb fj qb i4 gl xq si m1 jj lb l2 ul sn ue s1 ta hg zu lh nd j9 ci qu wd bh ef ro ra aq t7 ex t8 od en fz fc df h7 qz n1 v9 zy 4a tc bb ea yw mf ia yp eo aj rp ob
+\n o9 qa h9 dn vo a7 qj jt ji ne kc cj zh wo q0 w7 e5 ui vi ya wy c2 r6 ui px yh y2 to pr ab dj pn a9 pm tw sq ig hi bg ni ry lv wy ic bc li qe im dv rq xy ki i5 fa fv uu af do vv za l3 bi kd nx w8 nt lh hu ra ub un ec rj ua fk ir s0 f4 uf dw jt k2 kz ml cl km wv vv es tb yj w5 rn yo af ru ah ig
+\n vg wr zh wo on ew ef ae ha id uf eg p9 ef gi al ng 16 rz o3
+\n qs jw qh cv z4 ok wt k8 kg km wa uu us sj oy iv tw jm c9 fo nd 20 qw w3 yi re qo yv rr op qp ue oh s3 uu px jc hx wf v2 br ep wg pz t7 t9 au re zj kn xc bo kg 1v hb wv tt u9 gj yu ry iw dp o8
+\n qd wi ij rh ef fe jm kw xj wh uk ef ti e2 j8 ou xo ny wh rp wj ub s7 pb nb qv ev o6 o7 yx
+\n gr dc ft qs gm qd dn k6 lo k9 nb as zg bz lw ui ee g4 dl qv q9 lu jg rx w4 yj ep oo sv uq hq yw ao fc e3 ui dy du sk gc gy qs l7 kz ed ej wl un yu wm oc gq qk qc ks tk ti eq em ly vl er ry sy yo ro eo
+\n lq d7 i4 7w y0 qt gw ch o6 eo
+\n fr hb dc o0 yb hn gi jh sw kj we o1 vg nm q8 bz zk bf ml ev ed r8 iv ht fg th qv vz d3 ng xj 0h 42 ew vt yg qr qt ha qu hs qp ij yn eg of tl p8 fz oh iv jl ss dy zu or sk uj co kt rp wb wx fg ev t9 rj yp u5 us ys ak rw al io kc dt jr hl ln wl wz gy wy qv qb mu hd ky ku zp ww yw rl oh ee w4 yz
+\n fu dg qf pg jg o1 dc by q4 st t3 lj ve jr am 2i rz ea lh pl ed pz y4 g8 i2 db g0 fj q9 qn bl en hr m8 qw rn qt yi ei yk qu xi uh fy yn ix uy gn jx f2 gr fi x2 zo pl vh ek sz u1 s7 ya em u5 da re f7 hl qh ju oz ar zb ci tk ob n7 vh og w1 ok er o5 ri ro tw rp it
+\n gv ra fr ub h0 hm pf qj kk zf zh rj eq d7 oe eh ib oi gg i4 jd ph nu gc qw rr m3 vj ry is dk qi rm qy qu ep p3 ed pd ta s3 tc fd sa im ow jc oe qi j0 gt bm vm zf nj rg w7 x2 nr wf hi rp wk co t6 t7 e6 ag eb u3 e9 f4 om o2 dk h4 gq jo cr oz ka kx rn wn do ep wb vn ef rz ew yi r2 ro so ob
+\n ft a4 qs pq iz pd u5 cs q3 qz ra rh w7 rk mv kv ee y1 to dj sj ta pn oi tf i2 th q0 vx vf ww 2l cb wt yq ku ye gs qe w4 qy qi xi tt es qp ed ef ti i7 tc pl jz ho zo qi za fy zy rk x2 r3 ht yv ex op ae iq u2 ag pb of dd h4 lq wx cy cu zy wm ry ef dj vx st ia ey te
+\n rs al qd uq ga qj sw we pa bi ba e4 yy mo d6 er et ti rb py ek am ib fe y7 fh jv mn qe qr oe c0 l1 qi mh 44 xe ei ev hq ix e1 pg pj ui hp pm fr qs kd nk 1v wj fa wf yt t5 vp ex wx fh pn ug fc pq io gh dg oy nf v6 bt jo qz gu me wm n7 br tx mt q1 su eu di uz am if uc aj
+\n da a6 q1 ph uv oj ji mp t5 mi rj cf jl w0 pk ew ii rv oe r9 ic id sl se su q9 vd we j3 ac d9 yw ew w3 y0 tk ao hr in e4 hu du qu jb wp cr qs v9 p5 vi xm kf s1 ea t2 wh y1 co iq yo au iy on ds fx yf qa zv qv f1 y8 wm u8 rc o3
+\n iu r5 el dz rt m9 hb lc x2 zp aw uz
+\n k0 px qe qr i2 yz qo ap t1 ou n4
+\n qg q1 wr wt wu 5x ij rg lq eg ia r9 is dl aw g9 xx w2 qt au i7 us jc f2 ge qa gt l7 lb mc x3 3p tz u6 kx f8 fb ku ag hd oj o3 fn tw
+\n ds rs k5 go qg ga qj gs by q3 xy q6 k5 4k o8 ws td mo w8 th ys eq pk yf r5 uo rb r9 td y8 tg ho qn gz li m0 oq kw qr g1 wy iv b7 vt qr qu ti to ta ut sa i0 pl oq sd ho qa gy qq l4 ks fu wg qg kj eh ez yu tf s7 os s9 ya em pq tc fv qg ve sx af ci ah qj bj df ry rl wm zy tv ol ey ox ri ie tq ir yc
+\n ak ra yb ds gt fy qh d3 ql jk jl ni zs q5 zf lf so wo mu yt wa w8 kl ue e7 2d mb yn tu ac pv id pm sq sw jo dv jd jg qq qw qe wr j5 wu 1h b6 vr yf cx lz rn ho gh qi es ev ty p7 fx fs s5 pl sf lh sh i8 qa xs 1o kq zg qh wk fs vo wl ez iq uo tj u3 gs ii je jr hk ql xx 1j v8 nz kf vz ww yw yt w4 rb ol o4 rn ux ig sp gc
+\n yv fr qa rd gm ps jd a8 qh ls vg q5 lg eh z0 vt mi vy rg lp ex ew d6 yg rv oe fs sz g6 sy ha cx qq wy j6 dk hr l1 qe gl ex ln uk sv ty at ru uc ts hi hl lg jv qi vc m0 fy xg qg eo hf mu mo kz ot np oy na el yy wz fh gp up ir e9 s9 f4 gf pw uh uj jr ab qh uc wl ce qz h8 v9 wv ie 37 eu gf yv 1m ma yw wm oh dk sr oc ei o8
+\n qn cd zf y4 oi dv xq q0 lc av cw ki xd lx qi gn bh em uf we ja ox iw qb wn my zs y9 ux
+\n qd qf we ls lf k4 eg bc e5 rl ea r4 oq er ip g2 yl ot iv ps gx qr wy xj vz xl bx 3o qr eu qi uj p7 uc ph in pk qt i4 gq wp v6 kw kd xk zw 11 yj wj rd oz th yo eb ya tl au tx qj wl dz wz cg zv qa rb wm 7a zs vj yw ee eo
+\n jd go qg d2 ji qn wa bf t8 ys eq ui d6 ed yn r7 is qb q9 lp lz qe c0 wu tx wa te qp 64 uq in qt qy wp j0 lz l5 og ca sz un ec rh pb pw h2 kv aw wy qf 16 rw ew tb aj
+\n a2 gr qs fu db qn q1 uc jr qk cn q6 b2 ne lg q7 q8 wi wp b1 ec rk yj pc fo iv sk gk jb qm zw m1 wx zt xy wy em 41 ee gh xg cn yv qp sn od ao pj fs ut s5 tb ad jc j9 xa uj ws kf wg vp nv fa wk mq x6 vh wv t4 ex iq 7r y6 sv ox ev eb rj rk em aj pq gh f8 th os sb mt ak q1 xr yw ti ee tb as ox o5 yo gx uc
+\n qj lp z0 aj wp vr wa bb xt w9 ya on ew ym ia ix pt tw dz jo ae cc qe lc qr cn b3 c0 ib ml qi uj qp pf p8 e1 s3 tn ui sg pn i8 hb ij qw pd ld fo ap ty ro 3b r0 sz ie gp rj e9 fk gd pw rr uj cf qz zr rq 4p kp pr vj w5 iq ey rn ie eo ir pi
+\n gr rs gy pw qd ga jj z3 kj ql nn bg dm zz uy pl e0 lh ef oe am y5 fd qx hi uw i4 q9 hs jb vd cx ni qw wx zt qr d0 wi 43 w3 cc b9 qa rw oa ev ry p4 en tk ti yq pd i5 og ic ye so tc de pj ff hl oe sj qs wf v9 xn gm wg xm 1f ph dr vg wk ns t6 um oa e8 sb t0 gs sm fx o1 de h1 uk qh zj zk ng ct gp nx xe 3z wm rz yk tn ro
+\n qa pt k7 og kl wy rp hx wp wa ui mx eb 95 ac eg dj yz aq in ih i2 q0 cz cb dg xi cq jc qe qt es ed sb en iz fp ta fc tv tn gw ka i0 lz sd il qf 1s iz qf nc xj xk ep r7 rp gu t7 wc t0 en tl iy iu pw kn km ql ct qp ch fl wm n6 rw eo qm vx ty ee ru ig
+\n um rf qd db qf od d1 mb u0 le xu wy q6 mt bc qw cm uu us r5 uf or tq ek sx i1 it la cb ax t0 wu ab 1t qq g6 ko g7 mk qr ey ha ea qp y0 en ue tv ho i6 i8 sp xs qf v9 jl kt rk qy ot 14 na ub aq op yo en tk ob on tx f0 qk jp vi iw tj x9 zi n6 wo wb se aa ag oc gx
+\n iy ub gy pt pd qf me xp w7 rj tk r4 rx ui ii r7 us pb pt g5 fw gf dm wi w1 eu re tq oo es pd ri tl og s2 fx ap ok i4 di lh f2 1i vm cp bh wj wx of on tx dt h2 hl qk wq qz lr tl f3 ce kp yr yg ro yx
+\n k6 qf cp la wp gv es pl uo eg am tf y7 i3 hd jk we d7 rl b8 gg ug es rt p5 eg em tz ow 3y eo wg t1 lc wk ol tj en ak fc f6 df gt ol qc rn tz wv rx di ov
+\n pp qd iz qm vk jg r4 pl ym y7 sc qn jf qy rq p8 yr di qu hb wd rf ks gw qg s1 x7 ec ae iw eb ai sq v8 h8 le ea vh yw yp
+\n ik a7 cp sq q1 lq ql wa qz lr zh rp ra gb w9 ys ui ym px up r9 pr ek qv qb hs bg wt ku pu dc p1 qo ik uk y9 y0 en hr tx ts pk jl ce lj l5 p6 v4 wk nu vg oy aq aw rg os az uj kc py ql oj qc pc fj jr bf cx es vn q4 y0 og w2 ue u8 is ag ie yc
+\n rs dd ik k5 hm dg k7 go q1 qk wt q7 wi ws t6 k0 go ii ee io ym ey sl sz sw jg si d3 qq qw nh lp cc kw xt m3 ip ln nf zm qq tc ex ry at iz p7 ux of he og dq e1 i7 pj sp s4 ok qt gt sd xf ow qr pd hd wj qh x3 yb lx wx um e6 t8 s7 uo u2 it sw pm rr qg h3 aq ze h8 ks zb kb bh ec wb vb w2 oj af
+\n ak ds dh jg cp ws q5 nq wy su q7 kb o7 ys sf et r9 ta sq y6 dn sy cx na j6 jc qi qw qe qr rb tn 3g eo uh tr ft ri uw of i5 ue ta fs s3 uy as ss qu ns lj wp zf wg sm x1 ix mc va mi rx ej yy y4 t7 ex u1 y6 u3 up en au ds ap kv qh kn gw k1 zv eu lu kh tx qk dr dh wm ti h5 o4 w6 yk af fq so aj
+\n uv sa hb ps q4 as wi ej qm zc yd yn fp y3 td hy ue qw qy es tu uq tx e3 jz ud sv l6 fu xh dq wk wx yi dj qz v0 qd ga mp wm yy tn fn yx
+\n qh qz ar qq ma kq rx qa st ei
+\n dc df il he c1 jt qn yd yn pe et pn pi d7 ke g2 j6 rl sk ng z0 m8 mh qw j1 eu qu rr es ec uk ev ul pf e4 sg jv m9 qf vd wk gu rh e9 f3 on qv vj dh aa ru ux yx o8 a1
+\n ra qs h0 qh bf q3 dv bl mr if ws df ev b2 pl om tz ax yk ta y7 aw dn zr ax qt m5 xx wp qy qi qo at ti p7 tv i0 fm qu sh so lk qp hb p5 xk ib vd hk t2 np ek yt um u1 ir sm yf ug az qj v5 wr fg zv af qv ck ay cs ww pq wn w1 yh as yk ei
+\n tr yb df um qf iz k7 q3 we cb cj ne zg a2 e6 ya r3 ut on rq io ow qx ja qv cx cv bh vj qr lv pc 3a rm ep uk ed ev au p8 so fx p0 ts e4 fb hj qt dy px sf f1 zo vx qa wa sa qs vm wf xg kf fz r3 bu t1 tu ez t7 va e6 fl tz uf gg io qg qj h5 zz nh qz zt et ba lu tq vz xe bb md u7 oh 5k rv rt tb yu tn ah
+\n gr da ty qj by we ls av kc qc wi wo xr mx cm yg oe xs pr ua pt dk oy hp qm qq zw vk xi ln he rx ko dz yt qe tv eu qi yz tt y9 ev ry ym ay uq pg oj aa s4 sg hp f1 qu wp qa bn vi os iz hw kt t2 rs wl r0 ez rf pv hs om dd f8 uj dj pt dk km k1 qz qx wc n3 nl wv qn zo vx ww dr yr oj r1 tq
+\n ra jg jr ao c1 wh rj fp gz iy lo gc dh qw qr 8p eo ev fu tl i5 uy uu ui qp mb hk yt ou aq oi e9 ip dt k2 qx vb mf id
+\n rd h0 qn ql la vg qz lw q8 ra sp ts pr av qc vs vg ku am z0 lo ry ev eh i8 aa pl dt du i4 zs w7 wj xl yg yh ra ex u4 pn lw gu pc on n9 n0 wm em tn
+\n gb ik rd ql xi bd yr e3 qq w7 ex rz on ui yg ax fo pv ab ta jp qw xi wi qw qe fh mz eo gk qu uj ed ev en fo ux ye fv jv ws lx kr kf n5 qj ea s4 vh ez um tj ir od ga tk f5 dh uk pr pt in v9 js sv qb zn wb vl zj wm ca mu zs ef rl yw u8 er id uz ah
+\n iy ij ub qs lo ql jk dv h0 wy cm q7 wu eh fq w8 hm w9 mv yd rz rx rv r9 eh pr ek dk el hi qc sy i4 qq lp jj we m2 g2 fo j5 wy m6 ve tx yg w3 rv rn rq qy hs tt y9 ry ym eh to e1 ur ff hk do wa kq jw p7 yp ky r2 wx oy uv ra yt t6 yy sz t7 wc s0 of ds om kx ng ql qz vj wt wb ly wm lw dh md ew w3 tv er as yu an gz si ro do
+\n o9 k7 q2 dt 1i wa uu t8 ut mv ef uo g3 gj hp jn nt cm rj ms wi b6 im qu eo yc ex qp eg sf do sh i8 ih qa wa wf kd yo xj ql wf ek un wx t7 s8 rj f9 qh qk k1 lq h7 in nj um bu qv ov n7 bh bn 3z w1 yt et o4 gl
+\n a2 gt rs ty rd rf qd qn jf qh k8 q1 qj ql d4 cg wt q5 z7 lr wf wu q7 sd yg yh g1 eg to el ih sw tf fg qx dv q0 wq qq uu px vl xi js jd ze la ud qy rr ky ft i5 em p8 p9 i8 hg im as jz tn qo ul wg 8d vs ap mq x6 no t3 ub wl tf iw rh ox ua pv ir us pb tx pw dg h1 uk ux cr sz ko wx jw vl rc tv af du ei
+\n gm we cm jx lf vq vw kb wk e3 df r3 r4 ew yf ti id fe fr su xr sl jg rq rw uq tp ss qy ws od nv wg ro t7 ar th ak da yf sw io jt cq v9 kb iy u9
+\n qd ga q1 h8 xt um wt nq wy wp a2 rg w7 hm cf tj ut r3 ch oe r8 pa qb jb zw mm wq pl m2 wr wy mh hi ei qy nw uf yv s1 fx ut sa tb ss hl qu qi zp nf zd ar l5 5h gm vo ix xk wk wf vf el r0 sx e6 uo rj f3 em dd uh qj cf wz n9 ga tc qk mu rt ye w4 o4 ad ag
+\n qa jr kz c3 c6 vp e0 ng wu ug ty uk tu to hr sp ud m0 ar pa qf wf kr fi ya kk wl xs ed mp x6 ub gu fh rj e9 ya om wl vj ha ex y0 id
+\n qm q2 oh cd q7 kk ld ys yd rv yk id wt qy iz ri fi i5 ic e1 ht 5z iq ha ai sq pn al gh un kt wq mi dr ax u8 u9 gk ru ov hc ep
+\n iy sa un h9 rf fi he uc u6 cd q6 wu zl zz rk lf yd rx d7 ef er rb d9 r9 im hu zv ps qb jf qm m8 qq ji g2 kt qq ew la xy qo es ft ik tl ye ur as tb m9 i8 qa ka qs bm zg ix ya kl t1 wj r9 oi um aw yo ie ys yf hg gq nh zc sb nw qf xm bc xr bj es rx w3 yj iq tm di gx o7 pi aj sp
+\n il qf pd k6 h0 na is q8 4p zl jl z5 hm ec io sf dk if gd qw 1a ld lf qr yx re tq y9 pd iz yw sa wp bn jq w6 v3 x2 br ta yi ha en o1 io ip pr kp nl lt kd eu kf kn n8 zs rx ux
+\n ih db gm jd wr zj xp vp qb c8 pc g7 uf uz p7 sh or xh xm wh mt no fh dh wv tk li qm vb ms if
+\n he ql wi bn c1 rc ip ia av or y8 mx yr dx ex gz 1p ic wf aj kn 51 bj wn o6
+\n hb ty dv gu ps qj ls qz ch q8 zh xp bs vt rh oe ot pb y5 y6 fr ih sc q0 re zx lm id xp yy qr ry ay p6 he dq s4 ff qt sd vx jb qo qp gb ws wd sd co fp kg s1 nm rp cu 8l y2 tf ev sn au us fz hj qg wc u4 au qh wv bn eq r1
+\n uw vr eq rx et rb fa ek id qx ui kr wn uf p4 tl au hw tx im sf yd dz bo wb xw
+\n uv yb ik qd gm gp k8 qk ao z6 ps mw zf jc eg a1 wa 7c zz rh yi lf pl r7 yh d8 g2 r0 tq su cz pl qe qe wr wv ku ho qt yv uj ij es ec ik yn ym uw tl sm he p8 fa ho wo gy ws zf bw nb 5q ql t1 ro rp ej xg uv el l8 rd wz rg go rh sv fh ya it pn hd ao az tc dr ac dy ot sj nd qz ok um ol sx xb wb wi n8 ji rz yr sr h6 et o3 ru rm pi
+\n rs fi ag c3 lw ys ef sg qu qi uq eh e4 gy qt ya ro hx oa f5 1j qa cl wq rl yh pu
+\n ub rd qd fi jl zk oq r8 y1 tp sl i2 qn sd cq 6d mj w3 p6 ta fm bo nv qi wh yj e0 ao uh kn h6 r2
+\n pa q1 fm c4 ig ex 2a yi mx ek ez dv jf qw qe 4s xt ld dh qq mg qr yc eh s4 hj yy s9 pv rr uj or qj cd wc ly x0 wv hh ye ew yh rb yk o5 tm
+\n pp q3 mw rd up td j2 lv af ih hb ee xh yy ua ug aa tb
+\n sa tr ds az qd fi dn hw qg dh qh nt z3 qz ad q7 q8 tf vu ue mx vp lg tz er yj to hy fr sw th qn hf gx jj pz wt lb cm m7 wi b7 vr lo yl qi ry ef sn uq ri fx oh i3 sd i4 ho vb wa qa ik uk ar hw l8 ya cw s4 wg r7 ot wk gu u1 fh th rk en sm u5 iy iu re pr hk qg kn gq cf h8 nj ct gp wb qg hj wm cy ok er tv u0 sy fq o6 gx eo sp ob
+\n yv ak ra co wy zj e7 ew tl fo ek ez im q0 jm bj lc tc rm ec ou bn sd os x2 lh wj ot oi y6 e6 yp ob sq p0 js qh el bg rr rc xw pu
+\n ih um k9 q4 ls jx ej om sf uh dz oi qx cl zm qw qr zc qe i2 i6 uu qp wp ws qd sd fj mx qk yn wj ub gu ar pn rr qg ln dl al vg mf w6
+\n tr ub ds jd gp qk jk d4 kv xo ws gi yo sj sl el tw i3 ow qe zx nx b4 qq ee eu uf uh ex p2 rr ea ry ef eb y0 en ri eh e1 oh fx fv sj jn xf qd vi l7 wg x7 r9 uv ek yt ns aw sx sc vf tk ud ds o2 pr kv ab gt v6 un qz wr wv rb os ie u4 rm zm rw n8 vc za q3 zu yy o3 yi ag pu
+\n ij a4 uj gg jy dt 1w rj a6 r3 ii pe r0 ej ta ts ff i2 ho ov wq kw qt ot m7 qq xv ei lv kr yx yb ri fa ur de pj hi si jq wg r4 x5 hj oy 5i u3 tx tc nd v6 oz wc qv bz qb qj zl dg ed ka vh w3 yt ey w6
+\n o9 ft az ps hq uq a8 ql we wg z8 ye wk bf rs wa c6 dd ys rl wy om pe ix y3 g4 dz gf se tg pa va jn jj al qw sf ma j5 qy wu xo dc rn se eu xb nw qu qi p4 ef ru sm eh im ad gm jv pm zd g0 wg qf ai qz ym qc t8 op iw ox ay tc av k1 ko vj qb zo wq bg q2 n0 rl yt as rn uz
+\n o0 jq qf he qh 7k q7 kb wp z5 tl ew yg et or ez jp g9 jv gk lx vk vw qa qo ou qg kk xz rx ro wc oa us ip x0 ku hp jr o3
+\n pp dc gi fi qf ql by we la za un qz q4 jc zh wp kg o9 qm bm wt r6 rw eg ix yl tp am a0 aw i2 fh th xq gc eq xx yr qt xt nw qu ri uq tl ue pj p0 hk vx uf jm kq ws jl 1s p6 ca he x2 wk wd r5 wg bi hk ro wj t5 sx fh sv e9 ya aj e0 pn ao ug ac kn h4 gq nj wr cu qs kf vx xw k1 og yt u0 yk di gx dp
+\n ij tt ss dv a6 uz gp qk cv lq ql un kz gj wt 4y fq lh z0 6h w8 vp r5 ee tu sg pv y4 pu a0 tg q9 gx qq qw we la wx rr ls zy qu wi xz wi xp ew qe rn ei qo uj rt fy ik tr tk sn pf i5 tc sp s4 in gv i0 f1 si ks nl kw bq co mu eh oi ec sc wc u3 ga fk sm om kb wx bo zj hv y0 en og q6 er tv tb rb u0 w6 tm
+\n rd ik og q3 q5 cn xp c4 ig mi e0 rc ym id tq ou po gg qb sy ob hf pk xr qr up j6 ng xx b0 qt tm eo vw ux yw pg tc e4 gy p4 xv pd wg n5 r3 wk iv rl ht oy uv ub wc ar t9 ga s0 em pw x0 pt bw wm vv vm yg fn ad af do
+\n hb gg kl q5 t1 mi a4 b9 r4 ee up pr g8 gl q0 cc kr c9 vq yy wa qy mz ty yn yq ai og tx tn nd ws 1d ky x3 sz td gu t8 op gs tz de av sk cy zm be wv qk og uc
+\n ph qj d2 cd q3 q4 bi wp vt oq y1 ps cw kn gz ij p4 sp e4 wa sx nj v1 w7 me s7 e9 tc k1 lw zc vj wb kb tw a1 aj
+\n rd a5 hq qg qh q1 la cd kl mp k8 mj vy zz yu ut uo sg yz hi gk sy q0 m1 qw b5 wi dz qw rc aw eu zr uk ti em yr lk kw nz wg fx tt wg x7 rr lm ju th tt eq oc o6 yx ro o8 a1
+\n a2 dm wy ej rh rg pe a9 oi y7 zt vk ga yf pp fh ml tj p5 sn tk im jv v6 lb pf zq ty wh t5 go sv e8 it f7 ac p9 cu bw kg qk f7 w2 ee w5 tq ep
+\n vs rg rj 70 ys nq uf ex hh jn kg ep e0
+\n df q3 u9 4j bn rj hw up td i1 dc hi zb wv g3 l1 rz qt qy ty tj ef eg sm tx ap in px af mx r3 r6 t2 t4 rd uo e0 iy ii hh qg zc v8 qc ch px zy zi ye og er tm if
+\n gr pq se pp qe lq n1 cy qb wb ey
+\n gn q1 ji sc nh pe yh qt ss rw hf kx zm o6 gx
+\n ih hq ap bl wi wa 3y er pc eh r0 yl ta ts dl gg fg i3 hp kw lc ls rl ff wg qi uh uk yq yw ok as wp gc jm qf to yy fh e9 pn qk sz nm li q1 vb yq 2e rl tv sy di sp
+\n o0 yb rf k6 qm fv q5 wi rp fe dd mz rz ee oe ia yz am ig hp fj su gl li we m2 ls xj md z0 uh yx eb eh ur i8 qy oe i5 jv ce jb jm lx ci kg oy hx e5 u2 tj kv qh qo af pv tz az vg yz ri ge
+\n il z2 d2 cs ba wa 1o ys uy ed er d8 sh qx sr qb jd ov xq nu lv g1 ku pu rp qq ee et rm eu xh i5 p8 tx in i3 bn v3 ap r5 oy sl oo dr dg hj lm sk ff vu cu wb kd zi wv mu w3 yt ok rv ol ey yx ah
+\n hv qs qf qm ph o2 zg wa a2 rl pz ow uo y2 ey ix us d0 ek fg ww j4 wv a7 wu qq bv te tr uj tp ue ts do qo cy uk j5 ra ou aq iq ev tj u5 tx gg df dg h4 qj nc wz v7 im kv jt y8 rz w2 yy ei ge
+\n gt df jg og k0 lq kz zf iw kh gv cn ur ea eq yj ix id ez dc qc st wq c0 lm fa lj qq mj sw qe xb rq qo yn ru at e1 p9 s2 ts i8 s5 i3 sf jc xa hb qq gy wd p5 ow qf wj 1n wd qz yt ex e5 op at sm ud tz yf tc ax f7 dg qg ve sl qv tl wn 2h ky yv f8 rt o8
+\n yv dh kj jl wr bi kc 4j nt c5 lo z3 e8 on or g3 g8 uq sr qm hd ll c0 nh ws qu ug ph tc dy oq qo nj wn t7 ox zj qj km h6 ql zz qz qc hn bk fw ob
+\n un qs fu k7 co je gp o1 bg dt vu rj mc rz rx r6 g3 ek qc uw fj li qw lx ku z9 br b6 mz yc ty tl yw yr e4 i3 ud ce i9 rf bq xh ep wf ej rp vu wl u1 os ay of pm ap gg dt hk ab ql lq qs lt nl u3 wq n7 bm ef xm yh w6 ru fq tw
+\n qn ql zj rg ed hs we qw re p7 p8 yr tv pc lh gx i8 wp lz cp fv lq 1b di
+\n rs q0 is q0 yq rl qq vr qa v3 tu in h7 zy u9 o7
+\n ak pw af z0 wa jh tf to ey r0 ta fe tf th wt m5 xu wu xo l1 b7 bv se w4 qi es dy ge vm l8 nv kh l0 j9 bi ez iw ux f0 gt zc 6w bf cx u8 w6 yo o6 fw
+\n gb py gp pg ql um lf bx 6q ra w7 vu rj ui pj tl ii y1 r8 ac eg yl sj tp y3 py im tg zv ll ip wt av fq qw dc yu ei uf qu tw y9 em tl dq fx hk oq zu jx hb i9 bq iz mv wd qi pz lx ek aw fg ag u4 ir tk of pb az f7 qk wz le qs wy wn dd bj mp yq zb cj rc tm yo
+\n o0 pa rd qf dn qg nm ji q6 cm wp ec uu ax r8 us aq se lt g1 bl vz jc mj 6h ul en yw e1 ye tc i0 do ge gn v2 r1 7f ed x8 rf oo t8 s9 u5 pb fc ug ab pt uc ce qz h8 u3 ga fl bc yq iq iw id o6 r2 te
+\n da qf qn nm wr jw c2 ig rj vi ys pl ed ii ax y1 ua r9 ia ab tq an ek dl sl jo g8 qv gl hs jf d5 c7 kt ix wy wi lm qq dz ko qe ff pp qt eu wd yx ec p3 rt ty ik p6 pg i6 e3 sa fd dr gb jl as hz qo j9 j0 qs qd wf p7 br wd x5 hh t2 wl el rf tg ar rj tk em ud pn rw av iq nl qv x8 ov wq wb ec vm y0 w2 ew 5k w6 fw a1
+\n a4 a5 z1 hr qk q3 mq zf qc wu q8 bd a3 xf es yn rc et d8 pr or yl fa se dv dn uy vz wu en mb qa tj uq i6 pl du jx f1 xs qs qd vm kd wh kh mv ed rp ra co tg oa u2 ie ha ir da pr cq uv oy qb qb eo ye rz rc ei o6 id if do yc
+\n a3 qs w0 po gx xo nh uj e1 lz os wj uv ud tx rr gq ve ch cs xz
+\n rs rd qs qd hw dh q1 ql dy mj zz ws vy pj ea mv yf om uf sg up ix pb ab ej tq fr tg i3 nq dn gc wx wc qr wr lv t0 cm wy wu ko qe qr ze ug oa ed ym p5 os uq i5 tp pg s1 ok tb fb vx ns p1 wa qs ka qd lx ps sf zy kj pl ro rs rd wz tf ev gp e8 u3 ir od f5 dh qj cw qk h6 ql ad qb fj tl al zs h2 rl eq rx er w5 eu yz id pu hc te
+\n pa pw q0 j4 4f vq iv yu ry fa e4 kw xm wj t3 ff ye w1 oj rv ul ep
+\n mw di ec wt rx ko i4 qo l3 iq iw py yl
+\n dx uv yv qa a3 ij ps qh cg qc 2n mc ut eq rz ea kc pz sj dl in db fk su qm qe m2 we 1q ke bn xy wb yq qq vr qw xc gf tn re oo qo p8 iv e2 tc e3 e4 fv ff pl sd qy qi si pm ws aa zw bu hu fn yj pv hs gh o2 dy ln v5 qv zb tk bq 4p qj zz wv 8n h1 y9 wm yw og mf u7 tq ov sp
+\n iy wt gx nd t5 r0 yl tg dc qc th wt ld nd zn tc ke qu qo qp tk fa hh gn qp 5d qd ar rg oz fh at ag 2a kd nx w2 w5
+\n il wp ee ym tu ef dl el qb cl qn ob qe qr m6 mf xq i2 ud fn pc gw m9 l7 fs eh gi e7 fk ys pe ok 30 mf o8 uc
+\n db k5 a7 je kj q3 we wt q6 ie ck kg kn gr lf pj io us id in aq jo qx su hd qm li qq jm we lc wc ld d8 lb xj em rn j4 tq oo eg fl sm he ue so hg ok dt qt px j7 qi nz l9 lj x8 wj na wk ez ex rg e7 u5 h4 kn ad cu oa qd do q4 eq w3 yy sy su r1 ri if yc uc
+\n fr ij ft db dn gp qh ph ga gg kl ws wt ab q8 lk wp mi w9 tj pk yf ew us yz id gd y7 dc qx hp qv gl jn q0 d4 qw re qe cn cq pt wu lm dk cz yy qr rq te qi rr ea ex ye dq ok qt sg qa nk wh kt bt bd lh wf yb hu mw el rd rg sb em on qg wq oj zx wv n4 bo qf bf re wb ev yq cl yu w5 ad ag id a1 gc
+\n qd ga dt ej kn r5 ax sg ix av am pu g5 ez fe qm nu ii zt wr vm qt jd im rb ml yx yv ru hr gv ad dw x4 ub hx wz rd ol oo rg yf f6 ii uk dl wl yw yu ie id
+\n yv ga z3 kk pp nq le lr bp qc t1 c2 hc vt lw jl w9 ur r3 ys iu es rc ud yj r9 pb ot ta sk gj jm gx xg sf lc qr ht ve sl g7 qy nw vw fo ta tb oq fn qu hc qp op nz ow wk zo zq yg wg ke yt kr yy sb tk rk tz iu o1 rr pr qh dl dz p0 n2 ci cd vn ms aa yh ry w6 af du gc
+\n q1 q2 mw bz k6 xf ec r4 rx yg ed d8 pv is sj qc q9 zw vf gc cv d0 xx rn ex uj ij ts ff ge i8 zg 4m x5 vh oy yy y5 wc tg at fk ob fv f9 ql bo k4 zn be ww ea ry tv w4 ru ov
+\n tu g2 tq od pk tm fi y1 uf ku wn ew
+\n hn rd um qg qh ph aa zn bc gv w8 rj ea es ui r7 r8 ey fw gh jf qn nu cc la 3u bx ve po et ei eo ea qp ul i7 uu fb dt pz qt m0 zf l8 kl t1 ej wj oy rf th rk gs sm em ap hg o2 ub wx ka hd br q2 hg y8 2e eq tb an oc it ep
+\n tt um gi qh lp kl lw q4 q6 ro b1 if y6 qc th g0 q9 qm j2 we xt xi nd is ng bc ku yw sm ye dw e2 f1 lg qo wp zs gy l4 ul lv w9 br xl ql vf as yg y3 t5 wc ec iy hf ii f6 re hk qz jp oo qx xv v0 f2 vx cd vb yq ew zu yx
+\n lo dt mt z3 rg av us pa xq wq qe qt yx y9 ev ry tk hu oe oi r1 x7 wk wz td jy ww qc 15 ba hd mo 72
+\n rs wi vt rh we jl ur tz tw ht y8 fh i3 qb qm b3 qy ep op yn tu ay hw fd ug qp qd x7 rs yy td u1 t9 sm uh dz ql 4t rw kn wn rc eo
+\n a4 d3 kk q5 q6 wf wg m4 2b vt w7 ur uo rw pc g2 sl if y8 fj va rr ld wn xo qw rn ml la qu ep re rr qo pd eg og e1 i8 ui qt px i4 jc gq oe jj ws ks qd ul zh sm ql tf go e8 os tk rk ay us u6 dw dh pr qh qj oy lm jo cf ff wm br k1 en og aa rb yj o4 te
+\n gb dd rd qd a6 qj wt jx z7 xi q7 kv mv uy pk or yk ek el y5 ez aq dx qx th sy jn pe vl fp xz m8 ng jh kq qr la ei ft p9 oh hj tn ho ud xs wa jq vn il zg p7 fp ic qk wd bu e7 go hd sq ug hh ip sl ch qc px wb hs rq qh bk rl ef yw dj yt u9 st yi uz uc a1
+\n al tt pq um uq un z6 wa vu w8 ed sj r0 tq pu dz qx js 2j na ip vz lm qq qe yj ud ei wf qi te p1 tu il tk iv dy i0 xg sf ix gp fk ai qk lm ql ch fg qv vg yw rl yy rv rm pu uc
+\n rd ik qd jd ph gs k0 q3 qz ia q6 af q7 6w o8 tg gi sj ou aq po ja qn q0 qq gc nh xt wr fs m8 qw aq wp ho qu rm uh tr qp tj ay fi ao i8 aa i9 tv gv qu wo lj vv wa jm qa qd uk nl g0 qd dn gw ic kj nn wf wg dy ej rp y3 rs yy e6 wc oz fh eb e0 of hd yf uf ab gw qc ww yc cd ji y7 n0 wn cg u0 rn yp ie a1 dp o8
+\n ih h9 qd xy yl ez g9 lt qm on vg rz dx qa kt p1 ex yx od uz e3 in sd oi qa cq dr me e8 ua ya dk wx bw 4p vx tn o7 pu
+\n ij rf fu jt gs ld wu q7 wo 2u bd k8 rg ws gi oe yl if sq hi dn jn qw bb cb wt lm sq vr qe wd qi qo tk uc hp i0 pa w6 fo v4 1m x3 t7 u5 sq ai hg ap hm ju fl tz wv w4 ry tn yl fm gl ox
+\n ql db ch wu rl ih qc 25 pb qq ty yw fp ao qy sp p1 qa rf iu rw qg uk gq km dz wv at qb jo eq ur rb ad
+\n ra fy gm iz qk qz lf bp kc is nr ws mb es tl d6 up pe ey sj fq ek iv pn sx ly qb jd jn q0 lp m2 xl fs xx rs b8 hu rn ey qo yn vr yq ti ai e1 so ts jz du do oi sa i0 oq ow 3r 1a wg qg r4 yv ty r8 wk wl s7 u4 of e0 gd pm yf h1 qg n1 lt sv qb eu wi mt ez w2 yk su dp
+\n rd qf dz aa bo pd pm qw rn tf ah wu
+\n dv jd he jt ao ql zg eq pz oe dj fw sc fj nt io fi lr xu ns mx qe qt ei rq qy yw em e1 i8 sp in jv wo ci fa x6 fn ej wv oi um yi oc au yf mt te az yv yi ad yo yz
+\n uv hv ty qd nv jh qj q1 ql we fv vj q7 gw fe wl vu w8 vp 6k yd r7 yj ia ey ot g6 dx tf im hu ae qx fh g9 lt fk hs su ov lo m1 cn t9 wi ki qq qw xx aw nw es yb vw yn ry pd ix em so ut oj du f2 lj qp jm w5 xh ny wg 13 wc qc ek el sx oo th uo yo at pv hn uh hj qj zk ql lw zx qv cc wv en yr w3 yy st uy iq ox an ah
+\n rs pa df il iz qm k9 bu jz bi ji du lo ts yy xf cv gu mc ii rc up d8 ey pv av to yz y5 fd qx sc qv jv qr xt bk m6 ot md qq qt rq ex p2 yn sb xc fp pj qt ce wp i8 j0 wa qd gn ps qf be qt wh ky l9 za wg rp wn fh rh em vn dh vw ng wz k2 h8 f1 nz zh yz q5 zy e1 yh ad tw ep te
+\n db wr a1 pk ew uu r7 d0 pn fe g8 la qq b0 ef os oh pk fm wa wk yi ev ua sq wu n5 tw xc er
+\n fr yb qa hm d2 q2 o1 oj ox dm oc km kj r5 px rb et r9 y7 vs pj q0 4s lm gs qe eu ep ti tl of jq od pf nr nb ea ej yy rh u2 iu qh dk zl qv xe 2k vc vb zb xn yg gk ox tw
+\n hb dc rd fu gu zd js wu xa c5 a5 w9 vo r5 d0 g3 oy ib i3 ha jm nu rr wy m8 b9 ws qu ru eg uz hw eh hg sp sf do jb zp dp nz wj wv fg ae ah ob hd dd gh dz qz xc s5 vi je n4 re bh ma wm as st tn yl
+\n wi w7 om ug qa x4 yn r7 gi re n3 n7
+\n ij pa qd qg uw qm d4 z4 q8 t3 mu yp uf ia pm ez ih qn jm nu kw qr j6 qw bb yu qs rw wf re qi ru jz tm gw lh ce xa wa xs bn nl ys t1 wk r9 gu oc u4 hd ku hp yg rv as yz ro dp
+\n qa ss dc gu qk cs cv fv nm z7 lf z8 xs a3 rk r5 ys eg y1 po qv cl jf xq vh we wr qr b3 bv yr wf re qo sv tj ti eg dq ic fx jz du qp vb ws nl wd wj zq vg t2 zf wb tf yu ex yi yp tj en ua ud dr pe qk dl lq qz h7 ol v8 cy vi wv ck el gd q2 yw w1 ye xq w5 gk o6 ob
+\n k5 hr jk ju k3 jq q6 zg wi id bb wa gb rf rq g1 et ot pn ht dc ww c7 we c8 cm xo mg w1 1l yx tr p2 oo lm ry ao e2 i9 e4 hi tn di lv cp ca 2u t2 no ub ex rk ys pw qg av py ql qo lb pn eo wb er tb yk ie id r2 tw o8
+\n ra qa qd ph jh d2 dx d4 2z jl q5 ld cm wu wi 2t wa dd lg ui to id in uq ww rr g2 wu rl qe 1l qi qo ec yn ed uw p8 ut sj ig p4 zh xm p8 vs vg x7 ot cu l6 sx gu yp t0 gs az pe nf wl qz nj lr cy wr qv tj s7 u2 ly be br ym w2 af ri it ob
+\n pp uv nb bu kz wi ah z3 c6 rg la vi oe ia ot pm dv gk 2h xq kq xg bv qr b9 j2 ec od ay p8 qi wp zd ay kg ea mq 6b qc un fh tl u5 av ub ji zx k2 wc zy 1x kc ah rw vc wv yq zv e2 rp
+\n a2 iy sa ft pp un qn qz ol lf mg wo fr vu ya rk w0 pj pl el dz i3 jd su ob c8 pb id b9 ep yn ru tk s3 sh sj xa l3 wa nj ke kr ic xl bd ej rg yo f3 al f5 sw re uh h2 av cq bo vk kf bd mu wq wm ew ue tv ol tb o3 ul ov
+\n iy a5 gu q2 se ls dt zf o4 dm ez jj uu ik ue w0 ya ea on ui tu rv y1 et r9 tq y5 ht dc fg i2 vs q0 av iv ku in il en ri p7 uc e2 ut sp fv qt gn f2 wo qa op v7 ws l6 wh ys zq t2 wc y3 sx yi t9 t0 ys of rq ug o2 av kn h5 ju ji ko v0 nz wn kf te dw u8 yt fn r1 ie yc it
+\n qj lw ji eq oe g7 jf jc yr qo v7 p7 wd ma xg wz qb u7 w3
+\n un ol eh g5 px b8 rr og gn mx yf wv sl on jo uz
+\n qg qm q5 wy eg ri bm mz d5 rx pt ek fs pi td ez ho gh q0 ll pl kq wr d0 l1 qq ko er qt wf ei p2 ru uq ye tx s4 hc zd vn ps ix zo wk t4 y3 xh ez rf u3 up ys ou xx zv qa wb at rm eu qj wv za zs eq zy rv ry tn tq yc ob
+\n ss qa pp rd jf a7 lp h8 um kc q7 wl rg r3 w0 wy tl a0 ih ly qm qq m1 xg qr up ja b8 yh dc lx rw ep ea ev ay ux to p7 tp tb i3 qu gq do gr za l8 rj og oy ub e5 ae tg t0 sq tx hj ad gs it vc bh yb eb w2 yg u9 w5 fn iw si di ah hc
+\n ub o0 ik ps qd q1 ga lp cf kl m3 z7 q8 ue ee ud ix g5 ib gd aq fh th pa qc pg ue ur xw ww qr vj m5 jd ng in tx ff xc er qs eo qi p2 ky pd eg e1 yr ut ib oj tb hi hk ho gm qu qi hc ou gn wg sn wh ix wj wj wg ot ra wl un e5 rg s7 t0 oc sm tz hf fx pw x0 wz th qv 1z zt n4 qb cl wn xq xr y8 rt y9 rz tb iw vb
+\n qn lq bu eg iw wi 2u 3q t6 k0 yd sd ed rb eh ek yz if pu y6 in fr qc qm ob il ma b4 en wu dk nh b8 qe bb mj ws qt ho yl ug qi ea tw uh p5 eg tl yr i9 pl lh ce i7 wp qa xs dn 7p kr p7 eo vs mb pk ni yh ef rp wj ej y2 iq y6 u5 em e0 ii md jy lw nj fh bq pc xm km q2 wv k1 rx u7 ut et gj tb iw gx fw yx
+\n qa pw k6 qn qg qh as u0 dy q7 wp hv 4z 4c w0 d7 et aw wr bl mx md j6 an wi qt lc yx ec tj ri fx ht in gm ua qo qa ik ys eq n7 wh rs wz wx e7 eb ak gg ip sj py ka rl su ag
+\n gb uz q2 qz wr q4 z7 ia ad je am my vi zc mx ym r7 yk ua pt g6 hy y7 sx ih qx pa hp jd sy gk nh no qq yg rc 3s qy ep p2 yb oa tr eb p5 en ic yr dw tc in hk qt zp i8 lz ks ci lg x3 wd xa x5 zf yt y5 op u1 oa iw fh oc rk ay pb pw uh qg zj qh h5 nf cd nv qx kp qs qb 6q cl kh xe u7 ew tv sr as rt o4 ey tn is
+\n hv qj bo ru z9 t3 lj q9 rg vi rj sd r8 g2 sj yl aq fe po pa qv jf dm qq re we la wt wu qq vt gj ei yz rr xk uk pf so pk im zu ua sg j8 sk zd sd xz zw kl wk ol um yi rh sv u5 pb tz dl oi wz h7 s8 qf wn cx f4 mo wb ed oh ee er ry eu ei oc fw
+\n hn a7 cv q6 cj q8 fs jv rl qq qi od dt l5 co qr zq ex u2 ah on pr wx kp wb yh gx
+\n gv qg je zg jc q8 fr r6 yn ii g1 pe sj ta el jo sr jv ni jj zr bj ns qr qi ur hz vu wh cs ep s3 hu ez rh u2 t0 dw uj oi wx n5 18 bf wb yq oh ov
+\n gb dc um jr mn we bl t6 vi pj c4 d7 rb ia yz tf qn dm ke xb ft au ix tv xd qq xg rx x6 vg r8 wz op h3 qj qx lr xv qc kc wp lq ea rn rm ri eo yx
+\n ra gt qs dv ik gn co qg qm qj cb qz z6 wt ji q6 dy qc b4 ws ds vu cf on yg d8 eh py hu tg qc hp qn d3 wq c9 pv pr or qq ml eo ug tw es il os fi uw to em ic oj ho px wo qp m0 qa 1o ks 7o cp wh wk wg x5 ee yn bi ef wj ns r0 ez um u1 iw eb ir fk ov s0 fl hn h1 pr x0 ux cd wz aq jp im k4 qv bp wm n0 vm u7 w4 gj tm uz te a1
+\n gv il ps db he nb wr ql kc zh tf mp lw ab us pn a0 tg pa th ps hf wc 1a yw l1 fs eq wp qr rw yv tr eh so i0 qf wf l7 wg na ou ah ay f4 io ip f8 cd h7 nj rn wb qb qn wp oj w3 w6 di id pu eo
+\n hm fu pd qk bi wf q6 wu b2 q7 q8 oc lj c3 o7 6s a1 jh rg rj 2s z7 ya id ez fr gh vl cl zq hd jh xh ru c0 bz wu dl qw km kp b9 rn eu yc yc p4 ru tk ux fo ue p9 iv tv s5 do l4 cu rg w6 os fi 4b uz l7 ld l8 fx jb ee wx rp ek tg e8 uf de qh hz h4 qj gq nb wx qc sv go wm zi zo tc 3k ez ec rz ye oh ck w2 sy ia gk rm ei si dp
+\n gi go z5 qz wj mg kl yh g5 y6 g9 xt p6 eh ap sa qu dw j8 ql yg aw t7 ir zj v5 v7 ba tw yq cz gc
+\n ps qn z3 sw gs q4 ie gx ye wz r3 us ef d9 pb y6 tg y8 qb gc ww az c8 cb lv wy a9 qq qw l2 c8 qu uf yx qo ic de ut e4 uu tb fn oe dp wa uj bq sg mx lv v3 ya xk wd by n7 ra cp gu va yo u2 sv rk ir ya hf kc kp bo qb gp qb yc ku q3 dj o3 ey ad si o7 tw ge uc
+\n dx yv ij pw a8 qm ph k9 dz q1 q3 cn wo wp my el bb uo on eh id yz am fe hy sw ha m8 vg wt vl wm qq w3 ls gj yx eo ef en ta e3 i3 zu hl m0 wd co zy l9 nn ea yj e5 rg gi fg gp u4 ir tl tz pm dd kc p9 zx sx qc qv kf ln on qm lw vn vm ew yg se as is di ro gc
+\n al tt gu qf qj xo q8 c4 ws e5 ur vp ea rz g3 fw sx th db kq wt sv tb ad hv 1u gt ss xk wj qj pk rp e7 ha fk f6 dr rr hk dk nf qo lr ka ie fk cz yz q3 ym ks gl
+\n gb q1 qk we q3 q4 t1 ox di ny wa ws gi ea rx yg r6 io ow y1 d8 ey ab g3 is ek pu ez dx qx th i3 jv fk io xh wt oe kr nd md pb vz wi ro se b9 tr yb p4 i5 p7 ux fp p8 sp in ok hj qy hz wa qq zd qd 3t wj aa dy 5u el yi uo go t0 u5 tl dq gd rw uf gg kb ux dj qj go wb zm lu tx vc es ev ry zt w2 tp w5 tn o6
+\n ra ps hm qf qg 4q we ql q4 z6 d1 wp vt xs tg 2s e7 r3 ys oq ef c4 av dj pn aw sr th hf gx uy wr ac zv m6 wn ko c5 qt qy yl yc uh od ri uq fz dq i7 tx fc aa uu qt oe i5 ge ce wa gb vn xg wh og ya xk fs ea yf zw wh ub 8x th iw rj ah ya e9 tl yd tx yf ii fc kx hl zj or qj mh ww kf zm lb ob qn wp ww wn ym u7 rv ie pu
+\n uj by wo ml dl qx m3 8i 2y r1 u4 hj h6 qa xv rn rm
+\n qa gm qh ql u9 ls e3 yk fa ts wr vj ac en id ud ke ye i7 fn tn f1 ks at me l8 kl hk lx rp ek l6 ek oi e6 wc u4 2h pn 9y zq qk ec cf yq oj vz tb o4 iw ox gx te
+\n gt ub hn qd qf hq dh q1 lp qk by ql lq we wr sy wy lh z0 ge k9 w8 th vp pz yg ti fo tp r0 g4 yz ig sx im i1 jv qn q0 wq nu 5t pw wm id qe gg qt xg wh en uc tz pj e4 tv i0 ff qt gm dp jb qp cr gr qa nk ws os wf ne wd mm wf t1 vu wz t8 e7 t0 od hs dq df av km v5 kl we fg cx al ax yq y9 rx yh ul hc
+\n we cf q4 cj bf ws ww yd tk ef ek y8 qv fk wt ko qe ep rt ik ut op mr j0 ej t3 s8 ir pt qk km ww cg wc gi lu n6 yr rc oc
+\n ak ft gy rd hn a6 uq q2 q4 lr ia eg d1 eb on sj dj pn pp qv i4 hs gx ww xj m8 ko im rt fi tc uy tb pl qy pc uf kf kt mv l0 qj x7 oi um tf ap uk wl ql zb vj wv tk re y7 de q4 rc ad rm ul is fq yx r2
+\n dx gb he dl k9 z3 qk lf ad ch js o5 vq zl rj wr th r4 tu uo r8 fp ic g4 fs g6 im fr wq bg no wt dg ru ln rp wi yd qq xz ew i1 kq qt rq wg sb pj hk qu vx oi jm pa vi x5 wf ni ro ot oy di un y6 yo rh sb us tz ac f8 av ve h5 ji mj n2 ci rm yx ep rt 5f yq u9 rb hx aj
+\n ih a3 pi mt do w7 zc nh qe wv rs xc qr ts ut pj im hp xa lv x3 ph tt sc od rr qh km oc rq xl vv jp ef st tw
+\n ft ik a7 lp jz jx k4 hz wo bv q9 6t ur rl qx qv js dm fw wd re ea fd hu jc qu zo p3 lx pf wk vf fj 2o wx sb gs it ol yp di eo ro
+\n tr ih yv h9 k5 qj qk nb wy gk q8 c6 mj a5 yi ur rl uy eq up yj r8 xs sh a0 ez oi y8 ly lp lx fu il ke zy cj sk xq 7u ey p2 uh yv qp rt y9 eg pf so ph tx tc uy e4 tn j0 gy ik vm ul mt nm mm x6 wj rp eh sb u4 ov of tc jt pt qj jy k1 s5 qs 7z do zi cx wq wb ma wm uw sr w5 o3 o5 su r1 yx fe
+\n h9 gm cp z2 fb zd gk ve o7 mt bc wp bd rg w8 rk kx mv uu rb or yk eh r0 y5 ht pu tf se ar fj hp su m2 cb c9 c0 b3 ns qq qw rv gg ij y9 oa od of pg i7 hk do pm 2n sj wa vm zw vg yy om qx nj v9 f3 ee w5 w6 iw sp ep
+\n da jq iz z1 ls z5 cg nt zk 1i gt w7 yi r3 xi yn pc fa ta ez in i2 qc uw si qw d5 kw il b4 fa ib 6c ud rq yl wg tr qp p4 ry sm ut s5 hi qi do j7 jn j0 qs iz p7 wg aq ex go ax ku nc h8 n3 v0 oc ah wm li zp rl h5 is eu o6
+\n gr sa a4 ik dm gp q1 wy m5 fw a2 t6 rj mx e9 et ej q0 ot wu em fa mj qe yg gh j2 te tj p5 eg tk em ao i7 di lh ce m9 wa wf ys eq l1 t3 ej el tf t9 rk u4 ay rw gg dr hj ac qg zk h6 dz ok zy kc mr fz iu hk yj oz ey ag id r2 ov
+\n ds fu ps vo qf qn pf a8 ph q4 cb le cn h0 jg gy b9 rk r3 pj yd oe ht pu ig ez hu q0 qm nu ww qw ow xh b4 is a0 qq hu bb cn xh oo qo y0 fz ue e2 yr fa pj in sa hj ui sp nj zg wj ge wk xg ra ex vs oz eb pv tl iu x0 ln cq xx iq s7 wv qs zn tl wn lr yr r2
+\n az qf fi qn u5 we jq zh wh c4 sd is y5 po oq ki sz qe rm qy yv p4 ye tb ho 1u gq r3 pl td ov u4 hg ax zj wz wb vl vv se 5k eo
+\n tt gi pd jk lq q6 wi gx mj ut ax av g7 qv zm lo 5y dh cw xo ve vy xg yv iv i0 qq xg jv l2 yi sc ga pv pn iu ug gy k3 cl oj tv yl di
+\n qk se pd gt rz uu d6 io d7 tq gf em ym tu ib oe v3 si wa nm wf qf wg rk kz yd hl wx cy bp mx et
+\n uv yb dd k9 ph q4 me o6 nr xa mu ld r5 rb g2 or fe pa fj hp db lu qn nr j2 bk kt xl rn wf qp tl uc dq i7 tx fv ar sf xm mx x1 zw yh lz mr t6 l9 s8 ah u6 x7 yw ol tq eo gc pi
+\n ia ys jh wy sb i0 cp u3 ql kl kh
+\n po al qg qm d2 gs q2 ap qz q4 q7 kv ah o8 rs 2o ex zx qw z5 r4 r8 y1 is ts y8 qc dm ll we wr sg lb jx wu jv qe ee qt qy es ed ym hw to tx hr s2 oh dt dy af di do qo oo qa w5 uz wh kh x1 t1 hk no r7 rp na sl ej op ev tj eb sb ya pb u5 tx ds o1 hz v6 lw jp k4 wv do wn aj bc wn yw to w4 as ey yk is ig rp o8
+\n rs ty q1 wt wy xp e3 wa yd d7 ht ts sz fk su kw xg bw cw qw oo fu od ix sd zu jn qd ci fi xh mo cp ev th ua e0 em kc lm cu u3 n8 xr yq ti yj fm yx tw
+\n dx z2 ga kb yo sf sk fd gf i2 vs qw vj vw qe eu mz tu sp xs qs es t0 eb ak uh hl n1 v9 wv kd o8 rp
+\n ds qn qj qz cb kz bo wi o6 z9 fq wq ml cv cb lf eq r6 r8 ic y4 am sz sx po jp g8 ze we wu en ew qw 3q lz kw tt ty ti e1 fx ut tc uu s6 ow gm sh qp vn l4 uj op xn wh qk wz rc wh uv um ar e7 uf az uh py h5 lq vt nz lu li lm dd rl er rt yu o4 eu yz if
+\n iz ld me z1 y2 dj ar qb b4 l1 mz ij ry to ad xs sd wf eo hj wl ex ie u5 pr zj gt oi wc kg my ex zt ks yg eu aj
+\n gr iy ft pq um qj dz wt gj cn ru kx q8 ws ue rk eb ee fo jb jf la ji ke qq qi qw rq yb qp il eb y0 iv ff zp l3 xm fi x4 r5 xd r0 ol wc t8 ae iw ox fk of pb qj ku xc ct wc ie xn zi wm rz w2 tb u0 su pi
+\n a3 ss je un zf vq zg wo mx d7 pm gd vn eu yk tq ik fu ai qt qf j0 to yy at ii qk wz lw n7 ly
+\n ub dz rv qt rm wg ea pc j9 qa mr h8
+\n a4 wp td ur px qq ki yx go wc tm an
+\n po gn rf a5 uw qn q1 nn we is z8 wj ca t5 ij eb tz ef pr ix g3 ek ta a0 y6 sq pa wq cx kq qw we rt c0 mz pv py wi cw mj qt qu 0e oa tj ux pf to hr ao tx yr ts fd fv s5 ui qu j7 gw ug ss cy ks qf xm fk wg vp kt mv qj mn lk vh yt ol rf th os e8 tj ua rk on pq dg kv km wq kp ad bp os bw pb qh wp zs q6 u0 gj yu o5 if a1
+\n a6 he pu vd cd q4 jz z6 qc jc bz eh wi b1 ed ym eg fo us ib td y8 gj zm pk lc qr wu mh qw sw fi ue j0 xm kz y2 ev aj df h3 qk qc tx rr rl rc ut ad so ro tw
+\n a2 pa we kk eg q7 lh zj wp 4z gi yd yg rc io ix r9 jb xe rr iz jd ij tz p9 qi l4 pa g9 s4 vi tj pb hd f4 qh qk lq qs hn ro
+\n gn k9 qz aw wu ki yf e2 pk v8 xk wg 5y t3 sl u4 ya gd hn ql zr oj ig
+\n fr qn qh ph k0 q2 nq wi zz rh rx ee ef uo d7 ix el fh qv dm vg px wi m8 qq gh ud qy ec fu yw uw sa tb lg us sk wf 5h qf sh vp wk qk zw qz wg aq of ys ak al f5 re f8 pr ku qx wc u1 lr qs wb f4 cw k2 ka hk mf yr w3 ro ir
+\n gr qh ql wh kv e4 r4 oy lu qw pb et uj tb tn xs kr dt td t8 e8 uc xn eq yi af
+\n q5 dt ed y1 am qv ut gx m7 yt rr yq yr dy sh mt wd wm th bv ym
+\n tr qs ca lp uv q3 wu o5 c1 rx om ee er ta ou i1 jb rt ry os ti fc ss px jn gy jz vp ea tg ay rq u6 al de qc zt wn ez rz eq aa rm ox
+\n uv ds h9 fy rf jq he qh h8 d4 wr wf du ck wi km yp ut rv io g1 rb av y4 tw a0 hy sz qx gh ha q9 qw ze 1a bz bx bv qw po ee wa qi xk ri i6 ic he tm hl sj jb qp wp jk qr kf sm l8 be x3 qu ql t1 x6 yy fg rj ua ug qg kv k1 wl v7 xx bj ae wc n3 zy tl tz zk wq re n0 yw oh yr oz eu fm do ux uc
+\n hb pa h0 pg q3 mw q6 mg ls lh am sc gz al j2 wt t9 lm qe j2 rm re rr ry fl yw ux i0 2e eo bt vh ra ys sm pb on tx re ff wn wv tu rt ox ul ge
+\n al fw zb p6 hi qy ay ou rg sx ag rz uy
+\n bx wi kv t5 3w e7 sh ht ff nu la xo qi s3 uy jb 1o vm vi l6 be x3 ny pk aw u1 rk fx km qc be rw yn ey eo ro
+\n dn q3 jf w0 e0 lh rv zv js j2 xg ld hr qe mk s3 dr kw kc dh h2 ql cg zv n3 ym yt aa as
+\n ft ty qh pi d3 qz ip wu wi q8 wj a1 mg mj ut wt r3 om ua y3 ou fd dc zw lp xe cn dh ng qe kp qt mz xy ef ay od tz p8 i0 hu hz qo kw jw qf kf 3y v3 qj w0 ib ew t4 fg oc e9 ua ov hs u6 f9 h6 vj qv li wq iu yv xv rc w6 rm r2
+\n d2 d4 av jc si rs ut 5q pa mm s4 e5 tc km
+\n qa uk uw qh vd d3 q4 xo wo c3 wl wa w7 w9 mc r4 y2 fp r0 tw fr g8 ae qn lp sd bk en vr gd hu j1 xv xg rw ep wh ed fu ul eb fl fz i7 ht jx ns v3 ll zs j0 op xf qf l6 l7 sn wk zw wg ej ti wb wz t6 oz rh rj uf je av dj h6 ql wl oi im v8 zr qv wn ku wb bj ef o4 yl r1 ei so if uc
+\n k7 qg q5 kx oz mu wl ws rh b2 rk yh qn qe 6o 1y mj ei pf ye e1 dw hj hx do qo gc rh v2 zw x5 t1 t3 yu th e9 em au qh f0 qk km ql kp wc 5p vx bg ea ev wn wm w2 rx o3 yk ru
+\n a2 gr qa az dd gm d1 k9 hr we bz lg mg ny wp xd mp yo pj uy xs ua pt g4 tw ez jv q9 qn wr nd md nf qq ng pi bb j2 eu yl ij ty sb os eh hw i6 hg m9 nj wa qs w5 qf nz zh hw wh be zo fs rz me yk y3 ub t7 t8 u2 u3 en ha fl yd hf qh qk wc 1x ze w1 oj ee w3 ey du uz id ah pi
+\n o0 hn qz q5 du 2b bn a4 ex rj y1 dj yz y5 ig tf th js qv 5e gc j3 ls d8 yw bc cz tx mb wf ij ty ai as hi lh l3 qd n1 7o wf wh qh wg ot ra l6 el e5 s8 dg vm 3f 7o wn yw gj tb et
+\n fu a5 cp ch hx hc rd eh tg g4 li sw sf il eg eb zj 2o cg ew uc
+\n qs uc rp ml eb yd if pa c7 oq vk wu ot yq rl uz tv gb zu vm w0 sc tf ud qk wm ko yy er tw
+\n ak tt ub pa pq il jq q1 hr k0 uv ql q3 kk zs q5 z8 is lh q8 w7 qw es ii av yz dl ht td g6 vs jm zr px bj no g2 g7 la c7 xt mz yx tt ym os to s1 ur ta s3 gv pl qy pc qi sh jv j8 gr l3 bi oa wd fy v1 s1 zp hg 5e t1 rp wj ms wl t5 el wm at fl e0 sq h3 dl qz ka ox tj qb wb wq re xr rx em ee yu ri do uc a1 rp
+\n uv rs un qs um il ul q2 jy kl wo a1 rj tj pk ys r5 yn uo oe y4 ou y5 ar zb g0 qn gx zt lb 2v rm qy nw yz lm eg og i7 ht ss qy qi ge wf bw lv lb wh cs wk hf 7g yb zw hk ns ol rh th yo f4 rq dt uj qo fd wx nk rv ka fl mu rr q3 w2 oj ee rt w6 ru ul
+\n qs hq qf qn d1 k0 q2 kk o5 na si bv mx e9 tz d8 tq dl ez qv jf zw ww wu xo b8 w3 i2 te p3 ry iz s1 ut as s5 hk wo wa l6 wh bq xk wj wd ra gu yi u1 t0 ak ai tc ax ip uj nf zb wv bd wo fz qm qj pe md ew rv gj ol yk tn iq yl is si ie r2
+\n d3 ni wr ws li mj ds sh sl qx vs rp ft ik e1 sd af ho xn wg zh 6b rp eb f3 u5 uf df py k1 wz vk vx k2 dg wm er rv rb
+\n gv iz qz wo o7 k0 oq ti r9 us ib ps g0 jm jb tq ue iv pj sa cr kh t1 ot wc at e9 ys o2 ab qj ww za rn yi
+\n ty qn qh nt ql la q3 kl wt q5 mp mg o9 ls lh g2 id ez sw hu qb cx nu pl kw wt vz v2 1t wi mh qr eu rm qs xb ei ij uj yb sn ai iv pj oj de hy gv ka lz pa nx wg wj kj cw zp wk eq wj t5 ns rf e6 rh ev t9 eb ir e0 sm gs gd ds f5 dd de fc f8 x0 lm qz wz xc wc kx cu wv ks lv kv wn pv ei wm ju ww yv zy yt e2 rb w6 oc o6 tq ig
+\n q2 o2 gj q6 zh mg li mo vo ch tl ax ip ho wt ln ro wo qr tr tt os fo e1 de hg gb sk w5 fp kg mm l6 yi u3 fl hs u6 fx re rr dl wb jr el rw pm rt to rx w4 gx
+\n yv a3 qa uz k9 q2 o2 bp ny zl mj vo tk rx ui g5 pu qx nr xt ls lm rv qs yv ik fz tv wp nk gn qh qi yf ek e5 pb au tc ac kc br qz 4t qc mx ly wm kb ez w2 u8 ei o8
+\n rf vo q1 o2 wt q5 oc 5l a2 es oe sc cx wq qw ky em tx rm p2 qo ft ed uq fs i9 ok lz v3 au xj v4 wf l3 eg ej ex wl rv qv ak mi hm cj yr w4 si
+\n a2 tr qa fy qd qh oh kk lq dy bz oc jf wa k0 ip pm po qx wq cx we wr bj j5 yq qe gg rq rr oo ru od ix qt af or sj qo jn gr sk wd nc xo xl wk hh yf eh ek aq ex ar en tl ys rq pm ug ql qz wb wn yw og xw an if it
+\n ak kk wu ig df w0 rb y4 fr gg i2 qb qw yf j1 ij ue s2 qt ad i5 yp ai l9 wk km ql zn yb ee rb ir te
+\n hb q2 ld wt q7 qm km ws w7 vi iu yf rc g1 pr ot a9 pn dk ib qx hi qc jd jg hd q0 lo jj cn wb mz ec qp uj y9 tu yq au tp hg pl jx vx j7 wd 3y ca au rq kv qg dh k2 ok cf qa wv bp dw iu de k2 rt hj wm rc er o3 ey fn si if so
+\n hq q1 qj d3 ws as ld mu rj ut d8 ey ou ib ez gf y7 qx qn vz qm vd zw ww d8 xu v1 av b6 mg gs bb g8 rm qy yv rr ry oa tb dt jb qa j0 qs l5 nz qg wj t4 td t6 eb ua s0 pn ii ac x9 qj k4 wc v9 s7 cj zy wo 10 hn yq vz u0 fm uz ux
+\n ra pp qd d2 vg wj qn rh we ht st jm bv wu wi qy y9 de gw wa sb uz r1 qu pz ot td rg go e8 sn iy zr wc s0 ww ea pw ac w1 h4 w6 rp
+\n fr uv pa jt qk q4 ls wu wi mt xa vu tk ed rb pe fp am sr hp qv m1 gc en yw qq qt ud ey eo p3 tj tu en ix ux ye tp ic s3 ad hz qi uf qa kr wh vd lk yn 5t u1 t0 od rr x9 f0 zj kn nk wp zs se rv ei pi
+\n yb dc qs py qk nb jj ql q5 m3 mg zz e5 i1 zr lc zx xu vq hr xp by ei yx gl qi qp ij eg ai ph ap tn dy zp qp bp kf j5 ib vd eg el yy td yo ie ag em fc kx zx h8 wv sv q1 te wv vm yq ol if a1
+\n ak rd rf qg lo nq xy z6 q6 mw c1 cu z8 q7 vw wp a2 zz w8 yo uu ee yn r8 av yz y6 pp uq dv i3 db jv q9 2l xg rp ib lj sq tx tc mj mk qr rw te p3 ik eg pj e3 i9 im tb tn fm na j7 lj wa ct rg v4 he x3 kl bi r7 wj y1 l6 wk el 9f t6 gu tj od e9 tz re uh o2 zk ki cf lw jp sc s6 qs qb yn yw ms md w3 rv as yi ox du yz ir yc dp hc
+\n yv gy ik k5 db gm ux qj gc w8 ea g6 dx po jb 5t pv wi rz qp jv v1 ea en al ii dt qj py w4 if ux
+\n iy pd yg qq p4 in qa y1 yy ta fb zk s6 lu
+\n ql ws rv yj jk ke lm ff lb fx s4 av uv wl n1 rv dp
+\n qh d3 rs ih rc aq we 7y ud t3 h2 zt cu oc
+\n fr hn k6 je q3 k4 tm zh lj aj li a4 t7 w7 kx ut pl rc ih th hp wq pl ls ma oe lf wu l1 ve lp qt qy fi ti he oh hi ow tm cu p7 nr va r3 tt wk wc s8 s0 hs al o2 hk x0 qj lm v5 wl qz 7u iw os lu ah wm hk e1 o4 rm fq ro so
+\n gr sa rd um pf ca ga ql qz wt ld z6 vw kv my xt cn wr eq tk rw fe qx qc qr qr rk qa qi ex p3 p5 dq ff pc sh cy oq v4 wg s7 yo ya od rq dt un bw zm da bg q5 ru ah
+\n iy qd k6 dm oj qz zd vr w0 r7 d8 et y1 eg yj gz qq p3 il i8 ge wp sx s1 wj t4 lm jo qp pw xc vm sr uz ig
+\n qf u8 iq rg rk g4 im ih oq fp a0 ib tc uj tn nc kz ll u1 un qv ck lv vv
+\n a4 df um jg z2 lq wr tn xu id wo ez rk rz ew d5 ti ix to r0 sk fa fe fr hp jk wr v1 ms wu jm rc qt tr ex uk vr to tz ut hl sg hb qd xn rj jc 6x mo wz rp ek y4 oi oo ae sc e9 od pn hg wz js qv ln ju rx yg as rn gk o7 so it
+\n dd qs qd py k9 lw wt db gk zf nw wh t2 nf zx w8 rj ue w0 tl ew r6 ui ef g1 or ej pn an tq ou ff qv gj jv q0 kq 2l uo oe ku wi w1 tx qe rv 1l ws eu lv p4 ru fo tz ph ib fv uu i3 qu oe pc gw wp sx zd kw w6 bo w9 cs cw my qz zf rg fh e8 ay yd fz rw fc ng qz cg wx qp oz qv ly ha cx al iu rt mp e1 ee w4 tn ul ru o6 ag aj
+\n qa kv e5 bm yj j4 m5 rj qe ri ht oi qf qe t6 e5 aw t8 wc dw un yb
+\n iy jg jj d4 ju ol wy bs wk wq t8 a9 y4 ta dx jp qc q9 su si ut q0 m8 qq 4a zy qq zq sw po qe qt la sn p6 ht oj hy hh qi gc bn w7 au ya kz na oa ox ov je fb or wl qx ze cg we fk tz tv cg uq w6
+\n dx sa qa qs db go lo z3 lf ox jc wj 93 tj tk yf ii ef fo ua sk g5 fs el oi fr sx fg se q9 xq qw j3 m6 4h qq l2 eu rq qu qi hs uh p6 ix fx qa i0 wf ke bq ne wh xl ms un ex sx yi ua pb s0 rq ak ao fc pr qj cw wq vy wv u2 3h wq re yw eb gl eu
+\n hv gv il jd go qh d3 we q4 q5 ej lk my gv a2 ds ex e7 rk yf fq pu ff qx ho js gx j1 qe kw gm ja ns wy ln d0 cz xt te xu tt sb em ix ic p0 i8 im ui di gt ws os r1 qy pz l6 e5 e6 op sb pv sn ii rr v6 ql le zy pv mt da yq ol w6 yz ag it
+\n ds gy dg jf qg uw qj uv q5 q8 q8 q0 qm e5 rk yd tz pv if qn ju cv xy ki qw mj ls yv ru of tz so yr oq qi m9 sc kw qf zh jx wh kg l0 t1 pz un fj os ha sn f3 e0 om ab cw ct nj zy wn fl ww vn fn tn ie ov
+\n co qh jr jj cb bi wt q6 ra qm zx ur r5 an fw tg jm re j5 vl em sl xz qe wa 45 nq ex yb ed ef ph e4 tb s6 qy lz cu gm pd gq mc ca 85 yf wf hu sb eb fk fv dt cq lq qz ww wv xr n0 eq ok er et iw r2 o7 fe it
+\n ra dx og wd wt o9 i4 it pk qo ic dt hj jl oq sf lz wd ca hi fg f5 ap x9 gq nd iy q6 ep
+\n o9 gb a5 z5 q6 wu w0 tl r9 dj if ts ig it zq ll qw qy ep ed ry p5 ut hh dr i0 hl qp qa zs wd ya ot xk s7 e9 om io dl ki k2 wv q1 5g er rb rm
+\n qs w0 om ed tk ta th gf ii av og 6o ee o8
+\n po ty rf qf he qc hz bv c5 mi rh ew tu ef sh ix r0 d0 pb tq fw ig g8 fh i2 uw hf qw qr sf cn wi fh qt hp nq yv fy tj od ux ut fb hu tn qt oi qs oa xm dm fa nm qx yy oo ec ev ox sb ya rk ys ud jy dz zv qc zb qb 17 tb lt yt u9 w4 rb st et ry yl o5 di ux
+\n gb gt az uk gm qh d3 bt qz wd ld o3 bl cm q7 ck k7 we b2 yd ua ew tl rq yg us tq js fk jb gz jn jg qq in qr rq qy 3h c0 qp p3 yn ef sb ym jl sf sh hv qd p4 fj od ix kz ni wj wz tg t0 tj e0 sm om kx ku cw nb zc aw cy qv bq kg wq pn zz wv cd wn yr se o6 pu eo gc
+\n gy rf qf k6 qh qj cd q3 kk cj fw b4 fr mj w7 bm wr ya z7 wt w0 r8 ip ti is pn am y5 qb hs jf qw uu wr np qt wi 1t bx qq qw aw er cv qy rw eo oa iz fp iv qi jm nk kr qf xm eo nr w0 qj bi t3 uv wk ek wn ex e5 rf rh ga it f5 pm hm f8 qj gy jp le wc qc lt s9 zu lb q1 ju w1 uw w3 oj tn tq ir r2 te
+\n ak gn pw a5 qh k9 qk nb 2l qz wr kv mt gt w7 zx ii oe ug ix sz qx qc ar sr zb su vg qe np yg qt yj sv uk pd uq pf of eh jb sk gy ws ke kd be og kh x6 me wz e6 yo iw ah rw pm rr qg pt lm ql qz wz qs ly wb wn q3 ry rx gj ia o5 ge
+\n gy qm d3 q3 ia c1 ta ex e5 e8 eg sy cl jf qe nj nh m9 qa w6 ek iw kv qg ab n4 w5 iq do
+\n uv gv qa un az jd qm eg iw nr q8 zj ny c5 vu rl rx yn et ia ua is ot pt hu im gj qv vv xe xu 1g wo vt qt st qy rw qi wh ft es uk hw to p8 fn f1 hp qu sk p2 l4 zf qf g0 fi l7 be ky iv mn xp nn dt 6b ro kw uv ra tp e6 sv s8 sn tl fz iy qh hz ve v8 h8 th wc wy qb xm s9 hs wq zs yq tu en zt w4 dp yc
+\n sa ak gi hw qm gp pp qz fm id lh 6w 9h xr w7 ui ow rb oe ia us fq g5 y5 ig oi y6 im gk ze qe gn 3o ye xz jh qe db qi p1 ep re op te y9 os pj e3 p0 zp qa ih l4 cu zg wg sn rh lf fz ic kh ni wh vh wx th ag u3 f6 uj jy k3 2a wc kv lu wo hb eq w1 rb xw yo ei o7 gx
+\n hw wd r0 g4 sz b7 pi sn tc in qt zs rg eu
+\n iy hv hb hq qf z3 q2 xy ia tm zf jw wq a3 rk w0 d6 eg et is id po tg gh ob jj wr np no wt ja wy xl l2 yr bt bb tc cx qr rn qt lc rw sy ex y0 ru od to tz og ye hr pj de tv e4 qt ad oo qa jq fj l7 fo wh nt pl ro wl vp yy tf va e6 fg th ar s7 os at s8 re df pe hj f0 qx qc x7 lu nc zo tx bf ww pq cg w1 rx id pu it fe
+\n qf fi ld 4o ge da t5 zz mo zx vi ui w9 pj rl rz r4 uy r5 sf av ot fq tw sc zv g9 qv i4 ut iy m1 wy xo b0 ud cb qy rq ug wg sn fo ix uc aa i9 ss sd di sh j8 qp xa ep w0 7h wl t7 iw tj ya hs e0 fz hd u6 hh dr dh uk qj qk wx ol xb qv wb s8 15 wy zm jr nx it eo fx ww yv zy to ee yu yi iq ey iw rm uz yz ob
+\n po ra ik qd qf je jr lp wr ji ne wu 2b nt wa e7 rx xo ia yl ta ig ff hp gk jf q9 vd si gc qe rk wu by yg rq sb os fu eh em ux ic ao pj hy im du qy cr 2e l5 qw v1 lf mv wk rx dy rp ra rg gi eb hs au ap bo oa sn zi f6 h1 zt ey yz do
+\n lj vu y2 if qr wr ta so kg 3k ol u1 f5
+\n yb cv mq lf zz ue ui dx qe tv qu ex tz hh tb dy ds wi rb
+\n hb gt qa h9 rf qf qg k9 q1 lo q3 ql z8 gl zg q8 1t wo vr rg ez ws mo w9 yo r3 yd e9 ea sf a0 im tg y8 ar qb ni wr qr wv m4 ix d9 nz yq fa if yr bt qr mz qi ea rr xx at ic pk qi za hv kq w7 co xj wd x4 zs wh y3 rf ec u2 e9 ah s0 uh io un h8 iq zb bs nr be zo fz vb wm pr yw md rc ur er ia yl ox ei ux eo tw o8
+\n qs rd rh yf px ow d8 tq ig ih cl qr yw qq in qy wc ek rh ya qg cd x9 qm lq to o3 ul a1
+\n a2 cp as gk kc 4u zj z2 t5 zz rb eh sh sx fg fh g9 hp vd gx oq cv pe wv b8 qe cc nw tr il tl e3 qu l4 mt wk wh aq td e9 gf lm qz bu jq my wp vb dg y0 ye yq w6 r1
+\n ij az qn ph qv bv bf mz iu is y8 ar fh q9 hd qq ji sf ld up qo p2 at sp in qi nk l5 e0 rw px 5o ew oc te
+\n qd jr pa q5 w9 hw hu y8 dn qn zw wr ma ei xl dq i7 i9 vb sx wf wh na wl um e7 s0 h2 nh nk fj yl wn iu u7 as ad ey so uc
+\n la up ic g5 ay ic x8 u2 ar eb wb yr aj
+\n po da rd un qg uw lq m2 4r wg q8 z9 t3 zc d9 ae st q0 li qw wt kr qu ry en sm qf kf kh ny yt gi rh u5 em tc kv h8 qx lr jq ef
+\n a2 dh qh q1 h8 qz z6 kx z8 bv 3q df pk tl d8 tq tf g8 zb qw 5p zm qe cv yb ec uz iv e2 gq wp uh kq ws lc wk x3 t8 rj fc io je dk lr lt wv wt bw be eo q4 ye yy rv ok yo yp ir ig
+\n rs ij ty ps ul wr bh kb rs z4 z8 er px uo up y1 rb fo jo gg dv ph q0 jn xw ww d8 rp yd yf tx b0 op yn of jl tm px fm jc zf qd pk wh rp uv tp t9 ir yf ug qg v5 ku qz fd k4 cu mw zn iu bg lq ly rv su
+\n ik dm cm or tw pu lp eh qd kk j0 em ng tw
+\n ra ds qk cg q7 k6 4p t6 yu lq go yd eq r8 fw am dm xy cm v1 cz eo qi ij yn eg hq tc sj qa i0 oa l6 p6 wj vd wf mr yt ex e6 yo t9 ev s8 en rr bq kg hb lm re bj ms w1 et du
+\n o9 gy bl wz t8 hq iu ix av y5 y8 jn j1 np xt t9 vw qq 43 xv 9w yi ft es hy op lg vs hg wd ef wx ou ox sw dr ze xr st fm ah
+\n a3 wr fb jc c2 w8 rx fe q9 hd xq qq wi te y9 e1 qt qi qs nl ca bh u2 md tv hx
+\n ra jh q3 aa t3 1o eq lh rv fo us pt dj pm pi qn zr bj xj cm ix a0 ra hi eu nw yc p3 ru ri ue e2 jl hi wo g9 xn qh wl wx go yp rr dj nd ch u3 fj bd jy vn w1 ia ox tm uz
+\n ma y5 bl qi g7 ri fl ap 7a yo ko rp
+\n um dh pg wq r5 sf ia ta an hs ne q9 wt sh rk yi ym of tb oq do hv wj ic oi sc pe sc wq wb wm tq
+\n hq qm gg q4 xu k4 k7 uo wt kb et fo ey aw g0 xi am in qy eo qi eb ay ue og nt kl wx s9 df qg f9 v6 rv sv pc tl my bj wb eq h4 o3 ri
+\n tt uq qh nb qz wt jx ya on om io ow ha qp e2 fd e4 hp hx p2 vm xg xn ra l8 iu yf jr qh k4 1l oa tk zp yw rz sy ul yx eo ep
+\n ij yb qs fi ul qk by ql jl wr bi q5 bl tm q7 xp k7 vy gi tj rl rx yf ym tu er r8 pe ip ej y4 fd jn gx zt vj xt xh rj kt cm ri nh zq pp eu rw to pg e1 ue dw e2 so tb gm qi jb nk gy pa v1 xj fl kh 3k kl ed wx wc ek yy ez wc iq yo u4 it tz ak hn f8 h2 hl uv wz h8 gi nk ch zt wt bw kn yq e1 tv zp ag it
+\n qs rf jq go qh a8 jj xt le wu qq yd d6 d0 ff qc su c7 lc wp ty y0 tu ti pf ta aa ug vb rf vi rx no un yu e5 7r up rj ag ha hs fc wz bo qv bp tv ki er
+\n yb qa rd lo ok q6 o6 ba r6 ow or yl am aq gd dc ho cx c0 wu g4 ib c5 ep re qo ed yw iv ta hy jc pn xs oq xn bo zg ps kr iz yp wh wj xl xo zq me na ek wl wx wc rg uo ir tk aj da rw hm io uj fb jt qj dk nh zx jp qx wc zr zy zu nc xe ww wv vn h2 q6 en ew ad yl af eo if r2
+\n yb o0 dn z1 q1 sw qk po xt wr ls z8 ox wu jd ro q8 lh mh wa rg ea c3 pz tu g2 is a9 pn tw po qc sa jj vh ax xh kt wy jv mg nh kp b9 qi od ht e4 uu ij qf sf nc wk ap wd qz rd yi s7 tk pb it f7 o2 f9 kv qh h4 ln wz gi qd zn xm sn 39 yn rz u7 yr yj is ie ag ir tw
+\n po dx uv pq jd dm d2 hr cs gs d3 q4 wr np ab di mh vt w7 w0 on tl ia ta aq dz y7 i1 qc pg q0 wq j2 c7 la cb il wr lv na ru 4g vz nf bc sl qq qp qt ud rq wg ex uw he tz ye p9 ui ou vu at ix w0 vf bi ed yh wh ra y6 wc u1 t8 fj ov iy tz kx h1 hj jy qj h6 ng jo wz cj ie mt rq te n9 mp ma q5 8w rb o7 sp ob
+\n po ra dg ca qj q2 is kn rd ws lq tu ym yl y5 tg pp qw we cn a7 1t jd m7 wo yr sz qa hp ei qy ec p4 hw p7 to au iv ht qt qy qo l4 lz xm wd yf wg ez s7 en f3 tx yf rr f8 cw ji qv yz ry ew w2 oj w4 w5 st rn oz ri o6 dp aj
+\n gm jg ju q5 np lf q7 xo 1y qn k0 jo pp qx th q9 4f or ro pu bv eu nw uq ao dy jb j9 gr rd nz wj wj r9 co ta rk od dd gg hm df pr km ng oj qc sb qd wb tz cq ex wb vb ty eq tv iw
+\n fu uq co qk jl cg ld lg wo vr gc bd rj r3 yd rz iu ew d6 io to sh y7 jp db dn qn qm si xg qr ls jo lr wy rk wn m7 qu bb es op qp ru en ta e3 in hy dy hl vc gc gt jw ke 2t wh rk lj hg oy e6 yo ev em fz rw pq re dg qk ku oi qz k4 qv li rq n8 ec 4d yb wb e1 iw id o6 ir do ux pi ep
+\n a2 wu jd ef dc mn 5e qp pl xd ag ay
+\n yv o9 al a5 uq qg jw pi z2 jt cd q5 3m zl ez vu rg jl rz yf ix sj fp d0 tq ff ha hs zw om ni m0 xg c8 a7 ki qw cc ei xg j3 tt tu il p6 ix tp tx ib sp hg p0 fc pj su qu jv sj lk qp ws qs gm 1x mx lv wj qu l1 dt wh wv un aq fg rg e6 uo ar ie up it sw tx f6 o2 h3 qc qa ho vj u3 kd zy n6 my ww vc lr em w2 se rt o4 yc a1 te
+\n qs:1 dv:2 pa:3 ty:4 iz:5 uw:6 qh:7 jj:8 z3:9 tn:10 jc:11 eg:12 e4:13 qq:14 w7:15 ut:16 r3:17 uu:18 kb:19 up:20 g1:21 fo:22 iv:23 if:24 fd:25 gd:26 sc:27 qm:28 qe:29 xg:30 ia:31 wb:32 he:33 ky:34 hu:35 tv:36 rw:37 qu:38 rr:39 es:40 p3:41 ue:42 s2:43 as:44 i0:45 dt:46 qt:47 hz:48 jm:49 j0:50 gy:51 ci:52 fi:53 hw:54 nv:55 ea:56 kk:57 vf:58 rx:59 68:60 ti:61 rp:62 wl:63 oi:64 vp:65 at:66 om:67 io:68 uk:69 pt:70 qh:71 qj:72 dl:73 cf:74 lr:75 cx:76 wq:77 ku:78 ki:79 w2:80 yh:81 af:82 ul:83 sp:84 yc:85
+\n ub:1 yb:2 dc:3 ty:4 gm:5 dm:6 go:7 nv:8 we:9 ql:10 by:11 la:12 o1:13 ju:14 o3:15 jx:16 fm:17 aj:18 wa:19 rg:20 e4:21 vi:22 a6:23 r4:24 xo:25 tz:26 oe:27 ip:28 pv:29 dk:30 tq:31 a0:32 tf:33 fg:34 tg:35 i4:36 pz:37 sd:38 ry:39 ky:40 mg:41 hy:42 g7:43 eu:44 qy:45 yi:46 rw:47 qp:48 eg:49 yw:50 hw:51 sm:52 uc:53 i7:54 dw:55 fx:56 s3:57 sf:58 zo:59 m9:60 xs:61 vn:62 rf:63 ci:64 nz:65 kr:66 qt:67 9y:68 pj:69 lk:70 ee:71 pz:72 ef:73 rk:74 e0:75 fx:76 uf:77 az:78 fc:79 qg:80 jr:81 oy:82 lq:83 cg:84 qp:85 um:86 ad:87 wc:88 zn:89 bw:90 n6:91 my:92 xr:93 mp:94 tu:95 en:96 o3:97 iq:98 ir:99
+\n da:1 a3:2 d1:3 jr:4 dz:5 ca:6 ql:7 nu:8 q3:9 cf:10 o6:11 nr:12 mt:13 lk:14 yr:15 rs:16 lp:17 w7:18 a5:19 pj:20 ys:21 ym:22 r8:23 ey:24 to:25 fs:26 dz:27 im:28 ih:29 sw:30 qx:31 qv:32 zn:33 gl:34 j1:35 xe:36 lc:37 zc:38 vw:39 6a:40 mh:41 b9:42 qt:43 rm:44 re:45 oo:46 qp:47 p4:48 tk:49 ix:50 p7:51 og:52 tz:53 yr:54 sp:55 aa:56 hk:57 ih:58 lx:59 qd:60 mx:61 4n:62 kk:63 vf:64 el:65 oo:66 td:67 ae:68 yo:69 fj:70 uf:71 pr:72 hl:73 qj:74 qk:75 wr:76 qc:77 qv:78 kf:79 yz:80 my:81 wq:82 hn:83 zs:84 dr:85 ee:86 u8:87 rv:88 et:89 ru:90 ie:91 ag:92
+\n gt:1 ph:2 z0:3 zl:4 mu:5 ui:6 av:7 zm:8 om:9 ui:10 vh:11 qr:12 he:13 qr:14 es:15 fl:16 ws:17 w6:18 nc:19 ra:20 rk:21 kp:22 ol:23 wm:24 yu:25
+\n dd:1 df:2 jq:3 jd:4 ux:5 ql:6 el:7 3r:8 ya:9 uu:10 iu:11 ee:12 eh:13 g3:14 sj:15 us:16 ib:17 pp:18 qc:19 jv:20 hd:21 bh:22 zt:23 uo:24 d8:25 b3:26 xu:27 bc:28 rq:29 te:30 uh:31 ex:32 tt:33 eb:34 il:35 qu:36 pc:37 ge:38 sj:39 qp:40 ih:41 xf:42 3r:43 gr:44 yh:45 qx:46 tu:47 wl:48 wn:49 sz:50 up:51 ay:52 it:53 ab:54 jt:55 qz:56 v7:57 wn:58 li:59 za:60 6o:61 w3:62 fn:63 yk:64 eu:65 ie:66 gz:67
+\n yv:1 o9:2 qf:3 eg:4 eh:5 mh:6 jh:7 rh:8 r3:9 rv:10 ix:11 y3:12 a0:13 sr:14 qc:15 qq:16 qr:17 wr:18 qe:19 bx:20 ki:21 m8:22 mk:23 qi:24 lm:25 uk:26 eb:27 ai:28 ur:29 e2:30 xd:31 nc:32 ca:33 eo:34 mb:35 ed:36 uv:37 rs:38 up:39 ya:40 of:41 hn:42 lw:43 wz:44 qz:45 et:46 qh:47 wm:48 zr:49 rc:50 o3:51
+\n ss:1 qg:2 qj:3 ph:4 qk:5 q2:6 cs:7 z4:8 bi:9 qc:10 cj:11 q8:12 qn:13 w7:14 rj:15 ys:16 ea:17 r4:18 uy:19 om:20 rc:21 ii:22 fp:23 sj:24 ej:25 yz:26 el:27 qx:28 qv:29 zn:30 gk:31 q9:32 hs:33 m2:34 ii:35 d7:36 nk:37 c8:38 j4:39 qq:40 dl:41 gg:42 pp:43 ei:44 qo:45 yc:46 od:47 fo:48 eh:49 fp:50 ta:51 hy:52 ok:53 tv:54 uu:55 us:56 dp:57 qf:58 sb:59 zg:60 ks:61 sg:62 n3:63 wh:64 x2:65 nr:66 cs:67 wk:68 kh:69 wk:70 wf:71 ew:72 7h:73 7k:74 oy:75 t6:76 gi:77 rg:78 yp:79 s9:80 ya:81 e9:82 pb:83 tc:84 dt:85 dh:86 hk:87 h2:88 pt:89 qh:90 h7:91 wz:92 n1:93 qv:94 kd:95 pm:96 cc:97 xr:98 kp:99 yk:100 tm:101
+\n gr:1 qa:2 ft:3 tt:4 gn:5 qs:6 pw:7 pu:8 ca:9 ph:10 ls:11 cg:12 cn:13 zf:14 bz:15 q7:16 z0:17 c3:18 qn:19 gv:20 w7:21 rg:22 ut:23 e8:24 ii:25 er:26 ip:27 sg:28 y3:29 oy:30 ek:31 ht:32 gd:33 qx:34 g0:35 qv:36 db:37 su:38 jn:39 qq:40 lz:41 uu:42 jo:43 ru:44 an:45 wi:46 kn:47 sq:48 nh:49 qr:50 qy:51 yx:52 eo:53 qi:54 xz:55 y9:56 ru:57 pd:58 au:59 p7:60 dq:61 he:62 ut:63 ok:64 fd:65 jz:66 ui:67 hc:68 j9:69 l3:70 hb:71 xd:72 jq:73 gy:74 kh:75 wf:76 xs:77 sl:78 rs:79 aq:80 ez:81 y4:82 ts:83 um:84 yi:85 e0:86 gh:87 dk:88 py:89 v5:90 qk:91 ql:92 ko:93 jq:94 wc:95 nk:96 v0:97 wb:98 qv:99 br:100 iu:101 wm:102 6p:103 sr:104 gk:105 yp:106
+\n o0:1 pa:2 pf:3 z3:4 jt:5 jy:6 z7:7 cm:8 ne:9 w8:10 yz:11 fd:12 fg:13 zn:14 qq:15 ll:16 vg:17 wr:18 wb:19 ia:20 xx:21 yj:22 ty:23 eh:24 e1:25 so:26 ts:27 tc:28 s4:29 i0:30 tn:31 wo:32 wp:33 wa:34 op:35 va:36 wk:37 x3:38 vg:39 qx:40 rs:41 sn:42 au:43 f3:44 tz:45 sq:46 hn:47 rr:48 o2:49 fv:50 un:51 k2:52 vj:53 ey:54
+\n iy:1 ra:2 ij:3 ty:4 a4:5 un:6 rf:7 qg:8 dm:9 jr:10 kj:11 uv:12 we:13 cv:14 gk:15 wy:16 z8:17 oc:18 wp:19 mo:20 jl:21 mz:22 ev:23 ch:24 rv:25 tu:26 ax:27 y2:28 g3:29 oy:30 y6:31 im:32 uq:33 qv:34 hp:35 qb:36 hd:37 iy:38 lp:39 nk:40 w2:41 bb:42 ho:43 ep:44 tr:45 os:46 en:47 sm:48 p8:49 p9:50 hy:51 ss:52 ui:53 gm:54 qi:55 oo:56 vn:57 ae:58 qd:59 w6:60 ps:61 dn:62 wd:63 wg:64 ro:65 mr:66 yt:67 ol:68 oz:69 rg:70 s7:71 u5:72 tl:73 yd:74 rr:75 ax:76 f0:77 cq:78 ku:79 qx:80 ze:81 n4:82 wn:83 kt:84 ca:85 jy:86 bg:87 yc:88 zs:89 yw:90 rz:91 w1:92 eu:93 rm:94
+\n hv:1 fu:2 ca:3 q8:4 mt:5 la:6 r3:7 pl:8 yh:9 to:10 sy:11 yh:12 tv:13 x4:14 tg:15 yp:16 ov:17 wn:18 ze:19
+\n o9:1 qa:2 az:3 gm:4 qd:5 pw:6 hq:7 pd:8 ga:9 qj:10 cd:11 q3:12 jk:13 pd:14 du:15 c2:16 zk:17 xf:18 t8:19 eq:20 om:21 es:22 rc:23 ua:24 y3:25 pu:26 ig:27 qx:28 se:29 qv:30 db:31 st:32 qn:33 ii:34 lx:35 qe:36 wt:37 xk:38 nx:39 ku:40 br:41 qe:42 qr:43 qt:44 rm:45 eu:46 xf:47 xb:48 rn:49 qu:50 qi:51 ep:52 qo:53 rr:54 ex:55 xk:56 p5:57 ym:58 fi:59 uq:60 to:61 ux:62 ix:63 ai:64 hj:65 gn:66 zi:67 oq:68 qf:69 kd:70 wf:71 xn:72 kr:73 w8:74 rl:75 kk:76 mq:77 rp:78 rf:79 u1:80 s7:81 oa:82 fh:83 e7:84 yp:85 e0:86 pr:87 ql:88 sx:89 ck:90 ag:91 kg:92 kt:93 mp:94 eb:95 rl:96 em:97 ee:98 w6:99 du:100 rm:101 yz:102 if:103
+\n qs:1 pi:2 am:3 6a:4 ut:5 r4:6 ii:7 sd:8 ua:9 ib:10 y6:11 pa:12 kt:13 pb:14 wm:15 qq:16 dz:17 qt:18 qp:19 y0:20 he:21 p8:22 ue:23 tb:24 qu:25 or:26 qo:27 wh:28 40:29 8j:30 t4:31 sl:32 rf:33 iu:34 gh:35 hh:36 qc:37 iq:38 bf:39 rl:40 wm:41 mf:42 oh:43 ew:44 is:45
+\n da:1 ps:2 a7:3 jr:4 z4:5 q3:6 bu:7 xt:8 ip:9 jx:10 q6:11 np:12 z7:13 bp:14 lg:15 bz:16 ye:17 wo:18 ig:19 bb:20 ww:21 rf:22 om:23 uu:24 ef:25 r8:26 ey:27 pt:28 ta:29 pn:30 y7:31 gg:32 th:33 dn:34 pg:35 qb:36 ri:37 qq:38 42:39 qw:40 zw:41 b9:42 b0:43 xb:44 qt:45 qy:46 yl:47 wh:48 xk:49 ft:50 at:51 yw:52 i7:53 sp:54 de:55 pz:56 fn:57 qy:58 si:59 m0:60 ik:61 wf:62 sg:63 cq:64 ql:65 hk:66 er:67 eg:68 lc:69 ek:70 na:71 wc:72 iw:73 ir:74 rk:75 ua:76 e0:77 ak:78 iu:79 sw:80 ap:81 uj:82 av:83 ab:84 hl:85 uv:86 zc:87 qa:88 wc:89 jw:90 vj:91 qv:92 vk:93 ay:94 kg:95 xw:96 on:97 rw:98 1b:99 wn:100 rl:101 eb:102 vm:103 eq:104 h4:105 yt:106 oz:107 eu:108
+\n a2:1 qa:2 rd:3 cp:4 qh:5 ub:6 vg:7 ws:8 u0:9 4g:10 bp:11 da:12 yo:13 ev:14 kz:15 eq:16 uu:17 ee:18 ef:19 yk:20 pr:21 sj:22 sk:23 fe:24 oi:25 lt:26 uy:27 j2:28 gn:29 io:30 vk:31 ns:32 27:33 ln:34 wu:35 ve:36 yr:37 l2:38 qu:39 qi:40 ry:41 il:42 ul:43 tj:44 eg:45 ux:46 i5:47 yr:48 tx:49 ph:50 oj:51 gq:52 or:53 zp:54 wa:55 qs:56 gy:57 iz:58 v0:59 qt:60 wj:61 ic:62 ca:63 lh:64 yb:65 np:66 ej:67 sl:68 td:69 l8:70 yi:71 iw:72 s8:73 e8:74 ys:75 yd:76 sq:77 al:78 dt:79 pt:80 tg:81 te:82 yb:83 eu:84 tq:85 r1:86 ir:87
+\n tr:1 h9:2 go:3 qj:4 b1:5 wu:6 q7:7 zh:8 el:9 tg:10 mb:11 ys:12 ed:13 ii:14 er:15 r8:16 xs:17 pe:18 r0:19 sw:20 db:21 ov:22 m7:23 d3:24 re:25 no:26 nu:27 zr:28 pq:29 ji:30 wr:31 lc:32 or:33 qw:34 ee:35 yy:36 qr:37 rn:38 rm:39 re:40 qi:41 te:42 ea:43 qo:44 yb:45 yn:46 y0:47 uz:48 uq:49 iz:50 tl:51 yr:52 i0:53 fm:54 wp:55 qs:56 qd:57 he:58 wk:59 kl:60 ew:61 as:62 hl:63 ez:64 oo:65 ox:66 ie:67 s0:68 f4:69 dl:70 wq:71 wl:72 ww:73 lr:74 qc:75 qv:76 vk:77 mt:78 my:79 kn:80 ep:81 em:82 yt:83 sy:84 am:85 so:86 rp:87
+\n ak:1 ft:2 qg:3 bg:4 ji:5 q6:6 bk:7 xi:8 tf:9 mo:10 ur:11 pj:12 yk:13 ua:14 qv:15 wq:16 gc:17 qe:18 ke:19 ef:20 eb:21 tk:22 uq:23 i6:24 oh:25 iv:26 gb:27 qs:28 rx:29 el:30 yo:31 rr:32 pe:33 wz:34 wx:35 ho:36 xq:37 tc:38 mo:39 yk:40 du:41 r1:42 tq:43 oc:44 hc:45
+\n ra:1 ub:2 qj:3 jh:4 jt:5 dx:6 ql:7 q6:8 da:9 tf:10 r3:11 ew:12 iu:13 sg:14 tp:15 yl:16 el:17 gc:18 6n:19 tt:20 ry:21 pd:22 ye:23 ff:24 lz:25 kt:26 yp:27 fl:28 yf:29 dl:30
+\n o9:1 hb:2 h9:3 qd:4 dh:5 qg:6 q1:7 qj:8 jy:9 se:10 q5:11 wt:12 nr:13 qv:14 ge:15 c5:16 el:17 6y:18 uo:19 rv:20 ax:21 pe:22 et:23 r0:24 fe:25 y6:26 dx:27 qx:28 ha:29 qq:30 lo:31 we:32 zy:33 v1:34 wy:35 dl:36 vr:37 wa:38 qr:39 rm:40 qi:41 qp:42 yn:43 tz:44 pg:45 ph:46 de:47 p0:48 do:49 qp:50 wp:51 wf:52 bw:53 xh:54 ky:55 xz:56 wh:57 hl:58 to:59 ek:60 rd:61 sv:62 rj:63 rq:64 re:65 h1:66 qg:67 qh:68 kl:69 f1:70 zm:71 18:72 ez:73 xe:74 vm:75 en:76 5j:77 o3:78 rn:79 fw:80 fe:81
+\n db:1 c2:2 bb:3 o0:4 w8:5 kl:6 kc:7 y4:8 qx:9 zm:10 pk:11 cw:12 id:13 ve:14 mh:15 lp:16 rq:17 jb:18 fl:19 x1:20 qi:21 wd:22 lx:23 f3:24 cy:25 bq:26 dd:27 ye:28 fn:29
diff --git a/src/test/regress/expected/.gitignore b/src/test/regress/expected/.gitignore
new file mode 100644
index 0000000..b99caf5
--- /dev/null
+++ b/src/test/regress/expected/.gitignore
@@ -0,0 +1,10 @@
+/constraints.out
+/copy.out
+/create_function_0.out
+/create_function_1.out
+/create_function_2.out
+/largeobject.out
+/largeobject_1.out
+/misc.out
+/security_label.out
+/tablespace.out
diff --git a/src/test/regress/expected/advisory_lock.out b/src/test/regress/expected/advisory_lock.out
new file mode 100644
index 0000000..2a2df6f
--- /dev/null
+++ b/src/test/regress/expected/advisory_lock.out
@@ -0,0 +1,275 @@
+--
+-- ADVISORY LOCKS
+--
+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);
+ pg_advisory_xact_lock | pg_advisory_xact_lock_shared | pg_advisory_xact_lock | pg_advisory_xact_lock_shared
+-----------------------+------------------------------+-----------------------+------------------------------
+ | | |
+(1 row)
+
+SELECT locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ ORDER BY classid, objid, objsubid;
+ locktype | classid | objid | objsubid | mode | granted
+----------+---------+-------+----------+---------------+---------
+ advisory | 0 | 1 | 1 | ExclusiveLock | t
+ advisory | 0 | 2 | 1 | ShareLock | t
+ advisory | 1 | 1 | 2 | ExclusiveLock | t
+ advisory | 2 | 2 | 2 | ShareLock | t
+(4 rows)
+
+-- pg_advisory_unlock_all() shouldn't release xact locks
+SELECT pg_advisory_unlock_all();
+ pg_advisory_unlock_all
+------------------------
+
+(1 row)
+
+SELECT count(*) FROM pg_locks WHERE locktype = 'advisory';
+ count
+-------
+ 4
+(1 row)
+
+-- 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);
+WARNING: you don't own a lock of type ExclusiveLock
+WARNING: you don't own a lock of type ShareLock
+WARNING: you don't own a lock of type ExclusiveLock
+WARNING: you don't own a lock of type ShareLock
+ pg_advisory_unlock | pg_advisory_unlock_shared | pg_advisory_unlock | pg_advisory_unlock_shared
+--------------------+---------------------------+--------------------+---------------------------
+ f | f | f | f
+(1 row)
+
+-- automatically release xact locks at commit
+COMMIT;
+SELECT count(*) FROM pg_locks WHERE locktype = 'advisory';
+ count
+-------
+ 0
+(1 row)
+
+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);
+ pg_advisory_xact_lock | pg_advisory_xact_lock_shared | pg_advisory_xact_lock | pg_advisory_xact_lock_shared
+-----------------------+------------------------------+-----------------------+------------------------------
+ | | |
+(1 row)
+
+SELECT locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ ORDER BY classid, objid, objsubid;
+ locktype | classid | objid | objsubid | mode | granted
+----------+---------+-------+----------+---------------+---------
+ advisory | 0 | 1 | 1 | ExclusiveLock | t
+ advisory | 0 | 2 | 1 | ShareLock | t
+ advisory | 1 | 1 | 2 | ExclusiveLock | t
+ advisory | 2 | 2 | 2 | ShareLock | t
+(4 rows)
+
+SELECT
+ pg_advisory_lock(1), pg_advisory_lock_shared(2),
+ pg_advisory_lock(1, 1), pg_advisory_lock_shared(2, 2);
+ pg_advisory_lock | pg_advisory_lock_shared | pg_advisory_lock | pg_advisory_lock_shared
+------------------+-------------------------+------------------+-------------------------
+ | | |
+(1 row)
+
+ROLLBACK;
+SELECT locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ ORDER BY classid, objid, objsubid;
+ locktype | classid | objid | objsubid | mode | granted
+----------+---------+-------+----------+---------------+---------
+ advisory | 0 | 1 | 1 | ExclusiveLock | t
+ advisory | 0 | 2 | 1 | ShareLock | t
+ advisory | 1 | 1 | 2 | ExclusiveLock | t
+ advisory | 2 | 2 | 2 | ShareLock | t
+(4 rows)
+
+-- 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);
+WARNING: you don't own a lock of type ExclusiveLock
+WARNING: you don't own a lock of type ShareLock
+WARNING: you don't own a lock of type ExclusiveLock
+WARNING: you don't own a lock of type ShareLock
+ pg_advisory_unlock | pg_advisory_unlock | pg_advisory_unlock_shared | pg_advisory_unlock_shared | pg_advisory_unlock | pg_advisory_unlock | pg_advisory_unlock_shared | pg_advisory_unlock_shared
+--------------------+--------------------+---------------------------+---------------------------+--------------------+--------------------+---------------------------+---------------------------
+ t | f | t | f | t | f | t | f
+(1 row)
+
+SELECT count(*) FROM pg_locks WHERE locktype = 'advisory';
+ count
+-------
+ 0
+(1 row)
+
+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);
+ pg_advisory_lock | pg_advisory_lock_shared | pg_advisory_lock | pg_advisory_lock_shared
+------------------+-------------------------+------------------+-------------------------
+ | | |
+(1 row)
+
+SELECT locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ ORDER BY classid, objid, objsubid;
+ locktype | classid | objid | objsubid | mode | granted
+----------+---------+-------+----------+---------------+---------
+ advisory | 0 | 1 | 1 | ExclusiveLock | t
+ advisory | 0 | 2 | 1 | ShareLock | t
+ advisory | 1 | 1 | 2 | ExclusiveLock | t
+ advisory | 2 | 2 | 2 | ShareLock | t
+(4 rows)
+
+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);
+ pg_advisory_xact_lock | pg_advisory_xact_lock_shared | pg_advisory_xact_lock | pg_advisory_xact_lock_shared
+-----------------------+------------------------------+-----------------------+------------------------------
+ | | |
+(1 row)
+
+ROLLBACK;
+SELECT locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ ORDER BY classid, objid, objsubid;
+ locktype | classid | objid | objsubid | mode | granted
+----------+---------+-------+----------+---------------+---------
+ advisory | 0 | 1 | 1 | ExclusiveLock | t
+ advisory | 0 | 2 | 1 | ShareLock | t
+ advisory | 1 | 1 | 2 | ExclusiveLock | t
+ advisory | 2 | 2 | 2 | ShareLock | t
+(4 rows)
+
+-- releasing all session locks
+SELECT pg_advisory_unlock_all();
+ pg_advisory_unlock_all
+------------------------
+
+(1 row)
+
+SELECT count(*) FROM pg_locks WHERE locktype = 'advisory';
+ count
+-------
+ 0
+(1 row)
+
+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);
+ pg_advisory_xact_lock | pg_advisory_xact_lock | pg_advisory_xact_lock_shared | pg_advisory_xact_lock_shared | pg_advisory_xact_lock | pg_advisory_xact_lock | pg_advisory_xact_lock_shared | pg_advisory_xact_lock_shared
+-----------------------+-----------------------+------------------------------+------------------------------+-----------------------+-----------------------+------------------------------+------------------------------
+ | | | | | | |
+(1 row)
+
+SELECT locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ ORDER BY classid, objid, objsubid;
+ locktype | classid | objid | objsubid | mode | granted
+----------+---------+-------+----------+---------------+---------
+ advisory | 0 | 1 | 1 | ExclusiveLock | t
+ advisory | 0 | 2 | 1 | ShareLock | t
+ advisory | 1 | 1 | 2 | ExclusiveLock | t
+ advisory | 2 | 2 | 2 | ShareLock | t
+(4 rows)
+
+COMMIT;
+SELECT count(*) FROM pg_locks WHERE locktype = 'advisory';
+ count
+-------
+ 0
+(1 row)
+
+-- 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);
+ pg_advisory_lock | pg_advisory_lock | pg_advisory_lock_shared | pg_advisory_lock_shared | pg_advisory_lock | pg_advisory_lock | pg_advisory_lock_shared | pg_advisory_lock_shared
+------------------+------------------+-------------------------+-------------------------+------------------+------------------+-------------------------+-------------------------
+ | | | | | | |
+(1 row)
+
+SELECT locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ ORDER BY classid, objid, objsubid;
+ locktype | classid | objid | objsubid | mode | granted
+----------+---------+-------+----------+---------------+---------
+ advisory | 0 | 1 | 1 | ExclusiveLock | t
+ advisory | 0 | 2 | 1 | ShareLock | t
+ advisory | 1 | 1 | 2 | ExclusiveLock | t
+ advisory | 2 | 2 | 2 | ShareLock | t
+(4 rows)
+
+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);
+ pg_advisory_unlock | pg_advisory_unlock | pg_advisory_unlock_shared | pg_advisory_unlock_shared | pg_advisory_unlock | pg_advisory_unlock | pg_advisory_unlock_shared | pg_advisory_unlock_shared
+--------------------+--------------------+---------------------------+---------------------------+--------------------+--------------------+---------------------------+---------------------------
+ t | t | t | t | t | t | t | t
+(1 row)
+
+SELECT count(*) FROM pg_locks WHERE locktype = 'advisory';
+ count
+-------
+ 0
+(1 row)
+
+-- .. 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);
+ pg_advisory_lock | pg_advisory_lock | pg_advisory_lock_shared | pg_advisory_lock_shared | pg_advisory_lock | pg_advisory_lock | pg_advisory_lock_shared | pg_advisory_lock_shared
+------------------+------------------+-------------------------+-------------------------+------------------+------------------+-------------------------+-------------------------
+ | | | | | | |
+(1 row)
+
+SELECT locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ ORDER BY classid, objid, objsubid;
+ locktype | classid | objid | objsubid | mode | granted
+----------+---------+-------+----------+---------------+---------
+ advisory | 0 | 1 | 1 | ExclusiveLock | t
+ advisory | 0 | 2 | 1 | ShareLock | t
+ advisory | 1 | 1 | 2 | ExclusiveLock | t
+ advisory | 2 | 2 | 2 | ShareLock | t
+(4 rows)
+
+SELECT pg_advisory_unlock_all();
+ pg_advisory_unlock_all
+------------------------
+
+(1 row)
+
+SELECT count(*) FROM pg_locks WHERE locktype = 'advisory';
+ count
+-------
+ 0
+(1 row)
+
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
new file mode 100644
index 0000000..5949996
--- /dev/null
+++ b/src/test/regress/expected/aggregates.out
@@ -0,0 +1,2815 @@
+--
+-- AGGREGATES
+--
+-- avoid bit-exact output here because operations may not be bit-exact.
+SET extra_float_digits = 0;
+SELECT avg(four) AS avg_1 FROM onek;
+ avg_1
+--------------------
+ 1.5000000000000000
+(1 row)
+
+SELECT avg(a) AS avg_32 FROM aggtest WHERE a < 100;
+ avg_32
+---------------------
+ 32.6666666666666667
+(1 row)
+
+-- 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;
+ avg_107_943
+-------------
+ 107.943
+(1 row)
+
+SELECT avg(gpa) AS avg_3_4 FROM ONLY student;
+ avg_3_4
+---------
+ 3.4
+(1 row)
+
+SELECT sum(four) AS sum_1500 FROM onek;
+ sum_1500
+----------
+ 1500
+(1 row)
+
+SELECT sum(a) AS sum_198 FROM aggtest;
+ sum_198
+---------
+ 198
+(1 row)
+
+SELECT sum(b) AS avg_431_773 FROM aggtest;
+ avg_431_773
+-------------
+ 431.773
+(1 row)
+
+SELECT sum(gpa) AS avg_6_8 FROM ONLY student;
+ avg_6_8
+---------
+ 6.8
+(1 row)
+
+SELECT max(four) AS max_3 FROM onek;
+ max_3
+-------
+ 3
+(1 row)
+
+SELECT max(a) AS max_100 FROM aggtest;
+ max_100
+---------
+ 100
+(1 row)
+
+SELECT max(aggtest.b) AS max_324_78 FROM aggtest;
+ max_324_78
+------------
+ 324.78
+(1 row)
+
+SELECT max(student.gpa) AS max_3_7 FROM student;
+ max_3_7
+---------
+ 3.7
+(1 row)
+
+SELECT stddev_pop(b) FROM aggtest;
+ stddev_pop
+-----------------
+ 131.10703231895
+(1 row)
+
+SELECT stddev_samp(b) FROM aggtest;
+ stddev_samp
+------------------
+ 151.389360803998
+(1 row)
+
+SELECT var_pop(b) FROM aggtest;
+ var_pop
+------------------
+ 17189.0539234823
+(1 row)
+
+SELECT var_samp(b) FROM aggtest;
+ var_samp
+------------------
+ 22918.7385646431
+(1 row)
+
+SELECT stddev_pop(b::numeric) FROM aggtest;
+ stddev_pop
+------------------
+ 131.107032862199
+(1 row)
+
+SELECT stddev_samp(b::numeric) FROM aggtest;
+ stddev_samp
+------------------
+ 151.389361431288
+(1 row)
+
+SELECT var_pop(b::numeric) FROM aggtest;
+ var_pop
+--------------------
+ 17189.054065929769
+(1 row)
+
+SELECT var_samp(b::numeric) FROM aggtest;
+ var_samp
+--------------------
+ 22918.738754573025
+(1 row)
+
+-- population variance is defined for a single tuple, sample variance
+-- is not
+SELECT var_pop(1.0::float8), var_samp(2.0::float8);
+ var_pop | var_samp
+---------+----------
+ 0 |
+(1 row)
+
+SELECT stddev_pop(3.0::float8), stddev_samp(4.0::float8);
+ stddev_pop | stddev_samp
+------------+-------------
+ 0 |
+(1 row)
+
+SELECT var_pop('inf'::float8), var_samp('inf'::float8);
+ var_pop | var_samp
+---------+----------
+ NaN |
+(1 row)
+
+SELECT stddev_pop('inf'::float8), stddev_samp('inf'::float8);
+ stddev_pop | stddev_samp
+------------+-------------
+ NaN |
+(1 row)
+
+SELECT var_pop('nan'::float8), var_samp('nan'::float8);
+ var_pop | var_samp
+---------+----------
+ NaN |
+(1 row)
+
+SELECT stddev_pop('nan'::float8), stddev_samp('nan'::float8);
+ stddev_pop | stddev_samp
+------------+-------------
+ NaN |
+(1 row)
+
+SELECT var_pop(1.0::float4), var_samp(2.0::float4);
+ var_pop | var_samp
+---------+----------
+ 0 |
+(1 row)
+
+SELECT stddev_pop(3.0::float4), stddev_samp(4.0::float4);
+ stddev_pop | stddev_samp
+------------+-------------
+ 0 |
+(1 row)
+
+SELECT var_pop('inf'::float4), var_samp('inf'::float4);
+ var_pop | var_samp
+---------+----------
+ NaN |
+(1 row)
+
+SELECT stddev_pop('inf'::float4), stddev_samp('inf'::float4);
+ stddev_pop | stddev_samp
+------------+-------------
+ NaN |
+(1 row)
+
+SELECT var_pop('nan'::float4), var_samp('nan'::float4);
+ var_pop | var_samp
+---------+----------
+ NaN |
+(1 row)
+
+SELECT stddev_pop('nan'::float4), stddev_samp('nan'::float4);
+ stddev_pop | stddev_samp
+------------+-------------
+ NaN |
+(1 row)
+
+SELECT var_pop(1.0::numeric), var_samp(2.0::numeric);
+ var_pop | var_samp
+---------+----------
+ 0 |
+(1 row)
+
+SELECT stddev_pop(3.0::numeric), stddev_samp(4.0::numeric);
+ stddev_pop | stddev_samp
+------------+-------------
+ 0 |
+(1 row)
+
+SELECT var_pop('inf'::numeric), var_samp('inf'::numeric);
+ var_pop | var_samp
+---------+----------
+ NaN |
+(1 row)
+
+SELECT stddev_pop('inf'::numeric), stddev_samp('inf'::numeric);
+ stddev_pop | stddev_samp
+------------+-------------
+ NaN |
+(1 row)
+
+SELECT var_pop('nan'::numeric), var_samp('nan'::numeric);
+ var_pop | var_samp
+---------+----------
+ NaN |
+(1 row)
+
+SELECT stddev_pop('nan'::numeric), stddev_samp('nan'::numeric);
+ stddev_pop | stddev_samp
+------------+-------------
+ NaN |
+(1 row)
+
+-- verify correct results for null and NaN inputs
+select sum(null::int4) from generate_series(1,3);
+ sum
+-----
+
+(1 row)
+
+select sum(null::int8) from generate_series(1,3);
+ sum
+-----
+
+(1 row)
+
+select sum(null::numeric) from generate_series(1,3);
+ sum
+-----
+
+(1 row)
+
+select sum(null::float8) from generate_series(1,3);
+ sum
+-----
+
+(1 row)
+
+select avg(null::int4) from generate_series(1,3);
+ avg
+-----
+
+(1 row)
+
+select avg(null::int8) from generate_series(1,3);
+ avg
+-----
+
+(1 row)
+
+select avg(null::numeric) from generate_series(1,3);
+ avg
+-----
+
+(1 row)
+
+select avg(null::float8) from generate_series(1,3);
+ avg
+-----
+
+(1 row)
+
+select sum('NaN'::numeric) from generate_series(1,3);
+ sum
+-----
+ NaN
+(1 row)
+
+select avg('NaN'::numeric) from generate_series(1,3);
+ avg
+-----
+ NaN
+(1 row)
+
+-- verify correct results for infinite inputs
+SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
+FROM (VALUES ('1'), ('infinity')) v(x);
+ sum | avg | var_pop
+----------+----------+---------
+ Infinity | Infinity | NaN
+(1 row)
+
+SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
+FROM (VALUES ('infinity'), ('1')) v(x);
+ sum | avg | var_pop
+----------+----------+---------
+ Infinity | Infinity | NaN
+(1 row)
+
+SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
+FROM (VALUES ('infinity'), ('infinity')) v(x);
+ sum | avg | var_pop
+----------+----------+---------
+ Infinity | Infinity | NaN
+(1 row)
+
+SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
+FROM (VALUES ('-infinity'), ('infinity')) v(x);
+ sum | avg | var_pop
+-----+-----+---------
+ NaN | NaN | NaN
+(1 row)
+
+SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
+FROM (VALUES ('-infinity'), ('-infinity')) v(x);
+ sum | avg | var_pop
+-----------+-----------+---------
+ -Infinity | -Infinity | NaN
+(1 row)
+
+SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
+FROM (VALUES ('1'), ('infinity')) v(x);
+ sum | avg | var_pop
+----------+----------+---------
+ Infinity | Infinity | NaN
+(1 row)
+
+SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
+FROM (VALUES ('infinity'), ('1')) v(x);
+ sum | avg | var_pop
+----------+----------+---------
+ Infinity | Infinity | NaN
+(1 row)
+
+SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
+FROM (VALUES ('infinity'), ('infinity')) v(x);
+ sum | avg | var_pop
+----------+----------+---------
+ Infinity | Infinity | NaN
+(1 row)
+
+SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
+FROM (VALUES ('-infinity'), ('infinity')) v(x);
+ sum | avg | var_pop
+-----+-----+---------
+ NaN | NaN | NaN
+(1 row)
+
+SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
+FROM (VALUES ('-infinity'), ('-infinity')) v(x);
+ sum | avg | var_pop
+-----------+-----------+---------
+ -Infinity | -Infinity | NaN
+(1 row)
+
+-- test accuracy with a large input offset
+SELECT avg(x::float8), var_pop(x::float8)
+FROM (VALUES (100000003), (100000004), (100000006), (100000007)) v(x);
+ avg | var_pop
+-----------+---------
+ 100000005 | 2.5
+(1 row)
+
+SELECT avg(x::float8), var_pop(x::float8)
+FROM (VALUES (7000000000005), (7000000000007)) v(x);
+ avg | var_pop
+---------------+---------
+ 7000000000006 | 1
+(1 row)
+
+-- SQL2003 binary aggregates
+SELECT regr_count(b, a) FROM aggtest;
+ regr_count
+------------
+ 4
+(1 row)
+
+SELECT regr_sxx(b, a) FROM aggtest;
+ regr_sxx
+----------
+ 5099
+(1 row)
+
+SELECT regr_syy(b, a) FROM aggtest;
+ regr_syy
+------------------
+ 68756.2156939293
+(1 row)
+
+SELECT regr_sxy(b, a) FROM aggtest;
+ regr_sxy
+------------------
+ 2614.51582155004
+(1 row)
+
+SELECT regr_avgx(b, a), regr_avgy(b, a) FROM aggtest;
+ regr_avgx | regr_avgy
+-----------+------------------
+ 49.5 | 107.943152273074
+(1 row)
+
+SELECT regr_r2(b, a) FROM aggtest;
+ regr_r2
+--------------------
+ 0.0194977982031803
+(1 row)
+
+SELECT regr_slope(b, a), regr_intercept(b, a) FROM aggtest;
+ regr_slope | regr_intercept
+-------------------+------------------
+ 0.512750700441271 | 82.5619926012309
+(1 row)
+
+SELECT covar_pop(b, a), covar_samp(b, a) FROM aggtest;
+ covar_pop | covar_samp
+-----------------+------------------
+ 653.62895538751 | 871.505273850014
+(1 row)
+
+SELECT corr(b, a) FROM aggtest;
+ corr
+-------------------
+ 0.139634516517873
+(1 row)
+
+-- check single-tuple behavior
+SELECT covar_pop(1::float8,2::float8), covar_samp(3::float8,4::float8);
+ covar_pop | covar_samp
+-----------+------------
+ 0 |
+(1 row)
+
+SELECT covar_pop(1::float8,'inf'::float8), covar_samp(3::float8,'inf'::float8);
+ covar_pop | covar_samp
+-----------+------------
+ NaN |
+(1 row)
+
+SELECT covar_pop(1::float8,'nan'::float8), covar_samp(3::float8,'nan'::float8);
+ covar_pop | covar_samp
+-----------+------------
+ NaN |
+(1 row)
+
+-- 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);
+ count | sum | regr_sxx | sum | regr_syy | regr_sxy
+-------+-----+----------+------+----------+----------
+ 4 | 140 | 2900 | 1290 | 83075 | 15050
+(1 row)
+
+SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x)
+FROM regr_test;
+ count | sum | regr_sxx | sum | regr_syy | regr_sxy
+-------+-----+----------+------+----------+----------
+ 5 | 240 | 6280 | 1490 | 95080 | 8680
+(1 row)
+
+SELECT float8_accum('{4,140,2900}'::float8[], 100);
+ float8_accum
+--------------
+ {5,240,6280}
+(1 row)
+
+SELECT float8_regr_accum('{4,140,2900,1290,83075,15050}'::float8[], 200, 100);
+ float8_regr_accum
+------------------------------
+ {5,240,6280,1490,95080,8680}
+(1 row)
+
+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);
+ count | sum | regr_sxx | sum | regr_syy | regr_sxy
+-------+-----+----------+-----+----------+----------
+ 3 | 60 | 200 | 750 | 20000 | 2000
+(1 row)
+
+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);
+ count | sum | regr_sxx | sum | regr_syy | regr_sxy
+-------+-----+----------+-----+----------+----------
+ 2 | 180 | 200 | 740 | 57800 | -3400
+(1 row)
+
+SELECT float8_combine('{3,60,200}'::float8[], '{0,0,0}'::float8[]);
+ float8_combine
+----------------
+ {3,60,200}
+(1 row)
+
+SELECT float8_combine('{0,0,0}'::float8[], '{2,180,200}'::float8[]);
+ float8_combine
+----------------
+ {2,180,200}
+(1 row)
+
+SELECT float8_combine('{3,60,200}'::float8[], '{2,180,200}'::float8[]);
+ float8_combine
+----------------
+ {5,240,6280}
+(1 row)
+
+SELECT float8_regr_combine('{3,60,200,750,20000,2000}'::float8[],
+ '{0,0,0,0,0,0}'::float8[]);
+ float8_regr_combine
+---------------------------
+ {3,60,200,750,20000,2000}
+(1 row)
+
+SELECT float8_regr_combine('{0,0,0,0,0,0}'::float8[],
+ '{2,180,200,740,57800,-3400}'::float8[]);
+ float8_regr_combine
+-----------------------------
+ {2,180,200,740,57800,-3400}
+(1 row)
+
+SELECT float8_regr_combine('{3,60,200,750,20000,2000}'::float8[],
+ '{2,180,200,740,57800,-3400}'::float8[]);
+ float8_regr_combine
+------------------------------
+ {5,240,6280,1490,95080,8680}
+(1 row)
+
+DROP TABLE regr_test;
+-- test count, distinct
+SELECT count(four) AS cnt_1000 FROM onek;
+ cnt_1000
+----------
+ 1000
+(1 row)
+
+SELECT count(DISTINCT four) AS cnt_4 FROM onek;
+ cnt_4
+-------
+ 4
+(1 row)
+
+select ten, count(*), sum(four) from onek
+group by ten order by ten;
+ ten | count | sum
+-----+-------+-----
+ 0 | 100 | 100
+ 1 | 100 | 200
+ 2 | 100 | 100
+ 3 | 100 | 200
+ 4 | 100 | 100
+ 5 | 100 | 200
+ 6 | 100 | 100
+ 7 | 100 | 200
+ 8 | 100 | 100
+ 9 | 100 | 200
+(10 rows)
+
+select ten, count(four), sum(DISTINCT four) from onek
+group by ten order by ten;
+ ten | count | sum
+-----+-------+-----
+ 0 | 100 | 2
+ 1 | 100 | 4
+ 2 | 100 | 2
+ 3 | 100 | 4
+ 4 | 100 | 2
+ 5 | 100 | 4
+ 6 | 100 | 2
+ 7 | 100 | 4
+ 8 | 100 | 2
+ 9 | 100 | 4
+(10 rows)
+
+-- user-defined aggregates
+SELECT newavg(four) AS avg_1 FROM onek;
+ avg_1
+--------------------
+ 1.5000000000000000
+(1 row)
+
+SELECT newsum(four) AS sum_1500 FROM onek;
+ sum_1500
+----------
+ 1500
+(1 row)
+
+SELECT newcnt(four) AS cnt_1000 FROM onek;
+ cnt_1000
+----------
+ 1000
+(1 row)
+
+SELECT newcnt(*) AS cnt_1000 FROM onek;
+ cnt_1000
+----------
+ 1000
+(1 row)
+
+SELECT oldcnt(*) AS cnt_1000 FROM onek;
+ cnt_1000
+----------
+ 1000
+(1 row)
+
+SELECT sum2(q1,q2) FROM int8_tbl;
+ sum2
+-------------------
+ 18271560493827981
+(1 row)
+
+-- 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);
+ ten | sum
+-----+-----
+ 0 | 2
+ 2 | 2
+ 4 | 2
+ 6 | 2
+ 8 | 2
+(5 rows)
+
+-- 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);
+ERROR: aggregate functions are not allowed in WHERE
+LINE 4: 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;
+ max
+------
+ 9999
+(1 row)
+
+-- 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;
+ QUERY PLAN
+------------------------------------------------------------------
+ Sort
+ Output: s1.s1, s2.s2, (sum((s1.s1 + s2.s2)))
+ Sort Key: s1.s1, s2.s2
+ -> Nested Loop
+ Output: s1.s1, s2.s2, (sum((s1.s1 + s2.s2)))
+ -> Function Scan on pg_catalog.generate_series s1
+ Output: s1.s1
+ Function Call: generate_series(1, 3)
+ -> HashAggregate
+ Output: s2.s2, sum((s1.s1 + s2.s2))
+ Group Key: s2.s2
+ -> Function Scan on pg_catalog.generate_series s2
+ Output: s2.s2
+ Function Call: generate_series(1, 3)
+(14 rows)
+
+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;
+ s1 | s2 | sm
+----+----+----
+ 1 | 1 | 2
+ 1 | 2 | 3
+ 1 | 3 | 4
+ 2 | 1 | 3
+ 2 | 2 | 4
+ 2 | 3 | 5
+ 3 | 1 | 4
+ 3 | 2 | 5
+ 3 | 3 | 6
+(9 rows)
+
+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;
+ QUERY PLAN
+-------------------------------------------------------------------
+ Function Scan on pg_catalog.generate_series x
+ Output: (SubPlan 1)
+ Function Call: generate_series(1, 3)
+ SubPlan 1
+ -> Sort
+ Output: (sum((x.x + y.y))), y.y
+ Sort Key: (sum((x.x + y.y)))
+ -> HashAggregate
+ Output: sum((x.x + y.y)), y.y
+ Group Key: y.y
+ -> Function Scan on pg_catalog.generate_series y
+ Output: y.y
+ Function Call: generate_series(1, 3)
+(13 rows)
+
+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;
+ array
+---------
+ {2,3,4}
+ {3,4,5}
+ {4,5,6}
+(3 rows)
+
+--
+-- 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;
+ ? | ? | ?
+---+---+---
+ | |
+(1 row)
+
+COPY bitwise_test FROM STDIN NULL 'null';
+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;
+ 1 | 1 | 1 | ? | 0 | 0100 | 7 | 7 | 7 | ? | 7 | 1101 | 5 | 5 | 5 | ? | 7 | 1101
+---+---+---+---+---+------+---+---+---+---+---+------+---+---+---+---+---+------
+ 1 | 1 | 1 | 1 | 0 | 0100 | 7 | 7 | 7 | 3 | 7 | 1101 | 5 | 5 | 5 | 2 | 7 | 1101
+(1 row)
+
+--
+-- 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";
+ t | t | t | t | t | t | t | t | t
+---+---+---+---+---+---+---+---+---
+ t | t | t | t | t | t | t | t | t
+(1 row)
+
+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";
+ t | t | t | t | t | t | t | t | t
+---+---+---+---+---+---+---+---+---
+ t | t | t | t | t | t | t | t | t
+(1 row)
+
+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;
+ n | n
+---+---
+ |
+(1 row)
+
+COPY bool_test FROM STDIN NULL '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;
+ f | t | f | n | f | t
+---+---+---+---+---+---
+ f | t | f | | f | t
+(1 row)
+
+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;
+ f | t | f | n | f | t
+---+---+---+---+---+---
+ f | t | f | | f | t
+(1 row)
+
+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;
+ t | t | f | n | f | t
+---+---+---+---+---+---
+ t | t | f | | f | t
+(1 row)
+
+--
+-- Test cases that should be optimized into indexscans instead of
+-- the generic aggregate implementation.
+--
+-- Basic cases
+explain (costs off)
+ select min(unique1) from tenk1;
+ QUERY PLAN
+------------------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 IS NOT NULL)
+(5 rows)
+
+select min(unique1) from tenk1;
+ min
+-----
+ 0
+(1 row)
+
+explain (costs off)
+ select max(unique1) from tenk1;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan Backward using tenk1_unique1 on tenk1
+ Index Cond: (unique1 IS NOT NULL)
+(5 rows)
+
+select max(unique1) from tenk1;
+ max
+------
+ 9999
+(1 row)
+
+explain (costs off)
+ select max(unique1) from tenk1 where unique1 < 42;
+ QUERY PLAN
+------------------------------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan Backward using tenk1_unique1 on tenk1
+ Index Cond: ((unique1 IS NOT NULL) AND (unique1 < 42))
+(5 rows)
+
+select max(unique1) from tenk1 where unique1 < 42;
+ max
+-----
+ 41
+(1 row)
+
+explain (costs off)
+ select max(unique1) from tenk1 where unique1 > 42;
+ QUERY PLAN
+------------------------------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan Backward using tenk1_unique1 on tenk1
+ Index Cond: ((unique1 IS NOT NULL) AND (unique1 > 42))
+(5 rows)
+
+select max(unique1) from tenk1 where unique1 > 42;
+ max
+------
+ 9999
+(1 row)
+
+-- 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;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan Backward using tenk1_unique1 on tenk1
+ Index Cond: ((unique1 IS NOT NULL) AND (unique1 > 42000))
+(5 rows)
+
+select max(unique1) from tenk1 where unique1 > 42000;
+ max
+-----
+
+(1 row)
+
+rollback;
+-- multi-column index (uses tenk1_thous_tenthous)
+explain (costs off)
+ select max(tenthous) from tenk1 where thousand = 33;
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan Backward using tenk1_thous_tenthous on tenk1
+ Index Cond: ((thousand = 33) AND (tenthous IS NOT NULL))
+(5 rows)
+
+select max(tenthous) from tenk1 where thousand = 33;
+ max
+------
+ 9033
+(1 row)
+
+explain (costs off)
+ select min(tenthous) from tenk1 where thousand = 33;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan using tenk1_thous_tenthous on tenk1
+ Index Cond: ((thousand = 33) AND (tenthous IS NOT NULL))
+(5 rows)
+
+select min(tenthous) from tenk1 where thousand = 33;
+ min
+-----
+ 33
+(1 row)
+
+-- 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;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Seq Scan on int4_tbl
+ SubPlan 2
+ -> Result
+ InitPlan 1 (returns $1)
+ -> Limit
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: ((unique1 IS NOT NULL) AND (unique1 > int4_tbl.f1))
+(7 rows)
+
+select f1, (select min(unique1) from tenk1 where unique1 > f1) AS gt
+ from int4_tbl;
+ f1 | gt
+-------------+----
+ 0 | 1
+ 123456 |
+ -123456 | 0
+ 2147483647 |
+ -2147483647 | 0
+(5 rows)
+
+-- check some cases that were handled incorrectly in 8.3.0
+explain (costs off)
+ select distinct max(unique2) from tenk1;
+ QUERY PLAN
+---------------------------------------------------------------------
+ HashAggregate
+ Group Key: $0
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan Backward using tenk1_unique2 on tenk1
+ Index Cond: (unique2 IS NOT NULL)
+ -> Result
+(7 rows)
+
+select distinct max(unique2) from tenk1;
+ max
+------
+ 9999
+(1 row)
+
+explain (costs off)
+ select max(unique2) from tenk1 order by 1;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Sort
+ Sort Key: ($0)
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan Backward using tenk1_unique2 on tenk1
+ Index Cond: (unique2 IS NOT NULL)
+ -> Result
+(7 rows)
+
+select max(unique2) from tenk1 order by 1;
+ max
+------
+ 9999
+(1 row)
+
+explain (costs off)
+ select max(unique2) from tenk1 order by max(unique2);
+ QUERY PLAN
+---------------------------------------------------------------------
+ Sort
+ Sort Key: ($0)
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan Backward using tenk1_unique2 on tenk1
+ Index Cond: (unique2 IS NOT NULL)
+ -> Result
+(7 rows)
+
+select max(unique2) from tenk1 order by max(unique2);
+ max
+------
+ 9999
+(1 row)
+
+explain (costs off)
+ select max(unique2) from tenk1 order by max(unique2)+1;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Sort
+ Sort Key: (($0 + 1))
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan Backward using tenk1_unique2 on tenk1
+ Index Cond: (unique2 IS NOT NULL)
+ -> Result
+(7 rows)
+
+select max(unique2) from tenk1 order by max(unique2)+1;
+ max
+------
+ 9999
+(1 row)
+
+explain (costs off)
+ select max(unique2), generate_series(1,3) as g from tenk1 order by g desc;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Sort
+ Sort Key: (generate_series(1, 3)) DESC
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan Backward using tenk1_unique2 on tenk1
+ Index Cond: (unique2 IS NOT NULL)
+ -> ProjectSet
+ -> Result
+(8 rows)
+
+select max(unique2), generate_series(1,3) as g from tenk1 order by g desc;
+ max | g
+------+---
+ 9999 | 3
+ 9999 | 2
+ 9999 | 1
+(3 rows)
+
+-- interesting corner case: constant gets optimized into a seqscan
+explain (costs off)
+ select max(100) from tenk1;
+ QUERY PLAN
+----------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Result
+ One-Time Filter: (100 IS NOT NULL)
+ -> Seq Scan on tenk1
+(6 rows)
+
+select max(100) from tenk1;
+ max
+-----
+ 100
+(1 row)
+
+-- 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;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Merge Append
+ Sort Key: minmaxtest.f1
+ -> Index Only Scan using minmaxtesti on minmaxtest minmaxtest_1
+ Index Cond: (f1 IS NOT NULL)
+ -> Index Only Scan using minmaxtest1i on minmaxtest1 minmaxtest_2
+ Index Cond: (f1 IS NOT NULL)
+ -> Index Only Scan Backward using minmaxtest2i on minmaxtest2 minmaxtest_3
+ Index Cond: (f1 IS NOT NULL)
+ -> Index Only Scan using minmaxtest3i on minmaxtest3 minmaxtest_4
+ InitPlan 2 (returns $1)
+ -> Limit
+ -> Merge Append
+ Sort Key: minmaxtest_5.f1 DESC
+ -> Index Only Scan Backward using minmaxtesti on minmaxtest minmaxtest_6
+ Index Cond: (f1 IS NOT NULL)
+ -> Index Only Scan Backward using minmaxtest1i on minmaxtest1 minmaxtest_7
+ Index Cond: (f1 IS NOT NULL)
+ -> Index Only Scan using minmaxtest2i on minmaxtest2 minmaxtest_8
+ Index Cond: (f1 IS NOT NULL)
+ -> Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest_9
+(23 rows)
+
+select min(f1), max(f1) from minmaxtest;
+ min | max
+-----+-----
+ 11 | 18
+(1 row)
+
+-- DISTINCT doesn't do anything useful here, but it shouldn't fail
+explain (costs off)
+ select distinct min(f1), max(f1) from minmaxtest;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------
+ Unique
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Merge Append
+ Sort Key: minmaxtest.f1
+ -> Index Only Scan using minmaxtesti on minmaxtest minmaxtest_1
+ Index Cond: (f1 IS NOT NULL)
+ -> Index Only Scan using minmaxtest1i on minmaxtest1 minmaxtest_2
+ Index Cond: (f1 IS NOT NULL)
+ -> Index Only Scan Backward using minmaxtest2i on minmaxtest2 minmaxtest_3
+ Index Cond: (f1 IS NOT NULL)
+ -> Index Only Scan using minmaxtest3i on minmaxtest3 minmaxtest_4
+ InitPlan 2 (returns $1)
+ -> Limit
+ -> Merge Append
+ Sort Key: minmaxtest_5.f1 DESC
+ -> Index Only Scan Backward using minmaxtesti on minmaxtest minmaxtest_6
+ Index Cond: (f1 IS NOT NULL)
+ -> Index Only Scan Backward using minmaxtest1i on minmaxtest1 minmaxtest_7
+ Index Cond: (f1 IS NOT NULL)
+ -> Index Only Scan using minmaxtest2i on minmaxtest2 minmaxtest_8
+ Index Cond: (f1 IS NOT NULL)
+ -> Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest_9
+ -> Sort
+ Sort Key: ($0), ($1)
+ -> Result
+(26 rows)
+
+select distinct min(f1), max(f1) from minmaxtest;
+ min | max
+-----+-----
+ 11 | 18
+(1 row)
+
+drop table minmaxtest cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table minmaxtest1
+drop cascades to table minmaxtest2
+drop cascades to table minmaxtest3
+-- check for correct detection of nested-aggregate errors
+select max(min(unique1)) from tenk1;
+ERROR: aggregate function calls cannot be nested
+LINE 1: select max(min(unique1)) from tenk1;
+ ^
+select (select max(min(unique1)) from int8_tbl) from tenk1;
+ERROR: aggregate function calls cannot be nested
+LINE 1: 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;
+ QUERY PLAN
+----------------------
+ HashAggregate
+ Group Key: a, b
+ -> Seq Scan on t1
+(3 rows)
+
+-- 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;
+ QUERY PLAN
+----------------------
+ HashAggregate
+ Group Key: a, c, d
+ -> Seq Scan on t1
+(3 rows)
+
+-- Test removal across multiple relations
+explain (costs off) select *
+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;
+ QUERY PLAN
+------------------------------------------------------
+ HashAggregate
+ Group Key: t1.a, t1.b, t2.x, t2.y
+ -> Hash Join
+ Hash Cond: ((t2.x = t1.a) AND (t2.y = t1.b))
+ -> Seq Scan on t2
+ -> Hash
+ -> Seq Scan on t1
+(7 rows)
+
+-- Test case where t1 can be optimized but not t2
+explain (costs off) select t1.*,t2.x,t2.z
+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;
+ QUERY PLAN
+------------------------------------------------------
+ HashAggregate
+ Group Key: t1.a, t1.b, t2.x, t2.z
+ -> Hash Join
+ Hash Cond: ((t2.x = t1.a) AND (t2.y = t1.b))
+ -> Seq Scan on t2
+ -> Hash
+ -> Seq Scan on t1
+(7 rows)
+
+-- Cannot optimize when PK is deferrable
+explain (costs off) select * from t3 group by a,b,c;
+ QUERY PLAN
+----------------------
+ HashAggregate
+ Group Key: a, b, c
+ -> Seq Scan on t3
+(3 rows)
+
+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;
+ QUERY PLAN
+-------------------------------------
+ HashAggregate
+ Group Key: t1.a, t1.b, t1.c, t1.d
+ -> Append
+ -> Seq Scan on t1 t1_1
+ -> Seq Scan on t1c t1_2
+(5 rows)
+
+-- Okay to remove columns if we're only querying the parent.
+explain (costs off) select * from only t1 group by a,b,c,d;
+ QUERY PLAN
+----------------------
+ HashAggregate
+ Group Key: a, b
+ -> Seq Scan on t1
+(3 rows)
+
+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;
+ QUERY PLAN
+--------------------------------
+ HashAggregate
+ Group Key: p_t1.a, p_t1.b
+ -> Append
+ -> Seq Scan on p_t1_1
+ -> Seq Scan on p_t1_2
+(5 rows)
+
+drop table t1 cascade;
+NOTICE: drop cascades to table t1c
+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;
+ f1
+----
+(0 rows)
+
+select f1 from t1 left join t2 using (f1) group by t1.f1;
+ f1
+----
+(0 rows)
+
+select t1.f1 from t1 left join t2 using (f1) group by t1.f1;
+ f1
+----
+(0 rows)
+
+-- only this one should fail:
+select t1.f1 from t1 left join t2 using (f1) group by f1;
+ERROR: column "t1.f1" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: 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);
+ array_agg
+-----------
+ {3,4,2,1}
+(1 row)
+
+select array_agg(a order by a)
+ from (values (1,4),(2,3),(3,1),(4,2)) v(a,b);
+ array_agg
+-----------
+ {1,2,3,4}
+(1 row)
+
+select array_agg(a order by a desc)
+ from (values (1,4),(2,3),(3,1),(4,2)) v(a,b);
+ array_agg
+-----------
+ {4,3,2,1}
+(1 row)
+
+select array_agg(b order by a desc)
+ from (values (1,4),(2,3),(3,1),(4,2)) v(a,b);
+ array_agg
+-----------
+ {2,1,3,4}
+(1 row)
+
+select array_agg(distinct a)
+ from (values (1),(2),(1),(3),(null),(2)) v(a);
+ array_agg
+--------------
+ {1,2,3,NULL}
+(1 row)
+
+select array_agg(distinct a order by a)
+ from (values (1),(2),(1),(3),(null),(2)) v(a);
+ array_agg
+--------------
+ {1,2,3,NULL}
+(1 row)
+
+select array_agg(distinct a order by a desc)
+ from (values (1),(2),(1),(3),(null),(2)) v(a);
+ array_agg
+--------------
+ {NULL,3,2,1}
+(1 row)
+
+select array_agg(distinct a order by a desc nulls last)
+ from (values (1),(2),(1),(3),(null),(2)) v(a);
+ array_agg
+--------------
+ {3,2,1,NULL}
+(1 row)
+
+-- 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);
+ aggfstr
+---------------------------------------
+ {"(1,3,foo)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+select aggfns(a,b,c)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c);
+ aggfns
+-----------------------------------------------
+ {"(1,3,foo)","(0,,)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+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;
+ aggfstr
+---------------------------------------
+ {"(1,3,foo)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+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;
+ aggfns
+-----------------------------------------------
+ {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+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;
+ aggfstr
+---------------------------------------
+ {"(3,1,baz)","(2,2,bar)","(1,3,foo)"}
+(1 row)
+
+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;
+ aggfns
+-----------------------------------------------
+ {"(3,1,baz)","(2,2,bar)","(1,3,foo)","(0,,)"}
+(1 row)
+
+-- 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;
+ aggfns
+------------------------------------------------
+ {"(2,2,bar)","(3,3,baz)","(1,1,foo)","(0,0,)"}
+(1 row)
+
+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;
+ aggfns
+------------------------------------------------
+ {"(2,2,bar)","(3,3,baz)","(1,1,foo)","(0,0,)"}
+(1 row)
+
+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;
+ aggfns
+------------------------------------------------
+ {"(0,0,)","(1,1,foo)","(2,2,bar)","(3,3,baz)"}
+(1 row)
+
+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;
+ aggfns
+-----------------------------------------------
+ {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+-- 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;
+ aggfns
+-----------------------------------------------
+ {"(1,3,foo)","(0,,)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+ pg_get_viewdef
+---------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(v.a, v.b, v.c) AS aggfns +
+ FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
+(1 row)
+
+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;
+ aggfns
+-----------------------------------------------
+ {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+ pg_get_viewdef
+---------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(DISTINCT v.a, v.b, v.c) AS aggfns +
+ FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c),+
+ generate_series(1, 3) i(i);
+(1 row)
+
+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;
+ aggfns
+-----------------------------------------------
+ {"(3,1,baz)","(2,2,bar)","(1,3,foo)","(0,,)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+ pg_get_viewdef
+---------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(DISTINCT v.a, v.b, v.c ORDER BY v.b) AS aggfns +
+ FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c),+
+ generate_series(1, 3) i(i);
+(1 row)
+
+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;
+ aggfns
+-----------------------------------------------
+ {"(3,1,baz)","(2,2,bar)","(1,3,foo)","(0,,)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+ pg_get_viewdef
+---------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns +
+ FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
+(1 row)
+
+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;
+ aggfns
+------------------------------------------------
+ {"(3,3,baz)","(2,2,bar)","(1,1,foo)","(0,0,)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+ pg_get_viewdef
+---------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns +
+ FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
+(1 row)
+
+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;
+ aggfns
+-----------------------------------------------
+ {"(2,2,bar)","(3,1,baz)","(1,3,foo)","(0,,)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+ pg_get_viewdef
+---------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns +
+ FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
+(1 row)
+
+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;
+ aggfns
+-----------------------------------------------
+ {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+select pg_get_viewdef('agg_view1'::regclass);
+ pg_get_viewdef
+---------------------------------------------------------------------------------------------------------------------
+ SELECT aggfns(DISTINCT v.a, v.b, v.c ORDER BY v.a, v.c USING ~<~ NULLS LAST, v.b) AS aggfns +
+ FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c),+
+ generate_series(1, 2) i(i);
+(1 row)
+
+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;
+ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list
+LINE 1: select aggfns(distinct a,b,c order by 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;
+ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list
+LINE 1: select aggfns(distinct a,b,c order by a,b+1)
+ ^
+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;
+ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list
+LINE 1: select aggfns(distinct a,b,c order by a,b,i,c)
+ ^
+select aggfns(distinct a,a,c order by a,b)
+ from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i;
+ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list
+LINE 1: select aggfns(distinct a,a,c order by a,b)
+ ^
+-- string_agg tests
+select string_agg(a,',') from (values('aaaa'),('bbbb'),('cccc')) g(a);
+ string_agg
+----------------
+ aaaa,bbbb,cccc
+(1 row)
+
+select string_agg(a,',') from (values('aaaa'),(null),('bbbb'),('cccc')) g(a);
+ string_agg
+----------------
+ aaaa,bbbb,cccc
+(1 row)
+
+select string_agg(a,'AB') from (values(null),(null),('bbbb'),('cccc')) g(a);
+ string_agg
+------------
+ bbbbABcccc
+(1 row)
+
+select string_agg(a,',') from (values(null),(null)) g(a);
+ string_agg
+------------
+
+(1 row)
+
+-- check some implicit casting cases, as per bug #5564
+select string_agg(distinct f1, ',' order by f1) from varchar_tbl; -- ok
+ string_agg
+------------
+ a,ab,abcd
+(1 row)
+
+select string_agg(distinct f1::text, ',' order by f1) from varchar_tbl; -- not ok
+ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list
+LINE 1: select string_agg(distinct f1::text, ',' order by f1) from v...
+ ^
+select string_agg(distinct f1, ',' order by f1::text) from varchar_tbl; -- not ok
+ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list
+LINE 1: select string_agg(distinct f1, ',' order by f1::text) from v...
+ ^
+select string_agg(distinct f1::text, ',' order by f1::text) from varchar_tbl; -- ok
+ string_agg
+------------
+ a,ab,abcd
+(1 row)
+
+-- string_agg bytea tests
+create table bytea_test_table(v bytea);
+select string_agg(v, '') from bytea_test_table;
+ string_agg
+------------
+
+(1 row)
+
+insert into bytea_test_table values(decode('ff','hex'));
+select string_agg(v, '') from bytea_test_table;
+ string_agg
+------------
+ \xff
+(1 row)
+
+insert into bytea_test_table values(decode('aa','hex'));
+select string_agg(v, '') from bytea_test_table;
+ string_agg
+------------
+ \xffaa
+(1 row)
+
+select string_agg(v, NULL) from bytea_test_table;
+ string_agg
+------------
+ \xffaa
+(1 row)
+
+select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
+ string_agg
+------------
+ \xffeeaa
+(1 row)
+
+drop table bytea_test_table;
+-- FILTER tests
+select min(unique1) filter (where unique1 > 100) from tenk1;
+ min
+-----
+ 101
+(1 row)
+
+select sum(1/ten) filter (where ten > 0) from tenk1;
+ sum
+------
+ 1000
+(1 row)
+
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by ten;
+ ten | sum
+-----+-----
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+ 5 |
+ 6 |
+ 7 |
+ 8 |
+ 9 |
+(10 rows)
+
+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);
+ ten | sum
+-----+-----
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+(5 rows)
+
+select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0')
+from (values ('a', 'b')) AS v(foo,bar);
+ max
+-----
+ a
+(1 row)
+
+-- 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
+ count
+-------
+ 1
+ 1
+(2 rows)
+
+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
+ count
+-------
+ 2
+(1 row)
+
+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
+ count
+-------
+ 1
+ 1
+(2 rows)
+
+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
+ max
+------
+ 9998
+(1 row)
+
+-- subquery in FILTER clause (PostgreSQL extension)
+select sum(unique1) FILTER (WHERE
+ unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1;
+ sum
+------
+ 4950
+(1 row)
+
+-- 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;
+ aggfns
+---------------------------
+ {"(2,2,bar)","(3,1,baz)"}
+(1 row)
+
+-- check handling of bare boolean Var in FILTER
+select max(0) filter (where b1) from bool_test;
+ max
+-----
+ 0
+(1 row)
+
+select (select max(0) filter (where b1)) from bool_test;
+ max
+-----
+ 0
+(1 row)
+
+-- check for correct detection of nested-aggregate errors in FILTER
+select max(unique1) filter (where sum(ten) > 0) from tenk1;
+ERROR: aggregate functions are not allowed in FILTER
+LINE 1: select max(unique1) filter (where sum(ten) > 0) from tenk1;
+ ^
+select (select max(unique1) filter (where sum(ten) > 0) from int8_tbl) from tenk1;
+ERROR: aggregate function calls cannot be nested
+LINE 1: select (select max(unique1) filter (where sum(ten) > 0) from...
+ ^
+select max(unique1) filter (where bool_or(ten > 0)) from tenk1;
+ERROR: aggregate functions are not allowed in FILTER
+LINE 1: select max(unique1) filter (where bool_or(ten > 0)) from ten...
+ ^
+select (select max(unique1) filter (where bool_or(ten > 0)) from int8_tbl) from tenk1;
+ERROR: aggregate function calls cannot be nested
+LINE 1: select (select max(unique1) filter (where bool_or(ten > 0)) ...
+ ^
+-- 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;
+ p | percentile_cont
+------+-----------------
+ 0 | 1
+ 0.1 | 1.4
+ 0.25 | 2
+ 0.4 | 2.6
+ 0.5 | 3
+ 0.6 | 3.4
+ 0.75 | 4
+ 0.9 | 4.6
+ 1 | 5
+(9 rows)
+
+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;
+ERROR: cannot use multiple ORDER BY clauses with WITHIN GROUP
+LINE 1: select p, percentile_cont(p order by p) within group (order ...
+ ^
+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;
+ERROR: sum is not an ordered-set aggregate, so it cannot have WITHIN GROUP
+LINE 1: select p, sum() within group (order by x::float8)
+ ^
+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;
+ERROR: WITHIN GROUP is required for ordered-set aggregate percentile_cont
+LINE 1: select p, percentile_cont(p,p)
+ ^
+select percentile_cont(0.5) within group (order by b) from aggtest;
+ percentile_cont
+------------------
+ 53.4485001564026
+(1 row)
+
+select percentile_cont(0.5) within group (order by b), sum(b) from aggtest;
+ percentile_cont | sum
+------------------+---------
+ 53.4485001564026 | 431.773
+(1 row)
+
+select percentile_cont(0.5) within group (order by thousand) from tenk1;
+ percentile_cont
+-----------------
+ 499.5
+(1 row)
+
+select percentile_disc(0.5) within group (order by thousand) from tenk1;
+ percentile_disc
+-----------------
+ 499
+(1 row)
+
+select rank(3) within group (order by x)
+from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ rank
+------
+ 5
+(1 row)
+
+select cume_dist(3) within group (order by x)
+from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ cume_dist
+-----------
+ 0.875
+(1 row)
+
+select percent_rank(3) within group (order by x)
+from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x);
+ percent_rank
+--------------
+ 0.5
+(1 row)
+
+select dense_rank(3) within group (order by x)
+from (values (1),(1),(2),(2),(3),(3),(4)) v(x);
+ dense_rank
+------------
+ 3
+(1 row)
+
+select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand)
+from tenk1;
+ percentile_disc
+----------------------------
+ {0,99,249,499,749,899,999}
+(1 row)
+
+select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand)
+from tenk1;
+ percentile_cont
+-----------------------------
+ {0,249.75,499.5,749.25,999}
+(1 row)
+
+select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand)
+from tenk1;
+ percentile_disc
+---------------------------------
+ {{NULL,999,499},{749,249,NULL}}
+(1 row)
+
+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;
+ percentile_cont
+------------------------------------------
+ {1,6,2.25,4.75,3.5,6,2.5,2.6,2.75,2.9,3}
+(1 row)
+
+select ten, mode() within group (order by string4) from tenk1 group by ten;
+ ten | mode
+-----+--------
+ 0 | HHHHxx
+ 1 | OOOOxx
+ 2 | VVVVxx
+ 3 | OOOOxx
+ 4 | HHHHxx
+ 5 | HHHHxx
+ 6 | OOOOxx
+ 7 | AAAAxx
+ 8 | VVVVxx
+ 9 | VVVVxx
+(10 rows)
+
+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);
+ percentile_disc
+-----------------
+ {fred,jill,jim}
+(1 row)
+
+-- 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);
+ pg_collation_for
+------------------
+ "POSIX"
+(1 row)
+
+-- 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);
+ test_rank
+-----------
+ 5
+(1 row)
+
+select test_percentile_disc(0.5) within group (order by thousand) from tenk1;
+ test_percentile_disc
+----------------------
+ 499
+(1 row)
+
+-- 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;
+ERROR: column "x.x" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: select rank(x) within group (order by x) from generate_serie...
+ ^
+DETAIL: Direct arguments of an ordered-set aggregate must use only grouped columns.
+-- 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);
+ERROR: outer-level aggregate cannot contain a lower-level variable in its direct arguments
+LINE 1: select array(select percentile_disc(a) within group (order b...
+ ^
+-- 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;
+ERROR: aggregate function calls cannot be nested
+LINE 1: select rank(sum(x)) within group (order by x) from generate_...
+ ^
+-- hypothetical-set type unification and argument-count failures:
+select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x);
+ERROR: WITHIN GROUP types text and integer cannot be matched
+LINE 1: select rank(3) within group (order by x) from (values ('fred...
+ ^
+select rank(3) within group (order by stringu1,stringu2) from tenk1;
+ERROR: function rank(integer, name, name) does not exist
+LINE 1: select rank(3) within group (order by stringu1,stringu2) fro...
+ ^
+HINT: To use the hypothetical-set aggregate rank, the number of hypothetical direct arguments (here 1) must match the number of ordering columns (here 2).
+select rank('fred') within group (order by x) from generate_series(1,5) x;
+ERROR: invalid input syntax for type integer: "fred"
+LINE 1: select rank('fred') within group (order by x) from generate_...
+ ^
+select rank('adam'::text collate "C") within group (order by x collate "POSIX")
+ from (values ('fred'),('jim')) v(x);
+ERROR: collation mismatch between explicit collations "C" and "POSIX"
+LINE 1: ...adam'::text collate "C") within group (order by x collate "P...
+ ^
+-- hypothetical-set type unification successes:
+select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x);
+ rank
+------
+ 1
+(1 row)
+
+select rank('3') within group (order by x) from generate_series(1,5) x;
+ rank
+------
+ 3
+(1 row)
+
+-- divide by zero check
+select percent_rank(0) within group (order by x) from generate_series(1,0) x;
+ percent_rank
+--------------
+ 0
+(1 row)
+
+-- 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');
+ pg_get_viewdef
+-------------------------------------------------------------------------------------------------------------------------------
+ SELECT tenk1.ten, +
+ percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50, +
+ percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
+ rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank +
+ FROM tenk1 +
+ GROUP BY tenk1.ten +
+ ORDER BY tenk1.ten;
+(1 row)
+
+select * from aggordview1 order by ten;
+ ten | p50 | px | rank
+-----+-----+-----+------
+ 0 | 490 | | 101
+ 1 | 491 | 401 | 101
+ 2 | 492 | | 101
+ 3 | 493 | | 101
+ 4 | 494 | | 101
+ 5 | 495 | | 67
+ 6 | 496 | | 1
+ 7 | 497 | | 1
+ 8 | 498 | | 1
+ 9 | 499 | | 1
+(10 rows)
+
+drop view aggordview1;
+-- variadic aggregates
+select least_agg(q1,q2) from int8_tbl;
+ least_agg
+-------------------
+ -4567890123456789
+(1 row)
+
+select least_agg(variadic array[q1,q2]) from int8_tbl;
+ least_agg
+-------------------
+ -4567890123456789
+(1 row)
+
+select cleast_agg(q1,q2) from int8_tbl;
+ cleast_agg
+-------------------
+ -4567890123456789
+(1 row)
+
+select cleast_agg(4.5,f1) from int4_tbl;
+ cleast_agg
+-------------
+ -2147483647
+(1 row)
+
+select cleast_agg(variadic array[4.5,f1]) from int4_tbl;
+ cleast_agg
+-------------
+ -2147483647
+(1 row)
+
+select pg_typeof(cleast_agg(variadic array[4.5,f1])) from int4_tbl;
+ pg_typeof
+-----------
+ numeric
+(1 row)
+
+-- 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);
+NOTICE: avg_transfn called with 1
+NOTICE: avg_transfn called with 3
+ my_avg | my_avg
+--------+--------
+ 2 | 2
+(1 row)
+
+-- 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);
+NOTICE: avg_transfn called with 1
+NOTICE: avg_transfn called with 3
+ my_avg | my_sum
+--------+--------
+ 2 | 4
+(1 row)
+
+-- 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);
+NOTICE: avg_transfn called with 1
+NOTICE: avg_transfn called with 3
+ my_avg | my_sum
+--------+--------
+ 2 | 4
+(1 row)
+
+-- shouldn't share states due to the distinctness not matching.
+select my_avg(distinct one),my_sum(one) from (values(1),(3)) t(one);
+NOTICE: avg_transfn called with 1
+NOTICE: avg_transfn called with 3
+NOTICE: avg_transfn called with 1
+NOTICE: avg_transfn called with 3
+ my_avg | my_sum
+--------+--------
+ 2 | 4
+(1 row)
+
+-- 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);
+NOTICE: avg_transfn called with 1
+NOTICE: avg_transfn called with 3
+NOTICE: avg_transfn called with 3
+ my_avg | my_sum
+--------+--------
+ 3 | 4
+(1 row)
+
+-- 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);
+NOTICE: avg_transfn called with 1
+NOTICE: avg_transfn called with 2
+NOTICE: avg_transfn called with 3
+NOTICE: avg_transfn called with 4
+ my_avg | my_sum
+--------+--------
+ 2 | 6
+(1 row)
+
+-- 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);
+ percentile_cont | percentile_disc
+-----------------+-----------------
+ 4 | 3
+(1 row)
+
+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);
+ percentile_cont | percentile_disc
+-----------------+-----------------
+ 2.5 | 3
+(1 row)
+
+-- 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);
+ rank | dense_rank
+------+------------
+ 3 | 3
+(1 row)
+
+-- 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);
+NOTICE: avg_transfn called with 1
+NOTICE: avg_transfn called with 3
+ my_sum_init | my_avg_init
+-------------+-------------
+ 14 | 7
+(1 row)
+
+-- 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);
+NOTICE: avg_transfn called with 1
+NOTICE: avg_transfn called with 1
+NOTICE: avg_transfn called with 3
+NOTICE: avg_transfn called with 3
+ my_sum_init | my_avg_init2
+-------------+--------------
+ 14 | 4
+(1 row)
+
+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 aggregate my_sum(int4)
+(
+ 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);
+NOTICE: sum_transfn called with 1
+NOTICE: sum_transfn called with 2
+NOTICE: sum_transfn called with 3
+NOTICE: sum_transfn called with 4
+ my_sum | my_half_sum
+--------+-------------
+ 10 | 5
+(1 row)
+
+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;
+ balk
+------
+
+(1 row)
+
+ROLLBACK;
+-- 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;
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Index Only Scan using tenk1_hundred on tenk1
+(5 rows)
+
+SELECT balk(hundred) FROM tenk1;
+ balk
+------
+
+(1 row)
+
+ROLLBACK;
+-- 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;
+-- 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;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Finalize Aggregate
+ Output: variance(tenk1.unique1), sum((tenk1.unique1)::bigint), regr_count((tenk1.unique1)::double precision, (tenk1.unique1)::double precision)
+ -> Gather
+ Output: (PARTIAL variance(tenk1.unique1)), (PARTIAL sum((tenk1.unique1)::bigint)), (PARTIAL regr_count((tenk1.unique1)::double precision, (tenk1.unique1)::double precision))
+ Workers Planned: 4
+ -> Partial Aggregate
+ Output: PARTIAL variance(tenk1.unique1), PARTIAL sum((tenk1.unique1)::bigint), PARTIAL regr_count((tenk1.unique1)::double precision, (tenk1.unique1)::double precision)
+ -> Parallel Append
+ -> Parallel Seq Scan on public.tenk1
+ Output: tenk1.unique1
+ -> Parallel Seq Scan on public.tenk1 tenk1_1
+ Output: tenk1_1.unique1
+ -> Parallel Seq Scan on public.tenk1 tenk1_2
+ Output: tenk1_2.unique1
+ -> Parallel Seq Scan on public.tenk1 tenk1_3
+ Output: tenk1_3.unique1
+(16 rows)
+
+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;
+ variance | sum | regr_count
+----------------------+-----------+------------
+ 8333541.588539713493 | 199980000 | 40000
+(1 row)
+
+-- 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;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------
+ Finalize Aggregate
+ Output: variance((tenk1.unique1)::bigint), avg((tenk1.unique1)::numeric)
+ -> Gather
+ Output: (PARTIAL variance((tenk1.unique1)::bigint)), (PARTIAL avg((tenk1.unique1)::numeric))
+ Workers Planned: 4
+ -> Partial Aggregate
+ Output: PARTIAL variance((tenk1.unique1)::bigint), PARTIAL avg((tenk1.unique1)::numeric)
+ -> Parallel Append
+ -> Parallel Seq Scan on public.tenk1
+ Output: tenk1.unique1
+ -> Parallel Seq Scan on public.tenk1 tenk1_1
+ Output: tenk1_1.unique1
+ -> Parallel Seq Scan on public.tenk1 tenk1_2
+ Output: tenk1_2.unique1
+ -> Parallel Seq Scan on public.tenk1 tenk1_3
+ Output: tenk1_3.unique1
+(16 rows)
+
+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;
+ variance | avg
+----------------------+-----------------------
+ 8333541.588539713493 | 4999.5000000000000000
+(1 row)
+
+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;
+ dense_rank
+------------
+ 1
+ 1
+ 1
+(3 rows)
+
+-- 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);
+ min
+-----
+ 1
+(1 row)
+
+SELECT min(x ORDER BY y) FROM (VALUES(1, 2)) AS d(x,y);
+ min
+-----
+ 1
+(1 row)
+
+-- 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;
+ ?column? | case | count
+----------+------+-------
+ aa | 1 | 1
+ ba | 0 | 1
+(2 rows)
+
+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;
+ ?column? | case | count
+----------+------+-------
+ aa | 1 | 1
+ ba | 0 | 1
+(2 rows)
+
+-- 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);
+ QUERY PLAN
+-------------------------------------------------------------
+ Hash Join
+ Hash Cond: (tenk1.hundred = onek.twothousand)
+ -> Seq Scan on tenk1
+ Filter: (hundred = thousand)
+ -> Hash
+ -> HashAggregate
+ Group Key: onek.twothousand, onek.twothousand
+ -> Seq Scan on onek
+(8 rows)
+
+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);
+ unique1 | count | sum
+---------+-------+------
+ 4976 | 1 | 976
+ 4977 | 1 | 977
+ 4978 | 1 | 978
+ 4979 | 1 | 979
+ 4980 | 1 | 980
+ 4981 | 1 | 981
+ 4982 | 1 | 982
+ 4983 | 1 | 983
+ 4984 | 1 | 984
+ 4985 | 1 | 985
+ 4986 | 1 | 986
+ 4987 | 1 | 987
+ 4988 | 1 | 988
+ 4989 | 1 | 989
+ 4990 | 1 | 990
+ 4991 | 1 | 991
+ 4992 | 1 | 992
+ 4993 | 1 | 993
+ 4994 | 1 | 994
+ 4995 | 1 | 995
+ 4996 | 1 | 996
+ 4997 | 1 | 997
+ 4998 | 1 | 998
+ 4999 | 1 | 999
+ 9976 | 1 | 1976
+ 9977 | 1 | 1977
+ 9978 | 1 | 1978
+ 9979 | 1 | 1979
+ 9980 | 1 | 1980
+ 9981 | 1 | 1981
+ 9982 | 1 | 1982
+ 9983 | 1 | 1983
+ 9984 | 1 | 1984
+ 9985 | 1 | 1985
+ 9986 | 1 | 1986
+ 9987 | 1 | 1987
+ 9988 | 1 | 1988
+ 9989 | 1 | 1989
+ 9990 | 1 | 1990
+ 9991 | 1 | 1991
+ 9992 | 1 | 1992
+ 9993 | 1 | 1993
+ 9994 | 1 | 1994
+ 9995 | 1 | 1995
+ 9996 | 1 | 1996
+ 9997 | 1 | 1997
+ 9998 | 1 | 1998
+ 9999 | 1 | 1999
+(48 rows)
+
+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;
+ QUERY PLAN
+--------------------------------------
+ GroupAggregate
+ Group Key: ((g % 10000))
+ -> Sort
+ Sort Key: ((g % 10000))
+ -> Seq Scan on agg_data_20k
+(5 rows)
+
+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;
+ QUERY PLAN
+--------------------------------
+ HashAggregate
+ Group Key: (g % 10000)
+ -> Seq Scan on agg_data_20k
+(3 rows)
+
+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);
+ c1 | c2 | c3
+----+----+----
+(0 rows)
+
+(select * from agg_hash_2 except select * from agg_group_2)
+ union all
+(select * from agg_group_2 except select * from agg_hash_2);
+ a | c1 | c2 | c3
+---+----+----+----
+(0 rows)
+
+(select * from agg_hash_3 except select * from agg_group_3)
+ union all
+(select * from agg_group_3 except select * from agg_hash_3);
+ c1 | c2 | c3
+----+----+----
+(0 rows)
+
+(select * from agg_hash_4 except select * from agg_group_4)
+ union all
+(select * from agg_group_4 except select * from agg_hash_4);
+ c1 | c2 | c3
+----+----+----
+(0 rows)
+
+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/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
new file mode 100644
index 0000000..505eb7e
--- /dev/null
+++ b/src/test/regress/expected/alter_generic.out
@@ -0,0 +1,747 @@
+--
+-- Test for ALTER some_object {RENAME TO, OWNER TO, SET SCHEMA}
+--
+-- 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)
+ERROR: function alt_func1(integer) is not an aggregate
+ALTER AGGREGATE alt_func1(int) OWNER TO regress_alter_generic_user3; -- failed (not aggregate)
+ERROR: function alt_func1(integer) is not an aggregate
+ALTER AGGREGATE alt_func1(int) SET SCHEMA alt_nsp2; -- failed (not aggregate)
+ERROR: function alt_func1(integer) is not an aggregate
+ALTER FUNCTION alt_func1(int) RENAME TO alt_func2; -- failed (name conflict)
+ERROR: function alt_func2(integer) already exists in schema "alt_nsp1"
+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)
+ERROR: must be member of role "regress_alter_generic_user2"
+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)
+ERROR: function alt_agg2(integer) already exists in schema "alt_nsp1"
+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)
+ERROR: must be member of role "regress_alter_generic_user2"
+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)
+ERROR: must be owner of function alt_func3
+ALTER FUNCTION alt_func1(int) RENAME TO alt_func4; -- OK
+ALTER FUNCTION alt_func3(int) OWNER TO regress_alter_generic_user2; -- failed (not owner)
+ERROR: must be owner of function alt_func3
+ALTER FUNCTION alt_func2(int) OWNER TO regress_alter_generic_user3; -- failed (no role membership)
+ERROR: must be member of role "regress_alter_generic_user3"
+ALTER FUNCTION alt_func3(int) SET SCHEMA alt_nsp2; -- failed (not owner)
+ERROR: must be owner of function alt_func3
+ALTER FUNCTION alt_func2(int) SET SCHEMA alt_nsp2; -- failed (name conflicts)
+ERROR: function alt_func2(integer) already exists in schema "alt_nsp2"
+ALTER AGGREGATE alt_agg3(int) RENAME TO alt_agg4; -- failed (not owner)
+ERROR: must be owner of function alt_agg3
+ALTER AGGREGATE alt_agg1(int) RENAME TO alt_agg4; -- OK
+ALTER AGGREGATE alt_agg3(int) OWNER TO regress_alter_generic_user2; -- failed (not owner)
+ERROR: must be owner of function alt_agg3
+ALTER AGGREGATE alt_agg2(int) OWNER TO regress_alter_generic_user3; -- failed (no role membership)
+ERROR: must be member of role "regress_alter_generic_user3"
+ALTER AGGREGATE alt_agg3(int) SET SCHEMA alt_nsp2; -- failed (not owner)
+ERROR: must be owner of function alt_agg3
+ALTER AGGREGATE alt_agg2(int) SET SCHEMA alt_nsp2; -- failed (name conflict)
+ERROR: function alt_agg2(integer) already exists in schema "alt_nsp2"
+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;
+ nspname | proname | prorettype | prokind | rolname
+----------+-----------+------------+---------+-----------------------------
+ alt_nsp1 | alt_agg2 | integer | a | regress_alter_generic_user2
+ alt_nsp1 | alt_agg3 | integer | a | regress_alter_generic_user1
+ alt_nsp1 | alt_agg4 | integer | a | regress_alter_generic_user2
+ alt_nsp1 | alt_func2 | integer | f | regress_alter_generic_user2
+ alt_nsp1 | alt_func3 | integer | f | regress_alter_generic_user1
+ alt_nsp1 | alt_func4 | integer | f | regress_alter_generic_user2
+ alt_nsp2 | alt_agg2 | integer | a | regress_alter_generic_user3
+ alt_nsp2 | alt_func2 | integer | f | regress_alter_generic_user3
+(8 rows)
+
+--
+-- 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;
+CREATE CONVERSION alt_conv2 FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
+ALTER CONVERSION alt_conv1 RENAME TO alt_conv2; -- failed (name conflict)
+ERROR: conversion "alt_conv2" already exists in schema "alt_nsp1"
+ALTER CONVERSION alt_conv1 RENAME TO alt_conv3; -- OK
+ALTER CONVERSION alt_conv2 OWNER TO regress_alter_generic_user2; -- failed (no role membership)
+ERROR: must be member of role "regress_alter_generic_user2"
+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)
+ERROR: must be owner of conversion alt_conv3
+ALTER CONVERSION alt_conv1 RENAME TO alt_conv4; -- OK
+ALTER CONVERSION alt_conv3 OWNER TO regress_alter_generic_user2; -- failed (not owner)
+ERROR: must be owner of conversion alt_conv3
+ALTER CONVERSION alt_conv2 OWNER TO regress_alter_generic_user3; -- failed (no role membership)
+ERROR: must be member of role "regress_alter_generic_user3"
+ALTER CONVERSION alt_conv3 SET SCHEMA alt_nsp2; -- failed (not owner)
+ERROR: must be owner of conversion alt_conv3
+ALTER CONVERSION alt_conv2 SET SCHEMA alt_nsp2; -- failed (name conflict)
+ERROR: conversion "alt_conv2" already exists in schema "alt_nsp2"
+RESET SESSION AUTHORIZATION;
+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;
+ nspname | conname | rolname
+----------+-----------+-----------------------------
+ alt_nsp1 | alt_conv2 | regress_alter_generic_user2
+ alt_nsp1 | alt_conv3 | regress_alter_generic_user1
+ alt_nsp1 | alt_conv4 | regress_alter_generic_user2
+ alt_nsp2 | alt_conv2 | regress_alter_generic_user3
+(4 rows)
+
+--
+-- 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)
+ERROR: foreign-data wrapper "alt_fdw2" already exists
+ALTER FOREIGN DATA WRAPPER alt_fdw1 RENAME TO alt_fdw3; -- OK
+ALTER SERVER alt_fserv1 RENAME TO alt_fserv2; -- failed (name conflict)
+ERROR: server "alt_fserv2" already exists
+ALTER SERVER alt_fserv1 RENAME TO alt_fserv3; -- OK
+SELECT fdwname FROM pg_foreign_data_wrapper WHERE fdwname like 'alt_fdw%';
+ fdwname
+----------
+ alt_fdw2
+ alt_fdw3
+(2 rows)
+
+SELECT srvname FROM pg_foreign_server WHERE srvname like 'alt_fserv%';
+ srvname
+------------
+ alt_fserv2
+ alt_fserv3
+(2 rows)
+
+--
+-- 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)
+ERROR: language "alt_lang2" already exists
+ALTER LANGUAGE alt_lang2 RENAME TO alt_lang3; -- failed (not owner)
+ERROR: must be owner of language alt_lang2
+ALTER LANGUAGE alt_lang1 RENAME TO alt_lang3; -- OK
+ALTER LANGUAGE alt_lang2 OWNER TO regress_alter_generic_user3; -- failed (not owner)
+ERROR: must be owner of language alt_lang2
+ALTER LANGUAGE alt_lang3 OWNER TO regress_alter_generic_user2; -- failed (no role membership)
+ERROR: must be member of role "regress_alter_generic_user2"
+ALTER LANGUAGE alt_lang3 OWNER TO regress_alter_generic_user3; -- OK
+RESET SESSION AUTHORIZATION;
+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;
+ lanname | rolname
+-----------+-----------------------------
+ alt_lang2 | regress_alter_generic_user2
+ alt_lang3 | regress_alter_generic_user3
+(2 rows)
+
+--
+-- 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)
+ERROR: must be member of role "regress_alter_generic_user2"
+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)
+ERROR: must be owner of operator @+@
+ALTER OPERATOR @-@(int4, int4) OWNER TO regress_alter_generic_user3; -- failed (no role membership)
+ERROR: must be member of role "regress_alter_generic_user3"
+ALTER OPERATOR @+@(int4, int4) SET SCHEMA alt_nsp2; -- failed (not owner)
+ERROR: must be owner of operator @+@
+-- 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;
+ nspname | oprname | rolname | oprleft | oprright | oprcode
+----------+---------+-----------------------------+---------+----------+---------
+ alt_nsp1 | @+@ | regress_alter_generic_user3 | integer | integer | int4pl
+ alt_nsp1 | @-@ | regress_alter_generic_user2 | integer | integer | int4mi
+ alt_nsp2 | @-@ | regress_alter_generic_user1 | integer | integer | int4mi
+(3 rows)
+
+--
+-- 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)
+ERROR: operator family "alt_opf2" for access method "hash" already exists in schema "alt_nsp1"
+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)
+ERROR: must be member of role "regress_alter_generic_user2"
+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)
+ERROR: operator class "alt_opc2" for access method "hash" already exists in schema "alt_nsp1"
+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)
+ERROR: must be member of role "regress_alter_generic_user2"
+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)
+ERROR: must be owner of operator family alt_opf3
+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)
+ERROR: must be owner of operator family alt_opf3
+ALTER OPERATOR FAMILY alt_opf2 USING hash OWNER TO regress_alter_generic_user3; -- failed (no role membership)
+ERROR: must be member of role "regress_alter_generic_user3"
+ALTER OPERATOR FAMILY alt_opf3 USING hash SET SCHEMA alt_nsp2; -- failed (not owner)
+ERROR: must be owner of operator family alt_opf3
+ALTER OPERATOR FAMILY alt_opf2 USING hash SET SCHEMA alt_nsp2; -- failed (name conflict)
+ERROR: operator family "alt_opf2" for access method "hash" already exists in schema "alt_nsp2"
+ALTER OPERATOR CLASS alt_opc3 USING hash RENAME TO alt_opc4; -- failed (not owner)
+ERROR: must be owner of operator class alt_opc3
+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)
+ERROR: must be owner of operator class alt_opc3
+ALTER OPERATOR CLASS alt_opc2 USING hash OWNER TO regress_alter_generic_user3; -- failed (no role membership)
+ERROR: must be member of role "regress_alter_generic_user3"
+ALTER OPERATOR CLASS alt_opc3 USING hash SET SCHEMA alt_nsp2; -- failed (not owner)
+ERROR: must be owner of operator class alt_opc3
+ALTER OPERATOR CLASS alt_opc2 USING hash SET SCHEMA alt_nsp2; -- failed (name conflict)
+ERROR: operator class "alt_opc2" for access method "hash" already exists in schema "alt_nsp2"
+RESET SESSION AUTHORIZATION;
+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;
+ nspname | opfname | amname | rolname
+----------+----------+--------+-----------------------------
+ alt_nsp1 | alt_opf2 | hash | regress_alter_generic_user2
+ alt_nsp1 | alt_opf3 | hash | regress_alter_generic_user1
+ alt_nsp1 | alt_opf4 | hash | regress_alter_generic_user2
+ alt_nsp2 | alt_opf2 | hash | regress_alter_generic_user3
+(4 rows)
+
+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;
+ nspname | opcname | amname | rolname
+----------+----------+--------+-----------------------------
+ alt_nsp1 | alt_opc2 | hash | regress_alter_generic_user2
+ alt_nsp1 | alt_opc3 | hash | regress_alter_generic_user1
+ alt_nsp1 | alt_opc4 | hash | regress_alter_generic_user2
+ alt_nsp2 | alt_opc2 | hash | regress_alter_generic_user3
+(4 rows)
+
+-- 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
+ERROR: access method "invalid_index_method" does not exist
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
+ERROR: invalid operator number 6, must 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
+ERROR: invalid operator number 0, must be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
+ERROR: operator argument types must be specified in ALTER OPERATOR FAMILY
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- invalid options parsing function
+ERROR: invalid function number 0, must be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5
+ERROR: invalid function number 6, must 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
+ERROR: STORAGE cannot be specified in 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);
+ERROR: must be superuser to alter an operator family
+RESET ROLE;
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+DROP OPERATOR FAMILY alt_opf5 USING btree;
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+ROLLBACK;
+-- 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);
+ERROR: permission denied for schema alt_nsp6
+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);
+ERROR: one or two argument types must be specified
+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;
+ERROR: access method "btree" does not support ordering operators
+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);
+ERROR: btree comparison functions must return integer
+DROP OPERATOR FAMILY alt_opf12 USING btree;
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+ROLLBACK;
+-- 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);
+ERROR: hash function 1 must return integer
+DROP OPERATOR FAMILY alt_opf13 USING hash;
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+ROLLBACK;
+-- 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);
+ERROR: btree comparison functions must have two arguments
+DROP OPERATOR FAMILY alt_opf14 USING btree;
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+ROLLBACK;
+-- 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);
+ERROR: hash function 1 must have one argument
+DROP OPERATOR FAMILY alt_opf15 USING hash;
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+ROLLBACK;
+-- 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);
+ERROR: associated data types must be specified for index support function
+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
+ERROR: operator number 1 for (integer,integer) appears more than once
+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
+ERROR: operator 1(integer,integer) already exists in operator family "alt_opf17"
+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
+ERROR: function number 1 for (integer,smallint) appears more than once
+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
+ERROR: operator 1(integer,smallint) already exists in operator family "alt_opf17"
+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);
+ERROR: operator 1(integer,integer) does not exist in operator family "alt_opf18"
+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);
+ERROR: btree equal image functions must not be cross-type
+ALTER OPERATOR FAMILY alt_opf18 USING btree DROP FUNCTION 2 (int4, int4);
+ERROR: function 2(integer,integer) does not exist in operator family "alt_opf18"
+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);
+ERROR: function test_opclass_options_func(internal, text[], boolean) does not exist
+ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 (int4) btint42cmp(int4, int2);
+ERROR: invalid operator class options parsing function
+HINT: Valid signature of operator class options parsing function is (internal) RETURNS void.
+ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 (int4, int2) btint42cmp(int4, int2);
+ERROR: left and right associated data types for operator class options parsing functions must match
+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)
+ERROR: statistics object "alt_stat2" already exists in schema "alt_nsp1"
+ALTER STATISTICS alt_stat1 RENAME TO alt_stat3; -- OK
+ALTER STATISTICS alt_stat2 OWNER TO regress_alter_generic_user2; -- failed (no role membership)
+ERROR: must be member of role "regress_alter_generic_user2"
+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)
+ERROR: must be owner of statistics object alt_stat3
+ALTER STATISTICS alt_stat1 RENAME TO alt_stat4; -- OK
+ALTER STATISTICS alt_stat3 OWNER TO regress_alter_generic_user2; -- failed (not owner)
+ERROR: must be owner of statistics object alt_stat3
+ALTER STATISTICS alt_stat2 OWNER TO regress_alter_generic_user3; -- failed (no role membership)
+ERROR: must be member of role "regress_alter_generic_user3"
+ALTER STATISTICS alt_stat3 SET SCHEMA alt_nsp2; -- failed (not owner)
+ERROR: must be owner of statistics object alt_stat3
+ALTER STATISTICS alt_stat2 SET SCHEMA alt_nsp2; -- failed (name conflict)
+ERROR: statistics object "alt_stat2" already exists in schema "alt_nsp2"
+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;
+ nspname | stxname | rolname
+----------+-----------+-----------------------------
+ alt_nsp1 | alt_stat2 | regress_alter_generic_user2
+ alt_nsp1 | alt_stat3 | regress_alter_generic_user1
+ alt_nsp1 | alt_stat4 | regress_alter_generic_user2
+ alt_nsp2 | alt_stat2 | regress_alter_generic_user3
+(4 rows)
+
+--
+-- 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)
+ERROR: text search dictionary "alt_ts_dict2" already exists in schema "alt_nsp1"
+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)
+ERROR: must be member of role "regress_alter_generic_user2"
+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)
+ERROR: must be owner of text search dictionary alt_ts_dict3
+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)
+ERROR: must be owner of text search dictionary alt_ts_dict3
+ALTER TEXT SEARCH DICTIONARY alt_ts_dict2 OWNER TO regress_alter_generic_user3; -- failed (no role membership)
+ERROR: must be member of role "regress_alter_generic_user3"
+ALTER TEXT SEARCH DICTIONARY alt_ts_dict3 SET SCHEMA alt_nsp2; -- failed (not owner)
+ERROR: must be owner of text search dictionary alt_ts_dict3
+ALTER TEXT SEARCH DICTIONARY alt_ts_dict2 SET SCHEMA alt_nsp2; -- failed (name conflict)
+ERROR: text search dictionary "alt_ts_dict2" already exists in schema "alt_nsp2"
+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;
+ nspname | dictname | rolname
+----------+--------------+-----------------------------
+ alt_nsp1 | alt_ts_dict2 | regress_alter_generic_user2
+ alt_nsp1 | alt_ts_dict3 | regress_alter_generic_user1
+ alt_nsp1 | alt_ts_dict4 | regress_alter_generic_user2
+ alt_nsp2 | alt_ts_dict2 | regress_alter_generic_user3
+(4 rows)
+
+--
+-- 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)
+ERROR: text search configuration "alt_ts_conf2" already exists in schema "alt_nsp1"
+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)
+ERROR: must be member of role "regress_alter_generic_user2"
+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)
+ERROR: must be owner of text search configuration alt_ts_conf3
+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)
+ERROR: must be owner of text search configuration alt_ts_conf3
+ALTER TEXT SEARCH CONFIGURATION alt_ts_conf2 OWNER TO regress_alter_generic_user3; -- failed (no role membership)
+ERROR: must be member of role "regress_alter_generic_user3"
+ALTER TEXT SEARCH CONFIGURATION alt_ts_conf3 SET SCHEMA alt_nsp2; -- failed (not owner)
+ERROR: must be owner of text search configuration alt_ts_conf3
+ALTER TEXT SEARCH CONFIGURATION alt_ts_conf2 SET SCHEMA alt_nsp2; -- failed (name conflict)
+ERROR: text search configuration "alt_ts_conf2" already exists in schema "alt_nsp2"
+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;
+ nspname | cfgname | rolname
+----------+--------------+-----------------------------
+ alt_nsp1 | alt_ts_conf2 | regress_alter_generic_user2
+ alt_nsp1 | alt_ts_conf3 | regress_alter_generic_user1
+ alt_nsp1 | alt_ts_conf4 | regress_alter_generic_user2
+ alt_nsp2 | alt_ts_conf2 | regress_alter_generic_user3
+(4 rows)
+
+--
+-- 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)
+ERROR: text search template "alt_ts_temp2" already exists in schema "alt_nsp1"
+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)
+ERROR: text search template "alt_ts_temp2" already exists in schema "alt_nsp2"
+-- invalid: non-lowercase quoted identifiers
+CREATE TEXT SEARCH TEMPLATE tstemp_case ("Init" = init_function);
+ERROR: text search template parameter "Init" not recognized
+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;
+ nspname | tmplname
+----------+--------------
+ alt_nsp1 | alt_ts_temp2
+ alt_nsp1 | alt_ts_temp3
+ alt_nsp2 | alt_ts_temp2
+(3 rows)
+
+--
+-- 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)
+ERROR: text search parser "alt_ts_prs2" already exists in schema "alt_nsp1"
+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)
+ERROR: text search parser "alt_ts_prs2" already exists in schema "alt_nsp2"
+-- invalid: non-lowercase quoted identifiers
+CREATE TEXT SEARCH PARSER tspars_case ("Start" = start_function);
+ERROR: text search parser parameter "Start" not recognized
+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;
+ nspname | prsname
+----------+-------------
+ alt_nsp1 | alt_ts_prs2
+ alt_nsp1 | alt_ts_prs3
+ alt_nsp2 | alt_ts_prs2
+(3 rows)
+
+---
+--- Cleanup resources
+---
+DROP FOREIGN DATA WRAPPER alt_fdw2 CASCADE;
+NOTICE: drop cascades to server alt_fserv2
+DROP FOREIGN DATA WRAPPER alt_fdw3 CASCADE;
+NOTICE: drop cascades to server alt_fserv3
+DROP LANGUAGE alt_lang2 CASCADE;
+DROP LANGUAGE alt_lang3 CASCADE;
+DROP SCHEMA alt_nsp1 CASCADE;
+NOTICE: drop cascades to 28 other objects
+DETAIL: drop cascades to function alt_func3(integer)
+drop cascades to function alt_agg3(integer)
+drop cascades to function alt_func4(integer)
+drop cascades to function alt_func2(integer)
+drop cascades to function alt_agg4(integer)
+drop cascades to function alt_agg2(integer)
+drop cascades to conversion alt_conv3
+drop cascades to conversion alt_conv4
+drop cascades to conversion alt_conv2
+drop cascades to operator @+@(integer,integer)
+drop cascades to operator @-@(integer,integer)
+drop cascades to operator family alt_opf3 for access method hash
+drop cascades to operator family alt_opc1 for access method hash
+drop cascades to operator family alt_opc2 for access method hash
+drop cascades to operator family alt_opf4 for access method hash
+drop cascades to operator family alt_opf2 for access method hash
+drop cascades to table alt_regress_1
+drop cascades to table alt_regress_2
+drop cascades to text search dictionary alt_ts_dict3
+drop cascades to text search dictionary alt_ts_dict4
+drop cascades to text search dictionary alt_ts_dict2
+drop cascades to text search configuration alt_ts_conf3
+drop cascades to text search configuration alt_ts_conf4
+drop cascades to text search configuration alt_ts_conf2
+drop cascades to text search template alt_ts_temp3
+drop cascades to text search template alt_ts_temp2
+drop cascades to text search parser alt_ts_prs3
+drop cascades to text search parser alt_ts_prs2
+DROP SCHEMA alt_nsp2 CASCADE;
+NOTICE: drop cascades to 9 other objects
+DETAIL: drop cascades to function alt_nsp2.alt_func2(integer)
+drop cascades to function alt_nsp2.alt_agg2(integer)
+drop cascades to conversion alt_nsp2.alt_conv2
+drop cascades to operator alt_nsp2.@-@(integer,integer)
+drop cascades to operator family alt_nsp2.alt_opf2 for access method hash
+drop cascades to text search dictionary alt_nsp2.alt_ts_dict2
+drop cascades to text search configuration alt_nsp2.alt_ts_conf2
+drop cascades to text search template alt_nsp2.alt_ts_temp2
+drop cascades to text search parser alt_nsp2.alt_ts_prs2
+DROP USER regress_alter_generic_user1;
+DROP USER regress_alter_generic_user2;
+DROP USER regress_alter_generic_user3;
diff --git a/src/test/regress/expected/alter_operator.out b/src/test/regress/expected/alter_operator.out
new file mode 100644
index 0000000..71bd484
--- /dev/null
+++ b/src/test/regress/expected/alter_operator.out
@@ -0,0 +1,139 @@
+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
+);
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+ objid = '===(bool,bool)'::regoperator
+ORDER BY 1;
+ ref | deptype
+-------------------------------------------------------+---------
+ function alter_op_test_fn(boolean,boolean) | n
+ function customcontsel(internal,oid,internal,integer) | n
+ schema public | n
+(3 rows)
+
+--
+-- Reset and set params
+--
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NONE);
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+ oprrest | oprjoin
+---------+---------
+ - | -
+(1 row)
+
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+ objid = '===(bool,bool)'::regoperator
+ORDER BY 1;
+ ref | deptype
+--------------------------------------------+---------
+ function alter_op_test_fn(boolean,boolean) | n
+ schema public | n
+(2 rows)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = contjoinsel);
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+ oprrest | oprjoin
+---------+-------------
+ contsel | contjoinsel
+(1 row)
+
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+ objid = '===(bool,bool)'::regoperator
+ORDER BY 1;
+ ref | deptype
+--------------------------------------------+---------
+ function alter_op_test_fn(boolean,boolean) | n
+ schema public | n
+(2 rows)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE, JOIN = NONE);
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+ oprrest | oprjoin
+---------+---------
+ - | -
+(1 row)
+
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+ objid = '===(bool,bool)'::regoperator
+ORDER BY 1;
+ ref | deptype
+--------------------------------------------+---------
+ function alter_op_test_fn(boolean,boolean) | n
+ schema public | n
+(2 rows)
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = customcontsel, JOIN = contjoinsel);
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+ oprrest | oprjoin
+---------------+-------------
+ customcontsel | contjoinsel
+(1 row)
+
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+ objid = '===(bool,bool)'::regoperator
+ORDER BY 1;
+ ref | deptype
+-------------------------------------------------------+---------
+ function alter_op_test_fn(boolean,boolean) | n
+ function customcontsel(internal,oid,internal,integer) | n
+ schema public | n
+(3 rows)
+
+--
+-- Test invalid options.
+--
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+ERROR: operator attribute "commutator" cannot be changed
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+ERROR: operator attribute "negator" cannot be changed
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = non_existent_func);
+ERROR: function non_existent_func(internal, oid, internal, integer) does not exist
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = non_existent_func);
+ERROR: function non_existent_func(internal, oid, internal, smallint, internal) does not exist
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
+ERROR: operator attribute "commutator" cannot be changed
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+ERROR: operator attribute "negator" cannot be changed
+-- invalid: non-lowercase quoted identifiers
+ALTER OPERATOR & (bit, bit) SET ("Restrict" = _int_contsel, "Join" = _int_contjoinsel);
+ERROR: operator attribute "Restrict" not recognized
+--
+-- Test permission check. Must be owner to ALTER OPERATOR.
+--
+CREATE USER regress_alter_op_user;
+SET SESSION AUTHORIZATION regress_alter_op_user;
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
+ERROR: must be owner of operator ===
+-- Clean up
+RESET SESSION AUTHORIZATION;
+DROP USER regress_alter_op_user;
+DROP OPERATOR === (boolean, boolean);
+DROP FUNCTION customcontsel(internal, oid, internal, integer);
+DROP FUNCTION alter_op_test_fn(boolean, boolean);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
new file mode 100644
index 0000000..77c95d6
--- /dev/null
+++ b/src/test/regress/expected/alter_table.out
@@ -0,0 +1,4593 @@
+--
+-- 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';
+ERROR: relation "attmp_wrong" does not exist
+COMMENT ON TABLE attmp IS 'table comment';
+COMMENT ON TABLE attmp IS NULL;
+ALTER TABLE attmp ADD COLUMN xmin integer; -- fails
+ERROR: column name "xmin" conflicts with a system column name
+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;
+ initial | a | b | c | d | e | f | g | i | k | l | m | n | p | q | r | s | t | v | w | x | y | z
+---------+---+------+------+-----+-----+---+-----------------------+---+--------+-------+-----+-----------------+---+-----------+-----------------------+-----------------------------+---------------------+--------------------------+------------------+-----------+-----------+-----------
+ | 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 | t | (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) | Thu Jan 01 00:00:00 1970 | @ 1 hour 10 secs | {1,2,3,4} | {1,2,3,4} | {1,2,3,4}
+(1 row)
+
+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;
+ initial | a | b | c | d | e | f | g | i | k | l | m | n | p | q | r | s | t | v | w | x | y | z
+---------+---+------+------+-----+-----+---+-----------------------+---+--------+-------+-----+-----------------+---+-----------+-----------------------+-----------------------------+---------------------+--------------------------+------------------+-----------+-----------+-----------
+ | 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 | t | (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) | Thu Jan 01 00:00:00 1970 | @ 1 hour 10 secs | {1,2,3,4} | {1,2,3,4} | {1,2,3,4}
+(1 row)
+
+CREATE INDEX attmp_idx ON attmp (a, (d + e), b);
+ALTER INDEX attmp_idx ALTER COLUMN 0 SET STATISTICS 1000;
+ERROR: column number must be in range from 1 to 32767
+LINE 1: ALTER INDEX attmp_idx ALTER COLUMN 0 SET STATISTICS 1000;
+ ^
+ALTER INDEX attmp_idx ALTER COLUMN 1 SET STATISTICS 1000;
+ERROR: cannot alter statistics on non-expression column "a" of index "attmp_idx"
+HINT: Alter statistics on table column instead.
+ALTER INDEX attmp_idx ALTER COLUMN 2 SET STATISTICS 1000;
+\d+ attmp_idx
+ Index "public.attmp_idx"
+ Column | Type | Key? | Definition | Storage | Stats target
+--------+------------------+------+------------+---------+--------------
+ a | integer | yes | a | plain |
+ expr | double precision | yes | (d + e) | plain | 1000
+ b | cstring | yes | b | plain |
+btree, for table "public.attmp"
+
+ALTER INDEX attmp_idx ALTER COLUMN 3 SET STATISTICS 1000;
+ERROR: cannot alter statistics on non-expression column "b" of index "attmp_idx"
+HINT: Alter statistics on table column instead.
+ALTER INDEX attmp_idx ALTER COLUMN 4 SET STATISTICS 1000;
+ERROR: column number 4 of relation "attmp_idx" does not exist
+ALTER INDEX attmp_idx ALTER COLUMN 2 SET STATISTICS -1;
+DROP TABLE attmp;
+-- fails with incorrect object type
+CREATE VIEW at_v1 AS SELECT 1 as a;
+ALTER TABLE at_v1 ALTER COLUMN a SET STATISTICS 0;
+ERROR: "at_v1" is not a table, materialized view, index, partitioned index, or foreign table
+DROP VIEW at_v1;
+--
+-- 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;
+ regtable
+----------
+(0 rows)
+
+SELECT * FROM attmp_new;
+ attmptable
+------------
+(0 rows)
+
+ALTER TABLE attmp RENAME TO attmp_new2;
+SELECT * FROM attmp; -- should fail
+ERROR: relation "attmp" does not exist
+LINE 1: SELECT * FROM attmp;
+ ^
+SELECT * FROM attmp_new;
+ attmptable
+------------
+(0 rows)
+
+SELECT * FROM attmp_new2;
+ regtable
+----------
+(0 rows)
+
+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;
+ERROR: must be owner of index part_attmp_index
+ALTER INDEX part_attmp1_index RENAME TO fail;
+ERROR: must be owner of index part_attmp1_index
+ALTER TABLE part_at2tmp RENAME TO fail;
+ERROR: must be owner of table part_at2tmp
+ALTER TABLE part_at2tmp1 RENAME TO fail;
+ERROR: must be owner of table part_at2tmp1
+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;
+ typname
+--------------
+ _attmp_array
+(1 row)
+
+SELECT typname FROM pg_type WHERE oid = 'attmp_array2[]'::regtype;
+ typname
+---------------
+ _attmp_array2
+(1 row)
+
+ALTER TABLE attmp_array2 RENAME TO _attmp_array;
+SELECT typname FROM pg_type WHERE oid = 'attmp_array[]'::regtype;
+ typname
+---------------
+ __attmp_array
+(1 row)
+
+SELECT typname FROM pg_type WHERE oid = '_attmp_array[]'::regtype;
+ typname
+----------------
+ ___attmp_array
+(1 row)
+
+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;
+ typname
+--------------
+ _attmp_array
+(1 row)
+
+ALTER TABLE attmp_array RENAME TO _attmp_array;
+SELECT typname FROM pg_type WHERE oid = '_attmp_array[]'::regtype;
+ typname
+---------------
+ __attmp_array
+(1 row)
+
+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;
+NOTICE: relation "__onek_unique1" does not exist, skipping
+ALTER INDEX IF EXISTS __attmp_onek_unique1 RENAME TO onek_unique1;
+NOTICE: relation "__attmp_onek_unique1" does not exist, skipping
+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
+ERROR: must be owner of index onek_unique1
+RESET ROLE;
+-- rename statements with mismatching statement and object types
+CREATE TABLE alter_idx_rename_test (a INT);
+CREATE INDEX alter_idx_rename_test_idx ON alter_idx_rename_test (a);
+CREATE TABLE alter_idx_rename_test_parted (a INT) PARTITION BY LIST (a);
+CREATE INDEX alter_idx_rename_test_parted_idx ON alter_idx_rename_test_parted (a);
+BEGIN;
+ALTER INDEX alter_idx_rename_test RENAME TO alter_idx_rename_test_2;
+ALTER INDEX alter_idx_rename_test_parted RENAME TO alter_idx_rename_test_parted_2;
+SELECT relation::regclass, mode FROM pg_locks
+WHERE pid = pg_backend_pid() AND locktype = 'relation'
+ AND relation::regclass::text LIKE 'alter\_idx%'
+ORDER BY relation::regclass::text COLLATE "C";
+ relation | mode
+--------------------------------+---------------------
+ alter_idx_rename_test_2 | AccessExclusiveLock
+ alter_idx_rename_test_parted_2 | AccessExclusiveLock
+(2 rows)
+
+COMMIT;
+BEGIN;
+ALTER INDEX alter_idx_rename_test_idx RENAME TO alter_idx_rename_test_idx_2;
+ALTER INDEX alter_idx_rename_test_parted_idx RENAME TO alter_idx_rename_test_parted_idx_2;
+SELECT relation::regclass, mode FROM pg_locks
+WHERE pid = pg_backend_pid() AND locktype = 'relation'
+ AND relation::regclass::text LIKE 'alter\_idx%'
+ORDER BY relation::regclass::text COLLATE "C";
+ relation | mode
+------------------------------------+--------------------------
+ alter_idx_rename_test_idx_2 | ShareUpdateExclusiveLock
+ alter_idx_rename_test_parted_idx_2 | ShareUpdateExclusiveLock
+(2 rows)
+
+COMMIT;
+BEGIN;
+ALTER TABLE alter_idx_rename_test_idx_2 RENAME TO alter_idx_rename_test_idx_3;
+ALTER TABLE alter_idx_rename_test_parted_idx_2 RENAME TO alter_idx_rename_test_parted_idx_3;
+SELECT relation::regclass, mode FROM pg_locks
+WHERE pid = pg_backend_pid() AND locktype = 'relation'
+ AND relation::regclass::text LIKE 'alter\_idx%'
+ORDER BY relation::regclass::text COLLATE "C";
+ relation | mode
+------------------------------------+---------------------
+ alter_idx_rename_test_idx_3 | AccessExclusiveLock
+ alter_idx_rename_test_parted_idx_3 | AccessExclusiveLock
+(2 rows)
+
+COMMIT;
+DROP TABLE alter_idx_rename_test_2;
+-- renaming views
+CREATE VIEW attmp_view (unique1) AS SELECT unique1 FROM tenk1;
+ALTER TABLE attmp_view RENAME TO attmp_view_new;
+SET ROLE regress_alter_table_user1;
+ALTER VIEW attmp_view_new RENAME TO fail; -- permission denied
+ERROR: must be owner of view attmp_view_new
+RESET ROLE;
+-- hack to ensure we get an indexscan here
+set enable_seqscan to off;
+set enable_bitmapscan to off;
+-- 5 values, sorted
+SELECT unique1 FROM tenk1 WHERE unique1 < 5;
+ unique1
+---------
+ 0
+ 1
+ 2
+ 3
+ 4
+(5 rows)
+
+reset enable_seqscan;
+reset enable_bitmapscan;
+DROP VIEW attmp_view_new;
+-- toast-like relation name
+alter table stud_emp rename to pg_toast_stud_emp;
+alter table pg_toast_stud_emp rename to stud_emp;
+-- 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
+ERROR: cannot drop index onek_unique1_constraint because constraint onek_unique1_constraint on table onek requires it
+HINT: You can drop constraint onek_unique1_constraint on table onek instead.
+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
+ERROR: cannot drop index onek_unique1_constraint_foo because constraint onek_unique1_constraint_foo on table onek requires it
+HINT: You can drop constraint onek_unique1_constraint_foo on table onek instead.
+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
+ Table "public.constraint_rename_test"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | integer | | |
+Check constraints:
+ "con1" CHECK (a > 0)
+
+CREATE TABLE constraint_rename_test2 (a int CONSTRAINT con1 CHECK (a > 0), d int) INHERITS (constraint_rename_test);
+NOTICE: merging column "a" with inherited definition
+NOTICE: merging constraint "con1" with inherited definition
+\d constraint_rename_test2
+ Table "public.constraint_rename_test2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | integer | | |
+ d | integer | | |
+Check constraints:
+ "con1" CHECK (a > 0)
+Inherits: constraint_rename_test
+
+ALTER TABLE constraint_rename_test2 RENAME CONSTRAINT con1 TO con1foo; -- fail
+ERROR: cannot rename inherited constraint "con1"
+ALTER TABLE ONLY constraint_rename_test RENAME CONSTRAINT con1 TO con1foo; -- fail
+ERROR: inherited constraint "con1" must be renamed in child tables too
+ALTER TABLE constraint_rename_test RENAME CONSTRAINT con1 TO con1foo; -- ok
+\d constraint_rename_test
+ Table "public.constraint_rename_test"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | integer | | |
+Check constraints:
+ "con1foo" CHECK (a > 0)
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d constraint_rename_test2
+ Table "public.constraint_rename_test2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | integer | | |
+ d | integer | | |
+Check constraints:
+ "con1foo" CHECK (a > 0)
+Inherits: constraint_rename_test
+
+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
+ Table "public.constraint_rename_test"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | integer | | |
+Check constraints:
+ "con1foo" CHECK (a > 0)
+ "con2bar" CHECK (b > 0) NO INHERIT
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d constraint_rename_test2
+ Table "public.constraint_rename_test2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | integer | | |
+ d | integer | | |
+Check constraints:
+ "con1foo" CHECK (a > 0)
+Inherits: constraint_rename_test
+
+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
+ Table "public.constraint_rename_test"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+ b | integer | | |
+ c | integer | | |
+Indexes:
+ "con3foo" PRIMARY KEY, btree (a)
+Check constraints:
+ "con1foo" CHECK (a > 0)
+ "con2bar" CHECK (b > 0) NO INHERIT
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d constraint_rename_test2
+ Table "public.constraint_rename_test2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+ b | integer | | |
+ c | integer | | |
+ d | integer | | |
+Check constraints:
+ "con1foo" CHECK (a > 0)
+Inherits: constraint_rename_test
+
+DROP TABLE constraint_rename_test2;
+DROP TABLE constraint_rename_test;
+ALTER TABLE IF EXISTS constraint_not_exist RENAME CONSTRAINT con3 TO con3foo; -- ok
+NOTICE: relation "constraint_not_exist" does not exist, skipping
+ALTER TABLE IF EXISTS constraint_rename_test ADD CONSTRAINT con4 UNIQUE (a);
+NOTICE: relation "constraint_rename_test" does not exist, skipping
+-- 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
+ Table "public.like_constraint_rename_cache"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+Indexes:
+ "like_constraint_rename_cache_pkey" PRIMARY KEY, btree (a)
+Check constraints:
+ "chk_a_new" CHECK (a > 0)
+
+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;
+ERROR: column "c" referenced in foreign key constraint does not exist
+-- 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;
+ERROR: column "b" referenced in foreign key constraint does not exist
+-- Try (and fail) to add constraint due to invalid data
+ALTER TABLE attmp3 add constraint attmpconstr foreign key (a) references attmp2 match full;
+ERROR: insert or update on table "attmp3" violates foreign key constraint "attmpconstr"
+DETAIL: Key (a)=(5) is not present in table "attmp2".
+-- 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;
+ERROR: insert or update on table "attmp3" violates foreign key constraint "attmpconstr"
+DETAIL: Key (a)=(5) is not present in table "attmp2".
+-- 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
+ERROR: check constraint "b_greater_than_ten" of relation "attmp3" is violated by some row
+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
+ERROR: check constraint "b_greater_than_ten" of relation "attmp3" is violated by some row
+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;
+ a | b
+---+----
+ 1 | 20
+(1 row)
+
+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
+ERROR: check constraint "b_le_20" of relation "attmp6" is violated by some row
+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));
+NOTICE: boo: 18
+ALTER TABLE attmp3 ADD CONSTRAINT IDENTITY check (b = boo(b)) NOT VALID;
+NOTICE: merging constraint "identity" with inherited definition
+ALTER TABLE attmp3 VALIDATE CONSTRAINT identity;
+NOTICE: boo: 20
+NOTICE: boo: 16
+-- 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;
+ERROR: check constraint "check_a_is_2" of relation "parent_noinh_convalid" is violated by some row
+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';
+ convalidated
+--------------
+ t
+(1 row)
+
+-- 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;
+ERROR: there is no unique constraint matching given keys for referenced table "attmp4"
+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
+ Table "public.nv_parent"
+ Column | Type | Collation | Nullable | Default
+--------+------+-----------+----------+---------
+ d | date | | |
+Check constraints:
+ "nv_parent_check" CHECK (false) NO INHERIT
+
+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';
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Append
+ -> Seq Scan on nv_parent nv_parent_1
+ Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
+ -> Seq Scan on nv_child_2010 nv_parent_2
+ Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
+ -> Seq Scan on nv_child_2011 nv_parent_3
+ Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
+(7 rows)
+
+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;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Append
+ -> Seq Scan on nv_parent nv_parent_1
+ Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
+ -> Seq Scan on nv_child_2010 nv_parent_2
+ Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
+ -> Seq Scan on nv_child_2011 nv_parent_3
+ Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
+(7 rows)
+
+explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Append
+ -> Seq Scan on nv_parent nv_parent_1
+ Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+ -> Seq Scan on nv_child_2010 nv_parent_2
+ Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+ -> Seq Scan on nv_child_2011 nv_parent_3
+ Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+ -> Seq Scan on nv_child_2009 nv_parent_4
+ Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+(9 rows)
+
+-- 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;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Append
+ -> Seq Scan on nv_parent nv_parent_1
+ Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+ -> Seq Scan on nv_child_2010 nv_parent_2
+ Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+ -> Seq Scan on nv_child_2009 nv_parent_3
+ Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+(7 rows)
+
+-- 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
+ Table "public.nv_child_2009"
+ Column | Type | Collation | Nullable | Default
+--------+------+-----------+----------+---------
+ d | date | | |
+Check constraints:
+ "nv_child_2009_d_check" CHECK (d >= '01-01-2009'::date AND d <= '12-31-2009'::date)
+ "nv_parent_d_check" CHECK (d >= '01-01-2001'::date AND d <= '12-31-2099'::date) NOT VALID
+Inherits: nv_parent
+
+-- 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;
+ERROR: foreign key constraint "fktable_ftest1_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer.
+-- 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);
+ERROR: foreign key constraint "fktable_ftest1_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer.
+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
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(43) is not present in table "pktable".
+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;
+ERROR: foreign key constraint "fktable_ftest1_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: numeric and integer.
+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
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(43) is not present in table "pktable".
+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;
+ERROR: foreign key constraint "fktable_ftest1_ftest2_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: cidr and integer.
+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);
+ERROR: foreign key constraint "fktable_ftest1_ftest2_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: cidr and integer.
+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);
+ERROR: foreign key constraint "fktable_ftest1_ftest2_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest2" are of incompatible types: integer and inet.
+-- As does this...
+ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest2, ftest1)
+ references pktable(ptest1, ptest2);
+ERROR: foreign key constraint "fktable_ftest2_ftest1_fkey" cannot be implemented
+DETAIL: Key columns "ftest2" and "ptest1" are of incompatible types: inet and integer.
+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;
+ conname | tgfoid | tgtype | tgdeferrable | tginitdeferred
+---------+------------------------+--------+--------------+----------------
+ fkdd | "RI_FKey_cascade_del" | 9 | f | f
+ fkdd | "RI_FKey_noaction_upd" | 17 | t | t
+ fkdd2 | "RI_FKey_cascade_del" | 9 | f | f
+ fkdd2 | "RI_FKey_noaction_upd" | 17 | t | t
+ fkdi | "RI_FKey_cascade_del" | 9 | f | f
+ fkdi | "RI_FKey_noaction_upd" | 17 | t | f
+ fkdi2 | "RI_FKey_cascade_del" | 9 | f | f
+ fkdi2 | "RI_FKey_noaction_upd" | 17 | t | f
+ fknd | "RI_FKey_cascade_del" | 9 | f | f
+ fknd | "RI_FKey_noaction_upd" | 17 | f | f
+ fknd2 | "RI_FKey_cascade_del" | 9 | f | f
+ fknd2 | "RI_FKey_noaction_upd" | 17 | f | f
+(12 rows)
+
+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;
+ conname | tgfoid | tgtype | tgdeferrable | tginitdeferred
+---------+---------------------+--------+--------------+----------------
+ fkdd | "RI_FKey_check_ins" | 5 | t | t
+ fkdd | "RI_FKey_check_upd" | 17 | t | t
+ fkdd2 | "RI_FKey_check_ins" | 5 | t | t
+ fkdd2 | "RI_FKey_check_upd" | 17 | t | t
+ fkdi | "RI_FKey_check_ins" | 5 | t | f
+ fkdi | "RI_FKey_check_upd" | 17 | t | f
+ fkdi2 | "RI_FKey_check_ins" | 5 | t | f
+ fkdi2 | "RI_FKey_check_upd" | 17 | t | f
+ fknd | "RI_FKey_check_ins" | 5 | f | f
+ fknd | "RI_FKey_check_upd" | 17 | f | f
+ fknd2 | "RI_FKey_check_ins" | 5 | f | f
+ fknd2 | "RI_FKey_check_upd" | 17 | f | f
+(12 rows)
+
+-- 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);
+ERROR: new row for relation "atacc1" violates check constraint "atacc_test1"
+DETAIL: Failing row contains (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);
+ERROR: check constraint "atacc_test1" of relation "atacc1" is violated by some row
+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);
+ERROR: column "test1" does not exist
+HINT: Perhaps you meant to reference the column "atacc1.test".
+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+test2<test3*4);
+-- should fail
+insert into atacc1 (test,test2,test3) values (4,4,2);
+ERROR: new row for relation "atacc1" violates check constraint "atacc_test1"
+DETAIL: Failing row contains (4, 4, 2).
+-- should succeed
+insert into atacc1 (test,test2,test3) values (4,4,5);
+drop table atacc1;
+-- lets do some naming tests
+create table atacc1 (test int check (test>3), test2 int);
+alter table atacc1 add check (test2>test);
+-- should fail for $2
+insert into atacc1 (test2, test) values (3, 4);
+ERROR: new row for relation "atacc1" violates check constraint "atacc1_check"
+DETAIL: Failing row contains (4, 3).
+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);
+ERROR: new row for relation "atacc2" violates check constraint "foo"
+DETAIL: Failing row contains (-3).
+insert into atacc2 (test2) values (3);
+-- fail and then succeed on atacc3
+insert into atacc3 (test2) values (-3);
+ERROR: new row for relation "atacc3" violates check constraint "foo"
+DETAIL: Failing row contains (null, -3, null).
+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;
+ERROR: relation "atacc2" is not a parent of relation "atacc3"
+-- make sure it really isn't a child
+insert into atacc3 (test2) values (3);
+select test2 from atacc2;
+ test2
+-------
+(0 rows)
+
+-- fail due to missing constraint
+alter table atacc2 add constraint foo check (test2>0);
+alter table atacc3 inherit atacc2;
+ERROR: child table is missing constraint "foo"
+-- fail due to missing column
+alter table atacc3 rename test2 to testx;
+alter table atacc3 inherit atacc2;
+ERROR: child table is missing column "test2"
+-- fail due to mismatched data type
+alter table atacc3 add test2 bool;
+alter table atacc3 inherit atacc2;
+ERROR: child table "atacc3" has different type for column "test2"
+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;
+ERROR: relation "atacc2" would be inherited from more than once
+alter table atacc2 inherit atacc3;
+ERROR: circular inheritance not allowed
+DETAIL: "atacc3" is already a child of "atacc2".
+alter table atacc2 inherit atacc2;
+ERROR: circular inheritance not allowed
+DETAIL: "atacc2" is already a child of "atacc2".
+-- test that we really are a child now (should see 4 not 3 and cascade should go through)
+select test2 from atacc2;
+ test2
+-------
+ 4
+(1 row)
+
+drop table atacc2 cascade;
+NOTICE: drop cascades to table atacc3
+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);
+ERROR: new row for relation "atacc1" violates check constraint "foo"
+DETAIL: Failing row contains (-3).
+insert into atacc1 (test) values (3);
+-- fail, violating row:
+alter table atacc2 add constraint foo check (test>0) no inherit;
+ERROR: check constraint "foo" of relation "atacc2" is violated by some row
+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);
+ERROR: duplicate key value violates unique constraint "atacc_test1"
+DETAIL: Key (test)=(2) already exists.
+-- 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;
+ERROR: could not create unique index "atacc_test1"
+DETAIL: Key (test)=(0) is duplicated.
+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);
+ERROR: could not create unique index "atacc_test1"
+DETAIL: Key (test)=(2) is duplicated.
+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);
+ERROR: column "test1" named in key does not exist
+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);
+ERROR: duplicate key value violates unique constraint "atacc_test1"
+DETAIL: Key (test, test2)=(4, 4) already exists.
+-- 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);
+ERROR: duplicate key value violates unique constraint "atacc1_test_key"
+DETAIL: Key (test)=(3) already exists.
+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);
+ERROR: duplicate key value violates unique constraint "atacc_test1"
+DETAIL: Key (test)=(2) already exists.
+-- should succeed
+insert into atacc1 (test) values (4);
+-- inserting NULL should fail
+insert into atacc1 (test) values(NULL);
+ERROR: null value in column "test" of relation "atacc1" violates not-null constraint
+DETAIL: Failing row contains (4, null).
+-- try adding a second primary key (should fail)
+alter table atacc1 add constraint atacc_oid1 primary key(id);
+ERROR: multiple primary keys for table "atacc1" are not allowed
+-- 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);
+ERROR: could not create unique index "atacc_test1"
+DETAIL: Key (test)=(2) is duplicated.
+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);
+ERROR: column "test" of relation "atacc1" contains null values
+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);
+ERROR: column "test1" of relation "atacc1" does not exist
+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;
+ERROR: column "test2" of relation "atacc1" contains null values
+-- 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;
+ERROR: check constraint "atacc1_chk" of relation "atacc1" is violated by some row
+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);
+ERROR: multiple primary keys for table "atacc1" are not allowed
+-- insert initial value
+insert into atacc1 (test,test2) values (4,4);
+-- should fail
+insert into atacc1 (test,test2) values (4,4);
+ERROR: duplicate key value violates unique constraint "atacc_test1"
+DETAIL: Key (test, test2)=(4, 4) already exists.
+insert into atacc1 (test,test2) values (NULL,3);
+ERROR: null value in column "test" of relation "atacc1" violates not-null constraint
+DETAIL: Failing row contains (null, 3).
+insert into atacc1 (test,test2) values (3, NULL);
+ERROR: null value in column "test2" of relation "atacc1" violates not-null constraint
+DETAIL: Failing row contains (3, null).
+insert into atacc1 (test,test2) values (NULL,NULL);
+ERROR: null value in column "test" of relation "atacc1" violates not-null constraint
+DETAIL: Failing row contains (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);
+ERROR: duplicate key value violates unique constraint "atacc1_pkey"
+DETAIL: Key (test)=(3) already exists.
+insert into atacc1 (test2, test) values (1, NULL);
+ERROR: null value in column "test" of relation "atacc1" violates not-null constraint
+DETAIL: Failing row contains (null, 1).
+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;
+ERROR: permission denied: "pg_class" is a system catalog
+alter table pg_class alter relname set not null;
+ERROR: permission denied: "pg_class" is a system catalog
+-- try altering non-existent table, should fail
+alter table non_existent alter column bar set not null;
+ERROR: relation "non_existent" does not exist
+alter table non_existent alter column bar drop not null;
+ERROR: relation "non_existent" does not exist
+-- 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;
+ERROR: column "test" is in a primary key
+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;
+ERROR: column "test" of relation "atacc1" contains null values
+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;
+ERROR: column "bar" of relation "atacc1" does not exist
+alter table atacc1 alter bar drop not null;
+ERROR: column "bar" of relation "atacc1" does not exist
+-- 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;
+ERROR: "myview" is not a table or foreign table
+alter table myview alter column test set not null;
+ERROR: "myview" is not a table or foreign table
+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;
+ERROR: column "test_a" of relation "atacc1" contains null values
+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;
+ERROR: column "test_a" of relation "atacc1" contains null values
+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;
+ERROR: column "test_b" of relation "atacc1" contains null values
+-- commands order has no importance
+alter table atacc1 alter test_b set not null, alter test_a set not null;
+ERROR: column "test_b" of relation "atacc1" contains null values
+-- 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);
+ERROR: null value in column "a" of relation "parent" violates not-null constraint
+DETAIL: Failing row contains (null).
+insert into child (a, b) values (NULL, 'foo');
+ERROR: null value in column "a" of relation "child" violates not-null constraint
+DETAIL: Failing row contains (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;
+ERROR: column "a" of relation "parent" contains null values
+alter table child alter a set not null;
+ERROR: column "a" of relation "child" contains null values
+delete from parent;
+alter table only parent alter a set not null;
+insert into parent values (NULL);
+ERROR: null value in column "a" of relation "parent" violates not-null constraint
+DETAIL: Failing row contains (null).
+alter table child alter a set not null;
+insert into child (a, b) values (NULL, 'foo');
+ERROR: null value in column "a" of relation "child" violates not-null constraint
+DETAIL: Failing row contains (null, foo).
+delete from child;
+alter table child alter a set not null;
+insert into child (a, b) values (NULL, 'foo');
+ERROR: null value in column "a" of relation "child" violates not-null constraint
+DETAIL: Failing row contains (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;
+ c1 | c2
+----+-----------------
+ 5 | initial_default
+ | initial_default
+ |
+ 10 | new_default
+(4 rows)
+
+-- set defaults to an incorrect type: this should fail
+alter table def_test alter column c1 set default 'wrong_datatype';
+ERROR: invalid input syntax for type integer: "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;
+ERROR: column "c3" of relation "def_test" does not exist
+-- 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;
+ c1 | c2
+----+-----------------
+ 5 | initial_default
+ | initial_default
+ |
+ 10 | new_default
+ |
+ 45 |
+ 45 | view_default
+(7 rows)
+
+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;
+ERROR: permission denied: "pg_class" is a system catalog
+-- try altering non-existent table, should fail
+alter table nosuchtable drop column bar;
+ERROR: relation "nosuchtable" does not exist
+-- 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;
+ERROR: column "a" of relation "atacc1" does not exist
+-- SELECTs
+select * from atacc1;
+ b | c | d
+---+---+---
+ 2 | 3 | 4
+(1 row)
+
+select * from atacc1 order by a;
+ERROR: column "a" does not exist
+LINE 1: select * from atacc1 order by a;
+ ^
+select * from atacc1 order by "........pg.dropped.1........";
+ERROR: column "........pg.dropped.1........" does not exist
+LINE 1: select * from atacc1 order by "........pg.dropped.1........"...
+ ^
+select * from atacc1 group by a;
+ERROR: column "a" does not exist
+LINE 1: select * from atacc1 group by a;
+ ^
+select * from atacc1 group by "........pg.dropped.1........";
+ERROR: column "........pg.dropped.1........" does not exist
+LINE 1: select * from atacc1 group by "........pg.dropped.1........"...
+ ^
+select atacc1.* from atacc1;
+ b | c | d
+---+---+---
+ 2 | 3 | 4
+(1 row)
+
+select a from atacc1;
+ERROR: column "a" does not exist
+LINE 1: select a from atacc1;
+ ^
+select atacc1.a from atacc1;
+ERROR: column atacc1.a does not exist
+LINE 1: select atacc1.a from atacc1;
+ ^
+select b,c,d from atacc1;
+ b | c | d
+---+---+---
+ 2 | 3 | 4
+(1 row)
+
+select a,b,c,d from atacc1;
+ERROR: column "a" does not exist
+LINE 1: select a,b,c,d from atacc1;
+ ^
+select * from atacc1 where a = 1;
+ERROR: column "a" does not exist
+LINE 1: select * from atacc1 where a = 1;
+ ^
+select "........pg.dropped.1........" from atacc1;
+ERROR: column "........pg.dropped.1........" does not exist
+LINE 1: select "........pg.dropped.1........" from atacc1;
+ ^
+select atacc1."........pg.dropped.1........" from atacc1;
+ERROR: column atacc1.........pg.dropped.1........ does not exist
+LINE 1: select atacc1."........pg.dropped.1........" from atacc1;
+ ^
+select "........pg.dropped.1........",b,c,d from atacc1;
+ERROR: column "........pg.dropped.1........" does not exist
+LINE 1: select "........pg.dropped.1........",b,c,d from atacc1;
+ ^
+select * from atacc1 where "........pg.dropped.1........" = 1;
+ERROR: column "........pg.dropped.1........" does not exist
+LINE 1: select * from atacc1 where "........pg.dropped.1........" = ...
+ ^
+-- UPDATEs
+update atacc1 set a = 3;
+ERROR: column "a" of relation "atacc1" does not exist
+LINE 1: update atacc1 set a = 3;
+ ^
+update atacc1 set b = 2 where a = 3;
+ERROR: column "a" does not exist
+LINE 1: update atacc1 set b = 2 where a = 3;
+ ^
+update atacc1 set "........pg.dropped.1........" = 3;
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+LINE 1: update atacc1 set "........pg.dropped.1........" = 3;
+ ^
+update atacc1 set b = 2 where "........pg.dropped.1........" = 3;
+ERROR: column "........pg.dropped.1........" does not exist
+LINE 1: update atacc1 set b = 2 where "........pg.dropped.1........"...
+ ^
+-- INSERTs
+insert into atacc1 values (10, 11, 12, 13);
+ERROR: INSERT has more expressions than target columns
+LINE 1: insert into atacc1 values (10, 11, 12, 13);
+ ^
+insert into atacc1 values (default, 11, 12, 13);
+ERROR: INSERT has more expressions than target columns
+LINE 1: insert into atacc1 values (default, 11, 12, 13);
+ ^
+insert into atacc1 values (11, 12, 13);
+insert into atacc1 (a) values (10);
+ERROR: column "a" of relation "atacc1" does not exist
+LINE 1: insert into atacc1 (a) values (10);
+ ^
+insert into atacc1 (a) values (default);
+ERROR: column "a" of relation "atacc1" does not exist
+LINE 1: insert into atacc1 (a) values (default);
+ ^
+insert into atacc1 (a,b,c,d) values (10,11,12,13);
+ERROR: column "a" of relation "atacc1" does not exist
+LINE 1: insert into atacc1 (a,b,c,d) values (10,11,12,13);
+ ^
+insert into atacc1 (a,b,c,d) values (default,11,12,13);
+ERROR: column "a" of relation "atacc1" does not exist
+LINE 1: 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);
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+LINE 1: insert into atacc1 ("........pg.dropped.1........") values (...
+ ^
+insert into atacc1 ("........pg.dropped.1........") values (default);
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+LINE 1: insert into atacc1 ("........pg.dropped.1........") values (...
+ ^
+insert into atacc1 ("........pg.dropped.1........",b,c,d) values (10,11,12,13);
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+LINE 1: insert into atacc1 ("........pg.dropped.1........",b,c,d) va...
+ ^
+insert into atacc1 ("........pg.dropped.1........",b,c,d) values (default,11,12,13);
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+LINE 1: insert into atacc1 ("........pg.dropped.1........",b,c,d) va...
+ ^
+-- DELETEs
+delete from atacc1 where a = 3;
+ERROR: column "a" does not exist
+LINE 1: delete from atacc1 where a = 3;
+ ^
+delete from atacc1 where "........pg.dropped.1........" = 3;
+ERROR: column "........pg.dropped.1........" does not exist
+LINE 1: delete from atacc1 where "........pg.dropped.1........" = 3;
+ ^
+delete from atacc1;
+-- try dropping a non-existent column, should fail
+alter table atacc1 drop bar;
+ERROR: column "bar" of relation "atacc1" does not exist
+-- 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;
+ERROR: syntax error at or near "WITH"
+LINE 1: alter table atacc1 SET WITH OIDS;
+ ^
+-- try dropping the xmin column, should fail
+alter table atacc1 drop xmin;
+ERROR: cannot drop system column "xmin"
+-- try creating a view and altering that, should fail
+create view myview as select * from atacc1;
+select * from myview;
+ b | c | d
+---+---+---
+(0 rows)
+
+alter table myview drop d;
+ERROR: "myview" is not a table, composite type, or foreign table
+drop view myview;
+-- test some commands to make sure they fail on the dropped column
+analyze atacc1(a);
+ERROR: column "a" of relation "atacc1" does not exist
+analyze atacc1("........pg.dropped.1........");
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+vacuum analyze atacc1(a);
+ERROR: column "a" of relation "atacc1" does not exist
+vacuum analyze atacc1("........pg.dropped.1........");
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+comment on column atacc1.a is 'testing';
+ERROR: column "a" of relation "atacc1" does not exist
+comment on column atacc1."........pg.dropped.1........" is 'testing';
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+alter table atacc1 alter a set storage plain;
+ERROR: column "a" of relation "atacc1" does not exist
+alter table atacc1 alter "........pg.dropped.1........" set storage plain;
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+alter table atacc1 alter a set statistics 0;
+ERROR: column "a" of relation "atacc1" does not exist
+alter table atacc1 alter "........pg.dropped.1........" set statistics 0;
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+alter table atacc1 alter a set default 3;
+ERROR: column "a" of relation "atacc1" does not exist
+alter table atacc1 alter "........pg.dropped.1........" set default 3;
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+alter table atacc1 alter a drop default;
+ERROR: column "a" of relation "atacc1" does not exist
+alter table atacc1 alter "........pg.dropped.1........" drop default;
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+alter table atacc1 alter a set not null;
+ERROR: column "a" of relation "atacc1" does not exist
+alter table atacc1 alter "........pg.dropped.1........" set not null;
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+alter table atacc1 alter a drop not null;
+ERROR: column "a" of relation "atacc1" does not exist
+alter table atacc1 alter "........pg.dropped.1........" drop not null;
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+alter table atacc1 rename a to x;
+ERROR: column "a" does not exist
+alter table atacc1 rename "........pg.dropped.1........" to x;
+ERROR: column "........pg.dropped.1........" does not exist
+alter table atacc1 add primary key(a);
+ERROR: column "a" of relation "atacc1" does not exist
+alter table atacc1 add primary key("........pg.dropped.1........");
+ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+alter table atacc1 add unique(a);
+ERROR: column "a" named in key does not exist
+alter table atacc1 add unique("........pg.dropped.1........");
+ERROR: column "........pg.dropped.1........" named in key does not exist
+alter table atacc1 add check (a > 3);
+ERROR: column "a" does not exist
+alter table atacc1 add check ("........pg.dropped.1........" > 3);
+ERROR: column "........pg.dropped.1........" does not exist
+create table atacc2 (id int4 unique);
+alter table atacc1 add foreign key (a) references atacc2(id);
+ERROR: column "a" referenced in foreign key constraint does not exist
+alter table atacc1 add foreign key ("........pg.dropped.1........") references atacc2(id);
+ERROR: column "........pg.dropped.1........" referenced in foreign key constraint does not exist
+alter table atacc2 add foreign key (id) references atacc1(a);
+ERROR: column "a" referenced in foreign key constraint does not exist
+alter table atacc2 add foreign key (id) references atacc1("........pg.dropped.1........");
+ERROR: column "........pg.dropped.1........" referenced in foreign key constraint does not exist
+drop table atacc2;
+create index "testing_idx" on atacc1(a);
+ERROR: column "a" does not exist
+create index "testing_idx" on atacc1("........pg.dropped.1........");
+ERROR: column "........pg.dropped.1........" does not exist
+-- test create as and select into
+insert into atacc1 values (21, 22, 23);
+create table attest1 as select * from atacc1;
+select * from attest1;
+ b | c | d
+----+----+----
+ 21 | 22 | 23
+(1 row)
+
+drop table attest1;
+select * into attest2 from atacc1;
+select * from attest2;
+ b | c | d
+----+----+----
+ 21 | 22 | 23
+(1 row)
+
+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;
+--
+(1 row)
+
+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);
+ERROR: new row for relation "atacc1" violates check constraint "atacc1_value_check"
+DETAIL: Failing row contains (1, 100).
+alter table atacc1 drop column value;
+alter table atacc1 add column value int check (value < 10);
+insert into atacc1(value) values (100);
+ERROR: new row for relation "atacc1" violates check constraint "atacc1_value_check"
+DETAIL: Failing row contains (2, 100).
+insert into atacc1(id, value) values (null, 0);
+ERROR: null value in column "id" of relation "atacc1" violates not-null constraint
+DETAIL: Failing row contains (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;
+ b | c
+----+----
+ 2 | 3
+ 12 | 13
+(2 rows)
+
+select * from child;
+ b | c | d
+----+----+---------
+ 12 | 13 | testing
+(1 row)
+
+alter table parent drop c;
+select * from parent;
+ b
+----
+ 2
+ 12
+(2 rows)
+
+select * from child;
+ b | d
+----+---------
+ 12 | testing
+(1 row)
+
+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
+NOTICE: merging column "a" with inherited definition
+ERROR: column "a" has a type conflict
+DETAIL: double precision versus real
+create table child (b decimal(10,7)) inherits (parent); -- fail
+NOTICE: moving and merging column "b" with inherited definition
+DETAIL: User-specified column moved to the position of the inherited column.
+ERROR: column "b" has a type conflict
+DETAIL: numeric(10,4) versus numeric(10,7)
+create table child (c text collate "POSIX") inherits (parent); -- fail
+NOTICE: moving and merging column "c" with inherited definition
+DETAIL: User-specified column moved to the position of the inherited column.
+ERROR: column "c" has a collation conflict
+DETAIL: "C" versus "POSIX"
+create table child (a double precision, b decimal(10,4)) inherits (parent);
+NOTICE: merging column "a" with inherited definition
+NOTICE: merging column "b" with inherited definition
+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;
+2 3
+copy attest(a) to stdout;
+ERROR: column "a" of relation "attest" does not exist
+copy attest("........pg.dropped.1........") to stdout;
+ERROR: column "........pg.dropped.1........" of relation "attest" does not exist
+copy attest from stdin;
+ERROR: extra data after last expected column
+CONTEXT: COPY attest, line 1: "10 11 12"
+select * from attest;
+ b | c
+---+---
+ 2 | 3
+(1 row)
+
+copy attest from stdin;
+select * from attest;
+ b | c
+----+----
+ 2 | 3
+ 21 | 22
+(2 rows)
+
+copy attest(a) from stdin;
+ERROR: column "a" of relation "attest" does not exist
+copy attest("........pg.dropped.1........") from stdin;
+ERROR: column "........pg.dropped.1........" of relation "attest" does not exist
+copy attest(b,c) from stdin;
+select * from attest;
+ b | c
+----+----
+ 2 | 3
+ 21 | 22
+ 31 | 32
+(3 rows)
+
+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;
+ERROR: cannot drop inherited column "a"
+alter table only dropColumnChild drop column b;
+ERROR: cannot drop inherited 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;
+ERROR: cannot rename inherited column "a"
+alter table only renameColumnChild rename column a to d;
+ERROR: inherited column "a" must be renamed in child tables too
+alter table only renameColumn rename column a to d;
+ERROR: inherited column "a" must be renamed in child tables too
+-- 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;
+NOTICE: relation "doesnt_exist_tab" does not exist, skipping
+alter table if exists doesnt_exist_tab rename column b to a;
+NOTICE: relation "doesnt_exist_tab" does not exist, skipping
+-- this should work
+alter table renameColumn add column w int;
+-- this should fail
+alter table only renameColumn add column x int;
+ERROR: column must be added to child tables too
+-- Test corner cases in dropping of inherited columns
+create table p1 (f1 int, f2 int);
+create table c1 (f1 int not null) inherits(p1);
+NOTICE: merging column "f1" with inherited definition
+-- should be rejected since c1.f1 is inherited
+alter table c1 drop column f1;
+ERROR: cannot drop inherited column "f1"
+-- should work
+alter table p1 drop column f1;
+-- c1.f1 is still there, but no longer inherited
+select f1 from c1;
+ f1
+----
+(0 rows)
+
+alter table c1 drop column f1;
+select f1 from c1;
+ERROR: column "f1" does not exist
+LINE 1: select f1 from c1;
+ ^
+HINT: Perhaps you meant to reference the column "c1.f2".
+drop table p1 cascade;
+NOTICE: drop cascades to table c1
+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;
+ERROR: cannot drop inherited 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;
+ERROR: column "f1" does not exist
+LINE 1: select f1 from c1;
+ ^
+HINT: Perhaps you meant to reference the column "c1.f2".
+drop table p1 cascade;
+NOTICE: drop cascades to table c1
+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;
+ERROR: cannot drop inherited 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;
+NOTICE: drop cascades to table c1
+create table p1 (f1 int, f2 int);
+create table c1 (f1 int not null) inherits(p1);
+NOTICE: merging column "f1" with inherited definition
+-- should be rejected since c1.f1 is inherited
+alter table c1 drop column f1;
+ERROR: cannot drop inherited 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;
+NOTICE: drop cascades to table c1
+create table p1(id int, name text);
+create table p2(id2 int, name text, height int);
+create table c1(age int) inherits(p1,p2);
+NOTICE: merging multiple inherited definitions of column "name"
+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;
+ relname | attname | attinhcount | attislocal
+---------+---------+-------------+------------
+ c1 | id | 1 | f
+ c1 | name | 2 | f
+ c1 | id2 | 1 | f
+ c1 | height | 1 | f
+ c1 | age | 0 | t
+ gc1 | id | 1 | f
+ gc1 | name | 1 | f
+ gc1 | id2 | 1 | f
+ gc1 | height | 1 | f
+ gc1 | age | 1 | f
+ p1 | id | 0 | t
+ p1 | name | 0 | t
+ p2 | id2 | 0 | t
+ p2 | name | 0 | t
+ p2 | height | 0 | t
+(15 rows)
+
+-- should work
+alter table only p1 drop column name;
+-- should work. Now c1.name is local and inhcount is 0.
+alter table p2 drop column name;
+-- should be rejected since its inherited
+alter table gc1 drop column name;
+ERROR: cannot drop inherited column "name"
+-- 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;
+ERROR: column "name" of relation "gc1" does not exist
+-- 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
+ERROR: column "non_existing" of relation "dropcolumnexists" does not exist
+alter table dropColumnExists drop column if exists non_existing; --succeed
+NOTICE: column "non_existing" of relation "dropcolumnexists" does not exist, skipping
+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;
+ relname | attname | attinhcount | attislocal
+---------+---------+-------------+------------
+ c1 | id | 1 | f
+ c1 | id2 | 1 | f
+ c1 | age | 0 | t
+ gc1 | id | 1 | f
+ gc1 | id2 | 1 | f
+ gc1 | age | 1 | f
+ p1 | id | 0 | t
+ p2 | id2 | 0 | t
+(8 rows)
+
+drop table p1, p2 cascade;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table c1
+drop cascades to table gc1
+-- test attinhcount tracking with merged columns
+create table depth0();
+create table depth1(c text) inherits (depth0);
+create table depth2() inherits (depth1);
+alter table depth0 add c text;
+NOTICE: merging definition of column "c" for child "depth1"
+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;
+ attrelid | attname | attinhcount | attislocal
+----------+---------+-------------+------------
+ depth0 | c | 0 | t
+ depth1 | c | 1 | t
+ depth2 | c | 1 | f
+(3 rows)
+
+-- 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;
+NOTICE: merging definition of column "f2" for child "c1"
+insert into p1 values (1,2,'abc');
+insert into c1 values(11,'xyz',33,0); -- should fail
+ERROR: new row for relation "c1" violates check constraint "p1_a1_check"
+DETAIL: Failing row contains (11, xyz, 33, 0).
+insert into c1 values(11,'xyz',33,22);
+select * from p1;
+ f1 | a1 | f2
+----+----+-----
+ 1 | 2 | abc
+ 11 | 22 | xyz
+(2 rows)
+
+update p1 set a1 = a1 + 1, f2 = upper(f2);
+select * from p1;
+ f1 | a1 | f2
+----+----+-----
+ 1 | 3 | ABC
+ 11 | 23 | XYZ
+(2 rows)
+
+drop table p1 cascade;
+NOTICE: drop cascades to table c1
+-- 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;
+ f1 | f2 | f3
+----+----+----
+ bb | cc | dd
+(1 row)
+
+drop domain mytype cascade;
+NOTICE: drop cascades to column f2 of table foo
+select * from foo;
+ f1 | f3
+----+----
+ bb | dd
+(1 row)
+
+insert into foo values('qq','rr');
+select * from foo;
+ f1 | f3
+----+----
+ bb | dd
+ qq | rr
+(2 rows)
+
+update foo set f3 = 'zz';
+select * from foo;
+ f1 | f3
+----+----
+ bb | zz
+ qq | zz
+(2 rows)
+
+select f3,max(f1) from foo group by f3;
+ f3 | max
+----+-----
+ zz | qq
+(1 row)
+
+-- Simple tests for alter table column type
+alter table foo alter f1 TYPE integer; -- fails
+ERROR: column "f1" cannot be cast automatically to type integer
+HINT: You might need to specify "USING f1::integer".
+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;
+ atcol1 | atcol2
+--------+--------
+ 1 | t
+ 2 | f
+(2 rows)
+
+alter table anothertab alter column atcol1 type boolean; -- fails
+ERROR: column "atcol1" cannot be cast automatically to type boolean
+HINT: You might need to specify "USING atcol1::boolean".
+alter table anothertab alter column atcol1 type boolean using atcol1::int; -- fails
+ERROR: result of USING clause for column "atcol1" cannot be cast automatically to type boolean
+HINT: You might need to add an explicit cast.
+alter table anothertab alter column atcol1 type integer;
+select * from anothertab;
+ atcol1 | atcol2
+--------+--------
+ 1 | t
+ 2 | f
+(2 rows)
+
+insert into anothertab (atcol1, atcol2) values (45, null); -- fails
+ERROR: new row for relation "anothertab" violates check constraint "anothertab_chk"
+DETAIL: Failing row contains (45, null).
+insert into anothertab (atcol1, atcol2) values (default, null);
+select * from anothertab;
+ atcol1 | atcol2
+--------+--------
+ 1 | t
+ 2 | f
+ 3 |
+(3 rows)
+
+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;
+ atcol1 | atcol2
+--------+--------------
+ 1 | IT WAS TRUE
+ 2 | IT WAS FALSE
+ 3 | IT WAS NULL!
+(3 rows)
+
+alter table anothertab alter column atcol1 type boolean
+ using case when atcol1 % 2 = 0 then true else false end; -- fails
+ERROR: default for column "atcol1" cannot be cast automatically to type boolean
+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
+ERROR: operator does not exist: boolean <= integer
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+alter table anothertab drop constraint anothertab_chk;
+alter table anothertab drop constraint anothertab_chk; -- fails
+ERROR: constraint "anothertab_chk" of relation "anothertab" does not exist
+alter table anothertab drop constraint IF EXISTS anothertab_chk; -- succeeds
+NOTICE: constraint "anothertab_chk" of relation "anothertab" does not exist, skipping
+alter table anothertab alter column atcol1 type boolean
+ using case when atcol1 % 2 = 0 then true else false end;
+select * from anothertab;
+ atcol1 | atcol2
+--------+--------------
+ f | IT WAS TRUE
+ t | IT WAS FALSE
+ f | IT WAS NULL!
+(3 rows)
+
+drop table anothertab;
+-- Test index handling in alter table column type (cf. bugs #15835, #15865)
+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
+ Table "public.anothertab"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ f1 | integer | | not null |
+ f2 | integer | | |
+ f3 | integer | | |
+ f4 | integer | | |
+ f5 | integer | | |
+Indexes:
+ "anothertab_pkey" PRIMARY KEY, btree (f1)
+ "anothertab_f1_f4_key" UNIQUE CONSTRAINT, btree (f1, f4)
+ "anothertab_f2_f3_idx" btree (f2, f3)
+ "anothertab_f2_key" UNIQUE CONSTRAINT, btree (f2)
+ "anothertab_f3_excl" EXCLUDE USING btree (f3 WITH =)
+ "anothertab_f4_excl" EXCLUDE USING btree (f4 WITH =) WHERE (f4 IS NOT NULL)
+ "anothertab_f4_excl1" EXCLUDE USING btree (f4 WITH =) WHERE (f5 > 0)
+ "anothertab_f4_idx" UNIQUE, btree (f4)
+
+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
+ Table "public.anothertab"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+---------
+ f1 | bigint | | not null |
+ f2 | bigint | | |
+ f3 | bigint | | |
+ f4 | bigint | | |
+ f5 | bigint | | |
+Indexes:
+ "anothertab_pkey" PRIMARY KEY, btree (f1)
+ "anothertab_f1_f4_key" UNIQUE CONSTRAINT, btree (f1, f4)
+ "anothertab_f2_f3_idx" btree (f2, f3)
+ "anothertab_f2_key" UNIQUE CONSTRAINT, btree (f2)
+ "anothertab_f3_excl" EXCLUDE USING btree (f3 WITH =)
+ "anothertab_f4_excl" EXCLUDE USING btree (f4 WITH =) WHERE (f4 IS NOT NULL)
+ "anothertab_f4_excl1" EXCLUDE USING btree (f4 WITH =) WHERE (f5 > 0)
+ "anothertab_f4_idx" UNIQUE, btree (f4)
+
+drop table anothertab;
+-- 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;
+ f1 | f2 | f3
+----+-------+-----
+ 1 | one | uno
+ 2 | two | due
+ 3 | three | tre
+(3 rows)
+
+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;
+ f1 | f2
+--------------------+----
+ one and uno more | 10
+ two and due more | 20
+ three and tre more | 30
+(3 rows)
+
+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
+ERROR: cannot alter table "at_tab1" because column "at_tab2.y" uses its row type
+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
+ERROR: ROW() column has type text instead of type character varying
+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
+ERROR: cannot alter table "at_tab1" because column "at_tab2.y" uses its row type
+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
+ Table "public.at_part_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | text | | |
+Partition of: at_partitioned FOR VALUES FROM (0) TO (1000)
+Indexes:
+ "at_part_1_a_idx" btree (a)
+ "at_part_1_b_idx" btree (b)
+
+\d at_part_2
+ Table "public.at_part_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ b | text | | |
+ a | integer | | |
+
+alter table at_partitioned attach partition at_part_2 for values from (1000) to (2000);
+\d at_part_2
+ Table "public.at_part_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ b | text | | |
+ a | integer | | |
+Partition of: at_partitioned FOR VALUES FROM (1000) TO (2000)
+Indexes:
+ "at_part_2_a_idx" btree (a)
+ "at_part_2_b_idx" btree (b)
+
+alter table at_partitioned alter column b type numeric using b::numeric;
+\d at_part_1
+ Table "public.at_part_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | numeric | | |
+Partition of: at_partitioned FOR VALUES FROM (0) TO (1000)
+Indexes:
+ "at_part_1_a_idx" btree (a)
+ "at_part_1_b_idx" btree (b)
+
+\d at_part_2
+ Table "public.at_part_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ b | numeric | | |
+ a | integer | | |
+Partition of: at_partitioned FOR VALUES FROM (1000) TO (2000)
+Indexes:
+ "at_part_2_a_idx" btree (a)
+ "at_part_2_b_idx" btree (b)
+
+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;
+ relname | orig_oid | storage | desc
+------------------------------+----------+---------+---------------
+ at_partitioned | t | none |
+ at_partitioned_0 | t | own |
+ at_partitioned_0_id_name_key | t | own | child 0 index
+ at_partitioned_1 | t | own |
+ at_partitioned_1_id_name_key | t | own | child 1 index
+ at_partitioned_id_name_key | t | none | parent index
+(6 rows)
+
+select conname, obj_description(oid, 'pg_constraint') as desc
+ from pg_constraint where conname like 'at_partitioned%'
+ order by conname;
+ conname | desc
+------------------------------+--------------------
+ at_partitioned_0_id_name_key | child 0 constraint
+ at_partitioned_1_id_name_key | child 1 constraint
+ at_partitioned_id_name_key | parent constraint
+(3 rows)
+
+alter table at_partitioned alter column name type varchar(127);
+-- Note: these tests currently show the wrong behavior for comments :-(
+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;
+ relname | orig_oid | storage | desc
+------------------------------+----------+---------+--------------
+ at_partitioned | t | none |
+ at_partitioned_0 | t | own |
+ at_partitioned_0_id_name_key | f | own | parent index
+ at_partitioned_1 | t | own |
+ at_partitioned_1_id_name_key | f | own | parent index
+ at_partitioned_id_name_key | f | none | parent index
+(6 rows)
+
+select conname, obj_description(oid, 'pg_constraint') as desc
+ from pg_constraint where conname like 'at_partitioned%'
+ order by conname;
+ conname | desc
+------------------------------+-------------------
+ at_partitioned_0_id_name_key |
+ at_partitioned_1_id_name_key |
+ at_partitioned_id_name_key | parent constraint
+(3 rows)
+
+-- 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
+ERROR: composite type recur1 cannot be made a member of itself
+alter table recur1 add column f2 recur1[]; -- fails
+ERROR: composite type recur1 cannot be made a member of itself
+create domain array_of_recur1 as recur1[];
+alter table recur1 add column f2 array_of_recur1; -- fails
+ERROR: composite type recur1 cannot be made a member of itself
+create temp table recur2 (f1 int, f2 recur1);
+alter table recur1 add column f2 recur2; -- fails
+ERROR: composite type recur1 cannot be made a member of itself
+alter table recur1 add column f2 int;
+alter table recur1 alter column f2 type recur2; -- fails
+ERROR: composite type recur1 cannot be made a member of itself
+-- SET STORAGE may need to add a TOAST table
+create table test_storage (a text);
+alter table test_storage alter a set storage plain;
+alter table test_storage add b int default 0; -- rewrite table to remove its TOAST table
+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;
+ has_toast_table
+-----------------
+ t
+(1 row)
+
+-- 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;
+\d+ test_storage
+ Table "public.test_storage"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | external | |
+ b | integer | | | 0 | plain | |
+Indexes:
+ "test_storage_idx" btree (b, a)
+
+\d+ test_storage_idx
+ Index "public.test_storage_idx"
+ Column | Type | Key? | Definition | Storage | Stats target
+--------+---------+------+------------+----------+--------------
+ b | integer | yes | b | plain |
+ a | text | yes | a | external |
+btree, for table "public.test_storage"
+
+-- 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
+ Table "public.test_inh_check"
+ Column | Type | Collation | Nullable | Default
+--------+------------------+-----------+----------+---------
+ a | double precision | | |
+ b | double precision | | |
+Check constraints:
+ "test_inh_check_a_check" CHECK (a > 10.2::double precision)
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d test_inh_check_child
+ Table "public.test_inh_check_child"
+ Column | Type | Collation | Nullable | Default
+--------+------------------+-----------+----------+---------
+ a | double precision | | |
+ b | double precision | | |
+Check constraints:
+ "test_inh_check_a_check" CHECK (a > 10.2::double precision)
+Inherits: test_inh_check
+
+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;
+ relname | conname | coninhcount | conislocal | connoinherit
+----------------------+------------------------+-------------+------------+--------------
+ test_inh_check | test_inh_check_a_check | 0 | t | f
+ test_inh_check_child | test_inh_check_a_check | 1 | f | f
+(2 rows)
+
+ALTER TABLE test_inh_check ALTER COLUMN a TYPE numeric;
+\d test_inh_check
+ Table "public.test_inh_check"
+ Column | Type | Collation | Nullable | Default
+--------+------------------+-----------+----------+---------
+ a | numeric | | |
+ b | double precision | | |
+Check constraints:
+ "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision)
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d test_inh_check_child
+ Table "public.test_inh_check_child"
+ Column | Type | Collation | Nullable | Default
+--------+------------------+-----------+----------+---------
+ a | numeric | | |
+ b | double precision | | |
+Check constraints:
+ "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision)
+Inherits: test_inh_check
+
+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;
+ relname | conname | coninhcount | conislocal | connoinherit
+----------------------+------------------------+-------------+------------+--------------
+ test_inh_check | test_inh_check_a_check | 0 | t | f
+ test_inh_check_child | test_inh_check_a_check | 1 | f | f
+(2 rows)
+
+-- 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);
+NOTICE: merging constraint "bmerged" with inherited definition
+\d test_inh_check
+ Table "public.test_inh_check"
+ Column | Type | Collation | Nullable | Default
+--------+------------------+-----------+----------+---------
+ a | numeric | | |
+ b | double precision | | |
+Check constraints:
+ "bmerged" CHECK (b > 1::double precision)
+ "bnoinherit" CHECK (b > 100::double precision) NO INHERIT
+ "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision)
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d test_inh_check_child
+ Table "public.test_inh_check_child"
+ Column | Type | Collation | Nullable | Default
+--------+------------------+-----------+----------+---------
+ a | numeric | | |
+ b | double precision | | |
+Check constraints:
+ "blocal" CHECK (b < 1000::double precision)
+ "bmerged" CHECK (b > 1::double precision)
+ "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision)
+Inherits: test_inh_check
+
+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;
+ relname | conname | coninhcount | conislocal | connoinherit
+----------------------+------------------------+-------------+------------+--------------
+ test_inh_check | bmerged | 0 | t | f
+ test_inh_check | bnoinherit | 0 | t | t
+ test_inh_check | test_inh_check_a_check | 0 | t | f
+ test_inh_check_child | blocal | 0 | t | f
+ test_inh_check_child | bmerged | 1 | t | f
+ test_inh_check_child | test_inh_check_a_check | 1 | f | f
+(6 rows)
+
+ALTER TABLE test_inh_check ALTER COLUMN b TYPE numeric;
+NOTICE: merging constraint "bmerged" with inherited definition
+\d test_inh_check
+ Table "public.test_inh_check"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | numeric | | |
+ b | numeric | | |
+Check constraints:
+ "bmerged" CHECK (b::double precision > 1::double precision)
+ "bnoinherit" CHECK (b::double precision > 100::double precision) NO INHERIT
+ "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision)
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d test_inh_check_child
+ Table "public.test_inh_check_child"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | numeric | | |
+ b | numeric | | |
+Check constraints:
+ "blocal" CHECK (b::double precision < 1000::double precision)
+ "bmerged" CHECK (b::double precision > 1::double precision)
+ "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision)
+Inherits: test_inh_check
+
+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;
+ relname | conname | coninhcount | conislocal | connoinherit
+----------------------+------------------------+-------------+------------+--------------
+ test_inh_check | bmerged | 0 | t | f
+ test_inh_check | bnoinherit | 0 | t | t
+ test_inh_check | test_inh_check_a_check | 0 | t | f
+ test_inh_check_child | blocal | 0 | t | f
+ test_inh_check_child | bmerged | 1 | t | f
+ test_inh_check_child | test_inh_check_a_check | 1 | f | f
+(6 rows)
+
+-- 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));
+ERROR: cannot convert whole-row table reference
+DETAIL: USING expression contains a whole-row table reference.
+-- check for rollback of ANALYZE corrupting table property flags (bug #11638)
+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
+ Table "public.check_fk_presence_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ t | text | | |
+Foreign-key constraints:
+ "check_fk_presence_2_id_fkey" FOREIGN KEY (id) REFERENCES check_fk_presence_1(id)
+
+DROP TABLE check_fk_presence_1, check_fk_presence_2;
+-- 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
+ View "public.at_view_1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ id | integer | | | | plain |
+ stuff | text | | | | extended |
+View definition:
+ SELECT bt.id,
+ bt.stuff
+ FROM at_base_table bt;
+
+\d+ at_view_2
+ View "public.at_view_2"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ id | integer | | | | plain |
+ stuff | text | | | | extended |
+ j | json | | | | extended |
+View definition:
+ SELECT v1.id,
+ v1.stuff,
+ to_json(v1.*) AS j
+ FROM at_view_1 v1;
+
+explain (verbose, costs off) select * from at_view_2;
+ QUERY PLAN
+----------------------------------------------------------
+ Seq Scan on public.at_base_table bt
+ Output: bt.id, bt.stuff, to_json(ROW(bt.id, bt.stuff))
+(2 rows)
+
+select * from at_view_2;
+ id | stuff | j
+----+--------+----------------------------
+ 23 | skidoo | {"id":23,"stuff":"skidoo"}
+(1 row)
+
+create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
+\d+ at_view_1
+ View "public.at_view_1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ id | integer | | | | plain |
+ stuff | text | | | | extended |
+ more | integer | | | | plain |
+View definition:
+ SELECT bt.id,
+ bt.stuff,
+ 2 + 2 AS more
+ FROM at_base_table bt;
+
+\d+ at_view_2
+ View "public.at_view_2"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ id | integer | | | | plain |
+ stuff | text | | | | extended |
+ j | json | | | | extended |
+View definition:
+ SELECT v1.id,
+ v1.stuff,
+ to_json(v1.*) AS j
+ FROM at_view_1 v1;
+
+explain (verbose, costs off) select * from at_view_2;
+ QUERY PLAN
+----------------------------------------------------------------
+ Seq Scan on public.at_base_table bt
+ Output: bt.id, bt.stuff, to_json(ROW(bt.id, bt.stuff, NULL))
+(2 rows)
+
+select * from at_view_2;
+ id | stuff | j
+----+--------+----------------------------------------
+ 23 | skidoo | {"id":23,"stuff":"skidoo","more":null}
+(1 row)
+
+drop view at_view_2;
+drop view at_view_1;
+drop table at_base_table;
+-- check adding a column not iself 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;
+$$);
+ check_ddl_rewrite
+-------------------
+ t
+(1 row)
+
+SELECT check_ddl_rewrite('rewrite_test', $$
+ ALTER TABLE rewrite_test
+ ADD COLUMN notempty2_rewrite serial,
+ ADD COLUMN empty2 text;
+$$);
+ check_ddl_rewrite
+-------------------
+ t
+(1 row)
+
+-- 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;
+$$);
+ check_ddl_rewrite
+-------------------
+ f
+(1 row)
+
+SELECT check_ddl_rewrite('rewrite_test', $$
+ ALTER TABLE rewrite_test
+ ADD COLUMN notempty4_norewrite int default 42,
+ ADD COLUMN empty4 text;
+$$);
+ check_ddl_rewrite
+-------------------
+ f
+(1 row)
+
+-- 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;
+$$);
+ check_ddl_rewrite
+-------------------
+ t
+(1 row)
+
+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;
+$$);
+ check_ddl_rewrite
+-------------------
+ t
+(1 row)
+
+-- cleanup
+DROP FUNCTION check_ddl_rewrite(regclass, text);
+DROP TABLE rewrite_test;
+--
+-- lock levels
+--
+drop type lockmodes;
+ERROR: type "lockmodes" does not exist
+create type lockmodes as enum (
+ 'SIReadLock'
+,'AccessShareLock'
+,'RowShareLock'
+,'RowExclusiveLock'
+,'ShareUpdateExclusiveLock'
+,'ShareLock'
+,'ShareRowExclusiveLock'
+,'ExclusiveLock'
+,'AccessExclusiveLock'
+);
+drop view my_locks;
+ERROR: view "my_locks" does not exist
+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
+ where transactionid = pg_current_xact_id()::xid)
+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;
+ relname | max_lockmode
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+(1 row)
+
+rollback;
+begin; alter table alterlock cluster on alterlock_pkey;
+select * from my_locks order by 1;
+ relname | max_lockmode
+----------------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ alterlock_pkey | ShareUpdateExclusiveLock
+(2 rows)
+
+commit;
+begin; alter table alterlock set without cluster;
+select * from my_locks order by 1;
+ relname | max_lockmode
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+(1 row)
+
+commit;
+begin; alter table alterlock set (fillfactor = 100);
+select * from my_locks order by 1;
+ relname | max_lockmode
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast | ShareUpdateExclusiveLock
+(2 rows)
+
+commit;
+begin; alter table alterlock reset (fillfactor);
+select * from my_locks order by 1;
+ relname | max_lockmode
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast | ShareUpdateExclusiveLock
+(2 rows)
+
+commit;
+begin; alter table alterlock set (toast.autovacuum_enabled = off);
+select * from my_locks order by 1;
+ relname | max_lockmode
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast | ShareUpdateExclusiveLock
+(2 rows)
+
+commit;
+begin; alter table alterlock set (autovacuum_enabled = off);
+select * from my_locks order by 1;
+ relname | max_lockmode
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast | ShareUpdateExclusiveLock
+(2 rows)
+
+commit;
+begin; alter table alterlock alter column f2 set (n_distinct = 1);
+select * from my_locks order by 1;
+ relname | max_lockmode
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+(1 row)
+
+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;
+ relname | max_lockmode
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast | ShareUpdateExclusiveLock
+(2 rows)
+
+commit;
+begin; alter table alterlock alter column f2 set storage extended;
+select * from my_locks order by 1;
+ relname | max_lockmode
+-----------+---------------------
+ alterlock | AccessExclusiveLock
+(1 row)
+
+rollback;
+begin; alter table alterlock alter column f2 set default 'x';
+select * from my_locks order by 1;
+ relname | max_lockmode
+-----------+---------------------
+ alterlock | AccessExclusiveLock
+(1 row)
+
+rollback;
+begin;
+create trigger ttdummy
+ before delete or update on alterlock
+ for each row
+ execute procedure
+ ttdummy (1, 1);
+select * from my_locks order by 1;
+ relname | max_lockmode
+-----------+-----------------------
+ alterlock | ShareRowExclusiveLock
+(1 row)
+
+rollback;
+begin;
+select * from my_locks order by 1;
+ relname | max_lockmode
+---------+--------------
+(0 rows)
+
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+ relname | max_lockmode
+-----------------+-----------------------
+ alterlock | ShareRowExclusiveLock
+ alterlock2 | ShareRowExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey | AccessShareLock
+(4 rows)
+
+rollback;
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+ relname | max_lockmode
+------------+-----------------------
+ alterlock | ShareRowExclusiveLock
+ alterlock2 | ShareRowExclusiveLock
+(2 rows)
+
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+ relname | max_lockmode
+-----------------+--------------------------
+ alterlock | RowShareLock
+ alterlock2 | ShareUpdateExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey | AccessShareLock
+(4 rows)
+
+rollback;
+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
+ where transactionid = pg_current_xact_id()::xid)
+and locktype = 'relation'
+and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
+and c.relname = 'my_locks'
+group by c.relname;
+-- raise exception
+alter table my_locks set (autovacuum_enabled = false);
+ERROR: unrecognized parameter "autovacuum_enabled"
+alter view my_locks set (autovacuum_enabled = false);
+ERROR: unrecognized parameter "autovacuum_enabled"
+alter table my_locks reset (autovacuum_enabled);
+alter view my_locks reset (autovacuum_enabled);
+begin;
+alter view my_locks set (security_barrier=off);
+select * from my_locks order by 1;
+ relname | max_lockmode
+----------+---------------------
+ my_locks | AccessExclusiveLock
+(1 row)
+
+alter view my_locks reset (security_barrier);
+rollback;
+-- this test intentionally applies the ALTER TABLE command against a view, but
+-- uses a view option so we expect this to succeed. This form of SQL is
+-- accepted for historical reasons, as shown in the docs for ALTER VIEW
+begin;
+alter table my_locks set (security_barrier=off);
+select * from my_locks order by 1;
+ relname | max_lockmode
+----------+---------------------
+ my_locks | AccessExclusiveLock
+(1 row)
+
+alter table my_locks reset (security_barrier);
+rollback;
+-- cleanup
+drop table alterlock2;
+drop table alterlock;
+drop view my_locks;
+drop type lockmodes;
+--
+-- alter function
+--
+create function test_strict(text) returns text as
+ 'select coalesce($1, ''got passed a null'');'
+ language sql returns null on null input;
+select test_strict(NULL);
+ test_strict
+-------------
+
+(1 row)
+
+alter function test_strict(text) called on null input;
+select test_strict(NULL);
+ test_strict
+-------------------
+ got passed a null
+(1 row)
+
+create function non_strict(text) returns text as
+ 'select coalesce($1, ''got passed a null'');'
+ language sql called on null input;
+select non_strict(NULL);
+ non_strict
+-------------------
+ got passed a null
+(1 row)
+
+alter function non_strict(text) returns null on null input;
+select non_strict(NULL);
+ non_strict
+------------
+
+(1 row)
+
+--
+-- alter object set schema
+--
+create schema alter1;
+create schema alter2;
+create table alter1.t1(f1 serial primary key, f2 int check (f2 > 0));
+create view alter1.v1 as select * from alter1.t1;
+create function alter1.plus1(int) returns int as 'select $1+1' language sql;
+create domain alter1.posint integer check (value > 0);
+create type alter1.ctype as (f1 int, f2 text);
+create function alter1.same(alter1.ctype, alter1.ctype) returns boolean language sql
+as 'select $1.f1 is not distinct from $2.f1 and $1.f2 is not distinct from $2.f2';
+create operator alter1.=(procedure = alter1.same, leftarg = alter1.ctype, rightarg = alter1.ctype);
+create operator class alter1.ctype_hash_ops default for type alter1.ctype using hash as
+ operator 1 alter1.=(alter1.ctype, alter1.ctype);
+create conversion alter1.latin1_to_utf8 for 'latin1' to 'utf8' from iso8859_1_to_utf8;
+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);
+create text search template alter1.tmpl(init = dsimple_init, lexize = dsimple_lexize);
+create text search dictionary alter1.dict(template = alter1.tmpl);
+insert into alter1.t1(f2) values(11);
+insert into alter1.t1(f2) values(12);
+alter table alter1.t1 set schema alter1; -- no-op, same schema
+alter table alter1.t1 set schema alter2;
+alter table alter1.v1 set schema alter2;
+alter function alter1.plus1(int) set schema alter2;
+alter domain alter1.posint set schema alter2;
+alter operator class alter1.ctype_hash_ops using hash set schema alter2;
+alter operator family alter1.ctype_hash_ops using hash set schema alter2;
+alter operator alter1.=(alter1.ctype, alter1.ctype) set schema alter2;
+alter function alter1.same(alter1.ctype, alter1.ctype) set schema alter2;
+alter type alter1.ctype set schema alter1; -- no-op, same schema
+alter type alter1.ctype set schema alter2;
+alter conversion alter1.latin1_to_utf8 set schema alter2;
+alter text search parser alter1.prs set schema alter2;
+alter text search configuration alter1.cfg set schema alter2;
+alter text search template alter1.tmpl set schema alter2;
+alter text search dictionary alter1.dict set schema alter2;
+-- this should succeed because nothing is left in alter1
+drop schema alter1;
+insert into alter2.t1(f2) values(13);
+insert into alter2.t1(f2) values(14);
+select * from alter2.t1;
+ f1 | f2
+----+----
+ 1 | 11
+ 2 | 12
+ 3 | 13
+ 4 | 14
+(4 rows)
+
+select * from alter2.v1;
+ f1 | f2
+----+----
+ 1 | 11
+ 2 | 12
+ 3 | 13
+ 4 | 14
+(4 rows)
+
+select alter2.plus1(41);
+ plus1
+-------
+ 42
+(1 row)
+
+-- clean up
+drop schema alter2 cascade;
+NOTICE: drop cascades to 13 other objects
+DETAIL: drop cascades to table alter2.t1
+drop cascades to view alter2.v1
+drop cascades to function alter2.plus1(integer)
+drop cascades to type alter2.posint
+drop cascades to type alter2.ctype
+drop cascades to function alter2.same(alter2.ctype,alter2.ctype)
+drop cascades to operator alter2.=(alter2.ctype,alter2.ctype)
+drop cascades to operator family alter2.ctype_hash_ops for access method hash
+drop cascades to conversion alter2.latin1_to_utf8
+drop cascades to text search parser alter2.prs
+drop cascades to text search configuration alter2.cfg
+drop cascades to text search template alter2.tmpl
+drop cascades to text search dictionary alter2.dict
+--
+-- composite types
+--
+CREATE TYPE test_type AS (a int);
+\d test_type
+ Composite type "public.test_type"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+
+ALTER TYPE nosuchtype ADD ATTRIBUTE b text; -- fails
+ERROR: relation "nosuchtype" does not exist
+ALTER TYPE test_type ADD ATTRIBUTE b text;
+\d test_type
+ Composite type "public.test_type"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | text | | |
+
+ALTER TYPE test_type ADD ATTRIBUTE b text; -- fails
+ERROR: column "b" of relation "test_type" already exists
+ALTER TYPE test_type ALTER ATTRIBUTE b SET DATA TYPE varchar;
+\d test_type
+ Composite type "public.test_type"
+ Column | Type | Collation | Nullable | Default
+--------+-------------------+-----------+----------+---------
+ a | integer | | |
+ b | character varying | | |
+
+ALTER TYPE test_type ALTER ATTRIBUTE b SET DATA TYPE integer;
+\d test_type
+ Composite type "public.test_type"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+
+ALTER TYPE test_type DROP ATTRIBUTE b;
+\d test_type
+ Composite type "public.test_type"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+
+ALTER TYPE test_type DROP ATTRIBUTE c; -- fails
+ERROR: column "c" of relation "test_type" does not exist
+ALTER TYPE test_type DROP ATTRIBUTE IF EXISTS c;
+NOTICE: column "c" of relation "test_type" does not exist, skipping
+ALTER TYPE test_type DROP ATTRIBUTE a, ADD ATTRIBUTE d boolean;
+\d test_type
+ Composite type "public.test_type"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ d | boolean | | |
+
+ALTER TYPE test_type RENAME ATTRIBUTE a TO aa;
+ERROR: column "a" does not exist
+ALTER TYPE test_type RENAME ATTRIBUTE d TO dd;
+\d test_type
+ Composite type "public.test_type"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ dd | boolean | | |
+
+DROP TYPE test_type;
+CREATE TYPE test_type1 AS (a int, b text);
+CREATE TABLE test_tbl1 (x int, y test_type1);
+ALTER TYPE test_type1 ALTER ATTRIBUTE b TYPE varchar; -- fails
+ERROR: cannot alter type "test_type1" because column "test_tbl1.y" uses it
+CREATE TYPE test_type2 AS (a int, b text);
+CREATE TABLE test_tbl2 OF test_type2;
+CREATE TABLE test_tbl2_subclass () INHERITS (test_tbl2);
+\d test_type2
+ Composite type "public.test_type2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | text | | |
+
+\d test_tbl2
+ Table "public.test_tbl2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | text | | |
+Number of child tables: 1 (Use \d+ to list them.)
+Typed table of type: test_type2
+
+ALTER TYPE test_type2 ADD ATTRIBUTE c text; -- fails
+ERROR: cannot alter type "test_type2" because it is the type of a typed table
+HINT: Use ALTER ... CASCADE to alter the typed tables too.
+ALTER TYPE test_type2 ADD ATTRIBUTE c text CASCADE;
+\d test_type2
+ Composite type "public.test_type2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | text | | |
+ c | text | | |
+
+\d test_tbl2
+ Table "public.test_tbl2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | text | | |
+ c | text | | |
+Number of child tables: 1 (Use \d+ to list them.)
+Typed table of type: test_type2
+
+ALTER TYPE test_type2 ALTER ATTRIBUTE b TYPE varchar; -- fails
+ERROR: cannot alter type "test_type2" because it is the type of a typed table
+HINT: Use ALTER ... CASCADE to alter the typed tables too.
+ALTER TYPE test_type2 ALTER ATTRIBUTE b TYPE varchar CASCADE;
+\d test_type2
+ Composite type "public.test_type2"
+ Column | Type | Collation | Nullable | Default
+--------+-------------------+-----------+----------+---------
+ a | integer | | |
+ b | character varying | | |
+ c | text | | |
+
+\d test_tbl2
+ Table "public.test_tbl2"
+ Column | Type | Collation | Nullable | Default
+--------+-------------------+-----------+----------+---------
+ a | integer | | |
+ b | character varying | | |
+ c | text | | |
+Number of child tables: 1 (Use \d+ to list them.)
+Typed table of type: test_type2
+
+ALTER TYPE test_type2 DROP ATTRIBUTE b; -- fails
+ERROR: cannot alter type "test_type2" because it is the type of a typed table
+HINT: Use ALTER ... CASCADE to alter the typed tables too.
+ALTER TYPE test_type2 DROP ATTRIBUTE b CASCADE;
+\d test_type2
+ Composite type "public.test_type2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ c | text | | |
+
+\d test_tbl2
+ Table "public.test_tbl2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ c | text | | |
+Number of child tables: 1 (Use \d+ to list them.)
+Typed table of type: test_type2
+
+ALTER TYPE test_type2 RENAME ATTRIBUTE a TO aa; -- fails
+ERROR: cannot alter type "test_type2" because it is the type of a typed table
+HINT: Use ALTER ... CASCADE to alter the typed tables too.
+ALTER TYPE test_type2 RENAME ATTRIBUTE a TO aa CASCADE;
+\d test_type2
+ Composite type "public.test_type2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ aa | integer | | |
+ c | text | | |
+
+\d test_tbl2
+ Table "public.test_tbl2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ aa | integer | | |
+ c | text | | |
+Number of child tables: 1 (Use \d+ to list them.)
+Typed table of type: test_type2
+
+\d test_tbl2_subclass
+ Table "public.test_tbl2_subclass"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ aa | integer | | |
+ c | text | | |
+Inherits: test_tbl2
+
+DROP TABLE test_tbl2_subclass;
+CREATE TYPE test_typex AS (a int, b text);
+CREATE TABLE test_tblx (x int, y test_typex check ((y).a > 0));
+ALTER TYPE test_typex DROP ATTRIBUTE a; -- fails
+ERROR: cannot drop column a of composite type test_typex because other objects depend on it
+DETAIL: constraint test_tblx_y_check on table test_tblx depends on column a of composite type test_typex
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+ALTER TYPE test_typex DROP ATTRIBUTE a CASCADE;
+NOTICE: drop cascades to constraint test_tblx_y_check on table test_tblx
+\d test_tblx
+ Table "public.test_tblx"
+ Column | Type | Collation | Nullable | Default
+--------+------------+-----------+----------+---------
+ x | integer | | |
+ y | test_typex | | |
+
+DROP TABLE test_tblx;
+DROP TYPE test_typex;
+-- This test isn't that interesting on its own, but the purpose is to leave
+-- behind a table to test pg_upgrade with. The table has a composite type
+-- column in it, and the composite type has a dropped attribute.
+CREATE TYPE test_type3 AS (a int);
+CREATE TABLE test_tbl3 (c) AS SELECT '(1)'::test_type3;
+ALTER TYPE test_type3 DROP ATTRIBUTE a, ADD ATTRIBUTE b int;
+CREATE TYPE test_type_empty AS ();
+DROP TYPE test_type_empty;
+--
+-- 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;
+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;
+ERROR: table "tt1" has different type for column "y"
+ALTER TABLE tt2 OF tt_t0;
+ERROR: table "tt2" has different type for column "y"
+ALTER TABLE tt3 OF tt_t0;
+ERROR: table has column "y" where type requires "x"
+ALTER TABLE tt4 OF tt_t0;
+ERROR: table is missing column "y"
+ALTER TABLE tt5 OF tt_t0;
+ERROR: table has extra column "z"
+ALTER TABLE tt6 OF tt_t0;
+ERROR: typed tables cannot inherit
+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
+ Table "public.tt7"
+ Column | Type | Collation | Nullable | Default
+--------+--------------+-----------+----------+---------
+ x | integer | | |
+ y | numeric(8,2) | | |
+
+-- 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);
+ERROR: new row for relation "test_drop_constr_child" violates check constraint "test_drop_constr_parent_c_check"
+DETAIL: Failing row contains (null).
+DROP TABLE test_drop_constr_parent CASCADE;
+NOTICE: drop cascades to table test_drop_constr_child
+--
+-- IF EXISTS test
+--
+ALTER TABLE IF EXISTS tt8 ADD COLUMN f int;
+NOTICE: relation "tt8" does not exist, skipping
+ALTER TABLE IF EXISTS tt8 ADD CONSTRAINT xxx PRIMARY KEY(f);
+NOTICE: relation "tt8" does not exist, skipping
+ALTER TABLE IF EXISTS tt8 ADD CHECK (f BETWEEN 0 AND 10);
+NOTICE: relation "tt8" does not exist, skipping
+ALTER TABLE IF EXISTS tt8 ALTER COLUMN f SET DEFAULT 0;
+NOTICE: relation "tt8" does not exist, skipping
+ALTER TABLE IF EXISTS tt8 RENAME COLUMN f TO f1;
+NOTICE: relation "tt8" does not exist, skipping
+ALTER TABLE IF EXISTS tt8 SET SCHEMA alter2;
+NOTICE: relation "tt8" does not exist, skipping
+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
+ Table "alter2.tt8"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ f1 | integer | | not null | 0
+Indexes:
+ "xxx" PRIMARY KEY, btree (f1)
+Check constraints:
+ "tt8_f_check" CHECK (f1 >= 0 AND f1 <= 10)
+
+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
+ERROR: constraint "foo" for relation "tt9" already exists
+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
+ERROR: relation "tt9_c_key" already exists
+ALTER TABLE tt9 ADD CONSTRAINT foo UNIQUE(c); -- fail, dup name
+ERROR: constraint "foo" for relation "tt9" already exists
+ALTER TABLE tt9 ADD CONSTRAINT tt9_c_key CHECK(c > 5); -- fail, dup name
+ERROR: constraint "tt9_c_key" for relation "tt9" already exists
+ALTER TABLE tt9 ADD CONSTRAINT tt9_c_key2 CHECK(c > 6);
+ALTER TABLE tt9 ADD UNIQUE(c); -- picks nonconflicting name
+\d tt9
+ Table "public.tt9"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c | integer | | |
+Indexes:
+ "tt9_c_key" UNIQUE CONSTRAINT, btree (c)
+ "tt9_c_key1" UNIQUE CONSTRAINT, btree (c)
+ "tt9_c_key3" UNIQUE CONSTRAINT, btree (c)
+Check constraints:
+ "foo" CHECK (c > 3)
+ "tt9_c_check" CHECK (c > 1)
+ "tt9_c_check1" CHECK (c > 2)
+ "tt9_c_key2" CHECK (c > 6)
+
+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;
+ comment
+-----------------------------
+ Column 'id' on comment_test
+(1 row)
+
+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;
+ index | comment
+--------------------+-----------------------------------------------
+ comment_test_index | Simple index on comment_test
+ comment_test_pk | Index backing the PRIMARY KEY of comment_test
+(2 rows)
+
+SELECT conname as constraint, obj_description(oid, 'pg_constraint') as comment FROM pg_constraint where conrelid = 'comment_test'::regclass ORDER BY 1, 2;
+ constraint | comment
+---------------------------------+-----------------------------------------------
+ comment_test_pk | PRIMARY KEY constraint of comment_test
+ comment_test_positive_col_check | CHECK constraint on comment_test.positive_col
+(2 rows)
+
+-- 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;
+ comment
+-----------------------------
+ Column 'id' on comment_test
+(1 row)
+
+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;
+ index | comment
+--------------------+-----------------------------------------------
+ comment_test_index | Simple index on comment_test
+ comment_test_pk | Index backing the PRIMARY KEY of comment_test
+(2 rows)
+
+SELECT conname as constraint, obj_description(oid, 'pg_constraint') as comment FROM pg_constraint where conrelid = 'comment_test'::regclass ORDER BY 1, 2;
+ constraint | comment
+---------------------------------+-----------------------------------------------
+ comment_test_pk | PRIMARY KEY constraint of comment_test
+ comment_test_positive_col_check | CHECK constraint on comment_test.positive_col
+(2 rows)
+
+-- 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;
+ERROR: foreign key constraint "comment_test_child_fk" cannot be implemented
+DETAIL: Key columns "id" and "id" are of incompatible types: text and integer.
+-- Comments should be intact
+SELECT col_description('comment_test_child'::regclass, 1) as comment;
+ comment
+-----------------------------------
+ Column 'id' on comment_test_child
+(1 row)
+
+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;
+ index | comment
+-----------------------+-----------------------------------------------------
+ comment_test_child_fk | Index backing the FOREIGN KEY of comment_test_child
+(1 row)
+
+SELECT conname as constraint, obj_description(oid, 'pg_constraint') as comment FROM pg_constraint where conrelid = 'comment_test_child'::regclass ORDER BY 1, 2;
+ constraint | comment
+-----------------------+----------------------------------------------
+ comment_test_child_fk | FOREIGN KEY constraint of comment_test_child
+(1 row)
+
+-- 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;
+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;
+ oid | mapped_oid | reltablespace | relfilenode | relname
+-----+------------+---------------+-------------+---------
+(0 rows)
+
+-- Checks on creating and manipulation of user defined relations in
+-- pg_catalog.
+SHOW allow_system_table_mods;
+ allow_system_table_mods
+-------------------------
+ off
+(1 row)
+
+-- disallowed because of search_path issues with pg_dump
+CREATE TABLE pg_catalog.new_system_table();
+ERROR: permission denied to create "pg_catalog.new_system_table"
+DETAIL: System catalog modifications are currently disallowed.
+-- 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;
+-- set logged
+CREATE UNLOGGED TABLE unlogged1(f1 SERIAL PRIMARY KEY, f2 TEXT);
+-- check relpersistence of an unlogged table
+SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged1'
+UNION ALL
+SELECT '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 '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;
+ relname | relkind | relpersistence
+------------------+---------+----------------
+ toast index | i | u
+ toast table | t | u
+ unlogged1 | r | u
+ unlogged1_f1_seq | S | p
+ unlogged1_pkey | i | u
+(5 rows)
+
+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
+ALTER TABLE unlogged2 SET LOGGED; -- fails because a foreign key to an unlogged table exists
+ERROR: could not change table "unlogged2" to logged because it references unlogged table "unlogged1"
+ALTER TABLE unlogged1 SET LOGGED;
+-- check relpersistence of an unlogged table after changing to permanent
+SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged1'
+UNION ALL
+SELECT '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 '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;
+ relname | relkind | relpersistence
+------------------+---------+----------------
+ toast index | i | p
+ toast table | t | p
+ unlogged1 | r | p
+ unlogged1_f1_seq | S | p
+ unlogged1_pkey | i | p
+(5 rows)
+
+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);
+-- check relpersistence of a permanent table
+SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^logged1'
+UNION ALL
+SELECT '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 '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;
+ relname | relkind | relpersistence
+----------------+---------+----------------
+ logged1 | r | p
+ logged1_f1_seq | S | p
+ logged1_pkey | i | p
+ toast index | i | p
+ toast table | t | p
+(5 rows)
+
+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
+ERROR: could not change table "logged1" to unlogged because it references logged table "logged2"
+ALTER TABLE logged3 SET UNLOGGED; -- skip self-referencing foreign key
+ALTER TABLE logged2 SET UNLOGGED;
+ALTER TABLE logged1 SET UNLOGGED;
+-- check relpersistence of a permanent table after changing to unlogged
+SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^logged1'
+UNION ALL
+SELECT '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 '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;
+ relname | relkind | relpersistence
+----------------+---------+----------------
+ logged1 | r | u
+ logged1_f1_seq | S | p
+ logged1_pkey | i | u
+ toast index | i | u
+ toast table | t | u
+(5 rows)
+
+ALTER TABLE logged1 SET UNLOGGED; -- silently do nothing
+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
+ Table "public.test_add_column"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+
+ALTER TABLE test_add_column
+ ADD COLUMN c2 integer;
+\d test_add_column
+ Table "public.test_add_column"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+ c2 | integer | | |
+
+ALTER TABLE test_add_column
+ ADD COLUMN c2 integer; -- fail because c2 already exists
+ERROR: column "c2" of relation "test_add_column" already exists
+ALTER TABLE ONLY test_add_column
+ ADD COLUMN c2 integer; -- fail because c2 already exists
+ERROR: column "c2" of relation "test_add_column" already exists
+\d test_add_column
+ Table "public.test_add_column"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+ c2 | integer | | |
+
+ALTER TABLE test_add_column
+ ADD COLUMN IF NOT EXISTS c2 integer; -- skipping because c2 already exists
+NOTICE: column "c2" of relation "test_add_column" already exists, skipping
+ALTER TABLE ONLY test_add_column
+ ADD COLUMN IF NOT EXISTS c2 integer; -- skipping because c2 already exists
+NOTICE: column "c2" of relation "test_add_column" already exists, skipping
+\d test_add_column
+ Table "public.test_add_column"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+ c2 | integer | | |
+
+ALTER TABLE test_add_column
+ ADD COLUMN c2 integer, -- fail because c2 already exists
+ ADD COLUMN c3 integer primary key;
+ERROR: column "c2" of relation "test_add_column" already exists
+\d test_add_column
+ Table "public.test_add_column"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+ c2 | integer | | |
+
+ALTER TABLE test_add_column
+ ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
+ ADD COLUMN c3 integer primary key;
+NOTICE: column "c2" of relation "test_add_column" already exists, skipping
+\d test_add_column
+ Table "public.test_add_column"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+ c2 | integer | | |
+ c3 | integer | | not null |
+Indexes:
+ "test_add_column_pkey" PRIMARY KEY, btree (c3)
+
+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
+NOTICE: column "c2" of relation "test_add_column" already exists, skipping
+NOTICE: column "c3" of relation "test_add_column" already exists, skipping
+\d test_add_column
+ Table "public.test_add_column"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+ c2 | integer | | |
+ c3 | integer | | not null |
+Indexes:
+ "test_add_column_pkey" PRIMARY KEY, btree (c3)
+
+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;
+NOTICE: column "c2" of relation "test_add_column" already exists, skipping
+NOTICE: column "c3" of relation "test_add_column" already exists, skipping
+\d test_add_column
+ Table "public.test_add_column"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+ c2 | integer | | |
+ c3 | integer | | not null |
+ c4 | integer | | |
+Indexes:
+ "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+ "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+ TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+ ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column;
+NOTICE: column "c4" of relation "test_add_column" already exists, skipping
+\d test_add_column
+ Table "public.test_add_column"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+ c2 | integer | | |
+ c3 | integer | | not null |
+ c4 | integer | | |
+Indexes:
+ "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+ "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+ TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+ ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 8);
+\d test_add_column
+ Table "public.test_add_column"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------------------------------------------
+ c1 | integer | | |
+ c2 | integer | | |
+ c3 | integer | | not null |
+ c4 | integer | | |
+ c5 | integer | | not null | nextval('test_add_column_c5_seq'::regclass)
+Indexes:
+ "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Check constraints:
+ "test_add_column_c5_check" CHECK (c5 > 8)
+Foreign-key constraints:
+ "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+ TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+ ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10);
+NOTICE: column "c5" of relation "test_add_column" already exists, skipping
+\d test_add_column*
+ Table "public.test_add_column"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------------------------------------------
+ c1 | integer | | |
+ c2 | integer | | |
+ c3 | integer | | not null |
+ c4 | integer | | |
+ c5 | integer | | not null | nextval('test_add_column_c5_seq'::regclass)
+Indexes:
+ "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Check constraints:
+ "test_add_column_c5_check" CHECK (c5 > 8)
+Foreign-key constraints:
+ "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+ TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ Sequence "public.test_add_column_c5_seq"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer | 1 | 1 | 2147483647 | 1 | no | 1
+Owned by: public.test_add_column.c5
+
+ Index "public.test_add_column_pkey"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ c3 | integer | yes | c3
+primary key, btree, for table "public.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
+ Table "public.ataddindex"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+---------
+ f1 | bigint | | not null |
+Indexes:
+ "ataddindexi0" PRIMARY KEY, btree (f1)
+
+DROP TABLE ataddindex;
+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
+ Table "public.ataddindex"
+ Column | Type | Collation | Nullable | Default
+--------+------+-----------+----------+---------
+ f1 | text | | |
+Indexes:
+ "ataddindex_expr_excl" EXCLUDE USING btree ((f1 ~~ 'a'::text) WITH =)
+
+DROP TABLE ataddindex;
+CREATE TABLE ataddindex(id int, ref_id int);
+ALTER TABLE ataddindex
+ ADD PRIMARY KEY (id),
+ ADD FOREIGN KEY (ref_id) REFERENCES ataddindex;
+\d ataddindex
+ Table "public.ataddindex"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | not null |
+ ref_id | integer | | |
+Indexes:
+ "ataddindex_pkey" PRIMARY KEY, btree (id)
+Foreign-key constraints:
+ "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id)
+Referenced by:
+ TABLE "ataddindex" CONSTRAINT "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id)
+
+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
+ Table "public.ataddindex"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ ref_id | integer | | |
+Indexes:
+ "ataddindex_id_key" UNIQUE CONSTRAINT, btree (id)
+Foreign-key constraints:
+ "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id)
+Referenced by:
+ TABLE "ataddindex" CONSTRAINT "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id)
+
+DROP TABLE ataddindex;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR: cannot drop column "a" because it is part of the partition key of relation "partitioned"
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column "a" because it is part of the partition key of relation "partitioned"
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR: cannot drop column "b" because it is part of the partition key of relation "partitioned"
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR: cannot alter column "b" because it is part of the partition key of relation "partitioned"
+-- partitioned table cannot participate in regular inheritance
+CREATE TABLE nonpartitioned (
+ a int,
+ b int
+);
+ALTER TABLE partitioned INHERIT nonpartitioned;
+ERROR: cannot change inheritance of partitioned table
+ALTER TABLE nonpartitioned INHERIT partitioned;
+ERROR: cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+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');
+ERROR: table "unparted" is not partitioned
+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);
+ERROR: invalid bound specification for a list partition
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) T...
+ ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistent FOR VALUES IN (1);
+ERROR: relation "nonexistent" does not exist
+-- 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);
+ERROR: must be owner of table not_owned_by_me
+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);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ERROR: cannot attach inheritance child as partition
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+ERROR: cannot attach inheritance parent as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table child
+-- 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);
+ERROR: cannot attach a permanent relation as partition of temporary relation "temp_parted"
+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);
+ERROR: cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE: drop cascades to table fail_part
+-- 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);
+ERROR: table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL: The new partition may contain only the columns present in parent.
+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);
+ERROR: child table is missing column "b"
+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);
+ERROR: child table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "POSIX";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child table "fail_part" has different collation for column "b"
+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);
+ERROR: child table is missing constraint "check_a"
+-- 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);
+ERROR: child table "fail_part" has different definition for check constraint "check_a"
+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;
+ attislocal | attinhcount
+------------+-------------
+ f | 1
+ f | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+(1 row)
+
+-- 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);
+ERROR: partition "fail_part" would overlap partition "part_1"
+LINE 1: ...LE 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;
+ERROR: partition "fail_def_part" conflicts with existing default partition "def_part"
+LINE 1: ...ER 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);
+ERROR: partition constraint of relation "part_2" is violated by some row
+-- 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);
+ERROR: updated partition constraint for default partition "list_parted2_def" would be violated by some row
+-- 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);
+ERROR: partition constraint of relation "part1" is violated by some row
+-- 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;
+ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1"
+LINE 1: ...LTER 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);
+ERROR: updated partition constraint for default partition "partr_def1" would be violated by some row
+-- 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);
+ERROR: partition constraint of relation "part_5_a" is violated by some row
+-- 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;
+ tableoid | a | b
+---------------+---+---
+ part_7_a_null | 8 |
+ part_7_a_null | 9 | a
+(2 rows)
+
+ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+ERROR: partition constraint of relation "part_7_a_null" is violated by some row
+-- 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');
+ERROR: updated partition constraint for default partition "part5_def_p1" would be violated by some row
+-- 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);
+ERROR: "part_2" is already a partition
+-- check that circular inheritance is not allowed
+ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
+ERROR: circular inheritance not allowed
+DETAIL: "part_5" is already a child of "list_parted2".
+ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
+ERROR: circular inheritance not allowed
+DETAIL: "list_parted2" is already a child of "list_parted2".
+-- 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);
+ERROR: partition "fail_part" would overlap partition "hpart_1"
+LINE 1: ...hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODU...
+ ^
+ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 8, REMAINDER 0);
+ERROR: partition "fail_part" would overlap partition "hpart_1"
+LINE 1: ...hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODU...
+ ^
+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);
+ERROR: partition constraint of relation "hpart_2" is violated by some row
+-- 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);
+ERROR: partition constraint of relation "hpart_5_a" is violated by some row
+-- 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);
+ERROR: modulus for hash partition must be an integer value greater than zero
+ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 8, REMAINDER 8);
+ERROR: remainder for hash partition must be less than modulus
+ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 3, REMAINDER 2);
+ERROR: every hash partition modulus must be a factor of the next larger modulus
+DETAIL: The new modulus 3 is not a factor of 4, the modulus of existing partition "hpart_1".
+DROP TABLE fail_part;
+-- fails with incorrect object type
+CREATE VIEW at_v1 AS SELECT 1 as a;
+ALTER TABLE at_v1 ATTACH PARTITION dummy default;
+ERROR: "at_v1" is not a table or partitioned index
+DROP VIEW at_v1;
+--
+-- DETACH PARTITION
+--
+-- check that the table is partitioned at all
+CREATE TABLE regular_table (a int);
+ALTER TABLE regular_table DETACH PARTITION any_name;
+ERROR: table "regular_table" is not partitioned
+DROP TABLE regular_table;
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted2 DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+ALTER TABLE hash_parted DETACH PARTITION hpart_4;
+ERROR: relation "hpart_4" does not exist
+-- 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;
+ERROR: relation "not_a_part" is not a partition of relation "list_parted2"
+ALTER TABLE list_parted2 DETACH PARTITION part_1;
+ERROR: relation "part_1" is not a partition of relation "list_parted2"
+ALTER TABLE hash_parted DETACH PARTITION not_a_part;
+ERROR: relation "not_a_part" is not a partition of relation "hash_parted"
+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;
+ attinhcount | attislocal
+-------------+------------
+ 0 | t
+ 0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
+ coninhcount | conislocal
+-------------+------------
+ 0 | t
+(1 row)
+
+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;
+ a
+---
+(0 rows)
+
+DROP TABLE part_rp;
+-- concurrent detach
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE(a);
+CREATE TABLE part_rp PARTITION OF range_parted2 FOR VALUES FROM (0) to (100);
+BEGIN;
+-- doesn't work in a partition block
+ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
+ERROR: ALTER TABLE ... DETACH CONCURRENTLY cannot run inside a transaction block
+COMMIT;
+CREATE TABLE part_rpd PARTITION OF range_parted2 DEFAULT;
+-- doesn't work if there's a default partition
+ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
+ERROR: cannot detach partitions concurrently when a default partition exists
+-- doesn't work for the default partition
+ALTER TABLE range_parted2 DETACH PARTITION part_rpd CONCURRENTLY;
+ERROR: cannot detach partitions concurrently when a default partition exists
+DROP TABLE part_rpd;
+-- works fine
+ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
+\d+ range_parted2
+ Partitioned table "public.range_parted2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition key: RANGE (a)
+Number of partitions: 0
+
+-- constraint should be created
+\d part_rp
+ Table "public.part_rp"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Check constraints:
+ "part_rp_a_check" CHECK (a IS NOT NULL AND a >= 0 AND a < 100)
+
+CREATE TABLE part_rp100 PARTITION OF range_parted2 (CHECK (a>=123 AND a<133 AND a IS NOT NULL)) FOR VALUES FROM (100) to (200);
+ALTER TABLE range_parted2 DETACH PARTITION part_rp100 CONCURRENTLY;
+-- redundant constraint should not be created
+\d part_rp100
+ Table "public.part_rp100"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Check constraints:
+ "part_rp100_a_check" CHECK (a >= 123 AND a < 133 AND a IS NOT NULL)
+
+DROP TABLE range_parted2;
+-- 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;
+ERROR: column must be added to child tables too
+ALTER TABLE ONLY list_parted2 DROP COLUMN b;
+ERROR: cannot drop column from only the partitioned table when partitions exist
+HINT: Do not specify the ONLY keyword.
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_2 ADD COLUMN c text;
+ERROR: cannot add column to a partition
+ALTER TABLE part_2 DROP COLUMN b;
+ERROR: cannot drop inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_2 RENAME COLUMN b to c;
+ERROR: cannot rename inherited column "b"
+ALTER TABLE part_2 ALTER COLUMN b TYPE text;
+ERROR: cannot alter inherited column "b"
+-- 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;
+ERROR: constraint must be added to child tables too
+DETAIL: Column "b" of relation "part_2" is not already NOT NULL.
+HINT: Do not specify the ONLY keyword.
+ALTER TABLE ONLY list_parted2 ADD CONSTRAINT check_b CHECK (b <> 'zz');
+ERROR: constraint must be added to child tables too
+ALTER TABLE list_parted2 ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted2 ALTER b DROP NOT NULL;
+ERROR: cannot remove constraint from only the partitioned table when partitions exist
+HINT: Do not specify the ONLY keyword.
+ALTER TABLE list_parted2 ADD CONSTRAINT check_b CHECK (b <> 'zz');
+ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_b;
+ERROR: cannot remove constraint from only the partitioned table when partitions exist
+HINT: Do not specify the ONLY keyword.
+-- 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;
+ERROR: column "b" is marked NOT NULL in parent table
+ALTER TABLE part_2 DROP CONSTRAINT check_a2;
+ERROR: cannot drop inherited constraint "check_a2" of relation "part_2"
+-- 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;
+ERROR: cannot add NO INHERIT constraint to partitioned table "list_parted2"
+-- check that a partition cannot participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_2);
+ERROR: cannot inherit from partition "part_2"
+CREATE TABLE inh_test (LIKE part_2);
+ALTER TABLE inh_test INHERIT part_2;
+ERROR: cannot inherit from a partition
+ALTER TABLE part_2 INHERIT inh_test;
+ERROR: cannot change inheritance of a partition
+-- 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;
+ERROR: cannot drop column "b" because it is part of the partition key of relation "part_5"
+ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
+ERROR: cannot alter column "b" because it is part of the partition key of relation "part_5"
+-- dropping non-partition key columns should be allowed on the parent table.
+ALTER TABLE list_parted DROP COLUMN b;
+SELECT * FROM list_parted;
+ a
+---
+(0 rows)
+
+-- 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;
+ attrelid | attname | attnum
+----------+---------+--------
+ p | a | 1
+ p1 | a | 2
+ p11 | a | 4
+(3 rows)
+
+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);
+ERROR: partition constraint of relation "p11" is violated by some row
+-- 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);
+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;
+ERROR: partition constraint of relation "defpart_attach_test_d" is violated by some row
+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);
+ERROR: updated partition constraint for default partition "defpart_attach_test_d" would be violated by some row
+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
+ERROR: cannot attach a permanent relation as partition of temporary relation "temp_part_parent"
+alter table perm_part_parent attach partition temp_part_child default; -- error
+ERROR: cannot attach a temporary relation as partition of permanent relation "perm_part_parent"
+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);
+ERROR: cannot ALTER TABLE "tab_part_attach" because it is being used by active queries in this session
+CONTEXT: SQL statement "alter table tab_part_attach attach partition tab_part_attach_1 for values in (1)"
+PL/pgSQL function func_part_attach() line 4 at EXECUTE
+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);
+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;
+/* Test case for bug #16242 */
+-- We create a parent and child where the child has missing
+-- non-null attribute values, and arrange to pass them through
+-- tuple conversion from the child to the parent tupdesc
+create table bar1 (a integer, b integer not null default 1)
+ partition by range (a);
+create table bar2 (a integer);
+insert into bar2 values (1);
+alter table bar2 add column b integer not null default 1;
+-- (at this point bar2 contains tuple with natts=1)
+alter table bar1 attach partition bar2 default;
+-- this works:
+select * from bar1;
+ a | b
+---+---
+ 1 | 1
+(1 row)
+
+-- this exercises tuple conversion:
+create function xtrig()
+ returns trigger language plpgsql
+as $$
+ declare
+ r record;
+ begin
+ for r in select * from old loop
+ raise info 'a=%, b=%', r.a, r.b;
+ end loop;
+ return NULL;
+ end;
+$$;
+create trigger xtrig
+ after update on bar1
+ referencing old table as old
+ for each statement execute procedure xtrig();
+update bar1 set a = a + 1;
+INFO: a=1, b=1
+/* End test case for bug #16242 */
+/* Test case for bug #17409 */
+create table attbl (p1 int constraint pk_attbl primary key);
+create table atref (c1 int references attbl(p1));
+cluster attbl using pk_attbl;
+alter table attbl alter column p1 set data type bigint;
+alter table atref alter column c1 set data type bigint;
+drop table attbl, atref;
+create table attbl (p1 int constraint pk_attbl primary key);
+alter table attbl replica identity using index pk_attbl;
+create table atref (c1 int references attbl(p1));
+alter table attbl alter column p1 set data type bigint;
+alter table atref alter column c1 set data type bigint;
+drop table attbl, atref;
+/* End test case for bug #17409 */
+-- Test that ALTER TABLE rewrite preserves a clustered index
+-- for normal indexes and indexes on constraints.
+create table alttype_cluster (a int);
+alter table alttype_cluster add primary key (a);
+create index alttype_cluster_ind on alttype_cluster (a);
+alter table alttype_cluster cluster on alttype_cluster_ind;
+-- Normal index remains clustered.
+select indexrelid::regclass, indisclustered from pg_index
+ where indrelid = 'alttype_cluster'::regclass
+ order by indexrelid::regclass::text;
+ indexrelid | indisclustered
+----------------------+----------------
+ alttype_cluster_ind | t
+ alttype_cluster_pkey | f
+(2 rows)
+
+alter table alttype_cluster alter a type bigint;
+select indexrelid::regclass, indisclustered from pg_index
+ where indrelid = 'alttype_cluster'::regclass
+ order by indexrelid::regclass::text;
+ indexrelid | indisclustered
+----------------------+----------------
+ alttype_cluster_ind | t
+ alttype_cluster_pkey | f
+(2 rows)
+
+-- Constraint index remains clustered.
+alter table alttype_cluster cluster on alttype_cluster_pkey;
+select indexrelid::regclass, indisclustered from pg_index
+ where indrelid = 'alttype_cluster'::regclass
+ order by indexrelid::regclass::text;
+ indexrelid | indisclustered
+----------------------+----------------
+ alttype_cluster_ind | f
+ alttype_cluster_pkey | t
+(2 rows)
+
+alter table alttype_cluster alter a type int;
+select indexrelid::regclass, indisclustered from pg_index
+ where indrelid = 'alttype_cluster'::regclass
+ order by indexrelid::regclass::text;
+ indexrelid | indisclustered
+----------------------+----------------
+ alttype_cluster_ind | f
+ alttype_cluster_pkey | t
+(2 rows)
+
+drop table alttype_cluster;
+--
+-- 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);
+ERROR: new row for relation "attach_parted_part1" violates partition constraint
+DETAIL: Failing row contains (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/expected/amutils.out b/src/test/regress/expected/amutils.out
new file mode 100644
index 0000000..7ab6113
--- /dev/null
+++ b/src/test/regress/expected/amutils.out
@@ -0,0 +1,254 @@
+--
+-- Test index AM property-reporting functions
+--
+select prop,
+ pg_indexam_has_property(a.oid, prop) as "AM",
+ pg_index_has_property('onek_hundred'::regclass, prop) as "Index",
+ pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as "Column"
+ from pg_am a,
+ unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
+ 'orderable', 'distance_orderable', 'returnable',
+ 'search_array', 'search_nulls',
+ 'clusterable', 'index_scan', 'bitmap_scan',
+ 'backward_scan',
+ 'can_order', 'can_unique', 'can_multi_col',
+ 'can_exclude', 'can_include',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ where a.amname = 'btree'
+ order by ord;
+ prop | AM | Index | Column
+--------------------+----+-------+--------
+ asc | | | t
+ desc | | | f
+ nulls_first | | | f
+ nulls_last | | | t
+ orderable | | | t
+ distance_orderable | | | f
+ returnable | | | t
+ search_array | | | t
+ search_nulls | | | t
+ clusterable | | t |
+ index_scan | | t |
+ bitmap_scan | | t |
+ backward_scan | | t |
+ can_order | t | |
+ can_unique | t | |
+ can_multi_col | t | |
+ can_exclude | t | |
+ can_include | t | |
+ bogus | | |
+(19 rows)
+
+select prop,
+ pg_indexam_has_property(a.oid, prop) as "AM",
+ pg_index_has_property('gcircleind'::regclass, prop) as "Index",
+ pg_index_column_has_property('gcircleind'::regclass, 1, prop) as "Column"
+ from pg_am a,
+ unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
+ 'orderable', 'distance_orderable', 'returnable',
+ 'search_array', 'search_nulls',
+ 'clusterable', 'index_scan', 'bitmap_scan',
+ 'backward_scan',
+ 'can_order', 'can_unique', 'can_multi_col',
+ 'can_exclude', 'can_include',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ where a.amname = 'gist'
+ order by ord;
+ prop | AM | Index | Column
+--------------------+----+-------+--------
+ asc | | | f
+ desc | | | f
+ nulls_first | | | f
+ nulls_last | | | f
+ orderable | | | f
+ distance_orderable | | | t
+ returnable | | | f
+ search_array | | | f
+ search_nulls | | | t
+ clusterable | | t |
+ index_scan | | t |
+ bitmap_scan | | t |
+ backward_scan | | f |
+ can_order | f | |
+ can_unique | f | |
+ can_multi_col | t | |
+ can_exclude | t | |
+ can_include | t | |
+ bogus | | |
+(19 rows)
+
+select prop,
+ pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree,
+ pg_index_column_has_property('hash_i4_index'::regclass, 1, prop) as hash,
+ pg_index_column_has_property('gcircleind'::regclass, 1, prop) as gist,
+ pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist_radix,
+ pg_index_column_has_property('sp_quad_ind'::regclass, 1, prop) as spgist_quad,
+ pg_index_column_has_property('botharrayidx'::regclass, 1, prop) as gin,
+ pg_index_column_has_property('brinidx'::regclass, 1, prop) as brin
+ from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
+ 'orderable', 'distance_orderable', 'returnable',
+ 'search_array', 'search_nulls',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ order by ord;
+ prop | btree | hash | gist | spgist_radix | spgist_quad | gin | brin
+--------------------+-------+------+------+--------------+-------------+-----+------
+ asc | t | f | f | f | f | f | f
+ desc | f | f | f | f | f | f | f
+ nulls_first | f | f | f | f | f | f | f
+ nulls_last | t | f | f | f | f | f | f
+ orderable | t | f | f | f | f | f | f
+ distance_orderable | f | f | t | f | t | f | f
+ returnable | t | f | f | t | t | f | f
+ search_array | t | f | f | f | f | f | f
+ search_nulls | t | f | t | t | t | f | t
+ bogus | | | | | | |
+(10 rows)
+
+select prop,
+ pg_index_has_property('onek_hundred'::regclass, prop) as btree,
+ pg_index_has_property('hash_i4_index'::regclass, prop) as hash,
+ pg_index_has_property('gcircleind'::regclass, prop) as gist,
+ pg_index_has_property('sp_radix_ind'::regclass, prop) as spgist,
+ pg_index_has_property('botharrayidx'::regclass, prop) as gin,
+ pg_index_has_property('brinidx'::regclass, prop) as brin
+ from unnest(array['clusterable', 'index_scan', 'bitmap_scan',
+ 'backward_scan',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ order by ord;
+ prop | btree | hash | gist | spgist | gin | brin
+---------------+-------+------+------+--------+-----+------
+ clusterable | t | f | t | f | f | f
+ index_scan | t | t | t | t | f | f
+ bitmap_scan | t | t | t | t | t | t
+ backward_scan | t | t | f | f | f | f
+ bogus | | | | | |
+(5 rows)
+
+select amname, prop, pg_indexam_has_property(a.oid, prop) as p
+ from pg_am a,
+ unnest(array['can_order', 'can_unique', 'can_multi_col',
+ 'can_exclude', 'can_include', 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ where amtype = 'i'
+ order by amname, ord;
+ amname | prop | p
+--------+---------------+---
+ brin | can_order | f
+ brin | can_unique | f
+ brin | can_multi_col | t
+ brin | can_exclude | f
+ brin | can_include | f
+ brin | bogus |
+ btree | can_order | t
+ btree | can_unique | t
+ btree | can_multi_col | t
+ btree | can_exclude | t
+ btree | can_include | t
+ btree | bogus |
+ gin | can_order | f
+ gin | can_unique | f
+ gin | can_multi_col | t
+ gin | can_exclude | f
+ gin | can_include | f
+ gin | bogus |
+ gist | can_order | f
+ gist | can_unique | f
+ gist | can_multi_col | t
+ gist | can_exclude | t
+ gist | can_include | t
+ gist | bogus |
+ hash | can_order | f
+ hash | can_unique | f
+ hash | can_multi_col | f
+ hash | can_exclude | t
+ hash | can_include | f
+ hash | bogus |
+ spgist | can_order | f
+ spgist | can_unique | f
+ spgist | can_multi_col | f
+ spgist | can_exclude | t
+ spgist | can_include | t
+ spgist | bogus |
+(36 rows)
+
+--
+-- additional checks for pg_index_column_has_property
+--
+CREATE TEMP TABLE foo (f1 int, f2 int, f3 int, f4 int);
+CREATE INDEX fooindex ON foo (f1 desc, f2 asc, f3 nulls first, f4 nulls last);
+select col, prop, pg_index_column_has_property(o, col, prop)
+ from (values ('fooindex'::regclass)) v1(o),
+ (values (1,'orderable'),(2,'asc'),(3,'desc'),
+ (4,'nulls_first'),(5,'nulls_last'),
+ (6, 'bogus')) v2(idx,prop),
+ generate_series(1,4) col
+ order by col, idx;
+ col | prop | pg_index_column_has_property
+-----+-------------+------------------------------
+ 1 | orderable | t
+ 1 | asc | f
+ 1 | desc | t
+ 1 | nulls_first | t
+ 1 | nulls_last | f
+ 1 | bogus |
+ 2 | orderable | t
+ 2 | asc | t
+ 2 | desc | f
+ 2 | nulls_first | f
+ 2 | nulls_last | t
+ 2 | bogus |
+ 3 | orderable | t
+ 3 | asc | t
+ 3 | desc | f
+ 3 | nulls_first | t
+ 3 | nulls_last | f
+ 3 | bogus |
+ 4 | orderable | t
+ 4 | asc | t
+ 4 | desc | f
+ 4 | nulls_first | f
+ 4 | nulls_last | t
+ 4 | bogus |
+(24 rows)
+
+CREATE INDEX foocover ON foo (f1) INCLUDE (f2,f3);
+select col, prop, pg_index_column_has_property(o, col, prop)
+ from (values ('foocover'::regclass)) v1(o),
+ (values (1,'orderable'),(2,'asc'),(3,'desc'),
+ (4,'nulls_first'),(5,'nulls_last'),
+ (6,'distance_orderable'),(7,'returnable'),
+ (8, 'bogus')) v2(idx,prop),
+ generate_series(1,3) col
+ order by col, idx;
+ col | prop | pg_index_column_has_property
+-----+--------------------+------------------------------
+ 1 | orderable | t
+ 1 | asc | t
+ 1 | desc | f
+ 1 | nulls_first | f
+ 1 | nulls_last | t
+ 1 | distance_orderable | f
+ 1 | returnable | t
+ 1 | bogus |
+ 2 | orderable | f
+ 2 | asc |
+ 2 | desc |
+ 2 | nulls_first |
+ 2 | nulls_last |
+ 2 | distance_orderable | f
+ 2 | returnable | t
+ 2 | bogus |
+ 3 | orderable | f
+ 3 | asc |
+ 3 | desc |
+ 3 | nulls_first |
+ 3 | nulls_last |
+ 3 | distance_orderable | f
+ 3 | returnable | t
+ 3 | bogus |
+(24 rows)
+
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
new file mode 100644
index 0000000..4923cf3
--- /dev/null
+++ b/src/test/regress/expected/arrays.out
@@ -0,0 +1,2436 @@
+--
+-- ARRAYS
+--
+CREATE TABLE arrtest (
+ a int2[],
+ b int4[][][],
+ c name[],
+ d text[][],
+ e float8[],
+ f char(5)[],
+ g varchar(5)[]
+);
+--
+-- 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"}');
+ERROR: value too long for type character(5)
+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
+ERROR: subscripted assignment to "b" requires type integer but expression is of type timestamp with time zone
+LINE 1: INSERT INTO arrtest (b[2]) VALUES(now());
+ ^
+HINT: You will need to rewrite or cast the expression.
+INSERT INTO arrtest (b[1:2]) VALUES(now()); -- error, type mismatch
+ERROR: subscripted assignment to "b" requires type integer[] but expression is of type timestamp with time zone
+LINE 1: INSERT INTO arrtest (b[1:2]) VALUES(now());
+ ^
+HINT: You will need to rewrite or cast the expression.
+SELECT * FROM arrtest;
+ a | b | c | d | e | f | g
+-------------+-----------------+-----------+---------------+-----------------+-----------------+-------------
+ {1,2,3,4,5} | {{{0,0},{1,2}}} | {} | {} | [0:1]={1.1,2.2} | {} | {}
+ {11,12,23} | {{3,4},{4,5}} | {foobar} | {{elt1,elt2}} | {3.4,6.7} | {"abc ",abcde} | {abc,abcde}
+ {} | {3,4} | {foo,bar} | {bar,foo} | | |
+(3 rows)
+
+SELECT arrtest.a[1],
+ arrtest.b[1][1][1],
+ arrtest.c[1],
+ arrtest.d[1][1],
+ arrtest.e[0]
+ FROM arrtest;
+ a | b | c | d | e
+----+---+--------+------+-----
+ 1 | 0 | | | 1.1
+ 11 | | foobar | elt1 |
+ | | foo | |
+(3 rows)
+
+SELECT a[1], b[1][1][1], c[1], d[1][1], e[0]
+ FROM arrtest;
+ a | b | c | d | e
+----+---+--------+------+-----
+ 1 | 0 | | | 1.1
+ 11 | | foobar | elt1 |
+ | | foo | |
+(3 rows)
+
+SELECT a[1:3],
+ b[1:1][1:2][1:2],
+ c[1:2],
+ d[1:1][1:2]
+ FROM arrtest;
+ a | b | c | d
+------------+-----------------+-----------+---------------
+ {1,2,3} | {{{0,0},{1,2}}} | {} | {}
+ {11,12,23} | {} | {foobar} | {{elt1,elt2}}
+ {} | {} | {foo,bar} | {}
+(3 rows)
+
+SELECT array_ndims(a) AS a,array_ndims(b) AS b,array_ndims(c) AS c
+ FROM arrtest;
+ a | b | c
+---+---+---
+ 1 | 3 |
+ 1 | 2 | 1
+ | 1 | 1
+(3 rows)
+
+SELECT array_dims(a) AS a,array_dims(b) AS b,array_dims(c) AS c
+ FROM arrtest;
+ a | b | c
+-------+-----------------+-------
+ [1:5] | [1:1][1:2][1:2] |
+ [1:3] | [1:2][1:2] | [1:1]
+ | [1:2] | [1:2]
+(3 rows)
+
+-- returns nothing
+SELECT *
+ FROM arrtest
+ WHERE a[1] < 5 and
+ c = '{"foobar"}'::_name;
+ a | b | c | d | e | f | g
+---+---+---+---+---+---+---
+(0 rows)
+
+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;
+ a | b | c
+---------------+-----------------------+-------------------
+ {16,25,3,4,5} | {{{113,142},{1,147}}} | {}
+ {} | {3,4} | {foo,new_word}
+ {16,25,23} | {{3,4},{4,5}} | {foobar,new_word}
+(3 rows)
+
+SELECT a[1:3],
+ b[1:1][1:2][1:2],
+ c[1:2],
+ d[1:1][2:2]
+ FROM arrtest;
+ a | b | c | d
+------------+-----------------------+-------------------+----------
+ {16,25,3} | {{{113,142},{1,147}}} | {} | {}
+ {} | {} | {foo,new_word} | {}
+ {16,25,23} | {} | {foobar,new_word} | {{elt2}}
+(3 rows)
+
+SELECT b[1:1][2][2],
+ d[1:1][2]
+ FROM arrtest;
+ b | d
+-----------------------+---------------
+ {{{113,142},{1,147}}} | {}
+ {} | {}
+ {} | {{elt1,elt2}}
+(3 rows)
+
+INSERT INTO arrtest(a) VALUES('{1,null,3}');
+SELECT a FROM arrtest;
+ a
+---------------
+ {16,25,3,4,5}
+ {}
+ {16,25,23}
+ {1,NULL,3}
+(4 rows)
+
+UPDATE arrtest SET a[4] = NULL WHERE a[2] IS NULL;
+SELECT a FROM arrtest WHERE a[2] IS NULL;
+ a
+-----------------
+ [4:4]={NULL}
+ {1,NULL,3,NULL}
+(2 rows)
+
+DELETE FROM arrtest WHERE a[2] IS NULL AND b IS NULL;
+SELECT a,b,c FROM arrtest;
+ a | b | c
+---------------+-----------------------+-------------------
+ {16,25,3,4,5} | {{{113,142},{1,147}}} | {}
+ {16,25,23} | {{3,4},{4,5}} | {foobar,new_word}
+ [4:4]={NULL} | {3,4} | {foo,new_word}
+(3 rows)
+
+-- test mixed slice/scalar subscripting
+select '{{1,2,3},{4,5,6},{7,8,9}}'::int[];
+ int4
+---------------------------
+ {{1,2,3},{4,5,6},{7,8,9}}
+(1 row)
+
+select ('{{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2];
+ int4
+---------------
+ {{1,2},{4,5}}
+(1 row)
+
+select '[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[];
+ int4
+--------------------------------------
+ [0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}
+(1 row)
+
+select ('[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2];
+ int4
+---------------
+ {{5,6},{8,9}}
+(1 row)
+
+--
+-- check subscription corner cases
+--
+-- More subscripts than MAXDIM (6)
+SELECT ('{}'::int[])[1][2][3][4][5][6][7];
+ERROR: number of array dimensions (7) exceeds the maximum allowed (6)
+-- NULL index yields NULL when selecting
+SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][NULL][1];
+ int4
+------
+
+(1 row)
+
+SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][NULL:1][1];
+ int4
+------
+
+(1 row)
+
+SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][1:NULL][1];
+ int4
+------
+
+(1 row)
+
+-- NULL index in assignment is an error
+UPDATE arrtest
+ SET c[NULL] = '{"can''t assign"}'
+ WHERE array_dims(c) is not null;
+ERROR: array subscript in assignment must not be null
+UPDATE arrtest
+ SET c[NULL:1] = '{"can''t assign"}'
+ WHERE array_dims(c) is not null;
+ERROR: array subscript in assignment must not be null
+UPDATE arrtest
+ SET c[1:NULL] = '{"can''t assign"}'
+ WHERE array_dims(c) is not null;
+ERROR: array subscript in assignment must not be null
+-- Un-subscriptable type
+SELECT (now())[1];
+ERROR: cannot subscript type timestamp with time zone because it does not support subscripting
+LINE 1: 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;
+ a | b
+-------------------+--------------------------------------
+ {1,2,3,4,5} | {{1,2,3},{4,5,6},{7,8,9}}
+ [0:4]={1,2,3,4,5} | [0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}
+(2 rows)
+
+SELECT a[:3], b[:2][:2] FROM arrtest_s;
+ a | b
+-----------+---------------------------
+ {1,2,3} | {{1,2},{4,5}}
+ {1,2,3,4} | {{1,2,3},{4,5,6},{7,8,9}}
+(2 rows)
+
+SELECT a[2:], b[2:][2:] FROM arrtest_s;
+ a | b
+-----------+---------------
+ {2,3,4,5} | {{5,6},{8,9}}
+ {3,4,5} | {{9}}
+(2 rows)
+
+SELECT a[:], b[:] FROM arrtest_s;
+ a | b
+-------------+---------------------------
+ {1,2,3,4,5} | {{1,2,3},{4,5,6},{7,8,9}}
+ {1,2,3,4,5} | {{1,2,3},{4,5,6},{7,8,9}}
+(2 rows)
+
+-- 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;
+ a | b
+-------------------+--------------------------------------
+ [0:4]={1,2,3,4,5} | [0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}
+ {11,12,13,4,5} | {{11,12,3},{14,15,6},{7,8,9}}
+(2 rows)
+
+UPDATE arrtest_s SET a[3:] = '{23, 24, 25}', b[2:][2:] = '{{25,26}, {28,29}}';
+SELECT * FROM arrtest_s;
+ a | b
+---------------------+---------------------------------------
+ [0:4]={1,2,3,23,24} | [0:2][0:2]={{1,2,3},{4,5,6},{7,8,25}}
+ {11,12,23,24,25} | {{11,12,3},{14,25,26},{7,28,29}}
+(2 rows)
+
+UPDATE arrtest_s SET a[:] = '{11, 12, 13, 14, 15}';
+SELECT * FROM arrtest_s;
+ a | b
+------------------------+---------------------------------------
+ [0:4]={11,12,13,14,15} | [0:2][0:2]={{1,2,3},{4,5,6},{7,8,25}}
+ {11,12,13,14,15} | {{11,12,3},{14,25,26},{7,28,29}}
+(2 rows)
+
+UPDATE arrtest_s SET a[:] = '{23, 24, 25}'; -- fail, too small
+ERROR: source array too small
+INSERT INTO arrtest_s VALUES(NULL, NULL);
+UPDATE arrtest_s SET a[:] = '{11, 12, 13, 14, 15}'; -- fail, no good with null
+ERROR: array slice subscript must provide both boundaries
+DETAIL: When assigning to a slice of an empty array value, slice boundaries must be fully specified.
+-- check with fixed-length-array type, such as point
+SELECT f1[0:1] FROM POINT_TBL;
+ERROR: slices of fixed-length arrays not implemented
+SELECT f1[0:] FROM POINT_TBL;
+ERROR: slices of fixed-length arrays not implemented
+SELECT f1[:1] FROM POINT_TBL;
+ERROR: slices of fixed-length arrays not implemented
+SELECT f1[:] FROM POINT_TBL;
+ERROR: slices of fixed-length arrays not implemented
+-- 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 *;
+ f1
+----
+
+(1 row)
+
+INSERT INTO point_tbl(f1[0]) VALUES(0) RETURNING *;
+ f1
+----
+
+(1 row)
+
+-- NULL assignments get ignored
+UPDATE point_tbl SET f1[0] = NULL WHERE f1::text = '(10,10)'::point::text RETURNING *;
+ f1
+---------
+ (10,10)
+(1 row)
+
+-- but non-NULL subscript assignments work
+UPDATE point_tbl SET f1[0] = -10, f1[1] = -10 WHERE f1::text = '(10,10)'::point::text RETURNING *;
+ f1
+-----------
+ (-10,-10)
+(1 row)
+
+-- but not to expand the range
+UPDATE point_tbl SET f1[3] = 10 WHERE f1::text = '(-10,-10)'::point::text RETURNING *;
+ERROR: array subscript out of range
+--
+-- 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;
+ i | t
+--------------+---------------------
+ {1,2,NULL,4} | {one,two,NULL,four}
+(1 row)
+
+update arrtest1 set i[2] = 22, t[2] = 'twenty-two';
+select * from arrtest1;
+ i | t
+---------------+----------------------------
+ {1,22,NULL,4} | {one,twenty-two,NULL,four}
+(1 row)
+
+update arrtest1 set i[5] = 5, t[5] = 'five';
+select * from arrtest1;
+ i | t
+-----------------+---------------------------------
+ {1,22,NULL,4,5} | {one,twenty-two,NULL,four,five}
+(1 row)
+
+update arrtest1 set i[8] = 8, t[8] = 'eight';
+select * from arrtest1;
+ i | t
+-----------------------------+-------------------------------------------------
+ {1,22,NULL,4,5,NULL,NULL,8} | {one,twenty-two,NULL,four,five,NULL,NULL,eight}
+(1 row)
+
+update arrtest1 set i[0] = 0, t[0] = 'zero';
+select * from arrtest1;
+ i | t
+-------------------------------------+------------------------------------------------------------
+ [0:8]={0,1,22,NULL,4,5,NULL,NULL,8} | [0:8]={zero,one,twenty-two,NULL,four,five,NULL,NULL,eight}
+(1 row)
+
+update arrtest1 set i[-3] = -3, t[-3] = 'minus-three';
+select * from arrtest1;
+ i | t
+---------------------------------------------------+-----------------------------------------------------------------------------------
+ [-3:8]={-3,NULL,NULL,0,1,22,NULL,4,5,NULL,NULL,8} | [-3:8]={minus-three,NULL,NULL,zero,one,twenty-two,NULL,four,five,NULL,NULL,eight}
+(1 row)
+
+update arrtest1 set i[0:2] = array[10,11,12], t[0:2] = array['ten','eleven','twelve'];
+select * from arrtest1;
+ i | t
+-----------------------------------------------------+---------------------------------------------------------------------------------
+ [-3:8]={-3,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,8} | [-3:8]={minus-three,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,eight}
+(1 row)
+
+update arrtest1 set i[8:10] = array[18,null,20], t[8:10] = array['p18',null,'p20'];
+select * from arrtest1;
+ i | t
+---------------------------------------------------------------+-----------------------------------------------------------------------------------------
+ [-3:10]={-3,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20} | [-3:10]={minus-three,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20}
+(1 row)
+
+update arrtest1 set i[11:12] = array[null,22], t[11:12] = array[null,'p22'];
+select * from arrtest1;
+ i | t
+-----------------------------------------------------------------------+--------------------------------------------------------------------------------------------------
+ [-3:12]={-3,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22} | [-3:12]={minus-three,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22}
+(1 row)
+
+update arrtest1 set i[15:16] = array[null,26], t[15:16] = array[null,'p26'];
+select * from arrtest1;
+ i | t
+-----------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------
+ [-3:16]={-3,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22,NULL,NULL,NULL,26} | [-3:16]={minus-three,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22,NULL,NULL,NULL,p26}
+(1 row)
+
+update arrtest1 set i[-5:-3] = array[-15,-14,-13], t[-5:-3] = array['m15','m14','m13'];
+select * from arrtest1;
+ i | t
+--------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------
+ [-5:16]={-15,-14,-13,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22,NULL,NULL,NULL,26} | [-5:16]={m15,m14,m13,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22,NULL,NULL,NULL,p26}
+(1 row)
+
+update arrtest1 set i[-7:-6] = array[-17,null], t[-7:-6] = array['m17',null];
+select * from arrtest1;
+ i | t
+-----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------
+ [-7:16]={-17,NULL,-15,-14,-13,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22,NULL,NULL,NULL,26} | [-7:16]={m17,NULL,m15,m14,m13,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22,NULL,NULL,NULL,p26}
+(1 row)
+
+update arrtest1 set i[-12:-10] = array[-22,null,-20], t[-12:-10] = array['m22',null,'m20'];
+select * from arrtest1;
+ i | t
+-----------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------
+ [-12:16]={-22,NULL,-20,NULL,NULL,-17,NULL,-15,-14,-13,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22,NULL,NULL,NULL,26} | [-12:16]={m22,NULL,m20,NULL,NULL,m17,NULL,m15,m14,m13,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22,NULL,NULL,NULL,p26}
+(1 row)
+
+delete from arrtest1;
+insert into arrtest1 values(array[1,2,null,4], array['one','two',null,'four']);
+select * from arrtest1;
+ i | t
+--------------+---------------------
+ {1,2,NULL,4} | {one,two,NULL,four}
+(1 row)
+
+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;
+ i | t
+------------------------+----------------------------
+ [0:5]={0,1,2,NULL,4,5} | [0:5]={z,p1,p2,NULL,p4,p5}
+(1 row)
+
+--
+-- 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;
+ 131 | 221
+-----+-----
+ 131 | 221
+(1 row)
+
+SELECT ARRAY[[[[[['hello'],['world']]]]]];
+ array
+---------------------------
+ {{{{{{hello},{world}}}}}}
+(1 row)
+
+SELECT ARRAY[ARRAY['hello'],ARRAY['world']];
+ array
+-------------------
+ {{hello},{world}}
+(1 row)
+
+SELECT ARRAY(select f2 from arrtest_f order by f2) AS "ARRAY";
+ ARRAY
+-----------------------------------------------
+ {1.15,1.15,1.18,1.21,1.24,1.26,1.26,1.3,1.32}
+(1 row)
+
+-- with nulls
+SELECT '{1,null,3}'::int[];
+ int4
+------------
+ {1,NULL,3}
+(1 row)
+
+SELECT ARRAY[1,NULL,3];
+ array
+------------
+ {1,NULL,3}
+(1 row)
+
+-- functions
+SELECT array_append(array[42], 6) AS "{42,6}";
+ {42,6}
+--------
+ {42,6}
+(1 row)
+
+SELECT array_prepend(6, array[42]) AS "{6,42}";
+ {6,42}
+--------
+ {6,42}
+(1 row)
+
+SELECT array_cat(ARRAY[1,2], ARRAY[3,4]) AS "{1,2,3,4}";
+ {1,2,3,4}
+-----------
+ {1,2,3,4}
+(1 row)
+
+SELECT array_cat(ARRAY[1,2], ARRAY[[3,4],[5,6]]) AS "{{1,2},{3,4},{5,6}}";
+ {{1,2},{3,4},{5,6}}
+---------------------
+ {{1,2},{3,4},{5,6}}
+(1 row)
+
+SELECT array_cat(ARRAY[[3,4],[5,6]], ARRAY[1,2]) AS "{{3,4},{5,6},{1,2}}";
+ {{3,4},{5,6},{1,2}}
+---------------------
+ {{3,4},{5,6},{1,2}}
+(1 row)
+
+SELECT array_position(ARRAY[1,2,3,4,5], 4);
+ array_position
+----------------
+ 4
+(1 row)
+
+SELECT array_position(ARRAY[5,3,4,2,1], 4);
+ array_position
+----------------
+ 3
+(1 row)
+
+SELECT array_position(ARRAY[[1,2],[3,4]], 3);
+ERROR: searching for elements in multidimensional arrays is not supported
+SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'mon');
+ array_position
+----------------
+ 2
+(1 row)
+
+SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'sat');
+ array_position
+----------------
+ 7
+(1 row)
+
+SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], NULL);
+ array_position
+----------------
+
+(1 row)
+
+SELECT array_position(ARRAY['sun','mon','tue','wed','thu',NULL,'fri','sat'], NULL);
+ array_position
+----------------
+ 6
+(1 row)
+
+SELECT array_position(ARRAY['sun','mon','tue','wed','thu',NULL,'fri','sat'], 'sat');
+ array_position
+----------------
+ 8
+(1 row)
+
+SELECT array_positions(NULL, 10);
+ array_positions
+-----------------
+
+(1 row)
+
+SELECT array_positions(NULL, NULL::int);
+ array_positions
+-----------------
+
+(1 row)
+
+SELECT array_positions(ARRAY[1,2,3,4,5,6,1,2,3,4,5,6], 4);
+ array_positions
+-----------------
+ {4,10}
+(1 row)
+
+SELECT array_positions(ARRAY[[1,2],[3,4]], 4);
+ERROR: searching for elements in multidimensional arrays is not supported
+SELECT array_positions(ARRAY[1,2,3,4,5,6,1,2,3,4,5,6], NULL);
+ array_positions
+-----------------
+ {}
+(1 row)
+
+SELECT array_positions(ARRAY[1,2,3,NULL,5,6,1,2,3,NULL,5,6], NULL);
+ array_positions
+-----------------
+ {4,10}
+(1 row)
+
+SELECT array_length(array_positions(ARRAY(SELECT 'AAAAAAAAAAAAAAAAAAAAAAAAA'::text || i % 10
+ FROM generate_series(1,100) g(i)),
+ 'AAAAAAAAAAAAAAAAAAAAAAAAA5'), 1);
+ array_length
+--------------
+ 10
+(1 row)
+
+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;
+NOTICE: 2
+NOTICE: 4
+NOTICE: 7
+SELECT array_position('[2:4]={1,2,3}'::int[], 1);
+ array_position
+----------------
+ 2
+(1 row)
+
+SELECT array_positions('[2:4]={1,2,3}'::int[], 1);
+ array_positions
+-----------------
+ {2}
+(1 row)
+
+SELECT
+ array_position(ids, (1, 1)),
+ array_positions(ids, (1, 1))
+ FROM
+(VALUES
+ (ARRAY[(0, 0), (1, 1)]),
+ (ARRAY[(1, 1)])
+) AS f (ids);
+ array_position | array_positions
+----------------+-----------------
+ 2 | {2}
+ 1 | {1}
+(2 rows)
+
+-- operators
+SELECT a FROM arrtest WHERE b = ARRAY[[[113,142],[1,147]]];
+ a
+---------------
+ {16,25,3,4,5}
+(1 row)
+
+SELECT NOT ARRAY[1.1,1.2,1.3] = ARRAY[1.1,1.2,1.3] AS "FALSE";
+ FALSE
+-------
+ f
+(1 row)
+
+SELECT ARRAY[1,2] || 3 AS "{1,2,3}";
+ {1,2,3}
+---------
+ {1,2,3}
+(1 row)
+
+SELECT 0 || ARRAY[1,2] AS "{0,1,2}";
+ {0,1,2}
+---------
+ {0,1,2}
+(1 row)
+
+SELECT ARRAY[1,2] || ARRAY[3,4] AS "{1,2,3,4}";
+ {1,2,3,4}
+-----------
+ {1,2,3,4}
+(1 row)
+
+SELECT ARRAY[[['hello','world']]] || ARRAY[[['happy','birthday']]] AS "ARRAY";
+ ARRAY
+--------------------------------------
+ {{{hello,world}},{{happy,birthday}}}
+(1 row)
+
+SELECT ARRAY[[1,2],[3,4]] || ARRAY[5,6] AS "{{1,2},{3,4},{5,6}}";
+ {{1,2},{3,4},{5,6}}
+---------------------
+ {{1,2},{3,4},{5,6}}
+(1 row)
+
+SELECT ARRAY[0,0] || ARRAY[1,1] || ARRAY[2,2] AS "{0,0,1,1,2,2}";
+ {0,0,1,1,2,2}
+---------------
+ {0,0,1,1,2,2}
+(1 row)
+
+SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
+ {0,1,2,3}
+-----------
+ {0,1,2,3}
+(1 row)
+
+SELECT ARRAY[1.1] || ARRAY[2,3,4];
+ ?column?
+-------------
+ {1.1,2,3,4}
+(1 row)
+
+SELECT array_agg(x) || array_agg(x) FROM (VALUES (ROW(1,2)), (ROW(3,4))) v(x);
+ ?column?
+-----------------------------------
+ {"(1,2)","(3,4)","(1,2)","(3,4)"}
+(1 row)
+
+SELECT ROW(1,2) || array_agg(x) FROM (VALUES (ROW(3,4)), (ROW(5,6))) v(x);
+ ?column?
+---------------------------
+ {"(1,2)","(3,4)","(5,6)"}
+(1 row)
+
+SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(11 rows)
+
+SELECT * FROM array_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------+----------------------------------------------------------------------------------------------------------------------------
+ 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 101 | {} | {}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE i = '{}' ORDER BY seqno;
+ seqno | i | t
+-------+----+----
+ 101 | {} | {}
+(1 row)
+
+SELECT * FROM array_op_test WHERE i @> '{}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 1 | {92,75,71,52,64,83} | {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038}
+ 2 | {3,6} | {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793}
+ 3 | {37,64,95,43,3,41,13,30,11,43} | {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246}
+ 4 | {71,39,99,55,33,75,45} | {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557}
+ 5 | {50,42,77,50,4} | {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104}
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 7 | {12,51,88,64,8} | {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946}
+ 8 | {60,84} | {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407}
+ 9 | {56,52,35,27,80,44,81,22} | {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000}
+ 10 | {71,5,45} | {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249}
+ 11 | {41,86,74,48,22,74,47,50} | {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 13 | {3,52,34,23} | {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658}
+ 14 | {78,57,19} | {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 16 | {14,63,85,11} | {AAAAAA66777}
+ 17 | {7,10,81,85} | {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356}
+ 18 | {1} | {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 20 | {72,89,70,51,54,37,8,49,79} | {AAAAAA58494}
+ 21 | {2,8,65,10,5,79,43} | {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420}
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 23 | {40,90,5,38,72,40,30,10,43,55} | {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562}
+ 24 | {94,61,99,35,48} | {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219}
+ 25 | {31,1,10,11,27,79,38} | {AAAAAAAAAAAAAAAAAA59334,45449}
+ 26 | {71,10,9,69,75} | {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009}
+ 27 | {94} | {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254}
+ 28 | {14,33,6,34,14} | {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601}
+ 29 | {39,21} | {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194}
+ 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+ 31 | {80,24,18,21,54} | {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938}
+ 32 | {58,79,82,80,67,75,98,10,41} | {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533}
+ 33 | {74,73} | {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796}
+ 34 | {70,45} | {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242}
+ 35 | {23,40} | {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084}
+ 36 | {79,82,14,52,30,5,79} | {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598}
+ 37 | {53,11,81,39,3,78,58,64,74} | {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611}
+ 38 | {59,5,4,95,28} | {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387}
+ 39 | {82,43,99,16,74} | {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620}
+ 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623}
+ 41 | {19,26,63,12,93,73,27,94} | {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666}
+ 42 | {15,76,82,75,8,91} | {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587}
+ 43 | {39,87,91,97,79,28} | {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946}
+ 44 | {40,58,68,29,54} | {AAAAAAA81898,AAAAAA66777,AAAAAA98232}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 46 | {53,24} | {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621}
+ 47 | {98,23,64,12,75,61} | {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466}
+ 48 | {76,14} | {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037}
+ 49 | {56,5,54,37,49} | {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587}
+ 50 | {20,12,37,64,93} | {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955}
+ 51 | {47} | {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452}
+ 52 | {89,0} | {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 54 | {70,47} | {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322}
+ 55 | {47,79,47,64,72,25,71,24,93} | {AAAAAAAAAAAAAAAAAA55796,AAAAA62737}
+ 56 | {33,7,60,54,93,90,77,85,39} | {AAAAAAAAAAAAAAAAAA32918,AA42406}
+ 57 | {23,45,10,42,36,21,9,96} | {AAAAAAAAAAAAAAAAAAA70415}
+ 58 | {92} | {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119}
+ 59 | {9,69,46,77} | {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955}
+ 60 | {62,2,59,38,89} | {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875}
+ 61 | {72,2,44,95,54,54,13} | {AAAAAAAAAAAAAAAAAAA91804}
+ 62 | {83,72,29,73} | {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617}
+ 63 | {11,4,61,87} | {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938}
+ 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 66 | {31,23,70,52,4,33,48,25} | {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836}
+ 67 | {31,94,7,10} | {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946}
+ 68 | {90,43,38} | {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643}
+ 69 | {67,35,99,85,72,86,44} | {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955}
+ 70 | {56,70,83} | {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242}
+ 71 | {74,26} | {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 73 | {88,25,96,78,65,15,29,19} | {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 75 | {12,96,83,24,71,89,55} | {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052}
+ 76 | {92,55,10,7} | {AAAAAAAAAAAAAAA67062}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 78 | {55,89,44,84,34} | {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 80 | {74,89,44,80,0} | {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007}
+ 81 | {63,77,54,48,61,53,97} | {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121}
+ 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+ 83 | {14,10} | {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119}
+ 84 | {11,83,35,13,96,94} | {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183}
+ 85 | {39,60} | {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154}
+ 86 | {33,81,72,74,45,36,82} | {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176}
+ 87 | {57,27,50,12,97,68} | {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505}
+ 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 90 | {88,75} | {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526}
+ 91 | {78} | {AAAAAAAAAAAAA62007,AAA99043}
+ 92 | {85,63,49,45} | {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089}
+ 93 | {11} | {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383}
+ 94 | {98,9,85,62,88,91,60,61,38,86} | {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587}
+ 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+ 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 99 | {37,86} | {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+ 101 | {} | {}
+ 102 | {NULL} | {NULL}
+(102 rows)
+
+SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
+ seqno | i | t
+-------+----+----
+ 101 | {} | {}
+(1 row)
+
+SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
+ seqno | i | t
+-------+--------+--------
+ 102 | {NULL} | {NULL}
+(1 row)
+
+SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
+ seqno | i | t
+-------+----+----
+ 101 | {} | {}
+(1 row)
+
+SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+ seqno | i | t
+-------+------+--------------------------------------------------------------------
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(1 row)
+
+SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE t <@ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}' ORDER BY seqno;
+ seqno | i | t
+-------+--------------------+-----------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 101 | {} | {}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE t = '{}' ORDER BY seqno;
+ seqno | i | t
+-------+----+----
+ 101 | {} | {}
+(1 row)
+
+SELECT * FROM array_op_test WHERE t @> '{}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 1 | {92,75,71,52,64,83} | {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038}
+ 2 | {3,6} | {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793}
+ 3 | {37,64,95,43,3,41,13,30,11,43} | {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246}
+ 4 | {71,39,99,55,33,75,45} | {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557}
+ 5 | {50,42,77,50,4} | {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104}
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 7 | {12,51,88,64,8} | {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946}
+ 8 | {60,84} | {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407}
+ 9 | {56,52,35,27,80,44,81,22} | {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000}
+ 10 | {71,5,45} | {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249}
+ 11 | {41,86,74,48,22,74,47,50} | {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 13 | {3,52,34,23} | {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658}
+ 14 | {78,57,19} | {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 16 | {14,63,85,11} | {AAAAAA66777}
+ 17 | {7,10,81,85} | {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356}
+ 18 | {1} | {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 20 | {72,89,70,51,54,37,8,49,79} | {AAAAAA58494}
+ 21 | {2,8,65,10,5,79,43} | {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420}
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 23 | {40,90,5,38,72,40,30,10,43,55} | {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562}
+ 24 | {94,61,99,35,48} | {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219}
+ 25 | {31,1,10,11,27,79,38} | {AAAAAAAAAAAAAAAAAA59334,45449}
+ 26 | {71,10,9,69,75} | {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009}
+ 27 | {94} | {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254}
+ 28 | {14,33,6,34,14} | {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601}
+ 29 | {39,21} | {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194}
+ 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+ 31 | {80,24,18,21,54} | {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938}
+ 32 | {58,79,82,80,67,75,98,10,41} | {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533}
+ 33 | {74,73} | {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796}
+ 34 | {70,45} | {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242}
+ 35 | {23,40} | {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084}
+ 36 | {79,82,14,52,30,5,79} | {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598}
+ 37 | {53,11,81,39,3,78,58,64,74} | {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611}
+ 38 | {59,5,4,95,28} | {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387}
+ 39 | {82,43,99,16,74} | {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620}
+ 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623}
+ 41 | {19,26,63,12,93,73,27,94} | {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666}
+ 42 | {15,76,82,75,8,91} | {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587}
+ 43 | {39,87,91,97,79,28} | {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946}
+ 44 | {40,58,68,29,54} | {AAAAAAA81898,AAAAAA66777,AAAAAA98232}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 46 | {53,24} | {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621}
+ 47 | {98,23,64,12,75,61} | {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466}
+ 48 | {76,14} | {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037}
+ 49 | {56,5,54,37,49} | {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587}
+ 50 | {20,12,37,64,93} | {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955}
+ 51 | {47} | {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452}
+ 52 | {89,0} | {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 54 | {70,47} | {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322}
+ 55 | {47,79,47,64,72,25,71,24,93} | {AAAAAAAAAAAAAAAAAA55796,AAAAA62737}
+ 56 | {33,7,60,54,93,90,77,85,39} | {AAAAAAAAAAAAAAAAAA32918,AA42406}
+ 57 | {23,45,10,42,36,21,9,96} | {AAAAAAAAAAAAAAAAAAA70415}
+ 58 | {92} | {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119}
+ 59 | {9,69,46,77} | {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955}
+ 60 | {62,2,59,38,89} | {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875}
+ 61 | {72,2,44,95,54,54,13} | {AAAAAAAAAAAAAAAAAAA91804}
+ 62 | {83,72,29,73} | {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617}
+ 63 | {11,4,61,87} | {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938}
+ 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 66 | {31,23,70,52,4,33,48,25} | {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836}
+ 67 | {31,94,7,10} | {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946}
+ 68 | {90,43,38} | {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643}
+ 69 | {67,35,99,85,72,86,44} | {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955}
+ 70 | {56,70,83} | {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242}
+ 71 | {74,26} | {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 73 | {88,25,96,78,65,15,29,19} | {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 75 | {12,96,83,24,71,89,55} | {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052}
+ 76 | {92,55,10,7} | {AAAAAAAAAAAAAAA67062}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 78 | {55,89,44,84,34} | {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 80 | {74,89,44,80,0} | {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007}
+ 81 | {63,77,54,48,61,53,97} | {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121}
+ 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+ 83 | {14,10} | {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119}
+ 84 | {11,83,35,13,96,94} | {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183}
+ 85 | {39,60} | {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154}
+ 86 | {33,81,72,74,45,36,82} | {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176}
+ 87 | {57,27,50,12,97,68} | {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505}
+ 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 90 | {88,75} | {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526}
+ 91 | {78} | {AAAAAAAAAAAAA62007,AAA99043}
+ 92 | {85,63,49,45} | {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089}
+ 93 | {11} | {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383}
+ 94 | {98,9,85,62,88,91,60,61,38,86} | {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587}
+ 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+ 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 99 | {37,86} | {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+ 101 | {} | {}
+ 102 | {NULL} | {NULL}
+(102 rows)
+
+SELECT * FROM array_op_test WHERE t && '{}' ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE t <@ '{}' ORDER BY seqno;
+ seqno | i | t
+-------+----+----
+ 101 | {} | {}
+(1 row)
+
+-- array casts
+SELECT ARRAY[1,2,3]::text[]::int[]::float8[] AS "{1,2,3}";
+ {1,2,3}
+---------
+ {1,2,3}
+(1 row)
+
+SELECT pg_typeof(ARRAY[1,2,3]::text[]::int[]::float8[]) AS "double precision[]";
+ double precision[]
+--------------------
+ double precision[]
+(1 row)
+
+SELECT ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[] AS "{{a,bc},{def,hijk}}";
+ {{a,bc},{def,hijk}}
+---------------------
+ {{a,bc},{def,hijk}}
+(1 row)
+
+SELECT pg_typeof(ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[]) AS "character varying[]";
+ character varying[]
+---------------------
+ character varying[]
+(1 row)
+
+SELECT CAST(ARRAY[[[[[['a','bb','ccc']]]]]] as text[]) as "{{{{{{a,bb,ccc}}}}}}";
+ {{{{{{a,bb,ccc}}}}}}
+----------------------
+ {{{{{{a,bb,ccc}}}}}}
+(1 row)
+
+SELECT NULL::text[]::int[] AS "NULL";
+ NULL
+------
+
+(1 row)
+
+-- scalar op any/all (array)
+select 33 = any ('{1,2,3}');
+ ?column?
+----------
+ f
+(1 row)
+
+select 33 = any ('{1,2,33}');
+ ?column?
+----------
+ t
+(1 row)
+
+select 33 = all ('{1,2,33}');
+ ?column?
+----------
+ f
+(1 row)
+
+select 33 >= all ('{1,2,33}');
+ ?column?
+----------
+ t
+(1 row)
+
+-- boundary cases
+select null::int >= all ('{1,2,33}');
+ ?column?
+----------
+
+(1 row)
+
+select null::int >= all ('{}');
+ ?column?
+----------
+ t
+(1 row)
+
+select null::int >= any ('{}');
+ ?column?
+----------
+ f
+(1 row)
+
+-- cross-datatype
+select 33.4 = any (array[1,2,3]);
+ ?column?
+----------
+ f
+(1 row)
+
+select 33.4 > all (array[1,2,3]);
+ ?column?
+----------
+ t
+(1 row)
+
+-- errors
+select 33 * any ('{1,2,3}');
+ERROR: op ANY/ALL (array) requires operator to yield boolean
+LINE 1: select 33 * any ('{1,2,3}');
+ ^
+select 33 * any (44);
+ERROR: op ANY/ALL (array) requires array on right side
+LINE 1: select 33 * any (44);
+ ^
+-- nulls
+select 33 = any (null::int[]);
+ ?column?
+----------
+
+(1 row)
+
+select null::int = any ('{1,2,3}');
+ ?column?
+----------
+
+(1 row)
+
+select 33 = any ('{1,null,3}');
+ ?column?
+----------
+
+(1 row)
+
+select 33 = any ('{1,null,33}');
+ ?column?
+----------
+ t
+(1 row)
+
+select 33 = all (null::int[]);
+ ?column?
+----------
+
+(1 row)
+
+select null::int = all ('{1,2,3}');
+ ?column?
+----------
+
+(1 row)
+
+select 33 = all ('{1,null,3}');
+ ?column?
+----------
+ f
+(1 row)
+
+select 33 = all ('{33,null,33}');
+ ?column?
+----------
+
+(1 row)
+
+-- nulls later in the bitmap
+SELECT -1 != ALL(ARRAY(SELECT NULLIF(g.i, 900) FROM generate_series(1,1000) g(i)));
+ ?column?
+----------
+
+(1 row)
+
+-- 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}');
+ERROR: duplicate key value violates unique constraint "arr_tbl_f1_key"
+DETAIL: Key (f1)=({1,2,3}) already exists.
+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}';
+ f1
+----------
+ {1,2,10}
+ {1,5,3}
+(2 rows)
+
+select * from arr_tbl where f1 >= '{1,2,3}' and f1 < '{1,5,3}';
+ f1
+----------
+ {1,2,3}
+ {1,2,10}
+(2 rows)
+
+-- 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;
+ pk | f1
+----+---------
+ 1 | {3,2,5}
+(1 row)
+
+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;
+ pk | f1
+----+------------
+ 1 | {6,7,NULL}
+(1 row)
+
+-- 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
+ ?column?
+----------
+ t
+(1 row)
+
+select 'foo' like any (array['%a', '%b']); -- f
+ ?column?
+----------
+ f
+(1 row)
+
+select 'foo' like all (array['f%', '%o']); -- t
+ ?column?
+----------
+ t
+(1 row)
+
+select 'foo' like all (array['f%', '%b']); -- f
+ ?column?
+----------
+ f
+(1 row)
+
+select 'foo' not like any (array['%a', '%b']); -- t
+ ?column?
+----------
+ t
+(1 row)
+
+select 'foo' not like all (array['%a', '%o']); -- f
+ ?column?
+----------
+ f
+(1 row)
+
+select 'foo' ilike any (array['%A', '%O']); -- t
+ ?column?
+----------
+ t
+(1 row)
+
+select 'foo' ilike all (array['F%', '%O']); -- t
+ ?column?
+----------
+ t
+(1 row)
+
+--
+-- General array parser tests
+--
+-- none of the following should be accepted
+select '{{1,{2}},{2,3}}'::text[];
+ERROR: malformed array literal: "{{1,{2}},{2,3}}"
+LINE 1: select '{{1,{2}},{2,3}}'::text[];
+ ^
+DETAIL: Unexpected "{" character.
+select '{{},{}}'::text[];
+ERROR: malformed array literal: "{{},{}}"
+LINE 1: select '{{},{}}'::text[];
+ ^
+DETAIL: Unexpected "}" character.
+select E'{{1,2},\\{2,3}}'::text[];
+ERROR: malformed array literal: "{{1,2},\{2,3}}"
+LINE 1: select E'{{1,2},\\{2,3}}'::text[];
+ ^
+DETAIL: Unexpected "\" character.
+select '{{"1 2" x},{3}}'::text[];
+ERROR: malformed array literal: "{{"1 2" x},{3}}"
+LINE 1: select '{{"1 2" x},{3}}'::text[];
+ ^
+DETAIL: Unexpected array element.
+select '{}}'::text[];
+ERROR: malformed array literal: "{}}"
+LINE 1: select '{}}'::text[];
+ ^
+DETAIL: Junk after closing right brace.
+select '{ }}'::text[];
+ERROR: malformed array literal: "{ }}"
+LINE 1: select '{ }}'::text[];
+ ^
+DETAIL: Junk after closing right brace.
+select array[];
+ERROR: cannot determine type of empty array
+LINE 1: select array[];
+ ^
+HINT: Explicitly cast to the desired type, for example ARRAY[]::integer[].
+-- none of the above should be accepted
+-- all of the following should be accepted
+select '{}'::text[];
+ text
+------
+ {}
+(1 row)
+
+select '{{{1,2,3,4},{2,3,4,5}},{{3,4,5,6},{4,5,6,7}}}'::text[];
+ text
+-----------------------------------------------
+ {{{1,2,3,4},{2,3,4,5}},{{3,4,5,6},{4,5,6,7}}}
+(1 row)
+
+select '{0 second ,0 second}'::interval[];
+ interval
+---------------
+ {"@ 0","@ 0"}
+(1 row)
+
+select '{ { "," } , { 3 } }'::text[];
+ text
+-------------
+ {{","},{3}}
+(1 row)
+
+select ' { { " 0 second " , 0 second } }'::text[];
+ text
+-------------------------------
+ {{" 0 second ","0 second"}}
+(1 row)
+
+select '{
+ 0 second,
+ @ 1 hour @ 42 minutes @ 20 seconds
+ }'::interval[];
+ interval
+------------------------------------
+ {"@ 0","@ 1 hour 42 mins 20 secs"}
+(1 row)
+
+select array[]::text[];
+ array
+-------
+ {}
+(1 row)
+
+select '[0:1]={1.1,2.2}'::float8[];
+ float8
+-----------------
+ [0:1]={1.1,2.2}
+(1 row)
+
+-- 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;
+ max | min | max | min | max | min
+-----------+---------+--------------------------+--------------------------+---------+-------
+ {1,2,3,4} | {1,2,3} | {{grey,red},{grey,blue}} | {{grey,red},{blue,blue}} | {1.6,0} | {1.6}
+(1 row)
+
+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;
+ max | min | max | min | max | min
+---------------+---------+--------------------------------+--------------------------+-----------------------+-------
+ {3,3,2,4,5,6} | {1,2,3} | {{white,yellow},{pink,orange}} | {{grey,red},{blue,blue}} | {2.1,3.3,1.8,1.7,1.6} | {1.6}
+(1 row)
+
+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;
+ max | min | max | min | max | min
+---------------+---------+--------------------------------+------------------------------+-----------------------+-------
+ {3,3,2,4,5,6} | {1,2,3} | {{white,yellow},{pink,orange}} | {{black,red},{green,orange}} | {2.1,3.3,1.8,1.7,1.6} | {1.6}
+(1 row)
+
+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;
+ max | min | max | min | max | min
+---------------+---------+--------------------------------+------------------------------+-----------------------+-------
+ {4,2,6,7,8,1} | {1,2,3} | {{white,yellow},{pink,orange}} | {{black,red},{green,orange}} | {2.1,3.3,1.8,1.7,1.6} | {1.6}
+(1 row)
+
+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;
+ max | min | max | min | max | min
+---------------+-----+--------------------------------+------------------------------+-----------------------+-------
+ {4,2,6,7,8,1} | {} | {{white,yellow},{pink,orange}} | {{black,red},{green,orange}} | {2.1,3.3,1.8,1.7,1.6} | {1.6}
+(1 row)
+
+-- 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;
+ c1 | c2
+---------+-----------------------
+ (1,foo) | {"(2,bar)","(3,baz)"}
+(1 row)
+
+select c2[2].f2 from comptable;
+ f2
+-----
+ baz
+(1 row)
+
+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]);
+ unnest1
+---------
+ 1
+ 2
+ 3
+(3 rows)
+
+select * from unnest2(array[[1,2,3],[4,5,6]]);
+ unnest2
+---------
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+(6 rows)
+
+drop function unnest1(anyarray);
+drop function unnest2(anyarray);
+select array_fill(null::integer, array[3,3],array[2,2]);
+ array_fill
+-----------------------------------------------------------------
+ [2:4][2:4]={{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}}
+(1 row)
+
+select array_fill(null::integer, array[3,3]);
+ array_fill
+------------------------------------------------------
+ {{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}}
+(1 row)
+
+select array_fill(null::text, array[3,3],array[2,2]);
+ array_fill
+-----------------------------------------------------------------
+ [2:4][2:4]={{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}}
+(1 row)
+
+select array_fill(null::text, array[3,3]);
+ array_fill
+------------------------------------------------------
+ {{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}}
+(1 row)
+
+select array_fill(7, array[3,3],array[2,2]);
+ array_fill
+--------------------------------------
+ [2:4][2:4]={{7,7,7},{7,7,7},{7,7,7}}
+(1 row)
+
+select array_fill(7, array[3,3]);
+ array_fill
+---------------------------
+ {{7,7,7},{7,7,7},{7,7,7}}
+(1 row)
+
+select array_fill('juhu'::text, array[3,3],array[2,2]);
+ array_fill
+-----------------------------------------------------------------
+ [2:4][2:4]={{juhu,juhu,juhu},{juhu,juhu,juhu},{juhu,juhu,juhu}}
+(1 row)
+
+select array_fill('juhu'::text, array[3,3]);
+ array_fill
+------------------------------------------------------
+ {{juhu,juhu,juhu},{juhu,juhu,juhu},{juhu,juhu,juhu}}
+(1 row)
+
+select a, a = '{}' as is_eq, array_dims(a)
+ from (select array_fill(42, array[0]) as a) ss;
+ a | is_eq | array_dims
+----+-------+------------
+ {} | t |
+(1 row)
+
+select a, a = '{}' as is_eq, array_dims(a)
+ from (select array_fill(42, '{}') as a) ss;
+ a | is_eq | array_dims
+----+-------+------------
+ {} | t |
+(1 row)
+
+select a, a = '{}' as is_eq, array_dims(a)
+ from (select array_fill(42, '{}', '{}') as a) ss;
+ a | is_eq | array_dims
+----+-------+------------
+ {} | t |
+(1 row)
+
+-- raise exception
+select array_fill(1, null, array[2,2]);
+ERROR: dimension array or low bound array cannot be null
+select array_fill(1, array[2,2], null);
+ERROR: dimension array or low bound array cannot be null
+select array_fill(1, array[2,2], '{}');
+ERROR: wrong number of array subscripts
+DETAIL: Low bound array has different size than dimensions array.
+select array_fill(1, array[3,3], array[1,1,1]);
+ERROR: wrong number of array subscripts
+DETAIL: Low bound array has different size than dimensions array.
+select array_fill(1, array[1,2,null]);
+ERROR: dimension values cannot be null
+select array_fill(1, array[[1,2],[3,4]]);
+ERROR: wrong number of array subscripts
+DETAIL: Dimension array must be one dimensional.
+select string_to_array('1|2|3', '|');
+ string_to_array
+-----------------
+ {1,2,3}
+(1 row)
+
+select string_to_array('1|2|3|', '|');
+ string_to_array
+-----------------
+ {1,2,3,""}
+(1 row)
+
+select string_to_array('1||2|3||', '||');
+ string_to_array
+-----------------
+ {1,2|3,""}
+(1 row)
+
+select string_to_array('1|2|3', '');
+ string_to_array
+-----------------
+ {1|2|3}
+(1 row)
+
+select string_to_array('', '|');
+ string_to_array
+-----------------
+ {}
+(1 row)
+
+select string_to_array('1|2|3', NULL);
+ string_to_array
+-----------------
+ {1,|,2,|,3}
+(1 row)
+
+select string_to_array(NULL, '|') IS NULL;
+ ?column?
+----------
+ t
+(1 row)
+
+select string_to_array('abc', '');
+ string_to_array
+-----------------
+ {abc}
+(1 row)
+
+select string_to_array('abc', '', 'abc');
+ string_to_array
+-----------------
+ {NULL}
+(1 row)
+
+select string_to_array('abc', ',');
+ string_to_array
+-----------------
+ {abc}
+(1 row)
+
+select string_to_array('abc', ',', 'abc');
+ string_to_array
+-----------------
+ {NULL}
+(1 row)
+
+select string_to_array('1,2,3,4,,6', ',');
+ string_to_array
+-----------------
+ {1,2,3,4,"",6}
+(1 row)
+
+select string_to_array('1,2,3,4,,6', ',', '');
+ string_to_array
+------------------
+ {1,2,3,4,NULL,6}
+(1 row)
+
+select string_to_array('1,2,3,4,*,6', ',', '*');
+ string_to_array
+------------------
+ {1,2,3,4,NULL,6}
+(1 row)
+
+select v, v is null as "is null" from string_to_table('1|2|3', '|') g(v);
+ v | is null
+---+---------
+ 1 | f
+ 2 | f
+ 3 | f
+(3 rows)
+
+select v, v is null as "is null" from string_to_table('1|2|3|', '|') g(v);
+ v | is null
+---+---------
+ 1 | f
+ 2 | f
+ 3 | f
+ | f
+(4 rows)
+
+select v, v is null as "is null" from string_to_table('1||2|3||', '||') g(v);
+ v | is null
+-----+---------
+ 1 | f
+ 2|3 | f
+ | f
+(3 rows)
+
+select v, v is null as "is null" from string_to_table('1|2|3', '') g(v);
+ v | is null
+-------+---------
+ 1|2|3 | f
+(1 row)
+
+select v, v is null as "is null" from string_to_table('', '|') g(v);
+ v | is null
+---+---------
+(0 rows)
+
+select v, v is null as "is null" from string_to_table('1|2|3', NULL) g(v);
+ v | is null
+---+---------
+ 1 | f
+ | | f
+ 2 | f
+ | | f
+ 3 | f
+(5 rows)
+
+select v, v is null as "is null" from string_to_table(NULL, '|') g(v);
+ v | is null
+---+---------
+(0 rows)
+
+select v, v is null as "is null" from string_to_table('abc', '') g(v);
+ v | is null
+-----+---------
+ abc | f
+(1 row)
+
+select v, v is null as "is null" from string_to_table('abc', '', 'abc') g(v);
+ v | is null
+---+---------
+ | t
+(1 row)
+
+select v, v is null as "is null" from string_to_table('abc', ',') g(v);
+ v | is null
+-----+---------
+ abc | f
+(1 row)
+
+select v, v is null as "is null" from string_to_table('abc', ',', 'abc') g(v);
+ v | is null
+---+---------
+ | t
+(1 row)
+
+select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',') g(v);
+ v | is null
+---+---------
+ 1 | f
+ 2 | f
+ 3 | f
+ 4 | f
+ | f
+ 6 | f
+(6 rows)
+
+select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',', '') g(v);
+ v | is null
+---+---------
+ 1 | f
+ 2 | f
+ 3 | f
+ 4 | f
+ | t
+ 6 | f
+(6 rows)
+
+select v, v is null as "is null" from string_to_table('1,2,3,4,*,6', ',', '*') g(v);
+ v | is null
+---+---------
+ 1 | f
+ 2 | f
+ 3 | f
+ 4 | f
+ | t
+ 6 | f
+(6 rows)
+
+select array_to_string(NULL::int4[], ',') IS NULL;
+ ?column?
+----------
+ t
+(1 row)
+
+select array_to_string('{}'::int4[], ',');
+ array_to_string
+-----------------
+
+(1 row)
+
+select array_to_string(array[1,2,3,4,NULL,6], ',');
+ array_to_string
+-----------------
+ 1,2,3,4,6
+(1 row)
+
+select array_to_string(array[1,2,3,4,NULL,6], ',', '*');
+ array_to_string
+-----------------
+ 1,2,3,4,*,6
+(1 row)
+
+select array_to_string(array[1,2,3,4,NULL,6], NULL);
+ array_to_string
+-----------------
+
+(1 row)
+
+select array_to_string(array[1,2,3,4,NULL,6], ',', NULL);
+ array_to_string
+-----------------
+ 1,2,3,4,6
+(1 row)
+
+select array_to_string(string_to_array('1|2|3', '|'), '|');
+ array_to_string
+-----------------
+ 1|2|3
+(1 row)
+
+select array_length(array[1,2,3], 1);
+ array_length
+--------------
+ 3
+(1 row)
+
+select array_length(array[[1,2,3], [4,5,6]], 0);
+ array_length
+--------------
+
+(1 row)
+
+select array_length(array[[1,2,3], [4,5,6]], 1);
+ array_length
+--------------
+ 2
+(1 row)
+
+select array_length(array[[1,2,3], [4,5,6]], 2);
+ array_length
+--------------
+ 3
+(1 row)
+
+select array_length(array[[1,2,3], [4,5,6]], 3);
+ array_length
+--------------
+
+(1 row)
+
+select cardinality(NULL::int[]);
+ cardinality
+-------------
+
+(1 row)
+
+select cardinality('{}'::int[]);
+ cardinality
+-------------
+ 0
+(1 row)
+
+select cardinality(array[1,2,3]);
+ cardinality
+-------------
+ 3
+(1 row)
+
+select cardinality('[2:4]={5,6,7}'::int[]);
+ cardinality
+-------------
+ 3
+(1 row)
+
+select cardinality('{{1,2}}'::int[]);
+ cardinality
+-------------
+ 2
+(1 row)
+
+select cardinality('{{1,2},{3,4},{5,6}}'::int[]);
+ cardinality
+-------------
+ 6
+(1 row)
+
+select cardinality('{{{1,9},{5,6}},{{2,3},{3,4}}}'::int[]);
+ cardinality
+-------------
+ 8
+(1 row)
+
+-- array_agg(anynonarray)
+select array_agg(unique1) from (select unique1 from tenk1 where unique1 < 15 order by unique1) ss;
+ array_agg
+--------------------------------------
+ {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14}
+(1 row)
+
+select array_agg(ten) from (select ten from tenk1 where unique1 < 15 order by unique1) ss;
+ array_agg
+---------------------------------
+ {0,1,2,3,4,5,6,7,8,9,0,1,2,3,4}
+(1 row)
+
+select array_agg(nullif(ten, 4)) from (select ten from tenk1 where unique1 < 15 order by unique1) ss;
+ array_agg
+---------------------------------------
+ {0,1,2,3,NULL,5,6,7,8,9,0,1,2,3,NULL}
+(1 row)
+
+select array_agg(unique1) from tenk1 where unique1 < -15;
+ array_agg
+-----------
+
+(1 row)
+
+-- array_agg(anyarray)
+select array_agg(ar)
+ from (values ('{1,2}'::int[]), ('{3,4}'::int[])) v(ar);
+ array_agg
+---------------
+ {{1,2},{3,4}}
+(1 row)
+
+select array_agg(distinct ar order by ar desc)
+ from (select array[i / 2] from generate_series(1,10) a(i)) b(ar);
+ array_agg
+---------------------------
+ {{5},{4},{3},{2},{1},{0}}
+(1 row)
+
+select array_agg(ar)
+ from (select array_agg(array[i, i+1, i-1])
+ from generate_series(1,2) a(i)) b(ar);
+ array_agg
+---------------------
+ {{{1,2,0},{2,3,1}}}
+(1 row)
+
+select array_agg(array[i+1.2, i+1.3, i+1.4]) from generate_series(1,3) g(i);
+ array_agg
+---------------------------------------------
+ {{2.2,2.3,2.4},{3.2,3.3,3.4},{4.2,4.3,4.4}}
+(1 row)
+
+select array_agg(array['Hello', i::text]) from generate_series(9,11) g(i);
+ array_agg
+-----------------------------------
+ {{Hello,9},{Hello,10},{Hello,11}}
+(1 row)
+
+select array_agg(array[i, nullif(i, 3), i+1]) from generate_series(1,4) g(i);
+ array_agg
+--------------------------------------
+ {{1,1,2},{2,2,3},{3,NULL,4},{4,4,5}}
+(1 row)
+
+-- errors
+select array_agg('{}'::int[]) from generate_series(1,2);
+ERROR: cannot accumulate empty arrays
+select array_agg(null::int[]) from generate_series(1,2);
+ERROR: cannot accumulate null arrays
+select array_agg(ar)
+ from (values ('{1,2}'::int[]), ('{3}'::int[])) v(ar);
+ERROR: cannot accumulate arrays of different dimensionality
+select unnest(array[1,2,3]);
+ unnest
+--------
+ 1
+ 2
+ 3
+(3 rows)
+
+select * from unnest(array[1,2,3]);
+ unnest
+--------
+ 1
+ 2
+ 3
+(3 rows)
+
+select unnest(array[1,2,3,4.5]::float8[]);
+ unnest
+--------
+ 1
+ 2
+ 3
+ 4.5
+(4 rows)
+
+select unnest(array[1,2,3,4.5]::numeric[]);
+ unnest
+--------
+ 1
+ 2
+ 3
+ 4.5
+(4 rows)
+
+select unnest(array[1,2,3,null,4,null,null,5,6]);
+ unnest
+--------
+ 1
+ 2
+ 3
+
+ 4
+
+
+ 5
+ 6
+(9 rows)
+
+select unnest(array[1,2,3,null,4,null,null,5,6]::text[]);
+ unnest
+--------
+ 1
+ 2
+ 3
+
+ 4
+
+
+ 5
+ 6
+(9 rows)
+
+select abs(unnest(array[1,2,null,-3]));
+ abs
+-----
+ 1
+ 2
+
+ 3
+(4 rows)
+
+select array_remove(array[1,2,2,3], 2);
+ array_remove
+--------------
+ {1,3}
+(1 row)
+
+select array_remove(array[1,2,2,3], 5);
+ array_remove
+--------------
+ {1,2,2,3}
+(1 row)
+
+select array_remove(array[1,NULL,NULL,3], NULL);
+ array_remove
+--------------
+ {1,3}
+(1 row)
+
+select array_remove(array['A','CC','D','C','RR'], 'RR');
+ array_remove
+--------------
+ {A,CC,D,C}
+(1 row)
+
+select array_remove(array[1.0, 2.1, 3.3], 1);
+ array_remove
+--------------
+ {2.1,3.3}
+(1 row)
+
+select array_remove('{{1,2,2},{1,4,3}}', 2); -- not allowed
+ERROR: removing elements from multidimensional arrays is not supported
+select array_remove(array['X','X','X'], 'X') = '{}';
+ ?column?
+----------
+ t
+(1 row)
+
+select array_replace(array[1,2,5,4],5,3);
+ array_replace
+---------------
+ {1,2,3,4}
+(1 row)
+
+select array_replace(array[1,2,5,4],5,NULL);
+ array_replace
+---------------
+ {1,2,NULL,4}
+(1 row)
+
+select array_replace(array[1,2,NULL,4,NULL],NULL,5);
+ array_replace
+---------------
+ {1,2,5,4,5}
+(1 row)
+
+select array_replace(array['A','B','DD','B'],'B','CC');
+ array_replace
+---------------
+ {A,CC,DD,CC}
+(1 row)
+
+select array_replace(array[1,NULL,3],NULL,NULL);
+ array_replace
+---------------
+ {1,NULL,3}
+(1 row)
+
+select array_replace(array['AB',NULL,'CDE'],NULL,'12');
+ array_replace
+---------------
+ {AB,12,CDE}
+(1 row)
+
+-- array(select array-value ...)
+select array(select array[i,i/2] from generate_series(1,5) i);
+ array
+---------------------------------
+ {{1,0},{2,1},{3,1},{4,2},{5,2}}
+(1 row)
+
+select array(select array['Hello', i::text] from generate_series(9,11) i);
+ array
+-----------------------------------
+ {{Hello,9},{Hello,10},{Hello,11}}
+(1 row)
+
+-- 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;
+ f1
+-----------------
+ [5:5]={"(42,)"}
+(1 row)
+
+update t1 set f1[5].q2 = 43;
+select * from t1;
+ f1
+-------------------
+ [5:5]={"(42,43)"}
+(1 row)
+
+-- Check that arrays of composites are safely detoasted when needed
+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;
+ length
+--------
+ 32
+(1 row)
+
+delete from src;
+select length(md5((f1[1]).c2)) from dest;
+ length
+--------
+ 32
+(1 row)
+
+truncate table src;
+drop table src;
+select length(md5((f1[1]).c2)) from dest;
+ length
+--------
+ 32
+(1 row)
+
+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);
+ op | wb_n1 | wb_n2 | wb_n3 | wb_f1 | wb_f2 | wb_f3
+------------------+-------+-------+-------+-------+-------+-------
+ -5.2 | 0 | 0 | 1 | 0 | 0 | 1
+ -0.0000000001 | 0 | 0 | 2 | 0 | 0 | 2
+ 0.000000000001 | 0 | 1 | 2 | 0 | 1 | 2
+ 1 | 1 | 1 | 2 | 1 | 1 | 2
+ 1.99999999999999 | 1 | 1 | 2 | 1 | 1 | 2
+ 2 | 1 | 1 | 3 | 1 | 1 | 3
+ 2.00000000000001 | 1 | 1 | 3 | 1 | 1 | 3
+ 3 | 2 | 1 | 3 | 2 | 1 | 3
+ 4 | 2 | 1 | 3 | 2 | 1 | 3
+ 4.5 | 2 | 1 | 3 | 2 | 1 | 3
+ 5 | 3 | 1 | 3 | 3 | 1 | 3
+ 5.5 | 3 | 2 | 3 | 3 | 2 | 3
+ 6 | 3 | 2 | 3 | 3 | 2 | 3
+ 7 | 3 | 2 | 3 | 3 | 2 | 3
+ 8 | 3 | 2 | 3 | 3 | 2 | 3
+ 9 | 3 | 2 | 3 | 3 | 2 | 3
+ 9.99999999999999 | 3 | 3 | 3 | 3 | 3 | 3
+ 10 | 4 | 3 | 3 | 4 | 3 | 3
+ 10.0000000000001 | 4 | 3 | 3 | 4 | 3 | 3
+(19 rows)
+
+-- 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);
+ op | wb
+------+----
+ -5.2 | 0
+ 4 | 2
+ 77 | 3
+ NaN | 5
+(4 rows)
+
+-- 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;
+ op | wb_1
+----+------
+ 0 | 0
+ 1 | 1
+ 2 | 1
+ 3 | 2
+ 4 | 2
+ 5 | 3
+ 6 | 3
+ 7 | 3
+ 8 | 3
+ 9 | 3
+ 10 | 4
+ 11 | 4
+(12 rows)
+
+SELECT width_bucket(now(),
+ array['yesterday', 'today', 'tomorrow']::timestamptz[]);
+ width_bucket
+--------------
+ 2
+(1 row)
+
+-- corner cases
+SELECT width_bucket(5, ARRAY[3]);
+ width_bucket
+--------------
+ 1
+(1 row)
+
+SELECT width_bucket(5, '{}');
+ width_bucket
+--------------
+ 0
+(1 row)
+
+-- error cases
+SELECT width_bucket('5'::text, ARRAY[3, 4]::integer[]);
+ERROR: function width_bucket(text, integer[]) does not exist
+LINE 1: SELECT width_bucket('5'::text, ARRAY[3, 4]::integer[]);
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT width_bucket(5, ARRAY[3, 4, NULL]);
+ERROR: thresholds array must not contain NULLs
+SELECT width_bucket(5, ARRAY[ARRAY[1, 2], ARRAY[3, 4]]);
+ERROR: thresholds must be one-dimensional array
+-- 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);
+ arr | trim_array
+-------------------------------+-----------------
+ {1,2,3,4,5,6} | {1,2,3,4}
+ {1,2} | {}
+ [10:16]={1,2,3,4,5,6,7} | {1,2,3,4,5}
+ [-15:-10]={1,2,3,4,5,6} | {1,2,3,4}
+ {{1,10},{2,20},{3,30},{4,40}} | {{1,10},{2,20}}
+(5 rows)
+
+SELECT trim_array(ARRAY[1, 2, 3], -1); -- fail
+ERROR: number of elements to trim must be between 0 and 3
+SELECT trim_array(ARRAY[1, 2, 3], 10); -- fail
+ERROR: number of elements to trim must be between 0 and 3
+SELECT trim_array(ARRAY[]::int[], 1); -- fail
+ERROR: number of elements to trim must be between 0 and 0
diff --git a/src/test/regress/expected/async.out b/src/test/regress/expected/async.out
new file mode 100644
index 0000000..19cbe38
--- /dev/null
+++ b/src/test/regress/expected/async.out
@@ -0,0 +1,42 @@
+--
+-- ASYNC
+--
+--Should work. Send a valid message via a valid channel name
+SELECT pg_notify('notify_async1','sample message1');
+ pg_notify
+-----------
+
+(1 row)
+
+SELECT pg_notify('notify_async1','');
+ pg_notify
+-----------
+
+(1 row)
+
+SELECT pg_notify('notify_async1',NULL);
+ pg_notify
+-----------
+
+(1 row)
+
+-- Should fail. Send a valid message via an invalid channel name
+SELECT pg_notify('','sample message1');
+ERROR: channel name cannot be empty
+SELECT pg_notify(NULL,'sample message1');
+ERROR: channel name cannot be empty
+SELECT pg_notify('notify_async_channel_name_too_long______________________________','sample_message1');
+ERROR: channel name too long
+--Should work. Valid NOTIFY/LISTEN/UNLISTEN commands
+NOTIFY notify_async2;
+LISTEN notify_async2;
+UNLISTEN notify_async2;
+UNLISTEN *;
+-- Should return zero while there are no pending notifications.
+-- src/test/isolation/specs/async-notify.spec tests for actual usage.
+SELECT pg_notification_queue_usage();
+ pg_notification_queue_usage
+-----------------------------
+ 0
+(1 row)
+
diff --git a/src/test/regress/expected/bit.out b/src/test/regress/expected/bit.out
new file mode 100644
index 0000000..a5aab9c
--- /dev/null
+++ b/src/test/regress/expected/bit.out
@@ -0,0 +1,748 @@
+--
+-- BIT types
+--
+--
+-- Build tables for testing
+--
+CREATE TABLE BIT_TABLE(b BIT(11));
+INSERT INTO BIT_TABLE VALUES (B'10'); -- too short
+ERROR: bit string length 2 does not match type bit(11)
+INSERT INTO BIT_TABLE VALUES (B'00000000000');
+INSERT INTO BIT_TABLE VALUES (B'11011000000');
+INSERT INTO BIT_TABLE VALUES (B'01010101010');
+INSERT INTO BIT_TABLE VALUES (B'101011111010'); -- too long
+ERROR: bit string length 12 does not match type bit(11)
+--INSERT INTO BIT_TABLE VALUES ('X554');
+--INSERT INTO BIT_TABLE VALUES ('X555');
+SELECT * FROM BIT_TABLE;
+ b
+-------------
+ 00000000000
+ 11011000000
+ 01010101010
+(3 rows)
+
+CREATE TABLE VARBIT_TABLE(v BIT VARYING(11));
+INSERT INTO VARBIT_TABLE VALUES (B'');
+INSERT INTO VARBIT_TABLE VALUES (B'0');
+INSERT INTO VARBIT_TABLE VALUES (B'010101');
+INSERT INTO VARBIT_TABLE VALUES (B'01010101010');
+INSERT INTO VARBIT_TABLE VALUES (B'101011111010'); -- too long
+ERROR: bit string too long for type bit varying(11)
+--INSERT INTO VARBIT_TABLE VALUES ('X554');
+--INSERT INTO VARBIT_TABLE VALUES ('X555');
+SELECT * FROM VARBIT_TABLE;
+ v
+-------------
+
+ 0
+ 010101
+ 01010101010
+(4 rows)
+
+-- Concatenation
+SELECT v, b, (v || b) AS concat
+ FROM BIT_TABLE, VARBIT_TABLE
+ ORDER BY 3;
+ v | b | concat
+-------------+-------------+------------------------
+ | 00000000000 | 00000000000
+ 0 | 00000000000 | 000000000000
+ 0 | 01010101010 | 001010101010
+ 010101 | 00000000000 | 01010100000000000
+ | 01010101010 | 01010101010
+ 01010101010 | 00000000000 | 0101010101000000000000
+ 01010101010 | 01010101010 | 0101010101001010101010
+ 010101 | 01010101010 | 01010101010101010
+ 01010101010 | 11011000000 | 0101010101011011000000
+ 010101 | 11011000000 | 01010111011000000
+ 0 | 11011000000 | 011011000000
+ | 11011000000 | 11011000000
+(12 rows)
+
+-- Length
+SELECT b, length(b) AS lb
+ FROM BIT_TABLE;
+ b | lb
+-------------+----
+ 00000000000 | 11
+ 11011000000 | 11
+ 01010101010 | 11
+(3 rows)
+
+SELECT v, length(v) AS lv
+ FROM VARBIT_TABLE;
+ v | lv
+-------------+----
+ | 0
+ 0 | 1
+ 010101 | 6
+ 01010101010 | 11
+(4 rows)
+
+-- Substring
+SELECT b,
+ SUBSTRING(b FROM 2 FOR 4) AS sub_2_4,
+ SUBSTRING(b FROM 7 FOR 13) AS sub_7_13,
+ SUBSTRING(b FROM 6) AS sub_6
+ FROM BIT_TABLE;
+ b | sub_2_4 | sub_7_13 | sub_6
+-------------+---------+----------+--------
+ 00000000000 | 0000 | 00000 | 000000
+ 11011000000 | 1011 | 00000 | 000000
+ 01010101010 | 1010 | 01010 | 101010
+(3 rows)
+
+SELECT v,
+ SUBSTRING(v FROM 2 FOR 4) AS sub_2_4,
+ SUBSTRING(v FROM 7 FOR 13) AS sub_7_13,
+ SUBSTRING(v FROM 6) AS sub_6
+ FROM VARBIT_TABLE;
+ v | sub_2_4 | sub_7_13 | sub_6
+-------------+---------+----------+--------
+ | | |
+ 0 | | |
+ 010101 | 1010 | | 1
+ 01010101010 | 1010 | 01010 | 101010
+(4 rows)
+
+-- test overflow cases
+SELECT SUBSTRING('01010101'::bit(8) FROM 2 FOR 2147483646) AS "1010101";
+ 1010101
+---------
+ 1010101
+(1 row)
+
+SELECT SUBSTRING('01010101'::bit(8) FROM -10 FOR 2147483646) AS "01010101";
+ 01010101
+----------
+ 01010101
+(1 row)
+
+SELECT SUBSTRING('01010101'::bit(8) FROM -10 FOR -2147483646) AS "error";
+ERROR: negative substring length not allowed
+SELECT SUBSTRING('01010101'::varbit FROM 2 FOR 2147483646) AS "1010101";
+ 1010101
+---------
+ 1010101
+(1 row)
+
+SELECT SUBSTRING('01010101'::varbit FROM -10 FOR 2147483646) AS "01010101";
+ 01010101
+----------
+ 01010101
+(1 row)
+
+SELECT SUBSTRING('01010101'::varbit FROM -10 FOR -2147483646) AS "error";
+ERROR: negative substring length not allowed
+--- Bit operations
+DROP TABLE varbit_table;
+CREATE TABLE varbit_table (a BIT VARYING(16), b BIT VARYING(16));
+COPY varbit_table FROM stdin;
+SELECT a, b, ~a AS "~ a", a & b AS "a & b",
+ a | b AS "a | b", a # b AS "a # b" FROM varbit_table;
+ a | b | ~ a | a & b | a | b | a # b
+------------------+------------------+------------------+------------------+------------------+------------------
+ 00001111 | 00010000 | 11110000 | 00000000 | 00011111 | 00011111
+ 00011111 | 00010001 | 11100000 | 00010001 | 00011111 | 00001110
+ 00101111 | 00010010 | 11010000 | 00000010 | 00111111 | 00111101
+ 00111111 | 00010011 | 11000000 | 00010011 | 00111111 | 00101100
+ 10001111 | 00000100 | 01110000 | 00000100 | 10001111 | 10001011
+ 0000000000001111 | 0000000000010000 | 1111111111110000 | 0000000000000000 | 0000000000011111 | 0000000000011111
+ 0000000100100011 | 1111111111111111 | 1111111011011100 | 0000000100100011 | 1111111111111111 | 1111111011011100
+ 0010010001101000 | 0010010001101000 | 1101101110010111 | 0010010001101000 | 0010010001101000 | 0000000000000000
+ 1111101001010000 | 0000010110101111 | 0000010110101111 | 0000000000000000 | 1111111111111111 | 1111111111111111
+ 0001001000110100 | 1111111111110101 | 1110110111001011 | 0001001000110100 | 1111111111110101 | 1110110111000001
+(10 rows)
+
+SELECT a,b,a<b AS "a<b",a<=b AS "a<=b",a=b AS "a=b",
+ a>=b AS "a>=b",a>b AS "a>b",a<>b AS "a<>b" FROM varbit_table;
+ a | b | a<b | a<=b | a=b | a>=b | a>b | a<>b
+------------------+------------------+-----+------+-----+------+-----+------
+ 00001111 | 00010000 | t | t | f | f | f | t
+ 00011111 | 00010001 | f | f | f | t | t | t
+ 00101111 | 00010010 | f | f | f | t | t | t
+ 00111111 | 00010011 | f | f | f | t | t | t
+ 10001111 | 00000100 | f | f | f | t | t | t
+ 0000000000001111 | 0000000000010000 | t | t | f | f | f | t
+ 0000000100100011 | 1111111111111111 | t | t | f | f | f | t
+ 0010010001101000 | 0010010001101000 | f | t | t | t | f | f
+ 1111101001010000 | 0000010110101111 | f | f | f | t | t | t
+ 0001001000110100 | 1111111111110101 | t | t | f | f | f | t
+(10 rows)
+
+SELECT a,a<<4 AS "a<<4",b,b>>2 AS "b>>2" FROM varbit_table;
+ a | a<<4 | b | b>>2
+------------------+------------------+------------------+------------------
+ 00001111 | 11110000 | 00010000 | 00000100
+ 00011111 | 11110000 | 00010001 | 00000100
+ 00101111 | 11110000 | 00010010 | 00000100
+ 00111111 | 11110000 | 00010011 | 00000100
+ 10001111 | 11110000 | 00000100 | 00000001
+ 0000000000001111 | 0000000011110000 | 0000000000010000 | 0000000000000100
+ 0000000100100011 | 0001001000110000 | 1111111111111111 | 0011111111111111
+ 0010010001101000 | 0100011010000000 | 0010010001101000 | 0000100100011010
+ 1111101001010000 | 1010010100000000 | 0000010110101111 | 0000000101101011
+ 0001001000110100 | 0010001101000000 | 1111111111110101 | 0011111111111101
+(10 rows)
+
+DROP TABLE varbit_table;
+--- Bit operations
+DROP TABLE bit_table;
+CREATE TABLE bit_table (a BIT(16), b BIT(16));
+COPY bit_table FROM stdin;
+SELECT a,b,~a AS "~ a",a & b AS "a & b",
+ a|b AS "a | b", a # b AS "a # b" FROM bit_table;
+ a | b | ~ a | a & b | a | b | a # b
+------------------+------------------+------------------+------------------+------------------+------------------
+ 0000111100000000 | 0001000000000000 | 1111000011111111 | 0000000000000000 | 0001111100000000 | 0001111100000000
+ 0001111100000000 | 0001000100000000 | 1110000011111111 | 0001000100000000 | 0001111100000000 | 0000111000000000
+ 0010111100000000 | 0001001000000000 | 1101000011111111 | 0000001000000000 | 0011111100000000 | 0011110100000000
+ 0011111100000000 | 0001001100000000 | 1100000011111111 | 0001001100000000 | 0011111100000000 | 0010110000000000
+ 1000111100000000 | 0000010000000000 | 0111000011111111 | 0000010000000000 | 1000111100000000 | 1000101100000000
+ 0000000000001111 | 0000000000010000 | 1111111111110000 | 0000000000000000 | 0000000000011111 | 0000000000011111
+ 0000000100100011 | 1111111111111111 | 1111111011011100 | 0000000100100011 | 1111111111111111 | 1111111011011100
+ 0010010001101000 | 0010010001101000 | 1101101110010111 | 0010010001101000 | 0010010001101000 | 0000000000000000
+ 1111101001010000 | 0000010110101111 | 0000010110101111 | 0000000000000000 | 1111111111111111 | 1111111111111111
+ 0001001000110100 | 1111111111110101 | 1110110111001011 | 0001001000110100 | 1111111111110101 | 1110110111000001
+(10 rows)
+
+SELECT a,b,a<b AS "a<b",a<=b AS "a<=b",a=b AS "a=b",
+ a>=b AS "a>=b",a>b AS "a>b",a<>b AS "a<>b" FROM bit_table;
+ a | b | a<b | a<=b | a=b | a>=b | a>b | a<>b
+------------------+------------------+-----+------+-----+------+-----+------
+ 0000111100000000 | 0001000000000000 | t | t | f | f | f | t
+ 0001111100000000 | 0001000100000000 | f | f | f | t | t | t
+ 0010111100000000 | 0001001000000000 | f | f | f | t | t | t
+ 0011111100000000 | 0001001100000000 | f | f | f | t | t | t
+ 1000111100000000 | 0000010000000000 | f | f | f | t | t | t
+ 0000000000001111 | 0000000000010000 | t | t | f | f | f | t
+ 0000000100100011 | 1111111111111111 | t | t | f | f | f | t
+ 0010010001101000 | 0010010001101000 | f | t | t | t | f | f
+ 1111101001010000 | 0000010110101111 | f | f | f | t | t | t
+ 0001001000110100 | 1111111111110101 | t | t | f | f | f | t
+(10 rows)
+
+SELECT a,a<<4 AS "a<<4",b,b>>2 AS "b>>2" FROM bit_table;
+ a | a<<4 | b | b>>2
+------------------+------------------+------------------+------------------
+ 0000111100000000 | 1111000000000000 | 0001000000000000 | 0000010000000000
+ 0001111100000000 | 1111000000000000 | 0001000100000000 | 0000010001000000
+ 0010111100000000 | 1111000000000000 | 0001001000000000 | 0000010010000000
+ 0011111100000000 | 1111000000000000 | 0001001100000000 | 0000010011000000
+ 1000111100000000 | 1111000000000000 | 0000010000000000 | 0000000100000000
+ 0000000000001111 | 0000000011110000 | 0000000000010000 | 0000000000000100
+ 0000000100100011 | 0001001000110000 | 1111111111111111 | 0011111111111111
+ 0010010001101000 | 0100011010000000 | 0010010001101000 | 0000100100011010
+ 1111101001010000 | 1010010100000000 | 0000010110101111 | 0000000101101011
+ 0001001000110100 | 0010001101000000 | 1111111111110101 | 0011111111111101
+(10 rows)
+
+DROP TABLE bit_table;
+-- The following should fail
+select B'001' & B'10';
+ERROR: cannot AND bit strings of different sizes
+select B'0111' | B'011';
+ERROR: cannot OR bit strings of different sizes
+select B'0010' # B'011101';
+ERROR: cannot XOR bit strings of different sizes
+-- More position tests, checking all the boundary cases
+SELECT POSITION(B'1010' IN B'0000101'); -- 0
+ position
+----------
+ 0
+(1 row)
+
+SELECT POSITION(B'1010' IN B'00001010'); -- 5
+ position
+----------
+ 5
+(1 row)
+
+SELECT POSITION(B'1010' IN B'00000101'); -- 0
+ position
+----------
+ 0
+(1 row)
+
+SELECT POSITION(B'1010' IN B'000001010'); -- 6
+ position
+----------
+ 6
+(1 row)
+
+SELECT POSITION(B'' IN B'00001010'); -- 1
+ position
+----------
+ 1
+(1 row)
+
+SELECT POSITION(B'0' IN B''); -- 0
+ position
+----------
+ 0
+(1 row)
+
+SELECT POSITION(B'' IN B''); -- 0
+ position
+----------
+ 0
+(1 row)
+
+SELECT POSITION(B'101101' IN B'001011011011011000'); -- 3
+ position
+----------
+ 3
+(1 row)
+
+SELECT POSITION(B'10110110' IN B'001011011011010'); -- 3
+ position
+----------
+ 3
+(1 row)
+
+SELECT POSITION(B'1011011011011' IN B'001011011011011'); -- 3
+ position
+----------
+ 3
+(1 row)
+
+SELECT POSITION(B'1011011011011' IN B'00001011011011011'); -- 5
+ position
+----------
+ 5
+(1 row)
+
+SELECT POSITION(B'11101011' IN B'11101011'); -- 1
+ position
+----------
+ 1
+(1 row)
+
+SELECT POSITION(B'11101011' IN B'011101011'); -- 2
+ position
+----------
+ 2
+(1 row)
+
+SELECT POSITION(B'11101011' IN B'00011101011'); -- 4
+ position
+----------
+ 4
+(1 row)
+
+SELECT POSITION(B'11101011' IN B'0000011101011'); -- 6
+ position
+----------
+ 6
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'111010110'); -- 1
+ position
+----------
+ 1
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'0111010110'); -- 2
+ position
+----------
+ 2
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'000111010110'); -- 4
+ position
+----------
+ 4
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'00000111010110'); -- 6
+ position
+----------
+ 6
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'11101011'); -- 0
+ position
+----------
+ 0
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'011101011'); -- 0
+ position
+----------
+ 0
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'00011101011'); -- 0
+ position
+----------
+ 0
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'0000011101011'); -- 0
+ position
+----------
+ 0
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'111010110'); -- 1
+ position
+----------
+ 1
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'0111010110'); -- 2
+ position
+----------
+ 2
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'000111010110'); -- 4
+ position
+----------
+ 4
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'00000111010110'); -- 6
+ position
+----------
+ 6
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'000001110101111101011'); -- 0
+ position
+----------
+ 0
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'0000001110101111101011'); -- 0
+ position
+----------
+ 0
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'000000001110101111101011'); -- 0
+ position
+----------
+ 0
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'00000000001110101111101011'); -- 0
+ position
+----------
+ 0
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'0000011101011111010110'); -- 14
+ position
+----------
+ 14
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'00000011101011111010110'); -- 15
+ position
+----------
+ 15
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'0000000011101011111010110'); -- 17
+ position
+----------
+ 17
+(1 row)
+
+SELECT POSITION(B'111010110' IN B'000000000011101011111010110'); -- 19
+ position
+----------
+ 19
+(1 row)
+
+SELECT POSITION(B'000000000011101011111010110' IN B'000000000011101011111010110'); -- 1
+ position
+----------
+ 1
+(1 row)
+
+SELECT POSITION(B'00000000011101011111010110' IN B'000000000011101011111010110'); -- 2
+ position
+----------
+ 2
+(1 row)
+
+SELECT POSITION(B'0000000000011101011111010110' IN B'000000000011101011111010110'); -- 0
+ position
+----------
+ 0
+(1 row)
+
+-- Shifting
+CREATE TABLE BIT_SHIFT_TABLE(b BIT(16));
+INSERT INTO BIT_SHIFT_TABLE VALUES (B'1101100000000000');
+INSERT INTO BIT_SHIFT_TABLE SELECT b>>1 FROM BIT_SHIFT_TABLE;
+INSERT INTO BIT_SHIFT_TABLE SELECT b>>2 FROM BIT_SHIFT_TABLE;
+INSERT INTO BIT_SHIFT_TABLE SELECT b>>4 FROM BIT_SHIFT_TABLE;
+INSERT INTO BIT_SHIFT_TABLE SELECT b>>8 FROM BIT_SHIFT_TABLE;
+SELECT POSITION(B'1101' IN b),
+ POSITION(B'11011' IN b),
+ b
+ FROM BIT_SHIFT_TABLE ;
+ position | position | b
+----------+----------+------------------
+ 1 | 1 | 1101100000000000
+ 2 | 2 | 0110110000000000
+ 3 | 3 | 0011011000000000
+ 4 | 4 | 0001101100000000
+ 5 | 5 | 0000110110000000
+ 6 | 6 | 0000011011000000
+ 7 | 7 | 0000001101100000
+ 8 | 8 | 0000000110110000
+ 9 | 9 | 0000000011011000
+ 10 | 10 | 0000000001101100
+ 11 | 11 | 0000000000110110
+ 12 | 12 | 0000000000011011
+ 13 | 0 | 0000000000001101
+ 0 | 0 | 0000000000000110
+ 0 | 0 | 0000000000000011
+ 0 | 0 | 0000000000000001
+(16 rows)
+
+SELECT b, b >> 1 AS bsr, b << 1 AS bsl
+ FROM BIT_SHIFT_TABLE ;
+ b | bsr | bsl
+------------------+------------------+------------------
+ 1101100000000000 | 0110110000000000 | 1011000000000000
+ 0110110000000000 | 0011011000000000 | 1101100000000000
+ 0011011000000000 | 0001101100000000 | 0110110000000000
+ 0001101100000000 | 0000110110000000 | 0011011000000000
+ 0000110110000000 | 0000011011000000 | 0001101100000000
+ 0000011011000000 | 0000001101100000 | 0000110110000000
+ 0000001101100000 | 0000000110110000 | 0000011011000000
+ 0000000110110000 | 0000000011011000 | 0000001101100000
+ 0000000011011000 | 0000000001101100 | 0000000110110000
+ 0000000001101100 | 0000000000110110 | 0000000011011000
+ 0000000000110110 | 0000000000011011 | 0000000001101100
+ 0000000000011011 | 0000000000001101 | 0000000000110110
+ 0000000000001101 | 0000000000000110 | 0000000000011010
+ 0000000000000110 | 0000000000000011 | 0000000000001100
+ 0000000000000011 | 0000000000000001 | 0000000000000110
+ 0000000000000001 | 0000000000000000 | 0000000000000010
+(16 rows)
+
+SELECT b, b >> 8 AS bsr8, b << 8 AS bsl8
+ FROM BIT_SHIFT_TABLE ;
+ b | bsr8 | bsl8
+------------------+------------------+------------------
+ 1101100000000000 | 0000000011011000 | 0000000000000000
+ 0110110000000000 | 0000000001101100 | 0000000000000000
+ 0011011000000000 | 0000000000110110 | 0000000000000000
+ 0001101100000000 | 0000000000011011 | 0000000000000000
+ 0000110110000000 | 0000000000001101 | 1000000000000000
+ 0000011011000000 | 0000000000000110 | 1100000000000000
+ 0000001101100000 | 0000000000000011 | 0110000000000000
+ 0000000110110000 | 0000000000000001 | 1011000000000000
+ 0000000011011000 | 0000000000000000 | 1101100000000000
+ 0000000001101100 | 0000000000000000 | 0110110000000000
+ 0000000000110110 | 0000000000000000 | 0011011000000000
+ 0000000000011011 | 0000000000000000 | 0001101100000000
+ 0000000000001101 | 0000000000000000 | 0000110100000000
+ 0000000000000110 | 0000000000000000 | 0000011000000000
+ 0000000000000011 | 0000000000000000 | 0000001100000000
+ 0000000000000001 | 0000000000000000 | 0000000100000000
+(16 rows)
+
+SELECT b::bit(15), b::bit(15) >> 1 AS bsr, b::bit(15) << 1 AS bsl
+ FROM BIT_SHIFT_TABLE ;
+ b | bsr | bsl
+-----------------+-----------------+-----------------
+ 110110000000000 | 011011000000000 | 101100000000000
+ 011011000000000 | 001101100000000 | 110110000000000
+ 001101100000000 | 000110110000000 | 011011000000000
+ 000110110000000 | 000011011000000 | 001101100000000
+ 000011011000000 | 000001101100000 | 000110110000000
+ 000001101100000 | 000000110110000 | 000011011000000
+ 000000110110000 | 000000011011000 | 000001101100000
+ 000000011011000 | 000000001101100 | 000000110110000
+ 000000001101100 | 000000000110110 | 000000011011000
+ 000000000110110 | 000000000011011 | 000000001101100
+ 000000000011011 | 000000000001101 | 000000000110110
+ 000000000001101 | 000000000000110 | 000000000011010
+ 000000000000110 | 000000000000011 | 000000000001100
+ 000000000000011 | 000000000000001 | 000000000000110
+ 000000000000001 | 000000000000000 | 000000000000010
+ 000000000000000 | 000000000000000 | 000000000000000
+(16 rows)
+
+SELECT b::bit(15), b::bit(15) >> 8 AS bsr8, b::bit(15) << 8 AS bsl8
+ FROM BIT_SHIFT_TABLE ;
+ b | bsr8 | bsl8
+-----------------+-----------------+-----------------
+ 110110000000000 | 000000001101100 | 000000000000000
+ 011011000000000 | 000000000110110 | 000000000000000
+ 001101100000000 | 000000000011011 | 000000000000000
+ 000110110000000 | 000000000001101 | 000000000000000
+ 000011011000000 | 000000000000110 | 100000000000000
+ 000001101100000 | 000000000000011 | 110000000000000
+ 000000110110000 | 000000000000001 | 011000000000000
+ 000000011011000 | 000000000000000 | 101100000000000
+ 000000001101100 | 000000000000000 | 110110000000000
+ 000000000110110 | 000000000000000 | 011011000000000
+ 000000000011011 | 000000000000000 | 001101100000000
+ 000000000001101 | 000000000000000 | 000110100000000
+ 000000000000110 | 000000000000000 | 000011000000000
+ 000000000000011 | 000000000000000 | 000001100000000
+ 000000000000001 | 000000000000000 | 000000100000000
+ 000000000000000 | 000000000000000 | 000000000000000
+(16 rows)
+
+CREATE TABLE VARBIT_SHIFT_TABLE(v BIT VARYING(20));
+INSERT INTO VARBIT_SHIFT_TABLE VALUES (B'11011');
+INSERT INTO VARBIT_SHIFT_TABLE SELECT CAST(v || B'0' AS BIT VARYING(6)) >>1 FROM VARBIT_SHIFT_TABLE;
+INSERT INTO VARBIT_SHIFT_TABLE SELECT CAST(v || B'00' AS BIT VARYING(8)) >>2 FROM VARBIT_SHIFT_TABLE;
+INSERT INTO VARBIT_SHIFT_TABLE SELECT CAST(v || B'0000' AS BIT VARYING(12)) >>4 FROM VARBIT_SHIFT_TABLE;
+INSERT INTO VARBIT_SHIFT_TABLE SELECT CAST(v || B'00000000' AS BIT VARYING(20)) >>8 FROM VARBIT_SHIFT_TABLE;
+SELECT POSITION(B'1101' IN v),
+ POSITION(B'11011' IN v),
+ v
+ FROM VARBIT_SHIFT_TABLE ;
+ position | position | v
+----------+----------+----------------------
+ 1 | 1 | 11011
+ 2 | 2 | 011011
+ 3 | 3 | 0011011
+ 4 | 4 | 00011011
+ 5 | 5 | 000011011
+ 6 | 6 | 0000011011
+ 7 | 7 | 00000011011
+ 8 | 8 | 000000011011
+ 9 | 9 | 0000000011011
+ 10 | 10 | 00000000011011
+ 11 | 11 | 000000000011011
+ 12 | 12 | 0000000000011011
+ 13 | 13 | 00000000000011011
+ 14 | 14 | 000000000000011011
+ 15 | 15 | 0000000000000011011
+ 16 | 16 | 00000000000000011011
+(16 rows)
+
+SELECT v, v >> 1 AS vsr, v << 1 AS vsl
+ FROM VARBIT_SHIFT_TABLE ;
+ v | vsr | vsl
+----------------------+----------------------+----------------------
+ 11011 | 01101 | 10110
+ 011011 | 001101 | 110110
+ 0011011 | 0001101 | 0110110
+ 00011011 | 00001101 | 00110110
+ 000011011 | 000001101 | 000110110
+ 0000011011 | 0000001101 | 0000110110
+ 00000011011 | 00000001101 | 00000110110
+ 000000011011 | 000000001101 | 000000110110
+ 0000000011011 | 0000000001101 | 0000000110110
+ 00000000011011 | 00000000001101 | 00000000110110
+ 000000000011011 | 000000000001101 | 000000000110110
+ 0000000000011011 | 0000000000001101 | 0000000000110110
+ 00000000000011011 | 00000000000001101 | 00000000000110110
+ 000000000000011011 | 000000000000001101 | 000000000000110110
+ 0000000000000011011 | 0000000000000001101 | 0000000000000110110
+ 00000000000000011011 | 00000000000000001101 | 00000000000000110110
+(16 rows)
+
+SELECT v, v >> 8 AS vsr8, v << 8 AS vsl8
+ FROM VARBIT_SHIFT_TABLE ;
+ v | vsr8 | vsl8
+----------------------+----------------------+----------------------
+ 11011 | 00000 | 00000
+ 011011 | 000000 | 000000
+ 0011011 | 0000000 | 0000000
+ 00011011 | 00000000 | 00000000
+ 000011011 | 000000000 | 100000000
+ 0000011011 | 0000000000 | 1100000000
+ 00000011011 | 00000000000 | 01100000000
+ 000000011011 | 000000000000 | 101100000000
+ 0000000011011 | 0000000000000 | 1101100000000
+ 00000000011011 | 00000000000000 | 01101100000000
+ 000000000011011 | 000000000000000 | 001101100000000
+ 0000000000011011 | 0000000000000000 | 0001101100000000
+ 00000000000011011 | 00000000000000000 | 00001101100000000
+ 000000000000011011 | 000000000000000000 | 000001101100000000
+ 0000000000000011011 | 0000000000000000000 | 0000001101100000000
+ 00000000000000011011 | 00000000000000000000 | 00000001101100000000
+(16 rows)
+
+DROP TABLE BIT_SHIFT_TABLE;
+DROP TABLE VARBIT_SHIFT_TABLE;
+-- Get/Set bit
+SELECT get_bit(B'0101011000100', 10);
+ get_bit
+---------
+ 1
+(1 row)
+
+SELECT set_bit(B'0101011000100100', 15, 1);
+ set_bit
+------------------
+ 0101011000100101
+(1 row)
+
+SELECT set_bit(B'0101011000100100', 16, 1); -- fail
+ERROR: bit index 16 out of valid range (0..15)
+-- Overlay
+SELECT overlay(B'0101011100' placing '001' from 2 for 3);
+ overlay
+------------
+ 0001011100
+(1 row)
+
+SELECT overlay(B'0101011100' placing '101' from 6);
+ overlay
+------------
+ 0101010100
+(1 row)
+
+SELECT overlay(B'0101011100' placing '001' from 11);
+ overlay
+---------------
+ 0101011100001
+(1 row)
+
+SELECT overlay(B'0101011100' placing '001' from 20);
+ overlay
+---------------
+ 0101011100001
+(1 row)
+
+-- bit_count
+SELECT bit_count(B'0101011100'::bit(10));
+ bit_count
+-----------
+ 5
+(1 row)
+
+SELECT bit_count(B'1111111111'::bit(10));
+ bit_count
+-----------
+ 10
+(1 row)
+
+-- This table is intentionally left around to exercise pg_dump/pg_upgrade
+CREATE TABLE bit_defaults(
+ b1 bit(4) DEFAULT '1001',
+ b2 bit(4) DEFAULT B'0101',
+ b3 bit varying(5) DEFAULT '1001',
+ b4 bit varying(5) DEFAULT B'0101'
+);
+\d bit_defaults
+ Table "public.bit_defaults"
+ Column | Type | Collation | Nullable | Default
+--------+----------------+-----------+----------+---------------------
+ b1 | bit(4) | | | '1001'::"bit"
+ b2 | bit(4) | | | '0101'::"bit"
+ b3 | bit varying(5) | | | '1001'::bit varying
+ b4 | bit varying(5) | | | '0101'::"bit"
+
+INSERT INTO bit_defaults DEFAULT VALUES;
+TABLE bit_defaults;
+ b1 | b2 | b3 | b4
+------+------+------+------
+ 1001 | 0101 | 1001 | 0101
+(1 row)
+
diff --git a/src/test/regress/expected/bitmapops.out b/src/test/regress/expected/bitmapops.out
new file mode 100644
index 0000000..3570973
--- /dev/null
+++ b/src/test/regress/expected/bitmapops.out
@@ -0,0 +1,38 @@
+-- Test bitmap AND and OR
+-- Generate enough data that we can test the lossy bitmaps.
+-- There's 55 tuples per page in the table. 53 is just
+-- below 55, so that an index scan with qual a = constant
+-- will return at least one hit per page. 59 is just above
+-- 55, so that an index scan with qual b = constant will return
+-- hits on most but not all pages. 53 and 59 are prime, so that
+-- there's a maximum number of a,b combinations in the table.
+-- That allows us to test all the different combinations of
+-- lossy and non-lossy pages with the minimum amount of data
+CREATE TABLE bmscantest (a int, b int, t text);
+INSERT INTO bmscantest
+ SELECT (r%53), (r%59), 'foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo'
+ FROM generate_series(1,70000) r;
+CREATE INDEX i_bmtest_a ON bmscantest(a);
+CREATE INDEX i_bmtest_b ON bmscantest(b);
+-- We want to use bitmapscans. With default settings, the planner currently
+-- chooses a bitmap scan for the queries below anyway, but let's make sure.
+set enable_indexscan=false;
+set enable_seqscan=false;
+-- Lower work_mem to trigger use of lossy bitmaps
+set work_mem = 64;
+-- Test bitmap-and.
+SELECT count(*) FROM bmscantest WHERE a = 1 AND b = 1;
+ count
+-------
+ 23
+(1 row)
+
+-- Test bitmap-or.
+SELECT count(*) FROM bmscantest WHERE a = 1 OR b = 1;
+ count
+-------
+ 2485
+(1 row)
+
+-- clean up
+DROP TABLE bmscantest;
diff --git a/src/test/regress/expected/boolean.out b/src/test/regress/expected/boolean.out
new file mode 100644
index 0000000..4728fe2
--- /dev/null
+++ b/src/test/regress/expected/boolean.out
@@ -0,0 +1,559 @@
+--
+-- BOOLEAN
+--
+--
+-- sanity check - if this fails go insane!
+--
+SELECT 1 AS one;
+ one
+-----
+ 1
+(1 row)
+
+-- ******************testing built-in type bool********************
+-- check bool input syntax
+SELECT true AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT false AS false;
+ false
+-------
+ f
+(1 row)
+
+SELECT bool 't' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT bool ' f ' AS false;
+ false
+-------
+ f
+(1 row)
+
+SELECT bool 'true' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT bool 'test' AS error;
+ERROR: invalid input syntax for type boolean: "test"
+LINE 1: SELECT bool 'test' AS error;
+ ^
+SELECT bool 'false' AS false;
+ false
+-------
+ f
+(1 row)
+
+SELECT bool 'foo' AS error;
+ERROR: invalid input syntax for type boolean: "foo"
+LINE 1: SELECT bool 'foo' AS error;
+ ^
+SELECT bool 'y' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT bool 'yes' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT bool 'yeah' AS error;
+ERROR: invalid input syntax for type boolean: "yeah"
+LINE 1: SELECT bool 'yeah' AS error;
+ ^
+SELECT bool 'n' AS false;
+ false
+-------
+ f
+(1 row)
+
+SELECT bool 'no' AS false;
+ false
+-------
+ f
+(1 row)
+
+SELECT bool 'nay' AS error;
+ERROR: invalid input syntax for type boolean: "nay"
+LINE 1: SELECT bool 'nay' AS error;
+ ^
+SELECT bool 'on' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT bool 'off' AS false;
+ false
+-------
+ f
+(1 row)
+
+SELECT bool 'of' AS false;
+ false
+-------
+ f
+(1 row)
+
+SELECT bool 'o' AS error;
+ERROR: invalid input syntax for type boolean: "o"
+LINE 1: SELECT bool 'o' AS error;
+ ^
+SELECT bool 'on_' AS error;
+ERROR: invalid input syntax for type boolean: "on_"
+LINE 1: SELECT bool 'on_' AS error;
+ ^
+SELECT bool 'off_' AS error;
+ERROR: invalid input syntax for type boolean: "off_"
+LINE 1: SELECT bool 'off_' AS error;
+ ^
+SELECT bool '1' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT bool '11' AS error;
+ERROR: invalid input syntax for type boolean: "11"
+LINE 1: SELECT bool '11' AS error;
+ ^
+SELECT bool '0' AS false;
+ false
+-------
+ f
+(1 row)
+
+SELECT bool '000' AS error;
+ERROR: invalid input syntax for type boolean: "000"
+LINE 1: SELECT bool '000' AS error;
+ ^
+SELECT bool '' AS error;
+ERROR: invalid input syntax for type boolean: ""
+LINE 1: SELECT bool '' AS error;
+ ^
+-- and, or, not in qualifications
+SELECT bool 't' or bool 'f' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT bool 't' and bool 'f' AS false;
+ false
+-------
+ f
+(1 row)
+
+SELECT not bool 'f' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT bool 't' = bool 'f' AS false;
+ false
+-------
+ f
+(1 row)
+
+SELECT bool 't' <> bool 'f' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT bool 't' > bool 'f' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT bool 't' >= bool 'f' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT bool 'f' < bool 't' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT bool 'f' <= bool 't' AS true;
+ true
+------
+ t
+(1 row)
+
+-- explicit casts to/from text
+SELECT 'TrUe'::text::boolean AS true, 'fAlse'::text::boolean AS false;
+ true | false
+------+-------
+ t | f
+(1 row)
+
+SELECT ' true '::text::boolean AS true,
+ ' FALSE'::text::boolean AS false;
+ true | false
+------+-------
+ t | f
+(1 row)
+
+SELECT true::boolean::text AS true, false::boolean::text AS false;
+ true | false
+------+-------
+ true | false
+(1 row)
+
+SELECT ' tru e '::text::boolean AS invalid; -- error
+ERROR: invalid input syntax for type boolean: " tru e "
+SELECT ''::text::boolean AS invalid; -- error
+ERROR: invalid input syntax for type boolean: ""
+CREATE TABLE BOOLTBL1 (f1 bool);
+INSERT INTO BOOLTBL1 (f1) VALUES (bool 't');
+INSERT INTO BOOLTBL1 (f1) VALUES (bool 'True');
+INSERT INTO BOOLTBL1 (f1) VALUES (bool 'true');
+-- BOOLTBL1 should be full of true's at this point
+SELECT BOOLTBL1.* FROM BOOLTBL1;
+ f1
+----
+ t
+ t
+ t
+(3 rows)
+
+SELECT BOOLTBL1.*
+ FROM BOOLTBL1
+ WHERE f1 = bool 'true';
+ f1
+----
+ t
+ t
+ t
+(3 rows)
+
+SELECT BOOLTBL1.*
+ FROM BOOLTBL1
+ WHERE f1 <> bool 'false';
+ f1
+----
+ t
+ t
+ t
+(3 rows)
+
+SELECT BOOLTBL1.*
+ FROM BOOLTBL1
+ WHERE booleq(bool 'false', f1);
+ f1
+----
+(0 rows)
+
+INSERT INTO BOOLTBL1 (f1) VALUES (bool 'f');
+SELECT BOOLTBL1.*
+ FROM BOOLTBL1
+ WHERE f1 = bool 'false';
+ f1
+----
+ f
+(1 row)
+
+CREATE TABLE BOOLTBL2 (f1 bool);
+INSERT INTO BOOLTBL2 (f1) VALUES (bool 'f');
+INSERT INTO BOOLTBL2 (f1) VALUES (bool 'false');
+INSERT INTO BOOLTBL2 (f1) VALUES (bool 'False');
+INSERT INTO BOOLTBL2 (f1) VALUES (bool 'FALSE');
+-- This is now an invalid expression
+-- For pre-v6.3 this evaluated to false - thomas 1997-10-23
+INSERT INTO BOOLTBL2 (f1)
+ VALUES (bool 'XXX');
+ERROR: invalid input syntax for type boolean: "XXX"
+LINE 2: VALUES (bool 'XXX');
+ ^
+-- BOOLTBL2 should be full of false's at this point
+SELECT BOOLTBL2.* FROM BOOLTBL2;
+ f1
+----
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT BOOLTBL1.*, BOOLTBL2.*
+ FROM BOOLTBL1, BOOLTBL2
+ WHERE BOOLTBL2.f1 <> BOOLTBL1.f1;
+ f1 | f1
+----+----
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+(12 rows)
+
+SELECT BOOLTBL1.*, BOOLTBL2.*
+ FROM BOOLTBL1, BOOLTBL2
+ WHERE boolne(BOOLTBL2.f1,BOOLTBL1.f1);
+ f1 | f1
+----+----
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+(12 rows)
+
+SELECT BOOLTBL1.*, BOOLTBL2.*
+ FROM BOOLTBL1, BOOLTBL2
+ WHERE BOOLTBL2.f1 = BOOLTBL1.f1 and BOOLTBL1.f1 = bool 'false';
+ f1 | f1
+----+----
+ f | f
+ f | f
+ f | f
+ f | f
+(4 rows)
+
+SELECT BOOLTBL1.*, BOOLTBL2.*
+ FROM BOOLTBL1, BOOLTBL2
+ WHERE BOOLTBL2.f1 = BOOLTBL1.f1 or BOOLTBL1.f1 = bool 'true'
+ ORDER BY BOOLTBL1.f1, BOOLTBL2.f1;
+ f1 | f1
+----+----
+ f | f
+ f | f
+ f | f
+ f | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+ t | f
+(16 rows)
+
+--
+-- SQL syntax
+-- Try all combinations to ensure that we get nothing when we expect nothing
+-- - thomas 2000-01-04
+--
+SELECT f1
+ FROM BOOLTBL1
+ WHERE f1 IS TRUE;
+ f1
+----
+ t
+ t
+ t
+(3 rows)
+
+SELECT f1
+ FROM BOOLTBL1
+ WHERE f1 IS NOT FALSE;
+ f1
+----
+ t
+ t
+ t
+(3 rows)
+
+SELECT f1
+ FROM BOOLTBL1
+ WHERE f1 IS FALSE;
+ f1
+----
+ f
+(1 row)
+
+SELECT f1
+ FROM BOOLTBL1
+ WHERE f1 IS NOT TRUE;
+ f1
+----
+ f
+(1 row)
+
+SELECT f1
+ FROM BOOLTBL2
+ WHERE f1 IS TRUE;
+ f1
+----
+(0 rows)
+
+SELECT f1
+ FROM BOOLTBL2
+ WHERE f1 IS NOT FALSE;
+ f1
+----
+(0 rows)
+
+SELECT f1
+ FROM BOOLTBL2
+ WHERE f1 IS FALSE;
+ f1
+----
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT f1
+ FROM BOOLTBL2
+ WHERE f1 IS NOT TRUE;
+ f1
+----
+ f
+ f
+ f
+ f
+(4 rows)
+
+--
+-- Tests for BooleanTest
+--
+CREATE TABLE BOOLTBL3 (d text, b bool, o int);
+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;
+ d | istrue | isnottrue | isfalse | isnotfalse | isunknown | isnotunknown
+-------+--------+-----------+---------+------------+-----------+--------------
+ true | t | f | f | t | f | t
+ false | f | t | t | f | f | t
+ null | f | t | f | t | t | f
+(3 rows)
+
+-- Test to make sure short-circuiting and NULL handling is
+-- correct. Use a table as source to prevent constant simplification
+-- to interfer.
+CREATE TABLE booltbl4(isfalse bool, istrue bool, isnul bool);
+INSERT INTO booltbl4 VALUES (false, true, null);
+\pset null '(null)'
+-- AND expression need to return null if there's any nulls and not all
+-- of the value are true
+SELECT istrue AND isnul AND istrue FROM booltbl4;
+ ?column?
+----------
+ (null)
+(1 row)
+
+SELECT istrue AND istrue AND isnul FROM booltbl4;
+ ?column?
+----------
+ (null)
+(1 row)
+
+SELECT isnul AND istrue AND istrue FROM booltbl4;
+ ?column?
+----------
+ (null)
+(1 row)
+
+SELECT isfalse AND isnul AND istrue FROM booltbl4;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT istrue AND isfalse AND isnul FROM booltbl4;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT isnul AND istrue AND isfalse FROM booltbl4;
+ ?column?
+----------
+ f
+(1 row)
+
+-- OR expression need to return null if there's any nulls and none
+-- of the value is true
+SELECT isfalse OR isnul OR isfalse FROM booltbl4;
+ ?column?
+----------
+ (null)
+(1 row)
+
+SELECT isfalse OR isfalse OR isnul FROM booltbl4;
+ ?column?
+----------
+ (null)
+(1 row)
+
+SELECT isnul OR isfalse OR isfalse FROM booltbl4;
+ ?column?
+----------
+ (null)
+(1 row)
+
+SELECT isfalse OR isnul OR istrue FROM booltbl4;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT istrue OR isfalse OR isnul FROM booltbl4;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT isnul OR istrue OR isfalse FROM booltbl4;
+ ?column?
+----------
+ t
+(1 row)
+
+--
+-- 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;
+DROP TABLE BOOLTBL4;
diff --git a/src/test/regress/expected/box.out b/src/test/regress/expected/box.out
new file mode 100644
index 0000000..6bf4d0b
--- /dev/null
+++ b/src/test/regress/expected/box.out
@@ -0,0 +1,641 @@
+--
+-- 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)');
+ERROR: invalid input syntax for type box: "(2.3, 4.5)"
+LINE 1: INSERT INTO BOX_TBL (f1) VALUES ('(2.3, 4.5)');
+ ^
+INSERT INTO BOX_TBL (f1) VALUES ('[1, 2, 3, 4)');
+ERROR: invalid input syntax for type box: "[1, 2, 3, 4)"
+LINE 1: INSERT INTO BOX_TBL (f1) VALUES ('[1, 2, 3, 4)');
+ ^
+INSERT INTO BOX_TBL (f1) VALUES ('(1, 2, 3, 4]');
+ERROR: invalid input syntax for type box: "(1, 2, 3, 4]"
+LINE 1: INSERT INTO BOX_TBL (f1) VALUES ('(1, 2, 3, 4]');
+ ^
+INSERT INTO BOX_TBL (f1) VALUES ('(1, 2, 3, 4) x');
+ERROR: invalid input syntax for type box: "(1, 2, 3, 4) x"
+LINE 1: INSERT INTO BOX_TBL (f1) VALUES ('(1, 2, 3, 4) x');
+ ^
+INSERT INTO BOX_TBL (f1) VALUES ('asdfasdf(ad');
+ERROR: invalid input syntax for type box: "asdfasdf(ad"
+LINE 1: INSERT INTO BOX_TBL (f1) VALUES ('asdfasdf(ad');
+ ^
+SELECT * FROM BOX_TBL;
+ f1
+---------------------
+ (2,2),(0,0)
+ (3,3),(1,1)
+ (-2,2),(-8,-10)
+ (2.5,3.5),(2.5,2.5)
+ (3,3),(3,3)
+(5 rows)
+
+SELECT b.*, area(b.f1) as barea
+ FROM BOX_TBL b;
+ f1 | barea
+---------------------+-------
+ (2,2),(0,0) | 4
+ (3,3),(1,1) | 4
+ (-2,2),(-8,-10) | 72
+ (2.5,3.5),(2.5,2.5) | 0
+ (3,3),(3,3) | 0
+(5 rows)
+
+-- overlap
+SELECT b.f1
+ FROM BOX_TBL b
+ WHERE b.f1 && box '(2.5,2.5,1.0,1.0)';
+ f1
+---------------------
+ (2,2),(0,0)
+ (3,3),(1,1)
+ (2.5,3.5),(2.5,2.5)
+(3 rows)
+
+-- left-or-overlap (x only)
+SELECT b1.*
+ FROM BOX_TBL b1
+ WHERE b1.f1 &< box '(2.0,2.0,2.5,2.5)';
+ f1
+---------------------
+ (2,2),(0,0)
+ (-2,2),(-8,-10)
+ (2.5,3.5),(2.5,2.5)
+(3 rows)
+
+-- right-or-overlap (x only)
+SELECT b1.*
+ FROM BOX_TBL b1
+ WHERE b1.f1 &> box '(2.0,2.0,2.5,2.5)';
+ f1
+---------------------
+ (2.5,3.5),(2.5,2.5)
+ (3,3),(3,3)
+(2 rows)
+
+-- left of
+SELECT b.f1
+ FROM BOX_TBL b
+ WHERE b.f1 << box '(3.0,3.0,5.0,5.0)';
+ f1
+---------------------
+ (2,2),(0,0)
+ (-2,2),(-8,-10)
+ (2.5,3.5),(2.5,2.5)
+(3 rows)
+
+-- area <=
+SELECT b.f1
+ FROM BOX_TBL b
+ WHERE b.f1 <= box '(3.0,3.0,5.0,5.0)';
+ f1
+---------------------
+ (2,2),(0,0)
+ (3,3),(1,1)
+ (2.5,3.5),(2.5,2.5)
+ (3,3),(3,3)
+(4 rows)
+
+-- area <
+SELECT b.f1
+ FROM BOX_TBL b
+ WHERE b.f1 < box '(3.0,3.0,5.0,5.0)';
+ f1
+---------------------
+ (2.5,3.5),(2.5,2.5)
+ (3,3),(3,3)
+(2 rows)
+
+-- area =
+SELECT b.f1
+ FROM BOX_TBL b
+ WHERE b.f1 = box '(3.0,3.0,5.0,5.0)';
+ f1
+-------------
+ (2,2),(0,0)
+ (3,3),(1,1)
+(2 rows)
+
+-- area >
+SELECT b.f1
+ FROM BOX_TBL b -- zero area
+ WHERE b.f1 > box '(3.5,3.0,4.5,3.0)';
+ f1
+-----------------
+ (2,2),(0,0)
+ (3,3),(1,1)
+ (-2,2),(-8,-10)
+(3 rows)
+
+-- area >=
+SELECT b.f1
+ FROM BOX_TBL b -- zero area
+ WHERE b.f1 >= box '(3.5,3.0,4.5,3.0)';
+ f1
+---------------------
+ (2,2),(0,0)
+ (3,3),(1,1)
+ (-2,2),(-8,-10)
+ (2.5,3.5),(2.5,2.5)
+ (3,3),(3,3)
+(5 rows)
+
+-- right of
+SELECT b.f1
+ FROM BOX_TBL b
+ WHERE box '(3.0,3.0,5.0,5.0)' >> b.f1;
+ f1
+---------------------
+ (2,2),(0,0)
+ (-2,2),(-8,-10)
+ (2.5,3.5),(2.5,2.5)
+(3 rows)
+
+-- contained in
+SELECT b.f1
+ FROM BOX_TBL b
+ WHERE b.f1 <@ box '(0,0,3,3)';
+ f1
+-------------
+ (2,2),(0,0)
+ (3,3),(1,1)
+ (3,3),(3,3)
+(3 rows)
+
+-- contains
+SELECT b.f1
+ FROM BOX_TBL b
+ WHERE box '(0,0,3,3)' @> b.f1;
+ f1
+-------------
+ (2,2),(0,0)
+ (3,3),(1,1)
+ (3,3),(3,3)
+(3 rows)
+
+-- box equality
+SELECT b.f1
+ FROM BOX_TBL b
+ WHERE box '(1,1,3,3)' ~= b.f1;
+ f1
+-------------
+ (3,3),(1,1)
+(1 row)
+
+-- center of box, left unary operator
+SELECT @@(b1.f1) AS p
+ FROM BOX_TBL b1;
+ p
+---------
+ (1,1)
+ (2,2)
+ (-5,-4)
+ (2.5,3)
+ (3,3)
+(5 rows)
+
+-- wholly-contained
+SELECT b1.*, b2.*
+ FROM BOX_TBL b1, BOX_TBL b2
+ WHERE b1.f1 @> b2.f1 and not b1.f1 ~= b2.f1;
+ f1 | f1
+-------------+-------------
+ (3,3),(1,1) | (3,3),(3,3)
+(1 row)
+
+SELECT height(f1), width(f1) FROM BOX_TBL;
+ height | width
+--------+-------
+ 2 | 2
+ 2 | 2
+ 12 | 6
+ 1 | 0
+ 0 | 0
+(5 rows)
+
+--
+-- 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)';
+ f1
+----------------------------
+ (2,2),(1,1)
+ (4,4),(2,2)
+ (6,6),(3,3)
+ (8,8),(4,4)
+ (0,100),(0,0)
+ (0,Infinity),(0,100)
+ (0,Infinity),(-Infinity,0)
+(7 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 << '(10,20),(30,40)';
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using box_spgist on box_temp
+ Index Cond: (f1 << '(30,40),(10,20)'::box)
+(2 rows)
+
+SELECT * FROM box_temp WHERE f1 &< '(10,4.333334),(5,100)';
+ f1
+----------------------------
+ (2,2),(1,1)
+ (4,4),(2,2)
+ (6,6),(3,3)
+ (8,8),(4,4)
+ (10,10),(5,5)
+ (0,100),(0,0)
+ (0,Infinity),(0,100)
+ (0,Infinity),(-Infinity,0)
+(8 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 &< '(10,4.333334),(5,100)';
+ QUERY PLAN
+----------------------------------------------------
+ Index Only Scan using box_spgist on box_temp
+ Index Cond: (f1 &< '(10,100),(5,4.333334)'::box)
+(2 rows)
+
+SELECT * FROM box_temp WHERE f1 && '(15,20),(25,30)';
+ f1
+-------------------------------------------
+ (20,20),(10,10)
+ (22,22),(11,11)
+ (24,24),(12,12)
+ (26,26),(13,13)
+ (28,28),(14,14)
+ (30,30),(15,15)
+ (32,32),(16,16)
+ (34,34),(17,17)
+ (36,36),(18,18)
+ (38,38),(19,19)
+ (40,40),(20,20)
+ (42,42),(21,21)
+ (44,44),(22,22)
+ (46,46),(23,23)
+ (48,48),(24,24)
+ (50,50),(25,25)
+ (Infinity,Infinity),(-Infinity,-Infinity)
+(17 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 && '(15,20),(25,30)';
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using box_spgist on box_temp
+ Index Cond: (f1 && '(25,30),(15,20)'::box)
+(2 rows)
+
+SELECT * FROM box_temp WHERE f1 &> '(40,30),(45,50)';
+ f1
+-------------------
+ (80,80),(40,40)
+ (82,82),(41,41)
+ (84,84),(42,42)
+ (86,86),(43,43)
+ (88,88),(44,44)
+ (90,90),(45,45)
+ (92,92),(46,46)
+ (94,94),(47,47)
+ (96,96),(48,48)
+ (98,98),(49,49)
+ (100,100),(50,50)
+(11 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 &> '(40,30),(45,50)';
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using box_spgist on box_temp
+ Index Cond: (f1 &> '(45,50),(40,30)'::box)
+(2 rows)
+
+SELECT * FROM box_temp WHERE f1 >> '(30,40),(40,30)';
+ f1
+-------------------
+ (82,82),(41,41)
+ (84,84),(42,42)
+ (86,86),(43,43)
+ (88,88),(44,44)
+ (90,90),(45,45)
+ (92,92),(46,46)
+ (94,94),(47,47)
+ (96,96),(48,48)
+ (98,98),(49,49)
+ (100,100),(50,50)
+(10 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 >> '(30,40),(40,30)';
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using box_spgist on box_temp
+ Index Cond: (f1 >> '(40,40),(30,30)'::box)
+(2 rows)
+
+SELECT * FROM box_temp WHERE f1 <<| '(10,4.33334),(5,100)';
+ f1
+--------------------------
+ (2,2),(1,1)
+ (4,4),(2,2)
+ (40,4.3333333333),(-3,1)
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 <<| '(10,4.33334),(5,100)';
+ QUERY PLAN
+----------------------------------------------------
+ Index Only Scan using box_spgist on box_temp
+ Index Cond: (f1 <<| '(10,100),(5,4.33334)'::box)
+(2 rows)
+
+SELECT * FROM box_temp WHERE f1 &<| '(10,4.3333334),(5,1)';
+ f1
+--------------------------
+ (2,2),(1,1)
+ (4,4),(2,2)
+ (40,4.3333333333),(-3,1)
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 &<| '(10,4.3333334),(5,1)';
+ QUERY PLAN
+----------------------------------------------------
+ Index Only Scan using box_spgist on box_temp
+ Index Cond: (f1 &<| '(10,4.3333334),(5,1)'::box)
+(2 rows)
+
+SELECT * FROM box_temp WHERE f1 |&> '(49.99,49.99),(49.99,49.99)';
+ f1
+----------------------
+ (100,100),(50,50)
+ (0,Infinity),(0,100)
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 |&> '(49.99,49.99),(49.99,49.99)';
+ QUERY PLAN
+-----------------------------------------------------------
+ Index Only Scan using box_spgist on box_temp
+ Index Cond: (f1 |&> '(49.99,49.99),(49.99,49.99)'::box)
+(2 rows)
+
+SELECT * FROM box_temp WHERE f1 |>> '(37,38),(39,40)';
+ f1
+----------------------
+ (82,82),(41,41)
+ (84,84),(42,42)
+ (86,86),(43,43)
+ (88,88),(44,44)
+ (90,90),(45,45)
+ (92,92),(46,46)
+ (94,94),(47,47)
+ (96,96),(48,48)
+ (98,98),(49,49)
+ (100,100),(50,50)
+ (0,Infinity),(0,100)
+(11 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 |>> '(37,38),(39,40)';
+ QUERY PLAN
+-----------------------------------------------
+ Index Only Scan using box_spgist on box_temp
+ Index Cond: (f1 |>> '(39,40),(37,38)'::box)
+(2 rows)
+
+SELECT * FROM box_temp WHERE f1 @> '(10,11),(15,16)';
+ f1
+-------------------------------------------
+ (16,16),(8,8)
+ (18,18),(9,9)
+ (20,20),(10,10)
+ (Infinity,Infinity),(-Infinity,-Infinity)
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 @> '(10,11),(15,15)';
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using box_spgist on box_temp
+ Index Cond: (f1 @> '(15,15),(10,11)'::box)
+(2 rows)
+
+SELECT * FROM box_temp WHERE f1 <@ '(10,15),(30,35)';
+ f1
+-----------------
+ (30,30),(15,15)
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 <@ '(10,15),(30,35)';
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using box_spgist on box_temp
+ Index Cond: (f1 <@ '(30,35),(10,15)'::box)
+(2 rows)
+
+SELECT * FROM box_temp WHERE f1 ~= '(20,20),(40,40)';
+ f1
+-----------------
+ (40,40),(20,20)
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 ~= '(20,20),(40,40)';
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using box_spgist on box_temp
+ Index Cond: (f1 ~= '(40,40),(20,20)'::box)
+(2 rows)
+
+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))';
+ count
+-------
+ 901
+(1 row)
+
+SELECT count(*) FROM quad_box_tbl WHERE b &< box '((100,200),(300,500))';
+ count
+-------
+ 3901
+(1 row)
+
+SELECT count(*) FROM quad_box_tbl WHERE b && box '((100,200),(300,500))';
+ count
+-------
+ 1653
+(1 row)
+
+SELECT count(*) FROM quad_box_tbl WHERE b &> box '((100,200),(300,500))';
+ count
+-------
+ 10100
+(1 row)
+
+SELECT count(*) FROM quad_box_tbl WHERE b >> box '((100,200),(300,500))';
+ count
+-------
+ 7000
+(1 row)
+
+SELECT count(*) FROM quad_box_tbl WHERE b >> box '((100,200),(300,500))';
+ count
+-------
+ 7000
+(1 row)
+
+SELECT count(*) FROM quad_box_tbl WHERE b <<| box '((100,200),(300,500))';
+ count
+-------
+ 1900
+(1 row)
+
+SELECT count(*) FROM quad_box_tbl WHERE b &<| box '((100,200),(300,500))';
+ count
+-------
+ 5901
+(1 row)
+
+SELECT count(*) FROM quad_box_tbl WHERE b |&> box '((100,200),(300,500))';
+ count
+-------
+ 9100
+(1 row)
+
+SELECT count(*) FROM quad_box_tbl WHERE b |>> box '((100,200),(300,500))';
+ count
+-------
+ 5000
+(1 row)
+
+SELECT count(*) FROM quad_box_tbl WHERE b @> box '((201,301),(202,303))';
+ count
+-------
+ 1003
+(1 row)
+
+SELECT count(*) FROM quad_box_tbl WHERE b <@ box '((100,200),(300,500))';
+ count
+-------
+ 1600
+(1 row)
+
+SELECT count(*) FROM quad_box_tbl WHERE b ~= box '((200,300),(205,305))';
+ count
+-------
+ 1
+(1 row)
+
+-- 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;
+ QUERY PLAN
+---------------------------------------------------------
+ WindowAgg
+ -> Index Scan using quad_box_tbl_idx on quad_box_tbl
+ Order By: (b <-> '(123,456)'::point)
+(3 rows)
+
+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;
+ n | dist | id | n | dist | id
+---+------+----+---+------+----
+(0 rows)
+
+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))';
+ QUERY PLAN
+---------------------------------------------------------
+ WindowAgg
+ -> Index Scan using quad_box_tbl_idx on quad_box_tbl
+ Index Cond: (b <@ '(500,600),(200,300)'::box)
+ Order By: (b <-> '(123,456)'::point)
+(4 rows)
+
+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;
+ n | dist | id | n | dist | id
+---+------+----+---+------+----
+(0 rows)
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/expected/brin.out b/src/test/regress/expected/brin.out
new file mode 100644
index 0000000..e53d6e4
--- /dev/null
+++ b/src/test/regress/expected/brin.out
@@ -0,0 +1,569 @@
+CREATE TABLE brintest (byteacol bytea,
+ charcol "char",
+ namecol name,
+ int8col bigint,
+ int2col smallint,
+ int4col integer,
+ textcol text,
+ oidcol oid,
+ tidcol tid,
+ float4col real,
+ float8col double precision,
+ macaddrcol macaddr,
+ inetcol inet,
+ cidrcol cidr,
+ bpcharcol character,
+ datecol date,
+ timecol time without time zone,
+ timestampcol timestamp without time zone,
+ timestamptzcol timestamp with time zone,
+ intervalcol interval,
+ timetzcol time with time zone,
+ bitcol bit(10),
+ varbitcol bit varying(16),
+ numericcol numeric,
+ uuidcol uuid,
+ int4rangecol int4range,
+ lsncol pg_lsn,
+ boxcol box
+) WITH (fillfactor=10, autovacuum_enabled=off);
+INSERT INTO brintest SELECT
+ repeat(stringu1, 8)::bytea,
+ substr(stringu1, 1, 1)::"char",
+ stringu1::name, 142857 * tenthous,
+ thousand,
+ twothousand,
+ repeat(stringu1, 8),
+ unique1::oid,
+ format('(%s,%s)', tenthous, twenty)::tid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ inet '10.2.3.4/24' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ substr(stringu1, 1, 1)::bpchar,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20+02' + hundred * interval '15 seconds',
+ thousand::bit(10),
+ tenthous::bit(16)::varbit,
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ int4range(thousand, twothousand),
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn,
+ box(point(odd, even), point(thousand, twothousand))
+FROM tenk1 ORDER BY unique2 LIMIT 100;
+-- throw in some NULL's and different values
+INSERT INTO brintest (inetcol, cidrcol, int4rangecol) SELECT
+ inet 'fe80::6e40:8ff:fea9:8c46' + tenthous,
+ cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous,
+ 'empty'::int4range
+FROM tenk1 ORDER BY thousand, tenthous LIMIT 25;
+CREATE INDEX brinidx ON brintest USING brin (
+ byteacol,
+ charcol,
+ namecol,
+ int8col,
+ int2col,
+ int4col,
+ textcol,
+ oidcol,
+ tidcol,
+ float4col,
+ float8col,
+ macaddrcol,
+ inetcol inet_inclusion_ops,
+ inetcol inet_minmax_ops,
+ cidrcol inet_inclusion_ops,
+ cidrcol inet_minmax_ops,
+ bpcharcol,
+ datecol,
+ timecol,
+ timestampcol,
+ timestamptzcol,
+ intervalcol,
+ timetzcol,
+ bitcol,
+ varbitcol,
+ numericcol,
+ uuidcol,
+ int4rangecol,
+ lsncol,
+ boxcol
+) with (pages_per_range = 1);
+CREATE TABLE brinopers (colname name, typ text,
+ op text[], value text[], matches int[],
+ check (cardinality(op) = cardinality(value)),
+ check (cardinality(op) = cardinality(matches)));
+INSERT INTO brinopers VALUES
+ ('byteacol', 'bytea',
+ '{>, >=, =, <=, <}',
+ '{AAAAAA, AAAAAA, BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA, ZZZZZZ, ZZZZZZ}',
+ '{100, 100, 1, 100, 100}'),
+ ('charcol', '"char"',
+ '{>, >=, =, <=, <}',
+ '{A, A, M, Z, Z}',
+ '{97, 100, 6, 100, 98}'),
+ ('namecol', 'name',
+ '{>, >=, =, <=, <}',
+ '{AAAAAA, AAAAAA, MAAAAA, ZZAAAA, ZZAAAA}',
+ '{100, 100, 2, 100, 100}'),
+ ('int2col', 'int2',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 999, 999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int2col', 'int4',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 999, 1999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int2col', 'int8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 999, 1428427143}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4col', 'int2',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 1999, 1999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4col', 'int4',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 1999, 1999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4col', 'int8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 1999, 1428427143}',
+ '{100, 100, 1, 100, 100}'),
+ ('int8col', 'int2',
+ '{>, >=}',
+ '{0, 0}',
+ '{100, 100}'),
+ ('int8col', 'int4',
+ '{>, >=}',
+ '{0, 0}',
+ '{100, 100}'),
+ ('int8col', 'int8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 1257141600, 1428427143, 1428427143}',
+ '{100, 100, 1, 100, 100}'),
+ ('textcol', 'text',
+ '{>, >=, =, <=, <}',
+ '{ABABAB, ABABAB, BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA, ZZAAAA, ZZAAAA}',
+ '{100, 100, 1, 100, 100}'),
+ ('oidcol', 'oid',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 8800, 9999, 9999}',
+ '{100, 100, 1, 100, 100}'),
+ ('tidcol', 'tid',
+ '{>, >=, =, <=, <}',
+ '{"(0,0)", "(0,0)", "(8800,0)", "(9999,19)", "(9999,19)"}',
+ '{100, 100, 1, 100, 100}'),
+ ('float4col', 'float4',
+ '{>, >=, =, <=, <}',
+ '{0.0103093, 0.0103093, 1, 1, 1}',
+ '{100, 100, 4, 100, 96}'),
+ ('float4col', 'float8',
+ '{>, >=, =, <=, <}',
+ '{0.0103093, 0.0103093, 1, 1, 1}',
+ '{100, 100, 4, 100, 96}'),
+ ('float8col', 'float4',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 0, 1.98, 1.98}',
+ '{99, 100, 1, 100, 100}'),
+ ('float8col', 'float8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 0, 1.98, 1.98}',
+ '{99, 100, 1, 100, 100}'),
+ ('macaddrcol', 'macaddr',
+ '{>, >=, =, <=, <}',
+ '{00:00:01:00:00:00, 00:00:01:00:00:00, 2c:00:2d:00:16:00, ff:fe:00:00:00:00, ff:fe:00:00:00:00}',
+ '{99, 100, 2, 100, 100}'),
+ ('inetcol', 'inet',
+ '{&&, =, <, <=, >, >=, >>=, >>, <<=, <<}',
+ '{10/8, 10.2.14.231/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0, 10.2.14.231/24, 10.2.14.231/25, 10.2.14.231/8, 0/0}',
+ '{100, 1, 100, 100, 125, 125, 2, 2, 100, 100}'),
+ ('inetcol', 'inet',
+ '{&&, >>=, <<=, =}',
+ '{fe80::6e40:8ff:fea9:a673/32, fe80::6e40:8ff:fea9:8c46, fe80::6e40:8ff:fea9:a673/32, fe80::6e40:8ff:fea9:8c46}',
+ '{25, 1, 25, 1}'),
+ ('inetcol', 'cidr',
+ '{&&, <, <=, >, >=, >>=, >>, <<=, <<}',
+ '{10/8, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0, 10.2.14/24, 10.2.14/25, 10/8, 0/0}',
+ '{100, 100, 100, 125, 125, 2, 2, 100, 100}'),
+ ('inetcol', 'cidr',
+ '{&&, >>=, <<=, =}',
+ '{fe80::/32, fe80::6e40:8ff:fea9:8c46, fe80::/32, fe80::6e40:8ff:fea9:8c46}',
+ '{25, 1, 25, 1}'),
+ ('cidrcol', 'inet',
+ '{&&, =, <, <=, >, >=, >>=, >>, <<=, <<}',
+ '{10/8, 10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0, 10.2.14.231/24, 10.2.14.231/25, 10.2.14.231/8, 0/0}',
+ '{100, 2, 100, 100, 125, 125, 2, 2, 100, 100}'),
+ ('cidrcol', 'inet',
+ '{&&, >>=, <<=, =}',
+ '{fe80::6e40:8ff:fea9:a673/32, fe80::6e40:8ff:fea9:8c46, fe80::6e40:8ff:fea9:a673/32, fe80::6e40:8ff:fea9:8c46}',
+ '{25, 1, 25, 1}'),
+ ('cidrcol', 'cidr',
+ '{&&, =, <, <=, >, >=, >>=, >>, <<=, <<}',
+ '{10/8, 10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0, 10.2.14/24, 10.2.14/25, 10/8, 0/0}',
+ '{100, 2, 100, 100, 125, 125, 2, 2, 100, 100}'),
+ ('cidrcol', 'cidr',
+ '{&&, >>=, <<=, =}',
+ '{fe80::/32, fe80::6e40:8ff:fea9:8c46, fe80::/32, fe80::6e40:8ff:fea9:8c46}',
+ '{25, 1, 25, 1}'),
+ ('bpcharcol', 'bpchar',
+ '{>, >=, =, <=, <}',
+ '{A, A, W, Z, Z}',
+ '{97, 100, 6, 100, 98}'),
+ ('datecol', 'date',
+ '{>, >=, =, <=, <}',
+ '{1995-08-15, 1995-08-15, 2009-12-01, 2022-12-30, 2022-12-30}',
+ '{100, 100, 1, 100, 100}'),
+ ('timecol', 'time',
+ '{>, >=, =, <=, <}',
+ '{01:20:30, 01:20:30, 02:28:57, 06:28:31.5, 06:28:31.5}',
+ '{100, 100, 1, 100, 100}'),
+ ('timestampcol', 'timestamp',
+ '{>, >=, =, <=, <}',
+ '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}',
+ '{100, 100, 1, 100, 100}'),
+ ('timestampcol', 'timestamptz',
+ '{>, >=, =, <=, <}',
+ '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}',
+ '{100, 100, 1, 100, 100}'),
+ ('timestamptzcol', 'timestamptz',
+ '{>, >=, =, <=, <}',
+ '{1972-10-10 03:00:00-04, 1972-10-10 03:00:00-04, 1972-10-19 09:00:00-07, 1972-11-20 19:00:00-03, 1972-11-20 19:00:00-03}',
+ '{100, 100, 1, 100, 100}'),
+ ('intervalcol', 'interval',
+ '{>, >=, =, <=, <}',
+ '{00:00:00, 00:00:00, 1 mons 13 days 12:24, 2 mons 23 days 07:48:00, 1 year}',
+ '{100, 100, 1, 100, 100}'),
+ ('timetzcol', 'timetz',
+ '{>, >=, =, <=, <}',
+ '{01:30:20+02, 01:30:20+02, 01:35:50+02, 23:55:05+02, 23:55:05+02}',
+ '{99, 100, 2, 100, 100}'),
+ ('bitcol', 'bit(10)',
+ '{>, >=, =, <=, <}',
+ '{0000000010, 0000000010, 0011011110, 1111111000, 1111111000}',
+ '{100, 100, 1, 100, 100}'),
+ ('varbitcol', 'varbit(16)',
+ '{>, >=, =, <=, <}',
+ '{0000000000000100, 0000000000000100, 0001010001100110, 1111111111111000, 1111111111111000}',
+ '{100, 100, 1, 100, 100}'),
+ ('numericcol', 'numeric',
+ '{>, >=, =, <=, <}',
+ '{0.00, 0.01, 2268164.347826086956521739130434782609, 99470151.9, 99470151.9}',
+ '{100, 100, 1, 100, 100}'),
+ ('uuidcol', 'uuid',
+ '{>, >=, =, <=, <}',
+ '{00040004-0004-0004-0004-000400040004, 00040004-0004-0004-0004-000400040004, 52225222-5222-5222-5222-522252225222, 99989998-9998-9998-9998-999899989998, 99989998-9998-9998-9998-999899989998}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4rangecol', 'int4range',
+ '{<<, &<, &&, &>, >>, @>, <@, =, <, <=, >, >=}',
+ '{"[10000,)","[10000,)","(,]","[3,4)","[36,44)","(1500,1501]","[3,4)","[222,1222)","[36,44)","[43,1043)","[367,4466)","[519,)"}',
+ '{53, 53, 53, 53, 50, 22, 72, 1, 74, 75, 34, 21}'),
+ ('int4rangecol', 'int4range',
+ '{@>, <@, =, <=, >, >=}',
+ '{empty, empty, empty, empty, empty, empty}',
+ '{125, 72, 72, 72, 53, 125}'),
+ ('int4rangecol', 'int4',
+ '{@>}',
+ '{1500}',
+ '{22}'),
+ ('lsncol', 'pg_lsn',
+ '{>, >=, =, <=, <, IS, IS NOT}',
+ '{0/1200, 0/1200, 44/455222, 198/1999799, 198/1999799, NULL, NULL}',
+ '{100, 100, 1, 100, 100, 25, 100}'),
+ ('boxcol', 'point',
+ '{@>}',
+ '{"(500,43)"}',
+ '{11}'),
+ ('boxcol', 'box',
+ '{<<, &<, &&, &>, >>, <<|, &<|, |&>, |>>, @>, <@, ~=}',
+ '{"((1000,2000),(3000,4000))","((1,2),(3000,4000))","((1,2),(3000,4000))","((1,2),(3000,4000))","((1,2),(3,4))","((1000,2000),(3000,4000))","((1,2000),(3,4000))","((1000,2),(3000,4))","((1,2),(3,4))","((1,2),(300,400))","((1,2),(3000,4000))","((222,1222),(44,45))"}',
+ '{100, 100, 100, 99, 96, 100, 100, 99, 96, 1, 99, 1}');
+DO $x$
+DECLARE
+ r record;
+ r2 record;
+ cond text;
+ idx_ctids tid[];
+ ss_ctids tid[];
+ count int;
+ plan_ok bool;
+ plan_line text;
+BEGIN
+ FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers, unnest(op) WITH ORDINALITY AS oper LOOP
+
+ -- prepare the condition
+ IF r.value IS NULL THEN
+ cond := format('%I %s %L', r.colname, r.oper, r.value);
+ ELSE
+ cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ);
+ END IF;
+
+ -- run the query using the brin index
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Bitmap Heap Scan on brintest%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get bitmap indexscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest WHERE %s $y$, cond)
+ INTO idx_ctids;
+
+ -- run the query using a seqscan
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Seq Scan on brintest%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get seqscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest WHERE %s $y$, cond)
+ INTO ss_ctids;
+
+ -- make sure both return the same results
+ count := array_length(idx_ctids, 1);
+
+ IF NOT (count = array_length(ss_ctids, 1) AND
+ idx_ctids @> ss_ctids AND
+ idx_ctids <@ ss_ctids) THEN
+ -- report the results of each scan to make the differences obvious
+ RAISE WARNING 'something not right in %: count %', r, count;
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest WHERE ' || cond LOOP
+ RAISE NOTICE 'seqscan: %', r2;
+ END LOOP;
+
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest WHERE ' || cond LOOP
+ RAISE NOTICE 'bitmapscan: %', r2;
+ END LOOP;
+ END IF;
+
+ -- make sure we found expected number of matches
+ IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF;
+ END LOOP;
+END;
+$x$;
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+INSERT INTO brintest SELECT
+ repeat(stringu1, 42)::bytea,
+ substr(stringu1, 1, 1)::"char",
+ stringu1::name, 142857 * tenthous,
+ thousand,
+ twothousand,
+ repeat(stringu1, 42),
+ unique1::oid,
+ format('(%s,%s)', tenthous, twenty)::tid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ inet '10.2.3.4' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ substr(stringu1, 1, 1)::bpchar,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20' + hundred * interval '15 seconds',
+ thousand::bit(10),
+ tenthous::bit(16)::varbit,
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ int4range(thousand, twothousand),
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn,
+ box(point(odd, even), point(thousand, twothousand))
+FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5;
+SELECT brin_desummarize_range('brinidx', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+VACUUM brintest; -- force a summarization cycle in brinidx
+UPDATE brintest SET int8col = int8col * int4col;
+UPDATE brintest SET textcol = '' WHERE textcol IS NOT NULL;
+-- Tests for brin_summarize_new_values
+SELECT brin_summarize_new_values('brintest'); -- error, not an index
+ERROR: "brintest" is not an index
+SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index
+ERROR: "tenk1_unique1" is not a BRIN index
+SELECT brin_summarize_new_values('brinidx'); -- ok, no change expected
+ brin_summarize_new_values
+---------------------------
+ 0
+(1 row)
+
+-- Tests for brin_desummarize_range
+SELECT brin_desummarize_range('brinidx', -1); -- error, invalid range
+ERROR: block number out of range: -1
+SELECT brin_desummarize_range('brinidx', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+SELECT brin_desummarize_range('brinidx', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+SELECT brin_desummarize_range('brinidx', 100000000);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+-- Test brin_summarize_range
+CREATE TABLE brin_summarize (
+ value int
+) WITH (fillfactor=10, autovacuum_enabled=false);
+CREATE INDEX brin_summarize_idx ON brin_summarize USING brin (value) WITH (pages_per_range=2);
+-- Fill a few pages
+DO $$
+DECLARE curtid tid;
+BEGIN
+ LOOP
+ INSERT INTO brin_summarize VALUES (1) RETURNING ctid INTO curtid;
+ EXIT WHEN curtid > tid '(2, 0)';
+ END LOOP;
+END;
+$$;
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_idx', 0);
+ brin_summarize_range
+----------------------
+ 0
+(1 row)
+
+-- nothing: already summarized
+SELECT brin_summarize_range('brin_summarize_idx', 1);
+ brin_summarize_range
+----------------------
+ 0
+(1 row)
+
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_idx', 2);
+ brin_summarize_range
+----------------------
+ 1
+(1 row)
+
+-- nothing: page doesn't exist in table
+SELECT brin_summarize_range('brin_summarize_idx', 4294967295);
+ brin_summarize_range
+----------------------
+ 0
+(1 row)
+
+-- invalid block number values
+SELECT brin_summarize_range('brin_summarize_idx', -1);
+ERROR: block number out of range: -1
+SELECT brin_summarize_range('brin_summarize_idx', 4294967296);
+ERROR: block number out of range: 4294967296
+-- test value merging in add_value
+CREATE TABLE brintest_2 (n numrange);
+CREATE INDEX brinidx_2 ON brintest_2 USING brin (n);
+INSERT INTO brintest_2 VALUES ('empty');
+INSERT INTO brintest_2 VALUES (numrange(0, 2^1000::numeric));
+INSERT INTO brintest_2 VALUES ('(-1, 0)');
+SELECT brin_desummarize_range('brinidx', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+SELECT brin_summarize_range('brinidx', 0);
+ brin_summarize_range
+----------------------
+ 1
+(1 row)
+
+DROP TABLE brintest_2;
+-- test brin cost estimates behave sanely based on correlation of values
+CREATE TABLE brin_test (a INT, b INT);
+INSERT INTO brin_test SELECT x/100,x%100 FROM generate_series(1,10000) x(x);
+CREATE INDEX brin_test_a_idx ON brin_test USING brin (a) WITH (pages_per_range = 2);
+CREATE INDEX brin_test_b_idx ON brin_test USING brin (b) WITH (pages_per_range = 2);
+VACUUM ANALYZE brin_test;
+-- Ensure brin index is used when columns are perfectly correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test WHERE a = 1;
+ QUERY PLAN
+--------------------------------------------
+ Bitmap Heap Scan on brin_test
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on brin_test_a_idx
+ Index Cond: (a = 1)
+(4 rows)
+
+-- Ensure brin index is not used when values are not correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test WHERE b = 1;
+ QUERY PLAN
+-----------------------
+ Seq Scan on brin_test
+ Filter: (b = 1)
+(2 rows)
+
+-- make sure data are properly de-toasted in BRIN index
+CREATE TABLE brintest_3 (a text, b text, c text, d text);
+-- long random strings (~2000 chars each, so ~6kB for min/max on two
+-- columns) to trigger toasting
+WITH rand_value AS (SELECT string_agg(md5(i::text),'') AS val FROM generate_series(1,60) s(i))
+INSERT INTO brintest_3
+SELECT val, val, val, val FROM rand_value;
+CREATE INDEX brin_test_toast_idx ON brintest_3 USING brin (b, c);
+DELETE FROM brintest_3;
+-- We need to wait a bit for all transactions to complete, so that the
+-- vacuum actually removes the TOAST rows. Creating an index concurrently
+-- is a one way to achieve that, because it does exactly such wait.
+CREATE INDEX CONCURRENTLY brin_test_temp_idx ON brintest_3(a);
+DROP INDEX brin_test_temp_idx;
+-- vacuum the table, to discard TOAST data
+VACUUM brintest_3;
+-- retry insert with a different random-looking (but deterministic) value
+-- the value is different, and so should replace either min or max in the
+-- brin summary
+WITH rand_value AS (SELECT string_agg(md5((-i)::text),'') AS val FROM generate_series(1,60) s(i))
+INSERT INTO brintest_3
+SELECT val, val, val, val FROM rand_value;
+-- now try some queries, accessing the brin index
+SET enable_seqscan = off;
+EXPLAIN (COSTS OFF)
+SELECT * FROM brintest_3 WHERE b < '0';
+ QUERY PLAN
+------------------------------------------------
+ Bitmap Heap Scan on brintest_3
+ Recheck Cond: (b < '0'::text)
+ -> Bitmap Index Scan on brin_test_toast_idx
+ Index Cond: (b < '0'::text)
+(4 rows)
+
+SELECT * FROM brintest_3 WHERE b < '0';
+ a | b | c | d
+---+---+---+---
+(0 rows)
+
+DROP TABLE brintest_3;
+RESET enable_seqscan;
diff --git a/src/test/regress/expected/brin_bloom.out b/src/test/regress/expected/brin_bloom.out
new file mode 100644
index 0000000..32c56a9
--- /dev/null
+++ b/src/test/regress/expected/brin_bloom.out
@@ -0,0 +1,428 @@
+CREATE TABLE brintest_bloom (byteacol bytea,
+ charcol "char",
+ namecol name,
+ int8col bigint,
+ int2col smallint,
+ int4col integer,
+ textcol text,
+ oidcol oid,
+ float4col real,
+ float8col double precision,
+ macaddrcol macaddr,
+ inetcol inet,
+ cidrcol cidr,
+ bpcharcol character,
+ datecol date,
+ timecol time without time zone,
+ timestampcol timestamp without time zone,
+ timestamptzcol timestamp with time zone,
+ intervalcol interval,
+ timetzcol time with time zone,
+ numericcol numeric,
+ uuidcol uuid,
+ lsncol pg_lsn
+) WITH (fillfactor=10);
+INSERT INTO brintest_bloom SELECT
+ repeat(stringu1, 8)::bytea,
+ substr(stringu1, 1, 1)::"char",
+ stringu1::name, 142857 * tenthous,
+ thousand,
+ twothousand,
+ repeat(stringu1, 8),
+ unique1::oid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ inet '10.2.3.4/24' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ substr(stringu1, 1, 1)::bpchar,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20+02' + hundred * interval '15 seconds',
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn
+FROM tenk1 ORDER BY unique2 LIMIT 100;
+-- throw in some NULL's and different values
+INSERT INTO brintest_bloom (inetcol, cidrcol) SELECT
+ inet 'fe80::6e40:8ff:fea9:8c46' + tenthous,
+ cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous
+FROM tenk1 ORDER BY thousand, tenthous LIMIT 25;
+-- test bloom specific index options
+-- ndistinct must be >= -1.0
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops(n_distinct_per_range = -1.1)
+);
+ERROR: value -1.1 out of bounds for option "n_distinct_per_range"
+DETAIL: Valid values are between "-1.000000" and "2147483647.000000".
+-- false_positive_rate must be between 0.0001 and 0.25
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops(false_positive_rate = 0.00009)
+);
+ERROR: value 0.00009 out of bounds for option "false_positive_rate"
+DETAIL: Valid values are between "0.000100" and "0.250000".
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops(false_positive_rate = 0.26)
+);
+ERROR: value 0.26 out of bounds for option "false_positive_rate"
+DETAIL: Valid values are between "0.000100" and "0.250000".
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops,
+ charcol char_bloom_ops,
+ namecol name_bloom_ops,
+ int8col int8_bloom_ops,
+ int2col int2_bloom_ops,
+ int4col int4_bloom_ops,
+ textcol text_bloom_ops,
+ oidcol oid_bloom_ops,
+ float4col float4_bloom_ops,
+ float8col float8_bloom_ops,
+ macaddrcol macaddr_bloom_ops,
+ inetcol inet_bloom_ops,
+ cidrcol inet_bloom_ops,
+ bpcharcol bpchar_bloom_ops,
+ datecol date_bloom_ops,
+ timecol time_bloom_ops,
+ timestampcol timestamp_bloom_ops,
+ timestamptzcol timestamptz_bloom_ops,
+ intervalcol interval_bloom_ops,
+ timetzcol timetz_bloom_ops,
+ numericcol numeric_bloom_ops,
+ uuidcol uuid_bloom_ops,
+ lsncol pg_lsn_bloom_ops
+) with (pages_per_range = 1);
+CREATE TABLE brinopers_bloom (colname name, typ text,
+ op text[], value text[], matches int[],
+ check (cardinality(op) = cardinality(value)),
+ check (cardinality(op) = cardinality(matches)));
+INSERT INTO brinopers_bloom VALUES
+ ('byteacol', 'bytea',
+ '{=}',
+ '{BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA}',
+ '{1}'),
+ ('charcol', '"char"',
+ '{=}',
+ '{M}',
+ '{6}'),
+ ('namecol', 'name',
+ '{=}',
+ '{MAAAAA}',
+ '{2}'),
+ ('int2col', 'int2',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int4col', 'int4',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int8col', 'int8',
+ '{=}',
+ '{1257141600}',
+ '{1}'),
+ ('textcol', 'text',
+ '{=}',
+ '{BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA}',
+ '{1}'),
+ ('oidcol', 'oid',
+ '{=}',
+ '{8800}',
+ '{1}'),
+ ('float4col', 'float4',
+ '{=}',
+ '{1}',
+ '{4}'),
+ ('float8col', 'float8',
+ '{=}',
+ '{0}',
+ '{1}'),
+ ('macaddrcol', 'macaddr',
+ '{=}',
+ '{2c:00:2d:00:16:00}',
+ '{2}'),
+ ('inetcol', 'inet',
+ '{=}',
+ '{10.2.14.231/24}',
+ '{1}'),
+ ('inetcol', 'cidr',
+ '{=}',
+ '{fe80::6e40:8ff:fea9:8c46}',
+ '{1}'),
+ ('cidrcol', 'inet',
+ '{=}',
+ '{10.2.14/24}',
+ '{2}'),
+ ('cidrcol', 'inet',
+ '{=}',
+ '{fe80::6e40:8ff:fea9:8c46}',
+ '{1}'),
+ ('cidrcol', 'cidr',
+ '{=}',
+ '{10.2.14/24}',
+ '{2}'),
+ ('cidrcol', 'cidr',
+ '{=}',
+ '{fe80::6e40:8ff:fea9:8c46}',
+ '{1}'),
+ ('bpcharcol', 'bpchar',
+ '{=}',
+ '{W}',
+ '{6}'),
+ ('datecol', 'date',
+ '{=}',
+ '{2009-12-01}',
+ '{1}'),
+ ('timecol', 'time',
+ '{=}',
+ '{02:28:57}',
+ '{1}'),
+ ('timestampcol', 'timestamp',
+ '{=}',
+ '{1964-03-24 19:26:45}',
+ '{1}'),
+ ('timestamptzcol', 'timestamptz',
+ '{=}',
+ '{1972-10-19 09:00:00-07}',
+ '{1}'),
+ ('intervalcol', 'interval',
+ '{=}',
+ '{1 mons 13 days 12:24}',
+ '{1}'),
+ ('timetzcol', 'timetz',
+ '{=}',
+ '{01:35:50+02}',
+ '{2}'),
+ ('numericcol', 'numeric',
+ '{=}',
+ '{2268164.347826086956521739130434782609}',
+ '{1}'),
+ ('uuidcol', 'uuid',
+ '{=}',
+ '{52225222-5222-5222-5222-522252225222}',
+ '{1}'),
+ ('lsncol', 'pg_lsn',
+ '{=, IS, IS NOT}',
+ '{44/455222, NULL, NULL}',
+ '{1, 25, 100}');
+DO $x$
+DECLARE
+ r record;
+ r2 record;
+ cond text;
+ idx_ctids tid[];
+ ss_ctids tid[];
+ count int;
+ plan_ok bool;
+ plan_line text;
+BEGIN
+ FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers_bloom, unnest(op) WITH ORDINALITY AS oper LOOP
+
+ -- prepare the condition
+ IF r.value IS NULL THEN
+ cond := format('%I %s %L', r.colname, r.oper, r.value);
+ ELSE
+ cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ);
+ END IF;
+
+ -- run the query using the brin index
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Bitmap Heap Scan on brintest_bloom%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get bitmap indexscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond)
+ INTO idx_ctids;
+
+ -- run the query using a seqscan
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Seq Scan on brintest_bloom%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get seqscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond)
+ INTO ss_ctids;
+
+ -- make sure both return the same results
+ count := array_length(idx_ctids, 1);
+
+ IF NOT (count = array_length(ss_ctids, 1) AND
+ idx_ctids @> ss_ctids AND
+ idx_ctids <@ ss_ctids) THEN
+ -- report the results of each scan to make the differences obvious
+ RAISE WARNING 'something not right in %: count %', r, count;
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_bloom WHERE ' || cond LOOP
+ RAISE NOTICE 'seqscan: %', r2;
+ END LOOP;
+
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_bloom WHERE ' || cond LOOP
+ RAISE NOTICE 'bitmapscan: %', r2;
+ END LOOP;
+ END IF;
+
+ -- make sure we found expected number of matches
+ IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF;
+ END LOOP;
+END;
+$x$;
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+INSERT INTO brintest_bloom SELECT
+ repeat(stringu1, 42)::bytea,
+ substr(stringu1, 1, 1)::"char",
+ stringu1::name, 142857 * tenthous,
+ thousand,
+ twothousand,
+ repeat(stringu1, 42),
+ unique1::oid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ inet '10.2.3.4' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ substr(stringu1, 1, 1)::bpchar,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20' + hundred * interval '15 seconds',
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn
+FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5;
+SELECT brin_desummarize_range('brinidx_bloom', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+VACUUM brintest_bloom; -- force a summarization cycle in brinidx
+UPDATE brintest_bloom SET int8col = int8col * int4col;
+UPDATE brintest_bloom SET textcol = '' WHERE textcol IS NOT NULL;
+-- Tests for brin_summarize_new_values
+SELECT brin_summarize_new_values('brintest_bloom'); -- error, not an index
+ERROR: "brintest_bloom" is not an index
+SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index
+ERROR: "tenk1_unique1" is not a BRIN index
+SELECT brin_summarize_new_values('brinidx_bloom'); -- ok, no change expected
+ brin_summarize_new_values
+---------------------------
+ 0
+(1 row)
+
+-- Tests for brin_desummarize_range
+SELECT brin_desummarize_range('brinidx_bloom', -1); -- error, invalid range
+ERROR: block number out of range: -1
+SELECT brin_desummarize_range('brinidx_bloom', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+SELECT brin_desummarize_range('brinidx_bloom', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+SELECT brin_desummarize_range('brinidx_bloom', 100000000);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+-- Test brin_summarize_range
+CREATE TABLE brin_summarize_bloom (
+ value int
+) WITH (fillfactor=10, autovacuum_enabled=false);
+CREATE INDEX brin_summarize_bloom_idx ON brin_summarize_bloom USING brin (value) WITH (pages_per_range=2);
+-- Fill a few pages
+DO $$
+DECLARE curtid tid;
+BEGIN
+ LOOP
+ INSERT INTO brin_summarize_bloom VALUES (1) RETURNING ctid INTO curtid;
+ EXIT WHEN curtid > tid '(2, 0)';
+ END LOOP;
+END;
+$$;
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 0);
+ brin_summarize_range
+----------------------
+ 0
+(1 row)
+
+-- nothing: already summarized
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 1);
+ brin_summarize_range
+----------------------
+ 0
+(1 row)
+
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 2);
+ brin_summarize_range
+----------------------
+ 1
+(1 row)
+
+-- nothing: page doesn't exist in table
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 4294967295);
+ brin_summarize_range
+----------------------
+ 0
+(1 row)
+
+-- invalid block number values
+SELECT brin_summarize_range('brin_summarize_bloom_idx', -1);
+ERROR: block number out of range: -1
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 4294967296);
+ERROR: block number out of range: 4294967296
+-- test brin cost estimates behave sanely based on correlation of values
+CREATE TABLE brin_test_bloom (a INT, b INT);
+INSERT INTO brin_test_bloom SELECT x/100,x%100 FROM generate_series(1,10000) x(x);
+CREATE INDEX brin_test_bloom_a_idx ON brin_test_bloom USING brin (a) WITH (pages_per_range = 2);
+CREATE INDEX brin_test_bloom_b_idx ON brin_test_bloom USING brin (b) WITH (pages_per_range = 2);
+VACUUM ANALYZE brin_test_bloom;
+-- Ensure brin index is used when columns are perfectly correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE a = 1;
+ QUERY PLAN
+--------------------------------------------------
+ Bitmap Heap Scan on brin_test_bloom
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on brin_test_bloom_a_idx
+ Index Cond: (a = 1)
+(4 rows)
+
+-- Ensure brin index is not used when values are not correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE b = 1;
+ QUERY PLAN
+-----------------------------
+ Seq Scan on brin_test_bloom
+ Filter: (b = 1)
+(2 rows)
+
diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
new file mode 100644
index 0000000..f3309f4
--- /dev/null
+++ b/src/test/regress/expected/brin_multi.out
@@ -0,0 +1,462 @@
+CREATE TABLE brintest_multi (
+ int8col bigint,
+ int2col smallint,
+ int4col integer,
+ oidcol oid,
+ tidcol tid,
+ float4col real,
+ float8col double precision,
+ macaddrcol macaddr,
+ macaddr8col macaddr8,
+ inetcol inet,
+ cidrcol cidr,
+ datecol date,
+ timecol time without time zone,
+ timestampcol timestamp without time zone,
+ timestamptzcol timestamp with time zone,
+ intervalcol interval,
+ timetzcol time with time zone,
+ numericcol numeric,
+ uuidcol uuid,
+ lsncol pg_lsn
+) WITH (fillfactor=10);
+INSERT INTO brintest_multi SELECT
+ 142857 * tenthous,
+ thousand,
+ twothousand,
+ unique1::oid,
+ format('(%s,%s)', tenthous, twenty)::tid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ substr(md5(unique1::text), 1, 16)::macaddr8,
+ inet '10.2.3.4/24' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20+02' + hundred * interval '15 seconds',
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn
+FROM tenk1 ORDER BY unique2 LIMIT 100;
+-- throw in some NULL's and different values
+INSERT INTO brintest_multi (inetcol, cidrcol) SELECT
+ inet 'fe80::6e40:8ff:fea9:8c46' + tenthous,
+ cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous
+FROM tenk1 ORDER BY thousand, tenthous LIMIT 25;
+-- test minmax-multi specific index options
+-- number of values must be >= 16
+CREATE INDEX brinidx_multi ON brintest_multi USING brin (
+ int8col int8_minmax_multi_ops(values_per_range = 7)
+);
+ERROR: value 7 out of bounds for option "values_per_range"
+DETAIL: Valid values are between "8" and "256".
+-- number of values must be <= 256
+CREATE INDEX brinidx_multi ON brintest_multi USING brin (
+ int8col int8_minmax_multi_ops(values_per_range = 257)
+);
+ERROR: value 257 out of bounds for option "values_per_range"
+DETAIL: Valid values are between "8" and "256".
+-- first create an index with a single page range, to force compaction
+-- due to exceeding the number of values per summary
+CREATE INDEX brinidx_multi ON brintest_multi USING brin (
+ int8col int8_minmax_multi_ops,
+ int2col int2_minmax_multi_ops,
+ int4col int4_minmax_multi_ops,
+ oidcol oid_minmax_multi_ops,
+ tidcol tid_minmax_multi_ops,
+ float4col float4_minmax_multi_ops,
+ float8col float8_minmax_multi_ops,
+ macaddrcol macaddr_minmax_multi_ops,
+ macaddr8col macaddr8_minmax_multi_ops,
+ inetcol inet_minmax_multi_ops,
+ cidrcol inet_minmax_multi_ops,
+ datecol date_minmax_multi_ops,
+ timecol time_minmax_multi_ops,
+ timestampcol timestamp_minmax_multi_ops,
+ timestamptzcol timestamptz_minmax_multi_ops,
+ intervalcol interval_minmax_multi_ops,
+ timetzcol timetz_minmax_multi_ops,
+ numericcol numeric_minmax_multi_ops,
+ uuidcol uuid_minmax_multi_ops,
+ lsncol pg_lsn_minmax_multi_ops
+);
+DROP INDEX brinidx_multi;
+CREATE INDEX brinidx_multi ON brintest_multi USING brin (
+ int8col int8_minmax_multi_ops,
+ int2col int2_minmax_multi_ops,
+ int4col int4_minmax_multi_ops,
+ oidcol oid_minmax_multi_ops,
+ tidcol tid_minmax_multi_ops,
+ float4col float4_minmax_multi_ops,
+ float8col float8_minmax_multi_ops,
+ macaddrcol macaddr_minmax_multi_ops,
+ macaddr8col macaddr8_minmax_multi_ops,
+ inetcol inet_minmax_multi_ops,
+ cidrcol inet_minmax_multi_ops,
+ datecol date_minmax_multi_ops,
+ timecol time_minmax_multi_ops,
+ timestampcol timestamp_minmax_multi_ops,
+ timestamptzcol timestamptz_minmax_multi_ops,
+ intervalcol interval_minmax_multi_ops,
+ timetzcol timetz_minmax_multi_ops,
+ numericcol numeric_minmax_multi_ops,
+ uuidcol uuid_minmax_multi_ops,
+ lsncol pg_lsn_minmax_multi_ops
+) with (pages_per_range = 1);
+CREATE TABLE brinopers_multi (colname name, typ text,
+ op text[], value text[], matches int[],
+ check (cardinality(op) = cardinality(value)),
+ check (cardinality(op) = cardinality(matches)));
+INSERT INTO brinopers_multi VALUES
+ ('int2col', 'int2',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 999, 999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int2col', 'int4',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 999, 1999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int2col', 'int8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 999, 1428427143}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4col', 'int2',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 1999, 1999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4col', 'int4',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 1999, 1999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4col', 'int8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 1999, 1428427143}',
+ '{100, 100, 1, 100, 100}'),
+ ('int8col', 'int2',
+ '{>, >=}',
+ '{0, 0}',
+ '{100, 100}'),
+ ('int8col', 'int4',
+ '{>, >=}',
+ '{0, 0}',
+ '{100, 100}'),
+ ('int8col', 'int8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 1257141600, 1428427143, 1428427143}',
+ '{100, 100, 1, 100, 100}'),
+ ('oidcol', 'oid',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 8800, 9999, 9999}',
+ '{100, 100, 1, 100, 100}'),
+ ('tidcol', 'tid',
+ '{>, >=, =, <=, <}',
+ '{"(0,0)", "(0,0)", "(8800,0)", "(9999,19)", "(9999,19)"}',
+ '{100, 100, 1, 100, 100}'),
+ ('float4col', 'float4',
+ '{>, >=, =, <=, <}',
+ '{0.0103093, 0.0103093, 1, 1, 1}',
+ '{100, 100, 4, 100, 96}'),
+ ('float4col', 'float8',
+ '{>, >=, =, <=, <}',
+ '{0.0103093, 0.0103093, 1, 1, 1}',
+ '{100, 100, 4, 100, 96}'),
+ ('float8col', 'float4',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 0, 1.98, 1.98}',
+ '{99, 100, 1, 100, 100}'),
+ ('float8col', 'float8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 0, 1.98, 1.98}',
+ '{99, 100, 1, 100, 100}'),
+ ('macaddrcol', 'macaddr',
+ '{>, >=, =, <=, <}',
+ '{00:00:01:00:00:00, 00:00:01:00:00:00, 2c:00:2d:00:16:00, ff:fe:00:00:00:00, ff:fe:00:00:00:00}',
+ '{99, 100, 2, 100, 100}'),
+ ('macaddr8col', 'macaddr8',
+ '{>, >=, =, <=, <}',
+ '{b1:d1:0e:7b:af:a4:42:12, d9:35:91:bd:f7:86:0e:1e, 72:8f:20:6c:2a:01:bf:57, 23:e8:46:63:86:07:ad:cb, 13:16:8e:6a:2e:6c:84:b4}',
+ '{33, 15, 1, 13, 6}'),
+ ('inetcol', 'inet',
+ '{=, <, <=, >, >=}',
+ '{10.2.14.231/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}',
+ '{1, 100, 100, 125, 125}'),
+ ('inetcol', 'cidr',
+ '{<, <=, >, >=}',
+ '{255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}',
+ '{100, 100, 125, 125}'),
+ ('cidrcol', 'inet',
+ '{=, <, <=, >, >=}',
+ '{10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}',
+ '{2, 100, 100, 125, 125}'),
+ ('cidrcol', 'cidr',
+ '{=, <, <=, >, >=}',
+ '{10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}',
+ '{2, 100, 100, 125, 125}'),
+ ('datecol', 'date',
+ '{>, >=, =, <=, <}',
+ '{1995-08-15, 1995-08-15, 2009-12-01, 2022-12-30, 2022-12-30}',
+ '{100, 100, 1, 100, 100}'),
+ ('timecol', 'time',
+ '{>, >=, =, <=, <}',
+ '{01:20:30, 01:20:30, 02:28:57, 06:28:31.5, 06:28:31.5}',
+ '{100, 100, 1, 100, 100}'),
+ ('timestampcol', 'timestamp',
+ '{>, >=, =, <=, <}',
+ '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}',
+ '{100, 100, 1, 100, 100}'),
+ ('timestampcol', 'timestamptz',
+ '{>, >=, =, <=, <}',
+ '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}',
+ '{100, 100, 1, 100, 100}'),
+ ('timestamptzcol', 'timestamptz',
+ '{>, >=, =, <=, <}',
+ '{1972-10-10 03:00:00-04, 1972-10-10 03:00:00-04, 1972-10-19 09:00:00-07, 1972-11-20 19:00:00-03, 1972-11-20 19:00:00-03}',
+ '{100, 100, 1, 100, 100}'),
+ ('intervalcol', 'interval',
+ '{>, >=, =, <=, <}',
+ '{00:00:00, 00:00:00, 1 mons 13 days 12:24, 2 mons 23 days 07:48:00, 1 year}',
+ '{100, 100, 1, 100, 100}'),
+ ('timetzcol', 'timetz',
+ '{>, >=, =, <=, <}',
+ '{01:30:20+02, 01:30:20+02, 01:35:50+02, 23:55:05+02, 23:55:05+02}',
+ '{99, 100, 2, 100, 100}'),
+ ('numericcol', 'numeric',
+ '{>, >=, =, <=, <}',
+ '{0.00, 0.01, 2268164.347826086956521739130434782609, 99470151.9, 99470151.9}',
+ '{100, 100, 1, 100, 100}'),
+ ('uuidcol', 'uuid',
+ '{>, >=, =, <=, <}',
+ '{00040004-0004-0004-0004-000400040004, 00040004-0004-0004-0004-000400040004, 52225222-5222-5222-5222-522252225222, 99989998-9998-9998-9998-999899989998, 99989998-9998-9998-9998-999899989998}',
+ '{100, 100, 1, 100, 100}'),
+ ('lsncol', 'pg_lsn',
+ '{>, >=, =, <=, <, IS, IS NOT}',
+ '{0/1200, 0/1200, 44/455222, 198/1999799, 198/1999799, NULL, NULL}',
+ '{100, 100, 1, 100, 100, 25, 100}');
+DO $x$
+DECLARE
+ r record;
+ r2 record;
+ cond text;
+ idx_ctids tid[];
+ ss_ctids tid[];
+ count int;
+ plan_ok bool;
+ plan_line text;
+BEGIN
+ FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers_multi, unnest(op) WITH ORDINALITY AS oper LOOP
+
+ -- prepare the condition
+ IF r.value IS NULL THEN
+ cond := format('%I %s %L', r.colname, r.oper, r.value);
+ ELSE
+ cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ);
+ END IF;
+
+ -- run the query using the brin index
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Bitmap Heap Scan on brintest_multi%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get bitmap indexscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond)
+ INTO idx_ctids;
+
+ -- run the query using a seqscan
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Seq Scan on brintest_multi%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get seqscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond)
+ INTO ss_ctids;
+
+ -- make sure both return the same results
+ count := array_length(idx_ctids, 1);
+
+ IF NOT (count = array_length(ss_ctids, 1) AND
+ idx_ctids @> ss_ctids AND
+ idx_ctids <@ ss_ctids) THEN
+ -- report the results of each scan to make the differences obvious
+ RAISE WARNING 'something not right in %: count %', r, count;
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_multi WHERE ' || cond LOOP
+ RAISE NOTICE 'seqscan: %', r2;
+ END LOOP;
+
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_multi WHERE ' || cond LOOP
+ RAISE NOTICE 'bitmapscan: %', r2;
+ END LOOP;
+ END IF;
+
+ -- make sure we found expected number of matches
+ IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF;
+ END LOOP;
+END;
+$x$;
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+INSERT INTO brintest_multi SELECT
+ 142857 * tenthous,
+ thousand,
+ twothousand,
+ unique1::oid,
+ format('(%s,%s)', tenthous, twenty)::tid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ substr(md5(unique1::text), 1, 16)::macaddr8,
+ inet '10.2.3.4' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20' + hundred * interval '15 seconds',
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn
+FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5;
+SELECT brin_desummarize_range('brinidx_multi', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+VACUUM brintest_multi; -- force a summarization cycle in brinidx
+-- Try inserting a values with NaN, to test distance calculation.
+insert into public.brintest_multi (float4col) values (real 'nan');
+insert into public.brintest_multi (float8col) values (real 'nan');
+UPDATE brintest_multi SET int8col = int8col * int4col;
+-- Tests for brin_summarize_new_values
+SELECT brin_summarize_new_values('brintest_multi'); -- error, not an index
+ERROR: "brintest_multi" is not an index
+SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index
+ERROR: "tenk1_unique1" is not a BRIN index
+SELECT brin_summarize_new_values('brinidx_multi'); -- ok, no change expected
+ brin_summarize_new_values
+---------------------------
+ 0
+(1 row)
+
+-- Tests for brin_desummarize_range
+SELECT brin_desummarize_range('brinidx_multi', -1); -- error, invalid range
+ERROR: block number out of range: -1
+SELECT brin_desummarize_range('brinidx_multi', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+SELECT brin_desummarize_range('brinidx_multi', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+SELECT brin_desummarize_range('brinidx_multi', 100000000);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+-- test building an index with many values, to force compaction of the buffer
+CREATE TABLE brin_large_range (a int4);
+INSERT INTO brin_large_range SELECT i FROM generate_series(1,10000) s(i);
+CREATE INDEX brin_large_range_idx ON brin_large_range USING brin (a int4_minmax_multi_ops);
+DROP TABLE brin_large_range;
+-- Test brin_summarize_range
+CREATE TABLE brin_summarize_multi (
+ value int
+) WITH (fillfactor=10, autovacuum_enabled=false);
+CREATE INDEX brin_summarize_multi_idx ON brin_summarize_multi USING brin (value) WITH (pages_per_range=2);
+-- Fill a few pages
+DO $$
+DECLARE curtid tid;
+BEGIN
+ LOOP
+ INSERT INTO brin_summarize_multi VALUES (1) RETURNING ctid INTO curtid;
+ EXIT WHEN curtid > tid '(2, 0)';
+ END LOOP;
+END;
+$$;
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_multi_idx', 0);
+ brin_summarize_range
+----------------------
+ 0
+(1 row)
+
+-- nothing: already summarized
+SELECT brin_summarize_range('brin_summarize_multi_idx', 1);
+ brin_summarize_range
+----------------------
+ 0
+(1 row)
+
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_multi_idx', 2);
+ brin_summarize_range
+----------------------
+ 1
+(1 row)
+
+-- nothing: page doesn't exist in table
+SELECT brin_summarize_range('brin_summarize_multi_idx', 4294967295);
+ brin_summarize_range
+----------------------
+ 0
+(1 row)
+
+-- invalid block number values
+SELECT brin_summarize_range('brin_summarize_multi_idx', -1);
+ERROR: block number out of range: -1
+SELECT brin_summarize_range('brin_summarize_multi_idx', 4294967296);
+ERROR: block number out of range: 4294967296
+-- test brin cost estimates behave sanely based on correlation of values
+CREATE TABLE brin_test_multi (a INT, b INT);
+INSERT INTO brin_test_multi SELECT x/100,x%100 FROM generate_series(1,10000) x(x);
+CREATE INDEX brin_test_multi_a_idx ON brin_test_multi USING brin (a) WITH (pages_per_range = 2);
+CREATE INDEX brin_test_multi_b_idx ON brin_test_multi USING brin (b) WITH (pages_per_range = 2);
+VACUUM ANALYZE brin_test_multi;
+-- Ensure brin index is used when columns are perfectly correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE a = 1;
+ QUERY PLAN
+--------------------------------------------------
+ Bitmap Heap Scan on brin_test_multi
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on brin_test_multi_a_idx
+ Index Cond: (a = 1)
+(4 rows)
+
+-- Ensure brin index is not used when values are not correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1;
+ QUERY PLAN
+-----------------------------
+ Seq Scan on brin_test_multi
+ Filter: (b = 1)
+(2 rows)
+
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
new file mode 100644
index 0000000..c7328b1
--- /dev/null
+++ b/src/test/regress/expected/btree_index.out
@@ -0,0 +1,343 @@
+--
+-- BTREE_INDEX
+-- test retrieval of min/max keys for each index
+--
+SELECT b.*
+ FROM bt_i4_heap b
+ WHERE b.seqno < 1;
+ seqno | random
+-------+------------
+ 0 | 1935401906
+(1 row)
+
+SELECT b.*
+ FROM bt_i4_heap b
+ WHERE b.seqno >= 9999;
+ seqno | random
+-------+------------
+ 9999 | 1227676208
+(1 row)
+
+SELECT b.*
+ FROM bt_i4_heap b
+ WHERE b.seqno = 4500;
+ seqno | random
+-------+------------
+ 4500 | 2080851358
+(1 row)
+
+SELECT b.*
+ FROM bt_name_heap b
+ WHERE b.seqno < '1'::name;
+ seqno | random
+-------+------------
+ 0 | 1935401906
+(1 row)
+
+SELECT b.*
+ FROM bt_name_heap b
+ WHERE b.seqno >= '9999'::name;
+ seqno | random
+-------+------------
+ 9999 | 1227676208
+(1 row)
+
+SELECT b.*
+ FROM bt_name_heap b
+ WHERE b.seqno = '4500'::name;
+ seqno | random
+-------+------------
+ 4500 | 2080851358
+(1 row)
+
+SELECT b.*
+ FROM bt_txt_heap b
+ WHERE b.seqno < '1'::text;
+ seqno | random
+-------+------------
+ 0 | 1935401906
+(1 row)
+
+SELECT b.*
+ FROM bt_txt_heap b
+ WHERE b.seqno >= '9999'::text;
+ seqno | random
+-------+------------
+ 9999 | 1227676208
+(1 row)
+
+SELECT b.*
+ FROM bt_txt_heap b
+ WHERE b.seqno = '4500'::text;
+ seqno | random
+-------+------------
+ 4500 | 2080851358
+(1 row)
+
+SELECT b.*
+ FROM bt_f8_heap b
+ WHERE b.seqno < '1'::float8;
+ seqno | random
+-------+------------
+ 0 | 1935401906
+(1 row)
+
+SELECT b.*
+ FROM bt_f8_heap b
+ WHERE b.seqno >= '9999'::float8;
+ seqno | random
+-------+------------
+ 9999 | 1227676208
+(1 row)
+
+SELECT b.*
+ FROM bt_f8_heap b
+ WHERE b.seqno = '4500'::float8;
+ seqno | random
+-------+------------
+ 4500 | 2080851358
+(1 row)
+
+--
+-- Check correct optimization of LIKE (special index operator support)
+-- for both indexscan and bitmapscan cases
+--
+set enable_seqscan to false;
+set enable_indexscan to true;
+set enable_bitmapscan to false;
+explain (costs off)
+select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
+ QUERY PLAN
+------------------------------------------------------------------------------
+ Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc
+ Index Cond: ((proname >= 'RI_FKey'::text) AND (proname < 'RI_FKez'::text))
+ Filter: (proname ~~ 'RI\_FKey%del'::text)
+(3 rows)
+
+select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
+ proname
+------------------------
+ RI_FKey_cascade_del
+ RI_FKey_noaction_del
+ RI_FKey_restrict_del
+ RI_FKey_setdefault_del
+ RI_FKey_setnull_del
+(5 rows)
+
+explain (costs off)
+select proname from pg_proc where proname ilike '00%foo' order by 1;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc
+ Index Cond: ((proname >= '00'::text) AND (proname < '01'::text))
+ Filter: (proname ~~* '00%foo'::text)
+(3 rows)
+
+select proname from pg_proc where proname ilike '00%foo' order by 1;
+ proname
+---------
+(0 rows)
+
+explain (costs off)
+select proname from pg_proc where proname ilike 'ri%foo' order by 1;
+ QUERY PLAN
+-----------------------------------------------------------------
+ Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc
+ Filter: (proname ~~* 'ri%foo'::text)
+(2 rows)
+
+set enable_indexscan to false;
+set enable_bitmapscan to true;
+explain (costs off)
+select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
+ QUERY PLAN
+------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: proname
+ -> Bitmap Heap Scan on pg_proc
+ Filter: (proname ~~ 'RI\_FKey%del'::text)
+ -> Bitmap Index Scan on pg_proc_proname_args_nsp_index
+ Index Cond: ((proname >= 'RI_FKey'::text) AND (proname < 'RI_FKez'::text))
+(6 rows)
+
+select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
+ proname
+------------------------
+ RI_FKey_cascade_del
+ RI_FKey_noaction_del
+ RI_FKey_restrict_del
+ RI_FKey_setdefault_del
+ RI_FKey_setnull_del
+(5 rows)
+
+explain (costs off)
+select proname from pg_proc where proname ilike '00%foo' order by 1;
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ Sort
+ Sort Key: proname
+ -> Bitmap Heap Scan on pg_proc
+ Filter: (proname ~~* '00%foo'::text)
+ -> Bitmap Index Scan on pg_proc_proname_args_nsp_index
+ Index Cond: ((proname >= '00'::text) AND (proname < '01'::text))
+(6 rows)
+
+select proname from pg_proc where proname ilike '00%foo' order by 1;
+ proname
+---------
+(0 rows)
+
+explain (costs off)
+select proname from pg_proc where proname ilike 'ri%foo' order by 1;
+ QUERY PLAN
+-----------------------------------------------------------------
+ Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc
+ Filter: (proname ~~* 'ri%foo'::text)
+(2 rows)
+
+reset enable_seqscan;
+reset enable_indexscan;
+reset enable_bitmapscan;
+-- Also check LIKE optimization with binary-compatible cases
+create temp table btree_bpchar (f1 text collate "C");
+create index on btree_bpchar(f1 bpchar_ops) WITH (deduplicate_items=on);
+insert into btree_bpchar values ('foo'), ('fool'), ('bar'), ('quux');
+-- doesn't match index:
+explain (costs off)
+select * from btree_bpchar where f1 like 'foo';
+ QUERY PLAN
+-------------------------------
+ Seq Scan on btree_bpchar
+ Filter: (f1 ~~ 'foo'::text)
+(2 rows)
+
+select * from btree_bpchar where f1 like 'foo';
+ f1
+-----
+ foo
+(1 row)
+
+explain (costs off)
+select * from btree_bpchar where f1 like 'foo%';
+ QUERY PLAN
+--------------------------------
+ Seq Scan on btree_bpchar
+ Filter: (f1 ~~ 'foo%'::text)
+(2 rows)
+
+select * from btree_bpchar where f1 like 'foo%';
+ f1
+------
+ foo
+ fool
+(2 rows)
+
+-- these do match the index:
+explain (costs off)
+select * from btree_bpchar where f1::bpchar like 'foo';
+ QUERY PLAN
+----------------------------------------------------
+ Bitmap Heap Scan on btree_bpchar
+ Filter: ((f1)::bpchar ~~ 'foo'::text)
+ -> Bitmap Index Scan on btree_bpchar_f1_idx
+ Index Cond: ((f1)::bpchar = 'foo'::bpchar)
+(4 rows)
+
+select * from btree_bpchar where f1::bpchar like 'foo';
+ f1
+-----
+ foo
+(1 row)
+
+explain (costs off)
+select * from btree_bpchar where f1::bpchar like 'foo%';
+ QUERY PLAN
+------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on btree_bpchar
+ Filter: ((f1)::bpchar ~~ 'foo%'::text)
+ -> Bitmap Index Scan on btree_bpchar_f1_idx
+ Index Cond: (((f1)::bpchar >= 'foo'::bpchar) AND ((f1)::bpchar < 'fop'::bpchar))
+(4 rows)
+
+select * from btree_bpchar where f1::bpchar like 'foo%';
+ f1
+------
+ foo
+ fool
+(2 rows)
+
+-- get test coverage for "single value" deduplication strategy:
+insert into btree_bpchar select 'foo' from generate_series(1,1500);
+--
+-- Perform unique checking, with and without the use of deduplication
+--
+CREATE TABLE dedup_unique_test_table (a int) WITH (autovacuum_enabled=false);
+CREATE UNIQUE INDEX dedup_unique ON dedup_unique_test_table (a) WITH (deduplicate_items=on);
+CREATE UNIQUE INDEX plain_unique ON dedup_unique_test_table (a) WITH (deduplicate_items=off);
+-- Generate enough garbage tuples in index to ensure that even the unique index
+-- with deduplication enabled has to check multiple leaf pages during unique
+-- checking (at least with a BLCKSZ of 8192 or less)
+DO $$
+BEGIN
+ FOR r IN 1..1350 LOOP
+ DELETE FROM dedup_unique_test_table;
+ INSERT INTO dedup_unique_test_table SELECT 1;
+ END LOOP;
+END$$;
+-- Exercise the LP_DEAD-bit-set tuple deletion code with a posting list tuple.
+-- The implementation prefers deleting existing items to merging any duplicate
+-- tuples into a posting list, so we need an explicit test to make sure we get
+-- coverage (note that this test also assumes BLCKSZ is 8192 or less):
+DROP INDEX plain_unique;
+DELETE FROM dedup_unique_test_table WHERE a = 1;
+INSERT INTO dedup_unique_test_table SELECT i FROM generate_series(0,450) i;
+--
+-- Test B-tree fast path (cache rightmost leaf page) optimization.
+--
+-- First create a tree that's at least three levels deep (i.e. has one level
+-- between the root and leaf levels). The text inserted is long. It won't be
+-- TOAST compressed because we use plain storage in the table. Only a few
+-- index tuples fit on each internal page, allowing us to get a tall tree with
+-- few pages. (A tall tree is required to trigger caching.)
+--
+-- The text column must be the leading column in the index, since suffix
+-- truncation would otherwise truncate tuples on internal pages, leaving us
+-- with a short tree.
+create table btree_tall_tbl(id int4, t text);
+alter table btree_tall_tbl alter COLUMN t set storage plain;
+create index btree_tall_idx on btree_tall_tbl (t, id) with (fillfactor = 10);
+insert into btree_tall_tbl select g, repeat('x', 250)
+from generate_series(1, 130) g;
+--
+-- Test for multilevel page deletion
+--
+CREATE TABLE delete_test_table (a bigint, b bigint, c bigint, d bigint);
+INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,80000) i;
+ALTER TABLE delete_test_table ADD PRIMARY KEY (a,b,c,d);
+-- Delete most entries, and vacuum, deleting internal pages and creating "fast
+-- root"
+DELETE FROM delete_test_table WHERE a < 79990;
+VACUUM delete_test_table;
+--
+-- Test B-tree insertion with a metapage update (XLOG_BTREE_INSERT_META
+-- WAL record type). This happens when a "fast root" page is split. This
+-- also creates coverage for nbtree FSM page recycling.
+--
+-- The vacuum above should've turned the leaf page into a fast root. We just
+-- need to insert some rows to cause the fast root page to split.
+INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,1000) i;
+-- Test unsupported btree opclass parameters
+create index on btree_tall_tbl (id int4_ops(foo=1));
+ERROR: operator class int4_ops has no options
+-- Test case of ALTER INDEX with abuse of column names for indexes.
+-- This grammar is not officially supported, but the parser allows it.
+CREATE INDEX btree_tall_idx2 ON btree_tall_tbl (id);
+ALTER INDEX btree_tall_idx2 ALTER COLUMN id SET (n_distinct=100);
+ERROR: "btree_tall_idx2" is not a table, materialized view, or foreign table
+DROP INDEX btree_tall_idx2;
+-- Partitioned index
+CREATE TABLE btree_part (id int4) PARTITION BY RANGE (id);
+CREATE INDEX btree_part_idx ON btree_part(id);
+ALTER INDEX btree_part_idx ALTER COLUMN id SET (n_distinct=100);
+ERROR: "btree_part_idx" is not a table, materialized view, or foreign table
+DROP TABLE btree_part;
diff --git a/src/test/regress/expected/case.out b/src/test/regress/expected/case.out
new file mode 100644
index 0000000..f5136c1
--- /dev/null
+++ b/src/test/regress/expected/case.out
@@ -0,0 +1,419 @@
+--
+-- 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";
+ One | Simple WHEN
+-----+-------------
+ 3 | 3
+(1 row)
+
+SELECT '<NULL>' AS "One",
+ CASE
+ WHEN 1 > 2 THEN 3
+ END AS "Simple default";
+ One | Simple default
+--------+----------------
+ <NULL> |
+(1 row)
+
+SELECT '3' AS "One",
+ CASE
+ WHEN 1 < 2 THEN 3
+ ELSE 4
+ END AS "Simple ELSE";
+ One | Simple ELSE
+-----+-------------
+ 3 | 3
+(1 row)
+
+SELECT '4' AS "One",
+ CASE
+ WHEN 1 > 2 THEN 3
+ ELSE 4
+ END AS "ELSE default";
+ One | ELSE default
+-----+--------------
+ 4 | 4
+(1 row)
+
+SELECT '6' AS "One",
+ CASE
+ WHEN 1 > 2 THEN 3
+ WHEN 4 < 5 THEN 6
+ ELSE 7
+ END AS "Two WHEN with default";
+ One | Two WHEN with default
+-----+-----------------------
+ 6 | 6
+(1 row)
+
+SELECT '7' AS "None",
+ CASE WHEN random() < 0 THEN 1
+ END AS "NULL on no matches";
+ None | NULL on no matches
+------+--------------------
+ 7 |
+(1 row)
+
+-- 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;
+ case
+------
+ 1
+(1 row)
+
+SELECT CASE 1 WHEN 0 THEN 1/0 WHEN 1 THEN 1 ELSE 2/0 END;
+ case
+------
+ 1
+(1 row)
+
+-- 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;
+ERROR: division by zero
+-- Test for cases involving untyped literals in test expression
+SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 2 END;
+ case
+------
+ 1
+(1 row)
+
+--
+-- Examples of targets involving tables
+--
+SELECT
+ CASE
+ WHEN i >= 3 THEN i
+ END AS ">= 3 or Null"
+ FROM CASE_TBL;
+ >= 3 or Null
+--------------
+
+
+ 3
+ 4
+(4 rows)
+
+SELECT
+ CASE WHEN i >= 3 THEN (i + i)
+ ELSE i
+ END AS "Simplest Math"
+ FROM CASE_TBL;
+ Simplest Math
+---------------
+ 1
+ 2
+ 6
+ 8
+(4 rows)
+
+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;
+ Value | Category
+-------+----------
+ 1 | one
+ 2 | two
+ 3 | big
+ 4 | big
+(4 rows)
+
+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;
+ Category
+----------
+ one
+ two
+ big
+ big
+(4 rows)
+
+--
+-- 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;
+ i | f
+---+---
+ 4 |
+(1 row)
+
+SELECT * FROM CASE_TBL WHERE NULLIF(f,i) = 2;
+ i | f
+---+---
+(0 rows)
+
+SELECT COALESCE(a.f, b.i, b.j)
+ FROM CASE_TBL a, CASE2_TBL b;
+ coalesce
+----------
+ 10.1
+ 20.2
+ -30.3
+ 1
+ 10.1
+ 20.2
+ -30.3
+ 2
+ 10.1
+ 20.2
+ -30.3
+ 3
+ 10.1
+ 20.2
+ -30.3
+ 2
+ 10.1
+ 20.2
+ -30.3
+ 1
+ 10.1
+ 20.2
+ -30.3
+ -6
+(24 rows)
+
+SELECT *
+ FROM CASE_TBL a, CASE2_TBL b
+ WHERE COALESCE(a.f, b.i, b.j) = 2;
+ i | f | i | j
+---+---+---+----
+ 4 | | 2 | -2
+ 4 | | 2 | -4
+(2 rows)
+
+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;
+ NULLIF(a.i,b.i) | NULLIF(b.i,4)
+-----------------+---------------
+ | 1
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 1 | 2
+ | 2
+ 3 | 2
+ 4 | 2
+ 1 | 3
+ 2 | 3
+ | 3
+ 4 | 3
+ 1 | 2
+ | 2
+ 3 | 2
+ 4 | 2
+ | 1
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+(24 rows)
+
+SELECT *
+ FROM CASE_TBL a, CASE2_TBL b
+ WHERE COALESCE(f,b.i) = 2;
+ i | f | i | j
+---+---+---+----
+ 4 | | 2 | -2
+ 4 | | 2 | -4
+(2 rows)
+
+-- Tests for constant subexpression simplification
+explain (costs off)
+SELECT * FROM CASE_TBL WHERE NULLIF(1, 2) = 2;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+explain (costs off)
+SELECT * FROM CASE_TBL WHERE NULLIF(1, 1) IS NOT NULL;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+explain (costs off)
+SELECT * FROM CASE_TBL WHERE NULLIF(1, null) = 2;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+--
+-- Examples of updates involving tables
+--
+UPDATE CASE_TBL
+ SET i = CASE WHEN i >= 3 THEN (- i)
+ ELSE (2 * i) END;
+SELECT * FROM CASE_TBL;
+ i | f
+----+-------
+ 2 | 10.1
+ 4 | 20.2
+ -3 | -30.3
+ -4 |
+(4 rows)
+
+UPDATE CASE_TBL
+ SET i = CASE WHEN i >= 2 THEN (2 * i)
+ ELSE (3 * i) END;
+SELECT * FROM CASE_TBL;
+ i | f
+-----+-------
+ 4 | 10.1
+ 8 | 20.2
+ -9 | -30.3
+ -12 |
+(4 rows)
+
+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;
+ i | f
+-----+-------
+ 8 | 20.2
+ -9 | -30.3
+ -12 |
+ -8 | 10.1
+(4 rows)
+
+--
+-- 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;
+ case
+----------------
+ bar recognized
+(1 row)
+
+-- 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;
+ case
+------------
+ is not foo
+(1 row)
+
+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;
+ case
+-------
+ right
+(1 row)
+
+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;
+ array
+-----------------
+ {a,b,c,d,e,f,g}
+(1 row)
+
+ROLLBACK;
+--
+-- Clean up
+--
+DROP TABLE CASE_TBL;
+DROP TABLE CASE2_TBL;
diff --git a/src/test/regress/expected/char.out b/src/test/regress/expected/char.out
new file mode 100644
index 0000000..d515b3c
--- /dev/null
+++ b/src/test/regress/expected/char.out
@@ -0,0 +1,122 @@
+--
+-- CHAR
+--
+-- fixed-length by value
+-- internally passed by value if <= 4 bytes in storage
+SELECT char 'c' = char 'c' AS true;
+ true
+------
+ t
+(1 row)
+
+--
+-- Build a table for testing
+--
+CREATE TABLE CHAR_TBL(f1 char);
+INSERT INTO CHAR_TBL (f1) VALUES ('a');
+INSERT INTO CHAR_TBL (f1) VALUES ('A');
+-- any of the following three input formats are acceptable
+INSERT INTO CHAR_TBL (f1) VALUES ('1');
+INSERT INTO CHAR_TBL (f1) VALUES (2);
+INSERT INTO CHAR_TBL (f1) VALUES ('3');
+-- zero-length char
+INSERT INTO CHAR_TBL (f1) VALUES ('');
+-- try char's of greater than 1 length
+INSERT INTO CHAR_TBL (f1) VALUES ('cd');
+ERROR: value too long for type character(1)
+INSERT INTO CHAR_TBL (f1) VALUES ('c ');
+SELECT * FROM CHAR_TBL;
+ f1
+----
+ a
+ A
+ 1
+ 2
+ 3
+
+ c
+(7 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 <> 'a';
+ f1
+----
+ A
+ 1
+ 2
+ 3
+
+ c
+(6 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 = 'a';
+ f1
+----
+ a
+(1 row)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 < 'a';
+ f1
+----
+ A
+ 1
+ 2
+ 3
+
+(5 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 <= 'a';
+ f1
+----
+ a
+ A
+ 1
+ 2
+ 3
+
+(6 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 > 'a';
+ f1
+----
+ c
+(1 row)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 >= 'a';
+ f1
+----
+ a
+ c
+(2 rows)
+
+DROP TABLE CHAR_TBL;
+--
+-- Now test longer arrays of char
+--
+CREATE TABLE CHAR_TBL(f1 char(4));
+INSERT INTO CHAR_TBL (f1) VALUES ('a');
+INSERT INTO CHAR_TBL (f1) VALUES ('ab');
+INSERT INTO CHAR_TBL (f1) VALUES ('abcd');
+INSERT INTO CHAR_TBL (f1) VALUES ('abcde');
+ERROR: value too long for type character(4)
+INSERT INTO CHAR_TBL (f1) VALUES ('abcd ');
+SELECT * FROM CHAR_TBL;
+ f1
+------
+ a
+ ab
+ abcd
+ abcd
+(4 rows)
+
diff --git a/src/test/regress/expected/char_1.out b/src/test/regress/expected/char_1.out
new file mode 100644
index 0000000..6c917c0
--- /dev/null
+++ b/src/test/regress/expected/char_1.out
@@ -0,0 +1,122 @@
+--
+-- CHAR
+--
+-- fixed-length by value
+-- internally passed by value if <= 4 bytes in storage
+SELECT char 'c' = char 'c' AS true;
+ true
+------
+ t
+(1 row)
+
+--
+-- Build a table for testing
+--
+CREATE TABLE CHAR_TBL(f1 char);
+INSERT INTO CHAR_TBL (f1) VALUES ('a');
+INSERT INTO CHAR_TBL (f1) VALUES ('A');
+-- any of the following three input formats are acceptable
+INSERT INTO CHAR_TBL (f1) VALUES ('1');
+INSERT INTO CHAR_TBL (f1) VALUES (2);
+INSERT INTO CHAR_TBL (f1) VALUES ('3');
+-- zero-length char
+INSERT INTO CHAR_TBL (f1) VALUES ('');
+-- try char's of greater than 1 length
+INSERT INTO CHAR_TBL (f1) VALUES ('cd');
+ERROR: value too long for type character(1)
+INSERT INTO CHAR_TBL (f1) VALUES ('c ');
+SELECT * FROM CHAR_TBL;
+ f1
+----
+ a
+ A
+ 1
+ 2
+ 3
+
+ c
+(7 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 <> 'a';
+ f1
+----
+ A
+ 1
+ 2
+ 3
+
+ c
+(6 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 = 'a';
+ f1
+----
+ a
+(1 row)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 < 'a';
+ f1
+----
+ 1
+ 2
+ 3
+
+(4 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 <= 'a';
+ f1
+----
+ a
+ 1
+ 2
+ 3
+
+(5 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 > 'a';
+ f1
+----
+ A
+ c
+(2 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 >= 'a';
+ f1
+----
+ a
+ A
+ c
+(3 rows)
+
+DROP TABLE CHAR_TBL;
+--
+-- Now test longer arrays of char
+--
+CREATE TABLE CHAR_TBL(f1 char(4));
+INSERT INTO CHAR_TBL (f1) VALUES ('a');
+INSERT INTO CHAR_TBL (f1) VALUES ('ab');
+INSERT INTO CHAR_TBL (f1) VALUES ('abcd');
+INSERT INTO CHAR_TBL (f1) VALUES ('abcde');
+ERROR: value too long for type character(4)
+INSERT INTO CHAR_TBL (f1) VALUES ('abcd ');
+SELECT * FROM CHAR_TBL;
+ f1
+------
+ a
+ ab
+ abcd
+ abcd
+(4 rows)
+
diff --git a/src/test/regress/expected/char_2.out b/src/test/regress/expected/char_2.out
new file mode 100644
index 0000000..9a54658
--- /dev/null
+++ b/src/test/regress/expected/char_2.out
@@ -0,0 +1,122 @@
+--
+-- CHAR
+--
+-- fixed-length by value
+-- internally passed by value if <= 4 bytes in storage
+SELECT char 'c' = char 'c' AS true;
+ true
+------
+ t
+(1 row)
+
+--
+-- Build a table for testing
+--
+CREATE TABLE CHAR_TBL(f1 char);
+INSERT INTO CHAR_TBL (f1) VALUES ('a');
+INSERT INTO CHAR_TBL (f1) VALUES ('A');
+-- any of the following three input formats are acceptable
+INSERT INTO CHAR_TBL (f1) VALUES ('1');
+INSERT INTO CHAR_TBL (f1) VALUES (2);
+INSERT INTO CHAR_TBL (f1) VALUES ('3');
+-- zero-length char
+INSERT INTO CHAR_TBL (f1) VALUES ('');
+-- try char's of greater than 1 length
+INSERT INTO CHAR_TBL (f1) VALUES ('cd');
+ERROR: value too long for type character(1)
+INSERT INTO CHAR_TBL (f1) VALUES ('c ');
+SELECT * FROM CHAR_TBL;
+ f1
+----
+ a
+ A
+ 1
+ 2
+ 3
+
+ c
+(7 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 <> 'a';
+ f1
+----
+ A
+ 1
+ 2
+ 3
+
+ c
+(6 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 = 'a';
+ f1
+----
+ a
+(1 row)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 < 'a';
+ f1
+----
+
+(1 row)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 <= 'a';
+ f1
+----
+ a
+
+(2 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 > 'a';
+ f1
+----
+ A
+ 1
+ 2
+ 3
+ c
+(5 rows)
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 >= 'a';
+ f1
+----
+ a
+ A
+ 1
+ 2
+ 3
+ c
+(6 rows)
+
+DROP TABLE CHAR_TBL;
+--
+-- Now test longer arrays of char
+--
+CREATE TABLE CHAR_TBL(f1 char(4));
+INSERT INTO CHAR_TBL (f1) VALUES ('a');
+INSERT INTO CHAR_TBL (f1) VALUES ('ab');
+INSERT INTO CHAR_TBL (f1) VALUES ('abcd');
+INSERT INTO CHAR_TBL (f1) VALUES ('abcde');
+ERROR: value too long for type character(4)
+INSERT INTO CHAR_TBL (f1) VALUES ('abcd ');
+SELECT * FROM CHAR_TBL;
+ f1
+------
+ a
+ ab
+ abcd
+ abcd
+(4 rows)
+
diff --git a/src/test/regress/expected/circle.out b/src/test/regress/expected/circle.out
new file mode 100644
index 0000000..c3b0527
--- /dev/null
+++ b/src/test/regress/expected/circle.out
@@ -0,0 +1,125 @@
+--
+-- CIRCLE
+--
+-- Back off displayed precision a little bit to reduce platform-to-platform
+-- variation in results.
+SET extra_float_digits = -1;
+CREATE TABLE CIRCLE_TBL (f1 circle);
+INSERT INTO CIRCLE_TBL VALUES ('<(5,1),3>');
+INSERT INTO CIRCLE_TBL VALUES ('((1,2),100)');
+INSERT INTO CIRCLE_TBL VALUES (' 1 , 3 , 5 ');
+INSERT INTO CIRCLE_TBL VALUES (' ( ( 1 , 2 ) , 3 ) ');
+INSERT INTO CIRCLE_TBL VALUES (' ( 100 , 200 ) , 10 ');
+INSERT INTO CIRCLE_TBL VALUES (' < ( 100 , 1 ) , 115 > ');
+INSERT INTO CIRCLE_TBL VALUES ('<(3,5),0>'); -- Zero radius
+INSERT INTO CIRCLE_TBL VALUES ('<(3,5),NaN>'); -- NaN radius
+-- bad values
+INSERT INTO CIRCLE_TBL VALUES ('<(-100,0),-100>');
+ERROR: invalid input syntax for type circle: "<(-100,0),-100>"
+LINE 1: INSERT INTO CIRCLE_TBL VALUES ('<(-100,0),-100>');
+ ^
+INSERT INTO CIRCLE_TBL VALUES ('<(100,200),10');
+ERROR: invalid input syntax for type circle: "<(100,200),10"
+LINE 1: INSERT INTO CIRCLE_TBL VALUES ('<(100,200),10');
+ ^
+INSERT INTO CIRCLE_TBL VALUES ('<(100,200),10> x');
+ERROR: invalid input syntax for type circle: "<(100,200),10> x"
+LINE 1: INSERT INTO CIRCLE_TBL VALUES ('<(100,200),10> x');
+ ^
+INSERT INTO CIRCLE_TBL VALUES ('1abc,3,5');
+ERROR: invalid input syntax for type circle: "1abc,3,5"
+LINE 1: INSERT INTO CIRCLE_TBL VALUES ('1abc,3,5');
+ ^
+INSERT INTO CIRCLE_TBL VALUES ('(3,(1,2),3)');
+ERROR: invalid input syntax for type circle: "(3,(1,2),3)"
+LINE 1: INSERT INTO CIRCLE_TBL VALUES ('(3,(1,2),3)');
+ ^
+SELECT * FROM CIRCLE_TBL;
+ f1
+----------------
+ <(5,1),3>
+ <(1,2),100>
+ <(1,3),5>
+ <(1,2),3>
+ <(100,200),10>
+ <(100,1),115>
+ <(3,5),0>
+ <(3,5),NaN>
+(8 rows)
+
+SELECT center(f1) AS center
+ FROM CIRCLE_TBL;
+ center
+-----------
+ (5,1)
+ (1,2)
+ (1,3)
+ (1,2)
+ (100,200)
+ (100,1)
+ (3,5)
+ (3,5)
+(8 rows)
+
+SELECT radius(f1) AS radius
+ FROM CIRCLE_TBL;
+ radius
+--------
+ 3
+ 100
+ 5
+ 3
+ 10
+ 115
+ 0
+ NaN
+(8 rows)
+
+SELECT diameter(f1) AS diameter
+ FROM CIRCLE_TBL;
+ diameter
+----------
+ 6
+ 200
+ 10
+ 6
+ 20
+ 230
+ 0
+ NaN
+(8 rows)
+
+SELECT f1 FROM CIRCLE_TBL WHERE radius(f1) < 5;
+ f1
+-----------
+ <(5,1),3>
+ <(1,2),3>
+ <(3,5),0>
+(3 rows)
+
+SELECT f1 FROM CIRCLE_TBL WHERE diameter(f1) >= 10;
+ f1
+----------------
+ <(1,2),100>
+ <(1,3),5>
+ <(100,200),10>
+ <(100,1),115>
+ <(3,5),NaN>
+(5 rows)
+
+SELECT c1.f1 AS one, c2.f1 AS two, (c1.f1 <-> c2.f1) AS distance
+ FROM CIRCLE_TBL c1, CIRCLE_TBL c2
+ WHERE (c1.f1 < c2.f1) AND ((c1.f1 <-> c2.f1) > 0)
+ ORDER BY distance, area(c1.f1), area(c2.f1);
+ one | two | distance
+----------------+----------------+------------------
+ <(3,5),0> | <(1,2),3> | 0.60555127546399
+ <(3,5),0> | <(5,1),3> | 1.4721359549996
+ <(100,200),10> | <(100,1),115> | 74
+ <(100,200),10> | <(1,2),100> | 111.37072977248
+ <(1,3),5> | <(100,200),10> | 205.4767561445
+ <(5,1),3> | <(100,200),10> | 207.51303816328
+ <(3,5),0> | <(100,200),10> | 207.79348015953
+ <(1,2),3> | <(100,200),10> | 208.37072977248
+(8 rows)
+
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
new file mode 100644
index 0000000..7293c0f
--- /dev/null
+++ b/src/test/regress/expected/cluster.out
@@ -0,0 +1,601 @@
+--
+-- CLUSTER
+--
+CREATE TABLE clstr_tst_s (rf_a SERIAL PRIMARY KEY,
+ b INT);
+CREATE TABLE clstr_tst (a SERIAL PRIMARY KEY,
+ b INT,
+ c TEXT,
+ d TEXT,
+ CONSTRAINT clstr_tst_con FOREIGN KEY (b) REFERENCES clstr_tst_s);
+CREATE INDEX clstr_tst_b ON clstr_tst (b);
+CREATE INDEX clstr_tst_c ON clstr_tst (c);
+CREATE INDEX clstr_tst_c_b ON clstr_tst (c,b);
+CREATE INDEX clstr_tst_b_c ON clstr_tst (b,c);
+INSERT INTO clstr_tst_s (b) VALUES (0);
+INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s;
+INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s;
+INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s;
+INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s;
+INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s;
+CREATE TABLE clstr_tst_inh () INHERITS (clstr_tst);
+INSERT INTO clstr_tst (b, c) VALUES (11, 'once');
+INSERT INTO clstr_tst (b, c) VALUES (10, 'diez');
+INSERT INTO clstr_tst (b, c) VALUES (31, 'treinta y uno');
+INSERT INTO clstr_tst (b, c) VALUES (22, 'veintidos');
+INSERT INTO clstr_tst (b, c) VALUES (3, 'tres');
+INSERT INTO clstr_tst (b, c) VALUES (20, 'veinte');
+INSERT INTO clstr_tst (b, c) VALUES (23, 'veintitres');
+INSERT INTO clstr_tst (b, c) VALUES (21, 'veintiuno');
+INSERT INTO clstr_tst (b, c) VALUES (4, 'cuatro');
+INSERT INTO clstr_tst (b, c) VALUES (14, 'catorce');
+INSERT INTO clstr_tst (b, c) VALUES (2, 'dos');
+INSERT INTO clstr_tst (b, c) VALUES (18, 'dieciocho');
+INSERT INTO clstr_tst (b, c) VALUES (27, 'veintisiete');
+INSERT INTO clstr_tst (b, c) VALUES (25, 'veinticinco');
+INSERT INTO clstr_tst (b, c) VALUES (13, 'trece');
+INSERT INTO clstr_tst (b, c) VALUES (28, 'veintiocho');
+INSERT INTO clstr_tst (b, c) VALUES (32, 'treinta y dos');
+INSERT INTO clstr_tst (b, c) VALUES (5, 'cinco');
+INSERT INTO clstr_tst (b, c) VALUES (29, 'veintinueve');
+INSERT INTO clstr_tst (b, c) VALUES (1, 'uno');
+INSERT INTO clstr_tst (b, c) VALUES (24, 'veinticuatro');
+INSERT INTO clstr_tst (b, c) VALUES (30, 'treinta');
+INSERT INTO clstr_tst (b, c) VALUES (12, 'doce');
+INSERT INTO clstr_tst (b, c) VALUES (17, 'diecisiete');
+INSERT INTO clstr_tst (b, c) VALUES (9, 'nueve');
+INSERT INTO clstr_tst (b, c) VALUES (19, 'diecinueve');
+INSERT INTO clstr_tst (b, c) VALUES (26, 'veintiseis');
+INSERT INTO clstr_tst (b, c) VALUES (15, 'quince');
+INSERT INTO clstr_tst (b, c) VALUES (7, 'siete');
+INSERT INTO clstr_tst (b, c) VALUES (16, 'dieciseis');
+INSERT INTO clstr_tst (b, c) VALUES (8, 'ocho');
+-- This entry is needed to test that TOASTED values are copied correctly.
+INSERT INTO clstr_tst (b, c, d) VALUES (6, 'seis', repeat('xyzzy', 100000));
+CLUSTER clstr_tst_c ON clstr_tst;
+SELECT a,b,c,substring(d for 30), length(d) from clstr_tst;
+ a | b | c | substring | length
+----+----+---------------+--------------------------------+--------
+ 10 | 14 | catorce | |
+ 18 | 5 | cinco | |
+ 9 | 4 | cuatro | |
+ 26 | 19 | diecinueve | |
+ 12 | 18 | dieciocho | |
+ 30 | 16 | dieciseis | |
+ 24 | 17 | diecisiete | |
+ 2 | 10 | diez | |
+ 23 | 12 | doce | |
+ 11 | 2 | dos | |
+ 25 | 9 | nueve | |
+ 31 | 8 | ocho | |
+ 1 | 11 | once | |
+ 28 | 15 | quince | |
+ 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000
+ 29 | 7 | siete | |
+ 15 | 13 | trece | |
+ 22 | 30 | treinta | |
+ 17 | 32 | treinta y dos | |
+ 3 | 31 | treinta y uno | |
+ 5 | 3 | tres | |
+ 20 | 1 | uno | |
+ 6 | 20 | veinte | |
+ 14 | 25 | veinticinco | |
+ 21 | 24 | veinticuatro | |
+ 4 | 22 | veintidos | |
+ 19 | 29 | veintinueve | |
+ 16 | 28 | veintiocho | |
+ 27 | 26 | veintiseis | |
+ 13 | 27 | veintisiete | |
+ 7 | 23 | veintitres | |
+ 8 | 21 | veintiuno | |
+(32 rows)
+
+SELECT a,b,c,substring(d for 30), length(d) from clstr_tst ORDER BY a;
+ a | b | c | substring | length
+----+----+---------------+--------------------------------+--------
+ 1 | 11 | once | |
+ 2 | 10 | diez | |
+ 3 | 31 | treinta y uno | |
+ 4 | 22 | veintidos | |
+ 5 | 3 | tres | |
+ 6 | 20 | veinte | |
+ 7 | 23 | veintitres | |
+ 8 | 21 | veintiuno | |
+ 9 | 4 | cuatro | |
+ 10 | 14 | catorce | |
+ 11 | 2 | dos | |
+ 12 | 18 | dieciocho | |
+ 13 | 27 | veintisiete | |
+ 14 | 25 | veinticinco | |
+ 15 | 13 | trece | |
+ 16 | 28 | veintiocho | |
+ 17 | 32 | treinta y dos | |
+ 18 | 5 | cinco | |
+ 19 | 29 | veintinueve | |
+ 20 | 1 | uno | |
+ 21 | 24 | veinticuatro | |
+ 22 | 30 | treinta | |
+ 23 | 12 | doce | |
+ 24 | 17 | diecisiete | |
+ 25 | 9 | nueve | |
+ 26 | 19 | diecinueve | |
+ 27 | 26 | veintiseis | |
+ 28 | 15 | quince | |
+ 29 | 7 | siete | |
+ 30 | 16 | dieciseis | |
+ 31 | 8 | ocho | |
+ 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000
+(32 rows)
+
+SELECT a,b,c,substring(d for 30), length(d) from clstr_tst ORDER BY b;
+ a | b | c | substring | length
+----+----+---------------+--------------------------------+--------
+ 20 | 1 | uno | |
+ 11 | 2 | dos | |
+ 5 | 3 | tres | |
+ 9 | 4 | cuatro | |
+ 18 | 5 | cinco | |
+ 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000
+ 29 | 7 | siete | |
+ 31 | 8 | ocho | |
+ 25 | 9 | nueve | |
+ 2 | 10 | diez | |
+ 1 | 11 | once | |
+ 23 | 12 | doce | |
+ 15 | 13 | trece | |
+ 10 | 14 | catorce | |
+ 28 | 15 | quince | |
+ 30 | 16 | dieciseis | |
+ 24 | 17 | diecisiete | |
+ 12 | 18 | dieciocho | |
+ 26 | 19 | diecinueve | |
+ 6 | 20 | veinte | |
+ 8 | 21 | veintiuno | |
+ 4 | 22 | veintidos | |
+ 7 | 23 | veintitres | |
+ 21 | 24 | veinticuatro | |
+ 14 | 25 | veinticinco | |
+ 27 | 26 | veintiseis | |
+ 13 | 27 | veintisiete | |
+ 16 | 28 | veintiocho | |
+ 19 | 29 | veintinueve | |
+ 22 | 30 | treinta | |
+ 3 | 31 | treinta y uno | |
+ 17 | 32 | treinta y dos | |
+(32 rows)
+
+SELECT a,b,c,substring(d for 30), length(d) from clstr_tst ORDER BY c;
+ a | b | c | substring | length
+----+----+---------------+--------------------------------+--------
+ 10 | 14 | catorce | |
+ 18 | 5 | cinco | |
+ 9 | 4 | cuatro | |
+ 26 | 19 | diecinueve | |
+ 12 | 18 | dieciocho | |
+ 30 | 16 | dieciseis | |
+ 24 | 17 | diecisiete | |
+ 2 | 10 | diez | |
+ 23 | 12 | doce | |
+ 11 | 2 | dos | |
+ 25 | 9 | nueve | |
+ 31 | 8 | ocho | |
+ 1 | 11 | once | |
+ 28 | 15 | quince | |
+ 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000
+ 29 | 7 | siete | |
+ 15 | 13 | trece | |
+ 22 | 30 | treinta | |
+ 17 | 32 | treinta y dos | |
+ 3 | 31 | treinta y uno | |
+ 5 | 3 | tres | |
+ 20 | 1 | uno | |
+ 6 | 20 | veinte | |
+ 14 | 25 | veinticinco | |
+ 21 | 24 | veinticuatro | |
+ 4 | 22 | veintidos | |
+ 19 | 29 | veintinueve | |
+ 16 | 28 | veintiocho | |
+ 27 | 26 | veintiseis | |
+ 13 | 27 | veintisiete | |
+ 7 | 23 | veintitres | |
+ 8 | 21 | veintiuno | |
+(32 rows)
+
+-- Verify that inheritance link still works
+INSERT INTO clstr_tst_inh VALUES (0, 100, 'in child table');
+SELECT a,b,c,substring(d for 30), length(d) from clstr_tst;
+ a | b | c | substring | length
+----+-----+----------------+--------------------------------+--------
+ 10 | 14 | catorce | |
+ 18 | 5 | cinco | |
+ 9 | 4 | cuatro | |
+ 26 | 19 | diecinueve | |
+ 12 | 18 | dieciocho | |
+ 30 | 16 | dieciseis | |
+ 24 | 17 | diecisiete | |
+ 2 | 10 | diez | |
+ 23 | 12 | doce | |
+ 11 | 2 | dos | |
+ 25 | 9 | nueve | |
+ 31 | 8 | ocho | |
+ 1 | 11 | once | |
+ 28 | 15 | quince | |
+ 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000
+ 29 | 7 | siete | |
+ 15 | 13 | trece | |
+ 22 | 30 | treinta | |
+ 17 | 32 | treinta y dos | |
+ 3 | 31 | treinta y uno | |
+ 5 | 3 | tres | |
+ 20 | 1 | uno | |
+ 6 | 20 | veinte | |
+ 14 | 25 | veinticinco | |
+ 21 | 24 | veinticuatro | |
+ 4 | 22 | veintidos | |
+ 19 | 29 | veintinueve | |
+ 16 | 28 | veintiocho | |
+ 27 | 26 | veintiseis | |
+ 13 | 27 | veintisiete | |
+ 7 | 23 | veintitres | |
+ 8 | 21 | veintiuno | |
+ 0 | 100 | in child table | |
+(33 rows)
+
+-- Verify that foreign key link still works
+INSERT INTO clstr_tst (b, c) VALUES (1111, 'this should fail');
+ERROR: insert or update on table "clstr_tst" violates foreign key constraint "clstr_tst_con"
+DETAIL: Key (b)=(1111) is not present in table "clstr_tst_s".
+SELECT conname FROM pg_constraint WHERE conrelid = 'clstr_tst'::regclass
+ORDER BY 1;
+ conname
+----------------
+ clstr_tst_con
+ clstr_tst_pkey
+(2 rows)
+
+SELECT relname, relkind,
+ EXISTS(SELECT 1 FROM pg_class WHERE oid = c.reltoastrelid) AS hastoast
+FROM pg_class c WHERE relname LIKE 'clstr_tst%' ORDER BY relname;
+ relname | relkind | hastoast
+----------------------+---------+----------
+ clstr_tst | r | t
+ clstr_tst_a_seq | S | f
+ clstr_tst_b | i | f
+ clstr_tst_b_c | i | f
+ clstr_tst_c | i | f
+ clstr_tst_c_b | i | f
+ clstr_tst_inh | r | t
+ clstr_tst_pkey | i | f
+ clstr_tst_s | r | f
+ clstr_tst_s_pkey | i | f
+ clstr_tst_s_rf_a_seq | S | f
+(11 rows)
+
+-- Verify that indisclustered is correctly set
+SELECT pg_class.relname FROM pg_index, pg_class, pg_class AS pg_class_2
+WHERE pg_class.oid=indexrelid
+ AND indrelid=pg_class_2.oid
+ AND pg_class_2.relname = 'clstr_tst'
+ AND indisclustered;
+ relname
+-------------
+ clstr_tst_c
+(1 row)
+
+-- Try changing indisclustered
+ALTER TABLE clstr_tst CLUSTER ON clstr_tst_b_c;
+SELECT pg_class.relname FROM pg_index, pg_class, pg_class AS pg_class_2
+WHERE pg_class.oid=indexrelid
+ AND indrelid=pg_class_2.oid
+ AND pg_class_2.relname = 'clstr_tst'
+ AND indisclustered;
+ relname
+---------------
+ clstr_tst_b_c
+(1 row)
+
+-- Try turning off all clustering
+ALTER TABLE clstr_tst SET WITHOUT CLUSTER;
+SELECT pg_class.relname FROM pg_index, pg_class, pg_class AS pg_class_2
+WHERE pg_class.oid=indexrelid
+ AND indrelid=pg_class_2.oid
+ AND pg_class_2.relname = 'clstr_tst'
+ AND indisclustered;
+ relname
+---------
+(0 rows)
+
+-- Verify that clustering all tables does in fact cluster the right ones
+CREATE USER regress_clstr_user;
+CREATE TABLE clstr_1 (a INT PRIMARY KEY);
+CREATE TABLE clstr_2 (a INT PRIMARY KEY);
+CREATE TABLE clstr_3 (a INT PRIMARY KEY);
+ALTER TABLE clstr_1 OWNER TO regress_clstr_user;
+ALTER TABLE clstr_3 OWNER TO regress_clstr_user;
+GRANT SELECT ON clstr_2 TO regress_clstr_user;
+INSERT INTO clstr_1 VALUES (2);
+INSERT INTO clstr_1 VALUES (1);
+INSERT INTO clstr_2 VALUES (2);
+INSERT INTO clstr_2 VALUES (1);
+INSERT INTO clstr_3 VALUES (2);
+INSERT INTO clstr_3 VALUES (1);
+-- "CLUSTER <tablename>" on a table that hasn't been clustered
+CLUSTER clstr_2;
+ERROR: there is no previously clustered index for table "clstr_2"
+CLUSTER clstr_1_pkey ON clstr_1;
+CLUSTER clstr_2 USING clstr_2_pkey;
+SELECT * FROM clstr_1 UNION ALL
+ SELECT * FROM clstr_2 UNION ALL
+ SELECT * FROM clstr_3;
+ a
+---
+ 1
+ 2
+ 1
+ 2
+ 2
+ 1
+(6 rows)
+
+-- revert to the original state
+DELETE FROM clstr_1;
+DELETE FROM clstr_2;
+DELETE FROM clstr_3;
+INSERT INTO clstr_1 VALUES (2);
+INSERT INTO clstr_1 VALUES (1);
+INSERT INTO clstr_2 VALUES (2);
+INSERT INTO clstr_2 VALUES (1);
+INSERT INTO clstr_3 VALUES (2);
+INSERT INTO clstr_3 VALUES (1);
+-- this user can only cluster clstr_1 and clstr_3, but the latter
+-- has not been clustered
+SET SESSION AUTHORIZATION regress_clstr_user;
+CLUSTER;
+SELECT * FROM clstr_1 UNION ALL
+ SELECT * FROM clstr_2 UNION ALL
+ SELECT * FROM clstr_3;
+ a
+---
+ 1
+ 2
+ 2
+ 1
+ 2
+ 1
+(6 rows)
+
+-- cluster a single table using the indisclustered bit previously set
+DELETE FROM clstr_1;
+INSERT INTO clstr_1 VALUES (2);
+INSERT INTO clstr_1 VALUES (1);
+CLUSTER clstr_1;
+SELECT * FROM clstr_1;
+ a
+---
+ 1
+ 2
+(2 rows)
+
+-- Test MVCC-safety of cluster. There isn't much we can do to verify the
+-- results with a single backend...
+CREATE TABLE clustertest (key int PRIMARY KEY);
+INSERT INTO clustertest VALUES (10);
+INSERT INTO clustertest VALUES (20);
+INSERT INTO clustertest VALUES (30);
+INSERT INTO clustertest VALUES (40);
+INSERT INTO clustertest VALUES (50);
+-- Use a transaction so that updates are not committed when CLUSTER sees 'em
+BEGIN;
+-- Test update where the old row version is found first in the scan
+UPDATE clustertest SET key = 100 WHERE key = 10;
+-- Test update where the new row version is found first in the scan
+UPDATE clustertest SET key = 35 WHERE key = 40;
+-- Test longer update chain
+UPDATE clustertest SET key = 60 WHERE key = 50;
+UPDATE clustertest SET key = 70 WHERE key = 60;
+UPDATE clustertest SET key = 80 WHERE key = 70;
+SELECT * FROM clustertest;
+ key
+-----
+ 20
+ 30
+ 100
+ 35
+ 80
+(5 rows)
+
+CLUSTER clustertest_pkey ON clustertest;
+SELECT * FROM clustertest;
+ key
+-----
+ 20
+ 30
+ 35
+ 80
+ 100
+(5 rows)
+
+COMMIT;
+SELECT * FROM clustertest;
+ key
+-----
+ 20
+ 30
+ 35
+ 80
+ 100
+(5 rows)
+
+-- check that temp tables can be clustered
+create temp table clstr_temp (col1 int primary key, col2 text);
+insert into clstr_temp values (2, 'two'), (1, 'one');
+cluster clstr_temp using clstr_temp_pkey;
+select * from clstr_temp;
+ col1 | col2
+------+------
+ 1 | one
+ 2 | two
+(2 rows)
+
+drop table clstr_temp;
+RESET SESSION AUTHORIZATION;
+-- check clustering an empty table
+DROP TABLE clustertest;
+CREATE TABLE clustertest (f1 int PRIMARY KEY);
+CLUSTER clustertest USING clustertest_pkey;
+CLUSTER clustertest;
+-- Check that partitioned tables cannot be clustered
+CREATE TABLE clstrpart (a int) PARTITION BY RANGE (a);
+CREATE INDEX clstrpart_idx ON clstrpart (a);
+ALTER TABLE clstrpart CLUSTER ON clstrpart_idx;
+ERROR: cannot mark index clustered in partitioned table
+CLUSTER clstrpart USING clstrpart_idx;
+ERROR: cannot cluster a partitioned table
+DROP TABLE clstrpart;
+-- Test CLUSTER with external tuplesorting
+create table clstr_4 as select * from tenk1;
+create index cluster_sort on clstr_4 (hundred, thousand, tenthous);
+-- ensure we don't use the index in CLUSTER nor the checking SELECTs
+set enable_indexscan = off;
+-- Use external sort:
+set maintenance_work_mem = '1MB';
+cluster clstr_4 using cluster_sort;
+select * from
+(select hundred, lag(hundred) over () as lhundred,
+ thousand, lag(thousand) over () as lthousand,
+ tenthous, lag(tenthous) over () as ltenthous from clstr_4) ss
+where row(hundred, thousand, tenthous) <= row(lhundred, lthousand, ltenthous);
+ hundred | lhundred | thousand | lthousand | tenthous | ltenthous
+---------+----------+----------+-----------+----------+-----------
+(0 rows)
+
+reset enable_indexscan;
+reset maintenance_work_mem;
+-- test CLUSTER on expression index
+CREATE TABLE clstr_expression(id serial primary key, a int, b text COLLATE "C");
+INSERT INTO clstr_expression(a, b) SELECT g.i % 42, 'prefix'||g.i FROM generate_series(1, 133) g(i);
+CREATE INDEX clstr_expression_minus_a ON clstr_expression ((-a), b);
+CREATE INDEX clstr_expression_upper_b ON clstr_expression ((upper(b)));
+-- verify indexes work before cluster
+BEGIN;
+SET LOCAL enable_seqscan = false;
+EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
+ QUERY PLAN
+---------------------------------------------------------------
+ Index Scan using clstr_expression_upper_b on clstr_expression
+ Index Cond: (upper(b) = 'PREFIX3'::text)
+(2 rows)
+
+SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
+ id | a | b
+----+---+---------
+ 3 | 3 | prefix3
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
+ QUERY PLAN
+---------------------------------------------------------------
+ Index Scan using clstr_expression_minus_a on clstr_expression
+ Index Cond: ((- a) = '-3'::integer)
+(2 rows)
+
+SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
+ id | a | b
+-----+---+-----------
+ 129 | 3 | prefix129
+ 3 | 3 | prefix3
+ 45 | 3 | prefix45
+ 87 | 3 | prefix87
+(4 rows)
+
+COMMIT;
+-- and after clustering on clstr_expression_minus_a
+CLUSTER clstr_expression USING clstr_expression_minus_a;
+WITH rows AS
+ (SELECT ctid, lag(a) OVER (ORDER BY ctid) AS la, a FROM clstr_expression)
+SELECT * FROM rows WHERE la < a;
+ ctid | la | a
+------+----+---
+(0 rows)
+
+BEGIN;
+SET LOCAL enable_seqscan = false;
+EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
+ QUERY PLAN
+---------------------------------------------------------------
+ Index Scan using clstr_expression_upper_b on clstr_expression
+ Index Cond: (upper(b) = 'PREFIX3'::text)
+(2 rows)
+
+SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
+ id | a | b
+----+---+---------
+ 3 | 3 | prefix3
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
+ QUERY PLAN
+---------------------------------------------------------------
+ Index Scan using clstr_expression_minus_a on clstr_expression
+ Index Cond: ((- a) = '-3'::integer)
+(2 rows)
+
+SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
+ id | a | b
+-----+---+-----------
+ 129 | 3 | prefix129
+ 3 | 3 | prefix3
+ 45 | 3 | prefix45
+ 87 | 3 | prefix87
+(4 rows)
+
+COMMIT;
+-- and after clustering on clstr_expression_upper_b
+CLUSTER clstr_expression USING clstr_expression_upper_b;
+WITH rows AS
+ (SELECT ctid, lag(b) OVER (ORDER BY ctid) AS lb, b FROM clstr_expression)
+SELECT * FROM rows WHERE upper(lb) > upper(b);
+ ctid | lb | b
+------+----+---
+(0 rows)
+
+BEGIN;
+SET LOCAL enable_seqscan = false;
+EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
+ QUERY PLAN
+---------------------------------------------------------------
+ Index Scan using clstr_expression_upper_b on clstr_expression
+ Index Cond: (upper(b) = 'PREFIX3'::text)
+(2 rows)
+
+SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
+ id | a | b
+----+---+---------
+ 3 | 3 | prefix3
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
+ QUERY PLAN
+---------------------------------------------------------------
+ Index Scan using clstr_expression_minus_a on clstr_expression
+ Index Cond: ((- a) = '-3'::integer)
+(2 rows)
+
+SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
+ id | a | b
+-----+---+-----------
+ 129 | 3 | prefix129
+ 3 | 3 | prefix3
+ 45 | 3 | prefix45
+ 87 | 3 | prefix87
+(4 rows)
+
+COMMIT;
+-- clean up
+DROP TABLE clustertest;
+DROP TABLE clstr_1;
+DROP TABLE clstr_2;
+DROP TABLE clstr_3;
+DROP TABLE clstr_4;
+DROP TABLE clstr_expression;
+DROP USER regress_clstr_user;
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
new file mode 100644
index 0000000..70133df
--- /dev/null
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -0,0 +1,1959 @@
+/*
+ * 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
+ Table "collate_tests.collate_test1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | text | en-x-icu | not null |
+
+CREATE TABLE collate_test_fail (
+ a int,
+ b text COLLATE "ja_JP.eucjp-x-icu"
+);
+ERROR: collation "ja_JP.eucjp-x-icu" for encoding "UTF8" does not exist
+LINE 3: b text COLLATE "ja_JP.eucjp-x-icu"
+ ^
+CREATE TABLE collate_test_fail (
+ a int,
+ b text COLLATE "foo-x-icu"
+);
+ERROR: collation "foo-x-icu" for encoding "UTF8" does not exist
+LINE 3: b text COLLATE "foo-x-icu"
+ ^
+CREATE TABLE collate_test_fail (
+ a int COLLATE "en-x-icu",
+ b text
+);
+ERROR: collations are not supported by type integer
+LINE 2: a int COLLATE "en-x-icu",
+ ^
+CREATE TABLE collate_test_like (
+ LIKE collate_test1
+);
+\d collate_test_like
+ Table "collate_tests.collate_test_like"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | text | en-x-icu | not null |
+
+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';
+ a | b
+---+-----
+ 3 | bbc
+(1 row)
+
+SELECT * FROM collate_test2 WHERE b >= 'bbc';
+ a | b
+---+-----
+ 2 | äbc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test3 WHERE b >= 'bbc';
+ a | b
+---+-----
+ 2 | äbc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test3 WHERE b >= 'BBC';
+ a | b
+---+-----
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+(3 rows)
+
+SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc';
+ a | b
+---+-----
+ 2 | äbc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b >= 'bbc' COLLATE "C";
+ a | b
+---+-----
+ 2 | äbc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "C";
+ a | b
+---+-----
+ 2 | äbc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "en-x-icu";
+ERROR: collation mismatch between explicit collations "C" and "en-x-icu"
+LINE 1: ...* FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "e...
+ ^
+CREATE DOMAIN testdomain_sv AS text COLLATE "sv-x-icu";
+CREATE DOMAIN testdomain_i AS int COLLATE "sv-x-icu"; -- fails
+ERROR: collations are not supported by type integer
+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;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+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;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b FROM collate_test1 ORDER BY b;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b FROM collate_test2 ORDER BY b;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, b FROM collate_test3 ORDER BY b;
+ a | b
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
+ a | b
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+-- star expansion
+SELECT * FROM collate_test1 ORDER BY b;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT * FROM collate_test2 ORDER BY b;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT * FROM collate_test3 ORDER BY b;
+ a | b
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+-- constant expression folding
+SELECT 'bbc' COLLATE "en-x-icu" > 'äbc' COLLATE "en-x-icu" AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'bbc' COLLATE "sv-x-icu" > 'äbc' COLLATE "sv-x-icu" AS "false";
+ false
+-------
+ f
+(1 row)
+
+-- 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;
+ a | lower | lower | upper | upper | initcap | initcap
+---+-------+-------+-------+-------+---------+---------
+ 1 | hij | hij | HIJ | HÄ°J | Hij | Hij
+ 2 | hij | hıj | HIJ | HIJ | Hij | Hıj
+(2 rows)
+
+SELECT a, lower(x COLLATE "C"), lower(y COLLATE "C") FROM collate_test10;
+ a | lower | lower
+---+-------+-------
+ 1 | hij | hij
+ 2 | hij | hij
+(2 rows)
+
+SELECT a, x, y FROM collate_test10 ORDER BY lower(y), a;
+ a | x | y
+---+-----+-----
+ 2 | HIJ | HIJ
+ 1 | hij | hij
+(2 rows)
+
+-- LIKE/ILIKE
+SELECT * FROM collate_test1 WHERE b LIKE 'abc';
+ a | b
+---+-----
+ 1 | abc
+(1 row)
+
+SELECT * FROM collate_test1 WHERE b LIKE 'abc%';
+ a | b
+---+-----
+ 1 | abc
+(1 row)
+
+SELECT * FROM collate_test1 WHERE b LIKE '%bc%';
+ a | b
+---+-----
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+(3 rows)
+
+SELECT * FROM collate_test1 WHERE b ILIKE 'abc';
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b ILIKE 'abc%';
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b ILIKE '%bc%';
+ a | b
+---+-----
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+ 4 | ABC
+(4 rows)
+
+SELECT 'Türkiye' COLLATE "en-x-icu" ILIKE '%KI%' AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'Türkiye' COLLATE "tr-x-icu" ILIKE '%KI%' AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'bıt' ILIKE 'BIT' COLLATE "en-x-icu" AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'bıt' ILIKE 'BIT' COLLATE "tr-x-icu" AS "true";
+ true
+------
+ t
+(1 row)
+
+-- The following actually exercises the selectivity estimation for ILIKE.
+SELECT relname FROM pg_class WHERE relname ILIKE 'abc%';
+ relname
+---------
+(0 rows)
+
+-- regular expressions
+SELECT * FROM collate_test1 WHERE b ~ '^abc$';
+ a | b
+---+-----
+ 1 | abc
+(1 row)
+
+SELECT * FROM collate_test1 WHERE b ~ '^abc';
+ a | b
+---+-----
+ 1 | abc
+(1 row)
+
+SELECT * FROM collate_test1 WHERE b ~ 'bc';
+ a | b
+---+-----
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+(3 rows)
+
+SELECT * FROM collate_test1 WHERE b ~* '^abc$';
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b ~* '^abc';
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b ~* 'bc';
+ a | b
+---+-----
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+ 4 | ABC
+(4 rows)
+
+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;
+ b | is_alpha | is_upper | is_lower | is_digit | is_alnum | is_graph | is_print | is_punct | is_space
+-----+----------+----------+----------+----------+----------+----------+----------+----------+----------
+ abc | t | f | t | f | t | t | t | f | f
+ ABC | t | t | f | f | t | t | t | f | f
+ 123 | f | f | f | t | t | t | t | f | f
+ ab1 | f | f | f | f | t | t | t | f | f
+ a1! | f | f | f | f | f | t | t | f | f
+ a c | f | f | f | f | f | f | t | f | f
+ !.; | f | f | f | f | f | t | t | t | f
+ | f | f | f | f | f | f | t | f | t
+ äbç | t | f | t | f | t | t | t | f | f
+ ÄBÇ | t | t | f | f | t | t | t | f | f
+(10 rows)
+
+SELECT 'Türkiye' COLLATE "en-x-icu" ~* 'KI' AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'Türkiye' COLLATE "tr-x-icu" ~* 'KI' AS "true"; -- true with ICU
+ true
+------
+ t
+(1 row)
+
+SELECT 'bıt' ~* 'BIT' COLLATE "en-x-icu" AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'bıt' ~* 'BIT' COLLATE "tr-x-icu" AS "false"; -- false with ICU
+ false
+-------
+ f
+(1 row)
+
+-- The following actually exercises the selectivity estimation for ~*.
+SELECT relname FROM pg_class WHERE relname ~* '^abc';
+ relname
+---------
+(0 rows)
+
+/* 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;
+ table_name | view_definition
+------------+--------------------------------------------------------------------------
+ collview1 | SELECT collate_test1.a, +
+ | collate_test1.b +
+ | FROM collate_test1 +
+ | WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
+ collview2 | SELECT collate_test1.a, +
+ | collate_test1.b +
+ | FROM collate_test1 +
+ | ORDER BY (collate_test1.b COLLATE "C");
+ collview3 | SELECT collate_test10.a, +
+ | lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ | FROM collate_test10;
+(3 rows)
+
+-- collation propagation in various expression types
+SELECT a, coalesce(b, 'foo') FROM collate_test1 ORDER BY 2;
+ a | coalesce
+---+----------
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT a, coalesce(b, 'foo') FROM collate_test2 ORDER BY 2;
+ a | coalesce
+---+----------
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, coalesce(b, 'foo') FROM collate_test3 ORDER BY 2;
+ a | coalesce
+---+----------
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, lower(coalesce(x, 'foo')), lower(coalesce(y, 'foo')) FROM collate_test10;
+ a | lower | lower
+---+-------+-------
+ 1 | hij | hij
+ 2 | hij | hıj
+(2 rows)
+
+SELECT a, b, greatest(b, 'CCC') FROM collate_test1 ORDER BY 3;
+ a | b | greatest
+---+-----+----------
+ 1 | abc | CCC
+ 2 | äbc | CCC
+ 3 | bbc | CCC
+ 4 | ABC | CCC
+(4 rows)
+
+SELECT a, b, greatest(b, 'CCC') FROM collate_test2 ORDER BY 3;
+ a | b | greatest
+---+-----+----------
+ 1 | abc | CCC
+ 3 | bbc | CCC
+ 4 | ABC | CCC
+ 2 | äbc | äbc
+(4 rows)
+
+SELECT a, b, greatest(b, 'CCC') FROM collate_test3 ORDER BY 3;
+ a | b | greatest
+---+-----+----------
+ 4 | ABC | CCC
+ 1 | abc | abc
+ 3 | bbc | bbc
+ 2 | äbc | äbc
+(4 rows)
+
+SELECT a, x, y, lower(greatest(x, 'foo')), lower(greatest(y, 'foo')) FROM collate_test10;
+ a | x | y | lower | lower
+---+-----+-----+-------+-------
+ 1 | hij | hij | hij | hij
+ 2 | HIJ | HIJ | hij | hıj
+(2 rows)
+
+SELECT a, nullif(b, 'abc') FROM collate_test1 ORDER BY 2;
+ a | nullif
+---+--------
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+ 1 |
+(4 rows)
+
+SELECT a, nullif(b, 'abc') FROM collate_test2 ORDER BY 2;
+ a | nullif
+---+--------
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+ 1 |
+(4 rows)
+
+SELECT a, nullif(b, 'abc') FROM collate_test3 ORDER BY 2;
+ a | nullif
+---+--------
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+ 1 |
+(4 rows)
+
+SELECT a, lower(nullif(x, 'foo')), lower(nullif(y, 'foo')) FROM collate_test10;
+ a | lower | lower
+---+-------+-------
+ 1 | hij | hij
+ 2 | hij | hıj
+(2 rows)
+
+SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test1 ORDER BY 2;
+ a | b
+---+------
+ 4 | ABC
+ 2 | äbc
+ 1 | abcd
+ 3 | bbc
+(4 rows)
+
+SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test2 ORDER BY 2;
+ a | b
+---+------
+ 4 | ABC
+ 1 | abcd
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test3 ORDER BY 2;
+ a | b
+---+------
+ 4 | ABC
+ 1 | abcd
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+CREATE DOMAIN testdomain AS text;
+SELECT a, b::testdomain FROM collate_test1 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b::testdomain FROM collate_test2 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, b::testdomain FROM collate_test3 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, b::testdomain_sv FROM collate_test3 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, lower(x::testdomain), lower(y::testdomain) FROM collate_test10;
+ a | lower | lower
+---+-------+-------
+ 1 | hij | hij
+ 2 | hij | hıj
+(2 rows)
+
+SELECT min(b), max(b) FROM collate_test1;
+ min | max
+-----+-----
+ abc | bbc
+(1 row)
+
+SELECT min(b), max(b) FROM collate_test2;
+ min | max
+-----+-----
+ abc | äbc
+(1 row)
+
+SELECT min(b), max(b) FROM collate_test3;
+ min | max
+-----+-----
+ ABC | äbc
+(1 row)
+
+SELECT array_agg(b ORDER BY b) FROM collate_test1;
+ array_agg
+-------------------
+ {abc,ABC,äbc,bbc}
+(1 row)
+
+SELECT array_agg(b ORDER BY b) FROM collate_test2;
+ array_agg
+-------------------
+ {abc,ABC,bbc,äbc}
+(1 row)
+
+SELECT array_agg(b ORDER BY b) FROM collate_test3;
+ array_agg
+-------------------
+ {ABC,abc,bbc,äbc}
+(1 row)
+
+SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test1 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 1 | abc
+ 4 | ABC
+ 4 | ABC
+ 2 | äbc
+ 2 | äbc
+ 3 | bbc
+ 3 | bbc
+(8 rows)
+
+SELECT a, b FROM collate_test2 UNION SELECT a, b FROM collate_test2 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, b FROM collate_test3 WHERE a < 4 INTERSECT SELECT a, b FROM collate_test3 WHERE a > 1 ORDER BY 2;
+ a | b
+---+-----
+ 3 | bbc
+ 2 | äbc
+(2 rows)
+
+SELECT a, b FROM collate_test3 EXCEPT SELECT a, b FROM collate_test3 WHERE a < 2 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(3 rows)
+
+SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3 ORDER BY 2; -- fail
+ERROR: could not determine which collation to use for string comparison
+HINT: Use the COLLATE clause to set the collation explicitly.
+SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3; -- ok
+ a | b
+---+-----
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+ 4 | ABC
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+ 4 | ABC
+(8 rows)
+
+SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collate_test3 ORDER BY 2; -- fail
+ERROR: collation mismatch between implicit collations "en-x-icu" and "C"
+LINE 1: SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collat...
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+SELECT a, b COLLATE "C" FROM collate_test1 UNION SELECT a, b FROM collate_test3 ORDER BY 2; -- ok
+ a | b
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail
+ERROR: collation mismatch between implicit collations "en-x-icu" and "C"
+LINE 1: ...ELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM col...
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail
+ERROR: collation mismatch between implicit collations "en-x-icu" and "C"
+LINE 1: SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM colla...
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+CREATE TABLE test_u AS SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3; -- fail
+ERROR: no collation was derived for column "b" with collatable type text
+HINT: Use the COLLATE clause to set the collation explicitly.
+-- ideally this would be a parse-time error, but for now it must be run-time:
+select x < y from collate_test10; -- fail
+ERROR: could not determine which collation to use for string comparison
+HINT: Use the COLLATE clause to set the collation explicitly.
+select x || y from collate_test10; -- ok, because || is not collation aware
+ ?column?
+----------
+ hijhij
+ HIJHIJ
+(2 rows)
+
+select x, y from collate_test10 order by x || y; -- not so ok
+ERROR: collation mismatch between implicit collations "en-x-icu" and "tr-x-icu"
+LINE 1: select x, y from collate_test10 order by x || y;
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+-- 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;
+ERROR: recursive query "foo" column 1 has collation "en-x-icu" in non-recursive term but collation "de-x-icu" overall
+LINE 2: (SELECT x FROM (VALUES('a' COLLATE "en-x-icu"),('b')) t(x...
+ ^
+HINT: Use the COLLATE clause to set the collation of the non-recursive term.
+-- casting
+SELECT CAST('42' AS text COLLATE "C");
+ERROR: syntax error at or near "COLLATE"
+LINE 1: SELECT CAST('42' AS text COLLATE "C");
+ ^
+SELECT a, CAST(b AS varchar) FROM collate_test1 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT a, CAST(b AS varchar) FROM collate_test2 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, CAST(b AS varchar) FROM collate_test3 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+-- 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;
+ a | b | lt | mylt | mylt_noninline | mylt_plpgsql
+-----+-----+----+------+----------------+--------------
+ abc | abc | f | f | f | f
+ abc | ABC | t | t | t | t
+ abc | äbc | t | t | t | t
+ abc | bbc | t | t | t | t
+ ABC | abc | f | f | f | f
+ ABC | ABC | f | f | f | f
+ ABC | äbc | t | t | t | t
+ ABC | bbc | t | t | t | t
+ äbc | abc | f | f | f | f
+ äbc | ABC | f | f | f | f
+ äbc | äbc | f | f | f | f
+ äbc | bbc | t | t | t | t
+ bbc | abc | f | f | f | f
+ bbc | ABC | f | f | f | f
+ bbc | äbc | f | f | f | f
+ bbc | bbc | f | f | f | f
+(16 rows)
+
+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;
+ a | b | lt | mylt | mylt_noninline | mylt_plpgsql
+-----+-----+----+------+----------------+--------------
+ abc | abc | f | f | f | f
+ abc | ABC | f | f | f | f
+ abc | äbc | t | t | t | t
+ abc | bbc | t | t | t | t
+ ABC | abc | t | t | t | t
+ ABC | ABC | f | f | f | f
+ ABC | äbc | t | t | t | t
+ ABC | bbc | t | t | t | t
+ äbc | abc | f | f | f | f
+ äbc | ABC | f | f | f | f
+ äbc | äbc | f | f | f | f
+ äbc | bbc | f | f | f | f
+ bbc | abc | f | f | f | f
+ bbc | ABC | f | f | f | f
+ bbc | äbc | t | t | t | t
+ bbc | bbc | f | f | f | f
+(16 rows)
+
+-- 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;
+ t | f
+---+---
+ t | f
+(1 row)
+
+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;
+ f
+---
+ f
+(1 row)
+
+SELECT mylt2('a', 'B' collate "C") as fail; -- conflicting collations
+ERROR: could not determine which collation to use for string comparison
+HINT: Use the COLLATE clause to set the collation explicitly.
+CONTEXT: PL/pgSQL function mylt2(text,text) line 6 at RETURN
+SELECT mylt2('a', 'B' collate "POSIX") as f;
+ f
+---
+ f
+(1 row)
+
+-- polymorphism
+SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1;
+ unnest
+--------
+ abc
+ ABC
+ äbc
+ bbc
+(4 rows)
+
+SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test2)) ORDER BY 1;
+ unnest
+--------
+ abc
+ ABC
+ bbc
+ äbc
+(4 rows)
+
+SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test3)) ORDER BY 1;
+ unnest
+--------
+ ABC
+ abc
+ bbc
+ äbc
+(4 rows)
+
+CREATE FUNCTION dup (anyelement) RETURNS anyelement
+ AS 'select $1' LANGUAGE sql;
+SELECT a, dup(b) FROM collate_test1 ORDER BY 2;
+ a | dup
+---+-----
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT a, dup(b) FROM collate_test2 ORDER BY 2;
+ a | dup
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, dup(b) FROM collate_test3 ORDER BY 2;
+ a | dup
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+-- 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
+ERROR: collations are not supported by type integer
+CREATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "C")); -- fail
+ERROR: collations are not supported by type integer
+LINE 1: ...ATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "C...
+ ^
+SELECT relname, pg_get_indexdef(oid) FROM pg_class WHERE relname LIKE 'collate_test%_idx%' ORDER BY 1;
+ relname | pg_get_indexdef
+--------------------+-------------------------------------------------------------------------------------------------------------------
+ collate_test1_idx1 | CREATE INDEX collate_test1_idx1 ON collate_tests.collate_test1 USING btree (b)
+ collate_test1_idx2 | CREATE INDEX collate_test1_idx2 ON collate_tests.collate_test1 USING btree (b COLLATE "C")
+ collate_test1_idx3 | CREATE INDEX collate_test1_idx3 ON collate_tests.collate_test1 USING btree (b COLLATE "C")
+ collate_test1_idx4 | CREATE INDEX collate_test1_idx4 ON collate_tests.collate_test1 USING btree (((b || 'foo'::text)) COLLATE "POSIX")
+(4 rows)
+
+set enable_seqscan = off;
+explain (costs off)
+select * from collate_test1 where b ilike 'abc';
+ QUERY PLAN
+-------------------------------
+ Seq Scan on collate_test1
+ Filter: (b ~~* 'abc'::text)
+(2 rows)
+
+select * from collate_test1 where b ilike 'abc';
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+(2 rows)
+
+explain (costs off)
+select * from collate_test1 where b ilike 'ABC';
+ QUERY PLAN
+-------------------------------
+ Seq Scan on collate_test1
+ Filter: (b ~~* 'ABC'::text)
+(2 rows)
+
+select * from collate_test1 where b ilike 'ABC';
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+(2 rows)
+
+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
+ERROR: collation "test0" already exists
+do $$
+BEGIN
+ EXECUTE 'CREATE COLLATION test1 (provider = icu, lc_collate = ' ||
+ quote_literal(current_setting('lc_collate')) ||
+ ', lc_ctype = ' ||
+ quote_literal(current_setting('lc_ctype')) || ');';
+END
+$$;
+CREATE COLLATION test3 (provider = icu, lc_collate = 'en_US.utf8'); -- fail, need lc_ctype
+ERROR: parameter "lc_ctype" must be specified
+CREATE COLLATION testx (provider = icu, locale = 'nonsense'); /* never fails with ICU */ DROP COLLATION testx;
+CREATE COLLATION test4 FROM nonsense;
+ERROR: collation "nonsense" for encoding "UTF8" does not exist
+CREATE COLLATION test5 FROM test0;
+SELECT collname FROM pg_collation WHERE collname LIKE 'test%' ORDER BY 1;
+ collname
+----------
+ test0
+ test1
+ test5
+(3 rows)
+
+ALTER COLLATION test1 RENAME TO test11;
+ALTER COLLATION test0 RENAME TO test11; -- fail
+ERROR: collation "test11" already exists in schema "collate_tests"
+ALTER COLLATION test1 RENAME TO test22; -- fail
+ERROR: collation "test1" for encoding "UTF8" does not exist
+ALTER COLLATION test11 OWNER TO regress_test_role;
+ALTER COLLATION test11 OWNER TO nonsense;
+ERROR: role "nonsense" does not exist
+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;
+ collname | nspname | obj_description
+----------+---------------+-----------------
+ test0 | collate_tests | US English
+ test11 | test_schema |
+ test5 | collate_tests |
+(3 rows)
+
+DROP COLLATION test0, test_schema.test11, test5;
+DROP COLLATION test0; -- fail
+ERROR: collation "test0" for encoding "UTF8" does not exist
+DROP COLLATION IF EXISTS test0;
+NOTICE: collation "test0" does not exist, skipping
+SELECT collname FROM pg_collation WHERE collname LIKE 'test%';
+ collname
+----------
+(0 rows)
+
+DROP SCHEMA test_schema;
+DROP ROLE regress_test_role;
+-- ALTER
+ALTER COLLATION "en-x-icu" REFRESH VERSION;
+NOTICE: version has not changed
+-- 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
+ERROR: cannot drop collation test0 because other objects depend on it
+DETAIL: column b of table collate_dep_test1 depends on collation test0
+type collate_dep_dom1 depends on collation test0
+column y of composite type collate_dep_test2 depends on collation test0
+view collate_dep_test3 depends on collation test0
+index collate_dep_test4i depends on collation test0
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP COLLATION test0 CASCADE;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to column b of table collate_dep_test1
+drop cascades to type collate_dep_dom1
+drop cascades to column y of composite type collate_dep_test2
+drop cascades to view collate_dep_test3
+drop cascades to index collate_dep_test4i
+\d collate_dep_test1
+ Table "collate_tests.collate_dep_test1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+
+\d collate_dep_test2
+ Composite type "collate_tests.collate_dep_test2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ x | integer | | |
+
+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;
+ ?column?
+----------
+ f
+(1 row)
+
+select textrange_en_us('A','Z') @> 'b'::text;
+ ?column?
+----------
+ t
+(1 row)
+
+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;
+ ?column? | ?column?
+----------+----------
+ t | t
+(1 row)
+
+CREATE COLLATION testcoll_backwards (provider = icu, locale = '@colBackwards=yes');
+SELECT 'coté' < 'côte' COLLATE "und-x-icu", 'coté' > 'côte' COLLATE testcoll_backwards;
+ ?column? | ?column?
+----------+----------
+ t | t
+(1 row)
+
+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;
+ ?column? | ?column?
+----------+----------
+ t | t
+(1 row)
+
+CREATE COLLATION testcoll_shifted (provider = icu, locale = '@colAlternate=shifted');
+SELECT 'de-luge' < 'deanza' COLLATE "und-x-icu", 'de-luge' > 'deanza' COLLATE testcoll_shifted;
+ ?column? | ?column?
+----------+----------
+ t | t
+(1 row)
+
+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;
+ ?column? | ?column?
+----------+----------
+ t | t
+(1 row)
+
+CREATE COLLATION testcoll_error1 (provider = icu, locale = '@colNumeric=lower');
+ERROR: could not open collator for locale "@colNumeric=lower": U_ILLEGAL_ARGUMENT_ERROR
+-- 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;
+ ?column? | ?column?
+----------+----------
+ t | t
+(1 row)
+
+-- nondeterministic collations
+CREATE COLLATION ctest_det (provider = icu, locale = '', deterministic = true);
+CREATE COLLATION ctest_nondet (provider = icu, locale = '', deterministic = false);
+CREATE TABLE test6 (a int, b text);
+-- same string in different normal forms
+INSERT INTO test6 VALUES (1, U&'\00E4bc');
+INSERT INTO test6 VALUES (2, U&'\0061\0308bc');
+SELECT * FROM test6;
+ a | b
+---+-----
+ 1 | äbc
+ 2 | äbc
+(2 rows)
+
+SELECT * FROM test6 WHERE b = 'äbc' COLLATE ctest_det;
+ a | b
+---+-----
+ 1 | äbc
+(1 row)
+
+SELECT * FROM test6 WHERE b = 'äbc' COLLATE ctest_nondet;
+ a | b
+---+-----
+ 1 | äbc
+ 2 | äbc
+(2 rows)
+
+-- same with arrays
+CREATE TABLE test6a (a int, b text[]);
+INSERT INTO test6a VALUES (1, ARRAY[U&'\00E4bc']);
+INSERT INTO test6a VALUES (2, ARRAY[U&'\0061\0308bc']);
+SELECT * FROM test6a;
+ a | b
+---+-------
+ 1 | {äbc}
+ 2 | {äbc}
+(2 rows)
+
+SELECT * FROM test6a WHERE b = ARRAY['äbc'] COLLATE ctest_det;
+ a | b
+---+-------
+ 1 | {äbc}
+(1 row)
+
+SELECT * FROM test6a WHERE b = ARRAY['äbc'] COLLATE ctest_nondet;
+ a | b
+---+-------
+ 1 | {äbc}
+ 2 | {äbc}
+(2 rows)
+
+CREATE COLLATION case_sensitive (provider = icu, locale = '');
+CREATE COLLATION case_insensitive (provider = icu, locale = '@colStrength=secondary', deterministic = false);
+SELECT 'abc' <= 'ABC' COLLATE case_sensitive, 'abc' >= 'ABC' COLLATE case_sensitive;
+ ?column? | ?column?
+----------+----------
+ t | f
+(1 row)
+
+SELECT 'abc' <= 'ABC' COLLATE case_insensitive, 'abc' >= 'ABC' COLLATE case_insensitive;
+ ?column? | ?column?
+----------+----------
+ t | t
+(1 row)
+
+CREATE TABLE test1cs (x text COLLATE case_sensitive);
+CREATE TABLE test2cs (x text COLLATE case_sensitive);
+CREATE TABLE test3cs (x text COLLATE case_sensitive);
+INSERT INTO test1cs VALUES ('abc'), ('def'), ('ghi');
+INSERT INTO test2cs VALUES ('ABC'), ('ghi');
+INSERT INTO test3cs VALUES ('abc'), ('ABC'), ('def'), ('ghi');
+SELECT x FROM test3cs WHERE x = 'abc';
+ x
+-----
+ abc
+(1 row)
+
+SELECT x FROM test3cs WHERE x <> 'abc';
+ x
+-----
+ ABC
+ def
+ ghi
+(3 rows)
+
+SELECT x FROM test3cs WHERE x LIKE 'a%';
+ x
+-----
+ abc
+(1 row)
+
+SELECT x FROM test3cs WHERE x ILIKE 'a%';
+ x
+-----
+ abc
+ ABC
+(2 rows)
+
+SELECT x FROM test3cs WHERE x SIMILAR TO 'a%';
+ x
+-----
+ abc
+(1 row)
+
+SELECT x FROM test3cs WHERE x ~ 'a';
+ x
+-----
+ abc
+(1 row)
+
+SELECT x FROM test1cs UNION SELECT x FROM test2cs ORDER BY x;
+ x
+-----
+ abc
+ ABC
+ def
+ ghi
+(4 rows)
+
+SELECT x FROM test2cs UNION SELECT x FROM test1cs ORDER BY x;
+ x
+-----
+ abc
+ ABC
+ def
+ ghi
+(4 rows)
+
+SELECT x FROM test1cs INTERSECT SELECT x FROM test2cs;
+ x
+-----
+ ghi
+(1 row)
+
+SELECT x FROM test2cs INTERSECT SELECT x FROM test1cs;
+ x
+-----
+ ghi
+(1 row)
+
+SELECT x FROM test1cs EXCEPT SELECT x FROM test2cs;
+ x
+-----
+ abc
+ def
+(2 rows)
+
+SELECT x FROM test2cs EXCEPT SELECT x FROM test1cs;
+ x
+-----
+ ABC
+(1 row)
+
+SELECT DISTINCT x FROM test3cs ORDER BY x;
+ x
+-----
+ abc
+ ABC
+ def
+ ghi
+(4 rows)
+
+SELECT count(DISTINCT x) FROM test3cs;
+ count
+-------
+ 4
+(1 row)
+
+SELECT x, count(*) FROM test3cs GROUP BY x ORDER BY x;
+ x | count
+-----+-------
+ abc | 1
+ ABC | 1
+ def | 1
+ ghi | 1
+(4 rows)
+
+SELECT x, row_number() OVER (ORDER BY x), rank() OVER (ORDER BY x) FROM test3cs ORDER BY x;
+ x | row_number | rank
+-----+------------+------
+ abc | 1 | 1
+ ABC | 2 | 2
+ def | 3 | 3
+ ghi | 4 | 4
+(4 rows)
+
+CREATE UNIQUE INDEX ON test1cs (x); -- ok
+INSERT INTO test1cs VALUES ('ABC'); -- ok
+CREATE UNIQUE INDEX ON test3cs (x); -- ok
+SELECT string_to_array('ABC,DEF,GHI' COLLATE case_sensitive, ',', 'abc');
+ string_to_array
+-----------------
+ {ABC,DEF,GHI}
+(1 row)
+
+SELECT string_to_array('ABCDEFGHI' COLLATE case_sensitive, NULL, 'b');
+ string_to_array
+---------------------
+ {A,B,C,D,E,F,G,H,I}
+(1 row)
+
+CREATE TABLE test1ci (x text COLLATE case_insensitive);
+CREATE TABLE test2ci (x text COLLATE case_insensitive);
+CREATE TABLE test3ci (x text COLLATE case_insensitive);
+CREATE INDEX ON test3ci (x text_pattern_ops); -- error
+ERROR: nondeterministic collations are not supported for operator class "text_pattern_ops"
+INSERT INTO test1ci VALUES ('abc'), ('def'), ('ghi');
+INSERT INTO test2ci VALUES ('ABC'), ('ghi');
+INSERT INTO test3ci VALUES ('abc'), ('ABC'), ('def'), ('ghi');
+SELECT x FROM test3ci WHERE x = 'abc';
+ x
+-----
+ abc
+ ABC
+(2 rows)
+
+SELECT x FROM test3ci WHERE x <> 'abc';
+ x
+-----
+ def
+ ghi
+(2 rows)
+
+SELECT x FROM test3ci WHERE x LIKE 'a%';
+ERROR: nondeterministic collations are not supported for LIKE
+SELECT x FROM test3ci WHERE x ILIKE 'a%';
+ERROR: nondeterministic collations are not supported for ILIKE
+SELECT x FROM test3ci WHERE x SIMILAR TO 'a%';
+ERROR: nondeterministic collations are not supported for regular expressions
+SELECT x FROM test3ci WHERE x ~ 'a';
+ERROR: nondeterministic collations are not supported for regular expressions
+SELECT x FROM test1ci UNION SELECT x FROM test2ci ORDER BY x;
+ x
+-----
+ abc
+ def
+ ghi
+(3 rows)
+
+SELECT x FROM test2ci UNION SELECT x FROM test1ci ORDER BY x;
+ x
+-----
+ ABC
+ def
+ ghi
+(3 rows)
+
+SELECT x FROM test1ci INTERSECT SELECT x FROM test2ci ORDER BY x;
+ x
+-----
+ abc
+ ghi
+(2 rows)
+
+SELECT x FROM test2ci INTERSECT SELECT x FROM test1ci ORDER BY x;
+ x
+-----
+ ABC
+ ghi
+(2 rows)
+
+SELECT x FROM test1ci EXCEPT SELECT x FROM test2ci;
+ x
+-----
+ def
+(1 row)
+
+SELECT x FROM test2ci EXCEPT SELECT x FROM test1ci;
+ x
+---
+(0 rows)
+
+SELECT DISTINCT x FROM test3ci ORDER BY x;
+ x
+-----
+ abc
+ def
+ ghi
+(3 rows)
+
+SELECT count(DISTINCT x) FROM test3ci;
+ count
+-------
+ 3
+(1 row)
+
+SELECT x, count(*) FROM test3ci GROUP BY x ORDER BY x;
+ x | count
+-----+-------
+ abc | 2
+ def | 1
+ ghi | 1
+(3 rows)
+
+SELECT x, row_number() OVER (ORDER BY x), rank() OVER (ORDER BY x) FROM test3ci ORDER BY x;
+ x | row_number | rank
+-----+------------+------
+ abc | 1 | 1
+ ABC | 2 | 1
+ def | 3 | 3
+ ghi | 4 | 4
+(4 rows)
+
+CREATE UNIQUE INDEX ON test1ci (x); -- ok
+INSERT INTO test1ci VALUES ('ABC'); -- error
+ERROR: duplicate key value violates unique constraint "test1ci_x_idx"
+DETAIL: Key (x)=(ABC) already exists.
+CREATE UNIQUE INDEX ON test3ci (x); -- error
+ERROR: could not create unique index "test3ci_x_idx"
+DETAIL: Key (x)=(abc) is duplicated.
+SELECT string_to_array('ABC,DEF,GHI' COLLATE case_insensitive, ',', 'abc');
+ERROR: nondeterministic collations are not supported for substring searches
+SELECT string_to_array('ABCDEFGHI' COLLATE case_insensitive, NULL, 'b');
+ string_to_array
+------------------------
+ {A,NULL,C,D,E,F,G,H,I}
+(1 row)
+
+-- bpchar
+CREATE TABLE test1bpci (x char(3) COLLATE case_insensitive);
+CREATE TABLE test2bpci (x char(3) COLLATE case_insensitive);
+CREATE TABLE test3bpci (x char(3) COLLATE case_insensitive);
+CREATE INDEX ON test3bpci (x bpchar_pattern_ops); -- error
+ERROR: nondeterministic collations are not supported for operator class "bpchar_pattern_ops"
+INSERT INTO test1bpci VALUES ('abc'), ('def'), ('ghi');
+INSERT INTO test2bpci VALUES ('ABC'), ('ghi');
+INSERT INTO test3bpci VALUES ('abc'), ('ABC'), ('def'), ('ghi');
+SELECT x FROM test3bpci WHERE x = 'abc';
+ x
+-----
+ abc
+ ABC
+(2 rows)
+
+SELECT x FROM test3bpci WHERE x <> 'abc';
+ x
+-----
+ def
+ ghi
+(2 rows)
+
+SELECT x FROM test3bpci WHERE x LIKE 'a%';
+ERROR: nondeterministic collations are not supported for LIKE
+SELECT x FROM test3bpci WHERE x ILIKE 'a%';
+ERROR: nondeterministic collations are not supported for ILIKE
+SELECT x FROM test3bpci WHERE x SIMILAR TO 'a%';
+ERROR: nondeterministic collations are not supported for regular expressions
+SELECT x FROM test3bpci WHERE x ~ 'a';
+ERROR: nondeterministic collations are not supported for regular expressions
+SELECT x FROM test1bpci UNION SELECT x FROM test2bpci ORDER BY x;
+ x
+-----
+ abc
+ def
+ ghi
+(3 rows)
+
+SELECT x FROM test2bpci UNION SELECT x FROM test1bpci ORDER BY x;
+ x
+-----
+ ABC
+ def
+ ghi
+(3 rows)
+
+SELECT x FROM test1bpci INTERSECT SELECT x FROM test2bpci ORDER BY x;
+ x
+-----
+ abc
+ ghi
+(2 rows)
+
+SELECT x FROM test2bpci INTERSECT SELECT x FROM test1bpci ORDER BY x;
+ x
+-----
+ ABC
+ ghi
+(2 rows)
+
+SELECT x FROM test1bpci EXCEPT SELECT x FROM test2bpci;
+ x
+-----
+ def
+(1 row)
+
+SELECT x FROM test2bpci EXCEPT SELECT x FROM test1bpci;
+ x
+---
+(0 rows)
+
+SELECT DISTINCT x FROM test3bpci ORDER BY x;
+ x
+-----
+ abc
+ def
+ ghi
+(3 rows)
+
+SELECT count(DISTINCT x) FROM test3bpci;
+ count
+-------
+ 3
+(1 row)
+
+SELECT x, count(*) FROM test3bpci GROUP BY x ORDER BY x;
+ x | count
+-----+-------
+ abc | 2
+ def | 1
+ ghi | 1
+(3 rows)
+
+SELECT x, row_number() OVER (ORDER BY x), rank() OVER (ORDER BY x) FROM test3bpci ORDER BY x;
+ x | row_number | rank
+-----+------------+------
+ abc | 1 | 1
+ ABC | 2 | 1
+ def | 3 | 3
+ ghi | 4 | 4
+(4 rows)
+
+CREATE UNIQUE INDEX ON test1bpci (x); -- ok
+INSERT INTO test1bpci VALUES ('ABC'); -- error
+ERROR: duplicate key value violates unique constraint "test1bpci_x_idx"
+DETAIL: Key (x)=(ABC) already exists.
+CREATE UNIQUE INDEX ON test3bpci (x); -- error
+ERROR: could not create unique index "test3bpci_x_idx"
+DETAIL: Key (x)=(abc) is duplicated.
+SELECT string_to_array('ABC,DEF,GHI'::char(11) COLLATE case_insensitive, ',', 'abc');
+ERROR: nondeterministic collations are not supported for substring searches
+SELECT string_to_array('ABCDEFGHI'::char(9) COLLATE case_insensitive, NULL, 'b');
+ string_to_array
+------------------------
+ {A,NULL,C,D,E,F,G,H,I}
+(1 row)
+
+-- This tests the issue described in match_pattern_prefix(). In the
+-- absence of that check, the case_insensitive tests below would
+-- return no rows where they should logically return one.
+CREATE TABLE test4c (x text COLLATE "C");
+INSERT INTO test4c VALUES ('abc');
+CREATE INDEX ON test4c (x);
+SET enable_seqscan = off;
+SELECT x FROM test4c WHERE x LIKE 'ABC' COLLATE case_sensitive; -- ok, no rows
+ x
+---
+(0 rows)
+
+SELECT x FROM test4c WHERE x LIKE 'ABC%' COLLATE case_sensitive; -- ok, no rows
+ x
+---
+(0 rows)
+
+SELECT x FROM test4c WHERE x LIKE 'ABC' COLLATE case_insensitive; -- error
+ERROR: nondeterministic collations are not supported for LIKE
+SELECT x FROM test4c WHERE x LIKE 'ABC%' COLLATE case_insensitive; -- error
+ERROR: nondeterministic collations are not supported for LIKE
+RESET enable_seqscan;
+-- Unicode special case: different variants of Greek lower case sigma.
+-- A naive implementation like citext that just does lower(x) =
+-- lower(y) will do the wrong thing here, because lower('Σ') is 'σ'
+-- but upper('ς') is 'Σ'.
+SELECT 'ὀδυσσεÏÏ‚' = 'ὈΔΥΣΣΕΎΣ' COLLATE case_sensitive;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT 'ὀδυσσεÏÏ‚' = 'ὈΔΥΣΣΕΎΣ' COLLATE case_insensitive;
+ ?column?
+----------
+ t
+(1 row)
+
+-- name vs. text comparison operators
+SELECT relname FROM pg_class WHERE relname = 'PG_CLASS'::text COLLATE case_insensitive;
+ relname
+----------
+ pg_class
+(1 row)
+
+SELECT relname FROM pg_class WHERE 'PG_CLASS'::text = relname COLLATE case_insensitive;
+ relname
+----------
+ pg_class
+(1 row)
+
+SELECT typname FROM pg_type WHERE typname LIKE 'int_' AND typname <> 'INT2'::text
+ COLLATE case_insensitive ORDER BY typname;
+ typname
+---------
+ int4
+ int8
+(2 rows)
+
+SELECT typname FROM pg_type WHERE typname LIKE 'int_' AND 'INT2'::text <> typname
+ COLLATE case_insensitive ORDER BY typname;
+ typname
+---------
+ int4
+ int8
+(2 rows)
+
+-- test case adapted from subselect.sql
+CREATE TEMP TABLE outer_text (f1 text COLLATE case_insensitive, f2 text);
+INSERT INTO outer_text VALUES ('a', 'a');
+INSERT INTO outer_text VALUES ('b', 'a');
+INSERT INTO outer_text VALUES ('A', NULL);
+INSERT INTO outer_text VALUES ('B', NULL);
+CREATE TEMP TABLE inner_text (c1 text COLLATE case_insensitive, c2 text);
+INSERT INTO inner_text VALUES ('a', NULL);
+SELECT * FROM outer_text WHERE (f1, f2) NOT IN (SELECT * FROM inner_text);
+ f1 | f2
+----+----
+ b | a
+ B |
+(2 rows)
+
+-- accents
+CREATE COLLATION ignore_accents (provider = icu, locale = '@colStrength=primary;colCaseLevel=yes', deterministic = false);
+CREATE TABLE test4 (a int, b text);
+INSERT INTO test4 VALUES (1, 'cote'), (2, 'côte'), (3, 'coté'), (4, 'côté');
+SELECT * FROM test4 WHERE b = 'cote';
+ a | b
+---+------
+ 1 | cote
+(1 row)
+
+SELECT * FROM test4 WHERE b = 'cote' COLLATE ignore_accents;
+ a | b
+---+------
+ 1 | cote
+ 2 | côte
+ 3 | coté
+ 4 | côté
+(4 rows)
+
+SELECT * FROM test4 WHERE b = 'Cote' COLLATE ignore_accents; -- still case-sensitive
+ a | b
+---+---
+(0 rows)
+
+SELECT * FROM test4 WHERE b = 'Cote' COLLATE case_insensitive;
+ a | b
+---+------
+ 1 | cote
+(1 row)
+
+-- foreign keys (should use collation of primary key)
+-- PK is case-sensitive, FK is case-insensitive
+CREATE TABLE test10pk (x text COLLATE case_sensitive PRIMARY KEY);
+INSERT INTO test10pk VALUES ('abc'), ('def'), ('ghi');
+CREATE TABLE test10fk (x text COLLATE case_insensitive REFERENCES test10pk (x) ON UPDATE CASCADE ON DELETE CASCADE);
+INSERT INTO test10fk VALUES ('abc'); -- ok
+INSERT INTO test10fk VALUES ('ABC'); -- error
+ERROR: insert or update on table "test10fk" violates foreign key constraint "test10fk_x_fkey"
+DETAIL: Key (x)=(ABC) is not present in table "test10pk".
+INSERT INTO test10fk VALUES ('xyz'); -- error
+ERROR: insert or update on table "test10fk" violates foreign key constraint "test10fk_x_fkey"
+DETAIL: Key (x)=(xyz) is not present in table "test10pk".
+SELECT * FROM test10pk;
+ x
+-----
+ abc
+ def
+ ghi
+(3 rows)
+
+SELECT * FROM test10fk;
+ x
+-----
+ abc
+(1 row)
+
+-- restrict update even though the values are "equal" in the FK table
+UPDATE test10fk SET x = 'ABC' WHERE x = 'abc'; -- error
+ERROR: insert or update on table "test10fk" violates foreign key constraint "test10fk_x_fkey"
+DETAIL: Key (x)=(ABC) is not present in table "test10pk".
+SELECT * FROM test10fk;
+ x
+-----
+ abc
+(1 row)
+
+DELETE FROM test10pk WHERE x = 'abc';
+SELECT * FROM test10pk;
+ x
+-----
+ def
+ ghi
+(2 rows)
+
+SELECT * FROM test10fk;
+ x
+---
+(0 rows)
+
+-- PK is case-insensitive, FK is case-sensitive
+CREATE TABLE test11pk (x text COLLATE case_insensitive PRIMARY KEY);
+INSERT INTO test11pk VALUES ('abc'), ('def'), ('ghi');
+CREATE TABLE test11fk (x text COLLATE case_sensitive REFERENCES test11pk (x) ON UPDATE CASCADE ON DELETE CASCADE);
+INSERT INTO test11fk VALUES ('abc'); -- ok
+INSERT INTO test11fk VALUES ('ABC'); -- ok
+INSERT INTO test11fk VALUES ('xyz'); -- error
+ERROR: insert or update on table "test11fk" violates foreign key constraint "test11fk_x_fkey"
+DETAIL: Key (x)=(xyz) is not present in table "test11pk".
+SELECT * FROM test11pk;
+ x
+-----
+ abc
+ def
+ ghi
+(3 rows)
+
+SELECT * FROM test11fk;
+ x
+-----
+ abc
+ ABC
+(2 rows)
+
+-- cascade update even though the values are "equal" in the PK table
+UPDATE test11pk SET x = 'ABC' WHERE x = 'abc';
+SELECT * FROM test11fk;
+ x
+-----
+ ABC
+ ABC
+(2 rows)
+
+DELETE FROM test11pk WHERE x = 'abc';
+SELECT * FROM test11pk;
+ x
+-----
+ def
+ ghi
+(2 rows)
+
+SELECT * FROM test11fk;
+ x
+---
+(0 rows)
+
+-- partitioning
+CREATE TABLE test20 (a int, b text COLLATE case_insensitive) PARTITION BY LIST (b);
+CREATE TABLE test20_1 PARTITION OF test20 FOR VALUES IN ('abc');
+INSERT INTO test20 VALUES (1, 'abc');
+INSERT INTO test20 VALUES (2, 'ABC');
+SELECT * FROM test20_1;
+ a | b
+---+-----
+ 1 | abc
+ 2 | ABC
+(2 rows)
+
+CREATE TABLE test21 (a int, b text COLLATE case_insensitive) PARTITION BY RANGE (b);
+CREATE TABLE test21_1 PARTITION OF test21 FOR VALUES FROM ('ABC') TO ('DEF');
+INSERT INTO test21 VALUES (1, 'abc');
+INSERT INTO test21 VALUES (2, 'ABC');
+SELECT * FROM test21_1;
+ a | b
+---+-----
+ 1 | abc
+ 2 | ABC
+(2 rows)
+
+CREATE TABLE test22 (a int, b text COLLATE case_sensitive) PARTITION BY HASH (b);
+CREATE TABLE test22_0 PARTITION OF test22 FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE test22_1 PARTITION OF test22 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO test22 VALUES (1, 'def');
+INSERT INTO test22 VALUES (2, 'DEF');
+-- they end up in different partitions
+SELECT (SELECT count(*) FROM test22_0) = (SELECT count(*) FROM test22_1);
+ ?column?
+----------
+ t
+(1 row)
+
+-- same with arrays
+CREATE TABLE test22a (a int, b text[] COLLATE case_sensitive) PARTITION BY HASH (b);
+CREATE TABLE test22a_0 PARTITION OF test22a FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE test22a_1 PARTITION OF test22a FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO test22a VALUES (1, ARRAY['def']);
+INSERT INTO test22a VALUES (2, ARRAY['DEF']);
+-- they end up in different partitions
+SELECT (SELECT count(*) FROM test22a_0) = (SELECT count(*) FROM test22a_1);
+ ?column?
+----------
+ t
+(1 row)
+
+CREATE TABLE test23 (a int, b text COLLATE case_insensitive) PARTITION BY HASH (b);
+CREATE TABLE test23_0 PARTITION OF test23 FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE test23_1 PARTITION OF test23 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO test23 VALUES (1, 'def');
+INSERT INTO test23 VALUES (2, 'DEF');
+-- they end up in the same partition (but it's platform-dependent which one)
+SELECT (SELECT count(*) FROM test23_0) <> (SELECT count(*) FROM test23_1);
+ ?column?
+----------
+ t
+(1 row)
+
+-- same with arrays
+CREATE TABLE test23a (a int, b text[] COLLATE case_insensitive) PARTITION BY HASH (b);
+CREATE TABLE test23a_0 PARTITION OF test23a FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE test23a_1 PARTITION OF test23a FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO test23a VALUES (1, ARRAY['def']);
+INSERT INTO test23a VALUES (2, ARRAY['DEF']);
+-- they end up in the same partition (but it's platform-dependent which one)
+SELECT (SELECT count(*) FROM test23a_0) <> (SELECT count(*) FROM test23a_1);
+ ?column?
+----------
+ t
+(1 row)
+
+CREATE TABLE test30 (a int, b char(3) COLLATE case_insensitive) PARTITION BY LIST (b);
+CREATE TABLE test30_1 PARTITION OF test30 FOR VALUES IN ('abc');
+INSERT INTO test30 VALUES (1, 'abc');
+INSERT INTO test30 VALUES (2, 'ABC');
+SELECT * FROM test30_1;
+ a | b
+---+-----
+ 1 | abc
+ 2 | ABC
+(2 rows)
+
+CREATE TABLE test31 (a int, b char(3) COLLATE case_insensitive) PARTITION BY RANGE (b);
+CREATE TABLE test31_1 PARTITION OF test31 FOR VALUES FROM ('ABC') TO ('DEF');
+INSERT INTO test31 VALUES (1, 'abc');
+INSERT INTO test31 VALUES (2, 'ABC');
+SELECT * FROM test31_1;
+ a | b
+---+-----
+ 1 | abc
+ 2 | ABC
+(2 rows)
+
+CREATE TABLE test32 (a int, b char(3) COLLATE case_sensitive) PARTITION BY HASH (b);
+CREATE TABLE test32_0 PARTITION OF test32 FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE test32_1 PARTITION OF test32 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO test32 VALUES (1, 'def');
+INSERT INTO test32 VALUES (2, 'DEF');
+-- they end up in different partitions
+SELECT (SELECT count(*) FROM test32_0) = (SELECT count(*) FROM test32_1);
+ ?column?
+----------
+ t
+(1 row)
+
+CREATE TABLE test33 (a int, b char(3) COLLATE case_insensitive) PARTITION BY HASH (b);
+CREATE TABLE test33_0 PARTITION OF test33 FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE test33_1 PARTITION OF test33 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO test33 VALUES (1, 'def');
+INSERT INTO test33 VALUES (2, 'DEF');
+-- they end up in the same partition (but it's platform-dependent which one)
+SELECT (SELECT count(*) FROM test33_0) <> (SELECT count(*) FROM test33_1);
+ ?column?
+----------
+ t
+(1 row)
+
+-- 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/expected/collate.icu.utf8_1.out b/src/test/regress/expected/collate.icu.utf8_1.out
new file mode 100644
index 0000000..a6a33b3
--- /dev/null
+++ b/src/test/regress/expected/collate.icu.utf8_1.out
@@ -0,0 +1,9 @@
+/*
+ * 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
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
new file mode 100644
index 0000000..f06ae54
--- /dev/null
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -0,0 +1,1160 @@
+/*
+ * This test is for Linux/glibc systems and assumes that a full set of
+ * locales is installed. It must be run in a database with UTF-8 encoding,
+ * because other encodings don't support all the characters used.
+ */
+SELECT getdatabaseencoding() <> 'UTF8' OR
+ (SELECT count(*) FROM pg_collation WHERE collname IN ('de_DE', 'en_US', 'sv_SE', 'tr_TR') AND collencoding = pg_char_to_encoding('UTF8')) <> 4 OR
+ version() !~ 'linux-gnu'
+ 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_US" NOT NULL
+);
+\d collate_test1
+ Table "collate_tests.collate_test1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | text | en_US | not null |
+
+CREATE TABLE collate_test_fail (
+ a int,
+ b text COLLATE "ja_JP.eucjp"
+);
+ERROR: collation "ja_JP.eucjp" for encoding "UTF8" does not exist
+LINE 3: b text COLLATE "ja_JP.eucjp"
+ ^
+CREATE TABLE collate_test_fail (
+ a int,
+ b text COLLATE "foo"
+);
+ERROR: collation "foo" for encoding "UTF8" does not exist
+LINE 3: b text COLLATE "foo"
+ ^
+CREATE TABLE collate_test_fail (
+ a int COLLATE "en_US",
+ b text
+);
+ERROR: collations are not supported by type integer
+LINE 2: a int COLLATE "en_US",
+ ^
+CREATE TABLE collate_test_like (
+ LIKE collate_test1
+);
+\d collate_test_like
+ Table "collate_tests.collate_test_like"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | text | en_US | not null |
+
+CREATE TABLE collate_test2 (
+ a int,
+ b text COLLATE "sv_SE"
+);
+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';
+ a | b
+---+-----
+ 3 | bbc
+(1 row)
+
+SELECT * FROM collate_test2 WHERE b >= 'bbc';
+ a | b
+---+-----
+ 2 | äbc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test3 WHERE b >= 'bbc';
+ a | b
+---+-----
+ 2 | äbc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test3 WHERE b >= 'BBC';
+ a | b
+---+-----
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+(3 rows)
+
+SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc';
+ a | b
+---+-----
+ 2 | äbc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b >= 'bbc' COLLATE "C";
+ a | b
+---+-----
+ 2 | äbc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "C";
+ a | b
+---+-----
+ 2 | äbc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "en_US";
+ERROR: collation mismatch between explicit collations "C" and "en_US"
+LINE 1: ...* FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "e...
+ ^
+CREATE DOMAIN testdomain_sv AS text COLLATE "sv_SE";
+CREATE DOMAIN testdomain_i AS int COLLATE "sv_SE"; -- fails
+ERROR: collations are not supported by type integer
+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;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+CREATE TABLE collate_test5 (
+ a int,
+ b testdomain_sv COLLATE "en_US"
+);
+INSERT INTO collate_test5 SELECT * FROM collate_test1;
+SELECT a, b FROM collate_test5 ORDER BY b;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b FROM collate_test1 ORDER BY b;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b FROM collate_test2 ORDER BY b;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, b FROM collate_test3 ORDER BY b;
+ a | b
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
+ a | b
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+-- star expansion
+SELECT * FROM collate_test1 ORDER BY b;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT * FROM collate_test2 ORDER BY b;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT * FROM collate_test3 ORDER BY b;
+ a | b
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+-- constant expression folding
+SELECT 'bbc' COLLATE "en_US" > 'äbc' COLLATE "en_US" AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'bbc' COLLATE "sv_SE" > 'äbc' COLLATE "sv_SE" AS "false";
+ false
+-------
+ f
+(1 row)
+
+-- upper/lower
+CREATE TABLE collate_test10 (
+ a int,
+ x text COLLATE "en_US",
+ y text COLLATE "tr_TR"
+);
+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;
+ a | lower | lower | upper | upper | initcap | initcap
+---+-------+-------+-------+-------+---------+---------
+ 1 | hij | hij | HIJ | HÄ°J | Hij | Hij
+ 2 | hij | hıj | HIJ | HIJ | Hij | Hıj
+(2 rows)
+
+SELECT a, lower(x COLLATE "C"), lower(y COLLATE "C") FROM collate_test10;
+ a | lower | lower
+---+-------+-------
+ 1 | hij | hij
+ 2 | hij | hij
+(2 rows)
+
+SELECT a, x, y FROM collate_test10 ORDER BY lower(y), a;
+ a | x | y
+---+-----+-----
+ 2 | HIJ | HIJ
+ 1 | hij | hij
+(2 rows)
+
+-- LIKE/ILIKE
+SELECT * FROM collate_test1 WHERE b LIKE 'abc';
+ a | b
+---+-----
+ 1 | abc
+(1 row)
+
+SELECT * FROM collate_test1 WHERE b LIKE 'abc%';
+ a | b
+---+-----
+ 1 | abc
+(1 row)
+
+SELECT * FROM collate_test1 WHERE b LIKE '%bc%';
+ a | b
+---+-----
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+(3 rows)
+
+SELECT * FROM collate_test1 WHERE b ILIKE 'abc';
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b ILIKE 'abc%';
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b ILIKE '%bc%';
+ a | b
+---+-----
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+ 4 | ABC
+(4 rows)
+
+SELECT 'Türkiye' COLLATE "en_US" ILIKE '%KI%' AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'Türkiye' COLLATE "tr_TR" ILIKE '%KI%' AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'bıt' ILIKE 'BIT' COLLATE "en_US" AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'bıt' ILIKE 'BIT' COLLATE "tr_TR" AS "true";
+ true
+------
+ t
+(1 row)
+
+-- The following actually exercises the selectivity estimation for ILIKE.
+SELECT relname FROM pg_class WHERE relname ILIKE 'abc%';
+ relname
+---------
+(0 rows)
+
+-- regular expressions
+SELECT * FROM collate_test1 WHERE b ~ '^abc$';
+ a | b
+---+-----
+ 1 | abc
+(1 row)
+
+SELECT * FROM collate_test1 WHERE b ~ '^abc';
+ a | b
+---+-----
+ 1 | abc
+(1 row)
+
+SELECT * FROM collate_test1 WHERE b ~ 'bc';
+ a | b
+---+-----
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+(3 rows)
+
+SELECT * FROM collate_test1 WHERE b ~* '^abc$';
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b ~* '^abc';
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b ~* 'bc';
+ a | b
+---+-----
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+ 4 | ABC
+(4 rows)
+
+CREATE TABLE collate_test6 (
+ a int,
+ b text COLLATE "en_US"
+);
+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;
+ b | is_alpha | is_upper | is_lower | is_digit | is_alnum | is_graph | is_print | is_punct | is_space
+-----+----------+----------+----------+----------+----------+----------+----------+----------+----------
+ abc | t | f | t | f | t | t | t | f | f
+ ABC | t | t | f | f | t | t | t | f | f
+ 123 | f | f | f | t | t | t | t | f | f
+ ab1 | f | f | f | f | t | t | t | f | f
+ a1! | f | f | f | f | f | t | t | f | f
+ a c | f | f | f | f | f | f | t | f | f
+ !.; | f | f | f | f | f | t | t | t | f
+ | f | f | f | f | f | f | t | f | t
+ äbç | t | f | t | f | t | t | t | f | f
+ ÄBÇ | t | t | f | f | t | t | t | f | f
+(10 rows)
+
+SELECT 'Türkiye' COLLATE "en_US" ~* 'KI' AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'Türkiye' COLLATE "tr_TR" ~* 'KI' AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'bıt' ~* 'BIT' COLLATE "en_US" AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'bıt' ~* 'BIT' COLLATE "tr_TR" AS "true";
+ true
+------
+ t
+(1 row)
+
+-- The following actually exercises the selectivity estimation for ~*.
+SELECT relname FROM pg_class WHERE relname ~* '^abc';
+ relname
+---------
+(0 rows)
+
+-- to_char
+SET lc_time TO 'tr_TR';
+SELECT to_char(date '2010-02-01', 'DD TMMON YYYY');
+ to_char
+-------------
+ 01 ÅžUB 2010
+(1 row)
+
+SELECT to_char(date '2010-02-01', 'DD TMMON YYYY' COLLATE "tr_TR");
+ to_char
+-------------
+ 01 ÅžUB 2010
+(1 row)
+
+SELECT to_char(date '2010-04-01', 'DD TMMON YYYY');
+ to_char
+-------------
+ 01 NIS 2010
+(1 row)
+
+SELECT to_char(date '2010-04-01', 'DD TMMON YYYY' COLLATE "tr_TR");
+ to_char
+-------------
+ 01 NÄ°S 2010
+(1 row)
+
+-- to_date
+SELECT to_date('01 ÅžUB 2010', 'DD TMMON YYYY');
+ to_date
+------------
+ 02-01-2010
+(1 row)
+
+SELECT to_date('01 Åžub 2010', 'DD TMMON YYYY');
+ to_date
+------------
+ 02-01-2010
+(1 row)
+
+SELECT to_date('1234567890ab 2010', 'TMMONTH YYYY'); -- fail
+ERROR: invalid value "1234567890ab" for "MONTH"
+DETAIL: The given value did not match any of the allowed values for this field.
+-- 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;
+ table_name | view_definition
+------------+--------------------------------------------------------------------------
+ collview1 | SELECT collate_test1.a, +
+ | collate_test1.b +
+ | FROM collate_test1 +
+ | WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
+ collview2 | SELECT collate_test1.a, +
+ | collate_test1.b +
+ | FROM collate_test1 +
+ | ORDER BY (collate_test1.b COLLATE "C");
+ collview3 | SELECT collate_test10.a, +
+ | lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ | FROM collate_test10;
+(3 rows)
+
+-- collation propagation in various expression types
+SELECT a, coalesce(b, 'foo') FROM collate_test1 ORDER BY 2;
+ a | coalesce
+---+----------
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT a, coalesce(b, 'foo') FROM collate_test2 ORDER BY 2;
+ a | coalesce
+---+----------
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, coalesce(b, 'foo') FROM collate_test3 ORDER BY 2;
+ a | coalesce
+---+----------
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, lower(coalesce(x, 'foo')), lower(coalesce(y, 'foo')) FROM collate_test10;
+ a | lower | lower
+---+-------+-------
+ 1 | hij | hij
+ 2 | hij | hıj
+(2 rows)
+
+SELECT a, b, greatest(b, 'CCC') FROM collate_test1 ORDER BY 3;
+ a | b | greatest
+---+-----+----------
+ 1 | abc | CCC
+ 2 | äbc | CCC
+ 3 | bbc | CCC
+ 4 | ABC | CCC
+(4 rows)
+
+SELECT a, b, greatest(b, 'CCC') FROM collate_test2 ORDER BY 3;
+ a | b | greatest
+---+-----+----------
+ 1 | abc | CCC
+ 3 | bbc | CCC
+ 4 | ABC | CCC
+ 2 | äbc | äbc
+(4 rows)
+
+SELECT a, b, greatest(b, 'CCC') FROM collate_test3 ORDER BY 3;
+ a | b | greatest
+---+-----+----------
+ 4 | ABC | CCC
+ 1 | abc | abc
+ 3 | bbc | bbc
+ 2 | äbc | äbc
+(4 rows)
+
+SELECT a, x, y, lower(greatest(x, 'foo')), lower(greatest(y, 'foo')) FROM collate_test10;
+ a | x | y | lower | lower
+---+-----+-----+-------+-------
+ 1 | hij | hij | hij | hij
+ 2 | HIJ | HIJ | hij | hıj
+(2 rows)
+
+SELECT a, nullif(b, 'abc') FROM collate_test1 ORDER BY 2;
+ a | nullif
+---+--------
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+ 1 |
+(4 rows)
+
+SELECT a, nullif(b, 'abc') FROM collate_test2 ORDER BY 2;
+ a | nullif
+---+--------
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+ 1 |
+(4 rows)
+
+SELECT a, nullif(b, 'abc') FROM collate_test3 ORDER BY 2;
+ a | nullif
+---+--------
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+ 1 |
+(4 rows)
+
+SELECT a, lower(nullif(x, 'foo')), lower(nullif(y, 'foo')) FROM collate_test10;
+ a | lower | lower
+---+-------+-------
+ 1 | hij | hij
+ 2 | hij | hıj
+(2 rows)
+
+SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test1 ORDER BY 2;
+ a | b
+---+------
+ 4 | ABC
+ 2 | äbc
+ 1 | abcd
+ 3 | bbc
+(4 rows)
+
+SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test2 ORDER BY 2;
+ a | b
+---+------
+ 4 | ABC
+ 1 | abcd
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test3 ORDER BY 2;
+ a | b
+---+------
+ 4 | ABC
+ 1 | abcd
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+CREATE DOMAIN testdomain AS text;
+SELECT a, b::testdomain FROM collate_test1 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b::testdomain FROM collate_test2 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, b::testdomain FROM collate_test3 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, b::testdomain_sv FROM collate_test3 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, lower(x::testdomain), lower(y::testdomain) FROM collate_test10;
+ a | lower | lower
+---+-------+-------
+ 1 | hij | hij
+ 2 | hij | hıj
+(2 rows)
+
+SELECT min(b), max(b) FROM collate_test1;
+ min | max
+-----+-----
+ abc | bbc
+(1 row)
+
+SELECT min(b), max(b) FROM collate_test2;
+ min | max
+-----+-----
+ abc | äbc
+(1 row)
+
+SELECT min(b), max(b) FROM collate_test3;
+ min | max
+-----+-----
+ ABC | äbc
+(1 row)
+
+SELECT array_agg(b ORDER BY b) FROM collate_test1;
+ array_agg
+-------------------
+ {abc,ABC,äbc,bbc}
+(1 row)
+
+SELECT array_agg(b ORDER BY b) FROM collate_test2;
+ array_agg
+-------------------
+ {abc,ABC,bbc,äbc}
+(1 row)
+
+SELECT array_agg(b ORDER BY b) FROM collate_test3;
+ array_agg
+-------------------
+ {ABC,abc,bbc,äbc}
+(1 row)
+
+SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test1 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 1 | abc
+ 4 | ABC
+ 4 | ABC
+ 2 | äbc
+ 2 | äbc
+ 3 | bbc
+ 3 | bbc
+(8 rows)
+
+SELECT a, b FROM collate_test2 UNION SELECT a, b FROM collate_test2 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, b FROM collate_test3 WHERE a < 4 INTERSECT SELECT a, b FROM collate_test3 WHERE a > 1 ORDER BY 2;
+ a | b
+---+-----
+ 3 | bbc
+ 2 | äbc
+(2 rows)
+
+SELECT a, b FROM collate_test3 EXCEPT SELECT a, b FROM collate_test3 WHERE a < 2 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(3 rows)
+
+SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3 ORDER BY 2; -- fail
+ERROR: could not determine which collation to use for string comparison
+HINT: Use the COLLATE clause to set the collation explicitly.
+SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3; -- ok
+ a | b
+---+-----
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+ 4 | ABC
+ 1 | abc
+ 2 | äbc
+ 3 | bbc
+ 4 | ABC
+(8 rows)
+
+SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collate_test3 ORDER BY 2; -- fail
+ERROR: collation mismatch between implicit collations "en_US" and "C"
+LINE 1: SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collat...
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+SELECT a, b COLLATE "C" FROM collate_test1 UNION SELECT a, b FROM collate_test3 ORDER BY 2; -- ok
+ a | b
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail
+ERROR: collation mismatch between implicit collations "en_US" and "C"
+LINE 1: ...ELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM col...
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail
+ERROR: collation mismatch between implicit collations "en_US" and "C"
+LINE 1: SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM colla...
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+CREATE TABLE test_u AS SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3; -- fail
+ERROR: no collation was derived for column "b" with collatable type text
+HINT: Use the COLLATE clause to set the collation explicitly.
+-- ideally this would be a parse-time error, but for now it must be run-time:
+select x < y from collate_test10; -- fail
+ERROR: could not determine which collation to use for string comparison
+HINT: Use the COLLATE clause to set the collation explicitly.
+select x || y from collate_test10; -- ok, because || is not collation aware
+ ?column?
+----------
+ hijhij
+ HIJHIJ
+(2 rows)
+
+select x, y from collate_test10 order by x || y; -- not so ok
+ERROR: collation mismatch between implicit collations "en_US" and "tr_TR"
+LINE 1: select x, y from collate_test10 order by x || y;
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+-- collation mismatch between recursive and non-recursive term
+WITH RECURSIVE foo(x) AS
+ (SELECT x FROM (VALUES('a' COLLATE "en_US"),('b')) t(x)
+ UNION ALL
+ SELECT (x || 'c') COLLATE "de_DE" FROM foo WHERE length(x) < 10)
+SELECT * FROM foo;
+ERROR: recursive query "foo" column 1 has collation "en_US" in non-recursive term but collation "de_DE" overall
+LINE 2: (SELECT x FROM (VALUES('a' COLLATE "en_US"),('b')) t(x)
+ ^
+HINT: Use the COLLATE clause to set the collation of the non-recursive term.
+-- casting
+SELECT CAST('42' AS text COLLATE "C");
+ERROR: syntax error at or near "COLLATE"
+LINE 1: SELECT CAST('42' AS text COLLATE "C");
+ ^
+SELECT a, CAST(b AS varchar) FROM collate_test1 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT a, CAST(b AS varchar) FROM collate_test2 ORDER BY 2;
+ a | b
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, CAST(b AS varchar) FROM collate_test3 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+-- 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;
+ a | b | lt | mylt | mylt_noninline | mylt_plpgsql
+-----+-----+----+------+----------------+--------------
+ abc | abc | f | f | f | f
+ abc | ABC | t | t | t | t
+ abc | äbc | t | t | t | t
+ abc | bbc | t | t | t | t
+ ABC | abc | f | f | f | f
+ ABC | ABC | f | f | f | f
+ ABC | äbc | t | t | t | t
+ ABC | bbc | t | t | t | t
+ äbc | abc | f | f | f | f
+ äbc | ABC | f | f | f | f
+ äbc | äbc | f | f | f | f
+ äbc | bbc | t | t | t | t
+ bbc | abc | f | f | f | f
+ bbc | ABC | f | f | f | f
+ bbc | äbc | f | f | f | f
+ bbc | bbc | f | f | f | f
+(16 rows)
+
+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;
+ a | b | lt | mylt | mylt_noninline | mylt_plpgsql
+-----+-----+----+------+----------------+--------------
+ abc | abc | f | f | f | f
+ abc | ABC | f | f | f | f
+ abc | äbc | t | t | t | t
+ abc | bbc | t | t | t | t
+ ABC | abc | t | t | t | t
+ ABC | ABC | f | f | f | f
+ ABC | äbc | t | t | t | t
+ ABC | bbc | t | t | t | t
+ äbc | abc | f | f | f | f
+ äbc | ABC | f | f | f | f
+ äbc | äbc | f | f | f | f
+ äbc | bbc | f | f | f | f
+ bbc | abc | f | f | f | f
+ bbc | ABC | f | f | f | f
+ bbc | äbc | t | t | t | t
+ bbc | bbc | f | f | f | f
+(16 rows)
+
+-- 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_US") as t, mylt2('a', 'B' collate "C") as f;
+ t | f
+---+---
+ t | f
+(1 row)
+
+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;
+ f
+---
+ f
+(1 row)
+
+SELECT mylt2('a', 'B' collate "C") as fail; -- conflicting collations
+ERROR: could not determine which collation to use for string comparison
+HINT: Use the COLLATE clause to set the collation explicitly.
+CONTEXT: PL/pgSQL function mylt2(text,text) line 6 at RETURN
+SELECT mylt2('a', 'B' collate "POSIX") as f;
+ f
+---
+ f
+(1 row)
+
+-- polymorphism
+SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1;
+ unnest
+--------
+ abc
+ ABC
+ äbc
+ bbc
+(4 rows)
+
+SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test2)) ORDER BY 1;
+ unnest
+--------
+ abc
+ ABC
+ bbc
+ äbc
+(4 rows)
+
+SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test3)) ORDER BY 1;
+ unnest
+--------
+ ABC
+ abc
+ bbc
+ äbc
+(4 rows)
+
+CREATE FUNCTION dup (anyelement) RETURNS anyelement
+ AS 'select $1' LANGUAGE sql;
+SELECT a, dup(b) FROM collate_test1 ORDER BY 2;
+ a | dup
+---+-----
+ 1 | abc
+ 4 | ABC
+ 2 | äbc
+ 3 | bbc
+(4 rows)
+
+SELECT a, dup(b) FROM collate_test2 ORDER BY 2;
+ a | dup
+---+-----
+ 1 | abc
+ 4 | ABC
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+SELECT a, dup(b) FROM collate_test3 ORDER BY 2;
+ a | dup
+---+-----
+ 4 | ABC
+ 1 | abc
+ 3 | bbc
+ 2 | äbc
+(4 rows)
+
+-- 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
+ERROR: collations are not supported by type integer
+CREATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "C")); -- fail
+ERROR: collations are not supported by type integer
+LINE 1: ...ATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "C...
+ ^
+SELECT relname, pg_get_indexdef(oid) FROM pg_class WHERE relname LIKE 'collate_test%_idx%' ORDER BY 1;
+ relname | pg_get_indexdef
+--------------------+-------------------------------------------------------------------------------------------------------------------
+ collate_test1_idx1 | CREATE INDEX collate_test1_idx1 ON collate_tests.collate_test1 USING btree (b)
+ collate_test1_idx2 | CREATE INDEX collate_test1_idx2 ON collate_tests.collate_test1 USING btree (b COLLATE "C")
+ collate_test1_idx3 | CREATE INDEX collate_test1_idx3 ON collate_tests.collate_test1 USING btree (b COLLATE "C")
+ collate_test1_idx4 | CREATE INDEX collate_test1_idx4 ON collate_tests.collate_test1 USING btree (((b || 'foo'::text)) COLLATE "POSIX")
+(4 rows)
+
+-- 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 (locale = ' ||
+ quote_literal(current_setting('lc_collate')) || ');';
+END
+$$;
+CREATE COLLATION test0 FROM "C"; -- fail, duplicate name
+ERROR: collation "test0" already exists
+CREATE COLLATION IF NOT EXISTS test0 FROM "C"; -- ok, skipped
+NOTICE: collation "test0" already exists, skipping
+CREATE COLLATION IF NOT EXISTS test0 (locale = 'foo'); -- ok, skipped
+NOTICE: collation "test0" for encoding "UTF8" already exists, skipping
+do $$
+BEGIN
+ EXECUTE 'CREATE COLLATION test1 (lc_collate = ' ||
+ quote_literal(current_setting('lc_collate')) ||
+ ', lc_ctype = ' ||
+ quote_literal(current_setting('lc_ctype')) || ');';
+END
+$$;
+CREATE COLLATION test3 (lc_collate = 'en_US.utf8'); -- fail, need lc_ctype
+ERROR: parameter "lc_ctype" must be specified
+CREATE COLLATION testx (locale = 'nonsense'); -- fail
+ERROR: could not create locale "nonsense": No such file or directory
+DETAIL: The operating system could not find any locale data for the locale name "nonsense".
+CREATE COLLATION test4 FROM nonsense;
+ERROR: collation "nonsense" for encoding "UTF8" does not exist
+CREATE COLLATION test5 FROM test0;
+SELECT collname FROM pg_collation WHERE collname LIKE 'test%' ORDER BY 1;
+ collname
+----------
+ test0
+ test1
+ test5
+(3 rows)
+
+ALTER COLLATION test1 RENAME TO test11;
+ALTER COLLATION test0 RENAME TO test11; -- fail
+ERROR: collation "test11" for encoding "UTF8" already exists in schema "collate_tests"
+ALTER COLLATION test1 RENAME TO test22; -- fail
+ERROR: collation "test1" for encoding "UTF8" does not exist
+ALTER COLLATION test11 OWNER TO regress_test_role;
+ALTER COLLATION test11 OWNER TO nonsense;
+ERROR: role "nonsense" does not exist
+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;
+ collname | nspname | obj_description
+----------+---------------+-----------------
+ test0 | collate_tests | US English
+ test11 | test_schema |
+ test5 | collate_tests |
+(3 rows)
+
+DROP COLLATION test0, test_schema.test11, test5;
+DROP COLLATION test0; -- fail
+ERROR: collation "test0" for encoding "UTF8" does not exist
+DROP COLLATION IF EXISTS test0;
+NOTICE: collation "test0" does not exist, skipping
+SELECT collname FROM pg_collation WHERE collname LIKE 'test%';
+ collname
+----------
+(0 rows)
+
+DROP SCHEMA test_schema;
+DROP ROLE regress_test_role;
+-- ALTER
+ALTER COLLATION "en_US" REFRESH VERSION;
+NOTICE: version has not changed
+-- 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
+ERROR: cannot drop collation test0 because other objects depend on it
+DETAIL: column b of table collate_dep_test1 depends on collation test0
+type collate_dep_dom1 depends on collation test0
+column y of composite type collate_dep_test2 depends on collation test0
+view collate_dep_test3 depends on collation test0
+index collate_dep_test4i depends on collation test0
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP COLLATION test0 CASCADE;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to column b of table collate_dep_test1
+drop cascades to type collate_dep_dom1
+drop cascades to column y of composite type collate_dep_test2
+drop cascades to view collate_dep_test3
+drop cascades to index collate_dep_test4i
+\d collate_dep_test1
+ Table "collate_tests.collate_dep_test1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+
+\d collate_dep_test2
+ Composite type "collate_tests.collate_dep_test2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ x | integer | | |
+
+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_US");
+select textrange_c('A','Z') @> 'b'::text;
+ ?column?
+----------
+ f
+(1 row)
+
+select textrange_en_us('A','Z') @> 'b'::text;
+ ?column?
+----------
+ t
+(1 row)
+
+drop type textrange_c;
+drop type textrange_en_us;
+-- nondeterministic collations
+-- (not supported with libc provider)
+CREATE COLLATION ctest_det (locale = 'en_US.utf8', deterministic = true);
+CREATE COLLATION ctest_nondet (locale = 'en_US.utf8', deterministic = false);
+ERROR: nondeterministic collations not supported with this provider
+-- cleanup
+SET client_min_messages TO warning;
+DROP SCHEMA collate_tests CASCADE;
diff --git a/src/test/regress/expected/collate.linux.utf8_1.out b/src/test/regress/expected/collate.linux.utf8_1.out
new file mode 100644
index 0000000..ede5fdb
--- /dev/null
+++ b/src/test/regress/expected/collate.linux.utf8_1.out
@@ -0,0 +1,11 @@
+/*
+ * This test is for Linux/glibc systems and assumes that a full set of
+ * locales is installed. It must be run in a database with UTF-8 encoding,
+ * because other encodings don't support all the characters used.
+ */
+SELECT getdatabaseencoding() <> 'UTF8' OR
+ (SELECT count(*) FROM pg_collation WHERE collname IN ('de_DE', 'en_US', 'sv_SE', 'tr_TR') AND collencoding = pg_char_to_encoding('UTF8')) <> 4 OR
+ version() !~ 'linux-gnu'
+ AS skip_test \gset
+\if :skip_test
+\quit
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
new file mode 100644
index 0000000..0b60b55
--- /dev/null
+++ b/src/test/regress/expected/collate.out
@@ -0,0 +1,729 @@
+/*
+ * 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
+ Table "collate_tests.collate_test1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | text | C | not null |
+
+CREATE TABLE collate_test_fail (
+ a int COLLATE "C",
+ b text
+);
+ERROR: collations are not supported by type integer
+LINE 2: a int COLLATE "C",
+ ^
+CREATE TABLE collate_test_like (
+ LIKE collate_test1
+);
+\d collate_test_like
+ Table "collate_tests.collate_test_like"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | text | C | not null |
+
+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';
+ a | b
+---+-----
+ 1 | abc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b >= 'abc' COLLATE "C";
+ a | b
+---+-----
+ 1 | abc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'abc' COLLATE "C";
+ a | b
+---+-----
+ 1 | abc
+ 3 | bbc
+(2 rows)
+
+SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "POSIX"; -- fail
+ERROR: collation mismatch between explicit collations "C" and "POSIX"
+LINE 1: ...* FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "P...
+ ^
+CREATE DOMAIN testdomain_p AS text COLLATE "POSIX";
+CREATE DOMAIN testdomain_i AS int COLLATE "POSIX"; -- fail
+ERROR: collations are not supported by type integer
+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;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+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;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b FROM collate_test1 ORDER BY b;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b FROM collate_test2 ORDER BY b;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+-- star expansion
+SELECT * FROM collate_test1 ORDER BY b;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT * FROM collate_test2 ORDER BY b;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+-- constant expression folding
+SELECT 'bbc' COLLATE "C" > 'Abc' COLLATE "C" AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'bbc' COLLATE "POSIX" < 'Abc' COLLATE "POSIX" AS "false";
+ false
+-------
+ f
+(1 row)
+
+-- 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;
+ a | lower | lower | upper | upper | initcap | initcap
+---+-------+-------+-------+-------+---------+---------
+ 1 | hij | hij | HIJ | HIJ | Hij | Hij
+ 2 | hij | hij | HIJ | HIJ | Hij | Hij
+(2 rows)
+
+SELECT a, lower(x COLLATE "C"), lower(y COLLATE "C") FROM collate_test10;
+ a | lower | lower
+---+-------+-------
+ 1 | hij | hij
+ 2 | hij | hij
+(2 rows)
+
+SELECT a, x, y FROM collate_test10 ORDER BY lower(y), a;
+ a | x | y
+---+-----+-----
+ 1 | hij | hij
+ 2 | HIJ | HIJ
+(2 rows)
+
+-- 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;
+ table_name | view_definition
+------------+------------------------------------------------------------------------------
+ collview1 | SELECT collate_test1.a, +
+ | collate_test1.b +
+ | FROM collate_test1 +
+ | WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
+ collview2 | SELECT collate_test1.a, +
+ | collate_test1.b +
+ | FROM collate_test1 +
+ | ORDER BY (collate_test1.b COLLATE "C");
+ collview3 | SELECT collate_test10.a, +
+ | lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ | FROM collate_test10;
+(3 rows)
+
+-- collation propagation in various expression types
+SELECT a, coalesce(b, 'foo') FROM collate_test1 ORDER BY 2;
+ a | coalesce
+---+----------
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT a, coalesce(b, 'foo') FROM collate_test2 ORDER BY 2;
+ a | coalesce
+---+----------
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT a, lower(coalesce(x, 'foo')), lower(coalesce(y, 'foo')) FROM collate_test10;
+ a | lower | lower
+---+-------+-------
+ 1 | hij | hij
+ 2 | hij | hij
+(2 rows)
+
+SELECT a, b, greatest(b, 'CCC') FROM collate_test1 ORDER BY 3;
+ a | b | greatest
+---+-----+----------
+ 2 | Abc | CCC
+ 4 | ABD | CCC
+ 1 | abc | abc
+ 3 | bbc | bbc
+(4 rows)
+
+SELECT a, b, greatest(b, 'CCC') FROM collate_test2 ORDER BY 3;
+ a | b | greatest
+---+-----+----------
+ 2 | Abc | CCC
+ 4 | ABD | CCC
+ 1 | abc | abc
+ 3 | bbc | bbc
+(4 rows)
+
+SELECT a, x, y, lower(greatest(x, 'foo')), lower(greatest(y, 'foo')) FROM collate_test10;
+ a | x | y | lower | lower
+---+-----+-----+-------+-------
+ 1 | hij | hij | hij | hij
+ 2 | HIJ | HIJ | foo | foo
+(2 rows)
+
+SELECT a, nullif(b, 'abc') FROM collate_test1 ORDER BY 2;
+ a | nullif
+---+--------
+ 4 | ABD
+ 2 | Abc
+ 3 | bbc
+ 1 |
+(4 rows)
+
+SELECT a, nullif(b, 'abc') FROM collate_test2 ORDER BY 2;
+ a | nullif
+---+--------
+ 4 | ABD
+ 2 | Abc
+ 3 | bbc
+ 1 |
+(4 rows)
+
+SELECT a, lower(nullif(x, 'foo')), lower(nullif(y, 'foo')) FROM collate_test10;
+ a | lower | lower
+---+-------+-------
+ 1 | hij | hij
+ 2 | hij | hij
+(2 rows)
+
+SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test1 ORDER BY 2;
+ a | b
+---+------
+ 4 | ABD
+ 2 | Abc
+ 1 | abcd
+ 3 | bbc
+(4 rows)
+
+SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test2 ORDER BY 2;
+ a | b
+---+------
+ 4 | ABD
+ 2 | Abc
+ 1 | abcd
+ 3 | bbc
+(4 rows)
+
+CREATE DOMAIN testdomain AS text;
+SELECT a, b::testdomain FROM collate_test1 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b::testdomain FROM collate_test2 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b::testdomain_p FROM collate_test2 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT a, lower(x::testdomain), lower(y::testdomain) FROM collate_test10;
+ a | lower | lower
+---+-------+-------
+ 1 | hij | hij
+ 2 | hij | hij
+(2 rows)
+
+SELECT min(b), max(b) FROM collate_test1;
+ min | max
+-----+-----
+ ABD | bbc
+(1 row)
+
+SELECT min(b), max(b) FROM collate_test2;
+ min | max
+-----+-----
+ ABD | bbc
+(1 row)
+
+SELECT array_agg(b ORDER BY b) FROM collate_test1;
+ array_agg
+-------------------
+ {ABD,Abc,abc,bbc}
+(1 row)
+
+SELECT array_agg(b ORDER BY b) FROM collate_test2;
+ array_agg
+-------------------
+ {ABD,Abc,abc,bbc}
+(1 row)
+
+-- In aggregates, ORDER BY expressions don't affect aggregate's collation
+SELECT string_agg(x COLLATE "C", y COLLATE "POSIX") FROM collate_test10; -- fail
+ERROR: collation mismatch between explicit collations "C" and "POSIX"
+LINE 1: SELECT string_agg(x COLLATE "C", y COLLATE "POSIX") FROM col...
+ ^
+SELECT array_agg(x COLLATE "C" ORDER BY y COLLATE "POSIX") FROM collate_test10;
+ array_agg
+-----------
+ {HIJ,hij}
+(1 row)
+
+SELECT array_agg(a ORDER BY x COLLATE "C", y COLLATE "POSIX") FROM collate_test10;
+ array_agg
+-----------
+ {2,1}
+(1 row)
+
+SELECT array_agg(a ORDER BY x||y) FROM collate_test10; -- fail
+ERROR: collation mismatch between implicit collations "C" and "POSIX"
+LINE 1: SELECT array_agg(a ORDER BY x||y) FROM collate_test10;
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test1 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABD
+ 4 | ABD
+ 2 | Abc
+ 2 | Abc
+ 1 | abc
+ 1 | abc
+ 3 | bbc
+ 3 | bbc
+(8 rows)
+
+SELECT a, b FROM collate_test2 UNION SELECT a, b FROM collate_test2 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b FROM collate_test2 WHERE a < 4 INTERSECT SELECT a, b FROM collate_test2 WHERE a > 1 ORDER BY 2;
+ a | b
+---+-----
+ 2 | Abc
+ 3 | bbc
+(2 rows)
+
+SELECT a, b FROM collate_test2 EXCEPT SELECT a, b FROM collate_test2 WHERE a < 2 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 3 | bbc
+(3 rows)
+
+SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test2 ORDER BY 2; -- fail
+ERROR: could not determine which collation to use for string comparison
+HINT: Use the COLLATE clause to set the collation explicitly.
+SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test2; -- ok
+ a | b
+---+-----
+ 1 | abc
+ 2 | Abc
+ 3 | bbc
+ 4 | ABD
+ 1 | abc
+ 2 | Abc
+ 3 | bbc
+ 4 | ABD
+(8 rows)
+
+SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collate_test2 ORDER BY 2; -- fail
+ERROR: collation mismatch between implicit collations "C" and "POSIX"
+LINE 1: SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collat...
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+SELECT a, b COLLATE "C" FROM collate_test1 UNION SELECT a, b FROM collate_test2 ORDER BY 2; -- ok
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM collate_test2 ORDER BY 2; -- fail
+ERROR: collation mismatch between implicit collations "C" and "POSIX"
+LINE 1: ...ELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM col...
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM collate_test2 ORDER BY 2; -- fail
+ERROR: collation mismatch between implicit collations "C" and "POSIX"
+LINE 1: SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM colla...
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+CREATE TABLE test_u AS SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test2; -- fail
+ERROR: no collation was derived for column "b" with collatable type text
+HINT: Use the COLLATE clause to set the collation explicitly.
+-- ideally this would be a parse-time error, but for now it must be run-time:
+select x < y from collate_test10; -- fail
+ERROR: could not determine which collation to use for string comparison
+HINT: Use the COLLATE clause to set the collation explicitly.
+select x || y from collate_test10; -- ok, because || is not collation aware
+ ?column?
+----------
+ hijhij
+ HIJHIJ
+(2 rows)
+
+select x, y from collate_test10 order by x || y; -- not so ok
+ERROR: collation mismatch between implicit collations "C" and "POSIX"
+LINE 1: select x, y from collate_test10 order by x || y;
+ ^
+HINT: You can choose the collation by applying the COLLATE clause to one or both expressions.
+-- 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;
+ERROR: recursive query "foo" column 1 has collation "C" in non-recursive term but collation "POSIX" overall
+LINE 2: (SELECT x FROM (VALUES('a' COLLATE "C"),('b')) t(x)
+ ^
+HINT: Use the COLLATE clause to set the collation of the non-recursive term.
+SELECT a, b, a < b as lt FROM
+ (VALUES ('a', 'B'), ('A', 'b' COLLATE "C")) v(a,b);
+ a | b | lt
+---+---+----
+ a | B | f
+ A | b | t
+(2 rows)
+
+-- collation mismatch in subselects
+SELECT * FROM collate_test10 WHERE (x, y) NOT IN (SELECT y, x FROM collate_test10);
+ERROR: could not determine which collation to use for string hashing
+HINT: Use the COLLATE clause to set the collation explicitly.
+-- now it works with overrides
+SELECT * FROM collate_test10 WHERE (x COLLATE "POSIX", y COLLATE "C") NOT IN (SELECT y, x FROM collate_test10);
+ a | x | y
+---+---+---
+(0 rows)
+
+SELECT * FROM collate_test10 WHERE (x, y) NOT IN (SELECT y COLLATE "C", x COLLATE "POSIX" FROM collate_test10);
+ a | x | y
+---+---+---
+(0 rows)
+
+-- casting
+SELECT CAST('42' AS text COLLATE "C");
+ERROR: syntax error at or near "COLLATE"
+LINE 1: SELECT CAST('42' AS text COLLATE "C");
+ ^
+SELECT a, CAST(b AS varchar) FROM collate_test1 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT a, CAST(b AS varchar) FROM collate_test2 ORDER BY 2;
+ a | b
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+-- 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);
+ a | b
+---+-----
+ 1 | abc
+ 2 | Abc
+ 3 | bbc
+ 4 | ABD
+(4 rows)
+
+-- polymorphism
+SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1;
+ unnest
+--------
+ ABD
+ Abc
+ abc
+ bbc
+(4 rows)
+
+SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test2)) ORDER BY 1;
+ unnest
+--------
+ ABD
+ Abc
+ abc
+ bbc
+(4 rows)
+
+CREATE FUNCTION dup (anyelement) RETURNS anyelement
+ AS 'select $1' LANGUAGE sql;
+SELECT a, dup(b) FROM collate_test1 ORDER BY 2;
+ a | dup
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+SELECT a, dup(b) FROM collate_test2 ORDER BY 2;
+ a | dup
+---+-----
+ 4 | ABD
+ 2 | Abc
+ 1 | abc
+ 3 | bbc
+(4 rows)
+
+-- 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
+ERROR: collations are not supported by type integer
+CREATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "POSIX")); -- fail
+ERROR: collations are not supported by type integer
+LINE 1: ...ATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "P...
+ ^
+SELECT relname, pg_get_indexdef(oid) FROM pg_class WHERE relname LIKE 'collate_test%_idx%' ORDER BY 1;
+ relname | pg_get_indexdef
+--------------------+-------------------------------------------------------------------------------------------------------------------
+ collate_test1_idx1 | CREATE INDEX collate_test1_idx1 ON collate_tests.collate_test1 USING btree (b)
+ collate_test1_idx2 | CREATE INDEX collate_test1_idx2 ON collate_tests.collate_test1 USING btree (b COLLATE "POSIX")
+ collate_test1_idx3 | CREATE INDEX collate_test1_idx3 ON collate_tests.collate_test1 USING btree (b COLLATE "POSIX")
+ collate_test1_idx4 | CREATE INDEX collate_test1_idx4 ON collate_tests.collate_test1 USING btree (((b || 'foo'::text)) COLLATE "POSIX")
+(4 rows)
+
+-- 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
+ERROR: insert or update on table "collate_test21" violates foreign key constraint "collate_test21_f2_fkey"
+DETAIL: Key (f2)=(baz) is not present in table "collate_test20".
+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
+ERROR: insert or update on table "collate_test22" violates foreign key constraint "collate_test22_f2_fkey"
+DETAIL: Key (f2)=(baz) is not present in table "collate_test20".
+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;
+ QUERY PLAN
+----------------------------------------------
+ Sort
+ Sort Key: x COLLATE "C", y COLLATE "POSIX"
+ -> Seq Scan on collate_test10
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+ SELECT * FROM collate_test10 ORDER BY x DESC, y COLLATE "C" ASC NULLS FIRST;
+ QUERY PLAN
+-----------------------------------------------------------
+ Sort
+ Sort Key: x COLLATE "C" DESC, y COLLATE "C" NULLS FIRST
+ -> Seq Scan on collate_test10
+(3 rows)
+
+-- CREATE/DROP COLLATION
+CREATE COLLATION mycoll1 FROM "C";
+CREATE COLLATION mycoll2 ( LC_COLLATE = "POSIX", LC_CTYPE = "POSIX" );
+CREATE COLLATION mycoll3 FROM "default"; -- intentionally unsupported
+ERROR: collation "default" cannot be copied
+DROP COLLATION mycoll1;
+CREATE TABLE collate_test23 (f1 text collate mycoll2);
+DROP COLLATION mycoll2; -- fail
+ERROR: cannot drop collation mycoll2 because other objects depend on it
+DETAIL: column f1 of table collate_test23 depends on collation mycoll2
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- invalid: non-lowercase quoted identifiers
+CREATE COLLATION case_coll ("Lc_Collate" = "POSIX", "Lc_Ctype" = "POSIX");
+ERROR: collation attribute "Lc_Collate" not recognized
+LINE 1: CREATE COLLATION case_coll ("Lc_Collate" = "POSIX", "Lc_Ctyp...
+ ^
+-- 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
+ pg_collation_for
+------------------
+
+(1 row)
+
+SELECT collation for ('foo'::text);
+ pg_collation_for
+------------------
+ "default"
+(1 row)
+
+SELECT collation for ((SELECT a FROM collate_test1 LIMIT 1)); -- non-collatable type - error
+ERROR: collations are not supported by type integer
+SELECT collation for ((SELECT b FROM collate_test1 LIMIT 1));
+ pg_collation_for
+------------------
+ "C"
+(1 row)
+
+-- 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
+ View "collate_tests.collate_on_int"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+---------+-------------
+ c1p | integer | | | | plain |
+View definition:
+ SELECT ss.c1 + 1 AS c1p
+ FROM ( SELECT 4 AS c1) ss;
+
+--
+-- 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;
+NOTICE: drop cascades to 19 other objects
+DETAIL: drop cascades to table collate_test1
+drop cascades to table collate_test_like
+drop cascades to table collate_test2
+drop cascades to type testdomain_p
+drop cascades to table collate_test4
+drop cascades to table collate_test5
+drop cascades to table collate_test10
+drop cascades to view collview1
+drop cascades to view collview2
+drop cascades to view collview3
+drop cascades to type testdomain
+drop cascades to function vc(text)
+drop cascades to function dup(anyelement)
+drop cascades to table collate_test20
+drop cascades to table collate_test21
+drop cascades to table collate_test22
+drop cascades to collation mycoll2
+drop cascades to table collate_test23
+drop cascades to view collate_on_int
diff --git a/src/test/regress/expected/combocid.out b/src/test/regress/expected/combocid.out
new file mode 100644
index 0000000..2bf080b
--- /dev/null
+++ b/src/test/regress/expected/combocid.out
@@ -0,0 +1,169 @@
+--
+-- 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;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 10 | 1
+ (0,2) | 11 | 2
+(2 rows)
+
+SAVEPOINT s1;
+UPDATE combocidtest SET foobar = foobar + 10;
+-- here we should see only updated tuples
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,3) | 12 | 11
+ (0,4) | 12 | 12
+(2 rows)
+
+ROLLBACK TO s1;
+-- now we should see old tuples, but with combo CIDs starting at 0
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 0 | 1
+ (0,2) | 1 | 2
+(2 rows)
+
+COMMIT;
+-- combo data is not there anymore, but should still see tuples
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 0 | 1
+ (0,2) | 1 | 2
+(2 rows)
+
+-- 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;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 1 | 1
+ (0,2) | 1 | 2
+ (0,5) | 0 | 333
+(3 rows)
+
+ROLLBACK;
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 1 | 1
+ (0,2) | 1 | 2
+(2 rows)
+
+-- 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;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 1 | 1
+ (0,2) | 1 | 2
+ (0,6) | 10 | 444
+(3 rows)
+
+SAVEPOINT s1;
+-- this doesn't affect cmin
+SELECT ctid,cmin,* FROM combocidtest FOR UPDATE;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 1 | 1
+ (0,2) | 1 | 2
+ (0,6) | 10 | 444
+(3 rows)
+
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 1 | 1
+ (0,2) | 1 | 2
+ (0,6) | 10 | 444
+(3 rows)
+
+-- but this does
+UPDATE combocidtest SET foobar = foobar + 10;
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,7) | 12 | 11
+ (0,8) | 12 | 12
+ (0,9) | 12 | 454
+(3 rows)
+
+ROLLBACK TO s1;
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 12 | 1
+ (0,2) | 12 | 2
+ (0,6) | 0 | 444
+(3 rows)
+
+COMMIT;
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 12 | 1
+ (0,2) | 12 | 2
+ (0,6) | 0 | 444
+(3 rows)
+
+-- 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;
+ id | balance
+----+---------
+ 1 | 0
+(1 row)
+
+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;
+ id | balance
+----+---------
+ 1 | 400
+(1 row)
+
+ROLLBACK;
+DROP TABLE testcase;
diff --git a/src/test/regress/expected/comments.out b/src/test/regress/expected/comments.out
new file mode 100644
index 0000000..33f612e
--- /dev/null
+++ b/src/test/regress/expected/comments.out
@@ -0,0 +1,65 @@
+--
+-- COMMENTS
+--
+SELECT 'trailing' AS first; -- trailing single line
+ first
+----------
+ trailing
+(1 row)
+
+SELECT /* embedded single line */ 'embedded' AS second;
+ second
+----------
+ embedded
+(1 row)
+
+SELECT /* both embedded and trailing single line */ 'both' AS third; -- trailing single line
+ third
+-------
+ both
+(1 row)
+
+SELECT 'before multi-line' AS fourth;
+ fourth
+-------------------
+ before multi-line
+(1 row)
+
+/* This is an example of SQL which should not execute:
+ * select 'multi-line';
+ */
+SELECT 'after multi-line' AS fifth;
+ fifth
+------------------
+ after multi-line
+(1 row)
+
+--
+-- Nested comments
+--
+/*
+SELECT 'trailing' as x1; -- inside block comment
+*/
+/* This block comment surrounds a query which itself has a block comment...
+SELECT /* embedded single line */ 'embedded' AS x2;
+*/
+SELECT -- continued after the following block comments...
+/* Deeply nested comment.
+ This includes a single apostrophe to make sure we aren't decoding this part as a string.
+SELECT 'deep nest' AS n1;
+/* Second level of nesting...
+SELECT 'deeper nest' as n2;
+/* Third level of nesting...
+SELECT 'deepest nest' as n3;
+*/
+Hoo boy. Still two deep...
+*/
+Now just one deep...
+*/
+'deeply nested example' AS sixth;
+ sixth
+-----------------------
+ deeply nested example
+(1 row)
+
+/* and this is the end of the file */
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
new file mode 100644
index 0000000..4c997e2
--- /dev/null
+++ b/src/test/regress/expected/compression.out
@@ -0,0 +1,362 @@
+\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 TABLE cmdata(f1 text COMPRESSION pglz);
+CREATE INDEX idx ON cmdata(f1);
+INSERT INTO cmdata VALUES(repeat('1234567890', 1000));
+\d+ cmdata
+ Table "public.cmdata"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1 | text | | | | extended | pglz | |
+Indexes:
+ "idx" btree (f1)
+
+CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
+INSERT INTO cmdata1 VALUES(repeat('1234567890', 1004));
+\d+ cmdata1
+ Table "public.cmdata1"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1 | text | | | | extended | lz4 | |
+
+-- verify stored compression method in the data
+SELECT pg_column_compression(f1) FROM cmdata;
+ pg_column_compression
+-----------------------
+ pglz
+(1 row)
+
+SELECT pg_column_compression(f1) FROM cmdata1;
+ pg_column_compression
+-----------------------
+ lz4
+(1 row)
+
+-- decompress data slice
+SELECT SUBSTR(f1, 200, 5) FROM cmdata;
+ substr
+--------
+ 01234
+(1 row)
+
+SELECT SUBSTR(f1, 2000, 50) FROM cmdata1;
+ substr
+----------------------------------------------------
+ 01234567890123456789012345678901234567890123456789
+(1 row)
+
+-- copy with table creation
+SELECT * INTO cmmove1 FROM cmdata;
+\d+ cmmove1
+ Table "public.cmmove1"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1 | text | | | | extended | | |
+
+SELECT pg_column_compression(f1) FROM cmmove1;
+ pg_column_compression
+-----------------------
+ pglz
+(1 row)
+
+-- copy to existing table
+CREATE TABLE cmmove3(f1 text COMPRESSION pglz);
+INSERT INTO cmmove3 SELECT * FROM cmdata;
+INSERT INTO cmmove3 SELECT * FROM cmdata1;
+SELECT pg_column_compression(f1) FROM cmmove3;
+ pg_column_compression
+-----------------------
+ pglz
+ lz4
+(2 rows)
+
+-- test LIKE INCLUDING COMPRESSION
+CREATE TABLE cmdata2 (LIKE cmdata1 INCLUDING COMPRESSION);
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1 | text | | | | extended | lz4 | |
+
+DROP TABLE cmdata2;
+-- try setting compression for incompressible data type
+CREATE TABLE cmdata2 (f1 int COMPRESSION pglz);
+ERROR: column data type integer does not support compression
+-- update using datum from different table
+CREATE TABLE cmmove2(f1 text COMPRESSION pglz);
+INSERT INTO cmmove2 VALUES (repeat('1234567890', 1004));
+SELECT pg_column_compression(f1) FROM cmmove2;
+ pg_column_compression
+-----------------------
+ pglz
+(1 row)
+
+UPDATE cmmove2 SET f1 = cmdata1.f1 FROM cmdata1;
+SELECT pg_column_compression(f1) FROM cmmove2;
+ pg_column_compression
+-----------------------
+ lz4
+(1 row)
+
+-- 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 TABLE cmdata2 (f1 text COMPRESSION pglz);
+INSERT INTO cmdata2 SELECT large_val() || repeat('a', 4000);
+SELECT pg_column_compression(f1) FROM cmdata2;
+ pg_column_compression
+-----------------------
+ pglz
+(1 row)
+
+INSERT INTO cmdata1 SELECT large_val() || repeat('a', 4000);
+SELECT pg_column_compression(f1) FROM cmdata1;
+ pg_column_compression
+-----------------------
+ lz4
+ lz4
+(2 rows)
+
+SELECT SUBSTR(f1, 200, 5) FROM cmdata1;
+ substr
+--------
+ 01234
+ 8f14e
+(2 rows)
+
+SELECT SUBSTR(f1, 200, 5) FROM cmdata2;
+ substr
+--------
+ 8f14e
+(1 row)
+
+DROP TABLE cmdata2;
+--test column type update varlena/non-varlena
+CREATE TABLE cmdata2 (f1 int);
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ f1 | integer | | | | plain | | |
+
+ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1 | character varying | | | | extended | | |
+
+ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ f1 | integer | | | | plain | | |
+
+--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;
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1 | character varying | | | | extended | pglz | |
+
+ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
+ f1 | character varying | | | | plain | pglz | |
+
+INSERT INTO cmdata2 VALUES (repeat('123456789', 800));
+SELECT pg_column_compression(f1) FROM cmdata2;
+ pg_column_compression
+-----------------------
+
+(1 row)
+
+-- test compression with materialized view
+CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
+\d+ compressmv
+ Materialized view "public.compressmv"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ x | text | | | | extended | | |
+View definition:
+ SELECT cmdata1.f1 AS x
+ FROM cmdata1;
+
+SELECT pg_column_compression(f1) FROM cmdata1;
+ pg_column_compression
+-----------------------
+ lz4
+ lz4
+(2 rows)
+
+SELECT pg_column_compression(x) FROM compressmv;
+ pg_column_compression
+-----------------------
+ lz4
+ lz4
+(2 rows)
+
+-- test compression with partition
+CREATE TABLE cmpart(f1 text COMPRESSION lz4) PARTITION BY HASH(f1);
+CREATE TABLE cmpart1 PARTITION OF cmpart FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE cmpart2(f1 text COMPRESSION pglz);
+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;
+ pg_column_compression
+-----------------------
+ lz4
+(1 row)
+
+SELECT pg_column_compression(f1) FROM cmpart2;
+ pg_column_compression
+-----------------------
+ pglz
+(1 row)
+
+-- test compression with inheritance, error
+CREATE TABLE cminh() INHERITS(cmdata, cmdata1);
+NOTICE: merging multiple inherited definitions of column "f1"
+ERROR: column "f1" has a compression method conflict
+DETAIL: pglz versus lz4
+CREATE TABLE cminh(f1 TEXT COMPRESSION lz4) INHERITS(cmdata);
+NOTICE: merging column "f1" with inherited definition
+ERROR: column "f1" has a compression method conflict
+DETAIL: pglz versus lz4
+-- test default_toast_compression GUC
+SET default_toast_compression = '';
+ERROR: invalid value for parameter "default_toast_compression": ""
+HINT: Available values: pglz, lz4.
+SET default_toast_compression = 'I do not exist compression';
+ERROR: invalid value for parameter "default_toast_compression": "I do not exist compression"
+HINT: Available values: pglz, lz4.
+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));
+\d+ cmdata
+ Table "public.cmdata"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1 | text | | | | extended | lz4 | |
+Indexes:
+ "idx" btree (f1)
+
+SELECT pg_column_compression(f1) FROM cmdata;
+ pg_column_compression
+-----------------------
+ pglz
+ lz4
+(2 rows)
+
+ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default;
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
+ f1 | character varying | | | | plain | | |
+
+-- test alter compression method for materialized views
+ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
+\d+ compressmv
+ Materialized view "public.compressmv"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ x | text | | | | extended | lz4 | |
+View definition:
+ SELECT cmdata1.f1 AS x
+ FROM cmdata1;
+
+-- 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;
+ pg_column_compression
+-----------------------
+ lz4
+ pglz
+(2 rows)
+
+SELECT pg_column_compression(f1) FROM cmpart2;
+ pg_column_compression
+-----------------------
+ pglz
+ lz4
+(2 rows)
+
+-- VACUUM FULL does not recompress
+SELECT pg_column_compression(f1) FROM cmdata;
+ pg_column_compression
+-----------------------
+ pglz
+ lz4
+(2 rows)
+
+VACUUM FULL cmdata;
+SELECT pg_column_compression(f1) FROM cmdata;
+ pg_column_compression
+-----------------------
+ pglz
+ lz4
+(2 rows)
+
+-- test expression index
+DROP TABLE cmdata2;
+CREATE TABLE cmdata2 (f1 TEXT COMPRESSION pglz, f2 TEXT COMPRESSION lz4);
+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;
+ length
+--------
+ 10000
+ 36036
+(2 rows)
+
+SELECT length(f1) FROM cmdata1;
+ length
+--------
+ 10040
+ 12449
+(2 rows)
+
+SELECT length(f1) FROM cmmove1;
+ length
+--------
+ 10000
+(1 row)
+
+SELECT length(f1) FROM cmmove2;
+ length
+--------
+ 10040
+(1 row)
+
+SELECT length(f1) FROM cmmove3;
+ length
+--------
+ 10000
+ 10040
+(2 rows)
+
+CREATE TABLE badcompresstbl (a text COMPRESSION I_Do_Not_Exist_Compression); -- fails
+ERROR: invalid compression method "i_do_not_exist_compression"
+CREATE TABLE badcompresstbl (a text);
+ALTER TABLE badcompresstbl ALTER a SET COMPRESSION I_Do_Not_Exist_Compression; -- fails
+ERROR: invalid compression method "i_do_not_exist_compression"
+DROP TABLE badcompresstbl;
+\set HIDE_TOAST_COMPRESSION true
diff --git a/src/test/regress/expected/compression_1.out b/src/test/regress/expected/compression_1.out
new file mode 100644
index 0000000..1ce2962
--- /dev/null
+++ b/src/test/regress/expected/compression_1.out
@@ -0,0 +1,361 @@
+\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 TABLE cmdata(f1 text COMPRESSION pglz);
+CREATE INDEX idx ON cmdata(f1);
+INSERT INTO cmdata VALUES(repeat('1234567890', 1000));
+\d+ cmdata
+ Table "public.cmdata"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1 | text | | | | extended | pglz | |
+Indexes:
+ "idx" btree (f1)
+
+CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
+ERROR: compression method lz4 not supported
+DETAIL: This functionality requires the server to be built with lz4 support.
+HINT: You need to rebuild PostgreSQL using --with-lz4.
+INSERT INTO cmdata1 VALUES(repeat('1234567890', 1004));
+ERROR: relation "cmdata1" does not exist
+LINE 1: INSERT INTO cmdata1 VALUES(repeat('1234567890', 1004));
+ ^
+\d+ cmdata1
+-- verify stored compression method in the data
+SELECT pg_column_compression(f1) FROM cmdata;
+ pg_column_compression
+-----------------------
+ pglz
+(1 row)
+
+SELECT pg_column_compression(f1) FROM cmdata1;
+ERROR: relation "cmdata1" does not exist
+LINE 1: SELECT pg_column_compression(f1) FROM cmdata1;
+ ^
+-- decompress data slice
+SELECT SUBSTR(f1, 200, 5) FROM cmdata;
+ substr
+--------
+ 01234
+(1 row)
+
+SELECT SUBSTR(f1, 2000, 50) FROM cmdata1;
+ERROR: relation "cmdata1" does not exist
+LINE 1: SELECT SUBSTR(f1, 2000, 50) FROM cmdata1;
+ ^
+-- copy with table creation
+SELECT * INTO cmmove1 FROM cmdata;
+\d+ cmmove1
+ Table "public.cmmove1"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1 | text | | | | extended | | |
+
+SELECT pg_column_compression(f1) FROM cmmove1;
+ pg_column_compression
+-----------------------
+ pglz
+(1 row)
+
+-- copy to existing table
+CREATE TABLE cmmove3(f1 text COMPRESSION pglz);
+INSERT INTO cmmove3 SELECT * FROM cmdata;
+INSERT INTO cmmove3 SELECT * FROM cmdata1;
+ERROR: relation "cmdata1" does not exist
+LINE 1: INSERT INTO cmmove3 SELECT * FROM cmdata1;
+ ^
+SELECT pg_column_compression(f1) FROM cmmove3;
+ pg_column_compression
+-----------------------
+ pglz
+(1 row)
+
+-- test LIKE INCLUDING COMPRESSION
+CREATE TABLE cmdata2 (LIKE cmdata1 INCLUDING COMPRESSION);
+ERROR: relation "cmdata1" does not exist
+LINE 1: CREATE TABLE cmdata2 (LIKE cmdata1 INCLUDING COMPRESSION);
+ ^
+\d+ cmdata2
+DROP TABLE cmdata2;
+ERROR: table "cmdata2" does not exist
+-- try setting compression for incompressible data type
+CREATE TABLE cmdata2 (f1 int COMPRESSION pglz);
+ERROR: column data type integer does not support compression
+-- update using datum from different table
+CREATE TABLE cmmove2(f1 text COMPRESSION pglz);
+INSERT INTO cmmove2 VALUES (repeat('1234567890', 1004));
+SELECT pg_column_compression(f1) FROM cmmove2;
+ pg_column_compression
+-----------------------
+ pglz
+(1 row)
+
+UPDATE cmmove2 SET f1 = cmdata1.f1 FROM cmdata1;
+ERROR: relation "cmdata1" does not exist
+LINE 1: UPDATE cmmove2 SET f1 = cmdata1.f1 FROM cmdata1;
+ ^
+SELECT pg_column_compression(f1) FROM cmmove2;
+ pg_column_compression
+-----------------------
+ pglz
+(1 row)
+
+-- 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 TABLE cmdata2 (f1 text COMPRESSION pglz);
+INSERT INTO cmdata2 SELECT large_val() || repeat('a', 4000);
+SELECT pg_column_compression(f1) FROM cmdata2;
+ pg_column_compression
+-----------------------
+ pglz
+(1 row)
+
+INSERT INTO cmdata1 SELECT large_val() || repeat('a', 4000);
+ERROR: relation "cmdata1" does not exist
+LINE 1: INSERT INTO cmdata1 SELECT large_val() || repeat('a', 4000);
+ ^
+SELECT pg_column_compression(f1) FROM cmdata1;
+ERROR: relation "cmdata1" does not exist
+LINE 1: SELECT pg_column_compression(f1) FROM cmdata1;
+ ^
+SELECT SUBSTR(f1, 200, 5) FROM cmdata1;
+ERROR: relation "cmdata1" does not exist
+LINE 1: SELECT SUBSTR(f1, 200, 5) FROM cmdata1;
+ ^
+SELECT SUBSTR(f1, 200, 5) FROM cmdata2;
+ substr
+--------
+ 8f14e
+(1 row)
+
+DROP TABLE cmdata2;
+--test column type update varlena/non-varlena
+CREATE TABLE cmdata2 (f1 int);
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ f1 | integer | | | | plain | | |
+
+ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1 | character varying | | | | extended | | |
+
+ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ f1 | integer | | | | plain | | |
+
+--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;
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1 | character varying | | | | extended | pglz | |
+
+ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
+ f1 | character varying | | | | plain | pglz | |
+
+INSERT INTO cmdata2 VALUES (repeat('123456789', 800));
+SELECT pg_column_compression(f1) FROM cmdata2;
+ pg_column_compression
+-----------------------
+
+(1 row)
+
+-- test compression with materialized view
+CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
+ERROR: relation "cmdata1" does not exist
+LINE 1: ...TE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
+ ^
+\d+ compressmv
+SELECT pg_column_compression(f1) FROM cmdata1;
+ERROR: relation "cmdata1" does not exist
+LINE 1: SELECT pg_column_compression(f1) FROM cmdata1;
+ ^
+SELECT pg_column_compression(x) FROM compressmv;
+ERROR: relation "compressmv" does not exist
+LINE 1: SELECT pg_column_compression(x) FROM compressmv;
+ ^
+-- test compression with partition
+CREATE TABLE cmpart(f1 text COMPRESSION lz4) PARTITION BY HASH(f1);
+ERROR: compression method lz4 not supported
+DETAIL: This functionality requires the server to be built with lz4 support.
+HINT: You need to rebuild PostgreSQL using --with-lz4.
+CREATE TABLE cmpart1 PARTITION OF cmpart FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+ERROR: relation "cmpart" does not exist
+CREATE TABLE cmpart2(f1 text COMPRESSION pglz);
+ALTER TABLE cmpart ATTACH PARTITION cmpart2 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+ERROR: relation "cmpart" does not exist
+INSERT INTO cmpart VALUES (repeat('123456789', 1004));
+ERROR: relation "cmpart" does not exist
+LINE 1: INSERT INTO cmpart VALUES (repeat('123456789', 1004));
+ ^
+INSERT INTO cmpart VALUES (repeat('123456789', 4004));
+ERROR: relation "cmpart" does not exist
+LINE 1: INSERT INTO cmpart VALUES (repeat('123456789', 4004));
+ ^
+SELECT pg_column_compression(f1) FROM cmpart1;
+ERROR: relation "cmpart1" does not exist
+LINE 1: SELECT pg_column_compression(f1) FROM cmpart1;
+ ^
+SELECT pg_column_compression(f1) FROM cmpart2;
+ pg_column_compression
+-----------------------
+(0 rows)
+
+-- test compression with inheritance, error
+CREATE TABLE cminh() INHERITS(cmdata, cmdata1);
+ERROR: relation "cmdata1" does not exist
+CREATE TABLE cminh(f1 TEXT COMPRESSION lz4) INHERITS(cmdata);
+NOTICE: merging column "f1" with inherited definition
+ERROR: column "f1" has a compression method conflict
+DETAIL: pglz versus lz4
+-- test default_toast_compression GUC
+SET default_toast_compression = '';
+ERROR: invalid value for parameter "default_toast_compression": ""
+HINT: Available values: pglz.
+SET default_toast_compression = 'I do not exist compression';
+ERROR: invalid value for parameter "default_toast_compression": "I do not exist compression"
+HINT: Available values: pglz.
+SET default_toast_compression = 'lz4';
+ERROR: invalid value for parameter "default_toast_compression": "lz4"
+HINT: Available values: pglz.
+SET default_toast_compression = 'pglz';
+-- test alter compression method
+ALTER TABLE cmdata ALTER COLUMN f1 SET COMPRESSION lz4;
+ERROR: compression method lz4 not supported
+DETAIL: This functionality requires the server to be built with lz4 support.
+HINT: You need to rebuild PostgreSQL using --with-lz4.
+INSERT INTO cmdata VALUES (repeat('123456789', 4004));
+\d+ cmdata
+ Table "public.cmdata"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1 | text | | | | extended | pglz | |
+Indexes:
+ "idx" btree (f1)
+
+SELECT pg_column_compression(f1) FROM cmdata;
+ pg_column_compression
+-----------------------
+ pglz
+ pglz
+(2 rows)
+
+ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default;
+\d+ cmdata2
+ Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
+--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
+ f1 | character varying | | | | plain | | |
+
+-- test alter compression method for materialized views
+ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
+ERROR: relation "compressmv" does not exist
+\d+ compressmv
+-- test alter compression method for partitioned tables
+ALTER TABLE cmpart1 ALTER COLUMN f1 SET COMPRESSION pglz;
+ERROR: relation "cmpart1" does not exist
+ALTER TABLE cmpart2 ALTER COLUMN f1 SET COMPRESSION lz4;
+ERROR: compression method lz4 not supported
+DETAIL: This functionality requires the server to be built with lz4 support.
+HINT: You need to rebuild PostgreSQL using --with-lz4.
+-- new data should be compressed with the current compression method
+INSERT INTO cmpart VALUES (repeat('123456789', 1004));
+ERROR: relation "cmpart" does not exist
+LINE 1: INSERT INTO cmpart VALUES (repeat('123456789', 1004));
+ ^
+INSERT INTO cmpart VALUES (repeat('123456789', 4004));
+ERROR: relation "cmpart" does not exist
+LINE 1: INSERT INTO cmpart VALUES (repeat('123456789', 4004));
+ ^
+SELECT pg_column_compression(f1) FROM cmpart1;
+ERROR: relation "cmpart1" does not exist
+LINE 1: SELECT pg_column_compression(f1) FROM cmpart1;
+ ^
+SELECT pg_column_compression(f1) FROM cmpart2;
+ pg_column_compression
+-----------------------
+(0 rows)
+
+-- VACUUM FULL does not recompress
+SELECT pg_column_compression(f1) FROM cmdata;
+ pg_column_compression
+-----------------------
+ pglz
+ pglz
+(2 rows)
+
+VACUUM FULL cmdata;
+SELECT pg_column_compression(f1) FROM cmdata;
+ pg_column_compression
+-----------------------
+ pglz
+ pglz
+(2 rows)
+
+-- test expression index
+DROP TABLE cmdata2;
+CREATE TABLE cmdata2 (f1 TEXT COMPRESSION pglz, f2 TEXT COMPRESSION lz4);
+ERROR: compression method lz4 not supported
+DETAIL: This functionality requires the server to be built with lz4 support.
+HINT: You need to rebuild PostgreSQL using --with-lz4.
+CREATE UNIQUE INDEX idx1 ON cmdata2 ((f1 || f2));
+ERROR: relation "cmdata2" does not exist
+INSERT INTO cmdata2 VALUES((SELECT array_agg(md5(g::TEXT))::TEXT FROM
+generate_series(1, 50) g), VERSION());
+ERROR: relation "cmdata2" does not exist
+LINE 1: INSERT INTO cmdata2 VALUES((SELECT array_agg(md5(g::TEXT))::...
+ ^
+-- check data is ok
+SELECT length(f1) FROM cmdata;
+ length
+--------
+ 10000
+ 36036
+(2 rows)
+
+SELECT length(f1) FROM cmdata1;
+ERROR: relation "cmdata1" does not exist
+LINE 1: SELECT length(f1) FROM cmdata1;
+ ^
+SELECT length(f1) FROM cmmove1;
+ length
+--------
+ 10000
+(1 row)
+
+SELECT length(f1) FROM cmmove2;
+ length
+--------
+ 10040
+(1 row)
+
+SELECT length(f1) FROM cmmove3;
+ length
+--------
+ 10000
+(1 row)
+
+CREATE TABLE badcompresstbl (a text COMPRESSION I_Do_Not_Exist_Compression); -- fails
+ERROR: invalid compression method "i_do_not_exist_compression"
+CREATE TABLE badcompresstbl (a text);
+ALTER TABLE badcompresstbl ALTER a SET COMPRESSION I_Do_Not_Exist_Compression; -- fails
+ERROR: invalid compression method "i_do_not_exist_compression"
+DROP TABLE badcompresstbl;
+\set HIDE_TOAST_COMPRESSION true
diff --git a/src/test/regress/expected/conversion.out b/src/test/regress/expected/conversion.out
new file mode 100644
index 0000000..04fdcba
--- /dev/null
+++ b/src/test/regress/expected/conversion.out
@@ -0,0 +1,558 @@
+--
+-- create user defined conversion
+--
+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;
+--
+-- cannot make same name conversion in same schema
+--
+CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
+ERROR: conversion "myconv" already exists
+--
+-- create default conversion with qualified name
+--
+CREATE DEFAULT CONVERSION public.mydef FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
+--
+-- cannot make default conversion with same schema/for_encoding/to_encoding
+--
+CREATE DEFAULT CONVERSION public.mydef2 FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
+ERROR: default conversion for LATIN1 to UTF8 already exists
+-- test comments
+COMMENT ON CONVERSION myconv_bad IS 'foo';
+ERROR: conversion "myconv_bad" does not exist
+COMMENT ON CONVERSION myconv IS 'bar';
+COMMENT ON CONVERSION myconv IS NULL;
+--
+-- drop user defined conversion
+--
+DROP CONVERSION myconv;
+DROP CONVERSION mydef;
+--
+-- Note: the built-in conversions are exercised in opr_sanity.sql,
+-- so there's no need to do that here.
+--
+--
+-- return to the super user
+--
+RESET SESSION AUTHORIZATION;
+DROP USER regress_conversion_user;
+--
+-- Test built-in conversion functions.
+--
+-- Helper function to test a conversion. Uses the test_enc_conversion function
+-- that was created in the create_function_0 test.
+create or replace function test_conv(
+ input IN bytea,
+ src_encoding IN text,
+ dst_encoding IN text,
+ result OUT bytea,
+ errorat OUT bytea,
+ error OUT text)
+language plpgsql as
+$$
+declare
+ validlen int;
+begin
+ -- First try to perform the conversion with noError = false. If that errors out,
+ -- capture the error message, and try again with noError = true. The second call
+ -- should succeed and return the position of the error, return that too.
+ begin
+ select * into validlen, result from test_enc_conversion(input, src_encoding, dst_encoding, false);
+ errorat = NULL;
+ error := NULL;
+ exception when others then
+ error := sqlerrm;
+ select * into validlen, result from test_enc_conversion(input, src_encoding, dst_encoding, true);
+ errorat = substr(input, validlen + 1);
+ end;
+ return;
+end;
+$$;
+--
+-- UTF-8
+--
+CREATE TABLE utf8_inputs (inbytes bytea, description text);
+insert into utf8_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\xc3a4c3b6', 'valid, extra latin chars'),
+ ('\xd184d0bed0be', 'valid, cyrillic'),
+ ('\x666f6fe8b1a1', 'valid, kanji/Chinese'),
+ ('\xe382abe3829a', 'valid, two chars that combine to one in EUC_JIS_2004'),
+ ('\xe382ab', 'only first half of combined char in EUC_JIS_2004'),
+ ('\xe382abe382', 'incomplete combination when converted EUC_JIS_2004'),
+ ('\xecbd94eb81bceba6ac', 'valid, Hangul, Korean'),
+ ('\x666f6fefa8aa', 'valid, needs mapping function to convert to GB18030'),
+ ('\x66e8b1ff6f6f', 'invalid byte sequence'),
+ ('\x66006f', 'invalid, NUL byte'),
+ ('\x666f6fe8b100', 'invalid, NUL byte'),
+ ('\x666f6fe8b1', 'incomplete character at end');
+-- Test UTF-8 verification
+select description, (test_conv(inbytes, 'utf8', 'utf8')).* from utf8_inputs;
+ description | result | errorat | error
+------------------------------------------------------+----------------------+--------------+-----------------------------------------------------------
+ valid, pure ASCII | \x666f6f | |
+ valid, extra latin chars | \xc3a4c3b6 | |
+ valid, cyrillic | \xd184d0bed0be | |
+ valid, kanji/Chinese | \x666f6fe8b1a1 | |
+ valid, two chars that combine to one in EUC_JIS_2004 | \xe382abe3829a | |
+ only first half of combined char in EUC_JIS_2004 | \xe382ab | |
+ incomplete combination when converted EUC_JIS_2004 | \xe382ab | \xe382 | invalid byte sequence for encoding "UTF8": 0xe3 0x82
+ valid, Hangul, Korean | \xecbd94eb81bceba6ac | |
+ valid, needs mapping function to convert to GB18030 | \x666f6fefa8aa | |
+ invalid byte sequence | \x66 | \xe8b1ff6f6f | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0xff
+ invalid, NUL byte | \x66 | \x006f | invalid byte sequence for encoding "UTF8": 0x00
+ invalid, NUL byte | \x666f6f | \xe8b100 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0x00
+ incomplete character at end | \x666f6f | \xe8b1 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1
+(13 rows)
+
+-- Test conversions from UTF-8
+select description, inbytes, (test_conv(inbytes, 'utf8', 'euc_jis_2004')).* from utf8_inputs;
+ description | inbytes | result | errorat | error
+------------------------------------------------------+----------------------+----------------+----------------------+-------------------------------------------------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid, extra latin chars | \xc3a4c3b6 | \xa9daa9ec | |
+ valid, cyrillic | \xd184d0bed0be | \xa7e6a7e0a7e0 | |
+ valid, kanji/Chinese | \x666f6fe8b1a1 | \x666f6fbedd | |
+ valid, two chars that combine to one in EUC_JIS_2004 | \xe382abe3829a | \xa5f7 | |
+ only first half of combined char in EUC_JIS_2004 | \xe382ab | \xa5ab | |
+ incomplete combination when converted EUC_JIS_2004 | \xe382abe382 | \x | \xe382abe382 | invalid byte sequence for encoding "UTF8": 0xe3 0x82
+ valid, Hangul, Korean | \xecbd94eb81bceba6ac | \x | \xecbd94eb81bceba6ac | character with byte sequence 0xec 0xbd 0x94 in encoding "UTF8" has no equivalent in encoding "EUC_JIS_2004"
+ valid, needs mapping function to convert to GB18030 | \x666f6fefa8aa | \x666f6f | \xefa8aa | character with byte sequence 0xef 0xa8 0xaa in encoding "UTF8" has no equivalent in encoding "EUC_JIS_2004"
+ invalid byte sequence | \x66e8b1ff6f6f | \x66 | \xe8b1ff6f6f | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0xff
+ invalid, NUL byte | \x66006f | \x66 | \x006f | invalid byte sequence for encoding "UTF8": 0x00
+ invalid, NUL byte | \x666f6fe8b100 | \x666f6f | \xe8b100 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0x00
+ incomplete character at end | \x666f6fe8b1 | \x666f6f | \xe8b1 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1
+(13 rows)
+
+select description, inbytes, (test_conv(inbytes, 'utf8', 'latin1')).* from utf8_inputs;
+ description | inbytes | result | errorat | error
+------------------------------------------------------+----------------------+----------+----------------------+-------------------------------------------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid, extra latin chars | \xc3a4c3b6 | \xe4f6 | |
+ valid, cyrillic | \xd184d0bed0be | \x | \xd184d0bed0be | character with byte sequence 0xd1 0x84 in encoding "UTF8" has no equivalent in encoding "LATIN1"
+ valid, kanji/Chinese | \x666f6fe8b1a1 | \x666f6f | \xe8b1a1 | character with byte sequence 0xe8 0xb1 0xa1 in encoding "UTF8" has no equivalent in encoding "LATIN1"
+ valid, two chars that combine to one in EUC_JIS_2004 | \xe382abe3829a | \x | \xe382abe3829a | character with byte sequence 0xe3 0x82 0xab in encoding "UTF8" has no equivalent in encoding "LATIN1"
+ only first half of combined char in EUC_JIS_2004 | \xe382ab | \x | \xe382ab | character with byte sequence 0xe3 0x82 0xab in encoding "UTF8" has no equivalent in encoding "LATIN1"
+ incomplete combination when converted EUC_JIS_2004 | \xe382abe382 | \x | \xe382abe382 | character with byte sequence 0xe3 0x82 0xab in encoding "UTF8" has no equivalent in encoding "LATIN1"
+ valid, Hangul, Korean | \xecbd94eb81bceba6ac | \x | \xecbd94eb81bceba6ac | character with byte sequence 0xec 0xbd 0x94 in encoding "UTF8" has no equivalent in encoding "LATIN1"
+ valid, needs mapping function to convert to GB18030 | \x666f6fefa8aa | \x666f6f | \xefa8aa | character with byte sequence 0xef 0xa8 0xaa in encoding "UTF8" has no equivalent in encoding "LATIN1"
+ invalid byte sequence | \x66e8b1ff6f6f | \x66 | \xe8b1ff6f6f | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0xff
+ invalid, NUL byte | \x66006f | \x66 | \x006f | invalid byte sequence for encoding "UTF8": 0x00
+ invalid, NUL byte | \x666f6fe8b100 | \x666f6f | \xe8b100 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0x00
+ incomplete character at end | \x666f6fe8b1 | \x666f6f | \xe8b1 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1
+(13 rows)
+
+select description, inbytes, (test_conv(inbytes, 'utf8', 'latin2')).* from utf8_inputs;
+ description | inbytes | result | errorat | error
+------------------------------------------------------+----------------------+----------+----------------------+-------------------------------------------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid, extra latin chars | \xc3a4c3b6 | \xe4f6 | |
+ valid, cyrillic | \xd184d0bed0be | \x | \xd184d0bed0be | character with byte sequence 0xd1 0x84 in encoding "UTF8" has no equivalent in encoding "LATIN2"
+ valid, kanji/Chinese | \x666f6fe8b1a1 | \x666f6f | \xe8b1a1 | character with byte sequence 0xe8 0xb1 0xa1 in encoding "UTF8" has no equivalent in encoding "LATIN2"
+ valid, two chars that combine to one in EUC_JIS_2004 | \xe382abe3829a | \x | \xe382abe3829a | character with byte sequence 0xe3 0x82 0xab in encoding "UTF8" has no equivalent in encoding "LATIN2"
+ only first half of combined char in EUC_JIS_2004 | \xe382ab | \x | \xe382ab | character with byte sequence 0xe3 0x82 0xab in encoding "UTF8" has no equivalent in encoding "LATIN2"
+ incomplete combination when converted EUC_JIS_2004 | \xe382abe382 | \x | \xe382abe382 | character with byte sequence 0xe3 0x82 0xab in encoding "UTF8" has no equivalent in encoding "LATIN2"
+ valid, Hangul, Korean | \xecbd94eb81bceba6ac | \x | \xecbd94eb81bceba6ac | character with byte sequence 0xec 0xbd 0x94 in encoding "UTF8" has no equivalent in encoding "LATIN2"
+ valid, needs mapping function to convert to GB18030 | \x666f6fefa8aa | \x666f6f | \xefa8aa | character with byte sequence 0xef 0xa8 0xaa in encoding "UTF8" has no equivalent in encoding "LATIN2"
+ invalid byte sequence | \x66e8b1ff6f6f | \x66 | \xe8b1ff6f6f | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0xff
+ invalid, NUL byte | \x66006f | \x66 | \x006f | invalid byte sequence for encoding "UTF8": 0x00
+ invalid, NUL byte | \x666f6fe8b100 | \x666f6f | \xe8b100 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0x00
+ incomplete character at end | \x666f6fe8b1 | \x666f6f | \xe8b1 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1
+(13 rows)
+
+select description, inbytes, (test_conv(inbytes, 'utf8', 'latin5')).* from utf8_inputs;
+ description | inbytes | result | errorat | error
+------------------------------------------------------+----------------------+----------+----------------------+-------------------------------------------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid, extra latin chars | \xc3a4c3b6 | \xe4f6 | |
+ valid, cyrillic | \xd184d0bed0be | \x | \xd184d0bed0be | character with byte sequence 0xd1 0x84 in encoding "UTF8" has no equivalent in encoding "LATIN5"
+ valid, kanji/Chinese | \x666f6fe8b1a1 | \x666f6f | \xe8b1a1 | character with byte sequence 0xe8 0xb1 0xa1 in encoding "UTF8" has no equivalent in encoding "LATIN5"
+ valid, two chars that combine to one in EUC_JIS_2004 | \xe382abe3829a | \x | \xe382abe3829a | character with byte sequence 0xe3 0x82 0xab in encoding "UTF8" has no equivalent in encoding "LATIN5"
+ only first half of combined char in EUC_JIS_2004 | \xe382ab | \x | \xe382ab | character with byte sequence 0xe3 0x82 0xab in encoding "UTF8" has no equivalent in encoding "LATIN5"
+ incomplete combination when converted EUC_JIS_2004 | \xe382abe382 | \x | \xe382abe382 | character with byte sequence 0xe3 0x82 0xab in encoding "UTF8" has no equivalent in encoding "LATIN5"
+ valid, Hangul, Korean | \xecbd94eb81bceba6ac | \x | \xecbd94eb81bceba6ac | character with byte sequence 0xec 0xbd 0x94 in encoding "UTF8" has no equivalent in encoding "LATIN5"
+ valid, needs mapping function to convert to GB18030 | \x666f6fefa8aa | \x666f6f | \xefa8aa | character with byte sequence 0xef 0xa8 0xaa in encoding "UTF8" has no equivalent in encoding "LATIN5"
+ invalid byte sequence | \x66e8b1ff6f6f | \x66 | \xe8b1ff6f6f | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0xff
+ invalid, NUL byte | \x66006f | \x66 | \x006f | invalid byte sequence for encoding "UTF8": 0x00
+ invalid, NUL byte | \x666f6fe8b100 | \x666f6f | \xe8b100 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0x00
+ incomplete character at end | \x666f6fe8b1 | \x666f6f | \xe8b1 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1
+(13 rows)
+
+select description, inbytes, (test_conv(inbytes, 'utf8', 'koi8r')).* from utf8_inputs;
+ description | inbytes | result | errorat | error
+------------------------------------------------------+----------------------+----------+----------------------+------------------------------------------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid, extra latin chars | \xc3a4c3b6 | \x | \xc3a4c3b6 | character with byte sequence 0xc3 0xa4 in encoding "UTF8" has no equivalent in encoding "KOI8R"
+ valid, cyrillic | \xd184d0bed0be | \xc6cfcf | |
+ valid, kanji/Chinese | \x666f6fe8b1a1 | \x666f6f | \xe8b1a1 | character with byte sequence 0xe8 0xb1 0xa1 in encoding "UTF8" has no equivalent in encoding "KOI8R"
+ valid, two chars that combine to one in EUC_JIS_2004 | \xe382abe3829a | \x | \xe382abe3829a | character with byte sequence 0xe3 0x82 0xab in encoding "UTF8" has no equivalent in encoding "KOI8R"
+ only first half of combined char in EUC_JIS_2004 | \xe382ab | \x | \xe382ab | character with byte sequence 0xe3 0x82 0xab in encoding "UTF8" has no equivalent in encoding "KOI8R"
+ incomplete combination when converted EUC_JIS_2004 | \xe382abe382 | \x | \xe382abe382 | character with byte sequence 0xe3 0x82 0xab in encoding "UTF8" has no equivalent in encoding "KOI8R"
+ valid, Hangul, Korean | \xecbd94eb81bceba6ac | \x | \xecbd94eb81bceba6ac | character with byte sequence 0xec 0xbd 0x94 in encoding "UTF8" has no equivalent in encoding "KOI8R"
+ valid, needs mapping function to convert to GB18030 | \x666f6fefa8aa | \x666f6f | \xefa8aa | character with byte sequence 0xef 0xa8 0xaa in encoding "UTF8" has no equivalent in encoding "KOI8R"
+ invalid byte sequence | \x66e8b1ff6f6f | \x66 | \xe8b1ff6f6f | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0xff
+ invalid, NUL byte | \x66006f | \x66 | \x006f | invalid byte sequence for encoding "UTF8": 0x00
+ invalid, NUL byte | \x666f6fe8b100 | \x666f6f | \xe8b100 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0x00
+ incomplete character at end | \x666f6fe8b1 | \x666f6f | \xe8b1 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1
+(13 rows)
+
+select description, inbytes, (test_conv(inbytes, 'utf8', 'gb18030')).* from utf8_inputs;
+ description | inbytes | result | errorat | error
+------------------------------------------------------+----------------------+----------------------------+--------------+-----------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid, extra latin chars | \xc3a4c3b6 | \x81308a3181308b32 | |
+ valid, cyrillic | \xd184d0bed0be | \xa7e6a7e0a7e0 | |
+ valid, kanji/Chinese | \x666f6fe8b1a1 | \x666f6fcff3 | |
+ valid, two chars that combine to one in EUC_JIS_2004 | \xe382abe3829a | \xa5ab8139a732 | |
+ only first half of combined char in EUC_JIS_2004 | \xe382ab | \xa5ab | |
+ incomplete combination when converted EUC_JIS_2004 | \xe382abe382 | \xa5ab | \xe382 | invalid byte sequence for encoding "UTF8": 0xe3 0x82
+ valid, Hangul, Korean | \xecbd94eb81bceba6ac | \x8334e5398238c4338330b335 | |
+ valid, needs mapping function to convert to GB18030 | \x666f6fefa8aa | \x666f6f84309c38 | |
+ invalid byte sequence | \x66e8b1ff6f6f | \x66 | \xe8b1ff6f6f | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0xff
+ invalid, NUL byte | \x66006f | \x66 | \x006f | invalid byte sequence for encoding "UTF8": 0x00
+ invalid, NUL byte | \x666f6fe8b100 | \x666f6f | \xe8b100 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1 0x00
+ incomplete character at end | \x666f6fe8b1 | \x666f6f | \xe8b1 | invalid byte sequence for encoding "UTF8": 0xe8 0xb1
+(13 rows)
+
+--
+-- EUC_JIS_2004
+--
+CREATE TABLE euc_jis_2004_inputs (inbytes bytea, description text);
+insert into euc_jis_2004_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\x666f6fbedd', 'valid'),
+ ('\xa5f7', 'valid, translates to two UTF-8 chars '),
+ ('\xbeddbe', 'incomplete char '),
+ ('\x666f6f00bedd', 'invalid, NUL byte'),
+ ('\x666f6fbe00dd', 'invalid, NUL byte'),
+ ('\x666f6fbedd00', 'invalid, NUL byte'),
+ ('\xbe04', 'invalid byte sequence');
+-- Test EUC_JIS_2004 verification
+select description, inbytes, (test_conv(inbytes, 'euc_jis_2004', 'euc_jis_2004')).* from euc_jis_2004_inputs;
+ description | inbytes | result | errorat | error
+---------------------------------------+----------------+--------------+----------+--------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \x666f6fbedd | \x666f6fbedd | |
+ valid, translates to two UTF-8 chars | \xa5f7 | \xa5f7 | |
+ incomplete char | \xbeddbe | \xbedd | \xbe | invalid byte sequence for encoding "EUC_JIS_2004": 0xbe
+ invalid, NUL byte | \x666f6f00bedd | \x666f6f | \x00bedd | invalid byte sequence for encoding "EUC_JIS_2004": 0x00
+ invalid, NUL byte | \x666f6fbe00dd | \x666f6f | \xbe00dd | invalid byte sequence for encoding "EUC_JIS_2004": 0xbe 0x00
+ invalid, NUL byte | \x666f6fbedd00 | \x666f6fbedd | \x00 | invalid byte sequence for encoding "EUC_JIS_2004": 0x00
+ invalid byte sequence | \xbe04 | \x | \xbe04 | invalid byte sequence for encoding "EUC_JIS_2004": 0xbe 0x04
+(8 rows)
+
+-- Test conversions from EUC_JIS_2004
+select description, inbytes, (test_conv(inbytes, 'euc_jis_2004', 'utf8')).* from euc_jis_2004_inputs;
+ description | inbytes | result | errorat | error
+---------------------------------------+----------------+----------------+----------+--------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \x666f6fbedd | \x666f6fe8b1a1 | |
+ valid, translates to two UTF-8 chars | \xa5f7 | \xe382abe3829a | |
+ incomplete char | \xbeddbe | \xe8b1a1 | \xbe | invalid byte sequence for encoding "EUC_JIS_2004": 0xbe
+ invalid, NUL byte | \x666f6f00bedd | \x666f6f | \x00bedd | invalid byte sequence for encoding "EUC_JIS_2004": 0x00
+ invalid, NUL byte | \x666f6fbe00dd | \x666f6f | \xbe00dd | invalid byte sequence for encoding "EUC_JIS_2004": 0xbe 0x00
+ invalid, NUL byte | \x666f6fbedd00 | \x666f6fe8b1a1 | \x00 | invalid byte sequence for encoding "EUC_JIS_2004": 0x00
+ invalid byte sequence | \xbe04 | \x | \xbe04 | invalid byte sequence for encoding "EUC_JIS_2004": 0xbe 0x04
+(8 rows)
+
+--
+-- SHIFT-JIS-2004
+--
+CREATE TABLE shiftjis2004_inputs (inbytes bytea, description text);
+insert into shiftjis2004_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\x666f6f8fdb', 'valid'),
+ ('\x666f6f81c0', 'valid, no translation to UTF-8'),
+ ('\x666f6f82f5', 'valid, translates to two UTF-8 chars '),
+ ('\x666f6f8fdb8f', 'incomplete char '),
+ ('\x666f6f820a', 'incomplete char, followed by newline '),
+ ('\x666f6f008fdb', 'invalid, NUL byte'),
+ ('\x666f6f8f00db', 'invalid, NUL byte'),
+ ('\x666f6f8fdb00', 'invalid, NUL byte');
+-- Test SHIFT-JIS-2004 verification
+select description, inbytes, (test_conv(inbytes, 'shiftjis2004', 'shiftjis2004')).* from shiftjis2004_inputs;
+ description | inbytes | result | errorat | error
+---------------------------------------+----------------+--------------+----------+----------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \x666f6f8fdb | \x666f6f8fdb | |
+ valid, no translation to UTF-8 | \x666f6f81c0 | \x666f6f81c0 | |
+ valid, translates to two UTF-8 chars | \x666f6f82f5 | \x666f6f82f5 | |
+ incomplete char | \x666f6f8fdb8f | \x666f6f8fdb | \x8f | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x8f
+ incomplete char, followed by newline | \x666f6f820a | \x666f6f | \x820a | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x82 0x0a
+ invalid, NUL byte | \x666f6f008fdb | \x666f6f | \x008fdb | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x00
+ invalid, NUL byte | \x666f6f8f00db | \x666f6f | \x8f00db | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x8f 0x00
+ invalid, NUL byte | \x666f6f8fdb00 | \x666f6f8fdb | \x00 | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x00
+(9 rows)
+
+-- Test conversions from SHIFT-JIS-2004
+select description, inbytes, (test_conv(inbytes, 'shiftjis2004', 'utf8')).* from shiftjis2004_inputs;
+ description | inbytes | result | errorat | error
+---------------------------------------+----------------+----------------------+----------+----------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \x666f6f8fdb | \x666f6fe8b1a1 | |
+ valid, no translation to UTF-8 | \x666f6f81c0 | \x666f6fe28a84 | |
+ valid, translates to two UTF-8 chars | \x666f6f82f5 | \x666f6fe3818be3829a | |
+ incomplete char | \x666f6f8fdb8f | \x666f6fe8b1a1 | \x8f | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x8f
+ incomplete char, followed by newline | \x666f6f820a | \x666f6f | \x820a | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x82 0x0a
+ invalid, NUL byte | \x666f6f008fdb | \x666f6f | \x008fdb | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x00
+ invalid, NUL byte | \x666f6f8f00db | \x666f6f | \x8f00db | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x8f 0x00
+ invalid, NUL byte | \x666f6f8fdb00 | \x666f6fe8b1a1 | \x00 | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x00
+(9 rows)
+
+select description, inbytes, (test_conv(inbytes, 'shiftjis2004', 'euc_jis_2004')).* from shiftjis2004_inputs;
+ description | inbytes | result | errorat | error
+---------------------------------------+----------------+--------------+----------+----------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \x666f6f8fdb | \x666f6fbedd | |
+ valid, no translation to UTF-8 | \x666f6f81c0 | \x666f6fa2c2 | |
+ valid, translates to two UTF-8 chars | \x666f6f82f5 | \x666f6fa4f7 | |
+ incomplete char | \x666f6f8fdb8f | \x666f6fbedd | \x8f | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x8f
+ incomplete char, followed by newline | \x666f6f820a | \x666f6f | \x820a | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x82 0x0a
+ invalid, NUL byte | \x666f6f008fdb | \x666f6f | \x008fdb | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x00
+ invalid, NUL byte | \x666f6f8f00db | \x666f6f | \x8f00db | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x8f 0x00
+ invalid, NUL byte | \x666f6f8fdb00 | \x666f6fbedd | \x00 | invalid byte sequence for encoding "SHIFT_JIS_2004": 0x00
+(9 rows)
+
+--
+-- GB18030
+--
+CREATE TABLE gb18030_inputs (inbytes bytea, description text);
+insert into gb18030_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\x666f6fcff3', 'valid'),
+ ('\x666f6f8431a530', 'valid, no translation to UTF-8'),
+ ('\x666f6f84309c38', 'valid, translates to UTF-8 by mapping function'),
+ ('\x666f6f84309c', 'incomplete char '),
+ ('\x666f6f84309c0a', 'incomplete char, followed by newline '),
+ ('\x666f6f84309c3800', 'invalid, NUL byte'),
+ ('\x666f6f84309c0038', 'invalid, NUL byte');
+-- Test GB18030 verification
+select description, inbytes, (test_conv(inbytes, 'gb18030', 'gb18030')).* from gb18030_inputs;
+ description | inbytes | result | errorat | error
+------------------------------------------------+--------------------+------------------+--------------+-------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \x666f6fcff3 | \x666f6fcff3 | |
+ valid, no translation to UTF-8 | \x666f6f8431a530 | \x666f6f8431a530 | |
+ valid, translates to UTF-8 by mapping function | \x666f6f84309c38 | \x666f6f84309c38 | |
+ incomplete char | \x666f6f84309c | \x666f6f | \x84309c | invalid byte sequence for encoding "GB18030": 0x84 0x30 0x9c
+ incomplete char, followed by newline | \x666f6f84309c0a | \x666f6f | \x84309c0a | invalid byte sequence for encoding "GB18030": 0x84 0x30 0x9c 0x0a
+ invalid, NUL byte | \x666f6f84309c3800 | \x666f6f84309c38 | \x00 | invalid byte sequence for encoding "GB18030": 0x00
+ invalid, NUL byte | \x666f6f84309c0038 | \x666f6f | \x84309c0038 | invalid byte sequence for encoding "GB18030": 0x84 0x30 0x9c 0x00
+(8 rows)
+
+-- Test conversions from GB18030
+select description, inbytes, (test_conv(inbytes, 'gb18030', 'utf8')).* from gb18030_inputs;
+ description | inbytes | result | errorat | error
+------------------------------------------------+--------------------+----------------+--------------+-------------------------------------------------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \x666f6fcff3 | \x666f6fe8b1a1 | |
+ valid, no translation to UTF-8 | \x666f6f8431a530 | \x666f6f | \x8431a530 | character with byte sequence 0x84 0x31 0xa5 0x30 in encoding "GB18030" has no equivalent in encoding "UTF8"
+ valid, translates to UTF-8 by mapping function | \x666f6f84309c38 | \x666f6fefa8aa | |
+ incomplete char | \x666f6f84309c | \x666f6f | \x84309c | invalid byte sequence for encoding "GB18030": 0x84 0x30 0x9c
+ incomplete char, followed by newline | \x666f6f84309c0a | \x666f6f | \x84309c0a | invalid byte sequence for encoding "GB18030": 0x84 0x30 0x9c 0x0a
+ invalid, NUL byte | \x666f6f84309c3800 | \x666f6fefa8aa | \x00 | invalid byte sequence for encoding "GB18030": 0x00
+ invalid, NUL byte | \x666f6f84309c0038 | \x666f6f | \x84309c0038 | invalid byte sequence for encoding "GB18030": 0x84 0x30 0x9c 0x00
+(8 rows)
+
+--
+-- ISO-8859-5
+--
+CREATE TABLE iso8859_5_inputs (inbytes bytea, description text);
+insert into iso8859_5_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\xe4dede', 'valid'),
+ ('\x00', 'invalid, NUL byte'),
+ ('\xe400dede', 'invalid, NUL byte'),
+ ('\xe4dede00', 'invalid, NUL byte');
+-- Test ISO-8859-5 verification
+select description, inbytes, (test_conv(inbytes, 'iso8859-5', 'iso8859-5')).* from iso8859_5_inputs;
+ description | inbytes | result | errorat | error
+-------------------+------------+----------+----------+-------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \xe4dede | \xe4dede | |
+ invalid, NUL byte | \x00 | \x | \x00 | invalid byte sequence for encoding "ISO_8859_5": 0x00
+ invalid, NUL byte | \xe400dede | \xe4 | \x00dede | invalid byte sequence for encoding "ISO_8859_5": 0x00
+ invalid, NUL byte | \xe4dede00 | \xe4dede | \x00 | invalid byte sequence for encoding "ISO_8859_5": 0x00
+(5 rows)
+
+-- Test conversions from ISO-8859-5
+select description, inbytes, (test_conv(inbytes, 'iso8859-5', 'utf8')).* from iso8859_5_inputs;
+ description | inbytes | result | errorat | error
+-------------------+------------+----------------+----------+-------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \xe4dede | \xd184d0bed0be | |
+ invalid, NUL byte | \x00 | \x | \x00 | invalid byte sequence for encoding "ISO_8859_5": 0x00
+ invalid, NUL byte | \xe400dede | \xd184 | \x00dede | invalid byte sequence for encoding "ISO_8859_5": 0x00
+ invalid, NUL byte | \xe4dede00 | \xd184d0bed0be | \x00 | invalid byte sequence for encoding "ISO_8859_5": 0x00
+(5 rows)
+
+select description, inbytes, (test_conv(inbytes, 'iso8859-5', 'koi8r')).* from iso8859_5_inputs;
+ description | inbytes | result | errorat | error
+-------------------+------------+----------+----------+-------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \xe4dede | \xc6cfcf | |
+ invalid, NUL byte | \x00 | \x | \x00 | invalid byte sequence for encoding "ISO_8859_5": 0x00
+ invalid, NUL byte | \xe400dede | \xc6 | \x00dede | invalid byte sequence for encoding "ISO_8859_5": 0x00
+ invalid, NUL byte | \xe4dede00 | \xc6cfcf | \x00 | invalid byte sequence for encoding "ISO_8859_5": 0x00
+(5 rows)
+
+select description, inbytes, (test_conv(inbytes, 'iso8859_5', 'mule_internal')).* from iso8859_5_inputs;
+ description | inbytes | result | errorat | error
+-------------------+------------+----------------+----------+-------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \xe4dede | \x8bc68bcf8bcf | |
+ invalid, NUL byte | \x00 | \x | \x00 | invalid byte sequence for encoding "ISO_8859_5": 0x00
+ invalid, NUL byte | \xe400dede | \x8bc6 | \x00dede | invalid byte sequence for encoding "ISO_8859_5": 0x00
+ invalid, NUL byte | \xe4dede00 | \x8bc68bcf8bcf | \x00 | invalid byte sequence for encoding "ISO_8859_5": 0x00
+(5 rows)
+
+--
+-- Big5
+--
+CREATE TABLE big5_inputs (inbytes bytea, description text);
+insert into big5_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\x666f6fb648', 'valid'),
+ ('\x666f6fa27f', 'valid, no translation to UTF-8'),
+ ('\x666f6fb60048', 'invalid, NUL byte'),
+ ('\x666f6fb64800', 'invalid, NUL byte');
+-- Test Big5 verification
+select description, inbytes, (test_conv(inbytes, 'big5', 'big5')).* from big5_inputs;
+ description | inbytes | result | errorat | error
+--------------------------------+----------------+--------------+----------+------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \x666f6fb648 | \x666f6fb648 | |
+ valid, no translation to UTF-8 | \x666f6fa27f | \x666f6fa27f | |
+ invalid, NUL byte | \x666f6fb60048 | \x666f6f | \xb60048 | invalid byte sequence for encoding "BIG5": 0xb6 0x00
+ invalid, NUL byte | \x666f6fb64800 | \x666f6fb648 | \x00 | invalid byte sequence for encoding "BIG5": 0x00
+(5 rows)
+
+-- Test conversions from Big5
+select description, inbytes, (test_conv(inbytes, 'big5', 'utf8')).* from big5_inputs;
+ description | inbytes | result | errorat | error
+--------------------------------+----------------+----------------+----------+------------------------------------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \x666f6fb648 | \x666f6fe8b1a1 | |
+ valid, no translation to UTF-8 | \x666f6fa27f | \x666f6f | \xa27f | character with byte sequence 0xa2 0x7f in encoding "BIG5" has no equivalent in encoding "UTF8"
+ invalid, NUL byte | \x666f6fb60048 | \x666f6f | \xb60048 | invalid byte sequence for encoding "BIG5": 0xb6 0x00
+ invalid, NUL byte | \x666f6fb64800 | \x666f6fe8b1a1 | \x00 | invalid byte sequence for encoding "BIG5": 0x00
+(5 rows)
+
+select description, inbytes, (test_conv(inbytes, 'big5', 'mule_internal')).* from big5_inputs;
+ description | inbytes | result | errorat | error
+--------------------------------+----------------+----------------+----------+------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid | \x666f6fb648 | \x666f6f95e2af | |
+ valid, no translation to UTF-8 | \x666f6fa27f | \x666f6f95a3c1 | |
+ invalid, NUL byte | \x666f6fb60048 | \x666f6f | \xb60048 | invalid byte sequence for encoding "BIG5": 0xb6 0x00
+ invalid, NUL byte | \x666f6fb64800 | \x666f6f95e2af | \x00 | invalid byte sequence for encoding "BIG5": 0x00
+(5 rows)
+
+--
+-- MULE_INTERNAL
+--
+CREATE TABLE mic_inputs (inbytes bytea, description text);
+insert into mic_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\x8bc68bcf8bcf', 'valid (in KOI8R)'),
+ ('\x8bc68bcf8b', 'invalid,incomplete char'),
+ ('\x92bedd', 'valid (in SHIFT_JIS)'),
+ ('\x92be', 'invalid, incomplete char)'),
+ ('\x666f6f95a3c1', 'valid (in Big5)'),
+ ('\x666f6f95a3', 'invalid, incomplete char'),
+ ('\x9200bedd', 'invalid, NUL byte'),
+ ('\x92bedd00', 'invalid, NUL byte'),
+ ('\x8b00c68bcf8bcf', 'invalid, NUL byte');
+-- Test MULE_INTERNAL verification
+select description, inbytes, (test_conv(inbytes, 'mule_internal', 'mule_internal')).* from mic_inputs;
+ description | inbytes | result | errorat | error
+---------------------------+------------------+----------------+------------------+--------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid (in KOI8R) | \x8bc68bcf8bcf | \x8bc68bcf8bcf | |
+ invalid,incomplete char | \x8bc68bcf8b | \x8bc68bcf | \x8b | invalid byte sequence for encoding "MULE_INTERNAL": 0x8b
+ valid (in SHIFT_JIS) | \x92bedd | \x92bedd | |
+ invalid, incomplete char) | \x92be | \x | \x92be | invalid byte sequence for encoding "MULE_INTERNAL": 0x92 0xbe
+ valid (in Big5) | \x666f6f95a3c1 | \x666f6f95a3c1 | |
+ invalid, incomplete char | \x666f6f95a3 | \x666f6f | \x95a3 | invalid byte sequence for encoding "MULE_INTERNAL": 0x95 0xa3
+ invalid, NUL byte | \x9200bedd | \x | \x9200bedd | invalid byte sequence for encoding "MULE_INTERNAL": 0x92 0x00 0xbe
+ invalid, NUL byte | \x92bedd00 | \x92bedd | \x00 | invalid byte sequence for encoding "MULE_INTERNAL": 0x00
+ invalid, NUL byte | \x8b00c68bcf8bcf | \x | \x8b00c68bcf8bcf | invalid byte sequence for encoding "MULE_INTERNAL": 0x8b 0x00
+(10 rows)
+
+-- Test conversions from MULE_INTERNAL
+select description, inbytes, (test_conv(inbytes, 'mule_internal', 'koi8r')).* from mic_inputs;
+ description | inbytes | result | errorat | error
+---------------------------+------------------+----------+------------------+---------------------------------------------------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid (in KOI8R) | \x8bc68bcf8bcf | \xc6cfcf | |
+ invalid,incomplete char | \x8bc68bcf8b | \xc6cf | \x8b | invalid byte sequence for encoding "MULE_INTERNAL": 0x8b
+ valid (in SHIFT_JIS) | \x92bedd | \x | \x92bedd | character with byte sequence 0x92 0xbe 0xdd in encoding "MULE_INTERNAL" has no equivalent in encoding "KOI8R"
+ invalid, incomplete char) | \x92be | \x | \x92be | invalid byte sequence for encoding "MULE_INTERNAL": 0x92 0xbe
+ valid (in Big5) | \x666f6f95a3c1 | \x666f6f | \x95a3c1 | character with byte sequence 0x95 0xa3 0xc1 in encoding "MULE_INTERNAL" has no equivalent in encoding "KOI8R"
+ invalid, incomplete char | \x666f6f95a3 | \x666f6f | \x95a3 | invalid byte sequence for encoding "MULE_INTERNAL": 0x95 0xa3
+ invalid, NUL byte | \x9200bedd | \x | \x9200bedd | character with byte sequence 0x92 0x00 0xbe in encoding "MULE_INTERNAL" has no equivalent in encoding "KOI8R"
+ invalid, NUL byte | \x92bedd00 | \x | \x92bedd00 | character with byte sequence 0x92 0xbe 0xdd in encoding "MULE_INTERNAL" has no equivalent in encoding "KOI8R"
+ invalid, NUL byte | \x8b00c68bcf8bcf | \x | \x8b00c68bcf8bcf | character with byte sequence 0x8b 0x00 in encoding "MULE_INTERNAL" has no equivalent in encoding "KOI8R"
+(10 rows)
+
+select description, inbytes, (test_conv(inbytes, 'mule_internal', 'iso8859-5')).* from mic_inputs;
+ description | inbytes | result | errorat | error
+---------------------------+------------------+----------+------------------+--------------------------------------------------------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid (in KOI8R) | \x8bc68bcf8bcf | \xe4dede | |
+ invalid,incomplete char | \x8bc68bcf8b | \xe4de | \x8b | invalid byte sequence for encoding "MULE_INTERNAL": 0x8b
+ valid (in SHIFT_JIS) | \x92bedd | \x | \x92bedd | character with byte sequence 0x92 0xbe 0xdd in encoding "MULE_INTERNAL" has no equivalent in encoding "ISO_8859_5"
+ invalid, incomplete char) | \x92be | \x | \x92be | invalid byte sequence for encoding "MULE_INTERNAL": 0x92 0xbe
+ valid (in Big5) | \x666f6f95a3c1 | \x666f6f | \x95a3c1 | character with byte sequence 0x95 0xa3 0xc1 in encoding "MULE_INTERNAL" has no equivalent in encoding "ISO_8859_5"
+ invalid, incomplete char | \x666f6f95a3 | \x666f6f | \x95a3 | invalid byte sequence for encoding "MULE_INTERNAL": 0x95 0xa3
+ invalid, NUL byte | \x9200bedd | \x | \x9200bedd | character with byte sequence 0x92 0x00 0xbe in encoding "MULE_INTERNAL" has no equivalent in encoding "ISO_8859_5"
+ invalid, NUL byte | \x92bedd00 | \x | \x92bedd00 | character with byte sequence 0x92 0xbe 0xdd in encoding "MULE_INTERNAL" has no equivalent in encoding "ISO_8859_5"
+ invalid, NUL byte | \x8b00c68bcf8bcf | \x | \x8b00c68bcf8bcf | character with byte sequence 0x8b 0x00 in encoding "MULE_INTERNAL" has no equivalent in encoding "ISO_8859_5"
+(10 rows)
+
+select description, inbytes, (test_conv(inbytes, 'mule_internal', 'sjis')).* from mic_inputs;
+ description | inbytes | result | errorat | error
+---------------------------+------------------+----------+------------------+--------------------------------------------------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid (in KOI8R) | \x8bc68bcf8bcf | \x | \x8bc68bcf8bcf | character with byte sequence 0x8b 0xc6 in encoding "MULE_INTERNAL" has no equivalent in encoding "SJIS"
+ invalid,incomplete char | \x8bc68bcf8b | \x | \x8bc68bcf8b | character with byte sequence 0x8b 0xc6 in encoding "MULE_INTERNAL" has no equivalent in encoding "SJIS"
+ valid (in SHIFT_JIS) | \x92bedd | \x8fdb | |
+ invalid, incomplete char) | \x92be | \x | \x92be | invalid byte sequence for encoding "MULE_INTERNAL": 0x92 0xbe
+ valid (in Big5) | \x666f6f95a3c1 | \x666f6f | \x95a3c1 | character with byte sequence 0x95 0xa3 0xc1 in encoding "MULE_INTERNAL" has no equivalent in encoding "SJIS"
+ invalid, incomplete char | \x666f6f95a3 | \x666f6f | \x95a3 | invalid byte sequence for encoding "MULE_INTERNAL": 0x95 0xa3
+ invalid, NUL byte | \x9200bedd | \x | \x9200bedd | invalid byte sequence for encoding "MULE_INTERNAL": 0x92 0x00 0xbe
+ invalid, NUL byte | \x92bedd00 | \x8fdb | \x00 | invalid byte sequence for encoding "MULE_INTERNAL": 0x00
+ invalid, NUL byte | \x8b00c68bcf8bcf | \x | \x8b00c68bcf8bcf | invalid byte sequence for encoding "MULE_INTERNAL": 0x8b 0x00
+(10 rows)
+
+select description, inbytes, (test_conv(inbytes, 'mule_internal', 'big5')).* from mic_inputs;
+ description | inbytes | result | errorat | error
+---------------------------+------------------+--------------+------------------+--------------------------------------------------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid (in KOI8R) | \x8bc68bcf8bcf | \x | \x8bc68bcf8bcf | character with byte sequence 0x8b 0xc6 in encoding "MULE_INTERNAL" has no equivalent in encoding "BIG5"
+ invalid,incomplete char | \x8bc68bcf8b | \x | \x8bc68bcf8b | character with byte sequence 0x8b 0xc6 in encoding "MULE_INTERNAL" has no equivalent in encoding "BIG5"
+ valid (in SHIFT_JIS) | \x92bedd | \x | \x92bedd | character with byte sequence 0x92 0xbe 0xdd in encoding "MULE_INTERNAL" has no equivalent in encoding "BIG5"
+ invalid, incomplete char) | \x92be | \x | \x92be | invalid byte sequence for encoding "MULE_INTERNAL": 0x92 0xbe
+ valid (in Big5) | \x666f6f95a3c1 | \x666f6fa2a1 | |
+ invalid, incomplete char | \x666f6f95a3 | \x666f6f | \x95a3 | invalid byte sequence for encoding "MULE_INTERNAL": 0x95 0xa3
+ invalid, NUL byte | \x9200bedd | \x | \x9200bedd | invalid byte sequence for encoding "MULE_INTERNAL": 0x92 0x00 0xbe
+ invalid, NUL byte | \x92bedd00 | \x | \x92bedd00 | character with byte sequence 0x92 0xbe 0xdd in encoding "MULE_INTERNAL" has no equivalent in encoding "BIG5"
+ invalid, NUL byte | \x8b00c68bcf8bcf | \x | \x8b00c68bcf8bcf | invalid byte sequence for encoding "MULE_INTERNAL": 0x8b 0x00
+(10 rows)
+
+select description, inbytes, (test_conv(inbytes, 'mule_internal', 'euc_jp')).* from mic_inputs;
+ description | inbytes | result | errorat | error
+---------------------------+------------------+----------+------------------+----------------------------------------------------------------------------------------------------------------
+ valid, pure ASCII | \x666f6f | \x666f6f | |
+ valid (in KOI8R) | \x8bc68bcf8bcf | \x | \x8bc68bcf8bcf | character with byte sequence 0x8b 0xc6 in encoding "MULE_INTERNAL" has no equivalent in encoding "EUC_JP"
+ invalid,incomplete char | \x8bc68bcf8b | \x | \x8bc68bcf8b | character with byte sequence 0x8b 0xc6 in encoding "MULE_INTERNAL" has no equivalent in encoding "EUC_JP"
+ valid (in SHIFT_JIS) | \x92bedd | \xbedd | |
+ invalid, incomplete char) | \x92be | \x | \x92be | invalid byte sequence for encoding "MULE_INTERNAL": 0x92 0xbe
+ valid (in Big5) | \x666f6f95a3c1 | \x666f6f | \x95a3c1 | character with byte sequence 0x95 0xa3 0xc1 in encoding "MULE_INTERNAL" has no equivalent in encoding "EUC_JP"
+ invalid, incomplete char | \x666f6f95a3 | \x666f6f | \x95a3 | invalid byte sequence for encoding "MULE_INTERNAL": 0x95 0xa3
+ invalid, NUL byte | \x9200bedd | \x | \x9200bedd | invalid byte sequence for encoding "MULE_INTERNAL": 0x92 0x00 0xbe
+ invalid, NUL byte | \x92bedd00 | \xbedd | \x00 | invalid byte sequence for encoding "MULE_INTERNAL": 0x00
+ invalid, NUL byte | \x8b00c68bcf8bcf | \x | \x8b00c68bcf8bcf | invalid byte sequence for encoding "MULE_INTERNAL": 0x8b 0x00
+(10 rows)
+
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
new file mode 100644
index 0000000..c64f071
--- /dev/null
+++ b/src/test/regress/expected/copy2.out
@@ -0,0 +1,663 @@
+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;
+COPY x (b, d) from stdin;
+COPY x (b, d) from stdin;
+COPY x (a, b, c, d, e) from stdin;
+-- non-existent column in column list: should fail
+COPY x (xyz) from stdin;
+ERROR: column "xyz" of relation "x" does not exist
+-- redundant options
+COPY x from stdin (format CSV, FORMAT CSV);
+ERROR: conflicting or redundant options
+LINE 1: COPY x from stdin (format CSV, FORMAT CSV);
+ ^
+COPY x from stdin (freeze off, freeze on);
+ERROR: conflicting or redundant options
+LINE 1: COPY x from stdin (freeze off, freeze on);
+ ^
+COPY x from stdin (delimiter ',', delimiter ',');
+ERROR: conflicting or redundant options
+LINE 1: COPY x from stdin (delimiter ',', delimiter ',');
+ ^
+COPY x from stdin (null ' ', null ' ');
+ERROR: conflicting or redundant options
+LINE 1: COPY x from stdin (null ' ', null ' ');
+ ^
+COPY x from stdin (header off, header on);
+ERROR: conflicting or redundant options
+LINE 1: COPY x from stdin (header off, header on);
+ ^
+COPY x from stdin (quote ':', quote ':');
+ERROR: conflicting or redundant options
+LINE 1: COPY x from stdin (quote ':', quote ':');
+ ^
+COPY x from stdin (escape ':', escape ':');
+ERROR: conflicting or redundant options
+LINE 1: COPY x from stdin (escape ':', escape ':');
+ ^
+COPY x from stdin (force_quote (a), force_quote *);
+ERROR: conflicting or redundant options
+LINE 1: COPY x from stdin (force_quote (a), force_quote *);
+ ^
+COPY x from stdin (force_not_null (a), force_not_null (b));
+ERROR: conflicting or redundant options
+LINE 1: COPY x from stdin (force_not_null (a), force_not_null (b));
+ ^
+COPY x from stdin (force_null (a), force_null (b));
+ERROR: conflicting or redundant options
+COPY x from stdin (convert_selectively (a), convert_selectively (b));
+ERROR: conflicting or redundant options
+LINE 1: COPY x from stdin (convert_selectively (a), convert_selectiv...
+ ^
+COPY x from stdin (encoding 'sql_ascii', encoding 'sql_ascii');
+ERROR: conflicting or redundant options
+LINE 1: 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;
+ERROR: column "d" specified more than once
+-- missing data: should fail
+COPY x from stdin;
+ERROR: invalid input syntax for type integer: ""
+CONTEXT: COPY x, line 1, column a: ""
+COPY x from stdin;
+ERROR: missing data for column "e"
+CONTEXT: COPY x, line 1: "2000 230 23 23"
+COPY x from stdin;
+ERROR: missing data for column "e"
+CONTEXT: COPY x, line 1: "2001 231 \N \N"
+-- extra data: should fail
+COPY x from stdin;
+ERROR: extra data after last expected column
+CONTEXT: COPY x, line 1: "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';
+COPY x from stdin WITH DELIMITER AS ';' NULL AS '';
+COPY x from stdin WITH DELIMITER AS ':' NULL AS E'\\X' ENCODING 'sql_ascii';
+COPY x TO stdout WHERE a = 1;
+ERROR: WHERE clause not allowed with COPY TO
+LINE 1: COPY x TO stdout WHERE a = 1;
+ ^
+COPY x from stdin WHERE a = 50004;
+COPY x from stdin WHERE a > 60003;
+COPY x from stdin WHERE f > 60003;
+ERROR: column "f" does not exist
+LINE 1: COPY x from stdin WHERE f > 60003;
+ ^
+COPY x from stdin WHERE a = max(x.b);
+ERROR: aggregate functions are not allowed in COPY FROM WHERE conditions
+LINE 1: COPY x from stdin WHERE a = max(x.b);
+ ^
+COPY x from stdin WHERE a IN (SELECT 1 FROM x);
+ERROR: cannot use subquery in COPY FROM WHERE condition
+LINE 1: COPY x from stdin WHERE a IN (SELECT 1 FROM x);
+ ^
+COPY x from stdin WHERE a IN (generate_series(1,5));
+ERROR: set-returning functions are not allowed in COPY FROM WHERE conditions
+LINE 1: COPY x from stdin WHERE a IN (generate_series(1,5));
+ ^
+COPY x from stdin WHERE a = row_number() over(b);
+ERROR: window functions are not allowed in COPY FROM WHERE conditions
+LINE 1: COPY x from stdin WHERE a = row_number() over(b);
+ ^
+-- check results of copy in
+SELECT * FROM x;
+ a | b | c | d | e
+-------+----+------------+--------+----------------------
+ 9999 | | \N | NN | before trigger fired
+ 10000 | 21 | 31 | 41 | before trigger fired
+ 10001 | 22 | 32 | 42 | before trigger fired
+ 10002 | 23 | 33 | 43 | before trigger fired
+ 10003 | 24 | 34 | 44 | before trigger fired
+ 10004 | 25 | 35 | 45 | before trigger fired
+ 10005 | 26 | 36 | 46 | before trigger fired
+ 6 | | 45 | 80 | before trigger fired
+ 7 | | x | \x | before trigger fired
+ 8 | | , | \, | before trigger fired
+ 3000 | | c | | before trigger fired
+ 4000 | | C | | before trigger fired
+ 4001 | 1 | empty | | before trigger fired
+ 4002 | 2 | null | | before trigger fired
+ 4003 | 3 | Backslash | \ | before trigger fired
+ 4004 | 4 | BackslashX | \X | before trigger fired
+ 4005 | 5 | N | N | before trigger fired
+ 4006 | 6 | BackslashN | \N | before trigger fired
+ 4007 | 7 | XX | XX | before trigger fired
+ 4008 | 8 | Delimiter | : | before trigger fired
+ 50004 | 25 | 35 | 45 | before trigger fired
+ 60004 | 25 | 35 | 45 | before trigger fired
+ 60005 | 26 | 36 | 46 | before trigger fired
+ 1 | 1 | stuff | test_1 | after trigger fired
+ 2 | 2 | stuff | test_2 | after trigger fired
+ 3 | 3 | stuff | test_3 | after trigger fired
+ 4 | 4 | stuff | test_4 | after trigger fired
+ 5 | 5 | stuff | test_5 | after trigger fired
+(28 rows)
+
+-- check copy out
+COPY x TO stdout;
+9999 \N \\N NN before trigger fired
+10000 21 31 41 before trigger fired
+10001 22 32 42 before trigger fired
+10002 23 33 43 before trigger fired
+10003 24 34 44 before trigger fired
+10004 25 35 45 before trigger fired
+10005 26 36 46 before trigger fired
+6 \N 45 80 before trigger fired
+7 \N x \\x before trigger fired
+8 \N , \\, before trigger fired
+3000 \N c \N before trigger fired
+4000 \N C \N before trigger fired
+4001 1 empty before trigger fired
+4002 2 null \N before trigger fired
+4003 3 Backslash \\ before trigger fired
+4004 4 BackslashX \\X before trigger fired
+4005 5 N N before trigger fired
+4006 6 BackslashN \\N before trigger fired
+4007 7 XX XX before trigger fired
+4008 8 Delimiter : before trigger fired
+50004 25 35 45 before trigger fired
+60004 25 35 45 before trigger fired
+60005 26 36 46 before trigger fired
+1 1 stuff test_1 after trigger fired
+2 2 stuff test_2 after trigger fired
+3 3 stuff test_3 after trigger fired
+4 4 stuff test_4 after trigger fired
+5 5 stuff test_5 after trigger fired
+COPY x (c, e) TO stdout;
+\\N before trigger fired
+31 before trigger fired
+32 before trigger fired
+33 before trigger fired
+34 before trigger fired
+35 before trigger fired
+36 before trigger fired
+45 before trigger fired
+x before trigger fired
+, before trigger fired
+c before trigger fired
+C before trigger fired
+empty before trigger fired
+null before trigger fired
+Backslash before trigger fired
+BackslashX before trigger fired
+N before trigger fired
+BackslashN before trigger fired
+XX before trigger fired
+Delimiter before trigger fired
+35 before trigger fired
+35 before trigger fired
+36 before trigger fired
+stuff after trigger fired
+stuff after trigger fired
+stuff after trigger fired
+stuff after trigger fired
+stuff after trigger fired
+COPY x (b, e) TO stdout WITH NULL 'I''m null';
+I'm null before trigger fired
+21 before trigger fired
+22 before trigger fired
+23 before trigger fired
+24 before trigger fired
+25 before trigger fired
+26 before trigger fired
+I'm null before trigger fired
+I'm null before trigger fired
+I'm null before trigger fired
+I'm null before trigger fired
+I'm null before trigger fired
+1 before trigger fired
+2 before trigger fired
+3 before trigger fired
+4 before trigger fired
+5 before trigger fired
+6 before trigger fired
+7 before trigger fired
+8 before trigger fired
+25 before trigger fired
+25 before trigger fired
+26 before trigger fired
+1 after trigger fired
+2 after trigger fired
+3 after trigger fired
+4 after trigger fired
+5 after trigger fired
+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;
+"Jackson, Sam",\h
+"It is ""perfect"".",
+"",
+COPY y TO stdout WITH CSV QUOTE '''' DELIMITER '|';
+Jackson, Sam|\h
+It is "perfect".|
+''|
+COPY y TO stdout WITH CSV FORCE QUOTE col2 ESCAPE E'\\' ENCODING 'sql_ascii';
+"Jackson, Sam","\\h"
+"It is \"perfect\"."," "
+"",
+COPY y TO stdout WITH CSV FORCE QUOTE *;
+"Jackson, Sam","\h"
+"It is ""perfect""."," "
+"",
+-- Repeat above tests with new 9.0 option syntax
+COPY y TO stdout (FORMAT CSV);
+"Jackson, Sam",\h
+"It is ""perfect"".",
+"",
+COPY y TO stdout (FORMAT CSV, QUOTE '''', DELIMITER '|');
+Jackson, Sam|\h
+It is "perfect".|
+''|
+COPY y TO stdout (FORMAT CSV, FORCE_QUOTE (col2), ESCAPE E'\\');
+"Jackson, Sam","\\h"
+"It is \"perfect\"."," "
+"",
+COPY y TO stdout (FORMAT CSV, FORCE_QUOTE *);
+"Jackson, Sam","\h"
+"It is ""perfect""."," "
+"",
+\copy y TO stdout (FORMAT CSV)
+"Jackson, Sam",\h
+"It is ""perfect"".",
+"",
+\copy y TO stdout (FORMAT CSV, QUOTE '''', DELIMITER '|')
+Jackson, Sam|\h
+It is "perfect".|
+''|
+\copy y TO stdout (FORMAT CSV, FORCE_QUOTE (col2), ESCAPE E'\\')
+"Jackson, Sam","\\h"
+"It is \"perfect\"."," "
+"",
+\copy y TO stdout (FORMAT CSV, FORCE_QUOTE *)
+"Jackson, Sam","\h"
+"It is ""perfect""."," "
+"",
+--test that we read consecutive LFs properly
+CREATE TEMP TABLE testnl (a int, b text, c int);
+COPY testnl FROM stdin CSV;
+-- test end of copy marker
+CREATE TEMP TABLE testeoc (a text);
+COPY testeoc FROM stdin CSV;
+COPY testeoc TO stdout CSV;
+a\.
+\.b
+c\.d
+"\."
+-- 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';
+1 \\0
+\0 \0
+COPY testnull FROM stdin WITH NULL AS E'\\0';
+SELECT * FROM testnull;
+ a | b
+----+----
+ 1 | \0
+ |
+ 42 | \0
+ |
+(4 rows)
+
+BEGIN;
+CREATE TABLE vistest (LIKE testeoc);
+COPY vistest FROM stdin CSV;
+COMMIT;
+SELECT * FROM vistest;
+ a
+----
+ a0
+ b
+(2 rows)
+
+BEGIN;
+TRUNCATE vistest;
+COPY vistest FROM stdin CSV;
+SELECT * FROM vistest;
+ a
+----
+ a1
+ b
+(2 rows)
+
+SAVEPOINT s1;
+TRUNCATE vistest;
+COPY vistest FROM stdin CSV;
+SELECT * FROM vistest;
+ a
+----
+ d1
+ e
+(2 rows)
+
+COMMIT;
+SELECT * FROM vistest;
+ a
+----
+ d1
+ e
+(2 rows)
+
+BEGIN;
+TRUNCATE vistest;
+COPY vistest FROM stdin CSV FREEZE;
+SELECT * FROM vistest;
+ a
+----
+ a2
+ b
+(2 rows)
+
+SAVEPOINT s1;
+TRUNCATE vistest;
+COPY vistest FROM stdin CSV FREEZE;
+SELECT * FROM vistest;
+ a
+----
+ d2
+ e
+(2 rows)
+
+COMMIT;
+SELECT * FROM vistest;
+ a
+----
+ d2
+ e
+(2 rows)
+
+BEGIN;
+TRUNCATE vistest;
+COPY vistest FROM stdin CSV FREEZE;
+SELECT * FROM vistest;
+ a
+---
+ x
+ y
+(2 rows)
+
+COMMIT;
+TRUNCATE vistest;
+COPY vistest FROM stdin CSV FREEZE;
+ERROR: cannot perform COPY FREEZE because the table was not created or truncated in the current subtransaction
+BEGIN;
+TRUNCATE vistest;
+SAVEPOINT s1;
+COPY vistest FROM stdin CSV FREEZE;
+ERROR: cannot perform COPY FREEZE because the table was not created or truncated in the current subtransaction
+COMMIT;
+BEGIN;
+INSERT INTO vistest VALUES ('z');
+SAVEPOINT s1;
+TRUNCATE vistest;
+ROLLBACK TO SAVEPOINT s1;
+COPY vistest FROM stdin CSV FREEZE;
+ERROR: cannot perform COPY FREEZE because the table was not created or truncated in the current subtransaction
+COMMIT;
+CREATE FUNCTION truncate_in_subxact() RETURNS VOID AS
+$$
+BEGIN
+ TRUNCATE vistest;
+EXCEPTION
+ WHEN OTHERS THEN
+ INSERT INTO vistest VALUES ('subxact failure');
+END;
+$$ language plpgsql;
+BEGIN;
+INSERT INTO vistest VALUES ('z');
+SELECT truncate_in_subxact();
+ truncate_in_subxact
+---------------------
+
+(1 row)
+
+COPY vistest FROM stdin CSV FREEZE;
+SELECT * FROM vistest;
+ a
+----
+ d4
+ e
+(2 rows)
+
+COMMIT;
+SELECT * FROM vistest;
+ a
+----
+ d4
+ e
+(2 rows)
+
+-- 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));
+COMMIT;
+SELECT b, c FROM forcetest WHERE a = 1;
+ b | c
+---+------
+ | NULL
+(1 row)
+
+-- 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));
+COMMIT;
+SELECT c, d FROM forcetest WHERE a = 2;
+ c | d
+---+------
+ | NULL
+(1 row)
+
+-- 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));
+ERROR: null value in column "b" of relation "forcetest" violates not-null constraint
+DETAIL: Failing row contains (3, null, , null, null).
+CONTEXT: COPY forcetest, line 1: "3,,"""
+ROLLBACK;
+-- should fail with "not referenced by COPY" error
+BEGIN;
+COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(b));
+ERROR: FORCE_NOT_NULL column "b" not referenced by COPY
+ROLLBACK;
+-- should fail with "not referenced by COPY" error
+BEGIN;
+COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NULL(b));
+ERROR: FORCE_NULL column "b" not referenced by COPY
+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
+ Table "public.check_con_tbl"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | | | plain | |
+Check constraints:
+ "check_con_tbl_check" CHECK (check_con_function(check_con_tbl.*))
+
+copy check_con_tbl from stdin;
+NOTICE: input = {"f1":1}
+NOTICE: input = {"f1":null}
+copy check_con_tbl from stdin;
+NOTICE: input = {"f1":0}
+ERROR: new row for relation "check_con_tbl" violates check constraint "check_con_tbl_check"
+DETAIL: Failing row contains (0).
+CONTEXT: COPY check_con_tbl, line 1: "0"
+select * from check_con_tbl;
+ f1
+----
+ 1
+
+(2 rows)
+
+-- 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;
+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;
+1 4 1
+2 3 2
+3 2 3
+4 1 4
+COPY rls_t1 (a, b, c) TO stdout;
+1 4 1
+2 3 2
+3 2 3
+4 1 4
+-- subset of columns
+COPY rls_t1 (a) TO stdout;
+1
+2
+3
+4
+COPY rls_t1 (a, b) TO stdout;
+1 4
+2 3
+3 2
+4 1
+-- column reordering
+COPY rls_t1 (b, a) TO stdout;
+4 1
+3 2
+2 3
+1 4
+SET SESSION AUTHORIZATION regress_rls_copy_user;
+-- all columns
+COPY rls_t1 TO stdout;
+2 3 2
+4 1 4
+COPY rls_t1 (a, b, c) TO stdout;
+2 3 2
+4 1 4
+-- subset of columns
+COPY rls_t1 (a) TO stdout;
+2
+4
+COPY rls_t1 (a, b) TO stdout;
+2 3
+4 1
+-- column reordering
+COPY rls_t1 (b, a) TO stdout;
+3 2
+1 4
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION regress_rls_copy_user_colperms;
+-- attempt all columns (should fail)
+COPY rls_t1 TO stdout;
+ERROR: permission denied for table rls_t1
+COPY rls_t1 (a, b, c) TO stdout;
+ERROR: permission denied for table rls_t1
+-- try to copy column with no privileges (should fail)
+COPY rls_t1 (c) TO stdout;
+ERROR: permission denied for table rls_t1
+-- subset of columns (should succeed)
+COPY rls_t1 (a) TO stdout;
+2
+4
+COPY rls_t1 (a, b) TO stdout;
+2 3
+4 1
+RESET SESSION AUTHORIZATION;
+-- 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
+ERROR: cannot copy to view "instead_of_insert_tbl_view"
+HINT: To enable copying to a view, provide an INSTEAD OF INSERT trigger.
+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;
+SELECT * FROM instead_of_insert_tbl;
+ id | name
+----+-------
+ 1 | test1
+(1 row)
+
+-- 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;
+SELECT * FROM instead_of_insert_tbl;
+ id | name
+----+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+COMMIT;
+-- 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/expected/copydml.out b/src/test/regress/expected/copydml.out
new file mode 100644
index 0000000..b5a2256
--- /dev/null
+++ b/src/test/regress/expected/copydml.out
@@ -0,0 +1,112 @@
+--
+-- Test cases for COPY (INSERT/UPDATE/DELETE) TO
+--
+create table copydml_test (id serial, t text);
+insert into copydml_test (t) values ('a');
+insert into copydml_test (t) values ('b');
+insert into copydml_test (t) values ('c');
+insert into copydml_test (t) values ('d');
+insert into copydml_test (t) values ('e');
+--
+-- Test COPY (insert/update/delete ...)
+--
+copy (insert into copydml_test (t) values ('f') returning id) to stdout;
+6
+copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
+6
+copy (delete from copydml_test where t = 'g' returning id) to stdout;
+6
+--
+-- Test \copy (insert/update/delete ...)
+--
+\copy (insert into copydml_test (t) values ('f') returning id) to stdout;
+7
+\copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
+7
+\copy (delete from copydml_test where t = 'g' returning id) to stdout;
+7
+-- Error cases
+copy (insert into copydml_test default values) to stdout;
+ERROR: COPY query must have a RETURNING clause
+copy (update copydml_test set t = 'g') to stdout;
+ERROR: COPY query must have a RETURNING clause
+copy (delete from copydml_test) to stdout;
+ERROR: COPY query must have a RETURNING clause
+create rule qqq as on insert to copydml_test do instead nothing;
+copy (insert into copydml_test default values) to stdout;
+ERROR: DO INSTEAD NOTHING rules are not supported for COPY
+drop rule qqq on copydml_test;
+create rule qqq as on insert to copydml_test do also delete from copydml_test;
+copy (insert into copydml_test default values) to stdout;
+ERROR: DO ALSO rules are not supported for the COPY
+drop rule qqq on copydml_test;
+create rule qqq as on insert to copydml_test do instead (delete from copydml_test; delete from copydml_test);
+copy (insert into copydml_test default values) to stdout;
+ERROR: multi-statement DO INSTEAD rules are not supported for COPY
+drop rule qqq on copydml_test;
+create rule qqq as on insert to copydml_test where new.t <> 'f' do instead delete from copydml_test;
+copy (insert into copydml_test default values) to stdout;
+ERROR: conditional DO INSTEAD rules are not supported for COPY
+drop rule qqq on copydml_test;
+create rule qqq as on update to copydml_test do instead nothing;
+copy (update copydml_test set t = 'f') to stdout;
+ERROR: DO INSTEAD NOTHING rules are not supported for COPY
+drop rule qqq on copydml_test;
+create rule qqq as on update to copydml_test do also delete from copydml_test;
+copy (update copydml_test set t = 'f') to stdout;
+ERROR: DO ALSO rules are not supported for the COPY
+drop rule qqq on copydml_test;
+create rule qqq as on update to copydml_test do instead (delete from copydml_test; delete from copydml_test);
+copy (update copydml_test set t = 'f') to stdout;
+ERROR: multi-statement DO INSTEAD rules are not supported for COPY
+drop rule qqq on copydml_test;
+create rule qqq as on update to copydml_test where new.t <> 'f' do instead delete from copydml_test;
+copy (update copydml_test set t = 'f') to stdout;
+ERROR: conditional DO INSTEAD rules are not supported for COPY
+drop rule qqq on copydml_test;
+create rule qqq as on delete to copydml_test do instead nothing;
+copy (delete from copydml_test) to stdout;
+ERROR: DO INSTEAD NOTHING rules are not supported for COPY
+drop rule qqq on copydml_test;
+create rule qqq as on delete to copydml_test do also insert into copydml_test default values;
+copy (delete from copydml_test) to stdout;
+ERROR: DO ALSO rules are not supported for the COPY
+drop rule qqq on copydml_test;
+create rule qqq as on delete to copydml_test do instead (insert into copydml_test default values; insert into copydml_test default values);
+copy (delete from copydml_test) to stdout;
+ERROR: multi-statement DO INSTEAD rules are not supported for COPY
+drop rule qqq on copydml_test;
+create rule qqq as on delete to copydml_test where old.t <> 'f' do instead insert into copydml_test default values;
+copy (delete from copydml_test) to stdout;
+ERROR: conditional DO INSTEAD rules are not supported for COPY
+drop rule qqq on copydml_test;
+-- triggers
+create function qqq_trig() returns trigger as $$
+begin
+if tg_op in ('INSERT', 'UPDATE') then
+ raise notice '% % %', tg_when, tg_op, new.id;
+ return new;
+else
+ raise notice '% % %', tg_when, tg_op, old.id;
+ return old;
+end if;
+end
+$$ language plpgsql;
+create trigger qqqbef before insert or update or delete on copydml_test
+ for each row execute procedure qqq_trig();
+create trigger qqqaf after insert or update or delete on copydml_test
+ for each row execute procedure qqq_trig();
+copy (insert into copydml_test (t) values ('f') returning id) to stdout;
+NOTICE: BEFORE INSERT 8
+8
+NOTICE: AFTER INSERT 8
+copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
+NOTICE: BEFORE UPDATE 8
+8
+NOTICE: AFTER UPDATE 8
+copy (delete from copydml_test where t = 'g' returning id) to stdout;
+NOTICE: BEFORE DELETE 8
+8
+NOTICE: AFTER DELETE 8
+drop table copydml_test;
+drop function qqq_trig();
diff --git a/src/test/regress/expected/copyselect.out b/src/test/regress/expected/copyselect.out
new file mode 100644
index 0000000..72865fe
--- /dev/null
+++ b/src/test/regress/expected/copyselect.out
@@ -0,0 +1,151 @@
+--
+-- Test cases for COPY (select) TO
+--
+create table test1 (id serial, t text);
+insert into test1 (t) values ('a');
+insert into test1 (t) values ('b');
+insert into test1 (t) values ('c');
+insert into test1 (t) values ('d');
+insert into test1 (t) values ('e');
+create table test2 (id serial, t text);
+insert into test2 (t) values ('A');
+insert into test2 (t) values ('B');
+insert into test2 (t) values ('C');
+insert into test2 (t) values ('D');
+insert into test2 (t) values ('E');
+create view v_test1
+as select 'v_'||t from test1;
+--
+-- Test COPY table TO
+--
+copy test1 to stdout;
+1 a
+2 b
+3 c
+4 d
+5 e
+--
+-- This should fail
+--
+copy v_test1 to stdout;
+ERROR: cannot copy from view "v_test1"
+HINT: Try the COPY (SELECT ...) TO variant.
+--
+-- Test COPY (select) TO
+--
+copy (select t from test1 where id=1) to stdout;
+a
+--
+-- Test COPY (select for update) TO
+--
+copy (select t from test1 where id=3 for update) to stdout;
+c
+--
+-- This should fail
+--
+copy (select t into temp test3 from test1 where id=3) to stdout;
+ERROR: COPY (SELECT INTO) is not supported
+--
+-- This should fail
+--
+copy (select * from test1) from stdin;
+ERROR: syntax error at or near "from"
+LINE 1: copy (select * from test1) from stdin;
+ ^
+--
+-- This should fail
+--
+copy (select * from test1) (t,id) to stdout;
+ERROR: syntax error at or near "("
+LINE 1: copy (select * from test1) (t,id) to stdout;
+ ^
+--
+-- Test JOIN
+--
+copy (select * from test1 join test2 using (id)) to stdout;
+1 a A
+2 b B
+3 c C
+4 d D
+5 e E
+--
+-- Test UNION SELECT
+--
+copy (select t from test1 where id = 1 UNION select * from v_test1 ORDER BY 1) to stdout;
+a
+v_a
+v_b
+v_c
+v_d
+v_e
+--
+-- Test subselect
+--
+copy (select * from (select t from test1 where id = 1 UNION select * from v_test1 ORDER BY 1) t1) to stdout;
+a
+v_a
+v_b
+v_c
+v_d
+v_e
+--
+-- Test headers, CSV and quotes
+--
+copy (select t from test1 where id = 1) to stdout csv header force quote t;
+t
+"a"
+--
+-- Test psql builtins, plain table
+--
+\copy test1 to stdout
+1 a
+2 b
+3 c
+4 d
+5 e
+--
+-- This should fail
+--
+\copy v_test1 to stdout
+ERROR: cannot copy from view "v_test1"
+HINT: Try the COPY (SELECT ...) TO variant.
+--
+-- Test \copy (select ...)
+--
+\copy (select "id",'id','id""'||t,(id + 1)*id,t,"test1"."t" from test1 where id=3) to stdout
+3 id id""c 12 c c
+--
+-- 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
+1
+ERROR: division by zero
+select 1/0\; copy (select 1) to stdout; -- error only
+ERROR: division by zero
+copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; -- 1 2 3
+1
+2
+ ?column?
+----------
+ 3
+(1 row)
+
+create table test3 (c int);
+select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 1
+ ?column?
+----------
+ 1
+(1 row)
+
+select * from test3;
+ c
+---
+ 1
+ 2
+(2 rows)
+
+drop table test3;
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
new file mode 100644
index 0000000..dcf6909
--- /dev/null
+++ b/src/test/regress/expected/create_aggregate.out
@@ -0,0 +1,324 @@
+--
+-- CREATE_AGGREGATE
+--
+-- all functions CREATEd
+CREATE AGGREGATE newavg (
+ sfunc = int4_avg_accum, basetype = int4, stype = _int8,
+ finalfunc = int8_avg,
+ initcond1 = '{0,0}'
+);
+-- test comments
+COMMENT ON AGGREGATE newavg_wrong (int4) IS 'an agg comment';
+ERROR: aggregate newavg_wrong(integer) does not exist
+COMMENT ON AGGREGATE newavg (int4) IS 'an agg comment';
+COMMENT ON AGGREGATE newavg (int4) IS NULL;
+-- without finalfunc; test obsolete spellings 'sfunc1' etc
+CREATE AGGREGATE newsum (
+ sfunc1 = int4pl, basetype = int4, stype1 = int4,
+ initcond1 = '0'
+);
+-- zero-argument aggregate
+CREATE AGGREGATE newcnt (*) (
+ sfunc = int8inc, stype = int8,
+ initcond = '0', parallel = safe
+);
+-- old-style spelling of same (except without parallel-safe; that's too new)
+CREATE AGGREGATE oldcnt (
+ sfunc = int8inc, basetype = 'ANY', stype = int8,
+ initcond = '0'
+);
+-- aggregate that only cares about null/nonnull input
+CREATE AGGREGATE newcnt ("any") (
+ sfunc = int8inc_any, stype = int8,
+ initcond = '0'
+);
+COMMENT ON AGGREGATE nosuchagg (*) IS 'should fail';
+ERROR: aggregate nosuchagg(*) does not exist
+COMMENT ON AGGREGATE newcnt (*) IS 'an agg(*) comment';
+COMMENT ON AGGREGATE newcnt ("any") IS 'an agg(any) comment';
+-- multi-argument aggregate
+create function sum3(int8,int8,int8) returns int8 as
+'select $1 + $2 + $3' language sql strict immutable;
+create aggregate sum2(int8,int8) (
+ sfunc = sum3, stype = int8,
+ initcond = '0'
+);
+-- multi-argument aggregates sensitive to distinct/order, strict/nonstrict
+create type aggtype as (a integer, b integer, c text);
+create function aggf_trans(aggtype[],integer,integer,text) returns aggtype[]
+as 'select array_append($1,ROW($2,$3,$4)::aggtype)'
+language sql strict immutable;
+create function aggfns_trans(aggtype[],integer,integer,text) returns aggtype[]
+as 'select array_append($1,ROW($2,$3,$4)::aggtype)'
+language sql immutable;
+create aggregate aggfstr(integer,integer,text) (
+ sfunc = aggf_trans, stype = aggtype[],
+ initcond = '{}'
+);
+create aggregate aggfns(integer,integer,text) (
+ sfunc = aggfns_trans, stype = aggtype[], sspace = 10000,
+ initcond = '{}'
+);
+-- check error cases that would require run-time type coercion
+create function least_accum(int8, int8) returns int8 language sql as
+ 'select least($1, $2)';
+create aggregate least_agg(int4) (
+ stype = int8, sfunc = least_accum
+); -- fails
+ERROR: function least_accum(bigint, bigint) requires run-time type coercion
+drop function least_accum(int8, int8);
+create function least_accum(anycompatible, anycompatible)
+returns anycompatible language sql as
+ 'select least($1, $2)';
+create aggregate least_agg(int4) (
+ stype = int8, sfunc = least_accum
+); -- fails
+ERROR: function least_accum(bigint, bigint) requires run-time type coercion
+create aggregate least_agg(int8) (
+ stype = int8, sfunc = least_accum
+);
+drop function least_accum(anycompatible, anycompatible) cascade;
+NOTICE: drop cascades to function least_agg(bigint)
+-- variadic aggregates
+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
+);
+create function cleast_accum(anycompatible, variadic anycompatiblearray)
+returns anycompatible language sql as
+ 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)';
+create aggregate cleast_agg(variadic items anycompatiblearray) (
+ stype = anycompatible, sfunc = cleast_accum
+);
+-- test ordered-set aggs using built-in support functions
+create aggregate my_percentile_disc(float8 ORDER BY anyelement) (
+ stype = internal,
+ sfunc = ordered_set_transition,
+ finalfunc = percentile_disc_final,
+ finalfunc_extra = true,
+ finalfunc_modify = read_write
+);
+create aggregate my_rank(VARIADIC "any" ORDER BY VARIADIC "any") (
+ stype = internal,
+ sfunc = ordered_set_transition_multi,
+ finalfunc = rank_final,
+ finalfunc_extra = true,
+ hypothetical
+);
+alter aggregate my_percentile_disc(float8 ORDER BY anyelement)
+ rename to test_percentile_disc;
+alter aggregate my_rank(VARIADIC "any" ORDER BY VARIADIC "any")
+ rename to test_rank;
+\da test_*
+ List of aggregate functions
+ Schema | Name | Result data type | Argument data types | Description
+--------+----------------------+------------------+----------------------------------------+-------------
+ public | test_percentile_disc | anyelement | double precision ORDER BY anyelement |
+ public | test_rank | bigint | VARIADIC "any" ORDER BY VARIADIC "any" |
+(2 rows)
+
+-- moving-aggregate options
+CREATE AGGREGATE sumdouble (float8)
+(
+ stype = float8,
+ sfunc = float8pl,
+ mstype = float8,
+ msfunc = float8pl,
+ minvfunc = float8mi
+);
+-- aggregate combine and serialization functions
+-- can't specify just one of serialfunc and deserialfunc
+CREATE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ serialfunc = numeric_avg_serialize
+);
+ERROR: must specify both or neither of serialization and deserialization functions
+-- serialfunc must have correct parameters
+CREATE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ serialfunc = numeric_avg_deserialize,
+ deserialfunc = numeric_avg_deserialize
+);
+ERROR: function numeric_avg_deserialize(internal) does not exist
+-- deserialfunc must have correct parameters
+CREATE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ serialfunc = numeric_avg_serialize,
+ deserialfunc = numeric_avg_serialize
+);
+ERROR: function numeric_avg_serialize(bytea, internal) does not exist
+-- ensure combine function parameters are checked
+CREATE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ serialfunc = numeric_avg_serialize,
+ deserialfunc = numeric_avg_deserialize,
+ combinefunc = int4larger
+);
+ERROR: function int4larger(internal, internal) does not exist
+-- ensure create aggregate works.
+CREATE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ finalfunc = numeric_avg,
+ serialfunc = numeric_avg_serialize,
+ deserialfunc = numeric_avg_deserialize,
+ combinefunc = numeric_avg_combine,
+ finalfunc_modify = shareable -- just to test a non-default setting
+);
+-- Ensure all these functions made it into the catalog
+SELECT aggfnoid, aggtransfn, aggcombinefn, aggtranstype::regtype,
+ aggserialfn, aggdeserialfn, aggfinalmodify
+FROM pg_aggregate
+WHERE aggfnoid = 'myavg'::REGPROC;
+ aggfnoid | aggtransfn | aggcombinefn | aggtranstype | aggserialfn | aggdeserialfn | aggfinalmodify
+----------+-------------------+---------------------+--------------+-----------------------+-------------------------+----------------
+ myavg | numeric_avg_accum | numeric_avg_combine | internal | numeric_avg_serialize | numeric_avg_deserialize | s
+(1 row)
+
+DROP AGGREGATE myavg (numeric);
+-- create or replace aggregate
+CREATE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ finalfunc = numeric_avg
+);
+CREATE OR REPLACE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ finalfunc = numeric_avg,
+ serialfunc = numeric_avg_serialize,
+ deserialfunc = numeric_avg_deserialize,
+ combinefunc = numeric_avg_combine,
+ finalfunc_modify = shareable -- just to test a non-default setting
+);
+-- Ensure all these functions made it into the catalog again
+SELECT aggfnoid, aggtransfn, aggcombinefn, aggtranstype::regtype,
+ aggserialfn, aggdeserialfn, aggfinalmodify
+FROM pg_aggregate
+WHERE aggfnoid = 'myavg'::REGPROC;
+ aggfnoid | aggtransfn | aggcombinefn | aggtranstype | aggserialfn | aggdeserialfn | aggfinalmodify
+----------+-------------------+---------------------+--------------+-----------------------+-------------------------+----------------
+ myavg | numeric_avg_accum | numeric_avg_combine | internal | numeric_avg_serialize | numeric_avg_deserialize | s
+(1 row)
+
+-- can change stype:
+CREATE OR REPLACE AGGREGATE myavg (numeric)
+(
+ stype = numeric,
+ sfunc = numeric_add
+);
+SELECT aggfnoid, aggtransfn, aggcombinefn, aggtranstype::regtype,
+ aggserialfn, aggdeserialfn, aggfinalmodify
+FROM pg_aggregate
+WHERE aggfnoid = 'myavg'::REGPROC;
+ aggfnoid | aggtransfn | aggcombinefn | aggtranstype | aggserialfn | aggdeserialfn | aggfinalmodify
+----------+-------------+--------------+--------------+-------------+---------------+----------------
+ myavg | numeric_add | - | numeric | - | - | r
+(1 row)
+
+-- can't change return type:
+CREATE OR REPLACE AGGREGATE myavg (numeric)
+(
+ stype = numeric,
+ sfunc = numeric_add,
+ finalfunc = numeric_out
+);
+ERROR: cannot change return type of existing function
+HINT: Use DROP AGGREGATE myavg(numeric) first.
+-- can't change to a different kind:
+CREATE OR REPLACE AGGREGATE myavg (order by numeric)
+(
+ stype = numeric,
+ sfunc = numeric_add
+);
+ERROR: cannot change routine kind
+DETAIL: "myavg" is an ordinary aggregate function.
+-- can't change plain function to aggregate:
+create function sum4(int8,int8,int8,int8) returns int8 as
+'select $1 + $2 + $3 + $4' language sql strict immutable;
+CREATE OR REPLACE AGGREGATE sum3 (int8,int8,int8)
+(
+ stype = int8,
+ sfunc = sum4
+);
+ERROR: cannot change routine kind
+DETAIL: "sum3" is a function.
+drop function sum4(int8,int8,int8,int8);
+DROP AGGREGATE myavg (numeric);
+-- invalid: bad parallel-safety marking
+CREATE AGGREGATE mysum (int)
+(
+ stype = int,
+ sfunc = int4pl,
+ parallel = pear
+);
+ERROR: parameter "parallel" must be SAFE, RESTRICTED, or UNSAFE
+-- invalid: nonstrict inverse with strict forward function
+CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+$$ SELECT $1 - $2; $$
+LANGUAGE SQL;
+CREATE AGGREGATE invalidsumdouble (float8)
+(
+ stype = float8,
+ sfunc = float8pl,
+ mstype = float8,
+ msfunc = float8pl,
+ minvfunc = float8mi_n
+);
+ERROR: strictness of aggregate's forward and inverse transition functions must match
+-- invalid: non-matching result types
+CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+$$ SELECT CAST($1 - $2 AS INT); $$
+LANGUAGE SQL;
+CREATE AGGREGATE wrongreturntype (float8)
+(
+ stype = float8,
+ sfunc = float8pl,
+ mstype = float8,
+ msfunc = float8pl,
+ minvfunc = float8mi_int
+);
+ERROR: return type of inverse transition function float8mi_int is not double precision
+-- invalid: non-lowercase quoted identifiers
+CREATE AGGREGATE case_agg ( -- old syntax
+ "Sfunc1" = int4pl,
+ "Basetype" = int4,
+ "Stype1" = int4,
+ "Initcond1" = '0',
+ "Parallel" = safe
+);
+WARNING: aggregate attribute "Sfunc1" not recognized
+WARNING: aggregate attribute "Basetype" not recognized
+WARNING: aggregate attribute "Stype1" not recognized
+WARNING: aggregate attribute "Initcond1" not recognized
+WARNING: aggregate attribute "Parallel" not recognized
+ERROR: aggregate stype must be specified
+CREATE AGGREGATE case_agg(float8)
+(
+ "Stype" = internal,
+ "Sfunc" = ordered_set_transition,
+ "Finalfunc" = percentile_disc_final,
+ "Finalfunc_extra" = true,
+ "Finalfunc_modify" = read_write,
+ "Parallel" = safe
+);
+WARNING: aggregate attribute "Stype" not recognized
+WARNING: aggregate attribute "Sfunc" not recognized
+WARNING: aggregate attribute "Finalfunc" not recognized
+WARNING: aggregate attribute "Finalfunc_extra" not recognized
+WARNING: aggregate attribute "Finalfunc_modify" not recognized
+WARNING: aggregate attribute "Parallel" not recognized
+ERROR: aggregate stype must be specified
diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out
new file mode 100644
index 0000000..0dfb26c
--- /dev/null
+++ b/src/test/regress/expected/create_am.out
@@ -0,0 +1,301 @@
+--
+-- 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;
+ERROR: function int4in(internal) does not exist
+CREATE ACCESS METHOD bogus TYPE INDEX HANDLER heap_tableam_handler;
+ERROR: function heap_tableam_handler must return type index_am_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);
+ERROR: data type box has no default operator class for access method "gist2"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+-- 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;
+DROP INDEX grect2ind;
+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];
+ QUERY PLAN
+-----------------------------------------------------------------
+ Sort
+ Sort Key: ((home_base[0])[0])
+ -> Index Only Scan using grect2ind2 on fast_emp4000
+ Index Cond: (home_base <@ '(2000,1000),(200,200)'::box)
+(4 rows)
+
+SELECT * FROM fast_emp4000
+ WHERE home_base <@ '(200,200),(2000,1000)'::box
+ ORDER BY (home_base[0])[0];
+ home_base
+-----------------------
+ (337,455),(240,359)
+ (1444,403),(1346,344)
+(2 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
+ QUERY PLAN
+-------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using grect2ind2 on fast_emp4000
+ Index Cond: (home_base && '(1000,1000),(0,0)'::box)
+(3 rows)
+
+SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
+ count
+-------
+ 2
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
+ QUERY PLAN
+--------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using grect2ind2 on fast_emp4000
+ Index Cond: (home_base IS NULL)
+(3 rows)
+
+SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
+ count
+-------
+ 278
+(1 row)
+
+ROLLBACK;
+-- Try to drop access method: fail because of dependent objects
+DROP ACCESS METHOD gist2;
+ERROR: cannot drop access method gist2 because other objects depend on it
+DETAIL: index grect2ind2 depends on operator class box_ops for access method gist2
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- Drop access method cascade
+-- To prevent a (rare) deadlock against autovacuum,
+-- we must lock the table that owns the index that will be dropped
+BEGIN;
+LOCK TABLE fast_emp4000;
+DROP ACCESS METHOD gist2 CASCADE;
+NOTICE: drop cascades to index grect2ind2
+COMMIT;
+--
+-- Test table access methods
+--
+-- prevent empty values
+SET default_table_access_method = '';
+ERROR: invalid value for parameter "default_table_access_method": ""
+DETAIL: default_table_access_method cannot be empty.
+-- prevent nonexistent values
+SET default_table_access_method = 'I do not exist AM';
+ERROR: invalid value for parameter "default_table_access_method": "I do not exist AM"
+DETAIL: Table access method "I do not exist AM" does not exist.
+-- prevent setting it to an index AM
+SET default_table_access_method = 'btree';
+ERROR: access method "btree" is not of type TABLE
+-- Create a heap2 table am handler with heapam handler
+CREATE ACCESS METHOD heap2 TYPE TABLE HANDLER heap_tableam_handler;
+-- Verify return type checks for handlers
+CREATE ACCESS METHOD bogus TYPE TABLE HANDLER int4in;
+ERROR: function int4in(internal) does not exist
+CREATE ACCESS METHOD bogus TYPE TABLE HANDLER bthandler;
+ERROR: function bthandler must return type table_am_handler
+SELECT amname, amhandler, amtype FROM pg_am where amtype = 't' ORDER BY 1, 2;
+ amname | amhandler | amtype
+--------+----------------------+--------
+ heap | heap_tableam_handler | t
+ heap2 | heap_tableam_handler | t
+(2 rows)
+
+-- First create tables employing the new AM using USING
+-- plain CREATE TABLE
+CREATE TABLE tableam_tbl_heap2(f1 int) USING heap2;
+INSERT INTO tableam_tbl_heap2 VALUES(1);
+SELECT f1 FROM tableam_tbl_heap2 ORDER BY f1;
+ f1
+----
+ 1
+(1 row)
+
+-- CREATE TABLE AS
+CREATE TABLE tableam_tblas_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
+SELECT f1 FROM tableam_tbl_heap2 ORDER BY f1;
+ f1
+----
+ 1
+(1 row)
+
+-- SELECT INTO doesn't support USING
+SELECT INTO tableam_tblselectinto_heap2 USING heap2 FROM tableam_tbl_heap2;
+ERROR: syntax error at or near "USING"
+LINE 1: SELECT INTO tableam_tblselectinto_heap2 USING heap2 FROM tab...
+ ^
+-- CREATE VIEW doesn't support USING
+CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
+ERROR: syntax error at or near "USING"
+LINE 1: CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM ...
+ ^
+-- CREATE SEQUENCE doesn't support USING
+CREATE SEQUENCE tableam_seq_heap2 USING heap2;
+ERROR: syntax error at or near "USING"
+LINE 1: CREATE SEQUENCE tableam_seq_heap2 USING heap2;
+ ^
+-- CREATE MATERIALIZED VIEW does support USING
+CREATE MATERIALIZED VIEW tableam_tblmv_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
+SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1;
+ f1
+----
+ 1
+(1 row)
+
+-- CREATE TABLE .. PARTITION BY doesn't not support USING
+CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a) USING heap2;
+ERROR: specifying a table access method is not supported on a partitioned table
+CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a);
+-- new partitions will inherit from the current default, rather the partition root
+SET default_table_access_method = 'heap';
+CREATE TABLE tableam_parted_a_heap2 PARTITION OF tableam_parted_heap2 FOR VALUES IN ('a');
+SET default_table_access_method = 'heap2';
+CREATE TABLE tableam_parted_b_heap2 PARTITION OF tableam_parted_heap2 FOR VALUES IN ('b');
+RESET default_table_access_method;
+-- but the method can be explicitly specified
+CREATE TABLE tableam_parted_c_heap2 PARTITION OF tableam_parted_heap2 FOR VALUES IN ('c') USING heap;
+CREATE TABLE tableam_parted_d_heap2 PARTITION OF tableam_parted_heap2 FOR VALUES IN ('d') USING heap2;
+-- List all objects in AM
+SELECT
+ pc.relkind,
+ pa.amname,
+ CASE WHEN relkind = 't' THEN
+ (SELECT 'toast for ' || relname::regclass FROM pg_class pcm WHERE pcm.reltoastrelid = pc.oid)
+ ELSE
+ relname::regclass::text
+ END COLLATE "C" AS relname
+FROM pg_class AS pc,
+ pg_am AS pa
+WHERE pa.oid = pc.relam
+ AND pa.amname = 'heap2'
+ORDER BY 3, 1, 2;
+ relkind | amname | relname
+---------+--------+----------------------------------
+ r | heap2 | tableam_parted_b_heap2
+ r | heap2 | tableam_parted_d_heap2
+ r | heap2 | tableam_tbl_heap2
+ r | heap2 | tableam_tblas_heap2
+ m | heap2 | tableam_tblmv_heap2
+ t | heap2 | toast for tableam_parted_b_heap2
+ t | heap2 | toast for tableam_parted_d_heap2
+(7 rows)
+
+-- Show dependencies onto AM - there shouldn't be any for toast
+SELECT pg_describe_object(classid,objid,objsubid) AS obj
+FROM pg_depend, pg_am
+WHERE pg_depend.refclassid = 'pg_am'::regclass
+ AND pg_am.oid = pg_depend.refobjid
+ AND pg_am.amname = 'heap2'
+ORDER BY classid, objid, objsubid;
+ obj
+---------------------------------------
+ table tableam_tbl_heap2
+ table tableam_tblas_heap2
+ materialized view tableam_tblmv_heap2
+ table tableam_parted_b_heap2
+ table tableam_parted_d_heap2
+(5 rows)
+
+-- Second, create objects in the new AM by changing the default AM
+BEGIN;
+SET LOCAL default_table_access_method = 'heap2';
+-- following tests should all respect the default AM
+CREATE TABLE tableam_tbl_heapx(f1 int);
+CREATE TABLE tableam_tblas_heapx AS SELECT * FROM tableam_tbl_heapx;
+SELECT INTO tableam_tblselectinto_heapx FROM tableam_tbl_heapx;
+CREATE MATERIALIZED VIEW tableam_tblmv_heapx USING heap2 AS SELECT * FROM tableam_tbl_heapx;
+CREATE TABLE tableam_parted_heapx (a text, b int) PARTITION BY list (a);
+CREATE TABLE tableam_parted_1_heapx PARTITION OF tableam_parted_heapx FOR VALUES IN ('a', 'b');
+-- but an explicitly set AM overrides it
+CREATE TABLE tableam_parted_2_heapx PARTITION OF tableam_parted_heapx FOR VALUES IN ('c', 'd') USING heap;
+-- sequences, views and foreign servers shouldn't have an AM
+CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx;
+CREATE SEQUENCE tableam_seq_heapx;
+CREATE FOREIGN DATA WRAPPER fdw_heap2 VALIDATOR postgresql_fdw_validator;
+CREATE SERVER fs_heap2 FOREIGN DATA WRAPPER fdw_heap2 ;
+CREATE FOREIGN table tableam_fdw_heapx () SERVER fs_heap2;
+-- Verify that new AM was used for tables, matviews, but not for sequences, views and fdws
+SELECT
+ pc.relkind,
+ pa.amname,
+ CASE WHEN relkind = 't' THEN
+ (SELECT 'toast for ' || relname::regclass FROM pg_class pcm WHERE pcm.reltoastrelid = pc.oid)
+ ELSE
+ relname::regclass::text
+ END COLLATE "C" AS relname
+FROM pg_class AS pc
+ LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam)
+WHERE pc.relname LIKE 'tableam_%_heapx'
+ORDER BY 3, 1, 2;
+ relkind | amname | relname
+---------+--------+-----------------------------
+ f | | tableam_fdw_heapx
+ r | heap2 | tableam_parted_1_heapx
+ r | heap | tableam_parted_2_heapx
+ p | | tableam_parted_heapx
+ S | | tableam_seq_heapx
+ r | heap2 | tableam_tbl_heapx
+ r | heap2 | tableam_tblas_heapx
+ m | heap2 | tableam_tblmv_heapx
+ r | heap2 | tableam_tblselectinto_heapx
+ v | | tableam_view_heapx
+(10 rows)
+
+-- don't want to keep those tables, nor the default
+ROLLBACK;
+-- Third, check that we can neither create a table using a nonexistent
+-- AM, nor using an index AM
+CREATE TABLE i_am_a_failure() USING "";
+ERROR: zero-length delimited identifier at or near """"
+LINE 1: CREATE TABLE i_am_a_failure() USING "";
+ ^
+CREATE TABLE i_am_a_failure() USING i_do_not_exist_am;
+ERROR: access method "i_do_not_exist_am" does not exist
+CREATE TABLE i_am_a_failure() USING "I do not exist AM";
+ERROR: access method "I do not exist AM" does not exist
+CREATE TABLE i_am_a_failure() USING "btree";
+ERROR: access method "btree" is not of type TABLE
+-- Drop table access method, which fails as objects depends on it
+DROP ACCESS METHOD heap2;
+ERROR: cannot drop access method heap2 because other objects depend on it
+DETAIL: table tableam_tbl_heap2 depends on access method heap2
+table tableam_tblas_heap2 depends on access method heap2
+materialized view tableam_tblmv_heap2 depends on access method heap2
+table tableam_parted_b_heap2 depends on access method heap2
+table tableam_parted_d_heap2 depends on access method heap2
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- we intentionally leave the objects created above alive, to verify pg_dump support
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
new file mode 100644
index 0000000..88f94a6
--- /dev/null
+++ b/src/test/regress/expected/create_cast.out
@@ -0,0 +1,74 @@
+--
+-- CREATE_CAST
+--
+-- Create some types to test with
+CREATE TYPE casttesttype;
+CREATE FUNCTION casttesttype_in(cstring)
+ RETURNS casttesttype
+ AS 'textin'
+ LANGUAGE internal STRICT IMMUTABLE;
+NOTICE: return type casttesttype is only a shell
+CREATE FUNCTION casttesttype_out(casttesttype)
+ RETURNS cstring
+ AS 'textout'
+ LANGUAGE internal STRICT IMMUTABLE;
+NOTICE: argument type casttesttype is only a shell
+CREATE TYPE casttesttype (
+ internallength = variable,
+ input = casttesttype_in,
+ output = casttesttype_out,
+ alignment = int4
+);
+-- a dummy function to test with
+CREATE FUNCTION casttestfunc(casttesttype) RETURNS int4 LANGUAGE SQL AS
+$$ SELECT 1; $$;
+SELECT casttestfunc('foo'::text); -- fails, as there's no cast
+ERROR: function casttestfunc(text) does not exist
+LINE 1: SELECT casttestfunc('foo'::text);
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+-- Try binary coercion cast
+CREATE CAST (text AS casttesttype) WITHOUT FUNCTION;
+SELECT casttestfunc('foo'::text); -- doesn't work, as the cast is explicit
+ERROR: function casttestfunc(text) does not exist
+LINE 1: SELECT casttestfunc('foo'::text);
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT casttestfunc('foo'::text::casttesttype); -- should work
+ casttestfunc
+--------------
+ 1
+(1 row)
+
+DROP CAST (text AS casttesttype); -- cleanup
+-- Try IMPLICIT binary coercion cast
+CREATE CAST (text AS casttesttype) WITHOUT FUNCTION AS IMPLICIT;
+SELECT casttestfunc('foo'::text); -- Should work now
+ casttestfunc
+--------------
+ 1
+(1 row)
+
+-- Try I/O conversion cast.
+SELECT 1234::int4::casttesttype; -- No cast yet, should fail
+ERROR: cannot cast type integer to casttesttype
+LINE 1: SELECT 1234::int4::casttesttype;
+ ^
+CREATE CAST (int4 AS casttesttype) WITH INOUT;
+SELECT 1234::int4::casttesttype; -- Should work now
+ casttesttype
+--------------
+ 1234
+(1 row)
+
+DROP CAST (int4 AS casttesttype);
+-- Try cast with a function
+CREATE FUNCTION int4_casttesttype(int4) RETURNS casttesttype LANGUAGE SQL AS
+$$ SELECT ('foo'::text || $1::text)::casttesttype; $$;
+CREATE CAST (int4 AS casttesttype) WITH FUNCTION int4_casttesttype(int4) AS IMPLICIT;
+SELECT 1234::int4::casttesttype; -- Should work now
+ casttesttype
+--------------
+ foo1234
+(1 row)
+
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
new file mode 100644
index 0000000..3a4fd45
--- /dev/null
+++ b/src/test/regress/expected/create_function_3.out
@@ -0,0 +1,702 @@
+--
+-- CREATE FUNCTION
+--
+-- 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;
+ proname | prorettype | proargtypes
+--------------+------------+-------------------
+ functest_a_1 | boolean | [0:1]={text,date}
+ functest_a_2 | integer | [0:0]={text[]}
+ functest_a_3 | boolean | {}
+(3 rows)
+
+SELECT functest_A_1('abcd', '2020-01-01');
+ functest_a_1
+--------------
+ t
+(1 row)
+
+SELECT functest_A_2(ARRAY['1', '2', '3']);
+ functest_a_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_A_3();
+ functest_a_3
+--------------
+ f
+(1 row)
+
+--
+-- 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;
+ proname | provolatile
+--------------+-------------
+ functest_b_1 | v
+ functest_b_2 | i
+ functest_b_3 | s
+ functest_b_4 | v
+(4 rows)
+
+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;
+ proname | provolatile
+--------------+-------------
+ functest_b_1 | v
+ functest_b_2 | v
+ functest_b_3 | s
+ functest_b_4 | v
+(4 rows)
+
+--
+-- 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;
+ proname | prosecdef
+--------------+-----------
+ functest_c_1 | f
+ functest_c_2 | t
+ functest_c_3 | f
+(3 rows)
+
+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;
+ proname | prosecdef
+--------------+-----------
+ functest_c_1 | f
+ functest_c_2 | f
+ functest_c_3 | t
+(3 rows)
+
+--
+-- 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;
+ proname | proleakproof
+--------------+--------------
+ functest_e_1 | f
+ functest_e_2 | t
+(2 rows)
+
+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;
+ proname | proleakproof
+--------------+--------------
+ functest_e_1 | t
+ functest_e_2 | t
+(2 rows)
+
+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;
+ proname | proleakproof
+--------------+--------------
+ functest_e_1 | t
+ functest_e_2 | f
+(2 rows)
+
+-- 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;
+ERROR: only superuser can define a leakproof function
+CREATE FUNCTION functest_E_3(int) RETURNS bool LANGUAGE 'sql'
+ LEAKPROOF AS 'SELECT $1 < 200'; -- fail
+ERROR: only superuser can define a leakproof function
+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;
+ proname | proisstrict
+--------------+-------------
+ functest_f_1 | f
+ functest_f_2 | f
+ functest_f_3 | t
+ functest_f_4 | t
+(4 rows)
+
+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;
+ proname | proisstrict
+--------------+-------------
+ functest_f_1 | f
+ functest_f_2 | t
+ functest_f_3 | f
+ functest_f_4 | t
+(4 rows)
+
+-- pg_get_functiondef tests
+SELECT pg_get_functiondef('functest_A_1'::regproc);
+ pg_get_functiondef
+--------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_a_1(text, date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ AS $function$SELECT $1 = 'abcd' AND $2 > '2001-01-01'$function$ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_B_3'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_b_3(integer)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ STABLE +
+ AS $function$SELECT $1 = 0$function$ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_C_3'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_c_3(integer)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ SECURITY DEFINER +
+ AS $function$SELECT $1 < 0$function$ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_F_2'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_f_2(integer)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ STRICT +
+ AS $function$SELECT $1 = 50$function$ +
+
+(1 row)
+
+--
+-- 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 argments 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;
+ERROR: duplicate function body specified
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+-- check reporting of parse-analysis errors
+CREATE FUNCTION functest_S_xx(x date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN x > 1;
+ERROR: operator does not exist: date > integer
+LINE 3: RETURN x > 1;
+ ^
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+-- 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');
+ functest_s_1
+--------------
+ t
+(1 row)
+
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+ functest_s_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_S_3();
+ functest_s_3
+--------------
+ f
+(1 row)
+
+SELECT functest_S_10('abcd', '2020-01-01');
+ functest_s_10
+---------------
+ t
+(1 row)
+
+SELECT functest_S_13();
+ functest_s_13
+---------------
+ f
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_1(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_2(a text[])+
+ RETURNS integer +
+ LANGUAGE sql +
+ RETURN ((a)[1])::integer +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+ pg_get_functiondef
+----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN false +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3a()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ RETURN false; +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_10(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT ((a = 'abcd'::text) AND (b > '01-01-2001'::date)); +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_13()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT 1; +
+ SELECT false AS bool; +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_15'::regproc);
+ pg_get_functiondef
+--------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_15(x integer)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT +
+ CASE +
+ WHEN ((x % 2) = 0) THEN true +
+ ELSE false +
+ END AS "case"; +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_16'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_16(a integer, b integer)+
+ RETURNS void +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO functest1 (i) SELECT (functest_s_16.a + functest_s_16.b); +
+ END +
+
+(1 row)
+
+DROP TABLE functest1 CASCADE;
+NOTICE: drop cascades to function functest_s_16(integer,integer)
+-- 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();
+ functest_s_14
+---------------
+ 2
+(1 row)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view functestv3
+drop cascades to function functest_s_14()
+-- 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;
+ routine_name | ordinal_position | parameter_name | parameter_default
+---------------+------------------+----------------+-------------------
+ functest_is_1 | 1 | a |
+ functest_is_1 | 2 | b | 1
+ functest_is_1 | 3 | c | 'foo'::text
+ functest_is_2 | 1 | a |
+ functest_is_2 | 2 | b | 1
+ functest_is_3 | 1 | a | 1
+ functest_is_3 | 2 | b |
+(7 rows)
+
+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;
+ routine_name | routine_name
+----------------+----------------
+ functest_is_4b | functest_is_4a
+(1 row)
+
+SELECT routine_name, sequence_name FROM information_schema.routine_sequence_usage
+ WHERE routine_schema = 'temp_func_test'
+ ORDER BY 1, 2;
+ routine_name | sequence_name
+---------------+---------------
+ functest_is_5 | functest1
+ functest_is_6 | functest1
+(2 rows)
+
+SELECT routine_name, table_name, column_name FROM information_schema.routine_column_usage
+ WHERE routine_schema = 'temp_func_test'
+ ORDER BY 1, 2;
+ routine_name | table_name | column_name
+---------------+------------+-------------
+ functest_is_7 | functest2 | a
+(1 row)
+
+SELECT routine_name, table_name FROM information_schema.routine_table_usage
+ WHERE routine_schema = 'temp_func_test'
+ ORDER BY 1, 2;
+ routine_name | table_name
+---------------+------------
+ functest_is_7 | functest2
+(1 row)
+
+DROP FUNCTION functest_IS_4a CASCADE;
+NOTICE: drop cascades to function functest_is_4b(integer)
+DROP SEQUENCE functest1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to function functest_is_5(integer)
+drop cascades to function functest_is_6()
+DROP TABLE functest2 CASCADE;
+NOTICE: drop cascades to function functest_is_7()
+-- 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
+ERROR: could not find a function named "functest_b_1"
+DROP FUNCTION functest_b_2; -- error, ambiguous
+ERROR: function name "functest_b_2" is not unique
+HINT: Specify the argument list to select the function unambiguously.
+-- 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';
+ERROR: cannot change routine kind
+DETAIL: "functest1" is a function.
+CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
+ERROR: cannot change routine kind
+DETAIL: "functest1" is a function.
+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();
+ functest_sri1
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+ QUERY PLAN
+--------------------------------------
+ Seq Scan on temp_func_test.functest3
+ Output: functest3.a
+(2 rows)
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ SELECT * FROM functest3;
+END;
+SELECT * FROM functest_sri2();
+ functest_sri2
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+ QUERY PLAN
+--------------------------------------
+ Seq Scan on temp_func_test.functest3
+ Output: functest3.a
+(2 rows)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to function functest_sri2()
+-- Check behavior of VOID-returning SQL functions
+CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
+$$ SELECT a + 1 $$;
+SELECT voidtest1(42);
+ voidtest1
+-----------
+
+(1 row)
+
+CREATE FUNCTION voidtest2(a int, b int) RETURNS VOID LANGUAGE SQL AS
+$$ SELECT voidtest1(a + b) $$;
+SELECT voidtest2(11,22);
+ voidtest2
+-----------
+
+(1 row)
+
+-- currently, we can inline voidtest2 but not voidtest1
+EXPLAIN (verbose, costs off) SELECT voidtest2(11,22);
+ QUERY PLAN
+-------------------------
+ Result
+ Output: voidtest1(33)
+(2 rows)
+
+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);
+ voidtest3
+-----------
+
+(1 row)
+
+CREATE FUNCTION voidtest4(a int) RETURNS VOID LANGUAGE SQL AS
+$$ INSERT INTO sometable VALUES(a - 1) RETURNING f1 $$;
+SELECT voidtest4(39);
+ voidtest4
+-----------
+
+(1 row)
+
+TABLE sometable;
+ f1
+----
+ 18
+ 38
+(2 rows)
+
+CREATE FUNCTION voidtest5(a int) RETURNS SETOF VOID LANGUAGE SQL AS
+$$ SELECT generate_series(1, a) $$ STABLE;
+SELECT * FROM voidtest5(3);
+ voidtest5
+-----------
+(0 rows)
+
+-- Cleanup
+DROP SCHEMA temp_func_test CASCADE;
+NOTICE: drop cascades to 29 other objects
+DETAIL: drop cascades to function functest_a_1(text,date)
+drop cascades to function functest_a_2(text[])
+drop cascades to function functest_a_3()
+drop cascades to function functest_b_2(integer)
+drop cascades to function functest_b_3(integer)
+drop cascades to function functest_b_4(integer)
+drop cascades to function functest_c_1(integer)
+drop cascades to function functest_c_2(integer)
+drop cascades to function functest_c_3(integer)
+drop cascades to function functest_e_1(integer)
+drop cascades to function functest_e_2(integer)
+drop cascades to function functest_f_1(integer)
+drop cascades to function functest_f_2(integer)
+drop cascades to function functest_f_3(integer)
+drop cascades to function functest_f_4(integer)
+drop cascades to function functest_s_1(text,date)
+drop cascades to function functest_s_2(text[])
+drop cascades to function functest_s_3()
+drop cascades to function functest_s_3a()
+drop cascades to function functest_s_10(text,date)
+drop cascades to function functest_s_13()
+drop cascades to function functest_s_15(integer)
+drop cascades to function functest_b_2(bigint)
+drop cascades to function functest_sri1()
+drop cascades to function voidtest1(integer)
+drop cascades to function voidtest2(integer,integer)
+drop cascades to function voidtest3(integer)
+drop cascades to function voidtest4(integer)
+drop cascades to function voidtest5(integer)
+DROP USER regress_unpriv_user;
+RESET search_path;
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
new file mode 100644
index 0000000..88d11de
--- /dev/null
+++ b/src/test/regress/expected/create_index.out
@@ -0,0 +1,2845 @@
+--
+-- CREATE_INDEX
+-- Create ancillary data structures (i.e. indices)
+--
+--
+-- BTREE
+--
+CREATE INDEX onek_unique1 ON onek USING btree(unique1 int4_ops);
+CREATE INDEX IF NOT EXISTS onek_unique1 ON onek USING btree(unique1 int4_ops);
+NOTICE: relation "onek_unique1" already exists, skipping
+CREATE INDEX IF NOT EXISTS ON onek USING btree(unique1 int4_ops);
+ERROR: syntax error at or near "ON"
+LINE 1: CREATE INDEX IF NOT EXISTS ON onek USING btree(unique1 int4_...
+ ^
+CREATE INDEX onek_unique2 ON onek USING btree(unique2 int4_ops);
+CREATE INDEX onek_hundred ON onek USING btree(hundred int4_ops);
+CREATE INDEX onek_stringu1 ON onek USING btree(stringu1 name_ops);
+CREATE INDEX tenk1_unique1 ON tenk1 USING btree(unique1 int4_ops);
+CREATE INDEX tenk1_unique2 ON tenk1 USING btree(unique2 int4_ops);
+CREATE INDEX tenk1_hundred ON tenk1 USING btree(hundred int4_ops);
+CREATE INDEX tenk1_thous_tenthous ON tenk1 (thousand, tenthous);
+CREATE INDEX tenk2_unique1 ON tenk2 USING btree(unique1 int4_ops);
+CREATE INDEX tenk2_unique2 ON tenk2 USING btree(unique2 int4_ops);
+CREATE INDEX tenk2_hundred ON tenk2 USING btree(hundred int4_ops);
+CREATE INDEX rix ON road USING btree (name text_ops);
+CREATE INDEX iix ON ihighway USING btree (name text_ops);
+CREATE INDEX six ON shighway USING btree (name text_ops);
+-- test comments
+COMMENT ON INDEX six_wrong IS 'bad index';
+ERROR: relation "six_wrong" does not exist
+COMMENT ON INDEX six IS 'good index';
+COMMENT ON INDEX six IS NULL;
+--
+-- BTREE ascending/descending cases
+--
+-- we load int4/text from pure descending data (each key is a new
+-- low key) and name/f8 from pure ascending data (each key is a new
+-- high key). we had a bug where new low keys would sometimes be
+-- "lost".
+--
+CREATE INDEX bt_i4_index ON bt_i4_heap USING btree (seqno int4_ops);
+CREATE INDEX bt_name_index ON bt_name_heap USING btree (seqno name_ops);
+CREATE INDEX bt_txt_index ON bt_txt_heap USING btree (seqno text_ops);
+CREATE INDEX bt_f8_index ON bt_f8_heap USING btree (seqno float8_ops);
+--
+-- BTREE partial indices
+--
+CREATE INDEX onek2_u1_prtl ON onek2 USING btree(unique1 int4_ops)
+ where unique1 < 20 or unique1 > 980;
+CREATE INDEX onek2_u2_prtl ON onek2 USING btree(unique2 int4_ops)
+ where stringu1 < 'B';
+CREATE INDEX onek2_stu1_prtl ON onek2 USING btree(stringu1 name_ops)
+ where onek2.stringu1 >= 'J' and onek2.stringu1 < 'K';
+--
+-- GiST (rtree-equivalent opclasses only)
+--
+CREATE INDEX grect2ind ON fast_emp4000 USING gist (home_base);
+CREATE INDEX gpolygonind ON polygon_tbl USING gist (f1);
+CREATE INDEX gcircleind ON circle_tbl USING gist (f1);
+INSERT INTO POINT_TBL(f1) VALUES (NULL);
+CREATE INDEX gpointind ON point_tbl USING gist (f1);
+CREATE TEMP TABLE gpolygon_tbl AS
+ SELECT polygon(home_base) AS f1 FROM slow_emp4000;
+INSERT INTO gpolygon_tbl VALUES ( '(1000,0,0,1000)' );
+INSERT INTO gpolygon_tbl VALUES ( '(0,1000,1000,1000)' );
+CREATE TEMP TABLE gcircle_tbl AS
+ SELECT circle(home_base) AS f1 FROM slow_emp4000;
+CREATE INDEX ggpolygonind ON gpolygon_tbl USING gist (f1);
+CREATE INDEX ggcircleind ON gcircle_tbl USING gist (f1);
+--
+-- Test GiST indexes
+--
+-- get non-indexed results for comparison purposes
+SET enable_seqscan = ON;
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = OFF;
+SELECT * FROM fast_emp4000
+ WHERE home_base <@ '(200,200),(2000,1000)'::box
+ ORDER BY (home_base[0])[0];
+ home_base
+-----------------------
+ (337,455),(240,359)
+ (1444,403),(1346,344)
+(2 rows)
+
+SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
+ count
+-------
+ 278
+(1 row)
+
+SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon
+ ORDER BY (poly_center(f1))[0];
+ f1
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT * FROM circle_tbl WHERE f1 && circle(point(1,-2), 1)
+ ORDER BY area(f1);
+ f1
+---------------
+ <(1,2),3>
+ <(1,3),5>
+ <(1,2),100>
+ <(100,1),115>
+(4 rows)
+
+SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon;
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle;
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM point_tbl WHERE f1 <@ box '(0,0,100,100)';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1;
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
+ count
+-------
+ 5
+(1 row)
+
+SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)';
+ count
+-------
+ 4
+(1 row)
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 <<| '(0.0, 0.0)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 |>> '(0.0, 0.0)';
+ count
+-------
+ 5
+(1 row)
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
+ f1
+-------------------
+ (0,0)
+ (1e-300,-1e-300)
+ (-3,4)
+ (-10,0)
+ (10,10)
+ (-5,-12)
+ (5.1,34.5)
+ (Infinity,1e+300)
+ (1e+300,Infinity)
+ (NaN,NaN)
+
+(11 rows)
+
+SELECT * FROM point_tbl WHERE f1 IS NULL;
+ f1
+----
+
+(1 row)
+
+SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
+ f1
+-------------------
+ (0,0)
+ (1e-300,-1e-300)
+ (-3,4)
+ (-10,0)
+ (10,10)
+ (-5,-12)
+ (5.1,34.5)
+ (1e+300,Infinity)
+ (Infinity,1e+300)
+ (NaN,NaN)
+(10 rows)
+
+SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
+ f1
+------------------
+ (0,0)
+ (1e-300,-1e-300)
+ (-3,4)
+ (-10,0)
+ (10,10)
+(5 rows)
+
+SELECT * FROM gpolygon_tbl ORDER BY f1 <-> '(0,0)'::point LIMIT 10;
+ f1
+-------------------------------------------------
+ ((240,359),(240,455),(337,455),(337,359))
+ ((662,163),(662,187),(759,187),(759,163))
+ ((1000,0),(0,1000))
+ ((0,1000),(1000,1000))
+ ((1346,344),(1346,403),(1444,403),(1444,344))
+ ((278,1409),(278,1457),(369,1457),(369,1409))
+ ((907,1156),(907,1201),(948,1201),(948,1156))
+ ((1517,971),(1517,1043),(1594,1043),(1594,971))
+ ((175,1820),(175,1850),(259,1850),(259,1820))
+ ((2424,81),(2424,160),(2424,160),(2424,81))
+(10 rows)
+
+SELECT circle_center(f1), round(radius(f1)) as radius FROM gcircle_tbl ORDER BY f1 <-> '(200,300)'::point LIMIT 10;
+ circle_center | radius
+----------------+--------
+ (288.5,407) | 68
+ (710.5,175) | 50
+ (323.5,1433) | 51
+ (927.5,1178.5) | 30
+ (1395,373.5) | 57
+ (1555.5,1007) | 53
+ (217,1835) | 45
+ (489,2421.5) | 22
+ (2424,120.5) | 40
+ (751.5,2655) | 20
+(10 rows)
+
+-- Now check the results from plain indexscan
+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];
+ QUERY PLAN
+-----------------------------------------------------------------
+ Sort
+ Sort Key: ((home_base[0])[0])
+ -> Index Only Scan using grect2ind on fast_emp4000
+ Index Cond: (home_base <@ '(2000,1000),(200,200)'::box)
+(4 rows)
+
+SELECT * FROM fast_emp4000
+ WHERE home_base <@ '(200,200),(2000,1000)'::box
+ ORDER BY (home_base[0])[0];
+ home_base
+-----------------------
+ (337,455),(240,359)
+ (1444,403),(1346,344)
+(2 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
+ QUERY PLAN
+-------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using grect2ind on fast_emp4000
+ Index Cond: (home_base && '(1000,1000),(0,0)'::box)
+(3 rows)
+
+SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
+ count
+-------
+ 2
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using grect2ind on fast_emp4000
+ Index Cond: (home_base IS NULL)
+(3 rows)
+
+SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
+ count
+-------
+ 278
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon
+ ORDER BY (poly_center(f1))[0];
+ QUERY PLAN
+------------------------------------------------------------
+ Sort
+ Sort Key: ((poly_center(f1))[0])
+ -> Index Scan using gpolygonind on polygon_tbl
+ Index Cond: (f1 @> '((1,1),(2,2),(2,1))'::polygon)
+(4 rows)
+
+SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon
+ ORDER BY (poly_center(f1))[0];
+ f1
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM circle_tbl WHERE f1 && circle(point(1,-2), 1)
+ ORDER BY area(f1);
+ QUERY PLAN
+--------------------------------------------------
+ Sort
+ Sort Key: (area(f1))
+ -> Index Scan using gcircleind on circle_tbl
+ Index Cond: (f1 && '<(1,-2),1>'::circle)
+(4 rows)
+
+SELECT * FROM circle_tbl WHERE f1 && circle(point(1,-2), 1)
+ ORDER BY area(f1);
+ f1
+---------------
+ <(1,2),3>
+ <(1,3),5>
+ <(1,2),100>
+ <(100,1),115>
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon;
+ QUERY PLAN
+------------------------------------------------------------
+ Aggregate
+ -> Index Scan using ggpolygonind on gpolygon_tbl
+ Index Cond: (f1 && '((1000,1000),(0,0))'::polygon)
+(3 rows)
+
+SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon;
+ count
+-------
+ 2
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle;
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Index Scan using ggcircleind on gcircle_tbl
+ Index Cond: (f1 && '<(500,500),500>'::circle)
+(3 rows)
+
+SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle;
+ count
+-------
+ 2
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl WHERE f1 <@ box '(0,0,100,100)';
+ QUERY PLAN
+----------------------------------------------------
+ Aggregate
+ -> Index Only Scan using gpointind on point_tbl
+ Index Cond: (f1 <@ '(100,100),(0,0)'::box)
+(3 rows)
+
+SELECT count(*) FROM point_tbl WHERE f1 <@ box '(0,0,100,100)';
+ count
+-------
+ 3
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1;
+ QUERY PLAN
+----------------------------------------------------
+ Aggregate
+ -> Index Only Scan using gpointind on point_tbl
+ Index Cond: (f1 <@ '(100,100),(0,0)'::box)
+(3 rows)
+
+SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1;
+ count
+-------
+ 3
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
+ QUERY PLAN
+----------------------------------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using gpointind on point_tbl
+ Index Cond: (f1 <@ '((0,0),(0,100),(100,100),(50,50),(100,0),(0,0))'::polygon)
+(3 rows)
+
+SELECT count(*) FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
+ count
+-------
+ 4
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>';
+ QUERY PLAN
+----------------------------------------------------
+ Aggregate
+ -> Index Only Scan using gpointind on point_tbl
+ Index Cond: (f1 <@ '<(50,50),50>'::circle)
+(3 rows)
+
+SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)';
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using gpointind on point_tbl p
+ Index Cond: (f1 << '(0,0)'::point)
+(3 rows)
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)';
+ count
+-------
+ 3
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)';
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using gpointind on point_tbl p
+ Index Cond: (f1 >> '(0,0)'::point)
+(3 rows)
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)';
+ count
+-------
+ 4
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl p WHERE p.f1 <<| '(0.0, 0.0)';
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using gpointind on point_tbl p
+ Index Cond: (f1 <<| '(0,0)'::point)
+(3 rows)
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 <<| '(0.0, 0.0)';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl p WHERE p.f1 |>> '(0.0, 0.0)';
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using gpointind on point_tbl p
+ Index Cond: (f1 |>> '(0,0)'::point)
+(3 rows)
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 |>> '(0.0, 0.0)';
+ count
+-------
+ 5
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using gpointind on point_tbl p
+ Index Cond: (f1 ~= '(-5,-12)'::point)
+(3 rows)
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using gpointind on point_tbl
+ Order By: (f1 <-> '(0,1)'::point)
+(2 rows)
+
+SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
+ f1
+-------------------
+ (1e-300,-1e-300)
+ (0,0)
+ (-3,4)
+ (-10,0)
+ (10,10)
+ (-5,-12)
+ (5.1,34.5)
+ (Infinity,1e+300)
+ (1e+300,Infinity)
+ (NaN,NaN)
+
+(11 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM point_tbl WHERE f1 IS NULL;
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using gpointind on point_tbl
+ Index Cond: (f1 IS NULL)
+(2 rows)
+
+SELECT * FROM point_tbl WHERE f1 IS NULL;
+ f1
+----
+
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using gpointind on point_tbl
+ Index Cond: (f1 IS NOT NULL)
+ Order By: (f1 <-> '(0,1)'::point)
+(3 rows)
+
+SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
+ f1
+-------------------
+ (1e-300,-1e-300)
+ (0,0)
+ (-3,4)
+ (-10,0)
+ (10,10)
+ (-5,-12)
+ (5.1,34.5)
+ (Infinity,1e+300)
+ (1e+300,Infinity)
+ (NaN,NaN)
+(10 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
+ QUERY PLAN
+------------------------------------------------
+ Index Only Scan using gpointind on point_tbl
+ Index Cond: (f1 <@ '(10,10),(-10,-10)'::box)
+ Order By: (f1 <-> '(0,1)'::point)
+(3 rows)
+
+SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
+ f1
+------------------
+ (1e-300,-1e-300)
+ (0,0)
+ (-3,4)
+ (-10,0)
+ (10,10)
+(5 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM gpolygon_tbl ORDER BY f1 <-> '(0,0)'::point LIMIT 10;
+ QUERY PLAN
+-----------------------------------------------------
+ Limit
+ -> Index Scan using ggpolygonind on gpolygon_tbl
+ Order By: (f1 <-> '(0,0)'::point)
+(3 rows)
+
+SELECT * FROM gpolygon_tbl ORDER BY f1 <-> '(0,0)'::point LIMIT 10;
+ f1
+-------------------------------------------------
+ ((240,359),(240,455),(337,455),(337,359))
+ ((662,163),(662,187),(759,187),(759,163))
+ ((1000,0),(0,1000))
+ ((0,1000),(1000,1000))
+ ((1346,344),(1346,403),(1444,403),(1444,344))
+ ((278,1409),(278,1457),(369,1457),(369,1409))
+ ((907,1156),(907,1201),(948,1201),(948,1156))
+ ((1517,971),(1517,1043),(1594,1043),(1594,971))
+ ((175,1820),(175,1850),(259,1850),(259,1820))
+ ((2424,81),(2424,160),(2424,160),(2424,81))
+(10 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT circle_center(f1), round(radius(f1)) as radius FROM gcircle_tbl ORDER BY f1 <-> '(200,300)'::point LIMIT 10;
+ QUERY PLAN
+---------------------------------------------------
+ Limit
+ -> Index Scan using ggcircleind on gcircle_tbl
+ Order By: (f1 <-> '(200,300)'::point)
+(3 rows)
+
+SELECT circle_center(f1), round(radius(f1)) as radius FROM gcircle_tbl ORDER BY f1 <-> '(200,300)'::point LIMIT 10;
+ circle_center | radius
+----------------+--------
+ (288.5,407) | 68
+ (710.5,175) | 50
+ (323.5,1433) | 51
+ (927.5,1178.5) | 30
+ (1395,373.5) | 57
+ (1555.5,1007) | 53
+ (217,1835) | 45
+ (489,2421.5) | 22
+ (2424,120.5) | 40
+ (751.5,2655) | 20
+(10 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT point(x,x), (SELECT f1 FROM gpolygon_tbl ORDER BY f1 <-> point(x,x) LIMIT 1) as c FROM generate_series(0,10,1) x;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------
+ Function Scan on generate_series x
+ SubPlan 1
+ -> Limit
+ -> Index Scan using ggpolygonind on gpolygon_tbl
+ Order By: (f1 <-> point((x.x)::double precision, (x.x)::double precision))
+(5 rows)
+
+SELECT point(x,x), (SELECT f1 FROM gpolygon_tbl ORDER BY f1 <-> point(x,x) LIMIT 1) as c FROM generate_series(0,10,1) x;
+ point | c
+---------+-------------------------------------------
+ (0,0) | ((240,359),(240,455),(337,455),(337,359))
+ (1,1) | ((240,359),(240,455),(337,455),(337,359))
+ (2,2) | ((240,359),(240,455),(337,455),(337,359))
+ (3,3) | ((240,359),(240,455),(337,455),(337,359))
+ (4,4) | ((240,359),(240,455),(337,455),(337,359))
+ (5,5) | ((240,359),(240,455),(337,455),(337,359))
+ (6,6) | ((240,359),(240,455),(337,455),(337,359))
+ (7,7) | ((240,359),(240,455),(337,455),(337,359))
+ (8,8) | ((240,359),(240,455),(337,455),(337,359))
+ (9,9) | ((240,359),(240,455),(337,455),(337,359))
+ (10,10) | ((240,359),(240,455),(337,455),(337,359))
+(11 rows)
+
+-- Now check the results from bitmap indexscan
+SET enable_seqscan = OFF;
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = ON;
+EXPLAIN (COSTS OFF)
+SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
+ QUERY PLAN
+------------------------------------------------------------
+ Sort
+ Sort Key: ((f1 <-> '(0,1)'::point))
+ -> Bitmap Heap Scan on point_tbl
+ Recheck Cond: (f1 <@ '(10,10),(-10,-10)'::box)
+ -> Bitmap Index Scan on gpointind
+ Index Cond: (f1 <@ '(10,10),(-10,-10)'::box)
+(6 rows)
+
+SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
+ f1
+------------------
+ (0,0)
+ (1e-300,-1e-300)
+ (-3,4)
+ (-10,0)
+ (10,10)
+(5 rows)
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+--
+-- GIN over int[] and text[]
+--
+-- Note: GIN currently supports only bitmap scans, not plain indexscans
+--
+SET enable_seqscan = OFF;
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = ON;
+CREATE INDEX intarrayidx ON array_index_op_test USING gin (i);
+explain (costs off)
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+ QUERY PLAN
+----------------------------------------------------
+ Sort
+ Sort Key: seqno
+ -> Bitmap Heap Scan on array_index_op_test
+ Recheck Cond: (i @> '{32}'::integer[])
+ -> Bitmap Index Scan on intarrayidx
+ Index Cond: (i @> '{32}'::integer[])
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE i @> '{17}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_index_op_test WHERE i && '{17}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_index_op_test WHERE i @> '{32,17}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(3 rows)
+
+SELECT * FROM array_index_op_test WHERE i && '{32,17}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(11 rows)
+
+SELECT * FROM array_index_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------+----------------------------------------------------------------------------------------------------------------------------
+ 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 101 | {} | {}
+(4 rows)
+
+SELECT * FROM array_index_op_test WHERE i = '{47,77}' ORDER BY seqno;
+ seqno | i | t
+-------+---------+-----------------------------------------------------------------------------------------------------------------
+ 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483}
+(1 row)
+
+SELECT * FROM array_index_op_test WHERE i = '{}' ORDER BY seqno;
+ seqno | i | t
+-------+----+----
+ 101 | {} | {}
+(1 row)
+
+SELECT * FROM array_index_op_test WHERE i @> '{}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 1 | {92,75,71,52,64,83} | {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038}
+ 2 | {3,6} | {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793}
+ 3 | {37,64,95,43,3,41,13,30,11,43} | {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246}
+ 4 | {71,39,99,55,33,75,45} | {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557}
+ 5 | {50,42,77,50,4} | {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104}
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 7 | {12,51,88,64,8} | {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946}
+ 8 | {60,84} | {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407}
+ 9 | {56,52,35,27,80,44,81,22} | {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000}
+ 10 | {71,5,45} | {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249}
+ 11 | {41,86,74,48,22,74,47,50} | {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 13 | {3,52,34,23} | {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658}
+ 14 | {78,57,19} | {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 16 | {14,63,85,11} | {AAAAAA66777}
+ 17 | {7,10,81,85} | {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356}
+ 18 | {1} | {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 20 | {72,89,70,51,54,37,8,49,79} | {AAAAAA58494}
+ 21 | {2,8,65,10,5,79,43} | {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420}
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 23 | {40,90,5,38,72,40,30,10,43,55} | {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562}
+ 24 | {94,61,99,35,48} | {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219}
+ 25 | {31,1,10,11,27,79,38} | {AAAAAAAAAAAAAAAAAA59334,45449}
+ 26 | {71,10,9,69,75} | {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009}
+ 27 | {94} | {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254}
+ 28 | {14,33,6,34,14} | {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601}
+ 29 | {39,21} | {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194}
+ 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+ 31 | {80,24,18,21,54} | {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938}
+ 32 | {58,79,82,80,67,75,98,10,41} | {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533}
+ 33 | {74,73} | {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796}
+ 34 | {70,45} | {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242}
+ 35 | {23,40} | {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084}
+ 36 | {79,82,14,52,30,5,79} | {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598}
+ 37 | {53,11,81,39,3,78,58,64,74} | {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611}
+ 38 | {59,5,4,95,28} | {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387}
+ 39 | {82,43,99,16,74} | {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620}
+ 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623}
+ 41 | {19,26,63,12,93,73,27,94} | {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666}
+ 42 | {15,76,82,75,8,91} | {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587}
+ 43 | {39,87,91,97,79,28} | {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946}
+ 44 | {40,58,68,29,54} | {AAAAAAA81898,AAAAAA66777,AAAAAA98232}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 46 | {53,24} | {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621}
+ 47 | {98,23,64,12,75,61} | {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466}
+ 48 | {76,14} | {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037}
+ 49 | {56,5,54,37,49} | {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587}
+ 50 | {20,12,37,64,93} | {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955}
+ 51 | {47} | {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452}
+ 52 | {89,0} | {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 54 | {70,47} | {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322}
+ 55 | {47,79,47,64,72,25,71,24,93} | {AAAAAAAAAAAAAAAAAA55796,AAAAA62737}
+ 56 | {33,7,60,54,93,90,77,85,39} | {AAAAAAAAAAAAAAAAAA32918,AA42406}
+ 57 | {23,45,10,42,36,21,9,96} | {AAAAAAAAAAAAAAAAAAA70415}
+ 58 | {92} | {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119}
+ 59 | {9,69,46,77} | {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955}
+ 60 | {62,2,59,38,89} | {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875}
+ 61 | {72,2,44,95,54,54,13} | {AAAAAAAAAAAAAAAAAAA91804}
+ 62 | {83,72,29,73} | {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617}
+ 63 | {11,4,61,87} | {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938}
+ 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 66 | {31,23,70,52,4,33,48,25} | {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836}
+ 67 | {31,94,7,10} | {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946}
+ 68 | {90,43,38} | {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643}
+ 69 | {67,35,99,85,72,86,44} | {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955}
+ 70 | {56,70,83} | {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242}
+ 71 | {74,26} | {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 73 | {88,25,96,78,65,15,29,19} | {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 75 | {12,96,83,24,71,89,55} | {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052}
+ 76 | {92,55,10,7} | {AAAAAAAAAAAAAAA67062}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 78 | {55,89,44,84,34} | {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 80 | {74,89,44,80,0} | {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007}
+ 81 | {63,77,54,48,61,53,97} | {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121}
+ 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+ 83 | {14,10} | {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119}
+ 84 | {11,83,35,13,96,94} | {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183}
+ 85 | {39,60} | {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154}
+ 86 | {33,81,72,74,45,36,82} | {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176}
+ 87 | {57,27,50,12,97,68} | {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505}
+ 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 90 | {88,75} | {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526}
+ 91 | {78} | {AAAAAAAAAAAAA62007,AAA99043}
+ 92 | {85,63,49,45} | {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089}
+ 93 | {11} | {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383}
+ 94 | {98,9,85,62,88,91,60,61,38,86} | {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587}
+ 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+ 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 99 | {37,86} | {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+ 101 | {} | {}
+ 102 | {NULL} | {NULL}
+(102 rows)
+
+SELECT * FROM array_index_op_test WHERE i && '{}' ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_index_op_test WHERE i <@ '{}' ORDER BY seqno;
+ seqno | i | t
+-------+----+----
+ 101 | {} | {}
+(1 row)
+
+SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
+ seqno | i | t
+-------+--------+--------
+ 102 | {NULL} | {NULL}
+(1 row)
+
+SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
+ seqno | i | t
+-------+----+----
+ 101 | {} | {}
+(1 row)
+
+CREATE INDEX textarrayidx ON array_index_op_test USING gin (t);
+explain (costs off)
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+ QUERY PLAN
+------------------------------------------------------------
+ Sort
+ Sort Key: seqno
+ -> Bitmap Heap Scan on array_index_op_test
+ Recheck Cond: (t @> '{AAAAAAAA72908}'::text[])
+ -> Bitmap Index Scan on textarrayidx
+ Index Cond: (t @> '{AAAAAAAA72908}'::text[])
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+ seqno | i | t
+-------+------+--------------------------------------------------------------------
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(1 row)
+
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE t <@ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}' ORDER BY seqno;
+ seqno | i | t
+-------+--------------------+-----------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 101 | {} | {}
+(3 rows)
+
+SELECT * FROM array_index_op_test WHERE t = '{AAAAAAAAAA646,A87088}' ORDER BY seqno;
+ seqno | i | t
+-------+------------+------------------------
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(1 row)
+
+SELECT * FROM array_index_op_test WHERE t = '{}' ORDER BY seqno;
+ seqno | i | t
+-------+----+----
+ 101 | {} | {}
+(1 row)
+
+SELECT * FROM array_index_op_test WHERE t @> '{}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 1 | {92,75,71,52,64,83} | {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038}
+ 2 | {3,6} | {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793}
+ 3 | {37,64,95,43,3,41,13,30,11,43} | {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246}
+ 4 | {71,39,99,55,33,75,45} | {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557}
+ 5 | {50,42,77,50,4} | {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104}
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 7 | {12,51,88,64,8} | {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946}
+ 8 | {60,84} | {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407}
+ 9 | {56,52,35,27,80,44,81,22} | {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000}
+ 10 | {71,5,45} | {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249}
+ 11 | {41,86,74,48,22,74,47,50} | {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 13 | {3,52,34,23} | {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658}
+ 14 | {78,57,19} | {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 16 | {14,63,85,11} | {AAAAAA66777}
+ 17 | {7,10,81,85} | {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356}
+ 18 | {1} | {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 20 | {72,89,70,51,54,37,8,49,79} | {AAAAAA58494}
+ 21 | {2,8,65,10,5,79,43} | {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420}
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 23 | {40,90,5,38,72,40,30,10,43,55} | {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562}
+ 24 | {94,61,99,35,48} | {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219}
+ 25 | {31,1,10,11,27,79,38} | {AAAAAAAAAAAAAAAAAA59334,45449}
+ 26 | {71,10,9,69,75} | {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009}
+ 27 | {94} | {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254}
+ 28 | {14,33,6,34,14} | {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601}
+ 29 | {39,21} | {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194}
+ 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+ 31 | {80,24,18,21,54} | {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938}
+ 32 | {58,79,82,80,67,75,98,10,41} | {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533}
+ 33 | {74,73} | {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796}
+ 34 | {70,45} | {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242}
+ 35 | {23,40} | {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084}
+ 36 | {79,82,14,52,30,5,79} | {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598}
+ 37 | {53,11,81,39,3,78,58,64,74} | {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611}
+ 38 | {59,5,4,95,28} | {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387}
+ 39 | {82,43,99,16,74} | {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620}
+ 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623}
+ 41 | {19,26,63,12,93,73,27,94} | {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666}
+ 42 | {15,76,82,75,8,91} | {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587}
+ 43 | {39,87,91,97,79,28} | {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946}
+ 44 | {40,58,68,29,54} | {AAAAAAA81898,AAAAAA66777,AAAAAA98232}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 46 | {53,24} | {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621}
+ 47 | {98,23,64,12,75,61} | {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466}
+ 48 | {76,14} | {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037}
+ 49 | {56,5,54,37,49} | {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587}
+ 50 | {20,12,37,64,93} | {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955}
+ 51 | {47} | {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452}
+ 52 | {89,0} | {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 54 | {70,47} | {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322}
+ 55 | {47,79,47,64,72,25,71,24,93} | {AAAAAAAAAAAAAAAAAA55796,AAAAA62737}
+ 56 | {33,7,60,54,93,90,77,85,39} | {AAAAAAAAAAAAAAAAAA32918,AA42406}
+ 57 | {23,45,10,42,36,21,9,96} | {AAAAAAAAAAAAAAAAAAA70415}
+ 58 | {92} | {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119}
+ 59 | {9,69,46,77} | {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955}
+ 60 | {62,2,59,38,89} | {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875}
+ 61 | {72,2,44,95,54,54,13} | {AAAAAAAAAAAAAAAAAAA91804}
+ 62 | {83,72,29,73} | {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617}
+ 63 | {11,4,61,87} | {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938}
+ 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 66 | {31,23,70,52,4,33,48,25} | {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836}
+ 67 | {31,94,7,10} | {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946}
+ 68 | {90,43,38} | {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643}
+ 69 | {67,35,99,85,72,86,44} | {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955}
+ 70 | {56,70,83} | {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242}
+ 71 | {74,26} | {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 73 | {88,25,96,78,65,15,29,19} | {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 75 | {12,96,83,24,71,89,55} | {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052}
+ 76 | {92,55,10,7} | {AAAAAAAAAAAAAAA67062}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 78 | {55,89,44,84,34} | {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 80 | {74,89,44,80,0} | {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007}
+ 81 | {63,77,54,48,61,53,97} | {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121}
+ 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+ 83 | {14,10} | {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119}
+ 84 | {11,83,35,13,96,94} | {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183}
+ 85 | {39,60} | {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154}
+ 86 | {33,81,72,74,45,36,82} | {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176}
+ 87 | {57,27,50,12,97,68} | {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505}
+ 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 90 | {88,75} | {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526}
+ 91 | {78} | {AAAAAAAAAAAAA62007,AAA99043}
+ 92 | {85,63,49,45} | {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089}
+ 93 | {11} | {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383}
+ 94 | {98,9,85,62,88,91,60,61,38,86} | {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587}
+ 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+ 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 99 | {37,86} | {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+ 101 | {} | {}
+ 102 | {NULL} | {NULL}
+(102 rows)
+
+SELECT * FROM array_index_op_test WHERE t && '{}' ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_index_op_test WHERE t <@ '{}' ORDER BY seqno;
+ seqno | i | t
+-------+----+----
+ 101 | {} | {}
+(1 row)
+
+-- And try it with a multicolumn GIN index
+DROP INDEX intarrayidx, textarrayidx;
+CREATE INDEX botharrayidx ON array_index_op_test USING gin (i, t);
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno;
+ seqno | i | t
+-------+--------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+ 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+ 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+ 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+ 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(7 rows)
+
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno;
+ seqno | i | t
+-------+--------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+ 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+ 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+ 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+ 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(7 rows)
+
+SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------------+------------------------------------------------------------------------------
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(1 row)
+
+SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------------+------------------------------------------------------------------------------
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(1 row)
+
+SELECT * FROM array_index_op_test WHERE t = '{}' ORDER BY seqno;
+ seqno | i | t
+-------+----+----
+ 101 | {} | {}
+(1 row)
+
+SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
+ seqno | i | t
+-------+--------+--------
+ 102 | {NULL} | {NULL}
+(1 row)
+
+SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
+ seqno | i | t
+-------+----+----
+ 101 | {} | {}
+(1 row)
+
+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)
+--
+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}';
+ count
+-------
+ 2000
+(1 row)
+
+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
+ Index "public.gin_relopts_test"
+ Column | Type | Key? | Definition | Storage | Stats target
+--------+---------+------+------------+---------+--------------
+ i | integer | yes | i | plain |
+gin, for table "public.array_index_op_test"
+Options: fastupdate=on, gin_pending_list_limit=128
+
+--
+-- HASH
+--
+CREATE INDEX hash_i4_index ON hash_i4_heap USING hash (random int4_ops);
+CREATE INDEX hash_name_index ON hash_name_heap USING hash (random name_ops);
+CREATE INDEX hash_txt_index ON hash_txt_heap USING hash (random text_ops);
+CREATE INDEX hash_f8_index ON hash_f8_heap USING hash (random float8_ops) WITH (fillfactor=60);
+CREATE UNLOGGED TABLE unlogged_hash_table (id int4);
+CREATE INDEX unlogged_hash_index ON unlogged_hash_table USING hash (id int4_ops);
+DROP TABLE unlogged_hash_table;
+-- CREATE INDEX hash_ovfl_index ON hash_ovfl_heap USING hash (x int4_ops);
+-- Test hash index build tuplesorting. Force hash tuplesort using low
+-- maintenance_work_mem setting and fillfactor:
+SET maintenance_work_mem = '1MB';
+CREATE INDEX hash_tuplesort_idx ON tenk1 USING hash (stringu1 name_ops) WITH (fillfactor = 10);
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1 WHERE stringu1 = 'TVAAAA';
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on tenk1
+ Recheck Cond: (stringu1 = 'TVAAAA'::name)
+ -> Bitmap Index Scan on hash_tuplesort_idx
+ Index Cond: (stringu1 = 'TVAAAA'::name)
+(5 rows)
+
+SELECT count(*) FROM tenk1 WHERE stringu1 = 'TVAAAA';
+ count
+-------
+ 14
+(1 row)
+
+DROP INDEX hash_tuplesort_idx;
+RESET maintenance_work_mem;
+--
+-- Test functional index
+--
+CREATE TABLE func_index_heap (f1 text, f2 text);
+CREATE UNIQUE INDEX func_index_index on func_index_heap (textcat(f1,f2));
+INSERT INTO func_index_heap VALUES('ABC','DEF');
+INSERT INTO func_index_heap VALUES('AB','CDEFG');
+INSERT INTO func_index_heap VALUES('QWE','RTY');
+-- this should fail because of unique index:
+INSERT INTO func_index_heap VALUES('ABCD', 'EF');
+ERROR: duplicate key value violates unique constraint "func_index_index"
+DETAIL: Key (textcat(f1, f2))=(ABCDEF) already exists.
+-- but this shouldn't:
+INSERT INTO func_index_heap VALUES('QWERTY');
+-- while we're here, see that the metadata looks sane
+\d func_index_heap
+ Table "public.func_index_heap"
+ Column | Type | Collation | Nullable | Default
+--------+------+-----------+----------+---------
+ f1 | text | | |
+ f2 | text | | |
+Indexes:
+ "func_index_index" UNIQUE, btree (textcat(f1, f2))
+
+\d func_index_index
+ Index "public.func_index_index"
+ Column | Type | Key? | Definition
+---------+------+------+-----------------
+ textcat | text | yes | textcat(f1, f2)
+unique, btree, for table "public.func_index_heap"
+
+--
+-- Same test, expressional index
+--
+DROP TABLE func_index_heap;
+CREATE TABLE func_index_heap (f1 text, f2 text);
+CREATE UNIQUE INDEX func_index_index on func_index_heap ((f1 || f2) text_ops);
+INSERT INTO func_index_heap VALUES('ABC','DEF');
+INSERT INTO func_index_heap VALUES('AB','CDEFG');
+INSERT INTO func_index_heap VALUES('QWE','RTY');
+-- this should fail because of unique index:
+INSERT INTO func_index_heap VALUES('ABCD', 'EF');
+ERROR: duplicate key value violates unique constraint "func_index_index"
+DETAIL: Key ((f1 || f2))=(ABCDEF) already exists.
+-- but this shouldn't:
+INSERT INTO func_index_heap VALUES('QWERTY');
+-- while we're here, see that the metadata looks sane
+\d func_index_heap
+ Table "public.func_index_heap"
+ Column | Type | Collation | Nullable | Default
+--------+------+-----------+----------+---------
+ f1 | text | | |
+ f2 | text | | |
+Indexes:
+ "func_index_index" UNIQUE, btree ((f1 || f2))
+
+\d func_index_index
+ Index "public.func_index_index"
+ Column | Type | Key? | Definition
+--------+------+------+------------
+ expr | text | yes | (f1 || f2)
+unique, btree, for table "public.func_index_heap"
+
+-- this should fail because of unsafe column type (anonymous record)
+create index on func_index_heap ((f1 || f2), (row(f1, f2)));
+ERROR: column "row" has pseudo-type record
+--
+-- Test unique index with included columns
+--
+CREATE TABLE covering_index_heap (f1 int, f2 int, f3 text);
+CREATE UNIQUE INDEX covering_index_index on covering_index_heap (f1,f2) INCLUDE(f3);
+INSERT INTO covering_index_heap VALUES(1,1,'AAA');
+INSERT INTO covering_index_heap VALUES(1,2,'AAA');
+-- this should fail because of unique index on f1,f2:
+INSERT INTO covering_index_heap VALUES(1,2,'BBB');
+ERROR: duplicate key value violates unique constraint "covering_index_index"
+DETAIL: Key (f1, f2)=(1, 2) already exists.
+-- and this shouldn't:
+INSERT INTO covering_index_heap VALUES(1,4,'AAA');
+-- Try to build index on table that already contains data
+CREATE UNIQUE INDEX covering_pkey on covering_index_heap (f1,f2) INCLUDE(f3);
+-- Try to use existing covering index as primary key
+ALTER TABLE covering_index_heap ADD CONSTRAINT covering_pkey PRIMARY KEY USING INDEX
+covering_pkey;
+DROP TABLE covering_index_heap;
+--
+-- Also try building functional, expressional, and partial indexes on
+-- tables that already contain data.
+--
+create unique index hash_f8_index_1 on hash_f8_heap(abs(random));
+create unique index hash_f8_index_2 on hash_f8_heap((seqno + 1), random);
+create unique index hash_f8_index_3 on hash_f8_heap(random) where seqno > 1000;
+--
+-- 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);
+NOTICE: relation "concur_index1" already exists, skipping
+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);
+NOTICE: relation "concur_index2" already exists, skipping
+-- check if constraint is set up properly to be enforced
+INSERT INTO concur_heap VALUES ('b','x');
+ERROR: duplicate key value violates unique constraint "concur_index2"
+DETAIL: Key (f1)=(b) already exists.
+-- check if constraint is enforced properly at build time
+CREATE UNIQUE INDEX CONCURRENTLY concur_index3 ON concur_heap(f2);
+ERROR: could not create unique index "concur_index3"
+DETAIL: Key (f2)=(b) is duplicated.
+-- 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);
+ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block
+COMMIT;
+-- test where predicate is able to do a transactional update during
+-- a concurrent build before switching pg_index state flags.
+CREATE FUNCTION predicate_stable() RETURNS bool IMMUTABLE
+LANGUAGE plpgsql AS $$
+BEGIN
+ EXECUTE 'SELECT txid_current()';
+ RETURN true;
+END; $$;
+CREATE INDEX CONCURRENTLY concur_index8 ON concur_heap (f1)
+ WHERE predicate_stable();
+DROP INDEX concur_index8;
+DROP FUNCTION predicate_stable();
+-- 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
+VACUUM FULL concur_heap;
+REINDEX TABLE concur_heap;
+ERROR: could not create unique index "concur_index3"
+DETAIL: Key (f2)=(b) is duplicated.
+DELETE FROM concur_heap WHERE f1 = 'b';
+VACUUM FULL concur_heap;
+\d concur_heap
+ Table "public.concur_heap"
+ Column | Type | Collation | Nullable | Default
+--------+------+-----------+----------+---------
+ f1 | text | | |
+ f2 | text | | |
+Indexes:
+ "concur_heap_expr_idx" btree ((f2 || f1))
+ "concur_index1" btree (f2, f1)
+ "concur_index2" UNIQUE, btree (f1)
+ "concur_index3" UNIQUE, btree (f2) INVALID
+ "concur_index4" btree (f2) WHERE f1 = 'a'::text
+ "concur_index5" btree (f2) WHERE f1 = 'x'::text
+ "std_index" btree (f2)
+
+REINDEX TABLE concur_heap;
+\d concur_heap
+ Table "public.concur_heap"
+ Column | Type | Collation | Nullable | Default
+--------+------+-----------+----------+---------
+ f1 | text | | |
+ f2 | text | | |
+Indexes:
+ "concur_heap_expr_idx" btree ((f2 || f1))
+ "concur_index1" btree (f2, f1)
+ "concur_index2" UNIQUE, btree (f1)
+ "concur_index3" UNIQUE, btree (f2)
+ "concur_index4" btree (f2) WHERE f1 = 'a'::text
+ "concur_index5" btree (f2) WHERE f1 = 'x'::text
+ "std_index" btree (f2)
+
+-- Temporary tables with concurrent builds and on-commit actions
+-- CONCURRENTLY used with CREATE INDEX and DROP INDEX is ignored.
+-- PRESERVE ROWS, the default.
+CREATE TEMP TABLE concur_temp (f1 int, f2 text)
+ ON COMMIT PRESERVE ROWS;
+INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar');
+CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1);
+DROP INDEX CONCURRENTLY concur_temp_ind;
+DROP TABLE concur_temp;
+-- ON COMMIT DROP
+BEGIN;
+CREATE TEMP TABLE concur_temp (f1 int, f2 text)
+ ON COMMIT DROP;
+INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar');
+-- Fails when running in a transaction.
+CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1);
+ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block
+COMMIT;
+-- ON COMMIT DELETE ROWS
+CREATE TEMP TABLE concur_temp (f1 int, f2 text)
+ ON COMMIT DELETE ROWS;
+INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar');
+CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1);
+DROP INDEX CONCURRENTLY concur_temp_ind;
+DROP TABLE concur_temp;
+--
+-- Try some concurrent index drops
+--
+DROP INDEX CONCURRENTLY "concur_index2"; -- works
+DROP INDEX CONCURRENTLY IF EXISTS "concur_index2"; -- notice
+NOTICE: index "concur_index2" does not exist, skipping
+-- failures
+DROP INDEX CONCURRENTLY "concur_index2", "concur_index3";
+ERROR: DROP INDEX CONCURRENTLY does not support dropping multiple objects
+BEGIN;
+DROP INDEX CONCURRENTLY "concur_index5";
+ERROR: DROP INDEX CONCURRENTLY cannot run inside a transaction block
+ROLLBACK;
+-- successes
+DROP INDEX CONCURRENTLY IF EXISTS "concur_index3";
+DROP INDEX CONCURRENTLY "concur_index4";
+DROP INDEX CONCURRENTLY "concur_index5";
+DROP INDEX CONCURRENTLY "concur_index1";
+DROP INDEX CONCURRENTLY "concur_heap_expr_idx";
+\d concur_heap
+ Table "public.concur_heap"
+ Column | Type | Collation | Nullable | Default
+--------+------+-----------+----------+---------
+ f1 | text | | |
+ f2 | text | | |
+Indexes:
+ "std_index" btree (f2)
+
+DROP 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
+ Table "public.cwi_test"
+ Column | Type | Collation | Nullable | Default
+--------+-----------------------+-----------+----------+---------
+ a | integer | | not null |
+ b | character varying(10) | | not null |
+ c | character(1) | | |
+Indexes:
+ "cwi_uniq_idx" PRIMARY KEY, btree (a, b)
+
+\d cwi_uniq_idx
+ Index "public.cwi_uniq_idx"
+ Column | Type | Key? | Definition
+--------+-----------------------+------+------------
+ a | integer | yes | a
+ b | character varying(10) | yes | b
+primary key, btree, for table "public.cwi_test"
+
+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;
+NOTICE: ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index "cwi_uniq2_idx" to "cwi_replaced_pkey"
+\d cwi_test
+ Table "public.cwi_test"
+ Column | Type | Collation | Nullable | Default
+--------+-----------------------+-----------+----------+---------
+ a | integer | | not null |
+ b | character varying(10) | | not null |
+ c | character(1) | | |
+Indexes:
+ "cwi_replaced_pkey" PRIMARY KEY, btree (b, a)
+
+\d cwi_replaced_pkey
+ Index "public.cwi_replaced_pkey"
+ Column | Type | Key? | Definition
+--------+-----------------------+------+------------
+ b | character varying(10) | yes | b
+ a | integer | yes | a
+primary key, btree, for table "public.cwi_test"
+
+DROP INDEX cwi_replaced_pkey; -- Should fail; a constraint depends on it
+ERROR: cannot drop index cwi_replaced_pkey because constraint cwi_replaced_pkey on table cwi_test requires it
+HINT: You can drop constraint cwi_replaced_pkey on table cwi_test instead.
+-- Check that non-default index options are rejected
+CREATE UNIQUE INDEX cwi_uniq3_idx ON cwi_test(a desc);
+ALTER TABLE cwi_test ADD UNIQUE USING INDEX cwi_uniq3_idx; -- fail
+ERROR: index "cwi_uniq3_idx" column number 1 does not have default sorting behavior
+LINE 1: ALTER TABLE cwi_test ADD UNIQUE USING INDEX cwi_uniq3_idx;
+ ^
+DETAIL: Cannot create a primary key or unique constraint using such an index.
+CREATE UNIQUE INDEX cwi_uniq4_idx ON cwi_test(b collate "POSIX");
+ALTER TABLE cwi_test ADD UNIQUE USING INDEX cwi_uniq4_idx; -- fail
+ERROR: index "cwi_uniq4_idx" column number 1 does not have default sorting behavior
+LINE 1: ALTER TABLE cwi_test ADD UNIQUE USING INDEX cwi_uniq4_idx;
+ ^
+DETAIL: Cannot create a primary key or unique constraint using such an index.
+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 ;
+ERROR: ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables
+DROP TABLE cwi_test;
+--
+-- Check handling of indexes on system columns
+--
+CREATE TABLE syscol_table (a INT);
+-- System columns cannot be indexed
+CREATE INDEX ON syscolcol_table (ctid);
+ERROR: relation "syscolcol_table" does not exist
+-- nor used in expressions
+CREATE INDEX ON syscol_table ((ctid >= '(1000,0)'));
+ERROR: index creation on system columns is not supported
+-- nor used in predicates
+CREATE INDEX ON syscol_table (a) WHERE ctid >= '(1000,0)';
+ERROR: index creation on system columns is not supported
+DROP TABLE syscol_table;
+--
+-- Tests for IS NULL/IS NOT NULL with b-tree indexes
+--
+CREATE TABLE onek_with_null AS SELECT unique1, unique2 FROM onek;
+INSERT INTO onek_with_null (unique1,unique2) VALUES (NULL, -1), (NULL, NULL);
+CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2,unique1);
+SET enable_seqscan = OFF;
+SET enable_indexscan = ON;
+SET enable_bitmapscan = ON;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL;
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NULL;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL;
+ count
+-------
+ 1000
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500;
+ count
+-------
+ 499
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500;
+ count
+-------
+ 0
+(1 row)
+
+DROP INDEX onek_nulltest;
+CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2 desc,unique1);
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL;
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NULL;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL;
+ count
+-------
+ 1000
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500;
+ count
+-------
+ 499
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500;
+ count
+-------
+ 0
+(1 row)
+
+DROP INDEX onek_nulltest;
+CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2 desc nulls last,unique1);
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL;
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NULL;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL;
+ count
+-------
+ 1000
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500;
+ count
+-------
+ 499
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500;
+ count
+-------
+ 0
+(1 row)
+
+DROP INDEX onek_nulltest;
+CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2 nulls first,unique1);
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL;
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NULL;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL;
+ count
+-------
+ 1000
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500;
+ count
+-------
+ 499
+(1 row)
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500;
+ count
+-------
+ 0
+(1 row)
+
+DROP INDEX onek_nulltest;
+-- Check initial-positioning logic too
+CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2);
+SET enable_seqscan = OFF;
+SET enable_indexscan = ON;
+SET enable_bitmapscan = OFF;
+SELECT unique1, unique2 FROM onek_with_null
+ ORDER BY unique2 LIMIT 2;
+ unique1 | unique2
+---------+---------
+ | -1
+ 147 | 0
+(2 rows)
+
+SELECT unique1, unique2 FROM onek_with_null WHERE unique2 >= -1
+ ORDER BY unique2 LIMIT 2;
+ unique1 | unique2
+---------+---------
+ | -1
+ 147 | 0
+(2 rows)
+
+SELECT unique1, unique2 FROM onek_with_null WHERE unique2 >= 0
+ ORDER BY unique2 LIMIT 2;
+ unique1 | unique2
+---------+---------
+ 147 | 0
+ 931 | 1
+(2 rows)
+
+SELECT unique1, unique2 FROM onek_with_null
+ ORDER BY unique2 DESC LIMIT 2;
+ unique1 | unique2
+---------+---------
+ |
+ 278 | 999
+(2 rows)
+
+SELECT unique1, unique2 FROM onek_with_null WHERE unique2 >= -1
+ ORDER BY unique2 DESC LIMIT 2;
+ unique1 | unique2
+---------+---------
+ 278 | 999
+ 0 | 998
+(2 rows)
+
+SELECT unique1, unique2 FROM onek_with_null WHERE unique2 < 999
+ ORDER BY unique2 DESC LIMIT 2;
+ unique1 | unique2
+---------+---------
+ 0 | 998
+ 744 | 997
+(2 rows)
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+DROP TABLE onek_with_null;
+--
+-- Check bitmap index path planning
+--
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+ WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on tenk1
+ Recheck Cond: (((thousand = 42) AND (tenthous = 1)) OR ((thousand = 42) AND (tenthous = 3)) OR ((thousand = 42) AND (tenthous = 42)))
+ -> BitmapOr
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: ((thousand = 42) AND (tenthous = 1))
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: ((thousand = 42) AND (tenthous = 3))
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: ((thousand = 42) AND (tenthous = 42))
+(9 rows)
+
+SELECT * FROM tenk1
+ WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 42 | 5530 | 0 | 2 | 2 | 2 | 42 | 42 | 42 | 42 | 42 | 84 | 85 | QBAAAA | SEIAAA | OOOOxx
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1
+ WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+ QUERY PLAN
+---------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on tenk1
+ Recheck Cond: ((hundred = 42) AND ((thousand = 42) OR (thousand = 99)))
+ -> BitmapAnd
+ -> Bitmap Index Scan on tenk1_hundred
+ Index Cond: (hundred = 42)
+ -> BitmapOr
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: (thousand = 42)
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: (thousand = 99)
+(11 rows)
+
+SELECT count(*) FROM tenk1
+ WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+ count
+-------
+ 10
+(1 row)
+
+--
+-- Check behavior with duplicate index column contents
+--
+CREATE TABLE dupindexcols AS
+ SELECT unique1 as id, stringu2::text as f1 FROM tenk1;
+CREATE INDEX dupindexcols_i ON dupindexcols (f1, id, f1 text_pattern_ops);
+ANALYZE dupindexcols;
+EXPLAIN (COSTS OFF)
+ SELECT count(*) FROM dupindexcols
+ WHERE f1 BETWEEN 'WA' AND 'ZZZ' and id < 1000 and f1 ~<~ 'YX';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on dupindexcols
+ Recheck Cond: ((f1 >= 'WA'::text) AND (f1 <= 'ZZZ'::text) AND (id < 1000) AND (f1 ~<~ 'YX'::text))
+ -> Bitmap Index Scan on dupindexcols_i
+ Index Cond: ((f1 >= 'WA'::text) AND (f1 <= 'ZZZ'::text) AND (id < 1000) AND (f1 ~<~ 'YX'::text))
+(5 rows)
+
+SELECT count(*) FROM dupindexcols
+ WHERE f1 BETWEEN 'WA' AND 'ZZZ' and id < 1000 and f1 ~<~ 'YX';
+ count
+-------
+ 97
+(1 row)
+
+--
+-- Check ordering of =ANY indexqual results (bug in 9.2.0)
+--
+vacuum tenk1; -- ensure we get consistent plans here
+explain (costs off)
+SELECT unique1 FROM tenk1
+WHERE unique1 IN (1,42,7)
+ORDER BY unique1;
+ QUERY PLAN
+-------------------------------------------------------
+ Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = ANY ('{1,42,7}'::integer[]))
+(2 rows)
+
+SELECT unique1 FROM tenk1
+WHERE unique1 IN (1,42,7)
+ORDER BY unique1;
+ unique1
+---------
+ 1
+ 7
+ 42
+(3 rows)
+
+explain (costs off)
+SELECT thousand, tenthous FROM tenk1
+WHERE thousand < 2 AND tenthous IN (1001,3000)
+ORDER BY thousand;
+ QUERY PLAN
+-------------------------------------------------------
+ Index Only Scan using tenk1_thous_tenthous on tenk1
+ Index Cond: (thousand < 2)
+ Filter: (tenthous = ANY ('{1001,3000}'::integer[]))
+(3 rows)
+
+SELECT thousand, tenthous FROM tenk1
+WHERE thousand < 2 AND tenthous IN (1001,3000)
+ORDER BY thousand;
+ thousand | tenthous
+----------+----------
+ 0 | 3000
+ 1 | 1001
+(2 rows)
+
+SET enable_indexonlyscan = OFF;
+explain (costs off)
+SELECT thousand, tenthous FROM tenk1
+WHERE thousand < 2 AND tenthous IN (1001,3000)
+ORDER BY thousand;
+ QUERY PLAN
+--------------------------------------------------------------------------------------
+ Sort
+ Sort Key: thousand
+ -> Index Scan using tenk1_thous_tenthous on tenk1
+ Index Cond: ((thousand < 2) AND (tenthous = ANY ('{1001,3000}'::integer[])))
+(4 rows)
+
+SELECT thousand, tenthous FROM tenk1
+WHERE thousand < 2 AND tenthous IN (1001,3000)
+ORDER BY thousand;
+ thousand | tenthous
+----------+----------
+ 0 | 3000
+ 1 | 1001
+(2 rows)
+
+RESET enable_indexonlyscan;
+--
+-- Check elimination of constant-NULL subexpressions
+--
+explain (costs off)
+ select * from tenk1 where (thousand, tenthous) in ((1,1001), (null,null));
+ QUERY PLAN
+------------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+ Index Cond: ((thousand = 1) AND (tenthous = 1001))
+(2 rows)
+
+--
+-- Check matching of boolean index columns to WHERE conditions and sort keys
+--
+create temp table boolindex (b bool, i int, unique(b, i), junk float);
+explain (costs off)
+ select * from boolindex order by b, i limit 10;
+ QUERY PLAN
+-------------------------------------------------------
+ Limit
+ -> Index Scan using boolindex_b_i_key on boolindex
+(2 rows)
+
+explain (costs off)
+ select * from boolindex where b order by i limit 10;
+ QUERY PLAN
+-------------------------------------------------------
+ Limit
+ -> Index Scan using boolindex_b_i_key on boolindex
+ Index Cond: (b = true)
+(3 rows)
+
+explain (costs off)
+ select * from boolindex where b = true order by i desc limit 10;
+ QUERY PLAN
+----------------------------------------------------------------
+ Limit
+ -> Index Scan Backward using boolindex_b_i_key on boolindex
+ Index Cond: (b = true)
+(3 rows)
+
+explain (costs off)
+ select * from boolindex where not b order by i limit 10;
+ QUERY PLAN
+-------------------------------------------------------
+ Limit
+ -> Index Scan using boolindex_b_i_key on boolindex
+ Index Cond: (b = false)
+(3 rows)
+
+explain (costs off)
+ select * from boolindex where b is true order by i desc limit 10;
+ QUERY PLAN
+----------------------------------------------------------------
+ Limit
+ -> Index Scan Backward using boolindex_b_i_key on boolindex
+ Index Cond: (b = true)
+(3 rows)
+
+explain (costs off)
+ select * from boolindex where b is false order by i desc limit 10;
+ QUERY PLAN
+----------------------------------------------------------------
+ Limit
+ -> Index Scan Backward using boolindex_b_i_key on boolindex
+ Index Cond: (b = false)
+(3 rows)
+
+--
+-- REINDEX (VERBOSE)
+--
+CREATE TABLE reindex_verbose(id integer primary key);
+\set VERBOSITY terse \\ -- suppress machine-dependent details
+REINDEX (VERBOSE) TABLE reindex_verbose;
+INFO: index "reindex_verbose_pkey" was reindexed
+\set VERBOSITY default
+DROP TABLE reindex_verbose;
+--
+-- REINDEX CONCURRENTLY
+--
+CREATE TABLE concur_reindex_tab (c1 int);
+-- REINDEX
+REINDEX TABLE concur_reindex_tab; -- notice
+NOTICE: table "concur_reindex_tab" has no indexes to reindex
+REINDEX (CONCURRENTLY) TABLE concur_reindex_tab; -- notice
+NOTICE: table "concur_reindex_tab" has no indexes that can be reindexed concurrently
+ALTER TABLE concur_reindex_tab ADD COLUMN c2 text; -- add toast index
+-- Normal index with integer column
+CREATE UNIQUE INDEX concur_reindex_ind1 ON concur_reindex_tab(c1);
+-- Normal index with text column
+CREATE INDEX concur_reindex_ind2 ON concur_reindex_tab(c2);
+-- UNIQUE index with expression
+CREATE UNIQUE INDEX concur_reindex_ind3 ON concur_reindex_tab(abs(c1));
+-- Duplicate column names
+CREATE INDEX concur_reindex_ind4 ON concur_reindex_tab(c1, c1, c2);
+-- Create table for check on foreign key dependence switch with indexes swapped
+ALTER TABLE concur_reindex_tab ADD PRIMARY KEY USING INDEX concur_reindex_ind1;
+CREATE TABLE concur_reindex_tab2 (c1 int REFERENCES concur_reindex_tab);
+INSERT INTO concur_reindex_tab VALUES (1, 'a');
+INSERT INTO concur_reindex_tab VALUES (2, 'a');
+-- Reindex concurrently of exclusion constraint currently not supported
+CREATE TABLE concur_reindex_tab3 (c1 int, c2 int4range, EXCLUDE USING gist (c2 WITH &&));
+INSERT INTO concur_reindex_tab3 VALUES (3, '[1,2]');
+REINDEX INDEX CONCURRENTLY concur_reindex_tab3_c2_excl; -- error
+ERROR: concurrent index creation for exclusion constraints is not supported
+REINDEX TABLE CONCURRENTLY concur_reindex_tab3; -- succeeds with warning
+WARNING: cannot reindex exclusion constraint index "public.concur_reindex_tab3_c2_excl" concurrently, skipping
+INSERT INTO concur_reindex_tab3 VALUES (4, '[2,4]');
+ERROR: conflicting key value violates exclusion constraint "concur_reindex_tab3_c2_excl"
+DETAIL: Key (c2)=([2,5)) conflicts with existing key (c2)=([1,3)).
+-- Check materialized views
+CREATE MATERIALIZED VIEW concur_reindex_matview AS SELECT * FROM concur_reindex_tab;
+-- Dependency lookup before and after the follow-up REINDEX commands.
+-- These should remain consistent.
+SELECT pg_describe_object(classid, objid, objsubid) as obj,
+ pg_describe_object(refclassid,refobjid,refobjsubid) as objref,
+ deptype
+FROM pg_depend
+WHERE classid = 'pg_class'::regclass AND
+ objid in ('concur_reindex_tab'::regclass,
+ 'concur_reindex_ind1'::regclass,
+ 'concur_reindex_ind2'::regclass,
+ 'concur_reindex_ind3'::regclass,
+ 'concur_reindex_ind4'::regclass,
+ 'concur_reindex_matview'::regclass)
+ ORDER BY 1, 2;
+ obj | objref | deptype
+------------------------------------------+------------------------------------------------------------+---------
+ index concur_reindex_ind1 | constraint concur_reindex_ind1 on table concur_reindex_tab | i
+ index concur_reindex_ind2 | column c2 of table concur_reindex_tab | a
+ index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a
+ index concur_reindex_ind3 | table concur_reindex_tab | a
+ index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a
+ index concur_reindex_ind4 | column c2 of table concur_reindex_tab | a
+ materialized view concur_reindex_matview | schema public | n
+ table concur_reindex_tab | schema public | n
+(8 rows)
+
+REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
+REINDEX TABLE CONCURRENTLY concur_reindex_tab;
+REINDEX TABLE CONCURRENTLY concur_reindex_matview;
+SELECT pg_describe_object(classid, objid, objsubid) as obj,
+ pg_describe_object(refclassid,refobjid,refobjsubid) as objref,
+ deptype
+FROM pg_depend
+WHERE classid = 'pg_class'::regclass AND
+ objid in ('concur_reindex_tab'::regclass,
+ 'concur_reindex_ind1'::regclass,
+ 'concur_reindex_ind2'::regclass,
+ 'concur_reindex_ind3'::regclass,
+ 'concur_reindex_ind4'::regclass,
+ 'concur_reindex_matview'::regclass)
+ ORDER BY 1, 2;
+ obj | objref | deptype
+------------------------------------------+------------------------------------------------------------+---------
+ index concur_reindex_ind1 | constraint concur_reindex_ind1 on table concur_reindex_tab | i
+ index concur_reindex_ind2 | column c2 of table concur_reindex_tab | a
+ index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a
+ index concur_reindex_ind3 | table concur_reindex_tab | a
+ index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a
+ index concur_reindex_ind4 | column c2 of table concur_reindex_tab | a
+ materialized view concur_reindex_matview | schema public | n
+ table concur_reindex_tab | schema public | n
+(8 rows)
+
+-- Check that comments are preserved
+CREATE TABLE testcomment (i int);
+CREATE INDEX testcomment_idx1 ON testcomment (i);
+COMMENT ON INDEX testcomment_idx1 IS 'test comment';
+SELECT obj_description('testcomment_idx1'::regclass, 'pg_class');
+ obj_description
+-----------------
+ test comment
+(1 row)
+
+REINDEX TABLE testcomment;
+SELECT obj_description('testcomment_idx1'::regclass, 'pg_class');
+ obj_description
+-----------------
+ test comment
+(1 row)
+
+REINDEX TABLE CONCURRENTLY testcomment ;
+SELECT obj_description('testcomment_idx1'::regclass, 'pg_class');
+ obj_description
+-----------------
+ test comment
+(1 row)
+
+DROP TABLE testcomment;
+-- Check that indisclustered updates are preserved
+CREATE TABLE concur_clustered(i int);
+CREATE INDEX concur_clustered_i_idx ON concur_clustered(i);
+ALTER TABLE concur_clustered CLUSTER ON concur_clustered_i_idx;
+REINDEX TABLE CONCURRENTLY concur_clustered;
+SELECT indexrelid::regclass, indisclustered FROM pg_index
+ WHERE indrelid = 'concur_clustered'::regclass;
+ indexrelid | indisclustered
+------------------------+----------------
+ concur_clustered_i_idx | t
+(1 row)
+
+DROP TABLE concur_clustered;
+-- Check that indisreplident updates are preserved.
+CREATE TABLE concur_replident(i int NOT NULL);
+CREATE UNIQUE INDEX concur_replident_i_idx ON concur_replident(i);
+ALTER TABLE concur_replident REPLICA IDENTITY
+ USING INDEX concur_replident_i_idx;
+SELECT indexrelid::regclass, indisreplident FROM pg_index
+ WHERE indrelid = 'concur_replident'::regclass;
+ indexrelid | indisreplident
+------------------------+----------------
+ concur_replident_i_idx | t
+(1 row)
+
+REINDEX TABLE CONCURRENTLY concur_replident;
+SELECT indexrelid::regclass, indisreplident FROM pg_index
+ WHERE indrelid = 'concur_replident'::regclass;
+ indexrelid | indisreplident
+------------------------+----------------
+ concur_replident_i_idx | t
+(1 row)
+
+DROP TABLE concur_replident;
+-- Check that opclass parameters are preserved
+CREATE TABLE concur_appclass_tab(i tsvector, j tsvector, k tsvector);
+CREATE INDEX concur_appclass_ind on concur_appclass_tab
+ USING gist (i tsvector_ops (siglen='1000'), j tsvector_ops (siglen='500'));
+CREATE INDEX concur_appclass_ind_2 on concur_appclass_tab
+ USING gist (k tsvector_ops (siglen='300'), j tsvector_ops);
+REINDEX TABLE CONCURRENTLY concur_appclass_tab;
+\d concur_appclass_tab
+ Table "public.concur_appclass_tab"
+ Column | Type | Collation | Nullable | Default
+--------+----------+-----------+----------+---------
+ i | tsvector | | |
+ j | tsvector | | |
+ k | tsvector | | |
+Indexes:
+ "concur_appclass_ind" gist (i tsvector_ops (siglen='1000'), j tsvector_ops (siglen='500'))
+ "concur_appclass_ind_2" gist (k tsvector_ops (siglen='300'), j)
+
+DROP TABLE concur_appclass_tab;
+-- Partitions
+-- Create some partitioned tables
+CREATE TABLE concur_reindex_part (c1 int, c2 int) PARTITION BY RANGE (c1);
+CREATE TABLE concur_reindex_part_0 PARTITION OF concur_reindex_part
+ FOR VALUES FROM (0) TO (10) PARTITION BY list (c2);
+CREATE TABLE concur_reindex_part_0_1 PARTITION OF concur_reindex_part_0
+ FOR VALUES IN (1);
+CREATE TABLE concur_reindex_part_0_2 PARTITION OF concur_reindex_part_0
+ FOR VALUES IN (2);
+-- This partitioned table will have no partitions.
+CREATE TABLE concur_reindex_part_10 PARTITION OF concur_reindex_part
+ FOR VALUES FROM (10) TO (20) PARTITION BY list (c2);
+-- Create some partitioned indexes
+CREATE INDEX concur_reindex_part_index ON ONLY concur_reindex_part (c1);
+CREATE INDEX concur_reindex_part_index_0 ON ONLY concur_reindex_part_0 (c1);
+ALTER INDEX concur_reindex_part_index ATTACH PARTITION concur_reindex_part_index_0;
+-- This partitioned index will have no partitions.
+CREATE INDEX concur_reindex_part_index_10 ON ONLY concur_reindex_part_10 (c1);
+ALTER INDEX concur_reindex_part_index ATTACH PARTITION concur_reindex_part_index_10;
+CREATE INDEX concur_reindex_part_index_0_1 ON ONLY concur_reindex_part_0_1 (c1);
+ALTER INDEX concur_reindex_part_index_0 ATTACH PARTITION concur_reindex_part_index_0_1;
+CREATE INDEX concur_reindex_part_index_0_2 ON ONLY concur_reindex_part_0_2 (c1);
+ALTER INDEX concur_reindex_part_index_0 ATTACH PARTITION concur_reindex_part_index_0_2;
+SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index')
+ ORDER BY relid, level;
+ relid | parentrelid | level
+-------------------------------+-----------------------------+-------
+ concur_reindex_part_index | | 0
+ concur_reindex_part_index_0 | concur_reindex_part_index | 1
+ concur_reindex_part_index_10 | concur_reindex_part_index | 1
+ concur_reindex_part_index_0_1 | concur_reindex_part_index_0 | 2
+ concur_reindex_part_index_0_2 | concur_reindex_part_index_0 | 2
+(5 rows)
+
+SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index')
+ ORDER BY relid, level;
+ relid | parentrelid | level
+-------------------------------+-----------------------------+-------
+ concur_reindex_part_index | | 0
+ concur_reindex_part_index_0 | concur_reindex_part_index | 1
+ concur_reindex_part_index_10 | concur_reindex_part_index | 1
+ concur_reindex_part_index_0_1 | concur_reindex_part_index_0 | 2
+ concur_reindex_part_index_0_2 | concur_reindex_part_index_0 | 2
+(5 rows)
+
+-- REINDEX should preserve dependencies of partition tree.
+SELECT pg_describe_object(classid, objid, objsubid) as obj,
+ pg_describe_object(refclassid,refobjid,refobjsubid) as objref,
+ deptype
+FROM pg_depend
+WHERE classid = 'pg_class'::regclass AND
+ objid in ('concur_reindex_part'::regclass,
+ 'concur_reindex_part_0'::regclass,
+ 'concur_reindex_part_0_1'::regclass,
+ 'concur_reindex_part_0_2'::regclass,
+ 'concur_reindex_part_index'::regclass,
+ 'concur_reindex_part_index_0'::regclass,
+ 'concur_reindex_part_index_0_1'::regclass,
+ 'concur_reindex_part_index_0_2'::regclass)
+ ORDER BY 1, 2;
+ obj | objref | deptype
+------------------------------------------+--------------------------------------------+---------
+ column c1 of table concur_reindex_part | table concur_reindex_part | i
+ column c2 of table concur_reindex_part_0 | table concur_reindex_part_0 | i
+ index concur_reindex_part_index | column c1 of table concur_reindex_part | a
+ index concur_reindex_part_index_0 | column c1 of table concur_reindex_part_0 | a
+ index concur_reindex_part_index_0 | index concur_reindex_part_index | P
+ index concur_reindex_part_index_0 | table concur_reindex_part_0 | S
+ index concur_reindex_part_index_0_1 | column c1 of table concur_reindex_part_0_1 | a
+ index concur_reindex_part_index_0_1 | index concur_reindex_part_index_0 | P
+ index concur_reindex_part_index_0_1 | table concur_reindex_part_0_1 | S
+ index concur_reindex_part_index_0_2 | column c1 of table concur_reindex_part_0_2 | a
+ index concur_reindex_part_index_0_2 | index concur_reindex_part_index_0 | P
+ index concur_reindex_part_index_0_2 | table concur_reindex_part_0_2 | S
+ table concur_reindex_part | schema public | n
+ table concur_reindex_part_0 | schema public | n
+ table concur_reindex_part_0 | table concur_reindex_part | a
+ table concur_reindex_part_0_1 | schema public | n
+ table concur_reindex_part_0_1 | table concur_reindex_part_0 | a
+ table concur_reindex_part_0_2 | schema public | n
+ table concur_reindex_part_0_2 | table concur_reindex_part_0 | a
+(19 rows)
+
+REINDEX INDEX CONCURRENTLY concur_reindex_part_index_0_1;
+REINDEX INDEX CONCURRENTLY concur_reindex_part_index_0_2;
+SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index')
+ ORDER BY relid, level;
+ relid | parentrelid | level
+-------------------------------+-----------------------------+-------
+ concur_reindex_part_index | | 0
+ concur_reindex_part_index_0 | concur_reindex_part_index | 1
+ concur_reindex_part_index_10 | concur_reindex_part_index | 1
+ concur_reindex_part_index_0_1 | concur_reindex_part_index_0 | 2
+ concur_reindex_part_index_0_2 | concur_reindex_part_index_0 | 2
+(5 rows)
+
+REINDEX TABLE CONCURRENTLY concur_reindex_part_0_1;
+REINDEX TABLE CONCURRENTLY concur_reindex_part_0_2;
+SELECT pg_describe_object(classid, objid, objsubid) as obj,
+ pg_describe_object(refclassid,refobjid,refobjsubid) as objref,
+ deptype
+FROM pg_depend
+WHERE classid = 'pg_class'::regclass AND
+ objid in ('concur_reindex_part'::regclass,
+ 'concur_reindex_part_0'::regclass,
+ 'concur_reindex_part_0_1'::regclass,
+ 'concur_reindex_part_0_2'::regclass,
+ 'concur_reindex_part_index'::regclass,
+ 'concur_reindex_part_index_0'::regclass,
+ 'concur_reindex_part_index_0_1'::regclass,
+ 'concur_reindex_part_index_0_2'::regclass)
+ ORDER BY 1, 2;
+ obj | objref | deptype
+------------------------------------------+--------------------------------------------+---------
+ column c1 of table concur_reindex_part | table concur_reindex_part | i
+ column c2 of table concur_reindex_part_0 | table concur_reindex_part_0 | i
+ index concur_reindex_part_index | column c1 of table concur_reindex_part | a
+ index concur_reindex_part_index_0 | column c1 of table concur_reindex_part_0 | a
+ index concur_reindex_part_index_0 | index concur_reindex_part_index | P
+ index concur_reindex_part_index_0 | table concur_reindex_part_0 | S
+ index concur_reindex_part_index_0_1 | column c1 of table concur_reindex_part_0_1 | a
+ index concur_reindex_part_index_0_1 | index concur_reindex_part_index_0 | P
+ index concur_reindex_part_index_0_1 | table concur_reindex_part_0_1 | S
+ index concur_reindex_part_index_0_2 | column c1 of table concur_reindex_part_0_2 | a
+ index concur_reindex_part_index_0_2 | index concur_reindex_part_index_0 | P
+ index concur_reindex_part_index_0_2 | table concur_reindex_part_0_2 | S
+ table concur_reindex_part | schema public | n
+ table concur_reindex_part_0 | schema public | n
+ table concur_reindex_part_0 | table concur_reindex_part | a
+ table concur_reindex_part_0_1 | schema public | n
+ table concur_reindex_part_0_1 | table concur_reindex_part_0 | a
+ table concur_reindex_part_0_2 | schema public | n
+ table concur_reindex_part_0_2 | table concur_reindex_part_0 | a
+(19 rows)
+
+SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index')
+ ORDER BY relid, level;
+ relid | parentrelid | level
+-------------------------------+-----------------------------+-------
+ concur_reindex_part_index | | 0
+ concur_reindex_part_index_0 | concur_reindex_part_index | 1
+ concur_reindex_part_index_10 | concur_reindex_part_index | 1
+ concur_reindex_part_index_0_1 | concur_reindex_part_index_0 | 2
+ concur_reindex_part_index_0_2 | concur_reindex_part_index_0 | 2
+(5 rows)
+
+-- REINDEX for partitioned indexes
+-- REINDEX TABLE fails for partitioned indexes
+-- Top-most parent index
+REINDEX TABLE concur_reindex_part_index; -- error
+ERROR: "concur_reindex_part_index" is not a table or materialized view
+REINDEX TABLE CONCURRENTLY concur_reindex_part_index; -- error
+ERROR: "concur_reindex_part_index" is not a table or materialized view
+-- Partitioned index with no leaves
+REINDEX TABLE concur_reindex_part_index_10; -- error
+ERROR: "concur_reindex_part_index_10" is not a table or materialized view
+REINDEX TABLE CONCURRENTLY concur_reindex_part_index_10; -- error
+ERROR: "concur_reindex_part_index_10" is not a table or materialized view
+-- Cannot run in a transaction block
+BEGIN;
+REINDEX INDEX concur_reindex_part_index;
+ERROR: REINDEX INDEX cannot run inside a transaction block
+CONTEXT: while reindexing partitioned index "public.concur_reindex_part_index"
+ROLLBACK;
+-- Helper functions to track changes of relfilenodes in a partition tree.
+-- Create a table tracking the relfilenode state.
+CREATE OR REPLACE FUNCTION create_relfilenode_part(relname text, indname text)
+ RETURNS VOID AS
+ $func$
+ BEGIN
+ EXECUTE format('
+ CREATE TABLE %I AS
+ SELECT oid, relname, relfilenode, relkind, reltoastrelid
+ FROM pg_class
+ WHERE oid IN
+ (SELECT relid FROM pg_partition_tree(''%I''));',
+ relname, indname);
+ END
+ $func$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION compare_relfilenode_part(tabname text)
+ RETURNS TABLE (relname name, relkind "char", state text) AS
+ $func$
+ BEGIN
+ RETURN QUERY EXECUTE
+ format(
+ 'SELECT b.relname,
+ b.relkind,
+ CASE WHEN a.relfilenode = b.relfilenode THEN ''relfilenode is unchanged''
+ ELSE ''relfilenode has changed'' END
+ -- Do not join with OID here as CONCURRENTLY changes it.
+ FROM %I b JOIN pg_class a ON b.relname = a.relname
+ ORDER BY 1;', tabname);
+ END
+ $func$ LANGUAGE plpgsql;
+-- Check that expected relfilenodes are changed, non-concurrent case.
+SELECT create_relfilenode_part('reindex_index_status', 'concur_reindex_part_index');
+ create_relfilenode_part
+-------------------------
+
+(1 row)
+
+REINDEX INDEX concur_reindex_part_index;
+SELECT * FROM compare_relfilenode_part('reindex_index_status');
+ relname | relkind | state
+-------------------------------+---------+--------------------------
+ concur_reindex_part_index | I | relfilenode is unchanged
+ concur_reindex_part_index_0 | I | relfilenode is unchanged
+ concur_reindex_part_index_0_1 | i | relfilenode has changed
+ concur_reindex_part_index_0_2 | i | relfilenode has changed
+ concur_reindex_part_index_10 | I | relfilenode is unchanged
+(5 rows)
+
+DROP TABLE reindex_index_status;
+-- concurrent case.
+SELECT create_relfilenode_part('reindex_index_status', 'concur_reindex_part_index');
+ create_relfilenode_part
+-------------------------
+
+(1 row)
+
+REINDEX INDEX CONCURRENTLY concur_reindex_part_index;
+SELECT * FROM compare_relfilenode_part('reindex_index_status');
+ relname | relkind | state
+-------------------------------+---------+--------------------------
+ concur_reindex_part_index | I | relfilenode is unchanged
+ concur_reindex_part_index_0 | I | relfilenode is unchanged
+ concur_reindex_part_index_0_1 | i | relfilenode has changed
+ concur_reindex_part_index_0_2 | i | relfilenode has changed
+ concur_reindex_part_index_10 | I | relfilenode is unchanged
+(5 rows)
+
+DROP TABLE reindex_index_status;
+-- REINDEX for partitioned tables
+-- REINDEX INDEX fails for partitioned tables
+-- Top-most parent
+REINDEX INDEX concur_reindex_part; -- error
+ERROR: "concur_reindex_part" is not an index
+REINDEX INDEX CONCURRENTLY concur_reindex_part; -- error
+ERROR: "concur_reindex_part" is not an index
+-- Partitioned with no leaves
+REINDEX INDEX concur_reindex_part_10; -- error
+ERROR: "concur_reindex_part_10" is not an index
+REINDEX INDEX CONCURRENTLY concur_reindex_part_10; -- error
+ERROR: "concur_reindex_part_10" is not an index
+-- Cannot run in a transaction block
+BEGIN;
+REINDEX TABLE concur_reindex_part;
+ERROR: REINDEX TABLE cannot run inside a transaction block
+CONTEXT: while reindexing partitioned table "public.concur_reindex_part"
+ROLLBACK;
+-- Check that expected relfilenodes are changed, non-concurrent case.
+-- Note that the partition tree changes of the *indexes* need to be checked.
+SELECT create_relfilenode_part('reindex_index_status', 'concur_reindex_part_index');
+ create_relfilenode_part
+-------------------------
+
+(1 row)
+
+REINDEX TABLE concur_reindex_part;
+SELECT * FROM compare_relfilenode_part('reindex_index_status');
+ relname | relkind | state
+-------------------------------+---------+--------------------------
+ concur_reindex_part_index | I | relfilenode is unchanged
+ concur_reindex_part_index_0 | I | relfilenode is unchanged
+ concur_reindex_part_index_0_1 | i | relfilenode has changed
+ concur_reindex_part_index_0_2 | i | relfilenode has changed
+ concur_reindex_part_index_10 | I | relfilenode is unchanged
+(5 rows)
+
+DROP TABLE reindex_index_status;
+-- concurrent case.
+SELECT create_relfilenode_part('reindex_index_status', 'concur_reindex_part_index');
+ create_relfilenode_part
+-------------------------
+
+(1 row)
+
+REINDEX TABLE CONCURRENTLY concur_reindex_part;
+SELECT * FROM compare_relfilenode_part('reindex_index_status');
+ relname | relkind | state
+-------------------------------+---------+--------------------------
+ concur_reindex_part_index | I | relfilenode is unchanged
+ concur_reindex_part_index_0 | I | relfilenode is unchanged
+ concur_reindex_part_index_0_1 | i | relfilenode has changed
+ concur_reindex_part_index_0_2 | i | relfilenode has changed
+ concur_reindex_part_index_10 | I | relfilenode is unchanged
+(5 rows)
+
+DROP TABLE reindex_index_status;
+DROP FUNCTION create_relfilenode_part;
+DROP FUNCTION compare_relfilenode_part;
+-- Cleanup of partition tree used for REINDEX test.
+DROP TABLE concur_reindex_part;
+-- Check errors
+-- Cannot run inside a transaction block
+BEGIN;
+REINDEX TABLE CONCURRENTLY concur_reindex_tab;
+ERROR: REINDEX CONCURRENTLY cannot run inside a transaction block
+COMMIT;
+REINDEX TABLE CONCURRENTLY pg_class; -- no catalog relation
+ERROR: cannot reindex system catalogs concurrently
+REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index
+ERROR: cannot reindex system catalogs concurrently
+-- These are the toast table and index of pg_authid.
+REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table
+ERROR: cannot reindex system catalogs concurrently
+REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast index
+ERROR: cannot reindex system catalogs concurrently
+REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
+ERROR: cannot reindex system catalogs concurrently
+-- Warns about catalog relations
+REINDEX SCHEMA CONCURRENTLY pg_catalog;
+WARNING: cannot reindex system catalogs concurrently, skipping all
+-- Check the relation status, there should not be invalid indexes
+\d concur_reindex_tab
+ Table "public.concur_reindex_tab"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | not null |
+ c2 | text | | |
+Indexes:
+ "concur_reindex_ind1" PRIMARY KEY, btree (c1)
+ "concur_reindex_ind2" btree (c2)
+ "concur_reindex_ind3" UNIQUE, btree (abs(c1))
+ "concur_reindex_ind4" btree (c1, c1, c2)
+Referenced by:
+ TABLE "concur_reindex_tab2" CONSTRAINT "concur_reindex_tab2_c1_fkey" FOREIGN KEY (c1) REFERENCES concur_reindex_tab(c1)
+
+DROP MATERIALIZED VIEW concur_reindex_matview;
+DROP TABLE concur_reindex_tab, concur_reindex_tab2, concur_reindex_tab3;
+-- Check handling of invalid indexes
+CREATE TABLE concur_reindex_tab4 (c1 int);
+INSERT INTO concur_reindex_tab4 VALUES (1), (1), (2);
+-- This trick creates an invalid index.
+CREATE UNIQUE INDEX CONCURRENTLY concur_reindex_ind5 ON concur_reindex_tab4 (c1);
+ERROR: could not create unique index "concur_reindex_ind5"
+DETAIL: Key (c1)=(1) is duplicated.
+-- Reindexing concurrently this index fails with the same failure.
+-- The extra index created is itself invalid, and can be dropped.
+REINDEX INDEX CONCURRENTLY concur_reindex_ind5;
+ERROR: could not create unique index "concur_reindex_ind5_ccnew"
+DETAIL: Key (c1)=(1) is duplicated.
+\d concur_reindex_tab4
+ Table "public.concur_reindex_tab4"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+Indexes:
+ "concur_reindex_ind5" UNIQUE, btree (c1) INVALID
+ "concur_reindex_ind5_ccnew" UNIQUE, btree (c1) INVALID
+
+DROP INDEX concur_reindex_ind5_ccnew;
+-- This makes the previous failure go away, so the index can become valid.
+DELETE FROM concur_reindex_tab4 WHERE c1 = 1;
+-- The invalid index is not processed when running REINDEX TABLE.
+REINDEX TABLE CONCURRENTLY concur_reindex_tab4;
+WARNING: cannot reindex invalid index "public.concur_reindex_ind5" concurrently, skipping
+NOTICE: table "concur_reindex_tab4" has no indexes that can be reindexed concurrently
+\d concur_reindex_tab4
+ Table "public.concur_reindex_tab4"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+Indexes:
+ "concur_reindex_ind5" UNIQUE, btree (c1) INVALID
+
+-- But it is fixed with REINDEX INDEX.
+REINDEX INDEX CONCURRENTLY concur_reindex_ind5;
+\d concur_reindex_tab4
+ Table "public.concur_reindex_tab4"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+Indexes:
+ "concur_reindex_ind5" UNIQUE, btree (c1)
+
+DROP TABLE concur_reindex_tab4;
+-- Check handling of indexes with expressions and predicates. The
+-- definitions of the rebuilt indexes should match the original
+-- definitions.
+CREATE TABLE concur_exprs_tab (c1 int , c2 boolean);
+INSERT INTO concur_exprs_tab (c1, c2) VALUES (1369652450, FALSE),
+ (414515746, TRUE),
+ (897778963, FALSE);
+CREATE UNIQUE INDEX concur_exprs_index_expr
+ ON concur_exprs_tab ((c1::text COLLATE "C"));
+CREATE UNIQUE INDEX concur_exprs_index_pred ON concur_exprs_tab (c1)
+ WHERE (c1::text > 500000000::text COLLATE "C");
+CREATE UNIQUE INDEX concur_exprs_index_pred_2
+ ON concur_exprs_tab ((1 / c1))
+ WHERE ('-H') >= (c2::TEXT) COLLATE "C";
+ALTER INDEX concur_exprs_index_expr ALTER COLUMN 1 SET STATISTICS 100;
+ANALYZE concur_exprs_tab;
+SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN (
+ 'concur_exprs_index_expr'::regclass,
+ 'concur_exprs_index_pred'::regclass,
+ 'concur_exprs_index_pred_2'::regclass)
+ GROUP BY starelid ORDER BY starelid::regclass::text;
+ starelid | count
+-------------------------+-------
+ concur_exprs_index_expr | 1
+(1 row)
+
+SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass);
+ pg_get_indexdef
+---------------------------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX concur_exprs_index_expr ON public.concur_exprs_tab USING btree (((c1)::text) COLLATE "C")
+(1 row)
+
+SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass);
+ pg_get_indexdef
+----------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX concur_exprs_index_pred ON public.concur_exprs_tab USING btree (c1) WHERE ((c1)::text > ((500000000)::text COLLATE "C"))
+(1 row)
+
+SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass);
+ pg_get_indexdef
+--------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX concur_exprs_index_pred_2 ON public.concur_exprs_tab USING btree (((1 / c1))) WHERE ('-H'::text >= ((c2)::text COLLATE "C"))
+(1 row)
+
+REINDEX TABLE CONCURRENTLY concur_exprs_tab;
+SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass);
+ pg_get_indexdef
+---------------------------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX concur_exprs_index_expr ON public.concur_exprs_tab USING btree (((c1)::text) COLLATE "C")
+(1 row)
+
+SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass);
+ pg_get_indexdef
+----------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX concur_exprs_index_pred ON public.concur_exprs_tab USING btree (c1) WHERE ((c1)::text > ((500000000)::text COLLATE "C"))
+(1 row)
+
+SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass);
+ pg_get_indexdef
+--------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX concur_exprs_index_pred_2 ON public.concur_exprs_tab USING btree (((1 / c1))) WHERE ('-H'::text >= ((c2)::text COLLATE "C"))
+(1 row)
+
+-- ALTER TABLE recreates the indexes, which should keep their collations.
+ALTER TABLE concur_exprs_tab ALTER c2 TYPE TEXT;
+SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass);
+ pg_get_indexdef
+---------------------------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX concur_exprs_index_expr ON public.concur_exprs_tab USING btree (((c1)::text) COLLATE "C")
+(1 row)
+
+SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass);
+ pg_get_indexdef
+----------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX concur_exprs_index_pred ON public.concur_exprs_tab USING btree (c1) WHERE ((c1)::text > ((500000000)::text COLLATE "C"))
+(1 row)
+
+SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass);
+ pg_get_indexdef
+------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX concur_exprs_index_pred_2 ON public.concur_exprs_tab USING btree (((1 / c1))) WHERE ('-H'::text >= (c2 COLLATE "C"))
+(1 row)
+
+-- Statistics should remain intact.
+SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN (
+ 'concur_exprs_index_expr'::regclass,
+ 'concur_exprs_index_pred'::regclass,
+ 'concur_exprs_index_pred_2'::regclass)
+ GROUP BY starelid ORDER BY starelid::regclass::text;
+ starelid | count
+-------------------------+-------
+ concur_exprs_index_expr | 1
+(1 row)
+
+-- attstattarget should remain intact
+SELECT attrelid::regclass, attnum, attstattarget
+ FROM pg_attribute WHERE attrelid IN (
+ 'concur_exprs_index_expr'::regclass,
+ 'concur_exprs_index_pred'::regclass,
+ 'concur_exprs_index_pred_2'::regclass)
+ ORDER BY attrelid::regclass::text, attnum;
+ attrelid | attnum | attstattarget
+---------------------------+--------+---------------
+ concur_exprs_index_expr | 1 | 100
+ concur_exprs_index_pred | 1 | -1
+ concur_exprs_index_pred_2 | 1 | -1
+(3 rows)
+
+DROP TABLE concur_exprs_tab;
+-- Temporary tables and on-commit actions, where CONCURRENTLY is ignored.
+-- ON COMMIT PRESERVE ROWS, the default.
+CREATE TEMP TABLE concur_temp_tab_1 (c1 int, c2 text)
+ ON COMMIT PRESERVE ROWS;
+INSERT INTO concur_temp_tab_1 VALUES (1, 'foo'), (2, 'bar');
+CREATE INDEX concur_temp_ind_1 ON concur_temp_tab_1(c2);
+REINDEX TABLE CONCURRENTLY concur_temp_tab_1;
+REINDEX INDEX CONCURRENTLY concur_temp_ind_1;
+-- Still fails in transaction blocks
+BEGIN;
+REINDEX INDEX CONCURRENTLY concur_temp_ind_1;
+ERROR: REINDEX CONCURRENTLY cannot run inside a transaction block
+COMMIT;
+-- ON COMMIT DELETE ROWS
+CREATE TEMP TABLE concur_temp_tab_2 (c1 int, c2 text)
+ ON COMMIT DELETE ROWS;
+CREATE INDEX concur_temp_ind_2 ON concur_temp_tab_2(c2);
+REINDEX TABLE CONCURRENTLY concur_temp_tab_2;
+REINDEX INDEX CONCURRENTLY concur_temp_ind_2;
+-- ON COMMIT DROP
+BEGIN;
+CREATE TEMP TABLE concur_temp_tab_3 (c1 int, c2 text)
+ ON COMMIT PRESERVE ROWS;
+INSERT INTO concur_temp_tab_3 VALUES (1, 'foo'), (2, 'bar');
+CREATE INDEX concur_temp_ind_3 ON concur_temp_tab_3(c2);
+-- Fails when running in a transaction
+REINDEX INDEX CONCURRENTLY concur_temp_ind_3;
+ERROR: REINDEX CONCURRENTLY cannot run inside a transaction block
+COMMIT;
+-- REINDEX SCHEMA processes all temporary relations
+CREATE TABLE reindex_temp_before AS
+SELECT oid, relname, relfilenode, relkind, reltoastrelid
+ FROM pg_class
+ WHERE relname IN ('concur_temp_ind_1', 'concur_temp_ind_2');
+SELECT pg_my_temp_schema()::regnamespace as temp_schema_name \gset
+REINDEX SCHEMA CONCURRENTLY :temp_schema_name;
+SELECT b.relname,
+ b.relkind,
+ CASE WHEN a.relfilenode = b.relfilenode THEN 'relfilenode is unchanged'
+ ELSE 'relfilenode has changed' END
+ FROM reindex_temp_before b JOIN pg_class a ON b.oid = a.oid
+ ORDER BY 1;
+ relname | relkind | case
+-------------------+---------+-------------------------
+ concur_temp_ind_1 | i | relfilenode has changed
+ concur_temp_ind_2 | i | relfilenode has changed
+(2 rows)
+
+DROP TABLE concur_temp_tab_1, concur_temp_tab_2, reindex_temp_before;
+--
+-- REINDEX SCHEMA
+--
+REINDEX SCHEMA schema_to_reindex; -- failure, schema does not exist
+ERROR: schema "schema_to_reindex" does not exist
+CREATE SCHEMA schema_to_reindex;
+SET search_path = 'schema_to_reindex';
+CREATE TABLE table1(col1 SERIAL PRIMARY KEY);
+INSERT INTO table1 SELECT generate_series(1,400);
+CREATE TABLE table2(col1 SERIAL PRIMARY KEY, col2 TEXT NOT NULL);
+INSERT INTO table2 SELECT generate_series(1,400), 'abc';
+CREATE INDEX ON table2(col2);
+CREATE MATERIALIZED VIEW matview AS SELECT col1 FROM table2;
+CREATE INDEX ON matview(col1);
+CREATE VIEW view AS SELECT col2 FROM table2;
+CREATE TABLE reindex_before AS
+SELECT oid, relname, relfilenode, relkind, reltoastrelid
+ FROM pg_class
+ where relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'schema_to_reindex');
+INSERT INTO reindex_before
+SELECT oid, 'pg_toast_TABLE', relfilenode, relkind, reltoastrelid
+FROM pg_class WHERE oid IN
+ (SELECT reltoastrelid FROM reindex_before WHERE reltoastrelid > 0);
+INSERT INTO reindex_before
+SELECT oid, 'pg_toast_TABLE_index', relfilenode, relkind, reltoastrelid
+FROM pg_class where oid in
+ (select indexrelid from pg_index where indrelid in
+ (select reltoastrelid from reindex_before where reltoastrelid > 0));
+REINDEX SCHEMA schema_to_reindex;
+CREATE TABLE reindex_after AS SELECT oid, relname, relfilenode, relkind
+ FROM pg_class
+ where relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'schema_to_reindex');
+SELECT b.relname,
+ b.relkind,
+ CASE WHEN a.relfilenode = b.relfilenode THEN 'relfilenode is unchanged'
+ ELSE 'relfilenode has changed' END
+ FROM reindex_before b JOIN pg_class a ON b.oid = a.oid
+ ORDER BY 1;
+ relname | relkind | case
+----------------------+---------+--------------------------
+ matview | m | relfilenode is unchanged
+ matview_col1_idx | i | relfilenode has changed
+ pg_toast_TABLE | t | relfilenode is unchanged
+ pg_toast_TABLE_index | i | relfilenode has changed
+ table1 | r | relfilenode is unchanged
+ table1_col1_seq | S | relfilenode is unchanged
+ table1_pkey | i | relfilenode has changed
+ table2 | r | relfilenode is unchanged
+ table2_col1_seq | S | relfilenode is unchanged
+ table2_col2_idx | i | relfilenode has changed
+ table2_pkey | i | relfilenode has changed
+ view | v | relfilenode is unchanged
+(12 rows)
+
+REINDEX SCHEMA schema_to_reindex;
+BEGIN;
+REINDEX SCHEMA schema_to_reindex; -- failure, cannot run in a transaction
+ERROR: REINDEX SCHEMA cannot run inside a transaction block
+END;
+-- concurrently
+REINDEX SCHEMA CONCURRENTLY schema_to_reindex;
+-- Failure for unauthorized user
+CREATE ROLE regress_reindexuser NOLOGIN;
+SET SESSION ROLE regress_reindexuser;
+REINDEX SCHEMA schema_to_reindex;
+ERROR: must be owner of schema schema_to_reindex
+-- Permission failures with toast tables and indexes (pg_authid here)
+RESET ROLE;
+GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
+SET SESSION ROLE regress_reindexuser;
+REINDEX TABLE pg_toast.pg_toast_1260;
+ERROR: must be owner of table pg_toast_1260
+REINDEX INDEX pg_toast.pg_toast_1260_index;
+ERROR: must be owner of index pg_toast_1260_index
+-- Clean up
+RESET ROLE;
+REVOKE USAGE ON SCHEMA pg_toast FROM regress_reindexuser;
+DROP ROLE regress_reindexuser;
+DROP SCHEMA schema_to_reindex CASCADE;
+NOTICE: drop cascades to 6 other objects
+DETAIL: drop cascades to table table1
+drop cascades to table table2
+drop cascades to materialized view matview
+drop cascades to view view
+drop cascades to table reindex_before
+drop cascades to table reindex_after
diff --git a/src/test/regress/expected/create_index_spgist.out b/src/test/regress/expected/create_index_spgist.out
new file mode 100644
index 0000000..f4fb08a
--- /dev/null
+++ b/src/test/regress/expected/create_index_spgist.out
@@ -0,0 +1,1338 @@
+--
+-- SP-GiST index tests
+--
+CREATE TABLE quad_point_tbl AS
+ SELECT point(unique1,unique2) AS p FROM tenk1;
+INSERT INTO quad_point_tbl
+ SELECT '(333.0,400.0)'::point FROM generate_series(1,1000);
+INSERT INTO quad_point_tbl VALUES (NULL), (NULL), (NULL);
+CREATE INDEX sp_quad_ind ON quad_point_tbl USING spgist (p);
+CREATE TABLE kd_point_tbl AS SELECT * FROM quad_point_tbl;
+CREATE INDEX sp_kd_ind ON kd_point_tbl USING spgist (p kd_point_ops);
+CREATE TABLE radix_text_tbl AS
+ SELECT name AS t FROM road WHERE name !~ '^[0-9]';
+INSERT INTO radix_text_tbl
+ SELECT 'P0123456789abcdef' FROM generate_series(1,1000);
+INSERT INTO radix_text_tbl VALUES ('P0123456789abcde');
+INSERT INTO radix_text_tbl VALUES ('P0123456789abcdefF');
+CREATE INDEX sp_radix_ind ON radix_text_tbl USING spgist (t);
+-- get non-indexed results for comparison purposes
+SET enable_seqscan = ON;
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = OFF;
+SELECT count(*) FROM quad_point_tbl WHERE p IS NULL;
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p IS NOT NULL;
+ count
+-------
+ 11000
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl;
+ count
+-------
+ 11003
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+ count
+-------
+ 1057
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+ count
+-------
+ 1057
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
+ count
+-------
+ 6000
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
+ count
+-------
+ 4999
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <<| '(5000, 4000)';
+ count
+-------
+ 5000
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p |>> '(5000, 4000)';
+ count
+-------
+ 5999
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
+ count
+-------
+ 1
+(1 row)
+
+CREATE TEMP TABLE quad_point_tbl_ord_seq1 AS
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM quad_point_tbl;
+CREATE TEMP TABLE quad_point_tbl_ord_seq2 AS
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+CREATE TEMP TABLE quad_point_tbl_ord_seq3 AS
+SELECT row_number() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
+FROM quad_point_tbl WHERE p IS NOT NULL;
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
+ count
+-------
+ 1000
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdefF';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t < 'Aztec Ct ';
+ count
+-------
+ 272
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~<~ 'Aztec Ct ';
+ count
+-------
+ 272
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t <= 'Aztec Ct ';
+ count
+-------
+ 273
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~<=~ 'Aztec Ct ';
+ count
+-------
+ 273
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Aztec Ct ';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Worth St ';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t >= 'Worth St ';
+ count
+-------
+ 50
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~>=~ 'Worth St ';
+ count
+-------
+ 50
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t > 'Worth St ';
+ count
+-------
+ 48
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~>~ 'Worth St ';
+ count
+-------
+ 48
+(1 row)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
+ count
+-------
+ 2
+(1 row)
+
+-- Now check the results from plain indexscan
+SET enable_seqscan = OFF;
+SET enable_indexscan = ON;
+SET enable_bitmapscan = OFF;
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p IS NULL;
+ QUERY PLAN
+-----------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+ Index Cond: (p IS NULL)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p IS NULL;
+ count
+-------
+ 3
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p IS NOT NULL;
+ QUERY PLAN
+-----------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+ Index Cond: (p IS NOT NULL)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p IS NOT NULL;
+ count
+-------
+ 11000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl;
+ QUERY PLAN
+-----------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+(2 rows)
+
+SELECT count(*) FROM quad_point_tbl;
+ count
+-------
+ 11003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+ QUERY PLAN
+-----------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+ Index Cond: (p <@ '(1000,1000),(200,200)'::box)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+ count
+-------
+ 1057
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+ QUERY PLAN
+-----------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+ Index Cond: (p <@ '(1000,1000),(200,200)'::box)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+ count
+-------
+ 1057
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
+ QUERY PLAN
+-----------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+ Index Cond: (p << '(5000,4000)'::point)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
+ count
+-------
+ 6000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
+ QUERY PLAN
+-----------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+ Index Cond: (p >> '(5000,4000)'::point)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
+ count
+-------
+ 4999
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <<| '(5000, 4000)';
+ QUERY PLAN
+-----------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+ Index Cond: (p <<| '(5000,4000)'::point)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <<| '(5000, 4000)';
+ count
+-------
+ 5000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p |>> '(5000, 4000)';
+ QUERY PLAN
+-----------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+ Index Cond: (p |>> '(5000,4000)'::point)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p |>> '(5000, 4000)';
+ count
+-------
+ 5999
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
+ QUERY PLAN
+-----------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+ Index Cond: (p ~= '(4585,365)'::point)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM quad_point_tbl;
+ QUERY PLAN
+-----------------------------------------------------------
+ WindowAgg
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+ Order By: (p <-> '(0,0)'::point)
+(3 rows)
+
+CREATE TEMP TABLE quad_point_tbl_ord_idx1 AS
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM quad_point_tbl;
+SELECT * FROM quad_point_tbl_ord_seq1 seq FULL JOIN quad_point_tbl_ord_idx1 idx
+ON seq.n = idx.n
+WHERE seq.dist IS DISTINCT FROM idx.dist;
+ n | dist | p | n | dist | p
+---+------+---+---+------+---
+(0 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+ QUERY PLAN
+-----------------------------------------------------------
+ WindowAgg
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+ Index Cond: (p <@ '(1000,1000),(200,200)'::box)
+ Order By: (p <-> '(0,0)'::point)
+(4 rows)
+
+CREATE TEMP TABLE quad_point_tbl_ord_idx2 AS
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+SELECT * FROM quad_point_tbl_ord_seq2 seq FULL JOIN quad_point_tbl_ord_idx2 idx
+ON seq.n = idx.n
+WHERE seq.dist IS DISTINCT FROM idx.dist;
+ n | dist | p | n | dist | p
+---+------+---+---+------+---
+(0 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT row_number() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
+FROM quad_point_tbl WHERE p IS NOT NULL;
+ QUERY PLAN
+-----------------------------------------------------------
+ WindowAgg
+ -> Index Only Scan using sp_quad_ind on quad_point_tbl
+ Index Cond: (p IS NOT NULL)
+ Order By: (p <-> '(333,400)'::point)
+(4 rows)
+
+CREATE TEMP TABLE quad_point_tbl_ord_idx3 AS
+SELECT row_number() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
+FROM quad_point_tbl WHERE p IS NOT NULL;
+SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN quad_point_tbl_ord_idx3 idx
+ON seq.n = idx.n
+WHERE seq.dist IS DISTINCT FROM idx.dist;
+ n | dist | p | n | dist | p
+---+------+---+---+------+---
+(0 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+ QUERY PLAN
+---------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_kd_ind on kd_point_tbl
+ Index Cond: (p <@ '(1000,1000),(200,200)'::box)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+ count
+-------
+ 1057
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+ QUERY PLAN
+---------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_kd_ind on kd_point_tbl
+ Index Cond: (p <@ '(1000,1000),(200,200)'::box)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+ count
+-------
+ 1057
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)';
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_kd_ind on kd_point_tbl
+ Index Cond: (p << '(5000,4000)'::point)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)';
+ count
+-------
+ 6000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)';
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_kd_ind on kd_point_tbl
+ Index Cond: (p >> '(5000,4000)'::point)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)';
+ count
+-------
+ 4999
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <<| '(5000, 4000)';
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_kd_ind on kd_point_tbl
+ Index Cond: (p <<| '(5000,4000)'::point)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p <<| '(5000, 4000)';
+ count
+-------
+ 5000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p |>> '(5000, 4000)';
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_kd_ind on kd_point_tbl
+ Index Cond: (p |>> '(5000,4000)'::point)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p |>> '(5000, 4000)';
+ count
+-------
+ 5999
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_kd_ind on kd_point_tbl
+ Index Cond: (p ~= '(4585,365)'::point)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM kd_point_tbl;
+ QUERY PLAN
+-------------------------------------------------------
+ WindowAgg
+ -> Index Only Scan using sp_kd_ind on kd_point_tbl
+ Order By: (p <-> '(0,0)'::point)
+(3 rows)
+
+CREATE TEMP TABLE kd_point_tbl_ord_idx1 AS
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM kd_point_tbl;
+SELECT * FROM quad_point_tbl_ord_seq1 seq FULL JOIN kd_point_tbl_ord_idx1 idx
+ON seq.n = idx.n
+WHERE seq.dist IS DISTINCT FROM idx.dist;
+ n | dist | p | n | dist | p
+---+------+---+---+------+---
+(0 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+ QUERY PLAN
+---------------------------------------------------------
+ WindowAgg
+ -> Index Only Scan using sp_kd_ind on kd_point_tbl
+ Index Cond: (p <@ '(1000,1000),(200,200)'::box)
+ Order By: (p <-> '(0,0)'::point)
+(4 rows)
+
+CREATE TEMP TABLE kd_point_tbl_ord_idx2 AS
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+SELECT * FROM quad_point_tbl_ord_seq2 seq FULL JOIN kd_point_tbl_ord_idx2 idx
+ON seq.n = idx.n
+WHERE seq.dist IS DISTINCT FROM idx.dist;
+ n | dist | p | n | dist | p
+---+------+---+---+------+---
+(0 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT row_number() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
+FROM kd_point_tbl WHERE p IS NOT NULL;
+ QUERY PLAN
+-------------------------------------------------------
+ WindowAgg
+ -> Index Only Scan using sp_kd_ind on kd_point_tbl
+ Index Cond: (p IS NOT NULL)
+ Order By: (p <-> '(333,400)'::point)
+(4 rows)
+
+CREATE TEMP TABLE kd_point_tbl_ord_idx3 AS
+SELECT row_number() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
+FROM kd_point_tbl WHERE p IS NOT NULL;
+SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN kd_point_tbl_ord_idx3 idx
+ON seq.n = idx.n
+WHERE seq.dist IS DISTINCT FROM idx.dist;
+ n | dist | p | n | dist | p
+---+------+---+---+------+---
+(0 rows)
+
+-- test KNN scan with included columns
+-- the distance numbers are not exactly the same across platforms
+SET extra_float_digits = 0;
+CREATE INDEX ON quad_point_tbl_ord_seq1 USING spgist(p) INCLUDE(dist);
+EXPLAIN (COSTS OFF)
+SELECT p, dist FROM quad_point_tbl_ord_seq1 ORDER BY p <-> '0,0' LIMIT 10;
+ QUERY PLAN
+-------------------------------------------------------------------------------------------
+ Limit
+ -> Index Only Scan using quad_point_tbl_ord_seq1_p_dist_idx on quad_point_tbl_ord_seq1
+ Order By: (p <-> '(0,0)'::point)
+(3 rows)
+
+SELECT p, dist FROM quad_point_tbl_ord_seq1 ORDER BY p <-> '0,0' LIMIT 10;
+ p | dist
+-----------+------------------
+ (59,21) | 62.6258732474047
+ (88,104) | 136.235090927411
+ (39,143) | 148.222805262888
+ (139,160) | 211.945747775227
+ (209,38) | 212.42645786248
+ (157,156) | 221.325552072055
+ (175,150) | 230.488611432322
+ (236,34) | 238.436574375661
+ (263,28) | 264.486294540946
+ (322,53) | 326.33265236565
+(10 rows)
+
+RESET extra_float_digits;
+-- check ORDER BY distance to NULL
+SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt, p <-> '0,0' LIMIT 1)
+FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt);
+ p
+-------------
+ (59,21)
+ (59,21)
+ (1239,5647)
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
+ QUERY PLAN
+------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t = 'P0123456789abcdef'::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
+ count
+-------
+ 1000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde';
+ QUERY PLAN
+------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t = 'P0123456789abcde'::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdefF';
+ QUERY PLAN
+------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t = 'P0123456789abcdefF'::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdefF';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t < 'Aztec Ct ';
+ QUERY PLAN
+----------------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t < 'Aztec Ct '::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t < 'Aztec Ct ';
+ count
+-------
+ 272
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~<~ 'Aztec Ct ';
+ QUERY PLAN
+------------------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t ~<~ 'Aztec Ct '::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~<~ 'Aztec Ct ';
+ count
+-------
+ 272
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t <= 'Aztec Ct ';
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t <= 'Aztec Ct '::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t <= 'Aztec Ct ';
+ count
+-------
+ 273
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~<=~ 'Aztec Ct ';
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t ~<=~ 'Aztec Ct '::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~<=~ 'Aztec Ct ';
+ count
+-------
+ 273
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Aztec Ct ';
+ QUERY PLAN
+----------------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t = 'Aztec Ct '::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Aztec Ct ';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Worth St ';
+ QUERY PLAN
+----------------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t = 'Worth St '::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Worth St ';
+ count
+-------
+ 2
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t >= 'Worth St ';
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t >= 'Worth St '::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t >= 'Worth St ';
+ count
+-------
+ 50
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~>=~ 'Worth St ';
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t ~>=~ 'Worth St '::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~>=~ 'Worth St ';
+ count
+-------
+ 50
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t > 'Worth St ';
+ QUERY PLAN
+----------------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t > 'Worth St '::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t > 'Worth St ';
+ count
+-------
+ 48
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~>~ 'Worth St ';
+ QUERY PLAN
+------------------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t ~>~ 'Worth St '::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~>~ 'Worth St ';
+ count
+-------
+ 48
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
+ QUERY PLAN
+------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using sp_radix_ind on radix_text_tbl
+ Index Cond: (t ^@ 'Worth'::text)
+(3 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
+ count
+-------
+ 2
+(1 row)
+
+-- Now check the results from bitmap indexscan
+SET enable_seqscan = OFF;
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = ON;
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p IS NULL;
+ QUERY PLAN
+----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_point_tbl
+ Recheck Cond: (p IS NULL)
+ -> Bitmap Index Scan on sp_quad_ind
+ Index Cond: (p IS NULL)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p IS NULL;
+ count
+-------
+ 3
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p IS NOT NULL;
+ QUERY PLAN
+----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_point_tbl
+ Recheck Cond: (p IS NOT NULL)
+ -> Bitmap Index Scan on sp_quad_ind
+ Index Cond: (p IS NOT NULL)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p IS NOT NULL;
+ count
+-------
+ 11000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl;
+ QUERY PLAN
+----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_point_tbl
+ -> Bitmap Index Scan on sp_quad_ind
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl;
+ count
+-------
+ 11003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+ QUERY PLAN
+---------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_point_tbl
+ Recheck Cond: (p <@ '(1000,1000),(200,200)'::box)
+ -> Bitmap Index Scan on sp_quad_ind
+ Index Cond: (p <@ '(1000,1000),(200,200)'::box)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+ count
+-------
+ 1057
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+ QUERY PLAN
+---------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_point_tbl
+ Recheck Cond: ('(1000,1000),(200,200)'::box @> p)
+ -> Bitmap Index Scan on sp_quad_ind
+ Index Cond: (p <@ '(1000,1000),(200,200)'::box)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+ count
+-------
+ 1057
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_point_tbl
+ Recheck Cond: (p << '(5000,4000)'::point)
+ -> Bitmap Index Scan on sp_quad_ind
+ Index Cond: (p << '(5000,4000)'::point)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
+ count
+-------
+ 6000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_point_tbl
+ Recheck Cond: (p >> '(5000,4000)'::point)
+ -> Bitmap Index Scan on sp_quad_ind
+ Index Cond: (p >> '(5000,4000)'::point)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
+ count
+-------
+ 4999
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <<| '(5000, 4000)';
+ QUERY PLAN
+--------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_point_tbl
+ Recheck Cond: (p <<| '(5000,4000)'::point)
+ -> Bitmap Index Scan on sp_quad_ind
+ Index Cond: (p <<| '(5000,4000)'::point)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <<| '(5000, 4000)';
+ count
+-------
+ 5000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p |>> '(5000, 4000)';
+ QUERY PLAN
+--------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_point_tbl
+ Recheck Cond: (p |>> '(5000,4000)'::point)
+ -> Bitmap Index Scan on sp_quad_ind
+ Index Cond: (p |>> '(5000,4000)'::point)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p |>> '(5000, 4000)';
+ count
+-------
+ 5999
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_point_tbl
+ Recheck Cond: (p ~= '(4585,365)'::point)
+ -> Bitmap Index Scan on sp_quad_ind
+ Index Cond: (p ~= '(4585,365)'::point)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+ QUERY PLAN
+---------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on kd_point_tbl
+ Recheck Cond: (p <@ '(1000,1000),(200,200)'::box)
+ -> Bitmap Index Scan on sp_kd_ind
+ Index Cond: (p <@ '(1000,1000),(200,200)'::box)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+ count
+-------
+ 1057
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+ QUERY PLAN
+---------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on kd_point_tbl
+ Recheck Cond: ('(1000,1000),(200,200)'::box @> p)
+ -> Bitmap Index Scan on sp_kd_ind
+ Index Cond: (p <@ '(1000,1000),(200,200)'::box)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+ count
+-------
+ 1057
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)';
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on kd_point_tbl
+ Recheck Cond: (p << '(5000,4000)'::point)
+ -> Bitmap Index Scan on sp_kd_ind
+ Index Cond: (p << '(5000,4000)'::point)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)';
+ count
+-------
+ 6000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)';
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on kd_point_tbl
+ Recheck Cond: (p >> '(5000,4000)'::point)
+ -> Bitmap Index Scan on sp_kd_ind
+ Index Cond: (p >> '(5000,4000)'::point)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)';
+ count
+-------
+ 4999
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <<| '(5000, 4000)';
+ QUERY PLAN
+--------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on kd_point_tbl
+ Recheck Cond: (p <<| '(5000,4000)'::point)
+ -> Bitmap Index Scan on sp_kd_ind
+ Index Cond: (p <<| '(5000,4000)'::point)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p <<| '(5000, 4000)';
+ count
+-------
+ 5000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p |>> '(5000, 4000)';
+ QUERY PLAN
+--------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on kd_point_tbl
+ Recheck Cond: (p |>> '(5000,4000)'::point)
+ -> Bitmap Index Scan on sp_kd_ind
+ Index Cond: (p |>> '(5000,4000)'::point)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p |>> '(5000, 4000)';
+ count
+-------
+ 5999
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on kd_point_tbl
+ Recheck Cond: (p ~= '(4585,365)'::point)
+ -> Bitmap Index Scan on sp_kd_ind
+ Index Cond: (p ~= '(4585,365)'::point)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
+ QUERY PLAN
+-----------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t = 'P0123456789abcdef'::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t = 'P0123456789abcdef'::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
+ count
+-------
+ 1000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde';
+ QUERY PLAN
+----------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t = 'P0123456789abcde'::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t = 'P0123456789abcde'::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdefF';
+ QUERY PLAN
+------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t = 'P0123456789abcdefF'::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t = 'P0123456789abcdefF'::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdefF';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t < 'Aztec Ct ';
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t < 'Aztec Ct '::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t < 'Aztec Ct '::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t < 'Aztec Ct ';
+ count
+-------
+ 272
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~<~ 'Aztec Ct ';
+ QUERY PLAN
+------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t ~<~ 'Aztec Ct '::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t ~<~ 'Aztec Ct '::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~<~ 'Aztec Ct ';
+ count
+-------
+ 272
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t <= 'Aztec Ct ';
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t <= 'Aztec Ct '::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t <= 'Aztec Ct '::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t <= 'Aztec Ct ';
+ count
+-------
+ 273
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~<=~ 'Aztec Ct ';
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t ~<=~ 'Aztec Ct '::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t ~<=~ 'Aztec Ct '::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~<=~ 'Aztec Ct ';
+ count
+-------
+ 273
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Aztec Ct ';
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t = 'Aztec Ct '::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t = 'Aztec Ct '::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Aztec Ct ';
+ count
+-------
+ 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Worth St ';
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t = 'Worth St '::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t = 'Worth St '::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Worth St ';
+ count
+-------
+ 2
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t >= 'Worth St ';
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t >= 'Worth St '::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t >= 'Worth St '::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t >= 'Worth St ';
+ count
+-------
+ 50
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~>=~ 'Worth St ';
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t ~>=~ 'Worth St '::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t ~>=~ 'Worth St '::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~>=~ 'Worth St ';
+ count
+-------
+ 50
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t > 'Worth St ';
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t > 'Worth St '::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t > 'Worth St '::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t > 'Worth St ';
+ count
+-------
+ 48
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~>~ 'Worth St ';
+ QUERY PLAN
+------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t ~>~ 'Worth St '::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t ~>~ 'Worth St '::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~>~ 'Worth St ';
+ count
+-------
+ 48
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
+ QUERY PLAN
+------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on radix_text_tbl
+ Recheck Cond: (t ^@ 'Worth'::text)
+ -> Bitmap Index Scan on sp_radix_ind
+ Index Cond: (t ^@ 'Worth'::text)
+(5 rows)
+
+SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
+ count
+-------
+ 2
+(1 row)
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/expected/create_misc.out b/src/test/regress/expected/create_misc.out
new file mode 100644
index 0000000..41bc4d7
--- /dev/null
+++ b/src/test/regress/expected/create_misc.out
@@ -0,0 +1,151 @@
+--
+-- CREATE_MISC
+--
+-- CLASS POPULATION
+-- (any resemblance to real life is purely coincidental)
+--
+INSERT INTO tenk2 SELECT * FROM tenk1;
+CREATE TABLE onek2 AS SELECT * FROM onek;
+INSERT INTO fast_emp4000 SELECT * FROM slow_emp4000;
+SELECT *
+ INTO TABLE Bprime
+ FROM tenk1
+ WHERE unique2 < 1000;
+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');
+INSERT INTO city VALUES
+('Podunk', '(1,2),(3,4)', '100,127,1000'),
+('Gotham', '(1000,34),(1100,334)', '123456,127,-1000,6789');
+TABLE city;
+ name | location | budget
+--------+----------------------+-----------------------
+ Podunk | (3,4),(1,2) | 100,127,1000,0
+ Gotham | (1100,334),(1000,34) | 123456,127,-1000,6789
+(2 rows)
+
+SELECT *
+ INTO TABLE ramp
+ FROM road
+ WHERE name ~ '.*Ramp';
+INSERT INTO ihighway
+ SELECT *
+ FROM road
+ WHERE name ~ 'I- .*';
+INSERT INTO shighway
+ SELECT *
+ FROM road
+ WHERE name ~ 'State Hwy.*';
+UPDATE shighway
+ SET surface = 'asphalt';
+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;
+--
+-- for internal portal (cursor) tests
+--
+CREATE TABLE iportaltest (
+ i int4,
+ d float4,
+ p polygon
+);
+INSERT INTO iportaltest (i, d, p)
+ VALUES (1, 3.567, '(3.0,1.0),(4.0,2.0)'::polygon);
+INSERT INTO iportaltest (i, d, p)
+ VALUES (2, 89.05, '(4.0,2.0),(3.0,1.0)'::polygon);
diff --git a/src/test/regress/expected/create_operator.out b/src/test/regress/expected/create_operator.out
new file mode 100644
index 0000000..5303277
--- /dev/null
+++ b/src/test/regress/expected/create_operator.out
@@ -0,0 +1,293 @@
+--
+-- CREATE_OPERATOR
+--
+CREATE OPERATOR ## (
+ leftarg = path,
+ rightarg = path,
+ function = path_inter,
+ commutator = ##
+);
+CREATE OPERATOR <% (
+ leftarg = point,
+ rightarg = widget,
+ procedure = pt_in_widget,
+ commutator = >% ,
+ negator = >=%
+);
+CREATE OPERATOR @#@ (
+ rightarg = int8, -- prefix
+ procedure = factorial
+);
+CREATE OPERATOR #%# (
+ leftarg = int8, -- fail, postfix is no longer supported
+ procedure = factorial
+);
+ERROR: operator right argument type must be specified
+DETAIL: Postfix operators are not supported.
+-- Test operator created above
+SELECT point '(1,2)' <% widget '(0,0,3)' AS t,
+ point '(1,2)' <% widget '(0,0,1)' AS f;
+ t | f
+---+---
+ t | f
+(1 row)
+
+-- Test comments
+COMMENT ON OPERATOR ###### (NONE, int4) IS 'bad prefix';
+ERROR: operator does not exist: ###### integer
+COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad postfix';
+ERROR: postfix operators are not supported
+COMMENT ON OPERATOR ###### (int4, int8) IS 'bad infix';
+ERROR: operator does not exist: integer ###### bigint
+-- Check that DROP on a nonexistent op behaves sanely, too
+DROP OPERATOR ###### (NONE, int4);
+ERROR: operator does not exist: ###### integer
+DROP OPERATOR ###### (int4, NONE);
+ERROR: postfix operators are not supported
+DROP OPERATOR ###### (int4, int8);
+ERROR: operator does not exist: integer ###### bigint
+-- => is disallowed as an operator name now
+CREATE OPERATOR => (
+ rightarg = int8,
+ procedure = factorial
+);
+ERROR: syntax error at or near "=>"
+LINE 1: CREATE OPERATOR => (
+ ^
+-- lexing of <=, >=, <>, != has a number of edge cases
+-- (=> is tested elsewhere)
+-- this is legal because ! is not allowed in sql ops
+CREATE OPERATOR !=- (
+ rightarg = int8,
+ procedure = factorial
+);
+SELECT !=- 10;
+ ?column?
+----------
+ 3628800
+(1 row)
+
+-- postfix operators don't work anymore
+SELECT 10 !=-;
+ERROR: syntax error at or near ";"
+LINE 1: SELECT 10 !=-;
+ ^
+-- make sure lexer returns != as <> even in edge cases
+SELECT 2 !=/**/ 1, 2 !=/**/ 2;
+ ?column? | ?column?
+----------+----------
+ t | f
+(1 row)
+
+SELECT 2 !=-- comment to be removed by psql
+ 1;
+ ?column?
+----------
+ t
+(1 row)
+
+DO $$ -- use DO to protect -- from psql
+ declare r boolean;
+ begin
+ execute $e$ select 2 !=-- comment
+ 1 $e$ into r;
+ raise info 'r = %', r;
+ end;
+$$;
+INFO: r = t
+-- check that <= etc. followed by more operator characters are returned
+-- as the correct token with correct precedence
+SELECT true<>-1 BETWEEN 1 AND 1; -- BETWEEN has prec. above <> but below Op
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT false<>/**/1 BETWEEN 1 AND 1;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT false<=-1 BETWEEN 1 AND 1;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT false>=-1 BETWEEN 1 AND 1;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT 2<=/**/3, 3>=/**/2, 2<>/**/3;
+ ?column? | ?column? | ?column?
+----------+----------+----------
+ t | t | t
+(1 row)
+
+SELECT 3<=/**/2, 2>=/**/3, 2<>/**/2;
+ ?column? | ?column? | ?column?
+----------+----------+----------
+ f | f | f
+(1 row)
+
+-- Should fail. CREATE OPERATOR requires USAGE on SCHEMA
+BEGIN TRANSACTION;
+CREATE ROLE regress_rol_op1;
+CREATE SCHEMA schema_op1;
+GRANT USAGE ON SCHEMA schema_op1 TO PUBLIC;
+REVOKE USAGE ON SCHEMA schema_op1 FROM regress_rol_op1;
+SET ROLE regress_rol_op1;
+CREATE OPERATOR schema_op1.#*# (
+ rightarg = int8,
+ procedure = factorial
+);
+ERROR: permission denied for schema schema_op1
+ROLLBACK;
+-- Should fail. SETOF type functions not allowed as argument (testing leftarg)
+BEGIN TRANSACTION;
+CREATE OPERATOR #*# (
+ leftarg = SETOF int8,
+ procedure = factorial
+);
+ERROR: SETOF type not allowed for operator argument
+ROLLBACK;
+-- Should fail. SETOF type functions not allowed as argument (testing rightarg)
+BEGIN TRANSACTION;
+CREATE OPERATOR #*# (
+ rightarg = SETOF int8,
+ procedure = factorial
+);
+ERROR: SETOF type not allowed for operator argument
+ROLLBACK;
+-- Should work. Sample text-book case
+BEGIN TRANSACTION;
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+ SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+ LEFTARG = boolean,
+ RIGHTARG = boolean,
+ PROCEDURE = fn_op2,
+ COMMUTATOR = ===,
+ NEGATOR = !==,
+ RESTRICT = contsel,
+ JOIN = contjoinsel,
+ SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
+);
+ROLLBACK;
+-- Should fail. Invalid attribute
+CREATE OPERATOR #@%# (
+ rightarg = int8,
+ procedure = factorial,
+ invalid_att = int8
+);
+WARNING: operator attribute "invalid_att" not recognized
+-- Should fail. At least rightarg should be mandatorily specified
+CREATE OPERATOR #@%# (
+ procedure = factorial
+);
+ERROR: operator argument types must be specified
+-- Should fail. Procedure should be mandatorily specified
+CREATE OPERATOR #@%# (
+ rightarg = int8
+);
+ERROR: operator function must be specified
+-- Should fail. CREATE OPERATOR requires USAGE on TYPE
+BEGIN TRANSACTION;
+CREATE ROLE regress_rol_op3;
+CREATE TYPE type_op3 AS ENUM ('new', 'open', 'closed');
+CREATE FUNCTION fn_op3(type_op3, int8)
+RETURNS int8 AS $$
+ SELECT NULL::int8;
+$$ LANGUAGE sql IMMUTABLE;
+REVOKE USAGE ON TYPE type_op3 FROM regress_rol_op3;
+REVOKE USAGE ON TYPE type_op3 FROM PUBLIC; -- Need to do this so that regress_rol_op3 is not allowed USAGE via PUBLIC
+SET ROLE regress_rol_op3;
+CREATE OPERATOR #*# (
+ leftarg = type_op3,
+ rightarg = int8,
+ procedure = fn_op3
+);
+ERROR: permission denied for type type_op3
+ROLLBACK;
+-- Should fail. CREATE OPERATOR requires USAGE on TYPE (need to check separately for rightarg)
+BEGIN TRANSACTION;
+CREATE ROLE regress_rol_op4;
+CREATE TYPE type_op4 AS ENUM ('new', 'open', 'closed');
+CREATE FUNCTION fn_op4(int8, type_op4)
+RETURNS int8 AS $$
+ SELECT NULL::int8;
+$$ LANGUAGE sql IMMUTABLE;
+REVOKE USAGE ON TYPE type_op4 FROM regress_rol_op4;
+REVOKE USAGE ON TYPE type_op4 FROM PUBLIC; -- Need to do this so that regress_rol_op3 is not allowed USAGE via PUBLIC
+SET ROLE regress_rol_op4;
+CREATE OPERATOR #*# (
+ leftarg = int8,
+ rightarg = type_op4,
+ procedure = fn_op4
+);
+ERROR: permission denied for type type_op4
+ROLLBACK;
+-- Should fail. CREATE OPERATOR requires EXECUTE on function
+BEGIN TRANSACTION;
+CREATE ROLE regress_rol_op5;
+CREATE TYPE type_op5 AS ENUM ('new', 'open', 'closed');
+CREATE FUNCTION fn_op5(int8, int8)
+RETURNS int8 AS $$
+ SELECT NULL::int8;
+$$ LANGUAGE sql IMMUTABLE;
+REVOKE EXECUTE ON FUNCTION fn_op5(int8, int8) FROM regress_rol_op5;
+REVOKE EXECUTE ON FUNCTION fn_op5(int8, int8) FROM PUBLIC;-- Need to do this so that regress_rol_op3 is not allowed EXECUTE via PUBLIC
+SET ROLE regress_rol_op5;
+CREATE OPERATOR #*# (
+ leftarg = int8,
+ rightarg = int8,
+ procedure = fn_op5
+);
+ERROR: permission denied for function fn_op5
+ROLLBACK;
+-- Should fail. CREATE OPERATOR requires USAGE on return TYPE
+BEGIN TRANSACTION;
+CREATE ROLE regress_rol_op6;
+CREATE TYPE type_op6 AS ENUM ('new', 'open', 'closed');
+CREATE FUNCTION fn_op6(int8, int8)
+RETURNS type_op6 AS $$
+ SELECT NULL::type_op6;
+$$ LANGUAGE sql IMMUTABLE;
+REVOKE USAGE ON TYPE type_op6 FROM regress_rol_op6;
+REVOKE USAGE ON TYPE type_op6 FROM PUBLIC; -- Need to do this so that regress_rol_op3 is not allowed USAGE via PUBLIC
+SET ROLE regress_rol_op6;
+CREATE OPERATOR #*# (
+ leftarg = int8,
+ rightarg = int8,
+ procedure = fn_op6
+);
+ERROR: permission denied for type type_op6
+ROLLBACK;
+-- invalid: non-lowercase quoted identifiers
+CREATE OPERATOR ===
+(
+ "Leftarg" = box,
+ "Rightarg" = box,
+ "Procedure" = area_equal_function,
+ "Commutator" = ===,
+ "Negator" = !==,
+ "Restrict" = area_restriction_function,
+ "Join" = area_join_function,
+ "Hashes",
+ "Merges"
+);
+WARNING: operator attribute "Leftarg" not recognized
+WARNING: operator attribute "Rightarg" not recognized
+WARNING: operator attribute "Procedure" not recognized
+WARNING: operator attribute "Commutator" not recognized
+WARNING: operator attribute "Negator" not recognized
+WARNING: operator attribute "Restrict" not recognized
+WARNING: operator attribute "Join" not recognized
+WARNING: operator attribute "Hashes" not recognized
+WARNING: operator attribute "Merges" not recognized
+ERROR: operator function must be specified
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
new file mode 100644
index 0000000..46c827f
--- /dev/null
+++ b/src/test/regress/expected/create_procedure.out
@@ -0,0 +1,383 @@
+CALL nonexistent(); -- error
+ERROR: procedure nonexistent() does not exist
+LINE 1: CALL nonexistent();
+ ^
+HINT: No procedure matches the given name and argument types. You might need to add explicit type casts.
+CALL random(); -- error
+ERROR: random() is not a procedure
+LINE 1: CALL random();
+ ^
+HINT: To call a function, use SELECT.
+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 ptest1
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest1 | | IN x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1(IN x text)+
+ LANGUAGE sql +
+ AS $procedure$ +
+ INSERT INTO cp_test VALUES (1, x); +
+ $procedure$ +
+
+(1 row)
+
+-- show only normal functions
+\dfn public.*test*1
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------------+------------------+---------------------+------
+ public | cp_testfunc1 | integer | a integer | func
+(1 row)
+
+-- show only procedures
+\dfp public.*test*1
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest1 | | IN x text | proc
+(1 row)
+
+SELECT ptest1('x'); -- error
+ERROR: ptest1(unknown) is a procedure
+LINE 1: SELECT ptest1('x');
+ ^
+HINT: To call a procedure, use CALL.
+CALL ptest1('a'); -- ok
+CALL ptest1('xy' || 'zzy'); -- ok, constant-folded arg
+CALL ptest1(substring(random()::numeric(20,15)::text, 1, 1)); -- ok, volatile arg
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | xyzzy
+(3 rows)
+
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+\df ptest1s
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+---------------------+------
+ public | ptest1s | | IN x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1s'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1s(IN x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO cp_test (a, b) +
+ VALUES (1, ptest1s.x); +
+ END +
+
+(1 row)
+
+CALL ptest1s('b');
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | b
+ 1 | xyzzy
+(4 rows)
+
+-- utitlity functions currently not supported here
+CREATE PROCEDURE ptestx()
+LANGUAGE SQL
+BEGIN ATOMIC
+ CREATE TABLE x (a int);
+END;
+ERROR: CREATE TABLE is not yet supported in unquoted SQL function body
+CREATE PROCEDURE ptest2()
+LANGUAGE SQL
+AS $$
+SELECT 5;
+$$;
+CALL ptest2();
+-- nested CALL
+TRUNCATE cp_test;
+CREATE PROCEDURE ptest3(y text)
+LANGUAGE SQL
+AS $$
+CALL ptest1(y);
+CALL ptest1($1);
+$$;
+CALL ptest3('b');
+SELECT * FROM cp_test;
+ a | b
+---+---
+ 1 | b
+ 1 | b
+(2 rows)
+
+-- output arguments
+CREATE PROCEDURE ptest4a(INOUT a int, INOUT b int)
+LANGUAGE SQL
+AS $$
+SELECT 1, 2;
+$$;
+CALL ptest4a(NULL, NULL);
+ a | b
+---+---
+ 1 | 2
+(1 row)
+
+CREATE PROCEDURE ptest4b(INOUT b int, INOUT a int)
+LANGUAGE SQL
+AS $$
+CALL ptest4a(a, b); -- error, not supported
+$$;
+ERROR: calling procedures with output arguments is not supported in SQL functions
+CONTEXT: SQL function "ptest4b"
+DROP PROCEDURE ptest4a;
+-- named and default parameters
+CREATE OR REPLACE PROCEDURE ptest5(a int, b text, c int default 100)
+LANGUAGE SQL
+AS $$
+INSERT INTO cp_test VALUES(a, b);
+INSERT INTO cp_test VALUES(c, b);
+$$;
+TRUNCATE cp_test;
+CALL ptest5(10, 'Hello', 20);
+CALL ptest5(10, 'Hello');
+CALL ptest5(10, b => 'Hello');
+CALL ptest5(b => 'Hello', a => 10);
+SELECT * FROM cp_test;
+ a | b
+-----+-------
+ 10 | Hello
+ 20 | Hello
+ 10 | Hello
+ 100 | Hello
+ 10 | Hello
+ 100 | Hello
+ 10 | Hello
+ 100 | Hello
+(8 rows)
+
+-- polymorphic types
+CREATE PROCEDURE ptest6(a int, b anyelement)
+LANGUAGE SQL
+AS $$
+SELECT NULL::int;
+$$;
+CALL ptest6(1, 2);
+-- collation assignment
+CREATE PROCEDURE ptest7(a text, b text)
+LANGUAGE SQL
+AS $$
+SELECT a = b;
+$$;
+CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+\df ptest8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest8 | | IN x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest8'::regproc);
+ pg_get_functiondef
+------------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest8(IN x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ END +
+
+(1 row)
+
+CALL ptest8('');
+-- OUT parameters
+CREATE PROCEDURE ptest9(OUT a int)
+LANGUAGE SQL
+AS $$
+INSERT INTO cp_test VALUES (1, 'a');
+SELECT 1;
+$$;
+-- standard way to do a call:
+CALL ptest9(NULL);
+ a
+---
+ 1
+(1 row)
+
+-- you can write an expression, but it's not evaluated
+CALL ptest9(1/0); -- no error
+ a
+---
+ 1
+(1 row)
+
+-- ... and it had better match the type of the parameter
+CALL ptest9(1./0.); -- error
+ERROR: procedure ptest9(numeric) does not exist
+LINE 1: CALL ptest9(1./0.);
+ ^
+HINT: No procedure matches the given name and argument types. You might need to add explicit type casts.
+-- check named-parameter matching
+CREATE PROCEDURE ptest10(OUT a int, IN b int, IN c int)
+LANGUAGE SQL AS $$ SELECT b - c $$;
+CALL ptest10(null, 7, 4);
+ a
+---
+ 3
+(1 row)
+
+CALL ptest10(a => null, b => 8, c => 2);
+ a
+---
+ 6
+(1 row)
+
+CALL ptest10(null, 7, c => 2);
+ a
+---
+ 5
+(1 row)
+
+CALL ptest10(null, c => 4, b => 11);
+ a
+---
+ 7
+(1 row)
+
+CALL ptest10(b => 8, c => 2, a => 0);
+ a
+---
+ 6
+(1 row)
+
+CREATE PROCEDURE ptest11(a OUT int, VARIADIC b int[]) LANGUAGE SQL
+ AS $$ SELECT b[1] + b[2] $$;
+CALL ptest11(null, 11, 12, 13);
+ a
+----
+ 23
+(1 row)
+
+-- check resolution of ambiguous DROP commands
+CREATE PROCEDURE ptest10(IN a int, IN b int, IN c int)
+LANGUAGE SQL AS $$ SELECT a + b - c $$;
+\df ptest10
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+-------------------------------------------+------
+ public | ptest10 | | IN a integer, IN b integer, IN c integer | proc
+ public | ptest10 | | OUT a integer, IN b integer, IN c integer | proc
+(2 rows)
+
+drop procedure ptest10; -- fail
+ERROR: procedure name "ptest10" is not unique
+HINT: Specify the argument list to select the procedure unambiguously.
+drop procedure ptest10(int, int, int); -- fail
+ERROR: procedure name "ptest10" is not unique
+begin;
+drop procedure ptest10(out int, int, int);
+\df ptest10
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+------------------------------------------+------
+ public | ptest10 | | IN a integer, IN b integer, IN c integer | proc
+(1 row)
+
+drop procedure ptest10(int, int, int); -- now this would work
+rollback;
+begin;
+drop procedure ptest10(in int, int, int);
+\df ptest10
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+-------------------------------------------+------
+ public | ptest10 | | OUT a integer, IN b integer, IN c integer | proc
+(1 row)
+
+drop procedure ptest10(int, int, int); -- now this would work
+rollback;
+-- various error cases
+CALL version(); -- error: not a procedure
+ERROR: version() is not a procedure
+LINE 1: CALL version();
+ ^
+HINT: To call a function, use SELECT.
+CALL sum(1); -- error: not a procedure
+ERROR: sum(integer) is not a procedure
+LINE 1: CALL sum(1);
+ ^
+HINT: To call a function, use SELECT.
+CREATE PROCEDURE ptestx() LANGUAGE SQL WINDOW AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
+ERROR: invalid attribute in procedure definition
+LINE 1: CREATE PROCEDURE ptestx() LANGUAGE SQL WINDOW AS $$ INSERT I...
+ ^
+CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
+ERROR: invalid attribute in procedure definition
+LINE 1: CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT I...
+ ^
+CREATE PROCEDURE ptestx(a VARIADIC int[], b OUT int) LANGUAGE SQL
+ AS $$ SELECT a[1] $$;
+ERROR: VARIADIC parameter must be the last parameter
+CREATE PROCEDURE ptestx(a int DEFAULT 42, b OUT int) LANGUAGE SQL
+ AS $$ SELECT a $$;
+ERROR: procedure OUT parameters cannot appear after one with a default value
+ALTER PROCEDURE ptest1(text) STRICT;
+ERROR: invalid attribute in procedure definition
+LINE 1: ALTER PROCEDURE ptest1(text) STRICT;
+ ^
+ALTER FUNCTION ptest1(text) VOLATILE; -- error: not a function
+ERROR: ptest1(text) is not a function
+ALTER PROCEDURE cp_testfunc1(int) VOLATILE; -- error: not a procedure
+ERROR: cp_testfunc1(integer) is not a procedure
+ALTER PROCEDURE nonexistent() VOLATILE;
+ERROR: procedure nonexistent() does not exist
+DROP FUNCTION ptest1(text); -- error: not a function
+ERROR: ptest1(text) is not a function
+DROP PROCEDURE cp_testfunc1(int); -- error: not a procedure
+ERROR: cp_testfunc1(integer) is not a procedure
+DROP PROCEDURE nonexistent();
+ERROR: procedure nonexistent() does not exist
+-- privileges
+CREATE USER regress_cp_user1;
+GRANT INSERT ON cp_test TO regress_cp_user1;
+REVOKE EXECUTE ON PROCEDURE ptest1(text) FROM PUBLIC;
+SET ROLE regress_cp_user1;
+CALL ptest1('a'); -- error
+ERROR: permission denied for procedure ptest1
+RESET ROLE;
+GRANT EXECUTE ON PROCEDURE ptest1(text) TO regress_cp_user1;
+SET ROLE regress_cp_user1;
+CALL ptest1('a'); -- ok
+RESET ROLE;
+-- ROUTINE syntax
+ALTER ROUTINE cp_testfunc1(int) RENAME TO cp_testfunc1a;
+ALTER ROUTINE cp_testfunc1a RENAME TO cp_testfunc1;
+ALTER ROUTINE ptest1(text) RENAME TO ptest1a;
+ALTER ROUTINE ptest1a RENAME TO ptest1;
+DROP ROUTINE cp_testfunc1(int);
+-- cleanup
+DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
+DROP PROCEDURE ptest2;
+DROP TABLE cp_test;
+DROP USER regress_cp_user1;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
new file mode 100644
index 0000000..a958b84
--- /dev/null
+++ b/src/test/regress/expected/create_table.out
@@ -0,0 +1,1321 @@
+--
+-- CREATE_TABLE
+--
+--
+-- CLASS DEFINITIONS
+--
+CREATE TABLE hobbies_r (
+ name text,
+ person text
+);
+CREATE TABLE equipment_r (
+ name text,
+ hobby text
+);
+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
+);
+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
+);
+CREATE TABLE tenk2 (
+ 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
+);
+CREATE TABLE person (
+ name text,
+ age int4,
+ location point
+);
+CREATE TABLE emp (
+ salary int4,
+ manager name
+) INHERITS (person);
+CREATE TABLE student (
+ gpa float8
+) INHERITS (person);
+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"
+CREATE TABLE city (
+ name name,
+ location box,
+ budget city_budget
+);
+CREATE TABLE dept (
+ dname name,
+ mgrname text
+);
+CREATE TABLE slow_emp4000 (
+ home_base box
+);
+CREATE TABLE fast_emp4000 (
+ home_base box
+);
+CREATE TABLE road (
+ name text,
+ thepath path
+);
+CREATE TABLE ihighway () INHERITS (road);
+CREATE TABLE shighway (
+ surface text
+) INHERITS (road);
+CREATE TABLE real_city (
+ pop int4,
+ cname text,
+ outline path
+);
+--
+-- test the "star" operators a bit more thoroughly -- this time,
+-- throw in lots of NULL fields...
+--
+-- 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);
+NOTICE: merging multiple inherited definitions of column "class"
+NOTICE: merging multiple inherited definitions of column "a"
+CREATE TABLE e_star (
+ e int2
+) INHERITS (c_star);
+CREATE TABLE f_star (
+ f polygon
+) INHERITS (e_star);
+CREATE TABLE aggtest (
+ a int2,
+ b float4
+);
+CREATE TABLE hash_i4_heap (
+ seqno int4,
+ random int4
+);
+CREATE TABLE hash_name_heap (
+ seqno int4,
+ random name
+);
+CREATE TABLE hash_txt_heap (
+ seqno int4,
+ random text
+);
+CREATE TABLE hash_f8_heap (
+ seqno int4,
+ random float8
+);
+-- don't include the hash_ovfl_heap stuff in the distribution
+-- the data set is too large for what it's worth
+--
+-- CREATE TABLE hash_ovfl_heap (
+-- x int4,
+-- y int4
+-- );
+CREATE TABLE bt_i4_heap (
+ seqno int4,
+ random int4
+);
+CREATE TABLE bt_name_heap (
+ seqno name,
+ random int4
+);
+CREATE TABLE bt_txt_heap (
+ seqno text,
+ random int4
+);
+CREATE TABLE bt_f8_heap (
+ seqno float8,
+ random int4
+);
+CREATE TABLE array_op_test (
+ seqno int4,
+ i int4[],
+ t text[]
+);
+CREATE TABLE array_index_op_test (
+ seqno int4,
+ i int4[],
+ t text[]
+);
+CREATE TABLE testjsonb (
+ j jsonb
+);
+CREATE TABLE unknowntab (
+ u unknown -- fail
+);
+ERROR: column "u" has pseudo-type unknown
+CREATE TYPE unknown_comptype AS (
+ u unknown -- fail
+);
+ERROR: column "u" has pseudo-type unknown
+CREATE TABLE IF NOT EXISTS test_tsvector(
+ t text,
+ a tsvector
+);
+CREATE TABLE IF NOT EXISTS test_tsvector(
+ t text
+);
+NOTICE: relation "test_tsvector" already exists, skipping
+-- invalid: non-lowercase quoted reloptions identifiers
+CREATE TABLE tas_case WITH ("Fillfactor" = 10) AS SELECT 1 a;
+ERROR: unrecognized parameter "Fillfactor"
+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;
+ relname | relkind | relpersistence
+----------------+---------+----------------
+ unlogged1 | r | u
+ unlogged1_pkey | i | u
+ unlogged2 | r | t
+ unlogged2_pkey | i | t
+(4 rows)
+
+REINDEX INDEX unlogged1_pkey;
+REINDEX INDEX unlogged2_pkey;
+SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged\d' ORDER BY relname;
+ relname | relkind | relpersistence
+----------------+---------+----------------
+ unlogged1 | r | u
+ unlogged1_pkey | i | u
+ unlogged2 | r | t
+ unlogged2_pkey | i | t
+(4 rows)
+
+DROP TABLE unlogged2;
+INSERT INTO unlogged1 VALUES (42);
+CREATE UNLOGGED TABLE public.unlogged2 (a int primary key); -- also OK
+CREATE UNLOGGED TABLE pg_temp.unlogged3 (a int primary key); -- not OK
+ERROR: only temporary relations may be created in temporary schemas
+LINE 1: CREATE UNLOGGED TABLE pg_temp.unlogged3 (a int primary key);
+ ^
+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
+ERROR: cannot create temporary relation in non-temporary schema
+LINE 1: CREATE TEMP TABLE public.temp_to_perm (a int primary key);
+ ^
+DROP TABLE unlogged1, public.unlogged2;
+CREATE TABLE as_select1 AS SELECT * FROM pg_class WHERE relkind = 'r';
+CREATE TABLE as_select1 AS SELECT * FROM pg_class WHERE relkind = 'r';
+ERROR: relation "as_select1" already exists
+CREATE TABLE IF NOT EXISTS as_select1 AS SELECT * FROM pg_class WHERE relkind = 'r';
+NOTICE: relation "as_select1" already exists, skipping
+DROP TABLE as_select1;
+PREPARE select1 AS SELECT 1 as a;
+CREATE TABLE as_select1 AS EXECUTE select1;
+CREATE TABLE as_select1 AS EXECUTE select1;
+ERROR: relation "as_select1" already exists
+SELECT * FROM as_select1;
+ a
+---
+ 1
+(1 row)
+
+CREATE TABLE IF NOT EXISTS as_select1 AS EXECUTE select1;
+NOTICE: relation "as_select1" already exists, skipping
+DROP TABLE as_select1;
+DEALLOCATE select1;
+-- 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
+INSERT INTO extra_wide_table(firstc, lastc) VALUES('first col', 'last col');
+SELECT firstc, lastc FROM extra_wide_table;
+ firstc | lastc
+-----------+----------
+ first col | last col
+(1 row)
+
+-- check that tables with oids cannot be created anymore
+CREATE TABLE withoid() WITH OIDS;
+ERROR: syntax error at or near "OIDS"
+LINE 1: CREATE TABLE withoid() WITH OIDS;
+ ^
+CREATE TABLE withoid() WITH (oids);
+ERROR: tables declared WITH OIDS are not supported
+CREATE TABLE withoid() WITH (oids = true);
+ERROR: tables declared WITH OIDS are not supported
+-- 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));
+ERROR: cannot use column reference in DEFAULT expression
+LINE 1: CREATE TABLE default_expr_column (id int DEFAULT (id));
+ ^
+CREATE TABLE default_expr_column (id int DEFAULT (bar.id));
+ERROR: cannot use column reference in DEFAULT expression
+LINE 1: CREATE TABLE default_expr_column (id int DEFAULT (bar.id));
+ ^
+CREATE TABLE default_expr_agg_column (id int DEFAULT (avg(id)));
+ERROR: cannot use column reference in DEFAULT expression
+LINE 1: ...TE 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)));
+ERROR: cannot use column reference in DEFAULT expression
+LINE 1: ...TABLE default_expr_non_column (a int DEFAULT (avg(non_existe...
+ ^
+-- invalid use of aggregate
+CREATE TABLE default_expr_agg (a int DEFAULT (avg(1)));
+ERROR: aggregate functions are not allowed in DEFAULT expressions
+LINE 1: CREATE TABLE default_expr_agg (a int DEFAULT (avg(1)));
+ ^
+-- invalid use of subquery
+CREATE TABLE default_expr_agg (a int DEFAULT (select 1));
+ERROR: cannot use subquery in DEFAULT expression
+LINE 1: 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)));
+ERROR: set-returning functions are not allowed in DEFAULT expressions
+LINE 1: CREATE TABLE default_expr_agg (a int DEFAULT (generate_serie...
+ ^
+-- 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;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR: cannot create partitioned table as inheritance child
+-- 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
+ERROR: cannot use "list" partition strategy with more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 3: EXCLUDE USING gist (a WITH &&)
+ ^
+-- 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));
+ERROR: set-returning functions are not allowed in partition key expressions
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expressions
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expressions
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR: cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((42));
+ERROR: cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept valid partitioning strategy
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY MAGIC (a);
+ERROR: unrecognized partitioning strategy "magic"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (b);
+ERROR: column "b" named in partition key does not exist
+LINE 3: ) PARTITION BY RANGE (b);
+ ^
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (xmin);
+ERROR: cannot use system column "xmin" in partition key
+LINE 3: ) PARTITION BY RANGE (xmin);
+ ^
+-- cannot use pseudotypes
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE (((a, b)));
+ERROR: partition key column 1 has pseudo-type record
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE (a, ('unknown'));
+ERROR: partition key column 2 has pseudo-type 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));
+ERROR: functions in partition key expression must be marked IMMUTABLE
+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);
+ERROR: data type point has no default operator class for access method "btree"
+HINT: You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a);
+ERROR: data type point has no default operator class for access method "btree"
+HINT: You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- 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);
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- 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';
+ relkind
+---------
+ p
+(1 row)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table partitioned depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- 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);
+ERROR: cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+ Partitioned table "public.partitioned"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | text | | |
+ d | text | | |
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "C")
+Number of partitions: 0
+
+\d+ partitioned2
+ Partitioned table "public.partitioned2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | integer | | | | plain | |
+ b | text | | | | extended | |
+Partition key: RANGE (((a + 1)), substr(b, 1, 5))
+Number of partitions: 0
+
+INSERT INTO partitioned2 VALUES (1, 'hello');
+ERROR: no partition of relation "partitioned2" found for row
+DETAIL: Partition key of the failing row contains ((a + 1), substr(b, 1, 5)) = (2, hello).
+CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO (100, 'ccccc');
+\d+ part2_1
+ Table "public.part2_1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | integer | | | | plain | |
+ b | text | | | | extended | |
+Partition of: partitioned2 FOR VALUES FROM ('-1', 'aaaaa') TO (100, 'ccccc')
+Partition constraint: (((a + 1) IS NOT NULL) AND (substr(b, 1, 5) IS NOT NULL) AND (((a + 1) > '-1'::integer) OR (((a + 1) = '-1'::integer) AND (substr(b, 1, 5) >= 'aaaaa'::text))) AND (((a + 1) < 100) OR (((a + 1) = 100) AND (substr(b, 1, 5) < 'ccccc'::text))))
+
+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;
+ QUERY PLAN
+-----------------------------------------------------------
+ Seq Scan on partitioned1 partitioned
+ Filter: (ROW(a, b)::partitioned = '(1,2)'::partitioned)
+(2 rows)
+
+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;
+ QUERY PLAN
+-----------------------------------------------------------------
+ Seq Scan on partitioned1 partitioned
+ Filter: ((partitioned.*)::partitioned = '(1,2)'::partitioned)
+(2 rows)
+
+\d+ partitioned1
+ Table "public.partitioned1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | | | plain | |
+Partition of: partitioned FOR VALUES IN ('(1,2)')
+Partition constraint: (((partitioned1.*)::partitioned IS DISTINCT FROM NULL) AND ((partitioned1.*)::partitioned = '(1,2)'::partitioned))
+
+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
+ERROR: cannot drop column "a" because it is part of the partition key of relation "partitioned"
+drop domain intdom1; -- fail, requires cascade
+ERROR: cannot drop type intdom1 because other objects depend on it
+DETAIL: table partitioned depends on type intdom1
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+drop domain intdom1 cascade;
+NOTICE: drop cascades to table partitioned
+table partitioned; -- gone
+ERROR: relation "partitioned" does not exist
+LINE 1: table partitioned;
+ ^
+-- 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
+ERROR: cannot drop column "a" because it is part of the partition key of relation "partitioned"
+drop domain intdom1; -- fail, requires cascade
+ERROR: cannot drop type intdom1 because other objects depend on it
+DETAIL: table partitioned depends on type intdom1
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+drop domain intdom1 cascade;
+NOTICE: drop cascades to table partitioned
+table partitioned; -- gone
+ERROR: relation "partitioned" does not exist
+LINE 1: table partitioned;
+ ^
+--
+-- 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
+ Partitioned table "public.list_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition key: LIST (a)
+Partitions: part_null FOR VALUES IN (NULL),
+ part_p1 FOR VALUES IN (1),
+ part_p2 FOR VALUES IN (2),
+ part_p3 FOR VALUES IN (3)
+
+-- forbidden expressions for partition bound with list partitioned table
+CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename);
+ERROR: cannot use column reference in partition bound expression
+LINE 1: ...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);
+ERROR: cannot use column reference in partition bound expression
+LINE 1: ...expr_fail PARTITION OF list_parted FOR VALUES IN (somename.s...
+ ^
+CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (a);
+ERROR: cannot use column reference in partition bound expression
+LINE 1: ..._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));
+ERROR: cannot use column reference in partition bound expression
+LINE 1: ...s_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));
+ERROR: cannot use column reference in partition bound expression
+LINE 1: ..._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));
+ERROR: aggregate functions are not allowed in partition bound
+LINE 1: ...s_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));
+ERROR: cannot use subquery in partition bound
+LINE 1: ...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));
+ERROR: set-returning functions are not allowed in partition bound
+LINE 1: ...expr_fail PARTITION OF list_parted FOR VALUES IN (generate_s...
+ ^
+CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN ((1+1) collate "POSIX");
+ERROR: collations are not supported by type integer
+LINE 1: ...ail PARTITION OF list_parted FOR VALUES IN ((1+1) collate "P...
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ...E 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);
+ERROR: invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
+ ^
+-- 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);
+ERROR: invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES WITH (MODU...
+ ^
+-- 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;
+ERROR: partition "fail_default_part" conflicts with existing default partition "part_default"
+LINE 1: ...TE 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);
+ERROR: specified value cannot be cast to type boolean for column "a"
+LINE 1: ...REATE 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');
+ERROR: partition "bigintp_10_2" would overlap partition "bigintp_10"
+LINE 1: ...ABLE 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');
+ERROR: cannot use column reference in partition bound expression
+LINE 2: 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');
+ERROR: cannot use column reference in partition bound expression
+LINE 2: 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');
+ERROR: cannot use column reference in partition bound expression
+LINE 2: 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');
+ERROR: cannot use column reference in partition bound expression
+LINE 2: 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');
+ERROR: cannot use column reference in partition bound expression
+LINE 2: 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');
+ERROR: aggregate functions are not allowed in partition bound
+LINE 2: 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');
+ERROR: cannot use subquery in partition bound
+LINE 2: 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');
+ERROR: set-returning functions are not allowed in partition bound
+LINE 2: 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');
+ERROR: invalid bound specification for a range partition
+LINE 1: ...BLE 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);
+ERROR: invalid bound specification for a range partition
+LINE 1: ...LE fail_part PARTITION OF range_parted FOR VALUES WITH (MODU...
+ ^
+-- 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');
+ERROR: FROM must specify exactly one value per partitioning column
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+ERROR: TO must specify exactly one value per partitioning column
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (maxvalue);
+ERROR: cannot specify NULL in range bound
+-- 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);
+ERROR: invalid bound specification for a range partition
+LINE 1: ...LE fail_part PARTITION OF range_parted FOR VALUES WITH (MODU...
+ ^
+-- 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);
+ERROR: every hash partition modulus must be a factor of the next larger modulus
+DETAIL: The new modulus 25 is not divisible by 10, the modulus of existing partition "hpart_4".
+-- 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);
+ERROR: every hash partition modulus must be a factor of the next larger modulus
+DETAIL: The new modulus 150 is not a factor of 200, the modulus of existing partition "hpart_3".
+-- overlapping remainders
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 100, REMAINDER 3);
+ERROR: partition "fail_part" would overlap partition "hpart_4"
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODU...
+ ^
+-- trying to specify range for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR: invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a',...
+ ^
+-- trying to specify list value for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+ERROR: invalid bound specification for a hash partition
+LINE 1: ...BLE 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;
+ERROR: a hash-partitioned table may not have a default partition
+-- 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');
+ERROR: "unparted" is not partitioned
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+ERROR: "unparted" is not partitioned
+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');
+ERROR: cannot create a permanent relation as partition of temporary relation "temp_parted"
+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);
+ERROR: partition "fail_part" would overlap partition "part_null_z"
+LINE 1: ...LE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ ^
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_part" would overlap partition "part_ab"
+LINE 1: ...ail_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');
+ERROR: updated partition constraint for default partition "list_parted2_def" would be violated by some row
+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);
+ERROR: empty range bound specified for partition "fail_part"
+LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+ ^
+DETAIL: Specified lower bound (1) is greater than or equal to upper bound (0).
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+ERROR: empty range bound specified for partition "fail_part"
+LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+ ^
+DETAIL: Specified lower bound (1) is greater than or equal to upper bound (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);
+ERROR: partition "fail_part" would overlap partition "part0"
+LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (minvalue) ...
+ ^
+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);
+ERROR: partition "fail_part" would overlap partition "part0"
+LINE 1: ..._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);
+ERROR: partition "fail_part" would overlap partition "part1"
+LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (max...
+ ^
+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);
+ERROR: partition "fail_part" would overlap partition "part2"
+LINE 1: ...art PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
+ ^
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+ERROR: partition "fail_part" would overlap partition "part2"
+LINE 1: ...art 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;
+ERROR: partition "fail_default_part" conflicts with existing default partition "range2_default"
+LINE 1: ... 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);
+ERROR: updated partition constraint for default partition "range2_default" would be violated by some row
+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);
+ERROR: partition "fail_part" would overlap partition "part00"
+LINE 1: ..._part PARTITION OF range_parted3 FOR VALUES FROM (0, minvalu...
+ ^
+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);
+ERROR: partition "fail_part" would overlap partition "part12"
+LINE 1: ...rt PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1,...
+ ^
+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);
+ERROR: partition "fail_part" would overlap partition "part10"
+LINE 1: ..._part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalu...
+ ^
+-- 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);
+ERROR: partition "fail_part" would overlap partition "h2part_4"
+LINE 1: ...LE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (MODU...
+ ^
+-- modulus must be greater than zero
+CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 0, REMAINDER 1);
+ERROR: modulus for hash partition must be an integer value greater than zero
+-- 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);
+ERROR: remainder for hash partition must be less than modulus
+-- 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;
+ attname | attislocal | attinhcount
+---------+------------+-------------
+ a | f | 1
+ b | f | 1
+(2 rows)
+
+-- 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');
+ERROR: column "b" specified more than once
+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');
+NOTICE: merging constraint "check_a" with inherited definition
+-- 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;
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+ t | 0
+(2 rows)
+
+-- 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);
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass;
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+ f | 1
+(2 rows)
+
+-- Neither check_a nor check_b are droppable from part_b
+ALTER TABLE part_b DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_b"
+ALTER TABLE part_b DROP CONSTRAINT check_b;
+ERROR: cannot drop inherited constraint "check_b" of relation "part_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;
+ conislocal | coninhcount
+------------+-------------
+(0 rows)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR: column "c" named in partition key does not exist
+LINE 1: ...TITION 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);
+ERROR: null value in column "b" of relation "parted_notnull_inh_test1" violates not-null constraint
+DETAIL: Failing row contains (1, null).
+-- note that while b's default is overriden, a's default is preserved
+\d parted_notnull_inh_test1
+ Table "public.parted_notnull_inh_test1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null | 1
+ b | integer | | not null | 1
+Partition of: parted_notnull_inh_test FOR VALUES IN (1)
+
+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
+ Table "public.part_b"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 1 | plain | |
+Partition of: parted FOR VALUES IN ('b')
+Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
+
+-- Both partition bound and partition key in describe output
+\d+ part_c
+ Partitioned table "public.part_c"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 0 | plain | |
+Partition of: parted FOR VALUES IN ('c')
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
+Partition key: RANGE (b)
+Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+
+-- a level-2 partition's constraint will include the parent's expressions
+\d+ part_c_1_10
+ Table "public.part_c_1_10"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 0 | plain | |
+Partition of: part_c FOR VALUES FROM (1) TO (10)
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 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
+ Partitioned table "public.parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition key: LIST (a)
+Number of partitions: 3 (Use \d+ to list them.)
+
+\d hash_parted
+ Partitioned table "public.hash_parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: HASH (a)
+Number of partitions: 4 (Use \d+ to list them.)
+
+-- 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
+ Table "public.unbounded_range_part"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | | | plain | |
+ c | integer | | | | plain | |
+Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE)
+Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL))
+
+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
+ Table "public.range_parted4_1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | | | plain | |
+ c | integer | | | | plain | |
+Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE)
+Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1))
+
+CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE);
+\d+ range_parted4_2
+ Table "public.range_parted4_2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | | | plain | |
+ c | integer | | | | plain | |
+Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE)
+Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7))))
+
+CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE);
+\d+ range_parted4_3
+ Table "public.range_parted4_3"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | | | plain | |
+ c | integer | | | | plain | |
+Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE)
+Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
+
+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 btree AS
+ 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 btree;
+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);
+ obj_description
+----------------------
+ Am partitioned table
+(1 row)
+
+\d+ parted_col_comment
+ Partitioned table "public.parted_col_comment"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+---------------
+ a | integer | | | | plain | | Partition key
+ b | text | | | | extended | |
+Partition key: LIST (a)
+Number of partitions: 0
+
+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
+ Table "public.arrlp12"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+-----------+-----------+----------+---------+----------+--------------+-------------
+ a | integer[] | | | | extended | |
+Partition of: arrlp FOR VALUES IN ('{1}', '{2}')
+Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[])))
+
+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
+ Partitioned table "public.boolspart"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | boolean | | | | plain | |
+Partition key: LIST (a)
+Partitions: boolspart_f FOR VALUES IN (false),
+ boolspart_t FOR VALUES IN (true)
+
+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
+ERROR: cannot create a permanent relation as partition of temporary relation "temp_parted"
+create temp table temp_part partition of perm_parted default; -- error
+ERROR: cannot create a temporary relation as partition of permanent relation "perm_parted"
+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);
+ERROR: cannot CREATE TABLE .. PARTITION OF "tab_part_create" because it is being used by active queries in this session
+CONTEXT: SQL statement "create table tab_part_create_1 partition of tab_part_create for values in (1)"
+PL/pgSQL function func_part_create() line 3 at EXECUTE
+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;
+ tableoid
+--------------------------
+ volatile_partbound_test2
+(1 row)
+
+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);
+ERROR: updated partition constraint for default partition "defcheck_def" would be violated by some row
+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
+ Partitioned table "public.part_column_drop"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition key: RANGE (id)
+Indexes:
+ "part_column_drop_b_expr" btree ((b = 1))
+ "part_column_drop_b_pred" btree (b) WHERE b = 1
+ "part_column_drop_d_expr" btree ((d = 2))
+ "part_column_drop_d_pred" btree (d) WHERE d = 2
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d part_column_drop_1_10
+ Table "public.part_column_drop_1_10"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition of: part_column_drop FOR VALUES FROM (1) TO (10)
+Indexes:
+ "part_column_drop_1_10_b_idx" btree (b) WHERE b = 1
+ "part_column_drop_1_10_d_idx" btree (d) WHERE d = 2
+ "part_column_drop_1_10_expr_idx" btree ((b = 1))
+ "part_column_drop_1_10_expr_idx1" btree ((d = 2))
+
+drop table part_column_drop;
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
new file mode 100644
index 0000000..e308771
--- /dev/null
+++ b/src/test/regress/expected/create_table_like.out
@@ -0,0 +1,519 @@
+/* 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);
+ERROR: relation "nonexistent" does not exist
+LINE 1: 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 */
+ aa | bb | ee | xx
+---------+---------+----+---------
+ ee-col1 | ee-col2 | | ee-col4
+(1 row)
+
+SELECT * FROM inhx; /* Empty set since LIKE inherits structure only */
+ xx
+----
+(0 rows)
+
+SELECT * FROM ctlb; /* Has ee entry */
+ aa | bb
+---------+---------
+ ee-col1 | ee-col2
+(1 row)
+
+SELECT * FROM ctla; /* Has ee entry */
+ aa
+---------
+ ee-col1
+(1 row)
+
+CREATE TABLE inhf (LIKE inhx, LIKE inhx); /* Throw error */
+ERROR: column "xx" specified more than once
+CREATE TABLE inhf (LIKE inhx INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
+INSERT INTO inhf DEFAULT VALUES;
+SELECT * FROM inhf; /* Single entry with value 'text' */
+ xx
+------
+ text
+(1 row)
+
+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 */
+ERROR: new row for relation "inhg" violates check constraint "foo"
+DETAIL: Failing row contains (x, foo, y).
+SELECT * FROM inhg; /* Two records with three columns in order x=x, xx=text, y=y */
+ x | xx | y
+---+------+---
+ x | text | y
+ x | text | y
+(2 rows)
+
+DROP TABLE inhg;
+CREATE TABLE test_like_id_1 (a bigint GENERATED ALWAYS AS IDENTITY, b text);
+\d test_like_id_1
+ Table "public.test_like_id_1"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+------------------------------
+ a | bigint | | not null | generated always as identity
+ b | text | | |
+
+INSERT INTO test_like_id_1 (b) VALUES ('b1');
+SELECT * FROM test_like_id_1;
+ a | b
+---+----
+ 1 | b1
+(1 row)
+
+CREATE TABLE test_like_id_2 (LIKE test_like_id_1);
+\d test_like_id_2
+ Table "public.test_like_id_2"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+---------
+ a | bigint | | not null |
+ b | text | | |
+
+INSERT INTO test_like_id_2 (b) VALUES ('b2');
+ERROR: null value in column "a" of relation "test_like_id_2" violates not-null constraint
+DETAIL: Failing row contains (null, b2).
+SELECT * FROM test_like_id_2; -- identity was not copied
+ a | b
+---+---
+(0 rows)
+
+CREATE TABLE test_like_id_3 (LIKE test_like_id_1 INCLUDING IDENTITY);
+\d test_like_id_3
+ Table "public.test_like_id_3"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+------------------------------
+ a | bigint | | not null | generated always as identity
+ b | text | | |
+
+INSERT INTO test_like_id_3 (b) VALUES ('b3');
+SELECT * FROM test_like_id_3; -- identity was copied and applied
+ a | b
+---+----
+ 1 | b3
+(1 row)
+
+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
+ Table "public.test_like_gen_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ a | integer | | |
+ b | integer | | | generated always as (a * 2) stored
+
+INSERT INTO test_like_gen_1 (a) VALUES (1);
+SELECT * FROM test_like_gen_1;
+ a | b
+---+---
+ 1 | 2
+(1 row)
+
+CREATE TABLE test_like_gen_2 (LIKE test_like_gen_1);
+\d test_like_gen_2
+ Table "public.test_like_gen_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+
+INSERT INTO test_like_gen_2 (a) VALUES (1);
+SELECT * FROM test_like_gen_2;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+CREATE TABLE test_like_gen_3 (LIKE test_like_gen_1 INCLUDING GENERATED);
+\d test_like_gen_3
+ Table "public.test_like_gen_3"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ a | integer | | |
+ b | integer | | | generated always as (a * 2) stored
+
+INSERT INTO test_like_gen_3 (a) VALUES (1);
+SELECT * FROM test_like_gen_3;
+ a | b
+---+---
+ 1 | 2
+(1 row)
+
+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
+ Table "public.test_like_4"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ b | integer | | | 42
+ c | integer | | | generated always as (a * 2) stored
+ a | integer | | |
+Check constraints:
+ "test_like_4_a_check" CHECK (a > 0)
+
+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
+ Table "public.test_like_4a"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ b | integer | | |
+ c | integer | | |
+ a | integer | | |
+
+INSERT INTO test_like_4a (a) VALUES(11);
+SELECT a, b, c FROM test_like_4a;
+ a | b | c
+----+---+---
+ 11 | |
+(1 row)
+
+\d test_like_4b
+ Table "public.test_like_4b"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ b | integer | | | 42
+ c | integer | | |
+ a | integer | | |
+
+INSERT INTO test_like_4b (a) VALUES(11);
+SELECT a, b, c FROM test_like_4b;
+ a | b | c
+----+----+---
+ 11 | 42 |
+(1 row)
+
+\d test_like_4c
+ Table "public.test_like_4c"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ b | integer | | |
+ c | integer | | | generated always as (a * 2) stored
+ a | integer | | |
+
+INSERT INTO test_like_4c (a) VALUES(11);
+SELECT a, b, c FROM test_like_4c;
+ a | b | c
+----+---+----
+ 11 | | 22
+(1 row)
+
+\d test_like_4d
+ Table "public.test_like_4d"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ b | integer | | | 42
+ c | integer | | | generated always as (a * 2) stored
+ a | integer | | |
+
+INSERT INTO test_like_4d (a) VALUES(11);
+SELECT a, b, c FROM test_like_4d;
+ a | b | c
+----+----+----
+ 11 | 42 | 22
+(1 row)
+
+-- 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
+ Table "public.test_like_5c"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ x | point | | |
+ y | point | | |
+ z | point | | |
+ p | integer | | |
+ q | integer | | | generated always as (p * 2) stored
+ b | integer | | | 42
+ c | integer | | | generated always as (a * 2) stored
+ a | integer | | |
+Check constraints:
+ "test_like_4_a_check" CHECK (a > 0)
+ "test_like_5x_p_check" CHECK (p > 0)
+Inherits: test_like_5,
+ test_like_5x
+
+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
+ERROR: duplicate key value violates unique constraint "inhg_pkey"
+DETAIL: Key (xx)=(10) already exists.
+DROP TABLE inhg;
+/* Multiple primary keys creation should fail */
+CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, PRIMARY KEY(x)); /* fails */
+ERROR: multiple primary keys for table "inhg" are not allowed
+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
+ERROR: duplicate key value violates unique constraint "inhg_x_key"
+DETAIL: Key (x)=(15) already exists.
+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
+ Table "public.inhz"
+ Column | Type | Collation | Nullable | Default
+--------+------+-----------+----------+---------
+ x | text | | |
+ xx | text | | not null |
+Indexes:
+ "inhz_pkey" PRIMARY KEY, btree (xx)
+Foreign-key constraints:
+ "inhz_x_fkey" FOREIGN KEY (x) REFERENCES inhz(xx)
+Referenced by:
+ TABLE "inhz" CONSTRAINT "inhz_x_fkey" FOREIGN KEY (x) REFERENCES inhz(xx)
+
+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;
+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
+ Table "public.ctlt12_storage"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | not null | | main | |
+ b | text | | | | extended | |
+ c | text | | | | external | |
+
+CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
+\d+ ctlt12_comments
+ Table "public.ctlt12_comments"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | not null | | extended | | A
+ b | text | | | | extended | | B
+ c | text | | | | extended | | C
+
+CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1);
+NOTICE: merging column "a" with inherited definition
+NOTICE: merging column "b" with inherited definition
+NOTICE: merging constraint "ctlt1_a_check" with inherited definition
+\d+ ctlt1_inh
+ Table "public.ctlt1_inh"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | not null | | main | | A
+ b | text | | | | extended | | B
+Check constraints:
+ "ctlt1_a_check" CHECK (length(a) > 2)
+Inherits: ctlt1
+
+SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass;
+ description
+-------------
+ t1_a_check
+(1 row)
+
+CREATE TABLE ctlt13_inh () INHERITS (ctlt1, ctlt3);
+NOTICE: merging multiple inherited definitions of column "a"
+\d+ ctlt13_inh
+ Table "public.ctlt13_inh"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | not null | | main | |
+ b | text | | | | extended | |
+ c | text | | | | external | |
+Check constraints:
+ "ctlt1_a_check" CHECK (length(a) > 2)
+ "ctlt3_a_check" CHECK (length(a) < 5)
+ "ctlt3_c_check" CHECK (length(c) < 7)
+Inherits: ctlt1,
+ ctlt3
+
+CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1);
+NOTICE: merging column "a" with inherited definition
+\d+ ctlt13_like
+ Table "public.ctlt13_like"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | not null | | main | | A3
+ b | text | | | | extended | |
+ c | text | | | | external | | C
+Indexes:
+ "ctlt13_like_expr_idx" btree ((a || c))
+Check constraints:
+ "ctlt1_a_check" CHECK (length(a) > 2)
+ "ctlt3_a_check" CHECK (length(a) < 5)
+ "ctlt3_c_check" CHECK (length(c) < 7)
+Inherits: ctlt1
+
+SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass;
+ description
+-------------
+ t3_a_check
+(1 row)
+
+CREATE TABLE ctlt_all (LIKE ctlt1 INCLUDING ALL);
+\d+ ctlt_all
+ Table "public.ctlt_all"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | not null | | main | | A
+ b | text | | | | extended | | B
+Indexes:
+ "ctlt_all_pkey" PRIMARY KEY, btree (a)
+ "ctlt_all_b_idx" btree (b)
+ "ctlt_all_expr_idx" btree ((a || b))
+Check constraints:
+ "ctlt1_a_check" CHECK (length(a) > 2)
+Statistics objects:
+ "public.ctlt_all_a_b_stat" ON a, b FROM ctlt_all
+ "public.ctlt_all_expr_stat" ON (a || b) FROM 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;
+ relname | objsubid | description
+----------------+----------+-------------
+ ctlt_all_b_idx | 0 | index b_key
+ ctlt_all_pkey | 0 | index pkey
+(2 rows)
+
+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;
+ stxname | objsubid | description
+--------------------+----------+---------------
+ ctlt_all_a_b_stat | 0 | ab stats
+ ctlt_all_expr_stat | 0 | ab expr stats
+(2 rows)
+
+CREATE TABLE inh_error1 () INHERITS (ctlt1, ctlt4);
+NOTICE: merging multiple inherited definitions of column "a"
+ERROR: inherited column "a" has a storage parameter conflict
+DETAIL: MAIN versus EXTENDED
+CREATE TABLE inh_error2 (LIKE ctlt4 INCLUDING STORAGE) INHERITS (ctlt1);
+NOTICE: merging column "a" with inherited definition
+ERROR: column "a" has a storage parameter conflict
+DETAIL: MAIN versus EXTENDED
+-- 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
+ Table "public.pg_attrdef"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | not null | | main | | A
+ b | text | | | | extended | | B
+Indexes:
+ "pg_attrdef_pkey" PRIMARY KEY, btree (a)
+ "pg_attrdef_b_idx" btree (b)
+ "pg_attrdef_expr_idx" btree ((a || b))
+Check constraints:
+ "ctlt1_a_check" CHECK (length(a) > 2)
+Statistics objects:
+ "public.pg_attrdef_a_b_stat" ON a, b FROM public.pg_attrdef
+ "public.pg_attrdef_expr_stat" ON (a || b) FROM public.pg_attrdef
+
+DROP TABLE public.pg_attrdef;
+-- Check that LIKE isn't confused when new table masks the old, either
+BEGIN;
+CREATE SCHEMA ctl_schema;
+SET LOCAL search_path = ctl_schema, public;
+CREATE TABLE ctlt1 (LIKE ctlt1 INCLUDING ALL);
+\d+ ctlt1
+ Table "ctl_schema.ctlt1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | not null | | main | | A
+ b | text | | | | extended | | B
+Indexes:
+ "ctlt1_pkey" PRIMARY KEY, btree (a)
+ "ctlt1_b_idx" btree (b)
+ "ctlt1_expr_idx" btree ((a || b))
+Check constraints:
+ "ctlt1_a_check" CHECK (length(a) > 2)
+Statistics objects:
+ "ctl_schema.ctlt1_a_b_stat" ON a, b FROM ctlt1
+ "ctl_schema.ctlt1_expr_stat" ON (a || b) FROM ctlt1
+
+ROLLBACK;
+DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
+NOTICE: drop cascades to table inhe
+-- 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
+ Table "public.noinh_con_copy1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Check constraints:
+ "noinh_con_copy_a_check" CHECK (a > 0) NO INHERIT
+
+-- 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);
+ERROR: cannot add NO INHERIT constraint to partitioned table "noinh_con_copy1_parted"
+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
+ERROR: "ctlseq1" is not a table, view, materialized view, composite type, or foreign table
+LINE 1: CREATE TABLE ctlt10 (LIKE ctlseq1);
+ ^
+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;
+NOTICE: table "ctlt10" does not exist, skipping
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
new file mode 100644
index 0000000..14394cc
--- /dev/null
+++ b/src/test/regress/expected/create_type.out
@@ -0,0 +1,322 @@
+--
+-- CREATE_TYPE
+--
+--
+-- Note: widget_in/out were created in create_function_1, without any
+-- prior shell-type creation. These commands therefore complete a test
+-- of the "old style" approach of making the functions first.
+--
+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
+ERROR: type "shell" already exists
+DROP TYPE shell;
+DROP TYPE shell; -- fail, type not exist
+ERROR: type "shell" does 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;
+NOTICE: return type int42 is only a shell
+CREATE FUNCTION int42_out(int42)
+ RETURNS cstring
+ AS 'int4out'
+ LANGUAGE internal STRICT IMMUTABLE;
+NOTICE: argument type int42 is only a shell
+CREATE FUNCTION text_w_default_in(cstring)
+ RETURNS text_w_default
+ AS 'textin'
+ LANGUAGE internal STRICT IMMUTABLE;
+NOTICE: return type text_w_default is only a shell
+CREATE FUNCTION text_w_default_out(text_w_default)
+ RETURNS cstring
+ AS 'textout'
+ LANGUAGE internal STRICT IMMUTABLE;
+NOTICE: argument type text_w_default is only a shell
+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;
+ f1 | f2
+-------+----
+ zippo | 42
+(1 row)
+
+-- 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"
+);
+WARNING: type attribute "Internallength" not recognized
+LINE 2: "Internallength" = 4,
+ ^
+WARNING: type attribute "Input" not recognized
+LINE 3: "Input" = int42_in,
+ ^
+WARNING: type attribute "Output" not recognized
+LINE 4: "Output" = int42_out,
+ ^
+WARNING: type attribute "Alignment" not recognized
+LINE 5: "Alignment" = int4,
+ ^
+WARNING: type attribute "Default" not recognized
+LINE 6: "Default" = 42,
+ ^
+WARNING: type attribute "Passedbyvalue" not recognized
+LINE 7: "Passedbyvalue"
+ ^
+ERROR: type input function must be specified
+-- invalid: input/output function incompatibility
+CREATE TYPE bogus_type (INPUT = array_in,
+ OUTPUT = array_out,
+ ELEMENT = int,
+ INTERNALLENGTH = 32);
+ERROR: type input function array_in must return type bogus_type
+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);
+ERROR: type "bogus_type" does not exist
+HINT: Create the type as a shell type, then create its I/O functions, then do a full CREATE TYPE.
+-- 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();
+ f1 | f2
+-------+----
+ zippo | 42
+(1 row)
+
+-- Test comments
+COMMENT ON TYPE bad IS 'bad comment';
+ERROR: type "bad" does not exist
+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';
+ERROR: column "nope" of relation "default_test_row" does not exist
+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
+ERROR: type "text_w_default" already exists
+DROP TYPE default_test_row CASCADE;
+NOTICE: drop cascades to function get_default_test()
+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;
+NOTICE: return type base_type is only a shell
+CREATE FUNCTION base_fn_out(base_type) RETURNS cstring AS 'boolout'
+ LANGUAGE internal IMMUTABLE STRICT;
+NOTICE: argument type base_type is only a shell
+CREATE TYPE base_type(INPUT = base_fn_in, OUTPUT = base_fn_out);
+DROP FUNCTION base_fn_in(cstring); -- error
+ERROR: cannot drop function base_fn_in(cstring) because other objects depend on it
+DETAIL: type base_type depends on function base_fn_in(cstring)
+function base_fn_out(base_type) depends on type base_type
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP FUNCTION base_fn_out(base_type); -- error
+ERROR: cannot drop function base_fn_out(base_type) because other objects depend on it
+DETAIL: type base_type depends on function base_fn_out(base_type)
+function base_fn_in(cstring) depends on type base_type
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TYPE base_type; -- error
+ERROR: cannot drop type base_type because other objects depend on it
+DETAIL: function base_fn_in(cstring) depends on type base_type
+function base_fn_out(base_type) depends on type base_type
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TYPE base_type CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to function base_fn_in(cstring)
+drop cascades to function base_fn_out(base_type)
+-- 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
+ERROR: invalid NUMERIC type modifier
+LINE 1: CREATE TEMP TABLE mytab (foo widget(42,13,7));
+ ^
+CREATE TEMP TABLE mytab (foo widget(42,13));
+SELECT format_type(atttypid,atttypmod) FROM pg_attribute
+WHERE attrelid = 'mytab'::regclass AND attnum > 0;
+ format_type
+---------------
+ widget(42,13)
+(1 row)
+
+-- might as well exercise the widget type while we're here
+INSERT INTO mytab VALUES ('(1,2,3)'), ('(-44,5.5,12)');
+TABLE mytab;
+ foo
+--------------
+ (1,2,3)
+ (-44,5.5,12)
+(2 rows)
+
+-- and test format_type() a bit more, too
+select format_type('varchar'::regtype, 42);
+ format_type
+-----------------------
+ character varying(38)
+(1 row)
+
+select format_type('bpchar'::regtype, null);
+ format_type
+-------------
+ character
+(1 row)
+
+-- this behavior difference is intentional
+select format_type('bpchar'::regtype, -1);
+ format_type
+-------------
+ bpchar
+(1 row)
+
+--
+-- 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';
+NOTICE: return type myvarchar is only a shell
+CREATE FUNCTION myvarcharout(myvarchar) RETURNS cstring
+LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'varcharout';
+NOTICE: argument type myvarchar is only a shell
+CREATE FUNCTION myvarcharsend(myvarchar) RETURNS bytea
+LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharsend';
+NOTICE: argument type myvarchar is only a shell
+CREATE FUNCTION myvarcharrecv(internal, oid, integer) RETURNS myvarchar
+LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharrecv';
+NOTICE: return type myvarchar is only a shell
+-- fail, it's still a shell:
+ALTER TYPE myvarchar SET (storage = extended);
+ERROR: type "myvarchar" is only a shell
+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
+ERROR: cannot change type's storage to PLAIN
+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';
+ typinput | typoutput | typreceive | typsend | typmodin | typmodout | typanalyze | typsubscript | typstorage
+-------------+--------------+---------------+---------------+-----------------+------------------+---------------+-----------------------------+------------
+ myvarcharin | myvarcharout | myvarcharrecv | myvarcharsend | varchartypmodin | varchartypmodout | ts_typanalyze | raw_array_subscript_handler | x
+(1 row)
+
+SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout,
+ typanalyze, typsubscript, typstorage
+FROM pg_type WHERE typname = '_myvarchar';
+ typinput | typoutput | typreceive | typsend | typmodin | typmodout | typanalyze | typsubscript | typstorage
+----------+-----------+------------+------------+-----------------+------------------+------------------+-------------------------+------------
+ array_in | array_out | array_recv | array_send | varchartypmodin | varchartypmodout | array_typanalyze | array_subscript_handler | x
+(1 row)
+
+SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout,
+ typanalyze, typsubscript, typstorage
+FROM pg_type WHERE typname = 'myvarchardom';
+ typinput | typoutput | typreceive | typsend | typmodin | typmodout | typanalyze | typsubscript | typstorage
+-----------+--------------+-------------+---------------+----------+-----------+---------------+--------------+------------
+ domain_in | myvarcharout | domain_recv | myvarcharsend | - | - | ts_typanalyze | - | x
+(1 row)
+
+SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout,
+ typanalyze, typsubscript, typstorage
+FROM pg_type WHERE typname = '_myvarchardom';
+ typinput | typoutput | typreceive | typsend | typmodin | typmodout | typanalyze | typsubscript | typstorage
+----------+-----------+------------+------------+----------+-----------+------------------+-------------------------+------------
+ array_in | array_out | array_recv | array_send | - | - | array_typanalyze | array_subscript_handler | x
+(1 row)
+
+-- ensure dependencies are straight
+DROP FUNCTION myvarcharsend(myvarchar); -- fail
+ERROR: cannot drop function myvarcharsend(myvarchar) because other objects depend on it
+DETAIL: type myvarchar depends on function myvarcharsend(myvarchar)
+function myvarcharin(cstring,oid,integer) depends on type myvarchar
+function myvarcharout(myvarchar) depends on type myvarchar
+function myvarcharrecv(internal,oid,integer) depends on type myvarchar
+type myvarchardom depends on function myvarcharsend(myvarchar)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TYPE myvarchar; -- fail
+ERROR: cannot drop type myvarchar because other objects depend on it
+DETAIL: function myvarcharin(cstring,oid,integer) depends on type myvarchar
+function myvarcharout(myvarchar) depends on type myvarchar
+function myvarcharsend(myvarchar) depends on type myvarchar
+function myvarcharrecv(internal,oid,integer) depends on type myvarchar
+type myvarchardom depends on type myvarchar
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TYPE myvarchar CASCADE;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to function myvarcharin(cstring,oid,integer)
+drop cascades to function myvarcharout(myvarchar)
+drop cascades to function myvarcharsend(myvarchar)
+drop cascades to function myvarcharrecv(internal,oid,integer)
+drop cascades to type myvarchardom
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
new file mode 100644
index 0000000..a8df9cd
--- /dev/null
+++ b/src/test/regress/expected/create_view.out
@@ -0,0 +1,2096 @@
+--
+-- CREATE_VIEW
+-- Virtual class definitions
+-- (this also tests the query rewrite system)
+--
+CREATE VIEW street AS
+ SELECT r.name, r.thepath, c.cname AS cname
+ FROM ONLY road r, real_city c
+ WHERE c.outline ## r.thepath;
+CREATE VIEW iexit AS
+ SELECT ih.name, ih.thepath,
+ interpt_pp(ih.thepath, r.thepath) AS exit
+ FROM ihighway ih, ramp r
+ WHERE ih.thepath ## r.thepath;
+CREATE VIEW toyemp AS
+ SELECT name, age, location, 12*salary AS annualsal
+ FROM emp;
+-- Test comments
+COMMENT ON VIEW noview IS 'no view';
+ERROR: relation "noview" does not exist
+COMMENT ON VIEW toyemp IS 'is a view';
+COMMENT ON VIEW toyemp IS NULL;
+-- These views are left around mainly to exercise special cases in pg_dump.
+CREATE TABLE view_base_table (key int PRIMARY KEY, data varchar(20));
+CREATE VIEW key_dependent_view AS
+ SELECT * FROM view_base_table GROUP BY key;
+ALTER TABLE view_base_table DROP CONSTRAINT view_base_table_pkey; -- fails
+ERROR: cannot drop constraint view_base_table_pkey on table view_base_table because other objects depend on it
+DETAIL: view key_dependent_view depends on constraint view_base_table_pkey on table view_base_table
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+CREATE VIEW key_dependent_view_no_cols AS
+ SELECT FROM view_base_table GROUP BY key HAVING length(data) > 0;
+--
+-- CREATE OR REPLACE VIEW
+--
+CREATE TABLE viewtest_tbl (a int, b int);
+COPY viewtest_tbl FROM stdin;
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT * FROM viewtest_tbl;
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT * FROM viewtest_tbl WHERE a > 10;
+SELECT * FROM viewtest;
+ a | b
+----+----
+ 15 | 20
+ 20 | 25
+(2 rows)
+
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT a, b FROM viewtest_tbl WHERE a > 5 ORDER BY b DESC;
+SELECT * FROM viewtest;
+ a | b
+----+----
+ 20 | 25
+ 15 | 20
+ 10 | 15
+(3 rows)
+
+-- should fail
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT a FROM viewtest_tbl WHERE a <> 20;
+ERROR: cannot drop columns from view
+-- should fail
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT 1, * FROM viewtest_tbl;
+ERROR: cannot change name of view column "a" to "?column?"
+HINT: Use ALTER VIEW ... RENAME COLUMN ... to change name of view column instead.
+-- should fail
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT a, b::numeric FROM viewtest_tbl;
+ERROR: cannot change data type of view column "b" from integer to numeric
+-- should work
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT a, b, 0 AS c FROM viewtest_tbl;
+DROP VIEW viewtest;
+DROP TABLE viewtest_tbl;
+-- tests for temporary views
+CREATE SCHEMA temp_view_test
+ CREATE TABLE base_table (a int, id int)
+ CREATE TABLE base_table2 (a int, id int);
+SET search_path TO temp_view_test, public;
+CREATE TEMPORARY TABLE temp_table (a int, id int);
+-- should be created in temp_view_test schema
+CREATE VIEW v1 AS SELECT * FROM base_table;
+-- should be created in temp object schema
+CREATE VIEW v1_temp AS SELECT * FROM temp_table;
+NOTICE: view "v1_temp" will be a temporary view
+-- should be created in temp object schema
+CREATE TEMP VIEW v2_temp AS SELECT * FROM base_table;
+-- should be created in temp_views schema
+CREATE VIEW temp_view_test.v2 AS SELECT * FROM base_table;
+-- should fail
+CREATE VIEW temp_view_test.v3_temp AS SELECT * FROM temp_table;
+NOTICE: view "v3_temp" will be a temporary view
+ERROR: cannot create temporary relation in non-temporary schema
+-- should fail
+CREATE SCHEMA test_view_schema
+ CREATE TEMP VIEW testview AS SELECT 1;
+ERROR: cannot create temporary relation in non-temporary schema
+-- joins: if any of the join relations are temporary, the view
+-- should also be temporary
+-- should be non-temp
+CREATE VIEW v3 AS
+ SELECT t1.a AS t1_a, t2.a AS t2_a
+ FROM base_table t1, base_table2 t2
+ WHERE t1.id = t2.id;
+-- should be temp (one join rel is temp)
+CREATE VIEW v4_temp AS
+ SELECT t1.a AS t1_a, t2.a AS t2_a
+ FROM base_table t1, temp_table t2
+ WHERE t1.id = t2.id;
+NOTICE: view "v4_temp" will be a temporary view
+-- should be temp
+CREATE VIEW v5_temp AS
+ SELECT t1.a AS t1_a, t2.a AS t2_a, t3.a AS t3_a
+ FROM base_table t1, base_table2 t2, temp_table t3
+ WHERE t1.id = t2.id and t2.id = t3.id;
+NOTICE: view "v5_temp" will be a temporary view
+-- subqueries
+CREATE VIEW v4 AS SELECT * FROM base_table WHERE id IN (SELECT id FROM base_table2);
+CREATE VIEW v5 AS SELECT t1.id, t2.a FROM base_table t1, (SELECT * FROM base_table2) t2;
+CREATE VIEW v6 AS SELECT * FROM base_table WHERE EXISTS (SELECT 1 FROM base_table2);
+CREATE VIEW v7 AS SELECT * FROM base_table WHERE NOT EXISTS (SELECT 1 FROM base_table2);
+CREATE VIEW v8 AS SELECT * FROM base_table WHERE EXISTS (SELECT 1);
+CREATE VIEW v6_temp AS SELECT * FROM base_table WHERE id IN (SELECT id FROM temp_table);
+NOTICE: view "v6_temp" will be a temporary view
+CREATE VIEW v7_temp AS SELECT t1.id, t2.a FROM base_table t1, (SELECT * FROM temp_table) t2;
+NOTICE: view "v7_temp" will be a temporary view
+CREATE VIEW v8_temp AS SELECT * FROM base_table WHERE EXISTS (SELECT 1 FROM temp_table);
+NOTICE: view "v8_temp" will be a temporary view
+CREATE VIEW v9_temp AS SELECT * FROM base_table WHERE NOT EXISTS (SELECT 1 FROM temp_table);
+NOTICE: view "v9_temp" will be a temporary view
+-- a view should also be temporary if it references a temporary view
+CREATE VIEW v10_temp AS SELECT * FROM v7_temp;
+NOTICE: view "v10_temp" will be a temporary view
+CREATE VIEW v11_temp AS SELECT t1.id, t2.a FROM base_table t1, v10_temp t2;
+NOTICE: view "v11_temp" will be a temporary view
+CREATE VIEW v12_temp AS SELECT true FROM v11_temp;
+NOTICE: view "v12_temp" will be a temporary view
+-- a view should also be temporary if it references a temporary sequence
+CREATE SEQUENCE seq1;
+CREATE TEMPORARY SEQUENCE seq1_temp;
+CREATE VIEW v9 AS SELECT seq1.is_called FROM seq1;
+CREATE VIEW v13_temp AS SELECT seq1_temp.is_called FROM seq1_temp;
+NOTICE: view "v13_temp" will be a temporary view
+SELECT relname FROM pg_class
+ WHERE relname LIKE 'v_'
+ AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'temp_view_test')
+ ORDER BY relname;
+ relname
+---------
+ v1
+ v2
+ v3
+ v4
+ v5
+ v6
+ v7
+ v8
+ v9
+(9 rows)
+
+SELECT relname FROM pg_class
+ WHERE relname LIKE 'v%'
+ AND relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname LIKE 'pg_temp%')
+ ORDER BY relname;
+ relname
+----------
+ v10_temp
+ v11_temp
+ v12_temp
+ v13_temp
+ v1_temp
+ v2_temp
+ v4_temp
+ v5_temp
+ v6_temp
+ v7_temp
+ v8_temp
+ v9_temp
+(12 rows)
+
+CREATE SCHEMA testviewschm2;
+SET search_path TO testviewschm2, public;
+CREATE TABLE t1 (num int, name text);
+CREATE TABLE t2 (num2 int, value text);
+CREATE TEMP TABLE tt (num2 int, value text);
+CREATE VIEW nontemp1 AS SELECT * FROM t1 CROSS JOIN t2;
+CREATE VIEW temporal1 AS SELECT * FROM t1 CROSS JOIN tt;
+NOTICE: view "temporal1" will be a temporary view
+CREATE VIEW nontemp2 AS SELECT * FROM t1 INNER JOIN t2 ON t1.num = t2.num2;
+CREATE VIEW temporal2 AS SELECT * FROM t1 INNER JOIN tt ON t1.num = tt.num2;
+NOTICE: view "temporal2" will be a temporary view
+CREATE VIEW nontemp3 AS SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num2;
+CREATE VIEW temporal3 AS SELECT * FROM t1 LEFT JOIN tt ON t1.num = tt.num2;
+NOTICE: view "temporal3" will be a temporary view
+CREATE VIEW nontemp4 AS SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num2 AND t2.value = 'xxx';
+CREATE VIEW temporal4 AS SELECT * FROM t1 LEFT JOIN tt ON t1.num = tt.num2 AND tt.value = 'xxx';
+NOTICE: view "temporal4" will be a temporary view
+SELECT relname FROM pg_class
+ WHERE relname LIKE 'nontemp%'
+ AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'testviewschm2')
+ ORDER BY relname;
+ relname
+----------
+ nontemp1
+ nontemp2
+ nontemp3
+ nontemp4
+(4 rows)
+
+SELECT relname FROM pg_class
+ WHERE relname LIKE 'temporal%'
+ AND relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname LIKE 'pg_temp%')
+ ORDER BY relname;
+ relname
+-----------
+ temporal1
+ temporal2
+ temporal3
+ temporal4
+(4 rows)
+
+CREATE TABLE tbl1 ( a int, b int);
+CREATE TABLE tbl2 (c int, d int);
+CREATE TABLE tbl3 (e int, f int);
+CREATE TABLE tbl4 (g int, h int);
+CREATE TEMP TABLE tmptbl (i int, j int);
+--Should be in testviewschm2
+CREATE VIEW pubview AS SELECT * FROM tbl1 WHERE tbl1.a
+BETWEEN (SELECT d FROM tbl2 WHERE c = 1) AND (SELECT e FROM tbl3 WHERE f = 2)
+AND EXISTS (SELECT g FROM tbl4 LEFT JOIN tbl3 ON tbl4.h = tbl3.f);
+SELECT count(*) FROM pg_class where relname = 'pubview'
+AND relnamespace IN (SELECT OID FROM pg_namespace WHERE nspname = 'testviewschm2');
+ count
+-------
+ 1
+(1 row)
+
+--Should be in temp object schema
+CREATE VIEW mytempview AS SELECT * FROM tbl1 WHERE tbl1.a
+BETWEEN (SELECT d FROM tbl2 WHERE c = 1) AND (SELECT e FROM tbl3 WHERE f = 2)
+AND EXISTS (SELECT g FROM tbl4 LEFT JOIN tbl3 ON tbl4.h = tbl3.f)
+AND NOT EXISTS (SELECT g FROM tbl4 LEFT JOIN tmptbl ON tbl4.h = tmptbl.j);
+NOTICE: view "mytempview" will be a temporary view
+SELECT count(*) FROM pg_class where relname LIKE 'mytempview'
+And relnamespace IN (SELECT OID FROM pg_namespace WHERE nspname LIKE 'pg_temp%');
+ count
+-------
+ 1
+(1 row)
+
+--
+-- CREATE VIEW and WITH(...) clause
+--
+CREATE VIEW mysecview1
+ AS SELECT * FROM tbl1 WHERE a = 0;
+CREATE VIEW mysecview2 WITH (security_barrier=true)
+ AS SELECT * FROM tbl1 WHERE a > 0;
+CREATE VIEW mysecview3 WITH (security_barrier=false)
+ AS SELECT * FROM tbl1 WHERE a < 0;
+CREATE VIEW mysecview4 WITH (security_barrier)
+ AS SELECT * FROM tbl1 WHERE a <> 0;
+CREATE VIEW mysecview5 WITH (security_barrier=100) -- Error
+ AS SELECT * FROM tbl1 WHERE a > 100;
+ERROR: invalid value for boolean option "security_barrier": 100
+CREATE VIEW mysecview6 WITH (invalid_option) -- Error
+ AS SELECT * FROM tbl1 WHERE a < 100;
+ERROR: unrecognized parameter "invalid_option"
+SELECT relname, relkind, reloptions FROM pg_class
+ WHERE oid in ('mysecview1'::regclass, 'mysecview2'::regclass,
+ 'mysecview3'::regclass, 'mysecview4'::regclass)
+ ORDER BY relname;
+ relname | relkind | reloptions
+------------+---------+--------------------------
+ mysecview1 | v |
+ mysecview2 | v | {security_barrier=true}
+ mysecview3 | v | {security_barrier=false}
+ mysecview4 | v | {security_barrier=true}
+(4 rows)
+
+CREATE OR REPLACE VIEW mysecview1
+ AS SELECT * FROM tbl1 WHERE a = 256;
+CREATE OR REPLACE VIEW mysecview2
+ AS SELECT * FROM tbl1 WHERE a > 256;
+CREATE OR REPLACE VIEW mysecview3 WITH (security_barrier=true)
+ AS SELECT * FROM tbl1 WHERE a < 256;
+CREATE OR REPLACE VIEW mysecview4 WITH (security_barrier=false)
+ AS SELECT * FROM tbl1 WHERE a <> 256;
+SELECT relname, relkind, reloptions FROM pg_class
+ WHERE oid in ('mysecview1'::regclass, 'mysecview2'::regclass,
+ 'mysecview3'::regclass, 'mysecview4'::regclass)
+ ORDER BY relname;
+ relname | relkind | reloptions
+------------+---------+--------------------------
+ mysecview1 | v |
+ mysecview2 | v |
+ mysecview3 | v | {security_barrier=true}
+ mysecview4 | v | {security_barrier=false}
+(4 rows)
+
+-- Check that unknown literals are converted to "text" in CREATE VIEW,
+-- so that we don't end up with unknown-type columns.
+CREATE VIEW unspecified_types AS
+ SELECT 42 as i, 42.5 as num, 'foo' as u, 'foo'::unknown as u2, null as n;
+\d+ unspecified_types
+ View "testviewschm2.unspecified_types"
+ Column | Type | Collation | Nullable | Default | Storage | 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 unspecified_types;
+ i | num | u | u2 | n
+----+------+-----+-----+---
+ 42 | 42.5 | foo | foo |
+(1 row)
+
+-- This test checks that proper typmods are assigned in a multi-row VALUES
+CREATE VIEW tt1 AS
+ SELECT * FROM (
+ VALUES
+ ('abc'::varchar(3), '0123456789', 42, 'abcd'::varchar(4)),
+ ('0123456789', 'abc'::varchar(3), 42.12, 'abc'::varchar(4))
+ ) vv(a,b,c,d);
+\d+ tt1
+ View "testviewschm2.tt1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+----------------------+-----------+----------+---------+----------+-------------
+ a | character varying | | | | extended |
+ b | character varying | | | | extended |
+ c | numeric | | | | main |
+ d | character varying(4) | | | | extended |
+View definition:
+ SELECT vv.a,
+ vv.b,
+ vv.c,
+ vv.d
+ FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
+
+SELECT * FROM tt1;
+ a | b | c | d
+------------+------------+-------+------
+ abc | 0123456789 | 42 | abcd
+ 0123456789 | abc | 42.12 | abc
+(2 rows)
+
+SELECT a::varchar(3) FROM tt1;
+ a
+-----
+ abc
+ 012
+(2 rows)
+
+DROP VIEW tt1;
+-- Test view decompilation in the face of relation renaming conflicts
+CREATE TABLE tt1 (f1 int, f2 int, f3 text);
+CREATE TABLE tx1 (x1 int, x2 int, x3 text);
+CREATE TABLE temp_view_test.tt1 (y1 int, f2 int, f3 text);
+CREATE VIEW aliased_view_1 AS
+ select * from tt1
+ where exists (select 1 from tx1 where tt1.f1 = tx1.x1);
+CREATE VIEW aliased_view_2 AS
+ select * from tt1 a1
+ where exists (select 1 from tx1 where a1.f1 = tx1.x1);
+CREATE VIEW aliased_view_3 AS
+ select * from tt1
+ where exists (select 1 from tx1 a2 where tt1.f1 = a2.x1);
+CREATE VIEW aliased_view_4 AS
+ select * from temp_view_test.tt1
+ where exists (select 1 from tt1 where temp_view_test.tt1.y1 = tt1.f1);
+\d+ aliased_view_1
+ View "testviewschm2.aliased_view_1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tt1.f1,
+ tt1.f2,
+ tt1.f3
+ FROM tt1
+ WHERE (EXISTS ( SELECT 1
+ FROM tx1
+ WHERE tt1.f1 = tx1.x1));
+
+\d+ aliased_view_2
+ View "testviewschm2.aliased_view_2"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT a1.f1,
+ a1.f2,
+ a1.f3
+ FROM tt1 a1
+ WHERE (EXISTS ( SELECT 1
+ FROM tx1
+ WHERE a1.f1 = tx1.x1));
+
+\d+ aliased_view_3
+ View "testviewschm2.aliased_view_3"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tt1.f1,
+ tt1.f2,
+ tt1.f3
+ FROM tt1
+ WHERE (EXISTS ( SELECT 1
+ FROM tx1 a2
+ WHERE tt1.f1 = a2.x1));
+
+\d+ aliased_view_4
+ View "testviewschm2.aliased_view_4"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ y1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tt1.y1,
+ tt1.f2,
+ tt1.f3
+ FROM temp_view_test.tt1
+ WHERE (EXISTS ( SELECT 1
+ FROM tt1 tt1_1
+ WHERE tt1.y1 = tt1_1.f1));
+
+ALTER TABLE tx1 RENAME TO a1;
+\d+ aliased_view_1
+ View "testviewschm2.aliased_view_1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tt1.f1,
+ tt1.f2,
+ tt1.f3
+ FROM tt1
+ WHERE (EXISTS ( SELECT 1
+ FROM a1
+ WHERE tt1.f1 = a1.x1));
+
+\d+ aliased_view_2
+ View "testviewschm2.aliased_view_2"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT a1.f1,
+ a1.f2,
+ a1.f3
+ FROM tt1 a1
+ WHERE (EXISTS ( SELECT 1
+ FROM a1 a1_1
+ WHERE a1.f1 = a1_1.x1));
+
+\d+ aliased_view_3
+ View "testviewschm2.aliased_view_3"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tt1.f1,
+ tt1.f2,
+ tt1.f3
+ FROM tt1
+ WHERE (EXISTS ( SELECT 1
+ FROM a1 a2
+ WHERE tt1.f1 = a2.x1));
+
+\d+ aliased_view_4
+ View "testviewschm2.aliased_view_4"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ y1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tt1.y1,
+ tt1.f2,
+ tt1.f3
+ FROM temp_view_test.tt1
+ WHERE (EXISTS ( SELECT 1
+ FROM tt1 tt1_1
+ WHERE tt1.y1 = tt1_1.f1));
+
+ALTER TABLE tt1 RENAME TO a2;
+\d+ aliased_view_1
+ View "testviewschm2.aliased_view_1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT a2.f1,
+ a2.f2,
+ a2.f3
+ FROM a2
+ WHERE (EXISTS ( SELECT 1
+ FROM a1
+ WHERE a2.f1 = a1.x1));
+
+\d+ aliased_view_2
+ View "testviewschm2.aliased_view_2"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT a1.f1,
+ a1.f2,
+ a1.f3
+ FROM a2 a1
+ WHERE (EXISTS ( SELECT 1
+ FROM a1 a1_1
+ WHERE a1.f1 = a1_1.x1));
+
+\d+ aliased_view_3
+ View "testviewschm2.aliased_view_3"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT a2.f1,
+ a2.f2,
+ a2.f3
+ FROM a2
+ WHERE (EXISTS ( SELECT 1
+ FROM a1 a2_1
+ WHERE a2.f1 = a2_1.x1));
+
+\d+ aliased_view_4
+ View "testviewschm2.aliased_view_4"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ y1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tt1.y1,
+ tt1.f2,
+ tt1.f3
+ FROM temp_view_test.tt1
+ WHERE (EXISTS ( SELECT 1
+ FROM a2
+ WHERE tt1.y1 = a2.f1));
+
+ALTER TABLE a1 RENAME TO tt1;
+\d+ aliased_view_1
+ View "testviewschm2.aliased_view_1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT a2.f1,
+ a2.f2,
+ a2.f3
+ FROM a2
+ WHERE (EXISTS ( SELECT 1
+ FROM tt1
+ WHERE a2.f1 = tt1.x1));
+
+\d+ aliased_view_2
+ View "testviewschm2.aliased_view_2"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT a1.f1,
+ a1.f2,
+ a1.f3
+ FROM a2 a1
+ WHERE (EXISTS ( SELECT 1
+ FROM tt1
+ WHERE a1.f1 = tt1.x1));
+
+\d+ aliased_view_3
+ View "testviewschm2.aliased_view_3"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT a2.f1,
+ a2.f2,
+ a2.f3
+ FROM a2
+ WHERE (EXISTS ( SELECT 1
+ FROM tt1 a2_1
+ WHERE a2.f1 = a2_1.x1));
+
+\d+ aliased_view_4
+ View "testviewschm2.aliased_view_4"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ y1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tt1.y1,
+ tt1.f2,
+ tt1.f3
+ FROM temp_view_test.tt1
+ WHERE (EXISTS ( SELECT 1
+ FROM a2
+ WHERE tt1.y1 = a2.f1));
+
+ALTER TABLE a2 RENAME TO tx1;
+ALTER TABLE tx1 SET SCHEMA temp_view_test;
+\d+ aliased_view_1
+ View "testviewschm2.aliased_view_1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tx1.f1,
+ tx1.f2,
+ tx1.f3
+ FROM temp_view_test.tx1
+ WHERE (EXISTS ( SELECT 1
+ FROM tt1
+ WHERE tx1.f1 = tt1.x1));
+
+\d+ aliased_view_2
+ View "testviewschm2.aliased_view_2"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT a1.f1,
+ a1.f2,
+ a1.f3
+ FROM temp_view_test.tx1 a1
+ WHERE (EXISTS ( SELECT 1
+ FROM tt1
+ WHERE a1.f1 = tt1.x1));
+
+\d+ aliased_view_3
+ View "testviewschm2.aliased_view_3"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tx1.f1,
+ tx1.f2,
+ tx1.f3
+ FROM temp_view_test.tx1
+ WHERE (EXISTS ( SELECT 1
+ FROM tt1 a2
+ WHERE tx1.f1 = a2.x1));
+
+\d+ aliased_view_4
+ View "testviewschm2.aliased_view_4"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ y1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tt1.y1,
+ tt1.f2,
+ tt1.f3
+ FROM temp_view_test.tt1
+ WHERE (EXISTS ( SELECT 1
+ FROM temp_view_test.tx1
+ WHERE tt1.y1 = tx1.f1));
+
+ALTER TABLE temp_view_test.tt1 RENAME TO tmp1;
+ALTER TABLE temp_view_test.tmp1 SET SCHEMA testviewschm2;
+ALTER TABLE tmp1 RENAME TO tx1;
+\d+ aliased_view_1
+ View "testviewschm2.aliased_view_1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tx1.f1,
+ tx1.f2,
+ tx1.f3
+ FROM temp_view_test.tx1
+ WHERE (EXISTS ( SELECT 1
+ FROM tt1
+ WHERE tx1.f1 = tt1.x1));
+
+\d+ aliased_view_2
+ View "testviewschm2.aliased_view_2"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT a1.f1,
+ a1.f2,
+ a1.f3
+ FROM temp_view_test.tx1 a1
+ WHERE (EXISTS ( SELECT 1
+ FROM tt1
+ WHERE a1.f1 = tt1.x1));
+
+\d+ aliased_view_3
+ View "testviewschm2.aliased_view_3"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ f1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tx1.f1,
+ tx1.f2,
+ tx1.f3
+ FROM temp_view_test.tx1
+ WHERE (EXISTS ( SELECT 1
+ FROM tt1 a2
+ WHERE tx1.f1 = a2.x1));
+
+\d+ aliased_view_4
+ View "testviewschm2.aliased_view_4"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+----------+-------------
+ y1 | integer | | | | plain |
+ f2 | integer | | | | plain |
+ f3 | text | | | | extended |
+View definition:
+ SELECT tx1.y1,
+ tx1.f2,
+ tx1.f3
+ FROM tx1
+ WHERE (EXISTS ( SELECT 1
+ FROM temp_view_test.tx1 tx1_1
+ WHERE tx1.y1 = tx1_1.f1));
+
+-- Test aliasing of joins
+create view view_of_joins as
+select * from
+ (select * from (tbl1 cross join tbl2) same) ss,
+ (tbl3 cross join tbl4) same;
+\d+ view_of_joins
+ View "testviewschm2.view_of_joins"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+---------+-------------
+ a | integer | | | | plain |
+ b | integer | | | | plain |
+ c | integer | | | | plain |
+ d | integer | | | | plain |
+ e | integer | | | | plain |
+ f | integer | | | | plain |
+ g | integer | | | | plain |
+ h | integer | | | | plain |
+View definition:
+ SELECT ss.a,
+ ss.b,
+ ss.c,
+ ss.d,
+ same.e,
+ same.f,
+ same.g,
+ same.h
+ FROM ( SELECT same_1.a,
+ same_1.b,
+ same_1.c,
+ same_1.d
+ FROM (tbl1
+ CROSS JOIN tbl2) same_1) ss,
+ (tbl3
+ CROSS JOIN tbl4) same;
+
+create table tbl1a (a int, c int);
+create view view_of_joins_2a as select * from tbl1 join tbl1a using (a);
+create view view_of_joins_2b as select * from tbl1 join tbl1a using (a) as x;
+create view view_of_joins_2c as select * from (tbl1 join tbl1a using (a)) as y;
+create view view_of_joins_2d as select * from (tbl1 join tbl1a using (a) as x) as y;
+select pg_get_viewdef('view_of_joins_2a', true);
+ pg_get_viewdef
+----------------------------
+ SELECT tbl1.a, +
+ tbl1.b, +
+ tbl1a.c +
+ FROM tbl1 +
+ JOIN tbl1a USING (a);
+(1 row)
+
+select pg_get_viewdef('view_of_joins_2b', true);
+ pg_get_viewdef
+---------------------------------
+ SELECT tbl1.a, +
+ tbl1.b, +
+ tbl1a.c +
+ FROM tbl1 +
+ JOIN tbl1a USING (a) AS x;
+(1 row)
+
+select pg_get_viewdef('view_of_joins_2c', true);
+ pg_get_viewdef
+-------------------------------
+ SELECT y.a, +
+ y.b, +
+ y.c +
+ FROM (tbl1 +
+ JOIN tbl1a USING (a)) y;
+(1 row)
+
+select pg_get_viewdef('view_of_joins_2d', true);
+ pg_get_viewdef
+------------------------------------
+ SELECT y.a, +
+ y.b, +
+ y.c +
+ FROM (tbl1 +
+ JOIN tbl1a USING (a) AS x) y;
+(1 row)
+
+-- Test view decompilation in the face of column addition/deletion/renaming
+create table tt2 (a int, b int, c int);
+create table tt3 (ax int8, b int2, c numeric);
+create table tt4 (ay int, b int, q int);
+create view v1 as select * from tt2 natural join tt3;
+create view v1a as select * from (tt2 natural join tt3) j;
+create view v2 as select * from tt2 join tt3 using (b,c) join tt4 using (b);
+create view v2a as select * from (tt2 join tt3 using (b,c) join tt4 using (b)) j;
+create view v3 as select * from tt2 join tt3 using (b,c) full join tt4 using (b);
+select pg_get_viewdef('v1', true);
+ pg_get_viewdef
+-----------------------------
+ SELECT tt2.b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax +
+ FROM tt2 +
+ JOIN tt3 USING (b, c);
+(1 row)
+
+select pg_get_viewdef('v1a', true);
+ pg_get_viewdef
+--------------------------------
+ SELECT j.b, +
+ j.c, +
+ j.a, +
+ j.ax +
+ FROM (tt2 +
+ JOIN tt3 USING (b, c)) j;
+(1 row)
+
+select pg_get_viewdef('v2', true);
+ pg_get_viewdef
+----------------------------
+ SELECT tt2.b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax, +
+ tt4.ay, +
+ tt4.q +
+ FROM tt2 +
+ JOIN tt3 USING (b, c)+
+ JOIN tt4 USING (b);
+(1 row)
+
+select pg_get_viewdef('v2a', true);
+ pg_get_viewdef
+-----------------------------
+ SELECT j.b, +
+ j.c, +
+ j.a, +
+ j.ax, +
+ j.ay, +
+ j.q +
+ FROM (tt2 +
+ JOIN tt3 USING (b, c) +
+ JOIN tt4 USING (b)) j;
+(1 row)
+
+select pg_get_viewdef('v3', true);
+ pg_get_viewdef
+-------------------------------
+ SELECT b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax, +
+ tt4.ay, +
+ tt4.q +
+ FROM tt2 +
+ JOIN tt3 USING (b, c) +
+ FULL JOIN tt4 USING (b);
+(1 row)
+
+alter table tt2 add column d int;
+alter table tt2 add column e int;
+select pg_get_viewdef('v1', true);
+ pg_get_viewdef
+-----------------------------
+ SELECT tt2.b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax +
+ FROM tt2 +
+ JOIN tt3 USING (b, c);
+(1 row)
+
+select pg_get_viewdef('v1a', true);
+ pg_get_viewdef
+--------------------------------
+ SELECT j.b, +
+ j.c, +
+ j.a, +
+ j.ax +
+ FROM (tt2 +
+ JOIN tt3 USING (b, c)) j;
+(1 row)
+
+select pg_get_viewdef('v2', true);
+ pg_get_viewdef
+----------------------------
+ SELECT tt2.b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax, +
+ tt4.ay, +
+ tt4.q +
+ FROM tt2 +
+ JOIN tt3 USING (b, c)+
+ JOIN tt4 USING (b);
+(1 row)
+
+select pg_get_viewdef('v2a', true);
+ pg_get_viewdef
+-----------------------------
+ SELECT j.b, +
+ j.c, +
+ j.a, +
+ j.ax, +
+ j.ay, +
+ j.q +
+ FROM (tt2 +
+ JOIN tt3 USING (b, c) +
+ JOIN tt4 USING (b)) j;
+(1 row)
+
+select pg_get_viewdef('v3', true);
+ pg_get_viewdef
+-------------------------------
+ SELECT b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax, +
+ tt4.ay, +
+ tt4.q +
+ FROM tt2 +
+ JOIN tt3 USING (b, c) +
+ FULL JOIN tt4 USING (b);
+(1 row)
+
+alter table tt3 rename c to d;
+select pg_get_viewdef('v1', true);
+ pg_get_viewdef
+-------------------------------------------
+ SELECT tt2.b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax +
+ FROM tt2 +
+ JOIN tt3 tt3(ax, b, c) USING (b, c);
+(1 row)
+
+select pg_get_viewdef('v1a', true);
+ pg_get_viewdef
+----------------------------------------------
+ SELECT j.b, +
+ j.c, +
+ j.a, +
+ j.ax +
+ FROM (tt2 +
+ JOIN tt3 tt3(ax, b, c) USING (b, c)) j;
+(1 row)
+
+select pg_get_viewdef('v2', true);
+ pg_get_viewdef
+------------------------------------------
+ SELECT tt2.b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax, +
+ tt4.ay, +
+ tt4.q +
+ FROM tt2 +
+ JOIN tt3 tt3(ax, b, c) USING (b, c)+
+ JOIN tt4 USING (b);
+(1 row)
+
+select pg_get_viewdef('v2a', true);
+ pg_get_viewdef
+------------------------------------------
+ SELECT j.b, +
+ j.c, +
+ j.a, +
+ j.ax, +
+ j.ay, +
+ j.q +
+ FROM (tt2 +
+ JOIN tt3 tt3(ax, b, c) USING (b, c)+
+ JOIN tt4 USING (b)) j;
+(1 row)
+
+select pg_get_viewdef('v3', true);
+ pg_get_viewdef
+------------------------------------------
+ SELECT b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax, +
+ tt4.ay, +
+ tt4.q +
+ FROM tt2 +
+ JOIN tt3 tt3(ax, b, c) USING (b, c)+
+ FULL JOIN tt4 USING (b);
+(1 row)
+
+alter table tt3 add column c int;
+alter table tt3 add column e int;
+select pg_get_viewdef('v1', true);
+ pg_get_viewdef
+---------------------------------------------------
+ SELECT tt2.b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax +
+ FROM tt2 +
+ JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c);
+(1 row)
+
+select pg_get_viewdef('v1a', true);
+ pg_get_viewdef
+-----------------------------------------------------------------------------------
+ SELECT j.b, +
+ j.c, +
+ j.a, +
+ j.ax +
+ FROM (tt2 +
+ JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)) j(b, c, a, d, e, ax, c_1, e_1);
+(1 row)
+
+select pg_get_viewdef('v2', true);
+ pg_get_viewdef
+--------------------------------------------------
+ SELECT tt2.b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax, +
+ tt4.ay, +
+ tt4.q +
+ FROM tt2 +
+ JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)+
+ JOIN tt4 USING (b);
+(1 row)
+
+select pg_get_viewdef('v2a', true);
+ pg_get_viewdef
+-----------------------------------------------------------------
+ SELECT j.b, +
+ j.c, +
+ j.a, +
+ j.ax, +
+ j.ay, +
+ j.q +
+ FROM (tt2 +
+ JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c) +
+ JOIN tt4 USING (b)) j(b, c, a, d, e, ax, c_1, e_1, ay, q);
+(1 row)
+
+select pg_get_viewdef('v3', true);
+ pg_get_viewdef
+--------------------------------------------------
+ SELECT b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax, +
+ tt4.ay, +
+ tt4.q +
+ FROM tt2 +
+ JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)+
+ FULL JOIN tt4 USING (b);
+(1 row)
+
+alter table tt2 drop column d;
+select pg_get_viewdef('v1', true);
+ pg_get_viewdef
+---------------------------------------------------
+ SELECT tt2.b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax +
+ FROM tt2 +
+ JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c);
+(1 row)
+
+select pg_get_viewdef('v1a', true);
+ pg_get_viewdef
+--------------------------------------------------------------------------------
+ SELECT j.b, +
+ j.c, +
+ j.a, +
+ j.ax +
+ FROM (tt2 +
+ JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)) j(b, c, a, e, ax, c_1, e_1);
+(1 row)
+
+select pg_get_viewdef('v2', true);
+ pg_get_viewdef
+--------------------------------------------------
+ SELECT tt2.b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax, +
+ tt4.ay, +
+ tt4.q +
+ FROM tt2 +
+ JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)+
+ JOIN tt4 USING (b);
+(1 row)
+
+select pg_get_viewdef('v2a', true);
+ pg_get_viewdef
+--------------------------------------------------------------
+ SELECT j.b, +
+ j.c, +
+ j.a, +
+ j.ax, +
+ j.ay, +
+ j.q +
+ FROM (tt2 +
+ JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c) +
+ JOIN tt4 USING (b)) j(b, c, a, e, ax, c_1, e_1, ay, q);
+(1 row)
+
+select pg_get_viewdef('v3', true);
+ pg_get_viewdef
+--------------------------------------------------
+ SELECT b, +
+ tt3.c, +
+ tt2.a, +
+ tt3.ax, +
+ tt4.ay, +
+ tt4.q +
+ FROM tt2 +
+ JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)+
+ FULL JOIN tt4 USING (b);
+(1 row)
+
+create table tt5 (a int, b int);
+create table tt6 (c int, d int);
+create view vv1 as select * from (tt5 cross join tt6) j(aa,bb,cc,dd);
+select pg_get_viewdef('vv1', true);
+ pg_get_viewdef
+-----------------------------------------
+ SELECT j.aa, +
+ j.bb, +
+ j.cc, +
+ j.dd +
+ FROM (tt5 +
+ CROSS JOIN tt6) j(aa, bb, cc, dd);
+(1 row)
+
+alter table tt5 add column c int;
+select pg_get_viewdef('vv1', true);
+ pg_get_viewdef
+--------------------------------------------
+ SELECT j.aa, +
+ j.bb, +
+ j.cc, +
+ j.dd +
+ FROM (tt5 +
+ CROSS JOIN tt6) j(aa, bb, c, cc, dd);
+(1 row)
+
+alter table tt5 add column cc int;
+select pg_get_viewdef('vv1', true);
+ pg_get_viewdef
+--------------------------------------------------
+ SELECT j.aa, +
+ j.bb, +
+ j.cc, +
+ j.dd +
+ FROM (tt5 +
+ CROSS JOIN tt6) j(aa, bb, c, cc_1, cc, dd);
+(1 row)
+
+alter table tt5 drop column c;
+select pg_get_viewdef('vv1', true);
+ pg_get_viewdef
+-----------------------------------------------
+ SELECT j.aa, +
+ j.bb, +
+ j.cc, +
+ j.dd +
+ FROM (tt5 +
+ CROSS JOIN tt6) j(aa, bb, cc_1, cc, dd);
+(1 row)
+
+create view v4 as select * from v1;
+alter view v1 rename column a to x;
+select pg_get_viewdef('v1', true);
+ pg_get_viewdef
+---------------------------------------------------
+ SELECT tt2.b, +
+ tt3.c, +
+ tt2.a AS x, +
+ tt3.ax +
+ FROM tt2 +
+ JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c);
+(1 row)
+
+select pg_get_viewdef('v4', true);
+ pg_get_viewdef
+----------------
+ SELECT v1.b, +
+ v1.c, +
+ v1.x AS a,+
+ v1.ax +
+ FROM v1;
+(1 row)
+
+-- Unnamed FULL JOIN USING is lots of fun too
+create table tt7 (x int, xx int, y int);
+alter table tt7 drop column xx;
+create table tt8 (x int, z int);
+create view vv2 as
+select * from (values(1,2,3,4,5)) v(a,b,c,d,e)
+union all
+select * from tt7 full join tt8 using (x), tt8 tt8x;
+select pg_get_viewdef('vv2', true);
+ pg_get_viewdef
+------------------------------------------------
+ SELECT v.a, +
+ v.b, +
+ v.c, +
+ v.d, +
+ v.e +
+ FROM ( VALUES (1,2,3,4,5)) v(a, b, c, d, e)+
+ UNION ALL +
+ SELECT x AS a, +
+ tt7.y AS b, +
+ tt8.z AS c, +
+ tt8x.x_1 AS d, +
+ tt8x.z AS e +
+ FROM tt7 +
+ FULL JOIN tt8 USING (x), +
+ tt8 tt8x(x_1, z);
+(1 row)
+
+create view vv3 as
+select * from (values(1,2,3,4,5,6)) v(a,b,c,x,e,f)
+union all
+select * from
+ tt7 full join tt8 using (x),
+ tt7 tt7x full join tt8 tt8x using (x);
+select pg_get_viewdef('vv3', true);
+ pg_get_viewdef
+-----------------------------------------------------
+ SELECT v.a, +
+ v.b, +
+ v.c, +
+ v.x, +
+ v.e, +
+ v.f +
+ FROM ( VALUES (1,2,3,4,5,6)) v(a, b, c, x, e, f)+
+ UNION ALL +
+ SELECT x AS a, +
+ tt7.y AS b, +
+ tt8.z AS c, +
+ x_1 AS x, +
+ tt7x.y AS e, +
+ tt8x.z AS f +
+ FROM tt7 +
+ FULL JOIN tt8 USING (x), +
+ tt7 tt7x(x_1, y) +
+ FULL JOIN tt8 tt8x(x_1, z) USING (x_1);
+(1 row)
+
+create view vv4 as
+select * from (values(1,2,3,4,5,6,7)) v(a,b,c,x,e,f,g)
+union all
+select * from
+ tt7 full join tt8 using (x),
+ tt7 tt7x full join tt8 tt8x using (x) full join tt8 tt8y using (x);
+select pg_get_viewdef('vv4', true);
+ pg_get_viewdef
+----------------------------------------------------------
+ SELECT v.a, +
+ v.b, +
+ v.c, +
+ v.x, +
+ v.e, +
+ v.f, +
+ v.g +
+ FROM ( VALUES (1,2,3,4,5,6,7)) v(a, b, c, x, e, f, g)+
+ UNION ALL +
+ SELECT x AS a, +
+ tt7.y AS b, +
+ tt8.z AS c, +
+ x_1 AS x, +
+ tt7x.y AS e, +
+ tt8x.z AS f, +
+ tt8y.z AS g +
+ FROM tt7 +
+ FULL JOIN tt8 USING (x), +
+ tt7 tt7x(x_1, y) +
+ FULL JOIN tt8 tt8x(x_1, z) USING (x_1) +
+ FULL JOIN tt8 tt8y(x_1, z) USING (x_1);
+(1 row)
+
+alter table tt7 add column zz int;
+alter table tt7 add column z int;
+alter table tt7 drop column zz;
+alter table tt8 add column z2 int;
+select pg_get_viewdef('vv2', true);
+ pg_get_viewdef
+------------------------------------------------
+ SELECT v.a, +
+ v.b, +
+ v.c, +
+ v.d, +
+ v.e +
+ FROM ( VALUES (1,2,3,4,5)) v(a, b, c, d, e)+
+ UNION ALL +
+ SELECT x AS a, +
+ tt7.y AS b, +
+ tt8.z AS c, +
+ tt8x.x_1 AS d, +
+ tt8x.z AS e +
+ FROM tt7 +
+ FULL JOIN tt8 USING (x), +
+ tt8 tt8x(x_1, z, z2);
+(1 row)
+
+select pg_get_viewdef('vv3', true);
+ pg_get_viewdef
+-----------------------------------------------------
+ SELECT v.a, +
+ v.b, +
+ v.c, +
+ v.x, +
+ v.e, +
+ v.f +
+ FROM ( VALUES (1,2,3,4,5,6)) v(a, b, c, x, e, f)+
+ UNION ALL +
+ SELECT x AS a, +
+ tt7.y AS b, +
+ tt8.z AS c, +
+ x_1 AS x, +
+ tt7x.y AS e, +
+ tt8x.z AS f +
+ FROM tt7 +
+ FULL JOIN tt8 USING (x), +
+ tt7 tt7x(x_1, y, z) +
+ FULL JOIN tt8 tt8x(x_1, z, z2) USING (x_1);
+(1 row)
+
+select pg_get_viewdef('vv4', true);
+ pg_get_viewdef
+----------------------------------------------------------
+ SELECT v.a, +
+ v.b, +
+ v.c, +
+ v.x, +
+ v.e, +
+ v.f, +
+ v.g +
+ FROM ( VALUES (1,2,3,4,5,6,7)) v(a, b, c, x, e, f, g)+
+ UNION ALL +
+ SELECT x AS a, +
+ tt7.y AS b, +
+ tt8.z AS c, +
+ x_1 AS x, +
+ tt7x.y AS e, +
+ tt8x.z AS f, +
+ tt8y.z AS g +
+ FROM tt7 +
+ FULL JOIN tt8 USING (x), +
+ tt7 tt7x(x_1, y, z) +
+ FULL JOIN tt8 tt8x(x_1, z, z2) USING (x_1) +
+ FULL JOIN tt8 tt8y(x_1, z, z2) USING (x_1);
+(1 row)
+
+-- Implicit coercions in a JOIN USING create issues similar to FULL JOIN
+create table tt7a (x date, xx int, y int);
+alter table tt7a drop column xx;
+create table tt8a (x timestamptz, z int);
+create view vv2a as
+select * from (values(now(),2,3,now(),5)) v(a,b,c,d,e)
+union all
+select * from tt7a left join tt8a using (x), tt8a tt8ax;
+select pg_get_viewdef('vv2a', true);
+ pg_get_viewdef
+--------------------------------------------------------
+ SELECT v.a, +
+ v.b, +
+ v.c, +
+ v.d, +
+ v.e +
+ FROM ( VALUES (now(),2,3,now(),5)) v(a, b, c, d, e)+
+ UNION ALL +
+ SELECT x AS a, +
+ tt7a.y AS b, +
+ tt8a.z AS c, +
+ tt8ax.x_1 AS d, +
+ tt8ax.z AS e +
+ FROM tt7a +
+ LEFT JOIN tt8a USING (x), +
+ tt8a tt8ax(x_1, z);
+(1 row)
+
+--
+-- Also check dropping a column that existed when the view was made
+--
+create table tt9 (x int, xx int, y int);
+create table tt10 (x int, z int);
+create view vv5 as select x,y,z from tt9 join tt10 using(x);
+select pg_get_viewdef('vv5', true);
+ pg_get_viewdef
+---------------------------
+ SELECT tt9.x, +
+ tt9.y, +
+ tt10.z +
+ FROM tt9 +
+ JOIN tt10 USING (x);
+(1 row)
+
+alter table tt9 drop column xx;
+select pg_get_viewdef('vv5', true);
+ pg_get_viewdef
+---------------------------
+ SELECT tt9.x, +
+ tt9.y, +
+ tt10.z +
+ FROM tt9 +
+ JOIN tt10 USING (x);
+(1 row)
+
+--
+-- Another corner case is that we might add a column to a table below a
+-- JOIN USING, and thereby make the USING column name ambiguous
+--
+create table tt11 (x int, y int);
+create table tt12 (x int, z int);
+create table tt13 (z int, q int);
+create view vv6 as select x,y,z,q from
+ (tt11 join tt12 using(x)) join tt13 using(z);
+select pg_get_viewdef('vv6', true);
+ pg_get_viewdef
+---------------------------
+ SELECT tt11.x, +
+ tt11.y, +
+ tt12.z, +
+ tt13.q +
+ FROM tt11 +
+ JOIN tt12 USING (x) +
+ JOIN tt13 USING (z);
+(1 row)
+
+alter table tt11 add column z int;
+select pg_get_viewdef('vv6', true);
+ pg_get_viewdef
+------------------------------
+ SELECT tt11.x, +
+ tt11.y, +
+ tt12.z, +
+ tt13.q +
+ FROM tt11 tt11(x, y, z_1)+
+ JOIN tt12 USING (x) +
+ JOIN tt13 USING (z);
+(1 row)
+
+--
+-- Check cases involving dropped/altered columns in a function's rowtype result
+--
+create table tt14t (f1 text, f2 text, f3 text, f4 text);
+insert into tt14t values('foo', 'bar', 'baz', '42');
+alter table tt14t drop column f2;
+create function tt14f() returns setof tt14t as
+$$
+declare
+ rec1 record;
+begin
+ for rec1 in select * from tt14t
+ loop
+ return next rec1;
+ end loop;
+end;
+$$
+language plpgsql;
+create view tt14v as select t.* from tt14f() t;
+select pg_get_viewdef('tt14v', true);
+ pg_get_viewdef
+--------------------------------
+ SELECT t.f1, +
+ t.f3, +
+ t.f4 +
+ FROM tt14f() t(f1, f3, f4);
+(1 row)
+
+select * from tt14v;
+ f1 | f3 | f4
+-----+-----+----
+ foo | baz | 42
+(1 row)
+
+begin;
+-- this perhaps should be rejected, but it isn't:
+alter table tt14t drop column f3;
+-- column f3 is still in the view, sort of ...
+select pg_get_viewdef('tt14v', true);
+ pg_get_viewdef
+---------------------------------
+ SELECT t.f1, +
+ t."?dropped?column?" AS f3,+
+ t.f4 +
+ FROM tt14f() t(f1, f4);
+(1 row)
+
+-- ... and you can even EXPLAIN it ...
+explain (verbose, costs off) select * from tt14v;
+ QUERY PLAN
+----------------------------------------
+ Function Scan on testviewschm2.tt14f t
+ Output: t.f1, t.f3, t.f4
+ Function Call: tt14f()
+(3 rows)
+
+-- but it will fail at execution
+select f1, f4 from tt14v;
+ f1 | f4
+-----+----
+ foo | 42
+(1 row)
+
+select * from tt14v;
+ERROR: attribute 3 of type record has been dropped
+rollback;
+begin;
+-- this perhaps should be rejected, but it isn't:
+alter table tt14t alter column f4 type integer using f4::integer;
+-- f4 is still in the view ...
+select pg_get_viewdef('tt14v', true);
+ pg_get_viewdef
+--------------------------------
+ SELECT t.f1, +
+ t.f3, +
+ t.f4 +
+ FROM tt14f() t(f1, f3, f4);
+(1 row)
+
+-- but will fail at execution
+select f1, f3 from tt14v;
+ f1 | f3
+-----+-----
+ foo | baz
+(1 row)
+
+select * from tt14v;
+ERROR: attribute 4 of type record has wrong type
+DETAIL: Table has type integer, but query expects text.
+rollback;
+-- check display of whole-row variables in some corner cases
+create type nestedcomposite as (x int8_tbl);
+create view tt15v as select row(i)::nestedcomposite from int8_tbl i;
+select * from tt15v;
+ row
+------------------------------------------
+ ("(123,456)")
+ ("(123,4567890123456789)")
+ ("(4567890123456789,123)")
+ ("(4567890123456789,4567890123456789)")
+ ("(4567890123456789,-4567890123456789)")
+(5 rows)
+
+select pg_get_viewdef('tt15v', true);
+ pg_get_viewdef
+------------------------------------------------------
+ SELECT ROW(i.*::int8_tbl)::nestedcomposite AS "row"+
+ FROM int8_tbl i;
+(1 row)
+
+select row(i.*::int8_tbl)::nestedcomposite from int8_tbl i;
+ row
+------------------------------------------
+ ("(123,456)")
+ ("(123,4567890123456789)")
+ ("(4567890123456789,123)")
+ ("(4567890123456789,4567890123456789)")
+ ("(4567890123456789,-4567890123456789)")
+(5 rows)
+
+create view tt16v as select * from int8_tbl i, lateral(values(i)) ss;
+select * from tt16v;
+ q1 | q2 | column1
+------------------+-------------------+--------------------------------------
+ 123 | 456 | (123,456)
+ 123 | 4567890123456789 | (123,4567890123456789)
+ 4567890123456789 | 123 | (4567890123456789,123)
+ 4567890123456789 | 4567890123456789 | (4567890123456789,4567890123456789)
+ 4567890123456789 | -4567890123456789 | (4567890123456789,-4567890123456789)
+(5 rows)
+
+select pg_get_viewdef('tt16v', true);
+ pg_get_viewdef
+-------------------------------------------
+ SELECT i.q1, +
+ i.q2, +
+ ss.column1 +
+ FROM int8_tbl i, +
+ LATERAL ( VALUES (i.*::int8_tbl)) ss;
+(1 row)
+
+select * from int8_tbl i, lateral(values(i.*::int8_tbl)) ss;
+ q1 | q2 | column1
+------------------+-------------------+--------------------------------------
+ 123 | 456 | (123,456)
+ 123 | 4567890123456789 | (123,4567890123456789)
+ 4567890123456789 | 123 | (4567890123456789,123)
+ 4567890123456789 | 4567890123456789 | (4567890123456789,4567890123456789)
+ 4567890123456789 | -4567890123456789 | (4567890123456789,-4567890123456789)
+(5 rows)
+
+create view tt17v as select * from int8_tbl i where i in (values(i));
+select * from tt17v;
+ q1 | q2
+------------------+-------------------
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+(5 rows)
+
+select pg_get_viewdef('tt17v', true);
+ pg_get_viewdef
+---------------------------------------------
+ SELECT i.q1, +
+ i.q2 +
+ FROM int8_tbl i +
+ WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
+(1 row)
+
+select * from int8_tbl i where i.* in (values(i.*::int8_tbl));
+ q1 | q2
+------------------+-------------------
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+(5 rows)
+
+create table tt15v_log(o tt15v, n tt15v, incr bool);
+create rule updlog as on update to tt15v do also
+ insert into tt15v_log values(old, new, row(old,old) < row(new,new));
+\d+ tt15v
+ View "testviewschm2.tt15v"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+-----------------+-----------+----------+---------+----------+-------------
+ row | nestedcomposite | | | | extended |
+View definition:
+ SELECT ROW(i.*::int8_tbl)::nestedcomposite AS "row"
+ FROM int8_tbl i;
+Rules:
+ updlog AS
+ ON UPDATE TO tt15v DO INSERT INTO tt15v_log (o, n, incr)
+ VALUES (old.*::tt15v, new.*::tt15v, (ROW(old.*::tt15v, old.*::tt15v) < ROW(new.*::tt15v, new.*::tt15v)))
+
+-- check unique-ification of overlength names
+create view tt18v as
+ select * from int8_tbl xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxy
+ union all
+ select * from int8_tbl xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxz;
+NOTICE: identifier "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxy" will be truncated to "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+NOTICE: identifier "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxz" will be truncated to "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+select pg_get_viewdef('tt18v', true);
+ pg_get_viewdef
+-----------------------------------------------------------------------------------
+ SELECT xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.q1, +
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.q2 +
+ FROM int8_tbl xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +
+ UNION ALL +
+ SELECT xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.q1, +
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.q2 +
+ FROM int8_tbl xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
+(1 row)
+
+explain (costs off) select * from tt18v;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------
+ Append
+ -> Seq Scan on int8_tbl xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ -> Seq Scan on int8_tbl xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_1
+(3 rows)
+
+-- check display of ScalarArrayOp with a sub-select
+select 'foo'::text = any(array['abc','def','foo']::text[]);
+ ?column?
+----------
+ t
+(1 row)
+
+select 'foo'::text = any((select array['abc','def','foo']::text[])); -- fail
+ERROR: operator does not exist: text = text[]
+LINE 1: select 'foo'::text = any((select array['abc','def','foo']::t...
+ ^
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+select 'foo'::text = any((select array['abc','def','foo']::text[])::text[]);
+ ?column?
+----------
+ t
+(1 row)
+
+create view tt19v as
+select 'foo'::text = any(array['abc','def','foo']::text[]) c1,
+ 'foo'::text = any((select array['abc','def','foo']::text[])::text[]) c2;
+select pg_get_viewdef('tt19v', true);
+ pg_get_viewdef
+------------------------------------------------------------------------------------------------------------
+ SELECT 'foo'::text = ANY (ARRAY['abc'::text, 'def'::text, 'foo'::text]) AS c1, +
+ 'foo'::text = ANY ((( SELECT ARRAY['abc'::text, 'def'::text, 'foo'::text] AS "array"))::text[]) AS c2;
+(1 row)
+
+-- check display of assorted RTE_FUNCTION expressions
+create view tt20v as
+select * from
+ coalesce(1,2) as c,
+ collation for ('x'::text) col,
+ current_date as d,
+ localtimestamp(3) as t,
+ cast(1+2 as int4) as i4,
+ cast(1+2 as int8) as i8;
+select pg_get_viewdef('tt20v', true);
+ pg_get_viewdef
+---------------------------------------------
+ SELECT c.c, +
+ col.col, +
+ d.d, +
+ t.t, +
+ i4.i4, +
+ i8.i8 +
+ FROM COALESCE(1, 2) c(c), +
+ COLLATION FOR ('x'::text) col(col), +
+ CURRENT_DATE d(d), +
+ LOCALTIMESTAMP(3) t(t), +
+ CAST(1 + 2 AS integer) i4(i4), +
+ CAST((1 + 2)::bigint AS bigint) i8(i8);
+(1 row)
+
+-- reverse-listing of various special function syntaxes required by SQL
+create view tt201v as
+select
+ extract(day from now()) as extr,
+ (now(), '1 day'::interval) overlaps
+ (current_timestamp(2), '1 day'::interval) as o,
+ 'foo' is normalized isn,
+ 'foo' is nfkc normalized isnn,
+ normalize('foo') as n,
+ normalize('foo', nfkd) as nfkd,
+ overlay('foo' placing 'bar' from 2) as ovl,
+ overlay('foo' placing 'bar' from 2 for 3) as ovl2,
+ position('foo' in 'foobar') as p,
+ substring('foo' from 2 for 3) as s,
+ substring('foo' similar 'f' escape '#') as ss,
+ substring('foo' from 'oo') as ssf, -- historically-permitted abuse
+ trim(' ' from ' foo ') as bt,
+ trim(leading ' ' from ' foo ') as lt,
+ trim(trailing ' foo ') as rt,
+ trim(E'\\000'::bytea from E'\\000Tom\\000'::bytea) as btb,
+ trim(leading E'\\000'::bytea from E'\\000Tom\\000'::bytea) as ltb,
+ trim(trailing E'\\000'::bytea from E'\\000Tom\\000'::bytea) as rtb;
+select pg_get_viewdef('tt201v', true);
+ pg_get_viewdef
+-----------------------------------------------------------------------------------------------
+ SELECT EXTRACT(day FROM now()) AS extr, +
+ ((now(), '@ 1 day'::interval) OVERLAPS (CURRENT_TIMESTAMP(2), '@ 1 day'::interval)) AS o,+
+ (('foo'::text) IS NORMALIZED) AS isn, +
+ (('foo'::text) IS NFKC NORMALIZED) AS isnn, +
+ NORMALIZE('foo'::text) AS n, +
+ NORMALIZE('foo'::text, NFKD) AS nfkd, +
+ OVERLAY('foo'::text PLACING 'bar'::text FROM 2) AS ovl, +
+ OVERLAY('foo'::text PLACING 'bar'::text FROM 2 FOR 3) AS ovl2, +
+ POSITION(('foo'::text) IN ('foobar'::text)) AS p, +
+ SUBSTRING('foo'::text FROM 2 FOR 3) AS s, +
+ SUBSTRING('foo'::text SIMILAR 'f'::text ESCAPE '#'::text) AS ss, +
+ "substring"('foo'::text, 'oo'::text) AS ssf, +
+ TRIM(BOTH ' '::text FROM ' foo '::text) AS bt, +
+ TRIM(LEADING ' '::text FROM ' foo '::text) AS lt, +
+ TRIM(TRAILING FROM ' foo '::text) AS rt, +
+ TRIM(BOTH '\x00'::bytea FROM '\x00546f6d00'::bytea) AS btb, +
+ TRIM(LEADING '\x00'::bytea FROM '\x00546f6d00'::bytea) AS ltb, +
+ TRIM(TRAILING '\x00'::bytea FROM '\x00546f6d00'::bytea) AS rtb;
+(1 row)
+
+-- corner cases with empty join conditions
+create view tt21v as
+select * from tt5 natural inner join tt6;
+select pg_get_viewdef('tt21v', true);
+ pg_get_viewdef
+----------------------
+ SELECT tt5.a, +
+ tt5.b, +
+ tt5.cc, +
+ tt6.c, +
+ tt6.d +
+ FROM tt5 +
+ CROSS JOIN tt6;
+(1 row)
+
+create view tt22v as
+select * from tt5 natural left join tt6;
+select pg_get_viewdef('tt22v', true);
+ pg_get_viewdef
+-----------------------------
+ SELECT tt5.a, +
+ tt5.b, +
+ tt5.cc, +
+ tt6.c, +
+ tt6.d +
+ FROM tt5 +
+ LEFT JOIN tt6 ON TRUE;
+(1 row)
+
+-- check handling of views with immediately-renamed columns
+create view tt23v (col_a, col_b) as
+select q1 as other_name1, q2 as other_name2 from int8_tbl
+union
+select 42, 43;
+select pg_get_viewdef('tt23v', true);
+ pg_get_viewdef
+-------------------------------
+ SELECT int8_tbl.q1 AS col_a,+
+ int8_tbl.q2 AS col_b +
+ FROM int8_tbl +
+ UNION +
+ SELECT 42 AS col_a, +
+ 43 AS col_b;
+(1 row)
+
+select pg_get_ruledef(oid, true) from pg_rewrite
+ where ev_class = 'tt23v'::regclass and ev_type = '1';
+ pg_get_ruledef
+-----------------------------------------------------------------
+ CREATE RULE "_RETURN" AS +
+ ON SELECT TO tt23v DO INSTEAD SELECT int8_tbl.q1 AS col_a,+
+ int8_tbl.q2 AS col_b +
+ FROM int8_tbl +
+ UNION +
+ SELECT 42 AS col_a, +
+ 43 AS col_b;
+(1 row)
+
+-- test extraction of FieldSelect field names (get_name_for_var_field)
+create view tt24v as
+with cte as materialized (select r from (values(1,2),(3,4)) r)
+select (r).column2 as col_a, (rr).column2 as col_b from
+ cte join (select rr from (values(1,7),(3,8)) rr limit 2) ss
+ on (r).column1 = (rr).column1;
+select pg_get_viewdef('tt24v', true);
+ pg_get_viewdef
+------------------------------------------------------------
+ WITH cte AS MATERIALIZED ( +
+ SELECT r.*::record AS r +
+ FROM ( VALUES (1,2), (3,4)) r +
+ ) +
+ SELECT (cte.r).column2 AS col_a, +
+ (ss.rr).column2 AS col_b +
+ FROM cte +
+ JOIN ( SELECT rr.*::record AS rr +
+ FROM ( VALUES (1,7), (3,8)) rr +
+ LIMIT 2) ss ON (cte.r).column1 = (ss.rr).column1;
+(1 row)
+
+create view tt25v as
+with cte as materialized (select pg_get_keywords() k)
+select (k).word from cte;
+select pg_get_viewdef('tt25v', true);
+ pg_get_viewdef
+----------------------------------------
+ WITH cte AS MATERIALIZED ( +
+ SELECT pg_get_keywords() AS k+
+ ) +
+ SELECT (cte.k).word AS word +
+ FROM cte;
+(1 row)
+
+-- also check cases seen only in EXPLAIN
+explain (verbose, costs off)
+select * from tt24v;
+ QUERY PLAN
+------------------------------------------------------------------------------------------
+ Hash Join
+ Output: (cte.r).column2, ((ROW("*VALUES*".column1, "*VALUES*".column2))).column2
+ Hash Cond: (((ROW("*VALUES*".column1, "*VALUES*".column2))).column1 = (cte.r).column1)
+ CTE cte
+ -> Values Scan on "*VALUES*_1"
+ Output: ROW("*VALUES*_1".column1, "*VALUES*_1".column2)
+ -> Limit
+ Output: (ROW("*VALUES*".column1, "*VALUES*".column2))
+ -> Values Scan on "*VALUES*"
+ Output: ROW("*VALUES*".column1, "*VALUES*".column2)
+ -> Hash
+ Output: cte.r
+ -> CTE Scan on cte
+ Output: cte.r
+(14 rows)
+
+explain (verbose, costs off)
+select (r).column2 from (select r from (values(1,2),(3,4)) r limit 1) ss;
+ QUERY PLAN
+-------------------------------------------------------------------
+ Subquery Scan on ss
+ Output: (ss.r).column2
+ -> Limit
+ Output: (ROW("*VALUES*".column1, "*VALUES*".column2))
+ -> Values Scan on "*VALUES*"
+ Output: ROW("*VALUES*".column1, "*VALUES*".column2)
+(6 rows)
+
+-- test pretty-print parenthesization rules, and SubLink deparsing
+create view tt26v as
+select x + y + z as c1,
+ (x * y) + z as c2,
+ x + (y * z) as c3,
+ (x + y) * z as c4,
+ x * (y + z) as c5,
+ x + (y + z) as c6,
+ x + (y # z) as c7,
+ (x > y) AND (y > z OR x > z) as c8,
+ (x > y) OR (y > z AND NOT (x > z)) as c9,
+ (x,y) <> ALL (values(1,2),(3,4)) as c10,
+ (x,y) <= ANY (values(1,2),(3,4)) as c11
+from (values(1,2,3)) v(x,y,z);
+select pg_get_viewdef('tt26v', true);
+ pg_get_viewdef
+--------------------------------------------------------
+ SELECT v.x + v.y + v.z AS c1, +
+ v.x * v.y + v.z AS c2, +
+ v.x + v.y * v.z AS c3, +
+ (v.x + v.y) * v.z AS c4, +
+ v.x * (v.y + v.z) AS c5, +
+ v.x + (v.y + v.z) AS c6, +
+ v.x + (v.y # v.z) AS c7, +
+ v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8, +
+ v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9, +
+ ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+ ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+ FROM ( VALUES (1,2,3)) v(x, y, z);
+(1 row)
+
+-- clean up all the random objects we made above
+DROP SCHEMA temp_view_test CASCADE;
+NOTICE: drop cascades to 27 other objects
+DETAIL: drop cascades to table temp_view_test.base_table
+drop cascades to view v2_temp
+drop cascades to view v4_temp
+drop cascades to view v6_temp
+drop cascades to view v7_temp
+drop cascades to view v10_temp
+drop cascades to view v8_temp
+drop cascades to view v9_temp
+drop cascades to view v11_temp
+drop cascades to view v12_temp
+drop cascades to table temp_view_test.base_table2
+drop cascades to view v5_temp
+drop cascades to view temp_view_test.v1
+drop cascades to view temp_view_test.v2
+drop cascades to view temp_view_test.v3
+drop cascades to view temp_view_test.v4
+drop cascades to view temp_view_test.v5
+drop cascades to view temp_view_test.v6
+drop cascades to view temp_view_test.v7
+drop cascades to view temp_view_test.v8
+drop cascades to sequence temp_view_test.seq1
+drop cascades to view temp_view_test.v9
+drop cascades to table temp_view_test.tx1
+drop cascades to view aliased_view_1
+drop cascades to view aliased_view_2
+drop cascades to view aliased_view_3
+drop cascades to view aliased_view_4
+DROP SCHEMA testviewschm2 CASCADE;
+NOTICE: drop cascades to 74 other objects
+DETAIL: drop cascades to table t1
+drop cascades to view temporal1
+drop cascades to view temporal2
+drop cascades to view temporal3
+drop cascades to view temporal4
+drop cascades to table t2
+drop cascades to view nontemp1
+drop cascades to view nontemp2
+drop cascades to view nontemp3
+drop cascades to view nontemp4
+drop cascades to table tbl1
+drop cascades to table tbl2
+drop cascades to table tbl3
+drop cascades to table tbl4
+drop cascades to view mytempview
+drop cascades to view pubview
+drop cascades to view mysecview1
+drop cascades to view mysecview2
+drop cascades to view mysecview3
+drop cascades to view mysecview4
+drop cascades to view unspecified_types
+drop cascades to table tt1
+drop cascades to table tx1
+drop cascades to view view_of_joins
+drop cascades to table tbl1a
+drop cascades to view view_of_joins_2a
+drop cascades to view view_of_joins_2b
+drop cascades to view view_of_joins_2c
+drop cascades to view view_of_joins_2d
+drop cascades to table tt2
+drop cascades to table tt3
+drop cascades to table tt4
+drop cascades to view v1
+drop cascades to view v1a
+drop cascades to view v2
+drop cascades to view v2a
+drop cascades to view v3
+drop cascades to table tt5
+drop cascades to table tt6
+drop cascades to view vv1
+drop cascades to view v4
+drop cascades to table tt7
+drop cascades to table tt8
+drop cascades to view vv2
+drop cascades to view vv3
+drop cascades to view vv4
+drop cascades to table tt7a
+drop cascades to table tt8a
+drop cascades to view vv2a
+drop cascades to table tt9
+drop cascades to table tt10
+drop cascades to view vv5
+drop cascades to table tt11
+drop cascades to table tt12
+drop cascades to table tt13
+drop cascades to view vv6
+drop cascades to table tt14t
+drop cascades to function tt14f()
+drop cascades to view tt14v
+drop cascades to type nestedcomposite
+drop cascades to view tt15v
+drop cascades to view tt16v
+drop cascades to view tt17v
+drop cascades to table tt15v_log
+drop cascades to view tt18v
+drop cascades to view tt19v
+drop cascades to view tt20v
+drop cascades to view tt201v
+drop cascades to view tt21v
+drop cascades to view tt22v
+drop cascades to view tt23v
+drop cascades to view tt24v
+drop cascades to view tt25v
+drop cascades to view tt26v
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
new file mode 100644
index 0000000..c8b0566
--- /dev/null
+++ b/src/test/regress/expected/date.out
@@ -0,0 +1,1497 @@
+--
+-- DATE
+--
+CREATE TABLE DATE_TBL (f1 date);
+INSERT INTO DATE_TBL VALUES ('1957-04-09');
+INSERT INTO DATE_TBL VALUES ('1957-06-13');
+INSERT INTO DATE_TBL VALUES ('1996-02-28');
+INSERT INTO DATE_TBL VALUES ('1996-02-29');
+INSERT INTO DATE_TBL VALUES ('1996-03-01');
+INSERT INTO DATE_TBL VALUES ('1996-03-02');
+INSERT INTO DATE_TBL VALUES ('1997-02-28');
+INSERT INTO DATE_TBL VALUES ('1997-02-29');
+ERROR: date/time field value out of range: "1997-02-29"
+LINE 1: INSERT INTO DATE_TBL VALUES ('1997-02-29');
+ ^
+INSERT INTO DATE_TBL VALUES ('1997-03-01');
+INSERT INTO DATE_TBL VALUES ('1997-03-02');
+INSERT INTO DATE_TBL VALUES ('2000-04-01');
+INSERT INTO DATE_TBL VALUES ('2000-04-02');
+INSERT INTO DATE_TBL VALUES ('2000-04-03');
+INSERT INTO DATE_TBL VALUES ('2038-04-08');
+INSERT INTO DATE_TBL VALUES ('2039-04-09');
+INSERT INTO DATE_TBL VALUES ('2040-04-10');
+INSERT INTO DATE_TBL VALUES ('2040-04-10 BC');
+SELECT f1 FROM DATE_TBL;
+ f1
+---------------
+ 04-09-1957
+ 06-13-1957
+ 02-28-1996
+ 02-29-1996
+ 03-01-1996
+ 03-02-1996
+ 02-28-1997
+ 03-01-1997
+ 03-02-1997
+ 04-01-2000
+ 04-02-2000
+ 04-03-2000
+ 04-08-2038
+ 04-09-2039
+ 04-10-2040
+ 04-10-2040 BC
+(16 rows)
+
+SELECT f1 FROM DATE_TBL WHERE f1 < '2000-01-01';
+ f1
+---------------
+ 04-09-1957
+ 06-13-1957
+ 02-28-1996
+ 02-29-1996
+ 03-01-1996
+ 03-02-1996
+ 02-28-1997
+ 03-01-1997
+ 03-02-1997
+ 04-10-2040 BC
+(10 rows)
+
+SELECT f1 FROM DATE_TBL
+ WHERE f1 BETWEEN '2000-01-01' AND '2001-01-01';
+ f1
+------------
+ 04-01-2000
+ 04-02-2000
+ 04-03-2000
+(3 rows)
+
+--
+-- Check all the documented input formats
+--
+SET datestyle TO iso; -- display results in ISO
+SET datestyle TO ymd;
+SELECT date 'January 8, 1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999-01-08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999-01-18';
+ date
+------------
+ 1999-01-18
+(1 row)
+
+SELECT date '1/8/1999';
+ERROR: date/time field value out of range: "1/8/1999"
+LINE 1: SELECT date '1/8/1999';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '1/18/1999';
+ERROR: date/time field value out of range: "1/18/1999"
+LINE 1: SELECT date '1/18/1999';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '18/1/1999';
+ERROR: date/time field value out of range: "18/1/1999"
+LINE 1: SELECT date '18/1/1999';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '01/02/03';
+ date
+------------
+ 2001-02-03
+(1 row)
+
+SELECT date '19990108';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '990108';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999.008';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'J2451187';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'January 8, 99 BC';
+ERROR: date/time field value out of range: "January 8, 99 BC"
+LINE 1: SELECT date 'January 8, 99 BC';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '99-Jan-08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999-Jan-08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08-Jan-99';
+ERROR: date/time field value out of range: "08-Jan-99"
+LINE 1: SELECT date '08-Jan-99';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '08-Jan-1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'Jan-08-99';
+ERROR: date/time field value out of range: "Jan-08-99"
+LINE 1: SELECT date 'Jan-08-99';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date 'Jan-08-1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '99-08-Jan';
+ERROR: invalid input syntax for type date: "99-08-Jan"
+LINE 1: SELECT date '99-08-Jan';
+ ^
+SELECT date '1999-08-Jan';
+ERROR: invalid input syntax for type date: "1999-08-Jan"
+LINE 1: SELECT date '1999-08-Jan';
+ ^
+SELECT date '99 Jan 08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999 Jan 08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08 Jan 99';
+ERROR: date/time field value out of range: "08 Jan 99"
+LINE 1: SELECT date '08 Jan 99';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '08 Jan 1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'Jan 08 99';
+ERROR: date/time field value out of range: "Jan 08 99"
+LINE 1: SELECT date 'Jan 08 99';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date 'Jan 08 1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '99 08 Jan';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999 08 Jan';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '99-01-08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999-01-08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08-01-99';
+ERROR: date/time field value out of range: "08-01-99"
+LINE 1: SELECT date '08-01-99';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '08-01-1999';
+ERROR: date/time field value out of range: "08-01-1999"
+LINE 1: SELECT date '08-01-1999';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '01-08-99';
+ERROR: date/time field value out of range: "01-08-99"
+LINE 1: SELECT date '01-08-99';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '01-08-1999';
+ERROR: date/time field value out of range: "01-08-1999"
+LINE 1: SELECT date '01-08-1999';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '99-08-01';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '1999-08-01';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '99 01 08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999 01 08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08 01 99';
+ERROR: date/time field value out of range: "08 01 99"
+LINE 1: SELECT date '08 01 99';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '08 01 1999';
+ERROR: date/time field value out of range: "08 01 1999"
+LINE 1: SELECT date '08 01 1999';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '01 08 99';
+ERROR: date/time field value out of range: "01 08 99"
+LINE 1: SELECT date '01 08 99';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '01 08 1999';
+ERROR: date/time field value out of range: "01 08 1999"
+LINE 1: SELECT date '01 08 1999';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '99 08 01';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '1999 08 01';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SET datestyle TO dmy;
+SELECT date 'January 8, 1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999-01-08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999-01-18';
+ date
+------------
+ 1999-01-18
+(1 row)
+
+SELECT date '1/8/1999';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '1/18/1999';
+ERROR: date/time field value out of range: "1/18/1999"
+LINE 1: SELECT date '1/18/1999';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '18/1/1999';
+ date
+------------
+ 1999-01-18
+(1 row)
+
+SELECT date '01/02/03';
+ date
+------------
+ 2003-02-01
+(1 row)
+
+SELECT date '19990108';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '990108';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999.008';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'J2451187';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'January 8, 99 BC';
+ date
+---------------
+ 0099-01-08 BC
+(1 row)
+
+SELECT date '99-Jan-08';
+ERROR: date/time field value out of range: "99-Jan-08"
+LINE 1: SELECT date '99-Jan-08';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '1999-Jan-08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08-Jan-99';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08-Jan-1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'Jan-08-99';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'Jan-08-1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '99-08-Jan';
+ERROR: invalid input syntax for type date: "99-08-Jan"
+LINE 1: SELECT date '99-08-Jan';
+ ^
+SELECT date '1999-08-Jan';
+ERROR: invalid input syntax for type date: "1999-08-Jan"
+LINE 1: SELECT date '1999-08-Jan';
+ ^
+SELECT date '99 Jan 08';
+ERROR: date/time field value out of range: "99 Jan 08"
+LINE 1: SELECT date '99 Jan 08';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '1999 Jan 08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08 Jan 99';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08 Jan 1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'Jan 08 99';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'Jan 08 1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '99 08 Jan';
+ERROR: invalid input syntax for type date: "99 08 Jan"
+LINE 1: SELECT date '99 08 Jan';
+ ^
+SELECT date '1999 08 Jan';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '99-01-08';
+ERROR: date/time field value out of range: "99-01-08"
+LINE 1: SELECT date '99-01-08';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '1999-01-08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08-01-99';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08-01-1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '01-08-99';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '01-08-1999';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '99-08-01';
+ERROR: date/time field value out of range: "99-08-01"
+LINE 1: SELECT date '99-08-01';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '1999-08-01';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '99 01 08';
+ERROR: date/time field value out of range: "99 01 08"
+LINE 1: SELECT date '99 01 08';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '1999 01 08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08 01 99';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08 01 1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '01 08 99';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '01 08 1999';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '99 08 01';
+ERROR: date/time field value out of range: "99 08 01"
+LINE 1: SELECT date '99 08 01';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '1999 08 01';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SET datestyle TO mdy;
+SELECT date 'January 8, 1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999-01-08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999-01-18';
+ date
+------------
+ 1999-01-18
+(1 row)
+
+SELECT date '1/8/1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1/18/1999';
+ date
+------------
+ 1999-01-18
+(1 row)
+
+SELECT date '18/1/1999';
+ERROR: date/time field value out of range: "18/1/1999"
+LINE 1: SELECT date '18/1/1999';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '01/02/03';
+ date
+------------
+ 2003-01-02
+(1 row)
+
+SELECT date '19990108';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '990108';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '1999.008';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'J2451187';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'January 8, 99 BC';
+ date
+---------------
+ 0099-01-08 BC
+(1 row)
+
+SELECT date '99-Jan-08';
+ERROR: date/time field value out of range: "99-Jan-08"
+LINE 1: SELECT date '99-Jan-08';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '1999-Jan-08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08-Jan-99';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08-Jan-1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'Jan-08-99';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'Jan-08-1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '99-08-Jan';
+ERROR: invalid input syntax for type date: "99-08-Jan"
+LINE 1: SELECT date '99-08-Jan';
+ ^
+SELECT date '1999-08-Jan';
+ERROR: invalid input syntax for type date: "1999-08-Jan"
+LINE 1: SELECT date '1999-08-Jan';
+ ^
+SELECT date '99 Jan 08';
+ERROR: invalid input syntax for type date: "99 Jan 08"
+LINE 1: SELECT date '99 Jan 08';
+ ^
+SELECT date '1999 Jan 08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08 Jan 99';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08 Jan 1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'Jan 08 99';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date 'Jan 08 1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '99 08 Jan';
+ERROR: invalid input syntax for type date: "99 08 Jan"
+LINE 1: SELECT date '99 08 Jan';
+ ^
+SELECT date '1999 08 Jan';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '99-01-08';
+ERROR: date/time field value out of range: "99-01-08"
+LINE 1: SELECT date '99-01-08';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '1999-01-08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08-01-99';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '08-01-1999';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '01-08-99';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '01-08-1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '99-08-01';
+ERROR: date/time field value out of range: "99-08-01"
+LINE 1: SELECT date '99-08-01';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '1999-08-01';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '99 01 08';
+ERROR: date/time field value out of range: "99 01 08"
+LINE 1: SELECT date '99 01 08';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '1999 01 08';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '08 01 99';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '08 01 1999';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+SELECT date '01 08 99';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '01 08 1999';
+ date
+------------
+ 1999-01-08
+(1 row)
+
+SELECT date '99 08 01';
+ERROR: date/time field value out of range: "99 08 01"
+LINE 1: SELECT date '99 08 01';
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+SELECT date '1999 08 01';
+ date
+------------
+ 1999-08-01
+(1 row)
+
+-- Check upper and lower limits of date range
+SELECT date '4714-11-24 BC';
+ date
+---------------
+ 4714-11-24 BC
+(1 row)
+
+SELECT date '4714-11-23 BC'; -- out of range
+ERROR: date out of range: "4714-11-23 BC"
+LINE 1: SELECT date '4714-11-23 BC';
+ ^
+SELECT date '5874897-12-31';
+ date
+---------------
+ 5874897-12-31
+(1 row)
+
+SELECT date '5874898-01-01'; -- out of range
+ERROR: date out of range: "5874898-01-01"
+LINE 1: SELECT date '5874898-01-01';
+ ^
+RESET datestyle;
+--
+-- Simple math
+-- Leave most of it for the horology tests
+--
+SELECT f1 - date '2000-01-01' AS "Days From 2K" FROM DATE_TBL;
+ Days From 2K
+--------------
+ -15607
+ -15542
+ -1403
+ -1402
+ -1401
+ -1400
+ -1037
+ -1036
+ -1035
+ 91
+ 92
+ 93
+ 13977
+ 14343
+ 14710
+ -1475115
+(16 rows)
+
+SELECT f1 - date 'epoch' AS "Days From Epoch" FROM DATE_TBL;
+ Days From Epoch
+-----------------
+ -4650
+ -4585
+ 9554
+ 9555
+ 9556
+ 9557
+ 9920
+ 9921
+ 9922
+ 11048
+ 11049
+ 11050
+ 24934
+ 25300
+ 25667
+ -1464158
+(16 rows)
+
+SELECT date 'yesterday' - date 'today' AS "One day";
+ One day
+---------
+ -1
+(1 row)
+
+SELECT date 'today' - date 'tomorrow' AS "One day";
+ One day
+---------
+ -1
+(1 row)
+
+SELECT date 'yesterday' - date 'tomorrow' AS "Two days";
+ Two days
+----------
+ -2
+(1 row)
+
+SELECT date 'tomorrow' - date 'today' AS "One day";
+ One day
+---------
+ 1
+(1 row)
+
+SELECT date 'today' - date 'yesterday' AS "One day";
+ One day
+---------
+ 1
+(1 row)
+
+SELECT date 'tomorrow' - date 'yesterday' AS "Two days";
+ Two days
+----------
+ 2
+(1 row)
+
+--
+-- test extract!
+--
+SELECT f1 as "date",
+ date_part('year', f1) AS year,
+ date_part('month', f1) AS month,
+ date_part('day', f1) AS day,
+ date_part('quarter', f1) AS quarter,
+ date_part('decade', f1) AS decade,
+ date_part('century', f1) AS century,
+ date_part('millennium', f1) AS millennium,
+ date_part('isoyear', f1) AS isoyear,
+ date_part('week', f1) AS week,
+ date_part('dow', f1) AS dow,
+ date_part('isodow', f1) AS isodow,
+ date_part('doy', f1) AS doy,
+ date_part('julian', f1) AS julian,
+ date_part('epoch', f1) AS epoch
+ FROM date_tbl;
+ date | year | month | day | quarter | decade | century | millennium | isoyear | week | dow | isodow | doy | julian | epoch
+---------------+-------+-------+-----+---------+--------+---------+------------+---------+------+-----+--------+-----+---------+---------------
+ 04-09-1957 | 1957 | 4 | 9 | 2 | 195 | 20 | 2 | 1957 | 15 | 2 | 2 | 99 | 2435938 | -401760000
+ 06-13-1957 | 1957 | 6 | 13 | 2 | 195 | 20 | 2 | 1957 | 24 | 4 | 4 | 164 | 2436003 | -396144000
+ 02-28-1996 | 1996 | 2 | 28 | 1 | 199 | 20 | 2 | 1996 | 9 | 3 | 3 | 59 | 2450142 | 825465600
+ 02-29-1996 | 1996 | 2 | 29 | 1 | 199 | 20 | 2 | 1996 | 9 | 4 | 4 | 60 | 2450143 | 825552000
+ 03-01-1996 | 1996 | 3 | 1 | 1 | 199 | 20 | 2 | 1996 | 9 | 5 | 5 | 61 | 2450144 | 825638400
+ 03-02-1996 | 1996 | 3 | 2 | 1 | 199 | 20 | 2 | 1996 | 9 | 6 | 6 | 62 | 2450145 | 825724800
+ 02-28-1997 | 1997 | 2 | 28 | 1 | 199 | 20 | 2 | 1997 | 9 | 5 | 5 | 59 | 2450508 | 857088000
+ 03-01-1997 | 1997 | 3 | 1 | 1 | 199 | 20 | 2 | 1997 | 9 | 6 | 6 | 60 | 2450509 | 857174400
+ 03-02-1997 | 1997 | 3 | 2 | 1 | 199 | 20 | 2 | 1997 | 9 | 0 | 7 | 61 | 2450510 | 857260800
+ 04-01-2000 | 2000 | 4 | 1 | 2 | 200 | 20 | 2 | 2000 | 13 | 6 | 6 | 92 | 2451636 | 954547200
+ 04-02-2000 | 2000 | 4 | 2 | 2 | 200 | 20 | 2 | 2000 | 13 | 0 | 7 | 93 | 2451637 | 954633600
+ 04-03-2000 | 2000 | 4 | 3 | 2 | 200 | 20 | 2 | 2000 | 14 | 1 | 1 | 94 | 2451638 | 954720000
+ 04-08-2038 | 2038 | 4 | 8 | 2 | 203 | 21 | 3 | 2038 | 14 | 4 | 4 | 98 | 2465522 | 2154297600
+ 04-09-2039 | 2039 | 4 | 9 | 2 | 203 | 21 | 3 | 2039 | 14 | 6 | 6 | 99 | 2465888 | 2185920000
+ 04-10-2040 | 2040 | 4 | 10 | 2 | 204 | 21 | 3 | 2040 | 15 | 2 | 2 | 101 | 2466255 | 2217628800
+ 04-10-2040 BC | -2040 | 4 | 10 | 2 | -204 | -21 | -3 | -2040 | 15 | 1 | 1 | 100 | 976430 | -126503251200
+(16 rows)
+
+--
+-- epoch
+--
+SELECT EXTRACT(EPOCH FROM DATE '1970-01-01'); -- 0
+ extract
+---------
+ 0
+(1 row)
+
+--
+-- century
+--
+SELECT EXTRACT(CENTURY FROM DATE '0101-12-31 BC'); -- -2
+ extract
+---------
+ -2
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM DATE '0100-12-31 BC'); -- -1
+ extract
+---------
+ -1
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM DATE '0001-12-31 BC'); -- -1
+ extract
+---------
+ -1
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM DATE '0001-01-01'); -- 1
+ extract
+---------
+ 1
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM DATE '0001-01-01 AD'); -- 1
+ extract
+---------
+ 1
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM DATE '1900-12-31'); -- 19
+ extract
+---------
+ 19
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM DATE '1901-01-01'); -- 20
+ extract
+---------
+ 20
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM DATE '2000-12-31'); -- 20
+ extract
+---------
+ 20
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM DATE '2001-01-01'); -- 21
+ extract
+---------
+ 21
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM CURRENT_DATE)>=21 AS True; -- true
+ true
+------
+ t
+(1 row)
+
+--
+-- millennium
+--
+SELECT EXTRACT(MILLENNIUM FROM DATE '0001-12-31 BC'); -- -1
+ extract
+---------
+ -1
+(1 row)
+
+SELECT EXTRACT(MILLENNIUM FROM DATE '0001-01-01 AD'); -- 1
+ extract
+---------
+ 1
+(1 row)
+
+SELECT EXTRACT(MILLENNIUM FROM DATE '1000-12-31'); -- 1
+ extract
+---------
+ 1
+(1 row)
+
+SELECT EXTRACT(MILLENNIUM FROM DATE '1001-01-01'); -- 2
+ extract
+---------
+ 2
+(1 row)
+
+SELECT EXTRACT(MILLENNIUM FROM DATE '2000-12-31'); -- 2
+ extract
+---------
+ 2
+(1 row)
+
+SELECT EXTRACT(MILLENNIUM FROM DATE '2001-01-01'); -- 3
+ extract
+---------
+ 3
+(1 row)
+
+-- next test to be fixed on the turn of the next millennium;-)
+SELECT EXTRACT(MILLENNIUM FROM CURRENT_DATE); -- 3
+ extract
+---------
+ 3
+(1 row)
+
+--
+-- decade
+--
+SELECT EXTRACT(DECADE FROM DATE '1994-12-25'); -- 199
+ extract
+---------
+ 199
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '0010-01-01'); -- 1
+ extract
+---------
+ 1
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '0009-12-31'); -- 0
+ extract
+---------
+ 0
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '0001-01-01 BC'); -- 0
+ extract
+---------
+ 0
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '0002-12-31 BC'); -- -1
+ extract
+---------
+ -1
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '0011-01-01 BC'); -- -1
+ extract
+---------
+ -1
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '0012-12-31 BC'); -- -2
+ extract
+---------
+ -2
+(1 row)
+
+--
+-- all possible fields
+--
+SELECT EXTRACT(MICROSECONDS FROM DATE '2020-08-11');
+ERROR: date units "microseconds" not supported
+SELECT EXTRACT(MILLISECONDS FROM DATE '2020-08-11');
+ERROR: date units "milliseconds" not supported
+SELECT EXTRACT(SECOND FROM DATE '2020-08-11');
+ERROR: date units "second" not supported
+SELECT EXTRACT(MINUTE FROM DATE '2020-08-11');
+ERROR: date units "minute" not supported
+SELECT EXTRACT(HOUR FROM DATE '2020-08-11');
+ERROR: date units "hour" not supported
+SELECT EXTRACT(DAY FROM DATE '2020-08-11');
+ extract
+---------
+ 11
+(1 row)
+
+SELECT EXTRACT(MONTH FROM DATE '2020-08-11');
+ extract
+---------
+ 8
+(1 row)
+
+SELECT EXTRACT(YEAR FROM DATE '2020-08-11');
+ extract
+---------
+ 2020
+(1 row)
+
+SELECT EXTRACT(YEAR FROM DATE '2020-08-11 BC');
+ extract
+---------
+ -2020
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '2020-08-11');
+ extract
+---------
+ 202
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM DATE '2020-08-11');
+ extract
+---------
+ 21
+(1 row)
+
+SELECT EXTRACT(MILLENNIUM FROM DATE '2020-08-11');
+ extract
+---------
+ 3
+(1 row)
+
+SELECT EXTRACT(ISOYEAR FROM DATE '2020-08-11');
+ extract
+---------
+ 2020
+(1 row)
+
+SELECT EXTRACT(ISOYEAR FROM DATE '2020-08-11 BC');
+ extract
+---------
+ -2020
+(1 row)
+
+SELECT EXTRACT(QUARTER FROM DATE '2020-08-11');
+ extract
+---------
+ 3
+(1 row)
+
+SELECT EXTRACT(WEEK FROM DATE '2020-08-11');
+ extract
+---------
+ 33
+(1 row)
+
+SELECT EXTRACT(DOW FROM DATE '2020-08-11');
+ extract
+---------
+ 2
+(1 row)
+
+SELECT EXTRACT(DOW FROM DATE '2020-08-16');
+ extract
+---------
+ 0
+(1 row)
+
+SELECT EXTRACT(ISODOW FROM DATE '2020-08-11');
+ extract
+---------
+ 2
+(1 row)
+
+SELECT EXTRACT(ISODOW FROM DATE '2020-08-16');
+ extract
+---------
+ 7
+(1 row)
+
+SELECT EXTRACT(DOY FROM DATE '2020-08-11');
+ extract
+---------
+ 224
+(1 row)
+
+SELECT EXTRACT(TIMEZONE FROM DATE '2020-08-11');
+ERROR: date units "timezone" not supported
+SELECT EXTRACT(TIMEZONE_M FROM DATE '2020-08-11');
+ERROR: date units "timezone_m" not supported
+SELECT EXTRACT(TIMEZONE_H FROM DATE '2020-08-11');
+ERROR: date units "timezone_h" not supported
+SELECT EXTRACT(EPOCH FROM DATE '2020-08-11');
+ extract
+------------
+ 1597104000
+(1 row)
+
+SELECT EXTRACT(JULIAN FROM DATE '2020-08-11');
+ extract
+---------
+ 2459073
+(1 row)
+
+--
+-- test trunc function!
+--
+SELECT DATE_TRUNC('MILLENNIUM', TIMESTAMP '1970-03-20 04:30:00.00000'); -- 1001
+ date_trunc
+--------------------------
+ Thu Jan 01 00:00:00 1001
+(1 row)
+
+SELECT DATE_TRUNC('MILLENNIUM', DATE '1970-03-20'); -- 1001-01-01
+ date_trunc
+------------------------------
+ Thu Jan 01 00:00:00 1001 PST
+(1 row)
+
+SELECT DATE_TRUNC('CENTURY', TIMESTAMP '1970-03-20 04:30:00.00000'); -- 1901
+ date_trunc
+--------------------------
+ Tue Jan 01 00:00:00 1901
+(1 row)
+
+SELECT DATE_TRUNC('CENTURY', DATE '1970-03-20'); -- 1901
+ date_trunc
+------------------------------
+ Tue Jan 01 00:00:00 1901 PST
+(1 row)
+
+SELECT DATE_TRUNC('CENTURY', DATE '2004-08-10'); -- 2001-01-01
+ date_trunc
+------------------------------
+ Mon Jan 01 00:00:00 2001 PST
+(1 row)
+
+SELECT DATE_TRUNC('CENTURY', DATE '0002-02-04'); -- 0001-01-01
+ date_trunc
+------------------------------
+ Mon Jan 01 00:00:00 0001 PST
+(1 row)
+
+SELECT DATE_TRUNC('CENTURY', DATE '0055-08-10 BC'); -- 0100-01-01 BC
+ date_trunc
+---------------------------------
+ Tue Jan 01 00:00:00 0100 PST BC
+(1 row)
+
+SELECT DATE_TRUNC('DECADE', DATE '1993-12-25'); -- 1990-01-01
+ date_trunc
+------------------------------
+ Mon Jan 01 00:00:00 1990 PST
+(1 row)
+
+SELECT DATE_TRUNC('DECADE', DATE '0004-12-25'); -- 0001-01-01 BC
+ date_trunc
+---------------------------------
+ Sat Jan 01 00:00:00 0001 PST BC
+(1 row)
+
+SELECT DATE_TRUNC('DECADE', DATE '0002-12-31 BC'); -- 0011-01-01 BC
+ date_trunc
+---------------------------------
+ Mon Jan 01 00:00:00 0011 PST BC
+(1 row)
+
+--
+-- test infinity
+--
+select 'infinity'::date, '-infinity'::date;
+ date | date
+----------+-----------
+ infinity | -infinity
+(1 row)
+
+select 'infinity'::date > 'today'::date as t;
+ t
+---
+ t
+(1 row)
+
+select '-infinity'::date < 'today'::date as t;
+ t
+---
+ t
+(1 row)
+
+select isfinite('infinity'::date), isfinite('-infinity'::date), isfinite('today'::date);
+ isfinite | isfinite | isfinite
+----------+----------+----------
+ f | f | t
+(1 row)
+
+--
+-- oscillating fields from non-finite date:
+--
+SELECT EXTRACT(DAY FROM DATE 'infinity'); -- NULL
+ extract
+---------
+
+(1 row)
+
+SELECT EXTRACT(DAY FROM DATE '-infinity'); -- NULL
+ extract
+---------
+
+(1 row)
+
+-- all supported fields
+SELECT EXTRACT(DAY FROM DATE 'infinity'); -- NULL
+ extract
+---------
+
+(1 row)
+
+SELECT EXTRACT(MONTH FROM DATE 'infinity'); -- NULL
+ extract
+---------
+
+(1 row)
+
+SELECT EXTRACT(QUARTER FROM DATE 'infinity'); -- NULL
+ extract
+---------
+
+(1 row)
+
+SELECT EXTRACT(WEEK FROM DATE 'infinity'); -- NULL
+ extract
+---------
+
+(1 row)
+
+SELECT EXTRACT(DOW FROM DATE 'infinity'); -- NULL
+ extract
+---------
+
+(1 row)
+
+SELECT EXTRACT(ISODOW FROM DATE 'infinity'); -- NULL
+ extract
+---------
+
+(1 row)
+
+SELECT EXTRACT(DOY FROM DATE 'infinity'); -- NULL
+ extract
+---------
+
+(1 row)
+
+--
+-- monotonic fields from non-finite date:
+--
+SELECT EXTRACT(EPOCH FROM DATE 'infinity'); -- Infinity
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT EXTRACT(EPOCH FROM DATE '-infinity'); -- -Infinity
+ extract
+-----------
+ -Infinity
+(1 row)
+
+-- all supported fields
+SELECT EXTRACT(YEAR FROM DATE 'infinity'); -- Infinity
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE 'infinity'); -- Infinity
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM DATE 'infinity'); -- Infinity
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT EXTRACT(MILLENNIUM FROM DATE 'infinity'); -- Infinity
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT EXTRACT(JULIAN FROM DATE 'infinity'); -- Infinity
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT EXTRACT(ISOYEAR FROM DATE 'infinity'); -- Infinity
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT EXTRACT(EPOCH FROM DATE 'infinity'); -- Infinity
+ extract
+----------
+ Infinity
+(1 row)
+
+--
+-- wrong fields from non-finite date:
+--
+SELECT EXTRACT(MICROSEC FROM DATE 'infinity'); -- error
+ERROR: date units "microsec" not recognized
+-- test constructors
+select make_date(2013, 7, 15);
+ make_date
+------------
+ 07-15-2013
+(1 row)
+
+select make_date(-44, 3, 15);
+ make_date
+---------------
+ 03-15-0044 BC
+(1 row)
+
+select make_time(8, 20, 0.0);
+ make_time
+-----------
+ 08:20:00
+(1 row)
+
+-- should fail
+select make_date(0, 7, 15);
+ERROR: date field value out of range: 0-07-15
+select make_date(2013, 2, 30);
+ERROR: date field value out of range: 2013-02-30
+select make_date(2013, 13, 1);
+ERROR: date field value out of range: 2013-13-01
+select make_date(2013, 11, -1);
+ERROR: date field value out of range: 2013-11--1
+select make_time(10, 55, 100.1);
+ERROR: time field value out of range: 10:55:100.1
+select make_time(24, 0, 2.1);
+ERROR: time field value out of range: 24:00:2.1
diff --git a/src/test/regress/expected/dbsize.out b/src/test/regress/expected/dbsize.out
new file mode 100644
index 0000000..29804ae
--- /dev/null
+++ b/src/test/regress/expected/dbsize.out
@@ -0,0 +1,188 @@
+SELECT size, pg_size_pretty(size), pg_size_pretty(-1 * size) FROM
+ (VALUES (10::bigint), (1000::bigint), (1000000::bigint),
+ (1000000000::bigint), (1000000000000::bigint),
+ (1000000000000000::bigint)) x(size);
+ size | pg_size_pretty | pg_size_pretty
+------------------+----------------+----------------
+ 10 | 10 bytes | -10 bytes
+ 1000 | 1000 bytes | -1000 bytes
+ 1000000 | 977 kB | -977 kB
+ 1000000000 | 954 MB | -954 MB
+ 1000000000000 | 931 GB | -931 GB
+ 1000000000000000 | 909 TB | -909 TB
+(6 rows)
+
+SELECT size, pg_size_pretty(size), pg_size_pretty(-1 * size) FROM
+ (VALUES (10::numeric), (1000::numeric), (1000000::numeric),
+ (1000000000::numeric), (1000000000000::numeric),
+ (1000000000000000::numeric),
+ (10.5::numeric), (1000.5::numeric), (1000000.5::numeric),
+ (1000000000.5::numeric), (1000000000000.5::numeric),
+ (1000000000000000.5::numeric)) x(size);
+ size | pg_size_pretty | pg_size_pretty
+--------------------+----------------+----------------
+ 10 | 10 bytes | -10 bytes
+ 1000 | 1000 bytes | -1000 bytes
+ 1000000 | 977 kB | -977 kB
+ 1000000000 | 954 MB | -954 MB
+ 1000000000000 | 931 GB | -931 GB
+ 1000000000000000 | 909 TB | -909 TB
+ 10.5 | 10.5 bytes | -10.5 bytes
+ 1000.5 | 1000.5 bytes | -1000.5 bytes
+ 1000000.5 | 977 kB | -977 kB
+ 1000000000.5 | 954 MB | -954 MB
+ 1000000000000.5 | 931 GB | -931 GB
+ 1000000000000000.5 | 909 TB | -909 TB
+(12 rows)
+
+-- test where units change up
+SELECT size, pg_size_pretty(size), pg_size_pretty(-1 * size) FROM
+ (VALUES (10239::bigint), (10240::bigint),
+ (10485247::bigint), (10485248::bigint),
+ (10736893951::bigint), (10736893952::bigint),
+ (10994579406847::bigint), (10994579406848::bigint),
+ (11258449312612351::bigint), (11258449312612352::bigint)) x(size);
+ size | pg_size_pretty | pg_size_pretty
+-------------------+----------------+----------------
+ 10239 | 10239 bytes | -10239 bytes
+ 10240 | 10 kB | -10 kB
+ 10485247 | 10239 kB | -10239 kB
+ 10485248 | 10 MB | -10 MB
+ 10736893951 | 10239 MB | -10239 MB
+ 10736893952 | 10 GB | -10 GB
+ 10994579406847 | 10239 GB | -10239 GB
+ 10994579406848 | 10 TB | -10 TB
+ 11258449312612351 | 10239 TB | -10239 TB
+ 11258449312612352 | 10240 TB | -10240 TB
+(10 rows)
+
+SELECT size, pg_size_pretty(size), pg_size_pretty(-1 * size) FROM
+ (VALUES (10239::numeric), (10240::numeric),
+ (10485247::numeric), (10485248::numeric),
+ (10736893951::numeric), (10736893952::numeric),
+ (10994579406847::numeric), (10994579406848::numeric),
+ (11258449312612351::numeric), (11258449312612352::numeric)) x(size);
+ size | pg_size_pretty | pg_size_pretty
+-------------------+----------------+----------------
+ 10239 | 10239 bytes | -10239 bytes
+ 10240 | 10 kB | -10 kB
+ 10485247 | 10239 kB | -10239 kB
+ 10485248 | 10 MB | -10 MB
+ 10736893951 | 10239 MB | -10239 MB
+ 10736893952 | 10 GB | -10 GB
+ 10994579406847 | 10239 GB | -10239 GB
+ 10994579406848 | 10 TB | -10 TB
+ 11258449312612351 | 10239 TB | -10239 TB
+ 11258449312612352 | 10240 TB | -10240 TB
+(10 rows)
+
+-- pg_size_bytes() tests
+SELECT size, pg_size_bytes(size) FROM
+ (VALUES ('1'), ('123bytes'), ('1kB'), ('1MB'), (' 1 GB'), ('1.5 GB '),
+ ('1TB'), ('3000 TB'), ('1e6 MB')) x(size);
+ size | pg_size_bytes
+----------+------------------
+ 1 | 1
+ 123bytes | 123
+ 1kB | 1024
+ 1MB | 1048576
+ 1 GB | 1073741824
+ 1.5 GB | 1610612736
+ 1TB | 1099511627776
+ 3000 TB | 3298534883328000
+ 1e6 MB | 1048576000000
+(9 rows)
+
+-- case-insensitive units are supported
+SELECT size, pg_size_bytes(size) FROM
+ (VALUES ('1'), ('123bYteS'), ('1kb'), ('1mb'), (' 1 Gb'), ('1.5 gB '),
+ ('1tb'), ('3000 tb'), ('1e6 mb')) x(size);
+ size | pg_size_bytes
+----------+------------------
+ 1 | 1
+ 123bYteS | 123
+ 1kb | 1024
+ 1mb | 1048576
+ 1 Gb | 1073741824
+ 1.5 gB | 1610612736
+ 1tb | 1099511627776
+ 3000 tb | 3298534883328000
+ 1e6 mb | 1048576000000
+(9 rows)
+
+-- negative numbers are supported
+SELECT size, pg_size_bytes(size) FROM
+ (VALUES ('-1'), ('-123bytes'), ('-1kb'), ('-1mb'), (' -1 Gb'), ('-1.5 gB '),
+ ('-1tb'), ('-3000 TB'), ('-10e-1 MB')) x(size);
+ size | pg_size_bytes
+-----------+-------------------
+ -1 | -1
+ -123bytes | -123
+ -1kb | -1024
+ -1mb | -1048576
+ -1 Gb | -1073741824
+ -1.5 gB | -1610612736
+ -1tb | -1099511627776
+ -3000 TB | -3298534883328000
+ -10e-1 MB | -1048576
+(9 rows)
+
+-- different cases with allowed points
+SELECT size, pg_size_bytes(size) FROM
+ (VALUES ('-1.'), ('-1.kb'), ('-1. kb'), ('-0. gb'),
+ ('-.1'), ('-.1kb'), ('-.1 kb'), ('-.0 gb')) x(size);
+ size | pg_size_bytes
+--------+---------------
+ -1. | -1
+ -1.kb | -1024
+ -1. kb | -1024
+ -0. gb | 0
+ -.1 | 0
+ -.1kb | -102
+ -.1 kb | -102
+ -.0 gb | 0
+(8 rows)
+
+-- invalid inputs
+SELECT pg_size_bytes('1 AB');
+ERROR: invalid size: "1 AB"
+DETAIL: Invalid size unit: "AB".
+HINT: Valid units are "bytes", "kB", "MB", "GB", and "TB".
+SELECT pg_size_bytes('1 AB A');
+ERROR: invalid size: "1 AB A"
+DETAIL: Invalid size unit: "AB A".
+HINT: Valid units are "bytes", "kB", "MB", "GB", and "TB".
+SELECT pg_size_bytes('1 AB A ');
+ERROR: invalid size: "1 AB A "
+DETAIL: Invalid size unit: "AB A".
+HINT: Valid units are "bytes", "kB", "MB", "GB", and "TB".
+SELECT pg_size_bytes('9223372036854775807.9');
+ERROR: bigint out of range
+SELECT pg_size_bytes('1e100');
+ERROR: bigint out of range
+SELECT pg_size_bytes('1e1000000000000000000');
+ERROR: value overflows numeric format
+SELECT pg_size_bytes('1 byte'); -- the singular "byte" is not supported
+ERROR: invalid size: "1 byte"
+DETAIL: Invalid size unit: "byte".
+HINT: Valid units are "bytes", "kB", "MB", "GB", and "TB".
+SELECT pg_size_bytes('');
+ERROR: invalid size: ""
+SELECT pg_size_bytes('kb');
+ERROR: invalid size: "kb"
+SELECT pg_size_bytes('..');
+ERROR: invalid size: ".."
+SELECT pg_size_bytes('-.');
+ERROR: invalid size: "-."
+SELECT pg_size_bytes('-.kb');
+ERROR: invalid size: "-.kb"
+SELECT pg_size_bytes('-. kb');
+ERROR: invalid size: "-. kb"
+SELECT pg_size_bytes('.+912');
+ERROR: invalid size: ".+912"
+SELECT pg_size_bytes('+912+ kB');
+ERROR: invalid size: "+912+ kB"
+DETAIL: Invalid size unit: "+ kB".
+HINT: Valid units are "bytes", "kB", "MB", "GB", and "TB".
+SELECT pg_size_bytes('++123 kB');
+ERROR: invalid size: "++123 kB"
diff --git a/src/test/regress/expected/delete.out b/src/test/regress/expected/delete.out
new file mode 100644
index 0000000..e7eb328
--- /dev/null
+++ b/src/test/regress/expected/delete.out
@@ -0,0 +1,33 @@
+CREATE TABLE delete_test (
+ id SERIAL PRIMARY KEY,
+ a INT,
+ b text
+);
+INSERT INTO delete_test (a) VALUES (10);
+INSERT INTO delete_test (a, b) VALUES (50, repeat('x', 10000));
+INSERT INTO delete_test (a) VALUES (100);
+-- allow an alias to be specified for DELETE's target table
+DELETE FROM delete_test AS dt WHERE dt.a > 75;
+-- if an alias is specified, don't allow the original table name
+-- to be referenced
+DELETE FROM delete_test dt WHERE delete_test.a > 25;
+ERROR: invalid reference to FROM-clause entry for table "delete_test"
+LINE 1: DELETE FROM delete_test dt WHERE delete_test.a > 25;
+ ^
+HINT: Perhaps you meant to reference the table alias "dt".
+SELECT id, a, char_length(b) FROM delete_test;
+ id | a | char_length
+----+----+-------------
+ 1 | 10 |
+ 2 | 50 | 10000
+(2 rows)
+
+-- delete a row with a TOASTed value
+DELETE FROM delete_test WHERE a > 25;
+SELECT id, a, char_length(b) FROM delete_test;
+ id | a | char_length
+----+----+-------------
+ 1 | 10 |
+(1 row)
+
+DROP TABLE delete_test;
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
new file mode 100644
index 0000000..8232795
--- /dev/null
+++ b/src/test/regress/expected/dependency.out
@@ -0,0 +1,150 @@
+--
+-- DEPENDENCIES
+--
+CREATE USER regress_dep_user;
+CREATE USER regress_dep_user2;
+CREATE USER regress_dep_user3;
+CREATE GROUP regress_dep_group;
+CREATE TABLE deptest (f1 serial primary key, f2 text);
+GRANT SELECT ON TABLE deptest TO GROUP regress_dep_group;
+GRANT ALL ON TABLE deptest TO regress_dep_user, regress_dep_user2;
+-- can't drop neither because they have privileges somewhere
+DROP USER regress_dep_user;
+ERROR: role "regress_dep_user" cannot be dropped because some objects depend on it
+DETAIL: privileges for table deptest
+DROP GROUP regress_dep_group;
+ERROR: role "regress_dep_group" cannot be dropped because some objects depend on it
+DETAIL: privileges for table deptest
+-- if we revoke the privileges we can drop the group
+REVOKE SELECT ON deptest FROM GROUP regress_dep_group;
+DROP GROUP regress_dep_group;
+-- can't drop the user if we revoke the privileges partially
+REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON deptest FROM regress_dep_user;
+DROP USER regress_dep_user;
+ERROR: role "regress_dep_user" cannot be dropped because some objects depend on it
+DETAIL: privileges for table deptest
+-- now we are OK to drop him
+REVOKE TRIGGER ON deptest FROM regress_dep_user;
+DROP USER regress_dep_user;
+-- we are OK too if we drop the privileges all at once
+REVOKE ALL ON deptest FROM regress_dep_user2;
+DROP USER regress_dep_user2;
+-- can't drop the owner of an object
+-- the error message detail here would include a pg_toast_nnn name that
+-- is not constant, so suppress it
+\set VERBOSITY terse
+ALTER TABLE deptest OWNER TO regress_dep_user3;
+DROP USER regress_dep_user3;
+ERROR: role "regress_dep_user3" cannot be dropped because some objects depend on it
+\set VERBOSITY default
+-- if we drop the object, we can drop the user too
+DROP TABLE deptest;
+DROP USER regress_dep_user3;
+-- Test DROP OWNED
+CREATE USER regress_dep_user0;
+CREATE USER regress_dep_user1;
+CREATE USER regress_dep_user2;
+SET SESSION AUTHORIZATION regress_dep_user0;
+-- permission denied
+DROP OWNED BY regress_dep_user1;
+ERROR: permission denied to drop objects
+DROP OWNED BY regress_dep_user0, regress_dep_user2;
+ERROR: permission denied to drop objects
+REASSIGN OWNED BY regress_dep_user0 TO regress_dep_user1;
+ERROR: permission denied to reassign objects
+REASSIGN OWNED BY regress_dep_user1 TO regress_dep_user0;
+ERROR: permission denied to reassign objects
+-- this one is allowed
+DROP OWNED BY regress_dep_user0;
+CREATE TABLE deptest1 (f1 int unique);
+GRANT ALL ON deptest1 TO regress_dep_user1 WITH GRANT OPTION;
+SET SESSION AUTHORIZATION regress_dep_user1;
+CREATE TABLE deptest (a serial primary key, b text);
+GRANT ALL ON deptest1 TO regress_dep_user2;
+RESET SESSION AUTHORIZATION;
+\z deptest1
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+----------+-------+----------------------------------------------------+-------------------+----------
+ public | deptest1 | table | regress_dep_user0=arwdDxt/regress_dep_user0 +| |
+ | | | regress_dep_user1=a*r*w*d*D*x*t*/regress_dep_user0+| |
+ | | | regress_dep_user2=arwdDxt/regress_dep_user1 | |
+(1 row)
+
+DROP OWNED BY regress_dep_user1;
+-- all grants revoked
+\z deptest1
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+----------+-------+---------------------------------------------+-------------------+----------
+ public | deptest1 | table | regress_dep_user0=arwdDxt/regress_dep_user0 | |
+(1 row)
+
+-- table was dropped
+\d deptest
+-- Test REASSIGN OWNED
+GRANT ALL ON deptest1 TO regress_dep_user1;
+GRANT CREATE ON DATABASE regression TO regress_dep_user1;
+SET SESSION AUTHORIZATION regress_dep_user1;
+CREATE SCHEMA deptest;
+CREATE TABLE deptest (a serial primary key, b text);
+ALTER DEFAULT PRIVILEGES FOR ROLE regress_dep_user1 IN SCHEMA deptest
+ GRANT ALL ON TABLES TO regress_dep_user2;
+CREATE FUNCTION deptest_func() RETURNS void LANGUAGE plpgsql
+ AS $$ BEGIN END; $$;
+CREATE TYPE deptest_enum AS ENUM ('red');
+CREATE TYPE deptest_range AS RANGE (SUBTYPE = int4);
+CREATE TABLE deptest2 (f1 int);
+-- make a serial column the hard way
+CREATE SEQUENCE ss1;
+ALTER TABLE deptest2 ALTER f1 SET DEFAULT nextval('ss1');
+ALTER SEQUENCE ss1 OWNED BY deptest2.f1;
+-- When reassigning ownership of a composite type, its pg_class entry
+-- should match
+CREATE TYPE deptest_t AS (a int);
+SELECT typowner = relowner
+FROM pg_type JOIN pg_class c ON typrelid = c.oid WHERE typname = 'deptest_t';
+ ?column?
+----------
+ t
+(1 row)
+
+RESET SESSION AUTHORIZATION;
+REASSIGN OWNED BY regress_dep_user1 TO regress_dep_user2;
+\dt deptest
+ List of relations
+ Schema | Name | Type | Owner
+--------+---------+-------+-------------------
+ public | deptest | table | regress_dep_user2
+(1 row)
+
+SELECT typowner = relowner
+FROM pg_type JOIN pg_class c ON typrelid = c.oid WHERE typname = 'deptest_t';
+ ?column?
+----------
+ t
+(1 row)
+
+-- doesn't work: grant still exists
+DROP USER regress_dep_user1;
+ERROR: role "regress_dep_user1" cannot be dropped because some objects depend on it
+DETAIL: privileges for database regression
+privileges for table deptest1
+owner of default privileges on new relations belonging to role regress_dep_user1 in schema deptest
+DROP OWNED BY regress_dep_user1;
+DROP USER regress_dep_user1;
+DROP USER regress_dep_user2;
+ERROR: role "regress_dep_user2" cannot be dropped because some objects depend on it
+DETAIL: owner of schema deptest
+owner of sequence deptest_a_seq
+owner of table deptest
+owner of function deptest_func()
+owner of type deptest_enum
+owner of type deptest_multirange
+owner of type deptest_range
+owner of table deptest2
+owner of sequence ss1
+owner of type deptest_t
+DROP OWNED BY regress_dep_user2, regress_dep_user0;
+DROP USER regress_dep_user2;
+DROP USER regress_dep_user0;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
new file mode 100644
index 0000000..8af2b89
--- /dev/null
+++ b/src/test/regress/expected/domain.out
@@ -0,0 +1,1150 @@
+--
+-- Test domains.
+--
+-- Test Comment / Drop
+create domain domaindroptest int4;
+comment on domain domaindroptest is 'About to drop this..';
+create domain dependenttypetest domaindroptest;
+-- fail because of dependent type
+drop domain domaindroptest;
+ERROR: cannot drop type domaindroptest because other objects depend on it
+DETAIL: type dependenttypetest depends on type domaindroptest
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+drop domain domaindroptest cascade;
+NOTICE: drop cascades to type dependenttypetest
+-- this should fail because already gone
+drop domain domaindroptest cascade;
+ERROR: type "domaindroptest" does not exist
+-- Test domain input.
+-- Note: the point of checking both INSERT and COPY FROM is that INSERT
+-- exercises CoerceToDomain while COPY exercises domain_in.
+create domain domainvarchar varchar(5);
+create domain domainnumeric numeric(8,2);
+create domain domainint4 int4;
+create domain domaintext text;
+-- Test explicit coercions --- these should succeed (and truncate)
+SELECT cast('123456' as domainvarchar);
+ domainvarchar
+---------------
+ 12345
+(1 row)
+
+SELECT cast('12345' as domainvarchar);
+ domainvarchar
+---------------
+ 12345
+(1 row)
+
+-- Test tables using domains
+create table basictest
+ ( testint4 domainint4
+ , testtext domaintext
+ , testvarchar domainvarchar
+ , testnumeric domainnumeric
+ );
+INSERT INTO basictest values ('88', 'haha', 'short', '123.12'); -- Good
+INSERT INTO basictest values ('88', 'haha', 'short text', '123.12'); -- Bad varchar
+ERROR: value too long for type character varying(5)
+INSERT INTO basictest values ('88', 'haha', 'short', '123.1212'); -- Truncate numeric
+-- Test copy
+COPY basictest (testvarchar) FROM stdin; -- fail
+ERROR: value too long for type character varying(5)
+CONTEXT: COPY basictest, line 1, column testvarchar: "notsoshorttext"
+COPY basictest (testvarchar) FROM stdin;
+select * from basictest;
+ testint4 | testtext | testvarchar | testnumeric
+----------+----------+-------------+-------------
+ 88 | haha | short | 123.12
+ 88 | haha | short | 123.12
+ | | short |
+(3 rows)
+
+-- check that domains inherit operations from base types
+select testtext || testvarchar as concat, testnumeric + 42 as sum
+from basictest;
+ concat | sum
+-----------+--------
+ hahashort | 165.12
+ hahashort | 165.12
+ |
+(3 rows)
+
+-- check that union/case/coalesce type resolution handles domains properly
+select pg_typeof(coalesce(4::domainint4, 7));
+ pg_typeof
+-----------
+ integer
+(1 row)
+
+select pg_typeof(coalesce(4::domainint4, 7::domainint4));
+ pg_typeof
+------------
+ domainint4
+(1 row)
+
+drop table basictest;
+drop domain domainvarchar restrict;
+drop domain domainnumeric restrict;
+drop domain domainint4 restrict;
+drop domain domaintext;
+-- Test domains over array types
+create domain domainint4arr int4[1];
+create domain domainchar4arr varchar(4)[2][3];
+create table domarrtest
+ ( testint4arr domainint4arr
+ , testchar4arr domainchar4arr
+ );
+INSERT INTO domarrtest values ('{2,2}', '{{"a","b"},{"c","d"}}');
+INSERT INTO domarrtest values ('{{2,2},{2,2}}', '{{"a","b"}}');
+INSERT INTO domarrtest values ('{2,2}', '{{"a","b"},{"c","d"},{"e","f"}}');
+INSERT INTO domarrtest values ('{2,2}', '{{"a"},{"c"}}');
+INSERT INTO domarrtest values (NULL, '{{"a","b","c"},{"d","e","f"}}');
+INSERT INTO domarrtest values (NULL, '{{"toolong","b","c"},{"d","e","f"}}');
+ERROR: value too long for type character varying(4)
+INSERT INTO domarrtest (testint4arr[1], testint4arr[3]) values (11,22);
+select * from domarrtest;
+ testint4arr | testchar4arr
+---------------+---------------------
+ {2,2} | {{a,b},{c,d}}
+ {{2,2},{2,2}} | {{a,b}}
+ {2,2} | {{a,b},{c,d},{e,f}}
+ {2,2} | {{a},{c}}
+ | {{a,b,c},{d,e,f}}
+ {11,NULL,22} |
+(6 rows)
+
+select testint4arr[1], testchar4arr[2:2] from domarrtest;
+ testint4arr | testchar4arr
+-------------+--------------
+ 2 | {{c,d}}
+ | {}
+ 2 | {{c,d}}
+ 2 | {{c}}
+ | {{d,e,f}}
+ 11 |
+(6 rows)
+
+select array_dims(testint4arr), array_dims(testchar4arr) from domarrtest;
+ array_dims | array_dims
+------------+------------
+ [1:2] | [1:2][1:2]
+ [1:2][1:2] | [1:1][1:2]
+ [1:2] | [1:3][1:2]
+ [1:2] | [1:2][1:1]
+ | [1:2][1:3]
+ [1:3] |
+(6 rows)
+
+COPY domarrtest FROM stdin;
+COPY domarrtest FROM stdin; -- fail
+ERROR: value too long for type character varying(4)
+CONTEXT: COPY domarrtest, line 1, column testchar4arr: "{qwerty,w,e}"
+select * from domarrtest;
+ testint4arr | testchar4arr
+---------------+---------------------
+ {2,2} | {{a,b},{c,d}}
+ {{2,2},{2,2}} | {{a,b}}
+ {2,2} | {{a,b},{c,d},{e,f}}
+ {2,2} | {{a},{c}}
+ | {{a,b,c},{d,e,f}}
+ {11,NULL,22} |
+ {3,4} | {q,w,e}
+ |
+(8 rows)
+
+update domarrtest set
+ testint4arr[1] = testint4arr[1] + 1,
+ testint4arr[3] = testint4arr[3] - 1
+where testchar4arr is null;
+select * from domarrtest where testchar4arr is null;
+ testint4arr | testchar4arr
+------------------+--------------
+ {12,NULL,21} |
+ {NULL,NULL,NULL} |
+(2 rows)
+
+drop table domarrtest;
+drop domain domainint4arr restrict;
+drop domain domainchar4arr restrict;
+create domain dia as int[];
+select '{1,2,3}'::dia;
+ dia
+---------
+ {1,2,3}
+(1 row)
+
+select array_dims('{1,2,3}'::dia);
+ array_dims
+------------
+ [1:3]
+(1 row)
+
+select pg_typeof('{1,2,3}'::dia);
+ pg_typeof
+-----------
+ dia
+(1 row)
+
+select pg_typeof('{1,2,3}'::dia || 42); -- should be int[] not dia
+ pg_typeof
+-----------
+ integer[]
+(1 row)
+
+drop domain dia;
+-- Test domains over composites
+create type comptype as (r float8, i float8);
+create domain dcomptype as comptype;
+create table dcomptable (d1 dcomptype unique);
+insert into dcomptable values (row(1,2)::dcomptype);
+insert into dcomptable values (row(3,4)::comptype);
+insert into dcomptable values (row(1,2)::dcomptype); -- fail on uniqueness
+ERROR: duplicate key value violates unique constraint "dcomptable_d1_key"
+DETAIL: Key (d1)=((1,2)) already exists.
+insert into dcomptable (d1.r) values(11);
+select * from dcomptable;
+ d1
+-------
+ (1,2)
+ (3,4)
+ (11,)
+(3 rows)
+
+select (d1).r, (d1).i, (d1).* from dcomptable;
+ r | i | r | i
+----+---+----+---
+ 1 | 2 | 1 | 2
+ 3 | 4 | 3 | 4
+ 11 | | 11 |
+(3 rows)
+
+update dcomptable set d1.r = (d1).r + 1 where (d1).i > 0;
+select * from dcomptable;
+ d1
+-------
+ (11,)
+ (2,2)
+ (4,4)
+(3 rows)
+
+alter domain dcomptype add constraint c1 check ((value).r <= (value).i);
+alter domain dcomptype add constraint c2 check ((value).r > (value).i); -- fail
+ERROR: column "d1" of table "dcomptable" contains values that violate the new constraint
+select row(2,1)::dcomptype; -- fail
+ERROR: value for domain dcomptype violates check constraint "c1"
+insert into dcomptable values (row(1,2)::comptype);
+insert into dcomptable values (row(2,1)::comptype); -- fail
+ERROR: value for domain dcomptype violates check constraint "c1"
+insert into dcomptable (d1.r) values(99);
+insert into dcomptable (d1.r, d1.i) values(99, 100);
+insert into dcomptable (d1.r, d1.i) values(100, 99); -- fail
+ERROR: value for domain dcomptype violates check constraint "c1"
+update dcomptable set d1.r = (d1).r + 1 where (d1).i > 0; -- fail
+ERROR: value for domain dcomptype violates check constraint "c1"
+update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
+select * from dcomptable;
+ d1
+----------
+ (11,)
+ (99,)
+ (1,3)
+ (3,5)
+ (0,3)
+ (98,101)
+(6 rows)
+
+explain (verbose, costs off)
+ update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------
+ Update on public.dcomptable
+ -> Seq Scan on public.dcomptable
+ Output: ROW(((d1).r - '1'::double precision), ((d1).i + '1'::double precision)), ctid
+ Filter: ((dcomptable.d1).i > '0'::double precision)
+(4 rows)
+
+create rule silly as on delete to dcomptable do instead
+ update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
+\d+ dcomptable
+ Table "public.dcomptable"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+-----------+-----------+----------+---------+----------+--------------+-------------
+ d1 | dcomptype | | | | extended | |
+Indexes:
+ "dcomptable_d1_key" UNIQUE CONSTRAINT, btree (d1)
+Rules:
+ silly AS
+ ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1.r = (dcomptable.d1).r - 1::double precision, d1.i = (dcomptable.d1).i + 1::double precision
+ WHERE (dcomptable.d1).i > 0::double precision
+
+create function makedcomp(r float8, i float8) returns dcomptype
+as 'select row(r, i)' language sql;
+select makedcomp(1,2);
+ makedcomp
+-----------
+ (1,2)
+(1 row)
+
+select makedcomp(2,1); -- fail
+ERROR: value for domain dcomptype violates check constraint "c1"
+select * from makedcomp(1,2) m;
+ r | i
+---+---
+ 1 | 2
+(1 row)
+
+select m, m is not null from makedcomp(1,2) m;
+ m | ?column?
+-------+----------
+ (1,2) | t
+(1 row)
+
+drop function makedcomp(float8, float8);
+drop table dcomptable;
+drop type comptype cascade;
+NOTICE: drop cascades to type dcomptype
+-- check altering and dropping columns used by domain constraints
+create type comptype as (r float8, i float8);
+create domain dcomptype as comptype;
+alter domain dcomptype add constraint c1 check ((value).r > 0);
+comment on constraint c1 on domain dcomptype is 'random commentary';
+select row(0,1)::dcomptype; -- fail
+ERROR: value for domain dcomptype violates check constraint "c1"
+alter type comptype alter attribute r type varchar; -- fail
+ERROR: operator does not exist: character varying > double precision
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+alter type comptype alter attribute r type bigint;
+alter type comptype drop attribute r; -- fail
+ERROR: cannot drop column r of composite type comptype because other objects depend on it
+DETAIL: constraint c1 depends on column r of composite type comptype
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+alter type comptype drop attribute i;
+select conname, obj_description(oid, 'pg_constraint') from pg_constraint
+ where contypid = 'dcomptype'::regtype; -- check comment is still there
+ conname | obj_description
+---------+-------------------
+ c1 | random commentary
+(1 row)
+
+drop type comptype cascade;
+NOTICE: drop cascades to type dcomptype
+-- Test domains over arrays of composite
+create type comptype as (r float8, i float8);
+create domain dcomptypea as comptype[];
+create table dcomptable (d1 dcomptypea unique);
+insert into dcomptable values (array[row(1,2)]::dcomptypea);
+insert into dcomptable values (array[row(3,4), row(5,6)]::comptype[]);
+insert into dcomptable values (array[row(7,8)::comptype, row(9,10)::comptype]);
+insert into dcomptable values (array[row(1,2)]::dcomptypea); -- fail on uniqueness
+ERROR: duplicate key value violates unique constraint "dcomptable_d1_key"
+DETAIL: Key (d1)=({"(1,2)"}) already exists.
+insert into dcomptable (d1[1]) values(row(9,10));
+insert into dcomptable (d1[1].r) values(11);
+select * from dcomptable;
+ d1
+--------------------
+ {"(1,2)"}
+ {"(3,4)","(5,6)"}
+ {"(7,8)","(9,10)"}
+ {"(9,10)"}
+ {"(11,)"}
+(5 rows)
+
+select d1[2], d1[1].r, d1[1].i from dcomptable;
+ d1 | r | i
+--------+----+----
+ | 1 | 2
+ (5,6) | 3 | 4
+ (9,10) | 7 | 8
+ | 9 | 10
+ | 11 |
+(5 rows)
+
+update dcomptable set d1[2] = row(d1[2].i, d1[2].r);
+select * from dcomptable;
+ d1
+--------------------
+ {"(1,2)","(,)"}
+ {"(3,4)","(6,5)"}
+ {"(7,8)","(10,9)"}
+ {"(9,10)","(,)"}
+ {"(11,)","(,)"}
+(5 rows)
+
+update dcomptable set d1[1].r = d1[1].r + 1 where d1[1].i > 0;
+select * from dcomptable;
+ d1
+--------------------
+ {"(11,)","(,)"}
+ {"(2,2)","(,)"}
+ {"(4,4)","(6,5)"}
+ {"(8,8)","(10,9)"}
+ {"(10,10)","(,)"}
+(5 rows)
+
+alter domain dcomptypea add constraint c1 check (value[1].r <= value[1].i);
+alter domain dcomptypea add constraint c2 check (value[1].r > value[1].i); -- fail
+ERROR: column "d1" of table "dcomptable" contains values that violate the new constraint
+select array[row(2,1)]::dcomptypea; -- fail
+ERROR: value for domain dcomptypea violates check constraint "c1"
+insert into dcomptable values (array[row(1,2)]::comptype[]);
+insert into dcomptable values (array[row(2,1)]::comptype[]); -- fail
+ERROR: value for domain dcomptypea violates check constraint "c1"
+insert into dcomptable (d1[1].r) values(99);
+insert into dcomptable (d1[1].r, d1[1].i) values(99, 100);
+insert into dcomptable (d1[1].r, d1[1].i) values(100, 99); -- fail
+ERROR: value for domain dcomptypea violates check constraint "c1"
+update dcomptable set d1[1].r = d1[1].r + 1 where d1[1].i > 0; -- fail
+ERROR: value for domain dcomptypea violates check constraint "c1"
+update dcomptable set d1[1].r = d1[1].r - 1, d1[1].i = d1[1].i + 1
+ where d1[1].i > 0;
+select * from dcomptable;
+ d1
+--------------------
+ {"(11,)","(,)"}
+ {"(99,)"}
+ {"(1,3)","(,)"}
+ {"(3,5)","(6,5)"}
+ {"(7,9)","(10,9)"}
+ {"(9,11)","(,)"}
+ {"(0,3)"}
+ {"(98,101)"}
+(8 rows)
+
+explain (verbose, costs off)
+ update dcomptable set d1[1].r = d1[1].r - 1, d1[1].i = d1[1].i + 1
+ where d1[1].i > 0;
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------
+ Update on public.dcomptable
+ -> Seq Scan on public.dcomptable
+ Output: (d1[1].r := (d1[1].r - '1'::double precision))[1].i := (d1[1].i + '1'::double precision), ctid
+ Filter: (dcomptable.d1[1].i > '0'::double precision)
+(4 rows)
+
+create rule silly as on delete to dcomptable do instead
+ update dcomptable set d1[1].r = d1[1].r - 1, d1[1].i = d1[1].i + 1
+ where d1[1].i > 0;
+\d+ dcomptable
+ Table "public.dcomptable"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------------+-----------+----------+---------+----------+--------------+-------------
+ d1 | dcomptypea | | | | extended | |
+Indexes:
+ "dcomptable_d1_key" UNIQUE CONSTRAINT, btree (d1)
+Rules:
+ silly AS
+ ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1[1].r = dcomptable.d1[1].r - 1::double precision, d1[1].i = dcomptable.d1[1].i + 1::double precision
+ WHERE dcomptable.d1[1].i > 0::double precision
+
+drop table dcomptable;
+drop type comptype cascade;
+NOTICE: drop cascades to type dcomptypea
+-- Test arrays over domains
+create domain posint as int check (value > 0);
+create table pitable (f1 posint[]);
+insert into pitable values(array[42]);
+insert into pitable values(array[-1]); -- fail
+ERROR: value for domain posint violates check constraint "posint_check"
+insert into pitable values('{0}'); -- fail
+ERROR: value for domain posint violates check constraint "posint_check"
+LINE 1: insert into pitable values('{0}');
+ ^
+update pitable set f1[1] = f1[1] + 1;
+update pitable set f1[1] = 0; -- fail
+ERROR: value for domain posint violates check constraint "posint_check"
+select * from pitable;
+ f1
+------
+ {43}
+(1 row)
+
+drop table pitable;
+create domain vc4 as varchar(4);
+create table vc4table (f1 vc4[]);
+insert into vc4table values(array['too long']); -- fail
+ERROR: value too long for type character varying(4)
+insert into vc4table values(array['too long']::vc4[]); -- cast truncates
+select * from vc4table;
+ f1
+----------
+ {"too "}
+(1 row)
+
+drop table vc4table;
+drop type vc4;
+-- You can sort of fake arrays-of-arrays by putting a domain in between
+create domain dposinta as posint[];
+create table dposintatable (f1 dposinta[]);
+insert into dposintatable values(array[array[42]]); -- fail
+ERROR: column "f1" is of type dposinta[] but expression is of type integer[]
+LINE 1: insert into dposintatable values(array[array[42]]);
+ ^
+HINT: You will need to rewrite or cast the expression.
+insert into dposintatable values(array[array[42]::posint[]]); -- still fail
+ERROR: column "f1" is of type dposinta[] but expression is of type posint[]
+LINE 1: insert into dposintatable values(array[array[42]::posint[]])...
+ ^
+HINT: You will need to rewrite or cast the expression.
+insert into dposintatable values(array[array[42]::dposinta]); -- but this works
+select f1, f1[1], (f1[1])[1] from dposintatable;
+ f1 | f1 | f1
+----------+------+----
+ {"{42}"} | {42} | 42
+(1 row)
+
+select pg_typeof(f1) from dposintatable;
+ pg_typeof
+------------
+ dposinta[]
+(1 row)
+
+select pg_typeof(f1[1]) from dposintatable;
+ pg_typeof
+-----------
+ dposinta
+(1 row)
+
+select pg_typeof(f1[1][1]) from dposintatable;
+ pg_typeof
+-----------
+ dposinta
+(1 row)
+
+select pg_typeof((f1[1])[1]) from dposintatable;
+ pg_typeof
+-----------
+ posint
+(1 row)
+
+update dposintatable set f1[2] = array[99];
+select f1, f1[1], (f1[2])[1] from dposintatable;
+ f1 | f1 | f1
+-----------------+------+----
+ {"{42}","{99}"} | {42} | 99
+(1 row)
+
+-- it'd be nice if you could do something like this, but for now you can't:
+update dposintatable set f1[2][1] = array[97];
+ERROR: wrong number of array subscripts
+-- maybe someday we can make this syntax work:
+update dposintatable set (f1[2])[1] = array[98];
+ERROR: syntax error at or near "["
+LINE 1: update dposintatable set (f1[2])[1] = array[98];
+ ^
+drop table dposintatable;
+drop domain posint cascade;
+NOTICE: drop cascades to type dposinta
+-- Test arrays over domains of composite
+create type comptype as (cf1 int, cf2 int);
+create domain dcomptype as comptype check ((value).cf1 > 0);
+create table dcomptable (f1 dcomptype[]);
+insert into dcomptable values (null);
+update dcomptable set f1[1].cf2 = 5;
+table dcomptable;
+ f1
+----------
+ {"(,5)"}
+(1 row)
+
+update dcomptable set f1[1].cf1 = -1; -- fail
+ERROR: value for domain dcomptype violates check constraint "dcomptype_check"
+update dcomptable set f1[1].cf1 = 1;
+table dcomptable;
+ f1
+-----------
+ {"(1,5)"}
+(1 row)
+
+drop table dcomptable;
+drop type comptype cascade;
+NOTICE: drop cascades to type dcomptype
+-- Test not-null restrictions
+create domain dnotnull varchar(15) NOT NULL;
+create domain dnull varchar(15);
+create domain dcheck varchar(15) NOT NULL CHECK (VALUE = 'a' OR VALUE = 'c' OR VALUE = 'd');
+create table nulltest
+ ( col1 dnotnull
+ , col2 dnotnull NULL -- NOT NULL in the domain cannot be overridden
+ , col3 dnull NOT NULL
+ , col4 dnull
+ , col5 dcheck CHECK (col5 IN ('c', 'd'))
+ );
+INSERT INTO nulltest DEFAULT VALUES;
+ERROR: domain dnotnull does not allow null values
+INSERT INTO nulltest values ('a', 'b', 'c', 'd', 'c'); -- Good
+insert into nulltest values ('a', 'b', 'c', 'd', NULL);
+ERROR: domain dcheck does not allow null values
+insert into nulltest values ('a', 'b', 'c', 'd', 'a');
+ERROR: new row for relation "nulltest" violates check constraint "nulltest_col5_check"
+DETAIL: Failing row contains (a, b, c, d, a).
+INSERT INTO nulltest values (NULL, 'b', 'c', 'd', 'd');
+ERROR: domain dnotnull does not allow null values
+INSERT INTO nulltest values ('a', NULL, 'c', 'd', 'c');
+ERROR: domain dnotnull does not allow null values
+INSERT INTO nulltest values ('a', 'b', NULL, 'd', 'c');
+ERROR: null value in column "col3" of relation "nulltest" violates not-null constraint
+DETAIL: Failing row contains (a, b, null, d, c).
+INSERT INTO nulltest values ('a', 'b', 'c', NULL, 'd'); -- Good
+-- Test copy
+COPY nulltest FROM stdin; --fail
+ERROR: null value in column "col3" of relation "nulltest" violates not-null constraint
+DETAIL: Failing row contains (a, b, null, d, d).
+CONTEXT: COPY nulltest, line 1: "a b \N d d"
+COPY nulltest FROM stdin; --fail
+ERROR: domain dcheck does not allow null values
+CONTEXT: COPY nulltest, line 1, column col5: null input
+-- Last row is bad
+COPY nulltest FROM stdin;
+ERROR: new row for relation "nulltest" violates check constraint "nulltest_col5_check"
+DETAIL: Failing row contains (a, b, c, null, a).
+CONTEXT: COPY nulltest, line 3: "a b c \N a"
+select * from nulltest;
+ col1 | col2 | col3 | col4 | col5
+------+------+------+------+------
+ a | b | c | d | c
+ a | b | c | | d
+(2 rows)
+
+-- Test out coerced (casted) constraints
+SELECT cast('1' as dnotnull);
+ dnotnull
+----------
+ 1
+(1 row)
+
+SELECT cast(NULL as dnotnull); -- fail
+ERROR: domain dnotnull does not allow null values
+SELECT cast(cast(NULL as dnull) as dnotnull); -- fail
+ERROR: domain dnotnull does not allow null values
+SELECT cast(col4 as dnotnull) from nulltest; -- fail
+ERROR: domain dnotnull does not allow null values
+-- cleanup
+drop table nulltest;
+drop domain dnotnull restrict;
+drop domain dnull restrict;
+drop domain dcheck restrict;
+create domain ddef1 int4 DEFAULT 3;
+create domain ddef2 oid DEFAULT '12';
+-- Type mixing, function returns int8
+create domain ddef3 text DEFAULT 5;
+create sequence ddef4_seq;
+create domain ddef4 int4 DEFAULT nextval('ddef4_seq');
+create domain ddef5 numeric(8,2) NOT NULL DEFAULT '12.12';
+create table defaulttest
+ ( col1 ddef1
+ , col2 ddef2
+ , col3 ddef3
+ , col4 ddef4 PRIMARY KEY
+ , col5 ddef1 NOT NULL DEFAULT NULL
+ , col6 ddef2 DEFAULT '88'
+ , col7 ddef4 DEFAULT 8000
+ , col8 ddef5
+ );
+insert into defaulttest(col4) values(0); -- fails, col5 defaults to null
+ERROR: null value in column "col5" of relation "defaulttest" violates not-null constraint
+DETAIL: Failing row contains (3, 12, 5, 0, null, 88, 8000, 12.12).
+alter table defaulttest alter column col5 drop default;
+insert into defaulttest default values; -- succeeds, inserts domain default
+-- We used to treat SET DEFAULT NULL as equivalent to DROP DEFAULT; wrong
+alter table defaulttest alter column col5 set default null;
+insert into defaulttest(col4) values(0); -- fails
+ERROR: null value in column "col5" of relation "defaulttest" violates not-null constraint
+DETAIL: Failing row contains (3, 12, 5, 0, null, 88, 8000, 12.12).
+alter table defaulttest alter column col5 drop default;
+insert into defaulttest default values;
+insert into defaulttest default values;
+-- Test defaults with copy
+COPY defaulttest(col5) FROM stdin;
+select * from defaulttest;
+ col1 | col2 | col3 | col4 | col5 | col6 | col7 | col8
+------+------+------+------+------+------+------+-------
+ 3 | 12 | 5 | 1 | 3 | 88 | 8000 | 12.12
+ 3 | 12 | 5 | 2 | 3 | 88 | 8000 | 12.12
+ 3 | 12 | 5 | 3 | 3 | 88 | 8000 | 12.12
+ 3 | 12 | 5 | 4 | 42 | 88 | 8000 | 12.12
+(4 rows)
+
+drop table defaulttest cascade;
+-- Test ALTER DOMAIN .. NOT NULL
+create domain dnotnulltest integer;
+create table domnotnull
+( col1 dnotnulltest
+, col2 dnotnulltest
+);
+insert into domnotnull default values;
+alter domain dnotnulltest set not null; -- fails
+ERROR: column "col1" of table "domnotnull" contains null values
+update domnotnull set col1 = 5;
+alter domain dnotnulltest set not null; -- fails
+ERROR: column "col2" of table "domnotnull" contains null values
+update domnotnull set col2 = 6;
+alter domain dnotnulltest set not null;
+update domnotnull set col1 = null; -- fails
+ERROR: domain dnotnulltest does not allow null values
+alter domain dnotnulltest drop not null;
+update domnotnull set col1 = null;
+drop domain dnotnulltest cascade;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to column col2 of table domnotnull
+drop cascades to column col1 of table domnotnull
+-- Test ALTER DOMAIN .. DEFAULT ..
+create table domdeftest (col1 ddef1);
+insert into domdeftest default values;
+select * from domdeftest;
+ col1
+------
+ 3
+(1 row)
+
+alter domain ddef1 set default '42';
+insert into domdeftest default values;
+select * from domdeftest;
+ col1
+------
+ 3
+ 42
+(2 rows)
+
+alter domain ddef1 drop default;
+insert into domdeftest default values;
+select * from domdeftest;
+ col1
+------
+ 3
+ 42
+
+(3 rows)
+
+drop table domdeftest;
+-- Test ALTER DOMAIN .. CONSTRAINT ..
+create domain con as integer;
+create table domcontest (col1 con);
+insert into domcontest values (1);
+insert into domcontest values (2);
+alter domain con add constraint t check (VALUE < 1); -- fails
+ERROR: column "col1" of table "domcontest" contains values that violate the new constraint
+alter domain con add constraint t check (VALUE < 34);
+alter domain con add check (VALUE > 0);
+insert into domcontest values (-5); -- fails
+ERROR: value for domain con violates check constraint "con_check"
+insert into domcontest values (42); -- fails
+ERROR: value for domain con violates check constraint "t"
+insert into domcontest values (5);
+alter domain con drop constraint t;
+insert into domcontest values (-5); --fails
+ERROR: value for domain con violates check constraint "con_check"
+insert into domcontest values (42);
+alter domain con drop constraint nonexistent;
+ERROR: constraint "nonexistent" of domain "con" does not exist
+alter domain con drop constraint if exists nonexistent;
+NOTICE: constraint "nonexistent" of domain "con" does not exist, skipping
+-- Test ALTER DOMAIN .. CONSTRAINT .. NOT VALID
+create domain things AS INT;
+CREATE TABLE thethings (stuff things);
+INSERT INTO thethings (stuff) VALUES (55);
+ALTER DOMAIN things ADD CONSTRAINT meow CHECK (VALUE < 11);
+ERROR: column "stuff" of table "thethings" contains values that violate the new constraint
+ALTER DOMAIN things ADD CONSTRAINT meow CHECK (VALUE < 11) NOT VALID;
+ALTER DOMAIN things VALIDATE CONSTRAINT meow;
+ERROR: column "stuff" of table "thethings" contains values that violate the new constraint
+UPDATE thethings SET stuff = 10;
+ALTER DOMAIN things VALIDATE CONSTRAINT meow;
+-- Confirm ALTER DOMAIN with RULES.
+create table domtab (col1 integer);
+create domain dom as integer;
+create view domview as select cast(col1 as dom) from domtab;
+insert into domtab (col1) values (null);
+insert into domtab (col1) values (5);
+select * from domview;
+ col1
+------
+
+ 5
+(2 rows)
+
+alter domain dom set not null;
+select * from domview; -- fail
+ERROR: domain dom does not allow null values
+alter domain dom drop not null;
+select * from domview;
+ col1
+------
+
+ 5
+(2 rows)
+
+alter domain dom add constraint domchkgt6 check(value > 6);
+select * from domview; --fail
+ERROR: value for domain dom violates check constraint "domchkgt6"
+alter domain dom drop constraint domchkgt6 restrict;
+select * from domview;
+ col1
+------
+
+ 5
+(2 rows)
+
+-- cleanup
+drop domain ddef1 restrict;
+drop domain ddef2 restrict;
+drop domain ddef3 restrict;
+drop domain ddef4 restrict;
+drop domain ddef5 restrict;
+drop sequence ddef4_seq;
+-- Test domains over domains
+create domain vchar4 varchar(4);
+create domain dinter vchar4 check (substring(VALUE, 1, 1) = 'x');
+create domain dtop dinter check (substring(VALUE, 2, 1) = '1');
+select 'x123'::dtop;
+ dtop
+------
+ x123
+(1 row)
+
+select 'x1234'::dtop; -- explicit coercion should truncate
+ dtop
+------
+ x123
+(1 row)
+
+select 'y1234'::dtop; -- fail
+ERROR: value for domain dtop violates check constraint "dinter_check"
+select 'y123'::dtop; -- fail
+ERROR: value for domain dtop violates check constraint "dinter_check"
+select 'yz23'::dtop; -- fail
+ERROR: value for domain dtop violates check constraint "dinter_check"
+select 'xz23'::dtop; -- fail
+ERROR: value for domain dtop violates check constraint "dtop_check"
+create temp table dtest(f1 dtop);
+insert into dtest values('x123');
+insert into dtest values('x1234'); -- fail, implicit coercion
+ERROR: value too long for type character varying(4)
+insert into dtest values('y1234'); -- fail, implicit coercion
+ERROR: value too long for type character varying(4)
+insert into dtest values('y123'); -- fail
+ERROR: value for domain dtop violates check constraint "dinter_check"
+insert into dtest values('yz23'); -- fail
+ERROR: value for domain dtop violates check constraint "dinter_check"
+insert into dtest values('xz23'); -- fail
+ERROR: value for domain dtop violates check constraint "dtop_check"
+drop table dtest;
+drop domain vchar4 cascade;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to type dinter
+drop cascades to type dtop
+-- Make sure that constraints of newly-added domain columns are
+-- enforced correctly, even if there's no default value for the new
+-- column. Per bug #1433
+create domain str_domain as text not null;
+create table domain_test (a int, b int);
+insert into domain_test values (1, 2);
+insert into domain_test values (1, 2);
+-- should fail
+alter table domain_test add column c str_domain;
+ERROR: domain str_domain does not allow null values
+create domain str_domain2 as text check (value <> 'foo') default 'foo';
+-- should fail
+alter table domain_test add column d str_domain2;
+ERROR: value for domain str_domain2 violates check constraint "str_domain2_check"
+-- Check that domain constraints on prepared statement parameters of
+-- unknown type are enforced correctly.
+create domain pos_int as int4 check (value > 0) not null;
+prepare s1 as select $1::pos_int = 10 as "is_ten";
+execute s1(10);
+ is_ten
+--------
+ t
+(1 row)
+
+execute s1(0); -- should fail
+ERROR: value for domain pos_int violates check constraint "pos_int_check"
+execute s1(NULL); -- should fail
+ERROR: domain pos_int does not allow null values
+-- Check that domain constraints on plpgsql function parameters, results,
+-- and local variables are enforced correctly.
+create function doubledecrement(p1 pos_int) returns pos_int as $$
+declare v pos_int;
+begin
+ return p1;
+end$$ language plpgsql;
+select doubledecrement(3); -- fail because of implicit null assignment
+ERROR: domain pos_int does not allow null values
+CONTEXT: PL/pgSQL function doubledecrement(pos_int) line 3 during statement block local variable initialization
+create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
+declare v pos_int := 0;
+begin
+ return p1;
+end$$ language plpgsql;
+select doubledecrement(3); -- fail at initialization assignment
+ERROR: value for domain pos_int violates check constraint "pos_int_check"
+CONTEXT: PL/pgSQL function doubledecrement(pos_int) line 3 during statement block local variable initialization
+create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
+declare v pos_int := 1;
+begin
+ v := p1 - 1;
+ return v - 1;
+end$$ language plpgsql;
+select doubledecrement(null); -- fail before call
+ERROR: domain pos_int does not allow null values
+select doubledecrement(0); -- fail before call
+ERROR: value for domain pos_int violates check constraint "pos_int_check"
+select doubledecrement(1); -- fail at assignment to v
+ERROR: value for domain pos_int violates check constraint "pos_int_check"
+CONTEXT: PL/pgSQL function doubledecrement(pos_int) line 4 at assignment
+select doubledecrement(2); -- fail at return
+ERROR: value for domain pos_int violates check constraint "pos_int_check"
+CONTEXT: PL/pgSQL function doubledecrement(pos_int) while casting return value to function's return type
+select doubledecrement(3); -- good
+ doubledecrement
+-----------------
+ 1
+(1 row)
+
+-- Check that ALTER DOMAIN tests columns of derived types
+create domain posint as int4;
+-- Currently, this doesn't work for composite types, but verify it complains
+create type ddtest1 as (f1 posint);
+create table ddtest2(f1 ddtest1);
+insert into ddtest2 values(row(-1));
+alter domain posint add constraint c1 check(value >= 0);
+ERROR: cannot alter type "posint" because column "ddtest2.f1" uses it
+drop table ddtest2;
+-- Likewise for domains within arrays of composite
+create table ddtest2(f1 ddtest1[]);
+insert into ddtest2 values('{(-1)}');
+alter domain posint add constraint c1 check(value >= 0);
+ERROR: cannot alter type "posint" because column "ddtest2.f1" uses it
+drop table ddtest2;
+-- Likewise for domains within domains over composite
+create domain ddtest1d as ddtest1;
+create table ddtest2(f1 ddtest1d);
+insert into ddtest2 values('(-1)');
+alter domain posint add constraint c1 check(value >= 0);
+ERROR: cannot alter type "posint" because column "ddtest2.f1" uses it
+drop table ddtest2;
+drop domain ddtest1d;
+-- Likewise for domains within domains over array of composite
+create domain ddtest1d as ddtest1[];
+create table ddtest2(f1 ddtest1d);
+insert into ddtest2 values('{(-1)}');
+alter domain posint add constraint c1 check(value >= 0);
+ERROR: cannot alter type "posint" because column "ddtest2.f1" uses it
+drop table ddtest2;
+drop domain ddtest1d;
+-- Doesn't work for ranges, either
+create type rposint as range (subtype = posint);
+create table ddtest2(f1 rposint);
+insert into ddtest2 values('(-1,3]');
+alter domain posint add constraint c1 check(value >= 0);
+ERROR: cannot alter type "posint" because column "ddtest2.f1" uses it
+drop table ddtest2;
+drop type rposint;
+alter domain posint add constraint c1 check(value >= 0);
+create domain posint2 as posint check (value % 2 = 0);
+create table ddtest2(f1 posint2);
+insert into ddtest2 values(11); -- fail
+ERROR: value for domain posint2 violates check constraint "posint2_check"
+insert into ddtest2 values(-2); -- fail
+ERROR: value for domain posint2 violates check constraint "c1"
+insert into ddtest2 values(2);
+alter domain posint add constraint c2 check(value >= 10); -- fail
+ERROR: column "f1" of table "ddtest2" contains values that violate the new constraint
+alter domain posint add constraint c2 check(value > 0); -- OK
+drop table ddtest2;
+drop type ddtest1;
+drop domain posint cascade;
+NOTICE: drop cascades to type posint2
+--
+-- Check enforcement of domain-related typmod in plpgsql (bug #5717)
+--
+create or replace function array_elem_check(numeric) returns numeric as $$
+declare
+ x numeric(4,2)[1];
+begin
+ x[1] := $1;
+ return x[1];
+end$$ language plpgsql;
+select array_elem_check(121.00);
+ERROR: numeric field overflow
+DETAIL: A field with precision 4, scale 2 must round to an absolute value less than 10^2.
+CONTEXT: PL/pgSQL function array_elem_check(numeric) line 5 at assignment
+select array_elem_check(1.23456);
+ array_elem_check
+------------------
+ 1.23
+(1 row)
+
+create domain mynums as numeric(4,2)[1];
+create or replace function array_elem_check(numeric) returns numeric as $$
+declare
+ x mynums;
+begin
+ x[1] := $1;
+ return x[1];
+end$$ language plpgsql;
+select array_elem_check(121.00);
+ERROR: numeric field overflow
+DETAIL: A field with precision 4, scale 2 must round to an absolute value less than 10^2.
+CONTEXT: PL/pgSQL function array_elem_check(numeric) line 5 at assignment
+select array_elem_check(1.23456);
+ array_elem_check
+------------------
+ 1.23
+(1 row)
+
+create domain mynums2 as mynums;
+create or replace function array_elem_check(numeric) returns numeric as $$
+declare
+ x mynums2;
+begin
+ x[1] := $1;
+ return x[1];
+end$$ language plpgsql;
+select array_elem_check(121.00);
+ERROR: numeric field overflow
+DETAIL: A field with precision 4, scale 2 must round to an absolute value less than 10^2.
+CONTEXT: PL/pgSQL function array_elem_check(numeric) line 5 at assignment
+select array_elem_check(1.23456);
+ array_elem_check
+------------------
+ 1.23
+(1 row)
+
+drop function array_elem_check(numeric);
+--
+-- Check enforcement of array-level domain constraints
+--
+create domain orderedpair as int[2] check (value[1] < value[2]);
+select array[1,2]::orderedpair;
+ array
+-------
+ {1,2}
+(1 row)
+
+select array[2,1]::orderedpair; -- fail
+ERROR: value for domain orderedpair violates check constraint "orderedpair_check"
+create temp table op (f1 orderedpair);
+insert into op values (array[1,2]);
+insert into op values (array[2,1]); -- fail
+ERROR: value for domain orderedpair violates check constraint "orderedpair_check"
+update op set f1[2] = 3;
+update op set f1[2] = 0; -- fail
+ERROR: value for domain orderedpair violates check constraint "orderedpair_check"
+select * from op;
+ f1
+-------
+ {1,3}
+(1 row)
+
+create or replace function array_elem_check(int) returns int as $$
+declare
+ x orderedpair := '{1,2}';
+begin
+ x[2] := $1;
+ return x[2];
+end$$ language plpgsql;
+select array_elem_check(3);
+ array_elem_check
+------------------
+ 3
+(1 row)
+
+select array_elem_check(-1);
+ERROR: value for domain orderedpair violates check constraint "orderedpair_check"
+CONTEXT: PL/pgSQL function array_elem_check(integer) line 5 at assignment
+drop function array_elem_check(int);
+--
+-- Check enforcement of changing constraints in plpgsql
+--
+create domain di as int;
+create function dom_check(int) returns di as $$
+declare d di;
+begin
+ d := $1::di;
+ return d;
+end
+$$ language plpgsql immutable;
+select dom_check(0);
+ dom_check
+-----------
+ 0
+(1 row)
+
+alter domain di add constraint pos check (value > 0);
+select dom_check(0); -- fail
+ERROR: value for domain di violates check constraint "pos"
+CONTEXT: PL/pgSQL function dom_check(integer) line 4 at assignment
+alter domain di drop constraint pos;
+select dom_check(0);
+ dom_check
+-----------
+ 0
+(1 row)
+
+-- implicit cast during assignment is a separate code path, test that too
+create or replace function dom_check(int) returns di as $$
+declare d di;
+begin
+ d := $1;
+ return d;
+end
+$$ language plpgsql immutable;
+select dom_check(0);
+ dom_check
+-----------
+ 0
+(1 row)
+
+alter domain di add constraint pos check (value > 0);
+select dom_check(0); -- fail
+ERROR: value for domain di violates check constraint "pos"
+CONTEXT: PL/pgSQL function dom_check(integer) line 4 at assignment
+alter domain di drop constraint pos;
+select dom_check(0);
+ dom_check
+-----------
+ 0
+(1 row)
+
+drop function dom_check(int);
+drop domain di;
+--
+-- Check use of a (non-inline-able) SQL function in a domain constraint;
+-- this has caused issues in the past
+--
+create function sql_is_distinct_from(anyelement, anyelement)
+returns boolean language sql
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull int
+ check (sql_is_distinct_from(value, null));
+select 1::inotnull;
+ inotnull
+----------
+ 1
+(1 row)
+
+select null::inotnull;
+ERROR: value for domain inotnull violates check constraint "inotnull_check"
+create table dom_table (x inotnull);
+insert into dom_table values ('1');
+insert into dom_table values (1);
+insert into dom_table values (null);
+ERROR: value for domain inotnull violates check constraint "inotnull_check"
+drop table dom_table;
+drop domain inotnull;
+drop function sql_is_distinct_from(anyelement, anyelement);
+--
+-- Renaming
+--
+create domain testdomain1 as int;
+alter domain testdomain1 rename to testdomain2;
+alter type testdomain2 rename to testdomain3; -- alter type also works
+drop domain testdomain3;
+--
+-- Renaming domain constraints
+--
+create domain testdomain1 as int constraint unsigned check (value > 0);
+alter domain testdomain1 rename constraint unsigned to unsigned_foo;
+alter domain testdomain1 drop constraint unsigned_foo;
+drop domain testdomain1;
diff --git a/src/test/regress/expected/drop_if_exists.out b/src/test/regress/expected/drop_if_exists.out
new file mode 100644
index 0000000..5e44c2c
--- /dev/null
+++ b/src/test/regress/expected/drop_if_exists.out
@@ -0,0 +1,342 @@
+--
+-- IF EXISTS tests
+--
+-- table (will be really dropped at the end)
+DROP TABLE test_exists;
+ERROR: table "test_exists" does not exist
+DROP TABLE IF EXISTS test_exists;
+NOTICE: table "test_exists" does not exist, skipping
+CREATE TABLE test_exists (a int, b text);
+-- view
+DROP VIEW test_view_exists;
+ERROR: view "test_view_exists" does not exist
+DROP VIEW IF EXISTS test_view_exists;
+NOTICE: view "test_view_exists" does not exist, skipping
+CREATE VIEW test_view_exists AS select * from test_exists;
+DROP VIEW IF EXISTS test_view_exists;
+DROP VIEW test_view_exists;
+ERROR: view "test_view_exists" does not exist
+-- index
+DROP INDEX test_index_exists;
+ERROR: index "test_index_exists" does not exist
+DROP INDEX IF EXISTS test_index_exists;
+NOTICE: index "test_index_exists" does not exist, skipping
+CREATE INDEX test_index_exists on test_exists(a);
+DROP INDEX IF EXISTS test_index_exists;
+DROP INDEX test_index_exists;
+ERROR: index "test_index_exists" does not exist
+-- sequence
+DROP SEQUENCE test_sequence_exists;
+ERROR: sequence "test_sequence_exists" does not exist
+DROP SEQUENCE IF EXISTS test_sequence_exists;
+NOTICE: sequence "test_sequence_exists" does not exist, skipping
+CREATE SEQUENCE test_sequence_exists;
+DROP SEQUENCE IF EXISTS test_sequence_exists;
+DROP SEQUENCE test_sequence_exists;
+ERROR: sequence "test_sequence_exists" does not exist
+-- schema
+DROP SCHEMA test_schema_exists;
+ERROR: schema "test_schema_exists" does not exist
+DROP SCHEMA IF EXISTS test_schema_exists;
+NOTICE: schema "test_schema_exists" does not exist, skipping
+CREATE SCHEMA test_schema_exists;
+DROP SCHEMA IF EXISTS test_schema_exists;
+DROP SCHEMA test_schema_exists;
+ERROR: schema "test_schema_exists" does not exist
+-- type
+DROP TYPE test_type_exists;
+ERROR: type "test_type_exists" does not exist
+DROP TYPE IF EXISTS test_type_exists;
+NOTICE: type "test_type_exists" does not exist, skipping
+CREATE type test_type_exists as (a int, b text);
+DROP TYPE IF EXISTS test_type_exists;
+DROP TYPE test_type_exists;
+ERROR: type "test_type_exists" does not exist
+-- domain
+DROP DOMAIN test_domain_exists;
+ERROR: type "test_domain_exists" does not exist
+DROP DOMAIN IF EXISTS test_domain_exists;
+NOTICE: type "test_domain_exists" does not exist, skipping
+CREATE domain test_domain_exists as int not null check (value > 0);
+DROP DOMAIN IF EXISTS test_domain_exists;
+DROP DOMAIN test_domain_exists;
+ERROR: type "test_domain_exists" does not exist
+---
+--- role/user/group
+---
+CREATE USER regress_test_u1;
+CREATE ROLE regress_test_r1;
+CREATE GROUP regress_test_g1;
+DROP USER regress_test_u2;
+ERROR: role "regress_test_u2" does not exist
+DROP USER IF EXISTS regress_test_u1, regress_test_u2;
+NOTICE: role "regress_test_u2" does not exist, skipping
+DROP USER regress_test_u1;
+ERROR: role "regress_test_u1" does not exist
+DROP ROLE regress_test_r2;
+ERROR: role "regress_test_r2" does not exist
+DROP ROLE IF EXISTS regress_test_r1, regress_test_r2;
+NOTICE: role "regress_test_r2" does not exist, skipping
+DROP ROLE regress_test_r1;
+ERROR: role "regress_test_r1" does not exist
+DROP GROUP regress_test_g2;
+ERROR: role "regress_test_g2" does not exist
+DROP GROUP IF EXISTS regress_test_g1, regress_test_g2;
+NOTICE: role "regress_test_g2" does not exist, skipping
+DROP GROUP regress_test_g1;
+ERROR: role "regress_test_g1" does not exist
+-- collation
+DROP COLLATION IF EXISTS test_collation_exists;
+NOTICE: collation "test_collation_exists" does not exist, skipping
+-- conversion
+DROP CONVERSION test_conversion_exists;
+ERROR: conversion "test_conversion_exists" does not exist
+DROP CONVERSION IF EXISTS test_conversion_exists;
+NOTICE: conversion "test_conversion_exists" does not exist, skipping
+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;
+ERROR: text search parser "test_tsparser_exists" does not exist
+DROP TEXT SEARCH PARSER IF EXISTS test_tsparser_exists;
+NOTICE: text search parser "test_tsparser_exists" does not exist, skipping
+-- text search dictionary
+DROP TEXT SEARCH DICTIONARY test_tsdict_exists;
+ERROR: text search dictionary "test_tsdict_exists" does not exist
+DROP TEXT SEARCH DICTIONARY IF EXISTS test_tsdict_exists;
+NOTICE: text search dictionary "test_tsdict_exists" does not exist, skipping
+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;
+ERROR: text search template "test_tstemplate_exists" does not exist
+DROP TEXT SEARCH TEMPLATE IF EXISTS test_tstemplate_exists;
+NOTICE: text search template "test_tstemplate_exists" does not exist, skipping
+-- text search configuration
+DROP TEXT SEARCH CONFIGURATION test_tsconfig_exists;
+ERROR: text search configuration "test_tsconfig_exists" does not exist
+DROP TEXT SEARCH CONFIGURATION IF EXISTS test_tsconfig_exists;
+NOTICE: text search configuration "test_tsconfig_exists" does not exist, skipping
+CREATE TEXT SEARCH CONFIGURATION test_tsconfig_exists (COPY=english);
+DROP TEXT SEARCH CONFIGURATION test_tsconfig_exists;
+-- extension
+DROP EXTENSION test_extension_exists;
+ERROR: extension "test_extension_exists" does not exist
+DROP EXTENSION IF EXISTS test_extension_exists;
+NOTICE: extension "test_extension_exists" does not exist, skipping
+-- functions
+DROP FUNCTION test_function_exists();
+ERROR: function test_function_exists() does not exist
+DROP FUNCTION IF EXISTS test_function_exists();
+NOTICE: function test_function_exists() does not exist, skipping
+DROP FUNCTION test_function_exists(int, text, int[]);
+ERROR: function test_function_exists(integer, text, integer[]) does not exist
+DROP FUNCTION IF EXISTS test_function_exists(int, text, int[]);
+NOTICE: function test_function_exists(pg_catalog.int4,text,pg_catalog.int4[]) does not exist, skipping
+-- aggregate
+DROP AGGREGATE test_aggregate_exists(*);
+ERROR: aggregate test_aggregate_exists(*) does not exist
+DROP AGGREGATE IF EXISTS test_aggregate_exists(*);
+NOTICE: aggregate test_aggregate_exists() does not exist, skipping
+DROP AGGREGATE test_aggregate_exists(int);
+ERROR: aggregate test_aggregate_exists(integer) does not exist
+DROP AGGREGATE IF EXISTS test_aggregate_exists(int);
+NOTICE: aggregate test_aggregate_exists(pg_catalog.int4) does not exist, skipping
+-- operator
+DROP OPERATOR @#@ (int, int);
+ERROR: operator does not exist: integer @#@ integer
+DROP OPERATOR IF EXISTS @#@ (int, int);
+NOTICE: operator @#@ does not exist, skipping
+CREATE OPERATOR @#@
+ (leftarg = int8, rightarg = int8, procedure = int8xor);
+DROP OPERATOR @#@ (int8, int8);
+-- language
+DROP LANGUAGE test_language_exists;
+ERROR: language "test_language_exists" does not exist
+DROP LANGUAGE IF EXISTS test_language_exists;
+NOTICE: language "test_language_exists" does not exist, skipping
+-- cast
+DROP CAST (text AS text);
+ERROR: cast from type text to type text does not exist
+DROP CAST IF EXISTS (text AS text);
+NOTICE: cast from type text to type text does not exist, skipping
+-- trigger
+DROP TRIGGER test_trigger_exists ON test_exists;
+ERROR: trigger "test_trigger_exists" for table "test_exists" does not exist
+DROP TRIGGER IF EXISTS test_trigger_exists ON test_exists;
+NOTICE: trigger "test_trigger_exists" for relation "test_exists" does not exist, skipping
+DROP TRIGGER test_trigger_exists ON no_such_table;
+ERROR: relation "no_such_table" does not exist
+DROP TRIGGER IF EXISTS test_trigger_exists ON no_such_table;
+NOTICE: relation "no_such_table" does not exist, skipping
+DROP TRIGGER test_trigger_exists ON no_such_schema.no_such_table;
+ERROR: schema "no_such_schema" does not exist
+DROP TRIGGER IF EXISTS test_trigger_exists ON no_such_schema.no_such_table;
+NOTICE: schema "no_such_schema" does not exist, skipping
+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;
+ERROR: rule "test_rule_exists" for relation "test_exists" does not exist
+DROP RULE IF EXISTS test_rule_exists ON test_exists;
+NOTICE: rule "test_rule_exists" for relation "test_exists" does not exist, skipping
+DROP RULE test_rule_exists ON no_such_table;
+ERROR: relation "no_such_table" does not exist
+DROP RULE IF EXISTS test_rule_exists ON no_such_table;
+NOTICE: relation "no_such_table" does not exist, skipping
+DROP RULE test_rule_exists ON no_such_schema.no_such_table;
+ERROR: schema "no_such_schema" does not exist
+DROP RULE IF EXISTS test_rule_exists ON no_such_schema.no_such_table;
+NOTICE: schema "no_such_schema" does not exist, skipping
+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;
+ERROR: foreign-data wrapper "test_fdw_exists" does not exist
+DROP FOREIGN DATA WRAPPER IF EXISTS test_fdw_exists;
+NOTICE: foreign-data wrapper "test_fdw_exists" does not exist, skipping
+-- foreign server
+DROP SERVER test_server_exists;
+ERROR: server "test_server_exists" does not exist
+DROP SERVER IF EXISTS test_server_exists;
+NOTICE: server "test_server_exists" does not exist, skipping
+-- operator class
+DROP OPERATOR CLASS test_operator_class USING btree;
+ERROR: operator class "test_operator_class" does not exist for access method "btree"
+DROP OPERATOR CLASS IF EXISTS test_operator_class USING btree;
+NOTICE: operator class "test_operator_class" does not exist for access method "btree", skipping
+DROP OPERATOR CLASS test_operator_class USING no_such_am;
+ERROR: access method "no_such_am" does not exist
+DROP OPERATOR CLASS IF EXISTS test_operator_class USING no_such_am;
+ERROR: access method "no_such_am" does not exist
+-- operator family
+DROP OPERATOR FAMILY test_operator_family USING btree;
+ERROR: operator family "test_operator_family" does not exist for access method "btree"
+DROP OPERATOR FAMILY IF EXISTS test_operator_family USING btree;
+NOTICE: operator family "test_operator_family" does not exist for access method "btree", skipping
+DROP OPERATOR FAMILY test_operator_family USING no_such_am;
+ERROR: access method "no_such_am" does not exist
+DROP OPERATOR FAMILY IF EXISTS test_operator_family USING no_such_am;
+ERROR: access method "no_such_am" does not exist
+-- access method
+DROP ACCESS METHOD no_such_am;
+ERROR: access method "no_such_am" does not exist
+DROP ACCESS METHOD IF EXISTS no_such_am;
+NOTICE: access method "no_such_am" does not exist, skipping
+-- drop the table
+DROP TABLE IF EXISTS test_exists;
+DROP TABLE test_exists;
+ERROR: table "test_exists" does not exist
+-- be tolerant with missing schemas, types, etc
+DROP AGGREGATE IF EXISTS no_such_schema.foo(int);
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP AGGREGATE IF EXISTS foo(no_such_type);
+NOTICE: type "no_such_type" does not exist, skipping
+DROP AGGREGATE IF EXISTS foo(no_such_schema.no_such_type);
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP CAST IF EXISTS (INTEGER AS no_such_type2);
+NOTICE: type "no_such_type2" does not exist, skipping
+DROP CAST IF EXISTS (no_such_type1 AS INTEGER);
+NOTICE: type "no_such_type1" does not exist, skipping
+DROP CAST IF EXISTS (INTEGER AS no_such_schema.bar);
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP CAST IF EXISTS (no_such_schema.foo AS INTEGER);
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP COLLATION IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP CONVERSION IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP DOMAIN IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP FOREIGN TABLE IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP FUNCTION IF EXISTS no_such_schema.foo();
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP FUNCTION IF EXISTS foo(no_such_type);
+NOTICE: type "no_such_type" does not exist, skipping
+DROP FUNCTION IF EXISTS foo(no_such_schema.no_such_type);
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP INDEX IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP MATERIALIZED VIEW IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP OPERATOR IF EXISTS no_such_schema.+ (int, int);
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP OPERATOR IF EXISTS + (no_such_type, no_such_type);
+NOTICE: type "no_such_type" does not exist, skipping
+DROP OPERATOR IF EXISTS + (no_such_schema.no_such_type, no_such_schema.no_such_type);
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP OPERATOR IF EXISTS # (NONE, no_such_schema.no_such_type);
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP OPERATOR CLASS IF EXISTS no_such_schema.widget_ops USING btree;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP OPERATOR FAMILY IF EXISTS no_such_schema.float_ops USING btree;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP RULE IF EXISTS foo ON no_such_schema.bar;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP SEQUENCE IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP TABLE IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP TEXT SEARCH CONFIGURATION IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP TEXT SEARCH DICTIONARY IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP TEXT SEARCH PARSER IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP TEXT SEARCH TEMPLATE IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP TRIGGER IF EXISTS foo ON no_such_schema.bar;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP TYPE IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+DROP VIEW IF EXISTS no_such_schema.foo;
+NOTICE: schema "no_such_schema" does not exist, skipping
+-- 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;
+ERROR: function name "test_ambiguous_funcname" is not unique
+HINT: Specify the argument list to select the function unambiguously.
+DROP FUNCTION IF EXISTS test_ambiguous_funcname;
+ERROR: function name "test_ambiguous_funcname" is not unique
+HINT: Specify the argument list to select the function unambiguously.
+-- 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;
+ERROR: procedure name "test_ambiguous_procname" is not unique
+HINT: Specify the argument list to select the procedure unambiguously.
+DROP PROCEDURE IF EXISTS test_ambiguous_procname;
+ERROR: procedure name "test_ambiguous_procname" is not unique
+HINT: Specify the argument list to select the procedure unambiguously.
+-- Check we get a similar error if we use ROUTINE instead of PROCEDURE.
+DROP ROUTINE IF EXISTS test_ambiguous_procname;
+ERROR: routine name "test_ambiguous_procname" is not unique
+HINT: Specify the argument list to select the routine unambiguously.
+-- 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);
+ERROR: database "test_database_exists" does not exist
+drop database test_database_exists with (force);
+ERROR: database "test_database_exists" does not exist
+drop database if exists test_database_exists (force);
+NOTICE: database "test_database_exists" does not exist, skipping
+drop database if exists test_database_exists with (force);
+NOTICE: database "test_database_exists" does not exist, skipping
diff --git a/src/test/regress/expected/drop_operator.out b/src/test/regress/expected/drop_operator.out
new file mode 100644
index 0000000..cc8f5e7
--- /dev/null
+++ b/src/test/regress/expected/drop_operator.out
@@ -0,0 +1,61 @@
+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);
+ ctid | oprcom
+------+--------
+(0 rows)
+
+SELECT ctid, oprnegate
+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);
+ ctid | oprnegate
+------+-----------
+(0 rows)
+
+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 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);
+ ctid | oprcom
+------+--------
+(0 rows)
+
+SELECT ctid, oprnegate
+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);
+ ctid | oprnegate
+------+-----------
+(0 rows)
+
+DROP OPERATOR <|(bigint, bigint);
diff --git a/src/test/regress/expected/enum.out b/src/test/regress/expected/enum.out
new file mode 100644
index 0000000..908f67e
--- /dev/null
+++ b/src/test/regress/expected/enum.out
@@ -0,0 +1,691 @@
+--
+-- 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;
+ count
+-------
+ 6
+(1 row)
+
+--
+-- I/O functions
+--
+SELECT 'red'::rainbow;
+ rainbow
+---------
+ red
+(1 row)
+
+SELECT 'mauve'::rainbow;
+ERROR: invalid input value for enum rainbow: "mauve"
+LINE 1: 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;
+ enumlabel | enumsortorder
+-----------+---------------
+ venus | 1
+ earth | 2
+ mars | 3
+(3 rows)
+
+ALTER TYPE planets ADD VALUE 'uranus';
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'planets'::regtype
+ORDER BY 2;
+ enumlabel | enumsortorder
+-----------+---------------
+ venus | 1
+ earth | 2
+ mars | 3
+ uranus | 4
+(4 rows)
+
+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;
+ enumlabel | enumsortorder
+-----------+---------------
+ mercury | 0
+ venus | 1
+ earth | 2
+ mars | 3
+ jupiter | 3.25
+ saturn | 3.5
+ uranus | 4
+ neptune | 5
+(8 rows)
+
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'planets'::regtype
+ORDER BY enumlabel::planets;
+ enumlabel | enumsortorder
+-----------+---------------
+ mercury | 0
+ venus | 1
+ earth | 2
+ mars | 3
+ jupiter | 3.25
+ saturn | 3.5
+ uranus | 4
+ neptune | 5
+(8 rows)
+
+-- errors for adding labels
+ALTER TYPE planets ADD VALUE
+ 'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
+ERROR: invalid enum label "plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto"
+DETAIL: Labels must be 63 bytes or less.
+ALTER TYPE planets ADD VALUE 'pluto' AFTER 'zeus';
+ERROR: "zeus" is not an existing enum label
+-- if not exists tests
+-- existing value gives error
+ALTER TYPE planets ADD VALUE 'mercury';
+ERROR: enum label "mercury" already exists
+-- unless IF NOT EXISTS is specified
+ALTER TYPE planets ADD VALUE IF NOT EXISTS 'mercury';
+NOTICE: enum label "mercury" already exists, skipping
+-- should be neptune, not mercury
+SELECT enum_last(NULL::planets);
+ enum_last
+-----------
+ neptune
+(1 row)
+
+ALTER TYPE planets ADD VALUE IF NOT EXISTS 'pluto';
+-- should be pluto, i.e. the new value
+SELECT enum_last(NULL::planets);
+ enum_last
+-----------
+ pluto
+(1 row)
+
+--
+-- Test inserting so many values that we have to renumber
+--
+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 enumsortorder end as so
+FROM pg_enum
+WHERE enumtypid = 'insenum'::regtype
+ORDER BY enumsortorder;
+ enumlabel | so
+-----------+----
+ L1 | 1
+ i1 | 2
+ i2 | 3
+ i3 | 4
+ i4 | 5
+ i5 | 6
+ i6 | 7
+ i7 | 8
+ i8 | 9
+ i9 | 10
+ i10 | 11
+ i11 | 12
+ i12 | 13
+ i13 | 14
+ i14 | 15
+ i15 | 16
+ i16 | 17
+ i17 | 18
+ i18 | 19
+ i19 | 20
+ i20 |
+ i21 |
+ i22 |
+ i23 |
+ i24 |
+ i25 |
+ i26 |
+ i27 |
+ i28 |
+ i29 |
+ i30 |
+ L2 |
+(32 rows)
+
+--
+-- Basic table creation, row selection
+--
+CREATE TABLE enumtest (col rainbow);
+INSERT INTO enumtest values ('red'), ('orange'), ('yellow'), ('green');
+COPY enumtest FROM stdin;
+SELECT * FROM enumtest;
+ col
+--------
+ red
+ orange
+ yellow
+ green
+ blue
+ purple
+(6 rows)
+
+--
+-- Operators, no index
+--
+SELECT * FROM enumtest WHERE col = 'orange';
+ col
+--------
+ orange
+(1 row)
+
+SELECT * FROM enumtest WHERE col <> 'orange' ORDER BY col;
+ col
+--------
+ red
+ yellow
+ green
+ blue
+ purple
+(5 rows)
+
+SELECT * FROM enumtest WHERE col > 'yellow' ORDER BY col;
+ col
+--------
+ green
+ blue
+ purple
+(3 rows)
+
+SELECT * FROM enumtest WHERE col >= 'yellow' ORDER BY col;
+ col
+--------
+ yellow
+ green
+ blue
+ purple
+(4 rows)
+
+SELECT * FROM enumtest WHERE col < 'green' ORDER BY col;
+ col
+--------
+ red
+ orange
+ yellow
+(3 rows)
+
+SELECT * FROM enumtest WHERE col <= 'green' ORDER BY col;
+ col
+--------
+ red
+ orange
+ yellow
+ green
+(4 rows)
+
+--
+-- Cast to/from text
+--
+SELECT 'red'::rainbow::text || 'hithere';
+ ?column?
+------------
+ redhithere
+(1 row)
+
+SELECT 'red'::text::rainbow = 'red'::rainbow;
+ ?column?
+----------
+ t
+(1 row)
+
+--
+-- Aggregates
+--
+SELECT min(col) FROM enumtest;
+ min
+-----
+ red
+(1 row)
+
+SELECT max(col) FROM enumtest;
+ max
+--------
+ purple
+(1 row)
+
+SELECT max(col) FROM enumtest WHERE col < 'green';
+ max
+--------
+ yellow
+(1 row)
+
+--
+-- 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';
+ col
+--------
+ orange
+(1 row)
+
+SELECT * FROM enumtest WHERE col <> 'orange' ORDER BY col;
+ col
+--------
+ red
+ yellow
+ green
+ blue
+ purple
+(5 rows)
+
+SELECT * FROM enumtest WHERE col > 'yellow' ORDER BY col;
+ col
+--------
+ green
+ blue
+ purple
+(3 rows)
+
+SELECT * FROM enumtest WHERE col >= 'yellow' ORDER BY col;
+ col
+--------
+ yellow
+ green
+ blue
+ purple
+(4 rows)
+
+SELECT * FROM enumtest WHERE col < 'green' ORDER BY col;
+ col
+--------
+ red
+ orange
+ yellow
+(3 rows)
+
+SELECT * FROM enumtest WHERE col <= 'green' ORDER BY col;
+ col
+--------
+ red
+ orange
+ yellow
+ green
+(4 rows)
+
+SELECT min(col) FROM enumtest;
+ min
+-----
+ red
+(1 row)
+
+SELECT max(col) FROM enumtest;
+ max
+--------
+ purple
+(1 row)
+
+SELECT max(col) FROM enumtest WHERE col < 'green';
+ max
+--------
+ yellow
+(1 row)
+
+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';
+ col
+--------
+ orange
+(1 row)
+
+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;
+ rgb
+-----
+ red
+(1 row)
+
+SELECT 'purple'::rgb;
+ERROR: value for domain rgb violates check constraint "rgb_check"
+SELECT 'purple'::rainbow::rgb;
+ERROR: value for domain rgb violates check constraint "rgb_check"
+DROP DOMAIN rgb;
+--
+-- Arrays
+--
+SELECT '{red,green,blue}'::rainbow[];
+ rainbow
+------------------
+ {red,green,blue}
+(1 row)
+
+SELECT ('{red,green,blue}'::rainbow[])[2];
+ rainbow
+---------
+ green
+(1 row)
+
+SELECT 'red' = ANY ('{red,green,blue}'::rainbow[]);
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT 'yellow' = ANY ('{red,green,blue}'::rainbow[]);
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT 'red' = ALL ('{red,green,blue}'::rainbow[]);
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT 'red' = ALL ('{red,red}'::rainbow[]);
+ ?column?
+----------
+ t
+(1 row)
+
+--
+-- Support functions
+--
+SELECT enum_first(NULL::rainbow);
+ enum_first
+------------
+ red
+(1 row)
+
+SELECT enum_last('green'::rainbow);
+ enum_last
+-----------
+ purple
+(1 row)
+
+SELECT enum_range(NULL::rainbow);
+ enum_range
+---------------------------------------
+ {red,orange,yellow,green,blue,purple}
+(1 row)
+
+SELECT enum_range('orange'::rainbow, 'green'::rainbow);
+ enum_range
+-----------------------
+ {orange,yellow,green}
+(1 row)
+
+SELECT enum_range(NULL, 'green'::rainbow);
+ enum_range
+---------------------------
+ {red,orange,yellow,green}
+(1 row)
+
+SELECT enum_range('orange'::rainbow, NULL);
+ enum_range
+-----------------------------------
+ {orange,yellow,green,blue,purple}
+(1 row)
+
+SELECT enum_range(NULL::rainbow, NULL);
+ enum_range
+---------------------------------------
+ {red,orange,yellow,green,blue,purple}
+(1 row)
+
+--
+-- 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);
+ echo_me
+---------
+ redomg
+(1 row)
+
+--
+-- 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);
+ echo_me
+---------
+ redwtf
+(1 row)
+
+--
+-- 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');
+ echo_me
+---------
+ redwtf
+(1 row)
+
+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
+ERROR: insert or update on table "enumtest_child" violates foreign key constraint "enumtest_child_parent_fkey"
+DETAIL: Key (parent)=(blue) is not present in table "enumtest_parent".
+DELETE FROM enumtest_parent; -- fail
+ERROR: update or delete on table "enumtest_parent" violates foreign key constraint "enumtest_child_parent_fkey" on table "enumtest_child"
+DETAIL: Key (id)=(red) is still referenced from table "enumtest_child".
+--
+-- cross-type RI should fail
+--
+CREATE TYPE bogus AS ENUM('good', 'bad', 'ugly');
+CREATE TABLE enumtest_bogus_child(parent bogus REFERENCES enumtest_parent);
+ERROR: foreign key constraint "enumtest_bogus_child_parent_fkey" cannot be implemented
+DETAIL: Key columns "parent" and "id" are of incompatible types: bogus and rainbow.
+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;
+ enumlabel | enumsortorder
+-----------+---------------
+ crimson | 1
+ orange | 2
+ yellow | 3
+ green | 4
+ blue | 5
+ purple | 6
+(6 rows)
+
+-- check that renaming a non-existent value fails
+ALTER TYPE rainbow RENAME VALUE 'red' TO 'crimson';
+ERROR: "red" is not an existing enum label
+-- check that renaming to an existent value fails
+ALTER TYPE rainbow RENAME VALUE 'blue' TO 'green';
+ERROR: enum label "green" already exists
+--
+-- 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
+ERROR: unsafe use of new value "new" of enum type bogus
+LINE 1: SELECT 'new'::bogus;
+ ^
+HINT: New enum values must be committed before they can be used.
+ROLLBACK TO x;
+SELECT enum_first(null::bogus); -- safe
+ enum_first
+------------
+ good
+(1 row)
+
+SELECT enum_last(null::bogus); -- unsafe
+ERROR: unsafe use of new value "new" of enum type bogus
+HINT: New enum values must be committed before they can be used.
+ROLLBACK TO x;
+SELECT enum_range(null::bogus); -- unsafe
+ERROR: unsafe use of new value "new" of enum type bogus
+HINT: New enum values must be committed before they can be used.
+ROLLBACK TO x;
+COMMIT;
+SELECT 'new'::bogus; -- now safe
+ bogus
+-------
+ new
+(1 row)
+
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'bogus'::regtype
+ORDER BY 2;
+ enumlabel | enumsortorder
+-----------+---------------
+ good | 1
+ new | 2
+(2 rows)
+
+-- 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;
+ERROR: unsafe use of new value "bad" of enum type bogon
+LINE 1: SELECT 'bad'::bogon;
+ ^
+HINT: New enum values must be committed before they can be used.
+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;
+ bogus
+-------
+ bad
+(1 row)
+
+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);
+ enum_range
+-----------------
+ {good,bad,ugly}
+(1 row)
+
+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
+ERROR: unsafe use of new value "bad" of enum type bogon
+HINT: New enum values must be committed before they can be used.
+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';
+ count
+-------
+ 0
+(1 row)
+
+SELECT * FROM pg_enum WHERE NOT EXISTS
+ (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid);
+ oid | enumtypid | enumsortorder | enumlabel
+-----+-----------+---------------+-----------
+(0 rows)
+
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
new file mode 100644
index 0000000..126f704
--- /dev/null
+++ b/src/test/regress/expected/equivclass.out
@@ -0,0 +1,453 @@
+--
+-- Tests for the planner's "equivalence class" mechanism
+--
+-- One thing that's not tested well during normal querying is the logic
+-- for handling "broken" ECs. This is because an EC can only become broken
+-- if its underlying btree operator family doesn't include a complete set
+-- of cross-type equality operators. There are not (and should not be)
+-- any such families built into Postgres; so we have to hack things up
+-- to create one. We do this by making two alias types that are really
+-- int8 (so we need no new C code) and adding only some operators for them
+-- into the standard integer_ops opfamily.
+create type int8alias1;
+create function int8alias1in(cstring) returns int8alias1
+ strict immutable language internal as 'int8in';
+NOTICE: return type int8alias1 is only a shell
+create function int8alias1out(int8alias1) returns cstring
+ strict immutable language internal as 'int8out';
+NOTICE: argument type int8alias1 is only a shell
+create type int8alias1 (
+ input = int8alias1in,
+ output = int8alias1out,
+ like = int8
+);
+create type int8alias2;
+create function int8alias2in(cstring) returns int8alias2
+ strict immutable language internal as 'int8in';
+NOTICE: return type int8alias2 is only a shell
+create function int8alias2out(int8alias2) returns cstring
+ strict immutable language internal as 'int8out';
+NOTICE: argument type int8alias2 is only a shell
+create type int8alias2 (
+ input = int8alias2in,
+ output = int8alias2out,
+ like = int8
+);
+create cast (int8 as int8alias1) without function;
+create cast (int8 as int8alias2) without function;
+create cast (int8alias1 as int8) without function;
+create cast (int8alias2 as int8) without function;
+create function int8alias1eq(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias1,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias1);
+create function int8alias2eq(int8alias2, int8alias2) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias2eq,
+ leftarg = int8alias2, rightarg = int8alias2,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias2, int8alias2);
+create function int8alias1eq(int8, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8, rightarg = int8alias1,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8, int8alias1);
+create function int8alias1eq(int8alias1, int8alias2) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias2,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias2);
+create function int8alias1lt(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8lt';
+create operator < (
+ procedure = int8alias1lt,
+ leftarg = int8alias1, rightarg = int8alias1
+);
+alter operator family integer_ops using btree add
+ operator 1 < (int8alias1, int8alias1);
+create function int8alias1cmp(int8, int8alias1) returns int
+ strict immutable language internal as 'btint8cmp';
+alter operator family integer_ops using btree add
+ function 1 int8alias1cmp (int8, int8alias1);
+create table ec0 (ff int8 primary key, f1 int8, f2 int8);
+create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
+create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
+-- for the moment we only want to look at nestloop plans
+set enable_hashjoin = off;
+set enable_mergejoin = off;
+--
+-- Note that for cases where there's a missing operator, we don't care so
+-- much whether the plan is ideal as that we don't fail or generate an
+-- outright incorrect plan.
+--
+explain (costs off)
+ select * from ec0 where ff = f1 and f1 = '42'::int8;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using ec0_pkey on ec0
+ Index Cond: (ff = '42'::bigint)
+ Filter: (f1 = '42'::bigint)
+(3 rows)
+
+explain (costs off)
+ select * from ec0 where ff = f1 and f1 = '42'::int8alias1;
+ QUERY PLAN
+---------------------------------------
+ Index Scan using ec0_pkey on ec0
+ Index Cond: (ff = '42'::int8alias1)
+ Filter: (f1 = '42'::int8alias1)
+(3 rows)
+
+explain (costs off)
+ select * from ec1 where ff = f1 and f1 = '42'::int8alias1;
+ QUERY PLAN
+---------------------------------------
+ Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::int8alias1)
+ Filter: (f1 = '42'::int8alias1)
+(3 rows)
+
+explain (costs off)
+ select * from ec1 where ff = f1 and f1 = '42'::int8alias2;
+ QUERY PLAN
+---------------------------------------------------
+ Seq Scan on ec1
+ Filter: ((ff = f1) AND (f1 = '42'::int8alias2))
+(2 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and ff = '42'::int8;
+ QUERY PLAN
+-------------------------------------------------------------------
+ Nested Loop
+ Join Filter: (ec1.ff = ec2.x1)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: ((ff = '42'::bigint) AND (ff = '42'::bigint))
+ -> Seq Scan on ec2
+(5 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and ff = '42'::int8alias1;
+ QUERY PLAN
+---------------------------------------------
+ Nested Loop
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::int8alias1)
+ -> Seq Scan on ec2
+ Filter: (x1 = '42'::int8alias1)
+(5 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and '42'::int8 = x1;
+ QUERY PLAN
+-----------------------------------------
+ Nested Loop
+ Join Filter: (ec1.ff = ec2.x1)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::bigint)
+ -> Seq Scan on ec2
+ Filter: ('42'::bigint = x1)
+(6 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias1;
+ QUERY PLAN
+---------------------------------------------
+ Nested Loop
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::int8alias1)
+ -> Seq Scan on ec2
+ Filter: (x1 = '42'::int8alias1)
+(5 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias2;
+ QUERY PLAN
+-----------------------------------------
+ Nested Loop
+ -> Seq Scan on ec2
+ Filter: (x1 = '42'::int8alias2)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = ec2.x1)
+(5 rows)
+
+create unique index ec1_expr1 on ec1((ff + 1));
+create unique index ec1_expr2 on ec1((ff + 2 + 1));
+create unique index ec1_expr3 on ec1((ff + 3 + 1));
+create unique index ec1_expr4 on ec1((ff + 4));
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+ QUERY PLAN
+-----------------------------------------------------
+ Nested Loop
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::bigint)
+ -> Append
+ -> Index Scan using ec1_expr2 on ec1 ec1_1
+ Index Cond: (((ff + 2) + 1) = ec1.f1)
+ -> Index Scan using ec1_expr3 on ec1 ec1_2
+ Index Cond: (((ff + 3) + 1) = ec1.f1)
+ -> Index Scan using ec1_expr4 on ec1 ec1_3
+ Index Cond: ((ff + 4) = ec1.f1)
+(10 rows)
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8 and ec1.ff = ec1.f1;
+ QUERY PLAN
+-------------------------------------------------------------------
+ Nested Loop
+ Join Filter: ((((ec1_1.ff + 2) + 1)) = ec1.f1)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: ((ff = '42'::bigint) AND (ff = '42'::bigint))
+ Filter: (ff = f1)
+ -> Append
+ -> Index Scan using ec1_expr2 on ec1 ec1_1
+ Index Cond: (((ff + 2) + 1) = '42'::bigint)
+ -> Index Scan using ec1_expr3 on ec1 ec1_2
+ Index Cond: (((ff + 3) + 1) = '42'::bigint)
+ -> Index Scan using ec1_expr4 on ec1 ec1_3
+ Index Cond: ((ff + 4) = '42'::bigint)
+(12 rows)
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss2
+ where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Nested Loop
+ -> Nested Loop
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::bigint)
+ -> Append
+ -> Index Scan using ec1_expr2 on ec1 ec1_1
+ Index Cond: (((ff + 2) + 1) = ec1.f1)
+ -> Index Scan using ec1_expr3 on ec1 ec1_2
+ Index Cond: (((ff + 3) + 1) = ec1.f1)
+ -> Index Scan using ec1_expr4 on ec1 ec1_3
+ Index Cond: ((ff + 4) = ec1.f1)
+ -> Append
+ -> Index Scan using ec1_expr2 on ec1 ec1_4
+ Index Cond: (((ff + 2) + 1) = (((ec1_1.ff + 2) + 1)))
+ -> Index Scan using ec1_expr3 on ec1 ec1_5
+ Index Cond: (((ff + 3) + 1) = (((ec1_1.ff + 2) + 1)))
+ -> Index Scan using ec1_expr4 on ec1 ec1_6
+ Index Cond: ((ff + 4) = (((ec1_1.ff + 2) + 1)))
+(18 rows)
+
+-- let's try that as a mergejoin
+set enable_mergejoin = on;
+set enable_nestloop = off;
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss2
+ where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+ QUERY PLAN
+-----------------------------------------------------------------
+ Merge Join
+ Merge Cond: ((((ec1_4.ff + 2) + 1)) = (((ec1_1.ff + 2) + 1)))
+ -> Merge Append
+ Sort Key: (((ec1_4.ff + 2) + 1))
+ -> Index Scan using ec1_expr2 on ec1 ec1_4
+ -> Index Scan using ec1_expr3 on ec1 ec1_5
+ -> Index Scan using ec1_expr4 on ec1 ec1_6
+ -> Materialize
+ -> Merge Join
+ Merge Cond: ((((ec1_1.ff + 2) + 1)) = ec1.f1)
+ -> Merge Append
+ Sort Key: (((ec1_1.ff + 2) + 1))
+ -> Index Scan using ec1_expr2 on ec1 ec1_1
+ -> Index Scan using ec1_expr3 on ec1 ec1_2
+ -> Index Scan using ec1_expr4 on ec1 ec1_3
+ -> Sort
+ Sort Key: ec1.f1 USING <
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::bigint)
+(19 rows)
+
+-- check partially indexed scan
+set enable_nestloop = on;
+set enable_mergejoin = off;
+drop index ec1_expr3;
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+ QUERY PLAN
+-----------------------------------------------------
+ Nested Loop
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::bigint)
+ -> Append
+ -> Index Scan using ec1_expr2 on ec1 ec1_1
+ Index Cond: (((ff + 2) + 1) = ec1.f1)
+ -> Seq Scan on ec1 ec1_2
+ Filter: (((ff + 3) + 1) = ec1.f1)
+ -> Index Scan using ec1_expr4 on ec1 ec1_3
+ Index Cond: ((ff + 4) = ec1.f1)
+(10 rows)
+
+-- let's try that as a mergejoin
+set enable_mergejoin = on;
+set enable_nestloop = off;
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+ QUERY PLAN
+-----------------------------------------------------
+ Merge Join
+ Merge Cond: ((((ec1_1.ff + 2) + 1)) = ec1.f1)
+ -> Merge Append
+ Sort Key: (((ec1_1.ff + 2) + 1))
+ -> Index Scan using ec1_expr2 on ec1 ec1_1
+ -> Sort
+ Sort Key: (((ec1_2.ff + 3) + 1))
+ -> Seq Scan on ec1 ec1_2
+ -> Index Scan using ec1_expr4 on ec1 ec1_3
+ -> Sort
+ Sort Key: ec1.f1 USING <
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::bigint)
+(13 rows)
+
+-- check effects of row-level security
+set enable_nestloop = on;
+set enable_mergejoin = off;
+alter table ec1 enable row level security;
+create policy p1 on ec1 using (f1 < '5'::int8alias1);
+create user regress_user_ectest;
+grant select on ec0 to regress_user_ectest;
+grant select on ec1 to regress_user_ectest;
+-- without any RLS, we'll treat {a.ff, b.ff, 43} as an EquivalenceClass
+explain (costs off)
+ select * from ec0 a, ec1 b
+ where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
+ QUERY PLAN
+---------------------------------------------
+ Nested Loop
+ -> Index Scan using ec0_pkey on ec0 a
+ Index Cond: (ff = '43'::int8alias1)
+ -> Index Scan using ec1_pkey on ec1 b
+ Index Cond: (ff = '43'::int8alias1)
+(5 rows)
+
+set session authorization regress_user_ectest;
+-- with RLS active, the non-leakproof a.ff = 43 clause is not treated
+-- as a suitable source for an EquivalenceClass; currently, this is true
+-- even though the RLS clause has nothing to do directly with the EC
+explain (costs off)
+ select * from ec0 a, ec1 b
+ where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
+ QUERY PLAN
+---------------------------------------------
+ Nested Loop
+ -> Index Scan using ec0_pkey on ec0 a
+ Index Cond: (ff = '43'::int8alias1)
+ -> Index Scan using ec1_pkey on ec1 b
+ Index Cond: (ff = a.ff)
+ Filter: (f1 < '5'::int8alias1)
+(6 rows)
+
+reset session authorization;
+revoke select on ec0 from regress_user_ectest;
+revoke select on ec1 from regress_user_ectest;
+drop user regress_user_ectest;
+-- check that X=X is converted to X IS NOT NULL when appropriate
+explain (costs off)
+ select * from tenk1 where unique1 = unique1 and unique2 = unique2;
+ QUERY PLAN
+-------------------------------------------------------------
+ Seq Scan on tenk1
+ Filter: ((unique1 IS NOT NULL) AND (unique2 IS NOT NULL))
+(2 rows)
+
+-- this could be converted, but isn't at present
+explain (costs off)
+ select * from tenk1 where unique1 = unique1 or unique2 = unique2;
+ QUERY PLAN
+--------------------------------------------------------
+ Seq Scan on tenk1
+ Filter: ((unique1 = unique1) OR (unique2 = unique2))
+(2 rows)
+
+-- check that we recognize equivalence with dummy domains in the way
+create temp table undername (f1 name, f2 int);
+create temp view overview as
+ select f1::information_schema.sql_identifier as sqli, f2 from undername;
+explain (costs off) -- this should not require a sort
+ select * from overview where sqli = 'foo' order by sqli;
+ QUERY PLAN
+------------------------------
+ Seq Scan on undername
+ Filter: (f1 = 'foo'::name)
+(2 rows)
+
diff --git a/src/test/regress/expected/errors.out b/src/test/regress/expected/errors.out
new file mode 100644
index 0000000..15862d4
--- /dev/null
+++ b/src/test/regress/expected/errors.out
@@ -0,0 +1,447 @@
+--
+-- ERRORS
+--
+-- bad in postquel, but ok in PostgreSQL
+select 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+--
+-- 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;
+--
+(1 row)
+
+-- no such relation
+select * from nonesuch;
+ERROR: relation "nonesuch" does not exist
+LINE 1: select * from nonesuch;
+ ^
+-- bad name in target list
+select nonesuch from pg_database;
+ERROR: column "nonesuch" does not exist
+LINE 1: select nonesuch from pg_database;
+ ^
+-- empty distinct list isn't OK
+select distinct from pg_database;
+ERROR: syntax error at or near "from"
+LINE 1: select distinct from pg_database;
+ ^
+-- bad attribute name on lhs of operator
+select * from pg_database where nonesuch = pg_database.datname;
+ERROR: column "nonesuch" does not exist
+LINE 1: select * from pg_database where nonesuch = pg_database.datna...
+ ^
+-- bad attribute name on rhs of operator
+select * from pg_database where pg_database.datname = nonesuch;
+ERROR: column "nonesuch" does not exist
+LINE 1: ...ect * from pg_database where pg_database.datname = nonesuch;
+ ^
+-- bad attribute name in select distinct on
+select distinct on (foobar) * from pg_database;
+ERROR: column "foobar" does not exist
+LINE 1: select distinct on (foobar) * from pg_database;
+ ^
+-- grouping with FOR UPDATE
+select null from pg_database group by datname for update;
+ERROR: FOR UPDATE is not allowed with GROUP BY clause
+select null from pg_database group by grouping sets (()) for update;
+ERROR: FOR UPDATE is not allowed with GROUP BY clause
+--
+-- DELETE
+-- missing relation name (this had better not wildcard!)
+delete from;
+ERROR: syntax error at or near ";"
+LINE 1: delete from;
+ ^
+-- no such relation
+delete from nonesuch;
+ERROR: relation "nonesuch" does not exist
+LINE 1: delete from nonesuch;
+ ^
+--
+-- DROP
+-- missing relation name (this had better not wildcard!)
+drop table;
+ERROR: syntax error at or near ";"
+LINE 1: drop table;
+ ^
+-- no such relation
+drop table nonesuch;
+ERROR: table "nonesuch" does not exist
+--
+-- ALTER TABLE
+-- relation renaming
+-- missing relation name
+alter table rename;
+ERROR: syntax error at or near ";"
+LINE 1: alter table rename;
+ ^
+-- no such relation
+alter table nonesuch rename to newnonesuch;
+ERROR: relation "nonesuch" does not exist
+-- no such relation
+alter table nonesuch rename to stud_emp;
+ERROR: relation "nonesuch" does not exist
+-- conflict
+alter table stud_emp rename to aggtest;
+ERROR: relation "aggtest" already exists
+-- self-conflict
+alter table stud_emp rename to stud_emp;
+ERROR: relation "stud_emp" already exists
+-- attribute renaming
+-- no such relation
+alter table nonesuchrel rename column nonesuchatt to newnonesuchatt;
+ERROR: relation "nonesuchrel" does not exist
+-- no such attribute
+alter table emp rename column nonesuchatt to newnonesuchatt;
+ERROR: column "nonesuchatt" does not exist
+-- conflict
+alter table emp rename column salary to manager;
+ERROR: column "manager" of relation "stud_emp" already exists
+-- conflict
+alter table emp rename column salary to ctid;
+ERROR: column name "ctid" conflicts with a system column name
+--
+-- TRANSACTION STUFF
+-- not in a xact
+abort;
+WARNING: there is no transaction in progress
+-- not in a xact
+end;
+WARNING: there is no transaction in progress
+--
+-- CREATE AGGREGATE
+-- sfunc/finalfunc type disagreement
+create aggregate newavg2 (sfunc = int4pl,
+ basetype = int4,
+ stype = int4,
+ finalfunc = int2um,
+ initcond = '0');
+ERROR: function int2um(integer) does not exist
+-- left out basetype
+create aggregate newcnt1 (sfunc = int4inc,
+ stype = int4,
+ initcond = '0');
+ERROR: aggregate input type must be specified
+--
+-- DROP INDEX
+-- missing index name
+drop index;
+ERROR: syntax error at or near ";"
+LINE 1: drop index;
+ ^
+-- bad index name
+drop index 314159;
+ERROR: syntax error at or near "314159"
+LINE 1: drop index 314159;
+ ^
+-- no such index
+drop index nonesuch;
+ERROR: index "nonesuch" does not exist
+--
+-- DROP AGGREGATE
+-- missing aggregate name
+drop aggregate;
+ERROR: syntax error at or near ";"
+LINE 1: drop aggregate;
+ ^
+-- missing aggregate type
+drop aggregate newcnt1;
+ERROR: syntax error at or near ";"
+LINE 1: drop aggregate newcnt1;
+ ^
+-- bad aggregate name
+drop aggregate 314159 (int);
+ERROR: syntax error at or near "314159"
+LINE 1: drop aggregate 314159 (int);
+ ^
+-- bad aggregate type
+drop aggregate newcnt (nonesuch);
+ERROR: type "nonesuch" does not exist
+-- no such aggregate
+drop aggregate nonesuch (int4);
+ERROR: aggregate nonesuch(integer) does not exist
+-- no such aggregate for type
+drop aggregate newcnt (float4);
+ERROR: aggregate newcnt(real) does not exist
+--
+-- DROP FUNCTION
+-- missing function name
+drop function ();
+ERROR: syntax error at or near "("
+LINE 1: drop function ();
+ ^
+-- bad function name
+drop function 314159();
+ERROR: syntax error at or near "314159"
+LINE 1: drop function 314159();
+ ^
+-- no such function
+drop function nonesuch();
+ERROR: function nonesuch() does not exist
+--
+-- DROP TYPE
+-- missing type name
+drop type;
+ERROR: syntax error at or near ";"
+LINE 1: drop type;
+ ^
+-- bad type name
+drop type 314159;
+ERROR: syntax error at or near "314159"
+LINE 1: drop type 314159;
+ ^
+-- no such type
+drop type nonesuch;
+ERROR: type "nonesuch" does not exist
+--
+-- DROP OPERATOR
+-- missing everything
+drop operator;
+ERROR: syntax error at or near ";"
+LINE 1: drop operator;
+ ^
+-- bad operator name
+drop operator equals;
+ERROR: syntax error at or near ";"
+LINE 1: drop operator equals;
+ ^
+-- missing type list
+drop operator ===;
+ERROR: syntax error at or near ";"
+LINE 1: drop operator ===;
+ ^
+-- missing parentheses
+drop operator int4, int4;
+ERROR: syntax error at or near ","
+LINE 1: drop operator int4, int4;
+ ^
+-- missing operator name
+drop operator (int4, int4);
+ERROR: syntax error at or near "("
+LINE 1: drop operator (int4, int4);
+ ^
+-- missing type list contents
+drop operator === ();
+ERROR: syntax error at or near ")"
+LINE 1: drop operator === ();
+ ^
+-- no such operator
+drop operator === (int4);
+ERROR: missing argument
+LINE 1: drop operator === (int4);
+ ^
+HINT: Use NONE to denote the missing argument of a unary operator.
+-- no such operator by that name
+drop operator === (int4, int4);
+ERROR: operator does not exist: integer === integer
+-- no such type1
+drop operator = (nonesuch);
+ERROR: missing argument
+LINE 1: drop operator = (nonesuch);
+ ^
+HINT: Use NONE to denote the missing argument of a unary operator.
+-- no such type1
+drop operator = ( , int4);
+ERROR: syntax error at or near ","
+LINE 1: drop operator = ( , int4);
+ ^
+-- no such type1
+drop operator = (nonesuch, int4);
+ERROR: type "nonesuch" does not exist
+-- no such type2
+drop operator = (int4, nonesuch);
+ERROR: type "nonesuch" does not exist
+-- no such type2
+drop operator = (int4, );
+ERROR: syntax error at or near ")"
+LINE 1: drop operator = (int4, );
+ ^
+--
+-- DROP RULE
+-- missing rule name
+drop rule;
+ERROR: syntax error at or near ";"
+LINE 1: drop rule;
+ ^
+-- bad rule name
+drop rule 314159;
+ERROR: syntax error at or near "314159"
+LINE 1: drop rule 314159;
+ ^
+-- no such rule
+drop rule nonesuch on noplace;
+ERROR: relation "noplace" does not exist
+-- these postquel variants are no longer supported
+drop tuple rule nonesuch;
+ERROR: syntax error at or near "tuple"
+LINE 1: drop tuple rule nonesuch;
+ ^
+drop instance rule nonesuch on noplace;
+ERROR: syntax error at or near "instance"
+LINE 1: drop instance rule nonesuch on noplace;
+ ^
+drop rewrite rule nonesuch;
+ERROR: syntax error at or near "rewrite"
+LINE 1: drop rewrite rule nonesuch;
+ ^
+--
+-- Check that division-by-zero is properly caught.
+--
+select 1/0;
+ERROR: division by zero
+select 1::int8/0;
+ERROR: division by zero
+select 1/0::int8;
+ERROR: division by zero
+select 1::int2/0;
+ERROR: division by zero
+select 1/0::int2;
+ERROR: division by zero
+select 1::numeric/0;
+ERROR: division by zero
+select 1/0::numeric;
+ERROR: division by zero
+select 1::float8/0;
+ERROR: division by zero
+select 1/0::float8;
+ERROR: division by zero
+select 1::float4/0;
+ERROR: division by zero
+select 1/0::float4;
+ERROR: division by zero
+--
+-- Test psql's reporting of syntax error location
+--
+xxx;
+ERROR: syntax error at or near "xxx"
+LINE 1: xxx;
+ ^
+CREATE foo;
+ERROR: syntax error at or near "foo"
+LINE 1: CREATE foo;
+ ^
+CREATE TABLE ;
+ERROR: syntax error at or near ";"
+LINE 1: CREATE TABLE ;
+ ^
+CREATE TABLE
+\g
+ERROR: syntax error at end of input
+LINE 1: CREATE TABLE
+ ^
+INSERT INTO foo VALUES(123) foo;
+ERROR: syntax error at or near "foo"
+LINE 1: INSERT INTO foo VALUES(123) foo;
+ ^
+INSERT INTO 123
+VALUES(123);
+ERROR: syntax error at or near "123"
+LINE 1: INSERT INTO 123
+ ^
+INSERT INTO foo
+VALUES(123) 123
+;
+ERROR: syntax error at or near "123"
+LINE 2: 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);
+ERROR: syntax error at or near "NUL"
+LINE 3: id3 INTEGER NOT NUL,
+ ^
+-- 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);
+ERROR: syntax error at or near "NUL"
+LINE 1: ...OT NULL, id2 TEXT NOT NULL PRIMARY KEY, id3 INTEGER NOT NUL,
+ ^
+-- 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);
+ERROR: syntax error at or near "NUL"
+LINE 2: id3 INTEGER NOT NUL, id4 INT4 UNIQUE NOT NULL, id5 TEXT UNIQ...
+ ^
+-- 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);
+ERROR: syntax error at or near "NUL"
+LINE 1: ...L, id2 TEXT NOT NULL PRIMARY KEY, id3 INTEGER NOT NUL, id4 I...
+ ^
+-- 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)
+;
+ERROR: syntax error at or near "NUL"
+LINE 4: ...OT NULL, id2 TEXT NOT NULL PRIMARY KEY, id3 INTEGER NOT NUL,
+ ^
+-- 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)
+;
+ERROR: syntax error at or near "NUL"
+LINE 5: id3 INTEGER NOT NUL, id4 INT4 UNIQUE NOT NULL, id5 TEXT UNIQ...
+ ^
+-- 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);
+ERROR: syntax error at or near "NUL"
+LINE 7: ...L, id2 TEXT NOT NULL PRIMARY KEY, id3 INTEGER NOT NUL, id4 I...
+ ^
+-- 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);
+ERROR: syntax error at or near "NUL"
+LINE 16: ...L, id2 TEXT NOT NULL PRIMARY KEY, id3 INTEGER NOT NUL, id4 I...
+ ^
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
new file mode 100644
index 0000000..c95c30b
--- /dev/null
+++ b/src/test/regress/expected/event_trigger.out
@@ -0,0 +1,611 @@
+-- should fail, return type mismatch
+create event trigger regress_event_trigger
+ on ddl_command_start
+ execute procedure pg_backend_pid();
+ERROR: function pg_backend_pid must return type event_trigger
+-- 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, can't call it as a plain function
+SELECT test_event_trigger();
+ERROR: trigger functions can only be called as triggers
+CONTEXT: compilation of PL/pgSQL function "test_event_trigger" near line 1
+-- 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;
+ERROR: event trigger functions cannot have declared arguments
+CONTEXT: compilation of PL/pgSQL function "test_event_trigger_arg" near line 1
+-- should fail, SQL functions cannot be event triggers
+create function test_event_trigger_sql() returns event_trigger as $$
+SELECT 1 $$ language sql;
+ERROR: SQL functions cannot return type event_trigger
+-- should fail, no elephant_bootstrap entry point
+create event trigger regress_event_trigger on elephant_bootstrap
+ execute procedure test_event_trigger();
+ERROR: unrecognized event name "elephant_bootstrap"
+-- 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
+create event trigger regress_event_trigger2 on ddl_command_start
+ when food in ('sandwich')
+ execute procedure test_event_trigger();
+ERROR: unrecognized filter variable "food"
+-- 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();
+ERROR: filter value "sandwich" not recognized for filter variable "tag"
+-- 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();
+ERROR: filter value "create skunkcabbage" not recognized for filter variable "tag"
+-- 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();
+ERROR: event triggers are not supported for DROP 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();
+ERROR: event triggers are not supported for CREATE ROLE
+-- 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();
+ERROR: event triggers are not supported for CREATE DATABASE
+-- 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();
+ERROR: event triggers are not supported for CREATE TABLESPACE
+-- 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();
+ERROR: filter variable "tag" specified more than once
+-- should fail, can't have arguments
+create event trigger regress_event_trigger2 on ddl_command_start
+ execute procedure test_event_trigger('argument not allowed');
+ERROR: syntax error at or near "'argument not allowed'"
+LINE 2: execute procedure test_event_trigger('argument not allowe...
+ ^
+-- 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();
+ERROR: permission denied to create event trigger "regress_event_trigger_noperms"
+HINT: Must be superuser to create an 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);
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE: test_event_trigger: ddl_command_end CREATE TABLE
+alter event trigger regress_event_trigger enable;
+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);
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+alter event trigger regress_event_trigger enable always;
+-- fires only _trigger
+create table event_trigger_fire4 (a int);
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+reset session_replication_role;
+-- fires all three
+create table event_trigger_fire5 (a int);
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE: test_event_trigger: ddl_command_end CREATE TABLE
+-- non-top-level command
+create function f1() returns int
+language plpgsql
+as $$
+begin
+ create table event_trigger_fire6 (a int);
+ return 0;
+end $$;
+NOTICE: test_event_trigger: ddl_command_start CREATE FUNCTION
+NOTICE: test_event_trigger: ddl_command_start CREATE FUNCTION
+NOTICE: test_event_trigger: ddl_command_end CREATE FUNCTION
+select f1();
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE: test_event_trigger: ddl_command_end CREATE TABLE
+ f1
+----
+ 0
+(1 row)
+
+-- non-top-level command
+create procedure p1()
+language plpgsql
+as $$
+begin
+ create table event_trigger_fire7 (a int);
+end $$;
+NOTICE: test_event_trigger: ddl_command_start CREATE PROCEDURE
+NOTICE: test_event_trigger: ddl_command_end CREATE PROCEDURE
+call p1();
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE: test_event_trigger: ddl_command_end CREATE TABLE
+-- clean up
+alter event trigger regress_event_trigger disable;
+drop table event_trigger_fire2, event_trigger_fire3, event_trigger_fire4, event_trigger_fire5, event_trigger_fire6, event_trigger_fire7;
+NOTICE: test_event_trigger: ddl_command_end DROP TABLE
+drop routine f1(), p1();
+NOTICE: test_event_trigger: ddl_command_end DROP ROUTINE
+-- regress_event_trigger_end should fire on these commands
+grant all on table event_trigger_fire1 to public;
+NOTICE: test_event_trigger: ddl_command_end GRANT
+comment on table event_trigger_fire1 is 'here is a comment';
+NOTICE: test_event_trigger: ddl_command_end COMMENT
+revoke all on table event_trigger_fire1 from public;
+NOTICE: test_event_trigger: ddl_command_end REVOKE
+drop table event_trigger_fire1;
+NOTICE: test_event_trigger: ddl_command_end DROP TABLE
+create foreign data wrapper useless;
+NOTICE: test_event_trigger: ddl_command_end CREATE FOREIGN DATA WRAPPER
+create server useless_server foreign data wrapper useless;
+NOTICE: test_event_trigger: ddl_command_end CREATE SERVER
+create user mapping for regress_evt_user server useless_server;
+NOTICE: test_event_trigger: ddl_command_end CREATE USER MAPPING
+alter default privileges for role regress_evt_user
+ revoke delete on tables from regress_evt_user;
+NOTICE: test_event_trigger: ddl_command_end ALTER DEFAULT PRIVILEGES
+-- alter owner to non-superuser should fail
+alter event trigger regress_event_trigger owner to regress_evt_user;
+ERROR: permission denied to change owner of event trigger "regress_event_trigger"
+HINT: The owner of an event trigger must be a superuser.
+-- 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;
+ERROR: event trigger "regress_event_trigger2" already exists
+-- 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;
+ERROR: event trigger "regress_event_trigger" does not exist
+-- should fail, regress_evt_user owns some objects
+drop role regress_evt_user;
+ERROR: role "regress_evt_user" cannot be dropped because some objects depend on it
+DETAIL: owner of event trigger regress_event_trigger3
+owner of user mapping for regress_evt_user on server useless_server
+owner of default privileges on new relations belonging to role regress_evt_user
+-- 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;
+NOTICE: event trigger "regress_event_trigger2" does not exist, skipping
+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;
+NOTICE: drop cascades to 7 other objects
+DETAIL: drop cascades to table schema_two.table_two
+drop cascades to table schema_two.table_three
+drop cascades to function schema_two.add(integer,integer)
+drop cascades to function schema_two.newton(integer)
+drop cascades to table schema_one.table_one
+drop cascades to table schema_one."table two"
+drop cascades to table schema_one.table_three
+NOTICE: table "schema_two_table_two" does not exist, skipping
+NOTICE: table "audit_tbls_schema_two_table_three" does not exist, skipping
+ERROR: object audit_tbls.schema_two_table_three of type table cannot be dropped
+CONTEXT: PL/pgSQL function undroppable() line 14 at RAISE
+SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_two_table_three"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE
+DELETE FROM undroppable_objs WHERE object_identity = 'audit_tbls.schema_two_table_three';
+DROP SCHEMA schema_one, schema_two CASCADE;
+NOTICE: drop cascades to 7 other objects
+DETAIL: drop cascades to table schema_two.table_two
+drop cascades to table schema_two.table_three
+drop cascades to function schema_two.add(integer,integer)
+drop cascades to function schema_two.newton(integer)
+drop cascades to table schema_one.table_one
+drop cascades to table schema_one."table two"
+drop cascades to table schema_one.table_three
+NOTICE: table "schema_two_table_two" does not exist, skipping
+NOTICE: table "audit_tbls_schema_two_table_three" does not exist, skipping
+NOTICE: table "schema_one_table_one" does not exist, skipping
+NOTICE: table "schema_one_table two" does not exist, skipping
+NOTICE: table "schema_one_table_three" does not exist, skipping
+ERROR: object schema_one.table_three of type table cannot be dropped
+CONTEXT: PL/pgSQL function undroppable() line 14 at RAISE
+DELETE FROM undroppable_objs WHERE object_identity = 'schema_one.table_three';
+DROP SCHEMA schema_one, schema_two CASCADE;
+NOTICE: drop cascades to 7 other objects
+DETAIL: drop cascades to table schema_two.table_two
+drop cascades to table schema_two.table_three
+drop cascades to function schema_two.add(integer,integer)
+drop cascades to function schema_two.newton(integer)
+drop cascades to table schema_one.table_one
+drop cascades to table schema_one."table two"
+drop cascades to table schema_one.table_three
+NOTICE: table "schema_two_table_two" does not exist, skipping
+NOTICE: table "audit_tbls_schema_two_table_three" does not exist, skipping
+NOTICE: table "schema_one_table_one" does not exist, skipping
+NOTICE: table "schema_one_table two" does not exist, skipping
+NOTICE: table "schema_one_table_three" does not exist, skipping
+SELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast';
+ type | schema | object
+--------------+------------+-------------------------------------
+ table column | schema_one | schema_one.table_one.a
+ schema | | schema_two
+ table | schema_two | schema_two.table_two
+ type | schema_two | schema_two.table_two
+ type | schema_two | schema_two.table_two[]
+ table | audit_tbls | audit_tbls.schema_two_table_three
+ type | audit_tbls | audit_tbls.schema_two_table_three
+ type | audit_tbls | audit_tbls.schema_two_table_three[]
+ table | schema_two | schema_two.table_three
+ type | schema_two | schema_two.table_three
+ type | schema_two | schema_two.table_three[]
+ function | schema_two | schema_two.add(integer,integer)
+ aggregate | schema_two | schema_two.newton(integer)
+ schema | | schema_one
+ table | schema_one | schema_one.table_one
+ type | schema_one | schema_one.table_one
+ type | schema_one | schema_one.table_one[]
+ table | schema_one | schema_one."table two"
+ type | schema_one | schema_one."table two"
+ type | schema_one | schema_one."table two"[]
+ table | schema_one | schema_one.table_three
+ type | schema_one | schema_one.table_three
+ type | schema_one | schema_one.table_three[]
+(23 rows)
+
+DROP OWNED BY regress_evt_user;
+NOTICE: schema "audit_tbls" does not exist, skipping
+SELECT * FROM dropped_objects WHERE type = 'schema';
+ type | schema | object
+--------+--------+------------
+ schema | | schema_two
+ schema | | schema_one
+ schema | | audit_tbls
+(3 rows)
+
+DROP ROLE regress_evt_user;
+DROP EVENT TRIGGER regress_event_trigger_drop_objects;
+DROP EVENT TRIGGER undroppable;
+-- Event triggers on relations.
+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 'NORMAL: orig=% normal=% istemp=% type=% identity=% name=% args=%',
+ r.original, r.normal, r.is_temporary, r.object_type,
+ r.object_identity, r.address_names, r.address_args;
+ END LOOP;
+END; $$;
+CREATE EVENT TRIGGER regress_event_trigger_report_dropped ON sql_drop
+ EXECUTE PROCEDURE event_trigger_report_dropped();
+CREATE OR REPLACE FUNCTION event_trigger_report_end()
+ RETURNS event_trigger
+ LANGUAGE plpgsql
+AS $$
+DECLARE r RECORD;
+BEGIN
+ FOR r IN SELECT * FROM pg_event_trigger_ddl_commands()
+ LOOP
+ RAISE NOTICE 'END: command_tag=% type=% identity=%',
+ r.command_tag, r.object_type, r.object_identity;
+ END LOOP;
+END; $$;
+CREATE EVENT TRIGGER regress_event_trigger_report_end ON ddl_command_end
+ EXECUTE PROCEDURE event_trigger_report_end();
+CREATE SCHEMA evttrig
+ CREATE TABLE one (col_a SERIAL PRIMARY KEY, col_b text DEFAULT 'forty two', col_c SERIAL)
+ CREATE INDEX one_idx ON one (col_b)
+ CREATE TABLE two (col_c INTEGER CHECK (col_c > 0) REFERENCES one DEFAULT 42)
+ CREATE TABLE id (col_d int NOT NULL GENERATED ALWAYS AS IDENTITY);
+NOTICE: END: command_tag=CREATE SCHEMA type=schema identity=evttrig
+NOTICE: END: command_tag=CREATE SEQUENCE type=sequence identity=evttrig.one_col_a_seq
+NOTICE: END: command_tag=CREATE SEQUENCE type=sequence identity=evttrig.one_col_c_seq
+NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.one
+NOTICE: END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey
+NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq
+NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_c_seq
+NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.two
+NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.two
+NOTICE: END: command_tag=CREATE SEQUENCE type=sequence identity=evttrig.id_col_d_seq
+NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.id
+NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.id_col_d_seq
+NOTICE: END: command_tag=CREATE INDEX type=index identity=evttrig.one_idx
+-- Partitioned tables with a partitioned index
+CREATE TABLE evttrig.parted (
+ id int PRIMARY KEY)
+ PARTITION BY RANGE (id);
+NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.parted
+NOTICE: END: command_tag=CREATE INDEX type=index identity=evttrig.parted_pkey
+CREATE TABLE evttrig.part_1_10 PARTITION OF evttrig.parted (id)
+ FOR VALUES FROM (1) TO (10);
+NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.part_1_10
+CREATE TABLE evttrig.part_10_20 PARTITION OF evttrig.parted (id)
+ FOR VALUES FROM (10) TO (20) PARTITION BY RANGE (id);
+NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.part_10_20
+CREATE TABLE evttrig.part_10_15 PARTITION OF evttrig.part_10_20 (id)
+ FOR VALUES FROM (10) TO (15);
+NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.part_10_15
+CREATE TABLE evttrig.part_15_20 PARTITION OF evttrig.part_10_20 (id)
+ FOR VALUES FROM (15) TO (20);
+NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.part_15_20
+ALTER TABLE evttrig.two DROP COLUMN col_c;
+NOTICE: NORMAL: orig=t normal=f istemp=f type=table column identity=evttrig.two.col_c name={evttrig,two,col_c} args={}
+NOTICE: NORMAL: orig=f normal=t istemp=f type=table constraint identity=two_col_c_check on evttrig.two name={evttrig,two,two_col_c_check} args={}
+NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.two
+ALTER TABLE evttrig.one ALTER COLUMN col_b DROP DEFAULT;
+NOTICE: NORMAL: orig=t normal=f istemp=f type=default value identity=for evttrig.one.col_b name={evttrig,one,col_b} args={}
+NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.one
+ALTER TABLE evttrig.one DROP CONSTRAINT one_pkey;
+NOTICE: NORMAL: orig=t normal=f istemp=f type=table constraint identity=one_pkey on evttrig.one name={evttrig,one,one_pkey} args={}
+NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.one
+ALTER TABLE evttrig.one DROP COLUMN col_c;
+NOTICE: NORMAL: orig=t normal=f istemp=f type=table column identity=evttrig.one.col_c name={evttrig,one,col_c} args={}
+NOTICE: NORMAL: orig=f normal=t istemp=f type=default value identity=for evttrig.one.col_c name={evttrig,one,col_c} args={}
+NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.one
+ALTER TABLE evttrig.id ALTER COLUMN col_d SET DATA TYPE bigint;
+NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.id_col_d_seq
+NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.id
+ALTER TABLE evttrig.id ALTER COLUMN col_d DROP IDENTITY,
+ ALTER COLUMN col_d SET DATA TYPE int;
+NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.id
+DROP INDEX evttrig.one_idx;
+NOTICE: NORMAL: orig=t normal=f istemp=f type=index identity=evttrig.one_idx name={evttrig,one_idx} args={}
+DROP SCHEMA evttrig CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table evttrig.one
+drop cascades to table evttrig.two
+drop cascades to table evttrig.id
+drop cascades to table evttrig.parted
+NOTICE: NORMAL: orig=t normal=f istemp=f type=schema identity=evttrig name={evttrig} args={}
+NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.one name={evttrig,one} args={}
+NOTICE: NORMAL: orig=f normal=t istemp=f type=sequence identity=evttrig.one_col_a_seq name={evttrig,one_col_a_seq} args={}
+NOTICE: NORMAL: orig=f normal=t istemp=f type=default value identity=for evttrig.one.col_a name={evttrig,one,col_a} args={}
+NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.two name={evttrig,two} args={}
+NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.id name={evttrig,id} args={}
+NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.parted name={evttrig,parted} args={}
+NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_1_10 name={evttrig,part_1_10} args={}
+NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_10_20 name={evttrig,part_10_20} args={}
+NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_10_15 name={evttrig,part_10_15} args={}
+NOTICE: NORMAL: orig=f normal=t istemp=f type=table identity=evttrig.part_15_20 name={evttrig,part_15_20} args={}
+DROP TABLE a_temp_tbl;
+NOTICE: NORMAL: orig=t normal=f istemp=t type=table identity=pg_temp.a_temp_tbl name={pg_temp,a_temp_tbl} args={}
+-- CREATE OPERATOR CLASS without FAMILY clause should report
+-- both CREATE OPERATOR FAMILY and CREATE OPERATOR CLASS
+CREATE OPERATOR CLASS evttrigopclass FOR TYPE int USING btree AS STORAGE int;
+NOTICE: END: command_tag=CREATE OPERATOR FAMILY type=operator family identity=public.evttrigopclass USING btree
+NOTICE: END: command_tag=CREATE OPERATOR CLASS type=operator class identity=public.evttrigopclass USING btree
+DROP EVENT TRIGGER regress_event_trigger_report_dropped;
+DROP EVENT TRIGGER regress_event_trigger_report_end;
+-- only allowed from within an event trigger function, should fail
+select pg_event_trigger_table_rewrite_oid();
+ERROR: pg_event_trigger_table_rewrite_oid() can only be called in a table_rewrite event trigger function
+-- 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;
+ERROR: rewrites not allowed
+CONTEXT: PL/pgSQL function test_evtrig_no_rewrite() line 3 at RAISE
+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);
+NOTICE: Table 'rewriteme' is being rewritten (reason = 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
+NOTICE: Table 'rewriteme' is being rewritten (reason = 4)
+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;
+NOTICE: Table is being rewritten (reason = 4)
+NOTICE: Table is being rewritten (reason = 4)
+-- but this doesn't work
+create table rewritemetoo3 (a rewritetype);
+alter type rewritetype alter attribute a type varchar cascade;
+ERROR: cannot alter type "rewritetype" because column "rewritemetoo3.a" uses it
+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);
+NOTICE: CREATE POLICY - ddl_command_start
+NOTICE: CREATE POLICY - ddl_command_end
+ALTER POLICY p1 ON event_trigger_test USING (TRUE);
+NOTICE: ALTER POLICY - ddl_command_start
+NOTICE: ALTER POLICY - ddl_command_end
+ALTER POLICY p1 ON event_trigger_test RENAME TO p2;
+NOTICE: ALTER POLICY - ddl_command_start
+NOTICE: ALTER POLICY - ddl_command_end
+DROP POLICY p2 ON event_trigger_test;
+NOTICE: DROP POLICY - ddl_command_start
+NOTICE: DROP POLICY - sql_drop
+NOTICE: DROP POLICY - ddl_command_end
+-- Check the object addresses of all the event triggers.
+SELECT
+ e.evtname,
+ pg_describe_object('pg_event_trigger'::regclass, e.oid, 0) as descr,
+ b.type, b.object_names, b.object_args,
+ pg_identify_object(a.classid, a.objid, a.objsubid) as ident
+ FROM pg_event_trigger as e,
+ LATERAL pg_identify_object_as_address('pg_event_trigger'::regclass, e.oid, 0) as b,
+ LATERAL pg_get_object_address(b.type, b.object_names, b.object_args) as a
+ ORDER BY e.evtname;
+ evtname | descr | type | object_names | object_args | ident
+-------------------+---------------------------------+---------------+---------------------+-------------+--------------------------------------------------------
+ end_rls_command | event trigger end_rls_command | event trigger | {end_rls_command} | {} | ("event trigger",,end_rls_command,end_rls_command)
+ sql_drop_command | event trigger sql_drop_command | event trigger | {sql_drop_command} | {} | ("event trigger",,sql_drop_command,sql_drop_command)
+ start_rls_command | event trigger start_rls_command | event trigger | {start_rls_command} | {} | ("event trigger",,start_rls_command,start_rls_command)
+(3 rows)
+
+DROP EVENT TRIGGER start_rls_command;
+DROP EVENT TRIGGER end_rls_command;
+DROP EVENT TRIGGER sql_drop_command;
diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out
new file mode 100644
index 0000000..cda2809
--- /dev/null
+++ b/src/test/regress/expected/explain.out
@@ -0,0 +1,488 @@
+--
+-- EXPLAIN
+--
+-- There are many test cases elsewhere that use EXPLAIN as a vehicle for
+-- checking something else (usually planner behavior). This file is
+-- concerned with testing EXPLAIN in its own right.
+--
+-- 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.
+create function explain_filter(text) returns setof text
+language plpgsql as
+$$
+declare
+ ln text;
+begin
+ for ln in execute $1
+ loop
+ -- Replace any numeric word with just 'N'
+ ln := regexp_replace(ln, '-?\m\d+\M', 'N', 'g');
+ -- In sort output, the above won't match units-suffixed numbers
+ ln := regexp_replace(ln, '\m\d+kB', 'NkB', 'g');
+ -- Ignore text-mode buffers output because it varies depending
+ -- on the system state
+ CONTINUE WHEN (ln ~ ' +Buffers: .*');
+ -- Ignore text-mode "Planning:" line because whether it's output
+ -- varies depending on the system state
+ CONTINUE WHEN (ln = 'Planning:');
+ return next ln;
+ end loop;
+end;
+$$;
+-- 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;
+$$;
+-- Simple cases
+select explain_filter('explain select * from int8_tbl i8');
+ explain_filter
+---------------------------------------------------------
+ Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N)
+(1 row)
+
+select explain_filter('explain (analyze) select * from int8_tbl i8');
+ explain_filter
+-----------------------------------------------------------------------------------------------
+ Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Planning Time: N.N ms
+ Execution Time: N.N ms
+(3 rows)
+
+select explain_filter('explain (analyze, verbose) select * from int8_tbl i8');
+ explain_filter
+------------------------------------------------------------------------------------------------------
+ Seq Scan on public.int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Output: q1, q2
+ Planning Time: N.N ms
+ Execution Time: N.N ms
+(4 rows)
+
+select explain_filter('explain (analyze, buffers, format text) select * from int8_tbl i8');
+ explain_filter
+-----------------------------------------------------------------------------------------------
+ Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Planning Time: N.N ms
+ Execution Time: N.N ms
+(3 rows)
+
+select explain_filter('explain (analyze, buffers, format json) select * from int8_tbl i8');
+ explain_filter
+------------------------------------
+ [ +
+ { +
+ "Plan": { +
+ "Node Type": "Seq Scan", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Relation Name": "int8_tbl",+
+ "Alias": "i8", +
+ "Startup Cost": N.N, +
+ "Total Cost": N.N, +
+ "Plan Rows": N, +
+ "Plan Width": N, +
+ "Actual Startup Time": N.N, +
+ "Actual Total Time": N.N, +
+ "Actual Rows": N, +
+ "Actual Loops": N, +
+ "Shared Hit Blocks": N, +
+ "Shared Read Blocks": N, +
+ "Shared Dirtied Blocks": N, +
+ "Shared Written Blocks": N, +
+ "Local Hit Blocks": N, +
+ "Local Read Blocks": N, +
+ "Local Dirtied Blocks": N, +
+ "Local Written Blocks": N, +
+ "Temp Read Blocks": N, +
+ "Temp Written Blocks": N +
+ }, +
+ "Planning": { +
+ "Shared Hit Blocks": N, +
+ "Shared Read Blocks": N, +
+ "Shared Dirtied Blocks": N, +
+ "Shared Written Blocks": N, +
+ "Local Hit Blocks": N, +
+ "Local Read Blocks": N, +
+ "Local Dirtied Blocks": N, +
+ "Local Written Blocks": N, +
+ "Temp Read Blocks": N, +
+ "Temp Written Blocks": N +
+ }, +
+ "Planning Time": N.N, +
+ "Triggers": [ +
+ ], +
+ "Execution Time": N.N +
+ } +
+ ]
+(1 row)
+
+select explain_filter('explain (analyze, buffers, format xml) select * from int8_tbl i8');
+ explain_filter
+--------------------------------------------------------
+ <explain xmlns="http://www.postgresql.org/N/explain"> +
+ <Query> +
+ <Plan> +
+ <Node-Type>Seq Scan</Node-Type> +
+ <Parallel-Aware>false</Parallel-Aware> +
+ <Async-Capable>false</Async-Capable> +
+ <Relation-Name>int8_tbl</Relation-Name> +
+ <Alias>i8</Alias> +
+ <Startup-Cost>N.N</Startup-Cost> +
+ <Total-Cost>N.N</Total-Cost> +
+ <Plan-Rows>N</Plan-Rows> +
+ <Plan-Width>N</Plan-Width> +
+ <Actual-Startup-Time>N.N</Actual-Startup-Time> +
+ <Actual-Total-Time>N.N</Actual-Total-Time> +
+ <Actual-Rows>N</Actual-Rows> +
+ <Actual-Loops>N</Actual-Loops> +
+ <Shared-Hit-Blocks>N</Shared-Hit-Blocks> +
+ <Shared-Read-Blocks>N</Shared-Read-Blocks> +
+ <Shared-Dirtied-Blocks>N</Shared-Dirtied-Blocks>+
+ <Shared-Written-Blocks>N</Shared-Written-Blocks>+
+ <Local-Hit-Blocks>N</Local-Hit-Blocks> +
+ <Local-Read-Blocks>N</Local-Read-Blocks> +
+ <Local-Dirtied-Blocks>N</Local-Dirtied-Blocks> +
+ <Local-Written-Blocks>N</Local-Written-Blocks> +
+ <Temp-Read-Blocks>N</Temp-Read-Blocks> +
+ <Temp-Written-Blocks>N</Temp-Written-Blocks> +
+ </Plan> +
+ <Planning> +
+ <Shared-Hit-Blocks>N</Shared-Hit-Blocks> +
+ <Shared-Read-Blocks>N</Shared-Read-Blocks> +
+ <Shared-Dirtied-Blocks>N</Shared-Dirtied-Blocks>+
+ <Shared-Written-Blocks>N</Shared-Written-Blocks>+
+ <Local-Hit-Blocks>N</Local-Hit-Blocks> +
+ <Local-Read-Blocks>N</Local-Read-Blocks> +
+ <Local-Dirtied-Blocks>N</Local-Dirtied-Blocks> +
+ <Local-Written-Blocks>N</Local-Written-Blocks> +
+ <Temp-Read-Blocks>N</Temp-Read-Blocks> +
+ <Temp-Written-Blocks>N</Temp-Written-Blocks> +
+ </Planning> +
+ <Planning-Time>N.N</Planning-Time> +
+ <Triggers> +
+ </Triggers> +
+ <Execution-Time>N.N</Execution-Time> +
+ </Query> +
+ </explain>
+(1 row)
+
+select explain_filter('explain (analyze, buffers, format yaml) select * from int8_tbl i8');
+ explain_filter
+-------------------------------
+ - Plan: +
+ Node Type: "Seq Scan" +
+ Parallel Aware: false +
+ Async Capable: false +
+ Relation Name: "int8_tbl"+
+ Alias: "i8" +
+ Startup Cost: N.N +
+ Total Cost: N.N +
+ Plan Rows: N +
+ Plan Width: N +
+ Actual Startup Time: N.N +
+ Actual Total Time: N.N +
+ Actual Rows: N +
+ Actual Loops: N +
+ Shared Hit Blocks: N +
+ Shared Read Blocks: N +
+ Shared Dirtied Blocks: N +
+ Shared Written Blocks: N +
+ Local Hit Blocks: N +
+ Local Read Blocks: N +
+ Local Dirtied Blocks: N +
+ Local Written Blocks: N +
+ Temp Read Blocks: N +
+ Temp Written Blocks: N +
+ Planning: +
+ Shared Hit Blocks: N +
+ Shared Read Blocks: N +
+ Shared Dirtied Blocks: N +
+ Shared Written Blocks: N +
+ Local Hit Blocks: N +
+ Local Read Blocks: N +
+ Local Dirtied Blocks: N +
+ Local Written Blocks: N +
+ Temp Read Blocks: N +
+ Temp Written Blocks: N +
+ Planning Time: N.N +
+ Triggers: +
+ Execution Time: N.N
+(1 row)
+
+select explain_filter('explain (buffers, format text) select * from int8_tbl i8');
+ explain_filter
+---------------------------------------------------------
+ Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N)
+(1 row)
+
+select explain_filter('explain (buffers, format json) select * from int8_tbl i8');
+ explain_filter
+------------------------------------
+ [ +
+ { +
+ "Plan": { +
+ "Node Type": "Seq Scan", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Relation Name": "int8_tbl",+
+ "Alias": "i8", +
+ "Startup Cost": N.N, +
+ "Total Cost": N.N, +
+ "Plan Rows": N, +
+ "Plan Width": N, +
+ "Shared Hit Blocks": N, +
+ "Shared Read Blocks": N, +
+ "Shared Dirtied Blocks": N, +
+ "Shared Written Blocks": N, +
+ "Local Hit Blocks": N, +
+ "Local Read Blocks": N, +
+ "Local Dirtied Blocks": N, +
+ "Local Written Blocks": N, +
+ "Temp Read Blocks": N, +
+ "Temp Written Blocks": N +
+ }, +
+ "Planning": { +
+ "Shared Hit Blocks": N, +
+ "Shared Read Blocks": N, +
+ "Shared Dirtied Blocks": N, +
+ "Shared Written Blocks": N, +
+ "Local Hit Blocks": N, +
+ "Local Read Blocks": N, +
+ "Local Dirtied Blocks": N, +
+ "Local Written Blocks": N, +
+ "Temp Read Blocks": N, +
+ "Temp Written Blocks": N +
+ } +
+ } +
+ ]
+(1 row)
+
+-- SETTINGS option
+-- We have to ignore other settings that might be imposed by the environment,
+-- so printing the whole Settings field unfortunately won't do.
+begin;
+set local plan_cache_mode = force_generic_plan;
+select true as "OK"
+ from explain_filter('explain (settings) select * from int8_tbl i8') ln
+ where ln ~ '^ *Settings: .*plan_cache_mode = ''force_generic_plan''';
+ OK
+----
+ t
+(1 row)
+
+select explain_filter_to_json('explain (settings, format json) select * from int8_tbl i8') #> '{0,Settings,plan_cache_mode}';
+ ?column?
+----------------------
+ "force_generic_plan"
+(1 row)
+
+rollback;
+--
+-- Test production of per-worker data
+--
+-- Unfortunately, because we don't know how many worker processes we'll
+-- actually get (maybe none at all), we can't examine the "Workers" output
+-- in any detail. We can check that it parses correctly as JSON, and then
+-- remove it from the displayed results.
+begin;
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+select jsonb_pretty(
+ explain_filter_to_json('explain (analyze, verbose, buffers, format json)
+ select * from tenk1 order by tenthous')
+ -- remove "Workers" node of the Seq Scan plan node
+ #- '{0,Plan,Plans,0,Plans,0,Workers}'
+ -- remove "Workers" node of the Sort plan node
+ #- '{0,Plan,Plans,0,Workers}'
+ -- Also remove its sort-type fields, as those aren't 100% stable
+ #- '{0,Plan,Plans,0,Sort Method}'
+ #- '{0,Plan,Plans,0,Sort Space Type}'
+);
+ jsonb_pretty
+-------------------------------------------------------------
+ [ +
+ { +
+ "Plan": { +
+ "Plans": [ +
+ { +
+ "Plans": [ +
+ { +
+ "Alias": "tenk1", +
+ "Output": [ +
+ "unique1", +
+ "unique2", +
+ "two", +
+ "four", +
+ "ten", +
+ "twenty", +
+ "hundred", +
+ "thousand", +
+ "twothousand", +
+ "fivethous", +
+ "tenthous", +
+ "odd", +
+ "even", +
+ "stringu1", +
+ "stringu2", +
+ "string4" +
+ ], +
+ "Schema": "public", +
+ "Node Type": "Seq Scan", +
+ "Plan Rows": 0, +
+ "Plan Width": 0, +
+ "Total Cost": 0.0, +
+ "Actual Rows": 0, +
+ "Actual Loops": 0, +
+ "Startup Cost": 0.0, +
+ "Async Capable": false, +
+ "Relation Name": "tenk1", +
+ "Parallel Aware": true, +
+ "Local Hit Blocks": 0, +
+ "Temp Read Blocks": 0, +
+ "Actual Total Time": 0.0, +
+ "Local Read Blocks": 0, +
+ "Shared Hit Blocks": 0, +
+ "Shared Read Blocks": 0, +
+ "Actual Startup Time": 0.0, +
+ "Parent Relationship": "Outer",+
+ "Temp Written Blocks": 0, +
+ "Local Dirtied Blocks": 0, +
+ "Local Written Blocks": 0, +
+ "Shared Dirtied Blocks": 0, +
+ "Shared Written Blocks": 0 +
+ } +
+ ], +
+ "Output": [ +
+ "unique1", +
+ "unique2", +
+ "two", +
+ "four", +
+ "ten", +
+ "twenty", +
+ "hundred", +
+ "thousand", +
+ "twothousand", +
+ "fivethous", +
+ "tenthous", +
+ "odd", +
+ "even", +
+ "stringu1", +
+ "stringu2", +
+ "string4" +
+ ], +
+ "Sort Key": [ +
+ "tenk1.tenthous" +
+ ], +
+ "Node Type": "Sort", +
+ "Plan Rows": 0, +
+ "Plan Width": 0, +
+ "Total Cost": 0.0, +
+ "Actual Rows": 0, +
+ "Actual Loops": 0, +
+ "Startup Cost": 0.0, +
+ "Async Capable": false, +
+ "Parallel Aware": false, +
+ "Sort Space Used": 0, +
+ "Local Hit Blocks": 0, +
+ "Temp Read Blocks": 0, +
+ "Actual Total Time": 0.0, +
+ "Local Read Blocks": 0, +
+ "Shared Hit Blocks": 0, +
+ "Shared Read Blocks": 0, +
+ "Actual Startup Time": 0.0, +
+ "Parent Relationship": "Outer", +
+ "Temp Written Blocks": 0, +
+ "Local Dirtied Blocks": 0, +
+ "Local Written Blocks": 0, +
+ "Shared Dirtied Blocks": 0, +
+ "Shared Written Blocks": 0 +
+ } +
+ ], +
+ "Output": [ +
+ "unique1", +
+ "unique2", +
+ "two", +
+ "four", +
+ "ten", +
+ "twenty", +
+ "hundred", +
+ "thousand", +
+ "twothousand", +
+ "fivethous", +
+ "tenthous", +
+ "odd", +
+ "even", +
+ "stringu1", +
+ "stringu2", +
+ "string4" +
+ ], +
+ "Node Type": "Gather Merge", +
+ "Plan Rows": 0, +
+ "Plan Width": 0, +
+ "Total Cost": 0.0, +
+ "Actual Rows": 0, +
+ "Actual Loops": 0, +
+ "Startup Cost": 0.0, +
+ "Async Capable": false, +
+ "Parallel Aware": false, +
+ "Workers Planned": 0, +
+ "Local Hit Blocks": 0, +
+ "Temp Read Blocks": 0, +
+ "Workers Launched": 0, +
+ "Actual Total Time": 0.0, +
+ "Local Read Blocks": 0, +
+ "Shared Hit Blocks": 0, +
+ "Shared Read Blocks": 0, +
+ "Actual Startup Time": 0.0, +
+ "Temp Written Blocks": 0, +
+ "Local Dirtied Blocks": 0, +
+ "Local Written Blocks": 0, +
+ "Shared Dirtied Blocks": 0, +
+ "Shared Written Blocks": 0 +
+ }, +
+ "Planning": { +
+ "Local Hit Blocks": 0, +
+ "Temp Read Blocks": 0, +
+ "Local Read Blocks": 0, +
+ "Shared Hit Blocks": 0, +
+ "Shared Read Blocks": 0, +
+ "Temp Written Blocks": 0, +
+ "Local Dirtied Blocks": 0, +
+ "Local Written Blocks": 0, +
+ "Shared Dirtied Blocks": 0, +
+ "Shared Written Blocks": 0 +
+ }, +
+ "Triggers": [ +
+ ], +
+ "Planning Time": 0.0, +
+ "Execution Time": 0.0 +
+ } +
+ ]
+(1 row)
+
+rollback;
+set compute_query_id = on;
+select explain_filter('explain (verbose) select * from int8_tbl i8');
+ explain_filter
+----------------------------------------------------------------
+ Seq Scan on public.int8_tbl i8 (cost=N.N..N.N rows=N width=N)
+ Output: q1, q2
+ Query Identifier: N
+(3 rows)
+
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
new file mode 100644
index 0000000..9699d86
--- /dev/null
+++ b/src/test/regress/expected/expressions.out
@@ -0,0 +1,381 @@
+--
+-- expression evaluation 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;
+ ?column?
+----------
+ t
+(1 row)
+
+-- current_time / localtime
+SELECT now()::timetz::text = current_time::text;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT now()::timetz(4)::text = current_time(4)::text;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT now()::time::text = localtime::text;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT now()::time(3)::text = localtime(3)::text;
+ ?column?
+----------
+ t
+(1 row)
+
+-- current_timestamp / localtimestamp (always matches because of transactional behaviour)
+SELECT current_timestamp = NOW();
+ ?column?
+----------
+ t
+(1 row)
+
+-- precision
+SELECT length(current_timestamp::text) >= length(current_timestamp(0)::text);
+ ?column?
+----------
+ t
+(1 row)
+
+-- localtimestamp
+SELECT now()::timestamp::text = localtimestamp::text;
+ ?column?
+----------
+ t
+(1 row)
+
+-- current_role/user/user is tested in rolnames.sql
+-- current database / catalog
+SELECT current_catalog = current_database();
+ ?column?
+----------
+ t
+(1 row)
+
+-- current_schema
+SELECT current_schema;
+ current_schema
+----------------
+ public
+(1 row)
+
+SET search_path = 'notme';
+SELECT current_schema;
+ current_schema
+----------------
+
+(1 row)
+
+SET search_path = 'pg_catalog';
+SELECT current_schema;
+ current_schema
+----------------
+ pg_catalog
+(1 row)
+
+RESET search_path;
+--
+-- Tests for BETWEEN
+--
+explain (costs off)
+select count(*) from date_tbl
+ where f1 between '1997-01-01' and '1998-01-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Aggregate
+ -> Seq Scan on date_tbl
+ Filter: ((f1 >= '01-01-1997'::date) AND (f1 <= '01-01-1998'::date))
+(3 rows)
+
+select count(*) from date_tbl
+ where f1 between '1997-01-01' and '1998-01-01';
+ count
+-------
+ 3
+(1 row)
+
+explain (costs off)
+select count(*) from date_tbl
+ where f1 not between '1997-01-01' and '1998-01-01';
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Aggregate
+ -> Seq Scan on date_tbl
+ Filter: ((f1 < '01-01-1997'::date) OR (f1 > '01-01-1998'::date))
+(3 rows)
+
+select count(*) from date_tbl
+ where f1 not between '1997-01-01' and '1998-01-01';
+ count
+-------
+ 13
+(1 row)
+
+explain (costs off)
+select count(*) from date_tbl
+ where f1 between symmetric '1997-01-01' and '1998-01-01';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+ -> Seq Scan on date_tbl
+ Filter: (((f1 >= '01-01-1997'::date) AND (f1 <= '01-01-1998'::date)) OR ((f1 >= '01-01-1998'::date) AND (f1 <= '01-01-1997'::date)))
+(3 rows)
+
+select count(*) from date_tbl
+ where f1 between symmetric '1997-01-01' and '1998-01-01';
+ count
+-------
+ 3
+(1 row)
+
+explain (costs off)
+select count(*) from date_tbl
+ where f1 not between symmetric '1997-01-01' and '1998-01-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+ -> Seq Scan on date_tbl
+ Filter: (((f1 < '01-01-1997'::date) OR (f1 > '01-01-1998'::date)) AND ((f1 < '01-01-1998'::date) OR (f1 > '01-01-1997'::date)))
+(3 rows)
+
+select count(*) from date_tbl
+ where f1 not between symmetric '1997-01-01' and '1998-01-01';
+ count
+-------
+ 13
+(1 row)
+
+--
+-- Test parsing of a no-op cast to a type with unspecified typmod
+--
+begin;
+create table numeric_tbl (f1 numeric(18,3), f2 numeric);
+create view numeric_view as
+ select
+ f1, f1::numeric(16,4) as f1164, f1::numeric as f1n,
+ f2, f2::numeric(16,4) as f2164, f2::numeric as f2n
+ from numeric_tbl;
+\d+ numeric_view
+ View "public.numeric_view"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------------+-----------+----------+---------+---------+-------------
+ f1 | numeric(18,3) | | | | main |
+ f1164 | numeric(16,4) | | | | main |
+ f1n | numeric | | | | main |
+ f2 | numeric | | | | main |
+ f2164 | numeric(16,4) | | | | main |
+ f2n | numeric | | | | main |
+View definition:
+ SELECT numeric_tbl.f1,
+ numeric_tbl.f1::numeric(16,4) AS f1164,
+ numeric_tbl.f1::numeric AS f1n,
+ numeric_tbl.f2,
+ numeric_tbl.f2::numeric(16,4) AS f2164,
+ numeric_tbl.f2 AS f2n
+ FROM numeric_tbl;
+
+explain (verbose, costs off) select * from numeric_view;
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------------------------------------
+ Seq Scan on public.numeric_tbl
+ Output: numeric_tbl.f1, (numeric_tbl.f1)::numeric(16,4), (numeric_tbl.f1)::numeric, numeric_tbl.f2, (numeric_tbl.f2)::numeric(16,4), numeric_tbl.f2
+(2 rows)
+
+-- bpchar, lacking planner support for its length coercion function,
+-- could behave differently
+create table bpchar_tbl (f1 character(16) unique, f2 bpchar);
+create view bpchar_view as
+ select
+ f1, f1::character(14) as f114, f1::bpchar as f1n,
+ f2, f2::character(14) as f214, f2::bpchar as f2n
+ from bpchar_tbl;
+\d+ bpchar_view
+ View "public.bpchar_view"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------------+-----------+----------+---------+----------+-------------
+ f1 | character(16) | | | | extended |
+ f114 | character(14) | | | | extended |
+ f1n | bpchar | | | | extended |
+ f2 | bpchar | | | | extended |
+ f214 | character(14) | | | | extended |
+ f2n | bpchar | | | | extended |
+View definition:
+ SELECT bpchar_tbl.f1,
+ bpchar_tbl.f1::character(14) AS f114,
+ bpchar_tbl.f1::bpchar AS f1n,
+ bpchar_tbl.f2,
+ bpchar_tbl.f2::character(14) AS f214,
+ bpchar_tbl.f2 AS f2n
+ FROM bpchar_tbl;
+
+explain (verbose, costs off) select * from bpchar_view
+ where f1::bpchar = 'foo';
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------
+ Index Scan using bpchar_tbl_f1_key on public.bpchar_tbl
+ Output: bpchar_tbl.f1, (bpchar_tbl.f1)::character(14), (bpchar_tbl.f1)::bpchar, bpchar_tbl.f2, (bpchar_tbl.f2)::character(14), bpchar_tbl.f2
+ Index Cond: ((bpchar_tbl.f1)::bpchar = 'foo'::bpchar)
+(3 rows)
+
+rollback;
+--
+-- Ordinarily, IN/NOT IN can be converted to a ScalarArrayOpExpr
+-- with a suitably-chosen array type.
+--
+explain (verbose, costs off)
+select random() IN (1, 4, 8.0);
+ QUERY PLAN
+------------------------------------------------------------
+ Result
+ Output: (random() = ANY ('{1,4,8}'::double precision[]))
+(2 rows)
+
+explain (verbose, costs off)
+select random()::int IN (1, 4, 8.0);
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Result
+ Output: (((random())::integer)::numeric = ANY ('{1,4,8.0}'::numeric[]))
+(2 rows)
+
+-- However, if there's not a common supertype for the IN elements,
+-- we should instead try to produce "x = v1 OR x = v2 OR ...".
+-- In most cases that'll fail for lack of all the requisite = operators,
+-- but it can succeed sometimes. So this should complain about lack of
+-- an = operator, not about cast failure.
+select '(0,0)'::point in ('(0,0,0,0)'::box, point(0,0));
+ERROR: operator does not exist: point = box
+LINE 1: select '(0,0)'::point in ('(0,0,0,0)'::box, point(0,0));
+ ^
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Tests for ScalarArrayOpExpr with a hashfn
+--
+-- create a stable function so that the tests below are not
+-- evaluated using the planner's constant folding.
+begin;
+create function return_int_input(int) returns int as $$
+begin
+ return $1;
+end;
+$$ language plpgsql stable;
+create function return_text_input(text) returns text as $$
+begin
+ return $1;
+end;
+$$ language plpgsql stable;
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column?
+----------
+ t
+(1 row)
+
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column?
+----------
+
+(1 row)
+
+select return_int_input(1) in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column?
+----------
+
+(1 row)
+
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column?
+----------
+ t
+(1 row)
+
+select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column?
+----------
+
+(1 row)
+
+select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column?
+----------
+
+(1 row)
+
+select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+ ?column?
+----------
+ t
+(1 row)
+
+rollback;
+-- Test with non-strict equality function.
+-- We need to create our own type for this.
+begin;
+create type myint;
+create function myintin(cstring) returns myint strict immutable language
+ internal as 'int4in';
+NOTICE: return type myint is only a shell
+create function myintout(myint) returns cstring strict immutable language
+ internal as 'int4out';
+NOTICE: argument type myint is only a shell
+create function myinthash(myint) returns integer strict immutable language
+ internal as 'hashint4';
+NOTICE: argument type myint is only a shell
+create type myint (input = myintin, output = myintout, like = int4);
+create cast (int4 as myint) without function;
+create cast (myint as int4) without function;
+create function myinteq(myint, myint) returns bool as $$
+begin
+ if $1 is null and $2 is null then
+ return true;
+ else
+ return $1::int = $2::int;
+ end if;
+end;
+$$ language plpgsql immutable;
+create operator = (
+ leftarg = myint,
+ rightarg = myint,
+ commutator = =,
+ negator = <>,
+ procedure = myinteq,
+ restrict = eqsel,
+ join = eqjoinsel,
+ merges
+);
+create operator class myint_ops
+default for type myint using hash as
+ operator 1 = (myint, myint),
+ function 1 myinthash(myint);
+create table inttest (a myint);
+insert into inttest values(1::myint),(null);
+-- try an array with enough elements to cause hashing
+select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+ a
+---
+ 1
+
+(2 rows)
+
+-- ensure the result matched with the non-hashed version. We simply remove
+-- some array elements so that we don't reach the hashing threshold.
+select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+ a
+---
+ 1
+
+(2 rows)
+
+rollback;
diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out
new file mode 100644
index 0000000..91f2571
--- /dev/null
+++ b/src/test/regress/expected/fast_default.out
@@ -0,0 +1,843 @@
+--
+-- 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;
+NOTICE: rewriting table has_volatile for reason 2
+-- Test a large sample of different datatypes
+CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1);
+SELECT set('t');
+ set
+-----
+
+(1 row)
+
+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;
+ 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_origdef | c_hugetext_newdef
+----+-------+----------+--------+------------+--------------------------+--------------------------+--------------------------+---------+--------------+-------------------+-------------------+----------+------------+--------------------+-------------------
+ 1 | 1 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 2 | 1 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 3 | 2 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 4 | 2 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 5 | 2 | dog | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 6 | 2 | dog | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 7 | 2 | dog | cat | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 8 | 2 | dog | cat | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 9 | 2 | dog | cat | 01-01-2010 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 10 | 2 | dog | cat | 01-01-2010 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 11 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 12 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 13 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 14 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 15 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 16 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 17 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 18 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
+ 19 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 12:00:00 | @ 1 day | t | f
+ 20 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 12:00:00 | @ 1 day | t | f
+ 21 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 1 day | t | f
+ 22 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 1 day | t | f
+ 23 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 3 hours | t | f
+ 24 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 3 hours | t | f
+ 25 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | | f | t
+ 26 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | | f | t
+ 27 | 2 | | | | | Thu Sep 29 12:00:00 2016 | | | 13 | | | | | |
+ 28 | 2 | | | | | Thu Sep 29 12:00:00 2016 | | | 13 | | | | | |
+(28 rows)
+
+SELECT comp();
+ comp
+-----------
+ Unchanged
+(1 row)
+
+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');
+ set
+-----
+
+(1 row)
+
+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;
+ pk | c_int | c_bpchar | c_text | c_date | c_timestamp | c_array
+----+-------+----------+--------------+------------+--------------------------+-------------------------------
+ 1 | 6 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+ 2 | 6 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+ 3 | 8 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+ 4 | 8 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+ 5 | 8 | abc | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+ 6 | 8 | abc | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+ 7 | 8 | abc | abcdefghijkl | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+ 8 | 8 | abc | abcdefghijkl | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+ 9 | 8 | abc | abcdefghijkl | 12-28-2009 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+ 10 | 8 | abc | abcdefghijkl | 12-28-2009 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+ 11 | 8 | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,abcd,the,real,world}
+ 12 | 8 | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,abcd,the,real,world}
+ 13 | | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,a,fantasy}
+ 14 | | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,a,fantasy}
+ 15 | | | | | |
+ 16 | | | | | |
+(16 rows)
+
+SELECT comp();
+ comp
+-----------
+ Unchanged
+(1 row)
+
+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');
+ set
+-----
+
+(1 row)
+
+-- now() is stable, because it returns the transaction timestamp
+ALTER TABLE T ADD COLUMN c1 TIMESTAMP DEFAULT now();
+SELECT comp();
+ comp
+-----------
+ Unchanged
+(1 row)
+
+-- clock_timestamp() is volatile
+ALTER TABLE T ADD COLUMN c2 TIMESTAMP DEFAULT clock_timestamp();
+NOTICE: rewriting table t for reason 2
+SELECT comp();
+ comp
+-----------
+ Rewritten
+(1 row)
+
+DROP TABLE T;
+-- Simple querie
+CREATE TABLE T (pk INT NOT NULL PRIMARY KEY);
+SELECT set('t');
+ set
+-----
+
+(1 row)
+
+INSERT INTO T SELECT * FROM generate_series(1, 10) a;
+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;
+ c_bigint | c_text
+----------+--------
+ -1 | hello
+(1 row)
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE)
+SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1;
+ QUERY PLAN
+----------------------------------------------
+ Limit
+ Output: c_bigint, c_text
+ -> Seq Scan on fast_default.t
+ Output: c_bigint, c_text
+ Filter: (t.c_bigint = '-1'::integer)
+(5 rows)
+
+SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1;
+ c_bigint | c_text
+----------+--------
+ -1 | hello
+(1 row)
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1;
+ QUERY PLAN
+--------------------------------------------
+ Limit
+ Output: c_bigint, c_text
+ -> Seq Scan on fast_default.t
+ Output: c_bigint, c_text
+ Filter: (t.c_text = 'hello'::text)
+(5 rows)
+
+-- COALESCE
+SELECT COALESCE(c_bigint, pk), COALESCE(c_text, pk::text)
+FROM T
+ORDER BY pk LIMIT 10;
+ coalesce | coalesce
+----------+----------
+ -1 | hello
+ -1 | hello
+ -1 | hello
+ -1 | hello
+ -1 | hello
+ -1 | hello
+ -1 | hello
+ -1 | hello
+ -1 | hello
+ -1 | hello
+(10 rows)
+
+-- Aggregate function
+SELECT SUM(c_bigint), MAX(c_text COLLATE "C" ), MIN(c_text COLLATE "C") FROM T;
+ sum | max | min
+-----+-------+-----
+ 200 | hello | 31
+(1 row)
+
+-- ORDER BY
+SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10;
+ pk | c_bigint | c_text
+----+----------+--------
+ 1 | -1 | hello
+ 2 | -1 | hello
+ 3 | -1 | hello
+ 4 | -1 | hello
+ 5 | -1 | hello
+ 6 | -1 | hello
+ 7 | -1 | hello
+ 8 | -1 | hello
+ 9 | -1 | hello
+ 10 | -1 | hello
+(10 rows)
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE)
+SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10;
+ QUERY PLAN
+----------------------------------------------
+ Limit
+ Output: pk, c_bigint, c_text
+ -> Sort
+ Output: pk, c_bigint, c_text
+ Sort Key: t.c_bigint, t.c_text, t.pk
+ -> Seq Scan on fast_default.t
+ Output: pk, c_bigint, c_text
+(7 rows)
+
+-- LIMIT
+SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10;
+ pk | c_bigint | c_text
+----+----------+--------
+ 11 | 1 | hello
+ 12 | 2 | hello
+ 13 | 3 | hello
+ 14 | 4 | hello
+ 15 | 5 | hello
+ 16 | 6 | hello
+ 17 | 7 | hello
+ 18 | 8 | hello
+ 19 | 9 | hello
+ 20 | 10 | hello
+(10 rows)
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE)
+SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10;
+ QUERY PLAN
+----------------------------------------------------
+ Limit
+ Output: pk, c_bigint, c_text
+ -> Sort
+ Output: pk, c_bigint, c_text
+ Sort Key: t.c_bigint, t.c_text, t.pk
+ -> Seq Scan on fast_default.t
+ Output: pk, c_bigint, c_text
+ Filter: (t.c_bigint > '-1'::integer)
+(8 rows)
+
+-- DELETE with RETURNING
+DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *;
+ pk | c_bigint | c_text
+----+----------+--------
+ 10 | -1 | hello
+ 11 | 1 | hello
+ 12 | 2 | hello
+ 13 | 3 | hello
+ 14 | 4 | hello
+ 15 | 5 | hello
+ 16 | 6 | hello
+ 17 | 7 | hello
+ 18 | 8 | hello
+ 19 | 9 | hello
+ 20 | 10 | hello
+(11 rows)
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE)
+DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *;
+ QUERY PLAN
+-----------------------------------------------------------
+ Delete on fast_default.t
+ Output: pk, c_bigint, c_text
+ -> Bitmap Heap Scan on fast_default.t
+ Output: ctid
+ Recheck Cond: ((t.pk >= 10) AND (t.pk <= 20))
+ -> Bitmap Index Scan on t_pkey
+ Index Cond: ((t.pk >= 10) AND (t.pk <= 20))
+(7 rows)
+
+-- UPDATE
+UPDATE T SET c_text = '"' || c_text || '"' WHERE pk < 10;
+SELECT * FROM T WHERE c_text LIKE '"%"' ORDER BY PK;
+ pk | c_bigint | c_text
+----+----------+---------
+ 1 | -1 | "hello"
+ 2 | -1 | "hello"
+ 3 | -1 | "hello"
+ 4 | -1 | "hello"
+ 5 | -1 | "hello"
+ 6 | -1 | "hello"
+ 7 | -1 | "hello"
+ 8 | -1 | "hello"
+ 9 | -1 | "hello"
+(9 rows)
+
+SELECT comp();
+ comp
+-----------
+ Unchanged
+(1 row)
+
+DROP TABLE T;
+-- Combine with other DDL
+CREATE TABLE T(pk INT NOT NULL PRIMARY KEY);
+SELECT set('t');
+ set
+-----
+
+(1 row)
+
+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;
+ pk | c_int | c_text
+----+-------+--------
+ 1 | -1 | Hello
+ 2 | -1 | Hello
+ 3 | -1 | Hello
+ 4 | -1 | Hello
+ 5 | -1 | Hello
+ 6 | -1 | Hello
+ 7 | 1 | world
+ 8 | 1 | world
+(8 rows)
+
+-- Add an index
+CREATE INDEX i ON T(c_int, c_text);
+SELECT c_text FROM T WHERE c_int = -1;
+ c_text
+--------
+ Hello
+ Hello
+ Hello
+ Hello
+ Hello
+ Hello
+(6 rows)
+
+SELECT comp();
+ comp
+-----------
+ Unchanged
+(1 row)
+
+-- 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;
+ a | z
+---+---
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+ 1 | 0
+(20 rows)
+
+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;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | 3 | 4 | 5
+(1 row)
+
+UPDATE t SET y = 2;
+NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":4,"y":5}
+SELECT * FROM t;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | 3 | 4 | 2
+(1 row)
+
+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;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | 3 | 4 |
+(1 row)
+
+UPDATE t SET y = 2;
+NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":4,"y":null}
+SELECT * FROM t;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | 3 | 4 | 2
+(1 row)
+
+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;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | 3 | | 5
+(1 row)
+
+UPDATE t SET y = 2;
+NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":null,"y":5}
+SELECT * FROM t;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | 3 | | 2
+(1 row)
+
+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;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | 3 | |
+(1 row)
+
+UPDATE t SET y = 2;
+NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":null,"y":null}
+SELECT * FROM t;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | 3 | | 2
+(1 row)
+
+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;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | | 4 | 5
+(1 row)
+
+UPDATE t SET y = 2;
+NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":4,"y":5}
+SELECT * FROM t;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | | 4 | 2
+(1 row)
+
+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;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | | 4 |
+(1 row)
+
+UPDATE t SET y = 2;
+NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":4,"y":null}
+SELECT * FROM t;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | | 4 | 2
+(1 row)
+
+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;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | | | 5
+(1 row)
+
+UPDATE t SET y = 2;
+NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":null,"y":5}
+SELECT * FROM t;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | | | 2
+(1 row)
+
+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;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | | |
+(1 row)
+
+UPDATE t SET y = 2;
+NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":null,"y":null}
+SELECT * FROM t;
+ id | a | b | c | x | y
+----+---+---+---+---+---
+ 1 | 1 | 2 | | | 2
+(1 row)
+
+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;
+ a | b | c
+---+-----+---
+ 1 | 0.2 | t
+(1 row)
+
+ALTER TABLE vtype
+ ALTER b TYPE text USING b::text,
+ ALTER c TYPE text USING c::text;
+NOTICE: rewriting table vtype for reason 4
+SELECT * FROM vtype;
+ a | b | c
+---+-----+------
+ 1 | 0.2 | true
+(1 row)
+
+-- 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;
+ a | b
+---+-----
+ 1 | xxx
+ 2 | yyy
+(2 rows)
+
+-- 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;
+ a
+---
+
+(1 row)
+
+SET LOCAL enable_seqscan = false;
+SELECT * FROM t WHERE a IS NULL;
+ a
+---
+
+(1 row)
+
+ROLLBACK;
+-- verify that a default set on a non-plain table doesn't set a missing
+-- value on the attribute
+CREATE FOREIGN DATA WRAPPER dummy;
+CREATE SERVER s0 FOREIGN DATA WRAPPER dummy;
+CREATE FOREIGN TABLE ft1 (c1 integer NOT NULL) SERVER s0;
+ALTER FOREIGN TABLE ft1 ADD COLUMN c8 integer DEFAULT 0;
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10);
+SELECT count(*)
+ FROM pg_attribute
+ WHERE attrelid = 'ft1'::regclass AND
+ (attmissingval IS NOT NULL OR atthasmissing);
+ count
+-------
+ 0
+(1 row)
+
+-- cleanup
+DROP FOREIGN TABLE ft1;
+DROP SERVER s0;
+DROP FOREIGN DATA WRAPPER dummy;
+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;
+ f1 | f2
+----+----
+ 1 | 42
+(1 row)
+
diff --git a/src/test/regress/expected/float4-misrounded-input.out b/src/test/regress/expected/float4-misrounded-input.out
new file mode 100644
index 0000000..3d5d298
--- /dev/null
+++ b/src/test/regress/expected/float4-misrounded-input.out
@@ -0,0 +1,961 @@
+--
+-- 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');
+ERROR: "10e70" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e70');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70');
+ERROR: "-10e70" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70');
+ERROR: "10e-70" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70');
+ERROR: "-10e-70" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('10e70'::float8);
+ERROR: value out of range: overflow
+INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70'::float8);
+ERROR: value out of range: overflow
+INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70'::float8);
+ERROR: value out of range: underflow
+INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70'::float8);
+ERROR: value out of range: underflow
+INSERT INTO FLOAT4_TBL(f1) VALUES ('10e400');
+ERROR: "10e400" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e400');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e400');
+ERROR: "-10e400" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e400');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-400');
+ERROR: "10e-400" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-400');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-400');
+ERROR: "-10e-400" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-400');
+ ^
+-- bad input
+INSERT INTO FLOAT4_TBL(f1) VALUES ('');
+ERROR: invalid input syntax for type real: ""
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES (' ');
+ERROR: invalid input syntax for type real: " "
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES (' ');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('xyz');
+ERROR: invalid input syntax for type real: "xyz"
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('xyz');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('5.0.0');
+ERROR: invalid input syntax for type real: "5.0.0"
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('5.0.0');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('5 . 0');
+ERROR: invalid input syntax for type real: "5 . 0"
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('5 . 0');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('5. 0');
+ERROR: invalid input syntax for type real: "5. 0"
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('5. 0');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES (' - 3.0');
+ERROR: invalid input syntax for type real: " - 3.0"
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES (' - 3.0');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5');
+ERROR: invalid input syntax for type real: "123 5"
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5');
+ ^
+-- special inputs
+SELECT 'NaN'::float4;
+ float4
+--------
+ NaN
+(1 row)
+
+SELECT 'nan'::float4;
+ float4
+--------
+ NaN
+(1 row)
+
+SELECT ' NAN '::float4;
+ float4
+--------
+ NaN
+(1 row)
+
+SELECT 'infinity'::float4;
+ float4
+----------
+ Infinity
+(1 row)
+
+SELECT ' -INFINiTY '::float4;
+ float4
+-----------
+ -Infinity
+(1 row)
+
+-- bad special inputs
+SELECT 'N A N'::float4;
+ERROR: invalid input syntax for type real: "N A N"
+LINE 1: SELECT 'N A N'::float4;
+ ^
+SELECT 'NaN x'::float4;
+ERROR: invalid input syntax for type real: "NaN x"
+LINE 1: SELECT 'NaN x'::float4;
+ ^
+SELECT ' INFINITY x'::float4;
+ERROR: invalid input syntax for type real: " INFINITY x"
+LINE 1: SELECT ' INFINITY x'::float4;
+ ^
+SELECT 'Infinity'::float4 + 100.0;
+ ?column?
+----------
+ Infinity
+(1 row)
+
+SELECT 'Infinity'::float4 / 'Infinity'::float4;
+ ?column?
+----------
+ NaN
+(1 row)
+
+SELECT '42'::float4 / 'Infinity'::float4;
+ ?column?
+----------
+ 0
+(1 row)
+
+SELECT 'nan'::float4 / 'nan'::float4;
+ ?column?
+----------
+ NaN
+(1 row)
+
+SELECT 'nan'::float4 / '0'::float4;
+ ?column?
+----------
+ NaN
+(1 row)
+
+SELECT 'nan'::numeric::float4;
+ float4
+--------
+ NaN
+(1 row)
+
+SELECT * FROM FLOAT4_TBL;
+ f1
+---------------
+ 0
+ 1004.3
+ -34.84
+ 1.2345679e+20
+ 1.2345679e-20
+(5 rows)
+
+SELECT f.* FROM FLOAT4_TBL f WHERE f.f1 <> '1004.3';
+ f1
+---------------
+ 0
+ -34.84
+ 1.2345679e+20
+ 1.2345679e-20
+(4 rows)
+
+SELECT f.* FROM FLOAT4_TBL f WHERE f.f1 = '1004.3';
+ f1
+--------
+ 1004.3
+(1 row)
+
+SELECT f.* FROM FLOAT4_TBL f WHERE '1004.3' > f.f1;
+ f1
+---------------
+ 0
+ -34.84
+ 1.2345679e-20
+(3 rows)
+
+SELECT f.* FROM FLOAT4_TBL f WHERE f.f1 < '1004.3';
+ f1
+---------------
+ 0
+ -34.84
+ 1.2345679e-20
+(3 rows)
+
+SELECT f.* FROM FLOAT4_TBL f WHERE '1004.3' >= f.f1;
+ f1
+---------------
+ 0
+ 1004.3
+ -34.84
+ 1.2345679e-20
+(4 rows)
+
+SELECT f.* FROM FLOAT4_TBL f WHERE f.f1 <= '1004.3';
+ f1
+---------------
+ 0
+ 1004.3
+ -34.84
+ 1.2345679e-20
+(4 rows)
+
+SELECT f.f1, f.f1 * '-10' AS x FROM FLOAT4_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | x
+---------------+----------------
+ 1004.3 | -10043
+ 1.2345679e+20 | -1.2345678e+21
+ 1.2345679e-20 | -1.2345678e-19
+(3 rows)
+
+SELECT f.f1, f.f1 + '-10' AS x FROM FLOAT4_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | x
+---------------+---------------
+ 1004.3 | 994.3
+ 1.2345679e+20 | 1.2345679e+20
+ 1.2345679e-20 | -10
+(3 rows)
+
+SELECT f.f1, f.f1 / '-10' AS x FROM FLOAT4_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | x
+---------------+----------------
+ 1004.3 | -100.43
+ 1.2345679e+20 | -1.2345679e+19
+ 1.2345679e-20 | -1.2345679e-21
+(3 rows)
+
+SELECT f.f1, f.f1 - '-10' AS x FROM FLOAT4_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | x
+---------------+---------------
+ 1004.3 | 1014.3
+ 1.2345679e+20 | 1.2345679e+20
+ 1.2345679e-20 | 10
+(3 rows)
+
+-- test divide by zero
+SELECT f.f1 / '0.0' from FLOAT4_TBL f;
+ERROR: division by zero
+SELECT * FROM FLOAT4_TBL;
+ f1
+---------------
+ 0
+ 1004.3
+ -34.84
+ 1.2345679e+20
+ 1.2345679e-20
+(5 rows)
+
+-- test the unary float4abs operator
+SELECT f.f1, @f.f1 AS abs_f1 FROM FLOAT4_TBL f;
+ f1 | abs_f1
+---------------+---------------
+ 0 | 0
+ 1004.3 | 1004.3
+ -34.84 | 34.84
+ 1.2345679e+20 | 1.2345679e+20
+ 1.2345679e-20 | 1.2345679e-20
+(5 rows)
+
+UPDATE FLOAT4_TBL
+ SET f1 = FLOAT4_TBL.f1 * '-1'
+ WHERE FLOAT4_TBL.f1 > '0.0';
+SELECT * FROM FLOAT4_TBL;
+ f1
+----------------
+ 0
+ -34.84
+ -1004.3
+ -1.2345679e+20
+ -1.2345679e-20
+(5 rows)
+
+-- test edge-case coercions to integer
+SELECT '32767.4'::float4::int2;
+ int2
+-------
+ 32767
+(1 row)
+
+SELECT '32767.6'::float4::int2;
+ERROR: smallint out of range
+SELECT '-32768.4'::float4::int2;
+ int2
+--------
+ -32768
+(1 row)
+
+SELECT '-32768.6'::float4::int2;
+ERROR: smallint out of range
+SELECT '2147483520'::float4::int4;
+ int4
+------------
+ 2147483520
+(1 row)
+
+SELECT '2147483647'::float4::int4;
+ERROR: integer out of range
+SELECT '-2147483648.5'::float4::int4;
+ int4
+-------------
+ -2147483648
+(1 row)
+
+SELECT '-2147483900'::float4::int4;
+ERROR: integer out of range
+SELECT '9223369837831520256'::float4::int8;
+ int8
+---------------------
+ 9223369837831520256
+(1 row)
+
+SELECT '9223372036854775807'::float4::int8;
+ERROR: bigint out of range
+SELECT '-9223372036854775808.5'::float4::int8;
+ int8
+----------------------
+ -9223372036854775808
+(1 row)
+
+SELECT '-9223380000000000000'::float4::int8;
+ERROR: bigint out of range
+-- 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);
+ float4send
+------------
+ \x1f6c1e4a
+(1 row)
+
+SELECT float4send('67e14'::float4);
+ float4send
+------------
+ \x59be6cea
+(1 row)
+
+SELECT float4send('985e15'::float4);
+ float4send
+------------
+ \x5d5ab6c4
+(1 row)
+
+SELECT float4send('55895e-16'::float4);
+ float4send
+------------
+ \x2cc4a9bd
+(1 row)
+
+SELECT float4send('7038531e-32'::float4);
+ float4send
+------------
+ \x15ae43fe
+(1 row)
+
+SELECT float4send('702990899e-20'::float4);
+ float4send
+------------
+ \x2cf757ca
+(1 row)
+
+SELECT float4send('3e-23'::float4);
+ float4send
+------------
+ \x1a111234
+(1 row)
+
+SELECT float4send('57e18'::float4);
+ float4send
+------------
+ \x6045c22c
+(1 row)
+
+SELECT float4send('789e-35'::float4);
+ float4send
+------------
+ \x0a23de70
+(1 row)
+
+SELECT float4send('2539e-18'::float4);
+ float4send
+------------
+ \x2736f449
+(1 row)
+
+SELECT float4send('76173e28'::float4);
+ float4send
+------------
+ \x7616398a
+(1 row)
+
+SELECT float4send('887745e-11'::float4);
+ float4send
+------------
+ \x3714f05c
+(1 row)
+
+SELECT float4send('5382571e-37'::float4);
+ float4send
+------------
+ \x0d2eaca7
+(1 row)
+
+SELECT float4send('82381273e-35'::float4);
+ float4send
+------------
+ \x128289d0
+(1 row)
+
+SELECT float4send('750486563e-38'::float4);
+ float4send
+------------
+ \x0f18377e
+(1 row)
+
+-- 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);
+ float4send
+------------
+ \x00800000
+(1 row)
+
+SELECT float4send('1.1754944e-38'::float4);
+ float4send
+------------
+ \x00800000
+(1 row)
+
+-- 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';
+NOTICE: return type xfloat4 is only a shell
+create function xfloat4out(xfloat4) returns cstring immutable strict
+ language internal as 'int4out';
+NOTICE: argument type xfloat4 is only a shell
+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;
+ ibits | flt
+------------+---------------
+ \x00000001 | 1e-45
+ \x00000002 | 3e-45
+ \x00000003 | 4e-45
+ \x00000010 | 2.2e-44
+ \x00000011 | 2.4e-44
+ \x00000100 | 3.59e-43
+ \x00000101 | 3.6e-43
+ \x00004000 | 2.2959e-41
+ \x00004001 | 2.296e-41
+ \x00080000 | 7.34684e-40
+ \x00080001 | 7.34685e-40
+ \x0053c4f4 | 7.693e-39
+ \x006c85c4 | 9.96622e-39
+ \x0041ca76 | 6.041937e-39
+ \x004b7678 | 6.930161e-39
+ \x00000007 | 1e-44
+ \x00424fe2 | 6.0898e-39
+ \x007ffff0 | 1.1754921e-38
+ \x007ffff1 | 1.1754922e-38
+ \x007ffffe | 1.1754941e-38
+ \x007fffff | 1.1754942e-38
+(21 rows)
+
+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;
+ ibits | flt | r_flt | obits | correct
+------------+----------------+----------------+------------+---------
+ \x00000000 | 0 | 0 | \x00000000 | t
+ \x00800000 | 1.1754944e-38 | 1.1754944e-38 | \x00800000 | t
+ \x00800001 | 1.1754945e-38 | 1.1754945e-38 | \x00800001 | t
+ \x00800004 | 1.1754949e-38 | 1.1754949e-38 | \x00800004 | t
+ \x00800005 | 1.175495e-38 | 1.175495e-38 | \x00800005 | t
+ \x00800006 | 1.1754952e-38 | 1.1754952e-38 | \x00800006 | t
+ \x008002f1 | 1.1755999e-38 | 1.1755999e-38 | \x008002f1 | t
+ \x008002f2 | 1.1756e-38 | 1.1756e-38 | \x008002f2 | t
+ \x008002f3 | 1.1756001e-38 | 1.1756001e-38 | \x008002f3 | t
+ \x00800e17 | 1.1759998e-38 | 1.1759998e-38 | \x00800e17 | t
+ \x00800e18 | 1.176e-38 | 1.176e-38 | \x00800e18 | t
+ \x00800e19 | 1.1760001e-38 | 1.1760001e-38 | \x00800e19 | t
+ \x01000001 | 2.350989e-38 | 2.350989e-38 | \x01000001 | t
+ \x01102843 | 2.647751e-38 | 2.647751e-38 | \x01102843 | t
+ \x01a52c98 | 6.0675416e-38 | 6.0675416e-38 | \x01a52c98 | t
+ \x0219c229 | 1.1296386e-37 | 1.1296386e-37 | \x0219c229 | t
+ \x02e4464d | 3.354194e-37 | 3.354194e-37 | \x02e4464d | t
+ \x037343c1 | 7.148906e-37 | 7.148906e-37 | \x037343c1 | t
+ \x03a91b36 | 9.939175e-37 | 9.939175e-37 | \x03a91b36 | t
+ \x047ada65 | 2.948764e-36 | 2.948764e-36 | \x047ada65 | t
+ \x0496fe87 | 3.5498577e-36 | 3.5498577e-36 | \x0496fe87 | t
+ \x0550844f | 9.804414e-36 | 9.804414e-36 | \x0550844f | t
+ \x05999da3 | 1.4445957e-35 | 1.4445957e-35 | \x05999da3 | t
+ \x060ea5e2 | 2.6829103e-35 | 2.6829103e-35 | \x060ea5e2 | t
+ \x06e63c45 | 8.660494e-35 | 8.660494e-35 | \x06e63c45 | t
+ \x07f1e548 | 3.639641e-34 | 3.639641e-34 | \x07f1e548 | t
+ \x0fc5282b | 1.9441172e-29 | 1.9441172e-29 | \x0fc5282b | t
+ \x1f850283 | 5.6331846e-20 | 5.6331846e-20 | \x1f850283 | t
+ \x2874a9d6 | 1.3581548e-14 | 1.3581548e-14 | \x2874a9d6 | t
+ \x3356bf94 | 4.9999997e-08 | 4.9999997e-08 | \x3356bf94 | t
+ \x3356bf95 | 5e-08 | 5e-08 | \x3356bf95 | t
+ \x3356bf96 | 5.0000004e-08 | 5.0000004e-08 | \x3356bf96 | t
+ \x33d6bf94 | 9.9999994e-08 | 9.9999994e-08 | \x33d6bf94 | t
+ \x33d6bf95 | 1e-07 | 1e-07 | \x33d6bf95 | t
+ \x33d6bf96 | 1.0000001e-07 | 1.0000001e-07 | \x33d6bf96 | t
+ \x34a10faf | 2.9999998e-07 | 2.9999998e-07 | \x34a10faf | t
+ \x34a10fb0 | 3e-07 | 3e-07 | \x34a10fb0 | t
+ \x34a10fb1 | 3.0000004e-07 | 3.0000004e-07 | \x34a10fb1 | t
+ \x350637bc | 4.9999994e-07 | 4.9999994e-07 | \x350637bc | t
+ \x350637bd | 5e-07 | 5e-07 | \x350637bd | t
+ \x350637be | 5.0000006e-07 | 5.0000006e-07 | \x350637be | t
+ \x35719786 | 8.999999e-07 | 8.999999e-07 | \x35719786 | t
+ \x35719787 | 9e-07 | 9e-07 | \x35719787 | t
+ \x35719788 | 9.0000003e-07 | 9.0000003e-07 | \x35719788 | t
+ \x358637bc | 9.999999e-07 | 9.999999e-07 | \x358637bc | t
+ \x358637bd | 1e-06 | 1e-06 | \x358637bd | t
+ \x358637be | 1.0000001e-06 | 1.0000001e-06 | \x358637be | t
+ \x36a7c5ab | 4.9999994e-06 | 4.9999994e-06 | \x36a7c5ab | t
+ \x36a7c5ac | 5e-06 | 5e-06 | \x36a7c5ac | t
+ \x36a7c5ad | 5.0000003e-06 | 5.0000003e-06 | \x36a7c5ad | t
+ \x3727c5ab | 9.999999e-06 | 9.999999e-06 | \x3727c5ab | t
+ \x3727c5ac | 1e-05 | 1e-05 | \x3727c5ac | t
+ \x3727c5ad | 1.0000001e-05 | 1.0000001e-05 | \x3727c5ad | t
+ \x38d1b714 | 9.9999976e-05 | 9.9999976e-05 | \x38d1b714 | t
+ \x38d1b715 | 9.999998e-05 | 9.999998e-05 | \x38d1b715 | t
+ \x38d1b716 | 9.999999e-05 | 9.999999e-05 | \x38d1b716 | t
+ \x38d1b717 | 0.0001 | 0.0001 | \x38d1b717 | t
+ \x38d1b718 | 0.000100000005 | 0.000100000005 | \x38d1b718 | t
+ \x38d1b719 | 0.00010000001 | 0.00010000001 | \x38d1b719 | t
+ \x38d1b71a | 0.00010000002 | 0.00010000002 | \x38d1b71a | t
+ \x38d1b71b | 0.00010000003 | 0.00010000003 | \x38d1b71b | t
+ \x38d1b71c | 0.000100000034 | 0.000100000034 | \x38d1b71c | t
+ \x38d1b71d | 0.00010000004 | 0.00010000004 | \x38d1b71d | t
+ \x38dffffe | 0.00010681151 | 0.00010681151 | \x38dffffe | t
+ \x38dfffff | 0.000106811516 | 0.000106811516 | \x38dfffff | t
+ \x38e00000 | 0.00010681152 | 0.00010681152 | \x38e00000 | t
+ \x38efffff | 0.00011444091 | 0.00011444091 | \x38efffff | t
+ \x38f00000 | 0.00011444092 | 0.00011444092 | \x38f00000 | t
+ \x38f00001 | 0.000114440925 | 0.000114440925 | \x38f00001 | t
+ \x3a83126e | 0.0009999999 | 0.0009999999 | \x3a83126e | t
+ \x3a83126f | 0.001 | 0.001 | \x3a83126f | t
+ \x3a831270 | 0.0010000002 | 0.0010000002 | \x3a831270 | t
+ \x3c23d709 | 0.009999999 | 0.009999999 | \x3c23d709 | t
+ \x3c23d70a | 0.01 | 0.01 | \x3c23d70a | t
+ \x3c23d70b | 0.010000001 | 0.010000001 | \x3c23d70b | t
+ \x3dcccccc | 0.099999994 | 0.099999994 | \x3dcccccc | t
+ \x3dcccccd | 0.1 | 0.1 | \x3dcccccd | t
+ \x3dccccce | 0.10000001 | 0.10000001 | \x3dccccce | t
+ \x3dcccd6f | 0.10000121 | 0.10000121 | \x3dcccd6f | t
+ \x3dcccd70 | 0.100001216 | 0.100001216 | \x3dcccd70 | t
+ \x3dcccd71 | 0.10000122 | 0.10000122 | \x3dcccd71 | t
+ \x3effffff | 0.49999997 | 0.49999997 | \x3effffff | t
+ \x3f000000 | 0.5 | 0.5 | \x3f000000 | t
+ \x3f000001 | 0.50000006 | 0.50000006 | \x3f000001 | t
+ \x3f333332 | 0.6999999 | 0.6999999 | \x3f333332 | t
+ \x3f333333 | 0.7 | 0.7 | \x3f333333 | t
+ \x3f333334 | 0.70000005 | 0.70000005 | \x3f333334 | t
+ \x3f666665 | 0.8999999 | 0.8999999 | \x3f666665 | t
+ \x3f666666 | 0.9 | 0.9 | \x3f666666 | t
+ \x3f666667 | 0.90000004 | 0.90000004 | \x3f666667 | t
+ \x3f7d70a3 | 0.98999995 | 0.98999995 | \x3f7d70a3 | t
+ \x3f7d70a4 | 0.99 | 0.99 | \x3f7d70a4 | t
+ \x3f7d70a5 | 0.99000007 | 0.99000007 | \x3f7d70a5 | t
+ \x3f7fbe76 | 0.99899995 | 0.99899995 | \x3f7fbe76 | t
+ \x3f7fbe77 | 0.999 | 0.999 | \x3f7fbe77 | t
+ \x3f7fbe78 | 0.9990001 | 0.9990001 | \x3f7fbe78 | t
+ \x3f7ff971 | 0.9998999 | 0.9998999 | \x3f7ff971 | t
+ \x3f7ff972 | 0.9999 | 0.9999 | \x3f7ff972 | t
+ \x3f7ff973 | 0.99990004 | 0.99990004 | \x3f7ff973 | t
+ \x3f7fff57 | 0.9999899 | 0.9999899 | \x3f7fff57 | t
+ \x3f7fff58 | 0.99999 | 0.99999 | \x3f7fff58 | t
+ \x3f7fff59 | 0.99999005 | 0.99999005 | \x3f7fff59 | t
+ \x3f7fffee | 0.9999989 | 0.9999989 | \x3f7fffee | t
+ \x3f7fffef | 0.999999 | 0.999999 | \x3f7fffef | t
+ \x3f7ffff0 | 0.99999905 | 0.99999905 | \x3f7ffff0 | t
+ \x3f7ffff1 | 0.9999991 | 0.9999991 | \x3f7ffff1 | t
+ \x3f7ffff2 | 0.99999917 | 0.99999917 | \x3f7ffff2 | t
+ \x3f7ffff3 | 0.9999992 | 0.9999992 | \x3f7ffff3 | t
+ \x3f7ffff4 | 0.9999993 | 0.9999993 | \x3f7ffff4 | t
+ \x3f7ffff5 | 0.99999934 | 0.99999934 | \x3f7ffff5 | t
+ \x3f7ffff6 | 0.9999994 | 0.9999994 | \x3f7ffff6 | t
+ \x3f7ffff7 | 0.99999946 | 0.99999946 | \x3f7ffff7 | t
+ \x3f7ffff8 | 0.9999995 | 0.9999995 | \x3f7ffff8 | t
+ \x3f7ffff9 | 0.9999996 | 0.9999996 | \x3f7ffff9 | t
+ \x3f7ffffa | 0.99999964 | 0.99999964 | \x3f7ffffa | t
+ \x3f7ffffb | 0.9999997 | 0.9999997 | \x3f7ffffb | t
+ \x3f7ffffc | 0.99999976 | 0.99999976 | \x3f7ffffc | t
+ \x3f7ffffd | 0.9999998 | 0.9999998 | \x3f7ffffd | t
+ \x3f7ffffe | 0.9999999 | 0.9999999 | \x3f7ffffe | t
+ \x3f7fffff | 0.99999994 | 0.99999994 | \x3f7fffff | t
+ \x3f800000 | 1 | 1 | \x3f800000 | t
+ \x3f800001 | 1.0000001 | 1.0000001 | \x3f800001 | t
+ \x3f800002 | 1.0000002 | 1.0000002 | \x3f800002 | t
+ \x3f800003 | 1.0000004 | 1.0000004 | \x3f800003 | t
+ \x3f800004 | 1.0000005 | 1.0000005 | \x3f800004 | t
+ \x3f800005 | 1.0000006 | 1.0000006 | \x3f800005 | t
+ \x3f800006 | 1.0000007 | 1.0000007 | \x3f800006 | t
+ \x3f800007 | 1.0000008 | 1.0000008 | \x3f800007 | t
+ \x3f800008 | 1.000001 | 1.000001 | \x3f800008 | t
+ \x3f800009 | 1.0000011 | 1.0000011 | \x3f800009 | t
+ \x3f80000f | 1.0000018 | 1.0000018 | \x3f80000f | t
+ \x3f800010 | 1.0000019 | 1.0000019 | \x3f800010 | t
+ \x3f800011 | 1.000002 | 1.000002 | \x3f800011 | t
+ \x3f800012 | 1.0000021 | 1.0000021 | \x3f800012 | t
+ \x3f800013 | 1.0000023 | 1.0000023 | \x3f800013 | t
+ \x3f800014 | 1.0000024 | 1.0000024 | \x3f800014 | t
+ \x3f800017 | 1.0000027 | 1.0000027 | \x3f800017 | t
+ \x3f800018 | 1.0000029 | 1.0000029 | \x3f800018 | t
+ \x3f800019 | 1.000003 | 1.000003 | \x3f800019 | t
+ \x3f80001a | 1.0000031 | 1.0000031 | \x3f80001a | t
+ \x3f80001b | 1.0000032 | 1.0000032 | \x3f80001b | t
+ \x3f80001c | 1.0000033 | 1.0000033 | \x3f80001c | t
+ \x3f800029 | 1.0000049 | 1.0000049 | \x3f800029 | t
+ \x3f80002a | 1.000005 | 1.000005 | \x3f80002a | t
+ \x3f80002b | 1.0000051 | 1.0000051 | \x3f80002b | t
+ \x3f800053 | 1.0000099 | 1.0000099 | \x3f800053 | t
+ \x3f800054 | 1.00001 | 1.00001 | \x3f800054 | t
+ \x3f800055 | 1.0000101 | 1.0000101 | \x3f800055 | t
+ \x3f800346 | 1.0000999 | 1.0000999 | \x3f800346 | t
+ \x3f800347 | 1.0001 | 1.0001 | \x3f800347 | t
+ \x3f800348 | 1.0001001 | 1.0001001 | \x3f800348 | t
+ \x3f8020c4 | 1.0009999 | 1.0009999 | \x3f8020c4 | t
+ \x3f8020c5 | 1.001 | 1.001 | \x3f8020c5 | t
+ \x3f8020c6 | 1.0010002 | 1.0010002 | \x3f8020c6 | t
+ \x3f8147ad | 1.0099999 | 1.0099999 | \x3f8147ad | t
+ \x3f8147ae | 1.01 | 1.01 | \x3f8147ae | t
+ \x3f8147af | 1.0100001 | 1.0100001 | \x3f8147af | t
+ \x3f8ccccc | 1.0999999 | 1.0999999 | \x3f8ccccc | t
+ \x3f8ccccd | 1.1 | 1.1 | \x3f8ccccd | t
+ \x3f8cccce | 1.1000001 | 1.1000001 | \x3f8cccce | t
+ \x3fc90fdb | 1.5707964 | 1.5707964 | \x3fc90fdb | t
+ \x402df854 | 2.7182817 | 2.7182817 | \x402df854 | t
+ \x40490fdb | 3.1415927 | 3.1415927 | \x40490fdb | t
+ \x409fffff | 4.9999995 | 4.9999995 | \x409fffff | t
+ \x40a00000 | 5 | 5 | \x40a00000 | t
+ \x40a00001 | 5.0000005 | 5.0000005 | \x40a00001 | t
+ \x40afffff | 5.4999995 | 5.4999995 | \x40afffff | t
+ \x40b00000 | 5.5 | 5.5 | \x40b00000 | t
+ \x40b00001 | 5.5000005 | 5.5000005 | \x40b00001 | t
+ \x411fffff | 9.999999 | 9.999999 | \x411fffff | t
+ \x41200000 | 10 | 10 | \x41200000 | t
+ \x41200001 | 10.000001 | 10.000001 | \x41200001 | t
+ \x42c7ffff | 99.99999 | 99.99999 | \x42c7ffff | t
+ \x42c80000 | 100 | 100 | \x42c80000 | t
+ \x42c80001 | 100.00001 | 100.00001 | \x42c80001 | t
+ \x4479ffff | 999.99994 | 999.99994 | \x4479ffff | t
+ \x447a0000 | 1000 | 1000 | \x447a0000 | t
+ \x447a0001 | 1000.00006 | 1000.00006 | \x447a0001 | t
+ \x461c3fff | 9999.999 | 9999.999 | \x461c3fff | t
+ \x461c4000 | 10000 | 10000 | \x461c4000 | t
+ \x461c4001 | 10000.001 | 10000.001 | \x461c4001 | t
+ \x47c34fff | 99999.99 | 99999.99 | \x47c34fff | t
+ \x47c35000 | 100000 | 100000 | \x47c35000 | t
+ \x47c35001 | 100000.01 | 100000.01 | \x47c35001 | t
+ \x497423ff | 999999.94 | 999999.94 | \x497423ff | t
+ \x49742400 | 1e+06 | 1e+06 | \x49742400 | t
+ \x49742401 | 1.00000006e+06 | 1.00000006e+06 | \x49742401 | t
+ \x4b18967f | 9.999999e+06 | 9.999999e+06 | \x4b18967f | t
+ \x4b189680 | 1e+07 | 1e+07 | \x4b189680 | t
+ \x4b189681 | 1.0000001e+07 | 1.0000001e+07 | \x4b189681 | t
+ \x4cbebc1f | 9.999999e+07 | 9.999999e+07 | \x4cbebc1f | t
+ \x4cbebc20 | 1e+08 | 1e+08 | \x4cbebc20 | t
+ \x4cbebc21 | 1.0000001e+08 | 1.0000001e+08 | \x4cbebc21 | t
+ \x4e6e6b27 | 9.9999994e+08 | 9.9999994e+08 | \x4e6e6b27 | t
+ \x4e6e6b28 | 1e+09 | 1e+09 | \x4e6e6b28 | t
+ \x4e6e6b29 | 1.00000006e+09 | 1.00000006e+09 | \x4e6e6b29 | t
+ \x501502f8 | 9.999999e+09 | 9.999999e+09 | \x501502f8 | t
+ \x501502f9 | 1e+10 | 1e+10 | \x501502f9 | t
+ \x501502fa | 1.0000001e+10 | 1.0000001e+10 | \x501502fa | t
+ \x51ba43b6 | 9.999999e+10 | 9.999999e+10 | \x51ba43b6 | t
+ \x51ba43b7 | 1e+11 | 1e+11 | \x51ba43b7 | t
+ \x51ba43b8 | 1.0000001e+11 | 1.0000001e+11 | \x51ba43b8 | t
+ \x1f6c1e4a | 5e-20 | 5e-20 | \x1f6c1e4a | t
+ \x59be6cea | 6.7e+15 | 6.7e+15 | \x59be6cea | t
+ \x5d5ab6c4 | 9.85e+17 | 9.85e+17 | \x5d5ab6c4 | t
+ \x2cc4a9bd | 5.5895e-12 | 5.5895e-12 | \x2cc4a9bd | t
+ \x15ae43fd | 7.038531e-26 | 7.0385313e-26 | \x15ae43fe | f
+ \x2cf757ca | 7.0299088e-12 | 7.0299088e-12 | \x2cf757ca | t
+ \x665ba998 | 2.5933168e+23 | 2.5933168e+23 | \x665ba998 | t
+ \x743c3324 | 5.9642887e+31 | 5.9642887e+31 | \x743c3324 | t
+ \x47f1205a | 123456.7 | 123456.7 | \x47f1205a | t
+ \x4640e6ae | 12345.67 | 12345.67 | \x4640e6ae | t
+ \x449a5225 | 1234.567 | 1234.567 | \x449a5225 | t
+ \x42f6e9d5 | 123.4567 | 123.4567 | \x42f6e9d5 | t
+ \x414587dd | 12.34567 | 12.34567 | \x414587dd | t
+ \x3f9e064b | 1.234567 | 1.234567 | \x3f9e064b | t
+ \x4c000004 | 3.3554448e+07 | 3.3554448e+07 | \x4c000004 | t
+ \x50061c46 | 8.999999e+09 | 8.999999e+09 | \x50061c46 | t
+ \x510006a8 | 3.4366718e+10 | 3.4366718e+10 | \x510006a8 | t
+ \x48951f84 | 305404.12 | 305404.12 | \x48951f84 | t
+ \x45fd1840 | 8099.0312 | 8099.0312 | \x45fd1840 | t
+ \x39800000 | 0.00024414062 | 0.00024414062 | \x39800000 | t
+ \x3b200000 | 0.0024414062 | 0.0024414062 | \x3b200000 | t
+ \x3b900000 | 0.0043945312 | 0.0043945312 | \x3b900000 | t
+ \x3bd00000 | 0.0063476562 | 0.0063476562 | \x3bd00000 | t
+ \x63800000 | 4.7223665e+21 | 4.7223665e+21 | \x63800000 | t
+ \x4b000000 | 8.388608e+06 | 8.388608e+06 | \x4b000000 | t
+ \x4b800000 | 1.6777216e+07 | 1.6777216e+07 | \x4b800000 | t
+ \x4c000001 | 3.3554436e+07 | 3.3554436e+07 | \x4c000001 | t
+ \x4c800b0d | 6.7131496e+07 | 6.7131496e+07 | \x4c800b0d | t
+ \x00d24584 | 1.9310392e-38 | 1.9310392e-38 | \x00d24584 | t
+ \x00d90b88 | 1.993244e-38 | 1.993244e-38 | \x00d90b88 | t
+ \x45803f34 | 4103.9004 | 4103.9004 | \x45803f34 | t
+ \x4f9f24f7 | 5.3399997e+09 | 5.3399997e+09 | \x4f9f24f7 | t
+ \x3a8722c3 | 0.0010310042 | 0.0010310042 | \x3a8722c3 | t
+ \x5c800041 | 2.882326e+17 | 2.882326e+17 | \x5c800041 | t
+ \x15ae43fd | 7.038531e-26 | 7.0385313e-26 | \x15ae43fe | f
+ \x5d4cccfb | 9.223404e+17 | 9.223404e+17 | \x5d4cccfb | t
+ \x4c800001 | 6.710887e+07 | 6.710887e+07 | \x4c800001 | t
+ \x57800ed8 | 2.816025e+14 | 2.816025e+14 | \x57800ed8 | t
+ \x5f000000 | 9.223372e+18 | 9.223372e+18 | \x5f000000 | t
+ \x700000f0 | 1.5846086e+29 | 1.5846086e+29 | \x700000f0 | t
+ \x5f23e9ac | 1.1811161e+19 | 1.1811161e+19 | \x5f23e9ac | t
+ \x5e9502f9 | 5.368709e+18 | 5.368709e+18 | \x5e9502f9 | t
+ \x5e8012b1 | 4.6143166e+18 | 4.6143166e+18 | \x5e8012b1 | t
+ \x3c000028 | 0.007812537 | 0.007812537 | \x3c000028 | t
+ \x60cde861 | 1.18697725e+20 | 1.18697725e+20 | \x60cde861 | t
+ \x03aa2a50 | 1.00014165e-36 | 1.00014165e-36 | \x03aa2a50 | t
+ \x43480000 | 200 | 200 | \x43480000 | t
+ \x4c000000 | 3.3554432e+07 | 3.3554432e+07 | \x4c000000 | t
+ \x5d1502f9 | 6.7108864e+17 | 6.7108864e+17 | \x5d1502f9 | t
+ \x5d9502f9 | 1.3421773e+18 | 1.3421773e+18 | \x5d9502f9 | t
+ \x5e1502f9 | 2.6843546e+18 | 2.6843546e+18 | \x5e1502f9 | t
+ \x3f99999a | 1.2 | 1.2 | \x3f99999a | t
+ \x3f9d70a4 | 1.23 | 1.23 | \x3f9d70a4 | t
+ \x3f9df3b6 | 1.234 | 1.234 | \x3f9df3b6 | t
+ \x3f9e0419 | 1.2345 | 1.2345 | \x3f9e0419 | t
+ \x3f9e0610 | 1.23456 | 1.23456 | \x3f9e0610 | t
+ \x3f9e064b | 1.234567 | 1.234567 | \x3f9e064b | t
+ \x3f9e0651 | 1.2345678 | 1.2345678 | \x3f9e0651 | t
+ \x03d20cfe | 1.23456735e-36 | 1.23456735e-36 | \x03d20cfe | t
+(261 rows)
+
+-- clean up, lest opr_sanity complain
+drop type xfloat4 cascade;
+NOTICE: drop cascades to 6 other objects
+DETAIL: drop cascades to function xfloat4in(cstring)
+drop cascades to function xfloat4out(xfloat4)
+drop cascades to cast from xfloat4 to real
+drop cascades to cast from real to xfloat4
+drop cascades to cast from xfloat4 to integer
+drop cascades to cast from integer to xfloat4
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
new file mode 100644
index 0000000..6ad5d00
--- /dev/null
+++ b/src/test/regress/expected/float4.out
@@ -0,0 +1,961 @@
+--
+-- 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');
+ERROR: "10e70" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e70');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70');
+ERROR: "-10e70" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70');
+ERROR: "10e-70" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70');
+ERROR: "-10e-70" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('10e70'::float8);
+ERROR: value out of range: overflow
+INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70'::float8);
+ERROR: value out of range: overflow
+INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70'::float8);
+ERROR: value out of range: underflow
+INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70'::float8);
+ERROR: value out of range: underflow
+INSERT INTO FLOAT4_TBL(f1) VALUES ('10e400');
+ERROR: "10e400" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e400');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e400');
+ERROR: "-10e400" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e400');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-400');
+ERROR: "10e-400" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-400');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-400');
+ERROR: "-10e-400" is out of range for type real
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-400');
+ ^
+-- bad input
+INSERT INTO FLOAT4_TBL(f1) VALUES ('');
+ERROR: invalid input syntax for type real: ""
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES (' ');
+ERROR: invalid input syntax for type real: " "
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES (' ');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('xyz');
+ERROR: invalid input syntax for type real: "xyz"
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('xyz');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('5.0.0');
+ERROR: invalid input syntax for type real: "5.0.0"
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('5.0.0');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('5 . 0');
+ERROR: invalid input syntax for type real: "5 . 0"
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('5 . 0');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('5. 0');
+ERROR: invalid input syntax for type real: "5. 0"
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('5. 0');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES (' - 3.0');
+ERROR: invalid input syntax for type real: " - 3.0"
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES (' - 3.0');
+ ^
+INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5');
+ERROR: invalid input syntax for type real: "123 5"
+LINE 1: INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5');
+ ^
+-- special inputs
+SELECT 'NaN'::float4;
+ float4
+--------
+ NaN
+(1 row)
+
+SELECT 'nan'::float4;
+ float4
+--------
+ NaN
+(1 row)
+
+SELECT ' NAN '::float4;
+ float4
+--------
+ NaN
+(1 row)
+
+SELECT 'infinity'::float4;
+ float4
+----------
+ Infinity
+(1 row)
+
+SELECT ' -INFINiTY '::float4;
+ float4
+-----------
+ -Infinity
+(1 row)
+
+-- bad special inputs
+SELECT 'N A N'::float4;
+ERROR: invalid input syntax for type real: "N A N"
+LINE 1: SELECT 'N A N'::float4;
+ ^
+SELECT 'NaN x'::float4;
+ERROR: invalid input syntax for type real: "NaN x"
+LINE 1: SELECT 'NaN x'::float4;
+ ^
+SELECT ' INFINITY x'::float4;
+ERROR: invalid input syntax for type real: " INFINITY x"
+LINE 1: SELECT ' INFINITY x'::float4;
+ ^
+SELECT 'Infinity'::float4 + 100.0;
+ ?column?
+----------
+ Infinity
+(1 row)
+
+SELECT 'Infinity'::float4 / 'Infinity'::float4;
+ ?column?
+----------
+ NaN
+(1 row)
+
+SELECT '42'::float4 / 'Infinity'::float4;
+ ?column?
+----------
+ 0
+(1 row)
+
+SELECT 'nan'::float4 / 'nan'::float4;
+ ?column?
+----------
+ NaN
+(1 row)
+
+SELECT 'nan'::float4 / '0'::float4;
+ ?column?
+----------
+ NaN
+(1 row)
+
+SELECT 'nan'::numeric::float4;
+ float4
+--------
+ NaN
+(1 row)
+
+SELECT * FROM FLOAT4_TBL;
+ f1
+---------------
+ 0
+ 1004.3
+ -34.84
+ 1.2345679e+20
+ 1.2345679e-20
+(5 rows)
+
+SELECT f.* FROM FLOAT4_TBL f WHERE f.f1 <> '1004.3';
+ f1
+---------------
+ 0
+ -34.84
+ 1.2345679e+20
+ 1.2345679e-20
+(4 rows)
+
+SELECT f.* FROM FLOAT4_TBL f WHERE f.f1 = '1004.3';
+ f1
+--------
+ 1004.3
+(1 row)
+
+SELECT f.* FROM FLOAT4_TBL f WHERE '1004.3' > f.f1;
+ f1
+---------------
+ 0
+ -34.84
+ 1.2345679e-20
+(3 rows)
+
+SELECT f.* FROM FLOAT4_TBL f WHERE f.f1 < '1004.3';
+ f1
+---------------
+ 0
+ -34.84
+ 1.2345679e-20
+(3 rows)
+
+SELECT f.* FROM FLOAT4_TBL f WHERE '1004.3' >= f.f1;
+ f1
+---------------
+ 0
+ 1004.3
+ -34.84
+ 1.2345679e-20
+(4 rows)
+
+SELECT f.* FROM FLOAT4_TBL f WHERE f.f1 <= '1004.3';
+ f1
+---------------
+ 0
+ 1004.3
+ -34.84
+ 1.2345679e-20
+(4 rows)
+
+SELECT f.f1, f.f1 * '-10' AS x FROM FLOAT4_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | x
+---------------+----------------
+ 1004.3 | -10043
+ 1.2345679e+20 | -1.2345678e+21
+ 1.2345679e-20 | -1.2345678e-19
+(3 rows)
+
+SELECT f.f1, f.f1 + '-10' AS x FROM FLOAT4_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | x
+---------------+---------------
+ 1004.3 | 994.3
+ 1.2345679e+20 | 1.2345679e+20
+ 1.2345679e-20 | -10
+(3 rows)
+
+SELECT f.f1, f.f1 / '-10' AS x FROM FLOAT4_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | x
+---------------+----------------
+ 1004.3 | -100.43
+ 1.2345679e+20 | -1.2345679e+19
+ 1.2345679e-20 | -1.2345679e-21
+(3 rows)
+
+SELECT f.f1, f.f1 - '-10' AS x FROM FLOAT4_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | x
+---------------+---------------
+ 1004.3 | 1014.3
+ 1.2345679e+20 | 1.2345679e+20
+ 1.2345679e-20 | 10
+(3 rows)
+
+-- test divide by zero
+SELECT f.f1 / '0.0' from FLOAT4_TBL f;
+ERROR: division by zero
+SELECT * FROM FLOAT4_TBL;
+ f1
+---------------
+ 0
+ 1004.3
+ -34.84
+ 1.2345679e+20
+ 1.2345679e-20
+(5 rows)
+
+-- test the unary float4abs operator
+SELECT f.f1, @f.f1 AS abs_f1 FROM FLOAT4_TBL f;
+ f1 | abs_f1
+---------------+---------------
+ 0 | 0
+ 1004.3 | 1004.3
+ -34.84 | 34.84
+ 1.2345679e+20 | 1.2345679e+20
+ 1.2345679e-20 | 1.2345679e-20
+(5 rows)
+
+UPDATE FLOAT4_TBL
+ SET f1 = FLOAT4_TBL.f1 * '-1'
+ WHERE FLOAT4_TBL.f1 > '0.0';
+SELECT * FROM FLOAT4_TBL;
+ f1
+----------------
+ 0
+ -34.84
+ -1004.3
+ -1.2345679e+20
+ -1.2345679e-20
+(5 rows)
+
+-- test edge-case coercions to integer
+SELECT '32767.4'::float4::int2;
+ int2
+-------
+ 32767
+(1 row)
+
+SELECT '32767.6'::float4::int2;
+ERROR: smallint out of range
+SELECT '-32768.4'::float4::int2;
+ int2
+--------
+ -32768
+(1 row)
+
+SELECT '-32768.6'::float4::int2;
+ERROR: smallint out of range
+SELECT '2147483520'::float4::int4;
+ int4
+------------
+ 2147483520
+(1 row)
+
+SELECT '2147483647'::float4::int4;
+ERROR: integer out of range
+SELECT '-2147483648.5'::float4::int4;
+ int4
+-------------
+ -2147483648
+(1 row)
+
+SELECT '-2147483900'::float4::int4;
+ERROR: integer out of range
+SELECT '9223369837831520256'::float4::int8;
+ int8
+---------------------
+ 9223369837831520256
+(1 row)
+
+SELECT '9223372036854775807'::float4::int8;
+ERROR: bigint out of range
+SELECT '-9223372036854775808.5'::float4::int8;
+ int8
+----------------------
+ -9223372036854775808
+(1 row)
+
+SELECT '-9223380000000000000'::float4::int8;
+ERROR: bigint out of range
+-- 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);
+ float4send
+------------
+ \x1f6c1e4a
+(1 row)
+
+SELECT float4send('67e14'::float4);
+ float4send
+------------
+ \x59be6cea
+(1 row)
+
+SELECT float4send('985e15'::float4);
+ float4send
+------------
+ \x5d5ab6c4
+(1 row)
+
+SELECT float4send('55895e-16'::float4);
+ float4send
+------------
+ \x2cc4a9bd
+(1 row)
+
+SELECT float4send('7038531e-32'::float4);
+ float4send
+------------
+ \x15ae43fd
+(1 row)
+
+SELECT float4send('702990899e-20'::float4);
+ float4send
+------------
+ \x2cf757ca
+(1 row)
+
+SELECT float4send('3e-23'::float4);
+ float4send
+------------
+ \x1a111234
+(1 row)
+
+SELECT float4send('57e18'::float4);
+ float4send
+------------
+ \x6045c22c
+(1 row)
+
+SELECT float4send('789e-35'::float4);
+ float4send
+------------
+ \x0a23de70
+(1 row)
+
+SELECT float4send('2539e-18'::float4);
+ float4send
+------------
+ \x2736f449
+(1 row)
+
+SELECT float4send('76173e28'::float4);
+ float4send
+------------
+ \x7616398a
+(1 row)
+
+SELECT float4send('887745e-11'::float4);
+ float4send
+------------
+ \x3714f05c
+(1 row)
+
+SELECT float4send('5382571e-37'::float4);
+ float4send
+------------
+ \x0d2eaca7
+(1 row)
+
+SELECT float4send('82381273e-35'::float4);
+ float4send
+------------
+ \x128289d1
+(1 row)
+
+SELECT float4send('750486563e-38'::float4);
+ float4send
+------------
+ \x0f18377e
+(1 row)
+
+-- 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);
+ float4send
+------------
+ \x00800000
+(1 row)
+
+SELECT float4send('1.1754944e-38'::float4);
+ float4send
+------------
+ \x00800000
+(1 row)
+
+-- 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';
+NOTICE: return type xfloat4 is only a shell
+create function xfloat4out(xfloat4) returns cstring immutable strict
+ language internal as 'int4out';
+NOTICE: argument type xfloat4 is only a shell
+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;
+ ibits | flt
+------------+---------------
+ \x00000001 | 1e-45
+ \x00000002 | 3e-45
+ \x00000003 | 4e-45
+ \x00000010 | 2.2e-44
+ \x00000011 | 2.4e-44
+ \x00000100 | 3.59e-43
+ \x00000101 | 3.6e-43
+ \x00004000 | 2.2959e-41
+ \x00004001 | 2.296e-41
+ \x00080000 | 7.34684e-40
+ \x00080001 | 7.34685e-40
+ \x0053c4f4 | 7.693e-39
+ \x006c85c4 | 9.96622e-39
+ \x0041ca76 | 6.041937e-39
+ \x004b7678 | 6.930161e-39
+ \x00000007 | 1e-44
+ \x00424fe2 | 6.0898e-39
+ \x007ffff0 | 1.1754921e-38
+ \x007ffff1 | 1.1754922e-38
+ \x007ffffe | 1.1754941e-38
+ \x007fffff | 1.1754942e-38
+(21 rows)
+
+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;
+ ibits | flt | r_flt | obits | correct
+------------+----------------+----------------+------------+---------
+ \x00000000 | 0 | 0 | \x00000000 | t
+ \x00800000 | 1.1754944e-38 | 1.1754944e-38 | \x00800000 | t
+ \x00800001 | 1.1754945e-38 | 1.1754945e-38 | \x00800001 | t
+ \x00800004 | 1.1754949e-38 | 1.1754949e-38 | \x00800004 | t
+ \x00800005 | 1.175495e-38 | 1.175495e-38 | \x00800005 | t
+ \x00800006 | 1.1754952e-38 | 1.1754952e-38 | \x00800006 | t
+ \x008002f1 | 1.1755999e-38 | 1.1755999e-38 | \x008002f1 | t
+ \x008002f2 | 1.1756e-38 | 1.1756e-38 | \x008002f2 | t
+ \x008002f3 | 1.1756001e-38 | 1.1756001e-38 | \x008002f3 | t
+ \x00800e17 | 1.1759998e-38 | 1.1759998e-38 | \x00800e17 | t
+ \x00800e18 | 1.176e-38 | 1.176e-38 | \x00800e18 | t
+ \x00800e19 | 1.1760001e-38 | 1.1760001e-38 | \x00800e19 | t
+ \x01000001 | 2.350989e-38 | 2.350989e-38 | \x01000001 | t
+ \x01102843 | 2.647751e-38 | 2.647751e-38 | \x01102843 | t
+ \x01a52c98 | 6.0675416e-38 | 6.0675416e-38 | \x01a52c98 | t
+ \x0219c229 | 1.1296386e-37 | 1.1296386e-37 | \x0219c229 | t
+ \x02e4464d | 3.354194e-37 | 3.354194e-37 | \x02e4464d | t
+ \x037343c1 | 7.148906e-37 | 7.148906e-37 | \x037343c1 | t
+ \x03a91b36 | 9.939175e-37 | 9.939175e-37 | \x03a91b36 | t
+ \x047ada65 | 2.948764e-36 | 2.948764e-36 | \x047ada65 | t
+ \x0496fe87 | 3.5498577e-36 | 3.5498577e-36 | \x0496fe87 | t
+ \x0550844f | 9.804414e-36 | 9.804414e-36 | \x0550844f | t
+ \x05999da3 | 1.4445957e-35 | 1.4445957e-35 | \x05999da3 | t
+ \x060ea5e2 | 2.6829103e-35 | 2.6829103e-35 | \x060ea5e2 | t
+ \x06e63c45 | 8.660494e-35 | 8.660494e-35 | \x06e63c45 | t
+ \x07f1e548 | 3.639641e-34 | 3.639641e-34 | \x07f1e548 | t
+ \x0fc5282b | 1.9441172e-29 | 1.9441172e-29 | \x0fc5282b | t
+ \x1f850283 | 5.6331846e-20 | 5.6331846e-20 | \x1f850283 | t
+ \x2874a9d6 | 1.3581548e-14 | 1.3581548e-14 | \x2874a9d6 | t
+ \x3356bf94 | 4.9999997e-08 | 4.9999997e-08 | \x3356bf94 | t
+ \x3356bf95 | 5e-08 | 5e-08 | \x3356bf95 | t
+ \x3356bf96 | 5.0000004e-08 | 5.0000004e-08 | \x3356bf96 | t
+ \x33d6bf94 | 9.9999994e-08 | 9.9999994e-08 | \x33d6bf94 | t
+ \x33d6bf95 | 1e-07 | 1e-07 | \x33d6bf95 | t
+ \x33d6bf96 | 1.0000001e-07 | 1.0000001e-07 | \x33d6bf96 | t
+ \x34a10faf | 2.9999998e-07 | 2.9999998e-07 | \x34a10faf | t
+ \x34a10fb0 | 3e-07 | 3e-07 | \x34a10fb0 | t
+ \x34a10fb1 | 3.0000004e-07 | 3.0000004e-07 | \x34a10fb1 | t
+ \x350637bc | 4.9999994e-07 | 4.9999994e-07 | \x350637bc | t
+ \x350637bd | 5e-07 | 5e-07 | \x350637bd | t
+ \x350637be | 5.0000006e-07 | 5.0000006e-07 | \x350637be | t
+ \x35719786 | 8.999999e-07 | 8.999999e-07 | \x35719786 | t
+ \x35719787 | 9e-07 | 9e-07 | \x35719787 | t
+ \x35719788 | 9.0000003e-07 | 9.0000003e-07 | \x35719788 | t
+ \x358637bc | 9.999999e-07 | 9.999999e-07 | \x358637bc | t
+ \x358637bd | 1e-06 | 1e-06 | \x358637bd | t
+ \x358637be | 1.0000001e-06 | 1.0000001e-06 | \x358637be | t
+ \x36a7c5ab | 4.9999994e-06 | 4.9999994e-06 | \x36a7c5ab | t
+ \x36a7c5ac | 5e-06 | 5e-06 | \x36a7c5ac | t
+ \x36a7c5ad | 5.0000003e-06 | 5.0000003e-06 | \x36a7c5ad | t
+ \x3727c5ab | 9.999999e-06 | 9.999999e-06 | \x3727c5ab | t
+ \x3727c5ac | 1e-05 | 1e-05 | \x3727c5ac | t
+ \x3727c5ad | 1.0000001e-05 | 1.0000001e-05 | \x3727c5ad | t
+ \x38d1b714 | 9.9999976e-05 | 9.9999976e-05 | \x38d1b714 | t
+ \x38d1b715 | 9.999998e-05 | 9.999998e-05 | \x38d1b715 | t
+ \x38d1b716 | 9.999999e-05 | 9.999999e-05 | \x38d1b716 | t
+ \x38d1b717 | 0.0001 | 0.0001 | \x38d1b717 | t
+ \x38d1b718 | 0.000100000005 | 0.000100000005 | \x38d1b718 | t
+ \x38d1b719 | 0.00010000001 | 0.00010000001 | \x38d1b719 | t
+ \x38d1b71a | 0.00010000002 | 0.00010000002 | \x38d1b71a | t
+ \x38d1b71b | 0.00010000003 | 0.00010000003 | \x38d1b71b | t
+ \x38d1b71c | 0.000100000034 | 0.000100000034 | \x38d1b71c | t
+ \x38d1b71d | 0.00010000004 | 0.00010000004 | \x38d1b71d | t
+ \x38dffffe | 0.00010681151 | 0.00010681151 | \x38dffffe | t
+ \x38dfffff | 0.000106811516 | 0.000106811516 | \x38dfffff | t
+ \x38e00000 | 0.00010681152 | 0.00010681152 | \x38e00000 | t
+ \x38efffff | 0.00011444091 | 0.00011444091 | \x38efffff | t
+ \x38f00000 | 0.00011444092 | 0.00011444092 | \x38f00000 | t
+ \x38f00001 | 0.000114440925 | 0.000114440925 | \x38f00001 | t
+ \x3a83126e | 0.0009999999 | 0.0009999999 | \x3a83126e | t
+ \x3a83126f | 0.001 | 0.001 | \x3a83126f | t
+ \x3a831270 | 0.0010000002 | 0.0010000002 | \x3a831270 | t
+ \x3c23d709 | 0.009999999 | 0.009999999 | \x3c23d709 | t
+ \x3c23d70a | 0.01 | 0.01 | \x3c23d70a | t
+ \x3c23d70b | 0.010000001 | 0.010000001 | \x3c23d70b | t
+ \x3dcccccc | 0.099999994 | 0.099999994 | \x3dcccccc | t
+ \x3dcccccd | 0.1 | 0.1 | \x3dcccccd | t
+ \x3dccccce | 0.10000001 | 0.10000001 | \x3dccccce | t
+ \x3dcccd6f | 0.10000121 | 0.10000121 | \x3dcccd6f | t
+ \x3dcccd70 | 0.100001216 | 0.100001216 | \x3dcccd70 | t
+ \x3dcccd71 | 0.10000122 | 0.10000122 | \x3dcccd71 | t
+ \x3effffff | 0.49999997 | 0.49999997 | \x3effffff | t
+ \x3f000000 | 0.5 | 0.5 | \x3f000000 | t
+ \x3f000001 | 0.50000006 | 0.50000006 | \x3f000001 | t
+ \x3f333332 | 0.6999999 | 0.6999999 | \x3f333332 | t
+ \x3f333333 | 0.7 | 0.7 | \x3f333333 | t
+ \x3f333334 | 0.70000005 | 0.70000005 | \x3f333334 | t
+ \x3f666665 | 0.8999999 | 0.8999999 | \x3f666665 | t
+ \x3f666666 | 0.9 | 0.9 | \x3f666666 | t
+ \x3f666667 | 0.90000004 | 0.90000004 | \x3f666667 | t
+ \x3f7d70a3 | 0.98999995 | 0.98999995 | \x3f7d70a3 | t
+ \x3f7d70a4 | 0.99 | 0.99 | \x3f7d70a4 | t
+ \x3f7d70a5 | 0.99000007 | 0.99000007 | \x3f7d70a5 | t
+ \x3f7fbe76 | 0.99899995 | 0.99899995 | \x3f7fbe76 | t
+ \x3f7fbe77 | 0.999 | 0.999 | \x3f7fbe77 | t
+ \x3f7fbe78 | 0.9990001 | 0.9990001 | \x3f7fbe78 | t
+ \x3f7ff971 | 0.9998999 | 0.9998999 | \x3f7ff971 | t
+ \x3f7ff972 | 0.9999 | 0.9999 | \x3f7ff972 | t
+ \x3f7ff973 | 0.99990004 | 0.99990004 | \x3f7ff973 | t
+ \x3f7fff57 | 0.9999899 | 0.9999899 | \x3f7fff57 | t
+ \x3f7fff58 | 0.99999 | 0.99999 | \x3f7fff58 | t
+ \x3f7fff59 | 0.99999005 | 0.99999005 | \x3f7fff59 | t
+ \x3f7fffee | 0.9999989 | 0.9999989 | \x3f7fffee | t
+ \x3f7fffef | 0.999999 | 0.999999 | \x3f7fffef | t
+ \x3f7ffff0 | 0.99999905 | 0.99999905 | \x3f7ffff0 | t
+ \x3f7ffff1 | 0.9999991 | 0.9999991 | \x3f7ffff1 | t
+ \x3f7ffff2 | 0.99999917 | 0.99999917 | \x3f7ffff2 | t
+ \x3f7ffff3 | 0.9999992 | 0.9999992 | \x3f7ffff3 | t
+ \x3f7ffff4 | 0.9999993 | 0.9999993 | \x3f7ffff4 | t
+ \x3f7ffff5 | 0.99999934 | 0.99999934 | \x3f7ffff5 | t
+ \x3f7ffff6 | 0.9999994 | 0.9999994 | \x3f7ffff6 | t
+ \x3f7ffff7 | 0.99999946 | 0.99999946 | \x3f7ffff7 | t
+ \x3f7ffff8 | 0.9999995 | 0.9999995 | \x3f7ffff8 | t
+ \x3f7ffff9 | 0.9999996 | 0.9999996 | \x3f7ffff9 | t
+ \x3f7ffffa | 0.99999964 | 0.99999964 | \x3f7ffffa | t
+ \x3f7ffffb | 0.9999997 | 0.9999997 | \x3f7ffffb | t
+ \x3f7ffffc | 0.99999976 | 0.99999976 | \x3f7ffffc | t
+ \x3f7ffffd | 0.9999998 | 0.9999998 | \x3f7ffffd | t
+ \x3f7ffffe | 0.9999999 | 0.9999999 | \x3f7ffffe | t
+ \x3f7fffff | 0.99999994 | 0.99999994 | \x3f7fffff | t
+ \x3f800000 | 1 | 1 | \x3f800000 | t
+ \x3f800001 | 1.0000001 | 1.0000001 | \x3f800001 | t
+ \x3f800002 | 1.0000002 | 1.0000002 | \x3f800002 | t
+ \x3f800003 | 1.0000004 | 1.0000004 | \x3f800003 | t
+ \x3f800004 | 1.0000005 | 1.0000005 | \x3f800004 | t
+ \x3f800005 | 1.0000006 | 1.0000006 | \x3f800005 | t
+ \x3f800006 | 1.0000007 | 1.0000007 | \x3f800006 | t
+ \x3f800007 | 1.0000008 | 1.0000008 | \x3f800007 | t
+ \x3f800008 | 1.000001 | 1.000001 | \x3f800008 | t
+ \x3f800009 | 1.0000011 | 1.0000011 | \x3f800009 | t
+ \x3f80000f | 1.0000018 | 1.0000018 | \x3f80000f | t
+ \x3f800010 | 1.0000019 | 1.0000019 | \x3f800010 | t
+ \x3f800011 | 1.000002 | 1.000002 | \x3f800011 | t
+ \x3f800012 | 1.0000021 | 1.0000021 | \x3f800012 | t
+ \x3f800013 | 1.0000023 | 1.0000023 | \x3f800013 | t
+ \x3f800014 | 1.0000024 | 1.0000024 | \x3f800014 | t
+ \x3f800017 | 1.0000027 | 1.0000027 | \x3f800017 | t
+ \x3f800018 | 1.0000029 | 1.0000029 | \x3f800018 | t
+ \x3f800019 | 1.000003 | 1.000003 | \x3f800019 | t
+ \x3f80001a | 1.0000031 | 1.0000031 | \x3f80001a | t
+ \x3f80001b | 1.0000032 | 1.0000032 | \x3f80001b | t
+ \x3f80001c | 1.0000033 | 1.0000033 | \x3f80001c | t
+ \x3f800029 | 1.0000049 | 1.0000049 | \x3f800029 | t
+ \x3f80002a | 1.000005 | 1.000005 | \x3f80002a | t
+ \x3f80002b | 1.0000051 | 1.0000051 | \x3f80002b | t
+ \x3f800053 | 1.0000099 | 1.0000099 | \x3f800053 | t
+ \x3f800054 | 1.00001 | 1.00001 | \x3f800054 | t
+ \x3f800055 | 1.0000101 | 1.0000101 | \x3f800055 | t
+ \x3f800346 | 1.0000999 | 1.0000999 | \x3f800346 | t
+ \x3f800347 | 1.0001 | 1.0001 | \x3f800347 | t
+ \x3f800348 | 1.0001001 | 1.0001001 | \x3f800348 | t
+ \x3f8020c4 | 1.0009999 | 1.0009999 | \x3f8020c4 | t
+ \x3f8020c5 | 1.001 | 1.001 | \x3f8020c5 | t
+ \x3f8020c6 | 1.0010002 | 1.0010002 | \x3f8020c6 | t
+ \x3f8147ad | 1.0099999 | 1.0099999 | \x3f8147ad | t
+ \x3f8147ae | 1.01 | 1.01 | \x3f8147ae | t
+ \x3f8147af | 1.0100001 | 1.0100001 | \x3f8147af | t
+ \x3f8ccccc | 1.0999999 | 1.0999999 | \x3f8ccccc | t
+ \x3f8ccccd | 1.1 | 1.1 | \x3f8ccccd | t
+ \x3f8cccce | 1.1000001 | 1.1000001 | \x3f8cccce | t
+ \x3fc90fdb | 1.5707964 | 1.5707964 | \x3fc90fdb | t
+ \x402df854 | 2.7182817 | 2.7182817 | \x402df854 | t
+ \x40490fdb | 3.1415927 | 3.1415927 | \x40490fdb | t
+ \x409fffff | 4.9999995 | 4.9999995 | \x409fffff | t
+ \x40a00000 | 5 | 5 | \x40a00000 | t
+ \x40a00001 | 5.0000005 | 5.0000005 | \x40a00001 | t
+ \x40afffff | 5.4999995 | 5.4999995 | \x40afffff | t
+ \x40b00000 | 5.5 | 5.5 | \x40b00000 | t
+ \x40b00001 | 5.5000005 | 5.5000005 | \x40b00001 | t
+ \x411fffff | 9.999999 | 9.999999 | \x411fffff | t
+ \x41200000 | 10 | 10 | \x41200000 | t
+ \x41200001 | 10.000001 | 10.000001 | \x41200001 | t
+ \x42c7ffff | 99.99999 | 99.99999 | \x42c7ffff | t
+ \x42c80000 | 100 | 100 | \x42c80000 | t
+ \x42c80001 | 100.00001 | 100.00001 | \x42c80001 | t
+ \x4479ffff | 999.99994 | 999.99994 | \x4479ffff | t
+ \x447a0000 | 1000 | 1000 | \x447a0000 | t
+ \x447a0001 | 1000.00006 | 1000.00006 | \x447a0001 | t
+ \x461c3fff | 9999.999 | 9999.999 | \x461c3fff | t
+ \x461c4000 | 10000 | 10000 | \x461c4000 | t
+ \x461c4001 | 10000.001 | 10000.001 | \x461c4001 | t
+ \x47c34fff | 99999.99 | 99999.99 | \x47c34fff | t
+ \x47c35000 | 100000 | 100000 | \x47c35000 | t
+ \x47c35001 | 100000.01 | 100000.01 | \x47c35001 | t
+ \x497423ff | 999999.94 | 999999.94 | \x497423ff | t
+ \x49742400 | 1e+06 | 1e+06 | \x49742400 | t
+ \x49742401 | 1.00000006e+06 | 1.00000006e+06 | \x49742401 | t
+ \x4b18967f | 9.999999e+06 | 9.999999e+06 | \x4b18967f | t
+ \x4b189680 | 1e+07 | 1e+07 | \x4b189680 | t
+ \x4b189681 | 1.0000001e+07 | 1.0000001e+07 | \x4b189681 | t
+ \x4cbebc1f | 9.999999e+07 | 9.999999e+07 | \x4cbebc1f | t
+ \x4cbebc20 | 1e+08 | 1e+08 | \x4cbebc20 | t
+ \x4cbebc21 | 1.0000001e+08 | 1.0000001e+08 | \x4cbebc21 | t
+ \x4e6e6b27 | 9.9999994e+08 | 9.9999994e+08 | \x4e6e6b27 | t
+ \x4e6e6b28 | 1e+09 | 1e+09 | \x4e6e6b28 | t
+ \x4e6e6b29 | 1.00000006e+09 | 1.00000006e+09 | \x4e6e6b29 | t
+ \x501502f8 | 9.999999e+09 | 9.999999e+09 | \x501502f8 | t
+ \x501502f9 | 1e+10 | 1e+10 | \x501502f9 | t
+ \x501502fa | 1.0000001e+10 | 1.0000001e+10 | \x501502fa | t
+ \x51ba43b6 | 9.999999e+10 | 9.999999e+10 | \x51ba43b6 | t
+ \x51ba43b7 | 1e+11 | 1e+11 | \x51ba43b7 | t
+ \x51ba43b8 | 1.0000001e+11 | 1.0000001e+11 | \x51ba43b8 | t
+ \x1f6c1e4a | 5e-20 | 5e-20 | \x1f6c1e4a | t
+ \x59be6cea | 6.7e+15 | 6.7e+15 | \x59be6cea | t
+ \x5d5ab6c4 | 9.85e+17 | 9.85e+17 | \x5d5ab6c4 | t
+ \x2cc4a9bd | 5.5895e-12 | 5.5895e-12 | \x2cc4a9bd | t
+ \x15ae43fd | 7.038531e-26 | 7.038531e-26 | \x15ae43fd | t
+ \x2cf757ca | 7.0299088e-12 | 7.0299088e-12 | \x2cf757ca | t
+ \x665ba998 | 2.5933168e+23 | 2.5933168e+23 | \x665ba998 | t
+ \x743c3324 | 5.9642887e+31 | 5.9642887e+31 | \x743c3324 | t
+ \x47f1205a | 123456.7 | 123456.7 | \x47f1205a | t
+ \x4640e6ae | 12345.67 | 12345.67 | \x4640e6ae | t
+ \x449a5225 | 1234.567 | 1234.567 | \x449a5225 | t
+ \x42f6e9d5 | 123.4567 | 123.4567 | \x42f6e9d5 | t
+ \x414587dd | 12.34567 | 12.34567 | \x414587dd | t
+ \x3f9e064b | 1.234567 | 1.234567 | \x3f9e064b | t
+ \x4c000004 | 3.3554448e+07 | 3.3554448e+07 | \x4c000004 | t
+ \x50061c46 | 8.999999e+09 | 8.999999e+09 | \x50061c46 | t
+ \x510006a8 | 3.4366718e+10 | 3.4366718e+10 | \x510006a8 | t
+ \x48951f84 | 305404.12 | 305404.12 | \x48951f84 | t
+ \x45fd1840 | 8099.0312 | 8099.0312 | \x45fd1840 | t
+ \x39800000 | 0.00024414062 | 0.00024414062 | \x39800000 | t
+ \x3b200000 | 0.0024414062 | 0.0024414062 | \x3b200000 | t
+ \x3b900000 | 0.0043945312 | 0.0043945312 | \x3b900000 | t
+ \x3bd00000 | 0.0063476562 | 0.0063476562 | \x3bd00000 | t
+ \x63800000 | 4.7223665e+21 | 4.7223665e+21 | \x63800000 | t
+ \x4b000000 | 8.388608e+06 | 8.388608e+06 | \x4b000000 | t
+ \x4b800000 | 1.6777216e+07 | 1.6777216e+07 | \x4b800000 | t
+ \x4c000001 | 3.3554436e+07 | 3.3554436e+07 | \x4c000001 | t
+ \x4c800b0d | 6.7131496e+07 | 6.7131496e+07 | \x4c800b0d | t
+ \x00d24584 | 1.9310392e-38 | 1.9310392e-38 | \x00d24584 | t
+ \x00d90b88 | 1.993244e-38 | 1.993244e-38 | \x00d90b88 | t
+ \x45803f34 | 4103.9004 | 4103.9004 | \x45803f34 | t
+ \x4f9f24f7 | 5.3399997e+09 | 5.3399997e+09 | \x4f9f24f7 | t
+ \x3a8722c3 | 0.0010310042 | 0.0010310042 | \x3a8722c3 | t
+ \x5c800041 | 2.882326e+17 | 2.882326e+17 | \x5c800041 | t
+ \x15ae43fd | 7.038531e-26 | 7.038531e-26 | \x15ae43fd | t
+ \x5d4cccfb | 9.223404e+17 | 9.223404e+17 | \x5d4cccfb | t
+ \x4c800001 | 6.710887e+07 | 6.710887e+07 | \x4c800001 | t
+ \x57800ed8 | 2.816025e+14 | 2.816025e+14 | \x57800ed8 | t
+ \x5f000000 | 9.223372e+18 | 9.223372e+18 | \x5f000000 | t
+ \x700000f0 | 1.5846086e+29 | 1.5846086e+29 | \x700000f0 | t
+ \x5f23e9ac | 1.1811161e+19 | 1.1811161e+19 | \x5f23e9ac | t
+ \x5e9502f9 | 5.368709e+18 | 5.368709e+18 | \x5e9502f9 | t
+ \x5e8012b1 | 4.6143166e+18 | 4.6143166e+18 | \x5e8012b1 | t
+ \x3c000028 | 0.007812537 | 0.007812537 | \x3c000028 | t
+ \x60cde861 | 1.18697725e+20 | 1.18697725e+20 | \x60cde861 | t
+ \x03aa2a50 | 1.00014165e-36 | 1.00014165e-36 | \x03aa2a50 | t
+ \x43480000 | 200 | 200 | \x43480000 | t
+ \x4c000000 | 3.3554432e+07 | 3.3554432e+07 | \x4c000000 | t
+ \x5d1502f9 | 6.7108864e+17 | 6.7108864e+17 | \x5d1502f9 | t
+ \x5d9502f9 | 1.3421773e+18 | 1.3421773e+18 | \x5d9502f9 | t
+ \x5e1502f9 | 2.6843546e+18 | 2.6843546e+18 | \x5e1502f9 | t
+ \x3f99999a | 1.2 | 1.2 | \x3f99999a | t
+ \x3f9d70a4 | 1.23 | 1.23 | \x3f9d70a4 | t
+ \x3f9df3b6 | 1.234 | 1.234 | \x3f9df3b6 | t
+ \x3f9e0419 | 1.2345 | 1.2345 | \x3f9e0419 | t
+ \x3f9e0610 | 1.23456 | 1.23456 | \x3f9e0610 | t
+ \x3f9e064b | 1.234567 | 1.234567 | \x3f9e064b | t
+ \x3f9e0651 | 1.2345678 | 1.2345678 | \x3f9e0651 | t
+ \x03d20cfe | 1.23456735e-36 | 1.23456735e-36 | \x03d20cfe | t
+(261 rows)
+
+-- clean up, lest opr_sanity complain
+drop type xfloat4 cascade;
+NOTICE: drop cascades to 6 other objects
+DETAIL: drop cascades to function xfloat4in(cstring)
+drop cascades to function xfloat4out(xfloat4)
+drop cascades to cast from xfloat4 to real
+drop cascades to cast from real to xfloat4
+drop cascades to cast from xfloat4 to integer
+drop cascades to cast from integer to xfloat4
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
new file mode 100644
index 0000000..fc4212b
--- /dev/null
+++ b/src/test/regress/expected/float8.out
@@ -0,0 +1,1382 @@
+--
+-- FLOAT8
+--
+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;
+ERROR: "10e400" is out of range for type double precision
+LINE 1: SELECT '10e400'::float8;
+ ^
+SELECT '-10e400'::float8;
+ERROR: "-10e400" is out of range for type double precision
+LINE 1: SELECT '-10e400'::float8;
+ ^
+SELECT '10e-400'::float8;
+ERROR: "10e-400" is out of range for type double precision
+LINE 1: SELECT '10e-400'::float8;
+ ^
+SELECT '-10e-400'::float8;
+ERROR: "-10e-400" is out of range for type double precision
+LINE 1: SELECT '-10e-400'::float8;
+ ^
+-- test smallest normalized input
+SELECT float8send('2.2250738585072014E-308'::float8);
+ float8send
+--------------------
+ \x0010000000000000
+(1 row)
+
+-- bad input
+INSERT INTO FLOAT8_TBL(f1) VALUES ('');
+ERROR: invalid input syntax for type double precision: ""
+LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES ('');
+ ^
+INSERT INTO FLOAT8_TBL(f1) VALUES (' ');
+ERROR: invalid input syntax for type double precision: " "
+LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES (' ');
+ ^
+INSERT INTO FLOAT8_TBL(f1) VALUES ('xyz');
+ERROR: invalid input syntax for type double precision: "xyz"
+LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES ('xyz');
+ ^
+INSERT INTO FLOAT8_TBL(f1) VALUES ('5.0.0');
+ERROR: invalid input syntax for type double precision: "5.0.0"
+LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES ('5.0.0');
+ ^
+INSERT INTO FLOAT8_TBL(f1) VALUES ('5 . 0');
+ERROR: invalid input syntax for type double precision: "5 . 0"
+LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES ('5 . 0');
+ ^
+INSERT INTO FLOAT8_TBL(f1) VALUES ('5. 0');
+ERROR: invalid input syntax for type double precision: "5. 0"
+LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES ('5. 0');
+ ^
+INSERT INTO FLOAT8_TBL(f1) VALUES (' - 3');
+ERROR: invalid input syntax for type double precision: " - 3"
+LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES (' - 3');
+ ^
+INSERT INTO FLOAT8_TBL(f1) VALUES ('123 5');
+ERROR: invalid input syntax for type double precision: "123 5"
+LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES ('123 5');
+ ^
+-- special inputs
+SELECT 'NaN'::float8;
+ float8
+--------
+ NaN
+(1 row)
+
+SELECT 'nan'::float8;
+ float8
+--------
+ NaN
+(1 row)
+
+SELECT ' NAN '::float8;
+ float8
+--------
+ NaN
+(1 row)
+
+SELECT 'infinity'::float8;
+ float8
+----------
+ Infinity
+(1 row)
+
+SELECT ' -INFINiTY '::float8;
+ float8
+-----------
+ -Infinity
+(1 row)
+
+-- bad special inputs
+SELECT 'N A N'::float8;
+ERROR: invalid input syntax for type double precision: "N A N"
+LINE 1: SELECT 'N A N'::float8;
+ ^
+SELECT 'NaN x'::float8;
+ERROR: invalid input syntax for type double precision: "NaN x"
+LINE 1: SELECT 'NaN x'::float8;
+ ^
+SELECT ' INFINITY x'::float8;
+ERROR: invalid input syntax for type double precision: " INFINITY x"
+LINE 1: SELECT ' INFINITY x'::float8;
+ ^
+SELECT 'Infinity'::float8 + 100.0;
+ ?column?
+----------
+ Infinity
+(1 row)
+
+SELECT 'Infinity'::float8 / 'Infinity'::float8;
+ ?column?
+----------
+ NaN
+(1 row)
+
+SELECT '42'::float8 / 'Infinity'::float8;
+ ?column?
+----------
+ 0
+(1 row)
+
+SELECT 'nan'::float8 / 'nan'::float8;
+ ?column?
+----------
+ NaN
+(1 row)
+
+SELECT 'nan'::float8 / '0'::float8;
+ ?column?
+----------
+ NaN
+(1 row)
+
+SELECT 'nan'::numeric::float8;
+ float8
+--------
+ NaN
+(1 row)
+
+SELECT * FROM FLOAT8_TBL;
+ f1
+----------------------
+ 0
+ 1004.3
+ -34.84
+ 1.2345678901234e+200
+ 1.2345678901234e-200
+(5 rows)
+
+SELECT f.* FROM FLOAT8_TBL f WHERE f.f1 <> '1004.3';
+ f1
+----------------------
+ 0
+ -34.84
+ 1.2345678901234e+200
+ 1.2345678901234e-200
+(4 rows)
+
+SELECT f.* FROM FLOAT8_TBL f WHERE f.f1 = '1004.3';
+ f1
+--------
+ 1004.3
+(1 row)
+
+SELECT f.* FROM FLOAT8_TBL f WHERE '1004.3' > f.f1;
+ f1
+----------------------
+ 0
+ -34.84
+ 1.2345678901234e-200
+(3 rows)
+
+SELECT f.* FROM FLOAT8_TBL f WHERE f.f1 < '1004.3';
+ f1
+----------------------
+ 0
+ -34.84
+ 1.2345678901234e-200
+(3 rows)
+
+SELECT f.* FROM FLOAT8_TBL f WHERE '1004.3' >= f.f1;
+ f1
+----------------------
+ 0
+ 1004.3
+ -34.84
+ 1.2345678901234e-200
+(4 rows)
+
+SELECT f.* FROM FLOAT8_TBL f WHERE f.f1 <= '1004.3';
+ f1
+----------------------
+ 0
+ 1004.3
+ -34.84
+ 1.2345678901234e-200
+(4 rows)
+
+SELECT f.f1, f.f1 * '-10' AS x
+ FROM FLOAT8_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | x
+----------------------+-----------------------
+ 1004.3 | -10043
+ 1.2345678901234e+200 | -1.2345678901234e+201
+ 1.2345678901234e-200 | -1.2345678901234e-199
+(3 rows)
+
+SELECT f.f1, f.f1 + '-10' AS x
+ FROM FLOAT8_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | x
+----------------------+----------------------
+ 1004.3 | 994.3
+ 1.2345678901234e+200 | 1.2345678901234e+200
+ 1.2345678901234e-200 | -10
+(3 rows)
+
+SELECT f.f1, f.f1 / '-10' AS x
+ FROM FLOAT8_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | x
+----------------------+-----------------------
+ 1004.3 | -100.42999999999999
+ 1.2345678901234e+200 | -1.2345678901234e+199
+ 1.2345678901234e-200 | -1.2345678901234e-201
+(3 rows)
+
+SELECT f.f1, f.f1 - '-10' AS x
+ FROM FLOAT8_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | x
+----------------------+----------------------
+ 1004.3 | 1014.3
+ 1.2345678901234e+200 | 1.2345678901234e+200
+ 1.2345678901234e-200 | 10
+(3 rows)
+
+SELECT f.f1 ^ '2.0' AS square_f1
+ FROM FLOAT8_TBL f where f.f1 = '1004.3';
+ square_f1
+--------------------
+ 1008618.4899999999
+(1 row)
+
+-- absolute value
+SELECT f.f1, @f.f1 AS abs_f1
+ FROM FLOAT8_TBL f;
+ f1 | abs_f1
+----------------------+----------------------
+ 0 | 0
+ 1004.3 | 1004.3
+ -34.84 | 34.84
+ 1.2345678901234e+200 | 1.2345678901234e+200
+ 1.2345678901234e-200 | 1.2345678901234e-200
+(5 rows)
+
+-- truncate
+SELECT f.f1, trunc(f.f1) AS trunc_f1
+ FROM FLOAT8_TBL f;
+ f1 | trunc_f1
+----------------------+----------------------
+ 0 | 0
+ 1004.3 | 1004
+ -34.84 | -34
+ 1.2345678901234e+200 | 1.2345678901234e+200
+ 1.2345678901234e-200 | 0
+(5 rows)
+
+-- round
+SELECT f.f1, round(f.f1) AS round_f1
+ FROM FLOAT8_TBL f;
+ f1 | round_f1
+----------------------+----------------------
+ 0 | 0
+ 1004.3 | 1004
+ -34.84 | -35
+ 1.2345678901234e+200 | 1.2345678901234e+200
+ 1.2345678901234e-200 | 0
+(5 rows)
+
+-- ceil / ceiling
+select ceil(f1) as ceil_f1 from float8_tbl f;
+ ceil_f1
+----------------------
+ 0
+ 1005
+ -34
+ 1.2345678901234e+200
+ 1
+(5 rows)
+
+select ceiling(f1) as ceiling_f1 from float8_tbl f;
+ ceiling_f1
+----------------------
+ 0
+ 1005
+ -34
+ 1.2345678901234e+200
+ 1
+(5 rows)
+
+-- floor
+select floor(f1) as floor_f1 from float8_tbl f;
+ floor_f1
+----------------------
+ 0
+ 1004
+ -35
+ 1.2345678901234e+200
+ 0
+(5 rows)
+
+-- sign
+select sign(f1) as sign_f1 from float8_tbl f;
+ sign_f1
+---------
+ 0
+ 1
+ -1
+ 1
+ 1
+(5 rows)
+
+-- 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;
+ eight
+-------
+ 8
+(1 row)
+
+SELECT |/ float8 '64' AS eight;
+ eight
+-------
+ 8
+(1 row)
+
+SELECT f.f1, |/f.f1 AS sqrt_f1
+ FROM FLOAT8_TBL f
+ WHERE f.f1 > '0.0';
+ f1 | sqrt_f1
+----------------------+-----------------------
+ 1004.3 | 31.6906926399535
+ 1.2345678901234e+200 | 1.11111110611109e+100
+ 1.2345678901234e-200 | 1.11111110611109e-100
+(3 rows)
+
+-- power
+SELECT power(float8 '144', float8 '0.5');
+ power
+-------
+ 12
+(1 row)
+
+SELECT power(float8 'NaN', float8 '0.5');
+ power
+-------
+ NaN
+(1 row)
+
+SELECT power(float8 '144', float8 'NaN');
+ power
+-------
+ NaN
+(1 row)
+
+SELECT power(float8 'NaN', float8 'NaN');
+ power
+-------
+ NaN
+(1 row)
+
+SELECT power(float8 '-1', float8 'NaN');
+ power
+-------
+ NaN
+(1 row)
+
+SELECT power(float8 '1', float8 'NaN');
+ power
+-------
+ 1
+(1 row)
+
+SELECT power(float8 'NaN', float8 '0');
+ power
+-------
+ 1
+(1 row)
+
+SELECT power(float8 'inf', float8 '0');
+ power
+-------
+ 1
+(1 row)
+
+SELECT power(float8 '-inf', float8 '0');
+ power
+-------
+ 1
+(1 row)
+
+SELECT power(float8 '0', float8 'inf');
+ power
+-------
+ 0
+(1 row)
+
+SELECT power(float8 '0', float8 '-inf');
+ERROR: zero raised to a negative power is undefined
+SELECT power(float8 '1', float8 'inf');
+ power
+-------
+ 1
+(1 row)
+
+SELECT power(float8 '1', float8 '-inf');
+ power
+-------
+ 1
+(1 row)
+
+SELECT power(float8 '-1', float8 'inf');
+ power
+-------
+ 1
+(1 row)
+
+SELECT power(float8 '-1', float8 '-inf');
+ power
+-------
+ 1
+(1 row)
+
+SELECT power(float8 '0.1', float8 'inf');
+ power
+-------
+ 0
+(1 row)
+
+SELECT power(float8 '-0.1', float8 'inf');
+ power
+-------
+ 0
+(1 row)
+
+SELECT power(float8 '1.1', float8 'inf');
+ power
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 '-1.1', float8 'inf');
+ power
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 '0.1', float8 '-inf');
+ power
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 '-0.1', float8 '-inf');
+ power
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 '1.1', float8 '-inf');
+ power
+-------
+ 0
+(1 row)
+
+SELECT power(float8 '-1.1', float8 '-inf');
+ power
+-------
+ 0
+(1 row)
+
+SELECT power(float8 'inf', float8 '-2');
+ power
+-------
+ 0
+(1 row)
+
+SELECT power(float8 'inf', float8 '2');
+ power
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 'inf', float8 'inf');
+ power
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 'inf', float8 '-inf');
+ power
+-------
+ 0
+(1 row)
+
+-- 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';
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT power(float8 '-inf', float8 '-3');
+ power
+-------
+ -0
+(1 row)
+
+SELECT power(float8 '-inf', float8 '2');
+ power
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 '-inf', float8 '3');
+ power
+-----------
+ -Infinity
+(1 row)
+
+SELECT power(float8 '-inf', float8 '3.5');
+ERROR: a negative number raised to a non-integer power yields a complex result
+SELECT power(float8 '-inf', float8 'inf');
+ power
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 '-inf', float8 '-inf');
+ power
+-------
+ 0
+(1 row)
+
+-- 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';
+ f1 | exp_ln_f1
+----------------------+-----------------------
+ 1004.3 | 1004.3
+ 1.2345678901234e+200 | 1.23456789012338e+200
+ 1.2345678901234e-200 | 1.23456789012339e-200
+(3 rows)
+
+-- check edge cases for exp
+SELECT exp('inf'::float8), exp('-inf'::float8), exp('nan'::float8);
+ exp | exp | exp
+----------+-----+-----
+ Infinity | 0 | NaN
+(1 row)
+
+-- cube root
+SELECT ||/ float8 '27' AS three;
+ three
+-------
+ 3
+(1 row)
+
+SELECT f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
+ f1 | cbrt_f1
+----------------------+----------------------
+ 0 | 0
+ 1004.3 | 10.014312837827
+ -34.84 | -3.26607421344208
+ 1.2345678901234e+200 | 4.97933859234765e+66
+ 1.2345678901234e-200 | 2.3112042409018e-67
+(5 rows)
+
+SELECT * FROM FLOAT8_TBL;
+ f1
+----------------------
+ 0
+ 1004.3
+ -34.84
+ 1.2345678901234e+200
+ 1.2345678901234e-200
+(5 rows)
+
+UPDATE FLOAT8_TBL
+ SET f1 = FLOAT8_TBL.f1 * '-1'
+ WHERE FLOAT8_TBL.f1 > '0.0';
+SELECT f.f1 * '1e200' from FLOAT8_TBL f;
+ERROR: value out of range: overflow
+SELECT f.f1 ^ '1e200' from FLOAT8_TBL f;
+ERROR: value out of range: overflow
+SELECT 0 ^ 0 + 0 ^ 1 + 0 ^ 0.0 + 0 ^ 0.5;
+ ?column?
+----------
+ 2
+(1 row)
+
+SELECT ln(f.f1) from FLOAT8_TBL f where f.f1 = '0.0' ;
+ERROR: cannot take logarithm of zero
+SELECT ln(f.f1) from FLOAT8_TBL f where f.f1 < '0.0' ;
+ERROR: cannot take logarithm of a negative number
+SELECT exp(f.f1) from FLOAT8_TBL f;
+ERROR: value out of range: underflow
+SELECT f.f1 / '0.0' from FLOAT8_TBL f;
+ERROR: division by zero
+SELECT * FROM FLOAT8_TBL;
+ f1
+-----------------------
+ 0
+ -34.84
+ -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(5 rows)
+
+-- 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');
+ sinh
+-----------------
+ 1.1752011936438
+(1 row)
+
+SELECT cosh(float8 '1');
+ cosh
+------------------
+ 1.54308063481524
+(1 row)
+
+SELECT tanh(float8 '1');
+ tanh
+-------------------
+ 0.761594155955765
+(1 row)
+
+SELECT asinh(float8 '1');
+ asinh
+-------------------
+ 0.881373587019543
+(1 row)
+
+SELECT acosh(float8 '2');
+ acosh
+------------------
+ 1.31695789692482
+(1 row)
+
+SELECT atanh(float8 '0.5');
+ atanh
+-------------------
+ 0.549306144334055
+(1 row)
+
+-- test Inf/NaN cases for hyperbolic functions
+SELECT sinh(float8 'infinity');
+ sinh
+----------
+ Infinity
+(1 row)
+
+SELECT sinh(float8 '-infinity');
+ sinh
+-----------
+ -Infinity
+(1 row)
+
+SELECT sinh(float8 'nan');
+ sinh
+------
+ NaN
+(1 row)
+
+SELECT cosh(float8 'infinity');
+ cosh
+----------
+ Infinity
+(1 row)
+
+SELECT cosh(float8 '-infinity');
+ cosh
+----------
+ Infinity
+(1 row)
+
+SELECT cosh(float8 'nan');
+ cosh
+------
+ NaN
+(1 row)
+
+SELECT tanh(float8 'infinity');
+ tanh
+------
+ 1
+(1 row)
+
+SELECT tanh(float8 '-infinity');
+ tanh
+------
+ -1
+(1 row)
+
+SELECT tanh(float8 'nan');
+ tanh
+------
+ NaN
+(1 row)
+
+SELECT asinh(float8 'infinity');
+ asinh
+----------
+ Infinity
+(1 row)
+
+SELECT asinh(float8 '-infinity');
+ asinh
+-----------
+ -Infinity
+(1 row)
+
+SELECT asinh(float8 'nan');
+ asinh
+-------
+ NaN
+(1 row)
+
+-- acosh(Inf) should be Inf, but some mingw versions produce NaN, so skip test
+-- SELECT acosh(float8 'infinity');
+SELECT acosh(float8 '-infinity');
+ERROR: input is out of range
+SELECT acosh(float8 'nan');
+ acosh
+-------
+ NaN
+(1 row)
+
+SELECT atanh(float8 'infinity');
+ERROR: input is out of range
+SELECT atanh(float8 '-infinity');
+ERROR: input is out of range
+SELECT atanh(float8 'nan');
+ atanh
+-------
+ NaN
+(1 row)
+
+RESET extra_float_digits;
+-- test for over- and underflow
+INSERT INTO FLOAT8_TBL(f1) VALUES ('10e400');
+ERROR: "10e400" is out of range for type double precision
+LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES ('10e400');
+ ^
+INSERT INTO FLOAT8_TBL(f1) VALUES ('-10e400');
+ERROR: "-10e400" is out of range for type double precision
+LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES ('-10e400');
+ ^
+INSERT INTO FLOAT8_TBL(f1) VALUES ('10e-400');
+ERROR: "10e-400" is out of range for type double precision
+LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES ('10e-400');
+ ^
+INSERT INTO FLOAT8_TBL(f1) VALUES ('-10e-400');
+ERROR: "-10e-400" is out of range for type double precision
+LINE 1: INSERT INTO FLOAT8_TBL(f1) VALUES ('-10e-400');
+ ^
+-- maintain external table consistency across platforms
+-- delete all values and reinsert well-behaved ones
+DELETE FROM FLOAT8_TBL;
+INSERT INTO FLOAT8_TBL(f1) VALUES ('0.0');
+INSERT INTO FLOAT8_TBL(f1) VALUES ('-34.84');
+INSERT INTO FLOAT8_TBL(f1) VALUES ('-1004.30');
+INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2345678901234e+200');
+INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2345678901234e-200');
+SELECT * FROM FLOAT8_TBL;
+ f1
+-----------------------
+ 0
+ -34.84
+ -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(5 rows)
+
+-- test edge-case coercions to integer
+SELECT '32767.4'::float8::int2;
+ int2
+-------
+ 32767
+(1 row)
+
+SELECT '32767.6'::float8::int2;
+ERROR: smallint out of range
+SELECT '-32768.4'::float8::int2;
+ int2
+--------
+ -32768
+(1 row)
+
+SELECT '-32768.6'::float8::int2;
+ERROR: smallint out of range
+SELECT '2147483647.4'::float8::int4;
+ int4
+------------
+ 2147483647
+(1 row)
+
+SELECT '2147483647.6'::float8::int4;
+ERROR: integer out of range
+SELECT '-2147483648.4'::float8::int4;
+ int4
+-------------
+ -2147483648
+(1 row)
+
+SELECT '-2147483648.6'::float8::int4;
+ERROR: integer out of range
+SELECT '9223372036854773760'::float8::int8;
+ int8
+---------------------
+ 9223372036854773760
+(1 row)
+
+SELECT '9223372036854775807'::float8::int8;
+ERROR: bigint out of range
+SELECT '-9223372036854775808.5'::float8::int8;
+ int8
+----------------------
+ -9223372036854775808
+(1 row)
+
+SELECT '-9223372036854780000'::float8::int8;
+ERROR: bigint out of range
+-- 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);
+ x | sind | sind_exact
+-----+------+------------
+ 0 | 0 | t
+ 30 | 0.5 | t
+ 90 | 1 | t
+ 150 | 0.5 | t
+ 180 | 0 | t
+ 210 | -0.5 | t
+ 270 | -1 | t
+ 330 | -0.5 | t
+ 360 | 0 | t
+(9 rows)
+
+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);
+ x | cosd | cosd_exact
+-----+------+------------
+ 0 | 1 | t
+ 60 | 0.5 | t
+ 90 | 0 | t
+ 120 | -0.5 | t
+ 180 | -1 | t
+ 240 | -0.5 | t
+ 270 | 0 | t
+ 300 | 0.5 | t
+ 360 | 1 | t
+(9 rows)
+
+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);
+ x | tand | tand_exact | cotd | cotd_exact
+-----+-----------+------------+-----------+------------
+ 0 | 0 | t | Infinity | t
+ 45 | 1 | t | 1 | t
+ 90 | Infinity | t | 0 | t
+ 135 | -1 | t | -1 | t
+ 180 | 0 | t | -Infinity | t
+ 225 | 1 | t | 1 | t
+ 270 | -Infinity | t | 0 | t
+ 315 | -1 | t | -1 | t
+ 360 | 0 | t | Infinity | t
+(9 rows)
+
+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);
+ x | asind | asind_exact | acosd | acosd_exact
+------+-------+-------------+-------+-------------
+ -1 | -90 | t | 180 | t
+ -0.5 | -30 | t | 120 | t
+ 0 | 0 | t | 90 | t
+ 0.5 | 30 | t | 60 | t
+ 1 | 90 | t | 0 | t
+(5 rows)
+
+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);
+ x | atand | atand_exact
+-----------+-------+-------------
+ -Infinity | -90 | t
+ -1 | -45 | t
+ 0 | 0 | t
+ 1 | 45 | t
+ Infinity | 90 | t
+(5 rows)
+
+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);
+ x | y | atan2d | atan2d_exact
+-----+-----+--------+--------------
+ 10 | 0 | 0 | t
+ 0 | 10 | 90 | t
+ -10 | 0 | 180 | t
+ 0 | -10 | -90 | t
+ 10 | 0 | 0 | t
+(5 rows)
+
+--
+-- 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';
+NOTICE: return type xfloat8 is only a shell
+create function xfloat8out(xfloat8) returns cstring immutable strict
+ language internal as 'int8out';
+NOTICE: argument type xfloat8 is only a shell
+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;
+ ibits | flt
+--------------------+-------------------------
+ \x0000000000000001 | 5e-324
+ \x0000000000000002 | 1e-323
+ \x0000000000000003 | 1.5e-323
+ \x0000000000001000 | 2.0237e-320
+ \x0000000100000000 | 2.121995791e-314
+ \x0000010000000000 | 5.43230922487e-312
+ \x0000010100000000 | 5.45352918278e-312
+ \x0000400000000000 | 3.4766779039175e-310
+ \x0000400100000000 | 3.4768901034966e-310
+ \x0000800000000000 | 6.953355807835e-310
+ \x0000800000000001 | 6.95335580783505e-310
+ \x00000000000f4240 | 4.940656e-318
+ \x00000000016e3600 | 1.18575755e-316
+ \x0000008cdcdea440 | 2.989102097996e-312
+ \x000ffffffffffff0 | 2.2250738585071935e-308
+ \x000ffffffffffff1 | 2.225073858507194e-308
+ \x000ffffffffffffe | 2.2250738585072004e-308
+ \x000fffffffffffff | 2.225073858507201e-308
+(18 rows)
+
+-- 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;
+ ibits | flt | r_flt | obits | correct
+--------------------+-------------------------+-------------------------+--------------------+---------
+ \x0000000000000000 | 0 | 0 | \x0000000000000000 | t
+ \x0010000000000000 | 2.2250738585072014e-308 | 2.2250738585072014e-308 | \x0010000000000000 | t
+ \x0010000000000001 | 2.225073858507202e-308 | 2.225073858507202e-308 | \x0010000000000001 | t
+ \x0010000000000002 | 2.2250738585072024e-308 | 2.2250738585072024e-308 | \x0010000000000002 | t
+ \x0018000000000000 | 3.337610787760802e-308 | 3.337610787760802e-308 | \x0018000000000000 | t
+ \x3ddb7cdfd9d7bdba | 9.999999999999999e-11 | 9.999999999999999e-11 | \x3ddb7cdfd9d7bdba | t
+ \x3ddb7cdfd9d7bdbb | 1e-10 | 1e-10 | \x3ddb7cdfd9d7bdbb | t
+ \x3ddb7cdfd9d7bdbc | 1.0000000000000002e-10 | 1.0000000000000002e-10 | \x3ddb7cdfd9d7bdbc | t
+ \x3e112e0be826d694 | 9.999999999999999e-10 | 9.999999999999999e-10 | \x3e112e0be826d694 | t
+ \x3e112e0be826d695 | 1e-09 | 1e-09 | \x3e112e0be826d695 | t
+ \x3e112e0be826d696 | 1.0000000000000003e-09 | 1.0000000000000003e-09 | \x3e112e0be826d696 | t
+ \x3e45798ee2308c39 | 9.999999999999999e-09 | 9.999999999999999e-09 | \x3e45798ee2308c39 | t
+ \x3e45798ee2308c3a | 1e-08 | 1e-08 | \x3e45798ee2308c3a | t
+ \x3e45798ee2308c3b | 1.0000000000000002e-08 | 1.0000000000000002e-08 | \x3e45798ee2308c3b | t
+ \x3e7ad7f29abcaf47 | 9.999999999999998e-08 | 9.999999999999998e-08 | \x3e7ad7f29abcaf47 | t
+ \x3e7ad7f29abcaf48 | 1e-07 | 1e-07 | \x3e7ad7f29abcaf48 | t
+ \x3e7ad7f29abcaf49 | 1.0000000000000001e-07 | 1.0000000000000001e-07 | \x3e7ad7f29abcaf49 | t
+ \x3eb0c6f7a0b5ed8c | 9.999999999999997e-07 | 9.999999999999997e-07 | \x3eb0c6f7a0b5ed8c | t
+ \x3eb0c6f7a0b5ed8d | 1e-06 | 1e-06 | \x3eb0c6f7a0b5ed8d | t
+ \x3eb0c6f7a0b5ed8e | 1.0000000000000002e-06 | 1.0000000000000002e-06 | \x3eb0c6f7a0b5ed8e | t
+ \x3ee4f8b588e368ef | 9.999999999999997e-06 | 9.999999999999997e-06 | \x3ee4f8b588e368ef | t
+ \x3ee4f8b588e368f0 | 9.999999999999999e-06 | 9.999999999999999e-06 | \x3ee4f8b588e368f0 | t
+ \x3ee4f8b588e368f1 | 1e-05 | 1e-05 | \x3ee4f8b588e368f1 | t
+ \x3f1a36e2eb1c432c | 9.999999999999999e-05 | 9.999999999999999e-05 | \x3f1a36e2eb1c432c | t
+ \x3f1a36e2eb1c432d | 0.0001 | 0.0001 | \x3f1a36e2eb1c432d | t
+ \x3f1a36e2eb1c432e | 0.00010000000000000002 | 0.00010000000000000002 | \x3f1a36e2eb1c432e | t
+ \x3f50624dd2f1a9fb | 0.0009999999999999998 | 0.0009999999999999998 | \x3f50624dd2f1a9fb | t
+ \x3f50624dd2f1a9fc | 0.001 | 0.001 | \x3f50624dd2f1a9fc | t
+ \x3f50624dd2f1a9fd | 0.0010000000000000002 | 0.0010000000000000002 | \x3f50624dd2f1a9fd | t
+ \x3f847ae147ae147a | 0.009999999999999998 | 0.009999999999999998 | \x3f847ae147ae147a | t
+ \x3f847ae147ae147b | 0.01 | 0.01 | \x3f847ae147ae147b | t
+ \x3f847ae147ae147c | 0.010000000000000002 | 0.010000000000000002 | \x3f847ae147ae147c | t
+ \x3fb9999999999999 | 0.09999999999999999 | 0.09999999999999999 | \x3fb9999999999999 | t
+ \x3fb999999999999a | 0.1 | 0.1 | \x3fb999999999999a | t
+ \x3fb999999999999b | 0.10000000000000002 | 0.10000000000000002 | \x3fb999999999999b | t
+ \x3feffffffffffff0 | 0.9999999999999982 | 0.9999999999999982 | \x3feffffffffffff0 | t
+ \x3feffffffffffff1 | 0.9999999999999983 | 0.9999999999999983 | \x3feffffffffffff1 | t
+ \x3feffffffffffff2 | 0.9999999999999984 | 0.9999999999999984 | \x3feffffffffffff2 | t
+ \x3feffffffffffff3 | 0.9999999999999986 | 0.9999999999999986 | \x3feffffffffffff3 | t
+ \x3feffffffffffff4 | 0.9999999999999987 | 0.9999999999999987 | \x3feffffffffffff4 | t
+ \x3feffffffffffff5 | 0.9999999999999988 | 0.9999999999999988 | \x3feffffffffffff5 | t
+ \x3feffffffffffff6 | 0.9999999999999989 | 0.9999999999999989 | \x3feffffffffffff6 | t
+ \x3feffffffffffff7 | 0.999999999999999 | 0.999999999999999 | \x3feffffffffffff7 | t
+ \x3feffffffffffff8 | 0.9999999999999991 | 0.9999999999999991 | \x3feffffffffffff8 | t
+ \x3feffffffffffff9 | 0.9999999999999992 | 0.9999999999999992 | \x3feffffffffffff9 | t
+ \x3feffffffffffffa | 0.9999999999999993 | 0.9999999999999993 | \x3feffffffffffffa | t
+ \x3feffffffffffffb | 0.9999999999999994 | 0.9999999999999994 | \x3feffffffffffffb | t
+ \x3feffffffffffffc | 0.9999999999999996 | 0.9999999999999996 | \x3feffffffffffffc | t
+ \x3feffffffffffffd | 0.9999999999999997 | 0.9999999999999997 | \x3feffffffffffffd | t
+ \x3feffffffffffffe | 0.9999999999999998 | 0.9999999999999998 | \x3feffffffffffffe | t
+ \x3fefffffffffffff | 0.9999999999999999 | 0.9999999999999999 | \x3fefffffffffffff | t
+ \x3ff0000000000000 | 1 | 1 | \x3ff0000000000000 | t
+ \x3ff0000000000001 | 1.0000000000000002 | 1.0000000000000002 | \x3ff0000000000001 | t
+ \x3ff0000000000002 | 1.0000000000000004 | 1.0000000000000004 | \x3ff0000000000002 | t
+ \x3ff0000000000003 | 1.0000000000000007 | 1.0000000000000007 | \x3ff0000000000003 | t
+ \x3ff0000000000004 | 1.0000000000000009 | 1.0000000000000009 | \x3ff0000000000004 | t
+ \x3ff0000000000005 | 1.000000000000001 | 1.000000000000001 | \x3ff0000000000005 | t
+ \x3ff0000000000006 | 1.0000000000000013 | 1.0000000000000013 | \x3ff0000000000006 | t
+ \x3ff0000000000007 | 1.0000000000000016 | 1.0000000000000016 | \x3ff0000000000007 | t
+ \x3ff0000000000008 | 1.0000000000000018 | 1.0000000000000018 | \x3ff0000000000008 | t
+ \x3ff0000000000009 | 1.000000000000002 | 1.000000000000002 | \x3ff0000000000009 | t
+ \x3ff921fb54442d18 | 1.5707963267948966 | 1.5707963267948966 | \x3ff921fb54442d18 | t
+ \x4005bf0a8b14576a | 2.7182818284590455 | 2.7182818284590455 | \x4005bf0a8b14576a | t
+ \x400921fb54442d18 | 3.141592653589793 | 3.141592653589793 | \x400921fb54442d18 | t
+ \x4023ffffffffffff | 9.999999999999998 | 9.999999999999998 | \x4023ffffffffffff | t
+ \x4024000000000000 | 10 | 10 | \x4024000000000000 | t
+ \x4024000000000001 | 10.000000000000002 | 10.000000000000002 | \x4024000000000001 | t
+ \x4058ffffffffffff | 99.99999999999999 | 99.99999999999999 | \x4058ffffffffffff | t
+ \x4059000000000000 | 100 | 100 | \x4059000000000000 | t
+ \x4059000000000001 | 100.00000000000001 | 100.00000000000001 | \x4059000000000001 | t
+ \x408f3fffffffffff | 999.9999999999999 | 999.9999999999999 | \x408f3fffffffffff | t
+ \x408f400000000000 | 1000 | 1000 | \x408f400000000000 | t
+ \x408f400000000001 | 1000.0000000000001 | 1000.0000000000001 | \x408f400000000001 | t
+ \x40c387ffffffffff | 9999.999999999998 | 9999.999999999998 | \x40c387ffffffffff | t
+ \x40c3880000000000 | 10000 | 10000 | \x40c3880000000000 | t
+ \x40c3880000000001 | 10000.000000000002 | 10000.000000000002 | \x40c3880000000001 | t
+ \x40f869ffffffffff | 99999.99999999999 | 99999.99999999999 | \x40f869ffffffffff | t
+ \x40f86a0000000000 | 100000 | 100000 | \x40f86a0000000000 | t
+ \x40f86a0000000001 | 100000.00000000001 | 100000.00000000001 | \x40f86a0000000001 | t
+ \x412e847fffffffff | 999999.9999999999 | 999999.9999999999 | \x412e847fffffffff | t
+ \x412e848000000000 | 1000000 | 1000000 | \x412e848000000000 | t
+ \x412e848000000001 | 1000000.0000000001 | 1000000.0000000001 | \x412e848000000001 | t
+ \x416312cfffffffff | 9999999.999999998 | 9999999.999999998 | \x416312cfffffffff | t
+ \x416312d000000000 | 10000000 | 10000000 | \x416312d000000000 | t
+ \x416312d000000001 | 10000000.000000002 | 10000000.000000002 | \x416312d000000001 | t
+ \x4197d783ffffffff | 99999999.99999999 | 99999999.99999999 | \x4197d783ffffffff | t
+ \x4197d78400000000 | 100000000 | 100000000 | \x4197d78400000000 | t
+ \x4197d78400000001 | 100000000.00000001 | 100000000.00000001 | \x4197d78400000001 | t
+ \x41cdcd64ffffffff | 999999999.9999999 | 999999999.9999999 | \x41cdcd64ffffffff | t
+ \x41cdcd6500000000 | 1000000000 | 1000000000 | \x41cdcd6500000000 | t
+ \x41cdcd6500000001 | 1000000000.0000001 | 1000000000.0000001 | \x41cdcd6500000001 | t
+ \x4202a05f1fffffff | 9999999999.999998 | 9999999999.999998 | \x4202a05f1fffffff | t
+ \x4202a05f20000000 | 10000000000 | 10000000000 | \x4202a05f20000000 | t
+ \x4202a05f20000001 | 10000000000.000002 | 10000000000.000002 | \x4202a05f20000001 | t
+ \x42374876e7ffffff | 99999999999.99998 | 99999999999.99998 | \x42374876e7ffffff | t
+ \x42374876e8000000 | 100000000000 | 100000000000 | \x42374876e8000000 | t
+ \x42374876e8000001 | 100000000000.00002 | 100000000000.00002 | \x42374876e8000001 | t
+ \x426d1a94a1ffffff | 999999999999.9999 | 999999999999.9999 | \x426d1a94a1ffffff | t
+ \x426d1a94a2000000 | 1000000000000 | 1000000000000 | \x426d1a94a2000000 | t
+ \x426d1a94a2000001 | 1000000000000.0001 | 1000000000000.0001 | \x426d1a94a2000001 | t
+ \x42a2309ce53fffff | 9999999999999.998 | 9999999999999.998 | \x42a2309ce53fffff | t
+ \x42a2309ce5400000 | 10000000000000 | 10000000000000 | \x42a2309ce5400000 | t
+ \x42a2309ce5400001 | 10000000000000.002 | 10000000000000.002 | \x42a2309ce5400001 | t
+ \x42d6bcc41e8fffff | 99999999999999.98 | 99999999999999.98 | \x42d6bcc41e8fffff | t
+ \x42d6bcc41e900000 | 100000000000000 | 100000000000000 | \x42d6bcc41e900000 | t
+ \x42d6bcc41e900001 | 100000000000000.02 | 100000000000000.02 | \x42d6bcc41e900001 | t
+ \x430c6bf52633ffff | 999999999999999.9 | 999999999999999.9 | \x430c6bf52633ffff | t
+ \x430c6bf526340000 | 1e+15 | 1e+15 | \x430c6bf526340000 | t
+ \x430c6bf526340001 | 1.0000000000000001e+15 | 1.0000000000000001e+15 | \x430c6bf526340001 | t
+ \x4341c37937e07fff | 9.999999999999998e+15 | 9.999999999999998e+15 | \x4341c37937e07fff | t
+ \x4341c37937e08000 | 1e+16 | 1e+16 | \x4341c37937e08000 | t
+ \x4341c37937e08001 | 1.0000000000000002e+16 | 1.0000000000000002e+16 | \x4341c37937e08001 | t
+ \x4376345785d89fff | 9.999999999999998e+16 | 9.999999999999998e+16 | \x4376345785d89fff | t
+ \x4376345785d8a000 | 1e+17 | 1e+17 | \x4376345785d8a000 | t
+ \x4376345785d8a001 | 1.0000000000000002e+17 | 1.0000000000000002e+17 | \x4376345785d8a001 | t
+ \x43abc16d674ec7ff | 9.999999999999999e+17 | 9.999999999999999e+17 | \x43abc16d674ec7ff | t
+ \x43abc16d674ec800 | 1e+18 | 1e+18 | \x43abc16d674ec800 | t
+ \x43abc16d674ec801 | 1.0000000000000001e+18 | 1.0000000000000001e+18 | \x43abc16d674ec801 | t
+ \x43e158e460913cff | 9.999999999999998e+18 | 9.999999999999998e+18 | \x43e158e460913cff | t
+ \x43e158e460913d00 | 1e+19 | 1e+19 | \x43e158e460913d00 | t
+ \x43e158e460913d01 | 1.0000000000000002e+19 | 1.0000000000000002e+19 | \x43e158e460913d01 | t
+ \x4415af1d78b58c3f | 9.999999999999998e+19 | 9.999999999999998e+19 | \x4415af1d78b58c3f | t
+ \x4415af1d78b58c40 | 1e+20 | 1e+20 | \x4415af1d78b58c40 | t
+ \x4415af1d78b58c41 | 1.0000000000000002e+20 | 1.0000000000000002e+20 | \x4415af1d78b58c41 | t
+ \x444b1ae4d6e2ef4f | 9.999999999999999e+20 | 9.999999999999999e+20 | \x444b1ae4d6e2ef4f | t
+ \x444b1ae4d6e2ef50 | 1e+21 | 1e+21 | \x444b1ae4d6e2ef50 | t
+ \x444b1ae4d6e2ef51 | 1.0000000000000001e+21 | 1.0000000000000001e+21 | \x444b1ae4d6e2ef51 | t
+ \x4480f0cf064dd591 | 9.999999999999998e+21 | 9.999999999999998e+21 | \x4480f0cf064dd591 | t
+ \x4480f0cf064dd592 | 1e+22 | 1e+22 | \x4480f0cf064dd592 | t
+ \x4480f0cf064dd593 | 1.0000000000000002e+22 | 1.0000000000000002e+22 | \x4480f0cf064dd593 | t
+ \x44b52d02c7e14af5 | 9.999999999999997e+22 | 9.999999999999997e+22 | \x44b52d02c7e14af5 | t
+ \x44b52d02c7e14af6 | 9.999999999999999e+22 | 9.999999999999999e+22 | \x44b52d02c7e14af6 | t
+ \x44b52d02c7e14af7 | 1.0000000000000001e+23 | 1.0000000000000001e+23 | \x44b52d02c7e14af7 | t
+ \x44ea784379d99db3 | 9.999999999999998e+23 | 9.999999999999998e+23 | \x44ea784379d99db3 | t
+ \x44ea784379d99db4 | 1e+24 | 1e+24 | \x44ea784379d99db4 | t
+ \x44ea784379d99db5 | 1.0000000000000001e+24 | 1.0000000000000001e+24 | \x44ea784379d99db5 | t
+ \x45208b2a2c280290 | 9.999999999999999e+24 | 9.999999999999999e+24 | \x45208b2a2c280290 | t
+ \x45208b2a2c280291 | 1e+25 | 1e+25 | \x45208b2a2c280291 | t
+ \x45208b2a2c280292 | 1.0000000000000003e+25 | 1.0000000000000003e+25 | \x45208b2a2c280292 | t
+ \x7feffffffffffffe | 1.7976931348623155e+308 | 1.7976931348623155e+308 | \x7feffffffffffffe | t
+ \x7fefffffffffffff | 1.7976931348623157e+308 | 1.7976931348623157e+308 | \x7fefffffffffffff | t
+ \x4350000000000002 | 1.8014398509481992e+16 | 1.8014398509481992e+16 | \x4350000000000002 | t
+ \x4350000000002e06 | 1.8014398509529112e+16 | 1.8014398509529112e+16 | \x4350000000002e06 | t
+ \x4352000000000003 | 2.0266198323167244e+16 | 2.0266198323167244e+16 | \x4352000000000003 | t
+ \x4352000000000004 | 2.0266198323167248e+16 | 2.0266198323167248e+16 | \x4352000000000004 | t
+ \x4358000000000003 | 2.7021597764222988e+16 | 2.7021597764222988e+16 | \x4358000000000003 | t
+ \x4358000000000004 | 2.7021597764222992e+16 | 2.7021597764222992e+16 | \x4358000000000004 | t
+ \x435f000000000020 | 3.4902897112121472e+16 | 3.4902897112121472e+16 | \x435f000000000020 | t
+ \xc350000000000002 | -1.8014398509481992e+16 | -1.8014398509481992e+16 | \xc350000000000002 | t
+ \xc350000000002e06 | -1.8014398509529112e+16 | -1.8014398509529112e+16 | \xc350000000002e06 | t
+ \xc352000000000003 | -2.0266198323167244e+16 | -2.0266198323167244e+16 | \xc352000000000003 | t
+ \xc352000000000004 | -2.0266198323167248e+16 | -2.0266198323167248e+16 | \xc352000000000004 | t
+ \xc358000000000003 | -2.7021597764222988e+16 | -2.7021597764222988e+16 | \xc358000000000003 | t
+ \xc358000000000004 | -2.7021597764222992e+16 | -2.7021597764222992e+16 | \xc358000000000004 | t
+ \xc35f000000000020 | -3.4902897112121472e+16 | -3.4902897112121472e+16 | \xc35f000000000020 | t
+ \x42dc12218377de66 | 123456789012345.6 | 123456789012345.6 | \x42dc12218377de66 | t
+ \x42a674e79c5fe51f | 12345678901234.56 | 12345678901234.56 | \x42a674e79c5fe51f | t
+ \x4271f71fb04cb74c | 1234567890123.456 | 1234567890123.456 | \x4271f71fb04cb74c | t
+ \x423cbe991a145879 | 123456789012.3456 | 123456789012.3456 | \x423cbe991a145879 | t
+ \x4206fee0e1a9e061 | 12345678901.23456 | 12345678901.23456 | \x4206fee0e1a9e061 | t
+ \x41d26580b487e6b4 | 1234567890.123456 | 1234567890.123456 | \x41d26580b487e6b4 | t
+ \x419d6f34540ca453 | 123456789.0123456 | 123456789.0123456 | \x419d6f34540ca453 | t
+ \x41678c29dcd6e9dc | 12345678.90123456 | 12345678.90123456 | \x41678c29dcd6e9dc | t
+ \x4132d687e3df217d | 1234567.890123456 | 1234567.890123456 | \x4132d687e3df217d | t
+ \x40fe240c9fcb68c8 | 123456.7890123456 | 123456.7890123456 | \x40fe240c9fcb68c8 | t
+ \x40c81cd6e63c53d3 | 12345.67890123456 | 12345.67890123456 | \x40c81cd6e63c53d3 | t
+ \x40934a4584fd0fdc | 1234.567890123456 | 1234.567890123456 | \x40934a4584fd0fdc | t
+ \x405edd3c07fb4c93 | 123.4567890123456 | 123.4567890123456 | \x405edd3c07fb4c93 | t
+ \x4028b0fcd32f7076 | 12.34567890123456 | 12.34567890123456 | \x4028b0fcd32f7076 | t
+ \x3ff3c0ca428c59f8 | 1.234567890123456 | 1.234567890123456 | \x3ff3c0ca428c59f8 | t
+ \x3e60000000000000 | 2.9802322387695312e-08 | 2.9802322387695312e-08 | \x3e60000000000000 | t
+ \xc352bd2668e077c4 | -2.1098088986959632e+16 | -2.1098088986959632e+16 | \xc352bd2668e077c4 | t
+ \x434018601510c000 | 9.0608011534336e+15 | 9.0608011534336e+15 | \x434018601510c000 | t
+ \x43d055dc36f24000 | 4.708356024711512e+18 | 4.708356024711512e+18 | \x43d055dc36f24000 | t
+ \x43e052961c6f8000 | 9.409340012568248e+18 | 9.409340012568248e+18 | \x43e052961c6f8000 | t
+ \x3ff3c0ca2a5b1d5d | 1.2345678 | 1.2345678 | \x3ff3c0ca2a5b1d5d | t
+ \x4830f0cf064dd592 | 5.764607523034235e+39 | 5.764607523034235e+39 | \x4830f0cf064dd592 | t
+ \x4840f0cf064dd592 | 1.152921504606847e+40 | 1.152921504606847e+40 | \x4840f0cf064dd592 | t
+ \x4850f0cf064dd592 | 2.305843009213694e+40 | 2.305843009213694e+40 | \x4850f0cf064dd592 | t
+ \x3ff3333333333333 | 1.2 | 1.2 | \x3ff3333333333333 | t
+ \x3ff3ae147ae147ae | 1.23 | 1.23 | \x3ff3ae147ae147ae | t
+ \x3ff3be76c8b43958 | 1.234 | 1.234 | \x3ff3be76c8b43958 | t
+ \x3ff3c083126e978d | 1.2345 | 1.2345 | \x3ff3c083126e978d | t
+ \x3ff3c0c1fc8f3238 | 1.23456 | 1.23456 | \x3ff3c0c1fc8f3238 | t
+ \x3ff3c0c9539b8887 | 1.234567 | 1.234567 | \x3ff3c0c9539b8887 | t
+ \x3ff3c0ca2a5b1d5d | 1.2345678 | 1.2345678 | \x3ff3c0ca2a5b1d5d | t
+ \x3ff3c0ca4283de1b | 1.23456789 | 1.23456789 | \x3ff3c0ca4283de1b | t
+ \x3ff3c0ca43db770a | 1.234567895 | 1.234567895 | \x3ff3c0ca43db770a | t
+ \x3ff3c0ca428abd53 | 1.2345678901 | 1.2345678901 | \x3ff3c0ca428abd53 | t
+ \x3ff3c0ca428c1d2b | 1.23456789012 | 1.23456789012 | \x3ff3c0ca428c1d2b | t
+ \x3ff3c0ca428c51f2 | 1.234567890123 | 1.234567890123 | \x3ff3c0ca428c51f2 | t
+ \x3ff3c0ca428c58fc | 1.2345678901234 | 1.2345678901234 | \x3ff3c0ca428c58fc | t
+ \x3ff3c0ca428c59dd | 1.23456789012345 | 1.23456789012345 | \x3ff3c0ca428c59dd | t
+ \x3ff3c0ca428c59f8 | 1.234567890123456 | 1.234567890123456 | \x3ff3c0ca428c59f8 | t
+ \x3ff3c0ca428c59fb | 1.2345678901234567 | 1.2345678901234567 | \x3ff3c0ca428c59fb | t
+ \x40112e0be8047a7d | 4.294967294 | 4.294967294 | \x40112e0be8047a7d | t
+ \x40112e0be815a889 | 4.294967295 | 4.294967295 | \x40112e0be815a889 | t
+ \x40112e0be826d695 | 4.294967296 | 4.294967296 | \x40112e0be826d695 | t
+ \x40112e0be83804a1 | 4.294967297 | 4.294967297 | \x40112e0be83804a1 | t
+ \x40112e0be84932ad | 4.294967298 | 4.294967298 | \x40112e0be84932ad | t
+ \x0040000000000000 | 1.7800590868057611e-307 | 1.7800590868057611e-307 | \x0040000000000000 | t
+ \x007fffffffffffff | 2.8480945388892175e-306 | 2.8480945388892175e-306 | \x007fffffffffffff | t
+ \x0290000000000000 | 2.446494580089078e-296 | 2.446494580089078e-296 | \x0290000000000000 | t
+ \x029fffffffffffff | 4.8929891601781557e-296 | 4.8929891601781557e-296 | \x029fffffffffffff | t
+ \x4350000000000000 | 1.8014398509481984e+16 | 1.8014398509481984e+16 | \x4350000000000000 | t
+ \x435fffffffffffff | 3.6028797018963964e+16 | 3.6028797018963964e+16 | \x435fffffffffffff | t
+ \x1330000000000000 | 2.900835519859558e-216 | 2.900835519859558e-216 | \x1330000000000000 | t
+ \x133fffffffffffff | 5.801671039719115e-216 | 5.801671039719115e-216 | \x133fffffffffffff | t
+ \x3a6fa7161a4d6e0c | 3.196104012172126e-27 | 3.196104012172126e-27 | \x3a6fa7161a4d6e0c | t
+(209 rows)
+
+-- clean up, lest opr_sanity complain
+drop type xfloat8 cascade;
+NOTICE: drop cascades to 6 other objects
+DETAIL: drop cascades to function xfloat8in(cstring)
+drop cascades to function xfloat8out(xfloat8)
+drop cascades to cast from xfloat8 to double precision
+drop cascades to cast from double precision to xfloat8
+drop cascades to cast from xfloat8 to bigint
+drop cascades to cast from bigint to xfloat8
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
new file mode 100644
index 0000000..90ba1ff
--- /dev/null
+++ b/src/test/regress/expected/foreign_data.out
@@ -0,0 +1,2102 @@
+--
+-- Test foreign-data wrapper and server management.
+--
+-- 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;
+ fdwname | fdwhandler | fdwvalidator | fdwoptions
+------------+------------+--------------------------+------------
+ dummy | - | - |
+ postgresql | - | postgresql_fdw_validator |
+(2 rows)
+
+SELECT srvname, srvoptions FROM pg_foreign_server;
+ srvname | srvoptions
+---------+------------
+(0 rows)
+
+SELECT * FROM pg_user_mapping;
+ oid | umuser | umserver | umoptions
+-----+--------+----------+-----------
+(0 rows)
+
+-- CREATE FOREIGN DATA WRAPPER
+CREATE FOREIGN DATA WRAPPER foo VALIDATOR bar; -- ERROR
+ERROR: function bar(text[], oid) does not exist
+CREATE FOREIGN DATA WRAPPER foo;
+\dew
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator
+------------+---------------------------+---------+--------------------------
+ dummy | regress_foreign_data_user | - | -
+ foo | regress_foreign_data_user | - | -
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator
+(3 rows)
+
+CREATE FOREIGN DATA WRAPPER foo; -- duplicate
+ERROR: foreign-data wrapper "foo" already exists
+DROP FOREIGN DATA WRAPPER foo;
+CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1');
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+---------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ foo | regress_foreign_data_user | - | - | | (testing '1') |
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(3 rows)
+
+DROP FOREIGN DATA WRAPPER foo;
+CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1', testing '2'); -- ERROR
+ERROR: option "testing" provided more than once
+CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1', another '2');
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+----------------------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ foo | regress_foreign_data_user | - | - | | (testing '1', another '2') |
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(3 rows)
+
+DROP FOREIGN DATA WRAPPER foo;
+SET ROLE regress_test_role;
+CREATE FOREIGN DATA WRAPPER foo; -- ERROR
+ERROR: permission denied to create foreign-data wrapper "foo"
+HINT: Must be superuser to create a foreign-data wrapper.
+RESET ROLE;
+CREATE FOREIGN DATA WRAPPER foo VALIDATOR postgresql_fdw_validator;
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+-------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ foo | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(3 rows)
+
+-- 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
+ERROR: function invalid_fdw_handler must return type fdw_handler
+CREATE FOREIGN DATA WRAPPER test_fdw HANDLER test_fdw_handler HANDLER invalid_fdw_handler; -- ERROR
+ERROR: conflicting or redundant options
+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; -- ERROR
+ERROR: syntax error at or near ";"
+LINE 1: ALTER FOREIGN DATA WRAPPER foo;
+ ^
+ALTER FOREIGN DATA WRAPPER foo VALIDATOR bar; -- ERROR
+ERROR: function bar(text[], oid) does not exist
+ALTER FOREIGN DATA WRAPPER foo NO VALIDATOR;
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+-------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ foo | regress_foreign_data_user | - | - | | |
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(3 rows)
+
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '1', b '2');
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (SET c '4'); -- ERROR
+ERROR: option "c" not found
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP c); -- ERROR
+ERROR: option "c" not found
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD x '1', DROP x);
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+----------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ foo | regress_foreign_data_user | - | - | | (a '1', b '2') |
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(3 rows)
+
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP a, SET b '3', ADD c '4');
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+----------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ foo | regress_foreign_data_user | - | - | | (b '3', c '4') |
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(3 rows)
+
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '2');
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (b '4'); -- ERROR
+ERROR: option "b" provided more than once
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+-----------------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ foo | regress_foreign_data_user | - | - | | (b '3', c '4', a '2') |
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(3 rows)
+
+SET ROLE regress_test_role;
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD d '5'); -- ERROR
+ERROR: permission denied to alter foreign-data wrapper "foo"
+HINT: Must be superuser to alter a foreign-data wrapper.
+SET ROLE regress_test_role_super;
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD d '5');
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+------------------------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ foo | regress_foreign_data_user | - | - | | (b '3', c '4', a '2', d '5') |
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(3 rows)
+
+ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_test_role; -- ERROR
+ERROR: permission denied to change owner of foreign-data wrapper "foo"
+HINT: The owner of a foreign-data wrapper must be a superuser.
+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
+ERROR: permission denied to alter foreign-data wrapper "foo"
+HINT: Must be superuser to alter a foreign-data wrapper.
+RESET ROLE;
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+------------------------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ foo | regress_test_role_super | - | - | | (b '3', c '4', a '2', d '5') |
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(3 rows)
+
+ALTER FOREIGN DATA WRAPPER foo RENAME TO foo1;
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+------------------------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ foo1 | regress_test_role_super | - | - | | (b '3', c '4', a '2', d '5') |
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(3 rows)
+
+ALTER FOREIGN DATA WRAPPER foo1 RENAME TO foo;
+-- HANDLER related checks
+ALTER FOREIGN DATA WRAPPER foo HANDLER invalid_fdw_handler; -- ERROR
+ERROR: function invalid_fdw_handler must return type fdw_handler
+ALTER FOREIGN DATA WRAPPER foo HANDLER test_fdw_handler HANDLER anything; -- ERROR
+ERROR: conflicting or redundant options
+ALTER FOREIGN DATA WRAPPER foo HANDLER test_fdw_handler;
+WARNING: changing the foreign-data wrapper handler can change behavior of existing foreign tables
+DROP FUNCTION invalid_fdw_handler();
+-- DROP FOREIGN DATA WRAPPER
+DROP FOREIGN DATA WRAPPER nonexistent; -- ERROR
+ERROR: foreign-data wrapper "nonexistent" does not exist
+DROP FOREIGN DATA WRAPPER IF EXISTS nonexistent;
+NOTICE: foreign-data wrapper "nonexistent" does not exist, skipping
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+------------------+--------------------------+-------------------+------------------------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ foo | regress_test_role_super | test_fdw_handler | - | | (b '3', c '4', a '2', d '5') |
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(3 rows)
+
+DROP ROLE regress_test_role_super; -- ERROR
+ERROR: role "regress_test_role_super" cannot be dropped because some objects depend on it
+DETAIL: owner of foreign-data wrapper foo
+SET ROLE regress_test_role_super;
+DROP FOREIGN DATA WRAPPER foo;
+RESET ROLE;
+DROP ROLE regress_test_role_super;
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+-------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(2 rows)
+
+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
+ERROR: user mapping for "regress_foreign_data_user" already exists for server "s1"
+CREATE USER MAPPING IF NOT EXISTS FOR current_user SERVER s1; -- NOTICE
+NOTICE: user mapping for "regress_foreign_data_user" already exists for server "s1", skipping
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+-------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ foo | regress_foreign_data_user | - | - | | |
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(3 rows)
+
+\des+
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW options | Description
+------+---------------------------+----------------------+-------------------+------+---------+-------------+----------------
+ s1 | regress_foreign_data_user | foo | | | | | foreign server
+(1 row)
+
+\deu+
+ List of user mappings
+ Server | User name | FDW options
+--------+---------------------------+-------------
+ s1 | regress_foreign_data_user |
+(1 row)
+
+DROP FOREIGN DATA WRAPPER foo; -- ERROR
+ERROR: cannot drop foreign-data wrapper foo because other objects depend on it
+DETAIL: server s1 depends on foreign-data wrapper foo
+user mapping for regress_foreign_data_user on server s1 depends on server s1
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+SET ROLE regress_test_role;
+DROP FOREIGN DATA WRAPPER foo CASCADE; -- ERROR
+ERROR: must be owner of foreign-data wrapper foo
+RESET ROLE;
+DROP FOREIGN DATA WRAPPER foo CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to server s1
+drop cascades to user mapping for regress_foreign_data_user on server s1
+\dew+
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator | Access privileges | FDW options | Description
+------------+---------------------------+---------+--------------------------+-------------------+-------------+-------------
+ dummy | regress_foreign_data_user | - | - | | | useless
+ postgresql | regress_foreign_data_user | - | postgresql_fdw_validator | | |
+(2 rows)
+
+\des+
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW options | Description
+------+-------+----------------------+-------------------+------+---------+-------------+-------------
+(0 rows)
+
+\deu+
+ List of user mappings
+ Server | User name | FDW options
+--------+-----------+-------------
+(0 rows)
+
+-- exercise CREATE SERVER
+CREATE SERVER s1 FOREIGN DATA WRAPPER foo; -- ERROR
+ERROR: foreign-data wrapper "foo" does not exist
+CREATE FOREIGN DATA WRAPPER foo OPTIONS ("test wrapper" 'true');
+CREATE SERVER s1 FOREIGN DATA WRAPPER foo;
+CREATE SERVER s1 FOREIGN DATA WRAPPER foo; -- ERROR
+ERROR: server "s1" already exists
+CREATE SERVER IF NOT EXISTS s1 FOREIGN DATA WRAPPER foo; -- No ERROR, just NOTICE
+NOTICE: server "s1" already exists, skipping
+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
+ERROR: invalid option "foo"
+HINT: Valid options in this context are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, requiressl, sslmode, gsslib
+CREATE SERVER s8 FOREIGN DATA WRAPPER postgresql OPTIONS (host 'localhost', dbname 's8db');
+\des+
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW options | Description
+------+---------------------------+----------------------+-------------------+--------+---------+-----------------------------------+-------------
+ s1 | regress_foreign_data_user | foo | | | | |
+ s2 | regress_foreign_data_user | foo | | | | (host 'a', dbname 'b') |
+ s3 | regress_foreign_data_user | foo | | oracle | | |
+ s4 | regress_foreign_data_user | foo | | oracle | | (host 'a', dbname 'b') |
+ s5 | regress_foreign_data_user | foo | | | 15.0 | |
+ s6 | regress_foreign_data_user | foo | | | 16.0 | (host 'a', dbname 'b') |
+ s7 | regress_foreign_data_user | foo | | oracle | 17.0 | (host 'a', dbname 'b') |
+ s8 | regress_foreign_data_user | postgresql | | | | (host 'localhost', dbname 's8db') |
+(8 rows)
+
+SET ROLE regress_test_role;
+CREATE SERVER t1 FOREIGN DATA WRAPPER foo; -- ERROR: no usage on FDW
+ERROR: permission denied for foreign-data wrapper foo
+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+
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW options | Description
+------+---------------------------+----------------------+-------------------+--------+---------+-----------------------------------+-------------
+ s1 | regress_foreign_data_user | foo | | | | |
+ s2 | regress_foreign_data_user | foo | | | | (host 'a', dbname 'b') |
+ s3 | regress_foreign_data_user | foo | | oracle | | |
+ s4 | regress_foreign_data_user | foo | | oracle | | (host 'a', dbname 'b') |
+ s5 | regress_foreign_data_user | foo | | | 15.0 | |
+ s6 | regress_foreign_data_user | foo | | | 16.0 | (host 'a', dbname 'b') |
+ s7 | regress_foreign_data_user | foo | | oracle | 17.0 | (host 'a', dbname 'b') |
+ s8 | regress_foreign_data_user | postgresql | | | | (host 'localhost', dbname 's8db') |
+ t1 | regress_test_role | foo | | | | |
+(9 rows)
+
+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
+ERROR: permission denied for foreign-data wrapper foo
+RESET ROLE;
+GRANT regress_test_indirect TO regress_test_role;
+SET ROLE regress_test_role;
+CREATE SERVER t2 FOREIGN DATA WRAPPER foo;
+\des+
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW options | Description
+------+---------------------------+----------------------+-------------------+--------+---------+-----------------------------------+-------------
+ s1 | regress_foreign_data_user | foo | | | | |
+ s2 | regress_foreign_data_user | foo | | | | (host 'a', dbname 'b') |
+ s3 | regress_foreign_data_user | foo | | oracle | | |
+ s4 | regress_foreign_data_user | foo | | oracle | | (host 'a', dbname 'b') |
+ s5 | regress_foreign_data_user | foo | | | 15.0 | |
+ s6 | regress_foreign_data_user | foo | | | 16.0 | (host 'a', dbname 'b') |
+ s7 | regress_foreign_data_user | foo | | oracle | 17.0 | (host 'a', dbname 'b') |
+ s8 | regress_foreign_data_user | postgresql | | | | (host 'localhost', dbname 's8db') |
+ t1 | regress_test_role | foo | | | | |
+ t2 | regress_test_role | foo | | | | |
+(10 rows)
+
+RESET ROLE;
+REVOKE regress_test_indirect FROM regress_test_role;
+-- ALTER SERVER
+ALTER SERVER s0; -- ERROR
+ERROR: syntax error at or near ";"
+LINE 1: ALTER SERVER s0;
+ ^
+ALTER SERVER s0 OPTIONS (a '1'); -- ERROR
+ERROR: server "s0" does not exist
+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+
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW options | Description
+------+---------------------------+----------------------+-------------------------------------------------------+--------+---------+-----------------------------------+-------------
+ s1 | regress_foreign_data_user | foo | regress_foreign_data_user=U/regress_foreign_data_user+| | 1.0 | (servername 's1') |
+ | | | regress_test_role=U/regress_foreign_data_user | | | |
+ s2 | regress_foreign_data_user | foo | | | 1.1 | (host 'a', dbname 'b') |
+ s3 | regress_foreign_data_user | foo | | oracle | | ("tns name" 'orcl', port '1521') |
+ s4 | regress_foreign_data_user | foo | | oracle | | (host 'a', dbname 'b') |
+ s5 | regress_foreign_data_user | foo | | | 15.0 | |
+ s6 | regress_foreign_data_user | foo | regress_foreign_data_user=U/regress_foreign_data_user+| | 16.0 | (host 'a', dbname 'b') |
+ | | | regress_test_role2=U*/regress_foreign_data_user | | | |
+ s7 | regress_foreign_data_user | foo | | oracle | 17.0 | (host 'a', dbname 'b') |
+ s8 | regress_foreign_data_user | postgresql | | | | (host 'localhost', dbname 's8db') |
+ t1 | regress_test_role | foo | | | | |
+ t2 | regress_test_role | foo | | | | |
+(10 rows)
+
+SET ROLE regress_test_role;
+ALTER SERVER s1 VERSION '1.1'; -- ERROR
+ERROR: must be owner of foreign server s1
+ALTER SERVER s1 OWNER TO regress_test_role; -- ERROR
+ERROR: must be owner of foreign server s1
+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
+ERROR: permission denied for foreign-data wrapper foo
+RESET ROLE;
+ALTER SERVER s8 OPTIONS (foo '1'); -- ERROR option validation
+ERROR: invalid option "foo"
+HINT: Valid options in this context are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, requiressl, sslmode, gsslib
+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
+ERROR: must be member of role "regress_test_indirect"
+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
+ERROR: role "regress_test_indirect" cannot be dropped because some objects depend on it
+DETAIL: privileges for foreign-data wrapper foo
+owner of server s1
+\des+
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW options | Description
+------+---------------------------+----------------------+-------------------------------------------------------+--------+---------+--------------------------------------+-------------
+ s1 | regress_test_indirect | foo | regress_test_indirect=U/regress_test_indirect | | 1.1 | (servername 's1') |
+ s2 | regress_foreign_data_user | foo | | | 1.1 | (host 'a', dbname 'b') |
+ s3 | regress_foreign_data_user | foo | | oracle | | ("tns name" 'orcl', port '1521') |
+ s4 | regress_foreign_data_user | foo | | oracle | | (host 'a', dbname 'b') |
+ s5 | regress_foreign_data_user | foo | | | 15.0 | |
+ s6 | regress_foreign_data_user | foo | regress_foreign_data_user=U/regress_foreign_data_user+| | 16.0 | (host 'a', dbname 'b') |
+ | | | regress_test_role2=U*/regress_foreign_data_user | | | |
+ s7 | regress_foreign_data_user | foo | | oracle | 17.0 | (host 'a', dbname 'b') |
+ s8 | regress_foreign_data_user | postgresql | | | | (dbname 'db1', connect_timeout '30') |
+ t1 | regress_test_role | foo | | | | |
+ t2 | regress_test_role | foo | | | | |
+(10 rows)
+
+ALTER SERVER s8 RENAME to s8new;
+\des+
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW options | Description
+-------+---------------------------+----------------------+-------------------------------------------------------+--------+---------+--------------------------------------+-------------
+ s1 | regress_test_indirect | foo | regress_test_indirect=U/regress_test_indirect | | 1.1 | (servername 's1') |
+ s2 | regress_foreign_data_user | foo | | | 1.1 | (host 'a', dbname 'b') |
+ s3 | regress_foreign_data_user | foo | | oracle | | ("tns name" 'orcl', port '1521') |
+ s4 | regress_foreign_data_user | foo | | oracle | | (host 'a', dbname 'b') |
+ s5 | regress_foreign_data_user | foo | | | 15.0 | |
+ s6 | regress_foreign_data_user | foo | regress_foreign_data_user=U/regress_foreign_data_user+| | 16.0 | (host 'a', dbname 'b') |
+ | | | regress_test_role2=U*/regress_foreign_data_user | | | |
+ s7 | regress_foreign_data_user | foo | | oracle | 17.0 | (host 'a', dbname 'b') |
+ s8new | regress_foreign_data_user | postgresql | | | | (dbname 'db1', connect_timeout '30') |
+ t1 | regress_test_role | foo | | | | |
+ t2 | regress_test_role | foo | | | | |
+(10 rows)
+
+ALTER SERVER s8new RENAME to s8;
+-- DROP SERVER
+DROP SERVER nonexistent; -- ERROR
+ERROR: server "nonexistent" does not exist
+DROP SERVER IF EXISTS nonexistent;
+NOTICE: server "nonexistent" does not exist, skipping
+\des
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper
+------+---------------------------+----------------------
+ s1 | regress_test_indirect | foo
+ s2 | regress_foreign_data_user | foo
+ s3 | regress_foreign_data_user | foo
+ s4 | regress_foreign_data_user | foo
+ s5 | regress_foreign_data_user | foo
+ s6 | regress_foreign_data_user | foo
+ s7 | regress_foreign_data_user | foo
+ s8 | regress_foreign_data_user | postgresql
+ t1 | regress_test_role | foo
+ t2 | regress_test_role | foo
+(10 rows)
+
+SET ROLE regress_test_role;
+DROP SERVER s2; -- ERROR
+ERROR: must be owner of foreign server s2
+DROP SERVER s1;
+RESET ROLE;
+\des
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper
+------+---------------------------+----------------------
+ s2 | regress_foreign_data_user | foo
+ s3 | regress_foreign_data_user | foo
+ s4 | regress_foreign_data_user | foo
+ s5 | regress_foreign_data_user | foo
+ s6 | regress_foreign_data_user | foo
+ s7 | regress_foreign_data_user | foo
+ s8 | regress_foreign_data_user | postgresql
+ t1 | regress_test_role | foo
+ t2 | regress_test_role | foo
+(9 rows)
+
+ALTER SERVER s2 OWNER TO regress_test_role;
+SET ROLE regress_test_role;
+DROP SERVER s2;
+RESET ROLE;
+\des
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper
+------+---------------------------+----------------------
+ s3 | regress_foreign_data_user | foo
+ s4 | regress_foreign_data_user | foo
+ s5 | regress_foreign_data_user | foo
+ s6 | regress_foreign_data_user | foo
+ s7 | regress_foreign_data_user | foo
+ s8 | regress_foreign_data_user | postgresql
+ t1 | regress_test_role | foo
+ t2 | regress_test_role | foo
+(8 rows)
+
+CREATE USER MAPPING FOR current_user SERVER s3;
+\deu
+ List of user mappings
+ Server | User name
+--------+---------------------------
+ s3 | regress_foreign_data_user
+(1 row)
+
+DROP SERVER s3; -- ERROR
+ERROR: cannot drop server s3 because other objects depend on it
+DETAIL: user mapping for regress_foreign_data_user on server s3 depends on server s3
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP SERVER s3 CASCADE;
+NOTICE: drop cascades to user mapping for regress_foreign_data_user on server s3
+\des
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper
+------+---------------------------+----------------------
+ s4 | regress_foreign_data_user | foo
+ s5 | regress_foreign_data_user | foo
+ s6 | regress_foreign_data_user | foo
+ s7 | regress_foreign_data_user | foo
+ s8 | regress_foreign_data_user | postgresql
+ t1 | regress_test_role | foo
+ t2 | regress_test_role | foo
+(7 rows)
+
+\deu
+List of user mappings
+ Server | User name
+--------+-----------
+(0 rows)
+
+-- CREATE USER MAPPING
+CREATE USER MAPPING FOR regress_test_missing_role SERVER s1; -- ERROR
+ERROR: role "regress_test_missing_role" does not exist
+CREATE USER MAPPING FOR current_user SERVER s1; -- ERROR
+ERROR: server "s1" does not exist
+CREATE USER MAPPING FOR current_user SERVER s4;
+CREATE USER MAPPING FOR user SERVER s4; -- ERROR duplicate
+ERROR: user mapping for "regress_foreign_data_user" already exists for server "s4"
+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
+ERROR: invalid option "username"
+HINT: Valid options in this context are: user, password
+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
+ERROR: permission denied for foreign server s7
+CREATE USER MAPPING FOR public SERVER s8; -- ERROR
+ERROR: must be owner of foreign server s8
+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
+ List of user mappings
+ Server | User name
+--------+---------------------------
+ s4 | public
+ s4 | regress_foreign_data_user
+ s5 | regress_test_role
+ s6 | regress_test_role
+ s8 | regress_foreign_data_user
+ t1 | public
+ t1 | regress_test_role
+(7 rows)
+
+-- ALTER USER MAPPING
+ALTER USER MAPPING FOR regress_test_missing_role SERVER s4 OPTIONS (gotcha 'true'); -- ERROR
+ERROR: role "regress_test_missing_role" does not exist
+ALTER USER MAPPING FOR user SERVER ss4 OPTIONS (gotcha 'true'); -- ERROR
+ERROR: server "ss4" does not exist
+ALTER USER MAPPING FOR public SERVER s5 OPTIONS (gotcha 'true'); -- ERROR
+ERROR: user mapping for "public" does not exist for server "s5"
+ALTER USER MAPPING FOR current_user SERVER s8 OPTIONS (username 'test'); -- ERROR
+ERROR: invalid option "username"
+HINT: Valid options in this context are: user, password
+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
+ERROR: must be owner of foreign server s4
+ALTER USER MAPPING FOR public SERVER t1 OPTIONS (ADD modified '1');
+RESET ROLE;
+\deu+
+ List of user mappings
+ Server | User name | FDW options
+--------+---------------------------+----------------------------------
+ s4 | public | ("this mapping" 'is public')
+ s4 | regress_foreign_data_user |
+ s5 | regress_test_role | (modified '1')
+ s6 | regress_test_role | (username 'test')
+ s8 | regress_foreign_data_user | (password 'public')
+ t1 | public | (modified '1')
+ t1 | regress_test_role | (username 'bob', password 'boo')
+(7 rows)
+
+-- DROP USER MAPPING
+DROP USER MAPPING FOR regress_test_missing_role SERVER s4; -- ERROR
+ERROR: role "regress_test_missing_role" does not exist
+DROP USER MAPPING FOR user SERVER ss4;
+ERROR: server "ss4" does not exist
+DROP USER MAPPING FOR public SERVER s7; -- ERROR
+ERROR: user mapping for "public" does not exist for server "s7"
+DROP USER MAPPING IF EXISTS FOR regress_test_missing_role SERVER s4;
+NOTICE: role "regress_test_missing_role" does not exist, skipping
+DROP USER MAPPING IF EXISTS FOR user SERVER ss4;
+NOTICE: server "ss4" does not exist, skipping
+DROP USER MAPPING IF EXISTS FOR public SERVER s7;
+NOTICE: user mapping for "public" does not exist for server "s7", skipping
+CREATE USER MAPPING FOR public SERVER s8;
+SET ROLE regress_test_role;
+DROP USER MAPPING FOR public SERVER s8; -- ERROR
+ERROR: must be owner of foreign server s8
+RESET ROLE;
+DROP SERVER s7;
+\deu
+ List of user mappings
+ Server | User name
+--------+---------------------------
+ s4 | public
+ s4 | regress_foreign_data_user
+ s5 | regress_test_role
+ s6 | regress_test_role
+ s8 | public
+ s8 | regress_foreign_data_user
+ t1 | public
+ t1 | regress_test_role
+(8 rows)
+
+-- CREATE FOREIGN TABLE
+CREATE SCHEMA foreign_schema;
+CREATE SERVER s0 FOREIGN DATA WRAPPER dummy;
+CREATE FOREIGN TABLE ft1 (); -- ERROR
+ERROR: syntax error at or near ";"
+LINE 1: CREATE FOREIGN TABLE ft1 ();
+ ^
+CREATE FOREIGN TABLE ft1 () SERVER no_server; -- ERROR
+ERROR: server "no_server" does not exist
+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
+ERROR: primary key constraints are not supported on foreign tables
+LINE 2: c1 integer OPTIONS ("param 1" 'val1') PRIMARY KEY,
+ ^
+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
+ERROR: foreign key constraints are not supported on foreign tables
+LINE 2: c1 integer OPTIONS ("param 1" 'val1') REFERENCES ref_table ...
+ ^
+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
+ERROR: unique constraints are not supported on foreign tables
+LINE 5: UNIQUE (c3)
+ ^
+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
+ Foreign table "public.ft1"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+--------------------------------+----------+--------------+-------------
+ c1 | integer | | not null | | ("param 1" 'val1') | plain | | ft1.c1
+ c2 | text | | | | (param2 'val2', param3 'val3') | extended | |
+ c3 | date | | | | | plain | |
+Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
+ "ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+
+\det+
+ List of foreign tables
+ Schema | Table | Server | FDW options | Description
+--------+-------+--------+-------------------------------------------------+-------------
+ public | ft1 | s0 | (delimiter ',', quote '"', "be quoted" 'value') | ft1
+(1 row)
+
+CREATE INDEX id_ft1_c2 ON ft1 (c2); -- ERROR
+ERROR: cannot create index on foreign table "ft1"
+SELECT * FROM ft1; -- ERROR
+ERROR: foreign-data wrapper "dummy" has no handler
+EXPLAIN SELECT * FROM ft1; -- ERROR
+ERROR: foreign-data wrapper "dummy" has no handler
+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
+ERROR: cannot create unique index on partitioned table "lt1"
+DETAIL: Table "lt1" contains partitions that are foreign tables.
+ALTER TABLE lt1 ADD PRIMARY KEY (a); -- ERROR
+ERROR: cannot create unique index on partitioned table "lt1"
+DETAIL: Table "lt1" contains partitions that are foreign tables.
+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;
+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
+ERROR: cannot create foreign partition of partitioned table "lt1"
+DETAIL: Table "lt1" contains indexes that are unique.
+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
+ERROR: cannot attach foreign table "ft_part2" as partition of partitioned table "lt1"
+DETAIL: Partitioned table "lt1" contains unique indexes.
+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);
+ERROR: cannot create unique index on partitioned table "lt1"
+DETAIL: Table "lt1" contains partitions that are foreign tables.
+ALTER TABLE lt1 ADD PRIMARY KEY (a);
+ERROR: cannot create unique index on partitioned table "lt1_part1"
+DETAIL: Table "lt1_part1" contains partitions that are foreign tables.
+DROP FOREIGN TABLE ft_part_1_1, ft_part_1_2;
+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;
+ERROR: cannot create foreign partition of partitioned table "lt1_part1"
+DETAIL: Table "lt1_part1" contains indexes that are unique.
+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);
+ERROR: cannot attach foreign table "ft_part_1_2" as partition of partitioned table "lt1_part1"
+DETAIL: Partitioned table "lt1_part1" contains unique indexes.
+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
+ERROR: "ft1" is not a table
+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
+ERROR: cannot alter system column "xmin"
+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
+ Foreign table "public.ft1"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+--------------------------------+----------+--------------+-------------
+ c1 | integer | | not null | | ("param 1" 'val1') | plain | 10000 |
+ c2 | text | | | | (param2 'val2', param3 'val3') | extended | |
+ c3 | date | | | | | plain | |
+ c4 | integer | | | 0 | | plain | |
+ c5 | integer | | | | | plain | |
+ c6 | integer | | not null | | | plain | |
+ c7 | integer | | | | (p1 'v1', p2 'v2') | plain | |
+ c8 | text | | | | (p2 'V2') | plain | |
+ c9 | integer | | | | | plain | |
+ c10 | integer | | | | (p1 'v1') | plain | |
+Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
+ "ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+
+-- 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
+ERROR: cannot alter foreign table "ft1" because column "use_ft1_column_type.x" uses its row type
+DROP TABLE use_ft1_column_type;
+ALTER FOREIGN TABLE ft1 ADD PRIMARY KEY (c7); -- ERROR
+ERROR: primary key constraints are not supported on foreign tables
+LINE 1: ALTER FOREIGN TABLE ft1 ADD PRIMARY KEY (c7);
+ ^
+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
+ERROR: "ft1" is not a table
+ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check;
+ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR
+ERROR: constraint "no_const" of relation "ft1" does not exist
+ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
+NOTICE: constraint "no_const" of relation "ft1" does not exist, skipping
+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
+ERROR: column "no_column" of relation "ft1" does not exist
+ALTER FOREIGN TABLE ft1 DROP COLUMN IF EXISTS no_column;
+NOTICE: column "no_column" of relation "ft1" does not exist, skipping
+ALTER FOREIGN TABLE ft1 DROP COLUMN c9;
+ALTER FOREIGN TABLE ft1 SET SCHEMA foreign_schema;
+ALTER FOREIGN TABLE ft1 SET TABLESPACE ts; -- ERROR
+ERROR: relation "ft1" does not exist
+ALTER FOREIGN TABLE foreign_schema.ft1 SET TABLESPACE ts; -- ERROR
+ERROR: "ft1" is not a table, materialized view, index, or partitioned index
+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
+ Foreign table "foreign_schema.foreign_table_1"
+ Column | Type | Collation | Nullable | Default | FDW options
+------------------+---------+-----------+----------+---------+--------------------------------
+ foreign_column_1 | integer | | not null | | ("param 1" 'val1')
+ c2 | text | | | | (param2 'val2', param3 'val3')
+ c3 | date | | | |
+ c4 | integer | | | 0 |
+ c5 | integer | | | |
+ c6 | integer | | not null | |
+ c7 | integer | | | | (p1 'v1', p2 'v2')
+ c8 | text | | | | (p2 'V2')
+ c10 | integer | | | | (p1 'v1')
+Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
+ "ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
+Server: s0
+FDW options: (quote '~', "be quoted" 'value', escape '@')
+
+-- alter noexisting table
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c4 integer;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c6 integer;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c7 integer NOT NULL;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c8 integer;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c9 integer;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c10 integer OPTIONS (p1 'v1');
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c6 SET NOT NULL;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c7 DROP NOT NULL;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c8 TYPE char(10);
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c8 SET DATA TYPE text;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+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');
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c8 OPTIONS (SET p2 'V2', DROP p1);
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP CONSTRAINT IF EXISTS no_const;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP CONSTRAINT ft1_c1_check;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 OWNER TO regress_test_role;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@');
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP COLUMN IF EXISTS no_column;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP COLUMN c9;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 SET SCHEMA foreign_schema;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 RENAME c1 TO foreign_column_1;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 RENAME TO foreign_table_1;
+NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+-- Information schema
+SELECT * FROM information_schema.foreign_data_wrappers ORDER BY 1, 2;
+ foreign_data_wrapper_catalog | foreign_data_wrapper_name | authorization_identifier | library_name | foreign_data_wrapper_language
+------------------------------+---------------------------+---------------------------+--------------+-------------------------------
+ regression | dummy | regress_foreign_data_user | | c
+ regression | foo | regress_foreign_data_user | | c
+ regression | postgresql | regress_foreign_data_user | | c
+(3 rows)
+
+SELECT * FROM information_schema.foreign_data_wrapper_options ORDER BY 1, 2, 3;
+ foreign_data_wrapper_catalog | foreign_data_wrapper_name | option_name | option_value
+------------------------------+---------------------------+--------------+--------------
+ regression | foo | test wrapper | true
+(1 row)
+
+SELECT * FROM information_schema.foreign_servers ORDER BY 1, 2;
+ foreign_server_catalog | foreign_server_name | foreign_data_wrapper_catalog | foreign_data_wrapper_name | foreign_server_type | foreign_server_version | authorization_identifier
+------------------------+---------------------+------------------------------+---------------------------+---------------------+------------------------+---------------------------
+ regression | s0 | regression | dummy | | | regress_foreign_data_user
+ regression | s4 | regression | foo | oracle | | regress_foreign_data_user
+ regression | s5 | regression | foo | | 15.0 | regress_test_role
+ regression | s6 | regression | foo | | 16.0 | regress_test_indirect
+ regression | s8 | regression | postgresql | | | regress_foreign_data_user
+ regression | t1 | regression | foo | | | regress_test_indirect
+ regression | t2 | regression | foo | | | regress_test_role
+(7 rows)
+
+SELECT * FROM information_schema.foreign_server_options ORDER BY 1, 2, 3;
+ foreign_server_catalog | foreign_server_name | option_name | option_value
+------------------------+---------------------+-----------------+--------------
+ regression | s4 | dbname | b
+ regression | s4 | host | a
+ regression | s6 | dbname | b
+ regression | s6 | host | a
+ regression | s8 | connect_timeout | 30
+ regression | s8 | dbname | db1
+(6 rows)
+
+SELECT * FROM information_schema.user_mappings ORDER BY lower(authorization_identifier), 2, 3;
+ authorization_identifier | foreign_server_catalog | foreign_server_name
+---------------------------+------------------------+---------------------
+ PUBLIC | regression | s4
+ PUBLIC | regression | s8
+ PUBLIC | regression | t1
+ regress_foreign_data_user | regression | s4
+ regress_foreign_data_user | regression | s8
+ regress_test_role | regression | s5
+ regress_test_role | regression | s6
+ regress_test_role | regression | t1
+(8 rows)
+
+SELECT * FROM information_schema.user_mapping_options ORDER BY lower(authorization_identifier), 2, 3, 4;
+ authorization_identifier | foreign_server_catalog | foreign_server_name | option_name | option_value
+---------------------------+------------------------+---------------------+--------------+--------------
+ PUBLIC | regression | s4 | this mapping | is public
+ PUBLIC | regression | t1 | modified | 1
+ regress_foreign_data_user | regression | s8 | password | public
+ regress_test_role | regression | s5 | modified | 1
+ regress_test_role | regression | s6 | username | test
+ regress_test_role | regression | t1 | password | boo
+ regress_test_role | regression | t1 | username | bob
+(7 rows)
+
+SELECT * FROM information_schema.usage_privileges WHERE object_type LIKE 'FOREIGN%' AND object_name IN ('s6', 'foo') ORDER BY 1, 2, 3, 4, 5;
+ grantor | grantee | object_catalog | object_schema | object_name | object_type | privilege_type | is_grantable
+---------------------------+---------------------------+----------------+---------------+-------------+----------------------+----------------+--------------
+ regress_foreign_data_user | regress_foreign_data_user | regression | | foo | FOREIGN DATA WRAPPER | USAGE | YES
+ regress_foreign_data_user | regress_test_indirect | regression | | foo | FOREIGN DATA WRAPPER | USAGE | NO
+ regress_test_indirect | regress_test_indirect | regression | | s6 | FOREIGN SERVER | USAGE | YES
+ regress_test_indirect | regress_test_role2 | regression | | s6 | FOREIGN SERVER | USAGE | YES
+(4 rows)
+
+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;
+ grantor | grantee | object_catalog | object_schema | object_name | object_type | privilege_type | is_grantable
+---------------------------+---------------------------+----------------+---------------+-------------+----------------------+----------------+--------------
+ regress_foreign_data_user | regress_foreign_data_user | regression | | foo | FOREIGN DATA WRAPPER | USAGE | YES
+ regress_foreign_data_user | regress_test_indirect | regression | | foo | FOREIGN DATA WRAPPER | USAGE | NO
+ regress_test_indirect | regress_test_indirect | regression | | s6 | FOREIGN SERVER | USAGE | YES
+ regress_test_indirect | regress_test_role2 | regression | | s6 | FOREIGN SERVER | USAGE | YES
+(4 rows)
+
+SELECT * FROM information_schema.foreign_tables ORDER BY 1, 2, 3;
+ foreign_table_catalog | foreign_table_schema | foreign_table_name | foreign_server_catalog | foreign_server_name
+-----------------------+----------------------+--------------------+------------------------+---------------------
+ regression | foreign_schema | foreign_table_1 | regression | s0
+(1 row)
+
+SELECT * FROM information_schema.foreign_table_options ORDER BY 1, 2, 3, 4;
+ foreign_table_catalog | foreign_table_schema | foreign_table_name | option_name | option_value
+-----------------------+----------------------+--------------------+-------------+--------------
+ regression | foreign_schema | foreign_table_1 | be quoted | value
+ regression | foreign_schema | foreign_table_1 | escape | @
+ regression | foreign_schema | foreign_table_1 | quote | ~
+(3 rows)
+
+SET ROLE regress_test_role;
+SELECT * FROM information_schema.user_mapping_options ORDER BY 1, 2, 3, 4;
+ authorization_identifier | foreign_server_catalog | foreign_server_name | option_name | option_value
+--------------------------+------------------------+---------------------+-------------+--------------
+ PUBLIC | regression | t1 | modified | 1
+ regress_test_role | regression | s5 | modified | 1
+ regress_test_role | regression | s6 | username | test
+ regress_test_role | regression | t1 | password | boo
+ regress_test_role | regression | t1 | username | bob
+(5 rows)
+
+SELECT * FROM information_schema.usage_privileges WHERE object_type LIKE 'FOREIGN%' AND object_name IN ('s6', 'foo') ORDER BY 1, 2, 3, 4, 5;
+ grantor | grantee | object_catalog | object_schema | object_name | object_type | privilege_type | is_grantable
+---------------------------+-----------------------+----------------+---------------+-------------+----------------------+----------------+--------------
+ regress_foreign_data_user | regress_test_indirect | regression | | foo | FOREIGN DATA WRAPPER | USAGE | NO
+ regress_test_indirect | regress_test_indirect | regression | | s6 | FOREIGN SERVER | USAGE | YES
+ regress_test_indirect | regress_test_role2 | regression | | s6 | FOREIGN SERVER | USAGE | YES
+(3 rows)
+
+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;
+ grantor | grantee | object_catalog | object_schema | object_name | object_type | privilege_type | is_grantable
+---------------------------+-----------------------+----------------+---------------+-------------+----------------------+----------------+--------------
+ regress_foreign_data_user | regress_test_indirect | regression | | foo | FOREIGN DATA WRAPPER | USAGE | NO
+ regress_test_indirect | regress_test_indirect | regression | | s6 | FOREIGN SERVER | USAGE | YES
+ regress_test_indirect | regress_test_role2 | regression | | s6 | FOREIGN SERVER | USAGE | YES
+(3 rows)
+
+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;
+ authorization_identifier | foreign_server_catalog | foreign_server_name | option_name | option_value
+--------------------------+------------------------+---------------------+-------------+--------------
+ regress_test_role | regression | s6 | username |
+(1 row)
+
+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');
+ has_foreign_data_wrapper_privilege
+------------------------------------
+ t
+(1 row)
+
+SELECT has_foreign_data_wrapper_privilege('regress_test_role', 'foo', 'USAGE');
+ has_foreign_data_wrapper_privilege
+------------------------------------
+ t
+(1 row)
+
+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');
+ has_foreign_data_wrapper_privilege
+------------------------------------
+ t
+(1 row)
+
+SELECT has_foreign_data_wrapper_privilege(
+ (SELECT oid FROM pg_foreign_data_wrapper WHERE fdwname='foo'), 'USAGE');
+ has_foreign_data_wrapper_privilege
+------------------------------------
+ t
+(1 row)
+
+SELECT has_foreign_data_wrapper_privilege(
+ (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'), 'foo', 'USAGE');
+ has_foreign_data_wrapper_privilege
+------------------------------------
+ t
+(1 row)
+
+SELECT has_foreign_data_wrapper_privilege('foo', 'USAGE');
+ has_foreign_data_wrapper_privilege
+------------------------------------
+ t
+(1 row)
+
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role;
+SELECT has_foreign_data_wrapper_privilege('regress_test_role', 'foo', 'USAGE');
+ has_foreign_data_wrapper_privilege
+------------------------------------
+ t
+(1 row)
+
+-- has_server_privilege
+SELECT has_server_privilege('regress_test_role',
+ (SELECT oid FROM pg_foreign_server WHERE srvname='s8'), 'USAGE');
+ has_server_privilege
+----------------------
+ f
+(1 row)
+
+SELECT has_server_privilege('regress_test_role', 's8', 'USAGE');
+ has_server_privilege
+----------------------
+ f
+(1 row)
+
+SELECT has_server_privilege(
+ (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'),
+ (SELECT oid FROM pg_foreign_server WHERE srvname='s8'), 'USAGE');
+ has_server_privilege
+----------------------
+ f
+(1 row)
+
+SELECT has_server_privilege(
+ (SELECT oid FROM pg_foreign_server WHERE srvname='s8'), 'USAGE');
+ has_server_privilege
+----------------------
+ t
+(1 row)
+
+SELECT has_server_privilege(
+ (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'), 's8', 'USAGE');
+ has_server_privilege
+----------------------
+ f
+(1 row)
+
+SELECT has_server_privilege('s8', 'USAGE');
+ has_server_privilege
+----------------------
+ t
+(1 row)
+
+GRANT USAGE ON FOREIGN SERVER s8 TO regress_test_role;
+SELECT has_server_privilege('regress_test_role', 's8', 'USAGE');
+ has_server_privilege
+----------------------
+ t
+(1 row)
+
+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;
+WARNING: changing the foreign-data wrapper validator can cause the options for dependent objects to become invalid
+-- Privileges
+SET ROLE regress_unprivileged_role;
+CREATE FOREIGN DATA WRAPPER foobar; -- ERROR
+ERROR: permission denied to create foreign-data wrapper "foobar"
+HINT: Must be superuser to create a foreign-data wrapper.
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (gotcha 'true'); -- ERROR
+ERROR: permission denied to alter foreign-data wrapper "foo"
+HINT: Must be superuser to alter a foreign-data wrapper.
+ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_unprivileged_role; -- ERROR
+ERROR: permission denied to change owner of foreign-data wrapper "foo"
+HINT: Must be superuser to change owner of a foreign-data wrapper.
+DROP FOREIGN DATA WRAPPER foo; -- ERROR
+ERROR: must be owner of foreign-data wrapper foo
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role; -- ERROR
+ERROR: permission denied for foreign-data wrapper foo
+CREATE SERVER s9 FOREIGN DATA WRAPPER foo; -- ERROR
+ERROR: permission denied for foreign-data wrapper foo
+ALTER SERVER s4 VERSION '0.5'; -- ERROR
+ERROR: must be owner of foreign server s4
+ALTER SERVER s4 OWNER TO regress_unprivileged_role; -- ERROR
+ERROR: must be owner of foreign server s4
+DROP SERVER s4; -- ERROR
+ERROR: must be owner of foreign server s4
+GRANT USAGE ON FOREIGN SERVER s4 TO regress_test_role; -- ERROR
+ERROR: permission denied for foreign server s4
+CREATE USER MAPPING FOR public SERVER s4; -- ERROR
+ERROR: must be owner of foreign server s4
+ALTER USER MAPPING FOR regress_test_role SERVER s6 OPTIONS (gotcha 'true'); -- ERROR
+ERROR: must be owner of foreign server s6
+DROP USER MAPPING FOR regress_test_role SERVER s6; -- ERROR
+ERROR: must be owner of foreign server s6
+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
+ERROR: permission denied to create foreign-data wrapper "foobar"
+HINT: Must be superuser to create a foreign-data wrapper.
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (gotcha 'true'); -- ERROR
+ERROR: permission denied to alter foreign-data wrapper "foo"
+HINT: Must be superuser to alter a foreign-data wrapper.
+DROP FOREIGN DATA WRAPPER foo; -- ERROR
+ERROR: must be owner of foreign-data wrapper foo
+GRANT USAGE ON FOREIGN DATA WRAPPER postgresql TO regress_test_role; -- WARNING
+WARNING: no privileges were granted for "postgresql"
+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
+ERROR: must be owner of foreign server s6
+DROP SERVER s6; -- ERROR
+ERROR: must be owner of foreign server s6
+GRANT USAGE ON FOREIGN SERVER s6 TO regress_test_role; -- ERROR
+ERROR: permission denied for foreign server s6
+GRANT USAGE ON FOREIGN SERVER s9 TO regress_test_role;
+CREATE USER MAPPING FOR public SERVER s6; -- ERROR
+ERROR: must be owner of foreign server s6
+CREATE USER MAPPING FOR public SERVER s9;
+ALTER USER MAPPING FOR regress_test_role SERVER s6 OPTIONS (gotcha 'true'); -- ERROR
+ERROR: must be owner of foreign server s6
+DROP USER MAPPING FOR regress_test_role SERVER s6; -- ERROR
+ERROR: must be owner of foreign server s6
+RESET ROLE;
+REVOKE USAGE ON FOREIGN DATA WRAPPER foo FROM regress_unprivileged_role; -- ERROR
+ERROR: dependent privileges exist
+HINT: Use CASCADE to revoke them too.
+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
+ERROR: permission denied for foreign-data wrapper foo
+CREATE SERVER s10 FOREIGN DATA WRAPPER foo; -- ERROR
+ERROR: permission denied for foreign-data wrapper foo
+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;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server s9
+drop cascades to user mapping for regress_unprivileged_role on server s9
+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
+ERROR: must be owner of foreign server s9
+GRANT USAGE ON FOREIGN SERVER s9 TO regress_test_role; -- WARNING
+WARNING: no privileges were granted for "s9"
+CREATE USER MAPPING FOR current_user SERVER s9;
+DROP SERVER s9 CASCADE; -- ERROR
+ERROR: must be owner of foreign server s9
+-- 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+
+ List of user mappings
+ Server | User name | FDW options
+--------+---------------------------+-------------------
+ s10 | public | ("user" 'secret')
+ s10 | regress_unprivileged_role |
+ s4 | regress_foreign_data_user |
+ s5 | regress_test_role | (modified '1')
+ s6 | regress_test_role |
+ s8 | public |
+ s8 | regress_foreign_data_user |
+ s9 | regress_unprivileged_role |
+ t1 | public | (modified '1')
+(9 rows)
+
+RESET ROLE;
+-- superuser can see all option fields
+\deu+
+ List of user mappings
+ Server | User name | FDW options
+--------+---------------------------+---------------------
+ s10 | public | ("user" 'secret')
+ s10 | regress_unprivileged_role | ("user" 'secret')
+ s4 | regress_foreign_data_user |
+ s5 | regress_test_role | (modified '1')
+ s6 | regress_test_role |
+ s8 | public |
+ s8 | regress_foreign_data_user | (password 'public')
+ s9 | regress_unprivileged_role |
+ t1 | public | (modified '1')
+(9 rows)
+
+-- unprivileged user cannot see any option field
+SET ROLE regress_unprivileged_role;
+\deu+
+ List of user mappings
+ Server | User name | FDW options
+--------+---------------------------+-------------
+ s10 | public |
+ s10 | regress_unprivileged_role |
+ s4 | regress_foreign_data_user |
+ s5 | regress_test_role |
+ s6 | regress_test_role |
+ s8 | public |
+ s8 | regress_foreign_data_user |
+ s9 | regress_unprivileged_role |
+ t1 | public |
+(9 rows)
+
+RESET ROLE;
+DROP SERVER s10 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server s10
+drop cascades to user mapping for regress_unprivileged_role on server s10
+-- 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();
+ERROR: "foreign_table_1" is a foreign table
+DETAIL: Triggers on foreign tables cannot have transition tables.
+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();
+ERROR: "foreign_table_1" is a foreign table
+DETAIL: Foreign tables cannot have constraint triggers.
+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
+ Table "public.fd_pt1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+Child tables: ft2
+
+\d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: fd_pt1
+
+DROP FOREIGN TABLE ft2;
+\d+ fd_pt1
+ Table "public.fd_pt1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+
+CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+\d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+
+ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
+\d+ fd_pt1
+ Table "public.fd_pt1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+Child tables: ft2
+
+\d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: fd_pt1
+
+CREATE TABLE ct3() INHERITS(ft2);
+CREATE FOREIGN TABLE ft3 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+) INHERITS(ft2)
+ SERVER s0;
+NOTICE: merging column "c1" with inherited definition
+NOTICE: merging column "c2" with inherited definition
+NOTICE: merging column "c3" with inherited definition
+\d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: fd_pt1
+Child tables: ct3,
+ ft3
+
+\d+ ct3
+ Table "public.ct3"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+Inherits: ft2
+
+\d+ ft3
+ Foreign table "public.ft3"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Server: s0
+Inherits: ft2
+
+-- 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
+ Table "public.fd_pt1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+ c4 | integer | | | | plain | |
+ c5 | integer | | | 0 | plain | |
+ c6 | integer | | | | plain | |
+ c7 | integer | | not null | | plain | |
+ c8 | integer | | | | plain | |
+Child tables: ft2
+
+\d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+ c4 | integer | | | | | plain | |
+ c5 | integer | | | 0 | | plain | |
+ c6 | integer | | | | | plain | |
+ c7 | integer | | not null | | | plain | |
+ c8 | integer | | | | | plain | |
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: fd_pt1
+Child tables: ct3,
+ ft3
+
+\d+ ct3
+ Table "public.ct3"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+ c4 | integer | | | | plain | |
+ c5 | integer | | | 0 | plain | |
+ c6 | integer | | | | plain | |
+ c7 | integer | | not null | | plain | |
+ c8 | integer | | | | plain | |
+Inherits: ft2
+
+\d+ ft3
+ Foreign table "public.ft3"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+ c4 | integer | | | | | plain | |
+ c5 | integer | | | 0 | | plain | |
+ c6 | integer | | | | | plain | |
+ c7 | integer | | not null | | | plain | |
+ c8 | integer | | | | | plain | |
+Server: s0
+Inherits: ft2
+
+-- 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
+ERROR: "ft2" is not a table
+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
+ Table "public.fd_pt1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | 10000 |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+ c4 | integer | | | 0 | plain | |
+ c5 | integer | | | | plain | |
+ c6 | integer | | not null | | plain | |
+ c7 | integer | | | | plain | |
+ c8 | text | | | | external | |
+Child tables: ft2
+
+\d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | 10000 |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+ c4 | integer | | | 0 | | plain | |
+ c5 | integer | | | | | plain | |
+ c6 | integer | | not null | | | plain | |
+ c7 | integer | | | | | plain | |
+ c8 | text | | | | | external | |
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: fd_pt1
+Child tables: ct3,
+ ft3
+
+-- 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
+ Table "public.fd_pt1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | 10000 |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+Child tables: ft2
+
+\d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | 10000 |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: fd_pt1
+Child tables: ct3,
+ ft3
+
+-- 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;
+ relname | conname | contype | conislocal | coninhcount | connoinherit
+---------+------------+---------+------------+-------------+--------------
+ fd_pt1 | fd_pt1chk1 | c | t | 0 | t
+ fd_pt1 | fd_pt1chk2 | c | t | 0 | f
+(2 rows)
+
+-- child does not inherit NO INHERIT constraints
+\d+ fd_pt1
+ Table "public.fd_pt1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | 10000 |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+Check constraints:
+ "fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
+ "fd_pt1chk2" CHECK (c2 <> ''::text)
+Child tables: ft2
+
+\d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | 10000 |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Check constraints:
+ "fd_pt1chk2" CHECK (c2 <> ''::text)
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: fd_pt1
+Child tables: ct3,
+ ft3
+
+DROP FOREIGN TABLE ft2; -- ERROR
+ERROR: cannot drop foreign table ft2 because other objects depend on it
+DETAIL: table ct3 depends on foreign table ft2
+foreign table ft3 depends on foreign table ft2
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP FOREIGN TABLE ft2 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table ct3
+drop cascades to foreign table ft3
+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
+ERROR: child table is missing constraint "fd_pt1chk2"
+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
+ Table "public.fd_pt1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | 10000 |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+Check constraints:
+ "fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
+ "fd_pt1chk2" CHECK (c2 <> ''::text)
+Child tables: ft2
+
+\d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Check constraints:
+ "fd_pt1chk2" CHECK (c2 <> ''::text)
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: fd_pt1
+
+-- 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
+ Table "public.fd_pt1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | 10000 |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+Check constraints:
+ "fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
+Child tables: ft2
+
+\d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Check constraints:
+ "fd_pt1chk2" CHECK (c2 <> ''::text)
+ "fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: fd_pt1
+
+-- VALIDATE CONSTRAINT need do nothing on foreign tables
+ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
+\d+ fd_pt1
+ Table "public.fd_pt1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | 10000 |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+Check constraints:
+ "fd_pt1chk3" CHECK (c2 <> ''::text)
+Child tables: ft2
+
+\d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Check constraints:
+ "fd_pt1chk2" CHECK (c2 <> ''::text)
+ "fd_pt1chk3" CHECK (c2 <> ''::text)
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: fd_pt1
+
+-- 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
+ Table "public.fd_pt1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | not null | | plain | 10000 |
+ f2 | text | | | | extended | |
+ f3 | date | | | | plain | |
+Check constraints:
+ "f2_check" CHECK (f2 <> ''::text)
+Child tables: ft2
+
+\d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ f1 | integer | | not null | | | plain | |
+ f2 | text | | | | | extended | |
+ f3 | date | | | | | plain | |
+Check constraints:
+ "f2_check" CHECK (f2 <> ''::text)
+ "fd_pt1chk2" CHECK (f2 <> ''::text)
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: fd_pt1
+
+-- TRUNCATE doesn't work on foreign tables, either directly or recursively
+TRUNCATE ft2; -- ERROR
+ERROR: foreign-data wrapper "dummy" has no handler
+TRUNCATE fd_pt1; -- ERROR
+ERROR: foreign-data wrapper "dummy" has no handler
+DROP TABLE fd_pt1 CASCADE;
+NOTICE: drop cascades to foreign table ft2
+-- IMPORT FOREIGN SCHEMA
+IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
+ERROR: foreign-data wrapper "foo" has no handler
+IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR
+ERROR: foreign-data wrapper "foo" has no handler
+IMPORT FOREIGN SCHEMA s1 EXCEPT (t1) FROM SERVER s9 INTO public; -- ERROR
+ERROR: foreign-data wrapper "foo" has no handler
+IMPORT FOREIGN SCHEMA s1 EXCEPT (t1, t2) FROM SERVER s9 INTO public
+OPTIONS (option1 'value1', option2 'value2'); -- ERROR
+ERROR: foreign-data wrapper "foo" has no handler
+-- DROP FOREIGN TABLE
+DROP FOREIGN TABLE no_table; -- ERROR
+ERROR: foreign table "no_table" does not exist
+DROP FOREIGN TABLE IF EXISTS no_table;
+NOTICE: foreign table "no_table" does not exist, skipping
+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;
+ERROR: cannot drop desired object(s) because other objects depend on them
+DETAIL: user mapping for regress_test_role on server s5 depends on server s5
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP OWNED BY regress_test_role2 CASCADE;
+NOTICE: drop cascades to user mapping for regress_test_role on server s5
+-- 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
+ Partitioned table "public.fd_pt2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+Partition key: LIST (c1)
+Partitions: fd_pt2_1 FOR VALUES IN (1)
+
+\d+ fd_pt2_1
+ Foreign table "public.fd_pt2_1"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Partition of: fd_pt2 FOR VALUES IN (1)
+Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+
+-- 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
+ Foreign table "public.fd_pt2_1"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+--------------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+ c4 | character(1) | | | | | extended | |
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+
+ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
+ERROR: table "fd_pt2_1" contains column "c4" not found in parent "fd_pt2"
+DETAIL: The new partition may contain only the columns present in parent.
+DROP FOREIGN TABLE fd_pt2_1;
+\d+ fd_pt2
+ Partitioned table "public.fd_pt2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+Partition key: LIST (c1)
+Number of partitions: 0
+
+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
+ Foreign table "public.fd_pt2_1"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+
+-- no attach partition validation occurs for foreign tables
+ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
+\d+ fd_pt2
+ Partitioned table "public.fd_pt2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+Partition key: LIST (c1)
+Partitions: fd_pt2_1 FOR VALUES IN (1)
+
+\d+ fd_pt2_1
+ Foreign table "public.fd_pt2_1"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | | | | plain | |
+Partition of: fd_pt2 FOR VALUES IN (1)
+Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+
+-- cannot add column to a partition
+ALTER TABLE fd_pt2_1 ADD c4 char;
+ERROR: cannot add column to a partition
+-- 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
+ Partitioned table "public.fd_pt2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | |
+ c2 | text | | | | extended | |
+ c3 | date | | | | plain | |
+Partition key: LIST (c1)
+Partitions: fd_pt2_1 FOR VALUES IN (1)
+
+\d+ fd_pt2_1
+ Foreign table "public.fd_pt2_1"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | not null | | | plain | |
+Partition of: fd_pt2 FOR VALUES IN (1)
+Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
+Check constraints:
+ "p21chk" CHECK (c2 <> ''::text)
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+
+-- cannot drop inherited NOT NULL constraint from a partition
+ALTER TABLE fd_pt2_1 ALTER c1 DROP NOT NULL;
+ERROR: column "c1" is marked NOT NULL in parent table
+-- 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
+ Partitioned table "public.fd_pt2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | |
+ c2 | text | | not null | | extended | |
+ c3 | date | | | | plain | |
+Partition key: LIST (c1)
+Number of partitions: 0
+
+\d+ fd_pt2_1
+ Foreign table "public.fd_pt2_1"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | | | | extended | |
+ c3 | date | | not null | | | plain | |
+Check constraints:
+ "p21chk" CHECK (c2 <> ''::text)
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+
+ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
+ERROR: column "c2" in child table must be marked NOT NULL
+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
+ Partitioned table "public.fd_pt2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ c1 | integer | | not null | | plain | |
+ c2 | text | | not null | | extended | |
+ c3 | date | | | | plain | |
+Partition key: LIST (c1)
+Check constraints:
+ "fd_pt2chk1" CHECK (c1 > 0)
+Number of partitions: 0
+
+\d+ fd_pt2_1
+ Foreign table "public.fd_pt2_1"
+ Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+-------------+----------+--------------+-------------
+ c1 | integer | | not null | | | plain | |
+ c2 | text | | not null | | | extended | |
+ c3 | date | | not null | | | plain | |
+Check constraints:
+ "p21chk" CHECK (c2 <> ''::text)
+Server: s0
+FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+
+ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
+ERROR: child table is missing constraint "fd_pt2chk1"
+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);
+-- TRUNCATE doesn't work on foreign tables, either directly or recursively
+TRUNCATE fd_pt2_1; -- ERROR
+ERROR: foreign-data wrapper "dummy" has no handler
+TRUNCATE fd_pt2; -- ERROR
+ERROR: foreign-data wrapper "dummy" has no handler
+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
+ERROR: cannot create a permanent relation as partition of temporary relation "temp_parted"
+CREATE FOREIGN TABLE foreign_part (a int) SERVER s0;
+ALTER TABLE temp_parted ATTACH PARTITION foreign_part DEFAULT; -- ERROR
+ERROR: cannot attach a permanent relation as partition of temporary relation "temp_parted"
+DROP FOREIGN TABLE foreign_part;
+DROP TABLE temp_parted;
+-- Cleanup
+DROP SCHEMA foreign_schema CASCADE;
+DROP ROLE regress_test_role; -- ERROR
+ERROR: role "regress_test_role" cannot be dropped because some objects depend on it
+DETAIL: privileges for foreign-data wrapper foo
+privileges for server s4
+owner of user mapping for regress_test_role on server s6
+DROP SERVER t1 CASCADE;
+NOTICE: drop cascades to user mapping for public on server t1
+DROP USER MAPPING FOR regress_test_role SERVER s6;
+DROP FOREIGN DATA WRAPPER foo CASCADE;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to server s4
+drop cascades to user mapping for regress_foreign_data_user on server s4
+drop cascades to server s6
+drop cascades to server s9
+drop cascades to user mapping for regress_unprivileged_role on server s9
+DROP SERVER s8 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for regress_foreign_data_user on server s8
+drop cascades to user mapping for public on server s8
+DROP ROLE regress_test_indirect;
+DROP ROLE regress_test_role;
+DROP ROLE regress_unprivileged_role; -- ERROR
+ERROR: role "regress_unprivileged_role" cannot be dropped because some objects depend on it
+DETAIL: privileges for foreign-data wrapper postgresql
+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;
+NOTICE: drop cascades to server s0
+\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;
+ fdwname | fdwhandler | fdwvalidator | fdwoptions
+---------+------------+--------------+------------
+(0 rows)
+
+SELECT srvname, srvoptions FROM pg_foreign_server;
+ srvname | srvoptions
+---------+------------
+(0 rows)
+
+SELECT * FROM pg_user_mapping;
+ oid | umuser | umserver | umoptions
+-----+--------+----------+-----------
+(0 rows)
+
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
new file mode 100644
index 0000000..bf794dc
--- /dev/null
+++ b/src/test/regress/expected/foreign_key.out
@@ -0,0 +1,2568 @@
+--
+-- FOREIGN KEY
+--
+-- 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);
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(100) is not present in table "pktable".
+-- Check FKTABLE
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2
+--------+--------
+ 1 | 2
+ 2 | 3
+ 3 | 4
+ | 1
+(4 rows)
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLE WHERE ptest1=1;
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2
+--------+--------
+ 2 | 3
+ 3 | 4
+ | 1
+(3 rows)
+
+-- Update a row from PK TABLE
+UPDATE PKTABLE SET ptest1=1 WHERE ptest1=2;
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2
+--------+--------
+ 3 | 4
+ | 1
+ 1 | 3
+(3 rows)
+
+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';
+ERROR: constraint "constrname_wrong" for table "fktable" does not exist
+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);
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname"
+DETAIL: Key (ftest1, ftest2)=(100, 2) is not present in table "pktable".
+INSERT INTO FKTABLE VALUES (2, 2, 4);
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname"
+DETAIL: Key (ftest1, ftest2)=(2, 2) is not present in table "pktable".
+INSERT INTO FKTABLE VALUES (NULL, 2, 4);
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname"
+DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
+INSERT INTO FKTABLE VALUES (1, NULL, 4);
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname"
+DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
+-- Check FKTABLE
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2 | ftest3
+--------+--------+--------
+ 1 | 2 | 4
+ 1 | 3 | 5
+ 2 | 4 | 8
+ 3 | 6 | 12
+ | | 0
+(5 rows)
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLE WHERE ptest1=1 and ptest2=2;
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2 | ftest3
+--------+--------+--------
+ 1 | 3 | 5
+ 2 | 4 | 8
+ 3 | 6 | 12
+ | | 0
+ | | 4
+(5 rows)
+
+-- Delete another row from PK TABLE
+DELETE FROM PKTABLE WHERE ptest1=5 and ptest2=10;
+-- Check FKTABLE (should be no change)
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2 | ftest3
+--------+--------+--------
+ 1 | 3 | 5
+ 2 | 4 | 8
+ 3 | 6 | 12
+ | | 0
+ | | 4
+(5 rows)
+
+-- Update a row from PK TABLE
+UPDATE PKTABLE SET ptest1=1 WHERE ptest1=2;
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2 | ftest3
+--------+--------+--------
+ 1 | 3 | 5
+ 3 | 6 | 12
+ | | 0
+ | | 4
+ | | 8
+(5 rows)
+
+-- Check update with part of key null
+UPDATE FKTABLE SET ftest1 = NULL WHERE ftest1 = 1;
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname"
+DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
+-- Check update with old and new key values equal
+UPDATE FKTABLE SET ftest1 = 1 WHERE ftest1 = 1;
+-- 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;
+ ptest1 | ptest2 | ptest3
+--------+--------+---------
+ 1 | 3 | Test1-2
+ 3 | 6 | Test3
+ 4 | 8 | Test4
+ 1 | 4 | Test2
+(4 rows)
+
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2 | ftest3
+--------+--------+--------
+ 3 | 6 | 12
+ | | 0
+ | | 4
+ | | 8
+ 1 | 3 | 5
+(5 rows)
+
+DROP TABLE PKTABLE CASCADE;
+NOTICE: drop cascades to constraint constrname on table fktable
+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);
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname2"
+DETAIL: Key (ftest1, ftest2)=(100, 2) is not present in table "pktable".
+INSERT INTO FKTABLE VALUES (2, 2, 4);
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname2"
+DETAIL: Key (ftest1, ftest2)=(2, 2) is not present in table "pktable".
+INSERT INTO FKTABLE VALUES (NULL, 2, 4);
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname2"
+DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
+INSERT INTO FKTABLE VALUES (1, NULL, 4);
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname2"
+DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
+-- Check FKTABLE
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2 | ftest3
+--------+--------+--------
+ 1 | 2 | 4
+ 1 | 3 | 5
+ 2 | 4 | 8
+ 3 | 6 | 12
+ | | 0
+(5 rows)
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLE WHERE ptest1=1 and ptest2=2;
+-- Check FKTABLE to check for removal
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2 | ftest3
+--------+--------+--------
+ 1 | 3 | 5
+ 2 | 4 | 8
+ 3 | 6 | 12
+ | | 0
+ -1 | -2 | 4
+(5 rows)
+
+-- Delete another row from PK TABLE
+DELETE FROM PKTABLE WHERE ptest1=5 and ptest2=10;
+-- Check FKTABLE (should be no change)
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2 | ftest3
+--------+--------+--------
+ 1 | 3 | 5
+ 2 | 4 | 8
+ 3 | 6 | 12
+ | | 0
+ -1 | -2 | 4
+(5 rows)
+
+-- Update a row from PK TABLE
+UPDATE PKTABLE SET ptest1=1 WHERE ptest1=2;
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2 | ftest3
+--------+--------+--------
+ 1 | 3 | 5
+ 3 | 6 | 12
+ | | 0
+ -1 | -2 | 4
+ -1 | -2 | 8
+(5 rows)
+
+-- this should fail for lack of CASCADE
+DROP TABLE PKTABLE;
+ERROR: cannot drop table pktable because other objects depend on it
+DETAIL: constraint constrname2 on table fktable depends on table pktable
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE PKTABLE CASCADE;
+NOTICE: drop cascades to constraint constrname2 on table fktable
+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);
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(100) is not present in table "pktable".
+-- Check FKTABLE
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2
+--------+--------
+ 1 | 2
+ 2 | 3
+ 3 | 4
+ | 1
+(4 rows)
+
+-- Check PKTABLE
+SELECT * FROM PKTABLE;
+ ptest1 | ptest2
+--------+--------
+ 1 | Test1
+ 2 | Test2
+ 3 | Test3
+ 4 | Test4
+ 5 | Test5
+(5 rows)
+
+-- Delete a row from PK TABLE (should fail)
+DELETE FROM PKTABLE WHERE ptest1=1;
+ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "fktable"
+DETAIL: Key (ptest1)=(1) is still referenced from table "fktable".
+-- Delete a row from PK TABLE (should succeed)
+DELETE FROM PKTABLE WHERE ptest1=5;
+-- Check PKTABLE for deletes
+SELECT * FROM PKTABLE;
+ ptest1 | ptest2
+--------+--------
+ 1 | Test1
+ 2 | Test2
+ 3 | Test3
+ 4 | Test4
+(4 rows)
+
+-- Update a row from PK TABLE (should fail)
+UPDATE PKTABLE SET ptest1=0 WHERE ptest1=2;
+ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "fktable"
+DETAIL: Key (ptest1)=(2) is still referenced from table "fktable".
+-- Update a row from PK TABLE (should succeed)
+UPDATE PKTABLE SET ptest1=0 WHERE ptest1=4;
+-- Check PKTABLE for updates
+SELECT * FROM PKTABLE;
+ ptest1 | ptest2
+--------+--------
+ 1 | Test1
+ 2 | Test2
+ 3 | Test3
+ 0 | Test4
+(4 rows)
+
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
+--
+-- Check initial check upon ALTER TABLE
+--
+CREATE TABLE PKTABLE ( ptest1 int, ptest2 int, PRIMARY KEY(ptest1, ptest2) );
+CREATE TABLE FKTABLE ( ftest1 int, ftest2 int );
+INSERT INTO PKTABLE VALUES (1, 2);
+INSERT INTO FKTABLE VALUES (1, NULL);
+ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1, ftest2) REFERENCES PKTABLE MATCH FULL;
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_ftest2_fkey"
+DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
+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);
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname3"
+DETAIL: Key (ftest1, ftest2, ftest3)=(1, 2, 7) is not present in table "pktable".
+-- Show FKTABLE
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ 1 | 2 | 3 | 1
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+(5 rows)
+
+-- Try to update something that should fail
+UPDATE PKTABLE set ptest2=5 where ptest2=2;
+ERROR: update or delete on table "pktable" violates foreign key constraint "constrname3" on table "fktable"
+DETAIL: Key (ptest1, ptest2, ptest3)=(1, 2, 3) is still referenced from table "fktable".
+-- 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;
+ERROR: update or delete on table "pktable" violates foreign key constraint "constrname3" on table "fktable"
+DETAIL: Key (ptest1, ptest2, ptest3)=(1, 2, 3) is still referenced from table "fktable".
+-- Try to delete something that should work
+DELETE FROM PKTABLE where ptest1=2;
+-- Show PKTABLE and FKTABLE
+SELECT * from PKTABLE;
+ ptest1 | ptest2 | ptest3 | ptest4
+--------+--------+--------+--------
+ 1 | 2 | 3 | test1
+ 1 | 3 | 3 | test2
+ 1 | 3 | 4 | test3
+(3 rows)
+
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ 1 | 2 | 3 | 1
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+(5 rows)
+
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
+-- restrict with null values
+CREATE TABLE PKTABLE ( ptest1 int, ptest2 int, ptest3 int, ptest4 text, UNIQUE(ptest1, ptest2, ptest3) );
+CREATE TABLE FKTABLE ( ftest1 int, ftest2 int, ftest3 int, ftest4 int, CONSTRAINT constrname3
+ FOREIGN KEY(ftest1, ftest2, ftest3) REFERENCES PKTABLE (ptest1, ptest2, ptest3));
+INSERT INTO PKTABLE VALUES (1, 2, 3, 'test1');
+INSERT INTO PKTABLE VALUES (1, 3, NULL, 'test2');
+INSERT INTO PKTABLE VALUES (2, NULL, 4, 'test3');
+INSERT INTO FKTABLE VALUES (1, 2, 3, 1);
+DELETE FROM PKTABLE WHERE ptest1 = 2;
+SELECT * FROM PKTABLE;
+ ptest1 | ptest2 | ptest3 | ptest4
+--------+--------+--------+--------
+ 1 | 2 | 3 | test1
+ 1 | 3 | | test2
+(2 rows)
+
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ 1 | 2 | 3 | 1
+(1 row)
+
+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);
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname3"
+DETAIL: Key (ftest1, ftest2, ftest3)=(1, 2, 7) is not present in table "pktable".
+-- Show FKTABLE
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ 1 | 2 | 3 | 1
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+(5 rows)
+
+-- 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;
+ ptest1 | ptest2 | ptest3 | ptest4
+--------+--------+--------+--------
+ 2 | 4 | 5 | test4
+ 1 | 5 | 3 | test1
+ 1 | 3 | 3 | test2
+ 1 | 3 | 4 | test3
+(4 rows)
+
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+ 1 | 5 | 3 | 1
+(5 rows)
+
+-- 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;
+ ptest1 | ptest2 | ptest3 | ptest4
+--------+--------+--------+--------
+ 2 | 4 | 5 | test4
+ 1 | 3 | 3 | test2
+ 1 | 3 | 4 | test3
+(3 rows)
+
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+(4 rows)
+
+-- Try to delete something that should not have a cascade
+DELETE FROM PKTABLE where ptest1=2;
+-- Show PKTABLE and FKTABLE
+SELECT * from PKTABLE;
+ ptest1 | ptest2 | ptest3 | ptest4
+--------+--------+--------+--------
+ 1 | 3 | 3 | test2
+ 1 | 3 | 4 | test3
+(2 rows)
+
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+(4 rows)
+
+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);
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname3"
+DETAIL: Key (ftest1, ftest2, ftest3)=(1, 2, 7) is not present in table "pktable".
+-- Show FKTABLE
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ 1 | 2 | 3 | 1
+ 2 | 3 | 4 | 1
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+(6 rows)
+
+-- 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;
+ ptest1 | ptest2 | ptest3 | ptest4
+--------+--------+--------+--------
+ 2 | 3 | 4 | test3
+ 2 | 4 | 5 | test4
+ 1 | 5 | 3 | test1
+ 1 | 2 | 3 | test2
+(4 rows)
+
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ 2 | 3 | 4 | 1
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+ | | | 1
+(6 rows)
+
+-- 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;
+ ptest1 | ptest2 | ptest3 | ptest4
+--------+--------+--------+--------
+ 2 | 4 | 5 | test4
+ 1 | 5 | 3 | test1
+ 1 | 2 | 3 | test2
+(3 rows)
+
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+ | | | 1
+ 0 | | | 1
+(6 rows)
+
+-- Try to delete something that should not set default
+DELETE FROM PKTABLE where ptest2=5;
+-- Show PKTABLE and FKTABLE
+SELECT * from PKTABLE;
+ ptest1 | ptest2 | ptest3 | ptest4
+--------+--------+--------+--------
+ 2 | 4 | 5 | test4
+ 1 | 2 | 3 | test2
+(2 rows)
+
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+ | | | 1
+ 0 | | | 1
+(6 rows)
+
+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);
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname3"
+DETAIL: Key (ftest1, ftest2, ftest3)=(1, 2, 7) is not present in table "pktable".
+-- Show FKTABLE
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ 1 | 2 | 3 | 1
+ 2 | 3 | 4 | 1
+ 2 | 4 | 5 | 1
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+(7 rows)
+
+-- Try to update something that will fail
+UPDATE PKTABLE set ptest2=5 where ptest2=2;
+ERROR: insert or update on table "fktable" violates foreign key constraint "constrname3"
+DETAIL: Key (ftest1, ftest2, ftest3)=(0, -1, -2) is not present in table "pktable".
+-- 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;
+ ptest1 | ptest2 | ptest3 | ptest4
+--------+--------+--------+--------
+ 2 | 3 | 4 | test3
+ 2 | -1 | 5 | test5
+ 0 | -1 | -2 | test1
+ 2 | 10 | 5 | test4
+ 1 | 2 | 3 | test2
+(5 rows)
+
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ 2 | 3 | 4 | 1
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+ 0 | -1 | -2 | 1
+ 0 | -1 | -2 | 1
+(7 rows)
+
+-- 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;
+ ptest1 | ptest2 | ptest3 | ptest4
+--------+--------+--------+--------
+ 2 | -1 | 5 | test5
+ 0 | -1 | -2 | test1
+ 2 | 10 | 5 | test4
+ 1 | 2 | 3 | test2
+(4 rows)
+
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+ 0 | -1 | -2 | 1
+ 0 | -1 | -2 | 1
+ | | | 1
+(7 rows)
+
+-- 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;
+ ptest1 | ptest2 | ptest3 | ptest4
+--------+--------+--------+--------
+ 0 | -1 | -2 | test1
+ 2 | 10 | 5 | test4
+ 1 | 2 | 3 | test2
+(3 rows)
+
+SELECT * from FKTABLE;
+ ftest1 | ftest2 | ftest3 | ftest4
+--------+--------+--------+--------
+ | 2 | 3 | 2
+ 2 | | 3 | 3
+ | 2 | 7 | 4
+ | 3 | 4 | 5
+ 0 | -1 | -2 | 1
+ 0 | -1 | -2 | 1
+ | | | 1
+(7 rows)
+
+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);
+ERROR: column "ftest2" referenced in foreign key constraint does not exist
+CREATE TABLE FKTABLE_FAIL2 ( ftest1 int, CONSTRAINT fkfail1 FOREIGN KEY (ftest1) REFERENCES PKTABLE(ptest2));
+ERROR: column "ptest2" referenced in foreign key constraint does not exist
+DROP TABLE FKTABLE_FAIL1;
+ERROR: table "fktable_fail1" does not exist
+DROP TABLE FKTABLE_FAIL2;
+ERROR: table "fktable_fail2" does not exist
+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));
+ERROR: there is no unique constraint matching given keys for referenced table "pktable"
+DROP TABLE FKTABLE_FAIL1;
+ERROR: table "fktable_fail1" does not exist
+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);
+ERROR: foreign key constraint "fktable_ftest1_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer.
+-- This should also fail for the same reason, but here we
+-- give the column name
+CREATE TABLE FKTABLE (ftest1 inet REFERENCES pktable(ptest1));
+ERROR: foreign key constraint "fktable_ftest1_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer.
+-- 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
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(43) is not present in table "pktable".
+UPDATE FKTABLE SET ftest1 = ftest1; -- should succeed
+UPDATE FKTABLE SET ftest1 = ftest1 + 1; -- should fail
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(43) is not present in table "pktable".
+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);
+ERROR: foreign key constraint "fktable_ftest1_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: numeric and integer.
+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
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(43) is not present in table "pktable".
+UPDATE FKTABLE SET ftest1 = ftest1; -- should succeed
+UPDATE FKTABLE SET ftest1 = ftest1 + 1; -- should fail
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(43) is not present in table "pktable".
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
+-- Two columns, two tables
+CREATE TABLE PKTABLE (ptest1 int, ptest2 inet, 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);
+ERROR: foreign key constraint "fktable_ftest1_ftest2_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: cidr and integer.
+-- Again, so should this...
+CREATE TABLE FKTABLE (ftest1 cidr, ftest2 timestamp, FOREIGN KEY(ftest1, ftest2) REFERENCES pktable(ptest1, ptest2));
+ERROR: foreign key constraint "fktable_ftest1_ftest2_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: cidr and integer.
+-- This fails because we mixed up the column ordering
+CREATE TABLE FKTABLE (ftest1 int, ftest2 inet, FOREIGN KEY(ftest2, ftest1) REFERENCES pktable);
+ERROR: foreign key constraint "fktable_ftest2_ftest1_fkey" cannot be implemented
+DETAIL: Key columns "ftest2" and "ptest1" are of incompatible types: inet and integer.
+-- As does this...
+CREATE TABLE FKTABLE (ftest1 int, ftest2 inet, FOREIGN KEY(ftest2, ftest1) REFERENCES pktable(ptest1, ptest2));
+ERROR: foreign key constraint "fktable_ftest2_ftest1_fkey" cannot be implemented
+DETAIL: Key columns "ftest2" and "ptest1" are of incompatible types: inet and integer.
+-- And again..
+CREATE TABLE FKTABLE (ftest1 int, ftest2 inet, FOREIGN KEY(ftest1, ftest2) REFERENCES pktable(ptest2, ptest1));
+ERROR: foreign key constraint "fktable_ftest1_ftest2_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest2" are of incompatible types: integer and inet.
+-- This works...
+CREATE TABLE FKTABLE (ftest1 int, ftest2 inet, FOREIGN KEY(ftest2, ftest1) REFERENCES pktable(ptest2, ptest1));
+DROP TABLE FKTABLE;
+-- As does this
+CREATE TABLE FKTABLE (ftest1 int, ftest2 inet, 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 inet, ptest3 int, ptest4 inet, PRIMARY KEY(ptest1, ptest2), FOREIGN KEY(ptest3,
+ptest4) REFERENCES pktable(ptest1, ptest2));
+DROP TABLE PKTABLE;
+-- And this,
+CREATE TABLE PKTABLE (ptest1 int, ptest2 inet, ptest3 int, ptest4 inet, 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 inet, ptest3 int, ptest4 inet, PRIMARY KEY(ptest1, ptest2), FOREIGN KEY(ptest3,
+ptest4) REFERENCES pktable(ptest2, ptest1));
+ERROR: foreign key constraint "pktable_ptest3_ptest4_fkey" cannot be implemented
+DETAIL: Key columns "ptest3" and "ptest2" are of incompatible types: integer and inet.
+-- Nor should this... (same reason, we have 4,3 referencing 1,2 which mismatches types
+CREATE TABLE PKTABLE (ptest1 int, ptest2 inet, ptest3 int, ptest4 inet, PRIMARY KEY(ptest1, ptest2), FOREIGN KEY(ptest4,
+ptest3) REFERENCES pktable(ptest1, ptest2));
+ERROR: foreign key constraint "pktable_ptest4_ptest3_fkey" cannot be implemented
+DETAIL: Key columns "ptest4" and "ptest1" are of incompatible types: inet and integer.
+-- Not this one either... Same as the last one except we didn't defined the columns being referenced.
+CREATE TABLE PKTABLE (ptest1 int, ptest2 inet, ptest3 int, ptest4 inet, PRIMARY KEY(ptest1, ptest2), FOREIGN KEY(ptest4,
+ptest3) REFERENCES pktable);
+ERROR: foreign key constraint "pktable_ptest4_ptest3_fkey" cannot be implemented
+DETAIL: Key columns "ptest4" and "ptest1" are of incompatible types: inet and integer.
+--
+-- 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);
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(3) is not present in table "pktable".
+-- 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;
+ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "fktable"
+DETAIL: Key (base1)=(3) is still referenced from table "fktable".
+-- okay, let's try updating all of the base1 values to *4
+-- which should fail.
+update pktable set base1=base1*4;
+ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "fktable"
+DETAIL: Key (base1)=(3) is still referenced from table "fktable".
+-- 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);
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_ftest2_fkey"
+DETAIL: Key (ftest1, ftest2)=(3, 1) is not present in table "pktable".
+-- 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;
+ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_ftest1_ftest2_fkey" on table "fktable"
+DETAIL: Key (base1, ptest1)=(3, 1) is still referenced from table "fktable".
+-- okay, let's try updating all of the base1 values to *4
+-- which should fail.
+update pktable set base1=base1*4;
+ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_ftest1_ftest2_fkey" on table "fktable"
+DETAIL: Key (base1, ptest1)=(3, 1) is still referenced from table "fktable".
+-- 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);
+ERROR: insert or update on table "pktable" violates foreign key constraint "pktable_base2_ptest2_fkey"
+DETAIL: Key (base2, ptest2)=(3, 2) is not present in table "pktable".
+-- fails (2,2) is being referenced
+delete from pktable where base1=2;
+ERROR: update or delete on table "pktable" violates foreign key constraint "pktable_base2_ptest2_fkey" on table "pktable"
+DETAIL: Key (base1, ptest1)=(2, 2) is still referenced from table "pktable".
+-- fails (1,1) is being referenced (twice)
+update pktable set base1=3 where base1=1;
+ERROR: update or delete on table "pktable" violates foreign key constraint "pktable_base2_ptest2_fkey" on table "pktable"
+DETAIL: Key (base1, ptest1)=(1, 1) is still referenced from table "pktable".
+-- 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 inet, primary key(base1, ptest1)) inherits (pktable_base);
+-- 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);
+ERROR: foreign key constraint "fktable_ftest1_ftest2_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "base1" are of incompatible types: cidr and integer.
+create table fktable(ftest1 cidr, ftest2 int[], foreign key (ftest1, ftest2) references pktable(base1, ptest1));
+ERROR: foreign key constraint "fktable_ftest1_ftest2_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "base1" are of incompatible types: cidr and integer.
+-- let's mix up which columns reference which
+create table fktable(ftest1 int, ftest2 inet, foreign key(ftest2, ftest1) references pktable);
+ERROR: foreign key constraint "fktable_ftest2_ftest1_fkey" cannot be implemented
+DETAIL: Key columns "ftest2" and "base1" are of incompatible types: inet and integer.
+create table fktable(ftest1 int, ftest2 inet, foreign key(ftest2, ftest1) references pktable(base1, ptest1));
+ERROR: foreign key constraint "fktable_ftest2_ftest1_fkey" cannot be implemented
+DETAIL: Key columns "ftest2" and "base1" are of incompatible types: inet and integer.
+create table fktable(ftest1 int, ftest2 inet, foreign key(ftest1, ftest2) references pktable(ptest1, base1));
+ERROR: foreign key constraint "fktable_ftest1_ftest2_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: integer and inet.
+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 inet, ptest2 inet[], primary key(base1, ptest1), foreign key(base2, ptest2) references
+ pktable(base1, ptest1)) inherits (pktable_base);
+ERROR: foreign key constraint "pktable_base2_ptest2_fkey" cannot be implemented
+DETAIL: Key columns "ptest2" and "ptest1" are of incompatible types: inet[] and inet.
+create table pktable(ptest1 inet, ptest2 inet, primary key(base1, ptest1), foreign key(base2, ptest2) references
+ pktable(ptest1, base1)) inherits (pktable_base);
+ERROR: foreign key constraint "pktable_base2_ptest2_fkey" cannot be implemented
+DETAIL: Key columns "base2" and "ptest1" are of incompatible types: integer and inet.
+create table pktable(ptest1 inet, ptest2 inet, primary key(base1, ptest1), foreign key(ptest2, base2) references
+ pktable(base1, ptest1)) inherits (pktable_base);
+ERROR: foreign key constraint "pktable_ptest2_base2_fkey" cannot be implemented
+DETAIL: Key columns "ptest2" and "base1" are of incompatible types: inet and integer.
+create table pktable(ptest1 inet, ptest2 inet, primary key(base1, ptest1), foreign key(ptest2, base2) references
+ pktable(base1, ptest1)) inherits (pktable_base);
+ERROR: foreign key constraint "pktable_ptest2_base2_fkey" cannot be implemented
+DETAIL: Key columns "ptest2" and "base1" are of incompatible types: inet and integer.
+drop table pktable;
+ERROR: table "pktable" does not exist
+drop table pktable_base;
+--
+-- Deferrable constraints
+--
+-- 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);
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(10) is not present in table "pktable".
+-- 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);
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(1000) is not present in table "pktable".
+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;
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(2000) is not present in table "pktable".
+INSERT INTO pktable VALUES (2000, 3); -- too late
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+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;
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(200) is not present in table "pktable".
+DROP TABLE pktable, fktable;
+-- 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);
+ERROR: foreign key constraint "fk_2_3" cannot be implemented
+DETAIL: Key columns "x2" and "id3" are of incompatible types: character varying and real.
+-- nor to int4
+ALTER TABLE fktable ADD CONSTRAINT fk_2_1
+FOREIGN KEY (x2) REFERENCES pktable(id1);
+ERROR: foreign key constraint "fk_2_1" cannot be implemented
+DETAIL: Key columns "x2" and "id1" are of incompatible types: character varying and integer.
+-- real does not promote to int4
+ALTER TABLE fktable ADD CONSTRAINT fk_3_1
+FOREIGN KEY (x3) REFERENCES pktable(id1);
+ERROR: foreign key constraint "fk_3_1" cannot be implemented
+DETAIL: Key columns "x3" and "id1" are of incompatible types: real and integer.
+-- int4 does not promote to text
+ALTER TABLE fktable ADD CONSTRAINT fk_1_2
+FOREIGN KEY (x1) REFERENCES pktable(id2);
+ERROR: foreign key constraint "fk_1_2" cannot be implemented
+DETAIL: Key columns "x1" and "id2" are of incompatible types: integer and character varying.
+-- 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);
+ERROR: foreign key constraint "fk_123_231" cannot be implemented
+DETAIL: Key columns "x1" and "id2" are of incompatible types: integer and character varying.
+ALTER TABLE fktable ADD CONSTRAINT fk_241_132
+FOREIGN KEY (x2,x4,x1) REFERENCES pktable(id1,id3,id2);
+ERROR: foreign key constraint "fk_241_132" cannot be implemented
+DETAIL: Key columns "x2" and "id1" are of incompatible types: character varying and integer.
+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;
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(20) is not present in table "pktable".
+-- 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;
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(20) is not present in table "pktable".
+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;
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(20) is not present in table "pktable".
+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;
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(20) is not present in table "pktable".
+--
+-- 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;
+ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_fk_fkey" on table "fktable"
+DETAIL: Key (id)=(5) is still referenced from table "fktable".
+COMMIT;
+BEGIN;
+-- doesn't match PK, should throw error now
+INSERT INTO fktable VALUES (0, 20);
+ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(20) is not present in table "pktable".
+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;
+ERROR: constraint declared INITIALLY DEFERRED must be DEFERRABLE
+LINE 1: ...e ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY ...
+ ^
+-- 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;
+ id | owner | worker | checked_by
+----+-------+--------+------------
+ 1 | 1 | |
+ 2 | 2 | 2 |
+ 3 | 3 | 3 | 3
+(3 rows)
+
+UPDATE users SET id = 4 WHERE id = 3;
+SELECT * FROM tasks;
+ id | owner | worker | checked_by
+----+-------+--------+------------
+ 1 | 1 | |
+ 2 | 2 | 2 |
+ 3 | 4 | 4 | 4
+(3 rows)
+
+DELETE FROM users WHERE id = 4;
+SELECT * FROM tasks;
+ id | owner | worker | checked_by
+----+-------+--------+------------
+ 1 | 1 | |
+ 2 | 2 | 2 |
+ 3 | | |
+(3 rows)
+
+-- 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;
+ id | owner | worker | checked_by
+----+-------+--------+------------
+ 1 | 1 | |
+ 3 | | |
+ 2 | 2 | 2 |
+(3 rows)
+
+DELETE FROM users WHERE id = 2;
+SELECT * FROM tasks;
+ id | owner | worker | checked_by
+----+-------+--------+------------
+ 1 | 1 | |
+ 3 | | |
+ 2 | | |
+(3 rows)
+
+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;
+ a | b
+-----+-----
+ 1 | 1
+ 123 | 123
+(2 rows)
+
+ update selfref set a = 456 where a = 123;
+ select a, b from selfref;
+ a | b
+-----+-----
+ 1 | 1
+ 456 | 456
+(2 rows)
+
+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;
+ f1
+----
+ 2
+(1 row)
+
+delete from defp where f1 = 2;
+select * from defc;
+ f1
+----
+ 0
+(1 row)
+
+delete from defp where f1 = 0; -- fail
+ERROR: update or delete on table "defp" violates foreign key constraint "defc_f1_fkey" on table "defc"
+DETAIL: Key (f1)=(0) is still referenced from table "defc".
+alter table defc alter column f1 set default 1;
+delete from defp where f1 = 0;
+select * from defc;
+ f1
+----
+ 1
+(1 row)
+
+delete from defp where f1 = 1; -- fail
+ERROR: update or delete on table "defp" violates foreign key constraint "defc_f1_fkey" on table "defc"
+DETAIL: Key (f1)=(1) is still referenced from table "defc".
+--
+-- 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 on delete 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
+ERROR: update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
+DETAIL: Key (f1)=(13) is still referenced from table "cc".
+delete from pp where f1 = 13; -- fail
+ERROR: update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
+DETAIL: Key (f1)=(13) is still referenced from table "cc".
+drop table pp, cc;
+create temp table pp (f1 int primary key);
+create temp table cc (f1 int references pp on update restrict on delete 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
+ERROR: update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
+DETAIL: Key (f1)=(13) is still referenced from table "cc".
+delete from pp where f1 = 13; -- fail
+ERROR: update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
+DETAIL: Key (f1)=(13) is still referenced from table "cc".
+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;
+ QUERY PLAN
+--------------------------------------------
+ Delete on t2
+ -> Nested Loop
+ -> Index Scan using t1_pkey on t1
+ Index Cond: (a = 1)
+ -> Seq Scan on t2
+ Filter: (b = 1)
+
+ Delete on t1
+ -> Index Scan using t1_pkey on t1
+ Index Cond: (a = 1)
+(10 rows)
+
+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;
+ERROR: update or delete on table "pktable2" violates foreign key constraint "fktable2_d_e_fkey" on table "fktable2"
+DETAIL: Key (d, e)=(4, 5) is still referenced from table "fktable2".
+update pktable2 set d = 5;
+ERROR: update or delete on table "pktable2" violates foreign key constraint "fktable2_d_e_fkey" on table "fktable2"
+DETAIL: Key (d, e)=(4, 5) is still referenced from table "fktable2".
+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;
+ conname
+-----------------------------------------------------------------
+ fktable2_a_very_very_long_column_name_to_exceed_63_charac_fkey1
+ fktable2_a_very_very_long_column_name_to_exceed_63_charact_fkey
+ fktable2_very_very_long_column_name_to_exceed_63_character_fkey
+(3 rows)
+
+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
+ERROR: insert or update on table "fktable2" violates foreign key constraint "fktable2_f1_fkey"
+DETAIL: Key (f1)=(2) is not present in table "pktable2".
+--
+-- 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;
+ERROR: cannot ALTER TABLE "fktable2" because it has pending trigger events
+commit;
+begin;
+delete from pktable2 where f1 = 1;
+alter table fktable2 drop constraint fktable2_f1_fkey;
+ERROR: cannot ALTER TABLE "pktable2" because it has pending trigger events
+commit;
+drop table pktable2, fktable2;
+--
+-- Test keys that "look" different but compare as equal
+--
+create table pktable2 (a float8, b float8, primary key (a, b));
+create table fktable2 (x float8, y float8, foreign key (x, y) references pktable2 (a, b) on update cascade);
+insert into pktable2 values ('-0', '-0');
+insert into fktable2 values ('-0', '-0');
+select * from pktable2;
+ a | b
+----+----
+ -0 | -0
+(1 row)
+
+select * from fktable2;
+ x | y
+----+----
+ -0 | -0
+(1 row)
+
+update pktable2 set a = '0' where a = '-0';
+select * from pktable2;
+ a | b
+---+----
+ 0 | -0
+(1 row)
+
+-- should have updated fktable2.x
+select * from fktable2;
+ x | y
+---+----
+ 0 | -0
+(1 row)
+
+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;
+ERROR: cannot use ONLY for foreign key on partitioned table "fk_partitioned_fk" referencing relation "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;
+ERROR: cannot add NOT VALID foreign key on partitioned table "fk_partitioned_fk" referencing relation "fk_notpartitioned_pk"
+DETAIL: This feature is not yet supported on partitioned tables.
+-- 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);
+ERROR: insert or update on table "fk_partitioned_fk_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(500, 501) is not present in table "fk_notpartitioned_pk".
+INSERT INTO fk_partitioned_fk_1 (a,b) VALUES (500, 501);
+ERROR: insert or update on table "fk_partitioned_fk_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(500, 501) is not present in table "fk_notpartitioned_pk".
+INSERT INTO fk_partitioned_fk (a,b) VALUES (1500, 1501);
+ERROR: insert or update on table "fk_partitioned_fk_2" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(1500, 1501) is not present in table "fk_notpartitioned_pk".
+INSERT INTO fk_partitioned_fk_2 (a,b) VALUES (1500, 1501);
+ERROR: insert or update on table "fk_partitioned_fk_2" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(1500, 1501) is not present in table "fk_notpartitioned_pk".
+INSERT INTO fk_partitioned_fk (a,b) VALUES (2500, 2502);
+ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(2500, 2502) is not present in table "fk_notpartitioned_pk".
+INSERT INTO fk_partitioned_fk_3 (a,b) VALUES (2500, 2502);
+ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(2500, 2502) is not present in table "fk_notpartitioned_pk".
+INSERT INTO fk_partitioned_fk (a,b) VALUES (2501, 2503);
+ERROR: insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(2501, 2503) is not present in table "fk_notpartitioned_pk".
+INSERT INTO fk_partitioned_fk_3 (a,b) VALUES (2501, 2503);
+ERROR: insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(2501, 2503) is not present in table "fk_notpartitioned_pk".
+-- 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;
+ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(2502, 2503) is not present in table "fk_notpartitioned_pk".
+-- 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;
+ERROR: update or delete on table "fk_notpartitioned_pk" violates foreign key constraint "fk_partitioned_fk_a_b_fkey" on table "fk_partitioned_fk"
+DETAIL: Key (a, b)=(500, 501) is still referenced from table "fk_partitioned_fk".
+UPDATE fk_notpartitioned_pk SET b = 1502 WHERE a = 1500;
+ERROR: update or delete on table "fk_notpartitioned_pk" violates foreign key constraint "fk_partitioned_fk_a_b_fkey" on table "fk_partitioned_fk"
+DETAIL: Key (a, b)=(1500, 1501) is still referenced from table "fk_partitioned_fk".
+UPDATE fk_notpartitioned_pk SET b = 2504 WHERE a = 2500;
+ERROR: update or delete on table "fk_notpartitioned_pk" violates foreign key constraint "fk_partitioned_fk_a_b_fkey" on table "fk_partitioned_fk"
+DETAIL: Key (a, b)=(2500, 2502) is still referenced from table "fk_partitioned_fk".
+-- check psql behavior
+\d fk_notpartitioned_pk
+ Table "public.fk_notpartitioned_pk"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+ b | integer | | not null |
+Indexes:
+ "fk_notpartitioned_pk_pkey" PRIMARY KEY, btree (a, b)
+Referenced by:
+ TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b)
+
+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;
+ERROR: update or delete on table "fk_notpartitioned_pk" violates foreign key constraint "fk_partitioned_fk_a_fkey" on table "fk_partitioned_fk"
+DETAIL: Key (a)=(1) is still referenced from table "fk_partitioned_fk".
+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);
+ERROR: insert or update on table "fk_partitioned_fk_3" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(2502, 2503) is not present in table "fk_notpartitioned_pk".
+INSERT INTO fk_partitioned_fk_3 (a, b) VALUES (2502, 2503);
+ERROR: insert or update on table "fk_partitioned_fk_3" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(2502, 2503) is not present in table "fk_notpartitioned_pk".
+-- 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);
+-- MATCH FULL
+INSERT INTO fk_notpartitioned_pk VALUES (1, 2);
+CREATE TABLE fk_partitioned_fk_full (x int, y int) PARTITION BY RANGE (x);
+CREATE TABLE fk_partitioned_fk_full_1 PARTITION OF fk_partitioned_fk_full DEFAULT;
+INSERT INTO fk_partitioned_fk_full VALUES (1, NULL);
+ALTER TABLE fk_partitioned_fk_full ADD FOREIGN KEY (x, y) REFERENCES fk_notpartitioned_pk MATCH FULL; -- fails
+ERROR: insert or update on table "fk_partitioned_fk_full_1" violates foreign key constraint "fk_partitioned_fk_full_x_y_fkey"
+DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
+TRUNCATE fk_partitioned_fk_full;
+ALTER TABLE fk_partitioned_fk_full ADD FOREIGN KEY (x, y) REFERENCES fk_notpartitioned_pk MATCH FULL;
+INSERT INTO fk_partitioned_fk_full VALUES (1, NULL); -- fails
+ERROR: insert or update on table "fk_partitioned_fk_full_1" violates foreign key constraint "fk_partitioned_fk_full_x_y_fkey"
+DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
+DROP TABLE fk_partitioned_fk_full;
+-- ON UPDATE SET NULL
+SELECT tableoid::regclass, a, b FROM fk_partitioned_fk WHERE b IS NULL ORDER BY a;
+ tableoid | a | b
+---------------------+------+---
+ fk_partitioned_fk_3 | 2502 |
+ fk_partitioned_fk_1 | |
+(2 rows)
+
+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;
+ tableoid | a | b
+---------------------+------+---
+ fk_partitioned_fk_3 | 2502 |
+ fk_partitioned_fk_1 | |
+ fk_partitioned_fk_1 | |
+(3 rows)
+
+-- ON DELETE SET NULL
+INSERT INTO fk_partitioned_fk VALUES (2503, 2503);
+SELECT count(*) FROM fk_partitioned_fk WHERE a IS NULL;
+ count
+-------
+ 2
+(1 row)
+
+DELETE FROM fk_notpartitioned_pk;
+SELECT count(*) FROM fk_partitioned_fk WHERE a IS NULL;
+ count
+-------
+ 3
+(1 row)
+
+-- 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;
+ERROR: insert or update on table "fk_partitioned_fk_3" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(2501, 142857) is not present in table "fk_notpartitioned_pk".
+-- 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;
+ a | b
+------+--------
+ 2501 | 142857
+(1 row)
+
+-- 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;
+ a | b
+------+--------
+ 2502 | 142857
+(1 row)
+
+-- Now you see it ...
+SELECT * FROM fk_partitioned_fk WHERE b = 142857;
+ a | b
+------+--------
+ 2502 | 142857
+(1 row)
+
+DELETE FROM fk_notpartitioned_pk WHERE b = 142857;
+-- now you don't.
+SELECT * FROM fk_partitioned_fk WHERE a = 142857;
+ a | b
+---+---
+(0 rows)
+
+-- 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;
+BEGIN;
+DROP TABLE fk_partitioned_fk;
+-- constraint should still be there
+\d fk_partitioned_fk_2;
+ Table "public.fk_partitioned_fk_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | | 2501
+ b | integer | | | 142857
+Foreign-key constraints:
+ "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+
+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
+ Table "public.fk_partitioned_fk_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ b | integer | | |
+ a | integer | | |
+Partition of: fk_partitioned_fk FOR VALUES IN (1500, 1502)
+Foreign-key constraints:
+ TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+
+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
+ Partitioned table "public.fk_partitioned_fk_4"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition of: fk_partitioned_fk FOR VALUES IN (3500, 3502)
+Partition key: RANGE (b, a)
+Foreign-key constraints:
+ TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+Number of partitions: 2 (Use \d+ to list them.)
+
+\d fk_partitioned_fk_4_1
+ Table "public.fk_partitioned_fk_4_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition of: fk_partitioned_fk_4 FOR VALUES FROM (1, 1) TO (100, 100)
+Foreign-key constraints:
+ TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+
+-- this one has an FK with mismatched properties
+\d fk_partitioned_fk_4_2
+ Table "public.fk_partitioned_fk_4_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition of: fk_partitioned_fk_4 FOR VALUES FROM (100, 100) TO (1000, 1000)
+Foreign-key constraints:
+ "fk_partitioned_fk_4_2_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE SET NULL
+ TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+
+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
+ Partitioned table "public.fk_partitioned_fk_5"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition of: fk_partitioned_fk FOR VALUES IN (4500)
+Partition key: RANGE (a)
+Foreign-key constraints:
+ "fk_partitioned_fk_5_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE
+ "fk_partitioned_fk_5_a_b_fkey1" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
+ TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- 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
+ Table "public.fk_partitioned_fk_5_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition of: fk_partitioned_fk_5 FOR VALUES FROM (0) TO (10)
+Foreign-key constraints:
+ "fk_partitioned_fk_5_1_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b)
+ TABLE "fk_partitioned_fk_5" CONSTRAINT "fk_partitioned_fk_5_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE
+ TABLE "fk_partitioned_fk_5" CONSTRAINT "fk_partitioned_fk_5_a_b_fkey1" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
+ TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+
+-- 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);
+ERROR: insert or update on table "fk_partitioned_fk_2_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(1600, 601) is not present in table "fk_notpartitioned_pk".
+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 the case when the referenced table is owned by a different user
+create role regress_other_partitioned_fk_owner;
+grant references on fk_notpartitioned_pk to regress_other_partitioned_fk_owner;
+set role regress_other_partitioned_fk_owner;
+create table other_partitioned_fk(a int, b int) partition by list (a);
+create table other_partitioned_fk_1 partition of other_partitioned_fk
+ for values in (2048);
+insert into other_partitioned_fk
+ select 2048, x from generate_series(1,10) x;
+-- this should fail
+alter table other_partitioned_fk add foreign key (a, b)
+ references fk_notpartitioned_pk(a, b);
+ERROR: insert or update on table "other_partitioned_fk_1" violates foreign key constraint "other_partitioned_fk_a_b_fkey"
+DETAIL: Key (a, b)=(2048, 1) is not present in table "fk_notpartitioned_pk".
+-- add the missing keys and retry
+reset role;
+insert into fk_notpartitioned_pk (a, b)
+ select 2048, x from generate_series(1,10) x;
+set role regress_other_partitioned_fk_owner;
+alter table other_partitioned_fk add foreign key (a, b)
+ references fk_notpartitioned_pk(a, b);
+-- clean up
+drop table other_partitioned_fk;
+reset role;
+revoke all on fk_notpartitioned_pk from regress_other_partitioned_fk_owner;
+drop role regress_other_partitioned_fk_owner;
+-- 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);
+alter table fkpart0.fk_part add foreign key (a) references fkpart0.pkey;
+\d fkpart0.fk_part_1 \\ -- should have only one FK
+ Table "fkpart0.fk_part_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition of: fkpart0.fk_part FOR VALUES IN (1)
+Foreign-key constraints:
+ TABLE "fkpart0.fk_part" CONSTRAINT "fk_part_a_fkey" FOREIGN KEY (a) REFERENCES fkpart0.pkey(a)
+
+alter table fkpart0.fk_part_1 drop constraint fk_part_1_a_fkey;
+ERROR: cannot drop inherited constraint "fk_part_1_a_fkey" of relation "fk_part_1"
+\d fkpart0.fk_part_23 \\ -- should have only one FK
+ Partitioned table "fkpart0.fk_part_23"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition of: fkpart0.fk_part FOR VALUES IN (2, 3)
+Partition key: LIST (a)
+Foreign-key constraints:
+ TABLE "fkpart0.fk_part" CONSTRAINT "fk_part_a_fkey" FOREIGN KEY (a) REFERENCES fkpart0.pkey(a)
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d fkpart0.fk_part_23_2 \\ -- should have only one FK
+ Table "fkpart0.fk_part_23_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition of: fkpart0.fk_part_23 FOR VALUES IN (2)
+Foreign-key constraints:
+ TABLE "fkpart0.fk_part" CONSTRAINT "fk_part_a_fkey" FOREIGN KEY (a) REFERENCES fkpart0.pkey(a)
+
+alter table fkpart0.fk_part_23 drop constraint fk_part_23_a_fkey;
+ERROR: cannot drop inherited constraint "fk_part_23_a_fkey" of relation "fk_part_23"
+alter table fkpart0.fk_part_23_2 drop constraint fk_part_23_a_fkey;
+ERROR: cannot drop inherited constraint "fk_part_23_a_fkey" of relation "fk_part_23_2"
+create table fkpart0.fk_part_4 partition of fkpart0.fk_part for values in (4);
+\d fkpart0.fk_part_4
+ Table "fkpart0.fk_part_4"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition of: fkpart0.fk_part FOR VALUES IN (4)
+Foreign-key constraints:
+ TABLE "fkpart0.fk_part" CONSTRAINT "fk_part_a_fkey" FOREIGN KEY (a) REFERENCES fkpart0.pkey(a)
+
+alter table fkpart0.fk_part_4 drop constraint fk_part_a_fkey;
+ERROR: cannot drop inherited constraint "fk_part_a_fkey" of relation "fk_part_4"
+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
+ Partitioned table "fkpart0.fk_part_56"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition of: fkpart0.fk_part FOR VALUES IN (5, 6)
+Partition key: LIST (a)
+Foreign-key constraints:
+ TABLE "fkpart0.fk_part" CONSTRAINT "fk_part_a_fkey" FOREIGN KEY (a) REFERENCES fkpart0.pkey(a)
+Number of partitions: 1 (Use \d+ to list them.)
+
+alter table fkpart0.fk_part_56 drop constraint fk_part_a_fkey;
+ERROR: cannot drop inherited constraint "fk_part_a_fkey" of relation "fk_part_56"
+alter table fkpart0.fk_part_56_5 drop constraint fk_part_a_fkey;
+ERROR: cannot drop inherited constraint "fk_part_a_fkey" of relation "fk_part_56_5"
+-- 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
+ERROR: insert or update on table "fk_part_1_1" violates foreign key constraint "fk_part_a_fkey"
+DETAIL: Key (a)=(1) is not present in table "pkey".
+insert into fkpart1.pkey values (1);
+insert into fkpart1.fk_part values (1);
+delete from fkpart1.pkey where a = 1; -- should fail
+ERROR: update or delete on table "pkey" violates foreign key constraint "fk_part_a_fkey" on table "fk_part"
+DETAIL: Key (a)=(1) is still referenced from table "fk_part".
+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
+ERROR: insert or update on table "fk_part_1_2" violates foreign key constraint "fk_part_a_fkey"
+DETAIL: Key (a)=(2) is not present in table "pkey".
+delete from fkpart1.pkey where a = 1;
+ERROR: update or delete on table "pkey" violates foreign key constraint "fk_part_a_fkey" on table "fk_part_1"
+DETAIL: Key (a)=(1) is still referenced from table "fk_part_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
+ERROR: cannot drop inherited constraint "fkey" of relation "fk_part_1"
+alter table fkpart2.fk_part_1_1 drop constraint my_fkey; -- should fail
+ERROR: cannot drop inherited constraint "my_fkey" of relation "fk_part_1_1"
+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
+ERROR: constraint "my_fkey" of relation "fk_part_1_1" does not exist
+-- verify constraint deferrability
+create schema fkpart3
+ create table pkey (a int primary key)
+ create table fk_part (a int, constraint fkey foreign key (a) references fkpart3.pkey deferrable initially immediate) partition by list (a)
+ create table fk_part_1 partition of fkpart3.fk_part for values in (1) partition by list (a)
+ create table fk_part_1_1 partition of fkpart3.fk_part_1 for values in (1)
+ create table fk_part_2 partition of fkpart3.fk_part for values in (2);
+begin;
+set constraints fkpart3.fkey deferred;
+insert into fkpart3.fk_part values (1);
+insert into fkpart3.pkey values (1);
+commit;
+begin;
+set constraints fkpart3.fkey deferred;
+delete from fkpart3.pkey;
+delete from fkpart3.fk_part;
+commit;
+drop schema fkpart0, fkpart1, fkpart2, fkpart3 cascade;
+NOTICE: drop cascades to 10 other objects
+DETAIL: drop cascades to table fkpart3.pkey
+drop cascades to table fkpart3.fk_part
+drop cascades to table fkpart2.pkey
+drop cascades to table fkpart2.fk_part
+drop cascades to table fkpart2.fk_part_1
+drop cascades to table fkpart1.pkey
+drop cascades to table fkpart1.fk_part
+drop cascades to table fkpart1.fk_part_1
+drop cascades to table fkpart0.pkey
+drop cascades to table fkpart0.fk_part
+-- 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);
+ERROR: insert or update on table "fk1" violates foreign key constraint "fk_a_fkey"
+DETAIL: Key (a)=(1) is not present in table "pk".
+INSERT into fk VALUES (1000);
+ERROR: insert or update on table "fk2" violates foreign key constraint "fk_a_fkey"
+DETAIL: Key (a)=(1000) is not present in table "pk".
+INSERT into fk VALUES (2000);
+ERROR: insert or update on table "fk2" violates foreign key constraint "fk_a_fkey"
+DETAIL: Key (a)=(2000) is not present in table "pk".
+INSERT into fk VALUES (3000);
+ERROR: insert or update on table "fk2" violates foreign key constraint "fk_a_fkey"
+DETAIL: Key (a)=(3000) is not present in table "pk".
+INSERT into fk VALUES (4000);
+ERROR: insert or update on table "fk3" violates foreign key constraint "fk_a_fkey"
+DETAIL: Key (a)=(4000) is not present in table "pk".
+INSERT into fk VALUES (4500);
+ERROR: insert or update on table "fk3" violates foreign key constraint "fk_a_fkey"
+DETAIL: Key (a)=(4500) is not present in table "pk".
+-- 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;
+ERROR: update or delete on table "pk1" violates foreign key constraint "fk_a_fkey1" on table "fk"
+DETAIL: Key (a)=(1) is still referenced from table "fk".
+DELETE FROM pk WHERE a = 1000;
+ERROR: update or delete on table "pk2" violates foreign key constraint "fk_a_fkey2" on table "fk"
+DETAIL: Key (a)=(1000) is still referenced from table "fk".
+DELETE FROM pk WHERE a = 2000;
+ERROR: update or delete on table "pk3" violates foreign key constraint "fk_a_fkey3" on table "fk"
+DETAIL: Key (a)=(2000) is still referenced from table "fk".
+DELETE FROM pk WHERE a = 3000;
+ERROR: update or delete on table "pk4" violates foreign key constraint "fk_a_fkey4" on table "fk"
+DETAIL: Key (a)=(3000) is still referenced from table "fk".
+DELETE FROM pk WHERE a = 4000;
+ERROR: update or delete on table "pk51" violates foreign key constraint "fk_a_fkey6" on table "fk"
+DETAIL: Key (a)=(4000) is still referenced from table "fk".
+DELETE FROM pk WHERE a = 4500;
+ERROR: update or delete on table "pk52" violates foreign key constraint "fk_a_fkey7" on table "fk"
+DETAIL: Key (a)=(4500) is still referenced from table "fk".
+UPDATE pk SET a = 2 WHERE a = 1;
+ERROR: update or delete on table "pk1" violates foreign key constraint "fk_a_fkey1" on table "fk"
+DETAIL: Key (a)=(1) is still referenced from table "fk".
+UPDATE pk SET a = 1002 WHERE a = 1000;
+ERROR: update or delete on table "pk2" violates foreign key constraint "fk_a_fkey2" on table "fk"
+DETAIL: Key (a)=(1000) is still referenced from table "fk".
+UPDATE pk SET a = 2002 WHERE a = 2000;
+ERROR: update or delete on table "pk3" violates foreign key constraint "fk_a_fkey3" on table "fk"
+DETAIL: Key (a)=(2000) is still referenced from table "fk".
+UPDATE pk SET a = 3002 WHERE a = 3000;
+ERROR: update or delete on table "pk4" violates foreign key constraint "fk_a_fkey4" on table "fk"
+DETAIL: Key (a)=(3000) is still referenced from table "fk".
+UPDATE pk SET a = 4002 WHERE a = 4000;
+ERROR: update or delete on table "pk51" violates foreign key constraint "fk_a_fkey6" on table "fk"
+DETAIL: Key (a)=(4000) is still referenced from table "fk".
+UPDATE pk SET a = 4502 WHERE a = 4500;
+ERROR: update or delete on table "pk52" violates foreign key constraint "fk_a_fkey7" on table "fk"
+DETAIL: Key (a)=(4500) is still referenced from table "fk".
+-- 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;
+ERROR: removing partition "droppk_d" violates foreign key constraint "dropfk_a_fkey5"
+DETAIL: Key (a)=(2000) is still referenced from table "dropfk".
+ALTER TABLE droppk2 DETACH PARTITION droppk2_d;
+ERROR: removing partition "droppk2_d" violates foreign key constraint "dropfk_a_fkey4"
+DETAIL: Key (a)=(1500) is still referenced from table "dropfk".
+ALTER TABLE droppk DETACH PARTITION droppk1;
+ERROR: removing partition "droppk1" violates foreign key constraint "dropfk_a_fkey1"
+DETAIL: Key (a)=(1) is still referenced from table "dropfk".
+ALTER TABLE droppk DETACH PARTITION droppk2;
+ERROR: removing partition "droppk2" violates foreign key constraint "dropfk_a_fkey2"
+DETAIL: Key (a)=(1000) is still referenced from table "dropfk".
+ALTER TABLE droppk2 DETACH PARTITION droppk21;
+ERROR: removing partition "droppk21" violates foreign key constraint "dropfk_a_fkey3"
+DETAIL: Key (a)=(1000) is still referenced from table "dropfk".
+-- dropping partitions is disallowed
+DROP TABLE droppk_d;
+ERROR: cannot drop table droppk_d because other objects depend on it
+DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk_d
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE droppk2_d;
+ERROR: cannot drop table droppk2_d because other objects depend on it
+DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk2_d
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE droppk1;
+ERROR: cannot drop table droppk1 because other objects depend on it
+DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk1
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE droppk2;
+ERROR: cannot drop table droppk2 because other objects depend on it
+DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk2
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE droppk21;
+ERROR: cannot drop table droppk21 because other objects depend on it
+DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk21
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DELETE FROM dropfk;
+-- dropping partitions is disallowed, even when no referencing values
+DROP TABLE droppk_d;
+ERROR: cannot drop table droppk_d because other objects depend on it
+DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk_d
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE droppk2_d;
+ERROR: cannot drop table droppk2_d because other objects depend on it
+DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk2_d
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE droppk1;
+ERROR: cannot drop table droppk1 because other objects depend on it
+DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk1
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- but DETACH is allowed, and DROP afterwards works
+ALTER TABLE droppk2 DETACH PARTITION droppk21;
+DROP TABLE droppk2;
+ERROR: cannot drop table droppk2 because other objects depend on it
+DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk2
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- 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;
+ pg_describe_object | confrelid | case
+------------------------------------+-----------+-----------------------------------
+ constraint fk_a_fkey on table fk | pk | TOP
+ constraint fk_a_fkey1 on table fk | pk1 | constraint fk_a_fkey on table fk
+ constraint fk_a_fkey2 on table fk | pk11 | constraint fk_a_fkey1 on table fk
+ constraint fk_a_fkey3 on table fk | pk2 | constraint fk_a_fkey on table fk
+ constraint fk_a_fkey4 on table fk | pk3 | constraint fk_a_fkey on table fk
+ constraint fk_a_fkey5 on table fk | pk31 | constraint fk_a_fkey4 on table fk
+ constraint fk_a_fkey6 on table fk | pk32 | constraint fk_a_fkey4 on table fk
+ constraint fk_a_fkey on table fk1 | pk | constraint fk_a_fkey on table fk
+ constraint fk_a_fkey on table fk11 | pk | constraint fk_a_fkey on table fk1
+ constraint fk_a_fkey on table fk2 | pk | constraint fk_a_fkey on table fk
+ constraint fk_a_fkey on table fk3 | pk | constraint fk_a_fkey on table fk
+(11 rows)
+
+CREATE TABLE fk4 (LIKE fk);
+INSERT INTO fk4 VALUES (50);
+ALTER TABLE fk ATTACH PARTITION fk4 FOR VALUES IN (50);
+ERROR: insert or update on table "fk4" violates foreign key constraint "fk_a_fkey"
+DETAIL: Key (a)=(50) is not present in table "pk".
+-- 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
+ERROR: insert or update on table "fk" violates foreign key constraint "fk_a_fkey"
+DETAIL: Key (a)=(1) is not present in table "pk".
+BEGIN;
+SET CONSTRAINTS fk_a_fkey DEFERRED;
+INSERT INTO fk VALUES (1);
+COMMIT; -- should fail
+ERROR: insert or update on table "fk" violates foreign key constraint "fk_a_fkey"
+DETAIL: Key (a)=(1) is not present in table "pk".
+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
+ERROR: cannot alter constraint "ref_f1_f2_fkey" on relation "ref22"
+DETAIL: Constraint "ref_f1_f2_fkey" is derived from constraint "ref_f1_f2_fkey" of relation "ref".
+HINT: You may alter the constraint it derives from, instead.
+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
+ERROR: cannot alter constraint "ref_f1_f2_fkey1" on relation "ref"
+DETAIL: Constraint "ref_f1_f2_fkey1" is derived from constraint "ref_f1_f2_fkey" of relation "ref".
+HINT: You may alter the constraint it derives from, instead.
+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;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pk
+drop cascades to table fk
+-- 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;
+ tableoid | a
+----------+----
+ fk12 | 20
+(1 row)
+
+DELETE FROM pk WHERE a = 20;
+SELECT tableoid::regclass, * FROM fk;
+ tableoid | a
+----------+---
+(0 rows)
+
+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;
+ tableoid | a
+----------+---
+ fk_d |
+ fk_d |
+(2 rows)
+
+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 *;
+ a
+----
+ 20
+(1 row)
+
+UPDATE pk SET a = 90 WHERE a = 30 RETURNING *;
+ a
+----
+ 90
+(1 row)
+
+SELECT tableoid::regclass, * FROM fk;
+ tableoid | id | a
+----------+----+----
+ fk12 | 1 | 50
+ fk12 | 2 | 50
+(2 rows)
+
+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;
+ERROR: update or delete on table "pk11" violates foreign key constraint "fk_a_fkey2" on table "fk"
+DETAIL: Key (a)=(20) is still referenced from table "fk".
+UPDATE pk SET a = 90 WHERE a = 30;
+ERROR: update or delete on table "pk11" violates foreign key constraint "fk_a_fkey2" on table "fk"
+DETAIL: Key (a)=(30) is still referenced from table "fk".
+SELECT tableoid::regclass, * FROM fk;
+ tableoid | a
+----------+----
+ fk12 | 20
+ fk12 | 30
+(2 rows)
+
+DROP TABLE fk;
+-- test for reported bug: relispartition not set
+-- https://postgr.es/m/CA+HiwqHMsRtRYRWYTWavKJ8x14AFsv7bmAV46mYwnfD3vy8goQ@mail.gmail.com
+CREATE SCHEMA fkpart7
+ CREATE TABLE pkpart (a int) PARTITION BY LIST (a)
+ CREATE TABLE pkpart1 PARTITION OF pkpart FOR VALUES IN (1);
+ALTER TABLE fkpart7.pkpart1 ADD PRIMARY KEY (a);
+ALTER TABLE fkpart7.pkpart ADD PRIMARY KEY (a);
+CREATE TABLE fkpart7.fk (a int REFERENCES fkpart7.pkpart);
+DROP SCHEMA fkpart7 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table fkpart7.pkpart
+drop cascades to table fkpart7.fk
+-- ensure we check partitions are "not used" when dropping constraints
+CREATE SCHEMA fkpart8
+ CREATE TABLE tbl1(f1 int PRIMARY KEY)
+ CREATE TABLE tbl2(f1 int REFERENCES tbl1 DEFERRABLE INITIALLY DEFERRED) PARTITION BY RANGE(f1)
+ CREATE TABLE tbl2_p1 PARTITION OF tbl2 FOR VALUES FROM (minvalue) TO (maxvalue);
+INSERT INTO fkpart8.tbl1 VALUES(1);
+BEGIN;
+INSERT INTO fkpart8.tbl2 VALUES(1);
+ALTER TABLE fkpart8.tbl2 DROP CONSTRAINT tbl2_f1_fkey;
+ERROR: cannot ALTER TABLE "tbl2_p1" because it has pending trigger events
+COMMIT;
+DROP SCHEMA fkpart8 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table fkpart8.tbl1
+drop cascades to table fkpart8.tbl2
+-- ensure FK referencing a multi-level partitioned table are
+-- enforce reference to sub-children.
+CREATE SCHEMA fkpart9
+ CREATE TABLE pk (a INT PRIMARY KEY) PARTITION BY RANGE (a)
+ CREATE TABLE fk (
+ fk_a INT REFERENCES pk(a) ON DELETE CASCADE
+ )
+ CREATE TABLE pk1 PARTITION OF pk FOR VALUES FROM (30) TO (50) PARTITION BY RANGE (a)
+ CREATE TABLE pk11 PARTITION OF pk1 FOR VALUES FROM (30) TO (40);
+INSERT INTO fkpart9.pk VALUES (35);
+INSERT INTO fkpart9.fk VALUES (35);
+DELETE FROM fkpart9.pk WHERE a=35;
+SELECT * FROM fkpart9.pk;
+ a
+---
+(0 rows)
+
+SELECT * FROM fkpart9.fk;
+ fk_a
+------
+(0 rows)
+
+DROP SCHEMA fkpart9 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table fkpart9.pk
+drop cascades to table fkpart9.fk
+-- test that ri_Check_Pk_Match() scans the correct partition for a deferred
+-- ON DELETE/UPDATE NO ACTION constraint
+CREATE SCHEMA fkpart10
+ CREATE TABLE tbl1(f1 int PRIMARY KEY) PARTITION BY RANGE(f1)
+ CREATE TABLE tbl1_p1 PARTITION OF tbl1 FOR VALUES FROM (minvalue) TO (1)
+ CREATE TABLE tbl1_p2 PARTITION OF tbl1 FOR VALUES FROM (1) TO (maxvalue)
+ CREATE TABLE tbl2(f1 int REFERENCES tbl1 DEFERRABLE INITIALLY DEFERRED);
+INSERT INTO fkpart10.tbl1 VALUES (0), (1);
+INSERT INTO fkpart10.tbl2 VALUES (0), (1);
+BEGIN;
+DELETE FROM fkpart10.tbl1 WHERE f1 = 0;
+UPDATE fkpart10.tbl1 SET f1 = 2 WHERE f1 = 1;
+INSERT INTO fkpart10.tbl1 VALUES (0), (1);
+COMMIT;
+DROP SCHEMA fkpart10 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table fkpart10.tbl1
+drop cascades to table fkpart10.tbl2
diff --git a/src/test/regress/expected/functional_deps.out b/src/test/regress/expected/functional_deps.out
new file mode 100644
index 0000000..32381b8
--- /dev/null
+++ b/src/test/regress/expected/functional_deps.out
@@ -0,0 +1,232 @@
+-- from http://www.depesz.com/index.php/2010/04/19/getting-unique-elements/
+CREATE TEMP TABLE articles (
+ id int CONSTRAINT articles_pkey PRIMARY KEY,
+ keywords text,
+ title text UNIQUE NOT NULL,
+ body text UNIQUE,
+ created date
+);
+CREATE TEMP TABLE articles_in_category (
+ article_id int,
+ category_id int,
+ changed date,
+ PRIMARY KEY (article_id, category_id)
+);
+-- test functional dependencies based on primary keys/unique constraints
+-- base tables
+-- group by primary key (OK)
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY id;
+ id | keywords | title | body | created
+----+----------+-------+------+---------
+(0 rows)
+
+-- group by unique not null (fail/todo)
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY title;
+ERROR: column "articles.id" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT id, keywords, title, body, created
+ ^
+-- group by unique nullable (fail)
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY body;
+ERROR: column "articles.id" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT id, keywords, title, body, created
+ ^
+-- group by something else (fail)
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY keywords;
+ERROR: column "articles.id" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT id, keywords, title, body, created
+ ^
+-- multiple tables
+-- group by primary key (OK)
+SELECT a.id, a.keywords, a.title, a.body, a.created
+FROM articles AS a, articles_in_category AS aic
+WHERE a.id = aic.article_id AND aic.category_id in (14,62,70,53,138)
+GROUP BY a.id;
+ id | keywords | title | body | created
+----+----------+-------+------+---------
+(0 rows)
+
+-- group by something else (fail)
+SELECT a.id, a.keywords, a.title, a.body, a.created
+FROM articles AS a, articles_in_category AS aic
+WHERE a.id = aic.article_id AND aic.category_id in (14,62,70,53,138)
+GROUP BY aic.article_id, aic.category_id;
+ERROR: column "a.id" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT a.id, a.keywords, a.title, a.body, a.created
+ ^
+-- JOIN syntax
+-- group by left table's primary key (OK)
+SELECT a.id, a.keywords, a.title, a.body, a.created
+FROM articles AS a JOIN articles_in_category AS aic ON a.id = aic.article_id
+WHERE aic.category_id in (14,62,70,53,138)
+GROUP BY a.id;
+ id | keywords | title | body | created
+----+----------+-------+------+---------
+(0 rows)
+
+-- group by something else (fail)
+SELECT a.id, a.keywords, a.title, a.body, a.created
+FROM articles AS a JOIN articles_in_category AS aic ON a.id = aic.article_id
+WHERE aic.category_id in (14,62,70,53,138)
+GROUP BY aic.article_id, aic.category_id;
+ERROR: column "a.id" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT a.id, a.keywords, a.title, a.body, a.created
+ ^
+-- group by right table's (composite) primary key (OK)
+SELECT aic.changed
+FROM articles AS a JOIN articles_in_category AS aic ON a.id = aic.article_id
+WHERE aic.category_id in (14,62,70,53,138)
+GROUP BY aic.category_id, aic.article_id;
+ changed
+---------
+(0 rows)
+
+-- group by right table's partial primary key (fail)
+SELECT aic.changed
+FROM articles AS a JOIN articles_in_category AS aic ON a.id = aic.article_id
+WHERE aic.category_id in (14,62,70,53,138)
+GROUP BY aic.article_id;
+ERROR: column "aic.changed" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT aic.changed
+ ^
+-- example from documentation
+CREATE TEMP TABLE products (product_id int, name text, price numeric);
+CREATE TEMP TABLE sales (product_id int, units int);
+-- OK
+SELECT product_id, p.name, (sum(s.units) * p.price) AS sales
+ FROM products p LEFT JOIN sales s USING (product_id)
+ GROUP BY product_id, p.name, p.price;
+ product_id | name | sales
+------------+------+-------
+(0 rows)
+
+-- fail
+SELECT product_id, p.name, (sum(s.units) * p.price) AS sales
+ FROM products p LEFT JOIN sales s USING (product_id)
+ GROUP BY product_id;
+ERROR: column "p.name" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT product_id, p.name, (sum(s.units) * p.price) AS sales
+ ^
+ALTER TABLE products ADD PRIMARY KEY (product_id);
+-- OK now
+SELECT product_id, p.name, (sum(s.units) * p.price) AS sales
+ FROM products p LEFT JOIN sales s USING (product_id)
+ GROUP BY product_id;
+ product_id | name | sales
+------------+------+-------
+(0 rows)
+
+-- Drupal example, http://drupal.org/node/555530
+CREATE TEMP TABLE node (
+ nid SERIAL,
+ vid integer NOT NULL default '0',
+ type varchar(32) NOT NULL default '',
+ title varchar(128) NOT NULL default '',
+ uid integer NOT NULL default '0',
+ status integer NOT NULL default '1',
+ created integer NOT NULL default '0',
+ -- snip
+ PRIMARY KEY (nid, vid)
+);
+CREATE TEMP TABLE users (
+ uid integer NOT NULL default '0',
+ name varchar(60) NOT NULL default '',
+ pass varchar(32) NOT NULL default '',
+ -- snip
+ PRIMARY KEY (uid),
+ UNIQUE (name)
+);
+-- OK
+SELECT u.uid, u.name FROM node n
+INNER JOIN users u ON u.uid = n.uid
+WHERE n.type = 'blog' AND n.status = 1
+GROUP BY u.uid, u.name;
+ uid | name
+-----+------
+(0 rows)
+
+-- OK
+SELECT u.uid, u.name FROM node n
+INNER JOIN users u ON u.uid = n.uid
+WHERE n.type = 'blog' AND n.status = 1
+GROUP BY u.uid;
+ uid | name
+-----+------
+(0 rows)
+
+-- Check views and dependencies
+-- fail
+CREATE TEMP VIEW fdv1 AS
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY body;
+ERROR: column "articles.id" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 2: SELECT id, keywords, title, body, created
+ ^
+-- OK
+CREATE TEMP VIEW fdv1 AS
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY id;
+-- fail
+ALTER TABLE articles DROP CONSTRAINT articles_pkey RESTRICT;
+ERROR: cannot drop constraint articles_pkey on table articles because other objects depend on it
+DETAIL: view fdv1 depends on constraint articles_pkey on table articles
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP VIEW fdv1;
+-- multiple dependencies
+CREATE TEMP VIEW fdv2 AS
+SELECT a.id, a.keywords, a.title, aic.category_id, aic.changed
+FROM articles AS a JOIN articles_in_category AS aic ON a.id = aic.article_id
+WHERE aic.category_id in (14,62,70,53,138)
+GROUP BY a.id, aic.category_id, aic.article_id;
+ALTER TABLE articles DROP CONSTRAINT articles_pkey RESTRICT; -- fail
+ERROR: cannot drop constraint articles_pkey on table articles because other objects depend on it
+DETAIL: view fdv2 depends on constraint articles_pkey on table articles
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+ALTER TABLE articles_in_category DROP CONSTRAINT articles_in_category_pkey RESTRICT; --fail
+ERROR: cannot drop constraint articles_in_category_pkey on table articles_in_category because other objects depend on it
+DETAIL: view fdv2 depends on constraint articles_in_category_pkey on table articles_in_category
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP VIEW fdv2;
+-- nested queries
+CREATE TEMP VIEW fdv3 AS
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY id
+UNION
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY id;
+ALTER TABLE articles DROP CONSTRAINT articles_pkey RESTRICT; -- fail
+ERROR: cannot drop constraint articles_pkey on table articles because other objects depend on it
+DETAIL: view fdv3 depends on constraint articles_pkey on table articles
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP VIEW fdv3;
+CREATE TEMP VIEW fdv4 AS
+SELECT * FROM articles WHERE title IN (SELECT title FROM articles GROUP BY id);
+ALTER TABLE articles DROP CONSTRAINT articles_pkey RESTRICT; -- fail
+ERROR: cannot drop constraint articles_pkey on table articles because other objects depend on it
+DETAIL: view fdv4 depends on constraint articles_pkey on table articles
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP VIEW fdv4;
+-- prepared query plans: this results in failure on reuse
+PREPARE foo AS
+ SELECT id, keywords, title, body, created
+ FROM articles
+ GROUP BY id;
+EXECUTE foo;
+ id | keywords | title | body | created
+----+----------+-------+------+---------
+(0 rows)
+
+ALTER TABLE articles DROP CONSTRAINT articles_pkey RESTRICT;
+EXECUTE foo; -- fail
+ERROR: column "articles.keywords" must appear in the GROUP BY clause or be used in an aggregate function
diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out
new file mode 100644
index 0000000..c2e5676
--- /dev/null
+++ b/src/test/regress/expected/generated.out
@@ -0,0 +1,1065 @@
+-- sanity check of system catalog
+SELECT attrelid, attname, attgenerated FROM pg_attribute WHERE attgenerated NOT IN ('', 's');
+ attrelid | attname | attgenerated
+----------+---------+--------------
+(0 rows)
+
+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;
+ table_name | column_name | column_default | is_nullable | is_generated | generation_expression
+------------+-------------+----------------+-------------+--------------+-----------------------
+ gtest0 | a | | NO | NEVER |
+ gtest0 | b | | YES | ALWAYS | 55
+ gtest1 | a | | NO | NEVER |
+ gtest1 | b | | YES | ALWAYS | (a * 2)
+(4 rows)
+
+SELECT table_name, column_name, dependent_column FROM information_schema.column_column_usage ORDER BY 1, 2, 3;
+ table_name | column_name | dependent_column
+------------+-------------+------------------
+ gtest1 | a | b
+(1 row)
+
+\d gtest1
+ Table "public.gtest1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ a | integer | | not null |
+ b | integer | | | generated always as (a * 2) stored
+Indexes:
+ "gtest1_pkey" PRIMARY KEY, btree (a)
+
+-- 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);
+ERROR: multiple generation clauses specified for column "b" of table "gtest_err_1"
+LINE 1: ...ARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ...
+ ^
+-- 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);
+ERROR: cannot use generated column "b" in column generation expression
+LINE 1: ...2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STO...
+ ^
+DETAIL: A generated column cannot reference another generated column.
+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);
+ERROR: cannot use generated column "b" in column generation expression
+LINE 1: ...AYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STO...
+ ^
+DETAIL: A generated column cannot reference another generated column.
+-- 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);
+ERROR: cannot use whole-row variable in column generation expression
+LINE 2: b int GENERATED ALWAYS AS (num_nulls(gtest_err_2c)) STOR...
+ ^
+DETAIL: This would cause the generated column to depend on its own value.
+-- invalid reference
+CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STORED);
+ERROR: column "c" does not exist
+LINE 1: ..._3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STO...
+ ^
+-- generation expression must be immutable
+CREATE TABLE gtest_err_4 (a int PRIMARY KEY, b double precision GENERATED ALWAYS AS (random()) STORED);
+ERROR: generation expression is not immutable
+-- 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);
+ERROR: both default and generation expression specified for column "b" of table "gtest_err_5a"
+LINE 1: ... gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ...
+ ^
+CREATE TABLE gtest_err_5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ALWAYS AS (a * 2) STORED);
+ERROR: both identity and generation expression specified for column "b" of table "gtest_err_5b"
+LINE 1: ...t PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ...
+ ^
+-- 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);
+ERROR: cannot use system column "xmin" in column generation expression
+LINE 1: ...a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37...
+ ^
+-- various prohibited constructs
+CREATE TABLE gtest_err_7a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (avg(a)) STORED);
+ERROR: aggregate functions are not allowed in column generation expressions
+LINE 1: ...7a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (avg(a)) ST...
+ ^
+CREATE TABLE gtest_err_7b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (row_number() OVER (ORDER BY a)) STORED);
+ERROR: window functions are not allowed in column generation expressions
+LINE 1: ...7b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (row_number...
+ ^
+CREATE TABLE gtest_err_7c (a int PRIMARY KEY, b int GENERATED ALWAYS AS ((SELECT a)) STORED);
+ERROR: cannot use subquery in column generation expression
+LINE 1: ...7c (a int PRIMARY KEY, b int GENERATED ALWAYS AS ((SELECT a)...
+ ^
+CREATE TABLE gtest_err_7d (a int PRIMARY KEY, b int GENERATED ALWAYS AS (generate_series(1, a)) STORED);
+ERROR: set-returning functions are not allowed in column generation expressions
+LINE 1: ...7d (a int PRIMARY KEY, b int GENERATED ALWAYS AS (generate_s...
+ ^
+-- GENERATED BY DEFAULT not allowed
+CREATE TABLE gtest_err_8 (a int PRIMARY KEY, b int GENERATED BY DEFAULT AS (a * 2) STORED);
+ERROR: for a generated column, GENERATED ALWAYS must be specified
+LINE 1: ...E gtest_err_8 (a int PRIMARY KEY, b int GENERATED BY DEFAULT...
+ ^
+INSERT INTO gtest1 VALUES (1);
+INSERT INTO gtest1 VALUES (2, DEFAULT); -- ok
+INSERT INTO gtest1 VALUES (3, 33); -- error
+ERROR: cannot insert a non-DEFAULT value into column "b"
+DETAIL: Column "b" is a generated column.
+INSERT INTO gtest1 VALUES (3, 33), (4, 44); -- error
+ERROR: cannot insert a non-DEFAULT value into column "b"
+DETAIL: Column "b" is a generated column.
+INSERT INTO gtest1 VALUES (3, DEFAULT), (4, 44); -- error
+ERROR: cannot insert a non-DEFAULT value into column "b"
+DETAIL: Column "b" is a generated column.
+INSERT INTO gtest1 VALUES (3, 33), (4, DEFAULT); -- error
+ERROR: cannot insert a non-DEFAULT value into column "b"
+DETAIL: Column "b" is a generated column.
+INSERT INTO gtest1 VALUES (3, DEFAULT), (4, DEFAULT); -- ok
+SELECT * FROM gtest1 ORDER BY a;
+ a | b
+---+---
+ 1 | 2
+ 2 | 4
+ 3 | 6
+ 4 | 8
+(4 rows)
+
+DELETE FROM gtest1 WHERE a >= 3;
+UPDATE gtest1 SET b = DEFAULT WHERE a = 1;
+UPDATE gtest1 SET b = 11 WHERE a = 1; -- error
+ERROR: column "b" can only be updated to DEFAULT
+DETAIL: Column "b" is a generated column.
+SELECT * FROM gtest1 ORDER BY a;
+ a | b
+---+---
+ 1 | 2
+ 2 | 4
+(2 rows)
+
+SELECT a, b, b * 2 AS b2 FROM gtest1 ORDER BY a;
+ a | b | b2
+---+---+----
+ 1 | 2 | 4
+ 2 | 4 | 8
+(2 rows)
+
+SELECT a, b FROM gtest1 WHERE b = 4 ORDER BY a;
+ a | b
+---+---
+ 2 | 4
+(1 row)
+
+-- test that overflow error happens on write
+INSERT INTO gtest1 VALUES (2000000000);
+ERROR: integer out of range
+SELECT * FROM gtest1;
+ a | b
+---+---
+ 2 | 4
+ 1 | 2
+(2 rows)
+
+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;
+ x | y | a | b
+----+---+---+---
+ 11 | 1 | 1 | 2
+ 22 | 2 | 2 | 4
+(2 rows)
+
+DROP TABLE gtestx;
+-- test UPDATE/DELETE quals
+SELECT * FROM gtest1 ORDER BY a;
+ a | b
+---+---
+ 1 | 2
+ 2 | 4
+(2 rows)
+
+UPDATE gtest1 SET a = 3 WHERE b = 4;
+SELECT * FROM gtest1 ORDER BY a;
+ a | b
+---+---
+ 1 | 2
+ 3 | 6
+(2 rows)
+
+DELETE FROM gtest1 WHERE b = 2;
+SELECT * FROM gtest1 ORDER BY a;
+ a | b
+---+---
+ 3 | 6
+(1 row)
+
+-- views
+CREATE VIEW gtest1v AS SELECT * FROM gtest1;
+SELECT * FROM gtest1v;
+ a | b
+---+---
+ 3 | 6
+(1 row)
+
+INSERT INTO gtest1v VALUES (4, 8); -- error
+ERROR: cannot insert a non-DEFAULT value into column "b"
+DETAIL: Column "b" is a generated column.
+INSERT INTO gtest1v VALUES (5, DEFAULT); -- ok
+INSERT INTO gtest1v VALUES (6, 66), (7, 77); -- error
+ERROR: cannot insert a non-DEFAULT value into column "b"
+DETAIL: Column "b" is a generated column.
+INSERT INTO gtest1v VALUES (6, DEFAULT), (7, 77); -- error
+ERROR: cannot insert a non-DEFAULT value into column "b"
+DETAIL: Column "b" is a generated column.
+INSERT INTO gtest1v VALUES (6, 66), (7, DEFAULT); -- error
+ERROR: cannot insert a non-DEFAULT value into column "b"
+DETAIL: Column "b" is a generated column.
+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
+ERROR: cannot insert a non-DEFAULT value into column "b"
+DETAIL: Column "b" is a generated column.
+INSERT INTO gtest1v VALUES (8, DEFAULT), (9, DEFAULT); -- error
+ERROR: cannot insert a non-DEFAULT value into column "b"
+DETAIL: Column "b" is a generated column.
+SELECT * FROM gtest1v;
+ a | b
+---+----
+ 3 | 6
+ 5 | 10
+ 6 | 12
+ 7 | 14
+(4 rows)
+
+DELETE FROM gtest1v WHERE a >= 5;
+DROP VIEW gtest1v;
+-- CTEs
+WITH foo AS (SELECT * FROM gtest1) SELECT * FROM foo;
+ a | b
+---+---
+ 3 | 6
+(1 row)
+
+-- inheritance
+CREATE TABLE gtest1_1 () INHERITS (gtest1);
+SELECT * FROM gtest1_1;
+ a | b
+---+---
+(0 rows)
+
+\d gtest1_1
+ Table "public.gtest1_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ a | integer | | not null |
+ b | integer | | | generated always as (a * 2) stored
+Inherits: gtest1
+
+INSERT INTO gtest1_1 VALUES (4);
+SELECT * FROM gtest1_1;
+ a | b
+---+---
+ 4 | 8
+(1 row)
+
+SELECT * FROM gtest1;
+ a | b
+---+---
+ 3 | 6
+ 4 | 8
+(2 rows)
+
+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);
+NOTICE: merging column "a" with inherited definition
+NOTICE: merging column "b" with inherited definition
+\d gtest_normal_child
+ Table "public.gtest_normal_child"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ a | integer | | |
+ b | integer | | | generated always as (a * 2) stored
+Inherits: gtest_normal
+
+INSERT INTO gtest_normal (a) VALUES (1);
+INSERT INTO gtest_normal_child (a) VALUES (2);
+SELECT * FROM gtest_normal;
+ a | b
+---+---
+ 1 |
+ 2 | 4
+(2 rows)
+
+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;
+ a | b
+---+---
+ 1 |
+ 2 | 4
+ 3 | 9
+(3 rows)
+
+-- test inheritance mismatches between parent and child
+CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS (a * 22) STORED) INHERITS (gtest1); -- error
+NOTICE: merging column "b" with inherited definition
+ERROR: child column "b" specifies generation expression
+HINT: Omit the generation expression in the definition of the child table column to inherit the generation expression from the parent table.
+CREATE TABLE gtestx (x int, b int DEFAULT 10) INHERITS (gtest1); -- error
+NOTICE: merging column "b" with inherited definition
+ERROR: column "b" inherits from generated column but specifies default
+CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS IDENTITY) INHERITS (gtest1); -- error
+NOTICE: merging column "b" with inherited definition
+ERROR: column "b" inherits from generated column but specifies identity
+CREATE TABLE gtestxx_1 (a int NOT NULL, b int);
+ALTER TABLE gtestxx_1 INHERIT gtest1; -- error
+ERROR: column "b" in child table must be a generated column
+CREATE TABLE gtestxx_2 (a int NOT NULL, b int GENERATED ALWAYS AS (a * 22) STORED);
+ALTER TABLE gtestxx_2 INHERIT gtest1; -- error
+ERROR: column "b" in child table has a conflicting generation expression
+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
+NOTICE: merging multiple inherited definitions of column "b"
+ERROR: inherited column "b" has a generation conflict
+DROP TABLE gtesty;
+CREATE TABLE gtesty (x int, b int GENERATED ALWAYS AS (x * 22) STORED);
+CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty); -- error
+NOTICE: merging multiple inherited definitions of column "b"
+ERROR: column "b" inherits conflicting generation expressions
+DROP TABLE gtesty;
+CREATE TABLE gtesty (x int, b int DEFAULT 55);
+CREATE TABLE gtest1_2 () INHERITS (gtest0, gtesty); -- error
+NOTICE: merging multiple inherited definitions of column "b"
+ERROR: inherited column "b" has a generation conflict
+DROP TABLE gtesty;
+-- 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;
+ a | b
+---+---
+ 1 | 3
+ 2 | 6
+ 3 | 9
+ |
+(4 rows)
+
+UPDATE gtest3 SET a = 22 WHERE a = 2;
+SELECT * FROM gtest3 ORDER BY a;
+ a | b
+----+----
+ 1 | 3
+ 3 | 9
+ 22 | 66
+ |
+(4 rows)
+
+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;
+ a | b
+---+-----
+ a | a+a
+ b | b+b
+ c | c+c
+ |
+(4 rows)
+
+UPDATE gtest3a SET a = 'bb' WHERE a = 'b';
+SELECT * FROM gtest3a ORDER BY a;
+ a | b
+----+-------
+ a | a+a
+ bb | bb+bb
+ c | c+c
+ |
+(4 rows)
+
+-- COPY
+TRUNCATE gtest1;
+INSERT INTO gtest1 (a) VALUES (1), (2);
+COPY gtest1 TO stdout;
+1
+2
+COPY gtest1 (a, b) TO stdout;
+ERROR: column "b" is a generated column
+DETAIL: Generated columns cannot be used in COPY.
+COPY gtest1 FROM stdin;
+COPY gtest1 (a, b) FROM stdin;
+ERROR: column "b" is a generated column
+DETAIL: Generated columns cannot be used in COPY.
+SELECT * FROM gtest1 ORDER BY a;
+ a | b
+---+---
+ 1 | 2
+ 2 | 4
+ 3 | 6
+ 4 | 8
+(4 rows)
+
+TRUNCATE gtest3;
+INSERT INTO gtest3 (a) VALUES (1), (2);
+COPY gtest3 TO stdout;
+1
+2
+COPY gtest3 (a, b) TO stdout;
+ERROR: column "b" is a generated column
+DETAIL: Generated columns cannot be used in COPY.
+COPY gtest3 FROM stdin;
+COPY gtest3 (a, b) FROM stdin;
+ERROR: column "b" is a generated column
+DETAIL: Generated columns cannot be used in COPY.
+SELECT * FROM gtest3 ORDER BY a;
+ a | b
+---+----
+ 1 | 3
+ 2 | 6
+ 3 | 9
+ 4 | 12
+(4 rows)
+
+-- null values
+CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) STORED);
+INSERT INTO gtest2 VALUES (1);
+SELECT * FROM gtest2;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+-- 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;
+ a | b
+----------------------+----------------------
+ 01234567890123456789 | 01234567890123456789
+ |
+(2 rows)
+
+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;
+ a | b
+---+---------
+ 1 | (2,3)
+ 6 | (12,18)
+(2 rows)
+
+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;
+ a | b | c
+---+---+----------------
+ 1 | t | gtest_tableoid
+ 2 | t | gtest_tableoid
+(2 rows)
+
+-- 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;
+\d gtest10
+ Table "public.gtest10"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+Indexes:
+ "gtest10_pkey" PRIMARY KEY, btree (a)
+
+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
+ERROR: permission denied for table gtest11s
+SELECT a, c FROM gtest11s; -- allowed
+ a | c
+---+----
+ 1 | 20
+ 2 | 40
+(2 rows)
+
+SELECT gf1(10); -- not allowed
+ERROR: permission denied for function gf1
+SELECT a, c FROM gtest12s; -- allowed
+ a | c
+---+----
+ 1 | 30
+ 2 | 60
+(2 rows)
+
+RESET ROLE;
+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
+ERROR: new row for relation "gtest20" violates check constraint "gtest20_b_check"
+DETAIL: Failing row contains (30, 60).
+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
+ERROR: check constraint "gtest20a_b_check" of relation "gtest20a" is violated by some 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
+ERROR: check constraint "chk" of relation "gtest20b" is violated by some 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
+ERROR: null value in column "b" of relation "gtest21a" violates not-null constraint
+DETAIL: Failing row contains (0, null).
+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
+ERROR: null value in column "b" of relation "gtest21b" violates not-null constraint
+DETAIL: Failing row contains (0, null).
+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);
+ERROR: duplicate key value violates unique constraint "gtest22a_b_key"
+DETAIL: Key (b)=(1) already exists.
+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);
+ERROR: duplicate key value violates unique constraint "gtest22b_pkey"
+DETAIL: Key (a, b)=(2, 1) already exists.
+-- 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
+ Table "public.gtest22c"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ a | integer | | |
+ b | integer | | | generated always as (a * 2) stored
+Indexes:
+ "gtest22c_b_idx" btree (b)
+ "gtest22c_expr_idx" btree ((b * 3))
+ "gtest22c_pred_idx" btree (a) WHERE b > 0
+
+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;
+ QUERY PLAN
+---------------------------------------------
+ Index Scan using gtest22c_b_idx on gtest22c
+ Index Cond: (b = 4)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b = 4;
+ a | b
+---+---
+ 2 | 4
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
+ QUERY PLAN
+------------------------------------------------
+ Index Scan using gtest22c_expr_idx on gtest22c
+ Index Cond: ((b * 3) = 6)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b * 3 = 6;
+ a | b
+---+---
+ 1 | 2
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+ QUERY PLAN
+------------------------------------------------
+ Index Scan using gtest22c_pred_idx on gtest22c
+ Index Cond: (a = 1)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+ a | b
+---+---
+ 1 | 2
+(1 row)
+
+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
+ERROR: invalid ON UPDATE action for foreign key constraint containing generated column
+CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON DELETE SET NULL); -- error
+ERROR: invalid ON DELETE action for foreign key constraint containing generated column
+CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x));
+\d gtest23b
+ Table "public.gtest23b"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ a | integer | | not null |
+ b | integer | | | generated always as (a * 2) stored
+Indexes:
+ "gtest23b_pkey" PRIMARY KEY, btree (a)
+Foreign-key constraints:
+ "gtest23b_b_fkey" FOREIGN KEY (b) REFERENCES gtest23a(x)
+
+INSERT INTO gtest23b VALUES (1); -- ok
+INSERT INTO gtest23b VALUES (5); -- error
+ERROR: insert or update on table "gtest23b" violates foreign key constraint "gtest23b_b_fkey"
+DETAIL: Key (b)=(10) is not present in table "gtest23a".
+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
+ERROR: insert or update on table "gtest23q" violates foreign key constraint "gtest23q_b_fkey"
+DETAIL: Key (b)=(5) is not present in table "gtest23p".
+-- 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
+ERROR: value for domain gtestdomain1 violates check constraint "gtestdomain1_check"
+-- 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);
+ERROR: generated columns are not supported on typed tables
+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
+ERROR: generated columns are not supported on partitions
+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;
+ f1 | f2 | f3
+------------+----+----
+ 07-15-2016 | 1 | 2
+(1 row)
+
+SELECT * FROM gtest_child;
+ f1 | f2 | f3
+------------+----+----
+ 07-15-2016 | 1 | 2
+(1 row)
+
+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);
+ERROR: cannot use generated column in partition key
+LINE 1: ...ENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3);
+ ^
+DETAIL: Column "f3" is a generated column.
+CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3));
+ERROR: cannot use generated column in partition key
+LINE 1: ...ED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3));
+ ^
+DETAIL: Column "f3" is a generated column.
+-- 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;
+ a | b
+---+----
+ 3 | 9
+ 4 | 12
+(2 rows)
+
+ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4) STORED; -- error
+ERROR: cannot use generated column "b" in column generation expression
+DETAIL: A generated column cannot reference another generated column.
+ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error
+ERROR: column "z" does not exist
+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;
+ a | b | c | x | d | y
+---+----+----+-----+-----+-----
+ 3 | 9 | 42 | 168 | 101 | 404
+ 4 | 12 | 42 | 168 | 101 | 404
+(2 rows)
+
+\d gtest25
+ Table "public.gtest25"
+ Column | Type | Collation | Nullable | Default
+--------+------------------+-----------+----------+------------------------------------------------------
+ a | integer | | not null |
+ b | integer | | | generated always as (a * 3) stored
+ c | integer | | | 42
+ x | integer | | | generated always as (c * 4) stored
+ d | double precision | | | 101
+ y | double precision | | | generated always as (d * 4::double precision) stored
+Indexes:
+ "gtest25_pkey" PRIMARY KEY, btree (a)
+
+-- 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
+ERROR: cannot alter type of a column used by a generated column
+DETAIL: Column "a" is used by generated column "x".
+ALTER TABLE gtest27 ALTER COLUMN x TYPE numeric;
+\d gtest27
+ Table "public.gtest27"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+--------------------------------------------
+ a | integer | | |
+ b | integer | | |
+ x | numeric | | | generated always as (((a + b) * 2)) stored
+
+SELECT * FROM gtest27;
+ a | b | x
+---+----+----
+ 3 | 7 | 20
+ 4 | 11 | 30
+(2 rows)
+
+ALTER TABLE gtest27 ALTER COLUMN x TYPE boolean USING x <> 0; -- error
+ERROR: generation expression for column "x" cannot be cast automatically to type boolean
+ALTER TABLE gtest27 ALTER COLUMN x DROP DEFAULT; -- error
+ERROR: column "x" of relation "gtest27" is a generated column
+HINT: Use ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION instead.
+-- 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;
+\d gtest27
+ Table "public.gtest27"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+------------------------------------------
+ a | bigint | | |
+ b | bigint | | |
+ x | bigint | | | generated always as ((a + b) * 2) stored
+
+-- Ideally you could just do this, but not today (and should x change type?):
+ALTER TABLE gtest27
+ ALTER COLUMN a TYPE float8,
+ ALTER COLUMN b TYPE float8; -- error
+ERROR: cannot alter type of a column used by a generated column
+DETAIL: Column "a" is used by generated column "x".
+\d gtest27
+ Table "public.gtest27"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+------------------------------------------
+ a | bigint | | |
+ b | bigint | | |
+ x | bigint | | | generated always as ((a + b) * 2) stored
+
+SELECT * FROM gtest27;
+ a | b | x
+---+----+----
+ 3 | 7 | 20
+ 4 | 11 | 30
+(2 rows)
+
+-- 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
+ERROR: column "a" of relation "gtest29" is not a stored generated column
+ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION IF EXISTS; -- notice
+NOTICE: column "a" of relation "gtest29" is not a stored generated column, skipping
+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;
+ a | b
+---+----
+ 3 | 6
+ 4 | 8
+ 5 |
+ 6 | 66
+(4 rows)
+
+\d gtest29
+ Table "public.gtest29"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+
+-- check that dependencies between columns have also been removed
+ALTER TABLE gtest29 DROP COLUMN a; -- should not drop b
+\d gtest29
+ Table "public.gtest29"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ b | integer | | |
+
+-- 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
+ Table "public.gtest30"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d gtest30_1
+ Table "public.gtest30_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Inherits: gtest30
+
+DROP TABLE gtest30 CASCADE;
+NOTICE: drop cascades to table gtest30_1
+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
+ERROR: ALTER TABLE / DROP EXPRESSION must be applied to child tables too
+\d gtest30
+ Table "public.gtest30"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ a | integer | | |
+ b | integer | | | generated always as (a * 2) stored
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d gtest30_1
+ Table "public.gtest30_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ a | integer | | |
+ b | integer | | | generated always as (a * 2) stored
+Inherits: gtest30
+
+ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error
+ERROR: cannot drop generation expression from inherited column
+-- 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();
+ERROR: BEFORE trigger's WHEN condition cannot reference NEW generated columns
+LINE 3: WHEN (NEW.b < 0)
+ ^
+DETAIL: Column "b" is a generated column.
+CREATE TRIGGER gtest2b BEFORE INSERT OR UPDATE ON gtest26
+ FOR EACH ROW
+ WHEN (NEW.* IS NOT NULL) -- error
+ EXECUTE PROCEDURE gtest_trigger_func();
+ERROR: BEFORE trigger's WHEN condition cannot reference NEW generated columns
+LINE 3: WHEN (NEW.* IS NOT NULL)
+ ^
+DETAIL: A whole-row reference is used and the table contains generated columns.
+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);
+INFO: gtest2: BEFORE: new = (-2,)
+INFO: gtest4: AFTER: new = (-2,-4)
+SELECT * FROM gtest26 ORDER BY a;
+ a | b
+----+----
+ -2 | -4
+ 0 | 0
+ 3 | 6
+(3 rows)
+
+UPDATE gtest26 SET a = a * -2;
+INFO: gtest1: BEFORE: old = (-2,-4)
+INFO: gtest1: BEFORE: new = (4,)
+INFO: gtest3: AFTER: old = (-2,-4)
+INFO: gtest3: AFTER: new = (4,8)
+INFO: gtest4: AFTER: old = (3,6)
+INFO: gtest4: AFTER: new = (-6,-12)
+SELECT * FROM gtest26 ORDER BY a;
+ a | b
+----+-----
+ -6 | -12
+ 0 | 0
+ 4 | 8
+(3 rows)
+
+DELETE FROM gtest26 WHERE a = -6;
+INFO: gtest1: BEFORE: old = (-6,-12)
+INFO: gtest3: AFTER: old = (-6,-12)
+SELECT * FROM gtest26 ORDER BY a;
+ a | b
+---+---
+ 0 | 0
+ 4 | 8
+(2 rows)
+
+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;
+NOTICE: OK
+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;
+INFO: gtest12_01: BEFORE: old = (1,2)
+INFO: gtest12_01: BEFORE: new = (11,)
+INFO: gtest12_03: BEFORE: old = (1,2)
+INFO: gtest12_03: BEFORE: new = (10,)
+SELECT * FROM gtest26 ORDER BY a;
+ a | b
+----+----
+ 10 | 20
+(1 row)
+
+-- 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*
+ Table "public.gtest28a"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ b | integer | | |
+ c | integer | | |
+ x | integer | | | generated always as (b * 2) stored
+
+ Table "public.gtest28b"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ b | integer | | |
+ c | integer | | |
+ x | integer | | | generated always as (b * 2) stored
+
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
new file mode 100644
index 0000000..974e2ec
--- /dev/null
+++ b/src/test/regress/expected/geometry.out
@@ -0,0 +1,5200 @@
+--
+-- 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;
+ center
+---------
+ (1,1)
+ (2,2)
+ (-5,-4)
+ (2.5,3)
+ (3,3)
+(5 rows)
+
+SELECT (@@ f1) AS center
+ FROM BOX_TBL;
+ center
+---------
+ (1,1)
+ (2,2)
+ (-5,-4)
+ (2.5,3)
+ (3,3)
+(5 rows)
+
+SELECT point(f1) AS center
+ FROM CIRCLE_TBL;
+ center
+-----------
+ (5,1)
+ (1,2)
+ (1,3)
+ (1,2)
+ (100,200)
+ (100,1)
+ (3,5)
+ (3,5)
+(8 rows)
+
+SELECT (@@ f1) AS center
+ FROM CIRCLE_TBL;
+ center
+-----------
+ (5,1)
+ (1,2)
+ (1,3)
+ (1,2)
+ (100,200)
+ (100,1)
+ (3,5)
+ (3,5)
+(8 rows)
+
+SELECT (@@ f1) AS center
+ FROM POLYGON_TBL
+ WHERE (# f1) > 2;
+ center
+-------------------------------
+ (1.33333333333,1.33333333333)
+ (2.33333333333,1.33333333333)
+ (4,5)
+ (4,5)
+ (4,3)
+(5 rows)
+
+-- "is horizontal" function
+SELECT p1.f1
+ FROM POINT_TBL p1
+ WHERE ishorizontal(p1.f1, point '(0,0)');
+ f1
+------------------
+ (0,0)
+ (-10,0)
+ (1e-300,-1e-300)
+(3 rows)
+
+-- "is horizontal" operator
+SELECT p1.f1
+ FROM POINT_TBL p1
+ WHERE p1.f1 ?- point '(0,0)';
+ f1
+------------------
+ (0,0)
+ (-10,0)
+ (1e-300,-1e-300)
+(3 rows)
+
+-- "is vertical" function
+SELECT p1.f1
+ FROM POINT_TBL p1
+ WHERE isvertical(p1.f1, point '(5.1,34.5)');
+ f1
+------------
+ (5.1,34.5)
+(1 row)
+
+-- "is vertical" operator
+SELECT p1.f1
+ FROM POINT_TBL p1
+ WHERE p1.f1 ?| point '(5.1,34.5)';
+ f1
+------------
+ (5.1,34.5)
+(1 row)
+
+-- Slope
+SELECT p1.f1, p2.f1, slope(p1.f1, p2.f1) FROM POINT_TBL p1, POINT_TBL p2;
+ f1 | f1 | slope
+-------------------+-------------------+----------------
+ (0,0) | (0,0) | Infinity
+ (0,0) | (-10,0) | 0
+ (0,0) | (-3,4) | -1.33333333333
+ (0,0) | (5.1,34.5) | 6.76470588235
+ (0,0) | (-5,-12) | 2.4
+ (0,0) | (1e-300,-1e-300) | Infinity
+ (0,0) | (1e+300,Infinity) | Infinity
+ (0,0) | (Infinity,1e+300) | 0
+ (0,0) | (NaN,NaN) | NaN
+ (0,0) | (10,10) | 1
+ (-10,0) | (0,0) | 0
+ (-10,0) | (-10,0) | Infinity
+ (-10,0) | (-3,4) | 0.571428571429
+ (-10,0) | (5.1,34.5) | 2.28476821192
+ (-10,0) | (-5,-12) | -2.4
+ (-10,0) | (1e-300,-1e-300) | 0
+ (-10,0) | (1e+300,Infinity) | Infinity
+ (-10,0) | (Infinity,1e+300) | 0
+ (-10,0) | (NaN,NaN) | NaN
+ (-10,0) | (10,10) | 0.5
+ (-3,4) | (0,0) | -1.33333333333
+ (-3,4) | (-10,0) | 0.571428571429
+ (-3,4) | (-3,4) | Infinity
+ (-3,4) | (5.1,34.5) | 3.76543209877
+ (-3,4) | (-5,-12) | 8
+ (-3,4) | (1e-300,-1e-300) | -1.33333333333
+ (-3,4) | (1e+300,Infinity) | Infinity
+ (-3,4) | (Infinity,1e+300) | 0
+ (-3,4) | (NaN,NaN) | NaN
+ (-3,4) | (10,10) | 0.461538461538
+ (5.1,34.5) | (0,0) | 6.76470588235
+ (5.1,34.5) | (-10,0) | 2.28476821192
+ (5.1,34.5) | (-3,4) | 3.76543209877
+ (5.1,34.5) | (5.1,34.5) | Infinity
+ (5.1,34.5) | (-5,-12) | 4.60396039604
+ (5.1,34.5) | (1e-300,-1e-300) | 6.76470588235
+ (5.1,34.5) | (1e+300,Infinity) | Infinity
+ (5.1,34.5) | (Infinity,1e+300) | 0
+ (5.1,34.5) | (NaN,NaN) | NaN
+ (5.1,34.5) | (10,10) | -5
+ (-5,-12) | (0,0) | 2.4
+ (-5,-12) | (-10,0) | -2.4
+ (-5,-12) | (-3,4) | 8
+ (-5,-12) | (5.1,34.5) | 4.60396039604
+ (-5,-12) | (-5,-12) | Infinity
+ (-5,-12) | (1e-300,-1e-300) | 2.4
+ (-5,-12) | (1e+300,Infinity) | Infinity
+ (-5,-12) | (Infinity,1e+300) | 0
+ (-5,-12) | (NaN,NaN) | NaN
+ (-5,-12) | (10,10) | 1.46666666667
+ (1e-300,-1e-300) | (0,0) | Infinity
+ (1e-300,-1e-300) | (-10,0) | 0
+ (1e-300,-1e-300) | (-3,4) | -1.33333333333
+ (1e-300,-1e-300) | (5.1,34.5) | 6.76470588235
+ (1e-300,-1e-300) | (-5,-12) | 2.4
+ (1e-300,-1e-300) | (1e-300,-1e-300) | Infinity
+ (1e-300,-1e-300) | (1e+300,Infinity) | Infinity
+ (1e-300,-1e-300) | (Infinity,1e+300) | 0
+ (1e-300,-1e-300) | (NaN,NaN) | NaN
+ (1e-300,-1e-300) | (10,10) | 1
+ (1e+300,Infinity) | (0,0) | Infinity
+ (1e+300,Infinity) | (-10,0) | Infinity
+ (1e+300,Infinity) | (-3,4) | Infinity
+ (1e+300,Infinity) | (5.1,34.5) | Infinity
+ (1e+300,Infinity) | (-5,-12) | Infinity
+ (1e+300,Infinity) | (1e-300,-1e-300) | Infinity
+ (1e+300,Infinity) | (1e+300,Infinity) | Infinity
+ (1e+300,Infinity) | (Infinity,1e+300) | NaN
+ (1e+300,Infinity) | (NaN,NaN) | NaN
+ (1e+300,Infinity) | (10,10) | Infinity
+ (Infinity,1e+300) | (0,0) | 0
+ (Infinity,1e+300) | (-10,0) | 0
+ (Infinity,1e+300) | (-3,4) | 0
+ (Infinity,1e+300) | (5.1,34.5) | 0
+ (Infinity,1e+300) | (-5,-12) | 0
+ (Infinity,1e+300) | (1e-300,-1e-300) | 0
+ (Infinity,1e+300) | (1e+300,Infinity) | NaN
+ (Infinity,1e+300) | (Infinity,1e+300) | Infinity
+ (Infinity,1e+300) | (NaN,NaN) | NaN
+ (Infinity,1e+300) | (10,10) | 0
+ (NaN,NaN) | (0,0) | NaN
+ (NaN,NaN) | (-10,0) | NaN
+ (NaN,NaN) | (-3,4) | NaN
+ (NaN,NaN) | (5.1,34.5) | NaN
+ (NaN,NaN) | (-5,-12) | NaN
+ (NaN,NaN) | (1e-300,-1e-300) | NaN
+ (NaN,NaN) | (1e+300,Infinity) | NaN
+ (NaN,NaN) | (Infinity,1e+300) | NaN
+ (NaN,NaN) | (NaN,NaN) | NaN
+ (NaN,NaN) | (10,10) | NaN
+ (10,10) | (0,0) | 1
+ (10,10) | (-10,0) | 0.5
+ (10,10) | (-3,4) | 0.461538461538
+ (10,10) | (5.1,34.5) | -5
+ (10,10) | (-5,-12) | 1.46666666667
+ (10,10) | (1e-300,-1e-300) | 1
+ (10,10) | (1e+300,Infinity) | Infinity
+ (10,10) | (Infinity,1e+300) | 0
+ (10,10) | (NaN,NaN) | NaN
+ (10,10) | (10,10) | Infinity
+(100 rows)
+
+-- Add point
+SELECT p1.f1, p2.f1, p1.f1 + p2.f1 FROM POINT_TBL p1, POINT_TBL p2;
+ f1 | f1 | ?column?
+-------------------+-------------------+---------------------
+ (0,0) | (0,0) | (0,0)
+ (0,0) | (-10,0) | (-10,0)
+ (0,0) | (-3,4) | (-3,4)
+ (0,0) | (5.1,34.5) | (5.1,34.5)
+ (0,0) | (-5,-12) | (-5,-12)
+ (0,0) | (1e-300,-1e-300) | (1e-300,-1e-300)
+ (0,0) | (1e+300,Infinity) | (1e+300,Infinity)
+ (0,0) | (Infinity,1e+300) | (Infinity,1e+300)
+ (0,0) | (NaN,NaN) | (NaN,NaN)
+ (0,0) | (10,10) | (10,10)
+ (-10,0) | (0,0) | (-10,0)
+ (-10,0) | (-10,0) | (-20,0)
+ (-10,0) | (-3,4) | (-13,4)
+ (-10,0) | (5.1,34.5) | (-4.9,34.5)
+ (-10,0) | (-5,-12) | (-15,-12)
+ (-10,0) | (1e-300,-1e-300) | (-10,-1e-300)
+ (-10,0) | (1e+300,Infinity) | (1e+300,Infinity)
+ (-10,0) | (Infinity,1e+300) | (Infinity,1e+300)
+ (-10,0) | (NaN,NaN) | (NaN,NaN)
+ (-10,0) | (10,10) | (0,10)
+ (-3,4) | (0,0) | (-3,4)
+ (-3,4) | (-10,0) | (-13,4)
+ (-3,4) | (-3,4) | (-6,8)
+ (-3,4) | (5.1,34.5) | (2.1,38.5)
+ (-3,4) | (-5,-12) | (-8,-8)
+ (-3,4) | (1e-300,-1e-300) | (-3,4)
+ (-3,4) | (1e+300,Infinity) | (1e+300,Infinity)
+ (-3,4) | (Infinity,1e+300) | (Infinity,1e+300)
+ (-3,4) | (NaN,NaN) | (NaN,NaN)
+ (-3,4) | (10,10) | (7,14)
+ (5.1,34.5) | (0,0) | (5.1,34.5)
+ (5.1,34.5) | (-10,0) | (-4.9,34.5)
+ (5.1,34.5) | (-3,4) | (2.1,38.5)
+ (5.1,34.5) | (5.1,34.5) | (10.2,69)
+ (5.1,34.5) | (-5,-12) | (0.1,22.5)
+ (5.1,34.5) | (1e-300,-1e-300) | (5.1,34.5)
+ (5.1,34.5) | (1e+300,Infinity) | (1e+300,Infinity)
+ (5.1,34.5) | (Infinity,1e+300) | (Infinity,1e+300)
+ (5.1,34.5) | (NaN,NaN) | (NaN,NaN)
+ (5.1,34.5) | (10,10) | (15.1,44.5)
+ (-5,-12) | (0,0) | (-5,-12)
+ (-5,-12) | (-10,0) | (-15,-12)
+ (-5,-12) | (-3,4) | (-8,-8)
+ (-5,-12) | (5.1,34.5) | (0.1,22.5)
+ (-5,-12) | (-5,-12) | (-10,-24)
+ (-5,-12) | (1e-300,-1e-300) | (-5,-12)
+ (-5,-12) | (1e+300,Infinity) | (1e+300,Infinity)
+ (-5,-12) | (Infinity,1e+300) | (Infinity,1e+300)
+ (-5,-12) | (NaN,NaN) | (NaN,NaN)
+ (-5,-12) | (10,10) | (5,-2)
+ (1e-300,-1e-300) | (0,0) | (1e-300,-1e-300)
+ (1e-300,-1e-300) | (-10,0) | (-10,-1e-300)
+ (1e-300,-1e-300) | (-3,4) | (-3,4)
+ (1e-300,-1e-300) | (5.1,34.5) | (5.1,34.5)
+ (1e-300,-1e-300) | (-5,-12) | (-5,-12)
+ (1e-300,-1e-300) | (1e-300,-1e-300) | (2e-300,-2e-300)
+ (1e-300,-1e-300) | (1e+300,Infinity) | (1e+300,Infinity)
+ (1e-300,-1e-300) | (Infinity,1e+300) | (Infinity,1e+300)
+ (1e-300,-1e-300) | (NaN,NaN) | (NaN,NaN)
+ (1e-300,-1e-300) | (10,10) | (10,10)
+ (1e+300,Infinity) | (0,0) | (1e+300,Infinity)
+ (1e+300,Infinity) | (-10,0) | (1e+300,Infinity)
+ (1e+300,Infinity) | (-3,4) | (1e+300,Infinity)
+ (1e+300,Infinity) | (5.1,34.5) | (1e+300,Infinity)
+ (1e+300,Infinity) | (-5,-12) | (1e+300,Infinity)
+ (1e+300,Infinity) | (1e-300,-1e-300) | (1e+300,Infinity)
+ (1e+300,Infinity) | (1e+300,Infinity) | (2e+300,Infinity)
+ (1e+300,Infinity) | (Infinity,1e+300) | (Infinity,Infinity)
+ (1e+300,Infinity) | (NaN,NaN) | (NaN,NaN)
+ (1e+300,Infinity) | (10,10) | (1e+300,Infinity)
+ (Infinity,1e+300) | (0,0) | (Infinity,1e+300)
+ (Infinity,1e+300) | (-10,0) | (Infinity,1e+300)
+ (Infinity,1e+300) | (-3,4) | (Infinity,1e+300)
+ (Infinity,1e+300) | (5.1,34.5) | (Infinity,1e+300)
+ (Infinity,1e+300) | (-5,-12) | (Infinity,1e+300)
+ (Infinity,1e+300) | (1e-300,-1e-300) | (Infinity,1e+300)
+ (Infinity,1e+300) | (1e+300,Infinity) | (Infinity,Infinity)
+ (Infinity,1e+300) | (Infinity,1e+300) | (Infinity,2e+300)
+ (Infinity,1e+300) | (NaN,NaN) | (NaN,NaN)
+ (Infinity,1e+300) | (10,10) | (Infinity,1e+300)
+ (NaN,NaN) | (0,0) | (NaN,NaN)
+ (NaN,NaN) | (-10,0) | (NaN,NaN)
+ (NaN,NaN) | (-3,4) | (NaN,NaN)
+ (NaN,NaN) | (5.1,34.5) | (NaN,NaN)
+ (NaN,NaN) | (-5,-12) | (NaN,NaN)
+ (NaN,NaN) | (1e-300,-1e-300) | (NaN,NaN)
+ (NaN,NaN) | (1e+300,Infinity) | (NaN,NaN)
+ (NaN,NaN) | (Infinity,1e+300) | (NaN,NaN)
+ (NaN,NaN) | (NaN,NaN) | (NaN,NaN)
+ (NaN,NaN) | (10,10) | (NaN,NaN)
+ (10,10) | (0,0) | (10,10)
+ (10,10) | (-10,0) | (0,10)
+ (10,10) | (-3,4) | (7,14)
+ (10,10) | (5.1,34.5) | (15.1,44.5)
+ (10,10) | (-5,-12) | (5,-2)
+ (10,10) | (1e-300,-1e-300) | (10,10)
+ (10,10) | (1e+300,Infinity) | (1e+300,Infinity)
+ (10,10) | (Infinity,1e+300) | (Infinity,1e+300)
+ (10,10) | (NaN,NaN) | (NaN,NaN)
+ (10,10) | (10,10) | (20,20)
+(100 rows)
+
+-- Subtract point
+SELECT p1.f1, p2.f1, p1.f1 - p2.f1 FROM POINT_TBL p1, POINT_TBL p2;
+ f1 | f1 | ?column?
+-------------------+-------------------+----------------------
+ (0,0) | (0,0) | (0,0)
+ (0,0) | (-10,0) | (10,0)
+ (0,0) | (-3,4) | (3,-4)
+ (0,0) | (5.1,34.5) | (-5.1,-34.5)
+ (0,0) | (-5,-12) | (5,12)
+ (0,0) | (1e-300,-1e-300) | (-1e-300,1e-300)
+ (0,0) | (1e+300,Infinity) | (-1e+300,-Infinity)
+ (0,0) | (Infinity,1e+300) | (-Infinity,-1e+300)
+ (0,0) | (NaN,NaN) | (NaN,NaN)
+ (0,0) | (10,10) | (-10,-10)
+ (-10,0) | (0,0) | (-10,0)
+ (-10,0) | (-10,0) | (0,0)
+ (-10,0) | (-3,4) | (-7,-4)
+ (-10,0) | (5.1,34.5) | (-15.1,-34.5)
+ (-10,0) | (-5,-12) | (-5,12)
+ (-10,0) | (1e-300,-1e-300) | (-10,1e-300)
+ (-10,0) | (1e+300,Infinity) | (-1e+300,-Infinity)
+ (-10,0) | (Infinity,1e+300) | (-Infinity,-1e+300)
+ (-10,0) | (NaN,NaN) | (NaN,NaN)
+ (-10,0) | (10,10) | (-20,-10)
+ (-3,4) | (0,0) | (-3,4)
+ (-3,4) | (-10,0) | (7,4)
+ (-3,4) | (-3,4) | (0,0)
+ (-3,4) | (5.1,34.5) | (-8.1,-30.5)
+ (-3,4) | (-5,-12) | (2,16)
+ (-3,4) | (1e-300,-1e-300) | (-3,4)
+ (-3,4) | (1e+300,Infinity) | (-1e+300,-Infinity)
+ (-3,4) | (Infinity,1e+300) | (-Infinity,-1e+300)
+ (-3,4) | (NaN,NaN) | (NaN,NaN)
+ (-3,4) | (10,10) | (-13,-6)
+ (5.1,34.5) | (0,0) | (5.1,34.5)
+ (5.1,34.5) | (-10,0) | (15.1,34.5)
+ (5.1,34.5) | (-3,4) | (8.1,30.5)
+ (5.1,34.5) | (5.1,34.5) | (0,0)
+ (5.1,34.5) | (-5,-12) | (10.1,46.5)
+ (5.1,34.5) | (1e-300,-1e-300) | (5.1,34.5)
+ (5.1,34.5) | (1e+300,Infinity) | (-1e+300,-Infinity)
+ (5.1,34.5) | (Infinity,1e+300) | (-Infinity,-1e+300)
+ (5.1,34.5) | (NaN,NaN) | (NaN,NaN)
+ (5.1,34.5) | (10,10) | (-4.9,24.5)
+ (-5,-12) | (0,0) | (-5,-12)
+ (-5,-12) | (-10,0) | (5,-12)
+ (-5,-12) | (-3,4) | (-2,-16)
+ (-5,-12) | (5.1,34.5) | (-10.1,-46.5)
+ (-5,-12) | (-5,-12) | (0,0)
+ (-5,-12) | (1e-300,-1e-300) | (-5,-12)
+ (-5,-12) | (1e+300,Infinity) | (-1e+300,-Infinity)
+ (-5,-12) | (Infinity,1e+300) | (-Infinity,-1e+300)
+ (-5,-12) | (NaN,NaN) | (NaN,NaN)
+ (-5,-12) | (10,10) | (-15,-22)
+ (1e-300,-1e-300) | (0,0) | (1e-300,-1e-300)
+ (1e-300,-1e-300) | (-10,0) | (10,-1e-300)
+ (1e-300,-1e-300) | (-3,4) | (3,-4)
+ (1e-300,-1e-300) | (5.1,34.5) | (-5.1,-34.5)
+ (1e-300,-1e-300) | (-5,-12) | (5,12)
+ (1e-300,-1e-300) | (1e-300,-1e-300) | (0,0)
+ (1e-300,-1e-300) | (1e+300,Infinity) | (-1e+300,-Infinity)
+ (1e-300,-1e-300) | (Infinity,1e+300) | (-Infinity,-1e+300)
+ (1e-300,-1e-300) | (NaN,NaN) | (NaN,NaN)
+ (1e-300,-1e-300) | (10,10) | (-10,-10)
+ (1e+300,Infinity) | (0,0) | (1e+300,Infinity)
+ (1e+300,Infinity) | (-10,0) | (1e+300,Infinity)
+ (1e+300,Infinity) | (-3,4) | (1e+300,Infinity)
+ (1e+300,Infinity) | (5.1,34.5) | (1e+300,Infinity)
+ (1e+300,Infinity) | (-5,-12) | (1e+300,Infinity)
+ (1e+300,Infinity) | (1e-300,-1e-300) | (1e+300,Infinity)
+ (1e+300,Infinity) | (1e+300,Infinity) | (0,NaN)
+ (1e+300,Infinity) | (Infinity,1e+300) | (-Infinity,Infinity)
+ (1e+300,Infinity) | (NaN,NaN) | (NaN,NaN)
+ (1e+300,Infinity) | (10,10) | (1e+300,Infinity)
+ (Infinity,1e+300) | (0,0) | (Infinity,1e+300)
+ (Infinity,1e+300) | (-10,0) | (Infinity,1e+300)
+ (Infinity,1e+300) | (-3,4) | (Infinity,1e+300)
+ (Infinity,1e+300) | (5.1,34.5) | (Infinity,1e+300)
+ (Infinity,1e+300) | (-5,-12) | (Infinity,1e+300)
+ (Infinity,1e+300) | (1e-300,-1e-300) | (Infinity,1e+300)
+ (Infinity,1e+300) | (1e+300,Infinity) | (Infinity,-Infinity)
+ (Infinity,1e+300) | (Infinity,1e+300) | (NaN,0)
+ (Infinity,1e+300) | (NaN,NaN) | (NaN,NaN)
+ (Infinity,1e+300) | (10,10) | (Infinity,1e+300)
+ (NaN,NaN) | (0,0) | (NaN,NaN)
+ (NaN,NaN) | (-10,0) | (NaN,NaN)
+ (NaN,NaN) | (-3,4) | (NaN,NaN)
+ (NaN,NaN) | (5.1,34.5) | (NaN,NaN)
+ (NaN,NaN) | (-5,-12) | (NaN,NaN)
+ (NaN,NaN) | (1e-300,-1e-300) | (NaN,NaN)
+ (NaN,NaN) | (1e+300,Infinity) | (NaN,NaN)
+ (NaN,NaN) | (Infinity,1e+300) | (NaN,NaN)
+ (NaN,NaN) | (NaN,NaN) | (NaN,NaN)
+ (NaN,NaN) | (10,10) | (NaN,NaN)
+ (10,10) | (0,0) | (10,10)
+ (10,10) | (-10,0) | (20,10)
+ (10,10) | (-3,4) | (13,6)
+ (10,10) | (5.1,34.5) | (4.9,-24.5)
+ (10,10) | (-5,-12) | (15,22)
+ (10,10) | (1e-300,-1e-300) | (10,10)
+ (10,10) | (1e+300,Infinity) | (-1e+300,-Infinity)
+ (10,10) | (Infinity,1e+300) | (-Infinity,-1e+300)
+ (10,10) | (NaN,NaN) | (NaN,NaN)
+ (10,10) | (10,10) | (0,0)
+(100 rows)
+
+-- Multiply with point
+SELECT p1.f1, p2.f1, p1.f1 * p2.f1 FROM POINT_TBL p1, POINT_TBL p2 WHERE p1.f1[0] BETWEEN 1 AND 1000;
+ f1 | f1 | ?column?
+------------+-------------------+-----------------------
+ (5.1,34.5) | (0,0) | (0,0)
+ (10,10) | (0,0) | (0,0)
+ (5.1,34.5) | (-10,0) | (-51,-345)
+ (10,10) | (-10,0) | (-100,-100)
+ (5.1,34.5) | (-3,4) | (-153.3,-83.1)
+ (10,10) | (-3,4) | (-70,10)
+ (5.1,34.5) | (5.1,34.5) | (-1164.24,351.9)
+ (10,10) | (5.1,34.5) | (-294,396)
+ (5.1,34.5) | (-5,-12) | (388.5,-233.7)
+ (10,10) | (-5,-12) | (70,-170)
+ (5.1,34.5) | (1e-300,-1e-300) | (3.96e-299,2.94e-299)
+ (10,10) | (1e-300,-1e-300) | (2e-299,0)
+ (5.1,34.5) | (1e+300,Infinity) | (-Infinity,Infinity)
+ (10,10) | (1e+300,Infinity) | (-Infinity,Infinity)
+ (5.1,34.5) | (Infinity,1e+300) | (Infinity,Infinity)
+ (10,10) | (Infinity,1e+300) | (Infinity,Infinity)
+ (5.1,34.5) | (NaN,NaN) | (NaN,NaN)
+ (10,10) | (NaN,NaN) | (NaN,NaN)
+ (5.1,34.5) | (10,10) | (-294,396)
+ (10,10) | (10,10) | (0,200)
+(20 rows)
+
+-- Underflow error
+SELECT p1.f1, p2.f1, p1.f1 * p2.f1 FROM POINT_TBL p1, POINT_TBL p2 WHERE p1.f1[0] < 1;
+ERROR: value out of range: underflow
+-- 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;
+ f1 | f1 | ?column?
+-------------------+------------+-------------------------------------------
+ (0,0) | (5.1,34.5) | (0,0)
+ (0,0) | (10,10) | (0,0)
+ (-10,0) | (5.1,34.5) | (-0.0419318237877,0.283656455034)
+ (-10,0) | (10,10) | (-0.5,0.5)
+ (-3,4) | (5.1,34.5) | (0.100883034877,0.101869666025)
+ (-3,4) | (10,10) | (0.05,0.35)
+ (5.1,34.5) | (5.1,34.5) | (1,0)
+ (5.1,34.5) | (10,10) | (1.98,1.47)
+ (-5,-12) | (5.1,34.5) | (-0.361353657935,0.0915100389719)
+ (-5,-12) | (10,10) | (-0.85,-0.35)
+ (1e-300,-1e-300) | (5.1,34.5) | (-2.41724631247e-302,-3.25588278822e-302)
+ (1e-300,-1e-300) | (10,10) | (0,-1e-301)
+ (1e+300,Infinity) | (5.1,34.5) | (Infinity,Infinity)
+ (1e+300,Infinity) | (10,10) | (Infinity,Infinity)
+ (Infinity,1e+300) | (5.1,34.5) | (Infinity,-Infinity)
+ (Infinity,1e+300) | (10,10) | (Infinity,-Infinity)
+ (NaN,NaN) | (5.1,34.5) | (NaN,NaN)
+ (NaN,NaN) | (10,10) | (NaN,NaN)
+ (10,10) | (5.1,34.5) | (0.325588278822,-0.241724631247)
+ (10,10) | (10,10) | (1,0)
+(20 rows)
+
+-- Overflow error
+SELECT p1.f1, p2.f1, p1.f1 / p2.f1 FROM POINT_TBL p1, POINT_TBL p2 WHERE p2.f1[0] > 1000;
+ERROR: value out of range: overflow
+-- 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;
+ERROR: division by zero
+-- 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;
+ f1 | s | dist_pl | dist_lp
+-------------------+---------------------------------------+--------------------+--------------------
+ (0,0) | {0,-1,5} | 5 | 5
+ (0,0) | {1,0,5} | 5 | 5
+ (0,0) | {0,3,0} | 0 | 0
+ (0,0) | {1,-1,0} | 0 | 0
+ (0,0) | {-0.4,-1,-6} | 5.57086014531 | 5.57086014531
+ (0,0) | {-0.000184615384615,-1,15.3846153846} | 15.3846151224 | 15.3846151224
+ (0,0) | {3,NaN,5} | NaN | NaN
+ (0,0) | {NaN,NaN,NaN} | NaN | NaN
+ (0,0) | {0,-1,3} | 3 | 3
+ (0,0) | {-1,0,3} | 3 | 3
+ (-10,0) | {0,-1,5} | 5 | 5
+ (-10,0) | {1,0,5} | 5 | 5
+ (-10,0) | {0,3,0} | 0 | 0
+ (-10,0) | {1,-1,0} | 7.07106781187 | 7.07106781187
+ (-10,0) | {-0.4,-1,-6} | 1.85695338177 | 1.85695338177
+ (-10,0) | {-0.000184615384615,-1,15.3846153846} | 15.3864612763 | 15.3864612763
+ (-10,0) | {3,NaN,5} | NaN | NaN
+ (-10,0) | {NaN,NaN,NaN} | NaN | NaN
+ (-10,0) | {0,-1,3} | 3 | 3
+ (-10,0) | {-1,0,3} | 13 | 13
+ (-3,4) | {0,-1,5} | 1 | 1
+ (-3,4) | {1,0,5} | 2 | 2
+ (-3,4) | {0,3,0} | 4 | 4
+ (-3,4) | {1,-1,0} | 4.94974746831 | 4.94974746831
+ (-3,4) | {-0.4,-1,-6} | 8.17059487979 | 8.17059487979
+ (-3,4) | {-0.000184615384615,-1,15.3846153846} | 11.3851690368 | 11.3851690368
+ (-3,4) | {3,NaN,5} | NaN | NaN
+ (-3,4) | {NaN,NaN,NaN} | NaN | NaN
+ (-3,4) | {0,-1,3} | 1 | 1
+ (-3,4) | {-1,0,3} | 6 | 6
+ (5.1,34.5) | {0,-1,5} | 29.5 | 29.5
+ (5.1,34.5) | {1,0,5} | 10.1 | 10.1
+ (5.1,34.5) | {0,3,0} | 34.5 | 34.5
+ (5.1,34.5) | {1,-1,0} | 20.7889393669 | 20.7889393669
+ (5.1,34.5) | {-0.4,-1,-6} | 39.4973984303 | 39.4973984303
+ (5.1,34.5) | {-0.000184615384615,-1,15.3846153846} | 19.1163258281 | 19.1163258281
+ (5.1,34.5) | {3,NaN,5} | NaN | NaN
+ (5.1,34.5) | {NaN,NaN,NaN} | NaN | NaN
+ (5.1,34.5) | {0,-1,3} | 31.5 | 31.5
+ (5.1,34.5) | {-1,0,3} | 2.1 | 2.1
+ (-5,-12) | {0,-1,5} | 17 | 17
+ (-5,-12) | {1,0,5} | 0 | 0
+ (-5,-12) | {0,3,0} | 12 | 12
+ (-5,-12) | {1,-1,0} | 4.94974746831 | 4.94974746831
+ (-5,-12) | {-0.4,-1,-6} | 7.42781352708 | 7.42781352708
+ (-5,-12) | {-0.000184615384615,-1,15.3846153846} | 27.3855379948 | 27.3855379948
+ (-5,-12) | {3,NaN,5} | NaN | NaN
+ (-5,-12) | {NaN,NaN,NaN} | NaN | NaN
+ (-5,-12) | {0,-1,3} | 15 | 15
+ (-5,-12) | {-1,0,3} | 8 | 8
+ (1e-300,-1e-300) | {0,-1,5} | 5 | 5
+ (1e-300,-1e-300) | {1,0,5} | 5 | 5
+ (1e-300,-1e-300) | {0,3,0} | 1e-300 | 1e-300
+ (1e-300,-1e-300) | {1,-1,0} | 1.41421356237e-300 | 1.41421356237e-300
+ (1e-300,-1e-300) | {-0.4,-1,-6} | 5.57086014531 | 5.57086014531
+ (1e-300,-1e-300) | {-0.000184615384615,-1,15.3846153846} | 15.3846151224 | 15.3846151224
+ (1e-300,-1e-300) | {3,NaN,5} | NaN | NaN
+ (1e-300,-1e-300) | {NaN,NaN,NaN} | NaN | NaN
+ (1e-300,-1e-300) | {0,-1,3} | 3 | 3
+ (1e-300,-1e-300) | {-1,0,3} | 3 | 3
+ (1e+300,Infinity) | {0,-1,5} | Infinity | Infinity
+ (1e+300,Infinity) | {1,0,5} | NaN | NaN
+ (1e+300,Infinity) | {0,3,0} | Infinity | Infinity
+ (1e+300,Infinity) | {1,-1,0} | Infinity | Infinity
+ (1e+300,Infinity) | {-0.4,-1,-6} | Infinity | Infinity
+ (1e+300,Infinity) | {-0.000184615384615,-1,15.3846153846} | Infinity | Infinity
+ (1e+300,Infinity) | {3,NaN,5} | NaN | NaN
+ (1e+300,Infinity) | {NaN,NaN,NaN} | NaN | NaN
+ (1e+300,Infinity) | {0,-1,3} | Infinity | Infinity
+ (1e+300,Infinity) | {-1,0,3} | NaN | NaN
+ (Infinity,1e+300) | {0,-1,5} | NaN | NaN
+ (Infinity,1e+300) | {1,0,5} | Infinity | Infinity
+ (Infinity,1e+300) | {0,3,0} | NaN | NaN
+ (Infinity,1e+300) | {1,-1,0} | NaN | NaN
+ (Infinity,1e+300) | {-0.4,-1,-6} | NaN | NaN
+ (Infinity,1e+300) | {-0.000184615384615,-1,15.3846153846} | NaN | NaN
+ (Infinity,1e+300) | {3,NaN,5} | NaN | NaN
+ (Infinity,1e+300) | {NaN,NaN,NaN} | NaN | NaN
+ (Infinity,1e+300) | {0,-1,3} | NaN | NaN
+ (Infinity,1e+300) | {-1,0,3} | Infinity | Infinity
+ (NaN,NaN) | {0,-1,5} | NaN | NaN
+ (NaN,NaN) | {1,0,5} | NaN | NaN
+ (NaN,NaN) | {0,3,0} | NaN | NaN
+ (NaN,NaN) | {1,-1,0} | NaN | NaN
+ (NaN,NaN) | {-0.4,-1,-6} | NaN | NaN
+ (NaN,NaN) | {-0.000184615384615,-1,15.3846153846} | NaN | NaN
+ (NaN,NaN) | {3,NaN,5} | NaN | NaN
+ (NaN,NaN) | {NaN,NaN,NaN} | NaN | NaN
+ (NaN,NaN) | {0,-1,3} | NaN | NaN
+ (NaN,NaN) | {-1,0,3} | NaN | NaN
+ (10,10) | {0,-1,5} | 5 | 5
+ (10,10) | {1,0,5} | 15 | 15
+ (10,10) | {0,3,0} | 10 | 10
+ (10,10) | {1,-1,0} | 0 | 0
+ (10,10) | {-0.4,-1,-6} | 18.5695338177 | 18.5695338177
+ (10,10) | {-0.000184615384615,-1,15.3846153846} | 5.38276913903 | 5.38276913903
+ (10,10) | {3,NaN,5} | NaN | NaN
+ (10,10) | {NaN,NaN,NaN} | NaN | NaN
+ (10,10) | {0,-1,3} | 7 | 7
+ (10,10) | {-1,0,3} | 7 | 7
+(100 rows)
+
+-- 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;
+ f1 | s | dist_ps | dist_sp
+-------------------+-------------------------------+--------------------+--------------------
+ (0,0) | [(1,2),(3,4)] | 2.2360679775 | 2.2360679775
+ (0,0) | [(0,0),(6,6)] | 0 | 0
+ (0,0) | [(10,-10),(-3,-4)] | 4.88901207039 | 4.88901207039
+ (0,0) | [(-1000000,200),(300000,-40)] | 15.3846151224 | 15.3846151224
+ (0,0) | [(11,22),(33,44)] | 24.5967477525 | 24.5967477525
+ (0,0) | [(-10,2),(-10,3)] | 10.1980390272 | 10.1980390272
+ (0,0) | [(0,-20),(30,-20)] | 20 | 20
+ (0,0) | [(NaN,1),(NaN,90)] | NaN | NaN
+ (-10,0) | [(1,2),(3,4)] | 11.1803398875 | 11.1803398875
+ (-10,0) | [(0,0),(6,6)] | 10 | 10
+ (-10,0) | [(10,-10),(-3,-4)] | 8.0622577483 | 8.0622577483
+ (-10,0) | [(-1000000,200),(300000,-40)] | 15.3864612763 | 15.3864612763
+ (-10,0) | [(11,22),(33,44)] | 30.4138126515 | 30.4138126515
+ (-10,0) | [(-10,2),(-10,3)] | 2 | 2
+ (-10,0) | [(0,-20),(30,-20)] | 22.360679775 | 22.360679775
+ (-10,0) | [(NaN,1),(NaN,90)] | NaN | NaN
+ (-3,4) | [(1,2),(3,4)] | 4.472135955 | 4.472135955
+ (-3,4) | [(0,0),(6,6)] | 4.94974746831 | 4.94974746831
+ (-3,4) | [(10,-10),(-3,-4)] | 8 | 8
+ (-3,4) | [(-1000000,200),(300000,-40)] | 11.3851690367 | 11.3851690367
+ (-3,4) | [(11,22),(33,44)] | 22.803508502 | 22.803508502
+ (-3,4) | [(-10,2),(-10,3)] | 7.07106781187 | 7.07106781187
+ (-3,4) | [(0,-20),(30,-20)] | 24.1867732449 | 24.1867732449
+ (-3,4) | [(NaN,1),(NaN,90)] | NaN | NaN
+ (5.1,34.5) | [(1,2),(3,4)] | 30.5722096028 | 30.5722096028
+ (5.1,34.5) | [(0,0),(6,6)] | 28.5142069853 | 28.5142069853
+ (5.1,34.5) | [(10,-10),(-3,-4)] | 39.3428519556 | 39.3428519556
+ (5.1,34.5) | [(-1000000,200),(300000,-40)] | 19.1163258281 | 19.1163258281
+ (5.1,34.5) | [(11,22),(33,44)] | 13.0107647738 | 13.0107647738
+ (5.1,34.5) | [(-10,2),(-10,3)] | 34.932220084 | 34.932220084
+ (5.1,34.5) | [(0,-20),(30,-20)] | 54.5 | 54.5
+ (5.1,34.5) | [(NaN,1),(NaN,90)] | NaN | NaN
+ (-5,-12) | [(1,2),(3,4)] | 15.2315462117 | 15.2315462117
+ (-5,-12) | [(0,0),(6,6)] | 13 | 13
+ (-5,-12) | [(10,-10),(-3,-4)] | 8.10179143093 | 8.10179143093
+ (-5,-12) | [(-1000000,200),(300000,-40)] | 27.3855379949 | 27.3855379949
+ (-5,-12) | [(11,22),(33,44)] | 37.5765884561 | 37.5765884561
+ (-5,-12) | [(-10,2),(-10,3)] | 14.8660687473 | 14.8660687473
+ (-5,-12) | [(0,-20),(30,-20)] | 9.43398113206 | 9.43398113206
+ (-5,-12) | [(NaN,1),(NaN,90)] | NaN | NaN
+ (1e-300,-1e-300) | [(1,2),(3,4)] | 2.2360679775 | 2.2360679775
+ (1e-300,-1e-300) | [(0,0),(6,6)] | 1.41421356237e-300 | 1.41421356237e-300
+ (1e-300,-1e-300) | [(10,-10),(-3,-4)] | 4.88901207039 | 4.88901207039
+ (1e-300,-1e-300) | [(-1000000,200),(300000,-40)] | 15.3846151224 | 15.3846151224
+ (1e-300,-1e-300) | [(11,22),(33,44)] | 24.5967477525 | 24.5967477525
+ (1e-300,-1e-300) | [(-10,2),(-10,3)] | 10.1980390272 | 10.1980390272
+ (1e-300,-1e-300) | [(0,-20),(30,-20)] | 20 | 20
+ (1e-300,-1e-300) | [(NaN,1),(NaN,90)] | NaN | NaN
+ (1e+300,Infinity) | [(1,2),(3,4)] | Infinity | Infinity
+ (1e+300,Infinity) | [(0,0),(6,6)] | Infinity | Infinity
+ (1e+300,Infinity) | [(10,-10),(-3,-4)] | Infinity | Infinity
+ (1e+300,Infinity) | [(-1000000,200),(300000,-40)] | Infinity | Infinity
+ (1e+300,Infinity) | [(11,22),(33,44)] | Infinity | Infinity
+ (1e+300,Infinity) | [(-10,2),(-10,3)] | Infinity | Infinity
+ (1e+300,Infinity) | [(0,-20),(30,-20)] | Infinity | Infinity
+ (1e+300,Infinity) | [(NaN,1),(NaN,90)] | Infinity | Infinity
+ (Infinity,1e+300) | [(1,2),(3,4)] | Infinity | Infinity
+ (Infinity,1e+300) | [(0,0),(6,6)] | Infinity | Infinity
+ (Infinity,1e+300) | [(10,-10),(-3,-4)] | Infinity | Infinity
+ (Infinity,1e+300) | [(-1000000,200),(300000,-40)] | Infinity | Infinity
+ (Infinity,1e+300) | [(11,22),(33,44)] | Infinity | Infinity
+ (Infinity,1e+300) | [(-10,2),(-10,3)] | Infinity | Infinity
+ (Infinity,1e+300) | [(0,-20),(30,-20)] | Infinity | Infinity
+ (Infinity,1e+300) | [(NaN,1),(NaN,90)] | NaN | NaN
+ (NaN,NaN) | [(1,2),(3,4)] | NaN | NaN
+ (NaN,NaN) | [(0,0),(6,6)] | NaN | NaN
+ (NaN,NaN) | [(10,-10),(-3,-4)] | NaN | NaN
+ (NaN,NaN) | [(-1000000,200),(300000,-40)] | NaN | NaN
+ (NaN,NaN) | [(11,22),(33,44)] | NaN | NaN
+ (NaN,NaN) | [(-10,2),(-10,3)] | NaN | NaN
+ (NaN,NaN) | [(0,-20),(30,-20)] | NaN | NaN
+ (NaN,NaN) | [(NaN,1),(NaN,90)] | NaN | NaN
+ (10,10) | [(1,2),(3,4)] | 9.21954445729 | 9.21954445729
+ (10,10) | [(0,0),(6,6)] | 5.65685424949 | 5.65685424949
+ (10,10) | [(10,-10),(-3,-4)] | 18.15918769 | 18.15918769
+ (10,10) | [(-1000000,200),(300000,-40)] | 5.38276913904 | 5.38276913904
+ (10,10) | [(11,22),(33,44)] | 12.0415945788 | 12.0415945788
+ (10,10) | [(-10,2),(-10,3)] | 21.1896201004 | 21.1896201004
+ (10,10) | [(0,-20),(30,-20)] | 30 | 30
+ (10,10) | [(NaN,1),(NaN,90)] | NaN | NaN
+(80 rows)
+
+-- 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;
+ f1 | f1 | dist_pb | dist_bp
+-------------------+---------------------+--------------------+--------------------
+ (0,0) | (2,2),(0,0) | 0 | 0
+ (0,0) | (3,3),(1,1) | 1.41421356237 | 1.41421356237
+ (0,0) | (-2,2),(-8,-10) | 2 | 2
+ (0,0) | (2.5,3.5),(2.5,2.5) | 3.53553390593 | 3.53553390593
+ (0,0) | (3,3),(3,3) | 4.24264068712 | 4.24264068712
+ (-10,0) | (2,2),(0,0) | 10 | 10
+ (-10,0) | (3,3),(1,1) | 11.0453610172 | 11.0453610172
+ (-10,0) | (-2,2),(-8,-10) | 2 | 2
+ (-10,0) | (2.5,3.5),(2.5,2.5) | 12.747548784 | 12.747548784
+ (-10,0) | (3,3),(3,3) | 13.3416640641 | 13.3416640641
+ (-3,4) | (2,2),(0,0) | 3.60555127546 | 3.60555127546
+ (-3,4) | (3,3),(1,1) | 4.12310562562 | 4.12310562562
+ (-3,4) | (-2,2),(-8,-10) | 2 | 2
+ (-3,4) | (2.5,3.5),(2.5,2.5) | 5.52268050859 | 5.52268050859
+ (-3,4) | (3,3),(3,3) | 6.0827625303 | 6.0827625303
+ (5.1,34.5) | (2,2),(0,0) | 32.6475113906 | 32.6475113906
+ (5.1,34.5) | (3,3),(1,1) | 31.5699223946 | 31.5699223946
+ (5.1,34.5) | (-2,2),(-8,-10) | 33.2664996656 | 33.2664996656
+ (5.1,34.5) | (2.5,3.5),(2.5,2.5) | 31.108841187 | 31.108841187
+ (5.1,34.5) | (3,3),(3,3) | 31.5699223946 | 31.5699223946
+ (-5,-12) | (2,2),(0,0) | 13 | 13
+ (-5,-12) | (3,3),(1,1) | 14.3178210633 | 14.3178210633
+ (-5,-12) | (-2,2),(-8,-10) | 2 | 2
+ (-5,-12) | (2.5,3.5),(2.5,2.5) | 16.3248277173 | 16.3248277173
+ (-5,-12) | (3,3),(3,3) | 17 | 17
+ (1e-300,-1e-300) | (2,2),(0,0) | 1.41421356237e-300 | 1.41421356237e-300
+ (1e-300,-1e-300) | (3,3),(1,1) | 1.41421356237 | 1.41421356237
+ (1e-300,-1e-300) | (-2,2),(-8,-10) | 2 | 2
+ (1e-300,-1e-300) | (2.5,3.5),(2.5,2.5) | 3.53553390593 | 3.53553390593
+ (1e-300,-1e-300) | (3,3),(3,3) | 4.24264068712 | 4.24264068712
+ (1e+300,Infinity) | (2,2),(0,0) | Infinity | Infinity
+ (1e+300,Infinity) | (3,3),(1,1) | Infinity | Infinity
+ (1e+300,Infinity) | (-2,2),(-8,-10) | Infinity | Infinity
+ (1e+300,Infinity) | (2.5,3.5),(2.5,2.5) | Infinity | Infinity
+ (1e+300,Infinity) | (3,3),(3,3) | Infinity | Infinity
+ (Infinity,1e+300) | (2,2),(0,0) | Infinity | Infinity
+ (Infinity,1e+300) | (3,3),(1,1) | Infinity | Infinity
+ (Infinity,1e+300) | (-2,2),(-8,-10) | Infinity | Infinity
+ (Infinity,1e+300) | (2.5,3.5),(2.5,2.5) | Infinity | Infinity
+ (Infinity,1e+300) | (3,3),(3,3) | Infinity | Infinity
+ (NaN,NaN) | (2,2),(0,0) | NaN | NaN
+ (NaN,NaN) | (3,3),(1,1) | NaN | NaN
+ (NaN,NaN) | (-2,2),(-8,-10) | NaN | NaN
+ (NaN,NaN) | (2.5,3.5),(2.5,2.5) | NaN | NaN
+ (NaN,NaN) | (3,3),(3,3) | NaN | NaN
+ (10,10) | (2,2),(0,0) | 11.313708499 | 11.313708499
+ (10,10) | (3,3),(1,1) | 9.89949493661 | 9.89949493661
+ (10,10) | (-2,2),(-8,-10) | 14.4222051019 | 14.4222051019
+ (10,10) | (2.5,3.5),(2.5,2.5) | 9.92471662064 | 9.92471662064
+ (10,10) | (3,3),(3,3) | 9.89949493661 | 9.89949493661
+(50 rows)
+
+-- 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;
+ f1 | f1 | dist_ppath | dist_pathp
+-------------------+---------------------------+--------------------+--------------------
+ (0,0) | [(1,2),(3,4)] | 2.2360679775 | 2.2360679775
+ (0,0) | ((1,2),(3,4)) | 2.2360679775 | 2.2360679775
+ (0,0) | [(0,0),(3,0),(4,5),(1,6)] | 0 | 0
+ (0,0) | ((1,2),(3,4)) | 2.2360679775 | 2.2360679775
+ (0,0) | ((1,2),(3,4)) | 2.2360679775 | 2.2360679775
+ (0,0) | [(1,2),(3,4)] | 2.2360679775 | 2.2360679775
+ (0,0) | ((10,20)) | 22.360679775 | 22.360679775
+ (0,0) | [(11,12),(13,14)] | 16.2788205961 | 16.2788205961
+ (0,0) | ((11,12),(13,14)) | 16.2788205961 | 16.2788205961
+ (-10,0) | [(1,2),(3,4)] | 11.1803398875 | 11.1803398875
+ (-10,0) | ((1,2),(3,4)) | 11.1803398875 | 11.1803398875
+ (-10,0) | [(0,0),(3,0),(4,5),(1,6)] | 10 | 10
+ (-10,0) | ((1,2),(3,4)) | 11.1803398875 | 11.1803398875
+ (-10,0) | ((1,2),(3,4)) | 11.1803398875 | 11.1803398875
+ (-10,0) | [(1,2),(3,4)] | 11.1803398875 | 11.1803398875
+ (-10,0) | ((10,20)) | 28.2842712475 | 28.2842712475
+ (-10,0) | [(11,12),(13,14)] | 24.1867732449 | 24.1867732449
+ (-10,0) | ((11,12),(13,14)) | 24.1867732449 | 24.1867732449
+ (-3,4) | [(1,2),(3,4)] | 4.472135955 | 4.472135955
+ (-3,4) | ((1,2),(3,4)) | 4.472135955 | 4.472135955
+ (-3,4) | [(0,0),(3,0),(4,5),(1,6)] | 4.472135955 | 4.472135955
+ (-3,4) | ((1,2),(3,4)) | 4.472135955 | 4.472135955
+ (-3,4) | ((1,2),(3,4)) | 4.472135955 | 4.472135955
+ (-3,4) | [(1,2),(3,4)] | 4.472135955 | 4.472135955
+ (-3,4) | ((10,20)) | 20.6155281281 | 20.6155281281
+ (-3,4) | [(11,12),(13,14)] | 16.1245154966 | 16.1245154966
+ (-3,4) | ((11,12),(13,14)) | 16.1245154966 | 16.1245154966
+ (5.1,34.5) | [(1,2),(3,4)] | 30.5722096028 | 30.5722096028
+ (5.1,34.5) | ((1,2),(3,4)) | 30.5722096028 | 30.5722096028
+ (5.1,34.5) | [(0,0),(3,0),(4,5),(1,6)] | 28.793402022 | 28.793402022
+ (5.1,34.5) | ((1,2),(3,4)) | 30.5722096028 | 30.5722096028
+ (5.1,34.5) | ((1,2),(3,4)) | 30.5722096028 | 30.5722096028
+ (5.1,34.5) | [(1,2),(3,4)] | 30.5722096028 | 30.5722096028
+ (5.1,34.5) | ((10,20)) | 15.3055545473 | 15.3055545473
+ (5.1,34.5) | [(11,12),(13,14)] | 21.9695243462 | 21.9695243462
+ (5.1,34.5) | ((11,12),(13,14)) | 21.9695243462 | 21.9695243462
+ (-5,-12) | [(1,2),(3,4)] | 15.2315462117 | 15.2315462117
+ (-5,-12) | ((1,2),(3,4)) | 15.2315462117 | 15.2315462117
+ (-5,-12) | [(0,0),(3,0),(4,5),(1,6)] | 13 | 13
+ (-5,-12) | ((1,2),(3,4)) | 15.2315462117 | 15.2315462117
+ (-5,-12) | ((1,2),(3,4)) | 15.2315462117 | 15.2315462117
+ (-5,-12) | [(1,2),(3,4)] | 15.2315462117 | 15.2315462117
+ (-5,-12) | ((10,20)) | 35.3411940941 | 35.3411940941
+ (-5,-12) | [(11,12),(13,14)] | 28.8444102037 | 28.8444102037
+ (-5,-12) | ((11,12),(13,14)) | 28.8444102037 | 28.8444102037
+ (1e-300,-1e-300) | [(1,2),(3,4)] | 2.2360679775 | 2.2360679775
+ (1e-300,-1e-300) | ((1,2),(3,4)) | 2.2360679775 | 2.2360679775
+ (1e-300,-1e-300) | [(0,0),(3,0),(4,5),(1,6)] | 1.41421356237e-300 | 1.41421356237e-300
+ (1e-300,-1e-300) | ((1,2),(3,4)) | 2.2360679775 | 2.2360679775
+ (1e-300,-1e-300) | ((1,2),(3,4)) | 2.2360679775 | 2.2360679775
+ (1e-300,-1e-300) | [(1,2),(3,4)] | 2.2360679775 | 2.2360679775
+ (1e-300,-1e-300) | ((10,20)) | 22.360679775 | 22.360679775
+ (1e-300,-1e-300) | [(11,12),(13,14)] | 16.2788205961 | 16.2788205961
+ (1e-300,-1e-300) | ((11,12),(13,14)) | 16.2788205961 | 16.2788205961
+ (1e+300,Infinity) | [(1,2),(3,4)] | Infinity | Infinity
+ (1e+300,Infinity) | ((1,2),(3,4)) | Infinity | Infinity
+ (1e+300,Infinity) | [(0,0),(3,0),(4,5),(1,6)] | Infinity | Infinity
+ (1e+300,Infinity) | ((1,2),(3,4)) | Infinity | Infinity
+ (1e+300,Infinity) | ((1,2),(3,4)) | Infinity | Infinity
+ (1e+300,Infinity) | [(1,2),(3,4)] | Infinity | Infinity
+ (1e+300,Infinity) | ((10,20)) | Infinity | Infinity
+ (1e+300,Infinity) | [(11,12),(13,14)] | Infinity | Infinity
+ (1e+300,Infinity) | ((11,12),(13,14)) | Infinity | Infinity
+ (Infinity,1e+300) | [(1,2),(3,4)] | Infinity | Infinity
+ (Infinity,1e+300) | ((1,2),(3,4)) | Infinity | Infinity
+ (Infinity,1e+300) | [(0,0),(3,0),(4,5),(1,6)] | Infinity | Infinity
+ (Infinity,1e+300) | ((1,2),(3,4)) | Infinity | Infinity
+ (Infinity,1e+300) | ((1,2),(3,4)) | Infinity | Infinity
+ (Infinity,1e+300) | [(1,2),(3,4)] | Infinity | Infinity
+ (Infinity,1e+300) | ((10,20)) | Infinity | Infinity
+ (Infinity,1e+300) | [(11,12),(13,14)] | Infinity | Infinity
+ (Infinity,1e+300) | ((11,12),(13,14)) | Infinity | Infinity
+ (NaN,NaN) | [(1,2),(3,4)] | NaN | NaN
+ (NaN,NaN) | ((1,2),(3,4)) | NaN | NaN
+ (NaN,NaN) | [(0,0),(3,0),(4,5),(1,6)] | NaN | NaN
+ (NaN,NaN) | ((1,2),(3,4)) | NaN | NaN
+ (NaN,NaN) | ((1,2),(3,4)) | NaN | NaN
+ (NaN,NaN) | [(1,2),(3,4)] | NaN | NaN
+ (NaN,NaN) | ((10,20)) | NaN | NaN
+ (NaN,NaN) | [(11,12),(13,14)] | NaN | NaN
+ (NaN,NaN) | ((11,12),(13,14)) | NaN | NaN
+ (10,10) | [(1,2),(3,4)] | 9.21954445729 | 9.21954445729
+ (10,10) | ((1,2),(3,4)) | 9.21954445729 | 9.21954445729
+ (10,10) | [(0,0),(3,0),(4,5),(1,6)] | 7.81024967591 | 7.81024967591
+ (10,10) | ((1,2),(3,4)) | 9.21954445729 | 9.21954445729
+ (10,10) | ((1,2),(3,4)) | 9.21954445729 | 9.21954445729
+ (10,10) | [(1,2),(3,4)] | 9.21954445729 | 9.21954445729
+ (10,10) | ((10,20)) | 10 | 10
+ (10,10) | [(11,12),(13,14)] | 2.2360679775 | 2.2360679775
+ (10,10) | ((11,12),(13,14)) | 2.2360679775 | 2.2360679775
+(90 rows)
+
+-- 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;
+ f1 | f1 | dist_ppoly | dist_polyp
+-------------------+----------------------------+---------------+---------------
+ (0,0) | ((2,0),(2,4),(0,0)) | 0 | 0
+ (0,0) | ((3,1),(3,3),(1,0)) | 1 | 1
+ (0,0) | ((1,2),(3,4),(5,6),(7,8)) | 2.2360679775 | 2.2360679775
+ (0,0) | ((7,8),(5,6),(3,4),(1,2)) | 2.2360679775 | 2.2360679775
+ (0,0) | ((1,2),(7,8),(5,6),(3,-4)) | 1.58113883008 | 1.58113883008
+ (0,0) | ((0,0)) | 0 | 0
+ (0,0) | ((0,1),(0,1)) | 1 | 1
+ (-10,0) | ((2,0),(2,4),(0,0)) | 10 | 10
+ (-10,0) | ((3,1),(3,3),(1,0)) | 11 | 11
+ (-10,0) | ((1,2),(3,4),(5,6),(7,8)) | 11.1803398875 | 11.1803398875
+ (-10,0) | ((7,8),(5,6),(3,4),(1,2)) | 11.1803398875 | 11.1803398875
+ (-10,0) | ((1,2),(7,8),(5,6),(3,-4)) | 11.1803398875 | 11.1803398875
+ (-10,0) | ((0,0)) | 10 | 10
+ (-10,0) | ((0,1),(0,1)) | 10.0498756211 | 10.0498756211
+ (-3,4) | ((2,0),(2,4),(0,0)) | 4.472135955 | 4.472135955
+ (-3,4) | ((3,1),(3,3),(1,0)) | 5.54700196225 | 5.54700196225
+ (-3,4) | ((1,2),(3,4),(5,6),(7,8)) | 4.472135955 | 4.472135955
+ (-3,4) | ((7,8),(5,6),(3,4),(1,2)) | 4.472135955 | 4.472135955
+ (-3,4) | ((1,2),(7,8),(5,6),(3,-4)) | 4.472135955 | 4.472135955
+ (-3,4) | ((0,0)) | 5 | 5
+ (-3,4) | ((0,1),(0,1)) | 4.24264068712 | 4.24264068712
+ (5.1,34.5) | ((2,0),(2,4),(0,0)) | 30.6571362002 | 30.6571362002
+ (5.1,34.5) | ((3,1),(3,3),(1,0)) | 31.5699223946 | 31.5699223946
+ (5.1,34.5) | ((1,2),(3,4),(5,6),(7,8)) | 26.5680258958 | 26.5680258958
+ (5.1,34.5) | ((7,8),(5,6),(3,4),(1,2)) | 26.5680258958 | 26.5680258958
+ (5.1,34.5) | ((1,2),(7,8),(5,6),(3,-4)) | 26.5680258958 | 26.5680258958
+ (5.1,34.5) | ((0,0)) | 34.8749193547 | 34.8749193547
+ (5.1,34.5) | ((0,1),(0,1)) | 33.8859853037 | 33.8859853037
+ (-5,-12) | ((2,0),(2,4),(0,0)) | 13 | 13
+ (-5,-12) | ((3,1),(3,3),(1,0)) | 13.416407865 | 13.416407865
+ (-5,-12) | ((1,2),(3,4),(5,6),(7,8)) | 15.2315462117 | 15.2315462117
+ (-5,-12) | ((7,8),(5,6),(3,4),(1,2)) | 15.2315462117 | 15.2315462117
+ (-5,-12) | ((1,2),(7,8),(5,6),(3,-4)) | 11.313708499 | 11.313708499
+ (-5,-12) | ((0,0)) | 13 | 13
+ (-5,-12) | ((0,1),(0,1)) | 13.9283882772 | 13.9283882772
+ (1e-300,-1e-300) | ((2,0),(2,4),(0,0)) | 0 | 0
+ (1e-300,-1e-300) | ((3,1),(3,3),(1,0)) | 1 | 1
+ (1e-300,-1e-300) | ((1,2),(3,4),(5,6),(7,8)) | 2.2360679775 | 2.2360679775
+ (1e-300,-1e-300) | ((7,8),(5,6),(3,4),(1,2)) | 2.2360679775 | 2.2360679775
+ (1e-300,-1e-300) | ((1,2),(7,8),(5,6),(3,-4)) | 1.58113883008 | 1.58113883008
+ (1e-300,-1e-300) | ((0,0)) | 0 | 0
+ (1e-300,-1e-300) | ((0,1),(0,1)) | 1 | 1
+ (1e+300,Infinity) | ((2,0),(2,4),(0,0)) | Infinity | Infinity
+ (1e+300,Infinity) | ((3,1),(3,3),(1,0)) | Infinity | Infinity
+ (1e+300,Infinity) | ((1,2),(3,4),(5,6),(7,8)) | Infinity | Infinity
+ (1e+300,Infinity) | ((7,8),(5,6),(3,4),(1,2)) | Infinity | Infinity
+ (1e+300,Infinity) | ((1,2),(7,8),(5,6),(3,-4)) | Infinity | Infinity
+ (1e+300,Infinity) | ((0,0)) | Infinity | Infinity
+ (1e+300,Infinity) | ((0,1),(0,1)) | Infinity | Infinity
+ (Infinity,1e+300) | ((2,0),(2,4),(0,0)) | Infinity | Infinity
+ (Infinity,1e+300) | ((3,1),(3,3),(1,0)) | Infinity | Infinity
+ (Infinity,1e+300) | ((1,2),(3,4),(5,6),(7,8)) | Infinity | Infinity
+ (Infinity,1e+300) | ((7,8),(5,6),(3,4),(1,2)) | Infinity | Infinity
+ (Infinity,1e+300) | ((1,2),(7,8),(5,6),(3,-4)) | Infinity | Infinity
+ (Infinity,1e+300) | ((0,0)) | Infinity | Infinity
+ (Infinity,1e+300) | ((0,1),(0,1)) | Infinity | Infinity
+ (NaN,NaN) | ((2,0),(2,4),(0,0)) | 0 | 0
+ (NaN,NaN) | ((3,1),(3,3),(1,0)) | 0 | 0
+ (NaN,NaN) | ((1,2),(3,4),(5,6),(7,8)) | 0 | 0
+ (NaN,NaN) | ((7,8),(5,6),(3,4),(1,2)) | 0 | 0
+ (NaN,NaN) | ((1,2),(7,8),(5,6),(3,-4)) | 0 | 0
+ (NaN,NaN) | ((0,0)) | 0 | 0
+ (NaN,NaN) | ((0,1),(0,1)) | 0 | 0
+ (10,10) | ((2,0),(2,4),(0,0)) | 10 | 10
+ (10,10) | ((3,1),(3,3),(1,0)) | 9.89949493661 | 9.89949493661
+ (10,10) | ((1,2),(3,4),(5,6),(7,8)) | 3.60555127546 | 3.60555127546
+ (10,10) | ((7,8),(5,6),(3,4),(1,2)) | 3.60555127546 | 3.60555127546
+ (10,10) | ((1,2),(7,8),(5,6),(3,-4)) | 3.60555127546 | 3.60555127546
+ (10,10) | ((0,0)) | 14.1421356237 | 14.1421356237
+ (10,10) | ((0,1),(0,1)) | 13.4536240471 | 13.4536240471
+(70 rows)
+
+-- 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;
+ f1 | f1 | line
+-------------------+-------------------+----------------------------------------
+ (0,0) | (-10,0) | {0,-1,0}
+ (0,0) | (-3,4) | {-1.33333333333,-1,0}
+ (0,0) | (5.1,34.5) | {6.76470588235,-1,0}
+ (0,0) | (-5,-12) | {2.4,-1,0}
+ (0,0) | (1e+300,Infinity) | {-1,0,0}
+ (0,0) | (Infinity,1e+300) | {0,-1,0}
+ (0,0) | (NaN,NaN) | {NaN,-1,NaN}
+ (0,0) | (10,10) | {1,-1,0}
+ (-10,0) | (0,0) | {0,-1,0}
+ (-10,0) | (-3,4) | {0.571428571429,-1,5.71428571429}
+ (-10,0) | (5.1,34.5) | {2.28476821192,-1,22.8476821192}
+ (-10,0) | (-5,-12) | {-2.4,-1,-24}
+ (-10,0) | (1e-300,-1e-300) | {0,-1,0}
+ (-10,0) | (1e+300,Infinity) | {-1,0,-10}
+ (-10,0) | (Infinity,1e+300) | {0,-1,0}
+ (-10,0) | (NaN,NaN) | {NaN,-1,NaN}
+ (-10,0) | (10,10) | {0.5,-1,5}
+ (-3,4) | (0,0) | {-1.33333333333,-1,0}
+ (-3,4) | (-10,0) | {0.571428571429,-1,5.71428571429}
+ (-3,4) | (5.1,34.5) | {3.76543209877,-1,15.2962962963}
+ (-3,4) | (-5,-12) | {8,-1,28}
+ (-3,4) | (1e-300,-1e-300) | {-1.33333333333,-1,0}
+ (-3,4) | (1e+300,Infinity) | {-1,0,-3}
+ (-3,4) | (Infinity,1e+300) | {0,-1,4}
+ (-3,4) | (NaN,NaN) | {NaN,-1,NaN}
+ (-3,4) | (10,10) | {0.461538461538,-1,5.38461538462}
+ (5.1,34.5) | (0,0) | {6.76470588235,-1,0}
+ (5.1,34.5) | (-10,0) | {2.28476821192,-1,22.8476821192}
+ (5.1,34.5) | (-3,4) | {3.76543209877,-1,15.2962962963}
+ (5.1,34.5) | (-5,-12) | {4.60396039604,-1,11.0198019802}
+ (5.1,34.5) | (1e-300,-1e-300) | {6.76470588235,-1,0}
+ (5.1,34.5) | (1e+300,Infinity) | {-1,0,5.1}
+ (5.1,34.5) | (Infinity,1e+300) | {0,-1,34.5}
+ (5.1,34.5) | (NaN,NaN) | {NaN,-1,NaN}
+ (5.1,34.5) | (10,10) | {-5,-1,60}
+ (-5,-12) | (0,0) | {2.4,-1,0}
+ (-5,-12) | (-10,0) | {-2.4,-1,-24}
+ (-5,-12) | (-3,4) | {8,-1,28}
+ (-5,-12) | (5.1,34.5) | {4.60396039604,-1,11.0198019802}
+ (-5,-12) | (1e-300,-1e-300) | {2.4,-1,0}
+ (-5,-12) | (1e+300,Infinity) | {-1,0,-5}
+ (-5,-12) | (Infinity,1e+300) | {0,-1,-12}
+ (-5,-12) | (NaN,NaN) | {NaN,-1,NaN}
+ (-5,-12) | (10,10) | {1.46666666667,-1,-4.66666666667}
+ (1e-300,-1e-300) | (-10,0) | {0,-1,-1e-300}
+ (1e-300,-1e-300) | (-3,4) | {-1.33333333333,-1,3.33333333333e-301}
+ (1e-300,-1e-300) | (5.1,34.5) | {6.76470588235,-1,-7.76470588235e-300}
+ (1e-300,-1e-300) | (-5,-12) | {2.4,-1,-3.4e-300}
+ (1e-300,-1e-300) | (1e+300,Infinity) | {-1,0,1e-300}
+ (1e-300,-1e-300) | (Infinity,1e+300) | {0,-1,-1e-300}
+ (1e-300,-1e-300) | (NaN,NaN) | {NaN,-1,NaN}
+ (1e-300,-1e-300) | (10,10) | {1,-1,-2e-300}
+ (1e+300,Infinity) | (0,0) | {-1,0,1e+300}
+ (1e+300,Infinity) | (-10,0) | {-1,0,1e+300}
+ (1e+300,Infinity) | (-3,4) | {-1,0,1e+300}
+ (1e+300,Infinity) | (5.1,34.5) | {-1,0,1e+300}
+ (1e+300,Infinity) | (-5,-12) | {-1,0,1e+300}
+ (1e+300,Infinity) | (1e-300,-1e-300) | {-1,0,1e+300}
+ (1e+300,Infinity) | (Infinity,1e+300) | {NaN,-1,NaN}
+ (1e+300,Infinity) | (NaN,NaN) | {NaN,-1,NaN}
+ (1e+300,Infinity) | (10,10) | {-1,0,1e+300}
+ (Infinity,1e+300) | (0,0) | {0,-1,1e+300}
+ (Infinity,1e+300) | (-10,0) | {0,-1,1e+300}
+ (Infinity,1e+300) | (-3,4) | {0,-1,1e+300}
+ (Infinity,1e+300) | (5.1,34.5) | {0,-1,1e+300}
+ (Infinity,1e+300) | (-5,-12) | {0,-1,1e+300}
+ (Infinity,1e+300) | (1e-300,-1e-300) | {0,-1,1e+300}
+ (Infinity,1e+300) | (1e+300,Infinity) | {NaN,-1,NaN}
+ (Infinity,1e+300) | (NaN,NaN) | {NaN,-1,NaN}
+ (Infinity,1e+300) | (10,10) | {0,-1,1e+300}
+ (NaN,NaN) | (0,0) | {NaN,-1,NaN}
+ (NaN,NaN) | (-10,0) | {NaN,-1,NaN}
+ (NaN,NaN) | (-3,4) | {NaN,-1,NaN}
+ (NaN,NaN) | (5.1,34.5) | {NaN,-1,NaN}
+ (NaN,NaN) | (-5,-12) | {NaN,-1,NaN}
+ (NaN,NaN) | (1e-300,-1e-300) | {NaN,-1,NaN}
+ (NaN,NaN) | (1e+300,Infinity) | {NaN,-1,NaN}
+ (NaN,NaN) | (Infinity,1e+300) | {NaN,-1,NaN}
+ (NaN,NaN) | (10,10) | {NaN,-1,NaN}
+ (10,10) | (0,0) | {1,-1,0}
+ (10,10) | (-10,0) | {0.5,-1,5}
+ (10,10) | (-3,4) | {0.461538461538,-1,5.38461538462}
+ (10,10) | (5.1,34.5) | {-5,-1,60}
+ (10,10) | (-5,-12) | {1.46666666667,-1,-4.66666666667}
+ (10,10) | (1e-300,-1e-300) | {1,-1,0}
+ (10,10) | (1e+300,Infinity) | {-1,0,10}
+ (10,10) | (Infinity,1e+300) | {0,-1,10}
+ (10,10) | (NaN,NaN) | {NaN,-1,NaN}
+(88 rows)
+
+-- Closest point to line
+SELECT p.f1, l.s, p.f1 ## l.s FROM POINT_TBL p, LINE_TBL l;
+ f1 | s | ?column?
+-------------------+---------------------------------------+----------------------------------
+ (0,0) | {0,-1,5} | (0,5)
+ (0,0) | {1,0,5} | (-5,0)
+ (0,0) | {0,3,0} | (0,0)
+ (0,0) | {1,-1,0} | (0,0)
+ (0,0) | {-0.4,-1,-6} | (-2.06896551724,-5.1724137931)
+ (0,0) | {-0.000184615384615,-1,15.3846153846} | (0.00284023658959,15.3846148603)
+ (0,0) | {3,NaN,5} |
+ (0,0) | {NaN,NaN,NaN} |
+ (0,0) | {0,-1,3} | (0,3)
+ (0,0) | {-1,0,3} | (3,0)
+ (-10,0) | {0,-1,5} | (-10,5)
+ (-10,0) | {1,0,5} | (-5,0)
+ (-10,0) | {0,3,0} | (-10,0)
+ (-10,0) | {1,-1,0} | (-5,-5)
+ (-10,0) | {-0.4,-1,-6} | (-10.6896551724,-1.72413793103)
+ (-10,0) | {-0.000184615384615,-1,15.3846153846} | (-9.99715942258,15.386461014)
+ (-10,0) | {3,NaN,5} |
+ (-10,0) | {NaN,NaN,NaN} |
+ (-10,0) | {0,-1,3} | (-10,3)
+ (-10,0) | {-1,0,3} | (3,0)
+ (-3,4) | {0,-1,5} | (-3,5)
+ (-3,4) | {1,0,5} | (-5,4)
+ (-3,4) | {0,3,0} | (-3,0)
+ (-3,4) | {1,-1,0} | (0.5,0.5)
+ (-3,4) | {-0.4,-1,-6} | (-6.03448275862,-3.58620689655)
+ (-3,4) | {-0.000184615384615,-1,15.3846153846} | (-2.99789812268,15.3851688427)
+ (-3,4) | {3,NaN,5} |
+ (-3,4) | {NaN,NaN,NaN} |
+ (-3,4) | {0,-1,3} | (-3,3)
+ (-3,4) | {-1,0,3} | (3,4)
+ (5.1,34.5) | {0,-1,5} | (5.1,5)
+ (5.1,34.5) | {1,0,5} | (-5,34.5)
+ (5.1,34.5) | {0,3,0} | (5.1,0)
+ (5.1,34.5) | {1,-1,0} | (19.8,19.8)
+ (5.1,34.5) | {-0.4,-1,-6} | (-9.56896551724,-2.1724137931)
+ (5.1,34.5) | {-0.000184615384615,-1,15.3846153846} | (5.09647083221,15.3836744977)
+ (5.1,34.5) | {3,NaN,5} |
+ (5.1,34.5) | {NaN,NaN,NaN} |
+ (5.1,34.5) | {0,-1,3} | (5.1,3)
+ (5.1,34.5) | {-1,0,3} | (3,34.5)
+ (-5,-12) | {0,-1,5} | (-5,5)
+ (-5,-12) | {1,0,5} | (-5,-12)
+ (-5,-12) | {0,3,0} | (-5,0)
+ (-5,-12) | {1,-1,0} | (-8.5,-8.5)
+ (-5,-12) | {-0.4,-1,-6} | (-2.24137931034,-5.10344827586)
+ (-5,-12) | {-0.000184615384615,-1,15.3846153846} | (-4.99494420846,15.3855375282)
+ (-5,-12) | {3,NaN,5} |
+ (-5,-12) | {NaN,NaN,NaN} |
+ (-5,-12) | {0,-1,3} | (-5,3)
+ (-5,-12) | {-1,0,3} | (3,-12)
+ (1e-300,-1e-300) | {0,-1,5} | (1e-300,5)
+ (1e-300,-1e-300) | {1,0,5} | (-5,-1e-300)
+ (1e-300,-1e-300) | {0,3,0} | (1e-300,0)
+ (1e-300,-1e-300) | {1,-1,0} | (0,0)
+ (1e-300,-1e-300) | {-0.4,-1,-6} | (-2.06896551724,-5.1724137931)
+ (1e-300,-1e-300) | {-0.000184615384615,-1,15.3846153846} | (0.00284023658959,15.3846148603)
+ (1e-300,-1e-300) | {3,NaN,5} |
+ (1e-300,-1e-300) | {NaN,NaN,NaN} |
+ (1e-300,-1e-300) | {0,-1,3} | (1e-300,3)
+ (1e-300,-1e-300) | {-1,0,3} | (3,-1e-300)
+ (1e+300,Infinity) | {0,-1,5} | (1e+300,5)
+ (1e+300,Infinity) | {1,0,5} |
+ (1e+300,Infinity) | {0,3,0} | (1e+300,0)
+ (1e+300,Infinity) | {1,-1,0} | (Infinity,NaN)
+ (1e+300,Infinity) | {-0.4,-1,-6} | (-Infinity,NaN)
+ (1e+300,Infinity) | {-0.000184615384615,-1,15.3846153846} | (-Infinity,NaN)
+ (1e+300,Infinity) | {3,NaN,5} |
+ (1e+300,Infinity) | {NaN,NaN,NaN} |
+ (1e+300,Infinity) | {0,-1,3} | (1e+300,3)
+ (1e+300,Infinity) | {-1,0,3} |
+ (Infinity,1e+300) | {0,-1,5} |
+ (Infinity,1e+300) | {1,0,5} | (-5,1e+300)
+ (Infinity,1e+300) | {0,3,0} |
+ (Infinity,1e+300) | {1,-1,0} |
+ (Infinity,1e+300) | {-0.4,-1,-6} |
+ (Infinity,1e+300) | {-0.000184615384615,-1,15.3846153846} |
+ (Infinity,1e+300) | {3,NaN,5} |
+ (Infinity,1e+300) | {NaN,NaN,NaN} |
+ (Infinity,1e+300) | {0,-1,3} |
+ (Infinity,1e+300) | {-1,0,3} | (3,1e+300)
+ (NaN,NaN) | {0,-1,5} |
+ (NaN,NaN) | {1,0,5} |
+ (NaN,NaN) | {0,3,0} |
+ (NaN,NaN) | {1,-1,0} |
+ (NaN,NaN) | {-0.4,-1,-6} |
+ (NaN,NaN) | {-0.000184615384615,-1,15.3846153846} |
+ (NaN,NaN) | {3,NaN,5} |
+ (NaN,NaN) | {NaN,NaN,NaN} |
+ (NaN,NaN) | {0,-1,3} |
+ (NaN,NaN) | {-1,0,3} |
+ (10,10) | {0,-1,5} | (10,5)
+ (10,10) | {1,0,5} | (-5,10)
+ (10,10) | {0,3,0} | (10,0)
+ (10,10) | {1,-1,0} | (10,10)
+ (10,10) | {-0.4,-1,-6} | (3.10344827586,-7.24137931034)
+ (10,10) | {-0.000184615384615,-1,15.3846153846} | (10.000993742,15.3827690473)
+ (10,10) | {3,NaN,5} |
+ (10,10) | {NaN,NaN,NaN} |
+ (10,10) | {0,-1,3} | (10,3)
+ (10,10) | {-1,0,3} | (3,10)
+(100 rows)
+
+-- Closest point to line segment
+SELECT p.f1, l.s, p.f1 ## l.s FROM POINT_TBL p, LSEG_TBL l;
+ f1 | s | ?column?
+-------------------+-------------------------------+----------------------------------
+ (0,0) | [(1,2),(3,4)] | (1,2)
+ (0,0) | [(0,0),(6,6)] | (0,0)
+ (0,0) | [(10,-10),(-3,-4)] | (-2.0487804878,-4.43902439024)
+ (0,0) | [(-1000000,200),(300000,-40)] | (0.00284023658959,15.3846148603)
+ (0,0) | [(11,22),(33,44)] | (11,22)
+ (0,0) | [(-10,2),(-10,3)] | (-10,2)
+ (0,0) | [(0,-20),(30,-20)] | (0,-20)
+ (0,0) | [(NaN,1),(NaN,90)] |
+ (-10,0) | [(1,2),(3,4)] | (1,2)
+ (-10,0) | [(0,0),(6,6)] | (0,0)
+ (-10,0) | [(10,-10),(-3,-4)] | (-3,-4)
+ (-10,0) | [(-1000000,200),(300000,-40)] | (-9.99715942258,15.386461014)
+ (-10,0) | [(11,22),(33,44)] | (11,22)
+ (-10,0) | [(-10,2),(-10,3)] | (-10,2)
+ (-10,0) | [(0,-20),(30,-20)] | (0,-20)
+ (-10,0) | [(NaN,1),(NaN,90)] |
+ (-3,4) | [(1,2),(3,4)] | (1,2)
+ (-3,4) | [(0,0),(6,6)] | (0.5,0.5)
+ (-3,4) | [(10,-10),(-3,-4)] | (-3,-4)
+ (-3,4) | [(-1000000,200),(300000,-40)] | (-2.99789812268,15.3851688427)
+ (-3,4) | [(11,22),(33,44)] | (11,22)
+ (-3,4) | [(-10,2),(-10,3)] | (-10,3)
+ (-3,4) | [(0,-20),(30,-20)] | (0,-20)
+ (-3,4) | [(NaN,1),(NaN,90)] |
+ (5.1,34.5) | [(1,2),(3,4)] | (3,4)
+ (5.1,34.5) | [(0,0),(6,6)] | (6,6)
+ (5.1,34.5) | [(10,-10),(-3,-4)] | (-3,-4)
+ (5.1,34.5) | [(-1000000,200),(300000,-40)] | (5.09647083221,15.3836744977)
+ (5.1,34.5) | [(11,22),(33,44)] | (14.3,25.3)
+ (5.1,34.5) | [(-10,2),(-10,3)] | (-10,3)
+ (5.1,34.5) | [(0,-20),(30,-20)] | (5.1,-20)
+ (5.1,34.5) | [(NaN,1),(NaN,90)] |
+ (-5,-12) | [(1,2),(3,4)] | (1,2)
+ (-5,-12) | [(0,0),(6,6)] | (0,0)
+ (-5,-12) | [(10,-10),(-3,-4)] | (-1.60487804878,-4.64390243902)
+ (-5,-12) | [(-1000000,200),(300000,-40)] | (-4.99494420846,15.3855375282)
+ (-5,-12) | [(11,22),(33,44)] | (11,22)
+ (-5,-12) | [(-10,2),(-10,3)] | (-10,2)
+ (-5,-12) | [(0,-20),(30,-20)] | (0,-20)
+ (-5,-12) | [(NaN,1),(NaN,90)] |
+ (1e-300,-1e-300) | [(1,2),(3,4)] | (1,2)
+ (1e-300,-1e-300) | [(0,0),(6,6)] | (0,0)
+ (1e-300,-1e-300) | [(10,-10),(-3,-4)] | (-2.0487804878,-4.43902439024)
+ (1e-300,-1e-300) | [(-1000000,200),(300000,-40)] | (0.00284023658959,15.3846148603)
+ (1e-300,-1e-300) | [(11,22),(33,44)] | (11,22)
+ (1e-300,-1e-300) | [(-10,2),(-10,3)] | (-10,2)
+ (1e-300,-1e-300) | [(0,-20),(30,-20)] | (0,-20)
+ (1e-300,-1e-300) | [(NaN,1),(NaN,90)] |
+ (1e+300,Infinity) | [(1,2),(3,4)] | (3,4)
+ (1e+300,Infinity) | [(0,0),(6,6)] | (6,6)
+ (1e+300,Infinity) | [(10,-10),(-3,-4)] | (-3,-4)
+ (1e+300,Infinity) | [(-1000000,200),(300000,-40)] | (300000,-40)
+ (1e+300,Infinity) | [(11,22),(33,44)] | (33,44)
+ (1e+300,Infinity) | [(-10,2),(-10,3)] | (-10,3)
+ (1e+300,Infinity) | [(0,-20),(30,-20)] | (30,-20)
+ (1e+300,Infinity) | [(NaN,1),(NaN,90)] | (NaN,90)
+ (Infinity,1e+300) | [(1,2),(3,4)] | (3,4)
+ (Infinity,1e+300) | [(0,0),(6,6)] | (6,6)
+ (Infinity,1e+300) | [(10,-10),(-3,-4)] | (-3,-4)
+ (Infinity,1e+300) | [(-1000000,200),(300000,-40)] | (300000,-40)
+ (Infinity,1e+300) | [(11,22),(33,44)] | (33,44)
+ (Infinity,1e+300) | [(-10,2),(-10,3)] | (-10,3)
+ (Infinity,1e+300) | [(0,-20),(30,-20)] | (30,-20)
+ (Infinity,1e+300) | [(NaN,1),(NaN,90)] |
+ (NaN,NaN) | [(1,2),(3,4)] |
+ (NaN,NaN) | [(0,0),(6,6)] |
+ (NaN,NaN) | [(10,-10),(-3,-4)] |
+ (NaN,NaN) | [(-1000000,200),(300000,-40)] |
+ (NaN,NaN) | [(11,22),(33,44)] |
+ (NaN,NaN) | [(-10,2),(-10,3)] |
+ (NaN,NaN) | [(0,-20),(30,-20)] |
+ (NaN,NaN) | [(NaN,1),(NaN,90)] |
+ (10,10) | [(1,2),(3,4)] | (3,4)
+ (10,10) | [(0,0),(6,6)] | (6,6)
+ (10,10) | [(10,-10),(-3,-4)] | (2.39024390244,-6.48780487805)
+ (10,10) | [(-1000000,200),(300000,-40)] | (10.000993742,15.3827690473)
+ (10,10) | [(11,22),(33,44)] | (11,22)
+ (10,10) | [(-10,2),(-10,3)] | (-10,3)
+ (10,10) | [(0,-20),(30,-20)] | (10,-20)
+ (10,10) | [(NaN,1),(NaN,90)] |
+(80 rows)
+
+-- Closest point to box
+SELECT p.f1, b.f1, p.f1 ## b.f1 FROM POINT_TBL p, BOX_TBL b;
+ f1 | f1 | ?column?
+-------------------+---------------------+--------------
+ (0,0) | (2,2),(0,0) | (0,0)
+ (0,0) | (3,3),(1,1) | (1,1)
+ (0,0) | (-2,2),(-8,-10) | (-2,0)
+ (0,0) | (2.5,3.5),(2.5,2.5) | (2.5,2.5)
+ (0,0) | (3,3),(3,3) | (3,3)
+ (-10,0) | (2,2),(0,0) | (0,0)
+ (-10,0) | (3,3),(1,1) | (1,1)
+ (-10,0) | (-2,2),(-8,-10) | (-8,0)
+ (-10,0) | (2.5,3.5),(2.5,2.5) | (2.5,2.5)
+ (-10,0) | (3,3),(3,3) | (3,3)
+ (-3,4) | (2,2),(0,0) | (0,2)
+ (-3,4) | (3,3),(1,1) | (1,3)
+ (-3,4) | (-2,2),(-8,-10) | (-3,2)
+ (-3,4) | (2.5,3.5),(2.5,2.5) | (2.5,3.5)
+ (-3,4) | (3,3),(3,3) | (3,3)
+ (5.1,34.5) | (2,2),(0,0) | (2,2)
+ (5.1,34.5) | (3,3),(1,1) | (3,3)
+ (5.1,34.5) | (-2,2),(-8,-10) | (-2,2)
+ (5.1,34.5) | (2.5,3.5),(2.5,2.5) | (2.5,3.5)
+ (5.1,34.5) | (3,3),(3,3) | (3,3)
+ (-5,-12) | (2,2),(0,0) | (0,0)
+ (-5,-12) | (3,3),(1,1) | (1,1)
+ (-5,-12) | (-2,2),(-8,-10) | (-5,-10)
+ (-5,-12) | (2.5,3.5),(2.5,2.5) | (2.5,2.5)
+ (-5,-12) | (3,3),(3,3) | (3,3)
+ (1e-300,-1e-300) | (2,2),(0,0) | (0,0)
+ (1e-300,-1e-300) | (3,3),(1,1) | (1,1)
+ (1e-300,-1e-300) | (-2,2),(-8,-10) | (-2,-1e-300)
+ (1e-300,-1e-300) | (2.5,3.5),(2.5,2.5) | (2.5,2.5)
+ (1e-300,-1e-300) | (3,3),(3,3) | (3,3)
+ (1e+300,Infinity) | (2,2),(0,0) | (0,2)
+ (1e+300,Infinity) | (3,3),(1,1) | (1,3)
+ (1e+300,Infinity) | (-2,2),(-8,-10) | (-8,2)
+ (1e+300,Infinity) | (2.5,3.5),(2.5,2.5) | (2.5,3.5)
+ (1e+300,Infinity) | (3,3),(3,3) | (3,3)
+ (Infinity,1e+300) | (2,2),(0,0) | (0,2)
+ (Infinity,1e+300) | (3,3),(1,1) | (1,3)
+ (Infinity,1e+300) | (-2,2),(-8,-10) | (-8,2)
+ (Infinity,1e+300) | (2.5,3.5),(2.5,2.5) | (2.5,3.5)
+ (Infinity,1e+300) | (3,3),(3,3) | (3,3)
+ (NaN,NaN) | (2,2),(0,0) |
+ (NaN,NaN) | (3,3),(1,1) |
+ (NaN,NaN) | (-2,2),(-8,-10) |
+ (NaN,NaN) | (2.5,3.5),(2.5,2.5) |
+ (NaN,NaN) | (3,3),(3,3) |
+ (10,10) | (2,2),(0,0) | (2,2)
+ (10,10) | (3,3),(1,1) | (3,3)
+ (10,10) | (-2,2),(-8,-10) | (-2,2)
+ (10,10) | (2.5,3.5),(2.5,2.5) | (2.5,3.5)
+ (10,10) | (3,3),(3,3) | (3,3)
+(50 rows)
+
+-- On line
+SELECT p.f1, l.s FROM POINT_TBL p, LINE_TBL l WHERE p.f1 <@ l.s;
+ f1 | s
+------------------+----------
+ (0,0) | {0,3,0}
+ (0,0) | {1,-1,0}
+ (-10,0) | {0,3,0}
+ (-5,-12) | {1,0,5}
+ (1e-300,-1e-300) | {0,3,0}
+ (1e-300,-1e-300) | {1,-1,0}
+ (10,10) | {1,-1,0}
+(7 rows)
+
+-- On line segment
+SELECT p.f1, l.s FROM POINT_TBL p, LSEG_TBL l WHERE p.f1 <@ l.s;
+ f1 | s
+------------------+---------------
+ (0,0) | [(0,0),(6,6)]
+ (1e-300,-1e-300) | [(0,0),(6,6)]
+(2 rows)
+
+-- On path
+SELECT p.f1, p1.f1 FROM POINT_TBL p, PATH_TBL p1 WHERE p.f1 <@ p1.f1;
+ f1 | f1
+------------------+---------------------------
+ (0,0) | [(0,0),(3,0),(4,5),(1,6)]
+ (1e-300,-1e-300) | [(0,0),(3,0),(4,5),(1,6)]
+ (NaN,NaN) | ((1,2),(3,4))
+ (NaN,NaN) | ((1,2),(3,4))
+ (NaN,NaN) | ((1,2),(3,4))
+ (NaN,NaN) | ((10,20))
+ (NaN,NaN) | ((11,12),(13,14))
+(7 rows)
+
+--
+-- Lines
+--
+-- Vertical
+SELECT s FROM LINE_TBL WHERE ?| s;
+ s
+----------
+ {1,0,5}
+ {-1,0,3}
+(2 rows)
+
+-- Horizontal
+SELECT s FROM LINE_TBL WHERE ?- s;
+ s
+----------
+ {0,-1,5}
+ {0,3,0}
+ {0,-1,3}
+(3 rows)
+
+-- Same as line
+SELECT l1.s, l2.s FROM LINE_TBL l1, LINE_TBL l2 WHERE l1.s = l2.s;
+ s | s
+---------------------------------------+---------------------------------------
+ {0,-1,5} | {0,-1,5}
+ {1,0,5} | {1,0,5}
+ {0,3,0} | {0,3,0}
+ {1,-1,0} | {1,-1,0}
+ {-0.4,-1,-6} | {-0.4,-1,-6}
+ {-0.000184615384615,-1,15.3846153846} | {-0.000184615384615,-1,15.3846153846}
+ {3,NaN,5} | {3,NaN,5}
+ {NaN,NaN,NaN} | {NaN,NaN,NaN}
+ {0,-1,3} | {0,-1,3}
+ {-1,0,3} | {-1,0,3}
+(10 rows)
+
+-- Parallel to line
+SELECT l1.s, l2.s FROM LINE_TBL l1, LINE_TBL l2 WHERE l1.s ?|| l2.s;
+ s | s
+---------------------------------------+---------------------------------------
+ {0,-1,5} | {0,-1,5}
+ {0,-1,5} | {0,3,0}
+ {0,-1,5} | {0,-1,3}
+ {1,0,5} | {1,0,5}
+ {1,0,5} | {-1,0,3}
+ {0,3,0} | {0,-1,5}
+ {0,3,0} | {0,3,0}
+ {0,3,0} | {0,-1,3}
+ {1,-1,0} | {1,-1,0}
+ {-0.4,-1,-6} | {-0.4,-1,-6}
+ {-0.000184615384615,-1,15.3846153846} | {-0.000184615384615,-1,15.3846153846}
+ {0,-1,3} | {0,-1,5}
+ {0,-1,3} | {0,3,0}
+ {0,-1,3} | {0,-1,3}
+ {-1,0,3} | {1,0,5}
+ {-1,0,3} | {-1,0,3}
+(16 rows)
+
+-- Perpendicular to line
+SELECT l1.s, l2.s FROM LINE_TBL l1, LINE_TBL l2 WHERE l1.s ?-| l2.s;
+ s | s
+----------+----------
+ {0,-1,5} | {1,0,5}
+ {0,-1,5} | {-1,0,3}
+ {1,0,5} | {0,-1,5}
+ {1,0,5} | {0,3,0}
+ {1,0,5} | {0,-1,3}
+ {0,3,0} | {1,0,5}
+ {0,3,0} | {-1,0,3}
+ {0,-1,3} | {1,0,5}
+ {0,-1,3} | {-1,0,3}
+ {-1,0,3} | {0,-1,5}
+ {-1,0,3} | {0,3,0}
+ {-1,0,3} | {0,-1,3}
+(12 rows)
+
+-- Distance to line
+SELECT l1.s, l2.s, l1.s <-> l2.s FROM LINE_TBL l1, LINE_TBL l2;
+ s | s | ?column?
+---------------------------------------+---------------------------------------+----------
+ {0,-1,5} | {0,-1,5} | 0
+ {0,-1,5} | {1,0,5} | 0
+ {0,-1,5} | {0,3,0} | 5
+ {0,-1,5} | {1,-1,0} | 0
+ {0,-1,5} | {-0.4,-1,-6} | 0
+ {0,-1,5} | {-0.000184615384615,-1,15.3846153846} | 0
+ {0,-1,5} | {3,NaN,5} | 0
+ {0,-1,5} | {NaN,NaN,NaN} | 0
+ {0,-1,5} | {0,-1,3} | 2
+ {0,-1,5} | {-1,0,3} | 0
+ {1,0,5} | {0,-1,5} | 0
+ {1,0,5} | {1,0,5} | 0
+ {1,0,5} | {0,3,0} | 0
+ {1,0,5} | {1,-1,0} | 0
+ {1,0,5} | {-0.4,-1,-6} | 0
+ {1,0,5} | {-0.000184615384615,-1,15.3846153846} | 0
+ {1,0,5} | {3,NaN,5} | 0
+ {1,0,5} | {NaN,NaN,NaN} | 0
+ {1,0,5} | {0,-1,3} | 0
+ {1,0,5} | {-1,0,3} | 8
+ {0,3,0} | {0,-1,5} | 5
+ {0,3,0} | {1,0,5} | 0
+ {0,3,0} | {0,3,0} | 0
+ {0,3,0} | {1,-1,0} | 0
+ {0,3,0} | {-0.4,-1,-6} | 0
+ {0,3,0} | {-0.000184615384615,-1,15.3846153846} | 0
+ {0,3,0} | {3,NaN,5} | 0
+ {0,3,0} | {NaN,NaN,NaN} | 0
+ {0,3,0} | {0,-1,3} | 3
+ {0,3,0} | {-1,0,3} | 0
+ {1,-1,0} | {0,-1,5} | 0
+ {1,-1,0} | {1,0,5} | 0
+ {1,-1,0} | {0,3,0} | 0
+ {1,-1,0} | {1,-1,0} | 0
+ {1,-1,0} | {-0.4,-1,-6} | 0
+ {1,-1,0} | {-0.000184615384615,-1,15.3846153846} | 0
+ {1,-1,0} | {3,NaN,5} | 0
+ {1,-1,0} | {NaN,NaN,NaN} | 0
+ {1,-1,0} | {0,-1,3} | 0
+ {1,-1,0} | {-1,0,3} | 0
+ {-0.4,-1,-6} | {0,-1,5} | 0
+ {-0.4,-1,-6} | {1,0,5} | 0
+ {-0.4,-1,-6} | {0,3,0} | 0
+ {-0.4,-1,-6} | {1,-1,0} | 0
+ {-0.4,-1,-6} | {-0.4,-1,-6} | 0
+ {-0.4,-1,-6} | {-0.000184615384615,-1,15.3846153846} | 0
+ {-0.4,-1,-6} | {3,NaN,5} | 0
+ {-0.4,-1,-6} | {NaN,NaN,NaN} | 0
+ {-0.4,-1,-6} | {0,-1,3} | 0
+ {-0.4,-1,-6} | {-1,0,3} | 0
+ {-0.000184615384615,-1,15.3846153846} | {0,-1,5} | 0
+ {-0.000184615384615,-1,15.3846153846} | {1,0,5} | 0
+ {-0.000184615384615,-1,15.3846153846} | {0,3,0} | 0
+ {-0.000184615384615,-1,15.3846153846} | {1,-1,0} | 0
+ {-0.000184615384615,-1,15.3846153846} | {-0.4,-1,-6} | 0
+ {-0.000184615384615,-1,15.3846153846} | {-0.000184615384615,-1,15.3846153846} | 0
+ {-0.000184615384615,-1,15.3846153846} | {3,NaN,5} | 0
+ {-0.000184615384615,-1,15.3846153846} | {NaN,NaN,NaN} | 0
+ {-0.000184615384615,-1,15.3846153846} | {0,-1,3} | 0
+ {-0.000184615384615,-1,15.3846153846} | {-1,0,3} | 0
+ {3,NaN,5} | {0,-1,5} | 0
+ {3,NaN,5} | {1,0,5} | 0
+ {3,NaN,5} | {0,3,0} | 0
+ {3,NaN,5} | {1,-1,0} | 0
+ {3,NaN,5} | {-0.4,-1,-6} | 0
+ {3,NaN,5} | {-0.000184615384615,-1,15.3846153846} | 0
+ {3,NaN,5} | {3,NaN,5} | 0
+ {3,NaN,5} | {NaN,NaN,NaN} | 0
+ {3,NaN,5} | {0,-1,3} | 0
+ {3,NaN,5} | {-1,0,3} | 0
+ {NaN,NaN,NaN} | {0,-1,5} | 0
+ {NaN,NaN,NaN} | {1,0,5} | 0
+ {NaN,NaN,NaN} | {0,3,0} | 0
+ {NaN,NaN,NaN} | {1,-1,0} | 0
+ {NaN,NaN,NaN} | {-0.4,-1,-6} | 0
+ {NaN,NaN,NaN} | {-0.000184615384615,-1,15.3846153846} | 0
+ {NaN,NaN,NaN} | {3,NaN,5} | 0
+ {NaN,NaN,NaN} | {NaN,NaN,NaN} | 0
+ {NaN,NaN,NaN} | {0,-1,3} | 0
+ {NaN,NaN,NaN} | {-1,0,3} | 0
+ {0,-1,3} | {0,-1,5} | 2
+ {0,-1,3} | {1,0,5} | 0
+ {0,-1,3} | {0,3,0} | 3
+ {0,-1,3} | {1,-1,0} | 0
+ {0,-1,3} | {-0.4,-1,-6} | 0
+ {0,-1,3} | {-0.000184615384615,-1,15.3846153846} | 0
+ {0,-1,3} | {3,NaN,5} | 0
+ {0,-1,3} | {NaN,NaN,NaN} | 0
+ {0,-1,3} | {0,-1,3} | 0
+ {0,-1,3} | {-1,0,3} | 0
+ {-1,0,3} | {0,-1,5} | 0
+ {-1,0,3} | {1,0,5} | 8
+ {-1,0,3} | {0,3,0} | 0
+ {-1,0,3} | {1,-1,0} | 0
+ {-1,0,3} | {-0.4,-1,-6} | 0
+ {-1,0,3} | {-0.000184615384615,-1,15.3846153846} | 0
+ {-1,0,3} | {3,NaN,5} | 0
+ {-1,0,3} | {NaN,NaN,NaN} | 0
+ {-1,0,3} | {0,-1,3} | 0
+ {-1,0,3} | {-1,0,3} | 0
+(100 rows)
+
+-- Distance to box
+SELECT l.s, b.f1, l.s <-> b.f1 FROM LINE_TBL l, BOX_TBL b;
+ERROR: function "dist_lb" not implemented
+SELECT l.s, b.f1, b.f1 <-> l.s FROM LINE_TBL l, BOX_TBL b;
+ERROR: function "dist_bl" not implemented
+-- Intersect with line
+SELECT l1.s, l2.s FROM LINE_TBL l1, LINE_TBL l2 WHERE l1.s ?# l2.s;
+ s | s
+---------------------------------------+---------------------------------------
+ {0,-1,5} | {1,0,5}
+ {0,-1,5} | {1,-1,0}
+ {0,-1,5} | {-0.4,-1,-6}
+ {0,-1,5} | {-0.000184615384615,-1,15.3846153846}
+ {0,-1,5} | {3,NaN,5}
+ {0,-1,5} | {NaN,NaN,NaN}
+ {0,-1,5} | {-1,0,3}
+ {1,0,5} | {0,-1,5}
+ {1,0,5} | {0,3,0}
+ {1,0,5} | {1,-1,0}
+ {1,0,5} | {-0.4,-1,-6}
+ {1,0,5} | {-0.000184615384615,-1,15.3846153846}
+ {1,0,5} | {3,NaN,5}
+ {1,0,5} | {NaN,NaN,NaN}
+ {1,0,5} | {0,-1,3}
+ {0,3,0} | {1,0,5}
+ {0,3,0} | {1,-1,0}
+ {0,3,0} | {-0.4,-1,-6}
+ {0,3,0} | {-0.000184615384615,-1,15.3846153846}
+ {0,3,0} | {3,NaN,5}
+ {0,3,0} | {NaN,NaN,NaN}
+ {0,3,0} | {-1,0,3}
+ {1,-1,0} | {0,-1,5}
+ {1,-1,0} | {1,0,5}
+ {1,-1,0} | {0,3,0}
+ {1,-1,0} | {-0.4,-1,-6}
+ {1,-1,0} | {-0.000184615384615,-1,15.3846153846}
+ {1,-1,0} | {3,NaN,5}
+ {1,-1,0} | {NaN,NaN,NaN}
+ {1,-1,0} | {0,-1,3}
+ {1,-1,0} | {-1,0,3}
+ {-0.4,-1,-6} | {0,-1,5}
+ {-0.4,-1,-6} | {1,0,5}
+ {-0.4,-1,-6} | {0,3,0}
+ {-0.4,-1,-6} | {1,-1,0}
+ {-0.4,-1,-6} | {-0.000184615384615,-1,15.3846153846}
+ {-0.4,-1,-6} | {3,NaN,5}
+ {-0.4,-1,-6} | {NaN,NaN,NaN}
+ {-0.4,-1,-6} | {0,-1,3}
+ {-0.4,-1,-6} | {-1,0,3}
+ {-0.000184615384615,-1,15.3846153846} | {0,-1,5}
+ {-0.000184615384615,-1,15.3846153846} | {1,0,5}
+ {-0.000184615384615,-1,15.3846153846} | {0,3,0}
+ {-0.000184615384615,-1,15.3846153846} | {1,-1,0}
+ {-0.000184615384615,-1,15.3846153846} | {-0.4,-1,-6}
+ {-0.000184615384615,-1,15.3846153846} | {3,NaN,5}
+ {-0.000184615384615,-1,15.3846153846} | {NaN,NaN,NaN}
+ {-0.000184615384615,-1,15.3846153846} | {0,-1,3}
+ {-0.000184615384615,-1,15.3846153846} | {-1,0,3}
+ {3,NaN,5} | {0,-1,5}
+ {3,NaN,5} | {1,0,5}
+ {3,NaN,5} | {0,3,0}
+ {3,NaN,5} | {1,-1,0}
+ {3,NaN,5} | {-0.4,-1,-6}
+ {3,NaN,5} | {-0.000184615384615,-1,15.3846153846}
+ {3,NaN,5} | {3,NaN,5}
+ {3,NaN,5} | {NaN,NaN,NaN}
+ {3,NaN,5} | {0,-1,3}
+ {3,NaN,5} | {-1,0,3}
+ {NaN,NaN,NaN} | {0,-1,5}
+ {NaN,NaN,NaN} | {1,0,5}
+ {NaN,NaN,NaN} | {0,3,0}
+ {NaN,NaN,NaN} | {1,-1,0}
+ {NaN,NaN,NaN} | {-0.4,-1,-6}
+ {NaN,NaN,NaN} | {-0.000184615384615,-1,15.3846153846}
+ {NaN,NaN,NaN} | {3,NaN,5}
+ {NaN,NaN,NaN} | {NaN,NaN,NaN}
+ {NaN,NaN,NaN} | {0,-1,3}
+ {NaN,NaN,NaN} | {-1,0,3}
+ {0,-1,3} | {1,0,5}
+ {0,-1,3} | {1,-1,0}
+ {0,-1,3} | {-0.4,-1,-6}
+ {0,-1,3} | {-0.000184615384615,-1,15.3846153846}
+ {0,-1,3} | {3,NaN,5}
+ {0,-1,3} | {NaN,NaN,NaN}
+ {0,-1,3} | {-1,0,3}
+ {-1,0,3} | {0,-1,5}
+ {-1,0,3} | {0,3,0}
+ {-1,0,3} | {1,-1,0}
+ {-1,0,3} | {-0.4,-1,-6}
+ {-1,0,3} | {-0.000184615384615,-1,15.3846153846}
+ {-1,0,3} | {3,NaN,5}
+ {-1,0,3} | {NaN,NaN,NaN}
+ {-1,0,3} | {0,-1,3}
+(84 rows)
+
+-- Intersect with box
+SELECT l.s, b.f1 FROM LINE_TBL l, BOX_TBL b WHERE l.s ?# b.f1;
+ s | f1
+--------------+---------------------
+ {1,0,5} | (-2,2),(-8,-10)
+ {0,3,0} | (2,2),(0,0)
+ {0,3,0} | (-2,2),(-8,-10)
+ {1,-1,0} | (2,2),(0,0)
+ {1,-1,0} | (3,3),(1,1)
+ {1,-1,0} | (-2,2),(-8,-10)
+ {1,-1,0} | (2.5,3.5),(2.5,2.5)
+ {1,-1,0} | (3,3),(3,3)
+ {-0.4,-1,-6} | (-2,2),(-8,-10)
+ {0,-1,3} | (3,3),(1,1)
+ {0,-1,3} | (2.5,3.5),(2.5,2.5)
+ {0,-1,3} | (3,3),(3,3)
+ {-1,0,3} | (3,3),(1,1)
+(13 rows)
+
+-- Intersection point with line
+SELECT l1.s, l2.s, l1.s # l2.s FROM LINE_TBL l1, LINE_TBL l2;
+ s | s | ?column?
+---------------------------------------+---------------------------------------+-----------------------------------
+ {0,-1,5} | {0,-1,5} |
+ {0,-1,5} | {1,0,5} | (-5,5)
+ {0,-1,5} | {0,3,0} |
+ {0,-1,5} | {1,-1,0} | (5,5)
+ {0,-1,5} | {-0.4,-1,-6} | (-27.5,5)
+ {0,-1,5} | {-0.000184615384615,-1,15.3846153846} | (56250,5)
+ {0,-1,5} | {3,NaN,5} | (NaN,NaN)
+ {0,-1,5} | {NaN,NaN,NaN} | (NaN,NaN)
+ {0,-1,5} | {0,-1,3} |
+ {0,-1,5} | {-1,0,3} | (3,5)
+ {1,0,5} | {0,-1,5} | (-5,5)
+ {1,0,5} | {1,0,5} |
+ {1,0,5} | {0,3,0} | (-5,0)
+ {1,0,5} | {1,-1,0} | (-5,-5)
+ {1,0,5} | {-0.4,-1,-6} | (-5,-4)
+ {1,0,5} | {-0.000184615384615,-1,15.3846153846} | (-5,15.3855384615)
+ {1,0,5} | {3,NaN,5} | (NaN,NaN)
+ {1,0,5} | {NaN,NaN,NaN} | (NaN,NaN)
+ {1,0,5} | {0,-1,3} | (-5,3)
+ {1,0,5} | {-1,0,3} |
+ {0,3,0} | {0,-1,5} |
+ {0,3,0} | {1,0,5} | (-5,0)
+ {0,3,0} | {0,3,0} |
+ {0,3,0} | {1,-1,0} | (0,0)
+ {0,3,0} | {-0.4,-1,-6} | (-15,0)
+ {0,3,0} | {-0.000184615384615,-1,15.3846153846} | (83333.3333333,0)
+ {0,3,0} | {3,NaN,5} | (NaN,NaN)
+ {0,3,0} | {NaN,NaN,NaN} | (NaN,NaN)
+ {0,3,0} | {0,-1,3} |
+ {0,3,0} | {-1,0,3} | (3,0)
+ {1,-1,0} | {0,-1,5} | (5,5)
+ {1,-1,0} | {1,0,5} | (-5,-5)
+ {1,-1,0} | {0,3,0} | (0,0)
+ {1,-1,0} | {1,-1,0} |
+ {1,-1,0} | {-0.4,-1,-6} | (-4.28571428571,-4.28571428571)
+ {1,-1,0} | {-0.000184615384615,-1,15.3846153846} | (15.3817756722,15.3817756722)
+ {1,-1,0} | {3,NaN,5} | (NaN,NaN)
+ {1,-1,0} | {NaN,NaN,NaN} | (NaN,NaN)
+ {1,-1,0} | {0,-1,3} | (3,3)
+ {1,-1,0} | {-1,0,3} | (3,3)
+ {-0.4,-1,-6} | {0,-1,5} | (-27.5,5)
+ {-0.4,-1,-6} | {1,0,5} | (-5,-4)
+ {-0.4,-1,-6} | {0,3,0} | (-15,0)
+ {-0.4,-1,-6} | {1,-1,0} | (-4.28571428571,-4.28571428571)
+ {-0.4,-1,-6} | {-0.4,-1,-6} |
+ {-0.4,-1,-6} | {-0.000184615384615,-1,15.3846153846} | (-53.4862244113,15.3944897645)
+ {-0.4,-1,-6} | {3,NaN,5} | (NaN,NaN)
+ {-0.4,-1,-6} | {NaN,NaN,NaN} | (NaN,NaN)
+ {-0.4,-1,-6} | {0,-1,3} | (-22.5,3)
+ {-0.4,-1,-6} | {-1,0,3} | (3,-7.2)
+ {-0.000184615384615,-1,15.3846153846} | {0,-1,5} | (56250,5)
+ {-0.000184615384615,-1,15.3846153846} | {1,0,5} | (-5,15.3855384615)
+ {-0.000184615384615,-1,15.3846153846} | {0,3,0} | (83333.3333333,-1.7763568394e-15)
+ {-0.000184615384615,-1,15.3846153846} | {1,-1,0} | (15.3817756722,15.3817756722)
+ {-0.000184615384615,-1,15.3846153846} | {-0.4,-1,-6} | (-53.4862244113,15.3944897645)
+ {-0.000184615384615,-1,15.3846153846} | {-0.000184615384615,-1,15.3846153846} |
+ {-0.000184615384615,-1,15.3846153846} | {3,NaN,5} | (NaN,NaN)
+ {-0.000184615384615,-1,15.3846153846} | {NaN,NaN,NaN} | (NaN,NaN)
+ {-0.000184615384615,-1,15.3846153846} | {0,-1,3} | (67083.3333333,3)
+ {-0.000184615384615,-1,15.3846153846} | {-1,0,3} | (3,15.3840615385)
+ {3,NaN,5} | {0,-1,5} | (NaN,NaN)
+ {3,NaN,5} | {1,0,5} | (NaN,NaN)
+ {3,NaN,5} | {0,3,0} | (NaN,NaN)
+ {3,NaN,5} | {1,-1,0} | (NaN,NaN)
+ {3,NaN,5} | {-0.4,-1,-6} | (NaN,NaN)
+ {3,NaN,5} | {-0.000184615384615,-1,15.3846153846} | (NaN,NaN)
+ {3,NaN,5} | {3,NaN,5} | (NaN,NaN)
+ {3,NaN,5} | {NaN,NaN,NaN} | (NaN,NaN)
+ {3,NaN,5} | {0,-1,3} | (NaN,NaN)
+ {3,NaN,5} | {-1,0,3} | (NaN,NaN)
+ {NaN,NaN,NaN} | {0,-1,5} | (NaN,NaN)
+ {NaN,NaN,NaN} | {1,0,5} | (NaN,NaN)
+ {NaN,NaN,NaN} | {0,3,0} | (NaN,NaN)
+ {NaN,NaN,NaN} | {1,-1,0} | (NaN,NaN)
+ {NaN,NaN,NaN} | {-0.4,-1,-6} | (NaN,NaN)
+ {NaN,NaN,NaN} | {-0.000184615384615,-1,15.3846153846} | (NaN,NaN)
+ {NaN,NaN,NaN} | {3,NaN,5} | (NaN,NaN)
+ {NaN,NaN,NaN} | {NaN,NaN,NaN} | (NaN,NaN)
+ {NaN,NaN,NaN} | {0,-1,3} | (NaN,NaN)
+ {NaN,NaN,NaN} | {-1,0,3} | (NaN,NaN)
+ {0,-1,3} | {0,-1,5} |
+ {0,-1,3} | {1,0,5} | (-5,3)
+ {0,-1,3} | {0,3,0} |
+ {0,-1,3} | {1,-1,0} | (3,3)
+ {0,-1,3} | {-0.4,-1,-6} | (-22.5,3)
+ {0,-1,3} | {-0.000184615384615,-1,15.3846153846} | (67083.3333333,3)
+ {0,-1,3} | {3,NaN,5} | (NaN,NaN)
+ {0,-1,3} | {NaN,NaN,NaN} | (NaN,NaN)
+ {0,-1,3} | {0,-1,3} |
+ {0,-1,3} | {-1,0,3} | (3,3)
+ {-1,0,3} | {0,-1,5} | (3,5)
+ {-1,0,3} | {1,0,5} |
+ {-1,0,3} | {0,3,0} | (3,0)
+ {-1,0,3} | {1,-1,0} | (3,3)
+ {-1,0,3} | {-0.4,-1,-6} | (3,-7.2)
+ {-1,0,3} | {-0.000184615384615,-1,15.3846153846} | (3,15.3840615385)
+ {-1,0,3} | {3,NaN,5} | (NaN,NaN)
+ {-1,0,3} | {NaN,NaN,NaN} | (NaN,NaN)
+ {-1,0,3} | {0,-1,3} | (3,3)
+ {-1,0,3} | {-1,0,3} |
+(100 rows)
+
+-- Closest point to line segment
+SELECT l.s, l1.s, l.s ## l1.s FROM LINE_TBL l, LSEG_TBL l1;
+ s | s | ?column?
+---------------------------------------+-------------------------------+-----------------------------------
+ {0,-1,5} | [(1,2),(3,4)] | (3,4)
+ {0,-1,5} | [(0,0),(6,6)] | (5,5)
+ {0,-1,5} | [(10,-10),(-3,-4)] | (-3,-4)
+ {0,-1,5} | [(-1000000,200),(300000,-40)] | (56250,5)
+ {0,-1,5} | [(11,22),(33,44)] | (11,22)
+ {0,-1,5} | [(-10,2),(-10,3)] | (-10,3)
+ {0,-1,5} | [(0,-20),(30,-20)] |
+ {0,-1,5} | [(NaN,1),(NaN,90)] |
+ {1,0,5} | [(1,2),(3,4)] | (1,2)
+ {1,0,5} | [(0,0),(6,6)] | (0,0)
+ {1,0,5} | [(10,-10),(-3,-4)] | (-3,-4)
+ {1,0,5} | [(-1000000,200),(300000,-40)] | (-5,15.3855384615)
+ {1,0,5} | [(11,22),(33,44)] | (11,22)
+ {1,0,5} | [(-10,2),(-10,3)] |
+ {1,0,5} | [(0,-20),(30,-20)] | (0,-20)
+ {1,0,5} | [(NaN,1),(NaN,90)] |
+ {0,3,0} | [(1,2),(3,4)] | (1,2)
+ {0,3,0} | [(0,0),(6,6)] | (0,0)
+ {0,3,0} | [(10,-10),(-3,-4)] | (-3,-4)
+ {0,3,0} | [(-1000000,200),(300000,-40)] | (83333.3333333,-1.7763568394e-15)
+ {0,3,0} | [(11,22),(33,44)] | (11,22)
+ {0,3,0} | [(-10,2),(-10,3)] | (-10,2)
+ {0,3,0} | [(0,-20),(30,-20)] |
+ {0,3,0} | [(NaN,1),(NaN,90)] |
+ {1,-1,0} | [(1,2),(3,4)] |
+ {1,-1,0} | [(0,0),(6,6)] |
+ {1,-1,0} | [(10,-10),(-3,-4)] | (-3,-4)
+ {1,-1,0} | [(-1000000,200),(300000,-40)] | (15.3817756722,15.3817756722)
+ {1,-1,0} | [(11,22),(33,44)] |
+ {1,-1,0} | [(-10,2),(-10,3)] | (-10,2)
+ {1,-1,0} | [(0,-20),(30,-20)] | (0,-20)
+ {1,-1,0} | [(NaN,1),(NaN,90)] |
+ {-0.4,-1,-6} | [(1,2),(3,4)] | (1,2)
+ {-0.4,-1,-6} | [(0,0),(6,6)] | (0,0)
+ {-0.4,-1,-6} | [(10,-10),(-3,-4)] | (10,-10)
+ {-0.4,-1,-6} | [(-1000000,200),(300000,-40)] | (-53.4862244113,15.3944897645)
+ {-0.4,-1,-6} | [(11,22),(33,44)] | (11,22)
+ {-0.4,-1,-6} | [(-10,2),(-10,3)] | (-10,2)
+ {-0.4,-1,-6} | [(0,-20),(30,-20)] | (30,-20)
+ {-0.4,-1,-6} | [(NaN,1),(NaN,90)] |
+ {-0.000184615384615,-1,15.3846153846} | [(1,2),(3,4)] | (3,4)
+ {-0.000184615384615,-1,15.3846153846} | [(0,0),(6,6)] | (6,6)
+ {-0.000184615384615,-1,15.3846153846} | [(10,-10),(-3,-4)] | (-3,-4)
+ {-0.000184615384615,-1,15.3846153846} | [(-1000000,200),(300000,-40)] |
+ {-0.000184615384615,-1,15.3846153846} | [(11,22),(33,44)] | (11,22)
+ {-0.000184615384615,-1,15.3846153846} | [(-10,2),(-10,3)] | (-10,3)
+ {-0.000184615384615,-1,15.3846153846} | [(0,-20),(30,-20)] | (30,-20)
+ {-0.000184615384615,-1,15.3846153846} | [(NaN,1),(NaN,90)] |
+ {3,NaN,5} | [(1,2),(3,4)] |
+ {3,NaN,5} | [(0,0),(6,6)] |
+ {3,NaN,5} | [(10,-10),(-3,-4)] |
+ {3,NaN,5} | [(-1000000,200),(300000,-40)] |
+ {3,NaN,5} | [(11,22),(33,44)] |
+ {3,NaN,5} | [(-10,2),(-10,3)] |
+ {3,NaN,5} | [(0,-20),(30,-20)] |
+ {3,NaN,5} | [(NaN,1),(NaN,90)] |
+ {NaN,NaN,NaN} | [(1,2),(3,4)] |
+ {NaN,NaN,NaN} | [(0,0),(6,6)] |
+ {NaN,NaN,NaN} | [(10,-10),(-3,-4)] |
+ {NaN,NaN,NaN} | [(-1000000,200),(300000,-40)] |
+ {NaN,NaN,NaN} | [(11,22),(33,44)] |
+ {NaN,NaN,NaN} | [(-10,2),(-10,3)] |
+ {NaN,NaN,NaN} | [(0,-20),(30,-20)] |
+ {NaN,NaN,NaN} | [(NaN,1),(NaN,90)] |
+ {0,-1,3} | [(1,2),(3,4)] | (2,3)
+ {0,-1,3} | [(0,0),(6,6)] | (3,3)
+ {0,-1,3} | [(10,-10),(-3,-4)] | (-3,-4)
+ {0,-1,3} | [(-1000000,200),(300000,-40)] | (67083.3333333,3)
+ {0,-1,3} | [(11,22),(33,44)] | (11,22)
+ {0,-1,3} | [(-10,2),(-10,3)] | (-10,3)
+ {0,-1,3} | [(0,-20),(30,-20)] |
+ {0,-1,3} | [(NaN,1),(NaN,90)] |
+ {-1,0,3} | [(1,2),(3,4)] | (3,4)
+ {-1,0,3} | [(0,0),(6,6)] | (3,3)
+ {-1,0,3} | [(10,-10),(-3,-4)] | (3,-6.76923076923)
+ {-1,0,3} | [(-1000000,200),(300000,-40)] | (3,15.3840615385)
+ {-1,0,3} | [(11,22),(33,44)] | (11,22)
+ {-1,0,3} | [(-10,2),(-10,3)] |
+ {-1,0,3} | [(0,-20),(30,-20)] | (3,-20)
+ {-1,0,3} | [(NaN,1),(NaN,90)] |
+(80 rows)
+
+-- Closest point to box
+SELECT l.s, b.f1, l.s ## b.f1 FROM LINE_TBL l, BOX_TBL b;
+ERROR: function "close_lb" not implemented
+--
+-- Line segments
+--
+-- intersection
+SELECT p.f1, l.s, l.s # p.f1 AS intersection
+ FROM LSEG_TBL l, POINT_TBL p;
+ERROR: operator does not exist: lseg # point
+LINE 1: SELECT p.f1, l.s, l.s # p.f1 AS intersection
+ ^
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+-- Length
+SELECT s, @-@ s FROM LSEG_TBL;
+ s | ?column?
+-------------------------------+---------------
+ [(1,2),(3,4)] | 2.82842712475
+ [(0,0),(6,6)] | 8.48528137424
+ [(10,-10),(-3,-4)] | 14.3178210633
+ [(-1000000,200),(300000,-40)] | 1300000.02215
+ [(11,22),(33,44)] | 31.1126983722
+ [(-10,2),(-10,3)] | 1
+ [(0,-20),(30,-20)] | 30
+ [(NaN,1),(NaN,90)] | NaN
+(8 rows)
+
+-- Vertical
+SELECT s FROM LSEG_TBL WHERE ?| s;
+ s
+-------------------
+ [(-10,2),(-10,3)]
+(1 row)
+
+-- Horizontal
+SELECT s FROM LSEG_TBL WHERE ?- s;
+ s
+--------------------
+ [(0,-20),(30,-20)]
+(1 row)
+
+-- Center
+SELECT s, @@ s FROM LSEG_TBL;
+ s | ?column?
+-------------------------------+--------------
+ [(1,2),(3,4)] | (2,3)
+ [(0,0),(6,6)] | (3,3)
+ [(10,-10),(-3,-4)] | (3.5,-7)
+ [(-1000000,200),(300000,-40)] | (-350000,80)
+ [(11,22),(33,44)] | (22,33)
+ [(-10,2),(-10,3)] | (-10,2.5)
+ [(0,-20),(30,-20)] | (15,-20)
+ [(NaN,1),(NaN,90)] | (NaN,45.5)
+(8 rows)
+
+-- To point
+SELECT s, s::point FROM LSEG_TBL;
+ s | s
+-------------------------------+--------------
+ [(1,2),(3,4)] | (2,3)
+ [(0,0),(6,6)] | (3,3)
+ [(10,-10),(-3,-4)] | (3.5,-7)
+ [(-1000000,200),(300000,-40)] | (-350000,80)
+ [(11,22),(33,44)] | (22,33)
+ [(-10,2),(-10,3)] | (-10,2.5)
+ [(0,-20),(30,-20)] | (15,-20)
+ [(NaN,1),(NaN,90)] | (NaN,45.5)
+(8 rows)
+
+-- Has points less than line segment
+SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s < l2.s;
+ s | s
+--------------------+-------------------------------
+ [(1,2),(3,4)] | [(0,0),(6,6)]
+ [(1,2),(3,4)] | [(10,-10),(-3,-4)]
+ [(1,2),(3,4)] | [(-1000000,200),(300000,-40)]
+ [(1,2),(3,4)] | [(11,22),(33,44)]
+ [(1,2),(3,4)] | [(0,-20),(30,-20)]
+ [(0,0),(6,6)] | [(10,-10),(-3,-4)]
+ [(0,0),(6,6)] | [(-1000000,200),(300000,-40)]
+ [(0,0),(6,6)] | [(11,22),(33,44)]
+ [(0,0),(6,6)] | [(0,-20),(30,-20)]
+ [(10,-10),(-3,-4)] | [(-1000000,200),(300000,-40)]
+ [(10,-10),(-3,-4)] | [(11,22),(33,44)]
+ [(10,-10),(-3,-4)] | [(0,-20),(30,-20)]
+ [(11,22),(33,44)] | [(-1000000,200),(300000,-40)]
+ [(-10,2),(-10,3)] | [(1,2),(3,4)]
+ [(-10,2),(-10,3)] | [(0,0),(6,6)]
+ [(-10,2),(-10,3)] | [(10,-10),(-3,-4)]
+ [(-10,2),(-10,3)] | [(-1000000,200),(300000,-40)]
+ [(-10,2),(-10,3)] | [(11,22),(33,44)]
+ [(-10,2),(-10,3)] | [(0,-20),(30,-20)]
+ [(0,-20),(30,-20)] | [(-1000000,200),(300000,-40)]
+ [(0,-20),(30,-20)] | [(11,22),(33,44)]
+(21 rows)
+
+-- 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;
+ s | s
+-------------------------------+-------------------------------
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | [(0,0),(6,6)]
+ [(1,2),(3,4)] | [(10,-10),(-3,-4)]
+ [(1,2),(3,4)] | [(-1000000,200),(300000,-40)]
+ [(1,2),(3,4)] | [(11,22),(33,44)]
+ [(1,2),(3,4)] | [(0,-20),(30,-20)]
+ [(0,0),(6,6)] | [(0,0),(6,6)]
+ [(0,0),(6,6)] | [(10,-10),(-3,-4)]
+ [(0,0),(6,6)] | [(-1000000,200),(300000,-40)]
+ [(0,0),(6,6)] | [(11,22),(33,44)]
+ [(0,0),(6,6)] | [(0,-20),(30,-20)]
+ [(10,-10),(-3,-4)] | [(10,-10),(-3,-4)]
+ [(10,-10),(-3,-4)] | [(-1000000,200),(300000,-40)]
+ [(10,-10),(-3,-4)] | [(11,22),(33,44)]
+ [(10,-10),(-3,-4)] | [(0,-20),(30,-20)]
+ [(-1000000,200),(300000,-40)] | [(-1000000,200),(300000,-40)]
+ [(11,22),(33,44)] | [(-1000000,200),(300000,-40)]
+ [(11,22),(33,44)] | [(11,22),(33,44)]
+ [(-10,2),(-10,3)] | [(1,2),(3,4)]
+ [(-10,2),(-10,3)] | [(0,0),(6,6)]
+ [(-10,2),(-10,3)] | [(10,-10),(-3,-4)]
+ [(-10,2),(-10,3)] | [(-1000000,200),(300000,-40)]
+ [(-10,2),(-10,3)] | [(11,22),(33,44)]
+ [(-10,2),(-10,3)] | [(-10,2),(-10,3)]
+ [(-10,2),(-10,3)] | [(0,-20),(30,-20)]
+ [(0,-20),(30,-20)] | [(-1000000,200),(300000,-40)]
+ [(0,-20),(30,-20)] | [(11,22),(33,44)]
+ [(0,-20),(30,-20)] | [(0,-20),(30,-20)]
+(28 rows)
+
+-- Has points equal to line segment
+SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s = l2.s;
+ s | s
+-------------------------------+-------------------------------
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(0,0),(6,6)] | [(0,0),(6,6)]
+ [(10,-10),(-3,-4)] | [(10,-10),(-3,-4)]
+ [(-1000000,200),(300000,-40)] | [(-1000000,200),(300000,-40)]
+ [(11,22),(33,44)] | [(11,22),(33,44)]
+ [(-10,2),(-10,3)] | [(-10,2),(-10,3)]
+ [(0,-20),(30,-20)] | [(0,-20),(30,-20)]
+ [(NaN,1),(NaN,90)] | [(NaN,1),(NaN,90)]
+(8 rows)
+
+-- 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;
+ s | s
+-------------------------------+-------------------------------
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | [(-10,2),(-10,3)]
+ [(0,0),(6,6)] | [(1,2),(3,4)]
+ [(0,0),(6,6)] | [(0,0),(6,6)]
+ [(0,0),(6,6)] | [(-10,2),(-10,3)]
+ [(10,-10),(-3,-4)] | [(1,2),(3,4)]
+ [(10,-10),(-3,-4)] | [(0,0),(6,6)]
+ [(10,-10),(-3,-4)] | [(10,-10),(-3,-4)]
+ [(10,-10),(-3,-4)] | [(-10,2),(-10,3)]
+ [(-1000000,200),(300000,-40)] | [(1,2),(3,4)]
+ [(-1000000,200),(300000,-40)] | [(0,0),(6,6)]
+ [(-1000000,200),(300000,-40)] | [(10,-10),(-3,-4)]
+ [(-1000000,200),(300000,-40)] | [(-1000000,200),(300000,-40)]
+ [(-1000000,200),(300000,-40)] | [(11,22),(33,44)]
+ [(-1000000,200),(300000,-40)] | [(-10,2),(-10,3)]
+ [(-1000000,200),(300000,-40)] | [(0,-20),(30,-20)]
+ [(11,22),(33,44)] | [(1,2),(3,4)]
+ [(11,22),(33,44)] | [(0,0),(6,6)]
+ [(11,22),(33,44)] | [(10,-10),(-3,-4)]
+ [(11,22),(33,44)] | [(11,22),(33,44)]
+ [(11,22),(33,44)] | [(-10,2),(-10,3)]
+ [(11,22),(33,44)] | [(0,-20),(30,-20)]
+ [(-10,2),(-10,3)] | [(-10,2),(-10,3)]
+ [(0,-20),(30,-20)] | [(1,2),(3,4)]
+ [(0,-20),(30,-20)] | [(0,0),(6,6)]
+ [(0,-20),(30,-20)] | [(10,-10),(-3,-4)]
+ [(0,-20),(30,-20)] | [(-10,2),(-10,3)]
+ [(0,-20),(30,-20)] | [(0,-20),(30,-20)]
+(28 rows)
+
+-- Has points greater than line segment
+SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s > l2.s;
+ s | s
+-------------------------------+--------------------
+ [(1,2),(3,4)] | [(-10,2),(-10,3)]
+ [(0,0),(6,6)] | [(1,2),(3,4)]
+ [(0,0),(6,6)] | [(-10,2),(-10,3)]
+ [(10,-10),(-3,-4)] | [(1,2),(3,4)]
+ [(10,-10),(-3,-4)] | [(0,0),(6,6)]
+ [(10,-10),(-3,-4)] | [(-10,2),(-10,3)]
+ [(-1000000,200),(300000,-40)] | [(1,2),(3,4)]
+ [(-1000000,200),(300000,-40)] | [(0,0),(6,6)]
+ [(-1000000,200),(300000,-40)] | [(10,-10),(-3,-4)]
+ [(-1000000,200),(300000,-40)] | [(11,22),(33,44)]
+ [(-1000000,200),(300000,-40)] | [(-10,2),(-10,3)]
+ [(-1000000,200),(300000,-40)] | [(0,-20),(30,-20)]
+ [(11,22),(33,44)] | [(1,2),(3,4)]
+ [(11,22),(33,44)] | [(0,0),(6,6)]
+ [(11,22),(33,44)] | [(10,-10),(-3,-4)]
+ [(11,22),(33,44)] | [(-10,2),(-10,3)]
+ [(11,22),(33,44)] | [(0,-20),(30,-20)]
+ [(0,-20),(30,-20)] | [(1,2),(3,4)]
+ [(0,-20),(30,-20)] | [(0,0),(6,6)]
+ [(0,-20),(30,-20)] | [(10,-10),(-3,-4)]
+ [(0,-20),(30,-20)] | [(-10,2),(-10,3)]
+(21 rows)
+
+-- Has points not equal to line segment
+SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s != l2.s;
+ s | s
+-------------------------------+-------------------------------
+ [(1,2),(3,4)] | [(0,0),(6,6)]
+ [(1,2),(3,4)] | [(10,-10),(-3,-4)]
+ [(1,2),(3,4)] | [(-1000000,200),(300000,-40)]
+ [(1,2),(3,4)] | [(11,22),(33,44)]
+ [(1,2),(3,4)] | [(-10,2),(-10,3)]
+ [(1,2),(3,4)] | [(0,-20),(30,-20)]
+ [(1,2),(3,4)] | [(NaN,1),(NaN,90)]
+ [(0,0),(6,6)] | [(1,2),(3,4)]
+ [(0,0),(6,6)] | [(10,-10),(-3,-4)]
+ [(0,0),(6,6)] | [(-1000000,200),(300000,-40)]
+ [(0,0),(6,6)] | [(11,22),(33,44)]
+ [(0,0),(6,6)] | [(-10,2),(-10,3)]
+ [(0,0),(6,6)] | [(0,-20),(30,-20)]
+ [(0,0),(6,6)] | [(NaN,1),(NaN,90)]
+ [(10,-10),(-3,-4)] | [(1,2),(3,4)]
+ [(10,-10),(-3,-4)] | [(0,0),(6,6)]
+ [(10,-10),(-3,-4)] | [(-1000000,200),(300000,-40)]
+ [(10,-10),(-3,-4)] | [(11,22),(33,44)]
+ [(10,-10),(-3,-4)] | [(-10,2),(-10,3)]
+ [(10,-10),(-3,-4)] | [(0,-20),(30,-20)]
+ [(10,-10),(-3,-4)] | [(NaN,1),(NaN,90)]
+ [(-1000000,200),(300000,-40)] | [(1,2),(3,4)]
+ [(-1000000,200),(300000,-40)] | [(0,0),(6,6)]
+ [(-1000000,200),(300000,-40)] | [(10,-10),(-3,-4)]
+ [(-1000000,200),(300000,-40)] | [(11,22),(33,44)]
+ [(-1000000,200),(300000,-40)] | [(-10,2),(-10,3)]
+ [(-1000000,200),(300000,-40)] | [(0,-20),(30,-20)]
+ [(-1000000,200),(300000,-40)] | [(NaN,1),(NaN,90)]
+ [(11,22),(33,44)] | [(1,2),(3,4)]
+ [(11,22),(33,44)] | [(0,0),(6,6)]
+ [(11,22),(33,44)] | [(10,-10),(-3,-4)]
+ [(11,22),(33,44)] | [(-1000000,200),(300000,-40)]
+ [(11,22),(33,44)] | [(-10,2),(-10,3)]
+ [(11,22),(33,44)] | [(0,-20),(30,-20)]
+ [(11,22),(33,44)] | [(NaN,1),(NaN,90)]
+ [(-10,2),(-10,3)] | [(1,2),(3,4)]
+ [(-10,2),(-10,3)] | [(0,0),(6,6)]
+ [(-10,2),(-10,3)] | [(10,-10),(-3,-4)]
+ [(-10,2),(-10,3)] | [(-1000000,200),(300000,-40)]
+ [(-10,2),(-10,3)] | [(11,22),(33,44)]
+ [(-10,2),(-10,3)] | [(0,-20),(30,-20)]
+ [(-10,2),(-10,3)] | [(NaN,1),(NaN,90)]
+ [(0,-20),(30,-20)] | [(1,2),(3,4)]
+ [(0,-20),(30,-20)] | [(0,0),(6,6)]
+ [(0,-20),(30,-20)] | [(10,-10),(-3,-4)]
+ [(0,-20),(30,-20)] | [(-1000000,200),(300000,-40)]
+ [(0,-20),(30,-20)] | [(11,22),(33,44)]
+ [(0,-20),(30,-20)] | [(-10,2),(-10,3)]
+ [(0,-20),(30,-20)] | [(NaN,1),(NaN,90)]
+ [(NaN,1),(NaN,90)] | [(1,2),(3,4)]
+ [(NaN,1),(NaN,90)] | [(0,0),(6,6)]
+ [(NaN,1),(NaN,90)] | [(10,-10),(-3,-4)]
+ [(NaN,1),(NaN,90)] | [(-1000000,200),(300000,-40)]
+ [(NaN,1),(NaN,90)] | [(11,22),(33,44)]
+ [(NaN,1),(NaN,90)] | [(-10,2),(-10,3)]
+ [(NaN,1),(NaN,90)] | [(0,-20),(30,-20)]
+(56 rows)
+
+-- Parallel with line segment
+SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s ?|| l2.s;
+ s | s
+-------------------------------+-------------------------------
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | [(0,0),(6,6)]
+ [(1,2),(3,4)] | [(11,22),(33,44)]
+ [(0,0),(6,6)] | [(1,2),(3,4)]
+ [(0,0),(6,6)] | [(0,0),(6,6)]
+ [(0,0),(6,6)] | [(11,22),(33,44)]
+ [(10,-10),(-3,-4)] | [(10,-10),(-3,-4)]
+ [(-1000000,200),(300000,-40)] | [(-1000000,200),(300000,-40)]
+ [(11,22),(33,44)] | [(1,2),(3,4)]
+ [(11,22),(33,44)] | [(0,0),(6,6)]
+ [(11,22),(33,44)] | [(11,22),(33,44)]
+ [(-10,2),(-10,3)] | [(-10,2),(-10,3)]
+ [(0,-20),(30,-20)] | [(0,-20),(30,-20)]
+(13 rows)
+
+-- Perpendicular with line segment
+SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s ?-| l2.s;
+ s | s
+--------------------+--------------------
+ [(-10,2),(-10,3)] | [(0,-20),(30,-20)]
+ [(0,-20),(30,-20)] | [(-10,2),(-10,3)]
+(2 rows)
+
+-- Distance to line
+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;
+ s | s | dist_sl | dist_ls
+-------------------------------+---------------------------------------+----------------+----------------
+ [(1,2),(3,4)] | {0,-1,5} | 1 | 1
+ [(0,0),(6,6)] | {0,-1,5} | 0 | 0
+ [(10,-10),(-3,-4)] | {0,-1,5} | 9 | 9
+ [(-1000000,200),(300000,-40)] | {0,-1,5} | 0 | 0
+ [(11,22),(33,44)] | {0,-1,5} | 17 | 17
+ [(-10,2),(-10,3)] | {0,-1,5} | 2 | 2
+ [(0,-20),(30,-20)] | {0,-1,5} | 25 | 25
+ [(NaN,1),(NaN,90)] | {0,-1,5} | NaN | NaN
+ [(1,2),(3,4)] | {1,0,5} | 6 | 6
+ [(0,0),(6,6)] | {1,0,5} | 5 | 5
+ [(10,-10),(-3,-4)] | {1,0,5} | 2 | 2
+ [(-1000000,200),(300000,-40)] | {1,0,5} | 0 | 0
+ [(11,22),(33,44)] | {1,0,5} | 16 | 16
+ [(-10,2),(-10,3)] | {1,0,5} | 5 | 5
+ [(0,-20),(30,-20)] | {1,0,5} | 5 | 5
+ [(NaN,1),(NaN,90)] | {1,0,5} | NaN | NaN
+ [(1,2),(3,4)] | {0,3,0} | 2 | 2
+ [(0,0),(6,6)] | {0,3,0} | 0 | 0
+ [(10,-10),(-3,-4)] | {0,3,0} | 4 | 4
+ [(-1000000,200),(300000,-40)] | {0,3,0} | 0 | 0
+ [(11,22),(33,44)] | {0,3,0} | 22 | 22
+ [(-10,2),(-10,3)] | {0,3,0} | 2 | 2
+ [(0,-20),(30,-20)] | {0,3,0} | 20 | 20
+ [(NaN,1),(NaN,90)] | {0,3,0} | NaN | NaN
+ [(1,2),(3,4)] | {1,-1,0} | 0.707106781187 | 0.707106781187
+ [(0,0),(6,6)] | {1,-1,0} | 0 | 0
+ [(10,-10),(-3,-4)] | {1,-1,0} | 0.707106781187 | 0.707106781187
+ [(-1000000,200),(300000,-40)] | {1,-1,0} | 0 | 0
+ [(11,22),(33,44)] | {1,-1,0} | 7.77817459305 | 7.77817459305
+ [(-10,2),(-10,3)] | {1,-1,0} | 8.48528137424 | 8.48528137424
+ [(0,-20),(30,-20)] | {1,-1,0} | 14.1421356237 | 14.1421356237
+ [(NaN,1),(NaN,90)] | {1,-1,0} | NaN | NaN
+ [(1,2),(3,4)] | {-0.4,-1,-6} | 7.79920420344 | 7.79920420344
+ [(0,0),(6,6)] | {-0.4,-1,-6} | 5.57086014531 | 5.57086014531
+ [(10,-10),(-3,-4)] | {-0.4,-1,-6} | 0 | 0
+ [(-1000000,200),(300000,-40)] | {-0.4,-1,-6} | 0 | 0
+ [(11,22),(33,44)] | {-0.4,-1,-6} | 30.0826447847 | 30.0826447847
+ [(-10,2),(-10,3)] | {-0.4,-1,-6} | 3.71390676354 | 3.71390676354
+ [(0,-20),(30,-20)] | {-0.4,-1,-6} | 1.85695338177 | 1.85695338177
+ [(NaN,1),(NaN,90)] | {-0.4,-1,-6} | NaN | NaN
+ [(1,2),(3,4)] | {-0.000184615384615,-1,15.3846153846} | 11.3840613445 | 11.3840613445
+ [(0,0),(6,6)] | {-0.000184615384615,-1,15.3846153846} | 9.3835075324 | 9.3835075324
+ [(10,-10),(-3,-4)] | {-0.000184615384615,-1,15.3846153846} | 19.3851689004 | 19.3851689004
+ [(-1000000,200),(300000,-40)] | {-0.000184615384615,-1,15.3846153846} | 0 | 0
+ [(11,22),(33,44)] | {-0.000184615384615,-1,15.3846153846} | 6.61741527185 | 6.61741527185
+ [(-10,2),(-10,3)] | {-0.000184615384615,-1,15.3846153846} | 12.3864613274 | 12.3864613274
+ [(0,-20),(30,-20)] | {-0.000184615384615,-1,15.3846153846} | 35.3790763202 | 35.3790763202
+ [(NaN,1),(NaN,90)] | {-0.000184615384615,-1,15.3846153846} | NaN | NaN
+ [(1,2),(3,4)] | {3,NaN,5} | NaN | NaN
+ [(0,0),(6,6)] | {3,NaN,5} | NaN | NaN
+ [(10,-10),(-3,-4)] | {3,NaN,5} | NaN | NaN
+ [(-1000000,200),(300000,-40)] | {3,NaN,5} | NaN | NaN
+ [(11,22),(33,44)] | {3,NaN,5} | NaN | NaN
+ [(-10,2),(-10,3)] | {3,NaN,5} | NaN | NaN
+ [(0,-20),(30,-20)] | {3,NaN,5} | NaN | NaN
+ [(NaN,1),(NaN,90)] | {3,NaN,5} | NaN | NaN
+ [(1,2),(3,4)] | {NaN,NaN,NaN} | NaN | NaN
+ [(0,0),(6,6)] | {NaN,NaN,NaN} | NaN | NaN
+ [(10,-10),(-3,-4)] | {NaN,NaN,NaN} | NaN | NaN
+ [(-1000000,200),(300000,-40)] | {NaN,NaN,NaN} | NaN | NaN
+ [(11,22),(33,44)] | {NaN,NaN,NaN} | NaN | NaN
+ [(-10,2),(-10,3)] | {NaN,NaN,NaN} | NaN | NaN
+ [(0,-20),(30,-20)] | {NaN,NaN,NaN} | NaN | NaN
+ [(NaN,1),(NaN,90)] | {NaN,NaN,NaN} | NaN | NaN
+ [(1,2),(3,4)] | {0,-1,3} | 0 | 0
+ [(0,0),(6,6)] | {0,-1,3} | 0 | 0
+ [(10,-10),(-3,-4)] | {0,-1,3} | 7 | 7
+ [(-1000000,200),(300000,-40)] | {0,-1,3} | 0 | 0
+ [(11,22),(33,44)] | {0,-1,3} | 19 | 19
+ [(-10,2),(-10,3)] | {0,-1,3} | 0 | 0
+ [(0,-20),(30,-20)] | {0,-1,3} | 23 | 23
+ [(NaN,1),(NaN,90)] | {0,-1,3} | NaN | NaN
+ [(1,2),(3,4)] | {-1,0,3} | 0 | 0
+ [(0,0),(6,6)] | {-1,0,3} | 0 | 0
+ [(10,-10),(-3,-4)] | {-1,0,3} | 0 | 0
+ [(-1000000,200),(300000,-40)] | {-1,0,3} | 0 | 0
+ [(11,22),(33,44)] | {-1,0,3} | 8 | 8
+ [(-10,2),(-10,3)] | {-1,0,3} | 13 | 13
+ [(0,-20),(30,-20)] | {-1,0,3} | 0 | 0
+ [(NaN,1),(NaN,90)] | {-1,0,3} | NaN | NaN
+(80 rows)
+
+-- Distance to line segment
+SELECT l1.s, l2.s, l1.s <-> l2.s FROM LSEG_TBL l1, LSEG_TBL l2;
+ s | s | ?column?
+-------------------------------+-------------------------------+----------------
+ [(1,2),(3,4)] | [(1,2),(3,4)] | 0
+ [(1,2),(3,4)] | [(0,0),(6,6)] | 0.707106781187
+ [(1,2),(3,4)] | [(10,-10),(-3,-4)] | 7.12398901685
+ [(1,2),(3,4)] | [(-1000000,200),(300000,-40)] | 11.3840613445
+ [(1,2),(3,4)] | [(11,22),(33,44)] | 19.6977156036
+ [(1,2),(3,4)] | [(-10,2),(-10,3)] | 11
+ [(1,2),(3,4)] | [(0,-20),(30,-20)] | 22
+ [(1,2),(3,4)] | [(NaN,1),(NaN,90)] | NaN
+ [(0,0),(6,6)] | [(1,2),(3,4)] | 0.707106781187
+ [(0,0),(6,6)] | [(0,0),(6,6)] | 0
+ [(0,0),(6,6)] | [(10,-10),(-3,-4)] | 4.88901207039
+ [(0,0),(6,6)] | [(-1000000,200),(300000,-40)] | 9.3835075324
+ [(0,0),(6,6)] | [(11,22),(33,44)] | 16.7630546142
+ [(0,0),(6,6)] | [(-10,2),(-10,3)] | 10.1980390272
+ [(0,0),(6,6)] | [(0,-20),(30,-20)] | 20
+ [(0,0),(6,6)] | [(NaN,1),(NaN,90)] | NaN
+ [(10,-10),(-3,-4)] | [(1,2),(3,4)] | 7.12398901685
+ [(10,-10),(-3,-4)] | [(0,0),(6,6)] | 4.88901207039
+ [(10,-10),(-3,-4)] | [(10,-10),(-3,-4)] | 0
+ [(10,-10),(-3,-4)] | [(-1000000,200),(300000,-40)] | 19.3851689004
+ [(10,-10),(-3,-4)] | [(11,22),(33,44)] | 29.4737584815
+ [(10,-10),(-3,-4)] | [(-10,2),(-10,3)] | 9.21954445729
+ [(10,-10),(-3,-4)] | [(0,-20),(30,-20)] | 10
+ [(10,-10),(-3,-4)] | [(NaN,1),(NaN,90)] | NaN
+ [(-1000000,200),(300000,-40)] | [(1,2),(3,4)] | 11.3840613445
+ [(-1000000,200),(300000,-40)] | [(0,0),(6,6)] | 9.3835075324
+ [(-1000000,200),(300000,-40)] | [(10,-10),(-3,-4)] | 19.3851689004
+ [(-1000000,200),(300000,-40)] | [(-1000000,200),(300000,-40)] | 0
+ [(-1000000,200),(300000,-40)] | [(11,22),(33,44)] | 6.61741527185
+ [(-1000000,200),(300000,-40)] | [(-10,2),(-10,3)] | 12.3864613274
+ [(-1000000,200),(300000,-40)] | [(0,-20),(30,-20)] | 35.3790763202
+ [(-1000000,200),(300000,-40)] | [(NaN,1),(NaN,90)] | NaN
+ [(11,22),(33,44)] | [(1,2),(3,4)] | 19.6977156036
+ [(11,22),(33,44)] | [(0,0),(6,6)] | 16.7630546142
+ [(11,22),(33,44)] | [(10,-10),(-3,-4)] | 29.4737584815
+ [(11,22),(33,44)] | [(-1000000,200),(300000,-40)] | 6.61741527185
+ [(11,22),(33,44)] | [(11,22),(33,44)] | 0
+ [(11,22),(33,44)] | [(-10,2),(-10,3)] | 28.319604517
+ [(11,22),(33,44)] | [(0,-20),(30,-20)] | 42
+ [(11,22),(33,44)] | [(NaN,1),(NaN,90)] | NaN
+ [(-10,2),(-10,3)] | [(1,2),(3,4)] | 11
+ [(-10,2),(-10,3)] | [(0,0),(6,6)] | 10.1980390272
+ [(-10,2),(-10,3)] | [(10,-10),(-3,-4)] | 9.21954445729
+ [(-10,2),(-10,3)] | [(-1000000,200),(300000,-40)] | 12.3864613274
+ [(-10,2),(-10,3)] | [(11,22),(33,44)] | 28.319604517
+ [(-10,2),(-10,3)] | [(-10,2),(-10,3)] | 0
+ [(-10,2),(-10,3)] | [(0,-20),(30,-20)] | 24.1660919472
+ [(-10,2),(-10,3)] | [(NaN,1),(NaN,90)] | NaN
+ [(0,-20),(30,-20)] | [(1,2),(3,4)] | 22
+ [(0,-20),(30,-20)] | [(0,0),(6,6)] | 20
+ [(0,-20),(30,-20)] | [(10,-10),(-3,-4)] | 10
+ [(0,-20),(30,-20)] | [(-1000000,200),(300000,-40)] | 35.3790763202
+ [(0,-20),(30,-20)] | [(11,22),(33,44)] | 42
+ [(0,-20),(30,-20)] | [(-10,2),(-10,3)] | 24.1660919472
+ [(0,-20),(30,-20)] | [(0,-20),(30,-20)] | 0
+ [(0,-20),(30,-20)] | [(NaN,1),(NaN,90)] | NaN
+ [(NaN,1),(NaN,90)] | [(1,2),(3,4)] | NaN
+ [(NaN,1),(NaN,90)] | [(0,0),(6,6)] | NaN
+ [(NaN,1),(NaN,90)] | [(10,-10),(-3,-4)] | NaN
+ [(NaN,1),(NaN,90)] | [(-1000000,200),(300000,-40)] | NaN
+ [(NaN,1),(NaN,90)] | [(11,22),(33,44)] | NaN
+ [(NaN,1),(NaN,90)] | [(-10,2),(-10,3)] | NaN
+ [(NaN,1),(NaN,90)] | [(0,-20),(30,-20)] | NaN
+ [(NaN,1),(NaN,90)] | [(NaN,1),(NaN,90)] | NaN
+(64 rows)
+
+-- 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;
+ s | f1 | dist_sb | dist_bs
+-------------------------------+---------------------+----------------+----------------
+ [(1,2),(3,4)] | (2,2),(0,0) | 0 | 0
+ [(1,2),(3,4)] | (3,3),(1,1) | 0 | 0
+ [(1,2),(3,4)] | (-2,2),(-8,-10) | 3 | 3
+ [(1,2),(3,4)] | (2.5,3.5),(2.5,2.5) | 0 | 0
+ [(1,2),(3,4)] | (3,3),(3,3) | 0.707106781187 | 0.707106781187
+ [(0,0),(6,6)] | (2,2),(0,0) | 0 | 0
+ [(0,0),(6,6)] | (3,3),(1,1) | 0 | 0
+ [(0,0),(6,6)] | (-2,2),(-8,-10) | 2 | 2
+ [(0,0),(6,6)] | (2.5,3.5),(2.5,2.5) | 0 | 0
+ [(0,0),(6,6)] | (3,3),(3,3) | 0 | 0
+ [(10,-10),(-3,-4)] | (2,2),(0,0) | 4.88901207039 | 4.88901207039
+ [(10,-10),(-3,-4)] | (3,3),(1,1) | 6.21602963235 | 6.21602963235
+ [(10,-10),(-3,-4)] | (-2,2),(-8,-10) | 0 | 0
+ [(10,-10),(-3,-4)] | (2.5,3.5),(2.5,2.5) | 8.20655597529 | 8.20655597529
+ [(10,-10),(-3,-4)] | (3,3),(3,3) | 8.87006475627 | 8.87006475627
+ [(-1000000,200),(300000,-40)] | (2,2),(0,0) | 13.3842459258 | 13.3842459258
+ [(-1000000,200),(300000,-40)] | (3,3),(1,1) | 12.3840613274 | 12.3840613274
+ [(-1000000,200),(300000,-40)] | (-2,2),(-8,-10) | 13.3849843873 | 13.3849843873
+ [(-1000000,200),(300000,-40)] | (2.5,3.5),(2.5,2.5) | 11.8841536436 | 11.8841536436
+ [(-1000000,200),(300000,-40)] | (3,3),(3,3) | 12.3840613274 | 12.3840613274
+ [(11,22),(33,44)] | (2,2),(0,0) | 21.9317121995 | 21.9317121995
+ [(11,22),(33,44)] | (3,3),(1,1) | 20.6155281281 | 20.6155281281
+ [(11,22),(33,44)] | (-2,2),(-8,-10) | 23.8537208838 | 23.8537208838
+ [(11,22),(33,44)] | (2.5,3.5),(2.5,2.5) | 20.3592730715 | 20.3592730715
+ [(11,22),(33,44)] | (3,3),(3,3) | 20.6155281281 | 20.6155281281
+ [(-10,2),(-10,3)] | (2,2),(0,0) | 10 | 10
+ [(-10,2),(-10,3)] | (3,3),(1,1) | 11 | 11
+ [(-10,2),(-10,3)] | (-2,2),(-8,-10) | 2 | 2
+ [(-10,2),(-10,3)] | (2.5,3.5),(2.5,2.5) | 12.5 | 12.5
+ [(-10,2),(-10,3)] | (3,3),(3,3) | 13 | 13
+ [(0,-20),(30,-20)] | (2,2),(0,0) | 20 | 20
+ [(0,-20),(30,-20)] | (3,3),(1,1) | 21 | 21
+ [(0,-20),(30,-20)] | (-2,2),(-8,-10) | 10.1980390272 | 10.1980390272
+ [(0,-20),(30,-20)] | (2.5,3.5),(2.5,2.5) | 22.5 | 22.5
+ [(0,-20),(30,-20)] | (3,3),(3,3) | 23 | 23
+ [(NaN,1),(NaN,90)] | (2,2),(0,0) | NaN | NaN
+ [(NaN,1),(NaN,90)] | (3,3),(1,1) | NaN | NaN
+ [(NaN,1),(NaN,90)] | (-2,2),(-8,-10) | NaN | NaN
+ [(NaN,1),(NaN,90)] | (2.5,3.5),(2.5,2.5) | NaN | NaN
+ [(NaN,1),(NaN,90)] | (3,3),(3,3) | NaN | NaN
+(40 rows)
+
+-- Intersect with line segment
+SELECT l.s, l1.s FROM LSEG_TBL l, LINE_TBL l1 WHERE l.s ?# l1.s;
+ s | s
+-------------------------------+--------------
+ [(0,0),(6,6)] | {0,-1,5}
+ [(-1000000,200),(300000,-40)] | {0,-1,5}
+ [(-1000000,200),(300000,-40)] | {1,0,5}
+ [(0,0),(6,6)] | {0,3,0}
+ [(-1000000,200),(300000,-40)] | {0,3,0}
+ [(-1000000,200),(300000,-40)] | {1,-1,0}
+ [(10,-10),(-3,-4)] | {-0.4,-1,-6}
+ [(-1000000,200),(300000,-40)] | {-0.4,-1,-6}
+ [(1,2),(3,4)] | {0,-1,3}
+ [(0,0),(6,6)] | {0,-1,3}
+ [(-1000000,200),(300000,-40)] | {0,-1,3}
+ [(-10,2),(-10,3)] | {0,-1,3}
+ [(1,2),(3,4)] | {-1,0,3}
+ [(0,0),(6,6)] | {-1,0,3}
+ [(10,-10),(-3,-4)] | {-1,0,3}
+ [(-1000000,200),(300000,-40)] | {-1,0,3}
+ [(0,-20),(30,-20)] | {-1,0,3}
+(17 rows)
+
+-- Intersect with box
+SELECT l.s, b.f1 FROM LSEG_TBL l, BOX_TBL b WHERE l.s ?# b.f1;
+ s | f1
+--------------------+---------------------
+ [(1,2),(3,4)] | (2,2),(0,0)
+ [(1,2),(3,4)] | (3,3),(1,1)
+ [(1,2),(3,4)] | (2.5,3.5),(2.5,2.5)
+ [(0,0),(6,6)] | (2,2),(0,0)
+ [(0,0),(6,6)] | (3,3),(1,1)
+ [(0,0),(6,6)] | (2.5,3.5),(2.5,2.5)
+ [(0,0),(6,6)] | (3,3),(3,3)
+ [(10,-10),(-3,-4)] | (-2,2),(-8,-10)
+(8 rows)
+
+-- Intersection point with line segment
+SELECT l1.s, l2.s, l1.s # l2.s FROM LSEG_TBL l1, LSEG_TBL l2;
+ s | s | ?column?
+-------------------------------+-------------------------------+----------
+ [(1,2),(3,4)] | [(1,2),(3,4)] |
+ [(1,2),(3,4)] | [(0,0),(6,6)] |
+ [(1,2),(3,4)] | [(10,-10),(-3,-4)] |
+ [(1,2),(3,4)] | [(-1000000,200),(300000,-40)] |
+ [(1,2),(3,4)] | [(11,22),(33,44)] |
+ [(1,2),(3,4)] | [(-10,2),(-10,3)] |
+ [(1,2),(3,4)] | [(0,-20),(30,-20)] |
+ [(1,2),(3,4)] | [(NaN,1),(NaN,90)] |
+ [(0,0),(6,6)] | [(1,2),(3,4)] |
+ [(0,0),(6,6)] | [(0,0),(6,6)] |
+ [(0,0),(6,6)] | [(10,-10),(-3,-4)] |
+ [(0,0),(6,6)] | [(-1000000,200),(300000,-40)] |
+ [(0,0),(6,6)] | [(11,22),(33,44)] |
+ [(0,0),(6,6)] | [(-10,2),(-10,3)] |
+ [(0,0),(6,6)] | [(0,-20),(30,-20)] |
+ [(0,0),(6,6)] | [(NaN,1),(NaN,90)] |
+ [(10,-10),(-3,-4)] | [(1,2),(3,4)] |
+ [(10,-10),(-3,-4)] | [(0,0),(6,6)] |
+ [(10,-10),(-3,-4)] | [(10,-10),(-3,-4)] |
+ [(10,-10),(-3,-4)] | [(-1000000,200),(300000,-40)] |
+ [(10,-10),(-3,-4)] | [(11,22),(33,44)] |
+ [(10,-10),(-3,-4)] | [(-10,2),(-10,3)] |
+ [(10,-10),(-3,-4)] | [(0,-20),(30,-20)] |
+ [(10,-10),(-3,-4)] | [(NaN,1),(NaN,90)] |
+ [(-1000000,200),(300000,-40)] | [(1,2),(3,4)] |
+ [(-1000000,200),(300000,-40)] | [(0,0),(6,6)] |
+ [(-1000000,200),(300000,-40)] | [(10,-10),(-3,-4)] |
+ [(-1000000,200),(300000,-40)] | [(-1000000,200),(300000,-40)] |
+ [(-1000000,200),(300000,-40)] | [(11,22),(33,44)] |
+ [(-1000000,200),(300000,-40)] | [(-10,2),(-10,3)] |
+ [(-1000000,200),(300000,-40)] | [(0,-20),(30,-20)] |
+ [(-1000000,200),(300000,-40)] | [(NaN,1),(NaN,90)] |
+ [(11,22),(33,44)] | [(1,2),(3,4)] |
+ [(11,22),(33,44)] | [(0,0),(6,6)] |
+ [(11,22),(33,44)] | [(10,-10),(-3,-4)] |
+ [(11,22),(33,44)] | [(-1000000,200),(300000,-40)] |
+ [(11,22),(33,44)] | [(11,22),(33,44)] |
+ [(11,22),(33,44)] | [(-10,2),(-10,3)] |
+ [(11,22),(33,44)] | [(0,-20),(30,-20)] |
+ [(11,22),(33,44)] | [(NaN,1),(NaN,90)] |
+ [(-10,2),(-10,3)] | [(1,2),(3,4)] |
+ [(-10,2),(-10,3)] | [(0,0),(6,6)] |
+ [(-10,2),(-10,3)] | [(10,-10),(-3,-4)] |
+ [(-10,2),(-10,3)] | [(-1000000,200),(300000,-40)] |
+ [(-10,2),(-10,3)] | [(11,22),(33,44)] |
+ [(-10,2),(-10,3)] | [(-10,2),(-10,3)] |
+ [(-10,2),(-10,3)] | [(0,-20),(30,-20)] |
+ [(-10,2),(-10,3)] | [(NaN,1),(NaN,90)] |
+ [(0,-20),(30,-20)] | [(1,2),(3,4)] |
+ [(0,-20),(30,-20)] | [(0,0),(6,6)] |
+ [(0,-20),(30,-20)] | [(10,-10),(-3,-4)] |
+ [(0,-20),(30,-20)] | [(-1000000,200),(300000,-40)] |
+ [(0,-20),(30,-20)] | [(11,22),(33,44)] |
+ [(0,-20),(30,-20)] | [(-10,2),(-10,3)] |
+ [(0,-20),(30,-20)] | [(0,-20),(30,-20)] |
+ [(0,-20),(30,-20)] | [(NaN,1),(NaN,90)] |
+ [(NaN,1),(NaN,90)] | [(1,2),(3,4)] |
+ [(NaN,1),(NaN,90)] | [(0,0),(6,6)] |
+ [(NaN,1),(NaN,90)] | [(10,-10),(-3,-4)] |
+ [(NaN,1),(NaN,90)] | [(-1000000,200),(300000,-40)] |
+ [(NaN,1),(NaN,90)] | [(11,22),(33,44)] |
+ [(NaN,1),(NaN,90)] | [(-10,2),(-10,3)] |
+ [(NaN,1),(NaN,90)] | [(0,-20),(30,-20)] |
+ [(NaN,1),(NaN,90)] | [(NaN,1),(NaN,90)] |
+(64 rows)
+
+-- Closest point to line
+SELECT l.s, l1.s, l.s ## l1.s FROM LSEG_TBL l, LINE_TBL l1;
+ERROR: function "close_sl" not implemented
+-- Closest point to line segment
+SELECT l1.s, l2.s, l1.s ## l2.s FROM LSEG_TBL l1, LSEG_TBL l2;
+ s | s | ?column?
+-------------------------------+-------------------------------+---------------------------------
+ [(1,2),(3,4)] | [(1,2),(3,4)] |
+ [(1,2),(3,4)] | [(0,0),(6,6)] |
+ [(1,2),(3,4)] | [(10,-10),(-3,-4)] | (-1.98536585366,-4.46829268293)
+ [(1,2),(3,4)] | [(-1000000,200),(300000,-40)] | (3.00210167283,15.3840611505)
+ [(1,2),(3,4)] | [(11,22),(33,44)] |
+ [(1,2),(3,4)] | [(-10,2),(-10,3)] | (-10,2)
+ [(1,2),(3,4)] | [(0,-20),(30,-20)] | (1,-20)
+ [(1,2),(3,4)] | [(NaN,1),(NaN,90)] |
+ [(0,0),(6,6)] | [(1,2),(3,4)] |
+ [(0,0),(6,6)] | [(0,0),(6,6)] |
+ [(0,0),(6,6)] | [(10,-10),(-3,-4)] | (-2.0487804878,-4.43902439024)
+ [(0,0),(6,6)] | [(-1000000,200),(300000,-40)] | (6.00173233982,15.3835073725)
+ [(0,0),(6,6)] | [(11,22),(33,44)] |
+ [(0,0),(6,6)] | [(-10,2),(-10,3)] | (-10,2)
+ [(0,0),(6,6)] | [(0,-20),(30,-20)] | (0,-20)
+ [(0,0),(6,6)] | [(NaN,1),(NaN,90)] |
+ [(10,-10),(-3,-4)] | [(1,2),(3,4)] | (1,2)
+ [(10,-10),(-3,-4)] | [(0,0),(6,6)] | (0,0)
+ [(10,-10),(-3,-4)] | [(10,-10),(-3,-4)] |
+ [(10,-10),(-3,-4)] | [(-1000000,200),(300000,-40)] | (-2.99642119965,15.3851685701)
+ [(10,-10),(-3,-4)] | [(11,22),(33,44)] | (11,22)
+ [(10,-10),(-3,-4)] | [(-10,2),(-10,3)] | (-10,2)
+ [(10,-10),(-3,-4)] | [(0,-20),(30,-20)] | (10,-20)
+ [(10,-10),(-3,-4)] | [(NaN,1),(NaN,90)] |
+ [(-1000000,200),(300000,-40)] | [(1,2),(3,4)] | (3,4)
+ [(-1000000,200),(300000,-40)] | [(0,0),(6,6)] | (6,6)
+ [(-1000000,200),(300000,-40)] | [(10,-10),(-3,-4)] | (-3,-4)
+ [(-1000000,200),(300000,-40)] | [(-1000000,200),(300000,-40)] |
+ [(-1000000,200),(300000,-40)] | [(11,22),(33,44)] | (11,22)
+ [(-1000000,200),(300000,-40)] | [(-10,2),(-10,3)] | (-10,3)
+ [(-1000000,200),(300000,-40)] | [(0,-20),(30,-20)] | (30,-20)
+ [(-1000000,200),(300000,-40)] | [(NaN,1),(NaN,90)] |
+ [(11,22),(33,44)] | [(1,2),(3,4)] |
+ [(11,22),(33,44)] | [(0,0),(6,6)] |
+ [(11,22),(33,44)] | [(10,-10),(-3,-4)] | (-1.3512195122,-4.76097560976)
+ [(11,22),(33,44)] | [(-1000000,200),(300000,-40)] | (10.9987783234,15.3825848409)
+ [(11,22),(33,44)] | [(11,22),(33,44)] |
+ [(11,22),(33,44)] | [(-10,2),(-10,3)] | (-10,3)
+ [(11,22),(33,44)] | [(0,-20),(30,-20)] | (11,-20)
+ [(11,22),(33,44)] | [(NaN,1),(NaN,90)] |
+ [(-10,2),(-10,3)] | [(1,2),(3,4)] | (1,2)
+ [(-10,2),(-10,3)] | [(0,0),(6,6)] | (0,0)
+ [(-10,2),(-10,3)] | [(10,-10),(-3,-4)] | (-3,-4)
+ [(-10,2),(-10,3)] | [(-1000000,200),(300000,-40)] | (-9.99771326872,15.3864611163)
+ [(-10,2),(-10,3)] | [(11,22),(33,44)] | (11,22)
+ [(-10,2),(-10,3)] | [(-10,2),(-10,3)] |
+ [(-10,2),(-10,3)] | [(0,-20),(30,-20)] | (0,-20)
+ [(-10,2),(-10,3)] | [(NaN,1),(NaN,90)] |
+ [(0,-20),(30,-20)] | [(1,2),(3,4)] | (1,2)
+ [(0,-20),(30,-20)] | [(0,0),(6,6)] | (0,0)
+ [(0,-20),(30,-20)] | [(10,-10),(-3,-4)] | (10,-10)
+ [(0,-20),(30,-20)] | [(-1000000,200),(300000,-40)] | (30.0065315217,15.3790757173)
+ [(0,-20),(30,-20)] | [(11,22),(33,44)] | (11,22)
+ [(0,-20),(30,-20)] | [(-10,2),(-10,3)] | (-10,2)
+ [(0,-20),(30,-20)] | [(0,-20),(30,-20)] |
+ [(0,-20),(30,-20)] | [(NaN,1),(NaN,90)] |
+ [(NaN,1),(NaN,90)] | [(1,2),(3,4)] |
+ [(NaN,1),(NaN,90)] | [(0,0),(6,6)] |
+ [(NaN,1),(NaN,90)] | [(10,-10),(-3,-4)] |
+ [(NaN,1),(NaN,90)] | [(-1000000,200),(300000,-40)] |
+ [(NaN,1),(NaN,90)] | [(11,22),(33,44)] |
+ [(NaN,1),(NaN,90)] | [(-10,2),(-10,3)] |
+ [(NaN,1),(NaN,90)] | [(0,-20),(30,-20)] |
+ [(NaN,1),(NaN,90)] | [(NaN,1),(NaN,90)] |
+(64 rows)
+
+-- Closest point to box
+SELECT l.s, b.f1, l.s ## b.f1 FROM LSEG_TBL l, BOX_TBL b;
+ s | f1 | ?column?
+-------------------------------+---------------------+-------------
+ [(1,2),(3,4)] | (2,2),(0,0) | (1,2)
+ [(1,2),(3,4)] | (3,3),(1,1) | (1.5,2.5)
+ [(1,2),(3,4)] | (-2,2),(-8,-10) | (-2,2)
+ [(1,2),(3,4)] | (2.5,3.5),(2.5,2.5) | (2.25,3.25)
+ [(1,2),(3,4)] | (3,3),(3,3) | (3,3)
+ [(0,0),(6,6)] | (2,2),(0,0) | (1,1)
+ [(0,0),(6,6)] | (3,3),(1,1) | (2,2)
+ [(0,0),(6,6)] | (-2,2),(-8,-10) | (-2,0)
+ [(0,0),(6,6)] | (2.5,3.5),(2.5,2.5) | (2.75,2.75)
+ [(0,0),(6,6)] | (3,3),(3,3) | (3,3)
+ [(10,-10),(-3,-4)] | (2,2),(0,0) | (0,0)
+ [(10,-10),(-3,-4)] | (3,3),(1,1) | (1,1)
+ [(10,-10),(-3,-4)] | (-2,2),(-8,-10) | (-3,-4)
+ [(10,-10),(-3,-4)] | (2.5,3.5),(2.5,2.5) | (2.5,2.5)
+ [(10,-10),(-3,-4)] | (3,3),(3,3) | (3,3)
+ [(-1000000,200),(300000,-40)] | (2,2),(0,0) | (2,2)
+ [(-1000000,200),(300000,-40)] | (3,3),(1,1) | (3,3)
+ [(-1000000,200),(300000,-40)] | (-2,2),(-8,-10) | (-2,2)
+ [(-1000000,200),(300000,-40)] | (2.5,3.5),(2.5,2.5) | (2.5,3.5)
+ [(-1000000,200),(300000,-40)] | (3,3),(3,3) | (3,3)
+ [(11,22),(33,44)] | (2,2),(0,0) | (2,2)
+ [(11,22),(33,44)] | (3,3),(1,1) | (3,3)
+ [(11,22),(33,44)] | (-2,2),(-8,-10) | (-2,2)
+ [(11,22),(33,44)] | (2.5,3.5),(2.5,2.5) | (2.5,3.5)
+ [(11,22),(33,44)] | (3,3),(3,3) | (3,3)
+ [(-10,2),(-10,3)] | (2,2),(0,0) | (0,2)
+ [(-10,2),(-10,3)] | (3,3),(1,1) | (1,2)
+ [(-10,2),(-10,3)] | (-2,2),(-8,-10) | (-8,2)
+ [(-10,2),(-10,3)] | (2.5,3.5),(2.5,2.5) | (2.5,3)
+ [(-10,2),(-10,3)] | (3,3),(3,3) | (3,3)
+ [(0,-20),(30,-20)] | (2,2),(0,0) | (0,0)
+ [(0,-20),(30,-20)] | (3,3),(1,1) | (1,1)
+ [(0,-20),(30,-20)] | (-2,2),(-8,-10) | (-2,-10)
+ [(0,-20),(30,-20)] | (2.5,3.5),(2.5,2.5) | (2.5,2.5)
+ [(0,-20),(30,-20)] | (3,3),(3,3) | (3,3)
+ [(NaN,1),(NaN,90)] | (2,2),(0,0) |
+ [(NaN,1),(NaN,90)] | (3,3),(1,1) |
+ [(NaN,1),(NaN,90)] | (-2,2),(-8,-10) |
+ [(NaN,1),(NaN,90)] | (2.5,3.5),(2.5,2.5) |
+ [(NaN,1),(NaN,90)] | (3,3),(3,3) |
+(40 rows)
+
+-- On line
+SELECT l.s, l1.s FROM LSEG_TBL l, LINE_TBL l1 WHERE l.s <@ l1.s;
+ s | s
+-------------------------------+---------------------------------------
+ [(0,0),(6,6)] | {1,-1,0}
+ [(-1000000,200),(300000,-40)] | {-0.000184615384615,-1,15.3846153846}
+(2 rows)
+
+-- On box
+SELECT l.s, b.f1 FROM LSEG_TBL l, BOX_TBL b WHERE l.s <@ b.f1;
+ s | f1
+---+----
+(0 rows)
+
+--
+-- Boxes
+--
+SELECT box(f1) AS box FROM CIRCLE_TBL;
+ box
+----------------------------------------------------------------
+ (7.12132034356,3.12132034356),(2.87867965644,-1.12132034356)
+ (71.7106781187,72.7106781187),(-69.7106781187,-68.7106781187)
+ (4.53553390593,6.53553390593),(-2.53553390593,-0.535533905933)
+ (3.12132034356,4.12132034356),(-1.12132034356,-0.12132034356)
+ (107.071067812,207.071067812),(92.9289321881,192.928932188)
+ (181.317279836,82.3172798365),(18.6827201635,-80.3172798365)
+ (3,5),(3,5)
+ (NaN,NaN),(NaN,NaN)
+(8 rows)
+
+-- translation
+SELECT b.f1 + p.f1 AS translation
+ FROM BOX_TBL b, POINT_TBL p;
+ translation
+-------------------------------------
+ (2,2),(0,0)
+ (3,3),(1,1)
+ (-2,2),(-8,-10)
+ (2.5,3.5),(2.5,2.5)
+ (3,3),(3,3)
+ (-8,2),(-10,0)
+ (-7,3),(-9,1)
+ (-12,2),(-18,-10)
+ (-7.5,3.5),(-7.5,2.5)
+ (-7,3),(-7,3)
+ (-1,6),(-3,4)
+ (0,7),(-2,5)
+ (-5,6),(-11,-6)
+ (-0.5,7.5),(-0.5,6.5)
+ (0,7),(0,7)
+ (7.1,36.5),(5.1,34.5)
+ (8.1,37.5),(6.1,35.5)
+ (3.1,36.5),(-2.9,24.5)
+ (7.6,38),(7.6,37)
+ (8.1,37.5),(8.1,37.5)
+ (-3,-10),(-5,-12)
+ (-2,-9),(-4,-11)
+ (-7,-10),(-13,-22)
+ (-2.5,-8.5),(-2.5,-9.5)
+ (-2,-9),(-2,-9)
+ (2,2),(1e-300,-1e-300)
+ (3,3),(1,1)
+ (-2,2),(-8,-10)
+ (2.5,3.5),(2.5,2.5)
+ (3,3),(3,3)
+ (1e+300,Infinity),(1e+300,Infinity)
+ (1e+300,Infinity),(1e+300,Infinity)
+ (1e+300,Infinity),(1e+300,Infinity)
+ (1e+300,Infinity),(1e+300,Infinity)
+ (1e+300,Infinity),(1e+300,Infinity)
+ (Infinity,1e+300),(Infinity,1e+300)
+ (Infinity,1e+300),(Infinity,1e+300)
+ (Infinity,1e+300),(Infinity,1e+300)
+ (Infinity,1e+300),(Infinity,1e+300)
+ (Infinity,1e+300),(Infinity,1e+300)
+ (NaN,NaN),(NaN,NaN)
+ (NaN,NaN),(NaN,NaN)
+ (NaN,NaN),(NaN,NaN)
+ (NaN,NaN),(NaN,NaN)
+ (NaN,NaN),(NaN,NaN)
+ (12,12),(10,10)
+ (13,13),(11,11)
+ (8,12),(2,0)
+ (12.5,13.5),(12.5,12.5)
+ (13,13),(13,13)
+(50 rows)
+
+SELECT b.f1 - p.f1 AS translation
+ FROM BOX_TBL b, POINT_TBL p;
+ translation
+-----------------------------------------
+ (2,2),(0,0)
+ (3,3),(1,1)
+ (-2,2),(-8,-10)
+ (2.5,3.5),(2.5,2.5)
+ (3,3),(3,3)
+ (12,2),(10,0)
+ (13,3),(11,1)
+ (8,2),(2,-10)
+ (12.5,3.5),(12.5,2.5)
+ (13,3),(13,3)
+ (5,-2),(3,-4)
+ (6,-1),(4,-3)
+ (1,-2),(-5,-14)
+ (5.5,-0.5),(5.5,-1.5)
+ (6,-1),(6,-1)
+ (-3.1,-32.5),(-5.1,-34.5)
+ (-2.1,-31.5),(-4.1,-33.5)
+ (-7.1,-32.5),(-13.1,-44.5)
+ (-2.6,-31),(-2.6,-32)
+ (-2.1,-31.5),(-2.1,-31.5)
+ (7,14),(5,12)
+ (8,15),(6,13)
+ (3,14),(-3,2)
+ (7.5,15.5),(7.5,14.5)
+ (8,15),(8,15)
+ (2,2),(-1e-300,1e-300)
+ (3,3),(1,1)
+ (-2,2),(-8,-10)
+ (2.5,3.5),(2.5,2.5)
+ (3,3),(3,3)
+ (-1e+300,-Infinity),(-1e+300,-Infinity)
+ (-1e+300,-Infinity),(-1e+300,-Infinity)
+ (-1e+300,-Infinity),(-1e+300,-Infinity)
+ (-1e+300,-Infinity),(-1e+300,-Infinity)
+ (-1e+300,-Infinity),(-1e+300,-Infinity)
+ (-Infinity,-1e+300),(-Infinity,-1e+300)
+ (-Infinity,-1e+300),(-Infinity,-1e+300)
+ (-Infinity,-1e+300),(-Infinity,-1e+300)
+ (-Infinity,-1e+300),(-Infinity,-1e+300)
+ (-Infinity,-1e+300),(-Infinity,-1e+300)
+ (NaN,NaN),(NaN,NaN)
+ (NaN,NaN),(NaN,NaN)
+ (NaN,NaN),(NaN,NaN)
+ (NaN,NaN),(NaN,NaN)
+ (NaN,NaN),(NaN,NaN)
+ (-8,-8),(-10,-10)
+ (-7,-7),(-9,-9)
+ (-12,-8),(-18,-20)
+ (-7.5,-6.5),(-7.5,-7.5)
+ (-7,-7),(-7,-7)
+(50 rows)
+
+-- 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;
+ f1 | f1 | ?column?
+---------------------+------------+-----------------------------
+ (2,2),(0,0) | (5.1,34.5) | (0,79.2),(-58.8,0)
+ (2,2),(0,0) | (10,10) | (0,40),(0,0)
+ (3,3),(1,1) | (5.1,34.5) | (-29.4,118.8),(-88.2,39.6)
+ (3,3),(1,1) | (10,10) | (0,60),(0,20)
+ (-2,2),(-8,-10) | (5.1,34.5) | (304.2,-58.8),(-79.2,-327)
+ (-2,2),(-8,-10) | (10,10) | (20,0),(-40,-180)
+ (2.5,3.5),(2.5,2.5) | (5.1,34.5) | (-73.5,104.1),(-108,99)
+ (2.5,3.5),(2.5,2.5) | (10,10) | (0,60),(-10,50)
+ (3,3),(3,3) | (5.1,34.5) | (-88.2,118.8),(-88.2,118.8)
+ (3,3),(3,3) | (10,10) | (0,60),(0,60)
+(10 rows)
+
+-- Overflow error
+SELECT b.f1, p.f1, b.f1 * p.f1 FROM BOX_TBL b, POINT_TBL p WHERE p.f1[0] > 1000;
+ f1 | f1 | ?column?
+---------------------+-------------------+--------------------------------------------
+ (2,2),(0,0) | (1e+300,Infinity) | (NaN,NaN),(-Infinity,Infinity)
+ (2,2),(0,0) | (Infinity,1e+300) | (NaN,NaN),(Infinity,Infinity)
+ (2,2),(0,0) | (NaN,NaN) | (NaN,NaN),(NaN,NaN)
+ (3,3),(1,1) | (1e+300,Infinity) | (-Infinity,Infinity),(-Infinity,Infinity)
+ (3,3),(1,1) | (Infinity,1e+300) | (Infinity,Infinity),(Infinity,Infinity)
+ (3,3),(1,1) | (NaN,NaN) | (NaN,NaN),(NaN,NaN)
+ (-2,2),(-8,-10) | (1e+300,Infinity) | (Infinity,-Infinity),(-Infinity,-Infinity)
+ (-2,2),(-8,-10) | (Infinity,1e+300) | (-Infinity,Infinity),(-Infinity,-Infinity)
+ (-2,2),(-8,-10) | (NaN,NaN) | (NaN,NaN),(NaN,NaN)
+ (2.5,3.5),(2.5,2.5) | (1e+300,Infinity) | (-Infinity,Infinity),(-Infinity,Infinity)
+ (2.5,3.5),(2.5,2.5) | (Infinity,1e+300) | (Infinity,Infinity),(Infinity,Infinity)
+ (2.5,3.5),(2.5,2.5) | (NaN,NaN) | (NaN,NaN),(NaN,NaN)
+ (3,3),(3,3) | (1e+300,Infinity) | (-Infinity,Infinity),(-Infinity,Infinity)
+ (3,3),(3,3) | (Infinity,1e+300) | (Infinity,Infinity),(Infinity,Infinity)
+ (3,3),(3,3) | (NaN,NaN) | (NaN,NaN),(NaN,NaN)
+(15 rows)
+
+-- 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;
+ f1 | f1 | ?column?
+---------------------+------------+----------------------------------------------------------------------
+ (2,2),(0,0) | (5.1,34.5) | (0.0651176557644,0),(0,-0.0483449262493)
+ (2,2),(0,0) | (10,10) | (0.2,0),(0,0)
+ (3,3),(1,1) | (5.1,34.5) | (0.0976764836466,-0.0241724631247),(0.0325588278822,-0.072517389374)
+ (3,3),(1,1) | (10,10) | (0.3,0),(0.1,0)
+ (-2,2),(-8,-10) | (5.1,34.5) | (0.0483449262493,0.18499334024),(-0.317201914064,0.0651176557644)
+ (-2,2),(-8,-10) | (10,10) | (0,0.2),(-0.9,-0.1)
+ (2.5,3.5),(2.5,2.5) | (5.1,34.5) | (0.109762715209,-0.0562379754329),(0.0813970697055,-0.0604311578117)
+ (2.5,3.5),(2.5,2.5) | (10,10) | (0.3,0.05),(0.25,0)
+ (3,3),(3,3) | (5.1,34.5) | (0.0976764836466,-0.072517389374),(0.0976764836466,-0.072517389374)
+ (3,3),(3,3) | (10,10) | (0.3,0),(0.3,0)
+(10 rows)
+
+-- To box
+SELECT f1::box
+ FROM POINT_TBL;
+ f1
+-------------------------------------
+ (0,0),(0,0)
+ (-10,0),(-10,0)
+ (-3,4),(-3,4)
+ (5.1,34.5),(5.1,34.5)
+ (-5,-12),(-5,-12)
+ (1e-300,-1e-300),(1e-300,-1e-300)
+ (1e+300,Infinity),(1e+300,Infinity)
+ (Infinity,1e+300),(Infinity,1e+300)
+ (NaN,NaN),(NaN,NaN)
+ (10,10),(10,10)
+(10 rows)
+
+SELECT bound_box(a.f1, b.f1)
+ FROM BOX_TBL a, BOX_TBL b;
+ bound_box
+---------------------
+ (2,2),(0,0)
+ (3,3),(0,0)
+ (2,2),(-8,-10)
+ (2.5,3.5),(0,0)
+ (3,3),(0,0)
+ (3,3),(0,0)
+ (3,3),(1,1)
+ (3,3),(-8,-10)
+ (3,3.5),(1,1)
+ (3,3),(1,1)
+ (2,2),(-8,-10)
+ (3,3),(-8,-10)
+ (-2,2),(-8,-10)
+ (2.5,3.5),(-8,-10)
+ (3,3),(-8,-10)
+ (2.5,3.5),(0,0)
+ (3,3.5),(1,1)
+ (2.5,3.5),(-8,-10)
+ (2.5,3.5),(2.5,2.5)
+ (3,3.5),(2.5,2.5)
+ (3,3),(0,0)
+ (3,3),(1,1)
+ (3,3),(-8,-10)
+ (3,3.5),(2.5,2.5)
+ (3,3),(3,3)
+(25 rows)
+
+-- Below box
+SELECT b1.f1, b2.f1, b1.f1 <^ b2.f1 FROM BOX_TBL b1, BOX_TBL b2;
+ f1 | f1 | ?column?
+---------------------+---------------------+----------
+ (2,2),(0,0) | (2,2),(0,0) | f
+ (2,2),(0,0) | (3,3),(1,1) | f
+ (2,2),(0,0) | (-2,2),(-8,-10) | f
+ (2,2),(0,0) | (2.5,3.5),(2.5,2.5) | t
+ (2,2),(0,0) | (3,3),(3,3) | t
+ (3,3),(1,1) | (2,2),(0,0) | f
+ (3,3),(1,1) | (3,3),(1,1) | f
+ (3,3),(1,1) | (-2,2),(-8,-10) | f
+ (3,3),(1,1) | (2.5,3.5),(2.5,2.5) | f
+ (3,3),(1,1) | (3,3),(3,3) | t
+ (-2,2),(-8,-10) | (2,2),(0,0) | f
+ (-2,2),(-8,-10) | (3,3),(1,1) | f
+ (-2,2),(-8,-10) | (-2,2),(-8,-10) | f
+ (-2,2),(-8,-10) | (2.5,3.5),(2.5,2.5) | t
+ (-2,2),(-8,-10) | (3,3),(3,3) | t
+ (2.5,3.5),(2.5,2.5) | (2,2),(0,0) | f
+ (2.5,3.5),(2.5,2.5) | (3,3),(1,1) | f
+ (2.5,3.5),(2.5,2.5) | (-2,2),(-8,-10) | f
+ (2.5,3.5),(2.5,2.5) | (2.5,3.5),(2.5,2.5) | f
+ (2.5,3.5),(2.5,2.5) | (3,3),(3,3) | f
+ (3,3),(3,3) | (2,2),(0,0) | f
+ (3,3),(3,3) | (3,3),(1,1) | f
+ (3,3),(3,3) | (-2,2),(-8,-10) | f
+ (3,3),(3,3) | (2.5,3.5),(2.5,2.5) | f
+ (3,3),(3,3) | (3,3),(3,3) | t
+(25 rows)
+
+-- Above box
+SELECT b1.f1, b2.f1, b1.f1 >^ b2.f1 FROM BOX_TBL b1, BOX_TBL b2;
+ f1 | f1 | ?column?
+---------------------+---------------------+----------
+ (2,2),(0,0) | (2,2),(0,0) | f
+ (2,2),(0,0) | (3,3),(1,1) | f
+ (2,2),(0,0) | (-2,2),(-8,-10) | f
+ (2,2),(0,0) | (2.5,3.5),(2.5,2.5) | f
+ (2,2),(0,0) | (3,3),(3,3) | f
+ (3,3),(1,1) | (2,2),(0,0) | f
+ (3,3),(1,1) | (3,3),(1,1) | f
+ (3,3),(1,1) | (-2,2),(-8,-10) | f
+ (3,3),(1,1) | (2.5,3.5),(2.5,2.5) | f
+ (3,3),(1,1) | (3,3),(3,3) | f
+ (-2,2),(-8,-10) | (2,2),(0,0) | f
+ (-2,2),(-8,-10) | (3,3),(1,1) | f
+ (-2,2),(-8,-10) | (-2,2),(-8,-10) | f
+ (-2,2),(-8,-10) | (2.5,3.5),(2.5,2.5) | f
+ (-2,2),(-8,-10) | (3,3),(3,3) | f
+ (2.5,3.5),(2.5,2.5) | (2,2),(0,0) | t
+ (2.5,3.5),(2.5,2.5) | (3,3),(1,1) | f
+ (2.5,3.5),(2.5,2.5) | (-2,2),(-8,-10) | t
+ (2.5,3.5),(2.5,2.5) | (2.5,3.5),(2.5,2.5) | f
+ (2.5,3.5),(2.5,2.5) | (3,3),(3,3) | f
+ (3,3),(3,3) | (2,2),(0,0) | t
+ (3,3),(3,3) | (3,3),(1,1) | t
+ (3,3),(3,3) | (-2,2),(-8,-10) | t
+ (3,3),(3,3) | (2.5,3.5),(2.5,2.5) | f
+ (3,3),(3,3) | (3,3),(3,3) | t
+(25 rows)
+
+-- Intersection point with box
+SELECT b1.f1, b2.f1, b1.f1 # b2.f1 FROM BOX_TBL b1, BOX_TBL b2;
+ f1 | f1 | ?column?
+---------------------+---------------------+---------------------
+ (2,2),(0,0) | (2,2),(0,0) | (2,2),(0,0)
+ (2,2),(0,0) | (3,3),(1,1) | (2,2),(1,1)
+ (2,2),(0,0) | (-2,2),(-8,-10) |
+ (2,2),(0,0) | (2.5,3.5),(2.5,2.5) |
+ (2,2),(0,0) | (3,3),(3,3) |
+ (3,3),(1,1) | (2,2),(0,0) | (2,2),(1,1)
+ (3,3),(1,1) | (3,3),(1,1) | (3,3),(1,1)
+ (3,3),(1,1) | (-2,2),(-8,-10) |
+ (3,3),(1,1) | (2.5,3.5),(2.5,2.5) | (2.5,3),(2.5,2.5)
+ (3,3),(1,1) | (3,3),(3,3) | (3,3),(3,3)
+ (-2,2),(-8,-10) | (2,2),(0,0) |
+ (-2,2),(-8,-10) | (3,3),(1,1) |
+ (-2,2),(-8,-10) | (-2,2),(-8,-10) | (-2,2),(-8,-10)
+ (-2,2),(-8,-10) | (2.5,3.5),(2.5,2.5) |
+ (-2,2),(-8,-10) | (3,3),(3,3) |
+ (2.5,3.5),(2.5,2.5) | (2,2),(0,0) |
+ (2.5,3.5),(2.5,2.5) | (3,3),(1,1) | (2.5,3),(2.5,2.5)
+ (2.5,3.5),(2.5,2.5) | (-2,2),(-8,-10) |
+ (2.5,3.5),(2.5,2.5) | (2.5,3.5),(2.5,2.5) | (2.5,3.5),(2.5,2.5)
+ (2.5,3.5),(2.5,2.5) | (3,3),(3,3) |
+ (3,3),(3,3) | (2,2),(0,0) |
+ (3,3),(3,3) | (3,3),(1,1) | (3,3),(3,3)
+ (3,3),(3,3) | (-2,2),(-8,-10) |
+ (3,3),(3,3) | (2.5,3.5),(2.5,2.5) |
+ (3,3),(3,3) | (3,3),(3,3) | (3,3),(3,3)
+(25 rows)
+
+-- Diagonal
+SELECT f1, diagonal(f1) FROM BOX_TBL;
+ f1 | diagonal
+---------------------+-----------------------
+ (2,2),(0,0) | [(2,2),(0,0)]
+ (3,3),(1,1) | [(3,3),(1,1)]
+ (-2,2),(-8,-10) | [(-2,2),(-8,-10)]
+ (2.5,3.5),(2.5,2.5) | [(2.5,3.5),(2.5,2.5)]
+ (3,3),(3,3) | [(3,3),(3,3)]
+(5 rows)
+
+-- Distance to box
+SELECT b1.f1, b2.f1, b1.f1 <-> b2.f1 FROM BOX_TBL b1, BOX_TBL b2;
+ f1 | f1 | ?column?
+---------------------+---------------------+---------------
+ (2,2),(0,0) | (2,2),(0,0) | 0
+ (2,2),(0,0) | (3,3),(1,1) | 1.41421356237
+ (2,2),(0,0) | (-2,2),(-8,-10) | 7.81024967591
+ (2,2),(0,0) | (2.5,3.5),(2.5,2.5) | 2.5
+ (2,2),(0,0) | (3,3),(3,3) | 2.82842712475
+ (3,3),(1,1) | (2,2),(0,0) | 1.41421356237
+ (3,3),(1,1) | (3,3),(1,1) | 0
+ (3,3),(1,1) | (-2,2),(-8,-10) | 9.21954445729
+ (3,3),(1,1) | (2.5,3.5),(2.5,2.5) | 1.11803398875
+ (3,3),(1,1) | (3,3),(3,3) | 1.41421356237
+ (-2,2),(-8,-10) | (2,2),(0,0) | 7.81024967591
+ (-2,2),(-8,-10) | (3,3),(1,1) | 9.21954445729
+ (-2,2),(-8,-10) | (-2,2),(-8,-10) | 0
+ (-2,2),(-8,-10) | (2.5,3.5),(2.5,2.5) | 10.2591422643
+ (-2,2),(-8,-10) | (3,3),(3,3) | 10.6301458127
+ (2.5,3.5),(2.5,2.5) | (2,2),(0,0) | 2.5
+ (2.5,3.5),(2.5,2.5) | (3,3),(1,1) | 1.11803398875
+ (2.5,3.5),(2.5,2.5) | (-2,2),(-8,-10) | 10.2591422643
+ (2.5,3.5),(2.5,2.5) | (2.5,3.5),(2.5,2.5) | 0
+ (2.5,3.5),(2.5,2.5) | (3,3),(3,3) | 0.5
+ (3,3),(3,3) | (2,2),(0,0) | 2.82842712475
+ (3,3),(3,3) | (3,3),(1,1) | 1.41421356237
+ (3,3),(3,3) | (-2,2),(-8,-10) | 10.6301458127
+ (3,3),(3,3) | (2.5,3.5),(2.5,2.5) | 0.5
+ (3,3),(3,3) | (3,3),(3,3) | 0
+(25 rows)
+
+--
+-- Paths
+--
+-- Points
+SELECT f1, npoints(f1) FROM PATH_TBL;
+ f1 | npoints
+---------------------------+---------
+ [(1,2),(3,4)] | 2
+ ((1,2),(3,4)) | 2
+ [(0,0),(3,0),(4,5),(1,6)] | 4
+ ((1,2),(3,4)) | 2
+ ((1,2),(3,4)) | 2
+ [(1,2),(3,4)] | 2
+ ((10,20)) | 1
+ [(11,12),(13,14)] | 2
+ ((11,12),(13,14)) | 2
+(9 rows)
+
+-- Area
+SELECT f1, area(f1) FROM PATH_TBL;
+ f1 | area
+---------------------------+------
+ [(1,2),(3,4)] |
+ ((1,2),(3,4)) | 0
+ [(0,0),(3,0),(4,5),(1,6)] |
+ ((1,2),(3,4)) | 0
+ ((1,2),(3,4)) | 0
+ [(1,2),(3,4)] |
+ ((10,20)) | 0
+ [(11,12),(13,14)] |
+ ((11,12),(13,14)) | 0
+(9 rows)
+
+-- Length
+SELECT f1, @-@ f1 FROM PATH_TBL;
+ f1 | ?column?
+---------------------------+---------------
+ [(1,2),(3,4)] | 2.82842712475
+ ((1,2),(3,4)) | 5.65685424949
+ [(0,0),(3,0),(4,5),(1,6)] | 11.2612971738
+ ((1,2),(3,4)) | 5.65685424949
+ ((1,2),(3,4)) | 5.65685424949
+ [(1,2),(3,4)] | 2.82842712475
+ ((10,20)) | 0
+ [(11,12),(13,14)] | 2.82842712475
+ ((11,12),(13,14)) | 5.65685424949
+(9 rows)
+
+-- Center
+SELECT f1, @@ f1 FROM PATH_TBL;
+ERROR: function "path_center" not implemented
+-- To polygon
+SELECT f1, f1::polygon FROM PATH_TBL WHERE isclosed(f1);
+ f1 | f1
+-------------------+-------------------
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((10,20)) | ((10,20))
+ ((11,12),(13,14)) | ((11,12),(13,14))
+(5 rows)
+
+-- Open path cannot be converted to polygon error
+SELECT f1, f1::polygon FROM PATH_TBL WHERE isopen(f1);
+ERROR: open path cannot be converted to polygon
+-- Has points less than path
+SELECT p1.f1, p2.f1 FROM PATH_TBL p1, PATH_TBL p2 WHERE p1.f1 < p2.f1;
+ f1 | f1
+-------------------+---------------------------
+ [(1,2),(3,4)] | [(0,0),(3,0),(4,5),(1,6)]
+ ((1,2),(3,4)) | [(0,0),(3,0),(4,5),(1,6)]
+ ((1,2),(3,4)) | [(0,0),(3,0),(4,5),(1,6)]
+ ((1,2),(3,4)) | [(0,0),(3,0),(4,5),(1,6)]
+ [(1,2),(3,4)] | [(0,0),(3,0),(4,5),(1,6)]
+ ((10,20)) | [(1,2),(3,4)]
+ ((10,20)) | ((1,2),(3,4))
+ ((10,20)) | [(0,0),(3,0),(4,5),(1,6)]
+ ((10,20)) | ((1,2),(3,4))
+ ((10,20)) | ((1,2),(3,4))
+ ((10,20)) | [(1,2),(3,4)]
+ ((10,20)) | [(11,12),(13,14)]
+ ((10,20)) | ((11,12),(13,14))
+ [(11,12),(13,14)] | [(0,0),(3,0),(4,5),(1,6)]
+ ((11,12),(13,14)) | [(0,0),(3,0),(4,5),(1,6)]
+(15 rows)
+
+-- 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;
+ f1 | f1
+---------------------------+---------------------------
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | [(0,0),(3,0),(4,5),(1,6)]
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | [(11,12),(13,14)]
+ [(1,2),(3,4)] | ((11,12),(13,14))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | [(0,0),(3,0),(4,5),(1,6)]
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | [(11,12),(13,14)]
+ ((1,2),(3,4)) | ((11,12),(13,14))
+ [(0,0),(3,0),(4,5),(1,6)] | [(0,0),(3,0),(4,5),(1,6)]
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | [(0,0),(3,0),(4,5),(1,6)]
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | [(11,12),(13,14)]
+ ((1,2),(3,4)) | ((11,12),(13,14))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | [(0,0),(3,0),(4,5),(1,6)]
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | [(11,12),(13,14)]
+ ((1,2),(3,4)) | ((11,12),(13,14))
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | [(0,0),(3,0),(4,5),(1,6)]
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | [(11,12),(13,14)]
+ [(1,2),(3,4)] | ((11,12),(13,14))
+ ((10,20)) | [(1,2),(3,4)]
+ ((10,20)) | ((1,2),(3,4))
+ ((10,20)) | [(0,0),(3,0),(4,5),(1,6)]
+ ((10,20)) | ((1,2),(3,4))
+ ((10,20)) | ((1,2),(3,4))
+ ((10,20)) | [(1,2),(3,4)]
+ ((10,20)) | ((10,20))
+ ((10,20)) | [(11,12),(13,14)]
+ ((10,20)) | ((11,12),(13,14))
+ [(11,12),(13,14)] | [(1,2),(3,4)]
+ [(11,12),(13,14)] | ((1,2),(3,4))
+ [(11,12),(13,14)] | [(0,0),(3,0),(4,5),(1,6)]
+ [(11,12),(13,14)] | ((1,2),(3,4))
+ [(11,12),(13,14)] | ((1,2),(3,4))
+ [(11,12),(13,14)] | [(1,2),(3,4)]
+ [(11,12),(13,14)] | [(11,12),(13,14)]
+ [(11,12),(13,14)] | ((11,12),(13,14))
+ ((11,12),(13,14)) | [(1,2),(3,4)]
+ ((11,12),(13,14)) | ((1,2),(3,4))
+ ((11,12),(13,14)) | [(0,0),(3,0),(4,5),(1,6)]
+ ((11,12),(13,14)) | ((1,2),(3,4))
+ ((11,12),(13,14)) | ((1,2),(3,4))
+ ((11,12),(13,14)) | [(1,2),(3,4)]
+ ((11,12),(13,14)) | [(11,12),(13,14)]
+ ((11,12),(13,14)) | ((11,12),(13,14))
+(66 rows)
+
+-- Has points equal to path
+SELECT p1.f1, p2.f1 FROM PATH_TBL p1, PATH_TBL p2 WHERE p1.f1 = p2.f1;
+ f1 | f1
+---------------------------+---------------------------
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | [(11,12),(13,14)]
+ [(1,2),(3,4)] | ((11,12),(13,14))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | [(11,12),(13,14)]
+ ((1,2),(3,4)) | ((11,12),(13,14))
+ [(0,0),(3,0),(4,5),(1,6)] | [(0,0),(3,0),(4,5),(1,6)]
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | [(11,12),(13,14)]
+ ((1,2),(3,4)) | ((11,12),(13,14))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | [(11,12),(13,14)]
+ ((1,2),(3,4)) | ((11,12),(13,14))
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | [(11,12),(13,14)]
+ [(1,2),(3,4)] | ((11,12),(13,14))
+ ((10,20)) | ((10,20))
+ [(11,12),(13,14)] | [(1,2),(3,4)]
+ [(11,12),(13,14)] | ((1,2),(3,4))
+ [(11,12),(13,14)] | ((1,2),(3,4))
+ [(11,12),(13,14)] | ((1,2),(3,4))
+ [(11,12),(13,14)] | [(1,2),(3,4)]
+ [(11,12),(13,14)] | [(11,12),(13,14)]
+ [(11,12),(13,14)] | ((11,12),(13,14))
+ ((11,12),(13,14)) | [(1,2),(3,4)]
+ ((11,12),(13,14)) | ((1,2),(3,4))
+ ((11,12),(13,14)) | ((1,2),(3,4))
+ ((11,12),(13,14)) | ((1,2),(3,4))
+ ((11,12),(13,14)) | [(1,2),(3,4)]
+ ((11,12),(13,14)) | [(11,12),(13,14)]
+ ((11,12),(13,14)) | ((11,12),(13,14))
+(51 rows)
+
+-- 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;
+ f1 | f1
+---------------------------+---------------------------
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | ((10,20))
+ [(1,2),(3,4)] | [(11,12),(13,14)]
+ [(1,2),(3,4)] | ((11,12),(13,14))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | ((10,20))
+ ((1,2),(3,4)) | [(11,12),(13,14)]
+ ((1,2),(3,4)) | ((11,12),(13,14))
+ [(0,0),(3,0),(4,5),(1,6)] | [(1,2),(3,4)]
+ [(0,0),(3,0),(4,5),(1,6)] | ((1,2),(3,4))
+ [(0,0),(3,0),(4,5),(1,6)] | [(0,0),(3,0),(4,5),(1,6)]
+ [(0,0),(3,0),(4,5),(1,6)] | ((1,2),(3,4))
+ [(0,0),(3,0),(4,5),(1,6)] | ((1,2),(3,4))
+ [(0,0),(3,0),(4,5),(1,6)] | [(1,2),(3,4)]
+ [(0,0),(3,0),(4,5),(1,6)] | ((10,20))
+ [(0,0),(3,0),(4,5),(1,6)] | [(11,12),(13,14)]
+ [(0,0),(3,0),(4,5),(1,6)] | ((11,12),(13,14))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | ((10,20))
+ ((1,2),(3,4)) | [(11,12),(13,14)]
+ ((1,2),(3,4)) | ((11,12),(13,14))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | ((1,2),(3,4))
+ ((1,2),(3,4)) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | ((10,20))
+ ((1,2),(3,4)) | [(11,12),(13,14)]
+ ((1,2),(3,4)) | ((11,12),(13,14))
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(1,2),(3,4)] | [(1,2),(3,4)]
+ [(1,2),(3,4)] | ((10,20))
+ [(1,2),(3,4)] | [(11,12),(13,14)]
+ [(1,2),(3,4)] | ((11,12),(13,14))
+ ((10,20)) | ((10,20))
+ [(11,12),(13,14)] | [(1,2),(3,4)]
+ [(11,12),(13,14)] | ((1,2),(3,4))
+ [(11,12),(13,14)] | ((1,2),(3,4))
+ [(11,12),(13,14)] | ((1,2),(3,4))
+ [(11,12),(13,14)] | [(1,2),(3,4)]
+ [(11,12),(13,14)] | ((10,20))
+ [(11,12),(13,14)] | [(11,12),(13,14)]
+ [(11,12),(13,14)] | ((11,12),(13,14))
+ ((11,12),(13,14)) | [(1,2),(3,4)]
+ ((11,12),(13,14)) | ((1,2),(3,4))
+ ((11,12),(13,14)) | ((1,2),(3,4))
+ ((11,12),(13,14)) | ((1,2),(3,4))
+ ((11,12),(13,14)) | [(1,2),(3,4)]
+ ((11,12),(13,14)) | ((10,20))
+ ((11,12),(13,14)) | [(11,12),(13,14)]
+ ((11,12),(13,14)) | ((11,12),(13,14))
+(66 rows)
+
+-- Has points greater than path
+SELECT p1.f1, p2.f1 FROM PATH_TBL p1, PATH_TBL p2 WHERE p1.f1 > p2.f1;
+ f1 | f1
+---------------------------+-------------------
+ [(1,2),(3,4)] | ((10,20))
+ ((1,2),(3,4)) | ((10,20))
+ [(0,0),(3,0),(4,5),(1,6)] | [(1,2),(3,4)]
+ [(0,0),(3,0),(4,5),(1,6)] | ((1,2),(3,4))
+ [(0,0),(3,0),(4,5),(1,6)] | ((1,2),(3,4))
+ [(0,0),(3,0),(4,5),(1,6)] | ((1,2),(3,4))
+ [(0,0),(3,0),(4,5),(1,6)] | [(1,2),(3,4)]
+ [(0,0),(3,0),(4,5),(1,6)] | ((10,20))
+ [(0,0),(3,0),(4,5),(1,6)] | [(11,12),(13,14)]
+ [(0,0),(3,0),(4,5),(1,6)] | ((11,12),(13,14))
+ ((1,2),(3,4)) | ((10,20))
+ ((1,2),(3,4)) | ((10,20))
+ [(1,2),(3,4)] | ((10,20))
+ [(11,12),(13,14)] | ((10,20))
+ ((11,12),(13,14)) | ((10,20))
+(15 rows)
+
+-- Add path
+SELECT p1.f1, p2.f1, p1.f1 + p2.f1 FROM PATH_TBL p1, PATH_TBL p2;
+ f1 | f1 | ?column?
+---------------------------+---------------------------+---------------------------------------------------
+ [(1,2),(3,4)] | [(1,2),(3,4)] | [(1,2),(3,4),(1,2),(3,4)]
+ [(1,2),(3,4)] | ((1,2),(3,4)) |
+ [(1,2),(3,4)] | [(0,0),(3,0),(4,5),(1,6)] | [(1,2),(3,4),(0,0),(3,0),(4,5),(1,6)]
+ [(1,2),(3,4)] | ((1,2),(3,4)) |
+ [(1,2),(3,4)] | ((1,2),(3,4)) |
+ [(1,2),(3,4)] | [(1,2),(3,4)] | [(1,2),(3,4),(1,2),(3,4)]
+ [(1,2),(3,4)] | ((10,20)) |
+ [(1,2),(3,4)] | [(11,12),(13,14)] | [(1,2),(3,4),(11,12),(13,14)]
+ [(1,2),(3,4)] | ((11,12),(13,14)) |
+ ((1,2),(3,4)) | [(1,2),(3,4)] |
+ ((1,2),(3,4)) | ((1,2),(3,4)) |
+ ((1,2),(3,4)) | [(0,0),(3,0),(4,5),(1,6)] |
+ ((1,2),(3,4)) | ((1,2),(3,4)) |
+ ((1,2),(3,4)) | ((1,2),(3,4)) |
+ ((1,2),(3,4)) | [(1,2),(3,4)] |
+ ((1,2),(3,4)) | ((10,20)) |
+ ((1,2),(3,4)) | [(11,12),(13,14)] |
+ ((1,2),(3,4)) | ((11,12),(13,14)) |
+ [(0,0),(3,0),(4,5),(1,6)] | [(1,2),(3,4)] | [(0,0),(3,0),(4,5),(1,6),(1,2),(3,4)]
+ [(0,0),(3,0),(4,5),(1,6)] | ((1,2),(3,4)) |
+ [(0,0),(3,0),(4,5),(1,6)] | [(0,0),(3,0),(4,5),(1,6)] | [(0,0),(3,0),(4,5),(1,6),(0,0),(3,0),(4,5),(1,6)]
+ [(0,0),(3,0),(4,5),(1,6)] | ((1,2),(3,4)) |
+ [(0,0),(3,0),(4,5),(1,6)] | ((1,2),(3,4)) |
+ [(0,0),(3,0),(4,5),(1,6)] | [(1,2),(3,4)] | [(0,0),(3,0),(4,5),(1,6),(1,2),(3,4)]
+ [(0,0),(3,0),(4,5),(1,6)] | ((10,20)) |
+ [(0,0),(3,0),(4,5),(1,6)] | [(11,12),(13,14)] | [(0,0),(3,0),(4,5),(1,6),(11,12),(13,14)]
+ [(0,0),(3,0),(4,5),(1,6)] | ((11,12),(13,14)) |
+ ((1,2),(3,4)) | [(1,2),(3,4)] |
+ ((1,2),(3,4)) | ((1,2),(3,4)) |
+ ((1,2),(3,4)) | [(0,0),(3,0),(4,5),(1,6)] |
+ ((1,2),(3,4)) | ((1,2),(3,4)) |
+ ((1,2),(3,4)) | ((1,2),(3,4)) |
+ ((1,2),(3,4)) | [(1,2),(3,4)] |
+ ((1,2),(3,4)) | ((10,20)) |
+ ((1,2),(3,4)) | [(11,12),(13,14)] |
+ ((1,2),(3,4)) | ((11,12),(13,14)) |
+ ((1,2),(3,4)) | [(1,2),(3,4)] |
+ ((1,2),(3,4)) | ((1,2),(3,4)) |
+ ((1,2),(3,4)) | [(0,0),(3,0),(4,5),(1,6)] |
+ ((1,2),(3,4)) | ((1,2),(3,4)) |
+ ((1,2),(3,4)) | ((1,2),(3,4)) |
+ ((1,2),(3,4)) | [(1,2),(3,4)] |
+ ((1,2),(3,4)) | ((10,20)) |
+ ((1,2),(3,4)) | [(11,12),(13,14)] |
+ ((1,2),(3,4)) | ((11,12),(13,14)) |
+ [(1,2),(3,4)] | [(1,2),(3,4)] | [(1,2),(3,4),(1,2),(3,4)]
+ [(1,2),(3,4)] | ((1,2),(3,4)) |
+ [(1,2),(3,4)] | [(0,0),(3,0),(4,5),(1,6)] | [(1,2),(3,4),(0,0),(3,0),(4,5),(1,6)]
+ [(1,2),(3,4)] | ((1,2),(3,4)) |
+ [(1,2),(3,4)] | ((1,2),(3,4)) |
+ [(1,2),(3,4)] | [(1,2),(3,4)] | [(1,2),(3,4),(1,2),(3,4)]
+ [(1,2),(3,4)] | ((10,20)) |
+ [(1,2),(3,4)] | [(11,12),(13,14)] | [(1,2),(3,4),(11,12),(13,14)]
+ [(1,2),(3,4)] | ((11,12),(13,14)) |
+ ((10,20)) | [(1,2),(3,4)] |
+ ((10,20)) | ((1,2),(3,4)) |
+ ((10,20)) | [(0,0),(3,0),(4,5),(1,6)] |
+ ((10,20)) | ((1,2),(3,4)) |
+ ((10,20)) | ((1,2),(3,4)) |
+ ((10,20)) | [(1,2),(3,4)] |
+ ((10,20)) | ((10,20)) |
+ ((10,20)) | [(11,12),(13,14)] |
+ ((10,20)) | ((11,12),(13,14)) |
+ [(11,12),(13,14)] | [(1,2),(3,4)] | [(11,12),(13,14),(1,2),(3,4)]
+ [(11,12),(13,14)] | ((1,2),(3,4)) |
+ [(11,12),(13,14)] | [(0,0),(3,0),(4,5),(1,6)] | [(11,12),(13,14),(0,0),(3,0),(4,5),(1,6)]
+ [(11,12),(13,14)] | ((1,2),(3,4)) |
+ [(11,12),(13,14)] | ((1,2),(3,4)) |
+ [(11,12),(13,14)] | [(1,2),(3,4)] | [(11,12),(13,14),(1,2),(3,4)]
+ [(11,12),(13,14)] | ((10,20)) |
+ [(11,12),(13,14)] | [(11,12),(13,14)] | [(11,12),(13,14),(11,12),(13,14)]
+ [(11,12),(13,14)] | ((11,12),(13,14)) |
+ ((11,12),(13,14)) | [(1,2),(3,4)] |
+ ((11,12),(13,14)) | ((1,2),(3,4)) |
+ ((11,12),(13,14)) | [(0,0),(3,0),(4,5),(1,6)] |
+ ((11,12),(13,14)) | ((1,2),(3,4)) |
+ ((11,12),(13,14)) | ((1,2),(3,4)) |
+ ((11,12),(13,14)) | [(1,2),(3,4)] |
+ ((11,12),(13,14)) | ((10,20)) |
+ ((11,12),(13,14)) | [(11,12),(13,14)] |
+ ((11,12),(13,14)) | ((11,12),(13,14)) |
+(81 rows)
+
+-- Add point
+SELECT p.f1, p1.f1, p.f1 + p1.f1 FROM PATH_TBL p, POINT_TBL p1;
+ f1 | f1 | ?column?
+---------------------------+-------------------+---------------------------------------------------------------------------
+ [(1,2),(3,4)] | (0,0) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | (0,0) | ((1,2),(3,4))
+ [(0,0),(3,0),(4,5),(1,6)] | (0,0) | [(0,0),(3,0),(4,5),(1,6)]
+ ((1,2),(3,4)) | (0,0) | ((1,2),(3,4))
+ ((1,2),(3,4)) | (0,0) | ((1,2),(3,4))
+ [(1,2),(3,4)] | (0,0) | [(1,2),(3,4)]
+ ((10,20)) | (0,0) | ((10,20))
+ [(11,12),(13,14)] | (0,0) | [(11,12),(13,14)]
+ ((11,12),(13,14)) | (0,0) | ((11,12),(13,14))
+ [(1,2),(3,4)] | (-10,0) | [(-9,2),(-7,4)]
+ ((1,2),(3,4)) | (-10,0) | ((-9,2),(-7,4))
+ [(0,0),(3,0),(4,5),(1,6)] | (-10,0) | [(-10,0),(-7,0),(-6,5),(-9,6)]
+ ((1,2),(3,4)) | (-10,0) | ((-9,2),(-7,4))
+ ((1,2),(3,4)) | (-10,0) | ((-9,2),(-7,4))
+ [(1,2),(3,4)] | (-10,0) | [(-9,2),(-7,4)]
+ ((10,20)) | (-10,0) | ((0,20))
+ [(11,12),(13,14)] | (-10,0) | [(1,12),(3,14)]
+ ((11,12),(13,14)) | (-10,0) | ((1,12),(3,14))
+ [(1,2),(3,4)] | (-3,4) | [(-2,6),(0,8)]
+ ((1,2),(3,4)) | (-3,4) | ((-2,6),(0,8))
+ [(0,0),(3,0),(4,5),(1,6)] | (-3,4) | [(-3,4),(0,4),(1,9),(-2,10)]
+ ((1,2),(3,4)) | (-3,4) | ((-2,6),(0,8))
+ ((1,2),(3,4)) | (-3,4) | ((-2,6),(0,8))
+ [(1,2),(3,4)] | (-3,4) | [(-2,6),(0,8)]
+ ((10,20)) | (-3,4) | ((7,24))
+ [(11,12),(13,14)] | (-3,4) | [(8,16),(10,18)]
+ ((11,12),(13,14)) | (-3,4) | ((8,16),(10,18))
+ [(1,2),(3,4)] | (5.1,34.5) | [(6.1,36.5),(8.1,38.5)]
+ ((1,2),(3,4)) | (5.1,34.5) | ((6.1,36.5),(8.1,38.5))
+ [(0,0),(3,0),(4,5),(1,6)] | (5.1,34.5) | [(5.1,34.5),(8.1,34.5),(9.1,39.5),(6.1,40.5)]
+ ((1,2),(3,4)) | (5.1,34.5) | ((6.1,36.5),(8.1,38.5))
+ ((1,2),(3,4)) | (5.1,34.5) | ((6.1,36.5),(8.1,38.5))
+ [(1,2),(3,4)] | (5.1,34.5) | [(6.1,36.5),(8.1,38.5)]
+ ((10,20)) | (5.1,34.5) | ((15.1,54.5))
+ [(11,12),(13,14)] | (5.1,34.5) | [(16.1,46.5),(18.1,48.5)]
+ ((11,12),(13,14)) | (5.1,34.5) | ((16.1,46.5),(18.1,48.5))
+ [(1,2),(3,4)] | (-5,-12) | [(-4,-10),(-2,-8)]
+ ((1,2),(3,4)) | (-5,-12) | ((-4,-10),(-2,-8))
+ [(0,0),(3,0),(4,5),(1,6)] | (-5,-12) | [(-5,-12),(-2,-12),(-1,-7),(-4,-6)]
+ ((1,2),(3,4)) | (-5,-12) | ((-4,-10),(-2,-8))
+ ((1,2),(3,4)) | (-5,-12) | ((-4,-10),(-2,-8))
+ [(1,2),(3,4)] | (-5,-12) | [(-4,-10),(-2,-8)]
+ ((10,20)) | (-5,-12) | ((5,8))
+ [(11,12),(13,14)] | (-5,-12) | [(6,0),(8,2)]
+ ((11,12),(13,14)) | (-5,-12) | ((6,0),(8,2))
+ [(1,2),(3,4)] | (1e-300,-1e-300) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | (1e-300,-1e-300) | ((1,2),(3,4))
+ [(0,0),(3,0),(4,5),(1,6)] | (1e-300,-1e-300) | [(1e-300,-1e-300),(3,-1e-300),(4,5),(1,6)]
+ ((1,2),(3,4)) | (1e-300,-1e-300) | ((1,2),(3,4))
+ ((1,2),(3,4)) | (1e-300,-1e-300) | ((1,2),(3,4))
+ [(1,2),(3,4)] | (1e-300,-1e-300) | [(1,2),(3,4)]
+ ((10,20)) | (1e-300,-1e-300) | ((10,20))
+ [(11,12),(13,14)] | (1e-300,-1e-300) | [(11,12),(13,14)]
+ ((11,12),(13,14)) | (1e-300,-1e-300) | ((11,12),(13,14))
+ [(1,2),(3,4)] | (1e+300,Infinity) | [(1e+300,Infinity),(1e+300,Infinity)]
+ ((1,2),(3,4)) | (1e+300,Infinity) | ((1e+300,Infinity),(1e+300,Infinity))
+ [(0,0),(3,0),(4,5),(1,6)] | (1e+300,Infinity) | [(1e+300,Infinity),(1e+300,Infinity),(1e+300,Infinity),(1e+300,Infinity)]
+ ((1,2),(3,4)) | (1e+300,Infinity) | ((1e+300,Infinity),(1e+300,Infinity))
+ ((1,2),(3,4)) | (1e+300,Infinity) | ((1e+300,Infinity),(1e+300,Infinity))
+ [(1,2),(3,4)] | (1e+300,Infinity) | [(1e+300,Infinity),(1e+300,Infinity)]
+ ((10,20)) | (1e+300,Infinity) | ((1e+300,Infinity))
+ [(11,12),(13,14)] | (1e+300,Infinity) | [(1e+300,Infinity),(1e+300,Infinity)]
+ ((11,12),(13,14)) | (1e+300,Infinity) | ((1e+300,Infinity),(1e+300,Infinity))
+ [(1,2),(3,4)] | (Infinity,1e+300) | [(Infinity,1e+300),(Infinity,1e+300)]
+ ((1,2),(3,4)) | (Infinity,1e+300) | ((Infinity,1e+300),(Infinity,1e+300))
+ [(0,0),(3,0),(4,5),(1,6)] | (Infinity,1e+300) | [(Infinity,1e+300),(Infinity,1e+300),(Infinity,1e+300),(Infinity,1e+300)]
+ ((1,2),(3,4)) | (Infinity,1e+300) | ((Infinity,1e+300),(Infinity,1e+300))
+ ((1,2),(3,4)) | (Infinity,1e+300) | ((Infinity,1e+300),(Infinity,1e+300))
+ [(1,2),(3,4)] | (Infinity,1e+300) | [(Infinity,1e+300),(Infinity,1e+300)]
+ ((10,20)) | (Infinity,1e+300) | ((Infinity,1e+300))
+ [(11,12),(13,14)] | (Infinity,1e+300) | [(Infinity,1e+300),(Infinity,1e+300)]
+ ((11,12),(13,14)) | (Infinity,1e+300) | ((Infinity,1e+300),(Infinity,1e+300))
+ [(1,2),(3,4)] | (NaN,NaN) | [(NaN,NaN),(NaN,NaN)]
+ ((1,2),(3,4)) | (NaN,NaN) | ((NaN,NaN),(NaN,NaN))
+ [(0,0),(3,0),(4,5),(1,6)] | (NaN,NaN) | [(NaN,NaN),(NaN,NaN),(NaN,NaN),(NaN,NaN)]
+ ((1,2),(3,4)) | (NaN,NaN) | ((NaN,NaN),(NaN,NaN))
+ ((1,2),(3,4)) | (NaN,NaN) | ((NaN,NaN),(NaN,NaN))
+ [(1,2),(3,4)] | (NaN,NaN) | [(NaN,NaN),(NaN,NaN)]
+ ((10,20)) | (NaN,NaN) | ((NaN,NaN))
+ [(11,12),(13,14)] | (NaN,NaN) | [(NaN,NaN),(NaN,NaN)]
+ ((11,12),(13,14)) | (NaN,NaN) | ((NaN,NaN),(NaN,NaN))
+ [(1,2),(3,4)] | (10,10) | [(11,12),(13,14)]
+ ((1,2),(3,4)) | (10,10) | ((11,12),(13,14))
+ [(0,0),(3,0),(4,5),(1,6)] | (10,10) | [(10,10),(13,10),(14,15),(11,16)]
+ ((1,2),(3,4)) | (10,10) | ((11,12),(13,14))
+ ((1,2),(3,4)) | (10,10) | ((11,12),(13,14))
+ [(1,2),(3,4)] | (10,10) | [(11,12),(13,14)]
+ ((10,20)) | (10,10) | ((20,30))
+ [(11,12),(13,14)] | (10,10) | [(21,22),(23,24)]
+ ((11,12),(13,14)) | (10,10) | ((21,22),(23,24))
+(90 rows)
+
+-- Subtract point
+SELECT p.f1, p1.f1, p.f1 - p1.f1 FROM PATH_TBL p, POINT_TBL p1;
+ f1 | f1 | ?column?
+---------------------------+-------------------+-----------------------------------------------------------------------------------
+ [(1,2),(3,4)] | (0,0) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | (0,0) | ((1,2),(3,4))
+ [(0,0),(3,0),(4,5),(1,6)] | (0,0) | [(0,0),(3,0),(4,5),(1,6)]
+ ((1,2),(3,4)) | (0,0) | ((1,2),(3,4))
+ ((1,2),(3,4)) | (0,0) | ((1,2),(3,4))
+ [(1,2),(3,4)] | (0,0) | [(1,2),(3,4)]
+ ((10,20)) | (0,0) | ((10,20))
+ [(11,12),(13,14)] | (0,0) | [(11,12),(13,14)]
+ ((11,12),(13,14)) | (0,0) | ((11,12),(13,14))
+ [(1,2),(3,4)] | (-10,0) | [(11,2),(13,4)]
+ ((1,2),(3,4)) | (-10,0) | ((11,2),(13,4))
+ [(0,0),(3,0),(4,5),(1,6)] | (-10,0) | [(10,0),(13,0),(14,5),(11,6)]
+ ((1,2),(3,4)) | (-10,0) | ((11,2),(13,4))
+ ((1,2),(3,4)) | (-10,0) | ((11,2),(13,4))
+ [(1,2),(3,4)] | (-10,0) | [(11,2),(13,4)]
+ ((10,20)) | (-10,0) | ((20,20))
+ [(11,12),(13,14)] | (-10,0) | [(21,12),(23,14)]
+ ((11,12),(13,14)) | (-10,0) | ((21,12),(23,14))
+ [(1,2),(3,4)] | (-3,4) | [(4,-2),(6,0)]
+ ((1,2),(3,4)) | (-3,4) | ((4,-2),(6,0))
+ [(0,0),(3,0),(4,5),(1,6)] | (-3,4) | [(3,-4),(6,-4),(7,1),(4,2)]
+ ((1,2),(3,4)) | (-3,4) | ((4,-2),(6,0))
+ ((1,2),(3,4)) | (-3,4) | ((4,-2),(6,0))
+ [(1,2),(3,4)] | (-3,4) | [(4,-2),(6,0)]
+ ((10,20)) | (-3,4) | ((13,16))
+ [(11,12),(13,14)] | (-3,4) | [(14,8),(16,10)]
+ ((11,12),(13,14)) | (-3,4) | ((14,8),(16,10))
+ [(1,2),(3,4)] | (5.1,34.5) | [(-4.1,-32.5),(-2.1,-30.5)]
+ ((1,2),(3,4)) | (5.1,34.5) | ((-4.1,-32.5),(-2.1,-30.5))
+ [(0,0),(3,0),(4,5),(1,6)] | (5.1,34.5) | [(-5.1,-34.5),(-2.1,-34.5),(-1.1,-29.5),(-4.1,-28.5)]
+ ((1,2),(3,4)) | (5.1,34.5) | ((-4.1,-32.5),(-2.1,-30.5))
+ ((1,2),(3,4)) | (5.1,34.5) | ((-4.1,-32.5),(-2.1,-30.5))
+ [(1,2),(3,4)] | (5.1,34.5) | [(-4.1,-32.5),(-2.1,-30.5)]
+ ((10,20)) | (5.1,34.5) | ((4.9,-14.5))
+ [(11,12),(13,14)] | (5.1,34.5) | [(5.9,-22.5),(7.9,-20.5)]
+ ((11,12),(13,14)) | (5.1,34.5) | ((5.9,-22.5),(7.9,-20.5))
+ [(1,2),(3,4)] | (-5,-12) | [(6,14),(8,16)]
+ ((1,2),(3,4)) | (-5,-12) | ((6,14),(8,16))
+ [(0,0),(3,0),(4,5),(1,6)] | (-5,-12) | [(5,12),(8,12),(9,17),(6,18)]
+ ((1,2),(3,4)) | (-5,-12) | ((6,14),(8,16))
+ ((1,2),(3,4)) | (-5,-12) | ((6,14),(8,16))
+ [(1,2),(3,4)] | (-5,-12) | [(6,14),(8,16)]
+ ((10,20)) | (-5,-12) | ((15,32))
+ [(11,12),(13,14)] | (-5,-12) | [(16,24),(18,26)]
+ ((11,12),(13,14)) | (-5,-12) | ((16,24),(18,26))
+ [(1,2),(3,4)] | (1e-300,-1e-300) | [(1,2),(3,4)]
+ ((1,2),(3,4)) | (1e-300,-1e-300) | ((1,2),(3,4))
+ [(0,0),(3,0),(4,5),(1,6)] | (1e-300,-1e-300) | [(-1e-300,1e-300),(3,1e-300),(4,5),(1,6)]
+ ((1,2),(3,4)) | (1e-300,-1e-300) | ((1,2),(3,4))
+ ((1,2),(3,4)) | (1e-300,-1e-300) | ((1,2),(3,4))
+ [(1,2),(3,4)] | (1e-300,-1e-300) | [(1,2),(3,4)]
+ ((10,20)) | (1e-300,-1e-300) | ((10,20))
+ [(11,12),(13,14)] | (1e-300,-1e-300) | [(11,12),(13,14)]
+ ((11,12),(13,14)) | (1e-300,-1e-300) | ((11,12),(13,14))
+ [(1,2),(3,4)] | (1e+300,Infinity) | [(-1e+300,-Infinity),(-1e+300,-Infinity)]
+ ((1,2),(3,4)) | (1e+300,Infinity) | ((-1e+300,-Infinity),(-1e+300,-Infinity))
+ [(0,0),(3,0),(4,5),(1,6)] | (1e+300,Infinity) | [(-1e+300,-Infinity),(-1e+300,-Infinity),(-1e+300,-Infinity),(-1e+300,-Infinity)]
+ ((1,2),(3,4)) | (1e+300,Infinity) | ((-1e+300,-Infinity),(-1e+300,-Infinity))
+ ((1,2),(3,4)) | (1e+300,Infinity) | ((-1e+300,-Infinity),(-1e+300,-Infinity))
+ [(1,2),(3,4)] | (1e+300,Infinity) | [(-1e+300,-Infinity),(-1e+300,-Infinity)]
+ ((10,20)) | (1e+300,Infinity) | ((-1e+300,-Infinity))
+ [(11,12),(13,14)] | (1e+300,Infinity) | [(-1e+300,-Infinity),(-1e+300,-Infinity)]
+ ((11,12),(13,14)) | (1e+300,Infinity) | ((-1e+300,-Infinity),(-1e+300,-Infinity))
+ [(1,2),(3,4)] | (Infinity,1e+300) | [(-Infinity,-1e+300),(-Infinity,-1e+300)]
+ ((1,2),(3,4)) | (Infinity,1e+300) | ((-Infinity,-1e+300),(-Infinity,-1e+300))
+ [(0,0),(3,0),(4,5),(1,6)] | (Infinity,1e+300) | [(-Infinity,-1e+300),(-Infinity,-1e+300),(-Infinity,-1e+300),(-Infinity,-1e+300)]
+ ((1,2),(3,4)) | (Infinity,1e+300) | ((-Infinity,-1e+300),(-Infinity,-1e+300))
+ ((1,2),(3,4)) | (Infinity,1e+300) | ((-Infinity,-1e+300),(-Infinity,-1e+300))
+ [(1,2),(3,4)] | (Infinity,1e+300) | [(-Infinity,-1e+300),(-Infinity,-1e+300)]
+ ((10,20)) | (Infinity,1e+300) | ((-Infinity,-1e+300))
+ [(11,12),(13,14)] | (Infinity,1e+300) | [(-Infinity,-1e+300),(-Infinity,-1e+300)]
+ ((11,12),(13,14)) | (Infinity,1e+300) | ((-Infinity,-1e+300),(-Infinity,-1e+300))
+ [(1,2),(3,4)] | (NaN,NaN) | [(NaN,NaN),(NaN,NaN)]
+ ((1,2),(3,4)) | (NaN,NaN) | ((NaN,NaN),(NaN,NaN))
+ [(0,0),(3,0),(4,5),(1,6)] | (NaN,NaN) | [(NaN,NaN),(NaN,NaN),(NaN,NaN),(NaN,NaN)]
+ ((1,2),(3,4)) | (NaN,NaN) | ((NaN,NaN),(NaN,NaN))
+ ((1,2),(3,4)) | (NaN,NaN) | ((NaN,NaN),(NaN,NaN))
+ [(1,2),(3,4)] | (NaN,NaN) | [(NaN,NaN),(NaN,NaN)]
+ ((10,20)) | (NaN,NaN) | ((NaN,NaN))
+ [(11,12),(13,14)] | (NaN,NaN) | [(NaN,NaN),(NaN,NaN)]
+ ((11,12),(13,14)) | (NaN,NaN) | ((NaN,NaN),(NaN,NaN))
+ [(1,2),(3,4)] | (10,10) | [(-9,-8),(-7,-6)]
+ ((1,2),(3,4)) | (10,10) | ((-9,-8),(-7,-6))
+ [(0,0),(3,0),(4,5),(1,6)] | (10,10) | [(-10,-10),(-7,-10),(-6,-5),(-9,-4)]
+ ((1,2),(3,4)) | (10,10) | ((-9,-8),(-7,-6))
+ ((1,2),(3,4)) | (10,10) | ((-9,-8),(-7,-6))
+ [(1,2),(3,4)] | (10,10) | [(-9,-8),(-7,-6)]
+ ((10,20)) | (10,10) | ((0,10))
+ [(11,12),(13,14)] | (10,10) | [(1,2),(3,4)]
+ ((11,12),(13,14)) | (10,10) | ((1,2),(3,4))
+(90 rows)
+
+-- Multiply with point
+SELECT p.f1, p1.f1, p.f1 * p1.f1 FROM PATH_TBL p, POINT_TBL p1;
+ f1 | f1 | ?column?
+---------------------------+-------------------+----------------------------------------------------------------------
+ [(1,2),(3,4)] | (0,0) | [(0,0),(0,0)]
+ ((1,2),(3,4)) | (0,0) | ((0,0),(0,0))
+ [(0,0),(3,0),(4,5),(1,6)] | (0,0) | [(0,0),(0,0),(0,0),(0,0)]
+ ((1,2),(3,4)) | (0,0) | ((0,0),(0,0))
+ ((1,2),(3,4)) | (0,0) | ((0,0),(0,0))
+ [(1,2),(3,4)] | (0,0) | [(0,0),(0,0)]
+ ((10,20)) | (0,0) | ((0,0))
+ [(11,12),(13,14)] | (0,0) | [(0,0),(0,0)]
+ ((11,12),(13,14)) | (0,0) | ((0,0),(0,0))
+ [(1,2),(3,4)] | (-10,0) | [(-10,-20),(-30,-40)]
+ ((1,2),(3,4)) | (-10,0) | ((-10,-20),(-30,-40))
+ [(0,0),(3,0),(4,5),(1,6)] | (-10,0) | [(-0,0),(-30,0),(-40,-50),(-10,-60)]
+ ((1,2),(3,4)) | (-10,0) | ((-10,-20),(-30,-40))
+ ((1,2),(3,4)) | (-10,0) | ((-10,-20),(-30,-40))
+ [(1,2),(3,4)] | (-10,0) | [(-10,-20),(-30,-40)]
+ ((10,20)) | (-10,0) | ((-100,-200))
+ [(11,12),(13,14)] | (-10,0) | [(-110,-120),(-130,-140)]
+ ((11,12),(13,14)) | (-10,0) | ((-110,-120),(-130,-140))
+ [(1,2),(3,4)] | (-3,4) | [(-11,-2),(-25,0)]
+ ((1,2),(3,4)) | (-3,4) | ((-11,-2),(-25,0))
+ [(0,0),(3,0),(4,5),(1,6)] | (-3,4) | [(-0,0),(-9,12),(-32,1),(-27,-14)]
+ ((1,2),(3,4)) | (-3,4) | ((-11,-2),(-25,0))
+ ((1,2),(3,4)) | (-3,4) | ((-11,-2),(-25,0))
+ [(1,2),(3,4)] | (-3,4) | [(-11,-2),(-25,0)]
+ ((10,20)) | (-3,4) | ((-110,-20))
+ [(11,12),(13,14)] | (-3,4) | [(-81,8),(-95,10)]
+ ((11,12),(13,14)) | (-3,4) | ((-81,8),(-95,10))
+ [(1,2),(3,4)] | (5.1,34.5) | [(-63.9,44.7),(-122.7,123.9)]
+ ((1,2),(3,4)) | (5.1,34.5) | ((-63.9,44.7),(-122.7,123.9))
+ [(0,0),(3,0),(4,5),(1,6)] | (5.1,34.5) | [(0,0),(15.3,103.5),(-152.1,163.5),(-201.9,65.1)]
+ ((1,2),(3,4)) | (5.1,34.5) | ((-63.9,44.7),(-122.7,123.9))
+ ((1,2),(3,4)) | (5.1,34.5) | ((-63.9,44.7),(-122.7,123.9))
+ [(1,2),(3,4)] | (5.1,34.5) | [(-63.9,44.7),(-122.7,123.9)]
+ ((10,20)) | (5.1,34.5) | ((-639,447))
+ [(11,12),(13,14)] | (5.1,34.5) | [(-357.9,440.7),(-416.7,519.9)]
+ ((11,12),(13,14)) | (5.1,34.5) | ((-357.9,440.7),(-416.7,519.9))
+ [(1,2),(3,4)] | (-5,-12) | [(19,-22),(33,-56)]
+ ((1,2),(3,4)) | (-5,-12) | ((19,-22),(33,-56))
+ [(0,0),(3,0),(4,5),(1,6)] | (-5,-12) | [(0,-0),(-15,-36),(40,-73),(67,-42)]
+ ((1,2),(3,4)) | (-5,-12) | ((19,-22),(33,-56))
+ ((1,2),(3,4)) | (-5,-12) | ((19,-22),(33,-56))
+ [(1,2),(3,4)] | (-5,-12) | [(19,-22),(33,-56)]
+ ((10,20)) | (-5,-12) | ((190,-220))
+ [(11,12),(13,14)] | (-5,-12) | [(89,-192),(103,-226)]
+ ((11,12),(13,14)) | (-5,-12) | ((89,-192),(103,-226))
+ [(1,2),(3,4)] | (1e-300,-1e-300) | [(3e-300,1e-300),(7e-300,1e-300)]
+ ((1,2),(3,4)) | (1e-300,-1e-300) | ((3e-300,1e-300),(7e-300,1e-300))
+ [(0,0),(3,0),(4,5),(1,6)] | (1e-300,-1e-300) | [(0,0),(3e-300,-3e-300),(9e-300,1e-300),(7e-300,5e-300)]
+ ((1,2),(3,4)) | (1e-300,-1e-300) | ((3e-300,1e-300),(7e-300,1e-300))
+ ((1,2),(3,4)) | (1e-300,-1e-300) | ((3e-300,1e-300),(7e-300,1e-300))
+ [(1,2),(3,4)] | (1e-300,-1e-300) | [(3e-300,1e-300),(7e-300,1e-300)]
+ ((10,20)) | (1e-300,-1e-300) | ((3e-299,1e-299))
+ [(11,12),(13,14)] | (1e-300,-1e-300) | [(2.3e-299,1e-300),(2.7e-299,1e-300)]
+ ((11,12),(13,14)) | (1e-300,-1e-300) | ((2.3e-299,1e-300),(2.7e-299,1e-300))
+ [(1,2),(3,4)] | (1e+300,Infinity) | [(-Infinity,Infinity),(-Infinity,Infinity)]
+ ((1,2),(3,4)) | (1e+300,Infinity) | ((-Infinity,Infinity),(-Infinity,Infinity))
+ [(0,0),(3,0),(4,5),(1,6)] | (1e+300,Infinity) | [(NaN,NaN),(NaN,Infinity),(-Infinity,Infinity),(-Infinity,Infinity)]
+ ((1,2),(3,4)) | (1e+300,Infinity) | ((-Infinity,Infinity),(-Infinity,Infinity))
+ ((1,2),(3,4)) | (1e+300,Infinity) | ((-Infinity,Infinity),(-Infinity,Infinity))
+ [(1,2),(3,4)] | (1e+300,Infinity) | [(-Infinity,Infinity),(-Infinity,Infinity)]
+ ((10,20)) | (1e+300,Infinity) | ((-Infinity,Infinity))
+ [(11,12),(13,14)] | (1e+300,Infinity) | [(-Infinity,Infinity),(-Infinity,Infinity)]
+ ((11,12),(13,14)) | (1e+300,Infinity) | ((-Infinity,Infinity),(-Infinity,Infinity))
+ [(1,2),(3,4)] | (Infinity,1e+300) | [(Infinity,Infinity),(Infinity,Infinity)]
+ ((1,2),(3,4)) | (Infinity,1e+300) | ((Infinity,Infinity),(Infinity,Infinity))
+ [(0,0),(3,0),(4,5),(1,6)] | (Infinity,1e+300) | [(NaN,NaN),(Infinity,NaN),(Infinity,Infinity),(Infinity,Infinity)]
+ ((1,2),(3,4)) | (Infinity,1e+300) | ((Infinity,Infinity),(Infinity,Infinity))
+ ((1,2),(3,4)) | (Infinity,1e+300) | ((Infinity,Infinity),(Infinity,Infinity))
+ [(1,2),(3,4)] | (Infinity,1e+300) | [(Infinity,Infinity),(Infinity,Infinity)]
+ ((10,20)) | (Infinity,1e+300) | ((Infinity,Infinity))
+ [(11,12),(13,14)] | (Infinity,1e+300) | [(Infinity,Infinity),(Infinity,Infinity)]
+ ((11,12),(13,14)) | (Infinity,1e+300) | ((Infinity,Infinity),(Infinity,Infinity))
+ [(1,2),(3,4)] | (NaN,NaN) | [(NaN,NaN),(NaN,NaN)]
+ ((1,2),(3,4)) | (NaN,NaN) | ((NaN,NaN),(NaN,NaN))
+ [(0,0),(3,0),(4,5),(1,6)] | (NaN,NaN) | [(NaN,NaN),(NaN,NaN),(NaN,NaN),(NaN,NaN)]
+ ((1,2),(3,4)) | (NaN,NaN) | ((NaN,NaN),(NaN,NaN))
+ ((1,2),(3,4)) | (NaN,NaN) | ((NaN,NaN),(NaN,NaN))
+ [(1,2),(3,4)] | (NaN,NaN) | [(NaN,NaN),(NaN,NaN)]
+ ((10,20)) | (NaN,NaN) | ((NaN,NaN))
+ [(11,12),(13,14)] | (NaN,NaN) | [(NaN,NaN),(NaN,NaN)]
+ ((11,12),(13,14)) | (NaN,NaN) | ((NaN,NaN),(NaN,NaN))
+ [(1,2),(3,4)] | (10,10) | [(-10,30),(-10,70)]
+ ((1,2),(3,4)) | (10,10) | ((-10,30),(-10,70))
+ [(0,0),(3,0),(4,5),(1,6)] | (10,10) | [(0,0),(30,30),(-10,90),(-50,70)]
+ ((1,2),(3,4)) | (10,10) | ((-10,30),(-10,70))
+ ((1,2),(3,4)) | (10,10) | ((-10,30),(-10,70))
+ [(1,2),(3,4)] | (10,10) | [(-10,30),(-10,70)]
+ ((10,20)) | (10,10) | ((-100,300))
+ [(11,12),(13,14)] | (10,10) | [(-10,230),(-10,270)]
+ ((11,12),(13,14)) | (10,10) | ((-10,230),(-10,270))
+(90 rows)
+
+-- 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;
+ f1 | f1 | ?column?
+---------------------------+------------+-----------------------------------------------------------------------------------------------------------------
+ [(1,2),(3,4)] | (5.1,34.5) | [(0.0609244733856,-0.0199792807459),(0.12604212915,-0.0683242069952)]
+ [(1,2),(3,4)] | (10,10) | [(0.15,0.05),(0.35,0.05)]
+ ((1,2),(3,4)) | (5.1,34.5) | ((0.0609244733856,-0.0199792807459),(0.12604212915,-0.0683242069952))
+ ((1,2),(3,4)) | (10,10) | ((0.15,0.05),(0.35,0.05))
+ [(0,0),(3,0),(4,5),(1,6)] | (5.1,34.5) | [(0,0),(0.0125795471363,-0.0850969365103),(0.158600957032,-0.0924966701199),(0.174387055399,-0.00320655123082)]
+ [(0,0),(3,0),(4,5),(1,6)] | (10,10) | [(0,0),(0.15,-0.15),(0.45,0.05),(0.35,0.25)]
+ ((1,2),(3,4)) | (5.1,34.5) | ((0.0609244733856,-0.0199792807459),(0.12604212915,-0.0683242069952))
+ ((1,2),(3,4)) | (10,10) | ((0.15,0.05),(0.35,0.05))
+ ((1,2),(3,4)) | (5.1,34.5) | ((0.0609244733856,-0.0199792807459),(0.12604212915,-0.0683242069952))
+ ((1,2),(3,4)) | (10,10) | ((0.15,0.05),(0.35,0.05))
+ [(1,2),(3,4)] | (5.1,34.5) | [(0.0609244733856,-0.0199792807459),(0.12604212915,-0.0683242069952)]
+ [(1,2),(3,4)] | (10,10) | [(0.15,0.05),(0.35,0.05)]
+ ((10,20)) | (5.1,34.5) | ((0.609244733856,-0.199792807459))
+ ((10,20)) | (10,10) | ((1.5,0.5))
+ [(11,12),(13,14)] | (5.1,34.5) | [(0.386512752208,-0.261703911993),(0.451630407972,-0.310048838242)]
+ [(11,12),(13,14)] | (10,10) | [(1.15,0.05),(1.35,0.05)]
+ ((11,12),(13,14)) | (5.1,34.5) | ((0.386512752208,-0.261703911993),(0.451630407972,-0.310048838242))
+ ((11,12),(13,14)) | (10,10) | ((1.15,0.05),(1.35,0.05))
+(18 rows)
+
+-- 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;
+ERROR: division by zero
+-- Distance to path
+SELECT p1.f1, p2.f1, p1.f1 <-> p2.f1 FROM PATH_TBL p1, PATH_TBL p2;
+ f1 | f1 | ?column?
+---------------------------+---------------------------+----------------
+ [(1,2),(3,4)] | [(1,2),(3,4)] | 0
+ [(1,2),(3,4)] | ((1,2),(3,4)) | 0
+ [(1,2),(3,4)] | [(0,0),(3,0),(4,5),(1,6)] | 0.784464540553
+ [(1,2),(3,4)] | ((1,2),(3,4)) | 0
+ [(1,2),(3,4)] | ((1,2),(3,4)) | 0
+ [(1,2),(3,4)] | [(1,2),(3,4)] | 0
+ [(1,2),(3,4)] | ((10,20)) | 17.4642491966
+ [(1,2),(3,4)] | [(11,12),(13,14)] | 11.313708499
+ [(1,2),(3,4)] | ((11,12),(13,14)) | 11.313708499
+ ((1,2),(3,4)) | [(1,2),(3,4)] | 0
+ ((1,2),(3,4)) | ((1,2),(3,4)) | 0
+ ((1,2),(3,4)) | [(0,0),(3,0),(4,5),(1,6)] | 0.784464540553
+ ((1,2),(3,4)) | ((1,2),(3,4)) | 0
+ ((1,2),(3,4)) | ((1,2),(3,4)) | 0
+ ((1,2),(3,4)) | [(1,2),(3,4)] | 0
+ ((1,2),(3,4)) | ((10,20)) | 17.4642491966
+ ((1,2),(3,4)) | [(11,12),(13,14)] | 11.313708499
+ ((1,2),(3,4)) | ((11,12),(13,14)) | 11.313708499
+ [(0,0),(3,0),(4,5),(1,6)] | [(1,2),(3,4)] | 0.784464540553
+ [(0,0),(3,0),(4,5),(1,6)] | ((1,2),(3,4)) | 0.784464540553
+ [(0,0),(3,0),(4,5),(1,6)] | [(0,0),(3,0),(4,5),(1,6)] | 0
+ [(0,0),(3,0),(4,5),(1,6)] | ((1,2),(3,4)) | 0.784464540553
+ [(0,0),(3,0),(4,5),(1,6)] | ((1,2),(3,4)) | 0.784464540553
+ [(0,0),(3,0),(4,5),(1,6)] | [(1,2),(3,4)] | 0.784464540553
+ [(0,0),(3,0),(4,5),(1,6)] | ((10,20)) | 16.1554944214
+ [(0,0),(3,0),(4,5),(1,6)] | [(11,12),(13,14)] | 9.89949493661
+ [(0,0),(3,0),(4,5),(1,6)] | ((11,12),(13,14)) | 9.89949493661
+ ((1,2),(3,4)) | [(1,2),(3,4)] | 0
+ ((1,2),(3,4)) | ((1,2),(3,4)) | 0
+ ((1,2),(3,4)) | [(0,0),(3,0),(4,5),(1,6)] | 0.784464540553
+ ((1,2),(3,4)) | ((1,2),(3,4)) | 0
+ ((1,2),(3,4)) | ((1,2),(3,4)) | 0
+ ((1,2),(3,4)) | [(1,2),(3,4)] | 0
+ ((1,2),(3,4)) | ((10,20)) | 17.4642491966
+ ((1,2),(3,4)) | [(11,12),(13,14)] | 11.313708499
+ ((1,2),(3,4)) | ((11,12),(13,14)) | 11.313708499
+ ((1,2),(3,4)) | [(1,2),(3,4)] | 0
+ ((1,2),(3,4)) | ((1,2),(3,4)) | 0
+ ((1,2),(3,4)) | [(0,0),(3,0),(4,5),(1,6)] | 0.784464540553
+ ((1,2),(3,4)) | ((1,2),(3,4)) | 0
+ ((1,2),(3,4)) | ((1,2),(3,4)) | 0
+ ((1,2),(3,4)) | [(1,2),(3,4)] | 0
+ ((1,2),(3,4)) | ((10,20)) | 17.4642491966
+ ((1,2),(3,4)) | [(11,12),(13,14)] | 11.313708499
+ ((1,2),(3,4)) | ((11,12),(13,14)) | 11.313708499
+ [(1,2),(3,4)] | [(1,2),(3,4)] | 0
+ [(1,2),(3,4)] | ((1,2),(3,4)) | 0
+ [(1,2),(3,4)] | [(0,0),(3,0),(4,5),(1,6)] | 0.784464540553
+ [(1,2),(3,4)] | ((1,2),(3,4)) | 0
+ [(1,2),(3,4)] | ((1,2),(3,4)) | 0
+ [(1,2),(3,4)] | [(1,2),(3,4)] | 0
+ [(1,2),(3,4)] | ((10,20)) | 17.4642491966
+ [(1,2),(3,4)] | [(11,12),(13,14)] | 11.313708499
+ [(1,2),(3,4)] | ((11,12),(13,14)) | 11.313708499
+ ((10,20)) | [(1,2),(3,4)] | 17.4642491966
+ ((10,20)) | ((1,2),(3,4)) | 17.4642491966
+ ((10,20)) | [(0,0),(3,0),(4,5),(1,6)] | 16.1554944214
+ ((10,20)) | ((1,2),(3,4)) | 17.4642491966
+ ((10,20)) | ((1,2),(3,4)) | 17.4642491966
+ ((10,20)) | [(1,2),(3,4)] | 17.4642491966
+ ((10,20)) | ((10,20)) | 0
+ ((10,20)) | [(11,12),(13,14)] | 6.7082039325
+ ((10,20)) | ((11,12),(13,14)) | 6.7082039325
+ [(11,12),(13,14)] | [(1,2),(3,4)] | 11.313708499
+ [(11,12),(13,14)] | ((1,2),(3,4)) | 11.313708499
+ [(11,12),(13,14)] | [(0,0),(3,0),(4,5),(1,6)] | 9.89949493661
+ [(11,12),(13,14)] | ((1,2),(3,4)) | 11.313708499
+ [(11,12),(13,14)] | ((1,2),(3,4)) | 11.313708499
+ [(11,12),(13,14)] | [(1,2),(3,4)] | 11.313708499
+ [(11,12),(13,14)] | ((10,20)) | 6.7082039325
+ [(11,12),(13,14)] | [(11,12),(13,14)] | 0
+ [(11,12),(13,14)] | ((11,12),(13,14)) | 0
+ ((11,12),(13,14)) | [(1,2),(3,4)] | 11.313708499
+ ((11,12),(13,14)) | ((1,2),(3,4)) | 11.313708499
+ ((11,12),(13,14)) | [(0,0),(3,0),(4,5),(1,6)] | 9.89949493661
+ ((11,12),(13,14)) | ((1,2),(3,4)) | 11.313708499
+ ((11,12),(13,14)) | ((1,2),(3,4)) | 11.313708499
+ ((11,12),(13,14)) | [(1,2),(3,4)] | 11.313708499
+ ((11,12),(13,14)) | ((10,20)) | 6.7082039325
+ ((11,12),(13,14)) | [(11,12),(13,14)] | 0
+ ((11,12),(13,14)) | ((11,12),(13,14)) | 0
+(81 rows)
+
+--
+-- Polygons
+--
+-- containment
+SELECT p.f1, poly.f1, poly.f1 @> p.f1 AS contains
+ FROM POLYGON_TBL poly, POINT_TBL p;
+ f1 | f1 | contains
+-------------------+----------------------------+----------
+ (0,0) | ((2,0),(2,4),(0,0)) | t
+ (0,0) | ((3,1),(3,3),(1,0)) | f
+ (0,0) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (0,0) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (0,0) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (0,0) | ((0,0)) | t
+ (0,0) | ((0,1),(0,1)) | f
+ (-10,0) | ((2,0),(2,4),(0,0)) | f
+ (-10,0) | ((3,1),(3,3),(1,0)) | f
+ (-10,0) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (-10,0) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (-10,0) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (-10,0) | ((0,0)) | f
+ (-10,0) | ((0,1),(0,1)) | f
+ (-3,4) | ((2,0),(2,4),(0,0)) | f
+ (-3,4) | ((3,1),(3,3),(1,0)) | f
+ (-3,4) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (-3,4) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (-3,4) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (-3,4) | ((0,0)) | f
+ (-3,4) | ((0,1),(0,1)) | f
+ (5.1,34.5) | ((2,0),(2,4),(0,0)) | f
+ (5.1,34.5) | ((3,1),(3,3),(1,0)) | f
+ (5.1,34.5) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (5.1,34.5) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (5.1,34.5) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (5.1,34.5) | ((0,0)) | f
+ (5.1,34.5) | ((0,1),(0,1)) | f
+ (-5,-12) | ((2,0),(2,4),(0,0)) | f
+ (-5,-12) | ((3,1),(3,3),(1,0)) | f
+ (-5,-12) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (-5,-12) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (-5,-12) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (-5,-12) | ((0,0)) | f
+ (-5,-12) | ((0,1),(0,1)) | f
+ (1e-300,-1e-300) | ((2,0),(2,4),(0,0)) | t
+ (1e-300,-1e-300) | ((3,1),(3,3),(1,0)) | f
+ (1e-300,-1e-300) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (1e-300,-1e-300) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (1e-300,-1e-300) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (1e-300,-1e-300) | ((0,0)) | t
+ (1e-300,-1e-300) | ((0,1),(0,1)) | f
+ (1e+300,Infinity) | ((2,0),(2,4),(0,0)) | f
+ (1e+300,Infinity) | ((3,1),(3,3),(1,0)) | f
+ (1e+300,Infinity) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (1e+300,Infinity) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (1e+300,Infinity) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (1e+300,Infinity) | ((0,0)) | f
+ (1e+300,Infinity) | ((0,1),(0,1)) | f
+ (Infinity,1e+300) | ((2,0),(2,4),(0,0)) | f
+ (Infinity,1e+300) | ((3,1),(3,3),(1,0)) | f
+ (Infinity,1e+300) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (Infinity,1e+300) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (Infinity,1e+300) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (Infinity,1e+300) | ((0,0)) | f
+ (Infinity,1e+300) | ((0,1),(0,1)) | f
+ (NaN,NaN) | ((2,0),(2,4),(0,0)) | t
+ (NaN,NaN) | ((3,1),(3,3),(1,0)) | t
+ (NaN,NaN) | ((1,2),(3,4),(5,6),(7,8)) | t
+ (NaN,NaN) | ((7,8),(5,6),(3,4),(1,2)) | t
+ (NaN,NaN) | ((1,2),(7,8),(5,6),(3,-4)) | t
+ (NaN,NaN) | ((0,0)) | t
+ (NaN,NaN) | ((0,1),(0,1)) | t
+ (10,10) | ((2,0),(2,4),(0,0)) | f
+ (10,10) | ((3,1),(3,3),(1,0)) | f
+ (10,10) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (10,10) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (10,10) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (10,10) | ((0,0)) | f
+ (10,10) | ((0,1),(0,1)) | f
+(70 rows)
+
+SELECT p.f1, poly.f1, p.f1 <@ poly.f1 AS contained
+ FROM POLYGON_TBL poly, POINT_TBL p;
+ f1 | f1 | contained
+-------------------+----------------------------+-----------
+ (0,0) | ((2,0),(2,4),(0,0)) | t
+ (0,0) | ((3,1),(3,3),(1,0)) | f
+ (0,0) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (0,0) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (0,0) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (0,0) | ((0,0)) | t
+ (0,0) | ((0,1),(0,1)) | f
+ (-10,0) | ((2,0),(2,4),(0,0)) | f
+ (-10,0) | ((3,1),(3,3),(1,0)) | f
+ (-10,0) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (-10,0) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (-10,0) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (-10,0) | ((0,0)) | f
+ (-10,0) | ((0,1),(0,1)) | f
+ (-3,4) | ((2,0),(2,4),(0,0)) | f
+ (-3,4) | ((3,1),(3,3),(1,0)) | f
+ (-3,4) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (-3,4) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (-3,4) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (-3,4) | ((0,0)) | f
+ (-3,4) | ((0,1),(0,1)) | f
+ (5.1,34.5) | ((2,0),(2,4),(0,0)) | f
+ (5.1,34.5) | ((3,1),(3,3),(1,0)) | f
+ (5.1,34.5) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (5.1,34.5) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (5.1,34.5) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (5.1,34.5) | ((0,0)) | f
+ (5.1,34.5) | ((0,1),(0,1)) | f
+ (-5,-12) | ((2,0),(2,4),(0,0)) | f
+ (-5,-12) | ((3,1),(3,3),(1,0)) | f
+ (-5,-12) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (-5,-12) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (-5,-12) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (-5,-12) | ((0,0)) | f
+ (-5,-12) | ((0,1),(0,1)) | f
+ (1e-300,-1e-300) | ((2,0),(2,4),(0,0)) | t
+ (1e-300,-1e-300) | ((3,1),(3,3),(1,0)) | f
+ (1e-300,-1e-300) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (1e-300,-1e-300) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (1e-300,-1e-300) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (1e-300,-1e-300) | ((0,0)) | t
+ (1e-300,-1e-300) | ((0,1),(0,1)) | f
+ (1e+300,Infinity) | ((2,0),(2,4),(0,0)) | f
+ (1e+300,Infinity) | ((3,1),(3,3),(1,0)) | f
+ (1e+300,Infinity) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (1e+300,Infinity) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (1e+300,Infinity) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (1e+300,Infinity) | ((0,0)) | f
+ (1e+300,Infinity) | ((0,1),(0,1)) | f
+ (Infinity,1e+300) | ((2,0),(2,4),(0,0)) | f
+ (Infinity,1e+300) | ((3,1),(3,3),(1,0)) | f
+ (Infinity,1e+300) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (Infinity,1e+300) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (Infinity,1e+300) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (Infinity,1e+300) | ((0,0)) | f
+ (Infinity,1e+300) | ((0,1),(0,1)) | f
+ (NaN,NaN) | ((2,0),(2,4),(0,0)) | t
+ (NaN,NaN) | ((3,1),(3,3),(1,0)) | t
+ (NaN,NaN) | ((1,2),(3,4),(5,6),(7,8)) | t
+ (NaN,NaN) | ((7,8),(5,6),(3,4),(1,2)) | t
+ (NaN,NaN) | ((1,2),(7,8),(5,6),(3,-4)) | t
+ (NaN,NaN) | ((0,0)) | t
+ (NaN,NaN) | ((0,1),(0,1)) | t
+ (10,10) | ((2,0),(2,4),(0,0)) | f
+ (10,10) | ((3,1),(3,3),(1,0)) | f
+ (10,10) | ((1,2),(3,4),(5,6),(7,8)) | f
+ (10,10) | ((7,8),(5,6),(3,4),(1,2)) | f
+ (10,10) | ((1,2),(7,8),(5,6),(3,-4)) | f
+ (10,10) | ((0,0)) | f
+ (10,10) | ((0,1),(0,1)) | f
+(70 rows)
+
+SELECT npoints(f1) AS npoints, f1 AS polygon
+ FROM POLYGON_TBL;
+ npoints | polygon
+---------+----------------------------
+ 3 | ((2,0),(2,4),(0,0))
+ 3 | ((3,1),(3,3),(1,0))
+ 4 | ((1,2),(3,4),(5,6),(7,8))
+ 4 | ((7,8),(5,6),(3,4),(1,2))
+ 4 | ((1,2),(7,8),(5,6),(3,-4))
+ 1 | ((0,0))
+ 2 | ((0,1),(0,1))
+(7 rows)
+
+SELECT polygon(f1)
+ FROM BOX_TBL;
+ polygon
+-------------------------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+ ((1,1),(1,3),(3,3),(3,1))
+ ((-8,-10),(-8,2),(-2,2),(-2,-10))
+ ((2.5,2.5),(2.5,3.5),(2.5,3.5),(2.5,2.5))
+ ((3,3),(3,3),(3,3),(3,3))
+(5 rows)
+
+SELECT polygon(f1)
+ FROM PATH_TBL WHERE isclosed(f1);
+ polygon
+-------------------
+ ((1,2),(3,4))
+ ((1,2),(3,4))
+ ((1,2),(3,4))
+ ((10,20))
+ ((11,12),(13,14))
+(5 rows)
+
+SELECT f1 AS open_path, polygon( pclose(f1)) AS polygon
+ FROM PATH_TBL
+ WHERE isopen(f1);
+ open_path | polygon
+---------------------------+---------------------------
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(0,0),(3,0),(4,5),(1,6)] | ((0,0),(3,0),(4,5),(1,6))
+ [(1,2),(3,4)] | ((1,2),(3,4))
+ [(11,12),(13,14)] | ((11,12),(13,14))
+(4 rows)
+
+-- To box
+SELECT f1, f1::box FROM POLYGON_TBL;
+ f1 | f1
+----------------------------+--------------
+ ((2,0),(2,4),(0,0)) | (2,4),(0,0)
+ ((3,1),(3,3),(1,0)) | (3,3),(1,0)
+ ((1,2),(3,4),(5,6),(7,8)) | (7,8),(1,2)
+ ((7,8),(5,6),(3,4),(1,2)) | (7,8),(1,2)
+ ((1,2),(7,8),(5,6),(3,-4)) | (7,8),(1,-4)
+ ((0,0)) | (0,0),(0,0)
+ ((0,1),(0,1)) | (0,1),(0,1)
+(7 rows)
+
+-- To path
+SELECT f1, f1::path FROM POLYGON_TBL;
+ f1 | f1
+----------------------------+----------------------------
+ ((2,0),(2,4),(0,0)) | ((2,0),(2,4),(0,0))
+ ((3,1),(3,3),(1,0)) | ((3,1),(3,3),(1,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(3,4),(5,6),(7,8))
+ ((7,8),(5,6),(3,4),(1,2)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,0)) | ((0,0))
+ ((0,1),(0,1)) | ((0,1),(0,1))
+(7 rows)
+
+-- Same as polygon
+SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 ~= p2.f1;
+ f1 | f1
+----------------------------+----------------------------
+ ((2,0),(2,4),(0,0)) | ((2,0),(2,4),(0,0))
+ ((3,1),(3,3),(1,0)) | ((3,1),(3,3),(1,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(3,4),(5,6),(7,8)) | ((7,8),(5,6),(3,4),(1,2))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(3,4),(5,6),(7,8))
+ ((7,8),(5,6),(3,4),(1,2)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,0)) | ((0,0))
+ ((0,1),(0,1)) | ((0,1),(0,1))
+(9 rows)
+
+-- Contained by polygon
+SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 <@ p2.f1;
+ f1 | f1
+----------------------------+----------------------------
+ ((2,0),(2,4),(0,0)) | ((2,0),(2,4),(0,0))
+ ((3,1),(3,3),(1,0)) | ((3,1),(3,3),(1,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(3,4),(5,6),(7,8)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(3,4),(5,6),(7,8))
+ ((7,8),(5,6),(3,4),(1,2)) | ((7,8),(5,6),(3,4),(1,2))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,0)) | ((2,0),(2,4),(0,0))
+ ((0,0)) | ((0,0))
+ ((0,1),(0,1)) | ((0,1),(0,1))
+(12 rows)
+
+-- Contains polygon
+SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 @> p2.f1;
+ f1 | f1
+----------------------------+----------------------------
+ ((2,0),(2,4),(0,0)) | ((2,0),(2,4),(0,0))
+ ((2,0),(2,4),(0,0)) | ((0,0))
+ ((3,1),(3,3),(1,0)) | ((3,1),(3,3),(1,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(3,4),(5,6),(7,8)) | ((7,8),(5,6),(3,4),(1,2))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(3,4),(5,6),(7,8))
+ ((7,8),(5,6),(3,4),(1,2)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,0)) | ((0,0))
+ ((0,1),(0,1)) | ((0,1),(0,1))
+(12 rows)
+
+-- Overlap with polygon
+SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 && p2.f1;
+ f1 | f1
+----------------------------+----------------------------
+ ((2,0),(2,4),(0,0)) | ((2,0),(2,4),(0,0))
+ ((2,0),(2,4),(0,0)) | ((3,1),(3,3),(1,0))
+ ((2,0),(2,4),(0,0)) | ((1,2),(3,4),(5,6),(7,8))
+ ((2,0),(2,4),(0,0)) | ((7,8),(5,6),(3,4),(1,2))
+ ((2,0),(2,4),(0,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((2,0),(2,4),(0,0)) | ((0,0))
+ ((3,1),(3,3),(1,0)) | ((2,0),(2,4),(0,0))
+ ((3,1),(3,3),(1,0)) | ((3,1),(3,3),(1,0))
+ ((3,1),(3,3),(1,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((1,2),(3,4),(5,6),(7,8)) | ((2,0),(2,4),(0,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(3,4),(5,6),(7,8)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((7,8),(5,6),(3,4),(1,2)) | ((2,0),(2,4),(0,0))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(3,4),(5,6),(7,8))
+ ((7,8),(5,6),(3,4),(1,2)) | ((7,8),(5,6),(3,4),(1,2))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((2,0),(2,4),(0,0))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((3,1),(3,3),(1,0))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,0)) | ((2,0),(2,4),(0,0))
+ ((0,0)) | ((0,0))
+ ((0,1),(0,1)) | ((0,1),(0,1))
+(25 rows)
+
+-- Left of polygon
+SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 << p2.f1;
+ f1 | f1
+---------------+----------------------------
+ ((0,0)) | ((3,1),(3,3),(1,0))
+ ((0,0)) | ((1,2),(3,4),(5,6),(7,8))
+ ((0,0)) | ((7,8),(5,6),(3,4),(1,2))
+ ((0,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,1),(0,1)) | ((3,1),(3,3),(1,0))
+ ((0,1),(0,1)) | ((1,2),(3,4),(5,6),(7,8))
+ ((0,1),(0,1)) | ((7,8),(5,6),(3,4),(1,2))
+ ((0,1),(0,1)) | ((1,2),(7,8),(5,6),(3,-4))
+(8 rows)
+
+-- Overlap of left of polygon
+SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 &< p2.f1;
+ f1 | f1
+----------------------------+----------------------------
+ ((2,0),(2,4),(0,0)) | ((2,0),(2,4),(0,0))
+ ((2,0),(2,4),(0,0)) | ((3,1),(3,3),(1,0))
+ ((2,0),(2,4),(0,0)) | ((1,2),(3,4),(5,6),(7,8))
+ ((2,0),(2,4),(0,0)) | ((7,8),(5,6),(3,4),(1,2))
+ ((2,0),(2,4),(0,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((3,1),(3,3),(1,0)) | ((3,1),(3,3),(1,0))
+ ((3,1),(3,3),(1,0)) | ((1,2),(3,4),(5,6),(7,8))
+ ((3,1),(3,3),(1,0)) | ((7,8),(5,6),(3,4),(1,2))
+ ((3,1),(3,3),(1,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(3,4),(5,6),(7,8)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(3,4),(5,6),(7,8))
+ ((7,8),(5,6),(3,4),(1,2)) | ((7,8),(5,6),(3,4),(1,2))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,0)) | ((2,0),(2,4),(0,0))
+ ((0,0)) | ((3,1),(3,3),(1,0))
+ ((0,0)) | ((1,2),(3,4),(5,6),(7,8))
+ ((0,0)) | ((7,8),(5,6),(3,4),(1,2))
+ ((0,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,0)) | ((0,0))
+ ((0,0)) | ((0,1),(0,1))
+ ((0,1),(0,1)) | ((2,0),(2,4),(0,0))
+ ((0,1),(0,1)) | ((3,1),(3,3),(1,0))
+ ((0,1),(0,1)) | ((1,2),(3,4),(5,6),(7,8))
+ ((0,1),(0,1)) | ((7,8),(5,6),(3,4),(1,2))
+ ((0,1),(0,1)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,1),(0,1)) | ((0,0))
+ ((0,1),(0,1)) | ((0,1),(0,1))
+(32 rows)
+
+-- Right of polygon
+SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 >> p2.f1;
+ f1 | f1
+----------------------------+---------------
+ ((3,1),(3,3),(1,0)) | ((0,0))
+ ((3,1),(3,3),(1,0)) | ((0,1),(0,1))
+ ((1,2),(3,4),(5,6),(7,8)) | ((0,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((0,1),(0,1))
+ ((7,8),(5,6),(3,4),(1,2)) | ((0,0))
+ ((7,8),(5,6),(3,4),(1,2)) | ((0,1),(0,1))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((0,0))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((0,1),(0,1))
+(8 rows)
+
+-- Overlap of right of polygon
+SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 &> p2.f1;
+ f1 | f1
+----------------------------+----------------------------
+ ((2,0),(2,4),(0,0)) | ((2,0),(2,4),(0,0))
+ ((2,0),(2,4),(0,0)) | ((0,0))
+ ((2,0),(2,4),(0,0)) | ((0,1),(0,1))
+ ((3,1),(3,3),(1,0)) | ((2,0),(2,4),(0,0))
+ ((3,1),(3,3),(1,0)) | ((3,1),(3,3),(1,0))
+ ((3,1),(3,3),(1,0)) | ((1,2),(3,4),(5,6),(7,8))
+ ((3,1),(3,3),(1,0)) | ((7,8),(5,6),(3,4),(1,2))
+ ((3,1),(3,3),(1,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((3,1),(3,3),(1,0)) | ((0,0))
+ ((3,1),(3,3),(1,0)) | ((0,1),(0,1))
+ ((1,2),(3,4),(5,6),(7,8)) | ((2,0),(2,4),(0,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((3,1),(3,3),(1,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(3,4),(5,6),(7,8)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((1,2),(3,4),(5,6),(7,8)) | ((0,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((0,1),(0,1))
+ ((7,8),(5,6),(3,4),(1,2)) | ((2,0),(2,4),(0,0))
+ ((7,8),(5,6),(3,4),(1,2)) | ((3,1),(3,3),(1,0))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(3,4),(5,6),(7,8))
+ ((7,8),(5,6),(3,4),(1,2)) | ((7,8),(5,6),(3,4),(1,2))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((7,8),(5,6),(3,4),(1,2)) | ((0,0))
+ ((7,8),(5,6),(3,4),(1,2)) | ((0,1),(0,1))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((2,0),(2,4),(0,0))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((3,1),(3,3),(1,0))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((0,0))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((0,1),(0,1))
+ ((0,0)) | ((2,0),(2,4),(0,0))
+ ((0,0)) | ((0,0))
+ ((0,0)) | ((0,1),(0,1))
+ ((0,1),(0,1)) | ((2,0),(2,4),(0,0))
+ ((0,1),(0,1)) | ((0,0))
+ ((0,1),(0,1)) | ((0,1),(0,1))
+(37 rows)
+
+-- Below polygon
+SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 <<| p2.f1;
+ f1 | f1
+---------------+---------------------------
+ ((0,0)) | ((1,2),(3,4),(5,6),(7,8))
+ ((0,0)) | ((7,8),(5,6),(3,4),(1,2))
+ ((0,0)) | ((0,1),(0,1))
+ ((0,1),(0,1)) | ((1,2),(3,4),(5,6),(7,8))
+ ((0,1),(0,1)) | ((7,8),(5,6),(3,4),(1,2))
+(5 rows)
+
+-- Overlap or below polygon
+SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 &<| p2.f1;
+ f1 | f1
+----------------------------+----------------------------
+ ((2,0),(2,4),(0,0)) | ((2,0),(2,4),(0,0))
+ ((2,0),(2,4),(0,0)) | ((1,2),(3,4),(5,6),(7,8))
+ ((2,0),(2,4),(0,0)) | ((7,8),(5,6),(3,4),(1,2))
+ ((2,0),(2,4),(0,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((3,1),(3,3),(1,0)) | ((2,0),(2,4),(0,0))
+ ((3,1),(3,3),(1,0)) | ((3,1),(3,3),(1,0))
+ ((3,1),(3,3),(1,0)) | ((1,2),(3,4),(5,6),(7,8))
+ ((3,1),(3,3),(1,0)) | ((7,8),(5,6),(3,4),(1,2))
+ ((3,1),(3,3),(1,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(3,4),(5,6),(7,8)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(3,4),(5,6),(7,8))
+ ((7,8),(5,6),(3,4),(1,2)) | ((7,8),(5,6),(3,4),(1,2))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,0)) | ((2,0),(2,4),(0,0))
+ ((0,0)) | ((3,1),(3,3),(1,0))
+ ((0,0)) | ((1,2),(3,4),(5,6),(7,8))
+ ((0,0)) | ((7,8),(5,6),(3,4),(1,2))
+ ((0,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,0)) | ((0,0))
+ ((0,0)) | ((0,1),(0,1))
+ ((0,1),(0,1)) | ((2,0),(2,4),(0,0))
+ ((0,1),(0,1)) | ((3,1),(3,3),(1,0))
+ ((0,1),(0,1)) | ((1,2),(3,4),(5,6),(7,8))
+ ((0,1),(0,1)) | ((7,8),(5,6),(3,4),(1,2))
+ ((0,1),(0,1)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,1),(0,1)) | ((0,1),(0,1))
+(31 rows)
+
+-- Above polygon
+SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 |>> p2.f1;
+ f1 | f1
+---------------------------+---------------
+ ((1,2),(3,4),(5,6),(7,8)) | ((0,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((0,1),(0,1))
+ ((7,8),(5,6),(3,4),(1,2)) | ((0,0))
+ ((7,8),(5,6),(3,4),(1,2)) | ((0,1),(0,1))
+ ((0,1),(0,1)) | ((0,0))
+(5 rows)
+
+-- Overlap or above polygon
+SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 |&> p2.f1;
+ f1 | f1
+----------------------------+----------------------------
+ ((2,0),(2,4),(0,0)) | ((2,0),(2,4),(0,0))
+ ((2,0),(2,4),(0,0)) | ((3,1),(3,3),(1,0))
+ ((2,0),(2,4),(0,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((2,0),(2,4),(0,0)) | ((0,0))
+ ((3,1),(3,3),(1,0)) | ((2,0),(2,4),(0,0))
+ ((3,1),(3,3),(1,0)) | ((3,1),(3,3),(1,0))
+ ((3,1),(3,3),(1,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((3,1),(3,3),(1,0)) | ((0,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((2,0),(2,4),(0,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((3,1),(3,3),(1,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(3,4),(5,6),(7,8))
+ ((1,2),(3,4),(5,6),(7,8)) | ((7,8),(5,6),(3,4),(1,2))
+ ((1,2),(3,4),(5,6),(7,8)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((1,2),(3,4),(5,6),(7,8)) | ((0,0))
+ ((1,2),(3,4),(5,6),(7,8)) | ((0,1),(0,1))
+ ((7,8),(5,6),(3,4),(1,2)) | ((2,0),(2,4),(0,0))
+ ((7,8),(5,6),(3,4),(1,2)) | ((3,1),(3,3),(1,0))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(3,4),(5,6),(7,8))
+ ((7,8),(5,6),(3,4),(1,2)) | ((7,8),(5,6),(3,4),(1,2))
+ ((7,8),(5,6),(3,4),(1,2)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((7,8),(5,6),(3,4),(1,2)) | ((0,0))
+ ((7,8),(5,6),(3,4),(1,2)) | ((0,1),(0,1))
+ ((1,2),(7,8),(5,6),(3,-4)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,0)) | ((2,0),(2,4),(0,0))
+ ((0,0)) | ((3,1),(3,3),(1,0))
+ ((0,0)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,0)) | ((0,0))
+ ((0,1),(0,1)) | ((2,0),(2,4),(0,0))
+ ((0,1),(0,1)) | ((3,1),(3,3),(1,0))
+ ((0,1),(0,1)) | ((1,2),(7,8),(5,6),(3,-4))
+ ((0,1),(0,1)) | ((0,0))
+ ((0,1),(0,1)) | ((0,1),(0,1))
+(32 rows)
+
+-- Distance to polygon
+SELECT p1.f1, p2.f1, p1.f1 <-> p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2;
+ERROR: function "poly_distance" not implemented
+--
+-- Circles
+--
+SELECT circle(f1, 50.0)
+ FROM POINT_TBL;
+ circle
+------------------------
+ <(0,0),50>
+ <(-10,0),50>
+ <(-3,4),50>
+ <(5.1,34.5),50>
+ <(-5,-12),50>
+ <(1e-300,-1e-300),50>
+ <(1e+300,Infinity),50>
+ <(Infinity,1e+300),50>
+ <(NaN,NaN),50>
+ <(10,10),50>
+(10 rows)
+
+SELECT circle(f1)
+ FROM BOX_TBL;
+ circle
+------------------------
+ <(1,1),1.41421356237>
+ <(2,2),1.41421356237>
+ <(-5,-4),6.7082039325>
+ <(2.5,3),0.5>
+ <(3,3),0>
+(5 rows)
+
+SELECT circle(f1)
+ FROM POLYGON_TBL
+ WHERE (# f1) >= 3;
+ circle
+-----------------------------------------------
+ <(1.33333333333,1.33333333333),2.04168905064>
+ <(2.33333333333,1.33333333333),1.47534300379>
+ <(4,5),2.82842712475>
+ <(4,5),2.82842712475>
+ <(4,3),4.80664375676>
+(5 rows)
+
+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];
+ circle | point | distance
+----------------+-------------------+---------------
+ <(1,2),3> | (-3,4) | 1.472135955
+ <(5,1),3> | (0,0) | 2.09901951359
+ <(5,1),3> | (1e-300,-1e-300) | 2.09901951359
+ <(5,1),3> | (-3,4) | 5.54400374532
+ <(3,5),0> | (0,0) | 5.83095189485
+ <(3,5),0> | (1e-300,-1e-300) | 5.83095189485
+ <(3,5),0> | (-3,4) | 6.0827625303
+ <(1,3),5> | (-10,0) | 6.40175425099
+ <(1,3),5> | (10,10) | 6.40175425099
+ <(5,1),3> | (10,10) | 7.29563014099
+ <(1,2),3> | (-10,0) | 8.1803398875
+ <(3,5),0> | (10,10) | 8.60232526704
+ <(1,2),3> | (10,10) | 9.04159457879
+ <(1,3),5> | (-5,-12) | 11.1554944214
+ <(5,1),3> | (-10,0) | 12.0332963784
+ <(1,2),3> | (-5,-12) | 12.2315462117
+ <(5,1),3> | (-5,-12) | 13.4012194669
+ <(3,5),0> | (-10,0) | 13.9283882772
+ <(3,5),0> | (-5,-12) | 18.7882942281
+ <(1,3),5> | (5.1,34.5) | 26.7657047773
+ <(3,5),0> | (5.1,34.5) | 29.5746513082
+ <(1,2),3> | (5.1,34.5) | 29.7575945393
+ <(5,1),3> | (5.1,34.5) | 30.5001492534
+ <(100,200),10> | (5.1,34.5) | 180.778038568
+ <(100,200),10> | (10,10) | 200.237960416
+ <(100,200),10> | (-3,4) | 211.415898255
+ <(100,200),10> | (0,0) | 213.60679775
+ <(100,200),10> | (1e-300,-1e-300) | 213.60679775
+ <(100,200),10> | (-10,0) | 218.25424421
+ <(100,200),10> | (-5,-12) | 226.577682802
+ <(3,5),0> | (1e+300,Infinity) | Infinity
+ <(3,5),0> | (Infinity,1e+300) | Infinity
+ <(1,2),3> | (1e+300,Infinity) | Infinity
+ <(5,1),3> | (1e+300,Infinity) | Infinity
+ <(5,1),3> | (Infinity,1e+300) | Infinity
+ <(1,2),3> | (Infinity,1e+300) | Infinity
+ <(1,3),5> | (1e+300,Infinity) | Infinity
+ <(1,3),5> | (Infinity,1e+300) | Infinity
+ <(100,200),10> | (1e+300,Infinity) | Infinity
+ <(100,200),10> | (Infinity,1e+300) | Infinity
+ <(1,2),100> | (1e+300,Infinity) | Infinity
+ <(1,2),100> | (Infinity,1e+300) | Infinity
+ <(100,1),115> | (1e+300,Infinity) | Infinity
+ <(100,1),115> | (Infinity,1e+300) | Infinity
+ <(3,5),0> | (NaN,NaN) | NaN
+ <(1,2),3> | (NaN,NaN) | NaN
+ <(5,1),3> | (NaN,NaN) | NaN
+ <(1,3),5> | (NaN,NaN) | NaN
+ <(100,200),10> | (NaN,NaN) | NaN
+ <(1,2),100> | (NaN,NaN) | NaN
+ <(100,1),115> | (NaN,NaN) | NaN
+ <(3,5),NaN> | (-10,0) | NaN
+ <(3,5),NaN> | (-5,-12) | NaN
+ <(3,5),NaN> | (-3,4) | NaN
+ <(3,5),NaN> | (0,0) | NaN
+ <(3,5),NaN> | (1e-300,-1e-300) | NaN
+ <(3,5),NaN> | (5.1,34.5) | NaN
+ <(3,5),NaN> | (10,10) | NaN
+ <(3,5),NaN> | (1e+300,Infinity) | NaN
+ <(3,5),NaN> | (Infinity,1e+300) | NaN
+ <(3,5),NaN> | (NaN,NaN) | NaN
+(61 rows)
+
+-- To polygon
+SELECT f1, f1::polygon FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>';
+ f1 | f1
+----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ <(5,1),3> | ((2,1),(2.40192378865,2.5),(3.5,3.59807621135),(5,4),(6.5,3.59807621135),(7.59807621135,2.5),(8,1),(7.59807621135,-0.5),(6.5,-1.59807621135),(5,-2),(3.5,-1.59807621135),(2.40192378865,-0.5))
+ <(1,2),100> | ((-99,2),(-85.6025403784,52),(-49,88.6025403784),(1,102),(51,88.6025403784),(87.6025403784,52),(101,2),(87.6025403784,-48),(51,-84.6025403784),(1,-98),(-49,-84.6025403784),(-85.6025403784,-48))
+ <(1,3),5> | ((-4,3),(-3.33012701892,5.5),(-1.5,7.33012701892),(1,8),(3.5,7.33012701892),(5.33012701892,5.5),(6,3),(5.33012701892,0.5),(3.5,-1.33012701892),(1,-2),(-1.5,-1.33012701892),(-3.33012701892,0.5))
+ <(1,2),3> | ((-2,2),(-1.59807621135,3.5),(-0.5,4.59807621135),(1,5),(2.5,4.59807621135),(3.59807621135,3.5),(4,2),(3.59807621135,0.5),(2.5,-0.598076211353),(1,-1),(-0.5,-0.598076211353),(-1.59807621135,0.5))
+ <(100,200),10> | ((90,200),(91.3397459622,205),(95,208.660254038),(100,210),(105,208.660254038),(108.660254038,205),(110,200),(108.660254038,195),(105,191.339745962),(100,190),(95,191.339745962),(91.3397459622,195))
+ <(100,1),115> | ((-15,1),(0.40707856479,58.5),(42.5,100.592921435),(100,116),(157.5,100.592921435),(199.592921435,58.5),(215,1),(199.592921435,-56.5),(157.5,-98.5929214352),(100,-114),(42.5,-98.5929214352),(0.40707856479,-56.5))
+(6 rows)
+
+-- To polygon with less points
+SELECT f1, polygon(8, f1) FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>';
+ f1 | polygon
+----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ <(5,1),3> | ((2,1),(2.87867965644,3.12132034356),(5,4),(7.12132034356,3.12132034356),(8,1),(7.12132034356,-1.12132034356),(5,-2),(2.87867965644,-1.12132034356))
+ <(1,2),100> | ((-99,2),(-69.7106781187,72.7106781187),(1,102),(71.7106781187,72.7106781187),(101,2),(71.7106781187,-68.7106781187),(1,-98),(-69.7106781187,-68.7106781187))
+ <(1,3),5> | ((-4,3),(-2.53553390593,6.53553390593),(1,8),(4.53553390593,6.53553390593),(6,3),(4.53553390593,-0.535533905933),(1,-2),(-2.53553390593,-0.535533905933))
+ <(1,2),3> | ((-2,2),(-1.12132034356,4.12132034356),(1,5),(3.12132034356,4.12132034356),(4,2),(3.12132034356,-0.12132034356),(1,-1),(-1.12132034356,-0.12132034356))
+ <(100,200),10> | ((90,200),(92.9289321881,207.071067812),(100,210),(107.071067812,207.071067812),(110,200),(107.071067812,192.928932188),(100,190),(92.9289321881,192.928932188))
+ <(100,1),115> | ((-15,1),(18.6827201635,82.3172798365),(100,116),(181.317279836,82.3172798365),(215,1),(181.317279836,-80.3172798365),(100,-114),(18.6827201635,-80.3172798365))
+(6 rows)
+
+-- Error for insufficient number of points
+SELECT f1, polygon(1, f1) FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>';
+ERROR: must request at least 2 points
+-- Zero radius error
+SELECT f1, polygon(10, f1) FROM CIRCLE_TBL WHERE f1 < '<(0,0),1>';
+ERROR: cannot convert circle with radius zero to polygon
+-- Same as circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 ~= c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(5,1),3>
+ <(1,2),100> | <(1,2),100>
+ <(1,3),5> | <(1,3),5>
+ <(1,2),3> | <(1,2),3>
+ <(100,200),10> | <(100,200),10>
+ <(100,1),115> | <(100,1),115>
+ <(3,5),0> | <(3,5),0>
+ <(3,5),NaN> | <(3,5),0>
+ <(3,5),NaN> | <(3,5),NaN>
+(9 rows)
+
+-- Overlap with circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 && c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(5,1),3>
+ <(5,1),3> | <(1,2),100>
+ <(5,1),3> | <(1,3),5>
+ <(5,1),3> | <(1,2),3>
+ <(5,1),3> | <(100,1),115>
+ <(1,2),100> | <(5,1),3>
+ <(1,2),100> | <(1,2),100>
+ <(1,2),100> | <(1,3),5>
+ <(1,2),100> | <(1,2),3>
+ <(1,2),100> | <(100,1),115>
+ <(1,2),100> | <(3,5),0>
+ <(1,3),5> | <(5,1),3>
+ <(1,3),5> | <(1,2),100>
+ <(1,3),5> | <(1,3),5>
+ <(1,3),5> | <(1,2),3>
+ <(1,3),5> | <(100,1),115>
+ <(1,3),5> | <(3,5),0>
+ <(1,2),3> | <(5,1),3>
+ <(1,2),3> | <(1,2),100>
+ <(1,2),3> | <(1,3),5>
+ <(1,2),3> | <(1,2),3>
+ <(1,2),3> | <(100,1),115>
+ <(100,200),10> | <(100,200),10>
+ <(100,1),115> | <(5,1),3>
+ <(100,1),115> | <(1,2),100>
+ <(100,1),115> | <(1,3),5>
+ <(100,1),115> | <(1,2),3>
+ <(100,1),115> | <(100,1),115>
+ <(100,1),115> | <(3,5),0>
+ <(3,5),0> | <(1,2),100>
+ <(3,5),0> | <(1,3),5>
+ <(3,5),0> | <(100,1),115>
+ <(3,5),0> | <(3,5),0>
+(33 rows)
+
+-- Overlap or left of circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 &< c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(5,1),3>
+ <(5,1),3> | <(1,2),100>
+ <(5,1),3> | <(100,200),10>
+ <(5,1),3> | <(100,1),115>
+ <(1,2),100> | <(1,2),100>
+ <(1,2),100> | <(100,200),10>
+ <(1,2),100> | <(100,1),115>
+ <(1,3),5> | <(5,1),3>
+ <(1,3),5> | <(1,2),100>
+ <(1,3),5> | <(1,3),5>
+ <(1,3),5> | <(100,200),10>
+ <(1,3),5> | <(100,1),115>
+ <(1,2),3> | <(5,1),3>
+ <(1,2),3> | <(1,2),100>
+ <(1,2),3> | <(1,3),5>
+ <(1,2),3> | <(1,2),3>
+ <(1,2),3> | <(100,200),10>
+ <(1,2),3> | <(100,1),115>
+ <(100,200),10> | <(100,200),10>
+ <(100,200),10> | <(100,1),115>
+ <(100,1),115> | <(100,1),115>
+ <(3,5),0> | <(5,1),3>
+ <(3,5),0> | <(1,2),100>
+ <(3,5),0> | <(1,3),5>
+ <(3,5),0> | <(1,2),3>
+ <(3,5),0> | <(100,200),10>
+ <(3,5),0> | <(100,1),115>
+ <(3,5),0> | <(3,5),0>
+(28 rows)
+
+-- Left of circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 << c2.f1;
+ f1 | f1
+-----------+----------------
+ <(5,1),3> | <(100,200),10>
+ <(1,3),5> | <(100,200),10>
+ <(1,2),3> | <(100,200),10>
+ <(3,5),0> | <(100,200),10>
+(4 rows)
+
+-- Right of circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 >> c2.f1;
+ f1 | f1
+----------------+-----------
+ <(100,200),10> | <(5,1),3>
+ <(100,200),10> | <(1,3),5>
+ <(100,200),10> | <(1,2),3>
+ <(100,200),10> | <(3,5),0>
+(4 rows)
+
+-- Overlap or right of circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 &> c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(5,1),3>
+ <(5,1),3> | <(1,2),100>
+ <(5,1),3> | <(1,3),5>
+ <(5,1),3> | <(1,2),3>
+ <(5,1),3> | <(100,1),115>
+ <(1,2),100> | <(1,2),100>
+ <(1,3),5> | <(1,2),100>
+ <(1,3),5> | <(1,3),5>
+ <(1,3),5> | <(100,1),115>
+ <(1,2),3> | <(1,2),100>
+ <(1,2),3> | <(1,3),5>
+ <(1,2),3> | <(1,2),3>
+ <(1,2),3> | <(100,1),115>
+ <(100,200),10> | <(5,1),3>
+ <(100,200),10> | <(1,2),100>
+ <(100,200),10> | <(1,3),5>
+ <(100,200),10> | <(1,2),3>
+ <(100,200),10> | <(100,200),10>
+ <(100,200),10> | <(100,1),115>
+ <(100,200),10> | <(3,5),0>
+ <(100,1),115> | <(1,2),100>
+ <(100,1),115> | <(100,1),115>
+ <(3,5),0> | <(5,1),3>
+ <(3,5),0> | <(1,2),100>
+ <(3,5),0> | <(1,3),5>
+ <(3,5),0> | <(1,2),3>
+ <(3,5),0> | <(100,1),115>
+ <(3,5),0> | <(3,5),0>
+(28 rows)
+
+-- Contained by circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 <@ c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(5,1),3>
+ <(5,1),3> | <(1,2),100>
+ <(5,1),3> | <(100,1),115>
+ <(1,2),100> | <(1,2),100>
+ <(1,3),5> | <(1,2),100>
+ <(1,3),5> | <(1,3),5>
+ <(1,3),5> | <(100,1),115>
+ <(1,2),3> | <(1,2),100>
+ <(1,2),3> | <(1,3),5>
+ <(1,2),3> | <(1,2),3>
+ <(1,2),3> | <(100,1),115>
+ <(100,200),10> | <(100,200),10>
+ <(100,1),115> | <(100,1),115>
+ <(3,5),0> | <(1,2),100>
+ <(3,5),0> | <(1,3),5>
+ <(3,5),0> | <(100,1),115>
+ <(3,5),0> | <(3,5),0>
+(17 rows)
+
+-- Contain by circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 @> c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(5,1),3>
+ <(1,2),100> | <(5,1),3>
+ <(1,2),100> | <(1,2),100>
+ <(1,2),100> | <(1,3),5>
+ <(1,2),100> | <(1,2),3>
+ <(1,2),100> | <(3,5),0>
+ <(1,3),5> | <(1,3),5>
+ <(1,3),5> | <(1,2),3>
+ <(1,3),5> | <(3,5),0>
+ <(1,2),3> | <(1,2),3>
+ <(100,200),10> | <(100,200),10>
+ <(100,1),115> | <(5,1),3>
+ <(100,1),115> | <(1,3),5>
+ <(100,1),115> | <(1,2),3>
+ <(100,1),115> | <(100,1),115>
+ <(100,1),115> | <(3,5),0>
+ <(3,5),0> | <(3,5),0>
+(17 rows)
+
+-- Below circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 <<| c2.f1;
+ f1 | f1
+---------------+----------------
+ <(5,1),3> | <(100,200),10>
+ <(5,1),3> | <(3,5),0>
+ <(1,2),100> | <(100,200),10>
+ <(1,3),5> | <(100,200),10>
+ <(1,2),3> | <(100,200),10>
+ <(100,1),115> | <(100,200),10>
+ <(3,5),0> | <(100,200),10>
+(7 rows)
+
+-- Above circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 |>> c2.f1;
+ f1 | f1
+----------------+---------------
+ <(100,200),10> | <(5,1),3>
+ <(100,200),10> | <(1,2),100>
+ <(100,200),10> | <(1,3),5>
+ <(100,200),10> | <(1,2),3>
+ <(100,200),10> | <(100,1),115>
+ <(100,200),10> | <(3,5),0>
+ <(3,5),0> | <(5,1),3>
+(7 rows)
+
+-- Overlap or below circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 &<| c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(5,1),3>
+ <(5,1),3> | <(1,2),100>
+ <(5,1),3> | <(1,3),5>
+ <(5,1),3> | <(1,2),3>
+ <(5,1),3> | <(100,200),10>
+ <(5,1),3> | <(100,1),115>
+ <(5,1),3> | <(3,5),0>
+ <(1,2),100> | <(1,2),100>
+ <(1,2),100> | <(100,200),10>
+ <(1,2),100> | <(100,1),115>
+ <(1,3),5> | <(1,2),100>
+ <(1,3),5> | <(1,3),5>
+ <(1,3),5> | <(100,200),10>
+ <(1,3),5> | <(100,1),115>
+ <(1,2),3> | <(1,2),100>
+ <(1,2),3> | <(1,3),5>
+ <(1,2),3> | <(1,2),3>
+ <(1,2),3> | <(100,200),10>
+ <(1,2),3> | <(100,1),115>
+ <(1,2),3> | <(3,5),0>
+ <(100,200),10> | <(100,200),10>
+ <(100,1),115> | <(100,200),10>
+ <(100,1),115> | <(100,1),115>
+ <(3,5),0> | <(1,2),100>
+ <(3,5),0> | <(1,3),5>
+ <(3,5),0> | <(1,2),3>
+ <(3,5),0> | <(100,200),10>
+ <(3,5),0> | <(100,1),115>
+ <(3,5),0> | <(3,5),0>
+(29 rows)
+
+-- Overlap or above circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 |&> c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(5,1),3>
+ <(5,1),3> | <(1,2),100>
+ <(5,1),3> | <(1,3),5>
+ <(5,1),3> | <(100,1),115>
+ <(1,2),100> | <(1,2),100>
+ <(1,2),100> | <(100,1),115>
+ <(1,3),5> | <(5,1),3>
+ <(1,3),5> | <(1,2),100>
+ <(1,3),5> | <(1,3),5>
+ <(1,3),5> | <(100,1),115>
+ <(1,2),3> | <(5,1),3>
+ <(1,2),3> | <(1,2),100>
+ <(1,2),3> | <(1,3),5>
+ <(1,2),3> | <(1,2),3>
+ <(1,2),3> | <(100,1),115>
+ <(100,200),10> | <(5,1),3>
+ <(100,200),10> | <(1,2),100>
+ <(100,200),10> | <(1,3),5>
+ <(100,200),10> | <(1,2),3>
+ <(100,200),10> | <(100,200),10>
+ <(100,200),10> | <(100,1),115>
+ <(100,200),10> | <(3,5),0>
+ <(100,1),115> | <(100,1),115>
+ <(3,5),0> | <(5,1),3>
+ <(3,5),0> | <(1,2),100>
+ <(3,5),0> | <(1,3),5>
+ <(3,5),0> | <(1,2),3>
+ <(3,5),0> | <(100,1),115>
+ <(3,5),0> | <(3,5),0>
+(29 rows)
+
+-- Area equal with circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 = c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(5,1),3>
+ <(5,1),3> | <(1,2),3>
+ <(1,2),100> | <(1,2),100>
+ <(1,3),5> | <(1,3),5>
+ <(1,2),3> | <(5,1),3>
+ <(1,2),3> | <(1,2),3>
+ <(100,200),10> | <(100,200),10>
+ <(100,1),115> | <(100,1),115>
+ <(3,5),0> | <(3,5),0>
+(9 rows)
+
+-- Area not equal with circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 != c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(1,2),100>
+ <(5,1),3> | <(1,3),5>
+ <(5,1),3> | <(100,200),10>
+ <(5,1),3> | <(100,1),115>
+ <(5,1),3> | <(3,5),0>
+ <(1,2),100> | <(5,1),3>
+ <(1,2),100> | <(1,3),5>
+ <(1,2),100> | <(1,2),3>
+ <(1,2),100> | <(100,200),10>
+ <(1,2),100> | <(100,1),115>
+ <(1,2),100> | <(3,5),0>
+ <(1,3),5> | <(5,1),3>
+ <(1,3),5> | <(1,2),100>
+ <(1,3),5> | <(1,2),3>
+ <(1,3),5> | <(100,200),10>
+ <(1,3),5> | <(100,1),115>
+ <(1,3),5> | <(3,5),0>
+ <(1,2),3> | <(1,2),100>
+ <(1,2),3> | <(1,3),5>
+ <(1,2),3> | <(100,200),10>
+ <(1,2),3> | <(100,1),115>
+ <(1,2),3> | <(3,5),0>
+ <(100,200),10> | <(5,1),3>
+ <(100,200),10> | <(1,2),100>
+ <(100,200),10> | <(1,3),5>
+ <(100,200),10> | <(1,2),3>
+ <(100,200),10> | <(100,1),115>
+ <(100,200),10> | <(3,5),0>
+ <(100,1),115> | <(5,1),3>
+ <(100,1),115> | <(1,2),100>
+ <(100,1),115> | <(1,3),5>
+ <(100,1),115> | <(1,2),3>
+ <(100,1),115> | <(100,200),10>
+ <(100,1),115> | <(3,5),0>
+ <(3,5),0> | <(5,1),3>
+ <(3,5),0> | <(1,2),100>
+ <(3,5),0> | <(1,3),5>
+ <(3,5),0> | <(1,2),3>
+ <(3,5),0> | <(100,200),10>
+ <(3,5),0> | <(100,1),115>
+(40 rows)
+
+-- Area less than circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 < c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(1,2),100>
+ <(5,1),3> | <(1,3),5>
+ <(5,1),3> | <(100,200),10>
+ <(5,1),3> | <(100,1),115>
+ <(1,2),100> | <(100,1),115>
+ <(1,3),5> | <(1,2),100>
+ <(1,3),5> | <(100,200),10>
+ <(1,3),5> | <(100,1),115>
+ <(1,2),3> | <(1,2),100>
+ <(1,2),3> | <(1,3),5>
+ <(1,2),3> | <(100,200),10>
+ <(1,2),3> | <(100,1),115>
+ <(100,200),10> | <(1,2),100>
+ <(100,200),10> | <(100,1),115>
+ <(3,5),0> | <(5,1),3>
+ <(3,5),0> | <(1,2),100>
+ <(3,5),0> | <(1,3),5>
+ <(3,5),0> | <(1,2),3>
+ <(3,5),0> | <(100,200),10>
+ <(3,5),0> | <(100,1),115>
+(20 rows)
+
+-- Area greater than circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 > c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(3,5),0>
+ <(1,2),100> | <(5,1),3>
+ <(1,2),100> | <(1,3),5>
+ <(1,2),100> | <(1,2),3>
+ <(1,2),100> | <(100,200),10>
+ <(1,2),100> | <(3,5),0>
+ <(1,3),5> | <(5,1),3>
+ <(1,3),5> | <(1,2),3>
+ <(1,3),5> | <(3,5),0>
+ <(1,2),3> | <(3,5),0>
+ <(100,200),10> | <(5,1),3>
+ <(100,200),10> | <(1,3),5>
+ <(100,200),10> | <(1,2),3>
+ <(100,200),10> | <(3,5),0>
+ <(100,1),115> | <(5,1),3>
+ <(100,1),115> | <(1,2),100>
+ <(100,1),115> | <(1,3),5>
+ <(100,1),115> | <(1,2),3>
+ <(100,1),115> | <(100,200),10>
+ <(100,1),115> | <(3,5),0>
+(20 rows)
+
+-- Area less than or equal circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 <= c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(5,1),3>
+ <(5,1),3> | <(1,2),100>
+ <(5,1),3> | <(1,3),5>
+ <(5,1),3> | <(1,2),3>
+ <(5,1),3> | <(100,200),10>
+ <(5,1),3> | <(100,1),115>
+ <(1,2),100> | <(1,2),100>
+ <(1,2),100> | <(100,1),115>
+ <(1,3),5> | <(1,2),100>
+ <(1,3),5> | <(1,3),5>
+ <(1,3),5> | <(100,200),10>
+ <(1,3),5> | <(100,1),115>
+ <(1,2),3> | <(5,1),3>
+ <(1,2),3> | <(1,2),100>
+ <(1,2),3> | <(1,3),5>
+ <(1,2),3> | <(1,2),3>
+ <(1,2),3> | <(100,200),10>
+ <(1,2),3> | <(100,1),115>
+ <(100,200),10> | <(1,2),100>
+ <(100,200),10> | <(100,200),10>
+ <(100,200),10> | <(100,1),115>
+ <(100,1),115> | <(100,1),115>
+ <(3,5),0> | <(5,1),3>
+ <(3,5),0> | <(1,2),100>
+ <(3,5),0> | <(1,3),5>
+ <(3,5),0> | <(1,2),3>
+ <(3,5),0> | <(100,200),10>
+ <(3,5),0> | <(100,1),115>
+ <(3,5),0> | <(3,5),0>
+(29 rows)
+
+-- Area greater than or equal circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 >= c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(5,1),3>
+ <(5,1),3> | <(1,2),3>
+ <(5,1),3> | <(3,5),0>
+ <(1,2),100> | <(5,1),3>
+ <(1,2),100> | <(1,2),100>
+ <(1,2),100> | <(1,3),5>
+ <(1,2),100> | <(1,2),3>
+ <(1,2),100> | <(100,200),10>
+ <(1,2),100> | <(3,5),0>
+ <(1,3),5> | <(5,1),3>
+ <(1,3),5> | <(1,3),5>
+ <(1,3),5> | <(1,2),3>
+ <(1,3),5> | <(3,5),0>
+ <(1,2),3> | <(5,1),3>
+ <(1,2),3> | <(1,2),3>
+ <(1,2),3> | <(3,5),0>
+ <(100,200),10> | <(5,1),3>
+ <(100,200),10> | <(1,3),5>
+ <(100,200),10> | <(1,2),3>
+ <(100,200),10> | <(100,200),10>
+ <(100,200),10> | <(3,5),0>
+ <(100,1),115> | <(5,1),3>
+ <(100,1),115> | <(1,2),100>
+ <(100,1),115> | <(1,3),5>
+ <(100,1),115> | <(1,2),3>
+ <(100,1),115> | <(100,200),10>
+ <(100,1),115> | <(100,1),115>
+ <(100,1),115> | <(3,5),0>
+ <(3,5),0> | <(3,5),0>
+(29 rows)
+
+-- Area less than circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 < c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(1,2),100>
+ <(5,1),3> | <(1,3),5>
+ <(5,1),3> | <(100,200),10>
+ <(5,1),3> | <(100,1),115>
+ <(1,2),100> | <(100,1),115>
+ <(1,3),5> | <(1,2),100>
+ <(1,3),5> | <(100,200),10>
+ <(1,3),5> | <(100,1),115>
+ <(1,2),3> | <(1,2),100>
+ <(1,2),3> | <(1,3),5>
+ <(1,2),3> | <(100,200),10>
+ <(1,2),3> | <(100,1),115>
+ <(100,200),10> | <(1,2),100>
+ <(100,200),10> | <(100,1),115>
+ <(3,5),0> | <(5,1),3>
+ <(3,5),0> | <(1,2),100>
+ <(3,5),0> | <(1,3),5>
+ <(3,5),0> | <(1,2),3>
+ <(3,5),0> | <(100,200),10>
+ <(3,5),0> | <(100,1),115>
+(20 rows)
+
+-- Area greater than circle
+SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 < c2.f1;
+ f1 | f1
+----------------+----------------
+ <(5,1),3> | <(1,2),100>
+ <(5,1),3> | <(1,3),5>
+ <(5,1),3> | <(100,200),10>
+ <(5,1),3> | <(100,1),115>
+ <(1,2),100> | <(100,1),115>
+ <(1,3),5> | <(1,2),100>
+ <(1,3),5> | <(100,200),10>
+ <(1,3),5> | <(100,1),115>
+ <(1,2),3> | <(1,2),100>
+ <(1,2),3> | <(1,3),5>
+ <(1,2),3> | <(100,200),10>
+ <(1,2),3> | <(100,1),115>
+ <(100,200),10> | <(1,2),100>
+ <(100,200),10> | <(100,1),115>
+ <(3,5),0> | <(5,1),3>
+ <(3,5),0> | <(1,2),100>
+ <(3,5),0> | <(1,3),5>
+ <(3,5),0> | <(1,2),3>
+ <(3,5),0> | <(100,200),10>
+ <(3,5),0> | <(100,1),115>
+(20 rows)
+
+-- Add point
+SELECT c.f1, p.f1, c.f1 + p.f1 FROM CIRCLE_TBL c, POINT_TBL p;
+ f1 | f1 | ?column?
+----------------+-------------------+-------------------------
+ <(5,1),3> | (0,0) | <(5,1),3>
+ <(1,2),100> | (0,0) | <(1,2),100>
+ <(1,3),5> | (0,0) | <(1,3),5>
+ <(1,2),3> | (0,0) | <(1,2),3>
+ <(100,200),10> | (0,0) | <(100,200),10>
+ <(100,1),115> | (0,0) | <(100,1),115>
+ <(3,5),0> | (0,0) | <(3,5),0>
+ <(3,5),NaN> | (0,0) | <(3,5),NaN>
+ <(5,1),3> | (-10,0) | <(-5,1),3>
+ <(1,2),100> | (-10,0) | <(-9,2),100>
+ <(1,3),5> | (-10,0) | <(-9,3),5>
+ <(1,2),3> | (-10,0) | <(-9,2),3>
+ <(100,200),10> | (-10,0) | <(90,200),10>
+ <(100,1),115> | (-10,0) | <(90,1),115>
+ <(3,5),0> | (-10,0) | <(-7,5),0>
+ <(3,5),NaN> | (-10,0) | <(-7,5),NaN>
+ <(5,1),3> | (-3,4) | <(2,5),3>
+ <(1,2),100> | (-3,4) | <(-2,6),100>
+ <(1,3),5> | (-3,4) | <(-2,7),5>
+ <(1,2),3> | (-3,4) | <(-2,6),3>
+ <(100,200),10> | (-3,4) | <(97,204),10>
+ <(100,1),115> | (-3,4) | <(97,5),115>
+ <(3,5),0> | (-3,4) | <(0,9),0>
+ <(3,5),NaN> | (-3,4) | <(0,9),NaN>
+ <(5,1),3> | (5.1,34.5) | <(10.1,35.5),3>
+ <(1,2),100> | (5.1,34.5) | <(6.1,36.5),100>
+ <(1,3),5> | (5.1,34.5) | <(6.1,37.5),5>
+ <(1,2),3> | (5.1,34.5) | <(6.1,36.5),3>
+ <(100,200),10> | (5.1,34.5) | <(105.1,234.5),10>
+ <(100,1),115> | (5.1,34.5) | <(105.1,35.5),115>
+ <(3,5),0> | (5.1,34.5) | <(8.1,39.5),0>
+ <(3,5),NaN> | (5.1,34.5) | <(8.1,39.5),NaN>
+ <(5,1),3> | (-5,-12) | <(0,-11),3>
+ <(1,2),100> | (-5,-12) | <(-4,-10),100>
+ <(1,3),5> | (-5,-12) | <(-4,-9),5>
+ <(1,2),3> | (-5,-12) | <(-4,-10),3>
+ <(100,200),10> | (-5,-12) | <(95,188),10>
+ <(100,1),115> | (-5,-12) | <(95,-11),115>
+ <(3,5),0> | (-5,-12) | <(-2,-7),0>
+ <(3,5),NaN> | (-5,-12) | <(-2,-7),NaN>
+ <(5,1),3> | (1e-300,-1e-300) | <(5,1),3>
+ <(1,2),100> | (1e-300,-1e-300) | <(1,2),100>
+ <(1,3),5> | (1e-300,-1e-300) | <(1,3),5>
+ <(1,2),3> | (1e-300,-1e-300) | <(1,2),3>
+ <(100,200),10> | (1e-300,-1e-300) | <(100,200),10>
+ <(100,1),115> | (1e-300,-1e-300) | <(100,1),115>
+ <(3,5),0> | (1e-300,-1e-300) | <(3,5),0>
+ <(3,5),NaN> | (1e-300,-1e-300) | <(3,5),NaN>
+ <(5,1),3> | (1e+300,Infinity) | <(1e+300,Infinity),3>
+ <(1,2),100> | (1e+300,Infinity) | <(1e+300,Infinity),100>
+ <(1,3),5> | (1e+300,Infinity) | <(1e+300,Infinity),5>
+ <(1,2),3> | (1e+300,Infinity) | <(1e+300,Infinity),3>
+ <(100,200),10> | (1e+300,Infinity) | <(1e+300,Infinity),10>
+ <(100,1),115> | (1e+300,Infinity) | <(1e+300,Infinity),115>
+ <(3,5),0> | (1e+300,Infinity) | <(1e+300,Infinity),0>
+ <(3,5),NaN> | (1e+300,Infinity) | <(1e+300,Infinity),NaN>
+ <(5,1),3> | (Infinity,1e+300) | <(Infinity,1e+300),3>
+ <(1,2),100> | (Infinity,1e+300) | <(Infinity,1e+300),100>
+ <(1,3),5> | (Infinity,1e+300) | <(Infinity,1e+300),5>
+ <(1,2),3> | (Infinity,1e+300) | <(Infinity,1e+300),3>
+ <(100,200),10> | (Infinity,1e+300) | <(Infinity,1e+300),10>
+ <(100,1),115> | (Infinity,1e+300) | <(Infinity,1e+300),115>
+ <(3,5),0> | (Infinity,1e+300) | <(Infinity,1e+300),0>
+ <(3,5),NaN> | (Infinity,1e+300) | <(Infinity,1e+300),NaN>
+ <(5,1),3> | (NaN,NaN) | <(NaN,NaN),3>
+ <(1,2),100> | (NaN,NaN) | <(NaN,NaN),100>
+ <(1,3),5> | (NaN,NaN) | <(NaN,NaN),5>
+ <(1,2),3> | (NaN,NaN) | <(NaN,NaN),3>
+ <(100,200),10> | (NaN,NaN) | <(NaN,NaN),10>
+ <(100,1),115> | (NaN,NaN) | <(NaN,NaN),115>
+ <(3,5),0> | (NaN,NaN) | <(NaN,NaN),0>
+ <(3,5),NaN> | (NaN,NaN) | <(NaN,NaN),NaN>
+ <(5,1),3> | (10,10) | <(15,11),3>
+ <(1,2),100> | (10,10) | <(11,12),100>
+ <(1,3),5> | (10,10) | <(11,13),5>
+ <(1,2),3> | (10,10) | <(11,12),3>
+ <(100,200),10> | (10,10) | <(110,210),10>
+ <(100,1),115> | (10,10) | <(110,11),115>
+ <(3,5),0> | (10,10) | <(13,15),0>
+ <(3,5),NaN> | (10,10) | <(13,15),NaN>
+(80 rows)
+
+-- Subtract point
+SELECT c.f1, p.f1, c.f1 - p.f1 FROM CIRCLE_TBL c, POINT_TBL p;
+ f1 | f1 | ?column?
+----------------+-------------------+---------------------------
+ <(5,1),3> | (0,0) | <(5,1),3>
+ <(1,2),100> | (0,0) | <(1,2),100>
+ <(1,3),5> | (0,0) | <(1,3),5>
+ <(1,2),3> | (0,0) | <(1,2),3>
+ <(100,200),10> | (0,0) | <(100,200),10>
+ <(100,1),115> | (0,0) | <(100,1),115>
+ <(3,5),0> | (0,0) | <(3,5),0>
+ <(3,5),NaN> | (0,0) | <(3,5),NaN>
+ <(5,1),3> | (-10,0) | <(15,1),3>
+ <(1,2),100> | (-10,0) | <(11,2),100>
+ <(1,3),5> | (-10,0) | <(11,3),5>
+ <(1,2),3> | (-10,0) | <(11,2),3>
+ <(100,200),10> | (-10,0) | <(110,200),10>
+ <(100,1),115> | (-10,0) | <(110,1),115>
+ <(3,5),0> | (-10,0) | <(13,5),0>
+ <(3,5),NaN> | (-10,0) | <(13,5),NaN>
+ <(5,1),3> | (-3,4) | <(8,-3),3>
+ <(1,2),100> | (-3,4) | <(4,-2),100>
+ <(1,3),5> | (-3,4) | <(4,-1),5>
+ <(1,2),3> | (-3,4) | <(4,-2),3>
+ <(100,200),10> | (-3,4) | <(103,196),10>
+ <(100,1),115> | (-3,4) | <(103,-3),115>
+ <(3,5),0> | (-3,4) | <(6,1),0>
+ <(3,5),NaN> | (-3,4) | <(6,1),NaN>
+ <(5,1),3> | (5.1,34.5) | <(-0.1,-33.5),3>
+ <(1,2),100> | (5.1,34.5) | <(-4.1,-32.5),100>
+ <(1,3),5> | (5.1,34.5) | <(-4.1,-31.5),5>
+ <(1,2),3> | (5.1,34.5) | <(-4.1,-32.5),3>
+ <(100,200),10> | (5.1,34.5) | <(94.9,165.5),10>
+ <(100,1),115> | (5.1,34.5) | <(94.9,-33.5),115>
+ <(3,5),0> | (5.1,34.5) | <(-2.1,-29.5),0>
+ <(3,5),NaN> | (5.1,34.5) | <(-2.1,-29.5),NaN>
+ <(5,1),3> | (-5,-12) | <(10,13),3>
+ <(1,2),100> | (-5,-12) | <(6,14),100>
+ <(1,3),5> | (-5,-12) | <(6,15),5>
+ <(1,2),3> | (-5,-12) | <(6,14),3>
+ <(100,200),10> | (-5,-12) | <(105,212),10>
+ <(100,1),115> | (-5,-12) | <(105,13),115>
+ <(3,5),0> | (-5,-12) | <(8,17),0>
+ <(3,5),NaN> | (-5,-12) | <(8,17),NaN>
+ <(5,1),3> | (1e-300,-1e-300) | <(5,1),3>
+ <(1,2),100> | (1e-300,-1e-300) | <(1,2),100>
+ <(1,3),5> | (1e-300,-1e-300) | <(1,3),5>
+ <(1,2),3> | (1e-300,-1e-300) | <(1,2),3>
+ <(100,200),10> | (1e-300,-1e-300) | <(100,200),10>
+ <(100,1),115> | (1e-300,-1e-300) | <(100,1),115>
+ <(3,5),0> | (1e-300,-1e-300) | <(3,5),0>
+ <(3,5),NaN> | (1e-300,-1e-300) | <(3,5),NaN>
+ <(5,1),3> | (1e+300,Infinity) | <(-1e+300,-Infinity),3>
+ <(1,2),100> | (1e+300,Infinity) | <(-1e+300,-Infinity),100>
+ <(1,3),5> | (1e+300,Infinity) | <(-1e+300,-Infinity),5>
+ <(1,2),3> | (1e+300,Infinity) | <(-1e+300,-Infinity),3>
+ <(100,200),10> | (1e+300,Infinity) | <(-1e+300,-Infinity),10>
+ <(100,1),115> | (1e+300,Infinity) | <(-1e+300,-Infinity),115>
+ <(3,5),0> | (1e+300,Infinity) | <(-1e+300,-Infinity),0>
+ <(3,5),NaN> | (1e+300,Infinity) | <(-1e+300,-Infinity),NaN>
+ <(5,1),3> | (Infinity,1e+300) | <(-Infinity,-1e+300),3>
+ <(1,2),100> | (Infinity,1e+300) | <(-Infinity,-1e+300),100>
+ <(1,3),5> | (Infinity,1e+300) | <(-Infinity,-1e+300),5>
+ <(1,2),3> | (Infinity,1e+300) | <(-Infinity,-1e+300),3>
+ <(100,200),10> | (Infinity,1e+300) | <(-Infinity,-1e+300),10>
+ <(100,1),115> | (Infinity,1e+300) | <(-Infinity,-1e+300),115>
+ <(3,5),0> | (Infinity,1e+300) | <(-Infinity,-1e+300),0>
+ <(3,5),NaN> | (Infinity,1e+300) | <(-Infinity,-1e+300),NaN>
+ <(5,1),3> | (NaN,NaN) | <(NaN,NaN),3>
+ <(1,2),100> | (NaN,NaN) | <(NaN,NaN),100>
+ <(1,3),5> | (NaN,NaN) | <(NaN,NaN),5>
+ <(1,2),3> | (NaN,NaN) | <(NaN,NaN),3>
+ <(100,200),10> | (NaN,NaN) | <(NaN,NaN),10>
+ <(100,1),115> | (NaN,NaN) | <(NaN,NaN),115>
+ <(3,5),0> | (NaN,NaN) | <(NaN,NaN),0>
+ <(3,5),NaN> | (NaN,NaN) | <(NaN,NaN),NaN>
+ <(5,1),3> | (10,10) | <(-5,-9),3>
+ <(1,2),100> | (10,10) | <(-9,-8),100>
+ <(1,3),5> | (10,10) | <(-9,-7),5>
+ <(1,2),3> | (10,10) | <(-9,-8),3>
+ <(100,200),10> | (10,10) | <(90,190),10>
+ <(100,1),115> | (10,10) | <(90,-9),115>
+ <(3,5),0> | (10,10) | <(-7,-5),0>
+ <(3,5),NaN> | (10,10) | <(-7,-5),NaN>
+(80 rows)
+
+-- Multiply with point
+SELECT c.f1, p.f1, c.f1 * p.f1 FROM CIRCLE_TBL c, POINT_TBL p;
+ f1 | f1 | ?column?
+----------------+-------------------+--------------------------------------------
+ <(5,1),3> | (0,0) | <(0,0),0>
+ <(1,2),100> | (0,0) | <(0,0),0>
+ <(1,3),5> | (0,0) | <(0,0),0>
+ <(1,2),3> | (0,0) | <(0,0),0>
+ <(100,200),10> | (0,0) | <(0,0),0>
+ <(100,1),115> | (0,0) | <(0,0),0>
+ <(3,5),0> | (0,0) | <(0,0),0>
+ <(3,5),NaN> | (0,0) | <(0,0),NaN>
+ <(5,1),3> | (-10,0) | <(-50,-10),30>
+ <(1,2),100> | (-10,0) | <(-10,-20),1000>
+ <(1,3),5> | (-10,0) | <(-10,-30),50>
+ <(1,2),3> | (-10,0) | <(-10,-20),30>
+ <(100,200),10> | (-10,0) | <(-1000,-2000),100>
+ <(100,1),115> | (-10,0) | <(-1000,-10),1150>
+ <(3,5),0> | (-10,0) | <(-30,-50),0>
+ <(3,5),NaN> | (-10,0) | <(-30,-50),NaN>
+ <(5,1),3> | (-3,4) | <(-19,17),15>
+ <(1,2),100> | (-3,4) | <(-11,-2),500>
+ <(1,3),5> | (-3,4) | <(-15,-5),25>
+ <(1,2),3> | (-3,4) | <(-11,-2),15>
+ <(100,200),10> | (-3,4) | <(-1100,-200),50>
+ <(100,1),115> | (-3,4) | <(-304,397),575>
+ <(3,5),0> | (-3,4) | <(-29,-3),0>
+ <(3,5),NaN> | (-3,4) | <(-29,-3),NaN>
+ <(5,1),3> | (5.1,34.5) | <(-9,177.6),104.624758064>
+ <(1,2),100> | (5.1,34.5) | <(-63.9,44.7),3487.49193547>
+ <(1,3),5> | (5.1,34.5) | <(-98.4,49.8),174.374596774>
+ <(1,2),3> | (5.1,34.5) | <(-63.9,44.7),104.624758064>
+ <(100,200),10> | (5.1,34.5) | <(-6390,4470),348.749193547>
+ <(100,1),115> | (5.1,34.5) | <(475.5,3455.1),4010.6157258>
+ <(3,5),0> | (5.1,34.5) | <(-157.2,129),0>
+ <(3,5),NaN> | (5.1,34.5) | <(-157.2,129),NaN>
+ <(5,1),3> | (-5,-12) | <(-13,-65),39>
+ <(1,2),100> | (-5,-12) | <(19,-22),1300>
+ <(1,3),5> | (-5,-12) | <(31,-27),65>
+ <(1,2),3> | (-5,-12) | <(19,-22),39>
+ <(100,200),10> | (-5,-12) | <(1900,-2200),130>
+ <(100,1),115> | (-5,-12) | <(-488,-1205),1495>
+ <(3,5),0> | (-5,-12) | <(45,-61),0>
+ <(3,5),NaN> | (-5,-12) | <(45,-61),NaN>
+ <(5,1),3> | (1e-300,-1e-300) | <(6e-300,-4e-300),4.24264068712e-300>
+ <(1,2),100> | (1e-300,-1e-300) | <(3e-300,1e-300),1.41421356237e-298>
+ <(1,3),5> | (1e-300,-1e-300) | <(4e-300,2e-300),7.07106781187e-300>
+ <(1,2),3> | (1e-300,-1e-300) | <(3e-300,1e-300),4.24264068712e-300>
+ <(100,200),10> | (1e-300,-1e-300) | <(3e-298,1e-298),1.41421356237e-299>
+ <(100,1),115> | (1e-300,-1e-300) | <(1.01e-298,-9.9e-299),1.62634559673e-298>
+ <(3,5),0> | (1e-300,-1e-300) | <(8e-300,2e-300),0>
+ <(3,5),NaN> | (1e-300,-1e-300) | <(8e-300,2e-300),NaN>
+ <(5,1),3> | (1e+300,Infinity) | <(-Infinity,Infinity),Infinity>
+ <(1,2),100> | (1e+300,Infinity) | <(-Infinity,Infinity),Infinity>
+ <(1,3),5> | (1e+300,Infinity) | <(-Infinity,Infinity),Infinity>
+ <(1,2),3> | (1e+300,Infinity) | <(-Infinity,Infinity),Infinity>
+ <(100,200),10> | (1e+300,Infinity) | <(-Infinity,Infinity),Infinity>
+ <(100,1),115> | (1e+300,Infinity) | <(-Infinity,Infinity),Infinity>
+ <(3,5),0> | (1e+300,Infinity) | <(-Infinity,Infinity),NaN>
+ <(3,5),NaN> | (1e+300,Infinity) | <(-Infinity,Infinity),NaN>
+ <(5,1),3> | (Infinity,1e+300) | <(Infinity,Infinity),Infinity>
+ <(1,2),100> | (Infinity,1e+300) | <(Infinity,Infinity),Infinity>
+ <(1,3),5> | (Infinity,1e+300) | <(Infinity,Infinity),Infinity>
+ <(1,2),3> | (Infinity,1e+300) | <(Infinity,Infinity),Infinity>
+ <(100,200),10> | (Infinity,1e+300) | <(Infinity,Infinity),Infinity>
+ <(100,1),115> | (Infinity,1e+300) | <(Infinity,Infinity),Infinity>
+ <(3,5),0> | (Infinity,1e+300) | <(Infinity,Infinity),NaN>
+ <(3,5),NaN> | (Infinity,1e+300) | <(Infinity,Infinity),NaN>
+ <(5,1),3> | (NaN,NaN) | <(NaN,NaN),NaN>
+ <(1,2),100> | (NaN,NaN) | <(NaN,NaN),NaN>
+ <(1,3),5> | (NaN,NaN) | <(NaN,NaN),NaN>
+ <(1,2),3> | (NaN,NaN) | <(NaN,NaN),NaN>
+ <(100,200),10> | (NaN,NaN) | <(NaN,NaN),NaN>
+ <(100,1),115> | (NaN,NaN) | <(NaN,NaN),NaN>
+ <(3,5),0> | (NaN,NaN) | <(NaN,NaN),NaN>
+ <(3,5),NaN> | (NaN,NaN) | <(NaN,NaN),NaN>
+ <(5,1),3> | (10,10) | <(40,60),42.4264068712>
+ <(1,2),100> | (10,10) | <(-10,30),1414.21356237>
+ <(1,3),5> | (10,10) | <(-20,40),70.7106781187>
+ <(1,2),3> | (10,10) | <(-10,30),42.4264068712>
+ <(100,200),10> | (10,10) | <(-1000,3000),141.421356237>
+ <(100,1),115> | (10,10) | <(990,1010),1626.34559673>
+ <(3,5),0> | (10,10) | <(-20,80),0>
+ <(3,5),NaN> | (10,10) | <(-20,80),NaN>
+(80 rows)
+
+-- 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;
+ f1 | f1 | ?column?
+----------------+------------+------------------------------------------------------
+ <(5,1),3> | (5.1,34.5) | <(0.0493315573973,-0.137635045138),0.0860217042937>
+ <(5,1),3> | (10,10) | <(0.3,-0.2),0.212132034356>
+ <(1,2),100> | (5.1,34.5) | <(0.0609244733856,-0.0199792807459),2.86739014312>
+ <(1,2),100> | (10,10) | <(0.15,0.05),7.07106781187>
+ <(1,3),5> | (5.1,34.5) | <(0.0892901188891,-0.0157860983671),0.143369507156>
+ <(1,3),5> | (10,10) | <(0.2,0.1),0.353553390593>
+ <(1,2),3> | (5.1,34.5) | <(0.0609244733856,-0.0199792807459),0.0860217042937>
+ <(1,2),3> | (10,10) | <(0.15,0.05),0.212132034356>
+ <(100,200),10> | (5.1,34.5) | <(6.09244733856,-1.99792807459),0.286739014312>
+ <(100,200),10> | (10,10) | <(15,5),0.707106781187>
+ <(100,1),115> | (5.1,34.5) | <(0.44768388338,-2.83237136796),3.29749866459>
+ <(100,1),115> | (10,10) | <(5.05,-4.95),8.13172798365>
+ <(3,5),0> | (5.1,34.5) | <(0.154407774653,-0.0641310246164),0>
+ <(3,5),0> | (10,10) | <(0.4,0.1),0>
+ <(3,5),NaN> | (5.1,34.5) | <(0.154407774653,-0.0641310246164),NaN>
+ <(3,5),NaN> | (10,10) | <(0.4,0.1),NaN>
+(16 rows)
+
+-- Overflow error
+SELECT c.f1, p.f1, c.f1 / p.f1 FROM CIRCLE_TBL c, POINT_TBL p WHERE p.f1[0] > 1000;
+ERROR: value out of range: overflow
+-- 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;
+ERROR: division by zero
+-- Distance to polygon
+SELECT c.f1, p.f1, c.f1 <-> p.f1 FROM CIRCLE_TBL c, POLYGON_TBL p;
+ f1 | f1 | ?column?
+----------------+----------------------------+----------------
+ <(5,1),3> | ((2,0),(2,4),(0,0)) | 0
+ <(5,1),3> | ((3,1),(3,3),(1,0)) | 0
+ <(5,1),3> | ((1,2),(3,4),(5,6),(7,8)) | 0.535533905933
+ <(5,1),3> | ((7,8),(5,6),(3,4),(1,2)) | 0.535533905933
+ <(5,1),3> | ((1,2),(7,8),(5,6),(3,-4)) | 0
+ <(5,1),3> | ((0,0)) | 2.09901951359
+ <(5,1),3> | ((0,1),(0,1)) | 2
+ <(1,2),100> | ((2,0),(2,4),(0,0)) | 0
+ <(1,2),100> | ((3,1),(3,3),(1,0)) | 0
+ <(1,2),100> | ((1,2),(3,4),(5,6),(7,8)) | 0
+ <(1,2),100> | ((7,8),(5,6),(3,4),(1,2)) | 0
+ <(1,2),100> | ((1,2),(7,8),(5,6),(3,-4)) | 0
+ <(1,2),100> | ((0,0)) | 0
+ <(1,2),100> | ((0,1),(0,1)) | 0
+ <(1,3),5> | ((2,0),(2,4),(0,0)) | 0
+ <(1,3),5> | ((3,1),(3,3),(1,0)) | 0
+ <(1,3),5> | ((1,2),(3,4),(5,6),(7,8)) | 0
+ <(1,3),5> | ((7,8),(5,6),(3,4),(1,2)) | 0
+ <(1,3),5> | ((1,2),(7,8),(5,6),(3,-4)) | 0
+ <(1,3),5> | ((0,0)) | 0
+ <(1,3),5> | ((0,1),(0,1)) | 0
+ <(1,2),3> | ((2,0),(2,4),(0,0)) | 0
+ <(1,2),3> | ((3,1),(3,3),(1,0)) | 0
+ <(1,2),3> | ((1,2),(3,4),(5,6),(7,8)) | 0
+ <(1,2),3> | ((7,8),(5,6),(3,4),(1,2)) | 0
+ <(1,2),3> | ((1,2),(7,8),(5,6),(3,-4)) | 0
+ <(1,2),3> | ((0,0)) | 0
+ <(1,2),3> | ((0,1),(0,1)) | 0
+ <(100,200),10> | ((2,0),(2,4),(0,0)) | 209.134661795
+ <(100,200),10> | ((3,1),(3,3),(1,0)) | 209.585974051
+ <(100,200),10> | ((1,2),(3,4),(5,6),(7,8)) | 203.337760371
+ <(100,200),10> | ((7,8),(5,6),(3,4),(1,2)) | 203.337760371
+ <(100,200),10> | ((1,2),(7,8),(5,6),(3,-4)) | 203.337760371
+ <(100,200),10> | ((0,0)) | 213.60679775
+ <(100,200),10> | ((0,1),(0,1)) | 212.712819568
+ <(100,1),115> | ((2,0),(2,4),(0,0)) | 0
+ <(100,1),115> | ((3,1),(3,3),(1,0)) | 0
+ <(100,1),115> | ((1,2),(3,4),(5,6),(7,8)) | 0
+ <(100,1),115> | ((7,8),(5,6),(3,4),(1,2)) | 0
+ <(100,1),115> | ((1,2),(7,8),(5,6),(3,-4)) | 0
+ <(100,1),115> | ((0,0)) | 0
+ <(100,1),115> | ((0,1),(0,1)) | 0
+ <(3,5),0> | ((2,0),(2,4),(0,0)) | 1.41421356237
+ <(3,5),0> | ((3,1),(3,3),(1,0)) | 2
+ <(3,5),0> | ((1,2),(3,4),(5,6),(7,8)) | 0.707106781187
+ <(3,5),0> | ((7,8),(5,6),(3,4),(1,2)) | 0.707106781187
+ <(3,5),0> | ((1,2),(7,8),(5,6),(3,-4)) | 0.707106781187
+ <(3,5),0> | ((0,0)) | 5.83095189485
+ <(3,5),0> | ((0,1),(0,1)) | 5
+ <(3,5),NaN> | ((2,0),(2,4),(0,0)) | NaN
+ <(3,5),NaN> | ((3,1),(3,3),(1,0)) | NaN
+ <(3,5),NaN> | ((1,2),(3,4),(5,6),(7,8)) | NaN
+ <(3,5),NaN> | ((7,8),(5,6),(3,4),(1,2)) | NaN
+ <(3,5),NaN> | ((1,2),(7,8),(5,6),(3,-4)) | NaN
+ <(3,5),NaN> | ((0,0)) | NaN
+ <(3,5),NaN> | ((0,1),(0,1)) | NaN
+(56 rows)
+
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
new file mode 100644
index 0000000..6402e89
--- /dev/null
+++ b/src/test/regress/expected/gin.out
@@ -0,0 +1,290 @@
+--
+-- Test GIN indexes.
+--
+-- There are other tests to test different GIN opclasses. 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);
+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
+ many
+------
+ t
+(1 row)
+
+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
+ gin_clean_pending_list
+------------------------
+ 0
+(1 row)
+
+-- 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;
+-- Test for "rare && frequent" searches
+explain (costs off)
+select count(*) from gin_test_tbl where i @> array[1, 999];
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @> '{1,999}'::integer[])
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @> '{1,999}'::integer[])
+(5 rows)
+
+select count(*) from gin_test_tbl where i @> array[1, 999];
+ count
+-------
+ 3
+(1 row)
+
+-- Very weak test for gin_fuzzy_search_limit
+set gin_fuzzy_search_limit = 1000;
+explain (costs off)
+select count(*) > 0 as ok from gin_test_tbl where i @> array[1];
+ QUERY PLAN
+---------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @> '{1}'::integer[])
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @> '{1}'::integer[])
+(5 rows)
+
+select count(*) > 0 as ok from gin_test_tbl where i @> array[1];
+ ok
+----
+ t
+(1 row)
+
+reset gin_fuzzy_search_limit;
+-- Test optimization of empty queries
+create temp table t_gin_test_tbl(i int4[], j int4[]);
+create index on t_gin_test_tbl using gin (i, j);
+insert into t_gin_test_tbl
+values
+ (null, null),
+ ('{}', null),
+ ('{1}', null),
+ ('{1,2}', null),
+ (null, '{}'),
+ (null, '{10}'),
+ ('{1,2}', '{10}'),
+ ('{2}', '{10}'),
+ ('{1,3}', '{}'),
+ ('{1,1}', '{10}');
+set enable_seqscan = off;
+explain (costs off)
+select * from t_gin_test_tbl where array[0] <@ i;
+ QUERY PLAN
+---------------------------------------------------
+ Bitmap Heap Scan on t_gin_test_tbl
+ Recheck Cond: ('{0}'::integer[] <@ i)
+ -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx
+ Index Cond: (i @> '{0}'::integer[])
+(4 rows)
+
+select * from t_gin_test_tbl where array[0] <@ i;
+ i | j
+---+---
+(0 rows)
+
+select * from t_gin_test_tbl where array[0] <@ i and '{}'::int4[] <@ j;
+ i | j
+---+---
+(0 rows)
+
+explain (costs off)
+select * from t_gin_test_tbl where i @> '{}';
+ QUERY PLAN
+---------------------------------------------------
+ Bitmap Heap Scan on t_gin_test_tbl
+ Recheck Cond: (i @> '{}'::integer[])
+ -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx
+ Index Cond: (i @> '{}'::integer[])
+(4 rows)
+
+select * from t_gin_test_tbl where i @> '{}';
+ i | j
+-------+------
+ {} |
+ {1} |
+ {1,2} |
+ {1,2} | {10}
+ {2} | {10}
+ {1,3} | {}
+ {1,1} | {10}
+(7 rows)
+
+create function explain_query_json(query_sql text)
+returns table (explain_line json)
+language plpgsql as
+$$
+begin
+ set enable_seqscan = off;
+ set enable_bitmapscan = on;
+ return query execute 'EXPLAIN (ANALYZE, FORMAT json) ' || query_sql;
+end;
+$$;
+create function execute_text_query_index(query_sql text)
+returns setof text
+language plpgsql
+as
+$$
+begin
+ set enable_seqscan = off;
+ set enable_bitmapscan = on;
+ return query execute query_sql;
+end;
+$$;
+create function execute_text_query_heap(query_sql text)
+returns setof text
+language plpgsql
+as
+$$
+begin
+ set enable_seqscan = on;
+ set enable_bitmapscan = off;
+ return query execute query_sql;
+end;
+$$;
+-- check number of rows returned by index and removed by recheck
+select
+ query,
+ js->0->'Plan'->'Plans'->0->'Actual Rows' as "return by index",
+ js->0->'Plan'->'Rows Removed by Index Recheck' as "removed by recheck",
+ (res_index = res_heap) as "match"
+from
+ (values
+ ($$ i @> '{}' $$),
+ ($$ j @> '{}' $$),
+ ($$ i @> '{}' and j @> '{}' $$),
+ ($$ i @> '{1}' $$),
+ ($$ i @> '{1}' and j @> '{}' $$),
+ ($$ i @> '{1}' and i @> '{}' and j @> '{}' $$),
+ ($$ j @> '{10}' $$),
+ ($$ j @> '{10}' and i @> '{}' $$),
+ ($$ j @> '{10}' and j @> '{}' and i @> '{}' $$),
+ ($$ i @> '{1}' and j @> '{10}' $$)
+ ) q(query),
+ lateral explain_query_json($$select * from t_gin_test_tbl where $$ || query) js,
+ lateral execute_text_query_index($$select string_agg((i, j)::text, ' ') from t_gin_test_tbl where $$ || query) res_index,
+ lateral execute_text_query_heap($$select string_agg((i, j)::text, ' ') from t_gin_test_tbl where $$ || query) res_heap;
+ query | return by index | removed by recheck | match
+-------------------------------------------+-----------------+--------------------+-------
+ i @> '{}' | 7 | 0 | t
+ j @> '{}' | 6 | 0 | t
+ i @> '{}' and j @> '{}' | 4 | 0 | t
+ i @> '{1}' | 5 | 0 | t
+ i @> '{1}' and j @> '{}' | 3 | 0 | t
+ i @> '{1}' and i @> '{}' and j @> '{}' | 3 | 0 | t
+ j @> '{10}' | 4 | 0 | t
+ j @> '{10}' and i @> '{}' | 3 | 0 | t
+ j @> '{10}' and j @> '{}' and i @> '{}' | 3 | 0 | t
+ i @> '{1}' and j @> '{10}' | 2 | 0 | t
+(10 rows)
+
+reset enable_seqscan;
+reset enable_bitmapscan;
+-- re-purpose t_gin_test_tbl to test scans involving posting trees
+insert into t_gin_test_tbl select array[1, g, g/10], array[2, g, g/10]
+ from generate_series(1, 20000) g;
+select gin_clean_pending_list('t_gin_test_tbl_i_j_idx') is not null;
+ ?column?
+----------
+ t
+(1 row)
+
+analyze t_gin_test_tbl;
+set enable_seqscan = off;
+set enable_bitmapscan = on;
+explain (costs off)
+select count(*) from t_gin_test_tbl where j @> array[50];
+ QUERY PLAN
+---------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on t_gin_test_tbl
+ Recheck Cond: (j @> '{50}'::integer[])
+ -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx
+ Index Cond: (j @> '{50}'::integer[])
+(5 rows)
+
+select count(*) from t_gin_test_tbl where j @> array[50];
+ count
+-------
+ 11
+(1 row)
+
+explain (costs off)
+select count(*) from t_gin_test_tbl where j @> array[2];
+ QUERY PLAN
+---------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on t_gin_test_tbl
+ Recheck Cond: (j @> '{2}'::integer[])
+ -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx
+ Index Cond: (j @> '{2}'::integer[])
+(5 rows)
+
+select count(*) from t_gin_test_tbl where j @> array[2];
+ count
+-------
+ 20000
+(1 row)
+
+explain (costs off)
+select count(*) from t_gin_test_tbl where j @> '{}'::int[];
+ QUERY PLAN
+---------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on t_gin_test_tbl
+ Recheck Cond: (j @> '{}'::integer[])
+ -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx
+ Index Cond: (j @> '{}'::integer[])
+(5 rows)
+
+select count(*) from t_gin_test_tbl where j @> '{}'::int[];
+ count
+-------
+ 20006
+(1 row)
+
+-- test vacuuming of posting trees
+delete from t_gin_test_tbl where j @> array[2];
+vacuum t_gin_test_tbl;
+select count(*) from t_gin_test_tbl where j @> array[50];
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from t_gin_test_tbl where j @> array[2];
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from t_gin_test_tbl where j @> '{}'::int[];
+ count
+-------
+ 6
+(1 row)
+
+reset enable_seqscan;
+reset enable_bitmapscan;
+drop table t_gin_test_tbl;
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
new file mode 100644
index 0000000..2393132
--- /dev/null
+++ b/src/test/regress/expected/gist.out
@@ -0,0 +1,394 @@
+--
+-- Test GiST indexes.
+--
+-- There are other tests to test different GiST opclasses. This is for
+-- testing GiST code itself. Vacuuming in particular.
+create table gist_point_tbl(id int4, p point);
+create index gist_pointidx on gist_point_tbl using gist(p);
+-- Verify the fillfactor and buffering options
+create index gist_pointidx2 on gist_point_tbl using gist(p) with (buffering = on, fillfactor=50);
+create index gist_pointidx3 on gist_point_tbl using gist(p) with (buffering = off);
+create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = auto);
+drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
+-- Make sure bad values are refused
+create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
+ERROR: invalid value for enum option "buffering": invalid_value
+DETAIL: Valid values are "on", "off", and "auto".
+create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
+ERROR: value 9 out of bounds for option "fillfactor"
+DETAIL: Valid values are between "10" and "100".
+create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=101);
+ERROR: value 101 out of bounds for option "fillfactor"
+DETAIL: Valid values are between "10" and "100".
+-- Insert enough data to create a tree that's a couple of levels deep.
+insert into gist_point_tbl (id, p)
+select g, point(g*10, g*10) from generate_series(1, 10000) g;
+insert into gist_point_tbl (id, p)
+select g+100000, point(g*10+1, g*10+1) from generate_series(1, 10000) g;
+-- To test vacuum, delete some entries from all over the index.
+delete from gist_point_tbl where id % 2 = 1;
+-- And also delete some concentration of values.
+delete from gist_point_tbl where id > 5000;
+vacuum analyze gist_point_tbl;
+-- rebuild the index with a different fillfactor
+alter index gist_pointidx SET (fillfactor = 40);
+reindex index gist_pointidx;
+--
+-- Test Index-only plans on GiST indexes
+--
+create table gist_tbl (b box, p point, c circle);
+insert into gist_tbl
+select box(point(0.05*i, 0.05*i), point(0.05*i, 0.05*i)),
+ point(0.05*i, 0.05*i),
+ circle(point(0.05*i, 0.05*i), 1.0)
+from generate_series(0,10000) as i;
+vacuum analyze gist_tbl;
+set enable_seqscan=off;
+set enable_bitmapscan=off;
+set enable_indexonlyscan=on;
+-- Test index-only scan with point opclass
+create index gist_tbl_point_index on gist_tbl using gist (p);
+-- check that the planner chooses an index-only scan
+explain (costs off)
+select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5));
+ QUERY PLAN
+--------------------------------------------------------
+ Index Only Scan using gist_tbl_point_index on gist_tbl
+ Index Cond: (p <@ '(0.5,0.5),(0,0)'::box)
+(2 rows)
+
+-- execute the same
+select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5));
+ p
+-------------
+ (0,0)
+ (0.05,0.05)
+ (0.1,0.1)
+ (0.15,0.15)
+ (0.2,0.2)
+ (0.25,0.25)
+ (0.3,0.3)
+ (0.35,0.35)
+ (0.4,0.4)
+ (0.45,0.45)
+ (0.5,0.5)
+(11 rows)
+
+-- Also test an index-only knn-search
+explain (costs off)
+select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5))
+order by p <-> point(0.201, 0.201);
+ QUERY PLAN
+--------------------------------------------------------
+ Index Only Scan using gist_tbl_point_index on gist_tbl
+ Index Cond: (p <@ '(0.5,0.5),(0,0)'::box)
+ Order By: (p <-> '(0.201,0.201)'::point)
+(3 rows)
+
+select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5))
+order by p <-> point(0.201, 0.201);
+ p
+-------------
+ (0.2,0.2)
+ (0.25,0.25)
+ (0.15,0.15)
+ (0.3,0.3)
+ (0.1,0.1)
+ (0.35,0.35)
+ (0.05,0.05)
+ (0.4,0.4)
+ (0,0)
+ (0.45,0.45)
+ (0.5,0.5)
+(11 rows)
+
+-- Check commuted case as well
+explain (costs off)
+select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5))
+order by point(0.101, 0.101) <-> p;
+ QUERY PLAN
+--------------------------------------------------------
+ Index Only Scan using gist_tbl_point_index on gist_tbl
+ Index Cond: (p <@ '(0.5,0.5),(0,0)'::box)
+ Order By: (p <-> '(0.101,0.101)'::point)
+(3 rows)
+
+select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5))
+order by point(0.101, 0.101) <-> p;
+ p
+-------------
+ (0.1,0.1)
+ (0.15,0.15)
+ (0.05,0.05)
+ (0.2,0.2)
+ (0,0)
+ (0.25,0.25)
+ (0.3,0.3)
+ (0.35,0.35)
+ (0.4,0.4)
+ (0.45,0.45)
+ (0.5,0.5)
+(11 rows)
+
+-- Check case with multiple rescans (bug #14641)
+explain (costs off)
+select p from
+ (values (box(point(0,0), point(0.5,0.5))),
+ (box(point(0.5,0.5), point(0.75,0.75))),
+ (box(point(0.8,0.8), point(1.0,1.0)))) as v(bb)
+cross join lateral
+ (select p from gist_tbl where p <@ bb order by p <-> bb[0] limit 2) ss;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Nested Loop
+ -> Values Scan on "*VALUES*"
+ -> Limit
+ -> Index Only Scan using gist_tbl_point_index on gist_tbl
+ Index Cond: (p <@ "*VALUES*".column1)
+ Order By: (p <-> ("*VALUES*".column1)[0])
+(6 rows)
+
+select p from
+ (values (box(point(0,0), point(0.5,0.5))),
+ (box(point(0.5,0.5), point(0.75,0.75))),
+ (box(point(0.8,0.8), point(1.0,1.0)))) as v(bb)
+cross join lateral
+ (select p from gist_tbl where p <@ bb order by p <-> bb[0] limit 2) ss;
+ p
+-------------
+ (0.5,0.5)
+ (0.45,0.45)
+ (0.75,0.75)
+ (0.7,0.7)
+ (1,1)
+ (0.95,0.95)
+(6 rows)
+
+drop index gist_tbl_point_index;
+-- Test index-only scan with box opclass
+create index gist_tbl_box_index on gist_tbl using gist (b);
+-- check that the planner chooses an index-only scan
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6));
+ QUERY PLAN
+------------------------------------------------------
+ Index Only Scan using gist_tbl_box_index on gist_tbl
+ Index Cond: (b <@ '(6,6),(5,5)'::box)
+(2 rows)
+
+-- execute the same
+select b from gist_tbl where b <@ box(point(5,5), point(6,6));
+ b
+-------------------------
+ (5,5),(5,5)
+ (5.05,5.05),(5.05,5.05)
+ (5.1,5.1),(5.1,5.1)
+ (5.15,5.15),(5.15,5.15)
+ (5.2,5.2),(5.2,5.2)
+ (5.25,5.25),(5.25,5.25)
+ (5.3,5.3),(5.3,5.3)
+ (5.35,5.35),(5.35,5.35)
+ (5.4,5.4),(5.4,5.4)
+ (5.45,5.45),(5.45,5.45)
+ (5.5,5.5),(5.5,5.5)
+ (5.55,5.55),(5.55,5.55)
+ (5.6,5.6),(5.6,5.6)
+ (5.65,5.65),(5.65,5.65)
+ (5.7,5.7),(5.7,5.7)
+ (5.75,5.75),(5.75,5.75)
+ (5.8,5.8),(5.8,5.8)
+ (5.85,5.85),(5.85,5.85)
+ (5.9,5.9),(5.9,5.9)
+ (5.95,5.95),(5.95,5.95)
+ (6,6),(6,6)
+(21 rows)
+
+-- Also test an index-only knn-search
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+ QUERY PLAN
+------------------------------------------------------
+ Index Only Scan using gist_tbl_box_index on gist_tbl
+ Index Cond: (b <@ '(6,6),(5,5)'::box)
+ Order By: (b <-> '(5.2,5.91)'::point)
+(3 rows)
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+ b
+-------------------------
+ (5.55,5.55),(5.55,5.55)
+ (5.6,5.6),(5.6,5.6)
+ (5.5,5.5),(5.5,5.5)
+ (5.65,5.65),(5.65,5.65)
+ (5.45,5.45),(5.45,5.45)
+ (5.7,5.7),(5.7,5.7)
+ (5.4,5.4),(5.4,5.4)
+ (5.75,5.75),(5.75,5.75)
+ (5.35,5.35),(5.35,5.35)
+ (5.8,5.8),(5.8,5.8)
+ (5.3,5.3),(5.3,5.3)
+ (5.85,5.85),(5.85,5.85)
+ (5.25,5.25),(5.25,5.25)
+ (5.9,5.9),(5.9,5.9)
+ (5.2,5.2),(5.2,5.2)
+ (5.95,5.95),(5.95,5.95)
+ (5.15,5.15),(5.15,5.15)
+ (6,6),(6,6)
+ (5.1,5.1),(5.1,5.1)
+ (5.05,5.05),(5.05,5.05)
+ (5,5),(5,5)
+(21 rows)
+
+-- Check commuted case as well
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+ QUERY PLAN
+------------------------------------------------------
+ Index Only Scan using gist_tbl_box_index on gist_tbl
+ Index Cond: (b <@ '(6,6),(5,5)'::box)
+ Order By: (b <-> '(5.2,5.91)'::point)
+(3 rows)
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+ b
+-------------------------
+ (5.55,5.55),(5.55,5.55)
+ (5.6,5.6),(5.6,5.6)
+ (5.5,5.5),(5.5,5.5)
+ (5.65,5.65),(5.65,5.65)
+ (5.45,5.45),(5.45,5.45)
+ (5.7,5.7),(5.7,5.7)
+ (5.4,5.4),(5.4,5.4)
+ (5.75,5.75),(5.75,5.75)
+ (5.35,5.35),(5.35,5.35)
+ (5.8,5.8),(5.8,5.8)
+ (5.3,5.3),(5.3,5.3)
+ (5.85,5.85),(5.85,5.85)
+ (5.25,5.25),(5.25,5.25)
+ (5.9,5.9),(5.9,5.9)
+ (5.2,5.2),(5.2,5.2)
+ (5.95,5.95),(5.95,5.95)
+ (5.15,5.15),(5.15,5.15)
+ (6,6),(6,6)
+ (5.1,5.1),(5.1,5.1)
+ (5.05,5.05),(5.05,5.05)
+ (5,5),(5,5)
+(21 rows)
+
+drop index gist_tbl_box_index;
+-- Test that an index-only scan is not chosen, when the query involves the
+-- circle column (the circle opclass does not support index-only scans).
+create index gist_tbl_multi_index on gist_tbl using gist (p, c);
+explain (costs off)
+select p, c from gist_tbl
+where p <@ box(point(5,5), point(6, 6));
+ QUERY PLAN
+---------------------------------------------------
+ Index Scan using gist_tbl_multi_index on gist_tbl
+ Index Cond: (p <@ '(6,6),(5,5)'::box)
+(2 rows)
+
+-- execute the same
+select b, p from gist_tbl
+where b <@ box(point(4.5, 4.5), point(5.5, 5.5))
+and p <@ box(point(5,5), point(6, 6));
+ b | p
+-------------------------+-------------
+ (5,5),(5,5) | (5,5)
+ (5.05,5.05),(5.05,5.05) | (5.05,5.05)
+ (5.1,5.1),(5.1,5.1) | (5.1,5.1)
+ (5.15,5.15),(5.15,5.15) | (5.15,5.15)
+ (5.2,5.2),(5.2,5.2) | (5.2,5.2)
+ (5.25,5.25),(5.25,5.25) | (5.25,5.25)
+ (5.3,5.3),(5.3,5.3) | (5.3,5.3)
+ (5.35,5.35),(5.35,5.35) | (5.35,5.35)
+ (5.4,5.4),(5.4,5.4) | (5.4,5.4)
+ (5.45,5.45),(5.45,5.45) | (5.45,5.45)
+ (5.5,5.5),(5.5,5.5) | (5.5,5.5)
+(11 rows)
+
+drop index gist_tbl_multi_index;
+-- Test that we don't try to return the value of a non-returnable
+-- column in an index-only scan. (This isn't GIST-specific, but
+-- it only applies to index AMs that can return some columns and not
+-- others, so GIST with appropriate opclasses is a convenient test case.)
+create index gist_tbl_multi_index on gist_tbl using gist (circle(p,1), p);
+explain (verbose, costs off)
+select circle(p,1) from gist_tbl
+where p <@ box(point(5, 5), point(5.3, 5.3));
+ QUERY PLAN
+---------------------------------------------------------------
+ Index Only Scan using gist_tbl_multi_index on public.gist_tbl
+ Output: circle(p, '1'::double precision)
+ Index Cond: (gist_tbl.p <@ '(5.3,5.3),(5,5)'::box)
+(3 rows)
+
+select circle(p,1) from gist_tbl
+where p <@ box(point(5, 5), point(5.3, 5.3));
+ circle
+-----------------
+ <(5,5),1>
+ <(5.05,5.05),1>
+ <(5.1,5.1),1>
+ <(5.15,5.15),1>
+ <(5.2,5.2),1>
+ <(5.25,5.25),1>
+ <(5.3,5.3),1>
+(7 rows)
+
+-- Similarly, test that index rechecks involving a non-returnable column
+-- are done correctly.
+explain (verbose, costs off)
+select p from gist_tbl where circle(p,1) @> circle(point(0,0),0.95);
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Index Only Scan using gist_tbl_multi_index on public.gist_tbl
+ Output: p
+ Index Cond: ((circle(gist_tbl.p, '1'::double precision)) @> '<(0,0),0.95>'::circle)
+(3 rows)
+
+select p from gist_tbl where circle(p,1) @> circle(point(0,0),0.95);
+ p
+-------
+ (0,0)
+(1 row)
+
+-- Also check that use_physical_tlist doesn't trigger in such cases.
+explain (verbose, costs off)
+select count(*) from gist_tbl;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Aggregate
+ Output: count(*)
+ -> Index Only Scan using gist_tbl_multi_index on public.gist_tbl
+(3 rows)
+
+select count(*) from gist_tbl;
+ count
+-------
+ 10001
+(1 row)
+
+-- This case isn't supported, but it should at least EXPLAIN correctly.
+explain (verbose, costs off)
+select p from gist_tbl order by circle(p,1) <-> point(0,0) limit 1;
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Limit
+ Output: p, ((circle(p, '1'::double precision) <-> '(0,0)'::point))
+ -> Index Only Scan using gist_tbl_multi_index on public.gist_tbl
+ Output: p, (circle(p, '1'::double precision) <-> '(0,0)'::point)
+ Order By: ((circle(gist_tbl.p, '1'::double precision)) <-> '(0,0)'::point)
+(5 rows)
+
+select p from gist_tbl order by circle(p,1) <-> point(0,0) limit 1;
+ERROR: lossy distance functions are not supported in index-only scans
+-- Clean up
+reset enable_seqscan;
+reset enable_bitmapscan;
+reset enable_indexonlyscan;
+drop table gist_tbl;
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
new file mode 100644
index 0000000..2be94ed
--- /dev/null
+++ b/src/test/regress/expected/groupingsets.out
@@ -0,0 +1,2152 @@
+--
+-- 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;
+create temp table gstest3 (a integer, b integer, c integer, d integer);
+copy gstest3 from stdin;
+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);
+ a | b | grouping | sum | count | max
+---+---+----------+-----+-------+-----
+ 1 | 1 | 0 | 21 | 2 | 11
+ 1 | 2 | 0 | 25 | 2 | 13
+ 1 | 3 | 0 | 14 | 1 | 14
+ 1 | | 1 | 60 | 5 | 14
+ 2 | 3 | 0 | 15 | 1 | 15
+ 2 | | 1 | 15 | 1 | 15
+ 3 | 3 | 0 | 16 | 1 | 16
+ 3 | 4 | 0 | 17 | 1 | 17
+ 3 | | 1 | 33 | 2 | 17
+ 4 | 1 | 0 | 37 | 2 | 19
+ 4 | | 1 | 37 | 2 | 19
+ | | 3 | 145 | 10 | 19
+(12 rows)
+
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+ from gstest1 group by rollup (a,b) order by a,b;
+ a | b | grouping | sum | count | max
+---+---+----------+-----+-------+-----
+ 1 | 1 | 0 | 21 | 2 | 11
+ 1 | 2 | 0 | 25 | 2 | 13
+ 1 | 3 | 0 | 14 | 1 | 14
+ 1 | | 1 | 60 | 5 | 14
+ 2 | 3 | 0 | 15 | 1 | 15
+ 2 | | 1 | 15 | 1 | 15
+ 3 | 3 | 0 | 16 | 1 | 16
+ 3 | 4 | 0 | 17 | 1 | 17
+ 3 | | 1 | 33 | 2 | 17
+ 4 | 1 | 0 | 37 | 2 | 19
+ 4 | | 1 | 37 | 2 | 19
+ | | 3 | 145 | 10 | 19
+(12 rows)
+
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+ from gstest1 group by rollup (a,b) order by b desc, a;
+ a | b | grouping | sum | count | max
+---+---+----------+-----+-------+-----
+ 1 | | 1 | 60 | 5 | 14
+ 2 | | 1 | 15 | 1 | 15
+ 3 | | 1 | 33 | 2 | 17
+ 4 | | 1 | 37 | 2 | 19
+ | | 3 | 145 | 10 | 19
+ 3 | 4 | 0 | 17 | 1 | 17
+ 1 | 3 | 0 | 14 | 1 | 14
+ 2 | 3 | 0 | 15 | 1 | 15
+ 3 | 3 | 0 | 16 | 1 | 16
+ 1 | 2 | 0 | 25 | 2 | 13
+ 1 | 1 | 0 | 21 | 2 | 11
+ 4 | 1 | 0 | 37 | 2 | 19
+(12 rows)
+
+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);
+ a | b | grouping | sum | count | max
+---+---+----------+-----+-------+-----
+ | | 3 | 145 | 10 | 19
+ 1 | | 1 | 60 | 5 | 14
+ 1 | 1 | 0 | 21 | 2 | 11
+ 2 | | 1 | 15 | 1 | 15
+ 3 | | 1 | 33 | 2 | 17
+ 1 | 2 | 0 | 25 | 2 | 13
+ 1 | 3 | 0 | 14 | 1 | 14
+ 4 | | 1 | 37 | 2 | 19
+ 4 | 1 | 0 | 37 | 2 | 19
+ 2 | 3 | 0 | 15 | 1 | 15
+ 3 | 3 | 0 | 16 | 1 | 16
+ 3 | 4 | 0 | 17 | 1 | 17
+(12 rows)
+
+-- 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;
+ a | b | grouping | array_agg | string_agg | percentile_disc | rank
+---+---+----------+---------------------------------+-------------------------------+-----------------+------
+ 1 | 1 | 0 | {10,11} | 11:10 | 10 | 3
+ 1 | 2 | 0 | {12,13} | 13:12 | 12 | 1
+ 1 | 3 | 0 | {14} | 14 | 14 | 1
+ 1 | | 1 | {10,11,12,13,14} | 14:13:12:11:10 | 12 | 3
+ 2 | 3 | 0 | {15} | 15 | 15 | 1
+ 2 | | 1 | {15} | 15 | 15 | 1
+ 3 | 3 | 0 | {16} | 16 | 16 | 1
+ 3 | 4 | 0 | {17} | 17 | 17 | 1
+ 3 | | 1 | {16,17} | 17:16 | 16 | 1
+ 4 | 1 | 0 | {18,19} | 19:18 | 18 | 1
+ 4 | | 1 | {18,19} | 19:18 | 18 | 1
+ | | 3 | {10,11,12,13,14,15,16,17,18,19} | 19:18:17:16:15:14:13:12:11:10 | 14 | 3
+(12 rows)
+
+-- 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;
+ grouping | a | array_agg | rank | rank
+----------+---+-------------+------+------
+ 0 | 1 | {1,4,5} | 1 | 1
+ 0 | 3 | {1,2} | 3 | 3
+ 1 | | {1,4,5,1,2} | 1 | 6
+(3 rows)
+
+-- 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;
+ a | b | sum | rsum
+---+---+-----+------
+ 1 | 1 | 8 | 8
+ 1 | 2 | 2 | 10
+ 1 | | 10 | 20
+ 2 | 2 | 2 | 22
+ 2 | | 2 | 24
+ | | 12 | 36
+(6 rows)
+
+-- nesting with grouping sets
+select sum(c) from gstest2
+ group by grouping sets((), grouping sets((), grouping sets(())))
+ order by 1 desc;
+ sum
+-----
+ 12
+ 12
+ 12
+(3 rows)
+
+select sum(c) from gstest2
+ group by grouping sets((), grouping sets((), grouping sets(((a, b)))))
+ order by 1 desc;
+ sum
+-----
+ 12
+ 12
+ 8
+ 2
+ 2
+(5 rows)
+
+select sum(c) from gstest2
+ group by grouping sets(grouping sets(rollup(c), grouping sets(cube(c))))
+ order by 1 desc;
+ sum
+-----
+ 12
+ 12
+ 6
+ 6
+ 6
+ 6
+(6 rows)
+
+select sum(c) from gstest2
+ group by grouping sets(a, grouping sets(a, cube(b)))
+ order by 1 desc;
+ sum
+-----
+ 12
+ 10
+ 10
+ 8
+ 4
+ 2
+ 2
+(7 rows)
+
+select sum(c) from gstest2
+ group by grouping sets(grouping sets((a, (b))))
+ order by 1 desc;
+ sum
+-----
+ 8
+ 2
+ 2
+(3 rows)
+
+select sum(c) from gstest2
+ group by grouping sets(grouping sets((a, b)))
+ order by 1 desc;
+ sum
+-----
+ 8
+ 2
+ 2
+(3 rows)
+
+select sum(c) from gstest2
+ group by grouping sets(grouping sets(a, grouping sets(a), a))
+ order by 1 desc;
+ sum
+-----
+ 10
+ 10
+ 10
+ 2
+ 2
+ 2
+(6 rows)
+
+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;
+ sum
+-----
+ 10
+ 10
+ 10
+ 10
+ 10
+ 10
+ 10
+ 10
+ 2
+ 2
+ 2
+ 2
+ 2
+ 2
+ 2
+ 2
+(16 rows)
+
+select sum(c) from gstest2
+ group by grouping sets((a,(a,b)), grouping sets((a,(a,b)),a))
+ order by 1 desc;
+ sum
+-----
+ 10
+ 8
+ 8
+ 2
+ 2
+ 2
+ 2
+ 2
+(8 rows)
+
+-- 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);
+ a | b | sum | count
+---+---+-----+-------
+(0 rows)
+
+select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),());
+ a | b | sum | count
+---+---+-----+-------
+ | | | 0
+(1 row)
+
+select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),());
+ a | b | sum | count
+---+---+-----+-------
+ | | | 0
+ | | | 0
+ | | | 0
+(3 rows)
+
+select sum(v), count(*) from gstest_empty group by grouping sets ((),(),());
+ sum | count
+-----+-------
+ | 0
+ | 0
+ | 0
+(3 rows)
+
+-- 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),());
+ a | b | sum | count
+---+---+-----+-------
+ | | | 0
+(1 row)
+
+-- 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), ());
+ a | b | grouping | sum | max
+---+---+----------+------+-----
+ 1 | 1 | 0 | 420 | 1
+ 1 | 2 | 0 | 120 | 2
+ 2 | 1 | 0 | 105 | 1
+ 2 | 2 | 0 | 30 | 2
+ 3 | 1 | 0 | 231 | 1
+ 3 | 2 | 0 | 66 | 2
+ 4 | 1 | 0 | 259 | 1
+ 4 | 2 | 0 | 74 | 2
+ | | 3 | 1305 | 2
+(9 rows)
+
+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), ());
+ a | b | grouping | sum | max
+---+---+----------+-----+-----
+ 1 | 1 | 0 | 420 | 1
+ 1 | 2 | 0 | 60 | 1
+ 2 | 2 | 0 | 15 | 2
+ | | 3 | 495 | 2
+(4 rows)
+
+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), ());
+ a | b | grouping | sum | max
+---+---+----------+-----+-----
+ 1 | 1 | 0 | 147 | 2
+ 1 | 2 | 0 | 25 | 2
+ | | 3 | 172 | 2
+(3 rows)
+
+-- 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));
+ a | d | grouping
+---+---+----------
+ 1 | 1 | 1
+ 2 | 2 | 1
+ 1 | 1 | 2
+ 2 | 2 | 2
+(4 rows)
+
+-- 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);
+ QUERY PLAN
+------------------------------------------------
+ GroupAggregate
+ Group Key: g, g
+ Group Key: g
+ -> Sort
+ Sort Key: g
+ -> Function Scan on generate_series g
+(6 rows)
+
+select g as alias1, g as alias2
+ from generate_series(1,3) g
+ group by alias1, rollup(alias2);
+ alias1 | alias2
+--------+--------
+ 1 | 1
+ 1 |
+ 2 | 2
+ 2 |
+ 3 | 3
+ 3 |
+(6 rows)
+
+-- 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';
+ four | x
+------+-----
+ | foo
+(1 row)
+
+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;
+ four | ?column?
+------+----------
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ | foox
+(5 rows)
+
+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);
+ ?column? | sum
+----------+-----
+ 3 | 3
+ | 3
+(2 rows)
+
+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;
+ x | not_x | q2
+---+-------+-------------------
+ f | t |
+ | | -4567890123456789
+ | | 123
+ | | 456
+ | | 4567890123456789
+(5 rows)
+
+-- 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;
+ QUERY PLAN
+--------------------------------------------
+ Subquery Scan on ss
+ Output: ss.x, ss.q1, ss.sum
+ Filter: ((ss.x = 1) AND (ss.q1 = 123))
+ -> GroupAggregate
+ Output: (1), i1.q1, sum(i1.q2)
+ Group Key: 1
+ Sort Key: i1.q1
+ Group Key: i1.q1
+ -> Seq Scan on public.int8_tbl i1
+ Output: 1, i1.q1, i1.q2
+(10 rows)
+
+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;
+ x | q1 | sum
+---+----+-----
+(0 rows)
+
+-- 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;
+ QUERY PLAN
+------------------------------------------------
+ GroupAggregate
+ Output: GROUPING((SubPlan 1)), ((SubPlan 2))
+ Group Key: ((SubPlan 2))
+ -> Sort
+ Output: ((SubPlan 2)), i1.q1
+ Sort Key: ((SubPlan 2))
+ -> Seq Scan on public.int8_tbl i1
+ Output: (SubPlan 2), i1.q1
+ SubPlan 2
+ -> Result
+ Output: i1.q1
+(11 rows)
+
+select grouping(ss.x)
+from int8_tbl i1
+cross join lateral (select (select i1.q1) as x) ss
+group by ss.x;
+ grouping
+----------
+ 0
+ 0
+(2 rows)
+
+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;
+ QUERY PLAN
+--------------------------------------------
+ GroupAggregate
+ Output: (SubPlan 2), ((SubPlan 3))
+ Group Key: ((SubPlan 3))
+ -> Sort
+ Output: ((SubPlan 3)), i1.q1
+ Sort Key: ((SubPlan 3))
+ -> Seq Scan on public.int8_tbl i1
+ Output: (SubPlan 3), i1.q1
+ SubPlan 3
+ -> Result
+ Output: i1.q1
+ SubPlan 2
+ -> Result
+ Output: GROUPING((SubPlan 1))
+(14 rows)
+
+select (select grouping(ss.x))
+from int8_tbl i1
+cross join lateral (select (select i1.q1) as x) ss
+group by ss.x;
+ grouping
+----------
+ 0
+ 0
+(2 rows)
+
+-- simple rescan tests
+select a, b, sum(v.x)
+ from (values (1),(2)) v(x), gstest_data(v.x)
+ group by rollup (a,b);
+ a | b | sum
+---+---+-----
+ 1 | 1 | 1
+ 1 | 2 | 1
+ 1 | 3 | 1
+ 1 | | 3
+ 2 | 1 | 2
+ 2 | 2 | 2
+ 2 | 3 | 2
+ 2 | | 6
+ | | 9
+(9 rows)
+
+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;
+ERROR: aggregate functions are not allowed in FROM clause of their own query level
+LINE 3: lateral (select a, b, sum(v.x) from gstest_data(v.x) ...
+ ^
+-- min max optimization should still work with GROUP BY ()
+explain (costs off)
+ select min(unique1) from tenk1 GROUP BY ();
+ QUERY PLAN
+------------------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 IS NOT NULL)
+(5 rows)
+
+-- 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));
+NOTICE: view "gstest_view" will be a temporary view
+select pg_get_viewdef('gstest_view'::regclass, true);
+ pg_get_viewdef
+-------------------------------------------------------------------------------
+ SELECT gstest2.a, +
+ gstest2.b, +
+ GROUPING(gstest2.a, gstest2.b) AS "grouping", +
+ sum(gstest2.c) AS sum, +
+ count(*) AS count, +
+ max(gstest2.c) AS max +
+ FROM gstest2 +
+ GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+(1 row)
+
+-- 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);
+ 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
+----------
+ 0
+ 1
+ 3
+(3 rows)
+
+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);
+ grouping
+----------
+ 0
+ 0
+ 0
+(3 rows)
+
+-- Combinations of operations
+select a, b, c, d from gstest2 group by rollup(a,b),grouping sets(c,d);
+ a | b | c | d
+---+---+---+---
+ 1 | 1 | 1 |
+ 1 | | 1 |
+ | | 1 |
+ 1 | 1 | 2 |
+ 1 | 2 | 2 |
+ 1 | | 2 |
+ 2 | 2 | 2 |
+ 2 | | 2 |
+ | | 2 |
+ 1 | 1 | | 1
+ 1 | | | 1
+ | | | 1
+ 1 | 1 | | 2
+ 1 | 2 | | 2
+ 1 | | | 2
+ 2 | 2 | | 2
+ 2 | | | 2
+ | | | 2
+(18 rows)
+
+select a, b from (values (1,2),(2,3)) v(a,b) group by a,b, grouping sets(a);
+ a | b
+---+---
+ 1 | 2
+ 2 | 3
+(2 rows)
+
+-- 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;
+ a | b | grouping | sum | count | max
+---+---+----------+-----+-------+-----
+ 1 | 1 | 0 | 21 | 2 | 11
+ 1 | 2 | 0 | 25 | 2 | 13
+ 1 | 3 | 0 | 14 | 1 | 14
+ 2 | 3 | 0 | 15 | 1 | 15
+ 3 | 3 | 0 | 16 | 1 | 16
+ 3 | 4 | 0 | 17 | 1 | 17
+ 4 | 1 | 0 | 37 | 2 | 19
+ | | 3 | 21 | 2 | 11
+ | | 3 | 21 | 2 | 11
+ | | 3 | 25 | 2 | 13
+ | | 3 | 25 | 2 | 13
+ | | 3 | 14 | 1 | 14
+ | | 3 | 14 | 1 | 14
+ | | 3 | 15 | 1 | 15
+ | | 3 | 15 | 1 | 15
+ | | 3 | 16 | 1 | 16
+ | | 3 | 16 | 1 | 16
+ | | 3 | 17 | 1 | 17
+ | | 3 | 17 | 1 | 17
+ | | 3 | 37 | 2 | 19
+ | | 3 | 37 | 2 | 19
+(21 rows)
+
+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));
+ grouping
+----------
+ 0
+ 0
+ 0
+(3 rows)
+
+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);
+ grouping
+----------
+ 0
+ 0
+ 0
+ 0
+(4 rows)
+
+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;
+ a | b | sum | rsum
+---+---+-----+------
+ 1 | 1 | 8 | 8
+ 1 | 2 | 2 | 10
+ 1 | | 10 | 20
+ 2 | 2 | 2 | 22
+ 2 | | 2 | 24
+ | 1 | 8 | 32
+ | 2 | 4 | 36
+ | | 12 | 48
+(8 rows)
+
+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);
+ a | b | sum
+---+---+-----
+ 1 | 1 | 21
+ 1 | 2 | 25
+ 1 | 3 | 14
+ 1 | | 60
+ 2 | 3 | 15
+ 2 | | 15
+ 3 | 3 | 16
+ 3 | 4 | 17
+ 3 | | 33
+ 4 | 1 | 37
+ 4 | | 37
+ | | 145
+(12 rows)
+
+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;
+ a | b | sum
+---+---+-----
+ 1 | 1 | 1
+ 1 | 2 | 1
+ 1 | 3 | 1
+ 1 | | 3
+ 2 | 1 | 2
+ 2 | 2 | 2
+ 2 | 3 | 2
+ 2 | | 6
+ | 1 | 3
+ | 2 | 3
+ | 3 | 3
+ | | 9
+(12 rows)
+
+-- Test reordering of grouping sets
+explain (costs off)
+select * from gstest1 group by grouping sets((a,b,v),(v)) order by v,b,a;
+ QUERY PLAN
+------------------------------------------------------------------------------
+ GroupAggregate
+ Group Key: "*VALUES*".column3, "*VALUES*".column2, "*VALUES*".column1
+ Group Key: "*VALUES*".column3
+ -> Sort
+ Sort Key: "*VALUES*".column3, "*VALUES*".column2, "*VALUES*".column1
+ -> Values Scan on "*VALUES*"
+(6 rows)
+
+-- Agg level check. This query should error out.
+select (select grouping(a,b) from gstest2) from gstest2 group by a,b;
+ERROR: arguments to GROUPING must be grouping expressions of the associated query level
+LINE 1: select (select grouping(a,b) from gstest2) from gstest2 grou...
+ ^
+--Nested queries
+select a, b, sum(c), count(*) from gstest2 group by grouping sets (rollup(a,b),a);
+ a | b | sum | count
+---+---+-----+-------
+ 1 | 1 | 8 | 7
+ 1 | 2 | 2 | 1
+ 1 | | 10 | 8
+ 1 | | 10 | 8
+ 2 | 2 | 2 | 1
+ 2 | | 2 | 1
+ 2 | | 2 | 1
+ | | 12 | 9
+(8 rows)
+
+-- 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);
+ ten | sum
+-----+-----
+ 0 | 0
+ 0 | 2
+ 0 | 2
+ 1 | 1
+ 1 | 3
+ 2 | 0
+ 2 | 2
+ 2 | 2
+ 3 | 1
+ 3 | 3
+ 4 | 0
+ 4 | 2
+ 4 | 2
+ 5 | 1
+ 5 | 3
+ 6 | 0
+ 6 | 2
+ 6 | 2
+ 7 | 1
+ 7 | 3
+ 8 | 0
+ 8 | 2
+ 8 | 2
+ 9 | 1
+ 9 | 3
+(25 rows)
+
+-- Tests around pushdown of HAVING clauses, partially testing against previous bugs
+select a,count(*) from gstest2 group by rollup(a) order by a;
+ a | count
+---+-------
+ 1 | 8
+ 2 | 1
+ | 9
+(3 rows)
+
+select a,count(*) from gstest2 group by rollup(a) having a is distinct from 1 order by a;
+ a | count
+---+-------
+ 2 | 1
+ | 9
+(2 rows)
+
+explain (costs off)
+ select a,count(*) from gstest2 group by rollup(a) having a is distinct from 1 order by a;
+ QUERY PLAN
+----------------------------------
+ GroupAggregate
+ Group Key: a
+ Group Key: ()
+ Filter: (a IS DISTINCT FROM 1)
+ -> Sort
+ Sort Key: a
+ -> Seq Scan on gstest2
+(7 rows)
+
+select v.c, (select count(*) from gstest2 group by () having v.c)
+ from (values (false),(true)) v(c) order by v.c;
+ c | count
+---+-------
+ f |
+ t | 9
+(2 rows)
+
+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;
+ QUERY PLAN
+-----------------------------------------------------------
+ Sort
+ Sort Key: "*VALUES*".column1
+ -> Values Scan on "*VALUES*"
+ SubPlan 1
+ -> Aggregate
+ Group Key: ()
+ Filter: "*VALUES*".column1
+ -> Result
+ One-Time Filter: "*VALUES*".column1
+ -> Seq Scan on gstest2
+(10 rows)
+
+-- HAVING with GROUPING queries
+select ten, grouping(ten) from onek
+group by grouping sets(ten) having grouping(ten) >= 0
+order by 2,1;
+ ten | grouping
+-----+----------
+ 0 | 0
+ 1 | 0
+ 2 | 0
+ 3 | 0
+ 4 | 0
+ 5 | 0
+ 6 | 0
+ 7 | 0
+ 8 | 0
+ 9 | 0
+(10 rows)
+
+select ten, grouping(ten) from onek
+group by grouping sets(ten, four) having grouping(ten) > 0
+order by 2,1;
+ ten | grouping
+-----+----------
+ | 1
+ | 1
+ | 1
+ | 1
+(4 rows)
+
+select ten, grouping(ten) from onek
+group by rollup(ten) having grouping(ten) > 0
+order by 2,1;
+ ten | grouping
+-----+----------
+ | 1
+(1 row)
+
+select ten, grouping(ten) from onek
+group by cube(ten) having grouping(ten) > 0
+order by 2,1;
+ ten | grouping
+-----+----------
+ | 1
+(1 row)
+
+select ten, grouping(ten) from onek
+group by (ten) having grouping(ten) >= 0
+order by 2,1;
+ ten | grouping
+-----+----------
+ 0 | 0
+ 1 | 0
+ 2 | 0
+ 3 | 0
+ 4 | 0
+ 5 | 0
+ 6 | 0
+ 7 | 0
+ 8 | 0
+ 9 | 0
+(10 rows)
+
+-- FILTER queries
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by rollup(ten);
+ ten | sum
+-----+-----
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+ 5 |
+ 6 |
+ 7 |
+ 8 |
+ 9 |
+ |
+(11 rows)
+
+-- 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;
+ a | a | four | ten | count
+---+---+------+-----+-------
+ 1 | 1 | 0 | 0 | 50
+ 1 | 1 | 0 | 2 | 50
+ 1 | 1 | 0 | 4 | 50
+ 1 | 1 | 0 | 6 | 50
+ 1 | 1 | 0 | 8 | 50
+ 1 | 1 | 0 | | 250
+ 1 | 1 | 1 | 1 | 50
+ 1 | 1 | 1 | 3 | 50
+ 1 | 1 | 1 | 5 | 50
+ 1 | 1 | 1 | 7 | 50
+ 1 | 1 | 1 | 9 | 50
+ 1 | 1 | 1 | | 250
+ 1 | 1 | 2 | 0 | 50
+ 1 | 1 | 2 | 2 | 50
+ 1 | 1 | 2 | 4 | 50
+ 1 | 1 | 2 | 6 | 50
+ 1 | 1 | 2 | 8 | 50
+ 1 | 1 | 2 | | 250
+ 1 | 1 | 3 | 1 | 50
+ 1 | 1 | 3 | 3 | 50
+ 1 | 1 | 3 | 5 | 50
+ 1 | 1 | 3 | 7 | 50
+ 1 | 1 | 3 | 9 | 50
+ 1 | 1 | 3 | | 250
+ 1 | 1 | | 0 | 100
+ 1 | 1 | | 1 | 100
+ 1 | 1 | | 2 | 100
+ 1 | 1 | | 3 | 100
+ 1 | 1 | | 4 | 100
+ 1 | 1 | | 5 | 100
+ 1 | 1 | | 6 | 100
+ 1 | 1 | | 7 | 100
+ 1 | 1 | | 8 | 100
+ 1 | 1 | | 9 | 100
+ 1 | 1 | | | 1000
+ 2 | 2 | 0 | 0 | 50
+ 2 | 2 | 0 | 2 | 50
+ 2 | 2 | 0 | 4 | 50
+ 2 | 2 | 0 | 6 | 50
+ 2 | 2 | 0 | 8 | 50
+ 2 | 2 | 0 | | 250
+ 2 | 2 | 1 | 1 | 50
+ 2 | 2 | 1 | 3 | 50
+ 2 | 2 | 1 | 5 | 50
+ 2 | 2 | 1 | 7 | 50
+ 2 | 2 | 1 | 9 | 50
+ 2 | 2 | 1 | | 250
+ 2 | 2 | 2 | 0 | 50
+ 2 | 2 | 2 | 2 | 50
+ 2 | 2 | 2 | 4 | 50
+ 2 | 2 | 2 | 6 | 50
+ 2 | 2 | 2 | 8 | 50
+ 2 | 2 | 2 | | 250
+ 2 | 2 | 3 | 1 | 50
+ 2 | 2 | 3 | 3 | 50
+ 2 | 2 | 3 | 5 | 50
+ 2 | 2 | 3 | 7 | 50
+ 2 | 2 | 3 | 9 | 50
+ 2 | 2 | 3 | | 250
+ 2 | 2 | | 0 | 100
+ 2 | 2 | | 1 | 100
+ 2 | 2 | | 2 | 100
+ 2 | 2 | | 3 | 100
+ 2 | 2 | | 4 | 100
+ 2 | 2 | | 5 | 100
+ 2 | 2 | | 6 | 100
+ 2 | 2 | | 7 | 100
+ 2 | 2 | | 8 | 100
+ 2 | 2 | | 9 | 100
+ 2 | 2 | | | 1000
+(70 rows)
+
+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);
+ array
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"(1,0,0,250)","(1,0,2,250)","(1,0,,500)","(1,1,1,250)","(1,1,3,250)","(1,1,,500)","(1,,0,250)","(1,,1,250)","(1,,2,250)","(1,,3,250)","(1,,,1000)"}
+ {"(2,0,0,250)","(2,0,2,250)","(2,0,,500)","(2,1,1,250)","(2,1,3,250)","(2,1,,500)","(2,,0,250)","(2,,1,250)","(2,,2,250)","(2,,3,250)","(2,,,1000)"}
+(2 rows)
+
+-- Grouping on text columns
+select sum(ten) from onek group by two, rollup(four::text) order by 1;
+ sum
+------
+ 1000
+ 1000
+ 1250
+ 1250
+ 2000
+ 2500
+(6 rows)
+
+select sum(ten) from onek group by rollup(four::text), two order by 1;
+ sum
+------
+ 1000
+ 1000
+ 1250
+ 1250
+ 2000
+ 2500
+(6 rows)
+
+-- hashing support
+set enable_hashagg = true;
+-- failure cases
+select count(*) from gstest4 group by rollup(unhashable_col,unsortable_col);
+ERROR: could not implement GROUP BY
+DETAIL: Some of the datatypes only support hashing, while others only support sorting.
+select array_agg(v order by v) from gstest4 group by grouping sets ((id,unsortable_col),(id));
+ERROR: could not implement GROUP BY
+DETAIL: Some of the datatypes only support hashing, while others only support sorting.
+-- 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;
+ a | b | grouping | sum | count | max
+---+---+----------+-----+-------+-----
+ 1 | | 1 | 60 | 5 | 14
+ 2 | | 1 | 15 | 1 | 15
+ 3 | | 1 | 33 | 2 | 17
+ 4 | | 1 | 37 | 2 | 19
+ | 1 | 2 | 58 | 4 | 19
+ | 2 | 2 | 25 | 2 | 13
+ | 3 | 2 | 45 | 3 | 16
+ | 4 | 2 | 17 | 1 | 17
+(8 rows)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: (GROUPING("*VALUES*".column1, "*VALUES*".column2)), "*VALUES*".column1, "*VALUES*".column2
+ -> HashAggregate
+ Hash Key: "*VALUES*".column1
+ Hash Key: "*VALUES*".column2
+ -> Values Scan on "*VALUES*"
+(6 rows)
+
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+ from gstest1 group by cube(a,b) order by 3,1,2;
+ a | b | grouping | sum | count | max
+---+---+----------+-----+-------+-----
+ 1 | 1 | 0 | 21 | 2 | 11
+ 1 | 2 | 0 | 25 | 2 | 13
+ 1 | 3 | 0 | 14 | 1 | 14
+ 2 | 3 | 0 | 15 | 1 | 15
+ 3 | 3 | 0 | 16 | 1 | 16
+ 3 | 4 | 0 | 17 | 1 | 17
+ 4 | 1 | 0 | 37 | 2 | 19
+ 1 | | 1 | 60 | 5 | 14
+ 2 | | 1 | 15 | 1 | 15
+ 3 | | 1 | 33 | 2 | 17
+ 4 | | 1 | 37 | 2 | 19
+ | 1 | 2 | 58 | 4 | 19
+ | 2 | 2 | 25 | 2 | 13
+ | 3 | 2 | 45 | 3 | 16
+ | 4 | 2 | 17 | 1 | 17
+ | | 3 | 145 | 10 | 19
+(16 rows)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: (GROUPING("*VALUES*".column1, "*VALUES*".column2)), "*VALUES*".column1, "*VALUES*".column2
+ -> MixedAggregate
+ Hash Key: "*VALUES*".column1, "*VALUES*".column2
+ Hash Key: "*VALUES*".column1
+ Hash Key: "*VALUES*".column2
+ Group Key: ()
+ -> Values Scan on "*VALUES*"
+(8 rows)
+
+-- 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);
+ QUERY PLAN
+----------------------------------------------------------
+ GroupAggregate
+ Group Key: "*VALUES*".column1, "*VALUES*".column2
+ Group Key: "*VALUES*".column1
+ Group Key: ()
+ Sort Key: "*VALUES*".column2
+ Group Key: "*VALUES*".column2
+ -> Sort
+ Sort Key: "*VALUES*".column1, "*VALUES*".column2
+ -> Values Scan on "*VALUES*"
+(9 rows)
+
+-- unsortable cases
+select unsortable_col, count(*)
+ from gstest4 group by grouping sets ((unsortable_col),(unsortable_col))
+ order by unsortable_col::text;
+ unsortable_col | count
+----------------+-------
+ 1 | 4
+ 1 | 4
+ 2 | 4
+ 2 | 4
+(4 rows)
+
+-- 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;
+ unhashable_col | unsortable_col | grouping | count | sum
+----------------+----------------+----------+-------+-----
+ 0000 | | 1 | 2 | 17
+ 0001 | | 1 | 2 | 34
+ 0010 | | 1 | 2 | 68
+ 0011 | | 1 | 2 | 136
+ | 2 | 2 | 4 | 60
+ | 1 | 2 | 4 | 195
+(6 rows)
+
+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;
+ QUERY PLAN
+------------------------------------------------------------------
+ Sort
+ Sort Key: (GROUPING(unhashable_col, unsortable_col)), (sum(v))
+ -> MixedAggregate
+ Hash Key: unsortable_col
+ Group Key: unhashable_col
+ -> Sort
+ Sort Key: unhashable_col
+ -> Seq Scan on gstest4
+(8 rows)
+
+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;
+ unhashable_col | unsortable_col | grouping | count | sum
+----------------+----------------+----------+-------+-----
+ 0000 | | 1 | 1 | 1
+ 0001 | | 1 | 1 | 2
+ 0010 | | 1 | 1 | 4
+ 0011 | | 1 | 1 | 8
+ 0000 | | 1 | 1 | 16
+ 0001 | | 1 | 1 | 32
+ 0010 | | 1 | 1 | 64
+ 0011 | | 1 | 1 | 128
+ | 1 | 2 | 1 | 1
+ | 1 | 2 | 1 | 2
+ | 2 | 2 | 1 | 4
+ | 2 | 2 | 1 | 8
+ | 2 | 2 | 1 | 16
+ | 2 | 2 | 1 | 32
+ | 1 | 2 | 1 | 64
+ | 1 | 2 | 1 | 128
+(16 rows)
+
+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;
+ QUERY PLAN
+------------------------------------------------------------------
+ Sort
+ Sort Key: (GROUPING(unhashable_col, unsortable_col)), (sum(v))
+ -> MixedAggregate
+ Hash Key: v, unsortable_col
+ Group Key: v, unhashable_col
+ -> Sort
+ Sort Key: v, unhashable_col
+ -> Seq Scan on gstest4
+(8 rows)
+
+-- 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);
+ a | b | sum | count
+---+---+-----+-------
+(0 rows)
+
+explain (costs off)
+ select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),a);
+ QUERY PLAN
+--------------------------------
+ HashAggregate
+ Hash Key: a, b
+ Hash Key: a
+ -> Seq Scan on gstest_empty
+(4 rows)
+
+select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),());
+ a | b | sum | count
+---+---+-----+-------
+ | | | 0
+(1 row)
+
+select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),());
+ a | b | sum | count
+---+---+-----+-------
+ | | | 0
+ | | | 0
+ | | | 0
+(3 rows)
+
+explain (costs off)
+ select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),());
+ QUERY PLAN
+--------------------------------
+ MixedAggregate
+ Hash Key: a, b
+ Group Key: ()
+ Group Key: ()
+ Group Key: ()
+ -> Seq Scan on gstest_empty
+(6 rows)
+
+select sum(v), count(*) from gstest_empty group by grouping sets ((),(),());
+ sum | count
+-----+-------
+ | 0
+ | 0
+ | 0
+(3 rows)
+
+explain (costs off)
+ select sum(v), count(*) from gstest_empty group by grouping sets ((),(),());
+ QUERY PLAN
+--------------------------------
+ Aggregate
+ Group Key: ()
+ Group Key: ()
+ Group Key: ()
+ -> Seq Scan on gstest_empty
+(5 rows)
+
+-- 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));
+ a | d | grouping
+---+---+----------
+ 1 | 1 | 1
+ 2 | 2 | 1
+ 1 | 1 | 2
+ 2 | 2 | 2
+(4 rows)
+
+explain (costs off)
+ select a, d, grouping(a,b,c)
+ from gstest3
+ group by grouping sets ((a,b), (a,c));
+ QUERY PLAN
+---------------------------
+ HashAggregate
+ Hash Key: a, b
+ Hash Key: a, c
+ -> Seq Scan on gstest3
+(4 rows)
+
+-- 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;
+ a | b | sum
+---+---+-----
+ 1 | | 3
+ 2 | | 6
+ | 1 | 3
+ | 2 | 3
+ | 3 | 3
+(5 rows)
+
+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;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Sort
+ Sort Key: (sum("*VALUES*".column1)), gstest_data.a, gstest_data.b
+ -> HashAggregate
+ Hash Key: gstest_data.a
+ Hash Key: gstest_data.b
+ -> Nested Loop
+ -> Values Scan on "*VALUES*"
+ -> Function Scan on gstest_data
+(8 rows)
+
+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;
+ERROR: aggregate functions are not allowed in FROM clause of their own query level
+LINE 3: lateral (select a, b, sum(v.x) from gstest_data(v.x) ...
+ ^
+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;
+ERROR: aggregate functions are not allowed in FROM clause of their own query level
+LINE 4: lateral (select a, b, sum(v.x) from gstest_data(v.x...
+ ^
+-- 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;
+ a | b | grouping | sum | count | max
+---+---+----------+-----+-------+-----
+ 1 | 1 | 0 | 21 | 2 | 11
+ 1 | 2 | 0 | 25 | 2 | 13
+ 1 | 3 | 0 | 14 | 1 | 14
+ 2 | 3 | 0 | 15 | 1 | 15
+ 3 | 3 | 0 | 16 | 1 | 16
+ 3 | 4 | 0 | 17 | 1 | 17
+ 4 | 1 | 0 | 37 | 2 | 19
+ | | 3 | 21 | 2 | 11
+ | | 3 | 21 | 2 | 11
+ | | 3 | 25 | 2 | 13
+ | | 3 | 25 | 2 | 13
+ | | 3 | 14 | 1 | 14
+ | | 3 | 14 | 1 | 14
+ | | 3 | 15 | 1 | 15
+ | | 3 | 15 | 1 | 15
+ | | 3 | 16 | 1 | 16
+ | | 3 | 16 | 1 | 16
+ | | 3 | 17 | 1 | 17
+ | | 3 | 17 | 1 | 17
+ | | 3 | 37 | 2 | 19
+ | | 3 | 37 | 2 | 19
+(21 rows)
+
+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;
+ QUERY PLAN
+-------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: (GROUPING("*VALUES*".column1, "*VALUES*".column2)), (max("*VALUES*".column3))
+ -> HashAggregate
+ Hash Key: "*VALUES*".column1, "*VALUES*".column2
+ Hash Key: ("*VALUES*".column1 + 1), ("*VALUES*".column2 + 1)
+ Hash Key: ("*VALUES*".column1 + 2), ("*VALUES*".column2 + 2)
+ -> Values Scan on "*VALUES*"
+(7 rows)
+
+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;
+ a | b | sum | rsum
+---+---+-----+------
+ 1 | 1 | 8 | 8
+ 1 | 2 | 2 | 10
+ 1 | | 10 | 20
+ 2 | 2 | 2 | 22
+ 2 | | 2 | 24
+ | 1 | 8 | 32
+ | 2 | 4 | 36
+ | | 12 | 48
+(8 rows)
+
+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;
+ QUERY PLAN
+---------------------------------------------
+ Sort
+ Sort Key: (sum((sum(c))) OVER (?)), a, b
+ -> WindowAgg
+ -> Sort
+ Sort Key: a, b
+ -> MixedAggregate
+ Hash Key: a, b
+ Hash Key: a
+ Hash Key: b
+ Group Key: ()
+ -> Seq Scan on gstest2
+(11 rows)
+
+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;
+ a | b | sum
+---+---+-----
+ 1 | 1 | 1
+ 1 | 2 | 1
+ 1 | 3 | 1
+ 1 | | 3
+ 2 | 1 | 2
+ 2 | 2 | 2
+ 2 | 3 | 2
+ 2 | | 6
+ | 1 | 3
+ | 2 | 3
+ | 3 | 3
+ | | 9
+(12 rows)
+
+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;
+ QUERY PLAN
+------------------------------------------------
+ Sort
+ Sort Key: gstest_data.a, gstest_data.b
+ -> MixedAggregate
+ Hash Key: gstest_data.a, gstest_data.b
+ Hash Key: gstest_data.a
+ Hash Key: gstest_data.b
+ Group Key: ()
+ -> Nested Loop
+ -> Values Scan on "*VALUES*"
+ -> Function Scan on gstest_data
+(10 rows)
+
+-- 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;
+ QUERY PLAN
+---------------------------------------
+ Sort
+ Sort Key: a, b
+ -> GroupAggregate
+ Group Key: a
+ Group Key: ()
+ Sort Key: b
+ Group Key: b
+ -> Sort
+ Sort Key: a
+ -> Seq Scan on gstest3
+(10 rows)
+
+SELECT a, b, count(*), max(a), max(b) FROM gstest3 GROUP BY GROUPING SETS(a, b,()) ORDER BY a, b;
+ a | b | count | max | max
+---+---+-------+-----+-----
+ 1 | | 1 | 1 | 1
+ 2 | | 1 | 2 | 2
+ | 1 | 1 | 1 | 1
+ | 2 | 1 | 2 | 2
+ | | 2 | 2 | 2
+(5 rows)
+
+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;
+ QUERY PLAN
+------------------------------------------------------
+ Sort
+ Sort Key: a, b
+ -> GroupAggregate
+ Group Key: a
+ Group Key: ()
+ Sort Key: b
+ Group Key: b
+ -> Index Scan using gstest3_pkey on gstest3
+(8 rows)
+
+SELECT a, b, count(*), max(a), max(b) FROM gstest3 GROUP BY GROUPING SETS(a, b,()) ORDER BY a, b;
+ a | b | count | max | max
+---+---+-------+-----+-----
+ 1 | | 1 | 1 | 1
+ 2 | | 1 | 2 | 2
+ | 1 | 1 | 1 | 1
+ | 2 | 1 | 2 | 2
+ | | 2 | 2 | 2
+(5 rows)
+
+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;
+ a | a | four | ten | count
+---+---+------+-----+-------
+ 1 | 1 | 0 | 0 | 50
+ 1 | 1 | 0 | 2 | 50
+ 1 | 1 | 0 | 4 | 50
+ 1 | 1 | 0 | 6 | 50
+ 1 | 1 | 0 | 8 | 50
+ 1 | 1 | 0 | | 250
+ 1 | 1 | 1 | 1 | 50
+ 1 | 1 | 1 | 3 | 50
+ 1 | 1 | 1 | 5 | 50
+ 1 | 1 | 1 | 7 | 50
+ 1 | 1 | 1 | 9 | 50
+ 1 | 1 | 1 | | 250
+ 1 | 1 | 2 | 0 | 50
+ 1 | 1 | 2 | 2 | 50
+ 1 | 1 | 2 | 4 | 50
+ 1 | 1 | 2 | 6 | 50
+ 1 | 1 | 2 | 8 | 50
+ 1 | 1 | 2 | | 250
+ 1 | 1 | 3 | 1 | 50
+ 1 | 1 | 3 | 3 | 50
+ 1 | 1 | 3 | 5 | 50
+ 1 | 1 | 3 | 7 | 50
+ 1 | 1 | 3 | 9 | 50
+ 1 | 1 | 3 | | 250
+ 1 | 1 | | 0 | 100
+ 1 | 1 | | 1 | 100
+ 1 | 1 | | 2 | 100
+ 1 | 1 | | 3 | 100
+ 1 | 1 | | 4 | 100
+ 1 | 1 | | 5 | 100
+ 1 | 1 | | 6 | 100
+ 1 | 1 | | 7 | 100
+ 1 | 1 | | 8 | 100
+ 1 | 1 | | 9 | 100
+ 1 | 1 | | | 1000
+ 2 | 2 | 0 | 0 | 50
+ 2 | 2 | 0 | 2 | 50
+ 2 | 2 | 0 | 4 | 50
+ 2 | 2 | 0 | 6 | 50
+ 2 | 2 | 0 | 8 | 50
+ 2 | 2 | 0 | | 250
+ 2 | 2 | 1 | 1 | 50
+ 2 | 2 | 1 | 3 | 50
+ 2 | 2 | 1 | 5 | 50
+ 2 | 2 | 1 | 7 | 50
+ 2 | 2 | 1 | 9 | 50
+ 2 | 2 | 1 | | 250
+ 2 | 2 | 2 | 0 | 50
+ 2 | 2 | 2 | 2 | 50
+ 2 | 2 | 2 | 4 | 50
+ 2 | 2 | 2 | 6 | 50
+ 2 | 2 | 2 | 8 | 50
+ 2 | 2 | 2 | | 250
+ 2 | 2 | 3 | 1 | 50
+ 2 | 2 | 3 | 3 | 50
+ 2 | 2 | 3 | 5 | 50
+ 2 | 2 | 3 | 7 | 50
+ 2 | 2 | 3 | 9 | 50
+ 2 | 2 | 3 | | 250
+ 2 | 2 | | 0 | 100
+ 2 | 2 | | 1 | 100
+ 2 | 2 | | 2 | 100
+ 2 | 2 | | 3 | 100
+ 2 | 2 | | 4 | 100
+ 2 | 2 | | 5 | 100
+ 2 | 2 | | 6 | 100
+ 2 | 2 | | 7 | 100
+ 2 | 2 | | 8 | 100
+ 2 | 2 | | 9 | 100
+ 2 | 2 | | | 1000
+(70 rows)
+
+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);
+ array
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"(1,0,0,250)","(1,0,2,250)","(1,0,,500)","(1,1,1,250)","(1,1,3,250)","(1,1,,500)","(1,,0,250)","(1,,1,250)","(1,,2,250)","(1,,3,250)","(1,,,1000)"}
+ {"(2,0,0,250)","(2,0,2,250)","(2,0,,500)","(2,1,1,250)","(2,1,3,250)","(2,1,,500)","(2,,0,250)","(2,,1,250)","(2,,2,250)","(2,,3,250)","(2,,,1000)"}
+(2 rows)
+
+-- 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;
+ a | a | four | ten | count
+---+---+------+-----+-------
+ 1 | 1 | 0 | | 250
+ 1 | 1 | 1 | | 250
+ 1 | 1 | 2 | | 250
+ 1 | 1 | 3 | | 250
+ 1 | 1 | | 0 | 100
+ 1 | 1 | | 1 | 100
+ 1 | 1 | | 2 | 100
+ 1 | 1 | | 3 | 100
+ 1 | 1 | | 4 | 100
+ 1 | 1 | | 5 | 100
+ 1 | 1 | | 6 | 100
+ 1 | 1 | | 7 | 100
+ 1 | 1 | | 8 | 100
+ 1 | 1 | | 9 | 100
+ 2 | 2 | 0 | | 250
+ 2 | 2 | 1 | | 250
+ 2 | 2 | 2 | | 250
+ 2 | 2 | 3 | | 250
+ 2 | 2 | | 0 | 100
+ 2 | 2 | | 1 | 100
+ 2 | 2 | | 2 | 100
+ 2 | 2 | | 3 | 100
+ 2 | 2 | | 4 | 100
+ 2 | 2 | | 5 | 100
+ 2 | 2 | | 6 | 100
+ 2 | 2 | | 7 | 100
+ 2 | 2 | | 8 | 100
+ 2 | 2 | | 9 | 100
+(28 rows)
+
+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);
+ array
+---------------------------------------------------------------------------------
+ {"(1,0,,500)","(1,1,,500)","(1,,0,250)","(1,,1,250)","(1,,2,250)","(1,,3,250)"}
+ {"(2,0,,500)","(2,1,,500)","(2,,0,250)","(2,,1,250)","(2,,2,250)","(2,,3,250)"}
+(2 rows)
+
+-- test the knapsack
+set enable_indexscan = false;
+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);
+ QUERY PLAN
+-------------------------------
+ MixedAggregate
+ Hash Key: two
+ Hash Key: four
+ Hash Key: ten
+ Hash Key: hundred
+ Group Key: unique1
+ Sort Key: twothousand
+ Group Key: twothousand
+ Sort Key: thousand
+ Group Key: thousand
+ -> Sort
+ Sort Key: unique1
+ -> Seq Scan on tenk1
+(13 rows)
+
+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);
+ QUERY PLAN
+-------------------------------
+ MixedAggregate
+ Hash Key: two
+ Hash Key: four
+ Hash Key: ten
+ Hash Key: hundred
+ Group Key: unique1
+ -> Sort
+ Sort Key: unique1
+ -> Seq Scan on tenk1
+(9 rows)
+
+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);
+ QUERY PLAN
+-------------------------------
+ MixedAggregate
+ Hash Key: two
+ Hash Key: four
+ Hash Key: ten
+ Hash Key: hundred
+ Hash Key: thousand
+ Group Key: unique1
+ Sort Key: twothousand
+ Group Key: twothousand
+ -> Sort
+ Sort Key: unique1
+ -> Seq Scan on tenk1
+(12 rows)
+
+-- 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;
+ ?column? | case | count
+----------+------+-------
+ aa | 0 | 1
+ ba | 0 | 1
+ | 1 | 2
+ | 1 | 2
+(4 rows)
+
+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;
+ ?column? | case | count
+----------+------+-------
+ aa | 0 | 1
+ ba | 0 | 1
+ | 1 | 2
+ | 1 | 2
+(4 rows)
+
+-- Bug #16784
+create table bug_16784(i int, j int);
+analyze bug_16784;
+alter table bug_16784 set (autovacuum_enabled = 'false');
+update pg_class set reltuples = 10 where relname='bug_16784';
+insert into bug_16784 select g/10, g from generate_series(1,40) g;
+set work_mem='64kB';
+set enable_sort = false;
+select * from
+ (values (1),(2)) v(a),
+ lateral (select a, i, j, count(*) from
+ bug_16784 group by cube(i,j)) s
+ order by v.a, i, j;
+ a | a | i | j | count
+---+---+---+----+-------
+ 1 | 1 | 0 | 1 | 1
+ 1 | 1 | 0 | 2 | 1
+ 1 | 1 | 0 | 3 | 1
+ 1 | 1 | 0 | 4 | 1
+ 1 | 1 | 0 | 5 | 1
+ 1 | 1 | 0 | 6 | 1
+ 1 | 1 | 0 | 7 | 1
+ 1 | 1 | 0 | 8 | 1
+ 1 | 1 | 0 | 9 | 1
+ 1 | 1 | 0 | | 9
+ 1 | 1 | 1 | 10 | 1
+ 1 | 1 | 1 | 11 | 1
+ 1 | 1 | 1 | 12 | 1
+ 1 | 1 | 1 | 13 | 1
+ 1 | 1 | 1 | 14 | 1
+ 1 | 1 | 1 | 15 | 1
+ 1 | 1 | 1 | 16 | 1
+ 1 | 1 | 1 | 17 | 1
+ 1 | 1 | 1 | 18 | 1
+ 1 | 1 | 1 | 19 | 1
+ 1 | 1 | 1 | | 10
+ 1 | 1 | 2 | 20 | 1
+ 1 | 1 | 2 | 21 | 1
+ 1 | 1 | 2 | 22 | 1
+ 1 | 1 | 2 | 23 | 1
+ 1 | 1 | 2 | 24 | 1
+ 1 | 1 | 2 | 25 | 1
+ 1 | 1 | 2 | 26 | 1
+ 1 | 1 | 2 | 27 | 1
+ 1 | 1 | 2 | 28 | 1
+ 1 | 1 | 2 | 29 | 1
+ 1 | 1 | 2 | | 10
+ 1 | 1 | 3 | 30 | 1
+ 1 | 1 | 3 | 31 | 1
+ 1 | 1 | 3 | 32 | 1
+ 1 | 1 | 3 | 33 | 1
+ 1 | 1 | 3 | 34 | 1
+ 1 | 1 | 3 | 35 | 1
+ 1 | 1 | 3 | 36 | 1
+ 1 | 1 | 3 | 37 | 1
+ 1 | 1 | 3 | 38 | 1
+ 1 | 1 | 3 | 39 | 1
+ 1 | 1 | 3 | | 10
+ 1 | 1 | 4 | 40 | 1
+ 1 | 1 | 4 | | 1
+ 1 | 1 | | 1 | 1
+ 1 | 1 | | 2 | 1
+ 1 | 1 | | 3 | 1
+ 1 | 1 | | 4 | 1
+ 1 | 1 | | 5 | 1
+ 1 | 1 | | 6 | 1
+ 1 | 1 | | 7 | 1
+ 1 | 1 | | 8 | 1
+ 1 | 1 | | 9 | 1
+ 1 | 1 | | 10 | 1
+ 1 | 1 | | 11 | 1
+ 1 | 1 | | 12 | 1
+ 1 | 1 | | 13 | 1
+ 1 | 1 | | 14 | 1
+ 1 | 1 | | 15 | 1
+ 1 | 1 | | 16 | 1
+ 1 | 1 | | 17 | 1
+ 1 | 1 | | 18 | 1
+ 1 | 1 | | 19 | 1
+ 1 | 1 | | 20 | 1
+ 1 | 1 | | 21 | 1
+ 1 | 1 | | 22 | 1
+ 1 | 1 | | 23 | 1
+ 1 | 1 | | 24 | 1
+ 1 | 1 | | 25 | 1
+ 1 | 1 | | 26 | 1
+ 1 | 1 | | 27 | 1
+ 1 | 1 | | 28 | 1
+ 1 | 1 | | 29 | 1
+ 1 | 1 | | 30 | 1
+ 1 | 1 | | 31 | 1
+ 1 | 1 | | 32 | 1
+ 1 | 1 | | 33 | 1
+ 1 | 1 | | 34 | 1
+ 1 | 1 | | 35 | 1
+ 1 | 1 | | 36 | 1
+ 1 | 1 | | 37 | 1
+ 1 | 1 | | 38 | 1
+ 1 | 1 | | 39 | 1
+ 1 | 1 | | 40 | 1
+ 1 | 1 | | | 40
+ 2 | 2 | 0 | 1 | 1
+ 2 | 2 | 0 | 2 | 1
+ 2 | 2 | 0 | 3 | 1
+ 2 | 2 | 0 | 4 | 1
+ 2 | 2 | 0 | 5 | 1
+ 2 | 2 | 0 | 6 | 1
+ 2 | 2 | 0 | 7 | 1
+ 2 | 2 | 0 | 8 | 1
+ 2 | 2 | 0 | 9 | 1
+ 2 | 2 | 0 | | 9
+ 2 | 2 | 1 | 10 | 1
+ 2 | 2 | 1 | 11 | 1
+ 2 | 2 | 1 | 12 | 1
+ 2 | 2 | 1 | 13 | 1
+ 2 | 2 | 1 | 14 | 1
+ 2 | 2 | 1 | 15 | 1
+ 2 | 2 | 1 | 16 | 1
+ 2 | 2 | 1 | 17 | 1
+ 2 | 2 | 1 | 18 | 1
+ 2 | 2 | 1 | 19 | 1
+ 2 | 2 | 1 | | 10
+ 2 | 2 | 2 | 20 | 1
+ 2 | 2 | 2 | 21 | 1
+ 2 | 2 | 2 | 22 | 1
+ 2 | 2 | 2 | 23 | 1
+ 2 | 2 | 2 | 24 | 1
+ 2 | 2 | 2 | 25 | 1
+ 2 | 2 | 2 | 26 | 1
+ 2 | 2 | 2 | 27 | 1
+ 2 | 2 | 2 | 28 | 1
+ 2 | 2 | 2 | 29 | 1
+ 2 | 2 | 2 | | 10
+ 2 | 2 | 3 | 30 | 1
+ 2 | 2 | 3 | 31 | 1
+ 2 | 2 | 3 | 32 | 1
+ 2 | 2 | 3 | 33 | 1
+ 2 | 2 | 3 | 34 | 1
+ 2 | 2 | 3 | 35 | 1
+ 2 | 2 | 3 | 36 | 1
+ 2 | 2 | 3 | 37 | 1
+ 2 | 2 | 3 | 38 | 1
+ 2 | 2 | 3 | 39 | 1
+ 2 | 2 | 3 | | 10
+ 2 | 2 | 4 | 40 | 1
+ 2 | 2 | 4 | | 1
+ 2 | 2 | | 1 | 1
+ 2 | 2 | | 2 | 1
+ 2 | 2 | | 3 | 1
+ 2 | 2 | | 4 | 1
+ 2 | 2 | | 5 | 1
+ 2 | 2 | | 6 | 1
+ 2 | 2 | | 7 | 1
+ 2 | 2 | | 8 | 1
+ 2 | 2 | | 9 | 1
+ 2 | 2 | | 10 | 1
+ 2 | 2 | | 11 | 1
+ 2 | 2 | | 12 | 1
+ 2 | 2 | | 13 | 1
+ 2 | 2 | | 14 | 1
+ 2 | 2 | | 15 | 1
+ 2 | 2 | | 16 | 1
+ 2 | 2 | | 17 | 1
+ 2 | 2 | | 18 | 1
+ 2 | 2 | | 19 | 1
+ 2 | 2 | | 20 | 1
+ 2 | 2 | | 21 | 1
+ 2 | 2 | | 22 | 1
+ 2 | 2 | | 23 | 1
+ 2 | 2 | | 24 | 1
+ 2 | 2 | | 25 | 1
+ 2 | 2 | | 26 | 1
+ 2 | 2 | | 27 | 1
+ 2 | 2 | | 28 | 1
+ 2 | 2 | | 29 | 1
+ 2 | 2 | | 30 | 1
+ 2 | 2 | | 31 | 1
+ 2 | 2 | | 32 | 1
+ 2 | 2 | | 33 | 1
+ 2 | 2 | | 34 | 1
+ 2 | 2 | | 35 | 1
+ 2 | 2 | | 36 | 1
+ 2 | 2 | | 37 | 1
+ 2 | 2 | | 38 | 1
+ 2 | 2 | | 39 | 1
+ 2 | 2 | | 40 | 1
+ 2 | 2 | | | 40
+(172 rows)
+
+--
+-- Compare results between plans using sorting and plans using hash
+-- aggregation. Force spilling in both cases by setting work_mem low
+-- and altering the statistics.
+--
+create table gs_data_1 as
+select g%1000 as g1000, g%100 as g100, g%10 as g10, g
+ from generate_series(0,1999) g;
+analyze gs_data_1;
+alter table gs_data_1 set (autovacuum_enabled = 'false');
+update pg_class set reltuples = 10 where relname='gs_data_1';
+set work_mem='64kB';
+-- Produce results with sorting.
+set enable_sort = true;
+set enable_hashagg = false;
+set jit_above_cost = 0;
+explain (costs off)
+select g100, g10, sum(g::numeric), count(*), max(g::text)
+from gs_data_1 group by cube (g1000, g100,g10);
+ QUERY PLAN
+------------------------------------
+ GroupAggregate
+ Group Key: g1000, g100, g10
+ Group Key: g1000, g100
+ Group Key: g1000
+ Group Key: ()
+ Sort Key: g100, g10
+ Group Key: g100, g10
+ Group Key: g100
+ Sort Key: g10, g1000
+ Group Key: g10, g1000
+ Group Key: g10
+ -> Sort
+ Sort Key: g1000, g100, g10
+ -> Seq Scan on gs_data_1
+(14 rows)
+
+create table gs_group_1 as
+select g100, g10, sum(g::numeric), count(*), max(g::text)
+from gs_data_1 group by cube (g1000, g100,g10);
+-- Produce results with hash aggregation.
+set enable_hashagg = true;
+set enable_sort = false;
+explain (costs off)
+select g100, g10, sum(g::numeric), count(*), max(g::text)
+from gs_data_1 group by cube (g1000, g100,g10);
+ QUERY PLAN
+------------------------------
+ MixedAggregate
+ Hash Key: g1000, g100, g10
+ Hash Key: g1000, g100
+ Hash Key: g1000
+ Hash Key: g100, g10
+ Hash Key: g100
+ Hash Key: g10, g1000
+ Hash Key: g10
+ Group Key: ()
+ -> Seq Scan on gs_data_1
+(10 rows)
+
+create table gs_hash_1 as
+select g100, g10, sum(g::numeric), count(*), max(g::text)
+from gs_data_1 group by cube (g1000, g100,g10);
+set enable_sort = true;
+set work_mem to default;
+-- Compare results
+(select * from gs_hash_1 except select * from gs_group_1)
+ union all
+(select * from gs_group_1 except select * from gs_hash_1);
+ g100 | g10 | sum | count | max
+------+-----+-----+-------+-----
+(0 rows)
+
+drop table gs_group_1;
+drop table gs_hash_1;
+-- GROUP BY DISTINCT
+-- "normal" behavior...
+select a, b, c
+from (values (1, 2, 3), (4, null, 6), (7, 8, 9)) as t (a, b, c)
+group by all 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)
+
+-- ...which is also the default
+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)
+
+-- "group by distinct" behavior...
+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)
+
+-- ...which is not the same as "select distinct"
+select distinct 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 | | 3
+ 1 | |
+ 4 | | 6
+ 4 | |
+ 7 | 8 | 9
+ 7 | 8 |
+ 7 | | 9
+ 7 | |
+ | |
+(11 rows)
+
+-- test handling of outer GroupingFunc within subqueries
+explain (costs off)
+select (select grouping(v1)) from (values ((select 1))) v(v1) group by cube(v1);
+ QUERY PLAN
+---------------------------
+ MixedAggregate
+ Hash Key: $2
+ Group Key: ()
+ InitPlan 1 (returns $1)
+ -> Result
+ InitPlan 3 (returns $2)
+ -> Result
+ -> Result
+ SubPlan 2
+ -> Result
+(10 rows)
+
+select (select grouping(v1)) from (values ((select 1))) v(v1) group by cube(v1);
+ grouping
+----------
+ 1
+ 0
+(2 rows)
+
+explain (costs off)
+select (select grouping(v1)) from (values ((select 1))) v(v1) group by v1;
+ QUERY PLAN
+---------------------------
+ GroupAggregate
+ Group Key: $2
+ InitPlan 1 (returns $1)
+ -> Result
+ InitPlan 3 (returns $2)
+ -> Result
+ -> Result
+ SubPlan 2
+ -> Result
+(9 rows)
+
+select (select grouping(v1)) from (values ((select 1))) v(v1) group by v1;
+ grouping
+----------
+ 0
+(1 row)
+
+-- end
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
new file mode 100644
index 0000000..59da91f
--- /dev/null
+++ b/src/test/regress/expected/guc.out
@@ -0,0 +1,815 @@
+-- pg_regress should ensure that this default value applies; however
+-- we can't rely on any specific default value of vacuum_cost_delay
+SHOW datestyle;
+ DateStyle
+---------------
+ Postgres, MDY
+(1 row)
+
+-- SET to some nondefault value
+SET vacuum_cost_delay TO 40;
+SET datestyle = 'ISO, YMD';
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 40ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+-- SET LOCAL has no effect outside of a transaction
+SET LOCAL vacuum_cost_delay TO 50;
+WARNING: SET LOCAL can only be used in transaction blocks
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 40ms
+(1 row)
+
+SET LOCAL datestyle = 'SQL';
+WARNING: SET LOCAL can only be used in transaction blocks
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+-- SET LOCAL within a transaction that commits
+BEGIN;
+SET LOCAL vacuum_cost_delay TO 50;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 50ms
+(1 row)
+
+SET LOCAL datestyle = 'SQL';
+SHOW datestyle;
+ DateStyle
+-----------
+ SQL, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+-------------------------
+ 08/13/2006 12:34:56 PDT
+(1 row)
+
+COMMIT;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 40ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+-- SET should be reverted after ROLLBACK
+BEGIN;
+SET vacuum_cost_delay TO 60;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 60ms
+(1 row)
+
+SET datestyle = 'German';
+SHOW datestyle;
+ DateStyle
+-------------
+ German, DMY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+-------------------------
+ 13.08.2006 12:34:56 PDT
+(1 row)
+
+ROLLBACK;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 40ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+-- Some tests with subtransactions
+BEGIN;
+SET vacuum_cost_delay TO 70;
+SET datestyle = 'MDY';
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, MDY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+SAVEPOINT first_sp;
+SET vacuum_cost_delay TO 80.1;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 80100us
+(1 row)
+
+SET datestyle = 'German, DMY';
+SHOW datestyle;
+ DateStyle
+-------------
+ German, DMY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+-------------------------
+ 13.08.2006 12:34:56 PDT
+(1 row)
+
+ROLLBACK TO first_sp;
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, MDY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+SAVEPOINT second_sp;
+SET vacuum_cost_delay TO '900us';
+SET datestyle = 'SQL, YMD';
+SHOW datestyle;
+ DateStyle
+-----------
+ SQL, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+-------------------------
+ 08/13/2006 12:34:56 PDT
+(1 row)
+
+SAVEPOINT third_sp;
+SET vacuum_cost_delay TO 100;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 100ms
+(1 row)
+
+SET datestyle = 'Postgres, MDY';
+SHOW datestyle;
+ DateStyle
+---------------
+ Postgres, MDY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Aug 13 12:34:56 2006 PDT
+(1 row)
+
+ROLLBACK TO third_sp;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 900us
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+-----------
+ SQL, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+-------------------------
+ 08/13/2006 12:34:56 PDT
+(1 row)
+
+ROLLBACK TO second_sp;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 70ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, MDY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+ROLLBACK;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 40ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+-- SET LOCAL with Savepoints
+BEGIN;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 40ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+SAVEPOINT sp;
+SET LOCAL vacuum_cost_delay TO 30;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 30ms
+(1 row)
+
+SET LOCAL datestyle = 'Postgres, MDY';
+SHOW datestyle;
+ DateStyle
+---------------
+ Postgres, MDY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Aug 13 12:34:56 2006 PDT
+(1 row)
+
+ROLLBACK TO sp;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 40ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+ROLLBACK;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 40ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+-- SET LOCAL persists through RELEASE (which was not true in 8.0-8.2)
+BEGIN;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 40ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+SAVEPOINT sp;
+SET LOCAL vacuum_cost_delay TO 30;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 30ms
+(1 row)
+
+SET LOCAL datestyle = 'Postgres, MDY';
+SHOW datestyle;
+ DateStyle
+---------------
+ Postgres, MDY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Aug 13 12:34:56 2006 PDT
+(1 row)
+
+RELEASE SAVEPOINT sp;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 30ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+---------------
+ Postgres, MDY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Aug 13 12:34:56 2006 PDT
+(1 row)
+
+ROLLBACK;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 40ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+-- SET followed by SET LOCAL
+BEGIN;
+SET vacuum_cost_delay TO 40;
+SET LOCAL vacuum_cost_delay TO 50;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 50ms
+(1 row)
+
+SET datestyle = 'ISO, DMY';
+SET LOCAL datestyle = 'Postgres, MDY';
+SHOW datestyle;
+ DateStyle
+---------------
+ Postgres, MDY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Aug 13 12:34:56 2006 PDT
+(1 row)
+
+COMMIT;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 40ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, DMY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+--
+-- Test RESET. We use datestyle because the reset value is forced by
+-- pg_regress, so it doesn't depend on the installation's configuration.
+--
+SET datestyle = iso, ymd;
+SHOW datestyle;
+ DateStyle
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+RESET datestyle;
+SHOW datestyle;
+ DateStyle
+---------------
+ Postgres, MDY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Aug 13 12:34:56 2006 PDT
+(1 row)
+
+-- Test some simple error cases
+SET seq_page_cost TO 'NaN';
+ERROR: invalid value for parameter "seq_page_cost": "NaN"
+SET vacuum_cost_delay TO '10s';
+ERROR: 10000 ms is outside the valid range for parameter "vacuum_cost_delay" (0 .. 100)
+SET no_such_variable TO 42;
+ERROR: unrecognized configuration parameter "no_such_variable"
+-- Test "custom" GUCs created on the fly (which aren't really an
+-- intended feature, but many people use them).
+SHOW custom.my_guc; -- error, not known yet
+ERROR: unrecognized configuration parameter "custom.my_guc"
+SET custom.my_guc = 42;
+SHOW custom.my_guc;
+ custom.my_guc
+---------------
+ 42
+(1 row)
+
+RESET custom.my_guc; -- this makes it go to empty, not become unknown again
+SHOW custom.my_guc;
+ custom.my_guc
+---------------
+
+(1 row)
+
+SET custom.my.qualified.guc = 'foo';
+SHOW custom.my.qualified.guc;
+ custom.my.qualified.guc
+-------------------------
+ foo
+(1 row)
+
+SET custom."bad-guc" = 42; -- disallowed because -c cannot set this name
+ERROR: invalid configuration parameter name "custom.bad-guc"
+DETAIL: Custom parameter names must be two or more simple identifiers separated by dots.
+SHOW custom."bad-guc";
+ERROR: unrecognized configuration parameter "custom.bad-guc"
+SET special."weird name" = 'foo'; -- could be allowed, but we choose not to
+ERROR: invalid configuration parameter name "special.weird name"
+DETAIL: Custom parameter names must be two or more simple identifiers separated by dots.
+SHOW special."weird name";
+ERROR: unrecognized configuration parameter "special.weird name"
+--
+-- Test DISCARD TEMP
+--
+CREATE TEMP TABLE reset_test ( data text ) ON COMMIT DELETE ROWS;
+SELECT relname FROM pg_class WHERE relname = 'reset_test';
+ relname
+------------
+ reset_test
+(1 row)
+
+DISCARD TEMP;
+SELECT relname FROM pg_class WHERE relname = 'reset_test';
+ relname
+---------
+(0 rows)
+
+--
+-- Test DISCARD ALL
+--
+-- do changes
+DECLARE foo CURSOR WITH HOLD FOR SELECT 1;
+PREPARE foo AS SELECT 1;
+LISTEN foo_event;
+SET vacuum_cost_delay = 13;
+CREATE TEMP TABLE tmp_foo (data text) ON COMMIT DELETE ROWS;
+CREATE ROLE regress_guc_user;
+SET SESSION AUTHORIZATION regress_guc_user;
+-- look changes
+SELECT pg_listening_channels();
+ pg_listening_channels
+-----------------------
+ foo_event
+(1 row)
+
+SELECT name FROM pg_prepared_statements;
+ name
+------
+ foo
+(1 row)
+
+SELECT name FROM pg_cursors;
+ name
+------
+ foo
+(1 row)
+
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 13ms
+(1 row)
+
+SELECT relname from pg_class where relname = 'tmp_foo';
+ relname
+---------
+ tmp_foo
+(1 row)
+
+SELECT current_user = 'regress_guc_user';
+ ?column?
+----------
+ t
+(1 row)
+
+-- discard everything
+DISCARD ALL;
+-- look again
+SELECT pg_listening_channels();
+ pg_listening_channels
+-----------------------
+(0 rows)
+
+SELECT name FROM pg_prepared_statements;
+ name
+------
+(0 rows)
+
+SELECT name FROM pg_cursors;
+ name
+------
+(0 rows)
+
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay
+-------------------
+ 0
+(1 row)
+
+SELECT relname from pg_class where relname = 'tmp_foo';
+ relname
+---------
+(0 rows)
+
+SELECT current_user = 'regress_guc_user';
+ ?column?
+----------
+ f
+(1 row)
+
+DROP ROLE regress_guc_user;
+--
+-- search_path should react to changes in pg_namespace
+--
+set search_path = foo, public, not_there_initially;
+select current_schemas(false);
+ current_schemas
+-----------------
+ {public}
+(1 row)
+
+create schema not_there_initially;
+select current_schemas(false);
+ current_schemas
+------------------------------
+ {public,not_there_initially}
+(1 row)
+
+drop schema not_there_initially;
+select current_schemas(false);
+ current_schemas
+-----------------
+ {public}
+(1 row)
+
+reset search_path;
+--
+-- Tests for function-local GUC settings
+--
+set work_mem = '3MB';
+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');
+ report_guc | current_setting
+------------+-----------------
+ 1MB | 3MB
+(1 row)
+
+alter function report_guc(text) set work_mem = '2MB';
+select report_guc('work_mem'), current_setting('work_mem');
+ report_guc | current_setting
+------------+-----------------
+ 2MB | 3MB
+(1 row)
+
+alter function report_guc(text) reset all;
+select report_guc('work_mem'), current_setting('work_mem');
+ report_guc | current_setting
+------------+-----------------
+ 3MB | 3MB
+(1 row)
+
+-- 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');
+ myfunc | current_setting
+--------+-----------------
+ 2MB | 3MB
+(1 row)
+
+alter function myfunc(int) reset all;
+select myfunc(0), current_setting('work_mem');
+ myfunc | current_setting
+--------+-----------------
+ 2MB | 2MB
+(1 row)
+
+set work_mem = '3MB';
+-- but SET isn't
+create or replace function myfunc(int) returns text as $$
+begin
+ set work_mem = '2MB';
+ return current_setting('work_mem');
+end $$
+language plpgsql
+set work_mem = '1MB';
+select myfunc(0), current_setting('work_mem');
+ myfunc | current_setting
+--------+-----------------
+ 2MB | 2MB
+(1 row)
+
+set work_mem = '3MB';
+-- it should roll back on error, though
+create or replace function myfunc(int) returns text as $$
+begin
+ set work_mem = '2MB';
+ perform 1/$1;
+ return current_setting('work_mem');
+end $$
+language plpgsql
+set work_mem = '1MB';
+select myfunc(0);
+ERROR: division by zero
+CONTEXT: SQL statement "SELECT 1/$1"
+PL/pgSQL function myfunc(integer) line 4 at PERFORM
+select current_setting('work_mem');
+ current_setting
+-----------------
+ 3MB
+(1 row)
+
+select myfunc(1), current_setting('work_mem');
+ myfunc | current_setting
+--------+-----------------
+ 2MB | 2MB
+(1 row)
+
+-- check current_setting()'s behavior with invalid setting name
+select current_setting('nosuch.setting'); -- FAIL
+ERROR: unrecognized configuration parameter "nosuch.setting"
+select current_setting('nosuch.setting', false); -- FAIL
+ERROR: unrecognized configuration parameter "nosuch.setting"
+select current_setting('nosuch.setting', true) is null;
+ ?column?
+----------
+ t
+(1 row)
+
+-- after this, all three cases should yield 'nada'
+set nosuch.setting = 'nada';
+select current_setting('nosuch.setting');
+ current_setting
+-----------------
+ nada
+(1 row)
+
+select current_setting('nosuch.setting', false);
+ current_setting
+-----------------
+ nada
+(1 row)
+
+select current_setting('nosuch.setting', true);
+ current_setting
+-----------------
+ nada
+(1 row)
+
+-- Normally, CREATE FUNCTION should complain about invalid values in
+-- function SET options; but not if check_function_bodies is off,
+-- because that creates ordering hazards for pg_dump
+create function func_with_bad_set() returns int as $$ select 1 $$
+language sql
+set default_text_search_config = no_such_config;
+NOTICE: text search configuration "no_such_config" does not exist
+ERROR: invalid value for parameter "default_text_search_config": "no_such_config"
+set check_function_bodies = off;
+create function func_with_bad_set() returns int as $$ select 1 $$
+language sql
+set default_text_search_config = no_such_config;
+NOTICE: text search configuration "no_such_config" does not exist
+select func_with_bad_set();
+ERROR: invalid value for parameter "default_text_search_config": "no_such_config"
+reset check_function_bodies;
+set default_with_oids to f;
+-- Should not allow to set it to true.
+set default_with_oids to t;
+ERROR: tables declared WITH OIDS are not supported
diff --git a/src/test/regress/expected/hash_func.out b/src/test/regress/expected/hash_func.out
new file mode 100644
index 0000000..8e23dc3
--- /dev/null
+++ b/src/test/regress/expected/hash_func.out
@@ -0,0 +1,374 @@
+--
+-- 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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+-- array hashing with non-hashable element type
+SELECT v as value, hash_array(v)::bit(32) as standard
+FROM (VALUES ('{0}'::money[])) x(v);
+ERROR: could not identify a hash function for type money
+SELECT v as value, hash_array_extended(v, 0)::bit(32) as extended0
+FROM (VALUES ('{0}'::money[])) x(v);
+ERROR: could not identify an extended hash function for type money
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+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);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+SELECT v as value, hash_multirange(v)::bit(32) as standard,
+ hash_multirange_extended(v, 0)::bit(32) as extended0,
+ hash_multirange_extended(v, 1)::bit(32) as extended1
+FROM (VALUES ('{[10,20)}'::int4multirange), ('{[23, 43]}'::int4multirange),
+ ('{[5675, 550273)}'::int4multirange),
+ ('{[550274, 1550274)}'::int4multirange),
+ ('{[1550275, 208112489)}'::int4multirange)) x(v)
+WHERE hash_multirange(v)::bit(32) != hash_multirange_extended(v, 0)::bit(32)
+ OR hash_multirange(v)::bit(32) = hash_multirange_extended(v, 1)::bit(32);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+CREATE TYPE hash_test_t1 AS (a int, b text);
+SELECT v as value, hash_record(v)::bit(32) as standard,
+ hash_record_extended(v, 0)::bit(32) as extended0,
+ hash_record_extended(v, 1)::bit(32) as extended1
+FROM (VALUES (row(1, 'aaa')::hash_test_t1, row(2, 'bbb'), row(-1, 'ccc'))) x(v)
+WHERE hash_record(v)::bit(32) != hash_record_extended(v, 0)::bit(32)
+ OR hash_record(v)::bit(32) = hash_record_extended(v, 1)::bit(32);
+ value | standard | extended0 | extended1
+-------+----------+-----------+-----------
+(0 rows)
+
+DROP TYPE hash_test_t1;
+-- record hashing with non-hashable field type
+CREATE TYPE hash_test_t2 AS (a money, b text);
+SELECT v as value, hash_record(v)::bit(32) as standard
+FROM (VALUES (row(1, 'aaa')::hash_test_t2)) x(v);
+ERROR: could not identify a hash function for type money
+SELECT v as value, hash_record_extended(v, 0)::bit(32) as extended0
+FROM (VALUES (row(1, 'aaa')::hash_test_t2)) x(v);
+ERROR: could not identify an extended hash function for type money
+DROP TYPE hash_test_t2;
+--
+-- Check special cases for specific data types
+--
+SELECT hashfloat4('0'::float4) = hashfloat4('-0'::float4) AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT hashfloat4('NaN'::float4) = hashfloat4(-'NaN'::float4) AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT hashfloat8('0'::float8) = hashfloat8('-0'::float8) AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT hashfloat8('NaN'::float8) = hashfloat8(-'NaN'::float8) AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT hashfloat4('NaN'::float4) = hashfloat8('NaN'::float8) AS t;
+ t
+---
+ t
+(1 row)
+
diff --git a/src/test/regress/expected/hash_index.out b/src/test/regress/expected/hash_index.out
new file mode 100644
index 0000000..e23de21
--- /dev/null
+++ b/src/test/regress/expected/hash_index.out
@@ -0,0 +1,243 @@
+--
+-- HASH_INDEX
+-- grep 843938989 hash.data
+--
+SELECT * FROM hash_i4_heap
+ WHERE hash_i4_heap.random = 843938989;
+ seqno | random
+-------+-----------
+ 15 | 843938989
+(1 row)
+
+--
+-- hash index
+-- grep 66766766 hash.data
+--
+SELECT * FROM hash_i4_heap
+ WHERE hash_i4_heap.random = 66766766;
+ seqno | random
+-------+--------
+(0 rows)
+
+--
+-- hash index
+-- grep 1505703298 hash.data
+--
+SELECT * FROM hash_name_heap
+ WHERE hash_name_heap.random = '1505703298'::name;
+ seqno | random
+-------+------------
+ 9838 | 1505703298
+(1 row)
+
+--
+-- hash index
+-- grep 7777777 hash.data
+--
+SELECT * FROM hash_name_heap
+ WHERE hash_name_heap.random = '7777777'::name;
+ seqno | random
+-------+--------
+(0 rows)
+
+--
+-- hash index
+-- grep 1351610853 hash.data
+--
+SELECT * FROM hash_txt_heap
+ WHERE hash_txt_heap.random = '1351610853'::text;
+ seqno | random
+-------+------------
+ 5677 | 1351610853
+(1 row)
+
+--
+-- hash index
+-- grep 111111112222222233333333 hash.data
+--
+SELECT * FROM hash_txt_heap
+ WHERE hash_txt_heap.random = '111111112222222233333333'::text;
+ seqno | random
+-------+--------
+(0 rows)
+
+--
+-- hash index
+-- grep 444705537 hash.data
+--
+SELECT * FROM hash_f8_heap
+ WHERE hash_f8_heap.random = '444705537'::float8;
+ seqno | random
+-------+-----------
+ 7853 | 444705537
+(1 row)
+
+--
+-- hash index
+-- grep 88888888 hash.data
+--
+SELECT * FROM hash_f8_heap
+ WHERE hash_f8_heap.random = '88888888'::float8;
+ seqno | random
+-------+--------
+(0 rows)
+
+--
+-- hash index
+-- grep '^90[^0-9]' hashovfl.data
+--
+-- SELECT count(*) AS i988 FROM hash_ovfl_heap
+-- WHERE x = 90;
+--
+-- hash index
+-- grep '^1000[^0-9]' hashovfl.data
+--
+-- SELECT count(*) AS i0 FROM hash_ovfl_heap
+-- WHERE x = 1000;
+--
+-- HASH
+--
+UPDATE hash_i4_heap
+ SET random = 1
+ WHERE hash_i4_heap.seqno = 1492;
+SELECT h.seqno AS i1492, h.random AS i1
+ FROM hash_i4_heap h
+ WHERE h.random = 1;
+ i1492 | i1
+-------+----
+ 1492 | 1
+(1 row)
+
+UPDATE hash_i4_heap
+ SET seqno = 20000
+ WHERE hash_i4_heap.random = 1492795354;
+SELECT h.seqno AS i20000
+ FROM hash_i4_heap h
+ WHERE h.random = 1492795354;
+ i20000
+--------
+ 20000
+(1 row)
+
+UPDATE hash_name_heap
+ SET random = '0123456789abcdef'::name
+ WHERE hash_name_heap.seqno = 6543;
+SELECT h.seqno AS i6543, h.random AS c0_to_f
+ FROM hash_name_heap h
+ WHERE h.random = '0123456789abcdef'::name;
+ i6543 | c0_to_f
+-------+------------------
+ 6543 | 0123456789abcdef
+(1 row)
+
+UPDATE hash_name_heap
+ SET seqno = 20000
+ WHERE hash_name_heap.random = '76652222'::name;
+--
+-- this is the row we just replaced; index scan should return zero rows
+--
+SELECT h.seqno AS emptyset
+ FROM hash_name_heap h
+ WHERE h.random = '76652222'::name;
+ emptyset
+----------
+(0 rows)
+
+UPDATE hash_txt_heap
+ SET random = '0123456789abcdefghijklmnop'::text
+ WHERE hash_txt_heap.seqno = 4002;
+SELECT h.seqno AS i4002, h.random AS c0_to_p
+ FROM hash_txt_heap h
+ WHERE h.random = '0123456789abcdefghijklmnop'::text;
+ i4002 | c0_to_p
+-------+----------------------------
+ 4002 | 0123456789abcdefghijklmnop
+(1 row)
+
+UPDATE hash_txt_heap
+ SET seqno = 20000
+ WHERE hash_txt_heap.random = '959363399'::text;
+SELECT h.seqno AS t20000
+ FROM hash_txt_heap h
+ WHERE h.random = '959363399'::text;
+ t20000
+--------
+ 20000
+(1 row)
+
+UPDATE hash_f8_heap
+ SET random = '-1234.1234'::float8
+ WHERE hash_f8_heap.seqno = 8906;
+SELECT h.seqno AS i8096, h.random AS f1234_1234
+ FROM hash_f8_heap h
+ WHERE h.random = '-1234.1234'::float8;
+ i8096 | f1234_1234
+-------+------------
+ 8906 | -1234.1234
+(1 row)
+
+UPDATE hash_f8_heap
+ SET seqno = 20000
+ WHERE hash_f8_heap.random = '488912369'::float8;
+SELECT h.seqno AS f20000
+ FROM hash_f8_heap h
+ WHERE h.random = '488912369'::float8;
+ f20000
+--------
+ 20000
+(1 row)
+
+-- UPDATE hash_ovfl_heap
+-- SET x = 1000
+-- WHERE x = 90;
+-- this vacuums the index as well
+-- VACUUM hash_ovfl_heap;
+-- SELECT count(*) AS i0 FROM hash_ovfl_heap
+-- WHERE x = 90;
+-- SELECT count(*) AS i988 FROM hash_ovfl_heap
+-- WHERE x = 1000;
+--
+-- Cause some overflow insert and splits.
+--
+CREATE TABLE hash_split_heap (keycol INT);
+INSERT INTO hash_split_heap SELECT 1 FROM generate_series(1, 500) a;
+CREATE INDEX hash_split_index on hash_split_heap USING HASH (keycol);
+INSERT INTO hash_split_heap SELECT 1 FROM generate_series(1, 5000) a;
+-- Let's do a backward scan.
+BEGIN;
+SET enable_seqscan = OFF;
+SET enable_bitmapscan = OFF;
+DECLARE c CURSOR FOR SELECT * from hash_split_heap WHERE keycol = 1;
+MOVE FORWARD ALL FROM c;
+MOVE BACKWARD 10000 FROM c;
+MOVE BACKWARD ALL FROM c;
+CLOSE c;
+END;
+-- DELETE, INSERT, VACUUM.
+DELETE FROM hash_split_heap WHERE keycol = 1;
+INSERT INTO hash_split_heap SELECT a/2 FROM generate_series(1, 25000) a;
+VACUUM hash_split_heap;
+-- Rebuild the index using a different fillfactor
+ALTER INDEX hash_split_index SET (fillfactor = 10);
+REINDEX INDEX hash_split_index;
+-- Clean up.
+DROP TABLE hash_split_heap;
+-- Index on temp table.
+CREATE TEMP TABLE hash_temp_heap (x int, y int);
+INSERT INTO hash_temp_heap VALUES (1,1);
+CREATE INDEX hash_idx ON hash_temp_heap USING hash (x);
+DROP TABLE hash_temp_heap CASCADE;
+-- Float4 type.
+CREATE TABLE hash_heap_float4 (x float4, y int);
+INSERT INTO hash_heap_float4 VALUES (1.1,1);
+CREATE INDEX hash_idx ON hash_heap_float4 USING hash (x);
+DROP TABLE hash_heap_float4 CASCADE;
+-- Test out-of-range fillfactor values
+CREATE INDEX hash_f8_index2 ON hash_f8_heap USING hash (random float8_ops)
+ WITH (fillfactor=9);
+ERROR: value 9 out of bounds for option "fillfactor"
+DETAIL: Valid values are between "10" and "100".
+CREATE INDEX hash_f8_index2 ON hash_f8_heap USING hash (random float8_ops)
+ WITH (fillfactor=101);
+ERROR: value 101 out of bounds for option "fillfactor"
+DETAIL: Valid values are between "10" and "100".
diff --git a/src/test/regress/expected/hash_part.out b/src/test/regress/expected/hash_part.out
new file mode 100644
index 0000000..ac3aabe
--- /dev/null
+++ b/src/test/regress/expected/hash_part.out
@@ -0,0 +1,114 @@
+--
+-- 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 TABLE mchash (a int, b text, c jsonb)
+ PARTITION BY HASH (a part_test_int4_ops, b part_test_text_ops);
+CREATE TABLE mchash1
+ PARTITION OF mchash FOR VALUES WITH (MODULUS 4, REMAINDER 0);
+-- invalid OID, no such table
+SELECT satisfies_hash_partition(0, 4, 0, NULL);
+ERROR: could not open relation with OID 0
+-- not partitioned
+SELECT satisfies_hash_partition('tenk1'::regclass, 4, 0, NULL);
+ERROR: "tenk1" is not a hash partitioned table
+-- partition rather than the parent
+SELECT satisfies_hash_partition('mchash1'::regclass, 4, 0, NULL);
+ERROR: "mchash1" is not a hash partitioned table
+-- invalid modulus
+SELECT satisfies_hash_partition('mchash'::regclass, 0, 0, NULL);
+ERROR: modulus for hash partition must be an integer value greater than zero
+-- remainder too small
+SELECT satisfies_hash_partition('mchash'::regclass, 1, -1, NULL);
+ERROR: remainder for hash partition must be an integer value greater than or equal to zero
+-- remainder too large
+SELECT satisfies_hash_partition('mchash'::regclass, 1, 1, NULL);
+ERROR: remainder for hash partition must be less than modulus
+-- modulus is null
+SELECT satisfies_hash_partition('mchash'::regclass, NULL, 0, NULL);
+ satisfies_hash_partition
+--------------------------
+ f
+(1 row)
+
+-- remainder is null
+SELECT satisfies_hash_partition('mchash'::regclass, 4, NULL, NULL);
+ satisfies_hash_partition
+--------------------------
+ f
+(1 row)
+
+-- too many arguments
+SELECT satisfies_hash_partition('mchash'::regclass, 4, 0, NULL::int, NULL::text, NULL::json);
+ERROR: number of partitioning columns (2) does not match number of partition keys provided (3)
+-- too few arguments
+SELECT satisfies_hash_partition('mchash'::regclass, 3, 1, NULL::int);
+ERROR: number of partitioning columns (2) does not match number of partition keys provided (1)
+-- wrong argument type
+SELECT satisfies_hash_partition('mchash'::regclass, 2, 1, NULL::int, NULL::int);
+ERROR: column 2 of the partition key has type text, but supplied value is of type integer
+-- ok, should be false
+SELECT satisfies_hash_partition('mchash'::regclass, 4, 0, 0, ''::text);
+ satisfies_hash_partition
+--------------------------
+ f
+(1 row)
+
+-- ok, should be true
+SELECT satisfies_hash_partition('mchash'::regclass, 4, 0, 2, ''::text);
+ satisfies_hash_partition
+--------------------------
+ t
+(1 row)
+
+-- argument via variadic syntax, should fail because not all partitioning
+-- columns are of the correct type
+SELECT satisfies_hash_partition('mchash'::regclass, 2, 1,
+ variadic array[1,2]::int[]);
+ERROR: column 2 of the partition key has type "text", but supplied value is of type "integer"
+-- multiple partitioning columns of the same type
+CREATE TABLE mcinthash (a int, b int, c jsonb)
+ PARTITION BY HASH (a part_test_int4_ops, b part_test_int4_ops);
+-- now variadic should work, should be false
+SELECT satisfies_hash_partition('mcinthash'::regclass, 4, 0,
+ variadic array[0, 0]);
+ satisfies_hash_partition
+--------------------------
+ f
+(1 row)
+
+-- should be true
+SELECT satisfies_hash_partition('mcinthash'::regclass, 4, 0,
+ variadic array[0, 1]);
+ satisfies_hash_partition
+--------------------------
+ t
+(1 row)
+
+-- wrong length
+SELECT satisfies_hash_partition('mcinthash'::regclass, 4, 0,
+ variadic array[]::int[]);
+ERROR: number of partitioning columns (2) does not match number of partition keys provided (0)
+-- wrong type
+SELECT satisfies_hash_partition('mcinthash'::regclass, 4, 0,
+ variadic array[now(), now()]);
+ERROR: column 1 of the partition key has type "integer", but supplied value is of type "timestamp with time zone"
+-- check satisfies_hash_partition passes correct collation
+create table text_hashp (a text) partition by hash (a);
+create table text_hashp0 partition of text_hashp for values with (modulus 2, remainder 0);
+create table text_hashp1 partition of text_hashp for values with (modulus 2, remainder 1);
+-- The result here should always be true, because 'xxx' must belong to
+-- one of the two defined partitions
+select satisfies_hash_partition('text_hashp'::regclass, 2, 0, 'xxx'::text) OR
+ satisfies_hash_partition('text_hashp'::regclass, 2, 1, 'xxx'::text) AS satisfies;
+ satisfies
+-----------
+ t
+(1 row)
+
+-- cleanup
+DROP TABLE mchash;
+DROP TABLE mcinthash;
+DROP TABLE text_hashp;
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
new file mode 100644
index 0000000..4b865e7
--- /dev/null
+++ b/src/test/regress/expected/horology.out
@@ -0,0 +1,3344 @@
+--
+-- HOROLOGY
+--
+SET DateStyle = 'Postgres, MDY';
+SHOW TimeZone; -- Many of these tests depend on the prevailing setting
+ TimeZone
+----------
+ PST8PDT
+(1 row)
+
+--
+-- Test various input formats
+--
+SELECT timestamp with time zone '20011227 040506+08';
+ timestamptz
+------------------------------
+ Wed Dec 26 12:05:06 2001 PST
+(1 row)
+
+SELECT timestamp with time zone '20011227 040506-08';
+ timestamptz
+------------------------------
+ Thu Dec 27 04:05:06 2001 PST
+(1 row)
+
+SELECT timestamp with time zone '20011227 040506.789+08';
+ timestamptz
+----------------------------------
+ Wed Dec 26 12:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone '20011227 040506.789-08';
+ timestamptz
+----------------------------------
+ Thu Dec 27 04:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone '20011227T040506+08';
+ timestamptz
+------------------------------
+ Wed Dec 26 12:05:06 2001 PST
+(1 row)
+
+SELECT timestamp with time zone '20011227T040506-08';
+ timestamptz
+------------------------------
+ Thu Dec 27 04:05:06 2001 PST
+(1 row)
+
+SELECT timestamp with time zone '20011227T040506.789+08';
+ timestamptz
+----------------------------------
+ Wed Dec 26 12:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone '20011227T040506.789-08';
+ timestamptz
+----------------------------------
+ Thu Dec 27 04:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone '2001-12-27 04:05:06.789-08';
+ timestamptz
+----------------------------------
+ Thu Dec 27 04:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone '2001.12.27 04:05:06.789-08';
+ timestamptz
+----------------------------------
+ Thu Dec 27 04:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone '2001/12/27 04:05:06.789-08';
+ timestamptz
+----------------------------------
+ Thu Dec 27 04:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone '12/27/2001 04:05:06.789-08';
+ timestamptz
+----------------------------------
+ Thu Dec 27 04:05:06.789 2001 PST
+(1 row)
+
+-- should fail in mdy mode:
+SELECT timestamp with time zone '27/12/2001 04:05:06.789-08';
+ERROR: date/time field value out of range: "27/12/2001 04:05:06.789-08"
+LINE 1: SELECT timestamp with time zone '27/12/2001 04:05:06.789-08'...
+ ^
+HINT: Perhaps you need a different "datestyle" setting.
+set datestyle to dmy;
+SELECT timestamp with time zone '27/12/2001 04:05:06.789-08';
+ timestamptz
+----------------------------------
+ Thu 27 Dec 04:05:06.789 2001 PST
+(1 row)
+
+reset datestyle;
+SELECT timestamp with time zone 'Y2001M12D27H04M05S06.789+08';
+ timestamptz
+----------------------------------
+ Wed Dec 26 12:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'Y2001M12D27H04M05S06.789-08';
+ timestamptz
+----------------------------------
+ Thu Dec 27 04:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'Y2001M12D27H04MM05S06.789+08';
+ timestamptz
+----------------------------------
+ Wed Dec 26 12:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'Y2001M12D27H04MM05S06.789-08';
+ timestamptz
+----------------------------------
+ Thu Dec 27 04:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'J2452271+08';
+ timestamptz
+------------------------------
+ Wed Dec 26 08:00:00 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'J2452271-08';
+ timestamptz
+------------------------------
+ Thu Dec 27 00:00:00 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'J2452271.5+08';
+ timestamptz
+------------------------------
+ Wed Dec 26 20:00:00 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'J2452271.5-08';
+ timestamptz
+------------------------------
+ Thu Dec 27 12:00:00 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'J2452271 04:05:06+08';
+ timestamptz
+------------------------------
+ Wed Dec 26 12:05:06 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'J2452271 04:05:06-08';
+ timestamptz
+------------------------------
+ Thu Dec 27 04:05:06 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'J2452271T040506+08';
+ timestamptz
+------------------------------
+ Wed Dec 26 12:05:06 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'J2452271T040506-08';
+ timestamptz
+------------------------------
+ Thu Dec 27 04:05:06 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'J2452271T040506.789+08';
+ timestamptz
+----------------------------------
+ Wed Dec 26 12:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone 'J2452271T040506.789-08';
+ timestamptz
+----------------------------------
+ Thu Dec 27 04:05:06.789 2001 PST
+(1 row)
+
+-- German/European-style dates with periods as delimiters
+SELECT timestamp with time zone '12.27.2001 04:05:06.789+08';
+ timestamptz
+----------------------------------
+ Wed Dec 26 12:05:06.789 2001 PST
+(1 row)
+
+SELECT timestamp with time zone '12.27.2001 04:05:06.789-08';
+ timestamptz
+----------------------------------
+ Thu Dec 27 04:05:06.789 2001 PST
+(1 row)
+
+SET DateStyle = 'German';
+SELECT timestamp with time zone '27.12.2001 04:05:06.789+08';
+ timestamptz
+-----------------------------
+ 26.12.2001 12:05:06.789 PST
+(1 row)
+
+SELECT timestamp with time zone '27.12.2001 04:05:06.789-08';
+ timestamptz
+-----------------------------
+ 27.12.2001 04:05:06.789 PST
+(1 row)
+
+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';
+ time
+--------------
+ 04:05:06.789
+(1 row)
+
+SELECT time without time zone '040506.789-08';
+ time
+--------------
+ 04:05:06.789
+(1 row)
+
+SELECT time without time zone 'T040506.789+08';
+ time
+--------------
+ 04:05:06.789
+(1 row)
+
+SELECT time without time zone 'T040506.789-08';
+ time
+--------------
+ 04:05:06.789
+(1 row)
+
+SELECT time with time zone '040506.789+08';
+ timetz
+-----------------
+ 04:05:06.789+08
+(1 row)
+
+SELECT time with time zone '040506.789-08';
+ timetz
+-----------------
+ 04:05:06.789-08
+(1 row)
+
+SELECT time with time zone 'T040506.789+08';
+ timetz
+-----------------
+ 04:05:06.789+08
+(1 row)
+
+SELECT time with time zone 'T040506.789-08';
+ timetz
+-----------------
+ 04:05:06.789-08
+(1 row)
+
+SELECT time with time zone 'T040506.789 +08';
+ timetz
+-----------------
+ 04:05:06.789+08
+(1 row)
+
+SELECT time with time zone 'T040506.789 -08';
+ timetz
+-----------------
+ 04:05:06.789-08
+(1 row)
+
+SET DateStyle = 'Postgres, MDY';
+-- Check Julian dates BC
+SELECT date 'J1520447' AS "Confucius' Birthday";
+ Confucius' Birthday
+---------------------
+ 09-28-0551 BC
+(1 row)
+
+SELECT date 'J0' AS "Julian Epoch";
+ Julian Epoch
+---------------
+ 11-24-4714 BC
+(1 row)
+
+--
+-- date, time arithmetic
+--
+SELECT date '1981-02-03' + time '04:05:06' AS "Date + Time";
+ Date + Time
+--------------------------
+ Tue Feb 03 04:05:06 1981
+(1 row)
+
+SELECT date '1991-02-03' + time with time zone '04:05:06 PST' AS "Date + Time PST";
+ Date + Time PST
+------------------------------
+ Sun Feb 03 04:05:06 1991 PST
+(1 row)
+
+SELECT date '2001-02-03' + time with time zone '04:05:06 UTC' AS "Date + Time UTC";
+ Date + Time UTC
+------------------------------
+ Fri Feb 02 20:05:06 2001 PST
+(1 row)
+
+SELECT date '1991-02-03' + interval '2 years' AS "Add Two Years";
+ Add Two Years
+--------------------------
+ Wed Feb 03 00:00:00 1993
+(1 row)
+
+SELECT date '2001-12-13' - interval '2 years' AS "Subtract Two Years";
+ Subtract Two Years
+--------------------------
+ Mon Dec 13 00:00:00 1999
+(1 row)
+
+-- subtract time from date should not make sense; use interval instead
+SELECT date '1991-02-03' - time '04:05:06' AS "Subtract Time";
+ Subtract Time
+--------------------------
+ Sat Feb 02 19:54:54 1991
+(1 row)
+
+SELECT date '1991-02-03' - time with time zone '04:05:06 UTC' AS "Subtract Time UTC";
+ERROR: operator does not exist: date - time with time zone
+LINE 1: SELECT date '1991-02-03' - time with time zone '04:05:06 UTC...
+ ^
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+--
+-- timestamp, interval arithmetic
+--
+SELECT timestamp without time zone '1996-03-01' - interval '1 second' AS "Feb 29";
+ Feb 29
+--------------------------
+ Thu Feb 29 23:59:59 1996
+(1 row)
+
+SELECT timestamp without time zone '1999-03-01' - interval '1 second' AS "Feb 28";
+ Feb 28
+--------------------------
+ Sun Feb 28 23:59:59 1999
+(1 row)
+
+SELECT timestamp without time zone '2000-03-01' - interval '1 second' AS "Feb 29";
+ Feb 29
+--------------------------
+ Tue Feb 29 23:59:59 2000
+(1 row)
+
+SELECT timestamp without time zone '1999-12-01' + interval '1 month - 1 second' AS "Dec 31";
+ Dec 31
+--------------------------
+ Fri Dec 31 23:59:59 1999
+(1 row)
+
+SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '106000000 days' AS "Feb 23, 285506";
+ Feb 23, 285506
+----------------------------
+ Fri Feb 23 00:00:00 285506
+(1 row)
+
+SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '107000000 days' AS "Jan 20, 288244";
+ Jan 20, 288244
+----------------------------
+ Sat Jan 20 00:00:00 288244
+(1 row)
+
+SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '109203489 days' AS "Dec 31, 294276";
+ Dec 31, 294276
+----------------------------
+ Sun Dec 31 00:00:00 294276
+(1 row)
+
+SELECT timestamp without time zone '12/31/294276' - timestamp without time zone '12/23/1999' AS "106751991 Days";
+ 106751991 Days
+------------------
+ @ 106751991 days
+(1 row)
+
+-- 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";
+ True
+------
+ t
+(1 row)
+
+SELECT (timestamp without time zone 'today' = (timestamp without time zone 'tomorrow' - interval '1 day')) as "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (timestamp without time zone 'today 10:30' = (timestamp without time zone 'yesterday' + interval '1 day 10 hr 30 min')) as "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (timestamp without time zone '10:30 today' = (timestamp without time zone 'yesterday' + interval '1 day 10 hr 30 min')) as "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (timestamp without time zone 'tomorrow' = (timestamp without time zone 'yesterday' + interval '2 days')) as "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (timestamp without time zone 'tomorrow 16:00:00' = (timestamp without time zone 'today' + interval '1 day 16 hours')) as "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (timestamp without time zone '16:00:00 tomorrow' = (timestamp without time zone 'today' + interval '1 day 16 hours')) as "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (timestamp without time zone 'yesterday 12:34:56' = (timestamp without time zone 'tomorrow' - interval '2 days - 12:34:56')) as "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (timestamp without time zone '12:34:56 yesterday' = (timestamp without time zone 'tomorrow' - interval '2 days - 12:34:56')) as "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (timestamp without time zone 'tomorrow' > 'now') as "True";
+ True
+------
+ t
+(1 row)
+
+-- 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";
+ Jan_01_1994_11am
+--------------------------
+ Sat Jan 01 11:00:00 1994
+(1 row)
+
+SELECT date '1994-01-01' + time '10:00' AS "Jan_01_1994_10am";
+ Jan_01_1994_10am
+--------------------------
+ Sat Jan 01 10:00:00 1994
+(1 row)
+
+SELECT date '1994-01-01' + timetz '11:00-5' AS "Jan_01_1994_8am";
+ Jan_01_1994_8am
+------------------------------
+ Sat Jan 01 08:00:00 1994 PST
+(1 row)
+
+SELECT timestamptz(date '1994-01-01', time with time zone '11:00-5') AS "Jan_01_1994_8am";
+ Jan_01_1994_8am
+------------------------------
+ Sat Jan 01 08:00:00 1994 PST
+(1 row)
+
+SELECT d1 + interval '1 year' AS one_year FROM TIMESTAMP_TBL;
+ one_year
+-----------------------------
+ -infinity
+ infinity
+ Fri Jan 01 00:00:00 1971
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:02 1998
+ Tue Feb 10 17:32:01.4 1998
+ Tue Feb 10 17:32:01.5 1998
+ Tue Feb 10 17:32:01.6 1998
+ Fri Jan 02 00:00:00 1998
+ Fri Jan 02 03:04:05 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Wed Jun 10 17:32:01 1998
+ Sun Sep 22 18:19:20 2002
+ Thu Mar 15 08:14:01 2001
+ Thu Mar 15 13:14:02 2001
+ Thu Mar 15 12:14:03 2001
+ Thu Mar 15 03:14:04 2001
+ Thu Mar 15 02:14:05 2001
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:00 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Wed Jun 10 18:32:01 1998
+ Tue Feb 10 17:32:01 1998
+ Wed Feb 11 17:32:01 1998
+ Thu Feb 12 17:32:01 1998
+ Fri Feb 13 17:32:01 1998
+ Sat Feb 14 17:32:01 1998
+ Sun Feb 15 17:32:01 1998
+ Mon Feb 16 17:32:01 1998
+ Thu Feb 16 17:32:01 0096 BC
+ Sun Feb 16 17:32:01 0098
+ Fri Feb 16 17:32:01 0598
+ Wed Feb 16 17:32:01 1098
+ Sun Feb 16 17:32:01 1698
+ Fri Feb 16 17:32:01 1798
+ Wed Feb 16 17:32:01 1898
+ Mon Feb 16 17:32:01 1998
+ Sun Feb 16 17:32:01 2098
+ Fri Feb 28 17:32:01 1997
+ Fri Feb 28 17:32:01 1997
+ Sat Mar 01 17:32:01 1997
+ Tue Dec 30 17:32:01 1997
+ Wed Dec 31 17:32:01 1997
+ Thu Jan 01 17:32:01 1998
+ Sat Feb 28 17:32:01 1998
+ Sun Mar 01 17:32:01 1998
+ Wed Dec 30 17:32:01 1998
+ Thu Dec 31 17:32:01 1998
+ Sun Dec 31 17:32:01 2000
+ Mon Jan 01 17:32:01 2001
+ Mon Dec 31 17:32:01 2001
+ Tue Jan 01 17:32:01 2002
+(65 rows)
+
+SELECT d1 - interval '1 year' AS one_year FROM TIMESTAMP_TBL;
+ one_year
+-----------------------------
+ -infinity
+ infinity
+ Wed Jan 01 00:00:00 1969
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:02 1996
+ Sat Feb 10 17:32:01.4 1996
+ Sat Feb 10 17:32:01.5 1996
+ Sat Feb 10 17:32:01.6 1996
+ Tue Jan 02 00:00:00 1996
+ Tue Jan 02 03:04:05 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Mon Jun 10 17:32:01 1996
+ Fri Sep 22 18:19:20 2000
+ Mon Mar 15 08:14:01 1999
+ Mon Mar 15 13:14:02 1999
+ Mon Mar 15 12:14:03 1999
+ Mon Mar 15 03:14:04 1999
+ Mon Mar 15 02:14:05 1999
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:00 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Mon Jun 10 18:32:01 1996
+ Sat Feb 10 17:32:01 1996
+ Sun Feb 11 17:32:01 1996
+ Mon Feb 12 17:32:01 1996
+ Tue Feb 13 17:32:01 1996
+ Wed Feb 14 17:32:01 1996
+ Thu Feb 15 17:32:01 1996
+ Fri Feb 16 17:32:01 1996
+ Mon Feb 16 17:32:01 0098 BC
+ Thu Feb 16 17:32:01 0096
+ Tue Feb 16 17:32:01 0596
+ Sun Feb 16 17:32:01 1096
+ Thu Feb 16 17:32:01 1696
+ Tue Feb 16 17:32:01 1796
+ Sun Feb 16 17:32:01 1896
+ Fri Feb 16 17:32:01 1996
+ Thu Feb 16 17:32:01 2096
+ Tue Feb 28 17:32:01 1995
+ Tue Feb 28 17:32:01 1995
+ Wed Mar 01 17:32:01 1995
+ Sat Dec 30 17:32:01 1995
+ Sun Dec 31 17:32:01 1995
+ Mon Jan 01 17:32:01 1996
+ Wed Feb 28 17:32:01 1996
+ Fri Mar 01 17:32:01 1996
+ Mon Dec 30 17:32:01 1996
+ Tue Dec 31 17:32:01 1996
+ Thu Dec 31 17:32:01 1998
+ Fri Jan 01 17:32:01 1999
+ Fri Dec 31 17:32:01 1999
+ Sat Jan 01 17:32:01 2000
+(65 rows)
+
+SELECT timestamp with time zone '1996-03-01' - interval '1 second' AS "Feb 29";
+ Feb 29
+------------------------------
+ Thu Feb 29 23:59:59 1996 PST
+(1 row)
+
+SELECT timestamp with time zone '1999-03-01' - interval '1 second' AS "Feb 28";
+ Feb 28
+------------------------------
+ Sun Feb 28 23:59:59 1999 PST
+(1 row)
+
+SELECT timestamp with time zone '2000-03-01' - interval '1 second' AS "Feb 29";
+ Feb 29
+------------------------------
+ Tue Feb 29 23:59:59 2000 PST
+(1 row)
+
+SELECT timestamp with time zone '1999-12-01' + interval '1 month - 1 second' AS "Dec 31";
+ Dec 31
+------------------------------
+ Fri Dec 31 23:59:59 1999 PST
+(1 row)
+
+SELECT (timestamp with time zone 'today' = (timestamp with time zone 'yesterday' + interval '1 day')) as "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (timestamp with time zone 'today' = (timestamp with time zone 'tomorrow' - interval '1 day')) as "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (timestamp with time zone 'tomorrow' = (timestamp with time zone 'yesterday' + interval '2 days')) as "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (timestamp with time zone 'tomorrow' > 'now') as "True";
+ True
+------
+ t
+(1 row)
+
+-- 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";
+ Apr 3, 12:00
+------------------------------
+ Sun Apr 03 12:00:00 2005 CDT
+(1 row)
+
+SELECT timestamp with time zone '2005-04-02 12:00-07' + interval '24 hours' as "Apr 3, 13:00";
+ Apr 3, 13:00
+------------------------------
+ Sun Apr 03 13:00:00 2005 CDT
+(1 row)
+
+SELECT timestamp with time zone '2005-04-03 12:00-06' - interval '1 day' as "Apr 2, 12:00";
+ Apr 2, 12:00
+------------------------------
+ Sat Apr 02 12:00:00 2005 CST
+(1 row)
+
+SELECT timestamp with time zone '2005-04-03 12:00-06' - interval '24 hours' as "Apr 2, 11:00";
+ Apr 2, 11:00
+------------------------------
+ Sat Apr 02 11:00:00 2005 CST
+(1 row)
+
+RESET TIME ZONE;
+SELECT timestamptz(date '1994-01-01', time '11:00') AS "Jan_01_1994_10am";
+ Jan_01_1994_10am
+------------------------------
+ Sat Jan 01 11:00:00 1994 PST
+(1 row)
+
+SELECT timestamptz(date '1994-01-01', time '10:00') AS "Jan_01_1994_9am";
+ Jan_01_1994_9am
+------------------------------
+ Sat Jan 01 10:00:00 1994 PST
+(1 row)
+
+SELECT timestamptz(date '1994-01-01', time with time zone '11:00-8') AS "Jan_01_1994_11am";
+ Jan_01_1994_11am
+------------------------------
+ Sat Jan 01 11:00:00 1994 PST
+(1 row)
+
+SELECT timestamptz(date '1994-01-01', time with time zone '10:00-8') AS "Jan_01_1994_10am";
+ Jan_01_1994_10am
+------------------------------
+ Sat Jan 01 10:00:00 1994 PST
+(1 row)
+
+SELECT timestamptz(date '1994-01-01', time with time zone '11:00-5') AS "Jan_01_1994_8am";
+ Jan_01_1994_8am
+------------------------------
+ Sat Jan 01 08:00:00 1994 PST
+(1 row)
+
+SELECT d1 + interval '1 year' AS one_year FROM TIMESTAMPTZ_TBL;
+ one_year
+---------------------------------
+ -infinity
+ infinity
+ Thu Dec 31 16:00:00 1970 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Tue Feb 10 17:32:02 1998 PST
+ Tue Feb 10 17:32:01.4 1998 PST
+ Tue Feb 10 17:32:01.5 1998 PST
+ Tue Feb 10 17:32:01.6 1998 PST
+ Fri Jan 02 00:00:00 1998 PST
+ Fri Jan 02 03:04:05 1998 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Wed Jun 10 17:32:01 1998 PDT
+ Sun Sep 22 18:19:20 2002 PDT
+ Thu Mar 15 08:14:01 2001 PST
+ Thu Mar 15 04:14:02 2001 PST
+ Thu Mar 15 02:14:03 2001 PST
+ Thu Mar 15 03:14:04 2001 PST
+ Thu Mar 15 01:14:05 2001 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Tue Feb 10 17:32:00 1998 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Tue Feb 10 17:32:01 1998 PST
+ Tue Feb 10 09:32:01 1998 PST
+ Tue Feb 10 09:32:01 1998 PST
+ Tue Feb 10 09:32:01 1998 PST
+ Tue Feb 10 14:32:01 1998 PST
+ Fri Jul 10 14:32:01 1998 PDT
+ Wed Jun 10 18:32:01 1998 PDT
+ Tue Feb 10 17:32:01 1998 PST
+ Wed Feb 11 17:32:01 1998 PST
+ Thu Feb 12 17:32:01 1998 PST
+ Fri Feb 13 17:32:01 1998 PST
+ Sat Feb 14 17:32:01 1998 PST
+ Sun Feb 15 17:32:01 1998 PST
+ Mon Feb 16 17:32:01 1998 PST
+ Thu Feb 16 17:32:01 0096 PST BC
+ Sun Feb 16 17:32:01 0098 PST
+ Fri Feb 16 17:32:01 0598 PST
+ Wed Feb 16 17:32:01 1098 PST
+ Sun Feb 16 17:32:01 1698 PST
+ Fri Feb 16 17:32:01 1798 PST
+ Wed Feb 16 17:32:01 1898 PST
+ Mon Feb 16 17:32:01 1998 PST
+ Sun Feb 16 17:32:01 2098 PST
+ Fri Feb 28 17:32:01 1997 PST
+ Fri Feb 28 17:32:01 1997 PST
+ Sat Mar 01 17:32:01 1997 PST
+ Tue Dec 30 17:32:01 1997 PST
+ Wed Dec 31 17:32:01 1997 PST
+ Thu Jan 01 17:32:01 1998 PST
+ Sat Feb 28 17:32:01 1998 PST
+ Sun Mar 01 17:32:01 1998 PST
+ Wed Dec 30 17:32:01 1998 PST
+ Thu Dec 31 17:32:01 1998 PST
+ Sun Dec 31 17:32:01 2000 PST
+ Mon Jan 01 17:32:01 2001 PST
+ Mon Dec 31 17:32:01 2001 PST
+ Tue Jan 01 17:32:01 2002 PST
+(66 rows)
+
+SELECT d1 - interval '1 year' AS one_year FROM TIMESTAMPTZ_TBL;
+ one_year
+---------------------------------
+ -infinity
+ infinity
+ Tue Dec 31 16:00:00 1968 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Sat Feb 10 17:32:02 1996 PST
+ Sat Feb 10 17:32:01.4 1996 PST
+ Sat Feb 10 17:32:01.5 1996 PST
+ Sat Feb 10 17:32:01.6 1996 PST
+ Tue Jan 02 00:00:00 1996 PST
+ Tue Jan 02 03:04:05 1996 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Mon Jun 10 17:32:01 1996 PDT
+ Fri Sep 22 18:19:20 2000 PDT
+ Mon Mar 15 08:14:01 1999 PST
+ Mon Mar 15 04:14:02 1999 PST
+ Mon Mar 15 02:14:03 1999 PST
+ Mon Mar 15 03:14:04 1999 PST
+ Mon Mar 15 01:14:05 1999 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Sat Feb 10 17:32:00 1996 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Sat Feb 10 17:32:01 1996 PST
+ Sat Feb 10 09:32:01 1996 PST
+ Sat Feb 10 09:32:01 1996 PST
+ Sat Feb 10 09:32:01 1996 PST
+ Sat Feb 10 14:32:01 1996 PST
+ Wed Jul 10 14:32:01 1996 PDT
+ Mon Jun 10 18:32:01 1996 PDT
+ Sat Feb 10 17:32:01 1996 PST
+ Sun Feb 11 17:32:01 1996 PST
+ Mon Feb 12 17:32:01 1996 PST
+ Tue Feb 13 17:32:01 1996 PST
+ Wed Feb 14 17:32:01 1996 PST
+ Thu Feb 15 17:32:01 1996 PST
+ Fri Feb 16 17:32:01 1996 PST
+ Mon Feb 16 17:32:01 0098 PST BC
+ Thu Feb 16 17:32:01 0096 PST
+ Tue Feb 16 17:32:01 0596 PST
+ Sun Feb 16 17:32:01 1096 PST
+ Thu Feb 16 17:32:01 1696 PST
+ Tue Feb 16 17:32:01 1796 PST
+ Sun Feb 16 17:32:01 1896 PST
+ Fri Feb 16 17:32:01 1996 PST
+ Thu Feb 16 17:32:01 2096 PST
+ Tue Feb 28 17:32:01 1995 PST
+ Tue Feb 28 17:32:01 1995 PST
+ Wed Mar 01 17:32:01 1995 PST
+ Sat Dec 30 17:32:01 1995 PST
+ Sun Dec 31 17:32:01 1995 PST
+ Mon Jan 01 17:32:01 1996 PST
+ Wed Feb 28 17:32:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST
+ Mon Dec 30 17:32:01 1996 PST
+ Tue Dec 31 17:32:01 1996 PST
+ Thu Dec 31 17:32:01 1998 PST
+ Fri Jan 01 17:32:01 1999 PST
+ Fri Dec 31 17:32:01 1999 PST
+ Sat Jan 01 17:32:01 2000 PST
+(66 rows)
+
+--
+-- time, interval arithmetic
+--
+SELECT CAST(time '01:02' AS interval) AS "+01:02";
+ +01:02
+-----------------
+ @ 1 hour 2 mins
+(1 row)
+
+SELECT CAST(interval '02:03' AS time) AS "02:03:00";
+ 02:03:00
+----------
+ 02:03:00
+(1 row)
+
+SELECT time '01:30' + interval '02:01' AS "03:31:00";
+ 03:31:00
+----------
+ 03:31:00
+(1 row)
+
+SELECT time '01:30' - interval '02:01' AS "23:29:00";
+ 23:29:00
+----------
+ 23:29:00
+(1 row)
+
+SELECT time '02:30' + interval '36:01' AS "14:31:00";
+ 14:31:00
+----------
+ 14:31:00
+(1 row)
+
+SELECT time '03:30' + interval '1 month 04:01' AS "07:31:00";
+ 07:31:00
+----------
+ 07:31:00
+(1 row)
+
+SELECT CAST(time with time zone '01:02-08' AS interval) AS "+00:01";
+ERROR: cannot cast type time with time zone to interval
+LINE 1: SELECT CAST(time with time zone '01:02-08' AS interval) AS "...
+ ^
+SELECT CAST(interval '02:03' AS time with time zone) AS "02:03:00-08";
+ERROR: cannot cast type interval to time with time zone
+LINE 1: SELECT CAST(interval '02:03' AS time with time zone) AS "02:...
+ ^
+SELECT time with time zone '01:30-08' - interval '02:01' AS "23:29:00-08";
+ 23:29:00-08
+-------------
+ 23:29:00-08
+(1 row)
+
+SELECT time with time zone '02:30-08' + interval '36:01' AS "14:31:00-08";
+ 14:31:00-08
+-------------
+ 14:31:00-08
+(1 row)
+
+-- 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";
+ 07:31:00
+----------
+ 07:31:00
+(1 row)
+
+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";
+ 07:31:00
+----------
+ 07:31:00
+(1 row)
+
+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;
+ t | i | add | subtract
+----------------------------+-----------+----------------------------+----------------------------
+ Wed Feb 28 17:32:01 1996 | @ 1 min | Wed Feb 28 17:33:01 1996 | Wed Feb 28 17:31:01 1996
+ Wed Feb 28 17:32:01 1996 | @ 5 hours | Wed Feb 28 22:32:01 1996 | Wed Feb 28 12:32:01 1996
+ Thu Feb 29 17:32:01 1996 | @ 1 min | Thu Feb 29 17:33:01 1996 | Thu Feb 29 17:31:01 1996
+ Thu Feb 29 17:32:01 1996 | @ 5 hours | Thu Feb 29 22:32:01 1996 | Thu Feb 29 12:32:01 1996
+ Fri Mar 01 17:32:01 1996 | @ 1 min | Fri Mar 01 17:33:01 1996 | Fri Mar 01 17:31:01 1996
+ Fri Mar 01 17:32:01 1996 | @ 5 hours | Fri Mar 01 22:32:01 1996 | Fri Mar 01 12:32:01 1996
+ Mon Dec 30 17:32:01 1996 | @ 1 min | Mon Dec 30 17:33:01 1996 | Mon Dec 30 17:31:01 1996
+ Mon Dec 30 17:32:01 1996 | @ 5 hours | Mon Dec 30 22:32:01 1996 | Mon Dec 30 12:32:01 1996
+ Tue Dec 31 17:32:01 1996 | @ 1 min | Tue Dec 31 17:33:01 1996 | Tue Dec 31 17:31:01 1996
+ Tue Dec 31 17:32:01 1996 | @ 5 hours | Tue Dec 31 22:32:01 1996 | Tue Dec 31 12:32:01 1996
+ Wed Jan 01 17:32:01 1997 | @ 1 min | Wed Jan 01 17:33:01 1997 | Wed Jan 01 17:31:01 1997
+ Wed Jan 01 17:32:01 1997 | @ 5 hours | Wed Jan 01 22:32:01 1997 | Wed Jan 01 12:32:01 1997
+ Thu Jan 02 00:00:00 1997 | @ 1 min | Thu Jan 02 00:01:00 1997 | Wed Jan 01 23:59:00 1997
+ Thu Jan 02 00:00:00 1997 | @ 5 hours | Thu Jan 02 05:00:00 1997 | Wed Jan 01 19:00:00 1997
+ Thu Jan 02 03:04:05 1997 | @ 1 min | Thu Jan 02 03:05:05 1997 | Thu Jan 02 03:03:05 1997
+ Thu Jan 02 03:04:05 1997 | @ 5 hours | Thu Jan 02 08:04:05 1997 | Wed Jan 01 22:04:05 1997
+ Mon Feb 10 17:32:00 1997 | @ 1 min | Mon Feb 10 17:33:00 1997 | Mon Feb 10 17:31:00 1997
+ Mon Feb 10 17:32:00 1997 | @ 5 hours | Mon Feb 10 22:32:00 1997 | Mon Feb 10 12:32:00 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 1 min | Mon Feb 10 17:33:01 1997 | Mon Feb 10 17:31:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01 1997 | @ 5 hours | Mon Feb 10 22:32:01 1997 | Mon Feb 10 12:32:01 1997
+ Mon Feb 10 17:32:01.4 1997 | @ 1 min | Mon Feb 10 17:33:01.4 1997 | Mon Feb 10 17:31:01.4 1997
+ Mon Feb 10 17:32:01.4 1997 | @ 5 hours | Mon Feb 10 22:32:01.4 1997 | Mon Feb 10 12:32:01.4 1997
+ Mon Feb 10 17:32:01.5 1997 | @ 1 min | Mon Feb 10 17:33:01.5 1997 | Mon Feb 10 17:31:01.5 1997
+ Mon Feb 10 17:32:01.5 1997 | @ 5 hours | Mon Feb 10 22:32:01.5 1997 | Mon Feb 10 12:32:01.5 1997
+ Mon Feb 10 17:32:01.6 1997 | @ 1 min | Mon Feb 10 17:33:01.6 1997 | Mon Feb 10 17:31:01.6 1997
+ Mon Feb 10 17:32:01.6 1997 | @ 5 hours | Mon Feb 10 22:32:01.6 1997 | Mon Feb 10 12:32:01.6 1997
+ Mon Feb 10 17:32:02 1997 | @ 1 min | Mon Feb 10 17:33:02 1997 | Mon Feb 10 17:31:02 1997
+ Mon Feb 10 17:32:02 1997 | @ 5 hours | Mon Feb 10 22:32:02 1997 | Mon Feb 10 12:32:02 1997
+ Tue Feb 11 17:32:01 1997 | @ 1 min | Tue Feb 11 17:33:01 1997 | Tue Feb 11 17:31:01 1997
+ Tue Feb 11 17:32:01 1997 | @ 5 hours | Tue Feb 11 22:32:01 1997 | Tue Feb 11 12:32:01 1997
+ Wed Feb 12 17:32:01 1997 | @ 1 min | Wed Feb 12 17:33:01 1997 | Wed Feb 12 17:31:01 1997
+ Wed Feb 12 17:32:01 1997 | @ 5 hours | Wed Feb 12 22:32:01 1997 | Wed Feb 12 12:32:01 1997
+ Thu Feb 13 17:32:01 1997 | @ 1 min | Thu Feb 13 17:33:01 1997 | Thu Feb 13 17:31:01 1997
+ Thu Feb 13 17:32:01 1997 | @ 5 hours | Thu Feb 13 22:32:01 1997 | Thu Feb 13 12:32:01 1997
+ Fri Feb 14 17:32:01 1997 | @ 1 min | Fri Feb 14 17:33:01 1997 | Fri Feb 14 17:31:01 1997
+ Fri Feb 14 17:32:01 1997 | @ 5 hours | Fri Feb 14 22:32:01 1997 | Fri Feb 14 12:32:01 1997
+ Sat Feb 15 17:32:01 1997 | @ 1 min | Sat Feb 15 17:33:01 1997 | Sat Feb 15 17:31:01 1997
+ Sat Feb 15 17:32:01 1997 | @ 5 hours | Sat Feb 15 22:32:01 1997 | Sat Feb 15 12:32:01 1997
+ Sun Feb 16 17:32:01 1997 | @ 1 min | Sun Feb 16 17:33:01 1997 | Sun Feb 16 17:31:01 1997
+ Sun Feb 16 17:32:01 1997 | @ 1 min | Sun Feb 16 17:33:01 1997 | Sun Feb 16 17:31:01 1997
+ Sun Feb 16 17:32:01 1997 | @ 5 hours | Sun Feb 16 22:32:01 1997 | Sun Feb 16 12:32:01 1997
+ Sun Feb 16 17:32:01 1997 | @ 5 hours | Sun Feb 16 22:32:01 1997 | Sun Feb 16 12:32:01 1997
+ Fri Feb 28 17:32:01 1997 | @ 1 min | Fri Feb 28 17:33:01 1997 | Fri Feb 28 17:31:01 1997
+ Fri Feb 28 17:32:01 1997 | @ 5 hours | Fri Feb 28 22:32:01 1997 | Fri Feb 28 12:32:01 1997
+ Sat Mar 01 17:32:01 1997 | @ 1 min | Sat Mar 01 17:33:01 1997 | Sat Mar 01 17:31:01 1997
+ Sat Mar 01 17:32:01 1997 | @ 5 hours | Sat Mar 01 22:32:01 1997 | Sat Mar 01 12:32:01 1997
+ Tue Jun 10 17:32:01 1997 | @ 1 min | Tue Jun 10 17:33:01 1997 | Tue Jun 10 17:31:01 1997
+ Tue Jun 10 17:32:01 1997 | @ 5 hours | Tue Jun 10 22:32:01 1997 | Tue Jun 10 12:32:01 1997
+ Tue Jun 10 18:32:01 1997 | @ 1 min | Tue Jun 10 18:33:01 1997 | Tue Jun 10 18:31:01 1997
+ Tue Jun 10 18:32:01 1997 | @ 5 hours | Tue Jun 10 23:32:01 1997 | Tue Jun 10 13:32:01 1997
+ Tue Dec 30 17:32:01 1997 | @ 1 min | Tue Dec 30 17:33:01 1997 | Tue Dec 30 17:31:01 1997
+ Tue Dec 30 17:32:01 1997 | @ 5 hours | Tue Dec 30 22:32:01 1997 | Tue Dec 30 12:32:01 1997
+ Wed Dec 31 17:32:01 1997 | @ 1 min | Wed Dec 31 17:33:01 1997 | Wed Dec 31 17:31:01 1997
+ Wed Dec 31 17:32:01 1997 | @ 5 hours | Wed Dec 31 22:32:01 1997 | Wed Dec 31 12:32:01 1997
+ Fri Dec 31 17:32:01 1999 | @ 1 min | Fri Dec 31 17:33:01 1999 | Fri Dec 31 17:31:01 1999
+ Fri Dec 31 17:32:01 1999 | @ 5 hours | Fri Dec 31 22:32:01 1999 | Fri Dec 31 12:32:01 1999
+ Sat Jan 01 17:32:01 2000 | @ 1 min | Sat Jan 01 17:33:01 2000 | Sat Jan 01 17:31:01 2000
+ Sat Jan 01 17:32:01 2000 | @ 5 hours | Sat Jan 01 22:32:01 2000 | Sat Jan 01 12:32:01 2000
+ Wed Mar 15 02:14:05 2000 | @ 1 min | Wed Mar 15 02:15:05 2000 | Wed Mar 15 02:13:05 2000
+ Wed Mar 15 02:14:05 2000 | @ 5 hours | Wed Mar 15 07:14:05 2000 | Tue Mar 14 21:14:05 2000
+ Wed Mar 15 03:14:04 2000 | @ 1 min | Wed Mar 15 03:15:04 2000 | Wed Mar 15 03:13:04 2000
+ Wed Mar 15 03:14:04 2000 | @ 5 hours | Wed Mar 15 08:14:04 2000 | Tue Mar 14 22:14:04 2000
+ Wed Mar 15 08:14:01 2000 | @ 1 min | Wed Mar 15 08:15:01 2000 | Wed Mar 15 08:13:01 2000
+ Wed Mar 15 08:14:01 2000 | @ 5 hours | Wed Mar 15 13:14:01 2000 | Wed Mar 15 03:14:01 2000
+ Wed Mar 15 12:14:03 2000 | @ 1 min | Wed Mar 15 12:15:03 2000 | Wed Mar 15 12:13:03 2000
+ Wed Mar 15 12:14:03 2000 | @ 5 hours | Wed Mar 15 17:14:03 2000 | Wed Mar 15 07:14:03 2000
+ Wed Mar 15 13:14:02 2000 | @ 1 min | Wed Mar 15 13:15:02 2000 | Wed Mar 15 13:13:02 2000
+ Wed Mar 15 13:14:02 2000 | @ 5 hours | Wed Mar 15 18:14:02 2000 | Wed Mar 15 08:14:02 2000
+ Sun Dec 31 17:32:01 2000 | @ 1 min | Sun Dec 31 17:33:01 2000 | Sun Dec 31 17:31:01 2000
+ Sun Dec 31 17:32:01 2000 | @ 5 hours | Sun Dec 31 22:32:01 2000 | Sun Dec 31 12:32:01 2000
+(104 rows)
+
+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;
+ t | i | add | subtract
+-------------+-------------------------------+-------------+-------------
+ 00:00:00 | @ 14 secs ago | 23:59:46 | 00:00:14
+ 00:00:00 | @ 1 min | 00:01:00 | 23:59:00
+ 00:00:00 | @ 5 hours | 05:00:00 | 19:00:00
+ 00:00:00 | @ 1 day 2 hours 3 mins 4 secs | 02:03:04 | 21:56:56
+ 00:00:00 | @ 10 days | 00:00:00 | 00:00:00
+ 00:00:00 | @ 3 mons | 00:00:00 | 00:00:00
+ 00:00:00 | @ 5 mons | 00:00:00 | 00:00:00
+ 00:00:00 | @ 5 mons 12 hours | 12:00:00 | 12:00:00
+ 00:00:00 | @ 6 years | 00:00:00 | 00:00:00
+ 00:00:00 | @ 34 years | 00:00:00 | 00:00:00
+ 01:00:00 | @ 14 secs ago | 00:59:46 | 01:00:14
+ 01:00:00 | @ 1 min | 01:01:00 | 00:59:00
+ 01:00:00 | @ 5 hours | 06:00:00 | 20:00:00
+ 01:00:00 | @ 1 day 2 hours 3 mins 4 secs | 03:03:04 | 22:56:56
+ 01:00:00 | @ 10 days | 01:00:00 | 01:00:00
+ 01:00:00 | @ 3 mons | 01:00:00 | 01:00:00
+ 01:00:00 | @ 5 mons | 01:00:00 | 01:00:00
+ 01:00:00 | @ 5 mons 12 hours | 13:00:00 | 13:00:00
+ 01:00:00 | @ 6 years | 01:00:00 | 01:00:00
+ 01:00:00 | @ 34 years | 01:00:00 | 01:00:00
+ 02:03:00 | @ 14 secs ago | 02:02:46 | 02:03:14
+ 02:03:00 | @ 1 min | 02:04:00 | 02:02:00
+ 02:03:00 | @ 5 hours | 07:03:00 | 21:03:00
+ 02:03:00 | @ 1 day 2 hours 3 mins 4 secs | 04:06:04 | 23:59:56
+ 02:03:00 | @ 10 days | 02:03:00 | 02:03:00
+ 02:03:00 | @ 3 mons | 02:03:00 | 02:03:00
+ 02:03:00 | @ 5 mons | 02:03:00 | 02:03:00
+ 02:03:00 | @ 5 mons 12 hours | 14:03:00 | 14:03:00
+ 02:03:00 | @ 6 years | 02:03:00 | 02:03:00
+ 02:03:00 | @ 34 years | 02:03:00 | 02:03:00
+ 11:59:00 | @ 14 secs ago | 11:58:46 | 11:59:14
+ 11:59:00 | @ 1 min | 12:00:00 | 11:58:00
+ 11:59:00 | @ 5 hours | 16:59:00 | 06:59:00
+ 11:59:00 | @ 1 day 2 hours 3 mins 4 secs | 14:02:04 | 09:55:56
+ 11:59:00 | @ 10 days | 11:59:00 | 11:59:00
+ 11:59:00 | @ 3 mons | 11:59:00 | 11:59:00
+ 11:59:00 | @ 5 mons | 11:59:00 | 11:59:00
+ 11:59:00 | @ 5 mons 12 hours | 23:59:00 | 23:59:00
+ 11:59:00 | @ 6 years | 11:59:00 | 11:59:00
+ 11:59:00 | @ 34 years | 11:59:00 | 11:59:00
+ 12:00:00 | @ 14 secs ago | 11:59:46 | 12:00:14
+ 12:00:00 | @ 1 min | 12:01:00 | 11:59:00
+ 12:00:00 | @ 5 hours | 17:00:00 | 07:00:00
+ 12:00:00 | @ 1 day 2 hours 3 mins 4 secs | 14:03:04 | 09:56:56
+ 12:00:00 | @ 10 days | 12:00:00 | 12:00:00
+ 12:00:00 | @ 3 mons | 12:00:00 | 12:00:00
+ 12:00:00 | @ 5 mons | 12:00:00 | 12:00:00
+ 12:00:00 | @ 5 mons 12 hours | 00:00:00 | 00:00:00
+ 12:00:00 | @ 6 years | 12:00:00 | 12:00:00
+ 12:00:00 | @ 34 years | 12:00:00 | 12:00:00
+ 12:01:00 | @ 14 secs ago | 12:00:46 | 12:01:14
+ 12:01:00 | @ 1 min | 12:02:00 | 12:00:00
+ 12:01:00 | @ 5 hours | 17:01:00 | 07:01:00
+ 12:01:00 | @ 1 day 2 hours 3 mins 4 secs | 14:04:04 | 09:57:56
+ 12:01:00 | @ 10 days | 12:01:00 | 12:01:00
+ 12:01:00 | @ 3 mons | 12:01:00 | 12:01:00
+ 12:01:00 | @ 5 mons | 12:01:00 | 12:01:00
+ 12:01:00 | @ 5 mons 12 hours | 00:01:00 | 00:01:00
+ 12:01:00 | @ 6 years | 12:01:00 | 12:01:00
+ 12:01:00 | @ 34 years | 12:01:00 | 12:01:00
+ 15:36:39 | @ 14 secs ago | 15:36:25 | 15:36:53
+ 15:36:39 | @ 14 secs ago | 15:36:25 | 15:36:53
+ 15:36:39 | @ 1 min | 15:37:39 | 15:35:39
+ 15:36:39 | @ 1 min | 15:37:39 | 15:35:39
+ 15:36:39 | @ 5 hours | 20:36:39 | 10:36:39
+ 15:36:39 | @ 5 hours | 20:36:39 | 10:36:39
+ 15:36:39 | @ 1 day 2 hours 3 mins 4 secs | 17:39:43 | 13:33:35
+ 15:36:39 | @ 1 day 2 hours 3 mins 4 secs | 17:39:43 | 13:33:35
+ 15:36:39 | @ 10 days | 15:36:39 | 15:36:39
+ 15:36:39 | @ 10 days | 15:36:39 | 15:36:39
+ 15:36:39 | @ 3 mons | 15:36:39 | 15:36:39
+ 15:36:39 | @ 3 mons | 15:36:39 | 15:36:39
+ 15:36:39 | @ 5 mons | 15:36:39 | 15:36:39
+ 15:36:39 | @ 5 mons | 15:36:39 | 15:36:39
+ 15:36:39 | @ 5 mons 12 hours | 03:36:39 | 03:36:39
+ 15:36:39 | @ 5 mons 12 hours | 03:36:39 | 03:36:39
+ 15:36:39 | @ 6 years | 15:36:39 | 15:36:39
+ 15:36:39 | @ 6 years | 15:36:39 | 15:36:39
+ 15:36:39 | @ 34 years | 15:36:39 | 15:36:39
+ 15:36:39 | @ 34 years | 15:36:39 | 15:36:39
+ 23:59:00 | @ 14 secs ago | 23:58:46 | 23:59:14
+ 23:59:00 | @ 1 min | 00:00:00 | 23:58:00
+ 23:59:00 | @ 5 hours | 04:59:00 | 18:59:00
+ 23:59:00 | @ 1 day 2 hours 3 mins 4 secs | 02:02:04 | 21:55:56
+ 23:59:00 | @ 10 days | 23:59:00 | 23:59:00
+ 23:59:00 | @ 3 mons | 23:59:00 | 23:59:00
+ 23:59:00 | @ 5 mons | 23:59:00 | 23:59:00
+ 23:59:00 | @ 5 mons 12 hours | 11:59:00 | 11:59:00
+ 23:59:00 | @ 6 years | 23:59:00 | 23:59:00
+ 23:59:00 | @ 34 years | 23:59:00 | 23:59:00
+ 23:59:59.99 | @ 14 secs ago | 23:59:45.99 | 00:00:13.99
+ 23:59:59.99 | @ 1 min | 00:00:59.99 | 23:58:59.99
+ 23:59:59.99 | @ 5 hours | 04:59:59.99 | 18:59:59.99
+ 23:59:59.99 | @ 1 day 2 hours 3 mins 4 secs | 02:03:03.99 | 21:56:55.99
+ 23:59:59.99 | @ 10 days | 23:59:59.99 | 23:59:59.99
+ 23:59:59.99 | @ 3 mons | 23:59:59.99 | 23:59:59.99
+ 23:59:59.99 | @ 5 mons | 23:59:59.99 | 23:59:59.99
+ 23:59:59.99 | @ 5 mons 12 hours | 11:59:59.99 | 11:59:59.99
+ 23:59:59.99 | @ 6 years | 23:59:59.99 | 23:59:59.99
+ 23:59:59.99 | @ 34 years | 23:59:59.99 | 23:59:59.99
+(100 rows)
+
+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;
+ t | i | add | subtract
+----------------+-------------------------------+----------------+----------------
+ 00:01:00-07 | @ 14 secs ago | 00:00:46-07 | 00:01:14-07
+ 00:01:00-07 | @ 1 min | 00:02:00-07 | 00:00:00-07
+ 00:01:00-07 | @ 5 hours | 05:01:00-07 | 19:01:00-07
+ 00:01:00-07 | @ 1 day 2 hours 3 mins 4 secs | 02:04:04-07 | 21:57:56-07
+ 00:01:00-07 | @ 10 days | 00:01:00-07 | 00:01:00-07
+ 00:01:00-07 | @ 3 mons | 00:01:00-07 | 00:01:00-07
+ 00:01:00-07 | @ 5 mons | 00:01:00-07 | 00:01:00-07
+ 00:01:00-07 | @ 5 mons 12 hours | 12:01:00-07 | 12:01:00-07
+ 00:01:00-07 | @ 6 years | 00:01:00-07 | 00:01:00-07
+ 00:01:00-07 | @ 34 years | 00:01:00-07 | 00:01:00-07
+ 01:00:00-07 | @ 14 secs ago | 00:59:46-07 | 01:00:14-07
+ 01:00:00-07 | @ 1 min | 01:01:00-07 | 00:59:00-07
+ 01:00:00-07 | @ 5 hours | 06:00:00-07 | 20:00:00-07
+ 01:00:00-07 | @ 1 day 2 hours 3 mins 4 secs | 03:03:04-07 | 22:56:56-07
+ 01:00:00-07 | @ 10 days | 01:00:00-07 | 01:00:00-07
+ 01:00:00-07 | @ 3 mons | 01:00:00-07 | 01:00:00-07
+ 01:00:00-07 | @ 5 mons | 01:00:00-07 | 01:00:00-07
+ 01:00:00-07 | @ 5 mons 12 hours | 13:00:00-07 | 13:00:00-07
+ 01:00:00-07 | @ 6 years | 01:00:00-07 | 01:00:00-07
+ 01:00:00-07 | @ 34 years | 01:00:00-07 | 01:00:00-07
+ 02:03:00-07 | @ 14 secs ago | 02:02:46-07 | 02:03:14-07
+ 02:03:00-07 | @ 1 min | 02:04:00-07 | 02:02:00-07
+ 02:03:00-07 | @ 5 hours | 07:03:00-07 | 21:03:00-07
+ 02:03:00-07 | @ 1 day 2 hours 3 mins 4 secs | 04:06:04-07 | 23:59:56-07
+ 02:03:00-07 | @ 10 days | 02:03:00-07 | 02:03:00-07
+ 02:03:00-07 | @ 3 mons | 02:03:00-07 | 02:03:00-07
+ 02:03:00-07 | @ 5 mons | 02:03:00-07 | 02:03:00-07
+ 02:03:00-07 | @ 5 mons 12 hours | 14:03:00-07 | 14:03:00-07
+ 02:03:00-07 | @ 6 years | 02:03:00-07 | 02:03:00-07
+ 02:03:00-07 | @ 34 years | 02:03:00-07 | 02:03:00-07
+ 08:08:00-04 | @ 14 secs ago | 08:07:46-04 | 08:08:14-04
+ 08:08:00-04 | @ 1 min | 08:09:00-04 | 08:07:00-04
+ 08:08:00-04 | @ 5 hours | 13:08:00-04 | 03:08:00-04
+ 08:08:00-04 | @ 1 day 2 hours 3 mins 4 secs | 10:11:04-04 | 06:04:56-04
+ 08:08:00-04 | @ 10 days | 08:08:00-04 | 08:08:00-04
+ 08:08:00-04 | @ 3 mons | 08:08:00-04 | 08:08:00-04
+ 08:08:00-04 | @ 5 mons | 08:08:00-04 | 08:08:00-04
+ 08:08:00-04 | @ 5 mons 12 hours | 20:08:00-04 | 20:08:00-04
+ 08:08:00-04 | @ 6 years | 08:08:00-04 | 08:08:00-04
+ 08:08:00-04 | @ 34 years | 08:08:00-04 | 08:08:00-04
+ 07:07:00-08 | @ 14 secs ago | 07:06:46-08 | 07:07:14-08
+ 07:07:00-08 | @ 1 min | 07:08:00-08 | 07:06:00-08
+ 07:07:00-08 | @ 5 hours | 12:07:00-08 | 02:07:00-08
+ 07:07:00-08 | @ 1 day 2 hours 3 mins 4 secs | 09:10:04-08 | 05:03:56-08
+ 07:07:00-08 | @ 10 days | 07:07:00-08 | 07:07:00-08
+ 07:07:00-08 | @ 3 mons | 07:07:00-08 | 07:07:00-08
+ 07:07:00-08 | @ 5 mons | 07:07:00-08 | 07:07:00-08
+ 07:07:00-08 | @ 5 mons 12 hours | 19:07:00-08 | 19:07:00-08
+ 07:07:00-08 | @ 6 years | 07:07:00-08 | 07:07:00-08
+ 07:07:00-08 | @ 34 years | 07:07:00-08 | 07:07:00-08
+ 11:59:00-07 | @ 14 secs ago | 11:58:46-07 | 11:59:14-07
+ 11:59:00-07 | @ 1 min | 12:00:00-07 | 11:58:00-07
+ 11:59:00-07 | @ 5 hours | 16:59:00-07 | 06:59:00-07
+ 11:59:00-07 | @ 1 day 2 hours 3 mins 4 secs | 14:02:04-07 | 09:55:56-07
+ 11:59:00-07 | @ 10 days | 11:59:00-07 | 11:59:00-07
+ 11:59:00-07 | @ 3 mons | 11:59:00-07 | 11:59:00-07
+ 11:59:00-07 | @ 5 mons | 11:59:00-07 | 11:59:00-07
+ 11:59:00-07 | @ 5 mons 12 hours | 23:59:00-07 | 23:59:00-07
+ 11:59:00-07 | @ 6 years | 11:59:00-07 | 11:59:00-07
+ 11:59:00-07 | @ 34 years | 11:59:00-07 | 11:59:00-07
+ 12:00:00-07 | @ 14 secs ago | 11:59:46-07 | 12:00:14-07
+ 12:00:00-07 | @ 1 min | 12:01:00-07 | 11:59:00-07
+ 12:00:00-07 | @ 5 hours | 17:00:00-07 | 07:00:00-07
+ 12:00:00-07 | @ 1 day 2 hours 3 mins 4 secs | 14:03:04-07 | 09:56:56-07
+ 12:00:00-07 | @ 10 days | 12:00:00-07 | 12:00:00-07
+ 12:00:00-07 | @ 3 mons | 12:00:00-07 | 12:00:00-07
+ 12:00:00-07 | @ 5 mons | 12:00:00-07 | 12:00:00-07
+ 12:00:00-07 | @ 5 mons 12 hours | 00:00:00-07 | 00:00:00-07
+ 12:00:00-07 | @ 6 years | 12:00:00-07 | 12:00:00-07
+ 12:00:00-07 | @ 34 years | 12:00:00-07 | 12:00:00-07
+ 12:01:00-07 | @ 14 secs ago | 12:00:46-07 | 12:01:14-07
+ 12:01:00-07 | @ 1 min | 12:02:00-07 | 12:00:00-07
+ 12:01:00-07 | @ 5 hours | 17:01:00-07 | 07:01:00-07
+ 12:01:00-07 | @ 1 day 2 hours 3 mins 4 secs | 14:04:04-07 | 09:57:56-07
+ 12:01:00-07 | @ 10 days | 12:01:00-07 | 12:01:00-07
+ 12:01:00-07 | @ 3 mons | 12:01:00-07 | 12:01:00-07
+ 12:01:00-07 | @ 5 mons | 12:01:00-07 | 12:01:00-07
+ 12:01:00-07 | @ 5 mons 12 hours | 00:01:00-07 | 00:01:00-07
+ 12:01:00-07 | @ 6 years | 12:01:00-07 | 12:01:00-07
+ 12:01:00-07 | @ 34 years | 12:01:00-07 | 12:01:00-07
+ 15:36:39-04 | @ 14 secs ago | 15:36:25-04 | 15:36:53-04
+ 15:36:39-04 | @ 1 min | 15:37:39-04 | 15:35:39-04
+ 15:36:39-04 | @ 5 hours | 20:36:39-04 | 10:36:39-04
+ 15:36:39-04 | @ 1 day 2 hours 3 mins 4 secs | 17:39:43-04 | 13:33:35-04
+ 15:36:39-04 | @ 10 days | 15:36:39-04 | 15:36:39-04
+ 15:36:39-04 | @ 3 mons | 15:36:39-04 | 15:36:39-04
+ 15:36:39-04 | @ 5 mons | 15:36:39-04 | 15:36:39-04
+ 15:36:39-04 | @ 5 mons 12 hours | 03:36:39-04 | 03:36:39-04
+ 15:36:39-04 | @ 6 years | 15:36:39-04 | 15:36:39-04
+ 15:36:39-04 | @ 34 years | 15:36:39-04 | 15:36:39-04
+ 15:36:39-05 | @ 14 secs ago | 15:36:25-05 | 15:36:53-05
+ 15:36:39-05 | @ 1 min | 15:37:39-05 | 15:35:39-05
+ 15:36:39-05 | @ 5 hours | 20:36:39-05 | 10:36:39-05
+ 15:36:39-05 | @ 1 day 2 hours 3 mins 4 secs | 17:39:43-05 | 13:33:35-05
+ 15:36:39-05 | @ 10 days | 15:36:39-05 | 15:36:39-05
+ 15:36:39-05 | @ 3 mons | 15:36:39-05 | 15:36:39-05
+ 15:36:39-05 | @ 5 mons | 15:36:39-05 | 15:36:39-05
+ 15:36:39-05 | @ 5 mons 12 hours | 03:36:39-05 | 03:36:39-05
+ 15:36:39-05 | @ 6 years | 15:36:39-05 | 15:36:39-05
+ 15:36:39-05 | @ 34 years | 15:36:39-05 | 15:36:39-05
+ 23:59:00-07 | @ 14 secs ago | 23:58:46-07 | 23:59:14-07
+ 23:59:00-07 | @ 1 min | 00:00:00-07 | 23:58:00-07
+ 23:59:00-07 | @ 5 hours | 04:59:00-07 | 18:59:00-07
+ 23:59:00-07 | @ 1 day 2 hours 3 mins 4 secs | 02:02:04-07 | 21:55:56-07
+ 23:59:00-07 | @ 10 days | 23:59:00-07 | 23:59:00-07
+ 23:59:00-07 | @ 3 mons | 23:59:00-07 | 23:59:00-07
+ 23:59:00-07 | @ 5 mons | 23:59:00-07 | 23:59:00-07
+ 23:59:00-07 | @ 5 mons 12 hours | 11:59:00-07 | 11:59:00-07
+ 23:59:00-07 | @ 6 years | 23:59:00-07 | 23:59:00-07
+ 23:59:00-07 | @ 34 years | 23:59:00-07 | 23:59:00-07
+ 23:59:59.99-07 | @ 14 secs ago | 23:59:45.99-07 | 00:00:13.99-07
+ 23:59:59.99-07 | @ 1 min | 00:00:59.99-07 | 23:58:59.99-07
+ 23:59:59.99-07 | @ 5 hours | 04:59:59.99-07 | 18:59:59.99-07
+ 23:59:59.99-07 | @ 1 day 2 hours 3 mins 4 secs | 02:03:03.99-07 | 21:56:55.99-07
+ 23:59:59.99-07 | @ 10 days | 23:59:59.99-07 | 23:59:59.99-07
+ 23:59:59.99-07 | @ 3 mons | 23:59:59.99-07 | 23:59:59.99-07
+ 23:59:59.99-07 | @ 5 mons | 23:59:59.99-07 | 23:59:59.99-07
+ 23:59:59.99-07 | @ 5 mons 12 hours | 11:59:59.99-07 | 11:59:59.99-07
+ 23:59:59.99-07 | @ 6 years | 23:59:59.99-07 | 23:59:59.99-07
+ 23:59:59.99-07 | @ 34 years | 23:59:59.99-07 | 23:59:59.99-07
+(120 rows)
+
+-- 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";
+ True
+------
+ t
+(1 row)
+
+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";
+ False
+-------
+ f
+(1 row)
+
+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";
+ True
+------
+ t
+(1 row)
+
+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";
+ False
+-------
+ f
+(1 row)
+
+SELECT (timestamp with time zone '2000-11-27', interval '12 hours')
+ OVERLAPS (timestamp with time zone '2000-11-27', interval '12 hours') AS "True";
+ True
+------
+ t
+(1 row)
+
+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";
+ False
+-------
+ f
+(1 row)
+
+-- 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";
+ True
+------
+ t
+(1 row)
+
+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";
+ False
+-------
+ f
+(1 row)
+
+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";
+ True
+------
+ t
+(1 row)
+
+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";
+ False
+-------
+ f
+(1 row)
+
+SELECT (timestamp without time zone '2000-11-27', interval '12 hours')
+ OVERLAPS (timestamp without time zone '2000-11-27', interval '12 hours') AS "True";
+ True
+------
+ t
+(1 row)
+
+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";
+ False
+-------
+ f
+(1 row)
+
+-- test time and interval
+SELECT (time '00:00', time '01:00')
+ OVERLAPS (time '00:30', time '01:30') AS "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (time '00:00', interval '1 hour')
+ OVERLAPS (time '00:30', interval '1 hour') AS "True";
+ True
+------
+ t
+(1 row)
+
+SELECT (time '00:00', interval '1 hour')
+ OVERLAPS (time '01:30', interval '1 hour') AS "False";
+ False
+-------
+ f
+(1 row)
+
+-- 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";
+ False
+-------
+ f
+(1 row)
+
+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";
+ timestamp
+------------------------------
+ Thu Jan 01 00:00:00 1970 PST
+ Wed Feb 28 17:32:01 1996 PST
+ Thu Feb 29 17:32:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST
+ Mon Dec 30 17:32:01 1996 PST
+ Tue Dec 31 17:32:01 1996 PST
+ Fri Dec 31 17:32:01 1999 PST
+ Sat Jan 01 17:32:01 2000 PST
+ Wed Mar 15 02:14:05 2000 PST
+ Wed Mar 15 03:14:04 2000 PST
+ Wed Mar 15 08:14:01 2000 PST
+ Wed Mar 15 12:14:03 2000 PST
+ Wed Mar 15 13:14:02 2000 PST
+ Sun Dec 31 17:32:01 2000 PST
+ Mon Jan 01 17:32:01 2001 PST
+ Sat Sep 22 18:19:20 2001 PDT
+(16 rows)
+
+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";
+ timestamp | interval | plus
+------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | @ 14 secs ago | Wed Dec 31 23:59:46 1969 PST
+ Thu Jan 01 00:00:00 1970 PST | @ 1 min | Thu Jan 01 00:01:00 1970 PST
+ Thu Jan 01 00:00:00 1970 PST | @ 5 hours | Thu Jan 01 05:00:00 1970 PST
+ Thu Jan 01 00:00:00 1970 PST | @ 1 day 2 hours 3 mins 4 secs | Fri Jan 02 02:03:04 1970 PST
+ Thu Jan 01 00:00:00 1970 PST | @ 10 days | Sun Jan 11 00:00:00 1970 PST
+ Thu Jan 01 00:00:00 1970 PST | @ 3 mons | Wed Apr 01 00:00:00 1970 PST
+ Thu Jan 01 00:00:00 1970 PST | @ 5 mons | Mon Jun 01 00:00:00 1970 PDT
+ Thu Jan 01 00:00:00 1970 PST | @ 5 mons 12 hours | Mon Jun 01 12:00:00 1970 PDT
+ Thu Jan 01 00:00:00 1970 PST | @ 6 years | Thu Jan 01 00:00:00 1976 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 14 secs ago | Wed Feb 28 17:31:47 1996 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 1 min | Wed Feb 28 17:33:01 1996 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 5 hours | Wed Feb 28 22:32:01 1996 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 14 secs ago | Thu Feb 29 17:31:47 1996 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 1 min | Thu Feb 29 17:33:01 1996 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 1 day 2 hours 3 mins 4 secs | Thu Feb 29 19:35:05 1996 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 5 hours | Thu Feb 29 22:32:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 14 secs ago | Fri Mar 01 17:31:47 1996 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 1 min | Fri Mar 01 17:33:01 1996 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 1 day 2 hours 3 mins 4 secs | Fri Mar 01 19:35:05 1996 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 5 hours | Fri Mar 01 22:32:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 1 day 2 hours 3 mins 4 secs | Sat Mar 02 19:35:05 1996 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 10 days | Sat Mar 09 17:32:01 1996 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 10 days | Sun Mar 10 17:32:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 10 days | Mon Mar 11 17:32:01 1996 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 3 mons | Tue May 28 17:32:01 1996 PDT
+ Thu Feb 29 17:32:01 1996 PST | @ 3 mons | Wed May 29 17:32:01 1996 PDT
+ Fri Mar 01 17:32:01 1996 PST | @ 3 mons | Sat Jun 01 17:32:01 1996 PDT
+ Wed Feb 28 17:32:01 1996 PST | @ 5 mons | Sun Jul 28 17:32:01 1996 PDT
+ Wed Feb 28 17:32:01 1996 PST | @ 5 mons 12 hours | Mon Jul 29 05:32:01 1996 PDT
+ Thu Feb 29 17:32:01 1996 PST | @ 5 mons | Mon Jul 29 17:32:01 1996 PDT
+ Thu Feb 29 17:32:01 1996 PST | @ 5 mons 12 hours | Tue Jul 30 05:32:01 1996 PDT
+ Fri Mar 01 17:32:01 1996 PST | @ 5 mons | Thu Aug 01 17:32:01 1996 PDT
+ Fri Mar 01 17:32:01 1996 PST | @ 5 mons 12 hours | Fri Aug 02 05:32:01 1996 PDT
+ Mon Dec 30 17:32:01 1996 PST | @ 14 secs ago | Mon Dec 30 17:31:47 1996 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 1 min | Mon Dec 30 17:33:01 1996 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 5 hours | Mon Dec 30 22:32:01 1996 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 14 secs ago | Tue Dec 31 17:31:47 1996 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 1 min | Tue Dec 31 17:33:01 1996 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 1 day 2 hours 3 mins 4 secs | Tue Dec 31 19:35:05 1996 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 5 hours | Tue Dec 31 22:32:01 1996 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 1 day 2 hours 3 mins 4 secs | Wed Jan 01 19:35:05 1997 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 10 days | Thu Jan 09 17:32:01 1997 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 10 days | Fri Jan 10 17:32:01 1997 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 3 mons | Sun Mar 30 17:32:01 1997 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 3 mons | Mon Mar 31 17:32:01 1997 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 5 mons | Fri May 30 17:32:01 1997 PDT
+ Mon Dec 30 17:32:01 1996 PST | @ 5 mons 12 hours | Sat May 31 05:32:01 1997 PDT
+ Tue Dec 31 17:32:01 1996 PST | @ 5 mons | Sat May 31 17:32:01 1997 PDT
+ Tue Dec 31 17:32:01 1996 PST | @ 5 mons 12 hours | Sun Jun 01 05:32:01 1997 PDT
+ Fri Dec 31 17:32:01 1999 PST | @ 14 secs ago | Fri Dec 31 17:31:47 1999 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 1 min | Fri Dec 31 17:33:01 1999 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 5 hours | Fri Dec 31 22:32:01 1999 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 14 secs ago | Sat Jan 01 17:31:47 2000 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 1 min | Sat Jan 01 17:33:01 2000 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 1 day 2 hours 3 mins 4 secs | Sat Jan 01 19:35:05 2000 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 5 hours | Sat Jan 01 22:32:01 2000 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Sun Jan 02 19:35:05 2000 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 10 days | Mon Jan 10 17:32:01 2000 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 10 days | Tue Jan 11 17:32:01 2000 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 14 secs ago | Wed Mar 15 02:13:51 2000 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 1 min | Wed Mar 15 02:15:05 2000 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 14 secs ago | Wed Mar 15 03:13:50 2000 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 1 min | Wed Mar 15 03:15:04 2000 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 5 hours | Wed Mar 15 07:14:05 2000 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 14 secs ago | Wed Mar 15 08:13:47 2000 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 5 hours | Wed Mar 15 08:14:04 2000 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 1 min | Wed Mar 15 08:15:01 2000 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 14 secs ago | Wed Mar 15 12:13:49 2000 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 1 min | Wed Mar 15 12:15:03 2000 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 14 secs ago | Wed Mar 15 13:13:48 2000 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 5 hours | Wed Mar 15 13:14:01 2000 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 1 min | Wed Mar 15 13:15:02 2000 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 5 hours | Wed Mar 15 17:14:03 2000 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 5 hours | Wed Mar 15 18:14:02 2000 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Thu Mar 16 04:17:09 2000 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Thu Mar 16 05:17:08 2000 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Thu Mar 16 10:17:05 2000 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Thu Mar 16 14:17:07 2000 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Thu Mar 16 15:17:06 2000 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 10 days | Sat Mar 25 02:14:05 2000 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 10 days | Sat Mar 25 03:14:04 2000 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 10 days | Sat Mar 25 08:14:01 2000 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 10 days | Sat Mar 25 12:14:03 2000 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 10 days | Sat Mar 25 13:14:02 2000 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 3 mons | Fri Mar 31 17:32:01 2000 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 3 mons | Sat Apr 01 17:32:01 2000 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 5 mons | Wed May 31 17:32:01 2000 PDT
+ Fri Dec 31 17:32:01 1999 PST | @ 5 mons 12 hours | Thu Jun 01 05:32:01 2000 PDT
+ Sat Jan 01 17:32:01 2000 PST | @ 5 mons | Thu Jun 01 17:32:01 2000 PDT
+ Sat Jan 01 17:32:01 2000 PST | @ 5 mons 12 hours | Fri Jun 02 05:32:01 2000 PDT
+ Wed Mar 15 02:14:05 2000 PST | @ 3 mons | Thu Jun 15 02:14:05 2000 PDT
+ Wed Mar 15 03:14:04 2000 PST | @ 3 mons | Thu Jun 15 03:14:04 2000 PDT
+ Wed Mar 15 08:14:01 2000 PST | @ 3 mons | Thu Jun 15 08:14:01 2000 PDT
+ Wed Mar 15 12:14:03 2000 PST | @ 3 mons | Thu Jun 15 12:14:03 2000 PDT
+ Wed Mar 15 13:14:02 2000 PST | @ 3 mons | Thu Jun 15 13:14:02 2000 PDT
+ Wed Mar 15 02:14:05 2000 PST | @ 5 mons | Tue Aug 15 02:14:05 2000 PDT
+ Wed Mar 15 03:14:04 2000 PST | @ 5 mons | Tue Aug 15 03:14:04 2000 PDT
+ Wed Mar 15 08:14:01 2000 PST | @ 5 mons | Tue Aug 15 08:14:01 2000 PDT
+ Wed Mar 15 12:14:03 2000 PST | @ 5 mons | Tue Aug 15 12:14:03 2000 PDT
+ Wed Mar 15 13:14:02 2000 PST | @ 5 mons | Tue Aug 15 13:14:02 2000 PDT
+ Wed Mar 15 02:14:05 2000 PST | @ 5 mons 12 hours | Tue Aug 15 14:14:05 2000 PDT
+ Wed Mar 15 03:14:04 2000 PST | @ 5 mons 12 hours | Tue Aug 15 15:14:04 2000 PDT
+ Wed Mar 15 08:14:01 2000 PST | @ 5 mons 12 hours | Tue Aug 15 20:14:01 2000 PDT
+ Wed Mar 15 12:14:03 2000 PST | @ 5 mons 12 hours | Wed Aug 16 00:14:03 2000 PDT
+ Wed Mar 15 13:14:02 2000 PST | @ 5 mons 12 hours | Wed Aug 16 01:14:02 2000 PDT
+ Sun Dec 31 17:32:01 2000 PST | @ 14 secs ago | Sun Dec 31 17:31:47 2000 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 1 min | Sun Dec 31 17:33:01 2000 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 5 hours | Sun Dec 31 22:32:01 2000 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 14 secs ago | Mon Jan 01 17:31:47 2001 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 1 min | Mon Jan 01 17:33:01 2001 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Mon Jan 01 19:35:05 2001 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 5 hours | Mon Jan 01 22:32:01 2001 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 1 day 2 hours 3 mins 4 secs | Tue Jan 02 19:35:05 2001 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 10 days | Wed Jan 10 17:32:01 2001 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 10 days | Thu Jan 11 17:32:01 2001 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 3 mons | Sat Mar 31 17:32:01 2001 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 3 mons | Sun Apr 01 17:32:01 2001 PDT
+ Sun Dec 31 17:32:01 2000 PST | @ 5 mons | Thu May 31 17:32:01 2001 PDT
+ Sun Dec 31 17:32:01 2000 PST | @ 5 mons 12 hours | Fri Jun 01 05:32:01 2001 PDT
+ Mon Jan 01 17:32:01 2001 PST | @ 5 mons | Fri Jun 01 17:32:01 2001 PDT
+ Mon Jan 01 17:32:01 2001 PST | @ 5 mons 12 hours | Sat Jun 02 05:32:01 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:06 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:20:20 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 23:19:20 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 1 day 2 hours 3 mins 4 secs | Sun Sep 23 20:22:24 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 10 days | Tue Oct 02 18:19:20 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 3 mons | Sat Dec 22 18:19:20 2001 PST
+ Sat Sep 22 18:19:20 2001 PDT | @ 5 mons | Fri Feb 22 18:19:20 2002 PST
+ Sat Sep 22 18:19:20 2001 PDT | @ 5 mons 12 hours | Sat Feb 23 06:19:20 2002 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 6 years | Thu Feb 28 17:32:01 2002 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 6 years | Thu Feb 28 17:32:01 2002 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 6 years | Fri Mar 01 17:32:01 2002 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 6 years | Mon Dec 30 17:32:01 2002 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 6 years | Tue Dec 31 17:32:01 2002 PST
+ Thu Jan 01 00:00:00 1970 PST | @ 34 years | Thu Jan 01 00:00:00 2004 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 6 years | Sat Dec 31 17:32:01 2005 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 6 years | Sun Jan 01 17:32:01 2006 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 6 years | Wed Mar 15 02:14:05 2006 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 6 years | Wed Mar 15 03:14:04 2006 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 6 years | Wed Mar 15 08:14:01 2006 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 6 years | Wed Mar 15 12:14:03 2006 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 6 years | Wed Mar 15 13:14:02 2006 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 6 years | Sun Dec 31 17:32:01 2006 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 6 years | Mon Jan 01 17:32:01 2007 PST
+ Sat Sep 22 18:19:20 2001 PDT | @ 6 years | Sat Sep 22 18:19:20 2007 PDT
+ Wed Feb 28 17:32:01 1996 PST | @ 34 years | Thu Feb 28 17:32:01 2030 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 34 years | Thu Feb 28 17:32:01 2030 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 34 years | Fri Mar 01 17:32:01 2030 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 34 years | Mon Dec 30 17:32:01 2030 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 34 years | Tue Dec 31 17:32:01 2030 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 34 years | Sat Dec 31 17:32:01 2033 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 34 years | Sun Jan 01 17:32:01 2034 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 34 years | Wed Mar 15 02:14:05 2034 PDT
+ Wed Mar 15 03:14:04 2000 PST | @ 34 years | Wed Mar 15 03:14:04 2034 PDT
+ Wed Mar 15 08:14:01 2000 PST | @ 34 years | Wed Mar 15 08:14:01 2034 PDT
+ Wed Mar 15 12:14:03 2000 PST | @ 34 years | Wed Mar 15 12:14:03 2034 PDT
+ Wed Mar 15 13:14:02 2000 PST | @ 34 years | Wed Mar 15 13:14:02 2034 PDT
+ Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
+ Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
+(160 rows)
+
+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";
+ timestamp | interval | minus
+------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | @ 34 years | Wed Jan 01 00:00:00 1936 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 34 years | Thu Mar 01 17:32:01 1962 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 34 years | Sun Dec 30 17:32:01 1962 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 34 years | Mon Dec 31 17:32:01 1962 PST
+ Thu Jan 01 00:00:00 1970 PST | @ 6 years | Wed Jan 01 00:00:00 1964 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 34 years | Fri Dec 31 17:32:01 1965 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 34 years | Sat Jan 01 17:32:01 1966 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 34 years | Tue Mar 15 02:14:05 1966 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 34 years | Tue Mar 15 03:14:04 1966 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 34 years | Tue Mar 15 08:14:01 1966 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 34 years | Tue Mar 15 12:14:03 1966 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 34 years | Tue Mar 15 13:14:02 1966 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sat Dec 31 17:32:01 1966 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 34 years | Sun Jan 01 17:32:01 1967 PST
+ Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Fri Sep 22 18:19:20 1967 PDT
+ Thu Jan 01 00:00:00 1970 PST | @ 5 mons 12 hours | Thu Jul 31 12:00:00 1969 PDT
+ Thu Jan 01 00:00:00 1970 PST | @ 5 mons | Fri Aug 01 00:00:00 1969 PDT
+ Thu Jan 01 00:00:00 1970 PST | @ 3 mons | Wed Oct 01 00:00:00 1969 PDT
+ Thu Jan 01 00:00:00 1970 PST | @ 10 days | Mon Dec 22 00:00:00 1969 PST
+ Thu Jan 01 00:00:00 1970 PST | @ 1 day 2 hours 3 mins 4 secs | Tue Dec 30 21:56:56 1969 PST
+ Thu Jan 01 00:00:00 1970 PST | @ 5 hours | Wed Dec 31 19:00:00 1969 PST
+ Thu Jan 01 00:00:00 1970 PST | @ 1 min | Wed Dec 31 23:59:00 1969 PST
+ Thu Jan 01 00:00:00 1970 PST | @ 14 secs ago | Thu Jan 01 00:00:14 1970 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 6 years | Wed Feb 28 17:32:01 1990 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 6 years | Wed Feb 28 17:32:01 1990 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 6 years | Thu Mar 01 17:32:01 1990 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 6 years | Sun Dec 30 17:32:01 1990 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 6 years | Mon Dec 31 17:32:01 1990 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 6 years | Fri Dec 31 17:32:01 1993 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 6 years | Sat Jan 01 17:32:01 1994 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 6 years | Tue Mar 15 02:14:05 1994 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 6 years | Tue Mar 15 03:14:04 1994 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 6 years | Tue Mar 15 08:14:01 1994 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 6 years | Tue Mar 15 12:14:03 1994 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 6 years | Tue Mar 15 13:14:02 1994 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 6 years | Sat Dec 31 17:32:01 1994 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 6 years | Sun Jan 01 17:32:01 1995 PST
+ Sat Sep 22 18:19:20 2001 PDT | @ 6 years | Fri Sep 22 18:19:20 1995 PDT
+ Wed Feb 28 17:32:01 1996 PST | @ 5 mons 12 hours | Thu Sep 28 05:32:01 1995 PDT
+ Wed Feb 28 17:32:01 1996 PST | @ 5 mons | Thu Sep 28 17:32:01 1995 PDT
+ Thu Feb 29 17:32:01 1996 PST | @ 5 mons 12 hours | Fri Sep 29 05:32:01 1995 PDT
+ Thu Feb 29 17:32:01 1996 PST | @ 5 mons | Fri Sep 29 17:32:01 1995 PDT
+ Fri Mar 01 17:32:01 1996 PST | @ 5 mons 12 hours | Sun Oct 01 05:32:01 1995 PDT
+ Fri Mar 01 17:32:01 1996 PST | @ 5 mons | Sun Oct 01 17:32:01 1995 PDT
+ Wed Feb 28 17:32:01 1996 PST | @ 3 mons | Tue Nov 28 17:32:01 1995 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 3 mons | Wed Nov 29 17:32:01 1995 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 3 mons | Fri Dec 01 17:32:01 1995 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 10 days | Sun Feb 18 17:32:01 1996 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 10 days | Mon Feb 19 17:32:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 10 days | Tue Feb 20 17:32:01 1996 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 1 day 2 hours 3 mins 4 secs | Tue Feb 27 15:28:57 1996 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 5 hours | Wed Feb 28 12:32:01 1996 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 1 day 2 hours 3 mins 4 secs | Wed Feb 28 15:28:57 1996 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 1 min | Wed Feb 28 17:31:01 1996 PST
+ Wed Feb 28 17:32:01 1996 PST | @ 14 secs ago | Wed Feb 28 17:32:15 1996 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 5 hours | Thu Feb 29 12:32:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 1 day 2 hours 3 mins 4 secs | Thu Feb 29 15:28:57 1996 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 1 min | Thu Feb 29 17:31:01 1996 PST
+ Thu Feb 29 17:32:01 1996 PST | @ 14 secs ago | Thu Feb 29 17:32:15 1996 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 5 hours | Fri Mar 01 12:32:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 1 min | Fri Mar 01 17:31:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST | @ 14 secs ago | Fri Mar 01 17:32:15 1996 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 5 mons 12 hours | Tue Jul 30 05:32:01 1996 PDT
+ Mon Dec 30 17:32:01 1996 PST | @ 5 mons | Tue Jul 30 17:32:01 1996 PDT
+ Tue Dec 31 17:32:01 1996 PST | @ 5 mons 12 hours | Wed Jul 31 05:32:01 1996 PDT
+ Tue Dec 31 17:32:01 1996 PST | @ 5 mons | Wed Jul 31 17:32:01 1996 PDT
+ Mon Dec 30 17:32:01 1996 PST | @ 3 mons | Mon Sep 30 17:32:01 1996 PDT
+ Tue Dec 31 17:32:01 1996 PST | @ 3 mons | Mon Sep 30 17:32:01 1996 PDT
+ Mon Dec 30 17:32:01 1996 PST | @ 10 days | Fri Dec 20 17:32:01 1996 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 10 days | Sat Dec 21 17:32:01 1996 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 1 day 2 hours 3 mins 4 secs | Sun Dec 29 15:28:57 1996 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 5 hours | Mon Dec 30 12:32:01 1996 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 1 day 2 hours 3 mins 4 secs | Mon Dec 30 15:28:57 1996 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 1 min | Mon Dec 30 17:31:01 1996 PST
+ Mon Dec 30 17:32:01 1996 PST | @ 14 secs ago | Mon Dec 30 17:32:15 1996 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 5 hours | Tue Dec 31 12:32:01 1996 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 1 min | Tue Dec 31 17:31:01 1996 PST
+ Tue Dec 31 17:32:01 1996 PST | @ 14 secs ago | Tue Dec 31 17:32:15 1996 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 5 mons 12 hours | Sat Jul 31 05:32:01 1999 PDT
+ Fri Dec 31 17:32:01 1999 PST | @ 5 mons | Sat Jul 31 17:32:01 1999 PDT
+ Sat Jan 01 17:32:01 2000 PST | @ 5 mons 12 hours | Sun Aug 01 05:32:01 1999 PDT
+ Sat Jan 01 17:32:01 2000 PST | @ 5 mons | Sun Aug 01 17:32:01 1999 PDT
+ Fri Dec 31 17:32:01 1999 PST | @ 3 mons | Thu Sep 30 17:32:01 1999 PDT
+ Sat Jan 01 17:32:01 2000 PST | @ 3 mons | Fri Oct 01 17:32:01 1999 PDT
+ Wed Mar 15 02:14:05 2000 PST | @ 5 mons 12 hours | Thu Oct 14 14:14:05 1999 PDT
+ Wed Mar 15 03:14:04 2000 PST | @ 5 mons 12 hours | Thu Oct 14 15:14:04 1999 PDT
+ Wed Mar 15 08:14:01 2000 PST | @ 5 mons 12 hours | Thu Oct 14 20:14:01 1999 PDT
+ Wed Mar 15 12:14:03 2000 PST | @ 5 mons 12 hours | Fri Oct 15 00:14:03 1999 PDT
+ Wed Mar 15 13:14:02 2000 PST | @ 5 mons 12 hours | Fri Oct 15 01:14:02 1999 PDT
+ Wed Mar 15 02:14:05 2000 PST | @ 5 mons | Fri Oct 15 02:14:05 1999 PDT
+ Wed Mar 15 03:14:04 2000 PST | @ 5 mons | Fri Oct 15 03:14:04 1999 PDT
+ Wed Mar 15 08:14:01 2000 PST | @ 5 mons | Fri Oct 15 08:14:01 1999 PDT
+ Wed Mar 15 12:14:03 2000 PST | @ 5 mons | Fri Oct 15 12:14:03 1999 PDT
+ Wed Mar 15 13:14:02 2000 PST | @ 5 mons | Fri Oct 15 13:14:02 1999 PDT
+ Wed Mar 15 02:14:05 2000 PST | @ 3 mons | Wed Dec 15 02:14:05 1999 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 3 mons | Wed Dec 15 03:14:04 1999 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 3 mons | Wed Dec 15 08:14:01 1999 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 3 mons | Wed Dec 15 12:14:03 1999 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 3 mons | Wed Dec 15 13:14:02 1999 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 10 days | Tue Dec 21 17:32:01 1999 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 10 days | Wed Dec 22 17:32:01 1999 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 1 day 2 hours 3 mins 4 secs | Thu Dec 30 15:28:57 1999 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 5 hours | Fri Dec 31 12:32:01 1999 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Fri Dec 31 15:28:57 1999 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 1 min | Fri Dec 31 17:31:01 1999 PST
+ Fri Dec 31 17:32:01 1999 PST | @ 14 secs ago | Fri Dec 31 17:32:15 1999 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 5 hours | Sat Jan 01 12:32:01 2000 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 1 min | Sat Jan 01 17:31:01 2000 PST
+ Sat Jan 01 17:32:01 2000 PST | @ 14 secs ago | Sat Jan 01 17:32:15 2000 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 10 days | Sun Mar 05 02:14:05 2000 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 10 days | Sun Mar 05 03:14:04 2000 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 10 days | Sun Mar 05 08:14:01 2000 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 10 days | Sun Mar 05 12:14:03 2000 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 10 days | Sun Mar 05 13:14:02 2000 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Tue Mar 14 00:11:01 2000 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Tue Mar 14 01:11:00 2000 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Tue Mar 14 06:10:57 2000 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Tue Mar 14 10:10:59 2000 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Tue Mar 14 11:10:58 2000 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 5 hours | Tue Mar 14 21:14:05 2000 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 5 hours | Tue Mar 14 22:14:04 2000 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 1 min | Wed Mar 15 02:13:05 2000 PST
+ Wed Mar 15 02:14:05 2000 PST | @ 14 secs ago | Wed Mar 15 02:14:19 2000 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 1 min | Wed Mar 15 03:13:04 2000 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 5 hours | Wed Mar 15 03:14:01 2000 PST
+ Wed Mar 15 03:14:04 2000 PST | @ 14 secs ago | Wed Mar 15 03:14:18 2000 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 5 hours | Wed Mar 15 07:14:03 2000 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 1 min | Wed Mar 15 08:13:01 2000 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 5 hours | Wed Mar 15 08:14:02 2000 PST
+ Wed Mar 15 08:14:01 2000 PST | @ 14 secs ago | Wed Mar 15 08:14:15 2000 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 1 min | Wed Mar 15 12:13:03 2000 PST
+ Wed Mar 15 12:14:03 2000 PST | @ 14 secs ago | Wed Mar 15 12:14:17 2000 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 1 min | Wed Mar 15 13:13:02 2000 PST
+ Wed Mar 15 13:14:02 2000 PST | @ 14 secs ago | Wed Mar 15 13:14:16 2000 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 5 mons 12 hours | Mon Jul 31 05:32:01 2000 PDT
+ Sun Dec 31 17:32:01 2000 PST | @ 5 mons | Mon Jul 31 17:32:01 2000 PDT
+ Mon Jan 01 17:32:01 2001 PST | @ 5 mons 12 hours | Tue Aug 01 05:32:01 2000 PDT
+ Mon Jan 01 17:32:01 2001 PST | @ 5 mons | Tue Aug 01 17:32:01 2000 PDT
+ Sun Dec 31 17:32:01 2000 PST | @ 3 mons | Sat Sep 30 17:32:01 2000 PDT
+ Mon Jan 01 17:32:01 2001 PST | @ 3 mons | Sun Oct 01 17:32:01 2000 PDT
+ Sun Dec 31 17:32:01 2000 PST | @ 10 days | Thu Dec 21 17:32:01 2000 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 10 days | Fri Dec 22 17:32:01 2000 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 1 day 2 hours 3 mins 4 secs | Sat Dec 30 15:28:57 2000 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 5 hours | Sun Dec 31 12:32:01 2000 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 1 day 2 hours 3 mins 4 secs | Sun Dec 31 15:28:57 2000 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 1 min | Sun Dec 31 17:31:01 2000 PST
+ Sun Dec 31 17:32:01 2000 PST | @ 14 secs ago | Sun Dec 31 17:32:15 2000 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 5 hours | Mon Jan 01 12:32:01 2001 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 1 min | Mon Jan 01 17:31:01 2001 PST
+ Mon Jan 01 17:32:01 2001 PST | @ 14 secs ago | Mon Jan 01 17:32:15 2001 PST
+ Sat Sep 22 18:19:20 2001 PDT | @ 5 mons 12 hours | Sun Apr 22 06:19:20 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 5 mons | Sun Apr 22 18:19:20 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 3 mons | Fri Jun 22 18:19:20 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 10 days | Wed Sep 12 18:19:20 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 1 day 2 hours 3 mins 4 secs | Fri Sep 21 16:16:16 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
+ Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
+(160 rows)
+
+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;
+ timestamp | gpstime_zero | difference
+------------------------------+------------------------------+-------------------------------------
+ Thu Jan 01 00:00:00 1970 PST | Sat Jan 05 16:00:00 1980 PST | @ 3656 days 16 hours ago
+ Wed Feb 28 17:32:01 1996 PST | Sat Jan 05 16:00:00 1980 PST | @ 5898 days 1 hour 32 mins 1 sec
+ Thu Feb 29 17:32:01 1996 PST | Sat Jan 05 16:00:00 1980 PST | @ 5899 days 1 hour 32 mins 1 sec
+ Fri Mar 01 17:32:01 1996 PST | Sat Jan 05 16:00:00 1980 PST | @ 5900 days 1 hour 32 mins 1 sec
+ Mon Dec 30 17:32:01 1996 PST | Sat Jan 05 16:00:00 1980 PST | @ 6204 days 1 hour 32 mins 1 sec
+ Tue Dec 31 17:32:01 1996 PST | Sat Jan 05 16:00:00 1980 PST | @ 6205 days 1 hour 32 mins 1 sec
+ Fri Dec 31 17:32:01 1999 PST | Sat Jan 05 16:00:00 1980 PST | @ 7300 days 1 hour 32 mins 1 sec
+ Sat Jan 01 17:32:01 2000 PST | Sat Jan 05 16:00:00 1980 PST | @ 7301 days 1 hour 32 mins 1 sec
+ Wed Mar 15 02:14:05 2000 PST | Sat Jan 05 16:00:00 1980 PST | @ 7374 days 10 hours 14 mins 5 secs
+ Wed Mar 15 03:14:04 2000 PST | Sat Jan 05 16:00:00 1980 PST | @ 7374 days 11 hours 14 mins 4 secs
+ Wed Mar 15 08:14:01 2000 PST | Sat Jan 05 16:00:00 1980 PST | @ 7374 days 16 hours 14 mins 1 sec
+ Wed Mar 15 12:14:03 2000 PST | Sat Jan 05 16:00:00 1980 PST | @ 7374 days 20 hours 14 mins 3 secs
+ Wed Mar 15 13:14:02 2000 PST | Sat Jan 05 16:00:00 1980 PST | @ 7374 days 21 hours 14 mins 2 secs
+ Sun Dec 31 17:32:01 2000 PST | Sat Jan 05 16:00:00 1980 PST | @ 7666 days 1 hour 32 mins 1 sec
+ Mon Jan 01 17:32:01 2001 PST | Sat Jan 05 16:00:00 1980 PST | @ 7667 days 1 hour 32 mins 1 sec
+ Sat Sep 22 18:19:20 2001 PDT | Sat Jan 05 16:00:00 1980 PST | @ 7931 days 1 hour 19 mins 20 secs
+(16 rows)
+
+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;
+ timestamp1 | timestamp2 | difference
+------------------------------+------------------------------+-------------------------------------------
+ Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 PST | @ 0
+ Thu Jan 01 00:00:00 1970 PST | Wed Feb 28 17:32:01 1996 PST | @ 9554 days 17 hours 32 mins 1 sec ago
+ Thu Jan 01 00:00:00 1970 PST | Thu Feb 29 17:32:01 1996 PST | @ 9555 days 17 hours 32 mins 1 sec ago
+ Thu Jan 01 00:00:00 1970 PST | Fri Mar 01 17:32:01 1996 PST | @ 9556 days 17 hours 32 mins 1 sec ago
+ Thu Jan 01 00:00:00 1970 PST | Mon Dec 30 17:32:01 1996 PST | @ 9860 days 17 hours 32 mins 1 sec ago
+ Thu Jan 01 00:00:00 1970 PST | Tue Dec 31 17:32:01 1996 PST | @ 9861 days 17 hours 32 mins 1 sec ago
+ Thu Jan 01 00:00:00 1970 PST | Fri Dec 31 17:32:01 1999 PST | @ 10956 days 17 hours 32 mins 1 sec ago
+ Thu Jan 01 00:00:00 1970 PST | Sat Jan 01 17:32:01 2000 PST | @ 10957 days 17 hours 32 mins 1 sec ago
+ Thu Jan 01 00:00:00 1970 PST | Wed Mar 15 02:14:05 2000 PST | @ 11031 days 2 hours 14 mins 5 secs ago
+ Thu Jan 01 00:00:00 1970 PST | Wed Mar 15 03:14:04 2000 PST | @ 11031 days 3 hours 14 mins 4 secs ago
+ Thu Jan 01 00:00:00 1970 PST | Wed Mar 15 08:14:01 2000 PST | @ 11031 days 8 hours 14 mins 1 sec ago
+ Thu Jan 01 00:00:00 1970 PST | Wed Mar 15 12:14:03 2000 PST | @ 11031 days 12 hours 14 mins 3 secs ago
+ Thu Jan 01 00:00:00 1970 PST | Wed Mar 15 13:14:02 2000 PST | @ 11031 days 13 hours 14 mins 2 secs ago
+ Thu Jan 01 00:00:00 1970 PST | Sun Dec 31 17:32:01 2000 PST | @ 11322 days 17 hours 32 mins 1 sec ago
+ Thu Jan 01 00:00:00 1970 PST | Mon Jan 01 17:32:01 2001 PST | @ 11323 days 17 hours 32 mins 1 sec ago
+ Thu Jan 01 00:00:00 1970 PST | Sat Sep 22 18:19:20 2001 PDT | @ 11587 days 17 hours 19 mins 20 secs ago
+ Wed Feb 28 17:32:01 1996 PST | Thu Jan 01 00:00:00 1970 PST | @ 9554 days 17 hours 32 mins 1 sec
+ Wed Feb 28 17:32:01 1996 PST | Wed Feb 28 17:32:01 1996 PST | @ 0
+ Wed Feb 28 17:32:01 1996 PST | Thu Feb 29 17:32:01 1996 PST | @ 1 day ago
+ Wed Feb 28 17:32:01 1996 PST | Fri Mar 01 17:32:01 1996 PST | @ 2 days ago
+ Wed Feb 28 17:32:01 1996 PST | Mon Dec 30 17:32:01 1996 PST | @ 306 days ago
+ Wed Feb 28 17:32:01 1996 PST | Tue Dec 31 17:32:01 1996 PST | @ 307 days ago
+ Wed Feb 28 17:32:01 1996 PST | Fri Dec 31 17:32:01 1999 PST | @ 1402 days ago
+ Wed Feb 28 17:32:01 1996 PST | Sat Jan 01 17:32:01 2000 PST | @ 1403 days ago
+ Wed Feb 28 17:32:01 1996 PST | Wed Mar 15 02:14:05 2000 PST | @ 1476 days 8 hours 42 mins 4 secs ago
+ Wed Feb 28 17:32:01 1996 PST | Wed Mar 15 03:14:04 2000 PST | @ 1476 days 9 hours 42 mins 3 secs ago
+ Wed Feb 28 17:32:01 1996 PST | Wed Mar 15 08:14:01 2000 PST | @ 1476 days 14 hours 42 mins ago
+ Wed Feb 28 17:32:01 1996 PST | Wed Mar 15 12:14:03 2000 PST | @ 1476 days 18 hours 42 mins 2 secs ago
+ Wed Feb 28 17:32:01 1996 PST | Wed Mar 15 13:14:02 2000 PST | @ 1476 days 19 hours 42 mins 1 sec ago
+ Wed Feb 28 17:32:01 1996 PST | Sun Dec 31 17:32:01 2000 PST | @ 1768 days ago
+ Wed Feb 28 17:32:01 1996 PST | Mon Jan 01 17:32:01 2001 PST | @ 1769 days ago
+ Wed Feb 28 17:32:01 1996 PST | Sat Sep 22 18:19:20 2001 PDT | @ 2032 days 23 hours 47 mins 19 secs ago
+ Thu Feb 29 17:32:01 1996 PST | Thu Jan 01 00:00:00 1970 PST | @ 9555 days 17 hours 32 mins 1 sec
+ Thu Feb 29 17:32:01 1996 PST | Wed Feb 28 17:32:01 1996 PST | @ 1 day
+ Thu Feb 29 17:32:01 1996 PST | Thu Feb 29 17:32:01 1996 PST | @ 0
+ Thu Feb 29 17:32:01 1996 PST | Fri Mar 01 17:32:01 1996 PST | @ 1 day ago
+ Thu Feb 29 17:32:01 1996 PST | Mon Dec 30 17:32:01 1996 PST | @ 305 days ago
+ Thu Feb 29 17:32:01 1996 PST | Tue Dec 31 17:32:01 1996 PST | @ 306 days ago
+ Thu Feb 29 17:32:01 1996 PST | Fri Dec 31 17:32:01 1999 PST | @ 1401 days ago
+ Thu Feb 29 17:32:01 1996 PST | Sat Jan 01 17:32:01 2000 PST | @ 1402 days ago
+ Thu Feb 29 17:32:01 1996 PST | Wed Mar 15 02:14:05 2000 PST | @ 1475 days 8 hours 42 mins 4 secs ago
+ Thu Feb 29 17:32:01 1996 PST | Wed Mar 15 03:14:04 2000 PST | @ 1475 days 9 hours 42 mins 3 secs ago
+ Thu Feb 29 17:32:01 1996 PST | Wed Mar 15 08:14:01 2000 PST | @ 1475 days 14 hours 42 mins ago
+ Thu Feb 29 17:32:01 1996 PST | Wed Mar 15 12:14:03 2000 PST | @ 1475 days 18 hours 42 mins 2 secs ago
+ Thu Feb 29 17:32:01 1996 PST | Wed Mar 15 13:14:02 2000 PST | @ 1475 days 19 hours 42 mins 1 sec ago
+ Thu Feb 29 17:32:01 1996 PST | Sun Dec 31 17:32:01 2000 PST | @ 1767 days ago
+ Thu Feb 29 17:32:01 1996 PST | Mon Jan 01 17:32:01 2001 PST | @ 1768 days ago
+ Thu Feb 29 17:32:01 1996 PST | Sat Sep 22 18:19:20 2001 PDT | @ 2031 days 23 hours 47 mins 19 secs ago
+ Fri Mar 01 17:32:01 1996 PST | Thu Jan 01 00:00:00 1970 PST | @ 9556 days 17 hours 32 mins 1 sec
+ Fri Mar 01 17:32:01 1996 PST | Wed Feb 28 17:32:01 1996 PST | @ 2 days
+ Fri Mar 01 17:32:01 1996 PST | Thu Feb 29 17:32:01 1996 PST | @ 1 day
+ Fri Mar 01 17:32:01 1996 PST | Fri Mar 01 17:32:01 1996 PST | @ 0
+ Fri Mar 01 17:32:01 1996 PST | Mon Dec 30 17:32:01 1996 PST | @ 304 days ago
+ Fri Mar 01 17:32:01 1996 PST | Tue Dec 31 17:32:01 1996 PST | @ 305 days ago
+ Fri Mar 01 17:32:01 1996 PST | Fri Dec 31 17:32:01 1999 PST | @ 1400 days ago
+ Fri Mar 01 17:32:01 1996 PST | Sat Jan 01 17:32:01 2000 PST | @ 1401 days ago
+ Fri Mar 01 17:32:01 1996 PST | Wed Mar 15 02:14:05 2000 PST | @ 1474 days 8 hours 42 mins 4 secs ago
+ Fri Mar 01 17:32:01 1996 PST | Wed Mar 15 03:14:04 2000 PST | @ 1474 days 9 hours 42 mins 3 secs ago
+ Fri Mar 01 17:32:01 1996 PST | Wed Mar 15 08:14:01 2000 PST | @ 1474 days 14 hours 42 mins ago
+ Fri Mar 01 17:32:01 1996 PST | Wed Mar 15 12:14:03 2000 PST | @ 1474 days 18 hours 42 mins 2 secs ago
+ Fri Mar 01 17:32:01 1996 PST | Wed Mar 15 13:14:02 2000 PST | @ 1474 days 19 hours 42 mins 1 sec ago
+ Fri Mar 01 17:32:01 1996 PST | Sun Dec 31 17:32:01 2000 PST | @ 1766 days ago
+ Fri Mar 01 17:32:01 1996 PST | Mon Jan 01 17:32:01 2001 PST | @ 1767 days ago
+ Fri Mar 01 17:32:01 1996 PST | Sat Sep 22 18:19:20 2001 PDT | @ 2030 days 23 hours 47 mins 19 secs ago
+ Mon Dec 30 17:32:01 1996 PST | Thu Jan 01 00:00:00 1970 PST | @ 9860 days 17 hours 32 mins 1 sec
+ Mon Dec 30 17:32:01 1996 PST | Wed Feb 28 17:32:01 1996 PST | @ 306 days
+ Mon Dec 30 17:32:01 1996 PST | Thu Feb 29 17:32:01 1996 PST | @ 305 days
+ Mon Dec 30 17:32:01 1996 PST | Fri Mar 01 17:32:01 1996 PST | @ 304 days
+ Mon Dec 30 17:32:01 1996 PST | Mon Dec 30 17:32:01 1996 PST | @ 0
+ Mon Dec 30 17:32:01 1996 PST | Tue Dec 31 17:32:01 1996 PST | @ 1 day ago
+ Mon Dec 30 17:32:01 1996 PST | Fri Dec 31 17:32:01 1999 PST | @ 1096 days ago
+ Mon Dec 30 17:32:01 1996 PST | Sat Jan 01 17:32:01 2000 PST | @ 1097 days ago
+ Mon Dec 30 17:32:01 1996 PST | Wed Mar 15 02:14:05 2000 PST | @ 1170 days 8 hours 42 mins 4 secs ago
+ Mon Dec 30 17:32:01 1996 PST | Wed Mar 15 03:14:04 2000 PST | @ 1170 days 9 hours 42 mins 3 secs ago
+ Mon Dec 30 17:32:01 1996 PST | Wed Mar 15 08:14:01 2000 PST | @ 1170 days 14 hours 42 mins ago
+ Mon Dec 30 17:32:01 1996 PST | Wed Mar 15 12:14:03 2000 PST | @ 1170 days 18 hours 42 mins 2 secs ago
+ Mon Dec 30 17:32:01 1996 PST | Wed Mar 15 13:14:02 2000 PST | @ 1170 days 19 hours 42 mins 1 sec ago
+ Mon Dec 30 17:32:01 1996 PST | Sun Dec 31 17:32:01 2000 PST | @ 1462 days ago
+ Mon Dec 30 17:32:01 1996 PST | Mon Jan 01 17:32:01 2001 PST | @ 1463 days ago
+ Mon Dec 30 17:32:01 1996 PST | Sat Sep 22 18:19:20 2001 PDT | @ 1726 days 23 hours 47 mins 19 secs ago
+ Tue Dec 31 17:32:01 1996 PST | Thu Jan 01 00:00:00 1970 PST | @ 9861 days 17 hours 32 mins 1 sec
+ Tue Dec 31 17:32:01 1996 PST | Wed Feb 28 17:32:01 1996 PST | @ 307 days
+ Tue Dec 31 17:32:01 1996 PST | Thu Feb 29 17:32:01 1996 PST | @ 306 days
+ Tue Dec 31 17:32:01 1996 PST | Fri Mar 01 17:32:01 1996 PST | @ 305 days
+ Tue Dec 31 17:32:01 1996 PST | Mon Dec 30 17:32:01 1996 PST | @ 1 day
+ Tue Dec 31 17:32:01 1996 PST | Tue Dec 31 17:32:01 1996 PST | @ 0
+ Tue Dec 31 17:32:01 1996 PST | Fri Dec 31 17:32:01 1999 PST | @ 1095 days ago
+ Tue Dec 31 17:32:01 1996 PST | Sat Jan 01 17:32:01 2000 PST | @ 1096 days ago
+ Tue Dec 31 17:32:01 1996 PST | Wed Mar 15 02:14:05 2000 PST | @ 1169 days 8 hours 42 mins 4 secs ago
+ Tue Dec 31 17:32:01 1996 PST | Wed Mar 15 03:14:04 2000 PST | @ 1169 days 9 hours 42 mins 3 secs ago
+ Tue Dec 31 17:32:01 1996 PST | Wed Mar 15 08:14:01 2000 PST | @ 1169 days 14 hours 42 mins ago
+ Tue Dec 31 17:32:01 1996 PST | Wed Mar 15 12:14:03 2000 PST | @ 1169 days 18 hours 42 mins 2 secs ago
+ Tue Dec 31 17:32:01 1996 PST | Wed Mar 15 13:14:02 2000 PST | @ 1169 days 19 hours 42 mins 1 sec ago
+ Tue Dec 31 17:32:01 1996 PST | Sun Dec 31 17:32:01 2000 PST | @ 1461 days ago
+ Tue Dec 31 17:32:01 1996 PST | Mon Jan 01 17:32:01 2001 PST | @ 1462 days ago
+ Tue Dec 31 17:32:01 1996 PST | Sat Sep 22 18:19:20 2001 PDT | @ 1725 days 23 hours 47 mins 19 secs ago
+ Fri Dec 31 17:32:01 1999 PST | Thu Jan 01 00:00:00 1970 PST | @ 10956 days 17 hours 32 mins 1 sec
+ Fri Dec 31 17:32:01 1999 PST | Wed Feb 28 17:32:01 1996 PST | @ 1402 days
+ Fri Dec 31 17:32:01 1999 PST | Thu Feb 29 17:32:01 1996 PST | @ 1401 days
+ Fri Dec 31 17:32:01 1999 PST | Fri Mar 01 17:32:01 1996 PST | @ 1400 days
+ Fri Dec 31 17:32:01 1999 PST | Mon Dec 30 17:32:01 1996 PST | @ 1096 days
+ Fri Dec 31 17:32:01 1999 PST | Tue Dec 31 17:32:01 1996 PST | @ 1095 days
+ Fri Dec 31 17:32:01 1999 PST | Fri Dec 31 17:32:01 1999 PST | @ 0
+ Fri Dec 31 17:32:01 1999 PST | Sat Jan 01 17:32:01 2000 PST | @ 1 day ago
+ Fri Dec 31 17:32:01 1999 PST | Wed Mar 15 02:14:05 2000 PST | @ 74 days 8 hours 42 mins 4 secs ago
+ Fri Dec 31 17:32:01 1999 PST | Wed Mar 15 03:14:04 2000 PST | @ 74 days 9 hours 42 mins 3 secs ago
+ Fri Dec 31 17:32:01 1999 PST | Wed Mar 15 08:14:01 2000 PST | @ 74 days 14 hours 42 mins ago
+ Fri Dec 31 17:32:01 1999 PST | Wed Mar 15 12:14:03 2000 PST | @ 74 days 18 hours 42 mins 2 secs ago
+ Fri Dec 31 17:32:01 1999 PST | Wed Mar 15 13:14:02 2000 PST | @ 74 days 19 hours 42 mins 1 sec ago
+ Fri Dec 31 17:32:01 1999 PST | Sun Dec 31 17:32:01 2000 PST | @ 366 days ago
+ Fri Dec 31 17:32:01 1999 PST | Mon Jan 01 17:32:01 2001 PST | @ 367 days ago
+ Fri Dec 31 17:32:01 1999 PST | Sat Sep 22 18:19:20 2001 PDT | @ 630 days 23 hours 47 mins 19 secs ago
+ Sat Jan 01 17:32:01 2000 PST | Thu Jan 01 00:00:00 1970 PST | @ 10957 days 17 hours 32 mins 1 sec
+ Sat Jan 01 17:32:01 2000 PST | Wed Feb 28 17:32:01 1996 PST | @ 1403 days
+ Sat Jan 01 17:32:01 2000 PST | Thu Feb 29 17:32:01 1996 PST | @ 1402 days
+ Sat Jan 01 17:32:01 2000 PST | Fri Mar 01 17:32:01 1996 PST | @ 1401 days
+ Sat Jan 01 17:32:01 2000 PST | Mon Dec 30 17:32:01 1996 PST | @ 1097 days
+ Sat Jan 01 17:32:01 2000 PST | Tue Dec 31 17:32:01 1996 PST | @ 1096 days
+ Sat Jan 01 17:32:01 2000 PST | Fri Dec 31 17:32:01 1999 PST | @ 1 day
+ Sat Jan 01 17:32:01 2000 PST | Sat Jan 01 17:32:01 2000 PST | @ 0
+ Sat Jan 01 17:32:01 2000 PST | Wed Mar 15 02:14:05 2000 PST | @ 73 days 8 hours 42 mins 4 secs ago
+ Sat Jan 01 17:32:01 2000 PST | Wed Mar 15 03:14:04 2000 PST | @ 73 days 9 hours 42 mins 3 secs ago
+ Sat Jan 01 17:32:01 2000 PST | Wed Mar 15 08:14:01 2000 PST | @ 73 days 14 hours 42 mins ago
+ Sat Jan 01 17:32:01 2000 PST | Wed Mar 15 12:14:03 2000 PST | @ 73 days 18 hours 42 mins 2 secs ago
+ Sat Jan 01 17:32:01 2000 PST | Wed Mar 15 13:14:02 2000 PST | @ 73 days 19 hours 42 mins 1 sec ago
+ Sat Jan 01 17:32:01 2000 PST | Sun Dec 31 17:32:01 2000 PST | @ 365 days ago
+ Sat Jan 01 17:32:01 2000 PST | Mon Jan 01 17:32:01 2001 PST | @ 366 days ago
+ Sat Jan 01 17:32:01 2000 PST | Sat Sep 22 18:19:20 2001 PDT | @ 629 days 23 hours 47 mins 19 secs ago
+ Wed Mar 15 02:14:05 2000 PST | Thu Jan 01 00:00:00 1970 PST | @ 11031 days 2 hours 14 mins 5 secs
+ Wed Mar 15 02:14:05 2000 PST | Wed Feb 28 17:32:01 1996 PST | @ 1476 days 8 hours 42 mins 4 secs
+ Wed Mar 15 02:14:05 2000 PST | Thu Feb 29 17:32:01 1996 PST | @ 1475 days 8 hours 42 mins 4 secs
+ Wed Mar 15 02:14:05 2000 PST | Fri Mar 01 17:32:01 1996 PST | @ 1474 days 8 hours 42 mins 4 secs
+ Wed Mar 15 02:14:05 2000 PST | Mon Dec 30 17:32:01 1996 PST | @ 1170 days 8 hours 42 mins 4 secs
+ Wed Mar 15 02:14:05 2000 PST | Tue Dec 31 17:32:01 1996 PST | @ 1169 days 8 hours 42 mins 4 secs
+ Wed Mar 15 02:14:05 2000 PST | Fri Dec 31 17:32:01 1999 PST | @ 74 days 8 hours 42 mins 4 secs
+ Wed Mar 15 02:14:05 2000 PST | Sat Jan 01 17:32:01 2000 PST | @ 73 days 8 hours 42 mins 4 secs
+ Wed Mar 15 02:14:05 2000 PST | Wed Mar 15 02:14:05 2000 PST | @ 0
+ Wed Mar 15 02:14:05 2000 PST | Wed Mar 15 03:14:04 2000 PST | @ 59 mins 59 secs ago
+ Wed Mar 15 02:14:05 2000 PST | Wed Mar 15 08:14:01 2000 PST | @ 5 hours 59 mins 56 secs ago
+ Wed Mar 15 02:14:05 2000 PST | Wed Mar 15 12:14:03 2000 PST | @ 9 hours 59 mins 58 secs ago
+ Wed Mar 15 02:14:05 2000 PST | Wed Mar 15 13:14:02 2000 PST | @ 10 hours 59 mins 57 secs ago
+ Wed Mar 15 02:14:05 2000 PST | Sun Dec 31 17:32:01 2000 PST | @ 291 days 15 hours 17 mins 56 secs ago
+ Wed Mar 15 02:14:05 2000 PST | Mon Jan 01 17:32:01 2001 PST | @ 292 days 15 hours 17 mins 56 secs ago
+ Wed Mar 15 02:14:05 2000 PST | Sat Sep 22 18:19:20 2001 PDT | @ 556 days 15 hours 5 mins 15 secs ago
+ Wed Mar 15 03:14:04 2000 PST | Thu Jan 01 00:00:00 1970 PST | @ 11031 days 3 hours 14 mins 4 secs
+ Wed Mar 15 03:14:04 2000 PST | Wed Feb 28 17:32:01 1996 PST | @ 1476 days 9 hours 42 mins 3 secs
+ Wed Mar 15 03:14:04 2000 PST | Thu Feb 29 17:32:01 1996 PST | @ 1475 days 9 hours 42 mins 3 secs
+ Wed Mar 15 03:14:04 2000 PST | Fri Mar 01 17:32:01 1996 PST | @ 1474 days 9 hours 42 mins 3 secs
+ Wed Mar 15 03:14:04 2000 PST | Mon Dec 30 17:32:01 1996 PST | @ 1170 days 9 hours 42 mins 3 secs
+ Wed Mar 15 03:14:04 2000 PST | Tue Dec 31 17:32:01 1996 PST | @ 1169 days 9 hours 42 mins 3 secs
+ Wed Mar 15 03:14:04 2000 PST | Fri Dec 31 17:32:01 1999 PST | @ 74 days 9 hours 42 mins 3 secs
+ Wed Mar 15 03:14:04 2000 PST | Sat Jan 01 17:32:01 2000 PST | @ 73 days 9 hours 42 mins 3 secs
+ Wed Mar 15 03:14:04 2000 PST | Wed Mar 15 02:14:05 2000 PST | @ 59 mins 59 secs
+ Wed Mar 15 03:14:04 2000 PST | Wed Mar 15 03:14:04 2000 PST | @ 0
+ Wed Mar 15 03:14:04 2000 PST | Wed Mar 15 08:14:01 2000 PST | @ 4 hours 59 mins 57 secs ago
+ Wed Mar 15 03:14:04 2000 PST | Wed Mar 15 12:14:03 2000 PST | @ 8 hours 59 mins 59 secs ago
+ Wed Mar 15 03:14:04 2000 PST | Wed Mar 15 13:14:02 2000 PST | @ 9 hours 59 mins 58 secs ago
+ Wed Mar 15 03:14:04 2000 PST | Sun Dec 31 17:32:01 2000 PST | @ 291 days 14 hours 17 mins 57 secs ago
+ Wed Mar 15 03:14:04 2000 PST | Mon Jan 01 17:32:01 2001 PST | @ 292 days 14 hours 17 mins 57 secs ago
+ Wed Mar 15 03:14:04 2000 PST | Sat Sep 22 18:19:20 2001 PDT | @ 556 days 14 hours 5 mins 16 secs ago
+ Wed Mar 15 08:14:01 2000 PST | Thu Jan 01 00:00:00 1970 PST | @ 11031 days 8 hours 14 mins 1 sec
+ Wed Mar 15 08:14:01 2000 PST | Wed Feb 28 17:32:01 1996 PST | @ 1476 days 14 hours 42 mins
+ Wed Mar 15 08:14:01 2000 PST | Thu Feb 29 17:32:01 1996 PST | @ 1475 days 14 hours 42 mins
+ Wed Mar 15 08:14:01 2000 PST | Fri Mar 01 17:32:01 1996 PST | @ 1474 days 14 hours 42 mins
+ Wed Mar 15 08:14:01 2000 PST | Mon Dec 30 17:32:01 1996 PST | @ 1170 days 14 hours 42 mins
+ Wed Mar 15 08:14:01 2000 PST | Tue Dec 31 17:32:01 1996 PST | @ 1169 days 14 hours 42 mins
+ Wed Mar 15 08:14:01 2000 PST | Fri Dec 31 17:32:01 1999 PST | @ 74 days 14 hours 42 mins
+ Wed Mar 15 08:14:01 2000 PST | Sat Jan 01 17:32:01 2000 PST | @ 73 days 14 hours 42 mins
+ Wed Mar 15 08:14:01 2000 PST | Wed Mar 15 02:14:05 2000 PST | @ 5 hours 59 mins 56 secs
+ Wed Mar 15 08:14:01 2000 PST | Wed Mar 15 03:14:04 2000 PST | @ 4 hours 59 mins 57 secs
+ Wed Mar 15 08:14:01 2000 PST | Wed Mar 15 08:14:01 2000 PST | @ 0
+ Wed Mar 15 08:14:01 2000 PST | Wed Mar 15 12:14:03 2000 PST | @ 4 hours 2 secs ago
+ Wed Mar 15 08:14:01 2000 PST | Wed Mar 15 13:14:02 2000 PST | @ 5 hours 1 sec ago
+ Wed Mar 15 08:14:01 2000 PST | Sun Dec 31 17:32:01 2000 PST | @ 291 days 9 hours 18 mins ago
+ Wed Mar 15 08:14:01 2000 PST | Mon Jan 01 17:32:01 2001 PST | @ 292 days 9 hours 18 mins ago
+ Wed Mar 15 08:14:01 2000 PST | Sat Sep 22 18:19:20 2001 PDT | @ 556 days 9 hours 5 mins 19 secs ago
+ Wed Mar 15 12:14:03 2000 PST | Thu Jan 01 00:00:00 1970 PST | @ 11031 days 12 hours 14 mins 3 secs
+ Wed Mar 15 12:14:03 2000 PST | Wed Feb 28 17:32:01 1996 PST | @ 1476 days 18 hours 42 mins 2 secs
+ Wed Mar 15 12:14:03 2000 PST | Thu Feb 29 17:32:01 1996 PST | @ 1475 days 18 hours 42 mins 2 secs
+ Wed Mar 15 12:14:03 2000 PST | Fri Mar 01 17:32:01 1996 PST | @ 1474 days 18 hours 42 mins 2 secs
+ Wed Mar 15 12:14:03 2000 PST | Mon Dec 30 17:32:01 1996 PST | @ 1170 days 18 hours 42 mins 2 secs
+ Wed Mar 15 12:14:03 2000 PST | Tue Dec 31 17:32:01 1996 PST | @ 1169 days 18 hours 42 mins 2 secs
+ Wed Mar 15 12:14:03 2000 PST | Fri Dec 31 17:32:01 1999 PST | @ 74 days 18 hours 42 mins 2 secs
+ Wed Mar 15 12:14:03 2000 PST | Sat Jan 01 17:32:01 2000 PST | @ 73 days 18 hours 42 mins 2 secs
+ Wed Mar 15 12:14:03 2000 PST | Wed Mar 15 02:14:05 2000 PST | @ 9 hours 59 mins 58 secs
+ Wed Mar 15 12:14:03 2000 PST | Wed Mar 15 03:14:04 2000 PST | @ 8 hours 59 mins 59 secs
+ Wed Mar 15 12:14:03 2000 PST | Wed Mar 15 08:14:01 2000 PST | @ 4 hours 2 secs
+ Wed Mar 15 12:14:03 2000 PST | Wed Mar 15 12:14:03 2000 PST | @ 0
+ Wed Mar 15 12:14:03 2000 PST | Wed Mar 15 13:14:02 2000 PST | @ 59 mins 59 secs ago
+ Wed Mar 15 12:14:03 2000 PST | Sun Dec 31 17:32:01 2000 PST | @ 291 days 5 hours 17 mins 58 secs ago
+ Wed Mar 15 12:14:03 2000 PST | Mon Jan 01 17:32:01 2001 PST | @ 292 days 5 hours 17 mins 58 secs ago
+ Wed Mar 15 12:14:03 2000 PST | Sat Sep 22 18:19:20 2001 PDT | @ 556 days 5 hours 5 mins 17 secs ago
+ Wed Mar 15 13:14:02 2000 PST | Thu Jan 01 00:00:00 1970 PST | @ 11031 days 13 hours 14 mins 2 secs
+ Wed Mar 15 13:14:02 2000 PST | Wed Feb 28 17:32:01 1996 PST | @ 1476 days 19 hours 42 mins 1 sec
+ Wed Mar 15 13:14:02 2000 PST | Thu Feb 29 17:32:01 1996 PST | @ 1475 days 19 hours 42 mins 1 sec
+ Wed Mar 15 13:14:02 2000 PST | Fri Mar 01 17:32:01 1996 PST | @ 1474 days 19 hours 42 mins 1 sec
+ Wed Mar 15 13:14:02 2000 PST | Mon Dec 30 17:32:01 1996 PST | @ 1170 days 19 hours 42 mins 1 sec
+ Wed Mar 15 13:14:02 2000 PST | Tue Dec 31 17:32:01 1996 PST | @ 1169 days 19 hours 42 mins 1 sec
+ Wed Mar 15 13:14:02 2000 PST | Fri Dec 31 17:32:01 1999 PST | @ 74 days 19 hours 42 mins 1 sec
+ Wed Mar 15 13:14:02 2000 PST | Sat Jan 01 17:32:01 2000 PST | @ 73 days 19 hours 42 mins 1 sec
+ Wed Mar 15 13:14:02 2000 PST | Wed Mar 15 02:14:05 2000 PST | @ 10 hours 59 mins 57 secs
+ Wed Mar 15 13:14:02 2000 PST | Wed Mar 15 03:14:04 2000 PST | @ 9 hours 59 mins 58 secs
+ Wed Mar 15 13:14:02 2000 PST | Wed Mar 15 08:14:01 2000 PST | @ 5 hours 1 sec
+ Wed Mar 15 13:14:02 2000 PST | Wed Mar 15 12:14:03 2000 PST | @ 59 mins 59 secs
+ Wed Mar 15 13:14:02 2000 PST | Wed Mar 15 13:14:02 2000 PST | @ 0
+ Wed Mar 15 13:14:02 2000 PST | Sun Dec 31 17:32:01 2000 PST | @ 291 days 4 hours 17 mins 59 secs ago
+ Wed Mar 15 13:14:02 2000 PST | Mon Jan 01 17:32:01 2001 PST | @ 292 days 4 hours 17 mins 59 secs ago
+ Wed Mar 15 13:14:02 2000 PST | Sat Sep 22 18:19:20 2001 PDT | @ 556 days 4 hours 5 mins 18 secs ago
+ Sun Dec 31 17:32:01 2000 PST | Thu Jan 01 00:00:00 1970 PST | @ 11322 days 17 hours 32 mins 1 sec
+ Sun Dec 31 17:32:01 2000 PST | Wed Feb 28 17:32:01 1996 PST | @ 1768 days
+ Sun Dec 31 17:32:01 2000 PST | Thu Feb 29 17:32:01 1996 PST | @ 1767 days
+ Sun Dec 31 17:32:01 2000 PST | Fri Mar 01 17:32:01 1996 PST | @ 1766 days
+ Sun Dec 31 17:32:01 2000 PST | Mon Dec 30 17:32:01 1996 PST | @ 1462 days
+ Sun Dec 31 17:32:01 2000 PST | Tue Dec 31 17:32:01 1996 PST | @ 1461 days
+ Sun Dec 31 17:32:01 2000 PST | Fri Dec 31 17:32:01 1999 PST | @ 366 days
+ Sun Dec 31 17:32:01 2000 PST | Sat Jan 01 17:32:01 2000 PST | @ 365 days
+ Sun Dec 31 17:32:01 2000 PST | Wed Mar 15 02:14:05 2000 PST | @ 291 days 15 hours 17 mins 56 secs
+ Sun Dec 31 17:32:01 2000 PST | Wed Mar 15 03:14:04 2000 PST | @ 291 days 14 hours 17 mins 57 secs
+ Sun Dec 31 17:32:01 2000 PST | Wed Mar 15 08:14:01 2000 PST | @ 291 days 9 hours 18 mins
+ Sun Dec 31 17:32:01 2000 PST | Wed Mar 15 12:14:03 2000 PST | @ 291 days 5 hours 17 mins 58 secs
+ Sun Dec 31 17:32:01 2000 PST | Wed Mar 15 13:14:02 2000 PST | @ 291 days 4 hours 17 mins 59 secs
+ Sun Dec 31 17:32:01 2000 PST | Sun Dec 31 17:32:01 2000 PST | @ 0
+ Sun Dec 31 17:32:01 2000 PST | Mon Jan 01 17:32:01 2001 PST | @ 1 day ago
+ Sun Dec 31 17:32:01 2000 PST | Sat Sep 22 18:19:20 2001 PDT | @ 264 days 23 hours 47 mins 19 secs ago
+ Mon Jan 01 17:32:01 2001 PST | Thu Jan 01 00:00:00 1970 PST | @ 11323 days 17 hours 32 mins 1 sec
+ Mon Jan 01 17:32:01 2001 PST | Wed Feb 28 17:32:01 1996 PST | @ 1769 days
+ Mon Jan 01 17:32:01 2001 PST | Thu Feb 29 17:32:01 1996 PST | @ 1768 days
+ Mon Jan 01 17:32:01 2001 PST | Fri Mar 01 17:32:01 1996 PST | @ 1767 days
+ Mon Jan 01 17:32:01 2001 PST | Mon Dec 30 17:32:01 1996 PST | @ 1463 days
+ Mon Jan 01 17:32:01 2001 PST | Tue Dec 31 17:32:01 1996 PST | @ 1462 days
+ Mon Jan 01 17:32:01 2001 PST | Fri Dec 31 17:32:01 1999 PST | @ 367 days
+ Mon Jan 01 17:32:01 2001 PST | Sat Jan 01 17:32:01 2000 PST | @ 366 days
+ Mon Jan 01 17:32:01 2001 PST | Wed Mar 15 02:14:05 2000 PST | @ 292 days 15 hours 17 mins 56 secs
+ Mon Jan 01 17:32:01 2001 PST | Wed Mar 15 03:14:04 2000 PST | @ 292 days 14 hours 17 mins 57 secs
+ Mon Jan 01 17:32:01 2001 PST | Wed Mar 15 08:14:01 2000 PST | @ 292 days 9 hours 18 mins
+ Mon Jan 01 17:32:01 2001 PST | Wed Mar 15 12:14:03 2000 PST | @ 292 days 5 hours 17 mins 58 secs
+ Mon Jan 01 17:32:01 2001 PST | Wed Mar 15 13:14:02 2000 PST | @ 292 days 4 hours 17 mins 59 secs
+ Mon Jan 01 17:32:01 2001 PST | Sun Dec 31 17:32:01 2000 PST | @ 1 day
+ Mon Jan 01 17:32:01 2001 PST | Mon Jan 01 17:32:01 2001 PST | @ 0
+ Mon Jan 01 17:32:01 2001 PST | Sat Sep 22 18:19:20 2001 PDT | @ 263 days 23 hours 47 mins 19 secs ago
+ Sat Sep 22 18:19:20 2001 PDT | Thu Jan 01 00:00:00 1970 PST | @ 11587 days 17 hours 19 mins 20 secs
+ Sat Sep 22 18:19:20 2001 PDT | Wed Feb 28 17:32:01 1996 PST | @ 2032 days 23 hours 47 mins 19 secs
+ Sat Sep 22 18:19:20 2001 PDT | Thu Feb 29 17:32:01 1996 PST | @ 2031 days 23 hours 47 mins 19 secs
+ Sat Sep 22 18:19:20 2001 PDT | Fri Mar 01 17:32:01 1996 PST | @ 2030 days 23 hours 47 mins 19 secs
+ Sat Sep 22 18:19:20 2001 PDT | Mon Dec 30 17:32:01 1996 PST | @ 1726 days 23 hours 47 mins 19 secs
+ Sat Sep 22 18:19:20 2001 PDT | Tue Dec 31 17:32:01 1996 PST | @ 1725 days 23 hours 47 mins 19 secs
+ Sat Sep 22 18:19:20 2001 PDT | Fri Dec 31 17:32:01 1999 PST | @ 630 days 23 hours 47 mins 19 secs
+ Sat Sep 22 18:19:20 2001 PDT | Sat Jan 01 17:32:01 2000 PST | @ 629 days 23 hours 47 mins 19 secs
+ Sat Sep 22 18:19:20 2001 PDT | Wed Mar 15 02:14:05 2000 PST | @ 556 days 15 hours 5 mins 15 secs
+ Sat Sep 22 18:19:20 2001 PDT | Wed Mar 15 03:14:04 2000 PST | @ 556 days 14 hours 5 mins 16 secs
+ Sat Sep 22 18:19:20 2001 PDT | Wed Mar 15 08:14:01 2000 PST | @ 556 days 9 hours 5 mins 19 secs
+ Sat Sep 22 18:19:20 2001 PDT | Wed Mar 15 12:14:03 2000 PST | @ 556 days 5 hours 5 mins 17 secs
+ Sat Sep 22 18:19:20 2001 PDT | Wed Mar 15 13:14:02 2000 PST | @ 556 days 4 hours 5 mins 18 secs
+ Sat Sep 22 18:19:20 2001 PDT | Sun Dec 31 17:32:01 2000 PST | @ 264 days 23 hours 47 mins 19 secs
+ Sat Sep 22 18:19:20 2001 PDT | Mon Jan 01 17:32:01 2001 PST | @ 263 days 23 hours 47 mins 19 secs
+ Sat Sep 22 18:19:20 2001 PDT | Sat Sep 22 18:19:20 2001 PDT | @ 0
+(256 rows)
+
+--
+-- Conversions
+--
+SELECT f1 AS "timestamp", date(f1) AS date
+ FROM TEMP_TIMESTAMP
+ WHERE f1 <> timestamp 'now'
+ ORDER BY date, "timestamp";
+ timestamp | date
+------------------------------+------------
+ Thu Jan 01 00:00:00 1970 PST | 01-01-1970
+ Wed Feb 28 17:32:01 1996 PST | 02-28-1996
+ Thu Feb 29 17:32:01 1996 PST | 02-29-1996
+ Fri Mar 01 17:32:01 1996 PST | 03-01-1996
+ Mon Dec 30 17:32:01 1996 PST | 12-30-1996
+ Tue Dec 31 17:32:01 1996 PST | 12-31-1996
+ Fri Dec 31 17:32:01 1999 PST | 12-31-1999
+ Sat Jan 01 17:32:01 2000 PST | 01-01-2000
+ Wed Mar 15 02:14:05 2000 PST | 03-15-2000
+ Wed Mar 15 03:14:04 2000 PST | 03-15-2000
+ Wed Mar 15 08:14:01 2000 PST | 03-15-2000
+ Wed Mar 15 12:14:03 2000 PST | 03-15-2000
+ Wed Mar 15 13:14:02 2000 PST | 03-15-2000
+ Sun Dec 31 17:32:01 2000 PST | 12-31-2000
+ Mon Jan 01 17:32:01 2001 PST | 01-01-2001
+ Sat Sep 22 18:19:20 2001 PDT | 09-22-2001
+(16 rows)
+
+DROP TABLE TEMP_TIMESTAMP;
+--
+-- Comparisons between datetime types, especially overflow cases
+---
+SELECT '2202020-10-05'::date::timestamp; -- fail
+ERROR: date out of range for timestamp
+SELECT '2202020-10-05'::date > '2020-10-05'::timestamp as t;
+ t
+---
+ t
+(1 row)
+
+SELECT '2020-10-05'::timestamp > '2202020-10-05'::date as f;
+ f
+---
+ f
+(1 row)
+
+SELECT '2202020-10-05'::date::timestamptz; -- fail
+ERROR: date out of range for timestamp
+SELECT '2202020-10-05'::date > '2020-10-05'::timestamptz as t;
+ t
+---
+ t
+(1 row)
+
+SELECT '2020-10-05'::timestamptz > '2202020-10-05'::date as f;
+ f
+---
+ f
+(1 row)
+
+-- This conversion may work depending on timezone
+SELECT '4714-11-24 BC'::date::timestamptz;
+ timestamptz
+---------------------------------
+ Mon Nov 24 00:00:00 4714 PST BC
+(1 row)
+
+SET TimeZone = 'UTC-2';
+SELECT '4714-11-24 BC'::date::timestamptz; -- fail
+ERROR: date out of range for timestamp
+SELECT '4714-11-24 BC'::date < '2020-10-05'::timestamptz as t;
+ t
+---
+ t
+(1 row)
+
+SELECT '2020-10-05'::timestamptz >= '4714-11-24 BC'::date as t;
+ t
+---
+ t
+(1 row)
+
+SELECT '4714-11-24 BC'::timestamp < '2020-10-05'::timestamptz as t;
+ t
+---
+ t
+(1 row)
+
+SELECT '2020-10-05'::timestamptz >= '4714-11-24 BC'::timestamp as t;
+ t
+---
+ t
+(1 row)
+
+RESET TimeZone;
+--
+-- Formats
+--
+SET DateStyle TO 'US,Postgres';
+SHOW DateStyle;
+ DateStyle
+---------------
+ Postgres, MDY
+(1 row)
+
+SELECT d1 AS us_postgres FROM TIMESTAMP_TBL;
+ us_postgres
+-----------------------------
+ -infinity
+ infinity
+ Thu Jan 01 00:00:00 1970
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:02 1997
+ Mon Feb 10 17:32:01.4 1997
+ Mon Feb 10 17:32:01.5 1997
+ Mon Feb 10 17:32:01.6 1997
+ Thu Jan 02 00:00:00 1997
+ Thu Jan 02 03:04:05 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Jun 10 17:32:01 1997
+ Sat Sep 22 18:19:20 2001
+ Wed Mar 15 08:14:01 2000
+ Wed Mar 15 13:14:02 2000
+ Wed Mar 15 12:14:03 2000
+ Wed Mar 15 03:14:04 2000
+ Wed Mar 15 02:14:05 2000
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:00 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Jun 10 18:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Feb 11 17:32:01 1997
+ Wed Feb 12 17:32:01 1997
+ Thu Feb 13 17:32:01 1997
+ Fri Feb 14 17:32:01 1997
+ Sat Feb 15 17:32:01 1997
+ Sun Feb 16 17:32:01 1997
+ Tue Feb 16 17:32:01 0097 BC
+ Sat Feb 16 17:32:01 0097
+ Thu Feb 16 17:32:01 0597
+ Tue Feb 16 17:32:01 1097
+ Sat Feb 16 17:32:01 1697
+ Thu Feb 16 17:32:01 1797
+ Tue Feb 16 17:32:01 1897
+ Sun Feb 16 17:32:01 1997
+ Sat Feb 16 17:32:01 2097
+ Wed Feb 28 17:32:01 1996
+ Thu Feb 29 17:32:01 1996
+ Fri Mar 01 17:32:01 1996
+ Mon Dec 30 17:32:01 1996
+ Tue Dec 31 17:32:01 1996
+ Wed Jan 01 17:32:01 1997
+ Fri Feb 28 17:32:01 1997
+ Sat Mar 01 17:32:01 1997
+ Tue Dec 30 17:32:01 1997
+ Wed Dec 31 17:32:01 1997
+ Fri Dec 31 17:32:01 1999
+ Sat Jan 01 17:32:01 2000
+ Sun Dec 31 17:32:01 2000
+ Mon Jan 01 17:32:01 2001
+(65 rows)
+
+SET DateStyle TO 'US,ISO';
+SELECT d1 AS us_iso FROM TIMESTAMP_TBL;
+ us_iso
+------------------------
+ -infinity
+ infinity
+ 1970-01-01 00:00:00
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:02
+ 1997-02-10 17:32:01.4
+ 1997-02-10 17:32:01.5
+ 1997-02-10 17:32:01.6
+ 1997-01-02 00:00:00
+ 1997-01-02 03:04:05
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-06-10 17:32:01
+ 2001-09-22 18:19:20
+ 2000-03-15 08:14:01
+ 2000-03-15 13:14:02
+ 2000-03-15 12:14:03
+ 2000-03-15 03:14:04
+ 2000-03-15 02:14:05
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:00
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-06-10 18:32:01
+ 1997-02-10 17:32:01
+ 1997-02-11 17:32:01
+ 1997-02-12 17:32:01
+ 1997-02-13 17:32:01
+ 1997-02-14 17:32:01
+ 1997-02-15 17:32:01
+ 1997-02-16 17:32:01
+ 0097-02-16 17:32:01 BC
+ 0097-02-16 17:32:01
+ 0597-02-16 17:32:01
+ 1097-02-16 17:32:01
+ 1697-02-16 17:32:01
+ 1797-02-16 17:32:01
+ 1897-02-16 17:32:01
+ 1997-02-16 17:32:01
+ 2097-02-16 17:32:01
+ 1996-02-28 17:32:01
+ 1996-02-29 17:32:01
+ 1996-03-01 17:32:01
+ 1996-12-30 17:32:01
+ 1996-12-31 17:32:01
+ 1997-01-01 17:32:01
+ 1997-02-28 17:32:01
+ 1997-03-01 17:32:01
+ 1997-12-30 17:32:01
+ 1997-12-31 17:32:01
+ 1999-12-31 17:32:01
+ 2000-01-01 17:32:01
+ 2000-12-31 17:32:01
+ 2001-01-01 17:32:01
+(65 rows)
+
+SET DateStyle TO 'US,SQL';
+SHOW DateStyle;
+ DateStyle
+-----------
+ SQL, MDY
+(1 row)
+
+SELECT d1 AS us_sql FROM TIMESTAMP_TBL;
+ us_sql
+------------------------
+ -infinity
+ infinity
+ 01/01/1970 00:00:00
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:02
+ 02/10/1997 17:32:01.4
+ 02/10/1997 17:32:01.5
+ 02/10/1997 17:32:01.6
+ 01/02/1997 00:00:00
+ 01/02/1997 03:04:05
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 06/10/1997 17:32:01
+ 09/22/2001 18:19:20
+ 03/15/2000 08:14:01
+ 03/15/2000 13:14:02
+ 03/15/2000 12:14:03
+ 03/15/2000 03:14:04
+ 03/15/2000 02:14:05
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:00
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 02/10/1997 17:32:01
+ 06/10/1997 18:32:01
+ 02/10/1997 17:32:01
+ 02/11/1997 17:32:01
+ 02/12/1997 17:32:01
+ 02/13/1997 17:32:01
+ 02/14/1997 17:32:01
+ 02/15/1997 17:32:01
+ 02/16/1997 17:32:01
+ 02/16/0097 17:32:01 BC
+ 02/16/0097 17:32:01
+ 02/16/0597 17:32:01
+ 02/16/1097 17:32:01
+ 02/16/1697 17:32:01
+ 02/16/1797 17:32:01
+ 02/16/1897 17:32:01
+ 02/16/1997 17:32:01
+ 02/16/2097 17:32:01
+ 02/28/1996 17:32:01
+ 02/29/1996 17:32:01
+ 03/01/1996 17:32:01
+ 12/30/1996 17:32:01
+ 12/31/1996 17:32:01
+ 01/01/1997 17:32:01
+ 02/28/1997 17:32:01
+ 03/01/1997 17:32:01
+ 12/30/1997 17:32:01
+ 12/31/1997 17:32:01
+ 12/31/1999 17:32:01
+ 01/01/2000 17:32:01
+ 12/31/2000 17:32:01
+ 01/01/2001 17:32:01
+(65 rows)
+
+SET DateStyle TO 'European,Postgres';
+SHOW DateStyle;
+ DateStyle
+---------------
+ Postgres, DMY
+(1 row)
+
+INSERT INTO TIMESTAMP_TBL VALUES('13/06/1957');
+SELECT count(*) as one FROM TIMESTAMP_TBL WHERE d1 = 'Jun 13 1957';
+ one
+-----
+ 1
+(1 row)
+
+SELECT d1 AS european_postgres FROM TIMESTAMP_TBL;
+ european_postgres
+-----------------------------
+ -infinity
+ infinity
+ Thu 01 Jan 00:00:00 1970
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:02 1997
+ Mon 10 Feb 17:32:01.4 1997
+ Mon 10 Feb 17:32:01.5 1997
+ Mon 10 Feb 17:32:01.6 1997
+ Thu 02 Jan 00:00:00 1997
+ Thu 02 Jan 03:04:05 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Tue 10 Jun 17:32:01 1997
+ Sat 22 Sep 18:19:20 2001
+ Wed 15 Mar 08:14:01 2000
+ Wed 15 Mar 13:14:02 2000
+ Wed 15 Mar 12:14:03 2000
+ Wed 15 Mar 03:14:04 2000
+ Wed 15 Mar 02:14:05 2000
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:00 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Tue 10 Jun 18:32:01 1997
+ Mon 10 Feb 17:32:01 1997
+ Tue 11 Feb 17:32:01 1997
+ Wed 12 Feb 17:32:01 1997
+ Thu 13 Feb 17:32:01 1997
+ Fri 14 Feb 17:32:01 1997
+ Sat 15 Feb 17:32:01 1997
+ Sun 16 Feb 17:32:01 1997
+ Tue 16 Feb 17:32:01 0097 BC
+ Sat 16 Feb 17:32:01 0097
+ Thu 16 Feb 17:32:01 0597
+ Tue 16 Feb 17:32:01 1097
+ Sat 16 Feb 17:32:01 1697
+ Thu 16 Feb 17:32:01 1797
+ Tue 16 Feb 17:32:01 1897
+ Sun 16 Feb 17:32:01 1997
+ Sat 16 Feb 17:32:01 2097
+ Wed 28 Feb 17:32:01 1996
+ Thu 29 Feb 17:32:01 1996
+ Fri 01 Mar 17:32:01 1996
+ Mon 30 Dec 17:32:01 1996
+ Tue 31 Dec 17:32:01 1996
+ Wed 01 Jan 17:32:01 1997
+ Fri 28 Feb 17:32:01 1997
+ Sat 01 Mar 17:32:01 1997
+ Tue 30 Dec 17:32:01 1997
+ Wed 31 Dec 17:32:01 1997
+ Fri 31 Dec 17:32:01 1999
+ Sat 01 Jan 17:32:01 2000
+ Sun 31 Dec 17:32:01 2000
+ Mon 01 Jan 17:32:01 2001
+ Thu 13 Jun 00:00:00 1957
+(66 rows)
+
+SET DateStyle TO 'European,ISO';
+SHOW DateStyle;
+ DateStyle
+-----------
+ ISO, DMY
+(1 row)
+
+SELECT d1 AS european_iso FROM TIMESTAMP_TBL;
+ european_iso
+------------------------
+ -infinity
+ infinity
+ 1970-01-01 00:00:00
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:02
+ 1997-02-10 17:32:01.4
+ 1997-02-10 17:32:01.5
+ 1997-02-10 17:32:01.6
+ 1997-01-02 00:00:00
+ 1997-01-02 03:04:05
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-06-10 17:32:01
+ 2001-09-22 18:19:20
+ 2000-03-15 08:14:01
+ 2000-03-15 13:14:02
+ 2000-03-15 12:14:03
+ 2000-03-15 03:14:04
+ 2000-03-15 02:14:05
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:00
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-02-10 17:32:01
+ 1997-06-10 18:32:01
+ 1997-02-10 17:32:01
+ 1997-02-11 17:32:01
+ 1997-02-12 17:32:01
+ 1997-02-13 17:32:01
+ 1997-02-14 17:32:01
+ 1997-02-15 17:32:01
+ 1997-02-16 17:32:01
+ 0097-02-16 17:32:01 BC
+ 0097-02-16 17:32:01
+ 0597-02-16 17:32:01
+ 1097-02-16 17:32:01
+ 1697-02-16 17:32:01
+ 1797-02-16 17:32:01
+ 1897-02-16 17:32:01
+ 1997-02-16 17:32:01
+ 2097-02-16 17:32:01
+ 1996-02-28 17:32:01
+ 1996-02-29 17:32:01
+ 1996-03-01 17:32:01
+ 1996-12-30 17:32:01
+ 1996-12-31 17:32:01
+ 1997-01-01 17:32:01
+ 1997-02-28 17:32:01
+ 1997-03-01 17:32:01
+ 1997-12-30 17:32:01
+ 1997-12-31 17:32:01
+ 1999-12-31 17:32:01
+ 2000-01-01 17:32:01
+ 2000-12-31 17:32:01
+ 2001-01-01 17:32:01
+ 1957-06-13 00:00:00
+(66 rows)
+
+SET DateStyle TO 'European,SQL';
+SHOW DateStyle;
+ DateStyle
+-----------
+ SQL, DMY
+(1 row)
+
+SELECT d1 AS european_sql FROM TIMESTAMP_TBL;
+ european_sql
+------------------------
+ -infinity
+ infinity
+ 01/01/1970 00:00:00
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:02
+ 10/02/1997 17:32:01.4
+ 10/02/1997 17:32:01.5
+ 10/02/1997 17:32:01.6
+ 02/01/1997 00:00:00
+ 02/01/1997 03:04:05
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/06/1997 17:32:01
+ 22/09/2001 18:19:20
+ 15/03/2000 08:14:01
+ 15/03/2000 13:14:02
+ 15/03/2000 12:14:03
+ 15/03/2000 03:14:04
+ 15/03/2000 02:14:05
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:00
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/02/1997 17:32:01
+ 10/06/1997 18:32:01
+ 10/02/1997 17:32:01
+ 11/02/1997 17:32:01
+ 12/02/1997 17:32:01
+ 13/02/1997 17:32:01
+ 14/02/1997 17:32:01
+ 15/02/1997 17:32:01
+ 16/02/1997 17:32:01
+ 16/02/0097 17:32:01 BC
+ 16/02/0097 17:32:01
+ 16/02/0597 17:32:01
+ 16/02/1097 17:32:01
+ 16/02/1697 17:32:01
+ 16/02/1797 17:32:01
+ 16/02/1897 17:32:01
+ 16/02/1997 17:32:01
+ 16/02/2097 17:32:01
+ 28/02/1996 17:32:01
+ 29/02/1996 17:32:01
+ 01/03/1996 17:32:01
+ 30/12/1996 17:32:01
+ 31/12/1996 17:32:01
+ 01/01/1997 17:32:01
+ 28/02/1997 17:32:01
+ 01/03/1997 17:32:01
+ 30/12/1997 17:32:01
+ 31/12/1997 17:32:01
+ 31/12/1999 17:32:01
+ 01/01/2000 17:32:01
+ 31/12/2000 17:32:01
+ 01/01/2001 17:32:01
+ 13/06/1957 00:00:00
+(66 rows)
+
+RESET DateStyle;
+--
+-- to_timestamp()
+--
+SELECT to_timestamp('0097/Feb/16 --> 08:14:30', 'YYYY/Mon/DD --> HH:MI:SS');
+ to_timestamp
+------------------------------
+ Sat Feb 16 08:14:30 0097 PST
+(1 row)
+
+SELECT to_timestamp('97/2/16 8:14:30', 'FMYYYY/FMMM/FMDD FMHH:FMMI:FMSS');
+ to_timestamp
+------------------------------
+ Sat Feb 16 08:14:30 0097 PST
+(1 row)
+
+SELECT to_timestamp('2011$03!18 23_38_15', 'YYYY-MM-DD HH24:MI:SS');
+ to_timestamp
+------------------------------
+ Fri Mar 18 23:38:15 2011 PDT
+(1 row)
+
+SELECT to_timestamp('1985 January 12', 'YYYY FMMonth DD');
+ to_timestamp
+------------------------------
+ Sat Jan 12 00:00:00 1985 PST
+(1 row)
+
+SELECT to_timestamp('1985 FMMonth 12', 'YYYY "FMMonth" DD');
+ to_timestamp
+------------------------------
+ Sat Jan 12 00:00:00 1985 PST
+(1 row)
+
+SELECT to_timestamp('1985 \ 12', 'YYYY \\ DD');
+ to_timestamp
+------------------------------
+ Sat Jan 12 00:00:00 1985 PST
+(1 row)
+
+SELECT to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16',
+ '"My birthday-> Year:" YYYY, "Month:" FMMonth, "Day:" DD');
+ to_timestamp
+------------------------------
+ Sun May 16 00:00:00 1976 PDT
+(1 row)
+
+SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
+ to_timestamp
+------------------------------
+ Sat Aug 21 00:00:00 1582 PST
+(1 row)
+
+SELECT to_timestamp('15 "text between quote marks" 98 54 45',
+ E'HH24 "\\"text between quote marks\\"" YY MI SS');
+ to_timestamp
+------------------------------
+ Thu Jan 01 15:54:45 1998 PST
+(1 row)
+
+SELECT to_timestamp('05121445482000', 'MMDDHH24MISSYYYY');
+ to_timestamp
+------------------------------
+ Fri May 12 14:45:48 2000 PDT
+(1 row)
+
+SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
+ to_timestamp
+------------------------------
+ Sun Jan 09 00:00:00 2000 PST
+(1 row)
+
+SELECT to_timestamp('97/Feb/16', 'YYMonDD');
+ERROR: invalid value "/Feb/16" for "Mon"
+DETAIL: The given value did not match any of the allowed values for this field.
+SELECT to_timestamp('97/Feb/16', 'YY:Mon:DD');
+ to_timestamp
+------------------------------
+ Sun Feb 16 00:00:00 1997 PST
+(1 row)
+
+SELECT to_timestamp('97/Feb/16', 'FXYY:Mon:DD');
+ to_timestamp
+------------------------------
+ Sun Feb 16 00:00:00 1997 PST
+(1 row)
+
+SELECT to_timestamp('97/Feb/16', 'FXYY/Mon/DD');
+ to_timestamp
+------------------------------
+ Sun Feb 16 00:00:00 1997 PST
+(1 row)
+
+SELECT to_timestamp('19971116', 'YYYYMMDD');
+ to_timestamp
+------------------------------
+ Sun Nov 16 00:00:00 1997 PST
+(1 row)
+
+SELECT to_timestamp('20000-1116', 'YYYY-MMDD');
+ to_timestamp
+-------------------------------
+ Thu Nov 16 00:00:00 20000 PST
+(1 row)
+
+SELECT to_timestamp('1997 AD 11 16', 'YYYY BC MM DD');
+ to_timestamp
+------------------------------
+ Sun Nov 16 00:00:00 1997 PST
+(1 row)
+
+SELECT to_timestamp('1997 BC 11 16', 'YYYY BC MM DD');
+ to_timestamp
+---------------------------------
+ Tue Nov 16 00:00:00 1997 PST BC
+(1 row)
+
+SELECT to_timestamp('1997 A.D. 11 16', 'YYYY B.C. MM DD');
+ to_timestamp
+------------------------------
+ Sun Nov 16 00:00:00 1997 PST
+(1 row)
+
+SELECT to_timestamp('1997 B.C. 11 16', 'YYYY B.C. MM DD');
+ to_timestamp
+---------------------------------
+ Tue Nov 16 00:00:00 1997 PST BC
+(1 row)
+
+SELECT to_timestamp('9-1116', 'Y-MMDD');
+ to_timestamp
+------------------------------
+ Mon Nov 16 00:00:00 2009 PST
+(1 row)
+
+SELECT to_timestamp('95-1116', 'YY-MMDD');
+ to_timestamp
+------------------------------
+ Thu Nov 16 00:00:00 1995 PST
+(1 row)
+
+SELECT to_timestamp('995-1116', 'YYY-MMDD');
+ to_timestamp
+------------------------------
+ Thu Nov 16 00:00:00 1995 PST
+(1 row)
+
+SELECT to_timestamp('2005426', 'YYYYWWD');
+ to_timestamp
+------------------------------
+ Sat Oct 15 00:00:00 2005 PDT
+(1 row)
+
+SELECT to_timestamp('2005300', 'YYYYDDD');
+ to_timestamp
+------------------------------
+ Thu Oct 27 00:00:00 2005 PDT
+(1 row)
+
+SELECT to_timestamp('2005527', 'IYYYIWID');
+ to_timestamp
+------------------------------
+ Sun Jan 01 00:00:00 2006 PST
+(1 row)
+
+SELECT to_timestamp('005527', 'IYYIWID');
+ to_timestamp
+------------------------------
+ Sun Jan 01 00:00:00 2006 PST
+(1 row)
+
+SELECT to_timestamp('05527', 'IYIWID');
+ to_timestamp
+------------------------------
+ Sun Jan 01 00:00:00 2006 PST
+(1 row)
+
+SELECT to_timestamp('5527', 'IIWID');
+ to_timestamp
+------------------------------
+ Sun Jan 01 00:00:00 2006 PST
+(1 row)
+
+SELECT to_timestamp('2005364', 'IYYYIDDD');
+ to_timestamp
+------------------------------
+ Sun Jan 01 00:00:00 2006 PST
+(1 row)
+
+SELECT to_timestamp('20050302', 'YYYYMMDD');
+ to_timestamp
+------------------------------
+ Wed Mar 02 00:00:00 2005 PST
+(1 row)
+
+SELECT to_timestamp('2005 03 02', 'YYYYMMDD');
+ to_timestamp
+------------------------------
+ Wed Mar 02 00:00:00 2005 PST
+(1 row)
+
+SELECT to_timestamp(' 2005 03 02', 'YYYYMMDD');
+ to_timestamp
+------------------------------
+ Wed Mar 02 00:00:00 2005 PST
+(1 row)
+
+SELECT to_timestamp(' 20050302', 'YYYYMMDD');
+ to_timestamp
+------------------------------
+ Wed Mar 02 00:00:00 2005 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 11:38 AM', 'YYYY-MM-DD HH12:MI PM');
+ to_timestamp
+------------------------------
+ Sun Dec 18 11:38:00 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 11:38 PM', 'YYYY-MM-DD HH12:MI PM');
+ to_timestamp
+------------------------------
+ Sun Dec 18 23:38:00 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 11:38 A.M.', 'YYYY-MM-DD HH12:MI P.M.');
+ to_timestamp
+------------------------------
+ Sun Dec 18 11:38:00 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 11:38 P.M.', 'YYYY-MM-DD HH12:MI P.M.');
+ to_timestamp
+------------------------------
+ Sun Dec 18 23:38:00 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 11:38 +05', 'YYYY-MM-DD HH12:MI TZH');
+ to_timestamp
+------------------------------
+ Sat Dec 17 22:38:00 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI TZH');
+ to_timestamp
+------------------------------
+ Sun Dec 18 08:38:00 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
+ to_timestamp
+------------------------------
+ Sat Dec 17 22:18:00 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
+ to_timestamp
+------------------------------
+ Sun Dec 18 08:58:00 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
+ to_timestamp
+------------------------------
+ Sun Dec 18 03:18:00 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 11:38 PST', 'YYYY-MM-DD HH12:MI TZ'); -- NYI
+ERROR: formatting field "TZ" is only supported in to_char
+SELECT to_timestamp('2018-11-02 12:34:56.025', 'YYYY-MM-DD HH24:MI:SS.MS');
+ to_timestamp
+----------------------------------
+ Fri Nov 02 12:34:56.025 2018 PDT
+(1 row)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+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;
+ i | to_timestamp
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+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;
+ i | to_timestamp
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+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;
+ i | to_timestamp
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+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;
+ i | to_timestamp
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+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;
+ i | to_timestamp
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+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;
+ i | to_timestamp
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+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;
+ERROR: date/time field value out of range: "2018-11-02 12:34:56.123456789"
+SELECT to_date('1 4 1902', 'Q MM YYYY'); -- Q is ignored
+ to_date
+------------
+ 04-01-1902
+(1 row)
+
+SELECT to_date('3 4 21 01', 'W MM CC YY');
+ to_date
+------------
+ 04-15-2001
+(1 row)
+
+SELECT to_date('2458872', 'J');
+ to_date
+------------
+ 01-23-2020
+(1 row)
+
+--
+-- Check handling of BC dates
+--
+SELECT to_date('44-02-01 BC','YYYY-MM-DD BC');
+ to_date
+---------------
+ 02-01-0044 BC
+(1 row)
+
+SELECT to_date('-44-02-01','YYYY-MM-DD');
+ to_date
+---------------
+ 02-01-0044 BC
+(1 row)
+
+SELECT to_date('-44-02-01 BC','YYYY-MM-DD BC');
+ to_date
+------------
+ 02-01-0044
+(1 row)
+
+SELECT to_timestamp('44-02-01 11:12:13 BC','YYYY-MM-DD HH24:MI:SS BC');
+ to_timestamp
+---------------------------------
+ Fri Feb 01 11:12:13 0044 PST BC
+(1 row)
+
+SELECT to_timestamp('-44-02-01 11:12:13','YYYY-MM-DD HH24:MI:SS');
+ to_timestamp
+---------------------------------
+ Fri Feb 01 11:12:13 0044 PST BC
+(1 row)
+
+SELECT to_timestamp('-44-02-01 11:12:13 BC','YYYY-MM-DD HH24:MI:SS BC');
+ to_timestamp
+------------------------------
+ Mon Feb 01 11:12:13 0044 PST
+(1 row)
+
+--
+-- 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');
+ to_timestamp
+------------------------------
+ Sun Dec 18 23:38:15 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 23:38:15', 'YYYY-MM-DD HH24:MI:SS');
+ to_timestamp
+------------------------------
+ Sun Dec 18 23:38:15 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 23:38:15', 'YYYY-MM-DD HH24:MI:SS');
+ to_timestamp
+------------------------------
+ Sun Dec 18 23:38:15 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 23:38:15', 'YYYY-MM-DD HH24:MI:SS');
+ to_timestamp
+------------------------------
+ Sun Dec 18 23:38:15 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 23:38:15', 'YYYY-MM-DD HH24:MI:SS');
+ to_timestamp
+------------------------------
+ Sun Dec 18 23:38:15 2011 PST
+(1 row)
+
+SELECT to_timestamp('2011-12-18 23:38:15', 'YYYY-MM-DD HH24:MI:SS');
+ to_timestamp
+------------------------------
+ Sun Dec 18 23:38:15 2011 PST
+(1 row)
+
+SELECT to_timestamp('2000+ JUN', 'YYYY/MON');
+ to_timestamp
+------------------------------
+ Thu Jun 01 00:00:00 2000 PDT
+(1 row)
+
+SELECT to_timestamp(' 2000 +JUN', 'YYYY/MON');
+ to_timestamp
+------------------------------
+ Thu Jun 01 00:00:00 2000 PDT
+(1 row)
+
+SELECT to_timestamp(' 2000 +JUN', 'YYYY//MON');
+ to_timestamp
+------------------------------
+ Thu Jun 01 00:00:00 2000 PDT
+(1 row)
+
+SELECT to_timestamp('2000 +JUN', 'YYYY//MON');
+ to_timestamp
+------------------------------
+ Thu Jun 01 00:00:00 2000 PDT
+(1 row)
+
+SELECT to_timestamp('2000 + JUN', 'YYYY MON');
+ to_timestamp
+------------------------------
+ Thu Jun 01 00:00:00 2000 PDT
+(1 row)
+
+SELECT to_timestamp('2000 ++ JUN', 'YYYY MON');
+ to_timestamp
+------------------------------
+ Thu Jun 01 00:00:00 2000 PDT
+(1 row)
+
+SELECT to_timestamp('2000 + + JUN', 'YYYY MON');
+ERROR: invalid value "+" for "MON"
+DETAIL: The given value did not match any of the allowed values for this field.
+SELECT to_timestamp('2000 + + JUN', 'YYYY MON');
+ to_timestamp
+------------------------------
+ Thu Jun 01 00:00:00 2000 PDT
+(1 row)
+
+SELECT to_timestamp('2000 -10', 'YYYY TZH');
+ to_timestamp
+------------------------------
+ Sat Jan 01 02:00:00 2000 PST
+(1 row)
+
+SELECT to_timestamp('2000 -10', 'YYYY TZH');
+ to_timestamp
+------------------------------
+ Fri Dec 31 06:00:00 1999 PST
+(1 row)
+
+SELECT to_date('2011 12 18', 'YYYY MM DD');
+ to_date
+------------
+ 12-18-2011
+(1 row)
+
+SELECT to_date('2011 12 18', 'YYYY MM DD');
+ to_date
+------------
+ 12-18-2011
+(1 row)
+
+SELECT to_date('2011 12 18', 'YYYY MM DD');
+ to_date
+------------
+ 12-18-2011
+(1 row)
+
+SELECT to_date('2011 12 18', 'YYYY MM DD');
+ to_date
+------------
+ 12-18-2011
+(1 row)
+
+SELECT to_date('2011 12 18', 'YYYY MM DD');
+ to_date
+------------
+ 12-18-2011
+(1 row)
+
+SELECT to_date('2011 12 18', 'YYYY MM DD');
+ to_date
+------------
+ 12-18-2011
+(1 row)
+
+SELECT to_date('2011 12 18', 'YYYYxMMxDD');
+ to_date
+------------
+ 12-18-2011
+(1 row)
+
+SELECT to_date('2011x 12x 18', 'YYYYxMMxDD');
+ to_date
+------------
+ 12-18-2011
+(1 row)
+
+SELECT to_date('2011 x12 x18', 'YYYYxMMxDD');
+ERROR: invalid value "x1" for "MM"
+DETAIL: Value must be an integer.
+--
+-- 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');
+ERROR: invalid combination of date conventions
+HINT: Do not mix Gregorian and ISO week date conventions in a formatting template.
+-- Insufficient characters in the source string:
+SELECT to_timestamp('19971', 'YYYYMMDD');
+ERROR: source string too short for "MM" formatting field
+DETAIL: Field requires 2 characters, but only 1 remain.
+HINT: If your source string is not fixed-width, try using the "FM" modifier.
+-- Insufficient digit characters for a single node:
+SELECT to_timestamp('19971)24', 'YYYYMMDD');
+ERROR: invalid value "1)" for "MM"
+DETAIL: Field requires 2 characters, but only 1 could be parsed.
+HINT: If your source string is not fixed-width, try using the "FM" modifier.
+-- 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');
+ERROR: invalid value "da" for "DD"
+DETAIL: Value must be an integer.
+SELECT to_timestamp('Fri 1-January-1999', 'DY DD MON YYYY');
+ERROR: invalid value "uary" for "YYYY"
+DETAIL: Value must be an integer.
+SELECT to_timestamp('Fri 1-Jan-1999', 'DY DD MON YYYY'); -- ok
+ to_timestamp
+------------------------------
+ Fri Jan 01 00:00:00 1999 PST
+(1 row)
+
+-- Value clobbering:
+SELECT to_timestamp('1997-11-Jan-16', 'YYYY-MM-Mon-DD');
+ERROR: conflicting values for "Mon" field in formatting string
+DETAIL: This value contradicts a previous setting for the same field type.
+-- Non-numeric input:
+SELECT to_timestamp('199711xy', 'YYYYMMDD');
+ERROR: invalid value "xy" for "DD"
+DETAIL: Value must be an integer.
+-- Input that doesn't fit in an int:
+SELECT to_timestamp('10000000000', 'FMYYYY');
+ERROR: value for "YYYY" in source string is out of range
+DETAIL: Value must be in the range -2147483648 to 2147483647.
+-- 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');
+ERROR: date/time field value out of range: "2016-06-13 25:00:00"
+SELECT to_timestamp('2016-06-13 15:60:00', 'YYYY-MM-DD HH24:MI:SS');
+ERROR: date/time field value out of range: "2016-06-13 15:60:00"
+SELECT to_timestamp('2016-06-13 15:50:60', 'YYYY-MM-DD HH24:MI:SS');
+ERROR: date/time field value out of range: "2016-06-13 15:50:60"
+SELECT to_timestamp('2016-06-13 15:50:55', 'YYYY-MM-DD HH24:MI:SS'); -- ok
+ to_timestamp
+------------------------------
+ Mon Jun 13 15:50:55 2016 PDT
+(1 row)
+
+SELECT to_timestamp('2016-06-13 15:50:55', 'YYYY-MM-DD HH:MI:SS');
+ERROR: hour "15" is invalid for the 12-hour clock
+HINT: Use the 24-hour clock, or give an hour between 1 and 12.
+SELECT to_timestamp('2016-13-01 15:50:55', 'YYYY-MM-DD HH24:MI:SS');
+ERROR: date/time field value out of range: "2016-13-01 15:50:55"
+SELECT to_timestamp('2016-02-30 15:50:55', 'YYYY-MM-DD HH24:MI:SS');
+ERROR: date/time field value out of range: "2016-02-30 15:50:55"
+SELECT to_timestamp('2016-02-29 15:50:55', 'YYYY-MM-DD HH24:MI:SS'); -- ok
+ to_timestamp
+------------------------------
+ Mon Feb 29 15:50:55 2016 PST
+(1 row)
+
+SELECT to_timestamp('2015-02-29 15:50:55', 'YYYY-MM-DD HH24:MI:SS');
+ERROR: date/time field value out of range: "2015-02-29 15:50:55"
+SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSS'); -- ok
+ to_timestamp
+------------------------------
+ Wed Feb 11 23:53:20 2015 PST
+(1 row)
+
+SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSS');
+ERROR: date/time field value out of range: "2015-02-11 86400"
+SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSSS'); -- ok
+ to_timestamp
+------------------------------
+ Wed Feb 11 23:53:20 2015 PST
+(1 row)
+
+SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSSS');
+ERROR: date/time field value out of range: "2015-02-11 86400"
+SELECT to_date('2016-13-10', 'YYYY-MM-DD');
+ERROR: date/time field value out of range: "2016-13-10"
+SELECT to_date('2016-02-30', 'YYYY-MM-DD');
+ERROR: date/time field value out of range: "2016-02-30"
+SELECT to_date('2016-02-29', 'YYYY-MM-DD'); -- ok
+ to_date
+------------
+ 02-29-2016
+(1 row)
+
+SELECT to_date('2015-02-29', 'YYYY-MM-DD');
+ERROR: date/time field value out of range: "2015-02-29"
+SELECT to_date('2015 365', 'YYYY DDD'); -- ok
+ to_date
+------------
+ 12-31-2015
+(1 row)
+
+SELECT to_date('2015 366', 'YYYY DDD');
+ERROR: date/time field value out of range: "2015 366"
+SELECT to_date('2016 365', 'YYYY DDD'); -- ok
+ to_date
+------------
+ 12-30-2016
+(1 row)
+
+SELECT to_date('2016 366', 'YYYY DDD'); -- ok
+ to_date
+------------
+ 12-31-2016
+(1 row)
+
+SELECT to_date('2016 367', 'YYYY DDD');
+ERROR: date/time field value out of range: "2016 367"
+SELECT to_date('0000-02-01','YYYY-MM-DD'); -- allowed, though it shouldn't be
+ to_date
+---------------
+ 02-01-0001 BC
+(1 row)
+
+--
+-- 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;
+ TimeZone
+----------------
+ <-01:30>+01:30
+(1 row)
+
+SELECT '2012-12-12 12:00'::timestamptz;
+ timestamptz
+---------------------------------
+ Wed Dec 12 12:00:00 2012 -01:30
+(1 row)
+
+SELECT '2012-12-12 12:00 America/New_York'::timestamptz;
+ timestamptz
+---------------------------------
+ Wed Dec 12 15:30:00 2012 -01:30
+(1 row)
+
+SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
+ to_char
+----------------------------
+ 2012-12-12 12:00:00 -01:30
+(1 row)
+
+SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSS');
+ to_char
+------------------
+ 2012-12-12 43200
+(1 row)
+
+SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSSS');
+ to_char
+------------------
+ 2012-12-12 43200
+(1 row)
+
+RESET TIME ZONE;
diff --git a/src/test/regress/expected/hs_standby_allowed.out b/src/test/regress/expected/hs_standby_allowed.out
new file mode 100644
index 0000000..00b8faf
--- /dev/null
+++ b/src/test/regress/expected/hs_standby_allowed.out
@@ -0,0 +1,218 @@
+--
+-- Hot Standby tests
+--
+-- hs_standby_allowed.sql
+--
+-- SELECT
+select count(*) as should_be_1 from hs1;
+ should_be_1
+-------------
+ 1
+(1 row)
+
+select count(*) as should_be_2 from hs2;
+ should_be_2
+-------------
+ 2
+(1 row)
+
+select count(*) as should_be_3 from hs3;
+ should_be_3
+-------------
+ 3
+(1 row)
+
+COPY hs1 TO '/tmp/copy_test';
+\! cat /tmp/copy_test
+1
+-- Access sequence directly
+select is_called from hsseq;
+ is_called
+-----------
+ f
+(1 row)
+
+-- Transactions
+begin;
+select count(*) as should_be_1 from hs1;
+ should_be_1
+-------------
+ 1
+(1 row)
+
+end;
+begin transaction read only;
+select count(*) as should_be_1 from hs1;
+ should_be_1
+-------------
+ 1
+(1 row)
+
+end;
+begin transaction isolation level repeatable read;
+select count(*) as should_be_1 from hs1;
+ should_be_1
+-------------
+ 1
+(1 row)
+
+select count(*) as should_be_1 from hs1;
+ should_be_1
+-------------
+ 1
+(1 row)
+
+select count(*) as should_be_1 from hs1;
+ should_be_1
+-------------
+ 1
+(1 row)
+
+commit;
+begin;
+select count(*) as should_be_1 from hs1;
+ should_be_1
+-------------
+ 1
+(1 row)
+
+commit;
+begin;
+select count(*) as should_be_1 from hs1;
+ should_be_1
+-------------
+ 1
+(1 row)
+
+abort;
+start transaction;
+select count(*) as should_be_1 from hs1;
+ should_be_1
+-------------
+ 1
+(1 row)
+
+commit;
+begin;
+select count(*) as should_be_1 from hs1;
+ should_be_1
+-------------
+ 1
+(1 row)
+
+rollback;
+begin;
+select count(*) as should_be_1 from hs1;
+ should_be_1
+-------------
+ 1
+(1 row)
+
+savepoint s;
+select count(*) as should_be_2 from hs2;
+ should_be_2
+-------------
+ 2
+(1 row)
+
+commit;
+begin;
+select count(*) as should_be_1 from hs1;
+ should_be_1
+-------------
+ 1
+(1 row)
+
+savepoint s;
+select count(*) as should_be_2 from hs2;
+ should_be_2
+-------------
+ 2
+(1 row)
+
+release savepoint s;
+select count(*) as should_be_2 from hs2;
+ should_be_2
+-------------
+ 2
+(1 row)
+
+savepoint s;
+select count(*) as should_be_3 from hs3;
+ should_be_3
+-------------
+ 3
+(1 row)
+
+rollback to savepoint s;
+select count(*) as should_be_2 from hs2;
+ should_be_2
+-------------
+ 2
+(1 row)
+
+commit;
+-- SET parameters
+-- has no effect on read only transactions, but we can still set it
+set synchronous_commit = on;
+show synchronous_commit;
+ synchronous_commit
+--------------------
+ on
+(1 row)
+
+reset synchronous_commit;
+discard temp;
+discard all;
+-- CURSOR commands
+BEGIN;
+DECLARE hsc CURSOR FOR select * from hs3;
+FETCH next from hsc;
+ col1
+------
+ 113
+(1 row)
+
+fetch first from hsc;
+ col1
+------
+ 113
+(1 row)
+
+fetch last from hsc;
+ col1
+------
+ 115
+(1 row)
+
+fetch 1 from hsc;
+ col1
+------
+(0 rows)
+
+CLOSE hsc;
+COMMIT;
+-- Prepared plans
+PREPARE hsp AS select count(*) from hs1;
+PREPARE hsp_noexec (integer) AS insert into hs1 values ($1);
+EXECUTE hsp;
+ count
+-------
+ 1
+(1 row)
+
+DEALLOCATE hsp;
+-- LOCK
+BEGIN;
+LOCK hs1 IN ACCESS SHARE MODE;
+LOCK hs1 IN ROW SHARE MODE;
+LOCK hs1 IN ROW EXCLUSIVE MODE;
+COMMIT;
+-- UNLISTEN
+UNLISTEN a;
+UNLISTEN *;
+-- LOAD
+-- should work, easier if there is no test for that...
+-- ALLOWED COMMANDS
+CHECKPOINT;
+discard all;
diff --git a/src/test/regress/expected/hs_standby_check.out b/src/test/regress/expected/hs_standby_check.out
new file mode 100644
index 0000000..df885ea
--- /dev/null
+++ b/src/test/regress/expected/hs_standby_check.out
@@ -0,0 +1,20 @@
+--
+-- Hot Standby tests
+--
+-- hs_standby_check.sql
+--
+--
+-- If the query below returns false then all other tests will fail after it.
+--
+select case pg_is_in_recovery() when false then
+ 'These tests are intended only for execution on a standby server that is reading ' ||
+ 'WAL from a server upon which the regression database is already created and into ' ||
+ 'which src/test/regress/sql/hs_primary_setup.sql has been run'
+else
+ 'Tests are running on a standby server during recovery'
+end;
+ case
+-------------------------------------------------------
+ Tests are running on a standby server during recovery
+(1 row)
+
diff --git a/src/test/regress/expected/hs_standby_disallowed.out b/src/test/regress/expected/hs_standby_disallowed.out
new file mode 100644
index 0000000..8d3cafa
--- /dev/null
+++ b/src/test/regress/expected/hs_standby_disallowed.out
@@ -0,0 +1,133 @@
+--
+-- Hot Standby tests
+--
+-- hs_standby_disallowed.sql
+--
+SET transaction_read_only = off;
+ERROR: cannot set transaction read-write mode during recovery
+begin transaction read write;
+ERROR: cannot set transaction read-write mode during recovery
+commit;
+WARNING: there is no transaction in progress
+-- SELECT
+select * from hs1 FOR SHARE;
+ERROR: cannot execute SELECT FOR SHARE in a read-only transaction
+select * from hs1 FOR UPDATE;
+ERROR: cannot execute SELECT FOR UPDATE in a read-only transaction
+-- DML
+BEGIN;
+insert into hs1 values (37);
+ERROR: cannot execute INSERT in a read-only transaction
+ROLLBACK;
+BEGIN;
+delete from hs1 where col1 = 1;
+ERROR: cannot execute DELETE in a read-only transaction
+ROLLBACK;
+BEGIN;
+update hs1 set col1 = NULL where col1 > 0;
+ERROR: cannot execute UPDATE in a read-only transaction
+ROLLBACK;
+BEGIN;
+truncate hs3;
+ERROR: cannot execute TRUNCATE TABLE in a read-only transaction
+ROLLBACK;
+-- DDL
+create temporary table hstemp1 (col1 integer);
+ERROR: cannot execute CREATE TABLE in a read-only transaction
+BEGIN;
+drop table hs2;
+ERROR: cannot execute DROP TABLE in a read-only transaction
+ROLLBACK;
+BEGIN;
+create table hs4 (col1 integer);
+ERROR: cannot execute CREATE TABLE in a read-only transaction
+ROLLBACK;
+-- Sequences
+SELECT nextval('hsseq');
+ERROR: cannot execute nextval() in a read-only transaction
+-- Two-phase commit transaction stuff
+BEGIN;
+SELECT count(*) FROM hs1;
+ count
+-------
+ 1
+(1 row)
+
+PREPARE TRANSACTION 'foobar';
+ERROR: cannot execute PREPARE TRANSACTION during recovery
+ROLLBACK;
+BEGIN;
+SELECT count(*) FROM hs1;
+ count
+-------
+ 1
+(1 row)
+
+COMMIT PREPARED 'foobar';
+ERROR: cannot execute COMMIT PREPARED during recovery
+ROLLBACK;
+BEGIN;
+SELECT count(*) FROM hs1;
+ count
+-------
+ 1
+(1 row)
+
+PREPARE TRANSACTION 'foobar';
+ERROR: cannot execute PREPARE TRANSACTION during recovery
+ROLLBACK PREPARED 'foobar';
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+ROLLBACK;
+BEGIN;
+SELECT count(*) FROM hs1;
+ count
+-------
+ 1
+(1 row)
+
+ROLLBACK PREPARED 'foobar';
+ERROR: cannot execute ROLLBACK PREPARED during recovery
+ROLLBACK;
+-- Locks
+BEGIN;
+LOCK hs1;
+ERROR: cannot execute LOCK TABLE during recovery
+COMMIT;
+BEGIN;
+LOCK hs1 IN SHARE UPDATE EXCLUSIVE MODE;
+ERROR: cannot execute LOCK TABLE during recovery
+COMMIT;
+BEGIN;
+LOCK hs1 IN SHARE MODE;
+ERROR: cannot execute LOCK TABLE during recovery
+COMMIT;
+BEGIN;
+LOCK hs1 IN SHARE ROW EXCLUSIVE MODE;
+ERROR: cannot execute LOCK TABLE during recovery
+COMMIT;
+BEGIN;
+LOCK hs1 IN EXCLUSIVE MODE;
+ERROR: cannot execute LOCK TABLE during recovery
+COMMIT;
+BEGIN;
+LOCK hs1 IN ACCESS EXCLUSIVE MODE;
+ERROR: cannot execute LOCK TABLE during recovery
+COMMIT;
+-- Listen
+listen a;
+ERROR: cannot execute LISTEN during recovery
+notify a;
+ERROR: cannot execute NOTIFY during recovery
+-- disallowed commands
+ANALYZE hs1;
+ERROR: cannot execute ANALYZE during recovery
+VACUUM hs2;
+ERROR: cannot execute VACUUM during recovery
+CLUSTER hs2 using hs1_pkey;
+ERROR: cannot execute CLUSTER during recovery
+REINDEX TABLE hs2;
+ERROR: cannot execute REINDEX during recovery
+REVOKE SELECT ON hs1 FROM PUBLIC;
+ERROR: cannot execute REVOKE in a read-only transaction
+GRANT SELECT ON hs1 TO PUBLIC;
+ERROR: cannot execute GRANT in a read-only transaction
diff --git a/src/test/regress/expected/hs_standby_functions.out b/src/test/regress/expected/hs_standby_functions.out
new file mode 100644
index 0000000..ce846b7
--- /dev/null
+++ b/src/test/regress/expected/hs_standby_functions.out
@@ -0,0 +1,40 @@
+--
+-- Hot Standby tests
+--
+-- hs_standby_functions.sql
+--
+-- should fail
+select pg_current_xact_id();
+ERROR: cannot execute pg_current_xact_id() during recovery
+select length(pg_current_snapshot()::text) >= 4;
+ ?column?
+----------
+ t
+(1 row)
+
+select pg_start_backup('should fail');
+ERROR: recovery is in progress
+HINT: WAL control functions cannot be executed during recovery.
+select pg_switch_wal();
+ERROR: recovery is in progress
+HINT: WAL control functions cannot be executed during recovery.
+select pg_stop_backup();
+ERROR: recovery is in progress
+HINT: WAL control functions cannot be executed during recovery.
+-- should return no rows
+select * from pg_prepared_xacts;
+ transaction | gid | prepared | owner | database
+-------------+-----+----------+-------+----------
+(0 rows)
+
+-- just the startup process
+select locktype, virtualxid, virtualtransaction, mode, granted
+from pg_locks where virtualxid = '1/1';
+ locktype | virtualxid | virtualtransaction | mode | granted
+------------+------------+--------------------+---------------+---------
+ virtualxid | 1/1 | 1/0 | ExclusiveLock | t
+(1 row)
+
+-- suicide is painless
+select pg_cancel_backend(pg_backend_pid());
+ERROR: canceling statement due to user request
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
new file mode 100644
index 0000000..9981157
--- /dev/null
+++ b/src/test/regress/expected/identity.out
@@ -0,0 +1,562 @@
+-- sanity check of system catalog
+SELECT attrelid, attname, attidentity FROM pg_attribute WHERE attidentity NOT IN ('', 'a', 'd');
+ attrelid | attname | attidentity
+----------+---------+-------------
+(0 rows)
+
+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
+ERROR: column "a" of relation "itest3" is already an identity column
+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;
+ table_name | column_name | column_default | is_nullable | is_identity | identity_generation | identity_start | identity_increment | identity_maximum | identity_minimum | identity_cycle
+------------+-------------+----------------+-------------+-------------+---------------------+----------------+--------------------+---------------------+------------------+----------------
+ itest1 | a | | NO | YES | BY DEFAULT | 1 | 1 | 2147483647 | 1 | NO
+ itest1 | b | | YES | NO | | | | | | NO
+ itest2 | a | | NO | YES | ALWAYS | 1 | 1 | 9223372036854775807 | 1 | NO
+ itest2 | b | | YES | NO | | | | | | NO
+ itest3 | a | | NO | YES | BY DEFAULT | 7 | 5 | 32767 | 1 | NO
+ itest3 | b | | YES | NO | | | | | | NO
+(6 rows)
+
+-- internal sequences should not be shown here
+SELECT sequence_name FROM information_schema.sequences WHERE sequence_name LIKE 'itest%';
+ sequence_name
+---------------
+(0 rows)
+
+SELECT pg_get_serial_sequence('itest1', 'a');
+ pg_get_serial_sequence
+------------------------
+ public.itest1_a_seq
+(1 row)
+
+\d itest1_a_seq
+ Sequence "public.itest1_a_seq"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer | 1 | 1 | 2147483647 | 1 | no | 1
+Sequence for identity column: public.itest1.a
+
+CREATE TABLE itest4 (a int, b text);
+ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error, requires NOT NULL
+ERROR: column "a" of relation "itest4" must be declared NOT NULL before identity can be added
+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
+ERROR: column "a" of relation "itest4" is an identity column
+ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error, already set
+ERROR: column "a" of relation "itest4" is already an identity column
+ALTER TABLE itest4 ALTER COLUMN b ADD GENERATED ALWAYS AS IDENTITY; -- error, wrong data type
+ERROR: identity column type must be smallint, integer, or bigint
+-- 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);
+ERROR: identity column type must be smallint, integer, or bigint
+-- duplicate identity
+CREATE TABLE itest_err_2 (a int generated always as identity generated by default as identity);
+ERROR: multiple identity specifications for column "a" of table "itest_err_2"
+LINE 1: ...E itest_err_2 (a int generated always as identity generated ...
+ ^
+-- cannot have default and identity
+CREATE TABLE itest_err_3 (a int default 5 generated by default as identity);
+ERROR: both default and identity specified for column "a" of table "itest_err_3"
+LINE 1: CREATE TABLE itest_err_3 (a int default 5 generated by defau...
+ ^
+-- cannot combine serial and identity
+CREATE TABLE itest_err_4 (a serial generated by default as identity);
+ERROR: both default and identity specified for column "a" of table "itest_err_4"
+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;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SELECT * FROM itest2;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SELECT * FROM itest3;
+ a | b
+----+---
+ 7 |
+ 12 |
+(2 rows)
+
+SELECT * FROM itest4;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+-- VALUES RTEs
+CREATE TABLE itest5 (a int generated always as identity, b text);
+INSERT INTO itest5 VALUES (1, 'a'); -- error
+ERROR: cannot insert a non-DEFAULT value into column "a"
+DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
+HINT: Use OVERRIDING SYSTEM VALUE to override.
+INSERT INTO itest5 VALUES (DEFAULT, 'a'); -- ok
+INSERT INTO itest5 VALUES (2, 'b'), (3, 'c'); -- error
+ERROR: cannot insert a non-DEFAULT value into column "a"
+DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
+HINT: Use OVERRIDING SYSTEM VALUE to override.
+INSERT INTO itest5 VALUES (DEFAULT, 'b'), (3, 'c'); -- error
+ERROR: cannot insert a non-DEFAULT value into column "a"
+DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
+HINT: Use OVERRIDING SYSTEM VALUE to override.
+INSERT INTO itest5 VALUES (2, 'b'), (DEFAULT, 'c'); -- error
+ERROR: cannot insert a non-DEFAULT value into column "a"
+DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
+HINT: Use OVERRIDING SYSTEM VALUE to override.
+INSERT INTO itest5 VALUES (DEFAULT, 'b'), (DEFAULT, 'c'); -- ok
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-1, 'aa');
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-2, 'bb'), (-3, 'cc');
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (DEFAULT, 'dd'), (-4, 'ee');
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-5, 'ff'), (DEFAULT, 'gg');
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (DEFAULT, 'hh'), (DEFAULT, 'ii');
+INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-1, 'aaa');
+INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-2, 'bbb'), (-3, 'ccc');
+INSERT INTO itest5 OVERRIDING USER VALUE VALUES (DEFAULT, 'ddd'), (-4, 'eee');
+INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-5, 'fff'), (DEFAULT, 'ggg');
+INSERT INTO itest5 OVERRIDING USER VALUE VALUES (DEFAULT, 'hhh'), (DEFAULT, 'iii');
+SELECT * FROM itest5;
+ a | b
+----+-----
+ 1 | a
+ 2 | b
+ 3 | c
+ -1 | aa
+ -2 | bb
+ -3 | cc
+ 4 | dd
+ -4 | ee
+ -5 | ff
+ 5 | gg
+ 6 | hh
+ 7 | ii
+ 8 | aaa
+ 9 | bbb
+ 10 | ccc
+ 11 | ddd
+ 12 | eee
+ 13 | fff
+ 14 | ggg
+ 15 | hhh
+ 16 | iii
+(21 rows)
+
+DROP TABLE itest5;
+INSERT INTO itest3 VALUES (DEFAULT, 'a');
+INSERT INTO itest3 VALUES (DEFAULT, 'b'), (DEFAULT, 'c');
+SELECT * FROM itest3;
+ a | b
+----+---
+ 7 |
+ 12 |
+ 17 | a
+ 22 | b
+ 27 | c
+(5 rows)
+
+-- OVERRIDING tests
+-- GENERATED BY DEFAULT
+-- This inserts the row as presented:
+INSERT INTO itest1 VALUES (10, 'xyz');
+-- With GENERATED BY DEFAULT, OVERRIDING SYSTEM VALUE is not allowed
+-- by the standard, but we allow it as a no-op, since it is of use if
+-- there are multiple identity columns in a table, which is also an
+-- extension.
+INSERT INTO itest1 OVERRIDING SYSTEM VALUE VALUES (20, 'xyz');
+-- This ignores the 30 and uses the sequence value instead:
+INSERT INTO itest1 OVERRIDING USER VALUE VALUES (30, 'xyz');
+SELECT * FROM itest1;
+ a | b
+----+-----
+ 1 |
+ 2 |
+ 10 | xyz
+ 20 | xyz
+ 3 | xyz
+(5 rows)
+
+-- GENERATED ALWAYS
+-- This is an error:
+INSERT INTO itest2 VALUES (10, 'xyz');
+ERROR: cannot insert a non-DEFAULT value into column "a"
+DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
+HINT: Use OVERRIDING SYSTEM VALUE to override.
+-- This inserts the row as presented:
+INSERT INTO itest2 OVERRIDING SYSTEM VALUE VALUES (20, 'xyz');
+-- This ignores the 30 and uses the sequence value instead:
+INSERT INTO itest2 OVERRIDING USER VALUE VALUES (30, 'xyz');
+SELECT * FROM itest2;
+ a | b
+----+-----
+ 1 |
+ 2 |
+ 20 | xyz
+ 3 | xyz
+(4 rows)
+
+-- UPDATE tests
+-- GENERATED BY DEFAULT is not restricted.
+UPDATE itest1 SET a = 101 WHERE a = 1;
+UPDATE itest1 SET a = DEFAULT WHERE a = 2;
+SELECT * FROM itest1;
+ a | b
+-----+-----
+ 10 | xyz
+ 20 | xyz
+ 3 | xyz
+ 101 |
+ 4 |
+(5 rows)
+
+-- GENERATED ALWAYS allows only DEFAULT.
+UPDATE itest2 SET a = 101 WHERE a = 1; -- error
+ERROR: column "a" can only be updated to DEFAULT
+DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
+UPDATE itest2 SET a = DEFAULT WHERE a = 2; -- ok
+SELECT * FROM itest2;
+ a | b
+----+-----
+ 1 |
+ 20 | xyz
+ 3 | xyz
+ 4 |
+(4 rows)
+
+-- COPY tests
+CREATE TABLE itest9 (a int GENERATED ALWAYS AS IDENTITY, b text, c bigint);
+COPY itest9 FROM stdin;
+COPY itest9 (b, c) FROM stdin;
+SELECT * FROM itest9 ORDER BY c;
+ a | b | c
+-----+------+-----
+ 100 | foo | 200
+ 101 | bar | 201
+ 1 | foo2 | 202
+ 2 | bar2 | 203
+(4 rows)
+
+-- DROP IDENTITY tests
+ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY;
+ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY; -- error
+ERROR: column "a" of relation "itest4" is not an identity column
+ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY IF EXISTS; -- noop
+NOTICE: column "a" of relation "itest4" is not an identity column, skipping
+INSERT INTO itest4 DEFAULT VALUES; -- fails because NOT NULL is not dropped
+ERROR: null value in column "a" of relation "itest4" violates not-null constraint
+DETAIL: Failing row contains (null, ).
+ALTER TABLE itest4 ALTER COLUMN a DROP NOT NULL;
+INSERT INTO itest4 DEFAULT VALUES;
+SELECT * FROM itest4;
+ a | b
+---+---
+ 1 |
+ 2 |
+ |
+(3 rows)
+
+-- check that sequence is removed
+SELECT sequence_name FROM itest4_a_seq;
+ERROR: relation "itest4_a_seq" does not exist
+LINE 1: 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;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SELECT * FROM itestv11;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+INSERT INTO itestv10 VALUES (10, 'xyz');
+INSERT INTO itestv10 OVERRIDING USER VALUE VALUES (11, 'xyz');
+SELECT * FROM itestv10;
+ a | b
+----+-----
+ 1 |
+ 2 |
+ 10 | xyz
+ 3 | xyz
+(4 rows)
+
+INSERT INTO itestv11 VALUES (10, 'xyz');
+ERROR: cannot insert a non-DEFAULT value into column "a"
+DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
+HINT: Use OVERRIDING SYSTEM VALUE to override.
+INSERT INTO itestv11 OVERRIDING SYSTEM VALUE VALUES (11, 'xyz');
+SELECT * FROM itestv11;
+ a | b
+----+-----
+ 1 |
+ 2 |
+ 11 | xyz
+(3 rows)
+
+DROP VIEW itestv10, itestv11;
+-- 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;
+ a | b | c
+---+---+---
+ 1 | 1 | 1
+ 2 | 2 | 2
+ 3 | 3 | 3
+(3 rows)
+
+-- various ALTER COLUMN tests
+-- fail, not allowed for identity columns
+ALTER TABLE itest1 ALTER COLUMN a SET DEFAULT 1;
+ERROR: column "a" of relation "itest1" is an identity column
+-- 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;
+ERROR: column "a" of relation "itest5" already has a default value
+ALTER TABLE itest3 ALTER COLUMN a TYPE int;
+SELECT seqtypid::regtype FROM pg_sequence WHERE seqrelid = 'itest3_a_seq'::regclass;
+ seqtypid
+----------
+ integer
+(1 row)
+
+\d itest3
+ Table "public.itest3"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+----------------------------------
+ a | integer | | not null | generated by default as identity
+ b | text | | |
+
+ALTER TABLE itest3 ALTER COLUMN a TYPE text; -- error
+ERROR: identity column type must be smallint, integer, or bigint
+-- kinda silly to change property in the same command, but it should work
+ALTER TABLE itest3
+ ADD COLUMN c int GENERATED BY DEFAULT AS IDENTITY,
+ ALTER COLUMN c SET GENERATED ALWAYS;
+\d itest3
+ Table "public.itest3"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+----------------------------------
+ a | integer | | not null | generated by default as identity
+ b | text | | |
+ c | integer | | not null | generated always as identity
+
+-- 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;
+ a | b
+-----+---
+ 1 |
+ 100 |
+ 102 |
+(3 rows)
+
+SELECT table_name, column_name, is_identity, identity_generation FROM information_schema.columns WHERE table_name = 'itest6' ORDER BY 1, 2;
+ table_name | column_name | is_identity | identity_generation
+------------+-------------+-------------+---------------------
+ itest6 | a | YES | BY DEFAULT
+ itest6 | b | NO |
+(2 rows)
+
+ALTER TABLE itest6 ALTER COLUMN b SET INCREMENT BY 2; -- fail, not identity
+ERROR: column "b" of relation "itest6" is not an identity column
+-- prohibited direct modification of sequence
+ALTER SEQUENCE itest6_a_seq OWNED BY NONE;
+ERROR: cannot change ownership of identity sequence
+DETAIL: Sequence "itest6_a_seq" is linked to table "itest6".
+-- inheritance
+CREATE TABLE itest7 (a int GENERATED ALWAYS AS IDENTITY);
+INSERT INTO itest7 DEFAULT VALUES;
+SELECT * FROM itest7;
+ a
+---
+ 1
+(1 row)
+
+-- 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);
+NOTICE: merging column "a" with inherited definition
+INSERT INTO itest7c DEFAULT VALUES;
+SELECT * FROM itest7c;
+ a
+---
+ 1
+(1 row)
+
+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
+ERROR: cannot recursively add identity column to table that has child tables
+SELECT table_name, column_name, is_nullable, is_identity, identity_generation FROM information_schema.columns WHERE table_name LIKE 'itest7%' ORDER BY 1, 2;
+ table_name | column_name | is_nullable | is_identity | identity_generation
+------------+-------------+-------------+-------------+---------------------
+ itest7 | a | NO | YES | ALWAYS
+ itest7a | a | NO | NO |
+ itest7a | b | YES | NO |
+ itest7b | a | YES | NO |
+ itest7c | a | NO | YES | ALWAYS
+ itest7d | a | NO | YES | ALWAYS
+ itest7e | a | NO | NO |
+(7 rows)
+
+-- 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;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+RESET ROLE;
+DROP TABLE itest8;
+DROP USER regress_identity_user1;
+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+ALTER TABLE itest8
+ ADD COLUMN f2 int NOT NULL,
+ ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+ ADD COLUMN f3 int NOT NULL,
+ ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+ ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+ALTER TABLE itest8
+ ADD COLUMN f4 int;
+ALTER TABLE itest8
+ ALTER COLUMN f4 SET NOT NULL,
+ ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+ ALTER COLUMN f4 SET DATA TYPE bigint;
+ALTER TABLE itest8
+ ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+ ALTER COLUMN f5 DROP IDENTITY,
+ ALTER COLUMN f5 DROP NOT NULL,
+ ALTER COLUMN f5 SET DATA TYPE bigint;
+INSERT INTO itest8 VALUES(0), (1);
+-- This does not work when the table isn't empty. That's intentional,
+-- since ADD GENERATED should only affect later insertions:
+ALTER TABLE itest8
+ ADD COLUMN f22 int NOT NULL,
+ ALTER COLUMN f22 ADD GENERATED ALWAYS AS IDENTITY;
+ERROR: column "f22" of relation "itest8" contains null values
+TABLE itest8;
+ f1 | f2 | f3 | f4 | f5
+----+----+----+----+----
+ 0 | 1 | 1 | 1 |
+ 1 | 2 | 11 | 2 |
+(2 rows)
+
+\d+ itest8
+ Table "public.itest8"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+----------------------------------+---------+--------------+-------------
+ f1 | integer | | | | plain | |
+ f2 | integer | | not null | generated always as identity | plain | |
+ f3 | integer | | not null | generated by default as identity | plain | |
+ f4 | bigint | | not null | generated always as identity | plain | |
+ f5 | bigint | | | | plain | |
+
+\d itest8_f2_seq
+ Sequence "public.itest8_f2_seq"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer | 1 | 1 | 2147483647 | 1 | no | 1
+Sequence for identity column: public.itest8.f2
+
+\d itest8_f3_seq
+ Sequence "public.itest8_f3_seq"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer | 1 | 1 | 2147483647 | 10 | no | 1
+Sequence for identity column: public.itest8.f3
+
+\d itest8_f4_seq
+ Sequence "public.itest8_f4_seq"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Sequence for identity column: public.itest8.f4
+
+\d itest8_f5_seq
+DROP TABLE itest8;
+-- 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
+ERROR: identity columns are not supported on typed tables
+DROP TYPE itest_type CASCADE;
+-- table partitions (currently not supported)
+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
+ERROR: identity columns are not supported on partitions
+DROP TABLE itest_parent;
+-- test that sequence of half-dropped serial column is properly ignored
+CREATE TABLE itest14 (id serial);
+ALTER TABLE itest14 ALTER id DROP DEFAULT;
+ALTER TABLE itest14 ALTER id ADD GENERATED BY DEFAULT AS IDENTITY;
+INSERT INTO itest14 (id) VALUES (DEFAULT);
+-- Identity columns must be NOT NULL (cf bug #16913)
+CREATE TABLE itest15 (id integer GENERATED ALWAYS AS IDENTITY NULL); -- fail
+ERROR: conflicting NULL/NOT NULL declarations for column "id" of table "itest15"
+LINE 1: ...ABLE itest15 (id integer GENERATED ALWAYS AS IDENTITY NULL);
+ ^
+CREATE TABLE itest15 (id integer NULL GENERATED ALWAYS AS IDENTITY); -- fail
+ERROR: conflicting NULL/NOT NULL declarations for column "id" of table "itest15"
+LINE 1: CREATE TABLE itest15 (id integer NULL GENERATED ALWAYS AS ID...
+ ^
+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/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
new file mode 100644
index 0000000..a0b2747
--- /dev/null
+++ b/src/test/regress/expected/incremental_sort.out
@@ -0,0 +1,1676 @@
+-- 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;
+ QUERY PLAN
+-----------------------------------
+ Sort
+ Sort Key: tenk1.four, tenk1.ten
+ -> Sort
+ Sort Key: tenk1.four
+ -> Seq Scan on tenk1
+(5 rows)
+
+-- 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;
+ QUERY PLAN
+-----------------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: tenk1.four, tenk1.ten
+ Presorted Key: tenk1.four
+ -> Sort
+ Sort Key: tenk1.four
+ -> Seq Scan on tenk1
+(7 rows)
+
+-- 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;
+ QUERY PLAN
+-----------------------------------
+ Incremental Sort
+ Sort Key: tenk1.four, tenk1.ten
+ Presorted Key: tenk1.four
+ -> Sort
+ Sort Key: tenk1.four
+ -> Seq Scan on tenk1
+(6 rows)
+
+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;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 31;
+ a | b
+---+----
+ 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
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 1 | 20
+ 1 | 21
+ 1 | 22
+ 1 | 23
+ 1 | 24
+ 1 | 25
+ 1 | 26
+ 1 | 27
+ 1 | 28
+ 1 | 29
+ 1 | 30
+ 1 | 31
+(31 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 32;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 32;
+ a | b
+---+----
+ 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
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 1 | 20
+ 1 | 21
+ 1 | 22
+ 1 | 23
+ 1 | 24
+ 1 | 25
+ 1 | 26
+ 1 | 27
+ 1 | 28
+ 1 | 29
+ 1 | 30
+ 1 | 31
+ 1 | 32
+(32 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 33;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 33;
+ a | b
+---+----
+ 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
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 1 | 20
+ 1 | 21
+ 1 | 22
+ 1 | 23
+ 1 | 24
+ 1 | 25
+ 1 | 26
+ 1 | 27
+ 1 | 28
+ 1 | 29
+ 1 | 30
+ 1 | 31
+ 1 | 32
+ 1 | 33
+(33 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 65;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 65;
+ a | b
+---+----
+ 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
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 1 | 20
+ 1 | 21
+ 1 | 22
+ 1 | 23
+ 1 | 24
+ 1 | 25
+ 1 | 26
+ 1 | 27
+ 1 | 28
+ 1 | 29
+ 1 | 30
+ 1 | 31
+ 1 | 32
+ 1 | 33
+ 1 | 34
+ 1 | 35
+ 1 | 36
+ 1 | 37
+ 1 | 38
+ 1 | 39
+ 1 | 40
+ 1 | 41
+ 1 | 42
+ 1 | 43
+ 1 | 44
+ 1 | 45
+ 1 | 46
+ 1 | 47
+ 1 | 48
+ 1 | 49
+ 1 | 50
+ 1 | 51
+ 1 | 52
+ 1 | 53
+ 1 | 54
+ 1 | 55
+ 1 | 56
+ 1 | 57
+ 1 | 58
+ 1 | 59
+ 1 | 60
+ 1 | 61
+ 1 | 62
+ 1 | 63
+ 1 | 64
+ 1 | 65
+(65 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 66;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 66;
+ a | b
+---+----
+ 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
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 1 | 20
+ 1 | 21
+ 1 | 22
+ 1 | 23
+ 1 | 24
+ 1 | 25
+ 1 | 26
+ 1 | 27
+ 1 | 28
+ 1 | 29
+ 1 | 30
+ 1 | 31
+ 1 | 32
+ 1 | 33
+ 1 | 34
+ 1 | 35
+ 1 | 36
+ 1 | 37
+ 1 | 38
+ 1 | 39
+ 1 | 40
+ 1 | 41
+ 1 | 42
+ 1 | 43
+ 1 | 44
+ 1 | 45
+ 1 | 46
+ 1 | 47
+ 1 | 48
+ 1 | 49
+ 1 | 50
+ 1 | 51
+ 1 | 52
+ 1 | 53
+ 1 | 54
+ 1 | 55
+ 1 | 56
+ 1 | 57
+ 1 | 58
+ 1 | 59
+ 1 | 60
+ 1 | 61
+ 1 | 62
+ 1 | 63
+ 1 | 64
+ 1 | 65
+ 1 | 66
+(66 rows)
+
+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;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 55;
+ a | b
+---+----
+ 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
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 1 | 20
+ 1 | 21
+ 1 | 22
+ 1 | 23
+ 1 | 24
+ 1 | 25
+ 1 | 26
+ 1 | 27
+ 1 | 28
+ 1 | 29
+ 1 | 30
+ 1 | 31
+ 1 | 32
+ 1 | 33
+ 1 | 34
+ 1 | 35
+ 1 | 36
+ 1 | 37
+ 1 | 38
+ 1 | 39
+ 1 | 40
+ 1 | 41
+ 1 | 42
+ 1 | 43
+ 1 | 44
+ 1 | 45
+ 1 | 46
+ 1 | 47
+ 1 | 48
+ 1 | 49
+ 1 | 50
+ 2 | 51
+ 2 | 52
+ 2 | 53
+ 2 | 54
+ 2 | 55
+(55 rows)
+
+-- 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');
+ explain_analyze_without_memory
+---------------------------------------------------------------------------------------------------------------
+ Limit (actual rows=55 loops=1)
+ -> Incremental Sort (actual rows=55 loops=1)
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ Full-sort Groups: 2 Sort Methods: top-N heapsort, quicksort Average Memory: NNkB Peak Memory: NNkB
+ -> Sort (actual rows=101 loops=1)
+ Sort Key: t.a
+ Sort Method: quicksort Memory: NNkB
+ -> Seq Scan on t (actual rows=1000 loops=1)
+(9 rows)
+
+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'));
+ jsonb_pretty
+-------------------------------------------------
+ [ +
+ { +
+ "Sort Key": [ +
+ "t.a", +
+ "t.b" +
+ ], +
+ "Node Type": "Incremental Sort", +
+ "Actual Rows": 55, +
+ "Actual Loops": 1, +
+ "Async Capable": false, +
+ "Presorted Key": [ +
+ "t.a" +
+ ], +
+ "Parallel Aware": false, +
+ "Full-sort Groups": { +
+ "Group Count": 2, +
+ "Sort Methods Used": [ +
+ "top-N heapsort", +
+ "quicksort" +
+ ], +
+ "Sort Space Memory": { +
+ "Peak Sort Space Used": "NN", +
+ "Average Sort Space Used": "NN"+
+ } +
+ }, +
+ "Parent Relationship": "Outer" +
+ } +
+ ]
+(1 row)
+
+select explain_analyze_inc_sort_nodes_verify_invariants('select * from (select * from t order by a) s order by a, b limit 55');
+ explain_analyze_inc_sort_nodes_verify_invariants
+--------------------------------------------------
+ t
+(1 row)
+
+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;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 70;
+ a | b
+---+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 9 | 5
+ 9 | 6
+ 9 | 7
+ 9 | 8
+ 9 | 9
+ 9 | 10
+ 9 | 11
+ 9 | 12
+ 9 | 13
+ 9 | 14
+ 9 | 15
+ 9 | 16
+ 9 | 17
+ 9 | 18
+ 9 | 19
+ 9 | 20
+ 9 | 21
+ 9 | 22
+ 9 | 23
+ 9 | 24
+ 9 | 25
+ 9 | 26
+ 9 | 27
+ 9 | 28
+ 9 | 29
+ 9 | 30
+ 9 | 31
+ 9 | 32
+ 9 | 33
+ 9 | 34
+ 9 | 35
+ 9 | 36
+ 9 | 37
+ 9 | 38
+ 9 | 39
+ 9 | 40
+ 9 | 41
+ 9 | 42
+ 9 | 43
+ 9 | 44
+ 9 | 45
+ 9 | 46
+ 9 | 47
+ 9 | 48
+ 9 | 49
+ 9 | 50
+ 9 | 51
+ 9 | 52
+ 9 | 53
+ 9 | 54
+ 9 | 55
+ 9 | 56
+ 9 | 57
+ 9 | 58
+ 9 | 59
+ 9 | 60
+ 9 | 61
+ 9 | 62
+ 9 | 63
+ 9 | 64
+ 9 | 65
+ 9 | 66
+ 9 | 67
+ 9 | 68
+ 9 | 69
+ 9 | 70
+(70 rows)
+
+-- 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;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 5;
+ a | b
+---+---
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 9 | 5
+(5 rows)
+
+-- 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);
+ QUERY PLAN
+------------------------------------------------
+ Nested Loop Left Join
+ Join Filter: (t_1.a = t.a)
+ -> Seq Scan on t
+ Filter: (a = ANY ('{1,2}'::integer[]))
+ -> Incremental Sort
+ Sort Key: t_1.a, t_1.b
+ Presorted Key: t_1.a
+ -> Sort
+ Sort Key: t_1.a
+ -> Seq Scan on t t_1
+(10 rows)
+
+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);
+ a | b | a | b
+---+---+---+---
+ 1 | 1 | 1 | 1
+ 2 | 2 | 2 | 2
+(2 rows)
+
+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');
+ explain_analyze_without_memory
+----------------------------------------------------------------------------------------------------------------
+ Limit (actual rows=70 loops=1)
+ -> Incremental Sort (actual rows=70 loops=1)
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ Full-sort Groups: 1 Sort Method: quicksort Average Memory: NNkB Peak Memory: NNkB
+ Pre-sorted Groups: 5 Sort Methods: top-N heapsort, quicksort Average Memory: NNkB Peak Memory: NNkB
+ -> Sort (actual rows=1000 loops=1)
+ Sort Key: t.a
+ Sort Method: quicksort Memory: NNkB
+ -> Seq Scan on t (actual rows=1000 loops=1)
+(10 rows)
+
+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'));
+ jsonb_pretty
+-------------------------------------------------
+ [ +
+ { +
+ "Sort Key": [ +
+ "t.a", +
+ "t.b" +
+ ], +
+ "Node Type": "Incremental Sort", +
+ "Actual Rows": 70, +
+ "Actual Loops": 1, +
+ "Async Capable": false, +
+ "Presorted Key": [ +
+ "t.a" +
+ ], +
+ "Parallel Aware": false, +
+ "Full-sort Groups": { +
+ "Group Count": 1, +
+ "Sort Methods Used": [ +
+ "quicksort" +
+ ], +
+ "Sort Space Memory": { +
+ "Peak Sort Space Used": "NN", +
+ "Average Sort Space Used": "NN"+
+ } +
+ }, +
+ "Pre-sorted Groups": { +
+ "Group Count": 5, +
+ "Sort Methods Used": [ +
+ "top-N heapsort", +
+ "quicksort" +
+ ], +
+ "Sort Space Memory": { +
+ "Peak Sort Space Used": "NN", +
+ "Average Sort Space Used": "NN"+
+ } +
+ }, +
+ "Parent Relationship": "Outer" +
+ } +
+ ]
+(1 row)
+
+select explain_analyze_inc_sort_nodes_verify_invariants('select * from (select * from t order by a) s order by a, b limit 70');
+ explain_analyze_inc_sort_nodes_verify_invariants
+--------------------------------------------------
+ t
+(1 row)
+
+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;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 31;
+ a | b
+---+----
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 5
+ 0 | 6
+ 0 | 7
+ 0 | 8
+ 0 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 2 | 20
+ 2 | 21
+ 2 | 22
+ 2 | 23
+ 2 | 24
+ 2 | 25
+ 2 | 26
+ 2 | 27
+ 2 | 28
+ 2 | 29
+ 3 | 30
+ 3 | 31
+(31 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 32;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 32;
+ a | b
+---+----
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 5
+ 0 | 6
+ 0 | 7
+ 0 | 8
+ 0 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 2 | 20
+ 2 | 21
+ 2 | 22
+ 2 | 23
+ 2 | 24
+ 2 | 25
+ 2 | 26
+ 2 | 27
+ 2 | 28
+ 2 | 29
+ 3 | 30
+ 3 | 31
+ 3 | 32
+(32 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 33;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 33;
+ a | b
+---+----
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 5
+ 0 | 6
+ 0 | 7
+ 0 | 8
+ 0 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 2 | 20
+ 2 | 21
+ 2 | 22
+ 2 | 23
+ 2 | 24
+ 2 | 25
+ 2 | 26
+ 2 | 27
+ 2 | 28
+ 2 | 29
+ 3 | 30
+ 3 | 31
+ 3 | 32
+ 3 | 33
+(33 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 65;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 65;
+ a | b
+---+----
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 5
+ 0 | 6
+ 0 | 7
+ 0 | 8
+ 0 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 2 | 20
+ 2 | 21
+ 2 | 22
+ 2 | 23
+ 2 | 24
+ 2 | 25
+ 2 | 26
+ 2 | 27
+ 2 | 28
+ 2 | 29
+ 3 | 30
+ 3 | 31
+ 3 | 32
+ 3 | 33
+ 3 | 34
+ 3 | 35
+ 3 | 36
+ 3 | 37
+ 3 | 38
+ 3 | 39
+ 4 | 40
+ 4 | 41
+ 4 | 42
+ 4 | 43
+ 4 | 44
+ 4 | 45
+ 4 | 46
+ 4 | 47
+ 4 | 48
+ 4 | 49
+ 5 | 50
+ 5 | 51
+ 5 | 52
+ 5 | 53
+ 5 | 54
+ 5 | 55
+ 5 | 56
+ 5 | 57
+ 5 | 58
+ 5 | 59
+ 6 | 60
+ 6 | 61
+ 6 | 62
+ 6 | 63
+ 6 | 64
+ 6 | 65
+(65 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 66;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 66;
+ a | b
+---+----
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 5
+ 0 | 6
+ 0 | 7
+ 0 | 8
+ 0 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 2 | 20
+ 2 | 21
+ 2 | 22
+ 2 | 23
+ 2 | 24
+ 2 | 25
+ 2 | 26
+ 2 | 27
+ 2 | 28
+ 2 | 29
+ 3 | 30
+ 3 | 31
+ 3 | 32
+ 3 | 33
+ 3 | 34
+ 3 | 35
+ 3 | 36
+ 3 | 37
+ 3 | 38
+ 3 | 39
+ 4 | 40
+ 4 | 41
+ 4 | 42
+ 4 | 43
+ 4 | 44
+ 4 | 45
+ 4 | 46
+ 4 | 47
+ 4 | 48
+ 4 | 49
+ 5 | 50
+ 5 | 51
+ 5 | 52
+ 5 | 53
+ 5 | 54
+ 5 | 55
+ 5 | 56
+ 5 | 57
+ 5 | 58
+ 5 | 59
+ 6 | 60
+ 6 | 61
+ 6 | 62
+ 6 | 63
+ 6 | 64
+ 6 | 65
+ 6 | 66
+(66 rows)
+
+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;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 31;
+ a | b
+----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+ 10 | 10
+ 11 | 11
+ 12 | 12
+ 13 | 13
+ 14 | 14
+ 15 | 15
+ 16 | 16
+ 17 | 17
+ 18 | 18
+ 19 | 19
+ 20 | 20
+ 21 | 21
+ 22 | 22
+ 23 | 23
+ 24 | 24
+ 25 | 25
+ 26 | 26
+ 27 | 27
+ 28 | 28
+ 29 | 29
+ 30 | 30
+ 31 | 31
+(31 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 32;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 32;
+ a | b
+----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+ 10 | 10
+ 11 | 11
+ 12 | 12
+ 13 | 13
+ 14 | 14
+ 15 | 15
+ 16 | 16
+ 17 | 17
+ 18 | 18
+ 19 | 19
+ 20 | 20
+ 21 | 21
+ 22 | 22
+ 23 | 23
+ 24 | 24
+ 25 | 25
+ 26 | 26
+ 27 | 27
+ 28 | 28
+ 29 | 29
+ 30 | 30
+ 31 | 31
+ 32 | 32
+(32 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 33;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 33;
+ a | b
+----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+ 10 | 10
+ 11 | 11
+ 12 | 12
+ 13 | 13
+ 14 | 14
+ 15 | 15
+ 16 | 16
+ 17 | 17
+ 18 | 18
+ 19 | 19
+ 20 | 20
+ 21 | 21
+ 22 | 22
+ 23 | 23
+ 24 | 24
+ 25 | 25
+ 26 | 26
+ 27 | 27
+ 28 | 28
+ 29 | 29
+ 30 | 30
+ 31 | 31
+ 32 | 32
+ 33 | 33
+(33 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 65;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 65;
+ a | b
+----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+ 10 | 10
+ 11 | 11
+ 12 | 12
+ 13 | 13
+ 14 | 14
+ 15 | 15
+ 16 | 16
+ 17 | 17
+ 18 | 18
+ 19 | 19
+ 20 | 20
+ 21 | 21
+ 22 | 22
+ 23 | 23
+ 24 | 24
+ 25 | 25
+ 26 | 26
+ 27 | 27
+ 28 | 28
+ 29 | 29
+ 30 | 30
+ 31 | 31
+ 32 | 32
+ 33 | 33
+ 34 | 34
+ 35 | 35
+ 36 | 36
+ 37 | 37
+ 38 | 38
+ 39 | 39
+ 40 | 40
+ 41 | 41
+ 42 | 42
+ 43 | 43
+ 44 | 44
+ 45 | 45
+ 46 | 46
+ 47 | 47
+ 48 | 48
+ 49 | 49
+ 50 | 50
+ 51 | 51
+ 52 | 52
+ 53 | 53
+ 54 | 54
+ 55 | 55
+ 56 | 56
+ 57 | 57
+ 58 | 58
+ 59 | 59
+ 60 | 60
+ 61 | 61
+ 62 | 62
+ 63 | 63
+ 64 | 64
+ 65 | 65
+(65 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 66;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 66;
+ a | b
+----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+ 10 | 10
+ 11 | 11
+ 12 | 12
+ 13 | 13
+ 14 | 14
+ 15 | 15
+ 16 | 16
+ 17 | 17
+ 18 | 18
+ 19 | 19
+ 20 | 20
+ 21 | 21
+ 22 | 22
+ 23 | 23
+ 24 | 24
+ 25 | 25
+ 26 | 26
+ 27 | 27
+ 28 | 28
+ 29 | 29
+ 30 | 30
+ 31 | 31
+ 32 | 32
+ 33 | 33
+ 34 | 34
+ 35 | 35
+ 36 | 36
+ 37 | 37
+ 38 | 38
+ 39 | 39
+ 40 | 40
+ 41 | 41
+ 42 | 42
+ 43 | 43
+ 44 | 44
+ 45 | 45
+ 46 | 46
+ 47 | 47
+ 48 | 48
+ 49 | 49
+ 50 | 50
+ 51 | 51
+ 52 | 52
+ 53 | 53
+ 54 | 54
+ 55 | 55
+ 56 | 56
+ 57 | 57
+ 58 | 58
+ 59 | 59
+ 60 | 60
+ 61 | 61
+ 62 | 62
+ 63 | 63
+ 64 | 64
+ 65 | 65
+ 66 | 66
+(66 rows)
+
+delete from t;
+drop table t;
+-- Incremental sort vs. parallel queries
+set min_parallel_table_scan_size = '1kB';
+set min_parallel_index_scan_size = '1kB';
+set parallel_setup_cost = 0;
+set parallel_tuple_cost = 0;
+set max_parallel_workers_per_gather = 2;
+create table t (a int, b int, c int);
+insert into t select mod(i,10),mod(i,10),i from generate_series(1,10000) s(i);
+create index on t (a);
+analyze t;
+set enable_incremental_sort = off;
+explain (costs off) select a,b,sum(c) from t group by 1,2 order by 1,2,3 limit 1;
+ QUERY PLAN
+------------------------------------------------------
+ Limit
+ -> Sort
+ Sort Key: a, b, (sum(c))
+ -> Finalize HashAggregate
+ Group Key: a, b
+ -> Gather
+ Workers Planned: 2
+ -> Partial HashAggregate
+ Group Key: a, b
+ -> Parallel Seq Scan on t
+(10 rows)
+
+set enable_incremental_sort = on;
+explain (costs off) select a,b,sum(c) from t group by 1,2 order by 1,2,3 limit 1;
+ QUERY PLAN
+----------------------------------------------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: a, b, (sum(c))
+ Presorted Key: a, b
+ -> GroupAggregate
+ Group Key: a, b
+ -> Gather Merge
+ Workers Planned: 2
+ -> Incremental Sort
+ Sort Key: a, b
+ Presorted Key: a
+ -> Parallel Index Scan using t_a_idx on t
+(12 rows)
+
+-- Incremental sort vs. set operations with varno 0
+set enable_hashagg to off;
+explain (costs off) select * from t union select * from t order by 1,3;
+ QUERY PLAN
+----------------------------------------------------------
+ Incremental Sort
+ Sort Key: t.a, t.c
+ Presorted Key: t.a
+ -> Unique
+ -> Sort
+ Sort Key: t.a, t.b, t.c
+ -> Append
+ -> Gather
+ Workers Planned: 2
+ -> Parallel Seq Scan on t
+ -> Gather
+ Workers Planned: 2
+ -> Parallel Seq Scan on t t_1
+(13 rows)
+
+-- Full sort, not just incremental sort can be pushed below a gather merge path
+-- by generate_useful_gather_paths.
+explain (costs off) select distinct a,b from t;
+ QUERY PLAN
+------------------------------------------
+ Unique
+ -> Gather Merge
+ Workers Planned: 2
+ -> Sort
+ Sort Key: a, b
+ -> Parallel Seq Scan on t
+(6 rows)
+
+drop table t;
+-- Sort pushdown can't go below where expressions are part of the rel target.
+-- In particular this is interesting for volatile expressions which have to
+-- go above joins since otherwise we'll incorrectly use expression evaluations
+-- across multiple rows.
+set enable_hashagg=off;
+set enable_seqscan=off;
+set enable_incremental_sort = off;
+set parallel_tuple_cost=0;
+set parallel_setup_cost=0;
+set min_parallel_table_scan_size = 0;
+set min_parallel_index_scan_size = 0;
+-- Parallel sort below join.
+explain (costs off) select distinct sub.unique1, stringu1
+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Unique
+ -> Nested Loop
+ -> Gather Merge
+ Workers Planned: 2
+ -> Sort
+ Sort Key: tenk1.unique1, tenk1.stringu1
+ -> Parallel Index Scan using tenk1_unique1 on tenk1
+ -> Function Scan on generate_series
+(8 rows)
+
+explain (costs off) select sub.unique1, stringu1
+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub
+order by 1, 2;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Nested Loop
+ -> Gather Merge
+ Workers Planned: 2
+ -> Sort
+ Sort Key: tenk1.unique1, tenk1.stringu1
+ -> Parallel Index Scan using tenk1_unique1 on tenk1
+ -> Function Scan on generate_series
+(7 rows)
+
+-- Parallel sort but with expression that can be safely generated at the base rel.
+explain (costs off) select distinct sub.unique1, md5(stringu1)
+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;
+ QUERY PLAN
+----------------------------------------------------------------------------------------
+ Unique
+ -> Nested Loop
+ -> Gather Merge
+ Workers Planned: 2
+ -> Sort
+ Sort Key: tenk1.unique1, (md5((tenk1.stringu1)::text)) COLLATE "C"
+ -> Parallel Index Scan using tenk1_unique1 on tenk1
+ -> Function Scan on generate_series
+(8 rows)
+
+explain (costs off) select sub.unique1, md5(stringu1)
+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub
+order by 1, 2;
+ QUERY PLAN
+----------------------------------------------------------------------------------
+ Nested Loop
+ -> Gather Merge
+ Workers Planned: 2
+ -> Sort
+ Sort Key: tenk1.unique1, (md5((tenk1.stringu1)::text)) COLLATE "C"
+ -> Parallel Index Scan using tenk1_unique1 on tenk1
+ -> Function Scan on generate_series
+(7 rows)
+
+-- Parallel sort with an aggregate that can be safely generated in parallel,
+-- but we can't sort by partial aggregate values.
+explain (costs off) select count(*)
+from tenk1 t1
+join tenk1 t2 on t1.unique1 = t2.unique2
+join tenk1 t3 on t2.unique1 = t3.unique1
+order by count(*);
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: (count(*))
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Hash Join
+ Hash Cond: (t2.unique1 = t3.unique1)
+ -> Parallel Hash Join
+ Hash Cond: (t1.unique1 = t2.unique2)
+ -> Parallel Index Only Scan using tenk1_unique1 on tenk1 t1
+ -> Parallel Hash
+ -> Parallel Index Scan using tenk1_unique2 on tenk1 t2
+ -> Parallel Hash
+ -> Parallel Index Only Scan using tenk1_unique1 on tenk1 t3
+(15 rows)
+
+-- Parallel sort but with expression (correlated subquery) that
+-- is prohibited in parallel plans.
+explain (costs off) select distinct
+ unique1,
+ (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
+from tenk1 t, generate_series(1, 1000);
+ QUERY PLAN
+---------------------------------------------------------------------------------
+ Unique
+ -> Sort
+ Sort Key: t.unique1, ((SubPlan 1))
+ -> Gather
+ Workers Planned: 2
+ -> Nested Loop
+ -> Parallel Index Only Scan using tenk1_unique1 on tenk1 t
+ -> Function Scan on generate_series
+ SubPlan 1
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = t.unique1)
+(11 rows)
+
+explain (costs off) select
+ unique1,
+ (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
+from tenk1 t, generate_series(1, 1000)
+order by 1, 2;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Sort
+ Sort Key: t.unique1, ((SubPlan 1))
+ -> Gather
+ Workers Planned: 2
+ -> Nested Loop
+ -> Parallel Index Only Scan using tenk1_unique1 on tenk1 t
+ -> Function Scan on generate_series
+ SubPlan 1
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = t.unique1)
+(10 rows)
+
+-- Parallel sort but with expression not available until the upper rel.
+explain (costs off) select distinct sub.unique1, stringu1 || random()::text
+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------
+ Unique
+ -> Sort
+ Sort Key: tenk1.unique1, (((tenk1.stringu1)::text || (random())::text)) COLLATE "C"
+ -> Gather
+ Workers Planned: 2
+ -> Nested Loop
+ -> Parallel Index Scan using tenk1_unique1 on tenk1
+ -> Function Scan on generate_series
+(8 rows)
+
+explain (costs off) select sub.unique1, stringu1 || random()::text
+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub
+order by 1, 2;
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Sort
+ Sort Key: tenk1.unique1, (((tenk1.stringu1)::text || (random())::text)) COLLATE "C"
+ -> Gather
+ Workers Planned: 2
+ -> Nested Loop
+ -> Parallel Index Scan using tenk1_unique1 on tenk1
+ -> Function Scan on generate_series
+(7 rows)
+
diff --git a/src/test/regress/expected/index_including.out b/src/test/regress/expected/index_including.out
new file mode 100644
index 0000000..8651068
--- /dev/null
+++ b/src/test/regress/expected/index_including.out
@@ -0,0 +1,400 @@
+/*
+ * 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;
+ pg_get_indexdef
+---------------------------------------------------------------------------------------------------------------
+ CREATE INDEX tbl_include_reg_c1_c2_c11_c3_idx ON public.tbl_include_reg USING btree (c1, c2) INCLUDE (c1, c3)
+ CREATE INDEX tbl_include_reg_idx ON public.tbl_include_reg USING btree (c1, c2) INCLUDE (c3, c4)
+(2 rows)
+
+\d tbl_include_reg_idx
+ Index "public.tbl_include_reg_idx"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ c1 | integer | yes | c1
+ c2 | integer | yes | c2
+ c3 | integer | no | c3
+ c4 | box | no | c4
+btree, for table "public.tbl_include_reg"
+
+-- 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;
+ pg_get_indexdef
+-----------------------------------------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_include_unique1_c1_c2_c3_c4_key ON public.tbl_include_unique1 USING btree (c1, c2) INCLUDE (c3, c4)
+ CREATE UNIQUE INDEX tbl_include_unique1_idx_unique ON public.tbl_include_unique1 USING btree (c1, c2) INCLUDE (c3, c4)
+(2 rows)
+
+-- 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);
+ERROR: could not create unique index "tbl_include_unique2_idx_unique"
+DETAIL: Key (c1, c2)=(1, 2) is duplicated.
+ALTER TABLE tbl_include_unique2 add UNIQUE (c1, c2) INCLUDE (c3, c4);
+ERROR: could not create unique index "tbl_include_unique2_c1_c2_c3_c4_key"
+DETAIL: Key (c1, c2)=(1, 2) is duplicated.
+-- 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;
+ pg_get_indexdef
+--------------------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_include_pk_pkey ON public.tbl_include_pk USING btree (c1, c2) INCLUDE (c3, c4)
+(1 row)
+
+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;
+ pg_get_indexdef
+----------------------------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_include_box_idx_unique ON public.tbl_include_box USING btree (c1, c2) INCLUDE (c3, c4)
+(1 row)
+
+-- 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);
+ERROR: could not create unique index "tbl_include_box_pk_pkey"
+DETAIL: Key (c1, c2)=(1, 2) is duplicated.
+/*
+ * 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;
+ indexrelid | indnatts | indnkeyatts | indisunique | indisprimary | indkey | indclass
+------------+----------+-------------+-------------+--------------+---------+-----------
+ covering | 4 | 2 | t | f | 1 2 3 4 | 1978 1978
+(1 row)
+
+SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+ pg_get_constraintdef | conname | conkey
+----------------------------------+----------+--------
+ UNIQUE (c1, c2) INCLUDE (c3, c4) | covering | {1,2}
+(1 row)
+
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR: duplicate key value violates unique constraint "covering"
+DETAIL: Key (c1, c2)=(1, 2) already exists.
+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;
+ indexrelid | indnatts | indnkeyatts | indisunique | indisprimary | indkey | indclass
+------------+----------+-------------+-------------+--------------+---------+-----------
+ covering | 4 | 2 | t | t | 1 2 3 4 | 1978 1978
+(1 row)
+
+SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+ pg_get_constraintdef | conname | conkey
+---------------------------------------+----------+--------
+ PRIMARY KEY (c1, c2) INCLUDE (c3, c4) | covering | {1,2}
+(1 row)
+
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR: duplicate key value violates unique constraint "covering"
+DETAIL: Key (c1, c2)=(1, 2) already exists.
+INSERT INTO tbl SELECT 1, NULL, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR: null value in column "c2" of relation "tbl" violates not-null constraint
+DETAIL: Failing row contains (1, null, 3, (4,4),(4,4)).
+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);
+ QUERY PLAN
+------------------------------------------------
+ Bitmap Heap Scan on tbl
+ Filter: (ROW(c1, c2, c3) < ROW(2, 5, 1))
+ -> Bitmap Index Scan on covering
+ Index Cond: (ROW(c1, c2) <= ROW(2, 5))
+(4 rows)
+
+select * from tbl where (c1,c2,c3) < (2,5,1);
+ c1 | c2 | c3 | c4
+----+----+----+----
+ 1 | 2 | |
+ 2 | 4 | |
+(2 rows)
+
+-- 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;
+ QUERY PLAN
+----------------------------------------------------
+ Limit
+ -> Index Only Scan using covering on tbl
+ Index Cond: (ROW(c1, c2) <= ROW(262, 1))
+ Filter: (ROW(c1, c2, c3) < ROW(262, 1, 1))
+(4 rows)
+
+select * from tbl where (c1,c2,c3) < (262,1,1) limit 1;
+ c1 | c2 | c3 | c4
+----+----+----+----
+ 1 | 2 | |
+(1 row)
+
+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;
+ indexrelid | indnatts | indnkeyatts | indisunique | indisprimary | indkey | indclass
+---------------------+----------+-------------+-------------+--------------+---------+-----------
+ tbl_c1_c2_c3_c4_key | 4 | 2 | t | f | 1 2 3 4 | 1978 1978
+(1 row)
+
+SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+ pg_get_constraintdef | conname | conkey
+----------------------------------+---------------------+--------
+ UNIQUE (c1, c2) INCLUDE (c3, c4) | tbl_c1_c2_c3_c4_key | {1,2}
+(1 row)
+
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR: duplicate key value violates unique constraint "tbl_c1_c2_c3_c4_key"
+DETAIL: Key (c1, c2)=(1, 2) already exists.
+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;
+ indexrelid | indnatts | indnkeyatts | indisunique | indisprimary | indkey | indclass
+------------+----------+-------------+-------------+--------------+---------+-----------
+ tbl_pkey | 4 | 2 | t | t | 1 2 3 4 | 1978 1978
+(1 row)
+
+SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+ pg_get_constraintdef | conname | conkey
+---------------------------------------+----------+--------
+ PRIMARY KEY (c1, c2) INCLUDE (c3, c4) | tbl_pkey | {1,2}
+(1 row)
+
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR: duplicate key value violates unique constraint "tbl_pkey"
+DETAIL: Key (c1, c2)=(1, 2) already exists.
+INSERT INTO tbl SELECT 1, NULL, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR: null value in column "c2" of relation "tbl" violates not-null constraint
+DETAIL: Failing row contains (1, null, 3, (4,4),(4,4)).
+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));
+SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
+ indexrelid | indnatts | indnkeyatts | indisunique | indisprimary | indkey | indclass
+-------------------+----------+-------------+-------------+--------------+--------+----------
+ tbl_c1_c3_c4_excl | 3 | 1 | f | f | 1 3 4 | 1978
+(1 row)
+
+SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+ pg_get_constraintdef | conname | conkey
+--------------------------------------------------+-------------------+--------
+ EXCLUDE USING btree (c1 WITH =) INCLUDE (c3, c4) | tbl_c1_c3_c4_excl | {1}
+(1 row)
+
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR: conflicting key value violates exclusion constraint "tbl_c1_c3_c4_excl"
+DETAIL: Key (c1)=(1) conflicts with existing key (c1)=(1).
+INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,10) AS x;
+DROP TABLE tbl;
+/*
+ * 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;
+ indexdef
+------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_idx ON public.tbl USING btree (c1, c2, c3, c4)
+(1 row)
+
+ALTER TABLE tbl DROP COLUMN c3;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef
+----------
+(0 rows)
+
+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;
+ indexdef
+---------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_idx ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+(1 row)
+
+ALTER TABLE tbl DROP COLUMN c3;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef
+----------
+(0 rows)
+
+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;
+ indexdef
+---------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_c1_c2_c3_c4_key ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+(1 row)
+
+ALTER TABLE tbl DROP COLUMN c3;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef
+----------
+(0 rows)
+
+ALTER TABLE tbl DROP COLUMN c1;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef
+----------
+(0 rows)
+
+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;
+ERROR: cannot alter statistics on non-expression column "c1" of index "tbl_idx"
+HINT: Alter statistics on table column instead.
+ALTER INDEX tbl_idx ALTER COLUMN 2 SET STATISTICS 1000;
+ALTER INDEX tbl_idx ALTER COLUMN 3 SET STATISTICS 1000;
+ERROR: cannot alter statistics on included column "c2" of index "tbl_idx"
+ALTER INDEX tbl_idx ALTER COLUMN 4 SET STATISTICS 1000;
+ERROR: column number 4 of relation "tbl_idx" does not exist
+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;
+ indexdef
+---------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_c1_c2_c3_c4_idx ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+ CREATE UNIQUE INDEX tbl_c1_c2_c3_c4_key ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+(2 rows)
+
+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;
+ indexdef
+---------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_c1_c2_c3_c4_key ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+(1 row)
+
+ALTER TABLE tbl DROP COLUMN c3;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef
+----------
+(0 rows)
+
+REINDEX INDEX tbl_c1_c2_c3_c4_key;
+ERROR: relation "tbl_c1_c2_c3_c4_key" does not exist
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef
+----------
+(0 rows)
+
+ALTER TABLE tbl DROP COLUMN c1;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef
+----------
+(0 rows)
+
+DROP TABLE tbl;
+/*
+ * 7. Check various AMs. All but btree, gist and spgist must fail.
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 box, c4 box);
+CREATE INDEX on tbl USING brin(c1, c2) INCLUDE (c3, c4);
+ERROR: access method "brin" does not support included columns
+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);
+ERROR: access method "gin" does not support included columns
+CREATE INDEX on tbl USING hash(c1, c2) INCLUDE (c3, c4);
+ERROR: access method "hash" does not support included columns
+CREATE INDEX on tbl USING rtree(c3) INCLUDE (c1, c4);
+NOTICE: substituting access method "gist" for obsolete method "rtree"
+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;
+ERROR: duplicate key value violates unique constraint "tbl_idx_unique"
+DETAIL: Key (c1, c2)=(1, 2) already exists.
+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
+ Table "public.tbl"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | bigint | | |
+ c2 | integer | | |
+ c3 | bigint | | |
+ c4 | box | | |
+Indexes:
+ "tbl_c1_c2_c3_c4_key" UNIQUE CONSTRAINT, btree (c1, c2) INCLUDE (c3, c4)
+
+DROP TABLE tbl;
diff --git a/src/test/regress/expected/index_including_gist.out b/src/test/regress/expected/index_including_gist.out
new file mode 100644
index 0000000..ed9906d
--- /dev/null
+++ b/src/test/regress/expected/index_including_gist.out
@@ -0,0 +1,166 @@
+/*
+ * 1.1. test CREATE INDEX with buffered build
+ */
+-- Regular index with included columns
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+-- size is chosen to exceed page size and trigger actual truncation
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3);
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl_gist'::regclass ORDER BY c.relname;
+ pg_get_indexdef
+-----------------------------------------------------------------------------------
+ CREATE INDEX tbl_gist_idx ON public.tbl_gist USING gist (c4) INCLUDE (c1, c2, c3)
+(1 row)
+
+SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+ c1 | c2 | c3 | c4
+----+----+----+-------------
+ 1 | 2 | 3 | (2,3),(1,2)
+ 2 | 4 | 6 | (4,5),(2,3)
+ 3 | 6 | 9 | (6,7),(3,4)
+ 4 | 8 | 12 | (8,9),(4,5)
+(4 rows)
+
+SET enable_bitmapscan TO off;
+EXPLAIN (costs off) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+ QUERY PLAN
+------------------------------------------------
+ Index Only Scan using tbl_gist_idx on tbl_gist
+ Index Cond: (c4 <@ '(10,10),(1,1)'::box)
+(2 rows)
+
+SET enable_bitmapscan TO default;
+DROP TABLE tbl_gist;
+/*
+ * 1.2. test CREATE INDEX with inserts
+ */
+-- Regular index with included columns
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+-- size is chosen to exceed page size and trigger actual truncation
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x;
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl_gist'::regclass ORDER BY c.relname;
+ pg_get_indexdef
+-----------------------------------------------------------------------------------
+ CREATE INDEX tbl_gist_idx ON public.tbl_gist USING gist (c4) INCLUDE (c1, c2, c3)
+(1 row)
+
+SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+ c1 | c2 | c3 | c4
+----+----+----+-------------
+ 1 | 2 | 3 | (2,3),(1,2)
+ 2 | 4 | 6 | (4,5),(2,3)
+ 3 | 6 | 9 | (6,7),(3,4)
+ 4 | 8 | 12 | (8,9),(4,5)
+(4 rows)
+
+SET enable_bitmapscan TO off;
+EXPLAIN (costs off) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+ QUERY PLAN
+------------------------------------------------
+ Index Only Scan using tbl_gist_idx on tbl_gist
+ Index Cond: (c4 <@ '(10,10),(1,1)'::box)
+(2 rows)
+
+SET enable_bitmapscan TO default;
+DROP TABLE tbl_gist;
+/*
+ * 2. CREATE INDEX CONCURRENTLY
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX CONCURRENTLY tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+ indexdef
+-----------------------------------------------------------------------------------
+ CREATE INDEX tbl_gist_idx ON public.tbl_gist USING gist (c4) INCLUDE (c1, c2, c3)
+(1 row)
+
+DROP TABLE tbl_gist;
+/*
+ * 3. REINDEX
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c3);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+ indexdef
+-------------------------------------------------------------------------------
+ CREATE INDEX tbl_gist_idx ON public.tbl_gist USING gist (c4) INCLUDE (c1, c3)
+(1 row)
+
+REINDEX INDEX tbl_gist_idx;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+ indexdef
+-------------------------------------------------------------------------------
+ CREATE INDEX tbl_gist_idx ON public.tbl_gist USING gist (c4) INCLUDE (c1, c3)
+(1 row)
+
+ALTER TABLE tbl_gist DROP COLUMN c1;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+ indexdef
+----------
+(0 rows)
+
+DROP TABLE tbl_gist;
+/*
+ * 4. Update, delete values in indexed table.
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c3);
+UPDATE tbl_gist SET c1 = 100 WHERE c1 = 2;
+UPDATE tbl_gist SET c1 = 1 WHERE c1 = 3;
+DELETE FROM tbl_gist WHERE c1 = 5 OR c3 = 12;
+DROP TABLE tbl_gist;
+/*
+ * 5. Alter column type.
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c3);
+ALTER TABLE tbl_gist ALTER c1 TYPE bigint;
+ALTER TABLE tbl_gist ALTER c3 TYPE bigint;
+\d tbl_gist
+ Table "public.tbl_gist"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | bigint | | |
+ c2 | integer | | |
+ c3 | bigint | | |
+ c4 | box | | |
+Indexes:
+ "tbl_gist_idx" gist (c4) INCLUDE (c1, c3)
+
+DROP TABLE tbl_gist;
+/*
+ * 6. EXCLUDE constraint.
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box, EXCLUDE USING gist (c4 WITH &&) INCLUDE (c1, c2, c3));
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+ERROR: conflicting key value violates exclusion constraint "tbl_gist_c4_c1_c2_c3_excl"
+DETAIL: Key (c4)=((4,5),(2,3)) conflicts with existing key (c4)=((2,3),(1,2)).
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(3*x,2*x),point(3*x+1,2*x+1)) FROM generate_series(1,10) AS x;
+EXPLAIN (costs off) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+ QUERY PLAN
+-------------------------------------------------------------
+ Index Only Scan using tbl_gist_c4_c1_c2_c3_excl on tbl_gist
+ Index Cond: (c4 <@ '(10,10),(1,1)'::box)
+(2 rows)
+
+\d tbl_gist
+ Table "public.tbl_gist"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1 | integer | | |
+ c2 | integer | | |
+ c3 | integer | | |
+ c4 | box | | |
+Indexes:
+ "tbl_gist_c4_c1_c2_c3_excl" EXCLUDE USING gist (c4 WITH &&) INCLUDE (c1, c2, c3)
+
+DROP TABLE tbl_gist;
diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out
new file mode 100644
index 0000000..c93f447
--- /dev/null
+++ b/src/test/regress/expected/indexing.out
@@ -0,0 +1,1358 @@
+-- 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);
+-- relhassubclass of a partitioned index is false before creating any partition.
+-- It will be set after the first partition is created.
+create index idxpart_idx on idxpart (a);
+select relhassubclass from pg_class where relname = 'idxpart_idx';
+ relhassubclass
+----------------
+ f
+(1 row)
+
+-- Check that partitioned indexes are present in pg_indexes.
+select indexdef from pg_indexes where indexname like 'idxpart_idx%';
+ indexdef
+-----------------------------------------------------------------
+ CREATE INDEX idxpart_idx ON ONLY public.idxpart USING btree (a)
+(1 row)
+
+drop index idxpart_idx;
+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);
+-- Even with partitions, relhassubclass should not be set if a partitioned
+-- index is created only on the parent.
+create index idxpart_idx on only idxpart(a);
+select relhassubclass from pg_class where relname = 'idxpart_idx';
+ relhassubclass
+----------------
+ f
+(1 row)
+
+drop index idxpart_idx;
+create index on idxpart (a);
+select relname, relkind, relhassubclass, 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;
+ relname | relkind | relhassubclass | inhparent
+-----------------+---------+----------------+----------------
+ idxpart | p | t |
+ idxpart1 | r | f |
+ idxpart1_a_idx | i | f | idxpart_a_idx
+ idxpart2 | p | t |
+ idxpart21 | r | f |
+ idxpart21_a_idx | i | f | idxpart2_a_idx
+ idxpart2_a_idx | I | t | idxpart_a_idx
+ idxpart_a_idx | I | t |
+(8 rows)
+
+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);
+ERROR: cannot create index on partitioned table "idxpart" concurrently
+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);
+ col2
+------
+(0 rows)
+
+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
+ Table "public.idxpart1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | text | | |
+
+alter table idxpart attach partition idxpart1 for values from (0) to (10);
+\d idxpart1
+ Table "public.idxpart1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | text | | |
+Partition of: idxpart FOR VALUES FROM (0) TO (10)
+Indexes:
+ "idxpart1_a_idx" btree (a)
+ "idxpart1_b_c_idx" btree (b, c)
+
+\d+ idxpart1_a_idx
+ Index "public.idxpart1_a_idx"
+ Column | Type | Key? | Definition | Storage | Stats target
+--------+---------+------+------------+---------+--------------
+ a | integer | yes | a | plain |
+Partition of: idxparti
+No partition constraint
+btree, for table "public.idxpart1"
+
+\d+ idxpart1_b_c_idx
+ Index "public.idxpart1_b_c_idx"
+ Column | Type | Key? | Definition | Storage | Stats target
+--------+---------+------+------------+----------+--------------
+ b | integer | yes | b | plain |
+ c | text | yes | c | extended |
+Partition of: idxparti2
+No partition constraint
+btree, for table "public.idxpart1"
+
+-- Forbid ALTER TABLE when attaching or detaching an index to a partition.
+create index idxpart_c on only idxpart (c);
+create index idxpart1_c on idxpart1 (c);
+alter table idxpart_c attach partition idxpart1_c for values from (10) to (20);
+ERROR: "idxpart_c" is not a partitioned table
+alter index idxpart_c attach partition idxpart1_c;
+select relname, relpartbound from pg_class
+ where relname in ('idxpart_c', 'idxpart1_c')
+ order by relname;
+ relname | relpartbound
+------------+--------------
+ idxpart1_c |
+ idxpart_c |
+(2 rows)
+
+alter table idxpart_c detach partition idxpart1_c;
+ERROR: "idxpart_c" is not a table
+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
+ Table "public.idxpart1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition of: idxpart FOR VALUES FROM (0, 0) TO (10, 10)
+Indexes:
+ "idxpart1_a_b_idx" btree (a, b)
+
+select relname, relkind, relhassubclass, 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;
+ relname | relkind | relhassubclass | inhparent
+------------------+---------+----------------+-----------------
+ idxpart | p | t |
+ idxpart1 | r | f |
+ idxpart1_a_b_idx | i | f | idxpart_a_b_idx
+ idxpart_a_b_idx | I | t |
+(4 rows)
+
+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);
+drop index idxpart1_a_idx; -- no way
+ERROR: cannot drop index idxpart1_a_idx because index idxpart_a_idx requires it
+HINT: You can drop index idxpart_a_idx instead.
+drop index concurrently idxpart_a_idx; -- unsupported
+ERROR: cannot drop partitioned index "idxpart_a_idx" concurrently
+drop index idxpart_a_idx; -- both indexes go away
+select relname, relkind from pg_class
+ where relname like 'idxpart%' order by relname;
+ relname | relkind
+----------+---------
+ idxpart | p
+ idxpart1 | r
+(2 rows)
+
+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;
+ relname | relkind
+---------------+---------
+ idxpart | p
+ idxpart_a_idx | I
+(2 rows)
+
+drop table idxpart;
+-- DROP behavior with temporary partitioned indexes
+create temp table idxpart_temp (a int) partition by range (a);
+create index on idxpart_temp(a);
+create temp table idxpart1_temp partition of idxpart_temp
+ for values from (0) to (10);
+drop index idxpart1_temp_a_idx; -- error
+ERROR: cannot drop index idxpart1_temp_a_idx because index idxpart_temp_a_idx requires it
+HINT: You can drop index idxpart_temp_a_idx instead.
+-- non-concurrent drop is enforced here, so it is a valid case.
+drop index concurrently idxpart_temp_a_idx;
+select relname, relkind from pg_class
+ where relname like 'idxpart_temp%' order by relname;
+ relname | relkind
+--------------+---------
+ idxpart_temp | p
+(1 row)
+
+drop table idxpart_temp;
+-- 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;
+ERROR: "idxpart" is not an index
+alter index idxpart_a_b_idx attach partition idxpart1;
+ERROR: "idxpart1" is not an index
+alter index idxpart_a_b_idx attach partition idxpart_a_b_idx;
+ERROR: cannot attach index "idxpart_a_b_idx" as a partition of index "idxpart_a_b_idx"
+DETAIL: Index "idxpart_a_b_idx" is not an index on any partition of table "idxpart".
+alter index idxpart_a_b_idx attach partition idxpart1_b_idx;
+ERROR: relation "idxpart1_b_idx" does not exist
+alter index idxpart_a_b_idx attach partition idxpart1_tst1;
+ERROR: cannot attach index "idxpart1_tst1" as a partition of index "idxpart_a_b_idx"
+DETAIL: The index definitions do not match.
+alter index idxpart_a_b_idx attach partition idxpart1_tst2;
+ERROR: cannot attach index "idxpart1_tst2" as a partition of index "idxpart_a_b_idx"
+DETAIL: The index definitions do not match.
+alter index idxpart_a_b_idx attach partition idxpart1_tst3;
+ERROR: cannot attach index "idxpart1_tst3" as a partition of index "idxpart_a_b_idx"
+DETAIL: The index definitions do not match.
+-- 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;
+ERROR: cannot attach index "idxpart1_2_a_b" as a partition of index "idxpart_a_b_idx"
+DETAIL: Another index is already attached for partition "idxpart1".
+drop table idxpart;
+-- make sure everything's gone
+select indexrelid::regclass, indrelid::regclass
+ from pg_index where indexrelid::regclass::text like 'idxpart%';
+ indexrelid | indrelid
+------------+----------
+(0 rows)
+
+-- Don't auto-attach incompatible indexes
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 (a int, b int);
+create index on idxpart1 using hash (a);
+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);
+alter table idxpart attach partition idxpart1 for values from (0) to (1000);
+\d idxpart1
+ Table "public.idxpart1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition of: idxpart FOR VALUES FROM (0) TO (1000)
+Indexes:
+ "idxpart1_a_a1_idx" btree (a, a)
+ "idxpart1_a_idx" hash (a)
+ "idxpart1_a_idx1" btree (a) WHERE b > 1
+ "idxpart1_a_idx2" btree (a)
+ "idxpart1_expr_idx" btree ((a + 0))
+
+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
+ Table "public.idxpart1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition of: idxpart FOR VALUES FROM (0) TO (100)
+Indexes:
+ "idxpart1_a_idx" btree (a)
+
+\d idxpart2
+ Partitioned table "public.idxpart2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition of: idxpart FOR VALUES FROM (100) TO (1000)
+Partition key: RANGE (a)
+Indexes:
+ "idxpart2_a_idx" btree (a) INVALID
+Number of partitions: 2 (Use \d+ to list them.)
+
+\d idxpart21
+ Table "public.idxpart21"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition of: idxpart2 FOR VALUES FROM (100) TO (200)
+
+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";
+ indexrelid | indrelid | inhparent
+-----------------+-----------+---------------
+ idxpart1_a_idx | idxpart1 | idxpart_a_idx
+ idxpart22_a_idx | idxpart22 |
+ idxpart2_a_idx | idxpart2 | idxpart_a_idx
+ idxpart_a_idx | idxpart |
+(4 rows)
+
+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";
+ indexrelid | indrelid | inhparent
+-----------------+-----------+----------------
+ idxpart1_a_idx | idxpart1 | idxpart_a_idx
+ idxpart22_a_idx | idxpart22 | idxpart2_a_idx
+ idxpart2_a_idx | idxpart2 | idxpart_a_idx
+ idxpart_a_idx | idxpart |
+(4 rows)
+
+-- attaching idxpart22 is not enough to set idxpart22_a_idx valid ...
+alter index idxpart2_a_idx attach partition idxpart22_a_idx;
+\d idxpart2
+ Partitioned table "public.idxpart2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition of: idxpart FOR VALUES FROM (100) TO (1000)
+Partition key: RANGE (a)
+Indexes:
+ "idxpart2_a_idx" btree (a) INVALID
+Number of partitions: 2 (Use \d+ to list them.)
+
+-- ... but this one is.
+create index on idxpart21 (a);
+alter index idxpart2_a_idx attach partition idxpart21_a_idx;
+\d idxpart2
+ Partitioned table "public.idxpart2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition of: idxpart FOR VALUES FROM (100) TO (1000)
+Partition key: RANGE (a)
+Indexes:
+ "idxpart2_a_idx" btree (a)
+Number of partitions: 2 (Use \d+ to list them.)
+
+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
+ Table "public.idxpart1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | text | | |
+Indexes:
+ "idxpart1_a_idx" btree (a)
+ "idxpart1_b_c_idx" btree (b, c)
+
+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;
+ relname | relkind | inhparent
+------------------+---------+-----------
+ idxpart | p |
+ idxpart1 | r |
+ idxpart1_a_idx | i |
+ idxpart1_b_c_idx | i |
+ idxparti | I |
+ idxparti2 | I |
+(6 rows)
+
+alter table idxpart attach partition idxpart1 for values from (0) to (10);
+\d idxpart1
+ Table "public.idxpart1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | text | | |
+Partition of: idxpart FOR VALUES FROM (0) TO (10)
+Indexes:
+ "idxpart1_a_idx" btree (a)
+ "idxpart1_b_c_idx" btree (b, c)
+
+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;
+ relname | relkind | inhparent
+------------------+---------+-----------
+ idxpart | p |
+ idxpart1 | r |
+ idxpart1_a_idx | i | idxparti
+ idxpart1_b_c_idx | i | idxparti2
+ idxparti | I |
+ idxparti2 | I |
+(6 rows)
+
+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;
+ relname | indisvalid
+----------------+------------
+ idxpart1_a_idx | f
+ idxpart_a_idx | f
+(2 rows)
+
+-- 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;
+ relname | indisvalid
+----------------+------------
+ idxpart1_a_idx | f
+ idxpart_a_idx | f
+(2 rows)
+
+-- 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;
+ relname | indisvalid
+-----------------+------------
+ idxpart11_a_idx | t
+ idxpart1_a_idx | t
+ idxpart_a_idx | t
+(3 rows)
+
+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;
+ relname | relkind
+----------------+---------
+ idxpart | p
+ idxpart1 | r
+ idxpart1_a_idx | i
+ idxpart2 | r
+ idxpart2_a_idx | i
+ idxpart3 | r
+ idxpart3_a_idx | i
+ idxpart_a_idx | I
+(8 rows)
+
+-- 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;
+ relname | relkind
+---------------+---------
+ idxpart | p
+ idxpart1 | r
+ idxpart2 | r
+ idxpart3 | r
+ idxpart_a_idx | I
+(5 rows)
+
+drop table idxpart, idxpart1, idxpart2, idxpart3;
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+ relname | relkind
+---------+---------
+(0 rows)
+
+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;
+ relname | relkind
+----------------+---------
+ idxpart | p
+ idxpart1 | r
+ idxpart1_a_idx | i
+ idxpart2 | r
+ idxpart2_a_idx | i
+ idxpart3 | r
+ idxpart3_a_idx | i
+ idxpart_a_idx | I
+(8 rows)
+
+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;
+ relname | relkind
+----------------+---------
+ idxpart | p
+ idxpart1 | r
+ idxpart1_a_idx | i
+ idxpart2 | r
+ idxpart2_a_idx | i
+ idxpart3 | r
+ idxpart3_a_idx | i
+(7 rows)
+
+drop table idxpart, idxpart1, idxpart2, idxpart3;
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+ relname | relkind
+---------+---------
+(0 rows)
+
+create table idxpart (a int, b int, c int) partition by range(a);
+create index on idxpart(c);
+create table idxpart1 partition of idxpart for values from (0) to (250);
+create table idxpart2 partition of idxpart for values from (250) to (500);
+alter table idxpart detach partition idxpart2;
+\d idxpart2
+ Table "public.idxpart2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | integer | | |
+Indexes:
+ "idxpart2_c_idx" btree (c)
+
+alter table idxpart2 drop column c;
+\d idxpart2
+ Table "public.idxpart2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+
+drop table idxpart, idxpart2;
+-- 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;
+ child | parent | childdef
+-------------------+------------------+---------------------------------------------------------------------------
+ idxpart1_expr_idx | idxpart_expr_idx | CREATE INDEX idxpart1_expr_idx ON public.idxpart1 USING btree (((a + b)))
+ idxpart2_expr_idx | idxpart_expr_idx | CREATE INDEX idxpart2_expr_idx ON public.idxpart2 USING btree (((a + b)))
+ idxpart3_expr_idx | idxpart_expr_idx | CREATE INDEX idxpart3_expr_idx ON public.idxpart3 USING btree (((a + b)))
+(3 rows)
+
+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;
+ child | parent | childdef
+-----------------+---------------+--------------------------------------------------------------------------------
+ idxpart1_a_idx | idxpart_a_idx | CREATE INDEX idxpart1_a_idx ON public.idxpart1 USING btree (a COLLATE "C")
+ idxpart2_a_idx | | CREATE INDEX idxpart2_a_idx ON public.idxpart2 USING btree (a COLLATE "POSIX")
+ idxpart2_a_idx1 | | CREATE INDEX idxpart2_a_idx1 ON public.idxpart2 USING btree (a)
+ idxpart2_a_idx2 | idxpart_a_idx | CREATE INDEX idxpart2_a_idx2 ON public.idxpart2 USING btree (a COLLATE "C")
+ idxpart3_a_idx | idxpart_a_idx | CREATE INDEX idxpart3_a_idx ON public.idxpart3 USING btree (a COLLATE "C")
+ idxpart4_a_idx | idxpart_a_idx | CREATE INDEX idxpart4_a_idx ON public.idxpart4 USING btree (a COLLATE "C")
+ idxpart_a_idx | | CREATE INDEX idxpart_a_idx ON ONLY public.idxpart USING btree (a COLLATE "C")
+(7 rows)
+
+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;
+ child | parent | childdef
+-----------------+---------------+------------------------------------------------------------------------------------
+ idxpart1_a_idx | idxpart_a_idx | CREATE INDEX idxpart1_a_idx ON public.idxpart1 USING btree (a text_pattern_ops)
+ idxpart2_a_idx | | CREATE INDEX idxpart2_a_idx ON public.idxpart2 USING btree (a)
+ idxpart2_a_idx1 | idxpart_a_idx | CREATE INDEX idxpart2_a_idx1 ON public.idxpart2 USING btree (a text_pattern_ops)
+ idxpart3_a_idx | idxpart_a_idx | CREATE INDEX idxpart3_a_idx ON public.idxpart3 USING btree (a text_pattern_ops)
+ idxpart4_a_idx | idxpart_a_idx | CREATE INDEX idxpart4_a_idx ON public.idxpart4 USING btree (a text_pattern_ops)
+ idxpart_a_idx | | CREATE INDEX idxpart_a_idx ON ONLY public.idxpart USING btree (a text_pattern_ops)
+(6 rows)
+
+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;
+ERROR: cannot attach index "idxpart2_a_idx" as a partition of index "idxpart_a_idx"
+DETAIL: The index definitions do not match.
+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
+ERROR: cannot attach index "idxpart1_1b_idx" as a partition of index "idxpart_1_idx"
+DETAIL: The index definitions do not match.
+alter index idxpart_1_idx attach partition idxpart1_1_idx;
+alter index idxpart_2_idx attach partition idxpart1_2b_idx; -- fail
+ERROR: cannot attach index "idxpart1_2b_idx" as a partition of index "idxpart_2_idx"
+DETAIL: The index definitions do not match.
+alter index idxpart_2_idx attach partition idxpart1_2c_idx; -- fail
+ERROR: cannot attach index "idxpart1_2c_idx" as a partition of index "idxpart_2_idx"
+DETAIL: The index definitions do not match.
+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;
+ child | parent | childdef
+-----------------+---------------+-----------------------------------------------------------------------------------------
+ idxpart1_1_idx | idxpart_1_idx | CREATE INDEX idxpart1_1_idx ON public.idxpart1 USING btree (b, a)
+ idxpart1_1b_idx | | CREATE INDEX idxpart1_1b_idx ON public.idxpart1 USING btree (b)
+ idxpart1_2_idx | idxpart_2_idx | CREATE INDEX idxpart1_2_idx ON public.idxpart1 USING btree (((b + a))) WHERE (a > 1)
+ idxpart1_2b_idx | | CREATE INDEX idxpart1_2b_idx ON public.idxpart1 USING btree (((a + b))) WHERE (a > 1)
+ idxpart1_2c_idx | | CREATE INDEX idxpart1_2c_idx ON public.idxpart1 USING btree (((b + a))) WHERE (b > 1)
+ idxpart_1_idx | | CREATE INDEX idxpart_1_idx ON ONLY public.idxpart USING btree (b, a)
+ idxpart_2_idx | | CREATE INDEX idxpart_2_idx ON ONLY public.idxpart USING btree (((b + a))) WHERE (a > 1)
+(7 rows)
+
+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";
+ relname | pg_get_indexdef
+------------------+---------------------------------------------------------------------
+ idxpart1_a_idx | CREATE INDEX idxpart1_a_idx ON public.idxpart1 USING btree (a)
+ idxpart1_c_b_idx | CREATE INDEX idxpart1_c_b_idx ON public.idxpart1 USING btree (c, b)
+ idxpart2_a_idx | CREATE INDEX idxpart2_a_idx ON public.idxpart2 USING btree (a)
+ idxpart2_c_b_idx | CREATE INDEX idxpart2_c_b_idx ON public.idxpart2 USING btree (c, b)
+ idxparti | CREATE INDEX idxparti ON ONLY public.idxpart USING btree (a)
+ idxparti2 | CREATE INDEX idxparti2 ON ONLY public.idxpart USING btree (c, b)
+(6 rows)
+
+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";
+ relname | pg_get_indexdef
+-------------------+------------------------------------------------------------------------------
+ idxpart1_abs_idx | CREATE INDEX idxpart1_abs_idx ON public.idxpart1 USING btree (abs(b))
+ idxpart1_expr_idx | CREATE INDEX idxpart1_expr_idx ON public.idxpart1 USING btree (((b + 1)))
+ idxpart2_abs_idx | CREATE INDEX idxpart2_abs_idx ON public.idxpart2 USING btree (abs(b))
+ idxpart2_expr_idx | CREATE INDEX idxpart2_expr_idx ON public.idxpart2 USING btree (((b + 1)))
+ idxpart_abs_idx | CREATE INDEX idxpart_abs_idx ON ONLY public.idxpart USING btree (abs(b))
+ idxpart_expr_idx | CREATE INDEX idxpart_expr_idx ON ONLY public.idxpart USING btree (((b + 1)))
+(6 rows)
+
+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";
+ relname | pg_get_indexdef
+----------------+------------------------------------------------------------------------------------
+ idxpart1_a_idx | CREATE INDEX idxpart1_a_idx ON public.idxpart1 USING btree (a) WHERE (b > 1000)
+ idxpart2_a_idx | CREATE INDEX idxpart2_a_idx ON public.idxpart2 USING btree (a) WHERE (b > 1000)
+ idxpart_a_idx | CREATE INDEX idxpart_a_idx ON ONLY public.idxpart USING btree (a) WHERE (b > 1000)
+(3 rows)
+
+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
+ Partitioned table "public.idxpart"
+ Column | Type | Collation | Nullable | Default
+----------+---------+-----------+----------+---------
+ col_keep | integer | | |
+Partition key: RANGE (col_keep)
+Indexes:
+ "idxpart_col_keep_idx" btree (col_keep)
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d idxpart1
+ Table "public.idxpart1"
+ Column | Type | Collation | Nullable | Default
+----------+---------+-----------+----------+---------
+ col_keep | integer | | |
+Partition of: idxpart FOR VALUES FROM (0) TO (1000)
+Indexes:
+ "idxpart1_col_keep_idx" btree (col_keep)
+
+select attrelid::regclass, attname, attnum from pg_attribute
+ where attrelid::regclass::text like 'idxpart%' and attnum > 0
+ order by attrelid::regclass, attnum;
+ attrelid | attname | attnum
+-----------------------+------------------------------+--------
+ idxpart1 | ........pg.dropped.1........ | 1
+ idxpart1 | ........pg.dropped.2........ | 2
+ idxpart1 | col_keep | 3
+ idxpart1 | ........pg.dropped.4........ | 4
+ idxpart1_col_keep_idx | col_keep | 1
+ idxpart | col_keep | 1
+ idxpart_col_keep_idx | col_keep | 1
+(7 rows)
+
+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
+ Partitioned table "public.idxpart"
+ Column | Type | Collation | Nullable | Default
+----------+---------+-----------+----------+---------
+ col_keep | integer | | |
+Partition key: RANGE (col_keep)
+Indexes:
+ "idxpart_col_keep_idx" btree (col_keep)
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d idxpart1
+ Table "public.idxpart1"
+ Column | Type | Collation | Nullable | Default
+----------+---------+-----------+----------+---------
+ col_keep | integer | | |
+Partition of: idxpart FOR VALUES FROM (0) TO (1000)
+Indexes:
+ "idxpart1_col_keep_idx" btree (col_keep)
+
+select attrelid::regclass, attname, attnum from pg_attribute
+ where attrelid::regclass::text like 'idxpart%' and attnum > 0
+ order by attrelid::regclass, attnum;
+ attrelid | attname | attnum
+-----------------------+------------------------------+--------
+ idxpart | ........pg.dropped.1........ | 1
+ idxpart | ........pg.dropped.2........ | 2
+ idxpart | col_keep | 3
+ idxpart | ........pg.dropped.4........ | 4
+ idxpart1 | col_keep | 1
+ idxpart1_col_keep_idx | col_keep | 1
+ idxpart_col_keep_idx | col_keep | 1
+(7 rows)
+
+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
+ Partitioned table "public.idxpart"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+ b | integer | | |
+Partition key: RANGE (a)
+Indexes:
+ "idxpart_pkey" PRIMARY KEY, btree (a)
+Number of partitions: 0
+
+-- multiple primary key on child should fail
+create table failpart partition of idxpart (b primary key) for values from (0) to (100);
+ERROR: multiple primary keys for table "failpart" are not allowed
+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
+ Table "public.idxpart1pk"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+Partition of: idxpart FOR VALUES FROM (0) TO (100)
+Indexes:
+ "idxpart1pk_pkey" PRIMARY KEY, btree (a)
+
+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);
+ERROR: unique constraint on partitioned table must include all partitioning columns
+DETAIL: UNIQUE constraint on table "idxpart" lacks column "b" which is part of the partition key.
+create table idxpart (a int, b int unique) partition by range (a, b);
+ERROR: unique constraint on partitioned table must include all partitioning columns
+DETAIL: UNIQUE constraint on table "idxpart" lacks column "a" which is part of the partition key.
+create table idxpart (a int primary key, b int) partition by range (b, a);
+ERROR: unique constraint on partitioned table must include all partitioning columns
+DETAIL: PRIMARY KEY constraint on table "idxpart" lacks column "b" which is part of the partition key.
+create table idxpart (a int, b int primary key) partition by range (b, a);
+ERROR: unique constraint on partitioned table must include all partitioning columns
+DETAIL: PRIMARY KEY constraint on table "idxpart" lacks column "a" which is part of the partition key.
+-- 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);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: create table idxpart (a int, exclude (a with = )) partition ...
+ ^
+-- no expressions in partition key for PK/UNIQUE
+create table idxpart (a int primary key, b int) partition by range ((b + a));
+ERROR: unsupported PRIMARY KEY constraint with partition key definition
+DETAIL: PRIMARY KEY constraints cannot be used when partition keys include expressions.
+create table idxpart (a int unique, b int) partition by range ((b + a));
+ERROR: unsupported UNIQUE constraint with partition key definition
+DETAIL: UNIQUE constraints cannot be used when partition keys include expressions.
+-- 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
+ERROR: unique constraint on partitioned table must include all partitioning columns
+DETAIL: PRIMARY KEY constraint on table "idxpart" lacks column "b" which is part of the partition key.
+alter table idxpart add primary key (a, b); -- this works
+\d idxpart
+ Partitioned table "public.idxpart"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+ b | integer | | not null |
+ c | text | | |
+Partition key: RANGE (a, b)
+Indexes:
+ "idxpart_pkey" PRIMARY KEY, btree (a, b)
+Number of partitions: 0
+
+create table idxpart1 partition of idxpart for values from (0, 0) to (1000, 1000);
+\d idxpart1
+ Table "public.idxpart1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+ b | integer | | not null |
+ c | text | | |
+Partition of: idxpart FOR VALUES FROM (0, 0) TO (1000, 1000)
+Indexes:
+ "idxpart1_pkey" PRIMARY KEY, btree (a, b)
+
+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
+ERROR: unique constraint on partitioned table must include all partitioning columns
+DETAIL: UNIQUE constraint on table "idxpart" lacks column "b" which is part of the partition key.
+alter table idxpart add unique (b, a); -- this works
+\d idxpart
+ Partitioned table "public.idxpart"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition key: RANGE (a, b)
+Indexes:
+ "idxpart_b_a_key" UNIQUE CONSTRAINT, btree (b, a)
+Number of partitions: 0
+
+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 =);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: 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;
+ conname | contype | conrelid | conindid | conkey
+----------------+---------+-----------+----------------+--------
+ idxpart1_pkey | p | idxpart1 | idxpart1_pkey | {1,2}
+ idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2}
+ idxpart22_pkey | p | idxpart22 | idxpart22_pkey | {1,2}
+ idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2}
+ idxpart3_pkey | p | idxpart3 | idxpart3_pkey | {2,1}
+ idxpart_pkey | p | idxpart | idxpart_pkey | {1,2}
+(6 rows)
+
+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
+ERROR: unique constraint on partitioned table must include all partitioning columns
+DETAIL: PRIMARY KEY constraint on table "idxpart2" lacks column "b" which is part of the partition key.
+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);
+ERROR: unique constraint on partitioned table must include all partitioning columns
+DETAIL: UNIQUE constraint on table "idxpart1" lacks column "b" which is part of the partition key.
+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;
+ conname | contype | conrelid | conindid | conkey
+----------------+---------+-----------+----------------+--------
+ idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2}
+ idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2}
+ idxpart_pkey | p | idxpart | idxpart_pkey | {1,2}
+(3 rows)
+
+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";
+ indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated
+----------+---------------+--------------+------------+---------------+------------+-------------+--------------+--------------
+ idxpart0 | idxpart0_pkey | idxpart_pkey | t | idxpart0_pkey | f | 1 | t | t
+ idxpart1 | idxpart1_pkey | idxpart_pkey | t | idxpart1_pkey | f | 1 | f | t
+ idxpart | idxpart_pkey | | t | idxpart_pkey | t | 0 | t | t
+(3 rows)
+
+drop index idxpart0_pkey; -- fail
+ERROR: cannot drop index idxpart0_pkey because index idxpart_pkey requires it
+HINT: You can drop index idxpart_pkey instead.
+drop index idxpart1_pkey; -- fail
+ERROR: cannot drop index idxpart1_pkey because index idxpart_pkey requires it
+HINT: You can drop index idxpart_pkey instead.
+alter table idxpart0 drop constraint idxpart0_pkey; -- fail
+ERROR: cannot drop inherited constraint "idxpart0_pkey" of relation "idxpart0"
+alter table idxpart1 drop constraint idxpart1_pkey; -- fail
+ERROR: cannot drop inherited constraint "idxpart1_pkey" of relation "idxpart1"
+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";
+ indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated
+----------+------------+-----------+------------+---------+------------+-------------+--------------+--------------
+(0 rows)
+
+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);
+ERROR: multiple primary keys for table "idxpart1" are not allowed
+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);
+ERROR: multiple primary keys for table "idxpart11" are not allowed
+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";
+ indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated
+----------+---------------+-----------+------------+---------------+------------+-------------+--------------+--------------
+ idxpart0 | idxpart0_pkey | | t | idxpart0_pkey | t | 0 | t | t
+ idxpart | idxpart_pkey | | f | idxpart_pkey | t | 0 | t | t
+(2 rows)
+
+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";
+ indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated
+----------+---------------+--------------+------------+---------------+------------+-------------+--------------+--------------
+ idxpart0 | idxpart0_pkey | idxpart_pkey | t | idxpart0_pkey | f | 1 | t | t
+ idxpart | idxpart_pkey | | t | idxpart_pkey | t | 0 | t | t
+(2 rows)
+
+drop table idxpart;
+-- Related to the above scenario: ADD PRIMARY KEY on the parent mustn't
+-- automatically propagate NOT NULL to child columns.
+create table idxpart (a int) partition by range (a);
+create table idxpart0 (like idxpart);
+alter table idxpart0 add unique (a);
+alter table idxpart attach partition idxpart0 default;
+alter table only idxpart add primary key (a); -- fail, no NOT NULL constraint
+ERROR: constraint must be added to child tables too
+DETAIL: Column "a" of relation "idxpart0" is not already NOT NULL.
+HINT: Do not specify the ONLY keyword.
+alter table idxpart0 alter column a set not null;
+alter table only idxpart add primary key (a); -- now it works
+alter table idxpart0 alter column a drop not null; -- fail, pkey needs it
+ERROR: column "a" is marked NOT NULL in parent table
+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";
+ indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated
+----------+----------------+--------------+------------+---------------+------------+-------------+--------------+--------------
+ idxpart1 | idxpart1_a_idx | | t | | | | |
+ idxpart1 | idxpart1_pkey | idxpart_pkey | t | idxpart1_pkey | f | 1 | f | t
+ idxpart | idxpart_pkey | | t | idxpart_pkey | t | 0 | t | t
+(3 rows)
+
+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
+ERROR: cannot attach index "idxpart1_a_idx" as a partition of index "idxpart_pkey"
+DETAIL: The index "idxpart_pkey" belongs to a constraint in table "idxpart" but no constraint exists for index "idxpart1_a_idx".
+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;
+ERROR: duplicate key value violates unique constraint "idxpart1_a_idx"
+DETAIL: Key (a)=(65536) already exists.
+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;
+ERROR: duplicate key value violates unique constraint "idxpart2_a_idx"
+DETAIL: Key (a)=(285714) already exists.
+insert into idxpart values (572814, 'five');
+ERROR: duplicate key value violates unique constraint "idxpart2_a_idx"
+DETAIL: Key (a)=(572814) already exists.
+insert into idxpart values (857142, 'six');
+select tableoid::regclass, * from idxpart order by a;
+ tableoid | a | b
+----------+--------+----------------
+ idxpart1 | 0 | zero
+ idxpart1 | 16 | sixteen
+ idxpart1 | 42 | life
+ idxpart1 | 65536 | sixteen
+ idxpart2 | 142857 | one
+ idxpart2 | 285714 | two
+ idxpart2 | 572814 | inserted first
+ idxpart2 | 857142 | six
+(8 rows)
+
+drop table idxpart;
+-- 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);
+create table idxpart3 (c int, b int, a int) partition by range (a);
+alter table idxpart3 drop column b, drop column c;
+create table idxpart31 partition of idxpart3 for values from (1000) to (1200);
+create table idxpart32 partition of idxpart3 for values from (1200) to (1400);
+alter table idxpart attach partition idxpart3 for values from (1000) to (2000);
+-- More objects intentionally left behind, to verify some pg_dump/pg_upgrade
+-- behavior; see https://postgr.es/m/20190321204928.GA17535@alvherre.pgsql
+create schema regress_indexing;
+set search_path to regress_indexing;
+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 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 (like pk) partition by range (a);
+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);
+reset search_path;
+-- 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);
+ERROR: duplicate key value violates unique constraint "covidxpart1_a_b_idx"
+DETAIL: Key (a)=(1) already exists.
+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);
+ERROR: duplicate key value violates unique constraint "covidxpart3_a_b_idx"
+DETAIL: Key (a)=(3) already exists.
+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);
+ERROR: duplicate key value violates unique constraint "covidxpart4_a_b_idx"
+DETAIL: Key (a)=(4) already exists.
+create unique index on covidxpart (b) include (a); -- should fail
+ERROR: unique constraint on partitioned table must include all partitioning columns
+DETAIL: UNIQUE constraint on table "covidxpart" lacks column "a" which is part of the partition key.
+-- 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
+ERROR: cannot drop inherited constraint "parted_pk_detach_test1_pkey" of relation "parted_pk_detach_test1"
+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
+ERROR: cannot drop inherited constraint "parted_uniq_detach_test1_a_key" of relation "parted_uniq_detach_test1"
+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;
+-- check that dropping a column takes with it any partitioned indexes
+-- depending on it.
+create table parted_index_col_drop(a int, b int, c int)
+ partition by list (a);
+create table parted_index_col_drop1 partition of parted_index_col_drop
+ for values in (1) partition by list (a);
+-- leave this partition without children.
+create table parted_index_col_drop2 partition of parted_index_col_drop
+ for values in (2) partition by list (a);
+create table parted_index_col_drop11 partition of parted_index_col_drop1
+ for values in (1);
+create index on parted_index_col_drop (b);
+create index on parted_index_col_drop (c);
+create index on parted_index_col_drop (b, c);
+alter table parted_index_col_drop drop column c;
+\d parted_index_col_drop
+ Partitioned table "public.parted_index_col_drop"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition key: LIST (a)
+Indexes:
+ "parted_index_col_drop_b_idx" btree (b)
+Number of partitions: 2 (Use \d+ to list them.)
+
+\d parted_index_col_drop1
+ Partitioned table "public.parted_index_col_drop1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition of: parted_index_col_drop FOR VALUES IN (1)
+Partition key: LIST (a)
+Indexes:
+ "parted_index_col_drop1_b_idx" btree (b)
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d parted_index_col_drop2
+ Partitioned table "public.parted_index_col_drop2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition of: parted_index_col_drop FOR VALUES IN (2)
+Partition key: LIST (a)
+Indexes:
+ "parted_index_col_drop2_b_idx" btree (b)
+Number of partitions: 0
+
+\d parted_index_col_drop11
+ Table "public.parted_index_col_drop11"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition of: parted_index_col_drop1 FOR VALUES IN (1)
+Indexes:
+ "parted_index_col_drop11_b_idx" btree (b)
+
+drop table parted_index_col_drop;
diff --git a/src/test/regress/expected/indirect_toast.out b/src/test/regress/expected/indirect_toast.out
new file mode 100644
index 0000000..ab1fa5e
--- /dev/null
+++ b/src/test/regress/expected/indirect_toast.out
@@ -0,0 +1,158 @@
+--
+-- Tests for external toast datums
+--
+-- Other compression algorithms may cause the compressed data to be stored
+-- inline. pglz guarantees that the data is externalized, so stick to it.
+SET default_toast_compression = 'pglz';
+CREATE TABLE indtoasttest(descr text, cnt int DEFAULT 0, f1 text, f2 text);
+INSERT INTO indtoasttest(descr, f1, f2) VALUES('two-compressed', repeat('1234567890',1000), repeat('1234567890',1000));
+INSERT INTO indtoasttest(descr, f1, f2) VALUES('two-toasted', repeat('1234567890',30000), repeat('1234567890',50000));
+INSERT INTO indtoasttest(descr, f1, f2) VALUES('one-compressed,one-null', NULL, repeat('1234567890',1000));
+INSERT INTO indtoasttest(descr, f1, f2) VALUES('one-toasted,one-null', NULL, repeat('1234567890',50000));
+-- check whether indirect tuples works on the most basic level
+SELECT descr, substring(make_tuple_indirect(indtoasttest)::text, 1, 200) FROM indtoasttest;
+ descr | substring
+-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ two-compressed | (two-compressed,0,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012
+ two-toasted | (two-toasted,0,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+ one-compressed,one-null | ("one-compressed,one-null",0,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ one-toasted,one-null | ("one-toasted,one-null",0,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+(4 rows)
+
+-- modification without changing varlenas
+UPDATE indtoasttest SET cnt = cnt +1 RETURNING substring(indtoasttest::text, 1, 200);
+ substring
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,1,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012
+ (two-toasted,1,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+ ("one-compressed,one-null",1,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ ("one-toasted,one-null",1,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+(4 rows)
+
+-- modification without modifying assigned value
+UPDATE indtoasttest SET cnt = cnt +1, f1 = f1 RETURNING substring(indtoasttest::text, 1, 200);
+ substring
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,2,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012
+ (two-toasted,2,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+ ("one-compressed,one-null",2,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ ("one-toasted,one-null",2,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+(4 rows)
+
+-- modification modifying, but effectively not changing
+UPDATE indtoasttest SET cnt = cnt +1, f1 = f1||'' RETURNING substring(indtoasttest::text, 1, 200);
+ substring
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,3,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012
+ (two-toasted,3,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+ ("one-compressed,one-null",3,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ ("one-toasted,one-null",3,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+(4 rows)
+
+UPDATE indtoasttest SET cnt = cnt +1, f1 = '-'||f1||'-' RETURNING substring(indtoasttest::text, 1, 200);
+ substring
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,4,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (two-toasted,4,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234
+ ("one-compressed,one-null",4,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ ("one-toasted,one-null",4,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+(4 rows)
+
+SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
+ substring
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,4,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (two-toasted,4,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234
+ ("one-compressed,one-null",4,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ ("one-toasted,one-null",4,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+(4 rows)
+
+-- check we didn't screw with main/toast tuple visibility
+VACUUM FREEZE indtoasttest;
+SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
+ substring
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,4,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (two-toasted,4,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234
+ ("one-compressed,one-null",4,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ ("one-toasted,one-null",4,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+(4 rows)
+
+-- now create a trigger that forces all Datums to be indirect ones
+CREATE FUNCTION update_using_indirect()
+ RETURNS trigger
+ LANGUAGE plpgsql AS $$
+BEGIN
+ NEW := make_tuple_indirect(NEW);
+ RETURN NEW;
+END$$;
+CREATE TRIGGER indtoasttest_update_indirect
+ BEFORE INSERT OR UPDATE
+ ON indtoasttest
+ FOR EACH ROW
+ EXECUTE PROCEDURE update_using_indirect();
+-- modification without changing varlenas
+UPDATE indtoasttest SET cnt = cnt +1 RETURNING substring(indtoasttest::text, 1, 200);
+ substring
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,5,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (two-toasted,5,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234
+ ("one-compressed,one-null",5,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ ("one-toasted,one-null",5,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+(4 rows)
+
+-- modification without modifying assigned value
+UPDATE indtoasttest SET cnt = cnt +1, f1 = f1 RETURNING substring(indtoasttest::text, 1, 200);
+ substring
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,6,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (two-toasted,6,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234
+ ("one-compressed,one-null",6,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ ("one-toasted,one-null",6,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+(4 rows)
+
+-- modification modifying, but effectively not changing
+UPDATE indtoasttest SET cnt = cnt +1, f1 = f1||'' RETURNING substring(indtoasttest::text, 1, 200);
+ substring
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,7,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (two-toasted,7,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234
+ ("one-compressed,one-null",7,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ ("one-toasted,one-null",7,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+(4 rows)
+
+UPDATE indtoasttest SET cnt = cnt +1, f1 = '-'||f1||'-' RETURNING substring(indtoasttest::text, 1, 200);
+ substring
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,8,--123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ (two-toasted,8,--123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+ ("one-compressed,one-null",8,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ ("one-toasted,one-null",8,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+(4 rows)
+
+INSERT INTO indtoasttest(descr, f1, f2) VALUES('one-toasted,one-null, via indirect', repeat('1234567890',30000), NULL);
+SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
+ substring
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,8,--123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ (two-toasted,8,--123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+ ("one-compressed,one-null",8,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ ("one-toasted,one-null",8,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+ ("one-toasted,one-null, via indirect",0,1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+(5 rows)
+
+-- check we didn't screw with main/toast tuple visibility
+VACUUM FREEZE indtoasttest;
+SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
+ substring
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,8,--123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ (two-toasted,8,--123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+ ("one-compressed,one-null",8,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ ("one-toasted,one-null",8,,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
+ ("one-toasted,one-null, via indirect",0,1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+(5 rows)
+
+DROP TABLE indtoasttest;
+DROP FUNCTION update_using_indirect();
+RESET default_toast_compression;
diff --git a/src/test/regress/expected/inet.out b/src/test/regress/expected/inet.out
new file mode 100644
index 0000000..d5bf9e2
--- /dev/null
+++ b/src/test/regress/expected/inet.out
@@ -0,0 +1,1058 @@
+--
+-- INET
+--
+-- prepare the table...
+DROP TABLE INET_TBL;
+ERROR: table "inet_tbl" does not exist
+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');
+ERROR: invalid cidr value: "192.168.1.2/30"
+LINE 1: INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.2/30', '192.1...
+ ^
+DETAIL: Value has bits set to right of mask.
+INSERT INTO INET_TBL (c, i) VALUES ('1234::1234::1234', '::1.2.3.4');
+ERROR: invalid input syntax for type cidr: "1234::1234::1234"
+LINE 1: INSERT INTO INET_TBL (c, i) VALUES ('1234::1234::1234', '::1...
+ ^
+-- 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');
+ERROR: invalid cidr value: "192.168.1.2/30"
+LINE 1: INSERT INTO INET_TBL (c, i) VALUES (cidr('192.168.1.2/30'), ...
+ ^
+DETAIL: Value has bits set to right of mask.
+INSERT INTO INET_TBL (c, i) VALUES (cidr('ffff:ffff:ffff:ffff::/24'), '::192.168.1.226');
+ERROR: invalid cidr value: "ffff:ffff:ffff:ffff::/24"
+LINE 1: INSERT INTO INET_TBL (c, i) VALUES (cidr('ffff:ffff:ffff:fff...
+ ^
+DETAIL: Value has bits set to right of mask.
+SELECT c AS cidr, i AS inet FROM INET_TBL;
+ cidr | inet
+--------------------+------------------
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/26 | 192.168.1.226
+ 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.255/25
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
+ 10.1.2.3/32 | 10.1.2.3
+ 10.1.2.0/24 | 10.1.2.3/24
+ 10.1.0.0/16 | 10.1.2.3/16
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/8 | 11.1.2.3/8
+ 10.0.0.0/8 | 9.1.2.3/8
+ 10:23::f1/128 | 10:23::f1/64
+ 10:23::8000/113 | 10:23::ffff
+ ::ffff:1.2.3.4/128 | ::4.3.2.1/24
+(17 rows)
+
+-- now test some support functions
+SELECT i AS inet, host(i), text(i), family(i) FROM INET_TBL;
+ inet | host | text | family
+------------------+---------------+------------------+--------
+ 192.168.1.226/24 | 192.168.1.226 | 192.168.1.226/24 | 4
+ 192.168.1.226 | 192.168.1.226 | 192.168.1.226/32 | 4
+ 192.168.1.0/24 | 192.168.1.0 | 192.168.1.0/24 | 4
+ 192.168.1.0/25 | 192.168.1.0 | 192.168.1.0/25 | 4
+ 192.168.1.255/24 | 192.168.1.255 | 192.168.1.255/24 | 4
+ 192.168.1.255/25 | 192.168.1.255 | 192.168.1.255/25 | 4
+ 10.1.2.3/8 | 10.1.2.3 | 10.1.2.3/8 | 4
+ 10.1.2.3/8 | 10.1.2.3 | 10.1.2.3/8 | 4
+ 10.1.2.3 | 10.1.2.3 | 10.1.2.3/32 | 4
+ 10.1.2.3/24 | 10.1.2.3 | 10.1.2.3/24 | 4
+ 10.1.2.3/16 | 10.1.2.3 | 10.1.2.3/16 | 4
+ 10.1.2.3/8 | 10.1.2.3 | 10.1.2.3/8 | 4
+ 11.1.2.3/8 | 11.1.2.3 | 11.1.2.3/8 | 4
+ 9.1.2.3/8 | 9.1.2.3 | 9.1.2.3/8 | 4
+ 10:23::f1/64 | 10:23::f1 | 10:23::f1/64 | 6
+ 10:23::ffff | 10:23::ffff | 10:23::ffff/128 | 6
+ ::4.3.2.1/24 | ::4.3.2.1 | ::4.3.2.1/24 | 6
+(17 rows)
+
+SELECT c AS cidr, abbrev(c) FROM INET_TBL;
+ cidr | abbrev
+--------------------+--------------------
+ 192.168.1.0/24 | 192.168.1/24
+ 192.168.1.0/26 | 192.168.1.0/26
+ 192.168.1.0/24 | 192.168.1/24
+ 192.168.1.0/24 | 192.168.1/24
+ 192.168.1.0/24 | 192.168.1/24
+ 192.168.1.0/24 | 192.168.1/24
+ 10.0.0.0/8 | 10/8
+ 10.0.0.0/32 | 10.0.0.0/32
+ 10.1.2.3/32 | 10.1.2.3/32
+ 10.1.2.0/24 | 10.1.2/24
+ 10.1.0.0/16 | 10.1/16
+ 10.0.0.0/8 | 10/8
+ 10.0.0.0/8 | 10/8
+ 10.0.0.0/8 | 10/8
+ 10:23::f1/128 | 10:23::f1/128
+ 10:23::8000/113 | 10:23::8000/113
+ ::ffff:1.2.3.4/128 | ::ffff:1.2.3.4/128
+(17 rows)
+
+SELECT c AS cidr, broadcast(c),
+ i AS inet, broadcast(i) FROM INET_TBL;
+ cidr | broadcast | inet | broadcast
+--------------------+------------------+------------------+---------------------------------------
+ 192.168.1.0/24 | 192.168.1.255/24 | 192.168.1.226/24 | 192.168.1.255/24
+ 192.168.1.0/26 | 192.168.1.63/26 | 192.168.1.226 | 192.168.1.226
+ 192.168.1.0/24 | 192.168.1.255/24 | 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.255/24 | 192.168.1.0/25 | 192.168.1.127/25
+ 192.168.1.0/24 | 192.168.1.255/24 | 192.168.1.255/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.255/24 | 192.168.1.255/25 | 192.168.1.255/25
+ 10.0.0.0/8 | 10.255.255.255/8 | 10.1.2.3/8 | 10.255.255.255/8
+ 10.0.0.0/32 | 10.0.0.0 | 10.1.2.3/8 | 10.255.255.255/8
+ 10.1.2.3/32 | 10.1.2.3 | 10.1.2.3 | 10.1.2.3
+ 10.1.2.0/24 | 10.1.2.255/24 | 10.1.2.3/24 | 10.1.2.255/24
+ 10.1.0.0/16 | 10.1.255.255/16 | 10.1.2.3/16 | 10.1.255.255/16
+ 10.0.0.0/8 | 10.255.255.255/8 | 10.1.2.3/8 | 10.255.255.255/8
+ 10.0.0.0/8 | 10.255.255.255/8 | 11.1.2.3/8 | 11.255.255.255/8
+ 10.0.0.0/8 | 10.255.255.255/8 | 9.1.2.3/8 | 9.255.255.255/8
+ 10:23::f1/128 | 10:23::f1 | 10:23::f1/64 | 10:23::ffff:ffff:ffff:ffff/64
+ 10:23::8000/113 | 10:23::ffff/113 | 10:23::ffff | 10:23::ffff
+ ::ffff:1.2.3.4/128 | ::ffff:1.2.3.4 | ::4.3.2.1/24 | 0:ff:ffff:ffff:ffff:ffff:ffff:ffff/24
+(17 rows)
+
+SELECT c AS cidr, network(c) AS "network(cidr)",
+ i AS inet, network(i) AS "network(inet)" FROM INET_TBL;
+ cidr | network(cidr) | inet | network(inet)
+--------------------+--------------------+------------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/24 | 192.168.1.226/24 | 192.168.1.0/24
+ 192.168.1.0/26 | 192.168.1.0/26 | 192.168.1.226 | 192.168.1.226/32
+ 192.168.1.0/24 | 192.168.1.0/24 | 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.0/24 | 192.168.1.0/25 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.0/24 | 192.168.1.255/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.0/24 | 192.168.1.255/25 | 192.168.1.128/25
+ 10.0.0.0/8 | 10.0.0.0/8 | 10.1.2.3/8 | 10.0.0.0/8
+ 10.0.0.0/32 | 10.0.0.0/32 | 10.1.2.3/8 | 10.0.0.0/8
+ 10.1.2.3/32 | 10.1.2.3/32 | 10.1.2.3 | 10.1.2.3/32
+ 10.1.2.0/24 | 10.1.2.0/24 | 10.1.2.3/24 | 10.1.2.0/24
+ 10.1.0.0/16 | 10.1.0.0/16 | 10.1.2.3/16 | 10.1.0.0/16
+ 10.0.0.0/8 | 10.0.0.0/8 | 10.1.2.3/8 | 10.0.0.0/8
+ 10.0.0.0/8 | 10.0.0.0/8 | 11.1.2.3/8 | 11.0.0.0/8
+ 10.0.0.0/8 | 10.0.0.0/8 | 9.1.2.3/8 | 9.0.0.0/8
+ 10:23::f1/128 | 10:23::f1/128 | 10:23::f1/64 | 10:23::/64
+ 10:23::8000/113 | 10:23::8000/113 | 10:23::ffff | 10:23::ffff/128
+ ::ffff:1.2.3.4/128 | ::ffff:1.2.3.4/128 | ::4.3.2.1/24 | ::/24
+(17 rows)
+
+SELECT c AS cidr, masklen(c) AS "masklen(cidr)",
+ i AS inet, masklen(i) AS "masklen(inet)" FROM INET_TBL;
+ cidr | masklen(cidr) | inet | masklen(inet)
+--------------------+---------------+------------------+---------------
+ 192.168.1.0/24 | 24 | 192.168.1.226/24 | 24
+ 192.168.1.0/26 | 26 | 192.168.1.226 | 32
+ 192.168.1.0/24 | 24 | 192.168.1.0/24 | 24
+ 192.168.1.0/24 | 24 | 192.168.1.0/25 | 25
+ 192.168.1.0/24 | 24 | 192.168.1.255/24 | 24
+ 192.168.1.0/24 | 24 | 192.168.1.255/25 | 25
+ 10.0.0.0/8 | 8 | 10.1.2.3/8 | 8
+ 10.0.0.0/32 | 32 | 10.1.2.3/8 | 8
+ 10.1.2.3/32 | 32 | 10.1.2.3 | 32
+ 10.1.2.0/24 | 24 | 10.1.2.3/24 | 24
+ 10.1.0.0/16 | 16 | 10.1.2.3/16 | 16
+ 10.0.0.0/8 | 8 | 10.1.2.3/8 | 8
+ 10.0.0.0/8 | 8 | 11.1.2.3/8 | 8
+ 10.0.0.0/8 | 8 | 9.1.2.3/8 | 8
+ 10:23::f1/128 | 128 | 10:23::f1/64 | 64
+ 10:23::8000/113 | 113 | 10:23::ffff | 128
+ ::ffff:1.2.3.4/128 | 128 | ::4.3.2.1/24 | 24
+(17 rows)
+
+SELECT c AS cidr, masklen(c) AS "masklen(cidr)",
+ i AS inet, masklen(i) AS "masklen(inet)" FROM INET_TBL
+ WHERE masklen(c) <= 8;
+ cidr | masklen(cidr) | inet | masklen(inet)
+------------+---------------+------------+---------------
+ 10.0.0.0/8 | 8 | 10.1.2.3/8 | 8
+ 10.0.0.0/8 | 8 | 10.1.2.3/8 | 8
+ 10.0.0.0/8 | 8 | 11.1.2.3/8 | 8
+ 10.0.0.0/8 | 8 | 9.1.2.3/8 | 8
+(4 rows)
+
+SELECT c AS cidr, i AS inet FROM INET_TBL
+ WHERE c = i;
+ cidr | inet
+----------------+----------------
+ 192.168.1.0/24 | 192.168.1.0/24
+ 10.1.2.3/32 | 10.1.2.3
+(2 rows)
+
+SELECT 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;
+ i | c | lt | le | eq | ge | gt | ne | sb | sbe | sup | spe | ovr
+------------------+--------------------+----+----+----+----+----+----+----+-----+-----+-----+-----
+ 192.168.1.226/24 | 192.168.1.0/24 | f | f | f | t | t | t | f | t | f | t | t
+ 192.168.1.226 | 192.168.1.0/26 | f | f | f | t | t | t | f | f | f | f | f
+ 192.168.1.0/24 | 192.168.1.0/24 | f | t | t | t | f | f | f | t | f | t | t
+ 192.168.1.0/25 | 192.168.1.0/24 | f | f | f | t | t | t | t | t | f | f | t
+ 192.168.1.255/24 | 192.168.1.0/24 | f | f | f | t | t | t | f | t | f | t | t
+ 192.168.1.255/25 | 192.168.1.0/24 | f | f | f | t | t | t | t | t | f | f | t
+ 10.1.2.3/8 | 10.0.0.0/8 | f | f | f | t | t | t | f | t | f | t | t
+ 10.1.2.3/8 | 10.0.0.0/32 | t | t | f | f | f | t | f | f | t | t | t
+ 10.1.2.3 | 10.1.2.3/32 | f | t | t | t | f | f | f | t | f | t | t
+ 10.1.2.3/24 | 10.1.2.0/24 | f | f | f | t | t | t | f | t | f | t | t
+ 10.1.2.3/16 | 10.1.0.0/16 | f | f | f | t | t | t | f | t | f | t | t
+ 10.1.2.3/8 | 10.0.0.0/8 | f | f | f | t | t | t | f | t | f | t | t
+ 11.1.2.3/8 | 10.0.0.0/8 | f | f | f | t | t | t | f | f | f | f | f
+ 9.1.2.3/8 | 10.0.0.0/8 | t | t | f | f | f | t | f | f | f | f | f
+ 10:23::f1/64 | 10:23::f1/128 | t | t | f | f | f | t | f | f | t | t | t
+ 10:23::ffff | 10:23::8000/113 | f | f | f | t | t | t | t | t | f | f | t
+ ::4.3.2.1/24 | ::ffff:1.2.3.4/128 | t | t | f | f | f | t | f | f | t | t | t
+(17 rows)
+
+SELECT max(i) AS max, min(i) AS min FROM INET_TBL;
+ max | min
+-------------+-----------
+ 10:23::ffff | 9.1.2.3/8
+(1 row)
+
+SELECT max(c) AS max, min(c) AS min FROM INET_TBL;
+ max | min
+-----------------+------------
+ 10:23::8000/113 | 10.0.0.0/8
+(1 row)
+
+-- check the conversion to/from text and set_netmask
+SELECT set_masklen(inet(text(i)), 24) FROM INET_TBL;
+ set_masklen
+------------------
+ 192.168.1.226/24
+ 192.168.1.226/24
+ 192.168.1.0/24
+ 192.168.1.0/24
+ 192.168.1.255/24
+ 192.168.1.255/24
+ 10.1.2.3/24
+ 10.1.2.3/24
+ 10.1.2.3/24
+ 10.1.2.3/24
+ 10.1.2.3/24
+ 10.1.2.3/24
+ 11.1.2.3/24
+ 9.1.2.3/24
+ 10:23::f1/24
+ 10:23::ffff/24
+ ::4.3.2.1/24
+(17 rows)
+
+-- check that btree index works correctly
+CREATE INDEX inet_idx1 ON inet_tbl(i);
+SET enable_seqscan TO off;
+EXPLAIN (COSTS OFF)
+SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr;
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ Index Scan using inet_idx1 on inet_tbl
+ Index Cond: ((i > '192.168.1.0/24'::inet) AND (i <= '192.168.1.255'::inet))
+ Filter: (i << '192.168.1.0/24'::inet)
+(3 rows)
+
+SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr;
+ c | i
+----------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr;
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ Index Scan using inet_idx1 on inet_tbl
+ Index Cond: ((i >= '192.168.1.0/24'::inet) AND (i <= '192.168.1.255'::inet))
+ Filter: (i <<= '192.168.1.0/24'::inet)
+(3 rows)
+
+SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr;
+ c | i
+----------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+(6 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >>= i;
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ Index Scan using inet_idx1 on inet_tbl
+ Index Cond: ((i >= '192.168.1.0/24'::inet) AND (i <= '192.168.1.255'::inet))
+ Filter: ('192.168.1.0/24'::inet >>= i)
+(3 rows)
+
+SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >>= i;
+ c | i
+----------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+(6 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >> i;
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ Index Scan using inet_idx1 on inet_tbl
+ Index Cond: ((i > '192.168.1.0/24'::inet) AND (i <= '192.168.1.255'::inet))
+ Filter: ('192.168.1.0/24'::inet >> i)
+(3 rows)
+
+SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >> i;
+ c | i
+----------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+(3 rows)
+
+SET enable_seqscan TO on;
+DROP INDEX inet_idx1;
+-- check that gist index works correctly
+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 i;
+ c | i
+----------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+(3 rows)
+
+SELECT * FROM inet_tbl WHERE i <<= '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+----------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+(6 rows)
+
+SELECT * FROM inet_tbl WHERE i && '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+----------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+(6 rows)
+
+SELECT * FROM inet_tbl WHERE i >>= '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+----------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+(3 rows)
+
+SELECT * FROM inet_tbl WHERE i >> '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+---+---
+(0 rows)
+
+SELECT * FROM inet_tbl WHERE i < '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+-------------+-------------
+ 10.0.0.0/8 | 9.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.1.0.0/16 | 10.1.2.3/16
+ 10.1.2.0/24 | 10.1.2.3/24
+ 10.1.2.3/32 | 10.1.2.3
+ 10.0.0.0/8 | 11.1.2.3/8
+(8 rows)
+
+SELECT * FROM inet_tbl WHERE i <= '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+----------------+----------------
+ 10.0.0.0/8 | 9.1.2.3/8
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.1.0.0/16 | 10.1.2.3/16
+ 10.1.2.0/24 | 10.1.2.3/24
+ 10.1.2.3/32 | 10.1.2.3
+ 10.0.0.0/8 | 11.1.2.3/8
+ 192.168.1.0/24 | 192.168.1.0/24
+(9 rows)
+
+SELECT * FROM inet_tbl WHERE i = '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+----------------+----------------
+ 192.168.1.0/24 | 192.168.1.0/24
+(1 row)
+
+SELECT * FROM inet_tbl WHERE i >= '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+--------------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+ ::ffff:1.2.3.4/128 | ::4.3.2.1/24
+ 10:23::f1/128 | 10:23::f1/64
+ 10:23::8000/113 | 10:23::ffff
+(9 rows)
+
+SELECT * FROM inet_tbl WHERE i > '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+--------------------+------------------
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+ ::ffff:1.2.3.4/128 | ::4.3.2.1/24
+ 10:23::f1/128 | 10:23::f1/64
+ 10:23::8000/113 | 10:23::ffff
+(8 rows)
+
+SELECT * FROM inet_tbl WHERE i <> '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+--------------------+------------------
+ 10.0.0.0/8 | 9.1.2.3/8
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.1.0.0/16 | 10.1.2.3/16
+ 10.1.2.0/24 | 10.1.2.3/24
+ 10.1.2.3/32 | 10.1.2.3
+ 10.0.0.0/8 | 11.1.2.3/8
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+ ::ffff:1.2.3.4/128 | ::4.3.2.1/24
+ 10:23::f1/128 | 10:23::f1/64
+ 10:23::8000/113 | 10:23::ffff
+(16 rows)
+
+-- test index-only scans
+EXPLAIN (COSTS OFF)
+SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY i;
+ QUERY PLAN
+---------------------------------------------------
+ Sort
+ Sort Key: i
+ -> Index Only Scan using inet_idx2 on inet_tbl
+ Index Cond: (i << '192.168.1.0/24'::inet)
+(4 rows)
+
+SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY i;
+ i
+------------------
+ 192.168.1.0/25
+ 192.168.1.255/25
+ 192.168.1.226
+(3 rows)
+
+SET enable_seqscan TO on;
+DROP INDEX inet_idx2;
+-- check that spgist index works correctly
+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 i;
+ c | i
+----------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+(3 rows)
+
+SELECT * FROM inet_tbl WHERE i <<= '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+----------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+(6 rows)
+
+SELECT * FROM inet_tbl WHERE i && '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+----------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+(6 rows)
+
+SELECT * FROM inet_tbl WHERE i >>= '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+----------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+(3 rows)
+
+SELECT * FROM inet_tbl WHERE i >> '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+---+---
+(0 rows)
+
+SELECT * FROM inet_tbl WHERE i < '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+-------------+-------------
+ 10.0.0.0/8 | 9.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.1.0.0/16 | 10.1.2.3/16
+ 10.1.2.0/24 | 10.1.2.3/24
+ 10.1.2.3/32 | 10.1.2.3
+ 10.0.0.0/8 | 11.1.2.3/8
+(8 rows)
+
+SELECT * FROM inet_tbl WHERE i <= '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+----------------+----------------
+ 10.0.0.0/8 | 9.1.2.3/8
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.1.0.0/16 | 10.1.2.3/16
+ 10.1.2.0/24 | 10.1.2.3/24
+ 10.1.2.3/32 | 10.1.2.3
+ 10.0.0.0/8 | 11.1.2.3/8
+ 192.168.1.0/24 | 192.168.1.0/24
+(9 rows)
+
+SELECT * FROM inet_tbl WHERE i = '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+----------------+----------------
+ 192.168.1.0/24 | 192.168.1.0/24
+(1 row)
+
+SELECT * FROM inet_tbl WHERE i >= '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+--------------------+------------------
+ 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+ ::ffff:1.2.3.4/128 | ::4.3.2.1/24
+ 10:23::f1/128 | 10:23::f1/64
+ 10:23::8000/113 | 10:23::ffff
+(9 rows)
+
+SELECT * FROM inet_tbl WHERE i > '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+--------------------+------------------
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+ ::ffff:1.2.3.4/128 | ::4.3.2.1/24
+ 10:23::f1/128 | 10:23::f1/64
+ 10:23::8000/113 | 10:23::ffff
+(8 rows)
+
+SELECT * FROM inet_tbl WHERE i <> '192.168.1.0/24'::cidr ORDER BY i;
+ c | i
+--------------------+------------------
+ 10.0.0.0/8 | 9.1.2.3/8
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
+ 10.0.0.0/8 | 10.1.2.3/8
+ 10.1.0.0/16 | 10.1.2.3/16
+ 10.1.2.0/24 | 10.1.2.3/24
+ 10.1.2.3/32 | 10.1.2.3
+ 10.0.0.0/8 | 11.1.2.3/8
+ 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.0/24 | 192.168.1.255/25
+ 192.168.1.0/26 | 192.168.1.226
+ ::ffff:1.2.3.4/128 | ::4.3.2.1/24
+ 10:23::f1/128 | 10:23::f1/64
+ 10:23::8000/113 | 10:23::ffff
+(16 rows)
+
+-- test index-only scans
+EXPLAIN (COSTS OFF)
+SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY i;
+ QUERY PLAN
+---------------------------------------------------
+ Sort
+ Sort Key: i
+ -> Index Only Scan using inet_idx3 on inet_tbl
+ Index Cond: (i << '192.168.1.0/24'::inet)
+(4 rows)
+
+SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY i;
+ i
+------------------
+ 192.168.1.0/25
+ 192.168.1.255/25
+ 192.168.1.226
+(3 rows)
+
+SET enable_seqscan TO on;
+DROP INDEX inet_idx3;
+-- simple tests of inet boolean and arithmetic operators
+SELECT i, ~i AS "~i" FROM inet_tbl;
+ i | ~i
+------------------+--------------------------------------------
+ 192.168.1.226/24 | 63.87.254.29/24
+ 192.168.1.226 | 63.87.254.29
+ 192.168.1.0/24 | 63.87.254.255/24
+ 192.168.1.0/25 | 63.87.254.255/25
+ 192.168.1.255/24 | 63.87.254.0/24
+ 192.168.1.255/25 | 63.87.254.0/25
+ 10.1.2.3/8 | 245.254.253.252/8
+ 10.1.2.3/8 | 245.254.253.252/8
+ 10.1.2.3 | 245.254.253.252
+ 10.1.2.3/24 | 245.254.253.252/24
+ 10.1.2.3/16 | 245.254.253.252/16
+ 10.1.2.3/8 | 245.254.253.252/8
+ 11.1.2.3/8 | 244.254.253.252/8
+ 9.1.2.3/8 | 246.254.253.252/8
+ 10:23::f1/64 | ffef:ffdc:ffff:ffff:ffff:ffff:ffff:ff0e/64
+ 10:23::ffff | ffef:ffdc:ffff:ffff:ffff:ffff:ffff:0
+ ::4.3.2.1/24 | ffff:ffff:ffff:ffff:ffff:ffff:fbfc:fdfe/24
+(17 rows)
+
+SELECT i, c, i & c AS "and" FROM inet_tbl;
+ i | c | and
+------------------+--------------------+----------------
+ 192.168.1.226/24 | 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.226 | 192.168.1.0/26 | 192.168.1.0
+ 192.168.1.0/24 | 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/25 | 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.255/24 | 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.255/25 | 192.168.1.0/24 | 192.168.1.0/25
+ 10.1.2.3/8 | 10.0.0.0/8 | 10.0.0.0/8
+ 10.1.2.3/8 | 10.0.0.0/32 | 10.0.0.0
+ 10.1.2.3 | 10.1.2.3/32 | 10.1.2.3
+ 10.1.2.3/24 | 10.1.2.0/24 | 10.1.2.0/24
+ 10.1.2.3/16 | 10.1.0.0/16 | 10.1.0.0/16
+ 10.1.2.3/8 | 10.0.0.0/8 | 10.0.0.0/8
+ 11.1.2.3/8 | 10.0.0.0/8 | 10.0.0.0/8
+ 9.1.2.3/8 | 10.0.0.0/8 | 8.0.0.0/8
+ 10:23::f1/64 | 10:23::f1/128 | 10:23::f1
+ 10:23::ffff | 10:23::8000/113 | 10:23::8000
+ ::4.3.2.1/24 | ::ffff:1.2.3.4/128 | ::0.2.2.0
+(17 rows)
+
+SELECT i, c, i | c AS "or" FROM inet_tbl;
+ i | c | or
+------------------+--------------------+------------------
+ 192.168.1.226/24 | 192.168.1.0/24 | 192.168.1.226/24
+ 192.168.1.226 | 192.168.1.0/26 | 192.168.1.226
+ 192.168.1.0/24 | 192.168.1.0/24 | 192.168.1.0/24
+ 192.168.1.0/25 | 192.168.1.0/24 | 192.168.1.0/25
+ 192.168.1.255/24 | 192.168.1.0/24 | 192.168.1.255/24
+ 192.168.1.255/25 | 192.168.1.0/24 | 192.168.1.255/25
+ 10.1.2.3/8 | 10.0.0.0/8 | 10.1.2.3/8
+ 10.1.2.3/8 | 10.0.0.0/32 | 10.1.2.3
+ 10.1.2.3 | 10.1.2.3/32 | 10.1.2.3
+ 10.1.2.3/24 | 10.1.2.0/24 | 10.1.2.3/24
+ 10.1.2.3/16 | 10.1.0.0/16 | 10.1.2.3/16
+ 10.1.2.3/8 | 10.0.0.0/8 | 10.1.2.3/8
+ 11.1.2.3/8 | 10.0.0.0/8 | 11.1.2.3/8
+ 9.1.2.3/8 | 10.0.0.0/8 | 11.1.2.3/8
+ 10:23::f1/64 | 10:23::f1/128 | 10:23::f1
+ 10:23::ffff | 10:23::8000/113 | 10:23::ffff
+ ::4.3.2.1/24 | ::ffff:1.2.3.4/128 | ::ffff:5.3.3.5
+(17 rows)
+
+SELECT i, i + 500 AS "i+500" FROM inet_tbl;
+ i | i+500
+------------------+------------------
+ 192.168.1.226/24 | 192.168.3.214/24
+ 192.168.1.226 | 192.168.3.214
+ 192.168.1.0/24 | 192.168.2.244/24
+ 192.168.1.0/25 | 192.168.2.244/25
+ 192.168.1.255/24 | 192.168.3.243/24
+ 192.168.1.255/25 | 192.168.3.243/25
+ 10.1.2.3/8 | 10.1.3.247/8
+ 10.1.2.3/8 | 10.1.3.247/8
+ 10.1.2.3 | 10.1.3.247
+ 10.1.2.3/24 | 10.1.3.247/24
+ 10.1.2.3/16 | 10.1.3.247/16
+ 10.1.2.3/8 | 10.1.3.247/8
+ 11.1.2.3/8 | 11.1.3.247/8
+ 9.1.2.3/8 | 9.1.3.247/8
+ 10:23::f1/64 | 10:23::2e5/64
+ 10:23::ffff | 10:23::1:1f3
+ ::4.3.2.1/24 | ::4.3.3.245/24
+(17 rows)
+
+SELECT i, i - 500 AS "i-500" FROM inet_tbl;
+ i | i-500
+------------------+----------------------------------------
+ 192.168.1.226/24 | 192.167.255.238/24
+ 192.168.1.226 | 192.167.255.238
+ 192.168.1.0/24 | 192.167.255.12/24
+ 192.168.1.0/25 | 192.167.255.12/25
+ 192.168.1.255/24 | 192.168.0.11/24
+ 192.168.1.255/25 | 192.168.0.11/25
+ 10.1.2.3/8 | 10.1.0.15/8
+ 10.1.2.3/8 | 10.1.0.15/8
+ 10.1.2.3 | 10.1.0.15
+ 10.1.2.3/24 | 10.1.0.15/24
+ 10.1.2.3/16 | 10.1.0.15/16
+ 10.1.2.3/8 | 10.1.0.15/8
+ 11.1.2.3/8 | 11.1.0.15/8
+ 9.1.2.3/8 | 9.1.0.15/8
+ 10:23::f1/64 | 10:22:ffff:ffff:ffff:ffff:ffff:fefd/64
+ 10:23::ffff | 10:23::fe0b
+ ::4.3.2.1/24 | ::4.3.0.13/24
+(17 rows)
+
+SELECT i, c, i - c AS "minus" FROM inet_tbl;
+ i | c | minus
+------------------+--------------------+------------------
+ 192.168.1.226/24 | 192.168.1.0/24 | 226
+ 192.168.1.226 | 192.168.1.0/26 | 226
+ 192.168.1.0/24 | 192.168.1.0/24 | 0
+ 192.168.1.0/25 | 192.168.1.0/24 | 0
+ 192.168.1.255/24 | 192.168.1.0/24 | 255
+ 192.168.1.255/25 | 192.168.1.0/24 | 255
+ 10.1.2.3/8 | 10.0.0.0/8 | 66051
+ 10.1.2.3/8 | 10.0.0.0/32 | 66051
+ 10.1.2.3 | 10.1.2.3/32 | 0
+ 10.1.2.3/24 | 10.1.2.0/24 | 3
+ 10.1.2.3/16 | 10.1.0.0/16 | 515
+ 10.1.2.3/8 | 10.0.0.0/8 | 66051
+ 11.1.2.3/8 | 10.0.0.0/8 | 16843267
+ 9.1.2.3/8 | 10.0.0.0/8 | -16711165
+ 10:23::f1/64 | 10:23::f1/128 | 0
+ 10:23::ffff | 10:23::8000/113 | 32767
+ ::4.3.2.1/24 | ::ffff:1.2.3.4/128 | -281470631346435
+(17 rows)
+
+SELECT '127.0.0.1'::inet + 257;
+ ?column?
+-----------
+ 127.0.1.2
+(1 row)
+
+SELECT ('127.0.0.1'::inet + 257) - 257;
+ ?column?
+-----------
+ 127.0.0.1
+(1 row)
+
+SELECT '127::1'::inet + 257;
+ ?column?
+----------
+ 127::102
+(1 row)
+
+SELECT ('127::1'::inet + 257) - 257;
+ ?column?
+----------
+ 127::1
+(1 row)
+
+SELECT '127.0.0.2'::inet - ('127.0.0.2'::inet + 500);
+ ?column?
+----------
+ -500
+(1 row)
+
+SELECT '127.0.0.2'::inet - ('127.0.0.2'::inet - 500);
+ ?column?
+----------
+ 500
+(1 row)
+
+SELECT '127::2'::inet - ('127::2'::inet + 500);
+ ?column?
+----------
+ -500
+(1 row)
+
+SELECT '127::2'::inet - ('127::2'::inet - 500);
+ ?column?
+----------
+ 500
+(1 row)
+
+-- these should give overflow errors:
+SELECT '127.0.0.1'::inet + 10000000000;
+ERROR: result is out of range
+SELECT '127.0.0.1'::inet - 10000000000;
+ERROR: result is out of range
+SELECT '126::1'::inet - '127::2'::inet;
+ERROR: result is out of range
+SELECT '127::1'::inet - '126::2'::inet;
+ERROR: result is out of range
+-- but not these
+SELECT '127::1'::inet + 10000000000;
+ ?column?
+------------------
+ 127::2:540b:e401
+(1 row)
+
+SELECT '127::1'::inet - '127::2'::inet;
+ ?column?
+----------
+ -1
+(1 row)
+
+-- 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;
+ERROR: cannot merge addresses from different families
+-- fix it by inet_same_family() condition
+SELECT inet_merge(c, i) FROM INET_TBL WHERE inet_same_family(c, i);
+ inet_merge
+-----------------
+ 192.168.1.0/24
+ 192.168.1.0/24
+ 192.168.1.0/24
+ 192.168.1.0/24
+ 192.168.1.0/24
+ 192.168.1.0/24
+ 10.0.0.0/8
+ 10.0.0.0/8
+ 10.1.2.3/32
+ 10.1.2.0/24
+ 10.1.0.0/16
+ 10.0.0.0/8
+ 10.0.0.0/7
+ 8.0.0.0/6
+ 10:23::/64
+ 10:23::8000/113
+ ::/24
+(17 rows)
+
+-- Test inet sortsupport with a variety of boundary inputs:
+SELECT a FROM (VALUES
+ ('0.0.0.0/0'::inet),
+ ('0.0.0.0/1'::inet),
+ ('0.0.0.0/32'::inet),
+ ('0.0.0.1/0'::inet),
+ ('0.0.0.1/1'::inet),
+ ('127.126.127.127/0'::inet),
+ ('127.127.127.127/0'::inet),
+ ('127.128.127.127/0'::inet),
+ ('192.168.1.0/24'::inet),
+ ('192.168.1.0/25'::inet),
+ ('192.168.1.1/23'::inet),
+ ('192.168.1.1/5'::inet),
+ ('192.168.1.1/6'::inet),
+ ('192.168.1.1/25'::inet),
+ ('192.168.1.2/25'::inet),
+ ('192.168.1.1/26'::inet),
+ ('192.168.1.2/26'::inet),
+ ('192.168.1.2/23'::inet),
+ ('192.168.1.255/5'::inet),
+ ('192.168.1.255/6'::inet),
+ ('192.168.1.3/1'::inet),
+ ('192.168.1.3/23'::inet),
+ ('192.168.1.4/0'::inet),
+ ('192.168.1.5/0'::inet),
+ ('255.0.0.0/0'::inet),
+ ('255.1.0.0/0'::inet),
+ ('255.2.0.0/0'::inet),
+ ('255.255.000.000/0'::inet),
+ ('255.255.000.000/0'::inet),
+ ('255.255.000.000/15'::inet),
+ ('255.255.000.000/16'::inet),
+ ('255.255.255.254/32'::inet),
+ ('255.255.255.000/32'::inet),
+ ('255.255.255.001/31'::inet),
+ ('255.255.255.002/31'::inet),
+ ('255.255.255.003/31'::inet),
+ ('255.255.255.003/32'::inet),
+ ('255.255.255.001/32'::inet),
+ ('255.255.255.255/0'::inet),
+ ('255.255.255.255/0'::inet),
+ ('255.255.255.255/0'::inet),
+ ('255.255.255.255/1'::inet),
+ ('255.255.255.255/16'::inet),
+ ('255.255.255.255/16'::inet),
+ ('255.255.255.255/31'::inet),
+ ('255.255.255.255/32'::inet),
+ ('255.255.255.253/32'::inet),
+ ('255.255.255.252/32'::inet),
+ ('255.3.0.0/0'::inet),
+ ('0000:0000:0000:0000:0000:0000:0000:0000/0'::inet),
+ ('0000:0000:0000:0000:0000:0000:0000:0000/128'::inet),
+ ('0000:0000:0000:0000:0000:0000:0000:0001/128'::inet),
+ ('10:23::f1/64'::inet),
+ ('10:23::f1/65'::inet),
+ ('10:23::ffff'::inet),
+ ('127::1'::inet),
+ ('127::2'::inet),
+ ('8000:0000:0000:0000:0000:0000:0000:0000/1'::inet),
+ ('::1:ffff:ffff:ffff:ffff/128'::inet),
+ ('::2:ffff:ffff:ffff:ffff/128'::inet),
+ ('::4:3:2:0/24'::inet),
+ ('::4:3:2:1/24'::inet),
+ ('::4:3:2:2/24'::inet),
+ ('ffff:83e7:f118:57dc:6093:6d92:689d:58cf/70'::inet),
+ ('ffff:84b0:4775:536e:c3ed:7116:a6d6:34f0/44'::inet),
+ ('ffff:8566:f84:5867:47f1:7867:d2ba:8a1a/69'::inet),
+ ('ffff:8883:f028:7d2:4d68:d510:7d6b:ac43/73'::inet),
+ ('ffff:8ae8:7c14:65b3:196:8e4a:89ae:fb30/89'::inet),
+ ('ffff:8dd0:646:694c:7c16:7e35:6a26:171/104'::inet),
+ ('ffff:8eef:cbf:700:eda3:ae32:f4b4:318b/121'::inet),
+ ('ffff:90e7:e744:664:a93:8efe:1f25:7663/122'::inet),
+ ('ffff:9597:c69c:8b24:57a:8639:ec78:6026/111'::inet),
+ ('ffff:9e86:79ea:f16e:df31:8e4d:7783:532e/88'::inet),
+ ('ffff:a0c7:82d3:24de:f762:6e1f:316d:3fb2/23'::inet),
+ ('ffff:fffa:ffff:ffff:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:fffb:ffff:ffff:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:fffc:ffff:ffff:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:fffd:ffff:ffff:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:fffe:ffff:ffff:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:fffa:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:fffb:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:fffc:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:fffd::/128'::inet),
+ ('ffff:ffff:ffff:fffd:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:fffe::/128'::inet),
+ ('ffff:ffff:ffff:fffe:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:ffff:4:3:2:0/24'::inet),
+ ('ffff:ffff:ffff:ffff:4:3:2:1/24'::inet),
+ ('ffff:ffff:ffff:ffff:4:3:2:2/24'::inet),
+ ('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128'::inet)
+) AS i(a) ORDER BY a;
+ a
+--------------------------------------------
+ 0.0.0.0/0
+ 0.0.0.1/0
+ 127.126.127.127/0
+ 127.127.127.127/0
+ 127.128.127.127/0
+ 192.168.1.4/0
+ 192.168.1.5/0
+ 255.0.0.0/0
+ 255.1.0.0/0
+ 255.2.0.0/0
+ 255.3.0.0/0
+ 255.255.0.0/0
+ 255.255.0.0/0
+ 255.255.255.255/0
+ 255.255.255.255/0
+ 255.255.255.255/0
+ 0.0.0.0/1
+ 0.0.0.1/1
+ 0.0.0.0
+ 192.168.1.3/1
+ 255.255.255.255/1
+ 192.168.1.1/5
+ 192.168.1.255/5
+ 192.168.1.1/6
+ 192.168.1.255/6
+ 192.168.1.1/23
+ 192.168.1.2/23
+ 192.168.1.3/23
+ 192.168.1.0/24
+ 192.168.1.0/25
+ 192.168.1.1/25
+ 192.168.1.2/25
+ 192.168.1.1/26
+ 192.168.1.2/26
+ 255.255.0.0/15
+ 255.255.0.0/16
+ 255.255.255.255/16
+ 255.255.255.255/16
+ 255.255.255.1/31
+ 255.255.255.0
+ 255.255.255.1
+ 255.255.255.2/31
+ 255.255.255.3/31
+ 255.255.255.3
+ 255.255.255.252
+ 255.255.255.253
+ 255.255.255.255/31
+ 255.255.255.254
+ 255.255.255.255
+ ::/0
+ ffff:fffa:ffff:ffff:ffff:ffff:ffff:ffff/0
+ ffff:fffb:ffff:ffff:ffff:ffff:ffff:ffff/0
+ ffff:fffc:ffff:ffff:ffff:ffff:ffff:ffff/0
+ ffff:fffd:ffff:ffff:ffff:ffff:ffff:ffff/0
+ ffff:fffe:ffff:ffff:ffff:ffff:ffff:ffff/0
+ ffff:ffff:ffff:fffa:ffff:ffff:ffff:ffff/0
+ ffff:ffff:ffff:fffb:ffff:ffff:ffff:ffff/0
+ ffff:ffff:ffff:fffc:ffff:ffff:ffff:ffff/0
+ ffff:ffff:ffff:fffd:ffff:ffff:ffff:ffff/0
+ ffff:ffff:ffff:fffe:ffff:ffff:ffff:ffff/0
+ ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/0
+ ::4:3:2:0/24
+ ::4:3:2:1/24
+ ::4:3:2:2/24
+ ::
+ ::1
+ ::1:ffff:ffff:ffff:ffff
+ ::2:ffff:ffff:ffff:ffff
+ 10:23::f1/64
+ 10:23::f1/65
+ 10:23::ffff
+ 127::1
+ 127::2
+ 8000::/1
+ ffff:83e7:f118:57dc:6093:6d92:689d:58cf/70
+ ffff:84b0:4775:536e:c3ed:7116:a6d6:34f0/44
+ ffff:8566:f84:5867:47f1:7867:d2ba:8a1a/69
+ ffff:8883:f028:7d2:4d68:d510:7d6b:ac43/73
+ ffff:8ae8:7c14:65b3:196:8e4a:89ae:fb30/89
+ ffff:8dd0:646:694c:7c16:7e35:6a26:171/104
+ ffff:8eef:cbf:700:eda3:ae32:f4b4:318b/121
+ ffff:90e7:e744:664:a93:8efe:1f25:7663/122
+ ffff:9597:c69c:8b24:57a:8639:ec78:6026/111
+ ffff:9e86:79ea:f16e:df31:8e4d:7783:532e/88
+ ffff:a0c7:82d3:24de:f762:6e1f:316d:3fb2/23
+ ffff:ffff:ffff:ffff:4:3:2:0/24
+ ffff:ffff:ffff:ffff:4:3:2:1/24
+ ffff:ffff:ffff:ffff:4:3:2:2/24
+ ffff:ffff:ffff:fffd::
+ ffff:ffff:ffff:fffe::
+ ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+(91 rows)
+
diff --git a/src/test/regress/expected/infinite_recurse.out b/src/test/regress/expected/infinite_recurse.out
new file mode 100644
index 0000000..aa102fa
--- /dev/null
+++ b/src/test/regress/expected/infinite_recurse.out
@@ -0,0 +1,24 @@
+-- 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;
+-- Unfortunately, up till mid 2020 the Linux kernel had a bug in PPC64
+-- signal handling that would cause this test to crash if it happened
+-- to receive an sinval catchup interrupt while the stack is deep:
+-- https://bugzilla.kernel.org/show_bug.cgi?id=205183
+-- It is likely to be many years before that bug disappears from all
+-- production kernels, so disable this test on such platforms.
+-- (We still create the function, so as not to have a cross-platform
+-- difference in the end state of the regression database.)
+SELECT version() ~ 'powerpc64[^,]*-linux-gnu'
+ AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+-- The full error report is not very stable, so we show only SQLSTATE
+-- and primary error message.
+\set VERBOSITY sqlstate
+select infinite_recurse();
+ERROR: 54001
+\echo :LAST_ERROR_MESSAGE
+stack depth limit exceeded
diff --git a/src/test/regress/expected/infinite_recurse_1.out b/src/test/regress/expected/infinite_recurse_1.out
new file mode 100644
index 0000000..b2c99a0
--- /dev/null
+++ b/src/test/regress/expected/infinite_recurse_1.out
@@ -0,0 +1,16 @@
+-- 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;
+-- Unfortunately, up till mid 2020 the Linux kernel had a bug in PPC64
+-- signal handling that would cause this test to crash if it happened
+-- to receive an sinval catchup interrupt while the stack is deep:
+-- https://bugzilla.kernel.org/show_bug.cgi?id=205183
+-- It is likely to be many years before that bug disappears from all
+-- production kernels, so disable this test on such platforms.
+-- (We still create the function, so as not to have a cross-platform
+-- difference in the end state of the regression database.)
+SELECT version() ~ 'powerpc64[^,]*-linux-gnu'
+ AS skip_test \gset
+\if :skip_test
+\quit
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
new file mode 100644
index 0000000..06f4428
--- /dev/null
+++ b/src/test/regress/expected/inherit.out
@@ -0,0 +1,2532 @@
+--
+-- 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);
+NOTICE: merging multiple inherited definitions of column "aa"
+NOTICE: merging multiple inherited definitions of column "aa"
+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;
+ relname | aa
+---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ c | ccc
+ c | cccc
+ c | ccccc
+ c | cccccc
+ c | ccccccc
+ c | cccccccc
+ d | ddd
+ d | dddd
+ d | ddddd
+ d | dddddd
+ d | ddddddd
+ d | dddddddd
+(24 rows)
+
+SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ d | ddd |
+ d | dddd |
+ d | ddddd |
+ d | dddddd |
+ d | ddddddd |
+ d | dddddddd |
+(12 rows)
+
+SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid;
+ relname | aa | cc
+---------+----------+----
+ c | ccc |
+ c | cccc |
+ c | ccccc |
+ c | cccccc |
+ c | ccccccc |
+ c | cccccccc |
+ d | ddd |
+ d | dddd |
+ d | ddddd |
+ d | dddddd |
+ d | ddddddd |
+ d | dddddddd |
+(12 rows)
+
+SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid;
+ relname | aa | bb | cc | dd
+---------+----------+----+----+----
+ d | ddd | | |
+ d | dddd | | |
+ d | ddddd | | |
+ d | dddddd | | |
+ d | ddddddd | | |
+ d | dddddddd | | |
+(6 rows)
+
+SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+(6 rows)
+
+SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+(6 rows)
+
+SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid;
+ relname | aa | cc
+---------+----------+----
+ c | ccc |
+ c | cccc |
+ c | ccccc |
+ c | cccccc |
+ c | ccccccc |
+ c | cccccccc |
+(6 rows)
+
+SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid;
+ relname | aa | bb | cc | dd
+---------+----------+----+----+----
+ d | ddd | | |
+ d | dddd | | |
+ d | ddddd | | |
+ d | dddddd | | |
+ d | ddddddd | | |
+ d | dddddddd | | |
+(6 rows)
+
+UPDATE a SET aa='zzzz' WHERE aa='aaaa';
+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';
+UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+---------+----------
+ a | zzzz
+ a | zzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ c | ccc
+ c | cccc
+ c | ccccc
+ c | cccccc
+ c | ccccccc
+ c | cccccccc
+ d | ddd
+ d | dddd
+ d | ddddd
+ d | dddddd
+ d | ddddddd
+ d | dddddddd
+(24 rows)
+
+SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ d | ddd |
+ d | dddd |
+ d | ddddd |
+ d | dddddd |
+ d | ddddddd |
+ d | dddddddd |
+(12 rows)
+
+SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid;
+ relname | aa | cc
+---------+----------+----
+ c | ccc |
+ c | cccc |
+ c | ccccc |
+ c | cccccc |
+ c | ccccccc |
+ c | cccccccc |
+ d | ddd |
+ d | dddd |
+ d | ddddd |
+ d | dddddd |
+ d | ddddddd |
+ d | dddddddd |
+(12 rows)
+
+SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid;
+ relname | aa | bb | cc | dd
+---------+----------+----+----+----
+ d | ddd | | |
+ d | dddd | | |
+ d | ddddd | | |
+ d | dddddd | | |
+ d | ddddddd | | |
+ d | dddddddd | | |
+(6 rows)
+
+SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+---------+--------
+ a | zzzz
+ a | zzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+(6 rows)
+
+SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+(6 rows)
+
+SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid;
+ relname | aa | cc
+---------+----------+----
+ c | ccc |
+ c | cccc |
+ c | ccccc |
+ c | cccccc |
+ c | ccccccc |
+ c | cccccccc |
+(6 rows)
+
+SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid;
+ relname | aa | bb | cc | dd
+---------+----------+----+----+----
+ d | ddd | | |
+ d | dddd | | |
+ d | ddddd | | |
+ d | dddddd | | |
+ d | ddddddd | | |
+ d | dddddddd | | |
+(6 rows)
+
+UPDATE b SET aa='new';
+SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+---------+----------
+ a | zzzz
+ a | zzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ c | ccc
+ c | cccc
+ c | ccccc
+ c | cccccc
+ c | ccccccc
+ c | cccccccc
+ d | new
+ d | new
+ d | new
+ d | new
+ d | new
+ d | new
+(24 rows)
+
+SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ d | new |
+ d | new |
+ d | new |
+ d | new |
+ d | new |
+ d | new |
+(12 rows)
+
+SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid;
+ relname | aa | cc
+---------+----------+----
+ c | ccc |
+ c | cccc |
+ c | ccccc |
+ c | cccccc |
+ c | ccccccc |
+ c | cccccccc |
+ d | new |
+ d | new |
+ d | new |
+ d | new |
+ d | new |
+ d | new |
+(12 rows)
+
+SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid;
+ relname | aa | bb | cc | dd
+---------+-----+----+----+----
+ d | new | | |
+ d | new | | |
+ d | new | | |
+ d | new | | |
+ d | new | | |
+ d | new | | |
+(6 rows)
+
+SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+---------+--------
+ a | zzzz
+ a | zzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+(6 rows)
+
+SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+(6 rows)
+
+SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid;
+ relname | aa | cc
+---------+----------+----
+ c | ccc |
+ c | cccc |
+ c | ccccc |
+ c | cccccc |
+ c | ccccccc |
+ c | cccccccc |
+(6 rows)
+
+SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid;
+ relname | aa | bb | cc | dd
+---------+-----+----+----+----
+ d | new | | |
+ d | new | | |
+ d | new | | |
+ d | new | | |
+ d | new | | |
+ d | new | | |
+(6 rows)
+
+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;
+ relname | aa
+---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ d | new
+ d | new
+ d | new
+ d | new
+ d | new
+ d | new
+(18 rows)
+
+SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ d | new |
+ d | new |
+ d | new |
+ d | new |
+ d | new |
+ d | new |
+(12 rows)
+
+SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid;
+ relname | aa | cc
+---------+-----+----
+ d | new |
+ d | new |
+ d | new |
+ d | new |
+ d | new |
+ d | new |
+(6 rows)
+
+SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid;
+ relname | aa | bb | cc | dd
+---------+-----+----+----+----
+ d | new | | |
+ d | new | | |
+ d | new | | |
+ d | new | | |
+ d | new | | |
+ d | new | | |
+(6 rows)
+
+SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+(6 rows)
+
+SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+(6 rows)
+
+SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid;
+ relname | aa | cc
+---------+----+----
+(0 rows)
+
+SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid;
+ relname | aa | bb | cc | dd
+---------+-----+----+----+----
+ d | new | | |
+ d | new | | |
+ d | new | | |
+ d | new | | |
+ d | new | | |
+ d | new | | |
+(6 rows)
+
+DELETE FROM a;
+SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+---------+----
+(0 rows)
+
+SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+---------+----+----
+(0 rows)
+
+SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid;
+ relname | aa | cc
+---------+----+----
+(0 rows)
+
+SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid;
+ relname | aa | bb | cc | dd
+---------+----+----+----+----
+(0 rows)
+
+SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+---------+----
+(0 rows)
+
+SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+---------+----+----
+(0 rows)
+
+SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid;
+ relname | aa | cc
+---------+----+----
+(0 rows)
+
+SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid;
+ relname | aa | bb | cc | dd
+---------+----+----+----+----
+(0 rows)
+
+-- Confirm PRIMARY KEY adds NOT NULL constraint to child table
+CREATE TEMP TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a);
+INSERT INTO z VALUES (NULL, 'text'); -- should fail
+ERROR: null value in column "aa" of relation "z" violates not-null constraint
+DETAIL: Failing row contains (null, text).
+-- 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;
+ QUERY PLAN
+--------------------------------------------------------
+ Update on public.some_tab
+ -> Result
+ Output: (some_tab.a + 1), NULL::oid, NULL::tid
+ One-Time Filter: false
+(4 rows)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------
+ Update on public.some_tab
+ Output: some_tab.b, some_tab.a
+ -> Result
+ Output: (some_tab.a + 1), NULL::oid, NULL::tid
+ One-Time Filter: false
+(5 rows)
+
+update some_tab set a = a + 1 where false returning b, a;
+ b | a
+---+---
+(0 rows)
+
+table some_tab;
+ a | b
+---+---
+ 1 | 2
+(1 row)
+
+drop table some_tab cascade;
+NOTICE: drop cascades to table some_tab_child
+-- 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;
+ relname | f1 | f2
+---------+----+-----
+ bar | 1 | 101
+ bar | 2 | 102
+ bar | 3 | 103
+ bar | 4 | 4
+ bar2 | 1 | 101
+ bar2 | 2 | 102
+ bar2 | 3 | 103
+ bar2 | 4 | 4
+(8 rows)
+
+-- 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;
+ relname | f1 | f2
+---------+----+-----
+ bar | 1 | 201
+ bar | 2 | 202
+ bar | 3 | 203
+ bar | 4 | 104
+ bar2 | 1 | 201
+ bar2 | 2 | 202
+ bar2 | 3 | 203
+ bar2 | 4 | 104
+(8 rows)
+
+-- 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;
+ relname | a | b
+------------------+---+---
+ parted_tab_part1 | 1 | b
+ parted_tab_part2 | 2 | b
+ parted_tab_part3 | 3 | a
+(3 rows)
+
+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;
+ relname | a | b
+------------------+---+---
+ parted_tab_part1 | 1 | b
+ parted_tab_part2 | 2 | a
+ parted_tab_part3 | 3 | a
+(3 rows)
+
+-- modifies partition key, but no rows will actually be updated
+explain update parted_tab set a = 2 where false;
+ QUERY PLAN
+--------------------------------------------------------
+ Update on parted_tab (cost=0.00..0.00 rows=0 width=0)
+ -> Result (cost=0.00..0.00 rows=0 width=10)
+ One-Time Filter: false
+(3 rows)
+
+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;
+ relname | a | b | c
+---------------------+---+---+-----
+ mlparted_tab_part1 | 1 | a |
+ mlparted_tab_part2a | 2 | a |
+ mlparted_tab_part2b | 2 | b | xxx
+ mlparted_tab_part3 | 3 | a | xxx
+(4 rows)
+
+drop table mlparted_tab;
+drop table some_tab cascade;
+NOTICE: drop cascades to table some_tab_child
+/* 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
+NOTICE: merging multiple inherited definitions of column "tomorrow"
+CREATE TABLE thirdparent (tomorrow date default now()::date - 1);
+CREATE TABLE otherchild () INHERITS (firstparent, thirdparent); -- not ok
+NOTICE: merging multiple inherited definitions of column "tomorrow"
+ERROR: column "tomorrow" inherits conflicting default values
+HINT: To resolve the conflict, specify a default explicitly.
+CREATE TABLE otherchild (tomorrow date default now())
+ INHERITS (firstparent, thirdparent); -- ok, child resolves ambiguous default
+NOTICE: merging multiple inherited definitions of column "tomorrow"
+NOTICE: merging column "tomorrow" with inherited definition
+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;
+ aa | bb | cc | dd
+----+-----+-----+-------
+ 32 | one | two | three
+(1 row)
+
+-- 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);
+NOTICE: merging multiple inherited definitions of column "f1"
+alter table parent1 alter column f1 type bigint; -- fail, conflict w/parent2
+ERROR: cannot alter inherited column "f1" of relation "childtab"
+alter table parent1 alter column f2 type bigint; -- ok
+-- 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;
+ relname | conname | contype | conislocal | coninhcount | connoinherit
+---------+---------+---------+------------+-------------+--------------
+ p1 | p1chk | c | t | 0 | t
+ p1 | p2chk | c | t | 0 | f
+(2 rows)
+
+-- Test that child does not inherit NO INHERIT constraints
+create table c1 () inherits (p1);
+\d p1
+ Table "public.p1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ ff1 | integer | | |
+Check constraints:
+ "p1chk" CHECK (ff1 > 0) NO INHERIT
+ "p2chk" CHECK (ff1 > 10)
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d c1
+ Table "public.c1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ ff1 | integer | | |
+Check constraints:
+ "p2chk" CHECK (ff1 > 10)
+Inherits: p1
+
+-- Test that child does not override inheritable constraints of the parent
+create table c2 (constraint p2chk check (ff1 > 10) no inherit) inherits (p1); --fails
+ERROR: constraint "p2chk" conflicts with inherited constraint on relation "c2"
+drop table p1 cascade;
+NOTICE: drop cascades to table c1
+-- 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);
+NOTICE: merging column "i" with inherited definition
+insert into derived (i) values (0);
+select derived::base from derived;
+ derived
+---------
+ (0)
+(1 row)
+
+select NULL::derived::base;
+ base
+------
+
+(1 row)
+
+-- remove redundant conversions.
+explain (verbose on, costs off) select row(i, b)::more_derived::derived::base from more_derived;
+ QUERY PLAN
+-------------------------------------------
+ Seq Scan on public.more_derived
+ Output: (ROW(i, b)::more_derived)::base
+(2 rows)
+
+explain (verbose on, costs off) select (1, 2)::more_derived::derived::base;
+ QUERY PLAN
+-----------------------
+ Result
+ Output: '(1)'::base
+(2 rows)
+
+drop table more_derived;
+drop table derived;
+drop table base;
+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;
+ p2text
+--------
+ hi
+(1 row)
+
+drop function p2text(p2);
+drop table c1;
+drop table p2;
+drop table p1;
+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;
+ relname | conname | contype | conislocal | coninhcount | consrc
+---------+----------+---------+------------+-------------+------------------
+ ac | ac_check | c | t | 0 | (aa IS NOT NULL)
+ bc | ac_check | c | f | 1 | (aa IS NOT NULL)
+(2 rows)
+
+insert into ac (aa) values (NULL);
+ERROR: new row for relation "ac" violates check constraint "ac_check"
+DETAIL: Failing row contains (null).
+insert into bc (aa) values (NULL);
+ERROR: new row for relation "bc" violates check constraint "ac_check"
+DETAIL: Failing row contains (null, null).
+alter table bc drop constraint ac_check; -- fail, disallowed
+ERROR: cannot drop inherited constraint "ac_check" of relation "bc"
+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;
+ relname | conname | contype | conislocal | coninhcount | consrc
+---------+---------+---------+------------+-------------+--------
+(0 rows)
+
+-- 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;
+ relname | conname | contype | conislocal | coninhcount | consrc
+---------+-------------+---------+------------+-------------+------------------
+ ac | ac_aa_check | c | t | 0 | (aa IS NOT NULL)
+ bc | ac_aa_check | c | f | 1 | (aa IS NOT NULL)
+(2 rows)
+
+insert into ac (aa) values (NULL);
+ERROR: new row for relation "ac" violates check constraint "ac_aa_check"
+DETAIL: Failing row contains (null).
+insert into bc (aa) values (NULL);
+ERROR: new row for relation "bc" violates check constraint "ac_aa_check"
+DETAIL: Failing row contains (null, null).
+alter table bc drop constraint ac_aa_check; -- fail, disallowed
+ERROR: cannot drop inherited constraint "ac_aa_check" of relation "bc"
+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;
+ relname | conname | contype | conislocal | coninhcount | consrc
+---------+---------+---------+------------+-------------+--------
+(0 rows)
+
+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;
+ relname | conname | contype | conislocal | coninhcount | consrc
+---------+----------+---------+------------+-------------+------------------
+ ac | ac_check | c | t | 0 | (aa IS NOT NULL)
+ bc | ac_check | c | t | 0 | (aa IS NOT NULL)
+(2 rows)
+
+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;
+ relname | conname | contype | conislocal | coninhcount | consrc
+---------+----------+---------+------------+-------------+------------------
+ ac | ac_check | c | t | 0 | (aa IS NOT NULL)
+(1 row)
+
+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;
+ relname | conname | contype | conislocal | coninhcount | consrc
+---------+---------+---------+------------+-------------+--------
+(0 rows)
+
+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);
+NOTICE: merging column "a" with inherited definition
+NOTICE: merging constraint "check_a" with inherited definition
+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;
+ relname | conname | contype | conislocal | coninhcount | consrc
+---------+---------+---------+------------+-------------+----------
+ ac | check_a | c | t | 0 | (a <> 0)
+ bc | check_a | c | t | 1 | (a <> 0)
+ bc | check_b | c | t | 0 | (b <> 0)
+(3 rows)
+
+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;
+ relname | conname | contype | conislocal | coninhcount | consrc
+---------+---------+---------+------------+-------------+----------
+ ac | check_a | c | t | 0 | (a <> 0)
+ bc | check_b | c | t | 0 | (b <> 0)
+ cc | check_a | c | f | 1 | (a <> 0)
+ cc | check_b | c | f | 1 | (b <> 0)
+ cc | check_c | c | t | 0 | (c <> 0)
+(5 rows)
+
+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;
+ relname | conname | contype | conislocal | coninhcount | consrc
+---------+---------+---------+------------+-------------+----------
+ ac | check_a | c | t | 0 | (a <> 0)
+ bc | check_b | c | t | 0 | (b <> 0)
+ cc | check_a | c | f | 1 | (a <> 0)
+ cc | check_b | c | t | 0 | (b <> 0)
+ cc | check_c | c | t | 0 | (c <> 0)
+(5 rows)
+
+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
+ERROR: check constraint "cc" of relation "c1" is violated by some row
+alter table p2 add check (f2>0); -- check it without a name, too
+ERROR: check constraint "p2_f2_check" of relation "c1" is violated by some row
+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
+ERROR: new row for relation "c1" violates check constraint "p2_f2_check"
+DETAIL: Failing row contains (1, -1, 2).
+create table c2(f3 int) inherits(p1,p2);
+\d c2
+ Table "public.c2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ f1 | integer | | |
+ f2 | integer | | |
+ f3 | integer | | |
+Check constraints:
+ "p2_f2_check" CHECK (f2 > 0)
+Inherits: p1,
+ p2
+
+create table c3 (f4 int) inherits(c1,c2);
+NOTICE: merging multiple inherited definitions of column "f1"
+NOTICE: merging multiple inherited definitions of column "f2"
+NOTICE: merging multiple inherited definitions of column "f3"
+\d c3
+ Table "public.c3"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ f1 | integer | | |
+ f2 | integer | | |
+ f3 | integer | | |
+ f4 | integer | | |
+Check constraints:
+ "p2_f2_check" CHECK (f2 > 0)
+Inherits: c1,
+ c2
+
+drop table p1 cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table c1
+drop cascades to table c2
+drop cascades to table c3
+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
+ Table "public.cc1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ f1 | integer | | |
+ f2 | text | | |
+ f3 | integer | | |
+ a1 | integer | | |
+Check constraints:
+ "pp1_a1_check" CHECK (a1 > 0)
+Inherits: pp1
+
+create table cc2(f4 float) inherits(pp1,cc1);
+NOTICE: merging multiple inherited definitions of column "f1"
+NOTICE: merging multiple inherited definitions of column "a1"
+\d cc2
+ Table "public.cc2"
+ Column | Type | Collation | Nullable | Default
+--------+------------------+-----------+----------+---------
+ f1 | integer | | |
+ a1 | integer | | |
+ f2 | text | | |
+ f3 | integer | | |
+ f4 | double precision | | |
+Check constraints:
+ "pp1_a1_check" CHECK (a1 > 0)
+Inherits: pp1,
+ cc1
+
+alter table pp1 add column a2 int check (a2 > 0);
+NOTICE: merging definition of column "a2" for child "cc2"
+NOTICE: merging constraint "pp1_a2_check" with inherited definition
+\d cc2
+ Table "public.cc2"
+ Column | Type | Collation | Nullable | Default
+--------+------------------+-----------+----------+---------
+ f1 | integer | | |
+ a1 | integer | | |
+ f2 | text | | |
+ f3 | integer | | |
+ f4 | double precision | | |
+ a2 | integer | | |
+Check constraints:
+ "pp1_a1_check" CHECK (a1 > 0)
+ "pp1_a2_check" CHECK (a2 > 0)
+Inherits: pp1,
+ cc1
+
+drop table pp1 cascade;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table cc1
+drop cascades to table cc2
+-- 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);
+NOTICE: merging multiple inherited definitions of column "b"
+ALTER TABLE inht1 RENAME a TO aa;
+ALTER TABLE inht1 RENAME b TO bb; -- to be failed
+ERROR: cannot rename inherited column "b"
+ALTER TABLE inhts RENAME aa TO aaa; -- to be failed
+ERROR: cannot rename inherited column "aa"
+ALTER TABLE inhts RENAME d TO dd;
+\d+ inhts
+ Table "public.inhts"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ aa | integer | | | | plain | |
+ b | integer | | | | plain | |
+ c | integer | | | | plain | |
+ dd | integer | | | | plain | |
+Inherits: inht1,
+ inhs1
+
+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);
+NOTICE: merging multiple inherited definitions of column "aa"
+NOTICE: merging multiple inherited definitions of column "b"
+ALTER TABLE inht1 RENAME aa TO aaa;
+\d+ inht4
+ Table "public.inht4"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ aaa | integer | | | | plain | |
+ b | integer | | | | plain | |
+ x | integer | | | | plain | |
+ y | integer | | | | plain | |
+ z | integer | | | | plain | |
+Inherits: inht2,
+ inht3
+
+CREATE TABLE inhts (d int) INHERITS (inht2, inhs1);
+NOTICE: merging multiple inherited definitions of column "b"
+ALTER TABLE inht1 RENAME aaa TO aaaa;
+ALTER TABLE inht1 RENAME b TO bb; -- to be failed
+ERROR: cannot rename inherited column "b"
+\d+ inhts
+ Table "public.inhts"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ aaaa | integer | | | | plain | |
+ b | integer | | | | plain | |
+ x | integer | | | | plain | |
+ c | integer | | | | plain | |
+ d | integer | | | | plain | |
+Inherits: inht2,
+ inhs1
+
+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;
+ attrelid | attname | attinhcount | expected
+----------+---------+-------------+----------
+ inht2 | aaaa | 1 | 1
+ inht2 | b | 1 | 1
+ inht3 | aaaa | 1 | 1
+ inht3 | b | 1 | 1
+ inht4 | aaaa | 2 | 2
+ inht4 | b | 2 | 2
+ inht4 | x | 1 | 2
+ inht4 | y | 1 | 2
+ inhts | aaaa | 1 | 1
+ inhts | b | 2 | 1
+ inhts | x | 1 | 1
+ inhts | c | 1 | 1
+(12 rows)
+
+DROP TABLE inht1, inhs1 CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table inht2
+drop cascades to table inhts
+drop cascades to table inht3
+drop cascades to table inht4
+-- 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
+ Table "public.test_constraints"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+-------------------+-----------+----------+---------+----------+--------------+-------------
+ id | integer | | | | plain | |
+ val1 | character varying | | | | extended | |
+ val2 | integer | | | | plain | |
+Indexes:
+ "test_constraints_val1_val2_key" UNIQUE CONSTRAINT, btree (val1, val2)
+Child tables: test_constraints_inh
+
+ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key;
+\d+ test_constraints
+ Table "public.test_constraints"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+-------------------+-----------+----------+---------+----------+--------------+-------------
+ id | integer | | | | plain | |
+ val1 | character varying | | | | extended | |
+ val2 | integer | | | | plain | |
+Child tables: test_constraints_inh
+
+\d+ test_constraints_inh
+ Table "public.test_constraints_inh"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+-------------------+-----------+----------+---------+----------+--------------+-------------
+ id | integer | | | | plain | |
+ val1 | character varying | | | | extended | |
+ val2 | integer | | | | plain | |
+Inherits: test_constraints
+
+DROP TABLE test_constraints_inh;
+DROP TABLE test_constraints;
+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
+ Table "public.test_ex_constraints"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+--------+-----------+----------+---------+---------+--------------+-------------
+ c | circle | | | | plain | |
+Indexes:
+ "test_ex_constraints_c_excl" EXCLUDE USING gist (c WITH &&)
+Child tables: test_ex_constraints_inh
+
+ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
+\d+ test_ex_constraints
+ Table "public.test_ex_constraints"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+--------+-----------+----------+---------+---------+--------------+-------------
+ c | circle | | | | plain | |
+Child tables: test_ex_constraints_inh
+
+\d+ test_ex_constraints_inh
+ Table "public.test_ex_constraints_inh"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+--------+-----------+----------+---------+---------+--------------+-------------
+ c | circle | | | | plain | |
+Inherits: test_ex_constraints
+
+DROP TABLE test_ex_constraints_inh;
+DROP TABLE test_ex_constraints;
+-- 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
+ Table "public.test_primary_constraints"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ id | integer | | not null | | plain | |
+Indexes:
+ "test_primary_constraints_pkey" PRIMARY KEY, btree (id)
+Referenced by:
+ TABLE "test_foreign_constraints" CONSTRAINT "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
+
+\d+ test_foreign_constraints
+ Table "public.test_foreign_constraints"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ id1 | integer | | | | plain | |
+Foreign-key constraints:
+ "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
+Child tables: test_foreign_constraints_inh
+
+ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id1_fkey;
+\d+ test_foreign_constraints
+ Table "public.test_foreign_constraints"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ id1 | integer | | | | plain | |
+Child tables: test_foreign_constraints_inh
+
+\d+ test_foreign_constraints_inh
+ Table "public.test_foreign_constraints_inh"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ id1 | integer | | | | plain | |
+Inherits: test_foreign_constraints
+
+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;
+ a
+---
+ 2
+ 3
+(2 rows)
+
+select * from inh_fk_2 order by 1, 2;
+ x | y
+-----+---
+ 22 | 2
+ 33 | 3
+ 111 | 1
+ 222 | 2
+(4 rows)
+
+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);
+NOTICE: merging constraint "inh_check_constraint1" with inherited definition
+alter table p1_c1 add constraint inh_check_constraint2 check (f1 < 10);
+alter table p1 add constraint inh_check_constraint2 check (f1 < 10);
+NOTICE: merging constraint "inh_check_constraint2" with inherited definition
+select conrelid::regclass::text as relname, conname, conislocal, coninhcount
+from pg_constraint where conname like 'inh\_check\_constraint%'
+order by 1, 2;
+ relname | conname | conislocal | coninhcount
+---------+-----------------------+------------+-------------
+ p1 | inh_check_constraint1 | t | 0
+ p1 | inh_check_constraint2 | t | 0
+ p1_c1 | inh_check_constraint1 | t | 1
+ p1_c1 | inh_check_constraint2 | t | 1
+(4 rows)
+
+drop table p1 cascade;
+NOTICE: drop cascades to table p1_c1
+-- 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
+ERROR: constraint "inh_check_constraint" conflicts with NOT VALID constraint on relation "invalid_check_con_child"
+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;
+NOTICE: merging constraint "inh_check_constraint" with inherited definition
+insert into invalid_check_con values(0); -- fail
+ERROR: new row for relation "invalid_check_con" violates check constraint "inh_check_constraint"
+DETAIL: Failing row contains (0).
+insert into invalid_check_con_child values(0); -- fail
+ERROR: new row for relation "invalid_check_con_child" violates check constraint "inh_check_constraint"
+DETAIL: Failing row contains (0).
+select conrelid::regclass::text as relname, conname,
+ convalidated, conislocal, coninhcount, connoinherit
+from pg_constraint where conname like 'inh\_check\_constraint%'
+order by 1, 2;
+ relname | conname | convalidated | conislocal | coninhcount | connoinherit
+-------------------------+----------------------+--------------+------------+-------------+--------------
+ invalid_check_con | inh_check_constraint | f | t | 0 | f
+ invalid_check_con_child | inh_check_constraint | t | t | 1 | f
+(2 rows)
+
+-- 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;
+explain (costs off)
+select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
+ QUERY PLAN
+------------------------------------------------------------
+ Nested Loop
+ -> Limit
+ -> Seq Scan on int4_tbl
+ -> Append
+ -> Index Scan using patest0i on patest0 patest0_1
+ Index Cond: (id = int4_tbl.f1)
+ -> Index Scan using patest1i on patest1 patest0_2
+ Index Cond: (id = int4_tbl.f1)
+ -> Index Scan using patest2i on patest2 patest0_3
+ Index Cond: (id = int4_tbl.f1)
+(10 rows)
+
+select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
+ id | x | f1
+----+---+----
+ 0 | 0 | 0
+ 0 | 0 | 0
+ 0 | 0 | 0
+(3 rows)
+
+drop index patest2i;
+explain (costs off)
+select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
+ QUERY PLAN
+------------------------------------------------------------
+ Nested Loop
+ -> Limit
+ -> Seq Scan on int4_tbl
+ -> Append
+ -> Index Scan using patest0i on patest0 patest0_1
+ Index Cond: (id = int4_tbl.f1)
+ -> Index Scan using patest1i on patest1 patest0_2
+ Index Cond: (id = int4_tbl.f1)
+ -> Seq Scan on patest2 patest0_3
+ Filter: (int4_tbl.f1 = id)
+(10 rows)
+
+select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
+ id | x | f1
+----+---+----
+ 0 | 0 | 0
+ 0 | 0 | 0
+ 0 | 0 | 0
+(3 rows)
+
+drop table patest0 cascade;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table patest1
+drop cascades to table patest2
+--
+-- 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);
+NOTICE: merging column "id" with inherited definition
+create table matest2 (id integer primary key) inherits (matest0);
+NOTICE: merging column "id" with inherited definition
+create table matest3 (id integer primary key) inherits (matest0);
+NOTICE: merging column "id" with inherited definition
+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');
+set enable_indexscan = off; -- force use of seqscan/sort, so no merge
+explain (verbose, costs off) select * from matest0 order by 1-id;
+ QUERY PLAN
+------------------------------------------------------------
+ Sort
+ Output: matest0.id, matest0.name, ((1 - matest0.id))
+ Sort Key: ((1 - matest0.id))
+ -> Result
+ Output: matest0.id, matest0.name, (1 - matest0.id)
+ -> Append
+ -> Seq Scan on public.matest0 matest0_1
+ Output: matest0_1.id, matest0_1.name
+ -> Seq Scan on public.matest1 matest0_2
+ Output: matest0_2.id, matest0_2.name
+ -> Seq Scan on public.matest2 matest0_3
+ Output: matest0_3.id, matest0_3.name
+ -> Seq Scan on public.matest3 matest0_4
+ Output: matest0_4.id, matest0_4.name
+(14 rows)
+
+select * from matest0 order by 1-id;
+ id | name
+----+--------
+ 6 | Test 6
+ 5 | Test 5
+ 4 | Test 4
+ 3 | Test 3
+ 2 | Test 2
+ 1 | Test 1
+(6 rows)
+
+explain (verbose, costs off) select min(1-id) from matest0;
+ QUERY PLAN
+--------------------------------------------------
+ Aggregate
+ Output: min((1 - matest0.id))
+ -> Append
+ -> Seq Scan on public.matest0 matest0_1
+ Output: matest0_1.id
+ -> Seq Scan on public.matest1 matest0_2
+ Output: matest0_2.id
+ -> Seq Scan on public.matest2 matest0_3
+ Output: matest0_3.id
+ -> Seq Scan on public.matest3 matest0_4
+ Output: matest0_4.id
+(11 rows)
+
+select min(1-id) from matest0;
+ min
+-----
+ -5
+(1 row)
+
+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;
+ QUERY PLAN
+------------------------------------------------------------------------
+ Merge Append
+ Sort Key: ((1 - matest0.id))
+ -> Index Scan using matest0i on public.matest0 matest0_1
+ Output: matest0_1.id, matest0_1.name, (1 - matest0_1.id)
+ -> Index Scan using matest1i on public.matest1 matest0_2
+ Output: matest0_2.id, matest0_2.name, (1 - matest0_2.id)
+ -> Sort
+ Output: matest0_3.id, matest0_3.name, ((1 - matest0_3.id))
+ Sort Key: ((1 - matest0_3.id))
+ -> Seq Scan on public.matest2 matest0_3
+ Output: matest0_3.id, matest0_3.name, (1 - matest0_3.id)
+ -> Index Scan using matest3i on public.matest3 matest0_4
+ Output: matest0_4.id, matest0_4.name, (1 - matest0_4.id)
+(13 rows)
+
+select * from matest0 order by 1-id;
+ id | name
+----+--------
+ 6 | Test 6
+ 5 | Test 5
+ 4 | Test 4
+ 3 | Test 3
+ 2 | Test 2
+ 1 | Test 1
+(6 rows)
+
+explain (verbose, costs off) select min(1-id) from matest0;
+ QUERY PLAN
+---------------------------------------------------------------------------------
+ Result
+ Output: $0
+ InitPlan 1 (returns $0)
+ -> Limit
+ Output: ((1 - matest0.id))
+ -> Result
+ Output: ((1 - matest0.id))
+ -> Merge Append
+ Sort Key: ((1 - matest0.id))
+ -> Index Scan using matest0i on public.matest0 matest0_1
+ Output: matest0_1.id, (1 - matest0_1.id)
+ Index Cond: ((1 - matest0_1.id) IS NOT NULL)
+ -> Index Scan using matest1i on public.matest1 matest0_2
+ Output: matest0_2.id, (1 - matest0_2.id)
+ Index Cond: ((1 - matest0_2.id) IS NOT NULL)
+ -> Sort
+ Output: matest0_3.id, ((1 - matest0_3.id))
+ Sort Key: ((1 - matest0_3.id))
+ -> Bitmap Heap Scan on public.matest2 matest0_3
+ Output: matest0_3.id, (1 - matest0_3.id)
+ Filter: ((1 - matest0_3.id) IS NOT NULL)
+ -> Bitmap Index Scan on matest2_pkey
+ -> Index Scan using matest3i on public.matest3 matest0_4
+ Output: matest0_4.id, (1 - matest0_4.id)
+ Index Cond: ((1 - matest0_4.id) IS NOT NULL)
+(25 rows)
+
+select min(1-id) from matest0;
+ min
+-----
+ -5
+(1 row)
+
+reset enable_seqscan;
+reset enable_parallel_append;
+drop table matest0 cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table matest1
+drop cascades to table matest2
+drop cascades to table matest3
+--
+-- 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);
+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;
+ QUERY PLAN
+-------------------------------------------------------------------
+ Limit
+ -> Merge Join
+ Merge Cond: (t1.b = t2.b)
+ -> Merge Append
+ Sort Key: t1.b
+ -> Index Scan using matest0i on matest0 t1_1
+ -> Index Scan using matest1i on matest1 t1_2
+ -> Materialize
+ -> Merge Append
+ Sort Key: t2.b
+ -> Index Scan using matest0i on matest0 t2_1
+ Filter: (c = d)
+ -> Index Scan using matest1i on matest1 t2_2
+ Filter: (c = d)
+(14 rows)
+
+reset enable_nestloop;
+drop table matest0 cascade;
+NOTICE: drop cascades to table matest1
+--
+-- 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;
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Merge Append
+ Sort Key: tenk1.thousand, tenk1.tenthous
+ -> Index Only Scan using tenk1_thous_tenthous on tenk1
+ -> Sort
+ Sort Key: tenk1_1.thousand, tenk1_1.thousand
+ -> Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1
+(6 rows)
+
+explain (costs off)
+SELECT thousand, tenthous, thousand+tenthous AS x FROM tenk1
+UNION ALL
+SELECT 42, 42, hundred FROM tenk1
+ORDER BY thousand, tenthous;
+ QUERY PLAN
+------------------------------------------------------------------
+ Merge Append
+ Sort Key: tenk1.thousand, tenk1.tenthous
+ -> Index Only Scan using tenk1_thous_tenthous on tenk1
+ -> Sort
+ Sort Key: 42, 42
+ -> Index Only Scan using tenk1_hundred on tenk1 tenk1_1
+(6 rows)
+
+explain (costs off)
+SELECT thousand, tenthous FROM tenk1
+UNION ALL
+SELECT thousand, random()::integer FROM tenk1
+ORDER BY thousand, tenthous;
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Merge Append
+ Sort Key: tenk1.thousand, tenk1.tenthous
+ -> Index Only Scan using tenk1_thous_tenthous on tenk1
+ -> Sort
+ Sort Key: tenk1_1.thousand, ((random())::integer)
+ -> Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1
+(6 rows)
+
+-- 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;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Merge Append
+ Sort Key: a.unique1
+ -> Index Only Scan using tenk1_unique1 on tenk1 a
+ Index Cond: (unique1 IS NOT NULL)
+ -> Index Only Scan using tenk1_unique2 on tenk1 b
+ Index Cond: (unique2 IS NOT NULL)
+(9 rows)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Merge Append
+ Sort Key: a.unique1
+ -> Index Only Scan using tenk1_unique1 on tenk1 a
+ Index Cond: (unique1 IS NOT NULL)
+ -> Index Only Scan using tenk1_unique2 on tenk1 b
+ Index Cond: (unique2 IS NOT NULL)
+(9 rows)
+
+-- 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;
+ QUERY PLAN
+-------------------------------------------------------------
+ Merge Append
+ Sort Key: a.thousand, a.tenthous
+ -> Index Only Scan using tenk1_thous_tenthous on tenk1 a
+ -> Sort
+ Sort Key: b.unique2, b.unique2
+ -> Index Only Scan using tenk1_unique2 on tenk1 b
+(6 rows)
+
+-- 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);
+ QUERY PLAN
+----------------------------------------------------------------
+ Function Scan on generate_series g
+ SubPlan 1
+ -> Limit
+ -> Merge Append
+ Sort Key: ((d.d + g.i))
+ -> Sort
+ Sort Key: ((d.d + g.i))
+ -> Function Scan on generate_series d
+ -> Sort
+ Sort Key: ((d_1.d + g.i))
+ -> Function Scan on generate_series d_1
+(11 rows)
+
+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);
+ array
+------------------------------
+ {1,5,6,8,11,11,14,16,17,20}
+ {2,6,7,9,12,12,15,17,18,21}
+ {3,7,8,10,13,13,16,18,19,22}
+(3 rows)
+
+reset enable_seqscan;
+reset enable_indexscan;
+reset enable_bitmapscan;
+--
+-- 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;
+ f1
+----
+ 1
+ 2
+
+(3 rows)
+
+select * from cnullparent where f1 = 2;
+ f1
+----
+ 2
+(1 row)
+
+drop table cnullparent cascade;
+NOTICE: drop cascades to table cnullchild
+--
+-- 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
+ERROR: cannot inherit from temporary relation "inh_temp_parent"
+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;
+ tableoid | a1
+-----------------+----
+ inh_perm_parent | 1
+ inh_temp_child | 3
+(2 rows)
+
+select tableoid::regclass, a1 from inh_temp_parent;
+ tableoid | a1
+------------------+----
+ inh_temp_parent | 2
+ inh_temp_child_2 | 4
+(2 rows)
+
+drop table inh_perm_parent cascade;
+NOTICE: drop cascades to table inh_temp_child
+drop table inh_temp_parent cascade;
+NOTICE: drop cascades to table inh_temp_child_2
+--
+-- 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;
+ QUERY PLAN
+----------------------------------------------
+ Append
+ -> Seq Scan on part_ab_cd list_parted_1
+ -> Seq Scan on part_ef_gh list_parted_2
+ -> Seq Scan on part_null_xy list_parted_3
+(4 rows)
+
+explain (costs off) select * from list_parted where a is null;
+ QUERY PLAN
+--------------------------------------
+ Seq Scan on part_null_xy list_parted
+ Filter: (a IS NULL)
+(2 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+ QUERY PLAN
+----------------------------------------------
+ Append
+ -> Seq Scan on part_ab_cd list_parted_1
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ef_gh list_parted_2
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_null_xy list_parted_3
+ Filter: (a IS NOT NULL)
+(7 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+ QUERY PLAN
+----------------------------------------------------------
+ Append
+ -> Seq Scan on part_ab_cd list_parted_1
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ef_gh list_parted_2
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(5 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+ QUERY PLAN
+---------------------------------------------------------------------------------
+ Seq Scan on part_ab_cd list_parted
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(2 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+ QUERY PLAN
+------------------------------------
+ Seq Scan on part_ab_cd list_parted
+ Filter: ((a)::text = 'ab'::text)
+(2 rows)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------
+ Append
+ -> Seq Scan on part_1_10_ab range_list_parted_1
+ -> Seq Scan on part_1_10_cd range_list_parted_2
+ -> Seq Scan on part_10_20_ab range_list_parted_3
+ -> Seq Scan on part_10_20_cd range_list_parted_4
+ -> Seq Scan on part_21_30_ab range_list_parted_5
+ -> Seq Scan on part_21_30_cd range_list_parted_6
+ -> Seq Scan on part_40_inf_ab range_list_parted_7
+ -> Seq Scan on part_40_inf_cd range_list_parted_8
+ -> Seq Scan on part_40_inf_null range_list_parted_9
+(10 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+ QUERY PLAN
+----------------------------------------------------
+ Append
+ -> Seq Scan on part_1_10_ab range_list_parted_1
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_cd range_list_parted_2
+ Filter: (a = 5)
+(5 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+ QUERY PLAN
+------------------------------------------------------
+ Append
+ -> Seq Scan on part_1_10_ab range_list_parted_1
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20_ab range_list_parted_2
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_ab range_list_parted_3
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf_ab range_list_parted_4
+ Filter: (b = 'ab'::bpchar)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+ QUERY PLAN
+-----------------------------------------------------------------
+ Append
+ -> Seq Scan on part_1_10_ab range_list_parted_1
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20_ab range_list_parted_2
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_ab range_list_parted_3
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(7 rows)
+
+/* Should select no rows because range partition key cannot be null */
+explain (costs off) select * from range_list_parted where a is null;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+/* Should only select rows from the null-accepting partition */
+explain (costs off) select * from range_list_parted where b is null;
+ QUERY PLAN
+------------------------------------------------
+ Seq Scan on part_40_inf_null range_list_parted
+ Filter: (b IS NULL)
+(2 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+ QUERY PLAN
+--------------------------------------------------------
+ Append
+ -> Seq Scan on part_1_10_ab range_list_parted_1
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_cd range_list_parted_2
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_ab range_list_parted_3
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_cd range_list_parted_4
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_ab range_list_parted_5
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_cd range_list_parted_6
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_ab range_list_parted_7
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_cd range_list_parted_8
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_null range_list_parted_9
+ Filter: ((a IS NOT NULL) AND (a < 67))
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+ QUERY PLAN
+--------------------------------------------------------
+ Append
+ -> Seq Scan on part_40_inf_ab range_list_parted_1
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf_cd range_list_parted_2
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf_null range_list_parted_3
+ Filter: (a >= 30)
+(7 rows)
+
+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
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Seq Scan on mcrparted0 mcrparted_1
+ Filter: (a = 0)
+ -> Seq Scan on mcrparted_def mcrparted_2
+ Filter: (a = 0)
+(5 rows)
+
+explain (costs off) select * from mcrparted where a = 10 and abs(b) < 5; -- scans mcrparted1, mcrparted_def
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Seq Scan on mcrparted1 mcrparted_1
+ Filter: ((a = 10) AND (abs(b) < 5))
+ -> Seq Scan on mcrparted_def mcrparted_2
+ Filter: ((a = 10) AND (abs(b) < 5))
+(5 rows)
+
+explain (costs off) select * from mcrparted where a = 10 and abs(b) = 5; -- scans mcrparted1, mcrparted2, mcrparted_def
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Seq Scan on mcrparted1 mcrparted_1
+ Filter: ((a = 10) AND (abs(b) = 5))
+ -> Seq Scan on mcrparted2 mcrparted_2
+ Filter: ((a = 10) AND (abs(b) = 5))
+ -> Seq Scan on mcrparted_def mcrparted_3
+ Filter: ((a = 10) AND (abs(b) = 5))
+(7 rows)
+
+explain (costs off) select * from mcrparted where abs(b) = 5; -- scans all partitions
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Seq Scan on mcrparted0 mcrparted_1
+ Filter: (abs(b) = 5)
+ -> Seq Scan on mcrparted1 mcrparted_2
+ Filter: (abs(b) = 5)
+ -> Seq Scan on mcrparted2 mcrparted_3
+ Filter: (abs(b) = 5)
+ -> Seq Scan on mcrparted3 mcrparted_4
+ Filter: (abs(b) = 5)
+ -> Seq Scan on mcrparted4 mcrparted_5
+ Filter: (abs(b) = 5)
+ -> Seq Scan on mcrparted5 mcrparted_6
+ Filter: (abs(b) = 5)
+ -> Seq Scan on mcrparted_def mcrparted_7
+ Filter: (abs(b) = 5)
+(15 rows)
+
+explain (costs off) select * from mcrparted where a > -1; -- scans all partitions
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Seq Scan on mcrparted0 mcrparted_1
+ Filter: (a > '-1'::integer)
+ -> Seq Scan on mcrparted1 mcrparted_2
+ Filter: (a > '-1'::integer)
+ -> Seq Scan on mcrparted2 mcrparted_3
+ Filter: (a > '-1'::integer)
+ -> Seq Scan on mcrparted3 mcrparted_4
+ Filter: (a > '-1'::integer)
+ -> Seq Scan on mcrparted4 mcrparted_5
+ Filter: (a > '-1'::integer)
+ -> Seq Scan on mcrparted5 mcrparted_6
+ Filter: (a > '-1'::integer)
+ -> Seq Scan on mcrparted_def mcrparted_7
+ Filter: (a > '-1'::integer)
+(15 rows)
+
+explain (costs off) select * from mcrparted where a = 20 and abs(b) = 10 and c > 10; -- scans mcrparted4
+ QUERY PLAN
+-----------------------------------------------------
+ Seq Scan on mcrparted4 mcrparted
+ Filter: ((c > 10) AND (a = 20) AND (abs(b) = 10))
+(2 rows)
+
+explain (costs off) select * from mcrparted where a = 20 and c > 20; -- scans mcrparted3, mcrparte4, mcrparte5, mcrparted_def
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Seq Scan on mcrparted3 mcrparted_1
+ Filter: ((c > 20) AND (a = 20))
+ -> Seq Scan on mcrparted4 mcrparted_2
+ Filter: ((c > 20) AND (a = 20))
+ -> Seq Scan on mcrparted5 mcrparted_3
+ Filter: ((c > 20) AND (a = 20))
+ -> Seq Scan on mcrparted_def mcrparted_4
+ Filter: ((c > 20) AND (a = 20))
+(9 rows)
+
+-- 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');
+explain (costs off) select min(a), max(a) from parted_minmax where b = '12345';
+ QUERY PLAN
+------------------------------------------------------------------------------------------------
+ Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan using parted_minmax1i on parted_minmax1 parted_minmax
+ Index Cond: ((a IS NOT NULL) AND (b = '12345'::text))
+ InitPlan 2 (returns $1)
+ -> Limit
+ -> Index Only Scan Backward using parted_minmax1i on parted_minmax1 parted_minmax_1
+ Index Cond: ((a IS NOT NULL) AND (b = '12345'::text))
+(9 rows)
+
+select min(a), max(a) from parted_minmax where b = '12345';
+ min | max
+-----+-----
+ 1 | 1
+(1 row)
+
+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);
+-- MergeAppend must be used when a default partition exists
+explain (costs off) select * from mcrparted order by a, abs(b), c;
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ Merge Append
+ Sort Key: mcrparted.a, (abs(mcrparted.b)), mcrparted.c
+ -> Index Scan using mcrparted0_a_abs_c_idx on mcrparted0 mcrparted_1
+ -> Index Scan using mcrparted1_a_abs_c_idx on mcrparted1 mcrparted_2
+ -> Index Scan using mcrparted2_a_abs_c_idx on mcrparted2 mcrparted_3
+ -> Index Scan using mcrparted3_a_abs_c_idx on mcrparted3 mcrparted_4
+ -> Index Scan using mcrparted4_a_abs_c_idx on mcrparted4 mcrparted_5
+ -> Index Scan using mcrparted5_a_abs_c_idx on mcrparted5 mcrparted_6
+ -> Index Scan using mcrparted_def_a_abs_c_idx on mcrparted_def mcrparted_7
+(9 rows)
+
+drop table mcrparted_def;
+-- Append is used for a RANGE partitioned table with no default
+-- and no subpartitions
+explain (costs off) select * from mcrparted order by a, abs(b), c;
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Append
+ -> Index Scan using mcrparted0_a_abs_c_idx on mcrparted0 mcrparted_1
+ -> Index Scan using mcrparted1_a_abs_c_idx on mcrparted1 mcrparted_2
+ -> Index Scan using mcrparted2_a_abs_c_idx on mcrparted2 mcrparted_3
+ -> Index Scan using mcrparted3_a_abs_c_idx on mcrparted3 mcrparted_4
+ -> Index Scan using mcrparted4_a_abs_c_idx on mcrparted4 mcrparted_5
+ -> Index Scan using mcrparted5_a_abs_c_idx on mcrparted5 mcrparted_6
+(7 rows)
+
+-- 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;
+ QUERY PLAN
+----------------------------------------------------------------------------------
+ Append
+ -> Index Scan Backward using mcrparted5_a_abs_c_idx on mcrparted5 mcrparted_6
+ -> Index Scan Backward using mcrparted4_a_abs_c_idx on mcrparted4 mcrparted_5
+ -> Index Scan Backward using mcrparted3_a_abs_c_idx on mcrparted3 mcrparted_4
+ -> Index Scan Backward using mcrparted2_a_abs_c_idx on mcrparted2 mcrparted_3
+ -> Index Scan Backward using mcrparted1_a_abs_c_idx on mcrparted1 mcrparted_2
+ -> Index Scan Backward using mcrparted0_a_abs_c_idx on mcrparted0 mcrparted_1
+(7 rows)
+
+-- 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;
+explain (costs off) select * from mcrparted order by a, abs(b), c;
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Append
+ -> Index Scan using mcrparted0_a_abs_c_idx on mcrparted0 mcrparted_1
+ -> Index Scan using mcrparted1_a_abs_c_idx on mcrparted1 mcrparted_2
+ -> Index Scan using mcrparted2_a_abs_c_idx on mcrparted2 mcrparted_3
+ -> Index Scan using mcrparted3_a_abs_c_idx on mcrparted3 mcrparted_4
+ -> Index Scan using mcrparted4_a_abs_c_idx on mcrparted4 mcrparted_5
+ -> Merge Append
+ Sort Key: mcrparted_7.a, (abs(mcrparted_7.b)), mcrparted_7.c
+ -> Index Scan using mcrparted5a_a_abs_c_idx on mcrparted5a mcrparted_7
+ -> Index Scan using mcrparted5_def_a_abs_c_idx on mcrparted5_def mcrparted_8
+(10 rows)
+
+drop table mcrparted5_def;
+-- 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;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Append
+ -> Index Scan using mcrparted0_a_abs_c_idx on mcrparted0 mcrparted_1
+ -> Index Scan using mcrparted1_a_abs_c_idx on mcrparted1 mcrparted_2
+ -> Index Scan using mcrparted2_a_abs_c_idx on mcrparted2 mcrparted_3
+ -> Index Scan using mcrparted3_a_abs_c_idx on mcrparted3 mcrparted_4
+ -> Index Scan using mcrparted4_a_abs_c_idx on mcrparted4 mcrparted_5
+ -> Index Scan using mcrparted5a_a_abs_c_idx on mcrparted5a mcrparted_6
+(7 rows)
+
+-- 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;
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Append
+ -> Index Scan using mcrparted0_a_abs_c_idx on mcrparted0 mcrparted_1
+ Index Cond: (a < 20)
+ -> Index Scan using mcrparted1_a_abs_c_idx on mcrparted1 mcrparted_2
+ Index Cond: (a < 20)
+ -> Index Scan using mcrparted2_a_abs_c_idx on mcrparted2 mcrparted_3
+ Index Cond: (a < 20)
+ -> Index Scan using mcrparted3_a_abs_c_idx on mcrparted3 mcrparted_4
+ Index Cond: (a < 20)
+(9 rows)
+
+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.
+explain (costs off) select * from mclparted order by a;
+ QUERY PLAN
+------------------------------------------------------------------------
+ Append
+ -> Index Only Scan using mclparted1_a_idx on mclparted1 mclparted_1
+ -> Index Only Scan using mclparted2_a_idx on mclparted2 mclparted_2
+(3 rows)
+
+-- 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);
+explain (costs off) select * from mclparted order by a;
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Merge Append
+ Sort Key: mclparted.a
+ -> Index Only Scan using mclparted1_a_idx on mclparted1 mclparted_1
+ -> Index Only Scan using mclparted2_a_idx on mclparted2 mclparted_2
+ -> Index Only Scan using mclparted3_5_a_idx on mclparted3_5 mclparted_3
+ -> Index Only Scan using mclparted4_a_idx on mclparted4 mclparted_4
+(6 rows)
+
+drop table mclparted;
+-- 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);
+explain (costs off) select * from mcrparted where a < 20 order by a, abs(b), c limit 1;
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ Limit
+ -> Append
+ -> Sort
+ Sort Key: mcrparted_1.a, (abs(mcrparted_1.b)), mcrparted_1.c
+ -> Seq Scan on mcrparted0 mcrparted_1
+ Filter: (a < 20)
+ -> Index Scan using mcrparted1_a_abs_c_idx on mcrparted1 mcrparted_2
+ Index Cond: (a < 20)
+ -> Index Scan using mcrparted2_a_abs_c_idx on mcrparted2 mcrparted_3
+ Index Cond: (a < 20)
+ -> Index Scan using mcrparted3_a_abs_c_idx on mcrparted3 mcrparted_4
+ Index Cond: (a < 20)
+(12 rows)
+
+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;
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Append
+ -> Index Scan using mcrparted1_a_abs_c_idx on mcrparted1 mcrparted_1
+ Index Cond: (a = 10)
+ -> Index Scan using mcrparted2_a_abs_c_idx on mcrparted2 mcrparted_2
+ Index Cond: (a = 10)
+(5 rows)
+
+reset enable_bitmapscan;
+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);
+explain (costs off) select * from bool_lp order by b;
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Append
+ -> Index Only Scan using bool_lp_false_b_idx on bool_lp_false bool_lp_1
+ -> Index Only Scan using bool_lp_true_b_idx on bool_lp_true bool_lp_2
+(3 rows)
+
+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);
+explain (costs off) select * from bool_rp where b = true order by b,a;
+ QUERY PLAN
+----------------------------------------------------------------------------------
+ Append
+ -> Index Only Scan using bool_rp_true_1k_b_a_idx on bool_rp_true_1k bool_rp_1
+ Index Cond: (b = true)
+ -> Index Only Scan using bool_rp_true_2k_b_a_idx on bool_rp_true_2k bool_rp_2
+ Index Cond: (b = true)
+(5 rows)
+
+explain (costs off) select * from bool_rp where b = false order by b,a;
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Append
+ -> Index Only Scan using bool_rp_false_1k_b_a_idx on bool_rp_false_1k bool_rp_1
+ Index Cond: (b = false)
+ -> Index Only Scan using bool_rp_false_2k_b_a_idx on bool_rp_false_2k bool_rp_2
+ Index Cond: (b = false)
+(5 rows)
+
+explain (costs off) select * from bool_rp where b = true order by a;
+ QUERY PLAN
+----------------------------------------------------------------------------------
+ Append
+ -> Index Only Scan using bool_rp_true_1k_b_a_idx on bool_rp_true_1k bool_rp_1
+ Index Cond: (b = true)
+ -> Index Only Scan using bool_rp_true_2k_b_a_idx on bool_rp_true_2k bool_rp_2
+ Index Cond: (b = true)
+(5 rows)
+
+explain (costs off) select * from bool_rp where b = false order by a;
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Append
+ -> Index Only Scan using bool_rp_false_1k_b_a_idx on bool_rp_false_1k bool_rp_1
+ Index Cond: (b = false)
+ -> Index Only Scan using bool_rp_false_2k_b_a_idx on bool_rp_false_2k bool_rp_2
+ Index Cond: (b = false)
+(5 rows)
+
+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);
+explain (costs off) select * from range_parted order by a,b,c;
+ QUERY PLAN
+-------------------------------------------------------------------------------------
+ Append
+ -> Index Only Scan using range_parted1_a_b_c_idx on range_parted1 range_parted_1
+ -> Index Only Scan using range_parted2_a_b_c_idx on range_parted2 range_parted_2
+(3 rows)
+
+explain (costs off) select * from range_parted order by a desc,b desc,c desc;
+ QUERY PLAN
+----------------------------------------------------------------------------------------------
+ Append
+ -> Index Only Scan Backward using range_parted2_a_b_c_idx on range_parted2 range_parted_2
+ -> Index Only Scan Backward using range_parted1_a_b_c_idx on range_parted1 range_parted_1
+(3 rows)
+
+drop table range_parted;
+-- 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$';
+ QUERY PLAN
+------------------------------------------
+ Nested Loop
+ Join Filter: (p1.a = p2.a)
+ -> Seq Scan on permtest_grandchild p1
+ Filter: (c ~ 'a1$'::text)
+ -> Seq Scan on permtest_grandchild p2
+(5 rows)
+
+explain (costs off)
+ select * from permtest_parent p1 inner join permtest_parent p2
+ on p1.a = p2.a and left(p1.c, 3) ~ 'a1$';
+ QUERY PLAN
+----------------------------------------------
+ Nested Loop
+ Join Filter: (p1.a = p2.a)
+ -> Seq Scan on permtest_grandchild p1
+ Filter: ("left"(c, 3) ~ 'a1$'::text)
+ -> Seq Scan on permtest_grandchild p2
+(5 rows)
+
+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$';
+ QUERY PLAN
+------------------------------------------
+ Nested Loop
+ Join Filter: (p1.a = p2.a)
+ -> Seq Scan on permtest_grandchild p1
+ Filter: (c ~ 'a1$'::text)
+ -> Seq Scan on permtest_grandchild p2
+(5 rows)
+
+-- 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$';
+ QUERY PLAN
+----------------------------------------------------
+ Hash Join
+ Hash Cond: (p2.a = p1.a)
+ -> Seq Scan on permtest_grandchild p2
+ -> Hash
+ -> Seq Scan on permtest_grandchild p1
+ Filter: ("left"(c, 3) ~ 'a1$'::text)
+(6 rows)
+
+reset session authorization;
+revoke all on permtest_parent from regress_no_child_access;
+drop role regress_no_child_access;
+drop table permtest_parent;
+-- 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');
+ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check"
+DETAIL: Failing row contains (0, 1, 10).
+INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', '10');
+ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check"
+DETAIL: Failing row contains (10, 1, 10).
+INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '10');
+ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
+DETAIL: Failing row contains (20, 1, 10).
+-- insert with child not null constraint error
+INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', NULL);
+ERROR: null value in column "data" of relation "errtst_child_fastdef" violates not-null constraint
+DETAIL: Failing row contains (0, 1, null).
+INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', NULL);
+ERROR: null value in column "data" of relation "errtst_child_plaindef" violates not-null constraint
+DETAIL: Failing row contains (10, 1, null).
+INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', NULL);
+ERROR: null value in column "data" of relation "errtst_child_reorder" violates not-null constraint
+DETAIL: Failing row contains (20, 1, null).
+-- insert with shared check constraint error
+INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '5', '5');
+ERROR: new row for relation "errtst_child_fastdef" violates check constraint "shdata_small"
+DETAIL: Failing row contains (0, 5, 5).
+INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '5', '5');
+ERROR: new row for relation "errtst_child_plaindef" violates check constraint "shdata_small"
+DETAIL: Failing row contains (10, 5, 5).
+INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '5', '5');
+ERROR: new row for relation "errtst_child_reorder" violates check constraint "shdata_small"
+DETAIL: Failing row contains (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;
+ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check"
+DETAIL: Failing row contains (0, 1, 15).
+UPDATE errtst_parent SET data = data + 10 WHERE partid = 10;
+ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check"
+DETAIL: Failing row contains (10, 1, 15).
+UPDATE errtst_parent SET data = data + 10 WHERE partid = 20;
+ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
+DETAIL: Failing row contains (20, 1, 15).
+-- 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;
+ERROR: new row for relation "errtst_child_fastdef" violates partition constraint
+DETAIL: Failing row contains (10, 1, 5).
+UPDATE errtst_child_plaindef SET partid = partid + 10 WHERE partid = 10;
+ERROR: new row for relation "errtst_child_plaindef" violates partition constraint
+DETAIL: Failing row contains (20, 1, 5).
+UPDATE errtst_child_reorder SET partid = partid + 10 WHERE partid = 20;
+ERROR: new row for relation "errtst_child_reorder" violates partition constraint
+DETAIL: Failing row contains (5, 1, 30).
+-- 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;
+ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check"
+DETAIL: Failing row contains (10, 1, 15).
+UPDATE errtst_parent SET partid = 20, data = data + 10 WHERE partid = 10;
+ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check"
+DETAIL: Failing row contains (20, 1, 15).
+UPDATE errtst_parent SET partid = 0, data = data + 10 WHERE partid = 20;
+ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check"
+DETAIL: Failing row contains (0, 1, 15).
+-- partition move, without target partition
+UPDATE errtst_parent SET partid = 30, data = data + 10 WHERE partid = 20;
+ERROR: no partition of relation "errtst_parent" found for row
+DETAIL: Partition key of the failing row contains (partid) = (30).
+DROP TABLE errtst_parent;
diff --git a/src/test/regress/expected/init_privs.out b/src/test/regress/expected/init_privs.out
new file mode 100644
index 0000000..292b1a1
--- /dev/null
+++ b/src/test/regress/expected/init_privs.out
@@ -0,0 +1,12 @@
+-- Test initial privileges
+-- There should always be some initial privileges, set up by initdb
+SELECT count(*) > 0 FROM pg_init_privs;
+ ?column?
+----------
+ t
+(1 row)
+
+-- Intentionally include some non-initial privs for pg_dump to dump out
+GRANT SELECT ON pg_proc TO CURRENT_USER;
+GRANT SELECT (prosrc) ON pg_proc TO CURRENT_USER;
+GRANT SELECT (rolname, rolsuper) ON pg_authid TO CURRENT_USER;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
new file mode 100644
index 0000000..5063a3d
--- /dev/null
+++ b/src/test/regress/expected/insert.out
@@ -0,0 +1,1004 @@
+--
+-- 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);
+ERROR: null value in column "col2" of relation "inserttest" violates not-null constraint
+DETAIL: Failing row contains (null, null, testing).
+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;
+ col1 | col2 | col3
+------+------+---------
+ | 3 | testing
+ | 5 | testing
+ | 5 | test
+ | 7 | testing
+(4 rows)
+
+--
+-- insert with similar expression / target_list values (all fail)
+--
+insert into inserttest (col1, col2, col3) values (DEFAULT, DEFAULT);
+ERROR: INSERT has more target columns than expressions
+LINE 1: insert into inserttest (col1, col2, col3) values (DEFAULT, D...
+ ^
+insert into inserttest (col1, col2, col3) values (1, 2);
+ERROR: INSERT has more target columns than expressions
+LINE 1: insert into inserttest (col1, col2, col3) values (1, 2);
+ ^
+insert into inserttest (col1) values (1, 2);
+ERROR: INSERT has more expressions than target columns
+LINE 1: insert into inserttest (col1) values (1, 2);
+ ^
+insert into inserttest (col1) values (DEFAULT, DEFAULT);
+ERROR: INSERT has more expressions than target columns
+LINE 1: insert into inserttest (col1) values (DEFAULT, DEFAULT);
+ ^
+select * from inserttest;
+ col1 | col2 | col3
+------+------+---------
+ | 3 | testing
+ | 5 | testing
+ | 5 | test
+ | 7 | testing
+(4 rows)
+
+--
+-- 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;
+ col1 | col2 | col3
+------+------+-----------------
+ | 3 | testing
+ | 5 | testing
+ | 5 | test
+ | 7 | testing
+ 10 | 20 | 40
+ -1 | 2 | testing
+ 2 | 3 | values are fun!
+(7 rows)
+
+--
+-- TOASTed value test
+--
+insert into inserttest values(30, 50, repeat('x', 10000));
+select col1, col2, char_length(col3) from inserttest;
+ col1 | col2 | char_length
+------+------+-------------
+ | 3 | 7
+ | 5 | 7
+ | 5 | 4
+ | 7 | 7
+ 10 | 20 | 2
+ -1 | 2 | 7
+ 2 | 3 | 15
+ 30 | 50 | 10000
+(8 rows)
+
+drop table inserttest;
+--
+-- tuple larger than fillfactor
+--
+CREATE 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'));
+ pg_size_pretty
+----------------
+ 8192 bytes
+(1 row)
+
+-- 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;
+--
+-- 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
+ERROR: cannot set an array element to DEFAULT
+LINE 1: insert into inserttest (f2[1], f2[2]) values (1,default);
+ ^
+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
+ERROR: cannot set a subfield to DEFAULT
+LINE 1: insert into inserttest (f3.if1, f3.if2) values (1,default);
+ ^
+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;
+ f1 | f2 | f3 | f4
+----+-------+------------------+------------------------
+ | {1,2} | |
+ | {3,4} | |
+ | {5,6} | |
+ | {7,8} | |
+ | | (1,{foo}) |
+ | | (1,{foo}) |
+ | | (2,{bar}) |
+ | | (3,"{baz,quux}") |
+ | | (,"{foo,bar}") |
+ | | (,"{foo,bar}") |
+ | | (,"{baz,quux}") |
+ | | (,"{bear,beer}") |
+ | | | {"(,\"{foo,bar}\")"}
+ | | | {"(,\"{foo,bar}\")"}
+ | | | {"(,\"{baz,quux}\")"}
+ | | | {"(,\"{bear,beer}\")"}
+(16 rows)
+
+-- 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
+ Table "public.inserttest2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+--------+-----------+----------+---------+----------+--------------+-------------
+ f1 | bigint | | | | plain | |
+ f2 | text | | | | extended | |
+Rules:
+ irule1 AS
+ ON INSERT TO inserttest2 DO INSERT INTO inserttest (f3.if2[1], f3.if2[2])
+ VALUES (new.f1, new.f2)
+ irule2 AS
+ ON INSERT TO inserttest2 DO INSERT INTO inserttest (f4[1].if1, f4[1].if2[2]) VALUES (1,'fool'::text), (new.f1,new.f2)
+ irule3 AS
+ ON INSERT TO inserttest2 DO INSERT INTO inserttest (f4[1].if1, f4[1].if2[2]) SELECT new.f1,
+ new.f2
+
+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);
+ERROR: no partition of relation "range_parted" found for row
+DETAIL: Partition key of the failing row contains (a, (b + 0)) = (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);
+ERROR: new row for relation "part1" violates partition constraint
+DETAIL: Failing row contains (a, 11).
+insert into part1 values ('b', 1);
+ERROR: new row for relation "part1" violates partition constraint
+DETAIL: Failing row contains (b, 1).
+-- ok
+insert into part1 values ('a', 1);
+-- fail
+insert into part4 values ('b', 21);
+ERROR: new row for relation "part4" violates partition constraint
+DETAIL: Failing row contains (b, 21).
+insert into part4 values ('a', 10);
+ERROR: new row for relation "part4" violates partition constraint
+DETAIL: Failing row contains (a, 10).
+-- ok
+insert into part4 values ('b', 10);
+-- fail (partition key a has a NOT NULL constraint)
+insert into part1 values (null);
+ERROR: new row for relation "part1" violates partition constraint
+DETAIL: Failing row contains (null, null).
+-- fail (expression key (b+0) cannot be null either)
+insert into part1 values (1);
+ERROR: new row for relation "part1" violates partition constraint
+DETAIL: Failing row contains (1, null).
+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);
+ERROR: new row for relation "part_aa_bb" violates partition constraint
+DETAIL: Failing row contains (cc, 1).
+insert into part_aa_bb values ('AAa', 1);
+ERROR: new row for relation "part_aa_bb" violates partition constraint
+DETAIL: Failing row contains (AAa, 1).
+insert into part_aa_bb values (null);
+ERROR: new row for relation "part_aa_bb" violates partition constraint
+DETAIL: Failing row contains (null, 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);
+ERROR: new row for relation "part_default" violates partition constraint
+DETAIL: Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR: new row for relation "part_default" violates partition constraint
+DETAIL: Failing row contains (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);
+ERROR: new row for relation "part_ee_ff1" violates partition constraint
+DETAIL: Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR: new row for relation "part_default_p2" violates partition constraint
+DETAIL: Failing row contains (gg, 43).
+-- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
+insert into part_ee_ff1 values ('cc', 1);
+ERROR: new row for relation "part_ee_ff1" violates partition constraint
+DETAIL: Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR: no partition of relation "part_default" found for row
+DETAIL: Partition key of the failing row contains (b) = (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;
+ tableoid | a | b
+--------------------+----+----
+ part_cc_dd | cC | 1
+ part_ee_ff1 | ff | 1
+ part_ee_ff2 | ff | 11
+ part_xx_yy_p1 | xx | 1
+ part_xx_yy_defpart | yy | 2
+ part_null | | 0
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(9 rows)
+
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR: no partition of relation "range_parted" found for row
+DETAIL: Partition key of the failing row contains (a, (b + 0)) = (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);
+ERROR: no partition of relation "range_parted" found for row
+DETAIL: Partition key of the failing row contains (a, (b + 0)) = (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');
+ERROR: no partition of relation "range_parted" found for row
+DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (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;
+ tableoid | a | b
+----------+---+----
+ part1 | a | 1
+ part1 | a | 1
+ part2 | a | 10
+ part3 | b | 1
+ part4 | b | 10
+ part4 | b | 10
+ part_def | c | 10
+ part_def | |
+ part_def | a |
+ part_def | | 19
+ part_def | b | 20
+(11 rows)
+
+-- 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);
+ERROR: no partition of relation "part_ee_ff" found for row
+DETAIL: Partition key of the failing row contains (b) = (0).
+insert into part_ee_ff values ('EE', 0);
+ERROR: no partition of relation "part_ee_ff" found for row
+DETAIL: Partition key of the failing row contains (b) = (0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_ee_ff values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+ tableoid | a | b
+--------------------+----+----
+ part_aa_bb | aA |
+ part_cc_dd | cC | 1
+ part_ee_ff1 | ff | 1
+ part_ee_ff1 | EE | 1
+ part_ee_ff2 | ff | 11
+ part_ee_ff2 | EE | 10
+ part_xx_yy_p1 | xx | 1
+ part_xx_yy_defpart | yy | 2
+ part_null | | 0
+ part_null | | 1
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(13 rows)
+
+-- 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;
+ tableoid | a | min_b | max_b
+---------------+----+-------+-------
+ part_aa_bb | aa | |
+ part_cc_dd | cc | |
+ part_ee_ff1 | Ff | 1 | 9
+ part_ee_ff2 | Ff | 10 | 19
+ part_ee_ff3_1 | Ff | 20 | 24
+ part_ee_ff3_2 | Ff | 25 | 29
+ part_gg2_1 | gg | 1 | 4
+ part_gg2_2 | gg | 5 | 9
+ part_null | | 1 | 1
+(9 rows)
+
+-- direct partition inserts should check hash partition bound constraint
+-- 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 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 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);
+ERROR: new row for relation "hpart0" violates partition constraint
+DETAIL: Failing row contains (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;
+ part | a | remainder = a % 4
+--------+----+-------------------
+ hpart0 | 4 | 0
+ hpart0 | 8 | 0
+ hpart0 | 12 | 0
+ hpart0 | 16 | 0
+ hpart1 | 1 | 1
+ hpart1 | 5 | 1
+ hpart1 | 9 | 1
+ hpart2 | 2 | 2
+ hpart2 | 6 | 2
+ hpart2 | 10 | 2
+ hpart3 | 3 | 3
+ hpart3 | 7 | 3
+ hpart3 | 11 | 3
+(13 rows)
+
+-- test \d+ output on a table which has both partitioned and unpartitioned
+-- partitions
+\d+ list_parted
+ Partitioned table "public.list_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | | | plain | |
+Partition key: LIST (lower(a))
+Partitions: part_aa_bb FOR VALUES IN ('aa', 'bb'),
+ part_cc_dd FOR VALUES IN ('cc', 'dd'),
+ part_ee_ff FOR VALUES IN ('ee', 'ff'), PARTITIONED,
+ part_gg FOR VALUES IN ('gg'), PARTITIONED,
+ part_null FOR VALUES IN (NULL),
+ part_xx_yy FOR VALUES IN ('xx', 'yy'), PARTITIONED,
+ part_default DEFAULT, PARTITIONED
+
+-- 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
+ Table "public.part_default"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition of: list_parted DEFAULT
+No partition constraint
+
+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;
+ tableoid | a
+--------------+----
+ part_default |
+ part_default | 1
+ part_default | -1
+(3 rows)
+
+-- 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;
+ attrelid | attname | attnum
+------------+---------+--------
+ mlparted | a | 1
+ mlparted1 | a | 2
+ mlparted11 | a | 4
+(3 rows)
+
+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;
+ tableoid | a | b
+------------+---+---
+ mlparted11 | 1 | 2
+(1 row)
+
+-- check that proper message is shown after failure to route through mlparted1
+insert into mlparted (a, b) values (1, 5);
+ERROR: no partition of relation "mlparted1" found for row
+DETAIL: Partition key of the failing row contains ((b + 0)) = (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);
+ERROR: new row for relation "mlparted11" violates check constraint "check_b"
+DETAIL: Failing row contains (1, 4).
+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);
+ERROR: new row for relation "mlparted1" violates partition constraint
+DETAIL: Failing row contains (3, 2).
+-- 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);
+ERROR: no partition of relation "lparted_nonullpart" found for row
+DETAIL: Partition key of the failing row contains (b) = (null).
+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;
+ a | b | min | max
+------------+---+-----+-----
+ mlparted11 | 1 | 2 | 4
+ mlparted12 | 1 | 5 | 9
+ mlparted2 | 1 | 10 | 19
+ mlparted3 | 1 | 20 | 29
+ mlparted4 | 1 | 30 | 39
+(5 rows)
+
+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');
+ERROR: new row for relation "mlparted5a" violates check constraint "check_b"
+DETAIL: Failing row contains (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');
+ERROR: new row for relation "mlparted5a" violates partition constraint
+DETAIL: Failing row contains (b, 1, 40).
+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);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50, null).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50, null).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b | c
+---------------+----+-----+---
+ mlparted_def1 | 40 | 100 |
+ mlparted_def1 | 42 | 100 |
+ mlparted_def2 | 54 | 50 |
+ mlparted_defd | 70 | 100 |
+(4 rows)
+
+-- 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
+ERROR: no partition of relation "mlparted5_cd" found for row
+DETAIL: Partition key of the failing row contains (c) = (c).
+insert into mlparted values (1, 45, 'f', 1); -- goes to mlparted5, fails
+ERROR: no partition of relation "mlparted5" found for row
+DETAIL: Partition key of the failing row contains (c) = (f).
+select tableoid::regclass, * from mlparted order by a, b, c, d;
+ tableoid | a | b | c | d
+-------------+---+----+---+---
+ mlparted11 | 1 | 2 | a | 1
+ mlparted5_a | 1 | 40 | a | 1
+ mlparted5_b | 1 | 45 | b | 1
+(3 rows)
+
+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
+ERROR: no partition of relation "mlparted5_cd" found for row
+DETAIL: Partition key of the failing row contains (c) = (c).
+insert into mlparted values (1, 45, 'f', 1); -- goes to mlparted5, fails
+ERROR: no partition of relation "mlparted5" found for row
+DETAIL: Partition key of the failing row contains (c) = (f).
+select tableoid::regclass, * from mlparted order by a, b, c, d;
+ tableoid | a | b | c | d
+-------------+---+----+---+---
+ mlparted11 | 1 | 2 | a | 1
+ mlparted5_a | 1 | 40 | a | 1
+ mlparted5_b | 1 | 45 | b | 1
+(3 rows)
+
+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);
+ERROR: no partition of relation "key_desc_1" found for row
+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);
+ERROR: no partition of relation "key_desc_1" found for row
+DETAIL: Partition key of the failing row contains (b) = (1).
+-- key description is not shown if key contains expression
+insert into key_desc values (2, 1);
+ERROR: no partition of relation "key_desc" found for row
+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);
+ERROR: every bound following MINVALUE must also be MINVALUE
+LINE 1: ...partition of mcrparted for values from (minvalue, 0, 0) to (...
+ ^
+create table mcrparted2 partition of mcrparted for values from (10, 6, minvalue) to (10, maxvalue, minvalue);
+ERROR: every bound following MAXVALUE must also be MAXVALUE
+LINE 1: ...r 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);
+ERROR: every bound following MINVALUE must also be MINVALUE
+LINE 1: ...ition of mcrparted for values from (21, minvalue, 0) to (30,...
+ ^
+-- 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);
+ERROR: no partition of relation "mcrparted" found for row
+DETAIL: Partition key of the failing row contains (a, abs(b), c) = (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);
+ERROR: no partition of relation "mcrparted" found for row
+DETAIL: Partition key of the failing row contains (a, abs(b), c) = (11, 1, -1).
+insert into mcrparted3 values (11, 1, -1);
+ERROR: new row for relation "mcrparted3" violates partition constraint
+DETAIL: Failing row contains (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
+ERROR: new row for relation "mcrparted4" violates partition constraint
+DETAIL: Failing row contains (30, 21, 20).
+-- check rows
+select tableoid::regclass::text, * from mcrparted order by 1;
+ tableoid | a | b | c
+------------+----+------+------
+ mcrparted0 | 0 | 1 | 1
+ mcrparted0 | 0 | 1 | 1
+ mcrparted1 | 9 | 1000 | 1
+ mcrparted1 | 9 | 1000 | 1
+ mcrparted1 | 10 | 5 | -1
+ mcrparted1 | 10 | 5 | -1
+ mcrparted1 | 2 | 1 | 0
+ mcrparted1 | 2 | 1 | 0
+ mcrparted2 | 10 | 6 | 1000
+ mcrparted2 | 10 | 6 | 1000
+ mcrparted2 | 10 | 1000 | 1000
+ mcrparted2 | 10 | 1000 | 1000
+ mcrparted5 | 30 | 21 | 20
+ mcrparted5 | 30 | 21 | 20
+(14 rows)
+
+-- 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');
+ERROR: new row for relation "brtrigpartcon1" violates partition constraint
+DETAIL: Failing row contains (2, hi there).
+insert into brtrigpartcon1 values (1, 'hi there');
+ERROR: new row for relation "brtrigpartcon1" violates partition constraint
+DETAIL: Failing row contains (2, 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;
+ERROR: new row for relation "brtrigpartcon1" violates partition constraint
+DETAIL: Failing row contains (a, b) = (2, hi there).
+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');
+NOTICE: b: foo
+NOTICE: b: bar
+copy donothingbrtrig_test from stdout;
+NOTICE: b: baz
+NOTICE: b: qux
+select tableoid::regclass, * from donothingbrtrig_test;
+ tableoid | a | b
+----------+---+---
+(0 rows)
+
+-- 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
+ Partitioned table "public.mcrparted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | | | plain | |
+Partition key: RANGE (a, b)
+Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE),
+ mcrparted2_b FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE),
+ mcrparted3_c_to_common FOR VALUES FROM ('c', MINVALUE) TO ('common', MINVALUE),
+ mcrparted4_common_lt_0 FOR VALUES FROM ('common', MINVALUE) TO ('common', 0),
+ mcrparted5_common_0_to_10 FOR VALUES FROM ('common', 0) TO ('common', 10),
+ mcrparted6_common_ge_10 FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE),
+ mcrparted7_gt_common_lt_d FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE),
+ mcrparted8_ge_d FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
+
+\d+ mcrparted1_lt_b
+ Table "public.mcrparted1_lt_b"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | | | plain | |
+Partition of: mcrparted FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE)
+Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
+
+\d+ mcrparted2_b
+ Table "public.mcrparted2_b"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | | | plain | |
+Partition of: mcrparted FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE)
+Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text) AND (a < 'c'::text))
+
+\d+ mcrparted3_c_to_common
+ Table "public.mcrparted3_c_to_common"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | | | plain | |
+Partition of: mcrparted FOR VALUES FROM ('c', MINVALUE) TO ('common', MINVALUE)
+Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text) AND (a < 'common'::text))
+
+\d+ mcrparted4_common_lt_0
+ Table "public.mcrparted4_common_lt_0"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | | | plain | |
+Partition of: mcrparted FOR VALUES FROM ('common', MINVALUE) TO ('common', 0)
+Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b < 0))
+
+\d+ mcrparted5_common_0_to_10
+ Table "public.mcrparted5_common_0_to_10"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | | | plain | |
+Partition of: mcrparted FOR VALUES FROM ('common', 0) TO ('common', 10)
+Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 0) AND (b < 10))
+
+\d+ mcrparted6_common_ge_10
+ Table "public.mcrparted6_common_ge_10"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | | | plain | |
+Partition of: mcrparted FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE)
+Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 10))
+
+\d+ mcrparted7_gt_common_lt_d
+ Table "public.mcrparted7_gt_common_lt_d"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | | | plain | |
+Partition of: mcrparted FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE)
+Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::text) AND (a < 'd'::text))
+
+\d+ mcrparted8_ge_d
+ Table "public.mcrparted8_ge_d"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | | | plain | |
+Partition of: mcrparted FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
+Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'd'::text))
+
+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;
+ tableoid | a | b
+---------------------------+---------+-----
+ mcrparted1_lt_b | aaa | 0
+ mcrparted2_b | b | 0
+ mcrparted2_b | bz | 10
+ mcrparted3_c_to_common | c | -10
+ mcrparted3_c_to_common | comm | -10
+ mcrparted4_common_lt_0 | common | -10
+ mcrparted5_common_0_to_10 | common | 0
+ mcrparted6_common_ge_10 | common | 10
+ mcrparted7_gt_common_lt_d | commons | 0
+ mcrparted8_ge_d | d | -10
+ mcrparted8_ge_d | e | 0
+(11 rows)
+
+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;
+ returningwrtest
+-----------------
+ (1)
+(1 row)
+
+-- 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;
+ returningwrtest
+-----------------
+ (2,foo)
+(1 row)
+
+drop table returningwrtest;
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
new file mode 100644
index 0000000..66d8633
--- /dev/null
+++ b/src/test/regress/expected/insert_conflict.out
@@ -0,0 +1,866 @@
+--
+-- 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
+(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
+(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
+(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
+ 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
+(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
+(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
+(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
+(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
+(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
+(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
+(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
+(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
+(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
+(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", +
+ "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 <table>.* 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
+(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;
+ERROR: duplicate key value violates unique constraint "fruit_index"
+DETAIL: Key (fruit)=(Peach) already exists.
+-- 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
+(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
+(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;
+ERROR: column excluded.ctid does not exist
+LINE 1: ...values (1) on conflict (key) do update set data = excluded.c...
+ ^
+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;
+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;
+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)
+
+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;
+-- 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
+ERROR: duplicate key value violates unique constraint "twoconstraints_f1_key"
+DETAIL: Key (f1)=(1) already exists.
+insert into twoconstraints values(2, '((0,0),(1,2))'); -- fail on f2
+ERROR: conflicting key value violates exclusion constraint "twoconstraints_f2_excl"
+DETAIL: Key (f2)=((1,2),(0,0)) conflicts with existing key (f2)=((1,1),(0,0)).
+insert into twoconstraints values(2, '((0,0),(1,2))')
+ on conflict on constraint twoconstraints_f1_key do nothing; -- fail on f2
+ERROR: conflicting key value violates exclusion constraint "twoconstraints_f2_excl"
+DETAIL: Key (f2)=((1,2),(0,0)) conflicts with existing key (f2)=((1,1),(0,0)).
+insert into twoconstraints values(2, '((0,0),(1,2))')
+ on conflict on constraint twoconstraints_f2_excl do nothing; -- do nothing
+select * from twoconstraints;
+ f1 | f2
+----+-------------
+ 1 | (1,1),(0,0)
+(1 row)
+
+drop table twoconstraints;
+-- 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
+alter table parted_conflict_test drop b, 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();
+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;
+NOTICE: a = 0, b = cero, c = 2
+drop table parted_conflict;
+drop function parted_conflict_update_func();
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
new file mode 100644
index 0000000..8a64df7
--- /dev/null
+++ b/src/test/regress/expected/int2.out
@@ -0,0 +1,312 @@
+--
+-- INT2
+--
+CREATE TABLE INT2_TBL(f1 int2);
+INSERT INTO INT2_TBL(f1) VALUES ('0 ');
+INSERT INTO INT2_TBL(f1) VALUES (' 1234 ');
+INSERT INTO INT2_TBL(f1) VALUES (' -1234');
+INSERT INTO INT2_TBL(f1) VALUES ('34.5');
+ERROR: invalid input syntax for type smallint: "34.5"
+LINE 1: INSERT INTO INT2_TBL(f1) VALUES ('34.5');
+ ^
+-- largest and smallest values
+INSERT INTO INT2_TBL(f1) VALUES ('32767');
+INSERT INTO INT2_TBL(f1) VALUES ('-32767');
+-- bad input values -- should give errors
+INSERT INTO INT2_TBL(f1) VALUES ('100000');
+ERROR: value "100000" is out of range for type smallint
+LINE 1: INSERT INTO INT2_TBL(f1) VALUES ('100000');
+ ^
+INSERT INTO INT2_TBL(f1) VALUES ('asdf');
+ERROR: invalid input syntax for type smallint: "asdf"
+LINE 1: INSERT INTO INT2_TBL(f1) VALUES ('asdf');
+ ^
+INSERT INTO INT2_TBL(f1) VALUES (' ');
+ERROR: invalid input syntax for type smallint: " "
+LINE 1: INSERT INTO INT2_TBL(f1) VALUES (' ');
+ ^
+INSERT INTO INT2_TBL(f1) VALUES ('- 1234');
+ERROR: invalid input syntax for type smallint: "- 1234"
+LINE 1: INSERT INTO INT2_TBL(f1) VALUES ('- 1234');
+ ^
+INSERT INTO INT2_TBL(f1) VALUES ('4 444');
+ERROR: invalid input syntax for type smallint: "4 444"
+LINE 1: INSERT INTO INT2_TBL(f1) VALUES ('4 444');
+ ^
+INSERT INTO INT2_TBL(f1) VALUES ('123 dt');
+ERROR: invalid input syntax for type smallint: "123 dt"
+LINE 1: INSERT INTO INT2_TBL(f1) VALUES ('123 dt');
+ ^
+INSERT INTO INT2_TBL(f1) VALUES ('');
+ERROR: invalid input syntax for type smallint: ""
+LINE 1: INSERT INTO INT2_TBL(f1) VALUES ('');
+ ^
+SELECT * FROM INT2_TBL;
+ f1
+--------
+ 0
+ 1234
+ -1234
+ 32767
+ -32767
+(5 rows)
+
+SELECT * FROM INT2_TBL AS f(a, b);
+ERROR: table "f" has 1 columns available but 2 columns specified
+SELECT * FROM (TABLE int2_tbl) AS s (a, b);
+ERROR: table "s" has 1 columns available but 2 columns specified
+SELECT i.* FROM INT2_TBL i WHERE i.f1 <> int2 '0';
+ f1
+--------
+ 1234
+ -1234
+ 32767
+ -32767
+(4 rows)
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 <> int4 '0';
+ f1
+--------
+ 1234
+ -1234
+ 32767
+ -32767
+(4 rows)
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 = int2 '0';
+ f1
+----
+ 0
+(1 row)
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 = int4 '0';
+ f1
+----
+ 0
+(1 row)
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 < int2 '0';
+ f1
+--------
+ -1234
+ -32767
+(2 rows)
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 < int4 '0';
+ f1
+--------
+ -1234
+ -32767
+(2 rows)
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 <= int2 '0';
+ f1
+--------
+ 0
+ -1234
+ -32767
+(3 rows)
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 <= int4 '0';
+ f1
+--------
+ 0
+ -1234
+ -32767
+(3 rows)
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 > int2 '0';
+ f1
+-------
+ 1234
+ 32767
+(2 rows)
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 > int4 '0';
+ f1
+-------
+ 1234
+ 32767
+(2 rows)
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 >= int2 '0';
+ f1
+-------
+ 0
+ 1234
+ 32767
+(3 rows)
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 >= int4 '0';
+ f1
+-------
+ 0
+ 1234
+ 32767
+(3 rows)
+
+-- positive odds
+SELECT i.* FROM INT2_TBL i WHERE (i.f1 % int2 '2') = int2 '1';
+ f1
+-------
+ 32767
+(1 row)
+
+-- any evens
+SELECT i.* FROM INT2_TBL i WHERE (i.f1 % int4 '2') = int2 '0';
+ f1
+-------
+ 0
+ 1234
+ -1234
+(3 rows)
+
+SELECT i.f1, i.f1 * int2 '2' AS x FROM INT2_TBL i;
+ERROR: smallint out of range
+SELECT i.f1, i.f1 * int2 '2' AS x FROM INT2_TBL i
+WHERE abs(f1) < 16384;
+ f1 | x
+-------+-------
+ 0 | 0
+ 1234 | 2468
+ -1234 | -2468
+(3 rows)
+
+SELECT i.f1, i.f1 * int4 '2' AS x FROM INT2_TBL i;
+ f1 | x
+--------+--------
+ 0 | 0
+ 1234 | 2468
+ -1234 | -2468
+ 32767 | 65534
+ -32767 | -65534
+(5 rows)
+
+SELECT i.f1, i.f1 + int2 '2' AS x FROM INT2_TBL i;
+ERROR: smallint out of range
+SELECT i.f1, i.f1 + int2 '2' AS x FROM INT2_TBL i
+WHERE f1 < 32766;
+ f1 | x
+--------+--------
+ 0 | 2
+ 1234 | 1236
+ -1234 | -1232
+ -32767 | -32765
+(4 rows)
+
+SELECT i.f1, i.f1 + int4 '2' AS x FROM INT2_TBL i;
+ f1 | x
+--------+--------
+ 0 | 2
+ 1234 | 1236
+ -1234 | -1232
+ 32767 | 32769
+ -32767 | -32765
+(5 rows)
+
+SELECT i.f1, i.f1 - int2 '2' AS x FROM INT2_TBL i;
+ERROR: smallint out of range
+SELECT i.f1, i.f1 - int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+ f1 | x
+-------+-------
+ 0 | -2
+ 1234 | 1232
+ -1234 | -1236
+ 32767 | 32765
+(4 rows)
+
+SELECT i.f1, i.f1 - int4 '2' AS x FROM INT2_TBL i;
+ f1 | x
+--------+--------
+ 0 | -2
+ 1234 | 1232
+ -1234 | -1236
+ 32767 | 32765
+ -32767 | -32769
+(5 rows)
+
+SELECT i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i;
+ f1 | x
+--------+--------
+ 0 | 0
+ 1234 | 617
+ -1234 | -617
+ 32767 | 16383
+ -32767 | -16383
+(5 rows)
+
+SELECT i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
+ f1 | x
+--------+--------
+ 0 | 0
+ 1234 | 617
+ -1234 | -617
+ 32767 | 16383
+ -32767 | -16383
+(5 rows)
+
+-- corner cases
+SELECT (-1::int2<<15)::text;
+ text
+--------
+ -32768
+(1 row)
+
+SELECT ((-1::int2<<15)+1::int2)::text;
+ text
+--------
+ -32767
+(1 row)
+
+-- check sane handling of INT16_MIN overflow cases
+SELECT (-32768)::int2 * (-1)::int2;
+ERROR: smallint out of range
+SELECT (-32768)::int2 / (-1)::int2;
+ERROR: smallint out of range
+SELECT (-32768)::int2 % (-1)::int2;
+ ?column?
+----------
+ 0
+(1 row)
+
+-- check rounding when casting from float
+SELECT x, x::int2 AS int2_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 | int2_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::int2 AS int2_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 | int2_value
+------+------------
+ -2.5 | -3
+ -1.5 | -2
+ -0.5 | -1
+ 0.0 | 0
+ 0.5 | 1
+ 1.5 | 2
+ 2.5 | 3
+(7 rows)
+
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
new file mode 100644
index 0000000..9d20b33
--- /dev/null
+++ b/src/test/regress/expected/int4.out
@@ -0,0 +1,439 @@
+--
+-- INT4
+--
+CREATE TABLE INT4_TBL(f1 int4);
+INSERT INTO INT4_TBL(f1) VALUES (' 0 ');
+INSERT INTO INT4_TBL(f1) VALUES ('123456 ');
+INSERT INTO INT4_TBL(f1) VALUES (' -123456');
+INSERT INTO INT4_TBL(f1) VALUES ('34.5');
+ERROR: invalid input syntax for type integer: "34.5"
+LINE 1: INSERT INTO INT4_TBL(f1) VALUES ('34.5');
+ ^
+-- largest and smallest values
+INSERT INTO INT4_TBL(f1) VALUES ('2147483647');
+INSERT INTO INT4_TBL(f1) VALUES ('-2147483647');
+-- bad input values -- should give errors
+INSERT INTO INT4_TBL(f1) VALUES ('1000000000000');
+ERROR: value "1000000000000" is out of range for type integer
+LINE 1: INSERT INTO INT4_TBL(f1) VALUES ('1000000000000');
+ ^
+INSERT INTO INT4_TBL(f1) VALUES ('asdf');
+ERROR: invalid input syntax for type integer: "asdf"
+LINE 1: INSERT INTO INT4_TBL(f1) VALUES ('asdf');
+ ^
+INSERT INTO INT4_TBL(f1) VALUES (' ');
+ERROR: invalid input syntax for type integer: " "
+LINE 1: INSERT INTO INT4_TBL(f1) VALUES (' ');
+ ^
+INSERT INTO INT4_TBL(f1) VALUES (' asdf ');
+ERROR: invalid input syntax for type integer: " asdf "
+LINE 1: INSERT INTO INT4_TBL(f1) VALUES (' asdf ');
+ ^
+INSERT INTO INT4_TBL(f1) VALUES ('- 1234');
+ERROR: invalid input syntax for type integer: "- 1234"
+LINE 1: INSERT INTO INT4_TBL(f1) VALUES ('- 1234');
+ ^
+INSERT INTO INT4_TBL(f1) VALUES ('123 5');
+ERROR: invalid input syntax for type integer: "123 5"
+LINE 1: INSERT INTO INT4_TBL(f1) VALUES ('123 5');
+ ^
+INSERT INTO INT4_TBL(f1) VALUES ('');
+ERROR: invalid input syntax for type integer: ""
+LINE 1: INSERT INTO INT4_TBL(f1) VALUES ('');
+ ^
+SELECT * FROM INT4_TBL;
+ f1
+-------------
+ 0
+ 123456
+ -123456
+ 2147483647
+ -2147483647
+(5 rows)
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int2 '0';
+ f1
+-------------
+ 123456
+ -123456
+ 2147483647
+ -2147483647
+(4 rows)
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int4 '0';
+ f1
+-------------
+ 123456
+ -123456
+ 2147483647
+ -2147483647
+(4 rows)
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 = int2 '0';
+ f1
+----
+ 0
+(1 row)
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 = int4 '0';
+ f1
+----
+ 0
+(1 row)
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 < int2 '0';
+ f1
+-------------
+ -123456
+ -2147483647
+(2 rows)
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 < int4 '0';
+ f1
+-------------
+ -123456
+ -2147483647
+(2 rows)
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 <= int2 '0';
+ f1
+-------------
+ 0
+ -123456
+ -2147483647
+(3 rows)
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 <= int4 '0';
+ f1
+-------------
+ 0
+ -123456
+ -2147483647
+(3 rows)
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 > int2 '0';
+ f1
+------------
+ 123456
+ 2147483647
+(2 rows)
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 > int4 '0';
+ f1
+------------
+ 123456
+ 2147483647
+(2 rows)
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 >= int2 '0';
+ f1
+------------
+ 0
+ 123456
+ 2147483647
+(3 rows)
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 >= int4 '0';
+ f1
+------------
+ 0
+ 123456
+ 2147483647
+(3 rows)
+
+-- positive odds
+SELECT i.* FROM INT4_TBL i WHERE (i.f1 % int2 '2') = int2 '1';
+ f1
+------------
+ 2147483647
+(1 row)
+
+-- any evens
+SELECT i.* FROM INT4_TBL i WHERE (i.f1 % int4 '2') = int2 '0';
+ f1
+---------
+ 0
+ 123456
+ -123456
+(3 rows)
+
+SELECT i.f1, i.f1 * int2 '2' AS x FROM INT4_TBL i;
+ERROR: integer out of range
+SELECT i.f1, i.f1 * int2 '2' AS x FROM INT4_TBL i
+WHERE abs(f1) < 1073741824;
+ f1 | x
+---------+---------
+ 0 | 0
+ 123456 | 246912
+ -123456 | -246912
+(3 rows)
+
+SELECT i.f1, i.f1 * int4 '2' AS x FROM INT4_TBL i;
+ERROR: integer out of range
+SELECT i.f1, i.f1 * int4 '2' AS x FROM INT4_TBL i
+WHERE abs(f1) < 1073741824;
+ f1 | x
+---------+---------
+ 0 | 0
+ 123456 | 246912
+ -123456 | -246912
+(3 rows)
+
+SELECT i.f1, i.f1 + int2 '2' AS x FROM INT4_TBL i;
+ERROR: integer out of range
+SELECT i.f1, i.f1 + int2 '2' AS x FROM INT4_TBL i
+WHERE f1 < 2147483646;
+ f1 | x
+-------------+-------------
+ 0 | 2
+ 123456 | 123458
+ -123456 | -123454
+ -2147483647 | -2147483645
+(4 rows)
+
+SELECT i.f1, i.f1 + int4 '2' AS x FROM INT4_TBL i;
+ERROR: integer out of range
+SELECT i.f1, i.f1 + int4 '2' AS x FROM INT4_TBL i
+WHERE f1 < 2147483646;
+ f1 | x
+-------------+-------------
+ 0 | 2
+ 123456 | 123458
+ -123456 | -123454
+ -2147483647 | -2147483645
+(4 rows)
+
+SELECT i.f1, i.f1 - int2 '2' AS x FROM INT4_TBL i;
+ERROR: integer out of range
+SELECT i.f1, i.f1 - int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ f1 | x
+------------+------------
+ 0 | -2
+ 123456 | 123454
+ -123456 | -123458
+ 2147483647 | 2147483645
+(4 rows)
+
+SELECT i.f1, i.f1 - int4 '2' AS x FROM INT4_TBL i;
+ERROR: integer out of range
+SELECT i.f1, i.f1 - int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ f1 | x
+------------+------------
+ 0 | -2
+ 123456 | 123454
+ -123456 | -123458
+ 2147483647 | 2147483645
+(4 rows)
+
+SELECT i.f1, i.f1 / int2 '2' AS x FROM INT4_TBL i;
+ f1 | x
+-------------+-------------
+ 0 | 0
+ 123456 | 61728
+ -123456 | -61728
+ 2147483647 | 1073741823
+ -2147483647 | -1073741823
+(5 rows)
+
+SELECT i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
+ f1 | x
+-------------+-------------
+ 0 | 0
+ 123456 | 61728
+ -123456 | -61728
+ 2147483647 | 1073741823
+ -2147483647 | -1073741823
+(5 rows)
+
+--
+-- more complex expressions
+--
+-- variations on unary minus parsing
+SELECT -2+3 AS one;
+ one
+-----
+ 1
+(1 row)
+
+SELECT 4-2 AS two;
+ two
+-----
+ 2
+(1 row)
+
+SELECT 2- -1 AS three;
+ three
+-------
+ 3
+(1 row)
+
+SELECT 2 - -2 AS four;
+ four
+------
+ 4
+(1 row)
+
+SELECT int2 '2' * int2 '2' = int2 '16' / int2 '4' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT int4 '2' * int2 '2' = int2 '16' / int4 '4' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT int2 '2' * int4 '2' = int4 '16' / int2 '4' AS true;
+ true
+------
+ t
+(1 row)
+
+SELECT int4 '1000' < int4 '999' AS false;
+ false
+-------
+ f
+(1 row)
+
+SELECT 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 AS ten;
+ ten
+-----
+ 10
+(1 row)
+
+SELECT 2 + 2 / 2 AS three;
+ three
+-------
+ 3
+(1 row)
+
+SELECT (2 + 2) / 2 AS two;
+ two
+-----
+ 2
+(1 row)
+
+-- corner case
+SELECT (-1::int4<<31)::text;
+ text
+-------------
+ -2147483648
+(1 row)
+
+SELECT ((-1::int4<<31)+1)::text;
+ text
+-------------
+ -2147483647
+(1 row)
+
+-- check sane handling of INT_MIN overflow cases
+SELECT (-2147483648)::int4 * (-1)::int4;
+ERROR: integer out of range
+SELECT (-2147483648)::int4 / (-1)::int4;
+ERROR: integer out of range
+SELECT (-2147483648)::int4 % (-1)::int4;
+ ?column?
+----------
+ 0
+(1 row)
+
+SELECT (-2147483648)::int4 * (-1)::int2;
+ERROR: integer out of range
+SELECT (-2147483648)::int4 / (-1)::int2;
+ERROR: integer out of range
+SELECT (-2147483648)::int4 % (-1)::int2;
+ ?column?
+----------
+ 0
+(1 row)
+
+-- check rounding when casting from float
+SELECT x, x::int4 AS int4_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 | int4_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::int4 AS int4_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 | int4_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::int4, 0::int4),
+ (0::int4, 6410818::int4),
+ (61866666::int4, 6410818::int4),
+ (-61866666::int4, 6410818::int4),
+ ((-2147483648)::int4, 1::int4),
+ ((-2147483648)::int4, 2147483647::int4),
+ ((-2147483648)::int4, 1073741824::int4)) AS v(a, b);
+ a | b | gcd | gcd | gcd | gcd
+-------------+------------+------------+------------+------------+------------
+ 0 | 0 | 0 | 0 | 0 | 0
+ 0 | 6410818 | 6410818 | 6410818 | 6410818 | 6410818
+ 61866666 | 6410818 | 1466 | 1466 | 1466 | 1466
+ -61866666 | 6410818 | 1466 | 1466 | 1466 | 1466
+ -2147483648 | 1 | 1 | 1 | 1 | 1
+ -2147483648 | 2147483647 | 1 | 1 | 1 | 1
+ -2147483648 | 1073741824 | 1073741824 | 1073741824 | 1073741824 | 1073741824
+(7 rows)
+
+SELECT gcd((-2147483648)::int4, 0::int4); -- overflow
+ERROR: integer out of range
+SELECT gcd((-2147483648)::int4, (-2147483648)::int4); -- overflow
+ERROR: integer out of range
+-- test lcm()
+SELECT a, b, lcm(a, b), lcm(a, -b), lcm(b, a), lcm(-b, a)
+FROM (VALUES (0::int4, 0::int4),
+ (0::int4, 42::int4),
+ (42::int4, 42::int4),
+ (330::int4, 462::int4),
+ (-330::int4, 462::int4),
+ ((-2147483648)::int4, 0::int4)) AS v(a, b);
+ a | b | lcm | lcm | lcm | lcm
+-------------+-----+------+------+------+------
+ 0 | 0 | 0 | 0 | 0 | 0
+ 0 | 42 | 0 | 0 | 0 | 0
+ 42 | 42 | 42 | 42 | 42 | 42
+ 330 | 462 | 2310 | 2310 | 2310 | 2310
+ -330 | 462 | 2310 | 2310 | 2310 | 2310
+ -2147483648 | 0 | 0 | 0 | 0 | 0
+(6 rows)
+
+SELECT lcm((-2147483648)::int4, 1::int4); -- overflow
+ERROR: integer out of range
+SELECT lcm(2147483647::int4, 2147483646::int4); -- overflow
+ERROR: integer out of range
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
new file mode 100644
index 0000000..36540ec
--- /dev/null
+++ b/src/test/regress/expected/int8.out
@@ -0,0 +1,934 @@
+--
+-- INT8
+-- Test int8 64-bit integers.
+--
+CREATE TABLE INT8_TBL(q1 int8, q2 int8);
+INSERT INTO INT8_TBL VALUES(' 123 ',' 456');
+INSERT INTO INT8_TBL VALUES('123 ','4567890123456789');
+INSERT INTO INT8_TBL VALUES('4567890123456789','123');
+INSERT INTO INT8_TBL VALUES(+4567890123456789,'4567890123456789');
+INSERT INTO INT8_TBL VALUES('+4567890123456789','-4567890123456789');
+-- bad inputs
+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)
+
+SELECT to_char(q2, 'L9999999999999999.000') FROM INT8_TBL;
+ to_char
+------------------------
+ 456.000
+ 4567890123456789.000
+ 123.000
+ 4567890123456789.000
+ -4567890123456789.000
+(5 rows)
+
+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/interval.out b/src/test/regress/expected/interval.out
new file mode 100644
index 0000000..8e2d535
--- /dev/null
+++ b/src/test/regress/expected/interval.out
@@ -0,0 +1,1045 @@
+--
+-- INTERVAL
+--
+SET DATESTYLE = 'ISO';
+SET IntervalStyle to postgres;
+-- check acceptance of "time zone style"
+SELECT INTERVAL '01:00' AS "One hour";
+ One hour
+----------
+ 01:00:00
+(1 row)
+
+SELECT INTERVAL '+02:00' AS "Two hours";
+ Two hours
+-----------
+ 02:00:00
+(1 row)
+
+SELECT INTERVAL '-08:00' AS "Eight hours";
+ Eight hours
+-------------
+ -08:00:00
+(1 row)
+
+SELECT INTERVAL '-1 +02:03' AS "22 hours ago...";
+ 22 hours ago...
+-------------------
+ -1 days +02:03:00
+(1 row)
+
+SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
+ 22 hours ago...
+-------------------
+ -1 days +02:03:00
+(1 row)
+
+SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
+ Ten days twelve hours
+-----------------------
+ 10 days 12:00:00
+(1 row)
+
+SELECT INTERVAL '1.5 months' AS "One month 15 days";
+ One month 15 days
+-------------------
+ 1 mon 15 days
+(1 row)
+
+SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+ 9 years...
+----------------------------------
+ 9 years 1 mon -12 days +13:14:00
+(1 row)
+
+CREATE TABLE INTERVAL_TBL (f1 interval);
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 10 day');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 34 year');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 3 months');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 14 seconds ago');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+-- badly formatted interval
+INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
+ERROR: invalid input syntax for type interval: "badly formatted interval"
+LINE 1: INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted inter...
+ ^
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 30 eons ago');
+ERROR: invalid input syntax for type interval: "@ 30 eons ago"
+LINE 1: INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 30 eons ago');
+ ^
+-- test interval operators
+SELECT * FROM INTERVAL_TBL;
+ f1
+-----------------
+ 00:01:00
+ 05:00:00
+ 10 days
+ 34 years
+ 3 mons
+ -00:00:14
+ 1 day 02:03:04
+ 6 years
+ 5 mons
+ 5 mons 12:00:00
+(10 rows)
+
+SELECT * FROM INTERVAL_TBL
+ WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
+ f1
+-----------------
+ 00:01:00
+ 05:00:00
+ 34 years
+ 3 mons
+ -00:00:14
+ 1 day 02:03:04
+ 6 years
+ 5 mons
+ 5 mons 12:00:00
+(9 rows)
+
+SELECT * FROM INTERVAL_TBL
+ WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
+ f1
+-----------
+ 00:01:00
+ 05:00:00
+ -00:00:14
+(3 rows)
+
+SELECT * FROM INTERVAL_TBL
+ WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
+ f1
+-----------
+ 00:01:00
+ 05:00:00
+ -00:00:14
+(3 rows)
+
+SELECT * FROM INTERVAL_TBL
+ WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
+ f1
+----------
+ 34 years
+(1 row)
+
+SELECT * FROM INTERVAL_TBL
+ WHERE INTERVAL_TBL.f1 >= interval '@ 1 month';
+ f1
+-----------------
+ 34 years
+ 3 mons
+ 6 years
+ 5 mons
+ 5 mons 12:00:00
+(5 rows)
+
+SELECT * FROM INTERVAL_TBL
+ WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
+ f1
+-----------------
+ 00:01:00
+ 05:00:00
+ 10 days
+ 34 years
+ 3 mons
+ 1 day 02:03:04
+ 6 years
+ 5 mons
+ 5 mons 12:00:00
+(9 rows)
+
+SELECT r1.*, r2.*
+ FROM INTERVAL_TBL r1, INTERVAL_TBL r2
+ WHERE r1.f1 > r2.f1
+ ORDER BY r1.f1, r2.f1;
+ f1 | f1
+-----------------+-----------------
+ 00:01:00 | -00:00:14
+ 05:00:00 | -00:00:14
+ 05:00:00 | 00:01:00
+ 1 day 02:03:04 | -00:00:14
+ 1 day 02:03:04 | 00:01:00
+ 1 day 02:03:04 | 05:00:00
+ 10 days | -00:00:14
+ 10 days | 00:01:00
+ 10 days | 05:00:00
+ 10 days | 1 day 02:03:04
+ 3 mons | -00:00:14
+ 3 mons | 00:01:00
+ 3 mons | 05:00:00
+ 3 mons | 1 day 02:03:04
+ 3 mons | 10 days
+ 5 mons | -00:00:14
+ 5 mons | 00:01:00
+ 5 mons | 05:00:00
+ 5 mons | 1 day 02:03:04
+ 5 mons | 10 days
+ 5 mons | 3 mons
+ 5 mons 12:00:00 | -00:00:14
+ 5 mons 12:00:00 | 00:01:00
+ 5 mons 12:00:00 | 05:00:00
+ 5 mons 12:00:00 | 1 day 02:03:04
+ 5 mons 12:00:00 | 10 days
+ 5 mons 12:00:00 | 3 mons
+ 5 mons 12:00:00 | 5 mons
+ 6 years | -00:00:14
+ 6 years | 00:01:00
+ 6 years | 05:00:00
+ 6 years | 1 day 02:03:04
+ 6 years | 10 days
+ 6 years | 3 mons
+ 6 years | 5 mons
+ 6 years | 5 mons 12:00:00
+ 34 years | -00:00:14
+ 34 years | 00:01:00
+ 34 years | 05:00:00
+ 34 years | 1 day 02:03:04
+ 34 years | 10 days
+ 34 years | 3 mons
+ 34 years | 5 mons
+ 34 years | 5 mons 12:00:00
+ 34 years | 6 years
+(45 rows)
+
+-- Test intervals that are large enough to overflow 64 bits in comparisons
+CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
+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');
+-- these should fail as out-of-range
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
+ERROR: interval field value out of range: "2147483648 days"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
+ ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
+ERROR: interval field value out of range: "-2147483649 days"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days')...
+ ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 years');
+ERROR: interval out of range
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 years')...
+ ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
+ERROR: interval out of range
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years'...
+ ^
+-- Test edge-case overflow detection in interval multiplication
+select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
+ERROR: interval out of range
+SELECT r1.*, r2.*
+ FROM INTERVAL_TBL_OF r1, INTERVAL_TBL_OF r2
+ WHERE r1.f1 > r2.f1
+ ORDER BY r1.f1, r2.f1;
+ f1 | f1
+-------------------------------------------+-------------------------------------------
+ -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
+ 1 year | -178956970 years -8 mons -2147483648 days
+ 1 year | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
+ 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons -2147483648 days | 1 year
+ 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
+ 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons 2147483647 days | 1 year
+ 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+(10 rows)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Index Only Scan using interval_tbl_of_f1_idx on interval_tbl_of r1
+(1 row)
+
+SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
+ f1
+-------------------------------------------
+ -178956970 years -8 mons -2147483648 days
+ -178956970 years -8 mons +2147483647 days
+ 1 year
+ 178956970 years 7 mons -2147483648 days
+ 178956970 years 7 mons 2147483647 days
+(5 rows)
+
+RESET enable_seqscan;
+DROP TABLE INTERVAL_TBL_OF;
+-- Test multiplication and division with intervals.
+-- Floating point arithmetic rounding errors can lead to unexpected results,
+-- though the code attempts to do the right thing and round up to days and
+-- minutes to avoid results such as '3 days 24:00 hours' or '14:20:60'.
+-- Note that it is expected for some day components to be greater than 29 and
+-- some time components be greater than 23:59:59 due to how intervals are
+-- stored internally.
+CREATE TABLE INTERVAL_MULDIV_TBL (span interval);
+COPY INTERVAL_MULDIV_TBL FROM STDIN;
+SELECT span * 0.3 AS product
+FROM INTERVAL_MULDIV_TBL;
+ product
+------------------------------------
+ 1 year 12 days 122:24:00
+ -1 years -12 days +93:36:00
+ -3 days -14:24:00
+ 2 mons 13 days 01:22:28.8
+ -10 mons +120 days 37:28:21.6567
+ 1 mon 6 days
+ 4 mons 6 days
+ 24 years 11 mons 320 days 16:48:00
+(8 rows)
+
+SELECT span * 8.2 AS product
+FROM INTERVAL_MULDIV_TBL;
+ product
+---------------------------------------------
+ 28 years 104 days 2961:36:00
+ -28 years -104 days +2942:24:00
+ -98 days -09:36:00
+ 6 years 1 mon -197 days +93:34:27.2
+ -24 years -7 mons +3946 days 640:15:11.9498
+ 2 years 8 mons 24 days
+ 9 years 6 mons 24 days
+ 682 years 7 mons 8215 days 19:12:00
+(8 rows)
+
+SELECT span / 10 AS quotient
+FROM INTERVAL_MULDIV_TBL;
+ quotient
+----------------------------------
+ 4 mons 4 days 40:48:00
+ -4 mons -4 days +31:12:00
+ -1 days -04:48:00
+ 25 days -15:32:30.4
+ -3 mons +30 days 12:29:27.2189
+ 12 days
+ 1 mon 12 days
+ 8 years 3 mons 126 days 21:36:00
+(8 rows)
+
+SELECT span / 100 AS quotient
+FROM INTERVAL_MULDIV_TBL;
+ quotient
+-------------------------
+ 12 days 13:40:48
+ -12 days -06:28:48
+ -02:52:48
+ 2 days 10:26:44.96
+ -6 days +01:14:56.72189
+ 1 day 04:48:00
+ 4 days 04:48:00
+ 9 mons 39 days 16:33:36
+(8 rows)
+
+DROP TABLE INTERVAL_MULDIV_TBL;
+SET DATESTYLE = 'postgres';
+SET IntervalStyle to postgres_verbose;
+SELECT * FROM INTERVAL_TBL;
+ f1
+-------------------------------
+ @ 1 min
+ @ 5 hours
+ @ 10 days
+ @ 34 years
+ @ 3 mons
+ @ 14 secs ago
+ @ 1 day 2 hours 3 mins 4 secs
+ @ 6 years
+ @ 5 mons
+ @ 5 mons 12 hours
+(10 rows)
+
+-- test avg(interval), which is somewhat fragile since people have been
+-- known to change the allowed input syntax for type interval without
+-- updating pg_aggregate.agginitval
+select avg(f1) from interval_tbl;
+ avg
+-------------------------------------------------
+ @ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
+(1 row)
+
+-- test long interval input
+select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
+ interval
+--------------------------------------------
+ @ 4541 years 4 mons 4 days 17 mins 31 secs
+(1 row)
+
+-- test long interval output
+-- Note: the actual maximum length of the interval output is longer,
+-- but we need the test to work for both integer and floating-point
+-- timestamps.
+select '100000000y 10mon -1000000000d -100000h -10min -10.000001s ago'::interval;
+ interval
+---------------------------------------------------------------------------------------
+ @ 100000000 years 10 mons -1000000000 days -100000 hours -10 mins -10.000001 secs ago
+(1 row)
+
+-- test justify_hours() and justify_days()
+SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as "6 mons 5 days 4 hours 3 mins 2 seconds";
+ 6 mons 5 days 4 hours 3 mins 2 seconds
+----------------------------------------
+ @ 6 mons 5 days 4 hours 3 mins 2 secs
+(1 row)
+
+SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
+ 7 mons 6 days 5 hours 4 mins 3 seconds
+----------------------------------------
+ @ 7 mons 6 days 5 hours 4 mins 3 secs
+(1 row)
+
+-- test justify_interval()
+SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
+ 1 month -1 hour
+--------------------
+ @ 29 days 23 hours
+(1 row)
+
+-- test fractional second input, and detection of duplicate units
+SET DATESTYLE = 'ISO';
+SET IntervalStyle TO postgres;
+SELECT '1 millisecond'::interval, '1 microsecond'::interval,
+ '500 seconds 99 milliseconds 51 microseconds'::interval;
+ interval | interval | interval
+--------------+-----------------+-----------------
+ 00:00:00.001 | 00:00:00.000001 | 00:08:20.099051
+(1 row)
+
+SELECT '3 days 5 milliseconds'::interval;
+ interval
+---------------------
+ 3 days 00:00:00.005
+(1 row)
+
+SELECT '1 second 2 seconds'::interval; -- error
+ERROR: invalid input syntax for type interval: "1 second 2 seconds"
+LINE 1: SELECT '1 second 2 seconds'::interval;
+ ^
+SELECT '10 milliseconds 20 milliseconds'::interval; -- error
+ERROR: invalid input syntax for type interval: "10 milliseconds 20 milliseconds"
+LINE 1: SELECT '10 milliseconds 20 milliseconds'::interval;
+ ^
+SELECT '5.5 seconds 3 milliseconds'::interval; -- error
+ERROR: invalid input syntax for type interval: "5.5 seconds 3 milliseconds"
+LINE 1: SELECT '5.5 seconds 3 milliseconds'::interval;
+ ^
+SELECT '1:20:05 5 microseconds'::interval; -- error
+ERROR: invalid input syntax for type interval: "1:20:05 5 microseconds"
+LINE 1: SELECT '1:20:05 5 microseconds'::interval;
+ ^
+SELECT '1 day 1 day'::interval; -- error
+ERROR: invalid input syntax for type interval: "1 day 1 day"
+LINE 1: SELECT '1 day 1 day'::interval;
+ ^
+SELECT interval '1-2'; -- SQL year-month literal
+ interval
+---------------
+ 1 year 2 mons
+(1 row)
+
+SELECT interval '999' second; -- oversize leading field is ok
+ interval
+----------
+ 00:16:39
+(1 row)
+
+SELECT interval '999' minute;
+ interval
+----------
+ 16:39:00
+(1 row)
+
+SELECT interval '999' hour;
+ interval
+-----------
+ 999:00:00
+(1 row)
+
+SELECT interval '999' day;
+ interval
+----------
+ 999 days
+(1 row)
+
+SELECT interval '999' month;
+ interval
+-----------------
+ 83 years 3 mons
+(1 row)
+
+-- test SQL-spec syntaxes for restricted field sets
+SELECT interval '1' year;
+ interval
+----------
+ 1 year
+(1 row)
+
+SELECT interval '2' month;
+ interval
+----------
+ 2 mons
+(1 row)
+
+SELECT interval '3' day;
+ interval
+----------
+ 3 days
+(1 row)
+
+SELECT interval '4' hour;
+ interval
+----------
+ 04:00:00
+(1 row)
+
+SELECT interval '5' minute;
+ interval
+----------
+ 00:05:00
+(1 row)
+
+SELECT interval '6' second;
+ interval
+----------
+ 00:00:06
+(1 row)
+
+SELECT interval '1' year to month;
+ interval
+----------
+ 1 mon
+(1 row)
+
+SELECT interval '1-2' year to month;
+ interval
+---------------
+ 1 year 2 mons
+(1 row)
+
+SELECT interval '1 2' day to hour;
+ interval
+----------------
+ 1 day 02:00:00
+(1 row)
+
+SELECT interval '1 2:03' day to hour;
+ interval
+----------------
+ 1 day 02:00:00
+(1 row)
+
+SELECT interval '1 2:03:04' day to hour;
+ interval
+----------------
+ 1 day 02:00:00
+(1 row)
+
+SELECT interval '1 2' day to minute;
+ERROR: invalid input syntax for type interval: "1 2"
+LINE 1: SELECT interval '1 2' day to minute;
+ ^
+SELECT interval '1 2:03' day to minute;
+ interval
+----------------
+ 1 day 02:03:00
+(1 row)
+
+SELECT interval '1 2:03:04' day to minute;
+ interval
+----------------
+ 1 day 02:03:00
+(1 row)
+
+SELECT interval '1 2' day to second;
+ERROR: invalid input syntax for type interval: "1 2"
+LINE 1: SELECT interval '1 2' day to second;
+ ^
+SELECT interval '1 2:03' day to second;
+ interval
+----------------
+ 1 day 02:03:00
+(1 row)
+
+SELECT interval '1 2:03:04' day to second;
+ interval
+----------------
+ 1 day 02:03:04
+(1 row)
+
+SELECT interval '1 2' hour to minute;
+ERROR: invalid input syntax for type interval: "1 2"
+LINE 1: SELECT interval '1 2' hour to minute;
+ ^
+SELECT interval '1 2:03' hour to minute;
+ interval
+----------------
+ 1 day 02:03:00
+(1 row)
+
+SELECT interval '1 2:03:04' hour to minute;
+ interval
+----------------
+ 1 day 02:03:00
+(1 row)
+
+SELECT interval '1 2' hour to second;
+ERROR: invalid input syntax for type interval: "1 2"
+LINE 1: SELECT interval '1 2' hour to second;
+ ^
+SELECT interval '1 2:03' hour to second;
+ interval
+----------------
+ 1 day 02:03:00
+(1 row)
+
+SELECT interval '1 2:03:04' hour to second;
+ interval
+----------------
+ 1 day 02:03:04
+(1 row)
+
+SELECT interval '1 2' minute to second;
+ERROR: invalid input syntax for type interval: "1 2"
+LINE 1: SELECT interval '1 2' minute to second;
+ ^
+SELECT interval '1 2:03' minute to second;
+ interval
+----------------
+ 1 day 00:02:03
+(1 row)
+
+SELECT interval '1 2:03:04' minute to second;
+ interval
+----------------
+ 1 day 02:03:04
+(1 row)
+
+SELECT interval '1 +2:03' minute to second;
+ interval
+----------------
+ 1 day 00:02:03
+(1 row)
+
+SELECT interval '1 +2:03:04' minute to second;
+ interval
+----------------
+ 1 day 02:03:04
+(1 row)
+
+SELECT interval '1 -2:03' minute to second;
+ interval
+-----------------
+ 1 day -00:02:03
+(1 row)
+
+SELECT interval '1 -2:03:04' minute to second;
+ interval
+-----------------
+ 1 day -02:03:04
+(1 row)
+
+SELECT interval '123 11' day to hour; -- ok
+ interval
+-------------------
+ 123 days 11:00:00
+(1 row)
+
+SELECT interval '123 11' day; -- not ok
+ERROR: invalid input syntax for type interval: "123 11"
+LINE 1: SELECT interval '123 11' day;
+ ^
+SELECT interval '123 11'; -- not ok, too ambiguous
+ERROR: invalid input syntax for type interval: "123 11"
+LINE 1: SELECT interval '123 11';
+ ^
+SELECT interval '123 2:03 -2:04'; -- not ok, redundant hh:mm fields
+ERROR: invalid input syntax for type interval: "123 2:03 -2:04"
+LINE 1: SELECT interval '123 2:03 -2:04';
+ ^
+-- test syntaxes for restricted precision
+SELECT interval(0) '1 day 01:23:45.6789';
+ interval
+----------------
+ 1 day 01:23:46
+(1 row)
+
+SELECT interval(2) '1 day 01:23:45.6789';
+ interval
+-------------------
+ 1 day 01:23:45.68
+(1 row)
+
+SELECT interval '12:34.5678' minute to second(2); -- per SQL spec
+ interval
+-------------
+ 00:12:34.57
+(1 row)
+
+SELECT interval '1.234' second;
+ interval
+--------------
+ 00:00:01.234
+(1 row)
+
+SELECT interval '1.234' second(2);
+ interval
+-------------
+ 00:00:01.23
+(1 row)
+
+SELECT interval '1 2.345' day to second(2);
+ERROR: invalid input syntax for type interval: "1 2.345"
+LINE 1: SELECT interval '1 2.345' day to second(2);
+ ^
+SELECT interval '1 2:03' day to second(2);
+ interval
+----------------
+ 1 day 02:03:00
+(1 row)
+
+SELECT interval '1 2:03.4567' day to second(2);
+ interval
+-------------------
+ 1 day 00:02:03.46
+(1 row)
+
+SELECT interval '1 2:03:04.5678' day to second(2);
+ interval
+-------------------
+ 1 day 02:03:04.57
+(1 row)
+
+SELECT interval '1 2.345' hour to second(2);
+ERROR: invalid input syntax for type interval: "1 2.345"
+LINE 1: SELECT interval '1 2.345' hour to second(2);
+ ^
+SELECT interval '1 2:03.45678' hour to second(2);
+ interval
+-------------------
+ 1 day 00:02:03.46
+(1 row)
+
+SELECT interval '1 2:03:04.5678' hour to second(2);
+ interval
+-------------------
+ 1 day 02:03:04.57
+(1 row)
+
+SELECT interval '1 2.3456' minute to second(2);
+ERROR: invalid input syntax for type interval: "1 2.3456"
+LINE 1: SELECT interval '1 2.3456' minute to second(2);
+ ^
+SELECT interval '1 2:03.5678' minute to second(2);
+ interval
+-------------------
+ 1 day 00:02:03.57
+(1 row)
+
+SELECT interval '1 2:03:04.5678' minute to second(2);
+ interval
+-------------------
+ 1 day 02:03:04.57
+(1 row)
+
+-- test casting to restricted precision (bug #14479)
+SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
+ (f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years"
+ FROM interval_tbl;
+ f1 | minutes | years
+-----------------+-----------------+----------
+ 00:01:00 | 00:01:00 | 00:00:00
+ 05:00:00 | 05:00:00 | 00:00:00
+ 10 days | 10 days | 00:00:00
+ 34 years | 34 years | 34 years
+ 3 mons | 3 mons | 00:00:00
+ -00:00:14 | 00:00:00 | 00:00:00
+ 1 day 02:03:04 | 1 day 02:03:00 | 00:00:00
+ 6 years | 6 years | 6 years
+ 5 mons | 5 mons | 00:00:00
+ 5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00
+(10 rows)
+
+-- test inputting and outputting SQL standard interval literals
+SET IntervalStyle TO sql_standard;
+SELECT interval '0' AS "zero",
+ interval '1-2' year to month AS "year-month",
+ interval '1 2:03:04' day to second AS "day-time",
+ - interval '1-2' AS "negative year-month",
+ - interval '1 2:03:04' AS "negative day-time";
+ zero | year-month | day-time | negative year-month | negative day-time
+------+------------+-----------+---------------------+-------------------
+ 0 | 1-2 | 1 2:03:04 | -1-2 | -1 2:03:04
+(1 row)
+
+-- test input of some not-quite-standard interval values in the sql style
+SET IntervalStyle TO postgres;
+SELECT interval '+1 -1:00:00',
+ interval '-1 +1:00:00',
+ interval '+1-2 -3 +4:05:06.789',
+ interval '-1-2 +3 -4:05:06.789';
+ interval | interval | interval | interval
+-----------------+-------------------+-------------------------------------+----------------------------------------
+ 1 day -01:00:00 | -1 days +01:00:00 | 1 year 2 mons -3 days +04:05:06.789 | -1 years -2 mons +3 days -04:05:06.789
+(1 row)
+
+-- test output of couple non-standard interval values in the sql style
+SET IntervalStyle TO sql_standard;
+SELECT interval '1 day -1 hours',
+ interval '-1 days +1 hours',
+ interval '1 years 2 months -3 days 4 hours 5 minutes 6.789 seconds',
+ - interval '1 years 2 months -3 days 4 hours 5 minutes 6.789 seconds';
+ interval | interval | interval | ?column?
+------------------+------------------+----------------------+----------------------
+ +0-0 +1 -1:00:00 | +0-0 -1 +1:00:00 | +1-2 -3 +4:05:06.789 | -1-2 +3 -4:05:06.789
+(1 row)
+
+-- test outputting iso8601 intervals
+SET IntervalStyle to iso_8601;
+select interval '0' AS "zero",
+ interval '1-2' AS "a year 2 months",
+ interval '1 2:03:04' AS "a bit over a day",
+ interval '2:03:04.45679' AS "a bit over 2 hours",
+ (interval '1-2' + interval '3 4:05:06.7') AS "all fields",
+ (interval '1-2' - interval '3 4:05:06.7') AS "mixed sign",
+ (- interval '1-2' + interval '3 4:05:06.7') AS "negative";
+ zero | a year 2 months | a bit over a day | a bit over 2 hours | all fields | mixed sign | negative
+------+-----------------+------------------+--------------------+------------------+----------------------+--------------------
+ PT0S | P1Y2M | P1DT2H3M4S | PT2H3M4.45679S | P1Y2M3DT4H5M6.7S | P1Y2M-3DT-4H-5M-6.7S | P-1Y-2M3DT4H5M6.7S
+(1 row)
+
+-- test inputting ISO 8601 4.4.2.1 "Format With Time Unit Designators"
+SET IntervalStyle to sql_standard;
+select interval 'P0Y' AS "zero",
+ interval 'P1Y2M' AS "a year 2 months",
+ interval 'P1W' AS "a week",
+ interval 'P1DT2H3M4S' AS "a bit over a day",
+ interval 'P1Y2M3DT4H5M6.7S' AS "all fields",
+ interval 'P-1Y-2M-3DT-4H-5M-6.7S' AS "negative",
+ interval 'PT-0.1S' AS "fractional second";
+ zero | a year 2 months | a week | a bit over a day | all fields | negative | fractional second
+------+-----------------+-----------+------------------+--------------------+--------------------+-------------------
+ 0 | 1-2 | 7 0:00:00 | 1 2:03:04 | +1-2 +3 +4:05:06.7 | -1-2 -3 -4:05:06.7 | -0:00:00.1
+(1 row)
+
+-- test inputting ISO 8601 4.4.2.2 "Alternative Format"
+SET IntervalStyle to postgres;
+select interval 'P00021015T103020' AS "ISO8601 Basic Format",
+ interval 'P0002-10-15T10:30:20' AS "ISO8601 Extended Format";
+ ISO8601 Basic Format | ISO8601 Extended Format
+----------------------------------+----------------------------------
+ 2 years 10 mons 15 days 10:30:20 | 2 years 10 mons 15 days 10:30:20
+(1 row)
+
+-- Make sure optional ISO8601 alternative format fields are optional.
+select interval 'P0002' AS "year only",
+ interval 'P0002-10' AS "year month",
+ interval 'P0002-10-15' AS "year month day",
+ interval 'P0002T1S' AS "year only plus time",
+ interval 'P0002-10T1S' AS "year month plus time",
+ interval 'P0002-10-15T1S' AS "year month day plus time",
+ interval 'PT10' AS "hour only",
+ interval 'PT10:30' AS "hour minute";
+ year only | year month | year month day | year only plus time | year month plus time | year month day plus time | hour only | hour minute
+-----------+-----------------+-------------------------+---------------------+--------------------------+----------------------------------+-----------+-------------
+ 2 years | 2 years 10 mons | 2 years 10 mons 15 days | 2 years 00:00:01 | 2 years 10 mons 00:00:01 | 2 years 10 mons 15 days 00:00:01 | 10:00:00 | 10:30:00
+(1 row)
+
+-- test a couple rounding cases that changed since 8.3 w/ HAVE_INT64_TIMESTAMP.
+SET IntervalStyle to postgres_verbose;
+select interval '-10 mons -3 days +03:55:06.70';
+ interval
+--------------------------------------------------
+ @ 10 mons 3 days -3 hours -55 mins -6.7 secs ago
+(1 row)
+
+select interval '1 year 2 mons 3 days 04:05:06.699999';
+ interval
+-----------------------------------------------------
+ @ 1 year 2 mons 3 days 4 hours 5 mins 6.699999 secs
+(1 row)
+
+select interval '0:0:0.7', interval '@ 0.70 secs', interval '0.7 seconds';
+ interval | interval | interval
+------------+------------+------------
+ @ 0.7 secs | @ 0.7 secs | @ 0.7 secs
+(1 row)
+
+-- check that '30 days' equals '1 month' according to the hash function
+select '30 days'::interval = '1 month'::interval as t;
+ t
+---
+ t
+(1 row)
+
+select interval_hash('30 days'::interval) = interval_hash('1 month'::interval) as t;
+ t
+---
+ t
+(1 row)
+
+-- numeric constructor
+select make_interval(years := 2);
+ make_interval
+---------------
+ @ 2 years
+(1 row)
+
+select make_interval(years := 1, months := 6);
+ make_interval
+-----------------
+ @ 1 year 6 mons
+(1 row)
+
+select make_interval(years := 1, months := -1, weeks := 5, days := -7, hours := 25, mins := -180);
+ make_interval
+----------------------------
+ @ 11 mons 28 days 22 hours
+(1 row)
+
+select make_interval() = make_interval(years := 0, months := 0, weeks := 0, days := 0, mins := 0, secs := 0.0);
+ ?column?
+----------
+ t
+(1 row)
+
+select make_interval(hours := -2, mins := -10, secs := -25.3);
+ make_interval
+---------------------------------
+ @ 2 hours 10 mins 25.3 secs ago
+(1 row)
+
+select make_interval(years := 'inf'::float::int);
+ERROR: integer out of range
+select make_interval(months := 'NaN'::float::int);
+ERROR: integer out of range
+select make_interval(secs := 'inf');
+ERROR: interval out of range
+select make_interval(secs := 'NaN');
+ERROR: interval out of range
+select make_interval(secs := 7e12);
+ make_interval
+------------------------------------
+ @ 1944444444 hours 26 mins 40 secs
+(1 row)
+
+--
+-- test EXTRACT
+--
+SELECT f1,
+ EXTRACT(MICROSECOND FROM f1) AS MICROSECOND,
+ EXTRACT(MILLISECOND FROM f1) AS MILLISECOND,
+ EXTRACT(SECOND FROM f1) AS SECOND,
+ EXTRACT(MINUTE FROM f1) AS MINUTE,
+ EXTRACT(HOUR FROM f1) AS HOUR,
+ EXTRACT(DAY FROM f1) AS DAY,
+ EXTRACT(MONTH FROM f1) AS MONTH,
+ EXTRACT(QUARTER FROM f1) AS QUARTER,
+ EXTRACT(YEAR FROM f1) AS YEAR,
+ EXTRACT(DECADE FROM f1) AS DECADE,
+ EXTRACT(CENTURY FROM f1) AS CENTURY,
+ EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
+ EXTRACT(EPOCH FROM f1) AS EPOCH
+ FROM INTERVAL_TBL;
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+(10 rows)
+
+SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
+ERROR: interval units "fortnight" not recognized
+SELECT EXTRACT(TIMEZONE FROM INTERVAL '2 days'); -- error
+ERROR: interval units "timezone" not supported
+SELECT EXTRACT(DECADE FROM INTERVAL '100 y');
+ extract
+---------
+ 10
+(1 row)
+
+SELECT EXTRACT(DECADE FROM INTERVAL '99 y');
+ extract
+---------
+ 9
+(1 row)
+
+SELECT EXTRACT(DECADE FROM INTERVAL '-99 y');
+ extract
+---------
+ -9
+(1 row)
+
+SELECT EXTRACT(DECADE FROM INTERVAL '-100 y');
+ extract
+---------
+ -10
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM INTERVAL '100 y');
+ extract
+---------
+ 1
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM INTERVAL '99 y');
+ extract
+---------
+ 0
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM INTERVAL '-99 y');
+ extract
+---------
+ 0
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM INTERVAL '-100 y');
+ extract
+---------
+ -1
+(1 row)
+
+-- date_part implementation is mostly the same as extract, so only
+-- test a few cases for additional coverage.
+SELECT f1,
+ date_part('microsecond', f1) AS microsecond,
+ date_part('millisecond', f1) AS millisecond,
+ date_part('second', f1) AS second,
+ date_part('epoch', f1) AS epoch
+ FROM INTERVAL_TBL;
+ f1 | microsecond | millisecond | second | epoch
+-------------------------------+-------------+-------------+--------+------------
+ @ 1 min | 0 | 0 | 0 | 60
+ @ 5 hours | 0 | 0 | 0 | 18000
+ @ 10 days | 0 | 0 | 0 | 864000
+ @ 34 years | 0 | 0 | 0 | 1072958400
+ @ 3 mons | 0 | 0 | 0 | 7776000
+ @ 14 secs ago | -14000000 | -14000 | -14 | -14
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000 | 4 | 93784
+ @ 6 years | 0 | 0 | 0 | 189345600
+ @ 5 mons | 0 | 0 | 0 | 12960000
+ @ 5 mons 12 hours | 0 | 0 | 0 | 13003200
+(10 rows)
+
+-- internal overflow test case
+SELECT extract(epoch from interval '1000000000 days');
+ extract
+-----------------------
+ 86400000000000.000000
+(1 row)
+
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
new file mode 100644
index 0000000..5c56304
--- /dev/null
+++ b/src/test/regress/expected/join.out
@@ -0,0 +1,6628 @@
+--
+-- 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);
+-- useful in some tests below
+create temp table onerow();
+insert into onerow default values;
+analyze onerow;
+--
+-- CORRELATION NAMES
+-- Make sure that table/column aliases are supported
+-- before diving into more complex join syntax.
+--
+SELECT *
+ FROM J1_TBL AS tx;
+ i | j | t
+---+---+-------
+ 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
+ | 0 | zero
+(11 rows)
+
+SELECT *
+ FROM J1_TBL tx;
+ i | j | t
+---+---+-------
+ 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
+ | 0 | zero
+(11 rows)
+
+SELECT *
+ FROM J1_TBL AS t1 (a, b, c);
+ a | b | c
+---+---+-------
+ 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
+ | 0 | zero
+(11 rows)
+
+SELECT *
+ FROM J1_TBL t1 (a, b, c);
+ a | b | c
+---+---+-------
+ 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
+ | 0 | zero
+(11 rows)
+
+SELECT *
+ FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e);
+ a | b | c | d | e
+---+---+-------+---+----
+ 1 | 4 | one | 1 | -1
+ 2 | 3 | two | 1 | -1
+ 3 | 2 | three | 1 | -1
+ 4 | 1 | four | 1 | -1
+ 5 | 0 | five | 1 | -1
+ 6 | 6 | six | 1 | -1
+ 7 | 7 | seven | 1 | -1
+ 8 | 8 | eight | 1 | -1
+ 0 | | zero | 1 | -1
+ | | null | 1 | -1
+ | 0 | zero | 1 | -1
+ 1 | 4 | one | 2 | 2
+ 2 | 3 | two | 2 | 2
+ 3 | 2 | three | 2 | 2
+ 4 | 1 | four | 2 | 2
+ 5 | 0 | five | 2 | 2
+ 6 | 6 | six | 2 | 2
+ 7 | 7 | seven | 2 | 2
+ 8 | 8 | eight | 2 | 2
+ 0 | | zero | 2 | 2
+ | | null | 2 | 2
+ | 0 | zero | 2 | 2
+ 1 | 4 | one | 3 | -3
+ 2 | 3 | two | 3 | -3
+ 3 | 2 | three | 3 | -3
+ 4 | 1 | four | 3 | -3
+ 5 | 0 | five | 3 | -3
+ 6 | 6 | six | 3 | -3
+ 7 | 7 | seven | 3 | -3
+ 8 | 8 | eight | 3 | -3
+ 0 | | zero | 3 | -3
+ | | null | 3 | -3
+ | 0 | zero | 3 | -3
+ 1 | 4 | one | 2 | 4
+ 2 | 3 | two | 2 | 4
+ 3 | 2 | three | 2 | 4
+ 4 | 1 | four | 2 | 4
+ 5 | 0 | five | 2 | 4
+ 6 | 6 | six | 2 | 4
+ 7 | 7 | seven | 2 | 4
+ 8 | 8 | eight | 2 | 4
+ 0 | | zero | 2 | 4
+ | | null | 2 | 4
+ | 0 | zero | 2 | 4
+ 1 | 4 | one | 5 | -5
+ 2 | 3 | two | 5 | -5
+ 3 | 2 | three | 5 | -5
+ 4 | 1 | four | 5 | -5
+ 5 | 0 | five | 5 | -5
+ 6 | 6 | six | 5 | -5
+ 7 | 7 | seven | 5 | -5
+ 8 | 8 | eight | 5 | -5
+ 0 | | zero | 5 | -5
+ | | null | 5 | -5
+ | 0 | zero | 5 | -5
+ 1 | 4 | one | 5 | -5
+ 2 | 3 | two | 5 | -5
+ 3 | 2 | three | 5 | -5
+ 4 | 1 | four | 5 | -5
+ 5 | 0 | five | 5 | -5
+ 6 | 6 | six | 5 | -5
+ 7 | 7 | seven | 5 | -5
+ 8 | 8 | eight | 5 | -5
+ 0 | | zero | 5 | -5
+ | | null | 5 | -5
+ | 0 | zero | 5 | -5
+ 1 | 4 | one | 0 |
+ 2 | 3 | two | 0 |
+ 3 | 2 | three | 0 |
+ 4 | 1 | four | 0 |
+ 5 | 0 | five | 0 |
+ 6 | 6 | six | 0 |
+ 7 | 7 | seven | 0 |
+ 8 | 8 | eight | 0 |
+ 0 | | zero | 0 |
+ | | null | 0 |
+ | 0 | zero | 0 |
+ 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 | |
+ | 0 | zero | |
+ 1 | 4 | one | | 0
+ 2 | 3 | two | | 0
+ 3 | 2 | three | | 0
+ 4 | 1 | four | | 0
+ 5 | 0 | five | | 0
+ 6 | 6 | six | | 0
+ 7 | 7 | seven | | 0
+ 8 | 8 | eight | | 0
+ 0 | | zero | | 0
+ | | null | | 0
+ | 0 | zero | | 0
+(99 rows)
+
+SELECT t1.a, t2.e
+ FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e)
+ WHERE t1.a = t2.d;
+ 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 *
+ FROM J1_TBL CROSS JOIN J2_TBL;
+ i | j | t | i | k
+---+---+-------+---+----
+ 1 | 4 | one | 1 | -1
+ 2 | 3 | two | 1 | -1
+ 3 | 2 | three | 1 | -1
+ 4 | 1 | four | 1 | -1
+ 5 | 0 | five | 1 | -1
+ 6 | 6 | six | 1 | -1
+ 7 | 7 | seven | 1 | -1
+ 8 | 8 | eight | 1 | -1
+ 0 | | zero | 1 | -1
+ | | null | 1 | -1
+ | 0 | zero | 1 | -1
+ 1 | 4 | one | 2 | 2
+ 2 | 3 | two | 2 | 2
+ 3 | 2 | three | 2 | 2
+ 4 | 1 | four | 2 | 2
+ 5 | 0 | five | 2 | 2
+ 6 | 6 | six | 2 | 2
+ 7 | 7 | seven | 2 | 2
+ 8 | 8 | eight | 2 | 2
+ 0 | | zero | 2 | 2
+ | | null | 2 | 2
+ | 0 | zero | 2 | 2
+ 1 | 4 | one | 3 | -3
+ 2 | 3 | two | 3 | -3
+ 3 | 2 | three | 3 | -3
+ 4 | 1 | four | 3 | -3
+ 5 | 0 | five | 3 | -3
+ 6 | 6 | six | 3 | -3
+ 7 | 7 | seven | 3 | -3
+ 8 | 8 | eight | 3 | -3
+ 0 | | zero | 3 | -3
+ | | null | 3 | -3
+ | 0 | zero | 3 | -3
+ 1 | 4 | one | 2 | 4
+ 2 | 3 | two | 2 | 4
+ 3 | 2 | three | 2 | 4
+ 4 | 1 | four | 2 | 4
+ 5 | 0 | five | 2 | 4
+ 6 | 6 | six | 2 | 4
+ 7 | 7 | seven | 2 | 4
+ 8 | 8 | eight | 2 | 4
+ 0 | | zero | 2 | 4
+ | | null | 2 | 4
+ | 0 | zero | 2 | 4
+ 1 | 4 | one | 5 | -5
+ 2 | 3 | two | 5 | -5
+ 3 | 2 | three | 5 | -5
+ 4 | 1 | four | 5 | -5
+ 5 | 0 | five | 5 | -5
+ 6 | 6 | six | 5 | -5
+ 7 | 7 | seven | 5 | -5
+ 8 | 8 | eight | 5 | -5
+ 0 | | zero | 5 | -5
+ | | null | 5 | -5
+ | 0 | zero | 5 | -5
+ 1 | 4 | one | 5 | -5
+ 2 | 3 | two | 5 | -5
+ 3 | 2 | three | 5 | -5
+ 4 | 1 | four | 5 | -5
+ 5 | 0 | five | 5 | -5
+ 6 | 6 | six | 5 | -5
+ 7 | 7 | seven | 5 | -5
+ 8 | 8 | eight | 5 | -5
+ 0 | | zero | 5 | -5
+ | | null | 5 | -5
+ | 0 | zero | 5 | -5
+ 1 | 4 | one | 0 |
+ 2 | 3 | two | 0 |
+ 3 | 2 | three | 0 |
+ 4 | 1 | four | 0 |
+ 5 | 0 | five | 0 |
+ 6 | 6 | six | 0 |
+ 7 | 7 | seven | 0 |
+ 8 | 8 | eight | 0 |
+ 0 | | zero | 0 |
+ | | null | 0 |
+ | 0 | zero | 0 |
+ 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 | |
+ | 0 | zero | |
+ 1 | 4 | one | | 0
+ 2 | 3 | two | | 0
+ 3 | 2 | three | | 0
+ 4 | 1 | four | | 0
+ 5 | 0 | five | | 0
+ 6 | 6 | six | | 0
+ 7 | 7 | seven | | 0
+ 8 | 8 | eight | | 0
+ 0 | | zero | | 0
+ | | null | | 0
+ | 0 | zero | | 0
+(99 rows)
+
+-- ambiguous column
+SELECT i, k, t
+ FROM J1_TBL CROSS JOIN J2_TBL;
+ERROR: column reference "i" is ambiguous
+LINE 1: SELECT i, k, t
+ ^
+-- resolve previous ambiguity by specifying the table name
+SELECT t1.i, k, t
+ FROM J1_TBL t1 CROSS JOIN J2_TBL t2;
+ i | k | t
+---+----+-------
+ 1 | -1 | one
+ 2 | -1 | two
+ 3 | -1 | three
+ 4 | -1 | four
+ 5 | -1 | five
+ 6 | -1 | six
+ 7 | -1 | seven
+ 8 | -1 | eight
+ 0 | -1 | zero
+ | -1 | null
+ | -1 | zero
+ 1 | 2 | one
+ 2 | 2 | two
+ 3 | 2 | three
+ 4 | 2 | four
+ 5 | 2 | five
+ 6 | 2 | six
+ 7 | 2 | seven
+ 8 | 2 | eight
+ 0 | 2 | zero
+ | 2 | null
+ | 2 | zero
+ 1 | -3 | one
+ 2 | -3 | two
+ 3 | -3 | three
+ 4 | -3 | four
+ 5 | -3 | five
+ 6 | -3 | six
+ 7 | -3 | seven
+ 8 | -3 | eight
+ 0 | -3 | zero
+ | -3 | null
+ | -3 | zero
+ 1 | 4 | one
+ 2 | 4 | two
+ 3 | 4 | three
+ 4 | 4 | four
+ 5 | 4 | five
+ 6 | 4 | six
+ 7 | 4 | seven
+ 8 | 4 | eight
+ 0 | 4 | zero
+ | 4 | null
+ | 4 | zero
+ 1 | -5 | one
+ 2 | -5 | two
+ 3 | -5 | three
+ 4 | -5 | four
+ 5 | -5 | five
+ 6 | -5 | six
+ 7 | -5 | seven
+ 8 | -5 | eight
+ 0 | -5 | zero
+ | -5 | null
+ | -5 | zero
+ 1 | -5 | one
+ 2 | -5 | two
+ 3 | -5 | three
+ 4 | -5 | four
+ 5 | -5 | five
+ 6 | -5 | six
+ 7 | -5 | seven
+ 8 | -5 | eight
+ 0 | -5 | zero
+ | -5 | null
+ | -5 | zero
+ 1 | | one
+ 2 | | two
+ 3 | | three
+ 4 | | four
+ 5 | | five
+ 6 | | six
+ 7 | | seven
+ 8 | | eight
+ 0 | | zero
+ | | null
+ | | zero
+ 1 | | one
+ 2 | | two
+ 3 | | three
+ 4 | | four
+ 5 | | five
+ 6 | | six
+ 7 | | seven
+ 8 | | eight
+ 0 | | zero
+ | | null
+ | | zero
+ 1 | 0 | one
+ 2 | 0 | two
+ 3 | 0 | three
+ 4 | 0 | four
+ 5 | 0 | five
+ 6 | 0 | six
+ 7 | 0 | seven
+ 8 | 0 | eight
+ 0 | 0 | zero
+ | 0 | null
+ | 0 | zero
+(99 rows)
+
+SELECT ii, tt, kk
+ FROM (J1_TBL CROSS JOIN J2_TBL)
+ AS tx (ii, jj, tt, ii2, kk);
+ ii | tt | kk
+----+-------+----
+ 1 | one | -1
+ 2 | two | -1
+ 3 | three | -1
+ 4 | four | -1
+ 5 | five | -1
+ 6 | six | -1
+ 7 | seven | -1
+ 8 | eight | -1
+ 0 | zero | -1
+ | null | -1
+ | zero | -1
+ 1 | one | 2
+ 2 | two | 2
+ 3 | three | 2
+ 4 | four | 2
+ 5 | five | 2
+ 6 | six | 2
+ 7 | seven | 2
+ 8 | eight | 2
+ 0 | zero | 2
+ | null | 2
+ | zero | 2
+ 1 | one | -3
+ 2 | two | -3
+ 3 | three | -3
+ 4 | four | -3
+ 5 | five | -3
+ 6 | six | -3
+ 7 | seven | -3
+ 8 | eight | -3
+ 0 | zero | -3
+ | null | -3
+ | zero | -3
+ 1 | one | 4
+ 2 | two | 4
+ 3 | three | 4
+ 4 | four | 4
+ 5 | five | 4
+ 6 | six | 4
+ 7 | seven | 4
+ 8 | eight | 4
+ 0 | zero | 4
+ | null | 4
+ | zero | 4
+ 1 | one | -5
+ 2 | two | -5
+ 3 | three | -5
+ 4 | four | -5
+ 5 | five | -5
+ 6 | six | -5
+ 7 | seven | -5
+ 8 | eight | -5
+ 0 | zero | -5
+ | null | -5
+ | zero | -5
+ 1 | one | -5
+ 2 | two | -5
+ 3 | three | -5
+ 4 | four | -5
+ 5 | five | -5
+ 6 | six | -5
+ 7 | seven | -5
+ 8 | eight | -5
+ 0 | zero | -5
+ | null | -5
+ | zero | -5
+ 1 | one |
+ 2 | two |
+ 3 | three |
+ 4 | four |
+ 5 | five |
+ 6 | six |
+ 7 | seven |
+ 8 | eight |
+ 0 | zero |
+ | null |
+ | zero |
+ 1 | one |
+ 2 | two |
+ 3 | three |
+ 4 | four |
+ 5 | five |
+ 6 | six |
+ 7 | seven |
+ 8 | eight |
+ 0 | zero |
+ | null |
+ | zero |
+ 1 | one | 0
+ 2 | two | 0
+ 3 | three | 0
+ 4 | four | 0
+ 5 | five | 0
+ 6 | six | 0
+ 7 | seven | 0
+ 8 | eight | 0
+ 0 | zero | 0
+ | null | 0
+ | zero | 0
+(99 rows)
+
+SELECT 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);
+ ii | jj | kk
+----+----+----
+ 1 | 4 | -1
+ 2 | 3 | -1
+ 3 | 2 | -1
+ 4 | 1 | -1
+ 5 | 0 | -1
+ 6 | 6 | -1
+ 7 | 7 | -1
+ 8 | 8 | -1
+ 0 | | -1
+ | | -1
+ | 0 | -1
+ 1 | 4 | 2
+ 2 | 3 | 2
+ 3 | 2 | 2
+ 4 | 1 | 2
+ 5 | 0 | 2
+ 6 | 6 | 2
+ 7 | 7 | 2
+ 8 | 8 | 2
+ 0 | | 2
+ | | 2
+ | 0 | 2
+ 1 | 4 | -3
+ 2 | 3 | -3
+ 3 | 2 | -3
+ 4 | 1 | -3
+ 5 | 0 | -3
+ 6 | 6 | -3
+ 7 | 7 | -3
+ 8 | 8 | -3
+ 0 | | -3
+ | | -3
+ | 0 | -3
+ 1 | 4 | 4
+ 2 | 3 | 4
+ 3 | 2 | 4
+ 4 | 1 | 4
+ 5 | 0 | 4
+ 6 | 6 | 4
+ 7 | 7 | 4
+ 8 | 8 | 4
+ 0 | | 4
+ | | 4
+ | 0 | 4
+ 1 | 4 | -5
+ 2 | 3 | -5
+ 3 | 2 | -5
+ 4 | 1 | -5
+ 5 | 0 | -5
+ 6 | 6 | -5
+ 7 | 7 | -5
+ 8 | 8 | -5
+ 0 | | -5
+ | | -5
+ | 0 | -5
+ 1 | 4 | -5
+ 2 | 3 | -5
+ 3 | 2 | -5
+ 4 | 1 | -5
+ 5 | 0 | -5
+ 6 | 6 | -5
+ 7 | 7 | -5
+ 8 | 8 | -5
+ 0 | | -5
+ | | -5
+ | 0 | -5
+ 1 | 4 |
+ 2 | 3 |
+ 3 | 2 |
+ 4 | 1 |
+ 5 | 0 |
+ 6 | 6 |
+ 7 | 7 |
+ 8 | 8 |
+ 0 | |
+ | |
+ | 0 |
+ 1 | 4 |
+ 2 | 3 |
+ 3 | 2 |
+ 4 | 1 |
+ 5 | 0 |
+ 6 | 6 |
+ 7 | 7 |
+ 8 | 8 |
+ 0 | |
+ | |
+ | 0 |
+ 1 | 4 | 0
+ 2 | 3 | 0
+ 3 | 2 | 0
+ 4 | 1 | 0
+ 5 | 0 | 0
+ 6 | 6 | 0
+ 7 | 7 | 0
+ 8 | 8 | 0
+ 0 | | 0
+ | | 0
+ | 0 | 0
+(99 rows)
+
+SELECT *
+ FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b;
+ i | j | t | i | k | i | k
+---+---+-------+---+----+---+----
+ 1 | 4 | one | 1 | -1 | 1 | -1
+ 1 | 4 | one | 1 | -1 | 2 | 2
+ 1 | 4 | one | 1 | -1 | 3 | -3
+ 1 | 4 | one | 1 | -1 | 2 | 4
+ 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 | 1 | -1 | | 0
+ 2 | 3 | two | 1 | -1 | 1 | -1
+ 2 | 3 | two | 1 | -1 | 2 | 2
+ 2 | 3 | two | 1 | -1 | 3 | -3
+ 2 | 3 | two | 1 | -1 | 2 | 4
+ 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 | 1 | -1 | | 0
+ 3 | 2 | three | 1 | -1 | 1 | -1
+ 3 | 2 | three | 1 | -1 | 2 | 2
+ 3 | 2 | three | 1 | -1 | 3 | -3
+ 3 | 2 | three | 1 | -1 | 2 | 4
+ 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 | 1 | -1 | | 0
+ 4 | 1 | four | 1 | -1 | 1 | -1
+ 4 | 1 | four | 1 | -1 | 2 | 2
+ 4 | 1 | four | 1 | -1 | 3 | -3
+ 4 | 1 | four | 1 | -1 | 2 | 4
+ 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 | 1 | -1 | | 0
+ 5 | 0 | five | 1 | -1 | 1 | -1
+ 5 | 0 | five | 1 | -1 | 2 | 2
+ 5 | 0 | five | 1 | -1 | 3 | -3
+ 5 | 0 | five | 1 | -1 | 2 | 4
+ 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 | 1 | -1 | | 0
+ 6 | 6 | six | 1 | -1 | 1 | -1
+ 6 | 6 | six | 1 | -1 | 2 | 2
+ 6 | 6 | six | 1 | -1 | 3 | -3
+ 6 | 6 | six | 1 | -1 | 2 | 4
+ 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 | 1 | -1 | | 0
+ 7 | 7 | seven | 1 | -1 | 1 | -1
+ 7 | 7 | seven | 1 | -1 | 2 | 2
+ 7 | 7 | seven | 1 | -1 | 3 | -3
+ 7 | 7 | seven | 1 | -1 | 2 | 4
+ 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 | 1 | -1 | | 0
+ 8 | 8 | eight | 1 | -1 | 1 | -1
+ 8 | 8 | eight | 1 | -1 | 2 | 2
+ 8 | 8 | eight | 1 | -1 | 3 | -3
+ 8 | 8 | eight | 1 | -1 | 2 | 4
+ 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 | 1 | -1 | | 0
+ 0 | | zero | 1 | -1 | 1 | -1
+ 0 | | zero | 1 | -1 | 2 | 2
+ 0 | | zero | 1 | -1 | 3 | -3
+ 0 | | zero | 1 | -1 | 2 | 4
+ 0 | | zero | 1 | -1 | 5 | -5
+ 0 | | zero | 1 | -1 | 5 | -5
+ 0 | | zero | 1 | -1 | 0 |
+ 0 | | zero | 1 | -1 | |
+ 0 | | zero | 1 | -1 | | 0
+ | | null | 1 | -1 | 1 | -1
+ | | null | 1 | -1 | 2 | 2
+ | | null | 1 | -1 | 3 | -3
+ | | null | 1 | -1 | 2 | 4
+ | | null | 1 | -1 | 5 | -5
+ | | null | 1 | -1 | 5 | -5
+ | | null | 1 | -1 | 0 |
+ | | null | 1 | -1 | |
+ | | null | 1 | -1 | | 0
+ | 0 | zero | 1 | -1 | 1 | -1
+ | 0 | zero | 1 | -1 | 2 | 2
+ | 0 | zero | 1 | -1 | 3 | -3
+ | 0 | zero | 1 | -1 | 2 | 4
+ | 0 | zero | 1 | -1 | 5 | -5
+ | 0 | zero | 1 | -1 | 5 | -5
+ | 0 | zero | 1 | -1 | 0 |
+ | 0 | zero | 1 | -1 | |
+ | 0 | zero | 1 | -1 | | 0
+ 1 | 4 | one | 2 | 2 | 1 | -1
+ 1 | 4 | one | 2 | 2 | 2 | 2
+ 1 | 4 | one | 2 | 2 | 3 | -3
+ 1 | 4 | one | 2 | 2 | 2 | 4
+ 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 | 2 | | 0
+ 2 | 3 | two | 2 | 2 | 1 | -1
+ 2 | 3 | two | 2 | 2 | 2 | 2
+ 2 | 3 | two | 2 | 2 | 3 | -3
+ 2 | 3 | two | 2 | 2 | 2 | 4
+ 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 | 2 | | 0
+ 3 | 2 | three | 2 | 2 | 1 | -1
+ 3 | 2 | three | 2 | 2 | 2 | 2
+ 3 | 2 | three | 2 | 2 | 3 | -3
+ 3 | 2 | three | 2 | 2 | 2 | 4
+ 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 | 2 | | 0
+ 4 | 1 | four | 2 | 2 | 1 | -1
+ 4 | 1 | four | 2 | 2 | 2 | 2
+ 4 | 1 | four | 2 | 2 | 3 | -3
+ 4 | 1 | four | 2 | 2 | 2 | 4
+ 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 | 2 | | 0
+ 5 | 0 | five | 2 | 2 | 1 | -1
+ 5 | 0 | five | 2 | 2 | 2 | 2
+ 5 | 0 | five | 2 | 2 | 3 | -3
+ 5 | 0 | five | 2 | 2 | 2 | 4
+ 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 | 2 | | 0
+ 6 | 6 | six | 2 | 2 | 1 | -1
+ 6 | 6 | six | 2 | 2 | 2 | 2
+ 6 | 6 | six | 2 | 2 | 3 | -3
+ 6 | 6 | six | 2 | 2 | 2 | 4
+ 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 | 2 | | 0
+ 7 | 7 | seven | 2 | 2 | 1 | -1
+ 7 | 7 | seven | 2 | 2 | 2 | 2
+ 7 | 7 | seven | 2 | 2 | 3 | -3
+ 7 | 7 | seven | 2 | 2 | 2 | 4
+ 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 | 2 | | 0
+ 8 | 8 | eight | 2 | 2 | 1 | -1
+ 8 | 8 | eight | 2 | 2 | 2 | 2
+ 8 | 8 | eight | 2 | 2 | 3 | -3
+ 8 | 8 | eight | 2 | 2 | 2 | 4
+ 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 | 2 | | 0
+ 0 | | zero | 2 | 2 | 1 | -1
+ 0 | | zero | 2 | 2 | 2 | 2
+ 0 | | zero | 2 | 2 | 3 | -3
+ 0 | | zero | 2 | 2 | 2 | 4
+ 0 | | zero | 2 | 2 | 5 | -5
+ 0 | | zero | 2 | 2 | 5 | -5
+ 0 | | zero | 2 | 2 | 0 |
+ 0 | | zero | 2 | 2 | |
+ 0 | | zero | 2 | 2 | | 0
+ | | null | 2 | 2 | 1 | -1
+ | | null | 2 | 2 | 2 | 2
+ | | null | 2 | 2 | 3 | -3
+ | | null | 2 | 2 | 2 | 4
+ | | null | 2 | 2 | 5 | -5
+ | | null | 2 | 2 | 5 | -5
+ | | null | 2 | 2 | 0 |
+ | | null | 2 | 2 | |
+ | | null | 2 | 2 | | 0
+ | 0 | zero | 2 | 2 | 1 | -1
+ | 0 | zero | 2 | 2 | 2 | 2
+ | 0 | zero | 2 | 2 | 3 | -3
+ | 0 | zero | 2 | 2 | 2 | 4
+ | 0 | zero | 2 | 2 | 5 | -5
+ | 0 | zero | 2 | 2 | 5 | -5
+ | 0 | zero | 2 | 2 | 0 |
+ | 0 | zero | 2 | 2 | |
+ | 0 | zero | 2 | 2 | | 0
+ 1 | 4 | one | 3 | -3 | 1 | -1
+ 1 | 4 | one | 3 | -3 | 2 | 2
+ 1 | 4 | one | 3 | -3 | 3 | -3
+ 1 | 4 | one | 3 | -3 | 2 | 4
+ 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 | 3 | -3 | | 0
+ 2 | 3 | two | 3 | -3 | 1 | -1
+ 2 | 3 | two | 3 | -3 | 2 | 2
+ 2 | 3 | two | 3 | -3 | 3 | -3
+ 2 | 3 | two | 3 | -3 | 2 | 4
+ 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 | 3 | -3 | | 0
+ 3 | 2 | three | 3 | -3 | 1 | -1
+ 3 | 2 | three | 3 | -3 | 2 | 2
+ 3 | 2 | three | 3 | -3 | 3 | -3
+ 3 | 2 | three | 3 | -3 | 2 | 4
+ 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 | 3 | -3 | | 0
+ 4 | 1 | four | 3 | -3 | 1 | -1
+ 4 | 1 | four | 3 | -3 | 2 | 2
+ 4 | 1 | four | 3 | -3 | 3 | -3
+ 4 | 1 | four | 3 | -3 | 2 | 4
+ 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 | 3 | -3 | | 0
+ 5 | 0 | five | 3 | -3 | 1 | -1
+ 5 | 0 | five | 3 | -3 | 2 | 2
+ 5 | 0 | five | 3 | -3 | 3 | -3
+ 5 | 0 | five | 3 | -3 | 2 | 4
+ 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 | 3 | -3 | | 0
+ 6 | 6 | six | 3 | -3 | 1 | -1
+ 6 | 6 | six | 3 | -3 | 2 | 2
+ 6 | 6 | six | 3 | -3 | 3 | -3
+ 6 | 6 | six | 3 | -3 | 2 | 4
+ 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 | 3 | -3 | | 0
+ 7 | 7 | seven | 3 | -3 | 1 | -1
+ 7 | 7 | seven | 3 | -3 | 2 | 2
+ 7 | 7 | seven | 3 | -3 | 3 | -3
+ 7 | 7 | seven | 3 | -3 | 2 | 4
+ 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 | 3 | -3 | | 0
+ 8 | 8 | eight | 3 | -3 | 1 | -1
+ 8 | 8 | eight | 3 | -3 | 2 | 2
+ 8 | 8 | eight | 3 | -3 | 3 | -3
+ 8 | 8 | eight | 3 | -3 | 2 | 4
+ 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 | 3 | -3 | | 0
+ 0 | | zero | 3 | -3 | 1 | -1
+ 0 | | zero | 3 | -3 | 2 | 2
+ 0 | | zero | 3 | -3 | 3 | -3
+ 0 | | zero | 3 | -3 | 2 | 4
+ 0 | | zero | 3 | -3 | 5 | -5
+ 0 | | zero | 3 | -3 | 5 | -5
+ 0 | | zero | 3 | -3 | 0 |
+ 0 | | zero | 3 | -3 | |
+ 0 | | zero | 3 | -3 | | 0
+ | | null | 3 | -3 | 1 | -1
+ | | null | 3 | -3 | 2 | 2
+ | | null | 3 | -3 | 3 | -3
+ | | null | 3 | -3 | 2 | 4
+ | | null | 3 | -3 | 5 | -5
+ | | null | 3 | -3 | 5 | -5
+ | | null | 3 | -3 | 0 |
+ | | null | 3 | -3 | |
+ | | null | 3 | -3 | | 0
+ | 0 | zero | 3 | -3 | 1 | -1
+ | 0 | zero | 3 | -3 | 2 | 2
+ | 0 | zero | 3 | -3 | 3 | -3
+ | 0 | zero | 3 | -3 | 2 | 4
+ | 0 | zero | 3 | -3 | 5 | -5
+ | 0 | zero | 3 | -3 | 5 | -5
+ | 0 | zero | 3 | -3 | 0 |
+ | 0 | zero | 3 | -3 | |
+ | 0 | zero | 3 | -3 | | 0
+ 1 | 4 | one | 2 | 4 | 1 | -1
+ 1 | 4 | one | 2 | 4 | 2 | 2
+ 1 | 4 | one | 2 | 4 | 3 | -3
+ 1 | 4 | one | 2 | 4 | 2 | 4
+ 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 | 2 | 4 | | 0
+ 2 | 3 | two | 2 | 4 | 1 | -1
+ 2 | 3 | two | 2 | 4 | 2 | 2
+ 2 | 3 | two | 2 | 4 | 3 | -3
+ 2 | 3 | two | 2 | 4 | 2 | 4
+ 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 | 2 | 4 | | 0
+ 3 | 2 | three | 2 | 4 | 1 | -1
+ 3 | 2 | three | 2 | 4 | 2 | 2
+ 3 | 2 | three | 2 | 4 | 3 | -3
+ 3 | 2 | three | 2 | 4 | 2 | 4
+ 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 | 2 | 4 | | 0
+ 4 | 1 | four | 2 | 4 | 1 | -1
+ 4 | 1 | four | 2 | 4 | 2 | 2
+ 4 | 1 | four | 2 | 4 | 3 | -3
+ 4 | 1 | four | 2 | 4 | 2 | 4
+ 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 | 2 | 4 | | 0
+ 5 | 0 | five | 2 | 4 | 1 | -1
+ 5 | 0 | five | 2 | 4 | 2 | 2
+ 5 | 0 | five | 2 | 4 | 3 | -3
+ 5 | 0 | five | 2 | 4 | 2 | 4
+ 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 | 2 | 4 | | 0
+ 6 | 6 | six | 2 | 4 | 1 | -1
+ 6 | 6 | six | 2 | 4 | 2 | 2
+ 6 | 6 | six | 2 | 4 | 3 | -3
+ 6 | 6 | six | 2 | 4 | 2 | 4
+ 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 | 2 | 4 | | 0
+ 7 | 7 | seven | 2 | 4 | 1 | -1
+ 7 | 7 | seven | 2 | 4 | 2 | 2
+ 7 | 7 | seven | 2 | 4 | 3 | -3
+ 7 | 7 | seven | 2 | 4 | 2 | 4
+ 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 | 2 | 4 | | 0
+ 8 | 8 | eight | 2 | 4 | 1 | -1
+ 8 | 8 | eight | 2 | 4 | 2 | 2
+ 8 | 8 | eight | 2 | 4 | 3 | -3
+ 8 | 8 | eight | 2 | 4 | 2 | 4
+ 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 | 2 | 4 | | 0
+ 0 | | zero | 2 | 4 | 1 | -1
+ 0 | | zero | 2 | 4 | 2 | 2
+ 0 | | zero | 2 | 4 | 3 | -3
+ 0 | | zero | 2 | 4 | 2 | 4
+ 0 | | zero | 2 | 4 | 5 | -5
+ 0 | | zero | 2 | 4 | 5 | -5
+ 0 | | zero | 2 | 4 | 0 |
+ 0 | | zero | 2 | 4 | |
+ 0 | | zero | 2 | 4 | | 0
+ | | null | 2 | 4 | 1 | -1
+ | | null | 2 | 4 | 2 | 2
+ | | null | 2 | 4 | 3 | -3
+ | | null | 2 | 4 | 2 | 4
+ | | null | 2 | 4 | 5 | -5
+ | | null | 2 | 4 | 5 | -5
+ | | null | 2 | 4 | 0 |
+ | | null | 2 | 4 | |
+ | | null | 2 | 4 | | 0
+ | 0 | zero | 2 | 4 | 1 | -1
+ | 0 | zero | 2 | 4 | 2 | 2
+ | 0 | zero | 2 | 4 | 3 | -3
+ | 0 | zero | 2 | 4 | 2 | 4
+ | 0 | zero | 2 | 4 | 5 | -5
+ | 0 | zero | 2 | 4 | 5 | -5
+ | 0 | zero | 2 | 4 | 0 |
+ | 0 | zero | 2 | 4 | |
+ | 0 | zero | 2 | 4 | | 0
+ 1 | 4 | one | 5 | -5 | 1 | -1
+ 1 | 4 | one | 5 | -5 | 2 | 2
+ 1 | 4 | one | 5 | -5 | 3 | -3
+ 1 | 4 | one | 5 | -5 | 2 | 4
+ 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 | |
+ 1 | 4 | one | 5 | -5 | | 0
+ 2 | 3 | two | 5 | -5 | 1 | -1
+ 2 | 3 | two | 5 | -5 | 2 | 2
+ 2 | 3 | two | 5 | -5 | 3 | -3
+ 2 | 3 | two | 5 | -5 | 2 | 4
+ 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 | |
+ 2 | 3 | two | 5 | -5 | | 0
+ 3 | 2 | three | 5 | -5 | 1 | -1
+ 3 | 2 | three | 5 | -5 | 2 | 2
+ 3 | 2 | three | 5 | -5 | 3 | -3
+ 3 | 2 | three | 5 | -5 | 2 | 4
+ 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 | |
+ 3 | 2 | three | 5 | -5 | | 0
+ 4 | 1 | four | 5 | -5 | 1 | -1
+ 4 | 1 | four | 5 | -5 | 2 | 2
+ 4 | 1 | four | 5 | -5 | 3 | -3
+ 4 | 1 | four | 5 | -5 | 2 | 4
+ 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 | |
+ 4 | 1 | four | 5 | -5 | | 0
+ 5 | 0 | five | 5 | -5 | 1 | -1
+ 5 | 0 | five | 5 | -5 | 2 | 2
+ 5 | 0 | five | 5 | -5 | 3 | -3
+ 5 | 0 | five | 5 | -5 | 2 | 4
+ 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 | |
+ 5 | 0 | five | 5 | -5 | | 0
+ 6 | 6 | six | 5 | -5 | 1 | -1
+ 6 | 6 | six | 5 | -5 | 2 | 2
+ 6 | 6 | six | 5 | -5 | 3 | -3
+ 6 | 6 | six | 5 | -5 | 2 | 4
+ 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 | |
+ 6 | 6 | six | 5 | -5 | | 0
+ 7 | 7 | seven | 5 | -5 | 1 | -1
+ 7 | 7 | seven | 5 | -5 | 2 | 2
+ 7 | 7 | seven | 5 | -5 | 3 | -3
+ 7 | 7 | seven | 5 | -5 | 2 | 4
+ 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 | |
+ 7 | 7 | seven | 5 | -5 | | 0
+ 8 | 8 | eight | 5 | -5 | 1 | -1
+ 8 | 8 | eight | 5 | -5 | 2 | 2
+ 8 | 8 | eight | 5 | -5 | 3 | -3
+ 8 | 8 | eight | 5 | -5 | 2 | 4
+ 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 | |
+ 8 | 8 | eight | 5 | -5 | | 0
+ 0 | | zero | 5 | -5 | 1 | -1
+ 0 | | zero | 5 | -5 | 2 | 2
+ 0 | | zero | 5 | -5 | 3 | -3
+ 0 | | zero | 5 | -5 | 2 | 4
+ 0 | | zero | 5 | -5 | 5 | -5
+ 0 | | zero | 5 | -5 | 5 | -5
+ 0 | | zero | 5 | -5 | 0 |
+ 0 | | zero | 5 | -5 | |
+ 0 | | zero | 5 | -5 | | 0
+ | | null | 5 | -5 | 1 | -1
+ | | null | 5 | -5 | 2 | 2
+ | | null | 5 | -5 | 3 | -3
+ | | null | 5 | -5 | 2 | 4
+ | | null | 5 | -5 | 5 | -5
+ | | null | 5 | -5 | 5 | -5
+ | | null | 5 | -5 | 0 |
+ | | null | 5 | -5 | |
+ | | null | 5 | -5 | | 0
+ | 0 | zero | 5 | -5 | 1 | -1
+ | 0 | zero | 5 | -5 | 2 | 2
+ | 0 | zero | 5 | -5 | 3 | -3
+ | 0 | zero | 5 | -5 | 2 | 4
+ | 0 | zero | 5 | -5 | 5 | -5
+ | 0 | zero | 5 | -5 | 5 | -5
+ | 0 | zero | 5 | -5 | 0 |
+ | 0 | zero | 5 | -5 | |
+ | 0 | zero | 5 | -5 | | 0
+ 1 | 4 | one | 5 | -5 | 1 | -1
+ 1 | 4 | one | 5 | -5 | 2 | 2
+ 1 | 4 | one | 5 | -5 | 3 | -3
+ 1 | 4 | one | 5 | -5 | 2 | 4
+ 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 | |
+ 1 | 4 | one | 5 | -5 | | 0
+ 2 | 3 | two | 5 | -5 | 1 | -1
+ 2 | 3 | two | 5 | -5 | 2 | 2
+ 2 | 3 | two | 5 | -5 | 3 | -3
+ 2 | 3 | two | 5 | -5 | 2 | 4
+ 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 | |
+ 2 | 3 | two | 5 | -5 | | 0
+ 3 | 2 | three | 5 | -5 | 1 | -1
+ 3 | 2 | three | 5 | -5 | 2 | 2
+ 3 | 2 | three | 5 | -5 | 3 | -3
+ 3 | 2 | three | 5 | -5 | 2 | 4
+ 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 | |
+ 3 | 2 | three | 5 | -5 | | 0
+ 4 | 1 | four | 5 | -5 | 1 | -1
+ 4 | 1 | four | 5 | -5 | 2 | 2
+ 4 | 1 | four | 5 | -5 | 3 | -3
+ 4 | 1 | four | 5 | -5 | 2 | 4
+ 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 | |
+ 4 | 1 | four | 5 | -5 | | 0
+ 5 | 0 | five | 5 | -5 | 1 | -1
+ 5 | 0 | five | 5 | -5 | 2 | 2
+ 5 | 0 | five | 5 | -5 | 3 | -3
+ 5 | 0 | five | 5 | -5 | 2 | 4
+ 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 | |
+ 5 | 0 | five | 5 | -5 | | 0
+ 6 | 6 | six | 5 | -5 | 1 | -1
+ 6 | 6 | six | 5 | -5 | 2 | 2
+ 6 | 6 | six | 5 | -5 | 3 | -3
+ 6 | 6 | six | 5 | -5 | 2 | 4
+ 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 | |
+ 6 | 6 | six | 5 | -5 | | 0
+ 7 | 7 | seven | 5 | -5 | 1 | -1
+ 7 | 7 | seven | 5 | -5 | 2 | 2
+ 7 | 7 | seven | 5 | -5 | 3 | -3
+ 7 | 7 | seven | 5 | -5 | 2 | 4
+ 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 | |
+ 7 | 7 | seven | 5 | -5 | | 0
+ 8 | 8 | eight | 5 | -5 | 1 | -1
+ 8 | 8 | eight | 5 | -5 | 2 | 2
+ 8 | 8 | eight | 5 | -5 | 3 | -3
+ 8 | 8 | eight | 5 | -5 | 2 | 4
+ 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 | |
+ 8 | 8 | eight | 5 | -5 | | 0
+ 0 | | zero | 5 | -5 | 1 | -1
+ 0 | | zero | 5 | -5 | 2 | 2
+ 0 | | zero | 5 | -5 | 3 | -3
+ 0 | | zero | 5 | -5 | 2 | 4
+ 0 | | zero | 5 | -5 | 5 | -5
+ 0 | | zero | 5 | -5 | 5 | -5
+ 0 | | zero | 5 | -5 | 0 |
+ 0 | | zero | 5 | -5 | |
+ 0 | | zero | 5 | -5 | | 0
+ | | null | 5 | -5 | 1 | -1
+ | | null | 5 | -5 | 2 | 2
+ | | null | 5 | -5 | 3 | -3
+ | | null | 5 | -5 | 2 | 4
+ | | null | 5 | -5 | 5 | -5
+ | | null | 5 | -5 | 5 | -5
+ | | null | 5 | -5 | 0 |
+ | | null | 5 | -5 | |
+ | | null | 5 | -5 | | 0
+ | 0 | zero | 5 | -5 | 1 | -1
+ | 0 | zero | 5 | -5 | 2 | 2
+ | 0 | zero | 5 | -5 | 3 | -3
+ | 0 | zero | 5 | -5 | 2 | 4
+ | 0 | zero | 5 | -5 | 5 | -5
+ | 0 | zero | 5 | -5 | 5 | -5
+ | 0 | zero | 5 | -5 | 0 |
+ | 0 | zero | 5 | -5 | |
+ | 0 | zero | 5 | -5 | | 0
+ 1 | 4 | one | 0 | | 1 | -1
+ 1 | 4 | one | 0 | | 2 | 2
+ 1 | 4 | one | 0 | | 3 | -3
+ 1 | 4 | one | 0 | | 2 | 4
+ 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 | | | 0
+ 2 | 3 | two | 0 | | 1 | -1
+ 2 | 3 | two | 0 | | 2 | 2
+ 2 | 3 | two | 0 | | 3 | -3
+ 2 | 3 | two | 0 | | 2 | 4
+ 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 | | | 0
+ 3 | 2 | three | 0 | | 1 | -1
+ 3 | 2 | three | 0 | | 2 | 2
+ 3 | 2 | three | 0 | | 3 | -3
+ 3 | 2 | three | 0 | | 2 | 4
+ 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 | | | 0
+ 4 | 1 | four | 0 | | 1 | -1
+ 4 | 1 | four | 0 | | 2 | 2
+ 4 | 1 | four | 0 | | 3 | -3
+ 4 | 1 | four | 0 | | 2 | 4
+ 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 | | | 0
+ 5 | 0 | five | 0 | | 1 | -1
+ 5 | 0 | five | 0 | | 2 | 2
+ 5 | 0 | five | 0 | | 3 | -3
+ 5 | 0 | five | 0 | | 2 | 4
+ 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 | | | 0
+ 6 | 6 | six | 0 | | 1 | -1
+ 6 | 6 | six | 0 | | 2 | 2
+ 6 | 6 | six | 0 | | 3 | -3
+ 6 | 6 | six | 0 | | 2 | 4
+ 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 | | | 0
+ 7 | 7 | seven | 0 | | 1 | -1
+ 7 | 7 | seven | 0 | | 2 | 2
+ 7 | 7 | seven | 0 | | 3 | -3
+ 7 | 7 | seven | 0 | | 2 | 4
+ 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 | | | 0
+ 8 | 8 | eight | 0 | | 1 | -1
+ 8 | 8 | eight | 0 | | 2 | 2
+ 8 | 8 | eight | 0 | | 3 | -3
+ 8 | 8 | eight | 0 | | 2 | 4
+ 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 | | | 0
+ 0 | | zero | 0 | | 1 | -1
+ 0 | | zero | 0 | | 2 | 2
+ 0 | | zero | 0 | | 3 | -3
+ 0 | | zero | 0 | | 2 | 4
+ 0 | | zero | 0 | | 5 | -5
+ 0 | | zero | 0 | | 5 | -5
+ 0 | | zero | 0 | | 0 |
+ 0 | | zero | 0 | | |
+ 0 | | zero | 0 | | | 0
+ | | null | 0 | | 1 | -1
+ | | null | 0 | | 2 | 2
+ | | null | 0 | | 3 | -3
+ | | null | 0 | | 2 | 4
+ | | null | 0 | | 5 | -5
+ | | null | 0 | | 5 | -5
+ | | null | 0 | | 0 |
+ | | null | 0 | | |
+ | | null | 0 | | | 0
+ | 0 | zero | 0 | | 1 | -1
+ | 0 | zero | 0 | | 2 | 2
+ | 0 | zero | 0 | | 3 | -3
+ | 0 | zero | 0 | | 2 | 4
+ | 0 | zero | 0 | | 5 | -5
+ | 0 | zero | 0 | | 5 | -5
+ | 0 | zero | 0 | | 0 |
+ | 0 | zero | 0 | | |
+ | 0 | zero | 0 | | | 0
+ 1 | 4 | one | | | 1 | -1
+ 1 | 4 | one | | | 2 | 2
+ 1 | 4 | one | | | 3 | -3
+ 1 | 4 | one | | | 2 | 4
+ 1 | 4 | one | | | 5 | -5
+ 1 | 4 | one | | | 5 | -5
+ 1 | 4 | one | | | 0 |
+ 1 | 4 | one | | | |
+ 1 | 4 | one | | | | 0
+ 2 | 3 | two | | | 1 | -1
+ 2 | 3 | two | | | 2 | 2
+ 2 | 3 | two | | | 3 | -3
+ 2 | 3 | two | | | 2 | 4
+ 2 | 3 | two | | | 5 | -5
+ 2 | 3 | two | | | 5 | -5
+ 2 | 3 | two | | | 0 |
+ 2 | 3 | two | | | |
+ 2 | 3 | two | | | | 0
+ 3 | 2 | three | | | 1 | -1
+ 3 | 2 | three | | | 2 | 2
+ 3 | 2 | three | | | 3 | -3
+ 3 | 2 | three | | | 2 | 4
+ 3 | 2 | three | | | 5 | -5
+ 3 | 2 | three | | | 5 | -5
+ 3 | 2 | three | | | 0 |
+ 3 | 2 | three | | | |
+ 3 | 2 | three | | | | 0
+ 4 | 1 | four | | | 1 | -1
+ 4 | 1 | four | | | 2 | 2
+ 4 | 1 | four | | | 3 | -3
+ 4 | 1 | four | | | 2 | 4
+ 4 | 1 | four | | | 5 | -5
+ 4 | 1 | four | | | 5 | -5
+ 4 | 1 | four | | | 0 |
+ 4 | 1 | four | | | |
+ 4 | 1 | four | | | | 0
+ 5 | 0 | five | | | 1 | -1
+ 5 | 0 | five | | | 2 | 2
+ 5 | 0 | five | | | 3 | -3
+ 5 | 0 | five | | | 2 | 4
+ 5 | 0 | five | | | 5 | -5
+ 5 | 0 | five | | | 5 | -5
+ 5 | 0 | five | | | 0 |
+ 5 | 0 | five | | | |
+ 5 | 0 | five | | | | 0
+ 6 | 6 | six | | | 1 | -1
+ 6 | 6 | six | | | 2 | 2
+ 6 | 6 | six | | | 3 | -3
+ 6 | 6 | six | | | 2 | 4
+ 6 | 6 | six | | | 5 | -5
+ 6 | 6 | six | | | 5 | -5
+ 6 | 6 | six | | | 0 |
+ 6 | 6 | six | | | |
+ 6 | 6 | six | | | | 0
+ 7 | 7 | seven | | | 1 | -1
+ 7 | 7 | seven | | | 2 | 2
+ 7 | 7 | seven | | | 3 | -3
+ 7 | 7 | seven | | | 2 | 4
+ 7 | 7 | seven | | | 5 | -5
+ 7 | 7 | seven | | | 5 | -5
+ 7 | 7 | seven | | | 0 |
+ 7 | 7 | seven | | | |
+ 7 | 7 | seven | | | | 0
+ 8 | 8 | eight | | | 1 | -1
+ 8 | 8 | eight | | | 2 | 2
+ 8 | 8 | eight | | | 3 | -3
+ 8 | 8 | eight | | | 2 | 4
+ 8 | 8 | eight | | | 5 | -5
+ 8 | 8 | eight | | | 5 | -5
+ 8 | 8 | eight | | | 0 |
+ 8 | 8 | eight | | | |
+ 8 | 8 | eight | | | | 0
+ 0 | | zero | | | 1 | -1
+ 0 | | zero | | | 2 | 2
+ 0 | | zero | | | 3 | -3
+ 0 | | zero | | | 2 | 4
+ 0 | | zero | | | 5 | -5
+ 0 | | zero | | | 5 | -5
+ 0 | | zero | | | 0 |
+ 0 | | zero | | | |
+ 0 | | zero | | | | 0
+ | | null | | | 1 | -1
+ | | null | | | 2 | 2
+ | | null | | | 3 | -3
+ | | null | | | 2 | 4
+ | | null | | | 5 | -5
+ | | null | | | 5 | -5
+ | | null | | | 0 |
+ | | null | | | |
+ | | null | | | | 0
+ | 0 | zero | | | 1 | -1
+ | 0 | zero | | | 2 | 2
+ | 0 | zero | | | 3 | -3
+ | 0 | zero | | | 2 | 4
+ | 0 | zero | | | 5 | -5
+ | 0 | zero | | | 5 | -5
+ | 0 | zero | | | 0 |
+ | 0 | zero | | | |
+ | 0 | zero | | | | 0
+ 1 | 4 | one | | 0 | 1 | -1
+ 1 | 4 | one | | 0 | 2 | 2
+ 1 | 4 | one | | 0 | 3 | -3
+ 1 | 4 | one | | 0 | 2 | 4
+ 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 | | 0
+ 2 | 3 | two | | 0 | 1 | -1
+ 2 | 3 | two | | 0 | 2 | 2
+ 2 | 3 | two | | 0 | 3 | -3
+ 2 | 3 | two | | 0 | 2 | 4
+ 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 | | 0
+ 3 | 2 | three | | 0 | 1 | -1
+ 3 | 2 | three | | 0 | 2 | 2
+ 3 | 2 | three | | 0 | 3 | -3
+ 3 | 2 | three | | 0 | 2 | 4
+ 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 | | 0
+ 4 | 1 | four | | 0 | 1 | -1
+ 4 | 1 | four | | 0 | 2 | 2
+ 4 | 1 | four | | 0 | 3 | -3
+ 4 | 1 | four | | 0 | 2 | 4
+ 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 | | 0
+ 5 | 0 | five | | 0 | 1 | -1
+ 5 | 0 | five | | 0 | 2 | 2
+ 5 | 0 | five | | 0 | 3 | -3
+ 5 | 0 | five | | 0 | 2 | 4
+ 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 | | 0
+ 6 | 6 | six | | 0 | 1 | -1
+ 6 | 6 | six | | 0 | 2 | 2
+ 6 | 6 | six | | 0 | 3 | -3
+ 6 | 6 | six | | 0 | 2 | 4
+ 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 | | 0
+ 7 | 7 | seven | | 0 | 1 | -1
+ 7 | 7 | seven | | 0 | 2 | 2
+ 7 | 7 | seven | | 0 | 3 | -3
+ 7 | 7 | seven | | 0 | 2 | 4
+ 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 | | 0
+ 8 | 8 | eight | | 0 | 1 | -1
+ 8 | 8 | eight | | 0 | 2 | 2
+ 8 | 8 | eight | | 0 | 3 | -3
+ 8 | 8 | eight | | 0 | 2 | 4
+ 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 | | 0
+ 0 | | zero | | 0 | 1 | -1
+ 0 | | zero | | 0 | 2 | 2
+ 0 | | zero | | 0 | 3 | -3
+ 0 | | zero | | 0 | 2 | 4
+ 0 | | zero | | 0 | 5 | -5
+ 0 | | zero | | 0 | 5 | -5
+ 0 | | zero | | 0 | 0 |
+ 0 | | zero | | 0 | |
+ 0 | | zero | | 0 | | 0
+ | | null | | 0 | 1 | -1
+ | | null | | 0 | 2 | 2
+ | | null | | 0 | 3 | -3
+ | | null | | 0 | 2 | 4
+ | | null | | 0 | 5 | -5
+ | | null | | 0 | 5 | -5
+ | | null | | 0 | 0 |
+ | | null | | 0 | |
+ | | null | | 0 | | 0
+ | 0 | zero | | 0 | 1 | -1
+ | 0 | zero | | 0 | 2 | 2
+ | 0 | zero | | 0 | 3 | -3
+ | 0 | zero | | 0 | 2 | 4
+ | 0 | zero | | 0 | 5 | -5
+ | 0 | zero | | 0 | 5 | -5
+ | 0 | zero | | 0 | 0 |
+ | 0 | zero | | 0 | |
+ | 0 | zero | | 0 | | 0
+(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 *
+ FROM J1_TBL INNER JOIN J2_TBL USING (i);
+ 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 *
+ FROM J1_TBL JOIN J2_TBL USING (i);
+ 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 *
+ FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, d) USING (a)
+ ORDER BY a, d;
+ 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 *
+ FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, b) USING (b)
+ ORDER BY b, t1.a;
+ b | a | c | a
+---+---+-------+---
+ 0 | 5 | five |
+ 0 | | zero |
+ 2 | 3 | three | 2
+ 4 | 1 | one | 2
+(4 rows)
+
+-- test join using aliases
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) WHERE J1_TBL.t = 'one'; -- ok
+ i | j | t | k
+---+---+-----+----
+ 1 | 4 | one | -1
+(1 row)
+
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one'; -- ok
+ i | j | t | k
+---+---+-----+----
+ 1 | 4 | one | -1
+(1 row)
+
+SELECT * FROM (J1_TBL JOIN J2_TBL USING (i)) AS x WHERE J1_TBL.t = 'one'; -- error
+ERROR: invalid reference to FROM-clause entry for table "j1_tbl"
+LINE 1: ... * FROM (J1_TBL JOIN J2_TBL USING (i)) AS x WHERE J1_TBL.t =...
+ ^
+HINT: There is an entry for table "j1_tbl", but it cannot be referenced from this part of the query.
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.i = 1; -- ok
+ i | j | t | k
+---+---+-----+----
+ 1 | 4 | one | -1
+(1 row)
+
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.t = 'one'; -- error
+ERROR: column x.t does not exist
+LINE 1: ...CT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.t = 'one...
+ ^
+SELECT * FROM (J1_TBL JOIN J2_TBL USING (i) AS x) AS xx WHERE x.i = 1; -- error (XXX could use better hint)
+ERROR: missing FROM-clause entry for table "x"
+LINE 1: ...ROM (J1_TBL JOIN J2_TBL USING (i) AS x) AS xx WHERE x.i = 1;
+ ^
+SELECT * FROM J1_TBL a1 JOIN J2_TBL a2 USING (i) AS a1; -- error
+ERROR: table name "a1" specified more than once
+SELECT x.* FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one';
+ i
+---
+ 1
+(1 row)
+
+SELECT ROW(x.*) FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one';
+ row
+-----
+ (1)
+(1 row)
+
+SELECT row_to_json(x.*) FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one';
+ row_to_json
+-------------
+ {"i":1}
+(1 row)
+
+--
+-- NATURAL JOIN
+-- Inner equi-join on all columns with the same name
+--
+SELECT *
+ FROM J1_TBL NATURAL JOIN J2_TBL;
+ 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 *
+ FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (a, d);
+ 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 *
+ FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
+ 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 *
+ FROM J1_TBL t1 (a, b) NATURAL JOIN J2_TBL t2 (a);
+ 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 *
+ FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.i);
+ 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 *
+ FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k);
+ i | j | t | i | k
+---+---+------+---+---
+ 0 | | zero | | 0
+ 2 | 3 | two | 2 | 2
+ 4 | 1 | four | 2 | 4
+(3 rows)
+
+--
+-- Non-equi-joins
+--
+SELECT *
+ FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i <= J2_TBL.k);
+ i | j | t | i | k
+---+---+-------+---+---
+ 1 | 4 | one | 2 | 2
+ 2 | 3 | two | 2 | 2
+ 0 | | zero | 2 | 2
+ 1 | 4 | one | 2 | 4
+ 2 | 3 | two | 2 | 4
+ 3 | 2 | three | 2 | 4
+ 4 | 1 | four | 2 | 4
+ 0 | | zero | 2 | 4
+ 0 | | zero | | 0
+(9 rows)
+
+--
+-- Outer joins
+-- Note that OUTER is a noise word
+--
+SELECT *
+ FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i)
+ ORDER BY i, k, t;
+ 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 *
+ FROM J1_TBL LEFT JOIN J2_TBL USING (i)
+ ORDER BY i, k, t;
+ 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 *
+ FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i);
+ 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 *
+ FROM J1_TBL RIGHT JOIN J2_TBL USING (i);
+ 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 *
+ FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i)
+ ORDER BY i, k, t;
+ 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 *
+ FROM J1_TBL FULL JOIN J2_TBL USING (i)
+ ORDER BY i, k, t;
+ 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 *
+ FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (k = 1);
+ i | j | t | k
+---+---+---+---
+(0 rows)
+
+SELECT *
+ FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (i = 1);
+ 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;
+ QUERY PLAN
+----------------------------------------------
+ Hash Semi Join
+ Hash Cond: (a.twothousand = b.twothousand)
+ Join Filter: (a.fivethous <> b.fivethous)
+ -> Hash Join
+ Hash Cond: (a.tenthous = i4.f1)
+ -> Seq Scan on tenk1 a
+ -> Hash
+ -> Seq Scan on int4_tbl i4
+ -> Hash
+ -> Seq Scan on tenk1 b
+(10 rows)
+
+--
+-- 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);
+ 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);
+ 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);
+ 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);
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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)
+
+-- Constants as join keys can also be problematic
+SELECT * FROM
+ (SELECT name, n as s1_n FROM t1) as s1
+FULL JOIN
+ (SELECT name, 2 as s2_n FROM t2) as s2
+ON (s1_n = s2_n);
+ name | s1_n | name | s2_n
+------+------+------+------
+ | | bb | 2
+ | | cc | 2
+ | | ee | 2
+ bb | 11 | |
+(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;
+ x1 | x2
+----+----
+ 1 | 11
+ 2 | 22
+ 3 |
+ 4 | 44
+ 5 |
+(5 rows)
+
+select * from y;
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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;
+--
+-- regression test: be sure we cope with proven-dummy append rels
+--
+explain (costs off)
+select aa, bb, unique1, unique1
+ from tenk1 right join b on aa = unique1
+ where bb < bb and bb is null;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+select aa, bb, unique1, unique1
+ from tenk1 right join b on aa = unique1
+ where bb < bb and bb is null;
+ aa | bb | unique1 | unique1
+----+----+---------+---------
+(0 rows)
+
+--
+-- regression test: check handling of empty-FROM subquery underneath outer join
+--
+explain (costs off)
+select * from int8_tbl i1 left join (int8_tbl i2 join
+ (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2
+order by 1, 2;
+ QUERY PLAN
+-------------------------------------------
+ Sort
+ Sort Key: i1.q1, i1.q2
+ -> Hash Left Join
+ Hash Cond: (i1.q2 = i2.q2)
+ -> Seq Scan on int8_tbl i1
+ -> Hash
+ -> Seq Scan on int8_tbl i2
+ Filter: (q1 = 123)
+(8 rows)
+
+select * from int8_tbl i1 left join (int8_tbl i2 join
+ (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2
+order by 1, 2;
+ q1 | q2 | q1 | q2 | x
+------------------+-------------------+-----+------------------+-----
+ 123 | 456 | 123 | 456 | 123
+ 123 | 4567890123456789 | 123 | 4567890123456789 | 123
+ 4567890123456789 | -4567890123456789 | | |
+ 4567890123456789 | 123 | | |
+ 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 123
+(5 rows)
+
+--
+-- regression test: check a case where join_clause_is_movable_into() gives
+-- an imprecise result, causing an assertion failure
+--
+select count(*)
+from
+ (select t3.tenthous as x1, coalesce(t1.stringu1, t2.stringu1) as x2
+ from tenk1 t1
+ left join tenk1 t2 on t1.unique1 = t2.unique1
+ join tenk1 t3 on t1.unique2 = t3.unique2) ss,
+ tenk1 t4,
+ tenk1 t5
+where t4.thousand = t5.unique1 and ss.x1 = t4.tenthous and ss.x2 = t5.stringu1;
+ count
+-------
+ 1000
+(1 row)
+
+--
+-- regression test: check a case where we formerly missed including an EC
+-- enforcement clause because it was expected to be handled at scan level
+--
+explain (costs off)
+select a.f1, b.f1, t.thousand, t.tenthous from
+ tenk1 t,
+ (select sum(f1)+1 as f1 from int4_tbl i4a) a,
+ (select sum(f1) as f1 from int4_tbl i4b) b
+where b.f1 = t.thousand and a.f1 = b.f1 and (a.f1+b.f1+999) = t.tenthous;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ -> Aggregate
+ -> Seq Scan on int4_tbl i4b
+ -> Nested Loop
+ Join Filter: ((sum(i4b.f1)) = ((sum(i4a.f1) + 1)))
+ -> Aggregate
+ -> Seq Scan on int4_tbl i4a
+ -> Index Only Scan using tenk1_thous_tenthous on tenk1 t
+ Index Cond: ((thousand = (sum(i4b.f1))) AND (tenthous = ((((sum(i4a.f1) + 1)) + (sum(i4b.f1))) + 999)))
+(9 rows)
+
+select a.f1, b.f1, t.thousand, t.tenthous from
+ tenk1 t,
+ (select sum(f1)+1 as f1 from int4_tbl i4a) a,
+ (select sum(f1) as f1 from int4_tbl i4b) b
+where b.f1 = t.thousand and a.f1 = b.f1 and (a.f1+b.f1+999) = t.tenthous;
+ f1 | f1 | thousand | tenthous
+----+----+----------+----------
+(0 rows)
+
+--
+-- check a case where we formerly got confused by conflicting sort orders
+-- in redundant merge join path keys
+--
+explain (costs off)
+select * from
+ j1_tbl full join
+ (select * from j2_tbl order by j2_tbl.i desc, j2_tbl.k asc) j2_tbl
+ on j1_tbl.i = j2_tbl.i and j1_tbl.i = j2_tbl.k;
+ QUERY PLAN
+-----------------------------------------------------------------
+ Merge Full Join
+ Merge Cond: ((j2_tbl.i = j1_tbl.i) AND (j2_tbl.k = j1_tbl.i))
+ -> Sort
+ Sort Key: j2_tbl.i DESC, j2_tbl.k
+ -> Seq Scan on j2_tbl
+ -> Sort
+ Sort Key: j1_tbl.i DESC
+ -> Seq Scan on j1_tbl
+(8 rows)
+
+select * from
+ j1_tbl full join
+ (select * from j2_tbl order by j2_tbl.i desc, j2_tbl.k asc) j2_tbl
+ on j1_tbl.i = j2_tbl.i and j1_tbl.i = j2_tbl.k;
+ i | j | t | i | k
+---+---+-------+---+----
+ | | | | 0
+ | | | |
+ | 0 | zero | |
+ | | null | |
+ 8 | 8 | eight | |
+ 7 | 7 | seven | |
+ 6 | 6 | six | |
+ | | | 5 | -5
+ | | | 5 | -5
+ 5 | 0 | five | |
+ 4 | 1 | four | |
+ | | | 3 | -3
+ 3 | 2 | three | |
+ 2 | 3 | two | 2 | 2
+ | | | 2 | 4
+ | | | 1 | -1
+ | | | 0 |
+ 1 | 4 | one | |
+ 0 | | zero | |
+(19 rows)
+
+--
+-- a different check for handling of redundant sort keys in merge joins
+--
+explain (costs off)
+select count(*) from
+ (select * from tenk1 x order by x.thousand, x.twothousand, x.fivethous) x
+ left join
+ (select * from tenk1 y order by y.unique2) y
+ on x.thousand = y.unique2 and x.twothousand = y.hundred and x.fivethous = y.unique2;
+ QUERY PLAN
+----------------------------------------------------------------------------------
+ Aggregate
+ -> Merge Left Join
+ Merge Cond: (x.thousand = y.unique2)
+ Join Filter: ((x.twothousand = y.hundred) AND (x.fivethous = y.unique2))
+ -> Sort
+ Sort Key: x.thousand, x.twothousand, x.fivethous
+ -> Seq Scan on tenk1 x
+ -> Materialize
+ -> Index Scan using tenk1_unique2 on tenk1 y
+(9 rows)
+
+select count(*) from
+ (select * from tenk1 x order by x.thousand, x.twothousand, x.fivethous) x
+ left join
+ (select * from tenk1 y order by y.unique2) y
+ on x.thousand = y.unique2 and x.twothousand = y.hundred and x.fivethous = y.unique2;
+ count
+-------
+ 10000
+(1 row)
+
+--
+-- Clean up
+--
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
+DROP TABLE J1_TBL;
+DROP TABLE J2_TBL;
+-- Both DELETE and UPDATE allow the specification of additional tables
+-- to "join" against to determine which rows should be modified.
+CREATE TEMP TABLE t1 (a int, b int);
+CREATE TEMP TABLE t2 (a int, b int);
+CREATE TEMP TABLE t3 (x int, y int);
+INSERT INTO t1 VALUES (5, 10);
+INSERT INTO t1 VALUES (15, 20);
+INSERT INTO t1 VALUES (100, 100);
+INSERT INTO t1 VALUES (200, 1000);
+INSERT INTO t2 VALUES (200, 2000);
+INSERT INTO t3 VALUES (5, 20);
+INSERT INTO t3 VALUES (6, 7);
+INSERT INTO t3 VALUES (7, 8);
+INSERT INTO t3 VALUES (500, 100);
+DELETE FROM t3 USING t1 table1 WHERE t3.x = table1.a;
+SELECT * FROM t3;
+ x | y
+-----+-----
+ 6 | 7
+ 7 | 8
+ 500 | 100
+(3 rows)
+
+DELETE FROM t3 USING t1 JOIN t2 USING (a) WHERE t3.x > t1.a;
+SELECT * FROM t3;
+ x | y
+---+---
+ 6 | 7
+ 7 | 8
+(2 rows)
+
+DELETE FROM t3 USING t3 t3_other WHERE t3.x = t3_other.x AND t3.y = t3_other.y;
+SELECT * FROM t3;
+ x | y
+---+---
+(0 rows)
+
+-- Test join against inheritance tree
+create temp table t2a () inherits (t2);
+insert into t2a values (200, 2001);
+select * from t1 left join t2 on (t1.a = t2.a);
+ a | b | a | b
+-----+------+-----+------
+ 5 | 10 | |
+ 15 | 20 | |
+ 100 | 100 | |
+ 200 | 1000 | 200 | 2000
+ 200 | 1000 | 200 | 2001
+(5 rows)
+
+-- Test matching of column name with wrong alias
+select t1.x from t1 join t3 on (t1.a = t3.x);
+ERROR: column t1.x does not exist
+LINE 1: select t1.x from t1 join t3 on (t1.a = t3.x);
+ ^
+HINT: Perhaps you meant to reference the column "t3.x".
+-- Test matching of locking clause with wrong alias
+select t1.*, t2.*, unnamed_join.* from
+ t1 join t2 on (t1.a = t2.a), t3 as unnamed_join
+ for update of unnamed_join;
+ a | b | a | b | x | y
+---+---+---+---+---+---
+(0 rows)
+
+select foo.*, unnamed_join.* from
+ t1 join t2 using (a) as foo, t3 as unnamed_join
+ for update of unnamed_join;
+ a | x | y
+---+---+---
+(0 rows)
+
+select foo.*, unnamed_join.* from
+ t1 join t2 using (a) as foo, t3 as unnamed_join
+ for update of foo;
+ERROR: FOR UPDATE cannot be applied to a join
+LINE 3: for update of foo;
+ ^
+select bar.*, unnamed_join.* from
+ (t1 join t2 using (a) as foo) as bar, t3 as unnamed_join
+ for update of foo;
+ERROR: relation "foo" in FOR UPDATE clause not found in FROM clause
+LINE 3: for update of foo;
+ ^
+select bar.*, unnamed_join.* from
+ (t1 join t2 using (a) as foo) as bar, t3 as unnamed_join
+ for update of bar;
+ERROR: FOR UPDATE cannot be applied to a join
+LINE 3: for update of bar;
+ ^
+--
+-- regression test for 8.1 merge right join bug
+--
+CREATE TEMP TABLE tt1 ( tt1_id int4, joincol int4 );
+INSERT INTO tt1 VALUES (1, 11);
+INSERT INTO tt1 VALUES (2, NULL);
+CREATE TEMP TABLE tt2 ( tt2_id int4, joincol int4 );
+INSERT INTO tt2 VALUES (21, 11);
+INSERT INTO tt2 VALUES (22, 11);
+set enable_hashjoin to off;
+set enable_nestloop to off;
+-- these should give the same results
+select tt1.*, tt2.* from tt1 left join tt2 on tt1.joincol = tt2.joincol;
+ tt1_id | joincol | tt2_id | joincol
+--------+---------+--------+---------
+ 1 | 11 | 21 | 11
+ 1 | 11 | 22 | 11
+ 2 | | |
+(3 rows)
+
+select tt1.*, tt2.* from tt2 right join tt1 on tt1.joincol = tt2.joincol;
+ tt1_id | joincol | tt2_id | joincol
+--------+---------+--------+---------
+ 1 | 11 | 21 | 11
+ 1 | 11 | 22 | 11
+ 2 | | |
+(3 rows)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+--
+-- regression test for bug #13908 (hash join with skew tuples & nbatch increase)
+--
+set work_mem to '64kB';
+set enable_mergejoin to off;
+set enable_memoize to off;
+explain (costs off)
+select count(*) from tenk1 a, tenk1 b
+ where a.hundred = b.thousand and (b.fivethous % 10) < 10;
+ QUERY PLAN
+------------------------------------------------------------
+ Aggregate
+ -> Hash Join
+ Hash Cond: (a.hundred = b.thousand)
+ -> Index Only Scan using tenk1_hundred on tenk1 a
+ -> Hash
+ -> Seq Scan on tenk1 b
+ Filter: ((fivethous % 10) < 10)
+(7 rows)
+
+select count(*) from tenk1 a, tenk1 b
+ where a.hundred = b.thousand and (b.fivethous % 10) < 10;
+ count
+--------
+ 100000
+(1 row)
+
+reset work_mem;
+reset enable_mergejoin;
+reset enable_memoize;
+--
+-- regression test for 8.2 bug with improper re-ordering of left joins
+--
+create temp table tt3(f1 int, f2 text);
+insert into tt3 select x, repeat('xyzzy', 100) from generate_series(1,10000) x;
+create index tt3i on tt3(f1);
+analyze tt3;
+create temp table tt4(f1 int);
+insert into tt4 values (0),(1),(9999);
+analyze tt4;
+SELECT a.f1
+FROM tt4 a
+LEFT JOIN (
+ SELECT b.f1
+ FROM tt3 b LEFT JOIN tt3 c ON (b.f1 = c.f1)
+ WHERE c.f1 IS NULL
+) AS d ON (a.f1 = d.f1)
+WHERE d.f1 IS NULL;
+ f1
+------
+ 0
+ 1
+ 9999
+(3 rows)
+
+--
+-- regression test for proper handling of outer joins within antijoins
+--
+create temp table tt4x(c1 int, c2 int, c3 int);
+explain (costs off)
+select * from tt4x t1
+where not exists (
+ select 1 from tt4x t2
+ left join tt4x t3 on t2.c3 = t3.c1
+ left join ( select t5.c1 as c1
+ from tt4x t4 left join tt4x t5 on t4.c2 = t5.c1
+ ) a1 on t3.c2 = a1.c1
+ where t1.c1 = t2.c2
+);
+ QUERY PLAN
+---------------------------------------------------------
+ Hash Anti Join
+ Hash Cond: (t1.c1 = t2.c2)
+ -> Seq Scan on tt4x t1
+ -> Hash
+ -> Merge Right Join
+ Merge Cond: (t5.c1 = t3.c2)
+ -> Merge Join
+ Merge Cond: (t4.c2 = t5.c1)
+ -> Sort
+ Sort Key: t4.c2
+ -> Seq Scan on tt4x t4
+ -> Sort
+ Sort Key: t5.c1
+ -> Seq Scan on tt4x t5
+ -> Sort
+ Sort Key: t3.c2
+ -> Merge Left Join
+ Merge Cond: (t2.c3 = t3.c1)
+ -> Sort
+ Sort Key: t2.c3
+ -> Seq Scan on tt4x t2
+ -> Sort
+ Sort Key: t3.c1
+ -> Seq Scan on tt4x t3
+(24 rows)
+
+--
+-- regression test for problems of the sort depicted in bug #3494
+--
+create temp table tt5(f1 int, f2 int);
+create temp table tt6(f1 int, f2 int);
+insert into tt5 values(1, 10);
+insert into tt5 values(1, 11);
+insert into tt6 values(1, 9);
+insert into tt6 values(1, 2);
+insert into tt6 values(2, 9);
+select * from tt5,tt6 where tt5.f1 = tt6.f1 and tt5.f1 = tt5.f2 - tt6.f2;
+ f1 | f2 | f1 | f2
+----+----+----+----
+ 1 | 10 | 1 | 9
+(1 row)
+
+--
+-- regression test for problems of the sort depicted in bug #3588
+--
+create temp table xx (pkxx int);
+create temp table yy (pkyy int, pkxx int);
+insert into xx values (1);
+insert into xx values (2);
+insert into xx values (3);
+insert into yy values (101, 1);
+insert into yy values (201, 2);
+insert into yy values (301, NULL);
+select yy.pkyy as yy_pkyy, yy.pkxx as yy_pkxx, yya.pkyy as yya_pkyy,
+ xxa.pkxx as xxa_pkxx, xxb.pkxx as xxb_pkxx
+from yy
+ left join (SELECT * FROM yy where pkyy = 101) as yya ON yy.pkyy = yya.pkyy
+ left join xx xxa on yya.pkxx = xxa.pkxx
+ left join xx xxb on coalesce (xxa.pkxx, 1) = xxb.pkxx;
+ yy_pkyy | yy_pkxx | yya_pkyy | xxa_pkxx | xxb_pkxx
+---------+---------+----------+----------+----------
+ 101 | 1 | 101 | 1 | 1
+ 201 | 2 | | | 1
+ 301 | | | | 1
+(3 rows)
+
+--
+-- regression test for improper pushing of constants across outer-join clauses
+-- (as seen in early 8.2.x releases)
+--
+create temp table zt1 (f1 int primary key);
+create temp table zt2 (f2 int primary key);
+create temp table zt3 (f3 int primary key);
+insert into zt1 values(53);
+insert into zt2 values(53);
+select * from
+ zt2 left join zt3 on (f2 = f3)
+ left join zt1 on (f3 = f1)
+where f2 = 53;
+ f2 | f3 | f1
+----+----+----
+ 53 | |
+(1 row)
+
+create temp view zv1 as select *,'dummy'::text AS junk from zt1;
+select * from
+ zt2 left join zt3 on (f2 = f3)
+ left join zv1 on (f3 = f1)
+where f2 = 53;
+ f2 | f3 | f1 | junk
+----+----+----+------
+ 53 | | |
+(1 row)
+
+--
+-- regression test for improper extraction of OR indexqual conditions
+-- (as seen in early 8.3.x releases)
+--
+select a.unique2, a.ten, b.tenthous, b.unique2, b.hundred
+from tenk1 a left join tenk1 b on a.unique2 = b.tenthous
+where a.unique1 = 42 and
+ ((b.unique2 is null and a.ten = 2) or b.hundred = 3);
+ unique2 | ten | tenthous | unique2 | hundred
+---------+-----+----------+---------+---------
+(0 rows)
+
+--
+-- test proper positioning of one-time quals in EXISTS (8.4devel bug)
+--
+prepare foo(bool) as
+ select count(*) from tenk1 a left join tenk1 b
+ on (a.unique2 = b.unique1 and exists
+ (select 1 from tenk1 c where c.thousand = b.unique2 and $1));
+execute foo(true);
+ count
+-------
+ 10000
+(1 row)
+
+execute foo(false);
+ count
+-------
+ 10000
+(1 row)
+
+--
+-- test for sane behavior with noncanonical merge clauses, per bug #4926
+--
+begin;
+set enable_mergejoin = 1;
+set enable_hashjoin = 0;
+set enable_nestloop = 0;
+create temp table a (i integer);
+create temp table b (x integer, y integer);
+select * from a left join b on i = x and i = y and x = i;
+ i | x | y
+---+---+---
+(0 rows)
+
+rollback;
+--
+-- test handling of merge clauses using record_ops
+--
+begin;
+create type mycomptype as (id int, v bigint);
+create temp table tidv (idv mycomptype);
+create index on tidv (idv);
+explain (costs off)
+select a.idv, b.idv from tidv a, tidv b where a.idv = b.idv;
+ QUERY PLAN
+----------------------------------------------------------
+ Merge Join
+ Merge Cond: (a.idv = b.idv)
+ -> Index Only Scan using tidv_idv_idx on tidv a
+ -> Materialize
+ -> Index Only Scan using tidv_idv_idx on tidv b
+(5 rows)
+
+set enable_mergejoin = 0;
+set enable_hashjoin = 0;
+explain (costs off)
+select a.idv, b.idv from tidv a, tidv b where a.idv = b.idv;
+ QUERY PLAN
+----------------------------------------------------
+ Nested Loop
+ -> Seq Scan on tidv a
+ -> Index Only Scan using tidv_idv_idx on tidv b
+ Index Cond: (idv = a.idv)
+(4 rows)
+
+rollback;
+--
+-- test NULL behavior of whole-row Vars, per bug #5025
+--
+select t1.q2, count(t2.*)
+from int8_tbl t1 left join int8_tbl t2 on (t1.q2 = t2.q1)
+group by t1.q2 order by 1;
+ q2 | count
+-------------------+-------
+ -4567890123456789 | 0
+ 123 | 2
+ 456 | 0
+ 4567890123456789 | 6
+(4 rows)
+
+select t1.q2, count(t2.*)
+from int8_tbl t1 left join (select * from int8_tbl) t2 on (t1.q2 = t2.q1)
+group by t1.q2 order by 1;
+ q2 | count
+-------------------+-------
+ -4567890123456789 | 0
+ 123 | 2
+ 456 | 0
+ 4567890123456789 | 6
+(4 rows)
+
+select t1.q2, count(t2.*)
+from int8_tbl t1 left join (select * from int8_tbl offset 0) t2 on (t1.q2 = t2.q1)
+group by t1.q2 order by 1;
+ q2 | count
+-------------------+-------
+ -4567890123456789 | 0
+ 123 | 2
+ 456 | 0
+ 4567890123456789 | 6
+(4 rows)
+
+select t1.q2, count(t2.*)
+from int8_tbl t1 left join
+ (select q1, case when q2=1 then 1 else q2 end as q2 from int8_tbl) t2
+ on (t1.q2 = t2.q1)
+group by t1.q2 order by 1;
+ q2 | count
+-------------------+-------
+ -4567890123456789 | 0
+ 123 | 2
+ 456 | 0
+ 4567890123456789 | 6
+(4 rows)
+
+--
+-- test incorrect failure to NULL pulled-up subexpressions
+--
+begin;
+create temp table a (
+ code char not null,
+ constraint a_pk primary key (code)
+);
+create temp table b (
+ a char not null,
+ num integer not null,
+ constraint b_pk primary key (a, num)
+);
+create temp table c (
+ name char not null,
+ a char,
+ constraint c_pk primary key (name)
+);
+insert into a (code) values ('p');
+insert into a (code) values ('q');
+insert into b (a, num) values ('p', 1);
+insert into b (a, num) values ('p', 2);
+insert into c (name, a) values ('A', 'p');
+insert into c (name, a) values ('B', 'q');
+insert into c (name, a) values ('C', null);
+select c.name, ss.code, ss.b_cnt, ss.const
+from c left join
+ (select a.code, coalesce(b_grp.cnt, 0) as b_cnt, -1 as const
+ from a left join
+ (select count(1) as cnt, b.a from b group by b.a) as b_grp
+ on a.code = b_grp.a
+ ) as ss
+ on (c.a = ss.code)
+order by c.name;
+ name | code | b_cnt | const
+------+------+-------+-------
+ A | p | 2 | -1
+ B | q | 0 | -1
+ C | | |
+(3 rows)
+
+rollback;
+--
+-- test incorrect handling of placeholders that only appear in targetlists,
+-- per bug #6154
+--
+SELECT * FROM
+( SELECT 1 as key1 ) sub1
+LEFT JOIN
+( SELECT sub3.key3, sub4.value2, COALESCE(sub4.value2, 66) as value3 FROM
+ ( SELECT 1 as key3 ) sub3
+ LEFT JOIN
+ ( SELECT sub5.key5, COALESCE(sub6.value1, 1) as value2 FROM
+ ( SELECT 1 as key5 ) sub5
+ LEFT JOIN
+ ( SELECT 2 as key6, 42 as value1 ) sub6
+ ON sub5.key5 = sub6.key6
+ ) sub4
+ ON sub4.key5 = sub3.key3
+) sub2
+ON sub1.key1 = sub2.key3;
+ key1 | key3 | value2 | value3
+------+------+--------+--------
+ 1 | 1 | 1 | 1
+(1 row)
+
+-- test the path using join aliases, too
+SELECT * FROM
+( SELECT 1 as key1 ) sub1
+LEFT JOIN
+( SELECT sub3.key3, value2, COALESCE(value2, 66) as value3 FROM
+ ( SELECT 1 as key3 ) sub3
+ LEFT JOIN
+ ( SELECT sub5.key5, COALESCE(sub6.value1, 1) as value2 FROM
+ ( SELECT 1 as key5 ) sub5
+ LEFT JOIN
+ ( SELECT 2 as key6, 42 as value1 ) sub6
+ ON sub5.key5 = sub6.key6
+ ) sub4
+ ON sub4.key5 = sub3.key3
+) sub2
+ON sub1.key1 = sub2.key3;
+ key1 | key3 | value2 | value3
+------+------+--------+--------
+ 1 | 1 | 1 | 1
+(1 row)
+
+--
+-- test case where a PlaceHolderVar is used as a nestloop parameter
+--
+EXPLAIN (COSTS OFF)
+SELECT qq, unique1
+ FROM
+ ( SELECT COALESCE(q1, 0) AS qq FROM int8_tbl a ) AS ss1
+ FULL OUTER JOIN
+ ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2
+ USING (qq)
+ INNER JOIN tenk1 c ON qq = unique2;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------
+ Nested Loop
+ -> Hash Full Join
+ Hash Cond: ((COALESCE(a.q1, '0'::bigint)) = (COALESCE(b.q2, '-1'::bigint)))
+ -> Seq Scan on int8_tbl a
+ -> Hash
+ -> Seq Scan on int8_tbl b
+ -> Index Scan using tenk1_unique2 on tenk1 c
+ Index Cond: (unique2 = COALESCE((COALESCE(a.q1, '0'::bigint)), (COALESCE(b.q2, '-1'::bigint))))
+(8 rows)
+
+SELECT qq, unique1
+ FROM
+ ( SELECT COALESCE(q1, 0) AS qq FROM int8_tbl a ) AS ss1
+ FULL OUTER JOIN
+ ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2
+ USING (qq)
+ INNER JOIN tenk1 c ON qq = unique2;
+ qq | unique1
+-----+---------
+ 123 | 4596
+ 123 | 4596
+ 456 | 7318
+(3 rows)
+
+--
+-- nested nestloops can require nested PlaceHolderVars
+--
+create temp table nt1 (
+ id int primary key,
+ a1 boolean,
+ a2 boolean
+);
+create temp table nt2 (
+ id int primary key,
+ nt1_id int,
+ b1 boolean,
+ b2 boolean,
+ foreign key (nt1_id) references nt1(id)
+);
+create temp table nt3 (
+ id int primary key,
+ nt2_id int,
+ c1 boolean,
+ foreign key (nt2_id) references nt2(id)
+);
+insert into nt1 values (1,true,true);
+insert into nt1 values (2,true,false);
+insert into nt1 values (3,false,false);
+insert into nt2 values (1,1,true,true);
+insert into nt2 values (2,2,true,false);
+insert into nt2 values (3,3,false,false);
+insert into nt3 values (1,1,true);
+insert into nt3 values (2,2,false);
+insert into nt3 values (3,3,true);
+explain (costs off)
+select nt3.id
+from nt3 as nt3
+ left join
+ (select nt2.*, (nt2.b1 and ss1.a3) AS b3
+ from nt2 as nt2
+ left join
+ (select nt1.*, (nt1.id is not null) as a3 from nt1) as ss1
+ on ss1.id = nt2.nt1_id
+ ) as ss2
+ on ss2.id = nt3.nt2_id
+where nt3.id = 1 and ss2.b3;
+ QUERY PLAN
+-----------------------------------------------
+ Nested Loop
+ -> Nested Loop
+ -> Index Scan using nt3_pkey on nt3
+ Index Cond: (id = 1)
+ -> Index Scan using nt2_pkey on nt2
+ Index Cond: (id = nt3.nt2_id)
+ -> Index Only Scan using nt1_pkey on nt1
+ Index Cond: (id = nt2.nt1_id)
+ Filter: (nt2.b1 AND (id IS NOT NULL))
+(9 rows)
+
+select nt3.id
+from nt3 as nt3
+ left join
+ (select nt2.*, (nt2.b1 and ss1.a3) AS b3
+ from nt2 as nt2
+ left join
+ (select nt1.*, (nt1.id is not null) as a3 from nt1) as ss1
+ on ss1.id = nt2.nt1_id
+ ) as ss2
+ on ss2.id = nt3.nt2_id
+where nt3.id = 1 and ss2.b3;
+ id
+----
+ 1
+(1 row)
+
+--
+-- test case where a PlaceHolderVar is propagated into a subquery
+--
+explain (costs off)
+select * from
+ int8_tbl t1 left join
+ (select q1 as x, 42 as y from int8_tbl t2) ss
+ on t1.q2 = ss.x
+where
+ 1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1)
+order by 1,2;
+ QUERY PLAN
+-----------------------------------------------------------
+ Sort
+ Sort Key: t1.q1, t1.q2
+ -> Hash Left Join
+ Hash Cond: (t1.q2 = t2.q1)
+ Filter: (1 = (SubPlan 1))
+ -> Seq Scan on int8_tbl t1
+ -> Hash
+ -> Seq Scan on int8_tbl t2
+ SubPlan 1
+ -> Limit
+ -> Result
+ One-Time Filter: ((42) IS NOT NULL)
+ -> Seq Scan on int8_tbl t3
+(13 rows)
+
+select * from
+ int8_tbl t1 left join
+ (select q1 as x, 42 as y from int8_tbl t2) ss
+ on t1.q2 = ss.x
+where
+ 1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1)
+order by 1,2;
+ q1 | q2 | x | y
+------------------+------------------+------------------+----
+ 123 | 4567890123456789 | 4567890123456789 | 42
+ 123 | 4567890123456789 | 4567890123456789 | 42
+ 123 | 4567890123456789 | 4567890123456789 | 42
+ 4567890123456789 | 123 | 123 | 42
+ 4567890123456789 | 123 | 123 | 42
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 42
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 42
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 42
+(8 rows)
+
+--
+-- variant where a PlaceHolderVar is needed at a join, but not above the join
+--
+explain (costs off)
+select * from
+ int4_tbl as i41,
+ lateral
+ (select 1 as x from
+ (select i41.f1 as lat,
+ i42.f1 as loc from
+ int8_tbl as i81, int4_tbl as i42) as ss1
+ right join int4_tbl as i43 on (i43.f1 > 1)
+ where ss1.loc = ss1.lat) as ss2
+where i41.f1 > 0;
+ QUERY PLAN
+--------------------------------------------------
+ Nested Loop
+ -> Nested Loop
+ -> Seq Scan on int4_tbl i41
+ Filter: (f1 > 0)
+ -> Nested Loop
+ Join Filter: (i41.f1 = i42.f1)
+ -> Seq Scan on int8_tbl i81
+ -> Materialize
+ -> Seq Scan on int4_tbl i42
+ -> Materialize
+ -> Seq Scan on int4_tbl i43
+ Filter: (f1 > 1)
+(12 rows)
+
+select * from
+ int4_tbl as i41,
+ lateral
+ (select 1 as x from
+ (select i41.f1 as lat,
+ i42.f1 as loc from
+ int8_tbl as i81, int4_tbl as i42) as ss1
+ right join int4_tbl as i43 on (i43.f1 > 1)
+ where ss1.loc = ss1.lat) as ss2
+where i41.f1 > 0;
+ f1 | x
+------------+---
+ 123456 | 1
+ 123456 | 1
+ 123456 | 1
+ 123456 | 1
+ 123456 | 1
+ 123456 | 1
+ 123456 | 1
+ 123456 | 1
+ 123456 | 1
+ 123456 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+(20 rows)
+
+--
+-- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE
+--
+select * from int4_tbl a full join int4_tbl b on true;
+ f1 | f1
+-------------+-------------
+ 0 | 0
+ 0 | 123456
+ 0 | -123456
+ 0 | 2147483647
+ 0 | -2147483647
+ 123456 | 0
+ 123456 | 123456
+ 123456 | -123456
+ 123456 | 2147483647
+ 123456 | -2147483647
+ -123456 | 0
+ -123456 | 123456
+ -123456 | -123456
+ -123456 | 2147483647
+ -123456 | -2147483647
+ 2147483647 | 0
+ 2147483647 | 123456
+ 2147483647 | -123456
+ 2147483647 | 2147483647
+ 2147483647 | -2147483647
+ -2147483647 | 0
+ -2147483647 | 123456
+ -2147483647 | -123456
+ -2147483647 | 2147483647
+ -2147483647 | -2147483647
+(25 rows)
+
+select * from int4_tbl a full join int4_tbl b on false;
+ f1 | f1
+-------------+-------------
+ | 0
+ | 123456
+ | -123456
+ | 2147483647
+ | -2147483647
+ 0 |
+ 123456 |
+ -123456 |
+ 2147483647 |
+ -2147483647 |
+(10 rows)
+
+--
+-- test for ability to use a cartesian join when necessary
+--
+create temp table q1 as select 1 as q1;
+create temp table q2 as select 0 as q2;
+analyze q1;
+analyze q2;
+explain (costs off)
+select * from
+ tenk1 join int4_tbl on f1 = twothousand,
+ q1, q2
+where q1 = thousand or q2 = thousand;
+ QUERY PLAN
+------------------------------------------------------------------------
+ Hash Join
+ Hash Cond: (tenk1.twothousand = int4_tbl.f1)
+ -> Nested Loop
+ -> Nested Loop
+ -> Seq Scan on q1
+ -> Seq Scan on q2
+ -> Bitmap Heap Scan on tenk1
+ Recheck Cond: ((q1.q1 = thousand) OR (q2.q2 = thousand))
+ -> BitmapOr
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: (thousand = q1.q1)
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: (thousand = q2.q2)
+ -> Hash
+ -> Seq Scan on int4_tbl
+(15 rows)
+
+explain (costs off)
+select * from
+ tenk1 join int4_tbl on f1 = twothousand,
+ q1, q2
+where thousand = (q1 + q2);
+ QUERY PLAN
+--------------------------------------------------------------
+ Hash Join
+ Hash Cond: (tenk1.twothousand = int4_tbl.f1)
+ -> Nested Loop
+ -> Nested Loop
+ -> Seq Scan on q1
+ -> Seq Scan on q2
+ -> Bitmap Heap Scan on tenk1
+ Recheck Cond: (thousand = (q1.q1 + q2.q2))
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: (thousand = (q1.q1 + q2.q2))
+ -> Hash
+ -> Seq Scan on int4_tbl
+(12 rows)
+
+--
+-- test ability to generate a suitable plan for a star-schema query
+--
+explain (costs off)
+select * from
+ tenk1, int8_tbl a, int8_tbl b
+where thousand = a.q1 and tenthous = b.q1 and a.q2 = 1 and b.q2 = 2;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Nested Loop
+ -> Seq Scan on int8_tbl b
+ Filter: (q2 = 2)
+ -> Nested Loop
+ -> Seq Scan on int8_tbl a
+ Filter: (q2 = 1)
+ -> Index Scan using tenk1_thous_tenthous on tenk1
+ Index Cond: ((thousand = a.q1) AND (tenthous = b.q1))
+(8 rows)
+
+--
+-- test a corner case in which we shouldn't apply the star-schema optimization
+--
+explain (costs off)
+select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
+ tenk1 t1
+ inner join int4_tbl i1
+ left join (select v1.x2, v2.y1, 11 AS d1
+ from (select 1,0 from onerow) v1(x1,x2)
+ left join (select 3,1 from onerow) v2(y1,y2)
+ on v1.x1 = v2.y2) subq1
+ on (i1.f1 = subq1.x2)
+ on (t1.unique2 = subq1.d1)
+ left join tenk1 t2
+ on (subq1.y1 = t2.unique1)
+where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Nested Loop
+ -> Nested Loop
+ Join Filter: (t1.stringu1 > t2.stringu2)
+ -> Nested Loop
+ -> Nested Loop
+ -> Seq Scan on onerow
+ -> Seq Scan on onerow onerow_1
+ -> Index Scan using tenk1_unique2 on tenk1 t1
+ Index Cond: ((unique2 = (11)) AND (unique2 < 42))
+ -> Index Scan using tenk1_unique1 on tenk1 t2
+ Index Cond: (unique1 = (3))
+ -> Seq Scan on int4_tbl i1
+ Filter: (f1 = 0)
+(13 rows)
+
+select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
+ tenk1 t1
+ inner join int4_tbl i1
+ left join (select v1.x2, v2.y1, 11 AS d1
+ from (select 1,0 from onerow) v1(x1,x2)
+ left join (select 3,1 from onerow) v2(y1,y2)
+ on v1.x1 = v2.y2) subq1
+ on (i1.f1 = subq1.x2)
+ on (t1.unique2 = subq1.d1)
+ left join tenk1 t2
+ on (subq1.y1 = t2.unique1)
+where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
+ unique2 | stringu1 | unique1 | stringu2
+---------+----------+---------+----------
+ 11 | WFAAAA | 3 | LKIAAA
+(1 row)
+
+-- variant that isn't quite a star-schema case
+select ss1.d1 from
+ tenk1 as t1
+ inner join tenk1 as t2
+ on t1.tenthous = t2.ten
+ inner join
+ int8_tbl as i8
+ left join int4_tbl as i4
+ inner join (select 64::information_schema.cardinal_number as d1
+ from tenk1 t3,
+ lateral (select abs(t3.unique1) + random()) ss0(x)
+ where t3.fivethous < 0) as ss1
+ on i4.f1 = ss1.d1
+ on i8.q1 = i4.f1
+ on t1.tenthous = ss1.d1
+where t1.unique1 < i4.f1;
+ d1
+----
+(0 rows)
+
+-- this variant is foldable by the remove-useless-RESULT-RTEs code
+explain (costs off)
+select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
+ tenk1 t1
+ inner join int4_tbl i1
+ 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 (i1.f1 = subq1.x2)
+ on (t1.unique2 = subq1.d1)
+ left join tenk1 t2
+ on (subq1.y1 = t2.unique1)
+where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
+ QUERY PLAN
+-----------------------------------------------------------------
+ Nested Loop
+ Join Filter: (t1.stringu1 > t2.stringu2)
+ -> Nested Loop
+ -> Seq Scan on int4_tbl i1
+ Filter: (f1 = 0)
+ -> Index Scan using tenk1_unique2 on tenk1 t1
+ Index Cond: ((unique2 = (11)) AND (unique2 < 42))
+ -> Index Scan using tenk1_unique1 on tenk1 t2
+ Index Cond: (unique1 = (3))
+(9 rows)
+
+select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
+ tenk1 t1
+ inner join int4_tbl i1
+ 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 (i1.f1 = subq1.x2)
+ on (t1.unique2 = subq1.d1)
+ left join tenk1 t2
+ on (subq1.y1 = t2.unique1)
+where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
+ unique2 | stringu1 | unique1 | stringu2
+---------+----------+---------+----------
+ 11 | WFAAAA | 3 | LKIAAA
+(1 row)
+
+-- Here's a variant that we can't fold too aggressively, though,
+-- or we end up with noplace to evaluate the lateral PHV
+explain (verbose, costs off)
+select * from
+ (select 1 as x) ss1 left join (select 2 as y) ss2 on (true),
+ lateral (select ss2.y as z limit 1) ss3;
+ QUERY PLAN
+---------------------------
+ Nested Loop
+ Output: 1, (2), ((2))
+ -> Result
+ Output: 2
+ -> Limit
+ Output: ((2))
+ -> Result
+ Output: (2)
+(8 rows)
+
+select * from
+ (select 1 as x) ss1 left join (select 2 as y) ss2 on (true),
+ lateral (select ss2.y as z limit 1) ss3;
+ x | y | z
+---+---+---
+ 1 | 2 | 2
+(1 row)
+
+-- Test proper handling of appendrel PHVs during useless-RTE removal
+explain (costs off)
+select * from
+ (select 0 as z) as t1
+ left join
+ (select true as a) as t2
+ on true,
+ lateral (select true as b
+ union all
+ select a as b) as t3
+where b;
+ QUERY PLAN
+---------------------------------------
+ Nested Loop
+ -> Result
+ -> Append
+ -> Result
+ -> Result
+ One-Time Filter: (true)
+(6 rows)
+
+select * from
+ (select 0 as z) as t1
+ left join
+ (select true as a) as t2
+ on true,
+ lateral (select true as b
+ union all
+ select a as b) as t3
+where b;
+ z | a | b
+---+---+---
+ 0 | t | t
+ 0 | t | t
+(2 rows)
+
+--
+-- test inlining of immutable functions
+--
+create function f_immutable_int4(i integer) returns integer as
+$$ begin return i; end; $$ language plpgsql immutable;
+-- check optimization of function scan with join
+explain (costs off)
+select unique1 from tenk1, (select * from f_immutable_int4(1) x) x
+where x = unique1;
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = 1)
+(2 rows)
+
+explain (verbose, costs off)
+select unique1, x.*
+from tenk1, (select *, random() from f_immutable_int4(1) x) x
+where x = unique1;
+ QUERY PLAN
+-----------------------------------------------------------
+ Nested Loop
+ Output: tenk1.unique1, (1), (random())
+ -> Result
+ Output: 1, random()
+ -> Index Only Scan using tenk1_unique1 on public.tenk1
+ Output: tenk1.unique1
+ Index Cond: (tenk1.unique1 = (1))
+(7 rows)
+
+explain (costs off)
+select unique1 from tenk1, f_immutable_int4(1) x where x = unique1;
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = 1)
+(2 rows)
+
+explain (costs off)
+select unique1 from tenk1, lateral f_immutable_int4(1) x where x = unique1;
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = 1)
+(2 rows)
+
+explain (costs off)
+select unique1 from tenk1, lateral f_immutable_int4(1) x where x in (select 17);
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+explain (costs off)
+select unique1, x from tenk1 join f_immutable_int4(1) x on unique1 = x;
+ QUERY PLAN
+----------------------------------------------
+ Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = 1)
+(2 rows)
+
+explain (costs off)
+select unique1, x from tenk1 left join f_immutable_int4(1) x on unique1 = x;
+ QUERY PLAN
+----------------------------------------------------
+ Nested Loop Left Join
+ Join Filter: (tenk1.unique1 = 1)
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ -> Materialize
+ -> Result
+(5 rows)
+
+explain (costs off)
+select unique1, x from tenk1 right join f_immutable_int4(1) x on unique1 = x;
+ QUERY PLAN
+----------------------------------------------------
+ Nested Loop Left Join
+ -> Result
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = 1)
+(4 rows)
+
+explain (costs off)
+select unique1, x from tenk1 full join f_immutable_int4(1) x on unique1 = x;
+ QUERY PLAN
+----------------------------------------------------
+ Merge Full Join
+ Merge Cond: (tenk1.unique1 = (1))
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ -> Sort
+ Sort Key: (1)
+ -> Result
+(6 rows)
+
+-- check that pullup of a const function allows further const-folding
+explain (costs off)
+select unique1 from tenk1, f_immutable_int4(1) x where x = 42;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+-- test inlining of immutable functions with PlaceHolderVars
+explain (costs off)
+select nt3.id
+from nt3 as nt3
+ left join
+ (select nt2.*, (nt2.b1 or i4 = 42) AS b3
+ from nt2 as nt2
+ left join
+ f_immutable_int4(0) i4
+ on i4 = nt2.nt1_id
+ ) as ss2
+ on ss2.id = nt3.nt2_id
+where nt3.id = 1 and ss2.b3;
+ QUERY PLAN
+----------------------------------------------
+ Nested Loop Left Join
+ Filter: ((nt2.b1 OR ((0) = 42)))
+ -> Index Scan using nt3_pkey on nt3
+ Index Cond: (id = 1)
+ -> Nested Loop Left Join
+ Join Filter: (0 = nt2.nt1_id)
+ -> Index Scan using nt2_pkey on nt2
+ Index Cond: (id = nt3.nt2_id)
+ -> Result
+(9 rows)
+
+drop function f_immutable_int4(int);
+-- test inlining when function returns composite
+create function mki8(bigint, bigint) returns int8_tbl as
+$$select row($1,$2)::int8_tbl$$ language sql;
+create function mki4(int) returns int4_tbl as
+$$select row($1)::int4_tbl$$ language sql;
+explain (verbose, costs off)
+select * from mki8(1,2);
+ QUERY PLAN
+------------------------------------
+ Function Scan on mki8
+ Output: q1, q2
+ Function Call: '(1,2)'::int8_tbl
+(3 rows)
+
+select * from mki8(1,2);
+ q1 | q2
+----+----
+ 1 | 2
+(1 row)
+
+explain (verbose, costs off)
+select * from mki4(42);
+ QUERY PLAN
+-----------------------------------
+ Function Scan on mki4
+ Output: f1
+ Function Call: '(42)'::int4_tbl
+(3 rows)
+
+select * from mki4(42);
+ f1
+----
+ 42
+(1 row)
+
+drop function mki8(bigint, bigint);
+drop function mki4(int);
+--
+-- test extraction of restriction OR clauses from join OR clause
+-- (we used to only do this for indexable clauses)
+--
+explain (costs off)
+select * from tenk1 a join tenk1 b on
+ (a.unique1 = 1 and b.unique1 = 2) or (a.unique2 = 3 and b.hundred = 4);
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------
+ Nested Loop
+ Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR ((a.unique2 = 3) AND (b.hundred = 4)))
+ -> Bitmap Heap Scan on tenk1 b
+ Recheck Cond: ((unique1 = 2) OR (hundred = 4))
+ -> BitmapOr
+ -> Bitmap Index Scan on tenk1_unique1
+ Index Cond: (unique1 = 2)
+ -> Bitmap Index Scan on tenk1_hundred
+ Index Cond: (hundred = 4)
+ -> Materialize
+ -> Bitmap Heap Scan on tenk1 a
+ Recheck Cond: ((unique1 = 1) OR (unique2 = 3))
+ -> BitmapOr
+ -> Bitmap Index Scan on tenk1_unique1
+ Index Cond: (unique1 = 1)
+ -> Bitmap Index Scan on tenk1_unique2
+ Index Cond: (unique2 = 3)
+(17 rows)
+
+explain (costs off)
+select * from tenk1 a join tenk1 b on
+ (a.unique1 = 1 and b.unique1 = 2) or (a.unique2 = 3 and b.ten = 4);
+ QUERY PLAN
+---------------------------------------------------------------------------------------------
+ Nested Loop
+ Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR ((a.unique2 = 3) AND (b.ten = 4)))
+ -> Seq Scan on tenk1 b
+ Filter: ((unique1 = 2) OR (ten = 4))
+ -> Materialize
+ -> Bitmap Heap Scan on tenk1 a
+ Recheck Cond: ((unique1 = 1) OR (unique2 = 3))
+ -> BitmapOr
+ -> Bitmap Index Scan on tenk1_unique1
+ Index Cond: (unique1 = 1)
+ -> Bitmap Index Scan on tenk1_unique2
+ Index Cond: (unique2 = 3)
+(12 rows)
+
+explain (costs off)
+select * from tenk1 a join tenk1 b on
+ (a.unique1 = 1 and b.unique1 = 2) or
+ ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4);
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR (((a.unique2 = 3) OR (a.unique2 = 7)) AND (b.hundred = 4)))
+ -> Bitmap Heap Scan on tenk1 b
+ Recheck Cond: ((unique1 = 2) OR (hundred = 4))
+ -> BitmapOr
+ -> Bitmap Index Scan on tenk1_unique1
+ Index Cond: (unique1 = 2)
+ -> Bitmap Index Scan on tenk1_hundred
+ Index Cond: (hundred = 4)
+ -> Materialize
+ -> Bitmap Heap Scan on tenk1 a
+ Recheck Cond: ((unique1 = 1) OR (unique2 = 3) OR (unique2 = 7))
+ -> BitmapOr
+ -> Bitmap Index Scan on tenk1_unique1
+ Index Cond: (unique1 = 1)
+ -> Bitmap Index Scan on tenk1_unique2
+ Index Cond: (unique2 = 3)
+ -> Bitmap Index Scan on tenk1_unique2
+ Index Cond: (unique2 = 7)
+(19 rows)
+
+--
+-- test placement of movable quals in a parameterized join tree
+--
+explain (costs off)
+select * from tenk1 t1 left join
+ (tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2)
+ on t1.hundred = t2.hundred and t1.ten = t3.ten
+where t1.unique1 = 1;
+ QUERY PLAN
+--------------------------------------------------------
+ Nested Loop Left Join
+ -> Index Scan using tenk1_unique1 on tenk1 t1
+ Index Cond: (unique1 = 1)
+ -> Nested Loop
+ Join Filter: (t1.ten = t3.ten)
+ -> Bitmap Heap Scan on tenk1 t2
+ Recheck Cond: (t1.hundred = hundred)
+ -> Bitmap Index Scan on tenk1_hundred
+ Index Cond: (hundred = t1.hundred)
+ -> Index Scan using tenk1_unique2 on tenk1 t3
+ Index Cond: (unique2 = t2.thousand)
+(11 rows)
+
+explain (costs off)
+select * from tenk1 t1 left join
+ (tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2)
+ on t1.hundred = t2.hundred and t1.ten + t2.ten = t3.ten
+where t1.unique1 = 1;
+ QUERY PLAN
+--------------------------------------------------------
+ Nested Loop Left Join
+ -> Index Scan using tenk1_unique1 on tenk1 t1
+ Index Cond: (unique1 = 1)
+ -> Nested Loop
+ Join Filter: ((t1.ten + t2.ten) = t3.ten)
+ -> Bitmap Heap Scan on tenk1 t2
+ Recheck Cond: (t1.hundred = hundred)
+ -> Bitmap Index Scan on tenk1_hundred
+ Index Cond: (hundred = t1.hundred)
+ -> Index Scan using tenk1_unique2 on tenk1 t3
+ Index Cond: (unique2 = t2.thousand)
+(11 rows)
+
+explain (costs off)
+select count(*) from
+ tenk1 a join tenk1 b on a.unique1 = b.unique2
+ left join tenk1 c on a.unique2 = b.unique1 and c.thousand = a.thousand
+ join int4_tbl on b.thousand = f1;
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Aggregate
+ -> Nested Loop Left Join
+ Join Filter: (a.unique2 = b.unique1)
+ -> Nested Loop
+ -> Nested Loop
+ -> Seq Scan on int4_tbl
+ -> Bitmap Heap Scan on tenk1 b
+ Recheck Cond: (thousand = int4_tbl.f1)
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: (thousand = int4_tbl.f1)
+ -> Index Scan using tenk1_unique1 on tenk1 a
+ Index Cond: (unique1 = b.unique2)
+ -> Index Only Scan using tenk1_thous_tenthous on tenk1 c
+ Index Cond: (thousand = a.thousand)
+(14 rows)
+
+select count(*) from
+ tenk1 a join tenk1 b on a.unique1 = b.unique2
+ left join tenk1 c on a.unique2 = b.unique1 and c.thousand = a.thousand
+ join int4_tbl on b.thousand = f1;
+ count
+-------
+ 10
+(1 row)
+
+explain (costs off)
+select b.unique1 from
+ tenk1 a join tenk1 b on a.unique1 = b.unique2
+ left join tenk1 c on b.unique1 = 42 and c.thousand = a.thousand
+ join int4_tbl i1 on b.thousand = f1
+ right join int4_tbl i2 on i2.f1 = b.tenthous
+ order by 1;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Sort
+ Sort Key: b.unique1
+ -> Nested Loop Left Join
+ -> Seq Scan on int4_tbl i2
+ -> Nested Loop Left Join
+ Join Filter: (b.unique1 = 42)
+ -> Nested Loop
+ -> Nested Loop
+ -> Seq Scan on int4_tbl i1
+ -> Index Scan using tenk1_thous_tenthous on tenk1 b
+ Index Cond: ((thousand = i1.f1) AND (tenthous = i2.f1))
+ -> Index Scan using tenk1_unique1 on tenk1 a
+ Index Cond: (unique1 = b.unique2)
+ -> Index Only Scan using tenk1_thous_tenthous on tenk1 c
+ Index Cond: (thousand = a.thousand)
+(15 rows)
+
+select b.unique1 from
+ tenk1 a join tenk1 b on a.unique1 = b.unique2
+ left join tenk1 c on b.unique1 = 42 and c.thousand = a.thousand
+ join int4_tbl i1 on b.thousand = f1
+ right join int4_tbl i2 on i2.f1 = b.tenthous
+ order by 1;
+ unique1
+---------
+ 0
+
+
+
+
+(5 rows)
+
+explain (costs off)
+select * from
+(
+ select unique1, q1, coalesce(unique1, -1) + q1 as fault
+ from int8_tbl left join tenk1 on (q2 = unique2)
+) ss
+where fault = 122
+order by fault;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Nested Loop Left Join
+ Filter: ((COALESCE(tenk1.unique1, '-1'::integer) + int8_tbl.q1) = 122)
+ -> Seq Scan on int8_tbl
+ -> Index Scan using tenk1_unique2 on tenk1
+ Index Cond: (unique2 = int8_tbl.q2)
+(5 rows)
+
+select * from
+(
+ select unique1, q1, coalesce(unique1, -1) + q1 as fault
+ from int8_tbl left join tenk1 on (q2 = unique2)
+) ss
+where fault = 122
+order by fault;
+ unique1 | q1 | fault
+---------+-----+-------
+ | 123 | 122
+(1 row)
+
+explain (costs off)
+select * from
+(values (1, array[10,20]), (2, array[20,30])) as v1(v1x,v1ys)
+left join (values (1, 10), (2, 20)) as v2(v2x,v2y) on v2x = v1x
+left join unnest(v1ys) as u1(u1y) on u1y = v2y;
+ QUERY PLAN
+-------------------------------------------------------------
+ Nested Loop Left Join
+ -> Values Scan on "*VALUES*"
+ -> Hash Right Join
+ Hash Cond: (u1.u1y = "*VALUES*_1".column2)
+ Filter: ("*VALUES*_1".column1 = "*VALUES*".column1)
+ -> Function Scan on unnest u1
+ -> Hash
+ -> Values Scan on "*VALUES*_1"
+(8 rows)
+
+select * from
+(values (1, array[10,20]), (2, array[20,30])) as v1(v1x,v1ys)
+left join (values (1, 10), (2, 20)) as v2(v2x,v2y) on v2x = v1x
+left join unnest(v1ys) as u1(u1y) on u1y = v2y;
+ v1x | v1ys | v2x | v2y | u1y
+-----+---------+-----+-----+-----
+ 1 | {10,20} | 1 | 10 | 10
+ 2 | {20,30} | 2 | 20 | 20
+(2 rows)
+
+--
+-- test handling of potential equivalence clauses above outer joins
+--
+explain (costs off)
+select q1, unique2, thousand, hundred
+ from int8_tbl a left join tenk1 b on q1 = unique2
+ where coalesce(thousand,123) = q1 and q1 = coalesce(hundred,123);
+ QUERY PLAN
+--------------------------------------------------------------------------------------
+ Nested Loop Left Join
+ Filter: ((COALESCE(b.thousand, 123) = a.q1) AND (a.q1 = COALESCE(b.hundred, 123)))
+ -> Seq Scan on int8_tbl a
+ -> Index Scan using tenk1_unique2 on tenk1 b
+ Index Cond: (unique2 = a.q1)
+(5 rows)
+
+select q1, unique2, thousand, hundred
+ from int8_tbl a left join tenk1 b on q1 = unique2
+ where coalesce(thousand,123) = q1 and q1 = coalesce(hundred,123);
+ q1 | unique2 | thousand | hundred
+----+---------+----------+---------
+(0 rows)
+
+explain (costs off)
+select f1, unique2, case when unique2 is null then f1 else 0 end
+ from int4_tbl a left join tenk1 b on f1 = unique2
+ where (case when unique2 is null then f1 else 0 end) = 0;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Nested Loop Left Join
+ Filter: (CASE WHEN (b.unique2 IS NULL) THEN a.f1 ELSE 0 END = 0)
+ -> Seq Scan on int4_tbl a
+ -> Index Only Scan using tenk1_unique2 on tenk1 b
+ Index Cond: (unique2 = a.f1)
+(5 rows)
+
+select f1, unique2, case when unique2 is null then f1 else 0 end
+ from int4_tbl a left join tenk1 b on f1 = unique2
+ where (case when unique2 is null then f1 else 0 end) = 0;
+ f1 | unique2 | case
+----+---------+------
+ 0 | 0 | 0
+(1 row)
+
+--
+-- another case with equivalence clauses above outer joins (bug #8591)
+--
+explain (costs off)
+select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand)
+ from tenk1 a left join tenk1 b on b.thousand = a.unique1 left join tenk1 c on c.unique2 = coalesce(b.twothousand, a.twothousand)
+ where a.unique2 < 10 and coalesce(b.twothousand, a.twothousand) = 44;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------
+ Nested Loop Left Join
+ -> Nested Loop Left Join
+ Filter: (COALESCE(b.twothousand, a.twothousand) = 44)
+ -> Index Scan using tenk1_unique2 on tenk1 a
+ Index Cond: (unique2 < 10)
+ -> Bitmap Heap Scan on tenk1 b
+ Recheck Cond: (thousand = a.unique1)
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: (thousand = a.unique1)
+ -> Index Scan using tenk1_unique2 on tenk1 c
+ Index Cond: ((unique2 = COALESCE(b.twothousand, a.twothousand)) AND (unique2 = 44))
+(11 rows)
+
+select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand)
+ from tenk1 a left join tenk1 b on b.thousand = a.unique1 left join tenk1 c on c.unique2 = coalesce(b.twothousand, a.twothousand)
+ where a.unique2 < 10 and coalesce(b.twothousand, a.twothousand) = 44;
+ unique1 | unique1 | unique1 | coalesce
+---------+---------+---------+----------
+(0 rows)
+
+--
+-- check handling of join aliases when flattening multiple levels of subquery
+--
+explain (verbose, costs off)
+select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from
+ (values (0),(1)) foo1(join_key)
+left join
+ (select join_key, bug_field from
+ (select ss1.join_key, ss1.bug_field from
+ (select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1
+ ) foo2
+ left join
+ (select unique2 as join_key from tenk1 i2) ss2
+ using (join_key)
+ ) foo3
+using (join_key);
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Nested Loop Left Join
+ Output: "*VALUES*".column1, i1.f1, (666)
+ Join Filter: ("*VALUES*".column1 = i1.f1)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+ -> Materialize
+ Output: i1.f1, (666)
+ -> Nested Loop Left Join
+ Output: i1.f1, 666
+ -> Seq Scan on public.int4_tbl i1
+ Output: i1.f1
+ -> Index Only Scan using tenk1_unique2 on public.tenk1 i2
+ Output: i2.unique2
+ Index Cond: (i2.unique2 = i1.f1)
+(14 rows)
+
+select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from
+ (values (0),(1)) foo1(join_key)
+left join
+ (select join_key, bug_field from
+ (select ss1.join_key, ss1.bug_field from
+ (select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1
+ ) foo2
+ left join
+ (select unique2 as join_key from tenk1 i2) ss2
+ using (join_key)
+ ) foo3
+using (join_key);
+ foo1_id | foo3_id | bug_field
+---------+---------+-----------
+ 0 | 0 | 666
+ 1 | |
+(2 rows)
+
+--
+-- test successful handling of nested outer joins with degenerate join quals
+--
+explain (verbose, costs off)
+select t1.* from
+ text_tbl t1
+ left join (select *, '***'::text as d1 from int8_tbl i8b1) b1
+ left join int8_tbl i8
+ left join (select *, null::int as d2 from int8_tbl i8b2) b2
+ on (i8.q1 = b2.q1)
+ on (b2.d2 = b1.q2)
+ on (t1.f1 = b1.d1)
+ left join int4_tbl i4
+ on (i8.q2 = i4.f1);
+ QUERY PLAN
+----------------------------------------------------------------------
+ Hash Left Join
+ Output: t1.f1
+ Hash Cond: (i8.q2 = i4.f1)
+ -> Nested Loop Left Join
+ Output: t1.f1, i8.q2
+ Join Filter: (t1.f1 = '***'::text)
+ -> Seq Scan on public.text_tbl t1
+ Output: t1.f1
+ -> Materialize
+ Output: i8.q2
+ -> Hash Right Join
+ Output: i8.q2
+ Hash Cond: ((NULL::integer) = i8b1.q2)
+ -> Hash Join
+ Output: i8.q2, (NULL::integer)
+ Hash Cond: (i8.q1 = i8b2.q1)
+ -> Seq Scan on public.int8_tbl i8
+ Output: i8.q1, i8.q2
+ -> Hash
+ Output: i8b2.q1, (NULL::integer)
+ -> Seq Scan on public.int8_tbl i8b2
+ Output: i8b2.q1, NULL::integer
+ -> Hash
+ Output: i8b1.q2
+ -> Seq Scan on public.int8_tbl i8b1
+ Output: i8b1.q2
+ -> Hash
+ Output: i4.f1
+ -> Seq Scan on public.int4_tbl i4
+ Output: i4.f1
+(30 rows)
+
+select t1.* from
+ text_tbl t1
+ left join (select *, '***'::text as d1 from int8_tbl i8b1) b1
+ left join int8_tbl i8
+ left join (select *, null::int as d2 from int8_tbl i8b2) b2
+ on (i8.q1 = b2.q1)
+ on (b2.d2 = b1.q2)
+ on (t1.f1 = b1.d1)
+ left join int4_tbl i4
+ on (i8.q2 = i4.f1);
+ f1
+-------------------
+ doh!
+ hi de ho neighbor
+(2 rows)
+
+explain (verbose, costs off)
+select t1.* from
+ text_tbl t1
+ left join (select *, '***'::text as d1 from int8_tbl i8b1) b1
+ left join int8_tbl i8
+ left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2) b2
+ on (i8.q1 = b2.q1)
+ on (b2.d2 = b1.q2)
+ on (t1.f1 = b1.d1)
+ left join int4_tbl i4
+ on (i8.q2 = i4.f1);
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Hash Left Join
+ Output: t1.f1
+ Hash Cond: (i8.q2 = i4.f1)
+ -> Nested Loop Left Join
+ Output: t1.f1, i8.q2
+ Join Filter: (t1.f1 = '***'::text)
+ -> Seq Scan on public.text_tbl t1
+ Output: t1.f1
+ -> Materialize
+ Output: i8.q2
+ -> Hash Right Join
+ Output: i8.q2
+ Hash Cond: ((NULL::integer) = i8b1.q2)
+ -> Hash Right Join
+ Output: i8.q2, (NULL::integer)
+ Hash Cond: (i8b2.q1 = i8.q1)
+ -> Nested Loop
+ Output: i8b2.q1, NULL::integer
+ -> Seq Scan on public.int8_tbl i8b2
+ Output: i8b2.q1, i8b2.q2
+ -> Materialize
+ -> Seq Scan on public.int4_tbl i4b2
+ -> Hash
+ Output: i8.q1, i8.q2
+ -> Seq Scan on public.int8_tbl i8
+ Output: i8.q1, i8.q2
+ -> Hash
+ Output: i8b1.q2
+ -> Seq Scan on public.int8_tbl i8b1
+ Output: i8b1.q2
+ -> Hash
+ Output: i4.f1
+ -> Seq Scan on public.int4_tbl i4
+ Output: i4.f1
+(34 rows)
+
+select t1.* from
+ text_tbl t1
+ left join (select *, '***'::text as d1 from int8_tbl i8b1) b1
+ left join int8_tbl i8
+ left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2) b2
+ on (i8.q1 = b2.q1)
+ on (b2.d2 = b1.q2)
+ on (t1.f1 = b1.d1)
+ left join int4_tbl i4
+ on (i8.q2 = i4.f1);
+ f1
+-------------------
+ doh!
+ hi de ho neighbor
+(2 rows)
+
+explain (verbose, costs off)
+select t1.* from
+ text_tbl t1
+ left join (select *, '***'::text as d1 from int8_tbl i8b1) b1
+ left join int8_tbl i8
+ left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2
+ where q1 = f1) b2
+ on (i8.q1 = b2.q1)
+ on (b2.d2 = b1.q2)
+ on (t1.f1 = b1.d1)
+ left join int4_tbl i4
+ on (i8.q2 = i4.f1);
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Hash Left Join
+ Output: t1.f1
+ Hash Cond: (i8.q2 = i4.f1)
+ -> Nested Loop Left Join
+ Output: t1.f1, i8.q2
+ Join Filter: (t1.f1 = '***'::text)
+ -> Seq Scan on public.text_tbl t1
+ Output: t1.f1
+ -> Materialize
+ Output: i8.q2
+ -> Hash Right Join
+ Output: i8.q2
+ Hash Cond: ((NULL::integer) = i8b1.q2)
+ -> Hash Right Join
+ Output: i8.q2, (NULL::integer)
+ Hash Cond: (i8b2.q1 = i8.q1)
+ -> Hash Join
+ Output: i8b2.q1, NULL::integer
+ Hash Cond: (i8b2.q1 = i4b2.f1)
+ -> Seq Scan on public.int8_tbl i8b2
+ Output: i8b2.q1, i8b2.q2
+ -> Hash
+ Output: i4b2.f1
+ -> Seq Scan on public.int4_tbl i4b2
+ Output: i4b2.f1
+ -> Hash
+ Output: i8.q1, i8.q2
+ -> Seq Scan on public.int8_tbl i8
+ Output: i8.q1, i8.q2
+ -> Hash
+ Output: i8b1.q2
+ -> Seq Scan on public.int8_tbl i8b1
+ Output: i8b1.q2
+ -> Hash
+ Output: i4.f1
+ -> Seq Scan on public.int4_tbl i4
+ Output: i4.f1
+(37 rows)
+
+select t1.* from
+ text_tbl t1
+ left join (select *, '***'::text as d1 from int8_tbl i8b1) b1
+ left join int8_tbl i8
+ left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2
+ where q1 = f1) b2
+ on (i8.q1 = b2.q1)
+ on (b2.d2 = b1.q2)
+ on (t1.f1 = b1.d1)
+ left join int4_tbl i4
+ on (i8.q2 = i4.f1);
+ f1
+-------------------
+ doh!
+ hi de ho neighbor
+(2 rows)
+
+explain (verbose, costs off)
+select * from
+ text_tbl t1
+ inner join int8_tbl i8
+ on i8.q2 = 456
+ right join text_tbl t2
+ on t1.f1 = 'doh!'
+ left join int4_tbl i4
+ on i8.q1 = i4.f1;
+ QUERY PLAN
+--------------------------------------------------------
+ Nested Loop Left Join
+ Output: t1.f1, i8.q1, i8.q2, t2.f1, i4.f1
+ -> Seq Scan on public.text_tbl t2
+ Output: t2.f1
+ -> Materialize
+ Output: i8.q1, i8.q2, i4.f1, t1.f1
+ -> Nested Loop
+ Output: i8.q1, i8.q2, i4.f1, t1.f1
+ -> Nested Loop Left Join
+ Output: i8.q1, i8.q2, i4.f1
+ Join Filter: (i8.q1 = i4.f1)
+ -> Seq Scan on public.int8_tbl i8
+ Output: i8.q1, i8.q2
+ Filter: (i8.q2 = 456)
+ -> Seq Scan on public.int4_tbl i4
+ Output: i4.f1
+ -> Seq Scan on public.text_tbl t1
+ Output: t1.f1
+ Filter: (t1.f1 = 'doh!'::text)
+(19 rows)
+
+select * from
+ text_tbl t1
+ inner join int8_tbl i8
+ on i8.q2 = 456
+ right join text_tbl t2
+ on t1.f1 = 'doh!'
+ left join int4_tbl i4
+ on i8.q1 = i4.f1;
+ f1 | q1 | q2 | f1 | f1
+------+-----+-----+-------------------+----
+ doh! | 123 | 456 | doh! |
+ doh! | 123 | 456 | hi de ho neighbor |
+(2 rows)
+
+--
+-- test for appropriate join order in the presence of lateral references
+--
+explain (verbose, costs off)
+select * from
+ text_tbl t1
+ left join int8_tbl i8
+ on i8.q2 = 123,
+ lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss
+where t1.f1 = ss.f1;
+ QUERY PLAN
+--------------------------------------------------
+ Nested Loop
+ Output: t1.f1, i8.q1, i8.q2, (i8.q1), t2.f1
+ Join Filter: (t1.f1 = t2.f1)
+ -> Nested Loop Left Join
+ Output: t1.f1, i8.q1, i8.q2
+ -> Seq Scan on public.text_tbl t1
+ Output: t1.f1
+ -> Materialize
+ Output: i8.q1, i8.q2
+ -> Seq Scan on public.int8_tbl i8
+ Output: i8.q1, i8.q2
+ Filter: (i8.q2 = 123)
+ -> Memoize
+ Output: (i8.q1), t2.f1
+ Cache Key: i8.q1
+ Cache Mode: binary
+ -> Limit
+ Output: (i8.q1), t2.f1
+ -> Seq Scan on public.text_tbl t2
+ Output: i8.q1, t2.f1
+(20 rows)
+
+select * from
+ text_tbl t1
+ left join int8_tbl i8
+ on i8.q2 = 123,
+ lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss
+where t1.f1 = ss.f1;
+ f1 | q1 | q2 | q1 | f1
+------+------------------+-----+------------------+------
+ doh! | 4567890123456789 | 123 | 4567890123456789 | doh!
+(1 row)
+
+explain (verbose, costs off)
+select * from
+ text_tbl t1
+ left join int8_tbl i8
+ on i8.q2 = 123,
+ lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss1,
+ lateral (select ss1.* from text_tbl t3 limit 1) as ss2
+where t1.f1 = ss2.f1;
+ QUERY PLAN
+-------------------------------------------------------------------
+ Nested Loop
+ Output: t1.f1, i8.q1, i8.q2, (i8.q1), t2.f1, ((i8.q1)), (t2.f1)
+ Join Filter: (t1.f1 = (t2.f1))
+ -> Nested Loop
+ Output: t1.f1, i8.q1, i8.q2, (i8.q1), t2.f1
+ -> Nested Loop Left Join
+ Output: t1.f1, i8.q1, i8.q2
+ -> Seq Scan on public.text_tbl t1
+ Output: t1.f1
+ -> Materialize
+ Output: i8.q1, i8.q2
+ -> Seq Scan on public.int8_tbl i8
+ Output: i8.q1, i8.q2
+ Filter: (i8.q2 = 123)
+ -> Memoize
+ Output: (i8.q1), t2.f1
+ Cache Key: i8.q1
+ Cache Mode: binary
+ -> Limit
+ Output: (i8.q1), t2.f1
+ -> Seq Scan on public.text_tbl t2
+ Output: i8.q1, t2.f1
+ -> Memoize
+ Output: ((i8.q1)), (t2.f1)
+ Cache Key: (i8.q1), t2.f1
+ Cache Mode: binary
+ -> Limit
+ Output: ((i8.q1)), (t2.f1)
+ -> Seq Scan on public.text_tbl t3
+ Output: (i8.q1), t2.f1
+(30 rows)
+
+select * from
+ text_tbl t1
+ left join int8_tbl i8
+ on i8.q2 = 123,
+ lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss1,
+ lateral (select ss1.* from text_tbl t3 limit 1) as ss2
+where t1.f1 = ss2.f1;
+ f1 | q1 | q2 | q1 | f1 | q1 | f1
+------+------------------+-----+------------------+------+------------------+------
+ doh! | 4567890123456789 | 123 | 4567890123456789 | doh! | 4567890123456789 | doh!
+(1 row)
+
+explain (verbose, costs off)
+select 1 from
+ text_tbl as tt1
+ inner join text_tbl as tt2 on (tt1.f1 = 'foo')
+ left join text_tbl as tt3 on (tt3.f1 = 'foo')
+ left join text_tbl as tt4 on (tt3.f1 = tt4.f1),
+ lateral (select tt4.f1 as c0 from text_tbl as tt5 limit 1) as ss1
+where tt1.f1 = ss1.c0;
+ QUERY PLAN
+----------------------------------------------------------
+ Nested Loop
+ Output: 1
+ -> Nested Loop Left Join
+ Output: tt1.f1, tt4.f1
+ -> Nested Loop
+ Output: tt1.f1
+ -> Seq Scan on public.text_tbl tt1
+ Output: tt1.f1
+ Filter: (tt1.f1 = 'foo'::text)
+ -> Seq Scan on public.text_tbl tt2
+ Output: tt2.f1
+ -> Materialize
+ Output: tt4.f1
+ -> Nested Loop Left Join
+ Output: tt4.f1
+ Join Filter: (tt3.f1 = tt4.f1)
+ -> Seq Scan on public.text_tbl tt3
+ Output: tt3.f1
+ Filter: (tt3.f1 = 'foo'::text)
+ -> Seq Scan on public.text_tbl tt4
+ Output: tt4.f1
+ Filter: (tt4.f1 = 'foo'::text)
+ -> Memoize
+ Output: ss1.c0
+ Cache Key: tt4.f1
+ Cache Mode: binary
+ -> Subquery Scan on ss1
+ Output: ss1.c0
+ Filter: (ss1.c0 = 'foo'::text)
+ -> Limit
+ Output: (tt4.f1)
+ -> Seq Scan on public.text_tbl tt5
+ Output: tt4.f1
+(33 rows)
+
+select 1 from
+ text_tbl as tt1
+ inner join text_tbl as tt2 on (tt1.f1 = 'foo')
+ left join text_tbl as tt3 on (tt3.f1 = 'foo')
+ left join text_tbl as tt4 on (tt3.f1 = tt4.f1),
+ lateral (select tt4.f1 as c0 from text_tbl as tt5 limit 1) as ss1
+where tt1.f1 = ss1.c0;
+ ?column?
+----------
+(0 rows)
+
+--
+-- check a case in which a PlaceHolderVar forces join order
+--
+explain (verbose, costs off)
+select ss2.* from
+ int4_tbl i41
+ left join int8_tbl i8
+ join (select i42.f1 as c1, i43.f1 as c2, 42 as c3
+ from int4_tbl i42, int4_tbl i43) ss1
+ on i8.q1 = ss1.c2
+ on i41.f1 = ss1.c1,
+ lateral (select i41.*, i8.*, ss1.* from text_tbl limit 1) ss2
+where ss1.c2 = 0;
+ QUERY PLAN
+------------------------------------------------------------------------
+ Nested Loop
+ Output: (i41.f1), (i8.q1), (i8.q2), (i42.f1), (i43.f1), ((42))
+ -> Hash Join
+ Output: i41.f1, i42.f1, i8.q1, i8.q2, i43.f1, 42
+ Hash Cond: (i41.f1 = i42.f1)
+ -> Nested Loop
+ Output: i8.q1, i8.q2, i43.f1, i41.f1
+ -> Nested Loop
+ Output: i8.q1, i8.q2, i43.f1
+ -> Seq Scan on public.int8_tbl i8
+ Output: i8.q1, i8.q2
+ Filter: (i8.q1 = 0)
+ -> Seq Scan on public.int4_tbl i43
+ Output: i43.f1
+ Filter: (i43.f1 = 0)
+ -> Seq Scan on public.int4_tbl i41
+ Output: i41.f1
+ -> Hash
+ Output: i42.f1
+ -> Seq Scan on public.int4_tbl i42
+ Output: i42.f1
+ -> Limit
+ Output: (i41.f1), (i8.q1), (i8.q2), (i42.f1), (i43.f1), ((42))
+ -> Seq Scan on public.text_tbl
+ Output: i41.f1, i8.q1, i8.q2, i42.f1, i43.f1, (42)
+(25 rows)
+
+select ss2.* from
+ int4_tbl i41
+ left join int8_tbl i8
+ join (select i42.f1 as c1, i43.f1 as c2, 42 as c3
+ from int4_tbl i42, int4_tbl i43) ss1
+ on i8.q1 = ss1.c2
+ on i41.f1 = ss1.c1,
+ lateral (select i41.*, i8.*, ss1.* from text_tbl limit 1) ss2
+where ss1.c2 = 0;
+ f1 | q1 | q2 | c1 | c2 | c3
+----+----+----+----+----+----
+(0 rows)
+
+--
+-- test successful handling of full join underneath left join (bug #14105)
+--
+explain (costs off)
+select * from
+ (select 1 as id) as xx
+ left join
+ (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))
+ on (xx.id = coalesce(yy.id));
+ QUERY PLAN
+---------------------------------------
+ Nested Loop Left Join
+ -> Result
+ -> Hash Full Join
+ Hash Cond: (a1.unique1 = (1))
+ Filter: (1 = COALESCE((1)))
+ -> Seq Scan on tenk1 a1
+ -> Hash
+ -> Result
+(8 rows)
+
+select * from
+ (select 1 as id) as xx
+ left join
+ (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))
+ on (xx.id = coalesce(yy.id));
+ id | unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 | id
+----+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------+----
+ 1 | 1 | 2838 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 3 | BAAAAA | EFEAAA | OOOOxx | 1
+(1 row)
+
+--
+-- test ability to push constants through outer join clauses
+--
+explain (costs off)
+ select * from int4_tbl a left join tenk1 b on f1 = unique2 where f1 = 0;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop Left Join
+ Join Filter: (a.f1 = b.unique2)
+ -> Seq Scan on int4_tbl a
+ Filter: (f1 = 0)
+ -> Index Scan using tenk1_unique2 on tenk1 b
+ Index Cond: (unique2 = 0)
+(6 rows)
+
+explain (costs off)
+ select * from tenk1 a full join tenk1 b using(unique2) where unique2 = 42;
+ QUERY PLAN
+-------------------------------------------------
+ Merge Full Join
+ Merge Cond: (a.unique2 = b.unique2)
+ -> Index Scan using tenk1_unique2 on tenk1 a
+ Index Cond: (unique2 = 42)
+ -> Index Scan using tenk1_unique2 on tenk1 b
+ Index Cond: (unique2 = 42)
+(6 rows)
+
+--
+-- test that quals attached to an outer join have correct semantics,
+-- specifically that they don't re-use expressions computed below the join;
+-- we force a mergejoin so that coalesce(b.q1, 1) appears as a join input
+--
+set enable_hashjoin to off;
+set enable_nestloop to off;
+explain (verbose, costs off)
+ select a.q2, b.q1
+ from int8_tbl a left join int8_tbl b on a.q2 = coalesce(b.q1, 1)
+ where coalesce(b.q1, 1) > 0;
+ QUERY PLAN
+---------------------------------------------------------
+ Merge Left Join
+ Output: a.q2, b.q1
+ Merge Cond: (a.q2 = (COALESCE(b.q1, '1'::bigint)))
+ Filter: (COALESCE(b.q1, '1'::bigint) > 0)
+ -> Sort
+ Output: a.q2
+ Sort Key: a.q2
+ -> Seq Scan on public.int8_tbl a
+ Output: a.q2
+ -> Sort
+ Output: b.q1, (COALESCE(b.q1, '1'::bigint))
+ Sort Key: (COALESCE(b.q1, '1'::bigint))
+ -> Seq Scan on public.int8_tbl b
+ Output: b.q1, COALESCE(b.q1, '1'::bigint)
+(14 rows)
+
+select a.q2, b.q1
+ from int8_tbl a left join int8_tbl b on a.q2 = coalesce(b.q1, 1)
+ where coalesce(b.q1, 1) > 0;
+ q2 | q1
+-------------------+------------------
+ -4567890123456789 |
+ 123 | 123
+ 123 | 123
+ 456 |
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+(10 rows)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+--
+-- test join removal
+--
+begin;
+CREATE TEMP TABLE a (id int PRIMARY KEY, b_id int);
+CREATE TEMP TABLE b (id int PRIMARY KEY, c_id int);
+CREATE TEMP TABLE c (id int PRIMARY KEY);
+CREATE TEMP TABLE d (a int, b int);
+INSERT INTO a VALUES (0, 0), (1, NULL);
+INSERT INTO b VALUES (0, 0), (1, NULL);
+INSERT INTO c VALUES (0), (1);
+INSERT INTO d VALUES (1,3), (2,2), (3,1);
+-- all three cases should be optimizable into a simple seqscan
+explain (costs off) SELECT a.* FROM a LEFT JOIN b ON a.b_id = b.id;
+ QUERY PLAN
+---------------
+ Seq Scan on a
+(1 row)
+
+explain (costs off) SELECT b.* FROM b LEFT JOIN c ON b.c_id = c.id;
+ QUERY PLAN
+---------------
+ Seq Scan on b
+(1 row)
+
+explain (costs off)
+ SELECT a.* FROM a LEFT JOIN (b left join c on b.c_id = c.id)
+ ON (a.b_id = b.id);
+ QUERY PLAN
+---------------
+ Seq Scan on a
+(1 row)
+
+-- check optimization of outer join within another special join
+explain (costs off)
+select id from a where id in (
+ select b.id from b left join c on b.id = c.id
+);
+ QUERY PLAN
+----------------------------
+ Hash Join
+ Hash Cond: (a.id = b.id)
+ -> Seq Scan on a
+ -> Hash
+ -> Seq Scan on b
+(5 rows)
+
+-- check that join removal works for a left join when joining a subquery
+-- that is guaranteed to be unique by its GROUP BY clause
+explain (costs off)
+select d.* from d left join (select * from b group by b.id, b.c_id) s
+ on d.a = s.id and d.b = s.c_id;
+ QUERY PLAN
+---------------
+ Seq Scan on d
+(1 row)
+
+-- similarly, but keying off a DISTINCT clause
+explain (costs off)
+select d.* from d left join (select distinct * from b) s
+ on d.a = s.id and d.b = s.c_id;
+ QUERY PLAN
+---------------
+ Seq Scan on d
+(1 row)
+
+-- join removal is not possible when the GROUP BY contains a column that is
+-- not in the join condition. (Note: as of 9.6, we notice that b.id is a
+-- primary key and so drop b.c_id from the GROUP BY of the resulting plan;
+-- but this happens too late for join removal in the outer plan level.)
+explain (costs off)
+select d.* from d left join (select * from b group by b.id, b.c_id) s
+ on d.a = s.id;
+ QUERY PLAN
+------------------------------------------
+ Merge Right Join
+ Merge Cond: (b.id = d.a)
+ -> Group
+ Group Key: b.id
+ -> Index Scan using b_pkey on b
+ -> Sort
+ Sort Key: d.a
+ -> Seq Scan on d
+(8 rows)
+
+-- similarly, but keying off a DISTINCT clause
+explain (costs off)
+select d.* from d left join (select distinct * from b) s
+ on d.a = s.id;
+ QUERY PLAN
+--------------------------------------
+ Merge Right Join
+ Merge Cond: (b.id = d.a)
+ -> Unique
+ -> Sort
+ Sort Key: b.id, b.c_id
+ -> Seq Scan on b
+ -> Sort
+ Sort Key: d.a
+ -> Seq Scan on d
+(9 rows)
+
+-- check join removal works when uniqueness of the join condition is enforced
+-- by a UNION
+explain (costs off)
+select d.* from d left join (select id from a union select id from b) s
+ on d.a = s.id;
+ QUERY PLAN
+---------------
+ Seq Scan on d
+(1 row)
+
+-- check join removal with a cross-type comparison operator
+explain (costs off)
+select i8.* from int8_tbl i8 left join (select f1 from int4_tbl group by f1) i4
+ on i8.q1 = i4.f1;
+ QUERY PLAN
+-------------------------
+ Seq Scan on int8_tbl i8
+(1 row)
+
+-- check join removal with lateral references
+explain (costs off)
+select 1 from (select a.id FROM a left join b on a.b_id = b.id) q,
+ lateral generate_series(1, q.id) gs(i) where q.id = gs.i;
+ QUERY PLAN
+-------------------------------------------
+ Nested Loop
+ -> Seq Scan on a
+ -> Function Scan on generate_series gs
+ Filter: (a.id = i)
+(4 rows)
+
+rollback;
+create temp table parent (k int primary key, pd int);
+create temp table child (k int unique, cd int);
+insert into parent values (1, 10), (2, 20), (3, 30);
+insert into child values (1, 100), (4, 400);
+-- this case is optimizable
+select p.* from parent p left join child c on (p.k = c.k);
+ k | pd
+---+----
+ 1 | 10
+ 2 | 20
+ 3 | 30
+(3 rows)
+
+explain (costs off)
+ select p.* from parent p left join child c on (p.k = c.k);
+ QUERY PLAN
+----------------------
+ Seq Scan on parent p
+(1 row)
+
+-- this case is not
+select p.*, linked from parent p
+ left join (select c.*, true as linked from child c) as ss
+ on (p.k = ss.k);
+ k | pd | linked
+---+----+--------
+ 1 | 10 | t
+ 2 | 20 |
+ 3 | 30 |
+(3 rows)
+
+explain (costs off)
+ select p.*, linked from parent p
+ left join (select c.*, true as linked from child c) as ss
+ on (p.k = ss.k);
+ QUERY PLAN
+---------------------------------
+ Hash Left Join
+ Hash Cond: (p.k = c.k)
+ -> Seq Scan on parent p
+ -> Hash
+ -> Seq Scan on child c
+(5 rows)
+
+-- check for a 9.0rc1 bug: join removal breaks pseudoconstant qual handling
+select p.* from
+ parent p left join child c on (p.k = c.k)
+ where p.k = 1 and p.k = 2;
+ k | pd
+---+----
+(0 rows)
+
+explain (costs off)
+select p.* from
+ parent p left join child c on (p.k = c.k)
+ where p.k = 1 and p.k = 2;
+ QUERY PLAN
+------------------------------------------------
+ Result
+ One-Time Filter: false
+ -> Index Scan using parent_pkey on parent p
+ Index Cond: (k = 1)
+(4 rows)
+
+select p.* from
+ (parent p left join child c on (p.k = c.k)) join parent x on p.k = x.k
+ where p.k = 1 and p.k = 2;
+ k | pd
+---+----
+(0 rows)
+
+explain (costs off)
+select p.* from
+ (parent p left join child c on (p.k = c.k)) join parent x on p.k = x.k
+ where p.k = 1 and p.k = 2;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+-- bug 5255: this is not optimizable by join removal
+begin;
+CREATE TEMP TABLE a (id int PRIMARY KEY);
+CREATE TEMP TABLE b (id int PRIMARY KEY, a_id int);
+INSERT INTO a VALUES (0), (1);
+INSERT INTO b VALUES (0, 0), (1, NULL);
+SELECT * FROM b LEFT JOIN a ON (b.a_id = a.id) WHERE (a.id IS NULL OR a.id > 0);
+ id | a_id | id
+----+------+----
+ 1 | |
+(1 row)
+
+SELECT b.* FROM b LEFT JOIN a ON (b.a_id = a.id) WHERE (a.id IS NULL OR a.id > 0);
+ id | a_id
+----+------
+ 1 |
+(1 row)
+
+rollback;
+-- another join removal bug: this is not optimizable, either
+begin;
+create temp table innertab (id int8 primary key, dat1 int8);
+insert into innertab values(123, 42);
+SELECT * FROM
+ (SELECT 1 AS x) ss1
+ LEFT JOIN
+ (SELECT q1, q2, COALESCE(dat1, q1) AS y
+ FROM int8_tbl LEFT JOIN innertab ON q2 = id) ss2
+ ON true;
+ x | q1 | q2 | y
+---+------------------+-------------------+------------------
+ 1 | 123 | 456 | 123
+ 1 | 123 | 4567890123456789 | 123
+ 1 | 4567890123456789 | 123 | 42
+ 1 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 1 | 4567890123456789 | -4567890123456789 | 4567890123456789
+(5 rows)
+
+rollback;
+-- another join removal bug: we must clean up correctly when removing a PHV
+begin;
+create temp table uniquetbl (f1 text unique);
+explain (costs off)
+select t1.* from
+ uniquetbl as t1
+ left join (select *, '***'::text as d1 from uniquetbl) t2
+ on t1.f1 = t2.f1
+ left join uniquetbl t3
+ on t2.d1 = t3.f1;
+ QUERY PLAN
+--------------------------
+ Seq Scan on uniquetbl t1
+(1 row)
+
+explain (costs off)
+select t0.*
+from
+ text_tbl t0
+ left join
+ (select case t1.ten when 0 then 'doh!'::text else null::text end as case1,
+ t1.stringu2
+ from tenk1 t1
+ join int4_tbl i4 ON i4.f1 = t1.unique2
+ left join uniquetbl u1 ON u1.f1 = t1.string4) ss
+ on t0.f1 = ss.case1
+where ss.stringu2 !~* ss.case1;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------
+ Nested Loop
+ Join Filter: (CASE t1.ten WHEN 0 THEN 'doh!'::text ELSE NULL::text END = t0.f1)
+ -> Nested Loop
+ -> Seq Scan on int4_tbl i4
+ -> Index Scan using tenk1_unique2 on tenk1 t1
+ Index Cond: (unique2 = i4.f1)
+ Filter: (stringu2 !~* CASE ten WHEN 0 THEN 'doh!'::text ELSE NULL::text END)
+ -> Materialize
+ -> Seq Scan on text_tbl t0
+(9 rows)
+
+select t0.*
+from
+ text_tbl t0
+ left join
+ (select case t1.ten when 0 then 'doh!'::text else null::text end as case1,
+ t1.stringu2
+ from tenk1 t1
+ join int4_tbl i4 ON i4.f1 = t1.unique2
+ left join uniquetbl u1 ON u1.f1 = t1.string4) ss
+ on t0.f1 = ss.case1
+where ss.stringu2 !~* ss.case1;
+ f1
+------
+ doh!
+(1 row)
+
+rollback;
+-- test case to expose miscomputation of required relid set for a PHV
+explain (verbose, costs off)
+select i8.*, ss.v, t.unique2
+ from int8_tbl i8
+ left join int4_tbl i4 on i4.f1 = 1
+ left join lateral (select i4.f1 + 1 as v) as ss on true
+ left join tenk1 t on t.unique2 = ss.v
+where q2 = 456;
+ QUERY PLAN
+-------------------------------------------------------------
+ Nested Loop Left Join
+ Output: i8.q1, i8.q2, ((i4.f1 + 1)), t.unique2
+ -> Nested Loop Left Join
+ Output: i8.q1, i8.q2, (i4.f1 + 1)
+ -> Seq Scan on public.int8_tbl i8
+ Output: i8.q1, i8.q2
+ Filter: (i8.q2 = 456)
+ -> Seq Scan on public.int4_tbl i4
+ Output: i4.f1
+ Filter: (i4.f1 = 1)
+ -> Index Only Scan using tenk1_unique2 on public.tenk1 t
+ Output: t.unique2
+ Index Cond: (t.unique2 = ((i4.f1 + 1)))
+(13 rows)
+
+select i8.*, ss.v, t.unique2
+ from int8_tbl i8
+ left join int4_tbl i4 on i4.f1 = 1
+ left join lateral (select i4.f1 + 1 as v) as ss on true
+ left join tenk1 t on t.unique2 = ss.v
+where q2 = 456;
+ q1 | q2 | v | unique2
+-----+-----+---+---------
+ 123 | 456 | |
+(1 row)
+
+-- and check a related issue where we miscompute required relids for
+-- a PHV that's been translated to a child rel
+create temp table parttbl (a integer primary key) partition by range (a);
+create temp table parttbl1 partition of parttbl for values from (1) to (100);
+insert into parttbl values (11), (12);
+explain (costs off)
+select * from
+ (select *, 12 as phv from parttbl) as ss
+ right join int4_tbl on true
+where ss.a = ss.phv and f1 = 0;
+ QUERY PLAN
+------------------------------------
+ Nested Loop
+ -> Seq Scan on int4_tbl
+ Filter: (f1 = 0)
+ -> Seq Scan on parttbl1 parttbl
+ Filter: (a = 12)
+(5 rows)
+
+select * from
+ (select *, 12 as phv from parttbl) as ss
+ right join int4_tbl on true
+where ss.a = ss.phv and f1 = 0;
+ a | phv | f1
+----+-----+----
+ 12 | 12 | 0
+(1 row)
+
+-- bug #8444: we've historically allowed duplicate aliases within aliased JOINs
+select * from
+ int8_tbl x join (int4_tbl x cross join int4_tbl y) j on q1 = f1; -- error
+ERROR: column reference "f1" is ambiguous
+LINE 2: ..._tbl x join (int4_tbl x cross join int4_tbl y) j on q1 = f1;
+ ^
+select * from
+ int8_tbl x join (int4_tbl x cross join int4_tbl y) j on q1 = y.f1; -- error
+ERROR: invalid reference to FROM-clause entry for table "y"
+LINE 2: ...bl x join (int4_tbl x cross join int4_tbl y) j on q1 = y.f1;
+ ^
+HINT: There is an entry for table "y", but it cannot be referenced from this part of the query.
+select * from
+ int8_tbl x join (int4_tbl x cross join int4_tbl y(ff)) j on q1 = f1; -- ok
+ q1 | q2 | f1 | ff
+----+----+----+----
+(0 rows)
+
+--
+-- 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".
+--
+-- Take care to reference the correct RTE
+--
+select atts.relid::regclass, s.* from pg_stats s join
+ pg_attribute a on s.attname = a.attname and s.tablename =
+ a.attrelid::regclass::text join (select unnest(indkey) attnum,
+ indexrelid from pg_index i) atts on atts.attnum = a.attnum where
+ schemaname != 'pg_catalog';
+ERROR: column atts.relid does not exist
+LINE 1: select atts.relid::regclass, s.* from pg_stats s join
+ ^
+--
+-- Test LATERAL
+--
+select unique2, x.*
+from tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x;
+ unique2 | f1
+---------+----
+ 9998 | 0
+(1 row)
+
+explain (costs off)
+ select unique2, x.*
+ from tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ -> Seq Scan on int4_tbl b
+ -> Index Scan using tenk1_unique1 on tenk1 a
+ Index Cond: (unique1 = b.f1)
+(4 rows)
+
+select unique2, x.*
+from int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss;
+ unique2 | f1
+---------+----
+ 9998 | 0
+(1 row)
+
+explain (costs off)
+ select unique2, x.*
+ from int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss;
+ QUERY PLAN
+-----------------------------------------------
+ Nested Loop
+ -> Seq Scan on int4_tbl x
+ -> Index Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = x.f1)
+(4 rows)
+
+explain (costs off)
+ select unique2, x.*
+ from int4_tbl x cross join lateral (select unique2 from tenk1 where f1 = unique1) ss;
+ QUERY PLAN
+-----------------------------------------------
+ Nested Loop
+ -> Seq Scan on int4_tbl x
+ -> Index Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = x.f1)
+(4 rows)
+
+select unique2, x.*
+from int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on true;
+ unique2 | f1
+---------+-------------
+ 9998 | 0
+ | 123456
+ | -123456
+ | 2147483647
+ | -2147483647
+(5 rows)
+
+explain (costs off)
+ select unique2, x.*
+ from int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on true;
+ QUERY PLAN
+-----------------------------------------------
+ Nested Loop Left Join
+ -> Seq Scan on int4_tbl x
+ -> Index Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = x.f1)
+(4 rows)
+
+-- check scoping of lateral versus parent references
+-- the first of these should return int8_tbl.q2, the second int8_tbl.q1
+select *, (select r from (select q1 as q2) x, (select q2 as r) y) from int8_tbl;
+ q1 | q2 | r
+------------------+-------------------+-------------------
+ 123 | 456 | 456
+ 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | -4567890123456789
+(5 rows)
+
+select *, (select r from (select q1 as q2) x, lateral (select q2 as r) y) from int8_tbl;
+ q1 | q2 | r
+------------------+-------------------+------------------
+ 123 | 456 | 123
+ 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | 4567890123456789
+(5 rows)
+
+-- lateral with function in FROM
+select count(*) from tenk1 a, lateral generate_series(1,two) g;
+ count
+-------
+ 5000
+(1 row)
+
+explain (costs off)
+ select count(*) from tenk1 a, lateral generate_series(1,two) g;
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ -> Nested Loop
+ -> Seq Scan on tenk1 a
+ -> Memoize
+ Cache Key: a.two
+ Cache Mode: binary
+ -> Function Scan on generate_series g
+(7 rows)
+
+explain (costs off)
+ select count(*) from tenk1 a cross join lateral generate_series(1,two) g;
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ -> Nested Loop
+ -> Seq Scan on tenk1 a
+ -> Memoize
+ Cache Key: a.two
+ Cache Mode: binary
+ -> Function Scan on generate_series g
+(7 rows)
+
+-- don't need the explicit LATERAL keyword for functions
+explain (costs off)
+ select count(*) from tenk1 a, generate_series(1,two) g;
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ -> Nested Loop
+ -> Seq Scan on tenk1 a
+ -> Memoize
+ Cache Key: a.two
+ Cache Mode: binary
+ -> Function Scan on generate_series g
+(7 rows)
+
+-- lateral with UNION ALL subselect
+explain (costs off)
+ select * from generate_series(100,200) g,
+ lateral (select * from int8_tbl a where g = q1 union all
+ select * from int8_tbl b where g = q2) ss;
+ QUERY PLAN
+------------------------------------------
+ Nested Loop
+ -> Function Scan on generate_series g
+ -> Append
+ -> Seq Scan on int8_tbl a
+ Filter: (g.g = q1)
+ -> Seq Scan on int8_tbl b
+ Filter: (g.g = q2)
+(7 rows)
+
+select * from generate_series(100,200) g,
+ lateral (select * from int8_tbl a where g = q1 union all
+ select * from int8_tbl b where g = q2) ss;
+ g | q1 | q2
+-----+------------------+------------------
+ 123 | 123 | 456
+ 123 | 123 | 4567890123456789
+ 123 | 4567890123456789 | 123
+(3 rows)
+
+-- lateral with VALUES
+explain (costs off)
+ select count(*) from tenk1 a,
+ tenk1 b join lateral (values(a.unique1)) ss(x) on b.unique2 = ss.x;
+ QUERY PLAN
+------------------------------------------------------------
+ Aggregate
+ -> Merge Join
+ Merge Cond: (a.unique1 = b.unique2)
+ -> Index Only Scan using tenk1_unique1 on tenk1 a
+ -> Index Only Scan using tenk1_unique2 on tenk1 b
+(5 rows)
+
+select count(*) from tenk1 a,
+ tenk1 b join lateral (values(a.unique1)) ss(x) on b.unique2 = ss.x;
+ count
+-------
+ 10000
+(1 row)
+
+-- lateral with VALUES, no flattening possible
+explain (costs off)
+ select count(*) from tenk1 a,
+ tenk1 b join lateral (values(a.unique1),(-1)) ss(x) on b.unique2 = ss.x;
+ QUERY PLAN
+------------------------------------------------------------------
+ Aggregate
+ -> Nested Loop
+ -> Nested Loop
+ -> Index Only Scan using tenk1_unique1 on tenk1 a
+ -> Values Scan on "*VALUES*"
+ -> Memoize
+ Cache Key: "*VALUES*".column1
+ Cache Mode: logical
+ -> Index Only Scan using tenk1_unique2 on tenk1 b
+ Index Cond: (unique2 = "*VALUES*".column1)
+(10 rows)
+
+select count(*) from tenk1 a,
+ tenk1 b join lateral (values(a.unique1),(-1)) ss(x) on b.unique2 = ss.x;
+ count
+-------
+ 10000
+(1 row)
+
+-- lateral injecting a strange outer join condition
+explain (costs off)
+ select * from int8_tbl a,
+ int8_tbl x left join lateral (select a.q1 from int4_tbl y) ss(z)
+ on x.q2 = ss.z
+ order by a.q1, a.q2, x.q1, x.q2, ss.z;
+ QUERY PLAN
+------------------------------------------------
+ Sort
+ Sort Key: a.q1, a.q2, x.q1, x.q2, (a.q1)
+ -> Nested Loop
+ -> Seq Scan on int8_tbl a
+ -> Hash Right Join
+ Hash Cond: ((a.q1) = x.q2)
+ -> Seq Scan on int4_tbl y
+ -> Hash
+ -> Seq Scan on int8_tbl x
+(9 rows)
+
+select * from int8_tbl a,
+ int8_tbl x left join lateral (select a.q1 from int4_tbl y) ss(z)
+ on x.q2 = ss.z
+ order by a.q1, a.q2, x.q1, x.q2, ss.z;
+ q1 | q2 | q1 | q2 | z
+------------------+-------------------+------------------+-------------------+------------------
+ 123 | 456 | 123 | 456 |
+ 123 | 456 | 123 | 4567890123456789 |
+ 123 | 456 | 4567890123456789 | -4567890123456789 |
+ 123 | 456 | 4567890123456789 | 123 | 123
+ 123 | 456 | 4567890123456789 | 123 | 123
+ 123 | 456 | 4567890123456789 | 123 | 123
+ 123 | 456 | 4567890123456789 | 123 | 123
+ 123 | 456 | 4567890123456789 | 123 | 123
+ 123 | 456 | 4567890123456789 | 4567890123456789 |
+ 123 | 4567890123456789 | 123 | 456 |
+ 123 | 4567890123456789 | 123 | 4567890123456789 |
+ 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 |
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 |
+ 4567890123456789 | -4567890123456789 | 123 | 456 |
+ 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | 4567890123456789 | -4567890123456789 |
+ 4567890123456789 | -4567890123456789 | 4567890123456789 | 123 |
+ 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 123 | 456 |
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 4567890123456789 | -4567890123456789 |
+ 4567890123456789 | 123 | 4567890123456789 | 123 |
+ 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 123 | 456 |
+ 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 |
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 |
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+(57 rows)
+
+-- lateral reference to a join alias variable
+select * from (select f1/2 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1,
+ lateral (select x) ss2(y);
+ x | f1 | y
+---+----+---
+ 0 | 0 | 0
+(1 row)
+
+select * from (select f1 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1,
+ lateral (values(x)) ss2(y);
+ x | f1 | y
+-------------+-------------+-------------
+ 0 | 0 | 0
+ 123456 | 123456 | 123456
+ -123456 | -123456 | -123456
+ 2147483647 | 2147483647 | 2147483647
+ -2147483647 | -2147483647 | -2147483647
+(5 rows)
+
+select * from ((select f1/2 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1) j,
+ lateral (select x) ss2(y);
+ x | f1 | y
+---+----+---
+ 0 | 0 | 0
+(1 row)
+
+-- lateral references requiring pullup
+select * from (values(1)) x(lb),
+ lateral generate_series(lb,4) x4;
+ lb | x4
+----+----
+ 1 | 1
+ 1 | 2
+ 1 | 3
+ 1 | 4
+(4 rows)
+
+select * from (select f1/1000000000 from int4_tbl) x(lb),
+ lateral generate_series(lb,4) x4;
+ lb | x4
+----+----
+ 0 | 0
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 0
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 0
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 2 | 2
+ 2 | 3
+ 2 | 4
+ -2 | -2
+ -2 | -1
+ -2 | 0
+ -2 | 1
+ -2 | 2
+ -2 | 3
+ -2 | 4
+(25 rows)
+
+select * from (values(1)) x(lb),
+ lateral (values(lb)) y(lbcopy);
+ lb | lbcopy
+----+--------
+ 1 | 1
+(1 row)
+
+select * from (values(1)) x(lb),
+ lateral (select lb from int4_tbl) y(lbcopy);
+ lb | lbcopy
+----+--------
+ 1 | 1
+ 1 | 1
+ 1 | 1
+ 1 | 1
+ 1 | 1
+(5 rows)
+
+select * from
+ int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1,
+ lateral (values(x.q1,y.q1,y.q2)) v(xq1,yq1,yq2);
+ q1 | q2 | q1 | q2 | xq1 | yq1 | yq2
+------------------+-------------------+------------------+-------------------+------------------+------------------+-------------------
+ 123 | 456 | | | 123 | |
+ 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789
+ 4567890123456789 | 123 | 123 | 456 | 4567890123456789 | 123 | 456
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 123
+ 4567890123456789 | -4567890123456789 | | | 4567890123456789 | |
+(10 rows)
+
+select * from
+ int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1,
+ lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2);
+ q1 | q2 | q1 | q2 | xq1 | yq1 | yq2
+------------------+-------------------+------------------+-------------------+------------------+------------------+-------------------
+ 123 | 456 | | | 123 | |
+ 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789
+ 4567890123456789 | 123 | 123 | 456 | 4567890123456789 | 123 | 456
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 123
+ 4567890123456789 | -4567890123456789 | | | 4567890123456789 | |
+(10 rows)
+
+select x.* from
+ int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1,
+ lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2);
+ q1 | q2
+------------------+-------------------
+ 123 | 456
+ 123 | 4567890123456789
+ 123 | 4567890123456789
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+ 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+(10 rows)
+
+select v.* from
+ (int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1)
+ left join int4_tbl z on z.f1 = x.q2,
+ lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy);
+ vx | vy
+-------------------+-------------------
+ 123 |
+ 456 |
+ 123 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+ 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+ 4567890123456789 | 123
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+ 123 | 456
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123
+ 4567890123456789 |
+ -4567890123456789 |
+(20 rows)
+
+select v.* from
+ (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1)
+ left join int4_tbl z on z.f1 = x.q2,
+ lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy);
+ vx | vy
+-------------------+-------------------
+ 4567890123456789 | 123
+ 123 | 456
+ 4567890123456789 | 123
+ 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+ 123 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+ 123 |
+ 456 |
+ 4567890123456789 |
+ -4567890123456789 |
+(20 rows)
+
+select v.* from
+ (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1)
+ left join int4_tbl z on z.f1 = x.q2,
+ lateral (select x.q1,y.q1 from onerow union all select x.q2,y.q2 from onerow) v(vx,vy);
+ vx | vy
+-------------------+-------------------
+ 4567890123456789 | 123
+ 123 | 456
+ 4567890123456789 | 123
+ 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+ 123 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+ 123 |
+ 456 |
+ 4567890123456789 |
+ -4567890123456789 |
+(20 rows)
+
+explain (verbose, costs off)
+select * from
+ int8_tbl a left join
+ lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;
+ QUERY PLAN
+------------------------------------------
+ Nested Loop Left Join
+ Output: a.q1, a.q2, b.q1, b.q2, (a.q2)
+ -> Seq Scan on public.int8_tbl a
+ Output: a.q1, a.q2
+ -> Seq Scan on public.int8_tbl b
+ Output: b.q1, b.q2, a.q2
+ Filter: (a.q2 = b.q1)
+(7 rows)
+
+select * from
+ int8_tbl a left join
+ lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;
+ q1 | q2 | q1 | q2 | x
+------------------+-------------------+------------------+-------------------+------------------
+ 123 | 456 | | |
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 123 | 456 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | | |
+(10 rows)
+
+explain (verbose, costs off)
+select * from
+ int8_tbl a left join
+ lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;
+ QUERY PLAN
+------------------------------------------------------------------
+ Nested Loop Left Join
+ Output: a.q1, a.q2, b.q1, b.q2, (COALESCE(a.q2, '42'::bigint))
+ -> Seq Scan on public.int8_tbl a
+ Output: a.q1, a.q2
+ -> Seq Scan on public.int8_tbl b
+ Output: b.q1, b.q2, COALESCE(a.q2, '42'::bigint)
+ Filter: (a.q2 = b.q1)
+(7 rows)
+
+select * from
+ int8_tbl a left join
+ lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;
+ q1 | q2 | q1 | q2 | x
+------------------+-------------------+------------------+-------------------+------------------
+ 123 | 456 | | |
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 123 | 456 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | | |
+(10 rows)
+
+-- lateral can result in join conditions appearing below their
+-- real semantic level
+explain (verbose, costs off)
+select * from int4_tbl i left join
+ lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
+ QUERY PLAN
+-------------------------------------------
+ Hash Left Join
+ Output: i.f1, j.f1
+ Hash Cond: (i.f1 = j.f1)
+ -> Seq Scan on public.int4_tbl i
+ Output: i.f1
+ -> Hash
+ Output: j.f1
+ -> Seq Scan on public.int2_tbl j
+ Output: j.f1
+(9 rows)
+
+select * from int4_tbl i left join
+ lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
+ f1 | f1
+-------------+----
+ 0 | 0
+ 123456 |
+ -123456 |
+ 2147483647 |
+ -2147483647 |
+(5 rows)
+
+explain (verbose, costs off)
+select * from int4_tbl i left join
+ lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
+ QUERY PLAN
+-------------------------------------
+ Nested Loop Left Join
+ Output: i.f1, (COALESCE(i.*))
+ -> Seq Scan on public.int4_tbl i
+ Output: i.f1, i.*
+ -> Seq Scan on public.int2_tbl j
+ Output: j.f1, COALESCE(i.*)
+ Filter: (i.f1 = j.f1)
+(7 rows)
+
+select * from int4_tbl i left join
+ lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
+ f1 | coalesce
+-------------+----------
+ 0 | (0)
+ 123456 |
+ -123456 |
+ 2147483647 |
+ -2147483647 |
+(5 rows)
+
+explain (verbose, costs off)
+select * from int4_tbl a,
+ lateral (
+ select * from int4_tbl b left join int8_tbl c on (b.f1 = q1 and a.f1 = q2)
+ ) ss;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ Output: a.f1, b.f1, c.q1, c.q2
+ -> Seq Scan on public.int4_tbl a
+ Output: a.f1
+ -> Hash Left Join
+ Output: b.f1, c.q1, c.q2
+ Hash Cond: (b.f1 = c.q1)
+ -> Seq Scan on public.int4_tbl b
+ Output: b.f1
+ -> Hash
+ Output: c.q1, c.q2
+ -> Seq Scan on public.int8_tbl c
+ Output: c.q1, c.q2
+ Filter: (a.f1 = c.q2)
+(14 rows)
+
+select * from int4_tbl a,
+ lateral (
+ select * from int4_tbl b left join int8_tbl c on (b.f1 = q1 and a.f1 = q2)
+ ) ss;
+ f1 | f1 | q1 | q2
+-------------+-------------+----+----
+ 0 | 0 | |
+ 0 | 123456 | |
+ 0 | -123456 | |
+ 0 | 2147483647 | |
+ 0 | -2147483647 | |
+ 123456 | 0 | |
+ 123456 | 123456 | |
+ 123456 | -123456 | |
+ 123456 | 2147483647 | |
+ 123456 | -2147483647 | |
+ -123456 | 0 | |
+ -123456 | 123456 | |
+ -123456 | -123456 | |
+ -123456 | 2147483647 | |
+ -123456 | -2147483647 | |
+ 2147483647 | 0 | |
+ 2147483647 | 123456 | |
+ 2147483647 | -123456 | |
+ 2147483647 | 2147483647 | |
+ 2147483647 | -2147483647 | |
+ -2147483647 | 0 | |
+ -2147483647 | 123456 | |
+ -2147483647 | -123456 | |
+ -2147483647 | 2147483647 | |
+ -2147483647 | -2147483647 | |
+(25 rows)
+
+-- lateral reference in a PlaceHolderVar evaluated at join level
+explain (verbose, costs off)
+select * from
+ int8_tbl a left join lateral
+ (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from
+ int8_tbl b cross join int8_tbl c) ss
+ on a.q2 = ss.bq1;
+ QUERY PLAN
+-------------------------------------------------------------
+ Nested Loop Left Join
+ Output: a.q1, a.q2, b.q1, c.q1, (LEAST(a.q1, b.q1, c.q1))
+ -> Seq Scan on public.int8_tbl a
+ Output: a.q1, a.q2
+ -> Nested Loop
+ Output: b.q1, c.q1, LEAST(a.q1, b.q1, c.q1)
+ -> Seq Scan on public.int8_tbl b
+ Output: b.q1, b.q2
+ Filter: (a.q2 = b.q1)
+ -> Seq Scan on public.int8_tbl c
+ Output: c.q1, c.q2
+(11 rows)
+
+select * from
+ int8_tbl a left join lateral
+ (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from
+ int8_tbl b cross join int8_tbl c) ss
+ on a.q2 = ss.bq1;
+ q1 | q2 | bq1 | cq1 | least
+------------------+-------------------+------------------+------------------+------------------
+ 123 | 456 | | |
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 123 | 123
+ 4567890123456789 | 123 | 123 | 123 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 123 | 123
+ 4567890123456789 | 123 | 123 | 123 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | | |
+(42 rows)
+
+-- case requiring nested PlaceHolderVars
+explain (verbose, costs off)
+select * from
+ int8_tbl c left join (
+ int8_tbl a left join (select q1, coalesce(q2,42) as x from int8_tbl b) ss1
+ on a.q2 = ss1.q1
+ cross join
+ lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2
+ ) on c.q2 = ss2.q1,
+ lateral (select ss2.y offset 0) ss3;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: c.q1, c.q2, a.q1, a.q2, b.q1, (COALESCE(b.q2, '42'::bigint)), d.q1, (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)), ((COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)))
+ -> Hash Right Join
+ Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, '42'::bigint)), (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2))
+ Hash Cond: (d.q1 = c.q2)
+ -> Nested Loop
+ Output: a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, '42'::bigint)), (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2))
+ -> Hash Left Join
+ Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, '42'::bigint))
+ Hash Cond: (a.q2 = b.q1)
+ -> Seq Scan on public.int8_tbl a
+ Output: a.q1, a.q2
+ -> Hash
+ Output: b.q1, (COALESCE(b.q2, '42'::bigint))
+ -> Seq Scan on public.int8_tbl b
+ Output: b.q1, COALESCE(b.q2, '42'::bigint)
+ -> Seq Scan on public.int8_tbl d
+ Output: d.q1, COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)
+ -> Hash
+ Output: c.q1, c.q2
+ -> Seq Scan on public.int8_tbl c
+ Output: c.q1, c.q2
+ -> Result
+ Output: (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2))
+(24 rows)
+
+-- case that breaks the old ph_may_need optimization
+explain (verbose, costs off)
+select c.*,a.*,ss1.q1,ss2.q1,ss3.* from
+ int8_tbl c left join (
+ int8_tbl a left join
+ (select q1, coalesce(q2,f1) as x from int8_tbl b, int4_tbl b2
+ where q1 < f1) ss1
+ on a.q2 = ss1.q1
+ cross join
+ lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2
+ ) on c.q2 = ss2.q1,
+ lateral (select * from int4_tbl i where ss2.y > f1) ss3;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, i.f1
+ Join Filter: ((COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)) > i.f1)
+ -> Hash Right Join
+ Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, (COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2))
+ Hash Cond: (d.q1 = c.q2)
+ -> Nested Loop
+ Output: a.q1, a.q2, b.q1, d.q1, (COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2))
+ -> Hash Right Join
+ Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, (b2.f1)::bigint))
+ Hash Cond: (b.q1 = a.q2)
+ -> Nested Loop
+ Output: b.q1, COALESCE(b.q2, (b2.f1)::bigint)
+ Join Filter: (b.q1 < b2.f1)
+ -> Seq Scan on public.int8_tbl b
+ Output: b.q1, b.q2
+ -> Materialize
+ Output: b2.f1
+ -> Seq Scan on public.int4_tbl b2
+ Output: b2.f1
+ -> Hash
+ Output: a.q1, a.q2
+ -> Seq Scan on public.int8_tbl a
+ Output: a.q1, a.q2
+ -> Seq Scan on public.int8_tbl d
+ Output: d.q1, COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)
+ -> Hash
+ Output: c.q1, c.q2
+ -> Seq Scan on public.int8_tbl c
+ Output: c.q1, c.q2
+ -> Materialize
+ Output: i.f1
+ -> Seq Scan on public.int4_tbl i
+ Output: i.f1
+(34 rows)
+
+-- check processing of postponed quals (bug #9041)
+explain (verbose, costs off)
+select * from
+ (select 1 as x offset 0) x cross join (select 2 as y offset 0) y
+ left join lateral (
+ select * from (select 3 as z offset 0) z where z.z = x.x
+ ) zz on zz.z = y.y;
+ QUERY PLAN
+----------------------------------------------
+ Nested Loop Left Join
+ Output: (1), (2), (3)
+ Join Filter: (((3) = (1)) AND ((3) = (2)))
+ -> Nested Loop
+ Output: (1), (2)
+ -> Result
+ Output: 1
+ -> Result
+ Output: 2
+ -> Result
+ Output: 3
+(11 rows)
+
+-- check dummy rels with lateral references (bug #15694)
+explain (verbose, costs off)
+select * from int8_tbl i8 left join lateral
+ (select *, i8.q2 from int4_tbl where false) ss on true;
+ QUERY PLAN
+--------------------------------------
+ Nested Loop Left Join
+ Output: i8.q1, i8.q2, f1, (i8.q2)
+ -> Seq Scan on public.int8_tbl i8
+ Output: i8.q1, i8.q2
+ -> Result
+ Output: f1, i8.q2
+ One-Time Filter: false
+(7 rows)
+
+explain (verbose, costs off)
+select * from int8_tbl i8 left join lateral
+ (select *, i8.q2 from int4_tbl i1, int4_tbl i2 where false) ss on true;
+ QUERY PLAN
+-----------------------------------------
+ Nested Loop Left Join
+ Output: i8.q1, i8.q2, f1, f1, (i8.q2)
+ -> Seq Scan on public.int8_tbl i8
+ Output: i8.q1, i8.q2
+ -> Result
+ Output: f1, f1, i8.q2
+ One-Time Filter: false
+(7 rows)
+
+-- check handling of nested appendrels inside LATERAL
+select * from
+ ((select 2 as v) union all (select 3 as v)) as q1
+ cross join lateral
+ ((select * from
+ ((select 4 as v) union all (select 5 as v)) as q3)
+ union all
+ (select q1.v)
+ ) as q2;
+ v | v
+---+---
+ 2 | 4
+ 2 | 5
+ 2 | 2
+ 3 | 4
+ 3 | 5
+ 3 | 3
+(6 rows)
+
+-- check the number of columns specified
+SELECT * FROM (int8_tbl i cross join int4_tbl j) ss(a,b,c,d);
+ERROR: join expression "ss" has 3 columns available but 4 columns specified
+-- check we don't try to do a unique-ified semijoin with LATERAL
+explain (verbose, costs off)
+select * from
+ (values (0,9998), (1,1000)) v(id,x),
+ lateral (select f1 from int4_tbl
+ where f1 = any (select unique1 from tenk1
+ where unique2 = v.x offset 0)) ss;
+ QUERY PLAN
+----------------------------------------------------------------------
+ Nested Loop
+ Output: "*VALUES*".column1, "*VALUES*".column2, int4_tbl.f1
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1, "*VALUES*".column2
+ -> Nested Loop Semi Join
+ Output: int4_tbl.f1
+ Join Filter: (int4_tbl.f1 = tenk1.unique1)
+ -> Seq Scan on public.int4_tbl
+ Output: int4_tbl.f1
+ -> Materialize
+ Output: tenk1.unique1
+ -> Index Scan using tenk1_unique2 on public.tenk1
+ Output: tenk1.unique1
+ Index Cond: (tenk1.unique2 = "*VALUES*".column2)
+(14 rows)
+
+select * from
+ (values (0,9998), (1,1000)) v(id,x),
+ lateral (select f1 from int4_tbl
+ where f1 = any (select unique1 from tenk1
+ where unique2 = v.x offset 0)) ss;
+ id | x | f1
+----+------+----
+ 0 | 9998 | 0
+(1 row)
+
+-- check proper extParam/allParam handling (this isn't exactly a LATERAL issue,
+-- but we can make the test case much more compact with LATERAL)
+explain (verbose, costs off)
+select * from (values (0), (1)) v(id),
+lateral (select * from int8_tbl t1,
+ lateral (select * from
+ (select * from int8_tbl t2
+ where q1 = any (select q2 from int8_tbl t3
+ where q2 = (select greatest(t1.q1,t2.q2))
+ and (select v.id=0)) offset 0) ss2) ss
+ where t1.q1 = ss.q2) ss0;
+ QUERY PLAN
+-----------------------------------------------------------------
+ Nested Loop
+ Output: "*VALUES*".column1, t1.q1, t1.q2, ss2.q1, ss2.q2
+ -> Seq Scan on public.int8_tbl t1
+ Output: t1.q1, t1.q2
+ -> Nested Loop
+ Output: "*VALUES*".column1, ss2.q1, ss2.q2
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+ -> Subquery Scan on ss2
+ Output: ss2.q1, ss2.q2
+ Filter: (t1.q1 = ss2.q2)
+ -> Seq Scan on public.int8_tbl t2
+ Output: t2.q1, t2.q2
+ Filter: (SubPlan 3)
+ SubPlan 3
+ -> Result
+ Output: t3.q2
+ One-Time Filter: $4
+ InitPlan 1 (returns $2)
+ -> Result
+ Output: GREATEST($0, t2.q2)
+ InitPlan 2 (returns $4)
+ -> Result
+ Output: ($3 = 0)
+ -> Seq Scan on public.int8_tbl t3
+ Output: t3.q1, t3.q2
+ Filter: (t3.q2 = $2)
+(27 rows)
+
+select * from (values (0), (1)) v(id),
+lateral (select * from int8_tbl t1,
+ lateral (select * from
+ (select * from int8_tbl t2
+ where q1 = any (select q2 from int8_tbl t3
+ where q2 = (select greatest(t1.q1,t2.q2))
+ and (select v.id=0)) offset 0) ss2) ss
+ where t1.q1 = ss.q2) ss0;
+ id | q1 | q2 | q1 | q2
+----+------------------+-------------------+------------------+------------------
+ 0 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789
+ 0 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 0 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789
+(3 rows)
+
+-- test some error cases where LATERAL should have been used but wasn't
+select f1,g from int4_tbl a, (select f1 as g) ss;
+ERROR: column "f1" does not exist
+LINE 1: select f1,g from int4_tbl a, (select f1 as g) ss;
+ ^
+HINT: There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
+select f1,g from int4_tbl a, (select a.f1 as g) ss;
+ERROR: invalid reference to FROM-clause entry for table "a"
+LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
+ ^
+HINT: There is an entry for table "a", but it cannot be referenced from this part of the query.
+select f1,g from int4_tbl a cross join (select f1 as g) ss;
+ERROR: column "f1" does not exist
+LINE 1: select f1,g from int4_tbl a cross join (select f1 as g) ss;
+ ^
+HINT: There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
+select f1,g from int4_tbl a cross join (select a.f1 as g) ss;
+ERROR: invalid reference to FROM-clause entry for table "a"
+LINE 1: select f1,g from int4_tbl a cross join (select a.f1 as g) ss...
+ ^
+HINT: There is an entry for table "a", but it cannot be referenced from this part of the query.
+-- SQL:2008 says the left table is in scope but illegal to access here
+select f1,g from int4_tbl a right join lateral generate_series(0, a.f1) g on true;
+ERROR: invalid reference to FROM-clause entry for table "a"
+LINE 1: ... int4_tbl a right join lateral generate_series(0, a.f1) g on...
+ ^
+DETAIL: The combining JOIN type must be INNER or LEFT for a LATERAL reference.
+select f1,g from int4_tbl a full join lateral generate_series(0, a.f1) g on true;
+ERROR: invalid reference to FROM-clause entry for table "a"
+LINE 1: ...m int4_tbl a full join lateral generate_series(0, a.f1) g on...
+ ^
+DETAIL: The combining JOIN type must be INNER or LEFT for a LATERAL reference.
+-- check we complain about ambiguous table references
+select * from
+ int8_tbl x cross join (int4_tbl x cross join lateral (select x.f1) ss);
+ERROR: table reference "x" is ambiguous
+LINE 2: ...cross join (int4_tbl x cross join lateral (select x.f1) ss);
+ ^
+-- LATERAL can be used to put an aggregate into the FROM clause of its query
+select 1 from tenk1 a, lateral (select max(a.unique1) from int4_tbl b) ss;
+ERROR: aggregate functions are not allowed in FROM clause of their own query level
+LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
+ ^
+-- check behavior of LATERAL in UPDATE/DELETE
+create temp table xx1 as select f1 as x1, -f1 as x2 from int4_tbl;
+-- error, can't do this:
+update xx1 set x2 = f1 from (select * from int4_tbl where f1 = x1) ss;
+ERROR: column "x1" does not exist
+LINE 1: ... set x2 = f1 from (select * from int4_tbl where f1 = x1) ss;
+ ^
+HINT: There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query.
+update xx1 set x2 = f1 from (select * from int4_tbl where f1 = xx1.x1) ss;
+ERROR: invalid reference to FROM-clause entry for table "xx1"
+LINE 1: ...t x2 = f1 from (select * from int4_tbl where f1 = xx1.x1) ss...
+ ^
+HINT: There is an entry for table "xx1", but it cannot be referenced from this part of the query.
+-- can't do it even with LATERAL:
+update xx1 set x2 = f1 from lateral (select * from int4_tbl where f1 = x1) ss;
+ERROR: invalid reference to FROM-clause entry for table "xx1"
+LINE 1: ...= f1 from lateral (select * from int4_tbl where f1 = x1) ss;
+ ^
+HINT: There is an entry for table "xx1", but it cannot be referenced from this part of the query.
+-- we might in future allow something like this, but for now it's an error:
+update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1) ss;
+ERROR: table name "xx1" specified more than once
+-- also errors:
+delete from xx1 using (select * from int4_tbl where f1 = x1) ss;
+ERROR: column "x1" does not exist
+LINE 1: ...te from xx1 using (select * from int4_tbl where f1 = x1) ss;
+ ^
+HINT: There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query.
+delete from xx1 using (select * from int4_tbl where f1 = xx1.x1) ss;
+ERROR: invalid reference to FROM-clause entry for table "xx1"
+LINE 1: ...from xx1 using (select * from int4_tbl where f1 = xx1.x1) ss...
+ ^
+HINT: There is an entry for table "xx1", but it cannot be referenced from this part of the query.
+delete from xx1 using lateral (select * from int4_tbl where f1 = x1) ss;
+ERROR: invalid reference to FROM-clause entry for table "xx1"
+LINE 1: ...xx1 using lateral (select * from int4_tbl where f1 = x1) ss;
+ ^
+HINT: There is an entry for table "xx1", but it cannot be referenced from this part of the query.
+--
+-- test LATERAL reference propagation down a multi-level inheritance hierarchy
+-- produced for a multi-level partitioned table hierarchy.
+--
+create table join_pt1 (a int, b int, c varchar) partition by range(a);
+create table join_pt1p1 partition of join_pt1 for values from (0) to (100) partition by range(b);
+create table join_pt1p2 partition of join_pt1 for values from (100) to (200);
+create table join_pt1p1p1 partition of join_pt1p1 for values from (0) to (100);
+insert into join_pt1 values (1, 1, 'x'), (101, 101, 'y');
+create table join_ut1 (a int, b int, c varchar);
+insert into join_ut1 values (101, 101, 'y'), (2, 2, 'z');
+explain (verbose, costs off)
+select t1.b, ss.phv from join_ut1 t1 left join lateral
+ (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv
+ from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss
+ on t1.a = ss.t2a order by t1.a;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Sort
+ Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a
+ Sort Key: t1.a
+ -> Nested Loop Left Join
+ Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a
+ -> Seq Scan on public.join_ut1 t1
+ Output: t1.a, t1.b, t1.c
+ -> Hash Join
+ Output: t2.a, LEAST(t1.a, t2.a, t3.a)
+ Hash Cond: (t3.b = t2.a)
+ -> Seq Scan on public.join_ut1 t3
+ Output: t3.a, t3.b, t3.c
+ -> Hash
+ Output: t2.a
+ -> Append
+ -> Seq Scan on public.join_pt1p1p1 t2_1
+ Output: t2_1.a
+ Filter: (t1.a = t2_1.a)
+ -> Seq Scan on public.join_pt1p2 t2_2
+ Output: t2_2.a
+ Filter: (t1.a = t2_2.a)
+(21 rows)
+
+select t1.b, ss.phv from join_ut1 t1 left join lateral
+ (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv
+ from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss
+ on t1.a = ss.t2a order by t1.a;
+ b | phv
+-----+-----
+ 2 |
+ 101 | 101
+(2 rows)
+
+drop table join_pt1;
+drop table join_ut1;
+--
+-- test estimation behavior with multi-column foreign key and constant qual
+--
+begin;
+create table fkest (x integer, x10 integer, x10b integer, x100 integer);
+insert into fkest select x, x/10, x/10, x/100 from generate_series(1,1000) x;
+create unique index on fkest(x, x10, x100);
+analyze fkest;
+explain (costs off)
+select * from fkest f1
+ join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100)
+ join fkest f3 on f1.x = f3.x
+ where f1.x100 = 2;
+ QUERY PLAN
+-----------------------------------------------------------
+ Nested Loop
+ -> Hash Join
+ Hash Cond: ((f2.x = f1.x) AND (f2.x10b = f1.x10))
+ -> Seq Scan on fkest f2
+ Filter: (x100 = 2)
+ -> Hash
+ -> Seq Scan on fkest f1
+ Filter: (x100 = 2)
+ -> Index Scan using fkest_x_x10_x100_idx on fkest f3
+ Index Cond: (x = f1.x)
+(10 rows)
+
+alter table fkest add constraint fk
+ foreign key (x, x10b, x100) references fkest (x, x10, x100);
+explain (costs off)
+select * from fkest f1
+ join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100)
+ join fkest f3 on f1.x = f3.x
+ where f1.x100 = 2;
+ QUERY PLAN
+-----------------------------------------------------
+ Hash Join
+ Hash Cond: ((f2.x = f1.x) AND (f2.x10b = f1.x10))
+ -> Hash Join
+ Hash Cond: (f3.x = f2.x)
+ -> Seq Scan on fkest f3
+ -> Hash
+ -> Seq Scan on fkest f2
+ Filter: (x100 = 2)
+ -> Hash
+ -> Seq Scan on fkest f1
+ Filter: (x100 = 2)
+(11 rows)
+
+rollback;
+--
+-- test that foreign key join estimation performs sanely for outer joins
+--
+begin;
+create table fkest (a int, b int, c int unique, primary key(a,b));
+create table fkest1 (a int, b int, primary key(a,b));
+insert into fkest select x/10, x%10, x from generate_series(1,1000) x;
+insert into fkest1 select x/10, x%10 from generate_series(1,1000) x;
+alter table fkest1
+ add constraint fkest1_a_b_fkey foreign key (a,b) references fkest;
+analyze fkest;
+analyze fkest1;
+explain (costs off)
+select *
+from fkest f
+ left join fkest1 f1 on f.a = f1.a and f.b = f1.b
+ left join fkest1 f2 on f.a = f2.a and f.b = f2.b
+ left join fkest1 f3 on f.a = f3.a and f.b = f3.b
+where f.c = 1;
+ QUERY PLAN
+------------------------------------------------------------------
+ Nested Loop Left Join
+ -> Nested Loop Left Join
+ -> Nested Loop Left Join
+ -> Index Scan using fkest_c_key on fkest f
+ Index Cond: (c = 1)
+ -> Index Only Scan using fkest1_pkey on fkest1 f1
+ Index Cond: ((a = f.a) AND (b = f.b))
+ -> Index Only Scan using fkest1_pkey on fkest1 f2
+ Index Cond: ((a = f.a) AND (b = f.b))
+ -> Index Only Scan using fkest1_pkey on fkest1 f3
+ Index Cond: ((a = f.a) AND (b = f.b))
+(11 rows)
+
+rollback;
+--
+-- test planner's ability to mark joins as unique
+--
+create table j1 (id int primary key);
+create table j2 (id int primary key);
+create table j3 (id int);
+insert into j1 values(1),(2),(3);
+insert into j2 values(1),(2),(3);
+insert into j3 values(1),(1);
+analyze j1;
+analyze j2;
+analyze j3;
+-- ensure join is properly marked as unique
+explain (verbose, costs off)
+select * from j1 inner join j2 on j1.id = j2.id;
+ QUERY PLAN
+-----------------------------------
+ Hash Join
+ Output: j1.id, j2.id
+ Inner Unique: true
+ Hash Cond: (j1.id = j2.id)
+ -> Seq Scan on public.j1
+ Output: j1.id
+ -> Hash
+ Output: j2.id
+ -> Seq Scan on public.j2
+ Output: j2.id
+(10 rows)
+
+-- ensure join is not unique when not an equi-join
+explain (verbose, costs off)
+select * from j1 inner join j2 on j1.id > j2.id;
+ QUERY PLAN
+-----------------------------------
+ Nested Loop
+ Output: j1.id, j2.id
+ Join Filter: (j1.id > j2.id)
+ -> Seq Scan on public.j1
+ Output: j1.id
+ -> Materialize
+ Output: j2.id
+ -> Seq Scan on public.j2
+ Output: j2.id
+(9 rows)
+
+-- ensure non-unique rel is not chosen as inner
+explain (verbose, costs off)
+select * from j1 inner join j3 on j1.id = j3.id;
+ QUERY PLAN
+-----------------------------------
+ Hash Join
+ Output: j1.id, j3.id
+ Inner Unique: true
+ Hash Cond: (j3.id = j1.id)
+ -> Seq Scan on public.j3
+ Output: j3.id
+ -> Hash
+ Output: j1.id
+ -> Seq Scan on public.j1
+ Output: j1.id
+(10 rows)
+
+-- ensure left join is marked as unique
+explain (verbose, costs off)
+select * from j1 left join j2 on j1.id = j2.id;
+ QUERY PLAN
+-----------------------------------
+ Hash Left Join
+ Output: j1.id, j2.id
+ Inner Unique: true
+ Hash Cond: (j1.id = j2.id)
+ -> Seq Scan on public.j1
+ Output: j1.id
+ -> Hash
+ Output: j2.id
+ -> Seq Scan on public.j2
+ Output: j2.id
+(10 rows)
+
+-- ensure right join is marked as unique
+explain (verbose, costs off)
+select * from j1 right join j2 on j1.id = j2.id;
+ QUERY PLAN
+-----------------------------------
+ Hash Left Join
+ Output: j1.id, j2.id
+ Inner Unique: true
+ Hash Cond: (j2.id = j1.id)
+ -> Seq Scan on public.j2
+ Output: j2.id
+ -> Hash
+ Output: j1.id
+ -> Seq Scan on public.j1
+ Output: j1.id
+(10 rows)
+
+-- ensure full join is marked as unique
+explain (verbose, costs off)
+select * from j1 full join j2 on j1.id = j2.id;
+ QUERY PLAN
+-----------------------------------
+ Hash Full Join
+ Output: j1.id, j2.id
+ Inner Unique: true
+ Hash Cond: (j1.id = j2.id)
+ -> Seq Scan on public.j1
+ Output: j1.id
+ -> Hash
+ Output: j2.id
+ -> Seq Scan on public.j2
+ Output: j2.id
+(10 rows)
+
+-- a clauseless (cross) join can't be unique
+explain (verbose, costs off)
+select * from j1 cross join j2;
+ QUERY PLAN
+-----------------------------------
+ Nested Loop
+ Output: j1.id, j2.id
+ -> Seq Scan on public.j1
+ Output: j1.id
+ -> Materialize
+ Output: j2.id
+ -> Seq Scan on public.j2
+ Output: j2.id
+(8 rows)
+
+-- ensure a natural join is marked as unique
+explain (verbose, costs off)
+select * from j1 natural join j2;
+ QUERY PLAN
+-----------------------------------
+ Hash Join
+ Output: j1.id
+ Inner Unique: true
+ Hash Cond: (j1.id = j2.id)
+ -> Seq Scan on public.j1
+ Output: j1.id
+ -> Hash
+ Output: j2.id
+ -> Seq Scan on public.j2
+ Output: j2.id
+(10 rows)
+
+-- ensure a distinct clause allows the inner to become unique
+explain (verbose, costs off)
+select * from j1
+inner join (select distinct id from j3) j3 on j1.id = j3.id;
+ QUERY PLAN
+-----------------------------------------
+ Nested Loop
+ Output: j1.id, j3.id
+ Inner Unique: true
+ Join Filter: (j1.id = j3.id)
+ -> Unique
+ Output: j3.id
+ -> Sort
+ Output: j3.id
+ Sort Key: j3.id
+ -> Seq Scan on public.j3
+ Output: j3.id
+ -> Seq Scan on public.j1
+ Output: j1.id
+(13 rows)
+
+-- ensure group by clause allows the inner to become unique
+explain (verbose, costs off)
+select * from j1
+inner join (select id from j3 group by id) j3 on j1.id = j3.id;
+ QUERY PLAN
+-----------------------------------------
+ Nested Loop
+ Output: j1.id, j3.id
+ Inner Unique: true
+ Join Filter: (j1.id = j3.id)
+ -> Group
+ Output: j3.id
+ Group Key: j3.id
+ -> Sort
+ Output: j3.id
+ Sort Key: j3.id
+ -> Seq Scan on public.j3
+ Output: j3.id
+ -> Seq Scan on public.j1
+ Output: j1.id
+(14 rows)
+
+drop table j1;
+drop table j2;
+drop table j3;
+-- test more complex permutations of unique joins
+create table j1 (id1 int, id2 int, primary key(id1,id2));
+create table j2 (id1 int, id2 int, primary key(id1,id2));
+create table j3 (id1 int, id2 int, primary key(id1,id2));
+insert into j1 values(1,1),(1,2);
+insert into j2 values(1,1);
+insert into j3 values(1,1);
+analyze j1;
+analyze j2;
+analyze j3;
+-- ensure there's no unique join when not all columns which are part of the
+-- unique index are seen in the join clause
+explain (verbose, costs off)
+select * from j1
+inner join j2 on j1.id1 = j2.id1;
+ QUERY PLAN
+------------------------------------------
+ Nested Loop
+ Output: j1.id1, j1.id2, j2.id1, j2.id2
+ Join Filter: (j1.id1 = j2.id1)
+ -> Seq Scan on public.j2
+ Output: j2.id1, j2.id2
+ -> Seq Scan on public.j1
+ Output: j1.id1, j1.id2
+(7 rows)
+
+-- ensure proper unique detection with multiple join quals
+explain (verbose, costs off)
+select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2;
+ QUERY PLAN
+----------------------------------------------------------
+ Nested Loop
+ Output: j1.id1, j1.id2, j2.id1, j2.id2
+ Inner Unique: true
+ Join Filter: ((j1.id1 = j2.id1) AND (j1.id2 = j2.id2))
+ -> Seq Scan on public.j2
+ Output: j2.id1, j2.id2
+ -> Seq Scan on public.j1
+ Output: j1.id1, j1.id2
+(8 rows)
+
+-- ensure we don't detect the join to be unique when quals are not part of the
+-- join condition
+explain (verbose, costs off)
+select * from j1
+inner join j2 on j1.id1 = j2.id1 where j1.id2 = 1;
+ QUERY PLAN
+------------------------------------------
+ Nested Loop
+ Output: j1.id1, j1.id2, j2.id1, j2.id2
+ Join Filter: (j1.id1 = j2.id1)
+ -> Seq Scan on public.j1
+ Output: j1.id1, j1.id2
+ Filter: (j1.id2 = 1)
+ -> Seq Scan on public.j2
+ Output: j2.id1, j2.id2
+(8 rows)
+
+-- as above, but for left joins.
+explain (verbose, costs off)
+select * from j1
+left join j2 on j1.id1 = j2.id1 where j1.id2 = 1;
+ QUERY PLAN
+------------------------------------------
+ Nested Loop Left Join
+ Output: j1.id1, j1.id2, j2.id1, j2.id2
+ Join Filter: (j1.id1 = j2.id1)
+ -> Seq Scan on public.j1
+ Output: j1.id1, j1.id2
+ Filter: (j1.id2 = 1)
+ -> Seq Scan on public.j2
+ Output: j2.id1, j2.id2
+(8 rows)
+
+-- validate logic in merge joins which skips mark and restore.
+-- it should only do this if all quals which were used to detect the unique
+-- are present as join quals, and not plain quals.
+set enable_nestloop to 0;
+set enable_hashjoin to 0;
+set enable_sort to 0;
+-- create indexes that will be preferred over the PKs to perform the join
+create index j1_id1_idx on j1 (id1) where id1 % 1000 = 1;
+create index j2_id1_idx on j2 (id1) where id1 % 1000 = 1;
+-- need an additional row in j2, if we want j2_id1_idx to be preferred
+insert into j2 values(1,2);
+analyze j2;
+explain (costs off) select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
+where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1;
+ QUERY PLAN
+-----------------------------------------
+ Merge Join
+ Merge Cond: (j1.id1 = j2.id1)
+ Join Filter: (j1.id2 = j2.id2)
+ -> Index Scan using j1_id1_idx on j1
+ -> Index Scan using j2_id1_idx on j2
+(5 rows)
+
+select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
+where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1;
+ id1 | id2 | id1 | id2
+-----+-----+-----+-----
+ 1 | 1 | 1 | 1
+ 1 | 2 | 1 | 2
+(2 rows)
+
+-- Exercise array keys mark/restore B-Tree code
+explain (costs off) select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
+where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]);
+ QUERY PLAN
+----------------------------------------------------
+ Merge Join
+ Merge Cond: (j1.id1 = j2.id1)
+ Join Filter: (j1.id2 = j2.id2)
+ -> Index Scan using j1_id1_idx on j1
+ -> Index Scan using j2_id1_idx on j2
+ Index Cond: (id1 = ANY ('{1}'::integer[]))
+(6 rows)
+
+select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
+where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]);
+ id1 | id2 | id1 | id2
+-----+-----+-----+-----
+ 1 | 1 | 1 | 1
+ 1 | 2 | 1 | 2
+(2 rows)
+
+-- Exercise array keys "find extreme element" B-Tree code
+explain (costs off) select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
+where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]);
+ QUERY PLAN
+-------------------------------------------------------
+ Merge Join
+ Merge Cond: (j1.id1 = j2.id1)
+ Join Filter: (j1.id2 = j2.id2)
+ -> Index Scan using j1_id1_idx on j1
+ -> Index Only Scan using j2_pkey on j2
+ Index Cond: (id1 >= ANY ('{1,5}'::integer[]))
+ Filter: ((id1 % 1000) = 1)
+(7 rows)
+
+select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
+where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]);
+ id1 | id2 | id1 | id2
+-----+-----+-----+-----
+ 1 | 1 | 1 | 1
+ 1 | 2 | 1 | 2
+(2 rows)
+
+reset enable_nestloop;
+reset enable_hashjoin;
+reset enable_sort;
+drop table j1;
+drop table j2;
+drop table j3;
+-- check that semijoin inner is not seen as unique for a portion of the outerrel
+explain (verbose, costs off)
+select t1.unique1, t2.hundred
+from onek t1, tenk1 t2
+where exists (select 1 from tenk1 t3
+ where t3.thousand = t1.unique1 and t3.tenthous = t2.hundred)
+ and t1.unique1 < 1;
+ QUERY PLAN
+---------------------------------------------------------------------------------
+ Nested Loop
+ Output: t1.unique1, t2.hundred
+ -> Hash Join
+ Output: t1.unique1, t3.tenthous
+ Hash Cond: (t3.thousand = t1.unique1)
+ -> HashAggregate
+ Output: t3.thousand, t3.tenthous
+ Group Key: t3.thousand, t3.tenthous
+ -> Index Only Scan using tenk1_thous_tenthous on public.tenk1 t3
+ Output: t3.thousand, t3.tenthous
+ -> Hash
+ Output: t1.unique1
+ -> Index Only Scan using onek_unique1 on public.onek t1
+ Output: t1.unique1
+ Index Cond: (t1.unique1 < 1)
+ -> Index Only Scan using tenk1_hundred on public.tenk1 t2
+ Output: t2.hundred
+ Index Cond: (t2.hundred = t3.tenthous)
+(18 rows)
+
+-- ... unless it actually is unique
+create table j3 as select unique1, tenthous from onek;
+vacuum analyze j3;
+create unique index on j3(unique1, tenthous);
+explain (verbose, costs off)
+select t1.unique1, t2.hundred
+from onek t1, tenk1 t2
+where exists (select 1 from j3
+ where j3.unique1 = t1.unique1 and j3.tenthous = t2.hundred)
+ and t1.unique1 < 1;
+ QUERY PLAN
+------------------------------------------------------------------------
+ Nested Loop
+ Output: t1.unique1, t2.hundred
+ -> Nested Loop
+ Output: t1.unique1, j3.tenthous
+ -> Index Only Scan using onek_unique1 on public.onek t1
+ Output: t1.unique1
+ Index Cond: (t1.unique1 < 1)
+ -> Index Only Scan using j3_unique1_tenthous_idx on public.j3
+ Output: j3.unique1, j3.tenthous
+ Index Cond: (j3.unique1 = t1.unique1)
+ -> Index Only Scan using tenk1_hundred on public.tenk1 t2
+ Output: t2.hundred
+ Index Cond: (t2.hundred = j3.tenthous)
+(13 rows)
+
+drop table j3;
diff --git a/src/test/regress/expected/join_hash.out b/src/test/regress/expected/join_hash.out
new file mode 100644
index 0000000..3a91c14
--- /dev/null
+++ b/src/test/regress/expected/join_hash.out
@@ -0,0 +1,1015 @@
+--
+-- exercises for the hash join code
+--
+begin;
+set local min_parallel_table_scan_size = 0;
+set local parallel_setup_cost = 0;
+set local enable_hashjoin = on;
+-- Extract bucket and batch counts from an explain analyze plan. In
+-- general we can't make assertions about how many batches (or
+-- buckets) will be required because it can vary, but we can in some
+-- special cases and we can check for growth.
+create or replace function find_hash(node json)
+returns json language plpgsql
+as
+$$
+declare
+ x json;
+ child json;
+begin
+ if node->>'Node Type' = 'Hash' then
+ return node;
+ else
+ for child in select json_array_elements(node->'Plans')
+ loop
+ x := find_hash(child);
+ if x is not null then
+ return x;
+ end if;
+ end loop;
+ return null;
+ end if;
+end;
+$$;
+create or replace function hash_join_batches(query text)
+returns table (original int, final int) language plpgsql
+as
+$$
+declare
+ whole_plan json;
+ hash_node json;
+begin
+ for whole_plan in
+ execute 'explain (analyze, format ''json'') ' || query
+ loop
+ hash_node := find_hash(json_extract_path(whole_plan, '0', 'Plan'));
+ original := hash_node->>'Original Hash Batches';
+ final := hash_node->>'Hash Batches';
+ return next;
+ end loop;
+end;
+$$;
+-- Make a simple relation with well distributed keys and correctly
+-- estimated size.
+create table simple as
+ select generate_series(1, 20000) AS id, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
+alter table simple set (parallel_workers = 2);
+analyze simple;
+-- Make a relation whose size we will under-estimate. We want stats
+-- to say 1000 rows, but actually there are 20,000 rows.
+create table bigger_than_it_looks as
+ select generate_series(1, 20000) as id, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
+alter table bigger_than_it_looks set (autovacuum_enabled = 'false');
+alter table bigger_than_it_looks set (parallel_workers = 2);
+analyze bigger_than_it_looks;
+update pg_class set reltuples = 1000 where relname = 'bigger_than_it_looks';
+-- Make a relation whose size we underestimate and that also has a
+-- kind of skew that breaks our batching scheme. We want stats to say
+-- 2 rows, but actually there are 20,000 rows with the same key.
+create table extremely_skewed (id int, t text);
+alter table extremely_skewed set (autovacuum_enabled = 'false');
+alter table extremely_skewed set (parallel_workers = 2);
+analyze extremely_skewed;
+insert into extremely_skewed
+ select 42 as id, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+ from generate_series(1, 20000);
+update pg_class
+ set reltuples = 2, relpages = pg_relation_size('extremely_skewed') / 8192
+ where relname = 'extremely_skewed';
+-- Make a relation with a couple of enormous tuples.
+create table wide as select generate_series(1, 2) as id, rpad('', 320000, 'x') as t;
+alter table wide set (parallel_workers = 2);
+-- The "optimal" case: the hash table fits in memory; we plan for 1
+-- batch, we stick to that number, and peak memory usage stays within
+-- our work_mem budget
+-- non-parallel
+savepoint settings;
+set local max_parallel_workers_per_gather = 0;
+set local work_mem = '4MB';
+explain (costs off)
+ select count(*) from simple r join simple s using (id);
+ QUERY PLAN
+----------------------------------------
+ Aggregate
+ -> Hash Join
+ Hash Cond: (r.id = s.id)
+ -> Seq Scan on simple r
+ -> Hash
+ -> Seq Scan on simple s
+(6 rows)
+
+select count(*) from simple r join simple s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+ initially_multibatch | increased_batches
+----------------------+-------------------
+ f | f
+(1 row)
+
+rollback to settings;
+-- parallel with parallel-oblivious hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '4MB';
+set local enable_parallel_hash = off;
+explain (costs off)
+ select count(*) from simple r join simple s using (id);
+ QUERY PLAN
+-------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Hash Join
+ Hash Cond: (r.id = s.id)
+ -> Parallel Seq Scan on simple r
+ -> Hash
+ -> Seq Scan on simple s
+(9 rows)
+
+select count(*) from simple r join simple s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+ initially_multibatch | increased_batches
+----------------------+-------------------
+ f | f
+(1 row)
+
+rollback to settings;
+-- parallel with parallel-aware hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '4MB';
+set local enable_parallel_hash = on;
+explain (costs off)
+ select count(*) from simple r join simple s using (id);
+ QUERY PLAN
+-------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Hash Join
+ Hash Cond: (r.id = s.id)
+ -> Parallel Seq Scan on simple r
+ -> Parallel Hash
+ -> Parallel Seq Scan on simple s
+(9 rows)
+
+select count(*) from simple r join simple s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+ initially_multibatch | increased_batches
+----------------------+-------------------
+ f | f
+(1 row)
+
+rollback to settings;
+-- The "good" case: batches required, but we plan the right number; we
+-- plan for some number of batches, and we stick to that number, and
+-- peak memory usage says within our work_mem budget
+-- non-parallel
+savepoint settings;
+set local max_parallel_workers_per_gather = 0;
+set local work_mem = '128kB';
+explain (costs off)
+ select count(*) from simple r join simple s using (id);
+ QUERY PLAN
+----------------------------------------
+ Aggregate
+ -> Hash Join
+ Hash Cond: (r.id = s.id)
+ -> Seq Scan on simple r
+ -> Hash
+ -> Seq Scan on simple s
+(6 rows)
+
+select count(*) from simple r join simple s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+ initially_multibatch | increased_batches
+----------------------+-------------------
+ t | f
+(1 row)
+
+rollback to settings;
+-- parallel with parallel-oblivious hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '128kB';
+set local enable_parallel_hash = off;
+explain (costs off)
+ select count(*) from simple r join simple s using (id);
+ QUERY PLAN
+-------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Hash Join
+ Hash Cond: (r.id = s.id)
+ -> Parallel Seq Scan on simple r
+ -> Hash
+ -> Seq Scan on simple s
+(9 rows)
+
+select count(*) from simple r join simple s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+ initially_multibatch | increased_batches
+----------------------+-------------------
+ t | f
+(1 row)
+
+rollback to settings;
+-- parallel with parallel-aware hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '192kB';
+set local enable_parallel_hash = on;
+explain (costs off)
+ select count(*) from simple r join simple s using (id);
+ QUERY PLAN
+-------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Hash Join
+ Hash Cond: (r.id = s.id)
+ -> Parallel Seq Scan on simple r
+ -> Parallel Hash
+ -> Parallel Seq Scan on simple s
+(9 rows)
+
+select count(*) from simple r join simple s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+ initially_multibatch | increased_batches
+----------------------+-------------------
+ t | f
+(1 row)
+
+rollback to settings;
+-- The "bad" case: during execution we need to increase number of
+-- batches; in this case we plan for 1 batch, and increase at least a
+-- couple of times, and peak memory usage stays within our work_mem
+-- budget
+-- non-parallel
+savepoint settings;
+set local max_parallel_workers_per_gather = 0;
+set local work_mem = '128kB';
+explain (costs off)
+ select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ -> Hash Join
+ Hash Cond: (r.id = s.id)
+ -> Seq Scan on simple r
+ -> Hash
+ -> Seq Scan on bigger_than_it_looks s
+(6 rows)
+
+select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id);
+ count
+-------
+ 20000
+(1 row)
+
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id);
+$$);
+ initially_multibatch | increased_batches
+----------------------+-------------------
+ f | t
+(1 row)
+
+rollback to settings;
+-- parallel with parallel-oblivious hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '128kB';
+set local enable_parallel_hash = off;
+explain (costs off)
+ select count(*) from simple r join bigger_than_it_looks s using (id);
+ QUERY PLAN
+------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Hash Join
+ Hash Cond: (r.id = s.id)
+ -> Parallel Seq Scan on simple r
+ -> Hash
+ -> Seq Scan on bigger_than_it_looks s
+(9 rows)
+
+select count(*) from simple r join bigger_than_it_looks s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join bigger_than_it_looks s using (id);
+$$);
+ initially_multibatch | increased_batches
+----------------------+-------------------
+ f | t
+(1 row)
+
+rollback to settings;
+-- parallel with parallel-aware hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 1;
+set local work_mem = '192kB';
+set local enable_parallel_hash = on;
+explain (costs off)
+ select count(*) from simple r join bigger_than_it_looks s using (id);
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 1
+ -> Partial Aggregate
+ -> Parallel Hash Join
+ Hash Cond: (r.id = s.id)
+ -> Parallel Seq Scan on simple r
+ -> Parallel Hash
+ -> Parallel Seq Scan on bigger_than_it_looks s
+(9 rows)
+
+select count(*) from simple r join bigger_than_it_looks s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join bigger_than_it_looks s using (id);
+$$);
+ initially_multibatch | increased_batches
+----------------------+-------------------
+ f | t
+(1 row)
+
+rollback to settings;
+-- The "ugly" case: increasing the number of batches during execution
+-- doesn't help, so stop trying to fit in work_mem and hope for the
+-- best; in this case we plan for 1 batch, increases just once and
+-- then stop increasing because that didn't help at all, so we blow
+-- right through the work_mem budget and hope for the best...
+-- non-parallel
+savepoint settings;
+set local max_parallel_workers_per_gather = 0;
+set local work_mem = '128kB';
+explain (costs off)
+ select count(*) from simple r join extremely_skewed s using (id);
+ QUERY PLAN
+--------------------------------------------------
+ Aggregate
+ -> Hash Join
+ Hash Cond: (r.id = s.id)
+ -> Seq Scan on simple r
+ -> Hash
+ -> Seq Scan on extremely_skewed s
+(6 rows)
+
+select count(*) from simple r join extremely_skewed s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+select * from hash_join_batches(
+$$
+ select count(*) from simple r join extremely_skewed s using (id);
+$$);
+ original | final
+----------+-------
+ 1 | 2
+(1 row)
+
+rollback to settings;
+-- parallel with parallel-oblivious hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '128kB';
+set local enable_parallel_hash = off;
+explain (costs off)
+ select count(*) from simple r join extremely_skewed s using (id);
+ QUERY PLAN
+--------------------------------------------------------
+ Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Hash Join
+ Hash Cond: (r.id = s.id)
+ -> Parallel Seq Scan on simple r
+ -> Hash
+ -> Seq Scan on extremely_skewed s
+(8 rows)
+
+select count(*) from simple r join extremely_skewed s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+select * from hash_join_batches(
+$$
+ select count(*) from simple r join extremely_skewed s using (id);
+$$);
+ original | final
+----------+-------
+ 1 | 2
+(1 row)
+
+rollback to settings;
+-- parallel with parallel-aware hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 1;
+set local work_mem = '128kB';
+set local enable_parallel_hash = on;
+explain (costs off)
+ select count(*) from simple r join extremely_skewed s using (id);
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 1
+ -> Partial Aggregate
+ -> Parallel Hash Join
+ Hash Cond: (r.id = s.id)
+ -> Parallel Seq Scan on simple r
+ -> Parallel Hash
+ -> Parallel Seq Scan on extremely_skewed s
+(9 rows)
+
+select count(*) from simple r join extremely_skewed s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+select * from hash_join_batches(
+$$
+ select count(*) from simple r join extremely_skewed s using (id);
+$$);
+ original | final
+----------+-------
+ 1 | 4
+(1 row)
+
+rollback to settings;
+-- A couple of other hash join tests unrelated to work_mem management.
+-- Check that EXPLAIN ANALYZE has data even if the leader doesn't participate
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '4MB';
+set local parallel_leader_participation = off;
+select * from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+ original | final
+----------+-------
+ 1 | 1
+(1 row)
+
+rollback to settings;
+-- Exercise rescans. We'll turn off parallel_leader_participation so
+-- that we can check that instrumentation comes back correctly.
+create table join_foo as select generate_series(1, 3) as id, 'xxxxx'::text as t;
+alter table join_foo set (parallel_workers = 0);
+create table join_bar as select generate_series(1, 10000) as id, 'xxxxx'::text as t;
+alter table join_bar set (parallel_workers = 2);
+-- multi-batch with rescan, parallel-oblivious
+savepoint settings;
+set enable_parallel_hash = off;
+set parallel_leader_participation = off;
+set min_parallel_table_scan_size = 0;
+set parallel_setup_cost = 0;
+set parallel_tuple_cost = 0;
+set max_parallel_workers_per_gather = 2;
+set enable_material = off;
+set enable_mergejoin = off;
+set work_mem = '64kB';
+explain (costs off)
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Aggregate
+ -> Nested Loop Left Join
+ Join Filter: ((join_foo.id < (b1.id + 1)) AND (join_foo.id > (b1.id - 1)))
+ -> Seq Scan on join_foo
+ -> Gather
+ Workers Planned: 2
+ -> Hash Join
+ Hash Cond: (b1.id = b2.id)
+ -> Parallel Seq Scan on join_bar b1
+ -> Hash
+ -> Seq Scan on join_bar b2
+(11 rows)
+
+select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+ count
+-------
+ 3
+(1 row)
+
+select final > 1 as multibatch
+ from hash_join_batches(
+$$
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+$$);
+ multibatch
+------------
+ t
+(1 row)
+
+rollback to settings;
+-- single-batch with rescan, parallel-oblivious
+savepoint settings;
+set enable_parallel_hash = off;
+set parallel_leader_participation = off;
+set min_parallel_table_scan_size = 0;
+set parallel_setup_cost = 0;
+set parallel_tuple_cost = 0;
+set max_parallel_workers_per_gather = 2;
+set enable_material = off;
+set enable_mergejoin = off;
+set work_mem = '4MB';
+explain (costs off)
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Aggregate
+ -> Nested Loop Left Join
+ Join Filter: ((join_foo.id < (b1.id + 1)) AND (join_foo.id > (b1.id - 1)))
+ -> Seq Scan on join_foo
+ -> Gather
+ Workers Planned: 2
+ -> Hash Join
+ Hash Cond: (b1.id = b2.id)
+ -> Parallel Seq Scan on join_bar b1
+ -> Hash
+ -> Seq Scan on join_bar b2
+(11 rows)
+
+select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+ count
+-------
+ 3
+(1 row)
+
+select final > 1 as multibatch
+ from hash_join_batches(
+$$
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+$$);
+ multibatch
+------------
+ f
+(1 row)
+
+rollback to settings;
+-- multi-batch with rescan, parallel-aware
+savepoint settings;
+set enable_parallel_hash = on;
+set parallel_leader_participation = off;
+set min_parallel_table_scan_size = 0;
+set parallel_setup_cost = 0;
+set parallel_tuple_cost = 0;
+set max_parallel_workers_per_gather = 2;
+set enable_material = off;
+set enable_mergejoin = off;
+set work_mem = '64kB';
+explain (costs off)
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Aggregate
+ -> Nested Loop Left Join
+ Join Filter: ((join_foo.id < (b1.id + 1)) AND (join_foo.id > (b1.id - 1)))
+ -> Seq Scan on join_foo
+ -> Gather
+ Workers Planned: 2
+ -> Parallel Hash Join
+ Hash Cond: (b1.id = b2.id)
+ -> Parallel Seq Scan on join_bar b1
+ -> Parallel Hash
+ -> Parallel Seq Scan on join_bar b2
+(11 rows)
+
+select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+ count
+-------
+ 3
+(1 row)
+
+select final > 1 as multibatch
+ from hash_join_batches(
+$$
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+$$);
+ multibatch
+------------
+ t
+(1 row)
+
+rollback to settings;
+-- single-batch with rescan, parallel-aware
+savepoint settings;
+set enable_parallel_hash = on;
+set parallel_leader_participation = off;
+set min_parallel_table_scan_size = 0;
+set parallel_setup_cost = 0;
+set parallel_tuple_cost = 0;
+set max_parallel_workers_per_gather = 2;
+set enable_material = off;
+set enable_mergejoin = off;
+set work_mem = '4MB';
+explain (costs off)
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Aggregate
+ -> Nested Loop Left Join
+ Join Filter: ((join_foo.id < (b1.id + 1)) AND (join_foo.id > (b1.id - 1)))
+ -> Seq Scan on join_foo
+ -> Gather
+ Workers Planned: 2
+ -> Parallel Hash Join
+ Hash Cond: (b1.id = b2.id)
+ -> Parallel Seq Scan on join_bar b1
+ -> Parallel Hash
+ -> Parallel Seq Scan on join_bar b2
+(11 rows)
+
+select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+ count
+-------
+ 3
+(1 row)
+
+select final > 1 as multibatch
+ from hash_join_batches(
+$$
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+$$);
+ multibatch
+------------
+ f
+(1 row)
+
+rollback to settings;
+-- A full outer join where every record is matched.
+-- non-parallel
+savepoint settings;
+set local max_parallel_workers_per_gather = 0;
+explain (costs off)
+ select count(*) from simple r full outer join simple s using (id);
+ QUERY PLAN
+----------------------------------------
+ Aggregate
+ -> Hash Full Join
+ Hash Cond: (r.id = s.id)
+ -> Seq Scan on simple r
+ -> Hash
+ -> Seq Scan on simple s
+(6 rows)
+
+select count(*) from simple r full outer join simple s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+rollback to settings;
+-- parallelism not possible with parallel-oblivious outer hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+explain (costs off)
+ select count(*) from simple r full outer join simple s using (id);
+ QUERY PLAN
+----------------------------------------
+ Aggregate
+ -> Hash Full Join
+ Hash Cond: (r.id = s.id)
+ -> Seq Scan on simple r
+ -> Hash
+ -> Seq Scan on simple s
+(6 rows)
+
+select count(*) from simple r full outer join simple s using (id);
+ count
+-------
+ 20000
+(1 row)
+
+rollback to settings;
+-- An full outer join where every record is not matched.
+-- non-parallel
+savepoint settings;
+set local max_parallel_workers_per_gather = 0;
+explain (costs off)
+ select count(*) from simple r full outer join simple s on (r.id = 0 - s.id);
+ QUERY PLAN
+----------------------------------------
+ Aggregate
+ -> Hash Full Join
+ Hash Cond: ((0 - s.id) = r.id)
+ -> Seq Scan on simple s
+ -> Hash
+ -> Seq Scan on simple r
+(6 rows)
+
+select count(*) from simple r full outer join simple s on (r.id = 0 - s.id);
+ count
+-------
+ 40000
+(1 row)
+
+rollback to settings;
+-- parallelism not possible with parallel-oblivious outer hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+explain (costs off)
+ select count(*) from simple r full outer join simple s on (r.id = 0 - s.id);
+ QUERY PLAN
+----------------------------------------
+ Aggregate
+ -> Hash Full Join
+ Hash Cond: ((0 - s.id) = r.id)
+ -> Seq Scan on simple s
+ -> Hash
+ -> Seq Scan on simple r
+(6 rows)
+
+select count(*) from simple r full outer join simple s on (r.id = 0 - s.id);
+ count
+-------
+ 40000
+(1 row)
+
+rollback to settings;
+-- exercise special code paths for huge tuples (note use of non-strict
+-- expression and left join required to get the detoasted tuple into
+-- the hash table)
+-- parallel with parallel-aware hash join (hits ExecParallelHashLoadTuple and
+-- sts_puttuple oversized tuple cases because it's multi-batch)
+savepoint settings;
+set max_parallel_workers_per_gather = 2;
+set enable_parallel_hash = on;
+set work_mem = '128kB';
+explain (costs off)
+ select length(max(s.t))
+ from wide left join (select id, coalesce(t, '') || '' as t from wide) s using (id);
+ QUERY PLAN
+----------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Hash Left Join
+ Hash Cond: (wide.id = wide_1.id)
+ -> Parallel Seq Scan on wide
+ -> Parallel Hash
+ -> Parallel Seq Scan on wide wide_1
+(9 rows)
+
+select length(max(s.t))
+from wide left join (select id, coalesce(t, '') || '' as t from wide) s using (id);
+ length
+--------
+ 320000
+(1 row)
+
+select final > 1 as multibatch
+ from hash_join_batches(
+$$
+ select length(max(s.t))
+ from wide left join (select id, coalesce(t, '') || '' as t from wide) s using (id);
+$$);
+ multibatch
+------------
+ t
+(1 row)
+
+rollback to settings;
+rollback;
+-- Verify that hash key expressions reference the correct
+-- nodes. Hashjoin's hashkeys need to reference its outer plan, Hash's
+-- need to reference Hash's outer plan (which is below HashJoin's
+-- inner plan). It's not trivial to verify that the references are
+-- correct (we don't display the hashkeys themselves), but if the
+-- hashkeys contain subplan references, those will be displayed. Force
+-- subplans to appear just about everywhere.
+--
+-- Bug report:
+-- https://www.postgresql.org/message-id/CAPpHfdvGVegF_TKKRiBrSmatJL2dR9uwFCuR%2BteQ_8tEXU8mxg%40mail.gmail.com
+--
+BEGIN;
+SET LOCAL enable_sort = OFF; -- avoid mergejoins
+SET LOCAL from_collapse_limit = 1; -- allows easy changing of join order
+CREATE TABLE hjtest_1 (a text, b int, id int, c bool);
+CREATE TABLE hjtest_2 (a bool, id int, b text, c int);
+INSERT INTO hjtest_1(a, b, id, c) VALUES ('text', 2, 1, false); -- matches
+INSERT INTO hjtest_1(a, b, id, c) VALUES ('text', 1, 2, false); -- fails id join condition
+INSERT INTO hjtest_1(a, b, id, c) VALUES ('text', 20, 1, false); -- fails < 50
+INSERT INTO hjtest_1(a, b, id, c) VALUES ('text', 1, 1, false); -- fails (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5)
+INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 1, 'another', 2); -- matches
+INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 3, 'another', 7); -- fails id join condition
+INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 1, 'another', 90); -- fails < 55
+INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 1, 'another', 3); -- fails (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5)
+INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 1, 'text', 1); -- fails hjtest_1.a <> hjtest_2.b;
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT hjtest_1.a a1, hjtest_2.a a2,hjtest_1.tableoid::regclass t1, hjtest_2.tableoid::regclass t2
+FROM hjtest_1, hjtest_2
+WHERE
+ hjtest_1.id = (SELECT 1 WHERE hjtest_2.id = 1)
+ AND (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5)
+ AND (SELECT hjtest_1.b * 5) < 50
+ AND (SELECT hjtest_2.c * 5) < 55
+ AND hjtest_1.a <> hjtest_2.b;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------
+ Hash Join
+ Output: hjtest_1.a, hjtest_2.a, (hjtest_1.tableoid)::regclass, (hjtest_2.tableoid)::regclass
+ Hash Cond: ((hjtest_1.id = (SubPlan 1)) AND ((SubPlan 2) = (SubPlan 3)))
+ Join Filter: (hjtest_1.a <> hjtest_2.b)
+ -> Seq Scan on public.hjtest_1
+ Output: hjtest_1.a, hjtest_1.tableoid, hjtest_1.id, hjtest_1.b
+ Filter: ((SubPlan 4) < 50)
+ SubPlan 4
+ -> Result
+ Output: (hjtest_1.b * 5)
+ -> Hash
+ Output: hjtest_2.a, hjtest_2.tableoid, hjtest_2.id, hjtest_2.c, hjtest_2.b
+ -> Seq Scan on public.hjtest_2
+ Output: hjtest_2.a, hjtest_2.tableoid, hjtest_2.id, hjtest_2.c, hjtest_2.b
+ Filter: ((SubPlan 5) < 55)
+ SubPlan 5
+ -> Result
+ Output: (hjtest_2.c * 5)
+ SubPlan 1
+ -> Result
+ Output: 1
+ One-Time Filter: (hjtest_2.id = 1)
+ SubPlan 3
+ -> Result
+ Output: (hjtest_2.c * 5)
+ SubPlan 2
+ -> Result
+ Output: (hjtest_1.b * 5)
+(28 rows)
+
+SELECT hjtest_1.a a1, hjtest_2.a a2,hjtest_1.tableoid::regclass t1, hjtest_2.tableoid::regclass t2
+FROM hjtest_1, hjtest_2
+WHERE
+ hjtest_1.id = (SELECT 1 WHERE hjtest_2.id = 1)
+ AND (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5)
+ AND (SELECT hjtest_1.b * 5) < 50
+ AND (SELECT hjtest_2.c * 5) < 55
+ AND hjtest_1.a <> hjtest_2.b;
+ a1 | a2 | t1 | t2
+------+----+----------+----------
+ text | t | hjtest_1 | hjtest_2
+(1 row)
+
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT hjtest_1.a a1, hjtest_2.a a2,hjtest_1.tableoid::regclass t1, hjtest_2.tableoid::regclass t2
+FROM hjtest_2, hjtest_1
+WHERE
+ hjtest_1.id = (SELECT 1 WHERE hjtest_2.id = 1)
+ AND (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5)
+ AND (SELECT hjtest_1.b * 5) < 50
+ AND (SELECT hjtest_2.c * 5) < 55
+ AND hjtest_1.a <> hjtest_2.b;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------
+ Hash Join
+ Output: hjtest_1.a, hjtest_2.a, (hjtest_1.tableoid)::regclass, (hjtest_2.tableoid)::regclass
+ Hash Cond: (((SubPlan 1) = hjtest_1.id) AND ((SubPlan 3) = (SubPlan 2)))
+ Join Filter: (hjtest_1.a <> hjtest_2.b)
+ -> Seq Scan on public.hjtest_2
+ Output: hjtest_2.a, hjtest_2.tableoid, hjtest_2.id, hjtest_2.c, hjtest_2.b
+ Filter: ((SubPlan 5) < 55)
+ SubPlan 5
+ -> Result
+ Output: (hjtest_2.c * 5)
+ -> Hash
+ Output: hjtest_1.a, hjtest_1.tableoid, hjtest_1.id, hjtest_1.b
+ -> Seq Scan on public.hjtest_1
+ Output: hjtest_1.a, hjtest_1.tableoid, hjtest_1.id, hjtest_1.b
+ Filter: ((SubPlan 4) < 50)
+ SubPlan 4
+ -> Result
+ Output: (hjtest_1.b * 5)
+ SubPlan 2
+ -> Result
+ Output: (hjtest_1.b * 5)
+ SubPlan 1
+ -> Result
+ Output: 1
+ One-Time Filter: (hjtest_2.id = 1)
+ SubPlan 3
+ -> Result
+ Output: (hjtest_2.c * 5)
+(28 rows)
+
+SELECT hjtest_1.a a1, hjtest_2.a a2,hjtest_1.tableoid::regclass t1, hjtest_2.tableoid::regclass t2
+FROM hjtest_2, hjtest_1
+WHERE
+ hjtest_1.id = (SELECT 1 WHERE hjtest_2.id = 1)
+ AND (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5)
+ AND (SELECT hjtest_1.b * 5) < 50
+ AND (SELECT hjtest_2.c * 5) < 55
+ AND hjtest_1.a <> hjtest_2.b;
+ a1 | a2 | t1 | t2
+------+----+----------+----------
+ text | t | hjtest_1 | hjtest_2
+(1 row)
+
+ROLLBACK;
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
new file mode 100644
index 0000000..e9d6e9f
--- /dev/null
+++ b/src/test/regress/expected/json.out
@@ -0,0 +1,2638 @@
+-- Strings.
+SELECT '""'::json; -- OK.
+ json
+------
+ ""
+(1 row)
+
+SELECT $$''$$::json; -- ERROR, single quotes are not allowed
+ERROR: invalid input syntax for type json
+LINE 1: SELECT $$''$$::json;
+ ^
+DETAIL: Token "'" is invalid.
+CONTEXT: JSON data, line 1: '...
+SELECT '"abc"'::json; -- OK
+ json
+-------
+ "abc"
+(1 row)
+
+SELECT '"abc'::json; -- ERROR, quotes not closed
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"abc'::json;
+ ^
+DETAIL: Token ""abc" is invalid.
+CONTEXT: JSON data, line 1: "abc
+SELECT '"abc
+def"'::json; -- 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\"\\"'::json; -- OK, legal escapes
+ json
+----------
+ "\n\"\\"
+(1 row)
+
+SELECT '"\v"'::json; -- ERROR, not a valid JSON escape
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\v"'::json;
+ ^
+DETAIL: Escape sequence "\v" is invalid.
+CONTEXT: JSON data, line 1: "\v...
+-- see json_encoding test for input with unicode escapes
+-- Numbers.
+SELECT '1'::json; -- OK
+ json
+------
+ 1
+(1 row)
+
+SELECT '0'::json; -- OK
+ json
+------
+ 0
+(1 row)
+
+SELECT '01'::json; -- ERROR, not valid according to JSON spec
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '01'::json;
+ ^
+DETAIL: Token "01" is invalid.
+CONTEXT: JSON data, line 1: 01
+SELECT '0.1'::json; -- OK
+ json
+------
+ 0.1
+(1 row)
+
+SELECT '9223372036854775808'::json; -- OK, even though it's too large for int8
+ json
+---------------------
+ 9223372036854775808
+(1 row)
+
+SELECT '1e100'::json; -- OK
+ json
+-------
+ 1e100
+(1 row)
+
+SELECT '1.3e100'::json; -- OK
+ json
+---------
+ 1.3e100
+(1 row)
+
+SELECT '1f2'::json; -- ERROR
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '1f2'::json;
+ ^
+DETAIL: Token "1f2" is invalid.
+CONTEXT: JSON data, line 1: 1f2
+SELECT '0.x1'::json; -- ERROR
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '0.x1'::json;
+ ^
+DETAIL: Token "0.x1" is invalid.
+CONTEXT: JSON data, line 1: 0.x1
+SELECT '1.3ex100'::json; -- ERROR
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '1.3ex100'::json;
+ ^
+DETAIL: Token "1.3ex100" is invalid.
+CONTEXT: JSON data, line 1: 1.3ex100
+-- Arrays.
+SELECT '[]'::json; -- OK
+ json
+------
+ []
+(1 row)
+
+SELECT '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'::json; -- OK
+ json
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
+(1 row)
+
+SELECT '[1,2]'::json; -- OK
+ json
+-------
+ [1,2]
+(1 row)
+
+SELECT '[1,2,]'::json; -- ERROR, trailing comma
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '[1,2,]'::json;
+ ^
+DETAIL: Expected JSON value, but found "]".
+CONTEXT: JSON data, line 1: [1,2,]
+SELECT '[1,2'::json; -- ERROR, no closing bracket
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '[1,2'::json;
+ ^
+DETAIL: The input string ended unexpectedly.
+CONTEXT: JSON data, line 1: [1,2
+SELECT '[1,[2]'::json; -- ERROR, no closing bracket
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '[1,[2]'::json;
+ ^
+DETAIL: The input string ended unexpectedly.
+CONTEXT: JSON data, line 1: [1,[2]
+-- Objects.
+SELECT '{}'::json; -- OK
+ json
+------
+ {}
+(1 row)
+
+SELECT '{"abc"}'::json; -- ERROR, no value
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '{"abc"}'::json;
+ ^
+DETAIL: Expected ":", but found "}".
+CONTEXT: JSON data, line 1: {"abc"}
+SELECT '{"abc":1}'::json; -- OK
+ json
+-----------
+ {"abc":1}
+(1 row)
+
+SELECT '{1:"abc"}'::json; -- ERROR, keys must be strings
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '{1:"abc"}'::json;
+ ^
+DETAIL: Expected string or "}", but found "1".
+CONTEXT: JSON data, line 1: {1...
+SELECT '{"abc",1}'::json; -- ERROR, wrong separator
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '{"abc",1}'::json;
+ ^
+DETAIL: Expected ":", but found ",".
+CONTEXT: JSON data, line 1: {"abc",...
+SELECT '{"abc"=1}'::json; -- ERROR, totally wrong separator
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '{"abc"=1}'::json;
+ ^
+DETAIL: Token "=" is invalid.
+CONTEXT: JSON data, line 1: {"abc"=...
+SELECT '{"abc"::1}'::json; -- ERROR, another wrong separator
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '{"abc"::1}'::json;
+ ^
+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]}}'::json; -- OK
+ json
+---------------------------------------------------------
+ {"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}
+(1 row)
+
+SELECT '{"abc":1:2}'::json; -- ERROR, colon in wrong spot
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '{"abc":1:2}'::json;
+ ^
+DETAIL: Expected "," or "}", but found ":".
+CONTEXT: JSON data, line 1: {"abc":1:...
+SELECT '{"abc":1,3}'::json; -- ERROR, no value
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '{"abc":1,3}'::json;
+ ^
+DETAIL: Expected string, but found "3".
+CONTEXT: JSON data, line 1: {"abc":1,3...
+-- Recursion.
+SET max_stack_depth = '100kB';
+SELECT repeat('[', 10000)::json;
+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)::json;
+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'::json; -- OK
+ json
+------
+ true
+(1 row)
+
+SELECT 'false'::json; -- OK
+ json
+-------
+ false
+(1 row)
+
+SELECT 'null'::json; -- OK
+ json
+------
+ null
+(1 row)
+
+SELECT ' true '::json; -- OK, even with extra whitespace
+ json
+--------
+ true
+(1 row)
+
+SELECT 'true false'::json; -- ERROR, too many values
+ERROR: invalid input syntax for type json
+LINE 1: SELECT 'true false'::json;
+ ^
+DETAIL: Expected end of input, but found "false".
+CONTEXT: JSON data, line 1: true false
+SELECT 'true, false'::json; -- ERROR, too many values
+ERROR: invalid input syntax for type json
+LINE 1: SELECT 'true, false'::json;
+ ^
+DETAIL: Expected end of input, but found ",".
+CONTEXT: JSON data, line 1: true,...
+SELECT 'truf'::json; -- ERROR, not a keyword
+ERROR: invalid input syntax for type json
+LINE 1: SELECT 'truf'::json;
+ ^
+DETAIL: Token "truf" is invalid.
+CONTEXT: JSON data, line 1: truf
+SELECT 'trues'::json; -- ERROR, not a keyword
+ERROR: invalid input syntax for type json
+LINE 1: SELECT 'trues'::json;
+ ^
+DETAIL: Token "trues" is invalid.
+CONTEXT: JSON data, line 1: trues
+SELECT ''::json; -- ERROR, no value
+ERROR: invalid input syntax for type json
+LINE 1: SELECT ''::json;
+ ^
+DETAIL: The input string ended unexpectedly.
+CONTEXT: JSON data, line 1:
+SELECT ' '::json; -- ERROR, no value
+ERROR: invalid input syntax for type json
+LINE 1: SELECT ' '::json;
+ ^
+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}'::json; -- OK
+ json
+------------------------------
+ { +
+ "one": 1, +
+ "two":"two",+
+ "three": +
+ true}
+(1 row)
+
+SELECT '{
+ "one": 1,
+ "two":,"two", -- ERROR extraneous comma before field "two"
+ "three":
+ true}'::json;
+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":}'::json;
+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
+--constructors
+-- array_to_json
+SELECT array_to_json(array(select 1 as a));
+ array_to_json
+---------------
+ [1]
+(1 row)
+
+SELECT array_to_json(array_agg(q),false) from (select x as b, x * 2 as c from generate_series(1,3) x) q;
+ array_to_json
+---------------------------------------------
+ [{"b":1,"c":2},{"b":2,"c":4},{"b":3,"c":6}]
+(1 row)
+
+SELECT array_to_json(array_agg(q),true) from (select x as b, x * 2 as c from generate_series(1,3) x) q;
+ array_to_json
+-----------------
+ [{"b":1,"c":2},+
+ {"b":2,"c":4},+
+ {"b":3,"c":6}]
+(1 row)
+
+SELECT array_to_json(array_agg(q),false)
+ 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;
+ array_to_json
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [{"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 array_to_json(array_agg(x),false) from generate_series(5,10) x;
+ array_to_json
+----------------
+ [5,6,7,8,9,10]
+(1 row)
+
+SELECT array_to_json('{{1,5},{99,100}}'::int[]);
+ array_to_json
+------------------
+ [[1,5],[99,100]]
+(1 row)
+
+-- row_to_json
+SELECT row_to_json(row(1,'foo'));
+ row_to_json
+---------------------
+ {"f1":1,"f2":"foo"}
+(1 row)
+
+SELECT row_to_json(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;
+ row_to_json
+--------------------------------------------------------------------
+ {"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]}]}
+(4 rows)
+
+SELECT row_to_json(q,true)
+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;
+ row_to_json
+-----------------------------------------------------
+ {"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]}]}
+(4 rows)
+
+CREATE TEMP TABLE rows AS
+SELECT x, 'txt' || x as y
+FROM generate_series(1,3) AS x;
+SELECT row_to_json(q,true)
+FROM rows q;
+ row_to_json
+--------------
+ {"x":1, +
+ "y":"txt1"}
+ {"x":2, +
+ "y":"txt2"}
+ {"x":3, +
+ "y":"txt3"}
+(3 rows)
+
+SELECT row_to_json(row((select array_agg(x) as d from generate_series(5,10) x)),false);
+ row_to_json
+-----------------------
+ {"f1":[5,6,7,8,9,10]}
+(1 row)
+
+-- anyarray column
+analyze rows;
+select attname, to_json(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_json, timestamps
+select to_json(timestamp '2014-05-28 12:22:35.614298');
+ to_json
+------------------------------
+ "2014-05-28T12:22:35.614298"
+(1 row)
+
+BEGIN;
+SET LOCAL TIME ZONE 10.5;
+select to_json(timestamptz '2014-05-28 12:22:35.614298-04');
+ to_json
+------------------------------------
+ "2014-05-29T02:52:35.614298+10:30"
+(1 row)
+
+SET LOCAL TIME ZONE -8;
+select to_json(timestamptz '2014-05-28 12:22:35.614298-04');
+ to_json
+------------------------------------
+ "2014-05-28T08:22:35.614298-08:00"
+(1 row)
+
+COMMIT;
+select to_json(date '2014-05-28');
+ to_json
+--------------
+ "2014-05-28"
+(1 row)
+
+select to_json(date 'Infinity');
+ to_json
+------------
+ "infinity"
+(1 row)
+
+select to_json(date '-Infinity');
+ to_json
+-------------
+ "-infinity"
+(1 row)
+
+select to_json(timestamp 'Infinity');
+ to_json
+------------
+ "infinity"
+(1 row)
+
+select to_json(timestamp '-Infinity');
+ to_json
+-------------
+ "-infinity"
+(1 row)
+
+select to_json(timestamptz 'Infinity');
+ to_json
+------------
+ "infinity"
+(1 row)
+
+select to_json(timestamptz '-Infinity');
+ to_json
+-------------
+ "-infinity"
+(1 row)
+
+--json_agg
+SELECT json_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;
+ json_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 json_agg(q ORDER BY x, y)
+ FROM rows q;
+ json_agg
+-----------------------
+ [{"x":1,"y":"txt1"}, +
+ {"x":2,"y":"txt2"}, +
+ {"x":3,"y":"txt3"}]
+(1 row)
+
+UPDATE rows SET x = NULL WHERE x = 1;
+SELECT json_agg(q ORDER BY x NULLS FIRST, y)
+ FROM rows q;
+ json_agg
+--------------------------
+ [{"x":null,"y":"txt1"}, +
+ {"x":2,"y":"txt2"}, +
+ {"x":3,"y":"txt3"}]
+(1 row)
+
+-- non-numeric output
+SELECT row_to_json(q)
+FROM (SELECT 'NaN'::float8 AS "float8field") q;
+ row_to_json
+-----------------------
+ {"float8field":"NaN"}
+(1 row)
+
+SELECT row_to_json(q)
+FROM (SELECT 'Infinity'::float8 AS "float8field") q;
+ row_to_json
+----------------------------
+ {"float8field":"Infinity"}
+(1 row)
+
+SELECT row_to_json(q)
+FROM (SELECT '-Infinity'::float8 AS "float8field") q;
+ row_to_json
+-----------------------------
+ {"float8field":"-Infinity"}
+(1 row)
+
+-- json input
+SELECT row_to_json(q)
+FROM (SELECT '{"a":1,"b": [2,3,4,"d","e","f"],"c":{"p":1,"q":2}}'::json AS "jsonfield") q;
+ row_to_json
+------------------------------------------------------------------
+ {"jsonfield":{"a":1,"b": [2,3,4,"d","e","f"],"c":{"p":1,"q":2}}}
+(1 row)
+
+-- json extraction functions
+CREATE TEMP TABLE test_json (
+ json_type text,
+ test_json json
+);
+INSERT INTO test_json 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_json
+WHERE json_type = 'scalar';
+ ?column?
+----------
+
+(1 row)
+
+SELECT test_json -> 'x'
+FROM test_json
+WHERE json_type = 'array';
+ ?column?
+----------
+
+(1 row)
+
+SELECT test_json -> 'x'
+FROM test_json
+WHERE json_type = 'object';
+ ?column?
+----------
+
+(1 row)
+
+SELECT test_json->'field2'
+FROM test_json
+WHERE json_type = 'object';
+ ?column?
+----------
+ "val2"
+(1 row)
+
+SELECT test_json->>'field2'
+FROM test_json
+WHERE json_type = 'object';
+ ?column?
+----------
+ val2
+(1 row)
+
+SELECT test_json -> 2
+FROM test_json
+WHERE json_type = 'scalar';
+ ?column?
+----------
+
+(1 row)
+
+SELECT test_json -> 2
+FROM test_json
+WHERE json_type = 'array';
+ ?column?
+----------
+ "two"
+(1 row)
+
+SELECT test_json -> -1
+FROM test_json
+WHERE json_type = 'array';
+ ?column?
+----------
+ {"f1":9}
+(1 row)
+
+SELECT test_json -> 2
+FROM test_json
+WHERE json_type = 'object';
+ ?column?
+----------
+
+(1 row)
+
+SELECT test_json->>2
+FROM test_json
+WHERE json_type = 'array';
+ ?column?
+----------
+ two
+(1 row)
+
+SELECT test_json ->> 6 FROM test_json WHERE json_type = 'array';
+ ?column?
+----------
+ [1,2,3]
+(1 row)
+
+SELECT test_json ->> 7 FROM test_json WHERE json_type = 'array';
+ ?column?
+----------
+ {"f1":9}
+(1 row)
+
+SELECT test_json ->> 'field4' FROM test_json WHERE json_type = 'object';
+ ?column?
+----------
+ 4
+(1 row)
+
+SELECT test_json ->> 'field5' FROM test_json WHERE json_type = 'object';
+ ?column?
+----------
+ [1,2,3]
+(1 row)
+
+SELECT test_json ->> 'field6' FROM test_json WHERE json_type = 'object';
+ ?column?
+----------
+ {"f1":9}
+(1 row)
+
+SELECT json_object_keys(test_json)
+FROM test_json
+WHERE json_type = 'scalar';
+ERROR: cannot call json_object_keys on a scalar
+SELECT json_object_keys(test_json)
+FROM test_json
+WHERE json_type = 'array';
+ERROR: cannot call json_object_keys on an array
+SELECT json_object_keys(test_json)
+FROM test_json
+WHERE json_type = 'object';
+ json_object_keys
+------------------
+ field1
+ field2
+ field3
+ field4
+ field5
+ field6
+(6 rows)
+
+-- test extending object_keys resultset - initial resultset size is 256
+select count(*) from
+ (select json_object_keys(json_object(array_agg(g)))
+ from (select unnest(array['f'||n,n::text])as g
+ from generate_series(1,300) as n) x ) y;
+ count
+-------
+ 300
+(1 row)
+
+-- nulls
+select (test_json->'field3') is null as expect_false
+from test_json
+where json_type = 'object';
+ expect_false
+--------------
+ f
+(1 row)
+
+select (test_json->>'field3') is null as expect_true
+from test_json
+where json_type = 'object';
+ expect_true
+-------------
+ t
+(1 row)
+
+select (test_json->3) is null as expect_false
+from test_json
+where json_type = 'array';
+ expect_false
+--------------
+ f
+(1 row)
+
+select (test_json->>3) is null as expect_true
+from test_json
+where json_type = 'array';
+ expect_true
+-------------
+ t
+(1 row)
+
+-- corner cases
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json -> null::text;
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json -> null::int;
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json -> 1;
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json -> -1;
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json -> 'z';
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json -> '';
+ ?column?
+----------
+
+(1 row)
+
+select '[{"b": "c"}, {"b": "cc"}]'::json -> 1;
+ ?column?
+-------------
+ {"b": "cc"}
+(1 row)
+
+select '[{"b": "c"}, {"b": "cc"}]'::json -> 3;
+ ?column?
+----------
+
+(1 row)
+
+select '[{"b": "c"}, {"b": "cc"}]'::json -> 'z';
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": "c", "b": null}'::json -> 'b';
+ ?column?
+----------
+ null
+(1 row)
+
+select '"foo"'::json -> 1;
+ ?column?
+----------
+
+(1 row)
+
+select '"foo"'::json -> 'z';
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json ->> null::text;
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json ->> null::int;
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json ->> 1;
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json ->> 'z';
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json ->> '';
+ ?column?
+----------
+
+(1 row)
+
+select '[{"b": "c"}, {"b": "cc"}]'::json ->> 1;
+ ?column?
+-------------
+ {"b": "cc"}
+(1 row)
+
+select '[{"b": "c"}, {"b": "cc"}]'::json ->> 3;
+ ?column?
+----------
+
+(1 row)
+
+select '[{"b": "c"}, {"b": "cc"}]'::json ->> 'z';
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": "c", "b": null}'::json ->> 'b';
+ ?column?
+----------
+
+(1 row)
+
+select '"foo"'::json ->> 1;
+ ?column?
+----------
+
+(1 row)
+
+select '"foo"'::json ->> 'z';
+ ?column?
+----------
+
+(1 row)
+
+-- array length
+SELECT json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]');
+ json_array_length
+-------------------
+ 5
+(1 row)
+
+SELECT json_array_length('[]');
+ json_array_length
+-------------------
+ 0
+(1 row)
+
+SELECT json_array_length('{"f1":1,"f2":[5,6]}');
+ERROR: cannot get array length of a non-array
+SELECT json_array_length('4');
+ERROR: cannot get array length of a scalar
+-- each
+select json_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}');
+ json_each
+-------------------
+ (f1,"[1,2,3]")
+ (f2,"{""f3"":1}")
+ (f4,null)
+(3 rows)
+
+select * from json_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 json_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}');
+ json_each_text
+-------------------
+ (f1,"[1,2,3]")
+ (f2,"{""f3"":1}")
+ (f4,)
+ (f5,null)
+(4 rows)
+
+select * from json_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)
+
+-- extract_path, extract_path_as_text
+select json_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+ json_extract_path
+-------------------
+ "stringy"
+(1 row)
+
+select json_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+ json_extract_path
+-------------------
+ {"f3":1}
+(1 row)
+
+select json_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+ json_extract_path
+-------------------
+ "f3"
+(1 row)
+
+select json_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+ json_extract_path
+-------------------
+ 1
+(1 row)
+
+select json_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+ json_extract_path_text
+------------------------
+ stringy
+(1 row)
+
+select json_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+ json_extract_path_text
+------------------------
+ {"f3":1}
+(1 row)
+
+select json_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+ json_extract_path_text
+------------------------
+ f3
+(1 row)
+
+select json_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+ json_extract_path_text
+------------------------
+ 1
+(1 row)
+
+-- extract_path nulls
+select json_extract_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') is null as expect_false;
+ expect_false
+--------------
+ f
+(1 row)
+
+select json_extract_path_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') is null as expect_true;
+ expect_true
+-------------
+ t
+(1 row)
+
+select json_extract_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') is null as expect_false;
+ expect_false
+--------------
+ f
+(1 row)
+
+select json_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"}}'::json#>array['f4','f6'];
+ ?column?
+-----------
+ "stringy"
+(1 row)
+
+select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f2'];
+ ?column?
+----------
+ {"f3":1}
+(1 row)
+
+select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f2','0'];
+ ?column?
+----------
+ "f3"
+(1 row)
+
+select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f2','1'];
+ ?column?
+----------
+ 1
+(1 row)
+
+select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f4','f6'];
+ ?column?
+----------
+ stringy
+(1 row)
+
+select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f2'];
+ ?column?
+----------
+ {"f3":1}
+(1 row)
+
+select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f2','0'];
+ ?column?
+----------
+ f3
+(1 row)
+
+select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f2','1'];
+ ?column?
+----------
+ 1
+(1 row)
+
+-- corner cases for same
+select '{"a": {"b":{"c": "foo"}}}'::json #> '{}';
+ ?column?
+---------------------------
+ {"a": {"b":{"c": "foo"}}}
+(1 row)
+
+select '[1,2,3]'::json #> '{}';
+ ?column?
+----------
+ [1,2,3]
+(1 row)
+
+select '"foo"'::json #> '{}';
+ ?column?
+----------
+ "foo"
+(1 row)
+
+select '42'::json #> '{}';
+ ?column?
+----------
+ 42
+(1 row)
+
+select 'null'::json #> '{}';
+ ?column?
+----------
+ null
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a'];
+ ?column?
+--------------------
+ {"b":{"c": "foo"}}
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a', null];
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a', ''];
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a','b'];
+ ?column?
+--------------
+ {"c": "foo"}
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a','b','c'];
+ ?column?
+----------
+ "foo"
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a','b','c','d'];
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a','z','c'];
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json #> array['a','1','b'];
+ ?column?
+----------
+ "cc"
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json #> array['a','z','b'];
+ ?column?
+----------
+
+(1 row)
+
+select '[{"b": "c"}, {"b": "cc"}]'::json #> array['1','b'];
+ ?column?
+----------
+ "cc"
+(1 row)
+
+select '[{"b": "c"}, {"b": "cc"}]'::json #> array['z','b'];
+ ?column?
+----------
+
+(1 row)
+
+select '[{"b": "c"}, {"b": null}]'::json #> array['1','b'];
+ ?column?
+----------
+ null
+(1 row)
+
+select '"foo"'::json #> array['z'];
+ ?column?
+----------
+
+(1 row)
+
+select '42'::json #> array['f2'];
+ ?column?
+----------
+
+(1 row)
+
+select '42'::json #> array['0'];
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #>> '{}';
+ ?column?
+---------------------------
+ {"a": {"b":{"c": "foo"}}}
+(1 row)
+
+select '[1,2,3]'::json #>> '{}';
+ ?column?
+----------
+ [1,2,3]
+(1 row)
+
+select '"foo"'::json #>> '{}';
+ ?column?
+----------
+ foo
+(1 row)
+
+select '42'::json #>> '{}';
+ ?column?
+----------
+ 42
+(1 row)
+
+select 'null'::json #>> '{}';
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a'];
+ ?column?
+--------------------
+ {"b":{"c": "foo"}}
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a', null];
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a', ''];
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a','b'];
+ ?column?
+--------------
+ {"c": "foo"}
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a','b','c'];
+ ?column?
+----------
+ foo
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a','b','c','d'];
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a','z','c'];
+ ?column?
+----------
+
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json #>> array['a','1','b'];
+ ?column?
+----------
+ cc
+(1 row)
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json #>> array['a','z','b'];
+ ?column?
+----------
+
+(1 row)
+
+select '[{"b": "c"}, {"b": "cc"}]'::json #>> array['1','b'];
+ ?column?
+----------
+ cc
+(1 row)
+
+select '[{"b": "c"}, {"b": "cc"}]'::json #>> array['z','b'];
+ ?column?
+----------
+
+(1 row)
+
+select '[{"b": "c"}, {"b": null}]'::json #>> array['1','b'];
+ ?column?
+----------
+
+(1 row)
+
+select '"foo"'::json #>> array['z'];
+ ?column?
+----------
+
+(1 row)
+
+select '42'::json #>> array['f2'];
+ ?column?
+----------
+
+(1 row)
+
+select '42'::json #>> array['0'];
+ ?column?
+----------
+
+(1 row)
+
+-- array_elements
+select json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
+ json_array_elements
+-----------------------
+ 1
+ true
+ [1,[2,3]]
+ null
+ {"f1":1,"f2":[7,8,9]}
+ false
+ "stringy"
+(7 rows)
+
+select * from json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q;
+ value
+-----------------------
+ 1
+ true
+ [1,[2,3]]
+ null
+ {"f1":1,"f2":[7,8,9]}
+ false
+ "stringy"
+(7 rows)
+
+select json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
+ json_array_elements_text
+--------------------------
+ 1
+ true
+ [1,[2,3]]
+
+ {"f1":1,"f2":[7,8,9]}
+ false
+ stringy
+(7 rows)
+
+select * from json_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 jpop as (a text, b int, c timestamp);
+CREATE DOMAIN js_int_not_null AS int NOT NULL;
+CREATE DOMAIN js_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3);
+CREATE DOMAIN js_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3);
+create type j_unordered_pair as (x int, y int);
+create domain j_ordered_pair as j_unordered_pair check((value).x <= (value).y);
+CREATE TYPE jsrec AS (
+ i int,
+ ia _int4,
+ ia1 int[],
+ ia2 int[][],
+ ia3 int[][][],
+ ia1d js_int_array_1d,
+ ia2d js_int_array_2d,
+ t text,
+ ta text[],
+ c char(10),
+ ca char(10)[],
+ ts timestamp,
+ js json,
+ jsb jsonb,
+ jsa json[],
+ rec jpop,
+ reca jpop[]
+);
+CREATE TYPE jsrec_i_not_null AS (
+ i js_int_not_null
+);
+select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q;
+ a | b | c
+--------+---+---
+ blurfl | |
+(1 row)
+
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}') q;
+ a | b | c
+--------+---+--------------------------
+ blurfl | 3 | Mon Dec 31 15:30:56 2012
+(1 row)
+
+select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q;
+ a | b | c
+--------+---+---
+ blurfl | |
+(1 row)
+
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}') q;
+ a | b | c
+--------+---+--------------------------
+ blurfl | 3 | Mon Dec 31 15:30:56 2012
+(1 row)
+
+select * from json_populate_record(null::jpop,'{"a":[100,200,false],"x":43.2}') q;
+ a | b | c
+-----------------+---+---
+ [100,200,false] | |
+(1 row)
+
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"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 json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"c":[100,200,false],"x":43.2}') q;
+ERROR: invalid input syntax for type timestamp: "[100,200,false]"
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{}') q;
+ a | b | c
+---+---+--------------------------
+ x | 3 | Mon Dec 31 15:30:56 2012
+(1 row)
+
+SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"x": 43.2}') q;
+ERROR: domain js_int_not_null does not allow null values
+SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"i": null}') q;
+ERROR: domain js_int_not_null does not allow null values
+SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"i": 12345}') q;
+ i
+-------
+ 12345
+(1 row)
+
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": null}') q;
+ ia
+----
+
+(1 row)
+
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": 123}') q;
+ERROR: expected JSON array
+HINT: See the value of key "ia".
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [1, "2", null, 4]}') q;
+ ia
+--------------
+ {1,2,NULL,4}
+(1 row)
+
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1, 2], [3, 4]]}') q;
+ ia
+---------------
+ {{1,2},{3,4}}
+(1 row)
+
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1], 2]}') q;
+ERROR: expected JSON array
+HINT: See the array element [1] of key "ia".
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1], [2, 3]]}') q;
+ERROR: malformed JSON array
+DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions.
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": "{1,2,3}"}') q;
+ ia
+---------
+ {1,2,3}
+(1 row)
+
+SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": null}') q;
+ ia1
+-----
+
+(1 row)
+
+SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": 123}') q;
+ERROR: expected JSON array
+HINT: See the value of key "ia1".
+SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": [1, "2", null, 4]}') q;
+ ia1
+--------------
+ {1,2,NULL,4}
+(1 row)
+
+SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": [[1, 2, 3]]}') q;
+ ia1
+-----------
+ {{1,2,3}}
+(1 row)
+
+SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": null}') q;
+ ia1d
+------
+
+(1 row)
+
+SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": 123}') q;
+ERROR: expected JSON array
+HINT: See the value of key "ia1d".
+SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": [1, "2", null, 4]}') q;
+ERROR: value for domain js_int_array_1d violates check constraint "js_int_array_1d_check"
+SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": [1, "2", null]}') q;
+ ia1d
+------------
+ {1,2,NULL}
+(1 row)
+
+SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [1, "2", null, 4]}') q;
+ ia2
+--------------
+ {1,2,NULL,4}
+(1 row)
+
+SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[1, 2], [null, 4]]}') q;
+ ia2
+------------------
+ {{1,2},{NULL,4}}
+(1 row)
+
+SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[], []]}') q;
+ ia2
+-----
+ {}
+(1 row)
+
+SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[1, 2], [3]]}') q;
+ERROR: malformed JSON array
+DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions.
+SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[1, 2], 3, 4]}') q;
+ERROR: expected JSON array
+HINT: See the array element [1] of key "ia2".
+SELECT ia2d FROM json_populate_record(NULL::jsrec, '{"ia2d": [[1, "2"], [null, 4]]}') q;
+ERROR: value for domain js_int_array_2d violates check constraint "js_int_array_2d_check"
+SELECT ia2d FROM json_populate_record(NULL::jsrec, '{"ia2d": [[1, "2", 3], [null, 5, 6]]}') q;
+ ia2d
+----------------------
+ {{1,2,3},{NULL,5,6}}
+(1 row)
+
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [1, "2", null, 4]}') q;
+ ia3
+--------------
+ {1,2,NULL,4}
+(1 row)
+
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [[1, 2], [null, 4]]}') q;
+ ia3
+------------------
+ {{1,2},{NULL,4}}
+(1 row)
+
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[], []], [[], []], [[], []] ]}') q;
+ ia3
+-----
+ {}
+(1 row)
+
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[1, 2]], [[3, 4]] ]}') q;
+ ia3
+-------------------
+ {{{1,2}},{{3,4}}}
+(1 row)
+
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8]] ]}') q;
+ ia3
+-------------------------------
+ {{{1,2},{3,4}},{{5,6},{7,8}}}
+(1 row)
+
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"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 json_populate_record(NULL::jsrec, '{"ta": null}') q;
+ ta
+----
+
+(1 row)
+
+SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": 123}') q;
+ERROR: expected JSON array
+HINT: See the value of key "ta".
+SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": [1, "2", null, 4]}') q;
+ ta
+--------------
+ {1,2,NULL,4}
+(1 row)
+
+SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": [[1, 2, 3], {"k": "v"}]}') q;
+ERROR: expected JSON array
+HINT: See the array element [1] of key "ta".
+SELECT c FROM json_populate_record(NULL::jsrec, '{"c": null}') q;
+ c
+---
+
+(1 row)
+
+SELECT c FROM json_populate_record(NULL::jsrec, '{"c": "aaa"}') q;
+ c
+------------
+ aaa
+(1 row)
+
+SELECT c FROM json_populate_record(NULL::jsrec, '{"c": "aaaaaaaaaa"}') q;
+ c
+------------
+ aaaaaaaaaa
+(1 row)
+
+SELECT c FROM json_populate_record(NULL::jsrec, '{"c": "aaaaaaaaaaaaa"}') q;
+ERROR: value too long for type character(10)
+SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": null}') q;
+ ca
+----
+
+(1 row)
+
+SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": 123}') q;
+ERROR: expected JSON array
+HINT: See the value of key "ca".
+SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": [1, "2", null, 4]}') q;
+ ca
+-----------------------------------------------
+ {"1 ","2 ",NULL,"4 "}
+(1 row)
+
+SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": ["aaaaaaaaaaaaaaaa"]}') q;
+ERROR: value too long for type character(10)
+SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": [[1, 2, 3], {"k": "v"}]}') q;
+ERROR: expected JSON array
+HINT: See the array element [1] of key "ca".
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": null}') q;
+ js
+----
+
+(1 row)
+
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": true}') q;
+ js
+------
+ true
+(1 row)
+
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": 123.45}') q;
+ js
+--------
+ 123.45
+(1 row)
+
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": "123.45"}') q;
+ js
+----------
+ "123.45"
+(1 row)
+
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": "abc"}') q;
+ js
+-------
+ "abc"
+(1 row)
+
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": [123, "123", null, {"key": "value"}]}') q;
+ js
+--------------------------------------
+ [123, "123", null, {"key": "value"}]
+(1 row)
+
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": {"a": "bbb", "b": null, "c": 123.45}}') q;
+ js
+--------------------------------------
+ {"a": "bbb", "b": null, "c": 123.45}
+(1 row)
+
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": null}') q;
+ jsb
+-----
+
+(1 row)
+
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": true}') q;
+ jsb
+------
+ true
+(1 row)
+
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": 123.45}') q;
+ jsb
+--------
+ 123.45
+(1 row)
+
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": "123.45"}') q;
+ jsb
+----------
+ "123.45"
+(1 row)
+
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": "abc"}') q;
+ jsb
+-------
+ "abc"
+(1 row)
+
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": [123, "123", null, {"key": "value"}]}') q;
+ jsb
+--------------------------------------
+ [123, "123", null, {"key": "value"}]
+(1 row)
+
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": {"a": "bbb", "b": null, "c": 123.45}}') q;
+ jsb
+--------------------------------------
+ {"a": "bbb", "b": null, "c": 123.45}
+(1 row)
+
+SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": null}') q;
+ jsa
+-----
+
+(1 row)
+
+SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": 123}') q;
+ERROR: expected JSON array
+HINT: See the value of key "jsa".
+SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": [1, "2", null, 4]}') q;
+ jsa
+--------------------
+ {1,"\"2\"",NULL,4}
+(1 row)
+
+SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": ["aaa", null, [1, 2, "3", {}], { "k" : "v" }]}') q;
+ jsa
+----------------------------------------------------------
+ {"\"aaa\"",NULL,"[1, 2, \"3\", {}]","{ \"k\" : \"v\" }"}
+(1 row)
+
+SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": 123}') q;
+ERROR: cannot call populate_composite on a scalar
+SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": [1, 2]}') q;
+ERROR: cannot call populate_composite on an array
+SELECT rec FROM json_populate_record(NULL::jsrec, '{"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 json_populate_record(NULL::jsrec, '{"rec": "(abc,42,01.02.2003)"}') q;
+ rec
+-------------------------------------
+ (abc,42,"Thu Jan 02 00:00:00 2003")
+(1 row)
+
+SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": 123}') q;
+ERROR: expected JSON array
+HINT: See the value of key "reca".
+SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": [1, 2]}') q;
+ERROR: cannot call populate_composite on a scalar
+SELECT reca FROM json_populate_record(NULL::jsrec, '{"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 json_populate_record(NULL::jsrec, '{"reca": ["(abc,42,01.02.2003)"]}') q;
+ reca
+-------------------------------------------
+ {"(abc,42,\"Thu Jan 02 00:00:00 2003\")"}
+(1 row)
+
+SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": "{\"(abc,42,01.02.2003)\"}"}') q;
+ reca
+-------------------------------------------
+ {"(abc,42,\"Thu Jan 02 00:00:00 2003\")"}
+(1 row)
+
+SELECT rec FROM json_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')::jpop,NULL)::jsrec,
+ '{"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 json_populate_record(null::record, '{"x": 0, "y": 1}');
+ERROR: could not determine row type for result of json_populate_record
+HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list.
+SELECT json_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
+ json_populate_record
+----------------------
+ (0,1)
+(1 row)
+
+SELECT * FROM
+ json_populate_record(null::record, '{"x": 776}') AS (x int, y int);
+ x | y
+-----+---
+ 776 |
+(1 row)
+
+-- composite domain
+SELECT json_populate_record(null::j_ordered_pair, '{"x": 0, "y": 1}');
+ json_populate_record
+----------------------
+ (0,1)
+(1 row)
+
+SELECT json_populate_record(row(1,2)::j_ordered_pair, '{"x": 0}');
+ json_populate_record
+----------------------
+ (0,2)
+(1 row)
+
+SELECT json_populate_record(row(1,2)::j_ordered_pair, '{"x": 1, "y": 0}');
+ERROR: value for domain j_ordered_pair violates check constraint "j_ordered_pair_check"
+-- populate_recordset
+select * from json_populate_recordset(null::jpop,'[{"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 json_populate_recordset(row('def',99,null)::jpop,'[{"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 json_populate_recordset(null::jpop,'[{"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 json_populate_recordset(row('def',99,null)::jpop,'[{"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 json_populate_recordset(row('def',99,null)::jpop,'[{"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 json_populate_recordset(row('def',99,null)::jpop,'[{"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]"
+create type jpop2 as (a int, b json, c int, d int);
+select * from json_populate_recordset(null::jpop2, '[{"a":2,"c":3,"b":{"z":4},"d":6}]') q;
+ a | b | c | d
+---+---------+---+---
+ 2 | {"z":4} | 3 | 6
+(1 row)
+
+select * from json_populate_recordset(null::jpop,'[{"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 json_populate_recordset(row('def',99,null)::jpop,'[{"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 json_populate_recordset(row('def',99,null)::jpop,'[{"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 json_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
+ERROR: could not determine row type for result of json_populate_recordset
+HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list.
+SELECT json_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
+ json_populate_recordset
+-------------------------
+ (0,1)
+(1 row)
+
+SELECT i, json_populate_recordset(row(i,50), '[{"f1":"42"},{"f2":"43"}]')
+FROM (VALUES (1),(2)) v(i);
+ i | json_populate_recordset
+---+-------------------------
+ 1 | (42,50)
+ 1 | (1,43)
+ 2 | (42,50)
+ 2 | (2,43)
+(4 rows)
+
+SELECT * FROM
+ json_populate_recordset(null::record, '[{"x": 776}]') AS (x int, y int);
+ x | y
+-----+---
+ 776 |
+(1 row)
+
+-- empty array is a corner case
+SELECT json_populate_recordset(null::record, '[]');
+ERROR: could not determine row type for result of json_populate_recordset
+HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list.
+SELECT json_populate_recordset(row(1,2), '[]');
+ json_populate_recordset
+-------------------------
+(0 rows)
+
+SELECT * FROM json_populate_recordset(NULL::jpop,'[]') q;
+ a | b | c
+---+---+---
+(0 rows)
+
+SELECT * FROM
+ json_populate_recordset(null::record, '[]') AS (x int, y int);
+ x | y
+---+---
+(0 rows)
+
+-- composite domain
+SELECT json_populate_recordset(null::j_ordered_pair, '[{"x": 0, "y": 1}]');
+ json_populate_recordset
+-------------------------
+ (0,1)
+(1 row)
+
+SELECT json_populate_recordset(row(1,2)::j_ordered_pair, '[{"x": 0}, {"y": 3}]');
+ json_populate_recordset
+-------------------------
+ (0,2)
+ (1,3)
+(2 rows)
+
+SELECT json_populate_recordset(row(1,2)::j_ordered_pair, '[{"x": 1, "y": 0}]');
+ERROR: value for domain j_ordered_pair violates check constraint "j_ordered_pair_check"
+-- negative cases where the wrong record type is supplied
+select * from json_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 json_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 json_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 json_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.
+-- test type info caching in json_populate_record()
+CREATE TEMP TABLE jspoptest (js json);
+INSERT INTO jspoptest
+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}]
+}'::json
+FROM generate_series(1, 3);
+SELECT (json_populate_record(NULL::jsrec, js)).* FROM jspoptest;
+ 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 jsrec;
+DROP TYPE jsrec_i_not_null;
+DROP DOMAIN js_int_not_null;
+DROP DOMAIN js_int_array_1d;
+DROP DOMAIN js_int_array_2d;
+DROP DOMAIN j_ordered_pair;
+DROP TYPE j_unordered_pair;
+--json_typeof() function
+select value, json_typeof(value)
+ from (values (json '123.4'),
+ (json '-1'),
+ (json '"foo"'),
+ (json 'true'),
+ (json 'false'),
+ (json 'null'),
+ (json '[1, 2, 3]'),
+ (json '[]'),
+ (json '{"x":"foo", "y":123}'),
+ (json '{}'),
+ (NULL::json))
+ as data(value);
+ value | json_typeof
+----------------------+-------------
+ 123.4 | number
+ -1 | number
+ "foo" | string
+ true | boolean
+ false | boolean
+ null | null
+ [1, 2, 3] | array
+ [] | array
+ {"x":"foo", "y":123} | object
+ {} | object
+ |
+(11 rows)
+
+-- json_build_array, json_build_object, json_object_agg
+SELECT json_build_array('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}');
+ json_build_array
+-----------------------------------------------------------------------
+ ["a", 1, "b", 1.2, "c", true, "d", null, "e", {"x": 3, "y": [1,2,3]}]
+(1 row)
+
+SELECT json_build_array('a', NULL); -- ok
+ json_build_array
+------------------
+ ["a", null]
+(1 row)
+
+SELECT json_build_array(VARIADIC NULL::text[]); -- ok
+ json_build_array
+------------------
+
+(1 row)
+
+SELECT json_build_array(VARIADIC '{}'::text[]); -- ok
+ json_build_array
+------------------
+ []
+(1 row)
+
+SELECT json_build_array(VARIADIC '{a,b,c}'::text[]); -- ok
+ json_build_array
+------------------
+ ["a", "b", "c"]
+(1 row)
+
+SELECT json_build_array(VARIADIC ARRAY['a', NULL]::text[]); -- ok
+ json_build_array
+------------------
+ ["a", null]
+(1 row)
+
+SELECT json_build_array(VARIADIC '{1,2,3,4}'::text[]); -- ok
+ json_build_array
+----------------------
+ ["1", "2", "3", "4"]
+(1 row)
+
+SELECT json_build_array(VARIADIC '{1,2,3,4}'::int[]); -- ok
+ json_build_array
+------------------
+ [1, 2, 3, 4]
+(1 row)
+
+SELECT json_build_array(VARIADIC '{{1,4},{2,5},{3,6}}'::int[][]); -- ok
+ json_build_array
+--------------------
+ [1, 4, 2, 5, 3, 6]
+(1 row)
+
+SELECT json_build_object('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}');
+ json_build_object
+----------------------------------------------------------------------------
+ {"a" : 1, "b" : 1.2, "c" : true, "d" : null, "e" : {"x": 3, "y": [1,2,3]}}
+(1 row)
+
+SELECT json_build_object(
+ 'a', json_build_object('b',false,'c',99),
+ 'd', json_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)));
+ json_build_object
+-------------------------------------------------------------------------------------------------
+ {"a" : {"b" : false, "c" : 99}, "d" : {"e" : [9,8,7], "f" : {"relkind":"r","name":"pg_class"}}}
+(1 row)
+
+SELECT json_build_object('{a,b,c}'::text[]); -- error
+ERROR: argument list must have even number of elements
+HINT: The arguments of json_build_object() must consist of alternating keys and values.
+SELECT json_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 json_build_object('a', 'b', 'c'); -- error
+ERROR: argument list must have even number of elements
+HINT: The arguments of json_build_object() must consist of alternating keys and values.
+SELECT json_build_object(NULL, 'a'); -- error, key cannot be NULL
+ERROR: argument 1 cannot be null
+HINT: Object keys should be text.
+SELECT json_build_object('a', NULL); -- ok
+ json_build_object
+-------------------
+ {"a" : null}
+(1 row)
+
+SELECT json_build_object(VARIADIC NULL::text[]); -- ok
+ json_build_object
+-------------------
+
+(1 row)
+
+SELECT json_build_object(VARIADIC '{}'::text[]); -- ok
+ json_build_object
+-------------------
+ {}
+(1 row)
+
+SELECT json_build_object(VARIADIC '{a,b,c}'::text[]); -- error
+ERROR: argument list must have even number of elements
+HINT: The arguments of json_build_object() must consist of alternating keys and values.
+SELECT json_build_object(VARIADIC ARRAY['a', NULL]::text[]); -- ok
+ json_build_object
+-------------------
+ {"a" : null}
+(1 row)
+
+SELECT json_build_object(VARIADIC ARRAY[NULL, 'a']::text[]); -- error, key cannot be NULL
+ERROR: argument 1 cannot be null
+HINT: Object keys should be text.
+SELECT json_build_object(VARIADIC '{1,2,3,4}'::text[]); -- ok
+ json_build_object
+------------------------
+ {"1" : "2", "3" : "4"}
+(1 row)
+
+SELECT json_build_object(VARIADIC '{1,2,3,4}'::int[]); -- ok
+ json_build_object
+--------------------
+ {"1" : 2, "3" : 4}
+(1 row)
+
+SELECT json_build_object(VARIADIC '{{1,4},{2,5},{3,6}}'::int[][]); -- ok
+ json_build_object
+-----------------------------
+ {"1" : 4, "2" : 5, "3" : 6}
+(1 row)
+
+-- empty objects/arrays
+SELECT json_build_array();
+ json_build_array
+------------------
+ []
+(1 row)
+
+SELECT json_build_object();
+ json_build_object
+-------------------
+ {}
+(1 row)
+
+-- make sure keys are quoted
+SELECT json_build_object(1,2);
+ json_build_object
+-------------------
+ {"1" : 2}
+(1 row)
+
+-- keys must be scalar and not null
+SELECT json_build_object(null,2);
+ERROR: argument 1 cannot be null
+HINT: Object keys should be text.
+SELECT json_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 json_build_object(json '{"a":1,"b":2}', 3);
+ERROR: key value must be scalar, not array, composite, or json
+SELECT json_build_object('{1,2,3}'::int[], 3);
+ERROR: key value must be scalar, not array, composite, or json
+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 json_build_object('turbines',json_object_agg(serial_num,json_build_object('name',name,'type',type)))
+FROM foo;
+ json_build_object
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"turbines" : { "847001" : {"name" : "t15", "type" : "GE1043"}, "847002" : {"name" : "t16", "type" : "GE1043"}, "847003" : {"name" : "sub-alpha", "type" : "GESS90"} }}
+(1 row)
+
+SELECT json_object_agg(name, type) FROM foo;
+ json_object_agg
+----------------------------------------------------------------
+ { "t15" : "GE1043", "t16" : "GE1043", "sub-alpha" : "GESS90" }
+(1 row)
+
+INSERT INTO foo VALUES (999999, NULL, 'bar');
+SELECT json_object_agg(name, type) FROM foo;
+ERROR: field name must not be null
+-- json_object
+-- empty object, one dimension
+SELECT json_object('{}');
+ json_object
+-------------
+ {}
+(1 row)
+
+-- empty object, two dimensions
+SELECT json_object('{}', '{}');
+ json_object
+-------------
+ {}
+(1 row)
+
+-- one dimension
+SELECT json_object('{a,1,b,2,3,NULL,"d e f","a b c"}');
+ json_object
+-------------------------------------------------------
+ {"a" : "1", "b" : "2", "3" : null, "d e f" : "a b c"}
+(1 row)
+
+-- same but with two dimensions
+SELECT json_object('{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}');
+ json_object
+-------------------------------------------------------
+ {"a" : "1", "b" : "2", "3" : null, "d e f" : "a b c"}
+(1 row)
+
+-- odd number error
+SELECT json_object('{a,b,c}');
+ERROR: array must have even number of elements
+-- one column error
+SELECT json_object('{{a},{b}}');
+ERROR: array must have two columns
+-- too many columns error
+SELECT json_object('{{a,b,c},{b,c,d}}');
+ERROR: array must have two columns
+-- too many dimensions error
+SELECT json_object('{{{a,b},{c,d}},{{b,c},{d,e}}}');
+ERROR: wrong number of array subscripts
+--two argument form of json_object
+select json_object('{a,b,c,"d e f"}','{1,2,3,"a b c"}');
+ json_object
+------------------------------------------------------
+ {"a" : "1", "b" : "2", "c" : "3", "d e f" : "a b c"}
+(1 row)
+
+-- too many dimensions
+SELECT json_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 json_object('{a,b,c,"d e f",g}','{1,2,3,"a b c"}');
+ERROR: mismatched array dimensions
+select json_object('{a,b,c,"d e f"}','{1,2,3,"a b c",g}');
+ERROR: mismatched array dimensions
+-- null key error
+select json_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 json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}');
+ json_object
+-----------------------------------------------------
+ {"a" : "1", "b" : "2", "" : "3", "d e f" : "a b c"}
+(1 row)
+
+-- json_to_record and json_to_recordset
+select * from json_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 json_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 * from json_to_recordset('[{"a":1,"b":{"d":"foo"},"c":true},{"a":2,"c":false,"b":{"d":"bar"}}]')
+ as x(a int, b json, c boolean);
+ a | b | c
+---+-------------+---
+ 1 | {"d":"foo"} | t
+ 2 | {"d":"bar"} | f
+(2 rows)
+
+select *, c is null as c_is_null
+from json_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}}'::json)
+ as t(a int, b json, c text, x int, ca char(5)[], ia int[][], r jpop);
+ 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 json_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::json)
+ as t(a int, b json, c text, x int);
+ a | b | c | x | c_is_null
+---+-----------------+---+---+-----------
+ 1 | {"c":16, "d":2} | | 8 | t
+(1 row)
+
+select * from json_to_record('{"ia": null}') as x(ia _int4);
+ ia
+----
+
+(1 row)
+
+select * from json_to_record('{"ia": 123}') as x(ia _int4);
+ERROR: expected JSON array
+HINT: See the value of key "ia".
+select * from json_to_record('{"ia": [1, "2", null, 4]}') as x(ia _int4);
+ ia
+--------------
+ {1,2,NULL,4}
+(1 row)
+
+select * from json_to_record('{"ia": [[1, 2], [3, 4]]}') as x(ia _int4);
+ ia
+---------------
+ {{1,2},{3,4}}
+(1 row)
+
+select * from json_to_record('{"ia": [[1], 2]}') as x(ia _int4);
+ERROR: expected JSON array
+HINT: See the array element [1] of key "ia".
+select * from json_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 json_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]);
+ ia2
+---------
+ {1,2,3}
+(1 row)
+
+select * from json_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]);
+ ia2
+---------------
+ {{1,2},{3,4}}
+(1 row)
+
+select * from json_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]);
+ ia2
+-----------------
+ {{{1},{2},{3}}}
+(1 row)
+
+select * from json_to_record('{"out": {"key": 1}}') as x(out json);
+ out
+------------
+ {"key": 1}
+(1 row)
+
+select * from json_to_record('{"out": [{"key": 1}]}') as x(out json);
+ out
+--------------
+ [{"key": 1}]
+(1 row)
+
+select * from json_to_record('{"out": "{\"key\": 1}"}') as x(out json);
+ out
+----------------
+ "{\"key\": 1}"
+(1 row)
+
+select * from json_to_record('{"out": {"key": 1}}') as x(out jsonb);
+ out
+------------
+ {"key": 1}
+(1 row)
+
+select * from json_to_record('{"out": [{"key": 1}]}') as x(out jsonb);
+ out
+--------------
+ [{"key": 1}]
+(1 row)
+
+select * from json_to_record('{"out": "{\"key\": 1}"}') as x(out jsonb);
+ out
+----------------
+ "{\"key\": 1}"
+(1 row)
+
+-- json_strip_nulls
+select json_strip_nulls(null);
+ json_strip_nulls
+------------------
+
+(1 row)
+
+select json_strip_nulls('1');
+ json_strip_nulls
+------------------
+ 1
+(1 row)
+
+select json_strip_nulls('"a string"');
+ json_strip_nulls
+------------------
+ "a string"
+(1 row)
+
+select json_strip_nulls('null');
+ json_strip_nulls
+------------------
+ null
+(1 row)
+
+select json_strip_nulls('[1,2,null,3,4]');
+ json_strip_nulls
+------------------
+ [1,2,null,3,4]
+(1 row)
+
+select json_strip_nulls('{"a":1,"b":null,"c":[2,null,3],"d":{"e":4,"f":null}}');
+ json_strip_nulls
+------------------------------------
+ {"a":1,"c":[2,null,3],"d":{"e":4}}
+(1 row)
+
+select json_strip_nulls('[1,{"a":1,"b":null,"c":2},3]');
+ json_strip_nulls
+---------------------
+ [1,{"a":1,"c":2},3]
+(1 row)
+
+-- an empty object is not null and should not be stripped
+select json_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }');
+ json_strip_nulls
+------------------
+ {"a":{},"d":{}}
+(1 row)
+
+-- json to tsvector
+select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::json);
+ to_tsvector
+---------------------------------------------------------------------------
+ 'aaa':1 'bbb':2 'ccc':4 'ddd':3 'eee':6 'fff':7 'ggg':8 'hhh':10 'iii':11
+(1 row)
+
+-- json to tsvector with config
+select to_tsvector('simple', '{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::json);
+ to_tsvector
+---------------------------------------------------------------------------
+ 'aaa':1 'bbb':2 'ccc':4 'ddd':3 'eee':6 'fff':7 'ggg':8 'hhh':10 'iii':11
+(1 row)
+
+-- json to tsvector with stop words
+select to_tsvector('english', '{"a": "aaa in bbb ddd ccc", "b": ["the eee fff ggg"], "c": {"d": "hhh. iii"}}'::json);
+ to_tsvector
+----------------------------------------------------------------------------
+ 'aaa':1 'bbb':3 'ccc':5 'ddd':4 'eee':8 'fff':9 'ggg':10 'hhh':12 'iii':13
+(1 row)
+
+-- json to tsvector with numeric values
+select to_tsvector('english', '{"a": "aaa in bbb ddd ccc", "b": 123, "c": 456}'::json);
+ to_tsvector
+---------------------------------
+ 'aaa':1 'bbb':3 'ccc':5 'ddd':4
+(1 row)
+
+-- json_to_tsvector
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"all"');
+ json_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 json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"key"');
+ json_to_tsvector
+--------------------------------
+ 'b':2 'c':4 'd':6 'f':8 'g':10
+(1 row)
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"string"');
+ json_to_tsvector
+------------------
+ 'aaa':1 'bbb':3
+(1 row)
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"numeric"');
+ json_to_tsvector
+------------------
+ '123':1 '456':3
+(1 row)
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"boolean"');
+ json_to_tsvector
+-------------------
+ 'fals':3 'true':1
+(1 row)
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '["string", "numeric"]');
+ json_to_tsvector
+---------------------------------
+ '123':5 '456':7 'aaa':1 'bbb':3
+(1 row)
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"all"');
+ json_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 json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"key"');
+ json_to_tsvector
+--------------------------------
+ 'b':2 'c':4 'd':6 'f':8 'g':10
+(1 row)
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"string"');
+ json_to_tsvector
+------------------
+ 'aaa':1 'bbb':3
+(1 row)
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"numeric"');
+ json_to_tsvector
+------------------
+ '123':1 '456':3
+(1 row)
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"boolean"');
+ json_to_tsvector
+-------------------
+ 'fals':3 'true':1
+(1 row)
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '["string", "numeric"]');
+ json_to_tsvector
+---------------------------------
+ '123':5 '456':7 'aaa':1 'bbb':3
+(1 row)
+
+-- to_tsvector corner cases
+select to_tsvector('""'::json);
+ to_tsvector
+-------------
+
+(1 row)
+
+select to_tsvector('{}'::json);
+ to_tsvector
+-------------
+
+(1 row)
+
+select to_tsvector('[]'::json);
+ to_tsvector
+-------------
+
+(1 row)
+
+select to_tsvector('null'::json);
+ to_tsvector
+-------------
+
+(1 row)
+
+-- json_to_tsvector corner cases
+select json_to_tsvector('""'::json, '"all"');
+ json_to_tsvector
+------------------
+
+(1 row)
+
+select json_to_tsvector('{}'::json, '"all"');
+ json_to_tsvector
+------------------
+
+(1 row)
+
+select json_to_tsvector('[]'::json, '"all"');
+ json_to_tsvector
+------------------
+
+(1 row)
+
+select json_to_tsvector('null'::json, '"all"');
+ json_to_tsvector
+------------------
+
+(1 row)
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '""');
+ERROR: wrong flag in flag array: ""
+HINT: Possible values are: "string", "numeric", "boolean", "key", and "all".
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '{}');
+ERROR: wrong flag type, only arrays and scalars are allowed
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '[]');
+ json_to_tsvector
+------------------
+
+(1 row)
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, 'null');
+ERROR: flag array element is not a string
+HINT: Possible values are: "string", "numeric", "boolean", "key", and "all".
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '["all", null]');
+ERROR: flag array element is not a string
+HINT: Possible values are: "string", "numeric", "boolean", "key", and "all".
+-- ts_headline for json
+select ts_headline('{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]}'::json, tsquery('bbb & ddd & hhh'));
+ ts_headline
+---------------------------------------------------------------------------------------------------------
+ {"a":"aaa <b>bbb</b>","b":{"c":"ccc <b>ddd</b> fff","c1":"ccc1 ddd1"},"d":["ggg <b>hhh</b>","iii jjj"]}
+(1 row)
+
+select ts_headline('english', '{"a": "aaa bbb", "b": {"c": "ccc ddd fff"}, "d": ["ggg hhh", "iii jjj"]}'::json, tsquery('bbb & ddd & hhh'));
+ ts_headline
+----------------------------------------------------------------------------------------
+ {"a":"aaa <b>bbb</b>","b":{"c":"ccc <b>ddd</b> fff"},"d":["ggg <b>hhh</b>","iii jjj"]}
+(1 row)
+
+select ts_headline('{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]}'::json, tsquery('bbb & ddd & hhh'), 'StartSel = <, StopSel = >');
+ 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", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]}'::json, tsquery('bbb & ddd & hhh'), 'StartSel = <, StopSel = >');
+ ts_headline
+------------------------------------------------------------------------------------------
+ {"a":"aaa <bbb>","b":{"c":"ccc <ddd> fff","c1":"ccc1 ddd1"},"d":["ggg <hhh>","iii jjj"]}
+(1 row)
+
+-- corner cases for ts_headline with json
+select ts_headline('null'::json, tsquery('aaa & bbb'));
+ ts_headline
+-------------
+ null
+(1 row)
+
+select ts_headline('{}'::json, tsquery('aaa & bbb'));
+ ts_headline
+-------------
+ {}
+(1 row)
+
+select ts_headline('[]'::json, tsquery('aaa & bbb'));
+ ts_headline
+-------------
+ []
+(1 row)
+
diff --git a/src/test/regress/expected/json_encoding.out b/src/test/regress/expected/json_encoding.out
new file mode 100644
index 0000000..f343f74
--- /dev/null
+++ b/src/test/regress/expected/json_encoding.out
@@ -0,0 +1,262 @@
+--
+-- encoding-sensitive tests for json and jsonb
+--
+-- We provide expected-results files for UTF8 (json_encoding.out)
+-- and for SQL_ASCII (json_encoding_1.out). Skip otherwise.
+SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
+ AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+SELECT getdatabaseencoding(); -- just to label the results files
+ getdatabaseencoding
+---------------------
+ UTF8
+(1 row)
+
+-- first json
+-- basic unicode input
+SELECT '"\u"'::json; -- ERROR, incomplete escape
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\u"'::json;
+ ^
+DETAIL: "\u" must be followed by four hexadecimal digits.
+CONTEXT: JSON data, line 1: "\u"
+SELECT '"\u00"'::json; -- ERROR, incomplete escape
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\u00"'::json;
+ ^
+DETAIL: "\u" must be followed by four hexadecimal digits.
+CONTEXT: JSON data, line 1: "\u00"
+SELECT '"\u000g"'::json; -- ERROR, g is not a hex digit
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\u000g"'::json;
+ ^
+DETAIL: "\u" must be followed by four hexadecimal digits.
+CONTEXT: JSON data, line 1: "\u000g...
+SELECT '"\u0000"'::json; -- OK, legal escape
+ json
+----------
+ "\u0000"
+(1 row)
+
+SELECT '"\uaBcD"'::json; -- OK, uppercase and lower case both OK
+ json
+----------
+ "\uaBcD"
+(1 row)
+
+-- handling of unicode surrogate pairs
+select json '{ "a": "\ud83d\ude04\ud83d\udc36" }' -> 'a' as correct_in_utf8;
+ correct_in_utf8
+----------------------------
+ "\ud83d\ude04\ud83d\udc36"
+(1 row)
+
+select json '{ "a": "\ud83d\ud83d" }' -> 'a'; -- 2 high surrogates in a row
+ERROR: invalid input syntax for type json
+DETAIL: Unicode high surrogate must not follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+select json '{ "a": "\ude04\ud83d" }' -> 'a'; -- surrogates in wrong order
+ERROR: invalid input syntax for type json
+DETAIL: Unicode low surrogate must follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+select json '{ "a": "\ud83dX" }' -> 'a'; -- orphan high surrogate
+ERROR: invalid input syntax for type json
+DETAIL: Unicode low surrogate must follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+select json '{ "a": "\ude04X" }' -> 'a'; -- orphan low surrogate
+ERROR: invalid input syntax for type json
+DETAIL: Unicode low surrogate must follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+--handling of simple unicode escapes
+select json '{ "a": "the Copyright \u00a9 sign" }' as correct_in_utf8;
+ correct_in_utf8
+---------------------------------------
+ { "a": "the Copyright \u00a9 sign" }
+(1 row)
+
+select json '{ "a": "dollar \u0024 character" }' as correct_everywhere;
+ correct_everywhere
+-------------------------------------
+ { "a": "dollar \u0024 character" }
+(1 row)
+
+select json '{ "a": "dollar \\u0024 character" }' as not_an_escape;
+ not_an_escape
+--------------------------------------
+ { "a": "dollar \\u0024 character" }
+(1 row)
+
+select json '{ "a": "null \u0000 escape" }' as not_unescaped;
+ not_unescaped
+--------------------------------
+ { "a": "null \u0000 escape" }
+(1 row)
+
+select json '{ "a": "null \\u0000 escape" }' as not_an_escape;
+ not_an_escape
+---------------------------------
+ { "a": "null \\u0000 escape" }
+(1 row)
+
+select json '{ "a": "the Copyright \u00a9 sign" }' ->> 'a' as correct_in_utf8;
+ correct_in_utf8
+----------------------
+ the Copyright © sign
+(1 row)
+
+select json '{ "a": "dollar \u0024 character" }' ->> 'a' as correct_everywhere;
+ correct_everywhere
+--------------------
+ dollar $ character
+(1 row)
+
+select json '{ "a": "dollar \\u0024 character" }' ->> 'a' as not_an_escape;
+ not_an_escape
+-------------------------
+ dollar \u0024 character
+(1 row)
+
+select json '{ "a": "null \u0000 escape" }' ->> 'a' as fails;
+ERROR: unsupported Unicode escape sequence
+DETAIL: \u0000 cannot be converted to text.
+CONTEXT: JSON data, line 1: { "a":...
+select json '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape;
+ not_an_escape
+--------------------
+ null \u0000 escape
+(1 row)
+
+-- then jsonb
+-- basic unicode input
+SELECT '"\u"'::jsonb; -- ERROR, incomplete escape
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\u"'::jsonb;
+ ^
+DETAIL: "\u" must be followed by four hexadecimal digits.
+CONTEXT: JSON data, line 1: "\u"
+SELECT '"\u00"'::jsonb; -- ERROR, incomplete escape
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\u00"'::jsonb;
+ ^
+DETAIL: "\u" must be followed by four hexadecimal digits.
+CONTEXT: JSON data, line 1: "\u00"
+SELECT '"\u000g"'::jsonb; -- ERROR, g is not a hex digit
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\u000g"'::jsonb;
+ ^
+DETAIL: "\u" must be followed by four hexadecimal digits.
+CONTEXT: JSON data, line 1: "\u000g...
+SELECT '"\u0045"'::jsonb; -- OK, legal escape
+ jsonb
+-------
+ "E"
+(1 row)
+
+SELECT '"\u0000"'::jsonb; -- ERROR, we don't support U+0000
+ERROR: unsupported Unicode escape sequence
+LINE 1: SELECT '"\u0000"'::jsonb;
+ ^
+DETAIL: \u0000 cannot be converted to text.
+CONTEXT: JSON data, line 1: ...
+-- use octet_length here so we don't get an odd unicode char in the
+-- output
+SELECT octet_length('"\uaBcD"'::jsonb::text); -- OK, uppercase and lower case both OK
+ octet_length
+--------------
+ 5
+(1 row)
+
+-- handling of unicode surrogate pairs
+SELECT octet_length((jsonb '{ "a": "\ud83d\ude04\ud83d\udc36" }' -> 'a')::text) AS correct_in_utf8;
+ correct_in_utf8
+-----------------
+ 10
+(1 row)
+
+SELECT jsonb '{ "a": "\ud83d\ud83d" }' -> 'a'; -- 2 high surrogates in a row
+ERROR: invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a": "\ud83d\ud83d" }' -> 'a';
+ ^
+DETAIL: Unicode high surrogate must not follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+SELECT jsonb '{ "a": "\ude04\ud83d" }' -> 'a'; -- surrogates in wrong order
+ERROR: invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a": "\ude04\ud83d" }' -> 'a';
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+SELECT jsonb '{ "a": "\ud83dX" }' -> 'a'; -- orphan high surrogate
+ERROR: invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a": "\ud83dX" }' -> 'a';
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+SELECT jsonb '{ "a": "\ude04X" }' -> 'a'; -- orphan low surrogate
+ERROR: invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a": "\ude04X" }' -> 'a';
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+-- handling of simple unicode escapes
+SELECT jsonb '{ "a": "the Copyright \u00a9 sign" }' as correct_in_utf8;
+ correct_in_utf8
+-------------------------------
+ {"a": "the Copyright © sign"}
+(1 row)
+
+SELECT jsonb '{ "a": "dollar \u0024 character" }' as correct_everywhere;
+ correct_everywhere
+-----------------------------
+ {"a": "dollar $ character"}
+(1 row)
+
+SELECT jsonb '{ "a": "dollar \\u0024 character" }' as not_an_escape;
+ not_an_escape
+-----------------------------------
+ {"a": "dollar \\u0024 character"}
+(1 row)
+
+SELECT jsonb '{ "a": "null \u0000 escape" }' as fails;
+ERROR: unsupported Unicode escape sequence
+LINE 1: SELECT jsonb '{ "a": "null \u0000 escape" }' as fails;
+ ^
+DETAIL: \u0000 cannot be converted to text.
+CONTEXT: JSON data, line 1: { "a":...
+SELECT jsonb '{ "a": "null \\u0000 escape" }' as not_an_escape;
+ not_an_escape
+------------------------------
+ {"a": "null \\u0000 escape"}
+(1 row)
+
+SELECT jsonb '{ "a": "the Copyright \u00a9 sign" }' ->> 'a' as correct_in_utf8;
+ correct_in_utf8
+----------------------
+ the Copyright © sign
+(1 row)
+
+SELECT jsonb '{ "a": "dollar \u0024 character" }' ->> 'a' as correct_everywhere;
+ correct_everywhere
+--------------------
+ dollar $ character
+(1 row)
+
+SELECT jsonb '{ "a": "dollar \\u0024 character" }' ->> 'a' as not_an_escape;
+ not_an_escape
+-------------------------
+ dollar \u0024 character
+(1 row)
+
+SELECT jsonb '{ "a": "null \u0000 escape" }' ->> 'a' as fails;
+ERROR: unsupported Unicode escape sequence
+LINE 1: SELECT jsonb '{ "a": "null \u0000 escape" }' ->> 'a' as fai...
+ ^
+DETAIL: \u0000 cannot be converted to text.
+CONTEXT: JSON data, line 1: { "a":...
+SELECT jsonb '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape;
+ not_an_escape
+--------------------
+ null \u0000 escape
+(1 row)
+
diff --git a/src/test/regress/expected/json_encoding_1.out b/src/test/regress/expected/json_encoding_1.out
new file mode 100644
index 0000000..e2fc131
--- /dev/null
+++ b/src/test/regress/expected/json_encoding_1.out
@@ -0,0 +1,246 @@
+--
+-- encoding-sensitive tests for json and jsonb
+--
+-- We provide expected-results files for UTF8 (json_encoding.out)
+-- and for SQL_ASCII (json_encoding_1.out). Skip otherwise.
+SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
+ AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+SELECT getdatabaseencoding(); -- just to label the results files
+ getdatabaseencoding
+---------------------
+ SQL_ASCII
+(1 row)
+
+-- first json
+-- basic unicode input
+SELECT '"\u"'::json; -- ERROR, incomplete escape
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\u"'::json;
+ ^
+DETAIL: "\u" must be followed by four hexadecimal digits.
+CONTEXT: JSON data, line 1: "\u"
+SELECT '"\u00"'::json; -- ERROR, incomplete escape
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\u00"'::json;
+ ^
+DETAIL: "\u" must be followed by four hexadecimal digits.
+CONTEXT: JSON data, line 1: "\u00"
+SELECT '"\u000g"'::json; -- ERROR, g is not a hex digit
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\u000g"'::json;
+ ^
+DETAIL: "\u" must be followed by four hexadecimal digits.
+CONTEXT: JSON data, line 1: "\u000g...
+SELECT '"\u0000"'::json; -- OK, legal escape
+ json
+----------
+ "\u0000"
+(1 row)
+
+SELECT '"\uaBcD"'::json; -- OK, uppercase and lower case both OK
+ json
+----------
+ "\uaBcD"
+(1 row)
+
+-- handling of unicode surrogate pairs
+select json '{ "a": "\ud83d\ude04\ud83d\udc36" }' -> 'a' as correct_in_utf8;
+ERROR: conversion between UTF8 and SQL_ASCII is not supported
+select json '{ "a": "\ud83d\ud83d" }' -> 'a'; -- 2 high surrogates in a row
+ERROR: invalid input syntax for type json
+DETAIL: Unicode high surrogate must not follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+select json '{ "a": "\ude04\ud83d" }' -> 'a'; -- surrogates in wrong order
+ERROR: invalid input syntax for type json
+DETAIL: Unicode low surrogate must follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+select json '{ "a": "\ud83dX" }' -> 'a'; -- orphan high surrogate
+ERROR: invalid input syntax for type json
+DETAIL: Unicode low surrogate must follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+select json '{ "a": "\ude04X" }' -> 'a'; -- orphan low surrogate
+ERROR: invalid input syntax for type json
+DETAIL: Unicode low surrogate must follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+--handling of simple unicode escapes
+select json '{ "a": "the Copyright \u00a9 sign" }' as correct_in_utf8;
+ correct_in_utf8
+---------------------------------------
+ { "a": "the Copyright \u00a9 sign" }
+(1 row)
+
+select json '{ "a": "dollar \u0024 character" }' as correct_everywhere;
+ correct_everywhere
+-------------------------------------
+ { "a": "dollar \u0024 character" }
+(1 row)
+
+select json '{ "a": "dollar \\u0024 character" }' as not_an_escape;
+ not_an_escape
+--------------------------------------
+ { "a": "dollar \\u0024 character" }
+(1 row)
+
+select json '{ "a": "null \u0000 escape" }' as not_unescaped;
+ not_unescaped
+--------------------------------
+ { "a": "null \u0000 escape" }
+(1 row)
+
+select json '{ "a": "null \\u0000 escape" }' as not_an_escape;
+ not_an_escape
+---------------------------------
+ { "a": "null \\u0000 escape" }
+(1 row)
+
+select json '{ "a": "the Copyright \u00a9 sign" }' ->> 'a' as correct_in_utf8;
+ERROR: conversion between UTF8 and SQL_ASCII is not supported
+select json '{ "a": "dollar \u0024 character" }' ->> 'a' as correct_everywhere;
+ correct_everywhere
+--------------------
+ dollar $ character
+(1 row)
+
+select json '{ "a": "dollar \\u0024 character" }' ->> 'a' as not_an_escape;
+ not_an_escape
+-------------------------
+ dollar \u0024 character
+(1 row)
+
+select json '{ "a": "null \u0000 escape" }' ->> 'a' as fails;
+ERROR: unsupported Unicode escape sequence
+DETAIL: \u0000 cannot be converted to text.
+CONTEXT: JSON data, line 1: { "a":...
+select json '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape;
+ not_an_escape
+--------------------
+ null \u0000 escape
+(1 row)
+
+-- then jsonb
+-- basic unicode input
+SELECT '"\u"'::jsonb; -- ERROR, incomplete escape
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\u"'::jsonb;
+ ^
+DETAIL: "\u" must be followed by four hexadecimal digits.
+CONTEXT: JSON data, line 1: "\u"
+SELECT '"\u00"'::jsonb; -- ERROR, incomplete escape
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\u00"'::jsonb;
+ ^
+DETAIL: "\u" must be followed by four hexadecimal digits.
+CONTEXT: JSON data, line 1: "\u00"
+SELECT '"\u000g"'::jsonb; -- ERROR, g is not a hex digit
+ERROR: invalid input syntax for type json
+LINE 1: SELECT '"\u000g"'::jsonb;
+ ^
+DETAIL: "\u" must be followed by four hexadecimal digits.
+CONTEXT: JSON data, line 1: "\u000g...
+SELECT '"\u0045"'::jsonb; -- OK, legal escape
+ jsonb
+-------
+ "E"
+(1 row)
+
+SELECT '"\u0000"'::jsonb; -- ERROR, we don't support U+0000
+ERROR: unsupported Unicode escape sequence
+LINE 1: SELECT '"\u0000"'::jsonb;
+ ^
+DETAIL: \u0000 cannot be converted to text.
+CONTEXT: JSON data, line 1: ...
+-- use octet_length here so we don't get an odd unicode char in the
+-- output
+SELECT octet_length('"\uaBcD"'::jsonb::text); -- OK, uppercase and lower case both OK
+ERROR: conversion between UTF8 and SQL_ASCII is not supported
+LINE 1: SELECT octet_length('"\uaBcD"'::jsonb::text);
+ ^
+-- handling of unicode surrogate pairs
+SELECT octet_length((jsonb '{ "a": "\ud83d\ude04\ud83d\udc36" }' -> 'a')::text) AS correct_in_utf8;
+ERROR: conversion between UTF8 and SQL_ASCII is not supported
+LINE 1: SELECT octet_length((jsonb '{ "a": "\ud83d\ude04\ud83d\udc3...
+ ^
+SELECT jsonb '{ "a": "\ud83d\ud83d" }' -> 'a'; -- 2 high surrogates in a row
+ERROR: invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a": "\ud83d\ud83d" }' -> 'a';
+ ^
+DETAIL: Unicode high surrogate must not follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+SELECT jsonb '{ "a": "\ude04\ud83d" }' -> 'a'; -- surrogates in wrong order
+ERROR: invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a": "\ude04\ud83d" }' -> 'a';
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+SELECT jsonb '{ "a": "\ud83dX" }' -> 'a'; -- orphan high surrogate
+ERROR: invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a": "\ud83dX" }' -> 'a';
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+SELECT jsonb '{ "a": "\ude04X" }' -> 'a'; -- orphan low surrogate
+ERROR: invalid input syntax for type json
+LINE 1: SELECT jsonb '{ "a": "\ude04X" }' -> 'a';
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+CONTEXT: JSON data, line 1: { "a":...
+-- handling of simple unicode escapes
+SELECT jsonb '{ "a": "the Copyright \u00a9 sign" }' as correct_in_utf8;
+ERROR: conversion between UTF8 and SQL_ASCII is not supported
+LINE 1: SELECT jsonb '{ "a": "the Copyright \u00a9 sign" }' as corr...
+ ^
+SELECT jsonb '{ "a": "dollar \u0024 character" }' as correct_everywhere;
+ correct_everywhere
+-----------------------------
+ {"a": "dollar $ character"}
+(1 row)
+
+SELECT jsonb '{ "a": "dollar \\u0024 character" }' as not_an_escape;
+ not_an_escape
+-----------------------------------
+ {"a": "dollar \\u0024 character"}
+(1 row)
+
+SELECT jsonb '{ "a": "null \u0000 escape" }' as fails;
+ERROR: unsupported Unicode escape sequence
+LINE 1: SELECT jsonb '{ "a": "null \u0000 escape" }' as fails;
+ ^
+DETAIL: \u0000 cannot be converted to text.
+CONTEXT: JSON data, line 1: { "a":...
+SELECT jsonb '{ "a": "null \\u0000 escape" }' as not_an_escape;
+ not_an_escape
+------------------------------
+ {"a": "null \\u0000 escape"}
+(1 row)
+
+SELECT jsonb '{ "a": "the Copyright \u00a9 sign" }' ->> 'a' as correct_in_utf8;
+ERROR: conversion between UTF8 and SQL_ASCII is not supported
+LINE 1: SELECT jsonb '{ "a": "the Copyright \u00a9 sign" }' ->> 'a'...
+ ^
+SELECT jsonb '{ "a": "dollar \u0024 character" }' ->> 'a' as correct_everywhere;
+ correct_everywhere
+--------------------
+ dollar $ character
+(1 row)
+
+SELECT jsonb '{ "a": "dollar \\u0024 character" }' ->> 'a' as not_an_escape;
+ not_an_escape
+-------------------------
+ dollar \u0024 character
+(1 row)
+
+SELECT jsonb '{ "a": "null \u0000 escape" }' ->> 'a' as fails;
+ERROR: unsupported Unicode escape sequence
+LINE 1: SELECT jsonb '{ "a": "null \u0000 escape" }' ->> 'a' as fai...
+ ^
+DETAIL: \u0000 cannot be converted to text.
+CONTEXT: JSON data, line 1: { "a":...
+SELECT jsonb '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape;
+ not_an_escape
+--------------------
+ null \u0000 escape
+(1 row)
+
diff --git a/src/test/regress/expected/json_encoding_2.out b/src/test/regress/expected/json_encoding_2.out
new file mode 100644
index 0000000..4fc8f02
--- /dev/null
+++ b/src/test/regress/expected/json_encoding_2.out
@@ -0,0 +1,9 @@
+--
+-- encoding-sensitive tests for json and jsonb
+--
+-- We provide expected-results files for UTF8 (json_encoding.out)
+-- and for SQL_ASCII (json_encoding_1.out). Skip otherwise.
+SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
+ AS skip_test \gset
+\if :skip_test
+\quit
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
new file mode 100644
index 0000000..a9cd145
--- /dev/null
+++ b/src/test/regress/expected/jsonb.out
@@ -0,0 +1,5511 @@
+-- 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
+-- 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;
+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 @> '{}';
+ 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)
+
+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)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null';
+ QUERY PLAN
+-----------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @@ '($."wait" == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @@ '($."wait" == null)'::jsonpath)
+(5 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($)';
+ 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)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ QUERY PLAN
+-------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+(5 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 @? '$';
+ 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)
+
+-- 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 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);
+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;
+CREATE INDEX jidx ON testjsonb USING gin (j jsonb_path_ops);
+SET enable_seqscan = off;
+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 @> '{}';
+ count
+-------
+ 1012
+(1 row)
+
+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($)';
+ count
+-------
+ 1012
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ QUERY PLAN
+-------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+(5 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 @? '$';
+ 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)
+
+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.
+-- 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 <b>bbb</b>", "b": {"c": "ccc <b>ddd</b> fff", "c1": "ccc1 ddd1"}, "d": ["ggg <b>hhh</b>", "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 <b>bbb</b>", "b": {"c": "ccc <b>ddd</b> fff"}, "d": ["ggg <b>hhh</b>", "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 <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", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]}'::jsonb, tsquery('bbb & ddd & hhh'), 'StartSel = <, StopSel = >');
+ ts_headline
+---------------------------------------------------------------------------------------------------
+ {"a": "aaa <bbb>", "b": {"c": "ccc <ddd> fff", "c1": "ccc1 ddd1"}, "d": ["ggg <hhh>", "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/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
new file mode 100644
index 0000000..508ddd7
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,2539 @@
+select jsonb '{"a": 12}' @? '$';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12}' @? '1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.a + 2';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b + 2';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? 'strict $.*.b';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb_path_query('[1]', 'strict $[1]');
+ERROR: jsonpath array subscript is out of bounds
+select jsonb_path_query('[1]', 'strict $[1]', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb '[1]' @? 'lax $[10000000000000000]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1]' @? 'strict $[10000000000000000]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb_path_query('[1]', 'lax $[10000000000000000]');
+ERROR: jsonpath array subscript is out of integer range
+select jsonb_path_query('[1]', 'strict $[10000000000000000]');
+ERROR: jsonpath array subscript is out of integer range
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_exists('[{"a": 1}, {"a": 2}, 3]', 'lax $[*].a', silent => false);
+ jsonb_path_exists
+-------------------
+ t
+(1 row)
+
+select jsonb_path_exists('[{"a": 1}, {"a": 2}, 3]', 'lax $[*].a', silent => true);
+ jsonb_path_exists
+-------------------
+ t
+(1 row)
+
+select jsonb_path_exists('[{"a": 1}, {"a": 2}, 3]', 'strict $[*].a', silent => false);
+ERROR: jsonpath member accessor can only be applied to an object
+select jsonb_path_exists('[{"a": 1}, {"a": 2}, 3]', 'strict $[*].a', silent => true);
+ jsonb_path_exists
+-------------------
+
+(1 row)
+
+select jsonb_path_query('1', 'lax $.a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('1', 'strict $.a');
+ERROR: jsonpath member accessor can only be applied to an object
+select jsonb_path_query('1', 'strict $.*');
+ERROR: jsonpath wildcard member accessor can only be applied to an object
+select jsonb_path_query('1', 'strict $.a', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('1', 'strict $.*', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'lax $.a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.a');
+ERROR: jsonpath member accessor can only be applied to an object
+select jsonb_path_query('[]', 'strict $.a', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{}', 'lax $.a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{}', 'strict $.a');
+ERROR: JSON object does not contain key "a"
+select jsonb_path_query('{}', 'strict $.a', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('1', 'strict $[1]');
+ERROR: jsonpath array accessor can only be applied to an array
+select jsonb_path_query('1', 'strict $[*]');
+ERROR: jsonpath wildcard array accessor can only be applied to an array
+select jsonb_path_query('[]', 'strict $[1]');
+ERROR: jsonpath array subscript is out of bounds
+select jsonb_path_query('[]', 'strict $["a"]');
+ERROR: jsonpath array subscript is not a single numeric value
+select jsonb_path_query('1', 'strict $[1]', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('1', 'strict $[*]', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $[1]', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $["a"]', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.a');
+ jsonb_path_query
+------------------
+ 12
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.b');
+ jsonb_path_query
+------------------
+ {"a": 13}
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.*');
+ jsonb_path_query
+------------------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+ jsonb_path_query
+------------------
+ 13
+ 14
+(2 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10 / 0].a');
+ERROR: division by zero
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+ jsonb_path_query
+------------------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb_path_query('1', 'lax $[0]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('1', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1]', 'lax $[0]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1]', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1,2,3]', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb_path_query('[1,2,3]', 'strict $[*].a');
+ERROR: jsonpath member accessor can only be applied to an object
+select jsonb_path_query('[1,2,3]', 'strict $[*].a', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', '$[last]');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', '$[last ? (exists(last))]');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $[last]');
+ERROR: jsonpath array subscript is out of bounds
+select jsonb_path_query('[]', 'strict $[last]', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[1]', '$[last]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last]');
+ jsonb_path_query
+------------------
+ 3
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last - 1]');
+ jsonb_path_query
+------------------
+ 2
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
+ jsonb_path_query
+------------------
+ 3
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "string")]');
+ERROR: jsonpath array subscript is not a single numeric value
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "string")]', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select * from jsonb_path_query('{"a": 10}', '$');
+ jsonb_path_query
+------------------
+ {"a": 10}
+(1 row)
+
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)');
+ERROR: could not find jsonpath variable "value"
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '1');
+ERROR: "vars" argument is not an object
+DETAIL: Jsonpath parameters should be encoded as key-value pairs of "vars" object.
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '[{"value" : 13}]');
+ERROR: "vars" argument is not an object
+DETAIL: Jsonpath parameters should be encoded as key-value pairs of "vars" object.
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ {"a": 10}
+(1 row)
+
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 8}');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select * from jsonb_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+(1 row)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $x.value)', '{"x": {"value" : 13}}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonb_path_query
+------------------
+ "1"
+(1 row)
+
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonb_path_query
+------------------
+ "1"
+(1 row)
+
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : null}');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ != null)');
+ jsonb_path_query
+------------------
+ 1
+ "2"
+(2 rows)
+
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ == null)');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select * from jsonb_path_query('{}', '$ ? (@ == @)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select * from jsonb_path_query('[]', 'strict $ ? (@ == @)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}');
+ jsonb_path_query
+------------------
+ {"b": 1}
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}');
+ jsonb_path_query
+------------------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2}');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{last}');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))');
+ jsonb_path_query
+------------------
+ {"x": 2}
+(1 row)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
+ jsonb_path_query
+------------------
+ {"x": 2}
+(1 row)
+
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? (exists (@.x))');
+ jsonb_path_query
+------------------
+ {"x": 2}
+(1 row)
+
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? (exists (@.x + "3"))');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? ((exists (@.x + "3")) is unknown)');
+ jsonb_path_query
+------------------
+ {"x": 2}
+ {"y": 3}
+(2 rows)
+
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g[*] ? (exists (@.x))');
+ jsonb_path_query
+------------------
+ {"x": 2}
+(1 row)
+
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g[*] ? ((exists (@.x)) is unknown)');
+ jsonb_path_query
+------------------
+ {"y": 3}
+(1 row)
+
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g ? (exists (@[*].x))');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g ? ((exists (@[*].x)) is unknown)');
+ jsonb_path_query
+----------------------
+ [{"x": 2}, {"y": 3}]
+(1 row)
+
+--test ternary logic
+select
+ x, y,
+ jsonb_path_query(
+ '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true && $y == true) ||
+ @ == false && !($x == true && $y == true) ||
+ @ == null && ($x == true && $y == true) is unknown)',
+ jsonb_build_object('x', x, 'y', y)
+ ) as "x && y"
+from
+ (values (jsonb 'true'), ('false'), ('"null"')) x(x),
+ (values (jsonb 'true'), ('false'), ('"null"')) y(y);
+ x | y | x && y
+--------+--------+--------
+ true | true | true
+ true | false | false
+ true | "null" | null
+ false | true | false
+ false | false | false
+ false | "null" | false
+ "null" | true | null
+ "null" | false | false
+ "null" | "null" | null
+(9 rows)
+
+select
+ x, y,
+ jsonb_path_query(
+ '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true || $y == true) ||
+ @ == false && !($x == true || $y == true) ||
+ @ == null && ($x == true || $y == true) is unknown)',
+ jsonb_build_object('x', x, 'y', y)
+ ) as "x || y"
+from
+ (values (jsonb 'true'), ('false'), ('"null"')) x(x),
+ (values (jsonb 'true'), ('false'), ('"null"')) y(y);
+ x | y | x || y
+--------+--------+--------
+ true | true | true
+ true | false | true
+ true | "null" | true
+ false | true | true
+ false | false | false
+ false | "null" | null
+ "null" | true | true
+ "null" | false | null
+ "null" | "null" | null
+(9 rows)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (@.a == @.b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == 1 + 1)');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (1 + 1))');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == @.b + 1)');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (@.b + 1))');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -@.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (@.a == 1 - - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - +@.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- arithmetic errors
+select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+ jsonb_path_query
+------------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+ jsonb_path_query
+------------------
+ 0
+(1 row)
+
+select jsonb_path_query('0', '1 / $');
+ERROR: division by zero
+select jsonb_path_query('0', '1 / $ + 2');
+ERROR: division by zero
+select jsonb_path_query('0', '-(3 + 1 % $)');
+ERROR: division by zero
+select jsonb_path_query('1', '$ + "2"');
+ERROR: right operand of jsonpath operator + is not a single numeric value
+select jsonb_path_query('[1, 2]', '3 * $');
+ERROR: right operand of jsonpath operator * is not a single numeric value
+select jsonb_path_query('"a"', '-$');
+ERROR: operand of unary jsonpath operator - is not a numeric value
+select jsonb_path_query('[1,"2",3]', '+$');
+ERROR: operand of unary jsonpath operator + is not a numeric value
+select jsonb_path_query('1', '$ + "2"', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[1, 2]', '3 * $', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('"a"', '-$', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[1,"2",3]', '+$', silent => true);
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb '["1",2,0,3]' @? '-$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,"2",0,3]' @? '-$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '["1",2,0,3]' @? 'strict -$[*]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1,"2",0,3]' @? 'strict -$[*]';
+ ?column?
+----------
+
+(1 row)
+
+-- unwrapping of operator arguments in lax mode
+select jsonb_path_query('{"a": [2]}', 'lax $.a * 3');
+ jsonb_path_query
+------------------
+ 6
+(1 row)
+
+select jsonb_path_query('{"a": [2]}', 'lax $.a + 3');
+ jsonb_path_query
+------------------
+ 5
+(1 row)
+
+select jsonb_path_query('{"a": [2, 3, 4]}', 'lax -$.a');
+ jsonb_path_query
+------------------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb_path_query('{"a": [1, 2]}', 'lax $.a * 3');
+ERROR: left operand of jsonpath operator * is not a single numeric value
+select jsonb_path_query('{"a": [1, 2]}', 'lax $.a * 3', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+-- extension: boolean expressions
+select jsonb_path_query('2', '$ > 1');
+ jsonb_path_query
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('2', '$ <= 1');
+ jsonb_path_query
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('2', '$ == "2"');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb '2' @? '$ == "2"';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @@ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @@ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @@ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @@ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1,2,3]' @@ '$[*]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @@ '$[*]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+ jsonb_path_match
+------------------
+ f
+(1 row)
+
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonb_path_match
+------------------
+ t
+(1 row)
+
+select jsonb_path_match('[{"a": 1}, {"a": 2}, 3]', 'lax exists($[*].a)', silent => false);
+ jsonb_path_match
+------------------
+ t
+(1 row)
+
+select jsonb_path_match('[{"a": 1}, {"a": 2}, 3]', 'lax exists($[*].a)', silent => true);
+ jsonb_path_match
+------------------
+ t
+(1 row)
+
+select jsonb_path_match('[{"a": 1}, {"a": 2}, 3]', 'strict exists($[*].a)', silent => false);
+ jsonb_path_match
+------------------
+
+(1 row)
+
+select jsonb_path_match('[{"a": 1}, {"a": 2}, 3]', 'strict exists($[*].a)', silent => true);
+ jsonb_path_match
+------------------
+
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$.type()');
+ jsonb_path_query
+------------------
+ "array"
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', 'lax $.type()');
+ jsonb_path_query
+------------------
+ "array"
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$[*].type()');
+ jsonb_path_query
+------------------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb_path_query('null', 'null.type()');
+ jsonb_path_query
+------------------
+ "null"
+(1 row)
+
+select jsonb_path_query('null', 'true.type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('null', '(123).type()');
+ jsonb_path_query
+------------------
+ "number"
+(1 row)
+
+select jsonb_path_query('null', '"123".type()');
+ jsonb_path_query
+------------------
+ "string"
+(1 row)
+
+select jsonb_path_query('{"a": 2}', '($.a - 5).abs() + 10');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('{"a": 2.5}', '-($.a * $.a).floor() % 4.3');
+ jsonb_path_query
+------------------
+ -1.7
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)');
+ jsonb_path_query
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*] > 3).type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*].a > 3).type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()');
+ jsonb_path_query
+------------------
+ "null"
+(1 row)
+
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
+ERROR: jsonpath item method .size() can only be applied to an array
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
+ jsonb_path_query
+------------------
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1
+ 3
+ 1
+ 1
+(9 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()');
+ jsonb_path_query
+------------------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb_path_query('[{},1]', '$[*].keyvalue()');
+ERROR: jsonpath item method .keyvalue() can only be applied to an object
+select jsonb_path_query('[{},1]', '$[*].keyvalue()', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{}', '$.keyvalue()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()');
+ jsonb_path_query
+----------------------------------------------
+ {"id": 0, "key": "a", "value": 1}
+ {"id": 0, "key": "b", "value": [1, 2]}
+ {"id": 0, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+ jsonb_path_query
+-----------------------------------------------
+ {"id": 12, "key": "a", "value": 1}
+ {"id": 12, "key": "b", "value": [1, 2]}
+ {"id": 72, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()');
+ERROR: jsonpath item method .keyvalue() can only be applied to an object
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()');
+ jsonb_path_query
+-----------------------------------------------
+ {"id": 12, "key": "a", "value": 1}
+ {"id": 12, "key": "b", "value": [1, 2]}
+ {"id": 72, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue().a');
+ERROR: jsonpath item method .keyvalue() can only be applied to an object
+select jsonb '{"a": 1, "b": [1, 2]}' @? 'lax $.keyvalue()';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b": [1, 2]}' @? 'lax $.keyvalue().key';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('null', '$.double()');
+ERROR: jsonpath item method .double() can only be applied to a string or numeric value
+select jsonb_path_query('true', '$.double()');
+ERROR: jsonpath item method .double() can only be applied to a string or numeric value
+select jsonb_path_query('null', '$.double()', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('true', '$.double()', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', '$.double()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.double()');
+ERROR: jsonpath item method .double() can only be applied to a string or numeric value
+select jsonb_path_query('{}', '$.double()');
+ERROR: jsonpath item method .double() can only be applied to a string or numeric value
+select jsonb_path_query('[]', 'strict $.double()', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{}', '$.double()', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('1.23', '$.double()');
+ jsonb_path_query
+------------------
+ 1.23
+(1 row)
+
+select jsonb_path_query('"1.23"', '$.double()');
+ jsonb_path_query
+------------------
+ 1.23
+(1 row)
+
+select jsonb_path_query('"1.23aaa"', '$.double()');
+ERROR: string argument of jsonpath item method .double() is not a valid representation of a double precision number
+select jsonb_path_query('1e1000', '$.double()');
+ERROR: numeric argument of jsonpath item method .double() is out of range for type double precision
+select jsonb_path_query('"nan"', '$.double()');
+ERROR: string argument of jsonpath item method .double() is not a valid representation of a double precision number
+select jsonb_path_query('"NaN"', '$.double()');
+ERROR: string argument of jsonpath item method .double() is not a valid representation of a double precision number
+select jsonb_path_query('"inf"', '$.double()');
+ERROR: string argument of jsonpath item method .double() is not a valid representation of a double precision number
+select jsonb_path_query('"-inf"', '$.double()');
+ERROR: string argument of jsonpath item method .double() is not a valid representation of a double precision number
+select jsonb_path_query('"inf"', '$.double()', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('"-inf"', '$.double()', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{}', '$.abs()');
+ERROR: jsonpath item method .abs() can only be applied to a numeric value
+select jsonb_path_query('true', '$.floor()');
+ERROR: jsonpath item method .floor() can only be applied to a numeric value
+select jsonb_path_query('"1.2"', '$.ceiling()');
+ERROR: jsonpath item method .ceiling() can only be applied to a numeric value
+select jsonb_path_query('{}', '$.abs()', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('true', '$.floor()', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('"1.2"', '$.ceiling()', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
+ jsonb_path_query
+------------------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
+ jsonb_path_query
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
+ jsonb_path_query
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
+ jsonb_path_query
+------------------
+ null
+ 1
+(2 rows)
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc", "adc\nabc", "ab\nadc"]', 'lax $[*] ? (@ like_regex "^ab.*c")');
+ jsonb_path_query
+------------------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc", "adc\nabc", "ab\nadc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")');
+ jsonb_path_query
+------------------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc", "adc\nabc", "ab\nadc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "m")');
+ jsonb_path_query
+------------------
+ "abc"
+ "abdacb"
+ "adc\nabc"
+(3 rows)
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc", "adc\nabc", "ab\nadc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "s")');
+ jsonb_path_query
+------------------
+ "abc"
+ "abdacb"
+ "ab\nadc"
+(3 rows)
+
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "a\\b" flag "q")');
+ jsonb_path_query
+------------------
+ "a\\b"
+ "^a\\b$"
+(2 rows)
+
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "a\\b" flag "")');
+ jsonb_path_query
+------------------
+ "a\b"
+(1 row)
+
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "q")');
+ jsonb_path_query
+------------------
+ "^a\\b$"
+(1 row)
+
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "q")');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
+ jsonb_path_query
+------------------
+ "^a\\b$"
+(1 row)
+
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
+ jsonb_path_query
+------------------
+ "a\b"
+(1 row)
+
+select jsonb_path_query('null', '$.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('true', '$.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('1', '$.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('{}', '$.datetime()');
+ERROR: jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('"bogus"', '$.datetime()');
+ERROR: datetime format is not recognized: "bogus"
+HINT: Use a datetime template argument to specify the input data format.
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+ERROR: invalid datetime format separator: "a"
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR: invalid value "aa" for "HH24"
+DETAIL: Value must be an integer.
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ERROR: trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ERROR: trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+ jsonb_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+ jsonb_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017T12:34:56"', '$.datetime("dd-mm-yyyy\"T\"HH24:MI:SS")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"10-03-2017t12:34:56"', '$.datetime("dd-mm-yyyy\"T\"HH24:MI:SS")');
+ERROR: unmatched format character "T"
+select jsonb_path_query('"10-03-2017 12:34:56"', '$.datetime("dd-mm-yyyy\"T\"HH24:MI:SS")');
+ERROR: unmatched format character "T"
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+ jsonb_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56+3"', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56+3"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56+3:10"', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56+3:10"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10T12:34:56+3:10"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10t12:34:56+3:10"', '$.datetime()');
+ERROR: datetime format is not recognized: "2017-03-10t12:34:56+3:10"
+HINT: Use a datetime template argument to specify the input data format.
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+ jsonb_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56+3"', '$.datetime().type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56+3"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56+3:10"', '$.datetime().type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56+3:10"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR: cannot convert value from date to timestamptz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR: cannot convert value from date to timestamptz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR: cannot convert value from date to timestamptz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query_tz(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query_tz
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query_tz
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query_tz
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ERROR: cannot convert value from time to timetz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ERROR: cannot convert value from time to timetz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+ERROR: cannot convert value from time to timetz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query_tz(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz
+---------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz
+---------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz
+---------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+ '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR: cannot convert value from time to timetz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query(
+ '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR: cannot convert value from time to timetz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query(
+ '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR: cannot convert value from time to timetz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query_tz(
+ '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz
+---------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query_tz(
+ '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz
+---------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+ '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz
+---------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR: cannot convert value from timestamp to timestamptz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR: cannot convert value from timestamp to timestamptz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR: cannot convert value from timestamp to timestamptz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query_tz(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query_tz
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query_tz
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query_tz(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query_tz
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR: cannot convert value from timestamp to timestamptz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query(
+ '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR: cannot convert value from timestamp to timestamptz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query(
+ '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR: cannot convert value from timestamp to timestamptz without time zone usage
+HINT: Use *_tz() function for time zone support.
+select jsonb_path_query_tz(
+ '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query_tz
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+ '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query_tz
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query_tz(
+ '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query_tz
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+-- overflow during comparison
+select jsonb_path_query('"1000000-01-01"', '$.datetime() > "2020-01-01 12:00:00".datetime()'::jsonpath);
+ jsonb_path_query
+------------------
+ true
+(1 row)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
+ jsonb_path_query
+------------------
+ {"a": 1}
+ {"a": 2}
+(2 rows)
+
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
+ERROR: JSON object does not contain key "a"
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
+ jsonb_path_query_array
+------------------------
+ [1, 2]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ jsonb_path_query_array
+------------------------
+ [1]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ jsonb_path_query_array
+------------------------
+ []
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 1, "max": 4}');
+ jsonb_path_query_array
+------------------------
+ [2, 3]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 3, "max": 4}');
+ jsonb_path_query_array
+------------------------
+ []
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
+ERROR: JSON object does not contain key "a"
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a', silent => true);
+ jsonb_path_query_first
+------------------------
+ 1
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a');
+ jsonb_path_query_first
+------------------------
+ 1
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ jsonb_path_query_first
+------------------------
+ 1
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ jsonb_path_query_first
+------------------------
+
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 1, "max": 4}');
+ jsonb_path_query_first
+------------------------
+ 2
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 3, "max": 4}');
+ jsonb_path_query_first
+------------------------
+
+(1 row)
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 1)');
+ jsonb_path_exists
+-------------------
+ t
+(1 row)
+
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', vars => '{"min": 1, "max": 4}');
+ jsonb_path_exists
+-------------------
+ t
+(1 row)
+
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', vars => '{"min": 3, "max": 4}');
+ jsonb_path_exists
+-------------------
+ f
+(1 row)
+
+SELECT jsonb_path_match('true', '$', silent => false);
+ jsonb_path_match
+------------------
+ t
+(1 row)
+
+SELECT jsonb_path_match('false', '$', silent => false);
+ jsonb_path_match
+------------------
+ f
+(1 row)
+
+SELECT jsonb_path_match('null', '$', silent => false);
+ jsonb_path_match
+------------------
+
+(1 row)
+
+SELECT jsonb_path_match('1', '$', silent => true);
+ jsonb_path_match
+------------------
+
+(1 row)
+
+SELECT jsonb_path_match('1', '$', silent => false);
+ERROR: single boolean result is expected
+SELECT jsonb_path_match('"a"', '$', silent => false);
+ERROR: single boolean result is expected
+SELECT jsonb_path_match('{}', '$', silent => false);
+ERROR: single boolean result is expected
+SELECT jsonb_path_match('[true]', '$', silent => false);
+ERROR: single boolean result is expected
+SELECT jsonb_path_match('{}', 'lax $.a', silent => false);
+ERROR: single boolean result is expected
+SELECT jsonb_path_match('{}', 'strict $.a', silent => false);
+ERROR: JSON object does not contain key "a"
+SELECT jsonb_path_match('{}', 'strict $.a', silent => true);
+ jsonb_path_match
+------------------
+
+(1 row)
+
+SELECT jsonb_path_match('[true, true]', '$[*]', silent => false);
+ERROR: single boolean result is expected
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2';
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT jsonb_path_match('[{"a": 1}, {"a": 2}]', '$[*].a > 1');
+ jsonb_path_match
+------------------
+ t
+(1 row)
+
+-- test string comparison (Unicode codepoint collation)
+WITH str(j, num) AS
+(
+ SELECT jsonb_build_object('s', s), num
+ FROM unnest('{"", "a", "ab", "abc", "abcd", "b", "A", "AB", "ABC", "ABc", "ABcD", "B"}'::text[]) WITH ORDINALITY AS a(s, num)
+)
+SELECT
+ s1.j, s2.j,
+ jsonb_path_query_first(s1.j, '$.s < $s', vars => s2.j) lt,
+ jsonb_path_query_first(s1.j, '$.s <= $s', vars => s2.j) le,
+ jsonb_path_query_first(s1.j, '$.s == $s', vars => s2.j) eq,
+ jsonb_path_query_first(s1.j, '$.s >= $s', vars => s2.j) ge,
+ jsonb_path_query_first(s1.j, '$.s > $s', vars => s2.j) gt
+FROM str s1, str s2
+ORDER BY s1.num, s2.num;
+ j | j | lt | le | eq | ge | gt
+---------------+---------------+-------+-------+-------+-------+-------
+ {"s": ""} | {"s": ""} | false | true | true | true | false
+ {"s": ""} | {"s": "a"} | true | true | false | false | false
+ {"s": ""} | {"s": "ab"} | true | true | false | false | false
+ {"s": ""} | {"s": "abc"} | true | true | false | false | false
+ {"s": ""} | {"s": "abcd"} | true | true | false | false | false
+ {"s": ""} | {"s": "b"} | true | true | false | false | false
+ {"s": ""} | {"s": "A"} | true | true | false | false | false
+ {"s": ""} | {"s": "AB"} | true | true | false | false | false
+ {"s": ""} | {"s": "ABC"} | true | true | false | false | false
+ {"s": ""} | {"s": "ABc"} | true | true | false | false | false
+ {"s": ""} | {"s": "ABcD"} | true | true | false | false | false
+ {"s": ""} | {"s": "B"} | true | true | false | false | false
+ {"s": "a"} | {"s": ""} | false | false | false | true | true
+ {"s": "a"} | {"s": "a"} | false | true | true | true | false
+ {"s": "a"} | {"s": "ab"} | true | true | false | false | false
+ {"s": "a"} | {"s": "abc"} | true | true | false | false | false
+ {"s": "a"} | {"s": "abcd"} | true | true | false | false | false
+ {"s": "a"} | {"s": "b"} | true | true | false | false | false
+ {"s": "a"} | {"s": "A"} | false | false | false | true | true
+ {"s": "a"} | {"s": "AB"} | false | false | false | true | true
+ {"s": "a"} | {"s": "ABC"} | false | false | false | true | true
+ {"s": "a"} | {"s": "ABc"} | false | false | false | true | true
+ {"s": "a"} | {"s": "ABcD"} | false | false | false | true | true
+ {"s": "a"} | {"s": "B"} | false | false | false | true | true
+ {"s": "ab"} | {"s": ""} | false | false | false | true | true
+ {"s": "ab"} | {"s": "a"} | false | false | false | true | true
+ {"s": "ab"} | {"s": "ab"} | false | true | true | true | false
+ {"s": "ab"} | {"s": "abc"} | true | true | false | false | false
+ {"s": "ab"} | {"s": "abcd"} | true | true | false | false | false
+ {"s": "ab"} | {"s": "b"} | true | true | false | false | false
+ {"s": "ab"} | {"s": "A"} | false | false | false | true | true
+ {"s": "ab"} | {"s": "AB"} | false | false | false | true | true
+ {"s": "ab"} | {"s": "ABC"} | false | false | false | true | true
+ {"s": "ab"} | {"s": "ABc"} | false | false | false | true | true
+ {"s": "ab"} | {"s": "ABcD"} | false | false | false | true | true
+ {"s": "ab"} | {"s": "B"} | false | false | false | true | true
+ {"s": "abc"} | {"s": ""} | false | false | false | true | true
+ {"s": "abc"} | {"s": "a"} | false | false | false | true | true
+ {"s": "abc"} | {"s": "ab"} | false | false | false | true | true
+ {"s": "abc"} | {"s": "abc"} | false | true | true | true | false
+ {"s": "abc"} | {"s": "abcd"} | true | true | false | false | false
+ {"s": "abc"} | {"s": "b"} | true | true | false | false | false
+ {"s": "abc"} | {"s": "A"} | false | false | false | true | true
+ {"s": "abc"} | {"s": "AB"} | false | false | false | true | true
+ {"s": "abc"} | {"s": "ABC"} | false | false | false | true | true
+ {"s": "abc"} | {"s": "ABc"} | false | false | false | true | true
+ {"s": "abc"} | {"s": "ABcD"} | false | false | false | true | true
+ {"s": "abc"} | {"s": "B"} | false | false | false | true | true
+ {"s": "abcd"} | {"s": ""} | false | false | false | true | true
+ {"s": "abcd"} | {"s": "a"} | false | false | false | true | true
+ {"s": "abcd"} | {"s": "ab"} | false | false | false | true | true
+ {"s": "abcd"} | {"s": "abc"} | false | false | false | true | true
+ {"s": "abcd"} | {"s": "abcd"} | false | true | true | true | false
+ {"s": "abcd"} | {"s": "b"} | true | true | false | false | false
+ {"s": "abcd"} | {"s": "A"} | false | false | false | true | true
+ {"s": "abcd"} | {"s": "AB"} | false | false | false | true | true
+ {"s": "abcd"} | {"s": "ABC"} | false | false | false | true | true
+ {"s": "abcd"} | {"s": "ABc"} | false | false | false | true | true
+ {"s": "abcd"} | {"s": "ABcD"} | false | false | false | true | true
+ {"s": "abcd"} | {"s": "B"} | false | false | false | true | true
+ {"s": "b"} | {"s": ""} | false | false | false | true | true
+ {"s": "b"} | {"s": "a"} | false | false | false | true | true
+ {"s": "b"} | {"s": "ab"} | false | false | false | true | true
+ {"s": "b"} | {"s": "abc"} | false | false | false | true | true
+ {"s": "b"} | {"s": "abcd"} | false | false | false | true | true
+ {"s": "b"} | {"s": "b"} | false | true | true | true | false
+ {"s": "b"} | {"s": "A"} | false | false | false | true | true
+ {"s": "b"} | {"s": "AB"} | false | false | false | true | true
+ {"s": "b"} | {"s": "ABC"} | false | false | false | true | true
+ {"s": "b"} | {"s": "ABc"} | false | false | false | true | true
+ {"s": "b"} | {"s": "ABcD"} | false | false | false | true | true
+ {"s": "b"} | {"s": "B"} | false | false | false | true | true
+ {"s": "A"} | {"s": ""} | false | false | false | true | true
+ {"s": "A"} | {"s": "a"} | true | true | false | false | false
+ {"s": "A"} | {"s": "ab"} | true | true | false | false | false
+ {"s": "A"} | {"s": "abc"} | true | true | false | false | false
+ {"s": "A"} | {"s": "abcd"} | true | true | false | false | false
+ {"s": "A"} | {"s": "b"} | true | true | false | false | false
+ {"s": "A"} | {"s": "A"} | false | true | true | true | false
+ {"s": "A"} | {"s": "AB"} | true | true | false | false | false
+ {"s": "A"} | {"s": "ABC"} | true | true | false | false | false
+ {"s": "A"} | {"s": "ABc"} | true | true | false | false | false
+ {"s": "A"} | {"s": "ABcD"} | true | true | false | false | false
+ {"s": "A"} | {"s": "B"} | true | true | false | false | false
+ {"s": "AB"} | {"s": ""} | false | false | false | true | true
+ {"s": "AB"} | {"s": "a"} | true | true | false | false | false
+ {"s": "AB"} | {"s": "ab"} | true | true | false | false | false
+ {"s": "AB"} | {"s": "abc"} | true | true | false | false | false
+ {"s": "AB"} | {"s": "abcd"} | true | true | false | false | false
+ {"s": "AB"} | {"s": "b"} | true | true | false | false | false
+ {"s": "AB"} | {"s": "A"} | false | false | false | true | true
+ {"s": "AB"} | {"s": "AB"} | false | true | true | true | false
+ {"s": "AB"} | {"s": "ABC"} | true | true | false | false | false
+ {"s": "AB"} | {"s": "ABc"} | true | true | false | false | false
+ {"s": "AB"} | {"s": "ABcD"} | true | true | false | false | false
+ {"s": "AB"} | {"s": "B"} | true | true | false | false | false
+ {"s": "ABC"} | {"s": ""} | false | false | false | true | true
+ {"s": "ABC"} | {"s": "a"} | true | true | false | false | false
+ {"s": "ABC"} | {"s": "ab"} | true | true | false | false | false
+ {"s": "ABC"} | {"s": "abc"} | true | true | false | false | false
+ {"s": "ABC"} | {"s": "abcd"} | true | true | false | false | false
+ {"s": "ABC"} | {"s": "b"} | true | true | false | false | false
+ {"s": "ABC"} | {"s": "A"} | false | false | false | true | true
+ {"s": "ABC"} | {"s": "AB"} | false | false | false | true | true
+ {"s": "ABC"} | {"s": "ABC"} | false | true | true | true | false
+ {"s": "ABC"} | {"s": "ABc"} | true | true | false | false | false
+ {"s": "ABC"} | {"s": "ABcD"} | true | true | false | false | false
+ {"s": "ABC"} | {"s": "B"} | true | true | false | false | false
+ {"s": "ABc"} | {"s": ""} | false | false | false | true | true
+ {"s": "ABc"} | {"s": "a"} | true | true | false | false | false
+ {"s": "ABc"} | {"s": "ab"} | true | true | false | false | false
+ {"s": "ABc"} | {"s": "abc"} | true | true | false | false | false
+ {"s": "ABc"} | {"s": "abcd"} | true | true | false | false | false
+ {"s": "ABc"} | {"s": "b"} | true | true | false | false | false
+ {"s": "ABc"} | {"s": "A"} | false | false | false | true | true
+ {"s": "ABc"} | {"s": "AB"} | false | false | false | true | true
+ {"s": "ABc"} | {"s": "ABC"} | false | false | false | true | true
+ {"s": "ABc"} | {"s": "ABc"} | false | true | true | true | false
+ {"s": "ABc"} | {"s": "ABcD"} | true | true | false | false | false
+ {"s": "ABc"} | {"s": "B"} | true | true | false | false | false
+ {"s": "ABcD"} | {"s": ""} | false | false | false | true | true
+ {"s": "ABcD"} | {"s": "a"} | true | true | false | false | false
+ {"s": "ABcD"} | {"s": "ab"} | true | true | false | false | false
+ {"s": "ABcD"} | {"s": "abc"} | true | true | false | false | false
+ {"s": "ABcD"} | {"s": "abcd"} | true | true | false | false | false
+ {"s": "ABcD"} | {"s": "b"} | true | true | false | false | false
+ {"s": "ABcD"} | {"s": "A"} | false | false | false | true | true
+ {"s": "ABcD"} | {"s": "AB"} | false | false | false | true | true
+ {"s": "ABcD"} | {"s": "ABC"} | false | false | false | true | true
+ {"s": "ABcD"} | {"s": "ABc"} | false | false | false | true | true
+ {"s": "ABcD"} | {"s": "ABcD"} | false | true | true | true | false
+ {"s": "ABcD"} | {"s": "B"} | true | true | false | false | false
+ {"s": "B"} | {"s": ""} | false | false | false | true | true
+ {"s": "B"} | {"s": "a"} | true | true | false | false | false
+ {"s": "B"} | {"s": "ab"} | true | true | false | false | false
+ {"s": "B"} | {"s": "abc"} | true | true | false | false | false
+ {"s": "B"} | {"s": "abcd"} | true | true | false | false | false
+ {"s": "B"} | {"s": "b"} | true | true | false | false | false
+ {"s": "B"} | {"s": "A"} | false | false | false | true | true
+ {"s": "B"} | {"s": "AB"} | false | false | false | true | true
+ {"s": "B"} | {"s": "ABC"} | false | false | false | true | true
+ {"s": "B"} | {"s": "ABc"} | false | false | false | true | true
+ {"s": "B"} | {"s": "ABcD"} | false | false | false | true | true
+ {"s": "B"} | {"s": "B"} | false | true | true | true | false
+(144 rows)
+
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000..6dab98d
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,964 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for type jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select '$.a'::jsonpath;
+ jsonpath
+----------
+ $."a"
+(1 row)
+
+select '$.a.v'::jsonpath;
+ jsonpath
+-----------
+ $."a"."v"
+(1 row)
+
+select '$.a.*'::jsonpath;
+ jsonpath
+----------
+ $."a".*
+(1 row)
+
+select '$.*[*]'::jsonpath;
+ jsonpath
+----------
+ $.*[*]
+(1 row)
+
+select '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ jsonpath
+-------------
+ $."a"[*][*]
+(1 row)
+
+select '$[*]'::jsonpath;
+ jsonpath
+----------
+ $[*]
+(1 row)
+
+select '$[0]'::jsonpath;
+ jsonpath
+----------
+ $[0]
+(1 row)
+
+select '$[*][0]'::jsonpath;
+ jsonpath
+----------
+ $[*][0]
+(1 row)
+
+select '$[*].a'::jsonpath;
+ jsonpath
+----------
+ $[*]."a"
+(1 row)
+
+select '$[*][0].a.b'::jsonpath;
+ jsonpath
+-----------------
+ $[*][0]."a"."b"
+(1 row)
+
+select '$.a.**.b'::jsonpath;
+ jsonpath
+--------------
+ $."a".**."b"
+(1 row)
+
+select '$.a.**{2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{2 to 5}."b"
+(1 row)
+
+select '$.a.**{0 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{0 to 5}."b"
+(1 row)
+
+select '$.a.**{5 to last}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{5 to last}."b"
+(1 row)
+
+select '$.a.**{last}.b'::jsonpath;
+ jsonpath
+--------------------
+ $."a".**{last}."b"
+(1 row)
+
+select '$.a.**{last to 5}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{last to 5}."b"
+(1 row)
+
+select '$+1'::jsonpath;
+ jsonpath
+----------
+ ($ + 1)
+(1 row)
+
+select '$-1'::jsonpath;
+ jsonpath
+----------
+ ($ - 1)
+(1 row)
+
+select '$--+1'::jsonpath;
+ jsonpath
+----------
+ ($ - -1)
+(1 row)
+
+select '$.a/+-1'::jsonpath;
+ jsonpath
+--------------
+ ($."a" / -1)
+(1 row)
+
+select '1 * 2 + 4 % -3 != false'::jsonpath;
+ jsonpath
+---------------------------
+ (1 * 2 + 4 % -3 != false)
+(1 row)
+
+select '"\b\f\r\n\t\v\"\''\\"'::jsonpath;
+ jsonpath
+-------------------------
+ "\b\f\r\n\t\u000b\"'\\"
+(1 row)
+
+select '"\x50\u0067\u{53}\u{051}\u{00004C}"'::jsonpath;
+ jsonpath
+----------
+ "PgSQL"
+(1 row)
+
+select '$.foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar'::jsonpath;
+ jsonpath
+---------------------
+ $."fooPgSQL\t\"bar"
+(1 row)
+
+select '"\z"'::jsonpath; -- unrecognized escape is just the literal char
+ jsonpath
+----------
+ "z"
+(1 row)
+
+select '$.g ? ($.a == 1)'::jsonpath;
+ jsonpath
+--------------------
+ $."g"?($."a" == 1)
+(1 row)
+
+select '$.g ? (@ == 1)'::jsonpath;
+ jsonpath
+----------------
+ $."g"?(@ == 1)
+(1 row)
+
+select '$.g ? (@.a == 1)'::jsonpath;
+ jsonpath
+--------------------
+ $."g"?(@."a" == 1)
+(1 row)
+
+select '$.g ? (@.a == 1 || @.a == 4)'::jsonpath;
+ jsonpath
+----------------------------------
+ $."g"?(@."a" == 1 || @."a" == 4)
+(1 row)
+
+select '$.g ? (@.a == 1 && @.a == 4)'::jsonpath;
+ jsonpath
+----------------------------------
+ $."g"?(@."a" == 1 && @."a" == 4)
+(1 row)
+
+select '$.g ? (@.a == 1 || @.a == 4 && @.b == 7)'::jsonpath;
+ jsonpath
+------------------------------------------------
+ $."g"?(@."a" == 1 || @."a" == 4 && @."b" == 7)
+(1 row)
+
+select '$.g ? (@.a == 1 || !(@.a == 4) && @.b == 7)'::jsonpath;
+ jsonpath
+---------------------------------------------------
+ $."g"?(@."a" == 1 || !(@."a" == 4) && @."b" == 7)
+(1 row)
+
+select '$.g ? (@.a == 1 || !(@.x >= 123 || @.a == 4) && @.b == 7)'::jsonpath;
+ jsonpath
+-------------------------------------------------------------------
+ $."g"?(@."a" == 1 || !(@."x" >= 123 || @."a" == 4) && @."b" == 7)
+(1 row)
+
+select '$.g ? (@.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ jsonpath
+---------------------------------------
+ $."g"?(@."x" >= @[*]?(@."a" > "abc"))
+(1 row)
+
+select '$.g ? ((@.x >= 123 || @.a == 4) is unknown)'::jsonpath;
+ jsonpath
+-------------------------------------------------
+ $."g"?((@."x" >= 123 || @."a" == 4) is unknown)
+(1 row)
+
+select '$.g ? (exists (@.x))'::jsonpath;
+ jsonpath
+------------------------
+ $."g"?(exists (@."x"))
+(1 row)
+
+select '$.g ? (exists (@.x ? (@ == 14)))'::jsonpath;
+ jsonpath
+----------------------------------
+ $."g"?(exists (@."x"?(@ == 14)))
+(1 row)
+
+select '$.g ? ((@.x >= 123 || @.a == 4) && exists (@.x ? (@ == 14)))'::jsonpath;
+ jsonpath
+------------------------------------------------------------------
+ $."g"?((@."x" >= 123 || @."a" == 4) && exists (@."x"?(@ == 14)))
+(1 row)
+
+select '$.g ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(1 row)
+
+select '$a'::jsonpath;
+ jsonpath
+----------
+ $"a"
+(1 row)
+
+select '$a.b'::jsonpath;
+ jsonpath
+----------
+ $"a"."b"
+(1 row)
+
+select '$a[*]'::jsonpath;
+ jsonpath
+----------
+ $"a"[*]
+(1 row)
+
+select '$.g ? (@.zip == $zip)'::jsonpath;
+ jsonpath
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ jsonpath
+--------------------
+ $."a"[1,2,3 to 16]
+(1 row)
+
+select '$.a[$a + 1, ($b[*]) to -($[0] * 2)]'::jsonpath;
+ jsonpath
+----------------------------------------
+ $."a"[$"a" + 1,$"b"[*] to -($[0] * 2)]
+(1 row)
+
+select '$.a[$.a.size() - 3]'::jsonpath;
+ jsonpath
+-------------------------
+ $."a"[$."a".size() - 3]
+(1 row)
+
+select 'last'::jsonpath;
+ERROR: LAST is allowed only in array subscripts
+LINE 1: select 'last'::jsonpath;
+ ^
+select '"last"'::jsonpath;
+ jsonpath
+----------
+ "last"
+(1 row)
+
+select '$.last'::jsonpath;
+ jsonpath
+----------
+ $."last"
+(1 row)
+
+select '$ ? (last > 0)'::jsonpath;
+ERROR: LAST is allowed only in array subscripts
+LINE 1: select '$ ? (last > 0)'::jsonpath;
+ ^
+select '$[last]'::jsonpath;
+ jsonpath
+----------
+ $[last]
+(1 row)
+
+select '$[$[0] ? (last > 0)]'::jsonpath;
+ jsonpath
+--------------------
+ $[$[0]?(last > 0)]
+(1 row)
+
+select 'null.type()'::jsonpath;
+ jsonpath
+-------------
+ null.type()
+(1 row)
+
+select '1.type()'::jsonpath;
+ jsonpath
+----------
+ 1.type()
+(1 row)
+
+select '(1).type()'::jsonpath;
+ jsonpath
+----------
+ 1.type()
+(1 row)
+
+select '1.2.type()'::jsonpath;
+ jsonpath
+------------
+ 1.2.type()
+(1 row)
+
+select '"aaa".type()'::jsonpath;
+ jsonpath
+--------------
+ "aaa".type()
+(1 row)
+
+select 'true.type()'::jsonpath;
+ jsonpath
+-------------
+ true.type()
+(1 row)
+
+select '$.double().floor().ceiling().abs()'::jsonpath;
+ jsonpath
+------------------------------------
+ $.double().floor().ceiling().abs()
+(1 row)
+
+select '$.keyvalue().key'::jsonpath;
+ jsonpath
+--------------------
+ $.keyvalue()."key"
+(1 row)
+
+select '$.datetime()'::jsonpath;
+ jsonpath
+--------------
+ $.datetime()
+(1 row)
+
+select '$.datetime("datetime template")'::jsonpath;
+ jsonpath
+---------------------------------
+ $.datetime("datetime template")
+(1 row)
+
+select '$ ? (@ starts with "abc")'::jsonpath;
+ jsonpath
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ jsonpath
+--------------------------
+ $?(@ starts with $"var")
+(1 row)
+
+select '$ ? (@ like_regex "(invalid pattern")'::jsonpath;
+ERROR: invalid regular expression: parentheses () not balanced
+LINE 1: select '$ ? (@ like_regex "(invalid pattern")'::jsonpath;
+ ^
+select '$ ? (@ like_regex "pattern")'::jsonpath;
+ jsonpath
+----------------------------
+ $?(@ like_regex "pattern")
+(1 row)
+
+select '$ ? (@ like_regex "pattern" flag "")'::jsonpath;
+ jsonpath
+----------------------------
+ $?(@ like_regex "pattern")
+(1 row)
+
+select '$ ? (@ like_regex "pattern" flag "i")'::jsonpath;
+ jsonpath
+-------------------------------------
+ $?(@ like_regex "pattern" flag "i")
+(1 row)
+
+select '$ ? (@ like_regex "pattern" flag "is")'::jsonpath;
+ jsonpath
+--------------------------------------
+ $?(@ like_regex "pattern" flag "is")
+(1 row)
+
+select '$ ? (@ like_regex "pattern" flag "isim")'::jsonpath;
+ jsonpath
+---------------------------------------
+ $?(@ like_regex "pattern" flag "ism")
+(1 row)
+
+select '$ ? (@ like_regex "pattern" flag "xsms")'::jsonpath;
+ERROR: XQuery "x" flag (expanded regular expressions) is not implemented
+LINE 1: select '$ ? (@ like_regex "pattern" flag "xsms")'::jsonpath;
+ ^
+select '$ ? (@ like_regex "pattern" flag "q")'::jsonpath;
+ jsonpath
+-------------------------------------
+ $?(@ like_regex "pattern" flag "q")
+(1 row)
+
+select '$ ? (@ like_regex "pattern" flag "iq")'::jsonpath;
+ jsonpath
+--------------------------------------
+ $?(@ like_regex "pattern" flag "iq")
+(1 row)
+
+select '$ ? (@ like_regex "pattern" flag "smixq")'::jsonpath;
+ jsonpath
+-----------------------------------------
+ $?(@ like_regex "pattern" flag "ismxq")
+(1 row)
+
+select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath;
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath;
+ ^
+DETAIL: unrecognized flag character "a" in LIKE_REGEX predicate
+select '$ < 1'::jsonpath;
+ jsonpath
+----------
+ ($ < 1)
+(1 row)
+
+select '($ < 1) || $.a.b <= $x'::jsonpath;
+ jsonpath
+------------------------------
+ ($ < 1 || $."a"."b" <= $"x")
+(1 row)
+
+select '@ + 1'::jsonpath;
+ERROR: @ is not allowed in root expressions
+LINE 1: select '@ + 1'::jsonpath;
+ ^
+select '($).a.b'::jsonpath;
+ jsonpath
+-----------
+ $."a"."b"
+(1 row)
+
+select '($.a.b).c.d'::jsonpath;
+ jsonpath
+-------------------
+ $."a"."b"."c"."d"
+(1 row)
+
+select '($.a.b + -$.x.y).c.d'::jsonpath;
+ jsonpath
+----------------------------------
+ ($."a"."b" + -$."x"."y")."c"."d"
+(1 row)
+
+select '(-+$.a.b).c.d'::jsonpath;
+ jsonpath
+-------------------------
+ (-(+$."a"."b"))."c"."d"
+(1 row)
+
+select '1 + ($.a.b + 2).c.d'::jsonpath;
+ jsonpath
+-------------------------------
+ (1 + ($."a"."b" + 2)."c"."d")
+(1 row)
+
+select '1 + ($.a.b > 2).c.d'::jsonpath;
+ jsonpath
+-------------------------------
+ (1 + ($."a"."b" > 2)."c"."d")
+(1 row)
+
+select '($)'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select '(($))'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
+ jsonpath
+-------------------------------------------------
+ (($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c")))
+(1 row)
+
+select '$ ? (@.a < 1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < -1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < -1)
+(1 row)
+
+select '$ ? (@.a < +1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < .1)'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '$ ? (@.a < .1)'::jsonpath;
+ ^
+select '$ ? (@.a < -.1)'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '$ ? (@.a < -.1)'::jsonpath;
+ ^
+select '$ ? (@.a < +.1)'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '$ ? (@.a < +.1)'::jsonpath;
+ ^
+select '$ ? (@.a < 0.1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 0.1)
+(1 row)
+
+select '$ ? (@.a < -0.1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < -0.1)
+(1 row)
+
+select '$ ? (@.a < +0.1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 0.1)
+(1 row)
+
+select '$ ? (@.a < 10.1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 10.1)
+(1 row)
+
+select '$ ? (@.a < -10.1)'::jsonpath;
+ jsonpath
+-------------------
+ $?(@."a" < -10.1)
+(1 row)
+
+select '$ ? (@.a < +10.1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 10.1)
+(1 row)
+
+select '$ ? (@.a < 1e1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < 10)
+(1 row)
+
+select '$ ? (@.a < -1e1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < -10)
+(1 row)
+
+select '$ ? (@.a < +1e1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < 10)
+(1 row)
+
+select '$ ? (@.a < .1e1)'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '$ ? (@.a < .1e1)'::jsonpath;
+ ^
+select '$ ? (@.a < -.1e1)'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '$ ? (@.a < -.1e1)'::jsonpath;
+ ^
+select '$ ? (@.a < +.1e1)'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '$ ? (@.a < +.1e1)'::jsonpath;
+ ^
+select '$ ? (@.a < 0.1e1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < -0.1e1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < -1)
+(1 row)
+
+select '$ ? (@.a < +0.1e1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < 10.1e1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 101)
+(1 row)
+
+select '$ ? (@.a < -10.1e1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < -101)
+(1 row)
+
+select '$ ? (@.a < +10.1e1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 101)
+(1 row)
+
+select '$ ? (@.a < 1e-1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 0.1)
+(1 row)
+
+select '$ ? (@.a < -1e-1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < -0.1)
+(1 row)
+
+select '$ ? (@.a < +1e-1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 0.1)
+(1 row)
+
+select '$ ? (@.a < .1e-1)'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '$ ? (@.a < .1e-1)'::jsonpath;
+ ^
+select '$ ? (@.a < -.1e-1)'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '$ ? (@.a < -.1e-1)'::jsonpath;
+ ^
+select '$ ? (@.a < +.1e-1)'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '$ ? (@.a < +.1e-1)'::jsonpath;
+ ^
+select '$ ? (@.a < 0.1e-1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 0.01)
+(1 row)
+
+select '$ ? (@.a < -0.1e-1)'::jsonpath;
+ jsonpath
+-------------------
+ $?(@."a" < -0.01)
+(1 row)
+
+select '$ ? (@.a < +0.1e-1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 0.01)
+(1 row)
+
+select '$ ? (@.a < 10.1e-1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 1.01)
+(1 row)
+
+select '$ ? (@.a < -10.1e-1)'::jsonpath;
+ jsonpath
+-------------------
+ $?(@."a" < -1.01)
+(1 row)
+
+select '$ ? (@.a < +10.1e-1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 1.01)
+(1 row)
+
+select '$ ? (@.a < 1e+1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < 10)
+(1 row)
+
+select '$ ? (@.a < -1e+1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < -10)
+(1 row)
+
+select '$ ? (@.a < +1e+1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < 10)
+(1 row)
+
+select '$ ? (@.a < .1e+1)'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '$ ? (@.a < .1e+1)'::jsonpath;
+ ^
+select '$ ? (@.a < -.1e+1)'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '$ ? (@.a < -.1e+1)'::jsonpath;
+ ^
+select '$ ? (@.a < +.1e+1)'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '$ ? (@.a < +.1e+1)'::jsonpath;
+ ^
+select '$ ? (@.a < 0.1e+1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < -0.1e+1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < -1)
+(1 row)
+
+select '$ ? (@.a < +0.1e+1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < 10.1e+1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 101)
+(1 row)
+
+select '$ ? (@.a < -10.1e+1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < -101)
+(1 row)
+
+select '$ ? (@.a < +10.1e+1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 101)
+(1 row)
+
+select '0'::jsonpath;
+ jsonpath
+----------
+ 0
+(1 row)
+
+select '00'::jsonpath;
+ERROR: syntax error at end of jsonpath input
+LINE 1: select '00'::jsonpath;
+ ^
+select '0.0'::jsonpath;
+ jsonpath
+----------
+ 0.0
+(1 row)
+
+select '0.000'::jsonpath;
+ jsonpath
+----------
+ 0.000
+(1 row)
+
+select '0.000e1'::jsonpath;
+ jsonpath
+----------
+ 0.00
+(1 row)
+
+select '0.000e2'::jsonpath;
+ jsonpath
+----------
+ 0.0
+(1 row)
+
+select '0.000e3'::jsonpath;
+ jsonpath
+----------
+ 0
+(1 row)
+
+select '0.0010'::jsonpath;
+ jsonpath
+----------
+ 0.0010
+(1 row)
+
+select '0.0010e-1'::jsonpath;
+ jsonpath
+----------
+ 0.00010
+(1 row)
+
+select '0.0010e+1'::jsonpath;
+ jsonpath
+----------
+ 0.010
+(1 row)
+
+select '0.0010e+2'::jsonpath;
+ jsonpath
+----------
+ 0.10
+(1 row)
+
+select '1e'::jsonpath;
+ERROR: invalid floating point number at or near "1e" of jsonpath input
+LINE 1: select '1e'::jsonpath;
+ ^
+select '1.e'::jsonpath;
+ jsonpath
+----------
+ 1."e"
+(1 row)
+
+select '1.2e'::jsonpath;
+ERROR: invalid floating point number at or near "1.2e" of jsonpath input
+LINE 1: select '1.2e'::jsonpath;
+ ^
+select '1.2.e'::jsonpath;
+ jsonpath
+----------
+ 1.2."e"
+(1 row)
+
+select '(1.2).e'::jsonpath;
+ jsonpath
+----------
+ 1.2."e"
+(1 row)
+
+select '1e3'::jsonpath;
+ jsonpath
+----------
+ 1000
+(1 row)
+
+select '1.e3'::jsonpath;
+ jsonpath
+----------
+ 1."e3"
+(1 row)
+
+select '1.e3.e'::jsonpath;
+ jsonpath
+------------
+ 1."e3"."e"
+(1 row)
+
+select '1.e3.e4'::jsonpath;
+ jsonpath
+-------------
+ 1."e3"."e4"
+(1 row)
+
+select '1.2e3'::jsonpath;
+ jsonpath
+----------
+ 1200
+(1 row)
+
+select '1.2.e3'::jsonpath;
+ jsonpath
+----------
+ 1.2."e3"
+(1 row)
+
+select '(1.2).e3'::jsonpath;
+ jsonpath
+----------
+ 1.2."e3"
+(1 row)
+
+select '1..e'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '1..e'::jsonpath;
+ ^
+select '1..e3'::jsonpath;
+ERROR: syntax error at or near "." of jsonpath input
+LINE 1: select '1..e3'::jsonpath;
+ ^
+select '(1.).e'::jsonpath;
+ERROR: syntax error at or near ")" of jsonpath input
+LINE 1: select '(1.).e'::jsonpath;
+ ^
+select '(1.).e3'::jsonpath;
+ERROR: syntax error at or near ")" of jsonpath input
+LINE 1: select '(1.).e3'::jsonpath;
+ ^
diff --git a/src/test/regress/expected/jsonpath_encoding.out b/src/test/regress/expected/jsonpath_encoding.out
new file mode 100644
index 0000000..7cbfb6a
--- /dev/null
+++ b/src/test/regress/expected/jsonpath_encoding.out
@@ -0,0 +1,180 @@
+--
+-- encoding-sensitive tests for jsonpath
+--
+-- We provide expected-results files for UTF8 (jsonpath_encoding.out)
+-- and for SQL_ASCII (jsonpath_encoding_1.out). Skip otherwise.
+SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
+ AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+SELECT getdatabaseencoding(); -- just to label the results files
+ getdatabaseencoding
+---------------------
+ UTF8
+(1 row)
+
+-- checks for double-quoted values
+-- basic unicode input
+SELECT '"\u"'::jsonpath; -- ERROR, incomplete escape
+ERROR: invalid unicode sequence at or near "\u" of jsonpath input
+LINE 1: SELECT '"\u"'::jsonpath;
+ ^
+SELECT '"\u00"'::jsonpath; -- ERROR, incomplete escape
+ERROR: invalid unicode sequence at or near "\u00" of jsonpath input
+LINE 1: SELECT '"\u00"'::jsonpath;
+ ^
+SELECT '"\u000g"'::jsonpath; -- ERROR, g is not a hex digit
+ERROR: invalid unicode sequence at or near "\u000" of jsonpath input
+LINE 1: SELECT '"\u000g"'::jsonpath;
+ ^
+SELECT '"\u0000"'::jsonpath; -- OK, legal escape
+ERROR: unsupported Unicode escape sequence
+LINE 1: SELECT '"\u0000"'::jsonpath;
+ ^
+DETAIL: \u0000 cannot be converted to text.
+SELECT '"\uaBcD"'::jsonpath; -- OK, uppercase and lower case both OK
+ jsonpath
+----------
+ "ê¯"
+(1 row)
+
+-- handling of unicode surrogate pairs
+select '"\ud83d\ude04\ud83d\udc36"'::jsonpath as correct_in_utf8;
+ correct_in_utf8
+-----------------
+ "😄ðŸ¶"
+(1 row)
+
+select '"\ud83d\ud83d"'::jsonpath; -- 2 high surrogates in a row
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '"\ud83d\ud83d"'::jsonpath;
+ ^
+DETAIL: Unicode high surrogate must not follow a high surrogate.
+select '"\ude04\ud83d"'::jsonpath; -- surrogates in wrong order
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '"\ude04\ud83d"'::jsonpath;
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+select '"\ud83dX"'::jsonpath; -- orphan high surrogate
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '"\ud83dX"'::jsonpath;
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+select '"\ude04X"'::jsonpath; -- orphan low surrogate
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '"\ude04X"'::jsonpath;
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+--handling of simple unicode escapes
+select '"the Copyright \u00a9 sign"'::jsonpath as correct_in_utf8;
+ correct_in_utf8
+------------------------
+ "the Copyright © sign"
+(1 row)
+
+select '"dollar \u0024 character"'::jsonpath as correct_everywhere;
+ correct_everywhere
+----------------------
+ "dollar $ character"
+(1 row)
+
+select '"dollar \\u0024 character"'::jsonpath as not_an_escape;
+ not_an_escape
+----------------------------
+ "dollar \\u0024 character"
+(1 row)
+
+select '"null \u0000 escape"'::jsonpath as not_unescaped;
+ERROR: unsupported Unicode escape sequence
+LINE 1: select '"null \u0000 escape"'::jsonpath as not_unescaped;
+ ^
+DETAIL: \u0000 cannot be converted to text.
+select '"null \\u0000 escape"'::jsonpath as not_an_escape;
+ not_an_escape
+-----------------------
+ "null \\u0000 escape"
+(1 row)
+
+-- checks for quoted key names
+-- basic unicode input
+SELECT '$."\u"'::jsonpath; -- ERROR, incomplete escape
+ERROR: invalid unicode sequence at or near "\u" of jsonpath input
+LINE 1: SELECT '$."\u"'::jsonpath;
+ ^
+SELECT '$."\u00"'::jsonpath; -- ERROR, incomplete escape
+ERROR: invalid unicode sequence at or near "\u00" of jsonpath input
+LINE 1: SELECT '$."\u00"'::jsonpath;
+ ^
+SELECT '$."\u000g"'::jsonpath; -- ERROR, g is not a hex digit
+ERROR: invalid unicode sequence at or near "\u000" of jsonpath input
+LINE 1: SELECT '$."\u000g"'::jsonpath;
+ ^
+SELECT '$."\u0000"'::jsonpath; -- OK, legal escape
+ERROR: unsupported Unicode escape sequence
+LINE 1: SELECT '$."\u0000"'::jsonpath;
+ ^
+DETAIL: \u0000 cannot be converted to text.
+SELECT '$."\uaBcD"'::jsonpath; -- OK, uppercase and lower case both OK
+ jsonpath
+----------
+ $."ê¯"
+(1 row)
+
+-- handling of unicode surrogate pairs
+select '$."\ud83d\ude04\ud83d\udc36"'::jsonpath as correct_in_utf8;
+ correct_in_utf8
+-----------------
+ $."😄ðŸ¶"
+(1 row)
+
+select '$."\ud83d\ud83d"'::jsonpath; -- 2 high surrogates in a row
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '$."\ud83d\ud83d"'::jsonpath;
+ ^
+DETAIL: Unicode high surrogate must not follow a high surrogate.
+select '$."\ude04\ud83d"'::jsonpath; -- surrogates in wrong order
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '$."\ude04\ud83d"'::jsonpath;
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+select '$."\ud83dX"'::jsonpath; -- orphan high surrogate
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '$."\ud83dX"'::jsonpath;
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+select '$."\ude04X"'::jsonpath; -- orphan low surrogate
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '$."\ude04X"'::jsonpath;
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+--handling of simple unicode escapes
+select '$."the Copyright \u00a9 sign"'::jsonpath as correct_in_utf8;
+ correct_in_utf8
+--------------------------
+ $."the Copyright © sign"
+(1 row)
+
+select '$."dollar \u0024 character"'::jsonpath as correct_everywhere;
+ correct_everywhere
+------------------------
+ $."dollar $ character"
+(1 row)
+
+select '$."dollar \\u0024 character"'::jsonpath as not_an_escape;
+ not_an_escape
+------------------------------
+ $."dollar \\u0024 character"
+(1 row)
+
+select '$."null \u0000 escape"'::jsonpath as not_unescaped;
+ERROR: unsupported Unicode escape sequence
+LINE 1: select '$."null \u0000 escape"'::jsonpath as not_unescaped;
+ ^
+DETAIL: \u0000 cannot be converted to text.
+select '$."null \\u0000 escape"'::jsonpath as not_an_escape;
+ not_an_escape
+-------------------------
+ $."null \\u0000 escape"
+(1 row)
+
diff --git a/src/test/regress/expected/jsonpath_encoding_1.out b/src/test/regress/expected/jsonpath_encoding_1.out
new file mode 100644
index 0000000..005136c
--- /dev/null
+++ b/src/test/regress/expected/jsonpath_encoding_1.out
@@ -0,0 +1,168 @@
+--
+-- encoding-sensitive tests for jsonpath
+--
+-- We provide expected-results files for UTF8 (jsonpath_encoding.out)
+-- and for SQL_ASCII (jsonpath_encoding_1.out). Skip otherwise.
+SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
+ AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+SELECT getdatabaseencoding(); -- just to label the results files
+ getdatabaseencoding
+---------------------
+ SQL_ASCII
+(1 row)
+
+-- checks for double-quoted values
+-- basic unicode input
+SELECT '"\u"'::jsonpath; -- ERROR, incomplete escape
+ERROR: invalid unicode sequence at or near "\u" of jsonpath input
+LINE 1: SELECT '"\u"'::jsonpath;
+ ^
+SELECT '"\u00"'::jsonpath; -- ERROR, incomplete escape
+ERROR: invalid unicode sequence at or near "\u00" of jsonpath input
+LINE 1: SELECT '"\u00"'::jsonpath;
+ ^
+SELECT '"\u000g"'::jsonpath; -- ERROR, g is not a hex digit
+ERROR: invalid unicode sequence at or near "\u000" of jsonpath input
+LINE 1: SELECT '"\u000g"'::jsonpath;
+ ^
+SELECT '"\u0000"'::jsonpath; -- OK, legal escape
+ERROR: unsupported Unicode escape sequence
+LINE 1: SELECT '"\u0000"'::jsonpath;
+ ^
+DETAIL: \u0000 cannot be converted to text.
+SELECT '"\uaBcD"'::jsonpath; -- OK, uppercase and lower case both OK
+ERROR: conversion between UTF8 and SQL_ASCII is not supported
+LINE 1: SELECT '"\uaBcD"'::jsonpath;
+ ^
+-- handling of unicode surrogate pairs
+select '"\ud83d\ude04\ud83d\udc36"'::jsonpath as correct_in_utf8;
+ERROR: conversion between UTF8 and SQL_ASCII is not supported
+LINE 1: select '"\ud83d\ude04\ud83d\udc36"'::jsonpath as correct_in_...
+ ^
+select '"\ud83d\ud83d"'::jsonpath; -- 2 high surrogates in a row
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '"\ud83d\ud83d"'::jsonpath;
+ ^
+DETAIL: Unicode high surrogate must not follow a high surrogate.
+select '"\ude04\ud83d"'::jsonpath; -- surrogates in wrong order
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '"\ude04\ud83d"'::jsonpath;
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+select '"\ud83dX"'::jsonpath; -- orphan high surrogate
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '"\ud83dX"'::jsonpath;
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+select '"\ude04X"'::jsonpath; -- orphan low surrogate
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '"\ude04X"'::jsonpath;
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+--handling of simple unicode escapes
+select '"the Copyright \u00a9 sign"'::jsonpath as correct_in_utf8;
+ERROR: conversion between UTF8 and SQL_ASCII is not supported
+LINE 1: select '"the Copyright \u00a9 sign"'::jsonpath as correct_in...
+ ^
+select '"dollar \u0024 character"'::jsonpath as correct_everywhere;
+ correct_everywhere
+----------------------
+ "dollar $ character"
+(1 row)
+
+select '"dollar \\u0024 character"'::jsonpath as not_an_escape;
+ not_an_escape
+----------------------------
+ "dollar \\u0024 character"
+(1 row)
+
+select '"null \u0000 escape"'::jsonpath as not_unescaped;
+ERROR: unsupported Unicode escape sequence
+LINE 1: select '"null \u0000 escape"'::jsonpath as not_unescaped;
+ ^
+DETAIL: \u0000 cannot be converted to text.
+select '"null \\u0000 escape"'::jsonpath as not_an_escape;
+ not_an_escape
+-----------------------
+ "null \\u0000 escape"
+(1 row)
+
+-- checks for quoted key names
+-- basic unicode input
+SELECT '$."\u"'::jsonpath; -- ERROR, incomplete escape
+ERROR: invalid unicode sequence at or near "\u" of jsonpath input
+LINE 1: SELECT '$."\u"'::jsonpath;
+ ^
+SELECT '$."\u00"'::jsonpath; -- ERROR, incomplete escape
+ERROR: invalid unicode sequence at or near "\u00" of jsonpath input
+LINE 1: SELECT '$."\u00"'::jsonpath;
+ ^
+SELECT '$."\u000g"'::jsonpath; -- ERROR, g is not a hex digit
+ERROR: invalid unicode sequence at or near "\u000" of jsonpath input
+LINE 1: SELECT '$."\u000g"'::jsonpath;
+ ^
+SELECT '$."\u0000"'::jsonpath; -- OK, legal escape
+ERROR: unsupported Unicode escape sequence
+LINE 1: SELECT '$."\u0000"'::jsonpath;
+ ^
+DETAIL: \u0000 cannot be converted to text.
+SELECT '$."\uaBcD"'::jsonpath; -- OK, uppercase and lower case both OK
+ERROR: conversion between UTF8 and SQL_ASCII is not supported
+LINE 1: SELECT '$."\uaBcD"'::jsonpath;
+ ^
+-- handling of unicode surrogate pairs
+select '$."\ud83d\ude04\ud83d\udc36"'::jsonpath as correct_in_utf8;
+ERROR: conversion between UTF8 and SQL_ASCII is not supported
+LINE 1: select '$."\ud83d\ude04\ud83d\udc36"'::jsonpath as correct_i...
+ ^
+select '$."\ud83d\ud83d"'::jsonpath; -- 2 high surrogates in a row
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '$."\ud83d\ud83d"'::jsonpath;
+ ^
+DETAIL: Unicode high surrogate must not follow a high surrogate.
+select '$."\ude04\ud83d"'::jsonpath; -- surrogates in wrong order
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '$."\ude04\ud83d"'::jsonpath;
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+select '$."\ud83dX"'::jsonpath; -- orphan high surrogate
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '$."\ud83dX"'::jsonpath;
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+select '$."\ude04X"'::jsonpath; -- orphan low surrogate
+ERROR: invalid input syntax for type jsonpath
+LINE 1: select '$."\ude04X"'::jsonpath;
+ ^
+DETAIL: Unicode low surrogate must follow a high surrogate.
+--handling of simple unicode escapes
+select '$."the Copyright \u00a9 sign"'::jsonpath as correct_in_utf8;
+ERROR: conversion between UTF8 and SQL_ASCII is not supported
+LINE 1: select '$."the Copyright \u00a9 sign"'::jsonpath as correct_...
+ ^
+select '$."dollar \u0024 character"'::jsonpath as correct_everywhere;
+ correct_everywhere
+------------------------
+ $."dollar $ character"
+(1 row)
+
+select '$."dollar \\u0024 character"'::jsonpath as not_an_escape;
+ not_an_escape
+------------------------------
+ $."dollar \\u0024 character"
+(1 row)
+
+select '$."null \u0000 escape"'::jsonpath as not_unescaped;
+ERROR: unsupported Unicode escape sequence
+LINE 1: select '$."null \u0000 escape"'::jsonpath as not_unescaped;
+ ^
+DETAIL: \u0000 cannot be converted to text.
+select '$."null \\u0000 escape"'::jsonpath as not_an_escape;
+ not_an_escape
+-------------------------
+ $."null \\u0000 escape"
+(1 row)
+
diff --git a/src/test/regress/expected/jsonpath_encoding_2.out b/src/test/regress/expected/jsonpath_encoding_2.out
new file mode 100644
index 0000000..bb71bfe
--- /dev/null
+++ b/src/test/regress/expected/jsonpath_encoding_2.out
@@ -0,0 +1,9 @@
+--
+-- encoding-sensitive tests for jsonpath
+--
+-- We provide expected-results files for UTF8 (jsonpath_encoding.out)
+-- and for SQL_ASCII (jsonpath_encoding_1.out). Skip otherwise.
+SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
+ AS skip_test \gset
+\if :skip_test
+\quit
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
new file mode 100644
index 0000000..8a98bbe
--- /dev/null
+++ b/src/test/regress/expected/limit.out
@@ -0,0 +1,694 @@
+--
+-- 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)
+
+fetch backward 1 in c1;
+ q1 | q2
+------------------+-------------------
+ 4567890123456789 | -4567890123456789
+(1 row)
+
+fetch backward all in c1;
+ q1 | q2
+------------------+------------------
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | 123
+ 123 | 4567890123456789
+ 123 | 456
+(4 rows)
+
+fetch backward 1 in c1;
+ q1 | q2
+----+----
+(0 rows)
+
+fetch all in c1;
+ q1 | q2
+------------------+-------------------
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+(5 rows)
+
+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)
+
+fetch backward 1 in c2;
+ q1 | q2
+------------------+-----
+ 4567890123456789 | 123
+(1 row)
+
+fetch backward all in c2;
+ q1 | q2
+-----+------------------
+ 123 | 4567890123456789
+ 123 | 456
+(2 rows)
+
+fetch backward 1 in c2;
+ q1 | q2
+----+----
+(0 rows)
+
+fetch all in c2;
+ q1 | q2
+------------------+------------------
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+(3 rows)
+
+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)
+
+fetch backward 1 in c3;
+ q1 | q2
+------------------+-------------------
+ 4567890123456789 | -4567890123456789
+(1 row)
+
+fetch backward all in c3;
+ q1 | q2
+------------------+------------------
+ 4567890123456789 | 4567890123456789
+(1 row)
+
+fetch backward 1 in c3;
+ q1 | q2
+----+----
+(0 rows)
+
+fetch all in c3;
+ q1 | q2
+------------------+-------------------
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+(2 rows)
+
+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)
+
+fetch backward 1 in c4;
+ q1 | q2
+----+----
+(0 rows)
+
+fetch backward all in c4;
+ q1 | q2
+----+----
+(0 rows)
+
+fetch backward 1 in c4;
+ q1 | q2
+----+----
+(0 rows)
+
+fetch all in c4;
+ q1 | q2
+----+----
+(0 rows)
+
+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;
+ q1 | q2
+-----+------------------
+ 123 | 4567890123456789
+(1 row)
+
+fetch backward 1 in c5;
+ q1 | q2
+-----+-----
+ 123 | 456
+(1 row)
+
+fetch all in c5;
+ q1 | q2
+-----+------------------
+ 123 | 4567890123456789
+(1 row)
+
+fetch backward all in c5;
+ q1 | q2
+-----+------------------
+ 123 | 4567890123456789
+ 123 | 456
+(2 rows)
+
+fetch all in c5;
+ q1 | q2
+-----+------------------
+ 123 | 456
+ 123 | 4567890123456789
+(2 rows)
+
+fetch backward all in c5;
+ q1 | q2
+-----+------------------
+ 123 | 4567890123456789
+ 123 | 456
+(2 rows)
+
+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, two, four, ten, twenty, hundred, thousand, twothousand, fivethous, tenthous, odd, even, stringu1, stringu2, string4
+(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
+(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
+(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/line.out b/src/test/regress/expected/line.out
new file mode 100644
index 0000000..fe10658
--- /dev/null
+++ b/src/test/regress/expected/line.out
@@ -0,0 +1,87 @@
+--
+-- LINE
+-- Infinite lines
+--
+--DROP TABLE LINE_TBL;
+CREATE TABLE LINE_TBL (s line);
+INSERT INTO LINE_TBL VALUES ('{0,-1,5}'); -- A == 0
+INSERT INTO LINE_TBL VALUES ('{1,0,5}'); -- B == 0
+INSERT INTO LINE_TBL VALUES ('{0,3,0}'); -- A == C == 0
+INSERT INTO LINE_TBL VALUES (' (0,0), (6,6)');
+INSERT INTO LINE_TBL VALUES ('10,-10 ,-5,-4');
+INSERT INTO LINE_TBL VALUES ('[-1e6,2e2,3e5, -4e1]');
+INSERT INTO LINE_TBL VALUES ('{3,NaN,5}');
+INSERT INTO LINE_TBL VALUES ('{NaN,NaN,NaN}');
+-- horizontal
+INSERT INTO LINE_TBL VALUES ('[(1,3),(2,3)]');
+-- vertical
+INSERT INTO LINE_TBL VALUES (line(point '(3,1)', point '(3,2)'));
+-- bad values for parser testing
+INSERT INTO LINE_TBL VALUES ('{}');
+ERROR: invalid input syntax for type line: "{}"
+LINE 1: INSERT INTO LINE_TBL VALUES ('{}');
+ ^
+INSERT INTO LINE_TBL VALUES ('{0');
+ERROR: invalid input syntax for type line: "{0"
+LINE 1: INSERT INTO LINE_TBL VALUES ('{0');
+ ^
+INSERT INTO LINE_TBL VALUES ('{0,0}');
+ERROR: invalid input syntax for type line: "{0,0}"
+LINE 1: INSERT INTO LINE_TBL VALUES ('{0,0}');
+ ^
+INSERT INTO LINE_TBL VALUES ('{0,0,1');
+ERROR: invalid input syntax for type line: "{0,0,1"
+LINE 1: INSERT INTO LINE_TBL VALUES ('{0,0,1');
+ ^
+INSERT INTO LINE_TBL VALUES ('{0,0,1}');
+ERROR: invalid line specification: A and B cannot both be zero
+LINE 1: INSERT INTO LINE_TBL VALUES ('{0,0,1}');
+ ^
+INSERT INTO LINE_TBL VALUES ('{0,0,1} x');
+ERROR: invalid input syntax for type line: "{0,0,1} x"
+LINE 1: INSERT INTO LINE_TBL VALUES ('{0,0,1} x');
+ ^
+INSERT INTO LINE_TBL VALUES ('(3asdf,2 ,3,4r2)');
+ERROR: invalid input syntax for type line: "(3asdf,2 ,3,4r2)"
+LINE 1: INSERT INTO LINE_TBL VALUES ('(3asdf,2 ,3,4r2)');
+ ^
+INSERT INTO LINE_TBL VALUES ('[1,2,3, 4');
+ERROR: invalid input syntax for type line: "[1,2,3, 4"
+LINE 1: INSERT INTO LINE_TBL VALUES ('[1,2,3, 4');
+ ^
+INSERT INTO LINE_TBL VALUES ('[(,2),(3,4)]');
+ERROR: invalid input syntax for type line: "[(,2),(3,4)]"
+LINE 1: INSERT INTO LINE_TBL VALUES ('[(,2),(3,4)]');
+ ^
+INSERT INTO LINE_TBL VALUES ('[(1,2),(3,4)');
+ERROR: invalid input syntax for type line: "[(1,2),(3,4)"
+LINE 1: INSERT INTO LINE_TBL VALUES ('[(1,2),(3,4)');
+ ^
+INSERT INTO LINE_TBL VALUES ('[(1,2),(1,2)]');
+ERROR: invalid line specification: must be two distinct points
+LINE 1: INSERT INTO LINE_TBL VALUES ('[(1,2),(1,2)]');
+ ^
+INSERT INTO LINE_TBL VALUES (line(point '(1,0)', point '(1,0)'));
+ERROR: invalid line specification: must be two distinct points
+select * from LINE_TBL;
+ s
+------------------------------------------------
+ {0,-1,5}
+ {1,0,5}
+ {0,3,0}
+ {1,-1,0}
+ {-0.4,-1,-6}
+ {-0.0001846153846153846,-1,15.384615384615387}
+ {3,NaN,5}
+ {NaN,NaN,NaN}
+ {0,-1,3}
+ {-1,0,3}
+(10 rows)
+
+select '{nan, 1, nan}'::line = '{nan, 1, nan}'::line as true,
+ '{nan, 1, nan}'::line = '{nan, 2, nan}'::line as false;
+ true | false
+------+-------
+ t | f
+(1 row)
+
diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out
new file mode 100644
index 0000000..d43bee0
--- /dev/null
+++ b/src/test/regress/expected/lock.out
@@ -0,0 +1,178 @@
+--
+-- Test the LOCK statement
+--
+-- 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;
+-- 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;
+-- 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;
+-- 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;
+-- 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;
+-- 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;
+-- 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;
+--
+-- Clean up
+--
+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;
+SELECT test_atomic_ops();
+ test_atomic_ops
+-----------------
+ t
+(1 row)
+
diff --git a/src/test/regress/expected/lseg.out b/src/test/regress/expected/lseg.out
new file mode 100644
index 0000000..7e878b5
--- /dev/null
+++ b/src/test/regress/expected/lseg.out
@@ -0,0 +1,44 @@
+--
+-- LSEG
+-- Line segments
+--
+--DROP TABLE LSEG_TBL;
+CREATE TABLE LSEG_TBL (s lseg);
+INSERT INTO LSEG_TBL VALUES ('[(1,2),(3,4)]');
+INSERT INTO LSEG_TBL VALUES ('(0,0),(6,6)');
+INSERT INTO LSEG_TBL VALUES ('10,-10 ,-3,-4');
+INSERT INTO LSEG_TBL VALUES ('[-1e6,2e2,3e5, -4e1]');
+INSERT INTO LSEG_TBL VALUES (lseg(point(11, 22), point(33,44)));
+INSERT INTO LSEG_TBL VALUES ('[(-10,2),(-10,3)]'); -- vertical
+INSERT INTO LSEG_TBL VALUES ('[(0,-20),(30,-20)]'); -- horizontal
+INSERT INTO LSEG_TBL VALUES ('[(NaN,1),(NaN,90)]'); -- NaN
+-- bad values for parser testing
+INSERT INTO LSEG_TBL VALUES ('(3asdf,2 ,3,4r2)');
+ERROR: invalid input syntax for type lseg: "(3asdf,2 ,3,4r2)"
+LINE 1: INSERT INTO LSEG_TBL VALUES ('(3asdf,2 ,3,4r2)');
+ ^
+INSERT INTO LSEG_TBL VALUES ('[1,2,3, 4');
+ERROR: invalid input syntax for type lseg: "[1,2,3, 4"
+LINE 1: INSERT INTO LSEG_TBL VALUES ('[1,2,3, 4');
+ ^
+INSERT INTO LSEG_TBL VALUES ('[(,2),(3,4)]');
+ERROR: invalid input syntax for type lseg: "[(,2),(3,4)]"
+LINE 1: INSERT INTO LSEG_TBL VALUES ('[(,2),(3,4)]');
+ ^
+INSERT INTO LSEG_TBL VALUES ('[(1,2),(3,4)');
+ERROR: invalid input syntax for type lseg: "[(1,2),(3,4)"
+LINE 1: INSERT INTO LSEG_TBL VALUES ('[(1,2),(3,4)');
+ ^
+select * from LSEG_TBL;
+ s
+-------------------------------
+ [(1,2),(3,4)]
+ [(0,0),(6,6)]
+ [(10,-10),(-3,-4)]
+ [(-1000000,200),(300000,-40)]
+ [(11,22),(33,44)]
+ [(-10,2),(-10,3)]
+ [(0,-20),(30,-20)]
+ [(NaN,1),(NaN,90)]
+(8 rows)
+
diff --git a/src/test/regress/expected/macaddr.out b/src/test/regress/expected/macaddr.out
new file mode 100644
index 0000000..151f9ce
--- /dev/null
+++ b/src/test/regress/expected/macaddr.out
@@ -0,0 +1,160 @@
+--
+-- 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;
+ 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)
+
+CREATE INDEX macaddr_data_btree ON macaddr_data USING btree (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;
+ ?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;
+ ?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;
+ ?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/macaddr8.out b/src/test/regress/expected/macaddr8.out
new file mode 100644
index 0000000..74f53a1
--- /dev/null
+++ b/src/test/regress/expected/macaddr8.out
@@ -0,0 +1,354 @@
+--
+-- 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)
+
+CREATE INDEX macaddr8_data_btree ON macaddr8_data USING btree (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;
+ ?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;
+ ?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;
+ ?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/matview.out b/src/test/regress/expected/matview.out
new file mode 100644
index 0000000..c109d97
--- /dev/null
+++ b/src/test/regress/expected/matview.out
@@ -0,0 +1,678 @@
+-- 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;
+ 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" btree (grandtot)
+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
+ Materialized view "public.mvtest_tvmm"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+----------+---------+-----------+----------+---------+---------+--------------+-------------
+ grandtot | numeric | | | | main | |
+Indexes:
+ "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
+ "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
+View definition:
+ SELECT sum(mvtest_tvm.totamt) AS grandtot
+ FROM mvtest_mvschema.mvtest_tvm;
+
+SET search_path = mvtest_mvschema, public;
+\d+ mvtest_tvm
+ Materialized view "mvtest_mvschema.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;
+
+-- 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: view mvtest_tv depends on table mvtest_t
+materialized view mvtest_mvschema.mvtest_tvm depends on view mvtest_tv
+materialized view mvtest_tvmm depends on materialized view mvtest_mvschema.mvtest_tvm
+view mvtest_tvv depends on view mvtest_tv
+materialized view mvtest_tvvm depends on view mvtest_tvv
+view mvtest_tvvmv depends on materialized view mvtest_tvvm
+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
+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 view mvtest_tv
+drop cascades to materialized view mvtest_mvschema.mvtest_tvm
+drop cascades to materialized view mvtest_tvmm
+drop cascades to view mvtest_tvv
+drop cascades to materialized view mvtest_tvvm
+drop cascades to view mvtest_tvvmv
+drop cascades to materialized view mvtest_bb
+drop cascades to materialized view mvtest_tm
+drop cascades to materialized view mvtest_tmm
+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 view mvtest_vt2
+drop cascades to materialized view mv_test2
+drop cascades to materialized view mv_test3
+-- 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: could not create unique index "mvtest_mv_a_idx"
+DETAIL: Key (a)=(1) is duplicated.
+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;
+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;
+ a
+----
+ 6
+ 7
+ 8
+ 9
+ 10
+(5 rows)
+
+SELECT * FROM mvtest_mv_v;
+ 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 (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 (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_withdata1
+drop cascades to materialized view matview_schema.mv_withdata2
+drop cascades to materialized view matview_schema.mv_nodata1
+drop cascades to materialized view matview_schema.mv_nodata2
+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/memoize.out b/src/test/regress/expected/memoize.out
new file mode 100644
index 0000000..4ca0bd1
--- /dev/null
+++ b/src/test/regress/expected/memoize.out
@@ -0,0 +1,283 @@
+-- Perform tests on the Memoize node.
+-- The cache hits/misses/evictions from the Memoize node can vary between
+-- machines. Let's just replace the number with an 'N'. In order to allow us
+-- to perform validation when the measure was zero, we replace a zero value
+-- with "Zero". All other numbers are replaced with 'N'.
+create function explain_memoize(query text, hide_hitmiss bool) returns setof text
+language plpgsql as
+$$
+declare
+ ln text;
+begin
+ for ln in
+ execute format('explain (analyze, costs off, summary off, timing off) %s',
+ query)
+ loop
+ if hide_hitmiss = true then
+ ln := regexp_replace(ln, 'Hits: 0', 'Hits: Zero');
+ ln := regexp_replace(ln, 'Hits: \d+', 'Hits: N');
+ ln := regexp_replace(ln, 'Misses: 0', 'Misses: Zero');
+ ln := regexp_replace(ln, 'Misses: \d+', 'Misses: N');
+ end if;
+ ln := regexp_replace(ln, 'Evictions: 0', 'Evictions: Zero');
+ ln := regexp_replace(ln, 'Evictions: \d+', 'Evictions: N');
+ ln := regexp_replace(ln, 'Memory Usage: \d+', 'Memory Usage: N');
+ ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N');
+ ln := regexp_replace(ln, 'loops=\d+', 'loops=N');
+ return next ln;
+ end loop;
+end;
+$$;
+-- Ensure we get a memoize node on the inner side of the nested loop
+SET enable_hashjoin TO off;
+SET enable_bitmapscan TO off;
+SELECT explain_memoize('
+SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
+INNER JOIN tenk1 t2 ON t1.unique1 = t2.twenty
+WHERE t2.unique1 < 1000;', false);
+ explain_memoize
+-------------------------------------------------------------------------------------------
+ Aggregate (actual rows=1 loops=N)
+ -> Nested Loop (actual rows=1000 loops=N)
+ -> Seq Scan on tenk1 t2 (actual rows=1000 loops=N)
+ Filter: (unique1 < 1000)
+ Rows Removed by Filter: 9000
+ -> Memoize (actual rows=1 loops=N)
+ Cache Key: t2.twenty
+ Cache Mode: logical
+ Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ -> Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1 loops=N)
+ Index Cond: (unique1 = t2.twenty)
+ Heap Fetches: N
+(12 rows)
+
+-- And check we get the expected results.
+SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
+INNER JOIN tenk1 t2 ON t1.unique1 = t2.twenty
+WHERE t2.unique1 < 1000;
+ count | avg
+-------+--------------------
+ 1000 | 9.5000000000000000
+(1 row)
+
+-- Try with LATERAL joins
+SELECT explain_memoize('
+SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
+LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2
+WHERE t1.unique1 < 1000;', false);
+ explain_memoize
+-------------------------------------------------------------------------------------------
+ Aggregate (actual rows=1 loops=N)
+ -> Nested Loop (actual rows=1000 loops=N)
+ -> Seq Scan on tenk1 t1 (actual rows=1000 loops=N)
+ Filter: (unique1 < 1000)
+ Rows Removed by Filter: 9000
+ -> Memoize (actual rows=1 loops=N)
+ Cache Key: t1.twenty
+ Cache Mode: logical
+ Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ -> Index Only Scan using tenk1_unique1 on tenk1 t2 (actual rows=1 loops=N)
+ Index Cond: (unique1 = t1.twenty)
+ Heap Fetches: N
+(12 rows)
+
+-- And check we get the expected results.
+SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
+LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2
+WHERE t1.unique1 < 1000;
+ count | avg
+-------+--------------------
+ 1000 | 9.5000000000000000
+(1 row)
+
+-- Reduce work_mem so that we see some cache evictions
+SET work_mem TO '64kB';
+SET enable_mergejoin TO off;
+-- Ensure we get some evictions. We're unable to validate the hits and misses
+-- here as the number of entries that fit in the cache at once will vary
+-- between different machines.
+SELECT explain_memoize('
+SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
+INNER JOIN tenk1 t2 ON t1.unique1 = t2.thousand
+WHERE t2.unique1 < 1200;', true);
+ explain_memoize
+-------------------------------------------------------------------------------------------
+ Aggregate (actual rows=1 loops=N)
+ -> Nested Loop (actual rows=1200 loops=N)
+ -> Seq Scan on tenk1 t2 (actual rows=1200 loops=N)
+ Filter: (unique1 < 1200)
+ Rows Removed by Filter: 8800
+ -> Memoize (actual rows=1 loops=N)
+ Cache Key: t2.thousand
+ Cache Mode: logical
+ Hits: N Misses: N Evictions: N Overflows: 0 Memory Usage: NkB
+ -> Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1 loops=N)
+ Index Cond: (unique1 = t2.thousand)
+ Heap Fetches: N
+(12 rows)
+
+CREATE TABLE flt (f float);
+CREATE INDEX flt_f_idx ON flt (f);
+INSERT INTO flt VALUES('-0.0'::float),('+0.0'::float);
+ANALYZE flt;
+SET enable_seqscan TO off;
+-- Ensure memoize operates in logical mode
+SELECT explain_memoize('
+SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f = f2.f;', false);
+ explain_memoize
+-------------------------------------------------------------------------------
+ Nested Loop (actual rows=4 loops=N)
+ -> Index Only Scan using flt_f_idx on flt f1 (actual rows=2 loops=N)
+ Heap Fetches: N
+ -> Memoize (actual rows=2 loops=N)
+ Cache Key: f1.f
+ Cache Mode: logical
+ Hits: 1 Misses: 1 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ -> Index Only Scan using flt_f_idx on flt f2 (actual rows=2 loops=N)
+ Index Cond: (f = f1.f)
+ Heap Fetches: N
+(10 rows)
+
+-- Ensure memoize operates in binary mode
+SELECT explain_memoize('
+SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f >= f2.f;', false);
+ explain_memoize
+-------------------------------------------------------------------------------
+ Nested Loop (actual rows=4 loops=N)
+ -> Index Only Scan using flt_f_idx on flt f1 (actual rows=2 loops=N)
+ Heap Fetches: N
+ -> Memoize (actual rows=2 loops=N)
+ Cache Key: f1.f
+ Cache Mode: binary
+ Hits: 0 Misses: 2 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ -> Index Only Scan using flt_f_idx on flt f2 (actual rows=2 loops=N)
+ Index Cond: (f <= f1.f)
+ Heap Fetches: N
+(10 rows)
+
+DROP TABLE flt;
+-- Exercise Memoize in binary mode with a large fixed width type and a
+-- varlena type.
+CREATE TABLE strtest (n name, t text);
+CREATE INDEX strtest_n_idx ON strtest (n);
+CREATE INDEX strtest_t_idx ON strtest (t);
+INSERT INTO strtest VALUES('one','one'),('two','two'),('three',repeat(md5('three'),100));
+-- duplicate rows so we get some cache hits
+INSERT INTO strtest SELECT * FROM strtest;
+ANALYZE strtest;
+-- Ensure we get 3 hits and 3 misses
+SELECT explain_memoize('
+SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.n >= s2.n;', false);
+ explain_memoize
+----------------------------------------------------------------------------------
+ Nested Loop (actual rows=24 loops=N)
+ -> Seq Scan on strtest s1 (actual rows=6 loops=N)
+ -> Memoize (actual rows=4 loops=N)
+ Cache Key: s1.n
+ Cache Mode: binary
+ Hits: 3 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ -> Index Scan using strtest_n_idx on strtest s2 (actual rows=4 loops=N)
+ Index Cond: (n <= s1.n)
+(8 rows)
+
+-- Ensure we get 3 hits and 3 misses
+SELECT explain_memoize('
+SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.t >= s2.t;', false);
+ explain_memoize
+----------------------------------------------------------------------------------
+ Nested Loop (actual rows=24 loops=N)
+ -> Seq Scan on strtest s1 (actual rows=6 loops=N)
+ -> Memoize (actual rows=4 loops=N)
+ Cache Key: s1.t
+ Cache Mode: binary
+ Hits: 3 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ -> Index Scan using strtest_t_idx on strtest s2 (actual rows=4 loops=N)
+ Index Cond: (t <= s1.t)
+(8 rows)
+
+DROP TABLE strtest;
+-- Exercise Memoize code that flushes the cache when a parameter changes which
+-- is not part of the cache key.
+-- Ensure we get a Memoize plan
+EXPLAIN (COSTS OFF)
+SELECT unique1 FROM tenk1 t0
+WHERE unique1 < 3
+ AND EXISTS (
+ SELECT 1 FROM tenk1 t1
+ INNER JOIN tenk1 t2 ON t1.unique1 = t2.hundred
+ WHERE t0.ten = t1.twenty AND t0.two <> t2.four OFFSET 0);
+ QUERY PLAN
+----------------------------------------------------------------
+ Index Scan using tenk1_unique1 on tenk1 t0
+ Index Cond: (unique1 < 3)
+ Filter: (SubPlan 1)
+ SubPlan 1
+ -> Nested Loop
+ -> Index Scan using tenk1_hundred on tenk1 t2
+ Filter: (t0.two <> four)
+ -> Memoize
+ Cache Key: t2.hundred
+ Cache Mode: logical
+ -> Index Scan using tenk1_unique1 on tenk1 t1
+ Index Cond: (unique1 = t2.hundred)
+ Filter: (t0.ten = twenty)
+(13 rows)
+
+-- Ensure the above query returns the correct result
+SELECT unique1 FROM tenk1 t0
+WHERE unique1 < 3
+ AND EXISTS (
+ SELECT 1 FROM tenk1 t1
+ INNER JOIN tenk1 t2 ON t1.unique1 = t2.hundred
+ WHERE t0.ten = t1.twenty AND t0.two <> t2.four OFFSET 0);
+ unique1
+---------
+ 2
+(1 row)
+
+RESET enable_seqscan;
+RESET enable_mergejoin;
+RESET work_mem;
+RESET enable_bitmapscan;
+RESET enable_hashjoin;
+-- Test parallel plans with Memoize
+SET min_parallel_table_scan_size TO 0;
+SET parallel_setup_cost TO 0;
+SET parallel_tuple_cost TO 0;
+SET max_parallel_workers_per_gather TO 2;
+-- Ensure we get a parallel plan.
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
+LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2
+WHERE t1.unique1 < 1000;
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Nested Loop
+ -> Parallel Bitmap Heap Scan on tenk1 t1
+ Recheck Cond: (unique1 < 1000)
+ -> Bitmap Index Scan on tenk1_unique1
+ Index Cond: (unique1 < 1000)
+ -> Memoize
+ Cache Key: t1.twenty
+ Cache Mode: logical
+ -> Index Only Scan using tenk1_unique1 on tenk1 t2
+ Index Cond: (unique1 = t1.twenty)
+(14 rows)
+
+-- And ensure the parallel plan gives us the correct results.
+SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
+LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2
+WHERE t1.unique1 < 1000;
+ count | avg
+-------+--------------------
+ 1000 | 9.5000000000000000
+(1 row)
+
+RESET max_parallel_workers_per_gather;
+RESET parallel_tuple_cost;
+RESET parallel_setup_cost;
+RESET min_parallel_table_scan_size;
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
new file mode 100644
index 0000000..e845042
--- /dev/null
+++ b/src/test/regress/expected/misc_functions.out
@@ -0,0 +1,276 @@
+--
+-- 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.
+--
+-- 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.
+--
+SELECT * FROM pg_log_backend_memory_contexts(pg_backend_pid());
+ pg_log_backend_memory_contexts
+--------------------------------
+ t
+(1 row)
+
+--
+-- 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)
+
+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 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);
+ QUERY PLAN
+----------------------------------------------
+ Hash Join
+ Hash Cond: (b.unique1 = a.unique1)
+ -> Seq Scan on tenk1 b
+ -> Hash
+ -> Seq Scan on tenk1 a
+ Filter: my_int_eq(unique2, 42)
+(6 rows)
+
+-- With support function that knows it's int4eq, we get a different plan
+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);
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ -> Seq Scan on tenk1 a
+ Filter: my_int_eq(unique2, 42)
+ -> Index Scan using tenk1_unique1 on tenk1 b
+ Index Cond: (unique1 = a.unique1)
+(5 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
+-------------------------------------------------
+ Nested Loop
+ -> Function Scan on my_gen_series g
+ -> Index Scan using tenk1_unique1 on tenk1 a
+ Index Cond: (unique1 = g.g)
+(4 rows)
+
diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out
new file mode 100644
index 0000000..a67f401
--- /dev/null
+++ b/src/test/regress/expected/misc_sanity.out
@@ -0,0 +1,140 @@
+--
+-- MISC_SANITY
+-- Sanity checks for common errors in making system tables that don't fit
+-- comfortably into either opr_sanity or type_sanity.
+--
+-- Every test failure in this file should be closely inspected.
+-- The description of the failing test should be read carefully before
+-- adjusting the expected output. In most cases, the queries should
+-- not find *any* matching entries.
+--
+-- NB: run this test early, because some later tests create bogus entries.
+-- **************** pg_depend ****************
+-- Look for illegal values in pg_depend fields.
+-- classid/objid can be zero, but only in 'p' entries
+SELECT *
+FROM pg_depend as d1
+WHERE refclassid = 0 OR refobjid = 0 OR
+ deptype NOT IN ('a', 'e', 'i', 'n', 'p') OR
+ (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
+ (deptype = 'p' AND (classid != 0 OR objid != 0 OR objsubid != 0));
+ classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype
+---------+-------+----------+------------+----------+-------------+---------
+(0 rows)
+
+-- **************** pg_shdepend ****************
+-- Look for illegal values in pg_shdepend fields.
+-- classid/objid can be zero, but only in 'p' entries
+SELECT *
+FROM pg_shdepend as d1
+WHERE refclassid = 0 OR refobjid = 0 OR
+ deptype NOT IN ('a', 'o', 'p', 'r') OR
+ (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
+ (deptype = 'p' AND (dbid != 0 OR classid != 0 OR objid != 0 OR objsubid != 0));
+ dbid | classid | objid | objsubid | refclassid | refobjid | deptype
+------+---------+-------+----------+------------+----------+---------
+(0 rows)
+
+-- Check each OID-containing system catalog to see if its lowest-numbered OID
+-- is pinned. If not, and if that OID was generated during initdb, then
+-- perhaps initdb forgot to scan that catalog for pinnable entries.
+-- Generally, it's okay for a catalog to be listed in the output of this
+-- test if that catalog is scanned by initdb.c's setup_depend() function;
+-- whatever OID the test is complaining about must have been added later
+-- in initdb, where it intentionally isn't pinned. Legitimate exceptions
+-- to that rule are listed in the comments in setup_depend().
+-- Currently, pg_rewrite is also listed by this check, even though it is
+-- covered by setup_depend(). That happens because there are no rules in
+-- the pinned data, but initdb creates some intentionally-not-pinned views.
+do $$
+declare relnm text;
+ reloid oid;
+ shared bool;
+ lowoid oid;
+ pinned bool;
+begin
+for relnm, reloid, shared in
+ select relname, oid, relisshared from pg_class
+ where EXISTS(
+ SELECT * FROM pg_attribute
+ WHERE attrelid = pg_class.oid AND attname = 'oid')
+ and relkind = 'r' and oid < 16384 order by 1
+loop
+ execute 'select min(oid) from ' || relnm into lowoid;
+ continue when lowoid is null or lowoid >= 16384;
+ if shared then
+ pinned := exists(select 1 from pg_shdepend
+ where refclassid = reloid and refobjid = lowoid
+ and deptype = 'p');
+ else
+ pinned := exists(select 1 from pg_depend
+ where refclassid = reloid and refobjid = lowoid
+ and deptype = 'p');
+ end if;
+ if not pinned then
+ raise notice '% contains unpinned initdb-created object(s)', relnm;
+ end if;
+end loop;
+end$$;
+NOTICE: pg_database contains unpinned initdb-created object(s)
+NOTICE: pg_extension contains unpinned initdb-created object(s)
+NOTICE: pg_rewrite contains unpinned initdb-created object(s)
+NOTICE: pg_tablespace contains unpinned initdb-created object(s)
+-- **************** pg_class ****************
+-- Look for system tables with varlena columns but no toast table. All
+-- system tables with toastable columns should have toast tables, with
+-- the following exceptions:
+-- 1. pg_class, pg_attribute, and pg_index, due to fear of recursive
+-- dependencies as toast tables depend on them.
+-- 2. pg_largeobject and pg_largeobject_metadata. Large object catalogs
+-- and toast tables are mutually exclusive and large object data is handled
+-- as user data by pg_upgrade, which would cause failures.
+SELECT relname, attname, atttypid::regtype
+FROM pg_class c JOIN pg_attribute a ON c.oid = attrelid
+WHERE c.oid < 16384 AND
+ reltoastrelid = 0 AND
+ relkind = 'r' AND
+ attstorage != 'p'
+ORDER BY 1, 2;
+ relname | attname | atttypid
+-------------------------+---------------+--------------
+ pg_attribute | attacl | aclitem[]
+ pg_attribute | attfdwoptions | text[]
+ pg_attribute | attmissingval | anyarray
+ pg_attribute | attoptions | text[]
+ pg_class | relacl | aclitem[]
+ pg_class | reloptions | text[]
+ pg_class | relpartbound | pg_node_tree
+ pg_index | indexprs | pg_node_tree
+ pg_index | indpred | pg_node_tree
+ pg_largeobject | data | bytea
+ pg_largeobject_metadata | lomacl | aclitem[]
+(11 rows)
+
+-- system catalogs without primary keys
+--
+-- Current exceptions:
+-- * pg_depend, pg_shdepend don't have a unique key
+SELECT relname
+FROM pg_class
+WHERE relnamespace = 'pg_catalog'::regnamespace AND relkind = 'r'
+ AND pg_class.oid NOT IN (SELECT indrelid FROM pg_index WHERE indisprimary)
+ORDER BY 1;
+ relname
+-------------
+ pg_depend
+ pg_shdepend
+(2 rows)
+
+-- system catalog unique indexes not wrapped in a constraint
+-- (There should be none.)
+SELECT relname
+FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid
+WHERE relnamespace = 'pg_catalog'::regnamespace AND relkind = 'i'
+ AND i.indisunique
+ AND c.oid NOT IN (SELECT conindid FROM pg_constraint)
+ORDER BY 1;
+ relname
+---------
+(0 rows)
+
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
new file mode 100644
index 0000000..fc71a72
--- /dev/null
+++ b/src/test/regress/expected/money.out
@@ -0,0 +1,505 @@
+--
+-- MONEY
+--
+-- Note that we assume lc_monetary has been set to C.
+--
+CREATE TABLE money_data (m money);
+INSERT INTO money_data VALUES ('123');
+SELECT * FROM money_data;
+ m
+---------
+ $123.00
+(1 row)
+
+SELECT m + '123' FROM money_data;
+ ?column?
+----------
+ $246.00
+(1 row)
+
+SELECT m + '123.45' FROM money_data;
+ ?column?
+----------
+ $246.45
+(1 row)
+
+SELECT m - '123.45' FROM money_data;
+ ?column?
+----------
+ -$0.45
+(1 row)
+
+SELECT m / '2'::money FROM money_data;
+ ?column?
+----------
+ 61.5
+(1 row)
+
+SELECT m * 2 FROM money_data;
+ ?column?
+----------
+ $246.00
+(1 row)
+
+SELECT 2 * m FROM money_data;
+ ?column?
+----------
+ $246.00
+(1 row)
+
+SELECT m / 2 FROM money_data;
+ ?column?
+----------
+ $61.50
+(1 row)
+
+SELECT m * 2::int2 FROM money_data;
+ ?column?
+----------
+ $246.00
+(1 row)
+
+SELECT 2::int2 * m FROM money_data;
+ ?column?
+----------
+ $246.00
+(1 row)
+
+SELECT m / 2::int2 FROM money_data;
+ ?column?
+----------
+ $61.50
+(1 row)
+
+SELECT m * 2::int8 FROM money_data;
+ ?column?
+----------
+ $246.00
+(1 row)
+
+SELECT 2::int8 * m FROM money_data;
+ ?column?
+----------
+ $246.00
+(1 row)
+
+SELECT m / 2::int8 FROM money_data;
+ ?column?
+----------
+ $61.50
+(1 row)
+
+SELECT m * 2::float8 FROM money_data;
+ ?column?
+----------
+ $246.00
+(1 row)
+
+SELECT 2::float8 * m FROM money_data;
+ ?column?
+----------
+ $246.00
+(1 row)
+
+SELECT m / 2::float8 FROM money_data;
+ ?column?
+----------
+ $61.50
+(1 row)
+
+SELECT m * 2::float4 FROM money_data;
+ ?column?
+----------
+ $246.00
+(1 row)
+
+SELECT 2::float4 * m FROM money_data;
+ ?column?
+----------
+ $246.00
+(1 row)
+
+SELECT m / 2::float4 FROM money_data;
+ ?column?
+----------
+ $61.50
+(1 row)
+
+-- All true
+SELECT m = '$123.00' FROM money_data;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT m != '$124.00' FROM money_data;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT m <= '$123.00' FROM money_data;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT m >= '$123.00' FROM money_data;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT m < '$124.00' FROM money_data;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT m > '$122.00' FROM money_data;
+ ?column?
+----------
+ t
+(1 row)
+
+-- All false
+SELECT m = '$123.01' FROM money_data;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT m != '$123.00' FROM money_data;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT m <= '$122.99' FROM money_data;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT m >= '$123.01' FROM money_data;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT m > '$124.00' FROM money_data;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT m < '$122.00' FROM money_data;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT cashlarger(m, '$124.00') FROM money_data;
+ cashlarger
+------------
+ $124.00
+(1 row)
+
+SELECT cashsmaller(m, '$124.00') FROM money_data;
+ cashsmaller
+-------------
+ $123.00
+(1 row)
+
+SELECT cash_words(m) FROM money_data;
+ cash_words
+-------------------------------------------------
+ One hundred twenty three dollars and zero cents
+(1 row)
+
+SELECT cash_words(m + '1.23') FROM money_data;
+ cash_words
+--------------------------------------------------------
+ One hundred twenty four dollars and twenty three cents
+(1 row)
+
+DELETE FROM money_data;
+INSERT INTO money_data VALUES ('$123.45');
+SELECT * FROM money_data;
+ m
+---------
+ $123.45
+(1 row)
+
+DELETE FROM money_data;
+INSERT INTO money_data VALUES ('$123.451');
+SELECT * FROM money_data;
+ m
+---------
+ $123.45
+(1 row)
+
+DELETE FROM money_data;
+INSERT INTO money_data VALUES ('$123.454');
+SELECT * FROM money_data;
+ m
+---------
+ $123.45
+(1 row)
+
+DELETE FROM money_data;
+INSERT INTO money_data VALUES ('$123.455');
+SELECT * FROM money_data;
+ m
+---------
+ $123.46
+(1 row)
+
+DELETE FROM money_data;
+INSERT INTO money_data VALUES ('$123.456');
+SELECT * FROM money_data;
+ m
+---------
+ $123.46
+(1 row)
+
+DELETE FROM money_data;
+INSERT INTO money_data VALUES ('$123.459');
+SELECT * FROM money_data;
+ m
+---------
+ $123.46
+(1 row)
+
+-- input checks
+SELECT '1234567890'::money;
+ money
+-------------------
+ $1,234,567,890.00
+(1 row)
+
+SELECT '12345678901234567'::money;
+ money
+----------------------------
+ $12,345,678,901,234,567.00
+(1 row)
+
+SELECT '123456789012345678'::money;
+ERROR: value "123456789012345678" is out of range for type money
+LINE 1: SELECT '123456789012345678'::money;
+ ^
+SELECT '9223372036854775807'::money;
+ERROR: value "9223372036854775807" is out of range for type money
+LINE 1: SELECT '9223372036854775807'::money;
+ ^
+SELECT '-12345'::money;
+ money
+-------------
+ -$12,345.00
+(1 row)
+
+SELECT '-1234567890'::money;
+ money
+--------------------
+ -$1,234,567,890.00
+(1 row)
+
+SELECT '-12345678901234567'::money;
+ money
+-----------------------------
+ -$12,345,678,901,234,567.00
+(1 row)
+
+SELECT '-123456789012345678'::money;
+ERROR: value "-123456789012345678" is out of range for type money
+LINE 1: SELECT '-123456789012345678'::money;
+ ^
+SELECT '-9223372036854775808'::money;
+ERROR: value "-9223372036854775808" is out of range for type money
+LINE 1: SELECT '-9223372036854775808'::money;
+ ^
+-- special characters
+SELECT '(1)'::money;
+ money
+--------
+ -$1.00
+(1 row)
+
+SELECT '($123,456.78)'::money;
+ money
+--------------
+ -$123,456.78
+(1 row)
+
+-- documented minimums and maximums
+SELECT '-92233720368547758.08'::money;
+ money
+-----------------------------
+ -$92,233,720,368,547,758.08
+(1 row)
+
+SELECT '92233720368547758.07'::money;
+ money
+----------------------------
+ $92,233,720,368,547,758.07
+(1 row)
+
+SELECT '-92233720368547758.09'::money;
+ERROR: value "-92233720368547758.09" is out of range for type money
+LINE 1: SELECT '-92233720368547758.09'::money;
+ ^
+SELECT '92233720368547758.08'::money;
+ERROR: value "92233720368547758.08" is out of range for type money
+LINE 1: SELECT '92233720368547758.08'::money;
+ ^
+-- rounding
+SELECT '-92233720368547758.085'::money;
+ERROR: value "-92233720368547758.085" is out of range for type money
+LINE 1: SELECT '-92233720368547758.085'::money;
+ ^
+SELECT '92233720368547758.075'::money;
+ERROR: value "92233720368547758.075" is out of range for type money
+LINE 1: SELECT '92233720368547758.075'::money;
+ ^
+-- rounding vs. truncation in division
+SELECT '878.08'::money / 11::float8;
+ ?column?
+----------
+ $79.83
+(1 row)
+
+SELECT '878.08'::money / 11::float4;
+ ?column?
+----------
+ $79.83
+(1 row)
+
+SELECT '878.08'::money / 11::bigint;
+ ?column?
+----------
+ $79.82
+(1 row)
+
+SELECT '878.08'::money / 11::int;
+ ?column?
+----------
+ $79.82
+(1 row)
+
+SELECT '878.08'::money / 11::smallint;
+ ?column?
+----------
+ $79.82
+(1 row)
+
+-- check for precision loss in division
+SELECT '90000000000000099.00'::money / 10::bigint;
+ ?column?
+---------------------------
+ $9,000,000,000,000,009.90
+(1 row)
+
+SELECT '90000000000000099.00'::money / 10::int;
+ ?column?
+---------------------------
+ $9,000,000,000,000,009.90
+(1 row)
+
+SELECT '90000000000000099.00'::money / 10::smallint;
+ ?column?
+---------------------------
+ $9,000,000,000,000,009.90
+(1 row)
+
+-- Cast int4/int8/numeric to money
+SELECT 1234567890::money;
+ money
+-------------------
+ $1,234,567,890.00
+(1 row)
+
+SELECT 12345678901234567::money;
+ money
+----------------------------
+ $12,345,678,901,234,567.00
+(1 row)
+
+SELECT (-12345)::money;
+ money
+-------------
+ -$12,345.00
+(1 row)
+
+SELECT (-1234567890)::money;
+ money
+--------------------
+ -$1,234,567,890.00
+(1 row)
+
+SELECT (-12345678901234567)::money;
+ money
+-----------------------------
+ -$12,345,678,901,234,567.00
+(1 row)
+
+SELECT 1234567890::int4::money;
+ money
+-------------------
+ $1,234,567,890.00
+(1 row)
+
+SELECT 12345678901234567::int8::money;
+ money
+----------------------------
+ $12,345,678,901,234,567.00
+(1 row)
+
+SELECT 12345678901234567::numeric::money;
+ money
+----------------------------
+ $12,345,678,901,234,567.00
+(1 row)
+
+SELECT (-1234567890)::int4::money;
+ money
+--------------------
+ -$1,234,567,890.00
+(1 row)
+
+SELECT (-12345678901234567)::int8::money;
+ money
+-----------------------------
+ -$12,345,678,901,234,567.00
+(1 row)
+
+SELECT (-12345678901234567)::numeric::money;
+ money
+-----------------------------
+ -$12,345,678,901,234,567.00
+(1 row)
+
+-- Cast from money to numeric
+SELECT '12345678901234567'::money::numeric;
+ numeric
+----------------------
+ 12345678901234567.00
+(1 row)
+
+SELECT '-12345678901234567'::money::numeric;
+ numeric
+-----------------------
+ -12345678901234567.00
+(1 row)
+
+SELECT '92233720368547758.07'::money::numeric;
+ numeric
+----------------------
+ 92233720368547758.07
+(1 row)
+
+SELECT '-92233720368547758.08'::money::numeric;
+ numeric
+-----------------------
+ -92233720368547758.08
+(1 row)
+
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
new file mode 100644
index 0000000..bde3f24
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,3232 @@
+-- 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);
+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);
+-- 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)
+
+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)
+
+-- test with just one input:
+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 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);
+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);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+ f8mr | i
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- 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/mvcc.out b/src/test/regress/expected/mvcc.out
new file mode 100644
index 0000000..16ed4dd
--- /dev/null
+++ b/src/test/regress/expected/mvcc.out
@@ -0,0 +1,42 @@
+--
+-- Verify that index scans encountering dead rows produced by an
+-- aborted subtransaction of the current transaction can utilize the
+-- kill_prio_tuple optimization
+--
+-- NB: The table size is currently *not* expected to stay the same, we
+-- don't have logic to trigger opportunistic pruning in cases like
+-- this.
+BEGIN;
+SET LOCAL enable_seqscan = false;
+SET LOCAL enable_indexonlyscan = false;
+SET LOCAL enable_bitmapscan = false;
+-- Can't easily use a unique index, since dead tuples can be found
+-- independent of the kill_prior_tuples optimization.
+CREATE TABLE clean_aborted_self(key int, data text);
+CREATE INDEX clean_aborted_self_key ON clean_aborted_self(key);
+INSERT INTO clean_aborted_self (key, data) VALUES (-1, 'just to allocate metapage');
+-- save index size from before the changes, for comparison
+SELECT pg_relation_size('clean_aborted_self_key') AS clean_aborted_self_key_before \gset
+DO $$
+BEGIN
+ -- iterate often enough to see index growth even on larger-than-default page sizes
+ FOR i IN 1..100 LOOP
+ BEGIN
+ -- perform index scan over all the inserted keys to get them to be seen as dead
+ IF EXISTS(SELECT * FROM clean_aborted_self WHERE key > 0 AND key < 100) THEN
+ RAISE data_corrupted USING MESSAGE = 'these rows should not exist';
+ END IF;
+ INSERT INTO clean_aborted_self SELECT g.i, 'rolling back in a sec' FROM generate_series(1, 100) g(i);
+ -- just some error that's not normally thrown
+ RAISE reading_sql_data_not_permitted USING MESSAGE = 'round and round again';
+ EXCEPTION WHEN reading_sql_data_not_permitted THEN END;
+ END LOOP;
+END;$$;
+-- show sizes only if they differ
+SELECT :clean_aborted_self_key_before AS size_before, pg_relation_size('clean_aborted_self_key') size_after
+WHERE :clean_aborted_self_key_before != pg_relation_size('clean_aborted_self_key');
+ size_before | size_after
+-------------+------------
+(0 rows)
+
+ROLLBACK;
diff --git a/src/test/regress/expected/name.out b/src/test/regress/expected/name.out
new file mode 100644
index 0000000..d58df2b
--- /dev/null
+++ b/src/test/regress/expected/name.out
@@ -0,0 +1,197 @@
+--
+-- NAME
+-- all inputs are silently truncated at NAMEDATALEN-1 (63) characters
+--
+-- fixed-length by reference
+SELECT name 'name string' = name 'name string' AS "True";
+ True
+------
+ t
+(1 row)
+
+SELECT name 'name string' = name 'name string ' AS "False";
+ False
+-------
+ f
+(1 row)
+
+--
+--
+--
+CREATE TABLE NAME_TBL(f1 name);
+INSERT INTO NAME_TBL(f1) VALUES ('1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR');
+INSERT INTO NAME_TBL(f1) VALUES ('1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqr');
+INSERT INTO NAME_TBL(f1) VALUES ('asdfghjkl;');
+INSERT INTO NAME_TBL(f1) VALUES ('343f%2a');
+INSERT INTO NAME_TBL(f1) VALUES ('d34aaasdf');
+INSERT INTO NAME_TBL(f1) VALUES ('');
+INSERT INTO NAME_TBL(f1) VALUES ('1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ');
+SELECT * FROM NAME_TBL;
+ f1
+-----------------------------------------------------------------
+ 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ
+ 1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopq
+ asdfghjkl;
+ 343f%2a
+ d34aaasdf
+
+ 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ
+(7 rows)
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 <> '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR';
+ f1
+-----------------------------------------------------------------
+ 1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopq
+ asdfghjkl;
+ 343f%2a
+ d34aaasdf
+
+(5 rows)
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR';
+ f1
+-----------------------------------------------------------------
+ 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ
+ 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ
+(2 rows)
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 < '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR';
+ f1
+----
+
+(1 row)
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 <= '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR';
+ f1
+-----------------------------------------------------------------
+ 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ
+
+ 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ
+(3 rows)
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 > '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR';
+ f1
+-----------------------------------------------------------------
+ 1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopq
+ asdfghjkl;
+ 343f%2a
+ d34aaasdf
+(4 rows)
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 >= '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR';
+ f1
+-----------------------------------------------------------------
+ 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ
+ 1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopq
+ asdfghjkl;
+ 343f%2a
+ d34aaasdf
+ 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ
+(6 rows)
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 ~ '.*';
+ f1
+-----------------------------------------------------------------
+ 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ
+ 1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopq
+ asdfghjkl;
+ 343f%2a
+ d34aaasdf
+
+ 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ
+(7 rows)
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 !~ '.*';
+ f1
+----
+(0 rows)
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 ~ '[0-9]';
+ f1
+-----------------------------------------------------------------
+ 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ
+ 1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopq
+ 343f%2a
+ d34aaasdf
+ 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ
+(5 rows)
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 ~ '.*asdf.*';
+ f1
+------------
+ asdfghjkl;
+ d34aaasdf
+(2 rows)
+
+DROP TABLE NAME_TBL;
+DO $$
+DECLARE r text[];
+BEGIN
+ r := parse_ident('Schemax.Tabley');
+ RAISE NOTICE '%', format('%I.%I', r[1], r[2]);
+ r := parse_ident('"SchemaX"."TableY"');
+ RAISE NOTICE '%', format('%I.%I', r[1], r[2]);
+END;
+$$;
+NOTICE: schemax.tabley
+NOTICE: "SchemaX"."TableY"
+SELECT parse_ident('foo.boo');
+ parse_ident
+-------------
+ {foo,boo}
+(1 row)
+
+SELECT parse_ident('foo.boo[]'); -- should fail
+ERROR: string is not a valid identifier: "foo.boo[]"
+SELECT parse_ident('foo.boo[]', strict => false); -- ok
+ parse_ident
+-------------
+ {foo,boo}
+(1 row)
+
+-- should fail
+SELECT parse_ident(' ');
+ERROR: string is not a valid identifier: " "
+SELECT parse_ident(' .aaa');
+ERROR: string is not a valid identifier: " .aaa"
+DETAIL: No valid identifier before ".".
+SELECT parse_ident(' aaa . ');
+ERROR: string is not a valid identifier: " aaa . "
+DETAIL: No valid identifier after ".".
+SELECT parse_ident('aaa.a%b');
+ERROR: string is not a valid identifier: "aaa.a%b"
+SELECT parse_ident(E'X\rXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
+ERROR: string is not a valid identifier: "X XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+SELECT length(a[1]), length(a[2]) from parse_ident('"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy') as a ;
+ length | length
+--------+--------
+ 414 | 289
+(1 row)
+
+SELECT parse_ident(' first . " second " ." third ". " ' || repeat('x',66) || '"');
+ parse_ident
+-----------------------------------------------------------------------------------------------------------
+ {first," second "," third "," xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
+(1 row)
+
+SELECT parse_ident(' first . " second " ." third ". " ' || repeat('x',66) || '"')::name[];
+ parse_ident
+------------------------------------------------------------------------------------------------------
+ {first," second "," third "," xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
+(1 row)
+
+SELECT parse_ident(E'"c".X XXXX\002XXXXXX');
+ERROR: string is not a valid identifier: ""c".X XXXXXXXXXX"
+SELECT parse_ident('1020');
+ERROR: string is not a valid identifier: "1020"
+SELECT parse_ident('10.20');
+ERROR: string is not a valid identifier: "10.20"
+SELECT parse_ident('.');
+ERROR: string is not a valid identifier: "."
+DETAIL: No valid identifier before ".".
+SELECT parse_ident('.1020');
+ERROR: string is not a valid identifier: ".1020"
+DETAIL: No valid identifier before ".".
+SELECT parse_ident('xxx.1020');
+ERROR: string is not a valid identifier: "xxx.1020"
+DETAIL: No valid identifier after ".".
diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out
new file mode 100644
index 0000000..2564d1b
--- /dev/null
+++ b/src/test/regress/expected/namespace.out
@@ -0,0 +1,71 @@
+--
+-- Regression tests for schemas (namespaces)
+--
+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
+ );
+-- 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/numeric.out b/src/test/regress/expected/numeric.out
new file mode 100644
index 0000000..bea3318
--- /dev/null
+++ b/src/test/regress/expected/numeric.out
@@ -0,0 +1,3226 @@
+--
+-- 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;
+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;
+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;
+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;
+BEGIN TRANSACTION;
+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;
+BEGIN TRANSACTION;
+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;
+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;
+ 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');
+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
+ 9 | NaN
+(7 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
+ 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)
+
+SELECT to_char(val, 'L9999999999999999.099999999999999') 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, '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 ');
+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 (' ');
+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.50
+ -555.50
+ NaN
+ NaN
+ Infinity
+ Infinity
+ -Infinity
+ Infinity
+ Infinity
+ -Infinity
+(13 rows)
+
+--
+-- 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 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/numeric_big.out b/src/test/regress/expected/numeric_big.out
new file mode 100644
index 0000000..468c602
--- /dev/null
+++ b/src/test/regress/expected/numeric_big.out
@@ -0,0 +1,2082 @@
+-- ******************************
+-- * 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;
+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;
+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;
+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;
+BEGIN TRANSACTION;
+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;
+BEGIN TRANSACTION;
+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;
+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;
+ 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/numerology.out b/src/test/regress/expected/numerology.out
new file mode 100644
index 0000000..44d6c43
--- /dev/null
+++ b/src/test/regress/expected/numerology.out
@@ -0,0 +1,136 @@
+--
+-- NUMEROLOGY
+-- Test various combinations of numeric types and functions.
+--
+--
+-- Test implicit type conversions
+-- This fails for Postgres v6.1 (and earlier?)
+-- so let's try explicit conversions for now - tgl 97/05/07
+--
+CREATE TABLE TEMP_FLOAT (f1 FLOAT8);
+INSERT INTO TEMP_FLOAT (f1)
+ SELECT float8(f1) FROM INT4_TBL;
+INSERT INTO TEMP_FLOAT (f1)
+ SELECT float8(f1) FROM INT2_TBL;
+SELECT f1 FROM TEMP_FLOAT
+ ORDER BY f1;
+ f1
+-------------
+ -2147483647
+ -123456
+ -32767
+ -1234
+ 0
+ 0
+ 1234
+ 32767
+ 123456
+ 2147483647
+(10 rows)
+
+-- int4
+CREATE TABLE TEMP_INT4 (f1 INT4);
+INSERT INTO TEMP_INT4 (f1)
+ SELECT int4(f1) FROM FLOAT8_TBL
+ WHERE (f1 > -2147483647) AND (f1 < 2147483647);
+INSERT INTO TEMP_INT4 (f1)
+ SELECT int4(f1) FROM INT2_TBL;
+SELECT f1 FROM TEMP_INT4
+ ORDER BY f1;
+ f1
+--------
+ -32767
+ -1234
+ -1004
+ -35
+ 0
+ 0
+ 0
+ 1234
+ 32767
+(9 rows)
+
+-- int2
+CREATE TABLE TEMP_INT2 (f1 INT2);
+INSERT INTO TEMP_INT2 (f1)
+ SELECT int2(f1) FROM FLOAT8_TBL
+ WHERE (f1 >= -32767) AND (f1 <= 32767);
+INSERT INTO TEMP_INT2 (f1)
+ SELECT int2(f1) FROM INT4_TBL
+ WHERE (f1 >= -32767) AND (f1 <= 32767);
+SELECT f1 FROM TEMP_INT2
+ ORDER BY f1;
+ f1
+-------
+ -1004
+ -35
+ 0
+ 0
+ 0
+(5 rows)
+
+--
+-- Group-by combinations
+--
+CREATE TABLE TEMP_GROUP (f1 INT4, f2 INT4, f3 FLOAT8);
+INSERT INTO TEMP_GROUP
+ SELECT 1, (- i.f1), (- f.f1)
+ FROM INT4_TBL i, FLOAT8_TBL f;
+INSERT INTO TEMP_GROUP
+ SELECT 2, i.f1, f.f1
+ FROM INT4_TBL i, FLOAT8_TBL f;
+SELECT DISTINCT f1 AS two FROM TEMP_GROUP ORDER BY 1;
+ two
+-----
+ 1
+ 2
+(2 rows)
+
+SELECT f1 AS two, max(f3) AS max_float, min(f3) as min_float
+ FROM TEMP_GROUP
+ GROUP BY f1
+ ORDER BY two, max_float, min_float;
+ two | max_float | min_float
+-----+----------------------+-----------------------
+ 1 | 1.2345678901234e+200 | -0
+ 2 | 0 | -1.2345678901234e+200
+(2 rows)
+
+-- GROUP BY a result column name is not legal per SQL92, but we accept it
+-- anyway (if the name is not the name of any column exposed by FROM).
+SELECT f1 AS two, max(f3) AS max_float, min(f3) AS min_float
+ FROM TEMP_GROUP
+ GROUP BY two
+ ORDER BY two, max_float, min_float;
+ two | max_float | min_float
+-----+----------------------+-----------------------
+ 1 | 1.2345678901234e+200 | -0
+ 2 | 0 | -1.2345678901234e+200
+(2 rows)
+
+SELECT f1 AS two, (max(f3) + 1) AS max_plus_1, (min(f3) - 1) AS min_minus_1
+ FROM TEMP_GROUP
+ GROUP BY f1
+ ORDER BY two, min_minus_1;
+ two | max_plus_1 | min_minus_1
+-----+----------------------+-----------------------
+ 1 | 1.2345678901234e+200 | -1
+ 2 | 1 | -1.2345678901234e+200
+(2 rows)
+
+SELECT f1 AS two,
+ max(f2) + min(f2) AS max_plus_min,
+ min(f3) - 1 AS min_minus_1
+ FROM TEMP_GROUP
+ GROUP BY f1
+ ORDER BY two, min_minus_1;
+ two | max_plus_min | min_minus_1
+-----+--------------+-----------------------
+ 1 | 0 | -1
+ 2 | 0 | -1.2345678901234e+200
+(2 rows)
+
+DROP TABLE TEMP_INT2;
+DROP TABLE TEMP_INT4;
+DROP TABLE TEMP_FLOAT;
+DROP TABLE TEMP_GROUP;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
new file mode 100644
index 0000000..388097a
--- /dev/null
+++ b/src/test/regress/expected/object_address.out
@@ -0,0 +1,623 @@
+--
+-- Test for pg_get_object_address
+--
+-- Clean up in case a prior regression run failed
+SET client_min_messages TO 'warning';
+DROP ROLE IF EXISTS regress_addr_user;
+RESET client_min_messages;
+CREATE USER regress_addr_user;
+-- Test generic object addressing/identification functions
+CREATE SCHEMA addr_nsp;
+SET search_path TO 'addr_nsp';
+CREATE FOREIGN DATA WRAPPER addr_fdw;
+CREATE SERVER addr_fserv FOREIGN DATA WRAPPER addr_fdw;
+CREATE TEXT SEARCH DICTIONARY addr_ts_dict (template=simple);
+CREATE TEXT SEARCH CONFIGURATION addr_ts_conf (copy=english);
+CREATE TEXT SEARCH TEMPLATE addr_ts_temp (lexize=dsimple_lexize);
+CREATE TEXT SEARCH PARSER addr_ts_prs
+ (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype);
+CREATE TABLE addr_nsp.gentable (
+ a serial primary key CONSTRAINT a_chk CHECK (a > 0),
+ b text DEFAULT 'hello');
+CREATE TABLE addr_nsp.parttable (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE VIEW addr_nsp.genview AS SELECT * from addr_nsp.gentable;
+CREATE MATERIALIZED VIEW addr_nsp.genmatview AS SELECT * FROM addr_nsp.gentable;
+CREATE TYPE addr_nsp.gencomptype AS (a int);
+CREATE TYPE addr_nsp.genenum AS ENUM ('one', 'two');
+CREATE FOREIGN TABLE addr_nsp.genftable (a int) SERVER addr_fserv;
+CREATE AGGREGATE addr_nsp.genaggr(int4) (sfunc = int4pl, stype = int4);
+CREATE DOMAIN addr_nsp.gendomain AS int4 CONSTRAINT domconstr CHECK (value > 0);
+CREATE FUNCTION addr_nsp.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END; $$;
+CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDURE addr_nsp.trig();
+CREATE POLICY genpol ON addr_nsp.gentable;
+CREATE PROCEDURE addr_nsp.proc(int4) LANGUAGE SQL AS $$ $$;
+CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw;
+CREATE USER MAPPING FOR regress_addr_user SERVER "integer";
+ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user;
+ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM regress_addr_user;
+-- this transform would be quite unsafe to leave lying around,
+-- except that the SQL language pays no attention to transforms:
+CREATE TRANSFORM FOR int LANGUAGE SQL (
+ FROM SQL WITH FUNCTION prsd_lextype(internal),
+ TO SQL WITH FUNCTION int4recv(internal));
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+RESET client_min_messages;
+CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
+WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
+CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
+-- test some error cases
+SELECT pg_get_object_address('stone', '{}', '{}');
+ERROR: unrecognized object type "stone"
+SELECT pg_get_object_address('table', '{}', '{}');
+ERROR: name list length must be at least 1
+SELECT pg_get_object_address('table', '{NULL}', '{}');
+ERROR: name or argument lists may not contain nulls
+-- unrecognized object types
+DO $$
+DECLARE
+ objtype text;
+BEGIN
+ FOR objtype IN VALUES ('toast table'), ('index column'), ('sequence column'),
+ ('toast table column'), ('view column'), ('materialized view column')
+ LOOP
+ BEGIN
+ PERFORM pg_get_object_address(objtype, '{one}', '{}');
+ EXCEPTION WHEN invalid_parameter_value THEN
+ RAISE WARNING 'error for %: %', objtype, sqlerrm;
+ END;
+ END LOOP;
+END;
+$$;
+WARNING: error for toast table: unsupported object type "toast table"
+WARNING: error for index column: unsupported object type "index column"
+WARNING: error for sequence column: unsupported object type "sequence column"
+WARNING: error for toast table column: unsupported object type "toast table column"
+WARNING: error for view column: unsupported object type "view column"
+WARNING: error for materialized view column: unsupported object type "materialized view column"
+-- miscellaneous other errors
+select * from pg_get_object_address('operator of access method', '{btree,integer_ops,1}', '{int4,bool}');
+ERROR: operator 1 (int4, bool) of operator family integer_ops for access method btree does not exist
+select * from pg_get_object_address('operator of access method', '{btree,integer_ops,99}', '{int4,int4}');
+ERROR: operator 99 (int4, int4) of operator family integer_ops for access method btree does not exist
+select * from pg_get_object_address('function of access method', '{btree,integer_ops,1}', '{int4,bool}');
+ERROR: function 1 (int4, bool) of operator family integer_ops for access method btree does not exist
+select * from pg_get_object_address('function of access method', '{btree,integer_ops,99}', '{int4,int4}');
+ERROR: function 99 (int4, int4) of operator family integer_ops for access method btree does not exist
+DO $$
+DECLARE
+ objtype text;
+ names text[];
+ args text[];
+BEGIN
+ FOR objtype IN VALUES
+ ('table'), ('index'), ('sequence'), ('view'),
+ ('materialized view'), ('foreign table'),
+ ('table column'), ('foreign table column'),
+ ('aggregate'), ('function'), ('procedure'), ('type'), ('cast'),
+ ('table constraint'), ('domain constraint'), ('conversion'), ('default value'),
+ ('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'),
+ ('text search parser'), ('text search dictionary'),
+ ('text search template'), ('text search configuration'),
+ ('policy'), ('user mapping'), ('default acl'), ('transform'),
+ ('operator of access method'), ('function of access method'),
+ ('publication relation')
+ LOOP
+ FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}')
+ LOOP
+ FOR args IN VALUES ('{}'), ('{integer}')
+ LOOP
+ BEGIN
+ PERFORM pg_get_object_address(objtype, names, args);
+ EXCEPTION WHEN OTHERS THEN
+ RAISE WARNING 'error for %,%,%: %', objtype, names, args, sqlerrm;
+ END;
+ END LOOP;
+ END LOOP;
+ END LOOP;
+END;
+$$;
+WARNING: error for table,{eins},{}: relation "eins" does not exist
+WARNING: error for table,{eins},{integer}: relation "eins" does not exist
+WARNING: error for table,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist
+WARNING: error for table,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
+WARNING: error for table,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING: error for table,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING: error for index,{eins},{}: relation "eins" does not exist
+WARNING: error for index,{eins},{integer}: relation "eins" does not exist
+WARNING: error for index,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist
+WARNING: error for index,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
+WARNING: error for index,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING: error for index,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING: error for sequence,{eins},{}: relation "eins" does not exist
+WARNING: error for sequence,{eins},{integer}: relation "eins" does not exist
+WARNING: error for sequence,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist
+WARNING: error for sequence,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
+WARNING: error for sequence,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING: error for sequence,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING: error for view,{eins},{}: relation "eins" does not exist
+WARNING: error for view,{eins},{integer}: relation "eins" does not exist
+WARNING: error for view,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist
+WARNING: error for view,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
+WARNING: error for view,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING: error for view,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING: error for materialized view,{eins},{}: relation "eins" does not exist
+WARNING: error for materialized view,{eins},{integer}: relation "eins" does not exist
+WARNING: error for materialized view,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist
+WARNING: error for materialized view,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
+WARNING: error for materialized view,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING: error for materialized view,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING: error for foreign table,{eins},{}: relation "eins" does not exist
+WARNING: error for foreign table,{eins},{integer}: relation "eins" does not exist
+WARNING: error for foreign table,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist
+WARNING: error for foreign table,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
+WARNING: error for foreign table,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING: error for foreign table,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING: error for table column,{eins},{}: column name must be qualified
+WARNING: error for table column,{eins},{integer}: column name must be qualified
+WARNING: error for table column,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist
+WARNING: error for table column,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist
+WARNING: error for table column,{eins,zwei,drei},{}: schema "eins" does not exist
+WARNING: error for table column,{eins,zwei,drei},{integer}: schema "eins" does not exist
+WARNING: error for foreign table column,{eins},{}: column name must be qualified
+WARNING: error for foreign table column,{eins},{integer}: column name must be qualified
+WARNING: error for foreign table column,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist
+WARNING: error for foreign table column,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist
+WARNING: error for foreign table column,{eins,zwei,drei},{}: schema "eins" does not exist
+WARNING: error for foreign table column,{eins,zwei,drei},{integer}: schema "eins" does not exist
+WARNING: error for aggregate,{eins},{}: aggregate eins(*) does not exist
+WARNING: error for aggregate,{eins},{integer}: aggregate eins(integer) does not exist
+WARNING: error for aggregate,{addr_nsp,zwei},{}: aggregate addr_nsp.zwei(*) does not exist
+WARNING: error for aggregate,{addr_nsp,zwei},{integer}: aggregate addr_nsp.zwei(integer) does not exist
+WARNING: error for aggregate,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for aggregate,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for function,{eins},{}: function eins() does not exist
+WARNING: error for function,{eins},{integer}: function eins(integer) does not exist
+WARNING: error for function,{addr_nsp,zwei},{}: function addr_nsp.zwei() does not exist
+WARNING: error for function,{addr_nsp,zwei},{integer}: function addr_nsp.zwei(integer) does not exist
+WARNING: error for function,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for function,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for procedure,{eins},{}: procedure eins() does not exist
+WARNING: error for procedure,{eins},{integer}: procedure eins(integer) does not exist
+WARNING: error for procedure,{addr_nsp,zwei},{}: procedure addr_nsp.zwei() does not exist
+WARNING: error for procedure,{addr_nsp,zwei},{integer}: procedure addr_nsp.zwei(integer) does not exist
+WARNING: error for procedure,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for procedure,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for type,{eins},{}: type "eins" does not exist
+WARNING: error for type,{eins},{integer}: type "eins" does not exist
+WARNING: error for type,{addr_nsp,zwei},{}: name list length must be exactly 1
+WARNING: error for type,{addr_nsp,zwei},{integer}: name list length must be exactly 1
+WARNING: error for type,{eins,zwei,drei},{}: name list length must be exactly 1
+WARNING: error for type,{eins,zwei,drei},{integer}: name list length must be exactly 1
+WARNING: error for cast,{eins},{}: argument list length must be exactly 1
+WARNING: error for cast,{eins},{integer}: type "eins" does not exist
+WARNING: error for cast,{addr_nsp,zwei},{}: name list length must be exactly 1
+WARNING: error for cast,{addr_nsp,zwei},{integer}: name list length must be exactly 1
+WARNING: error for cast,{eins,zwei,drei},{}: name list length must be exactly 1
+WARNING: error for cast,{eins,zwei,drei},{integer}: name list length must be exactly 1
+WARNING: error for table constraint,{eins},{}: must specify relation and object name
+WARNING: error for table constraint,{eins},{integer}: must specify relation and object name
+WARNING: error for table constraint,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist
+WARNING: error for table constraint,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist
+WARNING: error for table constraint,{eins,zwei,drei},{}: schema "eins" does not exist
+WARNING: error for table constraint,{eins,zwei,drei},{integer}: schema "eins" does not exist
+WARNING: error for domain constraint,{eins},{}: argument list length must be exactly 1
+WARNING: error for domain constraint,{eins},{integer}: type "eins" does not exist
+WARNING: error for domain constraint,{addr_nsp,zwei},{}: name list length must be exactly 1
+WARNING: error for domain constraint,{addr_nsp,zwei},{integer}: name list length must be exactly 1
+WARNING: error for domain constraint,{eins,zwei,drei},{}: name list length must be exactly 1
+WARNING: error for domain constraint,{eins,zwei,drei},{integer}: name list length must be exactly 1
+WARNING: error for conversion,{eins},{}: conversion "eins" does not exist
+WARNING: error for conversion,{eins},{integer}: conversion "eins" does not exist
+WARNING: error for conversion,{addr_nsp,zwei},{}: conversion "addr_nsp.zwei" does not exist
+WARNING: error for conversion,{addr_nsp,zwei},{integer}: conversion "addr_nsp.zwei" does not exist
+WARNING: error for conversion,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for conversion,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for default value,{eins},{}: column name must be qualified
+WARNING: error for default value,{eins},{integer}: column name must be qualified
+WARNING: error for default value,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist
+WARNING: error for default value,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist
+WARNING: error for default value,{eins,zwei,drei},{}: schema "eins" does not exist
+WARNING: error for default value,{eins,zwei,drei},{integer}: schema "eins" does not exist
+WARNING: error for operator,{eins},{}: argument list length must be exactly 2
+WARNING: error for operator,{eins},{integer}: argument list length must be exactly 2
+WARNING: error for operator,{addr_nsp,zwei},{}: argument list length must be exactly 2
+WARNING: error for operator,{addr_nsp,zwei},{integer}: argument list length must be exactly 2
+WARNING: error for operator,{eins,zwei,drei},{}: argument list length must be exactly 2
+WARNING: error for operator,{eins,zwei,drei},{integer}: argument list length must be exactly 2
+WARNING: error for operator class,{eins},{}: name list length must be at least 2
+WARNING: error for operator class,{eins},{integer}: name list length must be at least 2
+WARNING: error for operator class,{addr_nsp,zwei},{}: access method "addr_nsp" does not exist
+WARNING: error for operator class,{addr_nsp,zwei},{integer}: access method "addr_nsp" does not exist
+WARNING: error for operator class,{eins,zwei,drei},{}: access method "eins" does not exist
+WARNING: error for operator class,{eins,zwei,drei},{integer}: access method "eins" does not exist
+WARNING: error for operator family,{eins},{}: name list length must be at least 2
+WARNING: error for operator family,{eins},{integer}: name list length must be at least 2
+WARNING: error for operator family,{addr_nsp,zwei},{}: access method "addr_nsp" does not exist
+WARNING: error for operator family,{addr_nsp,zwei},{integer}: access method "addr_nsp" does not exist
+WARNING: error for operator family,{eins,zwei,drei},{}: access method "eins" does not exist
+WARNING: error for operator family,{eins,zwei,drei},{integer}: access method "eins" does not exist
+WARNING: error for rule,{eins},{}: must specify relation and object name
+WARNING: error for rule,{eins},{integer}: must specify relation and object name
+WARNING: error for rule,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist
+WARNING: error for rule,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist
+WARNING: error for rule,{eins,zwei,drei},{}: schema "eins" does not exist
+WARNING: error for rule,{eins,zwei,drei},{integer}: schema "eins" does not exist
+WARNING: error for trigger,{eins},{}: must specify relation and object name
+WARNING: error for trigger,{eins},{integer}: must specify relation and object name
+WARNING: error for trigger,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist
+WARNING: error for trigger,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist
+WARNING: error for trigger,{eins,zwei,drei},{}: schema "eins" does not exist
+WARNING: error for trigger,{eins,zwei,drei},{integer}: schema "eins" does not exist
+WARNING: error for text search parser,{eins},{}: text search parser "eins" does not exist
+WARNING: error for text search parser,{eins},{integer}: text search parser "eins" does not exist
+WARNING: error for text search parser,{addr_nsp,zwei},{}: text search parser "addr_nsp.zwei" does not exist
+WARNING: error for text search parser,{addr_nsp,zwei},{integer}: text search parser "addr_nsp.zwei" does not exist
+WARNING: error for text search parser,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for text search parser,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for text search dictionary,{eins},{}: text search dictionary "eins" does not exist
+WARNING: error for text search dictionary,{eins},{integer}: text search dictionary "eins" does not exist
+WARNING: error for text search dictionary,{addr_nsp,zwei},{}: text search dictionary "addr_nsp.zwei" does not exist
+WARNING: error for text search dictionary,{addr_nsp,zwei},{integer}: text search dictionary "addr_nsp.zwei" does not exist
+WARNING: error for text search dictionary,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for text search dictionary,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for text search template,{eins},{}: text search template "eins" does not exist
+WARNING: error for text search template,{eins},{integer}: text search template "eins" does not exist
+WARNING: error for text search template,{addr_nsp,zwei},{}: text search template "addr_nsp.zwei" does not exist
+WARNING: error for text search template,{addr_nsp,zwei},{integer}: text search template "addr_nsp.zwei" does not exist
+WARNING: error for text search template,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for text search template,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for text search configuration,{eins},{}: text search configuration "eins" does not exist
+WARNING: error for text search configuration,{eins},{integer}: text search configuration "eins" does not exist
+WARNING: error for text search configuration,{addr_nsp,zwei},{}: text search configuration "addr_nsp.zwei" does not exist
+WARNING: error for text search configuration,{addr_nsp,zwei},{integer}: text search configuration "addr_nsp.zwei" does not exist
+WARNING: error for text search configuration,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for text search configuration,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for policy,{eins},{}: must specify relation and object name
+WARNING: error for policy,{eins},{integer}: must specify relation and object name
+WARNING: error for policy,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist
+WARNING: error for policy,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist
+WARNING: error for policy,{eins,zwei,drei},{}: schema "eins" does not exist
+WARNING: error for policy,{eins,zwei,drei},{integer}: schema "eins" does not exist
+WARNING: error for user mapping,{eins},{}: argument list length must be exactly 1
+WARNING: error for user mapping,{eins},{integer}: user mapping for user "eins" on server "integer" does not exist
+WARNING: error for user mapping,{addr_nsp,zwei},{}: argument list length must be exactly 1
+WARNING: error for user mapping,{addr_nsp,zwei},{integer}: user mapping for user "addr_nsp" on server "integer" does not exist
+WARNING: error for user mapping,{eins,zwei,drei},{}: argument list length must be exactly 1
+WARNING: error for user mapping,{eins,zwei,drei},{integer}: user mapping for user "eins" on server "integer" does not exist
+WARNING: error for default acl,{eins},{}: argument list length must be exactly 1
+WARNING: error for default acl,{eins},{integer}: unrecognized default ACL object type "i"
+WARNING: error for default acl,{addr_nsp,zwei},{}: argument list length must be exactly 1
+WARNING: error for default acl,{addr_nsp,zwei},{integer}: unrecognized default ACL object type "i"
+WARNING: error for default acl,{eins,zwei,drei},{}: argument list length must be exactly 1
+WARNING: error for default acl,{eins,zwei,drei},{integer}: unrecognized default ACL object type "i"
+WARNING: error for transform,{eins},{}: argument list length must be exactly 1
+WARNING: error for transform,{eins},{integer}: type "eins" does not exist
+WARNING: error for transform,{addr_nsp,zwei},{}: name list length must be exactly 1
+WARNING: error for transform,{addr_nsp,zwei},{integer}: name list length must be exactly 1
+WARNING: error for transform,{eins,zwei,drei},{}: name list length must be exactly 1
+WARNING: error for transform,{eins,zwei,drei},{integer}: name list length must be exactly 1
+WARNING: error for operator of access method,{eins},{}: name list length must be at least 3
+WARNING: error for operator of access method,{eins},{integer}: name list length must be at least 3
+WARNING: error for operator of access method,{addr_nsp,zwei},{}: name list length must be at least 3
+WARNING: error for operator of access method,{addr_nsp,zwei},{integer}: name list length must be at least 3
+WARNING: error for operator of access method,{eins,zwei,drei},{}: argument list length must be exactly 2
+WARNING: error for operator of access method,{eins,zwei,drei},{integer}: argument list length must be exactly 2
+WARNING: error for function of access method,{eins},{}: name list length must be at least 3
+WARNING: error for function of access method,{eins},{integer}: name list length must be at least 3
+WARNING: error for function of access method,{addr_nsp,zwei},{}: name list length must be at least 3
+WARNING: error for function of access method,{addr_nsp,zwei},{integer}: name list length must be at least 3
+WARNING: error for function of access method,{eins,zwei,drei},{}: argument list length must be exactly 2
+WARNING: error for function of access method,{eins,zwei,drei},{integer}: argument list length must be exactly 2
+WARNING: error for publication relation,{eins},{}: argument list length must be exactly 1
+WARNING: error for publication relation,{eins},{integer}: relation "eins" does not exist
+WARNING: error for publication relation,{addr_nsp,zwei},{}: argument list length must be exactly 1
+WARNING: error for publication relation,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
+WARNING: error for publication relation,{eins,zwei,drei},{}: argument list length must be exactly 1
+WARNING: error for publication relation,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
+-- these object types cannot be qualified names
+SELECT pg_get_object_address('language', '{one}', '{}');
+ERROR: language "one" does not exist
+SELECT pg_get_object_address('language', '{one,two}', '{}');
+ERROR: name list length must be exactly 1
+SELECT pg_get_object_address('large object', '{123}', '{}');
+ERROR: large object 123 does not exist
+SELECT pg_get_object_address('large object', '{123,456}', '{}');
+ERROR: name list length must be exactly 1
+SELECT pg_get_object_address('large object', '{blargh}', '{}');
+ERROR: invalid input syntax for type oid: "blargh"
+SELECT pg_get_object_address('schema', '{one}', '{}');
+ERROR: schema "one" does not exist
+SELECT pg_get_object_address('schema', '{one,two}', '{}');
+ERROR: name list length must be exactly 1
+SELECT pg_get_object_address('role', '{one}', '{}');
+ERROR: role "one" does not exist
+SELECT pg_get_object_address('role', '{one,two}', '{}');
+ERROR: name list length must be exactly 1
+SELECT pg_get_object_address('database', '{one}', '{}');
+ERROR: database "one" does not exist
+SELECT pg_get_object_address('database', '{one,two}', '{}');
+ERROR: name list length must be exactly 1
+SELECT pg_get_object_address('tablespace', '{one}', '{}');
+ERROR: tablespace "one" does not exist
+SELECT pg_get_object_address('tablespace', '{one,two}', '{}');
+ERROR: name list length must be exactly 1
+SELECT pg_get_object_address('foreign-data wrapper', '{one}', '{}');
+ERROR: foreign-data wrapper "one" does not exist
+SELECT pg_get_object_address('foreign-data wrapper', '{one,two}', '{}');
+ERROR: name list length must be exactly 1
+SELECT pg_get_object_address('server', '{one}', '{}');
+ERROR: server "one" does not exist
+SELECT pg_get_object_address('server', '{one,two}', '{}');
+ERROR: name list length must be exactly 1
+SELECT pg_get_object_address('extension', '{one}', '{}');
+ERROR: extension "one" does not exist
+SELECT pg_get_object_address('extension', '{one,two}', '{}');
+ERROR: name list length must be exactly 1
+SELECT pg_get_object_address('event trigger', '{one}', '{}');
+ERROR: event trigger "one" does not exist
+SELECT pg_get_object_address('event trigger', '{one,two}', '{}');
+ERROR: name list length must be exactly 1
+SELECT pg_get_object_address('access method', '{one}', '{}');
+ERROR: access method "one" does not exist
+SELECT pg_get_object_address('access method', '{one,two}', '{}');
+ERROR: name list length must be exactly 1
+SELECT pg_get_object_address('publication', '{one}', '{}');
+ERROR: publication "one" does not exist
+SELECT pg_get_object_address('publication', '{one,two}', '{}');
+ERROR: name list length must be exactly 1
+SELECT pg_get_object_address('subscription', '{one}', '{}');
+ERROR: subscription "one" does not exist
+SELECT pg_get_object_address('subscription', '{one,two}', '{}');
+ERROR: name list length must be exactly 1
+-- test successful cases
+WITH objects (type, name, args) AS (VALUES
+ ('table', '{addr_nsp, gentable}'::text[], '{}'::text[]),
+ ('table', '{addr_nsp, parttable}'::text[], '{}'::text[]),
+ ('index', '{addr_nsp, gentable_pkey}', '{}'),
+ ('index', '{addr_nsp, parttable_pkey}', '{}'),
+ ('sequence', '{addr_nsp, gentable_a_seq}', '{}'),
+ -- toast table
+ ('view', '{addr_nsp, genview}', '{}'),
+ ('materialized view', '{addr_nsp, genmatview}', '{}'),
+ ('foreign table', '{addr_nsp, genftable}', '{}'),
+ ('table column', '{addr_nsp, gentable, b}', '{}'),
+ ('foreign table column', '{addr_nsp, genftable, a}', '{}'),
+ ('aggregate', '{addr_nsp, genaggr}', '{int4}'),
+ ('function', '{pg_catalog, pg_identify_object}', '{pg_catalog.oid, pg_catalog.oid, int4}'),
+ ('procedure', '{addr_nsp, proc}', '{int4}'),
+ ('type', '{pg_catalog._int4}', '{}'),
+ ('type', '{addr_nsp.gendomain}', '{}'),
+ ('type', '{addr_nsp.gencomptype}', '{}'),
+ ('type', '{addr_nsp.genenum}', '{}'),
+ ('cast', '{int8}', '{int4}'),
+ ('collation', '{default}', '{}'),
+ ('table constraint', '{addr_nsp, gentable, a_chk}', '{}'),
+ ('domain constraint', '{addr_nsp.gendomain}', '{domconstr}'),
+ ('conversion', '{pg_catalog, koi8_r_to_mic}', '{}'),
+ ('default value', '{addr_nsp, gentable, b}', '{}'),
+ ('language', '{plpgsql}', '{}'),
+ -- large object
+ ('operator', '{+}', '{int4, int4}'),
+ ('operator class', '{btree, int4_ops}', '{}'),
+ ('operator family', '{btree, integer_ops}', '{}'),
+ ('operator of access method', '{btree,integer_ops,1}', '{integer,integer}'),
+ ('function of access method', '{btree,integer_ops,2}', '{integer,integer}'),
+ ('rule', '{addr_nsp, genview, _RETURN}', '{}'),
+ ('trigger', '{addr_nsp, gentable, t}', '{}'),
+ ('schema', '{addr_nsp}', '{}'),
+ ('text search parser', '{addr_ts_prs}', '{}'),
+ ('text search dictionary', '{addr_ts_dict}', '{}'),
+ ('text search template', '{addr_ts_temp}', '{}'),
+ ('text search configuration', '{addr_ts_conf}', '{}'),
+ ('role', '{regress_addr_user}', '{}'),
+ -- database
+ -- tablespace
+ ('foreign-data wrapper', '{addr_fdw}', '{}'),
+ ('server', '{addr_fserv}', '{}'),
+ ('user mapping', '{regress_addr_user}', '{integer}'),
+ ('default acl', '{regress_addr_user,public}', '{r}'),
+ ('default acl', '{regress_addr_user}', '{r}'),
+ -- extension
+ -- event trigger
+ ('policy', '{addr_nsp, gentable, genpol}', '{}'),
+ ('transform', '{int}', '{sql}'),
+ ('access method', '{btree}', '{}'),
+ ('publication', '{addr_pub}', '{}'),
+ ('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('subscription', '{regress_addr_sub}', '{}'),
+ ('statistics object', '{addr_nsp, gentable_stat}', '{}')
+ )
+SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
+ -- test roundtrip through pg_identify_object_as_address
+ ROW(pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)) =
+ ROW(pg_identify_object(addr2.classid, addr2.objid, addr2.objsubid))
+ FROM objects, pg_get_object_address(type, name, args) addr1,
+ pg_identify_object_as_address(classid, objid, objsubid) ioa(typ,nms,args),
+ pg_get_object_address(typ, nms, ioa.args) as addr2
+ ORDER BY addr1.classid, addr1.objid, addr1.objsubid;
+ type | schema | name | identity | ?column?
+---------------------------+------------+-------------------+----------------------------------------------------------------------+----------
+ default acl | | | for role regress_addr_user in schema public on tables | t
+ default acl | | | for role regress_addr_user on tables | t
+ type | pg_catalog | _int4 | integer[] | t
+ type | addr_nsp | gencomptype | addr_nsp.gencomptype | t
+ type | addr_nsp | genenum | addr_nsp.genenum | t
+ type | addr_nsp | gendomain | addr_nsp.gendomain | t
+ function | pg_catalog | | pg_catalog.pg_identify_object(pg_catalog.oid,pg_catalog.oid,integer) | t
+ aggregate | addr_nsp | | addr_nsp.genaggr(integer) | t
+ procedure | addr_nsp | | addr_nsp.proc(integer) | t
+ sequence | addr_nsp | gentable_a_seq | addr_nsp.gentable_a_seq | t
+ table | addr_nsp | gentable | addr_nsp.gentable | t
+ table column | addr_nsp | gentable | addr_nsp.gentable.b | t
+ index | addr_nsp | gentable_pkey | addr_nsp.gentable_pkey | t
+ table | addr_nsp | parttable | addr_nsp.parttable | t
+ index | addr_nsp | parttable_pkey | addr_nsp.parttable_pkey | t
+ view | addr_nsp | genview | addr_nsp.genview | t
+ materialized view | addr_nsp | genmatview | addr_nsp.genmatview | t
+ foreign table | addr_nsp | genftable | addr_nsp.genftable | t
+ foreign table column | addr_nsp | genftable | addr_nsp.genftable.a | t
+ role | | regress_addr_user | regress_addr_user | t
+ server | | addr_fserv | addr_fserv | t
+ user mapping | | | regress_addr_user on server integer | t
+ foreign-data wrapper | | addr_fdw | addr_fdw | t
+ access method | | btree | btree | t
+ operator of access method | | | operator 1 (integer, integer) of pg_catalog.integer_ops USING btree | t
+ function of access method | | | function 2 (integer, integer) of pg_catalog.integer_ops USING btree | t
+ default value | | | for addr_nsp.gentable.b | t
+ cast | | | (bigint AS integer) | t
+ table constraint | addr_nsp | | a_chk on addr_nsp.gentable | t
+ domain constraint | addr_nsp | | domconstr on addr_nsp.gendomain | t
+ conversion | pg_catalog | koi8_r_to_mic | pg_catalog.koi8_r_to_mic | t
+ language | | plpgsql | plpgsql | t
+ schema | | addr_nsp | addr_nsp | t
+ operator class | pg_catalog | int4_ops | pg_catalog.int4_ops USING btree | t
+ operator | pg_catalog | | pg_catalog.+(integer,integer) | t
+ rule | | | "_RETURN" on addr_nsp.genview | t
+ trigger | | | t on addr_nsp.gentable | t
+ operator family | pg_catalog | integer_ops | pg_catalog.integer_ops USING btree | t
+ policy | | | genpol on addr_nsp.gentable | t
+ statistics object | addr_nsp | gentable_stat | addr_nsp.gentable_stat | t
+ collation | pg_catalog | "default" | pg_catalog."default" | t
+ transform | | | for integer on language sql | t
+ text search dictionary | addr_nsp | addr_ts_dict | addr_nsp.addr_ts_dict | t
+ text search parser | addr_nsp | addr_ts_prs | addr_nsp.addr_ts_prs | t
+ text search configuration | addr_nsp | addr_ts_conf | addr_nsp.addr_ts_conf | t
+ text search template | addr_nsp | addr_ts_temp | addr_nsp.addr_ts_temp | t
+ subscription | | regress_addr_sub | regress_addr_sub | t
+ publication | | addr_pub | addr_pub | t
+ publication relation | | | addr_nsp.gentable in publication addr_pub | t
+(49 rows)
+
+---
+--- Cleanup resources
+---
+DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to server addr_fserv
+drop cascades to foreign table genftable
+drop cascades to server integer
+drop cascades to user mapping for regress_addr_user on server integer
+DROP PUBLICATION addr_pub;
+DROP SUBSCRIPTION regress_addr_sub;
+DROP SCHEMA addr_nsp CASCADE;
+NOTICE: drop cascades to 14 other objects
+DETAIL: drop cascades to text search dictionary addr_ts_dict
+drop cascades to text search configuration addr_ts_conf
+drop cascades to text search template addr_ts_temp
+drop cascades to text search parser addr_ts_prs
+drop cascades to table gentable
+drop cascades to table parttable
+drop cascades to view genview
+drop cascades to materialized view genmatview
+drop cascades to type gencomptype
+drop cascades to type genenum
+drop cascades to function genaggr(integer)
+drop cascades to type gendomain
+drop cascades to function trig()
+drop cascades to function proc(integer)
+DROP OWNED BY regress_addr_user;
+DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Make sure that NULL handling is correct.
+\pset null 'NULL'
+-- Temporarily disable fancy output, so as future additions never create
+-- a large amount of diffs.
+\a\t
+-- Keep this list in the same order as getObjectIdentityParts()
+-- in objectaddress.c.
+WITH objects (classid, objid, objsubid) AS (VALUES
+ ('pg_class'::regclass, 0, 0), -- no relation
+ ('pg_class'::regclass, 'pg_class'::regclass, 100), -- no column for relation
+ ('pg_proc'::regclass, 0, 0), -- no function
+ ('pg_type'::regclass, 0, 0), -- no type
+ ('pg_cast'::regclass, 0, 0), -- no cast
+ ('pg_collation'::regclass, 0, 0), -- no collation
+ ('pg_constraint'::regclass, 0, 0), -- no constraint
+ ('pg_conversion'::regclass, 0, 0), -- no conversion
+ ('pg_attrdef'::regclass, 0, 0), -- no default attribute
+ ('pg_language'::regclass, 0, 0), -- no language
+ ('pg_largeobject'::regclass, 0, 0), -- no large object, no error
+ ('pg_operator'::regclass, 0, 0), -- no operator
+ ('pg_opclass'::regclass, 0, 0), -- no opclass, no need to check for no access method
+ ('pg_opfamily'::regclass, 0, 0), -- no opfamily
+ ('pg_am'::regclass, 0, 0), -- no access method
+ ('pg_amop'::regclass, 0, 0), -- no AM operator
+ ('pg_amproc'::regclass, 0, 0), -- no AM proc
+ ('pg_rewrite'::regclass, 0, 0), -- no rewrite
+ ('pg_trigger'::regclass, 0, 0), -- no trigger
+ ('pg_namespace'::regclass, 0, 0), -- no schema
+ ('pg_statistic_ext'::regclass, 0, 0), -- no statistics
+ ('pg_ts_parser'::regclass, 0, 0), -- no TS parser
+ ('pg_ts_dict'::regclass, 0, 0), -- no TS dictionnary
+ ('pg_ts_template'::regclass, 0, 0), -- no TS template
+ ('pg_ts_config'::regclass, 0, 0), -- no TS configuration
+ ('pg_authid'::regclass, 0, 0), -- no role
+ ('pg_database'::regclass, 0, 0), -- no database
+ ('pg_tablespace'::regclass, 0, 0), -- no tablespace
+ ('pg_foreign_data_wrapper'::regclass, 0, 0), -- no FDW
+ ('pg_foreign_server'::regclass, 0, 0), -- no server
+ ('pg_user_mapping'::regclass, 0, 0), -- no user mapping
+ ('pg_default_acl'::regclass, 0, 0), -- no default ACL
+ ('pg_extension'::regclass, 0, 0), -- no extension
+ ('pg_event_trigger'::regclass, 0, 0), -- no event trigger
+ ('pg_policy'::regclass, 0, 0), -- no policy
+ ('pg_publication'::regclass, 0, 0), -- no publication
+ ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
+ ('pg_subscription'::regclass, 0, 0), -- no subscription
+ ('pg_transform'::regclass, 0, 0) -- no transformation
+ )
+SELECT ROW(pg_identify_object(objects.classid, objects.objid, objects.objsubid))
+ AS ident,
+ ROW(pg_identify_object_as_address(objects.classid, objects.objid, objects.objsubid))
+ AS addr,
+ pg_describe_object(objects.classid, objects.objid, objects.objsubid)
+ AS descr
+FROM objects
+ORDER BY objects.classid, objects.objid, objects.objsubid;
+("(""default acl"",,,)")|("(""default acl"",,)")|NULL
+("(tablespace,,,)")|("(tablespace,,)")|NULL
+("(type,,,)")|("(type,,)")|NULL
+("(routine,,,)")|("(routine,,)")|NULL
+("(relation,,,)")|("(relation,,)")|NULL
+("(""table column"",,,)")|("(""table column"",,)")|NULL
+("(role,,,)")|("(role,,)")|NULL
+("(database,,,)")|("(database,,)")|NULL
+("(server,,,)")|("(server,,)")|NULL
+("(""user mapping"",,,)")|("(""user mapping"",,)")|NULL
+("(""foreign-data wrapper"",,,)")|("(""foreign-data wrapper"",,)")|NULL
+("(""access method"",,,)")|("(""access method"",,)")|NULL
+("(""operator of access method"",,,)")|("(""operator of access method"",,)")|NULL
+("(""function of access method"",,,)")|("(""function of access method"",,)")|NULL
+("(""default value"",,,)")|("(""default value"",,)")|NULL
+("(cast,,,)")|("(cast,,)")|NULL
+("(constraint,,,)")|("(constraint,,)")|NULL
+("(conversion,,,)")|("(conversion,,)")|NULL
+("(language,,,)")|("(language,,)")|NULL
+("(""large object"",,,)")|("(""large object"",,)")|NULL
+("(schema,,,)")|("(schema,,)")|NULL
+("(""operator class"",,,)")|("(""operator class"",,)")|NULL
+("(operator,,,)")|("(operator,,)")|NULL
+("(rule,,,)")|("(rule,,)")|NULL
+("(trigger,,,)")|("(trigger,,)")|NULL
+("(""operator family"",,,)")|("(""operator family"",,)")|NULL
+("(extension,,,)")|("(extension,,)")|NULL
+("(policy,,,)")|("(policy,,)")|NULL
+("(""statistics object"",,,)")|("(""statistics object"",,)")|NULL
+("(collation,,,)")|("(collation,,)")|NULL
+("(""event trigger"",,,)")|("(""event trigger"",,)")|NULL
+("(transform,,,)")|("(transform,,)")|NULL
+("(""text search dictionary"",,,)")|("(""text search dictionary"",,)")|NULL
+("(""text search parser"",,,)")|("(""text search parser"",,)")|NULL
+("(""text search configuration"",,,)")|("(""text search configuration"",,)")|NULL
+("(""text search template"",,,)")|("(""text search template"",,)")|NULL
+("(subscription,,,)")|("(subscription,,)")|NULL
+("(publication,,,)")|("(publication,,)")|NULL
+("(""publication relation"",,,)")|("(""publication relation"",,)")|NULL
+-- restore normal output mode
+\a\t
diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out
new file mode 100644
index 0000000..8909373
--- /dev/null
+++ b/src/test/regress/expected/oid.out
@@ -0,0 +1,122 @@
+--
+-- OID
+--
+CREATE TABLE OID_TBL(f1 oid);
+INSERT INTO OID_TBL(f1) VALUES ('1234');
+INSERT INTO OID_TBL(f1) VALUES ('1235');
+INSERT INTO OID_TBL(f1) VALUES ('987');
+INSERT INTO OID_TBL(f1) VALUES ('-1040');
+INSERT INTO OID_TBL(f1) VALUES ('99999999');
+INSERT INTO OID_TBL(f1) VALUES ('5 ');
+INSERT INTO OID_TBL(f1) VALUES (' 10 ');
+-- leading/trailing hard tab is also allowed
+INSERT INTO OID_TBL(f1) VALUES (' 15 ');
+-- bad inputs
+INSERT INTO OID_TBL(f1) VALUES ('');
+ERROR: invalid input syntax for type oid: ""
+LINE 1: INSERT INTO OID_TBL(f1) VALUES ('');
+ ^
+INSERT INTO OID_TBL(f1) VALUES (' ');
+ERROR: invalid input syntax for type oid: " "
+LINE 1: INSERT INTO OID_TBL(f1) VALUES (' ');
+ ^
+INSERT INTO OID_TBL(f1) VALUES ('asdfasd');
+ERROR: invalid input syntax for type oid: "asdfasd"
+LINE 1: INSERT INTO OID_TBL(f1) VALUES ('asdfasd');
+ ^
+INSERT INTO OID_TBL(f1) VALUES ('99asdfasd');
+ERROR: invalid input syntax for type oid: "99asdfasd"
+LINE 1: INSERT INTO OID_TBL(f1) VALUES ('99asdfasd');
+ ^
+INSERT INTO OID_TBL(f1) VALUES ('5 d');
+ERROR: invalid input syntax for type oid: "5 d"
+LINE 1: INSERT INTO OID_TBL(f1) VALUES ('5 d');
+ ^
+INSERT INTO OID_TBL(f1) VALUES (' 5d');
+ERROR: invalid input syntax for type oid: " 5d"
+LINE 1: INSERT INTO OID_TBL(f1) VALUES (' 5d');
+ ^
+INSERT INTO OID_TBL(f1) VALUES ('5 5');
+ERROR: invalid input syntax for type oid: "5 5"
+LINE 1: INSERT INTO OID_TBL(f1) VALUES ('5 5');
+ ^
+INSERT INTO OID_TBL(f1) VALUES (' - 500');
+ERROR: invalid input syntax for type oid: " - 500"
+LINE 1: INSERT INTO OID_TBL(f1) VALUES (' - 500');
+ ^
+INSERT INTO OID_TBL(f1) VALUES ('32958209582039852935');
+ERROR: value "32958209582039852935" is out of range for type oid
+LINE 1: INSERT INTO OID_TBL(f1) VALUES ('32958209582039852935');
+ ^
+INSERT INTO OID_TBL(f1) VALUES ('-23582358720398502385');
+ERROR: value "-23582358720398502385" is out of range for type oid
+LINE 1: INSERT INTO OID_TBL(f1) VALUES ('-23582358720398502385');
+ ^
+SELECT * FROM OID_TBL;
+ f1
+------------
+ 1234
+ 1235
+ 987
+ 4294966256
+ 99999999
+ 5
+ 10
+ 15
+(8 rows)
+
+SELECT o.* FROM OID_TBL o WHERE o.f1 = 1234;
+ f1
+------
+ 1234
+(1 row)
+
+SELECT o.* FROM OID_TBL o WHERE o.f1 <> '1234';
+ f1
+------------
+ 1235
+ 987
+ 4294966256
+ 99999999
+ 5
+ 10
+ 15
+(7 rows)
+
+SELECT o.* FROM OID_TBL o WHERE o.f1 <= '1234';
+ f1
+------
+ 1234
+ 987
+ 5
+ 10
+ 15
+(5 rows)
+
+SELECT o.* FROM OID_TBL o WHERE o.f1 < '1234';
+ f1
+-----
+ 987
+ 5
+ 10
+ 15
+(4 rows)
+
+SELECT o.* FROM OID_TBL o WHERE o.f1 >= '1234';
+ f1
+------------
+ 1234
+ 1235
+ 4294966256
+ 99999999
+(4 rows)
+
+SELECT o.* FROM OID_TBL o WHERE o.f1 > '1234';
+ f1
+------------
+ 1235
+ 4294966256
+ 99999999
+(3 rows)
+
+DROP TABLE OID_TBL;
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
new file mode 100644
index 0000000..1461e94
--- /dev/null
+++ b/src/test/regress/expected/oidjoins.out
@@ -0,0 +1,266 @@
+--
+-- Verify system catalog foreign key relationships
+--
+DO $doblock$
+declare
+ fk record;
+ nkeys integer;
+ cmd text;
+ err record;
+begin
+ for fk in select * from pg_get_catalog_foreign_keys()
+ loop
+ raise notice 'checking % % => % %',
+ fk.fktable, fk.fkcols, fk.pktable, fk.pkcols;
+ nkeys := array_length(fk.fkcols, 1);
+ cmd := 'SELECT ctid';
+ for i in 1 .. nkeys loop
+ cmd := cmd || ', ' || quote_ident(fk.fkcols[i]);
+ end loop;
+ if fk.is_array then
+ cmd := cmd || ' FROM (SELECT ctid';
+ for i in 1 .. nkeys-1 loop
+ cmd := cmd || ', ' || quote_ident(fk.fkcols[i]);
+ end loop;
+ cmd := cmd || ', unnest(' || quote_ident(fk.fkcols[nkeys]);
+ cmd := cmd || ') as ' || quote_ident(fk.fkcols[nkeys]);
+ cmd := cmd || ' FROM ' || fk.fktable::text || ') fk WHERE ';
+ else
+ cmd := cmd || ' FROM ' || fk.fktable::text || ' fk WHERE ';
+ end if;
+ if fk.is_opt then
+ for i in 1 .. nkeys loop
+ cmd := cmd || quote_ident(fk.fkcols[i]) || ' != 0 AND ';
+ end loop;
+ end if;
+ cmd := cmd || 'NOT EXISTS(SELECT 1 FROM ' || fk.pktable::text || ' pk WHERE ';
+ for i in 1 .. nkeys loop
+ if i > 1 then cmd := cmd || ' AND '; end if;
+ cmd := cmd || 'pk.' || quote_ident(fk.pkcols[i]);
+ cmd := cmd || ' = fk.' || quote_ident(fk.fkcols[i]);
+ end loop;
+ cmd := cmd || ')';
+ -- raise notice 'cmd = %', cmd;
+ for err in execute cmd loop
+ raise warning 'FK VIOLATION IN %(%): %', fk.fktable, fk.fkcols, err;
+ end loop;
+ end loop;
+end
+$doblock$;
+NOTICE: checking pg_proc {pronamespace} => pg_namespace {oid}
+NOTICE: checking pg_proc {proowner} => pg_authid {oid}
+NOTICE: checking pg_proc {prolang} => pg_language {oid}
+NOTICE: checking pg_proc {provariadic} => pg_type {oid}
+NOTICE: checking pg_proc {prosupport} => pg_proc {oid}
+NOTICE: checking pg_proc {prorettype} => pg_type {oid}
+NOTICE: checking pg_proc {proargtypes} => pg_type {oid}
+NOTICE: checking pg_proc {proallargtypes} => pg_type {oid}
+NOTICE: checking pg_proc {protrftypes} => pg_type {oid}
+NOTICE: checking pg_type {typnamespace} => pg_namespace {oid}
+NOTICE: checking pg_type {typowner} => pg_authid {oid}
+NOTICE: checking pg_type {typrelid} => pg_class {oid}
+NOTICE: checking pg_type {typsubscript} => pg_proc {oid}
+NOTICE: checking pg_type {typelem} => pg_type {oid}
+NOTICE: checking pg_type {typarray} => pg_type {oid}
+NOTICE: checking pg_type {typinput} => pg_proc {oid}
+NOTICE: checking pg_type {typoutput} => pg_proc {oid}
+NOTICE: checking pg_type {typreceive} => pg_proc {oid}
+NOTICE: checking pg_type {typsend} => pg_proc {oid}
+NOTICE: checking pg_type {typmodin} => pg_proc {oid}
+NOTICE: checking pg_type {typmodout} => pg_proc {oid}
+NOTICE: checking pg_type {typanalyze} => pg_proc {oid}
+NOTICE: checking pg_type {typbasetype} => pg_type {oid}
+NOTICE: checking pg_type {typcollation} => pg_collation {oid}
+NOTICE: checking pg_attribute {attrelid} => pg_class {oid}
+NOTICE: checking pg_attribute {atttypid} => pg_type {oid}
+NOTICE: checking pg_attribute {attcollation} => pg_collation {oid}
+NOTICE: checking pg_class {relnamespace} => pg_namespace {oid}
+NOTICE: checking pg_class {reltype} => pg_type {oid}
+NOTICE: checking pg_class {reloftype} => pg_type {oid}
+NOTICE: checking pg_class {relowner} => pg_authid {oid}
+NOTICE: checking pg_class {relam} => pg_am {oid}
+NOTICE: checking pg_class {reltablespace} => pg_tablespace {oid}
+NOTICE: checking pg_class {reltoastrelid} => pg_class {oid}
+NOTICE: checking pg_class {relrewrite} => pg_class {oid}
+NOTICE: checking pg_attrdef {adrelid} => pg_class {oid}
+NOTICE: checking pg_attrdef {adrelid,adnum} => pg_attribute {attrelid,attnum}
+NOTICE: checking pg_constraint {connamespace} => pg_namespace {oid}
+NOTICE: checking pg_constraint {conrelid} => pg_class {oid}
+NOTICE: checking pg_constraint {contypid} => pg_type {oid}
+NOTICE: checking pg_constraint {conindid} => pg_class {oid}
+NOTICE: checking pg_constraint {conparentid} => pg_constraint {oid}
+NOTICE: checking pg_constraint {confrelid} => pg_class {oid}
+NOTICE: checking pg_constraint {conpfeqop} => pg_operator {oid}
+NOTICE: checking pg_constraint {conppeqop} => pg_operator {oid}
+NOTICE: checking pg_constraint {conffeqop} => pg_operator {oid}
+NOTICE: checking pg_constraint {conexclop} => pg_operator {oid}
+NOTICE: checking pg_constraint {conrelid,conkey} => pg_attribute {attrelid,attnum}
+NOTICE: checking pg_constraint {confrelid,confkey} => pg_attribute {attrelid,attnum}
+NOTICE: checking pg_inherits {inhrelid} => pg_class {oid}
+NOTICE: checking pg_inherits {inhparent} => pg_class {oid}
+NOTICE: checking pg_index {indexrelid} => pg_class {oid}
+NOTICE: checking pg_index {indrelid} => pg_class {oid}
+NOTICE: checking pg_index {indcollation} => pg_collation {oid}
+NOTICE: checking pg_index {indclass} => pg_opclass {oid}
+NOTICE: checking pg_index {indrelid,indkey} => pg_attribute {attrelid,attnum}
+NOTICE: checking pg_operator {oprnamespace} => pg_namespace {oid}
+NOTICE: checking pg_operator {oprowner} => pg_authid {oid}
+NOTICE: checking pg_operator {oprleft} => pg_type {oid}
+NOTICE: checking pg_operator {oprright} => pg_type {oid}
+NOTICE: checking pg_operator {oprresult} => pg_type {oid}
+NOTICE: checking pg_operator {oprcom} => pg_operator {oid}
+NOTICE: checking pg_operator {oprnegate} => pg_operator {oid}
+NOTICE: checking pg_operator {oprcode} => pg_proc {oid}
+NOTICE: checking pg_operator {oprrest} => pg_proc {oid}
+NOTICE: checking pg_operator {oprjoin} => pg_proc {oid}
+NOTICE: checking pg_opfamily {opfmethod} => pg_am {oid}
+NOTICE: checking pg_opfamily {opfnamespace} => pg_namespace {oid}
+NOTICE: checking pg_opfamily {opfowner} => pg_authid {oid}
+NOTICE: checking pg_opclass {opcmethod} => pg_am {oid}
+NOTICE: checking pg_opclass {opcnamespace} => pg_namespace {oid}
+NOTICE: checking pg_opclass {opcowner} => pg_authid {oid}
+NOTICE: checking pg_opclass {opcfamily} => pg_opfamily {oid}
+NOTICE: checking pg_opclass {opcintype} => pg_type {oid}
+NOTICE: checking pg_opclass {opckeytype} => pg_type {oid}
+NOTICE: checking pg_am {amhandler} => pg_proc {oid}
+NOTICE: checking pg_amop {amopfamily} => pg_opfamily {oid}
+NOTICE: checking pg_amop {amoplefttype} => pg_type {oid}
+NOTICE: checking pg_amop {amoprighttype} => pg_type {oid}
+NOTICE: checking pg_amop {amopopr} => pg_operator {oid}
+NOTICE: checking pg_amop {amopmethod} => pg_am {oid}
+NOTICE: checking pg_amop {amopsortfamily} => pg_opfamily {oid}
+NOTICE: checking pg_amproc {amprocfamily} => pg_opfamily {oid}
+NOTICE: checking pg_amproc {amproclefttype} => pg_type {oid}
+NOTICE: checking pg_amproc {amprocrighttype} => pg_type {oid}
+NOTICE: checking pg_amproc {amproc} => pg_proc {oid}
+NOTICE: checking pg_language {lanowner} => pg_authid {oid}
+NOTICE: checking pg_language {lanplcallfoid} => pg_proc {oid}
+NOTICE: checking pg_language {laninline} => pg_proc {oid}
+NOTICE: checking pg_language {lanvalidator} => pg_proc {oid}
+NOTICE: checking pg_largeobject_metadata {lomowner} => pg_authid {oid}
+NOTICE: checking pg_largeobject {loid} => pg_largeobject_metadata {oid}
+NOTICE: checking pg_aggregate {aggfnoid} => pg_proc {oid}
+NOTICE: checking pg_aggregate {aggtransfn} => pg_proc {oid}
+NOTICE: checking pg_aggregate {aggfinalfn} => pg_proc {oid}
+NOTICE: checking pg_aggregate {aggcombinefn} => pg_proc {oid}
+NOTICE: checking pg_aggregate {aggserialfn} => pg_proc {oid}
+NOTICE: checking pg_aggregate {aggdeserialfn} => pg_proc {oid}
+NOTICE: checking pg_aggregate {aggmtransfn} => pg_proc {oid}
+NOTICE: checking pg_aggregate {aggminvtransfn} => pg_proc {oid}
+NOTICE: checking pg_aggregate {aggmfinalfn} => pg_proc {oid}
+NOTICE: checking pg_aggregate {aggsortop} => pg_operator {oid}
+NOTICE: checking pg_aggregate {aggtranstype} => pg_type {oid}
+NOTICE: checking pg_aggregate {aggmtranstype} => pg_type {oid}
+NOTICE: checking pg_statistic {starelid} => pg_class {oid}
+NOTICE: checking pg_statistic {staop1} => pg_operator {oid}
+NOTICE: checking pg_statistic {staop2} => pg_operator {oid}
+NOTICE: checking pg_statistic {staop3} => pg_operator {oid}
+NOTICE: checking pg_statistic {staop4} => pg_operator {oid}
+NOTICE: checking pg_statistic {staop5} => pg_operator {oid}
+NOTICE: checking pg_statistic {stacoll1} => pg_collation {oid}
+NOTICE: checking pg_statistic {stacoll2} => pg_collation {oid}
+NOTICE: checking pg_statistic {stacoll3} => pg_collation {oid}
+NOTICE: checking pg_statistic {stacoll4} => pg_collation {oid}
+NOTICE: checking pg_statistic {stacoll5} => pg_collation {oid}
+NOTICE: checking pg_statistic {starelid,staattnum} => pg_attribute {attrelid,attnum}
+NOTICE: checking pg_statistic_ext {stxrelid} => pg_class {oid}
+NOTICE: checking pg_statistic_ext {stxnamespace} => pg_namespace {oid}
+NOTICE: checking pg_statistic_ext {stxowner} => pg_authid {oid}
+NOTICE: checking pg_statistic_ext {stxrelid,stxkeys} => pg_attribute {attrelid,attnum}
+NOTICE: checking pg_statistic_ext_data {stxoid} => pg_statistic_ext {oid}
+NOTICE: checking pg_rewrite {ev_class} => pg_class {oid}
+NOTICE: checking pg_trigger {tgrelid} => pg_class {oid}
+NOTICE: checking pg_trigger {tgparentid} => pg_trigger {oid}
+NOTICE: checking pg_trigger {tgfoid} => pg_proc {oid}
+NOTICE: checking pg_trigger {tgconstrrelid} => pg_class {oid}
+NOTICE: checking pg_trigger {tgconstrindid} => pg_class {oid}
+NOTICE: checking pg_trigger {tgconstraint} => pg_constraint {oid}
+NOTICE: checking pg_trigger {tgrelid,tgattr} => pg_attribute {attrelid,attnum}
+NOTICE: checking pg_event_trigger {evtowner} => pg_authid {oid}
+NOTICE: checking pg_event_trigger {evtfoid} => pg_proc {oid}
+NOTICE: checking pg_description {classoid} => pg_class {oid}
+NOTICE: checking pg_cast {castsource} => pg_type {oid}
+NOTICE: checking pg_cast {casttarget} => pg_type {oid}
+NOTICE: checking pg_cast {castfunc} => pg_proc {oid}
+NOTICE: checking pg_enum {enumtypid} => pg_type {oid}
+NOTICE: checking pg_namespace {nspowner} => pg_authid {oid}
+NOTICE: checking pg_conversion {connamespace} => pg_namespace {oid}
+NOTICE: checking pg_conversion {conowner} => pg_authid {oid}
+NOTICE: checking pg_conversion {conproc} => pg_proc {oid}
+NOTICE: checking pg_depend {classid} => pg_class {oid}
+NOTICE: checking pg_depend {refclassid} => pg_class {oid}
+NOTICE: checking pg_database {datdba} => pg_authid {oid}
+NOTICE: checking pg_database {dattablespace} => pg_tablespace {oid}
+NOTICE: checking pg_db_role_setting {setdatabase} => pg_database {oid}
+NOTICE: checking pg_db_role_setting {setrole} => pg_authid {oid}
+NOTICE: checking pg_tablespace {spcowner} => pg_authid {oid}
+NOTICE: checking pg_auth_members {roleid} => pg_authid {oid}
+NOTICE: checking pg_auth_members {member} => pg_authid {oid}
+NOTICE: checking pg_auth_members {grantor} => pg_authid {oid}
+NOTICE: checking pg_shdepend {dbid} => pg_database {oid}
+NOTICE: checking pg_shdepend {classid} => pg_class {oid}
+NOTICE: checking pg_shdepend {refclassid} => pg_class {oid}
+NOTICE: checking pg_shdescription {classoid} => pg_class {oid}
+NOTICE: checking pg_ts_config {cfgnamespace} => pg_namespace {oid}
+NOTICE: checking pg_ts_config {cfgowner} => pg_authid {oid}
+NOTICE: checking pg_ts_config {cfgparser} => pg_ts_parser {oid}
+NOTICE: checking pg_ts_config_map {mapcfg} => pg_ts_config {oid}
+NOTICE: checking pg_ts_config_map {mapdict} => pg_ts_dict {oid}
+NOTICE: checking pg_ts_dict {dictnamespace} => pg_namespace {oid}
+NOTICE: checking pg_ts_dict {dictowner} => pg_authid {oid}
+NOTICE: checking pg_ts_dict {dicttemplate} => pg_ts_template {oid}
+NOTICE: checking pg_ts_parser {prsnamespace} => pg_namespace {oid}
+NOTICE: checking pg_ts_parser {prsstart} => pg_proc {oid}
+NOTICE: checking pg_ts_parser {prstoken} => pg_proc {oid}
+NOTICE: checking pg_ts_parser {prsend} => pg_proc {oid}
+NOTICE: checking pg_ts_parser {prsheadline} => pg_proc {oid}
+NOTICE: checking pg_ts_parser {prslextype} => pg_proc {oid}
+NOTICE: checking pg_ts_template {tmplnamespace} => pg_namespace {oid}
+NOTICE: checking pg_ts_template {tmplinit} => pg_proc {oid}
+NOTICE: checking pg_ts_template {tmpllexize} => pg_proc {oid}
+NOTICE: checking pg_extension {extowner} => pg_authid {oid}
+NOTICE: checking pg_extension {extnamespace} => pg_namespace {oid}
+NOTICE: checking pg_extension {extconfig} => pg_class {oid}
+NOTICE: checking pg_foreign_data_wrapper {fdwowner} => pg_authid {oid}
+NOTICE: checking pg_foreign_data_wrapper {fdwhandler} => pg_proc {oid}
+NOTICE: checking pg_foreign_data_wrapper {fdwvalidator} => pg_proc {oid}
+NOTICE: checking pg_foreign_server {srvowner} => pg_authid {oid}
+NOTICE: checking pg_foreign_server {srvfdw} => pg_foreign_data_wrapper {oid}
+NOTICE: checking pg_user_mapping {umuser} => pg_authid {oid}
+NOTICE: checking pg_user_mapping {umserver} => pg_foreign_server {oid}
+NOTICE: checking pg_foreign_table {ftrelid} => pg_class {oid}
+NOTICE: checking pg_foreign_table {ftserver} => pg_foreign_server {oid}
+NOTICE: checking pg_policy {polrelid} => pg_class {oid}
+NOTICE: checking pg_policy {polroles} => pg_authid {oid}
+NOTICE: checking pg_default_acl {defaclrole} => pg_authid {oid}
+NOTICE: checking pg_default_acl {defaclnamespace} => pg_namespace {oid}
+NOTICE: checking pg_init_privs {classoid} => pg_class {oid}
+NOTICE: checking pg_seclabel {classoid} => pg_class {oid}
+NOTICE: checking pg_shseclabel {classoid} => pg_class {oid}
+NOTICE: checking pg_collation {collnamespace} => pg_namespace {oid}
+NOTICE: checking pg_collation {collowner} => pg_authid {oid}
+NOTICE: checking pg_partitioned_table {partrelid} => pg_class {oid}
+NOTICE: checking pg_partitioned_table {partdefid} => pg_class {oid}
+NOTICE: checking pg_partitioned_table {partclass} => pg_opclass {oid}
+NOTICE: checking pg_partitioned_table {partcollation} => pg_collation {oid}
+NOTICE: checking pg_partitioned_table {partrelid,partattrs} => pg_attribute {attrelid,attnum}
+NOTICE: checking pg_range {rngtypid} => pg_type {oid}
+NOTICE: checking pg_range {rngsubtype} => pg_type {oid}
+NOTICE: checking pg_range {rngmultitypid} => pg_type {oid}
+NOTICE: checking pg_range {rngcollation} => pg_collation {oid}
+NOTICE: checking pg_range {rngsubopc} => pg_opclass {oid}
+NOTICE: checking pg_range {rngcanonical} => pg_proc {oid}
+NOTICE: checking pg_range {rngsubdiff} => pg_proc {oid}
+NOTICE: checking pg_transform {trftype} => pg_type {oid}
+NOTICE: checking pg_transform {trflang} => pg_language {oid}
+NOTICE: checking pg_transform {trffromsql} => pg_proc {oid}
+NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
+NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
+NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
+NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
+NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
+NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
+NOTICE: checking pg_subscription_rel {srrelid} => pg_class {oid}
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
new file mode 100644
index 0000000..562b586
--- /dev/null
+++ b/src/test/regress/expected/opr_sanity.out
@@ -0,0 +1,2266 @@
+--
+-- OPR_SANITY
+-- Sanity checks for common errors in making operator/procedure system tables:
+-- pg_operator, pg_proc, pg_cast, pg_conversion, pg_aggregate, pg_am,
+-- pg_amop, pg_amproc, pg_opclass, pg_opfamily, pg_index.
+--
+-- Every test failure in this file should be closely inspected.
+-- The description of the failing test should be read carefully before
+-- adjusting the expected output. In most cases, the queries should
+-- not find *any* matching entries.
+--
+-- 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.
+--
+-- NB: run this test earlier than the create_operator test, because
+-- that test creates some bogus operators...
+-- **************** pg_proc ****************
+-- Look for illegal values in pg_proc fields.
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prolang = 0 OR p1.prorettype = 0 OR
+ p1.pronargs < 0 OR
+ p1.pronargdefaults < 0 OR
+ p1.pronargdefaults > p1.pronargs OR
+ array_lower(p1.proargtypes, 1) != 0 OR
+ array_upper(p1.proargtypes, 1) != p1.pronargs-1 OR
+ 0::oid = ANY (p1.proargtypes) OR
+ procost <= 0 OR
+ CASE WHEN proretset THEN prorows <= 0 ELSE prorows != 0 END OR
+ prokind NOT IN ('f', 'a', 'w', 'p') OR
+ provolatile NOT IN ('i', 's', 'v') OR
+ proparallel NOT IN ('s', 'r', 'u');
+ oid | proname
+-----+---------
+(0 rows)
+
+-- prosrc should never be null; it can be empty only if prosqlbody isn't null
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE prosrc IS NULL;
+ oid | proname
+-----+---------
+(0 rows)
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE (prosrc = '' OR prosrc = '-') AND prosqlbody IS NULL;
+ oid | proname
+-----+---------
+(0 rows)
+
+-- proretset should only be set for normal functions
+SELECT p1.oid, p1.proname
+FROM pg_proc AS p1
+WHERE proretset AND prokind != 'f';
+ oid | proname
+-----+---------
+(0 rows)
+
+-- currently, no built-in functions should be SECURITY DEFINER;
+-- this might change in future, but there will probably never be many.
+SELECT p1.oid, p1.proname
+FROM pg_proc AS p1
+WHERE prosecdef
+ORDER BY 1;
+ oid | proname
+-----+---------
+(0 rows)
+
+-- pronargdefaults should be 0 iff proargdefaults is null
+SELECT p1.oid, p1.proname
+FROM pg_proc AS p1
+WHERE (pronargdefaults <> 0) != (proargdefaults IS NOT NULL);
+ oid | proname
+-----+---------
+(0 rows)
+
+-- probin should be non-empty for C functions, null everywhere else
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE prolang = 13 AND (probin IS NULL OR probin = '' OR probin = '-');
+ oid | proname
+-----+---------
+(0 rows)
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE prolang != 13 AND probin IS NOT NULL;
+ oid | proname
+-----+---------
+(0 rows)
+
+-- Look for conflicting proc definitions (same names and input datatypes).
+-- (This test should be dead code now that we have the unique index
+-- pg_proc_proname_args_nsp_index, but I'll leave it in anyway.)
+SELECT p1.oid, p1.proname, p2.oid, p2.proname
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.proname = p2.proname AND
+ p1.pronargs = p2.pronargs AND
+ p1.proargtypes = p2.proargtypes;
+ oid | proname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- Considering only built-in procs (prolang = 12), look for multiple uses
+-- of the same internal function (ie, matching prosrc fields). It's OK to
+-- have several entries with different pronames for the same internal function,
+-- but conflicts in the number of arguments and other critical items should
+-- be complained of. (We don't check data types here; see next query.)
+-- Note: ignore aggregate functions here, since they all point to the same
+-- dummy built-in function.
+SELECT p1.oid, p1.proname, p2.oid, p2.proname
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid < p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ (p1.prokind != 'a' OR p2.prokind != 'a') AND
+ (p1.prolang != p2.prolang OR
+ p1.prokind != p2.prokind OR
+ p1.prosecdef != p2.prosecdef OR
+ p1.proleakproof != p2.proleakproof OR
+ p1.proisstrict != p2.proisstrict OR
+ p1.proretset != p2.proretset OR
+ p1.provolatile != p2.provolatile OR
+ p1.pronargs != p2.pronargs);
+ oid | proname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- Look for uses of different type OIDs in the argument/result type fields
+-- for different aliases of the same built-in function.
+-- This indicates that the types are being presumed to be binary-equivalent,
+-- or that the built-in function is prepared to deal with different types.
+-- That's not wrong, necessarily, but we make lists of all the types being
+-- so treated. Note that the expected output of this part of the test will
+-- need to be modified whenever new pairs of types are made binary-equivalent,
+-- or when new polymorphic built-in functions are added!
+-- Note: ignore aggregate functions here, since they all point to the same
+-- dummy built-in function. Likewise, ignore range and multirange constructor
+-- functions.
+SELECT DISTINCT p1.prorettype::regtype, p2.prorettype::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ p1.prosrc NOT LIKE E'range\\_constructor_' AND
+ p2.prosrc NOT LIKE E'range\\_constructor_' AND
+ p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
+ p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
+ (p1.prorettype < p2.prorettype)
+ORDER BY 1, 2;
+ prorettype | prorettype
+-----------------------------+--------------------------
+ bigint | xid8
+ text | character varying
+ timestamp without time zone | timestamp with time zone
+ txid_snapshot | pg_snapshot
+(4 rows)
+
+SELECT DISTINCT p1.proargtypes[0]::regtype, p2.proargtypes[0]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ p1.prosrc NOT LIKE E'range\\_constructor_' AND
+ p2.prosrc NOT LIKE E'range\\_constructor_' AND
+ p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
+ p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
+ (p1.proargtypes[0] < p2.proargtypes[0])
+ORDER BY 1, 2;
+ proargtypes | proargtypes
+-----------------------------+--------------------------
+ bigint | xid8
+ text | character
+ text | character varying
+ timestamp without time zone | timestamp with time zone
+ bit | bit varying
+ txid_snapshot | pg_snapshot
+(6 rows)
+
+SELECT DISTINCT p1.proargtypes[1]::regtype, p2.proargtypes[1]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ p1.prosrc NOT LIKE E'range\\_constructor_' AND
+ p2.prosrc NOT LIKE E'range\\_constructor_' AND
+ p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
+ p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
+ (p1.proargtypes[1] < p2.proargtypes[1])
+ORDER BY 1, 2;
+ proargtypes | proargtypes
+-----------------------------+--------------------------
+ integer | xid
+ timestamp without time zone | timestamp with time zone
+ bit | bit varying
+ txid_snapshot | pg_snapshot
+(4 rows)
+
+SELECT DISTINCT p1.proargtypes[2]::regtype, p2.proargtypes[2]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ (p1.proargtypes[2] < p2.proargtypes[2])
+ORDER BY 1, 2;
+ proargtypes | proargtypes
+-----------------------------+--------------------------
+ timestamp without time zone | timestamp with time zone
+(1 row)
+
+SELECT DISTINCT p1.proargtypes[3]::regtype, p2.proargtypes[3]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ (p1.proargtypes[3] < p2.proargtypes[3])
+ORDER BY 1, 2;
+ proargtypes | proargtypes
+-----------------------------+--------------------------
+ timestamp without time zone | timestamp with time zone
+(1 row)
+
+SELECT DISTINCT p1.proargtypes[4]::regtype, p2.proargtypes[4]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ (p1.proargtypes[4] < p2.proargtypes[4])
+ORDER BY 1, 2;
+ proargtypes | proargtypes
+-------------+-------------
+(0 rows)
+
+SELECT DISTINCT p1.proargtypes[5]::regtype, p2.proargtypes[5]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ (p1.proargtypes[5] < p2.proargtypes[5])
+ORDER BY 1, 2;
+ proargtypes | proargtypes
+-------------+-------------
+(0 rows)
+
+SELECT DISTINCT p1.proargtypes[6]::regtype, p2.proargtypes[6]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ (p1.proargtypes[6] < p2.proargtypes[6])
+ORDER BY 1, 2;
+ proargtypes | proargtypes
+-------------+-------------
+(0 rows)
+
+SELECT DISTINCT p1.proargtypes[7]::regtype, p2.proargtypes[7]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ (p1.proargtypes[7] < p2.proargtypes[7])
+ORDER BY 1, 2;
+ proargtypes | proargtypes
+-------------+-------------
+(0 rows)
+
+-- Look for functions that return type "internal" and do not have any
+-- "internal" argument. Such a function would be a security hole since
+-- it might be used to call an internal function from an SQL command.
+-- As of 7.3 this query should find only internal_in, which is safe because
+-- it always throws an error when called.
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prorettype = 'internal'::regtype AND NOT
+ 'internal'::regtype = ANY (p1.proargtypes);
+ oid | proname
+------+-------------
+ 2304 | internal_in
+(1 row)
+
+-- Look for functions that return a polymorphic type and do not have any
+-- polymorphic argument. Calls of such functions would be unresolvable
+-- at parse time. As of 9.6 this query should find only some input functions
+-- and GiST support functions associated with these pseudotypes.
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prorettype IN
+ ('anyelement'::regtype, 'anyarray'::regtype, 'anynonarray'::regtype,
+ 'anyenum'::regtype)
+ AND NOT
+ ('anyelement'::regtype = ANY (p1.proargtypes) OR
+ 'anyarray'::regtype = ANY (p1.proargtypes) OR
+ 'anynonarray'::regtype = ANY (p1.proargtypes) OR
+ 'anyenum'::regtype = ANY (p1.proargtypes) OR
+ 'anyrange'::regtype = ANY (p1.proargtypes) OR
+ 'anymultirange'::regtype = ANY (p1.proargtypes))
+ORDER BY 2;
+ oid | proname
+------+----------------
+ 2296 | anyarray_in
+ 2502 | anyarray_recv
+ 2312 | anyelement_in
+ 3504 | anyenum_in
+ 2777 | anynonarray_in
+ 750 | array_in
+ 2400 | array_recv
+ 3506 | enum_in
+ 3532 | enum_recv
+(9 rows)
+
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
+ AND NOT
+ ('anyrange'::regtype = ANY (p1.proargtypes) OR
+ 'anymultirange'::regtype = ANY (p1.proargtypes))
+ORDER BY 2;
+ oid | proname
+------+------------------
+ 4229 | anymultirange_in
+ 3832 | anyrange_in
+ 4231 | multirange_in
+ 4233 | multirange_recv
+ 3876 | range_gist_union
+ 3834 | range_in
+ 3836 | range_recv
+(7 rows)
+
+-- similarly for the anycompatible family
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prorettype IN
+ ('anycompatible'::regtype, 'anycompatiblearray'::regtype,
+ 'anycompatiblenonarray'::regtype)
+ AND NOT
+ ('anycompatible'::regtype = ANY (p1.proargtypes) OR
+ 'anycompatiblearray'::regtype = ANY (p1.proargtypes) OR
+ 'anycompatiblenonarray'::regtype = ANY (p1.proargtypes) OR
+ 'anycompatiblerange'::regtype = ANY (p1.proargtypes))
+ORDER BY 2;
+ oid | proname
+------+--------------------------
+ 5086 | anycompatible_in
+ 5088 | anycompatiblearray_in
+ 5090 | anycompatiblearray_recv
+ 5092 | anycompatiblenonarray_in
+(4 rows)
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prorettype = 'anycompatiblerange'::regtype
+ AND NOT
+ 'anycompatiblerange'::regtype = ANY (p1.proargtypes)
+ORDER BY 2;
+ oid | proname
+------+-----------------------
+ 5094 | anycompatiblerange_in
+(1 row)
+
+-- Look for functions that accept cstring and are neither datatype input
+-- functions nor encoding conversion functions. It's almost never a good
+-- idea to use cstring input for a function meant to be called from SQL;
+-- text should be used instead, because cstring lacks suitable casts.
+-- As of 9.6 this query should find only cstring_out and cstring_send.
+-- However, we must manually exclude shell_in, which might or might not be
+-- rejected by the EXISTS clause depending on whether there are currently
+-- any shell types.
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE 'cstring'::regtype = ANY (p1.proargtypes)
+ AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typinput = p1.oid)
+ AND NOT EXISTS(SELECT 1 FROM pg_conversion WHERE conproc = p1.oid)
+ AND p1.oid != 'shell_in(cstring)'::regprocedure
+ORDER BY 1;
+ oid | proname
+------+--------------
+ 2293 | cstring_out
+ 2501 | cstring_send
+(2 rows)
+
+-- Likewise, look for functions that return cstring and aren't datatype output
+-- functions nor typmod output functions.
+-- As of 9.6 this query should find only cstring_in and cstring_recv.
+-- However, we must manually exclude shell_out.
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prorettype = 'cstring'::regtype
+ AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typoutput = p1.oid)
+ AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typmodout = p1.oid)
+ AND p1.oid != 'shell_out(void)'::regprocedure
+ORDER BY 1;
+ oid | proname
+------+--------------
+ 2292 | cstring_in
+ 2500 | cstring_recv
+(2 rows)
+
+-- Check for length inconsistencies between the various argument-info arrays.
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE proallargtypes IS NOT NULL AND
+ array_length(proallargtypes,1) < array_length(proargtypes,1);
+ oid | proname
+-----+---------
+(0 rows)
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE proargmodes IS NOT NULL AND
+ array_length(proargmodes,1) < array_length(proargtypes,1);
+ oid | proname
+-----+---------
+(0 rows)
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE proargnames IS NOT NULL AND
+ array_length(proargnames,1) < array_length(proargtypes,1);
+ oid | proname
+-----+---------
+(0 rows)
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE proallargtypes IS NOT NULL AND proargmodes IS NOT NULL AND
+ array_length(proallargtypes,1) <> array_length(proargmodes,1);
+ oid | proname
+-----+---------
+(0 rows)
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE proallargtypes IS NOT NULL AND proargnames IS NOT NULL AND
+ array_length(proallargtypes,1) <> array_length(proargnames,1);
+ oid | proname
+-----+---------
+(0 rows)
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE proargmodes IS NOT NULL AND proargnames IS NOT NULL AND
+ array_length(proargmodes,1) <> array_length(proargnames,1);
+ oid | proname
+-----+---------
+(0 rows)
+
+-- Check that proallargtypes matches proargtypes
+SELECT p1.oid, p1.proname, p1.proargtypes, p1.proallargtypes, p1.proargmodes
+FROM pg_proc as p1
+WHERE proallargtypes IS NOT NULL AND
+ ARRAY(SELECT unnest(proargtypes)) <>
+ ARRAY(SELECT proallargtypes[i]
+ FROM generate_series(1, array_length(proallargtypes, 1)) g(i)
+ WHERE proargmodes IS NULL OR proargmodes[i] IN ('i', 'b', 'v'));
+ oid | proname | proargtypes | proallargtypes | proargmodes
+-----+---------+-------------+----------------+-------------
+(0 rows)
+
+-- Check for prosupport functions with the wrong signature
+SELECT p1.oid, p1.proname, p2.oid, p2.proname
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p2.oid = p1.prosupport AND
+ (p2.prorettype != 'internal'::regtype OR p2.proretset OR p2.pronargs != 1
+ OR p2.proargtypes[0] != 'internal'::regtype);
+ oid | proname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- Insist that all built-in pg_proc entries have descriptions
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1 LEFT JOIN pg_description as d
+ ON p1.tableoid = d.classoid and p1.oid = d.objoid and d.objsubid = 0
+WHERE d.classoid IS NULL AND p1.oid <= 9999;
+ oid | proname
+-----+---------
+(0 rows)
+
+-- List of built-in leakproof functions
+--
+-- Leakproof functions should only be added after carefully
+-- scrutinizing all possibly executed codepaths for possible
+-- information leaks. Don't add functions here unless you know what a
+-- leakproof function is. If unsure, don't mark it as such.
+-- temporarily disable fancy output, so catalog changes create less diff noise
+\a\t
+SELECT p1.oid::regprocedure
+FROM pg_proc p1 JOIN pg_namespace pn
+ ON pronamespace = pn.oid
+WHERE nspname = 'pg_catalog' AND proleakproof
+ORDER BY 1;
+boollt(boolean,boolean)
+boolgt(boolean,boolean)
+booleq(boolean,boolean)
+chareq("char","char")
+nameeq(name,name)
+int2eq(smallint,smallint)
+int2lt(smallint,smallint)
+int4eq(integer,integer)
+int4lt(integer,integer)
+texteq(text,text)
+xideq(xid,xid)
+cideq(cid,cid)
+charne("char","char")
+charle("char","char")
+chargt("char","char")
+charge("char","char")
+boolne(boolean,boolean)
+int4ne(integer,integer)
+int2ne(smallint,smallint)
+int2gt(smallint,smallint)
+int4gt(integer,integer)
+int2le(smallint,smallint)
+int4le(integer,integer)
+int4ge(integer,integer)
+int2ge(smallint,smallint)
+textne(text,text)
+int24eq(smallint,integer)
+int42eq(integer,smallint)
+int24lt(smallint,integer)
+int42lt(integer,smallint)
+int24gt(smallint,integer)
+int42gt(integer,smallint)
+int24ne(smallint,integer)
+int42ne(integer,smallint)
+int24le(smallint,integer)
+int42le(integer,smallint)
+int24ge(smallint,integer)
+int42ge(integer,smallint)
+oideq(oid,oid)
+oidne(oid,oid)
+float8(smallint)
+float4(smallint)
+nameeqtext(name,text)
+namelttext(name,text)
+nameletext(name,text)
+namegetext(name,text)
+namegttext(name,text)
+namenetext(name,text)
+btnametextcmp(name,text)
+texteqname(text,name)
+textltname(text,name)
+textlename(text,name)
+textgename(text,name)
+textgtname(text,name)
+textnename(text,name)
+bttextnamecmp(text,name)
+float4eq(real,real)
+float4ne(real,real)
+float4lt(real,real)
+float4le(real,real)
+float4gt(real,real)
+float4ge(real,real)
+float8eq(double precision,double precision)
+float8ne(double precision,double precision)
+float8lt(double precision,double precision)
+float8le(double precision,double precision)
+float8gt(double precision,double precision)
+float8ge(double precision,double precision)
+float48eq(real,double precision)
+float48ne(real,double precision)
+float48lt(real,double precision)
+float48le(real,double precision)
+float48gt(real,double precision)
+float48ge(real,double precision)
+float84eq(double precision,real)
+float84ne(double precision,real)
+float84lt(double precision,real)
+float84le(double precision,real)
+float84gt(double precision,real)
+float84ge(double precision,real)
+float8(real)
+int4(smallint)
+float8(integer)
+float4(integer)
+btint2cmp(smallint,smallint)
+btint4cmp(integer,integer)
+btfloat4cmp(real,real)
+btfloat8cmp(double precision,double precision)
+btoidcmp(oid,oid)
+btcharcmp("char","char")
+btnamecmp(name,name)
+bttextcmp(text,text)
+cash_cmp(money,money)
+btoidvectorcmp(oidvector,oidvector)
+text(name)
+name(text)
+name(character)
+text_larger(text,text)
+text_smaller(text,text)
+int8eq(bigint,bigint)
+int8ne(bigint,bigint)
+int8lt(bigint,bigint)
+int8gt(bigint,bigint)
+int8le(bigint,bigint)
+int8ge(bigint,bigint)
+int84eq(bigint,integer)
+int84ne(bigint,integer)
+int84lt(bigint,integer)
+int84gt(bigint,integer)
+int84le(bigint,integer)
+int84ge(bigint,integer)
+int8(integer)
+float8(bigint)
+oidvectorne(oidvector,oidvector)
+float4(bigint)
+namelt(name,name)
+namele(name,name)
+namegt(name,name)
+namege(name,name)
+namene(name,name)
+oidvectorlt(oidvector,oidvector)
+oidvectorle(oidvector,oidvector)
+oidvectoreq(oidvector,oidvector)
+oidvectorge(oidvector,oidvector)
+oidvectorgt(oidvector,oidvector)
+oidlt(oid,oid)
+oidle(oid,oid)
+text_lt(text,text)
+text_le(text,text)
+text_gt(text,text)
+text_ge(text,text)
+int8(smallint)
+macaddr_eq(macaddr,macaddr)
+macaddr_lt(macaddr,macaddr)
+macaddr_le(macaddr,macaddr)
+macaddr_gt(macaddr,macaddr)
+macaddr_ge(macaddr,macaddr)
+macaddr_ne(macaddr,macaddr)
+macaddr_cmp(macaddr,macaddr)
+btint8cmp(bigint,bigint)
+int48eq(integer,bigint)
+int48ne(integer,bigint)
+int48lt(integer,bigint)
+int48gt(integer,bigint)
+int48le(integer,bigint)
+int48ge(integer,bigint)
+cash_eq(money,money)
+cash_ne(money,money)
+cash_lt(money,money)
+cash_le(money,money)
+cash_gt(money,money)
+cash_ge(money,money)
+network_eq(inet,inet)
+network_lt(inet,inet)
+network_le(inet,inet)
+network_gt(inet,inet)
+network_ge(inet,inet)
+network_ne(inet,inet)
+network_cmp(inet,inet)
+lseg_eq(lseg,lseg)
+bpchareq(character,character)
+bpcharlt(character,character)
+bpcharle(character,character)
+bpchargt(character,character)
+bpcharge(character,character)
+bpcharne(character,character)
+bpchar_larger(character,character)
+bpchar_smaller(character,character)
+bpcharcmp(character,character)
+date_eq(date,date)
+date_lt(date,date)
+date_le(date,date)
+date_gt(date,date)
+date_ge(date,date)
+date_ne(date,date)
+date_cmp(date,date)
+time_lt(time without time zone,time without time zone)
+time_le(time without time zone,time without time zone)
+time_gt(time without time zone,time without time zone)
+time_ge(time without time zone,time without time zone)
+time_ne(time without time zone,time without time zone)
+time_cmp(time without time zone,time without time zone)
+time_eq(time without time zone,time without time zone)
+timestamptz_eq(timestamp with time zone,timestamp with time zone)
+timestamptz_ne(timestamp with time zone,timestamp with time zone)
+timestamptz_lt(timestamp with time zone,timestamp with time zone)
+timestamptz_le(timestamp with time zone,timestamp with time zone)
+timestamptz_ge(timestamp with time zone,timestamp with time zone)
+timestamptz_gt(timestamp with time zone,timestamp with time zone)
+interval_eq(interval,interval)
+interval_ne(interval,interval)
+interval_lt(interval,interval)
+interval_le(interval,interval)
+interval_ge(interval,interval)
+interval_gt(interval,interval)
+charlt("char","char")
+tidne(tid,tid)
+int8(oid)
+tideq(tid,tid)
+timestamptz_cmp(timestamp with time zone,timestamp with time zone)
+interval_cmp(interval,interval)
+xideqint4(xid,integer)
+timetz_eq(time with time zone,time with time zone)
+timetz_ne(time with time zone,time with time zone)
+timetz_lt(time with time zone,time with time zone)
+timetz_le(time with time zone,time with time zone)
+timetz_ge(time with time zone,time with time zone)
+timetz_gt(time with time zone,time with time zone)
+timetz_cmp(time with time zone,time with time zone)
+"interval"(time without time zone)
+name(character varying)
+"varchar"(name)
+circle_eq(circle,circle)
+circle_ne(circle,circle)
+circle_lt(circle,circle)
+circle_gt(circle,circle)
+circle_le(circle,circle)
+circle_ge(circle,circle)
+lseg_ne(lseg,lseg)
+lseg_lt(lseg,lseg)
+lseg_le(lseg,lseg)
+lseg_gt(lseg,lseg)
+lseg_ge(lseg,lseg)
+biteq(bit,bit)
+bitne(bit,bit)
+bitge(bit,bit)
+bitgt(bit,bit)
+bitle(bit,bit)
+bitlt(bit,bit)
+bitcmp(bit,bit)
+oidgt(oid,oid)
+oidge(oid,oid)
+varbiteq(bit varying,bit varying)
+varbitne(bit varying,bit varying)
+varbitge(bit varying,bit varying)
+varbitgt(bit varying,bit varying)
+varbitle(bit varying,bit varying)
+varbitlt(bit varying,bit varying)
+varbitcmp(bit varying,bit varying)
+boolle(boolean,boolean)
+boolge(boolean,boolean)
+btboolcmp(boolean,boolean)
+"numeric"(integer)
+"numeric"(real)
+"numeric"(double precision)
+"numeric"(bigint)
+"numeric"(smallint)
+int28eq(smallint,bigint)
+int28ne(smallint,bigint)
+int28lt(smallint,bigint)
+int28gt(smallint,bigint)
+int28le(smallint,bigint)
+int28ge(smallint,bigint)
+int82eq(bigint,smallint)
+int82ne(bigint,smallint)
+int82lt(bigint,smallint)
+int82gt(bigint,smallint)
+int82le(bigint,smallint)
+int82ge(bigint,smallint)
+byteaeq(bytea,bytea)
+bytealt(bytea,bytea)
+byteale(bytea,bytea)
+byteagt(bytea,bytea)
+byteage(bytea,bytea)
+byteane(bytea,bytea)
+byteacmp(bytea,bytea)
+timestamp_cmp(timestamp without time zone,timestamp without time zone)
+timestamp_eq(timestamp without time zone,timestamp without time zone)
+timestamp_ne(timestamp without time zone,timestamp without time zone)
+timestamp_lt(timestamp without time zone,timestamp without time zone)
+timestamp_le(timestamp without time zone,timestamp without time zone)
+timestamp_ge(timestamp without time zone,timestamp without time zone)
+timestamp_gt(timestamp without time zone,timestamp without time zone)
+text_pattern_lt(text,text)
+text_pattern_le(text,text)
+text_pattern_ge(text,text)
+text_pattern_gt(text,text)
+bttext_pattern_cmp(text,text)
+bpchar_pattern_lt(character,character)
+bpchar_pattern_le(character,character)
+bpchar_pattern_ge(character,character)
+bpchar_pattern_gt(character,character)
+btbpchar_pattern_cmp(character,character)
+btint48cmp(integer,bigint)
+btint84cmp(bigint,integer)
+btint24cmp(smallint,integer)
+btint42cmp(integer,smallint)
+btint28cmp(smallint,bigint)
+btint82cmp(bigint,smallint)
+btfloat48cmp(real,double precision)
+btfloat84cmp(double precision,real)
+md5(text)
+md5(bytea)
+bool(integer)
+int4(boolean)
+tidgt(tid,tid)
+tidlt(tid,tid)
+tidge(tid,tid)
+tidle(tid,tid)
+bttidcmp(tid,tid)
+uuid_lt(uuid,uuid)
+uuid_le(uuid,uuid)
+uuid_eq(uuid,uuid)
+uuid_ge(uuid,uuid)
+uuid_gt(uuid,uuid)
+uuid_ne(uuid,uuid)
+uuid_cmp(uuid,uuid)
+pg_lsn_lt(pg_lsn,pg_lsn)
+pg_lsn_le(pg_lsn,pg_lsn)
+pg_lsn_eq(pg_lsn,pg_lsn)
+pg_lsn_ge(pg_lsn,pg_lsn)
+pg_lsn_gt(pg_lsn,pg_lsn)
+pg_lsn_ne(pg_lsn,pg_lsn)
+pg_lsn_cmp(pg_lsn,pg_lsn)
+xidneq(xid,xid)
+xidneqint4(xid,integer)
+sha224(bytea)
+sha256(bytea)
+sha384(bytea)
+sha512(bytea)
+gen_random_uuid()
+starts_with(text,text)
+macaddr8_eq(macaddr8,macaddr8)
+macaddr8_lt(macaddr8,macaddr8)
+macaddr8_le(macaddr8,macaddr8)
+macaddr8_gt(macaddr8,macaddr8)
+macaddr8_ge(macaddr8,macaddr8)
+macaddr8_ne(macaddr8,macaddr8)
+macaddr8_cmp(macaddr8,macaddr8)
+macaddr8(macaddr)
+xid8lt(xid8,xid8)
+xid8gt(xid8,xid8)
+xid8le(xid8,xid8)
+xid8ge(xid8,xid8)
+xid8eq(xid8,xid8)
+xid8ne(xid8,xid8)
+xid8cmp(xid8,xid8)
+-- restore normal output mode
+\a\t
+-- List of functions used by libpq's fe-lobj.c
+--
+-- If the output of this query changes, you probably broke libpq.
+-- lo_initialize() assumes that there will be at most one match for
+-- each listed name.
+select proname, oid from pg_catalog.pg_proc
+where proname in (
+ 'lo_open',
+ 'lo_close',
+ 'lo_creat',
+ 'lo_create',
+ 'lo_unlink',
+ 'lo_lseek',
+ 'lo_lseek64',
+ 'lo_tell',
+ 'lo_tell64',
+ 'lo_truncate',
+ 'lo_truncate64',
+ 'loread',
+ 'lowrite')
+and pronamespace = (select oid from pg_catalog.pg_namespace
+ where nspname = 'pg_catalog')
+order by 1;
+ proname | oid
+---------------+------
+ lo_close | 953
+ lo_creat | 957
+ lo_create | 715
+ lo_lseek | 956
+ lo_lseek64 | 3170
+ lo_open | 952
+ lo_tell | 958
+ lo_tell64 | 3171
+ lo_truncate | 1004
+ lo_truncate64 | 3172
+ lo_unlink | 964
+ loread | 954
+ lowrite | 955
+(13 rows)
+
+-- Check that all immutable functions are marked parallel safe
+SELECT p1.oid, p1.proname
+FROM pg_proc AS p1
+WHERE provolatile = 'i' AND proparallel = 'u';
+ oid | proname
+-----+---------
+(0 rows)
+
+-- **************** pg_cast ****************
+-- Catch bogus values in pg_cast columns (other than cases detected by
+-- oidjoins test).
+SELECT *
+FROM pg_cast c
+WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
+ OR castmethod NOT IN ('f', 'b' ,'i');
+ oid | castsource | casttarget | castfunc | castcontext | castmethod
+-----+------------+------------+----------+-------------+------------
+(0 rows)
+
+-- Check that castfunc is nonzero only for cast methods that need a function,
+-- and zero otherwise
+SELECT *
+FROM pg_cast c
+WHERE (castmethod = 'f' AND castfunc = 0)
+ OR (castmethod IN ('b', 'i') AND castfunc <> 0);
+ oid | castsource | casttarget | castfunc | castcontext | castmethod
+-----+------------+------------+----------+-------------+------------
+(0 rows)
+
+-- Look for casts to/from the same type that aren't length coercion functions.
+-- (We assume they are length coercions if they take multiple arguments.)
+-- Such entries are not necessarily harmful, but they are useless.
+SELECT *
+FROM pg_cast c
+WHERE castsource = casttarget AND castfunc = 0;
+ oid | castsource | casttarget | castfunc | castcontext | castmethod
+-----+------------+------------+----------+-------------+------------
+(0 rows)
+
+SELECT c.*
+FROM pg_cast c, pg_proc p
+WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
+ oid | castsource | casttarget | castfunc | castcontext | castmethod
+-----+------------+------------+----------+-------------+------------
+(0 rows)
+
+-- Look for cast functions that don't have the right signature. The
+-- argument and result types in pg_proc must be the same as, or binary
+-- compatible with, what it says in pg_cast.
+-- As a special case, we allow casts from CHAR(n) that use functions
+-- declared to take TEXT. This does not pass the binary-coercibility test
+-- because CHAR(n)-to-TEXT normally invokes rtrim(). However, the results
+-- are the same, so long as the function is one that ignores trailing blanks.
+SELECT c.*
+FROM pg_cast c, pg_proc p
+WHERE c.castfunc = p.oid AND
+ (p.pronargs < 1 OR p.pronargs > 3
+ OR NOT (binary_coercible(c.castsource, p.proargtypes[0])
+ OR (c.castsource = 'character'::regtype AND
+ p.proargtypes[0] = 'text'::regtype))
+ OR NOT binary_coercible(p.prorettype, c.casttarget));
+ oid | castsource | casttarget | castfunc | castcontext | castmethod
+-----+------------+------------+----------+-------------+------------
+(0 rows)
+
+SELECT c.*
+FROM pg_cast c, pg_proc p
+WHERE c.castfunc = p.oid AND
+ ((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
+ (p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
+ oid | castsource | casttarget | castfunc | castcontext | castmethod
+-----+------------+------------+----------+-------------+------------
+(0 rows)
+
+-- Look for binary compatible casts that do not have the reverse
+-- direction registered as well, or where the reverse direction is not
+-- also binary compatible. This is legal, but usually not intended.
+-- As of 7.4, this finds the casts from text and varchar to bpchar, because
+-- those are binary-compatible while the reverse way goes through rtrim().
+-- As of 8.2, this finds the cast from cidr to inet, because that is a
+-- trivial binary coercion while the other way goes through inet_to_cidr().
+-- As of 8.3, this finds the casts from xml to text, varchar, and bpchar,
+-- because those are binary-compatible while the reverse goes through
+-- texttoxml(), which does an XML syntax check.
+-- As of 9.1, this finds the cast from pg_node_tree to text, which we
+-- intentionally do not provide a reverse pathway for.
+SELECT castsource::regtype, casttarget::regtype, castfunc, castcontext
+FROM pg_cast c
+WHERE c.castmethod = 'b' AND
+ NOT EXISTS (SELECT 1 FROM pg_cast k
+ WHERE k.castmethod = 'b' AND
+ k.castsource = c.casttarget AND
+ k.casttarget = c.castsource);
+ castsource | casttarget | castfunc | castcontext
+-------------------+-------------------+----------+-------------
+ text | character | 0 | i
+ character varying | character | 0 | i
+ pg_node_tree | text | 0 | i
+ pg_ndistinct | bytea | 0 | i
+ pg_dependencies | bytea | 0 | i
+ pg_mcv_list | bytea | 0 | i
+ cidr | inet | 0 | i
+ xml | text | 0 | a
+ xml | character varying | 0 | a
+ xml | character | 0 | a
+(10 rows)
+
+-- **************** pg_conversion ****************
+-- Look for illegal values in pg_conversion fields.
+SELECT p1.oid, p1.conname
+FROM pg_conversion as p1
+WHERE p1.conproc = 0 OR
+ pg_encoding_to_char(conforencoding) = '' OR
+ pg_encoding_to_char(contoencoding) = '';
+ oid | conname
+-----+---------
+(0 rows)
+
+-- Look for conprocs that don't have the expected signature.
+SELECT p.oid, p.proname, c.oid, c.conname
+FROM pg_proc p, pg_conversion c
+WHERE p.oid = c.conproc AND
+ (p.prorettype != 'int4'::regtype OR p.proretset OR
+ p.pronargs != 6 OR
+ p.proargtypes[0] != 'int4'::regtype OR
+ p.proargtypes[1] != 'int4'::regtype OR
+ p.proargtypes[2] != 'cstring'::regtype OR
+ p.proargtypes[3] != 'internal'::regtype OR
+ p.proargtypes[4] != 'int4'::regtype OR
+ p.proargtypes[5] != 'bool'::regtype);
+ oid | proname | oid | conname
+-----+---------+-----+---------
+(0 rows)
+
+-- Check for conprocs that don't perform the specific conversion that
+-- pg_conversion alleges they do, by trying to invoke each conversion
+-- on some simple ASCII data. (The conproc should throw an error if
+-- it doesn't accept the encodings that are passed to it.)
+-- Unfortunately, we can't test non-default conprocs this way, because
+-- there is no way to ask convert() to invoke them, and we cannot call
+-- them directly from SQL. But there are no non-default built-in
+-- conversions anyway.
+-- (Similarly, this doesn't cope with any search path issues.)
+SELECT p1.oid, p1.conname
+FROM pg_conversion as p1
+WHERE condefault AND
+ convert('ABC'::bytea, pg_encoding_to_char(conforencoding),
+ pg_encoding_to_char(contoencoding)) != 'ABC';
+ oid | conname
+-----+---------
+(0 rows)
+
+-- **************** pg_operator ****************
+-- Look for illegal values in pg_operator fields.
+SELECT p1.oid, p1.oprname
+FROM pg_operator as p1
+WHERE (p1.oprkind != 'b' AND p1.oprkind != 'l') OR
+ p1.oprresult = 0 OR p1.oprcode = 0;
+ oid | oprname
+-----+---------
+(0 rows)
+
+-- Look for missing or unwanted operand types
+SELECT p1.oid, p1.oprname
+FROM pg_operator as p1
+WHERE (p1.oprleft = 0 and p1.oprkind != 'l') OR
+ (p1.oprleft != 0 and p1.oprkind = 'l') OR
+ p1.oprright = 0;
+ oid | oprname
+-----+---------
+(0 rows)
+
+-- Look for conflicting operator definitions (same names and input datatypes).
+SELECT p1.oid, p1.oprcode, p2.oid, p2.oprcode
+FROM pg_operator AS p1, pg_operator AS p2
+WHERE p1.oid != p2.oid AND
+ p1.oprname = p2.oprname AND
+ p1.oprkind = p2.oprkind AND
+ p1.oprleft = p2.oprleft AND
+ p1.oprright = p2.oprright;
+ oid | oprcode | oid | oprcode
+-----+---------+-----+---------
+(0 rows)
+
+-- Look for commutative operators that don't commute.
+-- DEFINITIONAL NOTE: If A.oprcom = B, then x A y has the same result as y B x.
+-- We expect that B will always say that B.oprcom = A as well; that's not
+-- inherently essential, but it would be inefficient not to mark it so.
+SELECT p1.oid, p1.oprcode, p2.oid, p2.oprcode
+FROM pg_operator AS p1, pg_operator AS p2
+WHERE p1.oprcom = p2.oid AND
+ (p1.oprkind != 'b' OR
+ p1.oprleft != p2.oprright OR
+ p1.oprright != p2.oprleft OR
+ p1.oprresult != p2.oprresult OR
+ p1.oid != p2.oprcom);
+ oid | oprcode | oid | oprcode
+-----+---------+-----+---------
+(0 rows)
+
+-- Look for negatory operators that don't agree.
+-- DEFINITIONAL NOTE: If A.oprnegate = B, then both A and B must yield
+-- boolean results, and (x A y) == ! (x B y), or the equivalent for
+-- single-operand operators.
+-- We expect that B will always say that B.oprnegate = A as well; that's not
+-- inherently essential, but it would be inefficient not to mark it so.
+-- Also, A and B had better not be the same operator.
+SELECT p1.oid, p1.oprcode, p2.oid, p2.oprcode
+FROM pg_operator AS p1, pg_operator AS p2
+WHERE p1.oprnegate = p2.oid AND
+ (p1.oprkind != p2.oprkind OR
+ p1.oprleft != p2.oprleft OR
+ p1.oprright != p2.oprright OR
+ p1.oprresult != 'bool'::regtype OR
+ p2.oprresult != 'bool'::regtype OR
+ p1.oid != p2.oprnegate OR
+ p1.oid = p2.oid);
+ oid | oprcode | oid | oprcode
+-----+---------+-----+---------
+(0 rows)
+
+-- Make a list of the names of operators that are claimed to be commutator
+-- pairs. This list will grow over time, but before accepting a new entry
+-- make sure you didn't link the wrong operators.
+SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
+FROM pg_operator o1, pg_operator o2
+WHERE o1.oprcom = o2.oid AND o1.oprname <= o2.oprname
+ORDER BY 1, 2;
+ op1 | op2
+------+------
+ # | #
+ & | &
+ && | &&
+ * | *
+ *< | *>
+ *<= | *>=
+ *<> | *<>
+ *= | *=
+ + | +
+ -|- | -|-
+ < | >
+ <-> | <->
+ << | >>
+ <<= | >>=
+ <= | >=
+ <> | <>
+ <@ | @>
+ = | =
+ ?# | ?#
+ ?- | ?-
+ ?-| | ?-|
+ ?| | ?|
+ ?|| | ?||
+ @@ | @@
+ @@@ | @@@
+ | | |
+ ~<=~ | ~>=~
+ ~<~ | ~>~
+ ~= | ~=
+(29 rows)
+
+-- Likewise for negator pairs.
+SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
+FROM pg_operator o1, pg_operator o2
+WHERE o1.oprnegate = o2.oid AND o1.oprname <= o2.oprname
+ORDER BY 1, 2;
+ op1 | op2
+------+------
+ !~ | ~
+ !~* | ~*
+ !~~ | ~~
+ !~~* | ~~*
+ *< | *>=
+ *<= | *>
+ *<> | *=
+ < | >=
+ <= | >
+ <> | =
+ <> | ~=
+ ~<=~ | ~>~
+ ~<~ | ~>=~
+(13 rows)
+
+-- A mergejoinable or hashjoinable operator must be binary, must return
+-- boolean, and must have a commutator (itself, unless it's a cross-type
+-- operator).
+SELECT p1.oid, p1.oprname FROM pg_operator AS p1
+WHERE (p1.oprcanmerge OR p1.oprcanhash) AND NOT
+ (p1.oprkind = 'b' AND p1.oprresult = 'bool'::regtype AND p1.oprcom != 0);
+ oid | oprname
+-----+---------
+(0 rows)
+
+-- What's more, the commutator had better be mergejoinable/hashjoinable too.
+SELECT p1.oid, p1.oprname, p2.oid, p2.oprname
+FROM pg_operator AS p1, pg_operator AS p2
+WHERE p1.oprcom = p2.oid AND
+ (p1.oprcanmerge != p2.oprcanmerge OR
+ p1.oprcanhash != p2.oprcanhash);
+ oid | oprname | oid | oprname
+-----+---------+-----+---------
+(0 rows)
+
+-- Mergejoinable operators should appear as equality members of btree index
+-- opfamilies.
+SELECT p1.oid, p1.oprname
+FROM pg_operator AS p1
+WHERE p1.oprcanmerge AND NOT EXISTS
+ (SELECT 1 FROM pg_amop
+ WHERE amopmethod = (SELECT oid FROM pg_am WHERE amname = 'btree') AND
+ amopopr = p1.oid AND amopstrategy = 3);
+ oid | oprname
+-----+---------
+(0 rows)
+
+-- And the converse.
+SELECT p1.oid, p1.oprname, p.amopfamily
+FROM pg_operator AS p1, pg_amop p
+WHERE amopopr = p1.oid
+ AND amopmethod = (SELECT oid FROM pg_am WHERE amname = 'btree')
+ AND amopstrategy = 3
+ AND NOT p1.oprcanmerge;
+ oid | oprname | amopfamily
+-----+---------+------------
+(0 rows)
+
+-- Hashable operators should appear as members of hash index opfamilies.
+SELECT p1.oid, p1.oprname
+FROM pg_operator AS p1
+WHERE p1.oprcanhash AND NOT EXISTS
+ (SELECT 1 FROM pg_amop
+ WHERE amopmethod = (SELECT oid FROM pg_am WHERE amname = 'hash') AND
+ amopopr = p1.oid AND amopstrategy = 1);
+ oid | oprname
+-----+---------
+(0 rows)
+
+-- And the converse.
+SELECT p1.oid, p1.oprname, p.amopfamily
+FROM pg_operator AS p1, pg_amop p
+WHERE amopopr = p1.oid
+ AND amopmethod = (SELECT oid FROM pg_am WHERE amname = 'hash')
+ AND NOT p1.oprcanhash;
+ oid | oprname | amopfamily
+-----+---------+------------
+(0 rows)
+
+-- Check that each operator defined in pg_operator matches its oprcode entry
+-- in pg_proc. Easiest to do this separately for each oprkind.
+SELECT p1.oid, p1.oprname, p2.oid, p2.proname
+FROM pg_operator AS p1, pg_proc AS p2
+WHERE p1.oprcode = p2.oid AND
+ p1.oprkind = 'b' AND
+ (p2.pronargs != 2
+ OR NOT binary_coercible(p2.prorettype, p1.oprresult)
+ OR NOT binary_coercible(p1.oprleft, p2.proargtypes[0])
+ OR NOT binary_coercible(p1.oprright, p2.proargtypes[1]));
+ oid | oprname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+SELECT p1.oid, p1.oprname, p2.oid, p2.proname
+FROM pg_operator AS p1, pg_proc AS p2
+WHERE p1.oprcode = p2.oid AND
+ p1.oprkind = 'l' AND
+ (p2.pronargs != 1
+ OR NOT binary_coercible(p2.prorettype, p1.oprresult)
+ OR NOT binary_coercible(p1.oprright, p2.proargtypes[0])
+ OR p1.oprleft != 0);
+ oid | oprname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- If the operator is mergejoinable or hashjoinable, its underlying function
+-- should not be volatile.
+SELECT p1.oid, p1.oprname, p2.oid, p2.proname
+FROM pg_operator AS p1, pg_proc AS p2
+WHERE p1.oprcode = p2.oid AND
+ (p1.oprcanmerge OR p1.oprcanhash) AND
+ p2.provolatile = 'v';
+ oid | oprname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- If oprrest is set, the operator must return boolean,
+-- and it must link to a proc with the right signature
+-- to be a restriction selectivity estimator.
+-- The proc signature we want is: float8 proc(internal, oid, internal, int4)
+SELECT p1.oid, p1.oprname, p2.oid, p2.proname
+FROM pg_operator AS p1, pg_proc AS p2
+WHERE p1.oprrest = p2.oid AND
+ (p1.oprresult != 'bool'::regtype OR
+ p2.prorettype != 'float8'::regtype OR p2.proretset OR
+ p2.pronargs != 4 OR
+ p2.proargtypes[0] != 'internal'::regtype OR
+ p2.proargtypes[1] != 'oid'::regtype OR
+ p2.proargtypes[2] != 'internal'::regtype OR
+ p2.proargtypes[3] != 'int4'::regtype);
+ oid | oprname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- If oprjoin is set, the operator must be a binary boolean op,
+-- and it must link to a proc with the right signature
+-- to be a join selectivity estimator.
+-- The proc signature we want is: float8 proc(internal, oid, internal, int2, internal)
+-- (Note: the old signature with only 4 args is still allowed, but no core
+-- estimator should be using it.)
+SELECT p1.oid, p1.oprname, p2.oid, p2.proname
+FROM pg_operator AS p1, pg_proc AS p2
+WHERE p1.oprjoin = p2.oid AND
+ (p1.oprkind != 'b' OR p1.oprresult != 'bool'::regtype OR
+ p2.prorettype != 'float8'::regtype OR p2.proretset OR
+ p2.pronargs != 5 OR
+ p2.proargtypes[0] != 'internal'::regtype OR
+ p2.proargtypes[1] != 'oid'::regtype OR
+ p2.proargtypes[2] != 'internal'::regtype OR
+ p2.proargtypes[3] != 'int2'::regtype OR
+ p2.proargtypes[4] != 'internal'::regtype);
+ oid | oprname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- Insist that all built-in pg_operator entries have descriptions
+SELECT p1.oid, p1.oprname
+FROM pg_operator as p1 LEFT JOIN pg_description as d
+ ON p1.tableoid = d.classoid and p1.oid = d.objoid and d.objsubid = 0
+WHERE d.classoid IS NULL AND p1.oid <= 9999;
+ oid | oprname
+-----+---------
+(0 rows)
+
+-- Check that operators' underlying functions have suitable comments,
+-- namely 'implementation of XXX operator'. (Note: it's not necessary to
+-- put such comments into pg_proc.dat; initdb will generate them as needed.)
+-- In some cases involving legacy names for operators, there are multiple
+-- operators referencing the same pg_proc entry, so ignore operators whose
+-- comments say they are deprecated.
+-- We also have a few functions that are both operator support and meant to
+-- be called directly; those should have comments matching their operator.
+WITH funcdescs AS (
+ SELECT p.oid as p_oid, proname, o.oid as o_oid,
+ pd.description as prodesc,
+ 'implementation of ' || oprname || ' operator' as expecteddesc,
+ od.description as oprdesc
+ FROM pg_proc p JOIN pg_operator o ON oprcode = p.oid
+ LEFT JOIN pg_description pd ON
+ (pd.objoid = p.oid and pd.classoid = p.tableoid and pd.objsubid = 0)
+ LEFT JOIN pg_description od ON
+ (od.objoid = o.oid and od.classoid = o.tableoid and od.objsubid = 0)
+ WHERE o.oid <= 9999
+)
+SELECT * FROM funcdescs
+ WHERE prodesc IS DISTINCT FROM expecteddesc
+ AND oprdesc NOT LIKE 'deprecated%'
+ AND prodesc IS DISTINCT FROM oprdesc;
+ p_oid | proname | o_oid | prodesc | expecteddesc | oprdesc
+-------+---------+-------+---------+--------------+---------
+(0 rows)
+
+-- Show all the operator-implementation functions that have their own
+-- comments. This should happen only in cases where the function and
+-- operator syntaxes are both documented at the user level.
+-- This should be a pretty short list; it's mostly legacy cases.
+WITH funcdescs AS (
+ SELECT p.oid as p_oid, proname, o.oid as o_oid,
+ pd.description as prodesc,
+ 'implementation of ' || oprname || ' operator' as expecteddesc,
+ od.description as oprdesc
+ FROM pg_proc p JOIN pg_operator o ON oprcode = p.oid
+ LEFT JOIN pg_description pd ON
+ (pd.objoid = p.oid and pd.classoid = p.tableoid and pd.objsubid = 0)
+ LEFT JOIN pg_description od ON
+ (od.objoid = o.oid and od.classoid = o.tableoid and od.objsubid = 0)
+ WHERE o.oid <= 9999
+)
+SELECT p_oid, proname, prodesc FROM funcdescs
+ WHERE prodesc IS DISTINCT FROM expecteddesc
+ AND oprdesc NOT LIKE 'deprecated%'
+ORDER BY 1;
+ p_oid | proname | prodesc
+-------+-------------------------+-------------------------------------------------
+ 378 | array_append | append element onto end of array
+ 379 | array_prepend | prepend element onto front of array
+ 1035 | aclinsert | add/update ACL item
+ 1036 | aclremove | remove ACL item
+ 1037 | aclcontains | contains
+ 3217 | jsonb_extract_path | get value from jsonb with path elements
+ 3940 | jsonb_extract_path_text | get value from jsonb as text with path elements
+ 3951 | json_extract_path | get value from json with path elements
+ 3953 | json_extract_path_text | get value from json as text with path elements
+(9 rows)
+
+-- Operators that are commutator pairs should have identical volatility
+-- and leakproofness markings on their implementation functions.
+SELECT o1.oid, o1.oprcode, o2.oid, o2.oprcode
+FROM pg_operator AS o1, pg_operator AS o2, pg_proc AS p1, pg_proc AS p2
+WHERE o1.oprcom = o2.oid AND p1.oid = o1.oprcode AND p2.oid = o2.oprcode AND
+ (p1.provolatile != p2.provolatile OR
+ p1.proleakproof != p2.proleakproof);
+ oid | oprcode | oid | oprcode
+-----+---------+-----+---------
+(0 rows)
+
+-- Likewise for negator pairs.
+SELECT o1.oid, o1.oprcode, o2.oid, o2.oprcode
+FROM pg_operator AS o1, pg_operator AS o2, pg_proc AS p1, pg_proc AS p2
+WHERE o1.oprnegate = o2.oid AND p1.oid = o1.oprcode AND p2.oid = o2.oprcode AND
+ (p1.provolatile != p2.provolatile OR
+ p1.proleakproof != p2.proleakproof);
+ oid | oprcode | oid | oprcode
+-----+---------+-----+---------
+(0 rows)
+
+-- Btree comparison operators' functions should have the same volatility
+-- and leakproofness markings as the associated comparison support function.
+SELECT pp.oid::regprocedure as proc, pp.provolatile as vp, pp.proleakproof as lp,
+ po.oid::regprocedure as opr, po.provolatile as vo, po.proleakproof as lo
+FROM pg_proc pp, pg_proc po, pg_operator o, pg_amproc ap, pg_amop ao
+WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
+ ao.amopmethod = (SELECT oid FROM pg_am WHERE amname = 'btree') AND
+ ao.amopfamily = ap.amprocfamily AND
+ ao.amoplefttype = ap.amproclefttype AND
+ ao.amoprighttype = ap.amprocrighttype AND
+ ap.amprocnum = 1 AND
+ (pp.provolatile != po.provolatile OR
+ pp.proleakproof != po.proleakproof)
+ORDER BY 1;
+ proc | vp | lp | opr | vo | lo
+------+----+----+-----+----+----
+(0 rows)
+
+-- **************** pg_aggregate ****************
+-- Look for illegal values in pg_aggregate fields.
+SELECT ctid, aggfnoid::oid
+FROM pg_aggregate as p1
+WHERE aggfnoid = 0 OR aggtransfn = 0 OR
+ aggkind NOT IN ('n', 'o', 'h') OR
+ aggnumdirectargs < 0 OR
+ (aggkind = 'n' AND aggnumdirectargs > 0) OR
+ aggfinalmodify NOT IN ('r', 's', 'w') OR
+ aggmfinalmodify NOT IN ('r', 's', 'w') OR
+ aggtranstype = 0 OR aggtransspace < 0 OR aggmtransspace < 0;
+ ctid | aggfnoid
+------+----------
+(0 rows)
+
+-- Make sure the matching pg_proc entry is sensible, too.
+SELECT a.aggfnoid::oid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggfnoid = p.oid AND
+ (p.prokind != 'a' OR p.proretset OR p.pronargs < a.aggnumdirectargs);
+ aggfnoid | proname
+----------+---------
+(0 rows)
+
+-- Make sure there are no prokind = PROKIND_AGGREGATE pg_proc entries without matches.
+SELECT oid, proname
+FROM pg_proc as p
+WHERE p.prokind = 'a' AND
+ NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid);
+ oid | proname
+-----+---------
+(0 rows)
+
+-- If there is no finalfn then the output type must be the transtype.
+SELECT a.aggfnoid::oid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggfnoid = p.oid AND
+ a.aggfinalfn = 0 AND p.prorettype != a.aggtranstype;
+ aggfnoid | proname
+----------+---------
+(0 rows)
+
+-- Cross-check transfn against its entry in pg_proc.
+SELECT a.aggfnoid::oid, p.proname, ptr.oid, ptr.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr
+WHERE a.aggfnoid = p.oid AND
+ a.aggtransfn = ptr.oid AND
+ (ptr.proretset
+ OR NOT (ptr.pronargs =
+ CASE WHEN a.aggkind = 'n' THEN p.pronargs + 1
+ ELSE greatest(p.pronargs - a.aggnumdirectargs, 1) + 1 END)
+ OR NOT binary_coercible(ptr.prorettype, a.aggtranstype)
+ OR NOT binary_coercible(a.aggtranstype, ptr.proargtypes[0])
+ OR (p.pronargs > 0 AND
+ NOT binary_coercible(p.proargtypes[0], ptr.proargtypes[1]))
+ OR (p.pronargs > 1 AND
+ NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2]))
+ OR (p.pronargs > 2 AND
+ NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3]))
+ -- we could carry the check further, but 3 args is enough for now
+ OR (p.pronargs > 3)
+ );
+ aggfnoid | proname | oid | proname
+----------+---------+-----+---------
+(0 rows)
+
+-- Cross-check finalfn (if present) against its entry in pg_proc.
+SELECT a.aggfnoid::oid, p.proname, pfn.oid, pfn.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS pfn
+WHERE a.aggfnoid = p.oid AND
+ a.aggfinalfn = pfn.oid AND
+ (pfn.proretset OR
+ NOT binary_coercible(pfn.prorettype, p.prorettype) OR
+ NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]) OR
+ CASE WHEN a.aggfinalextra THEN pfn.pronargs != p.pronargs + 1
+ ELSE pfn.pronargs != a.aggnumdirectargs + 1 END
+ OR (pfn.pronargs > 1 AND
+ NOT binary_coercible(p.proargtypes[0], pfn.proargtypes[1]))
+ OR (pfn.pronargs > 2 AND
+ NOT binary_coercible(p.proargtypes[1], pfn.proargtypes[2]))
+ OR (pfn.pronargs > 3 AND
+ NOT binary_coercible(p.proargtypes[2], pfn.proargtypes[3]))
+ -- we could carry the check further, but 4 args is enough for now
+ OR (pfn.pronargs > 4)
+ );
+ aggfnoid | proname | oid | proname
+----------+---------+-----+---------
+(0 rows)
+
+-- If transfn is strict then either initval should be non-NULL, or
+-- input type should match transtype so that the first non-null input
+-- can be assigned as the state value.
+SELECT a.aggfnoid::oid, p.proname, ptr.oid, ptr.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr
+WHERE a.aggfnoid = p.oid AND
+ a.aggtransfn = ptr.oid AND ptr.proisstrict AND
+ a.agginitval IS NULL AND
+ NOT binary_coercible(p.proargtypes[0], a.aggtranstype);
+ aggfnoid | proname | oid | proname
+----------+---------+-----+---------
+(0 rows)
+
+-- Check for inconsistent specifications of moving-aggregate columns.
+SELECT ctid, aggfnoid::oid
+FROM pg_aggregate as p1
+WHERE aggmtranstype != 0 AND
+ (aggmtransfn = 0 OR aggminvtransfn = 0);
+ ctid | aggfnoid
+------+----------
+(0 rows)
+
+SELECT ctid, aggfnoid::oid
+FROM pg_aggregate as p1
+WHERE aggmtranstype = 0 AND
+ (aggmtransfn != 0 OR aggminvtransfn != 0 OR aggmfinalfn != 0 OR
+ aggmtransspace != 0 OR aggminitval IS NOT NULL);
+ ctid | aggfnoid
+------+----------
+(0 rows)
+
+-- If there is no mfinalfn then the output type must be the mtranstype.
+SELECT a.aggfnoid::oid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggfnoid = p.oid AND
+ a.aggmtransfn != 0 AND
+ a.aggmfinalfn = 0 AND p.prorettype != a.aggmtranstype;
+ aggfnoid | proname
+----------+---------
+(0 rows)
+
+-- Cross-check mtransfn (if present) against its entry in pg_proc.
+SELECT a.aggfnoid::oid, p.proname, ptr.oid, ptr.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr
+WHERE a.aggfnoid = p.oid AND
+ a.aggmtransfn = ptr.oid AND
+ (ptr.proretset
+ OR NOT (ptr.pronargs =
+ CASE WHEN a.aggkind = 'n' THEN p.pronargs + 1
+ ELSE greatest(p.pronargs - a.aggnumdirectargs, 1) + 1 END)
+ OR NOT binary_coercible(ptr.prorettype, a.aggmtranstype)
+ OR NOT binary_coercible(a.aggmtranstype, ptr.proargtypes[0])
+ OR (p.pronargs > 0 AND
+ NOT binary_coercible(p.proargtypes[0], ptr.proargtypes[1]))
+ OR (p.pronargs > 1 AND
+ NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2]))
+ OR (p.pronargs > 2 AND
+ NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3]))
+ -- we could carry the check further, but 3 args is enough for now
+ OR (p.pronargs > 3)
+ );
+ aggfnoid | proname | oid | proname
+----------+---------+-----+---------
+(0 rows)
+
+-- Cross-check minvtransfn (if present) against its entry in pg_proc.
+SELECT a.aggfnoid::oid, p.proname, ptr.oid, ptr.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr
+WHERE a.aggfnoid = p.oid AND
+ a.aggminvtransfn = ptr.oid AND
+ (ptr.proretset
+ OR NOT (ptr.pronargs =
+ CASE WHEN a.aggkind = 'n' THEN p.pronargs + 1
+ ELSE greatest(p.pronargs - a.aggnumdirectargs, 1) + 1 END)
+ OR NOT binary_coercible(ptr.prorettype, a.aggmtranstype)
+ OR NOT binary_coercible(a.aggmtranstype, ptr.proargtypes[0])
+ OR (p.pronargs > 0 AND
+ NOT binary_coercible(p.proargtypes[0], ptr.proargtypes[1]))
+ OR (p.pronargs > 1 AND
+ NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2]))
+ OR (p.pronargs > 2 AND
+ NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3]))
+ -- we could carry the check further, but 3 args is enough for now
+ OR (p.pronargs > 3)
+ );
+ aggfnoid | proname | oid | proname
+----------+---------+-----+---------
+(0 rows)
+
+-- Cross-check mfinalfn (if present) against its entry in pg_proc.
+SELECT a.aggfnoid::oid, p.proname, pfn.oid, pfn.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS pfn
+WHERE a.aggfnoid = p.oid AND
+ a.aggmfinalfn = pfn.oid AND
+ (pfn.proretset OR
+ NOT binary_coercible(pfn.prorettype, p.prorettype) OR
+ NOT binary_coercible(a.aggmtranstype, pfn.proargtypes[0]) OR
+ CASE WHEN a.aggmfinalextra THEN pfn.pronargs != p.pronargs + 1
+ ELSE pfn.pronargs != a.aggnumdirectargs + 1 END
+ OR (pfn.pronargs > 1 AND
+ NOT binary_coercible(p.proargtypes[0], pfn.proargtypes[1]))
+ OR (pfn.pronargs > 2 AND
+ NOT binary_coercible(p.proargtypes[1], pfn.proargtypes[2]))
+ OR (pfn.pronargs > 3 AND
+ NOT binary_coercible(p.proargtypes[2], pfn.proargtypes[3]))
+ -- we could carry the check further, but 4 args is enough for now
+ OR (pfn.pronargs > 4)
+ );
+ aggfnoid | proname | oid | proname
+----------+---------+-----+---------
+(0 rows)
+
+-- If mtransfn is strict then either minitval should be non-NULL, or
+-- input type should match mtranstype so that the first non-null input
+-- can be assigned as the state value.
+SELECT a.aggfnoid::oid, p.proname, ptr.oid, ptr.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr
+WHERE a.aggfnoid = p.oid AND
+ a.aggmtransfn = ptr.oid AND ptr.proisstrict AND
+ a.aggminitval IS NULL AND
+ NOT binary_coercible(p.proargtypes[0], a.aggmtranstype);
+ aggfnoid | proname | oid | proname
+----------+---------+-----+---------
+(0 rows)
+
+-- mtransfn and minvtransfn should have same strictness setting.
+SELECT a.aggfnoid::oid, p.proname, ptr.oid, ptr.proname, iptr.oid, iptr.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr, pg_proc AS iptr
+WHERE a.aggfnoid = p.oid AND
+ a.aggmtransfn = ptr.oid AND
+ a.aggminvtransfn = iptr.oid AND
+ ptr.proisstrict != iptr.proisstrict;
+ aggfnoid | proname | oid | proname | oid | proname
+----------+---------+-----+---------+-----+---------
+(0 rows)
+
+-- Check that all combine functions have signature
+-- combine(transtype, transtype) returns transtype
+SELECT a.aggfnoid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggcombinefn = p.oid AND
+ (p.pronargs != 2 OR
+ p.prorettype != p.proargtypes[0] OR
+ p.prorettype != p.proargtypes[1] OR
+ NOT binary_coercible(a.aggtranstype, p.proargtypes[0]));
+ aggfnoid | proname
+----------+---------
+(0 rows)
+
+-- Check that no combine function for an INTERNAL transtype is strict.
+SELECT a.aggfnoid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggcombinefn = p.oid AND
+ a.aggtranstype = 'internal'::regtype AND p.proisstrict;
+ aggfnoid | proname
+----------+---------
+(0 rows)
+
+-- serialize/deserialize functions should be specified only for aggregates
+-- with transtype internal and a combine function, and we should have both
+-- or neither of them.
+SELECT aggfnoid, aggtranstype, aggserialfn, aggdeserialfn
+FROM pg_aggregate
+WHERE (aggserialfn != 0 OR aggdeserialfn != 0)
+ AND (aggtranstype != 'internal'::regtype OR aggcombinefn = 0 OR
+ aggserialfn = 0 OR aggdeserialfn = 0);
+ aggfnoid | aggtranstype | aggserialfn | aggdeserialfn
+----------+--------------+-------------+---------------
+(0 rows)
+
+-- Check that all serialization functions have signature
+-- serialize(internal) returns bytea
+-- Also insist that they be strict; it's wasteful to run them on NULLs.
+SELECT a.aggfnoid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggserialfn = p.oid AND
+ (p.prorettype != 'bytea'::regtype OR p.pronargs != 1 OR
+ p.proargtypes[0] != 'internal'::regtype OR
+ NOT p.proisstrict);
+ aggfnoid | proname
+----------+---------
+(0 rows)
+
+-- Check that all deserialization functions have signature
+-- deserialize(bytea, internal) returns internal
+-- Also insist that they be strict; it's wasteful to run them on NULLs.
+SELECT a.aggfnoid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggdeserialfn = p.oid AND
+ (p.prorettype != 'internal'::regtype OR p.pronargs != 2 OR
+ p.proargtypes[0] != 'bytea'::regtype OR
+ p.proargtypes[1] != 'internal'::regtype OR
+ NOT p.proisstrict);
+ aggfnoid | proname
+----------+---------
+(0 rows)
+
+-- Check that aggregates which have the same transition function also have
+-- the same combine, serialization, and deserialization functions.
+-- While that isn't strictly necessary, it's fishy if they don't.
+SELECT a.aggfnoid, a.aggcombinefn, a.aggserialfn, a.aggdeserialfn,
+ b.aggfnoid, b.aggcombinefn, b.aggserialfn, b.aggdeserialfn
+FROM
+ pg_aggregate a, pg_aggregate b
+WHERE
+ a.aggfnoid < b.aggfnoid AND a.aggtransfn = b.aggtransfn AND
+ (a.aggcombinefn != b.aggcombinefn OR a.aggserialfn != b.aggserialfn
+ OR a.aggdeserialfn != b.aggdeserialfn);
+ aggfnoid | aggcombinefn | aggserialfn | aggdeserialfn | aggfnoid | aggcombinefn | aggserialfn | aggdeserialfn
+----------+--------------+-------------+---------------+----------+--------------+-------------+---------------
+(0 rows)
+
+-- Cross-check aggsortop (if present) against pg_operator.
+-- We expect to find entries for bool_and, bool_or, every, max, and min.
+SELECT DISTINCT proname, oprname
+FROM pg_operator AS o, pg_aggregate AS a, pg_proc AS p
+WHERE a.aggfnoid = p.oid AND a.aggsortop = o.oid
+ORDER BY 1, 2;
+ proname | oprname
+----------+---------
+ bool_and | <
+ bool_or | >
+ every | <
+ max | >
+ min | <
+(5 rows)
+
+-- Check datatypes match
+SELECT a.aggfnoid::oid, o.oid
+FROM pg_operator AS o, pg_aggregate AS a, pg_proc AS p
+WHERE a.aggfnoid = p.oid AND a.aggsortop = o.oid AND
+ (oprkind != 'b' OR oprresult != 'boolean'::regtype
+ OR oprleft != p.proargtypes[0] OR oprright != p.proargtypes[0]);
+ aggfnoid | oid
+----------+-----
+(0 rows)
+
+-- Check operator is a suitable btree opfamily member
+SELECT a.aggfnoid::oid, o.oid
+FROM pg_operator AS o, pg_aggregate AS a, pg_proc AS p
+WHERE a.aggfnoid = p.oid AND a.aggsortop = o.oid AND
+ NOT EXISTS(SELECT 1 FROM pg_amop
+ WHERE amopmethod = (SELECT oid FROM pg_am WHERE amname = 'btree')
+ AND amopopr = o.oid
+ AND amoplefttype = o.oprleft
+ AND amoprighttype = o.oprright);
+ aggfnoid | oid
+----------+-----
+(0 rows)
+
+-- Check correspondence of btree strategies and names
+SELECT DISTINCT proname, oprname, amopstrategy
+FROM pg_operator AS o, pg_aggregate AS a, pg_proc AS p,
+ pg_amop as ao
+WHERE a.aggfnoid = p.oid AND a.aggsortop = o.oid AND
+ amopopr = o.oid AND
+ amopmethod = (SELECT oid FROM pg_am WHERE amname = 'btree')
+ORDER BY 1, 2;
+ proname | oprname | amopstrategy
+----------+---------+--------------
+ bool_and | < | 1
+ bool_or | > | 5
+ every | < | 1
+ max | > | 5
+ min | < | 1
+(5 rows)
+
+-- Check that there are not aggregates with the same name and different
+-- numbers of arguments. While not technically wrong, we have a project policy
+-- to avoid this because it opens the door for confusion in connection with
+-- ORDER BY: novices frequently put the ORDER BY in the wrong place.
+-- See the fate of the single-argument form of string_agg() for history.
+-- (Note: we don't forbid users from creating such aggregates; the policy is
+-- just to think twice before creating built-in aggregates like this.)
+-- The only aggregates that should show up here are count(x) and count(*).
+SELECT p1.oid::regprocedure, p2.oid::regprocedure
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND
+ p1.prokind = 'a' AND p2.prokind = 'a' AND
+ array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
+ORDER BY 1;
+ oid | oid
+--------------+---------
+ count("any") | count()
+(1 row)
+
+-- For the same reason, built-in aggregates with default arguments are no good.
+SELECT oid, proname
+FROM pg_proc AS p
+WHERE prokind = 'a' AND proargdefaults IS NOT NULL;
+ oid | proname
+-----+---------
+(0 rows)
+
+-- For the same reason, we avoid creating built-in variadic aggregates, except
+-- that variadic ordered-set aggregates are OK (since they have special syntax
+-- that is not subject to the misplaced ORDER BY issue).
+SELECT p.oid, proname
+FROM pg_proc AS p JOIN pg_aggregate AS a ON a.aggfnoid = p.oid
+WHERE prokind = 'a' AND provariadic != 0 AND a.aggkind = 'n';
+ oid | proname
+-----+---------
+(0 rows)
+
+-- **************** pg_opfamily ****************
+-- Look for illegal values in pg_opfamily fields
+SELECT p1.oid
+FROM pg_opfamily as p1
+WHERE p1.opfmethod = 0 OR p1.opfnamespace = 0;
+ oid
+-----
+(0 rows)
+
+-- Look for opfamilies having no opclasses. While most validation of
+-- opfamilies is now handled by AM-specific amvalidate functions, that's
+-- driven from pg_opclass entries below, so an empty opfamily would not
+-- get noticed.
+SELECT oid, opfname FROM pg_opfamily f
+WHERE NOT EXISTS (SELECT 1 FROM pg_opclass WHERE opcfamily = f.oid);
+ oid | opfname
+-----+---------
+(0 rows)
+
+-- **************** pg_opclass ****************
+-- Look for illegal values in pg_opclass fields
+SELECT p1.oid
+FROM pg_opclass AS p1
+WHERE p1.opcmethod = 0 OR p1.opcnamespace = 0 OR p1.opcfamily = 0
+ OR p1.opcintype = 0;
+ oid
+-----
+(0 rows)
+
+-- opcmethod must match owning opfamily's opfmethod
+SELECT p1.oid, p2.oid
+FROM pg_opclass AS p1, pg_opfamily AS p2
+WHERE p1.opcfamily = p2.oid AND p1.opcmethod != p2.opfmethod;
+ oid | oid
+-----+-----
+(0 rows)
+
+-- There should not be multiple entries in pg_opclass with opcdefault true
+-- and the same opcmethod/opcintype combination.
+SELECT p1.oid, p2.oid
+FROM pg_opclass AS p1, pg_opclass AS p2
+WHERE p1.oid != p2.oid AND
+ p1.opcmethod = p2.opcmethod AND p1.opcintype = p2.opcintype AND
+ p1.opcdefault AND p2.opcdefault;
+ oid | oid
+-----+-----
+(0 rows)
+
+-- Ask access methods to validate opclasses
+-- (this replaces a lot of SQL-level checks that used to be done in this file)
+SELECT oid, opcname FROM pg_opclass WHERE NOT amvalidate(oid);
+ oid | opcname
+-----+---------
+(0 rows)
+
+-- **************** pg_am ****************
+-- Look for illegal values in pg_am fields
+SELECT p1.oid, p1.amname
+FROM pg_am AS p1
+WHERE p1.amhandler = 0;
+ oid | amname
+-----+--------
+(0 rows)
+
+-- Check for index amhandler functions with the wrong signature
+SELECT p1.oid, p1.amname, p2.oid, p2.proname
+FROM pg_am AS p1, pg_proc AS p2
+WHERE p2.oid = p1.amhandler AND p1.amtype = 'i' AND
+ (p2.prorettype != 'index_am_handler'::regtype
+ OR p2.proretset
+ OR p2.pronargs != 1
+ OR p2.proargtypes[0] != 'internal'::regtype);
+ oid | amname | oid | proname
+-----+--------+-----+---------
+(0 rows)
+
+-- Check for table amhandler functions with the wrong signature
+SELECT p1.oid, p1.amname, p2.oid, p2.proname
+FROM pg_am AS p1, pg_proc AS p2
+WHERE p2.oid = p1.amhandler AND p1.amtype = 's' AND
+ (p2.prorettype != 'table_am_handler'::regtype
+ OR p2.proretset
+ OR p2.pronargs != 1
+ OR p2.proargtypes[0] != 'internal'::regtype);
+ oid | amname | oid | proname
+-----+--------+-----+---------
+(0 rows)
+
+-- **************** pg_amop ****************
+-- Look for illegal values in pg_amop fields
+SELECT p1.amopfamily, p1.amopstrategy
+FROM pg_amop as p1
+WHERE p1.amopfamily = 0 OR p1.amoplefttype = 0 OR p1.amoprighttype = 0
+ OR p1.amopopr = 0 OR p1.amopmethod = 0 OR p1.amopstrategy < 1;
+ amopfamily | amopstrategy
+------------+--------------
+(0 rows)
+
+SELECT p1.amopfamily, p1.amopstrategy
+FROM pg_amop as p1
+WHERE NOT ((p1.amoppurpose = 's' AND p1.amopsortfamily = 0) OR
+ (p1.amoppurpose = 'o' AND p1.amopsortfamily <> 0));
+ amopfamily | amopstrategy
+------------+--------------
+(0 rows)
+
+-- amopmethod must match owning opfamily's opfmethod
+SELECT p1.oid, p2.oid
+FROM pg_amop AS p1, pg_opfamily AS p2
+WHERE p1.amopfamily = p2.oid AND p1.amopmethod != p2.opfmethod;
+ oid | oid
+-----+-----
+(0 rows)
+
+-- Make a list of all the distinct operator names being used in particular
+-- strategy slots. This is a bit hokey, since the list might need to change
+-- in future releases, but it's an effective way of spotting mistakes such as
+-- swapping two operators within a family.
+SELECT DISTINCT amopmethod, amopstrategy, oprname
+FROM pg_amop p1 LEFT JOIN pg_operator p2 ON amopopr = p2.oid
+ORDER BY 1, 2, 3;
+ amopmethod | amopstrategy | oprname
+------------+--------------+---------
+ 403 | 1 | *<
+ 403 | 1 | <
+ 403 | 1 | ~<~
+ 403 | 2 | *<=
+ 403 | 2 | <=
+ 403 | 2 | ~<=~
+ 403 | 3 | *=
+ 403 | 3 | =
+ 403 | 4 | *>=
+ 403 | 4 | >=
+ 403 | 4 | ~>=~
+ 403 | 5 | *>
+ 403 | 5 | >
+ 403 | 5 | ~>~
+ 405 | 1 | =
+ 783 | 1 | <<
+ 783 | 1 | @@
+ 783 | 2 | &<
+ 783 | 3 | &&
+ 783 | 4 | &>
+ 783 | 5 | >>
+ 783 | 6 | -|-
+ 783 | 6 | ~=
+ 783 | 7 | @>
+ 783 | 8 | <@
+ 783 | 9 | &<|
+ 783 | 10 | <<|
+ 783 | 11 | |>>
+ 783 | 12 | |&>
+ 783 | 15 | <->
+ 783 | 16 | @>
+ 783 | 18 | =
+ 783 | 19 | <>
+ 783 | 20 | <
+ 783 | 21 | <=
+ 783 | 22 | >
+ 783 | 23 | >=
+ 783 | 24 | <<
+ 783 | 25 | <<=
+ 783 | 26 | >>
+ 783 | 27 | >>=
+ 783 | 28 | <@
+ 783 | 29 | <^
+ 783 | 30 | >^
+ 783 | 48 | <@
+ 783 | 68 | <@
+ 2742 | 1 | &&
+ 2742 | 1 | @@
+ 2742 | 2 | @>
+ 2742 | 2 | @@@
+ 2742 | 3 | <@
+ 2742 | 4 | =
+ 2742 | 7 | @>
+ 2742 | 9 | ?
+ 2742 | 10 | ?|
+ 2742 | 11 | ?&
+ 2742 | 15 | @?
+ 2742 | 16 | @@
+ 3580 | 1 | <
+ 3580 | 1 | <<
+ 3580 | 1 | =
+ 3580 | 2 | &<
+ 3580 | 2 | <=
+ 3580 | 3 | &&
+ 3580 | 3 | =
+ 3580 | 4 | &>
+ 3580 | 4 | >=
+ 3580 | 5 | >
+ 3580 | 5 | >>
+ 3580 | 6 | ~=
+ 3580 | 7 | >>=
+ 3580 | 7 | @>
+ 3580 | 8 | <<=
+ 3580 | 8 | <@
+ 3580 | 9 | &<|
+ 3580 | 10 | <<|
+ 3580 | 11 | |>>
+ 3580 | 12 | |&>
+ 3580 | 16 | @>
+ 3580 | 17 | -|-
+ 3580 | 18 | =
+ 3580 | 20 | <
+ 3580 | 21 | <=
+ 3580 | 22 | >
+ 3580 | 23 | >=
+ 3580 | 24 | >>
+ 3580 | 26 | <<
+ 4000 | 1 | <<
+ 4000 | 1 | ~<~
+ 4000 | 2 | &<
+ 4000 | 2 | ~<=~
+ 4000 | 3 | &&
+ 4000 | 3 | =
+ 4000 | 4 | &>
+ 4000 | 4 | ~>=~
+ 4000 | 5 | >>
+ 4000 | 5 | ~>~
+ 4000 | 6 | -|-
+ 4000 | 6 | ~=
+ 4000 | 7 | @>
+ 4000 | 8 | <@
+ 4000 | 9 | &<|
+ 4000 | 10 | <<|
+ 4000 | 11 | <
+ 4000 | 11 | |>>
+ 4000 | 12 | <=
+ 4000 | 12 | |&>
+ 4000 | 14 | >=
+ 4000 | 15 | <->
+ 4000 | 15 | >
+ 4000 | 16 | @>
+ 4000 | 18 | =
+ 4000 | 19 | <>
+ 4000 | 20 | <
+ 4000 | 21 | <=
+ 4000 | 22 | >
+ 4000 | 23 | >=
+ 4000 | 24 | <<
+ 4000 | 25 | <<=
+ 4000 | 26 | >>
+ 4000 | 27 | >>=
+ 4000 | 28 | ^@
+ 4000 | 29 | <^
+ 4000 | 30 | >^
+(124 rows)
+
+-- Check that all opclass search operators have selectivity estimators.
+-- This is not absolutely required, but it seems a reasonable thing
+-- to insist on for all standard datatypes.
+SELECT p1.amopfamily, p1.amopopr, p2.oid, p2.oprname
+FROM pg_amop AS p1, pg_operator AS p2
+WHERE p1.amopopr = p2.oid AND p1.amoppurpose = 's' AND
+ (p2.oprrest = 0 OR p2.oprjoin = 0);
+ amopfamily | amopopr | oid | oprname
+------------+---------+-----+---------
+(0 rows)
+
+-- Check that each opclass in an opfamily has associated operators, that is
+-- ones whose oprleft matches opcintype (possibly by coercion).
+SELECT p1.opcname, p1.opcfamily
+FROM pg_opclass AS p1
+WHERE NOT EXISTS(SELECT 1 FROM pg_amop AS p2
+ WHERE p2.amopfamily = p1.opcfamily
+ AND binary_coercible(p1.opcintype, p2.amoplefttype));
+ opcname | opcfamily
+---------+-----------
+(0 rows)
+
+-- Check that each operator listed in pg_amop has an associated opclass,
+-- that is one whose opcintype matches oprleft (possibly by coercion).
+-- Otherwise the operator is useless because it cannot be matched to an index.
+-- (In principle it could be useful to list such operators in multiple-datatype
+-- btree opfamilies, but in practice you'd expect there to be an opclass for
+-- every datatype the family knows about.)
+SELECT p1.amopfamily, p1.amopstrategy, p1.amopopr
+FROM pg_amop AS p1
+WHERE NOT EXISTS(SELECT 1 FROM pg_opclass AS p2
+ WHERE p2.opcfamily = p1.amopfamily
+ AND binary_coercible(p2.opcintype, p1.amoplefttype));
+ amopfamily | amopstrategy | amopopr
+------------+--------------+---------
+(0 rows)
+
+-- Operators that are primary members of opclasses must be immutable (else
+-- it suggests that the index ordering isn't fixed). Operators that are
+-- cross-type members need only be stable, since they are just shorthands
+-- for index probe queries.
+SELECT p1.amopfamily, p1.amopopr, p2.oprname, p3.prosrc
+FROM pg_amop AS p1, pg_operator AS p2, pg_proc AS p3
+WHERE p1.amopopr = p2.oid AND p2.oprcode = p3.oid AND
+ p1.amoplefttype = p1.amoprighttype AND
+ p3.provolatile != 'i';
+ amopfamily | amopopr | oprname | prosrc
+------------+---------+---------+--------
+(0 rows)
+
+SELECT p1.amopfamily, p1.amopopr, p2.oprname, p3.prosrc
+FROM pg_amop AS p1, pg_operator AS p2, pg_proc AS p3
+WHERE p1.amopopr = p2.oid AND p2.oprcode = p3.oid AND
+ p1.amoplefttype != p1.amoprighttype AND
+ p3.provolatile = 'v';
+ amopfamily | amopopr | oprname | prosrc
+------------+---------+---------+--------
+(0 rows)
+
+-- **************** pg_amproc ****************
+-- Look for illegal values in pg_amproc fields
+SELECT p1.amprocfamily, p1.amprocnum
+FROM pg_amproc as p1
+WHERE p1.amprocfamily = 0 OR p1.amproclefttype = 0 OR p1.amprocrighttype = 0
+ OR p1.amprocnum < 0 OR p1.amproc = 0;
+ amprocfamily | amprocnum
+--------------+-----------
+(0 rows)
+
+-- Support routines that are primary members of opfamilies must be immutable
+-- (else it suggests that the index ordering isn't fixed). But cross-type
+-- members need only be stable, since they are just shorthands
+-- for index probe queries.
+SELECT p1.amprocfamily, p1.amproc, p2.prosrc
+FROM pg_amproc AS p1, pg_proc AS p2
+WHERE p1.amproc = p2.oid AND
+ p1.amproclefttype = p1.amprocrighttype AND
+ p2.provolatile != 'i';
+ amprocfamily | amproc | prosrc
+--------------+--------+--------
+(0 rows)
+
+SELECT p1.amprocfamily, p1.amproc, p2.prosrc
+FROM pg_amproc AS p1, pg_proc AS p2
+WHERE p1.amproc = p2.oid AND
+ p1.amproclefttype != p1.amprocrighttype AND
+ p2.provolatile = 'v';
+ amprocfamily | amproc | prosrc
+--------------+--------+--------
+(0 rows)
+
+-- Almost all of the core distribution's Btree opclasses can use one of the
+-- two generic "equalimage" functions as their support function 4. Look for
+-- opclasses that don't allow deduplication unconditionally here.
+--
+-- Newly added Btree opclasses don't have to support deduplication. It will
+-- usually be trivial to add support, though. Note that the expected output
+-- of this part of the test will need to be updated when a new opclass cannot
+-- support deduplication (by using btequalimage).
+SELECT amp.amproc::regproc AS proc, opf.opfname AS opfamily_name,
+ opc.opcname AS opclass_name, opc.opcintype::regtype AS opcintype
+FROM pg_am AS am
+JOIN pg_opclass AS opc ON opc.opcmethod = am.oid
+JOIN pg_opfamily AS opf ON opc.opcfamily = opf.oid
+LEFT JOIN pg_amproc AS amp ON amp.amprocfamily = opf.oid AND
+ amp.amproclefttype = opc.opcintype AND amp.amprocnum = 4
+WHERE am.amname = 'btree' AND
+ amp.amproc IS DISTINCT FROM 'btequalimage'::regproc
+ORDER BY 1, 2, 3;
+ proc | opfamily_name | opclass_name | opcintype
+--------------------+------------------+------------------+------------------
+ btvarstrequalimage | bpchar_ops | bpchar_ops | character
+ btvarstrequalimage | text_ops | name_ops | name
+ btvarstrequalimage | text_ops | text_ops | text
+ btvarstrequalimage | text_ops | varchar_ops | text
+ | array_ops | array_ops | anyarray
+ | float_ops | float4_ops | real
+ | float_ops | float8_ops | double precision
+ | jsonb_ops | jsonb_ops | jsonb
+ | multirange_ops | multirange_ops | anymultirange
+ | numeric_ops | numeric_ops | numeric
+ | range_ops | range_ops | anyrange
+ | record_image_ops | record_image_ops | record
+ | record_ops | record_ops | record
+ | tsquery_ops | tsquery_ops | tsquery
+ | tsvector_ops | tsvector_ops | tsvector
+(15 rows)
+
+-- **************** pg_index ****************
+-- Look for illegal values in pg_index fields.
+SELECT p1.indexrelid, p1.indrelid
+FROM pg_index as p1
+WHERE p1.indexrelid = 0 OR p1.indrelid = 0 OR
+ p1.indnatts <= 0 OR p1.indnatts > 32;
+ indexrelid | indrelid
+------------+----------
+(0 rows)
+
+-- oidvector and int2vector fields should be of length indnatts.
+SELECT p1.indexrelid, p1.indrelid
+FROM pg_index as p1
+WHERE array_lower(indkey, 1) != 0 OR array_upper(indkey, 1) != indnatts-1 OR
+ array_lower(indclass, 1) != 0 OR array_upper(indclass, 1) != indnatts-1 OR
+ array_lower(indcollation, 1) != 0 OR array_upper(indcollation, 1) != indnatts-1 OR
+ array_lower(indoption, 1) != 0 OR array_upper(indoption, 1) != indnatts-1;
+ indexrelid | indrelid
+------------+----------
+(0 rows)
+
+-- Check that opclasses and collations match the underlying columns.
+-- (As written, this test ignores expression indexes.)
+SELECT indexrelid::regclass, indrelid::regclass, attname, atttypid::regtype, opcname
+FROM (SELECT indexrelid, indrelid, unnest(indkey) as ikey,
+ unnest(indclass) as iclass, unnest(indcollation) as icoll
+ FROM pg_index) ss,
+ pg_attribute a,
+ pg_opclass opc
+WHERE a.attrelid = indrelid AND a.attnum = ikey AND opc.oid = iclass AND
+ (NOT binary_coercible(atttypid, opcintype) OR icoll != attcollation);
+ indexrelid | indrelid | attname | atttypid | opcname
+------------+----------+---------+----------+---------
+(0 rows)
+
+-- For system catalogs, be even tighter: nearly all indexes should be
+-- exact type matches not binary-coercible matches. At this writing
+-- the only exception is an OID index on a regproc column.
+SELECT indexrelid::regclass, indrelid::regclass, attname, atttypid::regtype, opcname
+FROM (SELECT indexrelid, indrelid, unnest(indkey) as ikey,
+ unnest(indclass) as iclass, unnest(indcollation) as icoll
+ FROM pg_index
+ WHERE indrelid < 16384) ss,
+ pg_attribute a,
+ pg_opclass opc
+WHERE a.attrelid = indrelid AND a.attnum = ikey AND opc.oid = iclass AND
+ (opcintype != atttypid OR icoll != attcollation)
+ORDER BY 1;
+ indexrelid | indrelid | attname | atttypid | opcname
+--------------------------+--------------+----------+----------+---------
+ pg_aggregate_fnoid_index | pg_aggregate | aggfnoid | regproc | oid_ops
+(1 row)
+
+-- Check for system catalogs with collation-sensitive ordering. This is not
+-- a representational error in pg_index, but simply wrong catalog design.
+-- It's bad because we expect to be able to clone template0 and assign the
+-- copy a different database collation. It would especially not work for
+-- shared catalogs.
+SELECT relname, attname, attcollation
+FROM pg_class c, pg_attribute a
+WHERE c.oid = attrelid AND c.oid < 16384 AND
+ c.relkind != 'v' AND -- we don't care about columns in views
+ attcollation != 0 AND
+ attcollation != (SELECT oid FROM pg_collation WHERE collname = 'C');
+ relname | attname | attcollation
+---------+---------+--------------
+(0 rows)
+
+-- Double-check that collation-sensitive indexes have "C" collation, too.
+SELECT indexrelid::regclass, indrelid::regclass, iclass, icoll
+FROM (SELECT indexrelid, indrelid,
+ unnest(indclass) as iclass, unnest(indcollation) as icoll
+ FROM pg_index
+ WHERE indrelid < 16384) ss
+WHERE icoll != 0 AND
+ icoll != (SELECT oid FROM pg_collation WHERE collname = 'C');
+ indexrelid | indrelid | iclass | icoll
+------------+----------+--------+-------
+(0 rows)
+
diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out
new file mode 100644
index 0000000..dfa4b03
--- /dev/null
+++ b/src/test/regress/expected/partition_aggregate.out
@@ -0,0 +1,1519 @@
+--
+-- 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.
+--
+-- 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.c, (sum(pagg_tab.a)), (avg(pagg_tab.b))
+ -> Append
+ -> HashAggregate
+ Group Key: pagg_tab.c
+ Filter: (avg(pagg_tab.d) < '15'::numeric)
+ -> Seq Scan on pagg_tab_p1 pagg_tab
+ -> HashAggregate
+ Group Key: pagg_tab_1.c
+ Filter: (avg(pagg_tab_1.d) < '15'::numeric)
+ -> Seq Scan on pagg_tab_p2 pagg_tab_1
+ -> HashAggregate
+ Group Key: pagg_tab_2.c
+ Filter: (avg(pagg_tab_2.d) < '15'::numeric)
+ -> Seq Scan on pagg_tab_p3 pagg_tab_2
+(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.a, (sum(pagg_tab.b)), (avg(pagg_tab.b))
+ -> Finalize HashAggregate
+ Group Key: pagg_tab.a
+ Filter: (avg(pagg_tab.d) < '15'::numeric)
+ -> Append
+ -> Partial HashAggregate
+ Group Key: pagg_tab.a
+ -> Seq Scan on pagg_tab_p1 pagg_tab
+ -> Partial HashAggregate
+ Group Key: pagg_tab_1.a
+ -> Seq Scan on pagg_tab_p2 pagg_tab_1
+ -> Partial HashAggregate
+ Group Key: pagg_tab_2.a
+ -> Seq Scan on pagg_tab_p3 pagg_tab_2
+(15 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.a, pagg_tab.c
+ -> Seq Scan on pagg_tab_p1 pagg_tab
+ -> HashAggregate
+ Group Key: pagg_tab_1.a, pagg_tab_1.c
+ -> Seq Scan on pagg_tab_p2 pagg_tab_1
+ -> HashAggregate
+ Group Key: pagg_tab_2.a, pagg_tab_2.c
+ -> Seq Scan on pagg_tab_p3 pagg_tab_2
+(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.c, pagg_tab.a
+ -> Seq Scan on pagg_tab_p1 pagg_tab
+ -> HashAggregate
+ Group Key: pagg_tab_1.c, pagg_tab_1.a
+ -> Seq Scan on pagg_tab_p2 pagg_tab_1
+ -> HashAggregate
+ Group Key: pagg_tab_2.c, pagg_tab_2.a
+ -> Seq Scan on pagg_tab_p3 pagg_tab_2
+(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.a, pagg_tab.c
+ -> Seq Scan on pagg_tab_p1 pagg_tab
+ -> HashAggregate
+ Group Key: pagg_tab_1.a, pagg_tab_1.c
+ -> Seq Scan on pagg_tab_p2 pagg_tab_1
+ -> HashAggregate
+ Group Key: pagg_tab_2.a, pagg_tab_2.c
+ -> Seq Scan on pagg_tab_p3 pagg_tab_2
+(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
+ 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
+ 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.c, (sum(pagg_tab.a)), (avg(pagg_tab.b))
+ -> Append
+ -> GroupAggregate
+ Group Key: pagg_tab.c
+ Filter: (avg(pagg_tab.d) < '15'::numeric)
+ -> Sort
+ Sort Key: pagg_tab.c
+ -> Seq Scan on pagg_tab_p1 pagg_tab
+ -> 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_p2 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_p3 pagg_tab_2
+(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.a, (sum(pagg_tab.b)), (avg(pagg_tab.b))
+ -> Finalize GroupAggregate
+ Group Key: pagg_tab.a
+ Filter: (avg(pagg_tab.d) < '15'::numeric)
+ -> Merge Append
+ Sort Key: pagg_tab.a
+ -> Partial GroupAggregate
+ Group Key: pagg_tab.a
+ -> Sort
+ Sort Key: pagg_tab.a
+ -> Seq Scan on pagg_tab_p1 pagg_tab
+ -> Partial GroupAggregate
+ Group Key: pagg_tab_1.a
+ -> Sort
+ Sort Key: pagg_tab_1.a
+ -> Seq Scan on pagg_tab_p2 pagg_tab_1
+ -> Partial GroupAggregate
+ Group Key: pagg_tab_2.a
+ -> Sort
+ Sort Key: pagg_tab_2.a
+ -> Seq Scan on pagg_tab_p3 pagg_tab_2
+(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.c
+ -> Group
+ Group Key: pagg_tab.c
+ -> Sort
+ Sort Key: pagg_tab.c
+ -> Seq Scan on pagg_tab_p1 pagg_tab
+ -> Group
+ Group Key: pagg_tab_1.c
+ -> Sort
+ Sort Key: pagg_tab_1.c
+ -> Seq Scan on pagg_tab_p2 pagg_tab_1
+ -> Group
+ Group Key: pagg_tab_2.c
+ -> Sort
+ Sort Key: pagg_tab_2.c
+ -> Seq Scan on pagg_tab_p3 pagg_tab_2
+(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.a
+ -> Merge Append
+ Sort Key: pagg_tab.a
+ -> Group
+ Group Key: pagg_tab.a
+ -> Sort
+ Sort Key: pagg_tab.a
+ -> Seq Scan on pagg_tab_p1 pagg_tab
+ Filter: (a < 3)
+ -> Group
+ Group Key: pagg_tab_1.a
+ -> Sort
+ Sort Key: pagg_tab_1.a
+ -> Seq Scan on pagg_tab_p2 pagg_tab_1
+ Filter: (a < 3)
+ -> Group
+ Group Key: pagg_tab_2.a
+ -> Sort
+ Sort Key: pagg_tab_2.a
+ -> Seq Scan on pagg_tab_p3 pagg_tab_2
+ 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.c, (sum(pagg_tab.b ORDER BY pagg_tab.a))
+ -> Append
+ -> GroupAggregate
+ Group Key: pagg_tab.c
+ -> Sort
+ Sort Key: pagg_tab.c
+ -> Seq Scan on pagg_tab_p1 pagg_tab
+ -> GroupAggregate
+ Group Key: pagg_tab_1.c
+ -> Sort
+ Sort Key: pagg_tab_1.c
+ -> Seq Scan on pagg_tab_p2 pagg_tab_1
+ -> GroupAggregate
+ Group Key: pagg_tab_2.c
+ -> Sort
+ Sort Key: pagg_tab_2.c
+ -> Seq Scan on pagg_tab_p3 pagg_tab_2
+(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.x, (sum(t1.y)), (count(*))
+ -> Append
+ -> HashAggregate
+ Group Key: t1.x
+ -> Hash Join
+ Hash Cond: (t1.x = t2.y)
+ -> Seq Scan on pagg_tab1_p1 t1
+ -> Hash
+ -> Seq Scan on pagg_tab2_p1 t2
+ -> HashAggregate
+ Group Key: t1_1.x
+ -> Hash Join
+ Hash Cond: (t1_1.x = t2_1.y)
+ -> Seq Scan on pagg_tab1_p2 t1_1
+ -> Hash
+ -> Seq Scan on pagg_tab2_p2 t2_1
+ -> HashAggregate
+ Group Key: t1_2.x
+ -> Hash Join
+ Hash Cond: (t2_2.y = t1_2.x)
+ -> Seq Scan on pagg_tab2_p3 t2_2
+ -> Hash
+ -> Seq Scan on pagg_tab1_p3 t1_2
+(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.y, (sum(t1.y)), (count(*))
+ -> Append
+ -> HashAggregate
+ Group Key: t2.y
+ -> Hash Join
+ Hash Cond: (t1.x = t2.y)
+ -> Seq Scan on pagg_tab1_p1 t1
+ -> Hash
+ -> Seq Scan on pagg_tab2_p1 t2
+ -> HashAggregate
+ Group Key: t2_1.y
+ -> Hash Join
+ Hash Cond: (t1_1.x = t2_1.y)
+ -> Seq Scan on pagg_tab1_p2 t1_1
+ -> Hash
+ -> Seq Scan on pagg_tab2_p2 t2_1
+ -> HashAggregate
+ Group Key: t2_2.y
+ -> Hash Join
+ Hash Cond: (t2_2.y = t1_2.x)
+ -> Seq Scan on pagg_tab2_p3 t2_2
+ -> Hash
+ -> Seq Scan on pagg_tab1_p3 t1_2
+(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.y, (sum(t1.x)), (count(*))
+ -> Finalize GroupAggregate
+ Group Key: t1.y
+ Filter: (avg(t1.x) > '10'::numeric)
+ -> Merge Append
+ Sort Key: t1.y
+ -> Partial GroupAggregate
+ Group Key: t1.y
+ -> Sort
+ Sort Key: t1.y
+ -> Hash Join
+ Hash Cond: (t1.x = t2.y)
+ -> Seq Scan on pagg_tab1_p1 t1
+ -> Hash
+ -> Seq Scan on pagg_tab2_p1 t2
+ -> 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_p2 t1_1
+ -> Hash
+ -> Seq Scan on pagg_tab2_p2 t2_1
+ -> Partial GroupAggregate
+ Group Key: t1_2.y
+ -> Sort
+ Sort Key: t1_2.y
+ -> Hash Join
+ Hash Cond: (t2_2.y = t1_2.x)
+ -> Seq Scan on pagg_tab2_p3 t2_2
+ -> Hash
+ -> Seq Scan on pagg_tab1_p3 t1_2
+(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.y
+ -> Sort
+ Sort Key: b.y
+ -> Append
+ -> Partial HashAggregate
+ Group Key: b.y
+ -> Hash Left Join
+ Hash Cond: (a.x = b.y)
+ -> Seq Scan on pagg_tab1_p1 a
+ -> Hash
+ -> Seq Scan on pagg_tab2_p1 b
+ -> Partial HashAggregate
+ Group Key: b_1.y
+ -> Hash Left Join
+ Hash Cond: (a_1.x = b_1.y)
+ -> Seq Scan on pagg_tab1_p2 a_1
+ -> Hash
+ -> Seq Scan on pagg_tab2_p2 b_1
+ -> Partial HashAggregate
+ Group Key: b_2.y
+ -> Hash Right Join
+ Hash Cond: (b_2.y = a_2.x)
+ -> Seq Scan on pagg_tab2_p3 b_2
+ -> Hash
+ -> Seq Scan on pagg_tab1_p3 a_2
+(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.y
+ -> Append
+ -> HashAggregate
+ Group Key: b.y
+ -> Hash Right Join
+ Hash Cond: (a.x = b.y)
+ -> Seq Scan on pagg_tab1_p1 a
+ -> Hash
+ -> Seq Scan on pagg_tab2_p1 b
+ -> HashAggregate
+ Group Key: b_1.y
+ -> Hash Right Join
+ Hash Cond: (a_1.x = b_1.y)
+ -> Seq Scan on pagg_tab1_p2 a_1
+ -> Hash
+ -> Seq Scan on pagg_tab2_p2 b_1
+ -> HashAggregate
+ Group Key: b_2.y
+ -> Hash Left Join
+ Hash Cond: (b_2.y = a_2.x)
+ -> Seq Scan on pagg_tab2_p3 b_2
+ -> Hash
+ -> Seq Scan on pagg_tab1_p3 a_2
+(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.x
+ -> Sort
+ Sort Key: a.x
+ -> Append
+ -> Partial HashAggregate
+ Group Key: a.x
+ -> Hash Full Join
+ Hash Cond: (a.x = b.y)
+ -> Seq Scan on pagg_tab1_p1 a
+ -> Hash
+ -> Seq Scan on pagg_tab2_p1 b
+ -> Partial HashAggregate
+ Group Key: a_1.x
+ -> Hash Full Join
+ Hash Cond: (a_1.x = b_1.y)
+ -> Seq Scan on pagg_tab1_p2 a_1
+ -> Hash
+ -> Seq Scan on pagg_tab2_p2 b_1
+ -> Partial HashAggregate
+ Group Key: a_2.x
+ -> Hash Full Join
+ Hash Cond: (b_2.y = a_2.x)
+ -> Seq Scan on pagg_tab2_p3 b_2
+ -> Hash
+ -> Seq Scan on pagg_tab1_p3 a_2
+(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
+ Filter: (x < 20)
+ -> Seq Scan on pagg_tab1_p2 pagg_tab1_2
+ Filter: (x < 20)
+ -> Hash
+ -> Append
+ -> Seq Scan on pagg_tab2_p2 pagg_tab2_1
+ Filter: (y > 10)
+ -> Seq Scan on pagg_tab2_p3 pagg_tab2_2
+ 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
+ Filter: (x < 20)
+ -> Seq Scan on pagg_tab1_p2 pagg_tab1_2
+ Filter: (x < 20)
+ -> Hash
+ -> Append
+ -> Seq Scan on pagg_tab2_p2 pagg_tab2_1
+ Filter: (y > 10)
+ -> Seq Scan on pagg_tab2_p3 pagg_tab2_2
+ 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.a, (sum(pagg_tab_m.b)), (avg(pagg_tab_m.c))
+ -> Finalize HashAggregate
+ Group Key: pagg_tab_m.a
+ Filter: (avg(pagg_tab_m.c) < '22'::numeric)
+ -> Append
+ -> Partial HashAggregate
+ Group Key: pagg_tab_m.a
+ -> Seq Scan on pagg_tab_m_p1 pagg_tab_m
+ -> Partial HashAggregate
+ Group Key: pagg_tab_m_1.a
+ -> Seq Scan on pagg_tab_m_p2 pagg_tab_m_1
+ -> Partial HashAggregate
+ Group Key: pagg_tab_m_2.a
+ -> Seq Scan on pagg_tab_m_p3 pagg_tab_m_2
+(15 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.a, (sum(pagg_tab_m.b)), (avg(pagg_tab_m.c))
+ -> Append
+ -> HashAggregate
+ Group Key: pagg_tab_m.a, ((pagg_tab_m.a + pagg_tab_m.b) / 2)
+ Filter: (sum(pagg_tab_m.b) < 50)
+ -> Seq Scan on pagg_tab_m_p1 pagg_tab_m
+ -> 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_p2 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_p3 pagg_tab_m_2
+(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.a, pagg_tab_m.c, (sum(pagg_tab_m.b))
+ -> Append
+ -> HashAggregate
+ Group Key: ((pagg_tab_m.a + pagg_tab_m.b) / 2), pagg_tab_m.c, pagg_tab_m.a
+ Filter: ((sum(pagg_tab_m.b) = 50) AND (avg(pagg_tab_m.c) > '25'::numeric))
+ -> Seq Scan on pagg_tab_m_p1 pagg_tab_m
+ -> 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_p2 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_p3 pagg_tab_m_2
+(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;
+-- 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.a, (sum(pagg_tab_ml.b)), (array_agg(DISTINCT pagg_tab_ml.c))
+ -> Gather
+ Workers Planned: 2
+ -> Parallel Append
+ -> GroupAggregate
+ Group Key: pagg_tab_ml.a
+ Filter: (avg(pagg_tab_ml.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml.a
+ -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
+ -> GroupAggregate
+ Group Key: pagg_tab_ml_5.a
+ Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml_5.a
+ -> Append
+ -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5
+ -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6
+ -> GroupAggregate
+ Group Key: pagg_tab_ml_2.a
+ Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml_2.a
+ -> Append
+ -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2
+ -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3
+(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.a
+ Filter: (avg(pagg_tab_ml.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml.a
+ -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
+ -> GroupAggregate
+ Group Key: pagg_tab_ml_5.a
+ Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml_5.a
+ -> Append
+ -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5
+ -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6
+ -> GroupAggregate
+ Group Key: pagg_tab_ml_2.a
+ Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml_2.a
+ -> Append
+ -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2
+ -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3
+(25 rows)
+
+-- 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.a, (sum(pagg_tab_ml.b)), (count(*))
+ -> Append
+ -> HashAggregate
+ Group Key: pagg_tab_ml.a
+ Filter: (avg(pagg_tab_ml.b) < '3'::numeric)
+ -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
+ -> Finalize GroupAggregate
+ Group Key: pagg_tab_ml_2.a
+ Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml_2.a
+ -> Append
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_2.a
+ -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_3.a
+ -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3
+ -> Finalize GroupAggregate
+ Group Key: pagg_tab_ml_5.a
+ Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml_5.a
+ -> Append
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_5.a
+ -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_6.a
+ -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6
+(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.b, (sum(pagg_tab_ml.a)), (count(*))
+ -> Finalize GroupAggregate
+ Group Key: pagg_tab_ml.b
+ -> Sort
+ Sort Key: pagg_tab_ml.b
+ -> Append
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml.b
+ -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_1.b
+ -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_1
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_2.b
+ -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_2
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_3.b
+ -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_3
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_4.b
+ -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_4
+(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.a, (sum(pagg_tab_ml.b)), (count(*))
+ -> Append
+ -> HashAggregate
+ Group Key: pagg_tab_ml.a, pagg_tab_ml.b, pagg_tab_ml.c
+ Filter: (avg(pagg_tab_ml.b) > '7'::numeric)
+ -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
+ -> 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_p2_s1 pagg_tab_ml_1
+ -> HashAggregate
+ Group Key: pagg_tab_ml_2.a, pagg_tab_ml_2.b, pagg_tab_ml_2.c
+ Filter: (avg(pagg_tab_ml_2.b) > '7'::numeric)
+ -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_2
+ -> 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_p3_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_p3_s2 pagg_tab_ml_4
+(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.a, (sum(pagg_tab_ml.b)), (count(*))
+ -> Append
+ -> Finalize GroupAggregate
+ Group Key: pagg_tab_ml.a
+ Filter: (avg(pagg_tab_ml.b) < '3'::numeric)
+ -> Gather Merge
+ Workers Planned: 2
+ -> Sort
+ Sort Key: pagg_tab_ml.a
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml.a
+ -> Parallel Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
+ -> Finalize GroupAggregate
+ Group Key: pagg_tab_ml_2.a
+ Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric)
+ -> Gather Merge
+ Workers Planned: 2
+ -> Sort
+ Sort Key: pagg_tab_ml_2.a
+ -> Parallel Append
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_2.a
+ -> Parallel Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_3.a
+ -> Parallel Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3
+ -> Finalize GroupAggregate
+ Group Key: pagg_tab_ml_5.a
+ Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric)
+ -> Gather Merge
+ Workers Planned: 2
+ -> Sort
+ Sort Key: pagg_tab_ml_5.a
+ -> Parallel Append
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_5.a
+ -> Parallel Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_6.a
+ -> Parallel Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6
+(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.b, (sum(pagg_tab_ml.a)), (count(*))
+ -> Finalize GroupAggregate
+ Group Key: pagg_tab_ml.b
+ -> Gather Merge
+ Workers Planned: 2
+ -> Sort
+ Sort Key: pagg_tab_ml.b
+ -> Parallel Append
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml.b
+ -> Parallel Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_3.b
+ -> Parallel Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_3
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_1.b
+ -> Parallel Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_1
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_4.b
+ -> Parallel Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_4
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_2.b
+ -> Parallel Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_2
+(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.a, (sum(pagg_tab_ml.b)), (count(*))
+ -> Parallel Append
+ -> HashAggregate
+ Group Key: pagg_tab_ml.a, pagg_tab_ml.b, pagg_tab_ml.c
+ Filter: (avg(pagg_tab_ml.b) > '7'::numeric)
+ -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
+ -> 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_p3_s1 pagg_tab_ml_3
+ -> 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_p2_s1 pagg_tab_ml_1
+ -> 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_p3_s2 pagg_tab_ml_4
+ -> HashAggregate
+ Group Key: pagg_tab_ml_2.a, pagg_tab_ml_2.b, pagg_tab_ml_2.c
+ Filter: (avg(pagg_tab_ml_2.b) > '7'::numeric)
+ -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_2
+(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.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
+ -> Parallel Append
+ -> Partial HashAggregate
+ Group Key: pagg_tab_para.x
+ -> Parallel Seq Scan on pagg_tab_para_p1 pagg_tab_para
+ -> Partial HashAggregate
+ Group Key: pagg_tab_para_1.x
+ -> Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_1
+ -> Partial HashAggregate
+ Group Key: pagg_tab_para_2.x
+ -> Parallel Seq Scan on pagg_tab_para_p3 pagg_tab_para_2
+(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.y, (sum(pagg_tab_para.x)), (avg(pagg_tab_para.x))
+ -> Finalize GroupAggregate
+ Group Key: pagg_tab_para.y
+ Filter: (avg(pagg_tab_para.x) < '12'::numeric)
+ -> Gather Merge
+ Workers Planned: 2
+ -> Sort
+ Sort Key: pagg_tab_para.y
+ -> Parallel Append
+ -> Partial HashAggregate
+ Group Key: pagg_tab_para.y
+ -> Parallel Seq Scan on pagg_tab_para_p1 pagg_tab_para
+ -> Partial HashAggregate
+ Group Key: pagg_tab_para_1.y
+ -> Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_1
+ -> Partial HashAggregate
+ Group Key: pagg_tab_para_2.y
+ -> Parallel Seq Scan on pagg_tab_para_p3 pagg_tab_para_2
+(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);
+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;
+ 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);
+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_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(*) 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)
+
+-- 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.x, (sum(pagg_tab_para.y)), (avg(pagg_tab_para.y))
+ -> Append
+ -> HashAggregate
+ Group Key: pagg_tab_para.x
+ Filter: (avg(pagg_tab_para.y) < '7'::numeric)
+ -> Seq Scan on pagg_tab_para_p1 pagg_tab_para
+ -> HashAggregate
+ Group Key: pagg_tab_para_1.x
+ Filter: (avg(pagg_tab_para_1.y) < '7'::numeric)
+ -> Seq Scan on pagg_tab_para_p2 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_p3 pagg_tab_para_2
+(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/partition_info.out b/src/test/regress/expected/partition_info.out
new file mode 100644
index 0000000..42b6bc7
--- /dev/null
+++ b/src/test/regress/expected/partition_info.out
@@ -0,0 +1,351 @@
+--
+-- Tests for functions providing information about partitions
+--
+SELECT * FROM pg_partition_tree(NULL);
+ relid | parentrelid | isleaf | level
+-------+-------------+--------+-------
+(0 rows)
+
+SELECT * FROM pg_partition_tree(0);
+ relid | parentrelid | isleaf | level
+-------+-------------+--------+-------
+(0 rows)
+
+SELECT * FROM pg_partition_ancestors(NULL);
+ relid
+-------
+(0 rows)
+
+SELECT * FROM pg_partition_ancestors(0);
+ relid
+-------
+(0 rows)
+
+SELECT pg_partition_root(NULL);
+ pg_partition_root
+-------------------
+
+(1 row)
+
+SELECT pg_partition_root(0);
+ pg_partition_root
+-------------------
+
+(1 row)
+
+-- Test table partition trees
+CREATE TABLE ptif_test (a int, b int) PARTITION BY range (a);
+CREATE TABLE ptif_test0 PARTITION OF ptif_test
+ FOR VALUES FROM (minvalue) TO (0) PARTITION BY list (b);
+CREATE TABLE ptif_test01 PARTITION OF ptif_test0 FOR VALUES IN (1);
+CREATE TABLE ptif_test1 PARTITION OF ptif_test
+ FOR VALUES FROM (0) TO (100) PARTITION BY list (b);
+CREATE TABLE ptif_test11 PARTITION OF ptif_test1 FOR VALUES IN (1);
+CREATE TABLE ptif_test2 PARTITION OF ptif_test
+ FOR VALUES FROM (100) TO (200);
+-- This partitioned table should remain with no partitions.
+CREATE TABLE ptif_test3 PARTITION OF ptif_test
+ FOR VALUES FROM (200) TO (maxvalue) PARTITION BY list (b);
+-- Test pg_partition_root for tables
+SELECT pg_partition_root('ptif_test');
+ pg_partition_root
+-------------------
+ ptif_test
+(1 row)
+
+SELECT pg_partition_root('ptif_test0');
+ pg_partition_root
+-------------------
+ ptif_test
+(1 row)
+
+SELECT pg_partition_root('ptif_test01');
+ pg_partition_root
+-------------------
+ ptif_test
+(1 row)
+
+SELECT pg_partition_root('ptif_test3');
+ pg_partition_root
+-------------------
+ ptif_test
+(1 row)
+
+-- Test index partition tree
+CREATE INDEX ptif_test_index ON ONLY ptif_test (a);
+CREATE INDEX ptif_test0_index ON ONLY ptif_test0 (a);
+ALTER INDEX ptif_test_index ATTACH PARTITION ptif_test0_index;
+CREATE INDEX ptif_test01_index ON ptif_test01 (a);
+ALTER INDEX ptif_test0_index ATTACH PARTITION ptif_test01_index;
+CREATE INDEX ptif_test1_index ON ONLY ptif_test1 (a);
+ALTER INDEX ptif_test_index ATTACH PARTITION ptif_test1_index;
+CREATE INDEX ptif_test11_index ON ptif_test11 (a);
+ALTER INDEX ptif_test1_index ATTACH PARTITION ptif_test11_index;
+CREATE INDEX ptif_test2_index ON ptif_test2 (a);
+ALTER INDEX ptif_test_index ATTACH PARTITION ptif_test2_index;
+CREATE INDEX ptif_test3_index ON ptif_test3 (a);
+ALTER INDEX ptif_test_index ATTACH PARTITION ptif_test3_index;
+-- Test pg_partition_root for indexes
+SELECT pg_partition_root('ptif_test_index');
+ pg_partition_root
+-------------------
+ ptif_test_index
+(1 row)
+
+SELECT pg_partition_root('ptif_test0_index');
+ pg_partition_root
+-------------------
+ ptif_test_index
+(1 row)
+
+SELECT pg_partition_root('ptif_test01_index');
+ pg_partition_root
+-------------------
+ ptif_test_index
+(1 row)
+
+SELECT pg_partition_root('ptif_test3_index');
+ pg_partition_root
+-------------------
+ ptif_test_index
+(1 row)
+
+-- List all tables members of the tree
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test');
+ relid | parentrelid | level | isleaf
+-------------+-------------+-------+--------
+ ptif_test | | 0 | f
+ ptif_test0 | ptif_test | 1 | f
+ ptif_test1 | ptif_test | 1 | f
+ ptif_test2 | ptif_test | 1 | t
+ ptif_test3 | ptif_test | 1 | f
+ ptif_test01 | ptif_test0 | 2 | t
+ ptif_test11 | ptif_test1 | 2 | t
+(7 rows)
+
+-- List tables from an intermediate level
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test0') p
+ JOIN pg_class c ON (p.relid = c.oid);
+ relid | parentrelid | level | isleaf
+-------------+-------------+-------+--------
+ ptif_test0 | ptif_test | 0 | f
+ ptif_test01 | ptif_test0 | 1 | t
+(2 rows)
+
+-- List from leaf table
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test01') p
+ JOIN pg_class c ON (p.relid = c.oid);
+ relid | parentrelid | level | isleaf
+-------------+-------------+-------+--------
+ ptif_test01 | ptif_test0 | 0 | t
+(1 row)
+
+-- List from partitioned table with no partitions
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test3') p
+ JOIN pg_class c ON (p.relid = c.oid);
+ relid | parentrelid | level | isleaf
+------------+-------------+-------+--------
+ ptif_test3 | ptif_test | 0 | f
+(1 row)
+
+-- List all ancestors of root and leaf tables
+SELECT * FROM pg_partition_ancestors('ptif_test01');
+ relid
+-------------
+ ptif_test01
+ ptif_test0
+ ptif_test
+(3 rows)
+
+SELECT * FROM pg_partition_ancestors('ptif_test');
+ relid
+-----------
+ ptif_test
+(1 row)
+
+-- List all members using pg_partition_root with leaf table reference
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree(pg_partition_root('ptif_test01')) p
+ JOIN pg_class c ON (p.relid = c.oid);
+ relid | parentrelid | level | isleaf
+-------------+-------------+-------+--------
+ ptif_test | | 0 | f
+ ptif_test0 | ptif_test | 1 | f
+ ptif_test1 | ptif_test | 1 | f
+ ptif_test2 | ptif_test | 1 | t
+ ptif_test3 | ptif_test | 1 | f
+ ptif_test01 | ptif_test0 | 2 | t
+ ptif_test11 | ptif_test1 | 2 | t
+(7 rows)
+
+-- List all indexes members of the tree
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test_index');
+ relid | parentrelid | level | isleaf
+-------------------+------------------+-------+--------
+ ptif_test_index | | 0 | f
+ ptif_test0_index | ptif_test_index | 1 | f
+ ptif_test1_index | ptif_test_index | 1 | f
+ ptif_test2_index | ptif_test_index | 1 | t
+ ptif_test3_index | ptif_test_index | 1 | f
+ ptif_test01_index | ptif_test0_index | 2 | t
+ ptif_test11_index | ptif_test1_index | 2 | t
+(7 rows)
+
+-- List indexes from an intermediate level
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test0_index') p
+ JOIN pg_class c ON (p.relid = c.oid);
+ relid | parentrelid | level | isleaf
+-------------------+------------------+-------+--------
+ ptif_test0_index | ptif_test_index | 0 | f
+ ptif_test01_index | ptif_test0_index | 1 | t
+(2 rows)
+
+-- List from leaf index
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test01_index') p
+ JOIN pg_class c ON (p.relid = c.oid);
+ relid | parentrelid | level | isleaf
+-------------------+------------------+-------+--------
+ ptif_test01_index | ptif_test0_index | 0 | t
+(1 row)
+
+-- List from partitioned index with no partitions
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test3_index') p
+ JOIN pg_class c ON (p.relid = c.oid);
+ relid | parentrelid | level | isleaf
+------------------+-----------------+-------+--------
+ ptif_test3_index | ptif_test_index | 0 | f
+(1 row)
+
+-- List all members using pg_partition_root with leaf index reference
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree(pg_partition_root('ptif_test01_index')) p
+ JOIN pg_class c ON (p.relid = c.oid);
+ relid | parentrelid | level | isleaf
+-------------------+------------------+-------+--------
+ ptif_test_index | | 0 | f
+ ptif_test0_index | ptif_test_index | 1 | f
+ ptif_test1_index | ptif_test_index | 1 | f
+ ptif_test2_index | ptif_test_index | 1 | t
+ ptif_test3_index | ptif_test_index | 1 | f
+ ptif_test01_index | ptif_test0_index | 2 | t
+ ptif_test11_index | ptif_test1_index | 2 | t
+(7 rows)
+
+-- List all ancestors of root and leaf indexes
+SELECT * FROM pg_partition_ancestors('ptif_test01_index');
+ relid
+-------------------
+ ptif_test01_index
+ ptif_test0_index
+ ptif_test_index
+(3 rows)
+
+SELECT * FROM pg_partition_ancestors('ptif_test_index');
+ relid
+-----------------
+ ptif_test_index
+(1 row)
+
+DROP TABLE ptif_test;
+-- Table that is not part of any partition tree is not listed.
+CREATE TABLE ptif_normal_table(a int);
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_normal_table');
+ relid | parentrelid | level | isleaf
+-------+-------------+-------+--------
+(0 rows)
+
+SELECT * FROM pg_partition_ancestors('ptif_normal_table');
+ relid
+-------
+(0 rows)
+
+SELECT pg_partition_root('ptif_normal_table');
+ pg_partition_root
+-------------------
+
+(1 row)
+
+DROP TABLE ptif_normal_table;
+-- Various partitioning-related functions return empty/NULL if passed relations
+-- of types that cannot be part of a partition tree; for example, views,
+-- materialized views, legacy inheritance children or parents, etc.
+CREATE VIEW ptif_test_view AS SELECT 1;
+CREATE MATERIALIZED VIEW ptif_test_matview AS SELECT 1;
+CREATE TABLE ptif_li_parent ();
+CREATE TABLE ptif_li_child () INHERITS (ptif_li_parent);
+SELECT * FROM pg_partition_tree('ptif_test_view');
+ relid | parentrelid | isleaf | level
+-------+-------------+--------+-------
+(0 rows)
+
+SELECT * FROM pg_partition_tree('ptif_test_matview');
+ relid | parentrelid | isleaf | level
+-------+-------------+--------+-------
+(0 rows)
+
+SELECT * FROM pg_partition_tree('ptif_li_parent');
+ relid | parentrelid | isleaf | level
+-------+-------------+--------+-------
+(0 rows)
+
+SELECT * FROM pg_partition_tree('ptif_li_child');
+ relid | parentrelid | isleaf | level
+-------+-------------+--------+-------
+(0 rows)
+
+SELECT * FROM pg_partition_ancestors('ptif_test_view');
+ relid
+-------
+(0 rows)
+
+SELECT * FROM pg_partition_ancestors('ptif_test_matview');
+ relid
+-------
+(0 rows)
+
+SELECT * FROM pg_partition_ancestors('ptif_li_parent');
+ relid
+-------
+(0 rows)
+
+SELECT * FROM pg_partition_ancestors('ptif_li_child');
+ relid
+-------
+(0 rows)
+
+SELECT pg_partition_root('ptif_test_view');
+ pg_partition_root
+-------------------
+
+(1 row)
+
+SELECT pg_partition_root('ptif_test_matview');
+ pg_partition_root
+-------------------
+
+(1 row)
+
+SELECT pg_partition_root('ptif_li_parent');
+ pg_partition_root
+-------------------
+
+(1 row)
+
+SELECT pg_partition_root('ptif_li_child');
+ pg_partition_root
+-------------------
+
+(1 row)
+
+DROP VIEW ptif_test_view;
+DROP MATERIALIZED VIEW ptif_test_matview;
+DROP TABLE ptif_li_parent, ptif_li_child;
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
new file mode 100644
index 0000000..27f7525
--- /dev/null
+++ b/src/test/regress/expected/partition_join.out
@@ -0,0 +1,4864 @@
+--
+-- PARTITION_JOIN
+-- Test partitionwise join between partitioned tables
+--
+-- Enable partitionwise join, which by default is disabled.
+SET enable_partitionwise_join to true;
+--
+-- 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
+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;
+ QUERY PLAN
+--------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Join
+ Hash Cond: (t2_1.b = t1_1.a)
+ -> Seq Scan on prt2_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_p1 t1_1
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: (t2_2.b = t1_2.a)
+ -> Seq Scan on prt2_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_p2 t1_2
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: (t2_3.b = t1_3.a)
+ -> Seq Scan on prt2_p3 t2_3
+ -> Hash
+ -> Seq Scan on prt1_p3 t1_3
+ Filter: (b = 0)
+(21 rows)
+
+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
+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;
+ QUERY PLAN
+--------------------------------------------------
+ Sort
+ Sort Key: t1.a, t2.b
+ -> Hash Right Join
+ Hash Cond: (t2.b = t1.a)
+ -> Append
+ -> Seq Scan on prt2_p1 t2_1
+ -> Seq Scan on prt2_p2 t2_2
+ -> Seq Scan on prt2_p3 t2_3
+ -> Hash
+ -> Append
+ -> Seq Scan on prt1_p1 t1_1
+ Filter: (b = 0)
+ -> Seq Scan on prt1_p2 t1_2
+ Filter: (b = 0)
+ -> Seq Scan on prt1_p3 t1_3
+ Filter: (b = 0)
+(16 rows)
+
+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
+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;
+ QUERY PLAN
+---------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t2.b
+ -> Append
+ -> Hash Right Join
+ Hash Cond: (t1_1.a = t2_1.b)
+ -> Seq Scan on prt1_p1 t1_1
+ -> Hash
+ -> Seq Scan on prt2_p1 t2_1
+ Filter: (a = 0)
+ -> Hash Right Join
+ Hash Cond: (t1_2.a = t2_2.b)
+ -> Seq Scan on prt1_p2 t1_2
+ -> Hash
+ -> Seq Scan on prt2_p2 t2_2
+ Filter: (a = 0)
+ -> Nested Loop Left Join
+ -> Seq Scan on prt2_p3 t2_3
+ Filter: (a = 0)
+ -> Index Scan using iprt1_p3_a on prt1_p3 t1_3
+ Index Cond: (a = t2_3.b)
+(20 rows)
+
+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
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_p1 prt2_1
+ 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
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_p2 prt2_2
+ 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
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_p3 prt2_3
+ 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
+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;
+ QUERY PLAN
+-----------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Hash Join
+ Hash Cond: (t2.b = t1.a)
+ -> Seq Scan on prt2_p2 t2
+ Filter: (b > 250)
+ -> Hash
+ -> Seq Scan on prt1_p2 t1
+ Filter: ((a < 450) AND (b = 0))
+(9 rows)
+
+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
+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;
+ QUERY PLAN
+-----------------------------------------------------------
+ Sort
+ Sort Key: prt1.a, prt2.b
+ -> Hash Right Join
+ Hash Cond: (prt2.b = prt1.a)
+ -> Append
+ -> Seq Scan on prt2_p2 prt2_1
+ Filter: (b > 250)
+ -> Seq Scan on prt2_p3 prt2_2
+ Filter: (b > 250)
+ -> Hash
+ -> Append
+ -> Seq Scan on prt1_p1 prt1_1
+ Filter: ((a < 450) AND (b = 0))
+ -> Seq Scan on prt1_p2 prt1_2
+ Filter: ((a < 450) AND (b = 0))
+(15 rows)
+
+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
+ Filter: (a < 450)
+ -> Seq Scan on prt1_p2 prt1_2
+ Filter: (a < 450)
+ -> Hash
+ -> Append
+ -> Seq Scan on prt2_p2 prt2_1
+ Filter: (b > 250)
+ -> Seq Scan on prt2_p3 prt2_2
+ 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
+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;
+ QUERY PLAN
+--------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Semi Join
+ Hash Cond: (t1_1.a = t2_1.b)
+ -> Seq Scan on prt1_p1 t1_1
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_p1 t2_1
+ Filter: (a = 0)
+ -> Hash Semi Join
+ Hash Cond: (t1_2.a = t2_2.b)
+ -> Seq Scan on prt1_p2 t1_2
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_p2 t2_2
+ Filter: (a = 0)
+ -> Nested Loop Semi Join
+ Join Filter: (t1_3.a = t2_3.b)
+ -> Seq Scan on prt1_p3 t1_3
+ Filter: (b = 0)
+ -> Materialize
+ -> Seq Scan on prt2_p3 t2_3
+ Filter: (a = 0)
+(24 rows)
+
+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
+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);
+ QUERY PLAN
+--------------------------------------------------
+ Aggregate
+ -> Append
+ -> Hash Anti Join
+ Hash Cond: (t1_1.a = t2_1.b)
+ -> Seq Scan on prt1_p1 t1_1
+ -> Hash
+ -> Seq Scan on prt2_p1 t2_1
+ -> Hash Anti Join
+ Hash Cond: (t1_2.a = t2_2.b)
+ -> Seq Scan on prt1_p2 t1_2
+ -> Hash
+ -> Seq Scan on prt2_p2 t2_2
+ -> Hash Anti Join
+ Hash Cond: (t1_3.a = t2_3.b)
+ -> Seq Scan on prt1_p3 t1_3
+ -> Hash
+ -> Seq Scan on prt2_p3 t2_3
+(17 rows)
+
+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
+ 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
+ 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
+ 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)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Hash Left Join
+ Hash Cond: ((t1.c)::text = (t2.c)::text)
+ Filter: ((t1.b + COALESCE(t2.b, 0)) = 0)
+ -> Append
+ -> Seq Scan on prt1_p1 t1_1
+ -> Seq Scan on prt1_p2 t1_2
+ -> Seq Scan on prt1_p3 t1_3
+ -> Hash
+ -> Append
+ -> Hash Join
+ Hash Cond: (t2_1.a = t3_1.b)
+ -> Seq Scan on prt1_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt2_p1 t3_1
+ -> Hash Join
+ Hash Cond: (t2_2.a = t3_2.b)
+ -> Seq Scan on prt1_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt2_p2 t3_2
+ -> Hash Join
+ Hash Cond: (t2_3.a = t3_3.b)
+ -> Seq Scan on prt1_p3 t2_3
+ -> Hash
+ -> Seq Scan on prt2_p3 t3_3
+(26 rows)
+
+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;
+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;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------
+ Group
+ Group Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b))
+ -> Merge Append
+ Sort Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b))
+ -> Group
+ Group Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b))
+ -> Sort
+ Sort Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b))
+ -> Merge Full Join
+ Merge Cond: ((prt1.a = p2.a) AND (prt1.b = p2.b))
+ Filter: ((COALESCE(prt1.a, p2.a) >= 490) AND (COALESCE(prt1.a, p2.a) <= 510))
+ -> Sort
+ Sort Key: prt1.a, prt1.b
+ -> Seq Scan on prt1_p1 prt1
+ -> Sort
+ Sort Key: p2.a, p2.b
+ -> Seq Scan on prt2_p1 p2
+ -> Group
+ Group Key: (COALESCE(prt1_1.a, p2_1.a)), (COALESCE(prt1_1.b, p2_1.b))
+ -> Sort
+ Sort Key: (COALESCE(prt1_1.a, p2_1.a)), (COALESCE(prt1_1.b, p2_1.b))
+ -> Merge Full Join
+ Merge Cond: ((prt1_1.a = p2_1.a) AND (prt1_1.b = p2_1.b))
+ Filter: ((COALESCE(prt1_1.a, p2_1.a) >= 490) AND (COALESCE(prt1_1.a, p2_1.a) <= 510))
+ -> Sort
+ Sort Key: prt1_1.a, prt1_1.b
+ -> Seq Scan on prt1_p2 prt1_1
+ -> Sort
+ Sort Key: p2_1.a, p2_1.b
+ -> Seq Scan on prt2_p2 p2_1
+ -> Group
+ Group Key: (COALESCE(prt1_2.a, p2_2.a)), (COALESCE(prt1_2.b, p2_2.b))
+ -> Sort
+ Sort Key: (COALESCE(prt1_2.a, p2_2.a)), (COALESCE(prt1_2.b, p2_2.b))
+ -> Merge Full Join
+ Merge Cond: ((prt1_2.a = p2_2.a) AND (prt1_2.b = p2_2.b))
+ Filter: ((COALESCE(prt1_2.a, p2_2.a) >= 490) AND (COALESCE(prt1_2.a, p2_2.a) <= 510))
+ -> Sort
+ Sort Key: prt1_2.a, prt1_2.b
+ -> Seq Scan on prt1_p3 prt1_2
+ -> Sort
+ Sort Key: p2_2.a, p2_2.b
+ -> Seq Scan on prt2_p3 p2_2
+(43 rows)
+
+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;
+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;
+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;
+ QUERY PLAN
+------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t2.b
+ -> Append
+ -> Hash Join
+ Hash Cond: (((t2_1.b + t2_1.a) / 2) = ((t1_1.a + t1_1.b) / 2))
+ -> Seq Scan on prt2_e_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_e_p1 t1_1
+ Filter: (c = 0)
+ -> Hash Join
+ Hash Cond: (((t2_2.b + t2_2.a) / 2) = ((t1_2.a + t1_2.b) / 2))
+ -> Seq Scan on prt2_e_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_e_p2 t1_2
+ Filter: (c = 0)
+ -> Hash Join
+ Hash Cond: (((t2_3.b + t2_3.a) / 2) = ((t1_3.a + t1_3.b) / 2))
+ -> Seq Scan on prt2_e_p3 t2_3
+ -> Hash
+ -> Seq Scan on prt1_e_p3 t1_3
+ Filter: (c = 0)
+(21 rows)
+
+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
+--
+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;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Nested Loop
+ Join Filter: (t1_1.a = ((t3_1.a + t3_1.b) / 2))
+ -> Hash Join
+ Hash Cond: (t2_1.b = t1_1.a)
+ -> Seq Scan on prt2_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_p1 t1_1
+ Filter: (b = 0)
+ -> Index Scan using iprt1_e_p1_ab2 on prt1_e_p1 t3_1
+ Index Cond: (((a + b) / 2) = t2_1.b)
+ -> Nested Loop
+ Join Filter: (t1_2.a = ((t3_2.a + t3_2.b) / 2))
+ -> Hash Join
+ Hash Cond: (t2_2.b = t1_2.a)
+ -> Seq Scan on prt2_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_p2 t1_2
+ Filter: (b = 0)
+ -> Index Scan using iprt1_e_p2_ab2 on prt1_e_p2 t3_2
+ Index Cond: (((a + b) / 2) = t2_2.b)
+ -> Nested Loop
+ Join Filter: (t1_3.a = ((t3_3.a + t3_3.b) / 2))
+ -> Hash Join
+ Hash Cond: (t2_3.b = t1_3.a)
+ -> Seq Scan on prt2_p3 t2_3
+ -> Hash
+ -> Seq Scan on prt1_p3 t1_3
+ Filter: (b = 0)
+ -> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t3_3
+ Index Cond: (((a + b) / 2) = t2_3.b)
+(33 rows)
+
+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)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+ -> Append
+ -> Hash Right Join
+ Hash Cond: (((t3_1.a + t3_1.b) / 2) = t1_1.a)
+ -> Seq Scan on prt1_e_p1 t3_1
+ -> Hash
+ -> Hash Right Join
+ Hash Cond: (t2_1.b = t1_1.a)
+ -> Seq Scan on prt2_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_p1 t1_1
+ Filter: (b = 0)
+ -> Hash Right Join
+ Hash Cond: (((t3_2.a + t3_2.b) / 2) = t1_2.a)
+ -> Seq Scan on prt1_e_p2 t3_2
+ -> Hash
+ -> Hash Right Join
+ Hash Cond: (t2_2.b = t1_2.a)
+ -> Seq Scan on prt2_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_p2 t1_2
+ Filter: (b = 0)
+ -> Hash Right Join
+ Hash Cond: (((t3_3.a + t3_3.b) / 2) = t1_3.a)
+ -> Seq Scan on prt1_e_p3 t3_3
+ -> Hash
+ -> Hash Right Join
+ Hash Cond: (t2_3.b = t1_3.a)
+ -> Seq Scan on prt2_p3 t2_3
+ -> Hash
+ -> Seq Scan on prt1_p3 t1_3
+ Filter: (b = 0)
+(33 rows)
+
+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)
+
+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;
+ QUERY PLAN
+-------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+ -> Append
+ -> Nested Loop Left Join
+ -> Hash Right Join
+ Hash Cond: (t1_1.a = ((t3_1.a + t3_1.b) / 2))
+ -> Seq Scan on prt1_p1 t1_1
+ -> Hash
+ -> Seq Scan on prt1_e_p1 t3_1
+ Filter: (c = 0)
+ -> Index Scan using iprt2_p1_b on prt2_p1 t2_1
+ Index Cond: (b = t1_1.a)
+ -> Nested Loop Left Join
+ -> Hash Right Join
+ Hash Cond: (t1_2.a = ((t3_2.a + t3_2.b) / 2))
+ -> Seq Scan on prt1_p2 t1_2
+ -> Hash
+ -> Seq Scan on prt1_e_p2 t3_2
+ Filter: (c = 0)
+ -> Index Scan using iprt2_p2_b on prt2_p2 t2_2
+ Index Cond: (b = t1_2.a)
+ -> Nested Loop Left Join
+ -> Hash Right Join
+ Hash Cond: (t1_3.a = ((t3_3.a + t3_3.b) / 2))
+ -> Seq Scan on prt1_p3 t1_3
+ -> Hash
+ -> Seq Scan on prt1_e_p3 t3_3
+ Filter: (c = 0)
+ -> Index Scan using iprt2_p3_b on prt2_p3 t2_3
+ Index Cond: (b = t1_3.a)
+(30 rows)
+
+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
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_p1 prt2_1
+ Filter: (a = 0)
+ -> Hash
+ -> Seq Scan on prt1_e_p1 prt1_e_1
+ 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
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_p2 prt2_2
+ Filter: (a = 0)
+ -> Hash
+ -> Seq Scan on prt1_e_p2 prt1_e_2
+ 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
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_p3 prt2_3
+ Filter: (a = 0)
+ -> Hash
+ -> Seq Scan on prt1_e_p3 prt1_e_3
+ 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
+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;
+ QUERY PLAN
+---------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Nested Loop
+ Join Filter: (t1_2.a = t1_5.b)
+ -> HashAggregate
+ Group Key: t1_5.b
+ -> Hash Join
+ Hash Cond: (((t2_1.a + t2_1.b) / 2) = t1_5.b)
+ -> Seq Scan on prt1_e_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt2_p1 t1_5
+ Filter: (a = 0)
+ -> Index Scan using iprt1_p1_a on prt1_p1 t1_2
+ Index Cond: (a = ((t2_1.a + t2_1.b) / 2))
+ Filter: (b = 0)
+ -> Nested Loop
+ Join Filter: (t1_3.a = t1_6.b)
+ -> HashAggregate
+ Group Key: t1_6.b
+ -> Hash Join
+ Hash Cond: (((t2_2.a + t2_2.b) / 2) = t1_6.b)
+ -> Seq Scan on prt1_e_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt2_p2 t1_6
+ Filter: (a = 0)
+ -> Index Scan using iprt1_p2_a on prt1_p2 t1_3
+ Index Cond: (a = ((t2_2.a + t2_2.b) / 2))
+ Filter: (b = 0)
+ -> Nested Loop
+ Join Filter: (t1_4.a = t1_7.b)
+ -> HashAggregate
+ Group Key: t1_7.b
+ -> Nested Loop
+ -> Seq Scan on prt2_p3 t1_7
+ Filter: (a = 0)
+ -> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t2_3
+ Index Cond: (((a + b) / 2) = t1_7.b)
+ -> Index Scan using iprt1_p3_a on prt1_p3 t1_4
+ Index Cond: (a = ((t2_3.a + t2_3.b) / 2))
+ Filter: (b = 0)
+(41 rows)
+
+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)
+
+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;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Nested Loop
+ -> HashAggregate
+ Group Key: t1_6.b
+ -> Hash Semi Join
+ Hash Cond: (t1_6.b = ((t1_9.a + t1_9.b) / 2))
+ -> Seq Scan on prt2_p1 t1_6
+ -> Hash
+ -> Seq Scan on prt1_e_p1 t1_9
+ Filter: (c = 0)
+ -> Index Scan using iprt1_p1_a on prt1_p1 t1_3
+ Index Cond: (a = t1_6.b)
+ Filter: (b = 0)
+ -> Nested Loop
+ -> HashAggregate
+ Group Key: t1_7.b
+ -> Hash Semi Join
+ Hash Cond: (t1_7.b = ((t1_10.a + t1_10.b) / 2))
+ -> Seq Scan on prt2_p2 t1_7
+ -> Hash
+ -> Seq Scan on prt1_e_p2 t1_10
+ Filter: (c = 0)
+ -> Index Scan using iprt1_p2_a on prt1_p2 t1_4
+ Index Cond: (a = t1_7.b)
+ Filter: (b = 0)
+ -> Nested Loop
+ -> HashAggregate
+ Group Key: t1_8.b
+ -> Hash Semi Join
+ Hash Cond: (t1_8.b = ((t1_11.a + t1_11.b) / 2))
+ -> Seq Scan on prt2_p3 t1_8
+ -> Hash
+ -> Seq Scan on prt1_e_p3 t1_11
+ Filter: (c = 0)
+ -> Index Scan using iprt1_p3_a on prt1_p3 t1_5
+ Index Cond: (a = t1_8.b)
+ Filter: (b = 0)
+(39 rows)
+
+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;
+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;
+ QUERY PLAN
+------------------------------------------------------------------
+ Merge Append
+ Sort Key: t1.a
+ -> Merge Semi Join
+ Merge Cond: (t1_3.a = t1_6.b)
+ -> Sort
+ Sort Key: t1_3.a
+ -> Seq Scan on prt1_p1 t1_3
+ Filter: (b = 0)
+ -> Merge Semi Join
+ Merge Cond: (t1_6.b = (((t1_9.a + t1_9.b) / 2)))
+ -> Sort
+ Sort Key: t1_6.b
+ -> Seq Scan on prt2_p1 t1_6
+ -> Sort
+ Sort Key: (((t1_9.a + t1_9.b) / 2))
+ -> Seq Scan on prt1_e_p1 t1_9
+ Filter: (c = 0)
+ -> Merge Semi Join
+ Merge Cond: (t1_4.a = t1_7.b)
+ -> Sort
+ Sort Key: t1_4.a
+ -> Seq Scan on prt1_p2 t1_4
+ Filter: (b = 0)
+ -> Merge Semi Join
+ Merge Cond: (t1_7.b = (((t1_10.a + t1_10.b) / 2)))
+ -> Sort
+ Sort Key: t1_7.b
+ -> Seq Scan on prt2_p2 t1_7
+ -> Sort
+ Sort Key: (((t1_10.a + t1_10.b) / 2))
+ -> Seq Scan on prt1_e_p2 t1_10
+ Filter: (c = 0)
+ -> Merge Semi Join
+ Merge Cond: (t1_5.a = t1_8.b)
+ -> Sort
+ Sort Key: t1_5.a
+ -> Seq Scan on prt1_p3 t1_5
+ Filter: (b = 0)
+ -> Merge Semi Join
+ Merge Cond: (t1_8.b = (((t1_11.a + t1_11.b) / 2)))
+ -> Sort
+ Sort Key: t1_8.b
+ -> Seq Scan on prt2_p3 t1_8
+ -> Sort
+ Sort Key: (((t1_11.a + t1_11.b) / 2))
+ -> Seq Scan on prt1_e_p3 t1_11
+ Filter: (c = 0)
+(47 rows)
+
+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)
+
+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;
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+ -> Append
+ -> Merge Left Join
+ Merge Cond: (t1_1.a = t2_1.b)
+ -> Sort
+ Sort Key: t1_1.a
+ -> Merge Left Join
+ Merge Cond: ((((t3_1.a + t3_1.b) / 2)) = t1_1.a)
+ -> Sort
+ Sort Key: (((t3_1.a + t3_1.b) / 2))
+ -> Seq Scan on prt1_e_p1 t3_1
+ Filter: (c = 0)
+ -> Sort
+ Sort Key: t1_1.a
+ -> Seq Scan on prt1_p1 t1_1
+ -> Sort
+ Sort Key: t2_1.b
+ -> Seq Scan on prt2_p1 t2_1
+ -> Merge Left Join
+ Merge Cond: (t1_2.a = t2_2.b)
+ -> Sort
+ Sort Key: t1_2.a
+ -> Merge Left Join
+ Merge Cond: ((((t3_2.a + t3_2.b) / 2)) = t1_2.a)
+ -> Sort
+ Sort Key: (((t3_2.a + t3_2.b) / 2))
+ -> Seq Scan on prt1_e_p2 t3_2
+ Filter: (c = 0)
+ -> Sort
+ Sort Key: t1_2.a
+ -> Seq Scan on prt1_p2 t1_2
+ -> Sort
+ Sort Key: t2_2.b
+ -> Seq Scan on prt2_p2 t2_2
+ -> Merge Left Join
+ Merge Cond: (t1_3.a = t2_3.b)
+ -> Sort
+ Sort Key: t1_3.a
+ -> Merge Left Join
+ Merge Cond: ((((t3_3.a + t3_3.b) / 2)) = t1_3.a)
+ -> Sort
+ Sort Key: (((t3_3.a + t3_3.b) / 2))
+ -> Seq Scan on prt1_e_p3 t3_3
+ Filter: (c = 0)
+ -> Sort
+ Sort Key: t1_3.a
+ -> Seq Scan on prt1_p3 t1_3
+ -> Sort
+ Sort Key: t2_3.b
+ -> Seq Scan on prt2_p3 t2_3
+(51 rows)
+
+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
+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;
+ QUERY PLAN
+-----------------------------------------------------------
+ Sort
+ Sort Key: prt1.a, prt2.b
+ -> Merge Left Join
+ Merge Cond: (prt1.a = prt2.b)
+ -> Sort
+ Sort Key: prt1.a
+ -> Append
+ -> Seq Scan on prt1_p1 prt1_1
+ Filter: ((a < 450) AND (b = 0))
+ -> Seq Scan on prt1_p2 prt1_2
+ Filter: ((a < 450) AND (b = 0))
+ -> Sort
+ Sort Key: prt2.b
+ -> Append
+ -> Seq Scan on prt2_p2 prt2_1
+ Filter: (b > 250)
+ -> Seq Scan on prt2_p3 prt2_2
+ Filter: (b > 250)
+(18 rows)
+
+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
+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;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Merge Join
+ Merge Cond: ((t1.a = t2.b) AND (((((t1.*)::prt1))::text) = ((((t2.*)::prt2))::text)))
+ -> Sort
+ Sort Key: t1.a, ((((t1.*)::prt1))::text)
+ -> Result
+ -> Append
+ -> Seq Scan on prt1_p1 t1_1
+ -> Seq Scan on prt1_p2 t1_2
+ -> Seq Scan on prt1_p3 t1_3
+ -> Sort
+ Sort Key: t2.b, ((((t2.*)::prt2))::text)
+ -> Result
+ -> Append
+ -> Seq Scan on prt2_p1 t2_1
+ -> Seq Scan on prt2_p2 t2_2
+ -> Seq Scan on prt2_p3 t2_3
+(16 rows)
+
+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
+ Filter: (c = 0)
+ -> Hash
+ -> Seq Scan on prt2_m_p1 prt2_m_1
+ 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
+ Filter: (c = 0)
+ -> Hash
+ -> Seq Scan on prt2_m_p2 prt2_m_2
+ 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
+ Filter: (c = 0)
+ -> Hash
+ -> Seq Scan on prt2_m_p3 prt2_m_3
+ 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
+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;
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ GroupAggregate
+ Group Key: t1.c, t2.c, t3.c
+ -> Sort
+ Sort Key: t1.c, t3.c
+ -> Append
+ -> Hash Join
+ Hash Cond: (t1_1.c = ltrim(t3_1.c, 'A'::text))
+ -> Hash Join
+ Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
+ -> Seq Scan on plt1_p1 t1_1
+ -> Hash
+ -> Seq Scan on plt2_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_e_p1 t3_1
+ -> Hash Join
+ Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text))
+ -> Hash Join
+ Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
+ -> Seq Scan on plt1_p2 t1_2
+ -> Hash
+ -> Seq Scan on plt2_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_e_p2 t3_2
+ -> Hash Join
+ Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text))
+ -> Hash Join
+ Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
+ -> Seq Scan on plt1_p3 t1_3
+ -> Hash
+ -> Seq Scan on plt2_p3 t2_3
+ -> Hash
+ -> Seq Scan on plt1_e_p3 t3_3
+(32 rows)
+
+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)
+
+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;
+ QUERY PLAN
+--------------------------------------------------
+ Hash Left Join
+ Hash Cond: (t2.b = a)
+ -> Append
+ -> Hash Join
+ Hash Cond: (t3_1.a = t2_1.b)
+ -> Seq Scan on prt1_p1 t3_1
+ -> Hash
+ -> Seq Scan on prt2_p1 t2_1
+ -> Hash Join
+ Hash Cond: (t3_2.a = t2_2.b)
+ -> Seq Scan on prt1_p2 t3_2
+ -> Hash
+ -> Seq Scan on prt2_p2 t2_2
+ -> Hash Join
+ Hash Cond: (t3_3.a = t2_3.b)
+ -> Seq Scan on prt1_p3 t3_3
+ -> Hash
+ -> Seq Scan on prt2_p3 t2_3
+ -> Hash
+ -> Result
+ One-Time Filter: false
+(21 rows)
+
+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;
+ QUERY PLAN
+--------------------------------------------
+ Sort
+ Sort Key: a, t2.b
+ -> Hash Left Join
+ Hash Cond: (t2.b = a)
+ -> Append
+ -> Seq Scan on prt2_p1 t2_1
+ Filter: (a = 0)
+ -> Seq Scan on prt2_p2 t2_2
+ Filter: (a = 0)
+ -> Seq Scan on prt2_p3 t2_3
+ Filter: (a = 0)
+ -> Hash
+ -> Result
+ One-Time Filter: false
+(14 rows)
+
+--
+-- 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
+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;
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ GroupAggregate
+ Group Key: t1.c, t2.c, t3.c
+ -> Sort
+ Sort Key: t1.c, t3.c
+ -> Append
+ -> Hash Join
+ Hash Cond: (t1_1.c = ltrim(t3_1.c, 'A'::text))
+ -> Hash Join
+ Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
+ -> Seq Scan on pht1_p1 t1_1
+ -> Hash
+ -> Seq Scan on pht2_p1 t2_1
+ -> Hash
+ -> Seq Scan on pht1_e_p1 t3_1
+ -> Hash Join
+ Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text))
+ -> Hash Join
+ Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
+ -> Seq Scan on pht1_p2 t1_2
+ -> Hash
+ -> Seq Scan on pht2_p2 t2_2
+ -> Hash
+ -> Seq Scan on pht1_e_p2 t3_2
+ -> Hash Join
+ Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text))
+ -> Hash Join
+ Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
+ -> Seq Scan on pht1_p3 t1_3
+ -> Hash
+ -> Seq Scan on pht2_p3 t2_3
+ -> Hash
+ -> Seq Scan on pht1_e_p3 t3_3
+(32 rows)
+
+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;
+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;
+ QUERY PLAN
+--------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Join
+ Hash Cond: (t2_1.b = t1_1.a)
+ -> Seq Scan on prt2_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_p1 t1_1
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: (t2_2.b = t1_2.a)
+ -> Seq Scan on prt2_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_p2 t1_2
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: (t2_3.b = t1_3.a)
+ -> Seq Scan on prt2_p3 t2_3
+ -> Hash
+ -> Seq Scan on prt1_p3 t1_3
+ Filter: (b = 0)
+(21 rows)
+
+-- 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;
+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;
+ QUERY PLAN
+--------------------------------------------------------
+ Sort
+ Sort Key: t1.c
+ -> HashAggregate
+ Group Key: t1.c, t2.c
+ -> Append
+ -> Hash Join
+ Hash Cond: (t2_1.c = t1_1.c)
+ -> Seq Scan on plt2_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_p1 t1_1
+ Filter: ((a % 25) = 0)
+ -> Hash Join
+ Hash Cond: (t2_2.c = t1_2.c)
+ -> Seq Scan on plt2_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_p2 t1_2
+ Filter: ((a % 25) = 0)
+ -> Hash Join
+ Hash Cond: (t2_3.c = t1_3.c)
+ -> Seq Scan on plt2_p3 t2_3
+ -> Hash
+ -> Seq Scan on plt1_p3 t1_3
+ Filter: ((a % 25) = 0)
+(23 rows)
+
+--
+-- 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
+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;
+ QUERY PLAN
+-------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Join
+ Hash Cond: (t2_1.b = t1_1.a)
+ -> Seq Scan on prt2_l_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_l_p1 t1_1
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: (t2_3.b = t1_3.a)
+ -> Append
+ -> Seq Scan on prt2_l_p2_p1 t2_3
+ -> Seq Scan on prt2_l_p2_p2 t2_4
+ -> Hash
+ -> Append
+ -> Seq Scan on prt1_l_p2_p1 t1_3
+ Filter: (b = 0)
+ -> Seq Scan on prt1_l_p2_p2 t1_4
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: (t2_6.b = t1_5.a)
+ -> Append
+ -> Seq Scan on prt2_l_p3_p1 t2_6
+ -> Seq Scan on prt2_l_p3_p2 t2_7
+ -> Hash
+ -> Seq Scan on prt1_l_p3_p1 t1_5
+ Filter: (b = 0)
+(28 rows)
+
+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
+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;
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t2.b
+ -> Append
+ -> Hash Right Join
+ Hash Cond: ((t2_1.b = t1_1.a) AND ((t2_1.c)::text = (t1_1.c)::text))
+ -> Seq Scan on prt2_l_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_l_p1 t1_1
+ Filter: (b = 0)
+ -> Hash Right Join
+ Hash Cond: ((t2_2.b = t1_2.a) AND ((t2_2.c)::text = (t1_2.c)::text))
+ -> Seq Scan on prt2_l_p2_p1 t2_2
+ -> Hash
+ -> Seq Scan on prt1_l_p2_p1 t1_2
+ Filter: (b = 0)
+ -> Hash Right Join
+ Hash Cond: ((t2_3.b = t1_3.a) AND ((t2_3.c)::text = (t1_3.c)::text))
+ -> Seq Scan on prt2_l_p2_p2 t2_3
+ -> Hash
+ -> Seq Scan on prt1_l_p2_p2 t1_3
+ Filter: (b = 0)
+ -> Hash Right Join
+ Hash Cond: ((t2_5.b = t1_4.a) AND ((t2_5.c)::text = (t1_4.c)::text))
+ -> Append
+ -> Seq Scan on prt2_l_p3_p1 t2_5
+ -> Seq Scan on prt2_l_p3_p2 t2_6
+ -> Hash
+ -> Seq Scan on prt1_l_p3_p1 t1_4
+ Filter: (b = 0)
+(29 rows)
+
+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
+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;
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t2.b
+ -> Append
+ -> Hash Right Join
+ Hash Cond: ((t1_1.a = t2_1.b) AND ((t1_1.c)::text = (t2_1.c)::text))
+ -> Seq Scan on prt1_l_p1 t1_1
+ -> Hash
+ -> Seq Scan on prt2_l_p1 t2_1
+ Filter: (a = 0)
+ -> Hash Right Join
+ Hash Cond: ((t1_2.a = t2_2.b) AND ((t1_2.c)::text = (t2_2.c)::text))
+ -> Seq Scan on prt1_l_p2_p1 t1_2
+ -> Hash
+ -> Seq Scan on prt2_l_p2_p1 t2_2
+ Filter: (a = 0)
+ -> Hash Right Join
+ Hash Cond: ((t1_3.a = t2_3.b) AND ((t1_3.c)::text = (t2_3.c)::text))
+ -> Seq Scan on prt1_l_p2_p2 t1_3
+ -> Hash
+ -> Seq Scan on prt2_l_p2_p2 t2_3
+ Filter: (a = 0)
+ -> Hash Right Join
+ Hash Cond: ((t1_5.a = t2_4.b) AND ((t1_5.c)::text = (t2_4.c)::text))
+ -> Append
+ -> Seq Scan on prt1_l_p3_p1 t1_5
+ -> Seq Scan on prt1_l_p3_p2 t1_6
+ -> Hash
+ -> Seq Scan on prt2_l_p3_p1 t2_4
+ Filter: (a = 0)
+(29 rows)
+
+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
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_l_p1 prt2_l_1
+ Filter: (a = 0)
+ -> Hash Full Join
+ Hash Cond: ((prt1_l_2.a = prt2_l_2.b) AND ((prt1_l_2.c)::text = (prt2_l_2.c)::text))
+ -> Seq Scan on prt1_l_p2_p1 prt1_l_2
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_l_p2_p1 prt2_l_2
+ 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_p2 prt1_l_3
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_l_p2_p2 prt2_l_3
+ 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_p3_p1 prt1_l_4
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_l_p3_p1 prt2_l_4
+ 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
+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;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Nested Loop Left Join
+ -> Seq Scan on prt1_l_p1 t1_1
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: ((t3_1.b = t2_1.a) AND ((t3_1.c)::text = (t2_1.c)::text))
+ -> Seq Scan on prt2_l_p1 t3_1
+ -> Hash
+ -> Seq Scan on prt1_l_p1 t2_1
+ Filter: ((t1_1.a = a) AND ((t1_1.c)::text = (c)::text))
+ -> Nested Loop Left Join
+ -> Seq Scan on prt1_l_p2_p1 t1_2
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: ((t3_2.b = t2_2.a) AND ((t3_2.c)::text = (t2_2.c)::text))
+ -> Seq Scan on prt2_l_p2_p1 t3_2
+ -> Hash
+ -> Seq Scan on prt1_l_p2_p1 t2_2
+ Filter: ((t1_2.a = a) AND ((t1_2.c)::text = (c)::text))
+ -> Nested Loop Left Join
+ -> Seq Scan on prt1_l_p2_p2 t1_3
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: ((t3_3.b = t2_3.a) AND ((t3_3.c)::text = (t2_3.c)::text))
+ -> Seq Scan on prt2_l_p2_p2 t3_3
+ -> Hash
+ -> Seq Scan on prt1_l_p2_p2 t2_3
+ Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text))
+ -> Nested Loop Left Join
+ -> Seq Scan on prt1_l_p3_p1 t1_4
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: ((t3_5.b = t2_5.a) AND ((t3_5.c)::text = (t2_5.c)::text))
+ -> Append
+ -> Seq Scan on prt2_l_p3_p1 t3_5
+ -> Seq Scan on prt2_l_p3_p2 t3_6
+ -> Hash
+ -> Append
+ -> Seq Scan on prt1_l_p3_p1 t2_5
+ Filter: ((t1_4.a = a) AND ((t1_4.c)::text = (c)::text))
+ -> Seq Scan on prt1_l_p3_p2 t2_6
+ Filter: ((t1_4.a = a) AND ((t1_4.c)::text = (c)::text))
+(44 rows)
+
+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_2
+ -> Seq Scan on prt2_l_p2_p2 t2_3
+ -> Seq Scan on prt2_l_p3_p1 t2_4
+ -> Seq Scan on prt2_l_p3_p2 t2_5
+ -> Hash
+ -> Result
+ 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.
+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);
+ QUERY PLAN
+----------------------------------------------------------
+ Delete on prt1_l
+ Delete on prt1_l_p1 prt1_l_1
+ Delete on prt1_l_p3_p1 prt1_l_2
+ Delete on prt1_l_p3_p2 prt1_l_3
+ -> Nested Loop Semi Join
+ -> Append
+ -> Seq Scan on prt1_l_p1 prt1_l_1
+ Filter: (c IS NULL)
+ -> Seq Scan on prt1_l_p3_p1 prt1_l_2
+ Filter: (c IS NULL)
+ -> Seq Scan on prt1_l_p3_p2 prt1_l_3
+ Filter: (c IS NULL)
+ -> Materialize
+ -> Nested Loop
+ -> Seq Scan on int4_tbl
+ -> Subquery Scan on ss
+ -> Limit
+ -> Seq Scan on int8_tbl
+(18 rows)
+
+--
+-- 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)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------
+ Hash Join
+ Hash Cond: (t2.a = t1.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
+ -> Hash
+ -> Append
+ -> Hash Join
+ Hash Cond: (t1_1.a = t3_1.b)
+ -> Seq Scan on prt1_p1 t1_1
+ -> Hash
+ -> Seq Scan on prt2_p1 t3_1
+ -> Hash Join
+ Hash Cond: (t1_2.a = t3_2.b)
+ -> Seq Scan on prt1_p2 t1_2
+ -> Hash
+ -> Seq Scan on prt2_p2 t3_2
+ -> Hash Join
+ Hash Cond: (t1_3.a = t3_3.b)
+ -> Seq Scan on prt1_p3 t1_3
+ -> Hash
+ -> Seq Scan on prt2_p3 t3_3
+(23 rows)
+
+-- 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);
+ QUERY PLAN
+---------------------------------------------------------
+ Nested Loop Left Join
+ -> Append
+ -> Seq Scan on prt1_p1 t1_1
+ -> Seq Scan on prt1_p2 t1_2
+ -> Seq Scan on prt1_p3 t1_3
+ -> Append
+ -> Index Scan using iprt2_p1_b on prt2_p1 t2_1
+ Index Cond: (b > t1.a)
+ -> Index Scan using iprt2_p2_b on prt2_p2 t2_2
+ Index Cond: (b > t1.a)
+ -> Index Scan using iprt2_p3_b on prt2_p3 t2_3
+ Index Cond: (b > t1.a)
+(12 rows)
+
+-- 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;
+ QUERY PLAN
+----------------------------------------------
+ Hash Join
+ Hash Cond: (((t2.b + t2.a) / 2) = t1.a)
+ -> 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
+ -> Hash
+ -> 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
+(11 rows)
+
+-- 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
+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);
+ QUERY PLAN
+----------------------------------------------------------
+ Hash 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
+ -> Hash Join
+ Hash Cond: (t3.c = (t1.c)::text)
+ -> Append
+ -> Seq Scan on plt1_p1 t3_1
+ -> Seq Scan on plt1_p2 t3_2
+ -> Seq Scan on plt1_p3 t3_3
+ -> Hash
+ -> Append
+ -> Seq Scan on prt1_n_p1 t1_1
+ -> Seq Scan on prt1_n_p2 t1_2
+(16 rows)
+
+-- 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;
+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
+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;
+ QUERY PLAN
+------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Join
+ Hash Cond: (t2_1.b = t1_1.a)
+ -> Seq Scan on prt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_adv_p1 t1_1
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: (t2_2.b = t1_2.a)
+ -> Seq Scan on prt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_adv_p2 t1_2
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: (t2_3.b = t1_3.a)
+ -> Seq Scan on prt2_adv_p3 t2_3
+ -> Hash
+ -> Seq Scan on prt1_adv_p3 t1_3
+ Filter: (b = 0)
+(21 rows)
+
+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
+ 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
+ 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
+ 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
+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;
+ QUERY PLAN
+------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t2.b
+ -> Append
+ -> Hash Right Join
+ Hash Cond: (t2_1.b = t1_1.a)
+ -> Seq Scan on prt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_adv_p1 t1_1
+ Filter: (b = 0)
+ -> Hash Right Join
+ Hash Cond: (t2_2.b = t1_2.a)
+ -> Seq Scan on prt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_adv_p2 t1_2
+ Filter: (b = 0)
+ -> Hash Right Join
+ Hash Cond: (t2_3.b = t1_3.a)
+ -> Seq Scan on prt2_adv_p3 t2_3
+ -> Hash
+ -> Seq Scan on prt1_adv_p3 t1_3
+ Filter: (b = 0)
+(21 rows)
+
+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
+ 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
+ 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
+ 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
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_adv_p1 prt2_adv_1
+ 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
+ Filter: (b = 0)
+ -> Hash
+ -> Seq Scan on prt2_adv_p2 prt2_adv_2
+ 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
+ Filter: (a = 0)
+ -> Hash
+ -> Seq Scan on prt1_adv_p3 prt1_adv_3
+ 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
+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;
+ QUERY PLAN
+------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Join
+ Hash Cond: (t2_1.b = t1_1.a)
+ -> Seq Scan on prt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_adv_p1 t1_1
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: (t2_2.b = t1_2.a)
+ -> Seq Scan on prt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_adv_p2 t1_2
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: (t2_3.b = t1_3.a)
+ -> Seq Scan on prt2_adv_p3 t2_3
+ -> Hash
+ -> Seq Scan on prt1_adv_p3 t1_3
+ Filter: (b = 0)
+(21 rows)
+
+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
+ 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
+ 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
+ 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
+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;
+ QUERY PLAN
+------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t2.b
+ -> Append
+ -> Hash Right Join
+ Hash Cond: (t2_1.b = t1_1.a)
+ -> Seq Scan on prt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_adv_p1 t1_1
+ Filter: (b = 0)
+ -> Hash Right Join
+ Hash Cond: (t2_2.b = t1_2.a)
+ -> Seq Scan on prt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_adv_p2 t1_2
+ Filter: (b = 0)
+ -> Hash Right Join
+ Hash Cond: (t2_3.b = t1_3.a)
+ -> Seq Scan on prt2_adv_p3 t2_3
+ -> Hash
+ -> Seq Scan on prt1_adv_p3 t1_3
+ Filter: (b = 0)
+(21 rows)
+
+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
+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;
+ QUERY PLAN
+---------------------------------------------------------
+ Sort
+ Sort Key: t1.b, t2.a
+ -> Hash Right Join
+ Hash Cond: (t2.a = t1.b)
+ -> 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
+ -> Hash
+ -> Append
+ -> Seq Scan on prt2_adv_p1 t1_1
+ Filter: (a = 0)
+ -> Seq Scan on prt2_adv_p2 t1_2
+ Filter: (a = 0)
+ -> Seq Scan on prt2_adv_p3 t1_3
+ Filter: (a = 0)
+ -> Seq Scan on prt2_adv_extra t1_4
+ Filter: (a = 0)
+(18 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
+ 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
+ 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
+ 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
+ Filter: (a = 0)
+ -> Seq Scan on prt2_adv_p2 t1_2
+ Filter: (a = 0)
+ -> Seq Scan on prt2_adv_p3 t1_3
+ Filter: (a = 0)
+ -> Seq Scan on prt2_adv_extra t1_4
+ 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
+ Filter: (a = 0)
+ -> Seq Scan on prt2_adv_p2 prt2_adv_2
+ Filter: (a = 0)
+ -> Seq Scan on prt2_adv_p3 prt2_adv_3
+ Filter: (a = 0)
+ -> Seq Scan on prt2_adv_extra prt2_adv_4
+ Filter: (a = 0)
+ -> Hash
+ -> Append
+ -> Seq Scan on prt1_adv_p1 prt1_adv_1
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p2 prt1_adv_2
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p3 prt1_adv_3
+ Filter: (b = 0)
+(22 rows)
+
+-- 3-way join where not every pair of relations can do partitioned join
+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;
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.b, t2.a
+ -> Append
+ -> Nested Loop Left Join
+ -> Nested Loop
+ -> Seq Scan on prt2_adv_p1 t1_1
+ Filter: (a = 0)
+ -> Index Scan using prt1_adv_p1_a_idx on prt1_adv_p1 t3_1
+ Index Cond: (a = t1_1.b)
+ -> Index Scan using prt1_adv_p1_a_idx on prt1_adv_p1 t2_1
+ Index Cond: (a = t1_1.b)
+ -> Hash Right Join
+ Hash Cond: (t2_2.a = t1_2.b)
+ -> Seq Scan on prt1_adv_p2 t2_2
+ -> Hash
+ -> Hash Join
+ Hash Cond: (t3_2.a = t1_2.b)
+ -> Seq Scan on prt1_adv_p2 t3_2
+ -> Hash
+ -> Seq Scan on prt2_adv_p2 t1_2
+ Filter: (a = 0)
+ -> Hash Right Join
+ Hash Cond: (t2_3.a = t1_3.b)
+ -> Seq Scan on prt1_adv_p3 t2_3
+ -> Hash
+ -> Hash Join
+ Hash Cond: (t3_3.a = t1_3.b)
+ -> Seq Scan on prt1_adv_p3 t3_3
+ -> Hash
+ -> Seq Scan on prt2_adv_p3 t1_3
+ Filter: (a = 0)
+(31 rows)
+
+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
+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;
+ QUERY PLAN
+------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Hash Join
+ Hash Cond: (t2.b = t1.a)
+ -> 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
+ -> Hash
+ -> Append
+ -> Seq Scan on prt1_adv_p1 t1_1
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p2 t1_2
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p3 t1_3
+ Filter: (b = 0)
+(17 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
+ -> Hash Semi Join
+ Hash Cond: (t1.a = t2.b)
+ -> Append
+ -> Seq Scan on prt1_adv_p1 t1_1
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p2 t1_2
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p3 t1_3
+ 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)
+
+-- 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;
+ QUERY PLAN
+------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t2.b
+ -> Hash Right Join
+ Hash Cond: (t2.b = t1.a)
+ -> 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
+ -> Hash
+ -> Append
+ -> Seq Scan on prt1_adv_p1 t1_1
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p2 t1_2
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p3 t1_3
+ Filter: (b = 0)
+(17 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
+ -> Hash Anti Join
+ Hash Cond: (t1.a = t2.b)
+ -> Append
+ -> Seq Scan on prt1_adv_p1 t1_1
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p2 t1_2
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p3 t1_3
+ 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
+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
+ Filter: (a = 0)
+ -> Seq Scan on prt2_adv_p2 prt2_adv_2
+ Filter: (a = 0)
+ -> Seq Scan on prt2_adv_p3_1 prt2_adv_3
+ Filter: (a = 0)
+ -> Seq Scan on prt2_adv_p3_2 prt2_adv_4
+ Filter: (a = 0)
+ -> Hash
+ -> Append
+ -> Seq Scan on prt1_adv_p1 prt1_adv_1
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p2 prt1_adv_2
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p3 prt1_adv_3
+ Filter: (b = 0)
+(22 rows)
+
+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
+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;
+ QUERY PLAN
+------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Join
+ Hash Cond: (t2_1.b = t1_2.a)
+ -> Seq Scan on prt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_adv_p1 t1_2
+ Filter: (b = 0)
+ -> Hash Join
+ Hash Cond: (t2_2.b = t1_1.a)
+ -> Seq Scan on prt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_adv_p2 t1_1
+ Filter: (b = 0)
+(15 rows)
+
+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
+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;
+ QUERY PLAN
+------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Hash Join
+ Hash Cond: (t2.b = t1.a)
+ -> Append
+ -> Seq Scan on prt2_adv_p1 t2_1
+ -> Seq Scan on prt2_adv_p2 t2_2
+ -> Seq Scan on prt2_adv_p3 t2_3
+ -> Hash
+ -> Append
+ -> Seq Scan on prt1_adv_p2 t1_1
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p3 t1_2
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p1 t1_3
+ Filter: (b = 0)
+(16 rows)
+
+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
+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;
+ QUERY PLAN
+------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Hash Join
+ Hash Cond: (t2.b = t1.a)
+ -> Append
+ -> Seq Scan on prt2_adv_p1 t2_1
+ -> Seq Scan on prt2_adv_p2 t2_2
+ -> Seq Scan on prt2_adv_p3 t2_3
+ -> Hash
+ -> Append
+ -> Seq Scan on prt1_adv_p2 t1_1
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p3 t1_2
+ Filter: (b = 0)
+ -> Seq Scan on prt1_adv_p1 t1_3
+ Filter: (b = 0)
+(16 rows)
+
+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
+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;
+ QUERY PLAN
+------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t2.b, t3.a
+ -> Append
+ -> Hash Right Join
+ Hash Cond: (t3_1.a = t1_1.a)
+ -> Seq Scan on prt3_adv_p1 t3_1
+ -> Hash
+ -> Hash Right Join
+ Hash Cond: (t2_2.b = t1_1.a)
+ -> Seq Scan on prt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_adv_p2 t1_1
+ Filter: (b = 0)
+ -> Hash Right Join
+ Hash Cond: (t3_2.a = t1_2.a)
+ -> Seq Scan on prt3_adv_p2 t3_2
+ -> Hash
+ -> Hash Right Join
+ Hash Cond: (t2_1.b = t1_2.a)
+ -> Seq Scan on prt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_adv_p1 t1_2
+ Filter: (b = 0)
+(23 rows)
+
+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;
+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;
+ QUERY PLAN
+-----------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Join
+ Hash Cond: (t2_1.b = t1_1.a)
+ -> Seq Scan on prt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_adv_p1 t1_1
+ Filter: ((a < 300) AND (b = 0))
+ -> Hash Join
+ Hash Cond: (t2_2.b = t1_2.a)
+ -> Seq Scan on prt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_adv_p2 t1_2
+ Filter: ((a < 300) AND (b = 0))
+(15 rows)
+
+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;
+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;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Join
+ Hash Cond: (t2_1.b = t1_1.a)
+ -> Seq Scan on prt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on prt1_adv_p1 t1_1
+ Filter: ((a >= 100) AND (a < 300) AND (b = 0))
+ -> Hash Join
+ Hash Cond: (t2_2.b = t1_2.a)
+ -> Seq Scan on prt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on prt1_adv_p2 t1_2
+ Filter: ((a >= 100) AND (a < 300) AND (b = 0))
+(15 rows)
+
+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
+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
+ -> Append
+ -> Hash Join
+ Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Hash Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Hash Join
+ Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
+ -> Seq Scan on plt2_adv_p3 t2_3
+ -> Hash
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+(21 rows)
+
+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
+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
+ -> Append
+ -> Nested Loop Semi Join
+ Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c))
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Nested Loop Semi Join
+ Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c))
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Nested Loop Semi Join
+ Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c))
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p3 t2_3
+(18 rows)
+
+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
+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;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Right Join
+ Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Hash Right Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Hash Right Join
+ Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
+ -> Seq Scan on plt2_adv_p3 t2_3
+ -> Hash
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+(21 rows)
+
+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
+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
+ -> Append
+ -> Nested Loop Anti Join
+ Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c))
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Nested Loop Anti Join
+ Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c))
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Nested Loop Anti Join
+ Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c))
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p3 t2_3
+(18 rows)
+
+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
+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
+ -> Append
+ -> Hash Join
+ Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Hash Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Hash Join
+ Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
+ -> Seq Scan on plt2_adv_p3 t2_3
+ -> Hash
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+(21 rows)
+
+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
+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
+ -> Append
+ -> Nested Loop Semi Join
+ Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c))
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Nested Loop Semi Join
+ Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c))
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Nested Loop Semi Join
+ Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c))
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p3 t2_3
+(18 rows)
+
+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
+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;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Right Join
+ Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Hash Right Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Hash Right Join
+ Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
+ -> Seq Scan on plt2_adv_p3 t2_3
+ -> Hash
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+(21 rows)
+
+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
+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;
+ QUERY PLAN
+---------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Hash Right Join
+ Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
+ -> 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
+ -> Hash
+ -> Append
+ -> Seq Scan on plt2_adv_extra t1_1
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p1 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p2 t1_3
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p3 t1_4
+ Filter: (b < 10)
+(18 rows)
+
+-- 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
+ -> Append
+ -> Nested Loop Anti Join
+ Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c))
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Nested Loop Anti Join
+ Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c))
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Nested Loop Anti Join
+ Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c))
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p3 t2_3
+(18 rows)
+
+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
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p1 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p2 t1_3
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p3 t1_4
+ 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
+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_1 t2_2
+ -> Seq Scan on plt2_adv_p2_2 t2_3
+ -> Seq Scan on plt2_adv_p3 t2_4
+ -> Hash
+ -> Append
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+(17 rows)
+
+-- 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
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_p3 t1_3
+ 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
+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;
+ QUERY PLAN
+------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Hash Right 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_1 t2_2
+ -> Seq Scan on plt2_adv_p2_2 t2_3
+ -> Seq Scan on plt2_adv_p3 t2_4
+ -> Hash
+ -> Append
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+(17 rows)
+
+-- 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
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_p3 t1_3
+ 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
+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_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
+ -> 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_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
+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
+ -> Append
+ -> Hash Join
+ Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1_null t1_1
+ Filter: (b < 10)
+ -> Hash Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Hash Join
+ Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
+ -> Seq Scan on plt2_adv_p3_null t2_3
+ -> Hash
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+(21 rows)
+
+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
+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
+ -> Append
+ -> Hash Semi Join
+ Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c))
+ -> Seq Scan on plt1_adv_p1_null t1_1
+ Filter: (b < 10)
+ -> Hash
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Nested Loop Semi Join
+ Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c))
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Nested Loop Semi Join
+ Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c))
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p3_null t2_3
+(19 rows)
+
+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
+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;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Right Join
+ Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1_null t1_1
+ Filter: (b < 10)
+ -> Hash Right Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Hash Right Join
+ Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
+ -> Seq Scan on plt2_adv_p3_null t2_3
+ -> Hash
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+(21 rows)
+
+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
+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
+ -> Append
+ -> Hash Anti Join
+ Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c))
+ -> Seq Scan on plt1_adv_p1_null t1_1
+ Filter: (b < 10)
+ -> Hash
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Nested Loop Anti Join
+ Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c))
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Nested Loop Anti Join
+ Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c))
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_p3_null t2_3
+(19 rows)
+
+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
+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
+ -> Append
+ -> Hash Join
+ Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Hash Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Hash Join
+ Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
+ -> Seq Scan on plt2_adv_p3 t2_3
+ -> Hash
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+(21 rows)
+
+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
+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;
+ QUERY PLAN
+---------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Hash Right 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 t2_2
+ -> Seq Scan on plt2_adv_p3 t2_3
+ -> Hash
+ -> Append
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_extra t1_4
+ Filter: (b < 10)
+(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: ((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
+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
+ -> Append
+ -> Hash Join
+ Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Hash Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Hash Join
+ Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
+ -> Seq Scan on plt2_adv_p3 t2_3
+ -> Hash
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+(21 rows)
+
+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
+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;
+ QUERY PLAN
+----------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Right Join
+ Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Hash Right Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Hash Right Join
+ Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
+ -> Seq Scan on plt2_adv_p3 t2_3
+ -> Hash
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+ -> Nested Loop Left Join
+ Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c))
+ -> Seq Scan on plt1_adv_extra t1_4
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_extra t2_4
+(26 rows)
+
+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
+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;
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Right Join
+ Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c))
+ -> Seq Scan on plt1_adv_p1 t3_1
+ -> Hash
+ -> Hash Right Join
+ Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1 t1_1
+ Filter: (b < 10)
+ -> Hash Right Join
+ Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c))
+ -> Seq Scan on plt1_adv_p2 t3_2
+ -> Hash
+ -> Hash Right Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_2
+ Filter: (b < 10)
+ -> Hash Right Join
+ Hash Cond: ((t3_3.a = t1_3.a) AND (t3_3.c = t1_3.c))
+ -> Seq Scan on plt1_adv_p3 t3_3
+ -> Hash
+ -> Hash Right Join
+ Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
+ -> Seq Scan on plt2_adv_p3 t2_3
+ -> Hash
+ -> Seq Scan on plt1_adv_p3 t1_3
+ Filter: (b < 10)
+ -> Nested Loop Left Join
+ Join Filter: ((t1_4.a = t3_4.a) AND (t1_4.c = t3_4.c))
+ -> Nested Loop Left Join
+ Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c))
+ -> Seq Scan on plt1_adv_extra t1_4
+ Filter: (b < 10)
+ -> Seq Scan on plt2_adv_extra t2_4
+ -> Seq Scan on plt1_adv_extra t3_4
+(41 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.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
+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
+ -> Append
+ -> Hash Join
+ Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1 t1_2
+ Filter: (b < 10)
+ -> Hash Join
+ Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_1
+ Filter: (b < 10)
+(15 rows)
+
+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
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_p1 t1_2
+ 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
+ Filter: (b < 10)
+ -> Seq Scan on plt1_adv_p1 t1_2
+ 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
+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;
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Right Join
+ Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c))
+ -> Seq Scan on plt3_adv_p1 t3_1
+ -> Hash
+ -> Hash Right Join
+ Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_1
+ Filter: (b < 10)
+ -> Hash Right Join
+ Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c))
+ -> Seq Scan on plt3_adv_p2 t3_2
+ -> Hash
+ -> Hash Right Join
+ Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p1 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1 t1_2
+ Filter: (b < 10)
+(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.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;
+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
+ -> Append
+ -> Hash Join
+ Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p1_null t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p1 t1_2
+ Filter: (b < 10)
+ -> Hash Join
+ Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p2 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1_1
+ Filter: (b < 10)
+(15 rows)
+
+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;
+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))
+ -> Seq Scan on plt2_adv_p2 t2
+ -> Hash
+ -> Seq Scan on plt1_adv_p2 t1
+ Filter: (b < 10)
+(8 rows)
+
+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;
+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;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Join
+ Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p3 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p3 t1_1
+ Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
+ -> Hash Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p4 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p4 t1_2
+ Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
+(15 rows)
+
+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)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Hash Right Join
+ Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
+ -> Seq Scan on plt2_adv_p4 t2
+ -> Hash
+ -> Seq Scan on plt1_adv_p4 t1
+ Filter: ((c IS NULL) AND (b < 10))
+(8 rows)
+
+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;
+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;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Append
+ -> Hash Join
+ Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
+ -> Seq Scan on plt2_adv_p3 t2_1
+ -> Hash
+ -> Seq Scan on plt1_adv_p3 t1_1
+ Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
+ -> Hash Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
+ -> Seq Scan on plt2_adv_p4 t2_2
+ -> Hash
+ -> Seq Scan on plt1_adv_p4 t1_2
+ Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
+(15 rows)
+
+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)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------
+ Sort
+ Sort Key: t1.a
+ -> Hash Right Join
+ Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
+ -> Seq Scan on plt2_adv_p4 t2
+ -> Hash
+ -> Seq Scan on plt1_adv_p4 t1
+ Filter: ((c IS NULL) AND (b < 10))
+(8 rows)
+
+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;
+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;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t1.b
+ -> Append
+ -> Hash Join
+ Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b))
+ -> Seq Scan on alpha_neg_p1 t1_1
+ Filter: ((b >= 125) AND (b < 225))
+ -> Hash
+ -> Seq Scan on beta_neg_p1 t2_1
+ -> Hash Join
+ Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.b = t1_2.b))
+ -> Seq Scan on beta_neg_p2 t2_2
+ -> Hash
+ -> Seq Scan on alpha_neg_p2 t1_2
+ Filter: ((b >= 125) AND (b < 225))
+ -> Hash Join
+ Hash Cond: ((t2_4.a = t1_4.a) AND (t2_4.b = t1_4.b))
+ -> Append
+ -> Seq Scan on beta_pos_p1 t2_4
+ -> Seq Scan on beta_pos_p2 t2_5
+ -> Seq Scan on beta_pos_p3 t2_6
+ -> Hash
+ -> Append
+ -> Seq Scan on alpha_pos_p1 t1_4
+ Filter: ((b >= 125) AND (b < 225))
+ -> Seq Scan on alpha_pos_p2 t1_5
+ Filter: ((b >= 125) AND (b < 225))
+ -> Seq Scan on alpha_pos_p3 t1_6
+ Filter: ((b >= 125) AND (b < 225))
+(29 rows)
+
+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)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t1.b, t2.b
+ -> Append
+ -> Hash Join
+ Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c))
+ -> Append
+ -> Seq Scan on alpha_neg_p1 t1_2
+ Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+ -> Seq Scan on alpha_neg_p2 t1_3
+ Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+ -> Hash
+ -> Append
+ -> Seq Scan on beta_neg_p1 t2_2
+ Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+ -> Seq Scan on beta_neg_p2 t2_3
+ Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+ -> Nested Loop
+ Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c))
+ -> Seq Scan on alpha_pos_p2 t1_4
+ Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+ -> Seq Scan on beta_pos_p2 t2_4
+ Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+ -> Nested Loop
+ Join Filter: ((t1_5.a = t2_5.a) AND (t1_5.c = t2_5.c))
+ -> Seq Scan on alpha_pos_p3 t1_5
+ Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+ -> Seq Scan on beta_pos_p3 t2_5
+ Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+(28 rows)
+
+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)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t1.b
+ -> Append
+ -> Hash Join
+ Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
+ -> Seq Scan on alpha_neg_p1 t1_1
+ Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+ -> Hash
+ -> Seq Scan on beta_neg_p1 t2_1
+ Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+ -> Hash Join
+ Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
+ -> Seq Scan on alpha_neg_p2 t1_2
+ Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+ -> Hash
+ -> Seq Scan on beta_neg_p2 t2_2
+ Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+ -> Nested Loop
+ Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
+ -> Seq Scan on alpha_pos_p2 t1_3
+ Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+ -> Seq Scan on beta_pos_p2 t2_3
+ Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+ -> Nested Loop
+ Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.b = t2_4.b) AND (t1_4.c = t2_4.c))
+ -> Seq Scan on alpha_pos_p3 t1_4
+ Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+ -> Seq Scan on beta_pos_p3 t2_4
+ Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+(29 rows)
+
+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)
+
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
new file mode 100644
index 0000000..7555764
--- /dev/null
+++ b/src/test/regress/expected/partition_prune.out
@@ -0,0 +1,3865 @@
+--
+-- Test partitioning planner code
+--
+-- Force generic plans to be used for all prepared statements in this file.
+set plan_cache_mode = force_generic_plan;
+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
+ Filter: ((a > 'a'::bpchar) AND (a < 'd'::bpchar))
+ -> Seq Scan on lp_default lp_2
+ 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
+ Filter: ((a > 'a'::bpchar) AND (a <= 'd'::bpchar))
+ -> Seq Scan on lp_bc lp_2
+ Filter: ((a > 'a'::bpchar) AND (a <= 'd'::bpchar))
+ -> Seq Scan on lp_default lp_3
+ 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
+ Filter: (a = 'a'::bpchar)
+(2 rows)
+
+explain (costs off) select * from lp where 'a' = a; /* commuted */
+ QUERY PLAN
+-----------------------------
+ Seq Scan on lp_ad lp
+ 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
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on lp_bc lp_2
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on lp_ef lp_3
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on lp_g lp_4
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on lp_default lp_5
+ 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
+ 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
+ Filter: ((a = 'a'::bpchar) OR (a = 'c'::bpchar))
+ -> Seq Scan on lp_bc lp_2
+ 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
+ Filter: ((a IS NOT NULL) AND ((a = 'a'::bpchar) OR (a = 'c'::bpchar)))
+ -> Seq Scan on lp_bc lp_2
+ 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
+ Filter: (a <> 'g'::bpchar)
+ -> Seq Scan on lp_bc lp_2
+ Filter: (a <> 'g'::bpchar)
+ -> Seq Scan on lp_ef lp_3
+ Filter: (a <> 'g'::bpchar)
+ -> Seq Scan on lp_default lp_4
+ 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
+ Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar))
+ -> Seq Scan on lp_ef lp_2
+ Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar))
+ -> Seq Scan on lp_g lp_3
+ Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar))
+ -> Seq Scan on lp_default lp_4
+ 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
+ Filter: (a <> ALL ('{a,d}'::bpchar[]))
+ -> Seq Scan on lp_ef lp_2
+ Filter: (a <> ALL ('{a,d}'::bpchar[]))
+ -> Seq Scan on lp_g lp_3
+ Filter: (a <> ALL ('{a,d}'::bpchar[]))
+ -> Seq Scan on lp_default lp_4
+ 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
+ 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
+ Filter: ((a)::text = 'a'::text COLLATE "POSIX")
+ -> Seq Scan on coll_pruning_b coll_pruning_2
+ Filter: ((a)::text = 'a'::text COLLATE "POSIX")
+ -> Seq Scan on coll_pruning_def coll_pruning_3
+ 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
+ Filter: (a < 1)
+(2 rows)
+
+explain (costs off) select * from rlp where 1 > a; /* commuted */
+ QUERY PLAN
+----------------------
+ Seq Scan on rlp1 rlp
+ Filter: (1 > a)
+(2 rows)
+
+explain (costs off) select * from rlp where a <= 1;
+ QUERY PLAN
+------------------------------
+ Append
+ -> Seq Scan on rlp1 rlp_1
+ Filter: (a <= 1)
+ -> Seq Scan on rlp2 rlp_2
+ Filter: (a <= 1)
+(5 rows)
+
+explain (costs off) select * from rlp where a = 1;
+ QUERY PLAN
+----------------------
+ Seq Scan on rlp2 rlp
+ 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
+ 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
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp2 rlp_2
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp3abcd rlp_3
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp3efgh rlp_4
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp3nullxy rlp_5
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp3_default rlp_6
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp4_1 rlp_7
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp4_2 rlp_8
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp4_default rlp_9
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp5_1 rlp_10
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp5_default rlp_11
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp_default_10 rlp_12
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp_default_30 rlp_13
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp_default_null rlp_14
+ Filter: ((a)::numeric = '1'::numeric)
+ -> Seq Scan on rlp_default_default rlp_15
+ 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
+ Filter: (a <= 10)
+ -> Seq Scan on rlp2 rlp_2
+ Filter: (a <= 10)
+ -> Seq Scan on rlp_default_10 rlp_3
+ Filter: (a <= 10)
+ -> Seq Scan on rlp_default_default rlp_4
+ Filter: (a <= 10)
+(9 rows)
+
+explain (costs off) select * from rlp where a > 10;
+ QUERY PLAN
+----------------------------------------------
+ Append
+ -> Seq Scan on rlp3abcd rlp_1
+ Filter: (a > 10)
+ -> Seq Scan on rlp3efgh rlp_2
+ Filter: (a > 10)
+ -> Seq Scan on rlp3nullxy rlp_3
+ Filter: (a > 10)
+ -> Seq Scan on rlp3_default rlp_4
+ Filter: (a > 10)
+ -> Seq Scan on rlp4_1 rlp_5
+ Filter: (a > 10)
+ -> Seq Scan on rlp4_2 rlp_6
+ Filter: (a > 10)
+ -> Seq Scan on rlp4_default rlp_7
+ Filter: (a > 10)
+ -> Seq Scan on rlp5_1 rlp_8
+ Filter: (a > 10)
+ -> Seq Scan on rlp5_default rlp_9
+ Filter: (a > 10)
+ -> Seq Scan on rlp_default_30 rlp_10
+ Filter: (a > 10)
+ -> Seq Scan on rlp_default_default rlp_11
+ Filter: (a > 10)
+(23 rows)
+
+explain (costs off) select * from rlp where a < 15;
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Seq Scan on rlp1 rlp_1
+ Filter: (a < 15)
+ -> Seq Scan on rlp2 rlp_2
+ Filter: (a < 15)
+ -> Seq Scan on rlp_default_10 rlp_3
+ Filter: (a < 15)
+ -> Seq Scan on rlp_default_default rlp_4
+ Filter: (a < 15)
+(9 rows)
+
+explain (costs off) select * from rlp where a <= 15;
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Seq Scan on rlp1 rlp_1
+ Filter: (a <= 15)
+ -> Seq Scan on rlp2 rlp_2
+ Filter: (a <= 15)
+ -> Seq Scan on rlp3abcd rlp_3
+ Filter: (a <= 15)
+ -> Seq Scan on rlp3efgh rlp_4
+ Filter: (a <= 15)
+ -> Seq Scan on rlp3nullxy rlp_5
+ Filter: (a <= 15)
+ -> Seq Scan on rlp3_default rlp_6
+ Filter: (a <= 15)
+ -> Seq Scan on rlp_default_10 rlp_7
+ Filter: (a <= 15)
+ -> Seq Scan on rlp_default_default rlp_8
+ 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_1
+ Filter: ((a > 15) AND ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp4_1 rlp_2
+ Filter: ((a > 15) AND ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp4_2 rlp_3
+ Filter: ((a > 15) AND ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp4_default rlp_4
+ Filter: ((a > 15) AND ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp5_1 rlp_5
+ Filter: ((a > 15) AND ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp5_default rlp_6
+ Filter: ((a > 15) AND ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp_default_30 rlp_7
+ Filter: ((a > 15) AND ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp_default_default rlp_8
+ 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_1
+ Filter: (a = 16)
+ -> Seq Scan on rlp3efgh rlp_2
+ Filter: (a = 16)
+ -> Seq Scan on rlp3nullxy rlp_3
+ Filter: (a = 16)
+ -> Seq Scan on rlp3_default rlp_4
+ 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
+ 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
+ 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_1
+ Filter: (((b)::text <= 'ab'::text) AND (a = 16))
+ -> Seq Scan on rlp3_default rlp_2
+ 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
+ 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_1
+ Filter: ((b IS NOT NULL) AND (a = 16))
+ -> Seq Scan on rlp3efgh rlp_2
+ Filter: ((b IS NOT NULL) AND (a = 16))
+ -> Seq Scan on rlp3nullxy rlp_3
+ Filter: ((b IS NOT NULL) AND (a = 16))
+ -> Seq Scan on rlp3_default rlp_4
+ 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
+ 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
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp2 rlp_2
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp3abcd rlp_3
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp3efgh rlp_4
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp3nullxy rlp_5
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp3_default rlp_6
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp4_1 rlp_7
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp4_2 rlp_8
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp4_default rlp_9
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp5_1 rlp_10
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp5_default rlp_11
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp_default_10 rlp_12
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp_default_30 rlp_13
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on rlp_default_default rlp_14
+ 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_1
+ Filter: (a > 30)
+ -> Seq Scan on rlp5_default rlp_2
+ Filter: (a > 30)
+ -> Seq Scan on rlp_default_default rlp_3
+ 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
+ Filter: (a = 30)
+(2 rows)
+
+explain (costs off) select * from rlp where a <= 31;
+ QUERY PLAN
+----------------------------------------------
+ Append
+ -> Seq Scan on rlp1 rlp_1
+ Filter: (a <= 31)
+ -> Seq Scan on rlp2 rlp_2
+ Filter: (a <= 31)
+ -> Seq Scan on rlp3abcd rlp_3
+ Filter: (a <= 31)
+ -> Seq Scan on rlp3efgh rlp_4
+ Filter: (a <= 31)
+ -> Seq Scan on rlp3nullxy rlp_5
+ Filter: (a <= 31)
+ -> Seq Scan on rlp3_default rlp_6
+ Filter: (a <= 31)
+ -> Seq Scan on rlp4_1 rlp_7
+ Filter: (a <= 31)
+ -> Seq Scan on rlp4_2 rlp_8
+ Filter: (a <= 31)
+ -> Seq Scan on rlp4_default rlp_9
+ Filter: (a <= 31)
+ -> Seq Scan on rlp5_1 rlp_10
+ Filter: (a <= 31)
+ -> Seq Scan on rlp_default_10 rlp_11
+ Filter: (a <= 31)
+ -> Seq Scan on rlp_default_30 rlp_12
+ Filter: (a <= 31)
+ -> Seq Scan on rlp_default_default rlp_13
+ Filter: (a <= 31)
+(27 rows)
+
+explain (costs off) select * from rlp where a = 1 or a = 7;
+ QUERY PLAN
+--------------------------------
+ Seq Scan on rlp2 rlp
+ 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
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp2 rlp_2
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp3abcd rlp_3
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp4_1 rlp_4
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp4_2 rlp_5
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp4_default rlp_6
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp5_1 rlp_7
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp5_default rlp_8
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp_default_10 rlp_9
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp_default_30 rlp_10
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp_default_null rlp_11
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp_default_default rlp_12
+ 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_1
+ Filter: ((a > 20) AND (a < 27))
+ -> Seq Scan on rlp4_2 rlp_2
+ 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
+ Filter: (a = 29)
+(2 rows)
+
+explain (costs off) select * from rlp where a >= 29;
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Seq Scan on rlp4_default rlp_1
+ Filter: (a >= 29)
+ -> Seq Scan on rlp5_1 rlp_2
+ Filter: (a >= 29)
+ -> Seq Scan on rlp5_default rlp_3
+ Filter: (a >= 29)
+ -> Seq Scan on rlp_default_30 rlp_4
+ Filter: (a >= 29)
+ -> Seq Scan on rlp_default_default rlp_5
+ 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
+ Filter: ((a < 1) OR ((a > 20) AND (a < 25)))
+ -> Seq Scan on rlp4_1 rlp_2
+ 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_1
+ Filter: ((a = 20) OR (a = 40))
+ -> Seq Scan on rlp5_default rlp_2
+ Filter: ((a = 20) OR (a = 40))
+(5 rows)
+
+explain (costs off) select * from rlp3 where a = 20; /* empty */
+ QUERY PLAN
+--------------------------
+ Result
+ 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
+ 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_1
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp3efgh rlp_2
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp3nullxy rlp_3
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp3_default rlp_4
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp4_1 rlp_5
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp4_2 rlp_6
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp4_default rlp_7
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp5_1 rlp_8
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp5_default rlp_9
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp_default_30 rlp_10
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp_default_default rlp_11
+ Filter: ((a > 1) AND (a >= 15))
+(23 rows)
+
+explain (costs off) select * from rlp where a = 1 and a = 3; /* empty */
+ QUERY PLAN
+--------------------------
+ Result
+ 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
+ Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+ -> Seq Scan on rlp3abcd rlp_2
+ Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+ -> Seq Scan on rlp3efgh rlp_3
+ Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+ -> Seq Scan on rlp3nullxy rlp_4
+ Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+ -> Seq Scan on rlp3_default rlp_5
+ 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
+ Filter: (a = 1)
+ -> Seq Scan on mc3p1 mc3p_2
+ Filter: (a = 1)
+ -> Seq Scan on mc3p_default mc3p_3
+ 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
+ Filter: ((a = 1) AND (abs(b) < 1))
+ -> Seq Scan on mc3p_default mc3p_2
+ 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
+ Filter: ((a = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p1 mc3p_2
+ Filter: ((a = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p_default mc3p_3
+ 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
+ Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p1 mc3p_2
+ 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
+ Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35))
+ -> Seq Scan on mc3p2 mc3p_2
+ Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35))
+ -> Seq Scan on mc3p3 mc3p_3
+ Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35))
+ -> Seq Scan on mc3p4 mc3p_4
+ Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35))
+ -> Seq Scan on mc3p_default mc3p_5
+ 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
+ Filter: (a > 10)
+ -> Seq Scan on mc3p6 mc3p_2
+ Filter: (a > 10)
+ -> Seq Scan on mc3p7 mc3p_3
+ Filter: (a > 10)
+ -> Seq Scan on mc3p_default mc3p_4
+ Filter: (a > 10)
+(9 rows)
+
+explain (costs off) select * from mc3p where a >= 10;
+ QUERY PLAN
+---------------------------------------
+ Append
+ -> Seq Scan on mc3p1 mc3p_1
+ Filter: (a >= 10)
+ -> Seq Scan on mc3p2 mc3p_2
+ Filter: (a >= 10)
+ -> Seq Scan on mc3p3 mc3p_3
+ Filter: (a >= 10)
+ -> Seq Scan on mc3p4 mc3p_4
+ Filter: (a >= 10)
+ -> Seq Scan on mc3p5 mc3p_5
+ Filter: (a >= 10)
+ -> Seq Scan on mc3p6 mc3p_6
+ Filter: (a >= 10)
+ -> Seq Scan on mc3p7 mc3p_7
+ Filter: (a >= 10)
+ -> Seq Scan on mc3p_default mc3p_8
+ Filter: (a >= 10)
+(17 rows)
+
+explain (costs off) select * from mc3p where a < 10;
+ QUERY PLAN
+---------------------------------------
+ Append
+ -> Seq Scan on mc3p0 mc3p_1
+ Filter: (a < 10)
+ -> Seq Scan on mc3p1 mc3p_2
+ Filter: (a < 10)
+ -> Seq Scan on mc3p_default mc3p_3
+ 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
+ Filter: ((a <= 10) AND (abs(b) < 10))
+ -> Seq Scan on mc3p1 mc3p_2
+ Filter: ((a <= 10) AND (abs(b) < 10))
+ -> Seq Scan on mc3p2 mc3p_3
+ Filter: ((a <= 10) AND (abs(b) < 10))
+ -> Seq Scan on mc3p_default mc3p_4
+ 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
+ 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
+ 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
+ Filter: (a > 20)
+ -> Seq Scan on mc3p_default mc3p_2
+ Filter: (a > 20)
+(5 rows)
+
+explain (costs off) select * from mc3p where a >= 20;
+ QUERY PLAN
+---------------------------------------
+ Append
+ -> Seq Scan on mc3p5 mc3p_1
+ Filter: (a >= 20)
+ -> Seq Scan on mc3p6 mc3p_2
+ Filter: (a >= 20)
+ -> Seq Scan on mc3p7 mc3p_3
+ Filter: (a >= 20)
+ -> Seq Scan on mc3p_default mc3p_4
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
+ -> Seq Scan on mc3p1 mc3p_2
+ Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
+ -> Seq Scan on mc3p2 mc3p_3
+ Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
+ -> Seq Scan on mc3p3 mc3p_4
+ Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
+ -> Seq Scan on mc3p4 mc3p_5
+ Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
+ -> Seq Scan on mc3p5 mc3p_6
+ Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
+ -> Seq Scan on mc3p6 mc3p_7
+ Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
+ -> Seq Scan on mc3p7 mc3p_8
+ Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1))
+ -> Seq Scan on mc3p_default mc3p_9
+ 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
+ Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10)))
+ -> Seq Scan on mc3p1 mc3p_2
+ Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10)))
+ -> Seq Scan on mc3p2 mc3p_3
+ Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10)))
+ -> Seq Scan on mc3p3 mc3p_4
+ Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10)))
+ -> Seq Scan on mc3p4 mc3p_5
+ Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10)))
+ -> Seq Scan on mc3p_default mc3p_6
+ 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
+ Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9)))
+ -> Seq Scan on mc3p1 mc3p_2
+ Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9)))
+ -> Seq Scan on mc3p2 mc3p_3
+ Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9)))
+ -> Seq Scan on mc3p_default mc3p_4
+ 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
+ Filter: (a < 2)
+ -> Seq Scan on mc2p1 mc2p_2
+ Filter: (a < 2)
+ -> Seq Scan on mc2p2 mc2p_3
+ Filter: (a < 2)
+ -> Seq Scan on mc2p_default mc2p_4
+ Filter: (a < 2)
+(9 rows)
+
+explain (costs off) select * from mc2p where a = 2 and b < 1;
+ QUERY PLAN
+---------------------------------
+ Seq Scan on mc2p3 mc2p
+ 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
+ Filter: (a > 1)
+ -> Seq Scan on mc2p3 mc2p_2
+ Filter: (a > 1)
+ -> Seq Scan on mc2p4 mc2p_3
+ Filter: (a > 1)
+ -> Seq Scan on mc2p5 mc2p_4
+ Filter: (a > 1)
+ -> Seq Scan on mc2p_default mc2p_5
+ Filter: (a > 1)
+(11 rows)
+
+explain (costs off) select * from mc2p where a = 1 and b > 1;
+ QUERY PLAN
+---------------------------------
+ Seq Scan on mc2p2 mc2p
+ 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
+ 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
+ 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
+ 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
+ Filter: (a IS NULL)
+(2 rows)
+
+explain (costs off) select * from mc2p where b is null;
+ QUERY PLAN
+-------------------------------
+ Seq Scan on mc2p_default mc2p
+ 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
+ Filter: (a = ANY ('{t,f}'::boolean[]))
+ -> Seq Scan on boolpart_t boolpart_2
+ 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
+ Filter: (NOT a)
+(2 rows)
+
+explain (costs off) select * from boolpart where not a = false;
+ QUERY PLAN
+---------------------------------
+ Seq Scan on boolpart_t boolpart
+ 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))
+(5 rows)
+
+explain (costs off) select * from boolpart where a is not true;
+ QUERY PLAN
+---------------------------------
+ Seq Scan on boolpart_f boolpart
+ Filter: (a IS NOT TRUE)
+(2 rows)
+
+explain (costs off) select * from boolpart where a is not true and a is not false;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: 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
+ 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
+ Filter: ((a)::text ~ ANY ('{ab}'::text[]))
+ -> Seq Scan on coercepart_bc coercepart_2
+ Filter: ((a)::text ~ ANY ('{ab}'::text[]))
+ -> Seq Scan on coercepart_cd coercepart_3
+ 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
+ Filter: ((a)::text !~ ALL ('{ab}'::text[]))
+ -> Seq Scan on coercepart_bc coercepart_2
+ Filter: ((a)::text !~ ALL ('{ab}'::text[]))
+ -> Seq Scan on coercepart_cd coercepart_3
+ 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
+ Filter: ((a)::text ~ ANY ('{ab,bc}'::text[]))
+ -> Seq Scan on coercepart_bc coercepart_2
+ Filter: ((a)::text ~ ANY ('{ab,bc}'::text[]))
+ -> Seq Scan on coercepart_cd coercepart_3
+ 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
+ Filter: ((a)::text !~ ALL ('{ab,bc}'::text[]))
+ -> Seq Scan on coercepart_bc coercepart_2
+ Filter: ((a)::text !~ ALL ('{ab,bc}'::text[]))
+ -> Seq Scan on coercepart_cd coercepart_3
+ 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
+ Filter: ((a)::text = ANY ('{ab,bc}'::text[]))
+ -> Seq Scan on coercepart_bc coercepart_2
+ 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
+ Filter: ((a)::text = ANY ('{ab,NULL}'::text[]))
+(2 rows)
+
+explain (costs off) select * from coercepart where a = any (null::text[]);
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+explain (costs off) select * from coercepart where a = all ('{ab}');
+ QUERY PLAN
+----------------------------------------------
+ Seq Scan on coercepart_ab coercepart
+ Filter: ((a)::text = ALL ('{ab}'::text[]))
+(2 rows)
+
+explain (costs off) select * from coercepart where a = all ('{ab,bc}');
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+explain (costs off) select * from coercepart where a = all ('{ab,null}');
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+explain (costs off) select * from coercepart where a = all (null::text[]);
+ QUERY PLAN
+--------------------------
+ Result
+ 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.tableoid)::regclass), part.a, part.b
+ -> Seq Scan on part_p2_p1 part
+ 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_3
+ Output: p_3.x, p_3.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
+ Filter: (a = 1)
+ -> Seq Scan on mc2p2 t1_2
+ Filter: (a = 1)
+ -> Seq Scan on mc2p_default t1_3
+ Filter: (a = 1)
+ -> Aggregate
+ -> Append
+ -> Seq Scan on mc3p0 t2_1
+ Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p1 t2_2
+ Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p2 t2_3
+ Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p3 t2_4
+ Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p4 t2_5
+ Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p5 t2_6
+ Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p6 t2_7
+ Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p7 t2_8
+ Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p_default t2_9
+ 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
+ Filter: (a = 1)
+ -> Seq Scan on mc2p2 t1_2
+ Filter: (a = 1)
+ -> Seq Scan on mc2p_default t1_3
+ Filter: (a = 1)
+ -> Aggregate
+ -> Append
+ -> Seq Scan on mc3p0 t2_1
+ Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p1 t2_2
+ Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1))
+ -> Seq Scan on mc3p_default t2_3
+ 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
+ -> Aggregate
+ -> Seq Scan on mc3p1 t2
+ Filter: ((a = 1) AND (c = 1) AND (abs(b) = 1))
+ -> Append
+ -> Seq Scan on mc2p1 t1_1
+ Filter: (a = 1)
+ -> Seq Scan on mc2p2 t1_2
+ Filter: (a = 1)
+ -> Seq Scan on mc2p_default t1_3
+ Filter: (a = 1)
+(11 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
+ Filter: (a <> 1)
+ -> Seq Scan on rp1 rp_2
+ Filter: (a <> 1)
+ -> Seq Scan on rp2 rp_3
+ 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
+ Filter: ((a <> 1) AND (a <> 2))
+ -> Seq Scan on rp1 rp_2
+ Filter: ((a <> 1) AND (a <> 2))
+ -> Seq Scan on rp2 rp_3
+ 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
+ Filter: (a <> 'a'::bpchar)
+ -> Seq Scan on lp_bc lp_2
+ Filter: (a <> 'a'::bpchar)
+ -> Seq Scan on lp_ef lp_3
+ Filter: (a <> 'a'::bpchar)
+ -> Seq Scan on lp_g lp_4
+ Filter: (a <> 'a'::bpchar)
+ -> Seq Scan on lp_default lp_5
+ 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
+ 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
+ Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL))
+ -> Seq Scan on lp_ef lp_2
+ Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL))
+ -> Seq Scan on lp_g lp_3
+ Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL))
+ -> Seq Scan on lp_null lp_4
+ Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL))
+ -> Seq Scan on lp_default lp_5
+ 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_1
+ 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_2
+ 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
+ Filter: (substr(a, 1) = 'e'::text COLLATE "C")
+ -> Seq Scan on coll_pruning_multi2 coll_pruning_multi_2
+ Filter: (substr(a, 1) = 'e'::text COLLATE "C")
+ -> Seq Scan on coll_pruning_multi3 coll_pruning_multi_3
+ 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
+ Filter: (substr(a, 1) = 'a'::text COLLATE "POSIX")
+ -> Seq Scan on coll_pruning_multi2 coll_pruning_multi_2
+ 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
+ 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
+ Filter: (a ~~ '%BC'::text)
+ -> Seq Scan on like_op_noprune2 like_op_noprune_2
+ 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
+ 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
+ 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
+ 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 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
+ Filter: (a = 1)
+ -> Seq Scan on hp1 hp_2
+ Filter: (a = 1)
+ -> Seq Scan on hp2 hp_3
+ Filter: (a = 1)
+ -> Seq Scan on hp3 hp_4
+ Filter: (a = 1)
+(9 rows)
+
+explain (costs off) select * from hp where b = 'xxx';
+ QUERY PLAN
+-----------------------------------
+ Append
+ -> Seq Scan on hp0 hp_1
+ Filter: (b = 'xxx'::text)
+ -> Seq Scan on hp1 hp_2
+ Filter: (b = 'xxx'::text)
+ -> Seq Scan on hp2 hp_3
+ Filter: (b = 'xxx'::text)
+ -> Seq Scan on hp3 hp_4
+ 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
+ Filter: (a IS NULL)
+ -> Seq Scan on hp1 hp_2
+ Filter: (a IS NULL)
+ -> Seq Scan on hp2 hp_3
+ Filter: (a IS NULL)
+ -> Seq Scan on hp3 hp_4
+ 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
+ Filter: (b IS NULL)
+ -> Seq Scan on hp1 hp_2
+ Filter: (b IS NULL)
+ -> Seq Scan on hp2 hp_3
+ Filter: (b IS NULL)
+ -> Seq Scan on hp3 hp_4
+ 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
+ Filter: ((a < 1) AND (b = 'xxx'::text))
+ -> Seq Scan on hp1 hp_2
+ Filter: ((a < 1) AND (b = 'xxx'::text))
+ -> Seq Scan on hp2 hp_3
+ Filter: ((a < 1) AND (b = 'xxx'::text))
+ -> Seq Scan on hp3 hp_4
+ 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
+ Filter: ((a <> 1) AND (b = 'yyy'::text))
+ -> Seq Scan on hp1 hp_2
+ Filter: ((a <> 1) AND (b = 'yyy'::text))
+ -> Seq Scan on hp2 hp_3
+ Filter: ((a <> 1) AND (b = 'yyy'::text))
+ -> Seq Scan on hp3 hp_4
+ 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
+ Filter: ((a <> 1) AND (b <> 'xxx'::text))
+ -> Seq Scan on hp1 hp_2
+ Filter: ((a <> 1) AND (b <> 'xxx'::text))
+ -> Seq Scan on hp2 hp_3
+ Filter: ((a <> 1) AND (b <> 'xxx'::text))
+ -> Seq Scan on hp3 hp_4
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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)
+ Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+ -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1)
+ Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+ -> Seq Scan on ab_a2_b3 ab_3 (actual rows=0 loops=1)
+ 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)
+ Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+ -> Seq Scan on ab_a1_b2 ab_2 (actual rows=0 loops=1)
+ Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+ -> Seq Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
+ Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+ -> Seq Scan on ab_a2_b1 ab_4 (actual rows=0 loops=1)
+ Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+ -> Seq Scan on ab_a2_b2 ab_5 (actual rows=0 loops=1)
+ Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+ -> Seq Scan on ab_a2_b3 ab_6 (actual rows=0 loops=1)
+ 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)
+ Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+ -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1)
+ 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)
+ Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+ -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1)
+ Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+ -> Seq Scan on ab_a3_b1 ab_3 (actual rows=0 loops=1)
+ Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+ -> Seq Scan on ab_a3_b2 ab_4 (actual rows=0 loops=1)
+ 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 (actual rows=1 loops=1)
+ -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1)
+ Filter: ((a >= $1) AND (a <= $2) AND (b < $0))
+ -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1)
+ Filter: ((a >= $1) AND (a <= $2) AND (b < $0))
+ -> Seq Scan on ab_a2_b3 ab_3 (never executed)
+ 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 (actual rows=1 loops=1)
+ -> Seq Scan on ab_a1_b2 ab_1 (actual rows=0 loops=1)
+ Filter: ((b >= $1) AND (b <= $2) AND (a < $0))
+ -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1)
+ Filter: ((b >= $1) AND (b <= $2) AND (a < $0))
+ -> Seq Scan on ab_a3_b2 ab_3 (never executed)
+ 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);
+-- move beyond the final row
+move 3 from cur;
+-- Ensure we get two rows.
+fetch backward all from cur;
+ ?column?
+----------
+ 1
+ 1
+(2 rows)
+
+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
+ 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;
+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
+------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=N loops=N)
+ -> Gather (actual rows=N loops=N)
+ Workers Planned: 2
+ Workers Launched: N
+ -> Partial Aggregate (actual rows=N loops=N)
+ -> Parallel Append (actual rows=N loops=N)
+ Subplans Removed: 6
+ -> Parallel Seq Scan on ab_a2_b1 ab_1 (actual rows=N loops=N)
+ Filter: ((a >= $1) AND (a <= $2) AND (b < 4))
+ -> Parallel Seq Scan on ab_a2_b2 ab_2 (actual rows=N loops=N)
+ Filter: ((a >= $1) AND (a <= $2) AND (b < 4))
+ -> Parallel Seq Scan on ab_a2_b3 ab_3 (actual rows=N loops=N)
+ Filter: ((a >= $1) AND (a <= $2) AND (b < 4))
+(13 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
+------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=N loops=N)
+ -> Gather (actual rows=N loops=N)
+ Workers Planned: 2
+ Workers Launched: N
+ -> Partial Aggregate (actual rows=N loops=N)
+ -> Parallel Append (actual rows=N loops=N)
+ Subplans Removed: 6
+ -> Parallel Seq Scan on ab_a1_b1 ab_1 (actual rows=N loops=N)
+ Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+ -> Parallel Seq Scan on ab_a1_b2 ab_2 (actual rows=N loops=N)
+ Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+ -> Parallel Seq Scan on ab_a1_b3 ab_3 (actual rows=N loops=N)
+ Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+(13 rows)
+
+select explain_parallel_append('execute ab_q5 (2, 3, 3)');
+ explain_parallel_append
+------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=N loops=N)
+ -> Gather (actual rows=N loops=N)
+ Workers Planned: 2
+ Workers Launched: N
+ -> Partial Aggregate (actual rows=N loops=N)
+ -> Parallel Append (actual rows=N loops=N)
+ Subplans Removed: 3
+ -> Parallel Seq Scan on ab_a2_b1 ab_1 (actual rows=N loops=N)
+ Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+ -> Parallel Seq Scan on ab_a2_b2 ab_2 (actual rows=N loops=N)
+ Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+ -> Parallel Seq Scan on ab_a2_b3 ab_3 (actual rows=N loops=N)
+ Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+ -> Parallel Seq Scan on ab_a3_b1 ab_4 (actual rows=N loops=N)
+ Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+ -> Parallel Seq Scan on ab_a3_b2 ab_5 (actual rows=N loops=N)
+ Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+ -> Parallel Seq Scan on ab_a3_b3 ab_6 (actual rows=N loops=N)
+ Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+(19 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
+-----------------------------------------------------------
+ Finalize Aggregate (actual rows=N loops=N)
+ -> Gather (actual rows=N loops=N)
+ Workers Planned: 2
+ Workers Launched: N
+ -> Partial Aggregate (actual rows=N loops=N)
+ -> Parallel Append (actual rows=N loops=N)
+ Subplans Removed: 9
+(7 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
+------------------------------------------------------------------------------
+ Aggregate (actual rows=N loops=N)
+ InitPlan 1 (returns $0)
+ -> Result (actual rows=N loops=N)
+ InitPlan 2 (returns $1)
+ -> Result (actual rows=N loops=N)
+ -> Gather (actual rows=N loops=N)
+ Workers Planned: 2
+ Params Evaluated: $0, $1
+ Workers Launched: N
+ -> Parallel Append (actual rows=N loops=N)
+ -> Parallel Seq Scan on ab_a1_b2 ab_1 (actual rows=N loops=N)
+ Filter: ((b = 2) AND ((a = $0) OR (a = $1)))
+ -> Parallel Seq Scan on ab_a2_b2 ab_2 (never executed)
+ Filter: ((b = 2) AND ((a = $0) OR (a = $1)))
+ -> Parallel Seq Scan on ab_a3_b2 ab_3 (actual rows=N loops=N)
+ Filter: ((b = 2) AND ((a = $0) OR (a = $1)))
+(16 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
+--------------------------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=N loops=N)
+ -> Gather (actual rows=N loops=N)
+ Workers Planned: 1
+ Workers Launched: N
+ -> Partial Aggregate (actual rows=N loops=N)
+ -> Nested Loop (actual rows=N loops=N)
+ -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N)
+ Filter: (a = ANY ('{0,0,1}'::integer[]))
+ -> Append (actual rows=N loops=N)
+ -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
+ Index Cond: (a = a.a)
+(27 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
+--------------------------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=N loops=N)
+ -> Gather (actual rows=N loops=N)
+ Workers Planned: 1
+ Workers Launched: N
+ -> Partial Aggregate (actual rows=N loops=N)
+ -> Nested Loop (actual rows=N loops=N)
+ -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N)
+ Filter: (a = ANY ('{0,0,1}'::integer[]))
+ -> Append (actual rows=N loops=N)
+ -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N)
+ Index Cond: (a = (a.a + 0))
+ -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N)
+ Index Cond: (a = (a.a + 0))
+ -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
+ Index Cond: (a = (a.a + 0))
+ -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
+ Index Cond: (a = (a.a + 0))
+ -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
+ Index Cond: (a = (a.a + 0))
+ -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
+ Index Cond: (a = (a.a + 0))
+ -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
+ Index Cond: (a = (a.a + 0))
+ -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
+ Index Cond: (a = (a.a + 0))
+ -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
+ Index Cond: (a = (a.a + 0))
+(27 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
+--------------------------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=N loops=N)
+ -> Gather (actual rows=N loops=N)
+ Workers Planned: 1
+ Workers Launched: N
+ -> Partial Aggregate (actual rows=N loops=N)
+ -> Nested Loop (actual rows=N loops=N)
+ -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N)
+ Filter: (a = ANY ('{1,0,3}'::integer[]))
+ -> Append (actual rows=N loops=N)
+ -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (actual rows=N loops=N)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (actual rows=N loops=N)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (actual rows=N loops=N)
+ Index Cond: (a = a.a)
+(27 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
+--------------------------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=N loops=N)
+ -> Gather (actual rows=N loops=N)
+ Workers Planned: 1
+ Workers Launched: N
+ -> Partial Aggregate (actual rows=N loops=N)
+ -> Nested Loop (actual rows=N loops=N)
+ -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N)
+ Filter: (a = ANY ('{1,0,0}'::integer[]))
+ Rows Removed by Filter: N
+ -> Append (actual rows=N loops=N)
+ -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
+ Index Cond: (a = a.a)
+(28 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
+-------------------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=N loops=N)
+ -> Gather (actual rows=N loops=N)
+ Workers Planned: 1
+ Workers Launched: N
+ -> Partial Aggregate (actual rows=N loops=N)
+ -> Nested Loop (actual rows=N loops=N)
+ -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N)
+ Filter: (a = ANY ('{1,0,0}'::integer[]))
+ Rows Removed by Filter: N
+ -> Append (actual rows=N loops=N)
+ -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
+ Index Cond: (a = a.a)
+ -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
+ Index Cond: (a = a.a)
+(28 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;
+-- 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)
+ -> Aggregate (actual rows=1 loops=1)
+ -> Seq Scan on lprt_a (actual rows=102 loops=1)
+ InitPlan 2 (returns $1)
+ -> Aggregate (actual rows=1 loops=1)
+ -> Seq Scan on lprt_a lprt_a_1 (actual rows=102 loops=1)
+ -> Bitmap Heap Scan on ab_a1_b1 ab_1 (never executed)
+ Recheck Cond: (a = $0)
+ Filter: (b = $1)
+ -> Bitmap Index Scan on ab_a1_b1_a_idx (never executed)
+ Index Cond: (a = $0)
+ -> Bitmap Heap Scan on ab_a1_b2 ab_2 (never executed)
+ Recheck Cond: (a = $0)
+ Filter: (b = $1)
+ -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed)
+ Index Cond: (a = $0)
+ -> Bitmap Heap Scan on ab_a1_b3 ab_3 (never executed)
+ Recheck Cond: (a = $0)
+ Filter: (b = $1)
+ -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed)
+ Index Cond: (a = $0)
+ -> Bitmap Heap Scan on ab_a2_b1 ab_4 (never executed)
+ Recheck Cond: (a = $0)
+ Filter: (b = $1)
+ -> Bitmap Index Scan on ab_a2_b1_a_idx (never executed)
+ Index Cond: (a = $0)
+ -> Bitmap Heap Scan on ab_a2_b2 ab_5 (never executed)
+ Recheck Cond: (a = $0)
+ Filter: (b = $1)
+ -> Bitmap Index Scan on ab_a2_b2_a_idx (never executed)
+ Index Cond: (a = $0)
+ -> Bitmap Heap Scan on ab_a2_b3 ab_6 (never executed)
+ Recheck Cond: (a = $0)
+ Filter: (b = $1)
+ -> Bitmap Index Scan on ab_a2_b3_a_idx (never executed)
+ Index Cond: (a = $0)
+ -> Bitmap Heap Scan on ab_a3_b1 ab_7 (never executed)
+ Recheck Cond: (a = $0)
+ Filter: (b = $1)
+ -> Bitmap Index Scan on ab_a3_b1_a_idx (never executed)
+ Index Cond: (a = $0)
+ -> Bitmap Heap Scan on ab_a3_b2 ab_8 (actual rows=0 loops=1)
+ Recheck Cond: (a = $0)
+ Filter: (b = $1)
+ -> Bitmap Index Scan on ab_a3_b2_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = $0)
+ -> Bitmap Heap Scan on ab_a3_b3 ab_9 (never executed)
+ Recheck Cond: (a = $0)
+ Filter: (b = $1)
+ -> Bitmap Index Scan on ab_a3_b3_a_idx (never executed)
+ Index Cond: (a = $0)
+(52 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 (actual rows=1 loops=1)
+ -> Append (actual rows=0 loops=1)
+ -> Bitmap Heap Scan on ab_a1_b1 ab_11 (actual rows=0 loops=1)
+ Recheck Cond: (a = 1)
+ Filter: (b = $0)
+ -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = 1)
+ -> Bitmap Heap Scan on ab_a1_b2 ab_12 (never executed)
+ Recheck Cond: (a = 1)
+ Filter: (b = $0)
+ -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed)
+ Index Cond: (a = 1)
+ -> Bitmap Heap Scan on ab_a1_b3 ab_13 (never executed)
+ Recheck Cond: (a = 1)
+ 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_1 (actual rows=0 loops=1)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a1_b2 ab_2 (never executed)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a1_b3 ab_3 (never executed)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a2_b1 ab_4 (actual rows=0 loops=1)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a2_b2 ab_5 (never executed)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a2_b3 ab_6 (never executed)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a3_b1 ab_7 (actual rows=0 loops=1)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a3_b2 ab_8 (never executed)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a3_b3 ab_9 (never executed)
+ Filter: (b = $0)
+(37 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 (actual rows=1 loops=1)
+ -> Append (actual rows=0 loops=1)
+ -> Bitmap Heap Scan on ab_a1_b1 ab_11 (actual rows=0 loops=1)
+ Recheck Cond: (a = 1)
+ Filter: (b = $0)
+ -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = 1)
+ -> Bitmap Heap Scan on ab_a1_b2 ab_12 (never executed)
+ Recheck Cond: (a = 1)
+ Filter: (b = $0)
+ -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed)
+ Index Cond: (a = 1)
+ -> Bitmap Heap Scan on ab_a1_b3 ab_13 (never executed)
+ Recheck Cond: (a = 1)
+ 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_1 (actual rows=0 loops=1)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a1_b2 ab_2 (never executed)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a1_b3 ab_3 (never executed)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a2_b1 ab_4 (actual rows=0 loops=1)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a2_b2 ab_5 (never executed)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a2_b3 ab_6 (never executed)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a3_b1 ab_7 (actual rows=0 loops=1)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a3_b2 ab_8 (never executed)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a3_b3 ab_9 (never executed)
+ Filter: (b = $0)
+(39 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 (actual rows=1 loops=1)
+ -> Seq Scan on ab_a1_b1 ab_1 (never executed)
+ Filter: ((a = $1) AND (b = $0))
+ -> Seq Scan on ab_a1_b2 ab_2 (never executed)
+ Filter: ((a = $1) AND (b = $0))
+ -> Seq Scan on ab_a1_b3 ab_3 (never executed)
+ Filter: ((a = $1) AND (b = $0))
+ -> Seq Scan on xy_1 (actual rows=0 loops=1)
+ Filter: ((x = $1) AND (y = $0))
+ Rows Removed by Filter: 1
+ -> Seq Scan on ab_a1_b1 ab_4 (never executed)
+ Filter: ((a = $1) AND (b = $0))
+ -> Seq Scan on ab_a1_b2 ab_5 (never executed)
+ Filter: ((a = $1) AND (b = $0))
+ -> Seq Scan on ab_a1_b3 ab_6 (never executed)
+ Filter: ((a = $1) AND (b = $0))
+(19 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;
+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)
+ -> Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1)
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = 1)
+ -> Bitmap Heap Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
+ Recheck Cond: (a = 1)
+ Heap Blocks: exact=1
+ -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
+ Index Cond: (a = 1)
+ -> Bitmap Heap Scan on ab_a1_b3 ab_a1_3 (actual rows=0 loops=1)
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
+ Index Cond: (a = 1)
+ -> Materialize (actual rows=1 loops=1)
+ -> Append (actual rows=1 loops=1)
+ -> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = 1)
+ -> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
+ Recheck Cond: (a = 1)
+ Heap Blocks: exact=1
+ -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
+ Index Cond: (a = 1)
+ -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
+ Index Cond: (a = 1)
+(34 rows)
+
+table ab;
+ a | b
+---+---
+ 1 | 3
+(1 row)
+
+-- 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 (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)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a2_b2 ab_a2_2 (never executed)
+ Filter: (b = $0)
+ -> Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
+ 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;
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 join tprt on tbl1.col1 > tprt.col1;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Nested Loop (actual rows=6 loops=1)
+ -> Seq Scan on tbl1 (actual rows=2 loops=1)
+ -> Append (actual rows=3 loops=2)
+ -> Index Scan using tprt1_idx on tprt_1 (actual rows=2 loops=2)
+ Index Cond: (col1 < tbl1.col1)
+ -> Index Scan using tprt2_idx on tprt_2 (actual rows=2 loops=1)
+ Index Cond: (col1 < tbl1.col1)
+ -> Index Scan using tprt3_idx on tprt_3 (never executed)
+ Index Cond: (col1 < tbl1.col1)
+ -> Index Scan using tprt4_idx on tprt_4 (never executed)
+ Index Cond: (col1 < tbl1.col1)
+ -> Index Scan using tprt5_idx on tprt_5 (never executed)
+ Index Cond: (col1 < tbl1.col1)
+ -> Index Scan using tprt6_idx on tprt_6 (never executed)
+ Index Cond: (col1 < tbl1.col1)
+(15 rows)
+
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Nested Loop (actual rows=2 loops=1)
+ -> Seq Scan on tbl1 (actual rows=2 loops=1)
+ -> Append (actual rows=1 loops=2)
+ -> Index Scan using tprt1_idx on tprt_1 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt2_idx on tprt_2 (actual rows=1 loops=2)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt3_idx on tprt_3 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt4_idx on tprt_4 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt5_idx on tprt_5 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt6_idx on tprt_6 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+(15 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);
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Nested Loop (actual rows=23 loops=1)
+ -> Seq Scan on tbl1 (actual rows=5 loops=1)
+ -> Append (actual rows=5 loops=5)
+ -> Index Scan using tprt1_idx on tprt_1 (actual rows=2 loops=5)
+ Index Cond: (col1 < tbl1.col1)
+ -> Index Scan using tprt2_idx on tprt_2 (actual rows=3 loops=4)
+ Index Cond: (col1 < tbl1.col1)
+ -> Index Scan using tprt3_idx on tprt_3 (actual rows=1 loops=2)
+ Index Cond: (col1 < tbl1.col1)
+ -> Index Scan using tprt4_idx on tprt_4 (never executed)
+ Index Cond: (col1 < tbl1.col1)
+ -> Index Scan using tprt5_idx on tprt_5 (never executed)
+ Index Cond: (col1 < tbl1.col1)
+ -> Index Scan using tprt6_idx on tprt_6 (never executed)
+ Index Cond: (col1 < tbl1.col1)
+(15 rows)
+
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Nested Loop (actual rows=3 loops=1)
+ -> Seq Scan on tbl1 (actual rows=5 loops=1)
+ -> Append (actual rows=1 loops=5)
+ -> Index Scan using tprt1_idx on tprt_1 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt2_idx on tprt_2 (actual rows=1 loops=2)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt3_idx on tprt_3 (actual rows=0 loops=3)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt4_idx on tprt_4 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt5_idx on tprt_5 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt6_idx on tprt_6 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+(15 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);
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 join tprt on tbl1.col1 < tprt.col1;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Nested Loop (actual rows=1 loops=1)
+ -> Seq Scan on tbl1 (actual rows=1 loops=1)
+ -> Append (actual rows=1 loops=1)
+ -> Index Scan using tprt1_idx on tprt_1 (never executed)
+ Index Cond: (col1 > tbl1.col1)
+ -> Index Scan using tprt2_idx on tprt_2 (never executed)
+ Index Cond: (col1 > tbl1.col1)
+ -> Index Scan using tprt3_idx on tprt_3 (never executed)
+ Index Cond: (col1 > tbl1.col1)
+ -> Index Scan using tprt4_idx on tprt_4 (never executed)
+ Index Cond: (col1 > tbl1.col1)
+ -> Index Scan using tprt5_idx on tprt_5 (never executed)
+ Index Cond: (col1 > tbl1.col1)
+ -> Index Scan using tprt6_idx on tprt_6 (actual rows=1 loops=1)
+ Index Cond: (col1 > tbl1.col1)
+(15 rows)
+
+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);
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
+ QUERY PLAN
+-------------------------------------------------------------------
+ Nested Loop (actual rows=0 loops=1)
+ -> 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 = tbl1.col1)
+ -> Index Scan using tprt2_idx on tprt_2 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt3_idx on tprt_3 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt4_idx on tprt_4 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt5_idx on tprt_5 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+ -> Index Scan using tprt6_idx on tprt_6 (never executed)
+ Index Cond: (col1 = tbl1.col1)
+(15 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)
+ 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)
+ Filter: ((b = ANY (ARRAY[$1, $2])) AND ($3 <> b) AND ($4 <> b))
+(4 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 (actual rows=1 loops=1)
+ -> Seq Scan on listp_1_1 listp_1 (never executed)
+ Filter: (a = $0)
+ -> Seq Scan on listp_2_1 listp_2 (never executed)
+ 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)
+ Filter: (a < LOCALTIMESTAMP)
+ -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_2 (actual rows=0 loops=1)
+ 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 (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 (actual rows=0 loops=1)
+ 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_1 (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_1 (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)
+ Filter: ((a < 3) AND (abs(b) = 1))
+ -> Seq Scan on mc3p1 mc3p_2 (actual rows=1 loops=1)
+ Filter: ((a < 3) AND (abs(b) = 1))
+ -> Seq Scan on mc3p2 mc3p_3 (actual rows=1 loops=1)
+ 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 (actual rows=1 loops=1)
+ -> Seq Scan on mc3p1 mc3p_1 (actual rows=1 loops=1)
+ 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 (actual rows=1 loops=1)
+ -> Seq Scan on mc3p0 mc3p_1 (actual rows=1 loops=1)
+ Filter: ((a <= $1) AND (abs(b) < $0))
+ -> Seq Scan on mc3p1 mc3p_2 (actual rows=1 loops=1)
+ 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)
+ Filter: value
+ Rows Removed by Filter: 1
+ -> Seq Scan on boolp_f boolp_1 (never executed)
+ Filter: (a = $0)
+ -> Seq Scan on boolp_t boolp_2 (actual rows=0 loops=1)
+ Filter: (a = $0)
+(9 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)
+ Filter: (NOT value)
+ Rows Removed by Filter: 1
+ -> Seq Scan on boolp_f boolp_1 (actual rows=0 loops=1)
+ Filter: (a = $0)
+ -> Seq Scan on boolp_t boolp_2 (never executed)
+ Filter: (a = $0)
+(9 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);
+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)
+ Filter: ((a >= $1) AND ((a % 10) = 5))
+ Rows Removed by Filter: 9
+ -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_2 (actual rows=1 loops=1)
+ Filter: ((a >= $1) AND ((a % 10) = 5))
+ Rows Removed by Filter: 9
+(9 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)
+ Filter: ((a >= $1) AND ((a % 10) = 5))
+ Rows Removed by Filter: 9
+(6 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)
+ Filter: (a >= $1)
+ -> Index Scan using ma_test_p2_b_idx on ma_test_p2 ma_test_2 (actual rows=10 loops=1)
+ Filter: (a >= $1)
+ -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_3 (actual rows=10 loops=1)
+ 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
+ Filter: (a = '{1}'::integer[])
+(2 rows)
+
+explain (costs off) select * from pp_arrpart where a = '{1, 2}';
+ QUERY PLAN
+--------------------------
+ Result
+ 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;
+ 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
+ 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
+ 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
+ Filter: (a = 'blue'::pp_colors)
+(2 rows)
+
+explain (costs off) select * from pp_enumpart where a = 'black';
+ QUERY PLAN
+--------------------------
+ Result
+ 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
+ Filter: (a = '(1,1)'::pp_rectype)
+(2 rows)
+
+explain (costs off) select * from pp_recpart where a = '(1,2)'::pp_rectype;
+ QUERY PLAN
+--------------------------
+ Result
+ 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
+ Filter: (a = '[1,3)'::int4range)
+(2 rows)
+
+explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range;
+ QUERY PLAN
+--------------------------
+ Result
+ 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
+ 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
+ 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
+ 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
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ 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
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ 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
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ 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
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ 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
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ 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
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ 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
+ Filter: (a = 1)
+ -> Seq Scan on inh_lp1 inh_lp_2
+ 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
+ -> Append
+ -> Seq Scan on inh_lp inh_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on inh_lp1 inh_lp_2
+ 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
+ Filter: (a = 1)
+ -> Seq Scan on inh_lp1 inh_lp_2
+ 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
+ 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
+ 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
+ -> Seq Scan on p1 p
+ Filter: ((a = 1) AND (b = 1) AND (c = $0))
+ -> Seq Scan on q111 q1
+ 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
+ -> 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);
+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 (actual rows=0 loops=1)
+ Filter: ((b <> 10) AND (a = $0))
+ InitPlan 1 (returns $0)
+ -> Result (never executed)
+(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
+ 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
+ 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
+ One-Time Filter: false
+(2 rows)
+
+explain (costs off) update listp1 set a = 1 where a = 2;
+ QUERY PLAN
+--------------------------------
+ Update on listp1
+ -> Result
+ 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);
+-- 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
+----------------------------------------------------------------------
+ Gather (actual rows=N loops=N)
+ Workers Planned: 2
+ Params Evaluated: $0
+ Workers Launched: N
+ InitPlan 1 (returns $0)
+ -> Result (actual rows=N loops=N)
+ -> Parallel Append (actual rows=N loops=N)
+ -> Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N)
+ Filter: (a = $0)
+ -> Parallel Seq Scan on listp_12_2 listp_2 (never executed)
+ Filter: (a = $0)
+(11 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
+-----------------------------------------------------------------------------------
+ Append (actual rows=N loops=N)
+ -> Gather (actual rows=N loops=N)
+ Workers Planned: 2
+ Params Evaluated: $0
+ Workers Launched: N
+ InitPlan 1 (returns $0)
+ -> Result (actual rows=N loops=N)
+ -> Parallel Append (actual rows=N loops=N)
+ -> Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N)
+ Filter: (a = $0)
+ -> Parallel Seq Scan on listp_12_2 listp_2 (never executed)
+ Filter: (a = $0)
+ -> Gather (actual rows=N loops=N)
+ Workers Planned: 2
+ Params Evaluated: $1
+ Workers Launched: N
+ InitPlan 2 (returns $1)
+ -> Result (actual rows=N loops=N)
+ -> Parallel Append (actual rows=N loops=N)
+ -> Seq Scan on listp_12_1 listp_4 (never executed)
+ Filter: (a = $1)
+ -> Parallel Seq Scan on listp_12_2 listp_5 (actual rows=N loops=N)
+ Filter: (a = $1)
+(23 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);
+-- 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
+------------------------------------------------------------------------------------------------------------
+ Append (actual rows=0 loops=1)
+ InitPlan 1 (returns $0)
+ -> Result (actual rows=1 loops=1)
+ InitPlan 2 (returns $1)
+ -> Result (actual rows=1 loops=1)
+ -> Merge Append (actual rows=0 loops=1)
+ Sort Key: rangep_2.a
+ -> 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]))
+(15 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..7c84c9d
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,144 @@
+--
+-- Tests for password types
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+HINT: Available values: md5, scram-sha-256.
+SET password_encryption = true; -- error
+ERROR: invalid value for parameter "password_encryption": "true"
+HINT: Available values: md5, scram-sha-256.
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'scram-sha-256'; -- ok
+-- consistency of password entries
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'scram-sha-256';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+CREATE ROLE regress_passwd4 PASSWORD NULL;
+-- check list of created entries
+--
+-- The scram secret will look something like:
+-- SCRAM-SHA-256$4096:E4HxLGtnRzsYwg==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo=
+--
+-- Since the salt is random, the exact value stored will be different on every test
+-- run. Use a regular expression to mask the changing parts.
+SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword_masked
+-----------------+---------------------------------------------------
+ regress_passwd1 | md5783277baca28003b33453252be4dbb34
+ regress_passwd2 | md54044304ba511dd062133eb5b4b84a2a3
+ regress_passwd3 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
+ regress_passwd4 |
+(4 rows)
+
+-- Rename a role
+ALTER ROLE regress_passwd2 RENAME TO regress_passwd2_new;
+NOTICE: MD5 password cleared because of role rename
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd2_new'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------------------+-------------
+ regress_passwd2_new |
+(1 row)
+
+ALTER ROLE regress_passwd2_new RENAME TO regress_passwd2;
+-- Change passwords with ALTER USER. With plaintext or already-encrypted
+-- passwords.
+SET password_encryption = 'md5';
+-- encrypt with MD5
+ALTER ROLE regress_passwd2 PASSWORD 'foo';
+-- already encrypted, use as they are
+ALTER ROLE regress_passwd1 PASSWORD 'md5cd3578025fe2c3d7ed1b9a9b26238b70';
+ALTER ROLE regress_passwd3 PASSWORD 'SCRAM-SHA-256$4096:VLK4RMaQLCvNtQ==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo=';
+SET password_encryption = 'scram-sha-256';
+-- create SCRAM secret
+ALTER ROLE regress_passwd4 PASSWORD 'foo';
+-- already encrypted with MD5, use as it is
+CREATE ROLE regress_passwd5 PASSWORD 'md5e73a4b11df52a6068f8b39f90be36023';
+-- This looks like a valid SCRAM-SHA-256 secret, but it is not
+-- so it should be hashed with SCRAM-SHA-256.
+CREATE ROLE regress_passwd6 PASSWORD 'SCRAM-SHA-256$1234';
+-- These may look like valid MD5 secrets, but they are not, so they
+-- should be hashed with SCRAM-SHA-256.
+-- trailing garbage at the end
+CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
+-- invalid length
+CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
+SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword_masked
+-----------------+---------------------------------------------------
+ regress_passwd1 | md5cd3578025fe2c3d7ed1b9a9b26238b70
+ regress_passwd2 | md5dfa155cadd5f4ad57860162f3fab9cdb
+ regress_passwd3 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
+ regress_passwd4 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
+ regress_passwd5 | md5e73a4b11df52a6068f8b39f90be36023
+ regress_passwd6 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
+ regress_passwd7 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
+ regress_passwd8 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
+(8 rows)
+
+-- An empty password is not allowed, in any form
+CREATE ROLE regress_passwd_empty PASSWORD '';
+NOTICE: empty string is not a valid password, clearing password
+ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a';
+NOTICE: empty string is not a valid password, clearing password
+ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4=';
+NOTICE: empty string is not a valid password, clearing password
+SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty';
+ rolpassword
+-------------
+
+(1 row)
+
+-- Test with invalid stored and server keys.
+--
+-- The first is valid, to act as a control. The others have too long
+-- stored/server keys. They will be re-hashed.
+CREATE ROLE regress_passwd_sha_len0 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI=';
+CREATE ROLE regress_passwd_sha_len1 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96RqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI=';
+CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
+-- Check that the invalid secrets were re-hashed. A re-hashed secret
+-- should not contain the original salt.
+SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd_sha_len%'
+ ORDER BY rolname;
+ rolname | is_rolpassword_rehashed
+-------------------------+-------------------------
+ regress_passwd_sha_len0 | f
+ regress_passwd_sha_len1 | t
+ regress_passwd_sha_len2 | t
+(3 rows)
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+DROP ROLE regress_passwd6;
+DROP ROLE regress_passwd7;
+DROP ROLE regress_passwd8;
+DROP ROLE regress_passwd_empty;
+DROP ROLE regress_passwd_sha_len0;
+DROP ROLE regress_passwd_sha_len1;
+DROP ROLE regress_passwd_sha_len2;
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------+-------------
+(0 rows)
+
diff --git a/src/test/regress/expected/path.out b/src/test/regress/expected/path.out
new file mode 100644
index 0000000..7ef68d0
--- /dev/null
+++ b/src/test/regress/expected/path.out
@@ -0,0 +1,82 @@
+--
+-- PATH
+--
+--DROP TABLE PATH_TBL;
+CREATE TABLE PATH_TBL (f1 path);
+INSERT INTO PATH_TBL VALUES ('[(1,2),(3,4)]');
+INSERT INTO PATH_TBL VALUES (' ( ( 1 , 2 ) , ( 3 , 4 ) ) ');
+INSERT INTO PATH_TBL VALUES ('[ (0,0),(3,0),(4,5),(1,6) ]');
+INSERT INTO PATH_TBL VALUES ('((1,2) ,(3,4 ))');
+INSERT INTO PATH_TBL VALUES ('1,2 ,3,4 ');
+INSERT INTO PATH_TBL VALUES (' [1,2,3, 4] ');
+INSERT INTO PATH_TBL VALUES ('((10,20))'); -- Only one point
+INSERT INTO PATH_TBL VALUES ('[ 11,12,13,14 ]');
+INSERT INTO PATH_TBL VALUES ('( 11,12,13,14) ');
+-- bad values for parser testing
+INSERT INTO PATH_TBL VALUES ('[]');
+ERROR: invalid input syntax for type path: "[]"
+LINE 1: INSERT INTO PATH_TBL VALUES ('[]');
+ ^
+INSERT INTO PATH_TBL VALUES ('[(,2),(3,4)]');
+ERROR: invalid input syntax for type path: "[(,2),(3,4)]"
+LINE 1: INSERT INTO PATH_TBL VALUES ('[(,2),(3,4)]');
+ ^
+INSERT INTO PATH_TBL VALUES ('[(1,2),(3,4)');
+ERROR: invalid input syntax for type path: "[(1,2),(3,4)"
+LINE 1: INSERT INTO PATH_TBL VALUES ('[(1,2),(3,4)');
+ ^
+INSERT INTO PATH_TBL VALUES ('(1,2,3,4');
+ERROR: invalid input syntax for type path: "(1,2,3,4"
+LINE 1: INSERT INTO PATH_TBL VALUES ('(1,2,3,4');
+ ^
+INSERT INTO PATH_TBL VALUES ('(1,2),(3,4)]');
+ERROR: invalid input syntax for type path: "(1,2),(3,4)]"
+LINE 1: INSERT INTO PATH_TBL VALUES ('(1,2),(3,4)]');
+ ^
+SELECT f1 AS open_path FROM PATH_TBL WHERE isopen(f1);
+ open_path
+---------------------------
+ [(1,2),(3,4)]
+ [(0,0),(3,0),(4,5),(1,6)]
+ [(1,2),(3,4)]
+ [(11,12),(13,14)]
+(4 rows)
+
+SELECT f1 AS closed_path FROM PATH_TBL WHERE isclosed(f1);
+ closed_path
+-------------------
+ ((1,2),(3,4))
+ ((1,2),(3,4))
+ ((1,2),(3,4))
+ ((10,20))
+ ((11,12),(13,14))
+(5 rows)
+
+SELECT pclose(f1) AS closed_path FROM PATH_TBL;
+ closed_path
+---------------------------
+ ((1,2),(3,4))
+ ((1,2),(3,4))
+ ((0,0),(3,0),(4,5),(1,6))
+ ((1,2),(3,4))
+ ((1,2),(3,4))
+ ((1,2),(3,4))
+ ((10,20))
+ ((11,12),(13,14))
+ ((11,12),(13,14))
+(9 rows)
+
+SELECT popen(f1) AS open_path FROM PATH_TBL;
+ open_path
+---------------------------
+ [(1,2),(3,4)]
+ [(1,2),(3,4)]
+ [(0,0),(3,0),(4,5),(1,6)]
+ [(1,2),(3,4)]
+ [(1,2),(3,4)]
+ [(1,2),(3,4)]
+ [(10,20)]
+ [(11,12),(13,14)]
+ [(11,12),(13,14)]
+(9 rows)
+
diff --git a/src/test/regress/expected/pg_lsn.out b/src/test/regress/expected/pg_lsn.out
new file mode 100644
index 0000000..99a748a
--- /dev/null
+++ b/src/test/regress/expected/pg_lsn.out
@@ -0,0 +1,257 @@
+--
+-- 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');
+ ^
+-- Min/Max aggregation
+SELECT MIN(f1), MAX(f1) FROM PG_LSN_TBL;
+ min | max
+-----+-------------------
+ 0/0 | FFFFFFFF/FFFFFFFF
+(1 row)
+
+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)
+
+SELECT '0/16AE7F7'::pg_lsn + 16::numeric;
+ ?column?
+-----------
+ 0/16AE807
+(1 row)
+
+SELECT 16::numeric + '0/16AE7F7'::pg_lsn;
+ ?column?
+-----------
+ 0/16AE807
+(1 row)
+
+SELECT '0/16AE7F7'::pg_lsn - 16::numeric;
+ ?column?
+-----------
+ 0/16AE7E7
+(1 row)
+
+SELECT 'FFFFFFFF/FFFFFFFE'::pg_lsn + 1::numeric;
+ ?column?
+-------------------
+ FFFFFFFF/FFFFFFFF
+(1 row)
+
+SELECT 'FFFFFFFF/FFFFFFFE'::pg_lsn + 2::numeric; -- out of range error
+ERROR: pg_lsn out of range
+SELECT '0/1'::pg_lsn - 1::numeric;
+ ?column?
+----------
+ 0/0
+(1 row)
+
+SELECT '0/1'::pg_lsn - 2::numeric; -- out of range error
+ERROR: pg_lsn out of range
+SELECT '0/0'::pg_lsn + ('FFFFFFFF/FFFFFFFF'::pg_lsn - '0/0'::pg_lsn);
+ ?column?
+-------------------
+ FFFFFFFF/FFFFFFFF
+(1 row)
+
+SELECT 'FFFFFFFF/FFFFFFFF'::pg_lsn - ('FFFFFFFF/FFFFFFFF'::pg_lsn - '0/0'::pg_lsn);
+ ?column?
+----------
+ 0/0
+(1 row)
+
+SELECT '0/16AE7F7'::pg_lsn + 'NaN'::numeric;
+ERROR: cannot add NaN to pg_lsn
+SELECT '0/16AE7F7'::pg_lsn - 'NaN'::numeric;
+ERROR: cannot subtract NaN from pg_lsn
+-- Check btree 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)
+
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
new file mode 100644
index 0000000..4e59188
--- /dev/null
+++ b/src/test/regress/expected/plancache.out
@@ -0,0 +1,400 @@
+--
+-- Tests to exercise the plan caching/invalidation mechanism
+--
+CREATE TEMP TABLE pcachetest AS SELECT * FROM int8_tbl;
+-- create and use a cached plan
+PREPARE prepstmt AS SELECT * FROM pcachetest;
+EXECUTE prepstmt;
+ q1 | q2
+------------------+-------------------
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+(5 rows)
+
+-- and one with parameters
+PREPARE prepstmt2(bigint) AS SELECT * FROM pcachetest WHERE q1 = $1;
+EXECUTE prepstmt2(123);
+ q1 | q2
+-----+------------------
+ 123 | 456
+ 123 | 4567890123456789
+(2 rows)
+
+-- invalidate the plans and see what happens
+DROP TABLE pcachetest;
+EXECUTE prepstmt;
+ERROR: relation "pcachetest" does not exist
+EXECUTE prepstmt2(123);
+ERROR: relation "pcachetest" does not exist
+-- recreate the temp table (this demonstrates that the raw plan is
+-- purely textual and doesn't depend on OIDs, for instance)
+CREATE TEMP TABLE pcachetest AS SELECT * FROM int8_tbl ORDER BY 2;
+EXECUTE prepstmt;
+ q1 | q2
+------------------+-------------------
+ 4567890123456789 | -4567890123456789
+ 4567890123456789 | 123
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+(5 rows)
+
+EXECUTE prepstmt2(123);
+ q1 | q2
+-----+------------------
+ 123 | 456
+ 123 | 4567890123456789
+(2 rows)
+
+-- prepared statements should prevent change in output tupdesc,
+-- since clients probably aren't expecting that to change on the fly
+ALTER TABLE pcachetest ADD COLUMN q3 bigint;
+EXECUTE prepstmt;
+ERROR: cached plan must not change result type
+EXECUTE prepstmt2(123);
+ERROR: cached plan must not change result type
+-- but we're nice guys and will let you undo your mistake
+ALTER TABLE pcachetest DROP COLUMN q3;
+EXECUTE prepstmt;
+ q1 | q2
+------------------+-------------------
+ 4567890123456789 | -4567890123456789
+ 4567890123456789 | 123
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+(5 rows)
+
+EXECUTE prepstmt2(123);
+ q1 | q2
+-----+------------------
+ 123 | 456
+ 123 | 4567890123456789
+(2 rows)
+
+-- Try it with a view, which isn't directly used in the resulting plan
+-- but should trigger invalidation anyway
+CREATE TEMP VIEW pcacheview AS
+ SELECT * FROM pcachetest;
+PREPARE vprep AS SELECT * FROM pcacheview;
+EXECUTE vprep;
+ q1 | q2
+------------------+-------------------
+ 4567890123456789 | -4567890123456789
+ 4567890123456789 | 123
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+(5 rows)
+
+CREATE OR REPLACE TEMP VIEW pcacheview AS
+ SELECT q1, q2/2 AS q2 FROM pcachetest;
+EXECUTE vprep;
+ q1 | q2
+------------------+-------------------
+ 4567890123456789 | -2283945061728394
+ 4567890123456789 | 61
+ 123 | 228
+ 123 | 2283945061728394
+ 4567890123456789 | 2283945061728394
+(5 rows)
+
+-- Check basic SPI plan invalidation
+create function cache_test(int) returns int as $$
+declare total int;
+begin
+ create temp table t1(f1 int);
+ insert into t1 values($1);
+ insert into t1 values(11);
+ insert into t1 values(12);
+ insert into t1 values(13);
+ select sum(f1) into total from t1;
+ drop table t1;
+ return total;
+end
+$$ language plpgsql;
+select cache_test(1);
+ cache_test
+------------
+ 37
+(1 row)
+
+select cache_test(2);
+ cache_test
+------------
+ 38
+(1 row)
+
+select cache_test(3);
+ cache_test
+------------
+ 39
+(1 row)
+
+-- Check invalidation of plpgsql "simple expression"
+create temp view v1 as
+ select 2+2 as f1;
+create function cache_test_2() returns int as $$
+begin
+ return f1 from v1;
+end$$ language plpgsql;
+select cache_test_2();
+ cache_test_2
+--------------
+ 4
+(1 row)
+
+create or replace temp view v1 as
+ select 2+2+4 as f1;
+select cache_test_2();
+ cache_test_2
+--------------
+ 8
+(1 row)
+
+create or replace temp view v1 as
+ select 2+2+4+(select max(unique1) from tenk1) as f1;
+select cache_test_2();
+ cache_test_2
+--------------
+ 10007
+(1 row)
+
+--- Check that change of search_path is honored when re-using cached plan
+create schema s1
+ create table abc (f1 int);
+create schema s2
+ create table abc (f1 int);
+insert into s1.abc values(123);
+insert into s2.abc values(456);
+set search_path = s1;
+prepare p1 as select f1 from abc;
+execute p1;
+ f1
+-----
+ 123
+(1 row)
+
+set search_path = s2;
+select f1 from abc;
+ f1
+-----
+ 456
+(1 row)
+
+execute p1;
+ f1
+-----
+ 456
+(1 row)
+
+alter table s1.abc add column f2 float8; -- force replan
+execute p1;
+ f1
+-----
+ 456
+(1 row)
+
+drop schema s1 cascade;
+NOTICE: drop cascades to table s1.abc
+drop schema s2 cascade;
+NOTICE: drop cascades to table abc
+reset search_path;
+-- Check that invalidation deals with regclass constants
+create temp sequence seq;
+prepare p2 as select nextval('seq');
+execute p2;
+ nextval
+---------
+ 1
+(1 row)
+
+drop sequence seq;
+create temp sequence seq;
+execute p2;
+ nextval
+---------
+ 1
+(1 row)
+
+-- Check DDL via SPI, immediately followed by SPI plan re-use
+-- (bug in original coding)
+create function cachebug() returns void as $$
+declare r int;
+begin
+ drop table if exists temptable cascade;
+ create temp table temptable as select * from generate_series(1,3) as f1;
+ create temp view vv as select * from temptable;
+ for r in select * from vv loop
+ raise notice '%', r;
+ end loop;
+end$$ language plpgsql;
+select cachebug();
+NOTICE: table "temptable" does not exist, skipping
+NOTICE: 1
+NOTICE: 2
+NOTICE: 3
+ cachebug
+----------
+
+(1 row)
+
+select cachebug();
+NOTICE: drop cascades to view vv
+NOTICE: 1
+NOTICE: 2
+NOTICE: 3
+ cachebug
+----------
+
+(1 row)
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table pc_list_parted (a int) partition by list(a);
+create table pc_list_part_null partition of pc_list_parted for values in (null);
+create table pc_list_part_1 partition of pc_list_parted for values in (1);
+create table pc_list_part_def partition of pc_list_parted default;
+prepare pstmt_def_insert (int) as insert into pc_list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR: new row for relation "pc_list_part_def" violates partition constraint
+DETAIL: Failing row contains (null).
+execute pstmt_def_insert(1);
+ERROR: new row for relation "pc_list_part_def" violates partition constraint
+DETAIL: Failing row contains (1).
+create table pc_list_part_2 partition of pc_list_parted for values in (2);
+execute pstmt_def_insert(2);
+ERROR: new row for relation "pc_list_part_def" violates partition constraint
+DETAIL: Failing row contains (2).
+alter table pc_list_parted detach partition pc_list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table pc_list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table pc_list_parted, pc_list_part_null;
+deallocate pstmt_def_insert;
+-- Test plan_cache_mode
+create table test_mode (a int);
+insert into test_mode select 1 from generate_series(1,1000) union all select 2;
+create index on test_mode (a);
+analyze test_mode;
+prepare test_mode_pp (int) as select count(*) from test_mode where a = $1;
+select name, generic_plans, custom_plans from pg_prepared_statements
+ where name = 'test_mode_pp';
+ name | generic_plans | custom_plans
+--------------+---------------+--------------
+ test_mode_pp | 0 | 0
+(1 row)
+
+-- up to 5 executions, custom plan is used
+set plan_cache_mode to auto;
+explain (costs off) execute test_mode_pp(2);
+ QUERY PLAN
+----------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using test_mode_a_idx on test_mode
+ Index Cond: (a = 2)
+(3 rows)
+
+select name, generic_plans, custom_plans from pg_prepared_statements
+ where name = 'test_mode_pp';
+ name | generic_plans | custom_plans
+--------------+---------------+--------------
+ test_mode_pp | 0 | 1
+(1 row)
+
+-- force generic plan
+set plan_cache_mode to force_generic_plan;
+explain (costs off) execute test_mode_pp(2);
+ QUERY PLAN
+-----------------------------
+ Aggregate
+ -> Seq Scan on test_mode
+ Filter: (a = $1)
+(3 rows)
+
+select name, generic_plans, custom_plans from pg_prepared_statements
+ where name = 'test_mode_pp';
+ name | generic_plans | custom_plans
+--------------+---------------+--------------
+ test_mode_pp | 1 | 1
+(1 row)
+
+-- get to generic plan by 5 executions
+set plan_cache_mode to auto;
+execute test_mode_pp(1); -- 1x
+ count
+-------
+ 1000
+(1 row)
+
+execute test_mode_pp(1); -- 2x
+ count
+-------
+ 1000
+(1 row)
+
+execute test_mode_pp(1); -- 3x
+ count
+-------
+ 1000
+(1 row)
+
+execute test_mode_pp(1); -- 4x
+ count
+-------
+ 1000
+(1 row)
+
+select name, generic_plans, custom_plans from pg_prepared_statements
+ where name = 'test_mode_pp';
+ name | generic_plans | custom_plans
+--------------+---------------+--------------
+ test_mode_pp | 1 | 5
+(1 row)
+
+execute test_mode_pp(1); -- 5x
+ count
+-------
+ 1000
+(1 row)
+
+select name, generic_plans, custom_plans from pg_prepared_statements
+ where name = 'test_mode_pp';
+ name | generic_plans | custom_plans
+--------------+---------------+--------------
+ test_mode_pp | 2 | 5
+(1 row)
+
+-- we should now get a really bad plan
+explain (costs off) execute test_mode_pp(2);
+ QUERY PLAN
+-----------------------------
+ Aggregate
+ -> Seq Scan on test_mode
+ Filter: (a = $1)
+(3 rows)
+
+-- but we can force a custom plan
+set plan_cache_mode to force_custom_plan;
+explain (costs off) execute test_mode_pp(2);
+ QUERY PLAN
+----------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using test_mode_a_idx on test_mode
+ Index Cond: (a = 2)
+(3 rows)
+
+select name, generic_plans, custom_plans from pg_prepared_statements
+ where name = 'test_mode_pp';
+ name | generic_plans | custom_plans
+--------------+---------------+--------------
+ test_mode_pp | 3 | 6
+(1 row)
+
+drop table test_mode;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
new file mode 100644
index 0000000..278d056
--- /dev/null
+++ b/src/test/regress/expected/plpgsql.out
@@ -0,0 +1,5728 @@
+--
+-- 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';
+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 '
+<<outer>>
+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"
+DETAIL: Key (name)=(PF1_1) already exists.
+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"
+DETAIL: Key (slotname)=(HS.base.hub1.1 ) already exists.
+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)
+
+select * from found_test_tbl;
+ 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;
+select * from test_table_func_rec();
+ 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;
+select * from test_table_func_row();
+ 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)
+
+SELECT * FROM perform_test;
+ a | b
+-----+-----
+ 5 | 15
+ 100 | 100
+ 100 | 100
+(3 rows)
+
+drop table perform_test;
+--
+-- 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)
+
+--
+-- 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); <NULL>
+ 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)
+
+select * from foo;
+ 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;
+select * from 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;
+select * from 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
+ open c scroll for execute 'select f1 from int4_tbl';
+ 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;
+select * from sc_test();
+ sc_test
+-------------
+ -2147483647
+ 123456
+(2 rows)
+
+create or replace function sc_test() returns setof integer as $$
+declare
+ c cursor for select * from generate_series(1, 10);
+ x integer;
+begin
+ open c;
+ loop
+ move relative 2 in c;
+ if not found then
+ exit;
+ end if;
+ fetch next from c into x;
+ if found then
+ return next x;
+ end if;
+ end loop;
+ close c;
+end;
+$$ language plpgsql;
+select * from sc_test();
+ sc_test
+---------
+ 3
+ 6
+ 9
+(3 rows)
+
+create or replace function sc_test() returns setof integer as $$
+declare
+ c cursor for select * from generate_series(1, 10);
+ x integer;
+begin
+ open c;
+ move forward all in c;
+ fetch backward from c into x;
+ if found then
+ return next x;
+ end if;
+ close c;
+end;
+$$ language plpgsql;
+select * from sc_test();
+ sc_test
+---------
+ 10
+(1 row)
+
+drop function sc_test();
+-- test qualified variable names
+create function pl_qual_names (param1 int) returns void as $$
+<<outerblock>>
+declare
+ param1 int := 1;
+begin
+ <<innerblock>>
+ 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 = <NULL>
+ 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;
+select forc01();
+NOTICE: 1, 1
+NOTICE: 2, 2
+NOTICE: 3, 3
+NOTICE: 4, 4
+NOTICE: 5, 5
+NOTICE: 6, 6
+NOTICE: 7, 7
+NOTICE: 8, 8
+NOTICE: 9, 9
+NOTICE: 10, 10
+ forc01
+--------
+
+(1 row)
+
+select * from forc_test;
+ i | j
+------+----
+ 100 | 2
+ 200 | 4
+ 300 | 6
+ 400 | 8
+ 500 | 10
+ 600 | 12
+ 700 | 14
+ 800 | 16
+ 900 | 18
+ 1000 | 20
+(10 rows)
+
+-- same, with a cursor whose portal name doesn't match variable name
+create or replace function forc01() returns void as $$
+declare
+ c refcursor := 'fooled_ya';
+ r record;
+begin
+ open c for select * from forc_test;
+ loop
+ fetch c into r;
+ exit when not found;
+ 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;
+select forc01();
+NOTICE: 100, 2
+NOTICE: 200, 4
+NOTICE: 300, 6
+NOTICE: 400, 8
+NOTICE: 500, 10
+NOTICE: 600, 12
+NOTICE: 700, 14
+NOTICE: 800, 16
+NOTICE: 900, 18
+NOTICE: 1000, 20
+ forc01
+--------
+
+(1 row)
+
+select * from forc_test;
+ i | j
+--------+----
+ 10000 | 4
+ 20000 | 8
+ 30000 | 12
+ 40000 | 16
+ 50000 | 20
+ 60000 | 24
+ 70000 | 28
+ 80000 | 32
+ 90000 | 36
+ 100000 | 40
+(10 rows)
+
+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
+ return query select * from tabwithcols;
+ return query execute 'select * from tabwithcols';
+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.
+create function scope_test() returns int as $$
+declare x int := 42;
+begin
+ declare y int := x + 1;
+ x int := x + 2;
+ begin
+ return x * 100 + y;
+ end;
+end;
+$$ language plpgsql;
+select scope_test();
+ scope_test
+------------
+ 4443
+(1 row)
+
+drop function scope_test();
+-- 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
+ 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: OLD TABLE can only be specified for a DELETE or UPDATE trigger
+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');
+INFO: Named Tuplestore Scan
+ Output: id, val
+
+INSERT INTO transition_table_base VALUES (3, 'Three'), (4, 'Four');
+INFO: Named Tuplestore Scan
+ Output: id, val
+
+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;
+INFO: Hash Full Join
+ Output: COALESCE(ot.id, nt.id), ot.val, nt.val
+ Hash Cond: (ot.id = nt.id)
+ -> Named Tuplestore Scan
+ Output: ot.id, ot.val
+ -> Hash
+ Output: nt.id, nt.val
+ -> Named Tuplestore Scan
+ Output: nt.id, nt.val
+
+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;
+ERROR: relation "dx" cannot be the target of a modifying statement
+CONTEXT: SQL statement "INSERT INTO dx VALUES (1000000, 1000000, 'x')"
+PL/pgSQL function transition_table_level2_bad_usage_func() line 3 at SQL statement
+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;
+ERROR: RI error
+CONTEXT: PL/pgSQL function transition_table_level1_ri_parent_del_func() line 6 at RAISE
+UPDATE transition_table_level1 SET level1_no = -1
+ WHERE level1_no = 30;
+ERROR: RI error
+CONTEXT: PL/pgSQL function transition_table_level1_ri_parent_upd_func() line 15 at RAISE
+INSERT INTO transition_table_level2 (level2_no, parent_no)
+ VALUES (10000, 10000);
+ERROR: RI error
+CONTEXT: PL/pgSQL function transition_table_level2_ri_child_insupd_func() line 8 at RAISE
+UPDATE transition_table_level2 SET parent_no = 2000
+ WHERE level2_no = 40;
+ERROR: RI error
+CONTEXT: PL/pgSQL function transition_table_level2_ri_child_insupd_func() line 8 at RAISE
+-- 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
+-------
+ 200
+(1 row)
+
+DELETE FROM transition_table_level2
+ WHERE level2_no BETWEEN 211 AND 220;
+SELECT count(*) FROM transition_table_level2;
+ count
+-------
+ 9989
+(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: TRUNCATE triggers with transition tables are not supported
+-- 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;
+WARNING: old table = 1=1,2=2,3=3, new table = 1=11,2=22,3=33
+NOTICE: one = 1
+-- 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;
+WARNING: old table = 1=11,2=22,3=33, new table = 1=1111,2=2222,3=3333
+NOTICE: one = 1
+-- now drop column 'name'
+ALTER TABLE alter_table_under_transition_tables
+ DROP column name;
+UPDATE alter_table_under_transition_tables
+ SET id = id;
+ERROR: column "name" does not exist
+LINE 1: (SELECT string_agg(id || '=' || name, ',') FROM d)
+ ^
+QUERY: (SELECT string_agg(id || '=' || name, ',') FROM d)
+CONTEXT: PL/pgSQL function alter_table_under_transition_tables_upd_func() line 3 at RAISE
+--
+-- 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;
+NOTICE: count = 1
+NOTICE: count union = 2
+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;
+ ^
diff --git a/src/test/regress/expected/point.out b/src/test/regress/expected/point.out
new file mode 100644
index 0000000..1dc535d
--- /dev/null
+++ b/src/test/regress/expected/point.out
@@ -0,0 +1,475 @@
+--
+-- POINT
+--
+-- avoid bit-exact output here because operations may not be bit-exact.
+SET extra_float_digits = 0;
+CREATE TABLE POINT_TBL(f1 point);
+INSERT INTO POINT_TBL(f1) VALUES ('(0.0,0.0)');
+INSERT INTO POINT_TBL(f1) VALUES ('(-10.0,0.0)');
+INSERT INTO POINT_TBL(f1) VALUES ('(-3.0,4.0)');
+INSERT INTO POINT_TBL(f1) VALUES ('(5.1, 34.5)');
+INSERT INTO POINT_TBL(f1) VALUES ('(-5.0,-12.0)');
+INSERT INTO POINT_TBL(f1) VALUES ('(1e-300,-1e-300)'); -- To underflow
+INSERT INTO POINT_TBL(f1) VALUES ('(1e+300,Inf)'); -- To overflow
+INSERT INTO POINT_TBL(f1) VALUES ('(Inf,1e+300)'); -- Transposed
+INSERT INTO POINT_TBL(f1) VALUES (' ( Nan , NaN ) ');
+-- bad format points
+INSERT INTO POINT_TBL(f1) VALUES ('asdfasdf');
+ERROR: invalid input syntax for type point: "asdfasdf"
+LINE 1: INSERT INTO POINT_TBL(f1) VALUES ('asdfasdf');
+ ^
+INSERT INTO POINT_TBL(f1) VALUES ('10.0,10.0');
+INSERT INTO POINT_TBL(f1) VALUES ('(10.0 10.0)');
+ERROR: invalid input syntax for type point: "(10.0 10.0)"
+LINE 1: INSERT INTO POINT_TBL(f1) VALUES ('(10.0 10.0)');
+ ^
+INSERT INTO POINT_TBL(f1) VALUES ('(10.0, 10.0) x');
+ERROR: invalid input syntax for type point: "(10.0, 10.0) x"
+LINE 1: INSERT INTO POINT_TBL(f1) VALUES ('(10.0, 10.0) x');
+ ^
+INSERT INTO POINT_TBL(f1) VALUES ('(10.0,10.0');
+ERROR: invalid input syntax for type point: "(10.0,10.0"
+LINE 1: INSERT INTO POINT_TBL(f1) VALUES ('(10.0,10.0');
+ ^
+INSERT INTO POINT_TBL(f1) VALUES ('(10.0, 1e+500)'); -- Out of range
+ERROR: "1e+500" is out of range for type double precision
+LINE 1: INSERT INTO POINT_TBL(f1) VALUES ('(10.0, 1e+500)');
+ ^
+SELECT * FROM POINT_TBL;
+ f1
+-------------------
+ (0,0)
+ (-10,0)
+ (-3,4)
+ (5.1,34.5)
+ (-5,-12)
+ (1e-300,-1e-300)
+ (1e+300,Infinity)
+ (Infinity,1e+300)
+ (NaN,NaN)
+ (10,10)
+(10 rows)
+
+-- left of
+SELECT p.* FROM POINT_TBL p WHERE p.f1 << '(0.0, 0.0)';
+ f1
+----------
+ (-10,0)
+ (-3,4)
+ (-5,-12)
+(3 rows)
+
+-- right of
+SELECT p.* FROM POINT_TBL p WHERE '(0.0,0.0)' >> p.f1;
+ f1
+----------
+ (-10,0)
+ (-3,4)
+ (-5,-12)
+(3 rows)
+
+-- above
+SELECT p.* FROM POINT_TBL p WHERE '(0.0,0.0)' |>> p.f1;
+ f1
+----------
+ (-5,-12)
+(1 row)
+
+-- below
+SELECT p.* FROM POINT_TBL p WHERE p.f1 <<| '(0.0, 0.0)';
+ f1
+----------
+ (-5,-12)
+(1 row)
+
+-- equal
+SELECT p.* FROM POINT_TBL p WHERE p.f1 ~= '(5.1, 34.5)';
+ f1
+------------
+ (5.1,34.5)
+(1 row)
+
+-- point in box
+SELECT p.* FROM POINT_TBL p
+ WHERE p.f1 <@ box '(0,0,100,100)';
+ f1
+------------
+ (0,0)
+ (5.1,34.5)
+ (10,10)
+(3 rows)
+
+SELECT p.* FROM POINT_TBL p
+ WHERE box '(0,0,100,100)' @> p.f1;
+ f1
+------------
+ (0,0)
+ (5.1,34.5)
+ (10,10)
+(3 rows)
+
+SELECT p.* FROM POINT_TBL p
+ WHERE not p.f1 <@ box '(0,0,100,100)';
+ f1
+-------------------
+ (-10,0)
+ (-3,4)
+ (-5,-12)
+ (1e-300,-1e-300)
+ (1e+300,Infinity)
+ (Infinity,1e+300)
+ (NaN,NaN)
+(7 rows)
+
+SELECT p.* FROM POINT_TBL p
+ WHERE p.f1 <@ path '[(0,0),(-10,0),(-10,10)]';
+ f1
+------------------
+ (0,0)
+ (-10,0)
+ (1e-300,-1e-300)
+(3 rows)
+
+SELECT p.* FROM POINT_TBL p
+ WHERE not box '(0,0,100,100)' @> p.f1;
+ f1
+-------------------
+ (-10,0)
+ (-3,4)
+ (-5,-12)
+ (1e-300,-1e-300)
+ (1e+300,Infinity)
+ (Infinity,1e+300)
+ (NaN,NaN)
+(7 rows)
+
+SELECT p.f1, p.f1 <-> point '(0,0)' AS dist
+ FROM POINT_TBL p
+ ORDER BY dist;
+ f1 | dist
+-------------------+----------------------
+ (0,0) | 0
+ (1e-300,-1e-300) | 1.4142135623731e-300
+ (-3,4) | 5
+ (-10,0) | 10
+ (-5,-12) | 13
+ (10,10) | 14.142135623731
+ (5.1,34.5) | 34.8749193547455
+ (1e+300,Infinity) | Infinity
+ (Infinity,1e+300) | Infinity
+ (NaN,NaN) | NaN
+(10 rows)
+
+SELECT p1.f1 AS point1, p2.f1 AS point2, p1.f1 <-> p2.f1 AS dist
+ FROM POINT_TBL p1, POINT_TBL p2
+ ORDER BY dist, p1.f1[0], p2.f1[0];
+ point1 | point2 | dist
+-------------------+-------------------+----------------------
+ (-10,0) | (-10,0) | 0
+ (-5,-12) | (-5,-12) | 0
+ (-3,4) | (-3,4) | 0
+ (0,0) | (0,0) | 0
+ (1e-300,-1e-300) | (1e-300,-1e-300) | 0
+ (5.1,34.5) | (5.1,34.5) | 0
+ (10,10) | (10,10) | 0
+ (0,0) | (1e-300,-1e-300) | 1.4142135623731e-300
+ (1e-300,-1e-300) | (0,0) | 1.4142135623731e-300
+ (-3,4) | (0,0) | 5
+ (-3,4) | (1e-300,-1e-300) | 5
+ (0,0) | (-3,4) | 5
+ (1e-300,-1e-300) | (-3,4) | 5
+ (-10,0) | (-3,4) | 8.06225774829855
+ (-3,4) | (-10,0) | 8.06225774829855
+ (-10,0) | (0,0) | 10
+ (-10,0) | (1e-300,-1e-300) | 10
+ (0,0) | (-10,0) | 10
+ (1e-300,-1e-300) | (-10,0) | 10
+ (-10,0) | (-5,-12) | 13
+ (-5,-12) | (-10,0) | 13
+ (-5,-12) | (0,0) | 13
+ (-5,-12) | (1e-300,-1e-300) | 13
+ (0,0) | (-5,-12) | 13
+ (1e-300,-1e-300) | (-5,-12) | 13
+ (0,0) | (10,10) | 14.142135623731
+ (1e-300,-1e-300) | (10,10) | 14.142135623731
+ (10,10) | (0,0) | 14.142135623731
+ (10,10) | (1e-300,-1e-300) | 14.142135623731
+ (-3,4) | (10,10) | 14.3178210632764
+ (10,10) | (-3,4) | 14.3178210632764
+ (-5,-12) | (-3,4) | 16.1245154965971
+ (-3,4) | (-5,-12) | 16.1245154965971
+ (-10,0) | (10,10) | 22.3606797749979
+ (10,10) | (-10,0) | 22.3606797749979
+ (5.1,34.5) | (10,10) | 24.9851956166046
+ (10,10) | (5.1,34.5) | 24.9851956166046
+ (-5,-12) | (10,10) | 26.6270539113887
+ (10,10) | (-5,-12) | 26.6270539113887
+ (-3,4) | (5.1,34.5) | 31.5572495632937
+ (5.1,34.5) | (-3,4) | 31.5572495632937
+ (0,0) | (5.1,34.5) | 34.8749193547455
+ (1e-300,-1e-300) | (5.1,34.5) | 34.8749193547455
+ (5.1,34.5) | (0,0) | 34.8749193547455
+ (5.1,34.5) | (1e-300,-1e-300) | 34.8749193547455
+ (-10,0) | (5.1,34.5) | 37.6597928831267
+ (5.1,34.5) | (-10,0) | 37.6597928831267
+ (-5,-12) | (5.1,34.5) | 47.5842410888311
+ (5.1,34.5) | (-5,-12) | 47.5842410888311
+ (-10,0) | (1e+300,Infinity) | Infinity
+ (-10,0) | (Infinity,1e+300) | Infinity
+ (-5,-12) | (1e+300,Infinity) | Infinity
+ (-5,-12) | (Infinity,1e+300) | Infinity
+ (-3,4) | (1e+300,Infinity) | Infinity
+ (-3,4) | (Infinity,1e+300) | Infinity
+ (0,0) | (1e+300,Infinity) | Infinity
+ (0,0) | (Infinity,1e+300) | Infinity
+ (1e-300,-1e-300) | (1e+300,Infinity) | Infinity
+ (1e-300,-1e-300) | (Infinity,1e+300) | Infinity
+ (5.1,34.5) | (1e+300,Infinity) | Infinity
+ (5.1,34.5) | (Infinity,1e+300) | Infinity
+ (10,10) | (1e+300,Infinity) | Infinity
+ (10,10) | (Infinity,1e+300) | Infinity
+ (1e+300,Infinity) | (-10,0) | Infinity
+ (1e+300,Infinity) | (-5,-12) | Infinity
+ (1e+300,Infinity) | (-3,4) | Infinity
+ (1e+300,Infinity) | (0,0) | Infinity
+ (1e+300,Infinity) | (1e-300,-1e-300) | Infinity
+ (1e+300,Infinity) | (5.1,34.5) | Infinity
+ (1e+300,Infinity) | (10,10) | Infinity
+ (1e+300,Infinity) | (Infinity,1e+300) | Infinity
+ (Infinity,1e+300) | (-10,0) | Infinity
+ (Infinity,1e+300) | (-5,-12) | Infinity
+ (Infinity,1e+300) | (-3,4) | Infinity
+ (Infinity,1e+300) | (0,0) | Infinity
+ (Infinity,1e+300) | (1e-300,-1e-300) | Infinity
+ (Infinity,1e+300) | (5.1,34.5) | Infinity
+ (Infinity,1e+300) | (10,10) | Infinity
+ (Infinity,1e+300) | (1e+300,Infinity) | Infinity
+ (-10,0) | (NaN,NaN) | NaN
+ (-5,-12) | (NaN,NaN) | NaN
+ (-3,4) | (NaN,NaN) | NaN
+ (0,0) | (NaN,NaN) | NaN
+ (1e-300,-1e-300) | (NaN,NaN) | NaN
+ (5.1,34.5) | (NaN,NaN) | NaN
+ (10,10) | (NaN,NaN) | NaN
+ (1e+300,Infinity) | (1e+300,Infinity) | NaN
+ (1e+300,Infinity) | (NaN,NaN) | NaN
+ (Infinity,1e+300) | (Infinity,1e+300) | NaN
+ (Infinity,1e+300) | (NaN,NaN) | NaN
+ (NaN,NaN) | (-10,0) | NaN
+ (NaN,NaN) | (-5,-12) | NaN
+ (NaN,NaN) | (-3,4) | NaN
+ (NaN,NaN) | (0,0) | NaN
+ (NaN,NaN) | (1e-300,-1e-300) | NaN
+ (NaN,NaN) | (5.1,34.5) | NaN
+ (NaN,NaN) | (10,10) | NaN
+ (NaN,NaN) | (1e+300,Infinity) | NaN
+ (NaN,NaN) | (Infinity,1e+300) | NaN
+ (NaN,NaN) | (NaN,NaN) | NaN
+(100 rows)
+
+SELECT p1.f1 AS point1, p2.f1 AS point2
+ FROM POINT_TBL p1, POINT_TBL p2
+ WHERE (p1.f1 <-> p2.f1) > 3;
+ point1 | point2
+-------------------+-------------------
+ (0,0) | (-10,0)
+ (0,0) | (-3,4)
+ (0,0) | (5.1,34.5)
+ (0,0) | (-5,-12)
+ (0,0) | (1e+300,Infinity)
+ (0,0) | (Infinity,1e+300)
+ (0,0) | (NaN,NaN)
+ (0,0) | (10,10)
+ (-10,0) | (0,0)
+ (-10,0) | (-3,4)
+ (-10,0) | (5.1,34.5)
+ (-10,0) | (-5,-12)
+ (-10,0) | (1e-300,-1e-300)
+ (-10,0) | (1e+300,Infinity)
+ (-10,0) | (Infinity,1e+300)
+ (-10,0) | (NaN,NaN)
+ (-10,0) | (10,10)
+ (-3,4) | (0,0)
+ (-3,4) | (-10,0)
+ (-3,4) | (5.1,34.5)
+ (-3,4) | (-5,-12)
+ (-3,4) | (1e-300,-1e-300)
+ (-3,4) | (1e+300,Infinity)
+ (-3,4) | (Infinity,1e+300)
+ (-3,4) | (NaN,NaN)
+ (-3,4) | (10,10)
+ (5.1,34.5) | (0,0)
+ (5.1,34.5) | (-10,0)
+ (5.1,34.5) | (-3,4)
+ (5.1,34.5) | (-5,-12)
+ (5.1,34.5) | (1e-300,-1e-300)
+ (5.1,34.5) | (1e+300,Infinity)
+ (5.1,34.5) | (Infinity,1e+300)
+ (5.1,34.5) | (NaN,NaN)
+ (5.1,34.5) | (10,10)
+ (-5,-12) | (0,0)
+ (-5,-12) | (-10,0)
+ (-5,-12) | (-3,4)
+ (-5,-12) | (5.1,34.5)
+ (-5,-12) | (1e-300,-1e-300)
+ (-5,-12) | (1e+300,Infinity)
+ (-5,-12) | (Infinity,1e+300)
+ (-5,-12) | (NaN,NaN)
+ (-5,-12) | (10,10)
+ (1e-300,-1e-300) | (-10,0)
+ (1e-300,-1e-300) | (-3,4)
+ (1e-300,-1e-300) | (5.1,34.5)
+ (1e-300,-1e-300) | (-5,-12)
+ (1e-300,-1e-300) | (1e+300,Infinity)
+ (1e-300,-1e-300) | (Infinity,1e+300)
+ (1e-300,-1e-300) | (NaN,NaN)
+ (1e-300,-1e-300) | (10,10)
+ (1e+300,Infinity) | (0,0)
+ (1e+300,Infinity) | (-10,0)
+ (1e+300,Infinity) | (-3,4)
+ (1e+300,Infinity) | (5.1,34.5)
+ (1e+300,Infinity) | (-5,-12)
+ (1e+300,Infinity) | (1e-300,-1e-300)
+ (1e+300,Infinity) | (1e+300,Infinity)
+ (1e+300,Infinity) | (Infinity,1e+300)
+ (1e+300,Infinity) | (NaN,NaN)
+ (1e+300,Infinity) | (10,10)
+ (Infinity,1e+300) | (0,0)
+ (Infinity,1e+300) | (-10,0)
+ (Infinity,1e+300) | (-3,4)
+ (Infinity,1e+300) | (5.1,34.5)
+ (Infinity,1e+300) | (-5,-12)
+ (Infinity,1e+300) | (1e-300,-1e-300)
+ (Infinity,1e+300) | (1e+300,Infinity)
+ (Infinity,1e+300) | (Infinity,1e+300)
+ (Infinity,1e+300) | (NaN,NaN)
+ (Infinity,1e+300) | (10,10)
+ (NaN,NaN) | (0,0)
+ (NaN,NaN) | (-10,0)
+ (NaN,NaN) | (-3,4)
+ (NaN,NaN) | (5.1,34.5)
+ (NaN,NaN) | (-5,-12)
+ (NaN,NaN) | (1e-300,-1e-300)
+ (NaN,NaN) | (1e+300,Infinity)
+ (NaN,NaN) | (Infinity,1e+300)
+ (NaN,NaN) | (NaN,NaN)
+ (NaN,NaN) | (10,10)
+ (10,10) | (0,0)
+ (10,10) | (-10,0)
+ (10,10) | (-3,4)
+ (10,10) | (5.1,34.5)
+ (10,10) | (-5,-12)
+ (10,10) | (1e-300,-1e-300)
+ (10,10) | (1e+300,Infinity)
+ (10,10) | (Infinity,1e+300)
+ (10,10) | (NaN,NaN)
+(91 rows)
+
+-- put distance result into output to allow sorting with GEQ optimizer - tgl 97/05/10
+SELECT p1.f1 AS point1, p2.f1 AS point2, (p1.f1 <-> p2.f1) AS distance
+ FROM POINT_TBL p1, POINT_TBL p2
+ WHERE (p1.f1 <-> p2.f1) > 3 and p1.f1 << p2.f1
+ ORDER BY distance, p1.f1[0], p2.f1[0];
+ point1 | point2 | distance
+-------------------+-------------------+------------------
+ (-3,4) | (0,0) | 5
+ (-3,4) | (1e-300,-1e-300) | 5
+ (-10,0) | (-3,4) | 8.06225774829855
+ (-10,0) | (0,0) | 10
+ (-10,0) | (1e-300,-1e-300) | 10
+ (-10,0) | (-5,-12) | 13
+ (-5,-12) | (0,0) | 13
+ (-5,-12) | (1e-300,-1e-300) | 13
+ (0,0) | (10,10) | 14.142135623731
+ (1e-300,-1e-300) | (10,10) | 14.142135623731
+ (-3,4) | (10,10) | 14.3178210632764
+ (-5,-12) | (-3,4) | 16.1245154965971
+ (-10,0) | (10,10) | 22.3606797749979
+ (5.1,34.5) | (10,10) | 24.9851956166046
+ (-5,-12) | (10,10) | 26.6270539113887
+ (-3,4) | (5.1,34.5) | 31.5572495632937
+ (0,0) | (5.1,34.5) | 34.8749193547455
+ (1e-300,-1e-300) | (5.1,34.5) | 34.8749193547455
+ (-10,0) | (5.1,34.5) | 37.6597928831267
+ (-5,-12) | (5.1,34.5) | 47.5842410888311
+ (-10,0) | (1e+300,Infinity) | Infinity
+ (-10,0) | (Infinity,1e+300) | Infinity
+ (-5,-12) | (1e+300,Infinity) | Infinity
+ (-5,-12) | (Infinity,1e+300) | Infinity
+ (-3,4) | (1e+300,Infinity) | Infinity
+ (-3,4) | (Infinity,1e+300) | Infinity
+ (0,0) | (1e+300,Infinity) | Infinity
+ (0,0) | (Infinity,1e+300) | Infinity
+ (1e-300,-1e-300) | (1e+300,Infinity) | Infinity
+ (1e-300,-1e-300) | (Infinity,1e+300) | Infinity
+ (5.1,34.5) | (1e+300,Infinity) | Infinity
+ (5.1,34.5) | (Infinity,1e+300) | Infinity
+ (10,10) | (1e+300,Infinity) | Infinity
+ (10,10) | (Infinity,1e+300) | Infinity
+ (1e+300,Infinity) | (Infinity,1e+300) | Infinity
+(35 rows)
+
+-- put distance result into output to allow sorting with GEQ optimizer - tgl 97/05/10
+SELECT p1.f1 AS point1, p2.f1 AS point2, (p1.f1 <-> p2.f1) AS distance
+ FROM POINT_TBL p1, POINT_TBL p2
+ WHERE (p1.f1 <-> p2.f1) > 3 and p1.f1 << p2.f1 and p1.f1 |>> p2.f1
+ ORDER BY distance;
+ point1 | point2 | distance
+-------------------+-------------------+------------------
+ (-3,4) | (0,0) | 5
+ (-3,4) | (1e-300,-1e-300) | 5
+ (-10,0) | (-5,-12) | 13
+ (5.1,34.5) | (10,10) | 24.9851956166046
+ (1e+300,Infinity) | (Infinity,1e+300) | Infinity
+(5 rows)
+
+-- Test that GiST indexes provide same behavior as sequential scan
+CREATE TEMP TABLE point_gist_tbl(f1 point);
+INSERT INTO point_gist_tbl SELECT '(0,0)' FROM generate_series(0,1000);
+CREATE INDEX point_gist_tbl_index ON point_gist_tbl USING gist (f1);
+INSERT INTO point_gist_tbl VALUES ('(0.0000009,0.0000009)');
+SET enable_seqscan TO true;
+SET enable_indexscan TO false;
+SET enable_bitmapscan TO false;
+SELECT COUNT(*) FROM point_gist_tbl WHERE f1 ~= '(0.0000009,0.0000009)'::point;
+ count
+-------
+ 1002
+(1 row)
+
+SELECT COUNT(*) FROM point_gist_tbl WHERE f1 <@ '(0.0000009,0.0000009),(0.0000009,0.0000009)'::box;
+ count
+-------
+ 1
+(1 row)
+
+SELECT COUNT(*) FROM point_gist_tbl WHERE f1 ~= '(0.0000018,0.0000018)'::point;
+ count
+-------
+ 1
+(1 row)
+
+SET enable_seqscan TO false;
+SET enable_indexscan TO true;
+SET enable_bitmapscan TO true;
+SELECT COUNT(*) FROM point_gist_tbl WHERE f1 ~= '(0.0000009,0.0000009)'::point;
+ count
+-------
+ 1002
+(1 row)
+
+SELECT COUNT(*) FROM point_gist_tbl WHERE f1 <@ '(0.0000009,0.0000009),(0.0000009,0.0000009)'::box;
+ count
+-------
+ 1
+(1 row)
+
+SELECT COUNT(*) FROM point_gist_tbl WHERE f1 ~= '(0.0000018,0.0000018)'::point;
+ count
+-------
+ 1
+(1 row)
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/expected/polygon.out b/src/test/regress/expected/polygon.out
new file mode 100644
index 0000000..38e433b
--- /dev/null
+++ b/src/test/regress/expected/polygon.out
@@ -0,0 +1,308 @@
+--
+-- 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);
+-- 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
+---------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_poly_tbl
+ Recheck Cond: (p << '((300,300),(400,600),(600,500),(700,200))'::polygon)
+ -> Bitmap Index Scan on quad_poly_tbl_idx
+ Index Cond: (p << '((300,300),(400,600),(600,500),(700,200))'::polygon)
+(5 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
+---------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_poly_tbl
+ Recheck Cond: (p &< '((300,300),(400,600),(600,500),(700,200))'::polygon)
+ -> Bitmap Index Scan on quad_poly_tbl_idx
+ Index Cond: (p &< '((300,300),(400,600),(600,500),(700,200))'::polygon)
+(5 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
+---------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_poly_tbl
+ Recheck Cond: (p && '((300,300),(400,600),(600,500),(700,200))'::polygon)
+ -> Bitmap Index Scan on quad_poly_tbl_idx
+ Index Cond: (p && '((300,300),(400,600),(600,500),(700,200))'::polygon)
+(5 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
+---------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_poly_tbl
+ Recheck Cond: (p &> '((300,300),(400,600),(600,500),(700,200))'::polygon)
+ -> Bitmap Index Scan on quad_poly_tbl_idx
+ Index Cond: (p &> '((300,300),(400,600),(600,500),(700,200))'::polygon)
+(5 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
+---------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_poly_tbl
+ Recheck Cond: (p >> '((300,300),(400,600),(600,500),(700,200))'::polygon)
+ -> Bitmap Index Scan on quad_poly_tbl_idx
+ Index Cond: (p >> '((300,300),(400,600),(600,500),(700,200))'::polygon)
+(5 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
+----------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_poly_tbl
+ Recheck Cond: (p <<| '((300,300),(400,600),(600,500),(700,200))'::polygon)
+ -> Bitmap Index Scan on quad_poly_tbl_idx
+ Index Cond: (p <<| '((300,300),(400,600),(600,500),(700,200))'::polygon)
+(5 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
+----------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_poly_tbl
+ Recheck Cond: (p &<| '((300,300),(400,600),(600,500),(700,200))'::polygon)
+ -> Bitmap Index Scan on quad_poly_tbl_idx
+ Index Cond: (p &<| '((300,300),(400,600),(600,500),(700,200))'::polygon)
+(5 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
+----------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_poly_tbl
+ Recheck Cond: (p |&> '((300,300),(400,600),(600,500),(700,200))'::polygon)
+ -> Bitmap Index Scan on quad_poly_tbl_idx
+ Index Cond: (p |&> '((300,300),(400,600),(600,500),(700,200))'::polygon)
+(5 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
+----------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_poly_tbl
+ Recheck Cond: (p |>> '((300,300),(400,600),(600,500),(700,200))'::polygon)
+ -> Bitmap Index Scan on quad_poly_tbl_idx
+ Index Cond: (p |>> '((300,300),(400,600),(600,500),(700,200))'::polygon)
+(5 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
+---------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_poly_tbl
+ Recheck Cond: (p <@ '((300,300),(400,600),(600,500),(700,200))'::polygon)
+ -> Bitmap Index Scan on quad_poly_tbl_idx
+ Index Cond: (p <@ '((300,300),(400,600),(600,500),(700,200))'::polygon)
+(5 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
+-----------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_poly_tbl
+ Recheck Cond: (p @> '((340,550),(343,552),(341,553))'::polygon)
+ -> Bitmap Index Scan on quad_poly_tbl_idx
+ Index Cond: (p @> '((340,550),(343,552),(341,553))'::polygon)
+(5 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
+-----------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on quad_poly_tbl
+ Recheck Cond: (p ~= '((200,300),(210,310),(230,290))'::polygon)
+ -> Bitmap Index Scan on quad_poly_tbl_idx
+ Index Cond: (p ~= '((200,300),(210,310),(230,290))'::polygon)
+(5 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
+ -> Index Scan using quad_poly_tbl_idx on quad_poly_tbl
+ Index Cond: (p <@ '((300,300),(400,600),(600,500),(700,200))'::polygon)
+ Order By: (p <-> '(123,456)'::point)
+(4 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/polymorphism.out b/src/test/regress/expected/polymorphism.out
new file mode 100644
index 0000000..1cd558d
--- /dev/null
+++ b/src/test/regress/expected/polymorphism.out
@@ -0,0 +1,2098 @@
+--
+-- Tests for polymorphic SQL functions and aggregates based on them.
+-- Tests for other features related to function-calling have snuck in, too.
+--
+create function polyf(x anyelement) returns anyelement as $$
+ select x + 1
+$$ language sql;
+select polyf(42) as int, polyf(4.5) as num;
+ int | num
+-----+-----
+ 43 | 5.5
+(1 row)
+
+select polyf(point(3,4)); -- fail for lack of + operator
+ERROR: operator does not exist: point + integer
+LINE 2: select x + 1
+ ^
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+QUERY:
+ select x + 1
+
+CONTEXT: SQL function "polyf" during inlining
+drop function polyf(x anyelement);
+create function polyf(x anyelement) returns anyarray as $$
+ select array[x + 1, x + 2]
+$$ language sql;
+select polyf(42) as int, polyf(4.5) as num;
+ int | num
+---------+-----------
+ {43,44} | {5.5,6.5}
+(1 row)
+
+drop function polyf(x anyelement);
+create function polyf(x anyarray) returns anyelement as $$
+ select x[1]
+$$ language sql;
+select polyf(array[2,4]) as int, polyf(array[4.5, 7.7]) as num;
+ int | num
+-----+-----
+ 2 | 4.5
+(1 row)
+
+select polyf(stavalues1) from pg_statistic; -- fail, can't infer element type
+ERROR: cannot determine element type of "anyarray" argument
+drop function polyf(x anyarray);
+create function polyf(x anyarray) returns anyarray as $$
+ select x
+$$ language sql;
+select polyf(array[2,4]) as int, polyf(array[4.5, 7.7]) as num;
+ int | num
+-------+-----------
+ {2,4} | {4.5,7.7}
+(1 row)
+
+select polyf(stavalues1) from pg_statistic; -- fail, can't infer element type
+ERROR: return type anyarray is not supported for SQL functions
+CONTEXT: SQL function "polyf" during inlining
+drop function polyf(x anyarray);
+-- fail, can't infer type:
+create function polyf(x anyelement) returns anyrange as $$
+ select array[x + 1, x + 2]
+$$ 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 polyf(x anyrange) returns anyarray as $$
+ select array[lower(x), upper(x)]
+$$ language sql;
+select polyf(int4range(42, 49)) as int, polyf(float8range(4.5, 7.8)) as num;
+ int | num
+---------+-----------
+ {42,49} | {4.5,7.8}
+(1 row)
+
+drop function polyf(x anyrange);
+create function polyf(x anycompatible, y anycompatible) returns anycompatiblearray as $$
+ select array[x, y]
+$$ language sql;
+select polyf(2, 4) as int, polyf(2, 4.5) as num;
+ int | num
+-------+---------
+ {2,4} | {2,4.5}
+(1 row)
+
+drop function polyf(x anycompatible, y anycompatible);
+create function polyf(x anycompatiblerange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+ select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(int4range(42, 49), 11, 2::smallint) as int, polyf(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 polyf(int4range(42, 49), 11, 4.5) as fail; -- range type doesn't fit
+ERROR: function polyf(int4range, integer, numeric) does not exist
+LINE 1: select polyf(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 polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+ select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(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 polyf(multirange(int4range(42, 49)), 11, 4.5) as fail; -- range type doesn't fit
+ERROR: function polyf(int4multirange, integer, numeric) does not exist
+LINE 1: select polyf(multirange(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 polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblerange as $$
+ select array[x + 1, x + 2]
+$$ language sql;
+ERROR: cannot determine result data type
+DETAIL: A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
+ select x
+$$ language sql;
+select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8), array[7]) as num;
+ int | num
+---------+-----------
+ [42,49) | [4.5,7.8)
+(1 row)
+
+drop function polyf(x anycompatiblerange, y anycompatiblearray);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+ select array[x + 1, x + 2]
+$$ language sql;
+ERROR: cannot determine result data type
+DETAIL: A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+ select x
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+ int | num
+-----------+-------------
+ {[42,49)} | {[4.5,7.8)}
+(1 row)
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+create function polyf(a anyelement, b anyarray,
+ c anycompatible, d anycompatible,
+ OUT x anyarray, OUT y anycompatiblearray)
+as $$
+ select a || b, array[c, d]
+$$ language sql;
+select x, pg_typeof(x), y, pg_typeof(y)
+ from polyf(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 polyf(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 polyf(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 polyf(11, array[1, 2.2], 42, 34.5); -- fail
+ERROR: function polyf(integer, numeric[], integer, numeric) does not exist
+LINE 2: from polyf(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 polyf(a anyelement, b anyarray,
+ c anycompatible, d anycompatible);
+create function polyf(anyrange) returns anymultirange
+as 'select multirange($1);' language sql;
+select polyf(int4range(1,10));
+ polyf
+----------
+ {[1,10)}
+(1 row)
+
+select polyf(null);
+ERROR: could not determine polymorphic type because input has type unknown
+drop function polyf(anyrange);
+create function polyf(anymultirange) returns anyelement
+as 'select lower($1);' language sql;
+select polyf(int4multirange(int4range(1,10), int4range(20,30)));
+ polyf
+-------
+ 1
+(1 row)
+
+select polyf(null);
+ERROR: could not determine polymorphic type because input has type unknown
+drop function polyf(anymultirange);
+create function polyf(anycompatiblerange) returns anycompatiblemultirange
+as 'select multirange($1);' language sql;
+select polyf(int4range(1,10));
+ polyf
+----------
+ {[1,10)}
+(1 row)
+
+select polyf(null);
+ERROR: could not determine polymorphic type anycompatiblerange because input has type unknown
+drop function polyf(anycompatiblerange);
+create function polyf(anymultirange) returns anyrange
+as 'select range_merge($1);' language sql;
+select polyf(int4multirange(int4range(1,10), int4range(20,30)));
+ polyf
+--------
+ [1,30)
+(1 row)
+
+select polyf(null);
+ERROR: could not determine polymorphic type because input has type unknown
+drop function polyf(anymultirange);
+create function polyf(anycompatiblemultirange) returns anycompatiblerange
+as 'select range_merge($1);' language sql;
+select polyf(int4multirange(int4range(1,10), int4range(20,30)));
+ polyf
+--------
+ [1,30)
+(1 row)
+
+select polyf(null);
+ERROR: could not determine polymorphic type anycompatiblerange because input has type unknown
+drop function polyf(anycompatiblemultirange);
+create function polyf(anycompatiblemultirange) returns anycompatible
+as 'select lower($1);' language sql;
+select polyf(int4multirange(int4range(1,10), int4range(20,30)));
+ polyf
+-------
+ 1
+(1 row)
+
+select polyf(null);
+ERROR: could not determine polymorphic type anycompatiblemultirange because input has type unknown
+drop function polyf(anycompatiblemultirange);
+--
+-- Polymorphic aggregate tests
+--
+-- Legend:
+-----------
+-- A = type is ANY
+-- P = type is polymorphic
+-- N = type is non-polymorphic
+-- B = aggregate base type
+-- S = aggregate state type
+-- R = aggregate return type
+-- 1 = arg1 of a function
+-- 2 = arg2 of a function
+-- ag = aggregate
+-- tf = trans (state) function
+-- ff = final function
+-- rt = return type of a function
+-- -> = implies
+-- => = allowed
+-- !> = not allowed
+-- E = exists
+-- NE = not-exists
+--
+-- Possible states:
+-- ----------------
+-- B = (A || P || N)
+-- when (B = A) -> (tf2 = NE)
+-- S = (P || N)
+-- ff = (E || NE)
+-- tf1 = (P || N)
+-- tf2 = (NE || P || N)
+-- R = (P || N)
+-- create functions for use as tf and ff with the needed combinations of
+-- argument polymorphism, but within the constraints of valid aggregate
+-- functions, i.e. tf arg1 and tf return type must match
+-- polymorphic single arg transfn
+CREATE FUNCTION stfp(anyarray) RETURNS anyarray AS
+'select $1' LANGUAGE SQL;
+-- non-polymorphic single arg transfn
+CREATE FUNCTION stfnp(int[]) RETURNS int[] AS
+'select $1' LANGUAGE SQL;
+-- dual polymorphic transfn
+CREATE FUNCTION tfp(anyarray,anyelement) RETURNS anyarray AS
+'select $1 || $2' LANGUAGE SQL;
+-- dual non-polymorphic transfn
+CREATE FUNCTION tfnp(int[],int) RETURNS int[] AS
+'select $1 || $2' LANGUAGE SQL;
+-- arg1 only polymorphic transfn
+CREATE FUNCTION tf1p(anyarray,int) RETURNS anyarray AS
+'select $1' LANGUAGE SQL;
+-- arg2 only polymorphic transfn
+CREATE FUNCTION tf2p(int[],anyelement) RETURNS int[] AS
+'select $1' LANGUAGE SQL;
+-- multi-arg polymorphic
+CREATE FUNCTION sum3(anyelement,anyelement,anyelement) returns anyelement AS
+'select $1+$2+$3' language sql strict;
+-- finalfn polymorphic
+CREATE FUNCTION ffp(anyarray) RETURNS anyarray AS
+'select $1' LANGUAGE SQL;
+-- finalfn non-polymorphic
+CREATE FUNCTION ffnp(int[]) returns int[] as
+'select $1' LANGUAGE SQL;
+-- Try to cover all the possible states:
+--
+-- Note: in Cases 1 & 2, we are trying to return P. Therefore, if the transfn
+-- is stfnp, tfnp, or tf2p, we must use ffp as finalfn, because stfnp, tfnp,
+-- and tf2p do not return P. Conversely, in Cases 3 & 4, we are trying to
+-- return N. Therefore, if the transfn is stfp, tfp, or tf1p, we must use ffnp
+-- as finalfn, because stfp, tfp, and tf1p do not return N.
+--
+-- Case1 (R = P) && (B = A)
+-- ------------------------
+-- S tf1
+-- -------
+-- N N
+-- should CREATE
+CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
+ FINALFUNC = ffp, INITCOND = '{}');
+-- P N
+-- should ERROR: stfnp(anyarray) not matched by stfnp(int[])
+CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
+ FINALFUNC = ffp, INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+-- N P
+-- should CREATE
+CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
+ FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
+ INITCOND = '{}');
+-- P P
+-- should ERROR: we have no way to resolve S
+CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
+ FINALFUNC = ffp, INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
+ INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+-- Case2 (R = P) && ((B = P) || (B = N))
+-- -------------------------------------
+-- S tf1 B tf2
+-- -----------------------
+-- N N N N
+-- should CREATE
+CREATE AGGREGATE myaggp05a(BASETYPE = int, SFUNC = tfnp, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+-- N N N P
+-- should CREATE
+CREATE AGGREGATE myaggp06a(BASETYPE = int, SFUNC = tf2p, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+-- N N P N
+-- should ERROR: tfnp(int[], anyelement) not matched by tfnp(int[], int)
+CREATE AGGREGATE myaggp07a(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+ERROR: function tfnp(integer[], anyelement) does not exist
+-- N N P P
+-- should CREATE
+CREATE AGGREGATE myaggp08a(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+-- N P N N
+-- should CREATE
+CREATE AGGREGATE myaggp09a(BASETYPE = int, SFUNC = tf1p, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp09b(BASETYPE = int, SFUNC = tf1p, STYPE = int[],
+ INITCOND = '{}');
+-- N P N P
+-- should CREATE
+CREATE AGGREGATE myaggp10a(BASETYPE = int, SFUNC = tfp, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp10b(BASETYPE = int, SFUNC = tfp, STYPE = int[],
+ INITCOND = '{}');
+-- N P P N
+-- should ERROR: tf1p(int[],anyelement) not matched by tf1p(anyarray,int)
+CREATE AGGREGATE myaggp11a(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+ERROR: function tf1p(integer[], anyelement) does not exist
+CREATE AGGREGATE myaggp11b(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[],
+ INITCOND = '{}');
+ERROR: function tf1p(integer[], anyelement) does not exist
+-- N P P P
+-- should ERROR: tfp(int[],anyelement) not matched by tfp(anyarray,anyelement)
+CREATE AGGREGATE myaggp12a(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+ERROR: function tfp(integer[], anyelement) does not exist
+CREATE AGGREGATE myaggp12b(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[],
+ INITCOND = '{}');
+ERROR: function tfp(integer[], anyelement) does not exist
+-- P N N N
+-- should ERROR: tfnp(anyarray, int) not matched by tfnp(int[],int)
+CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
+ FINALFUNC = ffp, INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+-- P N N P
+-- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
+CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
+ FINALFUNC = ffp, INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+-- P N P N
+-- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
+CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
+ STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}');
+ERROR: function tfnp(anyarray, anyelement) does not exist
+-- P N P P
+-- should ERROR: tf2p(anyarray, anyelement) not matched by tf2p(int[],anyelement)
+CREATE AGGREGATE myaggp16a(BASETYPE = anyelement, SFUNC = tf2p,
+ STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}');
+ERROR: function tf2p(anyarray, anyelement) does not exist
+-- P P N N
+-- should ERROR: we have no way to resolve S
+CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
+ FINALFUNC = ffp, INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
+ INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+-- P P N P
+-- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
+CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
+ FINALFUNC = ffp, INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
+ INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+-- P P P N
+-- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
+CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
+ STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}');
+ERROR: function tf1p(anyarray, anyelement) does not exist
+CREATE AGGREGATE myaggp19b(BASETYPE = anyelement, SFUNC = tf1p,
+ STYPE = anyarray, INITCOND = '{}');
+ERROR: function tf1p(anyarray, anyelement) does not exist
+-- P P P P
+-- should CREATE
+CREATE AGGREGATE myaggp20a(BASETYPE = anyelement, SFUNC = tfp,
+ STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp20b(BASETYPE = anyelement, SFUNC = tfp,
+ STYPE = anyarray, INITCOND = '{}');
+-- Case3 (R = N) && (B = A)
+-- ------------------------
+-- S tf1
+-- -------
+-- N N
+-- should CREATE
+CREATE AGGREGATE myaggn01a(*) (SFUNC = stfnp, STYPE = int4[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
+ INITCOND = '{}');
+-- P N
+-- should ERROR: stfnp(anyarray) not matched by stfnp(int[])
+CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
+ FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
+ INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+-- N P
+-- should CREATE
+CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+-- P P
+-- should ERROR: ffnp(anyarray) not matched by ffnp(int[])
+CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
+ FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+-- Case4 (R = N) && ((B = P) || (B = N))
+-- -------------------------------------
+-- S tf1 B tf2
+-- -----------------------
+-- N N N N
+-- should CREATE
+CREATE AGGREGATE myaggn05a(BASETYPE = int, SFUNC = tfnp, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn05b(BASETYPE = int, SFUNC = tfnp, STYPE = int[],
+ INITCOND = '{}');
+-- N N N P
+-- should CREATE
+CREATE AGGREGATE myaggn06a(BASETYPE = int, SFUNC = tf2p, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn06b(BASETYPE = int, SFUNC = tf2p, STYPE = int[],
+ INITCOND = '{}');
+-- N N P N
+-- should ERROR: tfnp(int[], anyelement) not matched by tfnp(int[], int)
+CREATE AGGREGATE myaggn07a(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: function tfnp(integer[], anyelement) does not exist
+CREATE AGGREGATE myaggn07b(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[],
+ INITCOND = '{}');
+ERROR: function tfnp(integer[], anyelement) does not exist
+-- N N P P
+-- should CREATE
+CREATE AGGREGATE myaggn08a(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn08b(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[],
+ INITCOND = '{}');
+-- N P N N
+-- should CREATE
+CREATE AGGREGATE myaggn09a(BASETYPE = int, SFUNC = tf1p, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+-- N P N P
+-- should CREATE
+CREATE AGGREGATE myaggn10a(BASETYPE = int, SFUNC = tfp, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+-- N P P N
+-- should ERROR: tf1p(int[],anyelement) not matched by tf1p(anyarray,int)
+CREATE AGGREGATE myaggn11a(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: function tf1p(integer[], anyelement) does not exist
+-- N P P P
+-- should ERROR: tfp(int[],anyelement) not matched by tfp(anyarray,anyelement)
+CREATE AGGREGATE myaggn12a(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: function tfp(integer[], anyelement) does not exist
+-- P N N N
+-- should ERROR: tfnp(anyarray, int) not matched by tfnp(int[],int)
+CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
+ FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
+ INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+-- P N N P
+-- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
+CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
+ FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
+ INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+-- P N P N
+-- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
+CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
+ STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: function tfnp(anyarray, anyelement) does not exist
+CREATE AGGREGATE myaggn15b(BASETYPE = anyelement, SFUNC = tfnp,
+ STYPE = anyarray, INITCOND = '{}');
+ERROR: function tfnp(anyarray, anyelement) does not exist
+-- P N P P
+-- should ERROR: tf2p(anyarray, anyelement) not matched by tf2p(int[],anyelement)
+CREATE AGGREGATE myaggn16a(BASETYPE = anyelement, SFUNC = tf2p,
+ STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: function tf2p(anyarray, anyelement) does not exist
+CREATE AGGREGATE myaggn16b(BASETYPE = anyelement, SFUNC = tf2p,
+ STYPE = anyarray, INITCOND = '{}');
+ERROR: function tf2p(anyarray, anyelement) does not exist
+-- P P N N
+-- should ERROR: ffnp(anyarray) not matched by ffnp(int[])
+CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
+ FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+-- P P N P
+-- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
+CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
+ FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: cannot determine transition data type
+DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+-- P P P N
+-- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
+CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
+ STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: function tf1p(anyarray, anyelement) does not exist
+-- P P P P
+-- should ERROR: ffnp(anyarray) not matched by ffnp(int[])
+CREATE AGGREGATE myaggn20a(BASETYPE = anyelement, SFUNC = tfp,
+ STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
+ERROR: function ffnp(anyarray) does not exist
+-- multi-arg polymorphic
+CREATE AGGREGATE mysum2(anyelement,anyelement) (SFUNC = sum3,
+ STYPE = anyelement, INITCOND = '0');
+-- create test data for polymorphic aggregates
+create temp table t(f1 int, f2 int[], f3 text);
+insert into t values(1,array[1],'a');
+insert into t values(1,array[11],'b');
+insert into t values(1,array[111],'c');
+insert into t values(2,array[2],'a');
+insert into t values(2,array[22],'b');
+insert into t values(2,array[222],'c');
+insert into t values(3,array[3],'a');
+insert into t values(3,array[3],'b');
+-- test the successfully created polymorphic aggregates
+select f3, myaggp01a(*) from t group by f3 order by f3;
+ f3 | myaggp01a
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggp03a(*) from t group by f3 order by f3;
+ f3 | myaggp03a
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggp03b(*) from t group by f3 order by f3;
+ f3 | myaggp03b
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggp05a(f1) from t group by f3 order by f3;
+ f3 | myaggp05a
+----+-----------
+ a | {1,2,3}
+ b | {1,2,3}
+ c | {1,2}
+(3 rows)
+
+select f3, myaggp06a(f1) from t group by f3 order by f3;
+ f3 | myaggp06a
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggp08a(f1) from t group by f3 order by f3;
+ f3 | myaggp08a
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggp09a(f1) from t group by f3 order by f3;
+ f3 | myaggp09a
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggp09b(f1) from t group by f3 order by f3;
+ f3 | myaggp09b
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggp10a(f1) from t group by f3 order by f3;
+ f3 | myaggp10a
+----+-----------
+ a | {1,2,3}
+ b | {1,2,3}
+ c | {1,2}
+(3 rows)
+
+select f3, myaggp10b(f1) from t group by f3 order by f3;
+ f3 | myaggp10b
+----+-----------
+ a | {1,2,3}
+ b | {1,2,3}
+ c | {1,2}
+(3 rows)
+
+select f3, myaggp20a(f1) from t group by f3 order by f3;
+ f3 | myaggp20a
+----+-----------
+ a | {1,2,3}
+ b | {1,2,3}
+ c | {1,2}
+(3 rows)
+
+select f3, myaggp20b(f1) from t group by f3 order by f3;
+ f3 | myaggp20b
+----+-----------
+ a | {1,2,3}
+ b | {1,2,3}
+ c | {1,2}
+(3 rows)
+
+select f3, myaggn01a(*) from t group by f3 order by f3;
+ f3 | myaggn01a
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggn01b(*) from t group by f3 order by f3;
+ f3 | myaggn01b
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggn03a(*) from t group by f3 order by f3;
+ f3 | myaggn03a
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggn05a(f1) from t group by f3 order by f3;
+ f3 | myaggn05a
+----+-----------
+ a | {1,2,3}
+ b | {1,2,3}
+ c | {1,2}
+(3 rows)
+
+select f3, myaggn05b(f1) from t group by f3 order by f3;
+ f3 | myaggn05b
+----+-----------
+ a | {1,2,3}
+ b | {1,2,3}
+ c | {1,2}
+(3 rows)
+
+select f3, myaggn06a(f1) from t group by f3 order by f3;
+ f3 | myaggn06a
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggn06b(f1) from t group by f3 order by f3;
+ f3 | myaggn06b
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggn08a(f1) from t group by f3 order by f3;
+ f3 | myaggn08a
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggn08b(f1) from t group by f3 order by f3;
+ f3 | myaggn08b
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggn09a(f1) from t group by f3 order by f3;
+ f3 | myaggn09a
+----+-----------
+ a | {}
+ b | {}
+ c | {}
+(3 rows)
+
+select f3, myaggn10a(f1) from t group by f3 order by f3;
+ f3 | myaggn10a
+----+-----------
+ a | {1,2,3}
+ b | {1,2,3}
+ c | {1,2}
+(3 rows)
+
+select mysum2(f1, f1 + 1) from t;
+ mysum2
+--------
+ 38
+(1 row)
+
+-- test inlining of polymorphic SQL functions
+create function bleat(int) returns int as $$
+begin
+ raise notice 'bleat %', $1;
+ return $1;
+end$$ language plpgsql;
+create function sql_if(bool, anyelement, anyelement) returns anyelement as $$
+select case when $1 then $2 else $3 end $$ language sql;
+-- Note this would fail with integer overflow, never mind wrong bleat() output,
+-- if the CASE expression were not successfully inlined
+select f1, sql_if(f1 > 0, bleat(f1), bleat(f1 + 1)) from int4_tbl;
+NOTICE: bleat 1
+NOTICE: bleat 123456
+NOTICE: bleat -123455
+NOTICE: bleat 2147483647
+NOTICE: bleat -2147483646
+ f1 | sql_if
+-------------+-------------
+ 0 | 1
+ 123456 | 123456
+ -123456 | -123455
+ 2147483647 | 2147483647
+ -2147483647 | -2147483646
+(5 rows)
+
+select q2, sql_if(q2 > 0, q2, q2 + 1) from int8_tbl;
+ q2 | sql_if
+-------------------+-------------------
+ 456 | 456
+ 4567890123456789 | 4567890123456789
+ 123 | 123
+ 4567890123456789 | 4567890123456789
+ -4567890123456789 | -4567890123456788
+(5 rows)
+
+-- another sort of polymorphic aggregate
+CREATE AGGREGATE array_larger_accum (anyarray)
+(
+ sfunc = array_larger,
+ stype = anyarray,
+ initcond = '{}'
+);
+SELECT array_larger_accum(i)
+FROM (VALUES (ARRAY[1,2]), (ARRAY[3,4])) as t(i);
+ array_larger_accum
+--------------------
+ {3,4}
+(1 row)
+
+SELECT array_larger_accum(i)
+FROM (VALUES (ARRAY[row(1,2),row(3,4)]), (ARRAY[row(5,6),row(7,8)])) as t(i);
+ array_larger_accum
+--------------------
+ {"(5,6)","(7,8)"}
+(1 row)
+
+-- another kind of polymorphic aggregate
+create function add_group(grp anyarray, ad anyelement, size integer)
+ returns anyarray
+ as $$
+begin
+ if grp is null then
+ return array[ad];
+ end if;
+ if array_upper(grp, 1) < size then
+ return grp || ad;
+ end if;
+ return grp;
+end;
+$$
+ language plpgsql immutable;
+create aggregate build_group(anyelement, integer) (
+ SFUNC = add_group,
+ STYPE = anyarray
+);
+select build_group(q1,3) from int8_tbl;
+ build_group
+----------------------------
+ {123,123,4567890123456789}
+(1 row)
+
+-- this should fail because stype isn't compatible with arg
+create aggregate build_group(int8, integer) (
+ SFUNC = add_group,
+ STYPE = int2[]
+);
+ERROR: function add_group(smallint[], bigint, integer) does not exist
+-- but we can make a non-poly agg from a poly sfunc if types are OK
+create aggregate build_group(int8, integer) (
+ SFUNC = add_group,
+ STYPE = int8[]
+);
+-- check proper resolution of data types for polymorphic transfn/finalfn
+create function first_el_transfn(anyarray, anyelement) returns anyarray as
+'select $1 || $2' language sql immutable;
+create function first_el(anyarray) returns anyelement as
+'select $1[1]' language sql strict immutable;
+create aggregate first_el_agg_f8(float8) (
+ SFUNC = array_append,
+ STYPE = float8[],
+ FINALFUNC = first_el
+);
+create aggregate first_el_agg_any(anyelement) (
+ SFUNC = first_el_transfn,
+ STYPE = anyarray,
+ FINALFUNC = first_el
+);
+select first_el_agg_f8(x::float8) from generate_series(1,10) x;
+ first_el_agg_f8
+-----------------
+ 1
+(1 row)
+
+select first_el_agg_any(x) from generate_series(1,10) x;
+ first_el_agg_any
+------------------
+ 1
+(1 row)
+
+select first_el_agg_f8(x::float8) over(order by x) from generate_series(1,10) x;
+ first_el_agg_f8
+-----------------
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+(10 rows)
+
+select first_el_agg_any(x) over(order by x) from generate_series(1,10) x;
+ first_el_agg_any
+------------------
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+(10 rows)
+
+-- check that we can apply functions taking ANYARRAY to pg_stats
+select distinct array_ndims(histogram_bounds) from pg_stats
+where histogram_bounds is not null;
+ array_ndims
+-------------
+ 1
+(1 row)
+
+-- such functions must protect themselves if varying element type isn't OK
+-- (WHERE clause here is to avoid possibly getting a collation error instead)
+select max(histogram_bounds) from pg_stats where tablename = 'pg_am';
+ERROR: cannot compare arrays of different element types
+-- another corner case is the input functions for polymorphic pseudotypes
+select array_in('{1,2,3}','int4'::regtype,-1); -- this has historically worked
+ array_in
+----------
+ {1,2,3}
+(1 row)
+
+select * from array_in('{1,2,3}','int4'::regtype,-1); -- this not
+ERROR: function "array_in" in FROM has unsupported return type anyarray
+LINE 1: select * from array_in('{1,2,3}','int4'::regtype,-1);
+ ^
+select anyrange_in('[10,20)','int4range'::regtype,-1);
+ERROR: cannot accept a value of type anyrange
+-- test variadic polymorphic functions
+create function myleast(variadic anyarray) returns anyelement as $$
+ select min($1[i]) from generate_subscripts($1,1) g(i)
+$$ language sql immutable strict;
+select myleast(10, 1, 20, 33);
+ myleast
+---------
+ 1
+(1 row)
+
+select myleast(1.1, 0.22, 0.55);
+ myleast
+---------
+ 0.22
+(1 row)
+
+select myleast('z'::text);
+ myleast
+---------
+ z
+(1 row)
+
+select myleast(); -- fail
+ERROR: function myleast() does not exist
+LINE 1: select myleast();
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+-- test with variadic call parameter
+select myleast(variadic array[1,2,3,4,-1]);
+ myleast
+---------
+ -1
+(1 row)
+
+select myleast(variadic array[1.1, -5.5]);
+ myleast
+---------
+ -5.5
+(1 row)
+
+--test with empty variadic call parameter
+select myleast(variadic array[]::int[]);
+ myleast
+---------
+
+(1 row)
+
+-- an example with some ordinary arguments too
+create function concat(text, variadic anyarray) returns text as $$
+ select array_to_string($2, $1);
+$$ language sql immutable strict;
+select concat('%', 1, 2, 3, 4, 5);
+ concat
+-----------
+ 1%2%3%4%5
+(1 row)
+
+select concat('|', 'a'::text, 'b', 'c');
+ concat
+--------
+ a|b|c
+(1 row)
+
+select concat('|', variadic array[1,2,33]);
+ concat
+--------
+ 1|2|33
+(1 row)
+
+select concat('|', variadic array[]::int[]);
+ concat
+--------
+
+(1 row)
+
+drop function concat(text, anyarray);
+-- mix variadic with anyelement
+create function formarray(anyelement, variadic anyarray) returns anyarray as $$
+ select array_prepend($1, $2);
+$$ language sql immutable strict;
+select formarray(1,2,3,4,5);
+ formarray
+-------------
+ {1,2,3,4,5}
+(1 row)
+
+select formarray(1.1, variadic array[1.2,55.5]);
+ formarray
+----------------
+ {1.1,1.2,55.5}
+(1 row)
+
+select formarray(1.1, array[1.2,55.5]); -- fail without variadic
+ERROR: function formarray(numeric, numeric[]) does not exist
+LINE 1: select formarray(1.1, array[1.2,55.5]);
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select formarray(1, 'x'::text); -- fail, type mismatch
+ERROR: function formarray(integer, text) does not exist
+LINE 1: select formarray(1, 'x'::text);
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select formarray(1, variadic array['x'::text]); -- fail, type mismatch
+ERROR: function formarray(integer, text[]) does not exist
+LINE 1: select formarray(1, variadic array['x'::text]);
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+drop function formarray(anyelement, variadic anyarray);
+-- test pg_typeof() function
+select pg_typeof(null); -- unknown
+ pg_typeof
+-----------
+ unknown
+(1 row)
+
+select pg_typeof(0); -- integer
+ pg_typeof
+-----------
+ integer
+(1 row)
+
+select pg_typeof(0.0); -- numeric
+ pg_typeof
+-----------
+ numeric
+(1 row)
+
+select pg_typeof(1+1 = 2); -- boolean
+ pg_typeof
+-----------
+ boolean
+(1 row)
+
+select pg_typeof('x'); -- unknown
+ pg_typeof
+-----------
+ unknown
+(1 row)
+
+select pg_typeof('' || ''); -- text
+ pg_typeof
+-----------
+ text
+(1 row)
+
+select pg_typeof(pg_typeof(0)); -- regtype
+ pg_typeof
+-----------
+ regtype
+(1 row)
+
+select pg_typeof(array[1.2,55.5]); -- numeric[]
+ pg_typeof
+-----------
+ numeric[]
+(1 row)
+
+select pg_typeof(myleast(10, 1, 20, 33)); -- polymorphic input
+ pg_typeof
+-----------
+ integer
+(1 row)
+
+-- test functions with default parameters
+-- test basic functionality
+create function dfunc(a int = 1, int = 2) returns int as $$
+ select $1 + $2;
+$$ language sql;
+select dfunc();
+ dfunc
+-------
+ 3
+(1 row)
+
+select dfunc(10);
+ dfunc
+-------
+ 12
+(1 row)
+
+select dfunc(10, 20);
+ dfunc
+-------
+ 30
+(1 row)
+
+select dfunc(10, 20, 30); -- fail
+ERROR: function dfunc(integer, integer, integer) does not exist
+LINE 1: select dfunc(10, 20, 30);
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+drop function dfunc(); -- fail
+ERROR: function dfunc() does not exist
+drop function dfunc(int); -- fail
+ERROR: function dfunc(integer) does not exist
+drop function dfunc(int, int); -- ok
+-- fail: defaults must be at end of argument list
+create function dfunc(a int = 1, b int) returns int as $$
+ select $1 + $2;
+$$ language sql;
+ERROR: input parameters after one with a default value must also have defaults
+-- however, this should work:
+create function dfunc(a int = 1, out sum int, b int = 2) as $$
+ select $1 + $2;
+$$ language sql;
+select dfunc();
+ dfunc
+-------
+ 3
+(1 row)
+
+-- verify it lists properly
+\df dfunc
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-----------------------------------------------------------+------
+ public | dfunc | integer | a integer DEFAULT 1, OUT sum integer, b integer DEFAULT 2 | func
+(1 row)
+
+drop function dfunc(int, int);
+-- check implicit coercion
+create function dfunc(a int DEFAULT 1.0, int DEFAULT '-1') returns int as $$
+ select $1 + $2;
+$$ language sql;
+select dfunc();
+ dfunc
+-------
+ 0
+(1 row)
+
+create function dfunc(a text DEFAULT 'Hello', b text DEFAULT 'World') returns text as $$
+ select $1 || ', ' || $2;
+$$ language sql;
+select dfunc(); -- fail: which dfunc should be called? int or text
+ERROR: function dfunc() is not unique
+LINE 1: select dfunc();
+ ^
+HINT: Could not choose a best candidate function. You might need to add explicit type casts.
+select dfunc('Hi'); -- ok
+ dfunc
+-----------
+ Hi, World
+(1 row)
+
+select dfunc('Hi', 'City'); -- ok
+ dfunc
+----------
+ Hi, City
+(1 row)
+
+select dfunc(0); -- ok
+ dfunc
+-------
+ -1
+(1 row)
+
+select dfunc(10, 20); -- ok
+ dfunc
+-------
+ 30
+(1 row)
+
+drop function dfunc(int, int);
+drop function dfunc(text, text);
+create function dfunc(int = 1, int = 2) returns int as $$
+ select 2;
+$$ language sql;
+create function dfunc(int = 1, int = 2, int = 3, int = 4) returns int as $$
+ select 4;
+$$ language sql;
+-- Now, dfunc(nargs = 2) and dfunc(nargs = 4) are ambiguous when called
+-- with 0 to 2 arguments.
+select dfunc(); -- fail
+ERROR: function dfunc() is not unique
+LINE 1: select dfunc();
+ ^
+HINT: Could not choose a best candidate function. You might need to add explicit type casts.
+select dfunc(1); -- fail
+ERROR: function dfunc(integer) is not unique
+LINE 1: select dfunc(1);
+ ^
+HINT: Could not choose a best candidate function. You might need to add explicit type casts.
+select dfunc(1, 2); -- fail
+ERROR: function dfunc(integer, integer) is not unique
+LINE 1: select dfunc(1, 2);
+ ^
+HINT: Could not choose a best candidate function. You might need to add explicit type casts.
+select dfunc(1, 2, 3); -- ok
+ dfunc
+-------
+ 4
+(1 row)
+
+select dfunc(1, 2, 3, 4); -- ok
+ dfunc
+-------
+ 4
+(1 row)
+
+drop function dfunc(int, int);
+drop function dfunc(int, int, int, int);
+-- default values are not allowed for output parameters
+create function dfunc(out int = 20) returns int as $$
+ select 1;
+$$ language sql;
+ERROR: only input parameters can have default values
+-- polymorphic parameter test
+create function dfunc(anyelement = 'World'::text) returns text as $$
+ select 'Hello, ' || $1::text;
+$$ language sql;
+select dfunc();
+ dfunc
+--------------
+ Hello, World
+(1 row)
+
+select dfunc(0);
+ dfunc
+----------
+ Hello, 0
+(1 row)
+
+select dfunc(to_date('20081215','YYYYMMDD'));
+ dfunc
+-------------------
+ Hello, 12-15-2008
+(1 row)
+
+select dfunc('City'::text);
+ dfunc
+-------------
+ Hello, City
+(1 row)
+
+drop function dfunc(anyelement);
+-- check defaults for variadics
+create function dfunc(a variadic int[]) returns int as
+$$ select array_upper($1, 1) $$ language sql;
+select dfunc(); -- fail
+ERROR: function dfunc() does not exist
+LINE 1: select dfunc();
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select dfunc(10);
+ dfunc
+-------
+ 1
+(1 row)
+
+select dfunc(10,20);
+ dfunc
+-------
+ 2
+(1 row)
+
+create or replace function dfunc(a variadic int[] default array[]::int[]) returns int as
+$$ select array_upper($1, 1) $$ language sql;
+select dfunc(); -- now ok
+ dfunc
+-------
+
+(1 row)
+
+select dfunc(10);
+ dfunc
+-------
+ 1
+(1 row)
+
+select dfunc(10,20);
+ dfunc
+-------
+ 2
+(1 row)
+
+-- can't remove the default once it exists
+create or replace function dfunc(a variadic int[]) returns int as
+$$ select array_upper($1, 1) $$ language sql;
+ERROR: cannot remove parameter defaults from existing function
+HINT: Use DROP FUNCTION dfunc(integer[]) first.
+\df dfunc
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------+------
+ public | dfunc | integer | VARIADIC a integer[] DEFAULT ARRAY[]::integer[] | func
+(1 row)
+
+drop function dfunc(a variadic int[]);
+-- Ambiguity should be reported only if there's not a better match available
+create function dfunc(int = 1, int = 2, int = 3) returns int as $$
+ select 3;
+$$ language sql;
+create function dfunc(int = 1, int = 2) returns int as $$
+ select 2;
+$$ language sql;
+create function dfunc(text) returns text as $$
+ select $1;
+$$ language sql;
+-- dfunc(narg=2) and dfunc(narg=3) are ambiguous
+select dfunc(1); -- fail
+ERROR: function dfunc(integer) is not unique
+LINE 1: select dfunc(1);
+ ^
+HINT: Could not choose a best candidate function. You might need to add explicit type casts.
+-- but this works since the ambiguous functions aren't preferred anyway
+select dfunc('Hi');
+ dfunc
+-------
+ Hi
+(1 row)
+
+drop function dfunc(int, int, int);
+drop function dfunc(int, int);
+drop function dfunc(text);
+--
+-- Tests for named- and mixed-notation function calling
+--
+create function dfunc(a int, b int, c int = 0, d int = 0)
+ returns table (a int, b int, c int, d int) as $$
+ select $1, $2, $3, $4;
+$$ language sql;
+select (dfunc(10,20,30)).*;
+ a | b | c | d
+----+----+----+---
+ 10 | 20 | 30 | 0
+(1 row)
+
+select (dfunc(a := 10, b := 20, c := 30)).*;
+ a | b | c | d
+----+----+----+---
+ 10 | 20 | 30 | 0
+(1 row)
+
+select * from dfunc(a := 10, b := 20);
+ a | b | c | d
+----+----+---+---
+ 10 | 20 | 0 | 0
+(1 row)
+
+select * from dfunc(b := 10, a := 20);
+ a | b | c | d
+----+----+---+---
+ 20 | 10 | 0 | 0
+(1 row)
+
+select * from dfunc(0); -- fail
+ERROR: function dfunc(integer) does not exist
+LINE 1: select * from dfunc(0);
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select * from dfunc(1,2);
+ a | b | c | d
+---+---+---+---
+ 1 | 2 | 0 | 0
+(1 row)
+
+select * from dfunc(1,2,c := 3);
+ a | b | c | d
+---+---+---+---
+ 1 | 2 | 3 | 0
+(1 row)
+
+select * from dfunc(1,2,d := 3);
+ a | b | c | d
+---+---+---+---
+ 1 | 2 | 0 | 3
+(1 row)
+
+select * from dfunc(x := 20, b := 10, x := 30); -- fail, duplicate name
+ERROR: argument name "x" used more than once
+LINE 1: select * from dfunc(x := 20, b := 10, x := 30);
+ ^
+select * from dfunc(10, b := 20, 30); -- fail, named args must be last
+ERROR: positional argument cannot follow named argument
+LINE 1: select * from dfunc(10, b := 20, 30);
+ ^
+select * from dfunc(x := 10, b := 20, c := 30); -- fail, unknown param
+ERROR: function dfunc(x => integer, b => integer, c => integer) does not exist
+LINE 1: select * from dfunc(x := 10, b := 20, c := 30);
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select * from dfunc(10, 10, a := 20); -- fail, a overlaps positional parameter
+ERROR: function dfunc(integer, integer, a => integer) does not exist
+LINE 1: select * from dfunc(10, 10, a := 20);
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select * from dfunc(1,c := 2,d := 3); -- fail, no value for b
+ERROR: function dfunc(integer, c => integer, d => integer) does not exist
+LINE 1: select * from dfunc(1,c := 2,d := 3);
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+drop function dfunc(int, int, int, int);
+-- test with different parameter types
+create function dfunc(a varchar, b numeric, c date = current_date)
+ returns table (a varchar, b numeric, c date) as $$
+ select $1, $2, $3;
+$$ language sql;
+select (dfunc('Hello World', 20, '2009-07-25'::date)).*;
+ a | b | c
+-------------+----+------------
+ Hello World | 20 | 07-25-2009
+(1 row)
+
+select * from dfunc('Hello World', 20, '2009-07-25'::date);
+ a | b | c
+-------------+----+------------
+ Hello World | 20 | 07-25-2009
+(1 row)
+
+select * from dfunc(c := '2009-07-25'::date, a := 'Hello World', b := 20);
+ a | b | c
+-------------+----+------------
+ Hello World | 20 | 07-25-2009
+(1 row)
+
+select * from dfunc('Hello World', b := 20, c := '2009-07-25'::date);
+ a | b | c
+-------------+----+------------
+ Hello World | 20 | 07-25-2009
+(1 row)
+
+select * from dfunc('Hello World', c := '2009-07-25'::date, b := 20);
+ a | b | c
+-------------+----+------------
+ Hello World | 20 | 07-25-2009
+(1 row)
+
+select * from dfunc('Hello World', c := 20, b := '2009-07-25'::date); -- fail
+ERROR: function dfunc(unknown, c => integer, b => date) does not exist
+LINE 1: select * from dfunc('Hello World', c := 20, b := '2009-07-25...
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+drop function dfunc(varchar, numeric, date);
+-- test out parameters with named params
+create function dfunc(a varchar = 'def a', out _a varchar, c numeric = NULL, out _c numeric)
+returns record as $$
+ select $1, $2;
+$$ language sql;
+select (dfunc()).*;
+ _a | _c
+-------+----
+ def a |
+(1 row)
+
+select * from dfunc();
+ _a | _c
+-------+----
+ def a |
+(1 row)
+
+select * from dfunc('Hello', 100);
+ _a | _c
+-------+-----
+ Hello | 100
+(1 row)
+
+select * from dfunc(a := 'Hello', c := 100);
+ _a | _c
+-------+-----
+ Hello | 100
+(1 row)
+
+select * from dfunc(c := 100, a := 'Hello');
+ _a | _c
+-------+-----
+ Hello | 100
+(1 row)
+
+select * from dfunc('Hello');
+ _a | _c
+-------+----
+ Hello |
+(1 row)
+
+select * from dfunc('Hello', c := 100);
+ _a | _c
+-------+-----
+ Hello | 100
+(1 row)
+
+select * from dfunc(c := 100);
+ _a | _c
+-------+-----
+ def a | 100
+(1 row)
+
+-- fail, can no longer change an input parameter's name
+create or replace function dfunc(a varchar = 'def a', out _a varchar, x numeric = NULL, out _c numeric)
+returns record as $$
+ select $1, $2;
+$$ language sql;
+ERROR: cannot change name of input parameter "c"
+HINT: Use DROP FUNCTION dfunc(character varying,numeric) first.
+create or replace function dfunc(a varchar = 'def a', out _a varchar, numeric = NULL, out _c numeric)
+returns record as $$
+ select $1, $2;
+$$ language sql;
+ERROR: cannot change name of input parameter "c"
+HINT: Use DROP FUNCTION dfunc(character varying,numeric) first.
+drop function dfunc(varchar, numeric);
+--fail, named parameters are not unique
+create function testpolym(a int, a int) returns int as $$ select 1;$$ language sql;
+ERROR: parameter name "a" used more than once
+create function testpolym(int, out a int, out a int) returns int as $$ select 1;$$ language sql;
+ERROR: parameter name "a" used more than once
+create function testpolym(out a int, inout a int) returns int as $$ select 1;$$ language sql;
+ERROR: parameter name "a" used more than once
+create function testpolym(a int, inout a int) returns int as $$ select 1;$$ language sql;
+ERROR: parameter name "a" used more than once
+-- valid
+create function testpolym(a int, out a int) returns int as $$ select $1;$$ language sql;
+select testpolym(37);
+ testpolym
+-----------
+ 37
+(1 row)
+
+drop function testpolym(int);
+create function testpolym(a int) returns table(a int) as $$ select $1;$$ language sql;
+select * from testpolym(37);
+ a
+----
+ 37
+(1 row)
+
+drop function testpolym(int);
+-- test polymorphic params and defaults
+create function dfunc(a anyelement, b anyelement = null, flag bool = true)
+returns anyelement as $$
+ select case when $3 then $1 else $2 end;
+$$ language sql;
+select dfunc(1,2);
+ dfunc
+-------
+ 1
+(1 row)
+
+select dfunc('a'::text, 'b'); -- positional notation with default
+ dfunc
+-------
+ a
+(1 row)
+
+select dfunc(a := 1, b := 2);
+ dfunc
+-------
+ 1
+(1 row)
+
+select dfunc(a := 'a'::text, b := 'b');
+ dfunc
+-------
+ a
+(1 row)
+
+select dfunc(a := 'a'::text, b := 'b', flag := false); -- named notation
+ dfunc
+-------
+ b
+(1 row)
+
+select dfunc(b := 'b'::text, a := 'a'); -- named notation with default
+ dfunc
+-------
+ a
+(1 row)
+
+select dfunc(a := 'a'::text, flag := true); -- named notation with default
+ dfunc
+-------
+ a
+(1 row)
+
+select dfunc(a := 'a'::text, flag := false); -- named notation with default
+ dfunc
+-------
+
+(1 row)
+
+select dfunc(b := 'b'::text, a := 'a', flag := true); -- named notation
+ dfunc
+-------
+ a
+(1 row)
+
+select dfunc('a'::text, 'b', false); -- full positional notation
+ dfunc
+-------
+ b
+(1 row)
+
+select dfunc('a'::text, 'b', flag := false); -- mixed notation
+ dfunc
+-------
+ b
+(1 row)
+
+select dfunc('a'::text, 'b', true); -- full positional notation
+ dfunc
+-------
+ a
+(1 row)
+
+select dfunc('a'::text, 'b', flag := true); -- mixed notation
+ dfunc
+-------
+ a
+(1 row)
+
+-- ansi/sql syntax
+select dfunc(a => 1, b => 2);
+ dfunc
+-------
+ 1
+(1 row)
+
+select dfunc(a => 'a'::text, b => 'b');
+ dfunc
+-------
+ a
+(1 row)
+
+select dfunc(a => 'a'::text, b => 'b', flag => false); -- named notation
+ dfunc
+-------
+ b
+(1 row)
+
+select dfunc(b => 'b'::text, a => 'a'); -- named notation with default
+ dfunc
+-------
+ a
+(1 row)
+
+select dfunc(a => 'a'::text, flag => true); -- named notation with default
+ dfunc
+-------
+ a
+(1 row)
+
+select dfunc(a => 'a'::text, flag => false); -- named notation with default
+ dfunc
+-------
+
+(1 row)
+
+select dfunc(b => 'b'::text, a => 'a', flag => true); -- named notation
+ dfunc
+-------
+ a
+(1 row)
+
+select dfunc('a'::text, 'b', false); -- full positional notation
+ dfunc
+-------
+ b
+(1 row)
+
+select dfunc('a'::text, 'b', flag => false); -- mixed notation
+ dfunc
+-------
+ b
+(1 row)
+
+select dfunc('a'::text, 'b', true); -- full positional notation
+ dfunc
+-------
+ a
+(1 row)
+
+select dfunc('a'::text, 'b', flag => true); -- mixed notation
+ dfunc
+-------
+ a
+(1 row)
+
+-- this tests lexer edge cases around =>
+select dfunc(a =>-1);
+ dfunc
+-------
+ -1
+(1 row)
+
+select dfunc(a =>+1);
+ dfunc
+-------
+ 1
+(1 row)
+
+select dfunc(a =>/**/1);
+ dfunc
+-------
+ 1
+(1 row)
+
+select dfunc(a =>--comment to be removed by psql
+ 1);
+ dfunc
+-------
+ 1
+(1 row)
+
+-- need DO to protect the -- from psql
+do $$
+ declare r integer;
+ begin
+ select dfunc(a=>-- comment
+ 1) into r;
+ raise info 'r = %', r;
+ end;
+$$;
+INFO: r = 1
+-- check reverse-listing of named-arg calls
+CREATE VIEW dfview AS
+ SELECT q1, q2,
+ dfunc(q1,q2, flag := q1>q2) as c3,
+ dfunc(q1, flag := q1<q2, b := q2) as c4
+ FROM int8_tbl;
+select * from dfview;
+ q1 | q2 | c3 | c4
+------------------+-------------------+------------------+-------------------
+ 123 | 456 | 456 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | 4567890123456789 | -4567890123456789
+(5 rows)
+
+\d+ dfview
+ View "public.dfview"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+--------+-----------+----------+---------+---------+-------------
+ q1 | bigint | | | | plain |
+ q2 | bigint | | | | plain |
+ c3 | bigint | | | | plain |
+ c4 | bigint | | | | plain |
+View definition:
+ SELECT int8_tbl.q1,
+ int8_tbl.q2,
+ dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
+ dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ FROM int8_tbl;
+
+drop view dfview;
+drop function dfunc(anyelement, anyelement, bool);
+--
+-- Tests for ANYCOMPATIBLE polymorphism family
+--
+create function anyctest(anycompatible, anycompatible)
+returns anycompatible as $$
+ select greatest($1, $2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, 12) x;
+ x | pg_typeof
+----+-----------
+ 12 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12.3) x;
+ x | pg_typeof
+------+-----------
+ 12.3 | numeric
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, point(1,2)) x; -- fail
+ERROR: function anyctest(integer, point) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, point(1,2)) x;
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest('11', '12.3') x; -- defaults to text
+ x | pg_typeof
+------+-----------
+ 12.3 | text
+(1 row)
+
+drop function anyctest(anycompatible, anycompatible);
+create function anyctest(anycompatible, anycompatible)
+returns anycompatiblearray as $$
+ select array[$1, $2]
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, 12) x;
+ x | pg_typeof
+---------+-----------
+ {11,12} | integer[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12.3) x;
+ x | pg_typeof
+-----------+-----------
+ {11,12.3} | numeric[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, array[1,2]) x; -- fail
+ERROR: function anyctest(integer, integer[]) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, array[1,2]) x;
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatible, anycompatible);
+create function anyctest(anycompatible, anycompatiblearray)
+returns anycompatiblearray as $$
+ select array[$1] || $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, array[12]) x;
+ x | pg_typeof
+---------+-----------
+ {11,12} | integer[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, array[12.3]) x;
+ x | pg_typeof
+-----------+-----------
+ {11,12.3} | numeric[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(12.3, array[13]) x;
+ x | pg_typeof
+-----------+-----------
+ {12.3,13} | numeric[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(12.3, '{13,14.4}') x;
+ x | pg_typeof
+----------------+-----------
+ {12.3,13,14.4} | numeric[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, array[point(1,2)]) x; -- fail
+ERROR: function anyctest(integer, point[]) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, array[point(1,2)]) ...
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11, 12) x; -- fail
+ERROR: function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatible, anycompatiblearray);
+create function anyctest(anycompatible, anycompatiblerange)
+returns anycompatiblerange as $$
+ select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, int4range(4,7)) x;
+ x | pg_typeof
+-------+-----------
+ [4,7) | int4range
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, numrange(4,7)) x;
+ x | pg_typeof
+-------+-----------
+ [4,7) | numrange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x; -- fail
+ERROR: function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, int4range(4,7)) x; -- fail
+ERROR: function anyctest(numeric, int4range) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, int4range(4,7)) x...
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '[4,7)') x; -- fail
+ERROR: could not determine polymorphic type anycompatiblerange because input has type unknown
+drop function anyctest(anycompatible, anycompatiblerange);
+create function anyctest(anycompatiblerange, anycompatiblerange)
+returns anycompatible as $$
+ select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(int4range(11,12), int4range(4,7)) x;
+ x | pg_typeof
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(int4range(11,12), numrange(4,7)) x; -- fail
+ERROR: function anyctest(int4range, numrange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(int4range(11,12), numra...
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblerange, anycompatiblerange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblerange as $$
+ select $1
+$$ language sql;
+ERROR: cannot determine result data type
+DETAIL: A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+ select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+ x | pg_typeof
+---------+----------------
+ {[4,7)} | int4multirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+ x | pg_typeof
+---------+---------------
+ {[4,7)} | nummultirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x; -- fail
+ERROR: function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x; -- fail
+ERROR: function anyctest(numeric, int4multirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x; -- fail
+ERROR: could not determine polymorphic type anycompatiblemultirange because input has type unknown
+drop function anyctest(anycompatible, anycompatiblemultirange);
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+ select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+ x | pg_typeof
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+ERROR: function anyctest(int4multirange, nummultirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+ select $1
+$$ language sql;
+ERROR: cannot determine result data type
+DETAIL: A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
+returns anycompatiblearray as $$
+ select array[$1, $2]
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, 12) x;
+ x | pg_typeof
+---------+-----------
+ {11,12} | integer[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12.3) x;
+ x | pg_typeof
+-----------+-----------
+ {11,12.3} | numeric[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(array[11], array[1,2]) x; -- fail
+ERROR: function anyctest(integer[], integer[]) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(array[11], array[1,2]) ...
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblenonarray, anycompatiblenonarray);
+create function anyctest(a anyelement, b anyarray,
+ c anycompatible, d anycompatible)
+returns anycompatiblearray as $$
+ select array[c, d]
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, array[1, 2], 42, 34.5) x;
+ x | pg_typeof
+-----------+-----------
+ {42,34.5} | numeric[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, array[1, 2], point(1,2), point(3,4)) x;
+ x | pg_typeof
+-------------------+-----------
+ {"(1,2)","(3,4)"} | point[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, '{1,2}', point(1,2), '(3,4)') x;
+ x | pg_typeof
+-------------------+-----------
+ {"(1,2)","(3,4)"} | point[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, array[1, 2.2], 42, 34.5) x; -- fail
+ERROR: function anyctest(integer, numeric[], integer, numeric) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, array[1, 2.2], 42, ...
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(a anyelement, b anyarray,
+ c anycompatible, d anycompatible);
+create function anyctest(variadic anycompatiblearray)
+returns anycompatiblearray as $$
+ select $1
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, 12) x;
+ x | pg_typeof
+---------+-----------
+ {11,12} | integer[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12.2) x;
+ x | pg_typeof
+-----------+-----------
+ {11,12.2} | numeric[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, '12') x;
+ x | pg_typeof
+---------+-----------
+ {11,12} | integer[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, '12.2') x; -- fail
+ERROR: invalid input syntax for type integer: "12.2"
+LINE 1: select x, pg_typeof(x) from anyctest(11, '12.2') x;
+ ^
+select x, pg_typeof(x) from anyctest(variadic array[11, 12]) x;
+ x | pg_typeof
+---------+-----------
+ {11,12} | integer[]
+(1 row)
+
+select x, pg_typeof(x) from anyctest(variadic array[11, 12.2]) x;
+ x | pg_typeof
+-----------+-----------
+ {11,12.2} | numeric[]
+(1 row)
+
+drop function anyctest(variadic anycompatiblearray);
diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out
new file mode 100644
index 0000000..bc52317
--- /dev/null
+++ b/src/test/regress/expected/portals.out
@@ -0,0 +1,1520 @@
+--
+-- Cursor regression tests
+--
+BEGIN;
+DECLARE foo1 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+DECLARE foo2 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo3 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+DECLARE foo4 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo5 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+DECLARE foo6 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo7 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+DECLARE foo8 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo9 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+DECLARE foo10 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo11 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+DECLARE foo12 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo13 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+DECLARE foo14 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo15 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+DECLARE foo16 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo17 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+DECLARE foo18 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo19 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+DECLARE foo20 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo21 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+DECLARE foo22 SCROLL CURSOR FOR SELECT * FROM tenk2;
+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 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 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;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 59 | 21 | 1 | 3 | 9 | 19 | 59 | 59 | 59 | 59 | 59 | 118 | 119 | HCAAAA | VAAAAA | HHHHxx
+(1 row)
+
+FETCH backward 2 in foo22;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 9460 | 20 | 0 | 0 | 0 | 0 | 60 | 460 | 1460 | 4460 | 9460 | 120 | 121 | WZAAAA | UAAAAA | AAAAxx
+ 6969 | 19 | 1 | 1 | 9 | 9 | 69 | 969 | 969 | 1969 | 6969 | 138 | 139 | BIAAAA | TAAAAA | VVVVxx
+(2 rows)
+
+FETCH backward 3 in foo21;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 6969 | 19 | 1 | 1 | 9 | 9 | 69 | 969 | 969 | 1969 | 6969 | 138 | 139 | BIAAAA | TAAAAA | VVVVxx
+ 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx
+ 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx
+(3 rows)
+
+FETCH backward 4 in foo20;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx
+ 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx
+ 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx
+ 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx
+(4 rows)
+
+FETCH backward 5 in foo19;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx
+ 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx
+ 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx
+ 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx
+ 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx
+(5 rows)
+
+FETCH backward 6 in foo18;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx
+ 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx
+ 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx
+ 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx
+ 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx
+ 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx
+(6 rows)
+
+FETCH backward 7 in foo17;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx
+ 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx
+ 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx
+ 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx
+ 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx
+ 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx
+ 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx
+(7 rows)
+
+FETCH backward 8 in foo16;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx
+ 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx
+ 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx
+ 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx
+ 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx
+ 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx
+ 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx
+ 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx
+(8 rows)
+
+FETCH backward 9 in foo15;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx
+ 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx
+ 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx
+ 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx
+ 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx
+ 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx
+ 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx
+ 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx
+ 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx
+(9 rows)
+
+FETCH backward 10 in foo14;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx
+ 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx
+ 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx
+ 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx
+ 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx
+ 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx
+ 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx
+ 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx
+ 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx
+ 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx
+(10 rows)
+
+FETCH backward 11 in foo13;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx
+ 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx
+ 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx
+ 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx
+ 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx
+ 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx
+ 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx
+ 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx
+ 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx
+ 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx
+ 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx
+(11 rows)
+
+FETCH backward 12 in foo12;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx
+ 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx
+ 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx
+ 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx
+ 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx
+ 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx
+ 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx
+ 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx
+ 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx
+ 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx
+ 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx
+(11 rows)
+
+FETCH backward 13 in foo11;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx
+ 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx
+ 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx
+ 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx
+ 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx
+ 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx
+ 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx
+ 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx
+ 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx
+ 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx
+(10 rows)
+
+FETCH backward 14 in foo10;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx
+ 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx
+ 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx
+ 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx
+ 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx
+ 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx
+ 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx
+ 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx
+ 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx
+(9 rows)
+
+FETCH backward 15 in foo9;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx
+ 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx
+ 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx
+ 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx
+ 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx
+ 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx
+ 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx
+ 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx
+(8 rows)
+
+FETCH backward 16 in foo8;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx
+ 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx
+ 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx
+ 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx
+ 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx
+ 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx
+ 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx
+(7 rows)
+
+FETCH backward 17 in foo7;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx
+ 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx
+ 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx
+ 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx
+ 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx
+ 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx
+(6 rows)
+
+FETCH backward 18 in foo6;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx
+ 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx
+ 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx
+ 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx
+ 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx
+(5 rows)
+
+FETCH backward 19 in foo5;
+ 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
+ 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx
+ 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx
+ 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx
+(4 rows)
+
+FETCH backward 20 in foo4;
+ 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
+ 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx
+ 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx
+(3 rows)
+
+FETCH backward 21 in foo3;
+ 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
+ 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx
+(2 rows)
+
+FETCH backward 22 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
+(1 row)
+
+FETCH backward 23 in foo1;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+(0 rows)
+
+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 (don't 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; | 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; | 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; | 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; | 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; | 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
+ERROR: cursor can only scan forward
+HINT: Declare it with SCROLL option to enable backward scan.
+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;
+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;
+ 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 ABSOLUTE -1 FROM foo25;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 2968 | 9999 | 0 | 0 | 8 | 8 | 68 | 968 | 968 | 2968 | 2968 | 136 | 137 | EKAAAA | PUOAAA | VVVVxx
+(1 row)
+
+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; | t | f | t
+(1 row)
+
+CLOSE foo25;
+BEGIN;
+DECLARE foo25ns NO SCROLL CURSOR WITH HOLD FOR SELECT * FROM tenk2;
+FETCH FROM foo25ns;
+ 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 foo25ns;
+ 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 foo25ns;
+ 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 ABSOLUTE 4 FROM foo25ns;
+ 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
+(1 row)
+
+FETCH ABSOLUTE 4 FROM foo25ns; -- fail
+ERROR: cursor can only scan forward
+HINT: Declare it with SCROLL option to enable backward scan.
+SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable
+---------+---------------------------------------------------------------------+-------------+-----------+---------------
+ foo25ns | DECLARE foo25ns NO SCROLL CURSOR WITH HOLD FOR SELECT * FROM tenk2; | t | f | f
+(1 row)
+
+CLOSE foo25ns;
+--
+-- 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)
+
+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 | t
+ 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;
+ f1 | f2
+----+-------
+ 1 | one
+ 2 | two
+ 3 | three
+(3 rows)
+
+-- Check DELETE WHERE CURRENT
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+FETCH 2 FROM c1;
+ f1 | f2
+----+-----
+ 1 | one
+ 2 | two
+(2 rows)
+
+DELETE FROM uctest WHERE CURRENT OF c1;
+-- should show deletion
+SELECT * FROM uctest;
+ f1 | f2
+----+-------
+ 1 | one
+ 3 | three
+(2 rows)
+
+-- cursor did not move
+FETCH ALL FROM c1;
+ f1 | f2
+----+-------
+ 3 | three
+(1 row)
+
+-- cursor is insensitive
+MOVE BACKWARD ALL IN c1;
+FETCH ALL FROM c1;
+ f1 | f2
+----+-------
+ 1 | one
+ 2 | two
+ 3 | three
+(3 rows)
+
+COMMIT;
+-- should still see deletion
+SELECT * FROM uctest;
+ f1 | f2
+----+-------
+ 1 | one
+ 3 | three
+(2 rows)
+
+-- Check UPDATE WHERE CURRENT; this time use FOR UPDATE
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE;
+FETCH c1;
+ f1 | f2
+----+-----
+ 1 | one
+(1 row)
+
+UPDATE uctest SET f1 = 8 WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+ f1 | f2
+----+-------
+ 3 | three
+ 8 | one
+(2 rows)
+
+COMMIT;
+SELECT * FROM uctest;
+ f1 | f2
+----+-------
+ 3 | three
+ 8 | one
+(2 rows)
+
+-- Check repeated-update and update-then-delete cases
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+FETCH c1;
+ f1 | f2
+----+-------
+ 3 | three
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+ f1 | f2
+----+-------
+ 8 | one
+ 13 | three
+(2 rows)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+ f1 | f2
+----+-------
+ 8 | one
+ 23 | three
+(2 rows)
+
+-- insensitive cursor should not show effects of updates or deletes
+FETCH RELATIVE 0 FROM c1;
+ f1 | f2
+----+-------
+ 3 | three
+(1 row)
+
+DELETE FROM uctest WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+ f1 | f2
+----+-----
+ 8 | one
+(1 row)
+
+DELETE FROM uctest WHERE CURRENT OF c1; -- no-op
+SELECT * FROM uctest;
+ f1 | f2
+----+-----
+ 8 | one
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- no-op
+SELECT * FROM uctest;
+ f1 | f2
+----+-----
+ 8 | one
+(1 row)
+
+FETCH RELATIVE 0 FROM c1;
+ f1 | f2
+----+-------
+ 3 | three
+(1 row)
+
+ROLLBACK;
+SELECT * FROM uctest;
+ f1 | f2
+----+-------
+ 3 | three
+ 8 | one
+(2 rows)
+
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE;
+FETCH c1;
+ f1 | f2
+----+-------
+ 3 | three
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+ f1 | f2
+----+-------
+ 8 | one
+ 13 | three
+(2 rows)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+ f1 | f2
+----+-------
+ 8 | one
+ 23 | three
+(2 rows)
+
+DELETE FROM uctest WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+ f1 | f2
+----+-----
+ 8 | one
+(1 row)
+
+DELETE FROM uctest WHERE CURRENT OF c1; -- no-op
+SELECT * FROM uctest;
+ f1 | f2
+----+-----
+ 8 | one
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- no-op
+SELECT * FROM uctest;
+ f1 | f2
+----+-----
+ 8 | one
+(1 row)
+
+--- FOR UPDATE cursors can't currently scroll back, so this is an error:
+FETCH RELATIVE 0 FROM c1;
+ERROR: cursor can only scan forward
+HINT: Declare it with SCROLL option to enable backward scan.
+ROLLBACK;
+SELECT * FROM uctest;
+ f1 | f2
+----+-------
+ 3 | three
+ 8 | one
+(2 rows)
+
+-- Check insensitive cursor with INSERT
+-- (The above tests don't test the SQL notion of an insensitive cursor
+-- correctly, because per SQL standard, changes from WHERE CURRENT OF
+-- commands should be visible in the cursor. So here we make the
+-- changes with a command that is independent of the cursor.)
+BEGIN;
+DECLARE c1 INSENSITIVE CURSOR FOR SELECT * FROM uctest;
+INSERT INTO uctest VALUES (10, 'ten');
+FETCH NEXT FROM c1;
+ f1 | f2
+----+-------
+ 3 | three
+(1 row)
+
+FETCH NEXT FROM c1;
+ f1 | f2
+----+-----
+ 8 | one
+(1 row)
+
+FETCH NEXT FROM c1; -- insert not visible
+ f1 | f2
+----+----
+(0 rows)
+
+COMMIT;
+SELECT * FROM uctest;
+ f1 | f2
+----+-------
+ 3 | three
+ 8 | one
+ 10 | ten
+(3 rows)
+
+DELETE FROM uctest WHERE f1 = 10; -- restore test table state
+-- Check inheritance cases
+CREATE TEMP TABLE ucchild () inherits (uctest);
+INSERT INTO ucchild values(100, 'hundred');
+SELECT * FROM uctest;
+ f1 | f2
+-----+---------
+ 3 | three
+ 8 | one
+ 100 | hundred
+(3 rows)
+
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE;
+FETCH 1 FROM c1;
+ f1 | f2
+----+-------
+ 3 | three
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+ f1 | f2
+----+-----
+ 8 | one
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+ f1 | f2
+-----+---------
+ 100 | hundred
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+ f1 | f2
+----+----
+(0 rows)
+
+COMMIT;
+SELECT * FROM uctest;
+ f1 | f2
+-----+---------
+ 13 | three
+ 18 | one
+ 110 | hundred
+(3 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
+----+-----+----+-------
+ 18 | one | 13 | three
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail
+ERROR: cursor "c1" is not a simply updatable scan of table "uctest"
+ROLLBACK;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5 FOR UPDATE;
+FETCH 1 FROM c1;
+ f1 | f2 | f1 | f2
+----+-----+----+-------
+ 18 | one | 13 | three
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail
+ERROR: cursor "c1" has multiple FOR UPDATE/SHARE references to table "uctest"
+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
+----+-----+----+-------
+ 18 | one | 13 | three
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+ f1 | f2
+-----+---------
+ 13 | three
+ 28 | one
+ 110 | hundred
+(3 rows)
+
+ROLLBACK;
+-- Check various error cases
+DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no such cursor
+ERROR: cursor "c1" does not exist
+DECLARE cx CURSOR WITH HOLD FOR SELECT * FROM uctest;
+DELETE FROM uctest WHERE CURRENT OF cx; -- fail, can't use held cursor
+ERROR: cursor "cx" is held from a previous transaction
+BEGIN;
+DECLARE c CURSOR FOR SELECT * FROM tenk2;
+DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor on wrong table
+ERROR: cursor "c" is not a simply updatable scan of table "uctest"
+ROLLBACK;
+BEGIN;
+DECLARE c CURSOR FOR SELECT * FROM tenk2 FOR SHARE;
+DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor on wrong table
+ERROR: cursor "c" does not have a FOR UPDATE/SHARE reference to table "uctest"
+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: cursor "c" is not a simply updatable scan of table "tenk1"
+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: cursor "c" is not a simply updatable scan of table "uctest"
+ROLLBACK;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no current row
+ERROR: cursor "c1" is not positioned on a row
+ROLLBACK;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT MIN(f1) FROM uctest FOR UPDATE;
+ERROR: FOR UPDATE is not allowed with aggregate functions
+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
+----+-------
+ 13 | three
+(1 row)
+
+DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported
+ERROR: WHERE CURRENT OF on a view is not implemented
+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;
+SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA';
+ stringu1
+----------
+(0 rows)
+
+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;
+-- This tests the fetch-backwards code path
+FETCH ABSOLUTE 12 FROM c1;
+ currentid | payload
+-----------+---------
+ 12 | P12
+(1 row)
+
+FETCH ABSOLUTE 8 FROM c1;
+ currentid | payload
+-----------+---------
+ 8 | p8
+(1 row)
+
+DELETE FROM current_check WHERE CURRENT OF c1 RETURNING *;
+ currentid | payload
+-----------+---------
+ 8 | p8
+(1 row)
+
+-- This tests the ExecutorRewind code path
+FETCH ABSOLUTE 13 FROM c1;
+ currentid | payload
+-----------+---------
+ 13 | P13
+(1 row)
+
+FETCH ABSOLUTE 1 FROM c1;
+ currentid | payload
+-----------+---------
+ 1 | p1
+(1 row)
+
+DELETE FROM current_check WHERE CURRENT OF c1 RETURNING *;
+ currentid | payload
+-----------+---------
+ 1 | p1
+(1 row)
+
+SELECT * FROM current_check;
+ currentid | payload
+-----------+---------
+ 2 | p2
+ 3 | p3
+ 4 | p4
+ 5 | p5
+ 6 | p6
+ 7 | p7
+ 9 | p9
+ 10 | P10
+ 11 | P11
+ 12 | P12
+ 13 | P13
+ 14 | P14
+ 15 | P15
+ 16 | P16
+ 17 | P17
+ 18 | P18
+ 19 | P19
+(17 rows)
+
+ROLLBACK;
+-- Make sure snapshot management works okay, per bug report in
+-- 235395b90909301035v7228ce63q392931f15aa74b31@mail.gmail.com
+BEGIN;
+SET 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;
+UPDATE cursor SET a = 2;
+FETCH ALL FROM c1;
+ a
+---
+(0 rows)
+
+COMMIT;
+DROP TABLE cursor;
+-- 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;
+ q1 | q2
+------------------+------------------
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+(3 rows)
+
+move backward all in c;
+fetch all from c;
+ q1 | q2
+------------------+------------------
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+(3 rows)
+
+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
+ InitPlan 1 (returns $0)
+ -> 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
+(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;
+ x
+----
+ 42
+(1 row)
+
+rollback;
+begin;
+explain (costs off) declare c2 cursor for select generate_series(1,3) as g;
+ QUERY PLAN
+--------------
+ ProjectSet
+ -> Result
+(2 rows)
+
+explain (costs off) declare c2 scroll cursor for select generate_series(1,3) as g;
+ QUERY PLAN
+--------------------
+ Materialize
+ -> ProjectSet
+ -> 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;
+ g
+---
+ 3
+ 2
+ 1
+(3 rows)
+
+rollback;
diff --git a/src/test/regress/expected/portals_p2.out b/src/test/regress/expected/portals_p2.out
new file mode 100644
index 0000000..1e2365a
--- /dev/null
+++ b/src/test/regress/expected/portals_p2.out
@@ -0,0 +1,122 @@
+--
+-- PORTALS_P2
+--
+BEGIN;
+DECLARE foo13 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 50;
+DECLARE foo14 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 51;
+DECLARE foo15 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 52;
+DECLARE foo16 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 53;
+DECLARE foo17 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 54;
+DECLARE foo18 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 55;
+DECLARE foo19 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 56;
+DECLARE foo20 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 57;
+DECLARE foo21 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 58;
+DECLARE foo22 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 59;
+DECLARE foo23 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 60;
+DECLARE foo24 CURSOR FOR
+ SELECT * FROM onek2 WHERE unique1 = 50;
+DECLARE foo25 CURSOR FOR
+ SELECT * FROM onek2 WHERE unique1 = 60;
+FETCH all in foo13;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 50 | 253 | 0 | 2 | 0 | 10 | 0 | 50 | 50 | 50 | 50 | 0 | 1 | YBAAAA | TJAAAA | HHHHxx
+(1 row)
+
+FETCH all in foo14;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 51 | 76 | 1 | 3 | 1 | 11 | 1 | 51 | 51 | 51 | 51 | 2 | 3 | ZBAAAA | YCAAAA | AAAAxx
+(1 row)
+
+FETCH all in foo15;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 52 | 985 | 0 | 0 | 2 | 12 | 2 | 52 | 52 | 52 | 52 | 4 | 5 | ACAAAA | XLBAAA | HHHHxx
+(1 row)
+
+FETCH all in foo16;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 53 | 196 | 1 | 1 | 3 | 13 | 3 | 53 | 53 | 53 | 53 | 6 | 7 | BCAAAA | OHAAAA | AAAAxx
+(1 row)
+
+FETCH all in foo17;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 54 | 356 | 0 | 2 | 4 | 14 | 4 | 54 | 54 | 54 | 54 | 8 | 9 | CCAAAA | SNAAAA | AAAAxx
+(1 row)
+
+FETCH all in foo18;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 55 | 627 | 1 | 3 | 5 | 15 | 5 | 55 | 55 | 55 | 55 | 10 | 11 | DCAAAA | DYAAAA | VVVVxx
+(1 row)
+
+FETCH all in foo19;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 56 | 54 | 0 | 0 | 6 | 16 | 6 | 56 | 56 | 56 | 56 | 12 | 13 | ECAAAA | CCAAAA | OOOOxx
+(1 row)
+
+FETCH all in foo20;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 57 | 942 | 1 | 1 | 7 | 17 | 7 | 57 | 57 | 57 | 57 | 14 | 15 | FCAAAA | GKBAAA | OOOOxx
+(1 row)
+
+FETCH all in foo21;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 58 | 114 | 0 | 2 | 8 | 18 | 8 | 58 | 58 | 58 | 58 | 16 | 17 | GCAAAA | KEAAAA | OOOOxx
+(1 row)
+
+FETCH all in foo22;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 59 | 593 | 1 | 3 | 9 | 19 | 9 | 59 | 59 | 59 | 59 | 18 | 19 | HCAAAA | VWAAAA | HHHHxx
+(1 row)
+
+FETCH all in foo23;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 60 | 483 | 0 | 0 | 0 | 0 | 0 | 60 | 60 | 60 | 60 | 0 | 1 | ICAAAA | PSAAAA | VVVVxx
+(1 row)
+
+FETCH all in foo24;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 50 | 253 | 0 | 2 | 0 | 10 | 0 | 50 | 50 | 50 | 50 | 0 | 1 | YBAAAA | TJAAAA | HHHHxx
+(1 row)
+
+FETCH all in foo25;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 60 | 483 | 0 | 0 | 0 | 0 | 0 | 60 | 60 | 60 | 60 | 0 | 1 | ICAAAA | PSAAAA | VVVVxx
+(1 row)
+
+CLOSE foo13;
+CLOSE foo14;
+CLOSE foo15;
+CLOSE foo16;
+CLOSE foo17;
+CLOSE foo18;
+CLOSE foo19;
+CLOSE foo20;
+CLOSE foo21;
+CLOSE foo22;
+CLOSE foo23;
+CLOSE foo24;
+CLOSE foo25;
+END;
diff --git a/src/test/regress/expected/prepare.out b/src/test/regress/expected/prepare.out
new file mode 100644
index 0000000..3306c69
--- /dev/null
+++ b/src/test/regress/expected/prepare.out
@@ -0,0 +1,189 @@
+-- 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');
+SELECT * FROM q5_prep_results;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 200 | 9441 | 0 | 0 | 0 | 0 | 0 | 200 | 200 | 200 | 200 | 0 | 1 | SHAAAA | DZNAAA | HHHHxx
+ 497 | 9092 | 1 | 1 | 7 | 17 | 97 | 497 | 497 | 497 | 497 | 194 | 195 | DTAAAA | SLNAAA | AAAAxx
+ 1173 | 6699 | 1 | 1 | 3 | 13 | 73 | 173 | 1173 | 1173 | 1173 | 146 | 147 | DTAAAA | RXJAAA | VVVVxx
+ 1849 | 8143 | 1 | 1 | 9 | 9 | 49 | 849 | 1849 | 1849 | 1849 | 98 | 99 | DTAAAA | FBMAAA | VVVVxx
+ 2525 | 64 | 1 | 1 | 5 | 5 | 25 | 525 | 525 | 2525 | 2525 | 50 | 51 | DTAAAA | MCAAAA | AAAAxx
+ 3201 | 7309 | 1 | 1 | 1 | 1 | 1 | 201 | 1201 | 3201 | 3201 | 2 | 3 | DTAAAA | DVKAAA | HHHHxx
+ 3877 | 4060 | 1 | 1 | 7 | 17 | 77 | 877 | 1877 | 3877 | 3877 | 154 | 155 | DTAAAA | EAGAAA | AAAAxx
+ 4553 | 4113 | 1 | 1 | 3 | 13 | 53 | 553 | 553 | 4553 | 4553 | 106 | 107 | DTAAAA | FCGAAA | HHHHxx
+ 5229 | 6407 | 1 | 1 | 9 | 9 | 29 | 229 | 1229 | 229 | 5229 | 58 | 59 | DTAAAA | LMJAAA | VVVVxx
+ 5905 | 9537 | 1 | 1 | 5 | 5 | 5 | 905 | 1905 | 905 | 5905 | 10 | 11 | DTAAAA | VCOAAA | HHHHxx
+ 6581 | 4686 | 1 | 1 | 1 | 1 | 81 | 581 | 581 | 1581 | 6581 | 162 | 163 | DTAAAA | GYGAAA | OOOOxx
+ 7257 | 1895 | 1 | 1 | 7 | 17 | 57 | 257 | 1257 | 2257 | 7257 | 114 | 115 | DTAAAA | XUCAAA | VVVVxx
+ 7933 | 4514 | 1 | 1 | 3 | 13 | 33 | 933 | 1933 | 2933 | 7933 | 66 | 67 | DTAAAA | QRGAAA | OOOOxx
+ 8609 | 5918 | 1 | 1 | 9 | 9 | 9 | 609 | 609 | 3609 | 8609 | 18 | 19 | DTAAAA | QTIAAA | OOOOxx
+ 9285 | 8469 | 1 | 1 | 5 | 5 | 85 | 285 | 1285 | 4285 | 9285 | 170 | 171 | DTAAAA | TNMAAA | HHHHxx
+ 9961 | 2058 | 1 | 1 | 1 | 1 | 61 | 961 | 1961 | 4961 | 9961 | 122 | 123 | DTAAAA | EBDAAA | OOOOxx
+(16 rows)
+
+CREATE TEMPORARY TABLE q5_prep_nodata AS EXECUTE q5(200, 'DTAAAA')
+ WITH NO DATA;
+SELECT * FROM q5_prep_nodata;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+(0 rows)
+
+-- 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/prepared_xacts.out b/src/test/regress/expected/prepared_xacts.out
new file mode 100644
index 0000000..ba8e3cc
--- /dev/null
+++ b/src/test/regress/expected/prepared_xacts.out
@@ -0,0 +1,270 @@
+--
+-- PREPARED TRANSACTIONS (two-phase commit)
+--
+-- We can't readily test persistence of prepared xacts within the
+-- regression script framework, unfortunately. Note that a crash
+-- isn't really needed ... stopping and starting the postmaster would
+-- be enough, but we can't even do that here.
+-- create a simple table that we'll use in the tests
+CREATE TABLE pxtest1 (foobar VARCHAR(10));
+INSERT INTO pxtest1 VALUES ('aaa');
+-- Test PREPARE TRANSACTION
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+UPDATE pxtest1 SET foobar = 'bbb' WHERE foobar = 'aaa';
+SELECT * FROM pxtest1;
+ foobar
+--------
+ bbb
+(1 row)
+
+PREPARE TRANSACTION 'foo1';
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+(1 row)
+
+-- Test pg_prepared_xacts system view
+SELECT gid FROM pg_prepared_xacts;
+ gid
+------
+ foo1
+(1 row)
+
+-- Test ROLLBACK PREPARED
+ROLLBACK PREPARED 'foo1';
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+(1 row)
+
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+-- Test COMMIT PREPARED
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+INSERT INTO pxtest1 VALUES ('ddd');
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+ ddd
+(2 rows)
+
+PREPARE TRANSACTION 'foo2';
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+(1 row)
+
+COMMIT PREPARED 'foo2';
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+ ddd
+(2 rows)
+
+-- Test duplicate gids
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd';
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+ eee
+(2 rows)
+
+PREPARE TRANSACTION 'foo3';
+SELECT gid FROM pg_prepared_xacts;
+ gid
+------
+ foo3
+(1 row)
+
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+INSERT INTO pxtest1 VALUES ('fff');
+-- This should fail, because the gid foo3 is already in use
+PREPARE TRANSACTION 'foo3';
+ERROR: transaction identifier "foo3" is already in use
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+ ddd
+(2 rows)
+
+ROLLBACK PREPARED 'foo3';
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+ ddd
+(2 rows)
+
+-- Test serialization failure (SSI)
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd';
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+ eee
+(2 rows)
+
+PREPARE TRANSACTION 'foo4';
+SELECT gid FROM pg_prepared_xacts;
+ gid
+------
+ foo4
+(1 row)
+
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+ ddd
+(2 rows)
+
+-- This should fail, because the two transactions have a write-skew anomaly
+INSERT INTO pxtest1 VALUES ('fff');
+ERROR: could not serialize access due to read/write dependencies among transactions
+DETAIL: Reason code: Canceled on identification as a pivot, during write.
+HINT: The transaction might succeed if retried.
+PREPARE TRANSACTION 'foo5';
+SELECT gid FROM pg_prepared_xacts;
+ gid
+------
+ foo4
+(1 row)
+
+ROLLBACK PREPARED 'foo4';
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+-- Clean up
+DROP TABLE pxtest1;
+-- Test detection of session-level and xact-level locks on same object
+BEGIN;
+SELECT pg_advisory_lock(1);
+ pg_advisory_lock
+------------------
+
+(1 row)
+
+SELECT pg_advisory_xact_lock_shared(1);
+ pg_advisory_xact_lock_shared
+------------------------------
+
+(1 row)
+
+PREPARE TRANSACTION 'foo6'; -- fails
+ERROR: cannot PREPARE while holding both session-level and transaction-level locks on the same object
+-- Test subtransactions
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+ CREATE TABLE pxtest2 (a int);
+ INSERT INTO pxtest2 VALUES (1);
+ SAVEPOINT a;
+ INSERT INTO pxtest2 VALUES (2);
+ ROLLBACK TO a;
+ SAVEPOINT b;
+ INSERT INTO pxtest2 VALUES (3);
+PREPARE TRANSACTION 'regress-one';
+CREATE TABLE pxtest3(fff int);
+-- Test shared invalidation
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+ DROP TABLE pxtest3;
+ CREATE TABLE pxtest4 (a int);
+ INSERT INTO pxtest4 VALUES (1);
+ INSERT INTO pxtest4 VALUES (2);
+ DECLARE foo CURSOR FOR SELECT * FROM pxtest4;
+ -- Fetch 1 tuple, keeping the cursor open
+ FETCH 1 FROM foo;
+ a
+---
+ 1
+(1 row)
+
+PREPARE TRANSACTION 'regress-two';
+-- No such cursor
+FETCH 1 FROM foo;
+ERROR: cursor "foo" does not exist
+-- Table doesn't exist, the creation hasn't been committed yet
+SELECT * FROM pxtest2;
+ERROR: relation "pxtest2" does not exist
+LINE 1: SELECT * FROM pxtest2;
+ ^
+-- There should be two prepared transactions
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-------------
+ regress-one
+ regress-two
+(2 rows)
+
+-- pxtest3 should be locked because of the pending DROP
+begin;
+lock table pxtest3 in access share mode nowait;
+ERROR: could not obtain lock on relation "pxtest3"
+rollback;
+-- Disconnect, we will continue testing in a different backend
+\c -
+-- There should still be two prepared transactions
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-------------
+ regress-one
+ regress-two
+(2 rows)
+
+-- pxtest3 should still be locked because of the pending DROP
+begin;
+lock table pxtest3 in access share mode nowait;
+ERROR: could not obtain lock on relation "pxtest3"
+rollback;
+-- Commit table creation
+COMMIT PREPARED 'regress-one';
+\d pxtest2
+ Table "public.pxtest2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+
+SELECT * FROM pxtest2;
+ a
+---
+ 1
+ 3
+(2 rows)
+
+-- There should be one prepared transaction
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-------------
+ regress-two
+(1 row)
+
+-- Commit table drop
+COMMIT PREPARED 'regress-two';
+SELECT * FROM pxtest3;
+ERROR: relation "pxtest3" does not exist
+LINE 1: SELECT * FROM pxtest3;
+ ^
+-- There should be no prepared transactions
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+-- Clean up
+DROP TABLE pxtest2;
+DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled
+ERROR: table "pxtest3" does not exist
+DROP TABLE pxtest4;
diff --git a/src/test/regress/expected/prepared_xacts_1.out b/src/test/regress/expected/prepared_xacts_1.out
new file mode 100644
index 0000000..2cd50ad
--- /dev/null
+++ b/src/test/regress/expected/prepared_xacts_1.out
@@ -0,0 +1,266 @@
+--
+-- PREPARED TRANSACTIONS (two-phase commit)
+--
+-- We can't readily test persistence of prepared xacts within the
+-- regression script framework, unfortunately. Note that a crash
+-- isn't really needed ... stopping and starting the postmaster would
+-- be enough, but we can't even do that here.
+-- create a simple table that we'll use in the tests
+CREATE TABLE pxtest1 (foobar VARCHAR(10));
+INSERT INTO pxtest1 VALUES ('aaa');
+-- Test PREPARE TRANSACTION
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+UPDATE pxtest1 SET foobar = 'bbb' WHERE foobar = 'aaa';
+SELECT * FROM pxtest1;
+ foobar
+--------
+ bbb
+(1 row)
+
+PREPARE TRANSACTION 'foo1';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+(1 row)
+
+-- Test pg_prepared_xacts system view
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+-- Test ROLLBACK PREPARED
+ROLLBACK PREPARED 'foo1';
+ERROR: prepared transaction with identifier "foo1" does not exist
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+(1 row)
+
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+-- Test COMMIT PREPARED
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+INSERT INTO pxtest1 VALUES ('ddd');
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+ ddd
+(2 rows)
+
+PREPARE TRANSACTION 'foo2';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+(1 row)
+
+COMMIT PREPARED 'foo2';
+ERROR: prepared transaction with identifier "foo2" does not exist
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+(1 row)
+
+-- Test duplicate gids
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd';
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+(1 row)
+
+PREPARE TRANSACTION 'foo3';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+INSERT INTO pxtest1 VALUES ('fff');
+-- This should fail, because the gid foo3 is already in use
+PREPARE TRANSACTION 'foo3';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+(1 row)
+
+ROLLBACK PREPARED 'foo3';
+ERROR: prepared transaction with identifier "foo3" does not exist
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+(1 row)
+
+-- Test serialization failure (SSI)
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd';
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+(1 row)
+
+PREPARE TRANSACTION 'foo4';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+SELECT * FROM pxtest1;
+ foobar
+--------
+ aaa
+(1 row)
+
+-- This should fail, because the two transactions have a write-skew anomaly
+INSERT INTO pxtest1 VALUES ('fff');
+PREPARE TRANSACTION 'foo5';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+ROLLBACK PREPARED 'foo4';
+ERROR: prepared transaction with identifier "foo4" does not exist
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+-- Clean up
+DROP TABLE pxtest1;
+-- Test detection of session-level and xact-level locks on same object
+BEGIN;
+SELECT pg_advisory_lock(1);
+ pg_advisory_lock
+------------------
+
+(1 row)
+
+SELECT pg_advisory_xact_lock_shared(1);
+ pg_advisory_xact_lock_shared
+------------------------------
+
+(1 row)
+
+PREPARE TRANSACTION 'foo6'; -- fails
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+-- Test subtransactions
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+ CREATE TABLE pxtest2 (a int);
+ INSERT INTO pxtest2 VALUES (1);
+ SAVEPOINT a;
+ INSERT INTO pxtest2 VALUES (2);
+ ROLLBACK TO a;
+ SAVEPOINT b;
+ INSERT INTO pxtest2 VALUES (3);
+PREPARE TRANSACTION 'regress-one';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+CREATE TABLE pxtest3(fff int);
+-- Test shared invalidation
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+ DROP TABLE pxtest3;
+ CREATE TABLE pxtest4 (a int);
+ INSERT INTO pxtest4 VALUES (1);
+ INSERT INTO pxtest4 VALUES (2);
+ DECLARE foo CURSOR FOR SELECT * FROM pxtest4;
+ -- Fetch 1 tuple, keeping the cursor open
+ FETCH 1 FROM foo;
+ a
+---
+ 1
+(1 row)
+
+PREPARE TRANSACTION 'regress-two';
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
+-- No such cursor
+FETCH 1 FROM foo;
+ERROR: cursor "foo" does not exist
+-- Table doesn't exist, the creation hasn't been committed yet
+SELECT * FROM pxtest2;
+ERROR: relation "pxtest2" does not exist
+LINE 1: SELECT * FROM pxtest2;
+ ^
+-- There should be two prepared transactions
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+-- pxtest3 should be locked because of the pending DROP
+begin;
+lock table pxtest3 in access share mode nowait;
+rollback;
+-- Disconnect, we will continue testing in a different backend
+\c -
+-- There should still be two prepared transactions
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+-- pxtest3 should still be locked because of the pending DROP
+begin;
+lock table pxtest3 in access share mode nowait;
+rollback;
+-- Commit table creation
+COMMIT PREPARED 'regress-one';
+ERROR: prepared transaction with identifier "regress-one" does not exist
+\d pxtest2
+SELECT * FROM pxtest2;
+ERROR: relation "pxtest2" does not exist
+LINE 1: SELECT * FROM pxtest2;
+ ^
+-- There should be one prepared transaction
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+-- Commit table drop
+COMMIT PREPARED 'regress-two';
+ERROR: prepared transaction with identifier "regress-two" does not exist
+SELECT * FROM pxtest3;
+ fff
+-----
+(0 rows)
+
+-- There should be no prepared transactions
+SELECT gid FROM pg_prepared_xacts;
+ gid
+-----
+(0 rows)
+
+-- Clean up
+DROP TABLE pxtest2;
+ERROR: table "pxtest2" does not exist
+DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled
+DROP TABLE pxtest4;
+ERROR: table "pxtest4" does not exist
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
new file mode 100644
index 0000000..b3c3b25
--- /dev/null
+++ b/src/test/regress/expected/privileges.out
@@ -0,0 +1,2493 @@
+--
+-- 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;
+SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid;
+ lo_unlink
+-----------
+(0 rows)
+
+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 ROLE regress_priv_role;
+GRANT pg_read_all_data TO regress_priv_user6;
+GRANT pg_write_all_data TO regress_priv_user7;
+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: permission denied for table pg_toast_1213
+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);
+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;
+-- 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;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ -> Seq Scan on atest12 atest12_1
+ Filter: (b <<< 5)
+ -> Index Scan using atest12_a_idx on atest12
+ Index Cond: (a = atest12_1.b)
+ Filter: (b <<< 5)
+(6 rows)
+
+-- And this one.
+EXPLAIN (COSTS OFF) SELECT * FROM atest12 x, atest12 y
+ WHERE x.a = y.b and abs(y.a) <<< 5;
+ QUERY PLAN
+---------------------------------------------------
+ Nested Loop
+ -> Seq Scan on atest12 y
+ Filter: (abs(a) <<< 5)
+ -> Index Scan using atest12_a_idx on atest12 x
+ Index Cond: (a = y.b)
+(5 rows)
+
+-- 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;
+ QUERY PLAN
+-------------------------------------------
+ Nested Loop
+ Join Filter: (atest12.a = atest12_1.b)
+ -> Seq Scan on atest12
+ Filter: (b <<< 5)
+ -> Materialize
+ -> Seq Scan on atest12 atest12_1
+ Filter: (b <<< 5)
+(7 rows)
+
+-- 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
+-- 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;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ -> Seq Scan on atest12 atest12_1
+ Filter: (b <<< 5)
+ -> Index Scan using atest12_a_idx on atest12
+ Index Cond: (a = atest12_1.b)
+ Filter: (b <<< 5)
+(6 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y WHERE x.a = y.b;
+ QUERY PLAN
+-------------------------------------------
+ Nested Loop
+ Join Filter: (atest12.a = atest12_1.b)
+ -> Seq Scan on atest12
+ Filter: (b <<< 5)
+ -> Materialize
+ -> Seq Scan on atest12 atest12_1
+ Filter: (b <<< 5)
+(7 rows)
+
+-- 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;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ -> Seq Scan on atest12 atest12_1
+ Filter: ((b <<< 5) AND (abs(a) <<< 5))
+ -> Index Scan using atest12_a_idx on atest12
+ Index Cond: (a = atest12_1.b)
+ Filter: (b <<< 5)
+(6 rows)
+
+-- 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;
+ QUERY PLAN
+-------------------------------------
+ Nested Loop
+ Join Filter: (atest12_1.a = y.b)
+ -> Subquery Scan on y
+ Filter: (abs(y.a) <<< 5)
+ -> Seq Scan on atest12
+ Filter: (b <<< 5)
+ -> Seq Scan on atest12 atest12_1
+ Filter: (b <<< 5)
+(8 rows)
+
+-- 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;
+-- 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;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ -> Seq Scan on atest12 atest12_1
+ Filter: (b <<< 5)
+ -> Index Scan using atest12_a_idx on atest12
+ Index Cond: (a = atest12_1.b)
+ Filter: (b <<< 5)
+(6 rows)
+
+-- 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;
+ QUERY PLAN
+--------------------------------------
+ Hash Join
+ Hash Cond: (x.a = y.b)
+ -> Seq Scan on atest12 x
+ -> Hash
+ -> Seq Scan on atest12 y
+ Filter: (abs(a) <<< 5)
+(6 rows)
+
+-- 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
+BEGIN;
+RESET SESSION AUTHORIZATION;
+ALTER ROLE regress_priv_user1 NOINHERIT;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3;
+ERROR: permission denied for table atest3
+ROLLBACK;
+-- 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
+-- 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
+BEGIN;
+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
+ROLLBACK;
+-- 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: permission denied for type priv_testdomain1
+ALTER TYPE test8a ALTER ATTRIBUTE b TYPE priv_testdomain1;
+ERROR: permission denied for type priv_testdomain1
+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;
+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;
+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;
+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)));
+SELECT brin_desummarize_range('sro_brin', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+SELECT brin_summarize_range('sro_brin', 0);
+ brin_summarize_range
+----------------------
+ 1
+(1 row)
+
+DROP TABLE sro_tab;
+-- 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;
+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();
+-- Now, REFRESH will issue such an INSERT, queueing the GRANT
+CREATE OR REPLACE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS
+ 'INSERT INTO sro_trojan_table DEFAULT VALUES; SELECT true';
+REFRESH MATERIALIZED VIEW sro_mv;
+ERROR: cannot fire deferred trigger within security-restricted operation
+CONTEXT: SQL function "mv_action" statement 1
+\c -
+REFRESH MATERIALIZED VIEW sro_mv;
+ERROR: cannot fire deferred trigger within security-restricted operation
+CONTEXT: SQL function "mv_action" statement 1
+BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
+ERROR: must have admin option on role "regress_priv_group2"
+CONTEXT: SQL function "unwanted_grant" statement 1
+SQL statement "SELECT unwanted_grant()"
+PL/pgSQL function sro_trojan() line 1 at PERFORM
+SQL function "mv_action" statement 1
+-- REFRESH MATERIALIZED VIEW CONCURRENTLY use of eval_const_expressions()
+SET SESSION AUTHORIZATION regress_sro_user;
+CREATE FUNCTION unwanted_grant_nofail(int) RETURNS int
+ IMMUTABLE LANGUAGE plpgsql AS $$
+BEGIN
+ PERFORM unwanted_grant();
+ RAISE WARNING 'owned';
+ RETURN 1;
+EXCEPTION WHEN OTHERS THEN
+ RETURN 2;
+END$$;
+CREATE MATERIALIZED VIEW sro_index_mv AS SELECT 1 AS c;
+CREATE UNIQUE INDEX ON sro_index_mv (c) WHERE unwanted_grant_nofail(1) > 0;
+\c -
+REFRESH MATERIALIZED VIEW CONCURRENTLY sro_index_mv;
+REFRESH MATERIALIZED VIEW sro_index_mv;
+DROP OWNED BY regress_sro_user;
+DROP ROLE regress_sro_user;
+-- 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; -- ok: a role can self-admin
+NOTICE: role "regress_priv_user5" is already a member of role "regress_priv_group2"
+CREATE FUNCTION dogrant_fails() RETURNS void LANGUAGE sql SECURITY DEFINER AS
+ 'GRANT regress_priv_group2 TO regress_priv_user5';
+SELECT dogrant_fails(); -- fails: no self-admin in SECURITY DEFINER
+ERROR: must have admin option on role "regress_priv_group2"
+CONTEXT: SQL function "dogrant_fails" statement 1
+DROP FUNCTION dogrant_fails();
+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;
+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)
+
+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;
+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)
+
+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;
+SELECT lo_unlink(1001); -- to be denied
+ERROR: must be owner of large object 1001
+SELECT lo_unlink(2002);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+\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;
+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)
+
+-- compatibility mode in largeobject permission
+\c -
+SET lo_compat_privileges = false; -- default setting
+SET SESSION AUTHORIZATION regress_priv_user4;
+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
+\c -
+SET lo_compat_privileges = true; -- compatibility mode
+SET SESSION AUTHORIZATION regress_priv_user4;
+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
+-- 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
+-- 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)
+
+BEGIN;
+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;
+SAVEPOINT q; DELETE FROM datdba_only; 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
+ROLLBACK;
+-- 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.
+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)
+
+DROP SCHEMA testns CASCADE;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to table testns.t1
+drop cascades to table testns.t2
+drop cascades to function testns.priv_testfunc(integer)
+drop cascades to function testns.priv_testagg(integer)
+drop cascades to function testns.priv_testproc(integer)
+-- 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;
+DROP SCHEMA testns CASCADE;
+-- 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;
+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)
+
+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;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
new file mode 100644
index 0000000..bff68bd
--- /dev/null
+++ b/src/test/regress/expected/psql.out
@@ -0,0 +1,5985 @@
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+-- \set
+-- fail: invalid name
+\set invalid/name foo
+invalid variable name: "invalid/name"
+-- fail: invalid value for special variable
+\set AUTOCOMMIT foo
+unrecognized value "foo" for "AUTOCOMMIT": Boolean expected
+\set FETCH_COUNT foo
+invalid value "foo" for "FETCH_COUNT": integer expected
+-- check handling of built-in boolean variable
+\echo :ON_ERROR_ROLLBACK
+off
+\set ON_ERROR_ROLLBACK
+\echo :ON_ERROR_ROLLBACK
+on
+\set ON_ERROR_ROLLBACK foo
+unrecognized value "foo" for "ON_ERROR_ROLLBACK"
+Available values are: on, off, interactive.
+\echo :ON_ERROR_ROLLBACK
+on
+\set ON_ERROR_ROLLBACK on
+\echo :ON_ERROR_ROLLBACK
+on
+\unset ON_ERROR_ROLLBACK
+\echo :ON_ERROR_ROLLBACK
+off
+-- \g and \gx
+SELECT 1 as one, 2 as two \g
+ one | two
+-----+-----
+ 1 | 2
+(1 row)
+
+\gx
+-[ RECORD 1 ]
+one | 1
+two | 2
+
+SELECT 3 as three, 4 as four \gx
+-[ RECORD 1 ]
+three | 3
+four | 4
+
+\g
+ three | four
+-------+------
+ 3 | 4
+(1 row)
+
+-- \gx should work in FETCH_COUNT mode too
+\set FETCH_COUNT 1
+SELECT 1 as one, 2 as two \g
+ one | two
+-----+-----
+ 1 | 2
+(1 row)
+
+\gx
+-[ RECORD 1 ]
+one | 1
+two | 2
+
+SELECT 3 as three, 4 as four \gx
+-[ RECORD 1 ]
+three | 3
+four | 4
+
+\g
+ three | four
+-------+------
+ 3 | 4
+(1 row)
+
+\unset FETCH_COUNT
+-- \g/\gx with pset options
+SELECT 1 as one, 2 as two \g (format=csv csv_fieldsep='\t')
+one two
+1 2
+\g
+ one | two
+-----+-----
+ 1 | 2
+(1 row)
+
+SELECT 1 as one, 2 as two \gx (title='foo bar')
+foo bar
+-[ RECORD 1 ]
+one | 1
+two | 2
+
+\g
+ one | two
+-----+-----
+ 1 | 2
+(1 row)
+
+-- \gset
+select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_
+\echo :pref01_test01 :pref01_test02 :pref01_test03
+10 20 Hello
+-- should fail: bad variable name
+select 10 as "bad name"
+\gset
+invalid variable name: "bad name"
+select 97 as "EOF", 'ok' as _foo \gset IGNORE
+attempt to \gset into specially treated variable "IGNOREEOF" ignored
+\echo :IGNORE_foo :IGNOREEOF
+ok 0
+-- multiple backslash commands in one line
+select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+1
+select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+3
+4
+select 5 as x, 6 as y \gset pref01_ \\ \g \echo :pref01_x :pref01_y
+ x | y
+---+---
+ 5 | 6
+(1 row)
+
+5 6
+select 7 as x, 8 as y \g \gset pref01_ \echo :pref01_x :pref01_y
+ x | y
+---+---
+ 7 | 8
+(1 row)
+
+7 8
+-- NULL should unset the variable
+\set var2 xyz
+select 1 as var1, NULL as var2, 3 as var3 \gset
+\echo :var1 :var2 :var3
+1 :var2 3
+-- \gset requires just one tuple
+select 10 as test01, 20 as test02 from generate_series(1,3) \gset
+more than one row returned for \gset
+select 10 as test01, 20 as test02 from generate_series(1,0) \gset
+no rows returned for \gset
+-- \gset should work in FETCH_COUNT mode too
+\set FETCH_COUNT 1
+select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+1
+select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+3
+4
+select 10 as test01, 20 as test02 from generate_series(1,3) \gset
+more than one row returned for \gset
+select 10 as test01, 20 as test02 from generate_series(1,0) \gset
+no rows returned for \gset
+\unset FETCH_COUNT
+-- \gdesc
+SELECT
+ NULL AS zero,
+ 1 AS one,
+ 2.0 AS two,
+ 'three' AS three,
+ $1 AS four,
+ sin($2) as five,
+ 'foo'::varchar(4) as six,
+ CURRENT_DATE AS now
+\gdesc
+ Column | Type
+--------+----------------------
+ zero | text
+ one | integer
+ two | numeric
+ three | text
+ four | text
+ five | double precision
+ six | character varying(4)
+ now | date
+(8 rows)
+
+-- should work with tuple-returning utilities, such as EXECUTE
+PREPARE test AS SELECT 1 AS first, 2 AS second;
+EXECUTE test \gdesc
+ Column | Type
+--------+---------
+ first | integer
+ second | integer
+(2 rows)
+
+EXPLAIN EXECUTE test \gdesc
+ Column | Type
+------------+------
+ QUERY PLAN | text
+(1 row)
+
+-- should fail cleanly - syntax error
+SELECT 1 + \gdesc
+ERROR: syntax error at end of input
+LINE 1: SELECT 1 +
+ ^
+-- check behavior with empty results
+SELECT \gdesc
+The command has no result, or the result has no columns.
+CREATE TABLE bububu(a int) \gdesc
+The command has no result, or the result has no columns.
+-- subject command should not have executed
+TABLE bububu; -- fail
+ERROR: relation "bububu" does not exist
+LINE 1: TABLE bububu;
+ ^
+-- query buffer should remain unchanged
+SELECT 1 AS x, 'Hello', 2 AS y, true AS "dirty\name"
+\gdesc
+ Column | Type
+------------+---------
+ x | integer
+ ?column? | text
+ y | integer
+ dirty\name | boolean
+(4 rows)
+
+\g
+ x | ?column? | y | dirty\name
+---+----------+---+------------
+ 1 | Hello | 2 | t
+(1 row)
+
+-- all on one line
+SELECT 3 AS x, 'Hello', 4 AS y, true AS "dirty\name" \gdesc \g
+ Column | Type
+------------+---------
+ x | integer
+ ?column? | text
+ y | integer
+ dirty\name | boolean
+(4 rows)
+
+ x | ?column? | y | dirty\name
+---+----------+---+------------
+ 3 | Hello | 4 | t
+(1 row)
+
+-- \gexec
+create temporary table gexec_test(a int, b text, c date, d float);
+select format('create index on gexec_test(%I)', attname)
+from pg_attribute
+where attrelid = 'gexec_test'::regclass and attnum > 0
+order by attnum
+\gexec
+create index on gexec_test(a)
+create index on gexec_test(b)
+create index on gexec_test(c)
+create index on gexec_test(d)
+-- \gexec should work in FETCH_COUNT mode too
+-- (though the fetch limit applies to the executed queries not the meta query)
+\set FETCH_COUNT 1
+select 'select 1 as ones', 'select x.y, x.y*2 as double from generate_series(1,4) as x(y)'
+union all
+select 'drop table gexec_test', NULL
+union all
+select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
+\gexec
+select 1 as ones
+ ones
+------
+ 1
+(1 row)
+
+select x.y, x.y*2 as double from generate_series(1,4) as x(y)
+ y | double
+---+--------
+ 1 | 2
+ 2 | 4
+ 3 | 6
+ 4 | 8
+(4 rows)
+
+drop table gexec_test
+drop table gexec_test
+ERROR: table "gexec_test" does not exist
+select '2000-01-01'::date as party_over
+ party_over
+------------
+ 01-01-2000
+(1 row)
+
+\unset FETCH_COUNT
+-- show all pset options
+\pset
+border 1
+columns 0
+csv_fieldsep ','
+expanded off
+fieldsep '|'
+fieldsep_zero off
+footer on
+format aligned
+linestyle ascii
+null ''
+numericlocale off
+pager 1
+pager_min_lines 0
+recordsep '\n'
+recordsep_zero off
+tableattr
+title
+tuples_only off
+unicode_border_linestyle single
+unicode_column_linestyle single
+unicode_header_linestyle single
+-- test multi-line headers, wrapping, and newline indicators
+-- in aligned, unaligned, and wrapped formats
+prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
+
+c", array_to_string(array_agg(repeat('y',20-2*n)),E'\n') as "a
+bc" from generate_series(1,10) as n(n) group by n>1 order by n>1;
+\pset linestyle ascii
+\pset expanded off
+\pset columns 40
+\pset border 0
+\pset format unaligned
+execute q;
+ab
+
+c|a
+bc
+xx|yyyyyyyyyyyyyyyyyy
+xxxx
+xxxxxx
+xxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx|yyyyyyyyyyyyyyyy
+yyyyyyyyyyyyyy
+yyyyyyyyyyyy
+yyyyyyyyyy
+yyyyyyyy
+yyyyyy
+yyyy
+yy
+
+(2 rows)
+\pset format aligned
+execute q;
+ ab + a +
+ + bc
+ c
+-------------------- ------------------
+xx yyyyyyyyyyyyyyyyyy
+xxxx +yyyyyyyyyyyyyyyy +
+xxxxxx +yyyyyyyyyyyyyy +
+xxxxxxxx +yyyyyyyyyyyy +
+xxxxxxxxxx +yyyyyyyyyy +
+xxxxxxxxxxxx +yyyyyyyy +
+xxxxxxxxxxxxxx +yyyyyy +
+xxxxxxxxxxxxxxxx +yyyy +
+xxxxxxxxxxxxxxxxxx +yy +
+xxxxxxxxxxxxxxxxxxxx
+(2 rows)
+
+\pset format wrapped
+execute q;
+ ab + a +
+ + bc
+ c
+-------------------- ------------------
+xx yyyyyyyyyyyyyyyyyy
+xxxx +yyyyyyyyyyyyyyyy +
+xxxxxx +yyyyyyyyyyyyyy +
+xxxxxxxx +yyyyyyyyyyyy +
+xxxxxxxxxx +yyyyyyyyyy +
+xxxxxxxxxxxx +yyyyyyyy +
+xxxxxxxxxxxxxx +yyyyyy +
+xxxxxxxxxxxxxxxx +yyyy +
+xxxxxxxxxxxxxxxxxx +yy +
+xxxxxxxxxxxxxxxxxxxx
+(2 rows)
+
+\pset border 1
+\pset format unaligned
+execute q;
+ab
+
+c|a
+bc
+xx|yyyyyyyyyyyyyyyyyy
+xxxx
+xxxxxx
+xxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx|yyyyyyyyyyyyyyyy
+yyyyyyyyyyyyyy
+yyyyyyyyyyyy
+yyyyyyyyyy
+yyyyyyyy
+yyyyyy
+yyyy
+yy
+
+(2 rows)
+\pset format aligned
+execute q;
+ ab +| a +
+ +| bc
+ c |
+----------------------+--------------------
+ xx | yyyyyyyyyyyyyyyyyy
+ xxxx +| yyyyyyyyyyyyyyyy +
+ xxxxxx +| yyyyyyyyyyyyyy +
+ xxxxxxxx +| yyyyyyyyyyyy +
+ xxxxxxxxxx +| yyyyyyyyyy +
+ xxxxxxxxxxxx +| yyyyyyyy +
+ xxxxxxxxxxxxxx +| yyyyyy +
+ xxxxxxxxxxxxxxxx +| yyyy +
+ xxxxxxxxxxxxxxxxxx +| yy +
+ xxxxxxxxxxxxxxxxxxxx |
+(2 rows)
+
+\pset format wrapped
+execute q;
+ ab +| a +
+ +| bc
+ c |
+-------------------+--------------------
+ xx | yyyyyyyyyyyyyyyyyy
+ xxxx +| yyyyyyyyyyyyyyyy +
+ xxxxxx +| yyyyyyyyyyyyyy +
+ xxxxxxxx +| yyyyyyyyyyyy +
+ xxxxxxxxxx +| yyyyyyyyyy +
+ xxxxxxxxxxxx +| yyyyyyyy +
+ xxxxxxxxxxxxxx +| yyyyyy +
+ xxxxxxxxxxxxxxxx +| yyyy +
+ xxxxxxxxxxxxxxxxx.| yy +
+.x +|
+ xxxxxxxxxxxxxxxxx.|
+.xxx |
+(2 rows)
+
+\pset border 2
+\pset format unaligned
+execute q;
+ab
+
+c|a
+bc
+xx|yyyyyyyyyyyyyyyyyy
+xxxx
+xxxxxx
+xxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx|yyyyyyyyyyyyyyyy
+yyyyyyyyyyyyyy
+yyyyyyyyyyyy
+yyyyyyyyyy
+yyyyyyyy
+yyyyyy
+yyyy
+yy
+
+(2 rows)
+\pset format aligned
+execute q;
++----------------------+--------------------+
+| ab +| a +|
+| +| bc |
+| c | |
++----------------------+--------------------+
+| xx | yyyyyyyyyyyyyyyyyy |
+| xxxx +| yyyyyyyyyyyyyyyy +|
+| xxxxxx +| yyyyyyyyyyyyyy +|
+| xxxxxxxx +| yyyyyyyyyyyy +|
+| xxxxxxxxxx +| yyyyyyyyyy +|
+| xxxxxxxxxxxx +| yyyyyyyy +|
+| xxxxxxxxxxxxxx +| yyyyyy +|
+| xxxxxxxxxxxxxxxx +| yyyy +|
+| xxxxxxxxxxxxxxxxxx +| yy +|
+| xxxxxxxxxxxxxxxxxxxx | |
++----------------------+--------------------+
+(2 rows)
+
+\pset format wrapped
+execute q;
++-----------------+--------------------+
+| ab +| a +|
+| +| bc |
+| c | |
++-----------------+--------------------+
+| xx | yyyyyyyyyyyyyyyyyy |
+| xxxx +| yyyyyyyyyyyyyyyy +|
+| xxxxxx +| yyyyyyyyyyyyyy +|
+| xxxxxxxx +| yyyyyyyyyyyy +|
+| xxxxxxxxxx +| yyyyyyyyyy +|
+| xxxxxxxxxxxx +| yyyyyyyy +|
+| xxxxxxxxxxxxxx +| yyyyyy +|
+| xxxxxxxxxxxxxxx.| yyyy +|
+|.x +| yy +|
+| xxxxxxxxxxxxxxx.| |
+|.xxx +| |
+| xxxxxxxxxxxxxxx.| |
+|.xxxxx | |
++-----------------+--------------------+
+(2 rows)
+
+\pset expanded on
+\pset columns 20
+\pset border 0
+\pset format unaligned
+execute q;
+ab
+
+c|xx
+a
+bc|yyyyyyyyyyyyyyyyyy
+
+ab
+
+c|xxxx
+xxxxxx
+xxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx
+a
+bc|yyyyyyyyyyyyyyyy
+yyyyyyyyyyyyyy
+yyyyyyyyyyyy
+yyyyyyyyyy
+yyyyyyyy
+yyyyyy
+yyyy
+yy
+
+\pset format aligned
+execute q;
+* Record 1
+ab+ xx
+ +
+c
+a + yyyyyyyyyyyyyyyyyy
+bc
+* Record 2
+ab+ xxxx +
+ + xxxxxx +
+c xxxxxxxx +
+ xxxxxxxxxx +
+ xxxxxxxxxxxx +
+ xxxxxxxxxxxxxx +
+ xxxxxxxxxxxxxxxx +
+ xxxxxxxxxxxxxxxxxx +
+ xxxxxxxxxxxxxxxxxxxx
+a + yyyyyyyyyyyyyyyy +
+bc yyyyyyyyyyyyyy +
+ yyyyyyyyyyyy +
+ yyyyyyyyyy +
+ yyyyyyyy +
+ yyyyyy +
+ yyyy +
+ yy +
+
+
+\pset format wrapped
+execute q;
+* Record 1
+ab+ xx
+ +
+c
+a + yyyyyyyyyyyyyyy.
+bc .yyy
+* Record 2
+ab+ xxxx +
+ + xxxxxx +
+c xxxxxxxx +
+ xxxxxxxxxx +
+ xxxxxxxxxxxx +
+ xxxxxxxxxxxxxx +
+ xxxxxxxxxxxxxxx.
+ .x +
+ xxxxxxxxxxxxxxx.
+ .xxx +
+ xxxxxxxxxxxxxxx.
+ .xxxxx
+a + yyyyyyyyyyyyyyy.
+bc .y +
+ yyyyyyyyyyyyyy +
+ yyyyyyyyyyyy +
+ yyyyyyyyyy +
+ yyyyyyyy +
+ yyyyyy +
+ yyyy +
+ yy +
+
+
+\pset border 1
+\pset format unaligned
+execute q;
+ab
+
+c|xx
+a
+bc|yyyyyyyyyyyyyyyyyy
+
+ab
+
+c|xxxx
+xxxxxx
+xxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx
+a
+bc|yyyyyyyyyyyyyyyy
+yyyyyyyyyyyyyy
+yyyyyyyyyyyy
+yyyyyyyyyy
+yyyyyyyy
+yyyyyy
+yyyy
+yy
+
+\pset format aligned
+execute q;
+-[ RECORD 1 ]------------
+ab+| xx
+ +|
+c |
+a +| yyyyyyyyyyyyyyyyyy
+bc |
+-[ RECORD 2 ]------------
+ab+| xxxx +
+ +| xxxxxx +
+c | xxxxxxxx +
+ | xxxxxxxxxx +
+ | xxxxxxxxxxxx +
+ | xxxxxxxxxxxxxx +
+ | xxxxxxxxxxxxxxxx +
+ | xxxxxxxxxxxxxxxxxx +
+ | xxxxxxxxxxxxxxxxxxxx
+a +| yyyyyyyyyyyyyyyy +
+bc | yyyyyyyyyyyyyy +
+ | yyyyyyyyyyyy +
+ | yyyyyyyyyy +
+ | yyyyyyyy +
+ | yyyyyy +
+ | yyyy +
+ | yy +
+ |
+
+\pset format wrapped
+execute q;
+-[ RECORD 1 ]------
+ab+| xx
+ +|
+c |
+a +| yyyyyyyyyyyyyy.
+bc |.yyyy
+-[ RECORD 2 ]------
+ab+| xxxx +
+ +| xxxxxx +
+c | xxxxxxxx +
+ | xxxxxxxxxx +
+ | xxxxxxxxxxxx +
+ | xxxxxxxxxxxxxx+
+ | xxxxxxxxxxxxxx.
+ |.xx +
+ | xxxxxxxxxxxxxx.
+ |.xxxx +
+ | xxxxxxxxxxxxxx.
+ |.xxxxxx
+a +| yyyyyyyyyyyyyy.
+bc |.yy +
+ | yyyyyyyyyyyyyy+
+ | yyyyyyyyyyyy +
+ | yyyyyyyyyy +
+ | yyyyyyyy +
+ | yyyyyy +
+ | yyyy +
+ | yy +
+ |
+
+\pset border 2
+\pset format unaligned
+execute q;
+ab
+
+c|xx
+a
+bc|yyyyyyyyyyyyyyyyyy
+
+ab
+
+c|xxxx
+xxxxxx
+xxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx
+a
+bc|yyyyyyyyyyyyyyyy
+yyyyyyyyyyyyyy
+yyyyyyyyyyyy
+yyyyyyyyyy
+yyyyyyyy
+yyyyyy
+yyyy
+yy
+
+\pset format aligned
+execute q;
++-[ RECORD 1 ]--------------+
+| ab+| xx |
+| +| |
+| c | |
+| a +| yyyyyyyyyyyyyyyyyy |
+| bc | |
++-[ RECORD 2 ]--------------+
+| ab+| xxxx +|
+| +| xxxxxx +|
+| c | xxxxxxxx +|
+| | xxxxxxxxxx +|
+| | xxxxxxxxxxxx +|
+| | xxxxxxxxxxxxxx +|
+| | xxxxxxxxxxxxxxxx +|
+| | xxxxxxxxxxxxxxxxxx +|
+| | xxxxxxxxxxxxxxxxxxxx |
+| a +| yyyyyyyyyyyyyyyy +|
+| bc | yyyyyyyyyyyyyy +|
+| | yyyyyyyyyyyy +|
+| | yyyyyyyyyy +|
+| | yyyyyyyy +|
+| | yyyyyy +|
+| | yyyy +|
+| | yy +|
+| | |
++----+----------------------+
+
+\pset format wrapped
+execute q;
++-[ RECORD 1 ]-----+
+| ab+| xx |
+| +| |
+| c | |
+| a +| yyyyyyyyyyy.|
+| bc |.yyyyyyy |
++-[ RECORD 2 ]-----+
+| ab+| xxxx +|
+| +| xxxxxx +|
+| c | xxxxxxxx +|
+| | xxxxxxxxxx +|
+| | xxxxxxxxxxx.|
+| |.x +|
+| | xxxxxxxxxxx.|
+| |.xxx +|
+| | xxxxxxxxxxx.|
+| |.xxxxx +|
+| | xxxxxxxxxxx.|
+| |.xxxxxxx +|
+| | xxxxxxxxxxx.|
+| |.xxxxxxxxx |
+| a +| yyyyyyyyyyy.|
+| bc |.yyyyy +|
+| | yyyyyyyyyyy.|
+| |.yyy +|
+| | yyyyyyyyyyy.|
+| |.y +|
+| | yyyyyyyyyy +|
+| | yyyyyyyy +|
+| | yyyyyy +|
+| | yyyy +|
+| | yy +|
+| | |
++----+-------------+
+
+\pset linestyle old-ascii
+\pset expanded off
+\pset columns 40
+\pset border 0
+\pset format unaligned
+execute q;
+ab
+
+c|a
+bc
+xx|yyyyyyyyyyyyyyyyyy
+xxxx
+xxxxxx
+xxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx|yyyyyyyyyyyyyyyy
+yyyyyyyyyyyyyy
+yyyyyyyyyyyy
+yyyyyyyyyy
+yyyyyyyy
+yyyyyy
+yyyy
+yy
+
+(2 rows)
+\pset format aligned
+execute q;
+ ab a
+ + bc
+ c +
+-------------------- ------------------
+xx yyyyyyyyyyyyyyyyyy
+xxxx yyyyyyyyyyyyyyyy
+xxxxxx yyyyyyyyyyyyyy
+xxxxxxxx yyyyyyyyyyyy
+xxxxxxxxxx yyyyyyyyyy
+xxxxxxxxxxxx yyyyyyyy
+xxxxxxxxxxxxxx yyyyyy
+xxxxxxxxxxxxxxxx yyyy
+xxxxxxxxxxxxxxxxxx yy
+xxxxxxxxxxxxxxxxxxxx
+(2 rows)
+
+\pset format wrapped
+execute q;
+ ab a
+ + bc
+ c +
+-------------------- ------------------
+xx yyyyyyyyyyyyyyyyyy
+xxxx yyyyyyyyyyyyyyyy
+xxxxxx yyyyyyyyyyyyyy
+xxxxxxxx yyyyyyyyyyyy
+xxxxxxxxxx yyyyyyyyyy
+xxxxxxxxxxxx yyyyyyyy
+xxxxxxxxxxxxxx yyyyyy
+xxxxxxxxxxxxxxxx yyyy
+xxxxxxxxxxxxxxxxxx yy
+xxxxxxxxxxxxxxxxxxxx
+(2 rows)
+
+\pset border 1
+\pset format unaligned
+execute q;
+ab
+
+c|a
+bc
+xx|yyyyyyyyyyyyyyyyyy
+xxxx
+xxxxxx
+xxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx|yyyyyyyyyyyyyyyy
+yyyyyyyyyyyyyy
+yyyyyyyyyyyy
+yyyyyyyyyy
+yyyyyyyy
+yyyyyy
+yyyy
+yy
+
+(2 rows)
+\pset format aligned
+execute q;
+ ab | a
++ |+ bc
++ c |+
+----------------------+--------------------
+ xx | yyyyyyyyyyyyyyyyyy
+ xxxx | yyyyyyyyyyyyyyyy
+ xxxxxx : yyyyyyyyyyyyyy
+ xxxxxxxx : yyyyyyyyyyyy
+ xxxxxxxxxx : yyyyyyyyyy
+ xxxxxxxxxxxx : yyyyyyyy
+ xxxxxxxxxxxxxx : yyyyyy
+ xxxxxxxxxxxxxxxx : yyyy
+ xxxxxxxxxxxxxxxxxx : yy
+ xxxxxxxxxxxxxxxxxxxx :
+(2 rows)
+
+\pset format wrapped
+execute q;
+ ab | a
++ |+ bc
++ c |+
+-------------------+--------------------
+ xx | yyyyyyyyyyyyyyyyyy
+ xxxx | yyyyyyyyyyyyyyyy
+ xxxxxx : yyyyyyyyyyyyyy
+ xxxxxxxx : yyyyyyyyyyyy
+ xxxxxxxxxx : yyyyyyyyyy
+ xxxxxxxxxxxx : yyyyyyyy
+ xxxxxxxxxxxxxx : yyyyyy
+ xxxxxxxxxxxxxxxx : yyyy
+ xxxxxxxxxxxxxxxxx : yy
+ x :
+ xxxxxxxxxxxxxxxxx
+ xxx
+(2 rows)
+
+\pset border 2
+\pset format unaligned
+execute q;
+ab
+
+c|a
+bc
+xx|yyyyyyyyyyyyyyyyyy
+xxxx
+xxxxxx
+xxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx|yyyyyyyyyyyyyyyy
+yyyyyyyyyyyyyy
+yyyyyyyyyyyy
+yyyyyyyyyy
+yyyyyyyy
+yyyyyy
+yyyy
+yy
+
+(2 rows)
+\pset format aligned
+execute q;
++----------------------+--------------------+
+| ab | a |
+|+ |+ bc |
+|+ c |+ |
++----------------------+--------------------+
+| xx | yyyyyyyyyyyyyyyyyy |
+| xxxx | yyyyyyyyyyyyyyyy |
+| xxxxxx : yyyyyyyyyyyyyy |
+| xxxxxxxx : yyyyyyyyyyyy |
+| xxxxxxxxxx : yyyyyyyyyy |
+| xxxxxxxxxxxx : yyyyyyyy |
+| xxxxxxxxxxxxxx : yyyyyy |
+| xxxxxxxxxxxxxxxx : yyyy |
+| xxxxxxxxxxxxxxxxxx : yy |
+| xxxxxxxxxxxxxxxxxxxx : |
++----------------------+--------------------+
+(2 rows)
+
+\pset format wrapped
+execute q;
++-----------------+--------------------+
+| ab | a |
+|+ |+ bc |
+|+ c |+ |
++-----------------+--------------------+
+| xx | yyyyyyyyyyyyyyyyyy |
+| xxxx | yyyyyyyyyyyyyyyy |
+| xxxxxx : yyyyyyyyyyyyyy |
+| xxxxxxxx : yyyyyyyyyyyy |
+| xxxxxxxxxx : yyyyyyyyyy |
+| xxxxxxxxxxxx : yyyyyyyy |
+| xxxxxxxxxxxxxx : yyyyyy |
+| xxxxxxxxxxxxxxx : yyyy |
+| x : yy |
+| xxxxxxxxxxxxxxx : |
+| xxx |
+| xxxxxxxxxxxxxxx |
+| xxxxx |
++-----------------+--------------------+
+(2 rows)
+
+\pset expanded on
+\pset columns 20
+\pset border 0
+\pset format unaligned
+execute q;
+ab
+
+c|xx
+a
+bc|yyyyyyyyyyyyyyyyyy
+
+ab
+
+c|xxxx
+xxxxxx
+xxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx
+a
+bc|yyyyyyyyyyyyyyyy
+yyyyyyyyyyyyyy
+yyyyyyyyyyyy
+yyyyyyyyyy
+yyyyyyyy
+yyyyyy
+yyyy
+yy
+
+\pset format aligned
+execute q;
+* Record 1
+ ab xx
++
++c
+ a yyyyyyyyyyyyyyyyyy
++bc
+* Record 2
+ ab xxxx
++ xxxxxx
++c xxxxxxxx
+ xxxxxxxxxx
+ xxxxxxxxxxxx
+ xxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxx
+ a yyyyyyyyyyyyyyyy
++bc yyyyyyyyyyyyyy
+ yyyyyyyyyyyy
+ yyyyyyyyyy
+ yyyyyyyy
+ yyyyyy
+ yyyy
+ yy
+
+
+\pset format wrapped
+execute q;
+* Record 1
+ ab xx
++
++c
+ a yyyyyyyyyyyyyyyy
++bc yy
+* Record 2
+ ab xxxx
++ xxxxxx
++c xxxxxxxx
+ xxxxxxxxxx
+ xxxxxxxxxxxx
+ xxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxx
+ xx
+ xxxxxxxxxxxxxxxx
+ xxxx
+ a yyyyyyyyyyyyyyyy
++bc yyyyyyyyyyyyyy
+ yyyyyyyyyyyy
+ yyyyyyyyyy
+ yyyyyyyy
+ yyyyyy
+ yyyy
+ yy
+
+
+\pset border 1
+\pset format unaligned
+execute q;
+ab
+
+c|xx
+a
+bc|yyyyyyyyyyyyyyyyyy
+
+ab
+
+c|xxxx
+xxxxxx
+xxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx
+a
+bc|yyyyyyyyyyyyyyyy
+yyyyyyyyyyyyyy
+yyyyyyyyyyyy
+yyyyyyyyyy
+yyyyyyyy
+yyyyyy
+yyyy
+yy
+
+\pset format aligned
+execute q;
+-[ RECORD 1 ]-------------
+ ab | xx
++ ;
++c ;
+ a | yyyyyyyyyyyyyyyyyy
++bc ;
+-[ RECORD 2 ]-------------
+ ab | xxxx
++ : xxxxxx
++c : xxxxxxxx
+ : xxxxxxxxxx
+ : xxxxxxxxxxxx
+ : xxxxxxxxxxxxxx
+ : xxxxxxxxxxxxxxxx
+ : xxxxxxxxxxxxxxxxxx
+ : xxxxxxxxxxxxxxxxxxxx
+ a | yyyyyyyyyyyyyyyy
++bc : yyyyyyyyyyyyyy
+ : yyyyyyyyyyyy
+ : yyyyyyyyyy
+ : yyyyyyyy
+ : yyyyyy
+ : yyyy
+ : yy
+ :
+
+\pset format wrapped
+execute q;
+-[ RECORD 1 ]-------
+ ab | xx
++ ;
++c ;
+ a | yyyyyyyyyyyyyy
++bc ; yyyy
+-[ RECORD 2 ]-------
+ ab | xxxx
++ : xxxxxx
++c : xxxxxxxx
+ : xxxxxxxxxx
+ : xxxxxxxxxxxx
+ : xxxxxxxxxxxxxx
+ : xxxxxxxxxxxxxx
+ ; xx
+ : xxxxxxxxxxxxxx
+ ; xxxx
+ : xxxxxxxxxxxxxx
+ ; xxxxxx
+ a | yyyyyyyyyyyyyy
++bc ; yy
+ : yyyyyyyyyyyyyy
+ : yyyyyyyyyyyy
+ : yyyyyyyyyy
+ : yyyyyyyy
+ : yyyyyy
+ : yyyy
+ : yy
+ :
+
+\pset border 2
+\pset format unaligned
+execute q;
+ab
+
+c|xx
+a
+bc|yyyyyyyyyyyyyyyyyy
+
+ab
+
+c|xxxx
+xxxxxx
+xxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx
+a
+bc|yyyyyyyyyyyyyyyy
+yyyyyyyyyyyyyy
+yyyyyyyyyyyy
+yyyyyyyyyy
+yyyyyyyy
+yyyyyy
+yyyy
+yy
+
+\pset format aligned
+execute q;
++-[ RECORD 1 ]--------------+
+| ab | xx |
+|+ ; |
+|+c ; |
+| a | yyyyyyyyyyyyyyyyyy |
+|+bc ; |
++-[ RECORD 2 ]--------------+
+| ab | xxxx |
+|+ : xxxxxx |
+|+c : xxxxxxxx |
+| : xxxxxxxxxx |
+| : xxxxxxxxxxxx |
+| : xxxxxxxxxxxxxx |
+| : xxxxxxxxxxxxxxxx |
+| : xxxxxxxxxxxxxxxxxx |
+| : xxxxxxxxxxxxxxxxxxxx |
+| a | yyyyyyyyyyyyyyyy |
+|+bc : yyyyyyyyyyyyyy |
+| : yyyyyyyyyyyy |
+| : yyyyyyyyyy |
+| : yyyyyyyy |
+| : yyyyyy |
+| : yyyy |
+| : yy |
+| : |
++----+----------------------+
+
+\pset format wrapped
+execute q;
++-[ RECORD 1 ]-----+
+| ab | xx |
+|+ ; |
+|+c ; |
+| a | yyyyyyyyyyy |
+|+bc ; yyyyyyy |
++-[ RECORD 2 ]-----+
+| ab | xxxx |
+|+ : xxxxxx |
+|+c : xxxxxxxx |
+| : xxxxxxxxxx |
+| : xxxxxxxxxxx |
+| ; x |
+| : xxxxxxxxxxx |
+| ; xxx |
+| : xxxxxxxxxxx |
+| ; xxxxx |
+| : xxxxxxxxxxx |
+| ; xxxxxxx |
+| : xxxxxxxxxxx |
+| ; xxxxxxxxx |
+| a | yyyyyyyyyyy |
+|+bc ; yyyyy |
+| : yyyyyyyyyyy |
+| ; yyy |
+| : yyyyyyyyyyy |
+| ; y |
+| : yyyyyyyyyy |
+| : yyyyyyyy |
+| : yyyyyy |
+| : yyyy |
+| : yy |
+| : |
++----+-------------+
+
+deallocate q;
+-- test single-line header and data
+prepare q as select repeat('x',2*n) as "0123456789abcdef", repeat('y',20-2*n) as "0123456789" from generate_series(1,10) as n;
+\pset linestyle ascii
+\pset expanded off
+\pset columns 40
+\pset border 0
+\pset format unaligned
+execute q;
+0123456789abcdef|0123456789
+xx|yyyyyyyyyyyyyyyyyy
+xxxx|yyyyyyyyyyyyyyyy
+xxxxxx|yyyyyyyyyyyyyy
+xxxxxxxx|yyyyyyyyyyyy
+xxxxxxxxxx|yyyyyyyyyy
+xxxxxxxxxxxx|yyyyyyyy
+xxxxxxxxxxxxxx|yyyyyy
+xxxxxxxxxxxxxxxx|yyyy
+xxxxxxxxxxxxxxxxxx|yy
+xxxxxxxxxxxxxxxxxxxx|
+(10 rows)
+\pset format aligned
+execute q;
+ 0123456789abcdef 0123456789
+-------------------- ------------------
+xx yyyyyyyyyyyyyyyyyy
+xxxx yyyyyyyyyyyyyyyy
+xxxxxx yyyyyyyyyyyyyy
+xxxxxxxx yyyyyyyyyyyy
+xxxxxxxxxx yyyyyyyyyy
+xxxxxxxxxxxx yyyyyyyy
+xxxxxxxxxxxxxx yyyyyy
+xxxxxxxxxxxxxxxx yyyy
+xxxxxxxxxxxxxxxxxx yy
+xxxxxxxxxxxxxxxxxxxx
+(10 rows)
+
+\pset format wrapped
+execute q;
+ 0123456789abcdef 0123456789
+-------------------- ------------------
+xx yyyyyyyyyyyyyyyyyy
+xxxx yyyyyyyyyyyyyyyy
+xxxxxx yyyyyyyyyyyyyy
+xxxxxxxx yyyyyyyyyyyy
+xxxxxxxxxx yyyyyyyyyy
+xxxxxxxxxxxx yyyyyyyy
+xxxxxxxxxxxxxx yyyyyy
+xxxxxxxxxxxxxxxx yyyy
+xxxxxxxxxxxxxxxxxx yy
+xxxxxxxxxxxxxxxxxxxx
+(10 rows)
+
+\pset border 1
+\pset format unaligned
+execute q;
+0123456789abcdef|0123456789
+xx|yyyyyyyyyyyyyyyyyy
+xxxx|yyyyyyyyyyyyyyyy
+xxxxxx|yyyyyyyyyyyyyy
+xxxxxxxx|yyyyyyyyyyyy
+xxxxxxxxxx|yyyyyyyyyy
+xxxxxxxxxxxx|yyyyyyyy
+xxxxxxxxxxxxxx|yyyyyy
+xxxxxxxxxxxxxxxx|yyyy
+xxxxxxxxxxxxxxxxxx|yy
+xxxxxxxxxxxxxxxxxxxx|
+(10 rows)
+\pset format aligned
+execute q;
+ 0123456789abcdef | 0123456789
+----------------------+--------------------
+ xx | yyyyyyyyyyyyyyyyyy
+ xxxx | yyyyyyyyyyyyyyyy
+ xxxxxx | yyyyyyyyyyyyyy
+ xxxxxxxx | yyyyyyyyyyyy
+ xxxxxxxxxx | yyyyyyyyyy
+ xxxxxxxxxxxx | yyyyyyyy
+ xxxxxxxxxxxxxx | yyyyyy
+ xxxxxxxxxxxxxxxx | yyyy
+ xxxxxxxxxxxxxxxxxx | yy
+ xxxxxxxxxxxxxxxxxxxx |
+(10 rows)
+
+\pset format wrapped
+execute q;
+ 0123456789abcdef | 0123456789
+---------------------+------------------
+ xx | yyyyyyyyyyyyyyyy.
+ |.yy
+ xxxx | yyyyyyyyyyyyyyyy
+ xxxxxx | yyyyyyyyyyyyyy
+ xxxxxxxx | yyyyyyyyyyyy
+ xxxxxxxxxx | yyyyyyyyyy
+ xxxxxxxxxxxx | yyyyyyyy
+ xxxxxxxxxxxxxx | yyyyyy
+ xxxxxxxxxxxxxxxx | yyyy
+ xxxxxxxxxxxxxxxxxx | yy
+ xxxxxxxxxxxxxxxxxxx.|
+.x |
+(10 rows)
+
+\pset border 2
+\pset format unaligned
+execute q;
+0123456789abcdef|0123456789
+xx|yyyyyyyyyyyyyyyyyy
+xxxx|yyyyyyyyyyyyyyyy
+xxxxxx|yyyyyyyyyyyyyy
+xxxxxxxx|yyyyyyyyyyyy
+xxxxxxxxxx|yyyyyyyyyy
+xxxxxxxxxxxx|yyyyyyyy
+xxxxxxxxxxxxxx|yyyyyy
+xxxxxxxxxxxxxxxx|yyyy
+xxxxxxxxxxxxxxxxxx|yy
+xxxxxxxxxxxxxxxxxxxx|
+(10 rows)
+\pset format aligned
+execute q;
++----------------------+--------------------+
+| 0123456789abcdef | 0123456789 |
++----------------------+--------------------+
+| xx | yyyyyyyyyyyyyyyyyy |
+| xxxx | yyyyyyyyyyyyyyyy |
+| xxxxxx | yyyyyyyyyyyyyy |
+| xxxxxxxx | yyyyyyyyyyyy |
+| xxxxxxxxxx | yyyyyyyyyy |
+| xxxxxxxxxxxx | yyyyyyyy |
+| xxxxxxxxxxxxxx | yyyyyy |
+| xxxxxxxxxxxxxxxx | yyyy |
+| xxxxxxxxxxxxxxxxxx | yy |
+| xxxxxxxxxxxxxxxxxxxx | |
++----------------------+--------------------+
+(10 rows)
+
+\pset format wrapped
+execute q;
++--------------------+-----------------+
+| 0123456789abcdef | 0123456789 |
++--------------------+-----------------+
+| xx | yyyyyyyyyyyyyyy.|
+| |.yyy |
+| xxxx | yyyyyyyyyyyyyyy.|
+| |.y |
+| xxxxxx | yyyyyyyyyyyyyy |
+| xxxxxxxx | yyyyyyyyyyyy |
+| xxxxxxxxxx | yyyyyyyyyy |
+| xxxxxxxxxxxx | yyyyyyyy |
+| xxxxxxxxxxxxxx | yyyyyy |
+| xxxxxxxxxxxxxxxx | yyyy |
+| xxxxxxxxxxxxxxxxxx | yy |
+| xxxxxxxxxxxxxxxxxx.| |
+|.xx | |
++--------------------+-----------------+
+(10 rows)
+
+\pset expanded on
+\pset columns 30
+\pset border 0
+\pset format unaligned
+execute q;
+0123456789abcdef|xx
+0123456789|yyyyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxx
+0123456789|yyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxx
+0123456789|yyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxx
+0123456789|yyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxx
+0123456789|yyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxx
+0123456789|yyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxx
+0123456789|yyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxx
+0123456789|yyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxx
+0123456789|yy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxxxx
+0123456789|
+\pset format aligned
+execute q;
+* Record 1
+0123456789abcdef xx
+0123456789 yyyyyyyyyyyyyyyyyy
+* Record 2
+0123456789abcdef xxxx
+0123456789 yyyyyyyyyyyyyyyy
+* Record 3
+0123456789abcdef xxxxxx
+0123456789 yyyyyyyyyyyyyy
+* Record 4
+0123456789abcdef xxxxxxxx
+0123456789 yyyyyyyyyyyy
+* Record 5
+0123456789abcdef xxxxxxxxxx
+0123456789 yyyyyyyyyy
+* Record 6
+0123456789abcdef xxxxxxxxxxxx
+0123456789 yyyyyyyy
+* Record 7
+0123456789abcdef xxxxxxxxxxxxxx
+0123456789 yyyyyy
+* Record 8
+0123456789abcdef xxxxxxxxxxxxxxxx
+0123456789 yyyy
+* Record 9
+0123456789abcdef xxxxxxxxxxxxxxxxxx
+0123456789 yy
+* Record 10
+0123456789abcdef xxxxxxxxxxxxxxxxxxxx
+0123456789
+
+\pset format wrapped
+execute q;
+* Record 1
+0123456789abcdef xx
+0123456789 yyyyyyyyyyyy.
+ .yyyyyy
+* Record 2
+0123456789abcdef xxxx
+0123456789 yyyyyyyyyyyy.
+ .yyyy
+* Record 3
+0123456789abcdef xxxxxx
+0123456789 yyyyyyyyyyyy.
+ .yy
+* Record 4
+0123456789abcdef xxxxxxxx
+0123456789 yyyyyyyyyyyy
+* Record 5
+0123456789abcdef xxxxxxxxxx
+0123456789 yyyyyyyyyy
+* Record 6
+0123456789abcdef xxxxxxxxxxxx
+0123456789 yyyyyyyy
+* Record 7
+0123456789abcdef xxxxxxxxxxxx.
+ .xx
+0123456789 yyyyyy
+* Record 8
+0123456789abcdef xxxxxxxxxxxx.
+ .xxxx
+0123456789 yyyy
+* Record 9
+0123456789abcdef xxxxxxxxxxxx.
+ .xxxxxx
+0123456789 yy
+* Record 10
+0123456789abcdef xxxxxxxxxxxx.
+ .xxxxxxxx
+0123456789
+
+\pset border 1
+\pset format unaligned
+execute q;
+0123456789abcdef|xx
+0123456789|yyyyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxx
+0123456789|yyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxx
+0123456789|yyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxx
+0123456789|yyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxx
+0123456789|yyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxx
+0123456789|yyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxx
+0123456789|yyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxx
+0123456789|yyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxx
+0123456789|yy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxxxx
+0123456789|
+\pset format aligned
+execute q;
+-[ RECORD 1 ]----+---------------------
+0123456789abcdef | xx
+0123456789 | yyyyyyyyyyyyyyyyyy
+-[ RECORD 2 ]----+---------------------
+0123456789abcdef | xxxx
+0123456789 | yyyyyyyyyyyyyyyy
+-[ RECORD 3 ]----+---------------------
+0123456789abcdef | xxxxxx
+0123456789 | yyyyyyyyyyyyyy
+-[ RECORD 4 ]----+---------------------
+0123456789abcdef | xxxxxxxx
+0123456789 | yyyyyyyyyyyy
+-[ RECORD 5 ]----+---------------------
+0123456789abcdef | xxxxxxxxxx
+0123456789 | yyyyyyyyyy
+-[ RECORD 6 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxx
+0123456789 | yyyyyyyy
+-[ RECORD 7 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxxxx
+0123456789 | yyyyyy
+-[ RECORD 8 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxxxxxx
+0123456789 | yyyy
+-[ RECORD 9 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxxxxxxxx
+0123456789 | yy
+-[ RECORD 10 ]---+---------------------
+0123456789abcdef | xxxxxxxxxxxxxxxxxxxx
+0123456789 |
+
+\pset format wrapped
+execute q;
+-[ RECORD 1 ]----+-----------
+0123456789abcdef | xx
+0123456789 | yyyyyyyyyy.
+ |.yyyyyyyy
+-[ RECORD 2 ]----+-----------
+0123456789abcdef | xxxx
+0123456789 | yyyyyyyyyy.
+ |.yyyyyy
+-[ RECORD 3 ]----+-----------
+0123456789abcdef | xxxxxx
+0123456789 | yyyyyyyyyy.
+ |.yyyy
+-[ RECORD 4 ]----+-----------
+0123456789abcdef | xxxxxxxx
+0123456789 | yyyyyyyyyy.
+ |.yy
+-[ RECORD 5 ]----+-----------
+0123456789abcdef | xxxxxxxxxx
+0123456789 | yyyyyyyyyy
+-[ RECORD 6 ]----+-----------
+0123456789abcdef | xxxxxxxxxx.
+ |.xx
+0123456789 | yyyyyyyy
+-[ RECORD 7 ]----+-----------
+0123456789abcdef | xxxxxxxxxx.
+ |.xxxx
+0123456789 | yyyyyy
+-[ RECORD 8 ]----+-----------
+0123456789abcdef | xxxxxxxxxx.
+ |.xxxxxx
+0123456789 | yyyy
+-[ RECORD 9 ]----+-----------
+0123456789abcdef | xxxxxxxxxx.
+ |.xxxxxxxx
+0123456789 | yy
+-[ RECORD 10 ]---+-----------
+0123456789abcdef | xxxxxxxxxx.
+ |.xxxxxxxxxx
+0123456789 |
+
+\pset border 2
+\pset format unaligned
+execute q;
+0123456789abcdef|xx
+0123456789|yyyyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxx
+0123456789|yyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxx
+0123456789|yyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxx
+0123456789|yyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxx
+0123456789|yyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxx
+0123456789|yyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxx
+0123456789|yyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxx
+0123456789|yyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxx
+0123456789|yy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxxxx
+0123456789|
+\pset format aligned
+execute q;
++-[ RECORD 1 ]-----+----------------------+
+| 0123456789abcdef | xx |
+| 0123456789 | yyyyyyyyyyyyyyyyyy |
++-[ RECORD 2 ]-----+----------------------+
+| 0123456789abcdef | xxxx |
+| 0123456789 | yyyyyyyyyyyyyyyy |
++-[ RECORD 3 ]-----+----------------------+
+| 0123456789abcdef | xxxxxx |
+| 0123456789 | yyyyyyyyyyyyyy |
++-[ RECORD 4 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxx |
+| 0123456789 | yyyyyyyyyyyy |
++-[ RECORD 5 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxx |
+| 0123456789 | yyyyyyyyyy |
++-[ RECORD 6 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxx |
+| 0123456789 | yyyyyyyy |
++-[ RECORD 7 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxx |
+| 0123456789 | yyyyyy |
++-[ RECORD 8 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxxxx |
+| 0123456789 | yyyy |
++-[ RECORD 9 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxxxxxx |
+| 0123456789 | yy |
++-[ RECORD 10 ]----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxxxxxxxx |
+| 0123456789 | |
++------------------+----------------------+
+
+\pset format wrapped
+execute q;
++-[ RECORD 1 ]-----+---------+
+| 0123456789abcdef | xx |
+| 0123456789 | yyyyyyy.|
+| |.yyyyyyy.|
+| |.yyyy |
++-[ RECORD 2 ]-----+---------+
+| 0123456789abcdef | xxxx |
+| 0123456789 | yyyyyyy.|
+| |.yyyyyyy.|
+| |.yy |
++-[ RECORD 3 ]-----+---------+
+| 0123456789abcdef | xxxxxx |
+| 0123456789 | yyyyyyy.|
+| |.yyyyyyy |
++-[ RECORD 4 ]-----+---------+
+| 0123456789abcdef | xxxxxxx.|
+| |.x |
+| 0123456789 | yyyyyyy.|
+| |.yyyyy |
++-[ RECORD 5 ]-----+---------+
+| 0123456789abcdef | xxxxxxx.|
+| |.xxx |
+| 0123456789 | yyyyyyy.|
+| |.yyy |
++-[ RECORD 6 ]-----+---------+
+| 0123456789abcdef | xxxxxxx.|
+| |.xxxxx |
+| 0123456789 | yyyyyyy.|
+| |.y |
++-[ RECORD 7 ]-----+---------+
+| 0123456789abcdef | xxxxxxx.|
+| |.xxxxxxx |
+| 0123456789 | yyyyyy |
++-[ RECORD 8 ]-----+---------+
+| 0123456789abcdef | xxxxxxx.|
+| |.xxxxxxx.|
+| |.xx |
+| 0123456789 | yyyy |
++-[ RECORD 9 ]-----+---------+
+| 0123456789abcdef | xxxxxxx.|
+| |.xxxxxxx.|
+| |.xxxx |
+| 0123456789 | yy |
++-[ RECORD 10 ]----+---------+
+| 0123456789abcdef | xxxxxxx.|
+| |.xxxxxxx.|
+| |.xxxxxx |
+| 0123456789 | |
++------------------+---------+
+
+\pset expanded on
+\pset columns 20
+\pset border 0
+\pset format unaligned
+execute q;
+0123456789abcdef|xx
+0123456789|yyyyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxx
+0123456789|yyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxx
+0123456789|yyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxx
+0123456789|yyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxx
+0123456789|yyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxx
+0123456789|yyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxx
+0123456789|yyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxx
+0123456789|yyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxx
+0123456789|yy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxxxx
+0123456789|
+\pset format aligned
+execute q;
+* Record 1
+0123456789abcdef xx
+0123456789 yyyyyyyyyyyyyyyyyy
+* Record 2
+0123456789abcdef xxxx
+0123456789 yyyyyyyyyyyyyyyy
+* Record 3
+0123456789abcdef xxxxxx
+0123456789 yyyyyyyyyyyyyy
+* Record 4
+0123456789abcdef xxxxxxxx
+0123456789 yyyyyyyyyyyy
+* Record 5
+0123456789abcdef xxxxxxxxxx
+0123456789 yyyyyyyyyy
+* Record 6
+0123456789abcdef xxxxxxxxxxxx
+0123456789 yyyyyyyy
+* Record 7
+0123456789abcdef xxxxxxxxxxxxxx
+0123456789 yyyyyy
+* Record 8
+0123456789abcdef xxxxxxxxxxxxxxxx
+0123456789 yyyy
+* Record 9
+0123456789abcdef xxxxxxxxxxxxxxxxxx
+0123456789 yy
+* Record 10
+0123456789abcdef xxxxxxxxxxxxxxxxxxxx
+0123456789
+
+\pset format wrapped
+execute q;
+* Record 1
+0123456789abcdef xx
+0123456789 yyy.
+ .yyy.
+ .yyy.
+ .yyy.
+ .yyy.
+ .yyy
+* Record 2
+0123456789abcdef xxx.
+ .x
+0123456789 yyy.
+ .yyy.
+ .yyy.
+ .yyy.
+ .yyy.
+ .y
+* Record 3
+0123456789abcdef xxx.
+ .xxx
+0123456789 yyy.
+ .yyy.
+ .yyy.
+ .yyy.
+ .yy
+* Record 4
+0123456789abcdef xxx.
+ .xxx.
+ .xx
+0123456789 yyy.
+ .yyy.
+ .yyy.
+ .yyy
+* Record 5
+0123456789abcdef xxx.
+ .xxx.
+ .xxx.
+ .x
+0123456789 yyy.
+ .yyy.
+ .yyy.
+ .y
+* Record 6
+0123456789abcdef xxx.
+ .xxx.
+ .xxx.
+ .xxx
+0123456789 yyy.
+ .yyy.
+ .yy
+* Record 7
+0123456789abcdef xxx.
+ .xxx.
+ .xxx.
+ .xxx.
+ .xx
+0123456789 yyy.
+ .yyy
+* Record 8
+0123456789abcdef xxx.
+ .xxx.
+ .xxx.
+ .xxx.
+ .xxx.
+ .x
+0123456789 yyy.
+ .y
+* Record 9
+0123456789abcdef xxx.
+ .xxx.
+ .xxx.
+ .xxx.
+ .xxx.
+ .xxx
+0123456789 yy
+* Record 10
+0123456789abcdef xxx.
+ .xxx.
+ .xxx.
+ .xxx.
+ .xxx.
+ .xxx.
+ .xx
+0123456789
+
+\pset border 1
+\pset format unaligned
+execute q;
+0123456789abcdef|xx
+0123456789|yyyyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxx
+0123456789|yyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxx
+0123456789|yyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxx
+0123456789|yyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxx
+0123456789|yyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxx
+0123456789|yyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxx
+0123456789|yyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxx
+0123456789|yyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxx
+0123456789|yy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxxxx
+0123456789|
+\pset format aligned
+execute q;
+-[ RECORD 1 ]----+---------------------
+0123456789abcdef | xx
+0123456789 | yyyyyyyyyyyyyyyyyy
+-[ RECORD 2 ]----+---------------------
+0123456789abcdef | xxxx
+0123456789 | yyyyyyyyyyyyyyyy
+-[ RECORD 3 ]----+---------------------
+0123456789abcdef | xxxxxx
+0123456789 | yyyyyyyyyyyyyy
+-[ RECORD 4 ]----+---------------------
+0123456789abcdef | xxxxxxxx
+0123456789 | yyyyyyyyyyyy
+-[ RECORD 5 ]----+---------------------
+0123456789abcdef | xxxxxxxxxx
+0123456789 | yyyyyyyyyy
+-[ RECORD 6 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxx
+0123456789 | yyyyyyyy
+-[ RECORD 7 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxxxx
+0123456789 | yyyyyy
+-[ RECORD 8 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxxxxxx
+0123456789 | yyyy
+-[ RECORD 9 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxxxxxxxx
+0123456789 | yy
+-[ RECORD 10 ]---+---------------------
+0123456789abcdef | xxxxxxxxxxxxxxxxxxxx
+0123456789 |
+
+\pset format wrapped
+execute q;
+-[ RECORD 1 ]----+----
+0123456789abcdef | xx
+0123456789 | yyy.
+ |.yyy.
+ |.yyy.
+ |.yyy.
+ |.yyy.
+ |.yyy
+-[ RECORD 2 ]----+----
+0123456789abcdef | xxx.
+ |.x
+0123456789 | yyy.
+ |.yyy.
+ |.yyy.
+ |.yyy.
+ |.yyy.
+ |.y
+-[ RECORD 3 ]----+----
+0123456789abcdef | xxx.
+ |.xxx
+0123456789 | yyy.
+ |.yyy.
+ |.yyy.
+ |.yyy.
+ |.yy
+-[ RECORD 4 ]----+----
+0123456789abcdef | xxx.
+ |.xxx.
+ |.xx
+0123456789 | yyy.
+ |.yyy.
+ |.yyy.
+ |.yyy
+-[ RECORD 5 ]----+----
+0123456789abcdef | xxx.
+ |.xxx.
+ |.xxx.
+ |.x
+0123456789 | yyy.
+ |.yyy.
+ |.yyy.
+ |.y
+-[ RECORD 6 ]----+----
+0123456789abcdef | xxx.
+ |.xxx.
+ |.xxx.
+ |.xxx
+0123456789 | yyy.
+ |.yyy.
+ |.yy
+-[ RECORD 7 ]----+----
+0123456789abcdef | xxx.
+ |.xxx.
+ |.xxx.
+ |.xxx.
+ |.xx
+0123456789 | yyy.
+ |.yyy
+-[ RECORD 8 ]----+----
+0123456789abcdef | xxx.
+ |.xxx.
+ |.xxx.
+ |.xxx.
+ |.xxx.
+ |.x
+0123456789 | yyy.
+ |.y
+-[ RECORD 9 ]----+----
+0123456789abcdef | xxx.
+ |.xxx.
+ |.xxx.
+ |.xxx.
+ |.xxx.
+ |.xxx
+0123456789 | yy
+-[ RECORD 10 ]---+----
+0123456789abcdef | xxx.
+ |.xxx.
+ |.xxx.
+ |.xxx.
+ |.xxx.
+ |.xxx.
+ |.xx
+0123456789 |
+
+\pset border 2
+\pset format unaligned
+execute q;
+0123456789abcdef|xx
+0123456789|yyyyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxx
+0123456789|yyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxx
+0123456789|yyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxx
+0123456789|yyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxx
+0123456789|yyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxx
+0123456789|yyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxx
+0123456789|yyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxx
+0123456789|yyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxx
+0123456789|yy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxxxx
+0123456789|
+\pset format aligned
+execute q;
++-[ RECORD 1 ]-----+----------------------+
+| 0123456789abcdef | xx |
+| 0123456789 | yyyyyyyyyyyyyyyyyy |
++-[ RECORD 2 ]-----+----------------------+
+| 0123456789abcdef | xxxx |
+| 0123456789 | yyyyyyyyyyyyyyyy |
++-[ RECORD 3 ]-----+----------------------+
+| 0123456789abcdef | xxxxxx |
+| 0123456789 | yyyyyyyyyyyyyy |
++-[ RECORD 4 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxx |
+| 0123456789 | yyyyyyyyyyyy |
++-[ RECORD 5 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxx |
+| 0123456789 | yyyyyyyyyy |
++-[ RECORD 6 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxx |
+| 0123456789 | yyyyyyyy |
++-[ RECORD 7 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxx |
+| 0123456789 | yyyyyy |
++-[ RECORD 8 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxxxx |
+| 0123456789 | yyyy |
++-[ RECORD 9 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxxxxxx |
+| 0123456789 | yy |
++-[ RECORD 10 ]----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxxxxxxxx |
+| 0123456789 | |
++------------------+----------------------+
+
+\pset format wrapped
+execute q;
++-[ RECORD 1 ]-----+-----+
+| 0123456789abcdef | xx |
+| 0123456789 | yyy.|
+| |.yyy.|
+| |.yyy.|
+| |.yyy.|
+| |.yyy.|
+| |.yyy |
++-[ RECORD 2 ]-----+-----+
+| 0123456789abcdef | xxx.|
+| |.x |
+| 0123456789 | yyy.|
+| |.yyy.|
+| |.yyy.|
+| |.yyy.|
+| |.yyy.|
+| |.y |
++-[ RECORD 3 ]-----+-----+
+| 0123456789abcdef | xxx.|
+| |.xxx |
+| 0123456789 | yyy.|
+| |.yyy.|
+| |.yyy.|
+| |.yyy.|
+| |.yy |
++-[ RECORD 4 ]-----+-----+
+| 0123456789abcdef | xxx.|
+| |.xxx.|
+| |.xx |
+| 0123456789 | yyy.|
+| |.yyy.|
+| |.yyy.|
+| |.yyy |
++-[ RECORD 5 ]-----+-----+
+| 0123456789abcdef | xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.x |
+| 0123456789 | yyy.|
+| |.yyy.|
+| |.yyy.|
+| |.y |
++-[ RECORD 6 ]-----+-----+
+| 0123456789abcdef | xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.xxx |
+| 0123456789 | yyy.|
+| |.yyy.|
+| |.yy |
++-[ RECORD 7 ]-----+-----+
+| 0123456789abcdef | xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.xx |
+| 0123456789 | yyy.|
+| |.yyy |
++-[ RECORD 8 ]-----+-----+
+| 0123456789abcdef | xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.x |
+| 0123456789 | yyy.|
+| |.y |
++-[ RECORD 9 ]-----+-----+
+| 0123456789abcdef | xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.xxx |
+| 0123456789 | yy |
++-[ RECORD 10 ]----+-----+
+| 0123456789abcdef | xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.xxx.|
+| |.xx |
+| 0123456789 | |
++------------------+-----+
+
+\pset linestyle old-ascii
+\pset expanded off
+\pset columns 40
+\pset border 0
+\pset format unaligned
+execute q;
+0123456789abcdef|0123456789
+xx|yyyyyyyyyyyyyyyyyy
+xxxx|yyyyyyyyyyyyyyyy
+xxxxxx|yyyyyyyyyyyyyy
+xxxxxxxx|yyyyyyyyyyyy
+xxxxxxxxxx|yyyyyyyyyy
+xxxxxxxxxxxx|yyyyyyyy
+xxxxxxxxxxxxxx|yyyyyy
+xxxxxxxxxxxxxxxx|yyyy
+xxxxxxxxxxxxxxxxxx|yy
+xxxxxxxxxxxxxxxxxxxx|
+(10 rows)
+\pset format aligned
+execute q;
+ 0123456789abcdef 0123456789
+-------------------- ------------------
+xx yyyyyyyyyyyyyyyyyy
+xxxx yyyyyyyyyyyyyyyy
+xxxxxx yyyyyyyyyyyyyy
+xxxxxxxx yyyyyyyyyyyy
+xxxxxxxxxx yyyyyyyyyy
+xxxxxxxxxxxx yyyyyyyy
+xxxxxxxxxxxxxx yyyyyy
+xxxxxxxxxxxxxxxx yyyy
+xxxxxxxxxxxxxxxxxx yy
+xxxxxxxxxxxxxxxxxxxx
+(10 rows)
+
+\pset format wrapped
+execute q;
+ 0123456789abcdef 0123456789
+-------------------- ------------------
+xx yyyyyyyyyyyyyyyyyy
+xxxx yyyyyyyyyyyyyyyy
+xxxxxx yyyyyyyyyyyyyy
+xxxxxxxx yyyyyyyyyyyy
+xxxxxxxxxx yyyyyyyyyy
+xxxxxxxxxxxx yyyyyyyy
+xxxxxxxxxxxxxx yyyyyy
+xxxxxxxxxxxxxxxx yyyy
+xxxxxxxxxxxxxxxxxx yy
+xxxxxxxxxxxxxxxxxxxx
+(10 rows)
+
+\pset border 1
+\pset format unaligned
+execute q;
+0123456789abcdef|0123456789
+xx|yyyyyyyyyyyyyyyyyy
+xxxx|yyyyyyyyyyyyyyyy
+xxxxxx|yyyyyyyyyyyyyy
+xxxxxxxx|yyyyyyyyyyyy
+xxxxxxxxxx|yyyyyyyyyy
+xxxxxxxxxxxx|yyyyyyyy
+xxxxxxxxxxxxxx|yyyyyy
+xxxxxxxxxxxxxxxx|yyyy
+xxxxxxxxxxxxxxxxxx|yy
+xxxxxxxxxxxxxxxxxxxx|
+(10 rows)
+\pset format aligned
+execute q;
+ 0123456789abcdef | 0123456789
+----------------------+--------------------
+ xx | yyyyyyyyyyyyyyyyyy
+ xxxx | yyyyyyyyyyyyyyyy
+ xxxxxx | yyyyyyyyyyyyyy
+ xxxxxxxx | yyyyyyyyyyyy
+ xxxxxxxxxx | yyyyyyyyyy
+ xxxxxxxxxxxx | yyyyyyyy
+ xxxxxxxxxxxxxx | yyyyyy
+ xxxxxxxxxxxxxxxx | yyyy
+ xxxxxxxxxxxxxxxxxx | yy
+ xxxxxxxxxxxxxxxxxxxx |
+(10 rows)
+
+\pset format wrapped
+execute q;
+ 0123456789abcdef | 0123456789
+---------------------+------------------
+ xx | yyyyyyyyyyyyyyyy
+ ; yy
+ xxxx | yyyyyyyyyyyyyyyy
+ xxxxxx | yyyyyyyyyyyyyy
+ xxxxxxxx | yyyyyyyyyyyy
+ xxxxxxxxxx | yyyyyyyyyy
+ xxxxxxxxxxxx | yyyyyyyy
+ xxxxxxxxxxxxxx | yyyyyy
+ xxxxxxxxxxxxxxxx | yyyy
+ xxxxxxxxxxxxxxxxxx | yy
+ xxxxxxxxxxxxxxxxxxx |
+ x
+(10 rows)
+
+\pset border 2
+\pset format unaligned
+execute q;
+0123456789abcdef|0123456789
+xx|yyyyyyyyyyyyyyyyyy
+xxxx|yyyyyyyyyyyyyyyy
+xxxxxx|yyyyyyyyyyyyyy
+xxxxxxxx|yyyyyyyyyyyy
+xxxxxxxxxx|yyyyyyyyyy
+xxxxxxxxxxxx|yyyyyyyy
+xxxxxxxxxxxxxx|yyyyyy
+xxxxxxxxxxxxxxxx|yyyy
+xxxxxxxxxxxxxxxxxx|yy
+xxxxxxxxxxxxxxxxxxxx|
+(10 rows)
+\pset format aligned
+execute q;
++----------------------+--------------------+
+| 0123456789abcdef | 0123456789 |
++----------------------+--------------------+
+| xx | yyyyyyyyyyyyyyyyyy |
+| xxxx | yyyyyyyyyyyyyyyy |
+| xxxxxx | yyyyyyyyyyyyyy |
+| xxxxxxxx | yyyyyyyyyyyy |
+| xxxxxxxxxx | yyyyyyyyyy |
+| xxxxxxxxxxxx | yyyyyyyy |
+| xxxxxxxxxxxxxx | yyyyyy |
+| xxxxxxxxxxxxxxxx | yyyy |
+| xxxxxxxxxxxxxxxxxx | yy |
+| xxxxxxxxxxxxxxxxxxxx | |
++----------------------+--------------------+
+(10 rows)
+
+\pset format wrapped
+execute q;
++--------------------+-----------------+
+| 0123456789abcdef | 0123456789 |
++--------------------+-----------------+
+| xx | yyyyyyyyyyyyyyy |
+| ; yyy |
+| xxxx | yyyyyyyyyyyyyyy |
+| ; y |
+| xxxxxx | yyyyyyyyyyyyyy |
+| xxxxxxxx | yyyyyyyyyyyy |
+| xxxxxxxxxx | yyyyyyyyyy |
+| xxxxxxxxxxxx | yyyyyyyy |
+| xxxxxxxxxxxxxx | yyyyyy |
+| xxxxxxxxxxxxxxxx | yyyy |
+| xxxxxxxxxxxxxxxxxx | yy |
+| xxxxxxxxxxxxxxxxxx | |
+| xx |
++--------------------+-----------------+
+(10 rows)
+
+\pset expanded on
+\pset border 0
+\pset format unaligned
+execute q;
+0123456789abcdef|xx
+0123456789|yyyyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxx
+0123456789|yyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxx
+0123456789|yyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxx
+0123456789|yyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxx
+0123456789|yyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxx
+0123456789|yyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxx
+0123456789|yyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxx
+0123456789|yyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxx
+0123456789|yy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxxxx
+0123456789|
+\pset format aligned
+execute q;
+* Record 1
+0123456789abcdef xx
+0123456789 yyyyyyyyyyyyyyyyyy
+* Record 2
+0123456789abcdef xxxx
+0123456789 yyyyyyyyyyyyyyyy
+* Record 3
+0123456789abcdef xxxxxx
+0123456789 yyyyyyyyyyyyyy
+* Record 4
+0123456789abcdef xxxxxxxx
+0123456789 yyyyyyyyyyyy
+* Record 5
+0123456789abcdef xxxxxxxxxx
+0123456789 yyyyyyyyyy
+* Record 6
+0123456789abcdef xxxxxxxxxxxx
+0123456789 yyyyyyyy
+* Record 7
+0123456789abcdef xxxxxxxxxxxxxx
+0123456789 yyyyyy
+* Record 8
+0123456789abcdef xxxxxxxxxxxxxxxx
+0123456789 yyyy
+* Record 9
+0123456789abcdef xxxxxxxxxxxxxxxxxx
+0123456789 yy
+* Record 10
+0123456789abcdef xxxxxxxxxxxxxxxxxxxx
+0123456789
+
+\pset format wrapped
+execute q;
+* Record 1
+0123456789abcdef xx
+0123456789 yyyyyyyyyyyyyyyyyy
+* Record 2
+0123456789abcdef xxxx
+0123456789 yyyyyyyyyyyyyyyy
+* Record 3
+0123456789abcdef xxxxxx
+0123456789 yyyyyyyyyyyyyy
+* Record 4
+0123456789abcdef xxxxxxxx
+0123456789 yyyyyyyyyyyy
+* Record 5
+0123456789abcdef xxxxxxxxxx
+0123456789 yyyyyyyyyy
+* Record 6
+0123456789abcdef xxxxxxxxxxxx
+0123456789 yyyyyyyy
+* Record 7
+0123456789abcdef xxxxxxxxxxxxxx
+0123456789 yyyyyy
+* Record 8
+0123456789abcdef xxxxxxxxxxxxxxxx
+0123456789 yyyy
+* Record 9
+0123456789abcdef xxxxxxxxxxxxxxxxxx
+0123456789 yy
+* Record 10
+0123456789abcdef xxxxxxxxxxxxxxxxxxxx
+0123456789
+
+\pset border 1
+\pset format unaligned
+execute q;
+0123456789abcdef|xx
+0123456789|yyyyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxx
+0123456789|yyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxx
+0123456789|yyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxx
+0123456789|yyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxx
+0123456789|yyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxx
+0123456789|yyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxx
+0123456789|yyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxx
+0123456789|yyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxx
+0123456789|yy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxxxx
+0123456789|
+\pset format aligned
+execute q;
+-[ RECORD 1 ]----+---------------------
+0123456789abcdef | xx
+0123456789 | yyyyyyyyyyyyyyyyyy
+-[ RECORD 2 ]----+---------------------
+0123456789abcdef | xxxx
+0123456789 | yyyyyyyyyyyyyyyy
+-[ RECORD 3 ]----+---------------------
+0123456789abcdef | xxxxxx
+0123456789 | yyyyyyyyyyyyyy
+-[ RECORD 4 ]----+---------------------
+0123456789abcdef | xxxxxxxx
+0123456789 | yyyyyyyyyyyy
+-[ RECORD 5 ]----+---------------------
+0123456789abcdef | xxxxxxxxxx
+0123456789 | yyyyyyyyyy
+-[ RECORD 6 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxx
+0123456789 | yyyyyyyy
+-[ RECORD 7 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxxxx
+0123456789 | yyyyyy
+-[ RECORD 8 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxxxxxx
+0123456789 | yyyy
+-[ RECORD 9 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxxxxxxxx
+0123456789 | yy
+-[ RECORD 10 ]---+---------------------
+0123456789abcdef | xxxxxxxxxxxxxxxxxxxx
+0123456789 |
+
+\pset format wrapped
+execute q;
+-[ RECORD 1 ]----+---------------------
+0123456789abcdef | xx
+0123456789 | yyyyyyyyyyyyyyyyyy
+-[ RECORD 2 ]----+---------------------
+0123456789abcdef | xxxx
+0123456789 | yyyyyyyyyyyyyyyy
+-[ RECORD 3 ]----+---------------------
+0123456789abcdef | xxxxxx
+0123456789 | yyyyyyyyyyyyyy
+-[ RECORD 4 ]----+---------------------
+0123456789abcdef | xxxxxxxx
+0123456789 | yyyyyyyyyyyy
+-[ RECORD 5 ]----+---------------------
+0123456789abcdef | xxxxxxxxxx
+0123456789 | yyyyyyyyyy
+-[ RECORD 6 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxx
+0123456789 | yyyyyyyy
+-[ RECORD 7 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxxxx
+0123456789 | yyyyyy
+-[ RECORD 8 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxxxxxx
+0123456789 | yyyy
+-[ RECORD 9 ]----+---------------------
+0123456789abcdef | xxxxxxxxxxxxxxxxxx
+0123456789 | yy
+-[ RECORD 10 ]---+---------------------
+0123456789abcdef | xxxxxxxxxxxxxxxxxxxx
+0123456789 |
+
+\pset border 2
+\pset format unaligned
+execute q;
+0123456789abcdef|xx
+0123456789|yyyyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxx
+0123456789|yyyyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxx
+0123456789|yyyyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxx
+0123456789|yyyyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxx
+0123456789|yyyyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxx
+0123456789|yyyyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxx
+0123456789|yyyyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxx
+0123456789|yyyy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxx
+0123456789|yy
+
+0123456789abcdef|xxxxxxxxxxxxxxxxxxxx
+0123456789|
+\pset format aligned
+execute q;
++-[ RECORD 1 ]-----+----------------------+
+| 0123456789abcdef | xx |
+| 0123456789 | yyyyyyyyyyyyyyyyyy |
++-[ RECORD 2 ]-----+----------------------+
+| 0123456789abcdef | xxxx |
+| 0123456789 | yyyyyyyyyyyyyyyy |
++-[ RECORD 3 ]-----+----------------------+
+| 0123456789abcdef | xxxxxx |
+| 0123456789 | yyyyyyyyyyyyyy |
++-[ RECORD 4 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxx |
+| 0123456789 | yyyyyyyyyyyy |
++-[ RECORD 5 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxx |
+| 0123456789 | yyyyyyyyyy |
++-[ RECORD 6 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxx |
+| 0123456789 | yyyyyyyy |
++-[ RECORD 7 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxx |
+| 0123456789 | yyyyyy |
++-[ RECORD 8 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxxxx |
+| 0123456789 | yyyy |
++-[ RECORD 9 ]-----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxxxxxx |
+| 0123456789 | yy |
++-[ RECORD 10 ]----+----------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxxxxxxxx |
+| 0123456789 | |
++------------------+----------------------+
+
+\pset format wrapped
+execute q;
++-[ RECORD 1 ]-----+-------------------+
+| 0123456789abcdef | xx |
+| 0123456789 | yyyyyyyyyyyyyyyyy |
+| ; y |
++-[ RECORD 2 ]-----+-------------------+
+| 0123456789abcdef | xxxx |
+| 0123456789 | yyyyyyyyyyyyyyyy |
++-[ RECORD 3 ]-----+-------------------+
+| 0123456789abcdef | xxxxxx |
+| 0123456789 | yyyyyyyyyyyyyy |
++-[ RECORD 4 ]-----+-------------------+
+| 0123456789abcdef | xxxxxxxx |
+| 0123456789 | yyyyyyyyyyyy |
++-[ RECORD 5 ]-----+-------------------+
+| 0123456789abcdef | xxxxxxxxxx |
+| 0123456789 | yyyyyyyyyy |
++-[ RECORD 6 ]-----+-------------------+
+| 0123456789abcdef | xxxxxxxxxxxx |
+| 0123456789 | yyyyyyyy |
++-[ RECORD 7 ]-----+-------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxx |
+| 0123456789 | yyyyyy |
++-[ RECORD 8 ]-----+-------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxxxx |
+| 0123456789 | yyyy |
++-[ RECORD 9 ]-----+-------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxxxxx |
+| ; x |
+| 0123456789 | yy |
++-[ RECORD 10 ]----+-------------------+
+| 0123456789abcdef | xxxxxxxxxxxxxxxxx |
+| ; xxx |
+| 0123456789 | |
++------------------+-------------------+
+
+deallocate q;
+\pset linestyle ascii
+\pset border 1
+-- support table for output-format tests (useful to create a footer)
+create table psql_serial_tab (id serial);
+-- test header/footer/tuples_only behavior in aligned/unaligned/wrapped cases
+\pset format aligned
+\pset expanded off
+\d psql_serial_tab_id_seq
+ Sequence "public.psql_serial_tab_id_seq"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer | 1 | 1 | 2147483647 | 1 | no | 1
+Owned by: public.psql_serial_tab.id
+
+\pset tuples_only true
+\df exp
+ pg_catalog | exp | double precision | double precision | func
+ pg_catalog | exp | numeric | numeric | func
+
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+Sequence "public.psql_serial_tab_id_seq"
+-[ RECORD 1 ]---------
+Type | integer
+Start | 1
+Minimum | 1
+Maximum | 2147483647
+Increment | 1
+Cycles? | no
+Cache | 1
+
+Owned by: public.psql_serial_tab.id
+
+\pset tuples_only true
+\df exp
+Schema | pg_catalog
+Name | exp
+Result data type | double precision
+Argument data types | double precision
+Type | func
+--------------------+-----------------
+Schema | pg_catalog
+Name | exp
+Result data type | numeric
+Argument data types | numeric
+Type | func
+
+\pset tuples_only false
+-- empty table is a special case for this format
+select 1 where false;
+(0 rows)
+
+\pset format unaligned
+\pset expanded off
+\d psql_serial_tab_id_seq
+Sequence "public.psql_serial_tab_id_seq"
+Type|Start|Minimum|Maximum|Increment|Cycles?|Cache
+integer|1|1|2147483647|1|no|1
+Owned by: public.psql_serial_tab.id
+\pset tuples_only true
+\df exp
+pg_catalog|exp|double precision|double precision|func
+pg_catalog|exp|numeric|numeric|func
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+Sequence "public.psql_serial_tab_id_seq"
+
+Type|integer
+Start|1
+Minimum|1
+Maximum|2147483647
+Increment|1
+Cycles?|no
+Cache|1
+
+Owned by: public.psql_serial_tab.id
+\pset tuples_only true
+\df exp
+Schema|pg_catalog
+Name|exp
+Result data type|double precision
+Argument data types|double precision
+Type|func
+
+Schema|pg_catalog
+Name|exp
+Result data type|numeric
+Argument data types|numeric
+Type|func
+\pset tuples_only false
+\pset format wrapped
+\pset expanded off
+\d psql_serial_tab_id_seq
+ Sequence "public.psql_serial_tab_id_seq"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer | 1 | 1 | 2147483647 | 1 | no | 1
+Owned by: public.psql_serial_tab.id
+
+\pset tuples_only true
+\df exp
+ pg_catalog | exp | double precision | double precision | func
+ pg_catalog | exp | numeric | numeric | func
+
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+Sequence "public.psql_serial_tab_id_seq"
+-[ RECORD 1 ]---------
+Type | integer
+Start | 1
+Minimum | 1
+Maximum | 2147483647
+Increment | 1
+Cycles? | no
+Cache | 1
+
+Owned by: public.psql_serial_tab.id
+
+\pset tuples_only true
+\df exp
+Schema | pg_catalog
+Name | exp
+Result data type | double precision
+Argument data types | double precision
+Type | func
+--------------------+-----------------
+Schema | pg_catalog
+Name | exp
+Result data type | numeric
+Argument data types | numeric
+Type | func
+
+\pset tuples_only false
+-- check conditional am display
+\pset expanded off
+CREATE SCHEMA tableam_display;
+CREATE ROLE regress_display_role;
+ALTER SCHEMA tableam_display OWNER TO regress_display_role;
+SET search_path TO tableam_display;
+CREATE ACCESS METHOD heap_psql TYPE TABLE HANDLER heap_tableam_handler;
+SET ROLE TO regress_display_role;
+-- Use only relations with a physical size of zero.
+CREATE TABLE tbl_heap_psql(f1 int, f2 char(100)) using heap_psql;
+CREATE TABLE tbl_heap(f1 int, f2 char(100)) using heap;
+CREATE VIEW view_heap_psql AS SELECT f1 from tbl_heap_psql;
+CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tbl_heap_psql;
+\d+ tbl_heap_psql
+ Table "tableam_display.tbl_heap_psql"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+----------------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | | | plain | |
+ f2 | character(100) | | | | extended | |
+
+\d+ tbl_heap
+ Table "tableam_display.tbl_heap"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+----------------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | | | plain | |
+ f2 | character(100) | | | | extended | |
+
+\set HIDE_TABLEAM off
+\d+ tbl_heap_psql
+ Table "tableam_display.tbl_heap_psql"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+----------------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | | | plain | |
+ f2 | character(100) | | | | extended | |
+Access method: heap_psql
+
+\d+ tbl_heap
+ Table "tableam_display.tbl_heap"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+----------------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | | | plain | |
+ f2 | character(100) | | | | extended | |
+Access method: heap
+
+-- AM is displayed for tables, indexes and materialized views.
+\d+
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | | 0 bytes |
+(4 rows)
+
+\dt+
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Size | Description
+-----------------+---------------+-------+----------------------+-------------+---------------+---------+-------------
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
+(2 rows)
+
+\dm+
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
+(1 row)
+
+-- But not for views and sequences.
+\dv+
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Size | Description
+-----------------+----------------+------+----------------------+-------------+---------+-------------
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+(1 row)
+
+\set HIDE_TABLEAM on
+\d+
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+(4 rows)
+
+RESET ROLE;
+RESET search_path;
+DROP SCHEMA tableam_display CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table tableam_display.tbl_heap_psql
+drop cascades to table tableam_display.tbl_heap
+drop cascades to view tableam_display.view_heap_psql
+drop cascades to materialized view tableam_display.mat_view_heap_psql
+DROP ACCESS METHOD heap_psql;
+DROP ROLE regress_display_role;
+-- test numericlocale (as best we can without control of psql's locale)
+\pset format aligned
+\pset expanded off
+\pset numericlocale true
+select n, -n as m, n * 111 as x, '1e90'::float8 as f
+from generate_series(0,3) n;
+ n | m | x | f
+---+----+-----+-------
+ 0 | 0 | 0 | 1e+90
+ 1 | -1 | 111 | 1e+90
+ 2 | -2 | 222 | 1e+90
+ 3 | -3 | 333 | 1e+90
+(4 rows)
+
+\pset numericlocale false
+-- test asciidoc output format
+\pset format asciidoc
+\pset border 1
+\pset expanded off
+\d psql_serial_tab_id_seq
+
+.Sequence "public.psql_serial_tab_id_seq"
+[options="header",cols="<l,>l,>l,>l,>l,<l,>l",frame="none"]
+|====
+^l|Type ^l|Start ^l|Minimum ^l|Maximum ^l|Increment ^l|Cycles? ^l|Cache
+|integer |1 |1 |2147483647 |1 |no |1
+|====
+
+....
+Owned by: public.psql_serial_tab.id
+....
+\pset tuples_only true
+\df exp
+
+[cols="<l,<l,<l,<l,<l",frame="none"]
+|====
+|pg_catalog |exp |double precision |double precision |func
+|pg_catalog |exp |numeric |numeric |func
+|====
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+
+.Sequence "public.psql_serial_tab_id_seq"
+[cols="h,l",frame="none"]
+|====
+2+^|Record 1
+<l|Type <l|integer
+<l|Start >l|1
+<l|Minimum >l|1
+<l|Maximum >l|2147483647
+<l|Increment >l|1
+<l|Cycles? <l|no
+<l|Cache >l|1
+|====
+
+....
+Owned by: public.psql_serial_tab.id
+....
+\pset tuples_only true
+\df exp
+
+[cols="h,l",frame="none"]
+|====
+2+|
+<l|Schema <l|pg_catalog
+<l|Name <l|exp
+<l|Result data type <l|double precision
+<l|Argument data types <l|double precision
+<l|Type <l|func
+2+|
+<l|Schema <l|pg_catalog
+<l|Name <l|exp
+<l|Result data type <l|numeric
+<l|Argument data types <l|numeric
+<l|Type <l|func
+|====
+\pset tuples_only false
+prepare q as
+ select 'some|text' as "a|title", ' ' as "empty ", n as int
+ from generate_series(1,2) as n;
+\pset expanded off
+\pset border 0
+execute q;
+
+[options="header",cols="<l,<l,>l",frame="none",grid="none"]
+|====
+^l|a\|title ^l|empty ^l|int
+|some\|text | |1
+|some\|text | |2
+|====
+
+....
+(2 rows)
+....
+\pset border 1
+execute q;
+
+[options="header",cols="<l,<l,>l",frame="none"]
+|====
+^l|a\|title ^l|empty ^l|int
+|some\|text | |1
+|some\|text | |2
+|====
+
+....
+(2 rows)
+....
+\pset border 2
+execute q;
+
+[options="header",cols="<l,<l,>l",frame="all",grid="all"]
+|====
+^l|a\|title ^l|empty ^l|int
+|some\|text | |1
+|some\|text | |2
+|====
+
+....
+(2 rows)
+....
+\pset expanded on
+\pset border 0
+execute q;
+
+[cols="h,l",frame="none",grid="none"]
+|====
+2+^|Record 1
+<l|a\|title <l|some\|text
+<l|empty <l|
+<l|int >l|1
+2+^|Record 2
+<l|a\|title <l|some\|text
+<l|empty <l|
+<l|int >l|2
+|====
+\pset border 1
+execute q;
+
+[cols="h,l",frame="none"]
+|====
+2+^|Record 1
+<l|a\|title <l|some\|text
+<l|empty <l|
+<l|int >l|1
+2+^|Record 2
+<l|a\|title <l|some\|text
+<l|empty <l|
+<l|int >l|2
+|====
+\pset border 2
+execute q;
+
+[cols="h,l",frame="all",grid="all"]
+|====
+2+^|Record 1
+<l|a\|title <l|some\|text
+<l|empty <l|
+<l|int >l|1
+2+^|Record 2
+<l|a\|title <l|some\|text
+<l|empty <l|
+<l|int >l|2
+|====
+deallocate q;
+-- test csv output format
+\pset format csv
+\pset border 1
+\pset expanded off
+\d psql_serial_tab_id_seq
+Type,Start,Minimum,Maximum,Increment,Cycles?,Cache
+integer,1,1,2147483647,1,no,1
+\pset tuples_only true
+\df exp
+pg_catalog,exp,double precision,double precision,func
+pg_catalog,exp,numeric,numeric,func
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+Type,integer
+Start,1
+Minimum,1
+Maximum,2147483647
+Increment,1
+Cycles?,no
+Cache,1
+\pset tuples_only true
+\df exp
+Schema,pg_catalog
+Name,exp
+Result data type,double precision
+Argument data types,double precision
+Type,func
+Schema,pg_catalog
+Name,exp
+Result data type,numeric
+Argument data types,numeric
+Type,func
+\pset tuples_only false
+prepare q as
+ select 'some"text' as "a""title", E' <foo>\n<bar>' as "junk",
+ ' ' as "empty", n as int
+ from generate_series(1,2) as n;
+\pset expanded off
+execute q;
+"a""title",junk,empty,int
+"some""text"," <foo>
+<bar>", ,1
+"some""text"," <foo>
+<bar>", ,2
+\pset expanded on
+execute q;
+"a""title","some""text"
+junk," <foo>
+<bar>"
+empty,
+int,1
+"a""title","some""text"
+junk," <foo>
+<bar>"
+empty,
+int,2
+deallocate q;
+-- special cases
+\pset expanded off
+select 'comma,comma' as comma, 'semi;semi' as semi;
+comma,semi
+"comma,comma",semi;semi
+\pset csv_fieldsep ';'
+select 'comma,comma' as comma, 'semi;semi' as semi;
+comma;semi
+comma,comma;"semi;semi"
+select '\.' as data;
+data
+"\."
+\pset csv_fieldsep '.'
+select '\' as d1, '' as d2;
+"d1"."d2"
+"\".""
+-- illegal csv separators
+\pset csv_fieldsep ''
+\pset: csv_fieldsep must be a single one-byte character
+\pset csv_fieldsep '\0'
+\pset: csv_fieldsep must be a single one-byte character
+\pset csv_fieldsep '\n'
+\pset: csv_fieldsep cannot be a double quote, a newline, or a carriage return
+\pset csv_fieldsep '\r'
+\pset: csv_fieldsep cannot be a double quote, a newline, or a carriage return
+\pset csv_fieldsep '"'
+\pset: csv_fieldsep cannot be a double quote, a newline, or a carriage return
+\pset csv_fieldsep ',,'
+\pset: csv_fieldsep must be a single one-byte character
+\pset csv_fieldsep ','
+-- test html output format
+\pset format html
+\pset border 1
+\pset expanded off
+\d psql_serial_tab_id_seq
+<table border="1">
+ <caption>Sequence &quot;public.psql_serial_tab_id_seq&quot;</caption>
+ <tr>
+ <th align="center">Type</th>
+ <th align="center">Start</th>
+ <th align="center">Minimum</th>
+ <th align="center">Maximum</th>
+ <th align="center">Increment</th>
+ <th align="center">Cycles?</th>
+ <th align="center">Cache</th>
+ </tr>
+ <tr valign="top">
+ <td align="left">integer</td>
+ <td align="right">1</td>
+ <td align="right">1</td>
+ <td align="right">2147483647</td>
+ <td align="right">1</td>
+ <td align="left">no</td>
+ <td align="right">1</td>
+ </tr>
+</table>
+<p>Owned by: public.psql_serial_tab.id<br />
+</p>
+\pset tuples_only true
+\df exp
+<table border="1">
+ <tr valign="top">
+ <td align="left">pg_catalog</td>
+ <td align="left">exp</td>
+ <td align="left">double precision</td>
+ <td align="left">double precision</td>
+ <td align="left">func</td>
+ </tr>
+ <tr valign="top">
+ <td align="left">pg_catalog</td>
+ <td align="left">exp</td>
+ <td align="left">numeric</td>
+ <td align="left">numeric</td>
+ <td align="left">func</td>
+ </tr>
+</table>
+
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+<table border="1">
+ <caption>Sequence &quot;public.psql_serial_tab_id_seq&quot;</caption>
+
+ <tr><td colspan="2" align="center">Record 1</td></tr>
+ <tr valign="top">
+ <th>Type</th>
+ <td align="left">integer</td>
+ </tr>
+ <tr valign="top">
+ <th>Start</th>
+ <td align="right">1</td>
+ </tr>
+ <tr valign="top">
+ <th>Minimum</th>
+ <td align="right">1</td>
+ </tr>
+ <tr valign="top">
+ <th>Maximum</th>
+ <td align="right">2147483647</td>
+ </tr>
+ <tr valign="top">
+ <th>Increment</th>
+ <td align="right">1</td>
+ </tr>
+ <tr valign="top">
+ <th>Cycles?</th>
+ <td align="left">no</td>
+ </tr>
+ <tr valign="top">
+ <th>Cache</th>
+ <td align="right">1</td>
+ </tr>
+</table>
+<p>Owned by: public.psql_serial_tab.id<br />
+</p>
+\pset tuples_only true
+\df exp
+<table border="1">
+
+ <tr><td colspan="2">&nbsp;</td></tr>
+ <tr valign="top">
+ <th>Schema</th>
+ <td align="left">pg_catalog</td>
+ </tr>
+ <tr valign="top">
+ <th>Name</th>
+ <td align="left">exp</td>
+ </tr>
+ <tr valign="top">
+ <th>Result data type</th>
+ <td align="left">double precision</td>
+ </tr>
+ <tr valign="top">
+ <th>Argument data types</th>
+ <td align="left">double precision</td>
+ </tr>
+ <tr valign="top">
+ <th>Type</th>
+ <td align="left">func</td>
+ </tr>
+
+ <tr><td colspan="2">&nbsp;</td></tr>
+ <tr valign="top">
+ <th>Schema</th>
+ <td align="left">pg_catalog</td>
+ </tr>
+ <tr valign="top">
+ <th>Name</th>
+ <td align="left">exp</td>
+ </tr>
+ <tr valign="top">
+ <th>Result data type</th>
+ <td align="left">numeric</td>
+ </tr>
+ <tr valign="top">
+ <th>Argument data types</th>
+ <td align="left">numeric</td>
+ </tr>
+ <tr valign="top">
+ <th>Type</th>
+ <td align="left">func</td>
+ </tr>
+</table>
+
+\pset tuples_only false
+prepare q as
+ select 'some"text' as "a&title", E' <foo>\n<bar>' as "junk",
+ ' ' as "empty", n as int
+ from generate_series(1,2) as n;
+\pset expanded off
+\pset border 0
+execute q;
+<table border="0">
+ <tr>
+ <th align="center">a&amp;title</th>
+ <th align="center">junk</th>
+ <th align="center">empty</th>
+ <th align="center">int</th>
+ </tr>
+ <tr valign="top">
+ <td align="left">some&quot;text</td>
+ <td align="left">&nbsp;&nbsp;&lt;foo&gt;<br />
+&lt;bar&gt;</td>
+ <td align="left">&nbsp; </td>
+ <td align="right">1</td>
+ </tr>
+ <tr valign="top">
+ <td align="left">some&quot;text</td>
+ <td align="left">&nbsp;&nbsp;&lt;foo&gt;<br />
+&lt;bar&gt;</td>
+ <td align="left">&nbsp; </td>
+ <td align="right">2</td>
+ </tr>
+</table>
+<p>(2 rows)<br />
+</p>
+\pset border 1
+execute q;
+<table border="1">
+ <tr>
+ <th align="center">a&amp;title</th>
+ <th align="center">junk</th>
+ <th align="center">empty</th>
+ <th align="center">int</th>
+ </tr>
+ <tr valign="top">
+ <td align="left">some&quot;text</td>
+ <td align="left">&nbsp;&nbsp;&lt;foo&gt;<br />
+&lt;bar&gt;</td>
+ <td align="left">&nbsp; </td>
+ <td align="right">1</td>
+ </tr>
+ <tr valign="top">
+ <td align="left">some&quot;text</td>
+ <td align="left">&nbsp;&nbsp;&lt;foo&gt;<br />
+&lt;bar&gt;</td>
+ <td align="left">&nbsp; </td>
+ <td align="right">2</td>
+ </tr>
+</table>
+<p>(2 rows)<br />
+</p>
+\pset tableattr foobar
+execute q;
+<table border="1" foobar>
+ <tr>
+ <th align="center">a&amp;title</th>
+ <th align="center">junk</th>
+ <th align="center">empty</th>
+ <th align="center">int</th>
+ </tr>
+ <tr valign="top">
+ <td align="left">some&quot;text</td>
+ <td align="left">&nbsp;&nbsp;&lt;foo&gt;<br />
+&lt;bar&gt;</td>
+ <td align="left">&nbsp; </td>
+ <td align="right">1</td>
+ </tr>
+ <tr valign="top">
+ <td align="left">some&quot;text</td>
+ <td align="left">&nbsp;&nbsp;&lt;foo&gt;<br />
+&lt;bar&gt;</td>
+ <td align="left">&nbsp; </td>
+ <td align="right">2</td>
+ </tr>
+</table>
+<p>(2 rows)<br />
+</p>
+\pset tableattr
+\pset expanded on
+\pset border 0
+execute q;
+<table border="0">
+
+ <tr><td colspan="2" align="center">Record 1</td></tr>
+ <tr valign="top">
+ <th>a&amp;title</th>
+ <td align="left">some&quot;text</td>
+ </tr>
+ <tr valign="top">
+ <th>junk</th>
+ <td align="left">&nbsp;&nbsp;&lt;foo&gt;<br />
+&lt;bar&gt;</td>
+ </tr>
+ <tr valign="top">
+ <th>empty</th>
+ <td align="left">&nbsp; </td>
+ </tr>
+ <tr valign="top">
+ <th>int</th>
+ <td align="right">1</td>
+ </tr>
+
+ <tr><td colspan="2" align="center">Record 2</td></tr>
+ <tr valign="top">
+ <th>a&amp;title</th>
+ <td align="left">some&quot;text</td>
+ </tr>
+ <tr valign="top">
+ <th>junk</th>
+ <td align="left">&nbsp;&nbsp;&lt;foo&gt;<br />
+&lt;bar&gt;</td>
+ </tr>
+ <tr valign="top">
+ <th>empty</th>
+ <td align="left">&nbsp; </td>
+ </tr>
+ <tr valign="top">
+ <th>int</th>
+ <td align="right">2</td>
+ </tr>
+</table>
+
+\pset border 1
+execute q;
+<table border="1">
+
+ <tr><td colspan="2" align="center">Record 1</td></tr>
+ <tr valign="top">
+ <th>a&amp;title</th>
+ <td align="left">some&quot;text</td>
+ </tr>
+ <tr valign="top">
+ <th>junk</th>
+ <td align="left">&nbsp;&nbsp;&lt;foo&gt;<br />
+&lt;bar&gt;</td>
+ </tr>
+ <tr valign="top">
+ <th>empty</th>
+ <td align="left">&nbsp; </td>
+ </tr>
+ <tr valign="top">
+ <th>int</th>
+ <td align="right">1</td>
+ </tr>
+
+ <tr><td colspan="2" align="center">Record 2</td></tr>
+ <tr valign="top">
+ <th>a&amp;title</th>
+ <td align="left">some&quot;text</td>
+ </tr>
+ <tr valign="top">
+ <th>junk</th>
+ <td align="left">&nbsp;&nbsp;&lt;foo&gt;<br />
+&lt;bar&gt;</td>
+ </tr>
+ <tr valign="top">
+ <th>empty</th>
+ <td align="left">&nbsp; </td>
+ </tr>
+ <tr valign="top">
+ <th>int</th>
+ <td align="right">2</td>
+ </tr>
+</table>
+
+\pset tableattr foobar
+execute q;
+<table border="1" foobar>
+
+ <tr><td colspan="2" align="center">Record 1</td></tr>
+ <tr valign="top">
+ <th>a&amp;title</th>
+ <td align="left">some&quot;text</td>
+ </tr>
+ <tr valign="top">
+ <th>junk</th>
+ <td align="left">&nbsp;&nbsp;&lt;foo&gt;<br />
+&lt;bar&gt;</td>
+ </tr>
+ <tr valign="top">
+ <th>empty</th>
+ <td align="left">&nbsp; </td>
+ </tr>
+ <tr valign="top">
+ <th>int</th>
+ <td align="right">1</td>
+ </tr>
+
+ <tr><td colspan="2" align="center">Record 2</td></tr>
+ <tr valign="top">
+ <th>a&amp;title</th>
+ <td align="left">some&quot;text</td>
+ </tr>
+ <tr valign="top">
+ <th>junk</th>
+ <td align="left">&nbsp;&nbsp;&lt;foo&gt;<br />
+&lt;bar&gt;</td>
+ </tr>
+ <tr valign="top">
+ <th>empty</th>
+ <td align="left">&nbsp; </td>
+ </tr>
+ <tr valign="top">
+ <th>int</th>
+ <td align="right">2</td>
+ </tr>
+</table>
+
+\pset tableattr
+deallocate q;
+-- test latex output format
+\pset format latex
+\pset border 1
+\pset expanded off
+\d psql_serial_tab_id_seq
+\begin{center}
+Sequence "public.psql\_serial\_tab\_id\_seq"
+\end{center}
+
+\begin{tabular}{l | r | r | r | r | l | r}
+\textit{Type} & \textit{Start} & \textit{Minimum} & \textit{Maximum} & \textit{Increment} & \textit{Cycles?} & \textit{Cache} \\
+\hline
+integer & 1 & 1 & 2147483647 & 1 & no & 1 \\
+\end{tabular}
+
+\noindent Owned by: public.psql\_serial\_tab.id \\
+
+\pset tuples_only true
+\df exp
+\begin{tabular}{l | l | l | l | l}
+pg\_catalog & exp & double precision & double precision & func \\
+pg\_catalog & exp & numeric & numeric & func \\
+\end{tabular}
+
+\noindent
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+\begin{center}
+Sequence "public.psql\_serial\_tab\_id\_seq"
+\end{center}
+
+\begin{tabular}{c|l}
+\multicolumn{2}{c}{\textit{Record 1}} \\
+\hline
+Type & integer \\
+Start & 1 \\
+Minimum & 1 \\
+Maximum & 2147483647 \\
+Increment & 1 \\
+Cycles? & no \\
+Cache & 1 \\
+\end{tabular}
+
+\noindent Owned by: public.psql\_serial\_tab.id \\
+
+\pset tuples_only true
+\df exp
+\begin{tabular}{c|l}
+\hline
+Schema & pg\_catalog \\
+Name & exp \\
+Result data type & double precision \\
+Argument data types & double precision \\
+Type & func \\
+\hline
+Schema & pg\_catalog \\
+Name & exp \\
+Result data type & numeric \\
+Argument data types & numeric \\
+Type & func \\
+\end{tabular}
+
+\noindent
+\pset tuples_only false
+prepare q as
+ select 'some\more_text' as "a$title", E' #<foo>%&^~|\n{bar}' as "junk",
+ ' ' as "empty", n as int
+ from generate_series(1,2) as n;
+\pset expanded off
+\pset border 0
+execute q;
+\begin{tabular}{lllr}
+\textit{a\$title} & \textit{junk} & \textit{empty} & \textit{int} \\
+\hline
+some\textbackslash{}more\_text & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} & & 1 \\
+some\textbackslash{}more\_text & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} & & 2 \\
+\end{tabular}
+
+\noindent (2 rows) \\
+
+\pset border 1
+execute q;
+\begin{tabular}{l | l | l | r}
+\textit{a\$title} & \textit{junk} & \textit{empty} & \textit{int} \\
+\hline
+some\textbackslash{}more\_text & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} & & 1 \\
+some\textbackslash{}more\_text & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} & & 2 \\
+\end{tabular}
+
+\noindent (2 rows) \\
+
+\pset border 2
+execute q;
+\begin{tabular}{| l | l | l | r |}
+\hline
+\textit{a\$title} & \textit{junk} & \textit{empty} & \textit{int} \\
+\hline
+some\textbackslash{}more\_text & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} & & 1 \\
+some\textbackslash{}more\_text & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} & & 2 \\
+\hline
+\end{tabular}
+
+\noindent (2 rows) \\
+
+\pset border 3
+execute q;
+\begin{tabular}{| l | l | l | r |}
+\hline
+\textit{a\$title} & \textit{junk} & \textit{empty} & \textit{int} \\
+\hline
+some\textbackslash{}more\_text & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} & & 1 \\
+\hline
+some\textbackslash{}more\_text & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} & & 2 \\
+\hline
+\end{tabular}
+
+\noindent (2 rows) \\
+
+\pset expanded on
+\pset border 0
+execute q;
+\begin{tabular}{cl}
+\multicolumn{2}{c}{\textit{Record 1}} \\
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 1 \\
+\multicolumn{2}{c}{\textit{Record 2}} \\
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 2 \\
+\end{tabular}
+
+\noindent
+\pset border 1
+execute q;
+\begin{tabular}{c|l}
+\multicolumn{2}{c}{\textit{Record 1}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 1 \\
+\multicolumn{2}{c}{\textit{Record 2}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 2 \\
+\end{tabular}
+
+\noindent
+\pset border 2
+execute q;
+\begin{tabular}{|c|l|}
+\hline
+\multicolumn{2}{|c|}{\textit{Record 1}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 1 \\
+\hline
+\multicolumn{2}{|c|}{\textit{Record 2}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 2 \\
+\hline
+\end{tabular}
+
+\noindent
+\pset border 3
+execute q;
+\begin{tabular}{|c|l|}
+\hline
+\multicolumn{2}{|c|}{\textit{Record 1}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 1 \\
+\hline
+\multicolumn{2}{|c|}{\textit{Record 2}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 2 \\
+\hline
+\end{tabular}
+
+\noindent
+deallocate q;
+-- test latex-longtable output format
+\pset format latex-longtable
+\pset border 1
+\pset expanded off
+\d psql_serial_tab_id_seq
+\begin{longtable}{l | r | r | r | r | l | r}
+\small\textbf{\textit{Type}} & \small\textbf{\textit{Start}} & \small\textbf{\textit{Minimum}} & \small\textbf{\textit{Maximum}} & \small\textbf{\textit{Increment}} & \small\textbf{\textit{Cycles?}} & \small\textbf{\textit{Cache}} \\
+\midrule
+\endfirsthead
+\small\textbf{\textit{Type}} & \small\textbf{\textit{Start}} & \small\textbf{\textit{Minimum}} & \small\textbf{\textit{Maximum}} & \small\textbf{\textit{Increment}} & \small\textbf{\textit{Cycles?}} & \small\textbf{\textit{Cache}} \\
+\midrule
+\endhead
+\caption[Sequence "public.psql\_serial\_tab\_id\_seq" (Continued)]{Sequence "public.psql\_serial\_tab\_id\_seq"}
+\endfoot
+\caption[Sequence "public.psql\_serial\_tab\_id\_seq"]{Sequence "public.psql\_serial\_tab\_id\_seq"}
+\endlastfoot
+\raggedright{integer}
+&
+\raggedright{1}
+&
+\raggedright{1}
+&
+\raggedright{2147483647}
+&
+\raggedright{1}
+&
+\raggedright{no}
+&
+\raggedright{1} \tabularnewline
+\end{longtable}
+\pset tuples_only true
+\df exp
+\begin{longtable}{l | l | l | l | l}
+\raggedright{pg\_catalog}
+&
+\raggedright{exp}
+&
+\raggedright{double precision}
+&
+\raggedright{double precision}
+&
+\raggedright{func} \tabularnewline
+\raggedright{pg\_catalog}
+&
+\raggedright{exp}
+&
+\raggedright{numeric}
+&
+\raggedright{numeric}
+&
+\raggedright{func} \tabularnewline
+\end{longtable}
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+\begin{center}
+Sequence "public.psql\_serial\_tab\_id\_seq"
+\end{center}
+
+\begin{tabular}{c|l}
+\multicolumn{2}{c}{\textit{Record 1}} \\
+\hline
+Type & integer \\
+Start & 1 \\
+Minimum & 1 \\
+Maximum & 2147483647 \\
+Increment & 1 \\
+Cycles? & no \\
+Cache & 1 \\
+\end{tabular}
+
+\noindent Owned by: public.psql\_serial\_tab.id \\
+
+\pset tuples_only true
+\df exp
+\begin{tabular}{c|l}
+\hline
+Schema & pg\_catalog \\
+Name & exp \\
+Result data type & double precision \\
+Argument data types & double precision \\
+Type & func \\
+\hline
+Schema & pg\_catalog \\
+Name & exp \\
+Result data type & numeric \\
+Argument data types & numeric \\
+Type & func \\
+\end{tabular}
+
+\noindent
+\pset tuples_only false
+prepare q as
+ select 'some\more_text' as "a$title", E' #<foo>%&^~|\n{bar}' as "junk",
+ ' ' as "empty", n as int
+ from generate_series(1,2) as n;
+\pset expanded off
+\pset border 0
+execute q;
+\begin{longtable}{lllr}
+\small\textbf{\textit{a\$title}} & \small\textbf{\textit{junk}} & \small\textbf{\textit{empty}} & \small\textbf{\textit{int}} \\
+\midrule
+\endfirsthead
+\small\textbf{\textit{a\$title}} & \small\textbf{\textit{junk}} & \small\textbf{\textit{empty}} & \small\textbf{\textit{int}} \\
+\midrule
+\endhead
+\raggedright{some\textbackslash{}more\_text}
+&
+\raggedright{ \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\}}
+&
+\raggedright{ }
+&
+\raggedright{1} \tabularnewline
+\raggedright{some\textbackslash{}more\_text}
+&
+\raggedright{ \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\}}
+&
+\raggedright{ }
+&
+\raggedright{2} \tabularnewline
+\end{longtable}
+\pset border 1
+execute q;
+\begin{longtable}{l | l | l | r}
+\small\textbf{\textit{a\$title}} & \small\textbf{\textit{junk}} & \small\textbf{\textit{empty}} & \small\textbf{\textit{int}} \\
+\midrule
+\endfirsthead
+\small\textbf{\textit{a\$title}} & \small\textbf{\textit{junk}} & \small\textbf{\textit{empty}} & \small\textbf{\textit{int}} \\
+\midrule
+\endhead
+\raggedright{some\textbackslash{}more\_text}
+&
+\raggedright{ \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\}}
+&
+\raggedright{ }
+&
+\raggedright{1} \tabularnewline
+\raggedright{some\textbackslash{}more\_text}
+&
+\raggedright{ \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\}}
+&
+\raggedright{ }
+&
+\raggedright{2} \tabularnewline
+\end{longtable}
+\pset border 2
+execute q;
+\begin{longtable}{| l | l | l | r |}
+\toprule
+\small\textbf{\textit{a\$title}} & \small\textbf{\textit{junk}} & \small\textbf{\textit{empty}} & \small\textbf{\textit{int}} \\
+\midrule
+\endfirsthead
+\toprule
+\small\textbf{\textit{a\$title}} & \small\textbf{\textit{junk}} & \small\textbf{\textit{empty}} & \small\textbf{\textit{int}} \\
+\midrule
+\endhead
+\bottomrule
+\endfoot
+\bottomrule
+\endlastfoot
+\raggedright{some\textbackslash{}more\_text}
+&
+\raggedright{ \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\}}
+&
+\raggedright{ }
+&
+\raggedright{1} \tabularnewline
+\raggedright{some\textbackslash{}more\_text}
+&
+\raggedright{ \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\}}
+&
+\raggedright{ }
+&
+\raggedright{2} \tabularnewline
+\end{longtable}
+\pset border 3
+execute q;
+\begin{longtable}{| l | l | l | r |}
+\toprule
+\small\textbf{\textit{a\$title}} & \small\textbf{\textit{junk}} & \small\textbf{\textit{empty}} & \small\textbf{\textit{int}} \\
+\midrule
+\endfirsthead
+\toprule
+\small\textbf{\textit{a\$title}} & \small\textbf{\textit{junk}} & \small\textbf{\textit{empty}} & \small\textbf{\textit{int}} \\
+\endhead
+\bottomrule
+\endfoot
+\bottomrule
+\endlastfoot
+\raggedright{some\textbackslash{}more\_text}
+&
+\raggedright{ \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\}}
+&
+\raggedright{ }
+&
+\raggedright{1} \tabularnewline
+ \hline
+\raggedright{some\textbackslash{}more\_text}
+&
+\raggedright{ \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\}}
+&
+\raggedright{ }
+&
+\raggedright{2} \tabularnewline
+ \hline
+\end{longtable}
+\pset tableattr lr
+execute q;
+\begin{longtable}{| p{lr\textwidth} | p{lr\textwidth} | p{lr\textwidth} | r |}
+\toprule
+\small\textbf{\textit{a\$title}} & \small\textbf{\textit{junk}} & \small\textbf{\textit{empty}} & \small\textbf{\textit{int}} \\
+\midrule
+\endfirsthead
+\toprule
+\small\textbf{\textit{a\$title}} & \small\textbf{\textit{junk}} & \small\textbf{\textit{empty}} & \small\textbf{\textit{int}} \\
+\endhead
+\bottomrule
+\endfoot
+\bottomrule
+\endlastfoot
+\raggedright{some\textbackslash{}more\_text}
+&
+\raggedright{ \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\}}
+&
+\raggedright{ }
+&
+\raggedright{1} \tabularnewline
+ \hline
+\raggedright{some\textbackslash{}more\_text}
+&
+\raggedright{ \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\}}
+&
+\raggedright{ }
+&
+\raggedright{2} \tabularnewline
+ \hline
+\end{longtable}
+\pset tableattr
+\pset expanded on
+\pset border 0
+execute q;
+\begin{tabular}{cl}
+\multicolumn{2}{c}{\textit{Record 1}} \\
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 1 \\
+\multicolumn{2}{c}{\textit{Record 2}} \\
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 2 \\
+\end{tabular}
+
+\noindent
+\pset border 1
+execute q;
+\begin{tabular}{c|l}
+\multicolumn{2}{c}{\textit{Record 1}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 1 \\
+\multicolumn{2}{c}{\textit{Record 2}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 2 \\
+\end{tabular}
+
+\noindent
+\pset border 2
+execute q;
+\begin{tabular}{|c|l|}
+\hline
+\multicolumn{2}{|c|}{\textit{Record 1}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 1 \\
+\hline
+\multicolumn{2}{|c|}{\textit{Record 2}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 2 \\
+\hline
+\end{tabular}
+
+\noindent
+\pset border 3
+execute q;
+\begin{tabular}{|c|l|}
+\hline
+\multicolumn{2}{|c|}{\textit{Record 1}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 1 \\
+\hline
+\multicolumn{2}{|c|}{\textit{Record 2}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 2 \\
+\hline
+\end{tabular}
+
+\noindent
+\pset tableattr lr
+execute q;
+\begin{tabular}{|c|l|}
+\hline
+\multicolumn{2}{|c|}{\textit{Record 1}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 1 \\
+\hline
+\multicolumn{2}{|c|}{\textit{Record 2}} \\
+\hline
+a\$title & some\textbackslash{}more\_text \\
+junk & \#\textless{}foo\textgreater{}\%\&\^{}\~{}\textbar{}\\\{bar\} \\
+empty & \\
+int & 2 \\
+\hline
+\end{tabular}
+
+\noindent
+\pset tableattr
+deallocate q;
+-- test troff-ms output format
+\pset format troff-ms
+\pset border 1
+\pset expanded off
+\d psql_serial_tab_id_seq
+.LP
+.DS C
+Sequence "public.psql_serial_tab_id_seq"
+.DE
+.LP
+.TS
+center;
+l | r | r | r | r | l | r.
+\fIType\fP \fIStart\fP \fIMinimum\fP \fIMaximum\fP \fIIncrement\fP \fICycles?\fP \fICache\fP
+_
+integer 1 1 2147483647 1 no 1
+.TE
+.DS L
+Owned by: public.psql_serial_tab.id
+.DE
+\pset tuples_only true
+\df exp
+.LP
+.TS
+center;
+l | l | l | l | l.
+pg_catalog exp double precision double precision func
+pg_catalog exp numeric numeric func
+.TE
+.DS L
+.DE
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+.LP
+.DS C
+Sequence "public.psql_serial_tab_id_seq"
+.DE
+.LP
+.TS
+center;
+c s.
+\fIRecord 1\fP
+_
+.T&
+c | l.
+Type integer
+Start 1
+Minimum 1
+Maximum 2147483647
+Increment 1
+Cycles? no
+Cache 1
+.TE
+.DS L
+Owned by: public.psql_serial_tab.id
+.DE
+\pset tuples_only true
+\df exp
+.LP
+.TS
+center;
+c l;
+_
+Schema pg_catalog
+Name exp
+Result data type double precision
+Argument data types double precision
+Type func
+_
+Schema pg_catalog
+Name exp
+Result data type numeric
+Argument data types numeric
+Type func
+.TE
+.DS L
+.DE
+\pset tuples_only false
+prepare q as
+ select 'some\text' as "a\title", E' <foo>\n<bar>' as "junk",
+ ' ' as "empty", n as int
+ from generate_series(1,2) as n;
+\pset expanded off
+\pset border 0
+execute q;
+.LP
+.TS
+center;
+lllr.
+\fIa\(rstitle\fP \fIjunk\fP \fIempty\fP \fIint\fP
+_
+some\(rstext <foo>
+<bar> 1
+some\(rstext <foo>
+<bar> 2
+.TE
+.DS L
+(2 rows)
+.DE
+\pset border 1
+execute q;
+.LP
+.TS
+center;
+l | l | l | r.
+\fIa\(rstitle\fP \fIjunk\fP \fIempty\fP \fIint\fP
+_
+some\(rstext <foo>
+<bar> 1
+some\(rstext <foo>
+<bar> 2
+.TE
+.DS L
+(2 rows)
+.DE
+\pset border 2
+execute q;
+.LP
+.TS
+center box;
+l | l | l | r.
+\fIa\(rstitle\fP \fIjunk\fP \fIempty\fP \fIint\fP
+_
+some\(rstext <foo>
+<bar> 1
+some\(rstext <foo>
+<bar> 2
+.TE
+.DS L
+(2 rows)
+.DE
+\pset expanded on
+\pset border 0
+execute q;
+.LP
+.TS
+center;
+c s.
+\fIRecord 1\fP
+.T&
+c l.
+a\(rstitle some\(rstext
+junk <foo>
+<bar>
+empty
+int 1
+.T&
+c s.
+\fIRecord 2\fP
+.T&
+c l.
+a\(rstitle some\(rstext
+junk <foo>
+<bar>
+empty
+int 2
+.TE
+.DS L
+.DE
+\pset border 1
+execute q;
+.LP
+.TS
+center;
+c s.
+\fIRecord 1\fP
+_
+.T&
+c | l.
+a\(rstitle some\(rstext
+junk <foo>
+<bar>
+empty
+int 1
+.T&
+c s.
+\fIRecord 2\fP
+_
+.T&
+c | l.
+a\(rstitle some\(rstext
+junk <foo>
+<bar>
+empty
+int 2
+.TE
+.DS L
+.DE
+\pset border 2
+execute q;
+.LP
+.TS
+center box;
+c s.
+\fIRecord 1\fP
+_
+.T&
+c l.
+a\(rstitle some\(rstext
+junk <foo>
+<bar>
+empty
+int 1
+_
+.T&
+c s.
+\fIRecord 2\fP
+_
+.T&
+c l.
+a\(rstitle some\(rstext
+junk <foo>
+<bar>
+empty
+int 2
+.TE
+.DS L
+.DE
+deallocate q;
+-- check ambiguous format requests
+\pset format a
+\pset: ambiguous abbreviation "a" matches both "aligned" and "asciidoc"
+\pset format l
+-- clean up after output format tests
+drop table psql_serial_tab;
+\pset format aligned
+\pset expanded off
+\pset border 1
+-- \echo and allied features
+\echo this is a test
+this is a test
+\echo -n without newline
+without newline\echo with -n newline
+with -n newline
+\echo '-n' with newline
+-n with newline
+\set foo bar
+\echo foo = :foo
+foo = bar
+\qecho this is a test
+this is a test
+\qecho foo = :foo
+foo = bar
+\warn this is a test
+this is a test
+\warn foo = :foo
+foo = bar
+-- tests for \if ... \endif
+\if true
+ select 'okay';
+ ?column?
+----------
+ okay
+(1 row)
+
+ select 'still okay';
+ ?column?
+------------
+ still okay
+(1 row)
+
+\else
+ not okay;
+ still not okay
+\endif
+-- at this point query buffer should still have last valid line
+\g
+ ?column?
+------------
+ still okay
+(1 row)
+
+-- \if should work okay on part of a query
+select
+ \if true
+ 42
+ \else
+ (bogus
+ \endif
+ forty_two;
+ forty_two
+-----------
+ 42
+(1 row)
+
+select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
+ forty_two
+-----------
+ 42
+(1 row)
+
+-- test a large nested if using a variety of true-equivalents
+\if true
+ \if 1
+ \if yes
+ \if on
+ \echo 'all true'
+all true
+ \else
+ \echo 'should not print #1-1'
+ \endif
+ \else
+ \echo 'should not print #1-2'
+ \endif
+ \else
+ \echo 'should not print #1-3'
+ \endif
+\else
+ \echo 'should not print #1-4'
+\endif
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+ \echo 'should not print #2-1'
+\elif 0
+ \echo 'should not print #2-2'
+\elif no
+ \echo 'should not print #2-3'
+\elif off
+ \echo 'should not print #2-4'
+\else
+ \echo 'all false'
+all false
+\endif
+-- test true-false elif after initial true branch
+\if true
+ \echo 'should print #2-5'
+should print #2-5
+\elif true
+ \echo 'should not print #2-6'
+\elif false
+ \echo 'should not print #2-7'
+\else
+ \echo 'should not print #2-8'
+\endif
+-- test simple true-then-else
+\if true
+ \echo 'first thing true'
+first thing true
+\else
+ \echo 'should not print #3-1'
+\endif
+-- test simple false-true-else
+\if false
+ \echo 'should not print #4-1'
+\elif true
+ \echo 'second thing true'
+second thing true
+\else
+ \echo 'should not print #5-1'
+\endif
+-- invalid boolean expressions are false
+\if invalid boolean expression
+unrecognized value "invalid boolean expression" for "\if expression": Boolean expected
+ \echo 'will not print #6-1'
+\else
+ \echo 'will print anyway #6-2'
+will print anyway #6-2
+\endif
+-- test un-matched endif
+\endif
+\endif: no matching \if
+-- test un-matched else
+\else
+\else: no matching \if
+-- test un-matched elif
+\elif
+\elif: no matching \if
+-- test double-else error
+\if true
+\else
+\else
+\else: cannot occur after \else
+\endif
+-- test elif out-of-order
+\if false
+\else
+\elif
+\elif: cannot occur after \else
+\endif
+-- test if-endif matching in a false branch
+\if false
+ \if false
+ \echo 'should not print #7-1'
+ \else
+ \echo 'should not print #7-2'
+ \endif
+ \echo 'should not print #7-3'
+\else
+ \echo 'should print #7-4'
+should print #7-4
+\endif
+-- show that vars and backticks are not expanded when ignoring extra args
+\set foo bar
+\echo :foo :'foo' :"foo"
+bar 'bar' "bar"
+\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
+\pset: extra argument "nosuchcommand" ignored
+\pset: extra argument ":foo" ignored
+\pset: extra argument ":'foo'" ignored
+\pset: extra argument ":"foo"" ignored
+-- show that vars and backticks are not expanded and commands are ignored
+-- when in a false if-branch
+\set try_to_quit '\\q'
+\if false
+ :try_to_quit
+ \echo `nosuchcommand` :foo :'foo' :"foo"
+ \pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
+ \a
+ \C arg1
+ \c arg1 arg2 arg3 arg4
+ \cd arg1
+ \conninfo
+ \copy arg1 arg2 arg3 arg4 arg5 arg6
+ \copyright
+ SELECT 1 as one, 2, 3 \crosstabview
+ \dt arg1
+ \e arg1 arg2
+ \ef whole_line
+ \ev whole_line
+ \echo arg1 arg2 arg3 arg4 arg5
+ \echo arg1
+ \encoding arg1
+ \errverbose
+ \f arg1
+ \g arg1
+ \gx arg1
+ \gexec
+ SELECT 1 AS one \gset
+ \h
+ \?
+ \html
+ \i arg1
+ \ir arg1
+ \l arg1
+ \lo arg1 arg2
+invalid command \lo
+ \lo_list
+ \o arg1
+ \p
+ \password arg1
+ \prompt arg1 arg2
+ \pset arg1 arg2
+ \q
+ \reset
+ \s arg1
+ \set arg1 arg2 arg3 arg4 arg5 arg6 arg7
+ \setenv arg1 arg2
+ \sf whole_line
+ \sv whole_line
+ \t arg1
+ \T arg1
+ \timing arg1
+ \unset arg1
+ \w arg1
+ \watch arg1
+ \x arg1
+ -- \else here is eaten as part of OT_FILEPIPE argument
+ \w |/no/such/file \else
+ -- \endif here is eaten as part of whole-line argument
+ \! whole_line \endif
+ \z
+\else
+ \echo 'should print #8-1'
+should print #8-1
+\endif
+-- :{?...} defined variable test
+\set i 1
+\if :{?i}
+ \echo '#9-1 ok, variable i is defined'
+#9-1 ok, variable i is defined
+\else
+ \echo 'should not print #9-2'
+\endif
+\if :{?no_such_variable}
+ \echo 'should not print #10-1'
+\else
+ \echo '#10-2 ok, variable no_such_variable is not defined'
+#10-2 ok, variable no_such_variable is not defined
+\endif
+SELECT :{?i} AS i_is_defined;
+ i_is_defined
+--------------
+ t
+(1 row)
+
+SELECT NOT :{?no_such_var} AS no_such_var_is_not_defined;
+ no_such_var_is_not_defined
+----------------------------
+ t
+(1 row)
+
+-- SHOW_CONTEXT
+\set SHOW_CONTEXT never
+do $$
+begin
+ raise notice 'foo';
+ raise exception 'bar';
+end $$;
+NOTICE: foo
+ERROR: bar
+\set SHOW_CONTEXT errors
+do $$
+begin
+ raise notice 'foo';
+ raise exception 'bar';
+end $$;
+NOTICE: foo
+ERROR: bar
+CONTEXT: PL/pgSQL function inline_code_block line 4 at RAISE
+\set SHOW_CONTEXT always
+do $$
+begin
+ raise notice 'foo';
+ raise exception 'bar';
+end $$;
+NOTICE: foo
+CONTEXT: PL/pgSQL function inline_code_block line 3 at RAISE
+ERROR: bar
+CONTEXT: PL/pgSQL function inline_code_block line 4 at RAISE
+-- test printing and clearing the query buffer
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+\p
+SELECT 1;
+SELECT 2 \r
+\p
+SELECT 1;
+SELECT 3 \p
+SELECT 3
+UNION SELECT 4 \p
+SELECT 3
+UNION SELECT 4
+UNION SELECT 5
+ORDER BY 1;
+ ?column?
+----------
+ 3
+ 4
+ 5
+(3 rows)
+
+\r
+\p
+SELECT 3
+UNION SELECT 4
+UNION SELECT 5
+ORDER BY 1;
+-- tests for special result variables
+-- working query, 2 rows selected
+SELECT 1 AS stuff UNION SELECT 2;
+ stuff
+-------
+ 1
+ 2
+(2 rows)
+
+\echo 'error:' :ERROR
+error: false
+\echo 'error code:' :SQLSTATE
+error code: 00000
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 2
+-- syntax error
+SELECT 1 UNION;
+ERROR: syntax error at or near ";"
+LINE 1: SELECT 1 UNION;
+ ^
+\echo 'error:' :ERROR
+error: true
+\echo 'error code:' :SQLSTATE
+error code: 42601
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 0
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: syntax error at or near ";"
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+last error code: 42601
+-- empty query
+;
+\echo 'error:' :ERROR
+error: false
+\echo 'error code:' :SQLSTATE
+error code: 00000
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 0
+-- must have kept previous values
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: syntax error at or near ";"
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+last error code: 42601
+-- other query error
+DROP TABLE this_table_does_not_exist;
+ERROR: table "this_table_does_not_exist" does not exist
+\echo 'error:' :ERROR
+error: true
+\echo 'error code:' :SQLSTATE
+error code: 42P01
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 0
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: table "this_table_does_not_exist" does not exist
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+last error code: 42P01
+-- nondefault verbosity error settings (except verbose, which is too unstable)
+\set VERBOSITY terse
+SELECT 1 UNION;
+ERROR: syntax error at or near ";" at character 15
+\echo 'error:' :ERROR
+error: true
+\echo 'error code:' :SQLSTATE
+error code: 42601
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: syntax error at or near ";"
+\set VERBOSITY sqlstate
+SELECT 1/0;
+ERROR: 22012
+\echo 'error:' :ERROR
+error: true
+\echo 'error code:' :SQLSTATE
+error code: 22012
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: division by zero
+\set VERBOSITY default
+-- working \gdesc
+SELECT 3 AS three, 4 AS four \gdesc
+ Column | Type
+--------+---------
+ three | integer
+ four | integer
+(2 rows)
+
+\echo 'error:' :ERROR
+error: false
+\echo 'error code:' :SQLSTATE
+error code: 00000
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 2
+-- \gdesc with an error
+SELECT 4 AS \gdesc
+ERROR: syntax error at end of input
+LINE 1: SELECT 4 AS
+ ^
+\echo 'error:' :ERROR
+error: true
+\echo 'error code:' :SQLSTATE
+error code: 42601
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 0
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: syntax error at end of input
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+last error code: 42601
+-- check row count for a cursor-fetched query
+\set FETCH_COUNT 10
+select unique2 from tenk1 order by unique2 limit 19;
+ unique2
+---------
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+(19 rows)
+
+\echo 'error:' :ERROR
+error: false
+\echo 'error code:' :SQLSTATE
+error code: 00000
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 19
+-- cursor-fetched query with an error after the first group
+select 1/(15-unique2) from tenk1 order by unique2 limit 19;
+ ?column?
+----------
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ERROR: division by zero
+\echo 'error:' :ERROR
+error: true
+\echo 'error code:' :SQLSTATE
+error code: 22012
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 0
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: division by zero
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+last error code: 22012
+\unset FETCH_COUNT
+create schema testpart;
+create role regress_partitioning_role;
+alter schema testpart owner to regress_partitioning_role;
+set role to regress_partitioning_role;
+-- run test inside own schema and hide other partitions
+set search_path to testpart;
+create table testtable_apple(logdate date);
+create table testtable_orange(logdate date);
+create index testtable_apple_index on testtable_apple(logdate);
+create index testtable_orange_index on testtable_orange(logdate);
+create table testpart_apple(logdate date) partition by range(logdate);
+create table testpart_orange(logdate date) partition by range(logdate);
+create index testpart_apple_index on testpart_apple(logdate);
+create index testpart_orange_index on testpart_orange(logdate);
+-- only partition related object should be displayed
+\dP test*apple*
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+----------+----------------------+---------------------------+-------------------+-------------+----------------
+ testpart | testpart_apple | regress_partitioning_role | partitioned table | |
+ testpart | testpart_apple_index | regress_partitioning_role | partitioned index | | testpart_apple
+(2 rows)
+
+\dPt test*apple*
+ List of partitioned tables
+ Schema | Name | Owner | Parent name
+----------+----------------+---------------------------+-------------
+ testpart | testpart_apple | regress_partitioning_role |
+(1 row)
+
+\dPi test*apple*
+ List of partitioned indexes
+ Schema | Name | Owner | Parent name | Table
+----------+----------------------+---------------------------+-------------+----------------
+ testpart | testpart_apple_index | regress_partitioning_role | | testpart_apple
+(1 row)
+
+drop table testtable_apple;
+drop table testtable_orange;
+drop table testpart_apple;
+drop table testpart_orange;
+create table parent_tab (id int) partition by range (id);
+create index parent_index on parent_tab (id);
+create table child_0_10 partition of parent_tab
+ for values from (0) to (10);
+create table child_10_20 partition of parent_tab
+ for values from (10) to (20);
+create table child_20_30 partition of parent_tab
+ for values from (20) to (30);
+insert into parent_tab values (generate_series(0,29));
+create table child_30_40 partition of parent_tab
+for values from (30) to (40)
+ partition by range(id);
+create table child_30_35 partition of child_30_40
+ for values from (30) to (35);
+create table child_35_40 partition of child_30_40
+ for values from (35) to (40);
+insert into parent_tab values (generate_series(30,39));
+\dPt
+ List of partitioned tables
+ Schema | Name | Owner
+----------+------------+---------------------------
+ testpart | parent_tab | regress_partitioning_role
+(1 row)
+
+\dPi
+ List of partitioned indexes
+ Schema | Name | Owner | Table
+----------+--------------+---------------------------+------------
+ testpart | parent_index | regress_partitioning_role | parent_tab
+(1 row)
+
+\dP testpart.*
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+----------+--------------------+---------------------------+-------------------+--------------+-------------
+ testpart | parent_tab | regress_partitioning_role | partitioned table | |
+ testpart | child_30_40 | regress_partitioning_role | partitioned table | parent_tab |
+ testpart | parent_index | regress_partitioning_role | partitioned index | | parent_tab
+ testpart | child_30_40_id_idx | regress_partitioning_role | partitioned index | parent_index | child_30_40
+(4 rows)
+
+\dP
+ List of partitioned relations
+ Schema | Name | Owner | Type | Table
+----------+--------------+---------------------------+-------------------+------------
+ testpart | parent_tab | regress_partitioning_role | partitioned table |
+ testpart | parent_index | regress_partitioning_role | partitioned index | parent_tab
+(2 rows)
+
+\dPtn
+ List of partitioned tables
+ Schema | Name | Owner | Parent name
+----------+-------------+---------------------------+-------------
+ testpart | parent_tab | regress_partitioning_role |
+ testpart | child_30_40 | regress_partitioning_role | parent_tab
+(2 rows)
+
+\dPin
+ List of partitioned indexes
+ Schema | Name | Owner | Parent name | Table
+----------+--------------------+---------------------------+--------------+-------------
+ testpart | parent_index | regress_partitioning_role | | parent_tab
+ testpart | child_30_40_id_idx | regress_partitioning_role | parent_index | child_30_40
+(2 rows)
+
+\dPn
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+----------+--------------------+---------------------------+-------------------+--------------+-------------
+ testpart | parent_tab | regress_partitioning_role | partitioned table | |
+ testpart | child_30_40 | regress_partitioning_role | partitioned table | parent_tab |
+ testpart | parent_index | regress_partitioning_role | partitioned index | | parent_tab
+ testpart | child_30_40_id_idx | regress_partitioning_role | partitioned index | parent_index | child_30_40
+(4 rows)
+
+\dPn testpart.*
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+----------+--------------------+---------------------------+-------------------+--------------+-------------
+ testpart | parent_tab | regress_partitioning_role | partitioned table | |
+ testpart | child_30_40 | regress_partitioning_role | partitioned table | parent_tab |
+ testpart | parent_index | regress_partitioning_role | partitioned index | | parent_tab
+ testpart | child_30_40_id_idx | regress_partitioning_role | partitioned index | parent_index | child_30_40
+(4 rows)
+
+drop table parent_tab cascade;
+drop schema testpart;
+set search_path to default;
+set role to default;
+drop role regress_partitioning_role;
+-- \d on toast table (use pg_statistic's toast table, which has a known name)
+\d pg_toast.pg_toast_2619
+TOAST table "pg_toast.pg_toast_2619"
+ Column | Type
+------------+---------
+ chunk_id | oid
+ chunk_seq | integer
+ chunk_data | bytea
+Owning table: "pg_catalog.pg_statistic"
+Indexes:
+ "pg_toast_2619_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
+
+-- check printing info about access methods
+\dA
+List of access methods
+ Name | Type
+--------+-------
+ brin | Index
+ btree | Index
+ gin | Index
+ gist | Index
+ hash | Index
+ heap | Table
+ heap2 | Table
+ spgist | Index
+(8 rows)
+
+\dA *
+List of access methods
+ Name | Type
+--------+-------
+ brin | Index
+ btree | Index
+ gin | Index
+ gist | Index
+ hash | Index
+ heap | Table
+ heap2 | Table
+ spgist | Index
+(8 rows)
+
+\dA h*
+List of access methods
+ Name | Type
+-------+-------
+ hash | Index
+ heap | Table
+ heap2 | Table
+(3 rows)
+
+\dA foo
+List of access methods
+ Name | Type
+------+------
+(0 rows)
+
+\dA foo bar
+List of access methods
+ Name | Type
+------+------
+(0 rows)
+
+\dA: extra argument "bar" ignored
+\dA+
+ List of access methods
+ Name | Type | Handler | Description
+--------+-------+----------------------+----------------------------------------
+ brin | Index | brinhandler | block range index (BRIN) access method
+ btree | Index | bthandler | b-tree index access method
+ gin | Index | ginhandler | GIN index access method
+ gist | Index | gisthandler | GiST index access method
+ hash | Index | hashhandler | hash index access method
+ heap | Table | heap_tableam_handler | heap table access method
+ heap2 | Table | heap_tableam_handler |
+ spgist | Index | spghandler | SP-GiST index access method
+(8 rows)
+
+\dA+ *
+ List of access methods
+ Name | Type | Handler | Description
+--------+-------+----------------------+----------------------------------------
+ brin | Index | brinhandler | block range index (BRIN) access method
+ btree | Index | bthandler | b-tree index access method
+ gin | Index | ginhandler | GIN index access method
+ gist | Index | gisthandler | GiST index access method
+ hash | Index | hashhandler | hash index access method
+ heap | Table | heap_tableam_handler | heap table access method
+ heap2 | Table | heap_tableam_handler |
+ spgist | Index | spghandler | SP-GiST index access method
+(8 rows)
+
+\dA+ h*
+ List of access methods
+ Name | Type | Handler | Description
+-------+-------+----------------------+--------------------------
+ hash | Index | hashhandler | hash index access method
+ heap | Table | heap_tableam_handler | heap table access method
+ heap2 | Table | heap_tableam_handler |
+(3 rows)
+
+\dA+ foo
+ List of access methods
+ Name | Type | Handler | Description
+------+------+---------+-------------
+(0 rows)
+
+\dAc brin pg*.oid*
+ List of operator classes
+ AM | Input type | Storage type | Operator class | Default?
+------+------------+--------------+----------------------+----------
+ brin | oid | | oid_bloom_ops | no
+ brin | oid | | oid_minmax_multi_ops | no
+ brin | oid | | oid_minmax_ops | yes
+(3 rows)
+
+\dAf spgist
+ List of operator families
+ AM | Operator family | Applicable types
+--------+-----------------+------------------
+ spgist | box_ops | box
+ spgist | kd_point_ops | point
+ spgist | network_ops | inet
+ spgist | poly_ops | polygon
+ spgist | quad_point_ops | point
+ spgist | range_ops | anyrange
+ spgist | text_ops | text
+(7 rows)
+
+\dAf btree int4
+ List of operator families
+ AM | Operator family | Applicable types
+-------+-----------------+---------------------------
+ btree | integer_ops | smallint, integer, bigint
+(1 row)
+
+\dAo+ btree float_ops
+ List of operators of operator families
+ AM | Operator family | Operator | Strategy | Purpose | Sort opfamily
+-------+-----------------+---------------------------------------+----------+---------+---------------
+ btree | float_ops | <(double precision,double precision) | 1 | search |
+ btree | float_ops | <=(double precision,double precision) | 2 | search |
+ btree | float_ops | =(double precision,double precision) | 3 | search |
+ btree | float_ops | >=(double precision,double precision) | 4 | search |
+ btree | float_ops | >(double precision,double precision) | 5 | search |
+ btree | float_ops | <(real,real) | 1 | search |
+ btree | float_ops | <=(real,real) | 2 | search |
+ btree | float_ops | =(real,real) | 3 | search |
+ btree | float_ops | >=(real,real) | 4 | search |
+ btree | float_ops | >(real,real) | 5 | search |
+ btree | float_ops | <(double precision,real) | 1 | search |
+ btree | float_ops | <=(double precision,real) | 2 | search |
+ btree | float_ops | =(double precision,real) | 3 | search |
+ btree | float_ops | >=(double precision,real) | 4 | search |
+ btree | float_ops | >(double precision,real) | 5 | search |
+ btree | float_ops | <(real,double precision) | 1 | search |
+ btree | float_ops | <=(real,double precision) | 2 | search |
+ btree | float_ops | =(real,double precision) | 3 | search |
+ btree | float_ops | >=(real,double precision) | 4 | search |
+ btree | float_ops | >(real,double precision) | 5 | search |
+(20 rows)
+
+\dAo * pg_catalog.jsonb_path_ops
+ List of operators of operator families
+ AM | Operator family | Operator | Strategy | Purpose
+-----+-----------------+--------------------+----------+---------
+ gin | jsonb_path_ops | @>(jsonb,jsonb) | 7 | search
+ gin | jsonb_path_ops | @?(jsonb,jsonpath) | 15 | search
+ gin | jsonb_path_ops | @@(jsonb,jsonpath) | 16 | search
+(3 rows)
+
+\dAp+ btree float_ops
+ List of support functions of operator families
+ AM | Operator family | Registered left type | Registered right type | Number | Function
+-------+-----------------+----------------------+-----------------------+--------+------------------------------------------------------------------------------
+ btree | float_ops | double precision | double precision | 1 | btfloat8cmp(double precision,double precision)
+ btree | float_ops | double precision | double precision | 2 | btfloat8sortsupport(internal)
+ btree | float_ops | double precision | double precision | 3 | in_range(double precision,double precision,double precision,boolean,boolean)
+ btree | float_ops | real | real | 1 | btfloat4cmp(real,real)
+ btree | float_ops | real | real | 2 | btfloat4sortsupport(internal)
+ btree | float_ops | double precision | real | 1 | btfloat84cmp(double precision,real)
+ btree | float_ops | real | double precision | 1 | btfloat48cmp(real,double precision)
+ btree | float_ops | real | double precision | 3 | in_range(real,real,double precision,boolean,boolean)
+(8 rows)
+
+\dAp * pg_catalog.uuid_ops
+ List of support functions of operator families
+ AM | Operator family | Registered left type | Registered right type | Number | Function
+-------+-----------------+----------------------+-----------------------+--------+--------------------
+ btree | uuid_ops | uuid | uuid | 1 | uuid_cmp
+ btree | uuid_ops | uuid | uuid | 2 | uuid_sortsupport
+ btree | uuid_ops | uuid | uuid | 4 | btequalimage
+ hash | uuid_ops | uuid | uuid | 1 | uuid_hash
+ hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
+(5 rows)
+
+-- check \df, \do with argument specifications
+\df *sqrt
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+--------------+------------------+---------------------+------
+ pg_catalog | dsqrt | double precision | double precision | func
+ pg_catalog | numeric_sqrt | numeric | numeric | func
+ pg_catalog | sqrt | double precision | double precision | func
+ pg_catalog | sqrt | numeric | numeric | func
+(4 rows)
+
+\df *sqrt num*
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+--------------+------------------+---------------------+------
+ pg_catalog | numeric_sqrt | numeric | numeric | func
+ pg_catalog | sqrt | numeric | numeric | func
+(2 rows)
+
+\df int*pl
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+-------------+------------------+---------------------+------
+ pg_catalog | int24pl | integer | smallint, integer | func
+ pg_catalog | int28pl | bigint | smallint, bigint | func
+ pg_catalog | int2pl | smallint | smallint, smallint | func
+ pg_catalog | int42pl | integer | integer, smallint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int4pl | integer | integer, integer | func
+ pg_catalog | int82pl | bigint | bigint, smallint | func
+ pg_catalog | int84pl | bigint | bigint, integer | func
+ pg_catalog | int8pl | bigint | bigint, bigint | func
+ pg_catalog | interval_pl | interval | interval, interval | func
+(10 rows)
+
+\df int*pl int4
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | int42pl | integer | integer, smallint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int4pl | integer | integer, integer | func
+(3 rows)
+
+\df int*pl * pg_catalog.int8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | int28pl | bigint | smallint, bigint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int8pl | bigint | bigint, bigint | func
+(3 rows)
+
+\df acl* aclitem[]
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+-------------+------------------+----------------------------------------------------------------------------------------------------+------
+ pg_catalog | aclcontains | boolean | aclitem[], aclitem | func
+ pg_catalog | aclexplode | SETOF record | acl aclitem[], OUT grantor oid, OUT grantee oid, OUT privilege_type text, OUT is_grantable boolean | func
+ pg_catalog | aclinsert | aclitem[] | aclitem[], aclitem | func
+ pg_catalog | aclremove | aclitem[] | aclitem[], aclitem | func
+(4 rows)
+
+\df has_database_privilege oid text
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+------------------------+------------------+---------------------+------
+ pg_catalog | has_database_privilege | boolean | oid, text | func
+ pg_catalog | has_database_privilege | boolean | oid, text, text | func
+(2 rows)
+
+\df has_database_privilege oid text -
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+------------------------+------------------+---------------------+------
+ pg_catalog | has_database_privilege | boolean | oid, text | func
+(1 row)
+
+\dfa bit* small*
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | bit_and | smallint | smallint | agg
+ pg_catalog | bit_or | smallint | smallint | agg
+ pg_catalog | bit_xor | smallint | smallint | agg
+(3 rows)
+
+\do - pg_catalog.int4
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+------------+------+---------------+----------------+-------------+-------------
+ pg_catalog | - | | integer | integer | negate
+(1 row)
+
+\do && anyarray *
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+------------+------+---------------+----------------+-------------+-------------
+ pg_catalog | && | anyarray | anyarray | boolean | overlaps
+(1 row)
+
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+cross-database references are not implemented: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+cross-database references are not implemented: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+cross-database references are not implemented: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+cross-database references are not implemented: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+cross-database references are not implemented: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+cross-database references are not implemented: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+cross-database references are not implemented: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+cross-database references are not implemented: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+cross-database references are not implemented: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+cross-database references are not implemented: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+cross-database references are not implemented: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+cross-database references are not implemented: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+cross-database references are not implemented: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+cross-database references are not implemented: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+cross-database references are not implemented: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+cross-database references are not implemented: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+cross-database references are not implemented: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
+-- check that dots within quoted name segments are not counted
+\dA "no.such.access.method"
+List of access methods
+ Name | Type
+------+------
+(0 rows)
+
+\dt "no.such.table.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\da "no.such.aggregate.function"
+ List of aggregate functions
+ Schema | Name | Result data type | Argument data types | Description
+--------+------+------------------+---------------------+-------------
+(0 rows)
+
+\dAc "no.such.operator.class"
+ List of operator classes
+ AM | Input type | Storage type | Operator class | Default?
+----+------------+--------------+----------------+----------
+(0 rows)
+
+\dAf "no.such.operator.family"
+ List of operator families
+ AM | Operator family | Applicable types
+----+-----------------+------------------
+(0 rows)
+
+\dAo "no.such.operator.of.operator.family"
+ List of operators of operator families
+ AM | Operator family | Operator | Strategy | Purpose
+----+-----------------+----------+----------+---------
+(0 rows)
+
+\dAp "no.such.operator.support.function.of.operator.family"
+ List of support functions of operator families
+ AM | Operator family | Registered left type | Registered right type | Number | Function
+----+-----------------+----------------------+-----------------------+--------+----------
+(0 rows)
+
+\db "no.such.tablespace"
+ List of tablespaces
+ Name | Owner | Location
+------+-------+----------
+(0 rows)
+
+\dc "no.such.conversion"
+ List of conversions
+ Schema | Name | Source | Destination | Default?
+--------+------+--------+-------------+----------
+(0 rows)
+
+\dC "no.such.cast"
+ List of casts
+ Source type | Target type | Function | Implicit?
+-------------+-------------+----------+-----------
+(0 rows)
+
+\dd "no.such.object.description"
+ Object descriptions
+ Schema | Name | Object | Description
+--------+------+--------+-------------
+(0 rows)
+
+\dD "no.such.domain"
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+--------+------+------+-----------+----------+---------+-------
+(0 rows)
+
+\ddp "no.such.default.access.privilege"
+ Default access privileges
+ Owner | Schema | Type | Access privileges
+-------+--------+------+-------------------
+(0 rows)
+
+\di "no.such.index.relation"
+ List of relations
+ Schema | Name | Type | Owner | Table
+--------+------+------+-------+-------
+(0 rows)
+
+\dm "no.such.materialized.view"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\ds "no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dt "no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dv "no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\des "no.such.foreign.server"
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper
+------+-------+----------------------
+(0 rows)
+
+\dew "no.such.foreign.data.wrapper"
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator
+------+-------+---------+-----------
+(0 rows)
+
+\df "no.such.function"
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+\dF "no.such.text.search.configuration"
+List of text search configurations
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFd "no.such.text.search.dictionary"
+List of text search dictionaries
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFp "no.such.text.search.parser"
+ List of text search parsers
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFt "no.such.text.search.template"
+List of text search templates
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dg "no.such.role"
+ List of roles
+ Role name | Attributes | Member of
+-----------+------------+-----------
+
+\dL "no.such.language"
+ List of languages
+ Name | Owner | Trusted | Description
+------+-------+---------+-------------
+(0 rows)
+
+\dn "no.such.schema"
+List of schemas
+ Name | Owner
+------+-------
+(0 rows)
+
+\do "no.such.operator"
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+--------+------+---------------+----------------+-------------+-------------
+(0 rows)
+
+\dO "no.such.collation"
+ List of collations
+ Schema | Name | Collate | Ctype | Provider | Deterministic?
+--------+------+---------+-------+----------+----------------
+(0 rows)
+
+\dp "no.such.access.privilege"
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+------+------+-------------------+-------------------+----------
+(0 rows)
+
+\dP "no.such.partitioned.relation"
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+--------+------+-------+------+-------------+-------
+(0 rows)
+
+\drds "no.such.setting"
+ List of settings
+ Role | Database | Settings
+------+----------+----------
+(0 rows)
+
+\dRp "no.such.publication"
+ List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+------+-------+------------+---------+---------+---------+-----------+----------
+(0 rows)
+
+\dRs "no.such.subscription"
+ List of subscriptions
+ Name | Owner | Enabled | Publication
+------+-------+---------+-------------
+(0 rows)
+
+\dT "no.such.data.type"
+ List of data types
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dx "no.such.installed.extension"
+ List of installed extensions
+ Name | Version | Schema | Description
+------+---------+--------+-------------
+(0 rows)
+
+\dX "no.such.extended.statistics"
+ List of extended statistics
+ Schema | Name | Definition | Ndistinct | Dependencies | MCV
+--------+------+------------+-----------+--------------+-----
+(0 rows)
+
+\dy "no.such.event.trigger"
+ List of event triggers
+ Name | Event | Owner | Enabled | Function | Tags
+------+-------+-------+---------+----------+------
+(0 rows)
+
+-- again, but with dotted schema qualifications.
+\dA "no.such.schema"."no.such.access.method"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.access.method"
+\dt "no.such.schema"."no.such.table.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\da "no.such.schema"."no.such.aggregate.function"
+ List of aggregate functions
+ Schema | Name | Result data type | Argument data types | Description
+--------+------+------------------+---------------------+-------------
+(0 rows)
+
+\dAc "no.such.schema"."no.such.operator.class"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.operator.class"
+\dAf "no.such.schema"."no.such.operator.family"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.operator.family"
+\dAo "no.such.schema"."no.such.operator.of.operator.family"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.operator.of.operator.family"
+\dAp "no.such.schema"."no.such.operator.support.function.of.operator.family"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.operator.support.function.of.operator.family"
+\db "no.such.schema"."no.such.tablespace"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.tablespace"
+\dc "no.such.schema"."no.such.conversion"
+ List of conversions
+ Schema | Name | Source | Destination | Default?
+--------+------+--------+-------------+----------
+(0 rows)
+
+\dC "no.such.schema"."no.such.cast"
+ List of casts
+ Source type | Target type | Function | Implicit?
+-------------+-------------+----------+-----------
+(0 rows)
+
+\dd "no.such.schema"."no.such.object.description"
+ Object descriptions
+ Schema | Name | Object | Description
+--------+------+--------+-------------
+(0 rows)
+
+\dD "no.such.schema"."no.such.domain"
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+--------+------+------+-----------+----------+---------+-------
+(0 rows)
+
+\ddp "no.such.schema"."no.such.default.access.privilege"
+ Default access privileges
+ Owner | Schema | Type | Access privileges
+-------+--------+------+-------------------
+(0 rows)
+
+\di "no.such.schema"."no.such.index.relation"
+ List of relations
+ Schema | Name | Type | Owner | Table
+--------+------+------+-------+-------
+(0 rows)
+
+\dm "no.such.schema"."no.such.materialized.view"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\ds "no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dt "no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dv "no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\des "no.such.schema"."no.such.foreign.server"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.foreign.server"
+\dew "no.such.schema"."no.such.foreign.data.wrapper"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.foreign.data.wrapper"
+\df "no.such.schema"."no.such.function"
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+\dF "no.such.schema"."no.such.text.search.configuration"
+List of text search configurations
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFd "no.such.schema"."no.such.text.search.dictionary"
+List of text search dictionaries
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFp "no.such.schema"."no.such.text.search.parser"
+ List of text search parsers
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFt "no.such.schema"."no.such.text.search.template"
+List of text search templates
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dg "no.such.schema"."no.such.role"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.role"
+\dL "no.such.schema"."no.such.language"
+cross-database references are not implemented: "no.such.schema"."no.such.language"
+\do "no.such.schema"."no.such.operator"
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+--------+------+---------------+----------------+-------------+-------------
+(0 rows)
+
+\dO "no.such.schema"."no.such.collation"
+ List of collations
+ Schema | Name | Collate | Ctype | Provider | Deterministic?
+--------+------+---------+-------+----------+----------------
+(0 rows)
+
+\dp "no.such.schema"."no.such.access.privilege"
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+------+------+-------------------+-------------------+----------
+(0 rows)
+
+\dP "no.such.schema"."no.such.partitioned.relation"
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+--------+------+-------+------+-------------+-------
+(0 rows)
+
+\drds "no.such.schema"."no.such.setting"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.setting"
+\dRp "no.such.schema"."no.such.publication"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.publication"
+\dRs "no.such.schema"."no.such.subscription"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.subscription"
+\dT "no.such.schema"."no.such.data.type"
+ List of data types
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dx "no.such.schema"."no.such.installed.extension"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.installed.extension"
+\dX "no.such.schema"."no.such.extended.statistics"
+ List of extended statistics
+ Schema | Name | Definition | Ndistinct | Dependencies | MCV
+--------+------+------------+-----------+--------------+-----
+(0 rows)
+
+\dy "no.such.schema"."no.such.event.trigger"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.event.trigger"
+-- again, but with current database and dotted schema qualifications.
+\dt regression."no.such.schema"."no.such.table.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\da regression."no.such.schema"."no.such.aggregate.function"
+ List of aggregate functions
+ Schema | Name | Result data type | Argument data types | Description
+--------+------+------------------+---------------------+-------------
+(0 rows)
+
+\dc regression."no.such.schema"."no.such.conversion"
+ List of conversions
+ Schema | Name | Source | Destination | Default?
+--------+------+--------+-------------+----------
+(0 rows)
+
+\dC regression."no.such.schema"."no.such.cast"
+ List of casts
+ Source type | Target type | Function | Implicit?
+-------------+-------------+----------+-----------
+(0 rows)
+
+\dd regression."no.such.schema"."no.such.object.description"
+ Object descriptions
+ Schema | Name | Object | Description
+--------+------+--------+-------------
+(0 rows)
+
+\dD regression."no.such.schema"."no.such.domain"
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+--------+------+------+-----------+----------+---------+-------
+(0 rows)
+
+\di regression."no.such.schema"."no.such.index.relation"
+ List of relations
+ Schema | Name | Type | Owner | Table
+--------+------+------+-------+-------
+(0 rows)
+
+\dm regression."no.such.schema"."no.such.materialized.view"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\ds regression."no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dt regression."no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dv regression."no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\df regression."no.such.schema"."no.such.function"
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+\dF regression."no.such.schema"."no.such.text.search.configuration"
+List of text search configurations
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFd regression."no.such.schema"."no.such.text.search.dictionary"
+List of text search dictionaries
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFp regression."no.such.schema"."no.such.text.search.parser"
+ List of text search parsers
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFt regression."no.such.schema"."no.such.text.search.template"
+List of text search templates
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\do regression."no.such.schema"."no.such.operator"
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+--------+------+---------------+----------------+-------------+-------------
+(0 rows)
+
+\dO regression."no.such.schema"."no.such.collation"
+ List of collations
+ Schema | Name | Collate | Ctype | Provider | Deterministic?
+--------+------+---------+-------+----------+----------------
+(0 rows)
+
+\dp regression."no.such.schema"."no.such.access.privilege"
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+------+------+-------------------+-------------------+----------
+(0 rows)
+
+\dP regression."no.such.schema"."no.such.partitioned.relation"
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+--------+------+-------+------+-------------+-------
+(0 rows)
+
+\dT regression."no.such.schema"."no.such.data.type"
+ List of data types
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dX regression."no.such.schema"."no.such.extended.statistics"
+ List of extended statistics
+ Schema | Name | Definition | Ndistinct | Dependencies | MCV
+--------+------+------------+-----------+--------------+-----
+(0 rows)
+
+-- again, but with dotted database and dotted schema qualifications.
+\dt "no.such.database"."no.such.schema"."no.such.table.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.table.relation"
+\da "no.such.database"."no.such.schema"."no.such.aggregate.function"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.aggregate.function"
+\dc "no.such.database"."no.such.schema"."no.such.conversion"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.conversion"
+\dC "no.such.database"."no.such.schema"."no.such.cast"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.cast"
+\dd "no.such.database"."no.such.schema"."no.such.object.description"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.object.description"
+\dD "no.such.database"."no.such.schema"."no.such.domain"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.domain"
+\ddp "no.such.database"."no.such.schema"."no.such.default.access.privilege"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.default.access.privilege"
+\di "no.such.database"."no.such.schema"."no.such.index.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.index.relation"
+\dm "no.such.database"."no.such.schema"."no.such.materialized.view"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.materialized.view"
+\ds "no.such.database"."no.such.schema"."no.such.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.relation"
+\dt "no.such.database"."no.such.schema"."no.such.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.relation"
+\dv "no.such.database"."no.such.schema"."no.such.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.relation"
+\df "no.such.database"."no.such.schema"."no.such.function"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.function"
+\dF "no.such.database"."no.such.schema"."no.such.text.search.configuration"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.text.search.configuration"
+\dFd "no.such.database"."no.such.schema"."no.such.text.search.dictionary"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.text.search.dictionary"
+\dFp "no.such.database"."no.such.schema"."no.such.text.search.parser"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.text.search.parser"
+\dFt "no.such.database"."no.such.schema"."no.such.text.search.template"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.text.search.template"
+\do "no.such.database"."no.such.schema"."no.such.operator"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.operator"
+\dO "no.such.database"."no.such.schema"."no.such.collation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.collation"
+\dp "no.such.database"."no.such.schema"."no.such.access.privilege"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.access.privilege"
+\dP "no.such.database"."no.such.schema"."no.such.partitioned.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.partitioned.relation"
+\dT "no.such.database"."no.such.schema"."no.such.data.type"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.data.type"
+\dX "no.such.database"."no.such.schema"."no.such.extended.statistics"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.extended.statistics"
diff --git a/src/test/regress/expected/psql_crosstab.out b/src/test/regress/expected/psql_crosstab.out
new file mode 100644
index 0000000..e09e331
--- /dev/null
+++ b/src/test/regress/expected/psql_crosstab.out
@@ -0,0 +1,216 @@
+--
+-- \crosstabview
+--
+CREATE TABLE ctv_data (v, h, c, i, d) AS
+VALUES
+ ('v1','h2','foo', 3, '2015-04-01'::date),
+ ('v2','h1','bar', 3, '2015-01-02'),
+ ('v1','h0','baz', NULL, '2015-07-12'),
+ ('v0','h4','qux', 4, '2015-07-15'),
+ ('v0','h4','dbl', -3, '2014-12-15'),
+ ('v0',NULL,'qux', 5, '2014-07-15'),
+ ('v1','h2','quux',7, '2015-04-04');
+-- make plans more stable
+ANALYZE ctv_data;
+-- running \crosstabview after query uses query in buffer
+SELECT v, EXTRACT(year FROM d), count(*)
+ FROM ctv_data
+ GROUP BY 1, 2
+ ORDER BY 1, 2;
+ v | extract | count
+----+---------+-------
+ v0 | 2014 | 2
+ v0 | 2015 | 1
+ v1 | 2015 | 3
+ v2 | 2015 | 1
+(4 rows)
+
+-- basic usage with 3 columns
+ \crosstabview
+ v | 2014 | 2015
+----+------+------
+ v0 | 2 | 1
+ v1 | | 3
+ v2 | | 1
+(3 rows)
+
+-- ordered months in horizontal header, quoted column name
+SELECT v, to_char(d, 'Mon') AS "month name", EXTRACT(month FROM d) AS num,
+ count(*) FROM ctv_data GROUP BY 1,2,3 ORDER BY 1
+ \crosstabview v "month name" 4 num
+ v | Jan | Apr | Jul | Dec
+----+-----+-----+-----+-----
+ v0 | | | 2 | 1
+ v1 | | 2 | 1 |
+ v2 | 1 | | |
+(3 rows)
+
+-- ordered months in vertical header, ordered years in horizontal header
+SELECT EXTRACT(year FROM d) AS year, to_char(d,'Mon') AS """month"" name",
+ EXTRACT(month FROM d) AS month,
+ format('sum=%s avg=%s', sum(i), avg(i)::numeric(2,1))
+ FROM ctv_data
+ GROUP BY EXTRACT(year FROM d), to_char(d,'Mon'), EXTRACT(month FROM d)
+ORDER BY month
+\crosstabview """month"" name" year format year
+ "month" name | 2014 | 2015
+--------------+-----------------+----------------
+ Jan | | sum=3 avg=3.0
+ Apr | | sum=10 avg=5.0
+ Jul | sum=5 avg=5.0 | sum=4 avg=4.0
+ Dec | sum=-3 avg=-3.0 |
+(4 rows)
+
+-- combine contents vertically into the same cell (V/H duplicates)
+SELECT v, h, string_agg(c, E'\n') FROM ctv_data GROUP BY v, h ORDER BY 1,2,3
+ \crosstabview 1 2 3
+ v | h4 | | h0 | h2 | h1
+----+-----+-----+-----+------+-----
+ v0 | qux+| qux | | |
+ | dbl | | | |
+ v1 | | | baz | foo +|
+ | | | | quux |
+ v2 | | | | | bar
+(3 rows)
+
+-- horizontal ASC order from window function
+SELECT v,h, string_agg(c, E'\n') AS c, row_number() OVER(ORDER BY h) AS r
+FROM ctv_data GROUP BY v, h ORDER BY 1,3,2
+ \crosstabview v h c r
+ v | h0 | h1 | h2 | h4 |
+----+-----+-----+------+-----+-----
+ v0 | | | | qux+| qux
+ | | | | dbl |
+ v1 | baz | | foo +| |
+ | | | quux | |
+ v2 | | bar | | |
+(3 rows)
+
+-- horizontal DESC order from window function
+SELECT v, h, string_agg(c, E'\n') AS c, row_number() OVER(ORDER BY h DESC) AS r
+FROM ctv_data GROUP BY v, h ORDER BY 1,3,2
+ \crosstabview v h c r
+ v | | h4 | h2 | h1 | h0
+----+-----+-----+------+-----+-----
+ v0 | qux | qux+| | |
+ | | dbl | | |
+ v1 | | | foo +| | baz
+ | | | quux | |
+ v2 | | | | bar |
+(3 rows)
+
+-- horizontal ASC order from window function, NULLs pushed rightmost
+SELECT v,h, string_agg(c, E'\n') AS c, row_number() OVER(ORDER BY h NULLS LAST) AS r
+FROM ctv_data GROUP BY v, h ORDER BY 1,3,2
+ \crosstabview v h c r
+ v | h0 | h1 | h2 | h4 |
+----+-----+-----+------+-----+-----
+ v0 | | | | qux+| qux
+ | | | | dbl |
+ v1 | baz | | foo +| |
+ | | | quux | |
+ v2 | | bar | | |
+(3 rows)
+
+-- only null, no column name, 2 columns: error
+SELECT null,null \crosstabview
+\crosstabview: query must return at least three columns
+-- only null, no column name, 3 columns: works
+SELECT null,null,null \crosstabview
+ ?column? |
+----------+--
+ |
+(1 row)
+
+-- null display
+\pset null '#null#'
+SELECT v,h, string_agg(i::text, E'\n') AS i FROM ctv_data
+GROUP BY v, h ORDER BY h,v
+ \crosstabview v h i
+ v | h0 | h1 | h2 | h4 | #null#
+----+--------+----+----+----+--------
+ v1 | #null# | | 3 +| |
+ | | | 7 | |
+ v2 | | 3 | | |
+ v0 | | | | 4 +| 5
+ | | | | -3 |
+(3 rows)
+
+\pset null ''
+-- refer to columns by position
+SELECT v,h,string_agg(i::text, E'\n'), string_agg(c, E'\n')
+FROM ctv_data GROUP BY v, h ORDER BY h,v
+ \crosstabview 2 1 4
+ h | v1 | v2 | v0
+----+------+-----+-----
+ h0 | baz | |
+ h1 | | bar |
+ h2 | foo +| |
+ | quux | |
+ h4 | | | qux+
+ | | | dbl
+ | | | qux
+(5 rows)
+
+-- refer to columns by positions and names mixed
+SELECT v,h, string_agg(i::text, E'\n') AS i, string_agg(c, E'\n') AS c
+FROM ctv_data GROUP BY v, h ORDER BY h,v
+ \crosstabview 1 "h" 4
+ v | h0 | h1 | h2 | h4 |
+----+-----+-----+------+-----+-----
+ v1 | baz | | foo +| |
+ | | | quux | |
+ v2 | | bar | | |
+ v0 | | | | qux+| qux
+ | | | | dbl |
+(3 rows)
+
+-- refer to columns by quoted names, check downcasing of unquoted name
+SELECT 1 as "22", 2 as b, 3 as "Foo"
+ \crosstabview "22" B "Foo"
+ 22 | 2
+----+---
+ 1 | 3
+(1 row)
+
+-- error: bad column name
+SELECT v,h,c,i FROM ctv_data
+ \crosstabview v h j
+\crosstabview: column name not found: "j"
+-- error: need to quote name
+SELECT 1 as "22", 2 as b, 3 as "Foo"
+ \crosstabview 1 2 Foo
+\crosstabview: column name not found: "foo"
+-- error: need to not quote name
+SELECT 1 as "22", 2 as b, 3 as "Foo"
+ \crosstabview 1 "B" "Foo"
+\crosstabview: column name not found: "B"
+-- error: bad column number
+SELECT v,h,i,c FROM ctv_data
+ \crosstabview 2 1 5
+\crosstabview: column number 5 is out of range 1..4
+-- error: same H and V columns
+SELECT v,h,i,c FROM ctv_data
+ \crosstabview 2 h 4
+\crosstabview: vertical and horizontal headers must be different columns
+-- error: too many columns
+SELECT a,a,1 FROM generate_series(1,3000) AS a
+ \crosstabview
+\crosstabview: maximum number of columns (1600) exceeded
+-- error: only one column
+SELECT 1 \crosstabview
+\crosstabview: query must return at least three columns
+DROP TABLE ctv_data;
+-- check error reporting (bug #14476)
+CREATE TABLE ctv_data (x int, y int, v text);
+INSERT INTO ctv_data SELECT 1, x, '*' || x FROM generate_series(1,10) x;
+SELECT * FROM ctv_data \crosstabview
+ x | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
+---+----+----+----+----+----+----+----+----+----+-----
+ 1 | *1 | *2 | *3 | *4 | *5 | *6 | *7 | *8 | *9 | *10
+(1 row)
+
+INSERT INTO ctv_data VALUES (1, 10, '*'); -- duplicate data to cause error
+SELECT * FROM ctv_data \crosstabview
+\crosstabview: query result contains multiple data values for row "1", column "10"
+DROP TABLE ctv_data;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
new file mode 100644
index 0000000..7c7e022
--- /dev/null
+++ b/src/test/regress/expected/publication.out
@@ -0,0 +1,344 @@
+--
+-- 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);
+RESET client_min_messages;
+ALTER PUBLICATION testpub_default SET (publish = update);
+-- error cases
+CREATE PUBLICATION testpub_xxx WITH (foo);
+ERROR: unrecognized publication parameter: "foo"
+CREATE PUBLICATION testpub_xxx WITH (publish = 'cluster, vacuum');
+ERROR: unrecognized "publish" value: "cluster"
+CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
+ERROR: conflicting or redundant options
+\dRp
+ List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f
+ testpub_default | regress_publication_user | f | f | t | f | f | f
+(2 rows)
+
+ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete');
+\dRp
+ List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f
+ testpub_default | regress_publication_user | f | t | t | t | f | f
+(2 rows)
+
+--- 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;
+ALTER PUBLICATION testpub_foralltables SET (publish = 'insert, update');
+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, btree (id)
+Publications:
+ "testpub_foralltables"
+
+\dRp+ testpub_foralltables
+ Publication testpub_foralltables
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | t | t | t | f | f | 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;
+CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3;
+RESET client_min_messages;
+\dRp+ testpub3
+ Publication testpub3
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables:
+ "public.testpub_tbl3"
+ "public.testpub_tbl3a"
+
+\dRp+ testpub4
+ Publication testpub4
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables:
+ "public.testpub_tbl3"
+
+DROP TABLE testpub_tbl3, testpub_tbl3a;
+DROP PUBLICATION testpub3, 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;
+\dRp+ testpub_forparted
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables:
+ "public.testpub_parted"
+
+-- should now fail, because parent's publication replicates updates
+UPDATE testpub_parted1 SET a = 1;
+ERROR: cannot update table "testpub_parted1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER TABLE testpub_parted DETACH PARTITION testpub_parted1;
+-- works again, because parent's publication is no longer considered
+UPDATE testpub_parted1 SET a = 1;
+ALTER PUBLICATION testpub_forparted SET (publish_via_partition_root = true);
+\dRp+ testpub_forparted
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | t
+Tables:
+ "public.testpub_parted"
+
+-- still fail, because parent's publication replicates updates
+UPDATE testpub_parted2 SET a = 2;
+ERROR: cannot update table "testpub_parted2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub_forparted DROP TABLE testpub_parted;
+-- works again, because update is no longer replicated
+UPDATE testpub_parted2 SET a = 2;
+-- publication includes both the parent table and the child table
+ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted, testpub_parted2;
+-- only parent is listed as being in publication, not the partition
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+-------------------+------------+----------------
+ testpub_forparted | public | testpub_parted
+(1 row)
+
+DROP TABLE testpub_parted1, testpub_parted2;
+DROP PUBLICATION testpub_forparted, testpub_forparted1;
+-- Test cache invalidation FOR ALL TABLES publication
+SET client_min_messages = 'ERROR';
+CREATE TABLE testpub_tbl4(a int);
+INSERT INTO testpub_tbl4 values(1);
+UPDATE testpub_tbl4 set a = 2;
+CREATE PUBLICATION testpub_foralltables FOR ALL TABLES;
+RESET client_min_messages;
+-- fail missing REPLICA IDENTITY
+UPDATE testpub_tbl4 set a = 3;
+ERROR: cannot update table "testpub_tbl4" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpub_foralltables;
+-- should pass after dropping the publication
+UPDATE testpub_tbl4 set a = 3;
+DROP TABLE testpub_tbl4;
+-- fail - view
+CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_view;
+ERROR: "testpub_view" is not a table
+DETAIL: Only tables can be added to publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1, pub_test.testpub_nopk;
+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:
+ "pub_test.testpub_nopk"
+ "public.testpub_tbl1"
+
+-- fail - view
+ALTER PUBLICATION testpub_default ADD TABLE testpub_view;
+ERROR: "testpub_view" is not a table
+DETAIL: Only tables can be added to publications.
+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
+ Table "pub_test.testpub_nopk"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ foo | integer | | | | plain | |
+ bar | integer | | | | plain | |
+Publications:
+ "testpib_ins_trunct"
+ "testpub_default"
+ "testpub_fortbl"
+
+\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, btree (id)
+Publications:
+ "testpib_ins_trunct"
+ "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 | f | f
+Tables:
+ "pub_test.testpub_nopk"
+ "public.testpub_tbl1"
+
+ALTER PUBLICATION testpub_default DROP TABLE testpub_tbl1, pub_test.testpub_nopk;
+-- 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, btree (id)
+Publications:
+ "testpib_ins_trunct"
+ "testpub_fortbl"
+
+-- verify relation cache invalidation when a primary key is added using
+-- an existing index
+CREATE TABLE pub_test.testpub_addpk (id int not null, data int);
+ALTER PUBLICATION testpub_default ADD TABLE pub_test.testpub_addpk;
+INSERT INTO pub_test.testpub_addpk VALUES(1, 11);
+CREATE UNIQUE INDEX testpub_addpk_id_idx ON pub_test.testpub_addpk(id);
+-- fail:
+UPDATE pub_test.testpub_addpk SET id = 2;
+ERROR: cannot update table "testpub_addpk" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER TABLE pub_test.testpub_addpk ADD PRIMARY KEY USING INDEX testpub_addpk_id_idx;
+-- now it should work:
+UPDATE pub_test.testpub_addpk SET id = 2;
+DROP TABLE pub_test.testpub_addpk;
+-- permissions
+SET ROLE regress_publication_user2;
+CREATE PUBLICATION testpub2; -- fail
+ERROR: permission denied for database regression
+SET ROLE regress_publication_user;
+GRANT CREATE ON DATABASE regression 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 regression FROM regress_publication_user2;
+DROP TABLE testpub_parted;
+DROP VIEW testpub_view;
+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 | f | 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 | f | 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 | f | f
+(1 row)
+
+DROP PUBLICATION testpub_default;
+DROP PUBLICATION testpib_ins_trunct;
+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/random.out b/src/test/regress/expected/random.out
new file mode 100644
index 0000000..a919b28
--- /dev/null
+++ b/src/test/regress/expected/random.out
@@ -0,0 +1,53 @@
+--
+-- RANDOM
+-- Test the random function
+--
+-- count the number of tuples originally, should be 1000
+SELECT count(*) FROM onek;
+ count
+-------
+ 1000
+(1 row)
+
+-- pick three random rows, they shouldn't match
+(SELECT unique1 AS random
+ FROM onek ORDER BY random() LIMIT 1)
+INTERSECT
+(SELECT unique1 AS random
+ FROM onek ORDER BY random() LIMIT 1)
+INTERSECT
+(SELECT unique1 AS random
+ FROM onek ORDER BY random() LIMIT 1);
+ random
+--------
+(0 rows)
+
+-- count roughly 1/10 of the tuples
+CREATE TABLE RANDOM_TBL AS
+ SELECT count(*) AS random
+ FROM onek WHERE random() < 1.0/10;
+-- select again, the count should be different
+INSERT INTO RANDOM_TBL (random)
+ SELECT count(*)
+ FROM onek WHERE random() < 1.0/10;
+-- select again, the count should be different
+INSERT INTO RANDOM_TBL (random)
+ SELECT count(*)
+ FROM onek WHERE random() < 1.0/10;
+-- select again, the count should be different
+INSERT INTO RANDOM_TBL (random)
+ SELECT count(*)
+ FROM onek WHERE random() < 1.0/10;
+-- now test that they are different counts
+SELECT random, count(random) FROM RANDOM_TBL
+ GROUP BY random HAVING count(random) > 3;
+ random | count
+--------+-------
+(0 rows)
+
+SELECT AVG(random) FROM RANDOM_TBL
+ HAVING AVG(random) NOT BETWEEN 80 AND 120;
+ avg
+-----
+(0 rows)
+
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
new file mode 100644
index 0000000..2334a13
--- /dev/null
+++ b/src/test/regress/expected/rangefuncs.out
@@ -0,0 +1,2447 @@
+CREATE TABLE rngfunc2(rngfuncid int, f2 int);
+INSERT INTO rngfunc2 VALUES(1, 11);
+INSERT INTO rngfunc2 VALUES(2, 22);
+INSERT INTO rngfunc2 VALUES(1, 111);
+CREATE FUNCTION rngfunct(int) returns setof rngfunc2 as 'SELECT * FROM rngfunc2 WHERE rngfuncid = $1 ORDER BY f2;' LANGUAGE SQL;
+-- function with ORDINALITY
+select * from rngfunct(1) with ordinality as z(a,b,ord);
+ a | b | ord
+---+-----+-----
+ 1 | 11 | 1
+ 1 | 111 | 2
+(2 rows)
+
+select * from rngfunct(1) with ordinality as z(a,b,ord) where b > 100; -- ordinal 2, not 1
+ a | b | ord
+---+-----+-----
+ 1 | 111 | 2
+(1 row)
+
+-- ordinality vs. column names and types
+select a,b,ord from rngfunct(1) with ordinality as z(a,b,ord);
+ a | b | ord
+---+-----+-----
+ 1 | 11 | 1
+ 1 | 111 | 2
+(2 rows)
+
+select a,ord from unnest(array['a','b']) with ordinality as z(a,ord);
+ a | ord
+---+-----
+ a | 1
+ b | 2
+(2 rows)
+
+select * from unnest(array['a','b']) with ordinality as z(a,ord);
+ a | ord
+---+-----
+ a | 1
+ b | 2
+(2 rows)
+
+select a,ord from unnest(array[1.0::float8]) with ordinality as z(a,ord);
+ a | ord
+---+-----
+ 1 | 1
+(1 row)
+
+select * from unnest(array[1.0::float8]) with ordinality as z(a,ord);
+ a | ord
+---+-----
+ 1 | 1
+(1 row)
+
+select row_to_json(s.*) from generate_series(11,14) with ordinality s;
+ row_to_json
+-------------------------
+ {"s":11,"ordinality":1}
+ {"s":12,"ordinality":2}
+ {"s":13,"ordinality":3}
+ {"s":14,"ordinality":4}
+(4 rows)
+
+-- ordinality vs. views
+create temporary view vw_ord as select * from (values (1)) v(n) join rngfunct(1) with ordinality as z(a,b,ord) on (n=ord);
+select * from vw_ord;
+ n | a | b | ord
+---+---+----+-----
+ 1 | 1 | 11 | 1
+(1 row)
+
+select definition from pg_views where viewname='vw_ord';
+ definition
+-------------------------------------------------------------------------
+ SELECT v.n, +
+ z.a, +
+ z.b, +
+ z.ord +
+ FROM (( VALUES (1)) v(n) +
+ JOIN rngfunct(1) WITH ORDINALITY z(a, b, ord) ON ((v.n = z.ord)));
+(1 row)
+
+drop view vw_ord;
+-- multiple functions
+select * from rows from(rngfunct(1),rngfunct(2)) with ordinality as z(a,b,c,d,ord);
+ a | b | c | d | ord
+---+-----+---+----+-----
+ 1 | 11 | 2 | 22 | 1
+ 1 | 111 | | | 2
+(2 rows)
+
+create temporary view vw_ord as select * from (values (1)) v(n) join rows from(rngfunct(1),rngfunct(2)) with ordinality as z(a,b,c,d,ord) on (n=ord);
+select * from vw_ord;
+ n | a | b | c | d | ord
+---+---+----+---+----+-----
+ 1 | 1 | 11 | 2 | 22 | 1
+(1 row)
+
+select definition from pg_views where viewname='vw_ord';
+ definition
+-------------------------------------------------------------------------------------------------------
+ SELECT v.n, +
+ z.a, +
+ z.b, +
+ z.c, +
+ z.d, +
+ z.ord +
+ FROM (( VALUES (1)) v(n) +
+ JOIN ROWS FROM(rngfunct(1), rngfunct(2)) WITH ORDINALITY z(a, b, c, d, ord) ON ((v.n = z.ord)));
+(1 row)
+
+drop view vw_ord;
+-- expansions of unnest()
+select * from unnest(array[10,20],array['foo','bar'],array[1.0]);
+ unnest | unnest | unnest
+--------+--------+--------
+ 10 | foo | 1.0
+ 20 | bar |
+(2 rows)
+
+select * from unnest(array[10,20],array['foo','bar'],array[1.0]) with ordinality as z(a,b,c,ord);
+ a | b | c | ord
+----+-----+-----+-----
+ 10 | foo | 1.0 | 1
+ 20 | bar | | 2
+(2 rows)
+
+select * from rows from(unnest(array[10,20],array['foo','bar'],array[1.0])) with ordinality as z(a,b,c,ord);
+ a | b | c | ord
+----+-----+-----+-----
+ 10 | foo | 1.0 | 1
+ 20 | bar | | 2
+(2 rows)
+
+select * from rows from(unnest(array[10,20],array['foo','bar']), generate_series(101,102)) with ordinality as z(a,b,c,ord);
+ a | b | c | ord
+----+-----+-----+-----
+ 10 | foo | 101 | 1
+ 20 | bar | 102 | 2
+(2 rows)
+
+create temporary view vw_ord as select * from unnest(array[10,20],array['foo','bar'],array[1.0]) as z(a,b,c);
+select * from vw_ord;
+ a | b | c
+----+-----+-----
+ 10 | foo | 1.0
+ 20 | bar |
+(2 rows)
+
+select definition from pg_views where viewname='vw_ord';
+ definition
+----------------------------------------------------------------------------------------
+ SELECT z.a, +
+ z.b, +
+ z.c +
+ FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
+(1 row)
+
+drop view vw_ord;
+create temporary view vw_ord as select * from rows from(unnest(array[10,20],array['foo','bar'],array[1.0])) as z(a,b,c);
+select * from vw_ord;
+ a | b | c
+----+-----+-----
+ 10 | foo | 1.0
+ 20 | bar |
+(2 rows)
+
+select definition from pg_views where viewname='vw_ord';
+ definition
+----------------------------------------------------------------------------------------
+ SELECT z.a, +
+ z.b, +
+ z.c +
+ FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
+(1 row)
+
+drop view vw_ord;
+create temporary view vw_ord as select * from rows from(unnest(array[10,20],array['foo','bar']), generate_series(1,2)) as z(a,b,c);
+select * from vw_ord;
+ a | b | c
+----+-----+---
+ 10 | foo | 1
+ 20 | bar | 2
+(2 rows)
+
+select definition from pg_views where viewname='vw_ord';
+ definition
+----------------------------------------------------------------------------------------------------------------------
+ SELECT z.a, +
+ z.b, +
+ z.c +
+ FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
+(1 row)
+
+drop view vw_ord;
+-- ordinality and multiple functions vs. rewind and reverse scan
+begin;
+declare rf_cur scroll cursor for select * from rows from(generate_series(1,5),generate_series(1,2)) with ordinality as g(i,j,o);
+fetch all from rf_cur;
+ i | j | o
+---+---+---
+ 1 | 1 | 1
+ 2 | 2 | 2
+ 3 | | 3
+ 4 | | 4
+ 5 | | 5
+(5 rows)
+
+fetch backward all from rf_cur;
+ i | j | o
+---+---+---
+ 5 | | 5
+ 4 | | 4
+ 3 | | 3
+ 2 | 2 | 2
+ 1 | 1 | 1
+(5 rows)
+
+fetch all from rf_cur;
+ i | j | o
+---+---+---
+ 1 | 1 | 1
+ 2 | 2 | 2
+ 3 | | 3
+ 4 | | 4
+ 5 | | 5
+(5 rows)
+
+fetch next from rf_cur;
+ i | j | o
+---+---+---
+(0 rows)
+
+fetch next from rf_cur;
+ i | j | o
+---+---+---
+(0 rows)
+
+fetch prior from rf_cur;
+ i | j | o
+---+---+---
+ 5 | | 5
+(1 row)
+
+fetch absolute 1 from rf_cur;
+ i | j | o
+---+---+---
+ 1 | 1 | 1
+(1 row)
+
+fetch next from rf_cur;
+ i | j | o
+---+---+---
+ 2 | 2 | 2
+(1 row)
+
+fetch next from rf_cur;
+ i | j | o
+---+---+---
+ 3 | | 3
+(1 row)
+
+fetch next from rf_cur;
+ i | j | o
+---+---+---
+ 4 | | 4
+(1 row)
+
+fetch prior from rf_cur;
+ i | j | o
+---+---+---
+ 3 | | 3
+(1 row)
+
+fetch prior from rf_cur;
+ i | j | o
+---+---+---
+ 2 | 2 | 2
+(1 row)
+
+fetch prior from rf_cur;
+ i | j | o
+---+---+---
+ 1 | 1 | 1
+(1 row)
+
+commit;
+-- function with implicit LATERAL
+select * from rngfunc2, rngfunct(rngfunc2.rngfuncid) z where rngfunc2.f2 = z.f2;
+ rngfuncid | f2 | rngfuncid | f2
+-----------+-----+-----------+-----
+ 1 | 11 | 1 | 11
+ 2 | 22 | 2 | 22
+ 1 | 111 | 1 | 111
+(3 rows)
+
+-- function with implicit LATERAL and explicit ORDINALITY
+select * from rngfunc2, rngfunct(rngfunc2.rngfuncid) with ordinality as z(rngfuncid,f2,ord) where rngfunc2.f2 = z.f2;
+ rngfuncid | f2 | rngfuncid | f2 | ord
+-----------+-----+-----------+-----+-----
+ 1 | 11 | 1 | 11 | 1
+ 2 | 22 | 2 | 22 | 1
+ 1 | 111 | 1 | 111 | 2
+(3 rows)
+
+-- function in subselect
+select * from rngfunc2 where f2 in (select f2 from rngfunct(rngfunc2.rngfuncid) z where z.rngfuncid = rngfunc2.rngfuncid) ORDER BY 1,2;
+ rngfuncid | f2
+-----------+-----
+ 1 | 11
+ 1 | 111
+ 2 | 22
+(3 rows)
+
+-- function in subselect
+select * from rngfunc2 where f2 in (select f2 from rngfunct(1) z where z.rngfuncid = rngfunc2.rngfuncid) ORDER BY 1,2;
+ rngfuncid | f2
+-----------+-----
+ 1 | 11
+ 1 | 111
+(2 rows)
+
+-- function in subselect
+select * from rngfunc2 where f2 in (select f2 from rngfunct(rngfunc2.rngfuncid) z where z.rngfuncid = 1) ORDER BY 1,2;
+ rngfuncid | f2
+-----------+-----
+ 1 | 11
+ 1 | 111
+(2 rows)
+
+-- nested functions
+select rngfunct.rngfuncid, rngfunct.f2 from rngfunct(sin(pi()/2)::int) ORDER BY 1,2;
+ rngfuncid | f2
+-----------+-----
+ 1 | 11
+ 1 | 111
+(2 rows)
+
+CREATE TABLE rngfunc (rngfuncid int, rngfuncsubid int, rngfuncname text, primary key(rngfuncid,rngfuncsubid));
+INSERT INTO rngfunc VALUES(1,1,'Joe');
+INSERT INTO rngfunc VALUES(1,2,'Ed');
+INSERT INTO rngfunc VALUES(2,1,'Mary');
+-- sql, proretset = f, prorettype = b
+CREATE FUNCTION getrngfunc1(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc1(1) AS t1;
+ t1
+----
+ 1
+(1 row)
+
+SELECT * FROM getrngfunc1(1) WITH ORDINALITY AS t1(v,o);
+ v | o
+---+---
+ 1 | 1
+(1 row)
+
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc1(1);
+SELECT * FROM vw_getrngfunc;
+ getrngfunc1
+-------------
+ 1
+(1 row)
+
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc1(1) WITH ORDINALITY as t1(v,o);
+SELECT * FROM vw_getrngfunc;
+ v | o
+---+---
+ 1 | 1
+(1 row)
+
+DROP VIEW vw_getrngfunc;
+-- sql, proretset = t, prorettype = b
+CREATE FUNCTION getrngfunc2(int) RETURNS setof int AS 'SELECT rngfuncid FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc2(1) AS t1;
+ t1
+----
+ 1
+ 1
+(2 rows)
+
+SELECT * FROM getrngfunc2(1) WITH ORDINALITY AS t1(v,o);
+ v | o
+---+---
+ 1 | 1
+ 1 | 2
+(2 rows)
+
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc2(1);
+SELECT * FROM vw_getrngfunc;
+ getrngfunc2
+-------------
+ 1
+ 1
+(2 rows)
+
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc2(1) WITH ORDINALITY AS t1(v,o);
+SELECT * FROM vw_getrngfunc;
+ v | o
+---+---
+ 1 | 1
+ 1 | 2
+(2 rows)
+
+DROP VIEW vw_getrngfunc;
+-- sql, proretset = t, prorettype = b
+CREATE FUNCTION getrngfunc3(int) RETURNS setof text AS 'SELECT rngfuncname FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc3(1) AS t1;
+ t1
+-----
+ Joe
+ Ed
+(2 rows)
+
+SELECT * FROM getrngfunc3(1) WITH ORDINALITY AS t1(v,o);
+ v | o
+-----+---
+ Joe | 1
+ Ed | 2
+(2 rows)
+
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc3(1);
+SELECT * FROM vw_getrngfunc;
+ getrngfunc3
+-------------
+ Joe
+ Ed
+(2 rows)
+
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc3(1) WITH ORDINALITY AS t1(v,o);
+SELECT * FROM vw_getrngfunc;
+ v | o
+-----+---
+ Joe | 1
+ Ed | 2
+(2 rows)
+
+DROP VIEW vw_getrngfunc;
+-- sql, proretset = f, prorettype = c
+CREATE FUNCTION getrngfunc4(int) RETURNS rngfunc AS 'SELECT * FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc4(1) AS t1;
+ rngfuncid | rngfuncsubid | rngfuncname
+-----------+--------------+-------------
+ 1 | 1 | Joe
+(1 row)
+
+SELECT * FROM getrngfunc4(1) WITH ORDINALITY AS t1(a,b,c,o);
+ a | b | c | o
+---+---+-----+---
+ 1 | 1 | Joe | 1
+(1 row)
+
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc4(1);
+SELECT * FROM vw_getrngfunc;
+ rngfuncid | rngfuncsubid | rngfuncname
+-----------+--------------+-------------
+ 1 | 1 | Joe
+(1 row)
+
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc4(1) WITH ORDINALITY AS t1(a,b,c,o);
+SELECT * FROM vw_getrngfunc;
+ a | b | c | o
+---+---+-----+---
+ 1 | 1 | Joe | 1
+(1 row)
+
+DROP VIEW vw_getrngfunc;
+-- sql, proretset = t, prorettype = c
+CREATE FUNCTION getrngfunc5(int) RETURNS setof rngfunc AS 'SELECT * FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc5(1) AS t1;
+ rngfuncid | rngfuncsubid | rngfuncname
+-----------+--------------+-------------
+ 1 | 1 | Joe
+ 1 | 2 | Ed
+(2 rows)
+
+SELECT * FROM getrngfunc5(1) WITH ORDINALITY AS t1(a,b,c,o);
+ a | b | c | o
+---+---+-----+---
+ 1 | 1 | Joe | 1
+ 1 | 2 | Ed | 2
+(2 rows)
+
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc5(1);
+SELECT * FROM vw_getrngfunc;
+ rngfuncid | rngfuncsubid | rngfuncname
+-----------+--------------+-------------
+ 1 | 1 | Joe
+ 1 | 2 | Ed
+(2 rows)
+
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc5(1) WITH ORDINALITY AS t1(a,b,c,o);
+SELECT * FROM vw_getrngfunc;
+ a | b | c | o
+---+---+-----+---
+ 1 | 1 | Joe | 1
+ 1 | 2 | Ed | 2
+(2 rows)
+
+DROP VIEW vw_getrngfunc;
+-- sql, proretset = f, prorettype = record
+CREATE FUNCTION getrngfunc6(int) RETURNS RECORD AS 'SELECT * FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc6(1) AS t1(rngfuncid int, rngfuncsubid int, rngfuncname text);
+ rngfuncid | rngfuncsubid | rngfuncname
+-----------+--------------+-------------
+ 1 | 1 | Joe
+(1 row)
+
+SELECT * FROM ROWS FROM( getrngfunc6(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text) ) WITH ORDINALITY;
+ rngfuncid | rngfuncsubid | rngfuncname | ordinality
+-----------+--------------+-------------+------------
+ 1 | 1 | Joe | 1
+(1 row)
+
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc6(1) AS
+(rngfuncid int, rngfuncsubid int, rngfuncname text);
+SELECT * FROM vw_getrngfunc;
+ rngfuncid | rngfuncsubid | rngfuncname
+-----------+--------------+-------------
+ 1 | 1 | Joe
+(1 row)
+
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS
+ SELECT * FROM ROWS FROM( getrngfunc6(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text) )
+ WITH ORDINALITY;
+SELECT * FROM vw_getrngfunc;
+ rngfuncid | rngfuncsubid | rngfuncname | ordinality
+-----------+--------------+-------------+------------
+ 1 | 1 | Joe | 1
+(1 row)
+
+DROP VIEW vw_getrngfunc;
+-- sql, proretset = t, prorettype = record
+CREATE FUNCTION getrngfunc7(int) RETURNS setof record AS 'SELECT * FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc7(1) AS t1(rngfuncid int, rngfuncsubid int, rngfuncname text);
+ rngfuncid | rngfuncsubid | rngfuncname
+-----------+--------------+-------------
+ 1 | 1 | Joe
+ 1 | 2 | Ed
+(2 rows)
+
+SELECT * FROM ROWS FROM( getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text) ) WITH ORDINALITY;
+ rngfuncid | rngfuncsubid | rngfuncname | ordinality
+-----------+--------------+-------------+------------
+ 1 | 1 | Joe | 1
+ 1 | 2 | Ed | 2
+(2 rows)
+
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc7(1) AS
+(rngfuncid int, rngfuncsubid int, rngfuncname text);
+SELECT * FROM vw_getrngfunc;
+ rngfuncid | rngfuncsubid | rngfuncname
+-----------+--------------+-------------
+ 1 | 1 | Joe
+ 1 | 2 | Ed
+(2 rows)
+
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS
+ SELECT * FROM ROWS FROM( getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text) )
+ WITH ORDINALITY;
+SELECT * FROM vw_getrngfunc;
+ rngfuncid | rngfuncsubid | rngfuncname | ordinality
+-----------+--------------+-------------+------------
+ 1 | 1 | Joe | 1
+ 1 | 2 | Ed | 2
+(2 rows)
+
+DROP VIEW vw_getrngfunc;
+-- plpgsql, proretset = f, prorettype = b
+CREATE FUNCTION getrngfunc8(int) RETURNS int AS 'DECLARE rngfuncint int; BEGIN SELECT rngfuncid into rngfuncint FROM rngfunc WHERE rngfuncid = $1; RETURN rngfuncint; END;' LANGUAGE plpgsql;
+SELECT * FROM getrngfunc8(1) AS t1;
+ t1
+----
+ 1
+(1 row)
+
+SELECT * FROM getrngfunc8(1) WITH ORDINALITY AS t1(v,o);
+ v | o
+---+---
+ 1 | 1
+(1 row)
+
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc8(1);
+SELECT * FROM vw_getrngfunc;
+ getrngfunc8
+-------------
+ 1
+(1 row)
+
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc8(1) WITH ORDINALITY AS t1(v,o);
+SELECT * FROM vw_getrngfunc;
+ v | o
+---+---
+ 1 | 1
+(1 row)
+
+DROP VIEW vw_getrngfunc;
+-- plpgsql, proretset = f, prorettype = c
+CREATE FUNCTION getrngfunc9(int) RETURNS rngfunc AS 'DECLARE rngfunctup rngfunc%ROWTYPE; BEGIN SELECT * into rngfunctup FROM rngfunc WHERE rngfuncid = $1; RETURN rngfunctup; END;' LANGUAGE plpgsql;
+SELECT * FROM getrngfunc9(1) AS t1;
+ rngfuncid | rngfuncsubid | rngfuncname
+-----------+--------------+-------------
+ 1 | 1 | Joe
+(1 row)
+
+SELECT * FROM getrngfunc9(1) WITH ORDINALITY AS t1(a,b,c,o);
+ a | b | c | o
+---+---+-----+---
+ 1 | 1 | Joe | 1
+(1 row)
+
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc9(1);
+SELECT * FROM vw_getrngfunc;
+ rngfuncid | rngfuncsubid | rngfuncname
+-----------+--------------+-------------
+ 1 | 1 | Joe
+(1 row)
+
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc9(1) WITH ORDINALITY AS t1(a,b,c,o);
+SELECT * FROM vw_getrngfunc;
+ a | b | c | o
+---+---+-----+---
+ 1 | 1 | Joe | 1
+(1 row)
+
+DROP VIEW vw_getrngfunc;
+-- mix 'n match kinds, to exercise expandRTE and related logic
+select * from rows from(getrngfunc1(1),getrngfunc2(1),getrngfunc3(1),getrngfunc4(1),getrngfunc5(1),
+ getrngfunc6(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text),
+ getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text),
+ getrngfunc8(1),getrngfunc9(1))
+ with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o,p,q,r,s,t,u);
+ a | b | c | d | e | f | g | h | i | j | k | l | m | o | p | q | r | s | t | u
+---+---+-----+---+---+-----+---+---+-----+---+---+-----+---+---+-----+---+---+---+-----+---
+ 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | 1 | Joe | 1
+ | 1 | Ed | | | | 1 | 2 | Ed | | | | 1 | 2 | Ed | | | | | 2
+(2 rows)
+
+select * from rows from(getrngfunc9(1),getrngfunc8(1),
+ getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text),
+ getrngfunc6(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text),
+ getrngfunc5(1),getrngfunc4(1),getrngfunc3(1),getrngfunc2(1),getrngfunc1(1))
+ with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o,p,q,r,s,t,u);
+ a | b | c | d | e | f | g | h | i | j | k | l | m | o | p | q | r | s | t | u
+---+---+-----+---+---+---+-----+---+---+-----+---+---+-----+---+---+-----+-----+---+---+---
+ 1 | 1 | Joe | 1 | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | Joe | 1 | 1 | 1
+ | | | | 1 | 2 | Ed | | | | 1 | 2 | Ed | | | | Ed | 1 | | 2
+(2 rows)
+
+create temporary view vw_rngfunc as
+ select * from rows from(getrngfunc9(1),
+ getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text),
+ getrngfunc1(1))
+ with ordinality as t1(a,b,c,d,e,f,g,n);
+select * from vw_rngfunc;
+ a | b | c | d | e | f | g | n
+---+---+-----+---+---+-----+---+---
+ 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1
+ | | | 1 | 2 | Ed | | 2
+(2 rows)
+
+select pg_get_viewdef('vw_rngfunc');
+ pg_get_viewdef
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ SELECT t1.a, +
+ t1.b, +
+ t1.c, +
+ t1.d, +
+ t1.e, +
+ t1.f, +
+ t1.g, +
+ t1.n +
+ FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
+(1 row)
+
+drop view vw_rngfunc;
+DROP FUNCTION getrngfunc1(int);
+DROP FUNCTION getrngfunc2(int);
+DROP FUNCTION getrngfunc3(int);
+DROP FUNCTION getrngfunc4(int);
+DROP FUNCTION getrngfunc5(int);
+DROP FUNCTION getrngfunc6(int);
+DROP FUNCTION getrngfunc7(int);
+DROP FUNCTION getrngfunc8(int);
+DROP FUNCTION getrngfunc9(int);
+DROP FUNCTION rngfunct(int);
+DROP TABLE rngfunc2;
+DROP TABLE rngfunc;
+-- Rescan tests --
+CREATE TEMPORARY SEQUENCE rngfunc_rescan_seq1;
+CREATE TEMPORARY SEQUENCE rngfunc_rescan_seq2;
+CREATE TYPE rngfunc_rescan_t AS (i integer, s bigint);
+CREATE FUNCTION rngfunc_sql(int,int) RETURNS setof rngfunc_rescan_t AS 'SELECT i, nextval(''rngfunc_rescan_seq1'') FROM generate_series($1,$2) i;' LANGUAGE SQL;
+-- plpgsql functions use materialize mode
+CREATE FUNCTION rngfunc_mat(int,int) RETURNS setof rngfunc_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''rngfunc_rescan_seq2'')); end loop; end;' LANGUAGE plpgsql;
+--invokes ExecReScanFunctionScan - all these cases should materialize the function only once
+-- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function
+-- is on the inner path of a nestloop join
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN rngfunc_sql(11,13) ON (r+i)<100;
+ r | i | s
+---+----+---
+ 1 | 11 | 1
+ 1 | 12 | 2
+ 1 | 13 | 3
+ 2 | 11 | 1
+ 2 | 12 | 2
+ 2 | 13 | 3
+ 3 | 11 | 1
+ 3 | 12 | 2
+ 3 | 13 | 3
+(9 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN rngfunc_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
+ r | i | s | o
+---+----+---+---
+ 1 | 11 | 1 | 1
+ 1 | 12 | 2 | 2
+ 1 | 13 | 3 | 3
+ 2 | 11 | 1 | 1
+ 2 | 12 | 2 | 2
+ 2 | 13 | 3 | 3
+ 3 | 11 | 1 | 1
+ 3 | 12 | 2 | 2
+ 3 | 13 | 3 | 3
+(9 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN rngfunc_mat(11,13) ON (r+i)<100;
+ r | i | s
+---+----+---
+ 1 | 11 | 1
+ 1 | 12 | 2
+ 1 | 13 | 3
+ 2 | 11 | 1
+ 2 | 12 | 2
+ 2 | 13 | 3
+ 3 | 11 | 1
+ 3 | 12 | 2
+ 3 | 13 | 3
+(9 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN rngfunc_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
+ r | i | s | o
+---+----+---+---
+ 1 | 11 | 1 | 1
+ 1 | 12 | 2 | 2
+ 1 | 13 | 3 | 3
+ 2 | 11 | 1 | 1
+ 2 | 12 | 2 | 2
+ 2 | 13 | 3 | 3
+ 3 | 11 | 1 | 1
+ 3 | 12 | 2 | 2
+ 3 | 13 | 3 | 3
+(9 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN ROWS FROM( rngfunc_sql(11,13), rngfunc_mat(11,13) ) WITH ORDINALITY AS f(i1,s1,i2,s2,o) ON (r+i1+i2)<100;
+ r | i1 | s1 | i2 | s2 | o
+---+----+----+----+----+---
+ 1 | 11 | 1 | 11 | 1 | 1
+ 1 | 12 | 2 | 12 | 2 | 2
+ 1 | 13 | 3 | 13 | 3 | 3
+ 2 | 11 | 1 | 11 | 1 | 1
+ 2 | 12 | 2 | 12 | 2 | 2
+ 2 | 13 | 3 | 13 | 3 | 3
+ 3 | 11 | 1 | 11 | 1 | 1
+ 3 | 12 | 2 | 12 | 2 | 2
+ 3 | 13 | 3 | 13 | 3 | 3
+(9 rows)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100;
+ r | i
+---+----
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 2 | 11
+ 2 | 12
+ 2 | 13
+ 3 | 11
+ 3 | 12
+ 3 | 13
+(9 rows)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
+ r | i | o
+---+----+---
+ 1 | 11 | 1
+ 1 | 12 | 2
+ 1 | 13 | 3
+ 2 | 11 | 1
+ 2 | 12 | 2
+ 2 | 13 | 3
+ 3 | 11 | 1
+ 3 | 12 | 2
+ 3 | 13 | 3
+(9 rows)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) f(i) ON (r+i)<100;
+ r | i
+---+----
+ 1 | 10
+ 1 | 20
+ 1 | 30
+ 2 | 10
+ 2 | 20
+ 2 | 30
+ 3 | 10
+ 3 | 20
+ 3 | 30
+(9 rows)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
+ r | i | o
+---+----+---
+ 1 | 10 | 1
+ 1 | 20 | 2
+ 1 | 30 | 3
+ 2 | 10 | 1
+ 2 | 20 | 2
+ 2 | 30 | 3
+ 3 | 10 | 1
+ 3 | 20 | 2
+ 3 | 30 | 3
+(9 rows)
+
+--invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_sql(10+r,13);
+ r | i | s
+---+----+---
+ 1 | 11 | 1
+ 1 | 12 | 2
+ 1 | 13 | 3
+ 2 | 12 | 4
+ 2 | 13 | 5
+ 3 | 13 | 6
+(6 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_sql(10+r,13) WITH ORDINALITY AS f(i,s,o);
+ r | i | s | o
+---+----+---+---
+ 1 | 11 | 1 | 1
+ 1 | 12 | 2 | 2
+ 1 | 13 | 3 | 3
+ 2 | 12 | 4 | 1
+ 2 | 13 | 5 | 2
+ 3 | 13 | 6 | 1
+(6 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_sql(11,10+r);
+ r | i | s
+---+----+---
+ 1 | 11 | 1
+ 2 | 11 | 2
+ 2 | 12 | 3
+ 3 | 11 | 4
+ 3 | 12 | 5
+ 3 | 13 | 6
+(6 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_sql(11,10+r) WITH ORDINALITY AS f(i,s,o);
+ r | i | s | o
+---+----+---+---
+ 1 | 11 | 1 | 1
+ 2 | 11 | 2 | 1
+ 2 | 12 | 3 | 2
+ 3 | 11 | 4 | 1
+ 3 | 12 | 5 | 2
+ 3 | 13 | 6 | 3
+(6 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), rngfunc_sql(r1,r2);
+ r1 | r2 | i | s
+----+----+----+----
+ 11 | 12 | 11 | 1
+ 11 | 12 | 12 | 2
+ 13 | 15 | 13 | 3
+ 13 | 15 | 14 | 4
+ 13 | 15 | 15 | 5
+ 16 | 20 | 16 | 6
+ 16 | 20 | 17 | 7
+ 16 | 20 | 18 | 8
+ 16 | 20 | 19 | 9
+ 16 | 20 | 20 | 10
+(10 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), rngfunc_sql(r1,r2) WITH ORDINALITY AS f(i,s,o);
+ r1 | r2 | i | s | o
+----+----+----+----+---
+ 11 | 12 | 11 | 1 | 1
+ 11 | 12 | 12 | 2 | 2
+ 13 | 15 | 13 | 3 | 1
+ 13 | 15 | 14 | 4 | 2
+ 13 | 15 | 15 | 5 | 3
+ 16 | 20 | 16 | 6 | 1
+ 16 | 20 | 17 | 7 | 2
+ 16 | 20 | 18 | 8 | 3
+ 16 | 20 | 19 | 9 | 4
+ 16 | 20 | 20 | 10 | 5
+(10 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_mat(10+r,13);
+ r | i | s
+---+----+---
+ 1 | 11 | 1
+ 1 | 12 | 2
+ 1 | 13 | 3
+ 2 | 12 | 4
+ 2 | 13 | 5
+ 3 | 13 | 6
+(6 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_mat(10+r,13) WITH ORDINALITY AS f(i,s,o);
+ r | i | s | o
+---+----+---+---
+ 1 | 11 | 1 | 1
+ 1 | 12 | 2 | 2
+ 1 | 13 | 3 | 3
+ 2 | 12 | 4 | 1
+ 2 | 13 | 5 | 2
+ 3 | 13 | 6 | 1
+(6 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_mat(11,10+r);
+ r | i | s
+---+----+---
+ 1 | 11 | 1
+ 2 | 11 | 2
+ 2 | 12 | 3
+ 3 | 11 | 4
+ 3 | 12 | 5
+ 3 | 13 | 6
+(6 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_mat(11,10+r) WITH ORDINALITY AS f(i,s,o);
+ r | i | s | o
+---+----+---+---
+ 1 | 11 | 1 | 1
+ 2 | 11 | 2 | 1
+ 2 | 12 | 3 | 2
+ 3 | 11 | 4 | 1
+ 3 | 12 | 5 | 2
+ 3 | 13 | 6 | 3
+(6 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), rngfunc_mat(r1,r2);
+ r1 | r2 | i | s
+----+----+----+----
+ 11 | 12 | 11 | 1
+ 11 | 12 | 12 | 2
+ 13 | 15 | 13 | 3
+ 13 | 15 | 14 | 4
+ 13 | 15 | 15 | 5
+ 16 | 20 | 16 | 6
+ 16 | 20 | 17 | 7
+ 16 | 20 | 18 | 8
+ 16 | 20 | 19 | 9
+ 16 | 20 | 20 | 10
+(10 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), rngfunc_mat(r1,r2) WITH ORDINALITY AS f(i,s,o);
+ r1 | r2 | i | s | o
+----+----+----+----+---
+ 11 | 12 | 11 | 1 | 1
+ 11 | 12 | 12 | 2 | 2
+ 13 | 15 | 13 | 3 | 1
+ 13 | 15 | 14 | 4 | 2
+ 13 | 15 | 15 | 5 | 3
+ 16 | 20 | 16 | 6 | 1
+ 16 | 20 | 17 | 7 | 2
+ 16 | 20 | 18 | 8 | 3
+ 16 | 20 | 19 | 9 | 4
+ 16 | 20 | 20 | 10 | 5
+(10 rows)
+
+-- selective rescan of multiple functions:
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( rngfunc_sql(11,11), rngfunc_mat(10+r,13) );
+ r | i | s | i | s
+---+----+---+----+---
+ 1 | 11 | 1 | 11 | 1
+ 1 | | | 12 | 2
+ 1 | | | 13 | 3
+ 2 | 11 | 1 | 12 | 4
+ 2 | | | 13 | 5
+ 3 | 11 | 1 | 13 | 6
+(6 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( rngfunc_sql(10+r,13), rngfunc_mat(11,11) );
+ r | i | s | i | s
+---+----+---+----+---
+ 1 | 11 | 1 | 11 | 1
+ 1 | 12 | 2 | |
+ 1 | 13 | 3 | |
+ 2 | 12 | 4 | 11 | 1
+ 2 | 13 | 5 | |
+ 3 | 13 | 6 | 11 | 1
+(6 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( rngfunc_sql(10+r,13), rngfunc_mat(10+r,13) );
+ r | i | s | i | s
+---+----+---+----+---
+ 1 | 11 | 1 | 11 | 1
+ 1 | 12 | 2 | 12 | 2
+ 1 | 13 | 3 | 13 | 3
+ 2 | 12 | 4 | 12 | 4
+ 2 | 13 | 5 | 13 | 5
+ 3 | 13 | 6 | 13 | 6
+(6 rows)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM generate_series(1,2) r1, generate_series(r1,3) r2, ROWS FROM( rngfunc_sql(10+r1,13), rngfunc_mat(10+r2,13) );
+ r1 | r2 | i | s | i | s
+----+----+----+----+----+---
+ 1 | 1 | 11 | 1 | 11 | 1
+ 1 | 1 | 12 | 2 | 12 | 2
+ 1 | 1 | 13 | 3 | 13 | 3
+ 1 | 2 | 11 | 4 | 12 | 4
+ 1 | 2 | 12 | 5 | 13 | 5
+ 1 | 2 | 13 | 6 | |
+ 1 | 3 | 11 | 7 | 13 | 6
+ 1 | 3 | 12 | 8 | |
+ 1 | 3 | 13 | 9 | |
+ 2 | 2 | 12 | 10 | 12 | 7
+ 2 | 2 | 13 | 11 | 13 | 8
+ 2 | 3 | 12 | 12 | 13 | 9
+ 2 | 3 | 13 | 13 | |
+(13 rows)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i);
+ r | i
+---+----
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 2 | 12
+ 2 | 13
+ 2 | 14
+ 2 | 15
+ 2 | 16
+ 2 | 17
+ 2 | 18
+ 3 | 13
+ 3 | 14
+ 3 | 15
+ 3 | 16
+ 3 | 17
+(21 rows)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) WITH ORDINALITY AS f(i,o);
+ r | i | o
+---+----+---
+ 1 | 11 | 1
+ 1 | 12 | 2
+ 1 | 13 | 3
+ 1 | 14 | 4
+ 1 | 15 | 5
+ 1 | 16 | 6
+ 1 | 17 | 7
+ 1 | 18 | 8
+ 1 | 19 | 9
+ 2 | 12 | 1
+ 2 | 13 | 2
+ 2 | 14 | 3
+ 2 | 15 | 4
+ 2 | 16 | 5
+ 2 | 17 | 6
+ 2 | 18 | 7
+ 3 | 13 | 1
+ 3 | 14 | 2
+ 3 | 15 | 3
+ 3 | 16 | 4
+ 3 | 17 | 5
+(21 rows)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) f(i);
+ r | i
+---+----
+ 1 | 10
+ 1 | 20
+ 1 | 30
+ 2 | 20
+ 2 | 40
+ 2 | 60
+ 3 | 30
+ 3 | 60
+ 3 | 90
+(9 rows)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) WITH ORDINALITY AS f(i,o);
+ r | i | o
+---+----+---
+ 1 | 10 | 1
+ 1 | 20 | 2
+ 1 | 30 | 3
+ 2 | 20 | 1
+ 2 | 40 | 2
+ 2 | 60 | 3
+ 3 | 30 | 1
+ 3 | 60 | 2
+ 3 | 90 | 3
+(9 rows)
+
+-- deep nesting
+SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
+ LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
+ LEFT JOIN generate_series(21,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
+ r1 | r1 | r2 | i
+----+----+----+----
+ 1 | 1 | 10 | 21
+ 1 | 1 | 10 | 22
+ 1 | 1 | 10 | 23
+ 1 | 1 | 20 | 21
+ 1 | 1 | 20 | 22
+ 1 | 1 | 20 | 23
+ 1 | 1 | 30 | 21
+ 1 | 1 | 30 | 22
+ 1 | 1 | 30 | 23
+ 2 | 2 | 10 | 21
+ 2 | 2 | 10 | 22
+ 2 | 2 | 10 | 23
+ 2 | 2 | 20 | 21
+ 2 | 2 | 20 | 22
+ 2 | 2 | 20 | 23
+ 2 | 2 | 30 | 21
+ 2 | 2 | 30 | 22
+ 2 | 2 | 30 | 23
+ 3 | 3 | 10 | 21
+ 3 | 3 | 10 | 22
+ 3 | 3 | 10 | 23
+ 3 | 3 | 20 | 21
+ 3 | 3 | 20 | 22
+ 3 | 3 | 20 | 23
+ 3 | 3 | 30 | 21
+ 3 | 3 | 30 | 22
+ 3 | 3 | 30 | 23
+(27 rows)
+
+SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
+ LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
+ LEFT JOIN generate_series(20+r1,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
+ r1 | r1 | r2 | i
+----+----+----+----
+ 1 | 1 | 10 | 21
+ 1 | 1 | 10 | 22
+ 1 | 1 | 10 | 23
+ 1 | 1 | 20 | 21
+ 1 | 1 | 20 | 22
+ 1 | 1 | 20 | 23
+ 1 | 1 | 30 | 21
+ 1 | 1 | 30 | 22
+ 1 | 1 | 30 | 23
+ 2 | 2 | 10 | 22
+ 2 | 2 | 10 | 23
+ 2 | 2 | 20 | 22
+ 2 | 2 | 20 | 23
+ 2 | 2 | 30 | 22
+ 2 | 2 | 30 | 23
+ 3 | 3 | 10 | 23
+ 3 | 3 | 20 | 23
+ 3 | 3 | 30 | 23
+(18 rows)
+
+SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
+ LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
+ LEFT JOIN generate_series(r2,r2+3) f(i) ON ((r2+i)<100) OFFSET 0) s1;
+ r1 | r1 | r2 | i
+----+----+----+----
+ 1 | 1 | 10 | 10
+ 1 | 1 | 10 | 11
+ 1 | 1 | 10 | 12
+ 1 | 1 | 10 | 13
+ 1 | 1 | 20 | 20
+ 1 | 1 | 20 | 21
+ 1 | 1 | 20 | 22
+ 1 | 1 | 20 | 23
+ 1 | 1 | 30 | 30
+ 1 | 1 | 30 | 31
+ 1 | 1 | 30 | 32
+ 1 | 1 | 30 | 33
+ 2 | 2 | 10 | 10
+ 2 | 2 | 10 | 11
+ 2 | 2 | 10 | 12
+ 2 | 2 | 10 | 13
+ 2 | 2 | 20 | 20
+ 2 | 2 | 20 | 21
+ 2 | 2 | 20 | 22
+ 2 | 2 | 20 | 23
+ 2 | 2 | 30 | 30
+ 2 | 2 | 30 | 31
+ 2 | 2 | 30 | 32
+ 2 | 2 | 30 | 33
+ 3 | 3 | 10 | 10
+ 3 | 3 | 10 | 11
+ 3 | 3 | 10 | 12
+ 3 | 3 | 10 | 13
+ 3 | 3 | 20 | 20
+ 3 | 3 | 20 | 21
+ 3 | 3 | 20 | 22
+ 3 | 3 | 20 | 23
+ 3 | 3 | 30 | 30
+ 3 | 3 | 30 | 31
+ 3 | 3 | 30 | 32
+ 3 | 3 | 30 | 33
+(36 rows)
+
+SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
+ LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
+ LEFT JOIN generate_series(r1,2+r2/5) f(i) ON ((r2+i)<100) OFFSET 0) s1;
+ r1 | r1 | r2 | i
+----+----+----+---
+ 1 | 1 | 10 | 1
+ 1 | 1 | 10 | 2
+ 1 | 1 | 10 | 3
+ 1 | 1 | 10 | 4
+ 1 | 1 | 20 | 1
+ 1 | 1 | 20 | 2
+ 1 | 1 | 20 | 3
+ 1 | 1 | 20 | 4
+ 1 | 1 | 20 | 5
+ 1 | 1 | 20 | 6
+ 1 | 1 | 30 | 1
+ 1 | 1 | 30 | 2
+ 1 | 1 | 30 | 3
+ 1 | 1 | 30 | 4
+ 1 | 1 | 30 | 5
+ 1 | 1 | 30 | 6
+ 1 | 1 | 30 | 7
+ 1 | 1 | 30 | 8
+ 2 | 2 | 10 | 2
+ 2 | 2 | 10 | 3
+ 2 | 2 | 10 | 4
+ 2 | 2 | 20 | 2
+ 2 | 2 | 20 | 3
+ 2 | 2 | 20 | 4
+ 2 | 2 | 20 | 5
+ 2 | 2 | 20 | 6
+ 2 | 2 | 30 | 2
+ 2 | 2 | 30 | 3
+ 2 | 2 | 30 | 4
+ 2 | 2 | 30 | 5
+ 2 | 2 | 30 | 6
+ 2 | 2 | 30 | 7
+ 2 | 2 | 30 | 8
+ 3 | 3 | 10 | 3
+ 3 | 3 | 10 | 4
+ 3 | 3 | 20 | 3
+ 3 | 3 | 20 | 4
+ 3 | 3 | 20 | 5
+ 3 | 3 | 20 | 6
+ 3 | 3 | 30 | 3
+ 3 | 3 | 30 | 4
+ 3 | 3 | 30 | 5
+ 3 | 3 | 30 | 6
+ 3 | 3 | 30 | 7
+ 3 | 3 | 30 | 8
+(45 rows)
+
+-- check handling of FULL JOIN with multiple lateral references (bug #15741)
+SELECT *
+FROM (VALUES (1),(2)) v1(r1)
+ LEFT JOIN LATERAL (
+ SELECT *
+ FROM generate_series(1, v1.r1) AS gs1
+ LEFT JOIN LATERAL (
+ SELECT *
+ FROM generate_series(1, gs1) AS gs2
+ LEFT JOIN generate_series(1, gs2) AS gs3 ON TRUE
+ ) AS ss1 ON TRUE
+ FULL JOIN generate_series(1, v1.r1) AS gs4 ON FALSE
+ ) AS ss0 ON TRUE;
+ r1 | gs1 | gs2 | gs3 | gs4
+----+-----+-----+-----+-----
+ 1 | | | | 1
+ 1 | 1 | 1 | 1 |
+ 2 | | | | 1
+ 2 | | | | 2
+ 2 | 1 | 1 | 1 |
+ 2 | 2 | 1 | 1 |
+ 2 | 2 | 2 | 1 |
+ 2 | 2 | 2 | 2 |
+(8 rows)
+
+DROP FUNCTION rngfunc_sql(int,int);
+DROP FUNCTION rngfunc_mat(int,int);
+DROP SEQUENCE rngfunc_rescan_seq1;
+DROP SEQUENCE rngfunc_rescan_seq2;
+--
+-- Test cases involving OUT parameters
+--
+CREATE FUNCTION rngfunc(in f1 int, out f2 int)
+AS 'select $1+1' LANGUAGE sql;
+SELECT rngfunc(42);
+ rngfunc
+---------
+ 43
+(1 row)
+
+SELECT * FROM rngfunc(42);
+ f2
+----
+ 43
+(1 row)
+
+SELECT * FROM rngfunc(42) AS p(x);
+ x
+----
+ 43
+(1 row)
+
+-- explicit spec of return type is OK
+CREATE OR REPLACE FUNCTION rngfunc(in f1 int, out f2 int) RETURNS int
+AS 'select $1+1' LANGUAGE sql;
+-- error, wrong result type
+CREATE OR REPLACE FUNCTION rngfunc(in f1 int, out f2 int) RETURNS float
+AS 'select $1+1' LANGUAGE sql;
+ERROR: function result type must be integer because of OUT parameters
+-- with multiple OUT params you must get a RECORD result
+CREATE OR REPLACE FUNCTION rngfunc(in f1 int, out f2 int, out f3 text) RETURNS int
+AS 'select $1+1' LANGUAGE sql;
+ERROR: function result type must be record because of OUT parameters
+CREATE OR REPLACE FUNCTION rngfunc(in f1 int, out f2 int, out f3 text)
+RETURNS record
+AS 'select $1+1' LANGUAGE sql;
+ERROR: cannot change return type of existing function
+HINT: Use DROP FUNCTION rngfunc(integer) first.
+CREATE OR REPLACE FUNCTION rngfuncr(in f1 int, out f2 int, out text)
+AS $$select $1-1, $1::text || 'z'$$ LANGUAGE sql;
+SELECT f1, rngfuncr(f1) FROM int4_tbl;
+ f1 | rngfuncr
+-------------+----------------------------
+ 0 | (-1,0z)
+ 123456 | (123455,123456z)
+ -123456 | (-123457,-123456z)
+ 2147483647 | (2147483646,2147483647z)
+ -2147483647 | (-2147483648,-2147483647z)
+(5 rows)
+
+SELECT * FROM rngfuncr(42);
+ f2 | column2
+----+---------
+ 41 | 42z
+(1 row)
+
+SELECT * FROM rngfuncr(42) AS p(a,b);
+ a | b
+----+-----
+ 41 | 42z
+(1 row)
+
+CREATE OR REPLACE FUNCTION rngfuncb(in f1 int, inout f2 int, out text)
+AS $$select $2-1, $1::text || 'z'$$ LANGUAGE sql;
+SELECT f1, rngfuncb(f1, f1/2) FROM int4_tbl;
+ f1 | rngfuncb
+-------------+----------------------------
+ 0 | (-1,0z)
+ 123456 | (61727,123456z)
+ -123456 | (-61729,-123456z)
+ 2147483647 | (1073741822,2147483647z)
+ -2147483647 | (-1073741824,-2147483647z)
+(5 rows)
+
+SELECT * FROM rngfuncb(42, 99);
+ f2 | column2
+----+---------
+ 98 | 42z
+(1 row)
+
+SELECT * FROM rngfuncb(42, 99) AS p(a,b);
+ a | b
+----+-----
+ 98 | 42z
+(1 row)
+
+-- Can reference function with or without OUT params for DROP, etc
+DROP FUNCTION rngfunc(int);
+DROP FUNCTION rngfuncr(in f2 int, out f1 int, out text);
+DROP FUNCTION rngfuncb(in f1 int, inout f2 int);
+--
+-- For my next trick, polymorphic OUT parameters
+--
+CREATE FUNCTION dup (f1 anyelement, f2 out anyelement, f3 out anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+SELECT dup(22);
+ dup
+----------------
+ (22,"{22,22}")
+(1 row)
+
+SELECT dup('xyz'); -- fails
+ERROR: could not determine polymorphic type because input has type unknown
+SELECT dup('xyz'::text);
+ dup
+-------------------
+ (xyz,"{xyz,xyz}")
+(1 row)
+
+SELECT * FROM dup('xyz'::text);
+ f2 | f3
+-----+-----------
+ xyz | {xyz,xyz}
+(1 row)
+
+-- fails, as we are attempting to rename first argument
+CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+ERROR: cannot change name of input parameter "f1"
+HINT: Use DROP FUNCTION dup(anyelement) first.
+DROP FUNCTION dup(anyelement);
+-- equivalent behavior, though different name exposed for input arg
+CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+SELECT dup(22);
+ dup
+----------------
+ (22,"{22,22}")
+(1 row)
+
+DROP FUNCTION dup(anyelement);
+-- fails, no way to deduce outputs
+CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+ERROR: cannot determine result data type
+DETAIL: A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
+CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
+AS 'select $1, $2' LANGUAGE sql;
+SELECT dup(22, array[44]);
+ dup
+-----------
+ (22,{44})
+(1 row)
+
+SELECT dup(4.5, array[44]);
+ dup
+------------
+ (4.5,{44})
+(1 row)
+
+SELECT dup(22, array[44::bigint]);
+ dup
+-----------
+ (22,{44})
+(1 row)
+
+SELECT *, pg_typeof(f3), pg_typeof(f4) FROM dup(22, array[44::bigint]);
+ f3 | f4 | pg_typeof | pg_typeof
+----+------+-----------+-----------
+ 22 | {44} | bigint | bigint[]
+(1 row)
+
+DROP FUNCTION dup(f1 anycompatible, f2 anycompatiblearray);
+CREATE FUNCTION dup (f1 anycompatiblerange, f2 out anycompatible, f3 out anycompatiblearray, f4 out anycompatiblerange)
+AS 'select lower($1), array[lower($1), upper($1)], $1' LANGUAGE sql;
+SELECT dup(int4range(4,7));
+ dup
+---------------------
+ (4,"{4,7}","[4,7)")
+(1 row)
+
+SELECT dup(numrange(4,7));
+ dup
+---------------------
+ (4,"{4,7}","[4,7)")
+(1 row)
+
+SELECT dup(textrange('aaa', 'bbb'));
+ dup
+-------------------------------
+ (aaa,"{aaa,bbb}","[aaa,bbb)")
+(1 row)
+
+DROP FUNCTION dup(f1 anycompatiblerange);
+-- fails, no way to deduce outputs
+CREATE FUNCTION bad (f1 anyarray, out f2 anycompatible, out f3 anycompatiblearray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+ERROR: cannot determine result data type
+DETAIL: A result of type anycompatible requires at least one input of type anycompatible, anycompatiblearray, anycompatiblenonarray, anycompatiblerange, or anycompatiblemultirange.
+--
+-- table functions
+--
+CREATE OR REPLACE FUNCTION rngfunc()
+RETURNS TABLE(a int)
+AS $$ SELECT a FROM generate_series(1,5) a(a) $$ LANGUAGE sql;
+SELECT * FROM rngfunc();
+ a
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+DROP FUNCTION rngfunc();
+CREATE OR REPLACE FUNCTION rngfunc(int)
+RETURNS TABLE(a int, b int)
+AS $$ SELECT a, b
+ FROM generate_series(1,$1) a(a),
+ generate_series(1,$1) b(b) $$ LANGUAGE sql;
+SELECT * FROM rngfunc(3);
+ a | b
+---+---
+ 1 | 1
+ 1 | 2
+ 1 | 3
+ 2 | 1
+ 2 | 2
+ 2 | 3
+ 3 | 1
+ 3 | 2
+ 3 | 3
+(9 rows)
+
+DROP FUNCTION rngfunc(int);
+-- case that causes change of typmod knowledge during inlining
+CREATE OR REPLACE FUNCTION rngfunc()
+RETURNS TABLE(a varchar(5))
+AS $$ SELECT 'hello'::varchar(5) $$ LANGUAGE sql STABLE;
+SELECT * FROM rngfunc() GROUP BY 1;
+ a
+-------
+ hello
+(1 row)
+
+DROP FUNCTION rngfunc();
+--
+-- some tests on SQL functions with RETURNING
+--
+create temp table tt(f1 serial, data text);
+create function insert_tt(text) returns int as
+$$ insert into tt(data) values($1) returning f1 $$
+language sql;
+select insert_tt('foo');
+ insert_tt
+-----------
+ 1
+(1 row)
+
+select insert_tt('bar');
+ insert_tt
+-----------
+ 2
+(1 row)
+
+select * from tt;
+ f1 | data
+----+------
+ 1 | foo
+ 2 | bar
+(2 rows)
+
+-- insert will execute to completion even if function needs just 1 row
+create or replace function insert_tt(text) returns int as
+$$ insert into tt(data) values($1),($1||$1) returning f1 $$
+language sql;
+select insert_tt('fool');
+ insert_tt
+-----------
+ 3
+(1 row)
+
+select * from tt;
+ f1 | data
+----+----------
+ 1 | foo
+ 2 | bar
+ 3 | fool
+ 4 | foolfool
+(4 rows)
+
+-- setof does what's expected
+create or replace function insert_tt2(text,text) returns setof int as
+$$ insert into tt(data) values($1),($2) returning f1 $$
+language sql;
+select insert_tt2('foolish','barrish');
+ insert_tt2
+------------
+ 5
+ 6
+(2 rows)
+
+select * from insert_tt2('baz','quux');
+ insert_tt2
+------------
+ 7
+ 8
+(2 rows)
+
+select * from tt;
+ f1 | data
+----+----------
+ 1 | foo
+ 2 | bar
+ 3 | fool
+ 4 | foolfool
+ 5 | foolish
+ 6 | barrish
+ 7 | baz
+ 8 | quux
+(8 rows)
+
+-- limit doesn't prevent execution to completion
+select insert_tt2('foolish','barrish') limit 1;
+ insert_tt2
+------------
+ 9
+(1 row)
+
+select * from tt;
+ f1 | data
+----+----------
+ 1 | foo
+ 2 | bar
+ 3 | fool
+ 4 | foolfool
+ 5 | foolish
+ 6 | barrish
+ 7 | baz
+ 8 | quux
+ 9 | foolish
+ 10 | barrish
+(10 rows)
+
+-- triggers will fire, too
+create function noticetrigger() returns trigger as $$
+begin
+ raise notice 'noticetrigger % %', new.f1, new.data;
+ return null;
+end $$ language plpgsql;
+create trigger tnoticetrigger after insert on tt for each row
+execute procedure noticetrigger();
+select insert_tt2('foolme','barme') limit 1;
+NOTICE: noticetrigger 11 foolme
+NOTICE: noticetrigger 12 barme
+ insert_tt2
+------------
+ 11
+(1 row)
+
+select * from tt;
+ f1 | data
+----+----------
+ 1 | foo
+ 2 | bar
+ 3 | fool
+ 4 | foolfool
+ 5 | foolish
+ 6 | barrish
+ 7 | baz
+ 8 | quux
+ 9 | foolish
+ 10 | barrish
+ 11 | foolme
+ 12 | barme
+(12 rows)
+
+-- and rules work
+create temp table tt_log(f1 int, data text);
+create rule insert_tt_rule as on insert to tt do also
+ insert into tt_log values(new.*);
+select insert_tt2('foollog','barlog') limit 1;
+NOTICE: noticetrigger 13 foollog
+NOTICE: noticetrigger 14 barlog
+ insert_tt2
+------------
+ 13
+(1 row)
+
+select * from tt;
+ f1 | data
+----+----------
+ 1 | foo
+ 2 | bar
+ 3 | fool
+ 4 | foolfool
+ 5 | foolish
+ 6 | barrish
+ 7 | baz
+ 8 | quux
+ 9 | foolish
+ 10 | barrish
+ 11 | foolme
+ 12 | barme
+ 13 | foollog
+ 14 | barlog
+(14 rows)
+
+-- note that nextval() gets executed a second time in the rule expansion,
+-- which is expected.
+select * from tt_log;
+ f1 | data
+----+---------
+ 15 | foollog
+ 16 | barlog
+(2 rows)
+
+-- test case for a whole-row-variable bug
+create function rngfunc1(n integer, out a text, out b text)
+ returns setof record
+ language sql
+ as $$ select 'foo ' || i, 'bar ' || i from generate_series(1,$1) i $$;
+set work_mem='64kB';
+select t.a, t, t.a from rngfunc1(10000) t limit 1;
+ a | t | a
+-------+-------------------+-------
+ foo 1 | ("foo 1","bar 1") | foo 1
+(1 row)
+
+reset work_mem;
+select t.a, t, t.a from rngfunc1(10000) t limit 1;
+ a | t | a
+-------+-------------------+-------
+ foo 1 | ("foo 1","bar 1") | foo 1
+(1 row)
+
+drop function rngfunc1(n integer);
+-- test use of SQL functions returning record
+-- this is supported in some cases where the query doesn't specify
+-- the actual record type ...
+create function array_to_set(anyarray) returns setof record as $$
+ select i AS "index", $1[i] AS "value" from generate_subscripts($1, 1) i
+$$ language sql strict immutable;
+select array_to_set(array['one', 'two']);
+ array_to_set
+--------------
+ (1,one)
+ (2,two)
+(2 rows)
+
+select * from array_to_set(array['one', 'two']) as t(f1 int,f2 text);
+ f1 | f2
+----+-----
+ 1 | one
+ 2 | two
+(2 rows)
+
+select * from array_to_set(array['one', 'two']); -- fail
+ERROR: a column definition list is required for functions returning "record"
+LINE 1: select * from array_to_set(array['one', 'two']);
+ ^
+-- after-the-fact coercion of the columns is now possible, too
+select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text);
+ f1 | f2
+------+-----
+ 1.00 | one
+ 2.00 | two
+(2 rows)
+
+-- and if it doesn't work, you get a compile-time not run-time error
+select * from array_to_set(array['one', 'two']) as t(f1 point,f2 text);
+ERROR: return type mismatch in function declared to return record
+DETAIL: Final statement returns integer instead of point at column 1.
+CONTEXT: SQL function "array_to_set" during startup
+-- with "strict", this function can't be inlined in FROM
+explain (verbose, costs off)
+ select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text);
+ QUERY PLAN
+----------------------------------------------------
+ Function Scan on public.array_to_set t
+ Output: f1, f2
+ Function Call: array_to_set('{one,two}'::text[])
+(3 rows)
+
+-- but without, it can be:
+create or replace function array_to_set(anyarray) returns setof record as $$
+ select i AS "index", $1[i] AS "value" from generate_subscripts($1, 1) i
+$$ language sql immutable;
+select array_to_set(array['one', 'two']);
+ array_to_set
+--------------
+ (1,one)
+ (2,two)
+(2 rows)
+
+select * from array_to_set(array['one', 'two']) as t(f1 int,f2 text);
+ f1 | f2
+----+-----
+ 1 | one
+ 2 | two
+(2 rows)
+
+select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text);
+ f1 | f2
+------+-----
+ 1.00 | one
+ 2.00 | two
+(2 rows)
+
+select * from array_to_set(array['one', 'two']) as t(f1 point,f2 text);
+ERROR: return type mismatch in function declared to return record
+DETAIL: Final statement returns integer instead of point at column 1.
+CONTEXT: SQL function "array_to_set" during inlining
+explain (verbose, costs off)
+ select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text);
+ QUERY PLAN
+--------------------------------------------------------------
+ Function Scan on pg_catalog.generate_subscripts i
+ Output: i.i, ('{one,two}'::text[])[i.i]
+ Function Call: generate_subscripts('{one,two}'::text[], 1)
+(3 rows)
+
+create temp table rngfunc(f1 int8, f2 int8);
+create function testrngfunc() returns record as $$
+ insert into rngfunc values (1,2) returning *;
+$$ language sql;
+select testrngfunc();
+ testrngfunc
+-------------
+ (1,2)
+(1 row)
+
+select * from testrngfunc() as t(f1 int8,f2 int8);
+ f1 | f2
+----+----
+ 1 | 2
+(1 row)
+
+select * from testrngfunc(); -- fail
+ERROR: a column definition list is required for functions returning "record"
+LINE 1: select * from testrngfunc();
+ ^
+drop function testrngfunc();
+create function testrngfunc() returns setof record as $$
+ insert into rngfunc values (1,2), (3,4) returning *;
+$$ language sql;
+select testrngfunc();
+ testrngfunc
+-------------
+ (1,2)
+ (3,4)
+(2 rows)
+
+select * from testrngfunc() as t(f1 int8,f2 int8);
+ f1 | f2
+----+----
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+select * from testrngfunc(); -- fail
+ERROR: a column definition list is required for functions returning "record"
+LINE 1: select * from testrngfunc();
+ ^
+drop function testrngfunc();
+-- Check that typmod imposed by a composite type is honored
+create type rngfunc_type as (f1 numeric(35,6), f2 numeric(35,2));
+create function testrngfunc() returns rngfunc_type as $$
+ select 7.136178319899999964, 7.136178319899999964;
+$$ language sql immutable;
+explain (verbose, costs off)
+select testrngfunc();
+ QUERY PLAN
+-------------------------------------------
+ Result
+ Output: '(7.136178,7.14)'::rngfunc_type
+(2 rows)
+
+select testrngfunc();
+ testrngfunc
+-----------------
+ (7.136178,7.14)
+(1 row)
+
+explain (verbose, costs off)
+select * from testrngfunc();
+ QUERY PLAN
+--------------------------------------------------
+ Function Scan on testrngfunc
+ Output: f1, f2
+ Function Call: '(7.136178,7.14)'::rngfunc_type
+(3 rows)
+
+select * from testrngfunc();
+ f1 | f2
+----------+------
+ 7.136178 | 7.14
+(1 row)
+
+create or replace function testrngfunc() returns rngfunc_type as $$
+ select 7.136178319899999964, 7.136178319899999964;
+$$ language sql volatile;
+explain (verbose, costs off)
+select testrngfunc();
+ QUERY PLAN
+-------------------------
+ Result
+ Output: testrngfunc()
+(2 rows)
+
+select testrngfunc();
+ testrngfunc
+-----------------
+ (7.136178,7.14)
+(1 row)
+
+explain (verbose, costs off)
+select * from testrngfunc();
+ QUERY PLAN
+-------------------------------------
+ Function Scan on public.testrngfunc
+ Output: f1, f2
+ Function Call: testrngfunc()
+(3 rows)
+
+select * from testrngfunc();
+ f1 | f2
+----------+------
+ 7.136178 | 7.14
+(1 row)
+
+drop function testrngfunc();
+create function testrngfunc() returns setof rngfunc_type as $$
+ select 7.136178319899999964, 7.136178319899999964;
+$$ language sql immutable;
+explain (verbose, costs off)
+select testrngfunc();
+ QUERY PLAN
+-------------------------
+ ProjectSet
+ Output: testrngfunc()
+ -> Result
+(3 rows)
+
+select testrngfunc();
+ testrngfunc
+-----------------
+ (7.136178,7.14)
+(1 row)
+
+explain (verbose, costs off)
+select * from testrngfunc();
+ QUERY PLAN
+--------------------------------------------------------
+ Result
+ Output: 7.136178::numeric(35,6), 7.14::numeric(35,2)
+(2 rows)
+
+select * from testrngfunc();
+ f1 | f2
+----------+------
+ 7.136178 | 7.14
+(1 row)
+
+create or replace function testrngfunc() returns setof rngfunc_type as $$
+ select 7.136178319899999964, 7.136178319899999964;
+$$ language sql volatile;
+explain (verbose, costs off)
+select testrngfunc();
+ QUERY PLAN
+-------------------------
+ ProjectSet
+ Output: testrngfunc()
+ -> Result
+(3 rows)
+
+select testrngfunc();
+ testrngfunc
+-----------------
+ (7.136178,7.14)
+(1 row)
+
+explain (verbose, costs off)
+select * from testrngfunc();
+ QUERY PLAN
+-------------------------------------
+ Function Scan on public.testrngfunc
+ Output: f1, f2
+ Function Call: testrngfunc()
+(3 rows)
+
+select * from testrngfunc();
+ f1 | f2
+----------+------
+ 7.136178 | 7.14
+(1 row)
+
+create or replace function testrngfunc() returns setof rngfunc_type as $$
+ select 1, 2 union select 3, 4 order by 1;
+$$ language sql immutable;
+explain (verbose, costs off)
+select testrngfunc();
+ QUERY PLAN
+-------------------------
+ ProjectSet
+ Output: testrngfunc()
+ -> Result
+(3 rows)
+
+select testrngfunc();
+ testrngfunc
+-----------------
+ (1.000000,2.00)
+ (3.000000,4.00)
+(2 rows)
+
+explain (verbose, costs off)
+select * from testrngfunc();
+ QUERY PLAN
+----------------------------------------------------------
+ Subquery Scan on "*SELECT*"
+ Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1"
+ -> Unique
+ Output: (1), (2)
+ -> Sort
+ Output: (1), (2)
+ Sort Key: (1), (2)
+ -> Append
+ -> Result
+ Output: 1, 2
+ -> Result
+ Output: 3, 4
+(12 rows)
+
+select * from testrngfunc();
+ f1 | f2
+----------+------
+ 1.000000 | 2.00
+ 3.000000 | 4.00
+(2 rows)
+
+-- Check a couple of error cases while we're here
+select * from testrngfunc() as t(f1 int8,f2 int8); -- fail, composite result
+ERROR: a column definition list is redundant for a function returning a named composite type
+LINE 1: select * from testrngfunc() as t(f1 int8,f2 int8);
+ ^
+select * from pg_get_keywords() as t(f1 int8,f2 int8); -- fail, OUT params
+ERROR: a column definition list is redundant for a function with OUT parameters
+LINE 1: select * from pg_get_keywords() as t(f1 int8,f2 int8);
+ ^
+select * from sin(3) as t(f1 int8,f2 int8); -- fail, scalar result type
+ERROR: a column definition list is only allowed for functions returning "record"
+LINE 1: select * from sin(3) as t(f1 int8,f2 int8);
+ ^
+drop type rngfunc_type cascade;
+NOTICE: drop cascades to function testrngfunc()
+--
+-- Check some cases involving added/dropped columns in a rowtype result
+--
+create temp table users (userid text, seq int, email text, todrop bool, moredrop int, enabled bool);
+insert into users values ('id',1,'email',true,11,true);
+insert into users values ('id2',2,'email2',true,12,true);
+alter table users drop column todrop;
+create or replace function get_first_user() returns users as
+$$ SELECT * FROM users ORDER BY userid LIMIT 1; $$
+language sql stable;
+SELECT get_first_user();
+ get_first_user
+-------------------
+ (id,1,email,11,t)
+(1 row)
+
+SELECT * FROM get_first_user();
+ userid | seq | email | moredrop | enabled
+--------+-----+-------+----------+---------
+ id | 1 | email | 11 | t
+(1 row)
+
+create or replace function get_users() returns setof users as
+$$ SELECT * FROM users ORDER BY userid; $$
+language sql stable;
+SELECT get_users();
+ get_users
+---------------------
+ (id,1,email,11,t)
+ (id2,2,email2,12,t)
+(2 rows)
+
+SELECT * FROM get_users();
+ userid | seq | email | moredrop | enabled
+--------+-----+--------+----------+---------
+ id | 1 | email | 11 | t
+ id2 | 2 | email2 | 12 | t
+(2 rows)
+
+SELECT * FROM get_users() WITH ORDINALITY; -- make sure ordinality copes
+ userid | seq | email | moredrop | enabled | ordinality
+--------+-----+--------+----------+---------+------------
+ id | 1 | email | 11 | t | 1
+ id2 | 2 | email2 | 12 | t | 2
+(2 rows)
+
+-- multiple functions vs. dropped columns
+SELECT * FROM ROWS FROM(generate_series(10,11), get_users()) WITH ORDINALITY;
+ generate_series | userid | seq | email | moredrop | enabled | ordinality
+-----------------+--------+-----+--------+----------+---------+------------
+ 10 | id | 1 | email | 11 | t | 1
+ 11 | id2 | 2 | email2 | 12 | t | 2
+(2 rows)
+
+SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY;
+ userid | seq | email | moredrop | enabled | generate_series | ordinality
+--------+-----+--------+----------+---------+-----------------+------------
+ id | 1 | email | 11 | t | 10 | 1
+ id2 | 2 | email2 | 12 | t | 11 | 2
+(2 rows)
+
+-- check that we can cope with post-parsing changes in rowtypes
+create temp view usersview as
+SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY;
+select * from usersview;
+ userid | seq | email | moredrop | enabled | generate_series | ordinality
+--------+-----+--------+----------+---------+-----------------+------------
+ id | 1 | email | 11 | t | 10 | 1
+ id2 | 2 | email2 | 12 | t | 11 | 2
+(2 rows)
+
+alter table users add column junk text;
+select * from usersview;
+ userid | seq | email | moredrop | enabled | generate_series | ordinality
+--------+-----+--------+----------+---------+-----------------+------------
+ id | 1 | email | 11 | t | 10 | 1
+ id2 | 2 | email2 | 12 | t | 11 | 2
+(2 rows)
+
+begin;
+alter table users drop column moredrop;
+select * from usersview; -- expect clean failure
+ERROR: attribute 5 of type record has been dropped
+rollback;
+alter table users alter column seq type numeric;
+select * from usersview; -- expect clean failure
+ERROR: attribute 2 of type record has wrong type
+DETAIL: Table has type numeric, but query expects integer.
+drop view usersview;
+drop function get_first_user();
+drop function get_users();
+drop table users;
+-- check behavior with type coercion required for a set-op
+create or replace function rngfuncbar() returns setof text as
+$$ select 'foo'::varchar union all select 'bar'::varchar ; $$
+language sql stable;
+select rngfuncbar();
+ rngfuncbar
+------------
+ foo
+ bar
+(2 rows)
+
+select * from rngfuncbar();
+ rngfuncbar
+------------
+ foo
+ bar
+(2 rows)
+
+-- this function is now inlinable, too:
+explain (verbose, costs off) select * from rngfuncbar();
+ QUERY PLAN
+------------------------------------------------
+ Result
+ Output: ('foo'::character varying)
+ -> Append
+ -> Result
+ Output: 'foo'::character varying
+ -> Result
+ Output: 'bar'::character varying
+(7 rows)
+
+drop function rngfuncbar();
+-- check handling of a SQL function with multiple OUT params (bug #5777)
+create or replace function rngfuncbar(out integer, out numeric) as
+$$ select (1, 2.1) $$ language sql;
+select * from rngfuncbar();
+ column1 | column2
+---------+---------
+ 1 | 2.1
+(1 row)
+
+create or replace function rngfuncbar(out integer, out numeric) as
+$$ select (1, 2) $$ language sql;
+select * from rngfuncbar(); -- fail
+ERROR: function return row and query-specified return row do not match
+DETAIL: Returned type integer at ordinal position 2, but query expects numeric.
+create or replace function rngfuncbar(out integer, out numeric) as
+$$ select (1, 2.1, 3) $$ language sql;
+select * from rngfuncbar(); -- fail
+ERROR: function return row and query-specified return row do not match
+DETAIL: Returned row contains 3 attributes, but query expects 2.
+drop function rngfuncbar();
+-- check whole-row-Var handling in nested lateral functions (bug #11703)
+create function extractq2(t int8_tbl) returns int8 as $$
+ select t.q2
+$$ language sql immutable;
+explain (verbose, costs off)
+select x from int8_tbl, extractq2(int8_tbl) f(x);
+ QUERY PLAN
+------------------------------------------
+ Nested Loop
+ Output: f.x
+ -> Seq Scan on public.int8_tbl
+ Output: int8_tbl.q1, int8_tbl.q2
+ -> Function Scan on f
+ Output: f.x
+ Function Call: int8_tbl.q2
+(7 rows)
+
+select x from int8_tbl, extractq2(int8_tbl) f(x);
+ x
+-------------------
+ 456
+ 4567890123456789
+ 123
+ 4567890123456789
+ -4567890123456789
+(5 rows)
+
+create function extractq2_2(t int8_tbl) returns table(ret1 int8) as $$
+ select extractq2(t) offset 0
+$$ language sql immutable;
+explain (verbose, costs off)
+select x from int8_tbl, extractq2_2(int8_tbl) f(x);
+ QUERY PLAN
+-----------------------------------
+ Nested Loop
+ Output: ((int8_tbl.*).q2)
+ -> Seq Scan on public.int8_tbl
+ Output: int8_tbl.*
+ -> Result
+ Output: (int8_tbl.*).q2
+(6 rows)
+
+select x from int8_tbl, extractq2_2(int8_tbl) f(x);
+ x
+-------------------
+ 456
+ 4567890123456789
+ 123
+ 4567890123456789
+ -4567890123456789
+(5 rows)
+
+-- without the "offset 0", this function gets optimized quite differently
+create function extractq2_2_opt(t int8_tbl) returns table(ret1 int8) as $$
+ select extractq2(t)
+$$ language sql immutable;
+explain (verbose, costs off)
+select x from int8_tbl, extractq2_2_opt(int8_tbl) f(x);
+ QUERY PLAN
+-----------------------------
+ Seq Scan on public.int8_tbl
+ Output: int8_tbl.q2
+(2 rows)
+
+select x from int8_tbl, extractq2_2_opt(int8_tbl) f(x);
+ x
+-------------------
+ 456
+ 4567890123456789
+ 123
+ 4567890123456789
+ -4567890123456789
+(5 rows)
+
+-- check handling of nulls in SRF results (bug #7808)
+create type rngfunc2 as (a integer, b text);
+select *, row_to_json(u) from unnest(array[(1,'foo')::rngfunc2, null::rngfunc2]) u;
+ a | b | row_to_json
+---+-----+---------------------
+ 1 | foo | {"a":1,"b":"foo"}
+ | | {"a":null,"b":null}
+(2 rows)
+
+select *, row_to_json(u) from unnest(array[null::rngfunc2, null::rngfunc2]) u;
+ a | b | row_to_json
+---+---+---------------------
+ | | {"a":null,"b":null}
+ | | {"a":null,"b":null}
+(2 rows)
+
+select *, row_to_json(u) from unnest(array[null::rngfunc2, (1,'foo')::rngfunc2, null::rngfunc2]) u;
+ a | b | row_to_json
+---+-----+---------------------
+ | | {"a":null,"b":null}
+ 1 | foo | {"a":1,"b":"foo"}
+ | | {"a":null,"b":null}
+(3 rows)
+
+select *, row_to_json(u) from unnest(array[]::rngfunc2[]) u;
+ a | b | row_to_json
+---+---+-------------
+(0 rows)
+
+drop type rngfunc2;
+-- check handling of functions pulled up into function RTEs (bug #17227)
+explain (verbose, costs off)
+select * from
+ (select jsonb_path_query_array(module->'lectures', '$[*]') as lecture
+ from unnest(array['{"lectures": [{"id": "1"}]}'::jsonb])
+ as unnested_modules(module)) as ss,
+ jsonb_to_recordset(ss.lecture) as j (id text);
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: jsonb_path_query_array((unnested_modules.module -> 'lectures'::text), '$[*]'::jsonpath, '{}'::jsonb, false), j.id
+ -> Function Scan on pg_catalog.unnest unnested_modules
+ Output: unnested_modules.module
+ Function Call: unnest('{"{\"lectures\": [{\"id\": \"1\"}]}"}'::jsonb[])
+ -> Function Scan on pg_catalog.jsonb_to_recordset j
+ Output: j.id
+ Function Call: jsonb_to_recordset(jsonb_path_query_array((unnested_modules.module -> 'lectures'::text), '$[*]'::jsonpath, '{}'::jsonb, false))
+(8 rows)
+
+select * from
+ (select jsonb_path_query_array(module->'lectures', '$[*]') as lecture
+ from unnest(array['{"lectures": [{"id": "1"}]}'::jsonb])
+ as unnested_modules(module)) as ss,
+ jsonb_to_recordset(ss.lecture) as j (id text);
+ lecture | id
+---------------+----
+ [{"id": "1"}] | 1
+(1 row)
+
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
new file mode 100644
index 0000000..2eaed6e
--- /dev/null
+++ b/src/test/regress/expected/rangetypes.out
@@ -0,0 +1,1770 @@
+-- Tests for range data types.
+create type textrange as range (subtype=text, collation="C");
+--
+-- test input parser
+--
+-- 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);
+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);
+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);
+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);
+insert into test_range_gist select int4range(g, g+10) from generate_series(1,2000) g;
+insert into test_range_gist select 'empty'::int4range from generate_series(1,500) g;
+insert into test_range_gist select int4range(g, g+10000) from generate_series(1,1000) g;
+insert into test_range_gist select 'empty'::int4range from generate_series(1,500) g;
+insert into test_range_gist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+insert into test_range_gist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+insert into test_range_gist select int4range(g, g+10) from generate_series(1,2000) g;
+-- 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_range_gist;
+-- first, verify non-indexed results
+SET enable_seqscan = t;
+SET enable_indexscan = f;
+SET enable_bitmapscan = f;
+select count(*) from test_range_gist where ir @> 'empty'::int4range;
+ count
+-------
+ 6200
+(1 row)
+
+select count(*) from test_range_gist where ir = int4range(10,20);
+ count
+-------
+ 2
+(1 row)
+
+select count(*) from test_range_gist where ir @> 10;
+ count
+-------
+ 130
+(1 row)
+
+select count(*) from test_range_gist where ir @> int4range(10,20);
+ count
+-------
+ 111
+(1 row)
+
+select count(*) from test_range_gist where ir && int4range(10,20);
+ count
+-------
+ 158
+(1 row)
+
+select count(*) from test_range_gist where ir <@ int4range(10,50);
+ count
+-------
+ 1062
+(1 row)
+
+select count(*) from test_range_gist where ir << int4range(100,500);
+ count
+-------
+ 189
+(1 row)
+
+select count(*) from test_range_gist where ir >> int4range(100,500);
+ count
+-------
+ 3554
+(1 row)
+
+select count(*) from test_range_gist where ir &< int4range(100,500);
+ count
+-------
+ 1029
+(1 row)
+
+select count(*) from test_range_gist where ir &> int4range(100,500);
+ count
+-------
+ 4794
+(1 row)
+
+select count(*) from test_range_gist where ir -|- int4range(100,500);
+ count
+-------
+ 5
+(1 row)
+
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+ count
+-------
+ 6200
+(1 row)
+
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+ count
+-------
+ 107
+(1 row)
+
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count
+-------
+ 271
+(1 row)
+
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count
+-------
+ 1060
+(1 row)
+
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 189
+(1 row)
+
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 3554
+(1 row)
+
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 1029
+(1 row)
+
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 4794
+(1 row)
+
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 5
+(1 row)
+
+-- now check same queries using index
+SET enable_seqscan = f;
+SET enable_indexscan = t;
+SET enable_bitmapscan = f;
+select count(*) from test_range_gist where ir @> 'empty'::int4range;
+ count
+-------
+ 6200
+(1 row)
+
+select count(*) from test_range_gist where ir = int4range(10,20);
+ count
+-------
+ 2
+(1 row)
+
+select count(*) from test_range_gist where ir @> 10;
+ count
+-------
+ 130
+(1 row)
+
+select count(*) from test_range_gist where ir @> int4range(10,20);
+ count
+-------
+ 111
+(1 row)
+
+select count(*) from test_range_gist where ir && int4range(10,20);
+ count
+-------
+ 158
+(1 row)
+
+select count(*) from test_range_gist where ir <@ int4range(10,50);
+ count
+-------
+ 1062
+(1 row)
+
+select count(*) from test_range_gist where ir << int4range(100,500);
+ count
+-------
+ 189
+(1 row)
+
+select count(*) from test_range_gist where ir >> int4range(100,500);
+ count
+-------
+ 3554
+(1 row)
+
+select count(*) from test_range_gist where ir &< int4range(100,500);
+ count
+-------
+ 1029
+(1 row)
+
+select count(*) from test_range_gist where ir &> int4range(100,500);
+ count
+-------
+ 4794
+(1 row)
+
+select count(*) from test_range_gist where ir -|- int4range(100,500);
+ count
+-------
+ 5
+(1 row)
+
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+ count
+-------
+ 6200
+(1 row)
+
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+ count
+-------
+ 107
+(1 row)
+
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count
+-------
+ 271
+(1 row)
+
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count
+-------
+ 1060
+(1 row)
+
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 189
+(1 row)
+
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 3554
+(1 row)
+
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 1029
+(1 row)
+
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 4794
+(1 row)
+
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 5
+(1 row)
+
+-- now check same queries using a bulk-loaded index
+drop index test_range_gist_idx;
+create index test_range_gist_idx on test_range_gist using gist (ir);
+select count(*) from test_range_gist where ir @> 'empty'::int4range;
+ count
+-------
+ 6200
+(1 row)
+
+select count(*) from test_range_gist where ir = int4range(10,20);
+ count
+-------
+ 2
+(1 row)
+
+select count(*) from test_range_gist where ir @> 10;
+ count
+-------
+ 130
+(1 row)
+
+select count(*) from test_range_gist where ir @> int4range(10,20);
+ count
+-------
+ 111
+(1 row)
+
+select count(*) from test_range_gist where ir && int4range(10,20);
+ count
+-------
+ 158
+(1 row)
+
+select count(*) from test_range_gist where ir <@ int4range(10,50);
+ count
+-------
+ 1062
+(1 row)
+
+select count(*) from test_range_gist where ir << int4range(100,500);
+ count
+-------
+ 189
+(1 row)
+
+select count(*) from test_range_gist where ir >> int4range(100,500);
+ count
+-------
+ 3554
+(1 row)
+
+select count(*) from test_range_gist where ir &< int4range(100,500);
+ count
+-------
+ 1029
+(1 row)
+
+select count(*) from test_range_gist where ir &> int4range(100,500);
+ count
+-------
+ 4794
+(1 row)
+
+select count(*) from test_range_gist where ir -|- int4range(100,500);
+ count
+-------
+ 5
+(1 row)
+
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+ count
+-------
+ 6200
+(1 row)
+
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+ count
+-------
+ 107
+(1 row)
+
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count
+-------
+ 271
+(1 row)
+
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count
+-------
+ 1060
+(1 row)
+
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 189
+(1 row)
+
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 3554
+(1 row)
+
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 1029
+(1 row)
+
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 4794
+(1 row)
+
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 5
+(1 row)
+
+-- 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);
+insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+insert into test_range_spgist select int4range(g, g+10000) from generate_series(1,1000) g;
+insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+insert into test_range_spgist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+insert into test_range_spgist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+-- first, verify non-indexed results
+SET enable_seqscan = t;
+SET enable_indexscan = f;
+SET enable_bitmapscan = f;
+select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ count
+-------
+ 6200
+(1 row)
+
+select count(*) from test_range_spgist where ir = int4range(10,20);
+ count
+-------
+ 2
+(1 row)
+
+select count(*) from test_range_spgist where ir @> 10;
+ count
+-------
+ 130
+(1 row)
+
+select count(*) from test_range_spgist where ir @> int4range(10,20);
+ count
+-------
+ 111
+(1 row)
+
+select count(*) from test_range_spgist where ir && int4range(10,20);
+ count
+-------
+ 158
+(1 row)
+
+select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ count
+-------
+ 1062
+(1 row)
+
+select count(*) from test_range_spgist where ir << int4range(100,500);
+ count
+-------
+ 189
+(1 row)
+
+select count(*) from test_range_spgist where ir >> int4range(100,500);
+ count
+-------
+ 3554
+(1 row)
+
+select count(*) from test_range_spgist where ir &< int4range(100,500);
+ count
+-------
+ 1029
+(1 row)
+
+select count(*) from test_range_spgist where ir &> int4range(100,500);
+ count
+-------
+ 4794
+(1 row)
+
+select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ count
+-------
+ 5
+(1 row)
+
+-- now check same queries using index
+SET enable_seqscan = f;
+SET enable_indexscan = t;
+SET enable_bitmapscan = f;
+select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ count
+-------
+ 6200
+(1 row)
+
+select count(*) from test_range_spgist where ir = int4range(10,20);
+ count
+-------
+ 2
+(1 row)
+
+select count(*) from test_range_spgist where ir @> 10;
+ count
+-------
+ 130
+(1 row)
+
+select count(*) from test_range_spgist where ir @> int4range(10,20);
+ count
+-------
+ 111
+(1 row)
+
+select count(*) from test_range_spgist where ir && int4range(10,20);
+ count
+-------
+ 158
+(1 row)
+
+select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ count
+-------
+ 1062
+(1 row)
+
+select count(*) from test_range_spgist where ir << int4range(100,500);
+ count
+-------
+ 189
+(1 row)
+
+select count(*) from test_range_spgist where ir >> int4range(100,500);
+ count
+-------
+ 3554
+(1 row)
+
+select count(*) from test_range_spgist where ir &< int4range(100,500);
+ count
+-------
+ 1029
+(1 row)
+
+select count(*) from test_range_spgist where ir &> int4range(100,500);
+ count
+-------
+ 4794
+(1 row)
+
+select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ count
+-------
+ 5
+(1 row)
+
+-- now check same queries using a bulk-loaded index
+drop index test_range_spgist_idx;
+create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ count
+-------
+ 6200
+(1 row)
+
+select count(*) from test_range_spgist where ir = int4range(10,20);
+ count
+-------
+ 2
+(1 row)
+
+select count(*) from test_range_spgist where ir @> 10;
+ count
+-------
+ 130
+(1 row)
+
+select count(*) from test_range_spgist where ir @> int4range(10,20);
+ count
+-------
+ 111
+(1 row)
+
+select count(*) from test_range_spgist where ir && int4range(10,20);
+ count
+-------
+ 158
+(1 row)
+
+select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ count
+-------
+ 1062
+(1 row)
+
+select count(*) from test_range_spgist where ir << int4range(100,500);
+ count
+-------
+ 189
+(1 row)
+
+select count(*) from test_range_spgist where ir >> int4range(100,500);
+ count
+-------
+ 3554
+(1 row)
+
+select count(*) from test_range_spgist where ir &< int4range(100,500);
+ count
+-------
+ 1029
+(1 row)
+
+select count(*) from test_range_spgist where ir &> int4range(100,500);
+ count
+-------
+ 4794
+(1 row)
+
+select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ count
+-------
+ 5
+(1 row)
+
+-- test index-only scans
+explain (costs off)
+select ir from test_range_spgist where ir -|- int4range(10,20) order by ir;
+ QUERY PLAN
+------------------------------------------------------------------------
+ Sort
+ Sort Key: ir
+ -> Index Only Scan using test_range_spgist_idx on test_range_spgist
+ Index Cond: (ir -|- '[10,20)'::int4range)
+(4 rows)
+
+select ir from test_range_spgist where ir -|- int4range(10,20) order by ir;
+ ir
+------------
+ [20,30)
+ [20,30)
+ [20,10020)
+(3 rows)
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+-- 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));
+explain (costs off)
+select count(*) from test_range_elem where int4range(i,i+10) <@ int4range(10,30);
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Aggregate
+ -> Index Scan using test_range_elem_int4range_idx on test_range_elem
+ Index Cond: (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 &&)
+);
+insert into test_range_excl
+ values(int4range(123, 123, '[]'), int4range(1, 1, '[]'), '[2010-01-02 10:00, 2010-01-02 11:00)');
+insert into test_range_excl
+ values(int4range(123, 123, '[]'), int4range(2, 2, '[]'), '[2010-01-02 11:00, 2010-01-02 12:00)');
+insert into test_range_excl
+ values(int4range(123, 123, '[]'), int4range(3, 3, '[]'), '[2010-01-02 10:10, 2010-01-02 11:00)');
+ERROR: conflicting key value violates exclusion constraint "test_range_excl_room_during_excl"
+DETAIL: Key (room, during)=([123,124), ["Sat Jan 02 10:10:00 2010","Sat Jan 02 11:00:00 2010")) conflicts with existing key (room, during)=([123,124), ["Sat Jan 02 10:00:00 2010","Sat Jan 02 11:00:00 2010")).
+insert into test_range_excl
+ values(int4range(124, 124, '[]'), int4range(3, 3, '[]'), '[2010-01-02 10:10, 2010-01-02 11:10)');
+insert into test_range_excl
+ values(int4range(125, 125, '[]'), int4range(1, 1, '[]'), '[2010-01-02 10:10, 2010-01-02 11:00)');
+ERROR: conflicting key value violates exclusion constraint "test_range_excl_speaker_during_excl"
+DETAIL: Key (speaker, during)=([1,2), ["Sat Jan 02 10:10:00 2010","Sat Jan 02 11:00:00 2010")) conflicts with existing key (speaker, during)=([1,2), ["Sat Jan 02 10:00:00 2010","Sat Jan 02 11:00:00 2010")).
+-- 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
+--
+--should fail
+create type float8range as range (subtype=float8, subtype_diff=float4mi);
+ERROR: function float4mi(double precision, double precision) does not exist
+--should succeed
+create type float8range as range (subtype=float8, subtype_diff=float8mi);
+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: composite type two_ints cannot be made a member of itself
+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/regex.linux.utf8.out b/src/test/regress/expected/regex.linux.utf8.out
new file mode 100644
index 0000000..7c170a9
--- /dev/null
+++ b/src/test/regress/expected/regex.linux.utf8.out
@@ -0,0 +1,164 @@
+/*
+ * This test is for Linux/glibc systems and others that implement proper
+ * locale classification of Unicode characters with high code values.
+ * It must be run in a database with UTF8 encoding and a Unicode-aware locale.
+ */
+SET client_encoding TO UTF8;
+--
+-- Test the "high colormap" logic with single characters and ranges that
+-- exceed the MAX_SIMPLE_CHR cutoff, here assumed to be less than U+2000.
+--
+-- trivial cases:
+SELECT 'aâ“' ~ U&'a\24D0' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aâ“' ~ U&'a\24D1' AS f;
+ f
+---
+ f
+(1 row)
+
+SELECT 'aâ“•' ~ 'a[â“-â“©]' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aâ’»' ~ 'a[â“-â“©]' AS f;
+ f
+---
+ f
+(1 row)
+
+-- cases requiring splitting of ranges:
+SELECT 'aâ“•â“•' ~ 'aâ“•[â“-â“©]' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aâ“•â“' ~ 'aâ“•[â“-â“©]' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aâ“â“•' ~ 'aâ“•[â“-â“©]' AS f;
+ f
+---
+ f
+(1 row)
+
+SELECT 'aâ“•â“•' ~ 'a[â“-â“©]â“•' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aâ“•â“' ~ 'a[â“-â“©]â“•' AS f;
+ f
+---
+ f
+(1 row)
+
+SELECT 'aâ“â“•' ~ 'a[â“-â“©]â“•' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aⒶⓜ' ~ 'a[Ⓐ-ⓜ][ⓜ-ⓩ]' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aⓜⓜ' ~ 'a[Ⓐ-ⓜ][ⓜ-ⓩ]' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aⓜⓩ' ~ 'a[Ⓐ-ⓜ][ⓜ-ⓩ]' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aⓩⓩ' ~ 'a[Ⓐ-ⓜ][ⓜ-ⓩ]' AS f;
+ f
+---
+ f
+(1 row)
+
+SELECT 'aⓜ⓪' ~ 'a[Ⓐ-ⓜ][ⓜ-ⓩ]' AS f;
+ f
+---
+ f
+(1 row)
+
+SELECT 'a0' ~ 'a[a-â“©]' AS f;
+ f
+---
+ f
+(1 row)
+
+SELECT 'aq' ~ 'a[a-â“©]' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aⓜ' ~ 'a[a-ⓩ]' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'a⓪' ~ 'a[a-ⓩ]' AS f;
+ f
+---
+ f
+(1 row)
+
+-- Locale-dependent character classes
+SELECT 'aⒶⓜ⓪' ~ '[[:alpha:]][[:alpha:]][[:alpha:]][[:graph:]]' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aⒶⓜ⓪' ~ '[[:alpha:]][[:alpha:]][[:alpha:]][[:alpha:]]' AS f;
+ f
+---
+ f
+(1 row)
+
+-- Locale-dependent character classes with high ranges
+SELECT 'aⒶⓜ⓪' ~ '[a-z][[:alpha:]][â“-â“©][[:graph:]]' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aⓜⒶ⓪' ~ '[a-z][[:alpha:]][â“-â“©][[:graph:]]' AS f;
+ f
+---
+ f
+(1 row)
+
+SELECT 'aⓜⒶ⓪' ~ '[a-z][â“-â“©][[:alpha:]][[:graph:]]' AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT 'aⒶⓜ⓪' ~ '[a-z][â“-â“©][[:alpha:]][[:graph:]]' AS f;
+ f
+---
+ f
+(1 row)
+
diff --git a/src/test/regress/expected/regex.out b/src/test/regress/expected/regex.out
new file mode 100644
index 0000000..ae0de73
--- /dev/null
+++ b/src/test/regress/expected/regex.out
@@ -0,0 +1,645 @@
+--
+-- Regular expression tests
+--
+-- Don't want to have to double backslashes in regexes
+set standard_conforming_strings = on;
+-- Test simple quantified backrefs
+select 'bbbbb' ~ '^([bc])\1*$' as t;
+ t
+---
+ t
+(1 row)
+
+select 'ccc' ~ '^([bc])\1*$' as t;
+ t
+---
+ t
+(1 row)
+
+select 'xxx' ~ '^([bc])\1*$' as f;
+ f
+---
+ f
+(1 row)
+
+select 'bbc' ~ '^([bc])\1*$' as f;
+ f
+---
+ f
+(1 row)
+
+select 'b' ~ '^([bc])\1*$' as t;
+ t
+---
+ t
+(1 row)
+
+-- Test quantified backref within a larger expression
+select 'abc abc abc' ~ '^(\w+)( \1)+$' as t;
+ t
+---
+ t
+(1 row)
+
+select 'abc abd abc' ~ '^(\w+)( \1)+$' as f;
+ f
+---
+ f
+(1 row)
+
+select 'abc abc abd' ~ '^(\w+)( \1)+$' as f;
+ f
+---
+ f
+(1 row)
+
+select 'abc abc abc' ~ '^(.+)( \1)+$' as t;
+ t
+---
+ t
+(1 row)
+
+select 'abc abd abc' ~ '^(.+)( \1)+$' as f;
+ f
+---
+ f
+(1 row)
+
+select 'abc abc abd' ~ '^(.+)( \1)+$' as f;
+ f
+---
+ f
+(1 row)
+
+-- Test some cases that crashed in 9.2beta1 due to pmatch[] array overrun
+select substring('asd TO foo' from ' TO (([a-z0-9._]+|"([^"]+|"")+")+)');
+ substring
+-----------
+ foo
+(1 row)
+
+select substring('a' from '((a))+');
+ substring
+-----------
+ a
+(1 row)
+
+select substring('a' from '((a)+)');
+ substring
+-----------
+ a
+(1 row)
+
+-- Test regexp_match()
+select regexp_match('abc', '');
+ regexp_match
+--------------
+ {""}
+(1 row)
+
+select regexp_match('abc', 'bc');
+ regexp_match
+--------------
+ {bc}
+(1 row)
+
+select regexp_match('abc', 'd') is null;
+ ?column?
+----------
+ t
+(1 row)
+
+select regexp_match('abc', '(B)(c)', 'i');
+ regexp_match
+--------------
+ {b,c}
+(1 row)
+
+select regexp_match('abc', 'Bd', 'ig'); -- error
+ERROR: regexp_match() does not support the "global" option
+HINT: Use the regexp_matches function instead.
+-- Test lookahead constraints
+select regexp_matches('ab', 'a(?=b)b*');
+ regexp_matches
+----------------
+ {ab}
+(1 row)
+
+select regexp_matches('a', 'a(?=b)b*');
+ regexp_matches
+----------------
+(0 rows)
+
+select regexp_matches('abc', 'a(?=b)b*(?=c)c*');
+ regexp_matches
+----------------
+ {abc}
+(1 row)
+
+select regexp_matches('ab', 'a(?=b)b*(?=c)c*');
+ regexp_matches
+----------------
+(0 rows)
+
+select regexp_matches('ab', 'a(?!b)b*');
+ regexp_matches
+----------------
+(0 rows)
+
+select regexp_matches('a', 'a(?!b)b*');
+ regexp_matches
+----------------
+ {a}
+(1 row)
+
+select regexp_matches('b', '(?=b)b');
+ regexp_matches
+----------------
+ {b}
+(1 row)
+
+select regexp_matches('a', '(?=b)b');
+ regexp_matches
+----------------
+(0 rows)
+
+-- Test lookbehind constraints
+select regexp_matches('abb', '(?<=a)b*');
+ regexp_matches
+----------------
+ {bb}
+(1 row)
+
+select regexp_matches('a', 'a(?<=a)b*');
+ regexp_matches
+----------------
+ {a}
+(1 row)
+
+select regexp_matches('abc', 'a(?<=a)b*(?<=b)c*');
+ regexp_matches
+----------------
+ {abc}
+(1 row)
+
+select regexp_matches('ab', 'a(?<=a)b*(?<=b)c*');
+ regexp_matches
+----------------
+ {ab}
+(1 row)
+
+select regexp_matches('ab', 'a*(?<!a)b*');
+ regexp_matches
+----------------
+ {""}
+(1 row)
+
+select regexp_matches('ab', 'a*(?<!a)b+');
+ regexp_matches
+----------------
+(0 rows)
+
+select regexp_matches('b', 'a*(?<!a)b+');
+ regexp_matches
+----------------
+ {b}
+(1 row)
+
+select regexp_matches('a', 'a(?<!a)b*');
+ regexp_matches
+----------------
+(0 rows)
+
+select regexp_matches('b', '(?<=b)b');
+ regexp_matches
+----------------
+(0 rows)
+
+select regexp_matches('foobar', '(?<=f)b+');
+ regexp_matches
+----------------
+(0 rows)
+
+select regexp_matches('foobar', '(?<=foo)b+');
+ regexp_matches
+----------------
+ {b}
+(1 row)
+
+select regexp_matches('foobar', '(?<=oo)b+');
+ regexp_matches
+----------------
+ {b}
+(1 row)
+
+-- Test optimization of single-chr-or-bracket-expression lookaround constraints
+select 'xz' ~ 'x(?=[xy])';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'xy' ~ 'x(?=[xy])';
+ ?column?
+----------
+ t
+(1 row)
+
+select 'xz' ~ 'x(?![xy])';
+ ?column?
+----------
+ t
+(1 row)
+
+select 'xy' ~ 'x(?![xy])';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'x' ~ 'x(?![xy])';
+ ?column?
+----------
+ t
+(1 row)
+
+select 'xyy' ~ '(?<=[xy])yy+';
+ ?column?
+----------
+ t
+(1 row)
+
+select 'zyy' ~ '(?<=[xy])yy+';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'xyy' ~ '(?<![xy])yy+';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'zyy' ~ '(?<![xy])yy+';
+ ?column?
+----------
+ t
+(1 row)
+
+-- Test conversion of regex patterns to indexable conditions
+explain (costs off) select * from pg_proc where proname ~ 'abc';
+ QUERY PLAN
+-----------------------------------
+ Seq Scan on pg_proc
+ Filter: (proname ~ 'abc'::text)
+(2 rows)
+
+explain (costs off) select * from pg_proc where proname ~ '^abc';
+ QUERY PLAN
+----------------------------------------------------------------------
+ Index Scan using pg_proc_proname_args_nsp_index on pg_proc
+ Index Cond: ((proname >= 'abc'::text) AND (proname < 'abd'::text))
+ Filter: (proname ~ '^abc'::text)
+(3 rows)
+
+explain (costs off) select * from pg_proc where proname ~ '^abc$';
+ QUERY PLAN
+------------------------------------------------------------
+ Index Scan using pg_proc_proname_args_nsp_index on pg_proc
+ Index Cond: (proname = 'abc'::text)
+ Filter: (proname ~ '^abc$'::text)
+(3 rows)
+
+explain (costs off) select * from pg_proc where proname ~ '^abcd*e';
+ QUERY PLAN
+----------------------------------------------------------------------
+ Index Scan using pg_proc_proname_args_nsp_index on pg_proc
+ Index Cond: ((proname >= 'abc'::text) AND (proname < 'abd'::text))
+ Filter: (proname ~ '^abcd*e'::text)
+(3 rows)
+
+explain (costs off) select * from pg_proc where proname ~ '^abc+d';
+ QUERY PLAN
+----------------------------------------------------------------------
+ Index Scan using pg_proc_proname_args_nsp_index on pg_proc
+ Index Cond: ((proname >= 'abc'::text) AND (proname < 'abd'::text))
+ Filter: (proname ~ '^abc+d'::text)
+(3 rows)
+
+explain (costs off) select * from pg_proc where proname ~ '^(abc)(def)';
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Index Scan using pg_proc_proname_args_nsp_index on pg_proc
+ Index Cond: ((proname >= 'abcdef'::text) AND (proname < 'abcdeg'::text))
+ Filter: (proname ~ '^(abc)(def)'::text)
+(3 rows)
+
+explain (costs off) select * from pg_proc where proname ~ '^(abc)$';
+ QUERY PLAN
+------------------------------------------------------------
+ Index Scan using pg_proc_proname_args_nsp_index on pg_proc
+ Index Cond: (proname = 'abc'::text)
+ Filter: (proname ~ '^(abc)$'::text)
+(3 rows)
+
+explain (costs off) select * from pg_proc where proname ~ '^(abc)?d';
+ QUERY PLAN
+----------------------------------------
+ Seq Scan on pg_proc
+ Filter: (proname ~ '^(abc)?d'::text)
+(2 rows)
+
+explain (costs off) select * from pg_proc where proname ~ '^abcd(x|(?=\w\w)q)';
+ QUERY PLAN
+------------------------------------------------------------------------
+ Index Scan using pg_proc_proname_args_nsp_index on pg_proc
+ Index Cond: ((proname >= 'abcd'::text) AND (proname < 'abce'::text))
+ Filter: (proname ~ '^abcd(x|(?=\w\w)q)'::text)
+(3 rows)
+
+-- Test for infinite loop in pullback() (CVE-2007-4772)
+select 'a' ~ '($|^)*';
+ ?column?
+----------
+ t
+(1 row)
+
+-- These cases expose a bug in the original fix for CVE-2007-4772
+select 'a' ~ '(^)+^';
+ ?column?
+----------
+ t
+(1 row)
+
+select 'a' ~ '$($$)+';
+ ?column?
+----------
+ t
+(1 row)
+
+-- More cases of infinite loop in pullback(), not fixed by CVE-2007-4772 fix
+select 'a' ~ '($^)+';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'a' ~ '(^$)*';
+ ?column?
+----------
+ t
+(1 row)
+
+select 'aa bb cc' ~ '(^(?!aa))+';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'aa x' ~ '(^(?!aa)(?!bb)(?!cc))+';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'bb x' ~ '(^(?!aa)(?!bb)(?!cc))+';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'cc x' ~ '(^(?!aa)(?!bb)(?!cc))+';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'dd x' ~ '(^(?!aa)(?!bb)(?!cc))+';
+ ?column?
+----------
+ t
+(1 row)
+
+-- Test for infinite loop in fixempties() (Tcl bugs 3604074, 3606683)
+select 'a' ~ '((((((a)*)*)*)*)*)*';
+ ?column?
+----------
+ t
+(1 row)
+
+select 'a' ~ '((((((a+|)+|)+|)+|)+|)+|)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- These cases used to give too-many-states failures
+select 'x' ~ 'abcd(\m)+xyz';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'a' ~ '^abcd*(((((^(a c(e?d)a+|)+|)+|)+|)+|a)+|)';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'x' ~ 'a^(^)bcd*xy(((((($a+|)+|)+|)+$|)+|)+|)^$';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'x' ~ 'xyz(\Y\Y)+';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'x' ~ 'x|(?:\M)+';
+ ?column?
+----------
+ t
+(1 row)
+
+-- This generates O(N) states but O(N^2) arcs, so it causes problems
+-- if arc count is not constrained
+select 'x' ~ repeat('x*y*z*', 1000);
+ERROR: invalid regular expression: regular expression is too complex
+-- Test backref in combination with non-greedy quantifier
+-- https://core.tcl.tk/tcl/tktview/6585b21ca8fa6f3678d442b97241fdd43dba2ec0
+select 'Programmer' ~ '(\w).*?\1' as t;
+ t
+---
+ t
+(1 row)
+
+select regexp_matches('Programmer', '(\w)(.*?\1)', 'g');
+ regexp_matches
+----------------
+ {r,ogr}
+ {m,m}
+(2 rows)
+
+-- Test for proper matching of non-greedy iteration (bug #11478)
+select regexp_matches('foo/bar/baz',
+ '^([^/]+?)(?:/([^/]+?))(?:/([^/]+?))?$', '');
+ regexp_matches
+----------------
+ {foo,bar,baz}
+(1 row)
+
+-- Test that greediness can be overridden by outer quantifier
+select regexp_matches('llmmmfff', '^(l*)(.*)(f*)$');
+ regexp_matches
+----------------
+ {ll,mmmfff,""}
+(1 row)
+
+select regexp_matches('llmmmfff', '^(l*){1,1}(.*)(f*)$');
+ regexp_matches
+----------------
+ {ll,mmmfff,""}
+(1 row)
+
+select regexp_matches('llmmmfff', '^(l*){1,1}?(.*)(f*)$');
+ regexp_matches
+------------------
+ {"",llmmmfff,""}
+(1 row)
+
+select regexp_matches('llmmmfff', '^(l*){1,1}?(.*){1,1}?(f*)$');
+ regexp_matches
+----------------
+ {"",llmmm,fff}
+(1 row)
+
+select regexp_matches('llmmmfff', '^(l*?)(.*)(f*)$');
+ regexp_matches
+------------------
+ {"",llmmmfff,""}
+(1 row)
+
+select regexp_matches('llmmmfff', '^(l*?){1,1}(.*)(f*)$');
+ regexp_matches
+----------------
+ {ll,mmmfff,""}
+(1 row)
+
+select regexp_matches('llmmmfff', '^(l*?){1,1}?(.*)(f*)$');
+ regexp_matches
+------------------
+ {"",llmmmfff,""}
+(1 row)
+
+select regexp_matches('llmmmfff', '^(l*?){1,1}?(.*){1,1}?(f*)$');
+ regexp_matches
+----------------
+ {"",llmmm,fff}
+(1 row)
+
+-- Test for infinite loop in cfindloop with zero-length possible match
+-- but no actual match (can only happen in the presence of backrefs)
+select 'a' ~ '$()|^\1';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'a' ~ '.. ()|\1';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'a' ~ '()*\1';
+ ?column?
+----------
+ t
+(1 row)
+
+select 'a' ~ '()+\1';
+ ?column?
+----------
+ t
+(1 row)
+
+-- Test incorrect removal of capture groups within {0}
+select 'xxx' ~ '(.){0}(\1)' as f;
+ f
+---
+ f
+(1 row)
+
+select 'xxx' ~ '((.)){0}(\2)' as f;
+ f
+---
+ f
+(1 row)
+
+select 'xyz' ~ '((.)){0}(\2){0}' as t;
+ t
+---
+ t
+(1 row)
+
+-- Test ancient oversight in when to apply zaptreesubs
+select 'abcdef' ~ '^(.)\1|\1.' as f;
+ f
+---
+ f
+(1 row)
+
+select 'abadef' ~ '^((.)\2|..)\2' as f;
+ f
+---
+ f
+(1 row)
+
+-- Add coverage for some cases in checkmatchall
+select regexp_match('xy', '.|...');
+ regexp_match
+--------------
+ {x}
+(1 row)
+
+select regexp_match('xyz', '.|...');
+ regexp_match
+--------------
+ {xyz}
+(1 row)
+
+select regexp_match('xy', '.*');
+ regexp_match
+--------------
+ {xy}
+(1 row)
+
+select regexp_match('fooba', '(?:..)*');
+ regexp_match
+--------------
+ {foob}
+(1 row)
+
+select regexp_match('xyz', repeat('.', 260));
+ regexp_match
+--------------
+
+(1 row)
+
+select regexp_match('foo', '(?:.|){99}');
+ regexp_match
+--------------
+ {foo}
+(1 row)
+
+-- Error conditions
+select 'xyz' ~ 'x(\w)(?=\1)'; -- no backrefs in LACONs
+ERROR: invalid regular expression: invalid backreference number
+select 'xyz' ~ 'x(\w)(?=(\1))';
+ERROR: invalid regular expression: invalid backreference number
+select 'a' ~ '\x7fffffff'; -- invalid chr code
+ERROR: invalid regular expression: invalid escape \ sequence
diff --git a/src/test/regress/expected/regproc.out b/src/test/regress/expected/regproc.out
new file mode 100644
index 0000000..e45ff54
--- /dev/null
+++ b/src/test/regress/expected/regproc.out
@@ -0,0 +1,437 @@
+--
+-- regproc
+--
+/* If objects exist, return oids */
+CREATE ROLE regress_regrole_test;
+-- without schemaname
+SELECT regoper('||/');
+ regoper
+---------
+ ||/
+(1 row)
+
+SELECT regoperator('+(int4,int4)');
+ regoperator
+--------------------
+ +(integer,integer)
+(1 row)
+
+SELECT regproc('now');
+ regproc
+---------
+ now
+(1 row)
+
+SELECT regprocedure('abs(numeric)');
+ regprocedure
+--------------
+ abs(numeric)
+(1 row)
+
+SELECT regclass('pg_class');
+ regclass
+----------
+ pg_class
+(1 row)
+
+SELECT regtype('int4');
+ regtype
+---------
+ integer
+(1 row)
+
+SELECT regcollation('"POSIX"');
+ regcollation
+--------------
+ "POSIX"
+(1 row)
+
+SELECT to_regoper('||/');
+ to_regoper
+------------
+ ||/
+(1 row)
+
+SELECT to_regoperator('+(int4,int4)');
+ to_regoperator
+--------------------
+ +(integer,integer)
+(1 row)
+
+SELECT to_regproc('now');
+ to_regproc
+------------
+ now
+(1 row)
+
+SELECT to_regprocedure('abs(numeric)');
+ to_regprocedure
+-----------------
+ abs(numeric)
+(1 row)
+
+SELECT to_regclass('pg_class');
+ to_regclass
+-------------
+ pg_class
+(1 row)
+
+SELECT to_regtype('int4');
+ to_regtype
+------------
+ integer
+(1 row)
+
+SELECT to_regcollation('"POSIX"');
+ to_regcollation
+-----------------
+ "POSIX"
+(1 row)
+
+-- with schemaname
+SELECT regoper('pg_catalog.||/');
+ regoper
+---------
+ ||/
+(1 row)
+
+SELECT regoperator('pg_catalog.+(int4,int4)');
+ regoperator
+--------------------
+ +(integer,integer)
+(1 row)
+
+SELECT regproc('pg_catalog.now');
+ regproc
+---------
+ now
+(1 row)
+
+SELECT regprocedure('pg_catalog.abs(numeric)');
+ regprocedure
+--------------
+ abs(numeric)
+(1 row)
+
+SELECT regclass('pg_catalog.pg_class');
+ regclass
+----------
+ pg_class
+(1 row)
+
+SELECT regtype('pg_catalog.int4');
+ regtype
+---------
+ integer
+(1 row)
+
+SELECT regcollation('pg_catalog."POSIX"');
+ regcollation
+--------------
+ "POSIX"
+(1 row)
+
+SELECT to_regoper('pg_catalog.||/');
+ to_regoper
+------------
+ ||/
+(1 row)
+
+SELECT to_regproc('pg_catalog.now');
+ to_regproc
+------------
+ now
+(1 row)
+
+SELECT to_regprocedure('pg_catalog.abs(numeric)');
+ to_regprocedure
+-----------------
+ abs(numeric)
+(1 row)
+
+SELECT to_regclass('pg_catalog.pg_class');
+ to_regclass
+-------------
+ pg_class
+(1 row)
+
+SELECT to_regtype('pg_catalog.int4');
+ to_regtype
+------------
+ integer
+(1 row)
+
+SELECT to_regcollation('pg_catalog."POSIX"');
+ to_regcollation
+-----------------
+ "POSIX"
+(1 row)
+
+-- schemaname not applicable
+SELECT regrole('regress_regrole_test');
+ regrole
+----------------------
+ regress_regrole_test
+(1 row)
+
+SELECT regrole('"regress_regrole_test"');
+ regrole
+----------------------
+ regress_regrole_test
+(1 row)
+
+SELECT regnamespace('pg_catalog');
+ regnamespace
+--------------
+ pg_catalog
+(1 row)
+
+SELECT regnamespace('"pg_catalog"');
+ regnamespace
+--------------
+ pg_catalog
+(1 row)
+
+SELECT to_regrole('regress_regrole_test');
+ to_regrole
+----------------------
+ regress_regrole_test
+(1 row)
+
+SELECT to_regrole('"regress_regrole_test"');
+ to_regrole
+----------------------
+ regress_regrole_test
+(1 row)
+
+SELECT to_regnamespace('pg_catalog');
+ to_regnamespace
+-----------------
+ pg_catalog
+(1 row)
+
+SELECT to_regnamespace('"pg_catalog"');
+ to_regnamespace
+-----------------
+ pg_catalog
+(1 row)
+
+/* If objects don't exist, raise errors. */
+DROP ROLE regress_regrole_test;
+-- without schemaname
+SELECT regoper('||//');
+ERROR: operator does not exist: ||//
+LINE 1: SELECT regoper('||//');
+ ^
+SELECT regoperator('++(int4,int4)');
+ERROR: operator does not exist: ++(int4,int4)
+LINE 1: SELECT regoperator('++(int4,int4)');
+ ^
+SELECT regproc('know');
+ERROR: function "know" does not exist
+LINE 1: SELECT regproc('know');
+ ^
+SELECT regprocedure('absinthe(numeric)');
+ERROR: function "absinthe(numeric)" does not exist
+LINE 1: SELECT regprocedure('absinthe(numeric)');
+ ^
+SELECT regclass('pg_classes');
+ERROR: relation "pg_classes" does not exist
+LINE 1: SELECT regclass('pg_classes');
+ ^
+SELECT regtype('int3');
+ERROR: type "int3" does not exist
+LINE 1: SELECT regtype('int3');
+ ^
+-- with schemaname
+SELECT regoper('ng_catalog.||/');
+ERROR: schema "ng_catalog" does not exist
+LINE 1: SELECT regoper('ng_catalog.||/');
+ ^
+SELECT regoperator('ng_catalog.+(int4,int4)');
+ERROR: operator does not exist: ng_catalog.+(int4,int4)
+LINE 1: SELECT regoperator('ng_catalog.+(int4,int4)');
+ ^
+SELECT regproc('ng_catalog.now');
+ERROR: schema "ng_catalog" does not exist
+LINE 1: SELECT regproc('ng_catalog.now');
+ ^
+SELECT regprocedure('ng_catalog.abs(numeric)');
+ERROR: schema "ng_catalog" does not exist
+LINE 1: SELECT regprocedure('ng_catalog.abs(numeric)');
+ ^
+SELECT regclass('ng_catalog.pg_class');
+ERROR: schema "ng_catalog" does not exist
+LINE 1: SELECT regclass('ng_catalog.pg_class');
+ ^
+SELECT regtype('ng_catalog.int4');
+ERROR: schema "ng_catalog" does not exist
+LINE 1: SELECT regtype('ng_catalog.int4');
+ ^
+SELECT regcollation('ng_catalog."POSIX"');
+ERROR: schema "ng_catalog" does not exist
+LINE 1: SELECT regcollation('ng_catalog."POSIX"');
+ ^
+-- schemaname not applicable
+SELECT regrole('regress_regrole_test');
+ERROR: role "regress_regrole_test" does not exist
+LINE 1: SELECT regrole('regress_regrole_test');
+ ^
+SELECT regrole('"regress_regrole_test"');
+ERROR: role "regress_regrole_test" does not exist
+LINE 1: SELECT regrole('"regress_regrole_test"');
+ ^
+SELECT regrole('Nonexistent');
+ERROR: role "nonexistent" does not exist
+LINE 1: SELECT regrole('Nonexistent');
+ ^
+SELECT regrole('"Nonexistent"');
+ERROR: role "Nonexistent" does not exist
+LINE 1: SELECT regrole('"Nonexistent"');
+ ^
+SELECT regrole('foo.bar');
+ERROR: invalid name syntax
+LINE 1: SELECT regrole('foo.bar');
+ ^
+SELECT regnamespace('Nonexistent');
+ERROR: schema "nonexistent" does not exist
+LINE 1: SELECT regnamespace('Nonexistent');
+ ^
+SELECT regnamespace('"Nonexistent"');
+ERROR: schema "Nonexistent" does not exist
+LINE 1: SELECT regnamespace('"Nonexistent"');
+ ^
+SELECT regnamespace('foo.bar');
+ERROR: invalid name syntax
+LINE 1: SELECT regnamespace('foo.bar');
+ ^
+/* If objects don't exist, return NULL with no error. */
+-- without schemaname
+SELECT to_regoper('||//');
+ to_regoper
+------------
+
+(1 row)
+
+SELECT to_regoperator('++(int4,int4)');
+ to_regoperator
+----------------
+
+(1 row)
+
+SELECT to_regproc('know');
+ to_regproc
+------------
+
+(1 row)
+
+SELECT to_regprocedure('absinthe(numeric)');
+ to_regprocedure
+-----------------
+
+(1 row)
+
+SELECT to_regclass('pg_classes');
+ to_regclass
+-------------
+
+(1 row)
+
+SELECT to_regtype('int3');
+ to_regtype
+------------
+
+(1 row)
+
+SELECT to_regcollation('notacollation');
+ to_regcollation
+-----------------
+
+(1 row)
+
+-- with schemaname
+SELECT to_regoper('ng_catalog.||/');
+ to_regoper
+------------
+
+(1 row)
+
+SELECT to_regoperator('ng_catalog.+(int4,int4)');
+ to_regoperator
+----------------
+
+(1 row)
+
+SELECT to_regproc('ng_catalog.now');
+ to_regproc
+------------
+
+(1 row)
+
+SELECT to_regprocedure('ng_catalog.abs(numeric)');
+ to_regprocedure
+-----------------
+
+(1 row)
+
+SELECT to_regclass('ng_catalog.pg_class');
+ to_regclass
+-------------
+
+(1 row)
+
+SELECT to_regtype('ng_catalog.int4');
+ to_regtype
+------------
+
+(1 row)
+
+SELECT to_regcollation('ng_catalog."POSIX"');
+ to_regcollation
+-----------------
+
+(1 row)
+
+-- schemaname not applicable
+SELECT to_regrole('regress_regrole_test');
+ to_regrole
+------------
+
+(1 row)
+
+SELECT to_regrole('"regress_regrole_test"');
+ to_regrole
+------------
+
+(1 row)
+
+SELECT to_regrole('foo.bar');
+ERROR: invalid name syntax
+SELECT to_regrole('Nonexistent');
+ to_regrole
+------------
+
+(1 row)
+
+SELECT to_regrole('"Nonexistent"');
+ to_regrole
+------------
+
+(1 row)
+
+SELECT to_regrole('foo.bar');
+ERROR: invalid name syntax
+SELECT to_regnamespace('Nonexistent');
+ to_regnamespace
+-----------------
+
+(1 row)
+
+SELECT to_regnamespace('"Nonexistent"');
+ to_regnamespace
+-----------------
+
+(1 row)
+
+SELECT to_regnamespace('foo.bar');
+ERROR: invalid name syntax
diff --git a/src/test/regress/expected/reindex_catalog.out b/src/test/regress/expected/reindex_catalog.out
new file mode 100644
index 0000000..204f056
--- /dev/null
+++ b/src/test/regress/expected/reindex_catalog.out
@@ -0,0 +1,48 @@
+--
+-- Check that system tables can be reindexed.
+--
+-- Note that this test currently is not included in the default
+-- schedules, as currently reindexing catalog tables can cause
+-- deadlocks:
+--
+-- * The lock upgrade between the ShareLock acquired for the reindex
+-- and RowExclusiveLock needed for pg_class/pg_index locks can
+-- trigger deadlocks.
+--
+-- * The uniqueness checks performed when reindexing a unique/primary
+-- key index possibly need to wait for the transaction of a
+-- about-to-deleted row in pg_class to commit. That can cause
+-- deadlocks because, in contrast to user tables, locks on catalog
+-- tables are routinely released before commit - therefore the lock
+-- held for reindexing doesn't guarantee that no running transaction
+-- performed modifications in the table underlying the index.
+--
+-- This is particularly problematic as such conflicts can be
+-- triggered even when run in isolation, as a previous session's
+-- temporary table cleanup might still be running (even when the
+-- session ended from a client perspective).
+-- Check reindexing of whole tables
+REINDEX TABLE pg_class; -- mapped, non-shared, critical
+REINDEX TABLE pg_index; -- non-mapped, non-shared, critical
+REINDEX TABLE pg_operator; -- non-mapped, non-shared, critical
+REINDEX TABLE pg_database; -- mapped, shared, critical
+REINDEX TABLE pg_shdescription; -- mapped, shared non-critical
+-- Check that individual system indexes can be reindexed. That's a bit
+-- different from the entire-table case because reindex_relation
+-- treats e.g. pg_class special.
+REINDEX INDEX pg_class_oid_index; -- mapped, non-shared, critical
+REINDEX INDEX pg_class_relname_nsp_index; -- mapped, non-shared, non-critical
+REINDEX INDEX pg_index_indexrelid_index; -- non-mapped, non-shared, critical
+REINDEX INDEX pg_index_indrelid_index; -- non-mapped, non-shared, non-critical
+REINDEX INDEX pg_database_oid_index; -- mapped, shared, critical
+REINDEX INDEX pg_shdescription_o_c_index; -- mapped, shared, non-critical
+-- Check the same REINDEX INDEX statements under parallelism.
+BEGIN;
+SET min_parallel_table_scan_size = 0;
+REINDEX INDEX pg_class_oid_index; -- mapped, non-shared, critical
+REINDEX INDEX pg_class_relname_nsp_index; -- mapped, non-shared, non-critical
+REINDEX INDEX pg_index_indexrelid_index; -- non-mapped, non-shared, critical
+REINDEX INDEX pg_index_indrelid_index; -- non-mapped, non-shared, non-critical
+REINDEX INDEX pg_database_oid_index; -- mapped, shared, critical
+REINDEX INDEX pg_shdescription_o_c_index; -- mapped, shared, non-critical
+ROLLBACK;
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
new file mode 100644
index 0000000..b6aef6f
--- /dev/null
+++ b/src/test/regress/expected/reloptions.out
@@ -0,0 +1,226 @@
+-- Simple create
+CREATE TABLE reloptions_test(i INT) WITH (FiLLFaCToR=30,
+ autovacuum_enabled = false, autovacuum_analyze_scale_factor = 0.2);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------------------------------------------------------------
+ {fillfactor=30,autovacuum_enabled=false,autovacuum_analyze_scale_factor=0.2}
+(1 row)
+
+-- Fail min/max values check
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=2);
+ERROR: value 2 out of bounds for option "fillfactor"
+DETAIL: Valid values are between "10" and "100".
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=110);
+ERROR: value 110 out of bounds for option "fillfactor"
+DETAIL: Valid values are between "10" and "100".
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor = -10.0);
+ERROR: value -10.0 out of bounds for option "autovacuum_analyze_scale_factor"
+DETAIL: Valid values are between "0.000000" and "100.000000".
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor = 110.0);
+ERROR: value 110.0 out of bounds for option "autovacuum_analyze_scale_factor"
+DETAIL: Valid values are between "0.000000" and "100.000000".
+-- Fail when option and namespace do not exist
+CREATE TABLE reloptions_test2(i INT) WITH (not_existing_option=2);
+ERROR: unrecognized parameter "not_existing_option"
+CREATE TABLE reloptions_test2(i INT) WITH (not_existing_namespace.fillfactor=2);
+ERROR: unrecognized parameter namespace "not_existing_namespace"
+-- Fail while setting improper values
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=-30.1);
+ERROR: value -30.1 out of bounds for option "fillfactor"
+DETAIL: Valid values are between "10" and "100".
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor='string');
+ERROR: invalid value for integer option "fillfactor": string
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=true);
+ERROR: invalid value for integer option "fillfactor": true
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=12);
+ERROR: invalid value for boolean option "autovacuum_enabled": 12
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=30.5);
+ERROR: invalid value for boolean option "autovacuum_enabled": 30.5
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled='string');
+ERROR: invalid value for boolean option "autovacuum_enabled": string
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor='string');
+ERROR: invalid value for floating point option "autovacuum_analyze_scale_factor": string
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor=true);
+ERROR: invalid value for floating point option "autovacuum_analyze_scale_factor": true
+-- Fail if option is specified twice
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=30, fillfactor=40);
+ERROR: parameter "fillfactor" specified more than once
+-- Specifying name only for a non-Boolean option should fail
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor);
+ERROR: invalid value for integer option "fillfactor": true
+-- Simple ALTER TABLE
+ALTER TABLE reloptions_test SET (fillfactor=31,
+ autovacuum_analyze_scale_factor = 0.3);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------------------------------------------------------------
+ {autovacuum_enabled=false,fillfactor=31,autovacuum_analyze_scale_factor=0.3}
+(1 row)
+
+-- Set boolean option to true without specifying value
+ALTER TABLE reloptions_test SET (autovacuum_enabled, fillfactor=32);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------------------------------------------------------
+ {autovacuum_analyze_scale_factor=0.3,autovacuum_enabled=true,fillfactor=32}
+(1 row)
+
+-- Check that RESET works well
+ALTER TABLE reloptions_test RESET (fillfactor);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------------------------------------------
+ {autovacuum_analyze_scale_factor=0.3,autovacuum_enabled=true}
+(1 row)
+
+-- Resetting all values causes the column to become null
+ALTER TABLE reloptions_test RESET (autovacuum_enabled,
+ autovacuum_analyze_scale_factor);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass AND
+ reloptions IS NULL;
+ reloptions
+------------
+
+(1 row)
+
+-- RESET fails if a value is specified
+ALTER TABLE reloptions_test RESET (fillfactor=12);
+ERROR: RESET must not include values for parameters
+-- Test vacuum_truncate option
+DROP TABLE reloptions_test;
+CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
+ WITH (vacuum_truncate=false,
+ toast.vacuum_truncate=false,
+ autovacuum_enabled=false);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+--------------------------------------------------
+ {vacuum_truncate=false,autovacuum_enabled=false}
+(1 row)
+
+INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL);
+ERROR: null value in column "i" of relation "reloptions_test" violates not-null constraint
+DETAIL: Failing row contains (null, null).
+-- Do an aggressive vacuum to prevent page-skipping.
+VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) reloptions_test;
+SELECT pg_relation_size('reloptions_test') > 0;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT reloptions FROM pg_class WHERE oid =
+ (SELECT reltoastrelid FROM pg_class
+ WHERE oid = 'reloptions_test'::regclass);
+ reloptions
+-------------------------
+ {vacuum_truncate=false}
+(1 row)
+
+ALTER TABLE reloptions_test RESET (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+----------------------------
+ {autovacuum_enabled=false}
+(1 row)
+
+INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL);
+ERROR: null value in column "i" of relation "reloptions_test" violates not-null constraint
+DETAIL: Failing row contains (null, null).
+-- Do an aggressive vacuum to prevent page-skipping.
+VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) reloptions_test;
+SELECT pg_relation_size('reloptions_test') = 0;
+ ?column?
+----------
+ t
+(1 row)
+
+-- Test toast.* options
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test (s VARCHAR)
+ WITH (toast.autovacuum_vacuum_cost_delay = 23);
+SELECT reltoastrelid as toast_oid
+ FROM pg_class WHERE oid = 'reloptions_test'::regclass \gset
+SELECT reloptions FROM pg_class WHERE oid = :toast_oid;
+ reloptions
+-----------------------------------
+ {autovacuum_vacuum_cost_delay=23}
+(1 row)
+
+ALTER TABLE reloptions_test SET (toast.autovacuum_vacuum_cost_delay = 24);
+SELECT reloptions FROM pg_class WHERE oid = :toast_oid;
+ reloptions
+-----------------------------------
+ {autovacuum_vacuum_cost_delay=24}
+(1 row)
+
+ALTER TABLE reloptions_test RESET (toast.autovacuum_vacuum_cost_delay);
+SELECT reloptions FROM pg_class WHERE oid = :toast_oid;
+ reloptions
+------------
+
+(1 row)
+
+-- Fail on non-existent options in toast namespace
+CREATE TABLE reloptions_test2 (i int) WITH (toast.not_existing_option = 42);
+ERROR: unrecognized parameter "not_existing_option"
+-- Mix TOAST & heap
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test (s VARCHAR) WITH
+ (toast.autovacuum_vacuum_cost_delay = 23,
+ autovacuum_vacuum_cost_delay = 24, fillfactor = 40);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-------------------------------------------------
+ {autovacuum_vacuum_cost_delay=24,fillfactor=40}
+(1 row)
+
+SELECT reloptions FROM pg_class WHERE oid = (
+ SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass);
+ reloptions
+-----------------------------------
+ {autovacuum_vacuum_cost_delay=23}
+(1 row)
+
+--
+-- CREATE INDEX, ALTER INDEX for btrees
+--
+CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (fillfactor=30);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx'::regclass;
+ reloptions
+-----------------
+ {fillfactor=30}
+(1 row)
+
+-- Fail when option and namespace do not exist
+CREATE INDEX reloptions_test_idx ON reloptions_test (s)
+ WITH (not_existing_option=2);
+ERROR: unrecognized parameter "not_existing_option"
+CREATE INDEX reloptions_test_idx ON reloptions_test (s)
+ WITH (not_existing_ns.fillfactor=2);
+ERROR: unrecognized parameter namespace "not_existing_ns"
+-- Check allowed ranges
+CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor=1);
+ERROR: value 1 out of bounds for option "fillfactor"
+DETAIL: Valid values are between "10" and "100".
+CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor=130);
+ERROR: value 130 out of bounds for option "fillfactor"
+DETAIL: Valid values are between "10" and "100".
+-- Check ALTER
+ALTER INDEX reloptions_test_idx SET (fillfactor=40);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx'::regclass;
+ reloptions
+-----------------
+ {fillfactor=40}
+(1 row)
+
+-- Check ALTER on empty reloption list
+CREATE INDEX reloptions_test_idx3 ON reloptions_test (s);
+ALTER INDEX reloptions_test_idx3 SET (fillfactor=40);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx3'::regclass;
+ reloptions
+-----------------
+ {fillfactor=40}
+(1 row)
+
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
new file mode 100644
index 0000000..e25ec06
--- /dev/null
+++ b/src/test/regress/expected/replica_identity.out
@@ -0,0 +1,233 @@
+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)
+) ;
+CREATE TABLE test_replica_identity_othertable (id serial primary key);
+CREATE INDEX test_replica_identity_keyab ON test_replica_identity (keya, keyb);
+CREATE UNIQUE INDEX test_replica_identity_keyab_key ON test_replica_identity (keya, keyb);
+CREATE UNIQUE INDEX test_replica_identity_nonkey ON test_replica_identity (keya, nonkey);
+CREATE INDEX test_replica_identity_hash ON test_replica_identity USING hash (nonkey);
+CREATE UNIQUE INDEX test_replica_identity_expr ON test_replica_identity (keya, keyb, (3));
+CREATE UNIQUE INDEX test_replica_identity_partial ON test_replica_identity (keya, keyb) WHERE keyb != '3';
+-- default is 'd'/DEFAULT for user created tables
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident
+--------------
+ d
+(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)
+
+----
+-- Make sure we detect ineligible indexes
+----
+-- fail, not unique
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab;
+ERROR: cannot use non-unique index "test_replica_identity_keyab" as replica identity
+-- fail, not a candidate key, nullable column
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_nonkey;
+ERROR: index "test_replica_identity_nonkey" cannot be used as replica identity because column "nonkey" is nullable
+-- fail, hash indexes cannot do uniqueness
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_hash;
+ERROR: cannot use non-unique index "test_replica_identity_hash" as replica identity
+-- fail, expression index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_expr;
+ERROR: cannot use expression index "test_replica_identity_expr" as replica identity
+-- fail, partial index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_partial;
+ERROR: cannot use partial index "test_replica_identity_partial" as replica identity
+-- fail, not our index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_othertable_pkey;
+ERROR: "test_replica_identity_othertable_pkey" is not an index for table "test_replica_identity"
+-- fail, deferrable
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_defer;
+ERROR: cannot use non-immediate index "test_replica_identity_unique_defer" as replica identity
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident
+--------------
+ d
+(1 row)
+
+----
+-- Make sure index cases succeed
+----
+-- succeed, primary key
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_pkey;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident
+--------------
+ i
+(1 row)
+
+\d test_replica_identity
+ Table "public.test_replica_identity"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------------------------------------------------
+ id | integer | | not null | nextval('test_replica_identity_id_seq'::regclass)
+ keya | text | | not null |
+ keyb | text | | not null |
+ nonkey | text | | |
+Indexes:
+ "test_replica_identity_pkey" PRIMARY KEY, btree (id) REPLICA IDENTITY
+ "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3))
+ "test_replica_identity_hash" hash (nonkey)
+ "test_replica_identity_keyab" btree (keya, keyb)
+ "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb)
+ "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey)
+ "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text
+ "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
+ "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
+
+-- succeed, nondeferrable unique constraint over nonnullable cols
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_nondefer;
+-- succeed unique index over nonnullable cols
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key;
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident
+--------------
+ i
+(1 row)
+
+\d test_replica_identity
+ Table "public.test_replica_identity"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------------------------------------------------
+ id | integer | | not null | nextval('test_replica_identity_id_seq'::regclass)
+ keya | text | | not null |
+ keyb | text | | not null |
+ nonkey | text | | |
+Indexes:
+ "test_replica_identity_pkey" PRIMARY KEY, btree (id)
+ "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3))
+ "test_replica_identity_hash" hash (nonkey)
+ "test_replica_identity_keyab" btree (keya, keyb)
+ "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb) REPLICA IDENTITY
+ "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey)
+ "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text
+ "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
+ "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
+
+SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident;
+ count
+-------
+ 1
+(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, btree (id)
+ "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3))
+ "test_replica_identity_hash" hash (nonkey)
+ "test_replica_identity_keyab" btree (keya, keyb)
+ "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb)
+ "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey)
+ "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text
+ "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
+ "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
+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)
+
+---
+-- Test that ALTER TABLE rewrite preserves nondefault replica identity
+---
+-- constraint variant
+CREATE TABLE test_replica_identity2 (id int UNIQUE NOT NULL);
+ALTER TABLE test_replica_identity2 REPLICA IDENTITY USING INDEX test_replica_identity2_id_key;
+\d test_replica_identity2
+ Table "public.test_replica_identity2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | not null |
+Indexes:
+ "test_replica_identity2_id_key" UNIQUE CONSTRAINT, btree (id) REPLICA IDENTITY
+
+ALTER TABLE test_replica_identity2 ALTER COLUMN id TYPE bigint;
+\d test_replica_identity2
+ Table "public.test_replica_identity2"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+---------
+ id | bigint | | not null |
+Indexes:
+ "test_replica_identity2_id_key" UNIQUE CONSTRAINT, btree (id) REPLICA IDENTITY
+
+-- straight index variant
+CREATE TABLE test_replica_identity3 (id int NOT NULL);
+CREATE UNIQUE INDEX test_replica_identity3_id_key ON test_replica_identity3 (id);
+ALTER TABLE test_replica_identity3 REPLICA IDENTITY USING INDEX test_replica_identity3_id_key;
+\d test_replica_identity3
+ Table "public.test_replica_identity3"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | not null |
+Indexes:
+ "test_replica_identity3_id_key" UNIQUE, btree (id) REPLICA IDENTITY
+
+ALTER TABLE test_replica_identity3 ALTER COLUMN id TYPE bigint;
+\d test_replica_identity3
+ Table "public.test_replica_identity3"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+---------
+ id | bigint | | not null |
+Indexes:
+ "test_replica_identity3_id_key" UNIQUE, btree (id) REPLICA IDENTITY
+
+-- ALTER TABLE DROP NOT NULL is not allowed for columns part of an index
+-- used as replica identity.
+ALTER TABLE test_replica_identity3 ALTER COLUMN id DROP NOT NULL;
+ERROR: column "id" is in index used as replica identity
+DROP TABLE test_replica_identity;
+DROP TABLE test_replica_identity2;
+DROP TABLE test_replica_identity3;
+DROP TABLE test_replica_identity_othertable;
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
new file mode 100644
index 0000000..cb51bb8
--- /dev/null
+++ b/src/test/regress/expected/returning.out
@@ -0,0 +1,357 @@
+--
+-- Test INSERT/UPDATE/DELETE RETURNING
+--
+-- Simple cases
+CREATE TEMP TABLE foo (f1 serial, f2 text, f3 int default 42);
+INSERT INTO foo (f2,f3)
+ VALUES ('test', DEFAULT), ('More', 11), (upper('more'), 7+9)
+ RETURNING *, f1+f3 AS sum;
+ f1 | f2 | f3 | sum
+----+------+----+-----
+ 1 | test | 42 | 43
+ 2 | More | 11 | 13
+ 3 | MORE | 16 | 19
+(3 rows)
+
+SELECT * FROM foo;
+ f1 | f2 | f3
+----+------+----
+ 1 | test | 42
+ 2 | More | 11
+ 3 | MORE | 16
+(3 rows)
+
+UPDATE foo SET f2 = lower(f2), f3 = DEFAULT RETURNING foo.*, f1+f3 AS sum13;
+ f1 | f2 | f3 | sum13
+----+------+----+-------
+ 1 | test | 42 | 43
+ 2 | more | 42 | 44
+ 3 | more | 42 | 45
+(3 rows)
+
+SELECT * FROM foo;
+ f1 | f2 | f3
+----+------+----
+ 1 | test | 42
+ 2 | more | 42
+ 3 | more | 42
+(3 rows)
+
+DELETE FROM foo WHERE f1 > 2 RETURNING f3, f2, f1, least(f1,f3);
+ f3 | f2 | f1 | least
+----+------+----+-------
+ 42 | more | 3 | 3
+(1 row)
+
+SELECT * FROM foo;
+ f1 | f2 | f3
+----+------+----
+ 1 | test | 42
+ 2 | more | 42
+(2 rows)
+
+-- Subplans and initplans in the RETURNING list
+INSERT INTO foo SELECT f1+10, f2, f3+99 FROM foo
+ RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan,
+ EXISTS(SELECT * FROM int4_tbl) AS initplan;
+ f1 | f2 | f3 | subplan | initplan
+----+------+-----+---------+----------
+ 11 | test | 141 | t | t
+ 12 | more | 141 | f | t
+(2 rows)
+
+UPDATE foo SET f3 = f3 * 2
+ WHERE f1 > 10
+ RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan,
+ EXISTS(SELECT * FROM int4_tbl) AS initplan;
+ f1 | f2 | f3 | subplan | initplan
+----+------+-----+---------+----------
+ 11 | test | 282 | t | t
+ 12 | more | 282 | f | t
+(2 rows)
+
+DELETE FROM foo
+ WHERE f1 > 10
+ RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan,
+ EXISTS(SELECT * FROM int4_tbl) AS initplan;
+ f1 | f2 | f3 | subplan | initplan
+----+------+-----+---------+----------
+ 11 | test | 282 | t | t
+ 12 | more | 282 | f | t
+(2 rows)
+
+-- Joins
+UPDATE foo SET f3 = f3*2
+ FROM int4_tbl i
+ WHERE foo.f1 + 123455 = i.f1
+ RETURNING foo.*, i.f1 as "i.f1";
+ f1 | f2 | f3 | i.f1
+----+------+----+--------
+ 1 | test | 84 | 123456
+(1 row)
+
+SELECT * FROM foo;
+ f1 | f2 | f3
+----+------+----
+ 2 | more | 42
+ 1 | test | 84
+(2 rows)
+
+DELETE FROM foo
+ USING int4_tbl i
+ WHERE foo.f1 + 123455 = i.f1
+ RETURNING foo.*, i.f1 as "i.f1";
+ f1 | f2 | f3 | i.f1
+----+------+----+--------
+ 1 | test | 84 | 123456
+(1 row)
+
+SELECT * FROM foo;
+ f1 | f2 | f3
+----+------+----
+ 2 | more | 42
+(1 row)
+
+-- Check inheritance cases
+CREATE TEMP TABLE foochild (fc int) INHERITS (foo);
+INSERT INTO foochild VALUES(123,'child',999,-123);
+ALTER TABLE foo ADD COLUMN f4 int8 DEFAULT 99;
+SELECT * FROM foo;
+ f1 | f2 | f3 | f4
+-----+-------+-----+----
+ 2 | more | 42 | 99
+ 123 | child | 999 | 99
+(2 rows)
+
+SELECT * FROM foochild;
+ f1 | f2 | f3 | fc | f4
+-----+-------+-----+------+----
+ 123 | child | 999 | -123 | 99
+(1 row)
+
+UPDATE foo SET f4 = f4 + f3 WHERE f4 = 99 RETURNING *;
+ f1 | f2 | f3 | f4
+-----+-------+-----+------
+ 2 | more | 42 | 141
+ 123 | child | 999 | 1098
+(2 rows)
+
+SELECT * FROM foo;
+ f1 | f2 | f3 | f4
+-----+-------+-----+------
+ 2 | more | 42 | 141
+ 123 | child | 999 | 1098
+(2 rows)
+
+SELECT * FROM foochild;
+ f1 | f2 | f3 | fc | f4
+-----+-------+-----+------+------
+ 123 | child | 999 | -123 | 1098
+(1 row)
+
+UPDATE foo SET f3 = f3*2
+ FROM int8_tbl i
+ WHERE foo.f1 = i.q2
+ RETURNING *;
+ f1 | f2 | f3 | f4 | q1 | q2
+-----+-------+------+------+------------------+-----
+ 123 | child | 1998 | 1098 | 4567890123456789 | 123
+(1 row)
+
+SELECT * FROM foo;
+ f1 | f2 | f3 | f4
+-----+-------+------+------
+ 2 | more | 42 | 141
+ 123 | child | 1998 | 1098
+(2 rows)
+
+SELECT * FROM foochild;
+ f1 | f2 | f3 | fc | f4
+-----+-------+------+------+------
+ 123 | child | 1998 | -123 | 1098
+(1 row)
+
+DELETE FROM foo
+ USING int8_tbl i
+ WHERE foo.f1 = i.q2
+ RETURNING *;
+ f1 | f2 | f3 | f4 | q1 | q2
+-----+-------+------+------+------------------+-----
+ 123 | child | 1998 | 1098 | 4567890123456789 | 123
+(1 row)
+
+SELECT * FROM foo;
+ f1 | f2 | f3 | f4
+----+------+----+-----
+ 2 | more | 42 | 141
+(1 row)
+
+SELECT * FROM foochild;
+ f1 | f2 | f3 | fc | f4
+----+----+----+----+----
+(0 rows)
+
+DROP TABLE foochild;
+-- Rules and views
+CREATE TEMP VIEW voo AS SELECT f1, f2 FROM foo;
+CREATE RULE voo_i AS ON INSERT TO voo DO INSTEAD
+ INSERT INTO foo VALUES(new.*, 57);
+INSERT INTO voo VALUES(11,'zit');
+-- fails:
+INSERT INTO voo VALUES(12,'zoo') RETURNING *, f1*2;
+ERROR: cannot perform INSERT RETURNING on relation "voo"
+HINT: You need an unconditional ON INSERT DO INSTEAD rule with a RETURNING clause.
+-- fails, incompatible list:
+CREATE OR REPLACE RULE voo_i AS ON INSERT TO voo DO INSTEAD
+ INSERT INTO foo VALUES(new.*, 57) RETURNING *;
+ERROR: RETURNING list has too many entries
+CREATE OR REPLACE RULE voo_i AS ON INSERT TO voo DO INSTEAD
+ INSERT INTO foo VALUES(new.*, 57) RETURNING f1, f2;
+-- should still work
+INSERT INTO voo VALUES(13,'zit2');
+-- works now
+INSERT INTO voo VALUES(14,'zoo2') RETURNING *;
+ f1 | f2
+----+------
+ 14 | zoo2
+(1 row)
+
+SELECT * FROM foo;
+ f1 | f2 | f3 | f4
+----+------+----+-----
+ 2 | more | 42 | 141
+ 11 | zit | 57 | 99
+ 13 | zit2 | 57 | 99
+ 14 | zoo2 | 57 | 99
+(4 rows)
+
+SELECT * FROM voo;
+ f1 | f2
+----+------
+ 2 | more
+ 11 | zit
+ 13 | zit2
+ 14 | zoo2
+(4 rows)
+
+CREATE OR REPLACE RULE voo_u AS ON UPDATE TO voo DO INSTEAD
+ UPDATE foo SET f1 = new.f1, f2 = new.f2 WHERE f1 = old.f1
+ RETURNING f1, f2;
+update voo set f1 = f1 + 1 where f2 = 'zoo2';
+update voo set f1 = f1 + 1 where f2 = 'zoo2' RETURNING *, f1*2;
+ f1 | f2 | ?column?
+----+------+----------
+ 16 | zoo2 | 32
+(1 row)
+
+SELECT * FROM foo;
+ f1 | f2 | f3 | f4
+----+------+----+-----
+ 2 | more | 42 | 141
+ 11 | zit | 57 | 99
+ 13 | zit2 | 57 | 99
+ 16 | zoo2 | 57 | 99
+(4 rows)
+
+SELECT * FROM voo;
+ f1 | f2
+----+------
+ 2 | more
+ 11 | zit
+ 13 | zit2
+ 16 | zoo2
+(4 rows)
+
+CREATE OR REPLACE RULE voo_d AS ON DELETE TO voo DO INSTEAD
+ DELETE FROM foo WHERE f1 = old.f1
+ RETURNING f1, f2;
+DELETE FROM foo WHERE f1 = 13;
+DELETE FROM foo WHERE f2 = 'zit' RETURNING *;
+ f1 | f2 | f3 | f4
+----+-----+----+----
+ 11 | zit | 57 | 99
+(1 row)
+
+SELECT * FROM foo;
+ f1 | f2 | f3 | f4
+----+------+----+-----
+ 2 | more | 42 | 141
+ 16 | zoo2 | 57 | 99
+(2 rows)
+
+SELECT * FROM voo;
+ f1 | f2
+----+------
+ 2 | more
+ 16 | zoo2
+(2 rows)
+
+-- Try a join case
+CREATE TEMP TABLE joinme (f2j text, other int);
+INSERT INTO joinme VALUES('more', 12345);
+INSERT INTO joinme VALUES('zoo2', 54321);
+INSERT INTO joinme VALUES('other', 0);
+CREATE TEMP VIEW joinview AS
+ SELECT foo.*, other FROM foo JOIN joinme ON (f2 = f2j);
+SELECT * FROM joinview;
+ f1 | f2 | f3 | f4 | other
+----+------+----+-----+-------
+ 2 | more | 42 | 141 | 12345
+ 16 | zoo2 | 57 | 99 | 54321
+(2 rows)
+
+CREATE RULE joinview_u AS ON UPDATE TO joinview DO INSTEAD
+ UPDATE foo SET f1 = new.f1, f3 = new.f3
+ FROM joinme WHERE f2 = f2j AND f2 = old.f2
+ RETURNING foo.*, other;
+UPDATE joinview SET f1 = f1 + 1 WHERE f3 = 57 RETURNING *, other + 1;
+ f1 | f2 | f3 | f4 | other | ?column?
+----+------+----+----+-------+----------
+ 17 | zoo2 | 57 | 99 | 54321 | 54322
+(1 row)
+
+SELECT * FROM joinview;
+ f1 | f2 | f3 | f4 | other
+----+------+----+-----+-------
+ 2 | more | 42 | 141 | 12345
+ 17 | zoo2 | 57 | 99 | 54321
+(2 rows)
+
+SELECT * FROM foo;
+ f1 | f2 | f3 | f4
+----+------+----+-----
+ 2 | more | 42 | 141
+ 17 | zoo2 | 57 | 99
+(2 rows)
+
+SELECT * FROM voo;
+ f1 | f2
+----+------
+ 2 | more
+ 17 | zoo2
+(2 rows)
+
+-- Check aliased target relation
+INSERT INTO foo AS bar DEFAULT VALUES RETURNING *; -- ok
+ f1 | f2 | f3 | f4
+----+----+----+----
+ 4 | | 42 | 99
+(1 row)
+
+INSERT INTO foo AS bar DEFAULT VALUES RETURNING foo.*; -- fails, wrong name
+ERROR: invalid reference to FROM-clause entry for table "foo"
+LINE 1: INSERT INTO foo AS bar DEFAULT VALUES RETURNING foo.*;
+ ^
+HINT: Perhaps you meant to reference the table alias "bar".
+INSERT INTO foo AS bar DEFAULT VALUES RETURNING bar.*; -- ok
+ f1 | f2 | f3 | f4
+----+----+----+----
+ 5 | | 42 | 99
+(1 row)
+
+INSERT INTO foo AS bar DEFAULT VALUES RETURNING bar.f3; -- ok
+ f3
+----
+ 42
+(1 row)
+
diff --git a/src/test/regress/expected/roleattributes.out b/src/test/regress/expected/roleattributes.out
new file mode 100644
index 0000000..5e6969b
--- /dev/null
+++ b/src/test/regress/expected/roleattributes.out
@@ -0,0 +1,249 @@
+-- default for superuser is false
+CREATE ROLE regress_test_def_superuser;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_superuser | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+CREATE ROLE regress_test_superuser WITH SUPERUSER;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_superuser | t | t | f | f | f | f | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_superuser WITH NOSUPERUSER;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_superuser | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_superuser WITH SUPERUSER;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_superuser | t | t | f | f | f | f | f | -1 | |
+(1 row)
+
+-- default for inherit is true
+CREATE ROLE regress_test_def_inherit;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_inherit | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+CREATE ROLE regress_test_inherit WITH NOINHERIT;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_inherit | f | f | f | f | f | f | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_inherit WITH INHERIT;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_inherit | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_inherit WITH NOINHERIT;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_inherit | f | f | f | f | f | f | f | -1 | |
+(1 row)
+
+-- default for create role is false
+CREATE ROLE regress_test_def_createrole;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_createrole | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+CREATE ROLE regress_test_createrole WITH CREATEROLE;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_createrole | f | t | t | f | f | f | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_createrole WITH NOCREATEROLE;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_createrole | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_createrole WITH CREATEROLE;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_createrole | f | t | t | f | f | f | f | -1 | |
+(1 row)
+
+-- default for create database is false
+CREATE ROLE regress_test_def_createdb;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_createdb | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+CREATE ROLE regress_test_createdb WITH CREATEDB;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_createdb | f | t | f | t | f | f | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_createdb WITH NOCREATEDB;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_createdb | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_createdb WITH CREATEDB;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_createdb | f | t | f | t | f | f | f | -1 | |
+(1 row)
+
+-- default for can login is false for role
+CREATE ROLE regress_test_def_role_canlogin;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+--------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_role_canlogin | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+CREATE ROLE regress_test_role_canlogin WITH LOGIN;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_role_canlogin WITH NOLOGIN;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_role_canlogin | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_role_canlogin WITH LOGIN;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+(1 row)
+
+-- default for can login is true for user
+CREATE USER regress_test_def_user_canlogin;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+--------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_user_canlogin | f | t | f | f | t | f | f | -1 | |
+(1 row)
+
+CREATE USER regress_test_user_canlogin WITH NOLOGIN;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+ALTER USER regress_test_user_canlogin WITH LOGIN;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_user_canlogin | f | t | f | f | t | f | f | -1 | |
+(1 row)
+
+ALTER USER regress_test_user_canlogin WITH NOLOGIN;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+-- default for replication is false
+CREATE ROLE regress_test_def_replication;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_replication | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+CREATE ROLE regress_test_replication WITH REPLICATION;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_replication | f | t | f | f | f | t | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_replication WITH NOREPLICATION;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_replication | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_replication WITH REPLICATION;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_replication | f | t | f | f | f | t | f | -1 | |
+(1 row)
+
+-- default for bypassrls is false
+CREATE ROLE regress_test_def_bypassrls;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_bypassrls | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+CREATE ROLE regress_test_bypassrls WITH BYPASSRLS;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | t | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_bypassrls WITH NOBYPASSRLS;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | f | -1 | |
+(1 row)
+
+ALTER ROLE regress_test_bypassrls WITH BYPASSRLS;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | t | -1 | |
+(1 row)
+
+-- clean up roles
+DROP ROLE regress_test_def_superuser;
+DROP ROLE regress_test_superuser;
+DROP ROLE regress_test_def_inherit;
+DROP ROLE regress_test_inherit;
+DROP ROLE regress_test_def_createrole;
+DROP ROLE regress_test_createrole;
+DROP ROLE regress_test_def_createdb;
+DROP ROLE regress_test_createdb;
+DROP ROLE regress_test_def_role_canlogin;
+DROP ROLE regress_test_role_canlogin;
+DROP USER regress_test_def_user_canlogin;
+DROP USER regress_test_user_canlogin;
+DROP ROLE regress_test_def_replication;
+DROP ROLE regress_test_replication;
+DROP ROLE regress_test_def_bypassrls;
+DROP ROLE regress_test_bypassrls;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index 0000000..89397e4
--- /dev/null
+++ b/src/test/regress/expected/rowsecurity.out
@@ -0,0 +1,4042 @@
+--
+-- 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, btree (did)
+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;
+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
+-----+-----+--------+-------------------+-------------------------
+ 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
+(4 rows)
+
+-- 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;
+NOTICE: f_leak => my first manga
+NOTICE: f_leak => my second manga
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => great manga
+NOTICE: f_leak => awesome science fiction
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 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
+ 8 | 44 | 1 | regress_rls_carol | great manga
+ 9 | 22 | 1 | regress_rls_dave | awesome science fiction
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
+ QUERY PLAN
+----------------------------------------------------
+ Seq Scan on document
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ QUERY PLAN
+-----------------------------------------------------------
+ Hash Join
+ Hash Cond: (category.cid = document.cid)
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+ -> Seq Scan on category
+ -> Hash
+ -> Seq Scan on document
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+(9 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
+ Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------
+ Hash Join
+ Hash Cond: (category.cid = document.cid)
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+ -> Seq Scan on category
+ -> Hash
+ -> Seq Scan on document
+ Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))
+(9 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
+---------------------------------------------------------------
+ Nested Loop
+ -> Seq Scan on document
+ Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))
+ -> Index Scan using category_pkey on category
+ Index Cond: (cid = document.cid)
+(5 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
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2 t1_2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3 t1_3
+ 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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(7 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
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2 t1_2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3 t1_3
+ 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
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2 t1_2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3 t1_3
+ 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
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2 t1_2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3 t1_3
+ 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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(8 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
+ 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
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ -> Seq Scan on part_document_satire part_document_2
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ -> Seq Scan on part_document_nonfiction part_document_3
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+(10 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
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ -> Seq Scan on part_document_satire part_document_2
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ -> Seq Scan on part_document_nonfiction part_document_3
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+(10 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
+ Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+(5 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
+ Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+(5 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
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ -> Seq Scan on part_document_satire part_document_2
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ -> Seq Scan on part_document_nonfiction part_document_3
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+(10 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;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to policy r1 on table rec1
+drop cascades to policy r2 on table rec2
+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
+ 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
+ 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
+ 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 s2_1
+ 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
+ Filter: ((a <= 2) AND ((a % 2) = 0))
+ -> Seq Scan on t2 t1_2
+ Filter: ((a <= 2) AND ((a % 2) = 0))
+ -> Seq Scan on t3 t1_3
+ 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
+ Filter: (a <= 2)
+ -> Seq Scan on t2 t1_2
+ Filter: (a <= 2)
+ -> Seq Scan on t3 t1_3
+ 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
+ Filter: (a = 2)
+ -> Seq Scan on t2 t1_2
+ Filter: (a = 2)
+ -> Seq Scan on t3 t1_3
+ 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
+ Filter: ((a = 2) AND ((a % 2) = 0))
+ -> Seq Scan on t2 t1_2
+ Filter: ((a = 2) AND ((a % 2) = 0))
+ -> Seq Scan on t3 t1_3
+ 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
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(12 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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(3 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
+ Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+ -> Seq Scan on t3
+ Filter: ((a = 2) AND f_leak(b))
+(6 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
+ -> Seq Scan on t2
+ Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+(14 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);
+NOTICE: f_leak => cde
+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
+ -> Seq Scan on t2
+ Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+(11 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);
+NOTICE: f_leak => cde
+-- 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
+ -> Nested Loop
+ Join Filter: (t2_1.b = t2_2.b)
+ -> Seq Scan on t2 t2_1
+ Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+ -> Seq Scan on t2 t2_2
+ Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+(7 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
+ -> Nested Loop
+ Join Filter: (t1_1.b = t1_2.b)
+ -> Append
+ -> Seq Scan on t1 t1_1_1
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_1_2
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_1_3
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Materialize
+ -> Append
+ -> Seq Scan on t1 t1_2_1
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2_2
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_2_3
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+(21 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 => daddad_updt
+NOTICE: f_leak => defdef
+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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(3 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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(11 rows)
+
+DELETE FROM only t1 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)
+
+DELETE FROM t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
+NOTICE: f_leak => bcdbcd
+NOTICE: f_leak => defdef
+NOTICE: f_leak => yyyyyy
+ tableoid | id | a | b | t1
+----------+-----+---+--------+----------------
+ t2 | 202 | 2 | bcdbcd | (202,2,bcdbcd)
+ t2 | 204 | 4 | defdef | (204,4,defdef)
+ t3 | 302 | 2 | yyyyyy | (302,2,yyyyyy)
+(3 rows)
+
+--
+-- 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;
+GRANT ALL ON bv1 TO regress_rls_carol;
+SET SESSION AUTHORIZATION regress_rls_carol;
+EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);
+ QUERY PLAN
+---------------------------------------------
+ Subquery Scan on bv1
+ Filter: f_leak(bv1.b)
+ -> Seq Scan on b1
+ Filter: ((a > 0) AND ((a % 2) = 0))
+(4 rows)
+
+SELECT * FROM bv1 WHERE f_leak(b);
+NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
+NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
+NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d
+NOTICE: f_leak => d3d9446802a44259755d38e6d163e820
+ a | b
+----+----------------------------------
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+(5 rows)
+
+INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO
+ERROR: new row violates row-level security policy for table "b1"
+INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check
+ERROR: new row violates row-level security policy for table "b1"
+INSERT INTO bv1 VALUES (12, 'xxx'); -- ok
+EXPLAIN (COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Update on b1
+ -> Seq Scan on b1
+ Filter: ((a > 0) AND (a = 4) AND ((a % 2) = 0) AND f_leak(b))
+(3 rows)
+
+UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
+EXPLAIN (COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Delete on b1
+ -> Seq Scan on b1
+ Filter: ((a > 0) AND (a = 6) AND ((a % 2) = 0) AND f_leak(b))
+(3 rows)
+
+DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM b1;
+ a | b
+-----+----------------------------------
+ -10 | 1b0fd9efa5279c4203b7c70233f86dbf
+ -9 | 252e691406782824eec43d7eadc3d256
+ -8 | a8d2ec85eaf98407310b72eb73dda247
+ -7 | 74687a12d3915d3c4d83f1af7b3683d5
+ -6 | 596a3d04481816330f07e4f97510c28f
+ -5 | 47c1b025fa18ea96c33fbb6718688c0f
+ -4 | 0267aaf632e87a63288a08331f22c7c3
+ -3 | b3149ecea4628efd23d2f86e5a723472
+ -2 | 5d7b9adcbe1c629ec722529dd12e5129
+ -1 | 6bb61e3b7bce0931da574d19d1d82c88
+ 0 | cfcd208495d565ef66e7dff9f98764da
+ 1 | c4ca4238a0b923820dcc509a6f75849b
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+ 5 | e4da3b7fbbce2345d7772b0674a318d5
+ 7 | 8f14e45fceea167a5a36dedd4bea2543
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | xxx
+ 4 | yyy
+(21 rows)
+
+--
+-- 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"
+--
+-- 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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
+PREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) EXECUTE plancache_test;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> CTE Scan on q
+ -> Materialize
+ -> Seq Scan on z2
+(7 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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(7 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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ CTE q
+ -> Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> CTE Scan on q
+ -> Materialize
+ -> Seq Scan on z2
+(7 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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(7 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
+ Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ CTE q
+ -> Seq Scan on z1
+ Filter: (((a % 2) = 1) AND f_leak(b))
+ -> CTE Scan on q
+ -> Materialize
+ -> Seq Scan on z2
+(7 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
+ Filter: (((a % 2) = 1) AND f_leak(b))
+(7 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
+ Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ CTE q
+ -> Seq Scan on z1
+ Filter: (((a % 2) = 1) AND f_leak(b))
+ -> CTE Scan on q
+ -> Materialize
+ -> Seq Scan on z2
+(7 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
+ Filter: (((a % 2) = 1) AND f_leak(b))
+(7 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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 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;
+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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 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 => cde
+NOTICE: f_leak => fgh
+NOTICE: f_leak => bcd_updt
+NOTICE: f_leak => def_updt
+NOTICE: f_leak => fgh_updt
+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 => cde
+NOTICE: f_leak => fgh
+NOTICE: f_leak => bcd_updt
+NOTICE: f_leak => def_updt
+NOTICE: f_leak => fgh_updt
+NOTICE: f_leak => fgh_updt
+ a | b | c
+---+---------------+-------------------
+ 3 | cde_updt | regress_rls_carol
+ 7 | fgh_updt | regress_rls_carol
+ 2 | bcd_updt_updt | regress_rls_bob
+ 4 | def_updt_updt | regress_rls_carol
+ 6 | fgh_updt_updt | regress_rls_bob
+ 8 | fgh_updt_updt | regress_rls_carol
+(6 rows)
+
+DELETE FROM x1 WHERE f_leak(b) RETURNING *;
+NOTICE: f_leak => cde_updt
+NOTICE: f_leak => fgh_updt
+NOTICE: f_leak => bcd_updt_updt
+NOTICE: f_leak => def_updt_updt
+NOTICE: f_leak => fgh_updt_updt
+NOTICE: f_leak => fgh_updt_updt
+ a | b | c
+---+---------------+-------------------
+ 3 | cde_updt | regress_rls_carol
+ 7 | fgh_updt | regress_rls_carol
+ 2 | bcd_updt_updt | regress_rls_bob
+ 4 | def_updt_updt | regress_rls_carol
+ 6 | fgh_updt_updt | regress_rls_bob
+ 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
+ Filter: (f_leak(b) AND (a = 1))
+(2 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
+ Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0)) AND f_leak(b))
+(2 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
+ Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))
+(2 rows)
+
+--
+-- Qual push-down of leaky functions, when not referring to table
+--
+SELECT * FROM y2 WHERE 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
+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
+ Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))
+(2 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
+-------------------------------------------------------------------------
+ Hash Join
+ Hash Cond: (test_qual_pushdown.abc = y2.b)
+ -> Seq Scan on test_qual_pushdown
+ Filter: f_leak(abc)
+ -> Hash
+ -> Seq Scan on y2
+ Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))
+(7 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
+-----------------------------------------------------------------------------------------
+ Hash Join
+ Hash Cond: (test_qual_pushdown.abc = y2.b)
+ -> Seq Scan on test_qual_pushdown
+ -> Hash
+ -> Seq Scan on y2
+ Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))
+(6 rows)
+
+DROP TABLE test_qual_pushdown;
+--
+-- Plancache invalidate on user change.
+--
+RESET SESSION AUTHORIZATION;
+DROP TABLE t1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table t2
+drop cascades to table t3
+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
+ 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
+ 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
+ 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
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(4 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 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
+ 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.
+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.
+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
+ 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
+ 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;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 2 | bcd | regress_rls_bob
+(1 row)
+
+-- Still cannot UPDATE row 2 through cursor
+UPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;
+ currentid | payload | rlsuser
+-----------+---------+---------
+(0 rows)
+
+-- Can update row 4 through cursor, which is the next visible row
+FETCH RELATIVE 1 FROM current_check_cursor;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 4 | def | regress_rls_bob
+(1 row)
+
+UPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 4 | def_new | regress_rls_bob
+(1 row)
+
+SELECT * FROM current_check;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 2 | bcd | regress_rls_bob
+ 4 | def_new | regress_rls_bob
+(2 rows)
+
+-- Plan should be a subquery TID scan
+EXPLAIN (COSTS OFF) UPDATE current_check SET payload = payload WHERE CURRENT OF current_check_cursor;
+ QUERY PLAN
+-------------------------------------------------------------
+ Update on current_check
+ -> Tid Scan on current_check
+ TID Cond: CURRENT OF current_check_cursor
+ Filter: ((currentid = 4) AND ((currentid % 2) = 0))
+(4 rows)
+
+-- Similarly can only delete row 4
+FETCH ABSOLUTE 1 FROM current_check_cursor;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 2 | bcd | regress_rls_bob
+(1 row)
+
+DELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;
+ currentid | payload | rlsuser
+-----------+---------+---------
+(0 rows)
+
+FETCH RELATIVE 1 FROM current_check_cursor;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 4 | def | regress_rls_bob
+(1 row)
+
+DELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 4 | def_new | regress_rls_bob
+(1 row)
+
+SELECT * FROM current_check;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 2 | bcd | regress_rls_bob
+(1 row)
+
+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;
+--
+-- 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
+--
+-- 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;
+--
+-- 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;
+--
+-- 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;
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA regress_rls_schema CASCADE;
+NOTICE: drop cascades to 29 other objects
+DETAIL: drop cascades to function f_leak(text)
+drop cascades to table uaccount
+drop cascades to table category
+drop cascades to table document
+drop cascades to table part_document
+drop cascades to table dependent
+drop cascades to table rec1
+drop cascades to table rec2
+drop cascades to view rec1v
+drop cascades to view rec2v
+drop cascades to table s1
+drop cascades to table s2
+drop cascades to view v2
+drop cascades to table b1
+drop cascades to view bv1
+drop cascades to table z1
+drop cascades to table z2
+drop cascades to table x1
+drop cascades to table y1
+drop cascades to table y2
+drop cascades to table t1
+drop cascades to table t2
+drop cascades to table t3
+drop cascades to table t4
+drop cascades to table current_check
+drop cascades to table dep1
+drop cascades to table dep2
+drop cascades to table dob_t1
+drop cascades to table dob_t2
+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/rowtypes.out b/src/test/regress/expected/rowtypes.out
new file mode 100644
index 0000000..a4cc2d8
--- /dev/null
+++ b/src/test/regress/expected/rowtypes.out
@@ -0,0 +1,1242 @@
+--
+-- 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));
+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;
+ first | substr | length
+-------+----------------------+---------
+ Joe | Blow | 4
+ Jim | abcdefghijklabcdefgh | 1200000
+(2 rows)
+
+-- 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;
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Sort
+ Sort Key: thousand, tenthous
+ -> Bitmap Heap Scan on tenk1
+ Filter: (ROW(thousand, tenthous, four) > ROW(998, 5000, 3))
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: (ROW(thousand, tenthous) >= ROW(998, 5000))
+(6 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;
+ QUERY PLAN
+-----------------------------------------------------------
+ Sort
+ Sort Key: thousand, hundred
+ -> Bitmap Heap Scan on tenk1
+ Filter: (ROW(998, 5000) < ROW(thousand, hundred))
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: (thousand >= 998)
+(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;
+ id | active | price
+----+--------+--------
+ 1 | f | 42
+ 10 | t | 123.00
+ 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)
+
+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;
+ row_to_json
+----------------------------------
+ {"q2":456,"q1":123}
+ {"q2":4567890123456789,"q1":123}
+ {"q2":0,"q1":0}
+(3 rows)
+
+-- 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;
+ row_to_json
+-------------------
+ {"q1":33,"q2":44}
+(1 row)
+
+--
+-- 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/rules.out b/src/test/regress/expected/rules.out
new file mode 100644
index 0000000..2fa00a3
--- /dev/null
+++ b/src/test/regress/expected/rules.out
@@ -0,0 +1,3701 @@
+--
+-- 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ a | b
+---+----
+ 1 | 21
+ 2 | 22
+ 3 | 23
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+ 5 |
+(8 rows)
+
+select * from rtest_v1 where b isnull;
+ 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;
+ 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;
+ a | b
+---+----
+ 1 | 21
+ 3 | 23
+ 2 | 42
+(3 rows)
+
+update rtest_v1 set b = 99 where b = 42;
+select * from rtest_v1;
+ a | b
+---+----
+ 1 | 21
+ 3 | 23
+ 2 | 99
+(3 rows)
+
+update rtest_v1 set b = 88 where b < 50;
+select * from rtest_v1;
+ a | b
+---+----
+ 2 | 99
+ 1 | 88
+ 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;
+ 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;
+ a | b
+---+----
+ 1 | 21
+ 2 | 22
+ 3 | 23
+(3 rows)
+
+insert into rtest_v1 select * from rtest_t3;
+select * from rtest_v1;
+ a | b
+---+----
+ 1 | 21
+ 2 | 22
+ 3 | 23
+ 1 | 31
+ 2 | 32
+ 3 | 33
+ 4 | 34
+ 5 | 35
+(8 rows)
+
+update rtest_t1 set a = a + 10 where b > 30;
+select * from rtest_v1;
+ 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;
+ 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;
+ sysname | ifname
+---------+--------
+ orion | eth0
+ orion | eth1
+ notjw | eth0
+ pluto | eth0
+(4 rows)
+
+select * from rtest_admin;
+ pname | sysname
+-------+---------
+ jw | orion
+ jw | notjw
+ bm | pluto
+(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;
+ sysname | ifname
+---------+--------
+ notjw | eth0
+ pluto | eth0
+(2 rows)
+
+select * from rtest_admin;
+ 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;
+ 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;
+ 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
+----+-------------------------------------
+ 20 | 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;
+ 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;
+ a | b
+---+------------------------------
+ 1 | Record should go to rtest_t4
+ 2 | Record should go to rtest_t4
+(2 rows)
+
+select * from rtest_t5;
+ 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;
+ 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;
+ 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
+----+-------------------------------------
+ 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;
+ 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
+ 21 | Record should go to rtest_t4 and t6
+ 31 | Record should go to rtest_t4
+ 41 | Record should go to rtest_t4
+(7 rows)
+
+select * from rtest_t5;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ a | b
+---+--------
+ 2 | item 2
+ 4 | item 4
+ 5 | item 5
+ 7 | item 7
+(4 rows)
+
+select * from rtest_vview2;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ f1
+------
+ 1001
+ 100
+(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;
+ pid | txt | descrip
+-----+---------+----------
+ 1 | parent1 | descrip1
+ 2 | parent2 |
+(2 rows)
+
+update vview set descrip='test1' where pid=1;
+select * from vview;
+ pid | txt | descrip
+-----+---------+---------
+ 1 | parent1 | test1
+ 2 | parent2 |
+(2 rows)
+
+update vview set descrip='test2' where pid=2;
+select * from vview;
+ pid | txt | descrip
+-----+---------+---------
+ 1 | parent1 | test1
+ 2 | parent2 | test2
+(2 rows)
+
+update vview set descrip='test3' where pid=3;
+select * from vview;
+ pid | txt | descrip
+-----+---------+---------
+ 1 | parent1 | test1
+ 2 | parent2 | test2
+(2 rows)
+
+select * from cchild;
+ 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
+SELECT viewname, definition FROM pg_views
+WHERE schemaname IN ('pg_catalog', 'public')
+ORDER BY viewname;
+iexit| SELECT ih.name,
+ ih.thepath,
+ interpt_pp(ih.thepath, r.thepath) AS exit
+ FROM ihighway ih,
+ ramp r
+ WHERE (ih.thepath ## r.thepath);
+key_dependent_view| SELECT view_base_table.key,
+ view_base_table.data
+ FROM view_base_table
+ GROUP BY view_base_table.key;
+key_dependent_view_no_cols| SELECT
+ FROM view_base_table
+ GROUP BY view_base_table.key
+ HAVING (length((view_base_table.data)::text) > 0);
+mvtest_tv| SELECT mvtest_t.type,
+ sum(mvtest_t.amt) AS totamt
+ FROM mvtest_t
+ GROUP BY mvtest_t.type;
+mvtest_tvv| SELECT sum(mvtest_tv.totamt) AS grandtot
+ FROM mvtest_tv;
+mvtest_tvvmv| SELECT mvtest_tvvm.grandtot
+ FROM mvtest_tvvm;
+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_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,
+ l.page,
+ l.tuple,
+ l.virtualxid,
+ l.transactionid,
+ l.classid,
+ l.objid,
+ l.objsubid,
+ l.virtualtransaction,
+ l.pid,
+ l.mode,
+ l.granted,
+ l.fastpath,
+ l.waitstart
+ FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
+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
+ FROM pg_publication p,
+ LATERAL pg_get_publication_tables((p.pubname)::text) gpt(relid),
+ (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
+ 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)
+ 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
+ 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)
+ LEFT JOIN pg_database d ON ((s.datid = d.oid)))
+ LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
+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)
+ 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,
+ 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'::text
+ WHEN 2 THEN 'CREATE INDEX CONCURRENTLY'::text
+ WHEN 3 THEN 'REINDEX'::text
+ WHEN 4 THEN 'REINDEX CONCURRENTLY'::text
+ ELSE NULL::text
+ END AS command,
+ CASE s.param10
+ WHEN 0 THEN 'initializing'::text
+ WHEN 1 THEN 'waiting for writers before build'::text
+ WHEN 2 THEN ('building index'::text || COALESCE((': '::text || pg_indexam_progress_phasename((s.param9)::oid, s.param11)), ''::text))
+ WHEN 3 THEN 'waiting for writers before validation'::text
+ WHEN 4 THEN 'index validation: scanning index'::text
+ WHEN 5 THEN 'index validation: sorting tuples'::text
+ WHEN 6 THEN 'index validation: scanning table'::text
+ WHEN 7 THEN 'waiting for old snapshots'::text
+ WHEN 8 THEN 'waiting for readers before marking dead'::text
+ WHEN 9 THEN 'waiting for readers before dropping'::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_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)
+ 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)
+ WHERE (s.client_port IS NOT NULL);
+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_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,
+ (sum((pg_stat_get_blocks_fetched(i.indexrelid) - pg_stat_get_blocks_hit(i.indexrelid))))::bigint AS idx_blks_read,
+ (sum(pg_stat_get_blocks_hit(i.indexrelid)))::bigint AS 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,
+ (pg_stat_get_blocks_fetched(x.indexrelid) - pg_stat_get_blocks_hit(x.indexrelid)) AS tidx_blks_read,
+ pg_stat_get_blocks_hit(x.indexrelid) AS tidx_blks_hit
+ FROM ((((pg_class c
+ LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
+ LEFT JOIN pg_class t ON ((c.reltoastrelid = t.oid)))
+ LEFT JOIN pg_index x ON ((t.oid = x.indrelid)))
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]))
+ GROUP BY c.oid, n.nspname, c.relname, t.oid, x.indexrelid;
+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
+ 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.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 ((NOT (EXISTS ( SELECT 1
+ FROM (unnest(s.stxkeys) k(k)
+ JOIN pg_attribute a ON (((a.attrelid = s.stxrelid) AND (a.attnum = k.k))))
+ WHERE (NOT has_column_privilege(c.oid, a.attnum, 'select'::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,
+ (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)));
+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");
+rtest_v1| SELECT rtest_t1.a,
+ rtest_t1.b
+ FROM rtest_t1;
+rtest_vcomp| SELECT x.part,
+ (x.size * y.factor) AS size_in_cm
+ FROM rtest_comp x,
+ rtest_unitfact y
+ WHERE (x.unit = y.unit);
+rtest_vview1| SELECT x.a,
+ x.b
+ FROM rtest_view1 x
+ WHERE (0 < ( SELECT count(*) AS count
+ FROM rtest_view2 y
+ WHERE (y.a = x.a)));
+rtest_vview2| SELECT rtest_view1.a,
+ rtest_view1.b
+ FROM rtest_view1
+ WHERE rtest_view1.v;
+rtest_vview3| SELECT x.a,
+ x.b
+ FROM rtest_vview2 x
+ WHERE (0 < ( SELECT count(*) AS count
+ FROM rtest_view2 y
+ WHERE (y.a = x.a)));
+rtest_vview4| 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;
+rtest_vview5| SELECT rtest_view1.a,
+ rtest_view1.b,
+ rtest_viewfunc1(rtest_view1.a) AS refcount
+ FROM rtest_view1;
+shoe| 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);
+shoe_ready| 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));
+shoelace| 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);
+shoelace_candelete| SELECT shoelace_obsolete.sl_name,
+ shoelace_obsolete.sl_avail,
+ shoelace_obsolete.sl_color,
+ shoelace_obsolete.sl_len,
+ shoelace_obsolete.sl_unit,
+ shoelace_obsolete.sl_len_cm
+ FROM shoelace_obsolete
+ WHERE (shoelace_obsolete.sl_avail = 0);
+shoelace_obsolete| SELECT shoelace.sl_name,
+ shoelace.sl_avail,
+ shoelace.sl_color,
+ shoelace.sl_len,
+ shoelace.sl_unit,
+ shoelace.sl_len_cm
+ FROM shoelace
+ WHERE (NOT (EXISTS ( SELECT shoe.shoename
+ FROM shoe
+ WHERE (shoe.slcolor = shoelace.sl_color))));
+street| SELECT r.name,
+ r.thepath,
+ c.cname
+ FROM ONLY road r,
+ real_city c
+ WHERE (c.outline ## r.thepath);
+test_tablesample_v1| SELECT test_tablesample.id
+ FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
+test_tablesample_v2| SELECT test_tablesample.id
+ FROM test_tablesample TABLESAMPLE system (99);
+toyemp| SELECT emp.name,
+ emp.age,
+ emp.location,
+ (12 * emp.salary) AS annualsal
+ FROM emp;
+SELECT tablename, rulename, definition FROM pg_rules
+WHERE schemaname IN ('pg_catalog', 'public')
+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;
+rtest_emp|rtest_emp_del|CREATE RULE rtest_emp_del AS
+ ON DELETE TO public.rtest_emp DO INSERT INTO rtest_emplog (ename, who, action, newsal, oldsal)
+ VALUES (old.ename, CURRENT_USER, 'fired'::bpchar, '$0.00'::money, old.salary);
+rtest_emp|rtest_emp_ins|CREATE RULE rtest_emp_ins AS
+ ON INSERT TO public.rtest_emp DO INSERT INTO rtest_emplog (ename, who, action, newsal, oldsal)
+ VALUES (new.ename, CURRENT_USER, 'hired'::bpchar, new.salary, '$0.00'::money);
+rtest_emp|rtest_emp_upd|CREATE RULE rtest_emp_upd AS
+ ON UPDATE TO public.rtest_emp
+ WHERE (new.salary <> old.salary) DO INSERT INTO rtest_emplog (ename, who, action, newsal, oldsal)
+ VALUES (new.ename, CURRENT_USER, 'honored'::bpchar, new.salary, old.salary);
+rtest_nothn1|rtest_nothn_r1|CREATE RULE rtest_nothn_r1 AS
+ ON INSERT TO public.rtest_nothn1
+ WHERE ((new.a >= 10) AND (new.a < 20)) DO INSTEAD NOTHING;
+rtest_nothn1|rtest_nothn_r2|CREATE RULE rtest_nothn_r2 AS
+ ON INSERT TO public.rtest_nothn1
+ WHERE ((new.a >= 30) AND (new.a < 40)) DO INSTEAD NOTHING;
+rtest_nothn2|rtest_nothn_r3|CREATE RULE rtest_nothn_r3 AS
+ ON INSERT TO public.rtest_nothn2
+ WHERE (new.a >= 100) DO INSTEAD INSERT INTO rtest_nothn3 (a, b)
+ VALUES (new.a, new.b);
+rtest_nothn2|rtest_nothn_r4|CREATE RULE rtest_nothn_r4 AS
+ ON INSERT TO public.rtest_nothn2 DO INSTEAD NOTHING;
+rtest_order1|rtest_order_r1|CREATE RULE rtest_order_r1 AS
+ ON INSERT TO public.rtest_order1 DO INSTEAD INSERT INTO rtest_order2 (a, b, c)
+ VALUES (new.a, nextval('rtest_seq'::regclass), 'rule 1 - this should run 1st'::text);
+rtest_order1|rtest_order_r2|CREATE RULE rtest_order_r2 AS
+ ON INSERT TO public.rtest_order1 DO INSERT INTO rtest_order2 (a, b, c)
+ VALUES (new.a, nextval('rtest_seq'::regclass), 'rule 2 - this should run 2nd'::text);
+rtest_order1|rtest_order_r3|CREATE RULE rtest_order_r3 AS
+ ON INSERT TO public.rtest_order1 DO INSTEAD INSERT INTO rtest_order2 (a, b, c)
+ VALUES (new.a, nextval('rtest_seq'::regclass), 'rule 3 - this should run 3rd'::text);
+rtest_order1|rtest_order_r4|CREATE RULE rtest_order_r4 AS
+ ON INSERT TO public.rtest_order1
+ WHERE (new.a < 100) DO INSTEAD INSERT INTO rtest_order2 (a, b, c)
+ VALUES (new.a, nextval('rtest_seq'::regclass), 'rule 4 - this should run 4th'::text);
+rtest_person|rtest_pers_del|CREATE RULE rtest_pers_del AS
+ ON DELETE TO public.rtest_person DO DELETE FROM rtest_admin
+ WHERE (rtest_admin.pname = old.pname);
+rtest_person|rtest_pers_upd|CREATE RULE rtest_pers_upd AS
+ ON UPDATE TO public.rtest_person DO UPDATE rtest_admin SET pname = new.pname
+ WHERE (rtest_admin.pname = old.pname);
+rtest_system|rtest_sys_del|CREATE RULE rtest_sys_del AS
+ ON DELETE TO public.rtest_system DO ( DELETE FROM rtest_interface
+ WHERE (rtest_interface.sysname = old.sysname);
+ DELETE FROM rtest_admin
+ WHERE (rtest_admin.sysname = old.sysname);
+);
+rtest_system|rtest_sys_upd|CREATE RULE rtest_sys_upd AS
+ ON UPDATE TO public.rtest_system DO ( UPDATE rtest_interface SET sysname = new.sysname
+ WHERE (rtest_interface.sysname = old.sysname);
+ UPDATE rtest_admin SET sysname = new.sysname
+ WHERE (rtest_admin.sysname = old.sysname);
+);
+rtest_t4|rtest_t4_ins1|CREATE RULE rtest_t4_ins1 AS
+ ON INSERT TO public.rtest_t4
+ WHERE ((new.a >= 10) AND (new.a < 20)) DO INSTEAD INSERT INTO rtest_t5 (a, b)
+ VALUES (new.a, new.b);
+rtest_t4|rtest_t4_ins2|CREATE RULE rtest_t4_ins2 AS
+ ON INSERT TO public.rtest_t4
+ WHERE ((new.a >= 20) AND (new.a < 30)) DO INSERT INTO rtest_t6 (a, b)
+ VALUES (new.a, new.b);
+rtest_t5|rtest_t5_ins|CREATE RULE rtest_t5_ins AS
+ ON INSERT TO public.rtest_t5
+ WHERE (new.a > 15) DO INSERT INTO rtest_t7 (a, b)
+ VALUES (new.a, new.b);
+rtest_t6|rtest_t6_ins|CREATE RULE rtest_t6_ins AS
+ ON INSERT TO public.rtest_t6
+ WHERE (new.a > 25) DO INSTEAD INSERT INTO rtest_t8 (a, b)
+ VALUES (new.a, new.b);
+rtest_v1|rtest_v1_del|CREATE RULE rtest_v1_del AS
+ ON DELETE TO public.rtest_v1 DO INSTEAD DELETE FROM rtest_t1
+ WHERE (rtest_t1.a = old.a);
+rtest_v1|rtest_v1_ins|CREATE RULE rtest_v1_ins AS
+ ON INSERT TO public.rtest_v1 DO INSTEAD INSERT INTO rtest_t1 (a, b)
+ VALUES (new.a, new.b);
+rtest_v1|rtest_v1_upd|CREATE RULE rtest_v1_upd AS
+ ON UPDATE TO public.rtest_v1 DO INSTEAD UPDATE rtest_t1 SET a = new.a, b = new.b
+ WHERE (rtest_t1.a = old.a);
+shoelace|shoelace_del|CREATE RULE shoelace_del AS
+ ON DELETE TO public.shoelace DO INSTEAD DELETE FROM shoelace_data
+ WHERE (shoelace_data.sl_name = old.sl_name);
+shoelace|shoelace_ins|CREATE RULE shoelace_ins AS
+ ON INSERT TO public.shoelace DO INSTEAD INSERT INTO shoelace_data (sl_name, sl_avail, sl_color, sl_len, sl_unit)
+ VALUES (new.sl_name, new.sl_avail, new.sl_color, new.sl_len, new.sl_unit);
+shoelace|shoelace_upd|CREATE RULE shoelace_upd AS
+ ON UPDATE TO public.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 (shoelace_data.sl_name = old.sl_name);
+shoelace_data|log_shoelace|CREATE RULE log_shoelace AS
+ ON UPDATE TO public.shoelace_data
+ WHERE (new.sl_avail <> old.sl_avail) DO INSERT INTO shoelace_log (sl_name, sl_avail, log_who, log_when)
+ VALUES (new.sl_name, new.sl_avail, 'Al Bundy'::name, 'Thu Jan 01 00:00:00 1970'::timestamp without time zone);
+shoelace_ok|shoelace_ok_ins|CREATE RULE shoelace_ok_ins AS
+ ON INSERT TO public.shoelace_ok DO INSTEAD UPDATE shoelace SET sl_avail = (shoelace.sl_avail + new.ok_quant)
+ WHERE (shoelace.sl_name = new.ok_name);
+-- 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;
+ a | b
+------+------
+ 10 | 10
+ 1000 | 1000
+(2 rows)
+
+-- Check that rewrite rules splitting one INSERT into multiple
+-- conditional statements does not disable FK checking.
+create table rule_and_refint_t1 (
+ id1a integer,
+ id1b integer,
+ primary key (id1a, id1b)
+);
+create table rule_and_refint_t2 (
+ id2a integer,
+ id2c integer,
+ primary key (id2a, id2c)
+);
+create table rule_and_refint_t3 (
+ id3a integer,
+ id3b integer,
+ id3c integer,
+ data text,
+ primary key (id3a, id3b, id3c),
+ foreign key (id3a, id3b) references rule_and_refint_t1 (id1a, id1b),
+ foreign key (id3a, id3c) references rule_and_refint_t2 (id2a, id2c)
+);
+insert into rule_and_refint_t1 values (1, 11);
+insert into rule_and_refint_t1 values (1, 12);
+insert into rule_and_refint_t1 values (2, 21);
+insert into rule_and_refint_t1 values (2, 22);
+insert into rule_and_refint_t2 values (1, 11);
+insert into rule_and_refint_t2 values (1, 12);
+insert into rule_and_refint_t2 values (2, 21);
+insert into rule_and_refint_t2 values (2, 22);
+insert into rule_and_refint_t3 values (1, 11, 11, 'row1');
+insert into rule_and_refint_t3 values (1, 11, 12, 'row2');
+insert into rule_and_refint_t3 values (1, 12, 11, 'row3');
+insert into rule_and_refint_t3 values (1, 12, 12, 'row4');
+insert into rule_and_refint_t3 values (1, 11, 13, 'row5');
+ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_id3c_fkey"
+DETAIL: Key (id3a, id3c)=(1, 13) is not present in table "rule_and_refint_t2".
+insert into rule_and_refint_t3 values (1, 13, 11, 'row6');
+ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_id3b_fkey"
+DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
+-- Ordinary table
+insert into rule_and_refint_t3 values (1, 13, 11, 'row6')
+ on conflict do nothing;
+ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_id3b_fkey"
+DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
+-- rule not fired, so fk violation
+insert into rule_and_refint_t3 values (1, 13, 11, 'row6')
+ on conflict (id3a, id3b, id3c) do update
+ set id3b = excluded.id3b;
+ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_id3b_fkey"
+DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
+-- rule fired, so unsupported
+insert into shoelace values ('sl9', 0, 'pink', 35.0, 'inch', 0.0)
+ on conflict (sl_name) do update
+ set sl_avail = excluded.sl_avail;
+ERROR: INSERT with ON CONFLICT clause cannot be used with table that has INSERT or UPDATE rules
+create rule rule_and_refint_t3_ins as on insert to rule_and_refint_t3
+ where (exists (select 1 from rule_and_refint_t3
+ where (((rule_and_refint_t3.id3a = new.id3a)
+ and (rule_and_refint_t3.id3b = new.id3b))
+ and (rule_and_refint_t3.id3c = new.id3c))))
+ do instead update rule_and_refint_t3 set data = new.data
+ where (((rule_and_refint_t3.id3a = new.id3a)
+ and (rule_and_refint_t3.id3b = new.id3b))
+ and (rule_and_refint_t3.id3c = new.id3c));
+insert into rule_and_refint_t3 values (1, 11, 13, 'row7');
+ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_id3c_fkey"
+DETAIL: Key (id3a, id3c)=(1, 13) is not present in table "rule_and_refint_t2".
+insert into rule_and_refint_t3 values (1, 13, 11, 'row8');
+ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_id3b_fkey"
+DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
+--
+-- disallow dropping a view's rule (bug #5072)
+--
+create view rules_fooview as select 'rules_foo'::text;
+drop rule "_RETURN" on rules_fooview;
+ERROR: cannot drop rule _RETURN on view rules_fooview because view rules_fooview requires it
+HINT: You can drop view rules_fooview instead.
+drop view rules_fooview;
+--
+-- test conversion of table to view (needed to load some pg_dump files)
+--
+create table rules_fooview (x int, y text);
+select xmin, * from rules_fooview;
+ xmin | x | y
+------+---+---
+(0 rows)
+
+create rule "_RETURN" as on select to rules_fooview do instead
+ select 1 as x, 'aaa'::text as y;
+select * from rules_fooview;
+ x | y
+---+-----
+ 1 | aaa
+(1 row)
+
+select xmin, * from rules_fooview; -- fail, views don't have such a column
+ERROR: column "xmin" does not exist
+LINE 1: select xmin, * from rules_fooview;
+ ^
+select reltoastrelid, relkind, relfrozenxid
+ from pg_class where oid = 'rules_fooview'::regclass;
+ reltoastrelid | relkind | relfrozenxid
+---------------+---------+--------------
+ 0 | v | 0
+(1 row)
+
+drop view rules_fooview;
+-- cannot convert an inheritance parent or child to a view, though
+create table rules_fooview (x int, y text);
+create table rules_fooview_child () inherits (rules_fooview);
+create rule "_RETURN" as on select to rules_fooview do instead
+ select 1 as x, 'aaa'::text as y;
+ERROR: could not convert table "rules_fooview" to a view because it has child tables
+create rule "_RETURN" as on select to rules_fooview_child do instead
+ select 1 as x, 'aaa'::text as y;
+ERROR: could not convert table "rules_fooview_child" to a view because it has parent tables
+drop table rules_fooview cascade;
+NOTICE: drop cascades to table rules_fooview_child
+-- likewise, converting a partitioned table or partition to view is not allowed
+create table rules_fooview (x int, y text) partition by list (x);
+create rule "_RETURN" as on select to rules_fooview do instead
+ select 1 as x, 'aaa'::text as y;
+ERROR: cannot convert partitioned table "rules_fooview" to a view
+create table rules_fooview_part partition of rules_fooview for values in (1);
+create rule "_RETURN" as on select to rules_fooview_part do instead
+ select 1 as x, 'aaa'::text as y;
+ERROR: cannot convert partition "rules_fooview_part" to a view
+drop table rules_fooview;
+--
+-- check for planner problems with complex inherited UPDATES
+--
+create table id (id serial primary key, name text);
+-- currently, must respecify PKEY for each inherited subtable
+create table test_1 (id integer primary key) inherits (id);
+NOTICE: merging column "id" with inherited definition
+create table test_2 (id integer primary key) inherits (id);
+NOTICE: merging column "id" with inherited definition
+create table test_3 (id integer primary key) inherits (id);
+NOTICE: merging column "id" with inherited definition
+insert into test_1 (name) values ('Test 1');
+insert into test_1 (name) values ('Test 2');
+insert into test_2 (name) values ('Test 3');
+insert into test_2 (name) values ('Test 4');
+insert into test_3 (name) values ('Test 5');
+insert into test_3 (name) values ('Test 6');
+create view id_ordered as select * from id order by id;
+create rule update_id_ordered as on update to id_ordered
+ do instead update id set name = new.name where id = old.id;
+select * from id_ordered;
+ id | name
+----+--------
+ 1 | Test 1
+ 2 | Test 2
+ 3 | Test 3
+ 4 | Test 4
+ 5 | Test 5
+ 6 | Test 6
+(6 rows)
+
+update id_ordered set name = 'update 2' where id = 2;
+update id_ordered set name = 'update 4' where id = 4;
+update id_ordered set name = 'update 5' where id = 5;
+select * from id_ordered;
+ id | name
+----+----------
+ 1 | Test 1
+ 2 | update 2
+ 3 | Test 3
+ 4 | update 4
+ 5 | update 5
+ 6 | Test 6
+(6 rows)
+
+drop table id cascade;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table test_1
+drop cascades to table test_2
+drop cascades to table test_3
+drop cascades to view id_ordered
+--
+-- check corner case where an entirely-dummy subplan is created by
+-- constraint exclusion
+--
+create temp table t1 (a integer primary key);
+create temp table t1_1 (check (a >= 0 and a < 10)) inherits (t1);
+create temp table t1_2 (check (a >= 10 and a < 20)) inherits (t1);
+create rule t1_ins_1 as on insert to t1
+ where new.a >= 0 and new.a < 10
+ do instead
+ insert into t1_1 values (new.a);
+create rule t1_ins_2 as on insert to t1
+ where new.a >= 10 and new.a < 20
+ do instead
+ insert into t1_2 values (new.a);
+create rule t1_upd_1 as on update to t1
+ where old.a >= 0 and old.a < 10
+ do instead
+ update t1_1 set a = new.a where a = old.a;
+create rule t1_upd_2 as on update to t1
+ where old.a >= 10 and old.a < 20
+ do instead
+ update t1_2 set a = new.a where a = old.a;
+set constraint_exclusion = on;
+insert into t1 select * from generate_series(5,19,1) g;
+update t1 set a = 4 where a = 5;
+select * from only t1;
+ a
+---
+(0 rows)
+
+select * from only t1_1;
+ a
+---
+ 6
+ 7
+ 8
+ 9
+ 4
+(5 rows)
+
+select * from only t1_2;
+ a
+----
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+(10 rows)
+
+reset constraint_exclusion;
+-- 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 various flavors of pg_get_viewdef()
+select pg_get_viewdef('shoe'::regclass) as unpretty;
+ unpretty
+------------------------------------------------
+ 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);
+(1 row)
+
+select pg_get_viewdef('shoe'::regclass,true) as pretty;
+ pretty
+----------------------------------------------
+ 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;
+(1 row)
+
+select pg_get_viewdef('shoe'::regclass,0) as prettier;
+ prettier
+----------------------------------------------
+ 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;
+(1 row)
+
+--
+-- check multi-row VALUES in rules
+--
+create table rules_src(f1 int, f2 int);
+create table rules_log(f1 int, f2 int, tag text);
+insert into rules_src values(1,2), (11,12);
+create rule r1 as on update to rules_src do also
+ insert into rules_log values(old.*, 'old'), (new.*, 'new');
+update rules_src set f2 = f2 + 1;
+update rules_src set f2 = f2 * 10;
+select * from rules_src;
+ f1 | f2
+----+-----
+ 1 | 30
+ 11 | 130
+(2 rows)
+
+select * from rules_log;
+ f1 | f2 | tag
+----+-----+-----
+ 1 | 2 | old
+ 1 | 3 | new
+ 11 | 12 | old
+ 11 | 13 | new
+ 1 | 3 | old
+ 1 | 30 | new
+ 11 | 13 | old
+ 11 | 130 | new
+(8 rows)
+
+create rule r2 as on update to rules_src do also
+ values(old.*, 'old'), (new.*, 'new');
+update rules_src set f2 = f2 / 10;
+ column1 | column2 | column3
+---------+---------+---------
+ 1 | 30 | old
+ 1 | 3 | new
+ 11 | 130 | old
+ 11 | 13 | new
+(4 rows)
+
+select * from rules_src;
+ f1 | f2
+----+----
+ 1 | 3
+ 11 | 13
+(2 rows)
+
+select * from rules_log;
+ f1 | f2 | tag
+----+-----+-----
+ 1 | 2 | old
+ 1 | 3 | new
+ 11 | 12 | old
+ 11 | 13 | new
+ 1 | 3 | old
+ 1 | 30 | new
+ 11 | 13 | old
+ 11 | 130 | new
+ 1 | 30 | old
+ 1 | 3 | new
+ 11 | 130 | old
+ 11 | 13 | new
+(12 rows)
+
+create rule r3 as on delete to rules_src do notify rules_src_deletion;
+\d+ rules_src
+ Table "public.rules_src"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | | | plain | |
+ f2 | integer | | | | plain | |
+Rules:
+ r1 AS
+ ON UPDATE TO rules_src DO INSERT INTO rules_log (f1, f2, tag) VALUES (old.f1,old.f2,'old'::text), (new.f1,new.f2,'new'::text)
+ r2 AS
+ ON UPDATE TO rules_src DO VALUES (old.f1,old.f2,'old'::text), (new.f1,new.f2,'new'::text)
+ r3 AS
+ ON DELETE TO rules_src DO
+ NOTIFY rules_src_deletion
+
+--
+-- Ensure an aliased target relation for insert is correctly deparsed.
+--
+create rule r4 as on insert to rules_src do instead insert into rules_log AS trgt SELECT NEW.* RETURNING trgt.f1, trgt.f2;
+create rule r5 as on update to rules_src do instead UPDATE rules_log AS trgt SET tag = 'updated' WHERE trgt.f1 = new.f1;
+\d+ rules_src
+ Table "public.rules_src"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | | | plain | |
+ f2 | integer | | | | plain | |
+Rules:
+ r1 AS
+ ON UPDATE TO rules_src DO INSERT INTO rules_log (f1, f2, tag) VALUES (old.f1,old.f2,'old'::text), (new.f1,new.f2,'new'::text)
+ r2 AS
+ ON UPDATE TO rules_src DO VALUES (old.f1,old.f2,'old'::text), (new.f1,new.f2,'new'::text)
+ r3 AS
+ ON DELETE TO rules_src DO
+ NOTIFY rules_src_deletion
+ r4 AS
+ ON INSERT TO rules_src DO INSTEAD INSERT INTO rules_log AS trgt (f1, f2) SELECT new.f1,
+ new.f2
+ RETURNING trgt.f1,
+ trgt.f2
+ r5 AS
+ ON UPDATE TO rules_src DO INSTEAD UPDATE rules_log trgt SET tag = 'updated'::text
+ WHERE trgt.f1 = new.f1
+
+--
+-- Also check multiassignment deparsing.
+--
+create table rule_t1(f1 int, f2 int);
+create table rule_dest(f1 int, f2 int[], tag text);
+create rule rr as on update to rule_t1 do instead UPDATE rule_dest trgt
+ SET (f2[1], f1, tag) = (SELECT new.f2, new.f1, 'updated'::varchar)
+ WHERE trgt.f1 = new.f1 RETURNING new.*;
+\d+ rule_t1
+ Table "public.rule_t1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | | | plain | |
+ f2 | integer | | | | plain | |
+Rules:
+ rr AS
+ ON UPDATE TO rule_t1 DO INSTEAD UPDATE rule_dest trgt SET (f2[1], f1, tag) = ( SELECT new.f2,
+ new.f1,
+ 'updated'::character varying AS "varchar")
+ WHERE trgt.f1 = new.f1
+ RETURNING new.f1,
+ new.f2
+
+drop table rule_t1, rule_dest;
+--
+-- check alter rename rule
+--
+CREATE TABLE rule_t1 (a INT);
+CREATE VIEW rule_v1 AS SELECT * FROM rule_t1;
+CREATE RULE InsertRule AS
+ ON INSERT TO rule_v1
+ DO INSTEAD
+ INSERT INTO rule_t1 VALUES(new.a);
+ALTER RULE InsertRule ON rule_v1 RENAME to NewInsertRule;
+INSERT INTO rule_v1 VALUES(1);
+SELECT * FROM rule_v1;
+ a
+---
+ 1
+(1 row)
+
+\d+ rule_v1
+ View "public.rule_v1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+---------+-------------
+ a | integer | | | | plain |
+View definition:
+ SELECT rule_t1.a
+ FROM rule_t1;
+Rules:
+ newinsertrule AS
+ ON INSERT TO rule_v1 DO INSTEAD INSERT INTO rule_t1 (a)
+ VALUES (new.a)
+
+--
+-- error conditions for alter rename rule
+--
+ALTER RULE InsertRule ON rule_v1 RENAME TO NewInsertRule; -- doesn't exist
+ERROR: rule "insertrule" for relation "rule_v1" does not exist
+ALTER RULE NewInsertRule ON rule_v1 RENAME TO "_RETURN"; -- already exists
+ERROR: rule "_RETURN" for relation "rule_v1" already exists
+ALTER RULE "_RETURN" ON rule_v1 RENAME TO abc; -- ON SELECT rule cannot be renamed
+ERROR: renaming an ON SELECT rule is not allowed
+DROP VIEW rule_v1;
+DROP TABLE rule_t1;
+--
+-- check display of VALUES in view definitions
+--
+create view rule_v1 as values(1,2);
+\d+ rule_v1
+ View "public.rule_v1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+---------+---------+-----------+----------+---------+---------+-------------
+ column1 | integer | | | | plain |
+ column2 | integer | | | | plain |
+View definition:
+ VALUES (1,2);
+
+alter table rule_v1 rename column column2 to q2;
+\d+ rule_v1
+ View "public.rule_v1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+---------+---------+-----------+----------+---------+---------+-------------
+ column1 | integer | | | | plain |
+ q2 | integer | | | | plain |
+View definition:
+ SELECT "*VALUES*".column1,
+ "*VALUES*".column2 AS q2
+ FROM (VALUES (1,2)) "*VALUES*";
+
+drop view rule_v1;
+create view rule_v1(x) as values(1,2);
+\d+ rule_v1
+ View "public.rule_v1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+---------+---------+-----------+----------+---------+---------+-------------
+ x | integer | | | | plain |
+ column2 | integer | | | | plain |
+View definition:
+ SELECT "*VALUES*".column1 AS x,
+ "*VALUES*".column2
+ FROM (VALUES (1,2)) "*VALUES*";
+
+drop view rule_v1;
+create view rule_v1(x) as select * from (values(1,2)) v;
+\d+ rule_v1
+ View "public.rule_v1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+---------+---------+-----------+----------+---------+---------+-------------
+ x | integer | | | | plain |
+ column2 | integer | | | | plain |
+View definition:
+ SELECT v.column1 AS x,
+ v.column2
+ FROM ( VALUES (1,2)) v;
+
+drop view rule_v1;
+create view rule_v1(x) as select * from (values(1,2)) v(q,w);
+\d+ rule_v1
+ View "public.rule_v1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+---------+-------------
+ x | integer | | | | plain |
+ w | integer | | | | plain |
+View definition:
+ SELECT v.q AS x,
+ v.w
+ FROM ( VALUES (1,2)) v(q, w);
+
+drop view rule_v1;
+--
+-- Check DO INSTEAD rules with ON CONFLICT
+--
+CREATE TABLE hats (
+ hat_name char(10) primary key,
+ hat_color char(10) -- hat color
+);
+CREATE TABLE hat_data (
+ hat_name char(10),
+ hat_color char(10) -- hat color
+);
+create unique index hat_data_unique_idx
+ on hat_data (hat_name COLLATE "C" bpchar_pattern_ops);
+-- DO NOTHING with ON CONFLICT
+CREATE RULE hat_nosert AS ON INSERT TO hats
+ DO INSTEAD
+ INSERT INTO hat_data VALUES (
+ NEW.hat_name,
+ NEW.hat_color)
+ ON CONFLICT (hat_name COLLATE "C" bpchar_pattern_ops) WHERE hat_color = 'green'
+ DO NOTHING
+ RETURNING *;
+SELECT definition FROM pg_rules WHERE tablename = 'hats' ORDER BY rulename;
+ definition
+---------------------------------------------------------------------------------------------
+ CREATE RULE hat_nosert AS +
+ ON INSERT TO public.hats DO INSTEAD INSERT INTO hat_data (hat_name, hat_color) +
+ VALUES (new.hat_name, new.hat_color) ON CONFLICT(hat_name COLLATE "C" bpchar_pattern_ops)+
+ WHERE (hat_color = 'green'::bpchar) DO NOTHING +
+ RETURNING hat_data.hat_name, +
+ hat_data.hat_color;
+(1 row)
+
+-- Works (projects row)
+INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
+ hat_name | hat_color
+------------+------------
+ h7 | black
+(1 row)
+
+-- Works (does nothing)
+INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
+ hat_name | hat_color
+----------+-----------
+(0 rows)
+
+SELECT tablename, rulename, definition FROM pg_rules
+ WHERE tablename = 'hats';
+ tablename | rulename | definition
+-----------+------------+---------------------------------------------------------------------------------------------
+ hats | hat_nosert | CREATE RULE hat_nosert AS +
+ | | ON INSERT TO public.hats DO INSTEAD INSERT INTO hat_data (hat_name, hat_color) +
+ | | VALUES (new.hat_name, new.hat_color) ON CONFLICT(hat_name COLLATE "C" bpchar_pattern_ops)+
+ | | WHERE (hat_color = 'green'::bpchar) DO NOTHING +
+ | | RETURNING hat_data.hat_name, +
+ | | hat_data.hat_color;
+(1 row)
+
+DROP RULE hat_nosert ON hats;
+-- DO NOTHING without ON CONFLICT
+CREATE RULE hat_nosert_all AS ON INSERT TO hats
+ DO INSTEAD
+ INSERT INTO hat_data VALUES (
+ NEW.hat_name,
+ NEW.hat_color)
+ ON CONFLICT
+ DO NOTHING
+ RETURNING *;
+SELECT definition FROM pg_rules WHERE tablename = 'hats' ORDER BY rulename;
+ definition
+-------------------------------------------------------------------------------------
+ CREATE RULE hat_nosert_all AS +
+ ON INSERT TO public.hats DO INSTEAD INSERT INTO hat_data (hat_name, hat_color)+
+ VALUES (new.hat_name, new.hat_color) ON CONFLICT DO NOTHING +
+ RETURNING hat_data.hat_name, +
+ hat_data.hat_color;
+(1 row)
+
+DROP RULE hat_nosert_all ON hats;
+-- Works (does nothing)
+INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
+ hat_name | hat_color
+------------+------------
+ h7 | black
+(1 row)
+
+-- DO UPDATE with a WHERE clause
+CREATE RULE hat_upsert AS ON INSERT TO hats
+ DO INSTEAD
+ INSERT INTO hat_data VALUES (
+ NEW.hat_name,
+ NEW.hat_color)
+ ON CONFLICT (hat_name)
+ DO UPDATE
+ SET hat_name = hat_data.hat_name, hat_color = excluded.hat_color
+ WHERE excluded.hat_color <> 'forbidden' AND hat_data.* != excluded.*
+ RETURNING *;
+SELECT definition FROM pg_rules WHERE tablename = 'hats' ORDER BY rulename;
+ definition
+-----------------------------------------------------------------------------------------------------------------------------------------
+ CREATE RULE hat_upsert AS +
+ ON INSERT TO public.hats DO INSTEAD INSERT INTO hat_data (hat_name, hat_color) +
+ VALUES (new.hat_name, new.hat_color) ON CONFLICT(hat_name) DO UPDATE SET hat_name = hat_data.hat_name, hat_color = excluded.hat_color+
+ WHERE ((excluded.hat_color <> 'forbidden'::bpchar) AND (hat_data.* <> excluded.*)) +
+ RETURNING hat_data.hat_name, +
+ hat_data.hat_color;
+(1 row)
+
+-- Works (does upsert)
+INSERT INTO hats VALUES ('h8', 'black') RETURNING *;
+ hat_name | hat_color
+------------+------------
+ h8 | black
+(1 row)
+
+SELECT * FROM hat_data WHERE hat_name = 'h8';
+ hat_name | hat_color
+------------+------------
+ h8 | black
+(1 row)
+
+INSERT INTO hats VALUES ('h8', 'white') RETURNING *;
+ hat_name | hat_color
+------------+------------
+ h8 | white
+(1 row)
+
+SELECT * FROM hat_data WHERE hat_name = 'h8';
+ hat_name | hat_color
+------------+------------
+ h8 | white
+(1 row)
+
+INSERT INTO hats VALUES ('h8', 'forbidden') RETURNING *;
+ hat_name | hat_color
+----------+-----------
+(0 rows)
+
+SELECT * FROM hat_data WHERE hat_name = 'h8';
+ hat_name | hat_color
+------------+------------
+ h8 | white
+(1 row)
+
+SELECT tablename, rulename, definition FROM pg_rules
+ WHERE tablename = 'hats';
+ tablename | rulename | definition
+-----------+------------+-----------------------------------------------------------------------------------------------------------------------------------------
+ hats | hat_upsert | CREATE RULE hat_upsert AS +
+ | | ON INSERT TO public.hats DO INSTEAD INSERT INTO hat_data (hat_name, hat_color) +
+ | | VALUES (new.hat_name, new.hat_color) ON CONFLICT(hat_name) DO UPDATE SET hat_name = hat_data.hat_name, hat_color = excluded.hat_color+
+ | | WHERE ((excluded.hat_color <> 'forbidden'::bpchar) AND (hat_data.* <> excluded.*)) +
+ | | RETURNING hat_data.hat_name, +
+ | | hat_data.hat_color;
+(1 row)
+
+-- ensure explain works for on insert conflict rules
+explain (costs off) INSERT INTO hats VALUES ('h8', 'forbidden') RETURNING *;
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------
+ Insert on hat_data
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: hat_data_unique_idx
+ Conflict Filter: ((excluded.hat_color <> 'forbidden'::bpchar) AND (hat_data.* <> excluded.*))
+ -> Result
+(5 rows)
+
+-- ensure upserting into a rule, with a CTE (different offsets!) works
+WITH data(hat_name, hat_color) AS MATERIALIZED (
+ VALUES ('h8', 'green'),
+ ('h9', 'blue'),
+ ('h7', 'forbidden')
+)
+INSERT INTO hats
+ SELECT * FROM data
+RETURNING *;
+ hat_name | hat_color
+------------+------------
+ h8 | green
+ h9 | blue
+(2 rows)
+
+EXPLAIN (costs off)
+WITH data(hat_name, hat_color) AS MATERIALIZED (
+ VALUES ('h8', 'green'),
+ ('h9', 'blue'),
+ ('h7', 'forbidden')
+)
+INSERT INTO hats
+ SELECT * FROM data
+RETURNING *;
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------
+ Insert on hat_data
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: hat_data_unique_idx
+ Conflict Filter: ((excluded.hat_color <> 'forbidden'::bpchar) AND (hat_data.* <> excluded.*))
+ CTE data
+ -> Values Scan on "*VALUES*"
+ -> CTE Scan on data
+(7 rows)
+
+SELECT * FROM hat_data WHERE hat_name IN ('h8', 'h9', 'h7') ORDER BY hat_name;
+ hat_name | hat_color
+------------+------------
+ h7 | black
+ h8 | green
+ h9 | blue
+(3 rows)
+
+DROP RULE hat_upsert ON hats;
+drop table hats;
+drop table hat_data;
+-- test for pg_get_functiondef properly regurgitating SET parameters
+-- Note that the function is kept around to stress pg_dump.
+CREATE FUNCTION func_with_set_params() RETURNS integer
+ AS 'select 1;'
+ LANGUAGE SQL
+ SET search_path TO PG_CATALOG
+ SET extra_float_digits TO 2
+ SET work_mem TO '4MB'
+ SET datestyle to iso, mdy
+ SET local_preload_libraries TO "Mixed/Case", 'c:/''a"/path', '', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789'
+ IMMUTABLE STRICT;
+SELECT pg_get_functiondef('func_with_set_params()'::regprocedure);
+ pg_get_functiondef
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION public.func_with_set_params() +
+ RETURNS integer +
+ LANGUAGE sql +
+ IMMUTABLE STRICT +
+ SET search_path TO 'pg_catalog' +
+ SET extra_float_digits TO '2' +
+ SET work_mem TO '4MB' +
+ SET "DateStyle" TO 'iso, mdy' +
+ SET local_preload_libraries TO 'Mixed/Case', 'c:/''a"/path', '', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789'+
+ AS $function$select 1;$function$ +
+
+(1 row)
+
+-- tests for pg_get_*def with invalid objects
+SELECT pg_get_constraintdef(0);
+ pg_get_constraintdef
+----------------------
+
+(1 row)
+
+SELECT pg_get_functiondef(0);
+ pg_get_functiondef
+--------------------
+
+(1 row)
+
+SELECT pg_get_indexdef(0);
+ pg_get_indexdef
+-----------------
+
+(1 row)
+
+SELECT pg_get_ruledef(0);
+ pg_get_ruledef
+----------------
+
+(1 row)
+
+SELECT pg_get_statisticsobjdef(0);
+ pg_get_statisticsobjdef
+-------------------------
+
+(1 row)
+
+SELECT pg_get_triggerdef(0);
+ pg_get_triggerdef
+-------------------
+
+(1 row)
+
+SELECT pg_get_viewdef(0);
+ pg_get_viewdef
+----------------
+
+(1 row)
+
+SELECT pg_get_function_arguments(0);
+ pg_get_function_arguments
+---------------------------
+
+(1 row)
+
+SELECT pg_get_function_identity_arguments(0);
+ pg_get_function_identity_arguments
+------------------------------------
+
+(1 row)
+
+SELECT pg_get_function_result(0);
+ pg_get_function_result
+------------------------
+
+(1 row)
+
+SELECT pg_get_function_arg_default(0, 0);
+ pg_get_function_arg_default
+-----------------------------
+
+(1 row)
+
+SELECT pg_get_function_arg_default('pg_class'::regclass, 0);
+ pg_get_function_arg_default
+-----------------------------
+
+(1 row)
+
+SELECT pg_get_partkeydef(0);
+ pg_get_partkeydef
+-------------------
+
+(1 row)
+
+-- test rename for a rule defined on a partitioned table
+CREATE TABLE rules_parted_table (a int) PARTITION BY LIST (a);
+CREATE TABLE rules_parted_table_1 PARTITION OF rules_parted_table FOR VALUES IN (1);
+CREATE RULE rules_parted_table_insert AS ON INSERT to rules_parted_table
+ DO INSTEAD INSERT INTO rules_parted_table_1 VALUES (NEW.*);
+ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parted_table_insert_redirect;
+DROP TABLE rules_parted_table;
+--
+-- Test enabling/disabling
+--
+CREATE TABLE ruletest1 (a int);
+CREATE TABLE ruletest2 (b int);
+CREATE RULE rule1 AS ON INSERT TO ruletest1
+ DO INSTEAD INSERT INTO ruletest2 VALUES (NEW.*);
+INSERT INTO ruletest1 VALUES (1);
+ALTER TABLE ruletest1 DISABLE RULE rule1;
+INSERT INTO ruletest1 VALUES (2);
+ALTER TABLE ruletest1 ENABLE RULE rule1;
+SET session_replication_role = replica;
+INSERT INTO ruletest1 VALUES (3);
+ALTER TABLE ruletest1 ENABLE REPLICA RULE rule1;
+INSERT INTO ruletest1 VALUES (4);
+RESET session_replication_role;
+INSERT INTO ruletest1 VALUES (5);
+SELECT * FROM ruletest1;
+ a
+---
+ 2
+ 3
+ 5
+(3 rows)
+
+SELECT * FROM ruletest2;
+ b
+---
+ 1
+ 4
+(2 rows)
+
+DROP TABLE ruletest1;
+DROP TABLE ruletest2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
new file mode 100644
index 0000000..99e7bb4
--- /dev/null
+++ b/src/test/regress/expected/sanity_check.out
@@ -0,0 +1,248 @@
+VACUUM;
+--
+-- sanity check, if we don't have indices the test will take years to
+-- complete. But skip TOAST relations (since they will have varying
+-- names depending on the current OID counter) as well as temp tables
+-- of other backends (to avoid timing-dependent behavior).
+--
+-- temporarily disable fancy output, so catalog changes create less diff noise
+\a\t
+SELECT relname, relhasindex
+ FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace
+ WHERE relkind IN ('r', 'p') AND (nspname ~ '^pg_temp_') IS NOT TRUE
+ ORDER BY relname;
+a|f
+a_star|f
+aggtest|f
+array_index_op_test|t
+array_op_test|f
+b|f
+b_star|f
+bit_defaults|f
+box_tbl|f
+bprime|f
+bt_f8_heap|t
+bt_i4_heap|t
+bt_name_heap|t
+bt_txt_heap|t
+c|f
+c_star|f
+char_tbl|f
+check2_tbl|f
+check_tbl|f
+circle_tbl|t
+city|f
+copy_tbl|f
+d|f
+d_star|f
+date_tbl|f
+default_tbl|f
+defaultexpr_tbl|f
+dept|f
+dupindexcols|t
+e_star|f
+emp|f
+equipment_r|f
+extra_wide_table|f
+f_star|f
+fast_emp4000|t
+float4_tbl|f
+float8_tbl|f
+func_index_heap|t
+hash_f8_heap|t
+hash_i4_heap|t
+hash_name_heap|t
+hash_txt_heap|t
+hobbies_r|f
+ihighway|t
+inet_tbl|f
+insert_tbl|f
+int2_tbl|f
+int4_tbl|f
+int8_tbl|f
+interval_tbl|f
+invalid_check_con|f
+invalid_check_con_child|f
+iportaltest|f
+kd_point_tbl|t
+line_tbl|f
+log_table|f
+lseg_tbl|f
+main_table|f
+mlparted|f
+mlparted1|f
+mlparted11|f
+mlparted12|f
+mlparted2|f
+mlparted3|f
+mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
+money_data|f
+num_data|f
+num_exp_add|t
+num_exp_div|t
+num_exp_ln|t
+num_exp_log10|t
+num_exp_mul|t
+num_exp_power_10_ln|t
+num_exp_sqrt|t
+num_exp_sub|t
+num_input_test|f
+num_result|f
+nummultirange_test|t
+numrange_test|t
+onek|t
+onek2|t
+path_tbl|f
+person|f
+persons|f
+persons2|t
+persons3|t
+pg_aggregate|t
+pg_am|t
+pg_amop|t
+pg_amproc|t
+pg_attrdef|t
+pg_attribute|t
+pg_auth_members|t
+pg_authid|t
+pg_cast|t
+pg_class|t
+pg_collation|t
+pg_constraint|t
+pg_conversion|t
+pg_database|t
+pg_db_role_setting|t
+pg_default_acl|t
+pg_depend|t
+pg_description|t
+pg_enum|t
+pg_event_trigger|t
+pg_extension|t
+pg_foreign_data_wrapper|t
+pg_foreign_server|t
+pg_foreign_table|t
+pg_index|t
+pg_inherits|t
+pg_init_privs|t
+pg_language|t
+pg_largeobject|t
+pg_largeobject_metadata|t
+pg_namespace|t
+pg_opclass|t
+pg_operator|t
+pg_opfamily|t
+pg_partitioned_table|t
+pg_policy|t
+pg_proc|t
+pg_publication|t
+pg_publication_rel|t
+pg_range|t
+pg_replication_origin|t
+pg_rewrite|t
+pg_seclabel|t
+pg_sequence|t
+pg_shdepend|t
+pg_shdescription|t
+pg_shseclabel|t
+pg_statistic|t
+pg_statistic_ext|t
+pg_statistic_ext_data|t
+pg_subscription|t
+pg_subscription_rel|t
+pg_tablespace|t
+pg_transform|t
+pg_trigger|t
+pg_ts_config|t
+pg_ts_config_map|t
+pg_ts_dict|t
+pg_ts_parser|t
+pg_ts_template|t
+pg_type|t
+pg_user_mapping|t
+point_tbl|t
+polygon_tbl|t
+quad_box_tbl|t
+quad_box_tbl_ord_seq1|f
+quad_box_tbl_ord_seq2|f
+quad_point_tbl|t
+quad_poly_tbl|t
+radix_text_tbl|t
+ramp|f
+real_city|f
+reservations|f
+road|t
+shighway|t
+slow_emp4000|f
+sql_features|f
+sql_implementation_info|f
+sql_parts|f
+sql_sizing|f
+stud_emp|f
+student|f
+tab_core_types|f
+tableam_parted_a_heap2|f
+tableam_parted_b_heap2|f
+tableam_parted_c_heap2|f
+tableam_parted_d_heap2|f
+tableam_parted_heap2|f
+tableam_tbl_heap2|f
+tableam_tblas_heap2|f
+tbl_include_box|t
+tbl_include_box_pk|f
+tbl_include_pk|t
+tbl_include_reg|t
+tbl_include_unique1|t
+tbl_include_unique2|f
+tenk1|t
+tenk2|t
+test_range_excl|t
+test_range_gist|t
+test_range_spgist|t
+test_tsvector|f
+testjsonb|f
+text_tbl|f
+textrange_test|t
+time_tbl|f
+timestamp_tbl|f
+timestamptz_tbl|f
+timetz_tbl|f
+tmp|f
+trigger_parted|t
+trigger_parted_p1|t
+trigger_parted_p1_1|t
+trigger_parted_p2|t
+trigger_parted_p2_2|t
+varchar_tbl|f
+view_base_table|t
+-- restore normal output mode
+\a\t
+--
+-- another sanity check: every system catalog that has OIDs should have
+-- a unique index on OID. This ensures that the OIDs will be unique,
+-- even after the OID counter wraps around.
+-- We exclude non-system tables from the check by looking at nspname.
+--
+SELECT relname, nspname
+ FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace JOIN pg_attribute a ON (attrelid = c.oid AND attname = 'oid')
+ WHERE relkind = 'r' and c.oid < 16384
+ AND ((nspname ~ '^pg_') IS NOT FALSE)
+ AND NOT EXISTS (SELECT 1 FROM pg_index i WHERE indrelid = c.oid
+ AND indkey[0] = a.attnum AND indnatts = 1
+ AND indisunique AND indimmediate);
+ relname | nspname
+---------+---------
+(0 rows)
+
+-- check that relations without storage don't have relfilenode
+SELECT relname, relkind
+ FROM pg_class
+ WHERE relkind IN ('v', 'c', 'f', 'p', 'I')
+ AND relfilenode <> 0;
+ relname | relkind
+---------+---------
+(0 rows)
+
diff --git a/src/test/regress/expected/security_label.out b/src/test/regress/expected/security_label.out
new file mode 100644
index 0000000..a8e01a6
--- /dev/null
+++ b/src/test/regress/expected/security_label.out
@@ -0,0 +1,44 @@
+--
+-- Test for facilities of security label
+--
+-- initial setups
+SET client_min_messages TO 'warning';
+DROP ROLE IF EXISTS regress_seclabel_user1;
+DROP ROLE IF EXISTS regress_seclabel_user2;
+RESET client_min_messages;
+CREATE USER regress_seclabel_user1 WITH CREATEROLE;
+CREATE USER regress_seclabel_user2;
+CREATE TABLE seclabel_tbl1 (a int, b text);
+CREATE TABLE seclabel_tbl2 (x int, y text);
+CREATE VIEW seclabel_view1 AS SELECT * FROM seclabel_tbl2;
+CREATE FUNCTION seclabel_four() RETURNS integer AS $$SELECT 4$$ language sql;
+CREATE DOMAIN seclabel_domain AS text;
+ALTER TABLE seclabel_tbl1 OWNER TO regress_seclabel_user1;
+ALTER TABLE seclabel_tbl2 OWNER TO regress_seclabel_user2;
+--
+-- Test of SECURITY LABEL statement without a plugin
+--
+SECURITY LABEL ON TABLE seclabel_tbl1 IS 'classified'; -- fail
+ERROR: no security label providers have been loaded
+SECURITY LABEL FOR 'dummy' ON TABLE seclabel_tbl1 IS 'classified'; -- fail
+ERROR: security label provider "dummy" is not loaded
+SECURITY LABEL ON TABLE seclabel_tbl1 IS '...invalid label...'; -- fail
+ERROR: no security label providers have been loaded
+SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified'; -- fail
+ERROR: no security label providers have been loaded
+SECURITY LABEL ON ROLE regress_seclabel_user1 IS 'classified'; -- fail
+ERROR: no security label providers have been loaded
+SECURITY LABEL FOR 'dummy' ON ROLE regress_seclabel_user1 IS 'classified'; -- fail
+ERROR: security label provider "dummy" is not loaded
+SECURITY LABEL ON ROLE regress_seclabel_user1 IS '...invalid label...'; -- fail
+ERROR: no security label providers have been loaded
+SECURITY LABEL ON ROLE regress_seclabel_user3 IS 'unclassified'; -- fail
+ERROR: no security label providers have been loaded
+-- clean up objects
+DROP FUNCTION seclabel_four();
+DROP DOMAIN seclabel_domain;
+DROP VIEW seclabel_view1;
+DROP TABLE seclabel_tbl1;
+DROP TABLE seclabel_tbl2;
+DROP USER regress_seclabel_user1;
+DROP USER regress_seclabel_user2;
diff --git a/src/test/regress/expected/select.out b/src/test/regress/expected/select.out
new file mode 100644
index 0000000..6dcd4fa
--- /dev/null
+++ b/src/test/regress/expected/select.out
@@ -0,0 +1,973 @@
+--
+-- SELECT
+--
+-- 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;
+SELECT two, stringu1, ten, string4
+ INTO TABLE tmp
+ FROM onek;
+--
+-- 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;
+ 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
+ sharon | 25
+ sam | 30
+ bill | 20
+ fred | 28
+ larry | 60
+ jeff | 23
+ cim | 30
+ linda | 19
+(58 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;
+ 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
+ larry | 60
+ 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
+ cim | 30
+ sam | 30
+ fred | 28
+ jean | 28
+ melissa | 28
+ nan | 28
+ lita | 25
+ sharon | 25
+ denise | 24
+ jeff | 23
+ bill | 20
+ joe | 20
+ linda | 19
+ sandra | 19
+ diane | 18
+ edna | 18
+ gina | 18
+ joan | 18
+ pat | 18
+ fanny | 8
+ mary | 8
+(58 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)
+ 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)
+ 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)
+ 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)
+ 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
+ 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
+-------------------------------------------------------------
+ Bitmap Heap Scan on onek2
+ Recheck Cond: ((unique2 = 11) AND (stringu1 < 'B'::name))
+ -> Bitmap Index Scan on onek2_u2_prtl
+ Index Cond: (unique2 = 11)
+(4 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
+--------------------------------------------------------------------------------
+ Bitmap Heap Scan on onek2
+ Recheck Cond: (((unique2 = 11) AND (stringu1 < 'B'::name)) OR (unique1 = 0))
+ 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)
+(8 rows)
+
+select unique1, unique2 from onek2
+ where (unique2 = 11 or unique1 = 0) and stringu1 < 'B';
+ unique1 | unique2
+---------+---------
+ 494 | 11
+ 0 | 998
+(2 rows)
+
+explain (costs off)
+select unique1, unique2 from onek2
+ where (unique2 = 11 and stringu1 < 'B') or unique1 = 0;
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ Bitmap Heap Scan on onek2
+ Recheck Cond: (((unique2 = 11) AND (stringu1 < 'B'::name)) OR (unique1 = 0))
+ -> 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 and stringu1 < 'B') or unique1 = 0;
+ unique1 | unique2
+---------+---------
+ 494 | 11
+ 0 | 998
+(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
+ One-Time Filter: false
+(2 rows)
+
+drop table list_parted_tbl;
diff --git a/src/test/regress/expected/select_distinct.out b/src/test/regress/expected/select_distinct.out
new file mode 100644
index 0000000..11c6f50
--- /dev/null
+++ b/src/test/regress/expected/select_distinct.out
@@ -0,0 +1,308 @@
+--
+-- SELECT_DISTINCT
+--
+--
+-- awk '{print $3;}' onek.data | sort -n | uniq
+--
+SELECT DISTINCT two FROM tmp ORDER BY 1;
+ two
+-----
+ 0
+ 1
+(2 rows)
+
+--
+-- awk '{print $5;}' onek.data | sort -n | uniq
+--
+SELECT DISTINCT ten FROM tmp 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 tmp 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 tmp
+ 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
+--
+SELECT DISTINCT p.age FROM person* p ORDER BY age using >;
+ age
+-----
+ 98
+ 88
+ 78
+ 68
+ 60
+ 58
+ 50
+ 48
+ 40
+ 38
+ 34
+ 30
+ 28
+ 25
+ 24
+ 23
+ 20
+ 19
+ 18
+ 8
+(20 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)
+
+--
+-- 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';
+-- Produce results with sorting.
+SET enable_hashagg=FALSE;
+SET jit_above_cost=0;
+EXPLAIN (costs off)
+SELECT DISTINCT g%1000 FROM generate_series(0,9999) g;
+ QUERY PLAN
+------------------------------------------------
+ Unique
+ -> Sort
+ Sort Key: ((g % 1000))
+ -> Function Scan on generate_series g
+(4 rows)
+
+CREATE TABLE distinct_group_1 AS
+SELECT DISTINCT g%1000 FROM generate_series(0,9999) g;
+SET jit_above_cost TO DEFAULT;
+CREATE TABLE distinct_group_2 AS
+SELECT DISTINCT (g%1000)::text FROM generate_series(0,9999) g;
+SET enable_hashagg=TRUE;
+-- Produce results with hash aggregation.
+SET enable_sort=FALSE;
+SET jit_above_cost=0;
+EXPLAIN (costs off)
+SELECT DISTINCT g%1000 FROM generate_series(0,9999) g;
+ QUERY PLAN
+------------------------------------------
+ HashAggregate
+ Group Key: (g % 1000)
+ -> Function Scan on generate_series g
+(3 rows)
+
+CREATE TABLE distinct_hash_1 AS
+SELECT DISTINCT g%1000 FROM generate_series(0,9999) g;
+SET jit_above_cost TO DEFAULT;
+CREATE TABLE distinct_hash_2 AS
+SELECT DISTINCT (g%1000)::text FROM generate_series(0,9999) g;
+SET enable_sort=TRUE;
+SET work_mem TO DEFAULT;
+-- Compare results
+(SELECT * FROM distinct_hash_1 EXCEPT SELECT * FROM distinct_group_1)
+ UNION ALL
+(SELECT * FROM distinct_group_1 EXCEPT SELECT * FROM distinct_hash_1);
+ ?column?
+----------
+(0 rows)
+
+(SELECT * FROM distinct_hash_1 EXCEPT SELECT * FROM distinct_group_1)
+ UNION ALL
+(SELECT * FROM distinct_group_1 EXCEPT SELECT * FROM distinct_hash_1);
+ ?column?
+----------
+(0 rows)
+
+DROP TABLE distinct_hash_1;
+DROP TABLE distinct_hash_2;
+DROP TABLE distinct_group_1;
+DROP TABLE distinct_group_2;
+--
+-- 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/select_distinct_on.out b/src/test/regress/expected/select_distinct_on.out
new file mode 100644
index 0000000..b787b30
--- /dev/null
+++ b/src/test/regress/expected/select_distinct_on.out
@@ -0,0 +1,75 @@
+--
+-- SELECT_DISTINCT_ON
+--
+SELECT DISTINCT ON (string4) string4, two, ten
+ FROM tmp
+ ORDER BY string4 using <, two using >, ten using <;
+ string4 | two | ten
+---------+-----+-----
+ AAAAxx | 1 | 1
+ HHHHxx | 1 | 1
+ OOOOxx | 1 | 1
+ VVVVxx | 1 | 1
+(4 rows)
+
+-- this will fail due to conflict of ordering requirements
+SELECT DISTINCT ON (string4, ten) string4, two, ten
+ FROM tmp
+ ORDER BY string4 using <, two using <, ten using <;
+ERROR: SELECT DISTINCT ON expressions must match initial ORDER BY expressions
+LINE 1: SELECT DISTINCT ON (string4, ten) string4, two, ten
+ ^
+SELECT DISTINCT ON (string4, ten) string4, ten, two
+ FROM tmp
+ ORDER BY string4 using <, ten using >, two using <;
+ string4 | ten | two
+---------+-----+-----
+ AAAAxx | 9 | 1
+ AAAAxx | 8 | 0
+ AAAAxx | 7 | 1
+ AAAAxx | 6 | 0
+ AAAAxx | 5 | 1
+ AAAAxx | 4 | 0
+ AAAAxx | 3 | 1
+ AAAAxx | 2 | 0
+ AAAAxx | 1 | 1
+ AAAAxx | 0 | 0
+ HHHHxx | 9 | 1
+ HHHHxx | 8 | 0
+ HHHHxx | 7 | 1
+ HHHHxx | 6 | 0
+ HHHHxx | 5 | 1
+ HHHHxx | 4 | 0
+ HHHHxx | 3 | 1
+ HHHHxx | 2 | 0
+ HHHHxx | 1 | 1
+ HHHHxx | 0 | 0
+ OOOOxx | 9 | 1
+ OOOOxx | 8 | 0
+ OOOOxx | 7 | 1
+ OOOOxx | 6 | 0
+ OOOOxx | 5 | 1
+ OOOOxx | 4 | 0
+ OOOOxx | 3 | 1
+ OOOOxx | 2 | 0
+ OOOOxx | 1 | 1
+ OOOOxx | 0 | 0
+ VVVVxx | 9 | 1
+ VVVVxx | 8 | 0
+ VVVVxx | 7 | 1
+ VVVVxx | 6 | 0
+ VVVVxx | 5 | 1
+ VVVVxx | 4 | 0
+ VVVVxx | 3 | 1
+ VVVVxx | 2 | 0
+ VVVVxx | 1 | 1
+ VVVVxx | 0 | 0
+(40 rows)
+
+-- bug #5049: early 8.4.x chokes on volatile DISTINCT ON clauses
+select distinct on (1) floor(random()) as r, f1 from int4_tbl order by 1,2;
+ r | f1
+---+-------------
+ 0 | -2147483647
+(1 row)
+
diff --git a/src/test/regress/expected/select_having.out b/src/test/regress/expected/select_having.out
new file mode 100644
index 0000000..3950c0b
--- /dev/null
+++ b/src/test/regress/expected/select_having.out
@@ -0,0 +1,93 @@
+--
+-- SELECT_HAVING
+--
+-- load test data
+CREATE TABLE test_having (a int, b int, c char(8), d char);
+INSERT INTO test_having VALUES (0, 1, 'XXXX', 'A');
+INSERT INTO test_having VALUES (1, 2, 'AAAA', 'b');
+INSERT INTO test_having VALUES (2, 2, 'AAAA', 'c');
+INSERT INTO test_having VALUES (3, 3, 'BBBB', 'D');
+INSERT INTO test_having VALUES (4, 3, 'BBBB', 'e');
+INSERT INTO test_having VALUES (5, 3, 'bbbb', 'F');
+INSERT INTO test_having VALUES (6, 4, 'cccc', 'g');
+INSERT INTO test_having VALUES (7, 4, 'cccc', 'h');
+INSERT INTO test_having VALUES (8, 4, 'CCCC', 'I');
+INSERT INTO test_having VALUES (9, 4, 'CCCC', 'j');
+SELECT b, c FROM test_having
+ GROUP BY b, c HAVING count(*) = 1 ORDER BY b, c;
+ b | c
+---+----------
+ 1 | XXXX
+ 3 | bbbb
+(2 rows)
+
+-- HAVING is effectively equivalent to WHERE in this case
+SELECT b, c FROM test_having
+ GROUP BY b, c HAVING b = 3 ORDER BY b, c;
+ b | c
+---+----------
+ 3 | BBBB
+ 3 | bbbb
+(2 rows)
+
+SELECT lower(c), count(c) FROM test_having
+ GROUP BY lower(c) HAVING count(*) > 2 OR min(a) = max(a)
+ ORDER BY lower(c);
+ lower | count
+-------+-------
+ bbbb | 3
+ cccc | 4
+ xxxx | 1
+(3 rows)
+
+SELECT c, max(a) FROM test_having
+ GROUP BY c HAVING count(*) > 2 OR min(a) = max(a)
+ ORDER BY c;
+ c | max
+----------+-----
+ XXXX | 0
+ bbbb | 5
+(2 rows)
+
+-- test degenerate cases involving HAVING without GROUP BY
+-- Per SQL spec, these should generate 0 or 1 row, even without aggregates
+SELECT min(a), max(a) FROM test_having HAVING min(a) = max(a);
+ min | max
+-----+-----
+(0 rows)
+
+SELECT min(a), max(a) FROM test_having HAVING min(a) < max(a);
+ min | max
+-----+-----
+ 0 | 9
+(1 row)
+
+-- errors: ungrouped column references
+SELECT a FROM test_having HAVING min(a) < max(a);
+ERROR: column "test_having.a" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT a FROM test_having HAVING min(a) < max(a);
+ ^
+SELECT 1 AS one FROM test_having HAVING a > 1;
+ERROR: column "test_having.a" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT 1 AS one FROM test_having HAVING a > 1;
+ ^
+-- the really degenerate case: need not scan table at all
+SELECT 1 AS one FROM test_having HAVING 1 > 2;
+ one
+-----
+(0 rows)
+
+SELECT 1 AS one FROM test_having HAVING 1 < 2;
+ one
+-----
+ 1
+(1 row)
+
+-- and just to prove that we aren't scanning the table:
+SELECT 1 AS one FROM test_having WHERE 1/a = 1 HAVING 1 < 2;
+ one
+-----
+ 1
+(1 row)
+
+DROP TABLE test_having;
diff --git a/src/test/regress/expected/select_having_1.out b/src/test/regress/expected/select_having_1.out
new file mode 100644
index 0000000..5c58da1
--- /dev/null
+++ b/src/test/regress/expected/select_having_1.out
@@ -0,0 +1,93 @@
+--
+-- SELECT_HAVING
+--
+-- load test data
+CREATE TABLE test_having (a int, b int, c char(8), d char);
+INSERT INTO test_having VALUES (0, 1, 'XXXX', 'A');
+INSERT INTO test_having VALUES (1, 2, 'AAAA', 'b');
+INSERT INTO test_having VALUES (2, 2, 'AAAA', 'c');
+INSERT INTO test_having VALUES (3, 3, 'BBBB', 'D');
+INSERT INTO test_having VALUES (4, 3, 'BBBB', 'e');
+INSERT INTO test_having VALUES (5, 3, 'bbbb', 'F');
+INSERT INTO test_having VALUES (6, 4, 'cccc', 'g');
+INSERT INTO test_having VALUES (7, 4, 'cccc', 'h');
+INSERT INTO test_having VALUES (8, 4, 'CCCC', 'I');
+INSERT INTO test_having VALUES (9, 4, 'CCCC', 'j');
+SELECT b, c FROM test_having
+ GROUP BY b, c HAVING count(*) = 1 ORDER BY b, c;
+ b | c
+---+----------
+ 1 | XXXX
+ 3 | bbbb
+(2 rows)
+
+-- HAVING is effectively equivalent to WHERE in this case
+SELECT b, c FROM test_having
+ GROUP BY b, c HAVING b = 3 ORDER BY b, c;
+ b | c
+---+----------
+ 3 | BBBB
+ 3 | bbbb
+(2 rows)
+
+SELECT lower(c), count(c) FROM test_having
+ GROUP BY lower(c) HAVING count(*) > 2 OR min(a) = max(a)
+ ORDER BY lower(c);
+ lower | count
+-------+-------
+ bbbb | 3
+ cccc | 4
+ xxxx | 1
+(3 rows)
+
+SELECT c, max(a) FROM test_having
+ GROUP BY c HAVING count(*) > 2 OR min(a) = max(a)
+ ORDER BY c;
+ c | max
+----------+-----
+ bbbb | 5
+ XXXX | 0
+(2 rows)
+
+-- test degenerate cases involving HAVING without GROUP BY
+-- Per SQL spec, these should generate 0 or 1 row, even without aggregates
+SELECT min(a), max(a) FROM test_having HAVING min(a) = max(a);
+ min | max
+-----+-----
+(0 rows)
+
+SELECT min(a), max(a) FROM test_having HAVING min(a) < max(a);
+ min | max
+-----+-----
+ 0 | 9
+(1 row)
+
+-- errors: ungrouped column references
+SELECT a FROM test_having HAVING min(a) < max(a);
+ERROR: column "test_having.a" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT a FROM test_having HAVING min(a) < max(a);
+ ^
+SELECT 1 AS one FROM test_having HAVING a > 1;
+ERROR: column "test_having.a" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT 1 AS one FROM test_having HAVING a > 1;
+ ^
+-- the really degenerate case: need not scan table at all
+SELECT 1 AS one FROM test_having HAVING 1 > 2;
+ one
+-----
+(0 rows)
+
+SELECT 1 AS one FROM test_having HAVING 1 < 2;
+ one
+-----
+ 1
+(1 row)
+
+-- and just to prove that we aren't scanning the table:
+SELECT 1 AS one FROM test_having WHERE 1/a = 1 HAVING 1 < 2;
+ one
+-----
+ 1
+(1 row)
+
+DROP TABLE test_having;
diff --git a/src/test/regress/expected/select_having_2.out b/src/test/regress/expected/select_having_2.out
new file mode 100644
index 0000000..7087fb1
--- /dev/null
+++ b/src/test/regress/expected/select_having_2.out
@@ -0,0 +1,93 @@
+--
+-- SELECT_HAVING
+--
+-- load test data
+CREATE TABLE test_having (a int, b int, c char(8), d char);
+INSERT INTO test_having VALUES (0, 1, 'XXXX', 'A');
+INSERT INTO test_having VALUES (1, 2, 'AAAA', 'b');
+INSERT INTO test_having VALUES (2, 2, 'AAAA', 'c');
+INSERT INTO test_having VALUES (3, 3, 'BBBB', 'D');
+INSERT INTO test_having VALUES (4, 3, 'BBBB', 'e');
+INSERT INTO test_having VALUES (5, 3, 'bbbb', 'F');
+INSERT INTO test_having VALUES (6, 4, 'cccc', 'g');
+INSERT INTO test_having VALUES (7, 4, 'cccc', 'h');
+INSERT INTO test_having VALUES (8, 4, 'CCCC', 'I');
+INSERT INTO test_having VALUES (9, 4, 'CCCC', 'j');
+SELECT b, c FROM test_having
+ GROUP BY b, c HAVING count(*) = 1 ORDER BY b, c;
+ b | c
+---+----------
+ 1 | XXXX
+ 3 | bbbb
+(2 rows)
+
+-- HAVING is effectively equivalent to WHERE in this case
+SELECT b, c FROM test_having
+ GROUP BY b, c HAVING b = 3 ORDER BY b, c;
+ b | c
+---+----------
+ 3 | bbbb
+ 3 | BBBB
+(2 rows)
+
+SELECT lower(c), count(c) FROM test_having
+ GROUP BY lower(c) HAVING count(*) > 2 OR min(a) = max(a)
+ ORDER BY lower(c);
+ lower | count
+-------+-------
+ bbbb | 3
+ cccc | 4
+ xxxx | 1
+(3 rows)
+
+SELECT c, max(a) FROM test_having
+ GROUP BY c HAVING count(*) > 2 OR min(a) = max(a)
+ ORDER BY c;
+ c | max
+----------+-----
+ bbbb | 5
+ XXXX | 0
+(2 rows)
+
+-- test degenerate cases involving HAVING without GROUP BY
+-- Per SQL spec, these should generate 0 or 1 row, even without aggregates
+SELECT min(a), max(a) FROM test_having HAVING min(a) = max(a);
+ min | max
+-----+-----
+(0 rows)
+
+SELECT min(a), max(a) FROM test_having HAVING min(a) < max(a);
+ min | max
+-----+-----
+ 0 | 9
+(1 row)
+
+-- errors: ungrouped column references
+SELECT a FROM test_having HAVING min(a) < max(a);
+ERROR: column "test_having.a" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT a FROM test_having HAVING min(a) < max(a);
+ ^
+SELECT 1 AS one FROM test_having HAVING a > 1;
+ERROR: column "test_having.a" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: SELECT 1 AS one FROM test_having HAVING a > 1;
+ ^
+-- the really degenerate case: need not scan table at all
+SELECT 1 AS one FROM test_having HAVING 1 > 2;
+ one
+-----
+(0 rows)
+
+SELECT 1 AS one FROM test_having HAVING 1 < 2;
+ one
+-----
+ 1
+(1 row)
+
+-- and just to prove that we aren't scanning the table:
+SELECT 1 AS one FROM test_having WHERE 1/a = 1 HAVING 1 < 2;
+ one
+-----
+ 1
+(1 row)
+
+DROP TABLE test_having;
diff --git a/src/test/regress/expected/select_implicit.out b/src/test/regress/expected/select_implicit.out
new file mode 100644
index 0000000..27c07de
--- /dev/null
+++ b/src/test/regress/expected/select_implicit.out
@@ -0,0 +1,338 @@
+--
+-- SELECT_IMPLICIT
+-- Test cases for queries with ordering terms missing from the target list.
+-- This used to be called "junkfilter.sql".
+-- The parser uses the term "resjunk" to handle these cases.
+-- - thomas 1998-07-09
+--
+-- load test data
+CREATE TABLE test_missing_target (a int, b int, c char(8), d char);
+INSERT INTO test_missing_target VALUES (0, 1, 'XXXX', 'A');
+INSERT INTO test_missing_target VALUES (1, 2, 'ABAB', 'b');
+INSERT INTO test_missing_target VALUES (2, 2, 'ABAB', 'c');
+INSERT INTO test_missing_target VALUES (3, 3, 'BBBB', 'D');
+INSERT INTO test_missing_target VALUES (4, 3, 'BBBB', 'e');
+INSERT INTO test_missing_target VALUES (5, 3, 'bbbb', 'F');
+INSERT INTO test_missing_target VALUES (6, 4, 'cccc', 'g');
+INSERT INTO test_missing_target VALUES (7, 4, 'cccc', 'h');
+INSERT INTO test_missing_target VALUES (8, 4, 'CCCC', 'I');
+INSERT INTO test_missing_target VALUES (9, 4, 'CCCC', 'j');
+-- w/ existing GROUP BY target
+SELECT c, count(*) FROM test_missing_target GROUP BY test_missing_target.c ORDER BY c;
+ c | count
+----------+-------
+ ABAB | 2
+ BBBB | 2
+ CCCC | 2
+ XXXX | 1
+ bbbb | 1
+ cccc | 2
+(6 rows)
+
+-- w/o existing GROUP BY target using a relation name in GROUP BY clause
+SELECT count(*) FROM test_missing_target GROUP BY test_missing_target.c ORDER BY c;
+ count
+-------
+ 2
+ 2
+ 2
+ 1
+ 1
+ 2
+(6 rows)
+
+-- w/o existing GROUP BY target and w/o existing a different ORDER BY target
+-- failure expected
+SELECT count(*) FROM test_missing_target GROUP BY a ORDER BY b;
+ERROR: column "test_missing_target.b" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: ...ECT count(*) FROM test_missing_target GROUP BY a ORDER BY b;
+ ^
+-- w/o existing GROUP BY target and w/o existing same ORDER BY target
+SELECT count(*) FROM test_missing_target GROUP BY b ORDER BY b;
+ count
+-------
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- w/ existing GROUP BY target using a relation name in target
+SELECT test_missing_target.b, count(*)
+ FROM test_missing_target GROUP BY b ORDER BY b;
+ b | count
+---+-------
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+(4 rows)
+
+-- w/o existing GROUP BY target
+SELECT c FROM test_missing_target ORDER BY a;
+ c
+----------
+ XXXX
+ ABAB
+ ABAB
+ BBBB
+ BBBB
+ bbbb
+ cccc
+ cccc
+ CCCC
+ CCCC
+(10 rows)
+
+-- w/o existing ORDER BY target
+SELECT count(*) FROM test_missing_target GROUP BY b ORDER BY b desc;
+ count
+-------
+ 4
+ 3
+ 2
+ 1
+(4 rows)
+
+-- group using reference number
+SELECT count(*) FROM test_missing_target ORDER BY 1 desc;
+ count
+-------
+ 10
+(1 row)
+
+-- order using reference number
+SELECT c, count(*) FROM test_missing_target GROUP BY 1 ORDER BY 1;
+ c | count
+----------+-------
+ ABAB | 2
+ BBBB | 2
+ CCCC | 2
+ XXXX | 1
+ bbbb | 1
+ cccc | 2
+(6 rows)
+
+-- group using reference number out of range
+-- failure expected
+SELECT c, count(*) FROM test_missing_target GROUP BY 3;
+ERROR: GROUP BY position 3 is not in select list
+LINE 1: SELECT c, count(*) FROM test_missing_target GROUP BY 3;
+ ^
+-- group w/o existing GROUP BY and ORDER BY target under ambiguous condition
+-- failure expected
+SELECT count(*) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY b ORDER BY b;
+ERROR: column reference "b" is ambiguous
+LINE 3: GROUP BY b ORDER BY b;
+ ^
+-- order w/ target under ambiguous condition
+-- failure NOT expected
+SELECT a, a FROM test_missing_target
+ ORDER BY a;
+ a | a
+---+---
+ 0 | 0
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+(10 rows)
+
+-- order expression w/ target under ambiguous condition
+-- failure NOT expected
+SELECT a/2, a/2 FROM test_missing_target
+ ORDER BY a/2;
+ ?column? | ?column?
+----------+----------
+ 0 | 0
+ 0 | 0
+ 1 | 1
+ 1 | 1
+ 2 | 2
+ 2 | 2
+ 3 | 3
+ 3 | 3
+ 4 | 4
+ 4 | 4
+(10 rows)
+
+-- group expression w/ target under ambiguous condition
+-- failure NOT expected
+SELECT a/2, a/2 FROM test_missing_target
+ GROUP BY a/2 ORDER BY a/2;
+ ?column? | ?column?
+----------+----------
+ 0 | 0
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+(5 rows)
+
+-- group w/ existing GROUP BY target under ambiguous condition
+SELECT x.b, count(*) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b ORDER BY x.b;
+ b | count
+---+-------
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+(4 rows)
+
+-- group w/o existing GROUP BY target under ambiguous condition
+SELECT count(*) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b ORDER BY x.b;
+ count
+-------
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- group w/o existing GROUP BY target under ambiguous condition
+-- into a table
+CREATE TABLE test_missing_target2 AS
+SELECT count(*)
+FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b ORDER BY x.b;
+SELECT * FROM test_missing_target2;
+ count
+-------
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- Functions and expressions
+-- w/ existing GROUP BY target
+SELECT a%2, count(b) FROM test_missing_target
+GROUP BY test_missing_target.a%2
+ORDER BY test_missing_target.a%2;
+ ?column? | count
+----------+-------
+ 0 | 5
+ 1 | 5
+(2 rows)
+
+-- w/o existing GROUP BY target using a relation name in GROUP BY clause
+SELECT count(c) FROM test_missing_target
+GROUP BY lower(test_missing_target.c)
+ORDER BY lower(test_missing_target.c);
+ count
+-------
+ 2
+ 3
+ 4
+ 1
+(4 rows)
+
+-- w/o existing GROUP BY target and w/o existing a different ORDER BY target
+-- failure expected
+SELECT count(a) FROM test_missing_target GROUP BY a ORDER BY b;
+ERROR: column "test_missing_target.b" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: ...ECT count(a) FROM test_missing_target GROUP BY a ORDER BY b;
+ ^
+-- w/o existing GROUP BY target and w/o existing same ORDER BY target
+SELECT count(b) FROM test_missing_target GROUP BY b/2 ORDER BY b/2;
+ count
+-------
+ 1
+ 5
+ 4
+(3 rows)
+
+-- w/ existing GROUP BY target using a relation name in target
+SELECT lower(test_missing_target.c), count(c)
+ FROM test_missing_target GROUP BY lower(c) ORDER BY lower(c);
+ lower | count
+-------+-------
+ abab | 2
+ bbbb | 3
+ cccc | 4
+ xxxx | 1
+(4 rows)
+
+-- w/o existing GROUP BY target
+SELECT a FROM test_missing_target ORDER BY upper(d);
+ a
+---
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+(10 rows)
+
+-- w/o existing ORDER BY target
+SELECT count(b) FROM test_missing_target
+ GROUP BY (b + 1) / 2 ORDER BY (b + 1) / 2 desc;
+ count
+-------
+ 7
+ 3
+(2 rows)
+
+-- group w/o existing GROUP BY and ORDER BY target under ambiguous condition
+-- failure expected
+SELECT count(x.a) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY b/2 ORDER BY b/2;
+ERROR: column reference "b" is ambiguous
+LINE 3: GROUP BY b/2 ORDER BY b/2;
+ ^
+-- group w/ existing GROUP BY target under ambiguous condition
+SELECT x.b/2, count(x.b) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b/2 ORDER BY x.b/2;
+ ?column? | count
+----------+-------
+ 0 | 1
+ 1 | 5
+ 2 | 4
+(3 rows)
+
+-- group w/o existing GROUP BY target under ambiguous condition
+-- failure expected due to ambiguous b in count(b)
+SELECT count(b) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b/2;
+ERROR: column reference "b" is ambiguous
+LINE 1: SELECT count(b) FROM test_missing_target x, test_missing_tar...
+ ^
+-- group w/o existing GROUP BY target under ambiguous condition
+-- into a table
+CREATE TABLE test_missing_target3 AS
+SELECT count(x.b)
+FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b/2 ORDER BY x.b/2;
+SELECT * FROM test_missing_target3;
+ count
+-------
+ 1
+ 5
+ 4
+(3 rows)
+
+-- Cleanup
+DROP TABLE test_missing_target;
+DROP TABLE test_missing_target2;
+DROP TABLE test_missing_target3;
diff --git a/src/test/regress/expected/select_implicit_1.out b/src/test/regress/expected/select_implicit_1.out
new file mode 100644
index 0000000..d67521e
--- /dev/null
+++ b/src/test/regress/expected/select_implicit_1.out
@@ -0,0 +1,338 @@
+--
+-- SELECT_IMPLICIT
+-- Test cases for queries with ordering terms missing from the target list.
+-- This used to be called "junkfilter.sql".
+-- The parser uses the term "resjunk" to handle these cases.
+-- - thomas 1998-07-09
+--
+-- load test data
+CREATE TABLE test_missing_target (a int, b int, c char(8), d char);
+INSERT INTO test_missing_target VALUES (0, 1, 'XXXX', 'A');
+INSERT INTO test_missing_target VALUES (1, 2, 'ABAB', 'b');
+INSERT INTO test_missing_target VALUES (2, 2, 'ABAB', 'c');
+INSERT INTO test_missing_target VALUES (3, 3, 'BBBB', 'D');
+INSERT INTO test_missing_target VALUES (4, 3, 'BBBB', 'e');
+INSERT INTO test_missing_target VALUES (5, 3, 'bbbb', 'F');
+INSERT INTO test_missing_target VALUES (6, 4, 'cccc', 'g');
+INSERT INTO test_missing_target VALUES (7, 4, 'cccc', 'h');
+INSERT INTO test_missing_target VALUES (8, 4, 'CCCC', 'I');
+INSERT INTO test_missing_target VALUES (9, 4, 'CCCC', 'j');
+-- w/ existing GROUP BY target
+SELECT c, count(*) FROM test_missing_target GROUP BY test_missing_target.c ORDER BY c;
+ c | count
+----------+-------
+ ABAB | 2
+ BBBB | 2
+ bbbb | 1
+ CCCC | 2
+ cccc | 2
+ XXXX | 1
+(6 rows)
+
+-- w/o existing GROUP BY target using a relation name in GROUP BY clause
+SELECT count(*) FROM test_missing_target GROUP BY test_missing_target.c ORDER BY c;
+ count
+-------
+ 2
+ 2
+ 1
+ 2
+ 2
+ 1
+(6 rows)
+
+-- w/o existing GROUP BY target and w/o existing a different ORDER BY target
+-- failure expected
+SELECT count(*) FROM test_missing_target GROUP BY a ORDER BY b;
+ERROR: column "test_missing_target.b" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: ...ECT count(*) FROM test_missing_target GROUP BY a ORDER BY b;
+ ^
+-- w/o existing GROUP BY target and w/o existing same ORDER BY target
+SELECT count(*) FROM test_missing_target GROUP BY b ORDER BY b;
+ count
+-------
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- w/ existing GROUP BY target using a relation name in target
+SELECT test_missing_target.b, count(*)
+ FROM test_missing_target GROUP BY b ORDER BY b;
+ b | count
+---+-------
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+(4 rows)
+
+-- w/o existing GROUP BY target
+SELECT c FROM test_missing_target ORDER BY a;
+ c
+----------
+ XXXX
+ ABAB
+ ABAB
+ BBBB
+ BBBB
+ bbbb
+ cccc
+ cccc
+ CCCC
+ CCCC
+(10 rows)
+
+-- w/o existing ORDER BY target
+SELECT count(*) FROM test_missing_target GROUP BY b ORDER BY b desc;
+ count
+-------
+ 4
+ 3
+ 2
+ 1
+(4 rows)
+
+-- group using reference number
+SELECT count(*) FROM test_missing_target ORDER BY 1 desc;
+ count
+-------
+ 10
+(1 row)
+
+-- order using reference number
+SELECT c, count(*) FROM test_missing_target GROUP BY 1 ORDER BY 1;
+ c | count
+----------+-------
+ ABAB | 2
+ BBBB | 2
+ bbbb | 1
+ CCCC | 2
+ cccc | 2
+ XXXX | 1
+(6 rows)
+
+-- group using reference number out of range
+-- failure expected
+SELECT c, count(*) FROM test_missing_target GROUP BY 3;
+ERROR: GROUP BY position 3 is not in select list
+LINE 1: SELECT c, count(*) FROM test_missing_target GROUP BY 3;
+ ^
+-- group w/o existing GROUP BY and ORDER BY target under ambiguous condition
+-- failure expected
+SELECT count(*) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY b ORDER BY b;
+ERROR: column reference "b" is ambiguous
+LINE 3: GROUP BY b ORDER BY b;
+ ^
+-- order w/ target under ambiguous condition
+-- failure NOT expected
+SELECT a, a FROM test_missing_target
+ ORDER BY a;
+ a | a
+---+---
+ 0 | 0
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+(10 rows)
+
+-- order expression w/ target under ambiguous condition
+-- failure NOT expected
+SELECT a/2, a/2 FROM test_missing_target
+ ORDER BY a/2;
+ ?column? | ?column?
+----------+----------
+ 0 | 0
+ 0 | 0
+ 1 | 1
+ 1 | 1
+ 2 | 2
+ 2 | 2
+ 3 | 3
+ 3 | 3
+ 4 | 4
+ 4 | 4
+(10 rows)
+
+-- group expression w/ target under ambiguous condition
+-- failure NOT expected
+SELECT a/2, a/2 FROM test_missing_target
+ GROUP BY a/2 ORDER BY a/2;
+ ?column? | ?column?
+----------+----------
+ 0 | 0
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+(5 rows)
+
+-- group w/ existing GROUP BY target under ambiguous condition
+SELECT x.b, count(*) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b ORDER BY x.b;
+ b | count
+---+-------
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+(4 rows)
+
+-- group w/o existing GROUP BY target under ambiguous condition
+SELECT count(*) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b ORDER BY x.b;
+ count
+-------
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- group w/o existing GROUP BY target under ambiguous condition
+-- into a table
+CREATE TABLE test_missing_target2 AS
+SELECT count(*)
+FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b ORDER BY x.b;
+SELECT * FROM test_missing_target2;
+ count
+-------
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- Functions and expressions
+-- w/ existing GROUP BY target
+SELECT a%2, count(b) FROM test_missing_target
+GROUP BY test_missing_target.a%2
+ORDER BY test_missing_target.a%2;
+ ?column? | count
+----------+-------
+ 0 | 5
+ 1 | 5
+(2 rows)
+
+-- w/o existing GROUP BY target using a relation name in GROUP BY clause
+SELECT count(c) FROM test_missing_target
+GROUP BY lower(test_missing_target.c)
+ORDER BY lower(test_missing_target.c);
+ count
+-------
+ 2
+ 3
+ 4
+ 1
+(4 rows)
+
+-- w/o existing GROUP BY target and w/o existing a different ORDER BY target
+-- failure expected
+SELECT count(a) FROM test_missing_target GROUP BY a ORDER BY b;
+ERROR: column "test_missing_target.b" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: ...ECT count(a) FROM test_missing_target GROUP BY a ORDER BY b;
+ ^
+-- w/o existing GROUP BY target and w/o existing same ORDER BY target
+SELECT count(b) FROM test_missing_target GROUP BY b/2 ORDER BY b/2;
+ count
+-------
+ 1
+ 5
+ 4
+(3 rows)
+
+-- w/ existing GROUP BY target using a relation name in target
+SELECT lower(test_missing_target.c), count(c)
+ FROM test_missing_target GROUP BY lower(c) ORDER BY lower(c);
+ lower | count
+-------+-------
+ abab | 2
+ bbbb | 3
+ cccc | 4
+ xxxx | 1
+(4 rows)
+
+-- w/o existing GROUP BY target
+SELECT a FROM test_missing_target ORDER BY upper(d);
+ a
+---
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+(10 rows)
+
+-- w/o existing ORDER BY target
+SELECT count(b) FROM test_missing_target
+ GROUP BY (b + 1) / 2 ORDER BY (b + 1) / 2 desc;
+ count
+-------
+ 7
+ 3
+(2 rows)
+
+-- group w/o existing GROUP BY and ORDER BY target under ambiguous condition
+-- failure expected
+SELECT count(x.a) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY b/2 ORDER BY b/2;
+ERROR: column reference "b" is ambiguous
+LINE 3: GROUP BY b/2 ORDER BY b/2;
+ ^
+-- group w/ existing GROUP BY target under ambiguous condition
+SELECT x.b/2, count(x.b) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b/2 ORDER BY x.b/2;
+ ?column? | count
+----------+-------
+ 0 | 1
+ 1 | 5
+ 2 | 4
+(3 rows)
+
+-- group w/o existing GROUP BY target under ambiguous condition
+-- failure expected due to ambiguous b in count(b)
+SELECT count(b) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b/2;
+ERROR: column reference "b" is ambiguous
+LINE 1: SELECT count(b) FROM test_missing_target x, test_missing_tar...
+ ^
+-- group w/o existing GROUP BY target under ambiguous condition
+-- into a table
+CREATE TABLE test_missing_target3 AS
+SELECT count(x.b)
+FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b/2 ORDER BY x.b/2;
+SELECT * FROM test_missing_target3;
+ count
+-------
+ 1
+ 5
+ 4
+(3 rows)
+
+-- Cleanup
+DROP TABLE test_missing_target;
+DROP TABLE test_missing_target2;
+DROP TABLE test_missing_target3;
diff --git a/src/test/regress/expected/select_implicit_2.out b/src/test/regress/expected/select_implicit_2.out
new file mode 100644
index 0000000..7a353d0
--- /dev/null
+++ b/src/test/regress/expected/select_implicit_2.out
@@ -0,0 +1,338 @@
+--
+-- SELECT_IMPLICIT
+-- Test cases for queries with ordering terms missing from the target list.
+-- This used to be called "junkfilter.sql".
+-- The parser uses the term "resjunk" to handle these cases.
+-- - thomas 1998-07-09
+--
+-- load test data
+CREATE TABLE test_missing_target (a int, b int, c char(8), d char);
+INSERT INTO test_missing_target VALUES (0, 1, 'XXXX', 'A');
+INSERT INTO test_missing_target VALUES (1, 2, 'ABAB', 'b');
+INSERT INTO test_missing_target VALUES (2, 2, 'ABAB', 'c');
+INSERT INTO test_missing_target VALUES (3, 3, 'BBBB', 'D');
+INSERT INTO test_missing_target VALUES (4, 3, 'BBBB', 'e');
+INSERT INTO test_missing_target VALUES (5, 3, 'bbbb', 'F');
+INSERT INTO test_missing_target VALUES (6, 4, 'cccc', 'g');
+INSERT INTO test_missing_target VALUES (7, 4, 'cccc', 'h');
+INSERT INTO test_missing_target VALUES (8, 4, 'CCCC', 'I');
+INSERT INTO test_missing_target VALUES (9, 4, 'CCCC', 'j');
+-- w/ existing GROUP BY target
+SELECT c, count(*) FROM test_missing_target GROUP BY test_missing_target.c ORDER BY c;
+ c | count
+----------+-------
+ ABAB | 2
+ bbbb | 1
+ BBBB | 2
+ cccc | 2
+ CCCC | 2
+ XXXX | 1
+(6 rows)
+
+-- w/o existing GROUP BY target using a relation name in GROUP BY clause
+SELECT count(*) FROM test_missing_target GROUP BY test_missing_target.c ORDER BY c;
+ count
+-------
+ 2
+ 1
+ 2
+ 2
+ 2
+ 1
+(6 rows)
+
+-- w/o existing GROUP BY target and w/o existing a different ORDER BY target
+-- failure expected
+SELECT count(*) FROM test_missing_target GROUP BY a ORDER BY b;
+ERROR: column "test_missing_target.b" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: ...ECT count(*) FROM test_missing_target GROUP BY a ORDER BY b;
+ ^
+-- w/o existing GROUP BY target and w/o existing same ORDER BY target
+SELECT count(*) FROM test_missing_target GROUP BY b ORDER BY b;
+ count
+-------
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- w/ existing GROUP BY target using a relation name in target
+SELECT test_missing_target.b, count(*)
+ FROM test_missing_target GROUP BY b ORDER BY b;
+ b | count
+---+-------
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+(4 rows)
+
+-- w/o existing GROUP BY target
+SELECT c FROM test_missing_target ORDER BY a;
+ c
+----------
+ XXXX
+ ABAB
+ ABAB
+ BBBB
+ BBBB
+ bbbb
+ cccc
+ cccc
+ CCCC
+ CCCC
+(10 rows)
+
+-- w/o existing ORDER BY target
+SELECT count(*) FROM test_missing_target GROUP BY b ORDER BY b desc;
+ count
+-------
+ 4
+ 3
+ 2
+ 1
+(4 rows)
+
+-- group using reference number
+SELECT count(*) FROM test_missing_target ORDER BY 1 desc;
+ count
+-------
+ 10
+(1 row)
+
+-- order using reference number
+SELECT c, count(*) FROM test_missing_target GROUP BY 1 ORDER BY 1;
+ c | count
+----------+-------
+ ABAB | 2
+ bbbb | 1
+ BBBB | 2
+ cccc | 2
+ CCCC | 2
+ XXXX | 1
+(6 rows)
+
+-- group using reference number out of range
+-- failure expected
+SELECT c, count(*) FROM test_missing_target GROUP BY 3;
+ERROR: GROUP BY position 3 is not in select list
+LINE 1: SELECT c, count(*) FROM test_missing_target GROUP BY 3;
+ ^
+-- group w/o existing GROUP BY and ORDER BY target under ambiguous condition
+-- failure expected
+SELECT count(*) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY b ORDER BY b;
+ERROR: column reference "b" is ambiguous
+LINE 3: GROUP BY b ORDER BY b;
+ ^
+-- order w/ target under ambiguous condition
+-- failure NOT expected
+SELECT a, a FROM test_missing_target
+ ORDER BY a;
+ a | a
+---+---
+ 0 | 0
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+(10 rows)
+
+-- order expression w/ target under ambiguous condition
+-- failure NOT expected
+SELECT a/2, a/2 FROM test_missing_target
+ ORDER BY a/2;
+ ?column? | ?column?
+----------+----------
+ 0 | 0
+ 0 | 0
+ 1 | 1
+ 1 | 1
+ 2 | 2
+ 2 | 2
+ 3 | 3
+ 3 | 3
+ 4 | 4
+ 4 | 4
+(10 rows)
+
+-- group expression w/ target under ambiguous condition
+-- failure NOT expected
+SELECT a/2, a/2 FROM test_missing_target
+ GROUP BY a/2 ORDER BY a/2;
+ ?column? | ?column?
+----------+----------
+ 0 | 0
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+(5 rows)
+
+-- group w/ existing GROUP BY target under ambiguous condition
+SELECT x.b, count(*) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b ORDER BY x.b;
+ b | count
+---+-------
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+(4 rows)
+
+-- group w/o existing GROUP BY target under ambiguous condition
+SELECT count(*) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b ORDER BY x.b;
+ count
+-------
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- group w/o existing GROUP BY target under ambiguous condition
+-- into a table
+CREATE TABLE test_missing_target2 AS
+SELECT count(*)
+FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b ORDER BY x.b;
+SELECT * FROM test_missing_target2;
+ count
+-------
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- Functions and expressions
+-- w/ existing GROUP BY target
+SELECT a%2, count(b) FROM test_missing_target
+GROUP BY test_missing_target.a%2
+ORDER BY test_missing_target.a%2;
+ ?column? | count
+----------+-------
+ 0 | 5
+ 1 | 5
+(2 rows)
+
+-- w/o existing GROUP BY target using a relation name in GROUP BY clause
+SELECT count(c) FROM test_missing_target
+GROUP BY lower(test_missing_target.c)
+ORDER BY lower(test_missing_target.c);
+ count
+-------
+ 2
+ 3
+ 4
+ 1
+(4 rows)
+
+-- w/o existing GROUP BY target and w/o existing a different ORDER BY target
+-- failure expected
+SELECT count(a) FROM test_missing_target GROUP BY a ORDER BY b;
+ERROR: column "test_missing_target.b" must appear in the GROUP BY clause or be used in an aggregate function
+LINE 1: ...ECT count(a) FROM test_missing_target GROUP BY a ORDER BY b;
+ ^
+-- w/o existing GROUP BY target and w/o existing same ORDER BY target
+SELECT count(b) FROM test_missing_target GROUP BY b/2 ORDER BY b/2;
+ count
+-------
+ 1
+ 5
+ 4
+(3 rows)
+
+-- w/ existing GROUP BY target using a relation name in target
+SELECT lower(test_missing_target.c), count(c)
+ FROM test_missing_target GROUP BY lower(c) ORDER BY lower(c);
+ lower | count
+-------+-------
+ abab | 2
+ bbbb | 3
+ cccc | 4
+ xxxx | 1
+(4 rows)
+
+-- w/o existing GROUP BY target
+SELECT a FROM test_missing_target ORDER BY upper(d);
+ a
+---
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+(10 rows)
+
+-- w/o existing ORDER BY target
+SELECT count(b) FROM test_missing_target
+ GROUP BY (b + 1) / 2 ORDER BY (b + 1) / 2 desc;
+ count
+-------
+ 7
+ 3
+(2 rows)
+
+-- group w/o existing GROUP BY and ORDER BY target under ambiguous condition
+-- failure expected
+SELECT count(x.a) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY b/2 ORDER BY b/2;
+ERROR: column reference "b" is ambiguous
+LINE 3: GROUP BY b/2 ORDER BY b/2;
+ ^
+-- group w/ existing GROUP BY target under ambiguous condition
+SELECT x.b/2, count(x.b) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b/2 ORDER BY x.b/2;
+ ?column? | count
+----------+-------
+ 0 | 1
+ 1 | 5
+ 2 | 4
+(3 rows)
+
+-- group w/o existing GROUP BY target under ambiguous condition
+-- failure expected due to ambiguous b in count(b)
+SELECT count(b) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b/2;
+ERROR: column reference "b" is ambiguous
+LINE 1: SELECT count(b) FROM test_missing_target x, test_missing_tar...
+ ^
+-- group w/o existing GROUP BY target under ambiguous condition
+-- into a table
+CREATE TABLE test_missing_target3 AS
+SELECT count(x.b)
+FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b/2 ORDER BY x.b/2;
+SELECT * FROM test_missing_target3;
+ count
+-------
+ 1
+ 5
+ 4
+(3 rows)
+
+-- Cleanup
+DROP TABLE test_missing_target;
+DROP TABLE test_missing_target2;
+DROP TABLE test_missing_target3;
diff --git a/src/test/regress/expected/select_into.out b/src/test/regress/expected/select_into.out
new file mode 100644
index 0000000..43b8209
--- /dev/null
+++ b/src/test/regress/expected/select_into.out
@@ -0,0 +1,222 @@
+--
+-- SELECT_INTO
+--
+SELECT *
+ INTO TABLE sitmp1
+ FROM onek
+ WHERE onek.unique1 < 2;
+DROP TABLE sitmp1;
+SELECT *
+ INTO TABLE sitmp1
+ FROM onek2
+ WHERE onek2.unique1 < 2;
+DROP TABLE sitmp1;
+--
+-- SELECT INTO and INSERT permission, if owner is not allowed to insert.
+--
+CREATE SCHEMA selinto_schema;
+CREATE USER regress_selinto_user;
+ALTER DEFAULT PRIVILEGES FOR ROLE regress_selinto_user
+ REVOKE INSERT ON TABLES FROM regress_selinto_user;
+GRANT ALL ON SCHEMA selinto_schema TO public;
+SET SESSION AUTHORIZATION regress_selinto_user;
+-- WITH DATA, passes.
+CREATE TABLE selinto_schema.tbl_withdata1 (a)
+ AS SELECT generate_series(1,3) WITH DATA;
+INSERT INTO selinto_schema.tbl_withdata1 VALUES (4);
+ERROR: permission denied for table tbl_withdata1
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE selinto_schema.tbl_withdata2 (a) AS
+ SELECT generate_series(1,3) WITH DATA;
+ QUERY PLAN
+--------------------------------------
+ ProjectSet (actual rows=3 loops=1)
+ -> Result (actual rows=1 loops=1)
+(2 rows)
+
+-- WITH NO DATA, passes.
+CREATE TABLE selinto_schema.tbl_nodata1 (a) AS
+ SELECT generate_series(1,3) WITH NO DATA;
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE selinto_schema.tbl_nodata2 (a) AS
+ SELECT generate_series(1,3) WITH NO DATA;
+ QUERY PLAN
+-------------------------------
+ ProjectSet (never executed)
+ -> Result (never executed)
+(2 rows)
+
+-- EXECUTE and WITH DATA, passes.
+PREPARE data_sel AS SELECT generate_series(1,3);
+CREATE TABLE selinto_schema.tbl_withdata3 (a) AS
+ EXECUTE data_sel WITH DATA;
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE selinto_schema.tbl_withdata4 (a) AS
+ EXECUTE data_sel WITH DATA;
+ QUERY PLAN
+--------------------------------------
+ ProjectSet (actual rows=3 loops=1)
+ -> Result (actual rows=1 loops=1)
+(2 rows)
+
+-- EXECUTE and WITH NO DATA, passes.
+CREATE TABLE selinto_schema.tbl_nodata3 (a) AS
+ EXECUTE data_sel WITH NO DATA;
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE selinto_schema.tbl_nodata4 (a) AS
+ EXECUTE data_sel WITH NO DATA;
+ QUERY PLAN
+-------------------------------
+ ProjectSet (never executed)
+ -> Result (never executed)
+(2 rows)
+
+RESET SESSION AUTHORIZATION;
+ALTER DEFAULT PRIVILEGES FOR ROLE regress_selinto_user
+ GRANT INSERT ON TABLES TO regress_selinto_user;
+SET SESSION AUTHORIZATION regress_selinto_user;
+RESET SESSION AUTHORIZATION;
+DEALLOCATE data_sel;
+DROP SCHEMA selinto_schema CASCADE;
+NOTICE: drop cascades to 8 other objects
+DETAIL: drop cascades to table selinto_schema.tbl_withdata1
+drop cascades to table selinto_schema.tbl_withdata2
+drop cascades to table selinto_schema.tbl_nodata1
+drop cascades to table selinto_schema.tbl_nodata2
+drop cascades to table selinto_schema.tbl_withdata3
+drop cascades to table selinto_schema.tbl_withdata4
+drop cascades to table selinto_schema.tbl_nodata3
+drop cascades to table selinto_schema.tbl_nodata4
+DROP USER regress_selinto_user;
+-- Tests for WITH NO DATA and column name consistency
+CREATE TABLE ctas_base (i int, j int);
+INSERT INTO ctas_base VALUES (1, 2);
+CREATE TABLE ctas_nodata (ii, jj, kk) AS SELECT i, j FROM ctas_base; -- Error
+ERROR: too many column names were specified
+CREATE TABLE ctas_nodata (ii, jj, kk) AS SELECT i, j FROM ctas_base WITH NO DATA; -- Error
+ERROR: too many column names were specified
+CREATE TABLE ctas_nodata (ii, jj) AS SELECT i, j FROM ctas_base; -- OK
+CREATE TABLE ctas_nodata_2 (ii, jj) AS SELECT i, j FROM ctas_base WITH NO DATA; -- OK
+CREATE TABLE ctas_nodata_3 (ii) AS SELECT i, j FROM ctas_base; -- OK
+CREATE TABLE ctas_nodata_4 (ii) AS SELECT i, j FROM ctas_base WITH NO DATA; -- OK
+SELECT * FROM ctas_nodata;
+ ii | jj
+----+----
+ 1 | 2
+(1 row)
+
+SELECT * FROM ctas_nodata_2;
+ ii | jj
+----+----
+(0 rows)
+
+SELECT * FROM ctas_nodata_3;
+ ii | j
+----+---
+ 1 | 2
+(1 row)
+
+SELECT * FROM ctas_nodata_4;
+ ii | j
+----+---
+(0 rows)
+
+DROP TABLE ctas_base;
+DROP TABLE ctas_nodata;
+DROP TABLE ctas_nodata_2;
+DROP TABLE ctas_nodata_3;
+DROP TABLE ctas_nodata_4;
+--
+-- CREATE TABLE AS/SELECT INTO as last command in a SQL function
+-- have been known to cause problems
+--
+CREATE FUNCTION make_table() RETURNS VOID
+AS $$
+ CREATE TABLE created_table AS SELECT * FROM int8_tbl;
+$$ LANGUAGE SQL;
+SELECT make_table();
+ make_table
+------------
+
+(1 row)
+
+SELECT * FROM created_table;
+ q1 | q2
+------------------+-------------------
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+(5 rows)
+
+-- Try EXPLAIN ANALYZE SELECT INTO and EXPLAIN ANALYZE CREATE TABLE AS
+-- WITH NO DATA, but hide the outputs since they won't be stable.
+DO $$
+BEGIN
+ EXECUTE 'EXPLAIN ANALYZE SELECT * INTO TABLE easi FROM int8_tbl';
+ EXECUTE 'EXPLAIN ANALYZE CREATE TABLE easi2 AS SELECT * FROM int8_tbl WITH NO DATA';
+END$$;
+DROP TABLE created_table;
+DROP TABLE easi, easi2;
+--
+-- Disallowed uses of SELECT ... INTO. All should fail
+--
+DECLARE foo CURSOR FOR SELECT 1 INTO b;
+ERROR: SELECT ... INTO is not allowed here
+LINE 1: DECLARE foo CURSOR FOR SELECT 1 INTO b;
+ ^
+COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob';
+ERROR: COPY (SELECT INTO) is not supported
+SELECT * FROM (SELECT 1 INTO f) bar;
+ERROR: SELECT ... INTO is not allowed here
+LINE 1: SELECT * FROM (SELECT 1 INTO f) bar;
+ ^
+CREATE VIEW foo AS SELECT 1 INTO b;
+ERROR: views must not contain SELECT INTO
+INSERT INTO b SELECT 1 INTO f;
+ERROR: SELECT ... INTO is not allowed here
+LINE 1: INSERT INTO b SELECT 1 INTO f;
+ ^
+-- Test CREATE TABLE AS ... IF NOT EXISTS
+CREATE TABLE ctas_ine_tbl AS SELECT 1;
+CREATE TABLE ctas_ine_tbl AS SELECT 1 / 0; -- error
+ERROR: relation "ctas_ine_tbl" already exists
+CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS SELECT 1 / 0; -- ok
+NOTICE: relation "ctas_ine_tbl" already exists, skipping
+CREATE TABLE ctas_ine_tbl AS SELECT 1 / 0 WITH NO DATA; -- error
+ERROR: relation "ctas_ine_tbl" already exists
+CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS SELECT 1 / 0 WITH NO DATA; -- ok
+NOTICE: relation "ctas_ine_tbl" already exists, skipping
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE ctas_ine_tbl AS SELECT 1 / 0; -- error
+ERROR: relation "ctas_ine_tbl" already exists
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS SELECT 1 / 0; -- ok
+NOTICE: relation "ctas_ine_tbl" already exists, skipping
+ QUERY PLAN
+------------
+(0 rows)
+
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE ctas_ine_tbl AS SELECT 1 / 0 WITH NO DATA; -- error
+ERROR: relation "ctas_ine_tbl" already exists
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS SELECT 1 / 0 WITH NO DATA; -- ok
+NOTICE: relation "ctas_ine_tbl" already exists, skipping
+ QUERY PLAN
+------------
+(0 rows)
+
+PREPARE ctas_ine_query AS SELECT 1 / 0;
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE ctas_ine_tbl AS EXECUTE ctas_ine_query; -- error
+ERROR: relation "ctas_ine_tbl" already exists
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS EXECUTE ctas_ine_query; -- ok
+NOTICE: relation "ctas_ine_tbl" already exists, skipping
+ QUERY PLAN
+------------
+(0 rows)
+
+DROP TABLE ctas_ine_tbl;
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
new file mode 100644
index 0000000..91f74fe
--- /dev/null
+++ b/src/test/regress/expected/select_parallel.out
@@ -0,0 +1,1221 @@
+--
+-- PARALLEL
+--
+create function sp_parallel_restricted(int) returns int as
+ $$begin return $1; end$$ language plpgsql parallel restricted;
+begin;
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+-- Parallel Append with partial-subplans
+explain (costs off)
+ select round(avg(aa)), sum(aa) from a_star;
+ QUERY PLAN
+--------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 3
+ -> Partial Aggregate
+ -> Parallel Append
+ -> Parallel Seq Scan on d_star a_star_4
+ -> Parallel Seq Scan on f_star a_star_6
+ -> Parallel Seq Scan on e_star a_star_5
+ -> Parallel Seq Scan on b_star a_star_2
+ -> Parallel Seq Scan on c_star a_star_3
+ -> Parallel Seq Scan on a_star a_star_1
+(11 rows)
+
+select round(avg(aa)), sum(aa) from a_star a1;
+ round | sum
+-------+-----
+ 14 | 355
+(1 row)
+
+-- Parallel Append with both partial and non-partial subplans
+alter table c_star set (parallel_workers = 0);
+alter table d_star set (parallel_workers = 0);
+explain (costs off)
+ select round(avg(aa)), sum(aa) from a_star;
+ QUERY PLAN
+--------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 3
+ -> Partial Aggregate
+ -> Parallel Append
+ -> Seq Scan on d_star a_star_4
+ -> Seq Scan on c_star a_star_3
+ -> Parallel Seq Scan on f_star a_star_6
+ -> Parallel Seq Scan on e_star a_star_5
+ -> Parallel Seq Scan on b_star a_star_2
+ -> Parallel Seq Scan on a_star a_star_1
+(11 rows)
+
+select round(avg(aa)), sum(aa) from a_star a2;
+ round | sum
+-------+-----
+ 14 | 355
+(1 row)
+
+-- Parallel Append with only non-partial subplans
+alter table a_star set (parallel_workers = 0);
+alter table b_star set (parallel_workers = 0);
+alter table e_star set (parallel_workers = 0);
+alter table f_star set (parallel_workers = 0);
+explain (costs off)
+ select round(avg(aa)), sum(aa) from a_star;
+ QUERY PLAN
+-----------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 3
+ -> Partial Aggregate
+ -> Parallel Append
+ -> Seq Scan on d_star a_star_4
+ -> Seq Scan on f_star a_star_6
+ -> Seq Scan on e_star a_star_5
+ -> Seq Scan on b_star a_star_2
+ -> Seq Scan on c_star a_star_3
+ -> Seq Scan on a_star a_star_1
+(11 rows)
+
+select round(avg(aa)), sum(aa) from a_star a3;
+ round | sum
+-------+-----
+ 14 | 355
+(1 row)
+
+-- Disable Parallel Append
+alter table a_star reset (parallel_workers);
+alter table b_star reset (parallel_workers);
+alter table c_star reset (parallel_workers);
+alter table d_star reset (parallel_workers);
+alter table e_star reset (parallel_workers);
+alter table f_star reset (parallel_workers);
+set enable_parallel_append to off;
+explain (costs off)
+ select round(avg(aa)), sum(aa) from a_star;
+ QUERY PLAN
+--------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 1
+ -> Partial Aggregate
+ -> Append
+ -> Parallel Seq Scan on a_star a_star_1
+ -> Parallel Seq Scan on b_star a_star_2
+ -> Parallel Seq Scan on c_star a_star_3
+ -> Parallel Seq Scan on d_star a_star_4
+ -> Parallel Seq Scan on e_star a_star_5
+ -> Parallel Seq Scan on f_star a_star_6
+(11 rows)
+
+select round(avg(aa)), sum(aa) from a_star a4;
+ round | sum
+-------+-----
+ 14 | 355
+(1 row)
+
+reset enable_parallel_append;
+-- Parallel Append that runs serially
+create function sp_test_func() returns setof text as
+$$ select 'foo'::varchar union all select 'bar'::varchar $$
+language sql stable;
+select sp_test_func() order by 1;
+ sp_test_func
+--------------
+ bar
+ foo
+(2 rows)
+
+-- 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;
+ QUERY PLAN
+--------------------------------------------------------------
+ Aggregate
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Append
+ -> Parallel Seq Scan on part_pa_test_p1 pa2_1
+ -> Parallel Seq Scan on part_pa_test_p2 pa2_2
+ SubPlan 2
+ -> Result
+ SubPlan 1
+ -> Append
+ -> Seq Scan on part_pa_test_p1 pa1_1
+ Filter: (a = pa2.a)
+ -> Seq Scan on part_pa_test_p2 pa1_2
+ Filter: (a = pa2.a)
+(14 rows)
+
+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';
+ QUERY PLAN
+---------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk1
+ Filter: (stringu1 = 'GRAAAA'::name)
+(6 rows)
+
+select count(*) from tenk1 where stringu1 = 'GRAAAA';
+ count
+-------
+ 15
+(1 row)
+
+-- 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';
+ QUERY PLAN
+---------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk1
+ Filter: (stringu1 = 'GRAAAA'::name)
+(6 rows)
+
+select count(*) from tenk1 where stringu1 = 'GRAAAA';
+ count
+-------
+ 15
+(1 row)
+
+reset max_parallel_workers;
+reset parallel_leader_participation;
+-- test that parallel_restricted function doesn't run in worker
+alter table tenk1 set (parallel_workers = 4);
+explain (verbose, costs off)
+select sp_parallel_restricted(unique1) from tenk1
+ where stringu1 = 'GRAAAA' order by 1;
+ QUERY PLAN
+---------------------------------------------------------
+ Sort
+ Output: (sp_parallel_restricted(unique1))
+ Sort Key: (sp_parallel_restricted(tenk1.unique1))
+ -> Gather
+ Output: sp_parallel_restricted(unique1)
+ Workers Planned: 4
+ -> Parallel Seq Scan on public.tenk1
+ Output: unique1
+ Filter: (tenk1.stringu1 = 'GRAAAA'::name)
+(9 rows)
+
+-- test parallel plan when group by expression is in target list.
+explain (costs off)
+ select length(stringu1) from tenk1 group by length(stringu1);
+ QUERY PLAN
+---------------------------------------------------
+ Finalize HashAggregate
+ Group Key: (length((stringu1)::text))
+ -> Gather
+ Workers Planned: 4
+ -> Partial HashAggregate
+ Group Key: length((stringu1)::text)
+ -> Parallel Seq Scan on tenk1
+(7 rows)
+
+select length(stringu1) from tenk1 group by length(stringu1);
+ length
+--------
+ 6
+(1 row)
+
+explain (costs off)
+ select stringu1, count(*) from tenk1 group by stringu1 order by stringu1;
+ QUERY PLAN
+----------------------------------------------------
+ Sort
+ Sort Key: stringu1
+ -> Finalize HashAggregate
+ Group Key: stringu1
+ -> Gather
+ Workers Planned: 4
+ -> Partial HashAggregate
+ Group Key: stringu1
+ -> Parallel Seq Scan on tenk1
+(9 rows)
+
+-- 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));
+ QUERY PLAN
+-------------------------------------------------------------------
+ HashAggregate
+ Group Key: sp_parallel_restricted(unique1)
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Index Only Scan using tenk1_unique1 on tenk1
+(5 rows)
+
+-- test prepared statement
+prepare tenk1_count(integer) As select count((unique1)) from tenk1 where hundred > $1;
+explain (costs off) execute tenk1_count(1);
+ QUERY PLAN
+----------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk1
+ Filter: (hundred > 1)
+(6 rows)
+
+execute tenk1_count(1);
+ count
+-------
+ 9800
+(1 row)
+
+deallocate tenk1_count;
+-- test parallel plans for queries containing un-correlated subplans.
+alter table tenk2 set (parallel_workers = 0);
+explain (costs off)
+ select count(*) from tenk1 where (two, four) not in
+ (select hundred, thousand from tenk2 where thousand > 100);
+ QUERY PLAN
+------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk1
+ Filter: (NOT (hashed SubPlan 1))
+ SubPlan 1
+ -> Seq Scan on tenk2
+ Filter: (thousand > 100)
+(9 rows)
+
+select count(*) from tenk1 where (two, four) not in
+ (select hundred, thousand from tenk2 where thousand > 100);
+ count
+-------
+ 10000
+(1 row)
+
+-- 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);
+ QUERY PLAN
+------------------------------------
+ Seq Scan on tenk1
+ Filter: (NOT (hashed SubPlan 1))
+ SubPlan 1
+ -> Seq Scan on tenk2
+(4 rows)
+
+alter table tenk2 reset (parallel_workers);
+-- test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $2)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $2
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $2)
+(12 rows)
+
+select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+-- 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;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Index Scan using tenk1_hundred on tenk1
+ Index Cond: (hundred > 1)
+(6 rows)
+
+select count((unique1)) from tenk1 where hundred > 1;
+ count
+-------
+ 9800
+(1 row)
+
+-- test parallel index-only scans.
+explain (costs off)
+ select count(*) from tenk1 where thousand > 95;
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Index Only Scan using tenk1_thous_tenthous on tenk1
+ Index Cond: (thousand > 95)
+(6 rows)
+
+select count(*) from tenk1 where thousand > 95;
+ count
+-------
+ 9040
+(1 row)
+
+-- 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;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Nested Loop Left Join
+ -> Values Scan on "*VALUES*"
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Index Scan using tenk1_hundred on tenk1
+ Index Cond: (hundred > 10)
+(8 rows)
+
+select * from
+ (select count(unique1) from tenk1 where hundred > 10) ss
+ right join (values (1),(2),(3)) v(x) on true;
+ count | x
+-------+---
+ 8900 | 1
+ 8900 | 2
+ 8900 | 3
+(3 rows)
+
+explain (costs off)
+select * from
+ (select count(*) from tenk1 where thousand > 99) ss
+ right join (values (1),(2),(3)) v(x) on true;
+ QUERY PLAN
+--------------------------------------------------------------------------------------
+ Nested Loop Left Join
+ -> Values Scan on "*VALUES*"
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Index Only Scan using tenk1_thous_tenthous on tenk1
+ Index Cond: (thousand > 99)
+(8 rows)
+
+select * from
+ (select count(*) from tenk1 where thousand > 99) ss
+ right join (values (1),(2),(3)) v(x) on true;
+ count | x
+-------+---
+ 9000 | 1
+ 9000 | 2
+ 9000 | 3
+(3 rows)
+
+-- test rescans for a Limit node with a parallel node beneath it.
+reset enable_seqscan;
+set enable_indexonlyscan to off;
+set enable_indexscan to off;
+alter table tenk1 set (parallel_workers = 0);
+alter table tenk2 set (parallel_workers = 1);
+explain (costs off)
+select count(*) from tenk1
+ left join (select tenk2.unique1 from tenk2 order by 1 limit 1000) ss
+ on tenk1.unique1 < ss.unique1 + 1
+ where tenk1.unique1 < 2;
+ QUERY PLAN
+------------------------------------------------------------
+ Aggregate
+ -> Nested Loop Left Join
+ Join Filter: (tenk1.unique1 < (tenk2.unique1 + 1))
+ -> Seq Scan on tenk1
+ Filter: (unique1 < 2)
+ -> Limit
+ -> Gather Merge
+ Workers Planned: 1
+ -> Sort
+ Sort Key: tenk2.unique1
+ -> Parallel Seq Scan on tenk2
+(11 rows)
+
+select count(*) from tenk1
+ left join (select tenk2.unique1 from tenk2 order by 1 limit 1000) ss
+ on tenk1.unique1 < ss.unique1 + 1
+ where tenk1.unique1 < 2;
+ count
+-------
+ 1999
+(1 row)
+
+--reset the value of workers for each table as it was before this test.
+alter table tenk1 set (parallel_workers = 4);
+alter table tenk2 reset (parallel_workers);
+reset enable_material;
+reset enable_bitmapscan;
+reset enable_indexonlyscan;
+reset enable_indexscan;
+-- test parallel bitmap heap scan.
+set enable_seqscan to off;
+set enable_indexscan to off;
+set enable_hashjoin to off;
+set enable_mergejoin to off;
+set enable_material to off;
+-- test prefetching, if the platform allows it
+DO $$
+BEGIN
+ SET effective_io_concurrency = 50;
+EXCEPTION WHEN invalid_parameter_value THEN
+END $$;
+set work_mem='64kB'; --set small work mem to force lossy pages
+explain (costs off)
+ select count(*) from tenk1, tenk2 where tenk1.hundred > 1 and tenk2.thousand=0;
+ QUERY PLAN
+------------------------------------------------------------
+ Aggregate
+ -> Nested Loop
+ -> Seq Scan on tenk2
+ Filter: (thousand = 0)
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Bitmap Heap Scan on tenk1
+ Recheck Cond: (hundred > 1)
+ -> Bitmap Index Scan on tenk1_hundred
+ Index Cond: (hundred > 1)
+(10 rows)
+
+select count(*) from tenk1, tenk2 where tenk1.hundred > 1 and tenk2.thousand=0;
+ count
+-------
+ 98000
+(1 row)
+
+create table bmscantest (a int, t text);
+insert into bmscantest select r, 'fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo' FROM generate_series(1,100000) r;
+create index i_bmtest ON bmscantest(a);
+select count(*) from bmscantest where a>1;
+ count
+-------
+ 99999
+(1 row)
+
+-- test accumulation of stats for parallel nodes
+reset enable_seqscan;
+alter table tenk2 set (parallel_workers = 0);
+explain (analyze, timing off, summary off, costs off)
+ select count(*) from tenk1, tenk2 where tenk1.hundred > 1
+ and tenk2.thousand=0;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Aggregate (actual rows=1 loops=1)
+ -> Nested Loop (actual rows=98000 loops=1)
+ -> Seq Scan on tenk2 (actual rows=10 loops=1)
+ Filter: (thousand = 0)
+ Rows Removed by Filter: 9990
+ -> Gather (actual rows=9800 loops=10)
+ Workers Planned: 4
+ Workers Launched: 4
+ -> Parallel Seq Scan on tenk1 (actual rows=1960 loops=50)
+ Filter: (hundred > 1)
+ Rows Removed by Filter: 40
+(11 rows)
+
+alter table tenk2 reset (parallel_workers);
+reset work_mem;
+create function explain_parallel_sort_stats() returns setof text
+language plpgsql as
+$$
+declare ln text;
+begin
+ for ln in
+ explain (analyze, timing off, summary off, costs off)
+ select * from
+ (select ten from tenk1 where ten < 100 order by ten) ss
+ right join (values (1),(2),(3)) v(x) on true
+ loop
+ ln := regexp_replace(ln, 'Memory: \S*', 'Memory: xxx');
+ return next ln;
+ end loop;
+end;
+$$;
+select * from explain_parallel_sort_stats();
+ explain_parallel_sort_stats
+--------------------------------------------------------------------------
+ Nested Loop Left Join (actual rows=30000 loops=1)
+ -> Values Scan on "*VALUES*" (actual rows=3 loops=1)
+ -> Gather Merge (actual rows=10000 loops=3)
+ Workers Planned: 4
+ Workers Launched: 4
+ -> Sort (actual rows=2000 loops=15)
+ Sort Key: tenk1.ten
+ Sort Method: quicksort Memory: xxx
+ Worker 0: Sort Method: quicksort Memory: xxx
+ Worker 1: Sort Method: quicksort Memory: xxx
+ Worker 2: Sort Method: quicksort Memory: xxx
+ Worker 3: Sort Method: quicksort Memory: xxx
+ -> Parallel Seq Scan on tenk1 (actual rows=2000 loops=15)
+ Filter: (ten < 100)
+(14 rows)
+
+reset enable_indexscan;
+reset enable_hashjoin;
+reset enable_mergejoin;
+reset enable_material;
+reset effective_io_concurrency;
+drop table bmscantest;
+drop function explain_parallel_sort_stats();
+-- 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;
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Merge Join
+ Merge Cond: (tenk1.unique1 = tenk2.unique1)
+ -> Parallel Index Only Scan using tenk1_unique1 on tenk1
+ -> Index Only Scan using tenk2_unique1 on tenk2
+(8 rows)
+
+select count(*) from tenk1, tenk2 where tenk1.unique1 = tenk2.unique1;
+ count
+-------
+ 10000
+(1 row)
+
+reset enable_hashjoin;
+reset enable_nestloop;
+-- test gather merge
+set enable_hashagg = false;
+explain (costs off)
+ select count(*) from tenk1 group by twenty;
+ QUERY PLAN
+----------------------------------------------------
+ Finalize GroupAggregate
+ Group Key: twenty
+ -> Gather Merge
+ Workers Planned: 4
+ -> Partial GroupAggregate
+ Group Key: twenty
+ -> Sort
+ Sort Key: twenty
+ -> Parallel Seq Scan on tenk1
+(9 rows)
+
+select count(*) from tenk1 group by twenty;
+ count
+-------
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+(20 rows)
+
+--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;
+ QUERY PLAN
+-----------------------------------------------------
+ Gather Merge
+ Output: ten, (sp_simple_func(ten))
+ Workers Planned: 4
+ -> Result
+ Output: ten, sp_simple_func(ten)
+ -> Sort
+ Output: ten
+ Sort Key: tenk1.ten
+ -> Parallel Seq Scan on public.tenk1
+ Output: ten
+ Filter: (tenk1.ten < 100)
+(11 rows)
+
+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;
+ QUERY PLAN
+----------------------------------------------------------
+ ProjectSet
+ -> Finalize GroupAggregate
+ Group Key: twenty
+ -> Gather Merge
+ Workers Planned: 4
+ -> Partial GroupAggregate
+ Group Key: twenty
+ -> Sort
+ Sort Key: twenty
+ -> Parallel Seq Scan on tenk1
+(10 rows)
+
+select count(*), generate_series(1,2) from tenk1 group by twenty;
+ count | generate_series
+-------+-----------------
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+(40 rows)
+
+-- test gather merge with parallel leader participation disabled
+set parallel_leader_participation = off;
+explain (costs off)
+ select count(*) from tenk1 group by twenty;
+ QUERY PLAN
+----------------------------------------------------
+ Finalize GroupAggregate
+ Group Key: twenty
+ -> Gather Merge
+ Workers Planned: 4
+ -> Partial GroupAggregate
+ Group Key: twenty
+ -> Sort
+ Sort Key: twenty
+ -> Parallel Seq Scan on tenk1
+(9 rows)
+
+select count(*) from tenk1 group by twenty;
+ count
+-------
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+ 500
+(20 rows)
+
+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;
+ QUERY PLAN
+----------------------------------------------------------
+ Nested Loop Left Join
+ -> Values Scan on "*VALUES*"
+ -> Finalize GroupAggregate
+ Group Key: tenk1.string4
+ -> Gather Merge
+ Workers Planned: 4
+ -> Partial GroupAggregate
+ Group Key: tenk1.string4
+ -> Sort
+ Sort Key: tenk1.string4
+ -> Parallel Seq Scan on tenk1
+(11 rows)
+
+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;
+ string4 | count | x
+---------+-------+---
+ AAAAxx | 2500 | 1
+ HHHHxx | 2500 | 1
+ OOOOxx | 2500 | 1
+ VVVVxx | 2500 | 1
+ AAAAxx | 2500 | 2
+ HHHHxx | 2500 | 2
+ OOOOxx | 2500 | 2
+ VVVVxx | 2500 | 2
+ AAAAxx | 2500 | 3
+ HHHHxx | 2500 | 3
+ OOOOxx | 2500 | 3
+ VVVVxx | 2500 | 3
+(12 rows)
+
+reset enable_material;
+reset enable_hashagg;
+-- check parallelized int8 aggregate (bug #14897)
+explain (costs off)
+select avg(unique1::int8) from tenk1;
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Index Only Scan using tenk1_unique1 on tenk1
+(5 rows)
+
+select avg(unique1::int8) from tenk1;
+ avg
+-----------------------
+ 4999.5000000000000000
+(1 row)
+
+-- gather merge test with a LIMIT
+explain (costs off)
+ select fivethous from tenk1 order by fivethous limit 4;
+ QUERY PLAN
+----------------------------------------------
+ Limit
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: fivethous
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+select fivethous from tenk1 order by fivethous limit 4;
+ fivethous
+-----------
+ 0
+ 0
+ 1
+ 1
+(4 rows)
+
+-- gather merge test with 0 worker
+set max_parallel_workers = 0;
+explain (costs off)
+ select string4 from tenk1 order by string4 limit 5;
+ QUERY PLAN
+----------------------------------------------
+ Limit
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: string4
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+select string4 from tenk1 order by string4 limit 5;
+ string4
+---------
+ AAAAxx
+ AAAAxx
+ AAAAxx
+ AAAAxx
+ AAAAxx
+(5 rows)
+
+-- 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;
+ QUERY PLAN
+----------------------------------------------
+ Limit
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: string4
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+select string4 from tenk1 order by string4 limit 5;
+ string4
+---------
+ AAAAxx
+ AAAAxx
+ AAAAxx
+ AAAAxx
+ AAAAxx
+(5 rows)
+
+reset parallel_leader_participation;
+reset max_parallel_workers;
+SAVEPOINT settings;
+SET LOCAL force_parallel_mode = 1;
+explain (costs off)
+ select stringu1::int2 from tenk1 where unique1 = 1;
+ QUERY PLAN
+-----------------------------------------------
+ Gather
+ Workers Planned: 1
+ Single Copy: true
+ -> Index Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = 1)
+(5 rows)
+
+ROLLBACK TO SAVEPOINT settings;
+-- 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;
+ make_record
+-------------
+ (1)
+ (1,2)
+ (1,2,3)
+ (1,2,3,4)
+ (1,2,3,4,5)
+(5 rows)
+
+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;
+NOTICE: role "regress_parallel_worker" does not exist, skipping
+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;
+ count
+-------
+ 10000
+(1 row)
+
+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);
+ QUERY PLAN
+----------------------------------------------------------------------------------------------
+ Aggregate
+ Output: count(*)
+ -> Hash Semi Join
+ Hash Cond: ((a.unique1 = b.unique1) AND (a.two = (row_number() OVER (?))))
+ -> Gather
+ Output: a.unique1, a.two
+ Workers Planned: 4
+ -> Parallel Seq Scan on public.tenk1 a
+ Output: a.unique1, a.two
+ -> Hash
+ Output: b.unique1, (row_number() OVER (?))
+ -> WindowAgg
+ Output: b.unique1, row_number() OVER (?)
+ -> Gather
+ Output: b.unique1
+ Workers Planned: 4
+ -> Parallel Index Only Scan using tenk1_unique1 on public.tenk1 b
+ Output: b.unique1
+(18 rows)
+
+-- 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);
+ QUERY PLAN
+---------------------------------------------------------------
+ Hash Semi Join
+ Hash Cond: (a.two = b.two)
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1 a
+ -> Hash
+ -> Limit
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1 b
+ Filter: (stringu1 ~~ '%AAAA'::text)
+(11 rows)
+
+-- 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;
+ QUERY PLAN
+-------------------------------------------------------------
+ Gather (actual rows=10000 loops=1)
+ Workers Planned: 4
+ Workers Launched: 4
+ -> Parallel Seq Scan on tenk1 (actual rows=2000 loops=5)
+(4 rows)
+
+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;
+ERROR: invalid input syntax for type smallint: "BAAAAAabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"
+CONTEXT: parallel worker
+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;
+ QUERY PLAN
+----------------------------------------------------
+ Gather
+ Workers Planned: 4
+ -> Parallel Append
+ -> Parallel Seq Scan on tenk1
+ Filter: (fivethous = (tenthous + 1))
+ -> Parallel Seq Scan on tenk1 tenk1_1
+ Filter: (fivethous = (tenthous + 1))
+(7 rows)
+
+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;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Sort
+ Sort Key: tenk1.unique1
+ -> Append
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $1
+ InitPlan 1 (returns $1)
+ -> Limit
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1 tenk1_2
+ Filter: (fivethous = 1)
+ -> Parallel Seq Scan on tenk1
+ Filter: (fivethous = $1)
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $3
+ InitPlan 2 (returns $3)
+ -> Limit
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1 tenk1_3
+ Filter: (fivethous = 1)
+ -> Parallel Seq Scan on tenk1 tenk1_1
+ Filter: (fivethous = $3)
+(25 rows)
+
+-- test interaction with SRFs
+SELECT * FROM information_schema.foreign_data_wrapper_options
+ORDER BY 1, 2, 3;
+ foreign_data_wrapper_catalog | foreign_data_wrapper_name | option_name | option_value
+------------------------------+---------------------------+-------------+--------------
+(0 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT generate_series(1, two), array(select generate_series(1, two))
+ FROM tenk1 ORDER BY tenthous;
+ QUERY PLAN
+----------------------------------------------------------------------
+ ProjectSet
+ Output: generate_series(1, tenk1.two), (SubPlan 1), tenk1.tenthous
+ -> Gather Merge
+ Output: tenk1.two, tenk1.tenthous
+ Workers Planned: 4
+ -> Result
+ Output: tenk1.two, tenk1.tenthous
+ -> Sort
+ Output: tenk1.tenthous, tenk1.two
+ Sort Key: tenk1.tenthous
+ -> Parallel Seq Scan on public.tenk1
+ Output: tenk1.tenthous, tenk1.two
+ SubPlan 1
+ -> ProjectSet
+ Output: generate_series(1, tenk1.two)
+ -> Result
+(16 rows)
+
+-- must disallow pushing sort below gather when pathkey contains an SRF
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT unnest(ARRAY[]::integer[]) + 1 AS pathkey
+ FROM tenk1 t1 JOIN tenk1 t2 ON TRUE
+ ORDER BY pathkey;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------
+ Sort
+ Output: (((unnest('{}'::integer[])) + 1))
+ Sort Key: (((unnest('{}'::integer[])) + 1))
+ -> Result
+ Output: ((unnest('{}'::integer[])) + 1)
+ -> ProjectSet
+ Output: unnest('{}'::integer[])
+ -> Nested Loop
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Index Only Scan using tenk1_hundred on public.tenk1 t1
+ -> Materialize
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Index Only Scan using tenk1_hundred on public.tenk1 t2
+(15 rows)
+
+-- 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));
+ QUERY PLAN
+------------------------------------------------------------------
+ Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on fooarr
+ Filter: ((f1 = '1'::text) AND (f2 = '{1,2}'::integer[]))
+(4 rows)
+
+EXECUTE pstmt('1', make_some_array(1,2));
+ f1 | f2 | f3
+----+-------+-----
+ 1 | {1,2} | one
+(1 row)
+
+DEALLOCATE pstmt;
+-- test interaction between subquery and partial_paths
+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;
+ QUERY PLAN
+-------------------------------------------------------------------
+ Subquery Scan on tenk1_vw_sec
+ Filter: ((SubPlan 1) < 100)
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Index Only Scan using tenk1_unique1 on tenk1
+ SubPlan 1
+ -> Aggregate
+ -> Seq Scan on int4_tbl
+ Filter: (f1 < tenk1_vw_sec.unique1)
+(9 rows)
+
+rollback;
diff --git a/src/test/regress/expected/select_views.out b/src/test/regress/expected/select_views.out
new file mode 100644
index 0000000..1aeed84
--- /dev/null
+++ b/src/test/regress/expected/select_views.out
@@ -0,0 +1,1552 @@
+--
+-- SELECT_VIEWS
+-- test the views defined in CREATE_VIEWS
+--
+SELECT * FROM street;
+ name | thepath | cname
+------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------
+ Access Rd 25 | [(-121.9283,37.894),(-121.9283,37.9)] | Oakland
+ Ada St | [(-122.2487,37.398),(-122.2496,37.401)] | Lafayette
+ Agua Fria Creek | [(-121.9254,37.922),(-121.9281,37.889)] | Oakland
+ Allen Ct | [(-122.0131,37.602),(-122.0117,37.597)] | Berkeley
+ Alvarado Niles Road | [(-122.0325,37.903),(-122.0316,37.9)] | Berkeley
+ Andrea Cir | [(-121.733218,37.88641),(-121.733286,37.90617)] | Oakland
+ Apricot Lane | [(-121.9471,37.401),(-121.9456,37.392)] | Oakland
+ Apricot Lane | [(-121.9471,37.401),(-121.9456,37.392)] | Oakland
+ Arden Road | [(-122.0978,37.177),(-122.1,37.177)] | Oakland
+ Arizona St | [(-122.0381,37.901),(-122.0367,37.898)] | Berkeley
+ Arlington Dr | [(-121.8802,37.408),(-121.8807,37.394)] | Oakland
+ Arlington Dr | [(-121.8802,37.408),(-121.8807,37.394)] | Oakland
+ Arlington Road | [(-121.7957,37.898),(-121.7956,37.906)] | Oakland
+ Arroyo Las Positas | [(-121.7973,37.997),(-121.7957,37.005)] | Oakland
+ Arroyo Las Positas | [(-121.7973,37.997),(-121.7957,37.005)] | Oakland
+ Arroyo Seco | [(-121.7073,37.766),(-121.6997,37.729)] | Oakland
+ Ash St | [(-122.0408,37.31),(-122.04,37.292)] | Oakland
+ Avenue 134th | [(-122.1823,37.002),(-122.1851,37.992)] | Oakland
+ Avenue 134th | [(-122.1823,37.002),(-122.1851,37.992)] | Berkeley
+ Avenue 140th | [(-122.1656,37.003),(-122.1691,37.988)] | Oakland
+ Avenue 140th | [(-122.1656,37.003),(-122.1691,37.988)] | Berkeley
+ Avenue D | [(-122.298,37.848),(-122.3024,37.849)] | Berkeley
+ B St | [(-122.1749,37.451),(-122.1743,37.443)] | Oakland
+ Bancroft Ave | [(-122.15714,37.4242),(-122.156,37.409)] | Oakland
+ Bancroft Ave | [(-122.1643,37.523),(-122.1631,37.508),(-122.1621,37.493)] | Oakland
+ Birch St | [(-122.1617,37.425),(-122.1614,37.417)] | Oakland
+ Birch St | [(-122.1673,37.509),(-122.1661,37.492)] | Oakland
+ Blacow Road | [(-122.0179,37.469),(-122.0167,37.465)] | Oakland
+ Bridgepointe Dr | [(-122.0514,37.305),(-122.0509,37.299)] | Oakland
+ Broadmore Ave | [(-122.095,37.522),(-122.0936,37.497)] | Oakland
+ Broadway | [(-122.2409,37.586),(-122.2395,37.601)] | Berkeley
+ Buckingham Blvd | [(-122.2231,37.59),(-122.2214,37.606)] | Berkeley
+ Butterfield Dr | [(-122.0838,37.002),(-122.0834,37.987)] | Oakland
+ Butterfield Dr | [(-122.0838,37.002),(-122.0834,37.987)] | Oakland
+ Butterfield Dr | [(-122.0838,37.002),(-122.0834,37.987)] | Berkeley
+ C St | [(-122.1768,37.46),(-122.1749,37.435)] | Oakland
+ Calaveras Creek | [(-121.8203,37.035),(-121.8207,37.931)] | Oakland
+ Calaveras Creek | [(-121.8203,37.035),(-121.8207,37.931)] | Oakland
+ California St | [(-122.2032,37.005),(-122.2016,37.996)] | Berkeley
+ California St | [(-122.2032,37.005),(-122.2016,37.996)] | Lafayette
+ Cameron Ave | [(-122.1316,37.502),(-122.1327,37.481)] | Oakland
+ Campus Dr | [(-122.1704,37.905),(-122.1678,37.868),(-122.1671,37.865)] | Berkeley
+ Capricorn Ave | [(-122.2176,37.404),(-122.2164,37.384)] | Lafayette
+ Carson St | [(-122.1846,37.9),(-122.1843,37.901)] | Berkeley
+ Cedar Blvd | [(-122.0282,37.446),(-122.0265,37.43)] | Oakland
+ Cedar St | [(-122.3011,37.737),(-122.2999,37.739)] | Berkeley
+ Celia St | [(-122.0611,37.3),(-122.0616,37.299)] | Oakland
+ Central Ave | [(-122.2343,37.602),(-122.2331,37.595)] | Berkeley
+ Chambers Dr | [(-122.2004,37.352),(-122.1972,37.368)] | Lafayette
+ Chambers Lane | [(-122.2001,37.359),(-122.1975,37.371)] | Lafayette
+ Champion St | [(-122.214,37.991),(-122.2147,37.002)] | Berkeley
+ Champion St | [(-122.214,37.991),(-122.2147,37.002)] | Lafayette
+ Chapman Dr | [(-122.0421,37.504),(-122.0414,37.498)] | Oakland
+ Charles St | [(-122.0255,37.505),(-122.0252,37.499)] | Oakland
+ Cherry St | [(-122.0437,37.42),(-122.0434,37.413)] | Oakland
+ Claremont Pl | [(-122.0542,37.995),(-122.0542,37.008)] | Oakland
+ Claremont Pl | [(-122.0542,37.995),(-122.0542,37.008)] | Oakland
+ Claremont Pl | [(-122.0542,37.995),(-122.0542,37.008)] | Berkeley
+ Coliseum Way | [(-122.2001,37.47),(-122.1978,37.516)] | Oakland
+ Coliseum Way | [(-122.2113,37.626),(-122.2085,37.592),(-122.2063,37.568)] | Berkeley
+ Coolidge Ave | [(-122.2007,37.058),(-122.1992,37.06)] | Lafayette
+ Cornell Ave | [(-122.2956,37.925),(-122.2949,37.906),(-122.2939,37.875)] | Berkeley
+ Corriea Way | [(-121.9501,37.402),(-121.9505,37.398)] | Oakland
+ Corriea Way | [(-121.9501,37.402),(-121.9505,37.398)] | Oakland
+ Cowing Road | [(-122.0002,37.934),(-121.9772,37.782)] | Oakland
+ Creston Road | [(-122.2639,37.002),(-122.2613,37.986),(-122.2602,37.978),(-122.2598,37.973)] | Berkeley
+ Creston Road | [(-122.2639,37.002),(-122.2613,37.986),(-122.2602,37.978),(-122.2598,37.973)] | Lafayette
+ Crow Canyon Creek | [(-122.043,37.905),(-122.0368,37.71)] | Berkeley
+ Crystaline Dr | [(-121.925856,37),(-121.925869,37.00527)] | Oakland
+ Cull Canyon Road | [(-122.0536,37.435),(-122.0499,37.315)] | Oakland
+ Cull Creek | [(-122.0624,37.875),(-122.0582,37.527)] | Berkeley
+ D St | [(-122.1811,37.505),(-122.1805,37.497)] | Oakland
+ Decoto Road | [(-122.0159,37.006),(-122.016,37.002),(-122.0164,37.993)] | Oakland
+ Decoto Road | [(-122.0159,37.006),(-122.016,37.002),(-122.0164,37.993)] | Oakland
+ Decoto Road | [(-122.0159,37.006),(-122.016,37.002),(-122.0164,37.993)] | Berkeley
+ Deering St | [(-122.2146,37.904),(-122.2126,37.897)] | Berkeley
+ Dimond Ave | [(-122.2167,37.994),(-122.2162,37.006)] | Berkeley
+ Dimond Ave | [(-122.2167,37.994),(-122.2162,37.006)] | Lafayette
+ Donna Way | [(-122.1333,37.606),(-122.1316,37.599)] | Berkeley
+ Driftwood Dr | [(-122.0109,37.482),(-122.0113,37.477)] | Oakland
+ Driscoll Road | [(-121.9482,37.403),(-121.948451,37.39995)] | Oakland
+ Driscoll Road | [(-121.9482,37.403),(-121.948451,37.39995)] | Oakland
+ E St | [(-122.1832,37.505),(-122.1826,37.498),(-122.182,37.49)] | Oakland
+ Eden Ave | [(-122.1143,37.505),(-122.1142,37.491)] | Oakland
+ Eden Creek | [(-122.022037,37.00675),(-122.0221,37.998)] | Oakland
+ Eden Creek | [(-122.022037,37.00675),(-122.0221,37.998)] | Oakland
+ Eden Creek | [(-122.022037,37.00675),(-122.0221,37.998)] | Berkeley
+ Edgewater Dr | [(-122.201,37.379),(-122.2042,37.41)] | Lafayette
+ Enos Way | [(-121.7677,37.896),(-121.7673,37.91)] | Oakland
+ Euclid Ave | [(-122.2671,37.009),(-122.2666,37.987)] | Berkeley
+ Euclid Ave | [(-122.2671,37.009),(-122.2666,37.987)] | Lafayette
+ Fairview Ave | [(-121.999,37.428),(-121.9863,37.351)] | Oakland
+ Fairview Ave | [(-121.999,37.428),(-121.9863,37.351)] | Oakland
+ Foothill Blvd | [(-122.2414,37.9),(-122.2403,37.893)] | Berkeley
+ Fountain St | [(-122.2306,37.593),(-122.2293,37.605)] | Berkeley
+ Gading Road | [(-122.0801,37.343),(-122.08,37.336)] | Oakland
+ Grizzly Peak Blvd | [(-122.2213,37.638),(-122.2127,37.581)] | Berkeley
+ Grove Way | [(-122.0643,37.884),(-122.062679,37.89162),(-122.061796,37.89578),(-122.0609,37.9)] | Berkeley
+ Harris Road | [(-122.0659,37.372),(-122.0675,37.363)] | Oakland
+ Heartwood Dr | [(-122.2006,37.341),(-122.1992,37.338)] | Lafayette
+ Hegenberger Exwy | [(-122.1946,37.52),(-122.1947,37.497)] | Oakland
+ Herrier St | [(-122.1943,37.006),(-122.1936,37.998)] | Oakland
+ Herrier St | [(-122.1943,37.006),(-122.1936,37.998)] | Berkeley
+ Hesperian Blvd | [(-122.097,37.333),(-122.0956,37.31),(-122.0946,37.293)] | Oakland
+ Hesperian Blvd | [(-122.097,37.333),(-122.0956,37.31),(-122.0946,37.293)] | Oakland
+ Hesperian Blvd | [(-122.1132,37.6),(-122.1123,37.586)] | Berkeley
+ Hollis St | [(-122.2885,37.397),(-122.289,37.414)] | Lafayette
+ I- 580 | [(-121.727,37.074),(-121.7229,37.093),(-121.722301,37.09522),(-121.721001,37.10005),(-121.7194,37.106),(-121.7188,37.109),(-121.7168,37.12),(-121.7163,37.123),(-121.7145,37.127),(-121.7096,37.148),(-121.707731,37.1568),(-121.7058,37.166),(-121.7055,37.168),(-121.7044,37.174),(-121.7038,37.172),(-121.7037,37.172),(-121.7027,37.175),(-121.7001,37.181),(-121.6957,37.191),(-121.6948,37.192),(-121.6897,37.204),(-121.6697,37.185)] | Oakland
+ I- 580 | [(-121.9322,37.989),(-121.9243,37.006),(-121.9217,37.014)] | Oakland
+ I- 580 | [(-121.9322,37.989),(-121.9243,37.006),(-121.9217,37.014)] | Oakland
+ I- 580 | [(-122.018,37.019),(-122.0009,37.032),(-121.9787,37.983),(-121.958,37.984),(-121.9571,37.986)] | Oakland
+ I- 580 | [(-122.018,37.019),(-122.0009,37.032),(-121.9787,37.983),(-121.958,37.984),(-121.9571,37.986)] | Oakland
+ I- 580 | [(-122.1108,37.023),(-122.1101,37.02),(-122.108103,37.00764),(-122.108,37.007),(-122.1069,37.998),(-122.1064,37.994),(-122.1053,37.982),(-122.1048,37.977),(-122.1032,37.958),(-122.1026,37.953),(-122.1013,37.938),(-122.0989,37.911),(-122.0984,37.91),(-122.098,37.908)] | Oakland
+ I- 580 | [(-122.1108,37.023),(-122.1101,37.02),(-122.108103,37.00764),(-122.108,37.007),(-122.1069,37.998),(-122.1064,37.994),(-122.1053,37.982),(-122.1048,37.977),(-122.1032,37.958),(-122.1026,37.953),(-122.1013,37.938),(-122.0989,37.911),(-122.0984,37.91),(-122.098,37.908)] | Berkeley
+ I- 580 | [(-122.1543,37.703),(-122.1535,37.694),(-122.1512,37.655),(-122.1475,37.603),(-122.1468,37.583),(-122.1472,37.569),(-122.149044,37.54874),(-122.1493,37.546),(-122.1501,37.532),(-122.1506,37.509),(-122.1495,37.482),(-122.1487,37.467),(-122.1477,37.447),(-122.1414,37.383),(-122.1404,37.376),(-122.1398,37.372),(-122.139,37.356),(-122.1388,37.353),(-122.1385,37.34),(-122.1382,37.33),(-122.1378,37.316)] | Oakland
+ I- 580 | [(-122.1543,37.703),(-122.1535,37.694),(-122.1512,37.655),(-122.1475,37.603),(-122.1468,37.583),(-122.1472,37.569),(-122.149044,37.54874),(-122.1493,37.546),(-122.1501,37.532),(-122.1506,37.509),(-122.1495,37.482),(-122.1487,37.467),(-122.1477,37.447),(-122.1414,37.383),(-122.1404,37.376),(-122.1398,37.372),(-122.139,37.356),(-122.1388,37.353),(-122.1385,37.34),(-122.1382,37.33),(-122.1378,37.316)] | Berkeley
+ I- 580 | [(-122.2197,37.99),(-122.22,37.99),(-122.222092,37.99523),(-122.2232,37.998),(-122.224146,37.99963),(-122.2261,37.003),(-122.2278,37.007),(-122.2302,37.026),(-122.2323,37.043),(-122.2344,37.059),(-122.235405,37.06427),(-122.2365,37.07)] | Berkeley
+ I- 580 | [(-122.2197,37.99),(-122.22,37.99),(-122.222092,37.99523),(-122.2232,37.998),(-122.224146,37.99963),(-122.2261,37.003),(-122.2278,37.007),(-122.2302,37.026),(-122.2323,37.043),(-122.2344,37.059),(-122.235405,37.06427),(-122.2365,37.07)] | Lafayette
+ I- 580 Ramp | [(-121.8521,37.011),(-121.8479,37.999),(-121.8476,37.999),(-121.8456,37.01),(-121.8455,37.011)] | Oakland
+ I- 580 Ramp | [(-121.8521,37.011),(-121.8479,37.999),(-121.8476,37.999),(-121.8456,37.01),(-121.8455,37.011)] | Oakland
+ I- 580 Ramp | [(-121.8743,37.014),(-121.8722,37.999),(-121.8714,37.999)] | Oakland
+ I- 580 Ramp | [(-121.8743,37.014),(-121.8722,37.999),(-121.8714,37.999)] | Oakland
+ I- 580 Ramp | [(-121.9043,37.998),(-121.9036,37.013),(-121.902632,37.0174),(-121.9025,37.018)] | Oakland
+ I- 580 Ramp | [(-121.9043,37.998),(-121.9036,37.013),(-121.902632,37.0174),(-121.9025,37.018)] | Oakland
+ I- 580 Ramp | [(-121.9368,37.986),(-121.936483,37.98832),(-121.9353,37.997),(-121.93504,37.00035),(-121.9346,37.006),(-121.933764,37.00031),(-121.9333,37.997),(-121.9322,37.989)] | Oakland
+ I- 580 Ramp | [(-121.9368,37.986),(-121.936483,37.98832),(-121.9353,37.997),(-121.93504,37.00035),(-121.9346,37.006),(-121.933764,37.00031),(-121.9333,37.997),(-121.9322,37.989)] | Oakland
+ I- 580 Ramp | [(-122.093241,37.90351),(-122.09364,37.89634),(-122.093788,37.89212)] | Berkeley
+ I- 580 Ramp | [(-122.0934,37.896),(-122.09257,37.89961),(-122.0911,37.906)] | Berkeley
+ I- 580 Ramp | [(-122.0941,37.897),(-122.0943,37.902)] | Berkeley
+ I- 580 Ramp | [(-122.096,37.888),(-122.0962,37.891),(-122.0964,37.9)] | Berkeley
+ I- 580 Ramp | [(-122.101,37.898),(-122.1005,37.902),(-122.0989,37.911)] | Berkeley
+ I- 580 Ramp | [(-122.1086,37.003),(-122.1068,37.993),(-122.1066,37.992),(-122.1053,37.982)] | Oakland
+ I- 580 Ramp | [(-122.1086,37.003),(-122.1068,37.993),(-122.1066,37.992),(-122.1053,37.982)] | Berkeley
+ I- 580 Ramp | [(-122.1414,37.383),(-122.1407,37.376),(-122.1403,37.372),(-122.139,37.356)] | Oakland
+ I- 580/I-680 Ramp | ((-121.9207,37.988),(-121.9192,37.016)) | Oakland
+ I- 580/I-680 Ramp | ((-121.9207,37.988),(-121.9192,37.016)) | Oakland
+ I- 680 | ((-121.939,37.15),(-121.9387,37.145),(-121.9373,37.125),(-121.934242,37.07643),(-121.933886,37.0709),(-121.9337,37.068),(-121.933122,37.06139),(-121.932736,37.05698),(-121.93222,37.05108),(-121.931844,37.04678),(-121.930113,37.027),(-121.926829,37),(-121.9265,37.998),(-121.9217,37.96),(-121.9203,37.949),(-121.9184,37.934)) | Oakland
+ I- 680 | ((-121.939,37.15),(-121.9387,37.145),(-121.9373,37.125),(-121.934242,37.07643),(-121.933886,37.0709),(-121.9337,37.068),(-121.933122,37.06139),(-121.932736,37.05698),(-121.93222,37.05108),(-121.931844,37.04678),(-121.930113,37.027),(-121.926829,37),(-121.9265,37.998),(-121.9217,37.96),(-121.9203,37.949),(-121.9184,37.934)) | Oakland
+ I- 680 | [(-121.9101,37.715),(-121.911269,37.74682),(-121.9119,37.764),(-121.9124,37.776),(-121.9174,37.905),(-121.9194,37.957),(-121.9207,37.988)] | Oakland
+ I- 680 | [(-121.9184,37.934),(-121.917,37.913),(-121.9122,37.83),(-121.9052,37.702)] | Oakland
+ I- 680 Ramp | [(-121.8833,37.376),(-121.8833,37.392),(-121.883,37.4),(-121.8835,37.402),(-121.8852,37.422)] | Oakland
+ I- 680 Ramp | [(-121.8833,37.376),(-121.8833,37.392),(-121.883,37.4),(-121.8835,37.402),(-121.8852,37.422)] | Oakland
+ I- 680 Ramp | [(-121.92,37.438),(-121.9218,37.424),(-121.9238,37.408),(-121.9252,37.392)] | Oakland
+ I- 680 Ramp | [(-121.92,37.438),(-121.9218,37.424),(-121.9238,37.408),(-121.9252,37.392)] | Oakland
+ I- 680 Ramp | [(-121.9238,37.402),(-121.9234,37.395),(-121.923,37.399)] | Oakland
+ I- 680 Ramp | [(-121.9238,37.402),(-121.9234,37.395),(-121.923,37.399)] | Oakland
+ I- 80 | ((-122.2937,37.277),(-122.3016,37.262)) | Lafayette
+ I- 80 | ((-122.2962,37.273),(-122.3004,37.264)) | Lafayette
+ I- 80 Ramp | [(-122.2962,37.413),(-122.2959,37.382),(-122.2951,37.372)] | Lafayette
+ I- 880 | ((-121.9669,37.075),(-121.9663,37.071),(-121.9656,37.065),(-121.9618,37.037),(-121.95689,37),(-121.948,37.933)) | Oakland
+ I- 880 | ((-121.9669,37.075),(-121.9663,37.071),(-121.9656,37.065),(-121.9618,37.037),(-121.95689,37),(-121.948,37.933)) | Oakland
+ I- 880 | [(-121.948,37.933),(-121.9471,37.925),(-121.9467,37.923),(-121.946,37.918),(-121.9452,37.912),(-121.937,37.852)] | Oakland
+ I- 880 | [(-122.0219,37.466),(-122.0205,37.447),(-122.020331,37.44447),(-122.020008,37.43962),(-122.0195,37.432),(-122.0193,37.429),(-122.0164,37.393),(-122.010219,37.34771),(-122.0041,37.313)] | Oakland
+ I- 880 | [(-122.0375,37.632),(-122.0359,37.619),(-122.0358,37.616),(-122.034514,37.60409),(-122.031876,37.57965),(-122.031193,37.57332),(-122.03016,37.56375),(-122.02943,37.55698),(-122.028689,37.54929),(-122.027833,37.53908),(-122.025979,37.51698),(-122.0238,37.491)] | Oakland
+ I- 880 | [(-122.0375,37.632),(-122.0359,37.619),(-122.0358,37.616),(-122.034514,37.60409),(-122.031876,37.57965),(-122.031193,37.57332),(-122.03016,37.56375),(-122.02943,37.55698),(-122.028689,37.54929),(-122.027833,37.53908),(-122.025979,37.51698),(-122.0238,37.491)] | Berkeley
+ I- 880 | [(-122.0612,37.003),(-122.0604,37.991),(-122.0596,37.982),(-122.0585,37.967),(-122.0583,37.961),(-122.0553,37.918),(-122.053635,37.89475),(-122.050759,37.8546),(-122.05,37.844),(-122.0485,37.817),(-122.0483,37.813),(-122.0482,37.811)] | Oakland
+ I- 880 | [(-122.0612,37.003),(-122.0604,37.991),(-122.0596,37.982),(-122.0585,37.967),(-122.0583,37.961),(-122.0553,37.918),(-122.053635,37.89475),(-122.050759,37.8546),(-122.05,37.844),(-122.0485,37.817),(-122.0483,37.813),(-122.0482,37.811)] | Oakland
+ I- 880 | [(-122.0612,37.003),(-122.0604,37.991),(-122.0596,37.982),(-122.0585,37.967),(-122.0583,37.961),(-122.0553,37.918),(-122.053635,37.89475),(-122.050759,37.8546),(-122.05,37.844),(-122.0485,37.817),(-122.0483,37.813),(-122.0482,37.811)] | Berkeley
+ I- 880 | [(-122.0831,37.312),(-122.0819,37.296),(-122.081,37.285),(-122.0786,37.248),(-122.078,37.24),(-122.077642,37.23496),(-122.076983,37.22567),(-122.076599,37.22026),(-122.076229,37.21505),(-122.0758,37.209)] | Oakland
+ I- 880 | [(-122.0978,37.528),(-122.096,37.496),(-122.0931,37.453),(-122.09277,37.4496),(-122.090189,37.41442),(-122.0896,37.405),(-122.085,37.34)] | Oakland
+ I- 880 | [(-122.1365,37.902),(-122.1358,37.898),(-122.1333,37.881),(-122.1323,37.874),(-122.1311,37.866),(-122.1308,37.865),(-122.1307,37.864),(-122.1289,37.851),(-122.1277,37.843),(-122.1264,37.834),(-122.1231,37.812),(-122.1165,37.766),(-122.1104,37.72),(-122.109695,37.71094),(-122.109,37.702),(-122.108312,37.69168),(-122.1076,37.681)] | Berkeley
+ I- 880 | [(-122.1755,37.185),(-122.1747,37.178),(-122.1742,37.173),(-122.1692,37.126),(-122.167792,37.11594),(-122.16757,37.11435),(-122.1671,37.111),(-122.1655,37.1),(-122.165169,37.09811),(-122.1641,37.092),(-122.1596,37.061),(-122.158381,37.05275),(-122.155991,37.03657),(-122.1531,37.017),(-122.1478,37.98),(-122.1407,37.932),(-122.1394,37.924),(-122.1389,37.92),(-122.1376,37.91)] | Oakland
+ I- 880 | [(-122.1755,37.185),(-122.1747,37.178),(-122.1742,37.173),(-122.1692,37.126),(-122.167792,37.11594),(-122.16757,37.11435),(-122.1671,37.111),(-122.1655,37.1),(-122.165169,37.09811),(-122.1641,37.092),(-122.1596,37.061),(-122.158381,37.05275),(-122.155991,37.03657),(-122.1531,37.017),(-122.1478,37.98),(-122.1407,37.932),(-122.1394,37.924),(-122.1389,37.92),(-122.1376,37.91)] | Berkeley
+ I- 880 | [(-122.2214,37.711),(-122.2202,37.699),(-122.2199,37.695),(-122.219,37.682),(-122.2184,37.672),(-122.2173,37.652),(-122.2159,37.638),(-122.2144,37.616),(-122.2138,37.612),(-122.2135,37.609),(-122.212,37.592),(-122.2116,37.586),(-122.2111,37.581)] | Berkeley
+ I- 880 | [(-122.2707,37.975),(-122.2693,37.972),(-122.2681,37.966),(-122.267,37.962),(-122.2659,37.957),(-122.2648,37.952),(-122.2636,37.946),(-122.2625,37.935),(-122.2617,37.927),(-122.2607,37.921),(-122.2593,37.916),(-122.258,37.911),(-122.2536,37.898),(-122.2432,37.858),(-122.2408,37.845),(-122.2386,37.827),(-122.2374,37.811)] | Berkeley
+ I- 880 Ramp | [(-122.0019,37.301),(-122.002,37.293)] | Oakland
+ I- 880 Ramp | [(-122.0041,37.313),(-122.0018,37.315),(-122.0007,37.315),(-122.0005,37.313),(-122.0002,37.308),(-121.9995,37.289)] | Oakland
+ I- 880 Ramp | [(-122.0041,37.313),(-122.0038,37.308),(-122.0039,37.284),(-122.0013,37.287),(-121.9995,37.289)] | Oakland
+ I- 880 Ramp | [(-122.0236,37.488),(-122.0231,37.458),(-122.0227,37.458),(-122.0223,37.452),(-122.0205,37.447)] | Oakland
+ I- 880 Ramp | [(-122.0238,37.491),(-122.0215,37.483),(-122.0211,37.477),(-122.0205,37.447)] | Oakland
+ I- 880 Ramp | [(-122.059,37.982),(-122.0577,37.984),(-122.0612,37.003)] | Oakland
+ I- 880 Ramp | [(-122.059,37.982),(-122.0577,37.984),(-122.0612,37.003)] | Oakland
+ I- 880 Ramp | [(-122.059,37.982),(-122.0577,37.984),(-122.0612,37.003)] | Berkeley
+ I- 880 Ramp | [(-122.0618,37.011),(-122.0631,37.982),(-122.0585,37.967)] | Oakland
+ I- 880 Ramp | [(-122.0618,37.011),(-122.0631,37.982),(-122.0585,37.967)] | Oakland
+ I- 880 Ramp | [(-122.0618,37.011),(-122.0631,37.982),(-122.0585,37.967)] | Berkeley
+ I- 880 Ramp | [(-122.085,37.34),(-122.0801,37.316),(-122.081,37.285)] | Oakland
+ I- 880 Ramp | [(-122.085,37.34),(-122.0801,37.316),(-122.081,37.285)] | Oakland
+ I- 880 Ramp | [(-122.085,37.34),(-122.0866,37.316),(-122.0819,37.296)] | Oakland
+ I- 880 Ramp | [(-122.085,37.34),(-122.0866,37.316),(-122.0819,37.296)] | Oakland
+ I- 880 Ramp | [(-122.1029,37.61),(-122.1013,37.587),(-122.0999,37.569)] | Berkeley
+ I- 880 Ramp | [(-122.1379,37.891),(-122.1383,37.897),(-122.1377,37.902)] | Berkeley
+ I- 880 Ramp | [(-122.1379,37.931),(-122.137597,37.92736),(-122.1374,37.925),(-122.1373,37.924),(-122.1369,37.914),(-122.1358,37.905),(-122.1365,37.908),(-122.1358,37.898)] | Berkeley
+ I- 880 Ramp | [(-122.2536,37.898),(-122.254,37.902)] | Berkeley
+ I- 880 Ramp | [(-122.2771,37.002),(-122.278,37)] | Lafayette
+ Indian Way | [(-122.2066,37.398),(-122.2045,37.411)] | Lafayette
+ Jackson St | [(-122.0845,37.6),(-122.0842,37.606)] | Berkeley
+ Johnson Dr | [(-121.9145,37.901),(-121.915,37.877)] | Oakland
+ Joyce St | [(-122.0792,37.604),(-122.0774,37.581)] | Berkeley
+ Juniper St | [(-121.7823,37.897),(-121.7815,37.9)] | Oakland
+ Kaiser Dr | [(-122.067163,37.47821),(-122.060402,37.51961)] | Oakland
+ Keeler Ave | [(-122.2578,37.906),(-122.2579,37.899)] | Berkeley
+ Kildare Road | [(-122.0968,37.016),(-122.0959,37)] | Oakland
+ La Playa Dr | [(-122.1039,37.545),(-122.101,37.493)] | Oakland
+ Laguna Ave | [(-122.2099,37.989),(-122.2089,37)] | Berkeley
+ Laguna Ave | [(-122.2099,37.989),(-122.2089,37)] | Lafayette
+ Lakehurst Cir | [(-122.284729,37.89025),(-122.286096,37.90364)] | Berkeley
+ Lakeshore Ave | [(-122.2586,37.99),(-122.2556,37.006)] | Berkeley
+ Lakeshore Ave | [(-122.2586,37.99),(-122.2556,37.006)] | Lafayette
+ Las Positas Road | [(-121.764488,37.99199),(-121.75569,37.02022)] | Oakland
+ Las Positas Road | [(-121.764488,37.99199),(-121.75569,37.02022)] | Oakland
+ Linden St | [(-122.2867,37.998),(-122.2864,37.008)] | Berkeley
+ Linden St | [(-122.2867,37.998),(-122.2864,37.008)] | Lafayette
+ Livermore Ave | [(-121.7687,37.448),(-121.769,37.375)] | Oakland
+ Livermore Ave | [(-121.7687,37.448),(-121.769,37.375)] | Oakland
+ Livermore Ave | [(-121.772719,37.99085),(-121.7728,37.001)] | Oakland
+ Livermore Ave | [(-121.772719,37.99085),(-121.7728,37.001)] | Oakland
+ Locust St | [(-122.1606,37.007),(-122.1593,37.987)] | Oakland
+ Locust St | [(-122.1606,37.007),(-122.1593,37.987)] | Berkeley
+ Logan Ct | [(-122.0053,37.492),(-122.0061,37.484)] | Oakland
+ Magnolia St | [(-122.0971,37.5),(-122.0962,37.484)] | Oakland
+ Mandalay Road | [(-122.2322,37.397),(-122.2321,37.403)] | Lafayette
+ Marin Ave | [(-122.2741,37.894),(-122.272,37.901)] | Berkeley
+ Martin Luther King Jr Way | [(-122.2712,37.608),(-122.2711,37.599)] | Berkeley
+ Mattos Dr | [(-122.0005,37.502),(-122.000898,37.49683)] | Oakland
+ Maubert Ave | [(-122.1114,37.009),(-122.1096,37.995)] | Oakland
+ Maubert Ave | [(-122.1114,37.009),(-122.1096,37.995)] | Berkeley
+ McClure Ave | [(-122.1431,37.001),(-122.1436,37.998)] | Oakland
+ McClure Ave | [(-122.1431,37.001),(-122.1436,37.998)] | Berkeley
+ Medlar Dr | [(-122.0627,37.378),(-122.0625,37.375)] | Oakland
+ Mildred Ct | [(-122.0002,37.388),(-121.9998,37.386)] | Oakland
+ Miller Road | [(-122.0902,37.645),(-122.0865,37.545)] | Berkeley
+ Miramar Ave | [(-122.1009,37.025),(-122.099089,37.03209)] | Oakland
+ Mission Blvd | [(-121.918886,37),(-121.9194,37.976),(-121.9198,37.975)] | Oakland
+ Mission Blvd | [(-121.918886,37),(-121.9194,37.976),(-121.9198,37.975)] | Oakland
+ Mission Blvd | [(-122.0006,37.896),(-121.9989,37.88)] | Oakland
+ Mission Blvd | [(-122.0006,37.896),(-121.9989,37.88)] | Berkeley
+ Moores Ave | [(-122.0087,37.301),(-122.0094,37.292)] | Oakland
+ National Ave | [(-122.1192,37.5),(-122.1281,37.489)] | Oakland
+ Navajo Ct | [(-121.8779,37.901),(-121.8783,37.9)] | Oakland
+ Newark Blvd | [(-122.0352,37.438),(-122.0341,37.423)] | Oakland
+ Oakland Inner Harbor | [(-122.2625,37.913),(-122.260016,37.89484)] | Berkeley
+ Oakridge Road | [(-121.8316,37.049),(-121.828382,37)] | Oakland
+ Oneil Ave | [(-122.076754,37.62476),(-122.0745,37.595)] | Berkeley
+ Parkridge Dr | [(-122.1438,37.884),(-122.1428,37.9)] | Berkeley
+ Parkside Dr | [(-122.0475,37.603),(-122.0443,37.596)] | Berkeley
+ Paseo Padre Pkwy | [(-121.9143,37.005),(-121.913522,37)] | Oakland
+ Paseo Padre Pkwy | [(-122.0021,37.639),(-121.996,37.628)] | Oakland
+ Paseo Padre Pkwy | [(-122.0021,37.639),(-121.996,37.628)] | Berkeley
+ Pearl St | [(-122.2383,37.594),(-122.2366,37.615)] | Berkeley
+ Periwinkle Road | [(-122.0451,37.301),(-122.044758,37.29844)] | Oakland
+ Pimlico Dr | [(-121.8616,37.998),(-121.8618,37.008)] | Oakland
+ Pimlico Dr | [(-121.8616,37.998),(-121.8618,37.008)] | Oakland
+ Portsmouth Ave | [(-122.1064,37.315),(-122.1064,37.308)] | Oakland
+ Proctor Ave | [(-122.2267,37.406),(-122.2251,37.386)] | Lafayette
+ Railroad Ave | [(-122.0245,37.013),(-122.0234,37.003),(-122.0223,37.993)] | Oakland
+ Railroad Ave | [(-122.0245,37.013),(-122.0234,37.003),(-122.0223,37.993)] | Oakland
+ Railroad Ave | [(-122.0245,37.013),(-122.0234,37.003),(-122.0223,37.993)] | Berkeley
+ Ranspot Dr | [(-122.0972,37.999),(-122.0959,37)] | Oakland
+ Ranspot Dr | [(-122.0972,37.999),(-122.0959,37)] | Oakland
+ Ranspot Dr | [(-122.0972,37.999),(-122.0959,37)] | Berkeley
+ Redding St | [(-122.1978,37.901),(-122.1975,37.895)] | Berkeley
+ Redwood Road | [(-122.1493,37.98),(-122.1437,37.001)] | Oakland
+ Redwood Road | [(-122.1493,37.98),(-122.1437,37.001)] | Berkeley
+ Roca Dr | [(-122.0335,37.609),(-122.0314,37.599)] | Berkeley
+ Rosedale Ct | [(-121.9232,37.9),(-121.924,37.897)] | Oakland
+ Sacramento St | [(-122.2799,37.606),(-122.2797,37.597)] | Berkeley
+ Saddle Brook Dr | [(-122.1478,37.909),(-122.1454,37.904),(-122.1451,37.888)] | Berkeley
+ Saginaw Ct | [(-121.8803,37.898),(-121.8806,37.901)] | Oakland
+ San Andreas Dr | [(-122.0609,37.9),(-122.0614,37.895)] | Berkeley
+ Santa Maria Ave | [(-122.0773,37),(-122.0773,37.98)] | Oakland
+ Santa Maria Ave | [(-122.0773,37),(-122.0773,37.98)] | Oakland
+ Santa Maria Ave | [(-122.0773,37),(-122.0773,37.98)] | Berkeley
+ Shattuck Ave | [(-122.2686,37.904),(-122.2686,37.897)] | Berkeley
+ Sheridan Road | [(-122.2279,37.425),(-122.2253,37.411),(-122.2223,37.377)] | Lafayette
+ Shoreline Dr | [(-122.2657,37.603),(-122.2648,37.6)] | Berkeley
+ Skyline Blvd | [(-122.1738,37.01),(-122.1714,37.996)] | Oakland
+ Skyline Blvd | [(-122.1738,37.01),(-122.1714,37.996)] | Berkeley
+ Skyline Dr | [(-122.0277,37.5),(-122.0284,37.498)] | Oakland
+ Skywest Dr | [(-122.1161,37.62),(-122.1123,37.586)] | Berkeley
+ Southern Pacific Railroad | [(-122.3002,37.674),(-122.2999,37.661)] | Berkeley
+ Sp Railroad | [(-121.893564,37.99009),(-121.897,37.016)] | Oakland
+ Sp Railroad | [(-121.893564,37.99009),(-121.897,37.016)] | Oakland
+ Sp Railroad | [(-121.9565,37.898),(-121.9562,37.9)] | Oakland
+ Sp Railroad | [(-122.0734,37.001),(-122.0734,37.997)] | Oakland
+ Sp Railroad | [(-122.0734,37.001),(-122.0734,37.997)] | Oakland
+ Sp Railroad | [(-122.0734,37.001),(-122.0734,37.997)] | Berkeley
+ Sp Railroad | [(-122.0914,37.601),(-122.087,37.56),(-122.086408,37.5551)] | Berkeley
+ Sp Railroad | [(-122.137792,37.003),(-122.1365,37.992),(-122.131257,37.94612)] | Oakland
+ Sp Railroad | [(-122.137792,37.003),(-122.1365,37.992),(-122.131257,37.94612)] | Berkeley
+ Sp Railroad | [(-122.1947,37.497),(-122.193328,37.4848)] | Oakland
+ Stanton Ave | [(-122.100392,37.0697),(-122.099513,37.06052)] | Oakland
+ State Hwy 123 | [(-122.3004,37.986),(-122.2998,37.969),(-122.2995,37.962),(-122.2992,37.952),(-122.299,37.942),(-122.2987,37.935),(-122.2984,37.924),(-122.2982,37.92),(-122.2976,37.904),(-122.297,37.88),(-122.2966,37.869),(-122.2959,37.848),(-122.2961,37.843)] | Berkeley
+ State Hwy 13 | [(-122.1797,37.943),(-122.179871,37.91849),(-122.18,37.9),(-122.179023,37.86615),(-122.1787,37.862),(-122.1781,37.851),(-122.1777,37.845),(-122.1773,37.839),(-122.177,37.833)] | Berkeley
+ State Hwy 13 | [(-122.2049,37.2),(-122.20328,37.17975),(-122.1989,37.125),(-122.198078,37.11641),(-122.1975,37.11)] | Lafayette
+ State Hwy 13 Ramp | [(-122.2244,37.427),(-122.223,37.414),(-122.2214,37.396),(-122.2213,37.388)] | Lafayette
+ State Hwy 238 | ((-122.098,37.908),(-122.0983,37.907),(-122.099,37.905),(-122.101,37.898),(-122.101535,37.89711),(-122.103173,37.89438),(-122.1046,37.892),(-122.106,37.89)) | Berkeley
+ State Hwy 238 Ramp | [(-122.1288,37.9),(-122.1293,37.895),(-122.1296,37.906)] | Berkeley
+ State Hwy 24 | [(-122.2674,37.246),(-122.2673,37.248),(-122.267,37.261),(-122.2668,37.271),(-122.2663,37.298),(-122.2659,37.315),(-122.2655,37.336),(-122.265007,37.35882),(-122.264443,37.37286),(-122.2641,37.381),(-122.2638,37.388),(-122.2631,37.396),(-122.2617,37.405),(-122.2615,37.407),(-122.2605,37.412)] | Lafayette
+ State Hwy 84 | [(-121.9565,37.898),(-121.956589,37.89911),(-121.9569,37.903),(-121.956,37.91),(-121.9553,37.919)] | Oakland
+ State Hwy 84 | [(-122.0671,37.426),(-122.07,37.402),(-122.074,37.37),(-122.0773,37.338)] | Oakland
+ State Hwy 92 | [(-122.1085,37.326),(-122.1095,37.322),(-122.1111,37.316),(-122.1119,37.313),(-122.1125,37.311),(-122.1131,37.308),(-122.1167,37.292),(-122.1187,37.285),(-122.12,37.28)] | Oakland
+ State Hwy 92 Ramp | [(-122.1086,37.321),(-122.1089,37.315),(-122.1111,37.316)] | Oakland
+ Stuart St | [(-122.2518,37.6),(-122.2507,37.601),(-122.2491,37.606)] | Berkeley
+ Sunol Ridge Trl | [(-121.9419,37.455),(-121.9345,37.38)] | Oakland
+ Sunol Ridge Trl | [(-121.9419,37.455),(-121.9345,37.38)] | Oakland
+ Tassajara Creek | [(-121.87866,37.98898),(-121.8782,37.015)] | Oakland
+ Tassajara Creek | [(-121.87866,37.98898),(-121.8782,37.015)] | Oakland
+ Taurus Ave | [(-122.2159,37.416),(-122.2128,37.389)] | Lafayette
+ Tennyson Road | [(-122.0891,37.317),(-122.0927,37.317)] | Oakland
+ Thackeray Ave | [(-122.072,37.305),(-122.0715,37.298)] | Oakland
+ Theresa Way | [(-121.7289,37.906),(-121.728,37.899)] | Oakland
+ Tissiack Way | [(-121.920364,37),(-121.9208,37.995)] | Oakland
+ Tissiack Way | [(-121.920364,37),(-121.9208,37.995)] | Oakland
+ Tupelo Ter | [(-122.059087,37.6113),(-122.057021,37.59942)] | Berkeley
+ Vallecitos Road | [(-121.8699,37.916),(-121.8703,37.891)] | Oakland
+ Warm Springs Blvd | [(-121.933956,37),(-121.9343,37.97)] | Oakland
+ Warm Springs Blvd | [(-121.933956,37),(-121.9343,37.97)] | Oakland
+ Welch Creek Road | [(-121.7695,37.386),(-121.7737,37.413)] | Oakland
+ Welch Creek Road | [(-121.7695,37.386),(-121.7737,37.413)] | Oakland
+ West Loop Road | [(-122.0576,37.604),(-122.0602,37.586)] | Berkeley
+ Western Pacific Railroad Spur | [(-122.0394,37.018),(-122.0394,37.961)] | Oakland
+ Western Pacific Railroad Spur | [(-122.0394,37.018),(-122.0394,37.961)] | Oakland
+ Western Pacific Railroad Spur | [(-122.0394,37.018),(-122.0394,37.961)] | Berkeley
+ Whitlock Creek | [(-121.74683,37.91276),(-121.733107,37)] | Oakland
+ Whitlock Creek | [(-121.74683,37.91276),(-121.733107,37)] | Oakland
+ Willimet Way | [(-122.0964,37.517),(-122.0949,37.493)] | Oakland
+ Wisconsin St | [(-122.1994,37.017),(-122.1975,37.998),(-122.1971,37.994)] | Oakland
+ Wisconsin St | [(-122.1994,37.017),(-122.1975,37.998),(-122.1971,37.994)] | Berkeley
+ Wp Railroad | [(-122.254,37.902),(-122.2506,37.891)] | Berkeley
+ 100th Ave | [(-122.1657,37.429),(-122.1647,37.432)] | Oakland
+ 107th Ave | [(-122.1555,37.403),(-122.1531,37.41)] | Oakland
+ 14th St | [(-122.299,37.147),(-122.3,37.148)] | Lafayette
+ 19th Ave | [(-122.2366,37.897),(-122.2359,37.905)] | Berkeley
+ 1st St | [(-121.75508,37.89294),(-121.753581,37.90031)] | Oakland
+ 5th St | [(-122.278,37),(-122.2792,37.005),(-122.2803,37.009)] | Lafayette
+ 5th St | [(-122.296,37.615),(-122.2953,37.598)] | Berkeley
+ 82nd Ave | [(-122.1695,37.596),(-122.1681,37.603)] | Berkeley
+ 85th Ave | [(-122.1877,37.466),(-122.186,37.476)] | Oakland
+ 89th Ave | [(-122.1822,37.459),(-122.1803,37.471)] | Oakland
+ 98th Ave | [(-122.1568,37.498),(-122.1558,37.502)] | Oakland
+ 98th Ave | [(-122.1693,37.438),(-122.1682,37.444)] | Oakland
+ 98th Ave | [(-122.2001,37.258),(-122.1974,37.27)] | Lafayette
+(333 rows)
+
+SELECT name, #thepath FROM iexit ORDER BY name COLLATE "C", 2;
+ name | ?column?
+------------------------------------+----------
+ I- 580 | 2
+ I- 580 | 2
+ I- 580 | 2
+ I- 580 | 2
+ I- 580 | 2
+ I- 580 | 2
+ I- 580 | 2
+ I- 580 | 2
+ I- 580 | 2
+ I- 580 | 2
+ I- 580 | 2
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 3
+ I- 580 | 4
+ I- 580 | 4
+ I- 580 | 4
+ I- 580 | 4
+ I- 580 | 5
+ I- 580 | 5
+ I- 580 | 5
+ I- 580 | 5
+ I- 580 | 5
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 6
+ I- 580 | 7
+ I- 580 | 7
+ I- 580 | 7
+ I- 580 | 7
+ I- 580 | 7
+ I- 580 | 7
+ I- 580 | 7
+ I- 580 | 8
+ I- 580 | 8
+ I- 580 | 8
+ I- 580 | 8
+ I- 580 | 8
+ I- 580 | 8
+ I- 580 | 8
+ I- 580 | 8
+ I- 580 | 8
+ I- 580 | 9
+ I- 580 | 9
+ I- 580 | 9
+ I- 580 | 9
+ I- 580 | 9
+ I- 580 | 12
+ I- 580 | 12
+ I- 580 | 12
+ I- 580 | 12
+ I- 580 | 12
+ I- 580 | 12
+ I- 580 | 12
+ I- 580 | 12
+ I- 580 | 12
+ I- 580 | 12
+ I- 580 | 13
+ I- 580 | 13
+ I- 580 | 13
+ I- 580 | 13
+ I- 580 | 13
+ I- 580 | 13
+ I- 580 | 14
+ I- 580 | 14
+ I- 580 | 14
+ I- 580 | 14
+ I- 580 | 14
+ I- 580 | 14
+ I- 580 | 14
+ I- 580 | 14
+ I- 580 | 18
+ I- 580 | 18
+ I- 580 | 18
+ I- 580 | 18
+ I- 580 | 18
+ I- 580 | 18
+ I- 580 | 21
+ I- 580 | 21
+ I- 580 | 21
+ I- 580 | 21
+ I- 580 | 21
+ I- 580 | 21
+ I- 580 | 21
+ I- 580 | 21
+ I- 580 | 21
+ I- 580 | 21
+ I- 580 | 22
+ I- 580 | 22
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 2
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 3
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 4
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 5
+ I- 580 Ramp | 6
+ I- 580 Ramp | 6
+ I- 580 Ramp | 6
+ I- 580 Ramp | 7
+ I- 580 Ramp | 8
+ I- 580 Ramp | 8
+ I- 580 Ramp | 8
+ I- 580 Ramp | 8
+ I- 580 Ramp | 8
+ I- 580 Ramp | 8
+ I- 580/I-680 Ramp | 2
+ I- 580/I-680 Ramp | 2
+ I- 580/I-680 Ramp | 2
+ I- 580/I-680 Ramp | 2
+ I- 580/I-680 Ramp | 2
+ I- 580/I-680 Ramp | 2
+ I- 580/I-680 Ramp | 4
+ I- 580/I-680 Ramp | 4
+ I- 580/I-680 Ramp | 4
+ I- 580/I-680 Ramp | 4
+ I- 580/I-680 Ramp | 5
+ I- 580/I-680 Ramp | 6
+ I- 580/I-680 Ramp | 6
+ I- 580/I-680 Ramp | 6
+ I- 680 | 2
+ I- 680 | 2
+ I- 680 | 2
+ I- 680 | 2
+ I- 680 | 2
+ I- 680 | 2
+ I- 680 | 2
+ I- 680 | 3
+ I- 680 | 3
+ I- 680 | 3
+ I- 680 | 4
+ I- 680 | 4
+ I- 680 | 4
+ I- 680 | 5
+ I- 680 | 5
+ I- 680 | 5
+ I- 680 | 7
+ I- 680 | 7
+ I- 680 | 7
+ I- 680 | 7
+ I- 680 | 8
+ I- 680 | 8
+ I- 680 | 8
+ I- 680 | 8
+ I- 680 | 10
+ I- 680 | 10
+ I- 680 | 10
+ I- 680 | 10
+ I- 680 | 10
+ I- 680 | 10
+ I- 680 | 10
+ I- 680 | 16
+ I- 680 | 16
+ I- 680 | 16
+ I- 680 | 16
+ I- 680 | 16
+ I- 680 | 16
+ I- 680 | 16
+ I- 680 | 16
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 2
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 3
+ I- 680 Ramp | 4
+ I- 680 Ramp | 4
+ I- 680 Ramp | 4
+ I- 680 Ramp | 5
+ I- 680 Ramp | 5
+ I- 680 Ramp | 5
+ I- 680 Ramp | 5
+ I- 680 Ramp | 5
+ I- 680 Ramp | 5
+ I- 680 Ramp | 6
+ I- 680 Ramp | 6
+ I- 680 Ramp | 6
+ I- 680 Ramp | 6
+ I- 680 Ramp | 7
+ I- 680 Ramp | 7
+ I- 680 Ramp | 7
+ I- 680 Ramp | 7
+ I- 680 Ramp | 8
+ I- 680 Ramp | 8
+ I- 680 Ramp | 8
+ I- 680 Ramp | 8
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 2
+ I- 80 | 3
+ I- 80 | 3
+ I- 80 | 3
+ I- 80 | 4
+ I- 80 | 4
+ I- 80 | 4
+ I- 80 | 4
+ I- 80 | 4
+ I- 80 | 5
+ I- 80 | 5
+ I- 80 | 5
+ I- 80 | 5
+ I- 80 | 5
+ I- 80 | 5
+ I- 80 | 5
+ I- 80 | 5
+ I- 80 | 5
+ I- 80 | 11
+ I- 80 | 11
+ I- 80 | 11
+ I- 80 | 11
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 2
+ I- 80 Ramp | 3
+ I- 80 Ramp | 3
+ I- 80 Ramp | 3
+ I- 80 Ramp | 3
+ I- 80 Ramp | 3
+ I- 80 Ramp | 3
+ I- 80 Ramp | 3
+ I- 80 Ramp | 3
+ I- 80 Ramp | 3
+ I- 80 Ramp | 4
+ I- 80 Ramp | 4
+ I- 80 Ramp | 4
+ I- 80 Ramp | 4
+ I- 80 Ramp | 5
+ I- 80 Ramp | 5
+ I- 80 Ramp | 5
+ I- 80 Ramp | 5
+ I- 80 Ramp | 5
+ I- 80 Ramp | 5
+ I- 80 Ramp | 5
+ I- 80 Ramp | 7
+ I- 80 Ramp | 7
+ I- 80 Ramp | 7
+ I- 80 Ramp | 7
+ I- 880 | 2
+ I- 880 | 2
+ I- 880 | 2
+ I- 880 | 2
+ I- 880 | 2
+ I- 880 | 5
+ I- 880 | 5
+ I- 880 | 5
+ I- 880 | 5
+ I- 880 | 5
+ I- 880 | 5
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 6
+ I- 880 | 7
+ I- 880 | 7
+ I- 880 | 7
+ I- 880 | 7
+ I- 880 | 7
+ I- 880 | 7
+ I- 880 | 7
+ I- 880 | 9
+ I- 880 | 9
+ I- 880 | 9
+ I- 880 | 9
+ I- 880 | 9
+ I- 880 | 9
+ I- 880 | 9
+ I- 880 | 10
+ I- 880 | 10
+ I- 880 | 10
+ I- 880 | 10
+ I- 880 | 10
+ I- 880 | 10
+ I- 880 | 10
+ I- 880 | 10
+ I- 880 | 10
+ I- 880 | 10
+ I- 880 | 10
+ I- 880 | 10
+ I- 880 | 12
+ I- 880 | 12
+ I- 880 | 12
+ I- 880 | 12
+ I- 880 | 12
+ I- 880 | 12
+ I- 880 | 12
+ I- 880 | 12
+ I- 880 | 12
+ I- 880 | 12
+ I- 880 | 12
+ I- 880 | 13
+ I- 880 | 13
+ I- 880 | 13
+ I- 880 | 13
+ I- 880 | 13
+ I- 880 | 13
+ I- 880 | 13
+ I- 880 | 13
+ I- 880 | 13
+ I- 880 | 13
+ I- 880 | 13
+ I- 880 | 13
+ I- 880 | 14
+ I- 880 | 14
+ I- 880 | 14
+ I- 880 | 14
+ I- 880 | 14
+ I- 880 | 14
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 17
+ I- 880 | 19
+ I- 880 | 19
+ I- 880 | 19
+ I- 880 | 19
+ I- 880 | 19
+ I- 880 | 19
+ I- 880 | 19
+ I- 880 | 19
+ I- 880 | 19
+ I- 880 | 19
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 2
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 3
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 4
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 5
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 6
+ I- 880 Ramp | 8
+ I- 880 Ramp | 8
+ I- 880 Ramp | 8
+ I- 980 | 2
+ I- 980 | 2
+ I- 980 | 2
+ I- 980 | 2
+ I- 980 | 2
+ I- 980 | 2
+ I- 980 | 2
+ I- 980 | 2
+ I- 980 | 3
+ I- 980 | 3
+ I- 980 | 3
+ I- 980 | 3
+ I- 980 | 3
+ I- 980 | 3
+ I- 980 | 3
+ I- 980 | 3
+ I- 980 | 3
+ I- 980 | 4
+ I- 980 | 4
+ I- 980 | 5
+ I- 980 | 5
+ I- 980 | 7
+ I- 980 | 7
+ I- 980 | 7
+ I- 980 | 7
+ I- 980 | 12
+ I- 980 Ramp | 3
+ I- 980 Ramp | 3
+ I- 980 Ramp | 3
+ I- 980 Ramp | 7
+(896 rows)
+
+SELECT * FROM toyemp WHERE name = 'sharon';
+ name | age | location | annualsal
+--------+-----+----------+-----------
+ sharon | 25 | (15,12) | 12000
+(1 row)
+
+--
+-- Test for Leaky view scenario
+--
+CREATE ROLE regress_alice;
+CREATE FUNCTION f_leak (text)
+ RETURNS bool LANGUAGE 'plpgsql' COST 0.0000001
+ AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';
+CREATE TABLE customer (
+ cid int primary key,
+ name text not null,
+ tel text,
+ passwd text
+);
+CREATE TABLE credit_card (
+ cid int references customer(cid),
+ cnum text,
+ climit int
+);
+CREATE TABLE credit_usage (
+ cid int references customer(cid),
+ ymd date,
+ usage int
+);
+INSERT INTO customer
+ VALUES (101, 'regress_alice', '+81-12-3456-7890', 'passwd123'),
+ (102, 'regress_bob', '+01-234-567-8901', 'beafsteak'),
+ (103, 'regress_eve', '+49-8765-43210', 'hamburger');
+INSERT INTO credit_card
+ VALUES (101, '1111-2222-3333-4444', 4000),
+ (102, '5555-6666-7777-8888', 3000),
+ (103, '9801-2345-6789-0123', 2000);
+INSERT INTO credit_usage
+ VALUES (101, '2011-09-15', 120),
+ (101, '2011-10-05', 90),
+ (101, '2011-10-18', 110),
+ (101, '2011-10-21', 200),
+ (101, '2011-11-10', 80),
+ (102, '2011-09-22', 300),
+ (102, '2011-10-12', 120),
+ (102, '2011-10-28', 200),
+ (103, '2011-10-15', 480);
+CREATE VIEW my_property_normal AS
+ SELECT * FROM customer WHERE name = current_user;
+CREATE VIEW my_property_secure WITH (security_barrier) AS
+ SELECT * FROM customer WHERE name = current_user;
+CREATE VIEW my_credit_card_normal AS
+ SELECT * FROM customer l NATURAL JOIN credit_card r
+ WHERE l.name = current_user;
+CREATE VIEW my_credit_card_secure WITH (security_barrier) AS
+ SELECT * FROM customer l NATURAL JOIN credit_card r
+ WHERE l.name = current_user;
+CREATE VIEW my_credit_card_usage_normal AS
+ SELECT * FROM my_credit_card_secure l NATURAL JOIN credit_usage r;
+CREATE VIEW my_credit_card_usage_secure WITH (security_barrier) AS
+ SELECT * FROM my_credit_card_secure l NATURAL JOIN credit_usage r;
+GRANT SELECT ON my_property_normal TO public;
+GRANT SELECT ON my_property_secure TO public;
+GRANT SELECT ON my_credit_card_normal TO public;
+GRANT SELECT ON my_credit_card_secure TO public;
+GRANT SELECT ON my_credit_card_usage_normal TO public;
+GRANT SELECT ON my_credit_card_usage_secure TO public;
+--
+-- Run leaky view scenarios
+--
+SET SESSION AUTHORIZATION regress_alice;
+--
+-- scenario: if a qualifier with tiny-cost is given, it shall be launched
+-- prior to the security policy of the view.
+--
+SELECT * FROM my_property_normal WHERE f_leak(passwd);
+NOTICE: f_leak => passwd123
+NOTICE: f_leak => beafsteak
+NOTICE: f_leak => hamburger
+ cid | name | tel | passwd
+-----+---------------+------------------+-----------
+ 101 | regress_alice | +81-12-3456-7890 | passwd123
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM my_property_normal WHERE f_leak(passwd);
+ QUERY PLAN
+------------------------------------------------------
+ Seq Scan on customer
+ Filter: (f_leak(passwd) AND (name = CURRENT_USER))
+(2 rows)
+
+SELECT * FROM my_property_secure WHERE f_leak(passwd);
+NOTICE: f_leak => passwd123
+ cid | name | tel | passwd
+-----+---------------+------------------+-----------
+ 101 | regress_alice | +81-12-3456-7890 | passwd123
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM my_property_secure WHERE f_leak(passwd);
+ QUERY PLAN
+---------------------------------------------
+ Subquery Scan on my_property_secure
+ Filter: f_leak(my_property_secure.passwd)
+ -> Seq Scan on customer
+ Filter: (name = CURRENT_USER)
+(4 rows)
+
+--
+-- scenario: qualifiers can be pushed down if they contain leaky functions,
+-- provided they aren't passed data from inside the view.
+--
+SELECT * FROM my_property_normal v
+ WHERE f_leak('passwd') AND f_leak(passwd);
+NOTICE: f_leak => passwd
+NOTICE: f_leak => passwd123
+NOTICE: f_leak => passwd
+NOTICE: f_leak => beafsteak
+NOTICE: f_leak => passwd
+NOTICE: f_leak => hamburger
+ cid | name | tel | passwd
+-----+---------------+------------------+-----------
+ 101 | regress_alice | +81-12-3456-7890 | passwd123
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM my_property_normal v
+ WHERE f_leak('passwd') AND f_leak(passwd);
+ QUERY PLAN
+---------------------------------------------------------------------------------
+ Seq Scan on customer
+ Filter: (f_leak('passwd'::text) AND f_leak(passwd) AND (name = CURRENT_USER))
+(2 rows)
+
+SELECT * FROM my_property_secure v
+ WHERE f_leak('passwd') AND f_leak(passwd);
+NOTICE: f_leak => passwd
+NOTICE: f_leak => passwd123
+NOTICE: f_leak => passwd
+NOTICE: f_leak => passwd
+ cid | name | tel | passwd
+-----+---------------+------------------+-----------
+ 101 | regress_alice | +81-12-3456-7890 | passwd123
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM my_property_secure v
+ WHERE f_leak('passwd') AND f_leak(passwd);
+ QUERY PLAN
+--------------------------------------------------------------------
+ Subquery Scan on v
+ Filter: f_leak(v.passwd)
+ -> Seq Scan on customer
+ Filter: (f_leak('passwd'::text) AND (name = CURRENT_USER))
+(4 rows)
+
+--
+-- scenario: if a qualifier references only one-side of a particular join-
+-- tree, it shall be distributed to the most deep scan plan as
+-- possible as we can.
+--
+SELECT * FROM my_credit_card_normal WHERE f_leak(cnum);
+NOTICE: f_leak => 1111-2222-3333-4444
+NOTICE: f_leak => 5555-6666-7777-8888
+NOTICE: f_leak => 9801-2345-6789-0123
+ cid | name | tel | passwd | cnum | climit
+-----+---------------+------------------+-----------+---------------------+--------
+ 101 | regress_alice | +81-12-3456-7890 | passwd123 | 1111-2222-3333-4444 | 4000
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_normal WHERE f_leak(cnum);
+ QUERY PLAN
+---------------------------------------------
+ Hash Join
+ Hash Cond: (r.cid = l.cid)
+ -> Seq Scan on credit_card r
+ Filter: f_leak(cnum)
+ -> Hash
+ -> Seq Scan on customer l
+ Filter: (name = CURRENT_USER)
+(7 rows)
+
+SELECT * FROM my_credit_card_secure WHERE f_leak(cnum);
+NOTICE: f_leak => 1111-2222-3333-4444
+ cid | name | tel | passwd | cnum | climit
+-----+---------------+------------------+-----------+---------------------+--------
+ 101 | regress_alice | +81-12-3456-7890 | passwd123 | 1111-2222-3333-4444 | 4000
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_secure WHERE f_leak(cnum);
+ QUERY PLAN
+---------------------------------------------------
+ Subquery Scan on my_credit_card_secure
+ Filter: f_leak(my_credit_card_secure.cnum)
+ -> Hash Join
+ Hash Cond: (r.cid = l.cid)
+ -> Seq Scan on credit_card r
+ -> Hash
+ -> Seq Scan on customer l
+ Filter: (name = CURRENT_USER)
+(8 rows)
+
+--
+-- scenario: an external qualifier can be pushed-down by in-front-of the
+-- views with "security_barrier" attribute, except for operators
+-- implemented with leakproof functions.
+--
+SELECT * FROM my_credit_card_usage_normal
+ WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
+NOTICE: f_leak => 1111-2222-3333-4444
+ cid | name | tel | passwd | cnum | climit | ymd | usage
+-----+---------------+------------------+-----------+---------------------+--------+------------+-------
+ 101 | regress_alice | +81-12-3456-7890 | passwd123 | 1111-2222-3333-4444 | 4000 | 10-05-2011 | 90
+ 101 | regress_alice | +81-12-3456-7890 | passwd123 | 1111-2222-3333-4444 | 4000 | 10-18-2011 | 110
+ 101 | regress_alice | +81-12-3456-7890 | passwd123 | 1111-2222-3333-4444 | 4000 | 10-21-2011 | 200
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_usage_normal
+ WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
+ QUERY PLAN
+------------------------------------------------------------------------------
+ Nested Loop
+ Join Filter: (l.cid = r.cid)
+ -> Seq Scan on credit_usage r
+ Filter: ((ymd >= '10-01-2011'::date) AND (ymd < '11-01-2011'::date))
+ -> Materialize
+ -> Subquery Scan on l
+ Filter: f_leak(l.cnum)
+ -> Hash Join
+ Hash Cond: (r_1.cid = l_1.cid)
+ -> Seq Scan on credit_card r_1
+ -> Hash
+ -> Seq Scan on customer l_1
+ Filter: (name = CURRENT_USER)
+(13 rows)
+
+SELECT * FROM my_credit_card_usage_secure
+ WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
+NOTICE: f_leak => 1111-2222-3333-4444
+NOTICE: f_leak => 1111-2222-3333-4444
+NOTICE: f_leak => 1111-2222-3333-4444
+ cid | name | tel | passwd | cnum | climit | ymd | usage
+-----+---------------+------------------+-----------+---------------------+--------+------------+-------
+ 101 | regress_alice | +81-12-3456-7890 | passwd123 | 1111-2222-3333-4444 | 4000 | 10-05-2011 | 90
+ 101 | regress_alice | +81-12-3456-7890 | passwd123 | 1111-2222-3333-4444 | 4000 | 10-18-2011 | 110
+ 101 | regress_alice | +81-12-3456-7890 | passwd123 | 1111-2222-3333-4444 | 4000 | 10-21-2011 | 200
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_usage_secure
+ WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Subquery Scan on my_credit_card_usage_secure
+ Filter: f_leak(my_credit_card_usage_secure.cnum)
+ -> Nested Loop
+ Join Filter: (l.cid = r.cid)
+ -> Seq Scan on credit_usage r
+ Filter: ((ymd >= '10-01-2011'::date) AND (ymd < '11-01-2011'::date))
+ -> Materialize
+ -> Hash Join
+ Hash Cond: (r_1.cid = l.cid)
+ -> Seq Scan on credit_card r_1
+ -> Hash
+ -> Seq Scan on customer l
+ Filter: (name = CURRENT_USER)
+(13 rows)
+
+--
+-- Test for the case when security_barrier gets changed between rewriter
+-- and planner stage.
+--
+PREPARE p1 AS SELECT * FROM my_property_normal WHERE f_leak(passwd);
+PREPARE p2 AS SELECT * FROM my_property_secure WHERE f_leak(passwd);
+EXECUTE p1;
+NOTICE: f_leak => passwd123
+NOTICE: f_leak => beafsteak
+NOTICE: f_leak => hamburger
+ cid | name | tel | passwd
+-----+---------------+------------------+-----------
+ 101 | regress_alice | +81-12-3456-7890 | passwd123
+(1 row)
+
+EXECUTE p2;
+NOTICE: f_leak => passwd123
+ cid | name | tel | passwd
+-----+---------------+------------------+-----------
+ 101 | regress_alice | +81-12-3456-7890 | passwd123
+(1 row)
+
+RESET SESSION AUTHORIZATION;
+ALTER VIEW my_property_normal SET (security_barrier=true);
+ALTER VIEW my_property_secure SET (security_barrier=false);
+SET SESSION AUTHORIZATION regress_alice;
+EXECUTE p1; -- To be perform as a view with security-barrier
+NOTICE: f_leak => passwd123
+ cid | name | tel | passwd
+-----+---------------+------------------+-----------
+ 101 | regress_alice | +81-12-3456-7890 | passwd123
+(1 row)
+
+EXECUTE p2; -- To be perform as a view without security-barrier
+NOTICE: f_leak => passwd123
+NOTICE: f_leak => beafsteak
+NOTICE: f_leak => hamburger
+ cid | name | tel | passwd
+-----+---------------+------------------+-----------
+ 101 | regress_alice | +81-12-3456-7890 | passwd123
+(1 row)
+
+-- Cleanup.
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_alice;
diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out
new file mode 100644
index 0000000..8b928b2
--- /dev/null
+++ b/src/test/regress/expected/sequence.out
@@ -0,0 +1,824 @@
+--
+-- CREATE SEQUENCE
+--
+-- various error cases
+CREATE UNLOGGED SEQUENCE sequence_testx;
+ERROR: unlogged sequences are not supported
+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: referenced relation "pg_class_oid_index" is not a table or foreign table
+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
+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;
+ 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 (31, 32) AS log_cnt_ok, is_called FROM foo_seq_new;
+ 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;
+ 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
+------------------+-----------------+--------------------+-----------+-------------------+-------------------------+---------------+-------------+----------------------+---------------------+-----------+--------------
+ regression | public | sequence_test10 | smallint | 16 | 2 | 0 | 1 | -20000 | 32767 | 1 | NO
+ regression | public | sequence_test11 | integer | 32 | 2 | 0 | 1 | 1 | 2147483647 | 1 | NO
+ regression | public | sequence_test12 | integer | 32 | 2 | 0 | -1 | -2147483648 | -1 | -1 | NO
+ regression | public | sequence_test13 | integer | 32 | 2 | 0 | -32768 | -2147483648 | 2147483647 | 1 | NO
+ regression | public | sequence_test14 | integer | 32 | 2 | 0 | 32767 | -2147483648 | 2147483647 | -1 | NO
+ regression | public | sequence_test2 | bigint | 64 | 2 | 0 | 32 | 5 | 36 | 4 | YES
+ regression | public | sequence_test3 | bigint | 64 | 2 | 0 | 1 | 1 | 9223372036854775807 | 1 | NO
+ regression | public | sequence_test4 | bigint | 64 | 2 | 0 | -1 | -9223372036854775808 | -1 | -1 | NO
+ regression | public | sequence_test5 | smallint | 16 | 2 | 0 | 1 | 1 | 32767 | 1 | NO
+ regression | public | sequence_test6 | smallint | 16 | 2 | 0 | 1 | 1 | 32767 | 1 | NO
+ regression | public | sequence_test7 | bigint | 64 | 2 | 0 | 1 | 1 | 9223372036854775807 | 1 | NO
+ regression | public | sequence_test8 | smallint | 16 | 2 | 0 | 1 | 1 | 20000 | 1 | NO
+ regression | public | sequence_test9 | smallint | 16 | 2 | 0 | -1 | -32768 | -1 | -1 | NO
+ regression | public | serialtest1_f2_foo | integer | 32 | 2 | 0 | 1 | 1 | 2147483647 | 1 | NO
+ regression | public | serialtest2_f2_seq | integer | 32 | 2 | 0 | 1 | 1 | 2147483647 | 1 | NO
+ regression | public | serialtest2_f3_seq | smallint | 16 | 2 | 0 | 1 | 1 | 32767 | 1 | NO
+ regression | public | serialtest2_f4_seq | smallint | 16 | 2 | 0 | 1 | 1 | 32767 | 1 | NO
+ regression | public | serialtest2_f5_seq | bigint | 64 | 2 | 0 | 1 | 1 | 9223372036854775807 | 1 | NO
+ regression | 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, last_value
+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 | last_value
+------------+--------------------+-------------+----------------------+---------------------+--------------+-------+------------+------------
+ 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 | 5
+ public | sequence_test3 | 1 | 1 | 9223372036854775807 | 1 | f | 1 |
+ public | sequence_test4 | -1 | -9223372036854775808 | -1 | -1 | f | 1 | -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 | 3
+ public | serialtest2_f2_seq | 1 | 1 | 2147483647 | 1 | f | 1 | 2
+ public | serialtest2_f3_seq | 1 | 1 | 32767 | 1 | f | 1 | 2
+ public | serialtest2_f4_seq | 1 | 1 | 32767 | 1 | f | 1 | 2
+ public | serialtest2_f5_seq | 1 | 1 | 9223372036854775807 | 1 | f | 1 | 2
+ public | serialtest2_f6_seq | 1 | 1 | 9223372036854775807 | 1 | f | 1 | 2
+(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
+-- 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;
+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;
+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;
+-- 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;
+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;
+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;
+-- 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;
+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;
+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;
+-- 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;
+-- 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
+------------------+-----------------+----------------+-----------+-------------------+-------------------------+---------------+-------------+---------------+---------------+-----------+--------------
+ regression | 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/spgist.out b/src/test/regress/expected/spgist.out
new file mode 100644
index 0000000..1688e0e
--- /dev/null
+++ b/src/test/regress/expected/spgist.out
@@ -0,0 +1,88 @@
+--
+-- Test SP-GiST indexes.
+--
+-- There are other tests to test different SP-GiST opclasses. This is for
+-- testing SP-GiST code itself.
+create table spgist_point_tbl(id int4, p point);
+create index spgist_point_idx on spgist_point_tbl using spgist(p) with (fillfactor = 75);
+-- Test vacuum-root operation. It gets invoked when the root is also a leaf,
+-- i.e. the index is very small.
+insert into spgist_point_tbl (id, p)
+select g, point(g*10, g*10) from generate_series(1, 10) g;
+delete from spgist_point_tbl where id < 5;
+vacuum spgist_point_tbl;
+-- Insert more data, to make the index a few levels deep.
+insert into spgist_point_tbl (id, p)
+select g, point(g*10, g*10) from generate_series(1, 10000) g;
+insert into spgist_point_tbl (id, p)
+select g+100000, point(g*10+1, g*10+1) from generate_series(1, 10000) g;
+-- To test vacuum, delete some entries from all over the index.
+delete from spgist_point_tbl where id % 2 = 1;
+-- And also delete some concentration of values. (SP-GiST doesn't currently
+-- attempt to delete pages even when they become empty, but if it did, this
+-- would exercise it)
+delete from spgist_point_tbl where id < 10000;
+vacuum spgist_point_tbl;
+-- Test rescan paths (cf. bug #15378)
+-- use box and && rather than point, so that rescan happens when the
+-- traverse stack is non-empty
+create table spgist_box_tbl(id serial, b box);
+insert into spgist_box_tbl(b)
+select box(point(i,j),point(i+s,j+s))
+ from generate_series(1,100,5) i,
+ generate_series(1,100,5) j,
+ generate_series(1,10) s;
+create index spgist_box_idx on spgist_box_tbl using spgist (b);
+select count(*)
+ from (values (point(5,5)),(point(8,8)),(point(12,12))) v(p)
+ where exists(select * from spgist_box_tbl b where b.b && box(v.p,v.p));
+ count
+-------
+ 3
+(1 row)
+
+-- The point opclass's choose method only uses the spgMatchNode action,
+-- so the other actions are not tested by the above. Create an index using
+-- text opclass, which uses the others actions.
+create table spgist_text_tbl(id int4, t text);
+create index spgist_text_idx on spgist_text_tbl using spgist(t);
+insert into spgist_text_tbl (id, t)
+select g, 'f' || repeat('o', 100) || g from generate_series(1, 10000) g
+union all
+select g, 'baaaaaaaaaaaaaar' || g from generate_series(1, 1000) g;
+-- Do a lot of insertions that have to split an existing node. Hopefully
+-- one of these will cause the page to run out of space, causing the inner
+-- tuple to be moved to another page.
+insert into spgist_text_tbl (id, t)
+select -g, 'f' || repeat('o', 100-g) || 'surprise' from generate_series(1, 100) g;
+-- Test out-of-range fillfactor values
+create index spgist_point_idx2 on spgist_point_tbl using spgist(p) with (fillfactor = 9);
+ERROR: value 9 out of bounds for option "fillfactor"
+DETAIL: Valid values are between "10" and "100".
+create index spgist_point_idx2 on spgist_point_tbl using spgist(p) with (fillfactor = 101);
+ERROR: value 101 out of bounds for option "fillfactor"
+DETAIL: Valid values are between "10" and "100".
+-- Modify fillfactor in existing index
+alter index spgist_point_idx set (fillfactor = 90);
+reindex index spgist_point_idx;
+-- Test index over a domain
+create domain spgist_text as varchar;
+create table spgist_domain_tbl (f1 spgist_text);
+create index spgist_domain_idx on spgist_domain_tbl using spgist(f1);
+insert into spgist_domain_tbl values('fee'), ('fi'), ('fo'), ('fum');
+explain (costs off)
+select * from spgist_domain_tbl where f1 = 'fo';
+ QUERY PLAN
+-----------------------------------------------
+ Bitmap Heap Scan on spgist_domain_tbl
+ Recheck Cond: ((f1)::text = 'fo'::text)
+ -> Bitmap Index Scan on spgist_domain_idx
+ Index Cond: ((f1)::text = 'fo'::text)
+(4 rows)
+
+select * from spgist_domain_tbl where f1 = 'fo';
+ f1
+----
+ fo
+(1 row)
+
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
new file mode 100644
index 0000000..4eeaaca
--- /dev/null
+++ b/src/test/regress/expected/stats.out
@@ -0,0 +1,211 @@
+--
+-- Test Statistics Collector
+--
+-- Must be run after tenk2 has been created (by create_table),
+-- populated (by create_misc) and indexed (by create_index).
+--
+-- conditio sine qua non
+SHOW track_counts; -- must be on
+ track_counts
+--------------
+ on
+(1 row)
+
+-- ensure that both seqscan and indexscan plans are allowed
+SET enable_seqscan TO on;
+SET enable_indexscan TO on;
+-- for the moment, we don't want index-only scans here
+SET enable_indexonlyscan TO off;
+-- save counters
+CREATE TABLE prevstats AS
+SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch,
+ (b.heap_blks_read + b.heap_blks_hit) AS heap_blks,
+ (b.idx_blks_read + b.idx_blks_hit) AS idx_blks,
+ pg_stat_get_snapshot_timestamp() as snap_ts
+ FROM pg_catalog.pg_stat_user_tables AS t,
+ pg_catalog.pg_statio_user_tables AS b
+ WHERE t.relname='tenk2' AND b.relname='tenk2';
+-- function to wait for counters to advance
+create function wait_for_stats() returns void as $$
+declare
+ start_time timestamptz := clock_timestamp();
+ updated1 bool;
+ updated2 bool;
+ updated3 bool;
+ updated4 bool;
+begin
+ -- we don't want to wait forever; loop will exit after 30 seconds
+ for i in 1 .. 300 loop
+
+ -- With parallel query, the seqscan and indexscan on tenk2 might be done
+ -- in parallel worker processes, which will send their stats counters
+ -- asynchronously to what our own session does. So we must check for
+ -- those counts to be registered separately from the update counts.
+
+ -- check to see if seqscan has been sensed
+ SELECT (st.seq_scan >= pr.seq_scan + 1) INTO updated1
+ FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
+ WHERE st.relname='tenk2' AND cl.relname='tenk2';
+
+ -- check to see if indexscan has been sensed
+ SELECT (st.idx_scan >= pr.idx_scan + 1) INTO updated2
+ FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
+ WHERE st.relname='tenk2' AND cl.relname='tenk2';
+
+ -- check to see if all updates have been sensed
+ SELECT (n_tup_ins > 0) INTO updated3
+ FROM pg_stat_user_tables WHERE relname='trunc_stats_test4';
+
+ -- We must also check explicitly that pg_stat_get_snapshot_timestamp has
+ -- advanced, because that comes from the global stats file which might
+ -- be older than the per-DB stats file we got the other values from.
+ SELECT (pr.snap_ts < pg_stat_get_snapshot_timestamp()) INTO updated4
+ FROM prevstats AS pr;
+
+ exit when updated1 and updated2 and updated3 and updated4;
+
+ -- wait a little
+ perform pg_sleep_for('100 milliseconds');
+
+ -- reset stats snapshot so we can test again
+ perform pg_stat_clear_snapshot();
+
+ end loop;
+
+ -- report time waited in postmaster log (where it won't change test output)
+ raise log 'wait_for_stats delayed % seconds',
+ extract(epoch from clock_timestamp() - start_time);
+end
+$$ language plpgsql;
+-- test effects of TRUNCATE on n_live_tup/n_dead_tup counters
+CREATE TABLE trunc_stats_test(id serial);
+CREATE TABLE trunc_stats_test1(id serial, stuff text);
+CREATE TABLE trunc_stats_test2(id serial);
+CREATE TABLE trunc_stats_test3(id serial, stuff text);
+CREATE TABLE trunc_stats_test4(id serial);
+-- check that n_live_tup is reset to 0 after truncate
+INSERT INTO trunc_stats_test DEFAULT VALUES;
+INSERT INTO trunc_stats_test DEFAULT VALUES;
+INSERT INTO trunc_stats_test DEFAULT VALUES;
+TRUNCATE trunc_stats_test;
+-- test involving a truncate in a transaction; 4 ins but only 1 live
+INSERT INTO trunc_stats_test1 DEFAULT VALUES;
+INSERT INTO trunc_stats_test1 DEFAULT VALUES;
+INSERT INTO trunc_stats_test1 DEFAULT VALUES;
+UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
+DELETE FROM trunc_stats_test1 WHERE id = 3;
+BEGIN;
+UPDATE trunc_stats_test1 SET id = id + 100;
+TRUNCATE trunc_stats_test1;
+INSERT INTO trunc_stats_test1 DEFAULT VALUES;
+COMMIT;
+-- use a savepoint: 1 insert, 1 live
+BEGIN;
+INSERT INTO trunc_stats_test2 DEFAULT VALUES;
+INSERT INTO trunc_stats_test2 DEFAULT VALUES;
+SAVEPOINT p1;
+INSERT INTO trunc_stats_test2 DEFAULT VALUES;
+TRUNCATE trunc_stats_test2;
+INSERT INTO trunc_stats_test2 DEFAULT VALUES;
+RELEASE SAVEPOINT p1;
+COMMIT;
+-- rollback a savepoint: this should count 4 inserts and have 2
+-- live tuples after commit (and 2 dead ones due to aborted subxact)
+BEGIN;
+INSERT INTO trunc_stats_test3 DEFAULT VALUES;
+INSERT INTO trunc_stats_test3 DEFAULT VALUES;
+SAVEPOINT p1;
+INSERT INTO trunc_stats_test3 DEFAULT VALUES;
+INSERT INTO trunc_stats_test3 DEFAULT VALUES;
+TRUNCATE trunc_stats_test3;
+INSERT INTO trunc_stats_test3 DEFAULT VALUES;
+ROLLBACK TO SAVEPOINT p1;
+COMMIT;
+-- rollback a truncate: this should count 2 inserts and produce 2 dead tuples
+BEGIN;
+INSERT INTO trunc_stats_test4 DEFAULT VALUES;
+INSERT INTO trunc_stats_test4 DEFAULT VALUES;
+TRUNCATE trunc_stats_test4;
+INSERT INTO trunc_stats_test4 DEFAULT VALUES;
+ROLLBACK;
+-- do a seqscan
+SELECT count(*) FROM tenk2;
+ count
+-------
+ 10000
+(1 row)
+
+-- do an indexscan
+-- make sure it is not a bitmap scan, which might skip fetching heap tuples
+SET enable_bitmapscan TO off;
+SELECT count(*) FROM tenk2 WHERE unique1 = 1;
+ count
+-------
+ 1
+(1 row)
+
+RESET enable_bitmapscan;
+-- We can't just call wait_for_stats() at this point, because we only
+-- transmit stats when the session goes idle, and we probably didn't
+-- transmit the last couple of counts yet thanks to the rate-limiting logic
+-- in pgstat_report_stat(). But instead of waiting for the rate limiter's
+-- timeout to elapse, let's just start a new session. The old one will
+-- then send its stats before dying.
+\c -
+-- wait for stats collector to update
+SELECT wait_for_stats();
+ wait_for_stats
+----------------
+
+(1 row)
+
+-- check effects
+SELECT relname, n_tup_ins, n_tup_upd, n_tup_del, n_live_tup, n_dead_tup
+ FROM pg_stat_user_tables
+ WHERE relname like 'trunc_stats_test%' order by relname;
+ relname | n_tup_ins | n_tup_upd | n_tup_del | n_live_tup | n_dead_tup
+-------------------+-----------+-----------+-----------+------------+------------
+ trunc_stats_test | 3 | 0 | 0 | 0 | 0
+ trunc_stats_test1 | 4 | 2 | 1 | 1 | 0
+ trunc_stats_test2 | 1 | 0 | 0 | 1 | 0
+ trunc_stats_test3 | 4 | 0 | 0 | 2 | 2
+ trunc_stats_test4 | 2 | 0 | 0 | 0 | 2
+(5 rows)
+
+SELECT st.seq_scan >= pr.seq_scan + 1,
+ st.seq_tup_read >= pr.seq_tup_read + cl.reltuples,
+ st.idx_scan >= pr.idx_scan + 1,
+ st.idx_tup_fetch >= pr.idx_tup_fetch + 1
+ FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
+ WHERE st.relname='tenk2' AND cl.relname='tenk2';
+ ?column? | ?column? | ?column? | ?column?
+----------+----------+----------+----------
+ t | t | t | t
+(1 row)
+
+SELECT st.heap_blks_read + st.heap_blks_hit >= pr.heap_blks + cl.relpages,
+ st.idx_blks_read + st.idx_blks_hit >= pr.idx_blks + 1
+ FROM pg_statio_user_tables AS st, pg_class AS cl, prevstats AS pr
+ WHERE st.relname='tenk2' AND cl.relname='tenk2';
+ ?column? | ?column?
+----------+----------
+ t | t
+(1 row)
+
+SELECT pr.snap_ts < pg_stat_get_snapshot_timestamp() as snapshot_newer
+FROM prevstats AS pr;
+ snapshot_newer
+----------------
+ t
+(1 row)
+
+DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
+DROP TABLE prevstats;
+-- ensure that stats accessors handle NULL input correctly
+SELECT pg_stat_get_replication_slot(NULL);
+ pg_stat_get_replication_slot
+------------------------------
+
+(1 row)
+
+-- End of Stats Test
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
new file mode 100644
index 0000000..42a01da
--- /dev/null
+++ b/src/test/regress/expected/stats_ext.out
@@ -0,0 +1,3256 @@
+-- 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;
+$$;
+-- 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
+ FROM pg_statistic_ext s, pg_statistic_ext_data d
+ WHERE s.stxname = 'ab1_a_b_stats'
+ AND d.stxoid = s.oid;
+ stxname | stxdndistinct | stxddependencies | stxdmcv
+---------------+---------------+------------------+---------
+ 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
+-- 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;
+NOTICE: drop cascades to table ab1c
+-- 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');
+ estimated | actual
+-----------+--------
+ 400 | 150
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT a, b FROM stxdinh* WHERE a = 0 AND b = 0');
+ estimated | actual
+-----------+--------
+ 3 | 40
+(1 row)
+
+CREATE STATISTICS stxdinh ON a, b FROM stxdinh;
+VACUUM ANALYZE stxdinh, stxdinh1, stxdinh2;
+-- Since the stats object does not include inherited stats, it should not
+-- affect the estimates
+SELECT * FROM check_estimated_rows('SELECT a, b FROM stxdinh* GROUP BY 1, 2');
+ estimated | actual
+-----------+--------
+ 400 | 150
+(1 row)
+
+-- 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');
+ estimated | actual
+-----------+--------
+ 22 | 40
+(1 row)
+
+DROP TABLE stxdinh, stxdinh1, stxdinh2;
+-- 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, b FROM stxdinp;
+VACUUM ANALYZE stxdinp; -- partitions are processed recursively
+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)
+
+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: relation "ti" is not a table, foreign table, or materialized view
+CREATE STATISTICS tststats.s3 ON a, b FROM tststats.s;
+ERROR: relation "s" is not a table, foreign table, or materialized view
+CREATE STATISTICS tststats.s4 ON a, b FROM tststats.v;
+ERROR: relation "v" is not a table, foreign table, or materialized view
+CREATE STATISTICS tststats.s5 ON a, b FROM tststats.mv;
+CREATE STATISTICS tststats.s6 ON a, b FROM tststats.ty;
+ERROR: relation "ty" is not a table, foreign table, or materialized view
+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;
+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;
+$$;
+NOTICE: stats on toast table not created
+DROP SCHEMA tststats CASCADE;
+NOTICE: drop cascades to 7 other objects
+DETAIL: drop cascades to table tststats.t
+drop cascades to sequence tststats.s
+drop cascades to view tststats.v
+drop cascades to materialized view tststats.mv
+drop cascades to type tststats.ty
+drop cascades to foreign table tststats.f
+drop cascades to table tststats.pt
+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);
+-- 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');
+ estimated | actual
+-----------+--------
+ 1000 | 1000
+(1 row)
+
+-- 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);
+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;
+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);
+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);
+-- 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);
+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;
+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);
+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);
+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');
+ 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);
+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;
+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/strings.out b/src/test/regress/expected/strings.out
new file mode 100644
index 0000000..91aa819
--- /dev/null
+++ b/src/test/regress/expected/strings.out
@@ -0,0 +1,2272 @@
+--
+-- 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 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 back reference in regexp_replace
+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('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"
+-- 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;
+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
+----------
+ f
+(1 row)
+
+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';
+ is_empty
+----------
+ t
+(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;
+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/subscription.out b/src/test/regress/expected/subscription.out
new file mode 100644
index 0000000..590d7f1
--- /dev/null
+++ b/src/test/regress/expected/subscription.out
@@ -0,0 +1,269 @@
+--
+-- SUBSCRIPTION
+--
+CREATE ROLE regress_subscription_user LOGIN SUPERUSER;
+CREATE ROLE regress_subscription_user2;
+CREATE ROLE regress_subscription_user_dummy LOGIN NOSUPERUSER;
+SET SESSION AUTHORIZATION 'regress_subscription_user';
+-- fail - no publications
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'foo';
+ERROR: syntax error at or near ";"
+LINE 1: CREATE SUBSCRIPTION regress_testsub CONNECTION 'foo';
+ ^
+-- fail - no connection
+CREATE SUBSCRIPTION regress_testsub PUBLICATION foo;
+ERROR: syntax error at or near "PUBLICATION"
+LINE 1: CREATE SUBSCRIPTION regress_testsub PUBLICATION foo;
+ ^
+-- fail - cannot do CREATE SUBSCRIPTION CREATE SLOT inside transaction block
+BEGIN;
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'testconn' PUBLICATION testpub WITH (create_slot);
+ERROR: CREATE SUBSCRIPTION ... WITH (create_slot = true) cannot run inside a transaction block
+COMMIT;
+-- fail - invalid connection string
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'testconn' PUBLICATION testpub;
+ERROR: invalid connection string syntax: missing "=" after "testconn" in connection info string
+
+-- fail - duplicate publications
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION foo, testpub, foo WITH (connect = false);
+ERROR: publication name "foo" used more than once
+-- ok
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false);
+WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
+COMMENT ON SUBSCRIPTION regress_testsub IS 'test subscription';
+SELECT obj_description(s.oid, 'pg_subscription') FROM pg_subscription s;
+ obj_description
+-------------------
+ test subscription
+(1 row)
+
+-- fail - name already exists
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false);
+ERROR: subscription "regress_testsub" already exists
+-- fail - must be superuser
+SET SESSION AUTHORIZATION 'regress_subscription_user2';
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION foo WITH (connect = false);
+ERROR: must be superuser to create subscriptions
+SET SESSION AUTHORIZATION 'regress_subscription_user';
+-- fail - invalid option combinations
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, copy_data = true);
+ERROR: connect = false and copy_data = true are mutually exclusive options
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, enabled = true);
+ERROR: connect = false and enabled = true are mutually exclusive options
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, create_slot = true);
+ERROR: connect = false and create_slot = true are mutually exclusive options
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, enabled = true);
+ERROR: slot_name = NONE and enabled = true are mutually exclusive options
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, create_slot = true);
+ERROR: slot_name = NONE and create_slot = true are mutually exclusive options
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE);
+ERROR: subscription with slot_name = NONE must also set enabled = false
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, enabled = false);
+ERROR: subscription with slot_name = NONE must also set create_slot = false
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, create_slot = false);
+ERROR: subscription with slot_name = NONE must also set enabled = false
+-- ok - with slot_name = NONE
+CREATE SUBSCRIPTION regress_testsub3 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, connect = false);
+WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
+-- fail
+ALTER SUBSCRIPTION regress_testsub3 ENABLE;
+ERROR: cannot enable subscription that does not have a slot name
+ALTER SUBSCRIPTION regress_testsub3 REFRESH PUBLICATION;
+ERROR: ALTER SUBSCRIPTION ... REFRESH is not allowed for disabled subscriptions
+DROP SUBSCRIPTION regress_testsub3;
+-- fail - invalid connection string
+ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
+ERROR: invalid connection string syntax: missing "=" after "foobar" in connection info string
+
+\dRs+
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
+-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+-----------------------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | f | off | dbname=regress_doesnotexist
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
+ALTER SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist2';
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
+-- fail
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = '');
+ERROR: replication slot name "" is too short
+-- fail
+ALTER SUBSCRIPTION regress_doesnotexist CONNECTION 'dbname=regress_doesnotexist2';
+ERROR: subscription "regress_doesnotexist" does not exist
+ALTER SUBSCRIPTION regress_testsub SET (create_slot = false);
+ERROR: unrecognized subscription parameter: "create_slot"
+\dRs+
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
+-----------------+---------------------------+---------+---------------------+--------+-----------+--------------------+------------------------------
+ regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | f | off | dbname=regress_doesnotexist2
+(1 row)
+
+BEGIN;
+ALTER SUBSCRIPTION regress_testsub ENABLE;
+\dRs
+ List of subscriptions
+ Name | Owner | Enabled | Publication
+-----------------+---------------------------+---------+---------------------
+ regress_testsub | regress_subscription_user | t | {testpub2,testpub3}
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub DISABLE;
+\dRs
+ List of subscriptions
+ Name | Owner | Enabled | Publication
+-----------------+---------------------------+---------+---------------------
+ regress_testsub | regress_subscription_user | f | {testpub2,testpub3}
+(1 row)
+
+COMMIT;
+-- fail - must be owner of subscription
+SET ROLE regress_subscription_user_dummy;
+ALTER SUBSCRIPTION regress_testsub RENAME TO regress_testsub_dummy;
+ERROR: must be owner of subscription regress_testsub
+RESET ROLE;
+ALTER SUBSCRIPTION regress_testsub RENAME TO regress_testsub_foo;
+ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = local);
+ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
+ERROR: invalid value for parameter "synchronous_commit": "foobar"
+HINT: Available values: local, remote_write, remote_apply, on, off.
+\dRs+
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
+---------------------+---------------------------+---------+---------------------+--------+-----------+--------------------+------------------------------
+ regress_testsub_foo | regress_subscription_user | f | {testpub2,testpub3} | f | f | local | dbname=regress_doesnotexist2
+(1 row)
+
+-- rename back to keep the rest simple
+ALTER SUBSCRIPTION regress_testsub_foo RENAME TO regress_testsub;
+-- fail - new owner must be superuser
+ALTER SUBSCRIPTION regress_testsub OWNER TO regress_subscription_user2;
+ERROR: permission denied to change owner of subscription "regress_testsub"
+HINT: The owner of a subscription must be a superuser.
+ALTER ROLE regress_subscription_user2 SUPERUSER;
+-- now it works
+ALTER SUBSCRIPTION regress_testsub OWNER TO regress_subscription_user2;
+-- fail - cannot do DROP SUBSCRIPTION inside transaction block with slot name
+BEGIN;
+DROP SUBSCRIPTION regress_testsub;
+ERROR: DROP SUBSCRIPTION cannot run inside a transaction block
+COMMIT;
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+-- now it works
+BEGIN;
+DROP SUBSCRIPTION regress_testsub;
+COMMIT;
+DROP SUBSCRIPTION IF EXISTS regress_testsub;
+NOTICE: subscription "regress_testsub" does not exist, skipping
+DROP SUBSCRIPTION regress_testsub; -- fail
+ERROR: subscription "regress_testsub" does not exist
+-- fail - binary must be boolean
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, binary = foo);
+ERROR: binary requires a Boolean value
+-- now it works
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, binary = true);
+WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
+\dRs+
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
+-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+-----------------------------
+ regress_testsub | regress_subscription_user | f | {testpub} | t | f | off | dbname=regress_doesnotexist
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (binary = false);
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+\dRs+
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
+-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+-----------------------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | f | off | dbname=regress_doesnotexist
+(1 row)
+
+DROP SUBSCRIPTION regress_testsub;
+-- fail - streaming must be boolean
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, streaming = foo);
+ERROR: streaming requires a Boolean value
+-- now it works
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, streaming = true);
+WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
+\dRs+
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
+-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+-----------------------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | t | off | dbname=regress_doesnotexist
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+\dRs+
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
+-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+-----------------------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | f | off | dbname=regress_doesnotexist
+(1 row)
+
+-- fail - publication already exists
+ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub WITH (refresh = false);
+ERROR: publication "testpub" is already in subscription "regress_testsub"
+-- fail - publication used more than once
+ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub1 WITH (refresh = false);
+ERROR: publication name "testpub1" used more than once
+-- ok - add two publications into subscription
+ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
+-- fail - publications already exist
+ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
+ERROR: publication "testpub1" is already in subscription "regress_testsub"
+\dRs+
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+--------------------+-----------------------------
+ regress_testsub | regress_subscription_user | f | {testpub,testpub1,testpub2} | f | f | off | dbname=regress_doesnotexist
+(1 row)
+
+-- fail - publication used more then once
+ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub1 WITH (refresh = false);
+ERROR: publication name "testpub1" used more than once
+-- fail - all publications are deleted
+ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub, testpub1, testpub2 WITH (refresh = false);
+ERROR: cannot drop all the publications from a subscription
+-- fail - publication does not exist in subscription
+ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub3 WITH (refresh = false);
+ERROR: publication "testpub3" is not in subscription "regress_testsub"
+-- ok - delete publications
+ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
+\dRs+
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
+-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+-----------------------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | f | off | dbname=regress_doesnotexist
+(1 row)
+
+DROP SUBSCRIPTION regress_testsub;
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION mypub
+ WITH (connect = false, create_slot = false, copy_data = false);
+WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
+ALTER SUBSCRIPTION regress_testsub ENABLE;
+-- fail - ALTER SUBSCRIPTION with refresh is not allowed in a transaction
+-- block or function
+BEGIN;
+ALTER SUBSCRIPTION regress_testsub SET PUBLICATION mypub WITH (refresh = true);
+ERROR: ALTER SUBSCRIPTION with refresh cannot run inside a transaction block
+END;
+BEGIN;
+ALTER SUBSCRIPTION regress_testsub REFRESH PUBLICATION;
+ERROR: ALTER SUBSCRIPTION ... REFRESH cannot run inside a transaction block
+END;
+CREATE FUNCTION func() RETURNS VOID AS
+$$ ALTER SUBSCRIPTION regress_testsub SET PUBLICATION mypub WITH (refresh = true) $$ LANGUAGE SQL;
+SELECT func();
+ERROR: ALTER SUBSCRIPTION with refresh cannot be executed from a function
+CONTEXT: SQL function "func" statement 1
+ALTER SUBSCRIPTION regress_testsub DISABLE;
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+DROP FUNCTION func;
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_subscription_user;
+DROP ROLE regress_subscription_user2;
+DROP ROLE regress_subscription_user_dummy;
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
new file mode 100644
index 0000000..45c75ee
--- /dev/null
+++ b/src/test/regress/expected/subselect.out
@@ -0,0 +1,1859 @@
+--
+-- SUBSELECT
+--
+SELECT 1 AS one WHERE 1 IN (SELECT 1);
+ one
+-----
+ 1
+(1 row)
+
+SELECT 1 AS zero WHERE 1 NOT IN (SELECT 1);
+ zero
+------
+(0 rows)
+
+SELECT 1 AS zero WHERE 1 IN (SELECT 2);
+ zero
+------
+(0 rows)
+
+-- Check grammar's handling of extra parens in assorted contexts
+SELECT * FROM (SELECT 1 AS x) ss;
+ x
+---
+ 1
+(1 row)
+
+SELECT * FROM ((SELECT 1 AS x)) ss;
+ x
+---
+ 1
+(1 row)
+
+(SELECT 2) UNION SELECT 2;
+ ?column?
+----------
+ 2
+(1 row)
+
+((SELECT 2)) UNION SELECT 2;
+ ?column?
+----------
+ 2
+(1 row)
+
+SELECT ((SELECT 2) UNION SELECT 2);
+ ?column?
+----------
+ 2
+(1 row)
+
+SELECT (((SELECT 2)) UNION SELECT 2);
+ ?column?
+----------
+ 2
+(1 row)
+
+SELECT (SELECT ARRAY[1,2,3])[1];
+ array
+-------
+ 1
+(1 row)
+
+SELECT ((SELECT ARRAY[1,2,3]))[2];
+ array
+-------
+ 2
+(1 row)
+
+SELECT (((SELECT ARRAY[1,2,3])))[3];
+ array
+-------
+ 3
+(1 row)
+
+-- Set up some simple test tables
+CREATE TABLE SUBSELECT_TBL (
+ f1 integer,
+ f2 integer,
+ f3 float
+);
+INSERT INTO SUBSELECT_TBL VALUES (1, 2, 3);
+INSERT INTO SUBSELECT_TBL VALUES (2, 3, 4);
+INSERT INTO SUBSELECT_TBL VALUES (3, 4, 5);
+INSERT INTO SUBSELECT_TBL VALUES (1, 1, 1);
+INSERT INTO SUBSELECT_TBL VALUES (2, 2, 2);
+INSERT INTO SUBSELECT_TBL VALUES (3, 3, 3);
+INSERT INTO SUBSELECT_TBL VALUES (6, 7, 8);
+INSERT INTO SUBSELECT_TBL VALUES (8, 9, NULL);
+SELECT * FROM SUBSELECT_TBL;
+ f1 | f2 | f3
+----+----+----
+ 1 | 2 | 3
+ 2 | 3 | 4
+ 3 | 4 | 5
+ 1 | 1 | 1
+ 2 | 2 | 2
+ 3 | 3 | 3
+ 6 | 7 | 8
+ 8 | 9 |
+(8 rows)
+
+-- Uncorrelated subselects
+SELECT f1 AS "Constant Select" FROM SUBSELECT_TBL
+ WHERE f1 IN (SELECT 1);
+ Constant Select
+-----------------
+ 1
+ 1
+(2 rows)
+
+SELECT f1 AS "Uncorrelated Field" FROM SUBSELECT_TBL
+ WHERE f1 IN (SELECT f2 FROM SUBSELECT_TBL);
+ Uncorrelated Field
+--------------------
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+(6 rows)
+
+SELECT f1 AS "Uncorrelated Field" FROM SUBSELECT_TBL
+ WHERE f1 IN (SELECT f2 FROM SUBSELECT_TBL WHERE
+ f2 IN (SELECT f1 FROM SUBSELECT_TBL));
+ Uncorrelated Field
+--------------------
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+(6 rows)
+
+SELECT f1, f2
+ FROM SUBSELECT_TBL
+ WHERE (f1, f2) NOT IN (SELECT f2, CAST(f3 AS int4) FROM SUBSELECT_TBL
+ WHERE f3 IS NOT NULL);
+ f1 | f2
+----+----
+ 1 | 2
+ 6 | 7
+ 8 | 9
+(3 rows)
+
+-- Correlated subselects
+SELECT f1 AS "Correlated Field", f2 AS "Second Field"
+ FROM SUBSELECT_TBL upper
+ WHERE f1 IN (SELECT f2 FROM SUBSELECT_TBL WHERE f1 = upper.f1);
+ Correlated Field | Second Field
+------------------+--------------
+ 1 | 2
+ 2 | 3
+ 3 | 4
+ 1 | 1
+ 2 | 2
+ 3 | 3
+(6 rows)
+
+SELECT f1 AS "Correlated Field", f3 AS "Second Field"
+ FROM SUBSELECT_TBL upper
+ WHERE f1 IN
+ (SELECT f2 FROM SUBSELECT_TBL WHERE CAST(upper.f2 AS float) = f3);
+ Correlated Field | Second Field
+------------------+--------------
+ 2 | 4
+ 3 | 5
+ 1 | 1
+ 2 | 2
+ 3 | 3
+(5 rows)
+
+SELECT f1 AS "Correlated Field", f3 AS "Second Field"
+ FROM SUBSELECT_TBL upper
+ WHERE f3 IN (SELECT upper.f1 + f2 FROM SUBSELECT_TBL
+ WHERE f2 = CAST(f3 AS integer));
+ Correlated Field | Second Field
+------------------+--------------
+ 1 | 3
+ 2 | 4
+ 3 | 5
+ 6 | 8
+(4 rows)
+
+SELECT f1 AS "Correlated Field"
+ FROM SUBSELECT_TBL
+ WHERE (f1, f2) IN (SELECT f2, CAST(f3 AS int4) FROM SUBSELECT_TBL
+ WHERE f3 IS NOT NULL);
+ Correlated Field
+------------------
+ 2
+ 3
+ 1
+ 2
+ 3
+(5 rows)
+
+--
+-- Use some existing tables in the regression test
+--
+SELECT ss.f1 AS "Correlated Field", ss.f3 AS "Second Field"
+ FROM SUBSELECT_TBL ss
+ WHERE f1 NOT IN (SELECT f1+1 FROM INT4_TBL
+ WHERE f1 != ss.f1 AND f1 < 2147483647);
+ Correlated Field | Second Field
+------------------+--------------
+ 2 | 4
+ 3 | 5
+ 2 | 2
+ 3 | 3
+ 6 | 8
+ 8 |
+(6 rows)
+
+select q1, float8(count(*)) / (select count(*) from int8_tbl)
+from int8_tbl group by q1 order by q1;
+ q1 | ?column?
+------------------+----------
+ 123 | 0.4
+ 4567890123456789 | 0.6
+(2 rows)
+
+-- Unspecified-type literals in output columns should resolve as text
+SELECT *, pg_typeof(f1) FROM
+ (SELECT 'foo' AS f1 FROM generate_series(1,3)) ss ORDER BY 1;
+ f1 | pg_typeof
+-----+-----------
+ foo | text
+ foo | text
+ foo | text
+(3 rows)
+
+-- ... unless there's context to suggest differently
+explain (verbose, costs off) select '42' union all select '43';
+ QUERY PLAN
+----------------------------
+ Append
+ -> Result
+ Output: '42'::text
+ -> Result
+ Output: '43'::text
+(5 rows)
+
+explain (verbose, costs off) select '42' union all select 43;
+ QUERY PLAN
+--------------------
+ Append
+ -> Result
+ Output: 42
+ -> Result
+ Output: 43
+(5 rows)
+
+-- check materialization of an initplan reference (bug #14524)
+explain (verbose, costs off)
+select 1 = all (select (select 1));
+ QUERY PLAN
+-----------------------------------
+ Result
+ Output: (SubPlan 2)
+ SubPlan 2
+ -> Materialize
+ Output: ($0)
+ InitPlan 1 (returns $0)
+ -> Result
+ Output: 1
+ -> Result
+ Output: $0
+(10 rows)
+
+select 1 = all (select (select 1));
+ ?column?
+----------
+ t
+(1 row)
+
+--
+-- Check EXISTS simplification with LIMIT
+--
+explain (costs off)
+select * from int4_tbl o where exists
+ (select 1 from int4_tbl i where i.f1=o.f1 limit null);
+ QUERY PLAN
+------------------------------------
+ Hash Semi Join
+ Hash Cond: (o.f1 = i.f1)
+ -> Seq Scan on int4_tbl o
+ -> Hash
+ -> Seq Scan on int4_tbl i
+(5 rows)
+
+explain (costs off)
+select * from int4_tbl o where not exists
+ (select 1 from int4_tbl i where i.f1=o.f1 limit 1);
+ QUERY PLAN
+------------------------------------
+ Hash Anti Join
+ Hash Cond: (o.f1 = i.f1)
+ -> Seq Scan on int4_tbl o
+ -> Hash
+ -> Seq Scan on int4_tbl i
+(5 rows)
+
+explain (costs off)
+select * from int4_tbl o where exists
+ (select 1 from int4_tbl i where i.f1=o.f1 limit 0);
+ QUERY PLAN
+--------------------------------------
+ Seq Scan on int4_tbl o
+ Filter: (SubPlan 1)
+ SubPlan 1
+ -> Limit
+ -> Seq Scan on int4_tbl i
+ Filter: (f1 = o.f1)
+(6 rows)
+
+--
+-- Test cases to catch unpleasant interactions between IN-join processing
+-- and subquery pullup.
+--
+select count(*) from
+ (select 1 from tenk1 a
+ where unique1 IN (select hundred from tenk1 b)) ss;
+ count
+-------
+ 100
+(1 row)
+
+select count(distinct ss.ten) from
+ (select ten from tenk1 a
+ where unique1 IN (select hundred from tenk1 b)) ss;
+ count
+-------
+ 10
+(1 row)
+
+select count(*) from
+ (select 1 from tenk1 a
+ where unique1 IN (select distinct hundred from tenk1 b)) ss;
+ count
+-------
+ 100
+(1 row)
+
+select count(distinct ss.ten) from
+ (select ten from tenk1 a
+ where unique1 IN (select distinct hundred from tenk1 b)) ss;
+ count
+-------
+ 10
+(1 row)
+
+--
+-- Test cases to check for overenthusiastic optimization of
+-- "IN (SELECT DISTINCT ...)" and related cases. Per example from
+-- Luca Pireddu and Michael Fuhr.
+--
+CREATE TEMP TABLE foo (id integer);
+CREATE TEMP TABLE bar (id1 integer, id2 integer);
+INSERT INTO foo VALUES (1);
+INSERT INTO bar VALUES (1, 1);
+INSERT INTO bar VALUES (2, 2);
+INSERT INTO bar VALUES (3, 1);
+-- These cases require an extra level of distinct-ing above subquery s
+SELECT * FROM foo WHERE id IN
+ (SELECT id2 FROM (SELECT DISTINCT id1, id2 FROM bar) AS s);
+ id
+----
+ 1
+(1 row)
+
+SELECT * FROM foo WHERE id IN
+ (SELECT id2 FROM (SELECT id1,id2 FROM bar GROUP BY id1,id2) AS s);
+ id
+----
+ 1
+(1 row)
+
+SELECT * FROM foo WHERE id IN
+ (SELECT id2 FROM (SELECT id1, id2 FROM bar UNION
+ SELECT id1, id2 FROM bar) AS s);
+ id
+----
+ 1
+(1 row)
+
+-- These cases do not
+SELECT * FROM foo WHERE id IN
+ (SELECT id2 FROM (SELECT DISTINCT ON (id2) id1, id2 FROM bar) AS s);
+ id
+----
+ 1
+(1 row)
+
+SELECT * FROM foo WHERE id IN
+ (SELECT id2 FROM (SELECT id2 FROM bar GROUP BY id2) AS s);
+ id
+----
+ 1
+(1 row)
+
+SELECT * FROM foo WHERE id IN
+ (SELECT id2 FROM (SELECT id2 FROM bar UNION
+ SELECT id2 FROM bar) AS s);
+ id
+----
+ 1
+(1 row)
+
+--
+-- Test case to catch problems with multiply nested sub-SELECTs not getting
+-- recalculated properly. Per bug report from Didier Moens.
+--
+CREATE TABLE orderstest (
+ approver_ref integer,
+ po_ref integer,
+ ordercanceled boolean
+);
+INSERT INTO orderstest VALUES (1, 1, false);
+INSERT INTO orderstest VALUES (66, 5, false);
+INSERT INTO orderstest VALUES (66, 6, false);
+INSERT INTO orderstest VALUES (66, 7, false);
+INSERT INTO orderstest VALUES (66, 1, true);
+INSERT INTO orderstest VALUES (66, 8, false);
+INSERT INTO orderstest VALUES (66, 1, false);
+INSERT INTO orderstest VALUES (77, 1, false);
+INSERT INTO orderstest VALUES (1, 1, false);
+INSERT INTO orderstest VALUES (66, 1, false);
+INSERT INTO orderstest VALUES (1, 1, false);
+CREATE VIEW orders_view AS
+SELECT *,
+(SELECT CASE
+ WHEN ord.approver_ref=1 THEN '---' ELSE 'Approved'
+ END) AS "Approved",
+(SELECT CASE
+ WHEN ord.ordercanceled
+ THEN 'Canceled'
+ ELSE
+ (SELECT CASE
+ WHEN ord.po_ref=1
+ THEN
+ (SELECT CASE
+ WHEN ord.approver_ref=1
+ THEN '---'
+ ELSE 'Approved'
+ END)
+ ELSE 'PO'
+ END)
+END) AS "Status",
+(CASE
+ WHEN ord.ordercanceled
+ THEN 'Canceled'
+ ELSE
+ (CASE
+ WHEN ord.po_ref=1
+ THEN
+ (CASE
+ WHEN ord.approver_ref=1
+ THEN '---'
+ ELSE 'Approved'
+ END)
+ ELSE 'PO'
+ END)
+END) AS "Status_OK"
+FROM orderstest ord;
+SELECT * FROM orders_view;
+ approver_ref | po_ref | ordercanceled | Approved | Status | Status_OK
+--------------+--------+---------------+----------+----------+-----------
+ 1 | 1 | f | --- | --- | ---
+ 66 | 5 | f | Approved | PO | PO
+ 66 | 6 | f | Approved | PO | PO
+ 66 | 7 | f | Approved | PO | PO
+ 66 | 1 | t | Approved | Canceled | Canceled
+ 66 | 8 | f | Approved | PO | PO
+ 66 | 1 | f | Approved | Approved | Approved
+ 77 | 1 | f | Approved | Approved | Approved
+ 1 | 1 | f | --- | --- | ---
+ 66 | 1 | f | Approved | Approved | Approved
+ 1 | 1 | f | --- | --- | ---
+(11 rows)
+
+DROP TABLE orderstest cascade;
+NOTICE: drop cascades to view orders_view
+--
+-- Test cases to catch situations where rule rewriter fails to propagate
+-- hasSubLinks flag correctly. Per example from Kyle Bateman.
+--
+create temp table parts (
+ partnum text,
+ cost float8
+);
+create temp table shipped (
+ ttype char(2),
+ ordnum int4,
+ partnum text,
+ value float8
+);
+create temp view shipped_view as
+ select * from shipped where ttype = 'wt';
+create rule shipped_view_insert as on insert to shipped_view do instead
+ insert into shipped values('wt', new.ordnum, new.partnum, new.value);
+insert into parts (partnum, cost) values (1, 1234.56);
+insert into shipped_view (ordnum, partnum, value)
+ values (0, 1, (select cost from parts where partnum = '1'));
+select * from shipped_view;
+ ttype | ordnum | partnum | value
+-------+--------+---------+---------
+ wt | 0 | 1 | 1234.56
+(1 row)
+
+create rule shipped_view_update as on update to shipped_view do instead
+ update shipped set partnum = new.partnum, value = new.value
+ where ttype = new.ttype and ordnum = new.ordnum;
+update shipped_view set value = 11
+ from int4_tbl a join int4_tbl b
+ on (a.f1 = (select f1 from int4_tbl c where c.f1=b.f1))
+ where ordnum = a.f1;
+select * from shipped_view;
+ ttype | ordnum | partnum | value
+-------+--------+---------+-------
+ wt | 0 | 1 | 11
+(1 row)
+
+select f1, ss1 as relabel from
+ (select *, (select sum(f1) from int4_tbl b where f1 >= a.f1) as ss1
+ from int4_tbl a) ss;
+ f1 | relabel
+-------------+------------
+ 0 | 2147607103
+ 123456 | 2147607103
+ -123456 | 2147483647
+ 2147483647 | 2147483647
+ -2147483647 | 0
+(5 rows)
+
+--
+-- Test cases involving PARAM_EXEC parameters and min/max index optimizations.
+-- Per bug report from David Sanchez i Gregori.
+--
+select * from (
+ select max(unique1) from tenk1 as a
+ where exists (select 1 from tenk1 as b where b.thousand = a.unique2)
+) ss;
+ max
+------
+ 9997
+(1 row)
+
+select * from (
+ select min(unique1) from tenk1 as a
+ where not exists (select 1 from tenk1 as b where b.unique2 = 10000)
+) ss;
+ min
+-----
+ 0
+(1 row)
+
+--
+-- Test that an IN implemented using a UniquePath does unique-ification
+-- with the right semantics, as per bug #4113. (Unfortunately we have
+-- no simple way to ensure that this test case actually chooses that type
+-- of plan, but it does in releases 7.4-8.3. Note that an ordering difference
+-- here might mean that some other plan type is being used, rendering the test
+-- pointless.)
+--
+create temp table numeric_table (num_col numeric);
+insert into numeric_table values (1), (1.000000000000000000001), (2), (3);
+create temp table float_table (float_col float8);
+insert into float_table values (1), (2), (3);
+select * from float_table
+ where float_col in (select num_col from numeric_table);
+ float_col
+-----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select * from numeric_table
+ where num_col in (select float_col from float_table);
+ num_col
+-------------------------
+ 1
+ 1.000000000000000000001
+ 2
+ 3
+(4 rows)
+
+--
+-- Test case for bug #4290: bogus calculation of subplan param sets
+--
+create temp table ta (id int primary key, val int);
+insert into ta values(1,1);
+insert into ta values(2,2);
+create temp table tb (id int primary key, aval int);
+insert into tb values(1,1);
+insert into tb values(2,1);
+insert into tb values(3,2);
+insert into tb values(4,2);
+create temp table tc (id int primary key, aid int);
+insert into tc values(1,1);
+insert into tc values(2,2);
+select
+ ( select min(tb.id) from tb
+ where tb.aval = (select ta.val from ta where ta.id = tc.aid) ) as min_tb_id
+from tc;
+ min_tb_id
+-----------
+ 1
+ 3
+(2 rows)
+
+--
+-- Test case for 8.3 "failed to locate grouping columns" bug
+--
+create temp table t1 (f1 numeric(14,0), f2 varchar(30));
+select * from
+ (select distinct f1, f2, (select f2 from t1 x where x.f1 = up.f1) as fs
+ from t1 up) ss
+group by f1,f2,fs;
+ f1 | f2 | fs
+----+----+----
+(0 rows)
+
+--
+-- Test case for bug #5514 (mishandling of whole-row Vars in subselects)
+--
+create temp table table_a(id integer);
+insert into table_a values (42);
+create temp view view_a as select * from table_a;
+select view_a from view_a;
+ view_a
+--------
+ (42)
+(1 row)
+
+select (select view_a) from view_a;
+ view_a
+--------
+ (42)
+(1 row)
+
+select (select (select view_a)) from view_a;
+ view_a
+--------
+ (42)
+(1 row)
+
+select (select (a.*)::text) from view_a a;
+ a
+------
+ (42)
+(1 row)
+
+--
+-- Check that whole-row Vars reading the result of a subselect don't include
+-- any junk columns therein
+--
+select q from (select max(f1) from int4_tbl group by f1 order by f1) q;
+ q
+---------------
+ (-2147483647)
+ (-123456)
+ (0)
+ (123456)
+ (2147483647)
+(5 rows)
+
+with q as (select max(f1) from int4_tbl group by f1 order by f1)
+ select q from q;
+ q
+---------------
+ (-2147483647)
+ (-123456)
+ (0)
+ (123456)
+ (2147483647)
+(5 rows)
+
+--
+-- Test case for sublinks pulled up into joinaliasvars lists in an
+-- inherited update/delete query
+--
+begin; -- this shouldn't delete anything, but be safe
+delete from road
+where exists (
+ select 1
+ from
+ int4_tbl cross join
+ ( select f1, array(select q1 from int8_tbl) as arr
+ from text_tbl ) ss
+ where road.name = ss.f1 );
+rollback;
+--
+-- Test case for sublinks pushed down into subselects via join alias expansion
+--
+select
+ (select sq1) as qq1
+from
+ (select exists(select 1 from int4_tbl where f1 = q2) as sq1, 42 as dummy
+ from int8_tbl) sq0
+ join
+ int4_tbl i4 on dummy = i4.f1;
+ qq1
+-----
+(0 rows)
+
+--
+-- Test case for subselect within UPDATE of INSERT...ON CONFLICT DO UPDATE
+--
+create temp table upsert(key int4 primary key, val text);
+insert into upsert values(1, 'val') on conflict (key) do update set val = 'not seen';
+insert into upsert values(1, 'val') on conflict (key) do update set val = 'seen with subselect ' || (select f1 from int4_tbl where f1 != 0 limit 1)::text;
+select * from upsert;
+ key | val
+-----+----------------------------
+ 1 | seen with subselect 123456
+(1 row)
+
+with aa as (select 'int4_tbl' u from int4_tbl limit 1)
+insert into upsert values (1, 'x'), (999, 'y')
+on conflict (key) do update set val = (select u from aa)
+returning *;
+ key | val
+-----+----------
+ 1 | int4_tbl
+ 999 | y
+(2 rows)
+
+--
+-- Test case for cross-type partial matching in hashed subplan (bug #7597)
+--
+create temp table outer_7597 (f1 int4, f2 int4);
+insert into outer_7597 values (0, 0);
+insert into outer_7597 values (1, 0);
+insert into outer_7597 values (0, null);
+insert into outer_7597 values (1, null);
+create temp table inner_7597(c1 int8, c2 int8);
+insert into inner_7597 values(0, null);
+select * from outer_7597 where (f1, f2) not in (select * from inner_7597);
+ f1 | f2
+----+----
+ 1 | 0
+ 1 |
+(2 rows)
+
+--
+-- Similar test case using text that verifies that collation
+-- information is passed through by execTuplesEqual() in nodeSubplan.c
+-- (otherwise it would error in texteq())
+--
+create temp table outer_text (f1 text, f2 text);
+insert into outer_text values ('a', 'a');
+insert into outer_text values ('b', 'a');
+insert into outer_text values ('a', null);
+insert into outer_text values ('b', null);
+create temp table inner_text (c1 text, c2 text);
+insert into inner_text values ('a', null);
+insert into inner_text values ('123', '456');
+select * from outer_text where (f1, f2) not in (select * from inner_text);
+ f1 | f2
+----+----
+ b | a
+ b |
+(2 rows)
+
+--
+-- Another test case for cross-type hashed subplans: comparison of
+-- inner-side values must be done with appropriate operator
+--
+explain (verbose, costs off)
+select 'foo'::text in (select 'bar'::name union all select 'bar'::name);
+ QUERY PLAN
+-------------------------------------
+ Result
+ Output: (hashed SubPlan 1)
+ SubPlan 1
+ -> Append
+ -> Result
+ Output: 'bar'::name
+ -> Result
+ Output: 'bar'::name
+(8 rows)
+
+select 'foo'::text in (select 'bar'::name union all select 'bar'::name);
+ ?column?
+----------
+ f
+(1 row)
+
+--
+-- Test that we don't try to hash nested records (bug #17363)
+-- (Hashing could be supported, but for now we don't)
+--
+explain (verbose, costs off)
+select row(row(row(1))) = any (select row(row(1)));
+ QUERY PLAN
+-------------------------------------------
+ Result
+ Output: (SubPlan 1)
+ SubPlan 1
+ -> Materialize
+ Output: '("(1)")'::record
+ -> Result
+ Output: '("(1)")'::record
+(7 rows)
+
+select row(row(row(1))) = any (select row(row(1)));
+ ?column?
+----------
+ t
+(1 row)
+
+--
+-- Test case for premature memory release during hashing of subplan output
+--
+select '1'::text in (select '1'::name union all select '1'::name);
+ ?column?
+----------
+ t
+(1 row)
+
+--
+-- Test that we don't try to use a hashed subplan if the simplified
+-- testexpr isn't of the right shape
+--
+-- this fails by default, of course
+select * from int8_tbl where q1 in (select c1 from inner_text);
+ERROR: operator does not exist: bigint = text
+LINE 1: select * from int8_tbl where q1 in (select c1 from inner_tex...
+ ^
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+begin;
+-- make an operator to allow it to succeed
+create function bogus_int8_text_eq(int8, text) returns boolean
+language sql as 'select $1::text = $2';
+create operator = (procedure=bogus_int8_text_eq, leftarg=int8, rightarg=text);
+explain (costs off)
+select * from int8_tbl where q1 in (select c1 from inner_text);
+ QUERY PLAN
+--------------------------------
+ Seq Scan on int8_tbl
+ Filter: (hashed SubPlan 1)
+ SubPlan 1
+ -> Seq Scan on inner_text
+(4 rows)
+
+select * from int8_tbl where q1 in (select c1 from inner_text);
+ q1 | q2
+-----+------------------
+ 123 | 456
+ 123 | 4567890123456789
+(2 rows)
+
+-- inlining of this function results in unusual number of hash clauses,
+-- which we can still cope with
+create or replace function bogus_int8_text_eq(int8, text) returns boolean
+language sql as 'select $1::text = $2 and $1::text = $2';
+explain (costs off)
+select * from int8_tbl where q1 in (select c1 from inner_text);
+ QUERY PLAN
+--------------------------------
+ Seq Scan on int8_tbl
+ Filter: (hashed SubPlan 1)
+ SubPlan 1
+ -> Seq Scan on inner_text
+(4 rows)
+
+select * from int8_tbl where q1 in (select c1 from inner_text);
+ q1 | q2
+-----+------------------
+ 123 | 456
+ 123 | 4567890123456789
+(2 rows)
+
+-- inlining of this function causes LHS and RHS to be switched,
+-- which we can't cope with, so hashing should be abandoned
+create or replace function bogus_int8_text_eq(int8, text) returns boolean
+language sql as 'select $2 = $1::text';
+explain (costs off)
+select * from int8_tbl where q1 in (select c1 from inner_text);
+ QUERY PLAN
+--------------------------------------
+ Seq Scan on int8_tbl
+ Filter: (SubPlan 1)
+ SubPlan 1
+ -> Materialize
+ -> Seq Scan on inner_text
+(5 rows)
+
+select * from int8_tbl where q1 in (select c1 from inner_text);
+ q1 | q2
+-----+------------------
+ 123 | 456
+ 123 | 4567890123456789
+(2 rows)
+
+rollback; -- to get rid of the bogus operator
+--
+-- Test resolution of hashed vs non-hashed implementation of EXISTS subplan
+--
+explain (costs off)
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0);
+ QUERY PLAN
+--------------------------------------------------------------
+ Aggregate
+ -> Seq Scan on tenk1 t
+ Filter: ((hashed SubPlan 2) OR (ten < 0))
+ SubPlan 2
+ -> Index Only Scan using tenk1_unique1 on tenk1 k
+(5 rows)
+
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0);
+ count
+-------
+ 10000
+(1 row)
+
+explain (costs off)
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0)
+ and thousand = 1;
+ QUERY PLAN
+--------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on tenk1 t
+ Recheck Cond: (thousand = 1)
+ Filter: ((SubPlan 1) OR (ten < 0))
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: (thousand = 1)
+ SubPlan 1
+ -> Index Only Scan using tenk1_unique1 on tenk1 k
+ Index Cond: (unique1 = t.unique2)
+(9 rows)
+
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0)
+ and thousand = 1;
+ count
+-------
+ 10
+(1 row)
+
+-- It's possible for the same EXISTS to get resolved both ways
+create temp table exists_tbl (c1 int, c2 int, c3 int) partition by list (c1);
+create temp table exists_tbl_null partition of exists_tbl for values in (null);
+create temp table exists_tbl_def partition of exists_tbl default;
+insert into exists_tbl select x, x/2, x+1 from generate_series(0,10) x;
+analyze exists_tbl;
+explain (costs off)
+select * from exists_tbl t1
+ where (exists(select 1 from exists_tbl t2 where t1.c1 = t2.c2) or c3 < 0);
+ QUERY PLAN
+------------------------------------------------------
+ Append
+ -> Seq Scan on exists_tbl_null t1_1
+ Filter: ((SubPlan 1) OR (c3 < 0))
+ SubPlan 1
+ -> Append
+ -> Seq Scan on exists_tbl_null t2_1
+ Filter: (t1_1.c1 = c2)
+ -> Seq Scan on exists_tbl_def t2_2
+ Filter: (t1_1.c1 = c2)
+ -> Seq Scan on exists_tbl_def t1_2
+ Filter: ((hashed SubPlan 2) OR (c3 < 0))
+ SubPlan 2
+ -> Append
+ -> Seq Scan on exists_tbl_null t2_4
+ -> Seq Scan on exists_tbl_def t2_5
+(15 rows)
+
+select * from exists_tbl t1
+ where (exists(select 1 from exists_tbl t2 where t1.c1 = t2.c2) or c3 < 0);
+ c1 | c2 | c3
+----+----+----
+ 0 | 0 | 1
+ 1 | 0 | 2
+ 2 | 1 | 3
+ 3 | 1 | 4
+ 4 | 2 | 5
+ 5 | 2 | 6
+(6 rows)
+
+--
+-- Test case for planner bug with nested EXISTS handling
+--
+select a.thousand from tenk1 a, tenk1 b
+where a.thousand = b.thousand
+ and exists ( select 1 from tenk1 c where b.hundred = c.hundred
+ and not exists ( select 1 from tenk1 d
+ where a.thousand = d.thousand ) );
+ thousand
+----------
+(0 rows)
+
+--
+-- Check that nested sub-selects are not pulled up if they contain volatiles
+--
+explain (verbose, costs off)
+ select x, x from
+ (select (select now()) as x from (values(1),(2)) v(y)) ss;
+ QUERY PLAN
+---------------------------
+ Values Scan on "*VALUES*"
+ Output: $0, $1
+ InitPlan 1 (returns $0)
+ -> Result
+ Output: now()
+ InitPlan 2 (returns $1)
+ -> Result
+ Output: now()
+(8 rows)
+
+explain (verbose, costs off)
+ select x, x from
+ (select (select random()) as x from (values(1),(2)) v(y)) ss;
+ QUERY PLAN
+----------------------------------
+ Subquery Scan on ss
+ Output: ss.x, ss.x
+ -> Values Scan on "*VALUES*"
+ Output: $0
+ InitPlan 1 (returns $0)
+ -> Result
+ Output: random()
+(7 rows)
+
+explain (verbose, costs off)
+ select x, x from
+ (select (select now() where y=y) as x from (values(1),(2)) v(y)) ss;
+ QUERY PLAN
+----------------------------------------------------------------------
+ Values Scan on "*VALUES*"
+ Output: (SubPlan 1), (SubPlan 2)
+ SubPlan 1
+ -> Result
+ Output: now()
+ One-Time Filter: ("*VALUES*".column1 = "*VALUES*".column1)
+ SubPlan 2
+ -> Result
+ Output: now()
+ One-Time Filter: ("*VALUES*".column1 = "*VALUES*".column1)
+(10 rows)
+
+explain (verbose, costs off)
+ select x, x from
+ (select (select random() where y=y) as x from (values(1),(2)) v(y)) ss;
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Subquery Scan on ss
+ Output: ss.x, ss.x
+ -> Values Scan on "*VALUES*"
+ Output: (SubPlan 1)
+ SubPlan 1
+ -> Result
+ Output: random()
+ One-Time Filter: ("*VALUES*".column1 = "*VALUES*".column1)
+(8 rows)
+
+--
+-- Test rescan of a hashed subplan (the use of random() is to prevent the
+-- sub-select from being pulled up, which would result in not hashing)
+--
+explain (verbose, costs off)
+select sum(ss.tst::int) from
+ onek o cross join lateral (
+ select i.ten in (select f1 from int4_tbl where f1 <= o.hundred) as tst,
+ random() as r
+ from onek i where i.unique1 = o.unique1 ) ss
+where o.ten = 0;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+ Output: sum((((hashed SubPlan 1)))::integer)
+ -> Nested Loop
+ Output: ((hashed SubPlan 1))
+ -> Seq Scan on public.onek o
+ Output: o.unique1, o.unique2, o.two, o.four, o.ten, o.twenty, o.hundred, o.thousand, o.twothousand, o.fivethous, o.tenthous, o.odd, o.even, o.stringu1, o.stringu2, o.string4
+ Filter: (o.ten = 0)
+ -> Index Scan using onek_unique1 on public.onek i
+ Output: (hashed SubPlan 1), random()
+ Index Cond: (i.unique1 = o.unique1)
+ SubPlan 1
+ -> Seq Scan on public.int4_tbl
+ Output: int4_tbl.f1
+ Filter: (int4_tbl.f1 <= $0)
+(14 rows)
+
+select sum(ss.tst::int) from
+ onek o cross join lateral (
+ select i.ten in (select f1 from int4_tbl where f1 <= o.hundred) as tst,
+ random() as r
+ from onek i where i.unique1 = o.unique1 ) ss
+where o.ten = 0;
+ sum
+-----
+ 100
+(1 row)
+
+--
+-- Test rescan of a SetOp node
+--
+explain (costs off)
+select count(*) from
+ onek o cross join lateral (
+ select * from onek i1 where i1.unique1 = o.unique1
+ except
+ select * from onek i2 where i2.unique1 = o.unique2
+ ) ss
+where o.ten = 1;
+ QUERY PLAN
+------------------------------------------------------------------------------
+ Aggregate
+ -> Nested Loop
+ -> Seq Scan on onek o
+ Filter: (ten = 1)
+ -> Subquery Scan on ss
+ -> HashSetOp Except
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Index Scan using onek_unique1 on onek i1
+ Index Cond: (unique1 = o.unique1)
+ -> Subquery Scan on "*SELECT* 2"
+ -> Index Scan using onek_unique1 on onek i2
+ Index Cond: (unique1 = o.unique2)
+(13 rows)
+
+select count(*) from
+ onek o cross join lateral (
+ select * from onek i1 where i1.unique1 = o.unique1
+ except
+ select * from onek i2 where i2.unique1 = o.unique2
+ ) ss
+where o.ten = 1;
+ count
+-------
+ 100
+(1 row)
+
+--
+-- Test rescan of a RecursiveUnion node
+--
+explain (costs off)
+select sum(o.four), sum(ss.a) from
+ onek o cross join lateral (
+ with recursive x(a) as
+ (select o.four as a
+ union
+ select a + 1 from x
+ where a < 10)
+ select * from x
+ ) ss
+where o.ten = 1;
+ QUERY PLAN
+---------------------------------------------------------
+ Aggregate
+ -> Nested Loop
+ -> Seq Scan on onek o
+ Filter: (ten = 1)
+ -> Memoize
+ Cache Key: o.four
+ Cache Mode: binary
+ -> CTE Scan on x
+ CTE x
+ -> Recursive Union
+ -> Result
+ -> WorkTable Scan on x x_1
+ Filter: (a < 10)
+(13 rows)
+
+select sum(o.four), sum(ss.a) from
+ onek o cross join lateral (
+ with recursive x(a) as
+ (select o.four as a
+ union
+ select a + 1 from x
+ where a < 10)
+ select * from x
+ ) ss
+where o.ten = 1;
+ sum | sum
+------+------
+ 1700 | 5350
+(1 row)
+
+--
+-- Check we don't misoptimize a NOT IN where the subquery returns no rows.
+--
+create temp table notinouter (a int);
+create temp table notininner (b int not null);
+insert into notinouter values (null), (1);
+select * from notinouter where a not in (select b from notininner);
+ a
+---
+
+ 1
+(2 rows)
+
+--
+-- Check we behave sanely in corner case of empty SELECT list (bug #8648)
+--
+create temp table nocolumns();
+select exists(select * from nocolumns);
+ exists
+--------
+ f
+(1 row)
+
+--
+-- Check behavior with a SubPlan in VALUES (bug #14924)
+--
+select val.x
+ from generate_series(1,10) as s(i),
+ lateral (
+ values ((select s.i + 1)), (s.i + 101)
+ ) as val(x)
+where s.i < 10 and (select val.x) < 110;
+ x
+-----
+ 2
+ 102
+ 3
+ 103
+ 4
+ 104
+ 5
+ 105
+ 6
+ 106
+ 7
+ 107
+ 8
+ 108
+ 9
+ 109
+ 10
+(17 rows)
+
+-- 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*"
+ Output: "*VALUES*".column1
+ SubPlan 1
+ -> Values Scan on "*VALUES*_1"
+ Output: "*VALUES*_1".column1
+(5 rows)
+
+select * from
+(values
+ (3 not in (select * from (values (1), (2)) ss1)),
+ (false)
+) ss;
+ column1
+---------
+ t
+ f
+(2 rows)
+
+--
+-- Check sane behavior with nested IN SubLinks
+--
+explain (verbose, costs off)
+select * from int4_tbl where
+ (case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
+ (select ten from tenk1 b);
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop Semi Join
+ Output: int4_tbl.f1
+ Join Filter: (CASE WHEN (hashed SubPlan 1) THEN int4_tbl.f1 ELSE NULL::integer END = b.ten)
+ -> Seq Scan on public.int4_tbl
+ Output: int4_tbl.f1
+ -> Seq Scan on public.tenk1 b
+ Output: b.unique1, b.unique2, b.two, b.four, b.ten, b.twenty, b.hundred, b.thousand, b.twothousand, b.fivethous, b.tenthous, b.odd, b.even, b.stringu1, b.stringu2, b.string4
+ SubPlan 1
+ -> Index Only Scan using tenk1_unique1 on public.tenk1 a
+ Output: a.unique1
+(10 rows)
+
+select * from int4_tbl where
+ (case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
+ (select ten from tenk1 b);
+ f1
+----
+ 0
+(1 row)
+
+--
+-- Check for incorrect optimization when IN subquery contains a SRF
+--
+explain (verbose, costs off)
+select * from int4_tbl o where (f1, f1) in
+ (select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1);
+ QUERY PLAN
+-------------------------------------------------------------------
+ Nested Loop Semi Join
+ Output: o.f1
+ Join Filter: (o.f1 = "ANY_subquery".f1)
+ -> Seq Scan on public.int4_tbl o
+ Output: o.f1
+ -> Materialize
+ Output: "ANY_subquery".f1, "ANY_subquery".g
+ -> Subquery Scan on "ANY_subquery"
+ Output: "ANY_subquery".f1, "ANY_subquery".g
+ Filter: ("ANY_subquery".f1 = "ANY_subquery".g)
+ -> Result
+ Output: i.f1, ((generate_series(1, 50)) / 10)
+ -> ProjectSet
+ Output: generate_series(1, 50), i.f1
+ -> HashAggregate
+ Output: i.f1
+ Group Key: i.f1
+ -> Seq Scan on public.int4_tbl i
+ Output: i.f1
+(19 rows)
+
+select * from int4_tbl o where (f1, f1) in
+ (select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1);
+ f1
+----
+ 0
+(1 row)
+
+--
+-- check for over-optimization of whole-row Var referencing an Append plan
+--
+select (select q from
+ (select 1,2,3 where f1 > 0
+ union all
+ select 4,5,6.0 where f1 <= 0
+ ) q )
+from int4_tbl;
+ q
+-----------
+ (4,5,6.0)
+ (1,2,3)
+ (4,5,6.0)
+ (1,2,3)
+ (4,5,6.0)
+(5 rows)
+
+--
+-- Check for sane handling of a lateral reference in a subquery's quals
+-- (most of the complication here is to prevent the test case from being
+-- flattened too much)
+--
+explain (verbose, costs off)
+select * from
+ int4_tbl i4,
+ lateral (
+ select i4.f1 > 1 as b, 1 as id
+ from (select random() order by 1) as t1
+ union all
+ select true as b, 2 as id
+ ) as t2
+where b and f1 >= 0;
+ QUERY PLAN
+--------------------------------------------
+ Nested Loop
+ Output: i4.f1, ((i4.f1 > 1)), (1)
+ -> Seq Scan on public.int4_tbl i4
+ Output: i4.f1
+ Filter: (i4.f1 >= 0)
+ -> Append
+ -> Subquery Scan on t1
+ Output: (i4.f1 > 1), 1
+ Filter: (i4.f1 > 1)
+ -> Sort
+ Output: (random())
+ Sort Key: (random())
+ -> Result
+ Output: random()
+ -> Result
+ Output: true, 2
+(16 rows)
+
+select * from
+ int4_tbl i4,
+ lateral (
+ select i4.f1 > 1 as b, 1 as id
+ from (select random() order by 1) as t1
+ union all
+ select true as b, 2 as id
+ ) as t2
+where b and f1 >= 0;
+ f1 | b | id
+------------+---+----
+ 0 | t | 2
+ 123456 | t | 1
+ 123456 | t | 2
+ 2147483647 | t | 1
+ 2147483647 | t | 2
+(5 rows)
+
+--
+-- Check that volatile quals aren't pushed down past a DISTINCT:
+-- nextval() should not be called more than the nominal number of times
+--
+create temp sequence ts1;
+select * from
+ (select distinct ten from tenk1) ss
+ where ten < 10 + nextval('ts1')
+ order by 1;
+ ten
+-----
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+(10 rows)
+
+select nextval('ts1');
+ nextval
+---------
+ 11
+(1 row)
+
+--
+-- Check that volatile quals aren't pushed down past a set-returning function;
+-- while a nonvolatile qual can be, if it doesn't reference the SRF.
+--
+create function tattle(x int, y int) returns bool
+volatile language plpgsql as $$
+begin
+ raise notice 'x = %, y = %', x, y;
+ return x > y;
+end$$;
+explain (verbose, costs off)
+select * from
+ (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
+ where tattle(x, 8);
+ QUERY PLAN
+----------------------------------------------------------
+ Subquery Scan on ss
+ Output: ss.x, ss.u
+ Filter: tattle(ss.x, 8)
+ -> ProjectSet
+ Output: 9, unnest('{1,2,3,11,12,13}'::integer[])
+ -> Result
+(6 rows)
+
+select * from
+ (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
+ where tattle(x, 8);
+NOTICE: x = 9, y = 8
+NOTICE: x = 9, y = 8
+NOTICE: x = 9, y = 8
+NOTICE: x = 9, y = 8
+NOTICE: x = 9, y = 8
+NOTICE: x = 9, y = 8
+ x | u
+---+----
+ 9 | 1
+ 9 | 2
+ 9 | 3
+ 9 | 11
+ 9 | 12
+ 9 | 13
+(6 rows)
+
+-- if we pretend it's stable, we get different results:
+alter function tattle(x int, y int) stable;
+explain (verbose, costs off)
+select * from
+ (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
+ where tattle(x, 8);
+ QUERY PLAN
+----------------------------------------------------
+ ProjectSet
+ Output: 9, unnest('{1,2,3,11,12,13}'::integer[])
+ -> Result
+ One-Time Filter: tattle(9, 8)
+(4 rows)
+
+select * from
+ (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
+ where tattle(x, 8);
+NOTICE: x = 9, y = 8
+ x | u
+---+----
+ 9 | 1
+ 9 | 2
+ 9 | 3
+ 9 | 11
+ 9 | 12
+ 9 | 13
+(6 rows)
+
+-- although even a stable qual should not be pushed down if it references SRF
+explain (verbose, costs off)
+select * from
+ (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
+ where tattle(x, u);
+ QUERY PLAN
+----------------------------------------------------------
+ Subquery Scan on ss
+ Output: ss.x, ss.u
+ Filter: tattle(ss.x, ss.u)
+ -> ProjectSet
+ Output: 9, unnest('{1,2,3,11,12,13}'::integer[])
+ -> Result
+(6 rows)
+
+select * from
+ (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
+ where tattle(x, u);
+NOTICE: x = 9, y = 1
+NOTICE: x = 9, y = 2
+NOTICE: x = 9, y = 3
+NOTICE: x = 9, y = 11
+NOTICE: x = 9, y = 12
+NOTICE: x = 9, y = 13
+ x | u
+---+---
+ 9 | 1
+ 9 | 2
+ 9 | 3
+(3 rows)
+
+drop function tattle(x int, y int);
+--
+-- Test that LIMIT can be pushed to SORT through a subquery that just projects
+-- columns. We check for that having happened by looking to see if EXPLAIN
+-- ANALYZE shows that a top-N sort was used. We must suppress or filter away
+-- all the non-invariant parts of the EXPLAIN ANALYZE output.
+--
+create table sq_limit (pk int primary key, c1 int, c2 int);
+insert into sq_limit values
+ (1, 1, 1),
+ (2, 2, 2),
+ (3, 3, 3),
+ (4, 4, 4),
+ (5, 1, 1),
+ (6, 2, 2),
+ (7, 3, 3),
+ (8, 4, 4);
+create function explain_sq_limit() returns setof text language plpgsql as
+$$
+declare ln text;
+begin
+ for ln in
+ explain (analyze, summary off, timing off, costs off)
+ select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3
+ loop
+ ln := regexp_replace(ln, 'Memory: \S*', 'Memory: xxx');
+ return next ln;
+ end loop;
+end;
+$$;
+select * from explain_sq_limit();
+ explain_sq_limit
+----------------------------------------------------------------
+ Limit (actual rows=3 loops=1)
+ -> Subquery Scan on x (actual rows=3 loops=1)
+ -> Sort (actual rows=3 loops=1)
+ Sort Key: sq_limit.c1, sq_limit.pk
+ Sort Method: top-N heapsort Memory: xxx
+ -> Seq Scan on sq_limit (actual rows=8 loops=1)
+(6 rows)
+
+select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3;
+ pk | c2
+----+----
+ 1 | 1
+ 5 | 1
+ 2 | 2
+(3 rows)
+
+drop function explain_sq_limit();
+drop table sq_limit;
+--
+-- Ensure that backward scan direction isn't propagated into
+-- expression subqueries (bug #15336)
+--
+begin;
+declare c1 scroll cursor for
+ select * from generate_series(1,4) i
+ where i <> all (values (2),(3));
+move forward all in c1;
+fetch backward all in c1;
+ i
+---
+ 4
+ 1
+(2 rows)
+
+commit;
+--
+-- Tests for CTE inlining behavior
+--
+-- Basic subquery that can be inlined
+explain (verbose, costs off)
+with x as (select * from (select f1 from subselect_tbl) ss)
+select * from x where f1 = 1;
+ QUERY PLAN
+----------------------------------
+ Seq Scan on public.subselect_tbl
+ Output: subselect_tbl.f1
+ Filter: (subselect_tbl.f1 = 1)
+(3 rows)
+
+-- Explicitly request materialization
+explain (verbose, costs off)
+with x as materialized (select * from (select f1 from subselect_tbl) ss)
+select * from x where f1 = 1;
+ QUERY PLAN
+------------------------------------------
+ CTE Scan on x
+ Output: x.f1
+ Filter: (x.f1 = 1)
+ CTE x
+ -> Seq Scan on public.subselect_tbl
+ Output: subselect_tbl.f1
+(6 rows)
+
+-- Stable functions are safe to inline
+explain (verbose, costs off)
+with x as (select * from (select f1, now() from subselect_tbl) ss)
+select * from x where f1 = 1;
+ QUERY PLAN
+-----------------------------------
+ Seq Scan on public.subselect_tbl
+ Output: subselect_tbl.f1, now()
+ Filter: (subselect_tbl.f1 = 1)
+(3 rows)
+
+-- Volatile functions prevent inlining
+explain (verbose, costs off)
+with x as (select * from (select f1, random() from subselect_tbl) ss)
+select * from x where f1 = 1;
+ QUERY PLAN
+----------------------------------------------
+ CTE Scan on x
+ Output: x.f1, x.random
+ Filter: (x.f1 = 1)
+ CTE x
+ -> Seq Scan on public.subselect_tbl
+ Output: subselect_tbl.f1, random()
+(6 rows)
+
+-- SELECT FOR UPDATE cannot be inlined
+explain (verbose, costs off)
+with x as (select * from (select f1 from subselect_tbl for update) ss)
+select * from x where f1 = 1;
+ QUERY PLAN
+--------------------------------------------------------------------
+ CTE Scan on x
+ Output: x.f1
+ Filter: (x.f1 = 1)
+ CTE x
+ -> Subquery Scan on ss
+ Output: ss.f1
+ -> LockRows
+ Output: subselect_tbl.f1, subselect_tbl.ctid
+ -> Seq Scan on public.subselect_tbl
+ Output: subselect_tbl.f1, subselect_tbl.ctid
+(10 rows)
+
+-- Multiply-referenced CTEs are inlined only when requested
+explain (verbose, costs off)
+with x as (select * from (select f1, now() as n from subselect_tbl) ss)
+select * from x, x x2 where x.n = x2.n;
+ QUERY PLAN
+-------------------------------------------
+ Merge Join
+ Output: x.f1, x.n, x2.f1, x2.n
+ Merge Cond: (x.n = x2.n)
+ CTE x
+ -> Seq Scan on public.subselect_tbl
+ Output: subselect_tbl.f1, now()
+ -> Sort
+ Output: x.f1, x.n
+ Sort Key: x.n
+ -> CTE Scan on x
+ Output: x.f1, x.n
+ -> Sort
+ Output: x2.f1, x2.n
+ Sort Key: x2.n
+ -> CTE Scan on x x2
+ Output: x2.f1, x2.n
+(16 rows)
+
+explain (verbose, costs off)
+with x as not materialized (select * from (select f1, now() as n from subselect_tbl) ss)
+select * from x, x x2 where x.n = x2.n;
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Result
+ Output: subselect_tbl.f1, now(), subselect_tbl_1.f1, now()
+ One-Time Filter: (now() = now())
+ -> Nested Loop
+ Output: subselect_tbl.f1, subselect_tbl_1.f1
+ -> Seq Scan on public.subselect_tbl
+ Output: subselect_tbl.f1, subselect_tbl.f2, subselect_tbl.f3
+ -> Materialize
+ Output: subselect_tbl_1.f1
+ -> Seq Scan on public.subselect_tbl subselect_tbl_1
+ Output: subselect_tbl_1.f1
+(11 rows)
+
+-- Multiply-referenced CTEs can't be inlined if they contain outer self-refs
+explain (verbose, costs off)
+with recursive x(a) as
+ ((values ('a'), ('b'))
+ union all
+ (with z as not materialized (select * from x)
+ select z.a || z1.a as a from z cross join z as z1
+ where length(z.a || z1.a) < 5))
+select * from x;
+ QUERY PLAN
+----------------------------------------------------------
+ CTE Scan on x
+ Output: x.a
+ CTE x
+ -> Recursive Union
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+ -> Nested Loop
+ Output: (z.a || z1.a)
+ Join Filter: (length((z.a || z1.a)) < 5)
+ CTE z
+ -> WorkTable Scan on x x_1
+ Output: x_1.a
+ -> CTE Scan on z
+ Output: z.a
+ -> CTE Scan on z z1
+ Output: z1.a
+(16 rows)
+
+with recursive x(a) as
+ ((values ('a'), ('b'))
+ union all
+ (with z as not materialized (select * from x)
+ select z.a || z1.a as a from z cross join z as z1
+ where length(z.a || z1.a) < 5))
+select * from x;
+ a
+------
+ a
+ b
+ aa
+ ab
+ ba
+ bb
+ aaaa
+ aaab
+ aaba
+ aabb
+ abaa
+ abab
+ abba
+ abbb
+ baaa
+ baab
+ baba
+ babb
+ bbaa
+ bbab
+ bbba
+ bbbb
+(22 rows)
+
+explain (verbose, costs off)
+with recursive x(a) as
+ ((values ('a'), ('b'))
+ union all
+ (with z as not materialized (select * from x)
+ select z.a || z.a as a from z
+ where length(z.a || z.a) < 5))
+select * from x;
+ QUERY PLAN
+--------------------------------------------------------
+ CTE Scan on x
+ Output: x.a
+ CTE x
+ -> Recursive Union
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+ -> WorkTable Scan on x x_1
+ Output: (x_1.a || x_1.a)
+ Filter: (length((x_1.a || x_1.a)) < 5)
+(9 rows)
+
+with recursive x(a) as
+ ((values ('a'), ('b'))
+ union all
+ (with z as not materialized (select * from x)
+ select z.a || z.a as a from z
+ where length(z.a || z.a) < 5))
+select * from x;
+ a
+------
+ a
+ b
+ aa
+ bb
+ aaaa
+ bbbb
+(6 rows)
+
+-- Check handling of outer references
+explain (verbose, costs off)
+with x as (select * from int4_tbl)
+select * from (with y as (select * from x) select * from y) ss;
+ QUERY PLAN
+-----------------------------
+ Seq Scan on public.int4_tbl
+ Output: int4_tbl.f1
+(2 rows)
+
+explain (verbose, costs off)
+with x as materialized (select * from int4_tbl)
+select * from (with y as (select * from x) select * from y) ss;
+ QUERY PLAN
+-------------------------------------
+ CTE Scan on x
+ Output: x.f1
+ CTE x
+ -> Seq Scan on public.int4_tbl
+ Output: int4_tbl.f1
+(5 rows)
+
+-- Ensure that we inline the currect CTE when there are
+-- multiple CTEs with the same name
+explain (verbose, costs off)
+with x as (select 1 as y)
+select * from (with x as (select 2 as y) select * from x) ss;
+ QUERY PLAN
+-------------
+ Result
+ Output: 2
+(2 rows)
+
+-- Row marks are not pushed into CTEs
+explain (verbose, costs off)
+with x as (select * from subselect_tbl)
+select * from x for update;
+ QUERY PLAN
+----------------------------------------------------------------
+ Seq Scan on public.subselect_tbl
+ Output: subselect_tbl.f1, subselect_tbl.f2, subselect_tbl.f3
+(2 rows)
+
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
new file mode 100644
index 0000000..2088857
--- /dev/null
+++ b/src/test/regress/expected/sysviews.out
@@ -0,0 +1,159 @@
+--
+-- Test assorted system views
+--
+-- This test is mainly meant to provide some code coverage for the
+-- set-returning functions that underlie certain system views.
+-- The output of most of these functions is very environment-dependent,
+-- so our ability to test with fixed expected output is pretty limited;
+-- but even a trivial check of count(*) will exercise the normal code path
+-- through the SRF.
+select count(*) >= 0 as ok from pg_available_extension_versions;
+ ok
+----
+ t
+(1 row)
+
+select count(*) >= 0 as ok from pg_available_extensions;
+ ok
+----
+ t
+(1 row)
+
+-- The entire output of pg_backend_memory_contexts is not stable,
+-- we test only the existance and basic condition of TopMemoryContext.
+select name, ident, parent, level, total_bytes >= free_bytes
+ from pg_backend_memory_contexts where level = 0;
+ name | ident | parent | level | ?column?
+------------------+-------+--------+-------+----------
+ TopMemoryContext | | | 0 | t
+(1 row)
+
+-- At introduction, pg_config had 23 entries; it may grow
+select count(*) > 20 as ok from pg_config;
+ ok
+----
+ t
+(1 row)
+
+-- We expect no cursors in this test; see also portals.sql
+select count(*) = 0 as ok from pg_cursors;
+ ok
+----
+ t
+(1 row)
+
+select count(*) >= 0 as ok from pg_file_settings;
+ ok
+----
+ t
+(1 row)
+
+-- There will surely be at least one rule
+select count(*) > 0 as ok from pg_hba_file_rules;
+ ok
+----
+ t
+(1 row)
+
+-- There will surely be at least one active lock
+select count(*) > 0 as ok from pg_locks;
+ ok
+----
+ t
+(1 row)
+
+-- We expect no prepared statements in this test; see also prepare.sql
+select count(*) = 0 as ok from pg_prepared_statements;
+ ok
+----
+ t
+(1 row)
+
+-- See also prepared_xacts.sql
+select count(*) >= 0 as ok from pg_prepared_xacts;
+ ok
+----
+ t
+(1 row)
+
+-- There will surely be at least one SLRU cache
+select count(*) > 0 as ok from pg_stat_slru;
+ ok
+----
+ t
+(1 row)
+
+-- There must be only one record
+select count(*) = 1 as ok from pg_stat_wal;
+ ok
+----
+ t
+(1 row)
+
+-- We expect no walreceiver running in this test
+select count(*) = 0 as ok from pg_stat_wal_receiver;
+ ok
+----
+ t
+(1 row)
+
+-- This is to record the prevailing planner enable_foo settings during
+-- a regression test run.
+select name, setting from pg_settings where name like 'enable%';
+ name | setting
+--------------------------------+---------
+ enable_async_append | on
+ enable_bitmapscan | on
+ enable_gathermerge | on
+ enable_hashagg | on
+ enable_hashjoin | on
+ enable_incremental_sort | on
+ enable_indexonlyscan | on
+ enable_indexscan | on
+ enable_material | on
+ enable_memoize | on
+ enable_mergejoin | on
+ enable_nestloop | on
+ enable_parallel_append | on
+ enable_parallel_hash | on
+ enable_partition_pruning | on
+ enable_partitionwise_aggregate | off
+ enable_partitionwise_join | off
+ enable_seqscan | on
+ enable_sort | on
+ enable_tidscan | on
+(20 rows)
+
+-- Test that the pg_timezone_names and pg_timezone_abbrevs views are
+-- more-or-less working. We can't test their contents in any great detail
+-- without the outputs changing anytime IANA updates the underlying data,
+-- but it seems reasonable to expect at least one entry per major meridian.
+-- (At the time of writing, the actual counts are around 38 because of
+-- zones using fractional GMT offsets, so this is a pretty loose test.)
+select count(distinct utc_offset) >= 24 as ok from pg_timezone_names;
+ ok
+----
+ t
+(1 row)
+
+select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
+ ok
+----
+ t
+(1 row)
+
+-- Let's check the non-default timezone abbreviation sets, too
+set timezone_abbreviations = 'Australia';
+select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
+ ok
+----
+ t
+(1 row)
+
+set timezone_abbreviations = 'India';
+select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
+ ok
+----
+ t
+(1 row)
+
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
new file mode 100644
index 0000000..078358d
--- /dev/null
+++ b/src/test/regress/expected/tablesample.out
@@ -0,0 +1,331 @@
+CREATE TABLE test_tablesample (id int, name text) WITH (fillfactor=10);
+-- use fillfactor so we don't have to load too much data to get multiple pages
+INSERT INTO test_tablesample
+ SELECT i, repeat(i::text, 200) FROM generate_series(0, 9) s(i);
+SELECT t.id FROM test_tablesample AS t TABLESAMPLE SYSTEM (50) REPEATABLE (0);
+ id
+----
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+(6 rows)
+
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (100.0/11) REPEATABLE (0);
+ id
+----
+(0 rows)
+
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (0);
+ id
+----
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+(6 rows)
+
+SELECT id FROM test_tablesample TABLESAMPLE BERNOULLI (50) REPEATABLE (0);
+ id
+----
+ 4
+ 5
+ 6
+ 7
+ 8
+(5 rows)
+
+SELECT id FROM test_tablesample TABLESAMPLE BERNOULLI (5.5) REPEATABLE (0);
+ id
+----
+ 7
+(1 row)
+
+-- 100% should give repeatable count results (ie, all rows) in any case
+SELECT count(*) FROM test_tablesample TABLESAMPLE SYSTEM (100);
+ count
+-------
+ 10
+(1 row)
+
+SELECT count(*) FROM test_tablesample TABLESAMPLE SYSTEM (100) REPEATABLE (1+2);
+ count
+-------
+ 10
+(1 row)
+
+SELECT count(*) FROM test_tablesample TABLESAMPLE SYSTEM (100) REPEATABLE (0.4);
+ count
+-------
+ 10
+(1 row)
+
+CREATE VIEW test_tablesample_v1 AS
+ SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (10*2) REPEATABLE (2);
+CREATE VIEW test_tablesample_v2 AS
+ SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (99);
+\d+ test_tablesample_v1
+ View "public.test_tablesample_v1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+---------+-------------
+ id | integer | | | | plain |
+View definition:
+ SELECT test_tablesample.id
+ FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
+
+\d+ test_tablesample_v2
+ View "public.test_tablesample_v2"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+---------+-------------
+ id | integer | | | | plain |
+View definition:
+ SELECT test_tablesample.id
+ FROM test_tablesample TABLESAMPLE system (99);
+
+-- check a sampled query doesn't affect cursor in progress
+BEGIN;
+DECLARE tablesample_cur CURSOR FOR
+ SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (0);
+FETCH FIRST FROM tablesample_cur;
+ id
+----
+ 3
+(1 row)
+
+FETCH NEXT FROM tablesample_cur;
+ id
+----
+ 4
+(1 row)
+
+FETCH NEXT FROM tablesample_cur;
+ id
+----
+ 5
+(1 row)
+
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (0);
+ id
+----
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+(6 rows)
+
+FETCH NEXT FROM tablesample_cur;
+ id
+----
+ 6
+(1 row)
+
+FETCH NEXT FROM tablesample_cur;
+ id
+----
+ 7
+(1 row)
+
+FETCH NEXT FROM tablesample_cur;
+ id
+----
+ 8
+(1 row)
+
+FETCH FIRST FROM tablesample_cur;
+ id
+----
+ 3
+(1 row)
+
+FETCH NEXT FROM tablesample_cur;
+ id
+----
+ 4
+(1 row)
+
+FETCH NEXT FROM tablesample_cur;
+ id
+----
+ 5
+(1 row)
+
+FETCH NEXT FROM tablesample_cur;
+ id
+----
+ 6
+(1 row)
+
+FETCH NEXT FROM tablesample_cur;
+ id
+----
+ 7
+(1 row)
+
+FETCH NEXT FROM tablesample_cur;
+ id
+----
+ 8
+(1 row)
+
+CLOSE tablesample_cur;
+END;
+EXPLAIN (COSTS OFF)
+ SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (2);
+ QUERY PLAN
+--------------------------------------------------------------------
+ Sample Scan on test_tablesample
+ Sampling: system ('50'::real) REPEATABLE ('2'::double precision)
+(2 rows)
+
+EXPLAIN (COSTS OFF)
+ SELECT * FROM test_tablesample_v1;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Sample Scan on test_tablesample
+ Sampling: system ('20'::real) REPEATABLE ('2'::double precision)
+(2 rows)
+
+-- check inheritance behavior
+explain (costs off)
+ select count(*) from person tablesample bernoulli (100);
+ QUERY PLAN
+-------------------------------------------------
+ Aggregate
+ -> Append
+ -> Sample Scan on person person_1
+ Sampling: bernoulli ('100'::real)
+ -> Sample Scan on emp person_2
+ Sampling: bernoulli ('100'::real)
+ -> Sample Scan on student person_3
+ Sampling: bernoulli ('100'::real)
+ -> Sample Scan on stud_emp person_4
+ Sampling: bernoulli ('100'::real)
+(10 rows)
+
+select count(*) from person tablesample bernoulli (100);
+ count
+-------
+ 58
+(1 row)
+
+select count(*) from person;
+ count
+-------
+ 58
+(1 row)
+
+-- check that collations get assigned within the tablesample arguments
+SELECT count(*) FROM test_tablesample TABLESAMPLE bernoulli (('1'::text < '0'::text)::int);
+ count
+-------
+ 0
+(1 row)
+
+-- check behavior during rescans, as well as correct handling of min/max pct
+select * from
+ (values (0),(100)) v(pct),
+ lateral (select count(*) from tenk1 tablesample bernoulli (pct)) ss;
+ pct | count
+-----+-------
+ 0 | 0
+ 100 | 10000
+(2 rows)
+
+select * from
+ (values (0),(100)) v(pct),
+ lateral (select count(*) from tenk1 tablesample system (pct)) ss;
+ pct | count
+-----+-------
+ 0 | 0
+ 100 | 10000
+(2 rows)
+
+explain (costs off)
+select pct, count(unique1) from
+ (values (0),(100)) v(pct),
+ lateral (select * from tenk1 tablesample bernoulli (pct)) ss
+ group by pct;
+ QUERY PLAN
+--------------------------------------------------------
+ HashAggregate
+ Group Key: "*VALUES*".column1
+ -> Nested Loop
+ -> Values Scan on "*VALUES*"
+ -> Sample Scan on tenk1
+ Sampling: bernoulli ("*VALUES*".column1)
+(6 rows)
+
+select pct, count(unique1) from
+ (values (0),(100)) v(pct),
+ lateral (select * from tenk1 tablesample bernoulli (pct)) ss
+ group by pct;
+ pct | count
+-----+-------
+ 100 | 10000
+(1 row)
+
+select pct, count(unique1) from
+ (values (0),(100)) v(pct),
+ lateral (select * from tenk1 tablesample system (pct)) ss
+ group by pct;
+ pct | count
+-----+-------
+ 100 | 10000
+(1 row)
+
+-- errors
+SELECT id FROM test_tablesample TABLESAMPLE FOOBAR (1);
+ERROR: tablesample method foobar does not exist
+LINE 1: SELECT id FROM test_tablesample TABLESAMPLE FOOBAR (1);
+ ^
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (NULL);
+ERROR: TABLESAMPLE parameter cannot be null
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (NULL);
+ERROR: TABLESAMPLE REPEATABLE parameter cannot be null
+SELECT id FROM test_tablesample TABLESAMPLE BERNOULLI (-1);
+ERROR: sample percentage must be between 0 and 100
+SELECT id FROM test_tablesample TABLESAMPLE BERNOULLI (200);
+ERROR: sample percentage must be between 0 and 100
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (-1);
+ERROR: sample percentage must be between 0 and 100
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (200);
+ERROR: sample percentage must be between 0 and 100
+SELECT id FROM test_tablesample_v1 TABLESAMPLE BERNOULLI (1);
+ERROR: TABLESAMPLE clause can only be applied to tables and materialized views
+LINE 1: SELECT id FROM test_tablesample_v1 TABLESAMPLE BERNOULLI (1)...
+ ^
+INSERT INTO test_tablesample_v1 VALUES(1);
+ERROR: cannot insert into view "test_tablesample_v1"
+DETAIL: Views containing TABLESAMPLE are not automatically updatable.
+HINT: To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
+WITH query_select AS (SELECT * FROM test_tablesample)
+SELECT * FROM query_select TABLESAMPLE BERNOULLI (5.5) REPEATABLE (1);
+ERROR: TABLESAMPLE clause can only be applied to tables and materialized views
+LINE 2: SELECT * FROM query_select TABLESAMPLE BERNOULLI (5.5) REPEA...
+ ^
+SELECT q.* FROM (SELECT * FROM test_tablesample) as q TABLESAMPLE BERNOULLI (5);
+ERROR: syntax error at or near "TABLESAMPLE"
+LINE 1: ...CT q.* FROM (SELECT * FROM test_tablesample) as q TABLESAMPL...
+ ^
+-- check partitioned tables support tablesample
+create table parted_sample (a int) partition by list (a);
+create table parted_sample_1 partition of parted_sample for values in (1);
+create table parted_sample_2 partition of parted_sample for values in (2);
+explain (costs off)
+ select * from parted_sample tablesample bernoulli (100);
+ QUERY PLAN
+-------------------------------------------
+ Append
+ -> Sample Scan on parted_sample_1
+ Sampling: bernoulli ('100'::real)
+ -> Sample Scan on parted_sample_2
+ Sampling: bernoulli ('100'::real)
+(5 rows)
+
+drop table parted_sample, parted_sample_1, parted_sample_2;
diff --git a/src/test/regress/expected/temp.out b/src/test/regress/expected/temp.out
new file mode 100644
index 0000000..a5b3ed3
--- /dev/null
+++ b/src/test/regress/expected/temp.out
@@ -0,0 +1,392 @@
+--
+-- 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;
+ tcol
+------
+(0 rows)
+
+DROP INDEX i_temptest;
+DROP TABLE temptest;
+SELECT * FROM temptest;
+ col
+-----
+(0 rows)
+
+DROP INDEX i_temptest;
+DROP TABLE temptest;
+-- 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;
+ tcol
+------
+ 2.1
+(1 row)
+
+DROP TABLE temptest;
+SELECT * FROM temptest;
+ col
+-----
+ 1
+(1 row)
+
+DROP TABLE temptest;
+-- test temp table deletion
+CREATE TEMP TABLE temptest(col int);
+\c
+SELECT * FROM temptest;
+ERROR: relation "temptest" does not exist
+LINE 1: SELECT * FROM temptest;
+ ^
+-- Test ON COMMIT DELETE ROWS
+CREATE TEMP TABLE temptest(col int) ON COMMIT DELETE ROWS;
+-- while we're here, verify successful truncation of index with SQL function
+CREATE INDEX ON temptest(bit_length(''));
+BEGIN;
+INSERT INTO temptest VALUES (1);
+INSERT INTO temptest VALUES (2);
+SELECT * FROM temptest;
+ col
+-----
+ 1
+ 2
+(2 rows)
+
+COMMIT;
+SELECT * FROM temptest;
+ col
+-----
+(0 rows)
+
+DROP TABLE temptest;
+BEGIN;
+CREATE TEMP TABLE temptest(col) ON COMMIT DELETE ROWS AS SELECT 1;
+SELECT * FROM temptest;
+ col
+-----
+ 1
+(1 row)
+
+COMMIT;
+SELECT * FROM temptest;
+ col
+-----
+(0 rows)
+
+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;
+ col
+-----
+ 1
+ 2
+(2 rows)
+
+COMMIT;
+SELECT * FROM temptest;
+ERROR: relation "temptest" does not exist
+LINE 1: SELECT * FROM temptest;
+ ^
+BEGIN;
+CREATE TEMP TABLE temptest(col) ON COMMIT DROP AS SELECT 1;
+SELECT * FROM temptest;
+ col
+-----
+ 1
+(1 row)
+
+COMMIT;
+SELECT * FROM temptest;
+ERROR: relation "temptest" does not exist
+LINE 1: SELECT * FROM temptest;
+ ^
+-- ON COMMIT is only allowed for TEMP
+CREATE TABLE temptest(col int) ON COMMIT DELETE ROWS;
+ERROR: ON COMMIT can only be used on temporary tables
+CREATE TABLE temptest(col) ON COMMIT DELETE ROWS AS SELECT 1;
+ERROR: ON COMMIT can only be used on temporary tables
+-- Test foreign keys
+BEGIN;
+CREATE TEMP TABLE temptest1(col int PRIMARY KEY);
+CREATE TEMP TABLE temptest2(col int REFERENCES temptest1)
+ ON COMMIT DELETE ROWS;
+INSERT INTO temptest1 VALUES (1);
+INSERT INTO temptest2 VALUES (1);
+COMMIT;
+SELECT * FROM temptest1;
+ col
+-----
+ 1
+(1 row)
+
+SELECT * FROM temptest2;
+ col
+-----
+(0 rows)
+
+BEGIN;
+CREATE TEMP TABLE temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE TEMP TABLE temptest4(col int REFERENCES temptest3);
+COMMIT;
+ERROR: unsupported ON COMMIT and foreign key combination
+DETAIL: Table "temptest4" references "temptest3", but they do not have the same ON COMMIT setting.
+-- Test manipulation of temp schema's placement in search path
+create table public.whereami (f1 text);
+insert into public.whereami values ('public');
+create temp table whereami (f1 text);
+insert into whereami values ('temp');
+create function public.whoami() returns text
+ as $$select 'public'::text$$ language sql;
+create function pg_temp.whoami() returns text
+ as $$select 'temp'::text$$ language sql;
+-- default should have pg_temp implicitly first, but only for tables
+select * from whereami;
+ f1
+------
+ temp
+(1 row)
+
+select whoami();
+ whoami
+--------
+ public
+(1 row)
+
+-- can list temp first explicitly, but it still doesn't affect functions
+set search_path = pg_temp, public;
+select * from whereami;
+ f1
+------
+ temp
+(1 row)
+
+select whoami();
+ whoami
+--------
+ public
+(1 row)
+
+-- or put it last for security
+set search_path = public, pg_temp;
+select * from whereami;
+ f1
+--------
+ public
+(1 row)
+
+select whoami();
+ whoami
+--------
+ public
+(1 row)
+
+-- you can invoke a temp function explicitly, though
+select pg_temp.whoami();
+ whoami
+--------
+ temp
+(1 row)
+
+drop table public.whereami;
+-- 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('');
+ERROR: function nonempty(unknown) does not exist
+LINE 1: select nonempty('');
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select pg_temp.nonempty('');
+ERROR: value for domain nonempty violates check constraint "nonempty_check"
+-- other syntax matches rules for tables
+select ''::nonempty;
+ERROR: value for domain nonempty violates check constraint "nonempty_check"
+reset search_path;
+-- For partitioned temp tables, ON COMMIT actions ignore storage-less
+-- partitioned tables.
+begin;
+create temp table temp_parted_oncommit (a int)
+ partition by list (a) on commit delete rows;
+create temp table temp_parted_oncommit_1
+ partition of temp_parted_oncommit
+ for values in (1) on commit delete rows;
+insert into temp_parted_oncommit values (1);
+commit;
+-- partitions are emptied by the previous commit
+select * from temp_parted_oncommit;
+ a
+---
+(0 rows)
+
+drop table temp_parted_oncommit;
+-- Check dependencies between ON COMMIT actions with a partitioned
+-- table and its partitions. Using ON COMMIT DROP on a parent removes
+-- the whole set.
+begin;
+create temp table temp_parted_oncommit_test (a int)
+ partition by list (a) on commit drop;
+create temp table temp_parted_oncommit_test1
+ partition of temp_parted_oncommit_test
+ for values in (1) on commit delete rows;
+create temp table temp_parted_oncommit_test2
+ partition of temp_parted_oncommit_test
+ for values in (2) on commit drop;
+insert into temp_parted_oncommit_test values (1), (2);
+commit;
+-- no relations remain in this case.
+select relname from pg_class where relname ~ '^temp_parted_oncommit_test';
+ relname
+---------
+(0 rows)
+
+-- Using ON COMMIT DELETE on a partitioned table does not remove
+-- all rows if partitions preserve their data.
+begin;
+create temp table temp_parted_oncommit_test (a int)
+ partition by list (a) on commit delete rows;
+create temp table temp_parted_oncommit_test1
+ partition of temp_parted_oncommit_test
+ for values in (1) on commit preserve rows;
+create temp table temp_parted_oncommit_test2
+ partition of temp_parted_oncommit_test
+ for values in (2) on commit drop;
+insert into temp_parted_oncommit_test values (1), (2);
+commit;
+-- Data from the remaining partition is still here as its rows are
+-- preserved.
+select * from temp_parted_oncommit_test;
+ a
+---
+ 1
+(1 row)
+
+-- two relations remain in this case.
+select relname from pg_class where relname ~ '^temp_parted_oncommit_test'
+ order by relname;
+ relname
+----------------------------
+ temp_parted_oncommit_test
+ temp_parted_oncommit_test1
+(2 rows)
+
+drop table temp_parted_oncommit_test;
+-- Check dependencies between ON COMMIT actions with inheritance trees.
+-- Using ON COMMIT DROP on a parent removes the whole set.
+begin;
+create temp table temp_inh_oncommit_test (a int) on commit drop;
+create temp table temp_inh_oncommit_test1 ()
+ inherits(temp_inh_oncommit_test) on commit delete rows;
+insert into temp_inh_oncommit_test1 values (1);
+commit;
+-- no relations remain in this case
+select relname from pg_class where relname ~ '^temp_inh_oncommit_test';
+ relname
+---------
+(0 rows)
+
+-- Data on the parent is removed, and the child goes away.
+begin;
+create temp table temp_inh_oncommit_test (a int) on commit delete rows;
+create temp table temp_inh_oncommit_test1 ()
+ inherits(temp_inh_oncommit_test) on commit drop;
+insert into temp_inh_oncommit_test1 values (1);
+insert into temp_inh_oncommit_test values (1);
+commit;
+select * from temp_inh_oncommit_test;
+ a
+---
+(0 rows)
+
+-- one relation remains
+select relname from pg_class where relname ~ '^temp_inh_oncommit_test';
+ relname
+------------------------
+ temp_inh_oncommit_test
+(1 row)
+
+drop table temp_inh_oncommit_test;
+-- Tests with two-phase commit
+-- Transactions creating objects in a temporary namespace cannot be used
+-- with two-phase commit.
+-- These cases generate errors about temporary namespace.
+-- Function creation
+begin;
+create function pg_temp.twophase_func() returns void as
+ $$ select '2pc_func'::text $$ language sql;
+prepare transaction 'twophase_func';
+ERROR: cannot PREPARE a transaction that has operated on temporary objects
+-- Function drop
+create function pg_temp.twophase_func() returns void as
+ $$ select '2pc_func'::text $$ language sql;
+begin;
+drop function pg_temp.twophase_func();
+prepare transaction 'twophase_func';
+ERROR: cannot PREPARE a transaction that has operated on temporary objects
+-- Operator creation
+begin;
+create operator pg_temp.@@ (leftarg = int4, rightarg = int4, procedure = int4mi);
+prepare transaction 'twophase_operator';
+ERROR: cannot PREPARE a transaction that has operated on temporary objects
+-- These generate errors about temporary tables.
+begin;
+create type pg_temp.twophase_type as (a int);
+prepare transaction 'twophase_type';
+ERROR: cannot PREPARE a transaction that has operated on temporary objects
+begin;
+create view pg_temp.twophase_view as select 1;
+prepare transaction 'twophase_view';
+ERROR: cannot PREPARE a transaction that has operated on temporary objects
+begin;
+create sequence pg_temp.twophase_seq;
+prepare transaction 'twophase_sequence';
+ERROR: cannot PREPARE a transaction that has operated on temporary objects
+-- Temporary tables cannot be used with two-phase commit.
+create temp table twophase_tab (a int);
+begin;
+select a from twophase_tab;
+ a
+---
+(0 rows)
+
+prepare transaction 'twophase_tab';
+ERROR: cannot PREPARE a transaction that has operated on temporary objects
+begin;
+insert into twophase_tab values (1);
+prepare transaction 'twophase_tab';
+ERROR: cannot PREPARE a transaction that has operated on temporary objects
+begin;
+lock twophase_tab in access exclusive mode;
+prepare transaction 'twophase_tab';
+ERROR: cannot PREPARE a transaction that has operated on temporary objects
+begin;
+drop table twophase_tab;
+prepare transaction 'twophase_tab';
+ERROR: cannot PREPARE a transaction that has operated on temporary objects
+-- Corner case: current_schema may create a temporary schema if namespace
+-- creation is pending, so check after that. First reset the connection
+-- to remove the temporary namespace.
+\c -
+SET search_path TO 'pg_temp';
+BEGIN;
+SELECT current_schema() ~ 'pg_temp' AS is_temp_schema;
+ is_temp_schema
+----------------
+ t
+(1 row)
+
+PREPARE TRANSACTION 'twophase_search';
+ERROR: cannot PREPARE a transaction that has operated on temporary objects
diff --git a/src/test/regress/expected/text.out b/src/test/regress/expected/text.out
new file mode 100644
index 0000000..b625b09
--- /dev/null
+++ b/src/test/regress/expected/text.out
@@ -0,0 +1,440 @@
+--
+-- 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)
+
+CREATE TABLE TEXT_TBL (f1 text);
+INSERT INTO TEXT_TBL VALUES ('doh!');
+INSERT INTO TEXT_TBL VALUES ('hi de ho neighbor');
+SELECT * FROM TEXT_TBL;
+ 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/tid.out b/src/test/regress/expected/tid.out
new file mode 100644
index 0000000..8da1a45
--- /dev/null
+++ b/src/test/regress/expected/tid.out
@@ -0,0 +1,76 @@
+-- tests for functions related to TID handling
+CREATE TABLE tid_tab (a int);
+-- min() and max() for TIDs
+INSERT INTO tid_tab VALUES (1), (2);
+SELECT min(ctid) FROM tid_tab;
+ min
+-------
+ (0,1)
+(1 row)
+
+SELECT max(ctid) FROM tid_tab;
+ max
+-------
+ (0,2)
+(1 row)
+
+TRUNCATE tid_tab;
+-- Tests for currtid2() with various relation kinds
+-- Materialized view
+CREATE MATERIALIZED VIEW tid_matview AS SELECT a FROM tid_tab;
+SELECT currtid2('tid_matview'::text, '(0,1)'::tid); -- fails
+ERROR: tid (0, 1) is not valid for relation "tid_matview"
+INSERT INTO tid_tab VALUES (1);
+REFRESH MATERIALIZED VIEW tid_matview;
+SELECT currtid2('tid_matview'::text, '(0,1)'::tid); -- ok
+ currtid2
+----------
+ (0,1)
+(1 row)
+
+DROP MATERIALIZED VIEW tid_matview;
+TRUNCATE tid_tab;
+-- Sequence
+CREATE SEQUENCE tid_seq;
+SELECT currtid2('tid_seq'::text, '(0,1)'::tid); -- ok
+ currtid2
+----------
+ (0,1)
+(1 row)
+
+DROP SEQUENCE tid_seq;
+-- Index, fails with incorrect relation type
+CREATE INDEX tid_ind ON tid_tab(a);
+SELECT currtid2('tid_ind'::text, '(0,1)'::tid); -- fails
+ERROR: "tid_ind" is an index
+DROP INDEX tid_ind;
+-- Partitioned table, no storage
+CREATE TABLE tid_part (a int) PARTITION BY RANGE (a);
+SELECT currtid2('tid_part'::text, '(0,1)'::tid); -- fails
+ERROR: cannot look at latest visible tid for relation "public.tid_part"
+DROP TABLE tid_part;
+-- Views
+-- ctid not defined in the view
+CREATE VIEW tid_view_no_ctid AS SELECT a FROM tid_tab;
+SELECT currtid2('tid_view_no_ctid'::text, '(0,1)'::tid); -- fails
+ERROR: currtid cannot handle views with no CTID
+DROP VIEW tid_view_no_ctid;
+-- ctid fetched directly from the source table.
+CREATE VIEW tid_view_with_ctid AS SELECT ctid, a FROM tid_tab;
+SELECT currtid2('tid_view_with_ctid'::text, '(0,1)'::tid); -- fails
+ERROR: tid (0, 1) is not valid for relation "tid_tab"
+INSERT INTO tid_tab VALUES (1);
+SELECT currtid2('tid_view_with_ctid'::text, '(0,1)'::tid); -- ok
+ currtid2
+----------
+ (0,1)
+(1 row)
+
+DROP VIEW tid_view_with_ctid;
+TRUNCATE tid_tab;
+-- ctid attribute with incorrect data type
+CREATE VIEW tid_view_fake_ctid AS SELECT 1 AS ctid, 2 AS a;
+SELECT currtid2('tid_view_fake_ctid'::text, '(0,1)'::tid); -- fails
+ERROR: ctid isn't of type TID
+DROP VIEW tid_view_fake_ctid;
+DROP TABLE tid_tab CASCADE;
diff --git a/src/test/regress/expected/tidrangescan.out b/src/test/regress/expected/tidrangescan.out
new file mode 100644
index 0000000..721f3b9
--- /dev/null
+++ b/src/test/regress/expected/tidrangescan.out
@@ -0,0 +1,300 @@
+-- tests for tidrangescans
+SET enable_seqscan TO off;
+CREATE TABLE tidrangescan(id integer, data text);
+-- empty table
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid < '(1, 0)';
+ QUERY PLAN
+-----------------------------------
+ Tid Range Scan on tidrangescan
+ TID Cond: (ctid < '(1,0)'::tid)
+(2 rows)
+
+SELECT ctid FROM tidrangescan WHERE ctid < '(1, 0)';
+ ctid
+------
+(0 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid > '(9, 0)';
+ QUERY PLAN
+-----------------------------------
+ Tid Range Scan on tidrangescan
+ TID Cond: (ctid > '(9,0)'::tid)
+(2 rows)
+
+SELECT ctid FROM tidrangescan WHERE ctid > '(9, 0)';
+ ctid
+------
+(0 rows)
+
+-- insert enough tuples to fill at least two pages
+INSERT INTO tidrangescan SELECT i,repeat('x', 100) FROM generate_series(1,200) AS s(i);
+-- remove all tuples after the 10th tuple on each page. Trying to ensure
+-- we get the same layout with all CPU architectures and smaller than standard
+-- page sizes.
+DELETE FROM tidrangescan
+WHERE substring(ctid::text FROM ',(\d+)\)')::integer > 10 OR substring(ctid::text FROM '\((\d+),')::integer > 2;
+VACUUM tidrangescan;
+-- range scans with upper bound
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)';
+ QUERY PLAN
+-----------------------------------
+ Tid Range Scan on tidrangescan
+ TID Cond: (ctid < '(1,0)'::tid)
+(2 rows)
+
+SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)';
+ ctid
+--------
+ (0,1)
+ (0,2)
+ (0,3)
+ (0,4)
+ (0,5)
+ (0,6)
+ (0,7)
+ (0,8)
+ (0,9)
+ (0,10)
+(10 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid <= '(1,5)';
+ QUERY PLAN
+------------------------------------
+ Tid Range Scan on tidrangescan
+ TID Cond: (ctid <= '(1,5)'::tid)
+(2 rows)
+
+SELECT ctid FROM tidrangescan WHERE ctid <= '(1,5)';
+ ctid
+--------
+ (0,1)
+ (0,2)
+ (0,3)
+ (0,4)
+ (0,5)
+ (0,6)
+ (0,7)
+ (0,8)
+ (0,9)
+ (0,10)
+ (1,1)
+ (1,2)
+ (1,3)
+ (1,4)
+ (1,5)
+(15 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)';
+ QUERY PLAN
+-----------------------------------
+ Tid Range Scan on tidrangescan
+ TID Cond: (ctid < '(0,0)'::tid)
+(2 rows)
+
+SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)';
+ ctid
+------
+(0 rows)
+
+-- range scans with lower bound
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid > '(2,8)';
+ QUERY PLAN
+-----------------------------------
+ Tid Range Scan on tidrangescan
+ TID Cond: (ctid > '(2,8)'::tid)
+(2 rows)
+
+SELECT ctid FROM tidrangescan WHERE ctid > '(2,8)';
+ ctid
+--------
+ (2,9)
+ (2,10)
+(2 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE '(2,8)' < ctid;
+ QUERY PLAN
+-----------------------------------
+ Tid Range Scan on tidrangescan
+ TID Cond: ('(2,8)'::tid < ctid)
+(2 rows)
+
+SELECT ctid FROM tidrangescan WHERE '(2,8)' < ctid;
+ ctid
+--------
+ (2,9)
+ (2,10)
+(2 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid >= '(2,8)';
+ QUERY PLAN
+------------------------------------
+ Tid Range Scan on tidrangescan
+ TID Cond: (ctid >= '(2,8)'::tid)
+(2 rows)
+
+SELECT ctid FROM tidrangescan WHERE ctid >= '(2,8)';
+ ctid
+--------
+ (2,8)
+ (2,9)
+ (2,10)
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid >= '(100,0)';
+ QUERY PLAN
+--------------------------------------
+ Tid Range Scan on tidrangescan
+ TID Cond: (ctid >= '(100,0)'::tid)
+(2 rows)
+
+SELECT ctid FROM tidrangescan WHERE ctid >= '(100,0)';
+ ctid
+------
+(0 rows)
+
+-- range scans with both bounds
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid > '(1,4)' AND '(1,7)' >= ctid;
+ QUERY PLAN
+----------------------------------------------------------------
+ Tid Range Scan on tidrangescan
+ TID Cond: ((ctid > '(1,4)'::tid) AND ('(1,7)'::tid >= ctid))
+(2 rows)
+
+SELECT ctid FROM tidrangescan WHERE ctid > '(1,4)' AND '(1,7)' >= ctid;
+ ctid
+-------
+ (1,5)
+ (1,6)
+ (1,7)
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE '(1,7)' >= ctid AND ctid > '(1,4)';
+ QUERY PLAN
+----------------------------------------------------------------
+ Tid Range Scan on tidrangescan
+ TID Cond: (('(1,7)'::tid >= ctid) AND (ctid > '(1,4)'::tid))
+(2 rows)
+
+SELECT ctid FROM tidrangescan WHERE '(1,7)' >= ctid AND ctid > '(1,4)';
+ ctid
+-------
+ (1,5)
+ (1,6)
+ (1,7)
+(3 rows)
+
+-- extreme offsets
+SELECT ctid FROM tidrangescan WHERE ctid > '(0,65535)' AND ctid < '(1,0)' LIMIT 1;
+ ctid
+------
+(0 rows)
+
+SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)' LIMIT 1;
+ ctid
+------
+(0 rows)
+
+SELECT ctid FROM tidrangescan WHERE ctid > '(4294967295,65535)';
+ ctid
+------
+(0 rows)
+
+SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)';
+ ctid
+------
+(0 rows)
+
+-- NULLs in the range cannot return tuples
+SELECT ctid FROM tidrangescan WHERE ctid >= (SELECT NULL::tid);
+ ctid
+------
+(0 rows)
+
+-- rescans
+EXPLAIN (COSTS OFF)
+SELECT t.ctid,t2.c FROM tidrangescan t,
+LATERAL (SELECT count(*) c FROM tidrangescan t2 WHERE t2.ctid <= t.ctid) t2
+WHERE t.ctid < '(1,0)';
+ QUERY PLAN
+-----------------------------------------------
+ Nested Loop
+ -> Tid Range Scan on tidrangescan t
+ TID Cond: (ctid < '(1,0)'::tid)
+ -> Aggregate
+ -> Tid Range Scan on tidrangescan t2
+ TID Cond: (ctid <= t.ctid)
+(6 rows)
+
+SELECT t.ctid,t2.c FROM tidrangescan t,
+LATERAL (SELECT count(*) c FROM tidrangescan t2 WHERE t2.ctid <= t.ctid) t2
+WHERE t.ctid < '(1,0)';
+ ctid | c
+--------+----
+ (0,1) | 1
+ (0,2) | 2
+ (0,3) | 3
+ (0,4) | 4
+ (0,5) | 5
+ (0,6) | 6
+ (0,7) | 7
+ (0,8) | 8
+ (0,9) | 9
+ (0,10) | 10
+(10 rows)
+
+-- cursors
+-- Ensure we get a TID Range scan without a Materialize node.
+EXPLAIN (COSTS OFF)
+DECLARE c SCROLL CURSOR FOR SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)';
+ QUERY PLAN
+-----------------------------------
+ Tid Range Scan on tidrangescan
+ TID Cond: (ctid < '(1,0)'::tid)
+(2 rows)
+
+BEGIN;
+DECLARE c SCROLL CURSOR FOR SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)';
+FETCH NEXT c;
+ ctid
+-------
+ (0,1)
+(1 row)
+
+FETCH NEXT c;
+ ctid
+-------
+ (0,2)
+(1 row)
+
+FETCH PRIOR c;
+ ctid
+-------
+ (0,1)
+(1 row)
+
+FETCH FIRST c;
+ ctid
+-------
+ (0,1)
+(1 row)
+
+FETCH LAST c;
+ ctid
+--------
+ (0,10)
+(1 row)
+
+COMMIT;
+DROP TABLE tidrangescan;
+RESET enable_seqscan;
diff --git a/src/test/regress/expected/tidscan.out b/src/test/regress/expected/tidscan.out
new file mode 100644
index 0000000..13c3c36
--- /dev/null
+++ b/src/test/regress/expected/tidscan.out
@@ -0,0 +1,296 @@
+-- tests for tidscans
+CREATE TABLE tidscan(id integer);
+-- only insert a few rows, we don't want to spill onto a second table page
+INSERT INTO tidscan VALUES (1), (2), (3);
+-- show ctids
+SELECT ctid, * FROM tidscan;
+ ctid | id
+-------+----
+ (0,1) | 1
+ (0,2) | 2
+ (0,3) | 3
+(3 rows)
+
+-- ctid equality - implemented as tidscan
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,1)';
+ QUERY PLAN
+-----------------------------------
+ Tid Scan on tidscan
+ TID Cond: (ctid = '(0,1)'::tid)
+(2 rows)
+
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,1)';
+ ctid | id
+-------+----
+ (0,1) | 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE '(0,1)' = ctid;
+ QUERY PLAN
+-----------------------------------
+ Tid Scan on tidscan
+ TID Cond: ('(0,1)'::tid = ctid)
+(2 rows)
+
+SELECT ctid, * FROM tidscan WHERE '(0,1)' = ctid;
+ ctid | id
+-------+----
+ (0,1) | 1
+(1 row)
+
+-- OR'd clauses
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+ QUERY PLAN
+--------------------------------------------------------------
+ Tid Scan on tidscan
+ TID Cond: ((ctid = '(0,2)'::tid) OR ('(0,1)'::tid = ctid))
+(2 rows)
+
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+ ctid | id
+-------+----
+ (0,1) | 1
+ (0,2) | 2
+(2 rows)
+
+-- ctid = ScalarArrayOp - implemented as tidscan
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
+ QUERY PLAN
+-------------------------------------------------------
+ Tid Scan on tidscan
+ TID Cond: (ctid = ANY ('{"(0,1)","(0,2)"}'::tid[]))
+(2 rows)
+
+SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
+ ctid | id
+-------+----
+ (0,1) | 1
+ (0,2) | 2
+(2 rows)
+
+-- ctid != ScalarArrayOp - can't be implemented as tidscan
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE ctid != ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
+ QUERY PLAN
+------------------------------------------------------
+ Seq Scan on tidscan
+ Filter: (ctid <> ANY ('{"(0,1)","(0,2)"}'::tid[]))
+(2 rows)
+
+SELECT ctid, * FROM tidscan WHERE ctid != ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
+ ctid | id
+-------+----
+ (0,1) | 1
+ (0,2) | 2
+ (0,3) | 3
+(3 rows)
+
+-- tid equality extracted from sub-AND clauses
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan
+WHERE (id = 3 AND ctid IN ('(0,2)', '(0,3)')) OR (ctid = '(0,1)' AND id = 1);
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------
+ Tid Scan on tidscan
+ TID Cond: ((ctid = ANY ('{"(0,2)","(0,3)"}'::tid[])) OR (ctid = '(0,1)'::tid))
+ Filter: (((id = 3) AND (ctid = ANY ('{"(0,2)","(0,3)"}'::tid[]))) OR ((ctid = '(0,1)'::tid) AND (id = 1)))
+(3 rows)
+
+SELECT ctid, * FROM tidscan
+WHERE (id = 3 AND ctid IN ('(0,2)', '(0,3)')) OR (ctid = '(0,1)' AND id = 1);
+ ctid | id
+-------+----
+ (0,1) | 1
+ (0,3) | 3
+(2 rows)
+
+-- nestloop-with-inner-tidscan joins on tid
+SET enable_hashjoin TO off; -- otherwise hash join might win
+EXPLAIN (COSTS OFF)
+SELECT t1.ctid, t1.*, t2.ctid, t2.*
+FROM tidscan t1 JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1;
+ QUERY PLAN
+------------------------------------
+ Nested Loop
+ -> Seq Scan on tidscan t1
+ Filter: (id = 1)
+ -> Tid Scan on tidscan t2
+ TID Cond: (ctid = t1.ctid)
+(5 rows)
+
+SELECT t1.ctid, t1.*, t2.ctid, t2.*
+FROM tidscan t1 JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1;
+ ctid | id | ctid | id
+-------+----+-------+----
+ (0,1) | 1 | (0,1) | 1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT t1.ctid, t1.*, t2.ctid, t2.*
+FROM tidscan t1 LEFT JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1;
+ QUERY PLAN
+------------------------------------
+ Nested Loop Left Join
+ -> Seq Scan on tidscan t1
+ Filter: (id = 1)
+ -> Tid Scan on tidscan t2
+ TID Cond: (t1.ctid = ctid)
+(5 rows)
+
+SELECT t1.ctid, t1.*, t2.ctid, t2.*
+FROM tidscan t1 LEFT JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1;
+ ctid | id | ctid | id
+-------+----+-------+----
+ (0,1) | 1 | (0,1) | 1
+(1 row)
+
+RESET enable_hashjoin;
+-- exercise backward scan and rewind
+BEGIN;
+DECLARE c CURSOR FOR
+SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
+FETCH ALL FROM c;
+ ctid | id
+-------+----
+ (0,1) | 1
+ (0,2) | 2
+(2 rows)
+
+FETCH BACKWARD 1 FROM c;
+ ctid | id
+-------+----
+ (0,2) | 2
+(1 row)
+
+FETCH FIRST FROM c;
+ ctid | id
+-------+----
+ (0,1) | 1
+(1 row)
+
+ROLLBACK;
+-- tidscan via CURRENT OF
+BEGIN;
+DECLARE c CURSOR FOR SELECT ctid, * FROM tidscan;
+FETCH NEXT FROM c; -- skip one row
+ ctid | id
+-------+----
+ (0,1) | 1
+(1 row)
+
+FETCH NEXT FROM c;
+ ctid | id
+-------+----
+ (0,2) | 2
+(1 row)
+
+-- perform update
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+UPDATE tidscan SET id = -id WHERE CURRENT OF c RETURNING *;
+ QUERY PLAN
+---------------------------------------------------
+ Update on tidscan (actual rows=1 loops=1)
+ -> Tid Scan on tidscan (actual rows=1 loops=1)
+ TID Cond: CURRENT OF c
+(3 rows)
+
+FETCH NEXT FROM c;
+ ctid | id
+-------+----
+ (0,3) | 3
+(1 row)
+
+-- perform update
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+UPDATE tidscan SET id = -id WHERE CURRENT OF c RETURNING *;
+ QUERY PLAN
+---------------------------------------------------
+ Update on tidscan (actual rows=1 loops=1)
+ -> Tid Scan on tidscan (actual rows=1 loops=1)
+ TID Cond: CURRENT OF c
+(3 rows)
+
+SELECT * FROM tidscan;
+ id
+----
+ 1
+ -2
+ -3
+(3 rows)
+
+-- position cursor past any rows
+FETCH NEXT FROM c;
+ ctid | id
+------+----
+(0 rows)
+
+-- should error out
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+UPDATE tidscan SET id = -id WHERE CURRENT OF c RETURNING *;
+ERROR: cursor "c" is not positioned on a row
+ROLLBACK;
+-- bulk joins on CTID
+-- (these plans don't use TID scans, but this still seems like an
+-- appropriate place for these tests)
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
+ QUERY PLAN
+----------------------------------------
+ Aggregate
+ -> Hash Join
+ Hash Cond: (t1.ctid = t2.ctid)
+ -> Seq Scan on tenk1 t1
+ -> Hash
+ -> Seq Scan on tenk1 t2
+(6 rows)
+
+SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
+ count
+-------
+ 10000
+(1 row)
+
+SET enable_hashjoin TO off;
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
+ QUERY PLAN
+-----------------------------------------
+ Aggregate
+ -> Merge Join
+ Merge Cond: (t1.ctid = t2.ctid)
+ -> Sort
+ Sort Key: t1.ctid
+ -> Seq Scan on tenk1 t1
+ -> Sort
+ Sort Key: t2.ctid
+ -> Seq Scan on tenk1 t2
+(9 rows)
+
+SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
+ count
+-------
+ 10000
+(1 row)
+
+RESET enable_hashjoin;
+-- check predicate lock on CTID
+BEGIN ISOLATION LEVEL SERIALIZABLE;
+SELECT * FROM tidscan WHERE ctid = '(0,1)';
+ id
+----
+ 1
+(1 row)
+
+-- locktype should be 'tuple'
+SELECT locktype, mode FROM pg_locks WHERE pid = pg_backend_pid() AND mode = 'SIReadLock';
+ locktype | mode
+----------+------------
+ tuple | SIReadLock
+(1 row)
+
+ROLLBACK;
+DROP TABLE tidscan;
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
new file mode 100644
index 0000000..39b409f
--- /dev/null
+++ b/src/test/regress/expected/time.out
@@ -0,0 +1,200 @@
+--
+-- TIME
+--
+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 ('2003-03-07 15:36:39 America/New_York');
+INSERT INTO TIME_TBL VALUES ('2003-07-07 15:36:39 America/New_York');
+-- this should fail (the timezone offset is not known)
+INSERT INTO TIME_TBL VALUES ('15:36:39 America/New_York');
+ERROR: invalid input syntax for type time: "15:36:39 America/New_York"
+LINE 1: INSERT INTO TIME_TBL VALUES ('15:36:39 America/New_York');
+ ^
+SELECT f1 AS "Time" FROM TIME_TBL;
+ Time
+-------------
+ 00:00:00
+ 01:00:00
+ 02:03:00
+ 11:59:00
+ 12:00:00
+ 12:01:00
+ 23:59:00
+ 23:59:59.99
+ 15:36:39
+ 15:36:39
+(10 rows)
+
+SELECT f1 AS "Three" FROM TIME_TBL WHERE f1 < '05:06:07';
+ Three
+----------
+ 00:00:00
+ 01:00:00
+ 02:03:00
+(3 rows)
+
+SELECT f1 AS "Five" FROM TIME_TBL WHERE f1 > '05:06:07';
+ Five
+-------------
+ 11:59:00
+ 12:00:00
+ 12:01:00
+ 23:59:00
+ 23:59:59.99
+ 15:36:39
+ 15:36:39
+(7 rows)
+
+SELECT f1 AS "None" FROM TIME_TBL WHERE f1 < '00:00';
+ None
+------
+(0 rows)
+
+SELECT f1 AS "Eight" FROM TIME_TBL WHERE f1 >= '00:00';
+ Eight
+-------------
+ 00:00:00
+ 01:00:00
+ 02:03:00
+ 11:59:00
+ 12:00:00
+ 12:01:00
+ 23:59:00
+ 23:59:59.99
+ 15:36:39
+ 15:36:39
+(10 rows)
+
+-- Check edge cases
+SELECT '23:59:59.999999'::time;
+ time
+-----------------
+ 23:59:59.999999
+(1 row)
+
+SELECT '23:59:59.9999999'::time; -- rounds up
+ time
+----------
+ 24:00:00
+(1 row)
+
+SELECT '23:59:60'::time; -- rounds up
+ time
+----------
+ 24:00:00
+(1 row)
+
+SELECT '24:00:00'::time; -- allowed
+ time
+----------
+ 24:00:00
+(1 row)
+
+SELECT '24:00:00.01'::time; -- not allowed
+ERROR: date/time field value out of range: "24:00:00.01"
+LINE 1: SELECT '24:00:00.01'::time;
+ ^
+SELECT '23:59:60.01'::time; -- not allowed
+ERROR: date/time field value out of range: "23:59:60.01"
+LINE 1: SELECT '23:59:60.01'::time;
+ ^
+SELECT '24:01:00'::time; -- not allowed
+ERROR: date/time field value out of range: "24:01:00"
+LINE 1: SELECT '24:01:00'::time;
+ ^
+SELECT '25:00:00'::time; -- not allowed
+ERROR: date/time field value out of range: "25:00:00"
+LINE 1: SELECT '25:00:00'::time;
+ ^
+--
+-- TIME simple math
+--
+-- We now make a distinction between time and intervals,
+-- and adding two times together makes no sense at all.
+-- Leave in one query to show that it is rejected,
+-- and do the rest of the testing in horology.sql
+-- where we do mixed-type arithmetic. - thomas 2000-12-02
+SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
+ERROR: operator is not unique: time without time zone + time without time zone
+LINE 1: SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
+ ^
+HINT: Could not choose a best candidate operator. You might need to add explicit type casts.
+--
+-- test EXTRACT
+--
+SELECT EXTRACT(MICROSECOND FROM TIME '2020-05-26 13:30:25.575401');
+ extract
+----------
+ 25575401
+(1 row)
+
+SELECT EXTRACT(MILLISECOND FROM TIME '2020-05-26 13:30:25.575401');
+ extract
+-----------
+ 25575.401
+(1 row)
+
+SELECT EXTRACT(SECOND FROM TIME '2020-05-26 13:30:25.575401');
+ extract
+-----------
+ 25.575401
+(1 row)
+
+SELECT EXTRACT(MINUTE FROM TIME '2020-05-26 13:30:25.575401');
+ extract
+---------
+ 30
+(1 row)
+
+SELECT EXTRACT(HOUR FROM TIME '2020-05-26 13:30:25.575401');
+ extract
+---------
+ 13
+(1 row)
+
+SELECT EXTRACT(DAY FROM TIME '2020-05-26 13:30:25.575401'); -- error
+ERROR: "time" units "day" not recognized
+SELECT EXTRACT(FORTNIGHT FROM TIME '2020-05-26 13:30:25.575401'); -- error
+ERROR: "time" units "fortnight" not recognized
+SELECT EXTRACT(TIMEZONE FROM TIME '2020-05-26 13:30:25.575401'); -- error
+ERROR: "time" units "timezone" not recognized
+SELECT EXTRACT(EPOCH FROM TIME '2020-05-26 13:30:25.575401');
+ extract
+--------------
+ 48625.575401
+(1 row)
+
+-- date_part implementation is mostly the same as extract, so only
+-- test a few cases for additional coverage.
+SELECT date_part('microsecond', TIME '2020-05-26 13:30:25.575401');
+ date_part
+-----------
+ 25575401
+(1 row)
+
+SELECT date_part('millisecond', TIME '2020-05-26 13:30:25.575401');
+ date_part
+-----------
+ 25575.401
+(1 row)
+
+SELECT date_part('second', TIME '2020-05-26 13:30:25.575401');
+ date_part
+-----------
+ 25.575401
+(1 row)
+
+SELECT date_part('epoch', TIME '2020-05-26 13:30:25.575401');
+ date_part
+--------------
+ 48625.575401
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
new file mode 100644
index 0000000..79f8180
--- /dev/null
+++ b/src/test/regress/expected/timestamp.out
@@ -0,0 +1,2081 @@
+--
+-- TIMESTAMP
+--
+CREATE TABLE TIMESTAMP_TBL (d1 timestamp(2) without time zone);
+-- Test shorthand input values
+-- We can't just "select" the results since they aren't constants; test for
+-- equality instead. We can do that by running the test inside a transaction
+-- block, within which the value of 'now' shouldn't change, and so these
+-- related values shouldn't either.
+BEGIN;
+INSERT INTO TIMESTAMP_TBL VALUES ('today');
+INSERT INTO TIMESTAMP_TBL VALUES ('yesterday');
+INSERT INTO TIMESTAMP_TBL VALUES ('tomorrow');
+-- time zone should be ignored by this data type
+INSERT INTO TIMESTAMP_TBL VALUES ('tomorrow EST');
+INSERT INTO TIMESTAMP_TBL VALUES ('tomorrow zulu');
+SELECT count(*) AS One FROM TIMESTAMP_TBL WHERE d1 = timestamp without time zone 'today';
+ one
+-----
+ 1
+(1 row)
+
+SELECT count(*) AS Three FROM TIMESTAMP_TBL WHERE d1 = timestamp without time zone 'tomorrow';
+ three
+-------
+ 3
+(1 row)
+
+SELECT count(*) AS One FROM TIMESTAMP_TBL WHERE d1 = timestamp without time zone 'yesterday';
+ one
+-----
+ 1
+(1 row)
+
+COMMIT;
+DELETE FROM TIMESTAMP_TBL;
+-- Verify that 'now' *does* change over a reasonable interval such as 100 msec,
+-- and that it doesn't change over the same interval within a transaction block
+INSERT INTO TIMESTAMP_TBL VALUES ('now');
+SELECT pg_sleep(0.1);
+ pg_sleep
+----------
+
+(1 row)
+
+BEGIN;
+INSERT INTO TIMESTAMP_TBL VALUES ('now');
+SELECT pg_sleep(0.1);
+ pg_sleep
+----------
+
+(1 row)
+
+INSERT INTO TIMESTAMP_TBL VALUES ('now');
+SELECT pg_sleep(0.1);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT count(*) AS two FROM TIMESTAMP_TBL WHERE d1 = timestamp(2) without time zone 'now';
+ two
+-----
+ 2
+(1 row)
+
+SELECT count(d1) AS three, count(DISTINCT d1) AS two FROM TIMESTAMP_TBL;
+ three | two
+-------+-----
+ 3 | 2
+(1 row)
+
+COMMIT;
+TRUNCATE TIMESTAMP_TBL;
+-- Special values
+INSERT INTO TIMESTAMP_TBL VALUES ('-infinity');
+INSERT INTO TIMESTAMP_TBL VALUES ('infinity');
+INSERT INTO TIMESTAMP_TBL VALUES ('epoch');
+-- Postgres v6.0 standard output format
+INSERT INTO TIMESTAMP_TBL VALUES ('Mon Feb 10 17:32:01 1997 PST');
+-- Variations on Postgres v6.1 standard output format
+INSERT INTO TIMESTAMP_TBL VALUES ('Mon Feb 10 17:32:01.000001 1997 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('Mon Feb 10 17:32:01.999999 1997 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('Mon Feb 10 17:32:01.4 1997 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('Mon Feb 10 17:32:01.5 1997 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('Mon Feb 10 17:32:01.6 1997 PST');
+-- ISO 8601 format
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-01-02');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-01-02 03:04:05');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-02-10 17:32:01-08');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-02-10 17:32:01-0800');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-02-10 17:32:01 -08:00');
+INSERT INTO TIMESTAMP_TBL VALUES ('19970210 173201 -0800');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-06-10 17:32:01 -07:00');
+INSERT INTO TIMESTAMP_TBL VALUES ('2001-09-22T18:19:20');
+-- POSIX format (note that the timezone abbrev is just decoration here)
+INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 08:14:01 GMT+8');
+INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 13:14:02 GMT-1');
+INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 12:14:03 GMT-2');
+INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 03:14:04 PST+8');
+INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 02:14:05 MST+7:00');
+-- Variations for acceptable input formats
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 17:32:01 1997 -0800');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 5:32PM 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997/02/10 17:32:01-0800');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-02-10 17:32:01 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb-10-1997 17:32:01 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('02-10-1997 17:32:01 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('19970210 173201 PST');
+set datestyle to ymd;
+INSERT INTO TIMESTAMP_TBL VALUES ('97FEB10 5:32:01PM UTC');
+INSERT INTO TIMESTAMP_TBL VALUES ('97/02/10 17:32:01 UTC');
+reset datestyle;
+INSERT INTO TIMESTAMP_TBL VALUES ('1997.041 17:32:01 UTC');
+INSERT INTO TIMESTAMP_TBL VALUES ('19970210 173201 America/New_York');
+-- this fails (even though TZ is a no-op, we still look it up)
+INSERT INTO TIMESTAMP_TBL VALUES ('19970710 173201 America/Does_not_exist');
+ERROR: time zone "america/does_not_exist" not recognized
+LINE 1: INSERT INTO TIMESTAMP_TBL VALUES ('19970710 173201 America/D...
+ ^
+-- Check date conversion and date arithmetic
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-06-10 18:32:01 PDT');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 11 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 12 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 13 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 14 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 15 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 0097 BC');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 0097');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 0597');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 1097');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 1697');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 1797');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 1897');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 2097');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 28 17:32:01 1996');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 29 17:32:01 1996');
+INSERT INTO TIMESTAMP_TBL VALUES ('Mar 01 17:32:01 1996');
+INSERT INTO TIMESTAMP_TBL VALUES ('Dec 30 17:32:01 1996');
+INSERT INTO TIMESTAMP_TBL VALUES ('Dec 31 17:32:01 1996');
+INSERT INTO TIMESTAMP_TBL VALUES ('Jan 01 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 28 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 29 17:32:01 1997');
+ERROR: date/time field value out of range: "Feb 29 17:32:01 1997"
+LINE 1: INSERT INTO TIMESTAMP_TBL VALUES ('Feb 29 17:32:01 1997');
+ ^
+INSERT INTO TIMESTAMP_TBL VALUES ('Mar 01 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Dec 30 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Dec 31 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Dec 31 17:32:01 1999');
+INSERT INTO TIMESTAMP_TBL VALUES ('Jan 01 17:32:01 2000');
+INSERT INTO TIMESTAMP_TBL VALUES ('Dec 31 17:32:01 2000');
+INSERT INTO TIMESTAMP_TBL VALUES ('Jan 01 17:32:01 2001');
+-- Currently unsupported syntax and ranges
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 -0097');
+ERROR: time zone displacement out of range: "Feb 16 17:32:01 -0097"
+LINE 1: INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 -0097');
+ ^
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 5097 BC');
+ERROR: timestamp out of range: "Feb 16 17:32:01 5097 BC"
+LINE 1: INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 5097 BC')...
+ ^
+SELECT d1 FROM TIMESTAMP_TBL;
+ d1
+-----------------------------
+ -infinity
+ infinity
+ Thu Jan 01 00:00:00 1970
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:02 1997
+ Mon Feb 10 17:32:01.4 1997
+ Mon Feb 10 17:32:01.5 1997
+ Mon Feb 10 17:32:01.6 1997
+ Thu Jan 02 00:00:00 1997
+ Thu Jan 02 03:04:05 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Jun 10 17:32:01 1997
+ Sat Sep 22 18:19:20 2001
+ Wed Mar 15 08:14:01 2000
+ Wed Mar 15 13:14:02 2000
+ Wed Mar 15 12:14:03 2000
+ Wed Mar 15 03:14:04 2000
+ Wed Mar 15 02:14:05 2000
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:00 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Jun 10 18:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Feb 11 17:32:01 1997
+ Wed Feb 12 17:32:01 1997
+ Thu Feb 13 17:32:01 1997
+ Fri Feb 14 17:32:01 1997
+ Sat Feb 15 17:32:01 1997
+ Sun Feb 16 17:32:01 1997
+ Tue Feb 16 17:32:01 0097 BC
+ Sat Feb 16 17:32:01 0097
+ Thu Feb 16 17:32:01 0597
+ Tue Feb 16 17:32:01 1097
+ Sat Feb 16 17:32:01 1697
+ Thu Feb 16 17:32:01 1797
+ Tue Feb 16 17:32:01 1897
+ Sun Feb 16 17:32:01 1997
+ Sat Feb 16 17:32:01 2097
+ Wed Feb 28 17:32:01 1996
+ Thu Feb 29 17:32:01 1996
+ Fri Mar 01 17:32:01 1996
+ Mon Dec 30 17:32:01 1996
+ Tue Dec 31 17:32:01 1996
+ Wed Jan 01 17:32:01 1997
+ Fri Feb 28 17:32:01 1997
+ Sat Mar 01 17:32:01 1997
+ Tue Dec 30 17:32:01 1997
+ Wed Dec 31 17:32:01 1997
+ Fri Dec 31 17:32:01 1999
+ Sat Jan 01 17:32:01 2000
+ Sun Dec 31 17:32:01 2000
+ Mon Jan 01 17:32:01 2001
+(65 rows)
+
+-- Check behavior at the boundaries of the timestamp range
+SELECT '4714-11-24 00:00:00 BC'::timestamp;
+ timestamp
+-----------------------------
+ Mon Nov 24 00:00:00 4714 BC
+(1 row)
+
+SELECT '4714-11-23 23:59:59 BC'::timestamp; -- out of range
+ERROR: timestamp out of range: "4714-11-23 23:59:59 BC"
+LINE 1: SELECT '4714-11-23 23:59:59 BC'::timestamp;
+ ^
+SELECT '294276-12-31 23:59:59'::timestamp;
+ timestamp
+----------------------------
+ Sun Dec 31 23:59:59 294276
+(1 row)
+
+SELECT '294277-01-01 00:00:00'::timestamp; -- out of range
+ERROR: timestamp out of range: "294277-01-01 00:00:00"
+LINE 1: SELECT '294277-01-01 00:00:00'::timestamp;
+ ^
+-- Demonstrate functions and operators
+SELECT d1 FROM TIMESTAMP_TBL
+ WHERE d1 > timestamp without time zone '1997-01-02';
+ d1
+----------------------------
+ infinity
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:02 1997
+ Mon Feb 10 17:32:01.4 1997
+ Mon Feb 10 17:32:01.5 1997
+ Mon Feb 10 17:32:01.6 1997
+ Thu Jan 02 03:04:05 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Jun 10 17:32:01 1997
+ Sat Sep 22 18:19:20 2001
+ Wed Mar 15 08:14:01 2000
+ Wed Mar 15 13:14:02 2000
+ Wed Mar 15 12:14:03 2000
+ Wed Mar 15 03:14:04 2000
+ Wed Mar 15 02:14:05 2000
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:00 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Jun 10 18:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Feb 11 17:32:01 1997
+ Wed Feb 12 17:32:01 1997
+ Thu Feb 13 17:32:01 1997
+ Fri Feb 14 17:32:01 1997
+ Sat Feb 15 17:32:01 1997
+ Sun Feb 16 17:32:01 1997
+ Sun Feb 16 17:32:01 1997
+ Sat Feb 16 17:32:01 2097
+ Fri Feb 28 17:32:01 1997
+ Sat Mar 01 17:32:01 1997
+ Tue Dec 30 17:32:01 1997
+ Wed Dec 31 17:32:01 1997
+ Fri Dec 31 17:32:01 1999
+ Sat Jan 01 17:32:01 2000
+ Sun Dec 31 17:32:01 2000
+ Mon Jan 01 17:32:01 2001
+(49 rows)
+
+SELECT d1 FROM TIMESTAMP_TBL
+ WHERE d1 < timestamp without time zone '1997-01-02';
+ d1
+-----------------------------
+ -infinity
+ Thu Jan 01 00:00:00 1970
+ Tue Feb 16 17:32:01 0097 BC
+ Sat Feb 16 17:32:01 0097
+ Thu Feb 16 17:32:01 0597
+ Tue Feb 16 17:32:01 1097
+ Sat Feb 16 17:32:01 1697
+ Thu Feb 16 17:32:01 1797
+ Tue Feb 16 17:32:01 1897
+ Wed Feb 28 17:32:01 1996
+ Thu Feb 29 17:32:01 1996
+ Fri Mar 01 17:32:01 1996
+ Mon Dec 30 17:32:01 1996
+ Tue Dec 31 17:32:01 1996
+ Wed Jan 01 17:32:01 1997
+(15 rows)
+
+SELECT d1 FROM TIMESTAMP_TBL
+ WHERE d1 = timestamp without time zone '1997-01-02';
+ d1
+--------------------------
+ Thu Jan 02 00:00:00 1997
+(1 row)
+
+SELECT d1 FROM TIMESTAMP_TBL
+ WHERE d1 != timestamp without time zone '1997-01-02';
+ d1
+-----------------------------
+ -infinity
+ infinity
+ Thu Jan 01 00:00:00 1970
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:02 1997
+ Mon Feb 10 17:32:01.4 1997
+ Mon Feb 10 17:32:01.5 1997
+ Mon Feb 10 17:32:01.6 1997
+ Thu Jan 02 03:04:05 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Jun 10 17:32:01 1997
+ Sat Sep 22 18:19:20 2001
+ Wed Mar 15 08:14:01 2000
+ Wed Mar 15 13:14:02 2000
+ Wed Mar 15 12:14:03 2000
+ Wed Mar 15 03:14:04 2000
+ Wed Mar 15 02:14:05 2000
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:00 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Jun 10 18:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Feb 11 17:32:01 1997
+ Wed Feb 12 17:32:01 1997
+ Thu Feb 13 17:32:01 1997
+ Fri Feb 14 17:32:01 1997
+ Sat Feb 15 17:32:01 1997
+ Sun Feb 16 17:32:01 1997
+ Tue Feb 16 17:32:01 0097 BC
+ Sat Feb 16 17:32:01 0097
+ Thu Feb 16 17:32:01 0597
+ Tue Feb 16 17:32:01 1097
+ Sat Feb 16 17:32:01 1697
+ Thu Feb 16 17:32:01 1797
+ Tue Feb 16 17:32:01 1897
+ Sun Feb 16 17:32:01 1997
+ Sat Feb 16 17:32:01 2097
+ Wed Feb 28 17:32:01 1996
+ Thu Feb 29 17:32:01 1996
+ Fri Mar 01 17:32:01 1996
+ Mon Dec 30 17:32:01 1996
+ Tue Dec 31 17:32:01 1996
+ Wed Jan 01 17:32:01 1997
+ Fri Feb 28 17:32:01 1997
+ Sat Mar 01 17:32:01 1997
+ Tue Dec 30 17:32:01 1997
+ Wed Dec 31 17:32:01 1997
+ Fri Dec 31 17:32:01 1999
+ Sat Jan 01 17:32:01 2000
+ Sun Dec 31 17:32:01 2000
+ Mon Jan 01 17:32:01 2001
+(64 rows)
+
+SELECT d1 FROM TIMESTAMP_TBL
+ WHERE d1 <= timestamp without time zone '1997-01-02';
+ d1
+-----------------------------
+ -infinity
+ Thu Jan 01 00:00:00 1970
+ Thu Jan 02 00:00:00 1997
+ Tue Feb 16 17:32:01 0097 BC
+ Sat Feb 16 17:32:01 0097
+ Thu Feb 16 17:32:01 0597
+ Tue Feb 16 17:32:01 1097
+ Sat Feb 16 17:32:01 1697
+ Thu Feb 16 17:32:01 1797
+ Tue Feb 16 17:32:01 1897
+ Wed Feb 28 17:32:01 1996
+ Thu Feb 29 17:32:01 1996
+ Fri Mar 01 17:32:01 1996
+ Mon Dec 30 17:32:01 1996
+ Tue Dec 31 17:32:01 1996
+ Wed Jan 01 17:32:01 1997
+(16 rows)
+
+SELECT d1 FROM TIMESTAMP_TBL
+ WHERE d1 >= timestamp without time zone '1997-01-02';
+ d1
+----------------------------
+ infinity
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:02 1997
+ Mon Feb 10 17:32:01.4 1997
+ Mon Feb 10 17:32:01.5 1997
+ Mon Feb 10 17:32:01.6 1997
+ Thu Jan 02 00:00:00 1997
+ Thu Jan 02 03:04:05 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Jun 10 17:32:01 1997
+ Sat Sep 22 18:19:20 2001
+ Wed Mar 15 08:14:01 2000
+ Wed Mar 15 13:14:02 2000
+ Wed Mar 15 12:14:03 2000
+ Wed Mar 15 03:14:04 2000
+ Wed Mar 15 02:14:05 2000
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:00 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Jun 10 18:32:01 1997
+ Mon Feb 10 17:32:01 1997
+ Tue Feb 11 17:32:01 1997
+ Wed Feb 12 17:32:01 1997
+ Thu Feb 13 17:32:01 1997
+ Fri Feb 14 17:32:01 1997
+ Sat Feb 15 17:32:01 1997
+ Sun Feb 16 17:32:01 1997
+ Sun Feb 16 17:32:01 1997
+ Sat Feb 16 17:32:01 2097
+ Fri Feb 28 17:32:01 1997
+ Sat Mar 01 17:32:01 1997
+ Tue Dec 30 17:32:01 1997
+ Wed Dec 31 17:32:01 1997
+ Fri Dec 31 17:32:01 1999
+ Sat Jan 01 17:32:01 2000
+ Sun Dec 31 17:32:01 2000
+ Mon Jan 01 17:32:01 2001
+(50 rows)
+
+SELECT d1 - timestamp without time zone '1997-01-02' AS diff
+ FROM TIMESTAMP_TBL WHERE d1 BETWEEN '1902-01-01' AND '2038-01-01';
+ diff
+----------------------------------------
+ @ 9863 days ago
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 2 secs
+ @ 39 days 17 hours 32 mins 1.4 secs
+ @ 39 days 17 hours 32 mins 1.5 secs
+ @ 39 days 17 hours 32 mins 1.6 secs
+ @ 0
+ @ 3 hours 4 mins 5 secs
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 159 days 17 hours 32 mins 1 sec
+ @ 1724 days 18 hours 19 mins 20 secs
+ @ 1168 days 8 hours 14 mins 1 sec
+ @ 1168 days 13 hours 14 mins 2 secs
+ @ 1168 days 12 hours 14 mins 3 secs
+ @ 1168 days 3 hours 14 mins 4 secs
+ @ 1168 days 2 hours 14 mins 5 secs
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 159 days 18 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 40 days 17 hours 32 mins 1 sec
+ @ 41 days 17 hours 32 mins 1 sec
+ @ 42 days 17 hours 32 mins 1 sec
+ @ 43 days 17 hours 32 mins 1 sec
+ @ 44 days 17 hours 32 mins 1 sec
+ @ 45 days 17 hours 32 mins 1 sec
+ @ 45 days 17 hours 32 mins 1 sec
+ @ 308 days 6 hours 27 mins 59 secs ago
+ @ 307 days 6 hours 27 mins 59 secs ago
+ @ 306 days 6 hours 27 mins 59 secs ago
+ @ 2 days 6 hours 27 mins 59 secs ago
+ @ 1 day 6 hours 27 mins 59 secs ago
+ @ 6 hours 27 mins 59 secs ago
+ @ 57 days 17 hours 32 mins 1 sec
+ @ 58 days 17 hours 32 mins 1 sec
+ @ 362 days 17 hours 32 mins 1 sec
+ @ 363 days 17 hours 32 mins 1 sec
+ @ 1093 days 17 hours 32 mins 1 sec
+ @ 1094 days 17 hours 32 mins 1 sec
+ @ 1459 days 17 hours 32 mins 1 sec
+ @ 1460 days 17 hours 32 mins 1 sec
+(55 rows)
+
+SELECT date_trunc( 'week', timestamp '2004-02-29 15:44:17.71393' ) AS week_trunc;
+ week_trunc
+--------------------------
+ Mon Feb 23 00:00:00 2004
+(1 row)
+
+-- verify date_bin behaves the same as date_trunc for relevant intervals
+-- case 1: AD dates, origin < input
+SELECT
+ str,
+ interval,
+ date_trunc(str, ts) = date_bin(interval::interval, ts, timestamp '2001-01-01') AS equal
+FROM (
+ VALUES
+ ('week', '7 d'),
+ ('day', '1 d'),
+ ('hour', '1 h'),
+ ('minute', '1 m'),
+ ('second', '1 s'),
+ ('millisecond', '1 ms'),
+ ('microsecond', '1 us')
+) intervals (str, interval),
+(VALUES (timestamp '2020-02-29 15:44:17.71393')) ts (ts);
+ str | interval | equal
+-------------+----------+-------
+ week | 7 d | t
+ day | 1 d | t
+ hour | 1 h | t
+ minute | 1 m | t
+ second | 1 s | t
+ millisecond | 1 ms | t
+ microsecond | 1 us | t
+(7 rows)
+
+-- case 2: BC dates, origin < input
+SELECT
+ str,
+ interval,
+ date_trunc(str, ts) = date_bin(interval::interval, ts, timestamp '2000-01-01 BC') AS equal
+FROM (
+ VALUES
+ ('week', '7 d'),
+ ('day', '1 d'),
+ ('hour', '1 h'),
+ ('minute', '1 m'),
+ ('second', '1 s'),
+ ('millisecond', '1 ms'),
+ ('microsecond', '1 us')
+) intervals (str, interval),
+(VALUES (timestamp '0055-6-10 15:44:17.71393 BC')) ts (ts);
+ str | interval | equal
+-------------+----------+-------
+ week | 7 d | t
+ day | 1 d | t
+ hour | 1 h | t
+ minute | 1 m | t
+ second | 1 s | t
+ millisecond | 1 ms | t
+ microsecond | 1 us | t
+(7 rows)
+
+-- case 3: AD dates, origin > input
+SELECT
+ str,
+ interval,
+ date_trunc(str, ts) = date_bin(interval::interval, ts, timestamp '2020-03-02') AS equal
+FROM (
+ VALUES
+ ('week', '7 d'),
+ ('day', '1 d'),
+ ('hour', '1 h'),
+ ('minute', '1 m'),
+ ('second', '1 s'),
+ ('millisecond', '1 ms'),
+ ('microsecond', '1 us')
+) intervals (str, interval),
+(VALUES (timestamp '2020-02-29 15:44:17.71393')) ts (ts);
+ str | interval | equal
+-------------+----------+-------
+ week | 7 d | t
+ day | 1 d | t
+ hour | 1 h | t
+ minute | 1 m | t
+ second | 1 s | t
+ millisecond | 1 ms | t
+ microsecond | 1 us | t
+(7 rows)
+
+-- case 4: BC dates, origin > input
+SELECT
+ str,
+ interval,
+ date_trunc(str, ts) = date_bin(interval::interval, ts, timestamp '0055-06-17 BC') AS equal
+FROM (
+ VALUES
+ ('week', '7 d'),
+ ('day', '1 d'),
+ ('hour', '1 h'),
+ ('minute', '1 m'),
+ ('second', '1 s'),
+ ('millisecond', '1 ms'),
+ ('microsecond', '1 us')
+) intervals (str, interval),
+(VALUES (timestamp '0055-6-10 15:44:17.71393 BC')) ts (ts);
+ str | interval | equal
+-------------+----------+-------
+ week | 7 d | t
+ day | 1 d | t
+ hour | 1 h | t
+ minute | 1 m | t
+ second | 1 s | t
+ millisecond | 1 ms | t
+ microsecond | 1 us | t
+(7 rows)
+
+-- bin timestamps into arbitrary intervals
+SELECT
+ interval,
+ ts,
+ origin,
+ date_bin(interval::interval, ts, origin)
+FROM (
+ VALUES
+ ('15 days'),
+ ('2 hours'),
+ ('1 hour 30 minutes'),
+ ('15 minutes'),
+ ('10 seconds'),
+ ('100 milliseconds'),
+ ('250 microseconds')
+) intervals (interval),
+(VALUES (timestamp '2020-02-11 15:44:17.71393')) ts (ts),
+(VALUES (timestamp '2001-01-01')) origin (origin);
+ interval | ts | origin | date_bin
+-------------------+--------------------------------+--------------------------+--------------------------------
+ 15 days | Tue Feb 11 15:44:17.71393 2020 | Mon Jan 01 00:00:00 2001 | Thu Feb 06 00:00:00 2020
+ 2 hours | Tue Feb 11 15:44:17.71393 2020 | Mon Jan 01 00:00:00 2001 | Tue Feb 11 14:00:00 2020
+ 1 hour 30 minutes | Tue Feb 11 15:44:17.71393 2020 | Mon Jan 01 00:00:00 2001 | Tue Feb 11 15:00:00 2020
+ 15 minutes | Tue Feb 11 15:44:17.71393 2020 | Mon Jan 01 00:00:00 2001 | Tue Feb 11 15:30:00 2020
+ 10 seconds | Tue Feb 11 15:44:17.71393 2020 | Mon Jan 01 00:00:00 2001 | Tue Feb 11 15:44:10 2020
+ 100 milliseconds | Tue Feb 11 15:44:17.71393 2020 | Mon Jan 01 00:00:00 2001 | Tue Feb 11 15:44:17.7 2020
+ 250 microseconds | Tue Feb 11 15:44:17.71393 2020 | Mon Jan 01 00:00:00 2001 | Tue Feb 11 15:44:17.71375 2020
+(7 rows)
+
+-- shift bins using the origin parameter:
+SELECT date_bin('5 min'::interval, timestamp '2020-02-01 01:01:01', timestamp '2020-02-01 00:02:30');
+ date_bin
+--------------------------
+ Sat Feb 01 00:57:30 2020
+(1 row)
+
+-- disallow intervals with months or years
+SELECT date_bin('5 months'::interval, timestamp '2020-02-01 01:01:01', timestamp '2001-01-01');
+ERROR: timestamps cannot be binned into intervals containing months or years
+SELECT date_bin('5 years'::interval, timestamp '2020-02-01 01:01:01', timestamp '2001-01-01');
+ERROR: timestamps cannot be binned into intervals containing months or years
+-- disallow zero intervals
+SELECT date_bin('0 days'::interval, timestamp '1970-01-01 01:00:00' , timestamp '1970-01-01 00:00:00');
+ERROR: stride must be greater than zero
+-- disallow negative intervals
+SELECT date_bin('-2 days'::interval, timestamp '1970-01-01 01:00:00' , timestamp '1970-01-01 00:00:00');
+ERROR: stride must be greater than zero
+-- Test casting within a BETWEEN qualifier
+SELECT d1 - timestamp without time zone '1997-01-02' AS diff
+ FROM TIMESTAMP_TBL
+ WHERE d1 BETWEEN timestamp without time zone '1902-01-01'
+ AND timestamp without time zone '2038-01-01';
+ diff
+----------------------------------------
+ @ 9863 days ago
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 2 secs
+ @ 39 days 17 hours 32 mins 1.4 secs
+ @ 39 days 17 hours 32 mins 1.5 secs
+ @ 39 days 17 hours 32 mins 1.6 secs
+ @ 0
+ @ 3 hours 4 mins 5 secs
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 159 days 17 hours 32 mins 1 sec
+ @ 1724 days 18 hours 19 mins 20 secs
+ @ 1168 days 8 hours 14 mins 1 sec
+ @ 1168 days 13 hours 14 mins 2 secs
+ @ 1168 days 12 hours 14 mins 3 secs
+ @ 1168 days 3 hours 14 mins 4 secs
+ @ 1168 days 2 hours 14 mins 5 secs
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 159 days 18 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 40 days 17 hours 32 mins 1 sec
+ @ 41 days 17 hours 32 mins 1 sec
+ @ 42 days 17 hours 32 mins 1 sec
+ @ 43 days 17 hours 32 mins 1 sec
+ @ 44 days 17 hours 32 mins 1 sec
+ @ 45 days 17 hours 32 mins 1 sec
+ @ 45 days 17 hours 32 mins 1 sec
+ @ 308 days 6 hours 27 mins 59 secs ago
+ @ 307 days 6 hours 27 mins 59 secs ago
+ @ 306 days 6 hours 27 mins 59 secs ago
+ @ 2 days 6 hours 27 mins 59 secs ago
+ @ 1 day 6 hours 27 mins 59 secs ago
+ @ 6 hours 27 mins 59 secs ago
+ @ 57 days 17 hours 32 mins 1 sec
+ @ 58 days 17 hours 32 mins 1 sec
+ @ 362 days 17 hours 32 mins 1 sec
+ @ 363 days 17 hours 32 mins 1 sec
+ @ 1093 days 17 hours 32 mins 1 sec
+ @ 1094 days 17 hours 32 mins 1 sec
+ @ 1459 days 17 hours 32 mins 1 sec
+ @ 1460 days 17 hours 32 mins 1 sec
+(55 rows)
+
+-- DATE_PART (timestamp_part)
+SELECT d1 as "timestamp",
+ date_part( 'year', d1) AS year, date_part( 'month', d1) AS month,
+ date_part( 'day', d1) AS day, date_part( 'hour', d1) AS hour,
+ date_part( 'minute', d1) AS minute, date_part( 'second', d1) AS second
+ FROM TIMESTAMP_TBL;
+ timestamp | year | month | day | hour | minute | second
+-----------------------------+-----------+-------+-----+------+--------+--------
+ -infinity | -Infinity | | | | |
+ infinity | Infinity | | | | |
+ Thu Jan 01 00:00:00 1970 | 1970 | 1 | 1 | 0 | 0 | 0
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:02 1997 | 1997 | 2 | 10 | 17 | 32 | 2
+ Mon Feb 10 17:32:01.4 1997 | 1997 | 2 | 10 | 17 | 32 | 1.4
+ Mon Feb 10 17:32:01.5 1997 | 1997 | 2 | 10 | 17 | 32 | 1.5
+ Mon Feb 10 17:32:01.6 1997 | 1997 | 2 | 10 | 17 | 32 | 1.6
+ Thu Jan 02 00:00:00 1997 | 1997 | 1 | 2 | 0 | 0 | 0
+ Thu Jan 02 03:04:05 1997 | 1997 | 1 | 2 | 3 | 4 | 5
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Tue Jun 10 17:32:01 1997 | 1997 | 6 | 10 | 17 | 32 | 1
+ Sat Sep 22 18:19:20 2001 | 2001 | 9 | 22 | 18 | 19 | 20
+ Wed Mar 15 08:14:01 2000 | 2000 | 3 | 15 | 8 | 14 | 1
+ Wed Mar 15 13:14:02 2000 | 2000 | 3 | 15 | 13 | 14 | 2
+ Wed Mar 15 12:14:03 2000 | 2000 | 3 | 15 | 12 | 14 | 3
+ Wed Mar 15 03:14:04 2000 | 2000 | 3 | 15 | 3 | 14 | 4
+ Wed Mar 15 02:14:05 2000 | 2000 | 3 | 15 | 2 | 14 | 5
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:00 1997 | 1997 | 2 | 10 | 17 | 32 | 0
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Tue Jun 10 18:32:01 1997 | 1997 | 6 | 10 | 18 | 32 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1
+ Tue Feb 11 17:32:01 1997 | 1997 | 2 | 11 | 17 | 32 | 1
+ Wed Feb 12 17:32:01 1997 | 1997 | 2 | 12 | 17 | 32 | 1
+ Thu Feb 13 17:32:01 1997 | 1997 | 2 | 13 | 17 | 32 | 1
+ Fri Feb 14 17:32:01 1997 | 1997 | 2 | 14 | 17 | 32 | 1
+ Sat Feb 15 17:32:01 1997 | 1997 | 2 | 15 | 17 | 32 | 1
+ Sun Feb 16 17:32:01 1997 | 1997 | 2 | 16 | 17 | 32 | 1
+ Tue Feb 16 17:32:01 0097 BC | -97 | 2 | 16 | 17 | 32 | 1
+ Sat Feb 16 17:32:01 0097 | 97 | 2 | 16 | 17 | 32 | 1
+ Thu Feb 16 17:32:01 0597 | 597 | 2 | 16 | 17 | 32 | 1
+ Tue Feb 16 17:32:01 1097 | 1097 | 2 | 16 | 17 | 32 | 1
+ Sat Feb 16 17:32:01 1697 | 1697 | 2 | 16 | 17 | 32 | 1
+ Thu Feb 16 17:32:01 1797 | 1797 | 2 | 16 | 17 | 32 | 1
+ Tue Feb 16 17:32:01 1897 | 1897 | 2 | 16 | 17 | 32 | 1
+ Sun Feb 16 17:32:01 1997 | 1997 | 2 | 16 | 17 | 32 | 1
+ Sat Feb 16 17:32:01 2097 | 2097 | 2 | 16 | 17 | 32 | 1
+ Wed Feb 28 17:32:01 1996 | 1996 | 2 | 28 | 17 | 32 | 1
+ Thu Feb 29 17:32:01 1996 | 1996 | 2 | 29 | 17 | 32 | 1
+ Fri Mar 01 17:32:01 1996 | 1996 | 3 | 1 | 17 | 32 | 1
+ Mon Dec 30 17:32:01 1996 | 1996 | 12 | 30 | 17 | 32 | 1
+ Tue Dec 31 17:32:01 1996 | 1996 | 12 | 31 | 17 | 32 | 1
+ Wed Jan 01 17:32:01 1997 | 1997 | 1 | 1 | 17 | 32 | 1
+ Fri Feb 28 17:32:01 1997 | 1997 | 2 | 28 | 17 | 32 | 1
+ Sat Mar 01 17:32:01 1997 | 1997 | 3 | 1 | 17 | 32 | 1
+ Tue Dec 30 17:32:01 1997 | 1997 | 12 | 30 | 17 | 32 | 1
+ Wed Dec 31 17:32:01 1997 | 1997 | 12 | 31 | 17 | 32 | 1
+ Fri Dec 31 17:32:01 1999 | 1999 | 12 | 31 | 17 | 32 | 1
+ Sat Jan 01 17:32:01 2000 | 2000 | 1 | 1 | 17 | 32 | 1
+ Sun Dec 31 17:32:01 2000 | 2000 | 12 | 31 | 17 | 32 | 1
+ Mon Jan 01 17:32:01 2001 | 2001 | 1 | 1 | 17 | 32 | 1
+(65 rows)
+
+SELECT d1 as "timestamp",
+ date_part( 'quarter', d1) AS quarter, date_part( 'msec', d1) AS msec,
+ date_part( 'usec', d1) AS usec
+ FROM TIMESTAMP_TBL;
+ timestamp | quarter | msec | usec
+-----------------------------+---------+-------+----------
+ -infinity | | |
+ infinity | | |
+ Thu Jan 01 00:00:00 1970 | 1 | 0 | 0
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:02 1997 | 1 | 2000 | 2000000
+ Mon Feb 10 17:32:01.4 1997 | 1 | 1400 | 1400000
+ Mon Feb 10 17:32:01.5 1997 | 1 | 1500 | 1500000
+ Mon Feb 10 17:32:01.6 1997 | 1 | 1600 | 1600000
+ Thu Jan 02 00:00:00 1997 | 1 | 0 | 0
+ Thu Jan 02 03:04:05 1997 | 1 | 5000 | 5000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Tue Jun 10 17:32:01 1997 | 2 | 1000 | 1000000
+ Sat Sep 22 18:19:20 2001 | 3 | 20000 | 20000000
+ Wed Mar 15 08:14:01 2000 | 1 | 1000 | 1000000
+ Wed Mar 15 13:14:02 2000 | 1 | 2000 | 2000000
+ Wed Mar 15 12:14:03 2000 | 1 | 3000 | 3000000
+ Wed Mar 15 03:14:04 2000 | 1 | 4000 | 4000000
+ Wed Mar 15 02:14:05 2000 | 1 | 5000 | 5000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:00 1997 | 1 | 0 | 0
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Tue Jun 10 18:32:01 1997 | 2 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000
+ Tue Feb 11 17:32:01 1997 | 1 | 1000 | 1000000
+ Wed Feb 12 17:32:01 1997 | 1 | 1000 | 1000000
+ Thu Feb 13 17:32:01 1997 | 1 | 1000 | 1000000
+ Fri Feb 14 17:32:01 1997 | 1 | 1000 | 1000000
+ Sat Feb 15 17:32:01 1997 | 1 | 1000 | 1000000
+ Sun Feb 16 17:32:01 1997 | 1 | 1000 | 1000000
+ Tue Feb 16 17:32:01 0097 BC | 1 | 1000 | 1000000
+ Sat Feb 16 17:32:01 0097 | 1 | 1000 | 1000000
+ Thu Feb 16 17:32:01 0597 | 1 | 1000 | 1000000
+ Tue Feb 16 17:32:01 1097 | 1 | 1000 | 1000000
+ Sat Feb 16 17:32:01 1697 | 1 | 1000 | 1000000
+ Thu Feb 16 17:32:01 1797 | 1 | 1000 | 1000000
+ Tue Feb 16 17:32:01 1897 | 1 | 1000 | 1000000
+ Sun Feb 16 17:32:01 1997 | 1 | 1000 | 1000000
+ Sat Feb 16 17:32:01 2097 | 1 | 1000 | 1000000
+ Wed Feb 28 17:32:01 1996 | 1 | 1000 | 1000000
+ Thu Feb 29 17:32:01 1996 | 1 | 1000 | 1000000
+ Fri Mar 01 17:32:01 1996 | 1 | 1000 | 1000000
+ Mon Dec 30 17:32:01 1996 | 4 | 1000 | 1000000
+ Tue Dec 31 17:32:01 1996 | 4 | 1000 | 1000000
+ Wed Jan 01 17:32:01 1997 | 1 | 1000 | 1000000
+ Fri Feb 28 17:32:01 1997 | 1 | 1000 | 1000000
+ Sat Mar 01 17:32:01 1997 | 1 | 1000 | 1000000
+ Tue Dec 30 17:32:01 1997 | 4 | 1000 | 1000000
+ Wed Dec 31 17:32:01 1997 | 4 | 1000 | 1000000
+ Fri Dec 31 17:32:01 1999 | 4 | 1000 | 1000000
+ Sat Jan 01 17:32:01 2000 | 1 | 1000 | 1000000
+ Sun Dec 31 17:32:01 2000 | 4 | 1000 | 1000000
+ Mon Jan 01 17:32:01 2001 | 1 | 1000 | 1000000
+(65 rows)
+
+SELECT d1 as "timestamp",
+ date_part( 'isoyear', d1) AS isoyear, date_part( 'week', d1) AS week,
+ date_part( 'isodow', d1) AS isodow, date_part( 'dow', d1) AS dow,
+ date_part( 'doy', d1) AS doy
+ FROM TIMESTAMP_TBL;
+ timestamp | isoyear | week | isodow | dow | doy
+-----------------------------+-----------+------+--------+-----+-----
+ -infinity | -Infinity | | | |
+ infinity | Infinity | | | |
+ Thu Jan 01 00:00:00 1970 | 1970 | 1 | 4 | 4 | 1
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:02 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01.4 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01.5 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01.6 1997 | 1997 | 7 | 1 | 1 | 41
+ Thu Jan 02 00:00:00 1997 | 1997 | 1 | 4 | 4 | 2
+ Thu Jan 02 03:04:05 1997 | 1997 | 1 | 4 | 4 | 2
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Tue Jun 10 17:32:01 1997 | 1997 | 24 | 2 | 2 | 161
+ Sat Sep 22 18:19:20 2001 | 2001 | 38 | 6 | 6 | 265
+ Wed Mar 15 08:14:01 2000 | 2000 | 11 | 3 | 3 | 75
+ Wed Mar 15 13:14:02 2000 | 2000 | 11 | 3 | 3 | 75
+ Wed Mar 15 12:14:03 2000 | 2000 | 11 | 3 | 3 | 75
+ Wed Mar 15 03:14:04 2000 | 2000 | 11 | 3 | 3 | 75
+ Wed Mar 15 02:14:05 2000 | 2000 | 11 | 3 | 3 | 75
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:00 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Tue Jun 10 18:32:01 1997 | 1997 | 24 | 2 | 2 | 161
+ Mon Feb 10 17:32:01 1997 | 1997 | 7 | 1 | 1 | 41
+ Tue Feb 11 17:32:01 1997 | 1997 | 7 | 2 | 2 | 42
+ Wed Feb 12 17:32:01 1997 | 1997 | 7 | 3 | 3 | 43
+ Thu Feb 13 17:32:01 1997 | 1997 | 7 | 4 | 4 | 44
+ Fri Feb 14 17:32:01 1997 | 1997 | 7 | 5 | 5 | 45
+ Sat Feb 15 17:32:01 1997 | 1997 | 7 | 6 | 6 | 46
+ Sun Feb 16 17:32:01 1997 | 1997 | 7 | 7 | 0 | 47
+ Tue Feb 16 17:32:01 0097 BC | -97 | 7 | 2 | 2 | 47
+ Sat Feb 16 17:32:01 0097 | 97 | 7 | 6 | 6 | 47
+ Thu Feb 16 17:32:01 0597 | 597 | 7 | 4 | 4 | 47
+ Tue Feb 16 17:32:01 1097 | 1097 | 7 | 2 | 2 | 47
+ Sat Feb 16 17:32:01 1697 | 1697 | 7 | 6 | 6 | 47
+ Thu Feb 16 17:32:01 1797 | 1797 | 7 | 4 | 4 | 47
+ Tue Feb 16 17:32:01 1897 | 1897 | 7 | 2 | 2 | 47
+ Sun Feb 16 17:32:01 1997 | 1997 | 7 | 7 | 0 | 47
+ Sat Feb 16 17:32:01 2097 | 2097 | 7 | 6 | 6 | 47
+ Wed Feb 28 17:32:01 1996 | 1996 | 9 | 3 | 3 | 59
+ Thu Feb 29 17:32:01 1996 | 1996 | 9 | 4 | 4 | 60
+ Fri Mar 01 17:32:01 1996 | 1996 | 9 | 5 | 5 | 61
+ Mon Dec 30 17:32:01 1996 | 1997 | 1 | 1 | 1 | 365
+ Tue Dec 31 17:32:01 1996 | 1997 | 1 | 2 | 2 | 366
+ Wed Jan 01 17:32:01 1997 | 1997 | 1 | 3 | 3 | 1
+ Fri Feb 28 17:32:01 1997 | 1997 | 9 | 5 | 5 | 59
+ Sat Mar 01 17:32:01 1997 | 1997 | 9 | 6 | 6 | 60
+ Tue Dec 30 17:32:01 1997 | 1998 | 1 | 2 | 2 | 364
+ Wed Dec 31 17:32:01 1997 | 1998 | 1 | 3 | 3 | 365
+ Fri Dec 31 17:32:01 1999 | 1999 | 52 | 5 | 5 | 365
+ Sat Jan 01 17:32:01 2000 | 1999 | 52 | 6 | 6 | 1
+ Sun Dec 31 17:32:01 2000 | 2000 | 52 | 7 | 0 | 366
+ Mon Jan 01 17:32:01 2001 | 2001 | 1 | 1 | 1 | 1
+(65 rows)
+
+SELECT d1 as "timestamp",
+ date_part( 'decade', d1) AS decade,
+ date_part( 'century', d1) AS century,
+ date_part( 'millennium', d1) AS millennium,
+ round(date_part( 'julian', d1)) AS julian,
+ date_part( 'epoch', d1) AS epoch
+ FROM TIMESTAMP_TBL;
+ timestamp | decade | century | millennium | julian | epoch
+-----------------------------+-----------+-----------+------------+-----------+--------------
+ -infinity | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+ infinity | Infinity | Infinity | Infinity | Infinity | Infinity
+ Thu Jan 01 00:00:00 1970 | 197 | 20 | 2 | 2440588 | 0
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:02 1997 | 199 | 20 | 2 | 2450491 | 855595922
+ Mon Feb 10 17:32:01.4 1997 | 199 | 20 | 2 | 2450491 | 855595921.4
+ Mon Feb 10 17:32:01.5 1997 | 199 | 20 | 2 | 2450491 | 855595921.5
+ Mon Feb 10 17:32:01.6 1997 | 199 | 20 | 2 | 2450491 | 855595921.6
+ Thu Jan 02 00:00:00 1997 | 199 | 20 | 2 | 2450451 | 852163200
+ Thu Jan 02 03:04:05 1997 | 199 | 20 | 2 | 2450451 | 852174245
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Tue Jun 10 17:32:01 1997 | 199 | 20 | 2 | 2450611 | 865963921
+ Sat Sep 22 18:19:20 2001 | 200 | 21 | 3 | 2452176 | 1001182760
+ Wed Mar 15 08:14:01 2000 | 200 | 20 | 2 | 2451619 | 953108041
+ Wed Mar 15 13:14:02 2000 | 200 | 20 | 2 | 2451620 | 953126042
+ Wed Mar 15 12:14:03 2000 | 200 | 20 | 2 | 2451620 | 953122443
+ Wed Mar 15 03:14:04 2000 | 200 | 20 | 2 | 2451619 | 953090044
+ Wed Mar 15 02:14:05 2000 | 200 | 20 | 2 | 2451619 | 953086445
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:00 1997 | 199 | 20 | 2 | 2450491 | 855595920
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Tue Jun 10 18:32:01 1997 | 199 | 20 | 2 | 2450611 | 865967521
+ Mon Feb 10 17:32:01 1997 | 199 | 20 | 2 | 2450491 | 855595921
+ Tue Feb 11 17:32:01 1997 | 199 | 20 | 2 | 2450492 | 855682321
+ Wed Feb 12 17:32:01 1997 | 199 | 20 | 2 | 2450493 | 855768721
+ Thu Feb 13 17:32:01 1997 | 199 | 20 | 2 | 2450494 | 855855121
+ Fri Feb 14 17:32:01 1997 | 199 | 20 | 2 | 2450495 | 855941521
+ Sat Feb 15 17:32:01 1997 | 199 | 20 | 2 | 2450496 | 856027921
+ Sun Feb 16 17:32:01 1997 | 199 | 20 | 2 | 2450497 | 856114321
+ Tue Feb 16 17:32:01 0097 BC | -10 | -1 | -1 | 1686043 | -65192711279
+ Sat Feb 16 17:32:01 0097 | 9 | 1 | 1 | 1756537 | -59102029679
+ Thu Feb 16 17:32:01 0597 | 59 | 6 | 1 | 1939158 | -43323575279
+ Tue Feb 16 17:32:01 1097 | 109 | 11 | 2 | 2121779 | -27545120879
+ Sat Feb 16 17:32:01 1697 | 169 | 17 | 2 | 2340925 | -8610906479
+ Thu Feb 16 17:32:01 1797 | 179 | 18 | 2 | 2377449 | -5455232879
+ Tue Feb 16 17:32:01 1897 | 189 | 19 | 2 | 2413973 | -2299559279
+ Sun Feb 16 17:32:01 1997 | 199 | 20 | 2 | 2450497 | 856114321
+ Sat Feb 16 17:32:01 2097 | 209 | 21 | 3 | 2487022 | 4011874321
+ Wed Feb 28 17:32:01 1996 | 199 | 20 | 2 | 2450143 | 825528721
+ Thu Feb 29 17:32:01 1996 | 199 | 20 | 2 | 2450144 | 825615121
+ Fri Mar 01 17:32:01 1996 | 199 | 20 | 2 | 2450145 | 825701521
+ Mon Dec 30 17:32:01 1996 | 199 | 20 | 2 | 2450449 | 851967121
+ Tue Dec 31 17:32:01 1996 | 199 | 20 | 2 | 2450450 | 852053521
+ Wed Jan 01 17:32:01 1997 | 199 | 20 | 2 | 2450451 | 852139921
+ Fri Feb 28 17:32:01 1997 | 199 | 20 | 2 | 2450509 | 857151121
+ Sat Mar 01 17:32:01 1997 | 199 | 20 | 2 | 2450510 | 857237521
+ Tue Dec 30 17:32:01 1997 | 199 | 20 | 2 | 2450814 | 883503121
+ Wed Dec 31 17:32:01 1997 | 199 | 20 | 2 | 2450815 | 883589521
+ Fri Dec 31 17:32:01 1999 | 199 | 20 | 2 | 2451545 | 946661521
+ Sat Jan 01 17:32:01 2000 | 200 | 20 | 2 | 2451546 | 946747921
+ Sun Dec 31 17:32:01 2000 | 200 | 20 | 2 | 2451911 | 978283921
+ Mon Jan 01 17:32:01 2001 | 200 | 21 | 3 | 2451912 | 978370321
+(65 rows)
+
+-- extract implementation is mostly the same as date_part, so only
+-- test a few cases for additional coverage.
+SELECT d1 as "timestamp",
+ extract(microseconds from d1) AS microseconds,
+ extract(milliseconds from d1) AS milliseconds,
+ extract(seconds from d1) AS seconds,
+ round(extract(julian from d1)) AS julian,
+ extract(epoch from d1) AS epoch
+ FROM TIMESTAMP_TBL;
+ timestamp | microseconds | milliseconds | seconds | julian | epoch
+-----------------------------+--------------+--------------+-----------+-----------+---------------------
+ -infinity | | | | -Infinity | -Infinity
+ infinity | | | | Infinity | Infinity
+ Thu Jan 01 00:00:00 1970 | 0 | 0.000 | 0.000000 | 2440588 | 0.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:02 1997 | 2000000 | 2000.000 | 2.000000 | 2450491 | 855595922.000000
+ Mon Feb 10 17:32:01.4 1997 | 1400000 | 1400.000 | 1.400000 | 2450491 | 855595921.400000
+ Mon Feb 10 17:32:01.5 1997 | 1500000 | 1500.000 | 1.500000 | 2450491 | 855595921.500000
+ Mon Feb 10 17:32:01.6 1997 | 1600000 | 1600.000 | 1.600000 | 2450491 | 855595921.600000
+ Thu Jan 02 00:00:00 1997 | 0 | 0.000 | 0.000000 | 2450451 | 852163200.000000
+ Thu Jan 02 03:04:05 1997 | 5000000 | 5000.000 | 5.000000 | 2450451 | 852174245.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Tue Jun 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450611 | 865963921.000000
+ Sat Sep 22 18:19:20 2001 | 20000000 | 20000.000 | 20.000000 | 2452176 | 1001182760.000000
+ Wed Mar 15 08:14:01 2000 | 1000000 | 1000.000 | 1.000000 | 2451619 | 953108041.000000
+ Wed Mar 15 13:14:02 2000 | 2000000 | 2000.000 | 2.000000 | 2451620 | 953126042.000000
+ Wed Mar 15 12:14:03 2000 | 3000000 | 3000.000 | 3.000000 | 2451620 | 953122443.000000
+ Wed Mar 15 03:14:04 2000 | 4000000 | 4000.000 | 4.000000 | 2451619 | 953090044.000000
+ Wed Mar 15 02:14:05 2000 | 5000000 | 5000.000 | 5.000000 | 2451619 | 953086445.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:00 1997 | 0 | 0.000 | 0.000000 | 2450491 | 855595920.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Tue Jun 10 18:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450611 | 865967521.000000
+ Mon Feb 10 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450491 | 855595921.000000
+ Tue Feb 11 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450492 | 855682321.000000
+ Wed Feb 12 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450493 | 855768721.000000
+ Thu Feb 13 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450494 | 855855121.000000
+ Fri Feb 14 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450495 | 855941521.000000
+ Sat Feb 15 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450496 | 856027921.000000
+ Sun Feb 16 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450497 | 856114321.000000
+ Tue Feb 16 17:32:01 0097 BC | 1000000 | 1000.000 | 1.000000 | 1686043 | -65192711279.000000
+ Sat Feb 16 17:32:01 0097 | 1000000 | 1000.000 | 1.000000 | 1756537 | -59102029679.000000
+ Thu Feb 16 17:32:01 0597 | 1000000 | 1000.000 | 1.000000 | 1939158 | -43323575279.000000
+ Tue Feb 16 17:32:01 1097 | 1000000 | 1000.000 | 1.000000 | 2121779 | -27545120879.000000
+ Sat Feb 16 17:32:01 1697 | 1000000 | 1000.000 | 1.000000 | 2340925 | -8610906479.000000
+ Thu Feb 16 17:32:01 1797 | 1000000 | 1000.000 | 1.000000 | 2377449 | -5455232879.000000
+ Tue Feb 16 17:32:01 1897 | 1000000 | 1000.000 | 1.000000 | 2413973 | -2299559279.000000
+ Sun Feb 16 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450497 | 856114321.000000
+ Sat Feb 16 17:32:01 2097 | 1000000 | 1000.000 | 1.000000 | 2487022 | 4011874321.000000
+ Wed Feb 28 17:32:01 1996 | 1000000 | 1000.000 | 1.000000 | 2450143 | 825528721.000000
+ Thu Feb 29 17:32:01 1996 | 1000000 | 1000.000 | 1.000000 | 2450144 | 825615121.000000
+ Fri Mar 01 17:32:01 1996 | 1000000 | 1000.000 | 1.000000 | 2450145 | 825701521.000000
+ Mon Dec 30 17:32:01 1996 | 1000000 | 1000.000 | 1.000000 | 2450449 | 851967121.000000
+ Tue Dec 31 17:32:01 1996 | 1000000 | 1000.000 | 1.000000 | 2450450 | 852053521.000000
+ Wed Jan 01 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450451 | 852139921.000000
+ Fri Feb 28 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450509 | 857151121.000000
+ Sat Mar 01 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450510 | 857237521.000000
+ Tue Dec 30 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450814 | 883503121.000000
+ Wed Dec 31 17:32:01 1997 | 1000000 | 1000.000 | 1.000000 | 2450815 | 883589521.000000
+ Fri Dec 31 17:32:01 1999 | 1000000 | 1000.000 | 1.000000 | 2451545 | 946661521.000000
+ Sat Jan 01 17:32:01 2000 | 1000000 | 1000.000 | 1.000000 | 2451546 | 946747921.000000
+ Sun Dec 31 17:32:01 2000 | 1000000 | 1000.000 | 1.000000 | 2451911 | 978283921.000000
+ Mon Jan 01 17:32:01 2001 | 1000000 | 1000.000 | 1.000000 | 2451912 | 978370321.000000
+(65 rows)
+
+-- value near upper bound uses special case in code
+SELECT date_part('epoch', '294270-01-01 00:00:00'::timestamp);
+ date_part
+---------------
+ 9224097091200
+(1 row)
+
+SELECT extract(epoch from '294270-01-01 00:00:00'::timestamp);
+ extract
+----------------------
+ 9224097091200.000000
+(1 row)
+
+-- another internal overflow test case
+SELECT extract(epoch from '5000-01-01 00:00:00'::timestamp);
+ extract
+--------------------
+ 95617584000.000000
+(1 row)
+
+-- TO_CHAR()
+SELECT to_char(d1, 'DAY Day day DY Dy dy MONTH Month month RM MON Mon mon')
+ FROM TIMESTAMP_TBL;
+ to_char
+------------------------------------------------------------------------------------------
+
+
+ THURSDAY Thursday thursday THU Thu thu JANUARY January january I JAN Jan jan
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ THURSDAY Thursday thursday THU Thu thu JANUARY January january I JAN Jan jan
+ THURSDAY Thursday thursday THU Thu thu JANUARY January january I JAN Jan jan
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ TUESDAY Tuesday tuesday TUE Tue tue JUNE June june VI JUN Jun jun
+ SATURDAY Saturday saturday SAT Sat sat SEPTEMBER September september IX SEP Sep sep
+ WEDNESDAY Wednesday wednesday WED Wed wed MARCH March march III MAR Mar mar
+ WEDNESDAY Wednesday wednesday WED Wed wed MARCH March march III MAR Mar mar
+ WEDNESDAY Wednesday wednesday WED Wed wed MARCH March march III MAR Mar mar
+ WEDNESDAY Wednesday wednesday WED Wed wed MARCH March march III MAR Mar mar
+ WEDNESDAY Wednesday wednesday WED Wed wed MARCH March march III MAR Mar mar
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ TUESDAY Tuesday tuesday TUE Tue tue JUNE June june VI JUN Jun jun
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ TUESDAY Tuesday tuesday TUE Tue tue FEBRUARY February february II FEB Feb feb
+ WEDNESDAY Wednesday wednesday WED Wed wed FEBRUARY February february II FEB Feb feb
+ THURSDAY Thursday thursday THU Thu thu FEBRUARY February february II FEB Feb feb
+ FRIDAY Friday friday FRI Fri fri FEBRUARY February february II FEB Feb feb
+ SATURDAY Saturday saturday SAT Sat sat FEBRUARY February february II FEB Feb feb
+ SUNDAY Sunday sunday SUN Sun sun FEBRUARY February february II FEB Feb feb
+ TUESDAY Tuesday tuesday TUE Tue tue FEBRUARY February february II FEB Feb feb
+ SATURDAY Saturday saturday SAT Sat sat FEBRUARY February february II FEB Feb feb
+ THURSDAY Thursday thursday THU Thu thu FEBRUARY February february II FEB Feb feb
+ TUESDAY Tuesday tuesday TUE Tue tue FEBRUARY February february II FEB Feb feb
+ SATURDAY Saturday saturday SAT Sat sat FEBRUARY February february II FEB Feb feb
+ THURSDAY Thursday thursday THU Thu thu FEBRUARY February february II FEB Feb feb
+ TUESDAY Tuesday tuesday TUE Tue tue FEBRUARY February february II FEB Feb feb
+ SUNDAY Sunday sunday SUN Sun sun FEBRUARY February february II FEB Feb feb
+ SATURDAY Saturday saturday SAT Sat sat FEBRUARY February february II FEB Feb feb
+ WEDNESDAY Wednesday wednesday WED Wed wed FEBRUARY February february II FEB Feb feb
+ THURSDAY Thursday thursday THU Thu thu FEBRUARY February february II FEB Feb feb
+ FRIDAY Friday friday FRI Fri fri MARCH March march III MAR Mar mar
+ MONDAY Monday monday MON Mon mon DECEMBER December december XII DEC Dec dec
+ TUESDAY Tuesday tuesday TUE Tue tue DECEMBER December december XII DEC Dec dec
+ WEDNESDAY Wednesday wednesday WED Wed wed JANUARY January january I JAN Jan jan
+ FRIDAY Friday friday FRI Fri fri FEBRUARY February february II FEB Feb feb
+ SATURDAY Saturday saturday SAT Sat sat MARCH March march III MAR Mar mar
+ TUESDAY Tuesday tuesday TUE Tue tue DECEMBER December december XII DEC Dec dec
+ WEDNESDAY Wednesday wednesday WED Wed wed DECEMBER December december XII DEC Dec dec
+ FRIDAY Friday friday FRI Fri fri DECEMBER December december XII DEC Dec dec
+ SATURDAY Saturday saturday SAT Sat sat JANUARY January january I JAN Jan jan
+ SUNDAY Sunday sunday SUN Sun sun DECEMBER December december XII DEC Dec dec
+ MONDAY Monday monday MON Mon mon JANUARY January january I JAN Jan jan
+(65 rows)
+
+SELECT to_char(d1, 'FMDAY FMDay FMday FMMONTH FMMonth FMmonth FMRM')
+ FROM TIMESTAMP_TBL;
+ to_char
+--------------------------------------------------------------
+
+
+ THURSDAY Thursday thursday JANUARY January january I
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ THURSDAY Thursday thursday JANUARY January january I
+ THURSDAY Thursday thursday JANUARY January january I
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ TUESDAY Tuesday tuesday JUNE June june VI
+ SATURDAY Saturday saturday SEPTEMBER September september IX
+ WEDNESDAY Wednesday wednesday MARCH March march III
+ WEDNESDAY Wednesday wednesday MARCH March march III
+ WEDNESDAY Wednesday wednesday MARCH March march III
+ WEDNESDAY Wednesday wednesday MARCH March march III
+ WEDNESDAY Wednesday wednesday MARCH March march III
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ TUESDAY Tuesday tuesday JUNE June june VI
+ MONDAY Monday monday FEBRUARY February february II
+ TUESDAY Tuesday tuesday FEBRUARY February february II
+ WEDNESDAY Wednesday wednesday FEBRUARY February february II
+ THURSDAY Thursday thursday FEBRUARY February february II
+ FRIDAY Friday friday FEBRUARY February february II
+ SATURDAY Saturday saturday FEBRUARY February february II
+ SUNDAY Sunday sunday FEBRUARY February february II
+ TUESDAY Tuesday tuesday FEBRUARY February february II
+ SATURDAY Saturday saturday FEBRUARY February february II
+ THURSDAY Thursday thursday FEBRUARY February february II
+ TUESDAY Tuesday tuesday FEBRUARY February february II
+ SATURDAY Saturday saturday FEBRUARY February february II
+ THURSDAY Thursday thursday FEBRUARY February february II
+ TUESDAY Tuesday tuesday FEBRUARY February february II
+ SUNDAY Sunday sunday FEBRUARY February february II
+ SATURDAY Saturday saturday FEBRUARY February february II
+ WEDNESDAY Wednesday wednesday FEBRUARY February february II
+ THURSDAY Thursday thursday FEBRUARY February february II
+ FRIDAY Friday friday MARCH March march III
+ MONDAY Monday monday DECEMBER December december XII
+ TUESDAY Tuesday tuesday DECEMBER December december XII
+ WEDNESDAY Wednesday wednesday JANUARY January january I
+ FRIDAY Friday friday FEBRUARY February february II
+ SATURDAY Saturday saturday MARCH March march III
+ TUESDAY Tuesday tuesday DECEMBER December december XII
+ WEDNESDAY Wednesday wednesday DECEMBER December december XII
+ FRIDAY Friday friday DECEMBER December december XII
+ SATURDAY Saturday saturday JANUARY January january I
+ SUNDAY Sunday sunday DECEMBER December december XII
+ MONDAY Monday monday JANUARY January january I
+(65 rows)
+
+SELECT to_char(d1, 'Y,YYY YYYY YYY YY Y CC Q MM WW DDD DD D J')
+ FROM TIMESTAMP_TBL;
+ to_char
+--------------------------------------------------
+
+
+ 1,970 1970 970 70 0 20 1 01 01 001 01 5 2440588
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 01 01 002 02 5 2450451
+ 1,997 1997 997 97 7 20 1 01 01 002 02 5 2450451
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 2 06 23 161 10 3 2450610
+ 2,001 2001 001 01 1 21 3 09 38 265 22 7 2452175
+ 2,000 2000 000 00 0 20 1 03 11 075 15 4 2451619
+ 2,000 2000 000 00 0 20 1 03 11 075 15 4 2451619
+ 2,000 2000 000 00 0 20 1 03 11 075 15 4 2451619
+ 2,000 2000 000 00 0 20 1 03 11 075 15 4 2451619
+ 2,000 2000 000 00 0 20 1 03 11 075 15 4 2451619
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 2 06 23 161 10 3 2450610
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 042 11 3 2450491
+ 1,997 1997 997 97 7 20 1 02 07 043 12 4 2450492
+ 1,997 1997 997 97 7 20 1 02 07 044 13 5 2450493
+ 1,997 1997 997 97 7 20 1 02 07 045 14 6 2450494
+ 1,997 1997 997 97 7 20 1 02 07 046 15 7 2450495
+ 1,997 1997 997 97 7 20 1 02 07 047 16 1 2450496
+ 0,097 0097 097 97 7 -01 1 02 07 047 16 3 1686042
+ 0,097 0097 097 97 7 01 1 02 07 047 16 7 1756536
+ 0,597 0597 597 97 7 06 1 02 07 047 16 5 1939157
+ 1,097 1097 097 97 7 11 1 02 07 047 16 3 2121778
+ 1,697 1697 697 97 7 17 1 02 07 047 16 7 2340924
+ 1,797 1797 797 97 7 18 1 02 07 047 16 5 2377448
+ 1,897 1897 897 97 7 19 1 02 07 047 16 3 2413972
+ 1,997 1997 997 97 7 20 1 02 07 047 16 1 2450496
+ 2,097 2097 097 97 7 21 1 02 07 047 16 7 2487021
+ 1,996 1996 996 96 6 20 1 02 09 059 28 4 2450142
+ 1,996 1996 996 96 6 20 1 02 09 060 29 5 2450143
+ 1,996 1996 996 96 6 20 1 03 09 061 01 6 2450144
+ 1,996 1996 996 96 6 20 4 12 53 365 30 2 2450448
+ 1,996 1996 996 96 6 20 4 12 53 366 31 3 2450449
+ 1,997 1997 997 97 7 20 1 01 01 001 01 4 2450450
+ 1,997 1997 997 97 7 20 1 02 09 059 28 6 2450508
+ 1,997 1997 997 97 7 20 1 03 09 060 01 7 2450509
+ 1,997 1997 997 97 7 20 4 12 52 364 30 3 2450813
+ 1,997 1997 997 97 7 20 4 12 53 365 31 4 2450814
+ 1,999 1999 999 99 9 20 4 12 53 365 31 6 2451544
+ 2,000 2000 000 00 0 20 1 01 01 001 01 7 2451545
+ 2,000 2000 000 00 0 20 4 12 53 366 31 1 2451910
+ 2,001 2001 001 01 1 21 1 01 01 001 01 2 2451911
+(65 rows)
+
+SELECT to_char(d1, 'FMY,YYY FMYYYY FMYYY FMYY FMY FMCC FMQ FMMM FMWW FMDDD FMDD FMD FMJ')
+ FROM TIMESTAMP_TBL;
+ to_char
+-------------------------------------------------
+
+
+ 1,970 1970 970 70 0 20 1 1 1 1 1 5 2440588
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 1 1 2 2 5 2450451
+ 1,997 1997 997 97 7 20 1 1 1 2 2 5 2450451
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 2 6 23 161 10 3 2450610
+ 2,001 2001 1 1 1 21 3 9 38 265 22 7 2452175
+ 2,000 2000 0 0 0 20 1 3 11 75 15 4 2451619
+ 2,000 2000 0 0 0 20 1 3 11 75 15 4 2451619
+ 2,000 2000 0 0 0 20 1 3 11 75 15 4 2451619
+ 2,000 2000 0 0 0 20 1 3 11 75 15 4 2451619
+ 2,000 2000 0 0 0 20 1 3 11 75 15 4 2451619
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 2 6 23 161 10 3 2450610
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 42 11 3 2450491
+ 1,997 1997 997 97 7 20 1 2 7 43 12 4 2450492
+ 1,997 1997 997 97 7 20 1 2 7 44 13 5 2450493
+ 1,997 1997 997 97 7 20 1 2 7 45 14 6 2450494
+ 1,997 1997 997 97 7 20 1 2 7 46 15 7 2450495
+ 1,997 1997 997 97 7 20 1 2 7 47 16 1 2450496
+ 0,097 97 97 97 7 -1 1 2 7 47 16 3 1686042
+ 0,097 97 97 97 7 1 1 2 7 47 16 7 1756536
+ 0,597 597 597 97 7 6 1 2 7 47 16 5 1939157
+ 1,097 1097 97 97 7 11 1 2 7 47 16 3 2121778
+ 1,697 1697 697 97 7 17 1 2 7 47 16 7 2340924
+ 1,797 1797 797 97 7 18 1 2 7 47 16 5 2377448
+ 1,897 1897 897 97 7 19 1 2 7 47 16 3 2413972
+ 1,997 1997 997 97 7 20 1 2 7 47 16 1 2450496
+ 2,097 2097 97 97 7 21 1 2 7 47 16 7 2487021
+ 1,996 1996 996 96 6 20 1 2 9 59 28 4 2450142
+ 1,996 1996 996 96 6 20 1 2 9 60 29 5 2450143
+ 1,996 1996 996 96 6 20 1 3 9 61 1 6 2450144
+ 1,996 1996 996 96 6 20 4 12 53 365 30 2 2450448
+ 1,996 1996 996 96 6 20 4 12 53 366 31 3 2450449
+ 1,997 1997 997 97 7 20 1 1 1 1 1 4 2450450
+ 1,997 1997 997 97 7 20 1 2 9 59 28 6 2450508
+ 1,997 1997 997 97 7 20 1 3 9 60 1 7 2450509
+ 1,997 1997 997 97 7 20 4 12 52 364 30 3 2450813
+ 1,997 1997 997 97 7 20 4 12 53 365 31 4 2450814
+ 1,999 1999 999 99 9 20 4 12 53 365 31 6 2451544
+ 2,000 2000 0 0 0 20 1 1 1 1 1 7 2451545
+ 2,000 2000 0 0 0 20 4 12 53 366 31 1 2451910
+ 2,001 2001 1 1 1 21 1 1 1 1 1 2 2451911
+(65 rows)
+
+SELECT to_char(d1, 'HH HH12 HH24 MI SS SSSS')
+ FROM TIMESTAMP_TBL;
+ to_char
+----------------------
+
+
+ 12 12 00 00 00 0
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 02 63122
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 12 12 00 00 00 0
+ 03 03 03 04 05 11045
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 06 06 18 19 20 65960
+ 08 08 08 14 01 29641
+ 01 01 13 14 02 47642
+ 12 12 12 14 03 44043
+ 03 03 03 14 04 11644
+ 02 02 02 14 05 8045
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 00 63120
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 06 06 18 32 01 66721
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+(65 rows)
+
+SELECT to_char(d1, E'"HH:MI:SS is" HH:MI:SS "\\"text between quote marks\\""')
+ FROM TIMESTAMP_TBL;
+ to_char
+-------------------------------------------------
+
+
+ HH:MI:SS is 12:00:00 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:02 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 12:00:00 "text between quote marks"
+ HH:MI:SS is 03:04:05 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 06:19:20 "text between quote marks"
+ HH:MI:SS is 08:14:01 "text between quote marks"
+ HH:MI:SS is 01:14:02 "text between quote marks"
+ HH:MI:SS is 12:14:03 "text between quote marks"
+ HH:MI:SS is 03:14:04 "text between quote marks"
+ HH:MI:SS is 02:14:05 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:00 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 06:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+(65 rows)
+
+SELECT to_char(d1, 'HH24--text--MI--text--SS')
+ FROM TIMESTAMP_TBL;
+ to_char
+------------------------
+
+
+ 00--text--00--text--00
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--02
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 00--text--00--text--00
+ 03--text--04--text--05
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 18--text--19--text--20
+ 08--text--14--text--01
+ 13--text--14--text--02
+ 12--text--14--text--03
+ 03--text--14--text--04
+ 02--text--14--text--05
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--00
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 18--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+(65 rows)
+
+SELECT to_char(d1, 'YYYYTH YYYYth Jth')
+ FROM TIMESTAMP_TBL;
+ to_char
+-------------------------
+
+
+ 1970TH 1970th 2440588th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450451st
+ 1997TH 1997th 2450451st
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450610th
+ 2001ST 2001st 2452175th
+ 2000TH 2000th 2451619th
+ 2000TH 2000th 2451619th
+ 2000TH 2000th 2451619th
+ 2000TH 2000th 2451619th
+ 2000TH 2000th 2451619th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450610th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450491st
+ 1997TH 1997th 2450492nd
+ 1997TH 1997th 2450493rd
+ 1997TH 1997th 2450494th
+ 1997TH 1997th 2450495th
+ 1997TH 1997th 2450496th
+ 0097TH 0097th 1686042nd
+ 0097TH 0097th 1756536th
+ 0597TH 0597th 1939157th
+ 1097TH 1097th 2121778th
+ 1697TH 1697th 2340924th
+ 1797TH 1797th 2377448th
+ 1897TH 1897th 2413972nd
+ 1997TH 1997th 2450496th
+ 2097TH 2097th 2487021st
+ 1996TH 1996th 2450142nd
+ 1996TH 1996th 2450143rd
+ 1996TH 1996th 2450144th
+ 1996TH 1996th 2450448th
+ 1996TH 1996th 2450449th
+ 1997TH 1997th 2450450th
+ 1997TH 1997th 2450508th
+ 1997TH 1997th 2450509th
+ 1997TH 1997th 2450813th
+ 1997TH 1997th 2450814th
+ 1999TH 1999th 2451544th
+ 2000TH 2000th 2451545th
+ 2000TH 2000th 2451910th
+ 2001ST 2001st 2451911th
+(65 rows)
+
+SELECT to_char(d1, 'YYYY A.D. YYYY a.d. YYYY bc HH:MI:SS P.M. HH:MI:SS p.m. HH:MI:SS pm')
+ FROM TIMESTAMP_TBL;
+ to_char
+---------------------------------------------------------------------
+
+
+ 1970 A.D. 1970 a.d. 1970 ad 12:00:00 A.M. 12:00:00 a.m. 12:00:00 am
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:02 P.M. 05:32:02 p.m. 05:32:02 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 12:00:00 A.M. 12:00:00 a.m. 12:00:00 am
+ 1997 A.D. 1997 a.d. 1997 ad 03:04:05 A.M. 03:04:05 a.m. 03:04:05 am
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 2001 A.D. 2001 a.d. 2001 ad 06:19:20 P.M. 06:19:20 p.m. 06:19:20 pm
+ 2000 A.D. 2000 a.d. 2000 ad 08:14:01 A.M. 08:14:01 a.m. 08:14:01 am
+ 2000 A.D. 2000 a.d. 2000 ad 01:14:02 P.M. 01:14:02 p.m. 01:14:02 pm
+ 2000 A.D. 2000 a.d. 2000 ad 12:14:03 P.M. 12:14:03 p.m. 12:14:03 pm
+ 2000 A.D. 2000 a.d. 2000 ad 03:14:04 A.M. 03:14:04 a.m. 03:14:04 am
+ 2000 A.D. 2000 a.d. 2000 ad 02:14:05 A.M. 02:14:05 a.m. 02:14:05 am
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:00 P.M. 05:32:00 p.m. 05:32:00 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 06:32:01 P.M. 06:32:01 p.m. 06:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 0097 B.C. 0097 b.c. 0097 bc 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 0097 A.D. 0097 a.d. 0097 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 0597 A.D. 0597 a.d. 0597 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1097 A.D. 1097 a.d. 1097 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1697 A.D. 1697 a.d. 1697 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1797 A.D. 1797 a.d. 1797 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1897 A.D. 1897 a.d. 1897 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 2097 A.D. 2097 a.d. 2097 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1996 A.D. 1996 a.d. 1996 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1996 A.D. 1996 a.d. 1996 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1996 A.D. 1996 a.d. 1996 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1996 A.D. 1996 a.d. 1996 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1996 A.D. 1996 a.d. 1996 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1999 A.D. 1999 a.d. 1999 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 2000 A.D. 2000 a.d. 2000 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 2000 A.D. 2000 a.d. 2000 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 2001 A.D. 2001 a.d. 2001 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+(65 rows)
+
+SELECT to_char(d1, 'IYYY IYY IY I IW IDDD ID')
+ FROM TIMESTAMP_TBL;
+ to_char
+------------------------
+
+
+ 1970 970 70 0 01 004 4
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 01 004 4
+ 1997 997 97 7 01 004 4
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 24 163 2
+ 2001 001 01 1 38 265 6
+ 2000 000 00 0 11 073 3
+ 2000 000 00 0 11 073 3
+ 2000 000 00 0 11 073 3
+ 2000 000 00 0 11 073 3
+ 2000 000 00 0 11 073 3
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 24 163 2
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 044 2
+ 1997 997 97 7 07 045 3
+ 1997 997 97 7 07 046 4
+ 1997 997 97 7 07 047 5
+ 1997 997 97 7 07 048 6
+ 1997 997 97 7 07 049 7
+ 0097 097 97 7 07 044 2
+ 0097 097 97 7 07 048 6
+ 0597 597 97 7 07 046 4
+ 1097 097 97 7 07 044 2
+ 1697 697 97 7 07 048 6
+ 1797 797 97 7 07 046 4
+ 1897 897 97 7 07 044 2
+ 1997 997 97 7 07 049 7
+ 2097 097 97 7 07 048 6
+ 1996 996 96 6 09 059 3
+ 1996 996 96 6 09 060 4
+ 1996 996 96 6 09 061 5
+ 1997 997 97 7 01 001 1
+ 1997 997 97 7 01 002 2
+ 1997 997 97 7 01 003 3
+ 1997 997 97 7 09 061 5
+ 1997 997 97 7 09 062 6
+ 1998 998 98 8 01 002 2
+ 1998 998 98 8 01 003 3
+ 1999 999 99 9 52 362 5
+ 1999 999 99 9 52 363 6
+ 2000 000 00 0 52 364 7
+ 2001 001 01 1 01 001 1
+(65 rows)
+
+SELECT to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
+ FROM TIMESTAMP_TBL;
+ to_char
+------------------------
+
+
+ 1970 970 70 0 1 4 4
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 1 4 4
+ 1997 997 97 7 1 4 4
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 24 163 2
+ 2001 1 1 1 38 265 6
+ 2000 0 0 0 11 73 3
+ 2000 0 0 0 11 73 3
+ 2000 0 0 0 11 73 3
+ 2000 0 0 0 11 73 3
+ 2000 0 0 0 11 73 3
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 24 163 2
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 44 2
+ 1997 997 97 7 7 45 3
+ 1997 997 97 7 7 46 4
+ 1997 997 97 7 7 47 5
+ 1997 997 97 7 7 48 6
+ 1997 997 97 7 7 49 7
+ 97 97 97 7 7 44 2
+ 97 97 97 7 7 48 6
+ 597 597 97 7 7 46 4
+ 1097 97 97 7 7 44 2
+ 1697 697 97 7 7 48 6
+ 1797 797 97 7 7 46 4
+ 1897 897 97 7 7 44 2
+ 1997 997 97 7 7 49 7
+ 2097 97 97 7 7 48 6
+ 1996 996 96 6 9 59 3
+ 1996 996 96 6 9 60 4
+ 1996 996 96 6 9 61 5
+ 1997 997 97 7 1 1 1
+ 1997 997 97 7 1 2 2
+ 1997 997 97 7 1 3 3
+ 1997 997 97 7 9 61 5
+ 1997 997 97 7 9 62 6
+ 1998 998 98 8 1 2 2
+ 1998 998 98 8 1 3 3
+ 1999 999 99 9 52 362 5
+ 1999 999 99 9 52 363 6
+ 2000 0 0 0 52 364 7
+ 2001 1 1 1 1 1 1
+(65 rows)
+
+SELECT to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char
+--------------------------------------------------------------------
+ 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
+-- Roman months, with upper and lower case.
+SELECT i,
+ to_char(i * interval '1mon', 'rm'),
+ to_char(i * interval '1mon', 'RM')
+ FROM generate_series(-13, 13) i;
+ i | to_char | to_char
+-----+---------+---------
+ -13 | xii | XII
+ -12 | i | I
+ -11 | ii | II
+ -10 | iii | III
+ -9 | iv | IV
+ -8 | v | V
+ -7 | vi | VI
+ -6 | vii | VII
+ -5 | viii | VIII
+ -4 | ix | IX
+ -3 | x | X
+ -2 | xi | XI
+ -1 | xii | XII
+ 0 | |
+ 1 | i | I
+ 2 | ii | II
+ 3 | iii | III
+ 4 | iv | IV
+ 5 | v | V
+ 6 | vi | VI
+ 7 | vii | VII
+ 8 | viii | VIII
+ 9 | ix | IX
+ 10 | x | X
+ 11 | xi | XI
+ 12 | xii | XII
+ 13 | i | I
+(27 rows)
+
+-- timestamp numeric fields constructor
+SELECT make_timestamp(2014, 12, 28, 6, 30, 45.887);
+ make_timestamp
+------------------------------
+ Sun Dec 28 06:30:45.887 2014
+(1 row)
+
+SELECT make_timestamp(-44, 3, 15, 12, 30, 15);
+ make_timestamp
+-----------------------------
+ Fri Mar 15 12:30:15 0044 BC
+(1 row)
+
+-- should fail
+select make_timestamp(0, 7, 15, 12, 30, 15);
+ERROR: date field value out of range: 0-07-15
+-- generate_series for timestamp
+select * from generate_series('2020-01-01 00:00'::timestamp,
+ '2020-01-02 03:00'::timestamp,
+ '1 hour'::interval);
+ generate_series
+--------------------------
+ Wed Jan 01 00:00:00 2020
+ Wed Jan 01 01:00:00 2020
+ Wed Jan 01 02:00:00 2020
+ Wed Jan 01 03:00:00 2020
+ Wed Jan 01 04:00:00 2020
+ Wed Jan 01 05:00:00 2020
+ Wed Jan 01 06:00:00 2020
+ Wed Jan 01 07:00:00 2020
+ Wed Jan 01 08:00:00 2020
+ Wed Jan 01 09:00:00 2020
+ Wed Jan 01 10:00:00 2020
+ Wed Jan 01 11:00:00 2020
+ Wed Jan 01 12:00:00 2020
+ Wed Jan 01 13:00:00 2020
+ Wed Jan 01 14:00:00 2020
+ Wed Jan 01 15:00:00 2020
+ Wed Jan 01 16:00:00 2020
+ Wed Jan 01 17:00:00 2020
+ Wed Jan 01 18:00:00 2020
+ Wed Jan 01 19:00:00 2020
+ Wed Jan 01 20:00:00 2020
+ Wed Jan 01 21:00:00 2020
+ Wed Jan 01 22:00:00 2020
+ Wed Jan 01 23:00:00 2020
+ Thu Jan 02 00:00:00 2020
+ Thu Jan 02 01:00:00 2020
+ Thu Jan 02 02:00:00 2020
+ Thu Jan 02 03:00:00 2020
+(28 rows)
+
+-- the LIMIT should allow this to terminate in a reasonable amount of time
+-- (but that unfortunately doesn't work yet for SELECT * FROM ...)
+select generate_series('2022-01-01 00:00'::timestamp,
+ 'infinity'::timestamp,
+ '1 month'::interval) limit 10;
+ generate_series
+--------------------------
+ Sat Jan 01 00:00:00 2022
+ Tue Feb 01 00:00:00 2022
+ Tue Mar 01 00:00:00 2022
+ Fri Apr 01 00:00:00 2022
+ Sun May 01 00:00:00 2022
+ Wed Jun 01 00:00:00 2022
+ Fri Jul 01 00:00:00 2022
+ Mon Aug 01 00:00:00 2022
+ Thu Sep 01 00:00:00 2022
+ Sat Oct 01 00:00:00 2022
+(10 rows)
+
+-- errors
+select * from generate_series('2020-01-01 00:00'::timestamp,
+ '2020-01-02 03:00'::timestamp,
+ '0 hour'::interval);
+ERROR: step size cannot equal zero
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
new file mode 100644
index 0000000..a0df947
--- /dev/null
+++ b/src/test/regress/expected/timestamptz.out
@@ -0,0 +1,2991 @@
+--
+-- TIMESTAMPTZ
+--
+CREATE TABLE TIMESTAMPTZ_TBL (d1 timestamp(2) with time zone);
+-- Test shorthand input values
+-- We can't just "select" the results since they aren't constants; test for
+-- equality instead. We can do that by running the test inside a transaction
+-- block, within which the value of 'now' shouldn't change, and so these
+-- related values shouldn't either.
+BEGIN;
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('today');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('yesterday');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('tomorrow');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('tomorrow EST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('tomorrow zulu');
+SELECT count(*) AS One FROM TIMESTAMPTZ_TBL WHERE d1 = timestamp with time zone 'today';
+ one
+-----
+ 1
+(1 row)
+
+SELECT count(*) AS One FROM TIMESTAMPTZ_TBL WHERE d1 = timestamp with time zone 'tomorrow';
+ one
+-----
+ 1
+(1 row)
+
+SELECT count(*) AS One FROM TIMESTAMPTZ_TBL WHERE d1 = timestamp with time zone 'yesterday';
+ one
+-----
+ 1
+(1 row)
+
+SELECT count(*) AS One FROM TIMESTAMPTZ_TBL WHERE d1 = timestamp with time zone 'tomorrow EST';
+ one
+-----
+ 1
+(1 row)
+
+SELECT count(*) AS One FROM TIMESTAMPTZ_TBL WHERE d1 = timestamp with time zone 'tomorrow zulu';
+ one
+-----
+ 1
+(1 row)
+
+COMMIT;
+DELETE FROM TIMESTAMPTZ_TBL;
+-- Verify that 'now' *does* change over a reasonable interval such as 100 msec,
+-- and that it doesn't change over the same interval within a transaction block
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('now');
+SELECT pg_sleep(0.1);
+ pg_sleep
+----------
+
+(1 row)
+
+BEGIN;
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('now');
+SELECT pg_sleep(0.1);
+ pg_sleep
+----------
+
+(1 row)
+
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('now');
+SELECT pg_sleep(0.1);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT count(*) AS two FROM TIMESTAMPTZ_TBL WHERE d1 = timestamp(2) with time zone 'now';
+ two
+-----
+ 2
+(1 row)
+
+SELECT count(d1) AS three, count(DISTINCT d1) AS two FROM TIMESTAMPTZ_TBL;
+ three | two
+-------+-----
+ 3 | 2
+(1 row)
+
+COMMIT;
+TRUNCATE TIMESTAMPTZ_TBL;
+-- Special values
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('-infinity');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('infinity');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('epoch');
+-- Postgres v6.0 standard output format
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mon Feb 10 17:32:01 1997 PST');
+-- Variations on Postgres v6.1 standard output format
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mon Feb 10 17:32:01.000001 1997 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mon Feb 10 17:32:01.999999 1997 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mon Feb 10 17:32:01.4 1997 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mon Feb 10 17:32:01.5 1997 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mon Feb 10 17:32:01.6 1997 PST');
+-- ISO 8601 format
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-01-02');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-01-02 03:04:05');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-02-10 17:32:01-08');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-02-10 17:32:01-0800');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-02-10 17:32:01 -08:00');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970210 173201 -0800');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-06-10 17:32:01 -07:00');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('2001-09-22T18:19:20');
+-- POSIX format (note that the timezone abbrev is just decoration here)
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 08:14:01 GMT+8');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 13:14:02 GMT-1');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 12:14:03 GMT-2');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 03:14:04 PST+8');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 02:14:05 MST+7:00');
+-- Variations for acceptable input formats
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 17:32:01 1997 -0800');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 5:32PM 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997/02/10 17:32:01-0800');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-02-10 17:32:01 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb-10-1997 17:32:01 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('02-10-1997 17:32:01 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970210 173201 PST');
+set datestyle to ymd;
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('97FEB10 5:32:01PM UTC');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('97/02/10 17:32:01 UTC');
+reset datestyle;
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997.041 17:32:01 UTC');
+-- timestamps at different timezones
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970210 173201 America/New_York');
+SELECT '19970210 173201' AT TIME ZONE 'America/New_York';
+ timezone
+--------------------------
+ Mon Feb 10 20:32:01 1997
+(1 row)
+
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970710 173201 America/New_York');
+SELECT '19970710 173201' AT TIME ZONE 'America/New_York';
+ timezone
+--------------------------
+ Thu Jul 10 20:32:01 1997
+(1 row)
+
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970710 173201 America/Does_not_exist');
+ERROR: time zone "america/does_not_exist" not recognized
+LINE 1: INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970710 173201 America...
+ ^
+SELECT '19970710 173201' AT TIME ZONE 'America/Does_not_exist';
+ERROR: time zone "America/Does_not_exist" not recognized
+-- Daylight saving time for timestamps beyond 32-bit time_t range.
+SELECT '20500710 173201 Europe/Helsinki'::timestamptz; -- DST
+ timestamptz
+------------------------------
+ Sun Jul 10 07:32:01 2050 PDT
+(1 row)
+
+SELECT '20500110 173201 Europe/Helsinki'::timestamptz; -- non-DST
+ timestamptz
+------------------------------
+ Mon Jan 10 07:32:01 2050 PST
+(1 row)
+
+SELECT '205000-07-10 17:32:01 Europe/Helsinki'::timestamptz; -- DST
+ timestamptz
+--------------------------------
+ Thu Jul 10 07:32:01 205000 PDT
+(1 row)
+
+SELECT '205000-01-10 17:32:01 Europe/Helsinki'::timestamptz; -- non-DST
+ timestamptz
+--------------------------------
+ Fri Jan 10 07:32:01 205000 PST
+(1 row)
+
+-- Check date conversion and date arithmetic
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-06-10 18:32:01 PDT');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 11 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 12 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 13 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 14 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 15 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 0097 BC');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 0097');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 0597');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 1097');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 1697');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 1797');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 1897');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 2097');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 28 17:32:01 1996');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 29 17:32:01 1996');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mar 01 17:32:01 1996');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 30 17:32:01 1996');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 31 17:32:01 1996');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Jan 01 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 28 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 29 17:32:01 1997');
+ERROR: date/time field value out of range: "Feb 29 17:32:01 1997"
+LINE 1: INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 29 17:32:01 1997');
+ ^
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mar 01 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 30 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 31 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 31 17:32:01 1999');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Jan 01 17:32:01 2000');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 31 17:32:01 2000');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Jan 01 17:32:01 2001');
+-- Currently unsupported syntax and ranges
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 -0097');
+ERROR: time zone displacement out of range: "Feb 16 17:32:01 -0097"
+LINE 1: INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 -0097')...
+ ^
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 5097 BC');
+ERROR: timestamp out of range: "Feb 16 17:32:01 5097 BC"
+LINE 1: INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 5097 BC...
+ ^
+-- Alternative field order that we've historically supported (sort of)
+-- with regular and POSIXy timezone specs
+SELECT 'Wed Jul 11 10:51:14 America/New_York 2001'::timestamptz;
+ timestamptz
+------------------------------
+ Wed Jul 11 07:51:14 2001 PDT
+(1 row)
+
+SELECT 'Wed Jul 11 10:51:14 GMT-4 2001'::timestamptz;
+ timestamptz
+------------------------------
+ Tue Jul 10 23:51:14 2001 PDT
+(1 row)
+
+SELECT 'Wed Jul 11 10:51:14 GMT+4 2001'::timestamptz;
+ timestamptz
+------------------------------
+ Wed Jul 11 07:51:14 2001 PDT
+(1 row)
+
+SELECT 'Wed Jul 11 10:51:14 PST-03:00 2001'::timestamptz;
+ timestamptz
+------------------------------
+ Wed Jul 11 00:51:14 2001 PDT
+(1 row)
+
+SELECT 'Wed Jul 11 10:51:14 PST+03:00 2001'::timestamptz;
+ timestamptz
+------------------------------
+ Wed Jul 11 06:51:14 2001 PDT
+(1 row)
+
+SELECT d1 FROM TIMESTAMPTZ_TBL;
+ d1
+---------------------------------
+ -infinity
+ infinity
+ Wed Dec 31 16:00:00 1969 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:02 1997 PST
+ Mon Feb 10 17:32:01.4 1997 PST
+ Mon Feb 10 17:32:01.5 1997 PST
+ Mon Feb 10 17:32:01.6 1997 PST
+ Thu Jan 02 00:00:00 1997 PST
+ Thu Jan 02 03:04:05 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Tue Jun 10 17:32:01 1997 PDT
+ Sat Sep 22 18:19:20 2001 PDT
+ Wed Mar 15 08:14:01 2000 PST
+ Wed Mar 15 04:14:02 2000 PST
+ Wed Mar 15 02:14:03 2000 PST
+ Wed Mar 15 03:14:04 2000 PST
+ Wed Mar 15 01:14:05 2000 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:00 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 09:32:01 1997 PST
+ Mon Feb 10 09:32:01 1997 PST
+ Mon Feb 10 09:32:01 1997 PST
+ Mon Feb 10 14:32:01 1997 PST
+ Thu Jul 10 14:32:01 1997 PDT
+ Tue Jun 10 18:32:01 1997 PDT
+ Mon Feb 10 17:32:01 1997 PST
+ Tue Feb 11 17:32:01 1997 PST
+ Wed Feb 12 17:32:01 1997 PST
+ Thu Feb 13 17:32:01 1997 PST
+ Fri Feb 14 17:32:01 1997 PST
+ Sat Feb 15 17:32:01 1997 PST
+ Sun Feb 16 17:32:01 1997 PST
+ Tue Feb 16 17:32:01 0097 PST BC
+ Sat Feb 16 17:32:01 0097 PST
+ Thu Feb 16 17:32:01 0597 PST
+ Tue Feb 16 17:32:01 1097 PST
+ Sat Feb 16 17:32:01 1697 PST
+ Thu Feb 16 17:32:01 1797 PST
+ Tue Feb 16 17:32:01 1897 PST
+ Sun Feb 16 17:32:01 1997 PST
+ Sat Feb 16 17:32:01 2097 PST
+ Wed Feb 28 17:32:01 1996 PST
+ Thu Feb 29 17:32:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST
+ Mon Dec 30 17:32:01 1996 PST
+ Tue Dec 31 17:32:01 1996 PST
+ Wed Jan 01 17:32:01 1997 PST
+ Fri Feb 28 17:32:01 1997 PST
+ Sat Mar 01 17:32:01 1997 PST
+ Tue Dec 30 17:32:01 1997 PST
+ Wed Dec 31 17:32:01 1997 PST
+ Fri Dec 31 17:32:01 1999 PST
+ Sat Jan 01 17:32:01 2000 PST
+ Sun Dec 31 17:32:01 2000 PST
+ Mon Jan 01 17:32:01 2001 PST
+(66 rows)
+
+-- Check behavior at the boundaries of the timestamp range
+SELECT '4714-11-24 00:00:00+00 BC'::timestamptz;
+ timestamptz
+---------------------------------
+ Sun Nov 23 16:00:00 4714 PST BC
+(1 row)
+
+SELECT '4714-11-23 16:00:00-08 BC'::timestamptz;
+ timestamptz
+---------------------------------
+ Sun Nov 23 16:00:00 4714 PST BC
+(1 row)
+
+SELECT 'Sun Nov 23 16:00:00 4714 PST BC'::timestamptz;
+ timestamptz
+---------------------------------
+ Sun Nov 23 16:00:00 4714 PST BC
+(1 row)
+
+SELECT '4714-11-23 23:59:59+00 BC'::timestamptz; -- out of range
+ERROR: timestamp out of range: "4714-11-23 23:59:59+00 BC"
+LINE 1: SELECT '4714-11-23 23:59:59+00 BC'::timestamptz;
+ ^
+SELECT '294276-12-31 23:59:59+00'::timestamptz;
+ timestamptz
+--------------------------------
+ Sun Dec 31 15:59:59 294276 PST
+(1 row)
+
+SELECT '294276-12-31 15:59:59-08'::timestamptz;
+ timestamptz
+--------------------------------
+ Sun Dec 31 15:59:59 294276 PST
+(1 row)
+
+SELECT '294277-01-01 00:00:00+00'::timestamptz; -- out of range
+ERROR: timestamp out of range: "294277-01-01 00:00:00+00"
+LINE 1: SELECT '294277-01-01 00:00:00+00'::timestamptz;
+ ^
+SELECT '294277-12-31 16:00:00-08'::timestamptz; -- out of range
+ERROR: timestamp out of range: "294277-12-31 16:00:00-08"
+LINE 1: SELECT '294277-12-31 16:00:00-08'::timestamptz;
+ ^
+-- Demonstrate functions and operators
+SELECT d1 FROM TIMESTAMPTZ_TBL
+ WHERE d1 > timestamp with time zone '1997-01-02';
+ d1
+--------------------------------
+ infinity
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:02 1997 PST
+ Mon Feb 10 17:32:01.4 1997 PST
+ Mon Feb 10 17:32:01.5 1997 PST
+ Mon Feb 10 17:32:01.6 1997 PST
+ Thu Jan 02 03:04:05 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Tue Jun 10 17:32:01 1997 PDT
+ Sat Sep 22 18:19:20 2001 PDT
+ Wed Mar 15 08:14:01 2000 PST
+ Wed Mar 15 04:14:02 2000 PST
+ Wed Mar 15 02:14:03 2000 PST
+ Wed Mar 15 03:14:04 2000 PST
+ Wed Mar 15 01:14:05 2000 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:00 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 09:32:01 1997 PST
+ Mon Feb 10 09:32:01 1997 PST
+ Mon Feb 10 09:32:01 1997 PST
+ Mon Feb 10 14:32:01 1997 PST
+ Thu Jul 10 14:32:01 1997 PDT
+ Tue Jun 10 18:32:01 1997 PDT
+ Mon Feb 10 17:32:01 1997 PST
+ Tue Feb 11 17:32:01 1997 PST
+ Wed Feb 12 17:32:01 1997 PST
+ Thu Feb 13 17:32:01 1997 PST
+ Fri Feb 14 17:32:01 1997 PST
+ Sat Feb 15 17:32:01 1997 PST
+ Sun Feb 16 17:32:01 1997 PST
+ Sun Feb 16 17:32:01 1997 PST
+ Sat Feb 16 17:32:01 2097 PST
+ Fri Feb 28 17:32:01 1997 PST
+ Sat Mar 01 17:32:01 1997 PST
+ Tue Dec 30 17:32:01 1997 PST
+ Wed Dec 31 17:32:01 1997 PST
+ Fri Dec 31 17:32:01 1999 PST
+ Sat Jan 01 17:32:01 2000 PST
+ Sun Dec 31 17:32:01 2000 PST
+ Mon Jan 01 17:32:01 2001 PST
+(50 rows)
+
+SELECT d1 FROM TIMESTAMPTZ_TBL
+ WHERE d1 < timestamp with time zone '1997-01-02';
+ d1
+---------------------------------
+ -infinity
+ Wed Dec 31 16:00:00 1969 PST
+ Tue Feb 16 17:32:01 0097 PST BC
+ Sat Feb 16 17:32:01 0097 PST
+ Thu Feb 16 17:32:01 0597 PST
+ Tue Feb 16 17:32:01 1097 PST
+ Sat Feb 16 17:32:01 1697 PST
+ Thu Feb 16 17:32:01 1797 PST
+ Tue Feb 16 17:32:01 1897 PST
+ Wed Feb 28 17:32:01 1996 PST
+ Thu Feb 29 17:32:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST
+ Mon Dec 30 17:32:01 1996 PST
+ Tue Dec 31 17:32:01 1996 PST
+ Wed Jan 01 17:32:01 1997 PST
+(15 rows)
+
+SELECT d1 FROM TIMESTAMPTZ_TBL
+ WHERE d1 = timestamp with time zone '1997-01-02';
+ d1
+------------------------------
+ Thu Jan 02 00:00:00 1997 PST
+(1 row)
+
+SELECT d1 FROM TIMESTAMPTZ_TBL
+ WHERE d1 != timestamp with time zone '1997-01-02';
+ d1
+---------------------------------
+ -infinity
+ infinity
+ Wed Dec 31 16:00:00 1969 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:02 1997 PST
+ Mon Feb 10 17:32:01.4 1997 PST
+ Mon Feb 10 17:32:01.5 1997 PST
+ Mon Feb 10 17:32:01.6 1997 PST
+ Thu Jan 02 03:04:05 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Tue Jun 10 17:32:01 1997 PDT
+ Sat Sep 22 18:19:20 2001 PDT
+ Wed Mar 15 08:14:01 2000 PST
+ Wed Mar 15 04:14:02 2000 PST
+ Wed Mar 15 02:14:03 2000 PST
+ Wed Mar 15 03:14:04 2000 PST
+ Wed Mar 15 01:14:05 2000 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:00 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 09:32:01 1997 PST
+ Mon Feb 10 09:32:01 1997 PST
+ Mon Feb 10 09:32:01 1997 PST
+ Mon Feb 10 14:32:01 1997 PST
+ Thu Jul 10 14:32:01 1997 PDT
+ Tue Jun 10 18:32:01 1997 PDT
+ Mon Feb 10 17:32:01 1997 PST
+ Tue Feb 11 17:32:01 1997 PST
+ Wed Feb 12 17:32:01 1997 PST
+ Thu Feb 13 17:32:01 1997 PST
+ Fri Feb 14 17:32:01 1997 PST
+ Sat Feb 15 17:32:01 1997 PST
+ Sun Feb 16 17:32:01 1997 PST
+ Tue Feb 16 17:32:01 0097 PST BC
+ Sat Feb 16 17:32:01 0097 PST
+ Thu Feb 16 17:32:01 0597 PST
+ Tue Feb 16 17:32:01 1097 PST
+ Sat Feb 16 17:32:01 1697 PST
+ Thu Feb 16 17:32:01 1797 PST
+ Tue Feb 16 17:32:01 1897 PST
+ Sun Feb 16 17:32:01 1997 PST
+ Sat Feb 16 17:32:01 2097 PST
+ Wed Feb 28 17:32:01 1996 PST
+ Thu Feb 29 17:32:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST
+ Mon Dec 30 17:32:01 1996 PST
+ Tue Dec 31 17:32:01 1996 PST
+ Wed Jan 01 17:32:01 1997 PST
+ Fri Feb 28 17:32:01 1997 PST
+ Sat Mar 01 17:32:01 1997 PST
+ Tue Dec 30 17:32:01 1997 PST
+ Wed Dec 31 17:32:01 1997 PST
+ Fri Dec 31 17:32:01 1999 PST
+ Sat Jan 01 17:32:01 2000 PST
+ Sun Dec 31 17:32:01 2000 PST
+ Mon Jan 01 17:32:01 2001 PST
+(65 rows)
+
+SELECT d1 FROM TIMESTAMPTZ_TBL
+ WHERE d1 <= timestamp with time zone '1997-01-02';
+ d1
+---------------------------------
+ -infinity
+ Wed Dec 31 16:00:00 1969 PST
+ Thu Jan 02 00:00:00 1997 PST
+ Tue Feb 16 17:32:01 0097 PST BC
+ Sat Feb 16 17:32:01 0097 PST
+ Thu Feb 16 17:32:01 0597 PST
+ Tue Feb 16 17:32:01 1097 PST
+ Sat Feb 16 17:32:01 1697 PST
+ Thu Feb 16 17:32:01 1797 PST
+ Tue Feb 16 17:32:01 1897 PST
+ Wed Feb 28 17:32:01 1996 PST
+ Thu Feb 29 17:32:01 1996 PST
+ Fri Mar 01 17:32:01 1996 PST
+ Mon Dec 30 17:32:01 1996 PST
+ Tue Dec 31 17:32:01 1996 PST
+ Wed Jan 01 17:32:01 1997 PST
+(16 rows)
+
+SELECT d1 FROM TIMESTAMPTZ_TBL
+ WHERE d1 >= timestamp with time zone '1997-01-02';
+ d1
+--------------------------------
+ infinity
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:02 1997 PST
+ Mon Feb 10 17:32:01.4 1997 PST
+ Mon Feb 10 17:32:01.5 1997 PST
+ Mon Feb 10 17:32:01.6 1997 PST
+ Thu Jan 02 00:00:00 1997 PST
+ Thu Jan 02 03:04:05 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Tue Jun 10 17:32:01 1997 PDT
+ Sat Sep 22 18:19:20 2001 PDT
+ Wed Mar 15 08:14:01 2000 PST
+ Wed Mar 15 04:14:02 2000 PST
+ Wed Mar 15 02:14:03 2000 PST
+ Wed Mar 15 03:14:04 2000 PST
+ Wed Mar 15 01:14:05 2000 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:00 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 17:32:01 1997 PST
+ Mon Feb 10 09:32:01 1997 PST
+ Mon Feb 10 09:32:01 1997 PST
+ Mon Feb 10 09:32:01 1997 PST
+ Mon Feb 10 14:32:01 1997 PST
+ Thu Jul 10 14:32:01 1997 PDT
+ Tue Jun 10 18:32:01 1997 PDT
+ Mon Feb 10 17:32:01 1997 PST
+ Tue Feb 11 17:32:01 1997 PST
+ Wed Feb 12 17:32:01 1997 PST
+ Thu Feb 13 17:32:01 1997 PST
+ Fri Feb 14 17:32:01 1997 PST
+ Sat Feb 15 17:32:01 1997 PST
+ Sun Feb 16 17:32:01 1997 PST
+ Sun Feb 16 17:32:01 1997 PST
+ Sat Feb 16 17:32:01 2097 PST
+ Fri Feb 28 17:32:01 1997 PST
+ Sat Mar 01 17:32:01 1997 PST
+ Tue Dec 30 17:32:01 1997 PST
+ Wed Dec 31 17:32:01 1997 PST
+ Fri Dec 31 17:32:01 1999 PST
+ Sat Jan 01 17:32:01 2000 PST
+ Sun Dec 31 17:32:01 2000 PST
+ Mon Jan 01 17:32:01 2001 PST
+(51 rows)
+
+SELECT d1 - timestamp with time zone '1997-01-02' AS diff
+ FROM TIMESTAMPTZ_TBL WHERE d1 BETWEEN '1902-01-01' AND '2038-01-01';
+ diff
+----------------------------------------
+ @ 9863 days 8 hours ago
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 2 secs
+ @ 39 days 17 hours 32 mins 1.4 secs
+ @ 39 days 17 hours 32 mins 1.5 secs
+ @ 39 days 17 hours 32 mins 1.6 secs
+ @ 0
+ @ 3 hours 4 mins 5 secs
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 159 days 16 hours 32 mins 1 sec
+ @ 1724 days 17 hours 19 mins 20 secs
+ @ 1168 days 8 hours 14 mins 1 sec
+ @ 1168 days 4 hours 14 mins 2 secs
+ @ 1168 days 2 hours 14 mins 3 secs
+ @ 1168 days 3 hours 14 mins 4 secs
+ @ 1168 days 1 hour 14 mins 5 secs
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 9 hours 32 mins 1 sec
+ @ 39 days 9 hours 32 mins 1 sec
+ @ 39 days 9 hours 32 mins 1 sec
+ @ 39 days 14 hours 32 mins 1 sec
+ @ 189 days 13 hours 32 mins 1 sec
+ @ 159 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 40 days 17 hours 32 mins 1 sec
+ @ 41 days 17 hours 32 mins 1 sec
+ @ 42 days 17 hours 32 mins 1 sec
+ @ 43 days 17 hours 32 mins 1 sec
+ @ 44 days 17 hours 32 mins 1 sec
+ @ 45 days 17 hours 32 mins 1 sec
+ @ 45 days 17 hours 32 mins 1 sec
+ @ 308 days 6 hours 27 mins 59 secs ago
+ @ 307 days 6 hours 27 mins 59 secs ago
+ @ 306 days 6 hours 27 mins 59 secs ago
+ @ 2 days 6 hours 27 mins 59 secs ago
+ @ 1 day 6 hours 27 mins 59 secs ago
+ @ 6 hours 27 mins 59 secs ago
+ @ 57 days 17 hours 32 mins 1 sec
+ @ 58 days 17 hours 32 mins 1 sec
+ @ 362 days 17 hours 32 mins 1 sec
+ @ 363 days 17 hours 32 mins 1 sec
+ @ 1093 days 17 hours 32 mins 1 sec
+ @ 1094 days 17 hours 32 mins 1 sec
+ @ 1459 days 17 hours 32 mins 1 sec
+ @ 1460 days 17 hours 32 mins 1 sec
+(56 rows)
+
+SELECT date_trunc( 'week', timestamp with time zone '2004-02-29 15:44:17.71393' ) AS week_trunc;
+ week_trunc
+------------------------------
+ Mon Feb 23 00:00:00 2004 PST
+(1 row)
+
+SELECT date_trunc('day', timestamp with time zone '2001-02-16 20:38:40+00', 'Australia/Sydney') as sydney_trunc; -- zone name
+ sydney_trunc
+------------------------------
+ Fri Feb 16 05:00:00 2001 PST
+(1 row)
+
+SELECT date_trunc('day', timestamp with time zone '2001-02-16 20:38:40+00', 'GMT') as gmt_trunc; -- fixed-offset abbreviation
+ gmt_trunc
+------------------------------
+ Thu Feb 15 16:00:00 2001 PST
+(1 row)
+
+SELECT date_trunc('day', timestamp with time zone '2001-02-16 20:38:40+00', 'VET') as vet_trunc; -- variable-offset abbreviation
+ vet_trunc
+------------------------------
+ Thu Feb 15 20:00:00 2001 PST
+(1 row)
+
+-- verify date_bin behaves the same as date_trunc for relevant intervals
+SELECT
+ str,
+ interval,
+ date_trunc(str, ts, 'Australia/Sydney') = date_bin(interval::interval, ts, timestamp with time zone '2001-01-01+11') AS equal
+FROM (
+ VALUES
+ ('day', '1 d'),
+ ('hour', '1 h'),
+ ('minute', '1 m'),
+ ('second', '1 s'),
+ ('millisecond', '1 ms'),
+ ('microsecond', '1 us')
+) intervals (str, interval),
+(VALUES (timestamptz '2020-02-29 15:44:17.71393+00')) ts (ts);
+ str | interval | equal
+-------------+----------+-------
+ day | 1 d | t
+ hour | 1 h | t
+ minute | 1 m | t
+ second | 1 s | t
+ millisecond | 1 ms | t
+ microsecond | 1 us | t
+(6 rows)
+
+-- bin timestamps into arbitrary intervals
+SELECT
+ interval,
+ ts,
+ origin,
+ date_bin(interval::interval, ts, origin)
+FROM (
+ VALUES
+ ('15 days'),
+ ('2 hours'),
+ ('1 hour 30 minutes'),
+ ('15 minutes'),
+ ('10 seconds'),
+ ('100 milliseconds'),
+ ('250 microseconds')
+) intervals (interval),
+(VALUES (timestamptz '2020-02-11 15:44:17.71393')) ts (ts),
+(VALUES (timestamptz '2001-01-01')) origin (origin);
+ interval | ts | origin | date_bin
+-------------------+------------------------------------+------------------------------+------------------------------------
+ 15 days | Tue Feb 11 15:44:17.71393 2020 PST | Mon Jan 01 00:00:00 2001 PST | Thu Feb 06 00:00:00 2020 PST
+ 2 hours | Tue Feb 11 15:44:17.71393 2020 PST | Mon Jan 01 00:00:00 2001 PST | Tue Feb 11 14:00:00 2020 PST
+ 1 hour 30 minutes | Tue Feb 11 15:44:17.71393 2020 PST | Mon Jan 01 00:00:00 2001 PST | Tue Feb 11 15:00:00 2020 PST
+ 15 minutes | Tue Feb 11 15:44:17.71393 2020 PST | Mon Jan 01 00:00:00 2001 PST | Tue Feb 11 15:30:00 2020 PST
+ 10 seconds | Tue Feb 11 15:44:17.71393 2020 PST | Mon Jan 01 00:00:00 2001 PST | Tue Feb 11 15:44:10 2020 PST
+ 100 milliseconds | Tue Feb 11 15:44:17.71393 2020 PST | Mon Jan 01 00:00:00 2001 PST | Tue Feb 11 15:44:17.7 2020 PST
+ 250 microseconds | Tue Feb 11 15:44:17.71393 2020 PST | Mon Jan 01 00:00:00 2001 PST | Tue Feb 11 15:44:17.71375 2020 PST
+(7 rows)
+
+-- shift bins using the origin parameter:
+SELECT date_bin('5 min'::interval, timestamptz '2020-02-01 01:01:01+00', timestamptz '2020-02-01 00:02:30+00');
+ date_bin
+------------------------------
+ Fri Jan 31 16:57:30 2020 PST
+(1 row)
+
+-- disallow intervals with months or years
+SELECT date_bin('5 months'::interval, timestamp with time zone '2020-02-01 01:01:01+00', timestamp with time zone '2001-01-01+00');
+ERROR: timestamps cannot be binned into intervals containing months or years
+SELECT date_bin('5 years'::interval, timestamp with time zone '2020-02-01 01:01:01+00', timestamp with time zone '2001-01-01+00');
+ERROR: timestamps cannot be binned into intervals containing months or years
+-- disallow zero intervals
+SELECT date_bin('0 days'::interval, timestamp with time zone '1970-01-01 01:00:00+00' , timestamp with time zone '1970-01-01 00:00:00+00');
+ERROR: stride must be greater than zero
+-- disallow negative intervals
+SELECT date_bin('-2 days'::interval, timestamp with time zone '1970-01-01 01:00:00+00' , timestamp with time zone '1970-01-01 00:00:00+00');
+ERROR: stride must be greater than zero
+-- Test casting within a BETWEEN qualifier
+SELECT d1 - timestamp with time zone '1997-01-02' AS diff
+ FROM TIMESTAMPTZ_TBL
+ WHERE d1 BETWEEN timestamp with time zone '1902-01-01' AND timestamp with time zone '2038-01-01';
+ diff
+----------------------------------------
+ @ 9863 days 8 hours ago
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 2 secs
+ @ 39 days 17 hours 32 mins 1.4 secs
+ @ 39 days 17 hours 32 mins 1.5 secs
+ @ 39 days 17 hours 32 mins 1.6 secs
+ @ 0
+ @ 3 hours 4 mins 5 secs
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 159 days 16 hours 32 mins 1 sec
+ @ 1724 days 17 hours 19 mins 20 secs
+ @ 1168 days 8 hours 14 mins 1 sec
+ @ 1168 days 4 hours 14 mins 2 secs
+ @ 1168 days 2 hours 14 mins 3 secs
+ @ 1168 days 3 hours 14 mins 4 secs
+ @ 1168 days 1 hour 14 mins 5 secs
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 39 days 9 hours 32 mins 1 sec
+ @ 39 days 9 hours 32 mins 1 sec
+ @ 39 days 9 hours 32 mins 1 sec
+ @ 39 days 14 hours 32 mins 1 sec
+ @ 189 days 13 hours 32 mins 1 sec
+ @ 159 days 17 hours 32 mins 1 sec
+ @ 39 days 17 hours 32 mins 1 sec
+ @ 40 days 17 hours 32 mins 1 sec
+ @ 41 days 17 hours 32 mins 1 sec
+ @ 42 days 17 hours 32 mins 1 sec
+ @ 43 days 17 hours 32 mins 1 sec
+ @ 44 days 17 hours 32 mins 1 sec
+ @ 45 days 17 hours 32 mins 1 sec
+ @ 45 days 17 hours 32 mins 1 sec
+ @ 308 days 6 hours 27 mins 59 secs ago
+ @ 307 days 6 hours 27 mins 59 secs ago
+ @ 306 days 6 hours 27 mins 59 secs ago
+ @ 2 days 6 hours 27 mins 59 secs ago
+ @ 1 day 6 hours 27 mins 59 secs ago
+ @ 6 hours 27 mins 59 secs ago
+ @ 57 days 17 hours 32 mins 1 sec
+ @ 58 days 17 hours 32 mins 1 sec
+ @ 362 days 17 hours 32 mins 1 sec
+ @ 363 days 17 hours 32 mins 1 sec
+ @ 1093 days 17 hours 32 mins 1 sec
+ @ 1094 days 17 hours 32 mins 1 sec
+ @ 1459 days 17 hours 32 mins 1 sec
+ @ 1460 days 17 hours 32 mins 1 sec
+(56 rows)
+
+-- DATE_PART (timestamptz_part)
+SELECT d1 as timestamptz,
+ date_part( 'year', d1) AS year, date_part( 'month', d1) AS month,
+ date_part( 'day', d1) AS day, date_part( 'hour', d1) AS hour,
+ date_part( 'minute', d1) AS minute, date_part( 'second', d1) AS second
+ FROM TIMESTAMPTZ_TBL;
+ timestamptz | year | month | day | hour | minute | second
+---------------------------------+-----------+-------+-----+------+--------+--------
+ -infinity | -Infinity | | | | |
+ infinity | Infinity | | | | |
+ Wed Dec 31 16:00:00 1969 PST | 1969 | 12 | 31 | 16 | 0 | 0
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:02 1997 PST | 1997 | 2 | 10 | 17 | 32 | 2
+ Mon Feb 10 17:32:01.4 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1.4
+ Mon Feb 10 17:32:01.5 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1.5
+ Mon Feb 10 17:32:01.6 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1.6
+ Thu Jan 02 00:00:00 1997 PST | 1997 | 1 | 2 | 0 | 0 | 0
+ Thu Jan 02 03:04:05 1997 PST | 1997 | 1 | 2 | 3 | 4 | 5
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Tue Jun 10 17:32:01 1997 PDT | 1997 | 6 | 10 | 17 | 32 | 1
+ Sat Sep 22 18:19:20 2001 PDT | 2001 | 9 | 22 | 18 | 19 | 20
+ Wed Mar 15 08:14:01 2000 PST | 2000 | 3 | 15 | 8 | 14 | 1
+ Wed Mar 15 04:14:02 2000 PST | 2000 | 3 | 15 | 4 | 14 | 2
+ Wed Mar 15 02:14:03 2000 PST | 2000 | 3 | 15 | 2 | 14 | 3
+ Wed Mar 15 03:14:04 2000 PST | 2000 | 3 | 15 | 3 | 14 | 4
+ Wed Mar 15 01:14:05 2000 PST | 2000 | 3 | 15 | 1 | 14 | 5
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:00 1997 PST | 1997 | 2 | 10 | 17 | 32 | 0
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Mon Feb 10 09:32:01 1997 PST | 1997 | 2 | 10 | 9 | 32 | 1
+ Mon Feb 10 09:32:01 1997 PST | 1997 | 2 | 10 | 9 | 32 | 1
+ Mon Feb 10 09:32:01 1997 PST | 1997 | 2 | 10 | 9 | 32 | 1
+ Mon Feb 10 14:32:01 1997 PST | 1997 | 2 | 10 | 14 | 32 | 1
+ Thu Jul 10 14:32:01 1997 PDT | 1997 | 7 | 10 | 14 | 32 | 1
+ Tue Jun 10 18:32:01 1997 PDT | 1997 | 6 | 10 | 18 | 32 | 1
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 2 | 10 | 17 | 32 | 1
+ Tue Feb 11 17:32:01 1997 PST | 1997 | 2 | 11 | 17 | 32 | 1
+ Wed Feb 12 17:32:01 1997 PST | 1997 | 2 | 12 | 17 | 32 | 1
+ Thu Feb 13 17:32:01 1997 PST | 1997 | 2 | 13 | 17 | 32 | 1
+ Fri Feb 14 17:32:01 1997 PST | 1997 | 2 | 14 | 17 | 32 | 1
+ Sat Feb 15 17:32:01 1997 PST | 1997 | 2 | 15 | 17 | 32 | 1
+ Sun Feb 16 17:32:01 1997 PST | 1997 | 2 | 16 | 17 | 32 | 1
+ Tue Feb 16 17:32:01 0097 PST BC | -97 | 2 | 16 | 17 | 32 | 1
+ Sat Feb 16 17:32:01 0097 PST | 97 | 2 | 16 | 17 | 32 | 1
+ Thu Feb 16 17:32:01 0597 PST | 597 | 2 | 16 | 17 | 32 | 1
+ Tue Feb 16 17:32:01 1097 PST | 1097 | 2 | 16 | 17 | 32 | 1
+ Sat Feb 16 17:32:01 1697 PST | 1697 | 2 | 16 | 17 | 32 | 1
+ Thu Feb 16 17:32:01 1797 PST | 1797 | 2 | 16 | 17 | 32 | 1
+ Tue Feb 16 17:32:01 1897 PST | 1897 | 2 | 16 | 17 | 32 | 1
+ Sun Feb 16 17:32:01 1997 PST | 1997 | 2 | 16 | 17 | 32 | 1
+ Sat Feb 16 17:32:01 2097 PST | 2097 | 2 | 16 | 17 | 32 | 1
+ Wed Feb 28 17:32:01 1996 PST | 1996 | 2 | 28 | 17 | 32 | 1
+ Thu Feb 29 17:32:01 1996 PST | 1996 | 2 | 29 | 17 | 32 | 1
+ Fri Mar 01 17:32:01 1996 PST | 1996 | 3 | 1 | 17 | 32 | 1
+ Mon Dec 30 17:32:01 1996 PST | 1996 | 12 | 30 | 17 | 32 | 1
+ Tue Dec 31 17:32:01 1996 PST | 1996 | 12 | 31 | 17 | 32 | 1
+ Wed Jan 01 17:32:01 1997 PST | 1997 | 1 | 1 | 17 | 32 | 1
+ Fri Feb 28 17:32:01 1997 PST | 1997 | 2 | 28 | 17 | 32 | 1
+ Sat Mar 01 17:32:01 1997 PST | 1997 | 3 | 1 | 17 | 32 | 1
+ Tue Dec 30 17:32:01 1997 PST | 1997 | 12 | 30 | 17 | 32 | 1
+ Wed Dec 31 17:32:01 1997 PST | 1997 | 12 | 31 | 17 | 32 | 1
+ Fri Dec 31 17:32:01 1999 PST | 1999 | 12 | 31 | 17 | 32 | 1
+ Sat Jan 01 17:32:01 2000 PST | 2000 | 1 | 1 | 17 | 32 | 1
+ Sun Dec 31 17:32:01 2000 PST | 2000 | 12 | 31 | 17 | 32 | 1
+ Mon Jan 01 17:32:01 2001 PST | 2001 | 1 | 1 | 17 | 32 | 1
+(66 rows)
+
+SELECT d1 as timestamptz,
+ date_part( 'quarter', d1) AS quarter, date_part( 'msec', d1) AS msec,
+ date_part( 'usec', d1) AS usec
+ FROM TIMESTAMPTZ_TBL;
+ timestamptz | quarter | msec | usec
+---------------------------------+---------+-------+----------
+ -infinity | | |
+ infinity | | |
+ Wed Dec 31 16:00:00 1969 PST | 4 | 0 | 0
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:02 1997 PST | 1 | 2000 | 2000000
+ Mon Feb 10 17:32:01.4 1997 PST | 1 | 1400 | 1400000
+ Mon Feb 10 17:32:01.5 1997 PST | 1 | 1500 | 1500000
+ Mon Feb 10 17:32:01.6 1997 PST | 1 | 1600 | 1600000
+ Thu Jan 02 00:00:00 1997 PST | 1 | 0 | 0
+ Thu Jan 02 03:04:05 1997 PST | 1 | 5000 | 5000000
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Tue Jun 10 17:32:01 1997 PDT | 2 | 1000 | 1000000
+ Sat Sep 22 18:19:20 2001 PDT | 3 | 20000 | 20000000
+ Wed Mar 15 08:14:01 2000 PST | 1 | 1000 | 1000000
+ Wed Mar 15 04:14:02 2000 PST | 1 | 2000 | 2000000
+ Wed Mar 15 02:14:03 2000 PST | 1 | 3000 | 3000000
+ Wed Mar 15 03:14:04 2000 PST | 1 | 4000 | 4000000
+ Wed Mar 15 01:14:05 2000 PST | 1 | 5000 | 5000000
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:00 1997 PST | 1 | 0 | 0
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 09:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 09:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 09:32:01 1997 PST | 1 | 1000 | 1000000
+ Mon Feb 10 14:32:01 1997 PST | 1 | 1000 | 1000000
+ Thu Jul 10 14:32:01 1997 PDT | 3 | 1000 | 1000000
+ Tue Jun 10 18:32:01 1997 PDT | 2 | 1000 | 1000000
+ Mon Feb 10 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Tue Feb 11 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Wed Feb 12 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Thu Feb 13 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Fri Feb 14 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Sat Feb 15 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Sun Feb 16 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Tue Feb 16 17:32:01 0097 PST BC | 1 | 1000 | 1000000
+ Sat Feb 16 17:32:01 0097 PST | 1 | 1000 | 1000000
+ Thu Feb 16 17:32:01 0597 PST | 1 | 1000 | 1000000
+ Tue Feb 16 17:32:01 1097 PST | 1 | 1000 | 1000000
+ Sat Feb 16 17:32:01 1697 PST | 1 | 1000 | 1000000
+ Thu Feb 16 17:32:01 1797 PST | 1 | 1000 | 1000000
+ Tue Feb 16 17:32:01 1897 PST | 1 | 1000 | 1000000
+ Sun Feb 16 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Sat Feb 16 17:32:01 2097 PST | 1 | 1000 | 1000000
+ Wed Feb 28 17:32:01 1996 PST | 1 | 1000 | 1000000
+ Thu Feb 29 17:32:01 1996 PST | 1 | 1000 | 1000000
+ Fri Mar 01 17:32:01 1996 PST | 1 | 1000 | 1000000
+ Mon Dec 30 17:32:01 1996 PST | 4 | 1000 | 1000000
+ Tue Dec 31 17:32:01 1996 PST | 4 | 1000 | 1000000
+ Wed Jan 01 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Fri Feb 28 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Sat Mar 01 17:32:01 1997 PST | 1 | 1000 | 1000000
+ Tue Dec 30 17:32:01 1997 PST | 4 | 1000 | 1000000
+ Wed Dec 31 17:32:01 1997 PST | 4 | 1000 | 1000000
+ Fri Dec 31 17:32:01 1999 PST | 4 | 1000 | 1000000
+ Sat Jan 01 17:32:01 2000 PST | 1 | 1000 | 1000000
+ Sun Dec 31 17:32:01 2000 PST | 4 | 1000 | 1000000
+ Mon Jan 01 17:32:01 2001 PST | 1 | 1000 | 1000000
+(66 rows)
+
+SELECT d1 as timestamptz,
+ date_part( 'isoyear', d1) AS isoyear, date_part( 'week', d1) AS week,
+ date_part( 'isodow', d1) AS isodow, date_part( 'dow', d1) AS dow,
+ date_part( 'doy', d1) AS doy
+ FROM TIMESTAMPTZ_TBL;
+ timestamptz | isoyear | week | isodow | dow | doy
+---------------------------------+-----------+------+--------+-----+-----
+ -infinity | -Infinity | | | |
+ infinity | Infinity | | | |
+ Wed Dec 31 16:00:00 1969 PST | 1970 | 1 | 3 | 3 | 365
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:02 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01.4 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01.5 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01.6 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Thu Jan 02 00:00:00 1997 PST | 1997 | 1 | 4 | 4 | 2
+ Thu Jan 02 03:04:05 1997 PST | 1997 | 1 | 4 | 4 | 2
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Tue Jun 10 17:32:01 1997 PDT | 1997 | 24 | 2 | 2 | 161
+ Sat Sep 22 18:19:20 2001 PDT | 2001 | 38 | 6 | 6 | 265
+ Wed Mar 15 08:14:01 2000 PST | 2000 | 11 | 3 | 3 | 75
+ Wed Mar 15 04:14:02 2000 PST | 2000 | 11 | 3 | 3 | 75
+ Wed Mar 15 02:14:03 2000 PST | 2000 | 11 | 3 | 3 | 75
+ Wed Mar 15 03:14:04 2000 PST | 2000 | 11 | 3 | 3 | 75
+ Wed Mar 15 01:14:05 2000 PST | 2000 | 11 | 3 | 3 | 75
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:00 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 09:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 09:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 09:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Mon Feb 10 14:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Thu Jul 10 14:32:01 1997 PDT | 1997 | 28 | 4 | 4 | 191
+ Tue Jun 10 18:32:01 1997 PDT | 1997 | 24 | 2 | 2 | 161
+ Mon Feb 10 17:32:01 1997 PST | 1997 | 7 | 1 | 1 | 41
+ Tue Feb 11 17:32:01 1997 PST | 1997 | 7 | 2 | 2 | 42
+ Wed Feb 12 17:32:01 1997 PST | 1997 | 7 | 3 | 3 | 43
+ Thu Feb 13 17:32:01 1997 PST | 1997 | 7 | 4 | 4 | 44
+ Fri Feb 14 17:32:01 1997 PST | 1997 | 7 | 5 | 5 | 45
+ Sat Feb 15 17:32:01 1997 PST | 1997 | 7 | 6 | 6 | 46
+ Sun Feb 16 17:32:01 1997 PST | 1997 | 7 | 7 | 0 | 47
+ Tue Feb 16 17:32:01 0097 PST BC | -97 | 7 | 2 | 2 | 47
+ Sat Feb 16 17:32:01 0097 PST | 97 | 7 | 6 | 6 | 47
+ Thu Feb 16 17:32:01 0597 PST | 597 | 7 | 4 | 4 | 47
+ Tue Feb 16 17:32:01 1097 PST | 1097 | 7 | 2 | 2 | 47
+ Sat Feb 16 17:32:01 1697 PST | 1697 | 7 | 6 | 6 | 47
+ Thu Feb 16 17:32:01 1797 PST | 1797 | 7 | 4 | 4 | 47
+ Tue Feb 16 17:32:01 1897 PST | 1897 | 7 | 2 | 2 | 47
+ Sun Feb 16 17:32:01 1997 PST | 1997 | 7 | 7 | 0 | 47
+ Sat Feb 16 17:32:01 2097 PST | 2097 | 7 | 6 | 6 | 47
+ Wed Feb 28 17:32:01 1996 PST | 1996 | 9 | 3 | 3 | 59
+ Thu Feb 29 17:32:01 1996 PST | 1996 | 9 | 4 | 4 | 60
+ Fri Mar 01 17:32:01 1996 PST | 1996 | 9 | 5 | 5 | 61
+ Mon Dec 30 17:32:01 1996 PST | 1997 | 1 | 1 | 1 | 365
+ Tue Dec 31 17:32:01 1996 PST | 1997 | 1 | 2 | 2 | 366
+ Wed Jan 01 17:32:01 1997 PST | 1997 | 1 | 3 | 3 | 1
+ Fri Feb 28 17:32:01 1997 PST | 1997 | 9 | 5 | 5 | 59
+ Sat Mar 01 17:32:01 1997 PST | 1997 | 9 | 6 | 6 | 60
+ Tue Dec 30 17:32:01 1997 PST | 1998 | 1 | 2 | 2 | 364
+ Wed Dec 31 17:32:01 1997 PST | 1998 | 1 | 3 | 3 | 365
+ Fri Dec 31 17:32:01 1999 PST | 1999 | 52 | 5 | 5 | 365
+ Sat Jan 01 17:32:01 2000 PST | 1999 | 52 | 6 | 6 | 1
+ Sun Dec 31 17:32:01 2000 PST | 2000 | 52 | 7 | 0 | 366
+ Mon Jan 01 17:32:01 2001 PST | 2001 | 1 | 1 | 1 | 1
+(66 rows)
+
+SELECT d1 as timestamptz,
+ date_part( 'decade', d1) AS decade,
+ date_part( 'century', d1) AS century,
+ date_part( 'millennium', d1) AS millennium,
+ round(date_part( 'julian', d1)) AS julian,
+ date_part( 'epoch', d1) AS epoch
+ FROM TIMESTAMPTZ_TBL;
+ timestamptz | decade | century | millennium | julian | epoch
+---------------------------------+-----------+-----------+------------+-----------+--------------
+ -infinity | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+ infinity | Infinity | Infinity | Infinity | Infinity | Infinity
+ Wed Dec 31 16:00:00 1969 PST | 196 | 20 | 2 | 2440588 | 0
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Mon Feb 10 17:32:02 1997 PST | 199 | 20 | 2 | 2450491 | 855624722
+ Mon Feb 10 17:32:01.4 1997 PST | 199 | 20 | 2 | 2450491 | 855624721.4
+ Mon Feb 10 17:32:01.5 1997 PST | 199 | 20 | 2 | 2450491 | 855624721.5
+ Mon Feb 10 17:32:01.6 1997 PST | 199 | 20 | 2 | 2450491 | 855624721.6
+ Thu Jan 02 00:00:00 1997 PST | 199 | 20 | 2 | 2450451 | 852192000
+ Thu Jan 02 03:04:05 1997 PST | 199 | 20 | 2 | 2450451 | 852203045
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Tue Jun 10 17:32:01 1997 PDT | 199 | 20 | 2 | 2450611 | 865989121
+ Sat Sep 22 18:19:20 2001 PDT | 200 | 21 | 3 | 2452176 | 1001207960
+ Wed Mar 15 08:14:01 2000 PST | 200 | 20 | 2 | 2451619 | 953136841
+ Wed Mar 15 04:14:02 2000 PST | 200 | 20 | 2 | 2451619 | 953122442
+ Wed Mar 15 02:14:03 2000 PST | 200 | 20 | 2 | 2451619 | 953115243
+ Wed Mar 15 03:14:04 2000 PST | 200 | 20 | 2 | 2451619 | 953118844
+ Wed Mar 15 01:14:05 2000 PST | 200 | 20 | 2 | 2451619 | 953111645
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Mon Feb 10 17:32:00 1997 PST | 199 | 20 | 2 | 2450491 | 855624720
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Mon Feb 10 09:32:01 1997 PST | 199 | 20 | 2 | 2450490 | 855595921
+ Mon Feb 10 09:32:01 1997 PST | 199 | 20 | 2 | 2450490 | 855595921
+ Mon Feb 10 09:32:01 1997 PST | 199 | 20 | 2 | 2450490 | 855595921
+ Mon Feb 10 14:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855613921
+ Thu Jul 10 14:32:01 1997 PDT | 199 | 20 | 2 | 2450641 | 868570321
+ Tue Jun 10 18:32:01 1997 PDT | 199 | 20 | 2 | 2450611 | 865992721
+ Mon Feb 10 17:32:01 1997 PST | 199 | 20 | 2 | 2450491 | 855624721
+ Tue Feb 11 17:32:01 1997 PST | 199 | 20 | 2 | 2450492 | 855711121
+ Wed Feb 12 17:32:01 1997 PST | 199 | 20 | 2 | 2450493 | 855797521
+ Thu Feb 13 17:32:01 1997 PST | 199 | 20 | 2 | 2450494 | 855883921
+ Fri Feb 14 17:32:01 1997 PST | 199 | 20 | 2 | 2450495 | 855970321
+ Sat Feb 15 17:32:01 1997 PST | 199 | 20 | 2 | 2450496 | 856056721
+ Sun Feb 16 17:32:01 1997 PST | 199 | 20 | 2 | 2450497 | 856143121
+ Tue Feb 16 17:32:01 0097 PST BC | -10 | -1 | -1 | 1686043 | -65192682479
+ Sat Feb 16 17:32:01 0097 PST | 9 | 1 | 1 | 1756537 | -59102000879
+ Thu Feb 16 17:32:01 0597 PST | 59 | 6 | 1 | 1939158 | -43323546479
+ Tue Feb 16 17:32:01 1097 PST | 109 | 11 | 2 | 2121779 | -27545092079
+ Sat Feb 16 17:32:01 1697 PST | 169 | 17 | 2 | 2340925 | -8610877679
+ Thu Feb 16 17:32:01 1797 PST | 179 | 18 | 2 | 2377449 | -5455204079
+ Tue Feb 16 17:32:01 1897 PST | 189 | 19 | 2 | 2413973 | -2299530479
+ Sun Feb 16 17:32:01 1997 PST | 199 | 20 | 2 | 2450497 | 856143121
+ Sat Feb 16 17:32:01 2097 PST | 209 | 21 | 3 | 2487022 | 4011903121
+ Wed Feb 28 17:32:01 1996 PST | 199 | 20 | 2 | 2450143 | 825557521
+ Thu Feb 29 17:32:01 1996 PST | 199 | 20 | 2 | 2450144 | 825643921
+ Fri Mar 01 17:32:01 1996 PST | 199 | 20 | 2 | 2450145 | 825730321
+ Mon Dec 30 17:32:01 1996 PST | 199 | 20 | 2 | 2450449 | 851995921
+ Tue Dec 31 17:32:01 1996 PST | 199 | 20 | 2 | 2450450 | 852082321
+ Wed Jan 01 17:32:01 1997 PST | 199 | 20 | 2 | 2450451 | 852168721
+ Fri Feb 28 17:32:01 1997 PST | 199 | 20 | 2 | 2450509 | 857179921
+ Sat Mar 01 17:32:01 1997 PST | 199 | 20 | 2 | 2450510 | 857266321
+ Tue Dec 30 17:32:01 1997 PST | 199 | 20 | 2 | 2450814 | 883531921
+ Wed Dec 31 17:32:01 1997 PST | 199 | 20 | 2 | 2450815 | 883618321
+ Fri Dec 31 17:32:01 1999 PST | 199 | 20 | 2 | 2451545 | 946690321
+ Sat Jan 01 17:32:01 2000 PST | 200 | 20 | 2 | 2451546 | 946776721
+ Sun Dec 31 17:32:01 2000 PST | 200 | 20 | 2 | 2451911 | 978312721
+ Mon Jan 01 17:32:01 2001 PST | 200 | 21 | 3 | 2451912 | 978399121
+(66 rows)
+
+SELECT d1 as timestamptz,
+ date_part( 'timezone', d1) AS timezone,
+ date_part( 'timezone_hour', d1) AS timezone_hour,
+ date_part( 'timezone_minute', d1) AS timezone_minute
+ FROM TIMESTAMPTZ_TBL;
+ timestamptz | timezone | timezone_hour | timezone_minute
+---------------------------------+----------+---------------+-----------------
+ -infinity | | |
+ infinity | | |
+ Wed Dec 31 16:00:00 1969 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:02 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01.4 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01.5 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01.6 1997 PST | -28800 | -8 | 0
+ Thu Jan 02 00:00:00 1997 PST | -28800 | -8 | 0
+ Thu Jan 02 03:04:05 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Tue Jun 10 17:32:01 1997 PDT | -25200 | -7 | 0
+ Sat Sep 22 18:19:20 2001 PDT | -25200 | -7 | 0
+ Wed Mar 15 08:14:01 2000 PST | -28800 | -8 | 0
+ Wed Mar 15 04:14:02 2000 PST | -28800 | -8 | 0
+ Wed Mar 15 02:14:03 2000 PST | -28800 | -8 | 0
+ Wed Mar 15 03:14:04 2000 PST | -28800 | -8 | 0
+ Wed Mar 15 01:14:05 2000 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:00 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 09:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 09:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 09:32:01 1997 PST | -28800 | -8 | 0
+ Mon Feb 10 14:32:01 1997 PST | -28800 | -8 | 0
+ Thu Jul 10 14:32:01 1997 PDT | -25200 | -7 | 0
+ Tue Jun 10 18:32:01 1997 PDT | -25200 | -7 | 0
+ Mon Feb 10 17:32:01 1997 PST | -28800 | -8 | 0
+ Tue Feb 11 17:32:01 1997 PST | -28800 | -8 | 0
+ Wed Feb 12 17:32:01 1997 PST | -28800 | -8 | 0
+ Thu Feb 13 17:32:01 1997 PST | -28800 | -8 | 0
+ Fri Feb 14 17:32:01 1997 PST | -28800 | -8 | 0
+ Sat Feb 15 17:32:01 1997 PST | -28800 | -8 | 0
+ Sun Feb 16 17:32:01 1997 PST | -28800 | -8 | 0
+ Tue Feb 16 17:32:01 0097 PST BC | -28800 | -8 | 0
+ Sat Feb 16 17:32:01 0097 PST | -28800 | -8 | 0
+ Thu Feb 16 17:32:01 0597 PST | -28800 | -8 | 0
+ Tue Feb 16 17:32:01 1097 PST | -28800 | -8 | 0
+ Sat Feb 16 17:32:01 1697 PST | -28800 | -8 | 0
+ Thu Feb 16 17:32:01 1797 PST | -28800 | -8 | 0
+ Tue Feb 16 17:32:01 1897 PST | -28800 | -8 | 0
+ Sun Feb 16 17:32:01 1997 PST | -28800 | -8 | 0
+ Sat Feb 16 17:32:01 2097 PST | -28800 | -8 | 0
+ Wed Feb 28 17:32:01 1996 PST | -28800 | -8 | 0
+ Thu Feb 29 17:32:01 1996 PST | -28800 | -8 | 0
+ Fri Mar 01 17:32:01 1996 PST | -28800 | -8 | 0
+ Mon Dec 30 17:32:01 1996 PST | -28800 | -8 | 0
+ Tue Dec 31 17:32:01 1996 PST | -28800 | -8 | 0
+ Wed Jan 01 17:32:01 1997 PST | -28800 | -8 | 0
+ Fri Feb 28 17:32:01 1997 PST | -28800 | -8 | 0
+ Sat Mar 01 17:32:01 1997 PST | -28800 | -8 | 0
+ Tue Dec 30 17:32:01 1997 PST | -28800 | -8 | 0
+ Wed Dec 31 17:32:01 1997 PST | -28800 | -8 | 0
+ Fri Dec 31 17:32:01 1999 PST | -28800 | -8 | 0
+ Sat Jan 01 17:32:01 2000 PST | -28800 | -8 | 0
+ Sun Dec 31 17:32:01 2000 PST | -28800 | -8 | 0
+ Mon Jan 01 17:32:01 2001 PST | -28800 | -8 | 0
+(66 rows)
+
+-- extract implementation is mostly the same as date_part, so only
+-- test a few cases for additional coverage.
+SELECT d1 as "timestamp",
+ extract(microseconds from d1) AS microseconds,
+ extract(milliseconds from d1) AS milliseconds,
+ extract(seconds from d1) AS seconds,
+ round(extract(julian from d1)) AS julian,
+ extract(epoch from d1) AS epoch
+ FROM TIMESTAMPTZ_TBL;
+ timestamp | microseconds | milliseconds | seconds | julian | epoch
+---------------------------------+--------------+--------------+-----------+-----------+---------------------
+ -infinity | | | | -Infinity | -Infinity
+ infinity | | | | Infinity | Infinity
+ Wed Dec 31 16:00:00 1969 PST | 0 | 0.000 | 0.000000 | 2440588 | 0.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Mon Feb 10 17:32:02 1997 PST | 2000000 | 2000.000 | 2.000000 | 2450491 | 855624722.000000
+ Mon Feb 10 17:32:01.4 1997 PST | 1400000 | 1400.000 | 1.400000 | 2450491 | 855624721.400000
+ Mon Feb 10 17:32:01.5 1997 PST | 1500000 | 1500.000 | 1.500000 | 2450491 | 855624721.500000
+ Mon Feb 10 17:32:01.6 1997 PST | 1600000 | 1600.000 | 1.600000 | 2450491 | 855624721.600000
+ Thu Jan 02 00:00:00 1997 PST | 0 | 0.000 | 0.000000 | 2450451 | 852192000.000000
+ Thu Jan 02 03:04:05 1997 PST | 5000000 | 5000.000 | 5.000000 | 2450451 | 852203045.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Tue Jun 10 17:32:01 1997 PDT | 1000000 | 1000.000 | 1.000000 | 2450611 | 865989121.000000
+ Sat Sep 22 18:19:20 2001 PDT | 20000000 | 20000.000 | 20.000000 | 2452176 | 1001207960.000000
+ Wed Mar 15 08:14:01 2000 PST | 1000000 | 1000.000 | 1.000000 | 2451619 | 953136841.000000
+ Wed Mar 15 04:14:02 2000 PST | 2000000 | 2000.000 | 2.000000 | 2451619 | 953122442.000000
+ Wed Mar 15 02:14:03 2000 PST | 3000000 | 3000.000 | 3.000000 | 2451619 | 953115243.000000
+ Wed Mar 15 03:14:04 2000 PST | 4000000 | 4000.000 | 4.000000 | 2451619 | 953118844.000000
+ Wed Mar 15 01:14:05 2000 PST | 5000000 | 5000.000 | 5.000000 | 2451619 | 953111645.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Mon Feb 10 17:32:00 1997 PST | 0 | 0.000 | 0.000000 | 2450491 | 855624720.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Mon Feb 10 09:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450490 | 855595921.000000
+ Mon Feb 10 09:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450490 | 855595921.000000
+ Mon Feb 10 09:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450490 | 855595921.000000
+ Mon Feb 10 14:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855613921.000000
+ Thu Jul 10 14:32:01 1997 PDT | 1000000 | 1000.000 | 1.000000 | 2450641 | 868570321.000000
+ Tue Jun 10 18:32:01 1997 PDT | 1000000 | 1000.000 | 1.000000 | 2450611 | 865992721.000000
+ Mon Feb 10 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450491 | 855624721.000000
+ Tue Feb 11 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450492 | 855711121.000000
+ Wed Feb 12 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450493 | 855797521.000000
+ Thu Feb 13 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450494 | 855883921.000000
+ Fri Feb 14 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450495 | 855970321.000000
+ Sat Feb 15 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450496 | 856056721.000000
+ Sun Feb 16 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450497 | 856143121.000000
+ Tue Feb 16 17:32:01 0097 PST BC | 1000000 | 1000.000 | 1.000000 | 1686043 | -65192682479.000000
+ Sat Feb 16 17:32:01 0097 PST | 1000000 | 1000.000 | 1.000000 | 1756537 | -59102000879.000000
+ Thu Feb 16 17:32:01 0597 PST | 1000000 | 1000.000 | 1.000000 | 1939158 | -43323546479.000000
+ Tue Feb 16 17:32:01 1097 PST | 1000000 | 1000.000 | 1.000000 | 2121779 | -27545092079.000000
+ Sat Feb 16 17:32:01 1697 PST | 1000000 | 1000.000 | 1.000000 | 2340925 | -8610877679.000000
+ Thu Feb 16 17:32:01 1797 PST | 1000000 | 1000.000 | 1.000000 | 2377449 | -5455204079.000000
+ Tue Feb 16 17:32:01 1897 PST | 1000000 | 1000.000 | 1.000000 | 2413973 | -2299530479.000000
+ Sun Feb 16 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450497 | 856143121.000000
+ Sat Feb 16 17:32:01 2097 PST | 1000000 | 1000.000 | 1.000000 | 2487022 | 4011903121.000000
+ Wed Feb 28 17:32:01 1996 PST | 1000000 | 1000.000 | 1.000000 | 2450143 | 825557521.000000
+ Thu Feb 29 17:32:01 1996 PST | 1000000 | 1000.000 | 1.000000 | 2450144 | 825643921.000000
+ Fri Mar 01 17:32:01 1996 PST | 1000000 | 1000.000 | 1.000000 | 2450145 | 825730321.000000
+ Mon Dec 30 17:32:01 1996 PST | 1000000 | 1000.000 | 1.000000 | 2450449 | 851995921.000000
+ Tue Dec 31 17:32:01 1996 PST | 1000000 | 1000.000 | 1.000000 | 2450450 | 852082321.000000
+ Wed Jan 01 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450451 | 852168721.000000
+ Fri Feb 28 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450509 | 857179921.000000
+ Sat Mar 01 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450510 | 857266321.000000
+ Tue Dec 30 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450814 | 883531921.000000
+ Wed Dec 31 17:32:01 1997 PST | 1000000 | 1000.000 | 1.000000 | 2450815 | 883618321.000000
+ Fri Dec 31 17:32:01 1999 PST | 1000000 | 1000.000 | 1.000000 | 2451545 | 946690321.000000
+ Sat Jan 01 17:32:01 2000 PST | 1000000 | 1000.000 | 1.000000 | 2451546 | 946776721.000000
+ Sun Dec 31 17:32:01 2000 PST | 1000000 | 1000.000 | 1.000000 | 2451911 | 978312721.000000
+ Mon Jan 01 17:32:01 2001 PST | 1000000 | 1000.000 | 1.000000 | 2451912 | 978399121.000000
+(66 rows)
+
+-- value near upper bound uses special case in code
+SELECT date_part('epoch', '294270-01-01 00:00:00+00'::timestamptz);
+ date_part
+---------------
+ 9224097091200
+(1 row)
+
+SELECT extract(epoch from '294270-01-01 00:00:00+00'::timestamptz);
+ extract
+----------------------
+ 9224097091200.000000
+(1 row)
+
+-- another internal overflow test case
+SELECT extract(epoch from '5000-01-01 00:00:00+00'::timestamptz);
+ extract
+--------------------
+ 95617584000.000000
+(1 row)
+
+-- TO_CHAR()
+SELECT to_char(d1, 'DAY Day day DY Dy dy MONTH Month month RM MON Mon mon')
+ FROM TIMESTAMPTZ_TBL;
+ to_char
+------------------------------------------------------------------------------------------
+
+
+ WEDNESDAY Wednesday wednesday WED Wed wed DECEMBER December december XII DEC Dec dec
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ THURSDAY Thursday thursday THU Thu thu JANUARY January january I JAN Jan jan
+ THURSDAY Thursday thursday THU Thu thu JANUARY January january I JAN Jan jan
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ TUESDAY Tuesday tuesday TUE Tue tue JUNE June june VI JUN Jun jun
+ SATURDAY Saturday saturday SAT Sat sat SEPTEMBER September september IX SEP Sep sep
+ WEDNESDAY Wednesday wednesday WED Wed wed MARCH March march III MAR Mar mar
+ WEDNESDAY Wednesday wednesday WED Wed wed MARCH March march III MAR Mar mar
+ WEDNESDAY Wednesday wednesday WED Wed wed MARCH March march III MAR Mar mar
+ WEDNESDAY Wednesday wednesday WED Wed wed MARCH March march III MAR Mar mar
+ WEDNESDAY Wednesday wednesday WED Wed wed MARCH March march III MAR Mar mar
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ THURSDAY Thursday thursday THU Thu thu JULY July july VII JUL Jul jul
+ TUESDAY Tuesday tuesday TUE Tue tue JUNE June june VI JUN Jun jun
+ MONDAY Monday monday MON Mon mon FEBRUARY February february II FEB Feb feb
+ TUESDAY Tuesday tuesday TUE Tue tue FEBRUARY February february II FEB Feb feb
+ WEDNESDAY Wednesday wednesday WED Wed wed FEBRUARY February february II FEB Feb feb
+ THURSDAY Thursday thursday THU Thu thu FEBRUARY February february II FEB Feb feb
+ FRIDAY Friday friday FRI Fri fri FEBRUARY February february II FEB Feb feb
+ SATURDAY Saturday saturday SAT Sat sat FEBRUARY February february II FEB Feb feb
+ SUNDAY Sunday sunday SUN Sun sun FEBRUARY February february II FEB Feb feb
+ TUESDAY Tuesday tuesday TUE Tue tue FEBRUARY February february II FEB Feb feb
+ SATURDAY Saturday saturday SAT Sat sat FEBRUARY February february II FEB Feb feb
+ THURSDAY Thursday thursday THU Thu thu FEBRUARY February february II FEB Feb feb
+ TUESDAY Tuesday tuesday TUE Tue tue FEBRUARY February february II FEB Feb feb
+ SATURDAY Saturday saturday SAT Sat sat FEBRUARY February february II FEB Feb feb
+ THURSDAY Thursday thursday THU Thu thu FEBRUARY February february II FEB Feb feb
+ TUESDAY Tuesday tuesday TUE Tue tue FEBRUARY February february II FEB Feb feb
+ SUNDAY Sunday sunday SUN Sun sun FEBRUARY February february II FEB Feb feb
+ SATURDAY Saturday saturday SAT Sat sat FEBRUARY February february II FEB Feb feb
+ WEDNESDAY Wednesday wednesday WED Wed wed FEBRUARY February february II FEB Feb feb
+ THURSDAY Thursday thursday THU Thu thu FEBRUARY February february II FEB Feb feb
+ FRIDAY Friday friday FRI Fri fri MARCH March march III MAR Mar mar
+ MONDAY Monday monday MON Mon mon DECEMBER December december XII DEC Dec dec
+ TUESDAY Tuesday tuesday TUE Tue tue DECEMBER December december XII DEC Dec dec
+ WEDNESDAY Wednesday wednesday WED Wed wed JANUARY January january I JAN Jan jan
+ FRIDAY Friday friday FRI Fri fri FEBRUARY February february II FEB Feb feb
+ SATURDAY Saturday saturday SAT Sat sat MARCH March march III MAR Mar mar
+ TUESDAY Tuesday tuesday TUE Tue tue DECEMBER December december XII DEC Dec dec
+ WEDNESDAY Wednesday wednesday WED Wed wed DECEMBER December december XII DEC Dec dec
+ FRIDAY Friday friday FRI Fri fri DECEMBER December december XII DEC Dec dec
+ SATURDAY Saturday saturday SAT Sat sat JANUARY January january I JAN Jan jan
+ SUNDAY Sunday sunday SUN Sun sun DECEMBER December december XII DEC Dec dec
+ MONDAY Monday monday MON Mon mon JANUARY January january I JAN Jan jan
+(66 rows)
+
+SELECT to_char(d1, 'FMDAY FMDay FMday FMMONTH FMMonth FMmonth FMRM')
+ FROM TIMESTAMPTZ_TBL;
+ to_char
+--------------------------------------------------------------
+
+
+ WEDNESDAY Wednesday wednesday DECEMBER December december XII
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ THURSDAY Thursday thursday JANUARY January january I
+ THURSDAY Thursday thursday JANUARY January january I
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ TUESDAY Tuesday tuesday JUNE June june VI
+ SATURDAY Saturday saturday SEPTEMBER September september IX
+ WEDNESDAY Wednesday wednesday MARCH March march III
+ WEDNESDAY Wednesday wednesday MARCH March march III
+ WEDNESDAY Wednesday wednesday MARCH March march III
+ WEDNESDAY Wednesday wednesday MARCH March march III
+ WEDNESDAY Wednesday wednesday MARCH March march III
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ MONDAY Monday monday FEBRUARY February february II
+ THURSDAY Thursday thursday JULY July july VII
+ TUESDAY Tuesday tuesday JUNE June june VI
+ MONDAY Monday monday FEBRUARY February february II
+ TUESDAY Tuesday tuesday FEBRUARY February february II
+ WEDNESDAY Wednesday wednesday FEBRUARY February february II
+ THURSDAY Thursday thursday FEBRUARY February february II
+ FRIDAY Friday friday FEBRUARY February february II
+ SATURDAY Saturday saturday FEBRUARY February february II
+ SUNDAY Sunday sunday FEBRUARY February february II
+ TUESDAY Tuesday tuesday FEBRUARY February february II
+ SATURDAY Saturday saturday FEBRUARY February february II
+ THURSDAY Thursday thursday FEBRUARY February february II
+ TUESDAY Tuesday tuesday FEBRUARY February february II
+ SATURDAY Saturday saturday FEBRUARY February february II
+ THURSDAY Thursday thursday FEBRUARY February february II
+ TUESDAY Tuesday tuesday FEBRUARY February february II
+ SUNDAY Sunday sunday FEBRUARY February february II
+ SATURDAY Saturday saturday FEBRUARY February february II
+ WEDNESDAY Wednesday wednesday FEBRUARY February february II
+ THURSDAY Thursday thursday FEBRUARY February february II
+ FRIDAY Friday friday MARCH March march III
+ MONDAY Monday monday DECEMBER December december XII
+ TUESDAY Tuesday tuesday DECEMBER December december XII
+ WEDNESDAY Wednesday wednesday JANUARY January january I
+ FRIDAY Friday friday FEBRUARY February february II
+ SATURDAY Saturday saturday MARCH March march III
+ TUESDAY Tuesday tuesday DECEMBER December december XII
+ WEDNESDAY Wednesday wednesday DECEMBER December december XII
+ FRIDAY Friday friday DECEMBER December december XII
+ SATURDAY Saturday saturday JANUARY January january I
+ SUNDAY Sunday sunday DECEMBER December december XII
+ MONDAY Monday monday JANUARY January january I
+(66 rows)
+
+SELECT to_char(d1, 'Y,YYY YYYY YYY YY Y CC Q MM WW DDD DD D J')
+ FROM TIMESTAMPTZ_TBL;
+ to_char
+--------------------------------------------------
+
+
+ 1,969 1969 969 69 9 20 4 12 53 365 31 4 2440587
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 01 01 002 02 5 2450451
+ 1,997 1997 997 97 7 20 1 01 01 002 02 5 2450451
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 2 06 23 161 10 3 2450610
+ 2,001 2001 001 01 1 21 3 09 38 265 22 7 2452175
+ 2,000 2000 000 00 0 20 1 03 11 075 15 4 2451619
+ 2,000 2000 000 00 0 20 1 03 11 075 15 4 2451619
+ 2,000 2000 000 00 0 20 1 03 11 075 15 4 2451619
+ 2,000 2000 000 00 0 20 1 03 11 075 15 4 2451619
+ 2,000 2000 000 00 0 20 1 03 11 075 15 4 2451619
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 3 07 28 191 10 5 2450640
+ 1,997 1997 997 97 7 20 2 06 23 161 10 3 2450610
+ 1,997 1997 997 97 7 20 1 02 06 041 10 2 2450490
+ 1,997 1997 997 97 7 20 1 02 06 042 11 3 2450491
+ 1,997 1997 997 97 7 20 1 02 07 043 12 4 2450492
+ 1,997 1997 997 97 7 20 1 02 07 044 13 5 2450493
+ 1,997 1997 997 97 7 20 1 02 07 045 14 6 2450494
+ 1,997 1997 997 97 7 20 1 02 07 046 15 7 2450495
+ 1,997 1997 997 97 7 20 1 02 07 047 16 1 2450496
+ 0,097 0097 097 97 7 -01 1 02 07 047 16 3 1686042
+ 0,097 0097 097 97 7 01 1 02 07 047 16 7 1756536
+ 0,597 0597 597 97 7 06 1 02 07 047 16 5 1939157
+ 1,097 1097 097 97 7 11 1 02 07 047 16 3 2121778
+ 1,697 1697 697 97 7 17 1 02 07 047 16 7 2340924
+ 1,797 1797 797 97 7 18 1 02 07 047 16 5 2377448
+ 1,897 1897 897 97 7 19 1 02 07 047 16 3 2413972
+ 1,997 1997 997 97 7 20 1 02 07 047 16 1 2450496
+ 2,097 2097 097 97 7 21 1 02 07 047 16 7 2487021
+ 1,996 1996 996 96 6 20 1 02 09 059 28 4 2450142
+ 1,996 1996 996 96 6 20 1 02 09 060 29 5 2450143
+ 1,996 1996 996 96 6 20 1 03 09 061 01 6 2450144
+ 1,996 1996 996 96 6 20 4 12 53 365 30 2 2450448
+ 1,996 1996 996 96 6 20 4 12 53 366 31 3 2450449
+ 1,997 1997 997 97 7 20 1 01 01 001 01 4 2450450
+ 1,997 1997 997 97 7 20 1 02 09 059 28 6 2450508
+ 1,997 1997 997 97 7 20 1 03 09 060 01 7 2450509
+ 1,997 1997 997 97 7 20 4 12 52 364 30 3 2450813
+ 1,997 1997 997 97 7 20 4 12 53 365 31 4 2450814
+ 1,999 1999 999 99 9 20 4 12 53 365 31 6 2451544
+ 2,000 2000 000 00 0 20 1 01 01 001 01 7 2451545
+ 2,000 2000 000 00 0 20 4 12 53 366 31 1 2451910
+ 2,001 2001 001 01 1 21 1 01 01 001 01 2 2451911
+(66 rows)
+
+SELECT to_char(d1, 'FMY,YYY FMYYYY FMYYY FMYY FMY FMCC FMQ FMMM FMWW FMDDD FMDD FMD FMJ')
+ FROM TIMESTAMPTZ_TBL;
+ to_char
+-------------------------------------------------
+
+
+ 1,969 1969 969 69 9 20 4 12 53 365 31 4 2440587
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 1 1 2 2 5 2450451
+ 1,997 1997 997 97 7 20 1 1 1 2 2 5 2450451
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 2 6 23 161 10 3 2450610
+ 2,001 2001 1 1 1 21 3 9 38 265 22 7 2452175
+ 2,000 2000 0 0 0 20 1 3 11 75 15 4 2451619
+ 2,000 2000 0 0 0 20 1 3 11 75 15 4 2451619
+ 2,000 2000 0 0 0 20 1 3 11 75 15 4 2451619
+ 2,000 2000 0 0 0 20 1 3 11 75 15 4 2451619
+ 2,000 2000 0 0 0 20 1 3 11 75 15 4 2451619
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 3 7 28 191 10 5 2450640
+ 1,997 1997 997 97 7 20 2 6 23 161 10 3 2450610
+ 1,997 1997 997 97 7 20 1 2 6 41 10 2 2450490
+ 1,997 1997 997 97 7 20 1 2 6 42 11 3 2450491
+ 1,997 1997 997 97 7 20 1 2 7 43 12 4 2450492
+ 1,997 1997 997 97 7 20 1 2 7 44 13 5 2450493
+ 1,997 1997 997 97 7 20 1 2 7 45 14 6 2450494
+ 1,997 1997 997 97 7 20 1 2 7 46 15 7 2450495
+ 1,997 1997 997 97 7 20 1 2 7 47 16 1 2450496
+ 0,097 97 97 97 7 -1 1 2 7 47 16 3 1686042
+ 0,097 97 97 97 7 1 1 2 7 47 16 7 1756536
+ 0,597 597 597 97 7 6 1 2 7 47 16 5 1939157
+ 1,097 1097 97 97 7 11 1 2 7 47 16 3 2121778
+ 1,697 1697 697 97 7 17 1 2 7 47 16 7 2340924
+ 1,797 1797 797 97 7 18 1 2 7 47 16 5 2377448
+ 1,897 1897 897 97 7 19 1 2 7 47 16 3 2413972
+ 1,997 1997 997 97 7 20 1 2 7 47 16 1 2450496
+ 2,097 2097 97 97 7 21 1 2 7 47 16 7 2487021
+ 1,996 1996 996 96 6 20 1 2 9 59 28 4 2450142
+ 1,996 1996 996 96 6 20 1 2 9 60 29 5 2450143
+ 1,996 1996 996 96 6 20 1 3 9 61 1 6 2450144
+ 1,996 1996 996 96 6 20 4 12 53 365 30 2 2450448
+ 1,996 1996 996 96 6 20 4 12 53 366 31 3 2450449
+ 1,997 1997 997 97 7 20 1 1 1 1 1 4 2450450
+ 1,997 1997 997 97 7 20 1 2 9 59 28 6 2450508
+ 1,997 1997 997 97 7 20 1 3 9 60 1 7 2450509
+ 1,997 1997 997 97 7 20 4 12 52 364 30 3 2450813
+ 1,997 1997 997 97 7 20 4 12 53 365 31 4 2450814
+ 1,999 1999 999 99 9 20 4 12 53 365 31 6 2451544
+ 2,000 2000 0 0 0 20 1 1 1 1 1 7 2451545
+ 2,000 2000 0 0 0 20 4 12 53 366 31 1 2451910
+ 2,001 2001 1 1 1 21 1 1 1 1 1 2 2451911
+(66 rows)
+
+SELECT to_char(d1, 'HH HH12 HH24 MI SS SSSS')
+ FROM TIMESTAMPTZ_TBL;
+ to_char
+----------------------
+
+
+ 04 04 16 00 00 57600
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 02 63122
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 12 12 00 00 00 0
+ 03 03 03 04 05 11045
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 06 06 18 19 20 65960
+ 08 08 08 14 01 29641
+ 04 04 04 14 02 15242
+ 02 02 02 14 03 8043
+ 03 03 03 14 04 11644
+ 01 01 01 14 05 4445
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 00 63120
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 09 09 09 32 01 34321
+ 09 09 09 32 01 34321
+ 09 09 09 32 01 34321
+ 02 02 14 32 01 52321
+ 02 02 14 32 01 52321
+ 06 06 18 32 01 66721
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+ 05 05 17 32 01 63121
+(66 rows)
+
+SELECT to_char(d1, E'"HH:MI:SS is" HH:MI:SS "\\"text between quote marks\\""')
+ FROM TIMESTAMPTZ_TBL;
+ to_char
+-------------------------------------------------
+
+
+ HH:MI:SS is 04:00:00 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:02 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 12:00:00 "text between quote marks"
+ HH:MI:SS is 03:04:05 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 06:19:20 "text between quote marks"
+ HH:MI:SS is 08:14:01 "text between quote marks"
+ HH:MI:SS is 04:14:02 "text between quote marks"
+ HH:MI:SS is 02:14:03 "text between quote marks"
+ HH:MI:SS is 03:14:04 "text between quote marks"
+ HH:MI:SS is 01:14:05 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:00 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 09:32:01 "text between quote marks"
+ HH:MI:SS is 09:32:01 "text between quote marks"
+ HH:MI:SS is 09:32:01 "text between quote marks"
+ HH:MI:SS is 02:32:01 "text between quote marks"
+ HH:MI:SS is 02:32:01 "text between quote marks"
+ HH:MI:SS is 06:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+ HH:MI:SS is 05:32:01 "text between quote marks"
+(66 rows)
+
+SELECT to_char(d1, 'HH24--text--MI--text--SS')
+ FROM TIMESTAMPTZ_TBL;
+ to_char
+------------------------
+
+
+ 16--text--00--text--00
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--02
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 00--text--00--text--00
+ 03--text--04--text--05
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 18--text--19--text--20
+ 08--text--14--text--01
+ 04--text--14--text--02
+ 02--text--14--text--03
+ 03--text--14--text--04
+ 01--text--14--text--05
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--00
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 09--text--32--text--01
+ 09--text--32--text--01
+ 09--text--32--text--01
+ 14--text--32--text--01
+ 14--text--32--text--01
+ 18--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+ 17--text--32--text--01
+(66 rows)
+
+SELECT to_char(d1, 'YYYYTH YYYYth Jth')
+ FROM TIMESTAMPTZ_TBL;
+ to_char
+-------------------------
+
+
+ 1969TH 1969th 2440587th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450451st
+ 1997TH 1997th 2450451st
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450610th
+ 2001ST 2001st 2452175th
+ 2000TH 2000th 2451619th
+ 2000TH 2000th 2451619th
+ 2000TH 2000th 2451619th
+ 2000TH 2000th 2451619th
+ 2000TH 2000th 2451619th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450640th
+ 1997TH 1997th 2450610th
+ 1997TH 1997th 2450490th
+ 1997TH 1997th 2450491st
+ 1997TH 1997th 2450492nd
+ 1997TH 1997th 2450493rd
+ 1997TH 1997th 2450494th
+ 1997TH 1997th 2450495th
+ 1997TH 1997th 2450496th
+ 0097TH 0097th 1686042nd
+ 0097TH 0097th 1756536th
+ 0597TH 0597th 1939157th
+ 1097TH 1097th 2121778th
+ 1697TH 1697th 2340924th
+ 1797TH 1797th 2377448th
+ 1897TH 1897th 2413972nd
+ 1997TH 1997th 2450496th
+ 2097TH 2097th 2487021st
+ 1996TH 1996th 2450142nd
+ 1996TH 1996th 2450143rd
+ 1996TH 1996th 2450144th
+ 1996TH 1996th 2450448th
+ 1996TH 1996th 2450449th
+ 1997TH 1997th 2450450th
+ 1997TH 1997th 2450508th
+ 1997TH 1997th 2450509th
+ 1997TH 1997th 2450813th
+ 1997TH 1997th 2450814th
+ 1999TH 1999th 2451544th
+ 2000TH 2000th 2451545th
+ 2000TH 2000th 2451910th
+ 2001ST 2001st 2451911th
+(66 rows)
+
+SELECT to_char(d1, 'YYYY A.D. YYYY a.d. YYYY bc HH:MI:SS P.M. HH:MI:SS p.m. HH:MI:SS pm')
+ FROM TIMESTAMPTZ_TBL;
+ to_char
+---------------------------------------------------------------------
+
+
+ 1969 A.D. 1969 a.d. 1969 ad 04:00:00 P.M. 04:00:00 p.m. 04:00:00 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:02 P.M. 05:32:02 p.m. 05:32:02 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 12:00:00 A.M. 12:00:00 a.m. 12:00:00 am
+ 1997 A.D. 1997 a.d. 1997 ad 03:04:05 A.M. 03:04:05 a.m. 03:04:05 am
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 2001 A.D. 2001 a.d. 2001 ad 06:19:20 P.M. 06:19:20 p.m. 06:19:20 pm
+ 2000 A.D. 2000 a.d. 2000 ad 08:14:01 A.M. 08:14:01 a.m. 08:14:01 am
+ 2000 A.D. 2000 a.d. 2000 ad 04:14:02 A.M. 04:14:02 a.m. 04:14:02 am
+ 2000 A.D. 2000 a.d. 2000 ad 02:14:03 A.M. 02:14:03 a.m. 02:14:03 am
+ 2000 A.D. 2000 a.d. 2000 ad 03:14:04 A.M. 03:14:04 a.m. 03:14:04 am
+ 2000 A.D. 2000 a.d. 2000 ad 01:14:05 A.M. 01:14:05 a.m. 01:14:05 am
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:00 P.M. 05:32:00 p.m. 05:32:00 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 09:32:01 A.M. 09:32:01 a.m. 09:32:01 am
+ 1997 A.D. 1997 a.d. 1997 ad 09:32:01 A.M. 09:32:01 a.m. 09:32:01 am
+ 1997 A.D. 1997 a.d. 1997 ad 09:32:01 A.M. 09:32:01 a.m. 09:32:01 am
+ 1997 A.D. 1997 a.d. 1997 ad 02:32:01 P.M. 02:32:01 p.m. 02:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 02:32:01 P.M. 02:32:01 p.m. 02:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 06:32:01 P.M. 06:32:01 p.m. 06:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 0097 B.C. 0097 b.c. 0097 bc 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 0097 A.D. 0097 a.d. 0097 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 0597 A.D. 0597 a.d. 0597 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1097 A.D. 1097 a.d. 1097 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1697 A.D. 1697 a.d. 1697 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1797 A.D. 1797 a.d. 1797 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1897 A.D. 1897 a.d. 1897 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 2097 A.D. 2097 a.d. 2097 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1996 A.D. 1996 a.d. 1996 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1996 A.D. 1996 a.d. 1996 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1996 A.D. 1996 a.d. 1996 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1996 A.D. 1996 a.d. 1996 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1996 A.D. 1996 a.d. 1996 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 1999 A.D. 1999 a.d. 1999 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 2000 A.D. 2000 a.d. 2000 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 2000 A.D. 2000 a.d. 2000 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+ 2001 A.D. 2001 a.d. 2001 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm
+(66 rows)
+
+SELECT to_char(d1, 'IYYY IYY IY I IW IDDD ID')
+ FROM TIMESTAMPTZ_TBL;
+ to_char
+------------------------
+
+
+ 1970 970 70 0 01 003 3
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 01 004 4
+ 1997 997 97 7 01 004 4
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 24 163 2
+ 2001 001 01 1 38 265 6
+ 2000 000 00 0 11 073 3
+ 2000 000 00 0 11 073 3
+ 2000 000 00 0 11 073 3
+ 2000 000 00 0 11 073 3
+ 2000 000 00 0 11 073 3
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 28 193 4
+ 1997 997 97 7 24 163 2
+ 1997 997 97 7 07 043 1
+ 1997 997 97 7 07 044 2
+ 1997 997 97 7 07 045 3
+ 1997 997 97 7 07 046 4
+ 1997 997 97 7 07 047 5
+ 1997 997 97 7 07 048 6
+ 1997 997 97 7 07 049 7
+ 0097 097 97 7 07 044 2
+ 0097 097 97 7 07 048 6
+ 0597 597 97 7 07 046 4
+ 1097 097 97 7 07 044 2
+ 1697 697 97 7 07 048 6
+ 1797 797 97 7 07 046 4
+ 1897 897 97 7 07 044 2
+ 1997 997 97 7 07 049 7
+ 2097 097 97 7 07 048 6
+ 1996 996 96 6 09 059 3
+ 1996 996 96 6 09 060 4
+ 1996 996 96 6 09 061 5
+ 1997 997 97 7 01 001 1
+ 1997 997 97 7 01 002 2
+ 1997 997 97 7 01 003 3
+ 1997 997 97 7 09 061 5
+ 1997 997 97 7 09 062 6
+ 1998 998 98 8 01 002 2
+ 1998 998 98 8 01 003 3
+ 1999 999 99 9 52 362 5
+ 1999 999 99 9 52 363 6
+ 2000 000 00 0 52 364 7
+ 2001 001 01 1 01 001 1
+(66 rows)
+
+SELECT to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
+ FROM TIMESTAMPTZ_TBL;
+ to_char
+------------------------
+
+
+ 1970 970 70 0 1 3 3
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 1 4 4
+ 1997 997 97 7 1 4 4
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 24 163 2
+ 2001 1 1 1 38 265 6
+ 2000 0 0 0 11 73 3
+ 2000 0 0 0 11 73 3
+ 2000 0 0 0 11 73 3
+ 2000 0 0 0 11 73 3
+ 2000 0 0 0 11 73 3
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 28 193 4
+ 1997 997 97 7 24 163 2
+ 1997 997 97 7 7 43 1
+ 1997 997 97 7 7 44 2
+ 1997 997 97 7 7 45 3
+ 1997 997 97 7 7 46 4
+ 1997 997 97 7 7 47 5
+ 1997 997 97 7 7 48 6
+ 1997 997 97 7 7 49 7
+ 97 97 97 7 7 44 2
+ 97 97 97 7 7 48 6
+ 597 597 97 7 7 46 4
+ 1097 97 97 7 7 44 2
+ 1697 697 97 7 7 48 6
+ 1797 797 97 7 7 46 4
+ 1897 897 97 7 7 44 2
+ 1997 997 97 7 7 49 7
+ 2097 97 97 7 7 48 6
+ 1996 996 96 6 9 59 3
+ 1996 996 96 6 9 60 4
+ 1996 996 96 6 9 61 5
+ 1997 997 97 7 1 1 1
+ 1997 997 97 7 1 2 2
+ 1997 997 97 7 1 3 3
+ 1997 997 97 7 9 61 5
+ 1997 997 97 7 9 62 6
+ 1998 998 98 8 1 2 2
+ 1998 998 98 8 1 3 3
+ 1999 999 99 9 52 362 5
+ 1999 999 99 9 52 363 6
+ 2000 0 0 0 52 364 7
+ 2001 1 1 1 1 1 1
+(66 rows)
+
+SELECT to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char
+--------------------------------------------------------------------
+ 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
+-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
+SET timezone = '00:00';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+ OF | TZH:TZM
+-----+---------
+ +00 | +00:00
+(1 row)
+
+SET timezone = '+02:00';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+ OF | TZH:TZM
+-----+---------
+ -02 | -02:00
+(1 row)
+
+SET timezone = '-13:00';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+ OF | TZH:TZM
+-----+---------
+ +13 | +13:00
+(1 row)
+
+SET timezone = '-00:30';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+ OF | TZH:TZM
+--------+---------
+ +00:30 | +00:30
+(1 row)
+
+SET timezone = '00:30';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+ OF | TZH:TZM
+--------+---------
+ -00:30 | -00:30
+(1 row)
+
+SET timezone = '-04:30';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+ OF | TZH:TZM
+--------+---------
+ +04:30 | +04:30
+(1 row)
+
+SET timezone = '04:30';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+ OF | TZH:TZM
+--------+---------
+ -04:30 | -04:30
+(1 row)
+
+SET timezone = '-04:15';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+ OF | TZH:TZM
+--------+---------
+ +04:15 | +04:15
+(1 row)
+
+SET timezone = '04:15';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+ OF | TZH:TZM
+--------+---------
+ -04:15 | -04:15
+(1 row)
+
+RESET timezone;
+CREATE TABLE TIMESTAMPTZ_TST (a int , b timestamptz);
+-- Test year field value with len > 4
+INSERT INTO TIMESTAMPTZ_TST VALUES(1, 'Sat Mar 12 23:58:48 1000 IST');
+INSERT INTO TIMESTAMPTZ_TST VALUES(2, 'Sat Mar 12 23:58:48 10000 IST');
+INSERT INTO TIMESTAMPTZ_TST VALUES(3, 'Sat Mar 12 23:58:48 100000 IST');
+INSERT INTO TIMESTAMPTZ_TST VALUES(3, '10000 Mar 12 23:58:48 IST');
+INSERT INTO TIMESTAMPTZ_TST VALUES(4, '100000312 23:58:48 IST');
+INSERT INTO TIMESTAMPTZ_TST VALUES(4, '1000000312 23:58:48 IST');
+--Verify data
+SELECT * FROM TIMESTAMPTZ_TST ORDER BY a;
+ a | b
+---+--------------------------------
+ 1 | Wed Mar 12 13:58:48 1000 PST
+ 2 | Sun Mar 12 14:58:48 10000 PDT
+ 3 | Sun Mar 12 14:58:48 100000 PDT
+ 3 | Sun Mar 12 14:58:48 10000 PDT
+ 4 | Sun Mar 12 14:58:48 10000 PDT
+ 4 | Sun Mar 12 14:58:48 100000 PDT
+(6 rows)
+
+--Cleanup
+DROP TABLE TIMESTAMPTZ_TST;
+-- test timestamptz constructors
+set TimeZone to 'America/New_York';
+-- numeric timezone
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33);
+ make_timestamptz
+---------------------------------
+ Sun Jul 15 08:15:55.33 1973 EDT
+(1 row)
+
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '+2');
+ make_timestamptz
+---------------------------------
+ Sun Jul 15 02:15:55.33 1973 EDT
+(1 row)
+
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '-2');
+ make_timestamptz
+---------------------------------
+ Sun Jul 15 06:15:55.33 1973 EDT
+(1 row)
+
+WITH tzs (tz) AS (VALUES
+ ('+1'), ('+1:'), ('+1:0'), ('+100'), ('+1:00'), ('+01:00'),
+ ('+10'), ('+1000'), ('+10:'), ('+10:0'), ('+10:00'), ('+10:00:'),
+ ('+10:00:1'), ('+10:00:01'),
+ ('+10:00:10'))
+ SELECT make_timestamptz(2010, 2, 27, 3, 45, 00, tz), tz FROM tzs;
+ make_timestamptz | tz
+------------------------------+-----------
+ Fri Feb 26 21:45:00 2010 EST | +1
+ Fri Feb 26 21:45:00 2010 EST | +1:
+ Fri Feb 26 21:45:00 2010 EST | +1:0
+ Fri Feb 26 21:45:00 2010 EST | +100
+ Fri Feb 26 21:45:00 2010 EST | +1:00
+ Fri Feb 26 21:45:00 2010 EST | +01:00
+ Fri Feb 26 12:45:00 2010 EST | +10
+ Fri Feb 26 12:45:00 2010 EST | +1000
+ Fri Feb 26 12:45:00 2010 EST | +10:
+ Fri Feb 26 12:45:00 2010 EST | +10:0
+ Fri Feb 26 12:45:00 2010 EST | +10:00
+ Fri Feb 26 12:45:00 2010 EST | +10:00:
+ Fri Feb 26 12:44:59 2010 EST | +10:00:1
+ Fri Feb 26 12:44:59 2010 EST | +10:00:01
+ Fri Feb 26 12:44:50 2010 EST | +10:00:10
+(15 rows)
+
+-- these should fail
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '2');
+ERROR: invalid input syntax for type numeric time zone: "2"
+HINT: Numeric time zones must have "-" or "+" as first character.
+SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, '+16');
+ERROR: numeric time zone "+16" out of range
+SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, '-16');
+ERROR: numeric time zone "-16" out of range
+-- should be true
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '+2') = '1973-07-15 08:15:55.33+02'::timestamptz;
+ ?column?
+----------
+ t
+(1 row)
+
+-- full timezone names
+SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Europe/Prague') = timestamptz '2014-12-10 00:00:00 Europe/Prague';
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Europe/Prague') AT TIME ZONE 'UTC';
+ timezone
+--------------------------
+ Tue Dec 09 23:00:00 2014
+(1 row)
+
+SELECT make_timestamptz(1846, 12, 10, 0, 0, 0, 'Asia/Manila') AT TIME ZONE 'UTC';
+ timezone
+--------------------------
+ Wed Dec 09 15:56:00 1846
+(1 row)
+
+SELECT make_timestamptz(1881, 12, 10, 0, 0, 0, 'Europe/Paris') AT TIME ZONE 'UTC';
+ timezone
+--------------------------
+ Fri Dec 09 23:50:39 1881
+(1 row)
+
+SELECT make_timestamptz(1910, 12, 24, 0, 0, 0, 'Nehwon/Lankhmar');
+ERROR: time zone "Nehwon/Lankhmar" not recognized
+-- abbreviations
+SELECT make_timestamptz(2008, 12, 10, 10, 10, 10, 'EST');
+ make_timestamptz
+------------------------------
+ Wed Dec 10 10:10:10 2008 EST
+(1 row)
+
+SELECT make_timestamptz(2008, 12, 10, 10, 10, 10, 'EDT');
+ make_timestamptz
+------------------------------
+ Wed Dec 10 09:10:10 2008 EST
+(1 row)
+
+SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, 'PST8PDT');
+ make_timestamptz
+------------------------------
+ Wed Dec 10 13:10:10 2014 EST
+(1 row)
+
+RESET TimeZone;
+-- generate_series for timestamptz
+select * from generate_series('2020-01-01 00:00'::timestamptz,
+ '2020-01-02 03:00'::timestamptz,
+ '1 hour'::interval);
+ generate_series
+------------------------------
+ Wed Jan 01 00:00:00 2020 PST
+ Wed Jan 01 01:00:00 2020 PST
+ Wed Jan 01 02:00:00 2020 PST
+ Wed Jan 01 03:00:00 2020 PST
+ Wed Jan 01 04:00:00 2020 PST
+ Wed Jan 01 05:00:00 2020 PST
+ Wed Jan 01 06:00:00 2020 PST
+ Wed Jan 01 07:00:00 2020 PST
+ Wed Jan 01 08:00:00 2020 PST
+ Wed Jan 01 09:00:00 2020 PST
+ Wed Jan 01 10:00:00 2020 PST
+ Wed Jan 01 11:00:00 2020 PST
+ Wed Jan 01 12:00:00 2020 PST
+ Wed Jan 01 13:00:00 2020 PST
+ Wed Jan 01 14:00:00 2020 PST
+ Wed Jan 01 15:00:00 2020 PST
+ Wed Jan 01 16:00:00 2020 PST
+ Wed Jan 01 17:00:00 2020 PST
+ Wed Jan 01 18:00:00 2020 PST
+ Wed Jan 01 19:00:00 2020 PST
+ Wed Jan 01 20:00:00 2020 PST
+ Wed Jan 01 21:00:00 2020 PST
+ Wed Jan 01 22:00:00 2020 PST
+ Wed Jan 01 23:00:00 2020 PST
+ Thu Jan 02 00:00:00 2020 PST
+ Thu Jan 02 01:00:00 2020 PST
+ Thu Jan 02 02:00:00 2020 PST
+ Thu Jan 02 03:00:00 2020 PST
+(28 rows)
+
+-- the LIMIT should allow this to terminate in a reasonable amount of time
+-- (but that unfortunately doesn't work yet for SELECT * FROM ...)
+select generate_series('2022-01-01 00:00'::timestamptz,
+ 'infinity'::timestamptz,
+ '1 month'::interval) limit 10;
+ generate_series
+------------------------------
+ Sat Jan 01 00:00:00 2022 PST
+ Tue Feb 01 00:00:00 2022 PST
+ Tue Mar 01 00:00:00 2022 PST
+ Fri Apr 01 00:00:00 2022 PDT
+ Sun May 01 00:00:00 2022 PDT
+ Wed Jun 01 00:00:00 2022 PDT
+ Fri Jul 01 00:00:00 2022 PDT
+ Mon Aug 01 00:00:00 2022 PDT
+ Thu Sep 01 00:00:00 2022 PDT
+ Sat Oct 01 00:00:00 2022 PDT
+(10 rows)
+
+-- errors
+select * from generate_series('2020-01-01 00:00'::timestamptz,
+ '2020-01-02 03:00'::timestamptz,
+ '0 hour'::interval);
+ERROR: step size cannot equal zero
+--
+-- Test behavior with a dynamic (time-varying) timezone abbreviation.
+-- These tests rely on the knowledge that MSK (Europe/Moscow standard time)
+-- moved forwards in Mar 2011 and backwards again in Oct 2014.
+--
+SET TimeZone to 'UTC';
+SELECT '2011-03-27 00:00:00 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 21:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 01:00:00 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 22:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 01:59:59 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 22:59:59 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 02:00:00 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 23:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 02:00:01 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 23:00:01 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 02:59:59 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 23:59:59 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 03:00:00 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 23:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 03:00:01 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 23:00:01 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 04:00:00 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Mar 27 00:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 00:00:00 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 21:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 01:00:00 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 22:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 01:59:59 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 22:59:59 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 02:00:00 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 22:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 02:00:01 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 22:00:01 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 02:59:59 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 22:59:59 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 03:00:00 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 23:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 03:00:01 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Mar 26 23:00:01 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 04:00:00 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Mar 27 00:00:00 2011 UTC
+(1 row)
+
+SELECT '2014-10-26 00:00:00 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Oct 25 20:00:00 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 00:59:59 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Oct 25 20:59:59 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 01:00:00 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Oct 25 22:00:00 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 01:00:01 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Oct 25 22:00:01 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 02:00:00 Europe/Moscow'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Oct 25 23:00:00 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 00:00:00 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Oct 25 20:00:00 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 00:59:59 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Oct 25 20:59:59 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 01:00:00 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Oct 25 22:00:00 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 01:00:01 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Oct 25 22:00:01 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 02:00:00 MSK'::timestamptz;
+ timestamptz
+------------------------------
+ Sat Oct 25 23:00:00 2014 UTC
+(1 row)
+
+SELECT '2011-03-27 00:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Mar 26 21:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 01:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Mar 26 22:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 01:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Mar 26 22:59:59 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 02:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Mar 26 23:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 02:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Mar 26 23:00:01 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 02:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Mar 26 23:59:59 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 03:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Mar 26 23:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 03:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Mar 26 23:00:01 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 04:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sun Mar 27 00:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 00:00:00'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Mar 26 21:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 01:00:00'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Mar 26 22:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 01:59:59'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Mar 26 22:59:59 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 02:00:00'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Mar 26 22:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 02:00:01'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Mar 26 22:00:01 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 02:59:59'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Mar 26 22:59:59 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 03:00:00'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Mar 26 23:00:00 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 03:00:01'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Mar 26 23:00:01 2011 UTC
+(1 row)
+
+SELECT '2011-03-27 04:00:00'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sun Mar 27 00:00:00 2011 UTC
+(1 row)
+
+SELECT '2014-10-26 00:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Oct 25 20:00:00 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 00:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Oct 25 20:59:59 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 01:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Oct 25 22:00:00 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 01:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Oct 25 22:00:01 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 02:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ timezone
+------------------------------
+ Sat Oct 25 23:00:00 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 00:00:00'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Oct 25 20:00:00 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 00:59:59'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Oct 25 20:59:59 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 01:00:00'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Oct 25 22:00:00 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 01:00:01'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Oct 25 22:00:01 2014 UTC
+(1 row)
+
+SELECT '2014-10-26 02:00:00'::timestamp AT TIME ZONE 'MSK';
+ timezone
+------------------------------
+ Sat Oct 25 23:00:00 2014 UTC
+(1 row)
+
+SELECT make_timestamptz(2014, 10, 26, 0, 0, 0, 'MSK');
+ make_timestamptz
+------------------------------
+ Sat Oct 25 20:00:00 2014 UTC
+(1 row)
+
+SELECT make_timestamptz(2014, 10, 26, 1, 0, 0, 'MSK');
+ make_timestamptz
+------------------------------
+ Sat Oct 25 22:00:00 2014 UTC
+(1 row)
+
+SELECT to_timestamp( 0); -- 1970-01-01 00:00:00+00
+ to_timestamp
+------------------------------
+ Thu Jan 01 00:00:00 1970 UTC
+(1 row)
+
+SELECT to_timestamp( 946684800); -- 2000-01-01 00:00:00+00
+ to_timestamp
+------------------------------
+ Sat Jan 01 00:00:00 2000 UTC
+(1 row)
+
+SELECT to_timestamp(1262349296.7890123); -- 2010-01-01 12:34:56.789012+00
+ to_timestamp
+-------------------------------------
+ Fri Jan 01 12:34:56.789012 2010 UTC
+(1 row)
+
+-- edge cases
+SELECT to_timestamp(-210866803200); -- 4714-11-24 00:00:00+00 BC
+ to_timestamp
+---------------------------------
+ Mon Nov 24 00:00:00 4714 UTC BC
+(1 row)
+
+-- upper limit varies between integer and float timestamps, so hard to test
+-- nonfinite values
+SELECT to_timestamp(' Infinity'::float);
+ to_timestamp
+--------------
+ infinity
+(1 row)
+
+SELECT to_timestamp('-Infinity'::float);
+ to_timestamp
+--------------
+ -infinity
+(1 row)
+
+SELECT to_timestamp('NaN'::float);
+ERROR: timestamp cannot be NaN
+SET TimeZone to 'Europe/Moscow';
+SELECT '2011-03-26 21:00:00 UTC'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Mar 27 00:00:00 2011 MSK
+(1 row)
+
+SELECT '2011-03-26 22:00:00 UTC'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Mar 27 01:00:00 2011 MSK
+(1 row)
+
+SELECT '2011-03-26 22:59:59 UTC'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Mar 27 01:59:59 2011 MSK
+(1 row)
+
+SELECT '2011-03-26 23:00:00 UTC'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Mar 27 03:00:00 2011 MSK
+(1 row)
+
+SELECT '2011-03-26 23:00:01 UTC'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Mar 27 03:00:01 2011 MSK
+(1 row)
+
+SELECT '2011-03-26 23:59:59 UTC'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Mar 27 03:59:59 2011 MSK
+(1 row)
+
+SELECT '2011-03-27 00:00:00 UTC'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Mar 27 04:00:00 2011 MSK
+(1 row)
+
+SELECT '2014-10-25 21:00:00 UTC'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Oct 26 01:00:00 2014 MSK
+(1 row)
+
+SELECT '2014-10-25 21:59:59 UTC'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Oct 26 01:59:59 2014 MSK
+(1 row)
+
+SELECT '2014-10-25 22:00:00 UTC'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Oct 26 01:00:00 2014 MSK
+(1 row)
+
+SELECT '2014-10-25 22:00:01 UTC'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Oct 26 01:00:01 2014 MSK
+(1 row)
+
+SELECT '2014-10-25 23:00:00 UTC'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Oct 26 02:00:00 2014 MSK
+(1 row)
+
+RESET TimeZone;
+SELECT '2011-03-26 21:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ timezone
+--------------------------
+ Sun Mar 27 00:00:00 2011
+(1 row)
+
+SELECT '2011-03-26 22:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ timezone
+--------------------------
+ Sun Mar 27 01:00:00 2011
+(1 row)
+
+SELECT '2011-03-26 22:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ timezone
+--------------------------
+ Sun Mar 27 01:59:59 2011
+(1 row)
+
+SELECT '2011-03-26 23:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ timezone
+--------------------------
+ Sun Mar 27 03:00:00 2011
+(1 row)
+
+SELECT '2011-03-26 23:00:01 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ timezone
+--------------------------
+ Sun Mar 27 03:00:01 2011
+(1 row)
+
+SELECT '2011-03-26 23:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ timezone
+--------------------------
+ Sun Mar 27 03:59:59 2011
+(1 row)
+
+SELECT '2011-03-27 00:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ timezone
+--------------------------
+ Sun Mar 27 04:00:00 2011
+(1 row)
+
+SELECT '2014-10-25 21:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ timezone
+--------------------------
+ Sun Oct 26 01:00:00 2014
+(1 row)
+
+SELECT '2014-10-25 21:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ timezone
+--------------------------
+ Sun Oct 26 01:59:59 2014
+(1 row)
+
+SELECT '2014-10-25 22:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ timezone
+--------------------------
+ Sun Oct 26 01:00:00 2014
+(1 row)
+
+SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ timezone
+--------------------------
+ Sun Oct 26 01:00:01 2014
+(1 row)
+
+SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ timezone
+--------------------------
+ Sun Oct 26 02:00:00 2014
+(1 row)
+
+SELECT '2011-03-26 21:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ timezone
+--------------------------
+ Sun Mar 27 00:00:00 2011
+(1 row)
+
+SELECT '2011-03-26 22:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ timezone
+--------------------------
+ Sun Mar 27 01:00:00 2011
+(1 row)
+
+SELECT '2011-03-26 22:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+ timezone
+--------------------------
+ Sun Mar 27 01:59:59 2011
+(1 row)
+
+SELECT '2011-03-26 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ timezone
+--------------------------
+ Sun Mar 27 03:00:00 2011
+(1 row)
+
+SELECT '2011-03-26 23:00:01 UTC'::timestamptz AT TIME ZONE 'MSK';
+ timezone
+--------------------------
+ Sun Mar 27 03:00:01 2011
+(1 row)
+
+SELECT '2011-03-26 23:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+ timezone
+--------------------------
+ Sun Mar 27 03:59:59 2011
+(1 row)
+
+SELECT '2011-03-27 00:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ timezone
+--------------------------
+ Sun Mar 27 04:00:00 2011
+(1 row)
+
+SELECT '2014-10-25 21:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ timezone
+--------------------------
+ Sun Oct 26 01:00:00 2014
+(1 row)
+
+SELECT '2014-10-25 21:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+ timezone
+--------------------------
+ Sun Oct 26 01:59:59 2014
+(1 row)
+
+SELECT '2014-10-25 22:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ timezone
+--------------------------
+ Sun Oct 26 01:00:00 2014
+(1 row)
+
+SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'MSK';
+ timezone
+--------------------------
+ Sun Oct 26 01:00:01 2014
+(1 row)
+
+SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ timezone
+--------------------------
+ Sun Oct 26 02:00:00 2014
+(1 row)
+
+--
+-- Test that AT TIME ZONE isn't misoptimized when using an index (bug #14504)
+--
+create temp table tmptz (f1 timestamptz primary key);
+insert into tmptz values ('2017-01-18 00:00+00');
+explain (costs off)
+select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------
+ Seq Scan on tmptz
+ Filter: ((f1 AT TIME ZONE 'utc'::text) = 'Wed Jan 18 00:00:00 2017'::timestamp without time zone)
+(2 rows)
+
+select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+ f1
+------------------------------
+ Tue Jan 17 16:00:00 2017 PST
+(1 row)
+
diff --git a/src/test/regress/expected/timetz.out b/src/test/regress/expected/timetz.out
new file mode 100644
index 0000000..f4960c0
--- /dev/null
+++ b/src/test/regress/expected/timetz.out
@@ -0,0 +1,233 @@
+--
+-- TIMETZ
+--
+CREATE TABLE TIMETZ_TBL (f1 time(2) with time zone);
+INSERT INTO TIMETZ_TBL VALUES ('00:01 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('01:00 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('02:03 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('07:07 PST');
+INSERT INTO TIMETZ_TBL VALUES ('08:08 EDT');
+INSERT INTO TIMETZ_TBL VALUES ('11:59 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('12:00 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('12:01 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('23:59 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('11:59:59.99 PM PDT');
+INSERT INTO TIMETZ_TBL VALUES ('2003-03-07 15:36:39 America/New_York');
+INSERT INTO TIMETZ_TBL VALUES ('2003-07-07 15:36:39 America/New_York');
+-- this should fail (the timezone offset is not known)
+INSERT INTO TIMETZ_TBL VALUES ('15:36:39 America/New_York');
+ERROR: invalid input syntax for type time with time zone: "15:36:39 America/New_York"
+LINE 1: INSERT INTO TIMETZ_TBL VALUES ('15:36:39 America/New_York');
+ ^
+-- this should fail (timezone not specified without a date)
+INSERT INTO TIMETZ_TBL VALUES ('15:36:39 m2');
+ERROR: invalid input syntax for type time with time zone: "15:36:39 m2"
+LINE 1: INSERT INTO TIMETZ_TBL VALUES ('15:36:39 m2');
+ ^
+-- this should fail (dynamic timezone abbreviation without a date)
+INSERT INTO TIMETZ_TBL VALUES ('15:36:39 MSK m2');
+ERROR: invalid input syntax for type time with time zone: "15:36:39 MSK m2"
+LINE 1: INSERT INTO TIMETZ_TBL VALUES ('15:36:39 MSK m2');
+ ^
+SELECT f1 AS "Time TZ" FROM TIMETZ_TBL;
+ Time TZ
+----------------
+ 00:01:00-07
+ 01:00:00-07
+ 02:03:00-07
+ 07:07:00-08
+ 08:08:00-04
+ 11:59:00-07
+ 12:00:00-07
+ 12:01:00-07
+ 23:59:00-07
+ 23:59:59.99-07
+ 15:36:39-05
+ 15:36:39-04
+(12 rows)
+
+SELECT f1 AS "Three" FROM TIMETZ_TBL WHERE f1 < '05:06:07-07';
+ Three
+-------------
+ 00:01:00-07
+ 01:00:00-07
+ 02:03:00-07
+(3 rows)
+
+SELECT f1 AS "Seven" FROM TIMETZ_TBL WHERE f1 > '05:06:07-07';
+ Seven
+----------------
+ 07:07:00-08
+ 08:08:00-04
+ 11:59:00-07
+ 12:00:00-07
+ 12:01:00-07
+ 23:59:00-07
+ 23:59:59.99-07
+ 15:36:39-05
+ 15:36:39-04
+(9 rows)
+
+SELECT f1 AS "None" FROM TIMETZ_TBL WHERE f1 < '00:00-07';
+ None
+------
+(0 rows)
+
+SELECT f1 AS "Ten" FROM TIMETZ_TBL WHERE f1 >= '00:00-07';
+ Ten
+----------------
+ 00:01:00-07
+ 01:00:00-07
+ 02:03:00-07
+ 07:07:00-08
+ 08:08:00-04
+ 11:59:00-07
+ 12:00:00-07
+ 12:01:00-07
+ 23:59:00-07
+ 23:59:59.99-07
+ 15:36:39-05
+ 15:36:39-04
+(12 rows)
+
+-- Check edge cases
+SELECT '23:59:59.999999 PDT'::timetz;
+ timetz
+--------------------
+ 23:59:59.999999-07
+(1 row)
+
+SELECT '23:59:59.9999999 PDT'::timetz; -- rounds up
+ timetz
+-------------
+ 24:00:00-07
+(1 row)
+
+SELECT '23:59:60 PDT'::timetz; -- rounds up
+ timetz
+-------------
+ 24:00:00-07
+(1 row)
+
+SELECT '24:00:00 PDT'::timetz; -- allowed
+ timetz
+-------------
+ 24:00:00-07
+(1 row)
+
+SELECT '24:00:00.01 PDT'::timetz; -- not allowed
+ERROR: date/time field value out of range: "24:00:00.01 PDT"
+LINE 1: SELECT '24:00:00.01 PDT'::timetz;
+ ^
+SELECT '23:59:60.01 PDT'::timetz; -- not allowed
+ERROR: date/time field value out of range: "23:59:60.01 PDT"
+LINE 1: SELECT '23:59:60.01 PDT'::timetz;
+ ^
+SELECT '24:01:00 PDT'::timetz; -- not allowed
+ERROR: date/time field value out of range: "24:01:00 PDT"
+LINE 1: SELECT '24:01:00 PDT'::timetz;
+ ^
+SELECT '25:00:00 PDT'::timetz; -- not allowed
+ERROR: date/time field value out of range: "25:00:00 PDT"
+LINE 1: SELECT '25:00:00 PDT'::timetz;
+ ^
+--
+-- TIME simple math
+--
+-- We now make a distinction between time and intervals,
+-- and adding two times together makes no sense at all.
+-- Leave in one query to show that it is rejected,
+-- and do the rest of the testing in horology.sql
+-- where we do mixed-type arithmetic. - thomas 2000-12-02
+SELECT f1 + time with time zone '00:01' AS "Illegal" FROM TIMETZ_TBL;
+ERROR: operator does not exist: time with time zone + time with time zone
+LINE 1: SELECT f1 + time with time zone '00:01' AS "Illegal" FROM TI...
+ ^
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+--
+-- test EXTRACT
+--
+SELECT EXTRACT(MICROSECOND FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+ extract
+----------
+ 25575401
+(1 row)
+
+SELECT EXTRACT(MILLISECOND FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+ extract
+-----------
+ 25575.401
+(1 row)
+
+SELECT EXTRACT(SECOND FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+ extract
+-----------
+ 25.575401
+(1 row)
+
+SELECT EXTRACT(MINUTE FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+ extract
+---------
+ 30
+(1 row)
+
+SELECT EXTRACT(HOUR FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+ extract
+---------
+ 13
+(1 row)
+
+SELECT EXTRACT(DAY FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04'); -- error
+ERROR: "time with time zone" units "day" not recognized
+SELECT EXTRACT(FORTNIGHT FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04'); -- error
+ERROR: "time with time zone" units "fortnight" not recognized
+SELECT EXTRACT(TIMEZONE FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04:30');
+ extract
+---------
+ -16200
+(1 row)
+
+SELECT EXTRACT(TIMEZONE_HOUR FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04:30');
+ extract
+---------
+ -4
+(1 row)
+
+SELECT EXTRACT(TIMEZONE_MINUTE FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04:30');
+ extract
+---------
+ -30
+(1 row)
+
+SELECT EXTRACT(EPOCH FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+ extract
+--------------
+ 63025.575401
+(1 row)
+
+-- date_part implementation is mostly the same as extract, so only
+-- test a few cases for additional coverage.
+SELECT date_part('microsecond', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+ date_part
+-----------
+ 25575401
+(1 row)
+
+SELECT date_part('millisecond', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+ date_part
+-----------
+ 25575.401
+(1 row)
+
+SELECT date_part('second', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+ date_part
+-----------
+ 25.575401
+(1 row)
+
+SELECT date_part('epoch', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+ date_part
+--------------
+ 63025.575401
+(1 row)
+
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
new file mode 100644
index 0000000..61862d5
--- /dev/null
+++ b/src/test/regress/expected/transactions.out
@@ -0,0 +1,1064 @@
+--
+-- TRANSACTIONS
+--
+BEGIN;
+SELECT *
+ INTO TABLE xacttest
+ FROM aggtest;
+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 aggtest;
+-- should be empty
+SELECT * FROM aggtest;
+ a | b
+---+---
+(0 rows)
+
+ABORT;
+-- should not exist
+SELECT oid FROM pg_class WHERE relname = 'disappear';
+ oid
+-----
+(0 rows)
+
+-- should have members again
+SELECT * FROM aggtest;
+ a | b
+-----+---------
+ 56 | 7.8
+ 100 | 99.097
+ 0 | 0.09561
+ 42 | 324.78
+(4 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
+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
+SET TRANSACTION READ ONLY; -- ok
+SELECT * FROM writetest; -- ok
+ a
+---
+(0 rows)
+
+SAVEPOINT x;
+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 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;
+ DROP TABLE trans_foo;
+ CREATE TABLE trans_bar (a int);
+ ROLLBACK TO SAVEPOINT one;
+ 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;
+ ?column?
+----------
+ t
+(1 row)
+
+-- 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;
+ ?column?
+----------
+ f
+(1 row)
+
+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;
+ ?column?
+----------
+ f
+(1 row)
+
+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)
+
+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;
+ a | b
+-----+---------
+ 0 | 0.09561
+ 787 | 7.8
+ 787 | 99.097
+ 787 | 324.78
+ 787 | 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;
+ a | b
+-----+---------
+ 0 | 0.09561
+ 787 | 7.8
+ 787 | 99.097
+ 787 | 324.78
+ 787 | 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"
+DETAIL: Key (a)=(1) already exists.
+ rollback to 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"
+DETAIL: Key (a)=(1) already exists.
+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;
+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;
+-- 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 only the last result in a multi-statement Query
+SELECT 1\; SELECT 2\; SELECT 3;
+ ?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;
+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);
+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;
+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;
+ERROR: VACUUM cannot run inside a transaction block
+SELECT 1\; COMMIT\; VACUUM;
+WARNING: there is no transaction in progress
+ERROR: VACUUM cannot run inside a transaction block
+-- we disallow savepoint-related commands in implicit-transaction state
+SELECT 1\; SAVEPOINT sp;
+ERROR: SAVEPOINT can only be used in transaction blocks
+SELECT 1\; COMMIT\; SAVEPOINT sp;
+WARNING: there is no transaction in progress
+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;
+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;
+-- 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/triggers.out b/src/test/regress/expected/triggers.out
new file mode 100644
index 0000000..9f60aea
--- /dev/null
+++ b/src/test/regress/expected/triggers.out
@@ -0,0 +1,3444 @@
+--
+-- TRIGGERS
+--
+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)
+
+-- Also check what happens when such a trigger runs before or after others
+create function f1_times_10() returns trigger as
+$$ begin new.f1 := new.f1 * 10; return new; end $$ language plpgsql;
+create trigger trigger_alpha
+ before insert or update on trigtest
+ for each row execute procedure f1_times_10();
+insert into trigtest values(1, 'foo');
+select * from trigtest;
+ f1 | f2
+----+-----
+ 10 | foo
+(1 row)
+
+update trigtest set f2 = f2 || 'bar';
+select * from trigtest;
+ f1 | f2
+----+-----
+ 10 | foo
+(1 row)
+
+delete from trigtest;
+select * from trigtest;
+ f1 | f2
+----+----
+(0 rows)
+
+create trigger trigger_zed
+ before insert or update on trigtest
+ for each row execute procedure f1_times_10();
+insert into trigtest values(1, 'foo');
+select * from trigtest;
+ f1 | f2
+-----+-----
+ 100 | foo
+(1 row)
+
+update trigtest set f2 = f2 || 'bar';
+select * from trigtest;
+ f1 | f2
+------+-----
+ 1000 | foo
+(1 row)
+
+delete from trigtest;
+select * from trigtest;
+ f1 | f2
+----+----
+(0 rows)
+
+drop trigger trigger_alpha on trigtest;
+insert into trigtest values(1, 'foo');
+select * from trigtest;
+ f1 | f2
+----+-----
+ 10 | foo
+(1 row)
+
+update trigtest set f2 = f2 || 'bar';
+select * from trigtest;
+ f1 | f2
+-----+-----
+ 100 | foo
+(1 row)
+
+delete from trigtest;
+select * from trigtest;
+ f1 | f2
+----+----
+(0 rows)
+
+drop table trigtest;
+-- Check behavior with an implicit column default, too (bug #16644)
+create table trigtest (
+ a integer,
+ b bool default true not null,
+ c text default 'xyzzy' not null);
+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);
+select * from trigtest;
+ a | b | c
+---+---+-------
+ 1 | t | xyzzy
+(1 row)
+
+alter table trigtest add column d integer default 42 not null;
+select * from trigtest;
+ a | b | c | d
+---+---+-------+----
+ 1 | t | xyzzy | 42
+(1 row)
+
+update trigtest set a = 2 where a = 1 returning *;
+ a | b | c | d
+---+---+-------+----
+ 1 | t | xyzzy | 42
+(1 row)
+
+select * from trigtest;
+ a | b | c | d
+---+---+-------+----
+ 1 | t | xyzzy | 42
+(1 row)
+
+alter table trigtest drop column b;
+select * from trigtest;
+ a | c | d
+---+-------+----
+ 1 | xyzzy | 42
+(1 row)
+
+update trigtest set a = 2 where a = 1 returning *;
+ a | c | d
+---+-------+----
+ 1 | xyzzy | 42
+(1 row)
+
+select * from trigtest;
+ a | c | d
+---+-------+----
+ 1 | xyzzy | 42
+(1 row)
+
+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;
+ 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;
+ price_id | price_val | price_on | price_off
+----------+-----------+----------+-----------
+ 1 | 1 | 10 | 999999
+ 3 | 3 | 30 | 999999
+ 2 | 2 | 20 | 40
+(3 rows)
+
+-- what do we see ?
+-- get current prices
+select * from tttest where price_off = 999999;
+ 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;
+select * from tttest;
+ price_id | price_val | price_on | price_off
+----------+-----------+----------+-----------
+ 1 | 1 | 10 | 999999
+ 2 | 2 | 20 | 40
+ 3 | 30 | 50 | 999999
+ 3 | 3 | 30 | 50
+(4 rows)
+
+-- now we want to change pric_id in ALL tuples
+-- this gets us not what we need
+update tttest set price_id = 5 where price_id = 3;
+select * from tttest;
+ price_id | price_val | price_on | price_off
+----------+-----------+----------+-----------
+ 1 | 1 | 10 | 999999
+ 2 | 2 | 20 | 40
+ 3 | 3 | 30 | 50
+ 5 | 30 | 60 | 999999
+ 3 | 30 | 50 | 60
+(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;
+ 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;
+ 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;
+ price_id | price_val | price_on | price_off
+----------+-----------+----------+-----------
+ 2 | 2 | 20 | 40
+ 5 | 3 | 30 | 50
+ 5 | 30 | 50 | 999999
+ 1 | 1 | -1 | 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
+UPDATE main_table SET a = 50, b = 60;
+NOTICE: trigger_func(modified_any) called: action = UPDATE, when = BEFORE, level = ROW
+NOTICE: trigger_func(modified_any) 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(modified_a) 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
+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)
+
+-- Test RENAME TRIGGER
+ALTER TRIGGER modified_a ON main_table RENAME TO modified_modified_a;
+SELECT count(*) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a';
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_modified_a';
+ count
+-------
+ 1
+(1 row)
+
+DROP TRIGGER modified_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 WHEN condition accessing system columns.
+create table table_with_oids(a int);
+insert into table_with_oids values (1);
+create trigger oid_unchanged_trig after update on table_with_oids
+ for each row
+ when (new.tableoid = old.tableoid AND new.tableoid <> 0)
+ execute procedure trigger_func('after_upd_oid_unchanged');
+update table_with_oids set a = a + 1;
+NOTICE: trigger_func(after_upd_oid_unchanged) called: action = UPDATE, when = AFTER, level = ROW
+drop table table_with_oids;
+-- 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
+ALTER TABLE main_table DROP COLUMN b;
+ERROR: cannot drop column b of table main_table because other objects depend on it
+DETAIL: trigger after_upd_b_row_trig on table main_table depends on column b of table main_table
+trigger after_upd_a_b_row_trig on table main_table depends on column b of table main_table
+trigger after_upd_b_stmt_trig on table main_table depends on column b of table main_table
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- 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_TABLE_NAME, 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;
+ 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
+--
+CREATE TABLE trigger_test (f1 int, f2 text, f3 text);
+-- 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, 'foo', 'bar');
+INSERT INTO trigger_test VALUES(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;
+SET 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;
+ 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_TABLE_NAME, 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
+-- 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 (20,21);
+NOTICE: main_view BEFORE DELETE STATEMENT (before_view_del_stmt)
+NOTICE: main_view INSTEAD OF DELETE ROW (instead_of_del)
+NOTICE: OLD: (21,10)
+NOTICE: main_view INSTEAD OF DELETE ROW (instead_of_del)
+NOTICE: OLD: (20,31)
+NOTICE: main_view INSTEAD OF DELETE ROW (instead_of_del)
+NOTICE: OLD: (21,32)
+NOTICE: main_view AFTER DELETE STATEMENT (after_view_del_stmt)
+DELETE 3
+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
+\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;
+ city_id | city_name | population | country_name | continent
+---------+---------------+------------+--------------+---------------
+ 1 | Tokyo | 13010279 | Japan | Asia
+ 123456 | New York | 8391881 | USA | North America
+ 2 | London | 7556900 | UK | Europe
+ 3 | Washington DC | 599657 | 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, depth_b, 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, 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
+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;
+ 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: "my_view" is a view
+DETAIL: Triggers on views cannot have transition tables.
+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: "parted_trig" is a partitioned table
+DETAIL: Triggers on partitioned tables cannot have transition tables.
+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)
+
+drop trigger trg1 on trigpart1; -- fail
+ERROR: cannot drop trigger trg1 on table trigpart1 because trigger trg1 on table trigpart requires it
+HINT: You can drop trigger trg1 on table trigpart instead.
+drop trigger trg1 on trigpart2; -- fail
+ERROR: cannot drop trigger trg1 on table trigpart2 because trigger trg1 on table trigpart requires it
+HINT: You can drop trigger trg1 on table trigpart instead.
+drop trigger trg1 on trigpart3; -- fail
+ERROR: cannot drop trigger trg1 on table trigpart3 because trigger trg1 on table trigpart requires it
+HINT: You can drop trigger trg1 on table trigpart instead.
+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 | t
+(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;
+-- check display of unrelated triggers
+create trigger samename after delete on trigpart execute function trigger_nothing();
+create trigger samename after delete on trigpart1 execute function trigger_nothing();
+\d trigpart1
+ Table "public.trigpart1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition of: trigpart FOR VALUES FROM (0) TO (1000)
+Triggers:
+ samename AFTER DELETE ON trigpart1 FOR EACH STATEMENT EXECUTE FUNCTION trigger_nothing()
+ trg1 AFTER INSERT ON trigpart1 FOR EACH ROW EXECUTE FUNCTION trigger_nothing(), ON TABLE trigpart
+
+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;
+-- Verify propagation of trigger arguments to partitions
+create table parted_trig (a int) partition by list (a);
+create table parted_trig1 partition of parted_trig for values in (1);
+create or replace function trigger_notice() returns trigger as $$
+ declare
+ arg1 text = TG_ARGV[0];
+ arg2 integer = TG_ARGV[1];
+ begin
+ raise notice 'trigger % on % % % for % args % %',
+ TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL, arg1, arg2;
+ return null;
+ end;
+ $$ language plpgsql;
+create trigger aaa after insert on parted_trig
+ for each row execute procedure trigger_notice('quirky', 1);
+-- Verify propagation of trigger arguments to partitions attached after creating trigger
+create table parted_trig2 partition of parted_trig for values in (2);
+create table parted_trig3 (like parted_trig);
+alter table parted_trig attach partition parted_trig3 for values in (3);
+insert into parted_trig values (1), (2), (3);
+NOTICE: trigger aaa on parted_trig1 AFTER INSERT for ROW args quirky 1
+NOTICE: trigger aaa on parted_trig2 AFTER INSERT for ROW args quirky 1
+NOTICE: trigger aaa on parted_trig3 AFTER INSERT for ROW args quirky 1
+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
+select tableoid::regclass, * from parted;
+ 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();
+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');
+NOTICE: aardvark <- woof!
+NOTICE: trigger parted_trig on parted1_constr AFTER INSERT for ROW: (a,b)=(1,aardvark)
+insert into parted1_constr values (2, 'aardwolf');
+NOTICE: aardwolf <- woof!
+NOTICE: trigger parted_trig on parted1_constr AFTER INSERT for ROW: (a,b)=(2,aardwolf)
+insert into parted_constr_ancestor values (3, 'aasvogel');
+NOTICE: aasvogel <- woof!
+NOTICE: trigger parted_trig on parted1_constr AFTER INSERT for ROW: (a,b)=(3,aasvogel)
+commit;
+NOTICE: trigger parted_trig_two on parted1_constr AFTER INSERT for ROW: (a,b)=(1,aardvark)
+NOTICE: trigger parted_trig_two on parted1_constr AFTER INSERT for ROW: (a,b)=(3,aasvogel)
+-- 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');
+NOTICE: aardvark <- woof!
+insert into parted1_constr values (2, 'aardwolf'), (3, 'aasvogel');
+NOTICE: aardwolf <- woof!
+NOTICE: aasvogel <- woof!
+commit;
+NOTICE: trigger parted_trig on parted1_constr AFTER INSERT for ROW: (a,b)=(1,aardvark)
+NOTICE: trigger parted_trig_two on parted1_constr AFTER INSERT for ROW: (a,b)=(1,aardvark)
+NOTICE: trigger parted_trig on parted1_constr AFTER INSERT for ROW: (a,b)=(2,aardwolf)
+NOTICE: trigger parted_trig on parted1_constr AFTER INSERT for ROW: (a,b)=(3,aasvogel)
+NOTICE: trigger parted_trig_two on parted1_constr AFTER INSERT for ROW: (a,b)=(3,aasvogel)
+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();
+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;
+ tgname | conname | tgrelid | tgconstrrelid | conrelid | confrelid
+----------------+----------------+--------------------+-------------------+--------------------+-----------
+ parted_trigger | parted_trigger | parted_trigger | parted_referenced | parted_trigger | -
+ parted_trigger | parted_trigger | parted_trigger_1 | parted_referenced | parted_trigger_1 | -
+ parted_trigger | parted_trigger | parted_trigger_2 | parted_referenced | parted_trigger_2 | -
+ parted_trigger | parted_trigger | parted_trigger_3 | parted_referenced | parted_trigger_3 | -
+ parted_trigger | parted_trigger | parted_trigger_3_1 | parted_referenced | parted_trigger_3_1 | -
+ parted_trigger | parted_trigger | parted_trigger_3_2 | parted_referenced | parted_trigger_3_2 | -
+ parted_trigger | parted_trigger | unparted_trigger | parted_referenced | unparted_trigger | -
+(7 rows)
+
+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;
+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;
+ tgrelid | count
+---------------+-------
+ trg_clone | 1
+ trg_clone1 | 1
+ trg_clone2 | 1
+ trg_clone3 | 1
+ trg_clone_3_3 | 1
+(5 rows)
+
+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)
+
+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).
+--
+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();
+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;
+ 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
+--------------------+--------------------+---------------------+--------------------+--------------+------------------+--------------------+---------------+----------------------------+----------------------------
+ child1_delete_trig | DELETE | public | child1 | 1 | | STATEMENT | AFTER | old_table |
+ child1_insert_trig | INSERT | public | child1 | 1 | | STATEMENT | AFTER | | new_table
+ child1_update_trig | UPDATE | public | child1 | 1 | | STATEMENT | AFTER | old_table | new_table
+ child2_delete_trig | DELETE | public | child2 | 1 | | STATEMENT | AFTER | old_table |
+ child2_insert_trig | INSERT | public | child2 | 1 | | STATEMENT | AFTER | | new_table
+ child2_update_trig | UPDATE | public | child2 | 1 | | STATEMENT | AFTER | old_table | new_table
+ child3_delete_trig | DELETE | public | child3 | 1 | | STATEMENT | AFTER | old_table |
+ child3_insert_trig | INSERT | public | child3 | 1 | | STATEMENT | AFTER | | new_table
+ child3_update_trig | UPDATE | public | child3 | 1 | | STATEMENT | AFTER | old_table | new_table
+ parent_delete_trig | DELETE | public | parent | 1 | | STATEMENT | AFTER | old_table |
+ parent_insert_trig | INSERT | public | parent | 1 | | STATEMENT | AFTER | | new_table
+ parent_update_trig | UPDATE | public | parent | 1 | | STATEMENT | AFTER | old_table | new_table
+(12 rows)
+
+-- insert directly into children sees respective child-format tuples
+insert into child1 values ('AAA', 42);
+NOTICE: trigger = child1_insert_trig, new table = (AAA,42)
+insert into child2 values ('BBB', 42);
+NOTICE: trigger = child2_insert_trig, new table = (BBB,42)
+insert into child3 values (42, 'CCC');
+NOTICE: trigger = child3_insert_trig, new table = (42,CCC)
+-- update via parent sees parent-format tuples
+update parent set b = b + 1;
+NOTICE: trigger = parent_update_trig, old table = (AAA,42), (BBB,42), (CCC,42), new table = (AAA,43), (BBB,43), (CCC,43)
+-- delete via parent sees parent-format tuples
+delete from parent;
+NOTICE: trigger = parent_delete_trig, old table = (AAA,43), (BBB,43), (CCC,43)
+-- insert into parent sees parent-format tuples
+insert into parent values ('AAA', 42);
+NOTICE: trigger = parent_insert_trig, new table = (AAA,42)
+insert into parent values ('BBB', 42);
+NOTICE: trigger = parent_insert_trig, new table = (BBB,42)
+insert into parent values ('CCC', 42);
+NOTICE: trigger = parent_insert_trig, new table = (CCC,42)
+-- delete from children sees respective child-format tuples
+delete from child1;
+NOTICE: trigger = child1_delete_trig, old table = (AAA,42)
+delete from child2;
+NOTICE: trigger = child2_delete_trig, old table = (BBB,42)
+delete from child3;
+NOTICE: trigger = child3_delete_trig, old table = (42,CCC)
+-- copy into parent sees parent-format tuples
+copy parent (a, b) from stdin;
+NOTICE: trigger = parent_insert_trig, new table = (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;
+NOTICE: trigger = parent_delete_trig, old table = (AAA,42), (BBB,42), (CCC,42)
+-- 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;
+NOTICE: trigger = parent_insert_trig, new table = (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);
+NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,1066)
+-- copy, parent trigger sees post-modification parent-format tuple
+copy parent (a, b) from stdin;
+NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,1234)
+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();
+ERROR: ROW triggers with transition tables are not supported on partitions
+-- 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');
+ERROR: trigger "child_row_trig" prevents table "child" from becoming a partition
+DETAIL: ROW triggers with transition tables are not supported on partitions
+-- 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);
+NOTICE: trigger = child1_insert_trig, new table = (AAA,42)
+insert into child2 values (42, 'BBB');
+NOTICE: trigger = child2_insert_trig, new table = (42,BBB)
+insert into child3 values ('CCC', 42, 'foo');
+NOTICE: trigger = child3_insert_trig, new table = (CCC,42,foo)
+-- update via parent sees parent-format tuples
+update parent set b = b + 1;
+NOTICE: trigger = parent_update_trig, old table = (AAA,42), (BBB,42), (CCC,42), new table = (AAA,43), (BBB,43), (CCC,43)
+-- delete via parent sees parent-format tuples
+delete from parent;
+NOTICE: trigger = parent_delete_trig, old table = (AAA,43), (BBB,43), (CCC,43)
+-- reinsert values into children for next test...
+insert into child1 values ('AAA', 42);
+NOTICE: trigger = child1_insert_trig, new table = (AAA,42)
+insert into child2 values (42, 'BBB');
+NOTICE: trigger = child2_insert_trig, new table = (42,BBB)
+insert into child3 values ('CCC', 42, 'foo');
+NOTICE: trigger = child3_insert_trig, new table = (CCC,42,foo)
+-- delete from children sees respective child-format tuples
+delete from child1;
+NOTICE: trigger = child1_delete_trig, old table = (AAA,42)
+delete from child2;
+NOTICE: trigger = child2_delete_trig, old table = (42,BBB)
+delete from child3;
+NOTICE: trigger = child3_delete_trig, old table = (CCC,42,foo)
+-- copy into parent sees parent-format tuples (no rerouting, so these
+-- are really inserted into the parent)
+copy parent (a, b) from stdin;
+NOTICE: trigger = parent_insert_trig, new table = (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 copyfrom.c if there are indexes)
+create index on parent(b);
+copy parent (a, b) from stdin;
+NOTICE: trigger = parent_insert_trig, new table = (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;
+NOTICE: trigger = parent_delete_trig, old table = (AAA,42), (BBB,42), (CCC,42), (DDD,42)
+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();
+ERROR: ROW triggers with transition tables are not supported on inheritance children
+-- 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;
+ERROR: trigger "child_row_trig" prevents table "child" from becoming an inheritance child
+DETAIL: ROW triggers with transition tables are not supported in inheritance hierarchies.
+-- 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 child, parent;
+--
+-- 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();
+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');
+NOTICE: trigger = table2_trig, new table = ("hello world")
+NOTICE: trigger = table1_trig, new table = (42)
+with wcte as (insert into table1 values (43))
+ insert into table1 values (44);
+NOTICE: trigger = table1_trig, new table = (43), (44)
+select * from table1;
+ a
+----
+ 42
+ 44
+ 43
+(3 rows)
+
+select * from table2;
+ a
+-------------
+ hello world
+(1 row)
+
+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;
+NOTICE: trigger = my_table_update_trig, old table = <NULL>, new table = <NULL>
+NOTICE: trigger = my_table_insert_trig, new table = (1,AAA), (2,BBB)
+-- 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;
+NOTICE: trigger = my_table_update_trig, old table = (1,AAA), (2,BBB), new table = (1,AAA:AAA), (2,BBB:BBB)
+NOTICE: trigger = my_table_insert_trig, new table = (3,CCC), (4,DDD)
+-- updates only
+insert into my_table values (3, 'CCC'), (4, 'DDD')
+ on conflict (a) do
+ update set b = my_table.b || ':' || excluded.b;
+NOTICE: trigger = my_table_update_trig, old table = (3,CCC), (4,DDD), new table = (3,CCC:CCC), (4,DDD:DDD)
+NOTICE: trigger = my_table_insert_trig, new table = <NULL>
+--
+-- 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;
+NOTICE: trigger = iocdu_tt_parted_update_trig, old table = <NULL>, new table = <NULL>
+NOTICE: trigger = iocdu_tt_parted_insert_trig, new table = (1,AAA), (2,BBB)
+-- 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;
+NOTICE: trigger = iocdu_tt_parted_update_trig, old table = (1,AAA), (2,BBB), new table = (1,AAA:AAA), (2,BBB:BBB)
+NOTICE: trigger = iocdu_tt_parted_insert_trig, new table = (3,CCC), (4,DDD)
+-- 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;
+NOTICE: trigger = iocdu_tt_parted_update_trig, old table = (3,CCC), (4,DDD), new table = (3,CCC:CCC), (4,DDD:DDD)
+NOTICE: trigger = iocdu_tt_parted_insert_trig, new table = <NULL>
+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();
+ERROR: transition tables cannot be specified for triggers with more than one event
+--
+-- 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();
+ERROR: transition tables cannot be specified for triggers with column lists
+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');
+NOTICE: trigger_func(trig_table) called: action = INSERT, when = BEFORE, level = STATEMENT
+NOTICE: trigger = trig_table_insert_trig, new table = (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';
+NOTICE: trigger_func(trig_table) called: action = UPDATE, when = BEFORE, level = STATEMENT
+NOTICE: trigger = trig_table_update_trig, old table = (1,"one a"), (1,"one b"), new table = (11,"one a"), (11,"one b")
+select * from trig_table;
+ a | b
+----+---------
+ 2 | two a
+ 2 | two b
+ 3 | three a
+ 3 | three b
+ 11 | one a
+ 11 | one b
+(6 rows)
+
+delete from refd_table where length(b) = 3;
+NOTICE: trigger_func(trig_table) called: action = DELETE, when = BEFORE, level = STATEMENT
+NOTICE: trigger = trig_table_delete_trig, old table = (2,"two a"), (2,"two b"), (11,"one a"), (11,"one b")
+select * from trig_table;
+ a | b
+---+---------
+ 3 | three a
+ 3 | three b
+(2 rows)
+
+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;
+NOTICE: trigger_func(self_ref) called: action = DELETE, when = BEFORE, level = STATEMENT
+NOTICE: trigger = self_ref_r_trig, old table = (1,), (2,1)
+NOTICE: trigger_func(self_ref) called: action = DELETE, when = BEFORE, level = STATEMENT
+NOTICE: trigger = self_ref_r_trig, old table = (1,), (2,1)
+NOTICE: trigger = self_ref_s_trig, old table = (1,), (2,1)
+NOTICE: trigger = self_ref_r_trig, old table = (3,2)
+NOTICE: trigger = self_ref_s_trig, old table = (3,2)
+-- 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;
+NOTICE: trigger_func(self_ref) called: action = DELETE, when = BEFORE, level = STATEMENT
+NOTICE: trigger = self_ref_s_trig, old table = (1,), (2,1), (3,2), (4,3)
+drop table self_ref;
+-- cleanup
+drop function dump_insert();
+drop function dump_update();
+drop function dump_delete();
+--
+-- 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 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;
+-- verify transition table conversion slot's lifetime
+-- https://postgr.es/m/39a71864-b120-5a5c-8cc5-c632b6f16761@amazon.com
+create table convslot_test_parent (col1 text primary key);
+create table convslot_test_child (col1 text primary key,
+ foreign key (col1) references convslot_test_parent(col1) on delete cascade on update cascade
+);
+alter table convslot_test_child add column col2 text not null default 'tutu';
+insert into convslot_test_parent(col1) values ('1');
+insert into convslot_test_child(col1) values ('1');
+insert into convslot_test_parent(col1) values ('3');
+insert into convslot_test_child(col1) values ('3');
+create or replace function trigger_function1()
+returns trigger
+language plpgsql
+AS $$
+begin
+raise notice 'trigger = %, old_table = %',
+ TG_NAME,
+ (select string_agg(old_table::text, ', ' order by col1) from old_table);
+return null;
+end; $$;
+create or replace function trigger_function2()
+returns trigger
+language plpgsql
+AS $$
+begin
+raise notice 'trigger = %, new table = %',
+ TG_NAME,
+ (select string_agg(new_table::text, ', ' order by col1) from new_table);
+return null;
+end; $$;
+create trigger but_trigger after update on convslot_test_child
+referencing new table as new_table
+for each statement execute function trigger_function2();
+update convslot_test_parent set col1 = col1 || '1';
+NOTICE: trigger = but_trigger, new table = (11,tutu), (31,tutu)
+create or replace function trigger_function3()
+returns trigger
+language plpgsql
+AS $$
+begin
+raise notice 'trigger = %, old_table = %, new table = %',
+ TG_NAME,
+ (select string_agg(old_table::text, ', ' order by col1) from old_table),
+ (select string_agg(new_table::text, ', ' order by col1) from new_table);
+return null;
+end; $$;
+create trigger but_trigger2 after update on convslot_test_child
+referencing old table as old_table new table as new_table
+for each statement execute function trigger_function3();
+update convslot_test_parent set col1 = col1 || '1';
+NOTICE: trigger = but_trigger, new table = (111,tutu), (311,tutu)
+NOTICE: trigger = but_trigger2, old_table = (11,tutu), (31,tutu), new table = (111,tutu), (311,tutu)
+create trigger bdt_trigger after delete on convslot_test_child
+referencing old table as old_table
+for each statement execute function trigger_function1();
+delete from convslot_test_parent;
+NOTICE: trigger = bdt_trigger, old_table = (111,tutu), (311,tutu)
+drop table convslot_test_child, convslot_test_parent;
diff --git a/src/test/regress/expected/truncate.out b/src/test/regress/expected/truncate.out
new file mode 100644
index 0000000..1e88e86
--- /dev/null
+++ b/src/test/regress/expected/truncate.out
@@ -0,0 +1,594 @@
+-- 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;
+ col1
+------
+ 1
+ 2
+(2 rows)
+
+-- 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');
+BEGIN;
+SELECT * FROM trunc_f;
+ col1
+------
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+TRUNCATE trunc_f;
+SELECT * FROM trunc_f;
+ col1
+------
+(0 rows)
+
+ROLLBACK;
+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;
+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;
+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;
+DROP TABLE trunc_f CASCADE;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table trunc_fa
+drop cascades to table trunc_faa
+drop cascades to table trunc_fb
+-- 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'));
+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));
+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)
+
+-- check rollback of a RESTART IDENTITY operation
+BEGIN;
+TRUNCATE truncate_a RESTART IDENTITY;
+INSERT INTO truncate_a DEFAULT VALUES;
+SELECT * FROM truncate_a;
+ id | id1
+----+-----
+ 1 | 33
+(1 row)
+
+ROLLBACK;
+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/tsdicts.out b/src/test/regress/expected/tsdicts.out
new file mode 100644
index 0000000..c804293
--- /dev/null
+++ b/src/test/regress/expected/tsdicts.out
@@ -0,0 +1,689 @@
+--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)
+
+-- Test suitability of affix and dict files
+CREATE TEXT SEARCH DICTIONARY hunspell_err (
+ Template=ispell,
+ DictFile=ispell_sample,
+ AffFile=hunspell_sample_long
+);
+ERROR: invalid affix alias "GJUS"
+CREATE TEXT SEARCH DICTIONARY hunspell_err (
+ Template=ispell,
+ DictFile=ispell_sample,
+ AffFile=hunspell_sample_num
+);
+ERROR: invalid affix flag "SZ\"
+CREATE TEXT SEARCH DICTIONARY hunspell_invalid_1 (
+ Template=ispell,
+ DictFile=hunspell_sample_long,
+ AffFile=ispell_sample
+);
+CREATE TEXT SEARCH DICTIONARY hunspell_invalid_2 (
+ Template=ispell,
+ DictFile=hunspell_sample_long,
+ AffFile=hunspell_sample_num
+);
+CREATE TEXT SEARCH DICTIONARY hunspell_invalid_3 (
+ Template=ispell,
+ DictFile=hunspell_sample_num,
+ AffFile=ispell_sample
+);
+CREATE TEXT SEARCH DICTIONARY hunspell_err (
+ Template=ispell,
+ DictFile=hunspell_sample_num,
+ AffFile=hunspell_sample_long
+);
+ERROR: invalid affix alias "302,301,202,303"
+-- 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)
+
+-- test altering boolean parameters
+SELECT dictinitoption FROM pg_ts_dict WHERE dictname = 'synonym';
+ dictinitoption
+-----------------------------
+ synonyms = 'synonym_sample'
+(1 row)
+
+ALTER TEXT SEARCH DICTIONARY synonym (CaseSensitive = 1);
+SELECT ts_lexize('synonym', 'PoStGrEs');
+ ts_lexize
+-----------
+
+(1 row)
+
+SELECT dictinitoption FROM pg_ts_dict WHERE dictname = 'synonym';
+ dictinitoption
+------------------------------------------------
+ synonyms = 'synonym_sample', casesensitive = 1
+(1 row)
+
+ALTER TEXT SEARCH DICTIONARY synonym (CaseSensitive = 2); -- fail
+ERROR: casesensitive requires a Boolean value
+ALTER TEXT SEARCH DICTIONARY synonym (CaseSensitive = off);
+SELECT ts_lexize('synonym', 'PoStGrEs');
+ ts_lexize
+-----------
+ {pgsql}
+(1 row)
+
+SELECT dictinitoption FROM pg_ts_dict WHERE dictname = 'synonym';
+ dictinitoption
+----------------------------------------------------
+ synonyms = 'synonym_sample', casesensitive = 'off'
+(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/tsearch.out b/src/test/regress/expected/tsearch.out
new file mode 100644
index 0000000..45b92a6
--- /dev/null
+++ b/src/test/regress/expected/tsearch.out
@@ -0,0 +1,2853 @@
+--
+-- 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)
+
+-- 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);
+SET enable_seqscan=OFF;
+SET enable_indexscan=ON;
+SET enable_bitmapscan=OFF;
+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';
+ 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)
+
+SET enable_indexscan=OFF;
+SET enable_bitmapscan=ON;
+explain (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+ QUERY PLAN
+-------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on test_tsvector
+ Recheck Cond: (a @@ '''wr'' | ''qh'''::tsquery)
+ -> Bitmap Index Scan on wowidx
+ Index Cond: (a @@ '''wr'' | ''qh'''::tsquery)
+(5 rows)
+
+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)
+
+-- Test siglen parameter of GiST tsvector_ops
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(foo=1));
+ERROR: unrecognized parameter "foo"
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=0));
+ERROR: value 0 out of bounds for option "siglen"
+DETAIL: Valid values are between "1" and "2024".
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=2048));
+ERROR: value 2048 out of bounds for option "siglen"
+DETAIL: Valid values are between "1" and "2024".
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100,foo='bar'));
+ERROR: unrecognized parameter "foo"
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100, siglen = 200));
+ERROR: parameter "siglen" specified more than once
+CREATE INDEX wowidx2 ON test_tsvector USING gist (a tsvector_ops(siglen=1));
+\d test_tsvector
+ Table "public.test_tsvector"
+ Column | Type | Collation | Nullable | Default
+--------+----------+-----------+----------+---------
+ t | text | | |
+ a | tsvector | | |
+Indexes:
+ "wowidx" gist (a)
+ "wowidx2" gist (a tsvector_ops (siglen='1'))
+
+DROP INDEX wowidx;
+EXPLAIN (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+ QUERY PLAN
+-------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on test_tsvector
+ Recheck Cond: (a @@ '''wr'' | ''qh'''::tsquery)
+ -> Bitmap Index Scan on wowidx2
+ Index Cond: (a @@ '''wr'' | ''qh'''::tsquery)
+(5 rows)
+
+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)
+
+DROP INDEX wowidx2;
+CREATE INDEX wowidx ON test_tsvector USING gist (a tsvector_ops(siglen=484));
+\d test_tsvector
+ Table "public.test_tsvector"
+ Column | Type | Collation | Nullable | Default
+--------+----------+-----------+----------+---------
+ t | text | | |
+ a | tsvector | | |
+Indexes:
+ "wowidx" gist (a tsvector_ops (siglen='484'))
+
+EXPLAIN (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+ QUERY PLAN
+-------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on test_tsvector
+ Recheck Cond: (a @@ '''wr'' | ''qh'''::tsquery)
+ -> Bitmap Index Scan on wowidx
+ Index Cond: (a @@ '''wr'' | ''qh'''::tsquery)
+(5 rows)
+
+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)
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+DROP INDEX wowidx;
+CREATE INDEX wowidx ON test_tsvector USING gin (a);
+SET enable_seqscan=OFF;
+-- GIN only supports bitmapscan, so no need to test plain indexscan
+explain (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+ QUERY PLAN
+-------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on test_tsvector
+ Recheck Cond: (a @@ '''wr'' | ''qh'''::tsquery)
+ -> Bitmap Index Scan on wowidx
+ Index Cond: (a @@ '''wr'' | ''qh'''::tsquery)
+(5 rows)
+
+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)
+
+-- Test optimization of non-empty GIN_SEARCH_MODE_ALL queries
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qh';
+ QUERY PLAN
+-----------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on test_tsvector
+ Recheck Cond: (a @@ '!''qh'''::tsquery)
+ -> Bitmap Index Scan on wowidx
+ Index Cond: (a @@ '!''qh'''::tsquery)
+(5 rows)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qh';
+ count
+-------
+ 410
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr' AND a @@ '!qh';
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on test_tsvector
+ Recheck Cond: ((a @@ '''wr'''::tsquery) AND (a @@ '!''qh'''::tsquery))
+ -> Bitmap Index Scan on wowidx
+ Index Cond: ((a @@ '''wr'''::tsquery) AND (a @@ '!''qh'''::tsquery))
+(5 rows)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr' AND a @@ '!qh';
+ count
+-------
+ 60
+(1 row)
+
+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 <fr>qwer jf sdjk<we hjwer <werrwe> ewr1> ewri2 <a href="qwe<qwe>">
+/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
+<i <b> 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 | <fr>
+ 1 | qwer
+ 12 |
+ 1 | jf
+ 12 |
+ 1 | sdjk
+ 12 | <
+ 1 | we
+ 12 |
+ 1 | hjwer
+ 12 |
+ 13 | <werrwe>
+ 12 |
+ 3 | ewr1
+ 12 | >
+ 3 | ewri2
+ 12 |
+ 13 | <a href="qwe<qwe>">
+ 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 | <b>
+ 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 <fr>qwer jf sdjk<we hjwer <werrwe> ewr1> ewri2 <a href="qwe<qwe>">
+/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
+<i <b> 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 <fr>qwer jf sdjk<we hjwer <werrwe> ewr1> ewri2 <a href="qwe<qwe>">
+/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
+<i <b> wow < jqw <> qwerty'));
+ length
+--------
+ 56
+(1 row)
+
+-- ts_debug
+SELECT * from ts_debug('english', '<myns:foo-bar_baz.blurfl>abc&nm1;def&#xa9;ghi&#245;jkl</myns:foo-bar_baz.blurfl>');
+ alias | description | token | dictionaries | dictionary | lexemes
+-----------+-----------------+----------------------------+----------------+--------------+---------
+ tag | XML tag | <myns:foo-bar_baz.blurfl> | {} | |
+ 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 | &#xa9; | {} | |
+ asciiword | Word, all ASCII | ghi | {english_stem} | english_stem | {ghi}
+ entity | XML entity | &#245; | {} | |
+ asciiword | Word, all ASCII | jkl | {english_stem} | english_stem | {jkl}
+ tag | XML tag | </myns:foo-bar_baz.blurfl> | {} | |
+(9 rows)
+
+-- check parsing of URLs
+SELECT * from ts_debug('english', 'http://www.harewoodsolutions.co.uk/press.aspx</span>');
+ 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 | </span> | {} | |
+(5 rows)
+
+SELECT * from ts_debug('english', 'http://aew.wer0c.ewr/id?ad=qwe&dw<span>');
+ 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 | <span> | {} | |
+(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
+-----------------------------------------
+ <b>painted</b> Ocean. +
+ <b>Water</b>, <b>water</b>, every where+
+ And all the boards did shrink; +
+ <b>Water</b>, <b>water</b>, 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
+----------------------------------
+ <b>breath</b> nor <b>motion</b>,+
+ As idle as a painted Ship +
+ Upon a painted Ocean. +
+ <b>Water</b>, <b>water</b>
+(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
+----------------------------------
+ <b>Ocean</b>. +
+ 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
+---------------------------------------
+ <b>painted</b> Ship +
+ Upon a <b>painted</b> <b>Ocean</b>.+
+ 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
+---------------------------------------------
+ <b>idle</b> as a <b>painted</b> <b>Ship</b>+
+ Upon a <b>painted</b> 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
+-------------------------------------------------------------------------------
+ <b>Lorem</b> ipsum <b>urna</b>. Nullam nullam <b>ullamcorper</b> <b>urna</b>
+(1 row)
+
+SELECT ts_headline('english', '
+<html>
+<!-- some comment -->
+<body>
+Sea view wow <u>foo bar</u> <i>qq</i>
+<a href="http://www.google.com/foo.bar.html" target="_blank">YES &nbsp;</a>
+ff-bg
+<script>
+ document.write(15);
+</script>
+</body>
+</html>',
+to_tsquery('english', 'sea&foo'), 'HighlightAll=true');
+ ts_headline
+-----------------------------------------------------------------------------
+ +
+ <html> +
+ <!-- some comment --> +
+ <body> +
+ <b>Sea</b> view wow <u><b>foo</b> bar</u> <i>qq</i> +
+ <a href="http://www.google.com/foo.bar.html" target="_blank">YES &nbsp;</a>+
+ ff-bg +
+ <script> +
+ document.write(15); +
+ </script> +
+ </body> +
+ </html>
+(1 row)
+
+SELECT ts_headline('simple', '1 2 3 1 3'::text, '1 <-> 3', 'MaxWords=2, MinWords=1');
+ ts_headline
+-------------------
+ <b>1</b> <b>3</b>
+(1 row)
+
+SELECT ts_headline('simple', '1 2 3 1 3'::text, '1 & 3', 'MaxWords=4, MinWords=1');
+ ts_headline
+---------------------
+ <b>1</b> 2 <b>3</b>
+(1 row)
+
+SELECT ts_headline('simple', '1 2 3 1 3'::text, '1 <-> 3', 'MaxWords=4, MinWords=1');
+ ts_headline
+----------------------------
+ <b>3</b> <b>1</b> <b>3</b>
+(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 <b>Ocean</b>. +
+ 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 <b>stuck</b>, 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. <b>Coleridge</b>
+(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 <b>stuck</b>, 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. <b>Coleridge</b>
+(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
+-------------------------------------------------------------------------------
+ <b>Lorem</b> ipsum <b>urna</b>. Nullam nullam <b>ullamcorper</b> <b>urna</b>
+(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);
+SET enable_seqscan=OFF;
+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)
+
+RESET enable_seqscan;
+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);
+SET enable_seqscan=OFF;
+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)
+
+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)
+
+RESET enable_seqscan;
+--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 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 ');
+ 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/tsrf.out b/src/test/regress/expected/tsrf.out
new file mode 100644
index 0000000..d47b5f6
--- /dev/null
+++ b/src/test/regress/expected/tsrf.out
@@ -0,0 +1,712 @@
+--
+-- tsrf - targetlist set returning function tests
+--
+-- simple srf
+SELECT generate_series(1, 3);
+ generate_series
+-----------------
+ 1
+ 2
+ 3
+(3 rows)
+
+-- parallel iteration
+SELECT generate_series(1, 3), generate_series(3,5);
+ generate_series | generate_series
+-----------------+-----------------
+ 1 | 3
+ 2 | 4
+ 3 | 5
+(3 rows)
+
+-- parallel iteration, different number of rows
+SELECT generate_series(1, 2), generate_series(1,4);
+ generate_series | generate_series
+-----------------+-----------------
+ 1 | 1
+ 2 | 2
+ | 3
+ | 4
+(4 rows)
+
+-- srf, with SRF argument
+SELECT generate_series(1, generate_series(1, 3));
+ generate_series
+-----------------
+ 1
+ 1
+ 2
+ 1
+ 2
+ 3
+(6 rows)
+
+-- but we've traditionally rejected the same in FROM
+SELECT * FROM generate_series(1, generate_series(1, 3));
+ERROR: set-returning functions must appear at top level of FROM
+LINE 1: SELECT * FROM generate_series(1, generate_series(1, 3));
+ ^
+-- srf, with two SRF arguments
+SELECT generate_series(generate_series(1,3), generate_series(2, 4));
+ generate_series
+-----------------
+ 1
+ 2
+ 2
+ 3
+ 3
+ 4
+(6 rows)
+
+-- check proper nesting of SRFs in different expressions
+explain (verbose, costs off)
+SELECT generate_series(1, generate_series(1, 3)), generate_series(2, 4);
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ ProjectSet
+ Output: generate_series(1, (generate_series(1, 3))), (generate_series(2, 4))
+ -> ProjectSet
+ Output: generate_series(1, 3), generate_series(2, 4)
+ -> Result
+(5 rows)
+
+SELECT generate_series(1, generate_series(1, 3)), generate_series(2, 4);
+ generate_series | generate_series
+-----------------+-----------------
+ 1 | 2
+ 1 | 3
+ 2 | 3
+ 1 | 4
+ 2 | 4
+ 3 | 4
+(6 rows)
+
+CREATE TABLE few(id int, dataa text, datab text);
+INSERT INTO few VALUES(1, 'a', 'foo'),(2, 'a', 'bar'),(3, 'b', 'bar');
+-- SRF with a provably-dummy relation
+explain (verbose, costs off)
+SELECT unnest(ARRAY[1, 2]) FROM few WHERE false;
+ QUERY PLAN
+--------------------------------------
+ ProjectSet
+ Output: unnest('{1,2}'::integer[])
+ -> Result
+ One-Time Filter: false
+(4 rows)
+
+SELECT unnest(ARRAY[1, 2]) FROM few WHERE false;
+ unnest
+--------
+(0 rows)
+
+-- SRF shouldn't prevent upper query from recognizing lower as dummy
+explain (verbose, costs off)
+SELECT * FROM few f1,
+ (SELECT unnest(ARRAY[1,2]) FROM few f2 WHERE false OFFSET 0) ss;
+ QUERY PLAN
+------------------------------------------------
+ Result
+ Output: f1.id, f1.dataa, f1.datab, ss.unnest
+ One-Time Filter: false
+(3 rows)
+
+SELECT * FROM few f1,
+ (SELECT unnest(ARRAY[1,2]) FROM few f2 WHERE false OFFSET 0) ss;
+ id | dataa | datab | unnest
+----+-------+-------+--------
+(0 rows)
+
+-- SRF output order of sorting is maintained, if SRF is not referenced
+SELECT few.id, generate_series(1,3) g FROM few ORDER BY id DESC;
+ id | g
+----+---
+ 3 | 1
+ 3 | 2
+ 3 | 3
+ 2 | 1
+ 2 | 2
+ 2 | 3
+ 1 | 1
+ 1 | 2
+ 1 | 3
+(9 rows)
+
+-- but SRFs can be referenced in sort
+SELECT few.id, generate_series(1,3) g FROM few ORDER BY id, g DESC;
+ id | g
+----+---
+ 1 | 3
+ 1 | 2
+ 1 | 1
+ 2 | 3
+ 2 | 2
+ 2 | 1
+ 3 | 3
+ 3 | 2
+ 3 | 1
+(9 rows)
+
+SELECT few.id, generate_series(1,3) g FROM few ORDER BY id, generate_series(1,3) DESC;
+ id | g
+----+---
+ 1 | 3
+ 1 | 2
+ 1 | 1
+ 2 | 3
+ 2 | 2
+ 2 | 1
+ 3 | 3
+ 3 | 2
+ 3 | 1
+(9 rows)
+
+-- it's weird to have ORDER BYs that increase the number of results
+SELECT few.id FROM few ORDER BY id, generate_series(1,3) DESC;
+ id
+----
+ 1
+ 1
+ 1
+ 2
+ 2
+ 2
+ 3
+ 3
+ 3
+(9 rows)
+
+-- SRFs are computed after aggregation
+SET enable_hashagg TO 0; -- stable output order
+SELECT few.dataa, count(*), min(id), max(id), unnest('{1,1,3}'::int[]) FROM few WHERE few.id = 1 GROUP BY few.dataa;
+ dataa | count | min | max | unnest
+-------+-------+-----+-----+--------
+ a | 1 | 1 | 1 | 1
+ a | 1 | 1 | 1 | 1
+ a | 1 | 1 | 1 | 3
+(3 rows)
+
+-- unless referenced in GROUP BY clause
+SELECT few.dataa, count(*), min(id), max(id), unnest('{1,1,3}'::int[]) FROM few WHERE few.id = 1 GROUP BY few.dataa, unnest('{1,1,3}'::int[]);
+ dataa | count | min | max | unnest
+-------+-------+-----+-----+--------
+ a | 2 | 1 | 1 | 1
+ a | 1 | 1 | 1 | 3
+(2 rows)
+
+SELECT few.dataa, count(*), min(id), max(id), unnest('{1,1,3}'::int[]) FROM few WHERE few.id = 1 GROUP BY few.dataa, 5;
+ dataa | count | min | max | unnest
+-------+-------+-----+-----+--------
+ a | 2 | 1 | 1 | 1
+ a | 1 | 1 | 1 | 3
+(2 rows)
+
+RESET enable_hashagg;
+-- check HAVING works when GROUP BY does [not] reference SRF output
+SELECT dataa, generate_series(1,1), count(*) FROM few GROUP BY 1 HAVING count(*) > 1;
+ dataa | generate_series | count
+-------+-----------------+-------
+ a | 1 | 2
+(1 row)
+
+SELECT dataa, generate_series(1,1), count(*) FROM few GROUP BY 1, 2 HAVING count(*) > 1;
+ dataa | generate_series | count
+-------+-----------------+-------
+ a | 1 | 2
+(1 row)
+
+-- it's weird to have GROUP BYs that increase the number of results
+SELECT few.dataa, count(*) FROM few WHERE dataa = 'a' GROUP BY few.dataa ORDER BY 2;
+ dataa | count
+-------+-------
+ a | 2
+(1 row)
+
+SELECT few.dataa, count(*) FROM few WHERE dataa = 'a' GROUP BY few.dataa, unnest('{1,1,3}'::int[]) ORDER BY 2;
+ dataa | count
+-------+-------
+ a | 2
+ a | 4
+(2 rows)
+
+-- SRFs are not allowed if they'd need to be conditionally executed
+SELECT q1, case when q1 > 0 then generate_series(1,3) else 0 end FROM int8_tbl;
+ERROR: set-returning functions are not allowed in CASE
+LINE 1: SELECT q1, case when q1 > 0 then generate_series(1,3) else 0...
+ ^
+HINT: You might be able to move the set-returning function into a LATERAL FROM item.
+SELECT q1, coalesce(generate_series(1,3), 0) FROM int8_tbl;
+ERROR: set-returning functions are not allowed in COALESCE
+LINE 1: SELECT q1, coalesce(generate_series(1,3), 0) FROM int8_tbl;
+ ^
+HINT: You might be able to move the set-returning function into a LATERAL FROM item.
+-- SRFs are not allowed in aggregate arguments
+SELECT min(generate_series(1, 3)) FROM few;
+ERROR: aggregate function calls cannot contain set-returning function calls
+LINE 1: SELECT min(generate_series(1, 3)) FROM few;
+ ^
+HINT: You might be able to move the set-returning function into a LATERAL FROM item.
+-- ... unless they're within a sub-select
+SELECT sum((3 = ANY(SELECT generate_series(1,4)))::int);
+ sum
+-----
+ 1
+(1 row)
+
+SELECT sum((3 = ANY(SELECT lag(x) over(order by x)
+ FROM generate_series(1,4) x))::int);
+ sum
+-----
+ 1
+(1 row)
+
+-- SRFs are not allowed in window function arguments, either
+SELECT min(generate_series(1, 3)) OVER() FROM few;
+ERROR: window function calls cannot contain set-returning function calls
+LINE 1: SELECT min(generate_series(1, 3)) OVER() FROM few;
+ ^
+HINT: You might be able to move the set-returning function into a LATERAL FROM item.
+-- SRFs are normally computed after window functions
+SELECT id,lag(id) OVER(), count(*) OVER(), generate_series(1,3) FROM few;
+ id | lag | count | generate_series
+----+-----+-------+-----------------
+ 1 | | 3 | 1
+ 1 | | 3 | 2
+ 1 | | 3 | 3
+ 2 | 1 | 3 | 1
+ 2 | 1 | 3 | 2
+ 2 | 1 | 3 | 3
+ 3 | 2 | 3 | 1
+ 3 | 2 | 3 | 2
+ 3 | 2 | 3 | 3
+(9 rows)
+
+-- unless referencing SRFs
+SELECT SUM(count(*)) OVER(PARTITION BY generate_series(1,3) ORDER BY generate_series(1,3)), generate_series(1,3) g FROM few GROUP BY g;
+ sum | g
+-----+---
+ 3 | 1
+ 3 | 2
+ 3 | 3
+(3 rows)
+
+-- sorting + grouping
+SELECT few.dataa, count(*), min(id), max(id), generate_series(1,3) FROM few GROUP BY few.dataa ORDER BY 5, 1;
+ dataa | count | min | max | generate_series
+-------+-------+-----+-----+-----------------
+ a | 2 | 1 | 2 | 1
+ b | 1 | 3 | 3 | 1
+ a | 2 | 1 | 2 | 2
+ b | 1 | 3 | 3 | 2
+ a | 2 | 1 | 2 | 3
+ b | 1 | 3 | 3 | 3
+(6 rows)
+
+-- grouping sets are a bit special, they produce NULLs in columns not actually NULL
+set enable_hashagg = false;
+SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab);
+ dataa | b | g | count
+-------+-----+---+-------
+ a | bar | 1 | 1
+ a | bar | 2 | 1
+ a | foo | 1 | 1
+ a | foo | 2 | 1
+ a | | 1 | 2
+ a | | 2 | 2
+ b | bar | 1 | 1
+ b | bar | 2 | 1
+ b | | 1 | 1
+ b | | 2 | 1
+ | | 1 | 3
+ | | 2 | 3
+ | bar | 1 | 2
+ | bar | 2 | 2
+ | foo | 1 | 1
+ | foo | 2 | 1
+(16 rows)
+
+SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab) ORDER BY dataa;
+ dataa | b | g | count
+-------+-----+---+-------
+ a | bar | 1 | 1
+ a | bar | 2 | 1
+ a | foo | 1 | 1
+ a | foo | 2 | 1
+ a | | 1 | 2
+ a | | 2 | 2
+ b | bar | 1 | 1
+ b | bar | 2 | 1
+ b | | 1 | 1
+ b | | 2 | 1
+ | | 1 | 3
+ | | 2 | 3
+ | bar | 1 | 2
+ | bar | 2 | 2
+ | foo | 1 | 1
+ | foo | 2 | 1
+(16 rows)
+
+SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab) ORDER BY g;
+ dataa | b | g | count
+-------+-----+---+-------
+ a | bar | 1 | 1
+ a | foo | 1 | 1
+ a | | 1 | 2
+ b | bar | 1 | 1
+ b | | 1 | 1
+ | | 1 | 3
+ | bar | 1 | 2
+ | foo | 1 | 1
+ | foo | 2 | 1
+ a | bar | 2 | 1
+ b | | 2 | 1
+ a | foo | 2 | 1
+ | bar | 2 | 2
+ a | | 2 | 2
+ | | 2 | 3
+ b | bar | 2 | 1
+(16 rows)
+
+SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g);
+ dataa | b | g | count
+-------+-----+---+-------
+ a | bar | 1 | 1
+ a | bar | 2 | 1
+ a | bar | | 2
+ a | foo | 1 | 1
+ a | foo | 2 | 1
+ a | foo | | 2
+ a | | | 4
+ b | bar | 1 | 1
+ b | bar | 2 | 1
+ b | bar | | 2
+ b | | | 2
+ | | | 6
+ | bar | 1 | 2
+ | bar | 2 | 2
+ | bar | | 4
+ | foo | 1 | 1
+ | foo | 2 | 1
+ | foo | | 2
+ a | | 1 | 2
+ b | | 1 | 1
+ | | 1 | 3
+ a | | 2 | 2
+ b | | 2 | 1
+ | | 2 | 3
+(24 rows)
+
+SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g) ORDER BY dataa;
+ dataa | b | g | count
+-------+-----+---+-------
+ a | foo | | 2
+ a | | | 4
+ a | | 2 | 2
+ a | bar | 1 | 1
+ a | bar | 2 | 1
+ a | bar | | 2
+ a | foo | 1 | 1
+ a | foo | 2 | 1
+ a | | 1 | 2
+ b | bar | 1 | 1
+ b | | | 2
+ b | | 1 | 1
+ b | bar | 2 | 1
+ b | bar | | 2
+ b | | 2 | 1
+ | | 2 | 3
+ | | | 6
+ | bar | 1 | 2
+ | bar | 2 | 2
+ | bar | | 4
+ | foo | 1 | 1
+ | foo | 2 | 1
+ | foo | | 2
+ | | 1 | 3
+(24 rows)
+
+SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g) ORDER BY g;
+ dataa | b | g | count
+-------+-----+---+-------
+ a | bar | 1 | 1
+ a | foo | 1 | 1
+ b | bar | 1 | 1
+ | bar | 1 | 2
+ | foo | 1 | 1
+ a | | 1 | 2
+ b | | 1 | 1
+ | | 1 | 3
+ a | | 2 | 2
+ b | | 2 | 1
+ | bar | 2 | 2
+ | | 2 | 3
+ | foo | 2 | 1
+ a | bar | 2 | 1
+ a | foo | 2 | 1
+ b | bar | 2 | 1
+ a | | | 4
+ b | bar | | 2
+ b | | | 2
+ | | | 6
+ a | foo | | 2
+ a | bar | | 2
+ | bar | | 4
+ | foo | | 2
+(24 rows)
+
+reset enable_hashagg;
+-- case with degenerate ORDER BY
+explain (verbose, costs off)
+select 'foo' as f, generate_series(1,2) as g from few order by 1;
+ QUERY PLAN
+----------------------------------------------
+ ProjectSet
+ Output: 'foo'::text, generate_series(1, 2)
+ -> Seq Scan on public.few
+ Output: id, dataa, datab
+(4 rows)
+
+select 'foo' as f, generate_series(1,2) as g from few order by 1;
+ f | g
+-----+---
+ foo | 1
+ foo | 2
+ foo | 1
+ foo | 2
+ foo | 1
+ foo | 2
+(6 rows)
+
+-- data modification
+CREATE TABLE fewmore AS SELECT generate_series(1,3) AS data;
+INSERT INTO fewmore VALUES(generate_series(4,5));
+SELECT * FROM fewmore;
+ data
+------
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+-- SRFs are not allowed in UPDATE (they once were, but it was nonsense)
+UPDATE fewmore SET data = generate_series(4,9);
+ERROR: set-returning functions are not allowed in UPDATE
+LINE 1: UPDATE fewmore SET data = generate_series(4,9);
+ ^
+-- SRFs are not allowed in RETURNING
+INSERT INTO fewmore VALUES(1) RETURNING generate_series(1,3);
+ERROR: set-returning functions are not allowed in RETURNING
+LINE 1: INSERT INTO fewmore VALUES(1) RETURNING generate_series(1,3)...
+ ^
+-- nor standalone VALUES (but surely this is a bug?)
+VALUES(1, generate_series(1,2));
+ERROR: set-returning functions are not allowed in VALUES
+LINE 1: VALUES(1, generate_series(1,2));
+ ^
+-- We allow tSRFs that are not at top level
+SELECT int4mul(generate_series(1,2), 10);
+ int4mul
+---------
+ 10
+ 20
+(2 rows)
+
+SELECT generate_series(1,3) IS DISTINCT FROM 2;
+ ?column?
+----------
+ t
+ f
+ t
+(3 rows)
+
+-- but SRFs in function RTEs must be at top level (annoying restriction)
+SELECT * FROM int4mul(generate_series(1,2), 10);
+ERROR: set-returning functions must appear at top level of FROM
+LINE 1: SELECT * FROM int4mul(generate_series(1,2), 10);
+ ^
+-- DISTINCT ON is evaluated before tSRF evaluation if SRF is not
+-- referenced either in ORDER BY or in the DISTINCT ON list. The ORDER
+-- BY reference can be implicitly generated, if there's no other ORDER BY.
+-- implicit reference (via implicit ORDER) to all columns
+SELECT DISTINCT ON (a) a, b, generate_series(1,3) g
+FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b);
+ a | b | g
+---+---+---
+ 1 | 1 | 1
+ 3 | 2 | 1
+ 5 | 3 | 1
+(3 rows)
+
+-- unreferenced in DISTINCT ON or ORDER BY
+SELECT DISTINCT ON (a) a, b, generate_series(1,3) g
+FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b)
+ORDER BY a, b DESC;
+ a | b | g
+---+---+---
+ 1 | 4 | 1
+ 1 | 4 | 2
+ 1 | 4 | 3
+ 3 | 2 | 1
+ 3 | 2 | 2
+ 3 | 2 | 3
+ 5 | 3 | 1
+ 5 | 3 | 2
+ 5 | 3 | 3
+(9 rows)
+
+-- referenced in ORDER BY
+SELECT DISTINCT ON (a) a, b, generate_series(1,3) g
+FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b)
+ORDER BY a, b DESC, g DESC;
+ a | b | g
+---+---+---
+ 1 | 4 | 3
+ 3 | 2 | 3
+ 5 | 3 | 3
+(3 rows)
+
+-- referenced in ORDER BY and DISTINCT ON
+SELECT DISTINCT ON (a, b, g) a, b, generate_series(1,3) g
+FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b)
+ORDER BY a, b DESC, g DESC;
+ a | b | g
+---+---+---
+ 1 | 4 | 3
+ 1 | 4 | 2
+ 1 | 4 | 1
+ 1 | 1 | 3
+ 1 | 1 | 2
+ 1 | 1 | 1
+ 3 | 2 | 3
+ 3 | 2 | 2
+ 3 | 2 | 1
+ 3 | 1 | 3
+ 3 | 1 | 2
+ 3 | 1 | 1
+ 5 | 3 | 3
+ 5 | 3 | 2
+ 5 | 3 | 1
+ 5 | 1 | 3
+ 5 | 1 | 2
+ 5 | 1 | 1
+(18 rows)
+
+-- only SRF mentioned in DISTINCT ON
+SELECT DISTINCT ON (g) a, b, generate_series(1,3) g
+FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b);
+ a | b | g
+---+---+---
+ 3 | 2 | 1
+ 5 | 1 | 2
+ 3 | 1 | 3
+(3 rows)
+
+-- LIMIT / OFFSET is evaluated after SRF evaluation
+SELECT a, generate_series(1,2) FROM (VALUES(1),(2),(3)) r(a) LIMIT 2 OFFSET 2;
+ a | generate_series
+---+-----------------
+ 2 | 1
+ 2 | 2
+(2 rows)
+
+-- SRFs are not allowed in LIMIT.
+SELECT 1 LIMIT generate_series(1,3);
+ERROR: set-returning functions are not allowed in LIMIT
+LINE 1: SELECT 1 LIMIT generate_series(1,3);
+ ^
+-- tSRF in correlated subquery, referencing table outside
+SELECT (SELECT generate_series(1,3) LIMIT 1 OFFSET few.id) FROM few;
+ generate_series
+-----------------
+ 2
+ 3
+
+(3 rows)
+
+-- tSRF in correlated subquery, referencing SRF outside
+SELECT (SELECT generate_series(1,3) LIMIT 1 OFFSET g.i) FROM generate_series(0,3) g(i);
+ generate_series
+-----------------
+ 1
+ 2
+ 3
+
+(4 rows)
+
+-- Operators can return sets too
+CREATE OPERATOR |@| (PROCEDURE = unnest, RIGHTARG = ANYARRAY);
+SELECT |@|ARRAY[1,2,3];
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+-- Some fun cases involving duplicate SRF calls
+explain (verbose, costs off)
+select generate_series(1,3) as x, generate_series(1,3) + 1 as xp1;
+ QUERY PLAN
+------------------------------------------------------------------
+ Result
+ Output: (generate_series(1, 3)), ((generate_series(1, 3)) + 1)
+ -> ProjectSet
+ Output: generate_series(1, 3)
+ -> Result
+(5 rows)
+
+select generate_series(1,3) as x, generate_series(1,3) + 1 as xp1;
+ x | xp1
+---+-----
+ 1 | 2
+ 2 | 3
+ 3 | 4
+(3 rows)
+
+explain (verbose, costs off)
+select generate_series(1,3)+1 order by generate_series(1,3);
+ QUERY PLAN
+------------------------------------------------------------------------
+ Sort
+ Output: (((generate_series(1, 3)) + 1)), (generate_series(1, 3))
+ Sort Key: (generate_series(1, 3))
+ -> Result
+ Output: ((generate_series(1, 3)) + 1), (generate_series(1, 3))
+ -> ProjectSet
+ Output: generate_series(1, 3)
+ -> Result
+(8 rows)
+
+select generate_series(1,3)+1 order by generate_series(1,3);
+ ?column?
+----------
+ 2
+ 3
+ 4
+(3 rows)
+
+-- Check that SRFs of same nesting level run in lockstep
+explain (verbose, costs off)
+select generate_series(1,3) as x, generate_series(3,6) + 1 as y;
+ QUERY PLAN
+------------------------------------------------------------------
+ Result
+ Output: (generate_series(1, 3)), ((generate_series(3, 6)) + 1)
+ -> ProjectSet
+ Output: generate_series(1, 3), generate_series(3, 6)
+ -> Result
+(5 rows)
+
+select generate_series(1,3) as x, generate_series(3,6) + 1 as y;
+ x | y
+---+---
+ 1 | 4
+ 2 | 5
+ 3 | 6
+ | 7
+(4 rows)
+
+-- Clean up
+DROP TABLE few;
+DROP TABLE fewmore;
diff --git a/src/test/regress/expected/tstypes.out b/src/test/regress/expected/tstypes.out
new file mode 100644
index 0000000..2601e31
--- /dev/null
+++ b/src/test/regress/expected/tstypes.out
@@ -0,0 +1,1391 @@
+-- deal with numeric instability of ts_rank
+SET extra_float_digits = 0;
+--Base tsvector test
+SELECT '1'::tsvector;
+ tsvector
+----------
+ '1'
+(1 row)
+
+SELECT '1 '::tsvector;
+ tsvector
+----------
+ '1'
+(1 row)
+
+SELECT ' 1'::tsvector;
+ tsvector
+----------
+ '1'
+(1 row)
+
+SELECT ' 1 '::tsvector;
+ tsvector
+----------
+ '1'
+(1 row)
+
+SELECT '1 2'::tsvector;
+ tsvector
+----------
+ '1' '2'
+(1 row)
+
+SELECT '''1 2'''::tsvector;
+ tsvector
+----------
+ '1 2'
+(1 row)
+
+SELECT E'''1 \\''2'''::tsvector;
+ tsvector
+----------
+ '1 ''2'
+(1 row)
+
+SELECT E'''1 \\''2''3'::tsvector;
+ tsvector
+-------------
+ '1 ''2' '3'
+(1 row)
+
+SELECT E'''1 \\''2'' 3'::tsvector;
+ tsvector
+-------------
+ '1 ''2' '3'
+(1 row)
+
+SELECT E'''1 \\''2'' '' 3'' 4 '::tsvector;
+ tsvector
+------------------
+ ' 3' '1 ''2' '4'
+(1 row)
+
+SELECT $$'\\as' ab\c ab\\c AB\\\c ab\\\\c$$::tsvector;
+ tsvector
+----------------------------------------
+ 'AB\\c' '\\as' 'ab\\\\c' 'ab\\c' 'abc'
+(1 row)
+
+SELECT tsvectorin(tsvectorout($$'\\as' ab\c ab\\c AB\\\c ab\\\\c$$::tsvector));
+ tsvectorin
+----------------------------------------
+ 'AB\\c' '\\as' 'ab\\\\c' 'ab\\c' 'abc'
+(1 row)
+
+SELECT '''w'':4A,3B,2C,1D,5 a:8';
+ ?column?
+-----------------------
+ 'w':4A,3B,2C,1D,5 a:8
+(1 row)
+
+SELECT 'a:3A b:2a'::tsvector || 'ba:1234 a:1B';
+ ?column?
+----------------------------
+ 'a':3A,4B 'b':2A 'ba':1237
+(1 row)
+
+--Base tsquery test
+SELECT '1'::tsquery;
+ tsquery
+---------
+ '1'
+(1 row)
+
+SELECT '1 '::tsquery;
+ tsquery
+---------
+ '1'
+(1 row)
+
+SELECT ' 1'::tsquery;
+ tsquery
+---------
+ '1'
+(1 row)
+
+SELECT ' 1 '::tsquery;
+ tsquery
+---------
+ '1'
+(1 row)
+
+SELECT '''1 2'''::tsquery;
+ tsquery
+---------
+ '1 2'
+(1 row)
+
+SELECT E'''1 \\''2'''::tsquery;
+ tsquery
+---------
+ '1 ''2'
+(1 row)
+
+SELECT '!1'::tsquery;
+ tsquery
+---------
+ !'1'
+(1 row)
+
+SELECT '1|2'::tsquery;
+ tsquery
+-----------
+ '1' | '2'
+(1 row)
+
+SELECT '1|!2'::tsquery;
+ tsquery
+------------
+ '1' | !'2'
+(1 row)
+
+SELECT '!1|2'::tsquery;
+ tsquery
+------------
+ !'1' | '2'
+(1 row)
+
+SELECT '!1|!2'::tsquery;
+ tsquery
+-------------
+ !'1' | !'2'
+(1 row)
+
+SELECT '!(!1|!2)'::tsquery;
+ tsquery
+------------------
+ !( !'1' | !'2' )
+(1 row)
+
+SELECT '!(!1|2)'::tsquery;
+ tsquery
+-----------------
+ !( !'1' | '2' )
+(1 row)
+
+SELECT '!(1|!2)'::tsquery;
+ tsquery
+-----------------
+ !( '1' | !'2' )
+(1 row)
+
+SELECT '!(1|2)'::tsquery;
+ tsquery
+----------------
+ !( '1' | '2' )
+(1 row)
+
+SELECT '1&2'::tsquery;
+ tsquery
+-----------
+ '1' & '2'
+(1 row)
+
+SELECT '!1&2'::tsquery;
+ tsquery
+------------
+ !'1' & '2'
+(1 row)
+
+SELECT '1&!2'::tsquery;
+ tsquery
+------------
+ '1' & !'2'
+(1 row)
+
+SELECT '!1&!2'::tsquery;
+ tsquery
+-------------
+ !'1' & !'2'
+(1 row)
+
+SELECT '(1&2)'::tsquery;
+ tsquery
+-----------
+ '1' & '2'
+(1 row)
+
+SELECT '1&(2)'::tsquery;
+ tsquery
+-----------
+ '1' & '2'
+(1 row)
+
+SELECT '!(1)&2'::tsquery;
+ tsquery
+------------
+ !'1' & '2'
+(1 row)
+
+SELECT '!(1&2)'::tsquery;
+ tsquery
+----------------
+ !( '1' & '2' )
+(1 row)
+
+SELECT '1|2&3'::tsquery;
+ tsquery
+-----------------
+ '1' | '2' & '3'
+(1 row)
+
+SELECT '1|(2&3)'::tsquery;
+ tsquery
+-----------------
+ '1' | '2' & '3'
+(1 row)
+
+SELECT '(1|2)&3'::tsquery;
+ tsquery
+---------------------
+ ( '1' | '2' ) & '3'
+(1 row)
+
+SELECT '1|2&!3'::tsquery;
+ tsquery
+------------------
+ '1' | '2' & !'3'
+(1 row)
+
+SELECT '1|!2&3'::tsquery;
+ tsquery
+------------------
+ '1' | !'2' & '3'
+(1 row)
+
+SELECT '!1|2&3'::tsquery;
+ tsquery
+------------------
+ !'1' | '2' & '3'
+(1 row)
+
+SELECT '!1|(2&3)'::tsquery;
+ tsquery
+------------------
+ !'1' | '2' & '3'
+(1 row)
+
+SELECT '!(1|2)&3'::tsquery;
+ tsquery
+----------------------
+ !( '1' | '2' ) & '3'
+(1 row)
+
+SELECT '(!1|2)&3'::tsquery;
+ tsquery
+----------------------
+ ( !'1' | '2' ) & '3'
+(1 row)
+
+SELECT '1|(2|(4|(5|6)))'::tsquery;
+ tsquery
+-----------------------------
+ '1' | '2' | '4' | '5' | '6'
+(1 row)
+
+SELECT '1|2|4|5|6'::tsquery;
+ tsquery
+-----------------------------
+ '1' | '2' | '4' | '5' | '6'
+(1 row)
+
+SELECT '1&(2&(4&(5&6)))'::tsquery;
+ tsquery
+-----------------------------
+ '1' & '2' & '4' & '5' & '6'
+(1 row)
+
+SELECT '1&2&4&5&6'::tsquery;
+ tsquery
+-----------------------------
+ '1' & '2' & '4' & '5' & '6'
+(1 row)
+
+SELECT '1&(2&(4&(5|6)))'::tsquery;
+ tsquery
+---------------------------------
+ '1' & '2' & '4' & ( '5' | '6' )
+(1 row)
+
+SELECT '1&(2&(4&(5|!6)))'::tsquery;
+ tsquery
+----------------------------------
+ '1' & '2' & '4' & ( '5' | !'6' )
+(1 row)
+
+SELECT E'1&(''2''&('' 4''&(\\|5 | ''6 \\'' !|&'')))'::tsquery;
+ tsquery
+------------------------------------------
+ '1' & '2' & ' 4' & ( '|5' | '6 '' !|&' )
+(1 row)
+
+SELECT $$'\\as'$$::tsquery;
+ tsquery
+---------
+ '\\as'
+(1 row)
+
+SELECT 'a:* & nbb:*ac | doo:a* | goo'::tsquery;
+ tsquery
+--------------------------------------
+ 'a':* & 'nbb':*AC | 'doo':*A | 'goo'
+(1 row)
+
+SELECT '!!b'::tsquery;
+ tsquery
+---------
+ !!'b'
+(1 row)
+
+SELECT '!!!b'::tsquery;
+ tsquery
+---------
+ !!!'b'
+(1 row)
+
+SELECT '!(!b)'::tsquery;
+ tsquery
+---------
+ !!'b'
+(1 row)
+
+SELECT 'a & !!b'::tsquery;
+ tsquery
+-------------
+ 'a' & !!'b'
+(1 row)
+
+SELECT '!!a & b'::tsquery;
+ tsquery
+-------------
+ !!'a' & 'b'
+(1 row)
+
+SELECT '!!a & !!b'::tsquery;
+ tsquery
+---------------
+ !!'a' & !!'b'
+(1 row)
+
+--comparisons
+SELECT 'a' < 'b & c'::tsquery as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a' > 'b & c'::tsquery as "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a | f' < 'b & c'::tsquery as "false";
+ false
+-------
+ t
+(1 row)
+
+SELECT 'a | ff' < 'b & c'::tsquery as "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a | f | g' < 'b & c'::tsquery as "false";
+ false
+-------
+ f
+(1 row)
+
+--concatenation
+SELECT numnode( 'new'::tsquery );
+ numnode
+---------
+ 1
+(1 row)
+
+SELECT numnode( 'new & york'::tsquery );
+ numnode
+---------
+ 3
+(1 row)
+
+SELECT numnode( 'new & york | qwery'::tsquery );
+ numnode
+---------
+ 5
+(1 row)
+
+SELECT 'foo & bar'::tsquery && 'asd';
+ ?column?
+-----------------------
+ 'foo' & 'bar' & 'asd'
+(1 row)
+
+SELECT 'foo & bar'::tsquery || 'asd & fg';
+ ?column?
+------------------------------
+ 'foo' & 'bar' | 'asd' & 'fg'
+(1 row)
+
+SELECT 'foo & bar'::tsquery || !!'asd & fg'::tsquery;
+ ?column?
+-----------------------------------
+ 'foo' & 'bar' | !( 'asd' & 'fg' )
+(1 row)
+
+SELECT 'foo & bar'::tsquery && 'asd | fg';
+ ?column?
+----------------------------------
+ 'foo' & 'bar' & ( 'asd' | 'fg' )
+(1 row)
+
+SELECT 'a' <-> 'b & d'::tsquery;
+ ?column?
+-----------------------
+ 'a' <-> ( 'b' & 'd' )
+(1 row)
+
+SELECT 'a & g' <-> 'b & d'::tsquery;
+ ?column?
+---------------------------------
+ ( 'a' & 'g' ) <-> ( 'b' & 'd' )
+(1 row)
+
+SELECT 'a & g' <-> 'b | d'::tsquery;
+ ?column?
+---------------------------------
+ ( 'a' & 'g' ) <-> ( 'b' | 'd' )
+(1 row)
+
+SELECT 'a & g' <-> 'b <-> d'::tsquery;
+ ?column?
+-----------------------------------
+ ( 'a' & 'g' ) <-> ( 'b' <-> 'd' )
+(1 row)
+
+SELECT tsquery_phrase('a <3> g', 'b & d', 10);
+ tsquery_phrase
+--------------------------------
+ 'a' <3> 'g' <10> ( 'b' & 'd' )
+(1 row)
+
+-- tsvector-tsquery operations
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & ca' as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & ca:B' as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & ca:A' as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & ca:C' as "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & ca:CB' as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & c:*C' as "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & c:*CB' as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a b:89 ca:23A,64b cb:80c d:34c'::tsvector @@ 'd:AC & c:*C' as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a b:89 ca:23A,64c cb:80b d:34c'::tsvector @@ 'd:AC & c:*C' as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a b:89 ca:23A,64c cb:80b d:34c'::tsvector @@ 'd:AC & c:*B' as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'wa:1D wb:2A'::tsvector @@ 'w:*D & w:*A'::tsquery as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'wa:1D wb:2A'::tsvector @@ 'w:*D <-> w:*A'::tsquery as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'wa:1A wb:2D'::tsvector @@ 'w:*D <-> w:*A'::tsquery as "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'wa:1A'::tsvector @@ 'w:*A'::tsquery as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'wa:1A'::tsvector @@ 'w:*D'::tsquery as "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'wa:1A'::tsvector @@ '!w:*A'::tsquery as "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'wa:1A'::tsvector @@ '!w:*D'::tsquery as "true";
+ true
+------
+ t
+(1 row)
+
+-- historically, a stripped tsvector matches queries ignoring weights:
+SELECT strip('wa:1A'::tsvector) @@ 'w:*A'::tsquery as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT strip('wa:1A'::tsvector) @@ 'w:*D'::tsquery as "true";
+ true
+------
+ t
+(1 row)
+
+SELECT strip('wa:1A'::tsvector) @@ '!w:*A'::tsquery as "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT strip('wa:1A'::tsvector) @@ '!w:*D'::tsquery as "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'supernova'::tsvector @@ 'super'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'supeanova supernova'::tsvector @@ 'super'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'supeznova supernova'::tsvector @@ 'super'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'supernova'::tsvector @@ 'super:*'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'supeanova supernova'::tsvector @@ 'super:*'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'supeznova supernova'::tsvector @@ 'super:*'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+--phrase search
+SELECT to_tsvector('simple', '1 2 3 1') @@ '1 <-> 2' AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT to_tsvector('simple', '1 2 3 1') @@ '1 <2> 2' AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT to_tsvector('simple', '1 2 3 1') @@ '1 <-> 3' AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT to_tsvector('simple', '1 2 3 1') @@ '1 <2> 3' AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT to_tsvector('simple', '1 2 1 2') @@ '1 <3> 2' AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT to_tsvector('simple', '1 2 11 3') @@ '1 <-> 3' AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT to_tsvector('simple', '1 2 11 3') @@ '1:* <-> 3' AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT to_tsvector('simple', '1 2 3 4') @@ '1 <-> 2 <-> 3' AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT to_tsvector('simple', '1 2 3 4') @@ '(1 <-> 2) <-> 3' AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT to_tsvector('simple', '1 2 3 4') @@ '1 <-> (2 <-> 3)' AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT to_tsvector('simple', '1 2 3 4') @@ '1 <2> (2 <-> 3)' AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT to_tsvector('simple', '1 2 1 2 3 4') @@ '(1 <-> 2) <-> 3' AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT to_tsvector('simple', '1 2 1 2 3 4') @@ '1 <-> 2 <-> 3' AS "true";
+ true
+------
+ t
+(1 row)
+
+-- without position data, phrase search does not match
+SELECT strip(to_tsvector('simple', '1 2 3 4')) @@ '1 <-> 2 <-> 3' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'q x q y') @@ 'q <-> (x & y)' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'q x') @@ 'q <-> (x | y <-> z)' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'q y') @@ 'q <-> (x | y <-> z)' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'q y z') @@ 'q <-> (x | y <-> z)' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'q y x') @@ 'q <-> (x | y <-> z)' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'q x y') @@ 'q <-> (x | y <-> z)' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'q x') @@ '(x | y <-> z) <-> q' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'x q') @@ '(x | y <-> z) <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q') @@ '(x | y <-> z) <-> q' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'x y z') @@ '(x | y <-> z) <-> q' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'x y z q') @@ '(x | y <-> z) <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'y z q') @@ '(x | y <-> z) <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'y y q') @@ '(x | y <-> z) <-> q' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'y y q') @@ '(!x | y <-> z) <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q') @@ '(!x | y <-> z) <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'y y q') @@ '(x | y <-> !z) <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x q') @@ '(x | y <-> !z) <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x q') @@ '(!x | y <-> z) <-> q' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'z q') @@ '(!x | y <-> z) <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q') @@ '(!x | y) <-> y <-> q' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'x y q') @@ '(!x | !y) <-> y <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q') @@ '(x | !y) <-> y <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q') @@ '(x | !!z) <-> y <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q y') @@ '!x <-> y' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q y') @@ '!x <-> !y' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q y') @@ '!x <-> !!y' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q y') @@ '!(x <-> y)' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'x y q y') @@ '!(x <2> y)' AS "true";
+ true
+------
+ t
+(1 row)
+
+select strip(to_tsvector('simple', 'x y q y')) @@ '!x <-> y' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select strip(to_tsvector('simple', 'x y q y')) @@ '!x <-> !y' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select strip(to_tsvector('simple', 'x y q y')) @@ '!x <-> !!y' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select strip(to_tsvector('simple', 'x y q y')) @@ '!(x <-> y)' AS "true";
+ true
+------
+ t
+(1 row)
+
+select strip(to_tsvector('simple', 'x y q y')) @@ '!(x <2> y)' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q y') @@ '!foo' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', '') @@ '!foo' AS "true";
+ true
+------
+ t
+(1 row)
+
+--ranking
+SELECT ts_rank(' a:1 s:2C d g'::tsvector, 'a | s');
+ ts_rank
+-----------
+ 0.0911891
+(1 row)
+
+SELECT ts_rank(' a:1 sa:2C d g'::tsvector, 'a | s');
+ ts_rank
+-----------
+ 0.0303964
+(1 row)
+
+SELECT ts_rank(' a:1 sa:2C d g'::tsvector, 'a | s:*');
+ ts_rank
+-----------
+ 0.0911891
+(1 row)
+
+SELECT ts_rank(' a:1 sa:2C d g'::tsvector, 'a | sa:*');
+ ts_rank
+-----------
+ 0.0911891
+(1 row)
+
+SELECT ts_rank(' a:1 s:2B d g'::tsvector, 'a | s');
+ ts_rank
+----------
+ 0.151982
+(1 row)
+
+SELECT ts_rank(' a:1 s:2 d g'::tsvector, 'a | s');
+ ts_rank
+-----------
+ 0.0607927
+(1 row)
+
+SELECT ts_rank(' a:1 s:2C d g'::tsvector, 'a & s');
+ ts_rank
+----------
+ 0.140153
+(1 row)
+
+SELECT ts_rank(' a:1 s:2B d g'::tsvector, 'a & s');
+ ts_rank
+----------
+ 0.198206
+(1 row)
+
+SELECT ts_rank(' a:1 s:2 d g'::tsvector, 'a & s');
+ ts_rank
+-----------
+ 0.0991032
+(1 row)
+
+SELECT ts_rank_cd(' a:1 s:2C d g'::tsvector, 'a | s');
+ ts_rank_cd
+------------
+ 0.3
+(1 row)
+
+SELECT ts_rank_cd(' a:1 sa:2C d g'::tsvector, 'a | s');
+ ts_rank_cd
+------------
+ 0.1
+(1 row)
+
+SELECT ts_rank_cd(' a:1 sa:2C d g'::tsvector, 'a | s:*');
+ ts_rank_cd
+------------
+ 0.3
+(1 row)
+
+SELECT ts_rank_cd(' a:1 sa:2C d g'::tsvector, 'a | sa:*');
+ ts_rank_cd
+------------
+ 0.3
+(1 row)
+
+SELECT ts_rank_cd(' a:1 sa:3C sab:2c d g'::tsvector, 'a | sa:*');
+ ts_rank_cd
+------------
+ 0.5
+(1 row)
+
+SELECT ts_rank_cd(' a:1 s:2B d g'::tsvector, 'a | s');
+ ts_rank_cd
+------------
+ 0.5
+(1 row)
+
+SELECT ts_rank_cd(' a:1 s:2 d g'::tsvector, 'a | s');
+ ts_rank_cd
+------------
+ 0.2
+(1 row)
+
+SELECT ts_rank_cd(' a:1 s:2C d g'::tsvector, 'a & s');
+ ts_rank_cd
+------------
+ 0.133333
+(1 row)
+
+SELECT ts_rank_cd(' a:1 s:2B d g'::tsvector, 'a & s');
+ ts_rank_cd
+------------
+ 0.16
+(1 row)
+
+SELECT ts_rank_cd(' a:1 s:2 d g'::tsvector, 'a & s');
+ ts_rank_cd
+------------
+ 0.1
+(1 row)
+
+SELECT ts_rank_cd(' a:1 s:2A d g'::tsvector, 'a <-> s');
+ ts_rank_cd
+------------
+ 0.181818
+(1 row)
+
+SELECT ts_rank_cd(' a:1 s:2C d g'::tsvector, 'a <-> s');
+ ts_rank_cd
+------------
+ 0.133333
+(1 row)
+
+SELECT ts_rank_cd(' a:1 s:2 d g'::tsvector, 'a <-> s');
+ ts_rank_cd
+------------
+ 0.1
+(1 row)
+
+SELECT ts_rank_cd(' a:1 s:2 d:2A g'::tsvector, 'a <-> s');
+ ts_rank_cd
+------------
+ 0.1
+(1 row)
+
+SELECT ts_rank_cd(' a:1 s:2,3A d:2A g'::tsvector, 'a <2> s:A');
+ ts_rank_cd
+------------
+ 0.0909091
+(1 row)
+
+SELECT ts_rank_cd(' a:1 b:2 s:3A d:2A g'::tsvector, 'a <2> s:A');
+ ts_rank_cd
+------------
+ 0.0909091
+(1 row)
+
+SELECT ts_rank_cd(' a:1 sa:2D sb:2A g'::tsvector, 'a <-> s:*');
+ ts_rank_cd
+------------
+ 0.1
+(1 row)
+
+SELECT ts_rank_cd(' a:1 sa:2A sb:2D g'::tsvector, 'a <-> s:*');
+ ts_rank_cd
+------------
+ 0.1
+(1 row)
+
+SELECT ts_rank_cd(' a:1 sa:2A sb:2D g'::tsvector, 'a <-> s:* <-> sa:A');
+ ts_rank_cd
+------------
+ 0
+(1 row)
+
+SELECT ts_rank_cd(' a:1 sa:2A sb:2D g'::tsvector, 'a <-> s:* <-> sa:B');
+ ts_rank_cd
+------------
+ 0
+(1 row)
+
+SELECT 'a:1 b:2'::tsvector @@ 'a <-> b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a:1 b:2'::tsvector @@ 'a <0> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:2'::tsvector @@ 'a <1> b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a:1 b:2'::tsvector @@ 'a <2> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ 'a <-> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ 'a <0> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ 'a <1> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ 'a <2> b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ 'a <3> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ 'a <0> a:*'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+-- tsvector editing operations
+SELECT strip('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd'::tsvector);
+ strip
+---------------
+ 'a' 'asd' 'w'
+(1 row)
+
+SELECT strip('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector);
+ strip
+----------------------------------------------
+ 'base' 'hidden' 'rebel' 'spaceship' 'strike'
+(1 row)
+
+SELECT strip('base hidden rebel spaceship strike'::tsvector);
+ strip
+----------------------------------------------
+ 'base' 'hidden' 'rebel' 'spaceship' 'strike'
+(1 row)
+
+SELECT ts_delete(to_tsvector('english', 'Rebel spaceships, striking from a hidden base'), 'spaceship');
+ ts_delete
+------------------------------------------
+ 'base':7 'hidden':6 'rebel':1 'strike':3
+(1 row)
+
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'base');
+ ts_delete
+--------------------------------------------------------------
+ 'hidden':6 'rebel':1 'spaceship':2,33A,34B,35C,36 'strike':3
+(1 row)
+
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'bas');
+ ts_delete
+-----------------------------------------------------------------------
+ 'base':7 'hidden':6 'rebel':1 'spaceship':2,33A,34B,35C,36 'strike':3
+(1 row)
+
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'bases');
+ ts_delete
+-----------------------------------------------------------------------
+ 'base':7 'hidden':6 'rebel':1 'spaceship':2,33A,34B,35C,36 'strike':3
+(1 row)
+
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'spaceship');
+ ts_delete
+------------------------------------------
+ 'base':7 'hidden':6 'rebel':1 'strike':3
+(1 row)
+
+SELECT ts_delete('base hidden rebel spaceship strike'::tsvector, 'spaceship');
+ ts_delete
+----------------------------------
+ 'base' 'hidden' 'rebel' 'strike'
+(1 row)
+
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceship','rebel']);
+ ts_delete
+--------------------------------
+ 'base':7 'hidden':6 'strike':3
+(1 row)
+
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceships','rebel']);
+ ts_delete
+-------------------------------------------------------------
+ 'base':7 'hidden':6 'spaceship':2,33A,34B,35C,36 'strike':3
+(1 row)
+
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceshi','rebel']);
+ ts_delete
+-------------------------------------------------------------
+ 'base':7 'hidden':6 'spaceship':2,33A,34B,35C,36 'strike':3
+(1 row)
+
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceship','leya','rebel']);
+ ts_delete
+--------------------------------
+ 'base':7 'hidden':6 'strike':3
+(1 row)
+
+SELECT ts_delete('base hidden rebel spaceship strike'::tsvector, ARRAY['spaceship','leya','rebel']);
+ ts_delete
+--------------------------
+ 'base' 'hidden' 'strike'
+(1 row)
+
+SELECT ts_delete('base hidden rebel spaceship strike'::tsvector, ARRAY['spaceship','leya','rebel','rebel']);
+ ts_delete
+--------------------------
+ 'base' 'hidden' 'strike'
+(1 row)
+
+SELECT ts_delete('base hidden rebel spaceship strike'::tsvector, ARRAY['spaceship','leya','rebel', NULL]);
+ERROR: lexeme array may not contain nulls
+SELECT unnest('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector);
+ unnest
+---------------------------------------------
+ (base,{7},{D})
+ (hidden,{6},{D})
+ (rebel,{1},{D})
+ (spaceship,"{2,33,34,35,36}","{D,A,B,C,D}")
+ (strike,{3},{D})
+(5 rows)
+
+SELECT unnest('base hidden rebel spaceship strike'::tsvector);
+ unnest
+---------------
+ (base,,)
+ (hidden,,)
+ (rebel,,)
+ (spaceship,,)
+ (strike,,)
+(5 rows)
+
+SELECT * FROM unnest('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector);
+ lexeme | positions | weights
+-----------+-----------------+-------------
+ base | {7} | {D}
+ hidden | {6} | {D}
+ rebel | {1} | {D}
+ spaceship | {2,33,34,35,36} | {D,A,B,C,D}
+ strike | {3} | {D}
+(5 rows)
+
+SELECT * FROM unnest('base hidden rebel spaceship strike'::tsvector);
+ lexeme | positions | weights
+-----------+-----------+---------
+ base | |
+ hidden | |
+ rebel | |
+ spaceship | |
+ strike | |
+(5 rows)
+
+SELECT lexeme, positions[1] from unnest('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector);
+ lexeme | positions
+-----------+-----------
+ base | 7
+ hidden | 6
+ rebel | 1
+ spaceship | 2
+ strike | 3
+(5 rows)
+
+SELECT tsvector_to_array('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector);
+ tsvector_to_array
+--------------------------------------
+ {base,hidden,rebel,spaceship,strike}
+(1 row)
+
+SELECT tsvector_to_array('base hidden rebel spaceship strike'::tsvector);
+ tsvector_to_array
+--------------------------------------
+ {base,hidden,rebel,spaceship,strike}
+(1 row)
+
+SELECT array_to_tsvector(ARRAY['base','hidden','rebel','spaceship','strike']);
+ array_to_tsvector
+----------------------------------------------
+ 'base' 'hidden' 'rebel' 'spaceship' 'strike'
+(1 row)
+
+SELECT array_to_tsvector(ARRAY['base','hidden','rebel','spaceship', NULL]);
+ERROR: lexeme array may not contain nulls
+-- array_to_tsvector must sort and de-dup
+SELECT array_to_tsvector(ARRAY['foo','bar','baz','bar']);
+ array_to_tsvector
+-------------------
+ 'bar' 'baz' 'foo'
+(1 row)
+
+SELECT setweight('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd zxc:81,567,222A'::tsvector, 'c');
+ setweight
+----------------------------------------------------------
+ 'a':1C,3C 'asd':1C 'w':5C,6C,12C,13C 'zxc':81C,222C,567C
+(1 row)
+
+SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c');
+ setweight
+----------------------------------------------------------
+ 'a':1C,3C 'asd':1C 'w':5C,6C,12C,13C 'zxc':81C,222C,567C
+(1 row)
+
+SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c', '{a}');
+ setweight
+------------------------------------------------------
+ 'a':1C,3C 'asd':1C 'w':5,6,12B,13A 'zxc':81,222A,567
+(1 row)
+
+SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c', '{a}');
+ setweight
+------------------------------------------------------
+ 'a':1C,3C 'asd':1C 'w':5,6,12B,13A 'zxc':81,222A,567
+(1 row)
+
+SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c', '{a,zxc}');
+ setweight
+--------------------------------------------------------
+ 'a':1C,3C 'asd':1C 'w':5,6,12B,13A 'zxc':81C,222C,567C
+(1 row)
+
+SELECT setweight('a asd w:5,6,12B,13A zxc'::tsvector, 'c', '{a,zxc}');
+ setweight
+---------------------------------
+ 'a' 'asd' 'w':5,6,12B,13A 'zxc'
+(1 row)
+
+SELECT setweight('a asd w:5,6,12B,13A zxc'::tsvector, 'c', ARRAY['a', 'zxc', NULL]);
+ERROR: lexeme array may not contain nulls
+SELECT ts_filter('base:7A empir:17 evil:15 first:11 galact:16 hidden:6A rebel:1A spaceship:2A strike:3A victori:12 won:9'::tsvector, '{a}');
+ ts_filter
+-------------------------------------------------------------
+ 'base':7A 'hidden':6A 'rebel':1A 'spaceship':2A 'strike':3A
+(1 row)
+
+SELECT ts_filter('base hidden rebel spaceship strike'::tsvector, '{a}');
+ ts_filter
+-----------
+
+(1 row)
+
+SELECT ts_filter('base hidden rebel spaceship strike'::tsvector, '{a,b,NULL}');
+ERROR: weight array may not contain nulls
diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out
new file mode 100644
index 0000000..418f296
--- /dev/null
+++ b/src/test/regress/expected/tuplesort.out
@@ -0,0 +1,686 @@
+-- only use parallelism when explicitly intending to do so
+SET max_parallel_maintenance_workers = 0;
+SET max_parallel_workers = 0;
+-- A table with contents that, when sorted, triggers abbreviated
+-- key aborts. One easy way to achieve that is to use uuids that all
+-- have the same prefix, as abbreviated keys for uuids just use the
+-- first sizeof(Datum) bytes.
+CREATE TEMP TABLE abbrev_abort_uuids (
+ id serial not null,
+ abort_increasing uuid,
+ abort_decreasing uuid,
+ noabort_increasing uuid,
+ noabort_decreasing uuid);
+INSERT INTO abbrev_abort_uuids (abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing)
+ SELECT
+ ('00000000-0000-0000-0000-'||to_char(g.i, '000000000000FM'))::uuid abort_increasing,
+ ('00000000-0000-0000-0000-'||to_char(20000 - g.i, '000000000000FM'))::uuid abort_decreasing,
+ (to_char(g.i % 10009, '00000000FM')||'-0000-0000-0000-'||to_char(g.i, '000000000000FM'))::uuid noabort_increasing,
+ (to_char(((20000 - g.i) % 10009), '00000000FM')||'-0000-0000-0000-'||to_char(20000 - g.i, '000000000000FM'))::uuid noabort_decreasing
+ FROM generate_series(0, 20000, 1) g(i);
+-- and a few NULLs
+INSERT INTO abbrev_abort_uuids(id) VALUES(0);
+INSERT INTO abbrev_abort_uuids DEFAULT VALUES;
+INSERT INTO abbrev_abort_uuids DEFAULT VALUES;
+-- add just a few duplicates
+INSERT INTO abbrev_abort_uuids (abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing)
+ SELECT abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+ FROM abbrev_abort_uuids
+ WHERE (id < 10 OR id > 19990) AND id % 3 = 0 AND abort_increasing is not null;
+----
+-- Check sort node uses of tuplesort wrt. abbreviated keys
+----
+-- plain sort triggering abbreviated abort
+SELECT abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_increasing OFFSET 20000 - 4;
+ abort_increasing | abort_decreasing
+--------------------------------------+--------------------------------------
+ 00000000-0000-0000-0000-000000019992 | 00000000-0000-0000-0000-000000000008
+ 00000000-0000-0000-0000-000000019993 | 00000000-0000-0000-0000-000000000007
+ 00000000-0000-0000-0000-000000019994 | 00000000-0000-0000-0000-000000000006
+ 00000000-0000-0000-0000-000000019994 | 00000000-0000-0000-0000-000000000006
+ 00000000-0000-0000-0000-000000019995 | 00000000-0000-0000-0000-000000000005
+ 00000000-0000-0000-0000-000000019996 | 00000000-0000-0000-0000-000000000004
+ 00000000-0000-0000-0000-000000019997 | 00000000-0000-0000-0000-000000000003
+ 00000000-0000-0000-0000-000000019997 | 00000000-0000-0000-0000-000000000003
+ 00000000-0000-0000-0000-000000019998 | 00000000-0000-0000-0000-000000000002
+ 00000000-0000-0000-0000-000000019999 | 00000000-0000-0000-0000-000000000001
+ 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ |
+ |
+ |
+(15 rows)
+
+SELECT abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_decreasing NULLS FIRST OFFSET 20000 - 4;
+ abort_increasing | abort_decreasing
+--------------------------------------+--------------------------------------
+ 00000000-0000-0000-0000-000000000011 | 00000000-0000-0000-0000-000000019989
+ 00000000-0000-0000-0000-000000000010 | 00000000-0000-0000-0000-000000019990
+ 00000000-0000-0000-0000-000000000009 | 00000000-0000-0000-0000-000000019991
+ 00000000-0000-0000-0000-000000000008 | 00000000-0000-0000-0000-000000019992
+ 00000000-0000-0000-0000-000000000008 | 00000000-0000-0000-0000-000000019992
+ 00000000-0000-0000-0000-000000000007 | 00000000-0000-0000-0000-000000019993
+ 00000000-0000-0000-0000-000000000006 | 00000000-0000-0000-0000-000000019994
+ 00000000-0000-0000-0000-000000000005 | 00000000-0000-0000-0000-000000019995
+ 00000000-0000-0000-0000-000000000005 | 00000000-0000-0000-0000-000000019995
+ 00000000-0000-0000-0000-000000000004 | 00000000-0000-0000-0000-000000019996
+ 00000000-0000-0000-0000-000000000003 | 00000000-0000-0000-0000-000000019997
+ 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998
+ 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998
+ 00000000-0000-0000-0000-000000000001 | 00000000-0000-0000-0000-000000019999
+ 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000020000
+(15 rows)
+
+-- plain sort not triggering abbreviated abort
+SELECT noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_increasing OFFSET 20000 - 4;
+ noabort_increasing | noabort_decreasing
+--------------------------------------+--------------------------------------
+ 00009997-0000-0000-0000-000000009997 | 00010003-0000-0000-0000-000000010003
+ 00009998-0000-0000-0000-000000009998 | 00010002-0000-0000-0000-000000010002
+ 00009999-0000-0000-0000-000000009999 | 00010001-0000-0000-0000-000000010001
+ 00010000-0000-0000-0000-000000010000 | 00010000-0000-0000-0000-000000010000
+ 00010001-0000-0000-0000-000000010001 | 00009999-0000-0000-0000-000000009999
+ 00010002-0000-0000-0000-000000010002 | 00009998-0000-0000-0000-000000009998
+ 00010003-0000-0000-0000-000000010003 | 00009997-0000-0000-0000-000000009997
+ 00010004-0000-0000-0000-000000010004 | 00009996-0000-0000-0000-000000009996
+ 00010005-0000-0000-0000-000000010005 | 00009995-0000-0000-0000-000000009995
+ 00010006-0000-0000-0000-000000010006 | 00009994-0000-0000-0000-000000009994
+ 00010007-0000-0000-0000-000000010007 | 00009993-0000-0000-0000-000000009993
+ 00010008-0000-0000-0000-000000010008 | 00009992-0000-0000-0000-000000009992
+ |
+ |
+ |
+(15 rows)
+
+SELECT noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing NULLS FIRST OFFSET 20000 - 4;
+ noabort_increasing | noabort_decreasing
+--------------------------------------+--------------------------------------
+ 00010006-0000-0000-0000-000000010006 | 00009994-0000-0000-0000-000000009994
+ 00010005-0000-0000-0000-000000010005 | 00009995-0000-0000-0000-000000009995
+ 00010004-0000-0000-0000-000000010004 | 00009996-0000-0000-0000-000000009996
+ 00010003-0000-0000-0000-000000010003 | 00009997-0000-0000-0000-000000009997
+ 00010002-0000-0000-0000-000000010002 | 00009998-0000-0000-0000-000000009998
+ 00010001-0000-0000-0000-000000010001 | 00009999-0000-0000-0000-000000009999
+ 00010000-0000-0000-0000-000000010000 | 00010000-0000-0000-0000-000000010000
+ 00009999-0000-0000-0000-000000009999 | 00010001-0000-0000-0000-000000010001
+ 00009998-0000-0000-0000-000000009998 | 00010002-0000-0000-0000-000000010002
+ 00009997-0000-0000-0000-000000009997 | 00010003-0000-0000-0000-000000010003
+ 00009996-0000-0000-0000-000000009996 | 00010004-0000-0000-0000-000000010004
+ 00009995-0000-0000-0000-000000009995 | 00010005-0000-0000-0000-000000010005
+ 00009994-0000-0000-0000-000000009994 | 00010006-0000-0000-0000-000000010006
+ 00009993-0000-0000-0000-000000009993 | 00010007-0000-0000-0000-000000010007
+ 00009992-0000-0000-0000-000000009992 | 00010008-0000-0000-0000-000000010008
+(15 rows)
+
+-- bounded sort (disables abbreviated keys)
+SELECT abort_increasing, noabort_increasing FROM abbrev_abort_uuids ORDER BY abort_increasing LIMIT 5;
+ abort_increasing | noabort_increasing
+--------------------------------------+--------------------------------------
+ 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000000000
+ 00000000-0000-0000-0000-000000000001 | 00000001-0000-0000-0000-000000000001
+ 00000000-0000-0000-0000-000000000002 | 00000002-0000-0000-0000-000000000002
+ 00000000-0000-0000-0000-000000000002 | 00000002-0000-0000-0000-000000000002
+ 00000000-0000-0000-0000-000000000003 | 00000003-0000-0000-0000-000000000003
+(5 rows)
+
+SELECT abort_increasing, noabort_increasing FROM abbrev_abort_uuids ORDER BY noabort_increasing NULLS FIRST LIMIT 5;
+ abort_increasing | noabort_increasing
+--------------------------------------+--------------------------------------
+ |
+ |
+ |
+ 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000000000
+ 00000000-0000-0000-0000-000000010009 | 00000000-0000-0000-0000-000000010009
+(5 rows)
+
+----
+-- Check index creation uses of tuplesort wrt. abbreviated keys
+----
+-- index creation using abbreviated keys successfully
+CREATE INDEX abbrev_abort_uuids__noabort_increasing_idx ON abbrev_abort_uuids (noabort_increasing);
+CREATE INDEX abbrev_abort_uuids__noabort_decreasing_idx ON abbrev_abort_uuids (noabort_decreasing);
+-- verify
+EXPLAIN (COSTS OFF)
+SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_increasing LIMIT 5;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Limit
+ -> Index Scan using abbrev_abort_uuids__noabort_increasing_idx on abbrev_abort_uuids
+(2 rows)
+
+SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_increasing LIMIT 5;
+ id | noabort_increasing | noabort_decreasing
+-------+--------------------------------------+--------------------------------------
+ 1 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000
+ 10010 | 00000000-0000-0000-0000-000000010009 | 00009991-0000-0000-0000-000000009991
+ 2 | 00000001-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999
+ 10011 | 00000001-0000-0000-0000-000000010010 | 00009990-0000-0000-0000-000000009990
+ 3 | 00000002-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998
+(5 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing LIMIT 5;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Limit
+ -> Index Scan using abbrev_abort_uuids__noabort_decreasing_idx on abbrev_abort_uuids
+(2 rows)
+
+SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing LIMIT 5;
+ id | noabort_increasing | noabort_decreasing
+-------+--------------------------------------+--------------------------------------
+ 20001 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20010 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 9992 | 00009991-0000-0000-0000-000000009991 | 00000000-0000-0000-0000-000000010009
+ 20000 | 00009990-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001
+ 9991 | 00009990-0000-0000-0000-000000009990 | 00000001-0000-0000-0000-000000010010
+(5 rows)
+
+-- index creation using abbreviated keys, hitting abort
+CREATE INDEX abbrev_abort_uuids__abort_increasing_idx ON abbrev_abort_uuids (abort_increasing);
+CREATE INDEX abbrev_abort_uuids__abort_decreasing_idx ON abbrev_abort_uuids (abort_decreasing);
+-- verify
+EXPLAIN (COSTS OFF)
+SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_increasing LIMIT 5;
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Limit
+ -> Index Scan using abbrev_abort_uuids__abort_increasing_idx on abbrev_abort_uuids
+(2 rows)
+
+SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_increasing LIMIT 5;
+ id | abort_increasing | abort_decreasing
+-------+--------------------------------------+--------------------------------------
+ 1 | 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000020000
+ 2 | 00000000-0000-0000-0000-000000000001 | 00000000-0000-0000-0000-000000019999
+ 3 | 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998
+ 20004 | 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998
+ 4 | 00000000-0000-0000-0000-000000000003 | 00000000-0000-0000-0000-000000019997
+(5 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_decreasing LIMIT 5;
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Limit
+ -> Index Scan using abbrev_abort_uuids__abort_decreasing_idx on abbrev_abort_uuids
+(2 rows)
+
+SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_decreasing LIMIT 5;
+ id | abort_increasing | abort_decreasing
+-------+--------------------------------------+--------------------------------------
+ 20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20000 | 00000000-0000-0000-0000-000000019999 | 00000000-0000-0000-0000-000000000001
+ 19999 | 00000000-0000-0000-0000-000000019998 | 00000000-0000-0000-0000-000000000002
+ 19998 | 00000000-0000-0000-0000-000000019997 | 00000000-0000-0000-0000-000000000003
+(5 rows)
+
+----
+-- Check CLUSTER uses of tuplesort wrt. abbreviated keys
+----
+-- when aborting, increasing order
+BEGIN;
+SET LOCAL enable_indexscan = false;
+CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__abort_increasing_idx;
+-- head
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid LIMIT 5;
+ id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
+-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
+ 1 | 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000
+ 2 | 00000000-0000-0000-0000-000000000001 | 00000000-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999
+ 3 | 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998 | 00000002-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998
+ 20004 | 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998 | 00000002-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998
+ 4 | 00000000-0000-0000-0000-000000000003 | 00000000-0000-0000-0000-000000019997 | 00000003-0000-0000-0000-000000000003 | 00009988-0000-0000-0000-000000019997
+(5 rows)
+
+-- tail
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid DESC LIMIT 5;
+ id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
+-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
+ 0 | | | |
+ 20002 | | | |
+ 20003 | | | |
+ 20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+(5 rows)
+
+ROLLBACK;
+-- when aborting, decreasing order
+BEGIN;
+SET LOCAL enable_indexscan = false;
+CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__abort_decreasing_idx;
+-- head
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid LIMIT 5;
+ id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
+-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
+ 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20000 | 00000000-0000-0000-0000-000000019999 | 00000000-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001
+ 19999 | 00000000-0000-0000-0000-000000019998 | 00000000-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998 | 00000002-0000-0000-0000-000000000002
+ 20009 | 00000000-0000-0000-0000-000000019997 | 00000000-0000-0000-0000-000000000003 | 00009988-0000-0000-0000-000000019997 | 00000003-0000-0000-0000-000000000003
+(5 rows)
+
+-- tail
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid DESC LIMIT 5;
+ id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
+-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
+ 0 | | | |
+ 20002 | | | |
+ 20003 | | | |
+ 1 | 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000
+ 2 | 00000000-0000-0000-0000-000000000001 | 00000000-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999
+(5 rows)
+
+ROLLBACK;
+-- when not aborting, increasing order
+BEGIN;
+SET LOCAL enable_indexscan = false;
+CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__noabort_increasing_idx;
+-- head
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid LIMIT 5;
+ id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
+-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
+ 1 | 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000
+ 10010 | 00000000-0000-0000-0000-000000010009 | 00000000-0000-0000-0000-000000009991 | 00000000-0000-0000-0000-000000010009 | 00009991-0000-0000-0000-000000009991
+ 2 | 00000000-0000-0000-0000-000000000001 | 00000000-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999
+ 10011 | 00000000-0000-0000-0000-000000010010 | 00000000-0000-0000-0000-000000009990 | 00000001-0000-0000-0000-000000010010 | 00009990-0000-0000-0000-000000009990
+ 20004 | 00000000-0000-0000-0000-000000000002 | 00000000-0000-0000-0000-000000019998 | 00000002-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998
+(5 rows)
+
+-- tail
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid DESC LIMIT 5;
+ id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
+-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
+ 0 | | | |
+ 20002 | | | |
+ 20003 | | | |
+ 10009 | 00000000-0000-0000-0000-000000010008 | 00000000-0000-0000-0000-000000009992 | 00010008-0000-0000-0000-000000010008 | 00009992-0000-0000-0000-000000009992
+ 10008 | 00000000-0000-0000-0000-000000010007 | 00000000-0000-0000-0000-000000009993 | 00010007-0000-0000-0000-000000010007 | 00009993-0000-0000-0000-000000009993
+(5 rows)
+
+ROLLBACK;
+-- when no aborting, decreasing order
+BEGIN;
+SET LOCAL enable_indexscan = false;
+CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__noabort_decreasing_idx;
+-- head
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid LIMIT 5;
+ id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
+-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
+ 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 9992 | 00000000-0000-0000-0000-000000009991 | 00000000-0000-0000-0000-000000010009 | 00009991-0000-0000-0000-000000009991 | 00000000-0000-0000-0000-000000010009
+ 20000 | 00000000-0000-0000-0000-000000019999 | 00000000-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001
+ 9991 | 00000000-0000-0000-0000-000000009990 | 00000000-0000-0000-0000-000000010010 | 00009990-0000-0000-0000-000000009990 | 00000001-0000-0000-0000-000000010010
+(5 rows)
+
+-- tail
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid DESC LIMIT 5;
+ id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
+-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
+ 0 | | | |
+ 20003 | | | |
+ 20002 | | | |
+ 9993 | 00000000-0000-0000-0000-000000009992 | 00000000-0000-0000-0000-000000010008 | 00009992-0000-0000-0000-000000009992 | 00010008-0000-0000-0000-000000010008
+ 9994 | 00000000-0000-0000-0000-000000009993 | 00000000-0000-0000-0000-000000010007 | 00009993-0000-0000-0000-000000009993 | 00010007-0000-0000-0000-000000010007
+(5 rows)
+
+ROLLBACK;
+----
+-- test forward and backward scans for in-memory and disk based tuplesort
+----
+-- in-memory
+BEGIN;
+SET LOCAL enable_indexscan = false;
+-- unfortunately can't show analyze output confirming sort method,
+-- the memory used output wouldn't be stable
+EXPLAIN (COSTS OFF) DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing;
+ QUERY PLAN
+--------------------------------------
+ Sort
+ Sort Key: noabort_decreasing
+ -> Seq Scan on abbrev_abort_uuids
+(3 rows)
+
+DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing;
+-- first and second
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------------------------
+ 00000000-0000-0000-0000-000000000000
+(1 row)
+
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------------------------
+ 00000000-0000-0000-0000-000000000000
+(1 row)
+
+-- scroll beyond beginning
+FETCH BACKWARD FROM c;
+ noabort_decreasing
+--------------------------------------
+ 00000000-0000-0000-0000-000000000000
+(1 row)
+
+FETCH BACKWARD FROM c;
+ noabort_decreasing
+--------------------
+(0 rows)
+
+FETCH BACKWARD FROM c;
+ noabort_decreasing
+--------------------
+(0 rows)
+
+FETCH BACKWARD FROM c;
+ noabort_decreasing
+--------------------
+(0 rows)
+
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------------------------
+ 00000000-0000-0000-0000-000000000000
+(1 row)
+
+-- scroll beyond end end
+FETCH LAST FROM c;
+ noabort_decreasing
+--------------------
+
+(1 row)
+
+FETCH BACKWARD FROM c;
+ noabort_decreasing
+--------------------
+
+(1 row)
+
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------
+
+(1 row)
+
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------
+(0 rows)
+
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------
+(0 rows)
+
+FETCH BACKWARD FROM c;
+ noabort_decreasing
+--------------------
+
+(1 row)
+
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------
+(0 rows)
+
+COMMIT;
+-- disk based
+BEGIN;
+SET LOCAL enable_indexscan = false;
+SET LOCAL work_mem = '100kB';
+-- unfortunately can't show analyze output confirming sort method,
+-- the memory used output wouldn't be stable
+EXPLAIN (COSTS OFF) DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing;
+ QUERY PLAN
+--------------------------------------
+ Sort
+ Sort Key: noabort_decreasing
+ -> Seq Scan on abbrev_abort_uuids
+(3 rows)
+
+DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing;
+-- first and second
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------------------------
+ 00000000-0000-0000-0000-000000000000
+(1 row)
+
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------------------------
+ 00000000-0000-0000-0000-000000000000
+(1 row)
+
+-- scroll beyond beginning
+FETCH BACKWARD FROM c;
+ noabort_decreasing
+--------------------------------------
+ 00000000-0000-0000-0000-000000000000
+(1 row)
+
+FETCH BACKWARD FROM c;
+ noabort_decreasing
+--------------------
+(0 rows)
+
+FETCH BACKWARD FROM c;
+ noabort_decreasing
+--------------------
+(0 rows)
+
+FETCH BACKWARD FROM c;
+ noabort_decreasing
+--------------------
+(0 rows)
+
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------------------------
+ 00000000-0000-0000-0000-000000000000
+(1 row)
+
+-- scroll beyond end end
+FETCH LAST FROM c;
+ noabort_decreasing
+--------------------
+
+(1 row)
+
+FETCH BACKWARD FROM c;
+ noabort_decreasing
+--------------------
+
+(1 row)
+
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------
+
+(1 row)
+
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------
+(0 rows)
+
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------
+(0 rows)
+
+FETCH BACKWARD FROM c;
+ noabort_decreasing
+--------------------
+
+(1 row)
+
+FETCH NEXT FROM c;
+ noabort_decreasing
+--------------------
+(0 rows)
+
+COMMIT;
+----
+-- test tuplesort using both in-memory and disk sort
+---
+-- memory based
+SELECT
+ -- fixed-width by-value datum
+ (array_agg(id ORDER BY id DESC NULLS FIRST))[0:5],
+ -- fixed-width by-ref datum
+ (array_agg(abort_increasing ORDER BY abort_increasing DESC NULLS LAST))[0:5],
+ -- variable-width datum
+ (array_agg(id::text ORDER BY id::text DESC NULLS LAST))[0:5],
+ -- fixed width by-value datum tuplesort
+ percentile_disc(0.99) WITHIN GROUP (ORDER BY id),
+ -- ensure state is shared
+ percentile_disc(0.01) WITHIN GROUP (ORDER BY id),
+ -- fixed width by-ref datum tuplesort
+ percentile_disc(0.8) WITHIN GROUP (ORDER BY abort_increasing),
+ -- variable width by-ref datum tuplesort
+ percentile_disc(0.2) WITHIN GROUP (ORDER BY id::text),
+ -- multi-column tuplesort
+ rank('00000000-0000-0000-0000-000000000000', '2', '2') WITHIN GROUP (ORDER BY noabort_increasing, id, id::text)
+FROM (
+ SELECT * FROM abbrev_abort_uuids
+ UNION ALL
+ SELECT NULL, NULL, NULL, NULL, NULL) s;
+ array_agg | array_agg | array_agg | percentile_disc | percentile_disc | percentile_disc | percentile_disc | rank
+--------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------+-----------------+-----------------+--------------------------------------+-----------------+------
+ {NULL,20010,20009,20008,20007} | {00000000-0000-0000-0000-000000020000,00000000-0000-0000-0000-000000020000,00000000-0000-0000-0000-000000019999,00000000-0000-0000-0000-000000019998,00000000-0000-0000-0000-000000019997} | {9999,9998,9997,9996,9995} | 19810 | 200 | 00000000-0000-0000-0000-000000016003 | 136 | 2
+(1 row)
+
+-- disk based (see also above)
+BEGIN;
+SET LOCAL work_mem = '100kB';
+SELECT
+ (array_agg(id ORDER BY id DESC NULLS FIRST))[0:5],
+ (array_agg(abort_increasing ORDER BY abort_increasing DESC NULLS LAST))[0:5],
+ (array_agg(id::text ORDER BY id::text DESC NULLS LAST))[0:5],
+ percentile_disc(0.99) WITHIN GROUP (ORDER BY id),
+ percentile_disc(0.01) WITHIN GROUP (ORDER BY id),
+ percentile_disc(0.8) WITHIN GROUP (ORDER BY abort_increasing),
+ percentile_disc(0.2) WITHIN GROUP (ORDER BY id::text),
+ rank('00000000-0000-0000-0000-000000000000', '2', '2') WITHIN GROUP (ORDER BY noabort_increasing, id, id::text)
+FROM (
+ SELECT * FROM abbrev_abort_uuids
+ UNION ALL
+ SELECT NULL, NULL, NULL, NULL, NULL) s;
+ array_agg | array_agg | array_agg | percentile_disc | percentile_disc | percentile_disc | percentile_disc | rank
+--------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------+-----------------+-----------------+--------------------------------------+-----------------+------
+ {NULL,20010,20009,20008,20007} | {00000000-0000-0000-0000-000000020000,00000000-0000-0000-0000-000000020000,00000000-0000-0000-0000-000000019999,00000000-0000-0000-0000-000000019998,00000000-0000-0000-0000-000000019997} | {9999,9998,9997,9996,9995} | 19810 | 200 | 00000000-0000-0000-0000-000000016003 | 136 | 2
+(1 row)
+
+ROLLBACK;
+----
+-- test tuplesort mark/restore
+---
+CREATE TEMP TABLE test_mark_restore(col1 int, col2 int, col12 int);
+-- need a few duplicates for mark/restore to matter
+INSERT INTO test_mark_restore(col1, col2, col12)
+ SELECT a.i, b.i, a.i * b.i FROM generate_series(1, 500) a(i), generate_series(1, 5) b(i);
+BEGIN;
+SET LOCAL enable_nestloop = off;
+SET LOCAL enable_hashjoin = off;
+SET LOCAL enable_material = off;
+-- set query into variable once, to avoid repetition of the fairly long query
+SELECT $$
+ SELECT col12, count(distinct a.col1), count(distinct a.col2), count(distinct b.col1), count(distinct b.col2), count(*)
+ FROM test_mark_restore a
+ JOIN test_mark_restore b USING(col12)
+ GROUP BY 1
+ HAVING count(*) > 1
+ ORDER BY 2 DESC, 1 DESC, 3 DESC, 4 DESC, 5 DESC, 6 DESC
+ LIMIT 10
+$$ AS qry \gset
+-- test mark/restore with in-memory sorts
+EXPLAIN (COSTS OFF) :qry;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
+ -> Sort
+ Sort Key: (count(DISTINCT a.col1)) DESC, a.col12 DESC, (count(DISTINCT a.col2)) DESC, (count(DISTINCT b.col1)) DESC, (count(DISTINCT b.col2)) DESC, (count(*)) DESC
+ -> GroupAggregate
+ Group Key: a.col12
+ Filter: (count(*) > 1)
+ -> Merge Join
+ Merge Cond: (a.col12 = b.col12)
+ -> Sort
+ Sort Key: a.col12 DESC
+ -> Seq Scan on test_mark_restore a
+ -> Sort
+ Sort Key: b.col12 DESC
+ -> Seq Scan on test_mark_restore b
+(14 rows)
+
+:qry;
+ col12 | count | count | count | count | count
+-------+-------+-------+-------+-------+-------
+ 480 | 5 | 5 | 5 | 5 | 25
+ 420 | 5 | 5 | 5 | 5 | 25
+ 360 | 5 | 5 | 5 | 5 | 25
+ 300 | 5 | 5 | 5 | 5 | 25
+ 240 | 5 | 5 | 5 | 5 | 25
+ 180 | 5 | 5 | 5 | 5 | 25
+ 120 | 5 | 5 | 5 | 5 | 25
+ 60 | 5 | 5 | 5 | 5 | 25
+ 960 | 4 | 4 | 4 | 4 | 16
+ 900 | 4 | 4 | 4 | 4 | 16
+(10 rows)
+
+-- test mark/restore with on-disk sorts
+SET LOCAL work_mem = '100kB';
+EXPLAIN (COSTS OFF) :qry;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
+ -> Sort
+ Sort Key: (count(DISTINCT a.col1)) DESC, a.col12 DESC, (count(DISTINCT a.col2)) DESC, (count(DISTINCT b.col1)) DESC, (count(DISTINCT b.col2)) DESC, (count(*)) DESC
+ -> GroupAggregate
+ Group Key: a.col12
+ Filter: (count(*) > 1)
+ -> Merge Join
+ Merge Cond: (a.col12 = b.col12)
+ -> Sort
+ Sort Key: a.col12 DESC
+ -> Seq Scan on test_mark_restore a
+ -> Sort
+ Sort Key: b.col12 DESC
+ -> Seq Scan on test_mark_restore b
+(14 rows)
+
+:qry;
+ col12 | count | count | count | count | count
+-------+-------+-------+-------+-------+-------
+ 480 | 5 | 5 | 5 | 5 | 25
+ 420 | 5 | 5 | 5 | 5 | 25
+ 360 | 5 | 5 | 5 | 5 | 25
+ 300 | 5 | 5 | 5 | 5 | 25
+ 240 | 5 | 5 | 5 | 5 | 25
+ 180 | 5 | 5 | 5 | 5 | 25
+ 120 | 5 | 5 | 5 | 5 | 25
+ 60 | 5 | 5 | 5 | 5 | 25
+ 960 | 4 | 4 | 4 | 4 | 16
+ 900 | 4 | 4 | 4 | 4 | 16
+(10 rows)
+
+COMMIT;
diff --git a/src/test/regress/expected/txid.out b/src/test/regress/expected/txid.out
new file mode 100644
index 0000000..95ba66e
--- /dev/null
+++ b/src/test/regress/expected/txid.out
@@ -0,0 +1,327 @@
+-- txid_snapshot data type and related functions
+-- Note: these are backward-compatibility functions and types, and have been
+-- replaced by new xid8-based variants. See xid.sql. The txid variants will
+-- be removed in a future release.
+-- i/o
+select '12:13:'::txid_snapshot;
+ txid_snapshot
+---------------
+ 12:13:
+(1 row)
+
+select '12:18:14,16'::txid_snapshot;
+ txid_snapshot
+---------------
+ 12:18:14,16
+(1 row)
+
+select '12:16:14,14'::txid_snapshot;
+ txid_snapshot
+---------------
+ 12:16:14
+(1 row)
+
+-- errors
+select '31:12:'::txid_snapshot;
+ERROR: invalid input syntax for type pg_snapshot: "31:12:"
+LINE 1: select '31:12:'::txid_snapshot;
+ ^
+select '0:1:'::txid_snapshot;
+ERROR: invalid input syntax for type pg_snapshot: "0:1:"
+LINE 1: select '0:1:'::txid_snapshot;
+ ^
+select '12:13:0'::txid_snapshot;
+ERROR: invalid input syntax for type pg_snapshot: "12:13:0"
+LINE 1: select '12:13:0'::txid_snapshot;
+ ^
+select '12:16:14,13'::txid_snapshot;
+ERROR: invalid input syntax for type pg_snapshot: "12:16:14,13"
+LINE 1: select '12:16:14,13'::txid_snapshot;
+ ^
+create temp table snapshot_test (
+ nr integer,
+ snap txid_snapshot
+);
+insert into snapshot_test values (1, '12:13:');
+insert into snapshot_test values (2, '12:20:13,15,18');
+insert into snapshot_test values (3, '100001:100009:100005,100007,100008');
+insert into snapshot_test values (4, '100:150: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');
+select snap from snapshot_test order by nr;
+ snap
+-------------------------------------------------------------------------------------------------------------------------------------
+ 12:13:
+ 12:20:13,15,18
+ 100001:100009:100005,100007,100008
+ 100:150: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
+(4 rows)
+
+select txid_snapshot_xmin(snap),
+ txid_snapshot_xmax(snap),
+ txid_snapshot_xip(snap)
+from snapshot_test order by nr;
+ txid_snapshot_xmin | txid_snapshot_xmax | txid_snapshot_xip
+--------------------+--------------------+-------------------
+ 12 | 20 | 13
+ 12 | 20 | 15
+ 12 | 20 | 18
+ 100001 | 100009 | 100005
+ 100001 | 100009 | 100007
+ 100001 | 100009 | 100008
+ 100 | 150 | 101
+ 100 | 150 | 102
+ 100 | 150 | 103
+ 100 | 150 | 104
+ 100 | 150 | 105
+ 100 | 150 | 106
+ 100 | 150 | 107
+ 100 | 150 | 108
+ 100 | 150 | 109
+ 100 | 150 | 110
+ 100 | 150 | 111
+ 100 | 150 | 112
+ 100 | 150 | 113
+ 100 | 150 | 114
+ 100 | 150 | 115
+ 100 | 150 | 116
+ 100 | 150 | 117
+ 100 | 150 | 118
+ 100 | 150 | 119
+ 100 | 150 | 120
+ 100 | 150 | 121
+ 100 | 150 | 122
+ 100 | 150 | 123
+ 100 | 150 | 124
+ 100 | 150 | 125
+ 100 | 150 | 126
+ 100 | 150 | 127
+ 100 | 150 | 128
+ 100 | 150 | 129
+ 100 | 150 | 130
+ 100 | 150 | 131
+(37 rows)
+
+select id, txid_visible_in_snapshot(id, snap)
+from snapshot_test, generate_series(11, 21) id
+where nr = 2;
+ id | txid_visible_in_snapshot
+----+--------------------------
+ 11 | t
+ 12 | t
+ 13 | f
+ 14 | t
+ 15 | f
+ 16 | t
+ 17 | t
+ 18 | f
+ 19 | t
+ 20 | f
+ 21 | f
+(11 rows)
+
+-- test bsearch
+select id, txid_visible_in_snapshot(id, snap)
+from snapshot_test, generate_series(90, 160) id
+where nr = 4;
+ id | txid_visible_in_snapshot
+-----+--------------------------
+ 90 | t
+ 91 | t
+ 92 | t
+ 93 | t
+ 94 | t
+ 95 | t
+ 96 | t
+ 97 | t
+ 98 | t
+ 99 | t
+ 100 | t
+ 101 | f
+ 102 | f
+ 103 | f
+ 104 | f
+ 105 | f
+ 106 | f
+ 107 | f
+ 108 | f
+ 109 | f
+ 110 | f
+ 111 | f
+ 112 | f
+ 113 | f
+ 114 | f
+ 115 | f
+ 116 | f
+ 117 | f
+ 118 | f
+ 119 | f
+ 120 | f
+ 121 | f
+ 122 | f
+ 123 | f
+ 124 | f
+ 125 | f
+ 126 | f
+ 127 | f
+ 128 | f
+ 129 | f
+ 130 | f
+ 131 | f
+ 132 | t
+ 133 | t
+ 134 | t
+ 135 | t
+ 136 | t
+ 137 | t
+ 138 | t
+ 139 | t
+ 140 | t
+ 141 | t
+ 142 | t
+ 143 | t
+ 144 | t
+ 145 | t
+ 146 | t
+ 147 | t
+ 148 | t
+ 149 | t
+ 150 | f
+ 151 | f
+ 152 | f
+ 153 | f
+ 154 | f
+ 155 | f
+ 156 | f
+ 157 | f
+ 158 | f
+ 159 | f
+ 160 | f
+(71 rows)
+
+-- test current values also
+select txid_current() >= txid_snapshot_xmin(txid_current_snapshot());
+ ?column?
+----------
+ t
+(1 row)
+
+-- we can't assume current is always less than xmax, however
+select txid_visible_in_snapshot(txid_current(), txid_current_snapshot());
+ txid_visible_in_snapshot
+--------------------------
+ f
+(1 row)
+
+-- test 64bitness
+select txid_snapshot '1000100010001000:1000100010001100:1000100010001012,1000100010001013';
+ txid_snapshot
+---------------------------------------------------------------------
+ 1000100010001000:1000100010001100:1000100010001012,1000100010001013
+(1 row)
+
+select txid_visible_in_snapshot('1000100010001012', '1000100010001000:1000100010001100:1000100010001012,1000100010001013');
+ txid_visible_in_snapshot
+--------------------------
+ f
+(1 row)
+
+select txid_visible_in_snapshot('1000100010001015', '1000100010001000:1000100010001100:1000100010001012,1000100010001013');
+ txid_visible_in_snapshot
+--------------------------
+ t
+(1 row)
+
+-- test 64bit overflow
+SELECT txid_snapshot '1:9223372036854775807:3';
+ txid_snapshot
+-------------------------
+ 1:9223372036854775807:3
+(1 row)
+
+SELECT txid_snapshot '1:9223372036854775808:3';
+ERROR: invalid input syntax for type pg_snapshot: "1:9223372036854775808:3"
+LINE 1: SELECT txid_snapshot '1:9223372036854775808:3';
+ ^
+-- test txid_current_if_assigned
+BEGIN;
+SELECT txid_current_if_assigned() IS NULL;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT txid_current() \gset
+SELECT txid_current_if_assigned() IS NOT DISTINCT FROM BIGINT :'txid_current';
+ ?column?
+----------
+ t
+(1 row)
+
+COMMIT;
+-- test xid status functions
+BEGIN;
+SELECT txid_current() AS committed \gset
+COMMIT;
+BEGIN;
+SELECT txid_current() AS rolledback \gset
+ROLLBACK;
+BEGIN;
+SELECT txid_current() AS inprogress \gset
+SELECT txid_status(:committed) AS committed;
+ committed
+-----------
+ committed
+(1 row)
+
+SELECT txid_status(:rolledback) AS rolledback;
+ rolledback
+------------
+ aborted
+(1 row)
+
+SELECT txid_status(:inprogress) AS inprogress;
+ inprogress
+-------------
+ in progress
+(1 row)
+
+SELECT txid_status(1); -- BootstrapTransactionId is always committed
+ txid_status
+-------------
+ committed
+(1 row)
+
+SELECT txid_status(2); -- FrozenTransactionId is always committed
+ txid_status
+-------------
+ committed
+(1 row)
+
+SELECT txid_status(3); -- in regress testing FirstNormalTransactionId will always be behind oldestXmin
+ txid_status
+-------------
+
+(1 row)
+
+COMMIT;
+BEGIN;
+CREATE FUNCTION test_future_xid_status(bigint)
+RETURNS void
+LANGUAGE plpgsql
+AS
+$$
+BEGIN
+ PERFORM txid_status($1);
+ RAISE EXCEPTION 'didn''t ERROR at xid in the future as expected';
+EXCEPTION
+ WHEN invalid_parameter_value THEN
+ RAISE NOTICE 'Got expected error for xid in the future';
+END;
+$$;
+SELECT test_future_xid_status(:inprogress + 10000);
+NOTICE: Got expected error for xid in the future
+ test_future_xid_status
+------------------------
+
+(1 row)
+
+ROLLBACK;
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
new file mode 100644
index 0000000..257b6ca
--- /dev/null
+++ b/src/test/regress/expected/type_sanity.out
@@ -0,0 +1,778 @@
+--
+-- TYPE_SANITY
+-- Sanity checks for common errors in making type-related system tables:
+-- pg_type, pg_class, pg_attribute, pg_range.
+--
+-- None of the SELECTs here should ever find any matching entries,
+-- so the expected output is easy to maintain ;-).
+-- A test failure indicates someone messed up an entry in the system tables.
+--
+-- 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.
+-- **************** pg_type ****************
+-- Look for illegal values in pg_type fields.
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE p1.typnamespace = 0 OR
+ (p1.typlen <= 0 AND p1.typlen != -1 AND p1.typlen != -2) OR
+ (p1.typtype not in ('b', 'c', 'd', 'e', 'p', 'r', 'm')) OR
+ NOT p1.typisdefined OR
+ (p1.typalign not in ('c', 's', 'i', 'd')) OR
+ (p1.typstorage not in ('p', 'x', 'e', 'm'));
+ oid | typname
+-----+---------
+(0 rows)
+
+-- Look for "pass by value" types that can't be passed by value.
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE p1.typbyval AND
+ (p1.typlen != 1 OR p1.typalign != 'c') AND
+ (p1.typlen != 2 OR p1.typalign != 's') AND
+ (p1.typlen != 4 OR p1.typalign != 'i') AND
+ (p1.typlen != 8 OR p1.typalign != 'd');
+ oid | typname
+-----+---------
+(0 rows)
+
+-- Look for "toastable" types that aren't varlena.
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE p1.typstorage != 'p' AND
+ (p1.typbyval OR p1.typlen != -1);
+ oid | typname
+-----+---------
+(0 rows)
+
+-- Look for complex types that do not have a typrelid entry,
+-- or basic types that do.
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE (p1.typtype = 'c' AND p1.typrelid = 0) OR
+ (p1.typtype != 'c' AND p1.typrelid != 0);
+ oid | typname
+-----+---------
+(0 rows)
+
+-- Look for types that should have an array type but don't.
+-- Generally anything that's not a pseudotype should have an array type.
+-- However, we do have a small number of exceptions.
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE p1.typtype not in ('p') AND p1.typname NOT LIKE E'\\_%'
+ AND NOT EXISTS
+ (SELECT 1 FROM pg_type as p2
+ WHERE p2.typname = ('_' || p1.typname)::name AND
+ p2.typelem = p1.oid and p1.typarray = p2.oid)
+ORDER BY p1.oid;
+ oid | typname
+------+------------------------------
+ 194 | pg_node_tree
+ 3361 | pg_ndistinct
+ 3402 | pg_dependencies
+ 4600 | pg_brin_bloom_summary
+ 4601 | pg_brin_minmax_multi_summary
+ 5017 | pg_mcv_list
+(6 rows)
+
+-- Make sure typarray points to a "true" array type of our own base
+SELECT p1.oid, p1.typname as basetype, p2.typname as arraytype,
+ p2.typsubscript
+FROM pg_type p1 LEFT JOIN pg_type p2 ON (p1.typarray = p2.oid)
+WHERE p1.typarray <> 0 AND
+ (p2.oid IS NULL OR
+ p2.typsubscript <> 'array_subscript_handler'::regproc);
+ oid | basetype | arraytype | typsubscript
+-----+----------+-----------+--------------
+(0 rows)
+
+-- Look for range types that do not have a pg_range entry
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE p1.typtype = 'r' AND
+ NOT EXISTS(SELECT 1 FROM pg_range r WHERE rngtypid = p1.oid);
+ oid | typname
+-----+---------
+(0 rows)
+
+-- Look for range types whose typalign isn't sufficient
+SELECT p1.oid, p1.typname, p1.typalign, p2.typname, p2.typalign
+FROM pg_type as p1
+ LEFT JOIN pg_range as r ON rngtypid = p1.oid
+ LEFT JOIN pg_type as p2 ON rngsubtype = p2.oid
+WHERE p1.typtype = 'r' AND
+ (p1.typalign != (CASE WHEN p2.typalign = 'd' THEN 'd'::"char"
+ ELSE 'i'::"char" END)
+ OR p2.oid IS NULL);
+ oid | typname | typalign | typname | typalign
+-----+---------+----------+---------+----------
+(0 rows)
+
+-- Text conversion routines must be provided.
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE (p1.typinput = 0 OR p1.typoutput = 0);
+ oid | typname
+-----+---------
+(0 rows)
+
+-- Check for bogus typinput routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typinput = p2.oid AND NOT
+ ((p2.pronargs = 1 AND p2.proargtypes[0] = 'cstring'::regtype) OR
+ (p2.pronargs = 2 AND p2.proargtypes[0] = 'cstring'::regtype AND
+ p2.proargtypes[1] = 'oid'::regtype) OR
+ (p2.pronargs = 3 AND p2.proargtypes[0] = 'cstring'::regtype AND
+ p2.proargtypes[1] = 'oid'::regtype AND
+ p2.proargtypes[2] = 'int4'::regtype));
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- Check for type of the variadic array parameter's elements.
+-- provariadic should be ANYOID if the type of the last element is ANYOID,
+-- ANYELEMENTOID if the type of the last element is ANYARRAYOID,
+-- ANYCOMPATIBLEOID if the type of the last element is ANYCOMPATIBLEARRAYOID,
+-- and otherwise the element type corresponding to the array type.
+SELECT oid::regprocedure, provariadic::regtype, proargtypes::regtype[]
+FROM pg_proc
+WHERE provariadic != 0
+AND case proargtypes[array_length(proargtypes, 1)-1]
+ WHEN '"any"'::regtype THEN '"any"'::regtype
+ WHEN 'anyarray'::regtype THEN 'anyelement'::regtype
+ WHEN 'anycompatiblearray'::regtype THEN 'anycompatible'::regtype
+ ELSE (SELECT t.oid
+ FROM pg_type t
+ WHERE t.typarray = proargtypes[array_length(proargtypes, 1)-1])
+ END != provariadic;
+ oid | provariadic | proargtypes
+-----+-------------+-------------
+(0 rows)
+
+-- Check that all and only those functions with a variadic type have
+-- a variadic argument.
+SELECT oid::regprocedure, proargmodes, provariadic
+FROM pg_proc
+WHERE (proargmodes IS NOT NULL AND 'v' = any(proargmodes))
+ IS DISTINCT FROM
+ (provariadic != 0);
+ oid | proargmodes | provariadic
+-----+-------------+-------------
+(0 rows)
+
+-- As of 8.0, this check finds refcursor, which is borrowing
+-- other types' I/O routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typinput = p2.oid AND p1.typtype in ('b', 'p') AND NOT
+ (p1.typelem != 0 AND p1.typlen < 0) AND NOT
+ (p2.prorettype = p1.oid AND NOT p2.proretset)
+ORDER BY 1;
+ oid | typname | oid | proname
+------+-----------+-----+---------
+ 1790 | refcursor | 46 | textin
+(1 row)
+
+-- Varlena array types will point to array_in
+-- Exception as of 8.1: int2vector and oidvector have their own I/O routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typinput = p2.oid AND
+ (p1.typelem != 0 AND p1.typlen < 0) AND NOT
+ (p2.oid = 'array_in'::regproc)
+ORDER BY 1;
+ oid | typname | oid | proname
+-----+------------+-----+--------------
+ 22 | int2vector | 40 | int2vectorin
+ 30 | oidvector | 54 | oidvectorin
+(2 rows)
+
+-- typinput routines should not be volatile
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typinput = p2.oid AND p2.provolatile NOT IN ('i', 's');
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- Composites, domains, enums, multiranges, ranges should all use the same input routines
+SELECT DISTINCT typtype, typinput
+FROM pg_type AS p1
+WHERE p1.typtype not in ('b', 'p')
+ORDER BY 1;
+ typtype | typinput
+---------+---------------
+ c | record_in
+ d | domain_in
+ e | enum_in
+ m | multirange_in
+ r | range_in
+(5 rows)
+
+-- Check for bogus typoutput routines
+-- As of 8.0, this check finds refcursor, which is borrowing
+-- other types' I/O routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typoutput = p2.oid AND p1.typtype in ('b', 'p') AND NOT
+ (p2.pronargs = 1 AND
+ (p2.proargtypes[0] = p1.oid OR
+ (p2.oid = 'array_out'::regproc AND
+ p1.typelem != 0 AND p1.typlen = -1)))
+ORDER BY 1;
+ oid | typname | oid | proname
+------+-----------+-----+---------
+ 1790 | refcursor | 47 | textout
+(1 row)
+
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typoutput = p2.oid AND NOT
+ (p2.prorettype = 'cstring'::regtype AND NOT p2.proretset);
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- typoutput routines should not be volatile
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typoutput = p2.oid AND p2.provolatile NOT IN ('i', 's');
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- Composites, enums, multiranges, ranges should all use the same output routines
+SELECT DISTINCT typtype, typoutput
+FROM pg_type AS p1
+WHERE p1.typtype not in ('b', 'd', 'p')
+ORDER BY 1;
+ typtype | typoutput
+---------+----------------
+ c | record_out
+ e | enum_out
+ m | multirange_out
+ r | range_out
+(4 rows)
+
+-- Domains should have same typoutput as their base types
+SELECT p1.oid, p1.typname, p2.oid, p2.typname
+FROM pg_type AS p1 LEFT JOIN pg_type AS p2 ON p1.typbasetype = p2.oid
+WHERE p1.typtype = 'd' AND p1.typoutput IS DISTINCT FROM p2.typoutput;
+ oid | typname | oid | typname
+-----+---------+-----+---------
+(0 rows)
+
+-- Check for bogus typreceive routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typreceive = p2.oid AND NOT
+ ((p2.pronargs = 1 AND p2.proargtypes[0] = 'internal'::regtype) OR
+ (p2.pronargs = 2 AND p2.proargtypes[0] = 'internal'::regtype AND
+ p2.proargtypes[1] = 'oid'::regtype) OR
+ (p2.pronargs = 3 AND p2.proargtypes[0] = 'internal'::regtype AND
+ p2.proargtypes[1] = 'oid'::regtype AND
+ p2.proargtypes[2] = 'int4'::regtype));
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- As of 7.4, this check finds refcursor, which is borrowing
+-- other types' I/O routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typreceive = p2.oid AND p1.typtype in ('b', 'p') AND NOT
+ (p1.typelem != 0 AND p1.typlen < 0) AND NOT
+ (p2.prorettype = p1.oid AND NOT p2.proretset)
+ORDER BY 1;
+ oid | typname | oid | proname
+------+-----------+------+----------
+ 1790 | refcursor | 2414 | textrecv
+(1 row)
+
+-- Varlena array types will point to array_recv
+-- Exception as of 8.1: int2vector and oidvector have their own I/O routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typreceive = p2.oid AND
+ (p1.typelem != 0 AND p1.typlen < 0) AND NOT
+ (p2.oid = 'array_recv'::regproc)
+ORDER BY 1;
+ oid | typname | oid | proname
+-----+------------+------+----------------
+ 22 | int2vector | 2410 | int2vectorrecv
+ 30 | oidvector | 2420 | oidvectorrecv
+(2 rows)
+
+-- Suspicious if typreceive doesn't take same number of args as typinput
+SELECT p1.oid, p1.typname, p2.oid, p2.proname, p3.oid, p3.proname
+FROM pg_type AS p1, pg_proc AS p2, pg_proc AS p3
+WHERE p1.typinput = p2.oid AND p1.typreceive = p3.oid AND
+ p2.pronargs != p3.pronargs;
+ oid | typname | oid | proname | oid | proname
+-----+---------+-----+---------+-----+---------
+(0 rows)
+
+-- typreceive routines should not be volatile
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typreceive = p2.oid AND p2.provolatile NOT IN ('i', 's');
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- Composites, domains, enums, multiranges, ranges should all use the same receive routines
+SELECT DISTINCT typtype, typreceive
+FROM pg_type AS p1
+WHERE p1.typtype not in ('b', 'p')
+ORDER BY 1;
+ typtype | typreceive
+---------+-----------------
+ c | record_recv
+ d | domain_recv
+ e | enum_recv
+ m | multirange_recv
+ r | range_recv
+(5 rows)
+
+-- Check for bogus typsend routines
+-- As of 7.4, this check finds refcursor, which is borrowing
+-- other types' I/O routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typsend = p2.oid AND p1.typtype in ('b', 'p') AND NOT
+ (p2.pronargs = 1 AND
+ (p2.proargtypes[0] = p1.oid OR
+ (p2.oid = 'array_send'::regproc AND
+ p1.typelem != 0 AND p1.typlen = -1)))
+ORDER BY 1;
+ oid | typname | oid | proname
+------+-----------+------+----------
+ 1790 | refcursor | 2415 | textsend
+(1 row)
+
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typsend = p2.oid AND NOT
+ (p2.prorettype = 'bytea'::regtype AND NOT p2.proretset);
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- typsend routines should not be volatile
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typsend = p2.oid AND p2.provolatile NOT IN ('i', 's');
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- Composites, enums, multiranges, ranges should all use the same send routines
+SELECT DISTINCT typtype, typsend
+FROM pg_type AS p1
+WHERE p1.typtype not in ('b', 'd', 'p')
+ORDER BY 1;
+ typtype | typsend
+---------+-----------------
+ c | record_send
+ e | enum_send
+ m | multirange_send
+ r | range_send
+(4 rows)
+
+-- Domains should have same typsend as their base types
+SELECT p1.oid, p1.typname, p2.oid, p2.typname
+FROM pg_type AS p1 LEFT JOIN pg_type AS p2 ON p1.typbasetype = p2.oid
+WHERE p1.typtype = 'd' AND p1.typsend IS DISTINCT FROM p2.typsend;
+ oid | typname | oid | typname
+-----+---------+-----+---------
+(0 rows)
+
+-- Check for bogus typmodin routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typmodin = p2.oid AND NOT
+ (p2.pronargs = 1 AND
+ p2.proargtypes[0] = 'cstring[]'::regtype AND
+ p2.prorettype = 'int4'::regtype AND NOT p2.proretset);
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- typmodin routines should not be volatile
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typmodin = p2.oid AND p2.provolatile NOT IN ('i', 's');
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- Check for bogus typmodout routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typmodout = p2.oid AND NOT
+ (p2.pronargs = 1 AND
+ p2.proargtypes[0] = 'int4'::regtype AND
+ p2.prorettype = 'cstring'::regtype AND NOT p2.proretset);
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- typmodout routines should not be volatile
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typmodout = p2.oid AND p2.provolatile NOT IN ('i', 's');
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- Array types should have same typmodin/out as their element types
+SELECT p1.oid, p1.typname, p2.oid, p2.typname
+FROM pg_type AS p1, pg_type AS p2
+WHERE p1.typelem = p2.oid AND NOT
+ (p1.typmodin = p2.typmodin AND p1.typmodout = p2.typmodout);
+ oid | typname | oid | typname
+-----+---------+-----+---------
+(0 rows)
+
+-- Array types should have same typdelim as their element types
+SELECT p1.oid, p1.typname, p2.oid, p2.typname
+FROM pg_type AS p1, pg_type AS p2
+WHERE p1.typarray = p2.oid AND NOT (p1.typdelim = p2.typdelim);
+ oid | typname | oid | typname
+-----+---------+-----+---------
+(0 rows)
+
+-- Look for array types whose typalign isn't sufficient
+SELECT p1.oid, p1.typname, p1.typalign, p2.typname, p2.typalign
+FROM pg_type AS p1, pg_type AS p2
+WHERE p1.typarray = p2.oid AND
+ p2.typalign != (CASE WHEN p1.typalign = 'd' THEN 'd'::"char"
+ ELSE 'i'::"char" END);
+ oid | typname | typalign | typname | typalign
+-----+---------+----------+---------+----------
+(0 rows)
+
+-- Check for typelem set without a handler
+SELECT p1.oid, p1.typname, p1.typelem
+FROM pg_type AS p1
+WHERE p1.typelem != 0 AND p1.typsubscript = 0;
+ oid | typname | typelem
+-----+---------+---------
+(0 rows)
+
+-- Check for misuse of standard subscript handlers
+SELECT p1.oid, p1.typname,
+ p1.typelem, p1.typlen, p1.typbyval
+FROM pg_type AS p1
+WHERE p1.typsubscript = 'array_subscript_handler'::regproc AND NOT
+ (p1.typelem != 0 AND p1.typlen = -1 AND NOT p1.typbyval);
+ oid | typname | typelem | typlen | typbyval
+-----+---------+---------+--------+----------
+(0 rows)
+
+SELECT p1.oid, p1.typname,
+ p1.typelem, p1.typlen, p1.typbyval
+FROM pg_type AS p1
+WHERE p1.typsubscript = 'raw_array_subscript_handler'::regproc AND NOT
+ (p1.typelem != 0 AND p1.typlen > 0 AND NOT p1.typbyval);
+ oid | typname | typelem | typlen | typbyval
+-----+---------+---------+--------+----------
+(0 rows)
+
+-- Check for bogus typanalyze routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typanalyze = p2.oid AND NOT
+ (p2.pronargs = 1 AND
+ p2.proargtypes[0] = 'internal'::regtype AND
+ p2.prorettype = 'bool'::regtype AND NOT p2.proretset);
+ oid | typname | oid | proname
+-----+---------+-----+---------
+(0 rows)
+
+-- there does not seem to be a reason to care about volatility of typanalyze
+-- domains inherit their base type's typanalyze
+SELECT d.oid, d.typname, d.typanalyze, t.oid, t.typname, t.typanalyze
+FROM pg_type d JOIN pg_type t ON d.typbasetype = t.oid
+WHERE d.typanalyze != t.typanalyze;
+ oid | typname | typanalyze | oid | typname | typanalyze
+-----+---------+------------+-----+---------+------------
+(0 rows)
+
+-- range_typanalyze should be used for all and only range types
+-- (but exclude domains, which we checked above)
+SELECT t.oid, t.typname, t.typanalyze
+FROM pg_type t LEFT JOIN pg_range r on t.oid = r.rngtypid
+WHERE t.typbasetype = 0 AND
+ (t.typanalyze = 'range_typanalyze'::regproc) != (r.rngtypid IS NOT NULL);
+ oid | typname | typanalyze
+-----+---------+------------
+(0 rows)
+
+-- array_typanalyze should be used for all and only array types
+-- (but exclude domains, which we checked above)
+-- As of 9.2 this finds int2vector and oidvector, which are weird anyway
+SELECT t.oid, t.typname, t.typanalyze
+FROM pg_type t
+WHERE t.typbasetype = 0 AND
+ (t.typanalyze = 'array_typanalyze'::regproc) !=
+ (t.typsubscript = 'array_subscript_handler'::regproc)
+ORDER BY 1;
+ oid | typname | typanalyze
+-----+------------+------------
+ 22 | int2vector | -
+ 30 | oidvector | -
+(2 rows)
+
+-- **************** pg_class ****************
+-- Look for illegal values in pg_class fields
+SELECT p1.oid, p1.relname
+FROM pg_class as p1
+WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p') OR
+ relpersistence NOT IN ('p', 'u', 't') OR
+ relreplident NOT IN ('d', 'n', 'f', 'i');
+ oid | relname
+-----+---------
+(0 rows)
+
+-- All tables and indexes should have an access method.
+SELECT p1.oid, p1.relname
+FROM pg_class as p1
+WHERE p1.relkind NOT IN ('S', 'v', 'f', 'c') and
+ p1.relam = 0;
+ oid | relname
+-----+---------
+(0 rows)
+
+-- Conversely, sequences, views, types shouldn't have them
+SELECT p1.oid, p1.relname
+FROM pg_class as p1
+WHERE p1.relkind IN ('S', 'v', 'f', 'c') and
+ p1.relam != 0;
+ oid | relname
+-----+---------
+(0 rows)
+
+-- Indexes should have AMs of type 'i'
+SELECT pc.oid, pc.relname, pa.amname, pa.amtype
+FROM pg_class as pc JOIN pg_am AS pa ON (pc.relam = pa.oid)
+WHERE pc.relkind IN ('i') and
+ pa.amtype != 'i';
+ oid | relname | amname | amtype
+-----+---------+--------+--------
+(0 rows)
+
+-- Tables, matviews etc should have AMs of type 't'
+SELECT pc.oid, pc.relname, pa.amname, pa.amtype
+FROM pg_class as pc JOIN pg_am AS pa ON (pc.relam = pa.oid)
+WHERE pc.relkind IN ('r', 't', 'm') and
+ pa.amtype != 't';
+ oid | relname | amname | amtype
+-----+---------+--------+--------
+(0 rows)
+
+-- **************** pg_attribute ****************
+-- Look for illegal values in pg_attribute fields
+SELECT p1.attrelid, p1.attname
+FROM pg_attribute as p1
+WHERE p1.attrelid = 0 OR p1.atttypid = 0 OR p1.attnum = 0 OR
+ p1.attcacheoff != -1 OR p1.attinhcount < 0 OR
+ (p1.attinhcount = 0 AND NOT p1.attislocal);
+ attrelid | attname
+----------+---------
+(0 rows)
+
+-- Cross-check attnum against parent relation
+SELECT p1.attrelid, p1.attname, p2.oid, p2.relname
+FROM pg_attribute AS p1, pg_class AS p2
+WHERE p1.attrelid = p2.oid AND p1.attnum > p2.relnatts;
+ attrelid | attname | oid | relname
+----------+---------+-----+---------
+(0 rows)
+
+-- Detect missing pg_attribute entries: should have as many non-system
+-- attributes as parent relation expects
+SELECT p1.oid, p1.relname
+FROM pg_class AS p1
+WHERE p1.relnatts != (SELECT count(*) FROM pg_attribute AS p2
+ WHERE p2.attrelid = p1.oid AND p2.attnum > 0);
+ oid | relname
+-----+---------
+(0 rows)
+
+-- Cross-check against pg_type entry
+-- NOTE: we allow attstorage to be 'plain' even when typstorage is not;
+-- this is mainly for toast tables.
+SELECT p1.attrelid, p1.attname, p2.oid, p2.typname
+FROM pg_attribute AS p1, pg_type AS p2
+WHERE p1.atttypid = p2.oid AND
+ (p1.attlen != p2.typlen OR
+ p1.attalign != p2.typalign OR
+ p1.attbyval != p2.typbyval OR
+ (p1.attstorage != p2.typstorage AND p1.attstorage != 'p'));
+ attrelid | attname | oid | typname
+----------+---------+-----+---------
+(0 rows)
+
+-- **************** pg_range ****************
+-- Look for illegal values in pg_range fields.
+SELECT p1.rngtypid, p1.rngsubtype
+FROM pg_range as p1
+WHERE p1.rngtypid = 0 OR p1.rngsubtype = 0 OR p1.rngsubopc = 0;
+ rngtypid | rngsubtype
+----------+------------
+(0 rows)
+
+-- rngcollation should be specified iff subtype is collatable
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngcollation, t.typcollation
+FROM pg_range p1 JOIN pg_type t ON t.oid = p1.rngsubtype
+WHERE (rngcollation = 0) != (typcollation = 0);
+ rngtypid | rngsubtype | rngcollation | typcollation
+----------+------------+--------------+--------------
+(0 rows)
+
+-- opclass had better be a btree opclass accepting the subtype.
+-- We must allow anyarray matches, cf IsBinaryCoercible()
+SELECT p1.rngtypid, p1.rngsubtype, o.opcmethod, o.opcname
+FROM pg_range p1 JOIN pg_opclass o ON o.oid = p1.rngsubopc
+WHERE o.opcmethod != 403 OR
+ ((o.opcintype != p1.rngsubtype) AND NOT
+ (o.opcintype = 'pg_catalog.anyarray'::regtype AND
+ EXISTS(select 1 from pg_catalog.pg_type where
+ oid = p1.rngsubtype and typelem != 0 and
+ typsubscript = 'array_subscript_handler'::regproc)));
+ rngtypid | rngsubtype | opcmethod | opcname
+----------+------------+-----------+---------
+(0 rows)
+
+-- canonical function, if any, had better match the range type
+SELECT p1.rngtypid, p1.rngsubtype, p.proname
+FROM pg_range p1 JOIN pg_proc p ON p.oid = p1.rngcanonical
+WHERE pronargs != 1 OR proargtypes[0] != rngtypid OR prorettype != rngtypid;
+ rngtypid | rngsubtype | proname
+----------+------------+---------
+(0 rows)
+
+-- subdiff function, if any, had better match the subtype
+SELECT p1.rngtypid, p1.rngsubtype, p.proname
+FROM pg_range p1 JOIN pg_proc p ON p.oid = p1.rngsubdiff
+WHERE pronargs != 2
+ OR proargtypes[0] != rngsubtype OR proargtypes[1] != rngsubtype
+ OR prorettype != 'pg_catalog.float8'::regtype;
+ rngtypid | rngsubtype | proname
+----------+------------+---------
+(0 rows)
+
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid
+----------+------------+---------------
+(0 rows)
+
+-- Create a table that holds all the known in-core data types and leave it
+-- around so as pg_upgrade is able to test their binary compatibility.
+CREATE TABLE tab_core_types AS SELECT
+ '(11,12)'::point,
+ '(1,1),(2,2)'::line,
+ '((11,11),(12,12))'::lseg,
+ '((11,11),(13,13))'::box,
+ '((11,12),(13,13),(14,14))'::path AS openedpath,
+ '[(11,12),(13,13),(14,14)]'::path AS closedpath,
+ '((11,12),(13,13),(14,14))'::polygon,
+ '1,1,1'::circle,
+ 'today'::date,
+ 'now'::time,
+ 'now'::timestamp,
+ 'now'::timetz,
+ 'now'::timestamptz,
+ '12 seconds'::interval,
+ '{"reason":"because"}'::json,
+ '{"when":"now"}'::jsonb,
+ '$.a[*] ? (@ > 2)'::jsonpath,
+ '127.0.0.1'::inet,
+ '127.0.0.0/8'::cidr,
+ '00:01:03:86:1c:ba'::macaddr8,
+ '00:01:03:86:1c:ba'::macaddr,
+ 2::int2, 4::int4, 8::int8,
+ 4::float4, '8'::float8, pi()::numeric,
+ 'foo'::"char",
+ 'c'::bpchar,
+ 'abc'::varchar,
+ 'name'::name,
+ 'txt'::text,
+ true::bool,
+ E'\\xDEADBEEF'::bytea,
+ B'10001'::bit,
+ B'10001'::varbit AS varbit,
+ '12.34'::money,
+ 'abc'::refcursor,
+ '1 2'::int2vector,
+ '1 2'::oidvector,
+ format('%I=UC/%I', USER, USER)::aclitem AS aclitem,
+ 'a fat cat sat on a mat and ate a fat rat'::tsvector,
+ 'fat & rat'::tsquery,
+ 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid,
+ '11'::xid8,
+ 'pg_class'::regclass,
+ 'regtype'::regtype type,
+ 'pg_monitor'::regrole,
+ 'pg_class'::regclass::oid,
+ '(1,1)'::tid, '2'::xid, '3'::cid,
+ '10:20:10,14,15'::txid_snapshot,
+ '10:20:10,14,15'::pg_snapshot,
+ '16/B374D848'::pg_lsn,
+ 1::information_schema.cardinal_number,
+ 'l'::information_schema.character_data,
+ 'n'::information_schema.sql_identifier,
+ 'now'::information_schema.time_stamp,
+ 'YES'::information_schema.yes_or_no,
+ 'venus'::planets,
+ 'i16'::insenum,
+ '(1,2)'::int4range, '{(1,2)}'::int4multirange,
+ '(3,4)'::int8range, '{(3,4)}'::int8multirange,
+ '(1,2)'::float8range, '{(1,2)}'::float8multirange,
+ '(3,4)'::numrange, '{(3,4)}'::nummultirange,
+ '(a,b)'::textrange, '{(a,b)}'::textmultirange,
+ '(12.34, 56.78)'::cashrange, '{(12.34, 56.78)}'::cashmultirange,
+ '(2020-01-02, 2021-02-03)'::daterange,
+ '{(2020-01-02, 2021-02-03)}'::datemultirange,
+ '(2020-01-02 03:04:05, 2021-02-03 06:07:08)'::tsrange,
+ '{(2020-01-02 03:04:05, 2021-02-03 06:07:08)}'::tsmultirange,
+ '(2020-01-02 03:04:05, 2021-02-03 06:07:08)'::tstzrange,
+ '{(2020-01-02 03:04:05, 2021-02-03 06:07:08)}'::tstzmultirange,
+ arrayrange(ARRAY[1,2], ARRAY[2,1]),
+ arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+-- Sanity check on the previous table, checking that all core types are
+-- included in this table.
+SELECT oid, typname, typtype, typelem, typarray, typarray
+ FROM pg_type t
+ WHERE typtype NOT IN ('p', 'c') AND
+ -- reg* types cannot be pg_upgraded, so discard them.
+ oid != ALL(ARRAY['regproc', 'regprocedure', 'regoper',
+ 'regoperator', 'regconfig', 'regdictionary',
+ 'regnamespace', 'regcollation']::regtype[]) AND
+ -- Discard types that do not accept input values as these cannot be
+ -- tested easily.
+ -- Note: XML might be disabled at compile-time.
+ oid != ALL(ARRAY['gtsvector', 'pg_node_tree',
+ 'pg_ndistinct', 'pg_dependencies', 'pg_mcv_list',
+ 'pg_brin_bloom_summary',
+ 'pg_brin_minmax_multi_summary', 'xml']::regtype[]) AND
+ -- Discard arrays.
+ NOT EXISTS (SELECT 1 FROM pg_type u WHERE u.typarray = t.oid)
+ -- Exclude everything from the table created above. This checks
+ -- that no in-core types are missing in tab_core_types.
+ AND NOT EXISTS (SELECT 1
+ FROM pg_attribute a
+ WHERE a.atttypid=t.oid AND
+ a.attnum > 0 AND
+ a.attrelid='tab_core_types'::regclass);
+ oid | typname | typtype | typelem | typarray | typarray
+-----+---------+---------+---------+----------+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/typed_table.out b/src/test/regress/expected/typed_table.out
new file mode 100644
index 0000000..2e47ecb
--- /dev/null
+++ b/src/test/regress/expected/typed_table.out
@@ -0,0 +1,133 @@
+CREATE TABLE ttable1 OF nothing;
+ERROR: type "nothing" does not exist
+CREATE TYPE person_type AS (id int, name text);
+CREATE TABLE persons OF person_type;
+CREATE TABLE IF NOT EXISTS persons OF person_type;
+NOTICE: relation "persons" already exists, skipping
+SELECT * FROM persons;
+ id | name
+----+------
+(0 rows)
+
+\d persons
+ Table "public.persons"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ name | text | | |
+Typed table of type: person_type
+
+CREATE FUNCTION get_all_persons() RETURNS SETOF person_type
+LANGUAGE SQL
+AS $$
+ SELECT * FROM persons;
+$$;
+SELECT * FROM get_all_persons();
+ id | name
+----+------
+(0 rows)
+
+-- certain ALTER TABLE operations on typed tables are not allowed
+ALTER TABLE persons ADD COLUMN comment text;
+ERROR: cannot add column to typed table
+ALTER TABLE persons DROP COLUMN name;
+ERROR: cannot drop column from typed table
+ALTER TABLE persons RENAME COLUMN id TO num;
+ERROR: cannot rename column of typed table
+ALTER TABLE persons ALTER COLUMN name TYPE varchar;
+ERROR: cannot alter column type of typed table
+CREATE TABLE stuff (id int);
+ALTER TABLE persons INHERIT stuff;
+ERROR: cannot change inheritance of typed table
+CREATE TABLE personsx OF person_type (myname WITH OPTIONS NOT NULL); -- error
+ERROR: column "myname" does not exist
+CREATE TABLE persons2 OF person_type (
+ id WITH OPTIONS PRIMARY KEY,
+ UNIQUE (name)
+);
+\d persons2
+ Table "public.persons2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | not null |
+ name | text | | |
+Indexes:
+ "persons2_pkey" PRIMARY KEY, btree (id)
+ "persons2_name_key" UNIQUE CONSTRAINT, btree (name)
+Typed table of type: person_type
+
+CREATE TABLE persons3 OF person_type (
+ PRIMARY KEY (id),
+ name WITH OPTIONS DEFAULT ''
+);
+\d persons3
+ Table "public.persons3"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+----------
+ id | integer | | not null |
+ name | text | | | ''::text
+Indexes:
+ "persons3_pkey" PRIMARY KEY, btree (id)
+Typed table of type: person_type
+
+CREATE TABLE persons4 OF person_type (
+ name WITH OPTIONS NOT NULL,
+ name WITH OPTIONS DEFAULT '' -- error, specified more than once
+);
+ERROR: column "name" specified more than once
+DROP TYPE person_type RESTRICT;
+ERROR: cannot drop type person_type because other objects depend on it
+DETAIL: table persons depends on type person_type
+function get_all_persons() depends on type person_type
+table persons2 depends on type person_type
+table persons3 depends on type person_type
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TYPE person_type CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table persons
+drop cascades to function get_all_persons()
+drop cascades to table persons2
+drop cascades to table persons3
+CREATE TABLE persons5 OF stuff; -- only CREATE TYPE AS types may be used
+ERROR: type stuff is not a composite type
+DROP TABLE stuff;
+-- implicit casting
+CREATE TYPE person_type AS (id int, name text);
+CREATE TABLE persons OF person_type;
+INSERT INTO persons VALUES (1, 'test');
+CREATE FUNCTION namelen(person_type) RETURNS int LANGUAGE SQL AS $$ SELECT length($1.name) $$;
+SELECT id, namelen(persons) FROM persons;
+ id | namelen
+----+---------
+ 1 | 4
+(1 row)
+
+CREATE TABLE persons2 OF person_type (
+ id WITH OPTIONS PRIMARY KEY,
+ UNIQUE (name)
+);
+\d persons2
+ Table "public.persons2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | not null |
+ name | text | | |
+Indexes:
+ "persons2_pkey" PRIMARY KEY, btree (id)
+ "persons2_name_key" UNIQUE CONSTRAINT, btree (name)
+Typed table of type: person_type
+
+CREATE TABLE persons3 OF person_type (
+ PRIMARY KEY (id),
+ name NOT NULL DEFAULT ''
+);
+\d persons3
+ Table "public.persons3"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+----------
+ id | integer | | not null |
+ name | text | | not null | ''::text
+Indexes:
+ "persons3_pkey" PRIMARY KEY, btree (id)
+Typed table of type: person_type
+
diff --git a/src/test/regress/expected/unicode.out b/src/test/regress/expected/unicode.out
new file mode 100644
index 0000000..f2713a2
--- /dev/null
+++ b/src/test/regress/expected/unicode.out
@@ -0,0 +1,89 @@
+SELECT getdatabaseencoding() <> 'UTF8' AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+SELECT U&'\0061\0308bc' <> U&'\00E4bc' COLLATE "C" AS sanity_check;
+ sanity_check
+--------------
+ t
+(1 row)
+
+SELECT normalize('');
+ normalize
+-----------
+
+(1 row)
+
+SELECT normalize(U&'\0061\0308\24D1c') = U&'\00E4\24D1c' COLLATE "C" AS test_default;
+ test_default
+--------------
+ t
+(1 row)
+
+SELECT normalize(U&'\0061\0308\24D1c', NFC) = U&'\00E4\24D1c' COLLATE "C" AS test_nfc;
+ test_nfc
+----------
+ t
+(1 row)
+
+SELECT normalize(U&'\00E4bc', NFC) = U&'\00E4bc' COLLATE "C" AS test_nfc_idem;
+ test_nfc_idem
+---------------
+ t
+(1 row)
+
+SELECT normalize(U&'\00E4\24D1c', NFD) = U&'\0061\0308\24D1c' COLLATE "C" AS test_nfd;
+ test_nfd
+----------
+ t
+(1 row)
+
+SELECT normalize(U&'\0061\0308\24D1c', NFKC) = U&'\00E4bc' COLLATE "C" AS test_nfkc;
+ test_nfkc
+-----------
+ t
+(1 row)
+
+SELECT normalize(U&'\00E4\24D1c', NFKD) = U&'\0061\0308bc' COLLATE "C" AS test_nfkd;
+ test_nfkd
+-----------
+ t
+(1 row)
+
+SELECT "normalize"('abc', 'def'); -- run-time error
+ERROR: invalid normalization form: def
+SELECT U&'\00E4\24D1c' IS NORMALIZED AS test_default;
+ test_default
+--------------
+ t
+(1 row)
+
+SELECT U&'\00E4\24D1c' IS NFC NORMALIZED AS test_nfc;
+ test_nfc
+----------
+ t
+(1 row)
+
+SELECT num, val,
+ val IS NFC NORMALIZED AS NFC,
+ val IS NFD NORMALIZED AS NFD,
+ val IS NFKC NORMALIZED AS NFKC,
+ val IS NFKD NORMALIZED AS NFKD
+FROM
+ (VALUES (1, U&'\00E4bc'),
+ (2, U&'\0061\0308bc'),
+ (3, U&'\00E4\24D1c'),
+ (4, U&'\0061\0308\24D1c'),
+ (5, '')) vals (num, val)
+ORDER BY num;
+ num | val | nfc | nfd | nfkc | nfkd
+-----+-----+-----+-----+------+------
+ 1 | äbc | t | f | t | f
+ 2 | äbc | f | t | f | t
+ 3 | äⓑc | t | f | f | f
+ 4 | äⓑc | f | t | f | f
+ 5 | | t | t | t | t
+(5 rows)
+
+SELECT is_normalized('abc', 'def'); -- run-time error
+ERROR: invalid normalization form: def
diff --git a/src/test/regress/expected/unicode_1.out b/src/test/regress/expected/unicode_1.out
new file mode 100644
index 0000000..8505c4f
--- /dev/null
+++ b/src/test/regress/expected/unicode_1.out
@@ -0,0 +1,3 @@
+SELECT getdatabaseencoding() <> 'UTF8' AS skip_test \gset
+\if :skip_test
+\quit
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
new file mode 100644
index 0000000..dece731
--- /dev/null
+++ b/src/test/regress/expected/union.out
@@ -0,0 +1,1434 @@
+--
+-- UNION (also INTERSECT, EXCEPT)
+--
+-- Simple UNION constructs
+SELECT 1 AS two UNION SELECT 2 ORDER BY 1;
+ two
+-----
+ 1
+ 2
+(2 rows)
+
+SELECT 1 AS one UNION SELECT 1 ORDER BY 1;
+ one
+-----
+ 1
+(1 row)
+
+SELECT 1 AS two UNION ALL SELECT 2;
+ two
+-----
+ 1
+ 2
+(2 rows)
+
+SELECT 1 AS two UNION ALL SELECT 1;
+ two
+-----
+ 1
+ 1
+(2 rows)
+
+SELECT 1 AS three UNION SELECT 2 UNION SELECT 3 ORDER BY 1;
+ three
+-------
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT 1 AS two UNION SELECT 2 UNION SELECT 2 ORDER BY 1;
+ two
+-----
+ 1
+ 2
+(2 rows)
+
+SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
+ three
+-------
+ 1
+ 2
+ 2
+(3 rows)
+
+SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
+ two
+-----
+ 1.1
+ 2.2
+(2 rows)
+
+-- Mixed types
+SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
+ two
+-----
+ 1.1
+ 2
+(2 rows)
+
+SELECT 1 AS two UNION SELECT 2.2 ORDER BY 1;
+ two
+-----
+ 1
+ 2.2
+(2 rows)
+
+SELECT 1 AS one UNION SELECT 1.0::float8 ORDER BY 1;
+ one
+-----
+ 1
+(1 row)
+
+SELECT 1.1 AS two UNION ALL SELECT 2 ORDER BY 1;
+ two
+-----
+ 1.1
+ 2
+(2 rows)
+
+SELECT 1.0::float8 AS two UNION ALL SELECT 1 ORDER BY 1;
+ two
+-----
+ 1
+ 1
+(2 rows)
+
+SELECT 1.1 AS three UNION SELECT 2 UNION SELECT 3 ORDER BY 1;
+ three
+-------
+ 1.1
+ 2
+ 3
+(3 rows)
+
+SELECT 1.1::float8 AS two UNION SELECT 2 UNION SELECT 2.0::float8 ORDER BY 1;
+ two
+-----
+ 1.1
+ 2
+(2 rows)
+
+SELECT 1.1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
+ three
+-------
+ 1.1
+ 2
+ 2
+(3 rows)
+
+SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
+ two
+-----
+ 1.1
+ 2
+(2 rows)
+
+--
+-- Try testing from tables...
+--
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION
+SELECT f1 FROM FLOAT8_TBL
+ORDER BY 1;
+ five
+-----------------------
+ -1.2345678901234e+200
+ -1004.3
+ -34.84
+ -1.2345678901234e-200
+ 0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL
+SELECT f1 FROM FLOAT8_TBL;
+ ten
+-----------------------
+ 0
+ -34.84
+ -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+ 0
+ -34.84
+ -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION
+SELECT f1 FROM INT4_TBL
+ORDER BY 1;
+ nine
+-----------------------
+ -1.2345678901234e+200
+ -2147483647
+ -123456
+ -1004.3
+ -34.84
+ -1.2345678901234e-200
+ 0
+ 123456
+ 2147483647
+(9 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL
+SELECT f1 FROM INT4_TBL;
+ ten
+-----------------------
+ 0
+ -34.84
+ -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+ 0
+ 123456
+ -123456
+ 2147483647
+ -2147483647
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+ WHERE f1 BETWEEN -1e6 AND 1e6
+UNION
+SELECT f1 FROM INT4_TBL
+ WHERE f1 BETWEEN 0 AND 1000000
+ORDER BY 1;
+ five
+-----------------------
+ -1004.3
+ -34.84
+ -1.2345678901234e-200
+ 0
+ 123456
+(5 rows)
+
+SELECT CAST(f1 AS char(4)) AS three FROM VARCHAR_TBL
+UNION
+SELECT f1 FROM CHAR_TBL
+ORDER BY 1;
+ three
+-------
+ a
+ ab
+ abcd
+(3 rows)
+
+SELECT f1 AS three FROM VARCHAR_TBL
+UNION
+SELECT CAST(f1 AS varchar) FROM CHAR_TBL
+ORDER BY 1;
+ three
+-------
+ a
+ ab
+ abcd
+(3 rows)
+
+SELECT f1 AS eight FROM VARCHAR_TBL
+UNION ALL
+SELECT f1 FROM CHAR_TBL;
+ eight
+-------
+ a
+ ab
+ abcd
+ abcd
+ a
+ ab
+ abcd
+ abcd
+(8 rows)
+
+SELECT f1 AS five FROM TEXT_TBL
+UNION
+SELECT f1 FROM VARCHAR_TBL
+UNION
+SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
+ORDER BY 1;
+ five
+-------------------
+ a
+ ab
+ abcd
+ doh!
+ hi de ho neighbor
+(5 rows)
+
+--
+-- INTERSECT and EXCEPT
+--
+SELECT q2 FROM int8_tbl INTERSECT SELECT q1 FROM int8_tbl ORDER BY 1;
+ q2
+------------------
+ 123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL SELECT q1 FROM int8_tbl ORDER BY 1;
+ q2
+------------------
+ 123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT SELECT q1 FROM int8_tbl ORDER BY 1;
+ q2
+-------------------
+ -4567890123456789
+ 456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl ORDER BY 1;
+ q2
+-------------------
+ -4567890123456789
+ 456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q1 FROM int8_tbl ORDER BY 1;
+ q2
+-------------------
+ -4567890123456789
+ 456
+ 4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY 1;
+ q1
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q2 FROM int8_tbl ORDER BY 1;
+ q1
+------------------
+ 123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
+ q1
+------------------
+ 123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
+ERROR: FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+-- nested cases
+(SELECT 1,2,3 UNION SELECT 4,5,6) INTERSECT SELECT 4,5,6;
+ ?column? | ?column? | ?column?
+----------+----------+----------
+ 4 | 5 | 6
+(1 row)
+
+(SELECT 1,2,3 UNION SELECT 4,5,6 ORDER BY 1,2) INTERSECT SELECT 4,5,6;
+ ?column? | ?column? | ?column?
+----------+----------+----------
+ 4 | 5 | 6
+(1 row)
+
+(SELECT 1,2,3 UNION SELECT 4,5,6) EXCEPT SELECT 4,5,6;
+ ?column? | ?column? | ?column?
+----------+----------+----------
+ 1 | 2 | 3
+(1 row)
+
+(SELECT 1,2,3 UNION SELECT 4,5,6 ORDER BY 1,2) EXCEPT SELECT 4,5,6;
+ ?column? | ?column? | ?column?
+----------+----------+----------
+ 1 | 2 | 3
+(1 row)
+
+-- exercise both hashed and sorted implementations of UNION/INTERSECT/EXCEPT
+set enable_hashagg to on;
+explain (costs off)
+select count(*) from
+ ( select unique1 from tenk1 union select fivethous from tenk1 ) ss;
+ QUERY PLAN
+----------------------------------------------------------------
+ Aggregate
+ -> HashAggregate
+ Group Key: tenk1.unique1
+ -> Append
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ -> Seq Scan on tenk1 tenk1_1
+(6 rows)
+
+select count(*) from
+ ( select unique1 from tenk1 union select fivethous from tenk1 ) ss;
+ count
+-------
+ 10000
+(1 row)
+
+explain (costs off)
+select count(*) from
+ ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss;
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Aggregate
+ -> Subquery Scan on ss
+ -> HashSetOp Intersect
+ -> Append
+ -> Subquery Scan on "*SELECT* 2"
+ -> Seq Scan on tenk1
+ -> Subquery Scan on "*SELECT* 1"
+ -> Index Only Scan using tenk1_unique1 on tenk1 tenk1_1
+(8 rows)
+
+select count(*) from
+ ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss;
+ count
+-------
+ 5000
+(1 row)
+
+explain (costs off)
+select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10;
+ QUERY PLAN
+------------------------------------------------------------------------
+ HashSetOp Except
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ -> Subquery Scan on "*SELECT* 2"
+ -> Index Only Scan using tenk1_unique2 on tenk1 tenk1_1
+ Filter: (unique2 <> 10)
+(7 rows)
+
+select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10;
+ unique1
+---------
+ 10
+(1 row)
+
+set enable_hashagg to off;
+explain (costs off)
+select count(*) from
+ ( select unique1 from tenk1 union select fivethous from tenk1 ) ss;
+ QUERY PLAN
+----------------------------------------------------------------------
+ Aggregate
+ -> Unique
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Append
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ -> Seq Scan on tenk1 tenk1_1
+(7 rows)
+
+select count(*) from
+ ( select unique1 from tenk1 union select fivethous from tenk1 ) ss;
+ count
+-------
+ 10000
+(1 row)
+
+explain (costs off)
+select count(*) from
+ ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss;
+ QUERY PLAN
+------------------------------------------------------------------------------------------
+ Aggregate
+ -> Subquery Scan on ss
+ -> SetOp Intersect
+ -> Sort
+ Sort Key: "*SELECT* 2".fivethous
+ -> Append
+ -> Subquery Scan on "*SELECT* 2"
+ -> Seq Scan on tenk1
+ -> Subquery Scan on "*SELECT* 1"
+ -> Index Only Scan using tenk1_unique1 on tenk1 tenk1_1
+(10 rows)
+
+select count(*) from
+ ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss;
+ count
+-------
+ 5000
+(1 row)
+
+explain (costs off)
+select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10;
+ QUERY PLAN
+------------------------------------------------------------------------------
+ SetOp Except
+ -> Sort
+ Sort Key: "*SELECT* 1".unique1
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ -> Subquery Scan on "*SELECT* 2"
+ -> Index Only Scan using tenk1_unique2 on tenk1 tenk1_1
+ Filter: (unique2 <> 10)
+(9 rows)
+
+select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10;
+ unique1
+---------
+ 10
+(1 row)
+
+reset enable_hashagg;
+-- non-hashable type
+set enable_hashagg to on;
+explain (costs off)
+select x from (values (100::money), (200::money)) _(x) union select x from (values (100::money), (300::money)) _(x);
+ QUERY PLAN
+-----------------------------------------------
+ Unique
+ -> Sort
+ Sort Key: "*VALUES*".column1
+ -> Append
+ -> Values Scan on "*VALUES*"
+ -> Values Scan on "*VALUES*_1"
+(6 rows)
+
+set enable_hashagg to off;
+explain (costs off)
+select x from (values (100::money), (200::money)) _(x) union select x from (values (100::money), (300::money)) _(x);
+ QUERY PLAN
+-----------------------------------------------
+ Unique
+ -> Sort
+ Sort Key: "*VALUES*".column1
+ -> Append
+ -> Values Scan on "*VALUES*"
+ -> Values Scan on "*VALUES*_1"
+(6 rows)
+
+reset enable_hashagg;
+-- arrays
+set enable_hashagg to on;
+explain (costs off)
+select x from (values (array[1, 2]), (array[1, 3])) _(x) union select x from (values (array[1, 2]), (array[1, 4])) _(x);
+ QUERY PLAN
+-----------------------------------------
+ HashAggregate
+ Group Key: "*VALUES*".column1
+ -> Append
+ -> Values Scan on "*VALUES*"
+ -> Values Scan on "*VALUES*_1"
+(5 rows)
+
+select x from (values (array[1, 2]), (array[1, 3])) _(x) union select x from (values (array[1, 2]), (array[1, 4])) _(x);
+ x
+-------
+ {1,4}
+ {1,2}
+ {1,3}
+(3 rows)
+
+explain (costs off)
+select x from (values (array[1, 2]), (array[1, 3])) _(x) intersect select x from (values (array[1, 2]), (array[1, 4])) _(x);
+ QUERY PLAN
+-----------------------------------------------
+ HashSetOp Intersect
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Values Scan on "*VALUES*"
+ -> Subquery Scan on "*SELECT* 2"
+ -> Values Scan on "*VALUES*_1"
+(6 rows)
+
+select x from (values (array[1, 2]), (array[1, 3])) _(x) intersect select x from (values (array[1, 2]), (array[1, 4])) _(x);
+ x
+-------
+ {1,2}
+(1 row)
+
+explain (costs off)
+select x from (values (array[1, 2]), (array[1, 3])) _(x) except select x from (values (array[1, 2]), (array[1, 4])) _(x);
+ QUERY PLAN
+-----------------------------------------------
+ HashSetOp Except
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Values Scan on "*VALUES*"
+ -> Subquery Scan on "*SELECT* 2"
+ -> Values Scan on "*VALUES*_1"
+(6 rows)
+
+select x from (values (array[1, 2]), (array[1, 3])) _(x) except select x from (values (array[1, 2]), (array[1, 4])) _(x);
+ x
+-------
+ {1,3}
+(1 row)
+
+-- non-hashable type
+explain (costs off)
+select x from (values (array[100::money]), (array[200::money])) _(x) union select x from (values (array[100::money]), (array[300::money])) _(x);
+ QUERY PLAN
+-----------------------------------------------
+ Unique
+ -> Sort
+ Sort Key: "*VALUES*".column1
+ -> Append
+ -> Values Scan on "*VALUES*"
+ -> Values Scan on "*VALUES*_1"
+(6 rows)
+
+select x from (values (array[100::money]), (array[200::money])) _(x) union select x from (values (array[100::money]), (array[300::money])) _(x);
+ x
+-----------
+ {$100.00}
+ {$200.00}
+ {$300.00}
+(3 rows)
+
+set enable_hashagg to off;
+explain (costs off)
+select x from (values (array[1, 2]), (array[1, 3])) _(x) union select x from (values (array[1, 2]), (array[1, 4])) _(x);
+ QUERY PLAN
+-----------------------------------------------
+ Unique
+ -> Sort
+ Sort Key: "*VALUES*".column1
+ -> Append
+ -> Values Scan on "*VALUES*"
+ -> Values Scan on "*VALUES*_1"
+(6 rows)
+
+select x from (values (array[1, 2]), (array[1, 3])) _(x) union select x from (values (array[1, 2]), (array[1, 4])) _(x);
+ x
+-------
+ {1,2}
+ {1,3}
+ {1,4}
+(3 rows)
+
+explain (costs off)
+select x from (values (array[1, 2]), (array[1, 3])) _(x) intersect select x from (values (array[1, 2]), (array[1, 4])) _(x);
+ QUERY PLAN
+-----------------------------------------------------
+ SetOp Intersect
+ -> Sort
+ Sort Key: "*SELECT* 1".x
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Values Scan on "*VALUES*"
+ -> Subquery Scan on "*SELECT* 2"
+ -> Values Scan on "*VALUES*_1"
+(8 rows)
+
+select x from (values (array[1, 2]), (array[1, 3])) _(x) intersect select x from (values (array[1, 2]), (array[1, 4])) _(x);
+ x
+-------
+ {1,2}
+(1 row)
+
+explain (costs off)
+select x from (values (array[1, 2]), (array[1, 3])) _(x) except select x from (values (array[1, 2]), (array[1, 4])) _(x);
+ QUERY PLAN
+-----------------------------------------------------
+ SetOp Except
+ -> Sort
+ Sort Key: "*SELECT* 1".x
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Values Scan on "*VALUES*"
+ -> Subquery Scan on "*SELECT* 2"
+ -> Values Scan on "*VALUES*_1"
+(8 rows)
+
+select x from (values (array[1, 2]), (array[1, 3])) _(x) except select x from (values (array[1, 2]), (array[1, 4])) _(x);
+ x
+-------
+ {1,3}
+(1 row)
+
+reset enable_hashagg;
+-- records
+set enable_hashagg to on;
+explain (costs off)
+select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x);
+ QUERY PLAN
+-----------------------------------------------
+ Unique
+ -> Sort
+ Sort Key: "*VALUES*".column1
+ -> Append
+ -> Values Scan on "*VALUES*"
+ -> Values Scan on "*VALUES*_1"
+(6 rows)
+
+select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x);
+ x
+-------
+ (1,2)
+ (1,3)
+ (1,4)
+(3 rows)
+
+explain (costs off)
+select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x);
+ QUERY PLAN
+-----------------------------------------------------
+ SetOp Intersect
+ -> Sort
+ Sort Key: "*SELECT* 1".x
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Values Scan on "*VALUES*"
+ -> Subquery Scan on "*SELECT* 2"
+ -> Values Scan on "*VALUES*_1"
+(8 rows)
+
+select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x);
+ x
+-------
+ (1,2)
+(1 row)
+
+explain (costs off)
+select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x);
+ QUERY PLAN
+-----------------------------------------------------
+ SetOp Except
+ -> Sort
+ Sort Key: "*SELECT* 1".x
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Values Scan on "*VALUES*"
+ -> Subquery Scan on "*SELECT* 2"
+ -> Values Scan on "*VALUES*_1"
+(8 rows)
+
+select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x);
+ x
+-------
+ (1,3)
+(1 row)
+
+-- non-hashable type
+-- With an anonymous row type, the typcache does not report that the
+-- type is hashable. (Otherwise, this would fail at execution time.)
+explain (costs off)
+select x from (values (row(100::money)), (row(200::money))) _(x) union select x from (values (row(100::money)), (row(300::money))) _(x);
+ QUERY PLAN
+-----------------------------------------------
+ Unique
+ -> Sort
+ Sort Key: "*VALUES*".column1
+ -> Append
+ -> Values Scan on "*VALUES*"
+ -> Values Scan on "*VALUES*_1"
+(6 rows)
+
+select x from (values (row(100::money)), (row(200::money))) _(x) union select x from (values (row(100::money)), (row(300::money))) _(x);
+ x
+-----------
+ ($100.00)
+ ($200.00)
+ ($300.00)
+(3 rows)
+
+-- With a defined row type, the typcache can inspect the type's fields
+-- for hashability.
+create type ct1 as (f1 money);
+explain (costs off)
+select x from (values (row(100::money)::ct1), (row(200::money)::ct1)) _(x) union select x from (values (row(100::money)::ct1), (row(300::money)::ct1)) _(x);
+ QUERY PLAN
+-----------------------------------------------
+ Unique
+ -> Sort
+ Sort Key: "*VALUES*".column1
+ -> Append
+ -> Values Scan on "*VALUES*"
+ -> Values Scan on "*VALUES*_1"
+(6 rows)
+
+select x from (values (row(100::money)::ct1), (row(200::money)::ct1)) _(x) union select x from (values (row(100::money)::ct1), (row(300::money)::ct1)) _(x);
+ x
+-----------
+ ($100.00)
+ ($200.00)
+ ($300.00)
+(3 rows)
+
+drop type ct1;
+set enable_hashagg to off;
+explain (costs off)
+select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x);
+ QUERY PLAN
+-----------------------------------------------
+ Unique
+ -> Sort
+ Sort Key: "*VALUES*".column1
+ -> Append
+ -> Values Scan on "*VALUES*"
+ -> Values Scan on "*VALUES*_1"
+(6 rows)
+
+select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x);
+ x
+-------
+ (1,2)
+ (1,3)
+ (1,4)
+(3 rows)
+
+explain (costs off)
+select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x);
+ QUERY PLAN
+-----------------------------------------------------
+ SetOp Intersect
+ -> Sort
+ Sort Key: "*SELECT* 1".x
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Values Scan on "*VALUES*"
+ -> Subquery Scan on "*SELECT* 2"
+ -> Values Scan on "*VALUES*_1"
+(8 rows)
+
+select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x);
+ x
+-------
+ (1,2)
+(1 row)
+
+explain (costs off)
+select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x);
+ QUERY PLAN
+-----------------------------------------------------
+ SetOp Except
+ -> Sort
+ Sort Key: "*SELECT* 1".x
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Values Scan on "*VALUES*"
+ -> Subquery Scan on "*SELECT* 2"
+ -> Values Scan on "*VALUES*_1"
+(8 rows)
+
+select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x);
+ x
+-------
+ (1,3)
+(1 row)
+
+reset enable_hashagg;
+--
+-- Mixed types
+--
+SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
+ f1
+----
+ 0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
+ f1
+-----------------------
+ -1.2345678901234e+200
+ -1004.3
+ -34.84
+ -1.2345678901234e-200
+(4 rows)
+
+--
+-- Operator precedence and (((((extra))))) parentheses
+--
+SELECT q1 FROM int8_tbl INTERSECT SELECT q2 FROM int8_tbl UNION ALL SELECT q2 FROM int8_tbl ORDER BY 1;
+ q1
+-------------------
+ -4567890123456789
+ 123
+ 123
+ 456
+ 4567890123456789
+ 4567890123456789
+ 4567890123456789
+(7 rows)
+
+SELECT q1 FROM int8_tbl INTERSECT (((SELECT q2 FROM int8_tbl UNION ALL SELECT q2 FROM int8_tbl))) ORDER BY 1;
+ q1
+------------------
+ 123
+ 4567890123456789
+(2 rows)
+
+(((SELECT q1 FROM int8_tbl INTERSECT SELECT q2 FROM int8_tbl ORDER BY 1))) UNION ALL SELECT q2 FROM int8_tbl;
+ q1
+-------------------
+ 123
+ 4567890123456789
+ 456
+ 4567890123456789
+ 123
+ 4567890123456789
+ -4567890123456789
+(7 rows)
+
+SELECT q1 FROM int8_tbl UNION ALL SELECT q2 FROM int8_tbl EXCEPT SELECT q1 FROM int8_tbl ORDER BY 1;
+ q1
+-------------------
+ -4567890123456789
+ 456
+(2 rows)
+
+SELECT q1 FROM int8_tbl UNION ALL (((SELECT q2 FROM int8_tbl EXCEPT SELECT q1 FROM int8_tbl ORDER BY 1)));
+ q1
+-------------------
+ 123
+ 123
+ 4567890123456789
+ 4567890123456789
+ 4567890123456789
+ -4567890123456789
+ 456
+(7 rows)
+
+(((SELECT q1 FROM int8_tbl UNION ALL SELECT q2 FROM int8_tbl))) EXCEPT SELECT q1 FROM int8_tbl ORDER BY 1;
+ q1
+-------------------
+ -4567890123456789
+ 456
+(2 rows)
+
+--
+-- Subqueries with ORDER BY & LIMIT clauses
+--
+-- In this syntax, ORDER BY/LIMIT apply to the result of the EXCEPT
+SELECT q1,q2 FROM int8_tbl EXCEPT SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2
+------------------+-------------------
+ 4567890123456789 | -4567890123456789
+ 123 | 456
+(2 rows)
+
+-- This should fail, because q2 isn't a name of an EXCEPT output column
+SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
+ERROR: column "q2" does not exist
+LINE 1: ... int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1...
+ ^
+HINT: There is a column named "q2" in table "*SELECT* 2", but it cannot be referenced from this part of the query.
+-- But this should work:
+SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
+ q1
+------------------
+ 123
+ 4567890123456789
+(2 rows)
+
+--
+-- New syntaxes (7.1) permit new tests
+--
+(((((select * from int8_tbl)))));
+ q1 | q2
+------------------+-------------------
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+(5 rows)
+
+--
+-- Check behavior with empty select list (allowed since 9.4)
+--
+select union select;
+--
+(1 row)
+
+select intersect select;
+--
+(1 row)
+
+select except select;
+--
+(0 rows)
+
+-- check hashed implementation
+set enable_hashagg = true;
+set enable_sort = false;
+explain (costs off)
+select from generate_series(1,5) union select from generate_series(1,3);
+ QUERY PLAN
+----------------------------------------------------------------
+ HashAggregate
+ -> Append
+ -> Function Scan on generate_series
+ -> Function Scan on generate_series generate_series_1
+(4 rows)
+
+explain (costs off)
+select from generate_series(1,5) intersect select from generate_series(1,3);
+ QUERY PLAN
+----------------------------------------------------------------------
+ HashSetOp Intersect
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Function Scan on generate_series
+ -> Subquery Scan on "*SELECT* 2"
+ -> Function Scan on generate_series generate_series_1
+(6 rows)
+
+select from generate_series(1,5) union select from generate_series(1,3);
+--
+(1 row)
+
+select from generate_series(1,5) union all select from generate_series(1,3);
+--
+(8 rows)
+
+select from generate_series(1,5) intersect select from generate_series(1,3);
+--
+(1 row)
+
+select from generate_series(1,5) intersect all select from generate_series(1,3);
+--
+(3 rows)
+
+select from generate_series(1,5) except select from generate_series(1,3);
+--
+(0 rows)
+
+select from generate_series(1,5) except all select from generate_series(1,3);
+--
+(2 rows)
+
+-- check sorted implementation
+set enable_hashagg = false;
+set enable_sort = true;
+explain (costs off)
+select from generate_series(1,5) union select from generate_series(1,3);
+ QUERY PLAN
+----------------------------------------------------------------
+ Unique
+ -> Append
+ -> Function Scan on generate_series
+ -> Function Scan on generate_series generate_series_1
+(4 rows)
+
+explain (costs off)
+select from generate_series(1,5) intersect select from generate_series(1,3);
+ QUERY PLAN
+----------------------------------------------------------------------
+ SetOp Intersect
+ -> Append
+ -> Subquery Scan on "*SELECT* 1"
+ -> Function Scan on generate_series
+ -> Subquery Scan on "*SELECT* 2"
+ -> Function Scan on generate_series generate_series_1
+(6 rows)
+
+select from generate_series(1,5) union select from generate_series(1,3);
+--
+(1 row)
+
+select from generate_series(1,5) union all select from generate_series(1,3);
+--
+(8 rows)
+
+select from generate_series(1,5) intersect select from generate_series(1,3);
+--
+(1 row)
+
+select from generate_series(1,5) intersect all select from generate_series(1,3);
+--
+(3 rows)
+
+select from generate_series(1,5) except select from generate_series(1,3);
+--
+(0 rows)
+
+select from generate_series(1,5) except all select from generate_series(1,3);
+--
+(2 rows)
+
+reset enable_hashagg;
+reset enable_sort;
+--
+-- Check handling of a case with unknown constants. We don't guarantee
+-- an undecorated constant will work in all cases, but historically this
+-- usage has worked, so test we don't break it.
+--
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+ f1
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
+-- This should fail, but it should produce an error cursor
+SELECT '3.4'::numeric UNION SELECT 'foo';
+ERROR: invalid input syntax for type numeric: "foo"
+LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
+ ^
+--
+-- Test that expression-index constraints can be pushed down through
+-- UNION or UNION ALL
+--
+CREATE TEMP TABLE t1 (a text, b text);
+CREATE INDEX t1_ab_idx on t1 ((a || b));
+CREATE TEMP TABLE t2 (ab text primary key);
+INSERT INTO t1 VALUES ('a', 'b'), ('x', 'y');
+INSERT INTO t2 VALUES ('ab'), ('xy');
+set enable_seqscan = off;
+set enable_indexscan = on;
+set enable_bitmapscan = off;
+explain (costs off)
+ SELECT * FROM
+ (SELECT a || b AS ab FROM t1
+ UNION ALL
+ SELECT * FROM t2) t
+ WHERE ab = 'ab';
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Index Scan using t1_ab_idx on t1
+ Index Cond: ((a || b) = 'ab'::text)
+ -> Index Only Scan using t2_pkey on t2
+ Index Cond: (ab = 'ab'::text)
+(5 rows)
+
+explain (costs off)
+ SELECT * FROM
+ (SELECT a || b AS ab FROM t1
+ UNION
+ SELECT * FROM t2) t
+ WHERE ab = 'ab';
+ QUERY PLAN
+---------------------------------------------------
+ HashAggregate
+ Group Key: ((t1.a || t1.b))
+ -> Append
+ -> Index Scan using t1_ab_idx on t1
+ Index Cond: ((a || b) = 'ab'::text)
+ -> Index Only Scan using t2_pkey on t2
+ Index Cond: (ab = 'ab'::text)
+(7 rows)
+
+--
+-- Test that ORDER BY for UNION ALL can be pushed down to inheritance
+-- children.
+--
+CREATE TEMP TABLE t1c (b text, a text);
+ALTER TABLE t1c INHERIT t1;
+CREATE TEMP TABLE t2c (primary key (ab)) INHERITS (t2);
+INSERT INTO t1c VALUES ('v', 'w'), ('c', 'd'), ('m', 'n'), ('e', 'f');
+INSERT INTO t2c VALUES ('vw'), ('cd'), ('mn'), ('ef');
+CREATE INDEX t1c_ab_idx on t1c ((a || b));
+set enable_seqscan = on;
+set enable_indexonlyscan = off;
+explain (costs off)
+ SELECT * FROM
+ (SELECT a || b AS ab FROM t1
+ UNION ALL
+ SELECT ab FROM t2) t
+ ORDER BY 1 LIMIT 8;
+ QUERY PLAN
+-----------------------------------------------------
+ Limit
+ -> Merge Append
+ Sort Key: ((t1.a || t1.b))
+ -> Index Scan using t1_ab_idx on t1
+ -> Index Scan using t1c_ab_idx on t1c t1_1
+ -> Index Scan using t2_pkey on t2
+ -> Index Scan using t2c_pkey on t2c t2_1
+(7 rows)
+
+ SELECT * FROM
+ (SELECT a || b AS ab FROM t1
+ UNION ALL
+ SELECT ab FROM t2) t
+ ORDER BY 1 LIMIT 8;
+ ab
+----
+ ab
+ ab
+ cd
+ dc
+ ef
+ fe
+ mn
+ nm
+(8 rows)
+
+reset enable_seqscan;
+reset enable_indexscan;
+reset enable_bitmapscan;
+-- This simpler variant of the above test has been observed to fail differently
+create table events (event_id int primary key);
+create table other_events (event_id int primary key);
+create table events_child () inherits (events);
+explain (costs off)
+select event_id
+ from (select event_id from events
+ union all
+ select event_id from other_events) ss
+ order by event_id;
+ QUERY PLAN
+----------------------------------------------------------
+ Merge Append
+ Sort Key: events.event_id
+ -> Index Scan using events_pkey on events
+ -> Sort
+ Sort Key: events_1.event_id
+ -> Seq Scan on events_child events_1
+ -> Index Scan using other_events_pkey on other_events
+(7 rows)
+
+drop table events_child, events, other_events;
+reset enable_indexonlyscan;
+-- Test constraint exclusion of UNION ALL subqueries
+explain (costs off)
+ SELECT * FROM
+ (SELECT 1 AS t, * FROM tenk1 a
+ UNION ALL
+ SELECT 2 AS t, * FROM tenk1 b) c
+ WHERE t = 2;
+ QUERY PLAN
+---------------------
+ Seq Scan on tenk1 b
+(1 row)
+
+-- Test that we push quals into UNION sub-selects only when it's safe
+explain (costs off)
+SELECT * FROM
+ (SELECT 1 AS t, 2 AS x
+ UNION
+ SELECT 2 AS t, 4 AS x) ss
+WHERE x < 4
+ORDER BY x;
+ QUERY PLAN
+--------------------------------------------------
+ Sort
+ Sort Key: (2)
+ -> Unique
+ -> Sort
+ Sort Key: (1), (2)
+ -> Append
+ -> Result
+ -> Result
+ One-Time Filter: false
+(9 rows)
+
+SELECT * FROM
+ (SELECT 1 AS t, 2 AS x
+ UNION
+ SELECT 2 AS t, 4 AS x) ss
+WHERE x < 4
+ORDER BY x;
+ t | x
+---+---
+ 1 | 2
+(1 row)
+
+explain (costs off)
+SELECT * FROM
+ (SELECT 1 AS t, generate_series(1,10) AS x
+ UNION
+ SELECT 2 AS t, 4 AS x) ss
+WHERE x < 4
+ORDER BY x;
+ QUERY PLAN
+--------------------------------------------------------
+ Sort
+ Sort Key: ss.x
+ -> Subquery Scan on ss
+ Filter: (ss.x < 4)
+ -> HashAggregate
+ Group Key: (1), (generate_series(1, 10))
+ -> Append
+ -> ProjectSet
+ -> Result
+ -> Result
+(10 rows)
+
+SELECT * FROM
+ (SELECT 1 AS t, generate_series(1,10) AS x
+ UNION
+ SELECT 2 AS t, 4 AS x) ss
+WHERE x < 4
+ORDER BY x;
+ t | x
+---+---
+ 1 | 1
+ 1 | 2
+ 1 | 3
+(3 rows)
+
+explain (costs off)
+SELECT * FROM
+ (SELECT 1 AS t, (random()*3)::int AS x
+ UNION
+ SELECT 2 AS t, 4 AS x) ss
+WHERE x > 3
+ORDER BY x;
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Sort
+ Sort Key: ss.x
+ -> Subquery Scan on ss
+ Filter: (ss.x > 3)
+ -> Unique
+ -> Sort
+ Sort Key: (1), (((random() * '3'::double precision))::integer)
+ -> Append
+ -> Result
+ -> Result
+(10 rows)
+
+SELECT * FROM
+ (SELECT 1 AS t, (random()*3)::int AS x
+ UNION
+ SELECT 2 AS t, 4 AS x) ss
+WHERE x > 3
+ORDER BY x;
+ t | x
+---+---
+ 2 | 4
+(1 row)
+
+-- Test cases where the native ordering of a sub-select has more pathkeys
+-- than the outer query cares about
+explain (costs off)
+select distinct q1 from
+ (select distinct * from int8_tbl i81
+ union all
+ select distinct * from int8_tbl i82) ss
+where q2 = q2;
+ QUERY PLAN
+----------------------------------------------------------
+ Unique
+ -> Merge Append
+ Sort Key: "*SELECT* 1".q1
+ -> Subquery Scan on "*SELECT* 1"
+ -> Unique
+ -> Sort
+ Sort Key: i81.q1, i81.q2
+ -> Seq Scan on int8_tbl i81
+ Filter: (q2 IS NOT NULL)
+ -> Subquery Scan on "*SELECT* 2"
+ -> Unique
+ -> Sort
+ Sort Key: i82.q1, i82.q2
+ -> Seq Scan on int8_tbl i82
+ Filter: (q2 IS NOT NULL)
+(15 rows)
+
+select distinct q1 from
+ (select distinct * from int8_tbl i81
+ union all
+ select distinct * from int8_tbl i82) ss
+where q2 = q2;
+ q1
+------------------
+ 123
+ 4567890123456789
+(2 rows)
+
+explain (costs off)
+select distinct q1 from
+ (select distinct * from int8_tbl i81
+ union all
+ select distinct * from int8_tbl i82) ss
+where -q1 = q2;
+ QUERY PLAN
+--------------------------------------------------------
+ Unique
+ -> Merge Append
+ Sort Key: "*SELECT* 1".q1
+ -> Subquery Scan on "*SELECT* 1"
+ -> Unique
+ -> Sort
+ Sort Key: i81.q1, i81.q2
+ -> Seq Scan on int8_tbl i81
+ Filter: ((- q1) = q2)
+ -> Subquery Scan on "*SELECT* 2"
+ -> Unique
+ -> Sort
+ Sort Key: i82.q1, i82.q2
+ -> Seq Scan on int8_tbl i82
+ Filter: ((- q1) = q2)
+(15 rows)
+
+select distinct q1 from
+ (select distinct * from int8_tbl i81
+ union all
+ select distinct * from int8_tbl i82) ss
+where -q1 = q2;
+ q1
+------------------
+ 4567890123456789
+(1 row)
+
+-- Test proper handling of parameterized appendrel paths when the
+-- potential join qual is expensive
+create function expensivefunc(int) returns int
+language plpgsql immutable strict cost 10000
+as $$begin return $1; end$$;
+create temp table t3 as select generate_series(-1000,1000) as x;
+create index t3i on t3 (expensivefunc(x));
+analyze t3;
+explain (costs off)
+select * from
+ (select * from t3 a union all select * from t3 b) ss
+ join int4_tbl on f1 = expensivefunc(x);
+ QUERY PLAN
+------------------------------------------------------------
+ Nested Loop
+ -> Seq Scan on int4_tbl
+ -> Append
+ -> Index Scan using t3i on t3 a
+ Index Cond: (expensivefunc(x) = int4_tbl.f1)
+ -> Index Scan using t3i on t3 b
+ Index Cond: (expensivefunc(x) = int4_tbl.f1)
+(7 rows)
+
+select * from
+ (select * from t3 a union all select * from t3 b) ss
+ join int4_tbl on f1 = expensivefunc(x);
+ x | f1
+---+----
+ 0 | 0
+ 0 | 0
+(2 rows)
+
+drop table t3;
+drop function expensivefunc(int);
+-- Test handling of appendrel quals that const-simplify into an AND
+explain (costs off)
+select * from
+ (select *, 0 as x from int8_tbl a
+ union all
+ select *, 1 as x from int8_tbl b) ss
+where (x = 0) or (q1 >= q2 and q1 <= q2);
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Seq Scan on int8_tbl a
+ -> Seq Scan on int8_tbl b
+ Filter: ((q1 >= q2) AND (q1 <= q2))
+(4 rows)
+
+select * from
+ (select *, 0 as x from int8_tbl a
+ union all
+ select *, 1 as x from int8_tbl b) ss
+where (x = 0) or (q1 >= q2 and q1 <= q2);
+ q1 | q2 | x
+------------------+-------------------+---
+ 123 | 456 | 0
+ 123 | 4567890123456789 | 0
+ 4567890123456789 | 123 | 0
+ 4567890123456789 | 4567890123456789 | 0
+ 4567890123456789 | -4567890123456789 | 0
+ 4567890123456789 | 4567890123456789 | 1
+(6 rows)
+
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
new file mode 100644
index 0000000..cdff914
--- /dev/null
+++ b/src/test/regress/expected/updatable_views.out
@@ -0,0 +1,3056 @@
+--
+-- UPDATABLE VIEWS
+--
+-- avoid bit-exact output here because operations may not be bit-exact.
+SET extra_float_digits = 0;
+-- check that non-updatable views and columns are rejected with useful error
+-- messages
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
+CREATE VIEW ro_view1 AS SELECT DISTINCT a, b FROM base_tbl; -- DISTINCT not supported
+CREATE VIEW ro_view2 AS SELECT a, b FROM base_tbl GROUP BY a, b; -- GROUP BY not supported
+CREATE VIEW ro_view3 AS SELECT 1 FROM base_tbl HAVING max(a) > 0; -- HAVING not supported
+CREATE VIEW ro_view4 AS SELECT count(*) FROM base_tbl; -- Aggregate functions not supported
+CREATE VIEW ro_view5 AS SELECT a, rank() OVER() FROM base_tbl; -- Window functions not supported
+CREATE VIEW ro_view6 AS SELECT a, b FROM base_tbl UNION SELECT -a, b FROM base_tbl; -- Set ops not supported
+CREATE VIEW ro_view7 AS WITH t AS (SELECT a, b FROM base_tbl) SELECT * FROM t; -- WITH not supported
+CREATE VIEW ro_view8 AS SELECT a, b FROM base_tbl ORDER BY a OFFSET 1; -- OFFSET not supported
+CREATE VIEW ro_view9 AS SELECT a, b FROM base_tbl ORDER BY a LIMIT 1; -- LIMIT not supported
+CREATE VIEW ro_view10 AS SELECT 1 AS a; -- No base relations
+CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
+CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
+CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
+CREATE VIEW rw_view14 AS SELECT ctid, a, b FROM base_tbl; -- System columns may be part of an updatable view
+CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
+CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
+CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
+CREATE VIEW ro_view18 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
+CREATE SEQUENCE uv_seq;
+CREATE VIEW ro_view19 AS SELECT * FROM uv_seq; -- View based on a sequence
+CREATE VIEW ro_view20 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE E'r_\\_view%'
+ ORDER BY table_name;
+ table_name | is_insertable_into
+------------+--------------------
+ ro_view1 | NO
+ ro_view10 | NO
+ ro_view11 | NO
+ ro_view12 | NO
+ ro_view13 | NO
+ ro_view17 | NO
+ ro_view18 | NO
+ ro_view19 | NO
+ ro_view2 | NO
+ ro_view20 | NO
+ ro_view3 | NO
+ ro_view4 | NO
+ ro_view5 | NO
+ ro_view6 | NO
+ ro_view7 | NO
+ ro_view8 | NO
+ ro_view9 | NO
+ rw_view14 | YES
+ rw_view15 | YES
+ rw_view16 | YES
+(20 rows)
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE E'r_\\_view%'
+ ORDER BY table_name;
+ table_name | is_updatable | is_insertable_into
+------------+--------------+--------------------
+ ro_view1 | NO | NO
+ ro_view10 | NO | NO
+ ro_view11 | NO | NO
+ ro_view12 | NO | NO
+ ro_view13 | NO | NO
+ ro_view17 | NO | NO
+ ro_view18 | NO | NO
+ ro_view19 | NO | NO
+ ro_view2 | NO | NO
+ ro_view20 | NO | NO
+ ro_view3 | NO | NO
+ ro_view4 | NO | NO
+ ro_view5 | NO | NO
+ ro_view6 | NO | NO
+ ro_view7 | NO | NO
+ ro_view8 | NO | NO
+ ro_view9 | NO | NO
+ rw_view14 | YES | YES
+ rw_view15 | YES | YES
+ rw_view16 | YES | YES
+(20 rows)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE E'r_\\_view%'
+ ORDER BY table_name, ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ ro_view1 | a | NO
+ ro_view1 | b | NO
+ ro_view10 | a | NO
+ ro_view11 | a | NO
+ ro_view11 | b | NO
+ ro_view12 | a | NO
+ ro_view13 | a | NO
+ ro_view13 | b | NO
+ ro_view17 | a | NO
+ ro_view17 | b | NO
+ ro_view18 | a | NO
+ ro_view19 | last_value | NO
+ ro_view19 | log_cnt | NO
+ ro_view19 | is_called | NO
+ ro_view2 | a | NO
+ ro_view2 | b | NO
+ ro_view20 | a | NO
+ ro_view20 | b | NO
+ ro_view20 | g | NO
+ ro_view3 | ?column? | NO
+ ro_view4 | count | NO
+ ro_view5 | a | NO
+ ro_view5 | rank | NO
+ ro_view6 | a | NO
+ ro_view6 | b | NO
+ ro_view7 | a | NO
+ ro_view7 | b | NO
+ ro_view8 | a | NO
+ ro_view8 | b | NO
+ ro_view9 | a | NO
+ ro_view9 | b | NO
+ rw_view14 | ctid | NO
+ rw_view14 | a | YES
+ rw_view14 | b | YES
+ rw_view15 | a | YES
+ rw_view15 | upper | NO
+ rw_view16 | a | YES
+ rw_view16 | b | YES
+ rw_view16 | aa | YES
+(39 rows)
+
+-- Read-only views
+DELETE FROM ro_view1;
+ERROR: cannot delete from view "ro_view1"
+DETAIL: Views containing DISTINCT are not automatically updatable.
+HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
+DELETE FROM ro_view2;
+ERROR: cannot delete from view "ro_view2"
+DETAIL: Views containing GROUP BY are not automatically updatable.
+HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
+DELETE FROM ro_view3;
+ERROR: cannot delete from view "ro_view3"
+DETAIL: Views containing HAVING are not automatically updatable.
+HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
+DELETE FROM ro_view4;
+ERROR: cannot delete from view "ro_view4"
+DETAIL: Views that return aggregate functions are not automatically updatable.
+HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
+DELETE FROM ro_view5;
+ERROR: cannot delete from view "ro_view5"
+DETAIL: Views that return window functions are not automatically updatable.
+HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
+DELETE FROM ro_view6;
+ERROR: cannot delete from view "ro_view6"
+DETAIL: Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.
+HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
+UPDATE ro_view7 SET a=a+1;
+ERROR: cannot update view "ro_view7"
+DETAIL: Views containing WITH are not automatically updatable.
+HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
+UPDATE ro_view8 SET a=a+1;
+ERROR: cannot update view "ro_view8"
+DETAIL: Views containing LIMIT or OFFSET are not automatically updatable.
+HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
+UPDATE ro_view9 SET a=a+1;
+ERROR: cannot update view "ro_view9"
+DETAIL: Views containing LIMIT or OFFSET are not automatically updatable.
+HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
+UPDATE ro_view10 SET a=a+1;
+ERROR: cannot update view "ro_view10"
+DETAIL: Views that do not select from a single table or view are not automatically updatable.
+HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
+UPDATE ro_view11 SET a=a+1;
+ERROR: cannot update view "ro_view11"
+DETAIL: Views that do not select from a single table or view are not automatically updatable.
+HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
+UPDATE ro_view12 SET a=a+1;
+ERROR: cannot update view "ro_view12"
+DETAIL: Views that do not select from a single table or view are not automatically updatable.
+HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
+INSERT INTO ro_view13 VALUES (3, 'Row 3');
+ERROR: cannot insert into view "ro_view13"
+DETAIL: Views that do not select from a single table or view are not automatically updatable.
+HINT: To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
+-- Partially updatable view
+INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
+ERROR: cannot insert into column "ctid" of view "rw_view14"
+DETAIL: View columns that refer to system columns are not updatable.
+INSERT INTO rw_view14 (a, b) VALUES (3, 'Row 3'); -- should be OK
+UPDATE rw_view14 SET ctid=null WHERE a=3; -- should fail
+ERROR: cannot update column "ctid" of view "rw_view14"
+DETAIL: View columns that refer to system columns are not updatable.
+UPDATE rw_view14 SET b='ROW 3' WHERE a=3; -- should be OK
+SELECT * FROM base_tbl;
+ a | b
+----+--------
+ -2 | Row -2
+ -1 | Row -1
+ 0 | Row 0
+ 1 | Row 1
+ 2 | Row 2
+ 3 | ROW 3
+(6 rows)
+
+DELETE FROM rw_view14 WHERE a=3; -- should be OK
+-- Partially updatable view
+INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
+ERROR: cannot insert into column "upper" of view "rw_view15"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
+INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT DO NOTHING; -- succeeds
+SELECT * FROM rw_view15;
+ a | upper
+----+-------------
+ -2 | ROW -2
+ -1 | ROW -1
+ 0 | ROW 0
+ 1 | ROW 1
+ 2 | ROW 2
+ 3 | UNSPECIFIED
+(6 rows)
+
+INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO NOTHING; -- succeeds
+SELECT * FROM rw_view15;
+ a | upper
+----+-------------
+ -2 | ROW -2
+ -1 | ROW -1
+ 0 | ROW 0
+ 1 | ROW 1
+ 2 | ROW 2
+ 3 | UNSPECIFIED
+(6 rows)
+
+INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO UPDATE set a = excluded.a; -- succeeds
+SELECT * FROM rw_view15;
+ a | upper
+----+-------------
+ -2 | ROW -2
+ -1 | ROW -1
+ 0 | ROW 0
+ 1 | ROW 1
+ 2 | ROW 2
+ 3 | UNSPECIFIED
+(6 rows)
+
+INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO UPDATE set upper = 'blarg'; -- fails
+ERROR: cannot insert into column "upper" of view "rw_view15"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+SELECT * FROM rw_view15;
+ a | upper
+----+-------------
+ -2 | ROW -2
+ -1 | ROW -1
+ 0 | ROW 0
+ 1 | ROW 1
+ 2 | ROW 2
+ 3 | UNSPECIFIED
+(6 rows)
+
+SELECT * FROM rw_view15;
+ a | upper
+----+-------------
+ -2 | ROW -2
+ -1 | ROW -1
+ 0 | ROW 0
+ 1 | ROW 1
+ 2 | ROW 2
+ 3 | UNSPECIFIED
+(6 rows)
+
+ALTER VIEW rw_view15 ALTER COLUMN upper SET DEFAULT 'NOT SET';
+INSERT INTO rw_view15 (a) VALUES (4); -- should fail
+ERROR: cannot insert into column "upper" of view "rw_view15"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+UPDATE rw_view15 SET upper='ROW 3' WHERE a=3; -- should fail
+ERROR: cannot update column "upper" of view "rw_view15"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+UPDATE rw_view15 SET upper=DEFAULT WHERE a=3; -- should fail
+ERROR: cannot update column "upper" of view "rw_view15"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+UPDATE rw_view15 SET a=4 WHERE a=3; -- should be OK
+SELECT * FROM base_tbl;
+ a | b
+----+-------------
+ -2 | Row -2
+ -1 | Row -1
+ 0 | Row 0
+ 1 | Row 1
+ 2 | Row 2
+ 4 | Unspecified
+(6 rows)
+
+DELETE FROM rw_view15 WHERE a=4; -- should be OK
+-- Partially updatable view
+INSERT INTO rw_view16 VALUES (3, 'Row 3', 3); -- should fail
+ERROR: multiple assignments to same column "a"
+INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should be OK
+UPDATE rw_view16 SET a=3, aa=-3 WHERE a=3; -- should fail
+ERROR: multiple assignments to same column "a"
+UPDATE rw_view16 SET aa=-3 WHERE a=3; -- should be OK
+SELECT * FROM base_tbl;
+ a | b
+----+--------
+ -2 | Row -2
+ -1 | Row -1
+ 0 | Row 0
+ 1 | Row 1
+ 2 | Row 2
+ -3 | Row 3
+(6 rows)
+
+DELETE FROM rw_view16 WHERE a=-3; -- should be OK
+-- Read-only views
+INSERT INTO ro_view17 VALUES (3, 'ROW 3');
+ERROR: cannot insert into view "ro_view1"
+DETAIL: Views containing DISTINCT are not automatically updatable.
+HINT: To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
+DELETE FROM ro_view18;
+ERROR: cannot delete from view "ro_view18"
+DETAIL: Views that do not select from a single table or view are not automatically updatable.
+HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
+UPDATE ro_view19 SET last_value=1000;
+ERROR: cannot update view "ro_view19"
+DETAIL: Views that do not select from a single table or view are not automatically updatable.
+HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
+UPDATE ro_view20 SET b=upper(b);
+ERROR: cannot update view "ro_view20"
+DETAIL: Views that return set-returning functions are not automatically updatable.
+HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
+-- A view with a conditional INSTEAD rule but no unconditional INSTEAD rules
+-- or INSTEAD OF triggers should be non-updatable and generate useful error
+-- messages with appropriate detail
+CREATE RULE rw_view16_ins_rule AS ON INSERT TO rw_view16
+ WHERE NEW.a > 0 DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, NEW.b);
+CREATE RULE rw_view16_upd_rule AS ON UPDATE TO rw_view16
+ WHERE OLD.a > 0 DO INSTEAD UPDATE base_tbl SET b=NEW.b WHERE a=OLD.a;
+CREATE RULE rw_view16_del_rule AS ON DELETE TO rw_view16
+ WHERE OLD.a > 0 DO INSTEAD DELETE FROM base_tbl WHERE a=OLD.a;
+INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should fail
+ERROR: cannot insert into view "rw_view16"
+DETAIL: Views with conditional DO INSTEAD rules are not automatically updatable.
+HINT: To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
+UPDATE rw_view16 SET b='ROW 2' WHERE a=2; -- should fail
+ERROR: cannot update view "rw_view16"
+DETAIL: Views with conditional DO INSTEAD rules are not automatically updatable.
+HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
+DELETE FROM rw_view16 WHERE a=2; -- should fail
+ERROR: cannot delete from view "rw_view16"
+DETAIL: Views with conditional DO INSTEAD rules are not automatically updatable.
+HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 16 other objects
+DETAIL: drop cascades to view ro_view1
+drop cascades to view ro_view17
+drop cascades to view ro_view2
+drop cascades to view ro_view3
+drop cascades to view ro_view4
+drop cascades to view ro_view5
+drop cascades to view ro_view6
+drop cascades to view ro_view7
+drop cascades to view ro_view8
+drop cascades to view ro_view9
+drop cascades to view ro_view11
+drop cascades to view ro_view13
+drop cascades to view rw_view14
+drop cascades to view rw_view15
+drop cascades to view rw_view16
+drop cascades to view ro_view20
+DROP VIEW ro_view10, ro_view12, ro_view18;
+DROP SEQUENCE uv_seq CASCADE;
+NOTICE: drop cascades to view ro_view19
+-- simple updatable view
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a>0;
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name = 'rw_view1';
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view1 | YES
+(1 row)
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name = 'rw_view1';
+ table_name | is_updatable | is_insertable_into
+------------+--------------+--------------------
+ rw_view1 | YES | YES
+(1 row)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name = 'rw_view1'
+ ORDER BY ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view1 | a | YES
+ rw_view1 | b | YES
+(2 rows)
+
+INSERT INTO rw_view1 VALUES (3, 'Row 3');
+INSERT INTO rw_view1 (a) VALUES (4);
+UPDATE rw_view1 SET a=5 WHERE a=4;
+DELETE FROM rw_view1 WHERE b='Row 2';
+SELECT * FROM base_tbl;
+ a | b
+----+-------------
+ -2 | Row -2
+ -1 | Row -1
+ 0 | Row 0
+ 1 | Row 1
+ 3 | Row 3
+ 5 | Unspecified
+(6 rows)
+
+EXPLAIN (costs off) UPDATE rw_view1 SET a=6 WHERE a=5;
+ QUERY PLAN
+--------------------------------------------------
+ Update on base_tbl
+ -> Index Scan using base_tbl_pkey on base_tbl
+ Index Cond: ((a > 0) AND (a = 5))
+(3 rows)
+
+EXPLAIN (costs off) DELETE FROM rw_view1 WHERE a=5;
+ QUERY PLAN
+--------------------------------------------------
+ Delete on base_tbl
+ -> Index Scan using base_tbl_pkey on base_tbl
+ Index Cond: ((a > 0) AND (a = 5))
+(3 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to view rw_view1
+-- view on top of view
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
+CREATE VIEW rw_view1 AS SELECT b AS bb, a AS aa FROM base_tbl WHERE a>0;
+CREATE VIEW rw_view2 AS SELECT aa AS aaa, bb AS bbb FROM rw_view1 WHERE aa<10;
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name = 'rw_view2';
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view2 | YES
+(1 row)
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name = 'rw_view2';
+ table_name | is_updatable | is_insertable_into
+------------+--------------+--------------------
+ rw_view2 | YES | YES
+(1 row)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name = 'rw_view2'
+ ORDER BY ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view2 | aaa | YES
+ rw_view2 | bbb | YES
+(2 rows)
+
+INSERT INTO rw_view2 VALUES (3, 'Row 3');
+INSERT INTO rw_view2 (aaa) VALUES (4);
+SELECT * FROM rw_view2;
+ aaa | bbb
+-----+-------------
+ 1 | Row 1
+ 2 | Row 2
+ 3 | Row 3
+ 4 | Unspecified
+(4 rows)
+
+UPDATE rw_view2 SET bbb='Row 4' WHERE aaa=4;
+DELETE FROM rw_view2 WHERE aaa=2;
+SELECT * FROM rw_view2;
+ aaa | bbb
+-----+-------
+ 1 | Row 1
+ 3 | Row 3
+ 4 | Row 4
+(3 rows)
+
+EXPLAIN (costs off) UPDATE rw_view2 SET aaa=5 WHERE aaa=4;
+ QUERY PLAN
+--------------------------------------------------------
+ Update on base_tbl
+ -> Index Scan using base_tbl_pkey on base_tbl
+ Index Cond: ((a < 10) AND (a > 0) AND (a = 4))
+(3 rows)
+
+EXPLAIN (costs off) DELETE FROM rw_view2 WHERE aaa=4;
+ QUERY PLAN
+--------------------------------------------------------
+ Delete on base_tbl
+ -> Index Scan using base_tbl_pkey on base_tbl
+ Index Cond: ((a < 10) AND (a > 0) AND (a = 4))
+(3 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to view rw_view2
+-- view on top of view with rules
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a>0 OFFSET 0; -- not updatable without rules/triggers
+CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a<10;
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view1 | NO
+ rw_view2 | NO
+(2 rows)
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_updatable | is_insertable_into
+------------+--------------+--------------------
+ rw_view1 | NO | NO
+ rw_view2 | NO | NO
+(2 rows)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view1 | a | NO
+ rw_view1 | b | NO
+ rw_view2 | a | NO
+ rw_view2 | b | NO
+(4 rows)
+
+CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, NEW.b) RETURNING *;
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view1 | YES
+ rw_view2 | YES
+(2 rows)
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_updatable | is_insertable_into
+------------+--------------+--------------------
+ rw_view1 | NO | YES
+ rw_view2 | NO | YES
+(2 rows)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view1 | a | NO
+ rw_view1 | b | NO
+ rw_view2 | a | NO
+ rw_view2 | b | NO
+(4 rows)
+
+CREATE RULE rw_view1_upd_rule AS ON UPDATE TO rw_view1
+ DO INSTEAD UPDATE base_tbl SET b=NEW.b WHERE a=OLD.a RETURNING NEW.*;
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view1 | YES
+ rw_view2 | YES
+(2 rows)
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_updatable | is_insertable_into
+------------+--------------+--------------------
+ rw_view1 | NO | YES
+ rw_view2 | NO | YES
+(2 rows)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view1 | a | NO
+ rw_view1 | b | NO
+ rw_view2 | a | NO
+ rw_view2 | b | NO
+(4 rows)
+
+CREATE RULE rw_view1_del_rule AS ON DELETE TO rw_view1
+ DO INSTEAD DELETE FROM base_tbl WHERE a=OLD.a RETURNING OLD.*;
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view1 | YES
+ rw_view2 | YES
+(2 rows)
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_updatable | is_insertable_into
+------------+--------------+--------------------
+ rw_view1 | YES | YES
+ rw_view2 | YES | YES
+(2 rows)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view1 | a | YES
+ rw_view1 | b | YES
+ rw_view2 | a | YES
+ rw_view2 | b | YES
+(4 rows)
+
+INSERT INTO rw_view2 VALUES (3, 'Row 3') RETURNING *;
+ a | b
+---+-------
+ 3 | Row 3
+(1 row)
+
+UPDATE rw_view2 SET b='Row three' WHERE a=3 RETURNING *;
+ a | b
+---+-----------
+ 3 | Row three
+(1 row)
+
+SELECT * FROM rw_view2;
+ a | b
+---+-----------
+ 1 | Row 1
+ 2 | Row 2
+ 3 | Row three
+(3 rows)
+
+DELETE FROM rw_view2 WHERE a=3 RETURNING *;
+ a | b
+---+-----------
+ 3 | Row three
+(1 row)
+
+SELECT * FROM rw_view2;
+ a | b
+---+-------
+ 1 | Row 1
+ 2 | Row 2
+(2 rows)
+
+EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
+ QUERY PLAN
+----------------------------------------------------------------
+ Update on base_tbl
+ -> Nested Loop
+ -> Index Scan using base_tbl_pkey on base_tbl
+ Index Cond: (a = 2)
+ -> Subquery Scan on rw_view1
+ Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2))
+ -> Bitmap Heap Scan on base_tbl base_tbl_1
+ Recheck Cond: (a > 0)
+ -> Bitmap Index Scan on base_tbl_pkey
+ Index Cond: (a > 0)
+(10 rows)
+
+EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
+ QUERY PLAN
+----------------------------------------------------------------
+ Delete on base_tbl
+ -> Nested Loop
+ -> Index Scan using base_tbl_pkey on base_tbl
+ Index Cond: (a = 2)
+ -> Subquery Scan on rw_view1
+ Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2))
+ -> Bitmap Heap Scan on base_tbl base_tbl_1
+ Recheck Cond: (a > 0)
+ -> Bitmap Index Scan on base_tbl_pkey
+ Index Cond: (a > 0)
+(10 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to view rw_view2
+-- view on top of view with triggers
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a>0 OFFSET 0; -- not updatable without rules/triggers
+CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a<10;
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view1 | NO
+ rw_view2 | NO
+(2 rows)
+
+SELECT table_name, is_updatable, is_insertable_into,
+ is_trigger_updatable, is_trigger_deletable,
+ is_trigger_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ rw_view1 | NO | NO | NO | NO | NO
+ rw_view2 | NO | NO | NO | NO | NO
+(2 rows)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view1 | a | NO
+ rw_view1 | b | NO
+ rw_view2 | a | NO
+ rw_view2 | b | NO
+(4 rows)
+
+CREATE FUNCTION rw_view1_trig_fn()
+RETURNS trigger AS
+$$
+BEGIN
+ IF TG_OP = 'INSERT' THEN
+ INSERT INTO base_tbl VALUES (NEW.a, NEW.b);
+ RETURN NEW;
+ ELSIF TG_OP = 'UPDATE' THEN
+ UPDATE base_tbl SET b=NEW.b WHERE a=OLD.a;
+ RETURN NEW;
+ ELSIF TG_OP = 'DELETE' THEN
+ DELETE FROM base_tbl WHERE a=OLD.a;
+ RETURN OLD;
+ END IF;
+END;
+$$
+LANGUAGE plpgsql;
+CREATE TRIGGER rw_view1_ins_trig INSTEAD OF INSERT ON rw_view1
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view1 | NO
+ rw_view2 | NO
+(2 rows)
+
+SELECT table_name, is_updatable, is_insertable_into,
+ is_trigger_updatable, is_trigger_deletable,
+ is_trigger_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ rw_view1 | NO | NO | NO | NO | YES
+ rw_view2 | NO | NO | NO | NO | NO
+(2 rows)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view1 | a | NO
+ rw_view1 | b | NO
+ rw_view2 | a | NO
+ rw_view2 | b | NO
+(4 rows)
+
+CREATE TRIGGER rw_view1_upd_trig INSTEAD OF UPDATE ON rw_view1
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view1 | NO
+ rw_view2 | NO
+(2 rows)
+
+SELECT table_name, is_updatable, is_insertable_into,
+ is_trigger_updatable, is_trigger_deletable,
+ is_trigger_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ rw_view1 | NO | NO | YES | NO | YES
+ rw_view2 | NO | NO | NO | NO | NO
+(2 rows)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view1 | a | NO
+ rw_view1 | b | NO
+ rw_view2 | a | NO
+ rw_view2 | b | NO
+(4 rows)
+
+CREATE TRIGGER rw_view1_del_trig INSTEAD OF DELETE ON rw_view1
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view1 | NO
+ rw_view2 | NO
+(2 rows)
+
+SELECT table_name, is_updatable, is_insertable_into,
+ is_trigger_updatable, is_trigger_deletable,
+ is_trigger_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+ table_name | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ rw_view1 | NO | NO | YES | YES | YES
+ rw_view2 | NO | NO | NO | NO | NO
+(2 rows)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view1 | a | NO
+ rw_view1 | b | NO
+ rw_view2 | a | NO
+ rw_view2 | b | NO
+(4 rows)
+
+INSERT INTO rw_view2 VALUES (3, 'Row 3') RETURNING *;
+ a | b
+---+-------
+ 3 | Row 3
+(1 row)
+
+UPDATE rw_view2 SET b='Row three' WHERE a=3 RETURNING *;
+ a | b
+---+-----------
+ 3 | Row three
+(1 row)
+
+SELECT * FROM rw_view2;
+ a | b
+---+-----------
+ 1 | Row 1
+ 2 | Row 2
+ 3 | Row three
+(3 rows)
+
+DELETE FROM rw_view2 WHERE a=3 RETURNING *;
+ a | b
+---+-----------
+ 3 | Row three
+(1 row)
+
+SELECT * FROM rw_view2;
+ a | b
+---+-------
+ 1 | Row 1
+ 2 | Row 2
+(2 rows)
+
+EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
+ QUERY PLAN
+----------------------------------------------------------
+ Update on rw_view1 rw_view1_1
+ -> Subquery Scan on rw_view1
+ Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2))
+ -> Bitmap Heap Scan on base_tbl
+ Recheck Cond: (a > 0)
+ -> Bitmap Index Scan on base_tbl_pkey
+ Index Cond: (a > 0)
+(7 rows)
+
+EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
+ QUERY PLAN
+----------------------------------------------------------
+ Delete on rw_view1 rw_view1_1
+ -> Subquery Scan on rw_view1
+ Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2))
+ -> Bitmap Heap Scan on base_tbl
+ Recheck Cond: (a > 0)
+ -> Bitmap Index Scan on base_tbl_pkey
+ Index Cond: (a > 0)
+(7 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to view rw_view2
+DROP FUNCTION rw_view1_trig_fn();
+-- update using whole row from view
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
+CREATE VIEW rw_view1 AS SELECT b AS bb, a AS aa FROM base_tbl;
+CREATE FUNCTION rw_view1_aa(x rw_view1)
+ RETURNS int AS $$ SELECT x.aa $$ LANGUAGE sql;
+UPDATE rw_view1 v SET bb='Updated row 2' WHERE rw_view1_aa(v)=2
+ RETURNING rw_view1_aa(v), v.bb;
+ rw_view1_aa | bb
+-------------+---------------
+ 2 | Updated row 2
+(1 row)
+
+SELECT * FROM base_tbl;
+ a | b
+----+---------------
+ -2 | Row -2
+ -1 | Row -1
+ 0 | Row 0
+ 1 | Row 1
+ 2 | Updated row 2
+(5 rows)
+
+EXPLAIN (costs off)
+UPDATE rw_view1 v SET bb='Updated row 2' WHERE rw_view1_aa(v)=2
+ RETURNING rw_view1_aa(v), v.bb;
+ QUERY PLAN
+--------------------------------------------------
+ Update on base_tbl
+ -> Index Scan using base_tbl_pkey on base_tbl
+ Index Cond: (a = 2)
+(3 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to function rw_view1_aa(rw_view1)
+-- permissions checks
+CREATE USER regress_view_user1;
+CREATE USER regress_view_user2;
+SET SESSION AUTHORIZATION regress_view_user1;
+CREATE TABLE base_tbl(a int, b text, c float);
+INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0);
+CREATE VIEW rw_view1 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl;
+INSERT INTO rw_view1 VALUES ('Row 2', 2.0, 2);
+GRANT SELECT ON base_tbl TO regress_view_user2;
+GRANT SELECT ON rw_view1 TO regress_view_user2;
+GRANT UPDATE (a,c) ON base_tbl TO regress_view_user2;
+GRANT UPDATE (bb,cc) ON rw_view1 TO regress_view_user2;
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION regress_view_user2;
+CREATE VIEW rw_view2 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl;
+SELECT * FROM base_tbl; -- ok
+ a | b | c
+---+-------+---
+ 1 | Row 1 | 1
+ 2 | Row 2 | 2
+(2 rows)
+
+SELECT * FROM rw_view1; -- ok
+ bb | cc | aa
+-------+----+----
+ Row 1 | 1 | 1
+ Row 2 | 2 | 2
+(2 rows)
+
+SELECT * FROM rw_view2; -- ok
+ bb | cc | aa
+-------+----+----
+ Row 1 | 1 | 1
+ Row 2 | 2 | 2
+(2 rows)
+
+INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- not allowed
+ERROR: permission denied for table base_tbl
+INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
+ERROR: permission denied for view rw_view1
+INSERT INTO rw_view2 VALUES ('Row 3', 3.0, 3); -- not allowed
+ERROR: permission denied for table base_tbl
+UPDATE base_tbl SET a=a, c=c; -- ok
+UPDATE base_tbl SET b=b; -- not allowed
+ERROR: permission denied for table base_tbl
+UPDATE rw_view1 SET bb=bb, cc=cc; -- ok
+UPDATE rw_view1 SET aa=aa; -- not allowed
+ERROR: permission denied for view rw_view1
+UPDATE rw_view2 SET aa=aa, cc=cc; -- ok
+UPDATE rw_view2 SET bb=bb; -- not allowed
+ERROR: permission denied for table base_tbl
+DELETE FROM base_tbl; -- not allowed
+ERROR: permission denied for table base_tbl
+DELETE FROM rw_view1; -- not allowed
+ERROR: permission denied for view rw_view1
+DELETE FROM rw_view2; -- not allowed
+ERROR: permission denied for table base_tbl
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION regress_view_user1;
+GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION regress_view_user2;
+INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- ok
+INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
+ERROR: permission denied for view rw_view1
+INSERT INTO rw_view2 VALUES ('Row 4', 4.0, 4); -- ok
+DELETE FROM base_tbl WHERE a=1; -- ok
+DELETE FROM rw_view1 WHERE aa=2; -- not allowed
+ERROR: permission denied for view rw_view1
+DELETE FROM rw_view2 WHERE aa=2; -- ok
+SELECT * FROM base_tbl;
+ a | b | c
+---+-------+---
+ 3 | Row 3 | 3
+ 4 | Row 4 | 4
+(2 rows)
+
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION regress_view_user1;
+REVOKE INSERT, DELETE ON base_tbl FROM regress_view_user2;
+GRANT INSERT, DELETE ON rw_view1 TO regress_view_user2;
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION regress_view_user2;
+INSERT INTO base_tbl VALUES (5, 'Row 5', 5.0); -- not allowed
+ERROR: permission denied for table base_tbl
+INSERT INTO rw_view1 VALUES ('Row 5', 5.0, 5); -- ok
+INSERT INTO rw_view2 VALUES ('Row 6', 6.0, 6); -- not allowed
+ERROR: permission denied for table base_tbl
+DELETE FROM base_tbl WHERE a=3; -- not allowed
+ERROR: permission denied for table base_tbl
+DELETE FROM rw_view1 WHERE aa=3; -- ok
+DELETE FROM rw_view2 WHERE aa=4; -- not allowed
+ERROR: permission denied for table base_tbl
+SELECT * FROM base_tbl;
+ a | b | c
+---+-------+---
+ 4 | Row 4 | 4
+ 5 | Row 5 | 5
+(2 rows)
+
+RESET SESSION AUTHORIZATION;
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to view rw_view2
+-- nested-view permissions
+CREATE TABLE base_tbl(a int, b text, c float);
+INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0);
+SET SESSION AUTHORIZATION regress_view_user1;
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl;
+SELECT * FROM rw_view1; -- not allowed
+ERROR: permission denied for table base_tbl
+SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
+ERROR: permission denied for table base_tbl
+UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
+ERROR: permission denied for table base_tbl
+SET SESSION AUTHORIZATION regress_view_user2;
+CREATE VIEW rw_view2 AS SELECT * FROM rw_view1;
+SELECT * FROM rw_view2; -- not allowed
+ERROR: permission denied for view rw_view1
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+ERROR: permission denied for view rw_view1
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+ERROR: permission denied for view rw_view1
+RESET SESSION AUTHORIZATION;
+GRANT SELECT ON base_tbl TO regress_view_user1;
+SET SESSION AUTHORIZATION regress_view_user1;
+SELECT * FROM rw_view1;
+ a | b | c
+---+-------+---
+ 1 | Row 1 | 1
+(1 row)
+
+SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
+ERROR: permission denied for table base_tbl
+UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
+ERROR: permission denied for table base_tbl
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2; -- not allowed
+ERROR: permission denied for view rw_view1
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+ERROR: permission denied for view rw_view1
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+ERROR: permission denied for view rw_view1
+SET SESSION AUTHORIZATION regress_view_user1;
+GRANT SELECT ON rw_view1 TO regress_view_user2;
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2;
+ a | b | c
+---+-------+---
+ 1 | Row 1 | 1
+(1 row)
+
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+ERROR: permission denied for view rw_view1
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+ERROR: permission denied for view rw_view1
+RESET SESSION AUTHORIZATION;
+GRANT UPDATE ON base_tbl TO regress_view_user1;
+SET SESSION AUTHORIZATION regress_view_user1;
+SELECT * FROM rw_view1;
+ a | b | c
+---+-------+---
+ 1 | Row 1 | 1
+(1 row)
+
+SELECT * FROM rw_view1 FOR UPDATE;
+ a | b | c
+---+-------+---
+ 1 | Row 1 | 1
+(1 row)
+
+UPDATE rw_view1 SET b = 'foo' WHERE a = 1;
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2;
+ a | b | c
+---+-----+---
+ 1 | foo | 1
+(1 row)
+
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+ERROR: permission denied for view rw_view1
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+ERROR: permission denied for view rw_view1
+SET SESSION AUTHORIZATION regress_view_user1;
+GRANT UPDATE ON rw_view1 TO regress_view_user2;
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2;
+ a | b | c
+---+-----+---
+ 1 | foo | 1
+(1 row)
+
+SELECT * FROM rw_view2 FOR UPDATE;
+ a | b | c
+---+-----+---
+ 1 | foo | 1
+(1 row)
+
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1;
+RESET SESSION AUTHORIZATION;
+REVOKE UPDATE ON base_tbl FROM regress_view_user1;
+SET SESSION AUTHORIZATION regress_view_user1;
+SELECT * FROM rw_view1;
+ a | b | c
+---+-----+---
+ 1 | bar | 1
+(1 row)
+
+SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
+ERROR: permission denied for table base_tbl
+UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
+ERROR: permission denied for table base_tbl
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2;
+ a | b | c
+---+-----+---
+ 1 | bar | 1
+(1 row)
+
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+ERROR: permission denied for table base_tbl
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+ERROR: permission denied for table base_tbl
+RESET SESSION AUTHORIZATION;
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to view rw_view2
+DROP USER regress_view_user1;
+DROP USER regress_view_user2;
+-- column defaults
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified', c serial);
+INSERT INTO base_tbl VALUES (1, 'Row 1');
+INSERT INTO base_tbl VALUES (2, 'Row 2');
+INSERT INTO base_tbl VALUES (3);
+CREATE VIEW rw_view1 AS SELECT a AS aa, b AS bb FROM base_tbl;
+ALTER VIEW rw_view1 ALTER COLUMN bb SET DEFAULT 'View default';
+INSERT INTO rw_view1 VALUES (4, 'Row 4');
+INSERT INTO rw_view1 (aa) VALUES (5);
+SELECT * FROM base_tbl;
+ a | b | c
+---+--------------+---
+ 1 | Row 1 | 1
+ 2 | Row 2 | 2
+ 3 | Unspecified | 3
+ 4 | Row 4 | 4
+ 5 | View default | 5
+(5 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to view rw_view1
+-- Table having triggers
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl VALUES (1, 'Row 1');
+INSERT INTO base_tbl VALUES (2, 'Row 2');
+CREATE FUNCTION rw_view1_trig_fn()
+RETURNS trigger AS
+$$
+BEGIN
+ IF TG_OP = 'INSERT' THEN
+ UPDATE base_tbl SET b=NEW.b WHERE a=1;
+ RETURN NULL;
+ END IF;
+ RETURN NULL;
+END;
+$$
+LANGUAGE plpgsql;
+CREATE TRIGGER rw_view1_ins_trig AFTER INSERT ON base_tbl
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+CREATE VIEW rw_view1 AS SELECT a AS aa, b AS bb FROM base_tbl;
+INSERT INTO rw_view1 VALUES (3, 'Row 3');
+select * from base_tbl;
+ a | b
+---+-------
+ 2 | Row 2
+ 3 | Row 3
+ 1 | Row 3
+(3 rows)
+
+DROP VIEW rw_view1;
+DROP TRIGGER rw_view1_ins_trig on base_tbl;
+DROP FUNCTION rw_view1_trig_fn();
+DROP TABLE base_tbl;
+-- view with ORDER BY
+CREATE TABLE base_tbl (a int, b int);
+INSERT INTO base_tbl VALUES (1,2), (4,5), (3,-3);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl ORDER BY a+b;
+SELECT * FROM rw_view1;
+ a | b
+---+----
+ 3 | -3
+ 1 | 2
+ 4 | 5
+(3 rows)
+
+INSERT INTO rw_view1 VALUES (7,-8);
+SELECT * FROM rw_view1;
+ a | b
+---+----
+ 7 | -8
+ 3 | -3
+ 1 | 2
+ 4 | 5
+(4 rows)
+
+EXPLAIN (verbose, costs off) UPDATE rw_view1 SET b = b + 1 RETURNING *;
+ QUERY PLAN
+-------------------------------------------------
+ Update on public.base_tbl
+ Output: base_tbl.a, base_tbl.b
+ -> Seq Scan on public.base_tbl
+ Output: (base_tbl.b + 1), base_tbl.ctid
+(4 rows)
+
+UPDATE rw_view1 SET b = b + 1 RETURNING *;
+ a | b
+---+----
+ 1 | 3
+ 4 | 6
+ 3 | -2
+ 7 | -7
+(4 rows)
+
+SELECT * FROM rw_view1;
+ a | b
+---+----
+ 7 | -7
+ 3 | -2
+ 1 | 3
+ 4 | 6
+(4 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to view rw_view1
+-- multiple array-column updates
+CREATE TABLE base_tbl (a int, arr int[]);
+INSERT INTO base_tbl VALUES (1,ARRAY[2]), (3,ARRAY[4]);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl;
+UPDATE rw_view1 SET arr[1] = 42, arr[2] = 77 WHERE a = 3;
+SELECT * FROM rw_view1;
+ a | arr
+---+---------
+ 1 | {2}
+ 3 | {42,77}
+(2 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to view rw_view1
+-- views with updatable and non-updatable columns
+CREATE TABLE base_tbl(a float);
+INSERT INTO base_tbl SELECT i/10.0 FROM generate_series(1,10) g(i);
+CREATE VIEW rw_view1 AS
+ SELECT ctid, sin(a) s, a, cos(a) c
+ FROM base_tbl
+ WHERE a != 0
+ ORDER BY abs(a);
+INSERT INTO rw_view1 VALUES (null, null, 1.1, null); -- should fail
+ERROR: cannot insert into column "ctid" of view "rw_view1"
+DETAIL: View columns that refer to system columns are not updatable.
+INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail
+ERROR: cannot insert into column "s" of view "rw_view1"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK
+ a | s | c
+-----+-------------------+-------------------
+ 1.1 | 0.891207360061435 | 0.453596121425577
+(1 row)
+
+UPDATE rw_view1 SET s = s WHERE a = 1.1; -- should fail
+ERROR: cannot update column "s" of view "rw_view1"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+UPDATE rw_view1 SET a = 1.05 WHERE a = 1.1 RETURNING s; -- OK
+ s
+-------------------
+ 0.867423225594017
+(1 row)
+
+DELETE FROM rw_view1 WHERE a = 1.05; -- OK
+CREATE VIEW rw_view2 AS
+ SELECT s, c, s/c t, a base_a, ctid
+ FROM rw_view1;
+INSERT INTO rw_view2 VALUES (null, null, null, 1.1, null); -- should fail
+ERROR: cannot insert into column "t" of view "rw_view2"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+INSERT INTO rw_view2(s, c, base_a) VALUES (null, null, 1.1); -- should fail
+ERROR: cannot insert into column "s" of view "rw_view1"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+INSERT INTO rw_view2(base_a) VALUES (1.1) RETURNING t; -- OK
+ t
+------------------
+ 1.96475965724865
+(1 row)
+
+UPDATE rw_view2 SET s = s WHERE base_a = 1.1; -- should fail
+ERROR: cannot update column "s" of view "rw_view1"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+UPDATE rw_view2 SET t = t WHERE base_a = 1.1; -- should fail
+ERROR: cannot update column "t" of view "rw_view2"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+UPDATE rw_view2 SET base_a = 1.05 WHERE base_a = 1.1; -- OK
+DELETE FROM rw_view2 WHERE base_a = 1.05 RETURNING base_a, s, c, t; -- OK
+ base_a | s | c | t
+--------+-------------------+-------------------+------------------
+ 1.05 | 0.867423225594017 | 0.497571047891727 | 1.74331530998317
+(1 row)
+
+CREATE VIEW rw_view3 AS
+ SELECT s, c, s/c t, ctid
+ FROM rw_view1;
+INSERT INTO rw_view3 VALUES (null, null, null, null); -- should fail
+ERROR: cannot insert into column "t" of view "rw_view3"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+INSERT INTO rw_view3(s) VALUES (null); -- should fail
+ERROR: cannot insert into column "s" of view "rw_view1"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+UPDATE rw_view3 SET s = s; -- should fail
+ERROR: cannot update column "s" of view "rw_view1"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+DELETE FROM rw_view3 WHERE s = sin(0.1); -- should be OK
+SELECT * FROM base_tbl ORDER BY a;
+ a
+-----
+ 0.2
+ 0.3
+ 0.4
+ 0.5
+ 0.6
+ 0.7
+ 0.8
+ 0.9
+ 1
+(9 rows)
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE E'r_\\_view%'
+ ORDER BY table_name;
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view1 | YES
+ rw_view2 | YES
+ rw_view3 | NO
+(3 rows)
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE E'r_\\_view%'
+ ORDER BY table_name;
+ table_name | is_updatable | is_insertable_into
+------------+--------------+--------------------
+ rw_view1 | YES | YES
+ rw_view2 | YES | YES
+ rw_view3 | NO | NO
+(3 rows)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE E'r_\\_view%'
+ ORDER BY table_name, ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view1 | ctid | NO
+ rw_view1 | s | NO
+ rw_view1 | a | YES
+ rw_view1 | c | NO
+ rw_view2 | s | NO
+ rw_view2 | c | NO
+ rw_view2 | t | NO
+ rw_view2 | base_a | YES
+ rw_view2 | ctid | NO
+ rw_view3 | s | NO
+ rw_view3 | c | NO
+ rw_view3 | t | NO
+ rw_view3 | ctid | NO
+(13 rows)
+
+SELECT events & 4 != 0 AS upd,
+ events & 8 != 0 AS ins,
+ events & 16 != 0 AS del
+ FROM pg_catalog.pg_relation_is_updatable('rw_view3'::regclass, false) t(events);
+ upd | ins | del
+-----+-----+-----
+ f | f | t
+(1 row)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to view rw_view2
+drop cascades to view rw_view3
+-- view on table with GENERATED columns
+CREATE TABLE base_tbl (id int, idplus1 int GENERATED ALWAYS AS (id + 1) STORED);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl;
+INSERT INTO base_tbl (id) VALUES (1);
+INSERT INTO rw_view1 (id) VALUES (2);
+INSERT INTO base_tbl (id, idplus1) VALUES (3, DEFAULT);
+INSERT INTO rw_view1 (id, idplus1) VALUES (4, DEFAULT);
+INSERT INTO base_tbl (id, idplus1) VALUES (5, 6); -- error
+ERROR: cannot insert a non-DEFAULT value into column "idplus1"
+DETAIL: Column "idplus1" is a generated column.
+INSERT INTO rw_view1 (id, idplus1) VALUES (6, 7); -- error
+ERROR: cannot insert a non-DEFAULT value into column "idplus1"
+DETAIL: Column "idplus1" is a generated column.
+SELECT * FROM base_tbl;
+ id | idplus1
+----+---------
+ 1 | 2
+ 2 | 3
+ 3 | 4
+ 4 | 5
+(4 rows)
+
+UPDATE base_tbl SET id = 2000 WHERE id = 2;
+UPDATE rw_view1 SET id = 3000 WHERE id = 3;
+SELECT * FROM base_tbl;
+ id | idplus1
+------+---------
+ 1 | 2
+ 4 | 5
+ 2000 | 2001
+ 3000 | 3001
+(4 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to view rw_view1
+-- inheritance tests
+CREATE TABLE base_tbl_parent (a int);
+CREATE TABLE base_tbl_child (CHECK (a > 0)) INHERITS (base_tbl_parent);
+INSERT INTO base_tbl_parent SELECT * FROM generate_series(-8, -1);
+INSERT INTO base_tbl_child SELECT * FROM generate_series(1, 8);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl_parent;
+CREATE VIEW rw_view2 AS SELECT * FROM ONLY base_tbl_parent;
+SELECT * FROM rw_view1 ORDER BY a;
+ a
+----
+ -8
+ -7
+ -6
+ -5
+ -4
+ -3
+ -2
+ -1
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+(16 rows)
+
+SELECT * FROM ONLY rw_view1 ORDER BY a;
+ a
+----
+ -8
+ -7
+ -6
+ -5
+ -4
+ -3
+ -2
+ -1
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+(16 rows)
+
+SELECT * FROM rw_view2 ORDER BY a;
+ a
+----
+ -8
+ -7
+ -6
+ -5
+ -4
+ -3
+ -2
+ -1
+(8 rows)
+
+INSERT INTO rw_view1 VALUES (-100), (100);
+INSERT INTO rw_view2 VALUES (-200), (200);
+UPDATE rw_view1 SET a = a*10 WHERE a IN (-1, 1); -- Should produce -10 and 10
+UPDATE ONLY rw_view1 SET a = a*10 WHERE a IN (-2, 2); -- Should produce -20 and 20
+UPDATE rw_view2 SET a = a*10 WHERE a IN (-3, 3); -- Should produce -30 only
+UPDATE ONLY rw_view2 SET a = a*10 WHERE a IN (-4, 4); -- Should produce -40 only
+DELETE FROM rw_view1 WHERE a IN (-5, 5); -- Should delete -5 and 5
+DELETE FROM ONLY rw_view1 WHERE a IN (-6, 6); -- Should delete -6 and 6
+DELETE FROM rw_view2 WHERE a IN (-7, 7); -- Should delete -7 only
+DELETE FROM ONLY rw_view2 WHERE a IN (-8, 8); -- Should delete -8 only
+SELECT * FROM ONLY base_tbl_parent ORDER BY a;
+ a
+------
+ -200
+ -100
+ -40
+ -30
+ -20
+ -10
+ 100
+ 200
+(8 rows)
+
+SELECT * FROM base_tbl_child ORDER BY a;
+ a
+----
+ 3
+ 4
+ 7
+ 8
+ 10
+ 20
+(6 rows)
+
+CREATE TABLE other_tbl_parent (id int);
+CREATE TABLE other_tbl_child () INHERITS (other_tbl_parent);
+INSERT INTO other_tbl_parent VALUES (7),(200);
+INSERT INTO other_tbl_child VALUES (8),(100);
+EXPLAIN (costs off)
+UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Update on base_tbl_parent
+ Update on base_tbl_parent base_tbl_parent_1
+ Update on base_tbl_child base_tbl_parent_2
+ -> Merge Join
+ Merge Cond: (base_tbl_parent.a = other_tbl_parent.id)
+ -> Sort
+ Sort Key: base_tbl_parent.a
+ -> Append
+ -> Seq Scan on base_tbl_parent base_tbl_parent_1
+ -> Seq Scan on base_tbl_child base_tbl_parent_2
+ -> Sort
+ Sort Key: other_tbl_parent.id
+ -> Append
+ -> Seq Scan on other_tbl_parent other_tbl_parent_1
+ -> Seq Scan on other_tbl_child other_tbl_parent_2
+(15 rows)
+
+UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
+SELECT * FROM ONLY base_tbl_parent ORDER BY a;
+ a
+------
+ -200
+ -100
+ -40
+ -30
+ -20
+ -10
+ 1100
+ 1200
+(8 rows)
+
+SELECT * FROM base_tbl_child ORDER BY a;
+ a
+------
+ 3
+ 4
+ 10
+ 20
+ 1007
+ 1008
+(6 rows)
+
+DROP TABLE base_tbl_parent, base_tbl_child CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to view rw_view2
+DROP TABLE other_tbl_parent CASCADE;
+NOTICE: drop cascades to table other_tbl_child
+-- simple WITH CHECK OPTION
+CREATE TABLE base_tbl (a int, b int DEFAULT 10);
+INSERT INTO base_tbl VALUES (1,2), (2,3), (1,-1);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
+ WITH LOCAL CHECK OPTION;
+\d+ rw_view1
+ View "public.rw_view1"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+---------+-------------
+ a | integer | | | | plain |
+ b | integer | | | | plain |
+View definition:
+ SELECT base_tbl.a,
+ base_tbl.b
+ FROM base_tbl
+ WHERE base_tbl.a < base_tbl.b;
+Options: check_option=local
+
+SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
+ table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+---------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression | public | rw_view1 | SELECT base_tbl.a, +| LOCAL | YES | YES | NO | NO | NO
+ | | | base_tbl.b +| | | | | |
+ | | | FROM base_tbl +| | | | | |
+ | | | WHERE (base_tbl.a < base_tbl.b); | | | | | |
+(1 row)
+
+INSERT INTO rw_view1 VALUES(3,4); -- ok
+INSERT INTO rw_view1 VALUES(4,3); -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (4, 3).
+INSERT INTO rw_view1 VALUES(5,null); -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (5, null).
+UPDATE rw_view1 SET b = 5 WHERE a = 3; -- ok
+UPDATE rw_view1 SET b = -5 WHERE a = 3; -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (3, -5).
+INSERT INTO rw_view1(a) VALUES (9); -- ok
+INSERT INTO rw_view1(a) VALUES (10); -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (10, 10).
+SELECT * FROM base_tbl;
+ a | b
+---+----
+ 1 | 2
+ 2 | 3
+ 1 | -1
+ 3 | 5
+ 9 | 10
+(5 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to view rw_view1
+-- WITH LOCAL/CASCADED CHECK OPTION
+CREATE TABLE base_tbl (a int);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a > 0;
+CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
+ WITH CHECK OPTION; -- implicitly cascaded
+\d+ rw_view2
+ View "public.rw_view2"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+---------+-------------
+ a | integer | | | | plain |
+View definition:
+ SELECT rw_view1.a
+ FROM rw_view1
+ WHERE rw_view1.a < 10;
+Options: check_option=cascaded
+
+SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
+ table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+---------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression | public | rw_view2 | SELECT rw_view1.a +| CASCADED | YES | YES | NO | NO | NO
+ | | | FROM rw_view1 +| | | | | |
+ | | | WHERE (rw_view1.a < 10); | | | | | |
+(1 row)
+
+INSERT INTO rw_view2 VALUES (-5); -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (-5).
+INSERT INTO rw_view2 VALUES (5); -- ok
+INSERT INTO rw_view2 VALUES (15); -- should fail
+ERROR: new row violates check option for view "rw_view2"
+DETAIL: Failing row contains (15).
+SELECT * FROM base_tbl;
+ a
+---
+ 5
+(1 row)
+
+UPDATE rw_view2 SET a = a - 10; -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (-5).
+UPDATE rw_view2 SET a = a + 10; -- should fail
+ERROR: new row violates check option for view "rw_view2"
+DETAIL: Failing row contains (15).
+CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
+ WITH LOCAL CHECK OPTION;
+\d+ rw_view2
+ View "public.rw_view2"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+---------+-------------
+ a | integer | | | | plain |
+View definition:
+ SELECT rw_view1.a
+ FROM rw_view1
+ WHERE rw_view1.a < 10;
+Options: check_option=local
+
+SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
+ table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+---------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression | public | rw_view2 | SELECT rw_view1.a +| LOCAL | YES | YES | NO | NO | NO
+ | | | FROM rw_view1 +| | | | | |
+ | | | WHERE (rw_view1.a < 10); | | | | | |
+(1 row)
+
+INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
+INSERT INTO rw_view2 VALUES (20); -- should fail
+ERROR: new row violates check option for view "rw_view2"
+DETAIL: Failing row contains (20).
+SELECT * FROM base_tbl;
+ a
+-----
+ 5
+ -10
+(2 rows)
+
+ALTER VIEW rw_view1 SET (check_option=here); -- invalid
+ERROR: invalid value for enum option "check_option": here
+DETAIL: Valid values are "local" and "cascaded".
+ALTER VIEW rw_view1 SET (check_option=local);
+INSERT INTO rw_view2 VALUES (-20); -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (-20).
+INSERT INTO rw_view2 VALUES (30); -- should fail
+ERROR: new row violates check option for view "rw_view2"
+DETAIL: Failing row contains (30).
+ALTER VIEW rw_view2 RESET (check_option);
+\d+ rw_view2
+ View "public.rw_view2"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+--------+---------+-----------+----------+---------+---------+-------------
+ a | integer | | | | plain |
+View definition:
+ SELECT rw_view1.a
+ FROM rw_view1
+ WHERE rw_view1.a < 10;
+
+SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
+ table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+---------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression | public | rw_view2 | SELECT rw_view1.a +| NONE | YES | YES | NO | NO | NO
+ | | | FROM rw_view1 +| | | | | |
+ | | | WHERE (rw_view1.a < 10); | | | | | |
+(1 row)
+
+INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
+SELECT * FROM base_tbl;
+ a
+-----
+ 5
+ -10
+ 30
+(3 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to view rw_view2
+-- WITH CHECK OPTION with no local view qual
+CREATE TABLE base_tbl (a int);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
+CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
+CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
+SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
+ table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+---------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression | public | rw_view1 | SELECT base_tbl.a +| CASCADED | YES | YES | NO | NO | NO
+ | | | FROM base_tbl; | | | | | |
+ regression | public | rw_view2 | SELECT rw_view1.a +| NONE | YES | YES | NO | NO | NO
+ | | | FROM rw_view1 +| | | | | |
+ | | | WHERE (rw_view1.a > 0); | | | | | |
+ regression | public | rw_view3 | SELECT rw_view2.a +| CASCADED | YES | YES | NO | NO | NO
+ | | | FROM rw_view2; | | | | | |
+(3 rows)
+
+INSERT INTO rw_view1 VALUES (-1); -- ok
+INSERT INTO rw_view1 VALUES (1); -- ok
+INSERT INTO rw_view2 VALUES (-2); -- ok, but not in view
+INSERT INTO rw_view2 VALUES (2); -- ok
+INSERT INTO rw_view3 VALUES (-3); -- should fail
+ERROR: new row violates check option for view "rw_view2"
+DETAIL: Failing row contains (-3).
+INSERT INTO rw_view3 VALUES (3); -- ok
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to view rw_view2
+drop cascades to view rw_view3
+-- WITH CHECK OPTION with scalar array ops
+CREATE TABLE base_tbl (a int, b int[]);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a = ANY (b)
+ WITH CHECK OPTION;
+INSERT INTO rw_view1 VALUES (1, ARRAY[1,2,3]); -- ok
+INSERT INTO rw_view1 VALUES (10, ARRAY[4,5]); -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (10, {4,5}).
+UPDATE rw_view1 SET b[2] = -b[2] WHERE a = 1; -- ok
+UPDATE rw_view1 SET b[1] = -b[1] WHERE a = 1; -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (1, {-1,-2,3}).
+PREPARE ins(int, int[]) AS INSERT INTO rw_view1 VALUES($1, $2);
+EXECUTE ins(2, ARRAY[1,2,3]); -- ok
+EXECUTE ins(10, ARRAY[4,5]); -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (10, {4,5}).
+DEALLOCATE PREPARE ins;
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to view rw_view1
+-- WITH CHECK OPTION with subquery
+CREATE TABLE base_tbl (a int);
+CREATE TABLE ref_tbl (a int PRIMARY KEY);
+INSERT INTO ref_tbl SELECT * FROM generate_series(1,10);
+CREATE VIEW rw_view1 AS
+ SELECT * FROM base_tbl b
+ WHERE EXISTS(SELECT 1 FROM ref_tbl r WHERE r.a = b.a)
+ WITH CHECK OPTION;
+INSERT INTO rw_view1 VALUES (5); -- ok
+INSERT INTO rw_view1 VALUES (15); -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (15).
+UPDATE rw_view1 SET a = a + 5; -- ok
+UPDATE rw_view1 SET a = a + 5; -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (15).
+EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
+ QUERY PLAN
+---------------------------------------------------------
+ Insert on base_tbl b
+ -> Result
+ SubPlan 1
+ -> Index Only Scan using ref_tbl_pkey on ref_tbl r
+ Index Cond: (a = b.a)
+(5 rows)
+
+EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
+ QUERY PLAN
+-----------------------------------------------------------
+ Update on base_tbl b
+ -> Hash Join
+ Hash Cond: (b.a = r.a)
+ -> Seq Scan on base_tbl b
+ -> Hash
+ -> Seq Scan on ref_tbl r
+ SubPlan 1
+ -> Index Only Scan using ref_tbl_pkey on ref_tbl r_1
+ Index Cond: (a = b.a)
+(9 rows)
+
+DROP TABLE base_tbl, ref_tbl CASCADE;
+NOTICE: drop cascades to view rw_view1
+-- WITH CHECK OPTION with BEFORE trigger on base table
+CREATE TABLE base_tbl (a int, b int);
+CREATE FUNCTION base_tbl_trig_fn()
+RETURNS trigger AS
+$$
+BEGIN
+ NEW.b := 10;
+ RETURN NEW;
+END;
+$$
+LANGUAGE plpgsql;
+CREATE TRIGGER base_tbl_trig BEFORE INSERT OR UPDATE ON base_tbl
+ FOR EACH ROW EXECUTE PROCEDURE base_tbl_trig_fn();
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b WITH CHECK OPTION;
+INSERT INTO rw_view1 VALUES (5,0); -- ok
+INSERT INTO rw_view1 VALUES (15, 20); -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (15, 10).
+UPDATE rw_view1 SET a = 20, b = 30; -- should fail
+ERROR: new row violates check option for view "rw_view1"
+DETAIL: Failing row contains (20, 10).
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to view rw_view1
+DROP FUNCTION base_tbl_trig_fn();
+-- WITH LOCAL CHECK OPTION with INSTEAD OF trigger on base view
+CREATE TABLE base_tbl (a int, b int);
+CREATE VIEW rw_view1 AS SELECT a FROM base_tbl WHERE a < b;
+CREATE FUNCTION rw_view1_trig_fn()
+RETURNS trigger AS
+$$
+BEGIN
+ IF TG_OP = 'INSERT' THEN
+ INSERT INTO base_tbl VALUES (NEW.a, 10);
+ RETURN NEW;
+ ELSIF TG_OP = 'UPDATE' THEN
+ UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
+ RETURN NEW;
+ ELSIF TG_OP = 'DELETE' THEN
+ DELETE FROM base_tbl WHERE a=OLD.a;
+ RETURN OLD;
+ END IF;
+END;
+$$
+LANGUAGE plpgsql;
+CREATE TRIGGER rw_view1_trig
+ INSTEAD OF INSERT OR UPDATE OR DELETE ON rw_view1
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+CREATE VIEW rw_view2 AS
+ SELECT * FROM rw_view1 WHERE a > 0 WITH LOCAL CHECK OPTION;
+INSERT INTO rw_view2 VALUES (-5); -- should fail
+ERROR: new row violates check option for view "rw_view2"
+DETAIL: Failing row contains (-5).
+INSERT INTO rw_view2 VALUES (5); -- ok
+INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
+UPDATE rw_view2 SET a = a - 10; -- should fail
+ERROR: new row violates check option for view "rw_view2"
+DETAIL: Failing row contains (-5).
+SELECT * FROM base_tbl;
+ a | b
+----+----
+ 5 | 10
+ 50 | 10
+(2 rows)
+
+-- Check option won't cascade down to base view with INSTEAD OF triggers
+ALTER VIEW rw_view2 SET (check_option=cascaded);
+INSERT INTO rw_view2 VALUES (100); -- ok, but not in view (doesn't fail rw_view1's check)
+UPDATE rw_view2 SET a = 200 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
+SELECT * FROM base_tbl;
+ a | b
+-----+----
+ 50 | 10
+ 100 | 10
+ 200 | 10
+(3 rows)
+
+-- Neither local nor cascaded check options work with INSTEAD rules
+DROP TRIGGER rw_view1_trig ON rw_view1;
+CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, 10);
+CREATE RULE rw_view1_upd_rule AS ON UPDATE TO rw_view1
+ DO INSTEAD UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
+INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view (doesn't fail rw_view2's check)
+INSERT INTO rw_view2 VALUES (5); -- ok
+INSERT INTO rw_view2 VALUES (20); -- ok, but not in view (doesn't fail rw_view1's check)
+UPDATE rw_view2 SET a = 30 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
+INSERT INTO rw_view2 VALUES (5); -- ok
+UPDATE rw_view2 SET a = -5 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view2's check)
+SELECT * FROM base_tbl;
+ a | b
+-----+----
+ 50 | 10
+ 100 | 10
+ 200 | 10
+ -10 | 10
+ 20 | 10
+ 30 | 10
+ -5 | 10
+(7 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to view rw_view2
+DROP FUNCTION rw_view1_trig_fn();
+CREATE TABLE base_tbl (a int);
+CREATE VIEW rw_view1 AS SELECT a,10 AS b FROM base_tbl;
+CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a);
+CREATE VIEW rw_view2 AS
+ SELECT * FROM rw_view1 WHERE a > b WITH LOCAL CHECK OPTION;
+INSERT INTO rw_view2 VALUES (2,3); -- ok, but not in view (doesn't fail rw_view2's check)
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to view rw_view2
+-- security barrier view
+CREATE TABLE base_tbl (person text, visibility text);
+INSERT INTO base_tbl VALUES ('Tom', 'public'),
+ ('Dick', 'private'),
+ ('Harry', 'public');
+CREATE VIEW rw_view1 AS
+ SELECT person FROM base_tbl WHERE visibility = 'public';
+CREATE FUNCTION snoop(anyelement)
+RETURNS boolean AS
+$$
+BEGIN
+ RAISE NOTICE 'snooped value: %', $1;
+ RETURN true;
+END;
+$$
+LANGUAGE plpgsql COST 0.000001;
+CREATE OR REPLACE FUNCTION leakproof(anyelement)
+RETURNS boolean AS
+$$
+BEGIN
+ RETURN true;
+END;
+$$
+LANGUAGE plpgsql STRICT IMMUTABLE LEAKPROOF;
+SELECT * FROM rw_view1 WHERE snoop(person);
+NOTICE: snooped value: Tom
+NOTICE: snooped value: Dick
+NOTICE: snooped value: Harry
+ person
+--------
+ Tom
+ Harry
+(2 rows)
+
+UPDATE rw_view1 SET person=person WHERE snoop(person);
+NOTICE: snooped value: Tom
+NOTICE: snooped value: Dick
+NOTICE: snooped value: Harry
+DELETE FROM rw_view1 WHERE NOT snoop(person);
+NOTICE: snooped value: Dick
+NOTICE: snooped value: Tom
+NOTICE: snooped value: Harry
+ALTER VIEW rw_view1 SET (security_barrier = true);
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name = 'rw_view1';
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view1 | YES
+(1 row)
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name = 'rw_view1';
+ table_name | is_updatable | is_insertable_into
+------------+--------------+--------------------
+ rw_view1 | YES | YES
+(1 row)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name = 'rw_view1'
+ ORDER BY ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view1 | person | YES
+(1 row)
+
+SELECT * FROM rw_view1 WHERE snoop(person);
+NOTICE: snooped value: Tom
+NOTICE: snooped value: Harry
+ person
+--------
+ Tom
+ Harry
+(2 rows)
+
+UPDATE rw_view1 SET person=person WHERE snoop(person);
+NOTICE: snooped value: Tom
+NOTICE: snooped value: Harry
+DELETE FROM rw_view1 WHERE NOT snoop(person);
+NOTICE: snooped value: Tom
+NOTICE: snooped value: Harry
+EXPLAIN (costs off) SELECT * FROM rw_view1 WHERE snoop(person);
+ QUERY PLAN
+-----------------------------------------------
+ Subquery Scan on rw_view1
+ Filter: snoop(rw_view1.person)
+ -> Seq Scan on base_tbl
+ Filter: (visibility = 'public'::text)
+(4 rows)
+
+EXPLAIN (costs off) UPDATE rw_view1 SET person=person WHERE snoop(person);
+ QUERY PLAN
+-------------------------------------------------------------------
+ Update on base_tbl
+ -> Seq Scan on base_tbl
+ Filter: ((visibility = 'public'::text) AND snoop(person))
+(3 rows)
+
+EXPLAIN (costs off) DELETE FROM rw_view1 WHERE NOT snoop(person);
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Delete on base_tbl
+ -> Seq Scan on base_tbl
+ Filter: ((visibility = 'public'::text) AND (NOT snoop(person)))
+(3 rows)
+
+-- security barrier view on top of security barrier view
+CREATE VIEW rw_view2 WITH (security_barrier = true) AS
+ SELECT * FROM rw_view1 WHERE snoop(person);
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name = 'rw_view2';
+ table_name | is_insertable_into
+------------+--------------------
+ rw_view2 | YES
+(1 row)
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name = 'rw_view2';
+ table_name | is_updatable | is_insertable_into
+------------+--------------+--------------------
+ rw_view2 | YES | YES
+(1 row)
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name = 'rw_view2'
+ ORDER BY ordinal_position;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ rw_view2 | person | YES
+(1 row)
+
+SELECT * FROM rw_view2 WHERE snoop(person);
+NOTICE: snooped value: Tom
+NOTICE: snooped value: Tom
+NOTICE: snooped value: Harry
+NOTICE: snooped value: Harry
+ person
+--------
+ Tom
+ Harry
+(2 rows)
+
+UPDATE rw_view2 SET person=person WHERE snoop(person);
+NOTICE: snooped value: Tom
+NOTICE: snooped value: Tom
+NOTICE: snooped value: Harry
+NOTICE: snooped value: Harry
+DELETE FROM rw_view2 WHERE NOT snoop(person);
+NOTICE: snooped value: Tom
+NOTICE: snooped value: Tom
+NOTICE: snooped value: Harry
+NOTICE: snooped value: Harry
+EXPLAIN (costs off) SELECT * FROM rw_view2 WHERE snoop(person);
+ QUERY PLAN
+-----------------------------------------------------
+ Subquery Scan on rw_view2
+ Filter: snoop(rw_view2.person)
+ -> Subquery Scan on rw_view1
+ Filter: snoop(rw_view1.person)
+ -> Seq Scan on base_tbl
+ Filter: (visibility = 'public'::text)
+(6 rows)
+
+EXPLAIN (costs off) UPDATE rw_view2 SET person=person WHERE snoop(person);
+ QUERY PLAN
+-------------------------------------------------------------------------------------
+ Update on base_tbl
+ -> Seq Scan on base_tbl
+ Filter: ((visibility = 'public'::text) AND snoop(person) AND snoop(person))
+(3 rows)
+
+EXPLAIN (costs off) DELETE FROM rw_view2 WHERE NOT snoop(person);
+ QUERY PLAN
+-------------------------------------------------------------------------------------------
+ Delete on base_tbl
+ -> Seq Scan on base_tbl
+ Filter: ((visibility = 'public'::text) AND snoop(person) AND (NOT snoop(person)))
+(3 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view rw_view1
+drop cascades to view rw_view2
+-- security barrier view on top of table with rules
+CREATE TABLE base_tbl(id int PRIMARY KEY, data text, deleted boolean);
+INSERT INTO base_tbl VALUES (1, 'Row 1', false), (2, 'Row 2', true);
+CREATE RULE base_tbl_ins_rule AS ON INSERT TO base_tbl
+ WHERE EXISTS (SELECT 1 FROM base_tbl t WHERE t.id = new.id)
+ DO INSTEAD
+ UPDATE base_tbl SET data = new.data, deleted = false WHERE id = new.id;
+CREATE RULE base_tbl_del_rule AS ON DELETE TO base_tbl
+ DO INSTEAD
+ UPDATE base_tbl SET deleted = true WHERE id = old.id;
+CREATE VIEW rw_view1 WITH (security_barrier=true) AS
+ SELECT id, data FROM base_tbl WHERE NOT deleted;
+SELECT * FROM rw_view1;
+ id | data
+----+-------
+ 1 | Row 1
+(1 row)
+
+EXPLAIN (costs off) DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
+ QUERY PLAN
+-------------------------------------------------------------------
+ Update on base_tbl base_tbl_1
+ -> Nested Loop
+ -> Index Scan using base_tbl_pkey on base_tbl base_tbl_1
+ Index Cond: (id = 1)
+ -> Index Scan using base_tbl_pkey on base_tbl
+ Index Cond: (id = 1)
+ Filter: ((NOT deleted) AND snoop(data))
+(7 rows)
+
+DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
+NOTICE: snooped value: Row 1
+EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (2, 'New row 2');
+ QUERY PLAN
+-----------------------------------------------------------
+ Insert on base_tbl
+ InitPlan 1 (returns $0)
+ -> Index Only Scan using base_tbl_pkey on base_tbl t
+ Index Cond: (id = 2)
+ -> Result
+ One-Time Filter: ($0 IS NOT TRUE)
+
+ Update on base_tbl
+ InitPlan 1 (returns $0)
+ -> Index Only Scan using base_tbl_pkey on base_tbl t
+ Index Cond: (id = 2)
+ -> Result
+ One-Time Filter: $0
+ -> Index Scan using base_tbl_pkey on base_tbl
+ Index Cond: (id = 2)
+(15 rows)
+
+INSERT INTO rw_view1 VALUES (2, 'New row 2');
+SELECT * FROM base_tbl;
+ id | data | deleted
+----+-----------+---------
+ 1 | Row 1 | t
+ 2 | New row 2 | f
+(2 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to view rw_view1
+-- security barrier view based on inheritance set
+CREATE TABLE t1 (a int, b float, c text);
+CREATE INDEX t1_a_idx ON t1(a);
+INSERT INTO t1
+SELECT i,i,'t1' FROM generate_series(1,10) g(i);
+ANALYZE t1;
+CREATE TABLE t11 (d text) INHERITS (t1);
+CREATE INDEX t11_a_idx ON t11(a);
+INSERT INTO t11
+SELECT i,i,'t11','t11d' FROM generate_series(1,10) g(i);
+ANALYZE t11;
+CREATE TABLE t12 (e int[]) INHERITS (t1);
+CREATE INDEX t12_a_idx ON t12(a);
+INSERT INTO t12
+SELECT i,i,'t12','{1,2}'::int[] FROM generate_series(1,10) g(i);
+ANALYZE t12;
+CREATE TABLE t111 () INHERITS (t11, t12);
+NOTICE: merging multiple inherited definitions of column "a"
+NOTICE: merging multiple inherited definitions of column "b"
+NOTICE: merging multiple inherited definitions of column "c"
+CREATE INDEX t111_a_idx ON t111(a);
+INSERT INTO t111
+SELECT i,i,'t111','t111d','{1,1,1}'::int[] FROM generate_series(1,10) g(i);
+ANALYZE t111;
+CREATE VIEW v1 WITH (security_barrier=true) AS
+SELECT *, (SELECT d FROM t11 WHERE t11.a = t1.a LIMIT 1) AS d
+FROM t1
+WHERE a > 5 AND EXISTS(SELECT 1 FROM t12 WHERE t12.a = t1.a);
+SELECT * FROM v1 WHERE a=3; -- should not see anything
+ a | b | c | d
+---+---+---+---
+(0 rows)
+
+SELECT * FROM v1 WHERE a=8;
+ a | b | c | d
+---+---+------+------
+ 8 | 8 | t1 | t11d
+ 8 | 8 | t11 | t11d
+ 8 | 8 | t12 | t11d
+ 8 | 8 | t111 | t11d
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------
+ Update on public.t1
+ Update on public.t1 t1_1
+ Update on public.t11 t1_2
+ Update on public.t12 t1_3
+ Update on public.t111 t1_4
+ -> Result
+ Output: 100, t1.tableoid, t1.ctid
+ -> Append
+ -> Index Scan using t1_a_idx on public.t1 t1_1
+ Output: t1_1.tableoid, t1_1.ctid
+ Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
+ Filter: ((t1_1.a <> 6) AND (SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+ SubPlan 1
+ -> Append
+ -> Seq Scan on public.t12 t12_1
+ Filter: (t12_1.a = t1_1.a)
+ -> Seq Scan on public.t111 t12_2
+ Filter: (t12_2.a = t1_1.a)
+ -> Index Scan using t11_a_idx on public.t11 t1_2
+ Output: t1_2.tableoid, t1_2.ctid
+ Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
+ Filter: ((t1_2.a <> 6) AND (SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+ -> Index Scan using t12_a_idx on public.t12 t1_3
+ Output: t1_3.tableoid, t1_3.ctid
+ Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
+ Filter: ((t1_3.a <> 6) AND (SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+ -> Index Scan using t111_a_idx on public.t111 t1_4
+ Output: t1_4.tableoid, t1_4.ctid
+ Index Cond: ((t1_4.a > 5) AND (t1_4.a < 7))
+ Filter: ((t1_4.a <> 6) AND (SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a))
+(30 rows)
+
+UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
+SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100
+ a | b | c | d
+---+---+---+---
+(0 rows)
+
+SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100
+ a | b | c
+---+---+---
+(0 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Update on public.t1
+ Update on public.t1 t1_1
+ Update on public.t11 t1_2
+ Update on public.t12 t1_3
+ Update on public.t111 t1_4
+ -> Result
+ Output: (t1.a + 1), t1.tableoid, t1.ctid
+ -> Append
+ -> Index Scan using t1_a_idx on public.t1 t1_1
+ Output: t1_1.a, t1_1.tableoid, t1_1.ctid
+ Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
+ Filter: ((SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+ SubPlan 1
+ -> Append
+ -> Seq Scan on public.t12 t12_1
+ Filter: (t12_1.a = t1_1.a)
+ -> Seq Scan on public.t111 t12_2
+ Filter: (t12_2.a = t1_1.a)
+ -> Index Scan using t11_a_idx on public.t11 t1_2
+ Output: t1_2.a, t1_2.tableoid, t1_2.ctid
+ Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
+ Filter: ((SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+ -> Index Scan using t12_a_idx on public.t12 t1_3
+ Output: t1_3.a, t1_3.tableoid, t1_3.ctid
+ Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
+ Filter: ((SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+ -> Index Scan using t111_a_idx on public.t111 t1_4
+ Output: t1_4.a, t1_4.tableoid, t1_4.ctid
+ Index Cond: ((t1_4.a > 5) AND (t1_4.a = 8))
+ Filter: ((SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a))
+(30 rows)
+
+UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
+NOTICE: snooped value: 8
+NOTICE: snooped value: 8
+NOTICE: snooped value: 8
+NOTICE: snooped value: 8
+SELECT * FROM v1 WHERE b=8;
+ a | b | c | d
+---+---+------+------
+ 9 | 8 | t1 | t11d
+ 9 | 8 | t11 | t11d
+ 9 | 8 | t12 | t11d
+ 9 | 8 | t111 | t11d
+(4 rows)
+
+DELETE FROM v1 WHERE snoop(a) AND leakproof(a); -- should not delete everything, just where a>5
+NOTICE: snooped value: 6
+NOTICE: snooped value: 7
+NOTICE: snooped value: 9
+NOTICE: snooped value: 10
+NOTICE: snooped value: 9
+NOTICE: snooped value: 6
+NOTICE: snooped value: 7
+NOTICE: snooped value: 9
+NOTICE: snooped value: 10
+NOTICE: snooped value: 9
+NOTICE: snooped value: 6
+NOTICE: snooped value: 7
+NOTICE: snooped value: 9
+NOTICE: snooped value: 10
+NOTICE: snooped value: 9
+NOTICE: snooped value: 6
+NOTICE: snooped value: 7
+NOTICE: snooped value: 9
+NOTICE: snooped value: 10
+NOTICE: snooped value: 9
+TABLE t1; -- verify all a<=5 are intact
+ a | b | c
+---+---+------
+ 1 | 1 | t1
+ 2 | 2 | t1
+ 3 | 3 | t1
+ 4 | 4 | t1
+ 5 | 5 | t1
+ 1 | 1 | t11
+ 2 | 2 | t11
+ 3 | 3 | t11
+ 4 | 4 | t11
+ 5 | 5 | t11
+ 1 | 1 | t12
+ 2 | 2 | t12
+ 3 | 3 | t12
+ 4 | 4 | t12
+ 5 | 5 | t12
+ 1 | 1 | t111
+ 2 | 2 | t111
+ 3 | 3 | t111
+ 4 | 4 | t111
+ 5 | 5 | t111
+(20 rows)
+
+DROP TABLE t1, t11, t12, t111 CASCADE;
+NOTICE: drop cascades to view v1
+DROP FUNCTION snoop(anyelement);
+DROP FUNCTION leakproof(anyelement);
+CREATE TABLE tx1 (a integer);
+CREATE TABLE tx2 (b integer);
+CREATE TABLE tx3 (c integer);
+CREATE VIEW vx1 AS SELECT a FROM tx1 WHERE EXISTS(SELECT 1 FROM tx2 JOIN tx3 ON b=c);
+INSERT INTO vx1 values (1);
+SELECT * FROM tx1;
+ a
+---
+ 1
+(1 row)
+
+SELECT * FROM vx1;
+ a
+---
+(0 rows)
+
+DROP VIEW vx1;
+DROP TABLE tx1;
+DROP TABLE tx2;
+DROP TABLE tx3;
+CREATE TABLE tx1 (a integer);
+CREATE TABLE tx2 (b integer);
+CREATE TABLE tx3 (c integer);
+CREATE VIEW vx1 AS SELECT a FROM tx1 WHERE EXISTS(SELECT 1 FROM tx2 JOIN tx3 ON b=c);
+INSERT INTO vx1 VALUES (1);
+INSERT INTO vx1 VALUES (1);
+SELECT * FROM tx1;
+ a
+---
+ 1
+ 1
+(2 rows)
+
+SELECT * FROM vx1;
+ a
+---
+(0 rows)
+
+DROP VIEW vx1;
+DROP TABLE tx1;
+DROP TABLE tx2;
+DROP TABLE tx3;
+CREATE TABLE tx1 (a integer, b integer);
+CREATE TABLE tx2 (b integer, c integer);
+CREATE TABLE tx3 (c integer, d integer);
+ALTER TABLE tx1 DROP COLUMN b;
+ALTER TABLE tx2 DROP COLUMN c;
+ALTER TABLE tx3 DROP COLUMN d;
+CREATE VIEW vx1 AS SELECT a FROM tx1 WHERE EXISTS(SELECT 1 FROM tx2 JOIN tx3 ON b=c);
+INSERT INTO vx1 VALUES (1);
+INSERT INTO vx1 VALUES (1);
+SELECT * FROM tx1;
+ a
+---
+ 1
+ 1
+(2 rows)
+
+SELECT * FROM vx1;
+ a
+---
+(0 rows)
+
+DROP VIEW vx1;
+DROP TABLE tx1;
+DROP TABLE tx2;
+DROP TABLE tx3;
+--
+-- Test handling of vars from correlated subqueries in quals from outer
+-- security barrier views, per bug #13988
+--
+CREATE TABLE t1 (a int, b text, c int);
+INSERT INTO t1 VALUES (1, 'one', 10);
+CREATE TABLE t2 (cc int);
+INSERT INTO t2 VALUES (10), (20);
+CREATE VIEW v1 WITH (security_barrier = true) AS
+ SELECT * FROM t1 WHERE (a > 0)
+ WITH CHECK OPTION;
+CREATE VIEW v2 WITH (security_barrier = true) AS
+ SELECT * FROM v1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.cc = v1.c)
+ WITH CHECK OPTION;
+INSERT INTO v2 VALUES (2, 'two', 20); -- ok
+INSERT INTO v2 VALUES (-2, 'minus two', 20); -- not allowed
+ERROR: new row violates check option for view "v1"
+DETAIL: Failing row contains (-2, minus two, 20).
+INSERT INTO v2 VALUES (3, 'three', 30); -- not allowed
+ERROR: new row violates check option for view "v2"
+DETAIL: Failing row contains (3, three, 30).
+UPDATE v2 SET b = 'ONE' WHERE a = 1; -- ok
+UPDATE v2 SET a = -1 WHERE a = 1; -- not allowed
+ERROR: new row violates check option for view "v1"
+DETAIL: Failing row contains (-1, ONE, 10).
+UPDATE v2 SET c = 30 WHERE a = 1; -- not allowed
+ERROR: new row violates check option for view "v2"
+DETAIL: Failing row contains (1, ONE, 30).
+DELETE FROM v2 WHERE a = 2; -- ok
+SELECT * FROM v2;
+ a | b | c
+---+-----+----
+ 1 | ONE | 10
+(1 row)
+
+DROP VIEW v2;
+DROP VIEW v1;
+DROP TABLE t2;
+DROP TABLE t1;
+--
+-- Test CREATE OR REPLACE VIEW turning a non-updatable view into an
+-- auto-updatable view and adding check options in a single step
+--
+CREATE TABLE t1 (a int, b text);
+CREATE VIEW v1 AS SELECT null::int AS a;
+CREATE OR REPLACE VIEW v1 AS SELECT * FROM t1 WHERE a > 0 WITH CHECK OPTION;
+INSERT INTO v1 VALUES (1, 'ok'); -- ok
+INSERT INTO v1 VALUES (-1, 'invalid'); -- should fail
+ERROR: new row violates check option for view "v1"
+DETAIL: Failing row contains (-1, invalid).
+DROP VIEW v1;
+DROP TABLE t1;
+-- check that an auto-updatable view on a partitioned table works correctly
+create table uv_pt (a int, b int, v varchar) partition by range (a, b);
+create table uv_pt1 (b int not null, v varchar, a int not null) partition by range (b);
+create table uv_pt11 (like uv_pt1);
+alter table uv_pt11 drop a;
+alter table uv_pt11 add a int;
+alter table uv_pt11 drop a;
+alter table uv_pt11 add a int not null;
+alter table uv_pt1 attach partition uv_pt11 for values from (2) to (5);
+alter table uv_pt attach partition uv_pt1 for values from (1, 2) to (1, 10);
+create view uv_ptv as select * from uv_pt;
+select events & 4 != 0 AS upd,
+ events & 8 != 0 AS ins,
+ events & 16 != 0 AS del
+ from pg_catalog.pg_relation_is_updatable('uv_pt'::regclass, false) t(events);
+ upd | ins | del
+-----+-----+-----
+ t | t | t
+(1 row)
+
+select pg_catalog.pg_column_is_updatable('uv_pt'::regclass, 1::smallint, false);
+ pg_column_is_updatable
+------------------------
+ t
+(1 row)
+
+select pg_catalog.pg_column_is_updatable('uv_pt'::regclass, 2::smallint, false);
+ pg_column_is_updatable
+------------------------
+ t
+(1 row)
+
+select table_name, is_updatable, is_insertable_into
+ from information_schema.views where table_name = 'uv_ptv';
+ table_name | is_updatable | is_insertable_into
+------------+--------------+--------------------
+ uv_ptv | YES | YES
+(1 row)
+
+select table_name, column_name, is_updatable
+ from information_schema.columns where table_name = 'uv_ptv' order by column_name;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ uv_ptv | a | YES
+ uv_ptv | b | YES
+ uv_ptv | v | YES
+(3 rows)
+
+insert into uv_ptv values (1, 2);
+select tableoid::regclass, * from uv_pt;
+ tableoid | a | b | v
+----------+---+---+---
+ uv_pt11 | 1 | 2 |
+(1 row)
+
+create view uv_ptv_wco as select * from uv_pt where a = 0 with check option;
+insert into uv_ptv_wco values (1, 2);
+ERROR: new row violates check option for view "uv_ptv_wco"
+DETAIL: Failing row contains (1, 2, null).
+drop view uv_ptv, uv_ptv_wco;
+drop table uv_pt, uv_pt1, uv_pt11;
+-- check that wholerow vars appearing in WITH CHECK OPTION constraint expressions
+-- work fine with partitioned tables
+create table wcowrtest (a int) partition by list (a);
+create table wcowrtest1 partition of wcowrtest for values in (1);
+create view wcowrtest_v as select * from wcowrtest where wcowrtest = '(2)'::wcowrtest with check option;
+insert into wcowrtest_v values (1);
+ERROR: new row violates check option for view "wcowrtest_v"
+DETAIL: Failing row contains (1).
+alter table wcowrtest add b text;
+create table wcowrtest2 (b text, c int, a int);
+alter table wcowrtest2 drop c;
+alter table wcowrtest attach partition wcowrtest2 for values in (2);
+create table sometable (a int, b text);
+insert into sometable values (1, 'a'), (2, 'b');
+create view wcowrtest_v2 as
+ select *
+ from wcowrtest r
+ where r in (select s from sometable s where r.a = s.a)
+with check option;
+-- WITH CHECK qual will be processed with wcowrtest2's
+-- rowtype after tuple-routing
+insert into wcowrtest_v2 values (2, 'no such row in sometable');
+ERROR: new row violates check option for view "wcowrtest_v2"
+DETAIL: Failing row contains (2, no such row in sometable).
+drop view wcowrtest_v, wcowrtest_v2;
+drop table wcowrtest, sometable;
+-- Check INSERT .. ON CONFLICT DO UPDATE works correctly when the view's
+-- columns are named and ordered differently than the underlying table's.
+create table uv_iocu_tab (a text unique, b float);
+insert into uv_iocu_tab values ('xyxyxy', 0);
+create view uv_iocu_view as
+ select b, b+1 as c, a, '2.0'::text as two from uv_iocu_tab;
+insert into uv_iocu_view (a, b) values ('xyxyxy', 1)
+ on conflict (a) do update set b = uv_iocu_view.b;
+select * from uv_iocu_tab;
+ a | b
+--------+---
+ xyxyxy | 0
+(1 row)
+
+insert into uv_iocu_view (a, b) values ('xyxyxy', 1)
+ on conflict (a) do update set b = excluded.b;
+select * from uv_iocu_tab;
+ a | b
+--------+---
+ xyxyxy | 1
+(1 row)
+
+-- OK to access view columns that are not present in underlying base
+-- relation in the ON CONFLICT portion of the query
+insert into uv_iocu_view (a, b) values ('xyxyxy', 3)
+ on conflict (a) do update set b = cast(excluded.two as float);
+select * from uv_iocu_tab;
+ a | b
+--------+---
+ xyxyxy | 2
+(1 row)
+
+explain (costs off)
+insert into uv_iocu_view (a, b) values ('xyxyxy', 3)
+ on conflict (a) do update set b = excluded.b where excluded.c > 0;
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Insert on uv_iocu_tab
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: uv_iocu_tab_a_key
+ Conflict Filter: ((excluded.b + '1'::double precision) > '0'::double precision)
+ -> Result
+(5 rows)
+
+insert into uv_iocu_view (a, b) values ('xyxyxy', 3)
+ on conflict (a) do update set b = excluded.b where excluded.c > 0;
+select * from uv_iocu_tab;
+ a | b
+--------+---
+ xyxyxy | 3
+(1 row)
+
+drop view uv_iocu_view;
+drop table uv_iocu_tab;
+-- Test whole-row references to the view
+create table uv_iocu_tab (a int unique, b text);
+create view uv_iocu_view as
+ select b as bb, a as aa, uv_iocu_tab::text as cc from uv_iocu_tab;
+insert into uv_iocu_view (aa,bb) values (1,'x');
+explain (costs off)
+insert into uv_iocu_view (aa,bb) values (1,'y')
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*
+ where excluded.aa > 0
+ and excluded.bb != ''
+ and excluded.cc is not null;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------
+ Insert on uv_iocu_tab
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: uv_iocu_tab_a_key
+ Conflict Filter: ((excluded.a > 0) AND (excluded.b <> ''::text) AND ((excluded.*)::text IS NOT NULL))
+ -> Result
+(5 rows)
+
+insert into uv_iocu_view (aa,bb) values (1,'y')
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*
+ where excluded.aa > 0
+ and excluded.bb != ''
+ and excluded.cc is not null;
+select * from uv_iocu_view;
+ bb | aa | cc
+-------------------------+----+---------------------------------
+ Rejected: (y,1,"(1,y)") | 1 | (1,"Rejected: (y,1,""(1,y)"")")
+(1 row)
+
+-- Test omitting a column of the base relation
+delete from uv_iocu_view;
+insert into uv_iocu_view (aa,bb) values (1,'x');
+insert into uv_iocu_view (aa) values (1)
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*;
+select * from uv_iocu_view;
+ bb | aa | cc
+-----------------------+----+-------------------------------
+ Rejected: (,1,"(1,)") | 1 | (1,"Rejected: (,1,""(1,)"")")
+(1 row)
+
+alter table uv_iocu_tab alter column b set default 'table default';
+insert into uv_iocu_view (aa) values (1)
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*;
+select * from uv_iocu_view;
+ bb | aa | cc
+-------------------------------------------------------+----+---------------------------------------------------------------------
+ Rejected: ("table default",1,"(1,""table default"")") | 1 | (1,"Rejected: (""table default"",1,""(1,""""table default"""")"")")
+(1 row)
+
+alter view uv_iocu_view alter column bb set default 'view default';
+insert into uv_iocu_view (aa) values (1)
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*;
+select * from uv_iocu_view;
+ bb | aa | cc
+-----------------------------------------------------+----+-------------------------------------------------------------------
+ Rejected: ("view default",1,"(1,""view default"")") | 1 | (1,"Rejected: (""view default"",1,""(1,""""view default"""")"")")
+(1 row)
+
+-- Should fail to update non-updatable columns
+insert into uv_iocu_view (aa) values (1)
+ on conflict (aa) do update set cc = 'XXX';
+ERROR: cannot insert into column "cc" of view "uv_iocu_view"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+drop view uv_iocu_view;
+drop table uv_iocu_tab;
+-- ON CONFLICT DO UPDATE permissions checks
+create user regress_view_user1;
+create user regress_view_user2;
+set session authorization regress_view_user1;
+create table base_tbl(a int unique, b text, c float);
+insert into base_tbl values (1,'xxx',1.0);
+create view rw_view1 as select b as bb, c as cc, a as aa from base_tbl;
+grant select (aa,bb) on rw_view1 to regress_view_user2;
+grant insert on rw_view1 to regress_view_user2;
+grant update (bb) on rw_view1 to regress_view_user2;
+set session authorization regress_view_user2;
+insert into rw_view1 values ('yyy',2.0,1)
+ on conflict (aa) do update set bb = excluded.cc; -- Not allowed
+ERROR: permission denied for view rw_view1
+insert into rw_view1 values ('yyy',2.0,1)
+ on conflict (aa) do update set bb = rw_view1.cc; -- Not allowed
+ERROR: permission denied for view rw_view1
+insert into rw_view1 values ('yyy',2.0,1)
+ on conflict (aa) do update set bb = excluded.bb; -- OK
+insert into rw_view1 values ('zzz',2.0,1)
+ on conflict (aa) do update set bb = rw_view1.bb||'xxx'; -- OK
+insert into rw_view1 values ('zzz',2.0,1)
+ on conflict (aa) do update set cc = 3.0; -- Not allowed
+ERROR: permission denied for view rw_view1
+reset session authorization;
+select * from base_tbl;
+ a | b | c
+---+--------+---
+ 1 | yyyxxx | 1
+(1 row)
+
+set session authorization regress_view_user1;
+grant select (a,b) on base_tbl to regress_view_user2;
+grant insert (a,b) on base_tbl to regress_view_user2;
+grant update (a,b) on base_tbl to regress_view_user2;
+set session authorization regress_view_user2;
+create view rw_view2 as select b as bb, c as cc, a as aa from base_tbl;
+insert into rw_view2 (aa,bb) values (1,'xxx')
+ on conflict (aa) do update set bb = excluded.bb; -- Not allowed
+ERROR: permission denied for table base_tbl
+create view rw_view3 as select b as bb, a as aa from base_tbl;
+insert into rw_view3 (aa,bb) values (1,'xxx')
+ on conflict (aa) do update set bb = excluded.bb; -- OK
+reset session authorization;
+select * from base_tbl;
+ a | b | c
+---+-----+---
+ 1 | xxx | 1
+(1 row)
+
+set session authorization regress_view_user2;
+create view rw_view4 as select aa, bb, cc FROM rw_view1;
+insert into rw_view4 (aa,bb) values (1,'yyy')
+ on conflict (aa) do update set bb = excluded.bb; -- Not allowed
+ERROR: permission denied for view rw_view1
+create view rw_view5 as select aa, bb FROM rw_view1;
+insert into rw_view5 (aa,bb) values (1,'yyy')
+ on conflict (aa) do update set bb = excluded.bb; -- OK
+reset session authorization;
+select * from base_tbl;
+ a | b | c
+---+-----+---
+ 1 | yyy | 1
+(1 row)
+
+drop view rw_view5;
+drop view rw_view4;
+drop view rw_view3;
+drop view rw_view2;
+drop view rw_view1;
+drop table base_tbl;
+drop user regress_view_user1;
+drop user regress_view_user2;
+-- Test single- and multi-row inserts with table and view defaults.
+-- Table defaults should be used, unless overridden by view defaults.
+create table base_tab_def (a int, b text default 'Table default',
+ c text default 'Table default', d text, e text);
+create view base_tab_def_view as select * from base_tab_def;
+alter view base_tab_def_view alter b set default 'View default';
+alter view base_tab_def_view alter d set default 'View default';
+insert into base_tab_def values (1);
+insert into base_tab_def values (2), (3);
+insert into base_tab_def values (4, default, default, default, default);
+insert into base_tab_def values (5, default, default, default, default),
+ (6, default, default, default, default);
+insert into base_tab_def_view values (11);
+insert into base_tab_def_view values (12), (13);
+insert into base_tab_def_view values (14, default, default, default, default);
+insert into base_tab_def_view values (15, default, default, default, default),
+ (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
+select * from base_tab_def order by a;
+ a | b | c | d | e
+----+---------------+---------------+--------------+---
+ 1 | Table default | Table default | |
+ 2 | Table default | Table default | |
+ 3 | Table default | Table default | |
+ 4 | Table default | Table default | |
+ 5 | Table default | Table default | |
+ 6 | Table default | Table default | |
+ 11 | View default | Table default | View default |
+ 12 | View default | Table default | View default |
+ 13 | View default | Table default | View default |
+ 14 | View default | Table default | View default |
+ 15 | View default | Table default | View default |
+ 16 | View default | Table default | View default |
+ 17 | View default | Table default | View default |
+ | View default | Table default | View default |
+(14 rows)
+
+-- Adding an INSTEAD OF trigger should cause NULLs to be inserted instead of
+-- table defaults, where there are no view defaults.
+create function base_tab_def_view_instrig_func() returns trigger
+as
+$$
+begin
+ insert into base_tab_def values (new.a, new.b, new.c, new.d, new.e);
+ return new;
+end;
+$$
+language plpgsql;
+create trigger base_tab_def_view_instrig instead of insert on base_tab_def_view
+ for each row execute function base_tab_def_view_instrig_func();
+truncate base_tab_def;
+insert into base_tab_def values (1);
+insert into base_tab_def values (2), (3);
+insert into base_tab_def values (4, default, default, default, default);
+insert into base_tab_def values (5, default, default, default, default),
+ (6, default, default, default, default);
+insert into base_tab_def_view values (11);
+insert into base_tab_def_view values (12), (13);
+insert into base_tab_def_view values (14, default, default, default, default);
+insert into base_tab_def_view values (15, default, default, default, default),
+ (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
+select * from base_tab_def order by a;
+ a | b | c | d | e
+----+---------------+---------------+--------------+---
+ 1 | Table default | Table default | |
+ 2 | Table default | Table default | |
+ 3 | Table default | Table default | |
+ 4 | Table default | Table default | |
+ 5 | Table default | Table default | |
+ 6 | Table default | Table default | |
+ 11 | View default | | View default |
+ 12 | View default | | View default |
+ 13 | View default | | View default |
+ 14 | View default | | View default |
+ 15 | View default | | View default |
+ 16 | View default | | View default |
+ 17 | View default | | View default |
+ | View default | | View default |
+(14 rows)
+
+-- Using an unconditional DO INSTEAD rule should also cause NULLs to be
+-- inserted where there are no view defaults.
+drop trigger base_tab_def_view_instrig on base_tab_def_view;
+drop function base_tab_def_view_instrig_func;
+create rule base_tab_def_view_ins_rule as on insert to base_tab_def_view
+ do instead insert into base_tab_def values (new.a, new.b, new.c, new.d, new.e);
+truncate base_tab_def;
+insert into base_tab_def values (1);
+insert into base_tab_def values (2), (3);
+insert into base_tab_def values (4, default, default, default, default);
+insert into base_tab_def values (5, default, default, default, default),
+ (6, default, default, default, default);
+insert into base_tab_def_view values (11);
+insert into base_tab_def_view values (12), (13);
+insert into base_tab_def_view values (14, default, default, default, default);
+insert into base_tab_def_view values (15, default, default, default, default),
+ (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
+select * from base_tab_def order by a;
+ a | b | c | d | e
+----+---------------+---------------+--------------+---
+ 1 | Table default | Table default | |
+ 2 | Table default | Table default | |
+ 3 | Table default | Table default | |
+ 4 | Table default | Table default | |
+ 5 | Table default | Table default | |
+ 6 | Table default | Table default | |
+ 11 | View default | | View default |
+ 12 | View default | | View default |
+ 13 | View default | | View default |
+ 14 | View default | | View default |
+ 15 | View default | | View default |
+ 16 | View default | | View default |
+ 17 | View default | | View default |
+ | View default | | View default |
+(14 rows)
+
+-- A DO ALSO rule should cause each row to be inserted twice. The first
+-- insert should behave the same as an auto-updatable view (using table
+-- defaults, unless overridden by view defaults). The second insert should
+-- behave the same as a rule-updatable view (inserting NULLs where there are
+-- no view defaults).
+drop rule base_tab_def_view_ins_rule on base_tab_def_view;
+create rule base_tab_def_view_ins_rule as on insert to base_tab_def_view
+ do also insert into base_tab_def values (new.a, new.b, new.c, new.d, new.e);
+truncate base_tab_def;
+insert into base_tab_def values (1);
+insert into base_tab_def values (2), (3);
+insert into base_tab_def values (4, default, default, default, default);
+insert into base_tab_def values (5, default, default, default, default),
+ (6, default, default, default, default);
+insert into base_tab_def_view values (11);
+insert into base_tab_def_view values (12), (13);
+insert into base_tab_def_view values (14, default, default, default, default);
+insert into base_tab_def_view values (15, default, default, default, default),
+ (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
+select * from base_tab_def order by a, c NULLS LAST;
+ a | b | c | d | e
+----+---------------+---------------+--------------+---
+ 1 | Table default | Table default | |
+ 2 | Table default | Table default | |
+ 3 | Table default | Table default | |
+ 4 | Table default | Table default | |
+ 5 | Table default | Table default | |
+ 6 | Table default | Table default | |
+ 11 | View default | Table default | View default |
+ 11 | View default | | View default |
+ 12 | View default | Table default | View default |
+ 12 | View default | | View default |
+ 13 | View default | Table default | View default |
+ 13 | View default | | View default |
+ 14 | View default | Table default | View default |
+ 14 | View default | | View default |
+ 15 | View default | Table default | View default |
+ 15 | View default | | View default |
+ 16 | View default | Table default | View default |
+ 16 | View default | | View default |
+ 17 | View default | Table default | View default |
+ 17 | View default | | View default |
+ | View default | Table default | View default |
+ | View default | | View default |
+(22 rows)
+
+drop view base_tab_def_view;
+drop table base_tab_def;
+-- Test defaults with array assignments
+create table base_tab (a serial, b int[], c text, d text default 'Table default');
+create view base_tab_view as select c, a, b from base_tab;
+alter view base_tab_view alter column c set default 'View default';
+insert into base_tab_view (b[1], b[2], c, b[5], b[4], a, b[3])
+values (1, 2, default, 5, 4, default, 3), (10, 11, 'C value', 14, 13, 100, 12);
+select * from base_tab order by a;
+ a | b | c | d
+-----+------------------+--------------+---------------
+ 1 | {1,2,3,4,5} | View default | Table default
+ 100 | {10,11,12,13,14} | C value | Table default
+(2 rows)
+
+drop view base_tab_view;
+drop table base_tab;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
new file mode 100644
index 0000000..c809f88
--- /dev/null
+++ b/src/test/regress/expected/update.out
@@ -0,0 +1,1028 @@
+--
+-- UPDATE syntax tests
+--
+CREATE TABLE update_test (
+ a INT DEFAULT 10,
+ b INT,
+ c TEXT
+);
+CREATE TABLE upsert_test (
+ a INT PRIMARY KEY,
+ b TEXT
+);
+INSERT INTO update_test VALUES (5, 10, 'foo');
+INSERT INTO update_test(b, a) VALUES (15, 10);
+SELECT * FROM update_test;
+ a | b | c
+----+----+-----
+ 5 | 10 | foo
+ 10 | 15 |
+(2 rows)
+
+UPDATE update_test SET a = DEFAULT, b = DEFAULT;
+SELECT * FROM update_test;
+ a | b | c
+----+---+-----
+ 10 | | foo
+ 10 | |
+(2 rows)
+
+-- aliases for the UPDATE target table
+UPDATE update_test AS t SET b = 10 WHERE t.a = 10;
+SELECT * FROM update_test;
+ a | b | c
+----+----+-----
+ 10 | 10 | foo
+ 10 | 10 |
+(2 rows)
+
+UPDATE update_test t SET b = t.b + 10 WHERE t.a = 10;
+SELECT * FROM update_test;
+ a | b | c
+----+----+-----
+ 10 | 20 | foo
+ 10 | 20 |
+(2 rows)
+
+--
+-- Test VALUES in FROM
+--
+UPDATE update_test SET a=v.i FROM (VALUES(100, 20)) AS v(i, j)
+ WHERE update_test.b = v.j;
+SELECT * FROM update_test;
+ a | b | c
+-----+----+-----
+ 100 | 20 | foo
+ 100 | 20 |
+(2 rows)
+
+-- fail, wrong data type:
+UPDATE update_test SET a = v.* FROM (VALUES(100, 20)) AS v(i, j)
+ WHERE update_test.b = v.j;
+ERROR: column "a" is of type integer but expression is of type record
+LINE 1: UPDATE update_test SET a = v.* FROM (VALUES(100, 20)) AS v(i...
+ ^
+HINT: You will need to rewrite or cast the expression.
+--
+-- Test multiple-set-clause syntax
+--
+INSERT INTO update_test SELECT a,b+1,c FROM update_test;
+SELECT * FROM update_test;
+ a | b | c
+-----+----+-----
+ 100 | 20 | foo
+ 100 | 20 |
+ 100 | 21 | foo
+ 100 | 21 |
+(4 rows)
+
+UPDATE update_test SET (c,b,a) = ('bugle', b+11, DEFAULT) WHERE c = 'foo';
+SELECT * FROM update_test;
+ a | b | c
+-----+----+-------
+ 100 | 20 |
+ 100 | 21 |
+ 10 | 31 | bugle
+ 10 | 32 | bugle
+(4 rows)
+
+UPDATE update_test SET (c,b) = ('car', a+b), a = a + 1 WHERE a = 10;
+SELECT * FROM update_test;
+ a | b | c
+-----+----+-----
+ 100 | 20 |
+ 100 | 21 |
+ 11 | 41 | car
+ 11 | 42 | car
+(4 rows)
+
+-- fail, multi assignment to same column:
+UPDATE update_test SET (c,b) = ('car', a+b), b = a + 1 WHERE a = 10;
+ERROR: multiple assignments to same column "b"
+-- uncorrelated sub-select:
+UPDATE update_test
+ SET (b,a) = (select a,b from update_test where b = 41 and c = 'car')
+ WHERE a = 100 AND b = 20;
+SELECT * FROM update_test;
+ a | b | c
+-----+----+-----
+ 100 | 21 |
+ 11 | 41 | car
+ 11 | 42 | car
+ 41 | 11 |
+(4 rows)
+
+-- correlated sub-select:
+UPDATE update_test o
+ SET (b,a) = (select a+1,b from update_test i
+ where i.a=o.a and i.b=o.b and i.c is not distinct from o.c);
+SELECT * FROM update_test;
+ a | b | c
+----+-----+-----
+ 21 | 101 |
+ 41 | 12 | car
+ 42 | 12 | car
+ 11 | 42 |
+(4 rows)
+
+-- fail, multiple rows supplied:
+UPDATE update_test SET (b,a) = (select a+1,b from update_test);
+ERROR: more than one row returned by a subquery used as an expression
+-- set to null if no rows supplied:
+UPDATE update_test SET (b,a) = (select a+1,b from update_test where a = 1000)
+ WHERE a = 11;
+SELECT * FROM update_test;
+ a | b | c
+----+-----+-----
+ 21 | 101 |
+ 41 | 12 | car
+ 42 | 12 | car
+ | |
+(4 rows)
+
+-- *-expansion should work in this context:
+UPDATE update_test SET (a,b) = ROW(v.*) FROM (VALUES(21, 100)) AS v(i, j)
+ WHERE update_test.a = v.i;
+-- you might expect this to work, but syntactically it's not a RowExpr:
+UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 101)) AS v(i, j)
+ WHERE update_test.a = v.i;
+ERROR: source for a multiple-column UPDATE item must be a sub-SELECT or ROW() expression
+LINE 1: UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 101)) ...
+ ^
+-- if an alias for the target table is specified, don't allow references
+-- to the original table name
+UPDATE update_test AS t SET b = update_test.b + 10 WHERE t.a = 10;
+ERROR: invalid reference to FROM-clause entry for table "update_test"
+LINE 1: UPDATE update_test AS t SET b = update_test.b + 10 WHERE t.a...
+ ^
+HINT: Perhaps you meant to reference the table alias "t".
+-- Make sure that we can update to a TOASTed value.
+UPDATE update_test SET c = repeat('x', 10000) WHERE c = 'car';
+SELECT a, b, char_length(c) FROM update_test;
+ a | b | char_length
+----+-----+-------------
+ | |
+ 21 | 100 |
+ 41 | 12 | 10000
+ 42 | 12 | 10000
+(4 rows)
+
+-- Check multi-assignment with a Result node to handle a one-time filter.
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE update_test t
+ SET (a, b) = (SELECT b, a FROM update_test s WHERE s.a = t.a)
+ WHERE CURRENT_USER = SESSION_USER;
+ QUERY PLAN
+-------------------------------------------------------------
+ Update on public.update_test t
+ -> Result
+ Output: $1, $2, (SubPlan 1 (returns $1,$2)), t.ctid
+ One-Time Filter: (CURRENT_USER = SESSION_USER)
+ -> Seq Scan on public.update_test t
+ Output: t.a, t.ctid
+ SubPlan 1 (returns $1,$2)
+ -> Seq Scan on public.update_test s
+ Output: s.b, s.a
+ Filter: (s.a = t.a)
+(10 rows)
+
+UPDATE update_test t
+ SET (a, b) = (SELECT b, a FROM update_test s WHERE s.a = t.a)
+ WHERE CURRENT_USER = SESSION_USER;
+SELECT a, b, char_length(c) FROM update_test;
+ a | b | char_length
+-----+----+-------------
+ | |
+ 100 | 21 |
+ 12 | 41 | 10000
+ 12 | 42 | 10000
+(4 rows)
+
+-- Test ON CONFLICT DO UPDATE
+INSERT INTO upsert_test VALUES(1, 'Boo'), (3, '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:
+INSERT INTO upsert_test VALUES (1, 'Baz'), (3, 'Zaz') ON CONFLICT(a)
+ DO UPDATE SET (b, a) = (SELECT b || ', Correlated', a from upsert_test i WHERE i.a = upsert_test.a)
+ RETURNING *;
+ a | b
+---+-----------------
+ 1 | Foo, Correlated
+ 3 | Zoo, Correlated
+(2 rows)
+
+-- correlated sub-select (EXCLUDED.* alias):
+INSERT INTO upsert_test VALUES (1, 'Bat'), (3, 'Zot') ON CONFLICT(a)
+ DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
+ RETURNING *;
+ a | b
+---+---------------------------
+ 1 | Foo, Correlated, Excluded
+ 3 | Zoo, Correlated, Excluded
+(2 rows)
+
+-- ON CONFLICT using system attributes in RETURNING, testing both the
+-- inserting and updating paths. See bug report at:
+-- https://www.postgresql.org/message-id/73436355-6432-49B1-92ED-1FE4F7E7E100%40finefun.com.au
+INSERT INTO upsert_test VALUES (2, 'Beeble') ON CONFLICT(a)
+ DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
+ RETURNING tableoid::regclass, xmin = pg_current_xact_id()::xid AS xmin_correct, xmax = 0 AS xmax_correct;
+ tableoid | xmin_correct | xmax_correct
+-------------+--------------+--------------
+ upsert_test | t | t
+(1 row)
+
+-- currently xmax is set after a conflict - that's probably not good,
+-- but it seems worthwhile to have to be explicit if that changes.
+INSERT INTO upsert_test VALUES (2, 'Brox') ON CONFLICT(a)
+ DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
+ RETURNING tableoid::regclass, xmin = pg_current_xact_id()::xid AS xmin_correct, xmax = pg_current_xact_id()::xid AS xmax_correct;
+ tableoid | xmin_correct | xmax_correct
+-------------+--------------+--------------
+ upsert_test | t | t
+(1 row)
+
+DROP TABLE update_test;
+DROP TABLE upsert_test;
+-- 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_4
+ Update on part_d_1_15 range_parted_5
+ Update on part_d_15_20 range_parted_6
+ Update on part_b_20_b_30 range_parted_7
+ -> Append
+ -> Seq Scan on part_a_1_a_10 range_parted_1
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_a_10_a_20 range_parted_2
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_b_1_b_10 range_parted_3
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_c_1_100 range_parted_4
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_d_1_15 range_parted_5
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_d_15_20 range_parted_6
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_b_20_b_30 range_parted_7
+ 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;
+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';
+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
+UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
+ 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 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.
+UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
+ a | ?column?
+---+----------
+ 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;
+-- ok
+UPDATE upview set c = 199 WHERE b = 4;
+-- fail, check option violation
+UPDATE upview set c = 120 WHERE b = 4;
+ERROR: new row violates check option for view "upview"
+DETAIL: Failing row contains (a, 4, 120, 1, 1).
+-- fail, row movement with check option violation
+UPDATE upview set a = 'b', b = 15, c = 120 WHERE b = 4;
+ERROR: new row violates check option for view "upview"
+DETAIL: Failing row contains (b, 15, 120, 1, 1).
+-- ok, row movement, check option passes
+UPDATE upview set a = 'b', b = 15 WHERE b = 4;
+:show_data;
+ partname | a | b | c | d | e
+---------------+---+----+-----+---+---
+ part_a_1_a_10 | a | 1 | 1 | 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
+ part_d_1_15 | b | 15 | 199 | 1 | 1
+(6 rows)
+
+-- cleanup
+DROP VIEW upview;
+-- RETURNING having whole-row vars.
+:init_range_parted;
+UPDATE range_parted set c = 95 WHERE a = 'b' and b > 10 and c > 100 returning (range_parted), *;
+ 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)
+
+-- 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;
+$$;
+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;
+NOTICE: trigger = trans_updatetrig, old table = (b,12,96,1,), (b,13,97,2,), (b,15,105,16,), (b,17,105,19,), new table = (b,12,110,1,), (b,13,98,2,), (b,15,106,16,), (b,17,106,19,)
+: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 | 13 | 98 | 2 |
+ part_d_15_20 | b | 15 | 106 | 16 |
+ part_d_15_20 | b | 17 | 106 | 19 |
+ part_d_1_15 | b | 12 | 110 | 1 |
+(6 rows)
+
+: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;
+NOTICE: trigger = trans_updatetrig, old table = (b,12,96,1,), (b,13,97,2,), (b,15,105,16,), (b,17,105,19,), new table = (b,12,146,1,), (b,13,147,2,), (b,15,155,16,), (b,17,155,19,)
+: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_d_15_20 | b | 15 | 155 | 16 |
+ part_d_15_20 | b | 17 | 155 | 19 |
+ part_d_1_15 | b | 12 | 146 | 1 |
+ part_d_1_15 | b | 13 | 147 | 2 |
+(6 rows)
+
+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;
+NOTICE: trigger = trans_updatetrig, old table = (b,13,96,1,), (b,14,97,2,), (b,16,105,16,), (b,18,105,19,), new table = (b,15,110,1,), (b,15,98,2,), (b,17,106,16,), (b,19,106,19,)
+: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 | 15 | 98 | 2 |
+ part_d_15_20 | b | 17 | 106 | 16 |
+ part_d_15_20 | b | 19 | 106 | 19 |
+ part_d_1_15 | b | 15 | 110 | 1 |
+(6 rows)
+
+:init_range_parted;
+UPDATE range_parted set c = c + 50 WHERE a = 'b' and b > 10 and c >= 96;
+NOTICE: trigger = trans_updatetrig, old table = (b,13,96,1,), (b,14,97,2,), (b,16,105,16,), (b,18,105,19,), new table = (b,15,146,1,), (b,16,147,2,), (b,17,155,16,), (b,19,155,19,)
+: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_d_15_20 | b | 17 | 155 | 16 |
+ part_d_15_20 | b | 19 | 155 | 19 |
+ part_d_1_15 | b | 15 | 146 | 1 |
+ part_d_1_15 | b | 16 | 147 | 2 |
+(6 rows)
+
+-- 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;
+NOTICE: trigger = trans_updatetrig, old table = (a,1,1,1,), new table = (a,15,1,1,)
+:show_data;
+ partname | a | b | c | d | e
+----------------+---+----+-----+----+---
+ part_a_10_a_20 | a | 10 | 200 | 1 |
+ part_a_10_a_20 | a | 15 | 1 | 1 |
+ part_c_1_100 | b | 13 | 96 | 1 |
+ part_c_1_100 | b | 14 | 97 | 2 |
+ part_d_15_20 | b | 16 | 105 | 16 |
+ part_d_15_20 | b | 18 | 105 | 19 |
+(6 rows)
+
+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;
+-- Test retrieval of system columns with non-consistent partition row types.
+-- This is only partially supported, as seen in the results.
+create table utrtest (a int, b text) partition by list (a);
+create table utr1 (a int check (a in (1)), q text, b text);
+create table utr2 (a int check (a in (2)), b text);
+alter table utr1 drop column q;
+alter table utrtest attach partition utr1 for values in (1);
+alter table utrtest attach partition utr2 for values in (2);
+insert into utrtest values (1, 'foo')
+ returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok;
+ a | b | tableoid | xmin_ok
+---+-----+----------+---------
+ 1 | foo | utr1 | t
+(1 row)
+
+insert into utrtest values (2, 'bar')
+ returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok; -- fails
+ERROR: cannot retrieve a system column in this context
+insert into utrtest values (2, 'bar')
+ returning *, tableoid::regclass;
+ a | b | tableoid
+---+-----+----------
+ 2 | bar | utr2
+(1 row)
+
+update utrtest set b = b || b from (values (1), (2)) s(x) where a = s.x
+ returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok;
+ a | b | x | tableoid | xmin_ok
+---+--------+---+----------+---------
+ 1 | foofoo | 1 | utr1 | t
+ 2 | barbar | 2 | utr2 | t
+(2 rows)
+
+update utrtest set a = 3 - a from (values (1), (2)) s(x) where a = s.x
+ returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok; -- fails
+ERROR: cannot retrieve a system column in this context
+update utrtest set a = 3 - a from (values (1), (2)) s(x) where a = s.x
+ returning *, tableoid::regclass;
+ a | b | x | tableoid
+---+--------+---+----------
+ 2 | foofoo | 1 | utr2
+ 1 | barbar | 2 | utr1
+(2 rows)
+
+delete from utrtest
+ returning *, tableoid::regclass, xmax = pg_current_xact_id()::xid as xmax_ok;
+ a | b | tableoid | xmax_ok
+---+--------+----------+---------
+ 1 | barbar | utr1 | t
+ 2 | foofoo | utr2 | t
+(2 rows)
+
+drop table utrtest;
+--------------
+-- 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);
diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out
new file mode 100644
index 0000000..090103d
--- /dev/null
+++ b/src/test/regress/expected/uuid.out
@@ -0,0 +1,159 @@
+-- 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');
+-- retrieving the inserted data
+SELECT guid_field FROM guid1;
+ guid_field
+--------------------------------------
+ 11111111-1111-1111-1111-111111111111
+ 22222222-2222-2222-2222-222222222222
+ 3f3e3c3b-3a30-3938-3736-353433a2313e
+(3 rows)
+
+-- 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)
+
+-- btree and hash index creation test
+CREATE INDEX guid1_btree ON guid1 USING BTREE (guid_field);
+CREATE INDEX guid1_hash ON guid1 USING HASH (guid_field);
+-- unique index test
+CREATE UNIQUE INDEX guid1_unique_BTREE ON guid1 USING BTREE (guid_field);
+-- should fail
+INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111');
+ERROR: duplicate key value violates unique constraint "guid1_unique_btree"
+DETAIL: Key (guid_field)=(11111111-1111-1111-1111-111111111111) already exists.
+-- check to see whether the new indexes are actually there
+SELECT count(*) FROM pg_class WHERE relkind='i' AND relname LIKE 'guid%';
+ count
+-------
+ 3
+(1 row)
+
+-- 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)
+
+-- generation test
+TRUNCATE guid1;
+INSERT INTO guid1 (guid_field) VALUES (gen_random_uuid());
+INSERT INTO guid1 (guid_field) VALUES (gen_random_uuid());
+SELECT count(DISTINCT guid_field) FROM guid1;
+ count
+-------
+ 2
+(1 row)
+
+-- clean up
+DROP TABLE guid1, guid2 CASCADE;
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
new file mode 100644
index 0000000..c63a157
--- /dev/null
+++ b/src/test/regress/expected/vacuum.out
@@ -0,0 +1,415 @@
+--
+-- VACUUM
+--
+CREATE TABLE vactst (i INT);
+INSERT INTO vactst VALUES (1);
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst VALUES (0);
+SELECT count(*) FROM vactst;
+ count
+-------
+ 2049
+(1 row)
+
+DELETE FROM vactst WHERE i != 0;
+SELECT * FROM vactst;
+ i
+---
+ 0
+(1 row)
+
+VACUUM FULL vactst;
+UPDATE vactst SET i = i + 1;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst VALUES (0);
+SELECT count(*) FROM vactst;
+ count
+-------
+ 2049
+(1 row)
+
+DELETE FROM vactst WHERE i != 0;
+VACUUM (FULL) vactst;
+DELETE FROM vactst;
+SELECT * FROM vactst;
+ i
+---
+(0 rows)
+
+VACUUM (FULL, FREEZE) vactst;
+VACUUM (ANALYZE, FULL) vactst;
+CREATE TABLE vaccluster (i INT PRIMARY KEY);
+ALTER TABLE vaccluster CLUSTER ON vaccluster_pkey;
+CLUSTER vaccluster;
+CREATE FUNCTION do_analyze() RETURNS VOID VOLATILE LANGUAGE SQL
+ AS 'ANALYZE pg_am';
+CREATE FUNCTION wrap_do_analyze(c INT) RETURNS INT IMMUTABLE LANGUAGE SQL
+ AS 'SELECT $1 FROM do_analyze()';
+CREATE INDEX ON vaccluster(wrap_do_analyze(i));
+INSERT INTO vaccluster VALUES (1), (2);
+ANALYZE vaccluster;
+ERROR: ANALYZE cannot be executed from VACUUM or ANALYZE
+CONTEXT: SQL function "do_analyze" statement 1
+SQL function "wrap_do_analyze" statement 1
+-- Test ANALYZE in transaction, where the transaction surrounding
+-- analyze performed modifications. This tests for the bug at
+-- https://postgr.es/m/c7988239-d42c-ddc4-41db-171b23b35e4f%40ssinger.info
+-- (which hopefully is unlikely to be reintroduced), but also seems
+-- independently worthwhile to cover.
+INSERT INTO vactst SELECT generate_series(1, 300);
+DELETE FROM vactst WHERE i % 7 = 0; -- delete a few rows outside
+BEGIN;
+INSERT INTO vactst SELECT generate_series(301, 400);
+DELETE FROM vactst WHERE i % 5 <> 0; -- delete a few rows inside
+ANALYZE vactst;
+COMMIT;
+VACUUM FULL pg_am;
+VACUUM FULL pg_class;
+VACUUM FULL pg_database;
+VACUUM FULL vaccluster;
+ERROR: ANALYZE cannot be executed from VACUUM or ANALYZE
+CONTEXT: SQL function "do_analyze" statement 1
+SQL function "wrap_do_analyze" statement 1
+VACUUM FULL vactst;
+VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
+-- PARALLEL option
+CREATE TABLE pvactst (i INT, a INT[], p POINT) with (autovacuum_enabled = off);
+INSERT INTO pvactst SELECT i, array[1,2,3], point(i, i+1) FROM generate_series(1,1000) i;
+CREATE INDEX btree_pvactst ON pvactst USING btree (i);
+CREATE INDEX hash_pvactst ON pvactst USING hash (i);
+CREATE INDEX brin_pvactst ON pvactst USING brin (i);
+CREATE INDEX gin_pvactst ON pvactst USING gin (a);
+CREATE INDEX gist_pvactst ON pvactst USING gist (p);
+CREATE INDEX spgist_pvactst ON pvactst USING spgist (p);
+-- VACUUM invokes parallel index cleanup
+SET min_parallel_index_scan_size to 0;
+VACUUM (PARALLEL 2) pvactst;
+-- VACUUM invokes parallel bulk-deletion
+UPDATE pvactst SET i = i WHERE i < 1000;
+VACUUM (PARALLEL 2) pvactst;
+UPDATE pvactst SET i = i WHERE i < 1000;
+VACUUM (PARALLEL 0) pvactst; -- disable parallel vacuum
+VACUUM (PARALLEL -1) pvactst; -- error
+ERROR: parallel workers for vacuum must be between 0 and 1024
+LINE 1: VACUUM (PARALLEL -1) pvactst;
+ ^
+VACUUM (PARALLEL 2, INDEX_CLEANUP FALSE) pvactst;
+VACUUM (PARALLEL 2, FULL TRUE) pvactst; -- error, cannot use both PARALLEL and FULL
+ERROR: VACUUM FULL cannot be performed in parallel
+VACUUM (PARALLEL) pvactst; -- error, cannot use PARALLEL option without parallel degree
+ERROR: parallel option requires a value between 0 and 1024
+LINE 1: VACUUM (PARALLEL) pvactst;
+ ^
+-- Test different combinations of parallel and full options for temporary tables
+CREATE TEMPORARY TABLE tmp (a int PRIMARY KEY);
+CREATE INDEX tmp_idx1 ON tmp (a);
+VACUUM (PARALLEL 1, FULL FALSE) tmp; -- parallel vacuum disabled for temp tables
+WARNING: disabling parallel option of vacuum on "tmp" --- cannot vacuum temporary tables in parallel
+VACUUM (PARALLEL 0, FULL TRUE) tmp; -- can specify parallel disabled (even though that's implied by FULL)
+RESET min_parallel_index_scan_size;
+DROP TABLE pvactst;
+-- INDEX_CLEANUP option
+CREATE TABLE no_index_cleanup (i INT PRIMARY KEY, t TEXT);
+-- Use uncompressed data stored in toast.
+CREATE INDEX no_index_cleanup_idx ON no_index_cleanup(t);
+ALTER TABLE no_index_cleanup ALTER COLUMN t SET STORAGE EXTERNAL;
+INSERT INTO no_index_cleanup(i, t) VALUES (generate_series(1,30),
+ repeat('1234567890',269));
+-- index cleanup option is ignored if VACUUM FULL
+VACUUM (INDEX_CLEANUP TRUE, FULL TRUE) no_index_cleanup;
+VACUUM (FULL TRUE) no_index_cleanup;
+-- Toast inherits the value from its parent table.
+ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = false);
+DELETE FROM no_index_cleanup WHERE i < 15;
+-- Nothing is cleaned up.
+VACUUM no_index_cleanup;
+-- Both parent relation and toast are cleaned up.
+ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = true);
+VACUUM no_index_cleanup;
+ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = auto);
+VACUUM no_index_cleanup;
+-- Parameter is set for both the parent table and its toast relation.
+INSERT INTO no_index_cleanup(i, t) VALUES (generate_series(31,60),
+ repeat('1234567890',269));
+DELETE FROM no_index_cleanup WHERE i < 45;
+-- Only toast index is cleaned up.
+ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = off,
+ toast.vacuum_index_cleanup = yes);
+VACUUM no_index_cleanup;
+-- Only parent is cleaned up.
+ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = true,
+ toast.vacuum_index_cleanup = false);
+VACUUM no_index_cleanup;
+-- Test some extra relations.
+VACUUM (INDEX_CLEANUP FALSE) vaccluster;
+VACUUM (INDEX_CLEANUP AUTO) vactst; -- index cleanup option is ignored if no indexes
+VACUUM (INDEX_CLEANUP FALSE, FREEZE TRUE) vaccluster;
+-- TRUNCATE option
+CREATE TEMP TABLE vac_truncate_test(i INT NOT NULL, j text)
+ WITH (vacuum_truncate=true, autovacuum_enabled=false);
+INSERT INTO vac_truncate_test VALUES (1, NULL), (NULL, NULL);
+ERROR: null value in column "i" of relation "vac_truncate_test" violates not-null constraint
+DETAIL: Failing row contains (null, null).
+VACUUM (TRUNCATE FALSE, DISABLE_PAGE_SKIPPING) vac_truncate_test;
+SELECT pg_relation_size('vac_truncate_test') > 0;
+ ?column?
+----------
+ t
+(1 row)
+
+VACUUM (DISABLE_PAGE_SKIPPING) vac_truncate_test;
+SELECT pg_relation_size('vac_truncate_test') = 0;
+ ?column?
+----------
+ t
+(1 row)
+
+VACUUM (TRUNCATE FALSE, FULL TRUE) vac_truncate_test;
+DROP TABLE vac_truncate_test;
+-- partitioned table
+CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
+CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
+INSERT INTO vacparted VALUES (1, 'a');
+UPDATE vacparted SET b = 'b';
+VACUUM (ANALYZE) vacparted;
+VACUUM (FULL) vacparted;
+VACUUM (FREEZE) vacparted;
+-- check behavior with duplicate column mentions
+VACUUM ANALYZE vacparted(a,b,a);
+ERROR: column "a" of relation "vacparted" appears more than once
+ANALYZE vacparted(a,b,b);
+ERROR: column "b" of relation "vacparted" appears more than once
+-- partitioned table with index
+CREATE TABLE vacparted_i (a int primary key, b varchar(100))
+ PARTITION BY HASH (a);
+CREATE TABLE vacparted_i1 PARTITION OF vacparted_i
+ FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE vacparted_i2 PARTITION OF vacparted_i
+ FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO vacparted_i SELECT i, 'test_'|| i from generate_series(1,10) i;
+VACUUM (ANALYZE) vacparted_i;
+VACUUM (FULL) vacparted_i;
+VACUUM (FREEZE) vacparted_i;
+SELECT relname, relhasindex FROM pg_class
+ WHERE relname LIKE 'vacparted_i%' AND relkind IN ('p','r')
+ ORDER BY relname;
+ relname | relhasindex
+--------------+-------------
+ vacparted_i | t
+ vacparted_i1 | t
+ vacparted_i2 | t
+(3 rows)
+
+DROP TABLE vacparted_i;
+-- multiple tables specified
+VACUUM vaccluster, vactst;
+VACUUM vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) does_not_exist, vaccluster;
+ERROR: relation "does_not_exist" does not exist
+VACUUM ANALYZE vactst, vacparted (a);
+VACUUM ANALYZE vactst (does_not_exist), vacparted (b);
+ERROR: column "does_not_exist" of relation "vactst" does not exist
+VACUUM FULL vacparted, vactst;
+VACUUM FULL vactst, vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE vactst, does_not_exist, vacparted;
+ERROR: relation "does_not_exist" does not exist
+ANALYZE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst;
+BEGIN; -- ANALYZE behaves differently inside a transaction block
+ANALYZE vactst, vactst;
+COMMIT;
+-- parenthesized syntax for ANALYZE
+ANALYZE (VERBOSE) does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+ANALYZE (nonexistent-arg) does_not_exist;
+ERROR: syntax error at or near "arg"
+LINE 1: ANALYZE (nonexistent-arg) does_not_exist;
+ ^
+ANALYZE (nonexistentarg) does_not_exit;
+ERROR: unrecognized ANALYZE option "nonexistentarg"
+LINE 1: ANALYZE (nonexistentarg) does_not_exit;
+ ^
+-- ensure argument order independence, and that SKIP_LOCKED on non-existing
+-- relation still errors out. Suppress WARNING messages caused by concurrent
+-- autovacuums.
+SET client_min_messages TO 'ERROR';
+ANALYZE (SKIP_LOCKED, VERBOSE) does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+ANALYZE (VERBOSE, SKIP_LOCKED) does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+-- SKIP_LOCKED option
+VACUUM (SKIP_LOCKED) vactst;
+VACUUM (SKIP_LOCKED, FULL) vactst;
+ANALYZE (SKIP_LOCKED) vactst;
+RESET client_min_messages;
+-- ensure VACUUM and ANALYZE don't have a problem with serializable
+SET default_transaction_isolation = serializable;
+VACUUM vactst;
+ANALYZE vactst;
+RESET default_transaction_isolation;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+ANALYZE vactst;
+COMMIT;
+-- PROCESS_TOAST option
+ALTER TABLE vactst ADD COLUMN t TEXT;
+ALTER TABLE vactst ALTER COLUMN t SET STORAGE EXTERNAL;
+VACUUM (PROCESS_TOAST FALSE) vactst;
+VACUUM (PROCESS_TOAST FALSE, FULL) vactst;
+ERROR: PROCESS_TOAST required with VACUUM FULL
+DROP TABLE vaccluster;
+DROP TABLE vactst;
+DROP TABLE vacparted;
+DROP TABLE no_index_cleanup;
+-- relation ownership, WARNING logs generated as all are skipped.
+CREATE TABLE vacowned (a int);
+CREATE TABLE vacowned_parted (a int) PARTITION BY LIST (a);
+CREATE TABLE vacowned_part1 PARTITION OF vacowned_parted FOR VALUES IN (1);
+CREATE TABLE vacowned_part2 PARTITION OF vacowned_parted FOR VALUES IN (2);
+CREATE ROLE regress_vacuum;
+SET ROLE regress_vacuum;
+-- Simple table
+VACUUM vacowned;
+WARNING: skipping "vacowned" --- only table or database owner can vacuum it
+ANALYZE vacowned;
+WARNING: skipping "vacowned" --- only table or database owner can analyze it
+VACUUM (ANALYZE) vacowned;
+WARNING: skipping "vacowned" --- only table or database owner can vacuum it
+-- Catalog
+VACUUM pg_catalog.pg_class;
+WARNING: skipping "pg_class" --- only superuser or database owner can vacuum it
+ANALYZE pg_catalog.pg_class;
+WARNING: skipping "pg_class" --- only superuser or database owner can analyze it
+VACUUM (ANALYZE) pg_catalog.pg_class;
+WARNING: skipping "pg_class" --- only superuser or database owner can vacuum it
+-- Shared catalog
+VACUUM pg_catalog.pg_authid;
+WARNING: skipping "pg_authid" --- only superuser can vacuum it
+ANALYZE pg_catalog.pg_authid;
+WARNING: skipping "pg_authid" --- only superuser can analyze it
+VACUUM (ANALYZE) pg_catalog.pg_authid;
+WARNING: skipping "pg_authid" --- only superuser can vacuum it
+-- Partitioned table and its partitions, nothing owned by other user.
+-- Relations are not listed in a single command to test ownership
+-- independently.
+VACUUM vacowned_parted;
+WARNING: skipping "vacowned_parted" --- only table or database owner can vacuum it
+WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+VACUUM vacowned_part1;
+WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it
+VACUUM vacowned_part2;
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+ANALYZE vacowned_parted;
+WARNING: skipping "vacowned_parted" --- only table or database owner can analyze it
+WARNING: skipping "vacowned_part1" --- only table or database owner can analyze it
+WARNING: skipping "vacowned_part2" --- only table or database owner can analyze it
+ANALYZE vacowned_part1;
+WARNING: skipping "vacowned_part1" --- only table or database owner can analyze it
+ANALYZE vacowned_part2;
+WARNING: skipping "vacowned_part2" --- only table or database owner can analyze it
+VACUUM (ANALYZE) vacowned_parted;
+WARNING: skipping "vacowned_parted" --- only table or database owner can vacuum it
+WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+VACUUM (ANALYZE) vacowned_part1;
+WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it
+VACUUM (ANALYZE) vacowned_part2;
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+RESET ROLE;
+-- Partitioned table and one partition owned by other user.
+ALTER TABLE vacowned_parted OWNER TO regress_vacuum;
+ALTER TABLE vacowned_part1 OWNER TO regress_vacuum;
+SET ROLE regress_vacuum;
+VACUUM vacowned_parted;
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+VACUUM vacowned_part1;
+VACUUM vacowned_part2;
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+ANALYZE vacowned_parted;
+WARNING: skipping "vacowned_part2" --- only table or database owner can analyze it
+ANALYZE vacowned_part1;
+ANALYZE vacowned_part2;
+WARNING: skipping "vacowned_part2" --- only table or database owner can analyze it
+VACUUM (ANALYZE) vacowned_parted;
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+VACUUM (ANALYZE) vacowned_part1;
+VACUUM (ANALYZE) vacowned_part2;
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+RESET ROLE;
+-- Only one partition owned by other user.
+ALTER TABLE vacowned_parted OWNER TO CURRENT_USER;
+SET ROLE regress_vacuum;
+VACUUM vacowned_parted;
+WARNING: skipping "vacowned_parted" --- only table or database owner can vacuum it
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+VACUUM vacowned_part1;
+VACUUM vacowned_part2;
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+ANALYZE vacowned_parted;
+WARNING: skipping "vacowned_parted" --- only table or database owner can analyze it
+WARNING: skipping "vacowned_part2" --- only table or database owner can analyze it
+ANALYZE vacowned_part1;
+ANALYZE vacowned_part2;
+WARNING: skipping "vacowned_part2" --- only table or database owner can analyze it
+VACUUM (ANALYZE) vacowned_parted;
+WARNING: skipping "vacowned_parted" --- only table or database owner can vacuum it
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+VACUUM (ANALYZE) vacowned_part1;
+VACUUM (ANALYZE) vacowned_part2;
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+RESET ROLE;
+-- Only partitioned table owned by other user.
+ALTER TABLE vacowned_parted OWNER TO regress_vacuum;
+ALTER TABLE vacowned_part1 OWNER TO CURRENT_USER;
+SET ROLE regress_vacuum;
+VACUUM vacowned_parted;
+WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+VACUUM vacowned_part1;
+WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it
+VACUUM vacowned_part2;
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+ANALYZE vacowned_parted;
+WARNING: skipping "vacowned_part1" --- only table or database owner can analyze it
+WARNING: skipping "vacowned_part2" --- only table or database owner can analyze it
+ANALYZE vacowned_part1;
+WARNING: skipping "vacowned_part1" --- only table or database owner can analyze it
+ANALYZE vacowned_part2;
+WARNING: skipping "vacowned_part2" --- only table or database owner can analyze it
+VACUUM (ANALYZE) vacowned_parted;
+WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+VACUUM (ANALYZE) vacowned_part1;
+WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it
+VACUUM (ANALYZE) vacowned_part2;
+WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it
+RESET ROLE;
+DROP TABLE vacowned;
+DROP TABLE vacowned_parted;
+DROP ROLE regress_vacuum;
diff --git a/src/test/regress/expected/vacuum_parallel.out b/src/test/regress/expected/vacuum_parallel.out
new file mode 100644
index 0000000..ddf0ee5
--- /dev/null
+++ b/src/test/regress/expected/vacuum_parallel.out
@@ -0,0 +1,49 @@
+SET max_parallel_maintenance_workers TO 4;
+SET min_parallel_index_scan_size TO '128kB';
+-- Bug #17245: Make sure that we don't totally fail to VACUUM individual indexes that
+-- happen to be below min_parallel_index_scan_size during parallel VACUUM:
+CREATE TABLE parallel_vacuum_table (a int) WITH (autovacuum_enabled = off);
+INSERT INTO parallel_vacuum_table SELECT i from generate_series(1, 10000) i;
+-- Parallel VACUUM will never be used unless there are at least two indexes
+-- that exceed min_parallel_index_scan_size. Create two such indexes, and
+-- a third index that is smaller than min_parallel_index_scan_size.
+CREATE INDEX regular_sized_index ON parallel_vacuum_table(a);
+CREATE INDEX typically_sized_index ON parallel_vacuum_table(a);
+-- Note: vacuum_in_leader_small_index can apply deduplication, making it ~3x
+-- smaller than the other indexes
+CREATE INDEX vacuum_in_leader_small_index ON parallel_vacuum_table((1));
+-- Verify (as best we can) that the cost model for parallel VACUUM
+-- will make our VACUUM run in parallel, while always leaving it up to the
+-- parallel leader to handle the vacuum_in_leader_small_index index:
+SELECT EXISTS (
+SELECT 1
+FROM pg_class
+WHERE oid = 'vacuum_in_leader_small_index'::regclass AND
+ pg_relation_size(oid) <
+ pg_size_bytes(current_setting('min_parallel_index_scan_size'))
+) as leader_will_handle_small_index;
+ leader_will_handle_small_index
+--------------------------------
+ t
+(1 row)
+
+SELECT count(*) as trigger_parallel_vacuum_nindexes
+FROM pg_class
+WHERE oid in ('regular_sized_index'::regclass, 'typically_sized_index'::regclass) AND
+ pg_relation_size(oid) >=
+ pg_size_bytes(current_setting('min_parallel_index_scan_size'));
+ trigger_parallel_vacuum_nindexes
+----------------------------------
+ 2
+(1 row)
+
+-- Parallel VACUUM with B-Tree page deletions, ambulkdelete calls:
+DELETE FROM parallel_vacuum_table;
+VACUUM (PARALLEL 4, INDEX_CLEANUP ON) parallel_vacuum_table;
+-- Since vacuum_in_leader_small_index uses deduplication, we expect an
+-- assertion failure with bug #17245 (in the absence of bugfix):
+INSERT INTO parallel_vacuum_table SELECT i FROM generate_series(1, 10000) i;
+RESET max_parallel_maintenance_workers;
+RESET min_parallel_index_scan_size;
+-- Deliberately don't drop table, to get further coverage from tools like
+-- pg_amcheck in some testing scenarios
diff --git a/src/test/regress/expected/varchar.out b/src/test/regress/expected/varchar.out
new file mode 100644
index 0000000..da23ae8
--- /dev/null
+++ b/src/test/regress/expected/varchar.out
@@ -0,0 +1,111 @@
+--
+-- VARCHAR
+--
+CREATE TABLE VARCHAR_TBL(f1 varchar(1));
+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 * FROM VARCHAR_TBL;
+ f1
+----
+ a
+ A
+ 1
+ 2
+ 3
+
+ c
+(7 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 <> 'a';
+ f1
+----
+ A
+ 1
+ 2
+ 3
+
+ c
+(6 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 = 'a';
+ f1
+----
+ a
+(1 row)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 < 'a';
+ f1
+----
+ A
+ 1
+ 2
+ 3
+
+(5 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 <= 'a';
+ f1
+----
+ a
+ A
+ 1
+ 2
+ 3
+
+(6 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 > 'a';
+ f1
+----
+ c
+(1 row)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 >= 'a';
+ f1
+----
+ a
+ c
+(2 rows)
+
+DROP TABLE VARCHAR_TBL;
+--
+-- Now test longer arrays of char
+--
+CREATE TABLE VARCHAR_TBL(f1 varchar(4));
+INSERT INTO VARCHAR_TBL (f1) VALUES ('a');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('ab');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('abcd');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('abcde');
+ERROR: value too long for type character varying(4)
+INSERT INTO VARCHAR_TBL (f1) VALUES ('abcd ');
+SELECT * FROM VARCHAR_TBL;
+ f1
+------
+ a
+ ab
+ abcd
+ abcd
+(4 rows)
+
diff --git a/src/test/regress/expected/varchar_1.out b/src/test/regress/expected/varchar_1.out
new file mode 100644
index 0000000..958f9c0
--- /dev/null
+++ b/src/test/regress/expected/varchar_1.out
@@ -0,0 +1,111 @@
+--
+-- VARCHAR
+--
+CREATE TABLE VARCHAR_TBL(f1 varchar(1));
+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 * FROM VARCHAR_TBL;
+ f1
+----
+ a
+ A
+ 1
+ 2
+ 3
+
+ c
+(7 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 <> 'a';
+ f1
+----
+ A
+ 1
+ 2
+ 3
+
+ c
+(6 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 = 'a';
+ f1
+----
+ a
+(1 row)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 < 'a';
+ f1
+----
+ 1
+ 2
+ 3
+
+(4 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 <= 'a';
+ f1
+----
+ a
+ 1
+ 2
+ 3
+
+(5 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 > 'a';
+ f1
+----
+ A
+ c
+(2 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 >= 'a';
+ f1
+----
+ a
+ A
+ c
+(3 rows)
+
+DROP TABLE VARCHAR_TBL;
+--
+-- Now test longer arrays of char
+--
+CREATE TABLE VARCHAR_TBL(f1 varchar(4));
+INSERT INTO VARCHAR_TBL (f1) VALUES ('a');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('ab');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('abcd');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('abcde');
+ERROR: value too long for type character varying(4)
+INSERT INTO VARCHAR_TBL (f1) VALUES ('abcd ');
+SELECT * FROM VARCHAR_TBL;
+ f1
+------
+ a
+ ab
+ abcd
+ abcd
+(4 rows)
+
diff --git a/src/test/regress/expected/varchar_2.out b/src/test/regress/expected/varchar_2.out
new file mode 100644
index 0000000..b1d412c
--- /dev/null
+++ b/src/test/regress/expected/varchar_2.out
@@ -0,0 +1,111 @@
+--
+-- VARCHAR
+--
+CREATE TABLE VARCHAR_TBL(f1 varchar(1));
+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 * FROM VARCHAR_TBL;
+ f1
+----
+ a
+ A
+ 1
+ 2
+ 3
+
+ c
+(7 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 <> 'a';
+ f1
+----
+ A
+ 1
+ 2
+ 3
+
+ c
+(6 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 = 'a';
+ f1
+----
+ a
+(1 row)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 < 'a';
+ f1
+----
+
+(1 row)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 <= 'a';
+ f1
+----
+ a
+
+(2 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 > 'a';
+ f1
+----
+ A
+ 1
+ 2
+ 3
+ c
+(5 rows)
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 >= 'a';
+ f1
+----
+ a
+ A
+ 1
+ 2
+ 3
+ c
+(6 rows)
+
+DROP TABLE VARCHAR_TBL;
+--
+-- Now test longer arrays of char
+--
+CREATE TABLE VARCHAR_TBL(f1 varchar(4));
+INSERT INTO VARCHAR_TBL (f1) VALUES ('a');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('ab');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('abcd');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('abcde');
+ERROR: value too long for type character varying(4)
+INSERT INTO VARCHAR_TBL (f1) VALUES ('abcd ');
+SELECT * FROM VARCHAR_TBL;
+ f1
+------
+ a
+ ab
+ abcd
+ abcd
+(4 rows)
+
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
new file mode 100644
index 0000000..19e2ac5
--- /dev/null
+++ b/src/test/regress/expected/window.out
@@ -0,0 +1,4065 @@
+--
+-- 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;
+ depname | empno | salary | rank
+-----------+-------+--------+------
+ develop | 7 | 4200 | 1
+ develop | 9 | 4500 | 2
+ develop | 11 | 5200 | 3
+ develop | 10 | 5200 | 3
+ develop | 8 | 6000 | 5
+ personnel | 5 | 3500 | 1
+ personnel | 2 | 3900 | 2
+ sales | 3 | 4800 | 1
+ sales | 4 | 4800 | 1
+ sales | 1 | 5000 | 3
+(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);
+ depname | empno | salary | sum
+-----------+-------+--------+-------
+ develop | 11 | 5200 | 25100
+ develop | 7 | 4200 | 25100
+ develop | 9 | 4500 | 25100
+ develop | 8 | 6000 | 25100
+ develop | 10 | 5200 | 25100
+ personnel | 5 | 3500 | 7400
+ personnel | 2 | 3900 | 7400
+ sales | 3 | 4800 | 14600
+ sales | 1 | 5000 | 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;
+ 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 ();
+ 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;
+ sum_1 | ten | four
+-------+-----+------
+ 0 | 0 | 0
+ 0 | 0 | 0
+ 2 | 0 | 2
+ 3 | 1 | 3
+ 4 | 1 | 1
+ 5 | 1 | 1
+ 3 | 3 | 3
+ 0 | 4 | 0
+ 1 | 7 | 1
+ 1 | 9 | 1
+(10 rows)
+
+SELECT row_number() OVER (ORDER BY unique2) FROM tenk1 WHERE unique2 < 10;
+ 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;
+ rank_1 | ten | four
+--------+-----+------
+ 1 | 0 | 0
+ 1 | 0 | 0
+ 3 | 4 | 0
+ 1 | 1 | 1
+ 1 | 1 | 1
+ 3 | 7 | 1
+ 4 | 9 | 1
+ 1 | 0 | 2
+ 1 | 1 | 3
+ 2 | 3 | 3
+(10 rows)
+
+SELECT dense_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+ dense_rank | ten | four
+------------+-----+------
+ 1 | 0 | 0
+ 1 | 0 | 0
+ 2 | 4 | 0
+ 1 | 1 | 1
+ 1 | 1 | 1
+ 2 | 7 | 1
+ 3 | 9 | 1
+ 1 | 0 | 2
+ 1 | 1 | 3
+ 2 | 3 | 3
+(10 rows)
+
+SELECT percent_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+ percent_rank | ten | four
+--------------------+-----+------
+ 0 | 0 | 0
+ 0 | 0 | 0
+ 1 | 4 | 0
+ 0 | 1 | 1
+ 0 | 1 | 1
+ 0.6666666666666666 | 7 | 1
+ 1 | 9 | 1
+ 0 | 0 | 2
+ 0 | 1 | 3
+ 1 | 3 | 3
+(10 rows)
+
+SELECT cume_dist() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+ cume_dist | ten | four
+--------------------+-----+------
+ 0.6666666666666666 | 0 | 0
+ 0.6666666666666666 | 0 | 0
+ 1 | 4 | 0
+ 0.5 | 1 | 1
+ 0.5 | 1 | 1
+ 0.75 | 7 | 1
+ 1 | 9 | 1
+ 1 | 0 | 2
+ 0.5 | 1 | 3
+ 1 | 3 | 3
+(10 rows)
+
+SELECT ntile(3) OVER (ORDER BY ten, four), ten, four FROM tenk1 WHERE unique2 < 10;
+ 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;
+ lag | ten | four
+-----+-----+------
+ | 0 | 0
+ 0 | 0 | 0
+ 0 | 4 | 0
+ | 1 | 1
+ 1 | 1 | 1
+ 1 | 7 | 1
+ 7 | 9 | 1
+ | 0 | 2
+ | 1 | 3
+ 1 | 3 | 3
+(10 rows)
+
+SELECT lag(ten, four) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+ lag | ten | four
+-----+-----+------
+ 0 | 0 | 0
+ 0 | 0 | 0
+ 4 | 4 | 0
+ | 1 | 1
+ 1 | 1 | 1
+ 1 | 7 | 1
+ 7 | 9 | 1
+ | 0 | 2
+ | 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;
+ lag | ten | four
+-----+-----+------
+ 0 | 0 | 0
+ 0 | 0 | 0
+ 4 | 4 | 0
+ 0 | 1 | 1
+ 1 | 1 | 1
+ 1 | 7 | 1
+ 7 | 9 | 1
+ 0 | 0 | 2
+ 0 | 1 | 3
+ 0 | 3 | 3
+(10 rows)
+
+SELECT lag(ten, four, 0.7) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten;
+ lag | ten | four
+-----+-----+------
+ 0 | 0 | 0
+ 0 | 0 | 0
+ 4 | 4 | 0
+ 0.7 | 1 | 1
+ 1 | 1 | 1
+ 1 | 7 | 1
+ 7 | 9 | 1
+ 0.7 | 0 | 2
+ 0.7 | 1 | 3
+ 0.7 | 3 | 3
+(10 rows)
+
+SELECT lead(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+ lead | ten | four
+------+-----+------
+ 0 | 0 | 0
+ 4 | 0 | 0
+ | 4 | 0
+ 1 | 1 | 1
+ 7 | 1 | 1
+ 9 | 7 | 1
+ | 9 | 1
+ | 0 | 2
+ 3 | 1 | 3
+ | 3 | 3
+(10 rows)
+
+SELECT lead(ten * 2, 1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+ lead | ten | four
+------+-----+------
+ 0 | 0 | 0
+ 8 | 0 | 0
+ | 4 | 0
+ 2 | 1 | 1
+ 14 | 1 | 1
+ 18 | 7 | 1
+ | 9 | 1
+ | 0 | 2
+ 6 | 1 | 3
+ | 3 | 3
+(10 rows)
+
+SELECT lead(ten * 2, 1, -1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+ lead | ten | four
+------+-----+------
+ 0 | 0 | 0
+ 8 | 0 | 0
+ -1 | 4 | 0
+ 2 | 1 | 1
+ 14 | 1 | 1
+ 18 | 7 | 1
+ -1 | 9 | 1
+ -1 | 0 | 2
+ 6 | 1 | 3
+ -1 | 3 | 3
+(10 rows)
+
+SELECT lead(ten * 2, 1, -1.4) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten;
+ lead | ten | four
+------+-----+------
+ 0 | 0 | 0
+ 8 | 0 | 0
+ -1.4 | 4 | 0
+ 2 | 1 | 1
+ 14 | 1 | 1
+ 18 | 7 | 1
+ -1.4 | 9 | 1
+ -1.4 | 0 | 2
+ 6 | 1 | 3
+ -1.4 | 3 | 3
+(10 rows)
+
+SELECT first_value(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+ first_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 | 0 | 2
+ 1 | 1 | 3
+ 1 | 3 | 3
+(10 rows)
+
+-- last_value returns the last row of the frame, which is CURRENT ROW in ORDER BY window.
+SELECT last_value(four) OVER (ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+ last_value | ten | four
+------------+-----+------
+ 0 | 0 | 0
+ 0 | 0 | 2
+ 0 | 0 | 0
+ 1 | 1 | 1
+ 1 | 1 | 3
+ 1 | 1 | 1
+ 3 | 3 | 3
+ 0 | 4 | 0
+ 1 | 7 | 1
+ 1 | 9 | 1
+(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;
+ count | four
+-------+------
+ 4 | 1
+ 4 | 1
+ 4 | 1
+ 4 | 1
+ 2 | 3
+ 2 | 3
+(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;
+ cntsum
+--------
+ 22
+ 22
+ 87
+ 24
+ 24
+ 82
+ 92
+ 51
+ 92
+ 136
+(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;
+ 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);
+ 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)
+
+-- 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);
+ 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;
+ lead
+------
+ 0
+ 0
+ 4
+ 1
+ 7
+ 9
+
+ 0
+ 3
+
+(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);
+ sum | rank
+-------+------
+ 6000 | 1
+ 16400 | 2
+ 16400 | 2
+ 20900 | 4
+ 25100 | 5
+ 3900 | 1
+ 7400 | 2
+ 5000 | 1
+ 14600 | 2
+ 14600 | 2
+(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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ sum | unique1 | four
+-----+---------+------
+ 45 | 0 | 0
+ 45 | 8 | 0
+ 45 | 4 | 0
+ 33 | 5 | 1
+ 33 | 9 | 1
+ 33 | 1 | 1
+ 18 | 6 | 2
+ 18 | 2 | 2
+ 10 | 3 | 3
+ 10 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (rows between current row and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 45 | 4 | 0
+ 41 | 2 | 2
+ 39 | 1 | 1
+ 38 | 6 | 2
+ 32 | 9 | 1
+ 23 | 8 | 0
+ 15 | 5 | 1
+ 10 | 3 | 3
+ 7 | 7 | 3
+ 0 | 0 | 0
+(10 rows)
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 7 | 4 | 0
+ 13 | 2 | 2
+ 22 | 1 | 1
+ 26 | 6 | 2
+ 29 | 9 | 1
+ 31 | 8 | 0
+ 32 | 5 | 1
+ 23 | 3 | 3
+ 15 | 7 | 3
+ 10 | 0 | 0
+(10 rows)
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude no others),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 7 | 4 | 0
+ 13 | 2 | 2
+ 22 | 1 | 1
+ 26 | 6 | 2
+ 29 | 9 | 1
+ 31 | 8 | 0
+ 32 | 5 | 1
+ 23 | 3 | 3
+ 15 | 7 | 3
+ 10 | 0 | 0
+(10 rows)
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude current row),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 3 | 4 | 0
+ 11 | 2 | 2
+ 21 | 1 | 1
+ 20 | 6 | 2
+ 20 | 9 | 1
+ 23 | 8 | 0
+ 27 | 5 | 1
+ 20 | 3 | 3
+ 8 | 7 | 3
+ 10 | 0 | 0
+(10 rows)
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ | 4 | 0
+ | 2 | 2
+ | 1 | 1
+ | 6 | 2
+ | 9 | 1
+ | 8 | 0
+ | 5 | 1
+ | 3 | 3
+ | 7 | 3
+ | 0 | 0
+(10 rows)
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude ties),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 4 | 4 | 0
+ 2 | 2 | 2
+ 1 | 1 | 1
+ 6 | 6 | 2
+ 9 | 9 | 1
+ 8 | 8 | 0
+ 5 | 5 | 1
+ 3 | 3 | 3
+ 7 | 7 | 3
+ 0 | 0 | 0
+(10 rows)
+
+SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 following exclude current row),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ first_value | unique1 | four
+-------------+---------+------
+ 8 | 0 | 0
+ 4 | 8 | 0
+ 5 | 4 | 0
+ 9 | 5 | 1
+ 1 | 9 | 1
+ 6 | 1 | 1
+ 2 | 6 | 2
+ 3 | 2 | 2
+ 7 | 3 | 3
+ | 7 | 3
+(10 rows)
+
+SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 following exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ first_value | unique1 | four
+-------------+---------+------
+ | 0 | 0
+ 5 | 8 | 0
+ 5 | 4 | 0
+ | 5 | 1
+ 6 | 9 | 1
+ 6 | 1 | 1
+ 3 | 6 | 2
+ 3 | 2 | 2
+ | 3 | 3
+ | 7 | 3
+(10 rows)
+
+SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 following exclude ties),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ first_value | unique1 | four
+-------------+---------+------
+ 0 | 0 | 0
+ 8 | 8 | 0
+ 4 | 4 | 0
+ 5 | 5 | 1
+ 9 | 9 | 1
+ 1 | 1 | 1
+ 6 | 6 | 2
+ 2 | 2 | 2
+ 3 | 3 | 3
+ 7 | 7 | 3
+(10 rows)
+
+SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 following exclude current row),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ last_value | unique1 | four
+------------+---------+------
+ 4 | 0 | 0
+ 5 | 8 | 0
+ 9 | 4 | 0
+ 1 | 5 | 1
+ 6 | 9 | 1
+ 2 | 1 | 1
+ 3 | 6 | 2
+ 7 | 2 | 2
+ 7 | 3 | 3
+ | 7 | 3
+(10 rows)
+
+SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 following exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ last_value | unique1 | four
+------------+---------+------
+ | 0 | 0
+ 5 | 8 | 0
+ 9 | 4 | 0
+ | 5 | 1
+ 6 | 9 | 1
+ 2 | 1 | 1
+ 3 | 6 | 2
+ 7 | 2 | 2
+ | 3 | 3
+ | 7 | 3
+(10 rows)
+
+SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 following exclude ties),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ last_value | unique1 | four
+------------+---------+------
+ 0 | 0 | 0
+ 5 | 8 | 0
+ 9 | 4 | 0
+ 5 | 5 | 1
+ 6 | 9 | 1
+ 2 | 1 | 1
+ 3 | 6 | 2
+ 7 | 2 | 2
+ 3 | 3 | 3
+ 7 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (rows between 2 preceding and 1 preceding),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ | 4 | 0
+ 4 | 2 | 2
+ 6 | 1 | 1
+ 3 | 6 | 2
+ 7 | 9 | 1
+ 15 | 8 | 0
+ 17 | 5 | 1
+ 13 | 3 | 3
+ 8 | 7 | 3
+ 10 | 0 | 0
+(10 rows)
+
+SELECT sum(unique1) over (rows between 1 following and 3 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 9 | 4 | 0
+ 16 | 2 | 2
+ 23 | 1 | 1
+ 22 | 6 | 2
+ 16 | 9 | 1
+ 15 | 8 | 0
+ 10 | 5 | 1
+ 7 | 3 | 3
+ 0 | 7 | 3
+ | 0 | 0
+(10 rows)
+
+SELECT sum(unique1) over (rows between unbounded preceding and 1 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 6 | 4 | 0
+ 7 | 2 | 2
+ 13 | 1 | 1
+ 22 | 6 | 2
+ 30 | 9 | 1
+ 35 | 8 | 0
+ 38 | 5 | 1
+ 45 | 3 | 3
+ 45 | 7 | 3
+ 45 | 0 | 0
+(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);
+ sum | unique1 | four
+-----+---------+------
+ 45 | 0 | 0
+ 45 | 8 | 0
+ 45 | 4 | 0
+ 33 | 5 | 1
+ 33 | 9 | 1
+ 33 | 1 | 1
+ 18 | 6 | 2
+ 18 | 2 | 2
+ 10 | 3 | 3
+ 10 | 7 | 3
+(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);
+ sum | unique1 | four
+-----+---------+------
+ 12 | 0 | 0
+ 4 | 8 | 0
+ 8 | 4 | 0
+ 22 | 5 | 1
+ 18 | 9 | 1
+ 26 | 1 | 1
+ 29 | 6 | 2
+ 33 | 2 | 2
+ 42 | 3 | 3
+ 38 | 7 | 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);
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 35 | 3 | 3
+ 35 | 7 | 3
+(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);
+ sum | unique1 | four
+-----+---------+------
+ 0 | 0 | 0
+ 8 | 8 | 0
+ 4 | 4 | 0
+ 17 | 5 | 1
+ 21 | 9 | 1
+ 13 | 1 | 1
+ 33 | 6 | 2
+ 29 | 2 | 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 four range between current row and unbounded following);
+ first_value | nth_2 | last_value | unique1 | four
+-------------+-------+------------+---------+------
+ 0 | 8 | 7 | 0 | 0
+ 0 | 8 | 7 | 8 | 0
+ 0 | 8 | 7 | 4 | 0
+ 5 | 9 | 7 | 5 | 1
+ 5 | 9 | 7 | 9 | 1
+ 5 | 9 | 7 | 1 | 1
+ 6 | 2 | 7 | 6 | 2
+ 6 | 2 | 7 | 2 | 2
+ 3 | 7 | 7 | 3 | 3
+ 3 | 7 | 7 | 7 | 3
+(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;
+ 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)
+
+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)
+
+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)
+
+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)
+
+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)
+
+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;
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(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;
+ sum | unique1 | four
+-----+---------+------
+ | 3 | 3
+ | 7 | 3
+ 10 | 6 | 2
+ 10 | 2 | 2
+ 18 | 9 | 1
+ 18 | 5 | 1
+ 18 | 1 | 1
+ 23 | 0 | 0
+ 23 | 8 | 0
+ 23 | 4 | 0
+(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;
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(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;
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(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;
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(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;
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(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;
+ sum | unique1 | four
+-----+---------+------
+ 33 | 0 | 0
+ 41 | 8 | 0
+ 37 | 4 | 0
+ 35 | 5 | 1
+ 39 | 9 | 1
+ 31 | 1 | 1
+ 43 | 6 | 2
+ 39 | 2 | 2
+ 26 | 3 | 3
+ 30 | 7 | 3
+(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;
+ sum | unique1 | four
+-----+---------+------
+ 33 | 0 | 0
+ 33 | 8 | 0
+ 33 | 4 | 0
+ 30 | 5 | 1
+ 30 | 9 | 1
+ 30 | 1 | 1
+ 37 | 6 | 2
+ 37 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(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;
+ sum | unique1 | four
+-----+---------+------
+ 4 | 0 | 0
+ 12 | 4 | 0
+ 12 | 8 | 0
+ 6 | 1 | 1
+ 15 | 5 | 1
+ 14 | 9 | 1
+ 8 | 2 | 2
+ 8 | 6 | 2
+ 10 | 3 | 3
+ 10 | 7 | 3
+(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;
+ sum | unique1 | four
+-----+---------+------
+ 4 | 0 | 0
+ 8 | 4 | 0
+ 4 | 8 | 0
+ 5 | 1 | 1
+ 10 | 5 | 1
+ 5 | 9 | 1
+ 6 | 2 | 2
+ 2 | 6 | 2
+ 7 | 3 | 3
+ 3 | 7 | 3
+(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;
+ sum | salary | enroll_date
+-------+--------+-------------
+ 34900 | 5000 | 10-01-2006
+ 34900 | 6000 | 10-01-2006
+ 38400 | 3900 | 12-23-2006
+ 47100 | 4800 | 08-01-2007
+ 47100 | 5200 | 08-01-2007
+ 47100 | 4800 | 08-08-2007
+ 47100 | 5200 | 08-15-2007
+ 36100 | 3500 | 12-10-2007
+ 32200 | 4500 | 01-01-2008
+ 32200 | 4200 | 01-01-2008
+(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;
+ sum | salary | enroll_date
+-------+--------+-------------
+ 32200 | 4200 | 01-01-2008
+ 32200 | 4500 | 01-01-2008
+ 36100 | 3500 | 12-10-2007
+ 47100 | 5200 | 08-15-2007
+ 47100 | 4800 | 08-08-2007
+ 47100 | 4800 | 08-01-2007
+ 47100 | 5200 | 08-01-2007
+ 38400 | 3900 | 12-23-2006
+ 34900 | 5000 | 10-01-2006
+ 34900 | 6000 | 10-01-2006
+(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;
+ sum | salary | enroll_date
+-----+--------+-------------
+ | 4200 | 01-01-2008
+ | 4500 | 01-01-2008
+ | 3500 | 12-10-2007
+ | 5200 | 08-15-2007
+ | 4800 | 08-08-2007
+ | 4800 | 08-01-2007
+ | 5200 | 08-01-2007
+ | 3900 | 12-23-2006
+ | 5000 | 10-01-2006
+ | 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;
+ sum | salary | enroll_date
+-------+--------+-------------
+ 29900 | 5000 | 10-01-2006
+ 28900 | 6000 | 10-01-2006
+ 34500 | 3900 | 12-23-2006
+ 42300 | 4800 | 08-01-2007
+ 41900 | 5200 | 08-01-2007
+ 42300 | 4800 | 08-08-2007
+ 41900 | 5200 | 08-15-2007
+ 32600 | 3500 | 12-10-2007
+ 27700 | 4500 | 01-01-2008
+ 28000 | 4200 | 01-01-2008
+(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;
+ sum | salary | enroll_date
+-------+--------+-------------
+ 23900 | 5000 | 10-01-2006
+ 23900 | 6000 | 10-01-2006
+ 34500 | 3900 | 12-23-2006
+ 37100 | 4800 | 08-01-2007
+ 37100 | 5200 | 08-01-2007
+ 42300 | 4800 | 08-08-2007
+ 41900 | 5200 | 08-15-2007
+ 32600 | 3500 | 12-10-2007
+ 23500 | 4500 | 01-01-2008
+ 23500 | 4200 | 01-01-2008
+(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;
+ sum | salary | enroll_date
+-------+--------+-------------
+ 28900 | 5000 | 10-01-2006
+ 29900 | 6000 | 10-01-2006
+ 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
+ 36100 | 3500 | 12-10-2007
+ 28000 | 4500 | 01-01-2008
+ 27700 | 4200 | 01-01-2008
+(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;
+ 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;
+ 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;
+ 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;
+ last_value | lag | salary
+------------+------+--------
+ 6000 | | 3500
+ 6000 | 3500 | 3900
+ 6000 | 3900 | 4200
+ 6000 | 4200 | 4500
+ 6000 | 4500 | 4800
+ 6000 | 4800 | 4800
+ 6000 | 4800 | 5000
+ | 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;
+ first_value | last_value | salary | enroll_date
+-------------+------------+--------+-------------
+ 5000 | 5200 | 5000 | 10-01-2006
+ 6000 | 5200 | 6000 | 10-01-2006
+ 5000 | 3500 | 3900 | 12-23-2006
+ 5000 | 4200 | 4800 | 08-01-2007
+ 5000 | 4200 | 5200 | 08-01-2007
+ 5000 | 4200 | 4800 | 08-08-2007
+ 5000 | 4200 | 5200 | 08-15-2007
+ 5000 | 4200 | 3500 | 12-10-2007
+ 5000 | 4200 | 4500 | 01-01-2008
+ 5000 | 4200 | 4200 | 01-01-2008
+(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;
+ first_value | last_value | salary | enroll_date
+-------------+------------+--------+-------------
+ 5000 | 5200 | 5000 | 10-01-2006
+ 6000 | 5200 | 6000 | 10-01-2006
+ 5000 | 3500 | 3900 | 12-23-2006
+ 5000 | 4200 | 4800 | 08-01-2007
+ 5000 | 4200 | 5200 | 08-01-2007
+ 5000 | 4200 | 4800 | 08-08-2007
+ 5000 | 4200 | 5200 | 08-15-2007
+ 5000 | 4200 | 3500 | 12-10-2007
+ 5000 | 4500 | 4500 | 01-01-2008
+ 5000 | 4200 | 4200 | 01-01-2008
+(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;
+ 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 | 4200 | 4800 | 08-01-2007
+ 5000 | 4200 | 5200 | 08-01-2007
+ 5000 | 4200 | 4800 | 08-08-2007
+ 5000 | 4200 | 5200 | 08-15-2007
+ 5000 | 4200 | 3500 | 12-10-2007
+ 5000 | 3500 | 4500 | 01-01-2008
+ 5000 | 3500 | 4200 | 01-01-2008
+(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;
+ first_value | last_value | salary | enroll_date
+-------------+------------+--------+-------------
+ 6000 | 5200 | 5000 | 10-01-2006
+ 5000 | 5200 | 6000 | 10-01-2006
+ 5000 | 3500 | 3900 | 12-23-2006
+ 5000 | 4200 | 4800 | 08-01-2007
+ 5000 | 4200 | 5200 | 08-01-2007
+ 5000 | 4200 | 4800 | 08-08-2007
+ 5000 | 4200 | 5200 | 08-15-2007
+ 5000 | 4200 | 3500 | 12-10-2007
+ 5000 | 4200 | 4500 | 01-01-2008
+ 5000 | 4500 | 4200 | 01-01-2008
+(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);
+ x | y | first_value | last_value
+---+----+-------------+------------
+ | 42 | 42 | 43
+ | 43 | 42 | 43
+ 1 | 1 | 1 | 3
+ 2 | 2 | 1 | 4
+ 3 | 3 | 1 | 5
+ 4 | 4 | 2 | 5
+ 5 | 5 | 3 | 5
+(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);
+ 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);
+ x | y | first_value | last_value
+---+----+-------------+------------
+ | 43 | 43 | 42
+ | 42 | 43 | 42
+ 5 | 5 | 5 | 3
+ 4 | 4 | 5 | 2
+ 3 | 3 | 5 | 1
+ 2 | 2 | 4 | 1
+ 1 | 1 | 3 | 1
+(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);
+ x | y | first_value | last_value
+---+----+-------------+------------
+ 5 | 5 | 5 | 3
+ 4 | 4 | 5 | 2
+ 3 | 3 | 5 | 1
+ 2 | 2 | 4 | 1
+ 1 | 1 | 3 | 1
+ | 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;
+ 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;
+ x | last_value
+--------+------------
+ -32764 | -32766
+ -32765 | -32766
+ -32766 | -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;
+ 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;
+ x | last_value
+-------------+-------------
+ -2147483644 | -2147483646
+ -2147483645 | -2147483646
+ -2147483646 | -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;
+ 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;
+ x | last_value
+----------------------+----------------------
+ -9223372036854775804 | -9223372036854775806
+ -9223372036854775805 | -9223372036854775806
+ -9223372036854775806 | -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', '-infinity'),
+(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', 'infinity'),
+(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);
+ 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);
+ 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);
+ 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
+ 'inf' preceding and 'inf' preceding);
+ id | f_float4 | first_value | last_value
+----+-----------+-------------+------------
+ 0 | -Infinity | 0 | 0
+ 1 | -3 | 0 | 0
+ 2 | -1 | 0 | 0
+ 3 | 0 | 0 | 0
+ 4 | 1.1 | 0 | 0
+ 5 | 1.12 | 0 | 0
+ 6 | 2 | 0 | 0
+ 7 | 100 | 0 | 0
+ 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
+ 'inf' following and 'inf' following);
+ id | f_float4 | first_value | last_value
+----+-----------+-------------+------------
+ 0 | -Infinity | 0 | 8
+ 1 | -3 | 8 | 8
+ 2 | -1 | 8 | 8
+ 3 | 0 | 8 | 8
+ 4 | 1.1 | 8 | 8
+ 5 | 1.12 | 8 | 8
+ 6 | 2 | 8 | 8
+ 7 | 100 | 8 | 8
+ 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.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);
+ 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);
+ 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);
+ 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
+ 'inf' preceding and 'inf' preceding);
+ id | f_float8 | first_value | last_value
+----+-----------+-------------+------------
+ 0 | -Infinity | 0 | 0
+ 1 | -3 | 0 | 0
+ 2 | -1 | 0 | 0
+ 3 | 0 | 0 | 0
+ 4 | 1.1 | 0 | 0
+ 5 | 1.12 | 0 | 0
+ 6 | 2 | 0 | 0
+ 7 | 100 | 0 | 0
+ 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
+ 'inf' following and 'inf' following);
+ id | f_float8 | first_value | last_value
+----+-----------+-------------+------------
+ 0 | -Infinity | 0 | 8
+ 1 | -3 | 8 | 8
+ 2 | -1 | 8 | 8
+ 3 | 0 | 8 | 8
+ 4 | 1.1 | 8 | 8
+ 5 | 1.12 | 8 | 8
+ 6 | 2 | 8 | 8
+ 7 | 100 | 8 | 8
+ 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.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);
+ id | f_numeric | 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_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);
+ id | f_numeric | 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_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
+ 'inf' preceding and 'inf' following);
+ id | f_numeric | 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_numeric, first_value(id) over w, last_value(id) over w
+from numerics
+window w as (order by f_numeric range between
+ 'inf' preceding and 'inf' preceding);
+ id | f_numeric | first_value | last_value
+----+-----------+-------------+------------
+ 0 | -Infinity | 0 | 0
+ 1 | -3 | 0 | 0
+ 2 | -1 | 0 | 0
+ 3 | 0 | 0 | 0
+ 4 | 1.1 | 0 | 0
+ 5 | 1.12 | 0 | 0
+ 6 | 2 | 0 | 0
+ 7 | 100 | 0 | 0
+ 8 | Infinity | 0 | 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
+ 'inf' following and 'inf' following);
+ id | f_numeric | first_value | last_value
+----+-----------+-------------+------------
+ 0 | -Infinity | 0 | 8
+ 1 | -3 | 8 | 8
+ 2 | -1 | 8 | 8
+ 3 | 0 | 8 | 8
+ 4 | 1.1 | 8 | 8
+ 5 | 1.12 | 8 | 8
+ 6 | 2 | 8 | 8
+ 7 | 100 | 8 | 8
+ 8 | Infinity | 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.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);
+ 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);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 10 | 20:00:00 | 10 | 8
+ 9 | 19:00:00 | 10 | 7
+ 8 | 18:00:00 | 9 | 7
+ 7 | 17:00:00 | 8 | 5
+ 6 | 15:00:00 | 6 | 3
+ 5 | 15:00:00 | 6 | 3
+ 4 | 14:00:00 | 6 | 2
+ 3 | 13:00:00 | 4 | 1
+ 2 | 12:00:00 | 3 | 1
+ 1 | 11:00:00 | 2 | 1
+(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);
+ 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);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 10 | 20:00:00+01 | 10 | 8
+ 9 | 19:00:00+01 | 10 | 7
+ 8 | 18:00:00+01 | 9 | 7
+ 7 | 17:00:00+01 | 8 | 5
+ 6 | 15:00:00+01 | 6 | 3
+ 5 | 15:00:00+01 | 6 | 3
+ 4 | 14:00:00+01 | 6 | 2
+ 3 | 13:00:00+01 | 4 | 1
+ 2 | 12:00:00+01 | 3 | 1
+ 1 | 11:00:00+01 | 2 | 1
+(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);
+ 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);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 10 | @ 10 years | 10 | 9
+ 9 | @ 9 years | 10 | 8
+ 8 | @ 8 years | 9 | 7
+ 7 | @ 7 years | 8 | 7
+ 6 | @ 5 years | 6 | 4
+ 5 | @ 5 years | 6 | 4
+ 4 | @ 4 years | 6 | 3
+ 3 | @ 3 years | 4 | 2
+ 2 | @ 2 years | 3 | 1
+ 1 | @ 1 year | 2 | 1
+(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);
+ 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);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 10 | 9
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 10 | 8
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 9 | 7
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 8 | 6
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 7 | 5
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 6 | 4
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 5 | 2
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
+(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);
+ 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);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 10 | Sun Oct 19 10:23:54 2008 | 10 | 9
+ 9 | Fri Oct 19 10:23:54 2007 | 10 | 8
+ 8 | Thu Oct 19 10:23:54 2006 | 9 | 7
+ 7 | Wed Oct 19 10:23:54 2005 | 8 | 6
+ 6 | Tue Oct 19 10:23:54 2004 | 7 | 5
+ 5 | Sun Oct 19 10:23:54 2003 | 6 | 4
+ 4 | Sat Oct 19 10:23:54 2002 | 5 | 2
+ 3 | Fri Oct 19 10:23:54 2001 | 4 | 1
+ 2 | Fri Oct 19 10:23:54 2001 | 4 | 1
+ 1 | Thu Oct 19 10:23:54 2000 | 3 | 1
+(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;
+ sum | unique1 | four
+-----+---------+------
+ 12 | 0 | 0
+ 12 | 8 | 0
+ 12 | 4 | 0
+ 27 | 5 | 1
+ 27 | 9 | 1
+ 27 | 1 | 1
+ 35 | 6 | 2
+ 35 | 2 | 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;
+ sum | unique1 | four
+-----+---------+------
+ 45 | 0 | 0
+ 45 | 8 | 0
+ 45 | 4 | 0
+ 45 | 5 | 1
+ 45 | 9 | 1
+ 45 | 1 | 1
+ 45 | 6 | 2
+ 45 | 2 | 2
+ 45 | 3 | 3
+ 45 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between current row and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 45 | 0 | 0
+ 45 | 8 | 0
+ 45 | 4 | 0
+ 33 | 5 | 1
+ 33 | 9 | 1
+ 33 | 1 | 1
+ 18 | 6 | 2
+ 18 | 2 | 2
+ 10 | 3 | 3
+ 10 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 1 preceding and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 45 | 0 | 0
+ 45 | 8 | 0
+ 45 | 4 | 0
+ 45 | 5 | 1
+ 45 | 9 | 1
+ 45 | 1 | 1
+ 33 | 6 | 2
+ 33 | 2 | 2
+ 18 | 3 | 3
+ 18 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 1 following and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 33 | 0 | 0
+ 33 | 8 | 0
+ 33 | 4 | 0
+ 18 | 5 | 1
+ 18 | 9 | 1
+ 18 | 1 | 1
+ 10 | 6 | 2
+ 10 | 2 | 2
+ | 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;
+ sum | unique1 | four
+-----+---------+------
+ 35 | 0 | 0
+ 35 | 8 | 0
+ 35 | 4 | 0
+ 45 | 5 | 1
+ 45 | 9 | 1
+ 45 | 1 | 1
+ 45 | 6 | 2
+ 45 | 2 | 2
+ 45 | 3 | 3
+ 45 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 preceding),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 27 | 0 | 0
+ 27 | 8 | 0
+ 27 | 4 | 0
+ 35 | 5 | 1
+ 35 | 9 | 1
+ 35 | 1 | 1
+ 45 | 6 | 2
+ 45 | 2 | 2
+ 33 | 3 | 3
+ 33 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 0 preceding and 0 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 12 | 0 | 0
+ 12 | 8 | 0
+ 12 | 4 | 0
+ 15 | 5 | 1
+ 15 | 9 | 1
+ 15 | 1 | 1
+ 8 | 6 | 2
+ 8 | 2 | 2
+ 10 | 3 | 3
+ 10 | 7 | 3
+(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;
+ sum | unique1 | four
+-----+---------+------
+ 27 | 0 | 0
+ 19 | 8 | 0
+ 23 | 4 | 0
+ 30 | 5 | 1
+ 26 | 9 | 1
+ 34 | 1 | 1
+ 39 | 6 | 2
+ 43 | 2 | 2
+ 30 | 3 | 3
+ 26 | 7 | 3
+(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;
+ sum | unique1 | four
+-----+---------+------
+ 15 | 0 | 0
+ 15 | 8 | 0
+ 15 | 4 | 0
+ 20 | 5 | 1
+ 20 | 9 | 1
+ 20 | 1 | 1
+ 37 | 6 | 2
+ 37 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(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;
+ sum | unique1 | four
+-----+---------+------
+ 15 | 0 | 0
+ 23 | 8 | 0
+ 19 | 4 | 0
+ 25 | 5 | 1
+ 29 | 9 | 1
+ 21 | 1 | 1
+ 43 | 6 | 2
+ 39 | 2 | 2
+ 26 | 3 | 3
+ 30 | 7 | 3
+(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;
+ 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;
+ 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;
+ 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;
+ 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;
+ first_value | lead | nth_value | salary | enroll_date
+-------------+------+-----------+--------+-------------
+ 5000 | 6000 | 5000 | 5000 | 10-01-2006
+ 5000 | 3900 | 5000 | 6000 | 10-01-2006
+ 5000 | 4800 | 5000 | 3900 | 12-23-2006
+ 3900 | 5200 | 3900 | 4800 | 08-01-2007
+ 3900 | 4800 | 3900 | 5200 | 08-01-2007
+ 4800 | 5200 | 4800 | 4800 | 08-08-2007
+ 4800 | 3500 | 4800 | 5200 | 08-15-2007
+ 5200 | 4500 | 5200 | 3500 | 12-10-2007
+ 3500 | 4200 | 3500 | 4500 | 01-01-2008
+ 3500 | | 3500 | 4200 | 01-01-2008
+(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;
+ last_value | lag | salary | enroll_date
+------------+------+--------+-------------
+ 3900 | | 5000 | 10-01-2006
+ 3900 | 5000 | 6000 | 10-01-2006
+ 5200 | 6000 | 3900 | 12-23-2006
+ 4800 | 3900 | 4800 | 08-01-2007
+ 4800 | 4800 | 5200 | 08-01-2007
+ 5200 | 5200 | 4800 | 08-08-2007
+ 3500 | 4800 | 5200 | 08-15-2007
+ 4200 | 5200 | 3500 | 12-10-2007
+ 4200 | 3500 | 4500 | 01-01-2008
+ 4200 | 4500 | 4200 | 01-01-2008
+(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;
+ first_value | lead | nth_value | salary | enroll_date
+-------------+------+-----------+--------+-------------
+ 3900 | 6000 | 3900 | 5000 | 10-01-2006
+ 3900 | 3900 | 3900 | 6000 | 10-01-2006
+ 4800 | 4800 | 4800 | 3900 | 12-23-2006
+ 4800 | 5200 | 4800 | 4800 | 08-01-2007
+ 4800 | 4800 | 4800 | 5200 | 08-01-2007
+ 5200 | 5200 | 5200 | 4800 | 08-08-2007
+ 3500 | 3500 | 3500 | 5200 | 08-15-2007
+ 4500 | 4500 | 4500 | 3500 | 12-10-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;
+ last_value | lag | salary | enroll_date
+------------+------+--------+-------------
+ 4800 | | 5000 | 10-01-2006
+ 4800 | 5000 | 6000 | 10-01-2006
+ 5200 | 6000 | 3900 | 12-23-2006
+ 3500 | 3900 | 4800 | 08-01-2007
+ 3500 | 4800 | 5200 | 08-01-2007
+ 4200 | 5200 | 4800 | 08-08-2007
+ 4200 | 4800 | 5200 | 08-15-2007
+ 4200 | 5200 | 3500 | 12-10-2007
+ | 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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;
+ QUERY PLAN
+---------------------------------
+ WindowAgg
+ -> Sort
+ Sort Key: f1
+ -> Seq Scan on t1
+ Filter: (f1 = f2)
+(5 rows)
+
+select f1, sum(f1) over (partition by f1 order by f2
+ range between 1 preceding and 1 following)
+from t1 where f1 = f2;
+ 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;
+ 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;
+ 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;
+ QUERY PLAN
+---------------------------------
+ WindowAgg
+ -> Sort
+ Sort Key: f1
+ -> Seq Scan on t1
+ Filter: (f1 = f2)
+(5 rows)
+
+select f1, sum(f1) over (partition by f1 order by f2
+ groups between 1 preceding and 1 following)
+from t1 where f1 = f2;
+ 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;
+ 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;
+ f1 | sum
+----+-----
+ 1 |
+ 2 |
+(2 rows)
+
+-- ordering by a non-integer constant is allowed
+SELECT rank() OVER (ORDER BY length('abc'));
+ 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;
+ sum | row_number | filtered_sum | depname
+-------+------------+--------------+-----------
+ 25100 | 1 | 22600 | develop
+ 7400 | 2 | 3500 | personnel
+ 14600 | 3 | | sales
+(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)
+
+-- Test Sort node collapsing
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+ (SELECT depname,
+ sum(salary) OVER (PARTITION BY depname order by empno) depsalary,
+ min(salary) OVER (PARTITION BY depname, empno order by enroll_date) depminsalary
+ FROM empsalary) emp
+WHERE depname = 'sales';
+ QUERY PLAN
+----------------------------------------------------------------------
+ Subquery Scan on emp
+ -> WindowAgg
+ -> WindowAgg
+ -> Sort
+ Sort Key: empsalary.empno, empsalary.enroll_date
+ -> Seq Scan on empsalary
+ Filter: ((depname)::text = 'sales'::text)
+(7 rows)
+
+-- Test Sort node reordering
+EXPLAIN (COSTS OFF)
+SELECT
+ lead(1) OVER (PARTITION BY depname ORDER BY salary, enroll_date),
+ lag(1) OVER (PARTITION BY depname ORDER BY salary,enroll_date,empno)
+FROM empsalary;
+ QUERY PLAN
+-------------------------------------------------------------
+ WindowAgg
+ -> WindowAgg
+ -> Sort
+ Sort Key: depname, salary, enroll_date, empno
+ -> Seq Scan on empsalary
+(5 rows)
+
+-- Test incremental sorting
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+ (SELECT depname,
+ empno,
+ salary,
+ enroll_date,
+ row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
+ row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+ FROM empsalary) emp
+WHERE first_emp = 1 OR last_emp = 1;
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Subquery Scan on emp
+ Filter: ((emp.first_emp = 1) OR (emp.last_emp = 1))
+ -> WindowAgg
+ -> Incremental Sort
+ Sort Key: empsalary.depname, empsalary.enroll_date
+ Presorted Key: empsalary.depname
+ -> WindowAgg
+ -> Sort
+ Sort Key: empsalary.depname, empsalary.enroll_date DESC
+ -> Seq Scan on empsalary
+(10 rows)
+
+SELECT * FROM
+ (SELECT depname,
+ empno,
+ salary,
+ enroll_date,
+ row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
+ row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+ FROM empsalary) emp
+WHERE first_emp = 1 OR last_emp = 1;
+ depname | empno | salary | enroll_date | first_emp | last_emp
+-----------+-------+--------+-------------+-----------+----------
+ develop | 8 | 6000 | 10-01-2006 | 1 | 5
+ develop | 7 | 4200 | 01-01-2008 | 5 | 1
+ personnel | 2 | 3900 | 12-23-2006 | 1 | 2
+ personnel | 5 | 3500 | 12-10-2007 | 2 | 1
+ sales | 1 | 5000 | 10-01-2006 | 1 | 3
+ sales | 4 | 4800 | 08-08-2007 | 3 | 1
+(6 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;
+ 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
+ | 0 | 2
+ 3 | 1 | 3
+ 3 | 3 | 3
+(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;
+ 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
+ 0 | 0 | 2
+ 1 | 1 | 3
+ 1 | 3 | 3
+(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)
+
+-- Tests for problems with failure to walk or mutate expressions
+-- within window frame clauses.
+-- test walker (fails with collation error if expressions are not walked)
+SELECT array_agg(i) OVER w
+ FROM generate_series(1,5) i
+WINDOW w AS (ORDER BY i ROWS BETWEEN (('foo' < 'foobar')::integer) PRECEDING AND CURRENT ROW);
+ array_agg
+-----------
+ {1}
+ {1,2}
+ {2,3}
+ {3,4}
+ {4,5}
+(5 rows)
+
+-- test mutator (fails when inlined if expressions are not mutated)
+CREATE FUNCTION pg_temp.f(group_size BIGINT) RETURNS SETOF integer[]
+AS $$
+ SELECT array_agg(s) OVER w
+ FROM generate_series(1,5) s
+ WINDOW w AS (ORDER BY s ROWS BETWEEN CURRENT ROW AND GROUP_SIZE FOLLOWING)
+$$ LANGUAGE SQL STABLE;
+EXPLAIN (costs off) SELECT * FROM pg_temp.f(2);
+ QUERY PLAN
+------------------------------------------------------
+ Subquery Scan on f
+ -> WindowAgg
+ -> Sort
+ Sort Key: s.s
+ -> Function Scan on generate_series s
+(5 rows)
+
+SELECT * FROM pg_temp.f(2);
+ f
+---------
+ {1,2,3}
+ {2,3,4}
+ {3,4,5}
+ {4,5}
+ {5}
+(5 rows)
+
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
new file mode 100644
index 0000000..f24b4de
--- /dev/null
+++ b/src/test/regress/expected/with.out
@@ -0,0 +1,3215 @@
+--
+-- 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 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 public.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 public.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 public.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 public.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)
+
+-- 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)
+
+drop table graph0 cascade;
+NOTICE: drop cascades to view 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;
+ 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
+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)
+
+-- 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 TEMPORARY TABLE y (a INTEGER);
+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 ( with cte(foo) as ( values(f1) )
+ select (select foo from cte) )
+from int4_tbl;
+ foo
+-------------
+ 0
+ 123456
+ -123456
+ 2147483647
+ -2147483647
+(5 rows)
+
+select ( with cte(foo) as ( values(f1) )
+ values((select foo from cte)) )
+from int4_tbl;
+ 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
+ ^
+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)
+ ^
+--
+-- 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 * FROM t;
+ 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)
+
+-- UPDATE ... RETURNING
+WITH t AS (
+ UPDATE y
+ SET a=a+1
+ RETURNING *
+)
+SELECT * FROM t;
+ a
+----
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+(20 rows)
+
+SELECT * FROM y;
+ 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 * FROM t;
+ a
+----
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+(9 rows)
+
+SELECT * FROM y;
+ 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 RETURNING *
+)
+SELECT * FROM t
+UNION ALL
+SELECT * FROM t2;
+ a
+----
+ 11
+ 12
+ 13
+ 14
+ 15
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+(16 rows)
+
+SELECT * FROM y;
+ 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 * FROM t;
+ a
+----
+ 42
+(1 row)
+
+SELECT * FROM y;
+ 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 * 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
+ 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 * FROM t2 JOIN y USING (a) ORDER BY a;
+ a
+---
+ 0
+ 1
+ 2
+ 3
+ 4
+(5 rows)
+
+SELECT * FROM y;
+ 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
+-----
+ 0
+ -1
+ -2
+ -3
+ -4
+ -5
+ -6
+ -7
+ -8
+ -9
+ -10
+ 0
+ -1
+ -2
+ -3
+ -4
+(16 rows)
+
+SELECT * FROM y;
+ 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 * FROM t LIMIT 10;
+ a
+------
+ 1100
+ 1200
+ 1300
+ 1400
+ 1500
+ 4200
+ 0
+ -100
+ -200
+ -300
+(10 rows)
+
+SELECT * FROM y;
+ 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 * FROM t JOIN y ON t.k = y.a ORDER BY a, k;
+ 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;
+ k | v
+---+---
+(0 rows)
+
+DROP TABLE withz;
+-- check that run to completion happens in proper ordering
+TRUNCATE TABLE y;
+INSERT INTO y SELECT generate_series(1, 3);
+CREATE TEMPORARY TABLE yy (a INTEGER);
+WITH RECURSIVE t1 AS (
+ INSERT INTO y SELECT * FROM y RETURNING *
+), t2 AS (
+ INSERT INTO yy SELECT * FROM t1 RETURNING *
+)
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT * FROM y;
+ a
+---
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+(6 rows)
+
+SELECT * FROM yy;
+ a
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+WITH RECURSIVE t1 AS (
+ INSERT INTO yy SELECT * FROM t2 RETURNING *
+), t2 AS (
+ INSERT INTO y SELECT * FROM y RETURNING *
+)
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT * FROM y;
+ a
+---
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+(12 rows)
+
+SELECT * FROM yy;
+ a
+---
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+(9 rows)
+
+-- triggers
+TRUNCATE TABLE y;
+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 * FROM t;
+NOTICE: y_trigger: a = 21
+NOTICE: y_trigger: a = 22
+NOTICE: y_trigger: a = 23
+ a
+----
+ 21
+ 22
+ 23
+(3 rows)
+
+SELECT * FROM y;
+ 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 * FROM t LIMIT 1;
+NOTICE: y_trigger: a = 31
+NOTICE: y_trigger: a = 32
+NOTICE: y_trigger: a = 33
+ a
+----
+ 31
+(1 row)
+
+SELECT * FROM y;
+ 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 * FROM t;
+NOTICE: y_trigger
+ a
+----
+ 41
+ 42
+ 43
+(3 rows)
+
+SELECT * FROM y;
+ 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 USING wcte WHERE aa = q2;
+ QUERY PLAN
+------------------------------------------------------------
+ Delete on public.a
+ Delete on public.a a_1
+ Delete on public.b a_2
+ Delete on public.c a_3
+ Delete on public.d a_4
+ CTE wcte
+ -> Insert on public.int8_tbl
+ Output: int8_tbl.q2
+ -> Result
+ Output: '42'::bigint, '47'::bigint
+ -> Hash Join
+ Output: wcte.*, a.tableoid, a.ctid
+ Hash Cond: (a.aa = wcte.q2)
+ -> Append
+ -> Seq Scan on public.a a_1
+ Output: a_1.aa, a_1.tableoid, a_1.ctid
+ -> Seq Scan on public.b a_2
+ Output: a_2.aa, a_2.tableoid, a_2.ctid
+ -> Seq Scan on public.c a_3
+ Output: a_3.aa, a_3.tableoid, a_3.ctid
+ -> Seq Scan on public.d a_4
+ Output: a_4.aa, a_4.tableoid, a_4.ctid
+ -> Hash
+ Output: wcte.*, wcte.q2
+ -> CTE Scan on wcte
+ Output: wcte.*, wcte.q2
+(26 rows)
+
+-- error cases
+-- data-modifying WITH tries to use its own output
+WITH RECURSIVE t AS (
+ INSERT INTO y
+ SELECT * FROM t
+)
+VALUES(FALSE);
+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/write_parallel.out b/src/test/regress/expected/write_parallel.out
new file mode 100644
index 0000000..dc0c4ba
--- /dev/null
+++ b/src/test/regress/expected/write_parallel.out
@@ -0,0 +1,80 @@
+--
+-- PARALLEL
+--
+begin;
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+--
+-- Test write operations that has an underlying query that is eligible
+-- for parallel plans
+--
+explain (costs off) create table parallel_write as
+ select length(stringu1) from tenk1 group by length(stringu1);
+ QUERY PLAN
+---------------------------------------------------
+ Finalize HashAggregate
+ Group Key: (length((stringu1)::text))
+ -> Gather
+ Workers Planned: 4
+ -> Partial HashAggregate
+ Group Key: length((stringu1)::text)
+ -> Parallel Seq Scan on tenk1
+(7 rows)
+
+create table parallel_write as
+ select length(stringu1) from tenk1 group by length(stringu1);
+drop table parallel_write;
+explain (costs off) select length(stringu1) into parallel_write
+ from tenk1 group by length(stringu1);
+ QUERY PLAN
+---------------------------------------------------
+ Finalize HashAggregate
+ Group Key: (length((stringu1)::text))
+ -> Gather
+ Workers Planned: 4
+ -> Partial HashAggregate
+ Group Key: length((stringu1)::text)
+ -> Parallel Seq Scan on tenk1
+(7 rows)
+
+select length(stringu1) into parallel_write
+ from tenk1 group by length(stringu1);
+drop table parallel_write;
+explain (costs off) create materialized view parallel_mat_view as
+ select length(stringu1) from tenk1 group by length(stringu1);
+ QUERY PLAN
+---------------------------------------------------
+ Finalize HashAggregate
+ Group Key: (length((stringu1)::text))
+ -> Gather
+ Workers Planned: 4
+ -> Partial HashAggregate
+ Group Key: length((stringu1)::text)
+ -> Parallel Seq Scan on tenk1
+(7 rows)
+
+create materialized view parallel_mat_view as
+ select length(stringu1) from tenk1 group by length(stringu1);
+create unique index on parallel_mat_view(length);
+refresh materialized view parallel_mat_view;
+refresh materialized view concurrently parallel_mat_view;
+drop materialized view parallel_mat_view;
+prepare prep_stmt as select length(stringu1) from tenk1 group by length(stringu1);
+explain (costs off) create table parallel_write as execute prep_stmt;
+ QUERY PLAN
+---------------------------------------------------
+ Finalize HashAggregate
+ Group Key: (length((stringu1)::text))
+ -> Gather
+ Workers Planned: 4
+ -> Partial HashAggregate
+ Group Key: length((stringu1)::text)
+ -> Parallel Seq Scan on tenk1
+(7 rows)
+
+create table parallel_write as execute prep_stmt;
+drop table parallel_write;
+rollback;
diff --git a/src/test/regress/expected/xid.out b/src/test/regress/expected/xid.out
new file mode 100644
index 0000000..b7a1ed0
--- /dev/null
+++ b/src/test/regress/expected/xid.out
@@ -0,0 +1,462 @@
+-- xid and xid8
+-- values in range, in octal, decimal, hex
+select '010'::xid,
+ '42'::xid,
+ '0xffffffff'::xid,
+ '-1'::xid,
+ '010'::xid8,
+ '42'::xid8,
+ '0xffffffffffffffff'::xid8,
+ '-1'::xid8;
+ xid | xid | xid | xid | xid8 | xid8 | xid8 | xid8
+-----+-----+------------+------------+------+------+----------------------+----------------------
+ 8 | 42 | 4294967295 | 4294967295 | 8 | 42 | 18446744073709551615 | 18446744073709551615
+(1 row)
+
+-- garbage values are not yet rejected (perhaps they should be)
+select ''::xid;
+ xid
+-----
+ 0
+(1 row)
+
+select 'asdf'::xid;
+ xid
+-----
+ 0
+(1 row)
+
+select ''::xid8;
+ xid8
+------
+ 0
+(1 row)
+
+select 'asdf'::xid8;
+ xid8
+------
+ 0
+(1 row)
+
+-- equality
+select '1'::xid = '1'::xid;
+ ?column?
+----------
+ t
+(1 row)
+
+select '1'::xid != '1'::xid;
+ ?column?
+----------
+ f
+(1 row)
+
+select '1'::xid8 = '1'::xid8;
+ ?column?
+----------
+ t
+(1 row)
+
+select '1'::xid8 != '1'::xid8;
+ ?column?
+----------
+ f
+(1 row)
+
+-- conversion
+select '1'::xid = '1'::xid8::xid;
+ ?column?
+----------
+ t
+(1 row)
+
+select '1'::xid != '1'::xid8::xid;
+ ?column?
+----------
+ f
+(1 row)
+
+-- we don't want relational operators for xid, due to use of modular arithmetic
+select '1'::xid < '2'::xid;
+ERROR: operator does not exist: xid < xid
+LINE 1: select '1'::xid < '2'::xid;
+ ^
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+select '1'::xid <= '2'::xid;
+ERROR: operator does not exist: xid <= xid
+LINE 1: select '1'::xid <= '2'::xid;
+ ^
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+select '1'::xid > '2'::xid;
+ERROR: operator does not exist: xid > xid
+LINE 1: select '1'::xid > '2'::xid;
+ ^
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+select '1'::xid >= '2'::xid;
+ERROR: operator does not exist: xid >= xid
+LINE 1: select '1'::xid >= '2'::xid;
+ ^
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
+-- we want them for xid8 though
+select '1'::xid8 < '2'::xid8, '2'::xid8 < '2'::xid8, '2'::xid8 < '1'::xid8;
+ ?column? | ?column? | ?column?
+----------+----------+----------
+ t | f | f
+(1 row)
+
+select '1'::xid8 <= '2'::xid8, '2'::xid8 <= '2'::xid8, '2'::xid8 <= '1'::xid8;
+ ?column? | ?column? | ?column?
+----------+----------+----------
+ t | t | f
+(1 row)
+
+select '1'::xid8 > '2'::xid8, '2'::xid8 > '2'::xid8, '2'::xid8 > '1'::xid8;
+ ?column? | ?column? | ?column?
+----------+----------+----------
+ f | f | t
+(1 row)
+
+select '1'::xid8 >= '2'::xid8, '2'::xid8 >= '2'::xid8, '2'::xid8 >= '1'::xid8;
+ ?column? | ?column? | ?column?
+----------+----------+----------
+ f | t | t
+(1 row)
+
+-- we also have a 3way compare for btrees
+select xid8cmp('1', '2'), xid8cmp('2', '2'), xid8cmp('2', '1');
+ xid8cmp | xid8cmp | xid8cmp
+---------+---------+---------
+ -1 | 0 | 1
+(1 row)
+
+-- xid8 has btree and hash opclasses
+create table xid8_t1 (x xid8);
+create index on xid8_t1 using btree(x);
+create index on xid8_t1 using hash(x);
+drop table xid8_t1;
+-- pg_snapshot data type and related functions
+-- Note: another set of tests similar to this exists in txid.sql, for a limited
+-- time (the relevant functions share C code)
+-- i/o
+select '12:13:'::pg_snapshot;
+ pg_snapshot
+-------------
+ 12:13:
+(1 row)
+
+select '12:18:14,16'::pg_snapshot;
+ pg_snapshot
+-------------
+ 12:18:14,16
+(1 row)
+
+select '12:16:14,14'::pg_snapshot;
+ pg_snapshot
+-------------
+ 12:16:14
+(1 row)
+
+-- errors
+select '31:12:'::pg_snapshot;
+ERROR: invalid input syntax for type pg_snapshot: "31:12:"
+LINE 1: select '31:12:'::pg_snapshot;
+ ^
+select '0:1:'::pg_snapshot;
+ERROR: invalid input syntax for type pg_snapshot: "0:1:"
+LINE 1: select '0:1:'::pg_snapshot;
+ ^
+select '12:13:0'::pg_snapshot;
+ERROR: invalid input syntax for type pg_snapshot: "12:13:0"
+LINE 1: select '12:13:0'::pg_snapshot;
+ ^
+select '12:16:14,13'::pg_snapshot;
+ERROR: invalid input syntax for type pg_snapshot: "12:16:14,13"
+LINE 1: select '12:16:14,13'::pg_snapshot;
+ ^
+create temp table snapshot_test (
+ nr integer,
+ snap pg_snapshot
+);
+insert into snapshot_test values (1, '12:13:');
+insert into snapshot_test values (2, '12:20:13,15,18');
+insert into snapshot_test values (3, '100001:100009:100005,100007,100008');
+insert into snapshot_test values (4, '100:150: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');
+select snap from snapshot_test order by nr;
+ snap
+-------------------------------------------------------------------------------------------------------------------------------------
+ 12:13:
+ 12:20:13,15,18
+ 100001:100009:100005,100007,100008
+ 100:150: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
+(4 rows)
+
+select pg_snapshot_xmin(snap),
+ pg_snapshot_xmax(snap),
+ pg_snapshot_xip(snap)
+from snapshot_test order by nr;
+ pg_snapshot_xmin | pg_snapshot_xmax | pg_snapshot_xip
+------------------+------------------+-----------------
+ 12 | 20 | 13
+ 12 | 20 | 15
+ 12 | 20 | 18
+ 100001 | 100009 | 100005
+ 100001 | 100009 | 100007
+ 100001 | 100009 | 100008
+ 100 | 150 | 101
+ 100 | 150 | 102
+ 100 | 150 | 103
+ 100 | 150 | 104
+ 100 | 150 | 105
+ 100 | 150 | 106
+ 100 | 150 | 107
+ 100 | 150 | 108
+ 100 | 150 | 109
+ 100 | 150 | 110
+ 100 | 150 | 111
+ 100 | 150 | 112
+ 100 | 150 | 113
+ 100 | 150 | 114
+ 100 | 150 | 115
+ 100 | 150 | 116
+ 100 | 150 | 117
+ 100 | 150 | 118
+ 100 | 150 | 119
+ 100 | 150 | 120
+ 100 | 150 | 121
+ 100 | 150 | 122
+ 100 | 150 | 123
+ 100 | 150 | 124
+ 100 | 150 | 125
+ 100 | 150 | 126
+ 100 | 150 | 127
+ 100 | 150 | 128
+ 100 | 150 | 129
+ 100 | 150 | 130
+ 100 | 150 | 131
+(37 rows)
+
+select id, pg_visible_in_snapshot(id::text::xid8, snap)
+from snapshot_test, generate_series(11, 21) id
+where nr = 2;
+ id | pg_visible_in_snapshot
+----+------------------------
+ 11 | t
+ 12 | t
+ 13 | f
+ 14 | t
+ 15 | f
+ 16 | t
+ 17 | t
+ 18 | f
+ 19 | t
+ 20 | f
+ 21 | f
+(11 rows)
+
+-- test bsearch
+select id, pg_visible_in_snapshot(id::text::xid8, snap)
+from snapshot_test, generate_series(90, 160) id
+where nr = 4;
+ id | pg_visible_in_snapshot
+-----+------------------------
+ 90 | t
+ 91 | t
+ 92 | t
+ 93 | t
+ 94 | t
+ 95 | t
+ 96 | t
+ 97 | t
+ 98 | t
+ 99 | t
+ 100 | t
+ 101 | f
+ 102 | f
+ 103 | f
+ 104 | f
+ 105 | f
+ 106 | f
+ 107 | f
+ 108 | f
+ 109 | f
+ 110 | f
+ 111 | f
+ 112 | f
+ 113 | f
+ 114 | f
+ 115 | f
+ 116 | f
+ 117 | f
+ 118 | f
+ 119 | f
+ 120 | f
+ 121 | f
+ 122 | f
+ 123 | f
+ 124 | f
+ 125 | f
+ 126 | f
+ 127 | f
+ 128 | f
+ 129 | f
+ 130 | f
+ 131 | f
+ 132 | t
+ 133 | t
+ 134 | t
+ 135 | t
+ 136 | t
+ 137 | t
+ 138 | t
+ 139 | t
+ 140 | t
+ 141 | t
+ 142 | t
+ 143 | t
+ 144 | t
+ 145 | t
+ 146 | t
+ 147 | t
+ 148 | t
+ 149 | t
+ 150 | f
+ 151 | f
+ 152 | f
+ 153 | f
+ 154 | f
+ 155 | f
+ 156 | f
+ 157 | f
+ 158 | f
+ 159 | f
+ 160 | f
+(71 rows)
+
+-- test current values also
+select pg_current_xact_id() >= pg_snapshot_xmin(pg_current_snapshot());
+ ?column?
+----------
+ t
+(1 row)
+
+-- we can't assume current is always less than xmax, however
+select pg_visible_in_snapshot(pg_current_xact_id(), pg_current_snapshot());
+ pg_visible_in_snapshot
+------------------------
+ f
+(1 row)
+
+-- test 64bitness
+select pg_snapshot '1000100010001000:1000100010001100:1000100010001012,1000100010001013';
+ pg_snapshot
+---------------------------------------------------------------------
+ 1000100010001000:1000100010001100:1000100010001012,1000100010001013
+(1 row)
+
+select pg_visible_in_snapshot('1000100010001012', '1000100010001000:1000100010001100:1000100010001012,1000100010001013');
+ pg_visible_in_snapshot
+------------------------
+ f
+(1 row)
+
+select pg_visible_in_snapshot('1000100010001015', '1000100010001000:1000100010001100:1000100010001012,1000100010001013');
+ pg_visible_in_snapshot
+------------------------
+ t
+(1 row)
+
+-- test 64bit overflow
+SELECT pg_snapshot '1:9223372036854775807:3';
+ pg_snapshot
+-------------------------
+ 1:9223372036854775807:3
+(1 row)
+
+SELECT pg_snapshot '1:9223372036854775808:3';
+ERROR: invalid input syntax for type pg_snapshot: "1:9223372036854775808:3"
+LINE 1: SELECT pg_snapshot '1:9223372036854775808:3';
+ ^
+-- test pg_current_xact_id_if_assigned
+BEGIN;
+SELECT pg_current_xact_id_if_assigned() IS NULL;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT pg_current_xact_id() \gset
+SELECT pg_current_xact_id_if_assigned() IS NOT DISTINCT FROM xid8 :'pg_current_xact_id';
+ ?column?
+----------
+ t
+(1 row)
+
+COMMIT;
+-- test xid status functions
+BEGIN;
+SELECT pg_current_xact_id() AS committed \gset
+COMMIT;
+BEGIN;
+SELECT pg_current_xact_id() AS rolledback \gset
+ROLLBACK;
+BEGIN;
+SELECT pg_current_xact_id() AS inprogress \gset
+SELECT pg_xact_status(:committed::text::xid8) AS committed;
+ committed
+-----------
+ committed
+(1 row)
+
+SELECT pg_xact_status(:rolledback::text::xid8) AS rolledback;
+ rolledback
+------------
+ aborted
+(1 row)
+
+SELECT pg_xact_status(:inprogress::text::xid8) AS inprogress;
+ inprogress
+-------------
+ in progress
+(1 row)
+
+SELECT pg_xact_status('1'::xid8); -- BootstrapTransactionId is always committed
+ pg_xact_status
+----------------
+ committed
+(1 row)
+
+SELECT pg_xact_status('2'::xid8); -- FrozenTransactionId is always committed
+ pg_xact_status
+----------------
+ committed
+(1 row)
+
+SELECT pg_xact_status('3'::xid8); -- in regress testing FirstNormalTransactionId will always be behind oldestXmin
+ pg_xact_status
+----------------
+
+(1 row)
+
+COMMIT;
+BEGIN;
+CREATE FUNCTION test_future_xid_status(xid8)
+RETURNS void
+LANGUAGE plpgsql
+AS
+$$
+BEGIN
+ PERFORM pg_xact_status($1);
+ RAISE EXCEPTION 'didn''t ERROR at xid in the future as expected';
+EXCEPTION
+ WHEN invalid_parameter_value THEN
+ RAISE NOTICE 'Got expected error for xid in the future';
+END;
+$$;
+SELECT test_future_xid_status((:inprogress + 10000)::text::xid8);
+NOTICE: Got expected error for xid in the future
+ test_future_xid_status
+------------------------
+
+(1 row)
+
+ROLLBACK;
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
new file mode 100644
index 0000000..55ac49b
--- /dev/null
+++ b/src/test/regress/expected/xml.out
@@ -0,0 +1,1570 @@
+CREATE TABLE xmltest (
+ id int,
+ data xml
+);
+INSERT INTO xmltest VALUES (1, '<value>one</value>');
+INSERT INTO xmltest VALUES (2, '<value>two</value>');
+INSERT INTO xmltest VALUES (3, '<wrong');
+ERROR: invalid XML content
+LINE 1: INSERT INTO xmltest VALUES (3, '<wrong');
+ ^
+DETAIL: line 1: Couldn't find end of Start Tag wrong line 1
+<wrong
+ ^
+SELECT * FROM xmltest;
+ id | data
+----+--------------------
+ 1 | <value>one</value>
+ 2 | <value>two</value>
+(2 rows)
+
+SELECT xmlcomment('test');
+ xmlcomment
+-------------
+ <!--test-->
+(1 row)
+
+SELECT xmlcomment('-test');
+ xmlcomment
+--------------
+ <!---test-->
+(1 row)
+
+SELECT xmlcomment('test-');
+ERROR: invalid XML comment
+SELECT xmlcomment('--test');
+ERROR: invalid XML comment
+SELECT xmlcomment('te st');
+ xmlcomment
+--------------
+ <!--te st-->
+(1 row)
+
+SELECT xmlconcat(xmlcomment('hello'),
+ xmlelement(NAME qux, 'foo'),
+ xmlcomment('world'));
+ xmlconcat
+----------------------------------------
+ <!--hello--><qux>foo</qux><!--world-->
+(1 row)
+
+SELECT xmlconcat('hello', 'you');
+ xmlconcat
+-----------
+ helloyou
+(1 row)
+
+SELECT xmlconcat(1, 2);
+ERROR: argument of XMLCONCAT must be type xml, not type integer
+LINE 1: SELECT xmlconcat(1, 2);
+ ^
+SELECT xmlconcat('bad', '<syntax');
+ERROR: invalid XML content
+LINE 1: SELECT xmlconcat('bad', '<syntax');
+ ^
+DETAIL: line 1: Couldn't find end of Start Tag syntax line 1
+<syntax
+ ^
+SELECT xmlconcat('<foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
+ xmlconcat
+--------------
+ <foo/><bar/>
+(1 row)
+
+SELECT xmlconcat('<?xml version="1.1"?><foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
+ xmlconcat
+-----------------------------------
+ <?xml version="1.1"?><foo/><bar/>
+(1 row)
+
+SELECT xmlconcat(NULL);
+ xmlconcat
+-----------
+
+(1 row)
+
+SELECT xmlconcat(NULL, NULL);
+ xmlconcat
+-----------
+
+(1 row)
+
+SELECT xmlelement(name element,
+ xmlattributes (1 as one, 'deuce' as two),
+ 'content');
+ xmlelement
+------------------------------------------------
+ <element one="1" two="deuce">content</element>
+(1 row)
+
+SELECT xmlelement(name element,
+ xmlattributes ('unnamed and wrong'));
+ERROR: unnamed XML attribute value must be a column reference
+LINE 2: xmlattributes ('unnamed and wrong'));
+ ^
+SELECT xmlelement(name element, xmlelement(name nested, 'stuff'));
+ xmlelement
+-------------------------------------------
+ <element><nested>stuff</nested></element>
+(1 row)
+
+SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
+ xmlelement
+----------------------------------------------------------------------
+ <employee><name>sharon</name><age>25</age><pay>1000</pay></employee>
+ <employee><name>sam</name><age>30</age><pay>2000</pay></employee>
+ <employee><name>bill</name><age>20</age><pay>1000</pay></employee>
+ <employee><name>jeff</name><age>23</age><pay>600</pay></employee>
+ <employee><name>cim</name><age>30</age><pay>400</pay></employee>
+ <employee><name>linda</name><age>19</age><pay>100</pay></employee>
+(6 rows)
+
+SELECT xmlelement(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
+ERROR: XML attribute name "a" appears more than once
+LINE 1: ...ment(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
+ ^
+SELECT xmlelement(name num, 37);
+ xmlelement
+---------------
+ <num>37</num>
+(1 row)
+
+SELECT xmlelement(name foo, text 'bar');
+ xmlelement
+----------------
+ <foo>bar</foo>
+(1 row)
+
+SELECT xmlelement(name foo, xml 'bar');
+ xmlelement
+----------------
+ <foo>bar</foo>
+(1 row)
+
+SELECT xmlelement(name foo, text 'b<a/>r');
+ xmlelement
+-------------------------
+ <foo>b&lt;a/&gt;r</foo>
+(1 row)
+
+SELECT xmlelement(name foo, xml 'b<a/>r');
+ xmlelement
+-------------------
+ <foo>b<a/>r</foo>
+(1 row)
+
+SELECT xmlelement(name foo, array[1, 2, 3]);
+ xmlelement
+-------------------------------------------------------------------------
+ <foo><element>1</element><element>2</element><element>3</element></foo>
+(1 row)
+
+SET xmlbinary TO base64;
+SELECT xmlelement(name foo, bytea 'bar');
+ xmlelement
+-----------------
+ <foo>YmFy</foo>
+(1 row)
+
+SET xmlbinary TO hex;
+SELECT xmlelement(name foo, bytea 'bar');
+ xmlelement
+-------------------
+ <foo>626172</foo>
+(1 row)
+
+SELECT xmlelement(name foo, xmlattributes(true as bar));
+ xmlelement
+-------------------
+ <foo bar="true"/>
+(1 row)
+
+SELECT xmlelement(name foo, xmlattributes('2009-04-09 00:24:37'::timestamp as bar));
+ xmlelement
+----------------------------------
+ <foo bar="2009-04-09T00:24:37"/>
+(1 row)
+
+SELECT xmlelement(name foo, xmlattributes('infinity'::timestamp as bar));
+ERROR: timestamp out of range
+DETAIL: XML does not support infinite timestamp values.
+SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as funnier));
+ xmlelement
+------------------------------------------------------------
+ <foo funny="&lt;&gt;&amp;&quot;'" funnier="b&lt;a/&gt;r"/>
+(1 row)
+
+SELECT xmlparse(content '');
+ xmlparse
+----------
+
+(1 row)
+
+SELECT xmlparse(content ' ');
+ xmlparse
+----------
+
+(1 row)
+
+SELECT xmlparse(content 'abc');
+ xmlparse
+----------
+ abc
+(1 row)
+
+SELECT xmlparse(content '<abc>x</abc>');
+ xmlparse
+--------------
+ <abc>x</abc>
+(1 row)
+
+SELECT xmlparse(content '<invalidentity>&</invalidentity>');
+ERROR: invalid XML content
+DETAIL: line 1: xmlParseEntityRef: no name
+<invalidentity>&</invalidentity>
+ ^
+line 1: chunk is not well balanced
+<invalidentity>&</invalidentity>
+ ^
+SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
+ERROR: invalid XML content
+DETAIL: line 1: Entity 'idontexist' not defined
+<undefinedentity>&idontexist;</undefinedentity>
+ ^
+line 1: chunk is not well balanced
+<undefinedentity>&idontexist;</undefinedentity>
+ ^
+SELECT xmlparse(content '<invalidns xmlns=''&lt;''/>');
+ xmlparse
+---------------------------
+ <invalidns xmlns='&lt;'/>
+(1 row)
+
+SELECT xmlparse(content '<relativens xmlns=''relative''/>');
+ xmlparse
+--------------------------------
+ <relativens xmlns='relative'/>
+(1 row)
+
+SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
+ERROR: invalid XML content
+DETAIL: line 1: Entity 'idontexist' not defined
+<twoerrors>&idontexist;</unbalanced>
+ ^
+line 1: Opening and ending tag mismatch: twoerrors line 1 and unbalanced
+<twoerrors>&idontexist;</unbalanced>
+ ^
+line 1: chunk is not well balanced
+<twoerrors>&idontexist;</unbalanced>
+ ^
+SELECT xmlparse(content '<nosuchprefix:tag/>');
+ xmlparse
+---------------------
+ <nosuchprefix:tag/>
+(1 row)
+
+SELECT xmlparse(document ' ');
+ERROR: invalid XML document
+DETAIL: line 1: Start tag expected, '<' not found
+
+ ^
+SELECT xmlparse(document 'abc');
+ERROR: invalid XML document
+DETAIL: line 1: Start tag expected, '<' not found
+abc
+^
+SELECT xmlparse(document '<abc>x</abc>');
+ xmlparse
+--------------
+ <abc>x</abc>
+(1 row)
+
+SELECT xmlparse(document '<invalidentity>&</abc>');
+ERROR: invalid XML document
+DETAIL: line 1: xmlParseEntityRef: no name
+<invalidentity>&</abc>
+ ^
+line 1: Opening and ending tag mismatch: invalidentity line 1 and abc
+<invalidentity>&</abc>
+ ^
+SELECT xmlparse(document '<undefinedentity>&idontexist;</abc>');
+ERROR: invalid XML document
+DETAIL: line 1: Entity 'idontexist' not defined
+<undefinedentity>&idontexist;</abc>
+ ^
+line 1: Opening and ending tag mismatch: undefinedentity line 1 and abc
+<undefinedentity>&idontexist;</abc>
+ ^
+SELECT xmlparse(document '<invalidns xmlns=''&lt;''/>');
+ xmlparse
+---------------------------
+ <invalidns xmlns='&lt;'/>
+(1 row)
+
+SELECT xmlparse(document '<relativens xmlns=''relative''/>');
+ xmlparse
+--------------------------------
+ <relativens xmlns='relative'/>
+(1 row)
+
+SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
+ERROR: invalid XML document
+DETAIL: line 1: Entity 'idontexist' not defined
+<twoerrors>&idontexist;</unbalanced>
+ ^
+line 1: Opening and ending tag mismatch: twoerrors line 1 and unbalanced
+<twoerrors>&idontexist;</unbalanced>
+ ^
+SELECT xmlparse(document '<nosuchprefix:tag/>');
+ xmlparse
+---------------------
+ <nosuchprefix:tag/>
+(1 row)
+
+SELECT xmlpi(name foo);
+ xmlpi
+---------
+ <?foo?>
+(1 row)
+
+SELECT xmlpi(name xml);
+ERROR: invalid XML processing instruction
+DETAIL: XML processing instruction target name cannot be "xml".
+SELECT xmlpi(name xmlstuff);
+ xmlpi
+--------------
+ <?xmlstuff?>
+(1 row)
+
+SELECT xmlpi(name foo, 'bar');
+ xmlpi
+-------------
+ <?foo bar?>
+(1 row)
+
+SELECT xmlpi(name foo, 'in?>valid');
+ERROR: invalid XML processing instruction
+DETAIL: XML processing instruction cannot contain "?>".
+SELECT xmlpi(name foo, null);
+ xmlpi
+-------
+
+(1 row)
+
+SELECT xmlpi(name xml, null);
+ERROR: invalid XML processing instruction
+DETAIL: XML processing instruction target name cannot be "xml".
+SELECT xmlpi(name xmlstuff, null);
+ xmlpi
+-------
+
+(1 row)
+
+SELECT xmlpi(name "xml-stylesheet", 'href="mystyle.css" type="text/css"');
+ xmlpi
+-------------------------------------------------------
+ <?xml-stylesheet href="mystyle.css" type="text/css"?>
+(1 row)
+
+SELECT xmlpi(name foo, ' bar');
+ xmlpi
+-------------
+ <?foo bar?>
+(1 row)
+
+SELECT xmlroot(xml '<foo/>', version no value, standalone no value);
+ xmlroot
+---------
+ <foo/>
+(1 row)
+
+SELECT xmlroot(xml '<foo/>', version '2.0');
+ xmlroot
+-----------------------------
+ <?xml version="2.0"?><foo/>
+(1 row)
+
+SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
+ xmlroot
+----------------------------------------------
+ <?xml version="1.0" standalone="yes"?><foo/>
+(1 row)
+
+SELECT xmlroot(xml '<?xml version="1.1"?><foo/>', version no value, standalone yes);
+ xmlroot
+----------------------------------------------
+ <?xml version="1.0" standalone="yes"?><foo/>
+(1 row)
+
+SELECT xmlroot(xmlroot(xml '<foo/>', version '1.0'), version '1.1', standalone no);
+ xmlroot
+---------------------------------------------
+ <?xml version="1.1" standalone="no"?><foo/>
+(1 row)
+
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no);
+ xmlroot
+---------------------------------------------
+ <?xml version="1.0" standalone="no"?><foo/>
+(1 row)
+
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no value);
+ xmlroot
+---------
+ <foo/>
+(1 row)
+
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value);
+ xmlroot
+----------------------------------------------
+ <?xml version="1.0" standalone="yes"?><foo/>
+(1 row)
+
+SELECT xmlroot (
+ xmlelement (
+ name gazonk,
+ xmlattributes (
+ 'val' AS name,
+ 1 + 1 AS num
+ ),
+ xmlelement (
+ NAME qux,
+ 'foo'
+ )
+ ),
+ version '1.0',
+ standalone yes
+);
+ xmlroot
+------------------------------------------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><gazonk name="val" num="2"><qux>foo</qux></gazonk>
+(1 row)
+
+SELECT xmlserialize(content data as character varying(20)) FROM xmltest;
+ xmlserialize
+--------------------
+ <value>one</value>
+ <value>two</value>
+(2 rows)
+
+SELECT xmlserialize(content 'good' as char(10));
+ xmlserialize
+--------------
+ good
+(1 row)
+
+SELECT xmlserialize(document 'bad' as text);
+ERROR: not an XML document
+SELECT xml '<foo>bar</foo>' IS DOCUMENT;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT xml '<abc/>' IS NOT DOCUMENT;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT xml 'abc' IS NOT DOCUMENT;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT '<>' IS NOT DOCUMENT;
+ERROR: invalid XML content
+LINE 1: SELECT '<>' IS NOT DOCUMENT;
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<>
+ ^
+SELECT xmlagg(data) FROM xmltest;
+ xmlagg
+--------------------------------------
+ <value>one</value><value>two</value>
+(1 row)
+
+SELECT xmlagg(data) FROM xmltest WHERE id > 10;
+ xmlagg
+--------
+
+(1 row)
+
+SELECT xmlelement(name employees, xmlagg(xmlelement(name name, name))) FROM emp;
+ xmlelement
+--------------------------------------------------------------------------------------------------------------------------------
+ <employees><name>sharon</name><name>sam</name><name>bill</name><name>jeff</name><name>cim</name><name>linda</name></employees>
+(1 row)
+
+-- Check mapping SQL identifier to XML name
+SELECT xmlpi(name ":::_xml_abc135.%-&_");
+ xmlpi
+-------------------------------------------------
+ <?_x003A_::_x005F_xml_abc135._x0025_-_x0026__?>
+(1 row)
+
+SELECT xmlpi(name "123");
+ xmlpi
+---------------
+ <?_x0031_23?>
+(1 row)
+
+PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
+SET XML OPTION DOCUMENT;
+EXECUTE foo ('<bar/>');
+ xmlconcat
+--------------
+ <foo/><bar/>
+(1 row)
+
+EXECUTE foo ('bad');
+ERROR: invalid XML document
+LINE 1: EXECUTE foo ('bad');
+ ^
+DETAIL: line 1: Start tag expected, '<' not found
+bad
+^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: invalid XML document
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+ ^
+SET XML OPTION CONTENT;
+EXECUTE foo ('<bar/>');
+ xmlconcat
+--------------
+ <foo/><bar/>
+(1 row)
+
+EXECUTE foo ('good');
+ xmlconcat
+------------
+ <foo/>good
+(1 row)
+
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+ xml
+--------------------------------------------------------------------
+ <!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+ xml
+------------------------------
+ <!-- hi--> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!DOCTYPE a><a/>';
+ xml
+------------------
+ <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<!-- hi--> oops <!DOCTYPE a><a/>
+ ^
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<!-- hi--> <oops/> <!DOCTYPE a><a/>
+ ^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+ ^
+-- Test backwards parsing
+CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
+CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
+CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&');
+CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
+CREATE VIEW xmlview5 AS SELECT xmlparse(content '<abc>x</abc>');
+CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar');
+CREATE VIEW xmlview7 AS SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
+CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
+CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
+SELECT table_name, view_definition FROM information_schema.views
+ WHERE table_name LIKE 'xmlview%' ORDER BY 1;
+ table_name | view_definition
+------------+-------------------------------------------------------------------------------------------------------------------
+ xmlview1 | SELECT xmlcomment('test'::text) AS xmlcomment;
+ xmlview2 | SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
+ xmlview3 | SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
+ xmlview4 | SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ | FROM emp;
+ xmlview5 | SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
+ xmlview6 | SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
+ xmlview7 | SELECT XMLROOT('<foo/>'::xml, VERSION NO VALUE, STANDALONE YES) AS "xmlroot";
+ xmlview8 | SELECT (XMLSERIALIZE(CONTENT 'good'::xml AS character(10)))::character(10) AS "xmlserialize";
+ xmlview9 | SELECT XMLSERIALIZE(CONTENT 'good'::xml AS text) AS "xmlserialize";
+(9 rows)
+
+-- Text XPath expressions evaluation
+SELECT xpath('/value', data) FROM xmltest;
+ xpath
+----------------------
+ {<value>one</value>}
+ {<value>two</value>}
+(2 rows)
+
+SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
+ ?column?
+----------
+ t
+ t
+(2 rows)
+
+SELECT xpath('', '<!-- error -->');
+ERROR: empty XPath expression
+CONTEXT: SQL function "xpath" statement 1
+SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+ xpath
+----------------
+ {"number one"}
+(1 row)
+
+SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath
+-------
+ {1,2}
+(1 row)
+
+SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath
+------------------------------------------------------------------------------------------------------------------------------------------------
+ {"<local:piece xmlns:local=\"http://127.0.0.1\" id=\"1\">number one</local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
+(1 row)
+
+SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1" xmlns="http://127.0.0.2"><local:piece id="1"><internal>number one</internal><internal2/></local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"<local:piece xmlns:local=\"http://127.0.0.1\" xmlns=\"http://127.0.0.2\" id=\"1\"><internal>number one</internal><internal2/></local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
+(1 row)
+
+SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
+ xpath
+-------------------------
+ {<b>two</b>,<b>etc</b>}
+(1 row)
+
+SELECT xpath('//text()', '<root>&lt;</root>');
+ xpath
+--------
+ {&lt;}
+(1 row)
+
+SELECT xpath('//@value', '<root value="&lt;"/>');
+ xpath
+--------
+ {&lt;}
+(1 row)
+
+SELECT xpath('''<<invalid>>''', '<root/>');
+ xpath
+---------------------------
+ {&lt;&lt;invalid&gt;&gt;}
+(1 row)
+
+SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
+ xpath
+-------
+ {3}
+(1 row)
+
+SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
+ xpath
+---------
+ {false}
+(1 row)
+
+SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
+ xpath
+--------
+ {true}
+(1 row)
+
+SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
+ xpath
+--------
+ {root}
+(1 row)
+
+SELECT xpath('/nosuchtag', '<root/>');
+ xpath
+-------
+ {}
+(1 row)
+
+SELECT xpath('root', '<root/>');
+ xpath
+-----------
+ {<root/>}
+(1 row)
+
+-- Round-trip non-ASCII data through xpath().
+DO $$
+DECLARE
+ xml_declaration text := '<?xml version="1.0" encoding="ISO-8859-1"?>';
+ degree_symbol text;
+ res xml[];
+BEGIN
+ -- Per the documentation, except when the server encoding is UTF8, xpath()
+ -- may not work on non-ASCII data. The untranslatable_character and
+ -- undefined_function traps below, currently dead code, will become relevant
+ -- if we remove this limitation.
+ IF current_setting('server_encoding') <> 'UTF8' THEN
+ RAISE LOG 'skip: encoding % unsupported for xpath',
+ current_setting('server_encoding');
+ RETURN;
+ END IF;
+
+ degree_symbol := convert_from('\xc2b0', 'UTF8');
+ res := xpath('text()', (xml_declaration ||
+ '<x>' || degree_symbol || '</x>')::xml);
+ IF degree_symbol <> res[1]::text THEN
+ RAISE 'expected % (%), got % (%)',
+ degree_symbol, convert_to(degree_symbol, 'UTF8'),
+ res[1], convert_to(res[1]::text, 'UTF8');
+ END IF;
+EXCEPTION
+ -- character with byte sequence 0xc2 0xb0 in encoding "UTF8" has no equivalent in encoding "LATIN8"
+ WHEN untranslatable_character
+ -- default conversion function for encoding "UTF8" to "MULE_INTERNAL" does not exist
+ OR undefined_function
+ -- unsupported XML feature
+ OR feature_not_supported THEN
+ RAISE LOG 'skip: %', SQLERRM;
+END
+$$;
+-- Test xmlexists and xpath_exists
+SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ xmlexists
+-----------
+ f
+(1 row)
+
+SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ xmlexists
+-----------
+ t
+(1 row)
+
+SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
+ xmlexists
+-----------
+ t
+(1 row)
+
+SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
+ xpath_exists
+--------------
+ f
+(1 row)
+
+SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
+ xpath_exists
+--------------
+ t
+(1 row)
+
+SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
+ xpath_exists
+--------------
+ t
+(1 row)
+
+INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Budvar</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
+ count
+-------
+ 2
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
+ count
+-------
+ 1
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data);
+ count
+-------
+ 2
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data);
+ count
+-------
+ 1
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 2
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 1
+(1 row)
+
+CREATE TABLE query ( expr TEXT );
+INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']');
+SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data);
+ count
+-------
+ 2
+(1 row)
+
+-- Test xml_is_well_formed and variants
+SELECT xml_is_well_formed_document('<foo>bar</foo>');
+ xml_is_well_formed_document
+-----------------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed_document('abc');
+ xml_is_well_formed_document
+-----------------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed_content('<foo>bar</foo>');
+ xml_is_well_formed_content
+----------------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed_content('abc');
+ xml_is_well_formed_content
+----------------------------
+ t
+(1 row)
+
+SET xmloption TO DOCUMENT;
+SELECT xml_is_well_formed('abc');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<abc/>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<foo>bar</foo>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<foo>bar</foo');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<foo><bar>baz</foo>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<invalidentity>&</abc>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<invalidns xmlns=''&lt;''/>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SET xmloption TO CONTENT;
+SELECT xml_is_well_formed('abc');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+-- Since xpath() deals with namespaces, it's a bit stricter about
+-- what's well-formed and what's not. If we don't obey these rules
+-- (i.e. ignore namespace-related errors from libxml), xpath()
+-- fails in subtle ways. The following would for example produce
+-- the xml value
+-- <invalidns xmlns='<'/>
+-- which is invalid because '<' may not appear un-escaped in
+-- attribute values.
+-- Since different libxml versions emit slightly different
+-- error messages, we suppress the DETAIL in this test.
+\set VERBOSITY terse
+SELECT xpath('/*', '<invalidns xmlns=''&lt;''/>');
+ERROR: could not parse XML document
+\set VERBOSITY default
+-- Again, the XML isn't well-formed for namespace purposes
+SELECT xpath('/*', '<nosuchprefix:tag/>');
+ERROR: could not parse XML document
+DETAIL: line 1: Namespace prefix nosuchprefix on tag is not defined
+<nosuchprefix:tag/>
+ ^
+CONTEXT: SQL function "xpath" statement 1
+-- XPath deprecates relative namespaces, but they're not supposed to
+-- throw an error, only a warning.
+SELECT xpath('/*', '<relativens xmlns=''relative''/>');
+WARNING: line 1: xmlns: URI relative is not absolute
+<relativens xmlns='relative'/>
+ ^
+ xpath
+--------------------------------------
+ {"<relativens xmlns=\"relative\"/>"}
+(1 row)
+
+-- External entity references should not leak filesystem information.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>');
+ xmlparse
+-----------------------------------------------------------------
+ <!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>
+(1 row)
+
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>');
+ xmlparse
+-----------------------------------------------------------------------
+ <!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>
+(1 row)
+
+-- This might or might not load the requested DTD, but it mustn't throw error.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>');
+ xmlparse
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>
+(1 row)
+
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+ <COUNTRY_ID>AU</COUNTRY_ID>
+ <COUNTRY_NAME>Australia</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+ <COUNTRY_ID>CN</COUNTRY_ID>
+ <COUNTRY_NAME>China</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+ <COUNTRY_ID>HK</COUNTRY_ID>
+ <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+ <COUNTRY_ID>IN</COUNTRY_ID>
+ <COUNTRY_NAME>India</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+ <COUNTRY_ID>JP</COUNTRY_ID>
+ <COUNTRY_NAME>Japan</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+ <COUNTRY_ID>SG</COUNTRY_ID>
+ <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+-- XMLTABLE with columns
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+(6 rows)
+
+CREATE VIEW xmltableview1 AS SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+(6 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+ "xmltable"._id,
+ "xmltable".country_name,
+ "xmltable".country_id,
+ "xmltable".region_id,
+ "xmltable".size,
+ "xmltable".unit,
+ "xmltable".premier_name
+ FROM ( SELECT xmldata.data
+ FROM xmldata) x,
+ LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+ QUERY PLAN
+-----------------------------------------
+ Nested Loop
+ -> Seq Scan on xmldata
+ -> Table Function Scan on "xmltable"
+(3 rows)
+
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- errors
+SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2);
+ERROR: XMLTABLE function has 1 columns available but 2 columns specified
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+ '/zz:rows/zz:row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'zz:a');
+ a
+----
+ 10
+(1 row)
+
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+ '/zz:rows/zz:row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'zz:a');
+SELECT * FROM xmltableview2;
+ a
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+ '/rows/row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'a');
+ERROR: DEFAULT namespace is not supported
+SELECT * FROM XMLTABLE('.'
+ PASSING '<foo/>'
+ COLUMNS a text PATH 'foo/namespace::node()');
+ a
+--------------------------------------
+ http://www.w3.org/XML/1998/namespace
+(1 row)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+(6 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID
+--------------+-----------
+ India | 3
+ Japan | 3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID
+----+--------------+-----------
+ 1 | India | 3
+ 2 | Japan | 3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID
+----+--------------+-----------
+ 4 | India | 3
+ 5 | Japan | 3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id
+----
+ 4
+ 5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id
+----
+ 1
+ 2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID | rawdata
+----+--------------+-----------+------------------------------------------------------------------
+ 4 | India | 3 | <ROW id="4"> +
+ | | | <COUNTRY_ID>IN</COUNTRY_ID> +
+ | | | <COUNTRY_NAME>India</COUNTRY_NAME> +
+ | | | <REGION_ID>3</REGION_ID> +
+ | | | </ROW>
+ 5 | Japan | 3 | <ROW id="5"> +
+ | | | <COUNTRY_ID>JP</COUNTRY_ID> +
+ | | | <COUNTRY_NAME>Japan</COUNTRY_NAME> +
+ | | | <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ | | | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID | rawdata
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+ 4 | India | 3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+ 5 | Japan | 3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+ element
+----------------------
+ a1aa2a bbbbxxxcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR: more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+ c
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+ ent
+-----
+ '
+ "
+ &
+ <
+ >
+(5 rows)
+
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+ ent
+------------------
+ <ent>'</ent>
+ <ent>"</ent>
+ <ent>&amp;</ent>
+ <ent>&lt;</ent>
+ <ent>&gt;</ent>
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID
+--------------+-----------
+ Japan | 3
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+ Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+ Filter: ("xmltable"."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+ <COUNTRY_ID>CZ</COUNTRY_ID>
+ <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+ <COUNTRY_ID>DE</COUNTRY_ID>
+ <COUNTRY_NAME>Germany</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+ <COUNTRY_ID>FR</COUNTRY_ID>
+ <COUNTRY_NAME>France</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+ <COUNTRY_ID>EG</COUNTRY_ID>
+ <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+ <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+ <COUNTRY_ID>SD</COUNTRY_ID>
+ <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+ <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+----------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+ 10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
+ 11 | 2 | Germany | DE | 2 | | | not specified
+ 12 | 3 | France | FR | 2 | | | not specified
+ 20 | 1 | Egypt | EG | 1 | | | not specified
+ 21 | 2 | Sudan | SD | 1 | | | not specified
+(11 rows)
+
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+ WHERE region_id = 2;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+----------------+------------+-----------+------+------+---------------
+ 10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
+ 11 | 2 | Germany | DE | 2 | | | not specified
+ 12 | 3 | France | FR | 2 | | | not specified
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+ WHERE region_id = 2;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+ Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE' NOT NULL,
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ERROR: null is not allowed in column "size"
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+ x AS (SELECT proname, proowner, procost::numeric, pronargs,
+ array_to_string(proargnames,',') as proargnames,
+ case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+ FROM pg_proc WHERE proname = 'f_leak'),
+ y AS (SELECT xmlelement(name proc,
+ xmlforest(proname, proowner,
+ procost, pronargs,
+ proargnames, proargtypes)) as proc
+ FROM x),
+ z AS (SELECT xmltable.*
+ FROM y,
+ LATERAL xmltable('/proc' PASSING proc
+ COLUMNS proname name,
+ proowner oid,
+ procost float,
+ pronargs int,
+ proargnames text,
+ proargtypes text))
+ SELECT * FROM z
+ EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+-- multi line xml test, result should be empty too
+WITH
+ x AS (SELECT proname, proowner, procost::numeric, pronargs,
+ array_to_string(proargnames,',') as proargnames,
+ case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+ FROM pg_proc),
+ y AS (SELECT xmlelement(name data,
+ xmlagg(xmlelement(name proc,
+ xmlforest(proname, proowner, procost,
+ pronargs, proargnames, proargtypes)))) as doc
+ FROM x),
+ z AS (SELECT xmltable.*
+ FROM y,
+ LATERAL xmltable('/data/proc' PASSING doc
+ COLUMNS proname name,
+ proowner oid,
+ procost float,
+ pronargs int,
+ proargnames text,
+ proargtypes text))
+ SELECT * FROM z
+ EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a
+----
+ 11
+ 12
+ 13
+ 14
+(4 rows)
+
+-- XPath result can be boolean or number too
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)');
+ a | b | c | d | e
+----------+---+----+---+---
+ <a>a</a> | a | hi | t | 1
+(1 row)
+
+\x
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH 'node()', y xml PATH '/');
+-[ RECORD 1 ]-----------------------------------------------------------
+x | pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post
+y | <e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>+
+ |
+
+\x
+SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
+ a | b
+--------+--------------
+ <foo/> | &lt;foo/&gt;
+(1 row)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
new file mode 100644
index 0000000..897ed38
--- /dev/null
+++ b/src/test/regress/expected/xml_1.out
@@ -0,0 +1,1417 @@
+CREATE TABLE xmltest (
+ id int,
+ data xml
+);
+INSERT INTO xmltest VALUES (1, '<value>one</value>');
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmltest VALUES (1, '<value>one</value>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest VALUES (2, '<value>two</value>');
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmltest VALUES (2, '<value>two</value>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest VALUES (3, '<wrong');
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmltest VALUES (3, '<wrong');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltest;
+ id | data
+----+------
+(0 rows)
+
+SELECT xmlcomment('test');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlcomment('-test');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlcomment('test-');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlcomment('--test');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlcomment('te st');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlconcat(xmlcomment('hello'),
+ xmlelement(NAME qux, 'foo'),
+ xmlcomment('world'));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlconcat('hello', 'you');
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlconcat('hello', 'you');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlconcat(1, 2);
+ERROR: argument of XMLCONCAT must be type xml, not type integer
+LINE 1: SELECT xmlconcat(1, 2);
+ ^
+SELECT xmlconcat('bad', '<syntax');
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlconcat('bad', '<syntax');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlconcat('<foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlconcat('<foo/>', NULL, '<?xml version="1.1" standa...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlconcat('<?xml version="1.1"?><foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlconcat('<?xml version="1.1"?><foo/>', NULL, '<?xml...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlconcat(NULL);
+ xmlconcat
+-----------
+
+(1 row)
+
+SELECT xmlconcat(NULL, NULL);
+ xmlconcat
+-----------
+
+(1 row)
+
+SELECT xmlelement(name element,
+ xmlattributes (1 as one, 'deuce' as two),
+ 'content');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name element,
+ xmlattributes ('unnamed and wrong'));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name element, xmlelement(name nested, 'stuff'));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name num, 37);
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name foo, text 'bar');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name foo, xml 'bar');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name foo, text 'b<a/>r');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name foo, xml 'b<a/>r');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name foo, array[1, 2, 3]);
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SET xmlbinary TO base64;
+SELECT xmlelement(name foo, bytea 'bar');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SET xmlbinary TO hex;
+SELECT xmlelement(name foo, bytea 'bar');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name foo, xmlattributes(true as bar));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name foo, xmlattributes('2009-04-09 00:24:37'::timestamp as bar));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name foo, xmlattributes('infinity'::timestamp as bar));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as funnier));
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(content '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(content ' ');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(content 'abc');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(content '<abc>x</abc>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(content '<invalidentity>&</invalidentity>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(content '<invalidns xmlns=''&lt;''/>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(content '<relativens xmlns=''relative''/>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(content '<nosuchprefix:tag/>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(document ' ');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(document 'abc');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(document '<abc>x</abc>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(document '<invalidentity>&</abc>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(document '<undefinedentity>&idontexist;</abc>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(document '<invalidns xmlns=''&lt;''/>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(document '<relativens xmlns=''relative''/>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlparse(document '<nosuchprefix:tag/>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlpi(name foo);
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlpi(name xml);
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlpi(name xmlstuff);
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlpi(name foo, 'bar');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlpi(name foo, 'in?>valid');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlpi(name foo, null);
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlpi(name xml, null);
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlpi(name xmlstuff, null);
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlpi(name "xml-stylesheet", 'href="mystyle.css" type="text/css"');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlpi(name foo, ' bar');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlroot(xml '<foo/>', version no value, standalone no value);
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlroot(xml '<foo/>', version no value, standalone no...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlroot(xml '<foo/>', version '2.0');
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlroot(xml '<foo/>', version '2.0');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlroot(xml '<foo/>', version no value, standalone ye...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlroot(xml '<?xml version="1.1"?><foo/>', version no value, standalone yes);
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlroot(xml '<?xml version="1.1"?><foo/>', version no...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlroot(xmlroot(xml '<foo/>', version '1.0'), version '1.1', standalone no);
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlroot(xmlroot(xml '<foo/>', version '1.0'), version...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no);
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no value);
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value);
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlroot (
+ xmlelement (
+ name gazonk,
+ xmlattributes (
+ 'val' AS name,
+ 1 + 1 AS num
+ ),
+ xmlelement (
+ NAME qux,
+ 'foo'
+ )
+ ),
+ version '1.0',
+ standalone yes
+);
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlserialize(content data as character varying(20)) FROM xmltest;
+ xmlserialize
+--------------
+(0 rows)
+
+SELECT xmlserialize(content 'good' as char(10));
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlserialize(content 'good' as char(10));
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlserialize(document 'bad' as text);
+ERROR: unsupported XML feature
+LINE 1: SELECT xmlserialize(document 'bad' as text);
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<foo>bar</foo>' IS DOCUMENT;
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<foo>bar</foo>' IS DOCUMENT;
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<abc/>' IS NOT DOCUMENT;
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<abc/>' IS NOT DOCUMENT;
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml 'abc' IS NOT DOCUMENT;
+ERROR: unsupported XML feature
+LINE 1: SELECT xml 'abc' IS NOT DOCUMENT;
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT '<>' IS NOT DOCUMENT;
+ERROR: unsupported XML feature
+LINE 1: SELECT '<>' IS NOT DOCUMENT;
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlagg(data) FROM xmltest;
+ xmlagg
+--------
+
+(1 row)
+
+SELECT xmlagg(data) FROM xmltest WHERE id > 10;
+ xmlagg
+--------
+
+(1 row)
+
+SELECT xmlelement(name employees, xmlagg(xmlelement(name name, name))) FROM emp;
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- Check mapping SQL identifier to XML name
+SELECT xmlpi(name ":::_xml_abc135.%-&_");
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlpi(name "123");
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
+ERROR: unsupported XML feature
+LINE 1: PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SET XML OPTION DOCUMENT;
+EXECUTE foo ('<bar/>');
+ERROR: prepared statement "foo" does not exist
+EXECUTE foo ('bad');
+ERROR: prepared statement "foo" does not exist
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SET XML OPTION CONTENT;
+EXECUTE foo ('<bar/>');
+ERROR: prepared statement "foo" does not exist
+EXECUTE foo ('good');
+ERROR: prepared statement "foo" does not exist
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<!DOCTYPE a><a/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<!DOCTYPE a><a/>';
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: unsupported XML feature
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- Test backwards parsing
+CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
+CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
+ERROR: unsupported XML feature
+LINE 1: CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmlview5 AS SELECT xmlparse(content '<abc>x</abc>');
+CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmlview7 AS SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
+ERROR: unsupported XML feature
+LINE 1: CREATE VIEW xmlview7 AS SELECT xmlroot(xml '<foo/>', version...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
+ERROR: unsupported XML feature
+LINE 1: ...EATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as ...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
+ERROR: unsupported XML feature
+LINE 1: ...EATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as ...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_name, view_definition FROM information_schema.views
+ WHERE table_name LIKE 'xmlview%' ORDER BY 1;
+ table_name | view_definition
+------------+--------------------------------------------------------------------------------
+ xmlview1 | SELECT xmlcomment('test'::text) AS xmlcomment;
+ xmlview5 | SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
+(2 rows)
+
+-- Text XPath expressions evaluation
+SELECT xpath('/value', data) FROM xmltest;
+ xpath
+-------
+(0 rows)
+
+SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
+ ?column?
+----------
+(0 rows)
+
+SELECT xpath('', '<!-- error -->');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('', '<!-- error -->');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('//text()', '<local:data xmlns:local="http://12...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="ht...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('//loc:piece', '<local:data xmlns:local="http:/...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1" xmlns="http://127.0.0.2"><local:piece id="1"><internal>number one</internal><internal2/></local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('//loc:piece', '<local:data xmlns:local="http:/...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>'...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('//text()', '<root>&lt;</root>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('//text()', '<root>&lt;</root>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('//@value', '<root value="&lt;"/>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('//@value', '<root value="&lt;"/>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('''<<invalid>>''', '<root/>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('''<<invalid>>''', '<root/>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('/nosuchtag', '<root/>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('/nosuchtag', '<root/>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath('root', '<root/>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('root', '<root/>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- Round-trip non-ASCII data through xpath().
+DO $$
+DECLARE
+ xml_declaration text := '<?xml version="1.0" encoding="ISO-8859-1"?>';
+ degree_symbol text;
+ res xml[];
+BEGIN
+ -- Per the documentation, except when the server encoding is UTF8, xpath()
+ -- may not work on non-ASCII data. The untranslatable_character and
+ -- undefined_function traps below, currently dead code, will become relevant
+ -- if we remove this limitation.
+ IF current_setting('server_encoding') <> 'UTF8' THEN
+ RAISE LOG 'skip: encoding % unsupported for xpath',
+ current_setting('server_encoding');
+ RETURN;
+ END IF;
+
+ degree_symbol := convert_from('\xc2b0', 'UTF8');
+ res := xpath('text()', (xml_declaration ||
+ '<x>' || degree_symbol || '</x>')::xml);
+ IF degree_symbol <> res[1]::text THEN
+ RAISE 'expected % (%), got % (%)',
+ degree_symbol, convert_to(degree_symbol, 'UTF8'),
+ res[1], convert_to(res[1]::text, 'UTF8');
+ END IF;
+EXCEPTION
+ -- character with byte sequence 0xc2 0xb0 in encoding "UTF8" has no equivalent in encoding "LATIN8"
+ WHEN untranslatable_character
+ -- default conversion function for encoding "UTF8" to "MULE_INTERNAL" does not exist
+ OR undefined_function
+ -- unsupported XML feature
+ OR feature_not_supported THEN
+ RAISE LOG 'skip: %', SQLERRM;
+END
+$$;
+-- Test xmlexists and xpath_exists
+SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ERROR: unsupported XML feature
+LINE 1: ...sts('//town[text() = ''Toronto'']' PASSING BY REF '<towns><t...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ERROR: unsupported XML feature
+LINE 1: ...sts('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><t...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
+ERROR: unsupported XML feature
+LINE 1: ...LECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>')...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
+ERROR: unsupported XML feature
+LINE 1: ...ELECT xpath_exists('//town[text() = ''Toronto'']','<towns><t...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
+ERROR: unsupported XML feature
+LINE 1: ...ELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><t...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</n...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</n...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Budvar</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http:...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http:...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 0
+(1 row)
+
+CREATE TABLE query ( expr TEXT );
+INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']');
+SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data);
+ count
+-------
+ 0
+(1 row)
+
+-- Test xml_is_well_formed and variants
+SELECT xml_is_well_formed_document('<foo>bar</foo>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed_document('abc');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed_content('<foo>bar</foo>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed_content('abc');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SET xmloption TO DOCUMENT;
+SELECT xml_is_well_formed('abc');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<abc/>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<foo>bar</foo>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<foo>bar</foo');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<foo><bar>baz</foo>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<invalidentity>&</abc>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<invalidns xmlns=''&lt;''/>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SET xmloption TO CONTENT;
+SELECT xml_is_well_formed('abc');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- Since xpath() deals with namespaces, it's a bit stricter about
+-- what's well-formed and what's not. If we don't obey these rules
+-- (i.e. ignore namespace-related errors from libxml), xpath()
+-- fails in subtle ways. The following would for example produce
+-- the xml value
+-- <invalidns xmlns='<'/>
+-- which is invalid because '<' may not appear un-escaped in
+-- attribute values.
+-- Since different libxml versions emit slightly different
+-- error messages, we suppress the DETAIL in this test.
+\set VERBOSITY terse
+SELECT xpath('/*', '<invalidns xmlns=''&lt;''/>');
+ERROR: unsupported XML feature at character 20
+\set VERBOSITY default
+-- Again, the XML isn't well-formed for namespace purposes
+SELECT xpath('/*', '<nosuchprefix:tag/>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('/*', '<nosuchprefix:tag/>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- XPath deprecates relative namespaces, but they're not supposed to
+-- throw an error, only a warning.
+SELECT xpath('/*', '<relativens xmlns=''relative''/>');
+ERROR: unsupported XML feature
+LINE 1: SELECT xpath('/*', '<relativens xmlns=''relative''/>');
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- External entity references should not leak filesystem information.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- This might or might not load the requested DTD, but it mustn't throw error.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+ <COUNTRY_ID>AU</COUNTRY_ID>
+ <COUNTRY_NAME>Australia</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+ <COUNTRY_ID>CN</COUNTRY_ID>
+ <COUNTRY_NAME>China</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+ <COUNTRY_ID>HK</COUNTRY_ID>
+ <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+ <COUNTRY_ID>IN</COUNTRY_ID>
+ <COUNTRY_NAME>India</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+ <COUNTRY_ID>JP</COUNTRY_ID>
+ <COUNTRY_NAME>Japan</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+ <COUNTRY_ID>SG</COUNTRY_ID>
+ <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmldata VALUES('<ROWS>
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- XMLTABLE with columns
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+CREATE VIEW xmltableview1 AS SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+ "xmltable"._id,
+ "xmltable".country_name,
+ "xmltable".country_id,
+ "xmltable".region_id,
+ "xmltable".size,
+ "xmltable".unit,
+ "xmltable".premier_name
+ FROM ( SELECT xmldata.data
+ FROM xmldata) x,
+ LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+ QUERY PLAN
+-----------------------------------------
+ Nested Loop
+ -> Seq Scan on xmldata
+ -> Table Function Scan on "xmltable"
+(3 rows)
+
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- errors
+SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2);
+ERROR: XMLTABLE function has 1 columns available but 2 columns specified
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+ '/zz:rows/zz:row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'zz:a');
+ERROR: unsupported XML feature
+LINE 3: PASSING '<rows xmlns="http://x.y"><row...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+ '/zz:rows/zz:row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'zz:a');
+ERROR: unsupported XML feature
+LINE 3: PASSING '<rows xmlns="http://x.y"><row...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltableview2;
+ERROR: relation "xmltableview2" does not exist
+LINE 1: SELECT * FROM xmltableview2;
+ ^
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+ '/rows/row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'a');
+ERROR: unsupported XML feature
+LINE 3: PASSING '<rows xmlns="http://x.y"><row...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM XMLTABLE('.'
+ PASSING '<foo/>'
+ COLUMNS a text PATH 'foo/namespace::node()');
+ERROR: unsupported XML feature
+LINE 2: PASSING '<foo/>'
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- used in prepare statements
+PREPARE pp AS
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID
+--------------+-----------
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID
+----+--------------+-----------
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID
+----+--------------+-----------
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id
+----
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id
+----
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID | rawdata
+----+--------------+-----------+---------
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID | rawdata
+----+--------------+-----------+---------
+(0 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+ERROR: unsupported XML feature
+LINE 1: SELECT * FROM xmltable('/root' passing '<root><element>a1a<!...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR: unsupported XML feature
+LINE 1: SELECT * FROM xmltable('/root' passing '<root><element>a1a<!...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- CDATA test
+select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+ERROR: unsupported XML feature
+LINE 1: select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hel...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+ERROR: unsupported XML feature
+LINE 1: SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</en...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+ERROR: unsupported XML feature
+LINE 1: SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</en...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID
+--------------+-----------
+(0 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+ Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+ Filter: ("xmltable"."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+ <COUNTRY_ID>CZ</COUNTRY_ID>
+ <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+ <COUNTRY_ID>DE</COUNTRY_ID>
+ <COUNTRY_NAME>Germany</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+ <COUNTRY_ID>FR</COUNTRY_ID>
+ <COUNTRY_NAME>France</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmldata VALUES('<ROWS>
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+ <COUNTRY_ID>EG</COUNTRY_ID>
+ <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+ <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+ <COUNTRY_ID>SD</COUNTRY_ID>
+ <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+ <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmldata VALUES('<ROWS>
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+ WHERE region_id = 2;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+ WHERE region_id = 2;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+ Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE' NOT NULL,
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+ x AS (SELECT proname, proowner, procost::numeric, pronargs,
+ array_to_string(proargnames,',') as proargnames,
+ case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+ FROM pg_proc WHERE proname = 'f_leak'),
+ y AS (SELECT xmlelement(name proc,
+ xmlforest(proname, proowner,
+ procost, pronargs,
+ proargnames, proargtypes)) as proc
+ FROM x),
+ z AS (SELECT xmltable.*
+ FROM y,
+ LATERAL xmltable('/proc' PASSING proc
+ COLUMNS proname name,
+ proowner oid,
+ procost float,
+ pronargs int,
+ proargnames text,
+ proargtypes text))
+ SELECT * FROM z
+ EXCEPT SELECT * FROM x;
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- multi line xml test, result should be empty too
+WITH
+ x AS (SELECT proname, proowner, procost::numeric, pronargs,
+ array_to_string(proargnames,',') as proargnames,
+ case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+ FROM pg_proc),
+ y AS (SELECT xmlelement(name data,
+ xmlagg(xmlelement(name proc,
+ xmlforest(proname, proowner, procost,
+ pronargs, proargnames, proargtypes)))) as doc
+ FROM x),
+ z AS (SELECT xmltable.*
+ FROM y,
+ LATERAL xmltable('/data/proc' PASSING doc
+ COLUMNS proname name,
+ proowner oid,
+ procost float,
+ pronargs int,
+ proargnames text,
+ proargtypes text))
+ SELECT * FROM z
+ EXCEPT SELECT * FROM x;
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A')...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B')...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C')...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+ERROR: unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D')...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a
+---
+(0 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a
+---
+(0 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a
+---
+(0 rows)
+
+-- XPath result can be boolean or number too
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)');
+ERROR: unsupported XML feature
+LINE 1: SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml ...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+\x
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH 'node()', y xml PATH '/');
+ERROR: unsupported XML feature
+LINE 1: SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+\x
+SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
new file mode 100644
index 0000000..493c618
--- /dev/null
+++ b/src/test/regress/expected/xml_2.out
@@ -0,0 +1,1550 @@
+CREATE TABLE xmltest (
+ id int,
+ data xml
+);
+INSERT INTO xmltest VALUES (1, '<value>one</value>');
+INSERT INTO xmltest VALUES (2, '<value>two</value>');
+INSERT INTO xmltest VALUES (3, '<wrong');
+ERROR: invalid XML content
+LINE 1: INSERT INTO xmltest VALUES (3, '<wrong');
+ ^
+DETAIL: line 1: Couldn't find end of Start Tag wrong line 1
+SELECT * FROM xmltest;
+ id | data
+----+--------------------
+ 1 | <value>one</value>
+ 2 | <value>two</value>
+(2 rows)
+
+SELECT xmlcomment('test');
+ xmlcomment
+-------------
+ <!--test-->
+(1 row)
+
+SELECT xmlcomment('-test');
+ xmlcomment
+--------------
+ <!---test-->
+(1 row)
+
+SELECT xmlcomment('test-');
+ERROR: invalid XML comment
+SELECT xmlcomment('--test');
+ERROR: invalid XML comment
+SELECT xmlcomment('te st');
+ xmlcomment
+--------------
+ <!--te st-->
+(1 row)
+
+SELECT xmlconcat(xmlcomment('hello'),
+ xmlelement(NAME qux, 'foo'),
+ xmlcomment('world'));
+ xmlconcat
+----------------------------------------
+ <!--hello--><qux>foo</qux><!--world-->
+(1 row)
+
+SELECT xmlconcat('hello', 'you');
+ xmlconcat
+-----------
+ helloyou
+(1 row)
+
+SELECT xmlconcat(1, 2);
+ERROR: argument of XMLCONCAT must be type xml, not type integer
+LINE 1: SELECT xmlconcat(1, 2);
+ ^
+SELECT xmlconcat('bad', '<syntax');
+ERROR: invalid XML content
+LINE 1: SELECT xmlconcat('bad', '<syntax');
+ ^
+DETAIL: line 1: Couldn't find end of Start Tag syntax line 1
+SELECT xmlconcat('<foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
+ xmlconcat
+--------------
+ <foo/><bar/>
+(1 row)
+
+SELECT xmlconcat('<?xml version="1.1"?><foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
+ xmlconcat
+-----------------------------------
+ <?xml version="1.1"?><foo/><bar/>
+(1 row)
+
+SELECT xmlconcat(NULL);
+ xmlconcat
+-----------
+
+(1 row)
+
+SELECT xmlconcat(NULL, NULL);
+ xmlconcat
+-----------
+
+(1 row)
+
+SELECT xmlelement(name element,
+ xmlattributes (1 as one, 'deuce' as two),
+ 'content');
+ xmlelement
+------------------------------------------------
+ <element one="1" two="deuce">content</element>
+(1 row)
+
+SELECT xmlelement(name element,
+ xmlattributes ('unnamed and wrong'));
+ERROR: unnamed XML attribute value must be a column reference
+LINE 2: xmlattributes ('unnamed and wrong'));
+ ^
+SELECT xmlelement(name element, xmlelement(name nested, 'stuff'));
+ xmlelement
+-------------------------------------------
+ <element><nested>stuff</nested></element>
+(1 row)
+
+SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
+ xmlelement
+----------------------------------------------------------------------
+ <employee><name>sharon</name><age>25</age><pay>1000</pay></employee>
+ <employee><name>sam</name><age>30</age><pay>2000</pay></employee>
+ <employee><name>bill</name><age>20</age><pay>1000</pay></employee>
+ <employee><name>jeff</name><age>23</age><pay>600</pay></employee>
+ <employee><name>cim</name><age>30</age><pay>400</pay></employee>
+ <employee><name>linda</name><age>19</age><pay>100</pay></employee>
+(6 rows)
+
+SELECT xmlelement(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
+ERROR: XML attribute name "a" appears more than once
+LINE 1: ...ment(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
+ ^
+SELECT xmlelement(name num, 37);
+ xmlelement
+---------------
+ <num>37</num>
+(1 row)
+
+SELECT xmlelement(name foo, text 'bar');
+ xmlelement
+----------------
+ <foo>bar</foo>
+(1 row)
+
+SELECT xmlelement(name foo, xml 'bar');
+ xmlelement
+----------------
+ <foo>bar</foo>
+(1 row)
+
+SELECT xmlelement(name foo, text 'b<a/>r');
+ xmlelement
+-------------------------
+ <foo>b&lt;a/&gt;r</foo>
+(1 row)
+
+SELECT xmlelement(name foo, xml 'b<a/>r');
+ xmlelement
+-------------------
+ <foo>b<a/>r</foo>
+(1 row)
+
+SELECT xmlelement(name foo, array[1, 2, 3]);
+ xmlelement
+-------------------------------------------------------------------------
+ <foo><element>1</element><element>2</element><element>3</element></foo>
+(1 row)
+
+SET xmlbinary TO base64;
+SELECT xmlelement(name foo, bytea 'bar');
+ xmlelement
+-----------------
+ <foo>YmFy</foo>
+(1 row)
+
+SET xmlbinary TO hex;
+SELECT xmlelement(name foo, bytea 'bar');
+ xmlelement
+-------------------
+ <foo>626172</foo>
+(1 row)
+
+SELECT xmlelement(name foo, xmlattributes(true as bar));
+ xmlelement
+-------------------
+ <foo bar="true"/>
+(1 row)
+
+SELECT xmlelement(name foo, xmlattributes('2009-04-09 00:24:37'::timestamp as bar));
+ xmlelement
+----------------------------------
+ <foo bar="2009-04-09T00:24:37"/>
+(1 row)
+
+SELECT xmlelement(name foo, xmlattributes('infinity'::timestamp as bar));
+ERROR: timestamp out of range
+DETAIL: XML does not support infinite timestamp values.
+SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as funnier));
+ xmlelement
+------------------------------------------------------------
+ <foo funny="&lt;&gt;&amp;&quot;'" funnier="b&lt;a/&gt;r"/>
+(1 row)
+
+SELECT xmlparse(content '');
+ xmlparse
+----------
+
+(1 row)
+
+SELECT xmlparse(content ' ');
+ xmlparse
+----------
+
+(1 row)
+
+SELECT xmlparse(content 'abc');
+ xmlparse
+----------
+ abc
+(1 row)
+
+SELECT xmlparse(content '<abc>x</abc>');
+ xmlparse
+--------------
+ <abc>x</abc>
+(1 row)
+
+SELECT xmlparse(content '<invalidentity>&</invalidentity>');
+ERROR: invalid XML content
+DETAIL: line 1: xmlParseEntityRef: no name
+<invalidentity>&</invalidentity>
+ ^
+line 1: chunk is not well balanced
+SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
+ERROR: invalid XML content
+DETAIL: line 1: Entity 'idontexist' not defined
+<undefinedentity>&idontexist;</undefinedentity>
+ ^
+line 1: chunk is not well balanced
+SELECT xmlparse(content '<invalidns xmlns=''&lt;''/>');
+ xmlparse
+---------------------------
+ <invalidns xmlns='&lt;'/>
+(1 row)
+
+SELECT xmlparse(content '<relativens xmlns=''relative''/>');
+ xmlparse
+--------------------------------
+ <relativens xmlns='relative'/>
+(1 row)
+
+SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
+ERROR: invalid XML content
+DETAIL: line 1: Entity 'idontexist' not defined
+<twoerrors>&idontexist;</unbalanced>
+ ^
+line 1: Opening and ending tag mismatch: twoerrors line 1 and unbalanced
+line 1: chunk is not well balanced
+SELECT xmlparse(content '<nosuchprefix:tag/>');
+ xmlparse
+---------------------
+ <nosuchprefix:tag/>
+(1 row)
+
+SELECT xmlparse(document ' ');
+ERROR: invalid XML document
+DETAIL: line 1: Start tag expected, '<' not found
+SELECT xmlparse(document 'abc');
+ERROR: invalid XML document
+DETAIL: line 1: Start tag expected, '<' not found
+abc
+^
+SELECT xmlparse(document '<abc>x</abc>');
+ xmlparse
+--------------
+ <abc>x</abc>
+(1 row)
+
+SELECT xmlparse(document '<invalidentity>&</abc>');
+ERROR: invalid XML document
+DETAIL: line 1: xmlParseEntityRef: no name
+<invalidentity>&</abc>
+ ^
+line 1: Opening and ending tag mismatch: invalidentity line 1 and abc
+SELECT xmlparse(document '<undefinedentity>&idontexist;</abc>');
+ERROR: invalid XML document
+DETAIL: line 1: Entity 'idontexist' not defined
+<undefinedentity>&idontexist;</abc>
+ ^
+line 1: Opening and ending tag mismatch: undefinedentity line 1 and abc
+SELECT xmlparse(document '<invalidns xmlns=''&lt;''/>');
+ xmlparse
+---------------------------
+ <invalidns xmlns='&lt;'/>
+(1 row)
+
+SELECT xmlparse(document '<relativens xmlns=''relative''/>');
+ xmlparse
+--------------------------------
+ <relativens xmlns='relative'/>
+(1 row)
+
+SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
+ERROR: invalid XML document
+DETAIL: line 1: Entity 'idontexist' not defined
+<twoerrors>&idontexist;</unbalanced>
+ ^
+line 1: Opening and ending tag mismatch: twoerrors line 1 and unbalanced
+SELECT xmlparse(document '<nosuchprefix:tag/>');
+ xmlparse
+---------------------
+ <nosuchprefix:tag/>
+(1 row)
+
+SELECT xmlpi(name foo);
+ xmlpi
+---------
+ <?foo?>
+(1 row)
+
+SELECT xmlpi(name xml);
+ERROR: invalid XML processing instruction
+DETAIL: XML processing instruction target name cannot be "xml".
+SELECT xmlpi(name xmlstuff);
+ xmlpi
+--------------
+ <?xmlstuff?>
+(1 row)
+
+SELECT xmlpi(name foo, 'bar');
+ xmlpi
+-------------
+ <?foo bar?>
+(1 row)
+
+SELECT xmlpi(name foo, 'in?>valid');
+ERROR: invalid XML processing instruction
+DETAIL: XML processing instruction cannot contain "?>".
+SELECT xmlpi(name foo, null);
+ xmlpi
+-------
+
+(1 row)
+
+SELECT xmlpi(name xml, null);
+ERROR: invalid XML processing instruction
+DETAIL: XML processing instruction target name cannot be "xml".
+SELECT xmlpi(name xmlstuff, null);
+ xmlpi
+-------
+
+(1 row)
+
+SELECT xmlpi(name "xml-stylesheet", 'href="mystyle.css" type="text/css"');
+ xmlpi
+-------------------------------------------------------
+ <?xml-stylesheet href="mystyle.css" type="text/css"?>
+(1 row)
+
+SELECT xmlpi(name foo, ' bar');
+ xmlpi
+-------------
+ <?foo bar?>
+(1 row)
+
+SELECT xmlroot(xml '<foo/>', version no value, standalone no value);
+ xmlroot
+---------
+ <foo/>
+(1 row)
+
+SELECT xmlroot(xml '<foo/>', version '2.0');
+ xmlroot
+-----------------------------
+ <?xml version="2.0"?><foo/>
+(1 row)
+
+SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
+ xmlroot
+----------------------------------------------
+ <?xml version="1.0" standalone="yes"?><foo/>
+(1 row)
+
+SELECT xmlroot(xml '<?xml version="1.1"?><foo/>', version no value, standalone yes);
+ xmlroot
+----------------------------------------------
+ <?xml version="1.0" standalone="yes"?><foo/>
+(1 row)
+
+SELECT xmlroot(xmlroot(xml '<foo/>', version '1.0'), version '1.1', standalone no);
+ xmlroot
+---------------------------------------------
+ <?xml version="1.1" standalone="no"?><foo/>
+(1 row)
+
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no);
+ xmlroot
+---------------------------------------------
+ <?xml version="1.0" standalone="no"?><foo/>
+(1 row)
+
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no value);
+ xmlroot
+---------
+ <foo/>
+(1 row)
+
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value);
+ xmlroot
+----------------------------------------------
+ <?xml version="1.0" standalone="yes"?><foo/>
+(1 row)
+
+SELECT xmlroot (
+ xmlelement (
+ name gazonk,
+ xmlattributes (
+ 'val' AS name,
+ 1 + 1 AS num
+ ),
+ xmlelement (
+ NAME qux,
+ 'foo'
+ )
+ ),
+ version '1.0',
+ standalone yes
+);
+ xmlroot
+------------------------------------------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><gazonk name="val" num="2"><qux>foo</qux></gazonk>
+(1 row)
+
+SELECT xmlserialize(content data as character varying(20)) FROM xmltest;
+ xmlserialize
+--------------------
+ <value>one</value>
+ <value>two</value>
+(2 rows)
+
+SELECT xmlserialize(content 'good' as char(10));
+ xmlserialize
+--------------
+ good
+(1 row)
+
+SELECT xmlserialize(document 'bad' as text);
+ERROR: not an XML document
+SELECT xml '<foo>bar</foo>' IS DOCUMENT;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT xml '<abc/>' IS NOT DOCUMENT;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT xml 'abc' IS NOT DOCUMENT;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT '<>' IS NOT DOCUMENT;
+ERROR: invalid XML content
+LINE 1: SELECT '<>' IS NOT DOCUMENT;
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<>
+ ^
+SELECT xmlagg(data) FROM xmltest;
+ xmlagg
+--------------------------------------
+ <value>one</value><value>two</value>
+(1 row)
+
+SELECT xmlagg(data) FROM xmltest WHERE id > 10;
+ xmlagg
+--------
+
+(1 row)
+
+SELECT xmlelement(name employees, xmlagg(xmlelement(name name, name))) FROM emp;
+ xmlelement
+--------------------------------------------------------------------------------------------------------------------------------
+ <employees><name>sharon</name><name>sam</name><name>bill</name><name>jeff</name><name>cim</name><name>linda</name></employees>
+(1 row)
+
+-- Check mapping SQL identifier to XML name
+SELECT xmlpi(name ":::_xml_abc135.%-&_");
+ xmlpi
+-------------------------------------------------
+ <?_x003A_::_x005F_xml_abc135._x0025_-_x0026__?>
+(1 row)
+
+SELECT xmlpi(name "123");
+ xmlpi
+---------------
+ <?_x0031_23?>
+(1 row)
+
+PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
+SET XML OPTION DOCUMENT;
+EXECUTE foo ('<bar/>');
+ xmlconcat
+--------------
+ <foo/><bar/>
+(1 row)
+
+EXECUTE foo ('bad');
+ERROR: invalid XML document
+LINE 1: EXECUTE foo ('bad');
+ ^
+DETAIL: line 1: Start tag expected, '<' not found
+bad
+^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: invalid XML document
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+ ^
+SET XML OPTION CONTENT;
+EXECUTE foo ('<bar/>');
+ xmlconcat
+--------------
+ <foo/><bar/>
+(1 row)
+
+EXECUTE foo ('good');
+ xmlconcat
+------------
+ <foo/>good
+(1 row)
+
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+ xml
+--------------------------------------------------------------------
+ <!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+ xml
+------------------------------
+ <!-- hi--> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!DOCTYPE a><a/>';
+ xml
+------------------
+ <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<!-- hi--> oops <!DOCTYPE a><a/>
+ ^
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ ^
+DETAIL: line 1: StartTag: invalid element name
+<!-- hi--> <oops/> <!DOCTYPE a><a/>
+ ^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR: invalid XML content
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+ ^
+DETAIL: line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+ ^
+-- Test backwards parsing
+CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
+CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
+CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&');
+CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
+CREATE VIEW xmlview5 AS SELECT xmlparse(content '<abc>x</abc>');
+CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar');
+CREATE VIEW xmlview7 AS SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
+CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
+CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
+SELECT table_name, view_definition FROM information_schema.views
+ WHERE table_name LIKE 'xmlview%' ORDER BY 1;
+ table_name | view_definition
+------------+-------------------------------------------------------------------------------------------------------------------
+ xmlview1 | SELECT xmlcomment('test'::text) AS xmlcomment;
+ xmlview2 | SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
+ xmlview3 | SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
+ xmlview4 | SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ | FROM emp;
+ xmlview5 | SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
+ xmlview6 | SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
+ xmlview7 | SELECT XMLROOT('<foo/>'::xml, VERSION NO VALUE, STANDALONE YES) AS "xmlroot";
+ xmlview8 | SELECT (XMLSERIALIZE(CONTENT 'good'::xml AS character(10)))::character(10) AS "xmlserialize";
+ xmlview9 | SELECT XMLSERIALIZE(CONTENT 'good'::xml AS text) AS "xmlserialize";
+(9 rows)
+
+-- Text XPath expressions evaluation
+SELECT xpath('/value', data) FROM xmltest;
+ xpath
+----------------------
+ {<value>one</value>}
+ {<value>two</value>}
+(2 rows)
+
+SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
+ ?column?
+----------
+ t
+ t
+(2 rows)
+
+SELECT xpath('', '<!-- error -->');
+ERROR: empty XPath expression
+CONTEXT: SQL function "xpath" statement 1
+SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+ xpath
+----------------
+ {"number one"}
+(1 row)
+
+SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath
+-------
+ {1,2}
+(1 row)
+
+SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath
+------------------------------------------------------------------------------------------------------------------------------------------------
+ {"<local:piece xmlns:local=\"http://127.0.0.1\" id=\"1\">number one</local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
+(1 row)
+
+SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1" xmlns="http://127.0.0.2"><local:piece id="1"><internal>number one</internal><internal2/></local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+ xpath
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"<local:piece xmlns:local=\"http://127.0.0.1\" xmlns=\"http://127.0.0.2\" id=\"1\"><internal>number one</internal><internal2/></local:piece>","<local:piece xmlns:local=\"http://127.0.0.1\" id=\"2\"/>"}
+(1 row)
+
+SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
+ xpath
+-------------------------
+ {<b>two</b>,<b>etc</b>}
+(1 row)
+
+SELECT xpath('//text()', '<root>&lt;</root>');
+ xpath
+--------
+ {&lt;}
+(1 row)
+
+SELECT xpath('//@value', '<root value="&lt;"/>');
+ xpath
+--------
+ {&lt;}
+(1 row)
+
+SELECT xpath('''<<invalid>>''', '<root/>');
+ xpath
+---------------------------
+ {&lt;&lt;invalid&gt;&gt;}
+(1 row)
+
+SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
+ xpath
+-------
+ {3}
+(1 row)
+
+SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
+ xpath
+---------
+ {false}
+(1 row)
+
+SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
+ xpath
+--------
+ {true}
+(1 row)
+
+SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
+ xpath
+--------
+ {root}
+(1 row)
+
+SELECT xpath('/nosuchtag', '<root/>');
+ xpath
+-------
+ {}
+(1 row)
+
+SELECT xpath('root', '<root/>');
+ xpath
+-----------
+ {<root/>}
+(1 row)
+
+-- Round-trip non-ASCII data through xpath().
+DO $$
+DECLARE
+ xml_declaration text := '<?xml version="1.0" encoding="ISO-8859-1"?>';
+ degree_symbol text;
+ res xml[];
+BEGIN
+ -- Per the documentation, except when the server encoding is UTF8, xpath()
+ -- may not work on non-ASCII data. The untranslatable_character and
+ -- undefined_function traps below, currently dead code, will become relevant
+ -- if we remove this limitation.
+ IF current_setting('server_encoding') <> 'UTF8' THEN
+ RAISE LOG 'skip: encoding % unsupported for xpath',
+ current_setting('server_encoding');
+ RETURN;
+ END IF;
+
+ degree_symbol := convert_from('\xc2b0', 'UTF8');
+ res := xpath('text()', (xml_declaration ||
+ '<x>' || degree_symbol || '</x>')::xml);
+ IF degree_symbol <> res[1]::text THEN
+ RAISE 'expected % (%), got % (%)',
+ degree_symbol, convert_to(degree_symbol, 'UTF8'),
+ res[1], convert_to(res[1]::text, 'UTF8');
+ END IF;
+EXCEPTION
+ -- character with byte sequence 0xc2 0xb0 in encoding "UTF8" has no equivalent in encoding "LATIN8"
+ WHEN untranslatable_character
+ -- default conversion function for encoding "UTF8" to "MULE_INTERNAL" does not exist
+ OR undefined_function
+ -- unsupported XML feature
+ OR feature_not_supported THEN
+ RAISE LOG 'skip: %', SQLERRM;
+END
+$$;
+-- Test xmlexists and xpath_exists
+SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ xmlexists
+-----------
+ f
+(1 row)
+
+SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+ xmlexists
+-----------
+ t
+(1 row)
+
+SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
+ xmlexists
+-----------
+ t
+(1 row)
+
+SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
+ xpath_exists
+--------------
+ f
+(1 row)
+
+SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
+ xpath_exists
+--------------
+ t
+(1 row)
+
+SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
+ xpath_exists
+--------------
+ t
+(1 row)
+
+INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Budvar</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
+ count
+-------
+ 2
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
+ count
+-------
+ 1
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data);
+ count
+-------
+ 2
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data);
+ count
+-------
+ 1
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 2
+(1 row)
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]);
+ count
+-------
+ 1
+(1 row)
+
+CREATE TABLE query ( expr TEXT );
+INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']');
+SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data);
+ count
+-------
+ 2
+(1 row)
+
+-- Test xml_is_well_formed and variants
+SELECT xml_is_well_formed_document('<foo>bar</foo>');
+ xml_is_well_formed_document
+-----------------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed_document('abc');
+ xml_is_well_formed_document
+-----------------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed_content('<foo>bar</foo>');
+ xml_is_well_formed_content
+----------------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed_content('abc');
+ xml_is_well_formed_content
+----------------------------
+ t
+(1 row)
+
+SET xmloption TO DOCUMENT;
+SELECT xml_is_well_formed('abc');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<abc/>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<foo>bar</foo>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<foo>bar</foo');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<foo><bar>baz</foo>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<invalidentity>&</abc>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SELECT xml_is_well_formed('<invalidns xmlns=''&lt;''/>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
+ xml_is_well_formed
+--------------------
+ f
+(1 row)
+
+SET xmloption TO CONTENT;
+SELECT xml_is_well_formed('abc');
+ xml_is_well_formed
+--------------------
+ t
+(1 row)
+
+-- Since xpath() deals with namespaces, it's a bit stricter about
+-- what's well-formed and what's not. If we don't obey these rules
+-- (i.e. ignore namespace-related errors from libxml), xpath()
+-- fails in subtle ways. The following would for example produce
+-- the xml value
+-- <invalidns xmlns='<'/>
+-- which is invalid because '<' may not appear un-escaped in
+-- attribute values.
+-- Since different libxml versions emit slightly different
+-- error messages, we suppress the DETAIL in this test.
+\set VERBOSITY terse
+SELECT xpath('/*', '<invalidns xmlns=''&lt;''/>');
+ERROR: could not parse XML document
+\set VERBOSITY default
+-- Again, the XML isn't well-formed for namespace purposes
+SELECT xpath('/*', '<nosuchprefix:tag/>');
+ERROR: could not parse XML document
+DETAIL: line 1: Namespace prefix nosuchprefix on tag is not defined
+<nosuchprefix:tag/>
+ ^
+CONTEXT: SQL function "xpath" statement 1
+-- XPath deprecates relative namespaces, but they're not supposed to
+-- throw an error, only a warning.
+SELECT xpath('/*', '<relativens xmlns=''relative''/>');
+WARNING: line 1: xmlns: URI relative is not absolute
+<relativens xmlns='relative'/>
+ ^
+ xpath
+--------------------------------------
+ {"<relativens xmlns=\"relative\"/>"}
+(1 row)
+
+-- External entity references should not leak filesystem information.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>');
+ xmlparse
+-----------------------------------------------------------------
+ <!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>
+(1 row)
+
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>');
+ xmlparse
+-----------------------------------------------------------------------
+ <!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>
+(1 row)
+
+-- This might or might not load the requested DTD, but it mustn't throw error.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>');
+ xmlparse
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>
+(1 row)
+
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+ <COUNTRY_ID>AU</COUNTRY_ID>
+ <COUNTRY_NAME>Australia</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+ <COUNTRY_ID>CN</COUNTRY_ID>
+ <COUNTRY_NAME>China</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+ <COUNTRY_ID>HK</COUNTRY_ID>
+ <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+ <COUNTRY_ID>IN</COUNTRY_ID>
+ <COUNTRY_NAME>India</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+ <COUNTRY_ID>JP</COUNTRY_ID>
+ <COUNTRY_NAME>Japan</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+ <COUNTRY_ID>SG</COUNTRY_ID>
+ <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+-- XMLTABLE with columns
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+(6 rows)
+
+CREATE VIEW xmltableview1 AS SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+(6 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+ "xmltable"._id,
+ "xmltable".country_name,
+ "xmltable".country_id,
+ "xmltable".region_id,
+ "xmltable".size,
+ "xmltable".unit,
+ "xmltable".premier_name
+ FROM ( SELECT xmldata.data
+ FROM xmldata) x,
+ LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+ QUERY PLAN
+-----------------------------------------
+ Nested Loop
+ -> Seq Scan on xmldata
+ -> Table Function Scan on "xmltable"
+(3 rows)
+
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME/text()'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- errors
+SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2);
+ERROR: XMLTABLE function has 1 columns available but 2 columns specified
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+ '/zz:rows/zz:row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'zz:a');
+ a
+----
+ 10
+(1 row)
+
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+ '/zz:rows/zz:row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'zz:a');
+SELECT * FROM xmltableview2;
+ a
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+ '/rows/row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'a');
+ERROR: DEFAULT namespace is not supported
+SELECT * FROM XMLTABLE('.'
+ PASSING '<foo/>'
+ COLUMNS a text PATH 'foo/namespace::node()');
+ a
+--------------------------------------
+ http://www.w3.org/XML/1998/namespace
+(1 row)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+--------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+(6 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID
+--------------+-----------
+ India | 3
+ Japan | 3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID
+----+--------------+-----------
+ 1 | India | 3
+ 2 | Japan | 3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID
+----+--------------+-----------
+ 4 | India | 3
+ 5 | Japan | 3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id
+----
+ 4
+ 5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id
+----
+ 1
+ 2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID | rawdata
+----+--------------+-----------+------------------------------------------------------------------
+ 4 | India | 3 | <ROW id="4"> +
+ | | | <COUNTRY_ID>IN</COUNTRY_ID> +
+ | | | <COUNTRY_NAME>India</COUNTRY_NAME> +
+ | | | <REGION_ID>3</REGION_ID> +
+ | | | </ROW>
+ 5 | Japan | 3 | <ROW id="5"> +
+ | | | <COUNTRY_ID>JP</COUNTRY_ID> +
+ | | | <COUNTRY_NAME>Japan</COUNTRY_NAME> +
+ | | | <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ | | | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID | rawdata
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+ 4 | India | 3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+ 5 | Japan | 3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+ element
+----------------------
+ a1aa2a bbbbxxxcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR: more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+ c
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+ ent
+-----
+ '
+ "
+ &
+ <
+ >
+(5 rows)
+
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+ ent
+------------------
+ <ent>'</ent>
+ <ent>"</ent>
+ <ent>&amp;</ent>
+ <ent>&lt;</ent>
+ <ent>&gt;</ent>
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID
+--------------+-----------
+ Japan | 3
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+ Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+ Filter: ("xmltable"."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+ <COUNTRY_ID>CZ</COUNTRY_ID>
+ <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+ <COUNTRY_ID>DE</COUNTRY_ID>
+ <COUNTRY_NAME>Germany</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+ <COUNTRY_ID>FR</COUNTRY_ID>
+ <COUNTRY_NAME>France</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+ <COUNTRY_ID>EG</COUNTRY_ID>
+ <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+ <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+ <COUNTRY_ID>SD</COUNTRY_ID>
+ <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+ <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+----------------+------------+-----------+------+------+---------------
+ 1 | 1 | Australia | AU | 3 | | | not specified
+ 2 | 2 | China | CN | 3 | | | not specified
+ 3 | 3 | HongKong | HK | 3 | | | not specified
+ 4 | 4 | India | IN | 3 | | | not specified
+ 5 | 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | 6 | Singapore | SG | 3 | 791 | km | not specified
+ 10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
+ 11 | 2 | Germany | DE | 2 | | | not specified
+ 12 | 3 | France | FR | 2 | | | not specified
+ 20 | 1 | Egypt | EG | 1 | | | not specified
+ 21 | 2 | Sudan | SD | 1 | | | not specified
+(11 rows)
+
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+ WHERE region_id = 2;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name
+----+-----+----------------+------------+-----------+------+------+---------------
+ 10 | 1 | Czech Republic | CZ | 2 | | | Milos Zeman
+ 11 | 2 | Germany | DE | 2 | | | not specified
+ 12 | 3 | France | FR | 2 | | | not specified
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+ WHERE region_id = 2;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ -> Seq Scan on public.xmldata
+ Output: xmldata.data
+ -> Table Function Scan on "xmltable"
+ Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+ Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+ Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE' NOT NULL,
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ERROR: null is not allowed in column "size"
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+ x AS (SELECT proname, proowner, procost::numeric, pronargs,
+ array_to_string(proargnames,',') as proargnames,
+ case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+ FROM pg_proc WHERE proname = 'f_leak'),
+ y AS (SELECT xmlelement(name proc,
+ xmlforest(proname, proowner,
+ procost, pronargs,
+ proargnames, proargtypes)) as proc
+ FROM x),
+ z AS (SELECT xmltable.*
+ FROM y,
+ LATERAL xmltable('/proc' PASSING proc
+ COLUMNS proname name,
+ proowner oid,
+ procost float,
+ pronargs int,
+ proargnames text,
+ proargtypes text))
+ SELECT * FROM z
+ EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+-- multi line xml test, result should be empty too
+WITH
+ x AS (SELECT proname, proowner, procost::numeric, pronargs,
+ array_to_string(proargnames,',') as proargnames,
+ case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+ FROM pg_proc),
+ y AS (SELECT xmlelement(name data,
+ xmlagg(xmlelement(name proc,
+ xmlforest(proname, proowner, procost,
+ pronargs, proargnames, proargtypes)))) as doc
+ FROM x),
+ z AS (SELECT xmltable.*
+ FROM y,
+ LATERAL xmltable('/data/proc' PASSING doc
+ COLUMNS proname name,
+ proowner oid,
+ procost float,
+ pronargs int,
+ proargnames text,
+ proargtypes text))
+ SELECT * FROM z
+ EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a
+----
+ 11
+ 12
+ 13
+ 14
+(4 rows)
+
+-- XPath result can be boolean or number too
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)');
+ a | b | c | d | e
+----------+---+----+---+---
+ <a>a</a> | a | hi | t | 1
+(1 row)
+
+\x
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH 'node()', y xml PATH '/');
+-[ RECORD 1 ]-----------------------------------------------------------
+x | pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post
+y | <e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>+
+ |
+
+\x
+SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
+ a | b
+--------+--------------
+ <foo/> | &lt;foo/&gt;
+(1 row)
+
diff --git a/src/test/regress/expected/xmlmap.out b/src/test/regress/expected/xmlmap.out
new file mode 100644
index 0000000..ccc5460
--- /dev/null
+++ b/src/test/regress/expected/xmlmap.out
@@ -0,0 +1,1305 @@
+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
+---------------------------------------------------------------
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ +
+ <row> +
+ <a>1</a> +
+ <b>one</b> +
+ </row> +
+ +
+ <row> +
+ <a>2</a> +
+ <b>two</b> +
+ </row> +
+ +
+ <row> +
+ <a>-1</a> +
+ </row> +
+ +
+ </test1> +
+
+(1 row)
+
+SELECT table_to_xml('testxmlschema.test1', true, false, 'foo');
+ table_to_xml
+---------------------------------------------------------------------------
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="foo">+
+ +
+ <row> +
+ <a>1</a> +
+ <b>one</b> +
+ </row> +
+ +
+ <row> +
+ <a>2</a> +
+ <b>two</b> +
+ </row> +
+ +
+ <row> +
+ <a>-1</a> +
+ <b xsi:nil="true"/> +
+ </row> +
+ +
+ </test1> +
+
+(1 row)
+
+SELECT table_to_xml('testxmlschema.test1', false, true, '');
+ table_to_xml
+---------------------------------------------------------------
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ <a>1</a> +
+ <b>one</b> +
+ </test1> +
+ +
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ <a>2</a> +
+ <b>two</b> +
+ </test1> +
+ +
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ <a>-1</a> +
+ </test1> +
+ +
+
+(1 row)
+
+SELECT table_to_xml('testxmlschema.test1', true, true, '');
+ table_to_xml
+---------------------------------------------------------------
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ <a>1</a> +
+ <b>one</b> +
+ </test1> +
+ +
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ <a>2</a> +
+ <b>two</b> +
+ </test1> +
+ +
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ <a>-1</a> +
+ <b xsi:nil="true"/> +
+ </test1> +
+ +
+
+(1 row)
+
+SELECT table_to_xml('testxmlschema.test2', false, false, '');
+ table_to_xml
+---------------------------------------------------------------
+ <test2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ +
+ <row> +
+ <z>55</z> +
+ <y>abc</y> +
+ <x>def </x> +
+ <w>98.60</w> +
+ <v>2</v> +
+ <u>999</u> +
+ <t>0</t> +
+ <s>21:07:00</s> +
+ <stz>21:11:00+05</stz> +
+ <r>2009-06-08T21:07:30</r> +
+ <rtz>2009-06-08T21:07:30-07:00</rtz> +
+ <q>2009-06-08</q> +
+ <o>ABC</o> +
+ <n>true</n> +
+ <m>WFla</m> +
+ </row> +
+ +
+ </test2> +
+
+(1 row)
+
+SELECT table_to_xmlschema('testxmlschema.test1', false, false, '');
+ table_to_xmlschema
+-----------------------------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType.regression.testxmlschema.test1"> +
+ <xsd:sequence> +
+ <xsd:element name="a" type="INTEGER" minOccurs="0"></xsd:element> +
+ <xsd:element name="b" type="UDT.regression.pg_catalog.text" minOccurs="0"></xsd:element> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:complexType name="TableType.regression.testxmlschema.test1"> +
+ <xsd:sequence> +
+ <xsd:element name="row" type="RowType.regression.testxmlschema.test1" minOccurs="0" maxOccurs="unbounded"/>+
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="test1" type="TableType.regression.testxmlschema.test1"/> +
+ +
+ </xsd:schema>
+(1 row)
+
+SELECT table_to_xmlschema('testxmlschema.test1', true, false, '');
+ table_to_xmlschema
+-----------------------------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType.regression.testxmlschema.test1"> +
+ <xsd:sequence> +
+ <xsd:element name="a" type="INTEGER" nillable="true"></xsd:element> +
+ <xsd:element name="b" type="UDT.regression.pg_catalog.text" nillable="true"></xsd:element> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:complexType name="TableType.regression.testxmlschema.test1"> +
+ <xsd:sequence> +
+ <xsd:element name="row" type="RowType.regression.testxmlschema.test1" minOccurs="0" maxOccurs="unbounded"/>+
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="test1" type="TableType.regression.testxmlschema.test1"/> +
+ +
+ </xsd:schema>
+(1 row)
+
+SELECT table_to_xmlschema('testxmlschema.test1', false, true, 'foo');
+ table_to_xmlschema
+----------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema" +
+ targetNamespace="foo" +
+ elementFormDefault="qualified"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType.regression.testxmlschema.test1"> +
+ <xsd:sequence> +
+ <xsd:element name="a" type="INTEGER" minOccurs="0"></xsd:element> +
+ <xsd:element name="b" type="UDT.regression.pg_catalog.text" minOccurs="0"></xsd:element>+
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="test1" type="RowType.regression.testxmlschema.test1"/> +
+ +
+ </xsd:schema>
+(1 row)
+
+SELECT table_to_xmlschema('testxmlschema.test1', true, true, '');
+ table_to_xmlschema
+------------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType.regression.testxmlschema.test1"> +
+ <xsd:sequence> +
+ <xsd:element name="a" type="INTEGER" nillable="true"></xsd:element> +
+ <xsd:element name="b" type="UDT.regression.pg_catalog.text" nillable="true"></xsd:element>+
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="test1" type="RowType.regression.testxmlschema.test1"/> +
+ +
+ </xsd:schema>
+(1 row)
+
+SELECT table_to_xmlschema('testxmlschema.test2', false, false, '');
+ table_to_xmlschema
+----------------------------------------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="VARCHAR"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="CHAR"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="NUMERIC"> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="SMALLINT"> +
+ <xsd:restriction base="xsd:short"> +
+ <xsd:maxInclusive value="32767"/> +
+ <xsd:minInclusive value="-32768"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="BIGINT"> +
+ <xsd:restriction base="xsd:long"> +
+ <xsd:maxInclusive value="9223372036854775807"/> +
+ <xsd:minInclusive value="-9223372036854775808"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="REAL"> +
+ <xsd:restriction base="xsd:float"></xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIME"> +
+ <xsd:restriction base="xsd:time"> +
+ <xsd:pattern value="\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIME_WTZ"> +
+ <xsd:restriction base="xsd:time"> +
+ <xsd:pattern value="\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?(\+|-)\p{Nd}{2}:\p{Nd}{2}"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIMESTAMP"> +
+ <xsd:restriction base="xsd:dateTime"> +
+ <xsd:pattern value="\p{Nd}{4}-\p{Nd}{2}-\p{Nd}{2}T\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIMESTAMP_WTZ"> +
+ <xsd:restriction base="xsd:dateTime"> +
+ <xsd:pattern value="\p{Nd}{4}-\p{Nd}{2}-\p{Nd}{2}T\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?(\+|-)\p{Nd}{2}:\p{Nd}{2}"/>+
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="DATE"> +
+ <xsd:restriction base="xsd:date"> +
+ <xsd:pattern value="\p{Nd}{4}-\p{Nd}{2}-\p{Nd}{2}"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType mixed="true"> +
+ <xsd:sequence> +
+ <xsd:any name="element" minOccurs="0" maxOccurs="unbounded" processContents="skip"/> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:simpleType name="Domain.regression.public.testxmldomain"> +
+ <xsd:restriction base="VARCHAR"/> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="BOOLEAN"> +
+ <xsd:restriction base="xsd:boolean"></xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.bytea"> +
+ <xsd:restriction base="xsd:base64Binary"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType.regression.testxmlschema.test2"> +
+ <xsd:sequence> +
+ <xsd:element name="z" type="INTEGER" minOccurs="0"></xsd:element> +
+ <xsd:element name="y" type="VARCHAR" minOccurs="0"></xsd:element> +
+ <xsd:element name="x" type="CHAR" minOccurs="0"></xsd:element> +
+ <xsd:element name="w" type="NUMERIC" minOccurs="0"></xsd:element> +
+ <xsd:element name="v" type="SMALLINT" minOccurs="0"></xsd:element> +
+ <xsd:element name="u" type="BIGINT" minOccurs="0"></xsd:element> +
+ <xsd:element name="t" type="REAL" minOccurs="0"></xsd:element> +
+ <xsd:element name="s" type="TIME" minOccurs="0"></xsd:element> +
+ <xsd:element name="stz" type="TIME_WTZ" minOccurs="0"></xsd:element> +
+ <xsd:element name="r" type="TIMESTAMP" minOccurs="0"></xsd:element> +
+ <xsd:element name="rtz" type="TIMESTAMP_WTZ" minOccurs="0"></xsd:element> +
+ <xsd:element name="q" type="DATE" minOccurs="0"></xsd:element> +
+ <xsd:element name="p" type="XML" minOccurs="0"></xsd:element> +
+ <xsd:element name="o" type="Domain.regression.public.testxmldomain" minOccurs="0"></xsd:element> +
+ <xsd:element name="n" type="BOOLEAN" minOccurs="0"></xsd:element> +
+ <xsd:element name="m" type="UDT.regression.pg_catalog.bytea" minOccurs="0"></xsd:element> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:complexType name="TableType.regression.testxmlschema.test2"> +
+ <xsd:sequence> +
+ <xsd:element name="row" type="RowType.regression.testxmlschema.test2" minOccurs="0" maxOccurs="unbounded"/> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="test2" type="TableType.regression.testxmlschema.test2"/> +
+ +
+ </xsd:schema>
+(1 row)
+
+SELECT table_to_xml_and_xmlschema('testxmlschema.test1', false, false, '');
+ table_to_xml_and_xmlschema
+-----------------------------------------------------------------------------------------------------------------
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="#"> +
+ +
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType.regression.testxmlschema.test1"> +
+ <xsd:sequence> +
+ <xsd:element name="a" type="INTEGER" minOccurs="0"></xsd:element> +
+ <xsd:element name="b" type="UDT.regression.pg_catalog.text" minOccurs="0"></xsd:element> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:complexType name="TableType.regression.testxmlschema.test1"> +
+ <xsd:sequence> +
+ <xsd:element name="row" type="RowType.regression.testxmlschema.test1" minOccurs="0" maxOccurs="unbounded"/>+
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="test1" type="TableType.regression.testxmlschema.test1"/> +
+ +
+ </xsd:schema> +
+ +
+ <row> +
+ <a>1</a> +
+ <b>one</b> +
+ </row> +
+ +
+ <row> +
+ <a>2</a> +
+ <b>two</b> +
+ </row> +
+ +
+ <row> +
+ <a>-1</a> +
+ </row> +
+ +
+ </test1> +
+
+(1 row)
+
+SELECT table_to_xml_and_xmlschema('testxmlschema.test1', true, false, '');
+ table_to_xml_and_xmlschema
+-----------------------------------------------------------------------------------------------------------------
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="#"> +
+ +
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType.regression.testxmlschema.test1"> +
+ <xsd:sequence> +
+ <xsd:element name="a" type="INTEGER" nillable="true"></xsd:element> +
+ <xsd:element name="b" type="UDT.regression.pg_catalog.text" nillable="true"></xsd:element> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:complexType name="TableType.regression.testxmlschema.test1"> +
+ <xsd:sequence> +
+ <xsd:element name="row" type="RowType.regression.testxmlschema.test1" minOccurs="0" maxOccurs="unbounded"/>+
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="test1" type="TableType.regression.testxmlschema.test1"/> +
+ +
+ </xsd:schema> +
+ +
+ <row> +
+ <a>1</a> +
+ <b>one</b> +
+ </row> +
+ +
+ <row> +
+ <a>2</a> +
+ <b>two</b> +
+ </row> +
+ +
+ <row> +
+ <a>-1</a> +
+ <b xsi:nil="true"/> +
+ </row> +
+ +
+ </test1> +
+
+(1 row)
+
+SELECT table_to_xml_and_xmlschema('testxmlschema.test1', false, true, '');
+ table_to_xml_and_xmlschema
+----------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType.regression.testxmlschema.test1"> +
+ <xsd:sequence> +
+ <xsd:element name="a" type="INTEGER" minOccurs="0"></xsd:element> +
+ <xsd:element name="b" type="UDT.regression.pg_catalog.text" minOccurs="0"></xsd:element>+
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="test1" type="RowType.regression.testxmlschema.test1"/> +
+ +
+ </xsd:schema> +
+ +
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +
+ <a>1</a> +
+ <b>one</b> +
+ </test1> +
+ +
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +
+ <a>2</a> +
+ <b>two</b> +
+ </test1> +
+ +
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +
+ <a>-1</a> +
+ </test1> +
+ +
+
+(1 row)
+
+SELECT table_to_xml_and_xmlschema('testxmlschema.test1', true, true, 'foo');
+ table_to_xml_and_xmlschema
+------------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema" +
+ targetNamespace="foo" +
+ elementFormDefault="qualified"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType.regression.testxmlschema.test1"> +
+ <xsd:sequence> +
+ <xsd:element name="a" type="INTEGER" nillable="true"></xsd:element> +
+ <xsd:element name="b" type="UDT.regression.pg_catalog.text" nillable="true"></xsd:element>+
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="test1" type="RowType.regression.testxmlschema.test1"/> +
+ +
+ </xsd:schema> +
+ +
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="foo"> +
+ <a>1</a> +
+ <b>one</b> +
+ </test1> +
+ +
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="foo"> +
+ <a>2</a> +
+ <b>two</b> +
+ </test1> +
+ +
+ <test1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="foo"> +
+ <a>-1</a> +
+ <b xsi:nil="true"/> +
+ </test1> +
+ +
+
+(1 row)
+
+SELECT query_to_xml('SELECT * FROM testxmlschema.test1', false, false, '');
+ query_to_xml
+---------------------------------------------------------------
+ <table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ +
+ <row> +
+ <a>1</a> +
+ <b>one</b> +
+ </row> +
+ +
+ <row> +
+ <a>2</a> +
+ <b>two</b> +
+ </row> +
+ +
+ <row> +
+ <a>-1</a> +
+ </row> +
+ +
+ </table> +
+
+(1 row)
+
+SELECT query_to_xmlschema('SELECT * FROM testxmlschema.test1', false, false, '');
+ query_to_xmlschema
+----------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType"> +
+ <xsd:sequence> +
+ <xsd:element name="a" type="INTEGER" minOccurs="0"></xsd:element> +
+ <xsd:element name="b" type="UDT.regression.pg_catalog.text" minOccurs="0"></xsd:element>+
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:complexType name="TableType"> +
+ <xsd:sequence> +
+ <xsd:element name="row" type="RowType" minOccurs="0" maxOccurs="unbounded"/> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="table" type="TableType"/> +
+ +
+ </xsd:schema>
+(1 row)
+
+SELECT query_to_xml_and_xmlschema('SELECT * FROM testxmlschema.test1', true, true, '');
+ query_to_xml_and_xmlschema
+------------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType"> +
+ <xsd:sequence> +
+ <xsd:element name="a" type="INTEGER" nillable="true"></xsd:element> +
+ <xsd:element name="b" type="UDT.regression.pg_catalog.text" nillable="true"></xsd:element>+
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="row" type="RowType"/> +
+ +
+ </xsd:schema> +
+ +
+ <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +
+ <a>1</a> +
+ <b>one</b> +
+ </row> +
+ +
+ <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +
+ <a>2</a> +
+ <b>two</b> +
+ </row> +
+ +
+ <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +
+ <a>-1</a> +
+ <b xsi:nil="true"/> +
+ </row> +
+ +
+
+(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
+-------------------------------------------------------------
+ <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ <a>-1</a> +
+ </row> +
+ +
+ <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ <a>1</a> +
+ <b>one</b> +
+ </row> +
+ +
+ <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ <a>2</a> +
+ <b>two</b> +
+ </row> +
+ +
+
+(1 row)
+
+SELECT cursor_to_xmlschema('xc'::refcursor, false, true, '');
+ cursor_to_xmlschema
+----------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType"> +
+ <xsd:sequence> +
+ <xsd:element name="a" type="INTEGER" minOccurs="0"></xsd:element> +
+ <xsd:element name="b" type="UDT.regression.pg_catalog.text" minOccurs="0"></xsd:element>+
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="row" type="RowType"/> +
+ +
+ </xsd:schema>
+(1 row)
+
+MOVE BACKWARD ALL IN xc;
+SELECT cursor_to_xml('xc'::refcursor, 5, true, false, '');
+ cursor_to_xml
+---------------------------------------------------------------
+ <table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ +
+ <row> +
+ <a>-1</a> +
+ <b xsi:nil="true"/> +
+ </row> +
+ +
+ <row> +
+ <a>1</a> +
+ <b>one</b> +
+ </row> +
+ +
+ <row> +
+ <a>2</a> +
+ <b>two</b> +
+ </row> +
+ +
+ </table> +
+
+(1 row)
+
+SELECT cursor_to_xmlschema('xc'::refcursor, true, false, '');
+ cursor_to_xmlschema
+------------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="RowType"> +
+ <xsd:sequence> +
+ <xsd:element name="a" type="INTEGER" nillable="true"></xsd:element> +
+ <xsd:element name="b" type="UDT.regression.pg_catalog.text" nillable="true"></xsd:element>+
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:complexType name="TableType"> +
+ <xsd:sequence> +
+ <xsd:element name="row" type="RowType" minOccurs="0" maxOccurs="unbounded"/> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="table" type="TableType"/> +
+ +
+ </xsd:schema>
+(1 row)
+
+SELECT schema_to_xml('testxmlschema', false, true, '');
+ schema_to_xml
+-----------------------------------------------------------------------
+ <testxmlschema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ +
+ <test1> +
+ <a>1</a> +
+ <b>one</b> +
+ </test1> +
+ +
+ <test1> +
+ <a>2</a> +
+ <b>two</b> +
+ </test1> +
+ +
+ <test1> +
+ <a>-1</a> +
+ </test1> +
+ +
+ +
+ <test2> +
+ <z>55</z> +
+ <y>abc</y> +
+ <x>def </x> +
+ <w>98.60</w> +
+ <v>2</v> +
+ <u>999</u> +
+ <t>0</t> +
+ <s>21:07:00</s> +
+ <stz>21:11:00+05</stz> +
+ <r>2009-06-08T21:07:30</r> +
+ <rtz>2009-06-08T21:07:30-07:00</rtz> +
+ <q>2009-06-08</q> +
+ <o>ABC</o> +
+ <n>true</n> +
+ <m>WFla</m> +
+ </test2> +
+ +
+ +
+ </testxmlschema> +
+
+(1 row)
+
+SELECT schema_to_xml('testxmlschema', true, false, '');
+ schema_to_xml
+-----------------------------------------------------------------------
+ <testxmlschema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ +
+ <test1> +
+ +
+ <row> +
+ <a>1</a> +
+ <b>one</b> +
+ </row> +
+ +
+ <row> +
+ <a>2</a> +
+ <b>two</b> +
+ </row> +
+ +
+ <row> +
+ <a>-1</a> +
+ <b xsi:nil="true"/> +
+ </row> +
+ +
+ </test1> +
+ +
+ <test2> +
+ +
+ <row> +
+ <z>55</z> +
+ <y>abc</y> +
+ <x>def </x> +
+ <w>98.60</w> +
+ <v>2</v> +
+ <u>999</u> +
+ <t>0</t> +
+ <s>21:07:00</s> +
+ <stz>21:11:00+05</stz> +
+ <r>2009-06-08T21:07:30</r> +
+ <rtz>2009-06-08T21:07:30-07:00</rtz> +
+ <q>2009-06-08</q> +
+ <p xsi:nil="true"/> +
+ <o>ABC</o> +
+ <n>true</n> +
+ <m>WFla</m> +
+ </row> +
+ +
+ </test2> +
+ +
+ </testxmlschema> +
+
+(1 row)
+
+SELECT schema_to_xmlschema('testxmlschema', false, true, '');
+ schema_to_xmlschema
+----------------------------------------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="VARCHAR"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="CHAR"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="NUMERIC"> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="SMALLINT"> +
+ <xsd:restriction base="xsd:short"> +
+ <xsd:maxInclusive value="32767"/> +
+ <xsd:minInclusive value="-32768"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="BIGINT"> +
+ <xsd:restriction base="xsd:long"> +
+ <xsd:maxInclusive value="9223372036854775807"/> +
+ <xsd:minInclusive value="-9223372036854775808"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="REAL"> +
+ <xsd:restriction base="xsd:float"></xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIME"> +
+ <xsd:restriction base="xsd:time"> +
+ <xsd:pattern value="\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIME_WTZ"> +
+ <xsd:restriction base="xsd:time"> +
+ <xsd:pattern value="\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?(\+|-)\p{Nd}{2}:\p{Nd}{2}"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIMESTAMP"> +
+ <xsd:restriction base="xsd:dateTime"> +
+ <xsd:pattern value="\p{Nd}{4}-\p{Nd}{2}-\p{Nd}{2}T\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIMESTAMP_WTZ"> +
+ <xsd:restriction base="xsd:dateTime"> +
+ <xsd:pattern value="\p{Nd}{4}-\p{Nd}{2}-\p{Nd}{2}T\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?(\+|-)\p{Nd}{2}:\p{Nd}{2}"/>+
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="DATE"> +
+ <xsd:restriction base="xsd:date"> +
+ <xsd:pattern value="\p{Nd}{4}-\p{Nd}{2}-\p{Nd}{2}"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType mixed="true"> +
+ <xsd:sequence> +
+ <xsd:any name="element" minOccurs="0" maxOccurs="unbounded" processContents="skip"/> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:simpleType name="Domain.regression.public.testxmldomain"> +
+ <xsd:restriction base="VARCHAR"/> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="BOOLEAN"> +
+ <xsd:restriction base="xsd:boolean"></xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.bytea"> +
+ <xsd:restriction base="xsd:base64Binary"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="SchemaType.regression.testxmlschema"> +
+ <xsd:sequence> +
+ <xsd:element name="test1" type="RowType.regression.testxmlschema.test1" minOccurs="0" maxOccurs="unbounded"/> +
+ <xsd:element name="test2" type="RowType.regression.testxmlschema.test2" minOccurs="0" maxOccurs="unbounded"/> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="testxmlschema" type="SchemaType.regression.testxmlschema"/> +
+ +
+ </xsd:schema>
+(1 row)
+
+SELECT schema_to_xmlschema('testxmlschema', true, false, '');
+ schema_to_xmlschema
+----------------------------------------------------------------------------------------------------------------------------
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="VARCHAR"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="CHAR"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="NUMERIC"> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="SMALLINT"> +
+ <xsd:restriction base="xsd:short"> +
+ <xsd:maxInclusive value="32767"/> +
+ <xsd:minInclusive value="-32768"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="BIGINT"> +
+ <xsd:restriction base="xsd:long"> +
+ <xsd:maxInclusive value="9223372036854775807"/> +
+ <xsd:minInclusive value="-9223372036854775808"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="REAL"> +
+ <xsd:restriction base="xsd:float"></xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIME"> +
+ <xsd:restriction base="xsd:time"> +
+ <xsd:pattern value="\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIME_WTZ"> +
+ <xsd:restriction base="xsd:time"> +
+ <xsd:pattern value="\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?(\+|-)\p{Nd}{2}:\p{Nd}{2}"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIMESTAMP"> +
+ <xsd:restriction base="xsd:dateTime"> +
+ <xsd:pattern value="\p{Nd}{4}-\p{Nd}{2}-\p{Nd}{2}T\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIMESTAMP_WTZ"> +
+ <xsd:restriction base="xsd:dateTime"> +
+ <xsd:pattern value="\p{Nd}{4}-\p{Nd}{2}-\p{Nd}{2}T\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?(\+|-)\p{Nd}{2}:\p{Nd}{2}"/>+
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="DATE"> +
+ <xsd:restriction base="xsd:date"> +
+ <xsd:pattern value="\p{Nd}{4}-\p{Nd}{2}-\p{Nd}{2}"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType mixed="true"> +
+ <xsd:sequence> +
+ <xsd:any name="element" minOccurs="0" maxOccurs="unbounded" processContents="skip"/> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:simpleType name="Domain.regression.public.testxmldomain"> +
+ <xsd:restriction base="VARCHAR"/> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="BOOLEAN"> +
+ <xsd:restriction base="xsd:boolean"></xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.bytea"> +
+ <xsd:restriction base="xsd:base64Binary"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="SchemaType.regression.testxmlschema"> +
+ <xsd:all> +
+ <xsd:element name="test1" type="TableType.regression.testxmlschema.test1"/> +
+ <xsd:element name="test2" type="TableType.regression.testxmlschema.test2"/> +
+ </xsd:all> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="testxmlschema" type="SchemaType.regression.testxmlschema"/> +
+ +
+ </xsd:schema>
+(1 row)
+
+SELECT schema_to_xml_and_xmlschema('testxmlschema', true, true, 'foo');
+ schema_to_xml_and_xmlschema
+----------------------------------------------------------------------------------------------------------------------------
+ <testxmlschema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="foo" xsi:schemaLocation="foo #"> +
+ +
+ <xsd:schema +
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema" +
+ targetNamespace="foo" +
+ elementFormDefault="qualified"> +
+ +
+ <xsd:simpleType name="INTEGER"> +
+ <xsd:restriction base="xsd:int"> +
+ <xsd:maxInclusive value="2147483647"/> +
+ <xsd:minInclusive value="-2147483648"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.text"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="VARCHAR"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="CHAR"> +
+ <xsd:restriction base="xsd:string"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="NUMERIC"> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="SMALLINT"> +
+ <xsd:restriction base="xsd:short"> +
+ <xsd:maxInclusive value="32767"/> +
+ <xsd:minInclusive value="-32768"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="BIGINT"> +
+ <xsd:restriction base="xsd:long"> +
+ <xsd:maxInclusive value="9223372036854775807"/> +
+ <xsd:minInclusive value="-9223372036854775808"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="REAL"> +
+ <xsd:restriction base="xsd:float"></xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIME"> +
+ <xsd:restriction base="xsd:time"> +
+ <xsd:pattern value="\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIME_WTZ"> +
+ <xsd:restriction base="xsd:time"> +
+ <xsd:pattern value="\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?(\+|-)\p{Nd}{2}:\p{Nd}{2}"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIMESTAMP"> +
+ <xsd:restriction base="xsd:dateTime"> +
+ <xsd:pattern value="\p{Nd}{4}-\p{Nd}{2}-\p{Nd}{2}T\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="TIMESTAMP_WTZ"> +
+ <xsd:restriction base="xsd:dateTime"> +
+ <xsd:pattern value="\p{Nd}{4}-\p{Nd}{2}-\p{Nd}{2}T\p{Nd}{2}:\p{Nd}{2}:\p{Nd}{2}(.\p{Nd}+)?(\+|-)\p{Nd}{2}:\p{Nd}{2}"/>+
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="DATE"> +
+ <xsd:restriction base="xsd:date"> +
+ <xsd:pattern value="\p{Nd}{4}-\p{Nd}{2}-\p{Nd}{2}"/> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType mixed="true"> +
+ <xsd:sequence> +
+ <xsd:any name="element" minOccurs="0" maxOccurs="unbounded" processContents="skip"/> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:simpleType name="Domain.regression.public.testxmldomain"> +
+ <xsd:restriction base="VARCHAR"/> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="BOOLEAN"> +
+ <xsd:restriction base="xsd:boolean"></xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:simpleType name="UDT.regression.pg_catalog.bytea"> +
+ <xsd:restriction base="xsd:base64Binary"> +
+ </xsd:restriction> +
+ </xsd:simpleType> +
+ +
+ <xsd:complexType name="SchemaType.regression.testxmlschema"> +
+ <xsd:sequence> +
+ <xsd:element name="test1" type="RowType.regression.testxmlschema.test1" minOccurs="0" maxOccurs="unbounded"/> +
+ <xsd:element name="test2" type="RowType.regression.testxmlschema.test2" minOccurs="0" maxOccurs="unbounded"/> +
+ </xsd:sequence> +
+ </xsd:complexType> +
+ +
+ <xsd:element name="testxmlschema" type="SchemaType.regression.testxmlschema"/> +
+ +
+ </xsd:schema> +
+ +
+ <test1> +
+ <a>1</a> +
+ <b>one</b> +
+ </test1> +
+ +
+ <test1> +
+ <a>2</a> +
+ <b>two</b> +
+ </test1> +
+ +
+ <test1> +
+ <a>-1</a> +
+ <b xsi:nil="true"/> +
+ </test1> +
+ +
+ +
+ <test2> +
+ <z>55</z> +
+ <y>abc</y> +
+ <x>def </x> +
+ <w>98.60</w> +
+ <v>2</v> +
+ <u>999</u> +
+ <t>0</t> +
+ <s>21:07:00</s> +
+ <stz>21:11:00+05</stz> +
+ <r>2009-06-08T21:07:30</r> +
+ <rtz>2009-06-08T21:07:30-07:00</rtz> +
+ <q>2009-06-08</q> +
+ <p xsi:nil="true"/> +
+ <o>ABC</o> +
+ <n>true</n> +
+ <m>WFla</m> +
+ </test2> +
+ +
+ +
+ </testxmlschema> +
+
+(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
+------------------------------------------------------------------
+ <c1>true</c1><c2>true</c2><c3>2013-02-21</c3><c4>2013-02-21</c4>
+(1 row)
+
+SELECT table_to_xml('testxmlschema.test3', true, true, '');
+ table_to_xml
+---------------------------------------------------------------
+ <test3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
+ <c1>true</c1> +
+ <c2>true</c2> +
+ <c3>2013-02-21</c3> +
+ <c4>2013-02-21</c4> +
+ </test3> +
+ +
+
+(1 row)
+
diff --git a/src/test/regress/expected/xmlmap_1.out b/src/test/regress/expected/xmlmap_1.out
new file mode 100644
index 0000000..4504acd
--- /dev/null
+++ b/src/test/regress/expected/xmlmap_1.out
@@ -0,0 +1,135 @@
+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, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xml('testxmlschema.test1', true, false, 'foo');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xml('testxmlschema.test1', false, true, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xml('testxmlschema.test1', true, true, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xml('testxmlschema.test2', false, false, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xmlschema('testxmlschema.test1', false, false, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xmlschema('testxmlschema.test1', true, false, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xmlschema('testxmlschema.test1', false, true, 'foo');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xmlschema('testxmlschema.test1', true, true, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xmlschema('testxmlschema.test2', false, false, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xml_and_xmlschema('testxmlschema.test1', false, false, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xml_and_xmlschema('testxmlschema.test1', true, false, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xml_and_xmlschema('testxmlschema.test1', false, true, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xml_and_xmlschema('testxmlschema.test1', true, true, 'foo');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT query_to_xml('SELECT * FROM testxmlschema.test1', false, false, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT query_to_xmlschema('SELECT * FROM testxmlschema.test1', false, false, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT query_to_xml_and_xmlschema('SELECT * FROM testxmlschema.test1', true, true, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+DECLARE xc CURSOR WITH HOLD FOR SELECT * FROM testxmlschema.test1 ORDER BY 1, 2;
+SELECT cursor_to_xml('xc'::refcursor, 5, false, true, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT cursor_to_xmlschema('xc'::refcursor, false, true, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+MOVE BACKWARD ALL IN xc;
+SELECT cursor_to_xml('xc'::refcursor, 5, true, false, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT cursor_to_xmlschema('xc'::refcursor, true, false, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT schema_to_xml('testxmlschema', false, true, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT schema_to_xml('testxmlschema', true, false, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT schema_to_xmlschema('testxmlschema', false, true, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT schema_to_xmlschema('testxmlschema', true, false, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT schema_to_xml_and_xmlschema('testxmlschema', true, true, 'foo');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+-- 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;
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT table_to_xml('testxmlschema.test3', true, true, '');
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
diff --git a/src/test/regress/input/constraints.source b/src/test/regress/input/constraints.source
new file mode 100644
index 0000000..6bb7648
--- /dev/null
+++ b/src/test/regress/input/constraints.source
@@ -0,0 +1,554 @@
+--
+-- CONSTRAINTS
+-- Constraints can be specified with:
+-- - DEFAULT clause
+-- - CHECK clauses
+-- - PRIMARY KEY clauses
+-- - UNIQUE clauses
+-- - EXCLUDE clauses
+--
+
+--
+-- DEFAULT syntax
+--
+
+CREATE TABLE DEFAULT_TBL (i int DEFAULT 100,
+ x text DEFAULT 'vadim', f float8 DEFAULT 123.456);
+
+INSERT INTO DEFAULT_TBL VALUES (1, 'thomas', 57.0613);
+INSERT INTO DEFAULT_TBL VALUES (1, 'bruce');
+INSERT INTO DEFAULT_TBL (i, f) VALUES (2, 987.654);
+INSERT INTO DEFAULT_TBL (x) VALUES ('marc');
+INSERT INTO DEFAULT_TBL VALUES (3, null, 1.0);
+
+SELECT * FROM DEFAULT_TBL;
+
+CREATE SEQUENCE DEFAULT_SEQ;
+
+CREATE TABLE DEFAULTEXPR_TBL (i1 int DEFAULT 100 + (200-199) * 2,
+ i2 int DEFAULT nextval('default_seq'));
+
+INSERT INTO DEFAULTEXPR_TBL VALUES (-1, -2);
+INSERT INTO DEFAULTEXPR_TBL (i1) VALUES (-3);
+INSERT INTO DEFAULTEXPR_TBL (i2) VALUES (-4);
+INSERT INTO DEFAULTEXPR_TBL (i2) VALUES (NULL);
+
+SELECT * FROM DEFAULTEXPR_TBL;
+
+-- syntax errors
+-- test for extraneous comma
+CREATE TABLE error_tbl (i int DEFAULT (100, ));
+-- this will fail because gram.y uses b_expr not a_expr for defaults,
+-- to avoid a shift/reduce conflict that arises from NOT NULL being
+-- part of the column definition syntax:
+CREATE TABLE error_tbl (b1 bool DEFAULT 1 IN (1, 2));
+-- this should work, however:
+CREATE TABLE error_tbl (b1 bool DEFAULT (1 IN (1, 2)));
+
+DROP TABLE error_tbl;
+
+--
+-- CHECK syntax
+--
+
+CREATE TABLE CHECK_TBL (x int,
+ CONSTRAINT CHECK_CON CHECK (x > 3));
+
+INSERT INTO CHECK_TBL VALUES (5);
+INSERT INTO CHECK_TBL VALUES (4);
+INSERT INTO CHECK_TBL VALUES (3);
+INSERT INTO CHECK_TBL VALUES (2);
+INSERT INTO CHECK_TBL VALUES (6);
+INSERT INTO CHECK_TBL VALUES (1);
+
+SELECT * FROM CHECK_TBL;
+
+CREATE SEQUENCE CHECK_SEQ;
+
+CREATE TABLE CHECK2_TBL (x int, y text, z int,
+ CONSTRAINT SEQUENCE_CON
+ CHECK (x > 3 and y <> 'check failed' and z < 8));
+
+INSERT INTO CHECK2_TBL VALUES (4, 'check ok', -2);
+INSERT INTO CHECK2_TBL VALUES (1, 'x check failed', -2);
+INSERT INTO CHECK2_TBL VALUES (5, 'z check failed', 10);
+INSERT INTO CHECK2_TBL VALUES (0, 'check failed', -2);
+INSERT INTO CHECK2_TBL VALUES (6, 'check failed', 11);
+INSERT INTO CHECK2_TBL VALUES (7, 'check ok', 7);
+
+SELECT * from CHECK2_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 constraints on system columns
+--
+
+CREATE TABLE SYS_COL_CHECK_TBL (city text, state text, is_capital bool,
+ altitude int,
+ CHECK (NOT (is_capital AND tableoid::regclass::text = 'sys_col_check_tbl')));
+
+INSERT INTO SYS_COL_CHECK_TBL VALUES ('Seattle', 'Washington', false, 100);
+INSERT INTO SYS_COL_CHECK_TBL VALUES ('Olympia', 'Washington', true, 100);
+
+SELECT *, tableoid::regclass::text FROM SYS_COL_CHECK_TBL;
+
+DROP TABLE SYS_COL_CHECK_TBL;
+
+--
+-- Check constraints on system columns other then TableOid should return error
+--
+CREATE TABLE SYS_COL_CHECK_TBL (city text, state text, is_capital bool,
+ altitude int,
+ CHECK (NOT (is_capital AND ctid::text = 'sys_col_check_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;
+
+--
+-- Check constraints on INSERT INTO
+--
+
+DELETE FROM INSERT_TBL;
+
+ALTER SEQUENCE INSERT_SEQ RESTART WITH 4;
+
+CREATE TEMP TABLE tmp (xd INT, yd TEXT, zd INT);
+
+INSERT INTO tmp VALUES (null, 'Y', null);
+INSERT INTO tmp VALUES (5, '!check failed', null);
+INSERT INTO tmp VALUES (null, 'try again', null);
+INSERT INTO INSERT_TBL(y) select yd from tmp;
+
+SELECT * FROM INSERT_TBL;
+
+INSERT INTO INSERT_TBL SELECT * FROM tmp WHERE yd = 'try again';
+INSERT INTO INSERT_TBL(y,z) SELECT yd, -7 FROM tmp WHERE yd = 'try again';
+INSERT INTO INSERT_TBL(y,z) SELECT yd, -8 FROM tmp WHERE yd = 'try again';
+
+SELECT * FROM INSERT_TBL;
+
+DROP TABLE tmp;
+
+--
+-- Check constraints on UPDATE
+--
+
+UPDATE INSERT_TBL SET x = NULL WHERE x = 5;
+UPDATE INSERT_TBL SET x = 6 WHERE x = 6;
+UPDATE INSERT_TBL SET x = -z, z = -x;
+UPDATE INSERT_TBL SET x = z, z = x;
+
+SELECT * FROM INSERT_TBL;
+
+-- DROP TABLE INSERT_TBL;
+
+--
+-- Check constraints on COPY FROM
+--
+
+CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
+ CONSTRAINT COPY_CON
+ CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
+
+COPY COPY_TBL FROM '@abs_srcdir@/data/constro.data';
+
+SELECT * FROM COPY_TBL;
+
+COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data';
+
+SELECT * FROM COPY_TBL;
+
+--
+-- Primary keys
+--
+
+CREATE TABLE PRIMARY_TBL (i int PRIMARY KEY, t text);
+
+INSERT INTO PRIMARY_TBL VALUES (1, 'one');
+INSERT INTO PRIMARY_TBL VALUES (2, 'two');
+INSERT INTO PRIMARY_TBL VALUES (1, 'three');
+INSERT INTO PRIMARY_TBL VALUES (4, 'three');
+INSERT INTO PRIMARY_TBL VALUES (5, 'one');
+INSERT INTO PRIMARY_TBL (t) VALUES ('six');
+
+SELECT * FROM PRIMARY_TBL;
+
+DROP TABLE PRIMARY_TBL;
+
+CREATE TABLE PRIMARY_TBL (i int, t text,
+ PRIMARY KEY(i,t));
+
+INSERT INTO PRIMARY_TBL VALUES (1, 'one');
+INSERT INTO PRIMARY_TBL VALUES (2, 'two');
+INSERT INTO PRIMARY_TBL VALUES (1, 'three');
+INSERT INTO PRIMARY_TBL VALUES (4, 'three');
+INSERT INTO PRIMARY_TBL VALUES (5, 'one');
+INSERT INTO PRIMARY_TBL (t) VALUES ('six');
+
+SELECT * FROM PRIMARY_TBL;
+
+DROP TABLE PRIMARY_TBL;
+
+--
+-- Unique keys
+--
+
+CREATE TABLE UNIQUE_TBL (i int UNIQUE, t text);
+
+INSERT INTO UNIQUE_TBL VALUES (1, 'one');
+INSERT INTO UNIQUE_TBL VALUES (2, 'two');
+INSERT INTO UNIQUE_TBL VALUES (1, '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');
+
+INSERT INTO UNIQUE_TBL VALUES (5, 'five-upsert-insert') ON CONFLICT (i) DO UPDATE SET t = 'five-upsert-update';
+INSERT INTO UNIQUE_TBL VALUES (6, 'six-upsert-insert') ON CONFLICT (i) DO UPDATE SET t = 'six-upsert-update';
+-- should fail
+INSERT INTO UNIQUE_TBL VALUES (1, 'a'), (2, 'b'), (2, 'b') ON CONFLICT (i) DO UPDATE SET t = 'fails';
+
+SELECT * FROM UNIQUE_TBL;
+
+DROP TABLE UNIQUE_TBL;
+
+CREATE TABLE UNIQUE_TBL (i int, t text,
+ UNIQUE(i,t));
+
+INSERT INTO UNIQUE_TBL VALUES (1, 'one');
+INSERT INTO UNIQUE_TBL VALUES (2, 'two');
+INSERT INTO UNIQUE_TBL VALUES (1, 'three');
+INSERT INTO UNIQUE_TBL VALUES (1, 'one');
+INSERT INTO UNIQUE_TBL VALUES (5, 'one');
+INSERT INTO UNIQUE_TBL (t) VALUES ('six');
+
+SELECT * FROM UNIQUE_TBL;
+
+DROP TABLE UNIQUE_TBL;
+
+--
+-- Deferrable unique constraints
+--
+
+CREATE TABLE unique_tbl (i int UNIQUE DEFERRABLE, t text);
+
+INSERT INTO unique_tbl VALUES (0, 'one');
+INSERT INTO unique_tbl VALUES (1, 'two');
+INSERT INTO unique_tbl VALUES (2, 'tree');
+INSERT INTO unique_tbl VALUES (3, 'four');
+INSERT INTO unique_tbl VALUES (4, 'five');
+
+BEGIN;
+
+-- default is immediate so this should fail right away
+UPDATE unique_tbl SET i = 1 WHERE i = 0;
+
+ROLLBACK;
+
+-- check is done at end of statement, so this should succeed
+UPDATE unique_tbl SET i = i+1;
+
+SELECT * FROM unique_tbl;
+
+-- explicitly defer the constraint
+BEGIN;
+
+SET CONSTRAINTS unique_tbl_i_key DEFERRED;
+
+INSERT INTO unique_tbl VALUES (3, 'three');
+DELETE FROM unique_tbl WHERE t = 'tree'; -- makes constraint valid again
+
+COMMIT; -- should succeed
+
+SELECT * FROM unique_tbl;
+
+-- try adding an initially deferred constraint
+ALTER TABLE unique_tbl DROP CONSTRAINT unique_tbl_i_key;
+ALTER TABLE unique_tbl ADD CONSTRAINT unique_tbl_i_key
+ UNIQUE (i) DEFERRABLE INITIALLY DEFERRED;
+
+BEGIN;
+
+INSERT INTO unique_tbl VALUES (1, 'five');
+INSERT INTO unique_tbl VALUES (5, 'one');
+UPDATE unique_tbl SET i = 4 WHERE i = 2;
+UPDATE unique_tbl SET i = 2 WHERE i = 4 AND t = 'four';
+DELETE FROM unique_tbl WHERE i = 1 AND t = 'one';
+DELETE FROM unique_tbl WHERE i = 5 AND t = 'five';
+
+COMMIT;
+
+SELECT * FROM unique_tbl;
+
+-- should fail at commit-time
+BEGIN;
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now
+COMMIT; -- should fail
+
+-- make constraint check immediate
+BEGIN;
+
+SET CONSTRAINTS ALL IMMEDIATE;
+
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should fail
+
+COMMIT;
+
+-- forced check when SET CONSTRAINTS is called
+BEGIN;
+
+SET CONSTRAINTS ALL DEFERRED;
+
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now
+
+SET CONSTRAINTS ALL IMMEDIATE; -- should fail
+
+COMMIT;
+
+-- test deferrable UNIQUE with a partitioned table
+CREATE TABLE parted_uniq_tbl (i int UNIQUE DEFERRABLE) partition by range (i);
+CREATE TABLE parted_uniq_tbl_1 PARTITION OF parted_uniq_tbl FOR VALUES FROM (0) TO (10);
+CREATE TABLE parted_uniq_tbl_2 PARTITION OF parted_uniq_tbl FOR VALUES FROM (20) TO (30);
+SELECT conname, conrelid::regclass FROM pg_constraint
+ WHERE conname LIKE 'parted_uniq%' ORDER BY conname;
+BEGIN;
+INSERT INTO parted_uniq_tbl VALUES (1);
+SAVEPOINT f;
+INSERT INTO parted_uniq_tbl VALUES (1); -- unique violation
+ROLLBACK TO f;
+SET CONSTRAINTS parted_uniq_tbl_i_key DEFERRED;
+INSERT INTO parted_uniq_tbl VALUES (1); -- OK now, fail at commit
+COMMIT;
+DROP TABLE parted_uniq_tbl;
+
+-- test a HOT update that invalidates the conflicting tuple.
+-- the trigger should still fire and catch the violation
+
+BEGIN;
+
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now
+UPDATE unique_tbl SET t = 'THREE' WHERE i = 3 AND t = 'Three';
+
+COMMIT; -- should fail
+
+SELECT * FROM unique_tbl;
+
+-- test a HOT update that modifies the newly inserted tuple,
+-- but should succeed because we then remove the other conflicting tuple.
+
+BEGIN;
+
+INSERT INTO unique_tbl VALUES(3, 'tree'); -- should succeed for now
+UPDATE unique_tbl SET t = 'threex' WHERE t = 'tree';
+DELETE FROM unique_tbl WHERE t = 'three';
+
+SELECT * FROM unique_tbl;
+
+COMMIT;
+
+SELECT * FROM unique_tbl;
+
+DROP TABLE unique_tbl;
+
+--
+-- EXCLUDE constraints
+--
+
+CREATE TABLE circles (
+ c1 CIRCLE,
+ c2 TEXT,
+ EXCLUDE USING gist
+ (c1 WITH &&, (c2::circle) WITH &&)
+ WHERE (circle_center(c1) <> '(0,0)')
+);
+
+-- these should succeed because they don't match the index predicate
+INSERT INTO circles VALUES('<(0,0), 5>', '<(0,0), 5>');
+INSERT INTO circles VALUES('<(0,0), 5>', '<(0,0), 4>');
+
+-- succeed
+INSERT INTO circles VALUES('<(10,10), 10>', '<(0,0), 5>');
+-- fail, overlaps
+INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 4>');
+-- succeed, because violation is ignored
+INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 4>')
+ ON CONFLICT ON CONSTRAINT circles_c1_c2_excl DO NOTHING;
+-- fail, because DO UPDATE variant requires unique index
+INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 4>')
+ ON CONFLICT ON CONSTRAINT circles_c1_c2_excl DO UPDATE SET c2 = EXCLUDED.c2;
+-- succeed because c1 doesn't overlap
+INSERT INTO circles VALUES('<(20,20), 1>', '<(0,0), 5>');
+-- succeed because c2 doesn't overlap
+INSERT INTO circles VALUES('<(20,20), 10>', '<(10,10), 5>');
+
+-- should fail on existing data without the WHERE clause
+ALTER TABLE circles ADD EXCLUDE USING gist
+ (c1 WITH &&, (c2::circle) WITH &&);
+
+-- try reindexing an existing constraint
+REINDEX INDEX circles_c1_c2_excl;
+
+DROP TABLE circles;
+
+-- Check deferred exclusion constraint
+
+CREATE TABLE deferred_excl (
+ f1 int,
+ f2 int,
+ CONSTRAINT deferred_excl_con EXCLUDE (f1 WITH =) INITIALLY DEFERRED
+);
+
+INSERT INTO deferred_excl VALUES(1);
+INSERT INTO deferred_excl VALUES(2);
+INSERT INTO deferred_excl VALUES(1); -- fail
+INSERT INTO deferred_excl VALUES(1) ON CONFLICT ON CONSTRAINT deferred_excl_con DO NOTHING; -- fail
+BEGIN;
+INSERT INTO deferred_excl VALUES(2); -- no fail here
+COMMIT; -- should fail here
+BEGIN;
+INSERT INTO deferred_excl VALUES(3);
+INSERT INTO deferred_excl VALUES(3); -- no fail here
+COMMIT; -- should fail here
+
+-- bug #13148: deferred constraint versus HOT update
+BEGIN;
+INSERT INTO deferred_excl VALUES(2, 1); -- no fail here
+DELETE FROM deferred_excl WHERE f1 = 2 AND f2 IS NULL; -- remove old row
+UPDATE deferred_excl SET f2 = 2 WHERE f1 = 2;
+COMMIT; -- should not fail
+
+SELECT * FROM deferred_excl;
+
+ALTER TABLE deferred_excl DROP CONSTRAINT deferred_excl_con;
+
+-- This should fail, but worth testing because of HOT updates
+UPDATE deferred_excl SET f1 = 3;
+
+ALTER TABLE deferred_excl ADD EXCLUDE (f1 WITH =);
+
+DROP TABLE deferred_excl;
+
+-- Comments
+-- Setup a low-level role to enforce non-superuser checks.
+CREATE ROLE regress_constraint_comments;
+SET SESSION AUTHORIZATION regress_constraint_comments;
+
+CREATE TABLE constraint_comments_tbl (a int CONSTRAINT the_constraint CHECK (a > 0));
+CREATE DOMAIN constraint_comments_dom AS int CONSTRAINT the_constraint CHECK (value > 0);
+
+COMMENT ON CONSTRAINT the_constraint ON constraint_comments_tbl IS 'yes, the comment';
+COMMENT ON CONSTRAINT the_constraint ON DOMAIN constraint_comments_dom IS 'yes, another comment';
+
+-- no such constraint
+COMMENT ON CONSTRAINT no_constraint ON constraint_comments_tbl IS 'yes, the comment';
+COMMENT ON CONSTRAINT no_constraint ON DOMAIN constraint_comments_dom IS 'yes, another comment';
+
+-- no such table/domain
+COMMENT ON CONSTRAINT the_constraint ON no_comments_tbl IS 'bad comment';
+COMMENT ON CONSTRAINT the_constraint ON DOMAIN no_comments_dom IS 'another bad comment';
+
+COMMENT ON CONSTRAINT the_constraint ON constraint_comments_tbl IS NULL;
+COMMENT ON CONSTRAINT the_constraint ON DOMAIN constraint_comments_dom IS NULL;
+
+-- unauthorized user
+RESET SESSION AUTHORIZATION;
+CREATE ROLE regress_constraint_comments_noaccess;
+SET SESSION AUTHORIZATION regress_constraint_comments_noaccess;
+COMMENT ON CONSTRAINT the_constraint ON constraint_comments_tbl IS 'no, the comment';
+COMMENT ON CONSTRAINT the_constraint ON DOMAIN constraint_comments_dom IS 'no, another comment';
+RESET SESSION AUTHORIZATION;
+
+DROP TABLE constraint_comments_tbl;
+DROP DOMAIN constraint_comments_dom;
+
+DROP ROLE regress_constraint_comments;
+DROP ROLE regress_constraint_comments_noaccess;
diff --git a/src/test/regress/input/copy.source b/src/test/regress/input/copy.source
new file mode 100644
index 0000000..8acb516
--- /dev/null
+++ b/src/test/regress/input/copy.source
@@ -0,0 +1,265 @@
+--
+-- COPY
+--
+
+-- CLASS POPULATION
+-- (any resemblance to real life is purely coincidental)
+--
+COPY aggtest FROM '@abs_srcdir@/data/agg.data';
+
+COPY onek FROM '@abs_srcdir@/data/onek.data';
+
+COPY onek TO '@abs_builddir@/results/onek.data';
+
+DELETE FROM onek;
+
+COPY onek FROM '@abs_builddir@/results/onek.data';
+
+COPY tenk1 FROM '@abs_srcdir@/data/tenk.data';
+
+COPY slow_emp4000 FROM '@abs_srcdir@/data/rect.data';
+
+COPY person FROM '@abs_srcdir@/data/person.data';
+
+COPY emp FROM '@abs_srcdir@/data/emp.data';
+
+COPY student FROM '@abs_srcdir@/data/student.data';
+
+COPY stud_emp FROM '@abs_srcdir@/data/stud_emp.data';
+
+COPY road FROM '@abs_srcdir@/data/streets.data';
+
+COPY real_city FROM '@abs_srcdir@/data/real_city.data';
+
+COPY hash_i4_heap FROM '@abs_srcdir@/data/hash.data';
+
+COPY hash_name_heap FROM '@abs_srcdir@/data/hash.data';
+
+COPY hash_txt_heap FROM '@abs_srcdir@/data/hash.data';
+
+COPY hash_f8_heap FROM '@abs_srcdir@/data/hash.data';
+
+COPY test_tsvector FROM '@abs_srcdir@/data/tsearch.data';
+
+COPY testjsonb FROM '@abs_srcdir@/data/jsonb.data';
+
+-- the data in this file has a lot of duplicates in the index key
+-- fields, leading to long bucket chains and lots of table expansion.
+-- this is therefore a stress test of the bucket overflow code (unlike
+-- the data in hash.data, which has unique index keys).
+--
+-- COPY hash_ovfl_heap FROM '@abs_srcdir@/data/hashovfl.data';
+
+COPY bt_i4_heap FROM '@abs_srcdir@/data/desc.data';
+
+COPY bt_name_heap FROM '@abs_srcdir@/data/hash.data';
+
+COPY bt_txt_heap FROM '@abs_srcdir@/data/desc.data';
+
+COPY bt_f8_heap FROM '@abs_srcdir@/data/hash.data';
+
+COPY array_op_test FROM '@abs_srcdir@/data/array.data';
+
+COPY array_index_op_test FROM '@abs_srcdir@/data/array.data';
+
+-- analyze all the data we just loaded, to ensure plan consistency
+-- in later tests
+
+ANALYZE aggtest;
+ANALYZE onek;
+ANALYZE tenk1;
+ANALYZE slow_emp4000;
+ANALYZE person;
+ANALYZE emp;
+ANALYZE student;
+ANALYZE stud_emp;
+ANALYZE road;
+ANALYZE real_city;
+ANALYZE hash_i4_heap;
+ANALYZE hash_name_heap;
+ANALYZE hash_txt_heap;
+ANALYZE hash_f8_heap;
+ANALYZE test_tsvector;
+ANALYZE bt_i4_heap;
+ANALYZE bt_name_heap;
+ANALYZE bt_txt_heap;
+ANALYZE bt_f8_heap;
+ANALYZE array_op_test;
+ANALYZE array_index_op_test;
+
+--- 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);
+
+copy copytest to '@abs_builddir@/results/copytest.csv' csv;
+
+create temp table copytest2 (like copytest);
+
+copy copytest2 from '@abs_builddir@/results/copytest.csv' csv;
+
+select * from copytest except select * from copytest2;
+
+truncate copytest2;
+
+--- same test but with an escape char different from quote char
+
+copy copytest to '@abs_builddir@/results/copytest.csv' csv quote '''' escape E'\\';
+
+copy copytest2 from '@abs_builddir@/results/copytest.csv' 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;
+
+-- 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;
+
+copy (select * from parted_copytest order by a) to '@abs_builddir@/results/parted_copytest.csv';
+
+truncate parted_copytest;
+
+copy parted_copytest from '@abs_builddir@/results/parted_copytest.csv';
+
+-- Ensure COPY FREEZE errors for partitioned tables.
+begin;
+truncate parted_copytest;
+copy parted_copytest from '@abs_builddir@/results/parted_copytest.csv' (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 '@abs_builddir@/results/parted_copytest.csv';
+
+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;
+copy tab_progress_reporting from '@abs_srcdir@/data/emp.data'
+ 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;
diff --git a/src/test/regress/input/create_function_0.source b/src/test/regress/input/create_function_0.source
new file mode 100644
index 0000000..f47f635
--- /dev/null
+++ b/src/test/regress/input/create_function_0.source
@@ -0,0 +1,95 @@
+--
+-- CREATE_FUNCTION_0
+--
+
+-- Create a bunch of C functions that will be used by later tests:
+
+CREATE FUNCTION check_primary_key ()
+ RETURNS trigger
+ AS '@libdir@/refint@DLSUFFIX@'
+ LANGUAGE C;
+
+CREATE FUNCTION check_foreign_key ()
+ RETURNS trigger
+ AS '@libdir@/refint@DLSUFFIX@'
+ LANGUAGE C;
+
+CREATE FUNCTION autoinc ()
+ RETURNS trigger
+ AS '@libdir@/autoinc@DLSUFFIX@'
+ LANGUAGE C;
+
+CREATE FUNCTION trigger_return_old ()
+ RETURNS trigger
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C;
+
+CREATE FUNCTION ttdummy ()
+ RETURNS trigger
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C;
+
+CREATE FUNCTION set_ttdummy (int4)
+ RETURNS int4
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT;
+
+CREATE FUNCTION make_tuple_indirect (record)
+ RETURNS record
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT;
+
+CREATE FUNCTION test_atomic_ops()
+ RETURNS bool
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C;
+
+CREATE FUNCTION test_fdw_handler()
+ RETURNS fdw_handler
+ AS '@libdir@/regress@DLSUFFIX@', 'test_fdw_handler'
+ LANGUAGE C;
+
+CREATE FUNCTION test_support_func(internal)
+ RETURNS internal
+ AS '@libdir@/regress@DLSUFFIX@', 'test_support_func'
+ LANGUAGE C STRICT;
+
+CREATE FUNCTION test_opclass_options_func(internal)
+ RETURNS void
+ AS '@libdir@/regress@DLSUFFIX@', 'test_opclass_options_func'
+ LANGUAGE C;
+
+CREATE FUNCTION test_enc_conversion(bytea, name, name, bool, validlen OUT int, result OUT bytea)
+ AS '@libdir@/regress@DLSUFFIX@', 'test_enc_conversion'
+ LANGUAGE C STRICT;
+
+CREATE FUNCTION binary_coercible(oid, oid)
+ RETURNS bool
+ AS '@libdir@/regress@DLSUFFIX@', 'binary_coercible'
+ LANGUAGE C STRICT STABLE PARALLEL SAFE;
+
+-- 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';
+
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE C
+ AS 'nosuchfile';
+
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE C
+ AS '@libdir@/regress@DLSUFFIX@', 'nosuchsymbol';
+
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE internal
+ AS 'nosuch';
diff --git a/src/test/regress/input/create_function_1.source b/src/test/regress/input/create_function_1.source
new file mode 100644
index 0000000..79a4156
--- /dev/null
+++ b/src/test/regress/input/create_function_1.source
@@ -0,0 +1,25 @@
+--
+-- CREATE_FUNCTION_1
+--
+
+-- Create C functions needed by create_type.sql
+
+CREATE FUNCTION widget_in(cstring)
+ RETURNS widget
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION widget_out(widget)
+ RETURNS cstring
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION int44in(cstring)
+ RETURNS city_budget
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION int44out(city_budget)
+ RETURNS cstring
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT IMMUTABLE;
diff --git a/src/test/regress/input/create_function_2.source b/src/test/regress/input/create_function_2.source
new file mode 100644
index 0000000..9e6d294
--- /dev/null
+++ b/src/test/regress/input/create_function_2.source
@@ -0,0 +1,88 @@
+--
+-- CREATE_FUNCTION_2
+--
+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;
+
+
+CREATE FUNCTION pt_in_widget(point, widget)
+ RETURNS bool
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT;
+
+CREATE FUNCTION overpaid(emp)
+ RETURNS bool
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT;
+
+CREATE FUNCTION interpt_pp(path, path)
+ RETURNS point
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT;
+
+CREATE FUNCTION reverse_name(name)
+ RETURNS name
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT;
+
+--
+-- Function dynamic loading
+--
+LOAD '@libdir@/regress@DLSUFFIX@';
diff --git a/src/test/regress/input/largeobject.source b/src/test/regress/input/largeobject.source
new file mode 100644
index 0000000..b1e7ae9
--- /dev/null
+++ b/src/test/regress/input/largeobject.source
@@ -0,0 +1,274 @@
+--
+-- Test large object support
+--
+
+-- ensure consistent test output regardless of the default bytea format
+SET bytea_output TO escape;
+
+-- Load a file
+CREATE TABLE lotest_stash_values (loid oid, fd integer);
+-- lo_creat(mode integer) returns oid
+-- The mode arg to lo_creat is unused, some vestigal holdover from ancient times
+-- returns the large object id
+INSERT INTO lotest_stash_values (loid) SELECT lo_creat(42);
+
+-- Test ALTER LARGE OBJECT
+CREATE ROLE regress_lo_user;
+DO $$
+ BEGIN
+ EXECUTE 'ALTER LARGE OBJECT ' || (select loid from lotest_stash_values)
+ || ' OWNER TO regress_lo_user';
+ END
+$$;
+SELECT
+ rol.rolname
+FROM
+ lotest_stash_values s
+ JOIN pg_largeobject_metadata lo ON s.loid = lo.oid
+ JOIN pg_authid rol ON lo.lomowner = rol.oid;
+
+-- NOTE: large objects require transactions
+BEGIN;
+
+-- lo_open(lobjId oid, mode integer) returns integer
+-- The mode parameter to lo_open uses two constants:
+-- INV_READ = 0x20000
+-- INV_WRITE = 0x40000
+-- The return value is a file descriptor-like value which remains valid for the
+-- transaction.
+UPDATE lotest_stash_values SET fd = lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+
+-- loread/lowrite names are wonky, different from other functions which are lo_*
+-- lowrite(fd integer, data bytea) returns integer
+-- the integer is the number of bytes written
+SELECT lowrite(fd, '
+I wandered lonely as a cloud
+That floats on high o''er vales and hills,
+When all at once I saw a crowd,
+A host, of golden daffodils;
+Beside the lake, beneath the trees,
+Fluttering and dancing in the breeze.
+
+Continuous as the stars that shine
+And twinkle on the milky way,
+They stretched in never-ending line
+Along the margin of a bay:
+Ten thousand saw I at a glance,
+Tossing their heads in sprightly dance.
+
+The waves beside them danced; but they
+Out-did the sparkling waves in glee:
+A poet could not but be gay,
+In such a jocund company:
+I gazed--and gazed--but little thought
+What wealth the show to me had brought:
+
+For oft, when on my couch I lie
+In vacant or in pensive mood,
+They flash upon that inward eye
+Which is the bliss of solitude;
+And then my heart with pleasure fills,
+And dances with the daffodils.
+
+ -- William Wordsworth
+') FROM lotest_stash_values;
+
+-- lo_close(fd integer) returns integer
+-- return value is 0 for success, or <0 for error (actually only -1, but...)
+SELECT lo_close(fd) FROM lotest_stash_values;
+
+END;
+
+-- Copy to another large object.
+-- Note: we intentionally don't remove the object created here;
+-- it's left behind to help test pg_dump.
+
+SELECT lo_from_bytea(0, lo_get(loid)) AS newloid FROM lotest_stash_values
+\gset
+
+-- Add a comment to it, as well, for pg_dump/pg_upgrade testing.
+COMMENT ON LARGE OBJECT :newloid IS 'I Wandered Lonely as a Cloud';
+
+-- Read out a portion
+BEGIN;
+UPDATE lotest_stash_values SET fd=lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+
+-- lo_lseek(fd integer, offset integer, whence integer) returns integer
+-- offset is in bytes, whence is one of three values:
+-- SEEK_SET (= 0) meaning relative to beginning
+-- SEEK_CUR (= 1) meaning relative to current position
+-- SEEK_END (= 2) meaning relative to end (offset better be negative)
+-- returns current position in file
+SELECT lo_lseek(fd, 104, 0) FROM lotest_stash_values;
+
+-- loread/lowrite names are wonky, different from other functions which are lo_*
+-- loread(fd integer, len integer) returns bytea
+SELECT loread(fd, 28) FROM lotest_stash_values;
+
+SELECT lo_lseek(fd, -19, 1) FROM lotest_stash_values;
+
+SELECT lowrite(fd, 'n') FROM lotest_stash_values;
+
+SELECT lo_tell(fd) FROM lotest_stash_values;
+
+SELECT lo_lseek(fd, -744, 2) FROM lotest_stash_values;
+
+SELECT loread(fd, 28) FROM lotest_stash_values;
+
+SELECT lo_close(fd) FROM lotest_stash_values;
+
+END;
+
+-- Test resource management
+BEGIN;
+SELECT lo_open(loid, x'40000'::int) from lotest_stash_values;
+ABORT;
+
+DO $$
+DECLARE
+ loid oid;
+BEGIN
+ SELECT tbl.loid INTO loid FROM lotest_stash_values tbl;
+ PERFORM lo_export(loid, '@abs_builddir@/results/invalid/path');
+EXCEPTION
+ WHEN UNDEFINED_FILE THEN RAISE NOTICE 'could not open file, as expected';
+END;
+$$;
+
+-- Test truncation.
+BEGIN;
+UPDATE lotest_stash_values SET fd=lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+
+SELECT lo_truncate(fd, 11) FROM lotest_stash_values;
+SELECT loread(fd, 15) FROM lotest_stash_values;
+
+SELECT lo_truncate(fd, 10000) FROM lotest_stash_values;
+SELECT loread(fd, 10) FROM lotest_stash_values;
+SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+SELECT lo_tell(fd) FROM lotest_stash_values;
+
+SELECT lo_truncate(fd, 5000) FROM lotest_stash_values;
+SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+SELECT lo_tell(fd) FROM lotest_stash_values;
+
+SELECT lo_close(fd) FROM lotest_stash_values;
+END;
+
+-- Test 64-bit large object functions.
+BEGIN;
+UPDATE lotest_stash_values SET fd = lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+
+SELECT lo_lseek64(fd, 4294967296, 0) FROM lotest_stash_values;
+SELECT lowrite(fd, 'offset:4GB') FROM lotest_stash_values;
+SELECT lo_tell64(fd) FROM lotest_stash_values;
+
+SELECT lo_lseek64(fd, -10, 1) FROM lotest_stash_values;
+SELECT lo_tell64(fd) FROM lotest_stash_values;
+SELECT loread(fd, 10) FROM lotest_stash_values;
+
+SELECT lo_truncate64(fd, 5000000000) FROM lotest_stash_values;
+SELECT lo_lseek64(fd, 0, 2) FROM lotest_stash_values;
+SELECT lo_tell64(fd) FROM lotest_stash_values;
+
+SELECT lo_truncate64(fd, 3000000000) FROM lotest_stash_values;
+SELECT lo_lseek64(fd, 0, 2) FROM lotest_stash_values;
+SELECT lo_tell64(fd) FROM lotest_stash_values;
+
+SELECT lo_close(fd) FROM lotest_stash_values;
+END;
+
+-- lo_unlink(lobjId oid) returns integer
+-- return value appears to always be 1
+SELECT lo_unlink(loid) from lotest_stash_values;
+
+TRUNCATE lotest_stash_values;
+
+INSERT INTO lotest_stash_values (loid) SELECT lo_import('@abs_srcdir@/data/tenk.data');
+
+BEGIN;
+UPDATE lotest_stash_values SET fd=lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+
+-- verify length of large object
+SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+
+-- with the default BLCKSZ, LOBLKSIZE = 2048, so this positions us for a block
+-- edge case
+SELECT lo_lseek(fd, 2030, 0) FROM lotest_stash_values;
+
+-- this should get half of the value from page 0 and half from page 1 of the
+-- large object
+SELECT loread(fd, 36) FROM lotest_stash_values;
+
+SELECT lo_tell(fd) FROM lotest_stash_values;
+
+SELECT lo_lseek(fd, -26, 1) FROM lotest_stash_values;
+
+SELECT lowrite(fd, 'abcdefghijklmnop') FROM lotest_stash_values;
+
+SELECT lo_lseek(fd, 2030, 0) FROM lotest_stash_values;
+
+SELECT loread(fd, 36) FROM lotest_stash_values;
+
+SELECT lo_close(fd) FROM lotest_stash_values;
+END;
+
+SELECT lo_export(loid, '@abs_builddir@/results/lotest.txt') FROM lotest_stash_values;
+
+\lo_import '@abs_builddir@/results/lotest.txt'
+
+\set newloid :LASTOID
+
+-- just make sure \lo_export does not barf
+\lo_export :newloid '@abs_builddir@/results/lotest2.txt'
+
+-- This is a hack to test that export/import are reversible
+-- This uses knowledge about the inner workings of large object mechanism
+-- which should not be used outside it. This makes it a HACK
+SELECT pageno, data FROM pg_largeobject WHERE loid = (SELECT loid from lotest_stash_values)
+EXCEPT
+SELECT pageno, data FROM pg_largeobject WHERE loid = :newloid;
+
+SELECT lo_unlink(loid) FROM lotest_stash_values;
+
+TRUNCATE lotest_stash_values;
+
+\lo_unlink :newloid
+
+\lo_import '@abs_builddir@/results/lotest.txt'
+
+\set newloid_1 :LASTOID
+
+SELECT lo_from_bytea(0, lo_get(:newloid_1)) AS newloid_2
+\gset
+
+SELECT md5(lo_get(:newloid_1)) = md5(lo_get(:newloid_2));
+
+SELECT lo_get(:newloid_1, 0, 20);
+SELECT lo_get(:newloid_1, 10, 20);
+SELECT lo_put(:newloid_1, 5, decode('afafafaf', 'hex'));
+SELECT lo_get(:newloid_1, 0, 20);
+
+SELECT lo_put(:newloid_1, 4294967310, 'foo');
+SELECT lo_get(:newloid_1);
+SELECT lo_get(:newloid_1, 4294967294, 100);
+
+\lo_unlink :newloid_1
+\lo_unlink :newloid_2
+
+-- This object is left in the database for pg_dump test purposes
+SELECT lo_from_bytea(0, E'\\xdeadbeef') AS newloid
+\gset
+
+SET bytea_output TO hex;
+SELECT lo_get(:newloid);
+
+-- Create one more object that we leave behind for testing pg_dump/pg_upgrade;
+-- this one intentionally has an OID in the system range
+SELECT lo_create(2121);
+
+COMMENT ON LARGE OBJECT 2121 IS 'testing comments';
+
+-- Clean up
+DROP TABLE lotest_stash_values;
+
+DROP ROLE regress_lo_user;
diff --git a/src/test/regress/input/misc.source b/src/test/regress/input/misc.source
new file mode 100644
index 0000000..b1dbc57
--- /dev/null
+++ b/src/test/regress/input/misc.source
@@ -0,0 +1,262 @@
+--
+-- MISC
+--
+
+--
+-- 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)
+--
+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
+--
+COPY onek TO '@abs_builddir@/results/onek.data';
+
+DELETE FROM onek;
+
+COPY onek FROM '@abs_builddir@/results/onek.data';
+
+SELECT unique1 FROM onek WHERE unique1 < 2 ORDER BY unique1;
+
+DELETE FROM onek2;
+
+COPY onek2 FROM '@abs_builddir@/results/onek.data';
+
+SELECT unique1 FROM onek2 WHERE unique1 < 2 ORDER BY unique1;
+
+COPY BINARY stud_emp TO '@abs_builddir@/results/stud_emp.data';
+
+DELETE FROM stud_emp;
+
+COPY BINARY stud_emp FROM '@abs_builddir@/results/stud_emp.data';
+
+SELECT * FROM stud_emp;
+
+-- COPY aggtest FROM stdin;
+-- 56 7.8
+-- 100 99.097
+-- 0 0.09561
+-- 42 324.78
+-- .
+-- COPY aggtest TO stdout;
+
+
+--
+-- 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;
+
+-- 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;
+
+--UPDATE b_star*
+-- SET a = text 'gazpacho'
+-- WHERE aa > 4;
+
+SELECT class, aa, a FROM a_star*;
+
+
+--
+-- versions
+--
+
+--
+-- postquel functions
+--
+--
+-- 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'));
+
+SELECT hobbies_by_name('basketball');
+
+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
+--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
new file mode 100644
index 0000000..c133e73
--- /dev/null
+++ b/src/test/regress/input/tablespace.source
@@ -0,0 +1,411 @@
+-- create a tablespace using WITH clause
+CREATE TABLESPACE regress_tblspacewith LOCATION '@testtablespace@' WITH (some_nonexistent_parameter = true); -- fail
+CREATE TABLESPACE regress_tblspacewith LOCATION '@testtablespace@' WITH (random_page_cost = 3.0); -- ok
+
+-- check to see the parameter was used
+SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
+
+-- drop the tablespace so we can re-use the location
+DROP TABLESPACE regress_tblspacewith;
+
+-- create a tablespace we can use
+CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+
+-- 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);
+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
+
+-- REINDEX (TABLESPACE)
+-- catalogs and system tablespaces
+-- system catalog, fail
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_am;
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am;
+-- shared catalog, fail
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_authid;
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid;
+-- toast relations, fail
+REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1260_index;
+REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1260_index;
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1260;
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1260;
+-- system catalog, fail
+REINDEX (TABLESPACE pg_global) TABLE pg_authid;
+REINDEX (TABLESPACE pg_global) TABLE CONCURRENTLY pg_authid;
+
+-- table with toast relation
+CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, t text);
+INSERT INTO regress_tblspace_test_tbl (num1, num2, t)
+ SELECT round(random()*100), random(), 'text'
+ FROM generate_series(1, 10) s(i);
+CREATE INDEX regress_tblspace_test_tbl_idx ON regress_tblspace_test_tbl (num1);
+-- move to global tablespace, fail
+REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx;
+REINDEX (TABLESPACE pg_global) INDEX CONCURRENTLY regress_tblspace_test_tbl_idx;
+
+-- check transactional behavior of REINDEX (TABLESPACE)
+BEGIN;
+REINDEX (TABLESPACE regress_tblspace) INDEX regress_tblspace_test_tbl_idx;
+REINDEX (TABLESPACE regress_tblspace) TABLE regress_tblspace_test_tbl;
+ROLLBACK;
+-- no relation moved to the new tablespace
+SELECT c.relname FROM pg_class c, pg_tablespace s
+ WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace';
+
+-- check that all indexes are moved to a new tablespace with different
+-- relfilenode.
+-- Save first the existing relfilenode for the toast and main relations.
+SELECT relfilenode as main_filenode FROM pg_class
+ WHERE relname = 'regress_tblspace_test_tbl_idx' \gset
+SELECT relfilenode as toast_filenode FROM pg_class
+ WHERE oid =
+ (SELECT i.indexrelid
+ FROM pg_class c,
+ pg_index i
+ WHERE i.indrelid = c.reltoastrelid AND
+ c.relname = 'regress_tblspace_test_tbl') \gset
+REINDEX (TABLESPACE regress_tblspace) TABLE regress_tblspace_test_tbl;
+SELECT c.relname FROM pg_class c, pg_tablespace s
+ WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace'
+ ORDER BY c.relname;
+ALTER TABLE regress_tblspace_test_tbl SET TABLESPACE regress_tblspace;
+ALTER TABLE regress_tblspace_test_tbl SET TABLESPACE pg_default;
+SELECT c.relname FROM pg_class c, pg_tablespace s
+ WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace'
+ ORDER BY c.relname;
+-- Move back to the default tablespace.
+ALTER INDEX regress_tblspace_test_tbl_idx SET TABLESPACE pg_default;
+SELECT c.relname FROM pg_class c, pg_tablespace s
+ WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace'
+ ORDER BY c.relname;
+REINDEX (TABLESPACE regress_tblspace, CONCURRENTLY) TABLE regress_tblspace_test_tbl;
+SELECT c.relname FROM pg_class c, pg_tablespace s
+ WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace'
+ ORDER BY c.relname;
+SELECT relfilenode = :main_filenode AS main_same FROM pg_class
+ WHERE relname = 'regress_tblspace_test_tbl_idx';
+SELECT relfilenode = :toast_filenode as toast_same FROM pg_class
+ WHERE oid =
+ (SELECT i.indexrelid
+ FROM pg_class c,
+ pg_index i
+ WHERE i.indrelid = c.reltoastrelid AND
+ c.relname = 'regress_tblspace_test_tbl');
+DROP TABLE regress_tblspace_test_tbl;
+
+-- REINDEX (TABLESPACE) with partitions
+-- Create a partition tree and check the set of relations reindexed
+-- with their new tablespace.
+CREATE TABLE tbspace_reindex_part (c1 int, c2 int) PARTITION BY RANGE (c1);
+CREATE TABLE tbspace_reindex_part_0 PARTITION OF tbspace_reindex_part
+ FOR VALUES FROM (0) TO (10) PARTITION BY list (c2);
+CREATE TABLE tbspace_reindex_part_0_1 PARTITION OF tbspace_reindex_part_0
+ FOR VALUES IN (1);
+CREATE TABLE tbspace_reindex_part_0_2 PARTITION OF tbspace_reindex_part_0
+ FOR VALUES IN (2);
+-- This partitioned table will have no partitions.
+CREATE TABLE tbspace_reindex_part_10 PARTITION OF tbspace_reindex_part
+ FOR VALUES FROM (10) TO (20) PARTITION BY list (c2);
+-- Create some partitioned indexes
+CREATE INDEX tbspace_reindex_part_index ON ONLY tbspace_reindex_part (c1);
+CREATE INDEX tbspace_reindex_part_index_0 ON ONLY tbspace_reindex_part_0 (c1);
+ALTER INDEX tbspace_reindex_part_index ATTACH PARTITION tbspace_reindex_part_index_0;
+-- This partitioned index will have no partitions.
+CREATE INDEX tbspace_reindex_part_index_10 ON ONLY tbspace_reindex_part_10 (c1);
+ALTER INDEX tbspace_reindex_part_index ATTACH PARTITION tbspace_reindex_part_index_10;
+CREATE INDEX tbspace_reindex_part_index_0_1 ON ONLY tbspace_reindex_part_0_1 (c1);
+ALTER INDEX tbspace_reindex_part_index_0 ATTACH PARTITION tbspace_reindex_part_index_0_1;
+CREATE INDEX tbspace_reindex_part_index_0_2 ON ONLY tbspace_reindex_part_0_2 (c1);
+ALTER INDEX tbspace_reindex_part_index_0 ATTACH PARTITION tbspace_reindex_part_index_0_2;
+SELECT relid, parentrelid, level FROM pg_partition_tree('tbspace_reindex_part_index')
+ ORDER BY relid, level;
+-- Track the original tablespace, relfilenode and OID of each index
+-- in the tree.
+CREATE TEMP TABLE reindex_temp_before AS
+ SELECT oid, relname, relfilenode, reltablespace
+ FROM pg_class
+ WHERE relname ~ 'tbspace_reindex_part_index';
+REINDEX (TABLESPACE regress_tblspace, CONCURRENTLY) TABLE tbspace_reindex_part;
+-- REINDEX CONCURRENTLY changes the OID of the old relation, hence a check
+-- based on the relation name below.
+SELECT b.relname,
+ CASE WHEN a.relfilenode = b.relfilenode THEN 'relfilenode is unchanged'
+ ELSE 'relfilenode has changed' END AS filenode,
+ CASE WHEN a.reltablespace = b.reltablespace THEN 'reltablespace is unchanged'
+ ELSE 'reltablespace has changed' END AS tbspace
+ FROM reindex_temp_before b JOIN pg_class a ON b.relname = a.relname
+ ORDER BY 1;
+DROP TABLE tbspace_reindex_part;
+
+-- create a schema we can use
+CREATE SCHEMA testschema;
+
+-- 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';
+
+-- 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';
+
+-- check \d output
+\d testschema.foo
+\d testschema.foo_idx
+
+--
+-- partitioned table
+--
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
+SET default_tablespace TO pg_global;
+CREATE TABLE testschema.part_1 PARTITION OF testschema.part FOR VALUES IN (1);
+RESET default_tablespace;
+CREATE TABLE testschema.part_1 PARTITION OF testschema.part FOR VALUES IN (1);
+SET default_tablespace TO regress_tblspace;
+CREATE TABLE testschema.part_2 PARTITION OF testschema.part FOR VALUES IN (2);
+SET default_tablespace TO pg_global;
+CREATE TABLE testschema.part_3 PARTITION OF testschema.part FOR VALUES IN (3);
+ALTER TABLE testschema.part SET TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part_3 PARTITION OF testschema.part FOR VALUES IN (3);
+CREATE TABLE testschema.part_4 PARTITION OF testschema.part FOR VALUES IN (4)
+ TABLESPACE pg_default;
+CREATE TABLE testschema.part_56 PARTITION OF testschema.part FOR VALUES IN (5, 6)
+ PARTITION BY LIST (a);
+ALTER TABLE testschema.part SET TABLESPACE pg_default;
+CREATE TABLE testschema.part_78 PARTITION OF testschema.part FOR VALUES IN (7, 8)
+ PARTITION BY LIST (a);
+CREATE TABLE testschema.part_910 PARTITION OF testschema.part FOR VALUES IN (9, 10)
+ PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+RESET default_tablespace;
+CREATE TABLE testschema.part_78 PARTITION OF testschema.part FOR VALUES IN (7, 8)
+ PARTITION BY LIST (a);
+
+SELECT relname, spcname FROM pg_catalog.pg_class c
+ JOIN pg_catalog.pg_namespace n ON (c.relnamespace = n.oid)
+ LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+ where c.relname LIKE 'part%' AND n.nspname = 'testschema' order by relname;
+RESET default_tablespace;
+DROP TABLE testschema.part;
+
+-- 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';
+\d testschema.part
+\d+ testschema.part
+\d testschema.part1
+\d+ testschema.part1
+\d testschema.part_a_idx
+\d+ testschema.part_a_idx
+
+-- partitioned rels cannot specify the default tablespace. These fail:
+CREATE TABLE testschema.dflt (a int PRIMARY KEY) PARTITION BY LIST (a) TABLESPACE pg_default;
+CREATE TABLE testschema.dflt (a int PRIMARY KEY USING INDEX TABLESPACE pg_default) PARTITION BY LIST (a);
+SET default_tablespace TO 'pg_default';
+CREATE TABLE testschema.dflt (a int PRIMARY KEY) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.dflt (a int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace) PARTITION BY LIST (a);
+-- but these work:
+CREATE TABLE testschema.dflt (a int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+SET default_tablespace TO '';
+CREATE TABLE testschema.dflt2 (a int PRIMARY KEY) PARTITION BY LIST (a);
+DROP TABLE testschema.dflt, testschema.dflt2;
+
+-- 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;
+ALTER TABLE testschema.test_default_tab ADD CONSTRAINT test_index3 PRIMARY KEY (id);
+ALTER TABLE testschema.test_default_tab ADD CONSTRAINT test_index4 UNIQUE (id) USING INDEX TABLESPACE regress_tblspace;
+
+\d testschema.test_index1
+\d testschema.test_index2
+\d testschema.test_index3
+\d testschema.test_index4
+-- 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
+\d testschema.test_index3
+\d testschema.test_index4
+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
+\d testschema.test_index3
+\d testschema.test_index4
+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
+\d testschema.test_index3
+\d testschema.test_index4
+-- 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
+\d testschema.test_index3
+\d testschema.test_index4
+DROP TABLE testschema.test_default_tab;
+
+-- check that default_tablespace doesn't affect ALTER TABLE index rebuilds
+-- (this time with a partitioned table)
+CREATE TABLE testschema.test_default_tab_p(id bigint, val bigint)
+ PARTITION BY LIST (id) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.test_default_tab_p1 PARTITION OF testschema.test_default_tab_p
+ FOR VALUES IN (1);
+INSERT INTO testschema.test_default_tab_p VALUES (1);
+CREATE INDEX test_index1 on testschema.test_default_tab_p (val);
+CREATE INDEX test_index2 on testschema.test_default_tab_p (val) TABLESPACE regress_tblspace;
+ALTER TABLE testschema.test_default_tab_p ADD CONSTRAINT test_index3 PRIMARY KEY (id);
+ALTER TABLE testschema.test_default_tab_p ADD CONSTRAINT test_index4 UNIQUE (id) USING INDEX TABLESPACE regress_tblspace;
+
+\d testschema.test_index1
+\d testschema.test_index2
+\d testschema.test_index3
+\d testschema.test_index4
+-- 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_p ALTER val TYPE bigint;
+\d testschema.test_index1
+\d testschema.test_index2
+\d testschema.test_index3
+\d testschema.test_index4
+SELECT * FROM testschema.test_default_tab_p;
+-- tablespace should not change even if there is an index rewrite
+ALTER TABLE testschema.test_default_tab_p ALTER val TYPE int;
+\d testschema.test_index1
+\d testschema.test_index2
+\d testschema.test_index3
+\d testschema.test_index4
+SELECT * FROM testschema.test_default_tab_p;
+-- 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_p ALTER val TYPE int;
+\d testschema.test_index1
+\d testschema.test_index2
+\d testschema.test_index3
+\d testschema.test_index4
+-- tablespace should not change even if there is an index rewrite
+ALTER TABLE testschema.test_default_tab_p ALTER val TYPE bigint;
+\d testschema.test_index1
+\d testschema.test_index2
+\d testschema.test_index3
+\d testschema.test_index4
+DROP TABLE testschema.test_default_tab_p;
+
+-- 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
+SELECT * FROM testschema.test_tab;
+DROP TABLE testschema.test_tab;
+
+-- check that default_tablespace is handled correctly by multi-command
+-- ALTER TABLE that includes a tablespace-preserving rewrite
+CREATE TABLE testschema.test_tab(a int, b int, c int);
+SET default_tablespace TO regress_tblspace;
+ALTER TABLE testschema.test_tab ADD CONSTRAINT test_tab_unique UNIQUE (a);
+CREATE INDEX test_tab_a_idx ON testschema.test_tab (a);
+SET default_tablespace TO '';
+CREATE INDEX test_tab_b_idx ON testschema.test_tab (b);
+\d testschema.test_tab_unique
+\d testschema.test_tab_a_idx
+\d testschema.test_tab_b_idx
+ALTER TABLE testschema.test_tab ALTER b TYPE bigint, ADD UNIQUE (c);
+\d testschema.test_tab_unique
+\d testschema.test_tab_a_idx
+\d testschema.test_tab_b_idx
+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;
+ALTER INDEX testschema.part_a_idx SET TABLESPACE pg_global;
+ALTER INDEX testschema.part_a_idx SET TABLESPACE pg_default;
+ALTER INDEX testschema.part_a_idx SET TABLESPACE regress_tblspace;
+
+INSERT INTO testschema.atable VALUES(3); -- ok
+INSERT INTO testschema.atable VALUES(1); -- fail (checks index)
+SELECT COUNT(*) FROM testschema.atable; -- checks heap
+
+-- Will fail with bad path
+CREATE TABLESPACE regress_badspace LOCATION '/no/such/location';
+
+-- No such tablespace
+CREATE TABLE bar (i int) TABLESPACE regress_nosuchspace;
+
+-- Fail, in use for some partitioned object
+DROP TABLESPACE regress_tblspace;
+ALTER INDEX testschema.part_a_idx SET TABLESPACE pg_default;
+-- Fail, not empty
+DROP TABLESPACE regress_tblspace;
+
+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;
+REINDEX (TABLESPACE regress_tblspace) TABLE tablespace_table; -- fail
+REINDEX (TABLESPACE regress_tblspace, CONCURRENTLY) TABLE tablespace_table; -- fail
+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;
+
+DROP SCHEMA testschema CASCADE;
+
+DROP ROLE regress_tablespace_user1;
+DROP ROLE regress_tablespace_user2;
diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source
new file mode 100644
index 0000000..eff793c
--- /dev/null
+++ b/src/test/regress/output/constraints.source
@@ -0,0 +1,738 @@
+--
+-- CONSTRAINTS
+-- Constraints can be specified with:
+-- - DEFAULT clause
+-- - CHECK clauses
+-- - PRIMARY KEY clauses
+-- - UNIQUE clauses
+-- - EXCLUDE clauses
+--
+--
+-- DEFAULT syntax
+--
+CREATE TABLE DEFAULT_TBL (i int DEFAULT 100,
+ x text DEFAULT 'vadim', f float8 DEFAULT 123.456);
+INSERT INTO DEFAULT_TBL VALUES (1, 'thomas', 57.0613);
+INSERT INTO DEFAULT_TBL VALUES (1, 'bruce');
+INSERT INTO DEFAULT_TBL (i, f) VALUES (2, 987.654);
+INSERT INTO DEFAULT_TBL (x) VALUES ('marc');
+INSERT INTO DEFAULT_TBL VALUES (3, null, 1.0);
+SELECT * FROM DEFAULT_TBL;
+ i | x | f
+-----+--------+---------
+ 1 | thomas | 57.0613
+ 1 | bruce | 123.456
+ 2 | vadim | 987.654
+ 100 | marc | 123.456
+ 3 | | 1
+(5 rows)
+
+CREATE SEQUENCE DEFAULT_SEQ;
+CREATE TABLE DEFAULTEXPR_TBL (i1 int DEFAULT 100 + (200-199) * 2,
+ i2 int DEFAULT nextval('default_seq'));
+INSERT INTO DEFAULTEXPR_TBL VALUES (-1, -2);
+INSERT INTO DEFAULTEXPR_TBL (i1) VALUES (-3);
+INSERT INTO DEFAULTEXPR_TBL (i2) VALUES (-4);
+INSERT INTO DEFAULTEXPR_TBL (i2) VALUES (NULL);
+SELECT * FROM DEFAULTEXPR_TBL;
+ i1 | i2
+-----+----
+ -1 | -2
+ -3 | 1
+ 102 | -4
+ 102 |
+(4 rows)
+
+-- syntax errors
+-- test for extraneous comma
+CREATE TABLE error_tbl (i int DEFAULT (100, ));
+ERROR: syntax error at or near ")"
+LINE 1: CREATE TABLE error_tbl (i int DEFAULT (100, ));
+ ^
+-- this will fail because gram.y uses b_expr not a_expr for defaults,
+-- to avoid a shift/reduce conflict that arises from NOT NULL being
+-- part of the column definition syntax:
+CREATE TABLE error_tbl (b1 bool DEFAULT 1 IN (1, 2));
+ERROR: syntax error at or near "IN"
+LINE 1: CREATE TABLE error_tbl (b1 bool DEFAULT 1 IN (1, 2));
+ ^
+-- this should work, however:
+CREATE TABLE error_tbl (b1 bool DEFAULT (1 IN (1, 2)));
+DROP TABLE error_tbl;
+--
+-- CHECK syntax
+--
+CREATE TABLE CHECK_TBL (x int,
+ CONSTRAINT CHECK_CON CHECK (x > 3));
+INSERT INTO CHECK_TBL VALUES (5);
+INSERT INTO CHECK_TBL VALUES (4);
+INSERT INTO CHECK_TBL VALUES (3);
+ERROR: new row for relation "check_tbl" violates check constraint "check_con"
+DETAIL: Failing row contains (3).
+INSERT INTO CHECK_TBL VALUES (2);
+ERROR: new row for relation "check_tbl" violates check constraint "check_con"
+DETAIL: Failing row contains (2).
+INSERT INTO CHECK_TBL VALUES (6);
+INSERT INTO CHECK_TBL VALUES (1);
+ERROR: new row for relation "check_tbl" violates check constraint "check_con"
+DETAIL: Failing row contains (1).
+SELECT * FROM CHECK_TBL;
+ x
+---
+ 5
+ 4
+ 6
+(3 rows)
+
+CREATE SEQUENCE CHECK_SEQ;
+CREATE TABLE CHECK2_TBL (x int, y text, z int,
+ CONSTRAINT SEQUENCE_CON
+ CHECK (x > 3 and y <> 'check failed' and z < 8));
+INSERT INTO CHECK2_TBL VALUES (4, 'check ok', -2);
+INSERT INTO CHECK2_TBL VALUES (1, 'x check failed', -2);
+ERROR: new row for relation "check2_tbl" violates check constraint "sequence_con"
+DETAIL: Failing row contains (1, x check failed, -2).
+INSERT INTO CHECK2_TBL VALUES (5, 'z check failed', 10);
+ERROR: new row for relation "check2_tbl" violates check constraint "sequence_con"
+DETAIL: Failing row contains (5, z check failed, 10).
+INSERT INTO CHECK2_TBL VALUES (0, 'check failed', -2);
+ERROR: new row for relation "check2_tbl" violates check constraint "sequence_con"
+DETAIL: Failing row contains (0, check failed, -2).
+INSERT INTO CHECK2_TBL VALUES (6, 'check failed', 11);
+ERROR: new row for relation "check2_tbl" violates check constraint "sequence_con"
+DETAIL: Failing row contains (6, check failed, 11).
+INSERT INTO CHECK2_TBL VALUES (7, 'check ok', 7);
+SELECT * from CHECK2_TBL;
+ x | y | z
+---+----------+----
+ 4 | check ok | -2
+ 7 | check ok | 7
+(2 rows)
+
+--
+-- 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);
+ERROR: new row for relation "insert_tbl" violates check constraint "insert_tbl_con"
+DETAIL: Failing row contains (2, -NULL-, -2).
+SELECT * FROM INSERT_TBL;
+ x | y | z
+---+---+---
+(0 rows)
+
+SELECT 'one' AS one, nextval('insert_seq');
+ one | nextval
+-----+---------
+ one | 1
+(1 row)
+
+INSERT INTO INSERT_TBL(y) VALUES ('Y');
+ERROR: new row for relation "insert_tbl" violates check constraint "insert_tbl_con"
+DETAIL: Failing row contains (2, Y, -2).
+INSERT INTO INSERT_TBL(y) VALUES ('Y');
+INSERT INTO INSERT_TBL(x,z) VALUES (1, -2);
+ERROR: new row for relation "insert_tbl" violates check constraint "insert_tbl_check"
+DETAIL: Failing row contains (1, -NULL-, -2).
+INSERT INTO INSERT_TBL(z,x) VALUES (-7, 7);
+INSERT INTO INSERT_TBL VALUES (5, 'check failed', -5);
+ERROR: new row for relation "insert_tbl" violates check constraint "insert_tbl_con"
+DETAIL: Failing row contains (5, check failed, -5).
+INSERT INTO INSERT_TBL VALUES (7, '!check failed', -7);
+INSERT INTO INSERT_TBL(y) VALUES ('-!NULL-');
+SELECT * FROM INSERT_TBL;
+ x | y | z
+---+---------------+----
+ 3 | Y | -3
+ 7 | -NULL- | -7
+ 7 | !check failed | -7
+ 4 | -!NULL- | -4
+(4 rows)
+
+INSERT INTO INSERT_TBL(y,z) VALUES ('check failed', 4);
+ERROR: new row for relation "insert_tbl" violates check constraint "insert_tbl_check"
+DETAIL: Failing row contains (5, check failed, 4).
+INSERT INTO INSERT_TBL(x,y) VALUES (5, 'check failed');
+ERROR: new row for relation "insert_tbl" violates check constraint "insert_tbl_con"
+DETAIL: Failing row contains (5, check failed, -5).
+INSERT INTO INSERT_TBL(x,y) VALUES (5, '!check failed');
+INSERT INTO INSERT_TBL(y) VALUES ('-!NULL-');
+SELECT * FROM INSERT_TBL;
+ x | y | z
+---+---------------+----
+ 3 | Y | -3
+ 7 | -NULL- | -7
+ 7 | !check failed | -7
+ 4 | -!NULL- | -4
+ 5 | !check failed | -5
+ 6 | -!NULL- | -6
+(6 rows)
+
+SELECT 'seven' AS one, nextval('insert_seq');
+ one | nextval
+-------+---------
+ seven | 7
+(1 row)
+
+INSERT INTO INSERT_TBL(y) VALUES ('Y');
+ERROR: new row for relation "insert_tbl" violates check constraint "insert_tbl_con"
+DETAIL: Failing row contains (8, Y, -8).
+SELECT 'eight' AS one, currval('insert_seq');
+ one | currval
+-------+---------
+ eight | 8
+(1 row)
+
+-- 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;
+ x | y | z
+---+---------------+----
+ 3 | Y | -3
+ 7 | -NULL- | -7
+ 7 | !check failed | -7
+ 4 | -!NULL- | -4
+ 5 | !check failed | -5
+ 6 | -!NULL- | -6
+ | |
+(7 rows)
+
+--
+-- Check constraints on system columns
+--
+CREATE TABLE SYS_COL_CHECK_TBL (city text, state text, is_capital bool,
+ altitude int,
+ CHECK (NOT (is_capital AND tableoid::regclass::text = 'sys_col_check_tbl')));
+INSERT INTO SYS_COL_CHECK_TBL VALUES ('Seattle', 'Washington', false, 100);
+INSERT INTO SYS_COL_CHECK_TBL VALUES ('Olympia', 'Washington', true, 100);
+ERROR: new row for relation "sys_col_check_tbl" violates check constraint "sys_col_check_tbl_check"
+DETAIL: Failing row contains (Olympia, Washington, t, 100).
+SELECT *, tableoid::regclass::text FROM SYS_COL_CHECK_TBL;
+ city | state | is_capital | altitude | tableoid
+---------+------------+------------+----------+-------------------
+ Seattle | Washington | f | 100 | sys_col_check_tbl
+(1 row)
+
+DROP TABLE SYS_COL_CHECK_TBL;
+--
+-- Check constraints on system columns other then TableOid should return error
+--
+CREATE TABLE SYS_COL_CHECK_TBL (city text, state text, is_capital bool,
+ altitude int,
+ CHECK (NOT (is_capital AND ctid::text = 'sys_col_check_tbl')));
+ERROR: system column "ctid" reference in check constraint is invalid
+LINE 3: CHECK (NOT (is_capital AND ctid::text = 'sys_col_check...
+ ^
+--
+-- 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);
+ERROR: new row for relation "insert_child" violates check constraint "insert_child_check"
+DETAIL: Failing row contains (7, -NULL-, -7, 42, 6).
+INSERT INTO INSERT_CHILD(x,z,cy) VALUES (6,-7,7);
+ERROR: new row for relation "insert_child" violates check constraint "insert_tbl_check"
+DETAIL: Failing row contains (6, -NULL-, -7, 42, 7).
+INSERT INTO INSERT_CHILD(x,y,z,cy) VALUES (6,'check failed',-6,7);
+ERROR: new row for relation "insert_child" violates check constraint "insert_tbl_con"
+DETAIL: Failing row contains (6, check failed, -6, 42, 7).
+SELECT * FROM INSERT_CHILD;
+ x | y | z | cx | cy
+---+--------+----+----+----
+ 7 | -NULL- | -7 | 42 | 11
+(1 row)
+
+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);
+ERROR: new row for relation "atacc1" violates check constraint "atacc1_test_check"
+DETAIL: Failing row contains (-3).
+DROP TABLE ATACC1 CASCADE;
+NOTICE: drop cascades to table atacc2
+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);
+ERROR: new row for relation "atacc2" violates check constraint "atacc1_test_check"
+DETAIL: Failing row contains (-3, null).
+-- check constraint is there on parent
+INSERT INTO ATACC1 (TEST) VALUES (-3);
+ERROR: new row for relation "atacc1" violates check constraint "atacc1_test_check"
+DETAIL: Failing row contains (-3, null).
+-- 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);
+ERROR: new row for relation "atacc1" violates check constraint "atacc1_test2_check"
+DETAIL: Failing row contains (null, 3).
+DROP TABLE ATACC1 CASCADE;
+NOTICE: drop cascades to table atacc2
+--
+-- Check constraints on INSERT INTO
+--
+DELETE FROM INSERT_TBL;
+ALTER SEQUENCE INSERT_SEQ RESTART WITH 4;
+CREATE TEMP TABLE tmp (xd INT, yd TEXT, zd INT);
+INSERT INTO tmp VALUES (null, 'Y', null);
+INSERT INTO tmp VALUES (5, '!check failed', null);
+INSERT INTO tmp VALUES (null, 'try again', null);
+INSERT INTO INSERT_TBL(y) select yd from tmp;
+SELECT * FROM INSERT_TBL;
+ x | y | z
+---+---------------+----
+ 4 | Y | -4
+ 5 | !check failed | -5
+ 6 | try again | -6
+(3 rows)
+
+INSERT INTO INSERT_TBL SELECT * FROM tmp WHERE yd = 'try again';
+INSERT INTO INSERT_TBL(y,z) SELECT yd, -7 FROM tmp WHERE yd = 'try again';
+INSERT INTO INSERT_TBL(y,z) SELECT yd, -8 FROM tmp WHERE yd = 'try again';
+ERROR: new row for relation "insert_tbl" violates check constraint "insert_tbl_con"
+DETAIL: Failing row contains (8, try again, -8).
+SELECT * FROM INSERT_TBL;
+ x | y | z
+---+---------------+----
+ 4 | Y | -4
+ 5 | !check failed | -5
+ 6 | try again | -6
+ | try again |
+ 7 | try again | -7
+(5 rows)
+
+DROP TABLE tmp;
+--
+-- Check constraints on UPDATE
+--
+UPDATE INSERT_TBL SET x = NULL WHERE x = 5;
+UPDATE INSERT_TBL SET x = 6 WHERE x = 6;
+UPDATE INSERT_TBL SET x = -z, z = -x;
+UPDATE INSERT_TBL SET x = z, z = x;
+ERROR: new row for relation "insert_tbl" violates check constraint "insert_tbl_con"
+DETAIL: Failing row contains (-4, Y, 4).
+SELECT * FROM INSERT_TBL;
+ x | y | z
+---+---------------+----
+ 4 | Y | -4
+ | try again |
+ 7 | try again | -7
+ 5 | !check failed |
+ 6 | try again | -6
+(5 rows)
+
+-- DROP TABLE INSERT_TBL;
+--
+-- Check constraints on COPY FROM
+--
+CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
+ CONSTRAINT COPY_CON
+ CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
+COPY COPY_TBL FROM '@abs_srcdir@/data/constro.data';
+SELECT * FROM COPY_TBL;
+ x | y | z
+---+---------------+---
+ 4 | !check failed | 5
+ 6 | OK | 4
+(2 rows)
+
+COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data';
+ERROR: new row for relation "copy_tbl" violates check constraint "copy_con"
+DETAIL: Failing row contains (7, check failed, 6).
+CONTEXT: COPY copy_tbl, line 2: "7 check failed 6"
+SELECT * FROM COPY_TBL;
+ x | y | z
+---+---------------+---
+ 4 | !check failed | 5
+ 6 | OK | 4
+(2 rows)
+
+--
+-- Primary keys
+--
+CREATE TABLE PRIMARY_TBL (i int PRIMARY KEY, t text);
+INSERT INTO PRIMARY_TBL VALUES (1, 'one');
+INSERT INTO PRIMARY_TBL VALUES (2, 'two');
+INSERT INTO PRIMARY_TBL VALUES (1, 'three');
+ERROR: duplicate key value violates unique constraint "primary_tbl_pkey"
+DETAIL: Key (i)=(1) already exists.
+INSERT INTO PRIMARY_TBL VALUES (4, 'three');
+INSERT INTO PRIMARY_TBL VALUES (5, 'one');
+INSERT INTO PRIMARY_TBL (t) VALUES ('six');
+ERROR: null value in column "i" of relation "primary_tbl" violates not-null constraint
+DETAIL: Failing row contains (null, six).
+SELECT * FROM PRIMARY_TBL;
+ i | t
+---+-------
+ 1 | one
+ 2 | two
+ 4 | three
+ 5 | one
+(4 rows)
+
+DROP TABLE PRIMARY_TBL;
+CREATE TABLE PRIMARY_TBL (i int, t text,
+ PRIMARY KEY(i,t));
+INSERT INTO PRIMARY_TBL VALUES (1, 'one');
+INSERT INTO PRIMARY_TBL VALUES (2, 'two');
+INSERT INTO PRIMARY_TBL VALUES (1, 'three');
+INSERT INTO PRIMARY_TBL VALUES (4, 'three');
+INSERT INTO PRIMARY_TBL VALUES (5, 'one');
+INSERT INTO PRIMARY_TBL (t) VALUES ('six');
+ERROR: null value in column "i" of relation "primary_tbl" violates not-null constraint
+DETAIL: Failing row contains (null, six).
+SELECT * FROM PRIMARY_TBL;
+ i | t
+---+-------
+ 1 | one
+ 2 | two
+ 1 | three
+ 4 | three
+ 5 | one
+(5 rows)
+
+DROP TABLE PRIMARY_TBL;
+--
+-- Unique keys
+--
+CREATE TABLE UNIQUE_TBL (i int UNIQUE, t text);
+INSERT INTO UNIQUE_TBL VALUES (1, 'one');
+INSERT INTO UNIQUE_TBL VALUES (2, 'two');
+INSERT INTO UNIQUE_TBL VALUES (1, 'three');
+ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+DETAIL: Key (i)=(1) already exists.
+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');
+INSERT INTO UNIQUE_TBL VALUES (5, 'five-upsert-insert') ON CONFLICT (i) DO UPDATE SET t = 'five-upsert-update';
+INSERT INTO UNIQUE_TBL VALUES (6, 'six-upsert-insert') ON CONFLICT (i) DO UPDATE SET t = 'six-upsert-update';
+-- should fail
+INSERT INTO UNIQUE_TBL VALUES (1, 'a'), (2, 'b'), (2, 'b') ON CONFLICT (i) DO UPDATE SET t = 'fails';
+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.
+SELECT * FROM UNIQUE_TBL;
+ i | t
+---+--------------------
+ 1 | one
+ 2 | two
+ 4 | four
+ | six
+ | seven
+ 5 | five-upsert-update
+ 6 | six-upsert-insert
+(7 rows)
+
+DROP TABLE UNIQUE_TBL;
+CREATE TABLE UNIQUE_TBL (i int, t text,
+ UNIQUE(i,t));
+INSERT INTO UNIQUE_TBL VALUES (1, 'one');
+INSERT INTO UNIQUE_TBL VALUES (2, 'two');
+INSERT INTO UNIQUE_TBL VALUES (1, 'three');
+INSERT INTO UNIQUE_TBL VALUES (1, 'one');
+ERROR: duplicate key value violates unique constraint "unique_tbl_i_t_key"
+DETAIL: Key (i, t)=(1, one) already exists.
+INSERT INTO UNIQUE_TBL VALUES (5, 'one');
+INSERT INTO UNIQUE_TBL (t) VALUES ('six');
+SELECT * FROM UNIQUE_TBL;
+ i | t
+---+-------
+ 1 | one
+ 2 | two
+ 1 | three
+ 5 | one
+ | six
+(5 rows)
+
+DROP TABLE UNIQUE_TBL;
+--
+-- Deferrable unique constraints
+--
+CREATE TABLE unique_tbl (i int UNIQUE DEFERRABLE, t text);
+INSERT INTO unique_tbl VALUES (0, 'one');
+INSERT INTO unique_tbl VALUES (1, 'two');
+INSERT INTO unique_tbl VALUES (2, 'tree');
+INSERT INTO unique_tbl VALUES (3, 'four');
+INSERT INTO unique_tbl VALUES (4, 'five');
+BEGIN;
+-- default is immediate so this should fail right away
+UPDATE unique_tbl SET i = 1 WHERE i = 0;
+ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+DETAIL: Key (i)=(1) already exists.
+ROLLBACK;
+-- check is done at end of statement, so this should succeed
+UPDATE unique_tbl SET i = i+1;
+SELECT * FROM unique_tbl;
+ i | t
+---+------
+ 1 | one
+ 2 | two
+ 3 | tree
+ 4 | four
+ 5 | five
+(5 rows)
+
+-- explicitly defer the constraint
+BEGIN;
+SET CONSTRAINTS unique_tbl_i_key DEFERRED;
+INSERT INTO unique_tbl VALUES (3, 'three');
+DELETE FROM unique_tbl WHERE t = 'tree'; -- makes constraint valid again
+COMMIT; -- should succeed
+SELECT * FROM unique_tbl;
+ i | t
+---+-------
+ 1 | one
+ 2 | two
+ 4 | four
+ 5 | five
+ 3 | three
+(5 rows)
+
+-- try adding an initially deferred constraint
+ALTER TABLE unique_tbl DROP CONSTRAINT unique_tbl_i_key;
+ALTER TABLE unique_tbl ADD CONSTRAINT unique_tbl_i_key
+ UNIQUE (i) DEFERRABLE INITIALLY DEFERRED;
+BEGIN;
+INSERT INTO unique_tbl VALUES (1, 'five');
+INSERT INTO unique_tbl VALUES (5, 'one');
+UPDATE unique_tbl SET i = 4 WHERE i = 2;
+UPDATE unique_tbl SET i = 2 WHERE i = 4 AND t = 'four';
+DELETE FROM unique_tbl WHERE i = 1 AND t = 'one';
+DELETE FROM unique_tbl WHERE i = 5 AND t = 'five';
+COMMIT;
+SELECT * FROM unique_tbl;
+ i | t
+---+-------
+ 3 | three
+ 1 | five
+ 5 | one
+ 4 | two
+ 2 | four
+(5 rows)
+
+-- should fail at commit-time
+BEGIN;
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now
+COMMIT; -- should fail
+ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+DETAIL: Key (i)=(3) already exists.
+-- make constraint check immediate
+BEGIN;
+SET CONSTRAINTS ALL IMMEDIATE;
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should fail
+ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+DETAIL: Key (i)=(3) already exists.
+COMMIT;
+-- forced check when SET CONSTRAINTS is called
+BEGIN;
+SET CONSTRAINTS ALL DEFERRED;
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now
+SET CONSTRAINTS ALL IMMEDIATE; -- should fail
+ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+DETAIL: Key (i)=(3) already exists.
+COMMIT;
+-- test deferrable UNIQUE with a partitioned table
+CREATE TABLE parted_uniq_tbl (i int UNIQUE DEFERRABLE) partition by range (i);
+CREATE TABLE parted_uniq_tbl_1 PARTITION OF parted_uniq_tbl FOR VALUES FROM (0) TO (10);
+CREATE TABLE parted_uniq_tbl_2 PARTITION OF parted_uniq_tbl FOR VALUES FROM (20) TO (30);
+SELECT conname, conrelid::regclass FROM pg_constraint
+ WHERE conname LIKE 'parted_uniq%' ORDER BY conname;
+ conname | conrelid
+-------------------------+-------------------
+ parted_uniq_tbl_1_i_key | parted_uniq_tbl_1
+ parted_uniq_tbl_2_i_key | parted_uniq_tbl_2
+ parted_uniq_tbl_i_key | parted_uniq_tbl
+(3 rows)
+
+BEGIN;
+INSERT INTO parted_uniq_tbl VALUES (1);
+SAVEPOINT f;
+INSERT INTO parted_uniq_tbl VALUES (1); -- unique violation
+ERROR: duplicate key value violates unique constraint "parted_uniq_tbl_1_i_key"
+DETAIL: Key (i)=(1) already exists.
+ROLLBACK TO f;
+SET CONSTRAINTS parted_uniq_tbl_i_key DEFERRED;
+INSERT INTO parted_uniq_tbl VALUES (1); -- OK now, fail at commit
+COMMIT;
+ERROR: duplicate key value violates unique constraint "parted_uniq_tbl_1_i_key"
+DETAIL: Key (i)=(1) already exists.
+DROP TABLE parted_uniq_tbl;
+-- test a HOT update that invalidates the conflicting tuple.
+-- the trigger should still fire and catch the violation
+BEGIN;
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now
+UPDATE unique_tbl SET t = 'THREE' WHERE i = 3 AND t = 'Three';
+COMMIT; -- should fail
+ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+DETAIL: Key (i)=(3) already exists.
+SELECT * FROM unique_tbl;
+ i | t
+---+-------
+ 3 | three
+ 1 | five
+ 5 | one
+ 4 | two
+ 2 | four
+(5 rows)
+
+-- test a HOT update that modifies the newly inserted tuple,
+-- but should succeed because we then remove the other conflicting tuple.
+BEGIN;
+INSERT INTO unique_tbl VALUES(3, 'tree'); -- should succeed for now
+UPDATE unique_tbl SET t = 'threex' WHERE t = 'tree';
+DELETE FROM unique_tbl WHERE t = 'three';
+SELECT * FROM unique_tbl;
+ i | t
+---+--------
+ 1 | five
+ 5 | one
+ 4 | two
+ 2 | four
+ 3 | threex
+(5 rows)
+
+COMMIT;
+SELECT * FROM unique_tbl;
+ i | t
+---+--------
+ 1 | five
+ 5 | one
+ 4 | two
+ 2 | four
+ 3 | threex
+(5 rows)
+
+DROP TABLE unique_tbl;
+--
+-- EXCLUDE constraints
+--
+CREATE TABLE circles (
+ c1 CIRCLE,
+ c2 TEXT,
+ EXCLUDE USING gist
+ (c1 WITH &&, (c2::circle) WITH &&)
+ WHERE (circle_center(c1) <> '(0,0)')
+);
+-- these should succeed because they don't match the index predicate
+INSERT INTO circles VALUES('<(0,0), 5>', '<(0,0), 5>');
+INSERT INTO circles VALUES('<(0,0), 5>', '<(0,0), 4>');
+-- succeed
+INSERT INTO circles VALUES('<(10,10), 10>', '<(0,0), 5>');
+-- fail, overlaps
+INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 4>');
+ERROR: conflicting key value violates exclusion constraint "circles_c1_c2_excl"
+DETAIL: Key (c1, (c2::circle))=(<(20,20),10>, <(0,0),4>) conflicts with existing key (c1, (c2::circle))=(<(10,10),10>, <(0,0),5>).
+-- succeed, because violation is ignored
+INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 4>')
+ ON CONFLICT ON CONSTRAINT circles_c1_c2_excl DO NOTHING;
+-- fail, because DO UPDATE variant requires unique index
+INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 4>')
+ ON CONFLICT ON CONSTRAINT circles_c1_c2_excl DO UPDATE SET c2 = EXCLUDED.c2;
+ERROR: ON CONFLICT DO UPDATE not supported with exclusion constraints
+-- succeed because c1 doesn't overlap
+INSERT INTO circles VALUES('<(20,20), 1>', '<(0,0), 5>');
+-- succeed because c2 doesn't overlap
+INSERT INTO circles VALUES('<(20,20), 10>', '<(10,10), 5>');
+-- should fail on existing data without the WHERE clause
+ALTER TABLE circles ADD EXCLUDE USING gist
+ (c1 WITH &&, (c2::circle) WITH &&);
+ERROR: could not create exclusion constraint "circles_c1_c2_excl1"
+DETAIL: Key (c1, (c2::circle))=(<(0,0),5>, <(0,0),5>) conflicts with key (c1, (c2::circle))=(<(0,0),5>, <(0,0),4>).
+-- try reindexing an existing constraint
+REINDEX INDEX circles_c1_c2_excl;
+DROP TABLE circles;
+-- Check deferred exclusion constraint
+CREATE TABLE deferred_excl (
+ f1 int,
+ f2 int,
+ CONSTRAINT deferred_excl_con EXCLUDE (f1 WITH =) INITIALLY DEFERRED
+);
+INSERT INTO deferred_excl VALUES(1);
+INSERT INTO deferred_excl VALUES(2);
+INSERT INTO deferred_excl VALUES(1); -- fail
+ERROR: conflicting key value violates exclusion constraint "deferred_excl_con"
+DETAIL: Key (f1)=(1) conflicts with existing key (f1)=(1).
+INSERT INTO deferred_excl VALUES(1) ON CONFLICT ON CONSTRAINT deferred_excl_con DO NOTHING; -- fail
+ERROR: ON CONFLICT does not support deferrable unique constraints/exclusion constraints as arbiters
+BEGIN;
+INSERT INTO deferred_excl VALUES(2); -- no fail here
+COMMIT; -- should fail here
+ERROR: conflicting key value violates exclusion constraint "deferred_excl_con"
+DETAIL: Key (f1)=(2) conflicts with existing key (f1)=(2).
+BEGIN;
+INSERT INTO deferred_excl VALUES(3);
+INSERT INTO deferred_excl VALUES(3); -- no fail here
+COMMIT; -- should fail here
+ERROR: conflicting key value violates exclusion constraint "deferred_excl_con"
+DETAIL: Key (f1)=(3) conflicts with existing key (f1)=(3).
+-- bug #13148: deferred constraint versus HOT update
+BEGIN;
+INSERT INTO deferred_excl VALUES(2, 1); -- no fail here
+DELETE FROM deferred_excl WHERE f1 = 2 AND f2 IS NULL; -- remove old row
+UPDATE deferred_excl SET f2 = 2 WHERE f1 = 2;
+COMMIT; -- should not fail
+SELECT * FROM deferred_excl;
+ f1 | f2
+----+----
+ 1 |
+ 2 | 2
+(2 rows)
+
+ALTER TABLE deferred_excl DROP CONSTRAINT deferred_excl_con;
+-- This should fail, but worth testing because of HOT updates
+UPDATE deferred_excl SET f1 = 3;
+ALTER TABLE deferred_excl ADD EXCLUDE (f1 WITH =);
+ERROR: could not create exclusion constraint "deferred_excl_f1_excl"
+DETAIL: Key (f1)=(3) conflicts with key (f1)=(3).
+DROP TABLE deferred_excl;
+-- Comments
+-- Setup a low-level role to enforce non-superuser checks.
+CREATE ROLE regress_constraint_comments;
+SET SESSION AUTHORIZATION regress_constraint_comments;
+CREATE TABLE constraint_comments_tbl (a int CONSTRAINT the_constraint CHECK (a > 0));
+CREATE DOMAIN constraint_comments_dom AS int CONSTRAINT the_constraint CHECK (value > 0);
+COMMENT ON CONSTRAINT the_constraint ON constraint_comments_tbl IS 'yes, the comment';
+COMMENT ON CONSTRAINT the_constraint ON DOMAIN constraint_comments_dom IS 'yes, another comment';
+-- no such constraint
+COMMENT ON CONSTRAINT no_constraint ON constraint_comments_tbl IS 'yes, the comment';
+ERROR: constraint "no_constraint" for table "constraint_comments_tbl" does not exist
+COMMENT ON CONSTRAINT no_constraint ON DOMAIN constraint_comments_dom IS 'yes, another comment';
+ERROR: constraint "no_constraint" for domain constraint_comments_dom does not exist
+-- no such table/domain
+COMMENT ON CONSTRAINT the_constraint ON no_comments_tbl IS 'bad comment';
+ERROR: relation "no_comments_tbl" does not exist
+COMMENT ON CONSTRAINT the_constraint ON DOMAIN no_comments_dom IS 'another bad comment';
+ERROR: type "no_comments_dom" does not exist
+COMMENT ON CONSTRAINT the_constraint ON constraint_comments_tbl IS NULL;
+COMMENT ON CONSTRAINT the_constraint ON DOMAIN constraint_comments_dom IS NULL;
+-- unauthorized user
+RESET SESSION AUTHORIZATION;
+CREATE ROLE regress_constraint_comments_noaccess;
+SET SESSION AUTHORIZATION regress_constraint_comments_noaccess;
+COMMENT ON CONSTRAINT the_constraint ON constraint_comments_tbl IS 'no, the comment';
+ERROR: must be owner of relation constraint_comments_tbl
+COMMENT ON CONSTRAINT the_constraint ON DOMAIN constraint_comments_dom IS 'no, another comment';
+ERROR: must be owner of type constraint_comments_dom
+RESET SESSION AUTHORIZATION;
+DROP TABLE constraint_comments_tbl;
+DROP DOMAIN constraint_comments_dom;
+DROP ROLE regress_constraint_comments;
+DROP ROLE regress_constraint_comments_noaccess;
diff --git a/src/test/regress/output/copy.source b/src/test/regress/output/copy.source
new file mode 100644
index 0000000..25bdec6
--- /dev/null
+++ b/src/test/regress/output/copy.source
@@ -0,0 +1,221 @@
+--
+-- COPY
+--
+-- CLASS POPULATION
+-- (any resemblance to real life is purely coincidental)
+--
+COPY aggtest FROM '@abs_srcdir@/data/agg.data';
+COPY onek FROM '@abs_srcdir@/data/onek.data';
+COPY onek TO '@abs_builddir@/results/onek.data';
+DELETE FROM onek;
+COPY onek FROM '@abs_builddir@/results/onek.data';
+COPY tenk1 FROM '@abs_srcdir@/data/tenk.data';
+COPY slow_emp4000 FROM '@abs_srcdir@/data/rect.data';
+COPY person FROM '@abs_srcdir@/data/person.data';
+COPY emp FROM '@abs_srcdir@/data/emp.data';
+COPY student FROM '@abs_srcdir@/data/student.data';
+COPY stud_emp FROM '@abs_srcdir@/data/stud_emp.data';
+COPY road FROM '@abs_srcdir@/data/streets.data';
+COPY real_city FROM '@abs_srcdir@/data/real_city.data';
+COPY hash_i4_heap FROM '@abs_srcdir@/data/hash.data';
+COPY hash_name_heap FROM '@abs_srcdir@/data/hash.data';
+COPY hash_txt_heap FROM '@abs_srcdir@/data/hash.data';
+COPY hash_f8_heap FROM '@abs_srcdir@/data/hash.data';
+COPY test_tsvector FROM '@abs_srcdir@/data/tsearch.data';
+COPY testjsonb FROM '@abs_srcdir@/data/jsonb.data';
+-- the data in this file has a lot of duplicates in the index key
+-- fields, leading to long bucket chains and lots of table expansion.
+-- this is therefore a stress test of the bucket overflow code (unlike
+-- the data in hash.data, which has unique index keys).
+--
+-- COPY hash_ovfl_heap FROM '@abs_srcdir@/data/hashovfl.data';
+COPY bt_i4_heap FROM '@abs_srcdir@/data/desc.data';
+COPY bt_name_heap FROM '@abs_srcdir@/data/hash.data';
+COPY bt_txt_heap FROM '@abs_srcdir@/data/desc.data';
+COPY bt_f8_heap FROM '@abs_srcdir@/data/hash.data';
+COPY array_op_test FROM '@abs_srcdir@/data/array.data';
+COPY array_index_op_test FROM '@abs_srcdir@/data/array.data';
+-- analyze all the data we just loaded, to ensure plan consistency
+-- in later tests
+ANALYZE aggtest;
+ANALYZE onek;
+ANALYZE tenk1;
+ANALYZE slow_emp4000;
+ANALYZE person;
+ANALYZE emp;
+ANALYZE student;
+ANALYZE stud_emp;
+ANALYZE road;
+ANALYZE real_city;
+ANALYZE hash_i4_heap;
+ANALYZE hash_name_heap;
+ANALYZE hash_txt_heap;
+ANALYZE hash_f8_heap;
+ANALYZE test_tsvector;
+ANALYZE bt_i4_heap;
+ANALYZE bt_name_heap;
+ANALYZE bt_txt_heap;
+ANALYZE bt_f8_heap;
+ANALYZE array_op_test;
+ANALYZE array_index_op_test;
+--- 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);
+copy copytest to '@abs_builddir@/results/copytest.csv' csv;
+create temp table copytest2 (like copytest);
+copy copytest2 from '@abs_builddir@/results/copytest.csv' csv;
+select * from copytest except select * from copytest2;
+ style | test | filler
+-------+------+--------
+(0 rows)
+
+truncate copytest2;
+--- same test but with an escape char different from quote char
+copy copytest to '@abs_builddir@/results/copytest.csv' csv quote '''' escape E'\\';
+copy copytest2 from '@abs_builddir@/results/copytest.csv' csv quote '''' escape E'\\';
+select * from copytest except select * from copytest2;
+ style | test | filler
+-------+------+--------
+(0 rows)
+
+-- test header line feature
+create temp table copytest3 (
+ c1 int,
+ "col with , comma" text,
+ "col with "" quote" int);
+copy copytest3 from stdin csv header;
+copy copytest3 to stdout csv header;
+c1,"col with , comma","col with "" quote"
+1,a,1
+2,b,2
+-- 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;
+copy (select * from parted_copytest order by a) to '@abs_builddir@/results/parted_copytest.csv';
+truncate parted_copytest;
+copy parted_copytest from '@abs_builddir@/results/parted_copytest.csv';
+-- Ensure COPY FREEZE errors for partitioned tables.
+begin;
+truncate parted_copytest;
+copy parted_copytest from '@abs_builddir@/results/parted_copytest.csv' (freeze);
+ERROR: cannot perform COPY FREEZE on a partitioned table
+rollback;
+select tableoid::regclass,count(*),sum(a) from parted_copytest
+group by tableoid order by tableoid::regclass::name;
+ tableoid | count | sum
+--------------------+-------+--------
+ parted_copytest_a1 | 1010 | 510655
+ parted_copytest_a2 | 10 | 10055
+(2 rows)
+
+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 '@abs_builddir@/results/parted_copytest.csv';
+select tableoid::regclass,count(*),sum(a) from parted_copytest
+group by tableoid order by tableoid::regclass::name;
+ tableoid | count | sum
+--------------------+-------+--------
+ parted_copytest_a1 | 1010 | 510655
+ parted_copytest_a2 | 10 | 10055
+(2 rows)
+
+truncate table parted_copytest;
+create index on parted_copytest (b);
+drop trigger part_ins_trig on parted_copytest_a2;
+copy parted_copytest from stdin;
+-- Ensure index entries were properly added during the copy.
+select * from parted_copytest where b = 1;
+ a | b | c
+---+---+------
+ 1 | 1 | str1
+(1 row)
+
+select * from parted_copytest where b = 2;
+ a | b | c
+---+---+------
+ 2 | 2 | str2
+(1 row)
+
+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;
+INFO: progress: {"type": "PIPE", "command": "COPY FROM", "relname": "tab_progress_reporting", "has_bytes_total": false, "tuples_excluded": 0, "tuples_processed": 3, "has_bytes_processed": true}
+-- Generate COPY FROM report with FILE, with some excluded tuples.
+truncate tab_progress_reporting;
+copy tab_progress_reporting from '@abs_srcdir@/data/emp.data'
+ where (salary < 2000);
+INFO: progress: {"type": "FILE", "command": "COPY FROM", "relname": "tab_progress_reporting", "has_bytes_total": true, "tuples_excluded": 1, "tuples_processed": 2, "has_bytes_processed": true}
+drop trigger check_after_tab_progress_reporting on tab_progress_reporting;
+drop function notice_after_tab_progress_reporting();
+drop table tab_progress_reporting;
diff --git a/src/test/regress/output/create_function_0.source b/src/test/regress/output/create_function_0.source
new file mode 100644
index 0000000..342bc40
--- /dev/null
+++ b/src/test/regress/output/create_function_0.source
@@ -0,0 +1,88 @@
+--
+-- CREATE_FUNCTION_0
+--
+-- Create a bunch of C functions that will be used by later tests:
+CREATE FUNCTION check_primary_key ()
+ RETURNS trigger
+ AS '@libdir@/refint@DLSUFFIX@'
+ LANGUAGE C;
+CREATE FUNCTION check_foreign_key ()
+ RETURNS trigger
+ AS '@libdir@/refint@DLSUFFIX@'
+ LANGUAGE C;
+CREATE FUNCTION autoinc ()
+ RETURNS trigger
+ AS '@libdir@/autoinc@DLSUFFIX@'
+ LANGUAGE C;
+CREATE FUNCTION trigger_return_old ()
+ RETURNS trigger
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C;
+CREATE FUNCTION ttdummy ()
+ RETURNS trigger
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C;
+CREATE FUNCTION set_ttdummy (int4)
+ RETURNS int4
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT;
+CREATE FUNCTION make_tuple_indirect (record)
+ RETURNS record
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT;
+CREATE FUNCTION test_atomic_ops()
+ RETURNS bool
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C;
+CREATE FUNCTION test_fdw_handler()
+ RETURNS fdw_handler
+ AS '@libdir@/regress@DLSUFFIX@', 'test_fdw_handler'
+ LANGUAGE C;
+CREATE FUNCTION test_support_func(internal)
+ RETURNS internal
+ AS '@libdir@/regress@DLSUFFIX@', 'test_support_func'
+ LANGUAGE C STRICT;
+CREATE FUNCTION test_opclass_options_func(internal)
+ RETURNS void
+ AS '@libdir@/regress@DLSUFFIX@', 'test_opclass_options_func'
+ LANGUAGE C;
+CREATE FUNCTION test_enc_conversion(bytea, name, name, bool, validlen OUT int, result OUT bytea)
+ AS '@libdir@/regress@DLSUFFIX@', 'test_enc_conversion'
+ LANGUAGE C STRICT;
+CREATE FUNCTION binary_coercible(oid, oid)
+ RETURNS bool
+ AS '@libdir@/regress@DLSUFFIX@', 'binary_coercible'
+ LANGUAGE C STRICT STABLE PARALLEL SAFE;
+-- Things that shouldn't work:
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
+ AS 'SELECT ''not an integer'';';
+ERROR: return type mismatch in function declared to return integer
+DETAIL: Actual return type is text.
+CONTEXT: SQL function "test1"
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
+ AS 'not even SQL';
+ERROR: syntax error at or near "not"
+LINE 2: AS 'not even SQL';
+ ^
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
+ AS 'SELECT 1, 2, 3;';
+ERROR: return type mismatch in function declared to return integer
+DETAIL: Final statement must return exactly one column.
+CONTEXT: SQL function "test1"
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
+ AS 'SELECT $2;';
+ERROR: there is no parameter $2
+LINE 2: AS 'SELECT $2;';
+ ^
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
+ AS 'a', 'b';
+ERROR: only one AS item needed for language "sql"
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE C
+ AS 'nosuchfile';
+ERROR: could not access file "nosuchfile": No such file or directory
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE C
+ AS '@libdir@/regress@DLSUFFIX@', 'nosuchsymbol';
+ERROR: could not find function "nosuchsymbol" in file "@libdir@/regress@DLSUFFIX@"
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE internal
+ AS 'nosuch';
+ERROR: there is no built-in function named "nosuch"
diff --git a/src/test/regress/output/create_function_1.source b/src/test/regress/output/create_function_1.source
new file mode 100644
index 0000000..616b610
--- /dev/null
+++ b/src/test/regress/output/create_function_1.source
@@ -0,0 +1,26 @@
+--
+-- CREATE_FUNCTION_1
+--
+-- Create C functions needed by create_type.sql
+CREATE FUNCTION widget_in(cstring)
+ RETURNS widget
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT IMMUTABLE;
+NOTICE: type "widget" is not yet defined
+DETAIL: Creating a shell type definition.
+CREATE FUNCTION widget_out(widget)
+ RETURNS cstring
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT IMMUTABLE;
+NOTICE: argument type widget is only a shell
+CREATE FUNCTION int44in(cstring)
+ RETURNS city_budget
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT IMMUTABLE;
+NOTICE: type "city_budget" is not yet defined
+DETAIL: Creating a shell type definition.
+CREATE FUNCTION int44out(city_budget)
+ RETURNS cstring
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT IMMUTABLE;
+NOTICE: argument type city_budget is only a shell
diff --git a/src/test/regress/output/create_function_2.source b/src/test/regress/output/create_function_2.source
new file mode 100644
index 0000000..ac9a7f5
--- /dev/null
+++ b/src/test/regress/output/create_function_2.source
@@ -0,0 +1,69 @@
+--
+-- CREATE_FUNCTION_2
+--
+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;
+CREATE FUNCTION pt_in_widget(point, widget)
+ RETURNS bool
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT;
+CREATE FUNCTION overpaid(emp)
+ RETURNS bool
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT;
+CREATE FUNCTION interpt_pp(path, path)
+ RETURNS point
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT;
+CREATE FUNCTION reverse_name(name)
+ RETURNS name
+ AS '@libdir@/regress@DLSUFFIX@'
+ LANGUAGE C STRICT;
+--
+-- Function dynamic loading
+--
+LOAD '@libdir@/regress@DLSUFFIX@';
diff --git a/src/test/regress/output/largeobject.source b/src/test/regress/output/largeobject.source
new file mode 100644
index 0000000..91d33b4
--- /dev/null
+++ b/src/test/regress/output/largeobject.source
@@ -0,0 +1,492 @@
+--
+-- Test large object support
+--
+-- ensure consistent test output regardless of the default bytea format
+SET bytea_output TO escape;
+-- Load a file
+CREATE TABLE lotest_stash_values (loid oid, fd integer);
+-- lo_creat(mode integer) returns oid
+-- The mode arg to lo_creat is unused, some vestigal holdover from ancient times
+-- returns the large object id
+INSERT INTO lotest_stash_values (loid) SELECT lo_creat(42);
+-- Test ALTER LARGE OBJECT
+CREATE ROLE regress_lo_user;
+DO $$
+ BEGIN
+ EXECUTE 'ALTER LARGE OBJECT ' || (select loid from lotest_stash_values)
+ || ' OWNER TO regress_lo_user';
+ END
+$$;
+SELECT
+ rol.rolname
+FROM
+ lotest_stash_values s
+ JOIN pg_largeobject_metadata lo ON s.loid = lo.oid
+ JOIN pg_authid rol ON lo.lomowner = rol.oid;
+ rolname
+-----------------
+ regress_lo_user
+(1 row)
+
+-- NOTE: large objects require transactions
+BEGIN;
+-- lo_open(lobjId oid, mode integer) returns integer
+-- The mode parameter to lo_open uses two constants:
+-- INV_READ = 0x20000
+-- INV_WRITE = 0x40000
+-- The return value is a file descriptor-like value which remains valid for the
+-- transaction.
+UPDATE lotest_stash_values SET fd = lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+-- loread/lowrite names are wonky, different from other functions which are lo_*
+-- lowrite(fd integer, data bytea) returns integer
+-- the integer is the number of bytes written
+SELECT lowrite(fd, '
+I wandered lonely as a cloud
+That floats on high o''er vales and hills,
+When all at once I saw a crowd,
+A host, of golden daffodils;
+Beside the lake, beneath the trees,
+Fluttering and dancing in the breeze.
+
+Continuous as the stars that shine
+And twinkle on the milky way,
+They stretched in never-ending line
+Along the margin of a bay:
+Ten thousand saw I at a glance,
+Tossing their heads in sprightly dance.
+
+The waves beside them danced; but they
+Out-did the sparkling waves in glee:
+A poet could not but be gay,
+In such a jocund company:
+I gazed--and gazed--but little thought
+What wealth the show to me had brought:
+
+For oft, when on my couch I lie
+In vacant or in pensive mood,
+They flash upon that inward eye
+Which is the bliss of solitude;
+And then my heart with pleasure fills,
+And dances with the daffodils.
+
+ -- William Wordsworth
+') FROM lotest_stash_values;
+ lowrite
+---------
+ 848
+(1 row)
+
+-- lo_close(fd integer) returns integer
+-- return value is 0 for success, or <0 for error (actually only -1, but...)
+SELECT lo_close(fd) FROM lotest_stash_values;
+ lo_close
+----------
+ 0
+(1 row)
+
+END;
+-- Copy to another large object.
+-- Note: we intentionally don't remove the object created here;
+-- it's left behind to help test pg_dump.
+SELECT lo_from_bytea(0, lo_get(loid)) AS newloid FROM lotest_stash_values
+\gset
+-- Add a comment to it, as well, for pg_dump/pg_upgrade testing.
+COMMENT ON LARGE OBJECT :newloid IS 'I Wandered Lonely as a Cloud';
+-- Read out a portion
+BEGIN;
+UPDATE lotest_stash_values SET fd=lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+-- lo_lseek(fd integer, offset integer, whence integer) returns integer
+-- offset is in bytes, whence is one of three values:
+-- SEEK_SET (= 0) meaning relative to beginning
+-- SEEK_CUR (= 1) meaning relative to current position
+-- SEEK_END (= 2) meaning relative to end (offset better be negative)
+-- returns current position in file
+SELECT lo_lseek(fd, 104, 0) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 104
+(1 row)
+
+-- loread/lowrite names are wonky, different from other functions which are lo_*
+-- loread(fd integer, len integer) returns bytea
+SELECT loread(fd, 28) FROM lotest_stash_values;
+ loread
+------------------------------
+ A host, of golden daffodils;
+(1 row)
+
+SELECT lo_lseek(fd, -19, 1) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 113
+(1 row)
+
+SELECT lowrite(fd, 'n') FROM lotest_stash_values;
+ lowrite
+---------
+ 1
+(1 row)
+
+SELECT lo_tell(fd) FROM lotest_stash_values;
+ lo_tell
+---------
+ 114
+(1 row)
+
+SELECT lo_lseek(fd, -744, 2) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 104
+(1 row)
+
+SELECT loread(fd, 28) FROM lotest_stash_values;
+ loread
+------------------------------
+ A host, on golden daffodils;
+(1 row)
+
+SELECT lo_close(fd) FROM lotest_stash_values;
+ lo_close
+----------
+ 0
+(1 row)
+
+END;
+-- Test resource management
+BEGIN;
+SELECT lo_open(loid, x'40000'::int) from lotest_stash_values;
+ lo_open
+---------
+ 0
+(1 row)
+
+ABORT;
+DO $$
+DECLARE
+ loid oid;
+BEGIN
+ SELECT tbl.loid INTO loid FROM lotest_stash_values tbl;
+ PERFORM lo_export(loid, '@abs_builddir@/results/invalid/path');
+EXCEPTION
+ WHEN UNDEFINED_FILE THEN RAISE NOTICE 'could not open file, as expected';
+END;
+$$;
+NOTICE: could not open file, as expected
+-- Test truncation.
+BEGIN;
+UPDATE lotest_stash_values SET fd=lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+SELECT lo_truncate(fd, 11) FROM lotest_stash_values;
+ lo_truncate
+-------------
+ 0
+(1 row)
+
+SELECT loread(fd, 15) FROM lotest_stash_values;
+ loread
+----------------
+ \012I wandered
+(1 row)
+
+SELECT lo_truncate(fd, 10000) FROM lotest_stash_values;
+ lo_truncate
+-------------
+ 0
+(1 row)
+
+SELECT loread(fd, 10) FROM lotest_stash_values;
+ loread
+------------------------------------------
+ \000\000\000\000\000\000\000\000\000\000
+(1 row)
+
+SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 10000
+(1 row)
+
+SELECT lo_tell(fd) FROM lotest_stash_values;
+ lo_tell
+---------
+ 10000
+(1 row)
+
+SELECT lo_truncate(fd, 5000) FROM lotest_stash_values;
+ lo_truncate
+-------------
+ 0
+(1 row)
+
+SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 5000
+(1 row)
+
+SELECT lo_tell(fd) FROM lotest_stash_values;
+ lo_tell
+---------
+ 5000
+(1 row)
+
+SELECT lo_close(fd) FROM lotest_stash_values;
+ lo_close
+----------
+ 0
+(1 row)
+
+END;
+-- Test 64-bit large object functions.
+BEGIN;
+UPDATE lotest_stash_values SET fd = lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+SELECT lo_lseek64(fd, 4294967296, 0) FROM lotest_stash_values;
+ lo_lseek64
+------------
+ 4294967296
+(1 row)
+
+SELECT lowrite(fd, 'offset:4GB') FROM lotest_stash_values;
+ lowrite
+---------
+ 10
+(1 row)
+
+SELECT lo_tell64(fd) FROM lotest_stash_values;
+ lo_tell64
+------------
+ 4294967306
+(1 row)
+
+SELECT lo_lseek64(fd, -10, 1) FROM lotest_stash_values;
+ lo_lseek64
+------------
+ 4294967296
+(1 row)
+
+SELECT lo_tell64(fd) FROM lotest_stash_values;
+ lo_tell64
+------------
+ 4294967296
+(1 row)
+
+SELECT loread(fd, 10) FROM lotest_stash_values;
+ loread
+------------
+ offset:4GB
+(1 row)
+
+SELECT lo_truncate64(fd, 5000000000) FROM lotest_stash_values;
+ lo_truncate64
+---------------
+ 0
+(1 row)
+
+SELECT lo_lseek64(fd, 0, 2) FROM lotest_stash_values;
+ lo_lseek64
+------------
+ 5000000000
+(1 row)
+
+SELECT lo_tell64(fd) FROM lotest_stash_values;
+ lo_tell64
+------------
+ 5000000000
+(1 row)
+
+SELECT lo_truncate64(fd, 3000000000) FROM lotest_stash_values;
+ lo_truncate64
+---------------
+ 0
+(1 row)
+
+SELECT lo_lseek64(fd, 0, 2) FROM lotest_stash_values;
+ lo_lseek64
+------------
+ 3000000000
+(1 row)
+
+SELECT lo_tell64(fd) FROM lotest_stash_values;
+ lo_tell64
+------------
+ 3000000000
+(1 row)
+
+SELECT lo_close(fd) FROM lotest_stash_values;
+ lo_close
+----------
+ 0
+(1 row)
+
+END;
+-- lo_unlink(lobjId oid) returns integer
+-- return value appears to always be 1
+SELECT lo_unlink(loid) from lotest_stash_values;
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+TRUNCATE lotest_stash_values;
+INSERT INTO lotest_stash_values (loid) SELECT lo_import('@abs_srcdir@/data/tenk.data');
+BEGIN;
+UPDATE lotest_stash_values SET fd=lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+-- verify length of large object
+SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 670800
+(1 row)
+
+-- with the default BLCKSZ, LOBLKSIZE = 2048, so this positions us for a block
+-- edge case
+SELECT lo_lseek(fd, 2030, 0) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 2030
+(1 row)
+
+-- this should get half of the value from page 0 and half from page 1 of the
+-- large object
+SELECT loread(fd, 36) FROM lotest_stash_values;
+ loread
+-----------------------------------------------------------------
+ AAA\011FBAAAA\011VVVVxx\0122513\01132\0111\0111\0113\01113\0111
+(1 row)
+
+SELECT lo_tell(fd) FROM lotest_stash_values;
+ lo_tell
+---------
+ 2066
+(1 row)
+
+SELECT lo_lseek(fd, -26, 1) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 2040
+(1 row)
+
+SELECT lowrite(fd, 'abcdefghijklmnop') FROM lotest_stash_values;
+ lowrite
+---------
+ 16
+(1 row)
+
+SELECT lo_lseek(fd, 2030, 0) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 2030
+(1 row)
+
+SELECT loread(fd, 36) FROM lotest_stash_values;
+ loread
+-----------------------------------------------------
+ AAA\011FBAAAAabcdefghijklmnop1\0111\0113\01113\0111
+(1 row)
+
+SELECT lo_close(fd) FROM lotest_stash_values;
+ lo_close
+----------
+ 0
+(1 row)
+
+END;
+SELECT lo_export(loid, '@abs_builddir@/results/lotest.txt') FROM lotest_stash_values;
+ lo_export
+-----------
+ 1
+(1 row)
+
+\lo_import '@abs_builddir@/results/lotest.txt'
+\set newloid :LASTOID
+-- just make sure \lo_export does not barf
+\lo_export :newloid '@abs_builddir@/results/lotest2.txt'
+-- This is a hack to test that export/import are reversible
+-- This uses knowledge about the inner workings of large object mechanism
+-- which should not be used outside it. This makes it a HACK
+SELECT pageno, data FROM pg_largeobject WHERE loid = (SELECT loid from lotest_stash_values)
+EXCEPT
+SELECT pageno, data FROM pg_largeobject WHERE loid = :newloid;
+ pageno | data
+--------+------
+(0 rows)
+
+SELECT lo_unlink(loid) FROM lotest_stash_values;
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+TRUNCATE lotest_stash_values;
+\lo_unlink :newloid
+\lo_import '@abs_builddir@/results/lotest.txt'
+\set newloid_1 :LASTOID
+SELECT lo_from_bytea(0, lo_get(:newloid_1)) AS newloid_2
+\gset
+SELECT md5(lo_get(:newloid_1)) = md5(lo_get(:newloid_2));
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT lo_get(:newloid_1, 0, 20);
+ lo_get
+-------------------------------------------
+ 8800\0110\0110\0110\0110\0110\0110\011800
+(1 row)
+
+SELECT lo_get(:newloid_1, 10, 20);
+ lo_get
+-------------------------------------------
+ \0110\0110\0110\011800\011800\0113800\011
+(1 row)
+
+SELECT lo_put(:newloid_1, 5, decode('afafafaf', 'hex'));
+ lo_put
+--------
+
+(1 row)
+
+SELECT lo_get(:newloid_1, 0, 20);
+ lo_get
+-------------------------------------------------
+ 8800\011\257\257\257\2570\0110\0110\0110\011800
+(1 row)
+
+SELECT lo_put(:newloid_1, 4294967310, 'foo');
+ lo_put
+--------
+
+(1 row)
+
+SELECT lo_get(:newloid_1);
+ERROR: large object read request is too large
+SELECT lo_get(:newloid_1, 4294967294, 100);
+ lo_get
+---------------------------------------------------------------------
+ \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000foo
+(1 row)
+
+\lo_unlink :newloid_1
+\lo_unlink :newloid_2
+-- This object is left in the database for pg_dump test purposes
+SELECT lo_from_bytea(0, E'\\xdeadbeef') AS newloid
+\gset
+SET bytea_output TO hex;
+SELECT lo_get(:newloid);
+ lo_get
+------------
+ \xdeadbeef
+(1 row)
+
+-- Create one more object that we leave behind for testing pg_dump/pg_upgrade;
+-- this one intentionally has an OID in the system range
+SELECT lo_create(2121);
+ lo_create
+-----------
+ 2121
+(1 row)
+
+COMMENT ON LARGE OBJECT 2121 IS 'testing comments';
+-- Clean up
+DROP TABLE lotest_stash_values;
+DROP ROLE regress_lo_user;
diff --git a/src/test/regress/output/largeobject_1.source b/src/test/regress/output/largeobject_1.source
new file mode 100644
index 0000000..cb910e2
--- /dev/null
+++ b/src/test/regress/output/largeobject_1.source
@@ -0,0 +1,492 @@
+--
+-- Test large object support
+--
+-- ensure consistent test output regardless of the default bytea format
+SET bytea_output TO escape;
+-- Load a file
+CREATE TABLE lotest_stash_values (loid oid, fd integer);
+-- lo_creat(mode integer) returns oid
+-- The mode arg to lo_creat is unused, some vestigal holdover from ancient times
+-- returns the large object id
+INSERT INTO lotest_stash_values (loid) SELECT lo_creat(42);
+-- Test ALTER LARGE OBJECT
+CREATE ROLE regress_lo_user;
+DO $$
+ BEGIN
+ EXECUTE 'ALTER LARGE OBJECT ' || (select loid from lotest_stash_values)
+ || ' OWNER TO regress_lo_user';
+ END
+$$;
+SELECT
+ rol.rolname
+FROM
+ lotest_stash_values s
+ JOIN pg_largeobject_metadata lo ON s.loid = lo.oid
+ JOIN pg_authid rol ON lo.lomowner = rol.oid;
+ rolname
+-----------------
+ regress_lo_user
+(1 row)
+
+-- NOTE: large objects require transactions
+BEGIN;
+-- lo_open(lobjId oid, mode integer) returns integer
+-- The mode parameter to lo_open uses two constants:
+-- INV_READ = 0x20000
+-- INV_WRITE = 0x40000
+-- The return value is a file descriptor-like value which remains valid for the
+-- transaction.
+UPDATE lotest_stash_values SET fd = lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+-- loread/lowrite names are wonky, different from other functions which are lo_*
+-- lowrite(fd integer, data bytea) returns integer
+-- the integer is the number of bytes written
+SELECT lowrite(fd, '
+I wandered lonely as a cloud
+That floats on high o''er vales and hills,
+When all at once I saw a crowd,
+A host, of golden daffodils;
+Beside the lake, beneath the trees,
+Fluttering and dancing in the breeze.
+
+Continuous as the stars that shine
+And twinkle on the milky way,
+They stretched in never-ending line
+Along the margin of a bay:
+Ten thousand saw I at a glance,
+Tossing their heads in sprightly dance.
+
+The waves beside them danced; but they
+Out-did the sparkling waves in glee:
+A poet could not but be gay,
+In such a jocund company:
+I gazed--and gazed--but little thought
+What wealth the show to me had brought:
+
+For oft, when on my couch I lie
+In vacant or in pensive mood,
+They flash upon that inward eye
+Which is the bliss of solitude;
+And then my heart with pleasure fills,
+And dances with the daffodils.
+
+ -- William Wordsworth
+') FROM lotest_stash_values;
+ lowrite
+---------
+ 848
+(1 row)
+
+-- lo_close(fd integer) returns integer
+-- return value is 0 for success, or <0 for error (actually only -1, but...)
+SELECT lo_close(fd) FROM lotest_stash_values;
+ lo_close
+----------
+ 0
+(1 row)
+
+END;
+-- Copy to another large object.
+-- Note: we intentionally don't remove the object created here;
+-- it's left behind to help test pg_dump.
+SELECT lo_from_bytea(0, lo_get(loid)) AS newloid FROM lotest_stash_values
+\gset
+-- Add a comment to it, as well, for pg_dump/pg_upgrade testing.
+COMMENT ON LARGE OBJECT :newloid IS 'I Wandered Lonely as a Cloud';
+-- Read out a portion
+BEGIN;
+UPDATE lotest_stash_values SET fd=lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+-- lo_lseek(fd integer, offset integer, whence integer) returns integer
+-- offset is in bytes, whence is one of three values:
+-- SEEK_SET (= 0) meaning relative to beginning
+-- SEEK_CUR (= 1) meaning relative to current position
+-- SEEK_END (= 2) meaning relative to end (offset better be negative)
+-- returns current position in file
+SELECT lo_lseek(fd, 104, 0) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 104
+(1 row)
+
+-- loread/lowrite names are wonky, different from other functions which are lo_*
+-- loread(fd integer, len integer) returns bytea
+SELECT loread(fd, 28) FROM lotest_stash_values;
+ loread
+------------------------------
+ A host, of golden daffodils;
+(1 row)
+
+SELECT lo_lseek(fd, -19, 1) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 113
+(1 row)
+
+SELECT lowrite(fd, 'n') FROM lotest_stash_values;
+ lowrite
+---------
+ 1
+(1 row)
+
+SELECT lo_tell(fd) FROM lotest_stash_values;
+ lo_tell
+---------
+ 114
+(1 row)
+
+SELECT lo_lseek(fd, -744, 2) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 104
+(1 row)
+
+SELECT loread(fd, 28) FROM lotest_stash_values;
+ loread
+------------------------------
+ A host, on golden daffodils;
+(1 row)
+
+SELECT lo_close(fd) FROM lotest_stash_values;
+ lo_close
+----------
+ 0
+(1 row)
+
+END;
+-- Test resource management
+BEGIN;
+SELECT lo_open(loid, x'40000'::int) from lotest_stash_values;
+ lo_open
+---------
+ 0
+(1 row)
+
+ABORT;
+DO $$
+DECLARE
+ loid oid;
+BEGIN
+ SELECT tbl.loid INTO loid FROM lotest_stash_values tbl;
+ PERFORM lo_export(loid, '@abs_builddir@/results/invalid/path');
+EXCEPTION
+ WHEN UNDEFINED_FILE THEN RAISE NOTICE 'could not open file, as expected';
+END;
+$$;
+NOTICE: could not open file, as expected
+-- Test truncation.
+BEGIN;
+UPDATE lotest_stash_values SET fd=lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+SELECT lo_truncate(fd, 11) FROM lotest_stash_values;
+ lo_truncate
+-------------
+ 0
+(1 row)
+
+SELECT loread(fd, 15) FROM lotest_stash_values;
+ loread
+----------------
+ \012I wandered
+(1 row)
+
+SELECT lo_truncate(fd, 10000) FROM lotest_stash_values;
+ lo_truncate
+-------------
+ 0
+(1 row)
+
+SELECT loread(fd, 10) FROM lotest_stash_values;
+ loread
+------------------------------------------
+ \000\000\000\000\000\000\000\000\000\000
+(1 row)
+
+SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 10000
+(1 row)
+
+SELECT lo_tell(fd) FROM lotest_stash_values;
+ lo_tell
+---------
+ 10000
+(1 row)
+
+SELECT lo_truncate(fd, 5000) FROM lotest_stash_values;
+ lo_truncate
+-------------
+ 0
+(1 row)
+
+SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 5000
+(1 row)
+
+SELECT lo_tell(fd) FROM lotest_stash_values;
+ lo_tell
+---------
+ 5000
+(1 row)
+
+SELECT lo_close(fd) FROM lotest_stash_values;
+ lo_close
+----------
+ 0
+(1 row)
+
+END;
+-- Test 64-bit large object functions.
+BEGIN;
+UPDATE lotest_stash_values SET fd = lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+SELECT lo_lseek64(fd, 4294967296, 0) FROM lotest_stash_values;
+ lo_lseek64
+------------
+ 4294967296
+(1 row)
+
+SELECT lowrite(fd, 'offset:4GB') FROM lotest_stash_values;
+ lowrite
+---------
+ 10
+(1 row)
+
+SELECT lo_tell64(fd) FROM lotest_stash_values;
+ lo_tell64
+------------
+ 4294967306
+(1 row)
+
+SELECT lo_lseek64(fd, -10, 1) FROM lotest_stash_values;
+ lo_lseek64
+------------
+ 4294967296
+(1 row)
+
+SELECT lo_tell64(fd) FROM lotest_stash_values;
+ lo_tell64
+------------
+ 4294967296
+(1 row)
+
+SELECT loread(fd, 10) FROM lotest_stash_values;
+ loread
+------------
+ offset:4GB
+(1 row)
+
+SELECT lo_truncate64(fd, 5000000000) FROM lotest_stash_values;
+ lo_truncate64
+---------------
+ 0
+(1 row)
+
+SELECT lo_lseek64(fd, 0, 2) FROM lotest_stash_values;
+ lo_lseek64
+------------
+ 5000000000
+(1 row)
+
+SELECT lo_tell64(fd) FROM lotest_stash_values;
+ lo_tell64
+------------
+ 5000000000
+(1 row)
+
+SELECT lo_truncate64(fd, 3000000000) FROM lotest_stash_values;
+ lo_truncate64
+---------------
+ 0
+(1 row)
+
+SELECT lo_lseek64(fd, 0, 2) FROM lotest_stash_values;
+ lo_lseek64
+------------
+ 3000000000
+(1 row)
+
+SELECT lo_tell64(fd) FROM lotest_stash_values;
+ lo_tell64
+------------
+ 3000000000
+(1 row)
+
+SELECT lo_close(fd) FROM lotest_stash_values;
+ lo_close
+----------
+ 0
+(1 row)
+
+END;
+-- lo_unlink(lobjId oid) returns integer
+-- return value appears to always be 1
+SELECT lo_unlink(loid) from lotest_stash_values;
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+TRUNCATE lotest_stash_values;
+INSERT INTO lotest_stash_values (loid) SELECT lo_import('@abs_srcdir@/data/tenk.data');
+BEGIN;
+UPDATE lotest_stash_values SET fd=lo_open(loid, CAST(x'20000' | x'40000' AS integer));
+-- verify length of large object
+SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 680800
+(1 row)
+
+-- with the default BLCKSZ, LOBLKSIZE = 2048, so this positions us for a block
+-- edge case
+SELECT lo_lseek(fd, 2030, 0) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 2030
+(1 row)
+
+-- this should get half of the value from page 0 and half from page 1 of the
+-- large object
+SELECT loread(fd, 36) FROM lotest_stash_values;
+ loread
+--------------------------------------------------------------
+ 44\011144\0111144\0114144\0119144\01188\01189\011SNAAAA\011F
+(1 row)
+
+SELECT lo_tell(fd) FROM lotest_stash_values;
+ lo_tell
+---------
+ 2066
+(1 row)
+
+SELECT lo_lseek(fd, -26, 1) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 2040
+(1 row)
+
+SELECT lowrite(fd, 'abcdefghijklmnop') FROM lotest_stash_values;
+ lowrite
+---------
+ 16
+(1 row)
+
+SELECT lo_lseek(fd, 2030, 0) FROM lotest_stash_values;
+ lo_lseek
+----------
+ 2030
+(1 row)
+
+SELECT loread(fd, 36) FROM lotest_stash_values;
+ loread
+--------------------------------------------------
+ 44\011144\011114abcdefghijklmnop9\011SNAAAA\011F
+(1 row)
+
+SELECT lo_close(fd) FROM lotest_stash_values;
+ lo_close
+----------
+ 0
+(1 row)
+
+END;
+SELECT lo_export(loid, '@abs_builddir@/results/lotest.txt') FROM lotest_stash_values;
+ lo_export
+-----------
+ 1
+(1 row)
+
+\lo_import '@abs_builddir@/results/lotest.txt'
+\set newloid :LASTOID
+-- just make sure \lo_export does not barf
+\lo_export :newloid '@abs_builddir@/results/lotest2.txt'
+-- This is a hack to test that export/import are reversible
+-- This uses knowledge about the inner workings of large object mechanism
+-- which should not be used outside it. This makes it a HACK
+SELECT pageno, data FROM pg_largeobject WHERE loid = (SELECT loid from lotest_stash_values)
+EXCEPT
+SELECT pageno, data FROM pg_largeobject WHERE loid = :newloid;
+ pageno | data
+--------+------
+(0 rows)
+
+SELECT lo_unlink(loid) FROM lotest_stash_values;
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+TRUNCATE lotest_stash_values;
+\lo_unlink :newloid
+\lo_import '@abs_builddir@/results/lotest.txt'
+\set newloid_1 :LASTOID
+SELECT lo_from_bytea(0, lo_get(:newloid_1)) AS newloid_2
+\gset
+SELECT md5(lo_get(:newloid_1)) = md5(lo_get(:newloid_2));
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT lo_get(:newloid_1, 0, 20);
+ lo_get
+-------------------------------------------
+ 8800\0110\0110\0110\0110\0110\0110\011800
+(1 row)
+
+SELECT lo_get(:newloid_1, 10, 20);
+ lo_get
+-------------------------------------------
+ \0110\0110\0110\011800\011800\0113800\011
+(1 row)
+
+SELECT lo_put(:newloid_1, 5, decode('afafafaf', 'hex'));
+ lo_put
+--------
+
+(1 row)
+
+SELECT lo_get(:newloid_1, 0, 20);
+ lo_get
+-------------------------------------------------
+ 8800\011\257\257\257\2570\0110\0110\0110\011800
+(1 row)
+
+SELECT lo_put(:newloid_1, 4294967310, 'foo');
+ lo_put
+--------
+
+(1 row)
+
+SELECT lo_get(:newloid_1);
+ERROR: large object read request is too large
+SELECT lo_get(:newloid_1, 4294967294, 100);
+ lo_get
+---------------------------------------------------------------------
+ \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000foo
+(1 row)
+
+\lo_unlink :newloid_1
+\lo_unlink :newloid_2
+-- This object is left in the database for pg_dump test purposes
+SELECT lo_from_bytea(0, E'\\xdeadbeef') AS newloid
+\gset
+SET bytea_output TO hex;
+SELECT lo_get(:newloid);
+ lo_get
+------------
+ \xdeadbeef
+(1 row)
+
+-- Create one more object that we leave behind for testing pg_dump/pg_upgrade;
+-- this one intentionally has an OID in the system range
+SELECT lo_create(2121);
+ lo_create
+-----------
+ 2121
+(1 row)
+
+COMMENT ON LARGE OBJECT 2121 IS 'testing comments';
+-- Clean up
+DROP TABLE lotest_stash_values;
+DROP ROLE regress_lo_user;
diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source
new file mode 100644
index 0000000..b9595cc
--- /dev/null
+++ b/src/test/regress/output/misc.source
@@ -0,0 +1,692 @@
+--
+-- MISC
+--
+--
+-- 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)
+--
+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
+--
+COPY onek TO '@abs_builddir@/results/onek.data';
+DELETE FROM onek;
+COPY onek FROM '@abs_builddir@/results/onek.data';
+SELECT unique1 FROM onek WHERE unique1 < 2 ORDER BY unique1;
+ unique1
+---------
+ 0
+ 1
+(2 rows)
+
+DELETE FROM onek2;
+COPY onek2 FROM '@abs_builddir@/results/onek.data';
+SELECT unique1 FROM onek2 WHERE unique1 < 2 ORDER BY unique1;
+ unique1
+---------
+ 0
+ 1
+(2 rows)
+
+COPY BINARY stud_emp TO '@abs_builddir@/results/stud_emp.data';
+DELETE FROM stud_emp;
+COPY BINARY stud_emp FROM '@abs_builddir@/results/stud_emp.data';
+SELECT * FROM stud_emp;
+ 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)
+
+-- COPY aggtest FROM stdin;
+-- 56 7.8
+-- 100 99.097
+-- 0 0.09561
+-- 42 324.78
+-- .
+-- COPY aggtest TO stdout;
+--
+-- inheritance stress test
+--
+SELECT * FROM a_star*;
+ class | a
+-------+----
+ a | 1
+ a | 2
+ a |
+ b | 3
+ b | 4
+ b |
+ b |
+ c | 5
+ c | 6
+ c |
+ c |
+ d | 7
+ d | 8
+ d | 9
+ d | 10
+ d |
+ d | 11
+ d | 12
+ d | 13
+ d |
+ d |
+ d |
+ d | 14
+ d |
+ d |
+ d |
+ d |
+ e | 15
+ e | 16
+ e | 17
+ e |
+ e | 18
+ e |
+ e |
+ f | 19
+ f | 20
+ f | 21
+ f | 22
+ f |
+ f | 24
+ f | 25
+ f | 26
+ f |
+ f |
+ f |
+ f | 27
+ f |
+ f |
+ f |
+ f |
+(50 rows)
+
+SELECT *
+ FROM b_star* x
+ WHERE x.b = text 'bumble' or x.a < 3;
+ class | a | b
+-------+---+--------
+ b | | bumble
+(1 row)
+
+SELECT class, a
+ FROM c_star* x
+ WHERE x.c ~ text 'hi';
+ class | a
+-------+----
+ c | 5
+ c |
+ d | 7
+ d | 8
+ d | 10
+ d |
+ d | 12
+ d |
+ d |
+ d |
+ e | 15
+ e | 16
+ e |
+ e |
+ f | 19
+ f | 20
+ f | 21
+ f |
+ f | 24
+ f |
+ f |
+ f |
+(22 rows)
+
+SELECT class, b, c
+ FROM d_star* x
+ WHERE x.a < 100;
+ class | b | c
+-------+---------+------------
+ d | grumble | hi sunita
+ d | stumble | hi koko
+ d | rumble |
+ d | | hi kristin
+ d | fumble |
+ d | | hi avi
+ d | |
+ d | |
+(8 rows)
+
+SELECT class, c FROM e_star* x WHERE x.c NOTNULL;
+ class | c
+-------+-------------
+ e | hi carol
+ e | hi bob
+ e | hi michelle
+ e | hi elisa
+ f | hi claire
+ f | hi mike
+ f | hi marcel
+ f | hi keith
+ f | hi marc
+ f | hi allison
+ f | hi jeff
+ f | hi carl
+(12 rows)
+
+SELECT * FROM f_star* x WHERE x.c ISNULL;
+ class | a | c | e | f
+-------+----+---+-----+-------------------------------------------
+ f | 22 | | -7 | ((111,555),(222,666),(333,777),(444,888))
+ f | 25 | | -9 |
+ f | 26 | | | ((11111,33333),(22222,44444))
+ f | | | -11 | ((1111111,3333333),(2222222,4444444))
+ f | 27 | | |
+ f | | | -12 |
+ f | | | | ((11111111,33333333),(22222222,44444444))
+ f | | | |
+(8 rows)
+
+-- grouping and aggregation on inherited sets have been busted in the past...
+SELECT sum(a) FROM a_star*;
+ sum
+-----
+ 355
+(1 row)
+
+SELECT class, sum(a) FROM a_star* GROUP BY class ORDER BY class;
+ class | sum
+-------+-----
+ a | 3
+ b | 7
+ c | 11
+ d | 84
+ e | 66
+ f | 184
+(6 rows)
+
+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;
+ class | aa
+-------+----
+ a |
+ b |
+ b |
+ c |
+ c |
+ d |
+ d |
+ d |
+ d |
+ d |
+ d |
+ d |
+ d |
+ e |
+ e |
+ e |
+ f |
+ f |
+ f |
+ f |
+ f |
+ f |
+ f |
+ f |
+(24 rows)
+
+-- 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;
+ class | foo
+-------+-----
+ a | 2
+ b | 3
+ b | 4
+ c | 5
+ c | 6
+ d | 7
+ d | 8
+ d | 9
+ d | 10
+ d | 11
+ d | 12
+ d | 13
+ d | 14
+ e | 15
+ e | 16
+ e | 17
+ e | 18
+ f | 19
+ f | 20
+ f | 21
+ f | 22
+ f | 24
+ f | 25
+ f | 26
+ f | 27
+(25 rows)
+
+ALTER TABLE a_star RENAME COLUMN foo TO aa;
+SELECT *
+ from a_star*
+ WHERE aa < 1000;
+ class | aa
+-------+----
+ a | 1
+ a | 2
+ b | 3
+ b | 4
+ c | 5
+ c | 6
+ d | 7
+ d | 8
+ d | 9
+ d | 10
+ d | 11
+ d | 12
+ d | 13
+ d | 14
+ e | 15
+ e | 16
+ e | 17
+ e | 18
+ f | 19
+ f | 20
+ f | 21
+ f | 22
+ f | 24
+ f | 25
+ f | 26
+ f | 27
+(26 rows)
+
+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*;
+ class | aa | cc | ee | e
+-------+----+-------------+-----+---
+ e | 15 | hi carol | -1 |
+ e | 16 | hi bob | |
+ e | 17 | | -2 |
+ e | | hi michelle | -3 |
+ e | 18 | | |
+ e | | hi elisa | |
+ e | | | -4 |
+ f | 19 | hi claire | -5 |
+ f | 20 | hi mike | -6 |
+ f | 21 | hi marcel | |
+ f | 22 | | -7 |
+ f | | hi keith | -8 |
+ f | 24 | hi marc | |
+ f | 25 | | -9 |
+ f | 26 | | |
+ f | | hi allison | -10 |
+ f | | hi jeff | |
+ f | | | -11 |
+ f | 27 | | |
+ f | | hi carl | |
+ f | | | -12 |
+ f | | | |
+ f | | | |
+(23 rows)
+
+ALTER TABLE a_star* ADD COLUMN a text;
+NOTICE: merging definition of column "a" for child "d_star"
+-- 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;
+ relname | has_toast_table
+---------+-----------------
+ a_star | t
+ c_star | t
+(2 rows)
+
+--UPDATE b_star*
+-- SET a = text 'gazpacho'
+-- WHERE aa > 4;
+SELECT class, aa, a FROM a_star*;
+ class | aa | a
+-------+----+---
+ a | 1 |
+ a | 2 |
+ a | |
+ b | 3 |
+ b | 4 |
+ b | |
+ b | |
+ c | 5 |
+ c | 6 |
+ c | |
+ c | |
+ d | 7 |
+ d | 8 |
+ d | 9 |
+ d | 10 |
+ d | |
+ d | 11 |
+ d | 12 |
+ d | 13 |
+ d | |
+ d | |
+ d | |
+ d | 14 |
+ d | |
+ d | |
+ d | |
+ d | |
+ e | 15 |
+ e | 16 |
+ e | 17 |
+ e | |
+ e | 18 |
+ e | |
+ e | |
+ f | 19 |
+ f | 20 |
+ f | 21 |
+ f | 22 |
+ f | |
+ f | 24 |
+ f | 25 |
+ f | 26 |
+ f | |
+ f | |
+ f | |
+ f | 27 |
+ f | |
+ f | |
+ f | |
+ f | |
+(50 rows)
+
+--
+-- versions
+--
+--
+-- postquel functions
+--
+--
+-- 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)
+
+SELECT hobbies_by_name('basketball');
+ hobbies_by_name
+-----------------
+ joe
+(1 row)
+
+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
+--
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
new file mode 100644
index 0000000..1bbe7e0
--- /dev/null
+++ b/src/test/regress/output/tablespace.source
@@ -0,0 +1,936 @@
+-- create a tablespace using WITH clause
+CREATE TABLESPACE regress_tblspacewith LOCATION '@testtablespace@' WITH (some_nonexistent_parameter = true); -- fail
+ERROR: unrecognized parameter "some_nonexistent_parameter"
+CREATE TABLESPACE regress_tblspacewith LOCATION '@testtablespace@' WITH (random_page_cost = 3.0); -- ok
+-- check to see the parameter was used
+SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
+ spcoptions
+------------------------
+ {random_page_cost=3.0}
+(1 row)
+
+-- drop the tablespace so we can re-use the location
+DROP TABLESPACE regress_tblspacewith;
+-- create a tablespace we can use
+CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- 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);
+ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
+ERROR: unrecognized parameter "some_nonexistent_parameter"
+ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
+ERROR: RESET must not include values for parameters
+ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
+-- REINDEX (TABLESPACE)
+-- catalogs and system tablespaces
+-- system catalog, fail
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_am;
+ERROR: cannot move system relation "pg_am_name_index"
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am;
+ERROR: cannot reindex system catalogs concurrently
+-- shared catalog, fail
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_authid;
+ERROR: cannot move system relation "pg_authid_rolname_index"
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid;
+ERROR: cannot reindex system catalogs concurrently
+-- toast relations, fail
+REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1260_index;
+ERROR: cannot move system relation "pg_toast_1260_index"
+REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1260_index;
+ERROR: cannot reindex system catalogs concurrently
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1260;
+ERROR: cannot move system relation "pg_toast_1260_index"
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1260;
+ERROR: cannot reindex system catalogs concurrently
+-- system catalog, fail
+REINDEX (TABLESPACE pg_global) TABLE pg_authid;
+ERROR: cannot move system relation "pg_authid_rolname_index"
+REINDEX (TABLESPACE pg_global) TABLE CONCURRENTLY pg_authid;
+ERROR: cannot reindex system catalogs concurrently
+-- table with toast relation
+CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, t text);
+INSERT INTO regress_tblspace_test_tbl (num1, num2, t)
+ SELECT round(random()*100), random(), 'text'
+ FROM generate_series(1, 10) s(i);
+CREATE INDEX regress_tblspace_test_tbl_idx ON regress_tblspace_test_tbl (num1);
+-- move to global tablespace, fail
+REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx;
+ERROR: only shared relations can be placed in pg_global tablespace
+REINDEX (TABLESPACE pg_global) INDEX CONCURRENTLY regress_tblspace_test_tbl_idx;
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check transactional behavior of REINDEX (TABLESPACE)
+BEGIN;
+REINDEX (TABLESPACE regress_tblspace) INDEX regress_tblspace_test_tbl_idx;
+REINDEX (TABLESPACE regress_tblspace) TABLE regress_tblspace_test_tbl;
+ROLLBACK;
+-- no relation moved to the new tablespace
+SELECT c.relname FROM pg_class c, pg_tablespace s
+ WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace';
+ relname
+---------
+(0 rows)
+
+-- check that all indexes are moved to a new tablespace with different
+-- relfilenode.
+-- Save first the existing relfilenode for the toast and main relations.
+SELECT relfilenode as main_filenode FROM pg_class
+ WHERE relname = 'regress_tblspace_test_tbl_idx' \gset
+SELECT relfilenode as toast_filenode FROM pg_class
+ WHERE oid =
+ (SELECT i.indexrelid
+ FROM pg_class c,
+ pg_index i
+ WHERE i.indrelid = c.reltoastrelid AND
+ c.relname = 'regress_tblspace_test_tbl') \gset
+REINDEX (TABLESPACE regress_tblspace) TABLE regress_tblspace_test_tbl;
+SELECT c.relname FROM pg_class c, pg_tablespace s
+ WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace'
+ ORDER BY c.relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+ALTER TABLE regress_tblspace_test_tbl SET TABLESPACE regress_tblspace;
+ALTER TABLE regress_tblspace_test_tbl SET TABLESPACE pg_default;
+SELECT c.relname FROM pg_class c, pg_tablespace s
+ WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace'
+ ORDER BY c.relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- Move back to the default tablespace.
+ALTER INDEX regress_tblspace_test_tbl_idx SET TABLESPACE pg_default;
+SELECT c.relname FROM pg_class c, pg_tablespace s
+ WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace'
+ ORDER BY c.relname;
+ relname
+---------
+(0 rows)
+
+REINDEX (TABLESPACE regress_tblspace, CONCURRENTLY) TABLE regress_tblspace_test_tbl;
+SELECT c.relname FROM pg_class c, pg_tablespace s
+ WHERE c.reltablespace = s.oid AND s.spcname = 'regress_tblspace'
+ ORDER BY c.relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+SELECT relfilenode = :main_filenode AS main_same FROM pg_class
+ WHERE relname = 'regress_tblspace_test_tbl_idx';
+ main_same
+-----------
+ f
+(1 row)
+
+SELECT relfilenode = :toast_filenode as toast_same FROM pg_class
+ WHERE oid =
+ (SELECT i.indexrelid
+ FROM pg_class c,
+ pg_index i
+ WHERE i.indrelid = c.reltoastrelid AND
+ c.relname = 'regress_tblspace_test_tbl');
+ toast_same
+------------
+ f
+(1 row)
+
+DROP TABLE regress_tblspace_test_tbl;
+-- REINDEX (TABLESPACE) with partitions
+-- Create a partition tree and check the set of relations reindexed
+-- with their new tablespace.
+CREATE TABLE tbspace_reindex_part (c1 int, c2 int) PARTITION BY RANGE (c1);
+CREATE TABLE tbspace_reindex_part_0 PARTITION OF tbspace_reindex_part
+ FOR VALUES FROM (0) TO (10) PARTITION BY list (c2);
+CREATE TABLE tbspace_reindex_part_0_1 PARTITION OF tbspace_reindex_part_0
+ FOR VALUES IN (1);
+CREATE TABLE tbspace_reindex_part_0_2 PARTITION OF tbspace_reindex_part_0
+ FOR VALUES IN (2);
+-- This partitioned table will have no partitions.
+CREATE TABLE tbspace_reindex_part_10 PARTITION OF tbspace_reindex_part
+ FOR VALUES FROM (10) TO (20) PARTITION BY list (c2);
+-- Create some partitioned indexes
+CREATE INDEX tbspace_reindex_part_index ON ONLY tbspace_reindex_part (c1);
+CREATE INDEX tbspace_reindex_part_index_0 ON ONLY tbspace_reindex_part_0 (c1);
+ALTER INDEX tbspace_reindex_part_index ATTACH PARTITION tbspace_reindex_part_index_0;
+-- This partitioned index will have no partitions.
+CREATE INDEX tbspace_reindex_part_index_10 ON ONLY tbspace_reindex_part_10 (c1);
+ALTER INDEX tbspace_reindex_part_index ATTACH PARTITION tbspace_reindex_part_index_10;
+CREATE INDEX tbspace_reindex_part_index_0_1 ON ONLY tbspace_reindex_part_0_1 (c1);
+ALTER INDEX tbspace_reindex_part_index_0 ATTACH PARTITION tbspace_reindex_part_index_0_1;
+CREATE INDEX tbspace_reindex_part_index_0_2 ON ONLY tbspace_reindex_part_0_2 (c1);
+ALTER INDEX tbspace_reindex_part_index_0 ATTACH PARTITION tbspace_reindex_part_index_0_2;
+SELECT relid, parentrelid, level FROM pg_partition_tree('tbspace_reindex_part_index')
+ ORDER BY relid, level;
+ relid | parentrelid | level
+--------------------------------+------------------------------+-------
+ tbspace_reindex_part_index | | 0
+ tbspace_reindex_part_index_0 | tbspace_reindex_part_index | 1
+ tbspace_reindex_part_index_10 | tbspace_reindex_part_index | 1
+ tbspace_reindex_part_index_0_1 | tbspace_reindex_part_index_0 | 2
+ tbspace_reindex_part_index_0_2 | tbspace_reindex_part_index_0 | 2
+(5 rows)
+
+-- Track the original tablespace, relfilenode and OID of each index
+-- in the tree.
+CREATE TEMP TABLE reindex_temp_before AS
+ SELECT oid, relname, relfilenode, reltablespace
+ FROM pg_class
+ WHERE relname ~ 'tbspace_reindex_part_index';
+REINDEX (TABLESPACE regress_tblspace, CONCURRENTLY) TABLE tbspace_reindex_part;
+-- REINDEX CONCURRENTLY changes the OID of the old relation, hence a check
+-- based on the relation name below.
+SELECT b.relname,
+ CASE WHEN a.relfilenode = b.relfilenode THEN 'relfilenode is unchanged'
+ ELSE 'relfilenode has changed' END AS filenode,
+ CASE WHEN a.reltablespace = b.reltablespace THEN 'reltablespace is unchanged'
+ ELSE 'reltablespace has changed' END AS tbspace
+ FROM reindex_temp_before b JOIN pg_class a ON b.relname = a.relname
+ ORDER BY 1;
+ relname | filenode | tbspace
+--------------------------------+--------------------------+----------------------------
+ tbspace_reindex_part_index | relfilenode is unchanged | reltablespace is unchanged
+ tbspace_reindex_part_index_0 | relfilenode is unchanged | reltablespace is unchanged
+ tbspace_reindex_part_index_0_1 | relfilenode has changed | reltablespace has changed
+ tbspace_reindex_part_index_0_2 | relfilenode has changed | reltablespace has changed
+ tbspace_reindex_part_index_10 | relfilenode is unchanged | reltablespace is unchanged
+(5 rows)
+
+DROP TABLE tbspace_reindex_part;
+-- create a schema we can use
+CREATE SCHEMA testschema;
+-- 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';
+ relname | spcname
+---------+------------------
+ foo | regress_tblspace
+(1 row)
+
+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';
+ relname | spcname
+----------+------------------
+ asselect | regress_tblspace
+(1 row)
+
+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';
+ relname | spcname
+-----------+------------------
+ asexecute | regress_tblspace
+(1 row)
+
+-- 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';
+ relname | spcname
+---------+------------------
+ foo_idx | regress_tblspace
+(1 row)
+
+-- check \d output
+\d testschema.foo
+ Table "testschema.foo"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ i | integer | | |
+Indexes:
+ "foo_idx" btree (i), tablespace "regress_tblspace"
+Tablespace: "regress_tblspace"
+
+\d testschema.foo_idx
+ Index "testschema.foo_idx"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ i | integer | yes | i
+btree, for table "testschema.foo"
+Tablespace: "regress_tblspace"
+
+--
+-- partitioned table
+--
+CREATE TABLE testschema.part (a int) PARTITION BY LIST (a);
+SET default_tablespace TO pg_global;
+CREATE TABLE testschema.part_1 PARTITION OF testschema.part FOR VALUES IN (1);
+ERROR: only shared relations can be placed in pg_global tablespace
+RESET default_tablespace;
+CREATE TABLE testschema.part_1 PARTITION OF testschema.part FOR VALUES IN (1);
+SET default_tablespace TO regress_tblspace;
+CREATE TABLE testschema.part_2 PARTITION OF testschema.part FOR VALUES IN (2);
+SET default_tablespace TO pg_global;
+CREATE TABLE testschema.part_3 PARTITION OF testschema.part FOR VALUES IN (3);
+ERROR: only shared relations can be placed in pg_global tablespace
+ALTER TABLE testschema.part SET TABLESPACE regress_tblspace;
+CREATE TABLE testschema.part_3 PARTITION OF testschema.part FOR VALUES IN (3);
+CREATE TABLE testschema.part_4 PARTITION OF testschema.part FOR VALUES IN (4)
+ TABLESPACE pg_default;
+CREATE TABLE testschema.part_56 PARTITION OF testschema.part FOR VALUES IN (5, 6)
+ PARTITION BY LIST (a);
+ALTER TABLE testschema.part SET TABLESPACE pg_default;
+CREATE TABLE testschema.part_78 PARTITION OF testschema.part FOR VALUES IN (7, 8)
+ PARTITION BY LIST (a);
+ERROR: only shared relations can be placed in pg_global tablespace
+CREATE TABLE testschema.part_910 PARTITION OF testschema.part FOR VALUES IN (9, 10)
+ PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+RESET default_tablespace;
+CREATE TABLE testschema.part_78 PARTITION OF testschema.part FOR VALUES IN (7, 8)
+ PARTITION BY LIST (a);
+SELECT relname, spcname FROM pg_catalog.pg_class c
+ JOIN pg_catalog.pg_namespace n ON (c.relnamespace = n.oid)
+ LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid
+ where c.relname LIKE 'part%' AND n.nspname = 'testschema' order by relname;
+ relname | spcname
+----------+------------------
+ part |
+ part_1 |
+ part_2 | regress_tblspace
+ part_3 | regress_tblspace
+ part_4 |
+ part_56 | regress_tblspace
+ part_78 |
+ part_910 | regress_tblspace
+(8 rows)
+
+RESET default_tablespace;
+DROP TABLE testschema.part;
+-- 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';
+ relname | spcname
+-------------+------------------
+ part1_a_idx | regress_tblspace
+ part2_a_idx | regress_tblspace
+ part_a_idx | regress_tblspace
+(3 rows)
+
+\d testschema.part
+ Partitioned table "testschema.part"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: LIST (a)
+Indexes:
+ "part_a_idx" btree (a), tablespace "regress_tblspace"
+Number of partitions: 2 (Use \d+ to list them.)
+
+\d+ testschema.part
+ Partitioned table "testschema.part"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition key: LIST (a)
+Indexes:
+ "part_a_idx" btree (a), tablespace "regress_tblspace"
+Partitions: testschema.part1 FOR VALUES IN (1),
+ testschema.part2 FOR VALUES IN (2)
+
+\d testschema.part1
+ Table "testschema.part1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition of: testschema.part FOR VALUES IN (1)
+Indexes:
+ "part1_a_idx" btree (a), tablespace "regress_tblspace"
+
+\d+ testschema.part1
+ Table "testschema.part1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition of: testschema.part FOR VALUES IN (1)
+Partition constraint: ((a IS NOT NULL) AND (a = 1))
+Indexes:
+ "part1_a_idx" btree (a), tablespace "regress_tblspace"
+
+\d testschema.part_a_idx
+Partitioned index "testschema.part_a_idx"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ a | integer | yes | a
+btree, for table "testschema.part"
+Number of partitions: 2 (Use \d+ to list them.)
+Tablespace: "regress_tblspace"
+
+\d+ testschema.part_a_idx
+ Partitioned index "testschema.part_a_idx"
+ Column | Type | Key? | Definition | Storage | Stats target
+--------+---------+------+------------+---------+--------------
+ a | integer | yes | a | plain |
+btree, for table "testschema.part"
+Partitions: testschema.part1_a_idx,
+ testschema.part2_a_idx
+Tablespace: "regress_tblspace"
+
+-- partitioned rels cannot specify the default tablespace. These fail:
+CREATE TABLE testschema.dflt (a int PRIMARY KEY) PARTITION BY LIST (a) TABLESPACE pg_default;
+ERROR: cannot specify default tablespace for partitioned relations
+CREATE TABLE testschema.dflt (a int PRIMARY KEY USING INDEX TABLESPACE pg_default) PARTITION BY LIST (a);
+ERROR: cannot specify default tablespace for partitioned relations
+SET default_tablespace TO 'pg_default';
+CREATE TABLE testschema.dflt (a int PRIMARY KEY) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+ERROR: cannot specify default tablespace for partitioned relations
+CREATE TABLE testschema.dflt (a int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace) PARTITION BY LIST (a);
+ERROR: cannot specify default tablespace for partitioned relations
+-- but these work:
+CREATE TABLE testschema.dflt (a int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace) PARTITION BY LIST (a) TABLESPACE regress_tblspace;
+SET default_tablespace TO '';
+CREATE TABLE testschema.dflt2 (a int PRIMARY KEY) PARTITION BY LIST (a);
+DROP TABLE testschema.dflt, testschema.dflt2;
+-- 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;
+ALTER TABLE testschema.test_default_tab ADD CONSTRAINT test_index3 PRIMARY KEY (id);
+ALTER TABLE testschema.test_default_tab ADD CONSTRAINT test_index4 UNIQUE (id) USING INDEX TABLESPACE regress_tblspace;
+\d testschema.test_index1
+ Index "testschema.test_index1"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+btree, for table "testschema.test_default_tab"
+
+\d testschema.test_index2
+ Index "testschema.test_index2"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+btree, for table "testschema.test_default_tab"
+Tablespace: "regress_tblspace"
+
+\d testschema.test_index3
+ Index "testschema.test_index3"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+primary key, btree, for table "testschema.test_default_tab"
+
+\d testschema.test_index4
+ Index "testschema.test_index4"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+unique, btree, for table "testschema.test_default_tab"
+Tablespace: "regress_tblspace"
+
+-- 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
+ Index "testschema.test_index1"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+btree, for table "testschema.test_default_tab"
+
+\d testschema.test_index2
+ Index "testschema.test_index2"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+btree, for table "testschema.test_default_tab"
+Tablespace: "regress_tblspace"
+
+\d testschema.test_index3
+ Index "testschema.test_index3"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+primary key, btree, for table "testschema.test_default_tab"
+
+\d testschema.test_index4
+ Index "testschema.test_index4"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+unique, btree, for table "testschema.test_default_tab"
+Tablespace: "regress_tblspace"
+
+SELECT * FROM testschema.test_default_tab;
+ id
+----
+ 1
+(1 row)
+
+-- 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
+ Index "testschema.test_index1"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ id | integer | yes | id
+btree, for table "testschema.test_default_tab"
+
+\d testschema.test_index2
+ Index "testschema.test_index2"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ id | integer | yes | id
+btree, for table "testschema.test_default_tab"
+Tablespace: "regress_tblspace"
+
+\d testschema.test_index3
+ Index "testschema.test_index3"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ id | integer | yes | id
+primary key, btree, for table "testschema.test_default_tab"
+
+\d testschema.test_index4
+ Index "testschema.test_index4"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ id | integer | yes | id
+unique, btree, for table "testschema.test_default_tab"
+Tablespace: "regress_tblspace"
+
+SELECT * FROM testschema.test_default_tab;
+ id
+----
+ 1
+(1 row)
+
+-- 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
+ Index "testschema.test_index1"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ id | integer | yes | id
+btree, for table "testschema.test_default_tab"
+
+\d testschema.test_index2
+ Index "testschema.test_index2"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ id | integer | yes | id
+btree, for table "testschema.test_default_tab"
+Tablespace: "regress_tblspace"
+
+\d testschema.test_index3
+ Index "testschema.test_index3"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ id | integer | yes | id
+primary key, btree, for table "testschema.test_default_tab"
+
+\d testschema.test_index4
+ Index "testschema.test_index4"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ id | integer | yes | id
+unique, btree, for table "testschema.test_default_tab"
+Tablespace: "regress_tblspace"
+
+-- 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
+ Index "testschema.test_index1"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+btree, for table "testschema.test_default_tab"
+
+\d testschema.test_index2
+ Index "testschema.test_index2"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+btree, for table "testschema.test_default_tab"
+Tablespace: "regress_tblspace"
+
+\d testschema.test_index3
+ Index "testschema.test_index3"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+primary key, btree, for table "testschema.test_default_tab"
+
+\d testschema.test_index4
+ Index "testschema.test_index4"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+unique, btree, for table "testschema.test_default_tab"
+Tablespace: "regress_tblspace"
+
+DROP TABLE testschema.test_default_tab;
+-- check that default_tablespace doesn't affect ALTER TABLE index rebuilds
+-- (this time with a partitioned table)
+CREATE TABLE testschema.test_default_tab_p(id bigint, val bigint)
+ PARTITION BY LIST (id) TABLESPACE regress_tblspace;
+CREATE TABLE testschema.test_default_tab_p1 PARTITION OF testschema.test_default_tab_p
+ FOR VALUES IN (1);
+INSERT INTO testschema.test_default_tab_p VALUES (1);
+CREATE INDEX test_index1 on testschema.test_default_tab_p (val);
+CREATE INDEX test_index2 on testschema.test_default_tab_p (val) TABLESPACE regress_tblspace;
+ALTER TABLE testschema.test_default_tab_p ADD CONSTRAINT test_index3 PRIMARY KEY (id);
+ALTER TABLE testschema.test_default_tab_p ADD CONSTRAINT test_index4 UNIQUE (id) USING INDEX TABLESPACE regress_tblspace;
+\d testschema.test_index1
+Partitioned index "testschema.test_index1"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ val | bigint | yes | val
+btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d testschema.test_index2
+Partitioned index "testschema.test_index2"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ val | bigint | yes | val
+btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+Tablespace: "regress_tblspace"
+
+\d testschema.test_index3
+Partitioned index "testschema.test_index3"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+primary key, btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d testschema.test_index4
+Partitioned index "testschema.test_index4"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+unique, btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+Tablespace: "regress_tblspace"
+
+-- 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_p ALTER val TYPE bigint;
+\d testschema.test_index1
+Partitioned index "testschema.test_index1"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ val | bigint | yes | val
+btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d testschema.test_index2
+Partitioned index "testschema.test_index2"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ val | bigint | yes | val
+btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+Tablespace: "regress_tblspace"
+
+\d testschema.test_index3
+Partitioned index "testschema.test_index3"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+primary key, btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d testschema.test_index4
+Partitioned index "testschema.test_index4"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+unique, btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+Tablespace: "regress_tblspace"
+
+SELECT * FROM testschema.test_default_tab_p;
+ id | val
+----+-----
+ 1 |
+(1 row)
+
+-- tablespace should not change even if there is an index rewrite
+ALTER TABLE testschema.test_default_tab_p ALTER val TYPE int;
+\d testschema.test_index1
+Partitioned index "testschema.test_index1"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ val | integer | yes | val
+btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d testschema.test_index2
+Partitioned index "testschema.test_index2"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ val | integer | yes | val
+btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+Tablespace: "regress_tblspace"
+
+\d testschema.test_index3
+Partitioned index "testschema.test_index3"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+primary key, btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d testschema.test_index4
+Partitioned index "testschema.test_index4"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+unique, btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+Tablespace: "regress_tblspace"
+
+SELECT * FROM testschema.test_default_tab_p;
+ id | val
+----+-----
+ 1 |
+(1 row)
+
+-- 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_p ALTER val TYPE int;
+\d testschema.test_index1
+Partitioned index "testschema.test_index1"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ val | integer | yes | val
+btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d testschema.test_index2
+Partitioned index "testschema.test_index2"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ val | integer | yes | val
+btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+Tablespace: "regress_tblspace"
+
+\d testschema.test_index3
+Partitioned index "testschema.test_index3"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+primary key, btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d testschema.test_index4
+Partitioned index "testschema.test_index4"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+unique, btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+Tablespace: "regress_tblspace"
+
+-- tablespace should not change even if there is an index rewrite
+ALTER TABLE testschema.test_default_tab_p ALTER val TYPE bigint;
+\d testschema.test_index1
+Partitioned index "testschema.test_index1"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ val | bigint | yes | val
+btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d testschema.test_index2
+Partitioned index "testschema.test_index2"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ val | bigint | yes | val
+btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+Tablespace: "regress_tblspace"
+
+\d testschema.test_index3
+Partitioned index "testschema.test_index3"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+primary key, btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d testschema.test_index4
+Partitioned index "testschema.test_index4"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ id | bigint | yes | id
+unique, btree, for table "testschema.test_default_tab_p"
+Number of partitions: 1 (Use \d+ to list them.)
+Tablespace: "regress_tblspace"
+
+DROP TABLE testschema.test_default_tab_p;
+-- 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
+ Index "testschema.test_tab_unique"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ id | integer | yes | id
+unique, btree, for table "testschema.test_tab"
+Tablespace: "regress_tblspace"
+
+\d testschema.test_tab_pkey
+ Index "testschema.test_tab_pkey"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ id | integer | yes | id
+primary key, btree, for table "testschema.test_tab"
+
+SELECT * FROM testschema.test_tab;
+ id
+----
+ 1
+(1 row)
+
+DROP TABLE testschema.test_tab;
+-- check that default_tablespace is handled correctly by multi-command
+-- ALTER TABLE that includes a tablespace-preserving rewrite
+CREATE TABLE testschema.test_tab(a int, b int, c int);
+SET default_tablespace TO regress_tblspace;
+ALTER TABLE testschema.test_tab ADD CONSTRAINT test_tab_unique UNIQUE (a);
+CREATE INDEX test_tab_a_idx ON testschema.test_tab (a);
+SET default_tablespace TO '';
+CREATE INDEX test_tab_b_idx ON testschema.test_tab (b);
+\d testschema.test_tab_unique
+ Index "testschema.test_tab_unique"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ a | integer | yes | a
+unique, btree, for table "testschema.test_tab"
+Tablespace: "regress_tblspace"
+
+\d testschema.test_tab_a_idx
+ Index "testschema.test_tab_a_idx"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ a | integer | yes | a
+btree, for table "testschema.test_tab"
+Tablespace: "regress_tblspace"
+
+\d testschema.test_tab_b_idx
+ Index "testschema.test_tab_b_idx"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ b | integer | yes | b
+btree, for table "testschema.test_tab"
+
+ALTER TABLE testschema.test_tab ALTER b TYPE bigint, ADD UNIQUE (c);
+\d testschema.test_tab_unique
+ Index "testschema.test_tab_unique"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ a | integer | yes | a
+unique, btree, for table "testschema.test_tab"
+Tablespace: "regress_tblspace"
+
+\d testschema.test_tab_a_idx
+ Index "testschema.test_tab_a_idx"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ a | integer | yes | a
+btree, for table "testschema.test_tab"
+Tablespace: "regress_tblspace"
+
+\d testschema.test_tab_b_idx
+ Index "testschema.test_tab_b_idx"
+ Column | Type | Key? | Definition
+--------+--------+------+------------
+ b | bigint | yes | b
+btree, for table "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;
+ALTER INDEX testschema.part_a_idx SET TABLESPACE pg_global;
+ERROR: only shared relations can be placed in pg_global tablespace
+ALTER INDEX testschema.part_a_idx SET TABLESPACE pg_default;
+ALTER INDEX testschema.part_a_idx SET TABLESPACE regress_tblspace;
+INSERT INTO testschema.atable VALUES(3); -- ok
+INSERT INTO testschema.atable VALUES(1); -- fail (checks index)
+ERROR: duplicate key value violates unique constraint "anindex"
+DETAIL: Key (column1)=(1) already exists.
+SELECT COUNT(*) FROM testschema.atable; -- checks heap
+ count
+-------
+ 3
+(1 row)
+
+-- Will fail with bad path
+CREATE TABLESPACE regress_badspace LOCATION '/no/such/location';
+ERROR: directory "/no/such/location" does not exist
+-- No such tablespace
+CREATE TABLE bar (i int) TABLESPACE regress_nosuchspace;
+ERROR: tablespace "regress_nosuchspace" does not exist
+-- Fail, in use for some partitioned object
+DROP TABLESPACE regress_tblspace;
+ERROR: tablespace "regress_tblspace" cannot be dropped because some objects depend on it
+DETAIL: tablespace for index testschema.part_a_idx
+ALTER INDEX testschema.part_a_idx SET TABLESPACE pg_default;
+-- Fail, not empty
+DROP TABLESPACE regress_tblspace;
+ERROR: tablespace "regress_tblspace" is not empty
+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
+ERROR: permission denied for tablespace regress_tblspace
+ALTER TABLE testschema.tablespace_acl ALTER c TYPE bigint;
+REINDEX (TABLESPACE regress_tblspace) TABLE tablespace_table; -- fail
+ERROR: permission denied for tablespace regress_tblspace
+REINDEX (TABLESPACE regress_tblspace, CONCURRENTLY) TABLE tablespace_table; -- fail
+ERROR: permission denied for tablespace regress_tblspace
+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;
+NOTICE: no matching relations in tablespace "regress_tblspace_renamed" found
+-- Should succeed
+DROP TABLESPACE regress_tblspace_renamed;
+DROP SCHEMA testschema CASCADE;
+NOTICE: drop cascades to 6 other objects
+DETAIL: drop cascades to table testschema.foo
+drop cascades to table testschema.asselect
+drop cascades to table testschema.asexecute
+drop cascades to table testschema.part
+drop cascades to table testschema.atable
+drop cascades to table testschema.tablespace_acl
+DROP ROLE regress_tablespace_user1;
+DROP ROLE regress_tablespace_user2;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
new file mode 100644
index 0000000..017e962
--- /dev/null
+++ b/src/test/regress/parallel_schedule
@@ -0,0 +1,133 @@
+# ----------
+# src/test/regress/parallel_schedule
+#
+# By convention, we put no more than twenty tests in any one parallel group;
+# this limits the number of connections needed to run the tests.
+# ----------
+
+# run tablespace by itself, and first, because it forces a checkpoint;
+# we'd prefer not to have checkpoints later in the tests because that
+# interferes with crash-recovery testing.
+test: tablespace
+
+# ----------
+# The first group of parallel tests
+# ----------
+test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric txid uuid enum money rangetypes pg_lsn regproc
+
+# ----------
+# The second group of parallel tests
+# strings depends on char, varchar and text
+# numerology depends on int2, int4, int8, float4, float8
+# multirangetypes depends on rangetypes
+# multirangetypes shouldn't run concurrently with type_sanity
+# ----------
+test: strings numerology point lseg line box path polygon circle date time timetz timestamp timestamptz interval inet macaddr macaddr8 multirangetypes create_function_0
+
+# ----------
+# Another group of parallel tests
+# geometry depends on point, lseg, box, path, polygon and circle
+# horology depends on interval, timetz, timestamp, timestamptz
+# opr_sanity depends on create_function_0
+# ----------
+test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc
+
+# ----------
+# These four each depend on the previous one
+# ----------
+test: create_function_1
+test: create_type
+test: create_table
+test: create_function_2
+
+# ----------
+# Load huge amounts of data
+# We should split the data files into single files and then
+# execute two copy tests parallel, to check that copy itself
+# is concurrent safe.
+# ----------
+test: copy copyselect copydml insert insert_conflict
+
+# ----------
+# More groups of parallel tests
+# ----------
+test: create_misc create_operator create_procedure
+# These depend on create_misc and create_operator
+test: create_index create_index_spgist create_view index_including index_including_gist
+
+# ----------
+# Another group of parallel tests
+# ----------
+test: create_aggregate create_function_3 create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse
+
+# ----------
+# sanity_check does a vacuum, affecting the sort order of SELECT *
+# results. So it should not run parallel to other tests.
+# ----------
+test: sanity_check
+
+# ----------
+# Another group of parallel tests
+# Note: the ignore: line does not run random, just mark it as ignorable
+# ----------
+ignore: random
+test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update delete namespace prepared_xacts
+
+# ----------
+# Another group of parallel tests
+# ----------
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated join_hash
+
+# ----------
+# Additional BRIN tests
+# ----------
+test: brin_bloom brin_multi
+
+# ----------
+# Another group of parallel tests
+# ----------
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort
+
+# rules cannot run concurrently with any test that creates
+# a view or rule in the public schema
+# collate.*.utf8 tests cannot be run in parallel with each other
+test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
+
+# run by itself so it can run parallel workers
+test: select_parallel
+test: write_parallel
+test: vacuum_parallel
+
+# no relation related tests can be put in this group
+test: publication subscription
+
+# ----------
+# Another group of parallel tests
+# ----------
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass
+
+# ----------
+# Another group of parallel tests (JSON related)
+# ----------
+test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath
+
+# ----------
+# Another group of parallel tests
+# NB: temp.sql does a reconnect which transiently uses 2 connections,
+# so keep this parallel group to at most 19 tests
+# ----------
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+
+# ----------
+# Another group of parallel tests
+# ----------
+test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression memoize
+
+# event triggers cannot run concurrently with any test that runs DDL
+# oidjoins is read-only, though, and should run late for best coverage
+test: event_trigger oidjoins
+# this test also uses event triggers, so likewise run it by itself
+test: fast_default
+
+# run stats by itself because its delay may be insufficient under heavy load
+test: stats
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
new file mode 100644
index 0000000..27486cf
--- /dev/null
+++ b/src/test/regress/pg_regress.c
@@ -0,0 +1,2697 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_regress --- regression test driver
+ *
+ * This is a C implementation of the previous shell script for running
+ * the regression tests, and should be mostly compatible with it.
+ * Initial author of C translation: Magnus Hagander
+ *
+ * This code is released under the terms of the PostgreSQL License.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/test/regress/pg_regress.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+
+#include "common/logging.h"
+#include "common/restricted_token.h"
+#include "common/string.h"
+#include "common/username.h"
+#include "getopt_long.h"
+#include "lib/stringinfo.h"
+#include "libpq/pqcomm.h" /* needed for UNIXSOCK_PATH() */
+#include "pg_config_paths.h"
+#include "pg_regress.h"
+#include "portability/instr_time.h"
+
+/* for resultmap we need a list of pairs of strings */
+typedef struct _resultmap
+{
+ char *test;
+ char *type;
+ char *resultfile;
+ struct _resultmap *next;
+} _resultmap;
+
+/*
+ * Values obtained from Makefile.
+ */
+char *host_platform = HOST_TUPLE;
+
+#ifndef WIN32 /* not used in WIN32 case */
+static char *shellprog = SHELLPROG;
+#endif
+
+/*
+ * On Windows we use -w in diff switches to avoid problems with inconsistent
+ * newline representation. The actual result files will generally have
+ * Windows-style newlines, but the comparison files might or might not.
+ */
+#ifndef WIN32
+const char *basic_diff_opts = "";
+const char *pretty_diff_opts = "-U3";
+#else
+const char *basic_diff_opts = "-w";
+const char *pretty_diff_opts = "-w -U3";
+#endif
+
+/* options settable from command line */
+_stringlist *dblist = NULL;
+bool debug = false;
+char *inputdir = ".";
+char *outputdir = ".";
+char *bindir = PGBINDIR;
+char *launcher = NULL;
+static _stringlist *loadextension = NULL;
+static int max_connections = 0;
+static int max_concurrent_tests = 0;
+static char *encoding = NULL;
+static _stringlist *schedulelist = NULL;
+static _stringlist *extra_tests = NULL;
+static char *temp_instance = NULL;
+static _stringlist *temp_configs = NULL;
+static bool nolocale = false;
+static bool use_existing = false;
+static char *hostname = NULL;
+static int port = -1;
+static bool port_specified_by_user = false;
+static char *dlpath = PKGLIBDIR;
+static char *user = NULL;
+static _stringlist *extraroles = NULL;
+static char *config_auth_datadir = NULL;
+
+/* internal variables */
+static const char *progname;
+static char *logfilename;
+static FILE *logfile;
+static char *difffilename;
+static const char *sockdir;
+#ifdef HAVE_UNIX_SOCKETS
+static const char *temp_sockdir;
+static char sockself[MAXPGPATH];
+static char socklock[MAXPGPATH];
+#endif
+
+static _resultmap *resultmap = NULL;
+
+static PID_TYPE postmaster_pid = INVALID_PID;
+static bool postmaster_running = false;
+
+static int success_count = 0;
+static int fail_count = 0;
+static int fail_ignore_count = 0;
+
+static bool directory_exists(const char *dir);
+static void make_directory(const char *dir);
+
+static void header(const char *fmt,...) pg_attribute_printf(1, 2);
+static void status(const char *fmt,...) pg_attribute_printf(1, 2);
+static void psql_command(const char *database, const char *query,...) pg_attribute_printf(2, 3);
+
+/*
+ * allow core files if possible.
+ */
+#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE)
+static void
+unlimit_core_size(void)
+{
+ struct rlimit lim;
+
+ getrlimit(RLIMIT_CORE, &lim);
+ if (lim.rlim_max == 0)
+ {
+ fprintf(stderr,
+ _("%s: could not set core size: disallowed by hard limit\n"),
+ progname);
+ return;
+ }
+ else if (lim.rlim_max == RLIM_INFINITY || lim.rlim_cur < lim.rlim_max)
+ {
+ lim.rlim_cur = lim.rlim_max;
+ setrlimit(RLIMIT_CORE, &lim);
+ }
+}
+#endif
+
+
+/*
+ * Add an item at the end of a stringlist.
+ */
+void
+add_stringlist_item(_stringlist **listhead, const char *str)
+{
+ _stringlist *newentry = pg_malloc(sizeof(_stringlist));
+ _stringlist *oldentry;
+
+ newentry->str = pg_strdup(str);
+ newentry->next = NULL;
+ if (*listhead == NULL)
+ *listhead = newentry;
+ else
+ {
+ for (oldentry = *listhead; oldentry->next; oldentry = oldentry->next)
+ /* skip */ ;
+ oldentry->next = newentry;
+ }
+}
+
+/*
+ * Free a stringlist.
+ */
+static void
+free_stringlist(_stringlist **listhead)
+{
+ if (listhead == NULL || *listhead == NULL)
+ return;
+ if ((*listhead)->next != NULL)
+ free_stringlist(&((*listhead)->next));
+ free((*listhead)->str);
+ free(*listhead);
+ *listhead = NULL;
+}
+
+/*
+ * Split a delimited string into a stringlist
+ */
+static void
+split_to_stringlist(const char *s, const char *delim, _stringlist **listhead)
+{
+ char *sc = pg_strdup(s);
+ char *token = strtok(sc, delim);
+
+ while (token)
+ {
+ add_stringlist_item(listhead, token);
+ token = strtok(NULL, delim);
+ }
+ free(sc);
+}
+
+/*
+ * Print a progress banner on stdout.
+ */
+static void
+header(const char *fmt,...)
+{
+ char tmp[64];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(tmp, sizeof(tmp), fmt, ap);
+ va_end(ap);
+
+ fprintf(stdout, "============== %-38s ==============\n", tmp);
+ fflush(stdout);
+}
+
+/*
+ * Print "doing something ..." --- supplied text should not end with newline
+ */
+static void
+status(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stdout, fmt, ap);
+ fflush(stdout);
+ va_end(ap);
+
+ if (logfile)
+ {
+ va_start(ap, fmt);
+ vfprintf(logfile, fmt, ap);
+ va_end(ap);
+ }
+}
+
+/*
+ * Done "doing something ..."
+ */
+static void
+status_end(void)
+{
+ fprintf(stdout, "\n");
+ fflush(stdout);
+ if (logfile)
+ fprintf(logfile, "\n");
+}
+
+/*
+ * shut down temp postmaster
+ */
+static void
+stop_postmaster(void)
+{
+ if (postmaster_running)
+ {
+ /* We use pg_ctl to issue the kill and wait for stop */
+ char buf[MAXPGPATH * 2];
+ int r;
+
+ /* On Windows, system() seems not to force fflush, so... */
+ fflush(stdout);
+ fflush(stderr);
+
+ snprintf(buf, sizeof(buf),
+ "\"%s%spg_ctl\" stop -D \"%s/data\" -s",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ temp_instance);
+ r = system(buf);
+ if (r != 0)
+ {
+ fprintf(stderr, _("\n%s: could not stop postmaster: exit code was %d\n"),
+ progname, r);
+ _exit(2); /* not exit(), that could be recursive */
+ }
+
+ postmaster_running = false;
+ }
+}
+
+#ifdef HAVE_UNIX_SOCKETS
+/*
+ * Remove the socket temporary directory. pg_regress never waits for a
+ * postmaster exit, so it is indeterminate whether the postmaster has yet to
+ * unlink the socket and lock file. Unlink them here so we can proceed to
+ * remove the directory. Ignore errors; leaking a temporary directory is
+ * unimportant. This can run from a signal handler. The code is not
+ * acceptable in a Windows signal handler (see initdb.c:trapsig()), but
+ * on Windows, pg_regress does not use Unix sockets by default.
+ */
+static void
+remove_temp(void)
+{
+ Assert(temp_sockdir);
+ unlink(sockself);
+ unlink(socklock);
+ rmdir(temp_sockdir);
+}
+
+/*
+ * Signal handler that calls remove_temp() and reraises the signal.
+ */
+static void
+signal_remove_temp(int signum)
+{
+ remove_temp();
+
+ pqsignal(signum, SIG_DFL);
+ raise(signum);
+}
+
+/*
+ * Create a temporary directory suitable for the server's Unix-domain socket.
+ * The directory will have mode 0700 or stricter, so no other OS user can open
+ * our socket to exploit our use of trust authentication. Most systems
+ * constrain the length of socket paths well below _POSIX_PATH_MAX, so we
+ * place the directory under /tmp rather than relative to the possibly-deep
+ * current working directory.
+ *
+ * Compared to using the compiled-in DEFAULT_PGSOCKET_DIR, this also permits
+ * testing to work in builds that relocate it to a directory not writable to
+ * the build/test user.
+ */
+static const char *
+make_temp_sockdir(void)
+{
+ char *template = psprintf("%s/pg_regress-XXXXXX",
+ getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp");
+
+ temp_sockdir = mkdtemp(template);
+ if (temp_sockdir == NULL)
+ {
+ fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
+ progname, template, strerror(errno));
+ exit(2);
+ }
+
+ /* Stage file names for remove_temp(). Unsafe in a signal handler. */
+ UNIXSOCK_PATH(sockself, port, temp_sockdir);
+ snprintf(socklock, sizeof(socklock), "%s.lock", sockself);
+
+ /* Remove the directory during clean exit. */
+ atexit(remove_temp);
+
+ /*
+ * Remove the directory before dying to the usual signals. Omit SIGQUIT,
+ * preserving it as a quick, untidy exit.
+ */
+ pqsignal(SIGHUP, signal_remove_temp);
+ pqsignal(SIGINT, signal_remove_temp);
+ pqsignal(SIGPIPE, signal_remove_temp);
+ pqsignal(SIGTERM, signal_remove_temp);
+
+ return temp_sockdir;
+}
+#endif /* HAVE_UNIX_SOCKETS */
+
+/*
+ * Check whether string matches pattern
+ *
+ * In the original shell script, this function was implemented using expr(1),
+ * which provides basic regular expressions restricted to match starting at
+ * the string start (in conventional regex terms, there's an implicit "^"
+ * at the start of the pattern --- but no implicit "$" at the end).
+ *
+ * For now, we only support "." and ".*" as non-literal metacharacters,
+ * because that's all that anyone has found use for in resultmap. This
+ * code could be extended if more functionality is needed.
+ */
+static bool
+string_matches_pattern(const char *str, const char *pattern)
+{
+ while (*str && *pattern)
+ {
+ if (*pattern == '.' && pattern[1] == '*')
+ {
+ pattern += 2;
+ /* Trailing .* matches everything. */
+ if (*pattern == '\0')
+ return true;
+
+ /*
+ * Otherwise, scan for a text position at which we can match the
+ * rest of the pattern.
+ */
+ while (*str)
+ {
+ /*
+ * Optimization to prevent most recursion: don't recurse
+ * unless first pattern char might match this text char.
+ */
+ if (*str == *pattern || *pattern == '.')
+ {
+ if (string_matches_pattern(str, pattern))
+ return true;
+ }
+
+ str++;
+ }
+
+ /*
+ * End of text with no match.
+ */
+ return false;
+ }
+ else if (*pattern != '.' && *str != *pattern)
+ {
+ /*
+ * Not the single-character wildcard and no explicit match? Then
+ * time to quit...
+ */
+ return false;
+ }
+
+ str++;
+ pattern++;
+ }
+
+ if (*pattern == '\0')
+ return true; /* end of pattern, so declare match */
+
+ /* End of input string. Do we have matching pattern remaining? */
+ while (*pattern == '.' && pattern[1] == '*')
+ pattern += 2;
+ if (*pattern == '\0')
+ return true; /* end of pattern, so declare match */
+
+ return false;
+}
+
+/*
+ * Replace all occurrences of "replace" in "string" with "replacement".
+ * The StringInfo will be suitably enlarged if necessary.
+ *
+ * Note: this is optimized on the assumption that most calls will find
+ * no more than one occurrence of "replace", and quite likely none.
+ */
+void
+replace_string(StringInfo string, const char *replace, const char *replacement)
+{
+ int pos = 0;
+ char *ptr;
+
+ while ((ptr = strstr(string->data + pos, replace)) != NULL)
+ {
+ /* Must copy the remainder of the string out of the StringInfo */
+ char *suffix = pg_strdup(ptr + strlen(replace));
+
+ /* Truncate StringInfo at start of found string ... */
+ string->len = ptr - string->data;
+ /* ... and append the replacement (this restores the trailing '\0') */
+ appendStringInfoString(string, replacement);
+ /* Next search should start after the replacement */
+ pos = string->len;
+ /* Put back the remainder of the string */
+ appendStringInfoString(string, suffix);
+ free(suffix);
+ }
+}
+
+/*
+ * Convert *.source found in the "source" directory, replacing certain tokens
+ * in the file contents with their intended values, and put the resulting files
+ * in the "dest" directory, replacing the ".source" prefix in their names with
+ * the given suffix.
+ */
+static void
+convert_sourcefiles_in(const char *source_subdir, const char *dest_dir, const char *dest_subdir, const char *suffix)
+{
+ char testtablespace[MAXPGPATH];
+ char indir[MAXPGPATH];
+ char outdir_sub[MAXPGPATH];
+ char **name;
+ char **names;
+ int count = 0;
+
+ snprintf(indir, MAXPGPATH, "%s/%s", inputdir, source_subdir);
+
+ /* Check that indir actually exists and is a directory */
+ if (!directory_exists(indir))
+ {
+ /*
+ * No warning, to avoid noise in tests that do not have these
+ * directories; for example, ecpg, contrib and src/pl.
+ */
+ return;
+ }
+
+ names = pgfnames(indir);
+ if (!names)
+ /* Error logged in pgfnames */
+ exit(2);
+
+ /* Create the "dest" subdirectory if not present */
+ snprintf(outdir_sub, MAXPGPATH, "%s/%s", dest_dir, dest_subdir);
+ if (!directory_exists(outdir_sub))
+ make_directory(outdir_sub);
+
+ /* We might need to replace @testtablespace@ */
+ snprintf(testtablespace, MAXPGPATH, "%s/testtablespace", outputdir);
+
+ /* finally loop on each file and do the replacement */
+ for (name = names; *name; name++)
+ {
+ char srcfile[MAXPGPATH];
+ char destfile[MAXPGPATH];
+ char prefix[MAXPGPATH];
+ FILE *infile,
+ *outfile;
+ StringInfoData line;
+
+ /* reject filenames not finishing in ".source" */
+ if (strlen(*name) < 8)
+ continue;
+ if (strcmp(*name + strlen(*name) - 7, ".source") != 0)
+ continue;
+
+ count++;
+
+ /* build the full actual paths to open */
+ snprintf(prefix, strlen(*name) - 6, "%s", *name);
+ snprintf(srcfile, MAXPGPATH, "%s/%s", indir, *name);
+ snprintf(destfile, MAXPGPATH, "%s/%s/%s.%s", dest_dir, dest_subdir,
+ prefix, suffix);
+
+ infile = fopen(srcfile, "r");
+ if (!infile)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
+ progname, srcfile, strerror(errno));
+ exit(2);
+ }
+ outfile = fopen(destfile, "w");
+ if (!outfile)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+ progname, destfile, strerror(errno));
+ exit(2);
+ }
+
+ initStringInfo(&line);
+
+ while (pg_get_line_buf(infile, &line))
+ {
+ replace_string(&line, "@abs_srcdir@", inputdir);
+ replace_string(&line, "@abs_builddir@", outputdir);
+ replace_string(&line, "@testtablespace@", testtablespace);
+ replace_string(&line, "@libdir@", dlpath);
+ replace_string(&line, "@DLSUFFIX@", DLSUFFIX);
+ fputs(line.data, outfile);
+ }
+
+ pfree(line.data);
+ fclose(infile);
+ fclose(outfile);
+ }
+
+ /*
+ * If we didn't process any files, complain because it probably means
+ * somebody neglected to pass the needed --inputdir argument.
+ */
+ if (count <= 0)
+ {
+ fprintf(stderr, _("%s: no *.source files found in \"%s\"\n"),
+ progname, indir);
+ exit(2);
+ }
+
+ pgfnames_cleanup(names);
+}
+
+/* Create the .sql and .out files from the .source files, if any */
+static void
+convert_sourcefiles(void)
+{
+ convert_sourcefiles_in("input", outputdir, "sql", "sql");
+ convert_sourcefiles_in("output", outputdir, "expected", "out");
+}
+
+/*
+ * Clean out the test tablespace dir, or create it if it doesn't exist.
+ *
+ * On Windows, doing this cleanup here makes it possible to run the
+ * regression tests under a Windows administrative user account with the
+ * restricted token obtained when starting pg_regress.
+ */
+static void
+prepare_testtablespace_dir(void)
+{
+ char testtablespace[MAXPGPATH];
+
+ snprintf(testtablespace, MAXPGPATH, "%s/testtablespace", outputdir);
+
+ if (directory_exists(testtablespace))
+ {
+ if (!rmtree(testtablespace, true))
+ {
+ fprintf(stderr, _("\n%s: could not remove test tablespace \"%s\"\n"),
+ progname, testtablespace);
+ exit(2);
+ }
+ }
+ make_directory(testtablespace);
+}
+
+/*
+ * Scan resultmap file to find which platform-specific expected files to use.
+ *
+ * The format of each line of the file is
+ * testname/hostplatformpattern=substitutefile
+ * where the hostplatformpattern is evaluated per the rules of expr(1),
+ * namely, it is a standard regular expression with an implicit ^ at the start.
+ * (We currently support only a very limited subset of regular expressions,
+ * see string_matches_pattern() above.) What hostplatformpattern will be
+ * matched against is the config.guess output. (In the shell-script version,
+ * we also provided an indication of whether gcc or another compiler was in
+ * use, but that facility isn't used anymore.)
+ */
+static void
+load_resultmap(void)
+{
+ char buf[MAXPGPATH];
+ FILE *f;
+
+ /* scan the file ... */
+ snprintf(buf, sizeof(buf), "%s/resultmap", inputdir);
+ f = fopen(buf, "r");
+ if (!f)
+ {
+ /* OK if it doesn't exist, else complain */
+ if (errno == ENOENT)
+ return;
+ fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
+ progname, buf, strerror(errno));
+ exit(2);
+ }
+
+ while (fgets(buf, sizeof(buf), f))
+ {
+ char *platform;
+ char *file_type;
+ char *expected;
+ int i;
+
+ /* strip trailing whitespace, especially the newline */
+ i = strlen(buf);
+ while (i > 0 && isspace((unsigned char) buf[i - 1]))
+ buf[--i] = '\0';
+
+ /* parse out the line fields */
+ file_type = strchr(buf, ':');
+ if (!file_type)
+ {
+ fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
+ buf);
+ exit(2);
+ }
+ *file_type++ = '\0';
+
+ platform = strchr(file_type, ':');
+ if (!platform)
+ {
+ fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
+ buf);
+ exit(2);
+ }
+ *platform++ = '\0';
+ expected = strchr(platform, '=');
+ if (!expected)
+ {
+ fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
+ buf);
+ exit(2);
+ }
+ *expected++ = '\0';
+
+ /*
+ * if it's for current platform, save it in resultmap list. Note: by
+ * adding at the front of the list, we ensure that in ambiguous cases,
+ * the last match in the resultmap file is used. This mimics the
+ * behavior of the old shell script.
+ */
+ if (string_matches_pattern(host_platform, platform))
+ {
+ _resultmap *entry = pg_malloc(sizeof(_resultmap));
+
+ entry->test = pg_strdup(buf);
+ entry->type = pg_strdup(file_type);
+ entry->resultfile = pg_strdup(expected);
+ entry->next = resultmap;
+ resultmap = entry;
+ }
+ }
+ fclose(f);
+}
+
+/*
+ * Check in resultmap if we should be looking at a different file
+ */
+static
+const char *
+get_expectfile(const char *testname, const char *file)
+{
+ char *file_type;
+ _resultmap *rm;
+
+ /*
+ * Determine the file type from the file name. This is just what is
+ * following the last dot in the file name.
+ */
+ if (!file || !(file_type = strrchr(file, '.')))
+ return NULL;
+
+ file_type++;
+
+ for (rm = resultmap; rm != NULL; rm = rm->next)
+ {
+ if (strcmp(testname, rm->test) == 0 && strcmp(file_type, rm->type) == 0)
+ {
+ return rm->resultfile;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Prepare environment variables for running regression tests
+ */
+static void
+initialize_environment(void)
+{
+ /*
+ * Set default application_name. (The test_start_function may choose to
+ * override this, but if it doesn't, we have something useful in place.)
+ */
+ setenv("PGAPPNAME", "pg_regress", 1);
+
+ if (nolocale)
+ {
+ /*
+ * Clear out any non-C locale settings
+ */
+ unsetenv("LC_COLLATE");
+ unsetenv("LC_CTYPE");
+ unsetenv("LC_MONETARY");
+ unsetenv("LC_NUMERIC");
+ unsetenv("LC_TIME");
+ unsetenv("LANG");
+
+ /*
+ * Most platforms have adopted the POSIX locale as their
+ * implementation-defined default locale. Exceptions include native
+ * Windows, macOS with --enable-nls, and Cygwin with --enable-nls.
+ * (Use of --enable-nls matters because libintl replaces setlocale().)
+ * Also, PostgreSQL does not support macOS with locale environment
+ * variables unset; see PostmasterMain().
+ */
+#if defined(WIN32) || defined(__CYGWIN__) || defined(__darwin__)
+ setenv("LANG", "C", 1);
+#endif
+ }
+
+ /*
+ * Set translation-related settings to English; otherwise psql will
+ * produce translated messages and produce diffs. (XXX If we ever support
+ * translation of pg_regress, this needs to be moved elsewhere, where psql
+ * is actually called.)
+ */
+ unsetenv("LANGUAGE");
+ unsetenv("LC_ALL");
+ setenv("LC_MESSAGES", "C", 1);
+
+ /*
+ * Set encoding as requested
+ */
+ if (encoding)
+ setenv("PGCLIENTENCODING", encoding, 1);
+ else
+ unsetenv("PGCLIENTENCODING");
+
+ /*
+ * Set timezone and datestyle for datetime-related tests
+ */
+ setenv("PGTZ", "PST8PDT", 1);
+ setenv("PGDATESTYLE", "Postgres, MDY", 1);
+
+ /*
+ * Likewise set intervalstyle to ensure consistent results. This is a bit
+ * more painful because we must use PGOPTIONS, and we want to preserve the
+ * user's ability to set other variables through that.
+ */
+ {
+ const char *my_pgoptions = "-c intervalstyle=postgres_verbose";
+ const char *old_pgoptions = getenv("PGOPTIONS");
+ char *new_pgoptions;
+
+ if (!old_pgoptions)
+ old_pgoptions = "";
+ new_pgoptions = psprintf("%s %s",
+ old_pgoptions, my_pgoptions);
+ setenv("PGOPTIONS", new_pgoptions, 1);
+ free(new_pgoptions);
+ }
+
+ if (temp_instance)
+ {
+ /*
+ * Clear out any environment vars that might cause psql to connect to
+ * the wrong postmaster, or otherwise behave in nondefault ways. (Note
+ * we also use psql's -X switch consistently, so that ~/.psqlrc files
+ * won't mess things up.) Also, set PGPORT to the temp port, and set
+ * PGHOST depending on whether we are using TCP or Unix sockets.
+ *
+ * This list should be kept in sync with TestLib.pm.
+ */
+ unsetenv("PGCHANNELBINDING");
+ /* PGCLIENTENCODING, see above */
+ unsetenv("PGCONNECT_TIMEOUT");
+ unsetenv("PGDATA");
+ unsetenv("PGDATABASE");
+ unsetenv("PGGSSENCMODE");
+ unsetenv("PGGSSLIB");
+ /* PGHOSTADDR, see below */
+ unsetenv("PGKRBSRVNAME");
+ unsetenv("PGPASSFILE");
+ unsetenv("PGPASSWORD");
+ unsetenv("PGREQUIREPEER");
+ unsetenv("PGREQUIRESSL");
+ unsetenv("PGSERVICE");
+ unsetenv("PGSERVICEFILE");
+ unsetenv("PGSSLCERT");
+ unsetenv("PGSSLCRL");
+ unsetenv("PGSSLCRLDIR");
+ unsetenv("PGSSLKEY");
+ unsetenv("PGSSLMAXPROTOCOLVERSION");
+ unsetenv("PGSSLMINPROTOCOLVERSION");
+ unsetenv("PGSSLMODE");
+ unsetenv("PGSSLROOTCERT");
+ unsetenv("PGSSLSNI");
+ unsetenv("PGTARGETSESSIONATTRS");
+ unsetenv("PGUSER");
+ /* PGPORT, see below */
+ /* PGHOST, see below */
+
+#ifdef HAVE_UNIX_SOCKETS
+ if (hostname != NULL)
+ setenv("PGHOST", hostname, 1);
+ else
+ {
+ sockdir = getenv("PG_REGRESS_SOCK_DIR");
+ if (!sockdir)
+ sockdir = make_temp_sockdir();
+ setenv("PGHOST", sockdir, 1);
+ }
+#else
+ Assert(hostname != NULL);
+ setenv("PGHOST", hostname, 1);
+#endif
+ unsetenv("PGHOSTADDR");
+ if (port != -1)
+ {
+ char s[16];
+
+ sprintf(s, "%d", port);
+ setenv("PGPORT", s, 1);
+ }
+ }
+ else
+ {
+ const char *pghost;
+ const char *pgport;
+
+ /*
+ * When testing an existing install, we honor existing environment
+ * variables, except if they're overridden by command line options.
+ */
+ if (hostname != NULL)
+ {
+ setenv("PGHOST", hostname, 1);
+ unsetenv("PGHOSTADDR");
+ }
+ if (port != -1)
+ {
+ char s[16];
+
+ sprintf(s, "%d", port);
+ setenv("PGPORT", s, 1);
+ }
+ if (user != NULL)
+ setenv("PGUSER", user, 1);
+
+ /*
+ * However, we *don't* honor PGDATABASE, since we certainly don't wish
+ * to connect to whatever database the user might like as default.
+ * (Most tests override PGDATABASE anyway, but there are some ECPG
+ * test cases that don't.)
+ */
+ unsetenv("PGDATABASE");
+
+ /*
+ * Report what we're connecting to
+ */
+ pghost = getenv("PGHOST");
+ pgport = getenv("PGPORT");
+ if (!pghost)
+ {
+ /* Keep this bit in sync with libpq's default host location: */
+#ifdef HAVE_UNIX_SOCKETS
+ if (DEFAULT_PGSOCKET_DIR[0])
+ /* do nothing, we'll print "Unix socket" below */ ;
+ else
+#endif
+ pghost = "localhost"; /* DefaultHost in fe-connect.c */
+ }
+
+ if (pghost && pgport)
+ printf(_("(using postmaster on %s, port %s)\n"), pghost, pgport);
+ if (pghost && !pgport)
+ printf(_("(using postmaster on %s, default port)\n"), pghost);
+ if (!pghost && pgport)
+ printf(_("(using postmaster on Unix socket, port %s)\n"), pgport);
+ if (!pghost && !pgport)
+ printf(_("(using postmaster on Unix socket, default port)\n"));
+ }
+
+ convert_sourcefiles();
+ load_resultmap();
+}
+
+#ifdef ENABLE_SSPI
+
+/* support for config_sspi_auth() */
+static const char *
+fmtHba(const char *raw)
+{
+ static char *ret;
+ const char *rp;
+ char *wp;
+
+ wp = ret = realloc(ret, 3 + strlen(raw) * 2);
+
+ *wp++ = '"';
+ for (rp = raw; *rp; rp++)
+ {
+ if (*rp == '"')
+ *wp++ = '"';
+ *wp++ = *rp;
+ }
+ *wp++ = '"';
+ *wp++ = '\0';
+
+ return ret;
+}
+
+/*
+ * Get account and domain/realm names for the current user. This is based on
+ * pg_SSPI_recvauth(). The returned strings use static storage.
+ */
+static void
+current_windows_user(const char **acct, const char **dom)
+{
+ static char accountname[MAXPGPATH];
+ static char domainname[MAXPGPATH];
+ HANDLE token;
+ TOKEN_USER *tokenuser;
+ DWORD retlen;
+ DWORD accountnamesize = sizeof(accountname);
+ DWORD domainnamesize = sizeof(domainname);
+ SID_NAME_USE accountnameuse;
+
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &token))
+ {
+ fprintf(stderr,
+ _("%s: could not open process token: error code %lu\n"),
+ progname, GetLastError());
+ exit(2);
+ }
+
+ if (!GetTokenInformation(token, TokenUser, NULL, 0, &retlen) && GetLastError() != 122)
+ {
+ fprintf(stderr,
+ _("%s: could not get token information buffer size: error code %lu\n"),
+ progname, GetLastError());
+ exit(2);
+ }
+ tokenuser = pg_malloc(retlen);
+ if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen))
+ {
+ fprintf(stderr,
+ _("%s: could not get token information: error code %lu\n"),
+ progname, GetLastError());
+ exit(2);
+ }
+
+ if (!LookupAccountSid(NULL, tokenuser->User.Sid, accountname, &accountnamesize,
+ domainname, &domainnamesize, &accountnameuse))
+ {
+ fprintf(stderr,
+ _("%s: could not look up account SID: error code %lu\n"),
+ progname, GetLastError());
+ exit(2);
+ }
+
+ free(tokenuser);
+
+ *acct = accountname;
+ *dom = domainname;
+}
+
+/*
+ * Rewrite pg_hba.conf and pg_ident.conf to use SSPI authentication. Permit
+ * the current OS user to authenticate as the bootstrap superuser and as any
+ * user named in a --create-role option.
+ *
+ * In --config-auth mode, the --user switch can be used to specify the
+ * bootstrap superuser's name, otherwise we assume it is the default.
+ */
+static void
+config_sspi_auth(const char *pgdata, const char *superuser_name)
+{
+ const char *accountname,
+ *domainname;
+ char *errstr;
+ bool have_ipv6;
+ char fname[MAXPGPATH];
+ int res;
+ FILE *hba,
+ *ident;
+ _stringlist *sl;
+
+ /* Find out the name of the current OS user */
+ current_windows_user(&accountname, &domainname);
+
+ /* Determine the bootstrap superuser's name */
+ if (superuser_name == NULL)
+ {
+ /*
+ * Compute the default superuser name the same way initdb does.
+ *
+ * It's possible that this result always matches "accountname", the
+ * value SSPI authentication discovers. But the underlying system
+ * functions do not clearly guarantee that.
+ */
+ superuser_name = get_user_name(&errstr);
+ if (superuser_name == NULL)
+ {
+ fprintf(stderr, "%s: %s\n", progname, errstr);
+ exit(2);
+ }
+ }
+
+ /*
+ * Like initdb.c:setup_config(), determine whether the platform recognizes
+ * ::1 (IPv6 loopback) as a numeric host address string.
+ */
+ {
+ struct addrinfo *gai_result;
+ struct addrinfo hints;
+ WSADATA wsaData;
+
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = 0;
+ hints.ai_protocol = 0;
+ hints.ai_addrlen = 0;
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+
+ have_ipv6 = (WSAStartup(MAKEWORD(2, 2), &wsaData) == 0 &&
+ getaddrinfo("::1", NULL, &hints, &gai_result) == 0);
+ }
+
+ /* Check a Write outcome and report any error. */
+#define CW(cond) \
+ do { \
+ if (!(cond)) \
+ { \
+ fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"), \
+ progname, fname, strerror(errno)); \
+ exit(2); \
+ } \
+ } while (0)
+
+ res = snprintf(fname, sizeof(fname), "%s/pg_hba.conf", pgdata);
+ if (res < 0 || res >= sizeof(fname))
+ {
+ /*
+ * Truncating this name is a fatal error, because we must not fail to
+ * overwrite an original trust-authentication pg_hba.conf.
+ */
+ fprintf(stderr, _("%s: directory name too long\n"), progname);
+ exit(2);
+ }
+ hba = fopen(fname, "w");
+ if (hba == NULL)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+ progname, fname, strerror(errno));
+ exit(2);
+ }
+ CW(fputs("# Configuration written by config_sspi_auth()\n", hba) >= 0);
+ CW(fputs("host all all 127.0.0.1/32 sspi include_realm=1 map=regress\n",
+ hba) >= 0);
+ if (have_ipv6)
+ CW(fputs("host all all ::1/128 sspi include_realm=1 map=regress\n",
+ hba) >= 0);
+ CW(fclose(hba) == 0);
+
+ snprintf(fname, sizeof(fname), "%s/pg_ident.conf", pgdata);
+ ident = fopen(fname, "w");
+ if (ident == NULL)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+ progname, fname, strerror(errno));
+ exit(2);
+ }
+ CW(fputs("# Configuration written by config_sspi_auth()\n", ident) >= 0);
+
+ /*
+ * Double-quote for the benefit of account names containing whitespace or
+ * '#'. Windows forbids the double-quote character itself, so don't
+ * bother escaping embedded double-quote characters.
+ */
+ CW(fprintf(ident, "regress \"%s@%s\" %s\n",
+ accountname, domainname, fmtHba(superuser_name)) >= 0);
+ for (sl = extraroles; sl; sl = sl->next)
+ CW(fprintf(ident, "regress \"%s@%s\" %s\n",
+ accountname, domainname, fmtHba(sl->str)) >= 0);
+ CW(fclose(ident) == 0);
+}
+
+#endif /* ENABLE_SSPI */
+
+/*
+ * Issue a command via psql, connecting to the specified database
+ *
+ * Since we use system(), this doesn't return until the operation finishes
+ */
+static void
+psql_command(const char *database, const char *query,...)
+{
+ char query_formatted[1024];
+ char query_escaped[2048];
+ char psql_cmd[MAXPGPATH + 2048];
+ va_list args;
+ char *s;
+ char *d;
+
+ /* Generate the query with insertion of sprintf arguments */
+ va_start(args, query);
+ vsnprintf(query_formatted, sizeof(query_formatted), query, args);
+ va_end(args);
+
+ /* Now escape any shell double-quote metacharacters */
+ d = query_escaped;
+ for (s = query_formatted; *s; s++)
+ {
+ if (strchr("\\\"$`", *s))
+ *d++ = '\\';
+ *d++ = *s;
+ }
+ *d = '\0';
+
+ /* And now we can build and execute the shell command */
+ snprintf(psql_cmd, sizeof(psql_cmd),
+ "\"%s%spsql\" -X -c \"%s\" \"%s\"",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ query_escaped,
+ database);
+
+ if (system(psql_cmd) != 0)
+ {
+ /* psql probably already reported the error */
+ fprintf(stderr, _("command failed: %s\n"), psql_cmd);
+ exit(2);
+ }
+}
+
+/*
+ * Spawn a process to execute the given shell command; don't wait for it
+ *
+ * Returns the process ID (or HANDLE) so we can wait for it later
+ */
+PID_TYPE
+spawn_process(const char *cmdline)
+{
+#ifndef WIN32
+ pid_t pid;
+
+ /*
+ * Must flush I/O buffers before fork. Ideally we'd use fflush(NULL) here
+ * ... does anyone still care about systems where that doesn't work?
+ */
+ fflush(stdout);
+ fflush(stderr);
+ if (logfile)
+ fflush(logfile);
+
+ pid = fork();
+ if (pid == -1)
+ {
+ fprintf(stderr, _("%s: could not fork: %s\n"),
+ progname, strerror(errno));
+ exit(2);
+ }
+ if (pid == 0)
+ {
+ /*
+ * In child
+ *
+ * Instead of using system(), exec the shell directly, and tell it to
+ * "exec" the command too. This saves two useless processes per
+ * parallel test case.
+ */
+ char *cmdline2;
+
+ cmdline2 = psprintf("exec %s", cmdline);
+ execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
+ fprintf(stderr, _("%s: could not exec \"%s\": %s\n"),
+ progname, shellprog, strerror(errno));
+ _exit(1); /* not exit() here... */
+ }
+ /* in parent */
+ return pid;
+#else
+ PROCESS_INFORMATION pi;
+ char *cmdline2;
+ HANDLE restrictedToken;
+ const char *comspec;
+
+ /* Find CMD.EXE location using COMSPEC, if it's set */
+ comspec = getenv("COMSPEC");
+ if (comspec == NULL)
+ comspec = "CMD";
+
+ memset(&pi, 0, sizeof(pi));
+ cmdline2 = psprintf("\"%s\" /c \"%s\"", comspec, cmdline);
+
+ if ((restrictedToken =
+ CreateRestrictedProcess(cmdline2, &pi)) == 0)
+ exit(2);
+
+ CloseHandle(pi.hThread);
+ return pi.hProcess;
+#endif
+}
+
+/*
+ * Count bytes in file
+ */
+static long
+file_size(const char *file)
+{
+ long r;
+ FILE *f = fopen(file, "r");
+
+ if (!f)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
+ progname, file, strerror(errno));
+ return -1;
+ }
+ fseek(f, 0, SEEK_END);
+ r = ftell(f);
+ fclose(f);
+ return r;
+}
+
+/*
+ * Count lines in file
+ */
+static int
+file_line_count(const char *file)
+{
+ int c;
+ int l = 0;
+ FILE *f = fopen(file, "r");
+
+ if (!f)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
+ progname, file, strerror(errno));
+ return -1;
+ }
+ while ((c = fgetc(f)) != EOF)
+ {
+ if (c == '\n')
+ l++;
+ }
+ fclose(f);
+ return l;
+}
+
+bool
+file_exists(const char *file)
+{
+ FILE *f = fopen(file, "r");
+
+ if (!f)
+ return false;
+ fclose(f);
+ return true;
+}
+
+static bool
+directory_exists(const char *dir)
+{
+ struct stat st;
+
+ if (stat(dir, &st) != 0)
+ return false;
+ if (S_ISDIR(st.st_mode))
+ return true;
+ return false;
+}
+
+/* Create a directory */
+static void
+make_directory(const char *dir)
+{
+ if (mkdir(dir, S_IRWXU | S_IRWXG | S_IRWXO) < 0)
+ {
+ fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
+ progname, dir, strerror(errno));
+ exit(2);
+ }
+}
+
+/*
+ * In: filename.ext, Return: filename_i.ext, where 0 < i <= 9
+ */
+static char *
+get_alternative_expectfile(const char *expectfile, int i)
+{
+ char *last_dot;
+ int ssize = strlen(expectfile) + 2 + 1;
+ char *tmp;
+ char *s;
+
+ if (!(tmp = (char *) malloc(ssize)))
+ return NULL;
+
+ if (!(s = (char *) malloc(ssize)))
+ {
+ free(tmp);
+ return NULL;
+ }
+
+ strcpy(tmp, expectfile);
+ last_dot = strrchr(tmp, '.');
+ if (!last_dot)
+ {
+ free(tmp);
+ free(s);
+ return NULL;
+ }
+ *last_dot = '\0';
+ snprintf(s, ssize, "%s_%d.%s", tmp, i, last_dot + 1);
+ free(tmp);
+ return s;
+}
+
+/*
+ * Run a "diff" command and also check that it didn't crash
+ */
+static int
+run_diff(const char *cmd, const char *filename)
+{
+ int r;
+
+ r = system(cmd);
+ if (!WIFEXITED(r) || WEXITSTATUS(r) > 1)
+ {
+ fprintf(stderr, _("diff command failed with status %d: %s\n"), r, cmd);
+ exit(2);
+ }
+#ifdef WIN32
+
+ /*
+ * On WIN32, if the 'diff' command cannot be found, system() returns 1,
+ * but produces nothing to stdout, so we check for that here.
+ */
+ if (WEXITSTATUS(r) == 1 && file_size(filename) <= 0)
+ {
+ fprintf(stderr, _("diff command not found: %s\n"), cmd);
+ exit(2);
+ }
+#endif
+
+ return WEXITSTATUS(r);
+}
+
+/*
+ * Check the actual result file for the given test against expected results
+ *
+ * Returns true if different (failure), false if correct match found.
+ * In the true case, the diff is appended to the diffs file.
+ */
+static bool
+results_differ(const char *testname, const char *resultsfile, const char *default_expectfile)
+{
+ char expectfile[MAXPGPATH];
+ char diff[MAXPGPATH];
+ char cmd[MAXPGPATH * 3];
+ char best_expect_file[MAXPGPATH];
+ FILE *difffile;
+ int best_line_count;
+ int i;
+ int l;
+ const char *platform_expectfile;
+
+ /*
+ * We can pass either the resultsfile or the expectfile, they should have
+ * the same type (filename.type) anyway.
+ */
+ platform_expectfile = get_expectfile(testname, resultsfile);
+
+ strlcpy(expectfile, default_expectfile, sizeof(expectfile));
+ if (platform_expectfile)
+ {
+ /*
+ * Replace everything after the last slash in expectfile with what the
+ * platform_expectfile contains.
+ */
+ char *p = strrchr(expectfile, '/');
+
+ if (p)
+ strcpy(++p, platform_expectfile);
+ }
+
+ /* Name to use for temporary diff file */
+ snprintf(diff, sizeof(diff), "%s.diff", resultsfile);
+
+ /* OK, run the diff */
+ snprintf(cmd, sizeof(cmd),
+ "diff %s \"%s\" \"%s\" > \"%s\"",
+ basic_diff_opts, expectfile, resultsfile, diff);
+
+ /* Is the diff file empty? */
+ if (run_diff(cmd, diff) == 0)
+ {
+ unlink(diff);
+ return false;
+ }
+
+ /* There may be secondary comparison files that match better */
+ best_line_count = file_line_count(diff);
+ strcpy(best_expect_file, expectfile);
+
+ for (i = 0; i <= 9; i++)
+ {
+ char *alt_expectfile;
+
+ alt_expectfile = get_alternative_expectfile(expectfile, i);
+ if (!alt_expectfile)
+ {
+ fprintf(stderr, _("Unable to check secondary comparison files: %s\n"),
+ strerror(errno));
+ exit(2);
+ }
+
+ if (!file_exists(alt_expectfile))
+ {
+ free(alt_expectfile);
+ continue;
+ }
+
+ snprintf(cmd, sizeof(cmd),
+ "diff %s \"%s\" \"%s\" > \"%s\"",
+ basic_diff_opts, alt_expectfile, resultsfile, diff);
+
+ if (run_diff(cmd, diff) == 0)
+ {
+ unlink(diff);
+ free(alt_expectfile);
+ return false;
+ }
+
+ l = file_line_count(diff);
+ if (l < best_line_count)
+ {
+ /* This diff was a better match than the last one */
+ best_line_count = l;
+ strlcpy(best_expect_file, alt_expectfile, sizeof(best_expect_file));
+ }
+ free(alt_expectfile);
+ }
+
+ /*
+ * fall back on the canonical results file if we haven't tried it yet and
+ * haven't found a complete match yet.
+ */
+
+ if (platform_expectfile)
+ {
+ snprintf(cmd, sizeof(cmd),
+ "diff %s \"%s\" \"%s\" > \"%s\"",
+ basic_diff_opts, default_expectfile, resultsfile, diff);
+
+ if (run_diff(cmd, diff) == 0)
+ {
+ /* No diff = no changes = good */
+ unlink(diff);
+ return false;
+ }
+
+ l = file_line_count(diff);
+ if (l < best_line_count)
+ {
+ /* This diff was a better match than the last one */
+ best_line_count = l;
+ strlcpy(best_expect_file, default_expectfile, sizeof(best_expect_file));
+ }
+ }
+
+ /*
+ * Use the best comparison file to generate the "pretty" diff, which we
+ * append to the diffs summary file.
+ */
+
+ /* Write diff header */
+ difffile = fopen(difffilename, "a");
+ if (difffile)
+ {
+ fprintf(difffile,
+ "diff %s %s %s\n",
+ pretty_diff_opts, best_expect_file, resultsfile);
+ fclose(difffile);
+ }
+
+ /* Run diff */
+ snprintf(cmd, sizeof(cmd),
+ "diff %s \"%s\" \"%s\" >> \"%s\"",
+ pretty_diff_opts, best_expect_file, resultsfile, difffilename);
+ run_diff(cmd, difffilename);
+
+ unlink(diff);
+ return true;
+}
+
+/*
+ * Wait for specified subprocesses to finish, and return their exit
+ * statuses into statuses[] and stop times into stoptimes[]
+ *
+ * If names isn't NULL, print each subprocess's name as it finishes
+ *
+ * Note: it's OK to scribble on the pids array, but not on the names array
+ */
+static void
+wait_for_tests(PID_TYPE * pids, int *statuses, instr_time *stoptimes,
+ char **names, int num_tests)
+{
+ int tests_left;
+ int i;
+
+#ifdef WIN32
+ PID_TYPE *active_pids = pg_malloc(num_tests * sizeof(PID_TYPE));
+
+ memcpy(active_pids, pids, num_tests * sizeof(PID_TYPE));
+#endif
+
+ tests_left = num_tests;
+ while (tests_left > 0)
+ {
+ PID_TYPE p;
+
+#ifndef WIN32
+ int exit_status;
+
+ p = wait(&exit_status);
+
+ if (p == INVALID_PID)
+ {
+ fprintf(stderr, _("failed to wait for subprocesses: %s\n"),
+ strerror(errno));
+ exit(2);
+ }
+#else
+ DWORD exit_status;
+ int r;
+
+ r = WaitForMultipleObjects(tests_left, active_pids, FALSE, INFINITE);
+ if (r < WAIT_OBJECT_0 || r >= WAIT_OBJECT_0 + tests_left)
+ {
+ fprintf(stderr, _("failed to wait for subprocesses: error code %lu\n"),
+ GetLastError());
+ exit(2);
+ }
+ p = active_pids[r - WAIT_OBJECT_0];
+ /* compact the active_pids array */
+ active_pids[r - WAIT_OBJECT_0] = active_pids[tests_left - 1];
+#endif /* WIN32 */
+
+ for (i = 0; i < num_tests; i++)
+ {
+ if (p == pids[i])
+ {
+#ifdef WIN32
+ GetExitCodeProcess(pids[i], &exit_status);
+ CloseHandle(pids[i]);
+#endif
+ pids[i] = INVALID_PID;
+ statuses[i] = (int) exit_status;
+ INSTR_TIME_SET_CURRENT(stoptimes[i]);
+ if (names)
+ status(" %s", names[i]);
+ tests_left--;
+ break;
+ }
+ }
+ }
+
+#ifdef WIN32
+ free(active_pids);
+#endif
+}
+
+/*
+ * report nonzero exit code from a test process
+ */
+static void
+log_child_failure(int exitstatus)
+{
+ if (WIFEXITED(exitstatus))
+ status(_(" (test process exited with exit code %d)"),
+ WEXITSTATUS(exitstatus));
+ else if (WIFSIGNALED(exitstatus))
+ {
+#if defined(WIN32)
+ status(_(" (test process was terminated by exception 0x%X)"),
+ WTERMSIG(exitstatus));
+#else
+ status(_(" (test process was terminated by signal %d: %s)"),
+ WTERMSIG(exitstatus), pg_strsignal(WTERMSIG(exitstatus)));
+#endif
+ }
+ else
+ status(_(" (test process exited with unrecognized status %d)"),
+ exitstatus);
+}
+
+/*
+ * Run all the tests specified in one schedule file
+ */
+static void
+run_schedule(const char *schedule, test_start_function startfunc,
+ postprocess_result_function postfunc)
+{
+#define MAX_PARALLEL_TESTS 100
+ char *tests[MAX_PARALLEL_TESTS];
+ _stringlist *resultfiles[MAX_PARALLEL_TESTS];
+ _stringlist *expectfiles[MAX_PARALLEL_TESTS];
+ _stringlist *tags[MAX_PARALLEL_TESTS];
+ PID_TYPE pids[MAX_PARALLEL_TESTS];
+ instr_time starttimes[MAX_PARALLEL_TESTS];
+ instr_time stoptimes[MAX_PARALLEL_TESTS];
+ int statuses[MAX_PARALLEL_TESTS];
+ _stringlist *ignorelist = NULL;
+ char scbuf[1024];
+ FILE *scf;
+ int line_num = 0;
+
+ memset(tests, 0, sizeof(tests));
+ memset(resultfiles, 0, sizeof(resultfiles));
+ memset(expectfiles, 0, sizeof(expectfiles));
+ memset(tags, 0, sizeof(tags));
+
+ scf = fopen(schedule, "r");
+ if (!scf)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
+ progname, schedule, strerror(errno));
+ exit(2);
+ }
+
+ while (fgets(scbuf, sizeof(scbuf), scf))
+ {
+ char *test = NULL;
+ char *c;
+ int num_tests;
+ bool inword;
+ int i;
+
+ line_num++;
+
+ /* strip trailing whitespace, especially the newline */
+ i = strlen(scbuf);
+ while (i > 0 && isspace((unsigned char) scbuf[i - 1]))
+ scbuf[--i] = '\0';
+
+ if (scbuf[0] == '\0' || scbuf[0] == '#')
+ continue;
+ if (strncmp(scbuf, "test: ", 6) == 0)
+ test = scbuf + 6;
+ else if (strncmp(scbuf, "ignore: ", 8) == 0)
+ {
+ c = scbuf + 8;
+ while (*c && isspace((unsigned char) *c))
+ c++;
+ add_stringlist_item(&ignorelist, c);
+
+ /*
+ * Note: ignore: lines do not run the test, they just say that
+ * failure of this test when run later on is to be ignored. A bit
+ * odd but that's how the shell-script version did it.
+ */
+ continue;
+ }
+ else
+ {
+ fprintf(stderr, _("syntax error in schedule file \"%s\" line %d: %s\n"),
+ schedule, line_num, scbuf);
+ exit(2);
+ }
+
+ num_tests = 0;
+ inword = false;
+ for (c = test;; c++)
+ {
+ if (*c == '\0' || isspace((unsigned char) *c))
+ {
+ if (inword)
+ {
+ /* Reached end of a test name */
+ char sav;
+
+ if (num_tests >= MAX_PARALLEL_TESTS)
+ {
+ fprintf(stderr, _("too many parallel tests (more than %d) in schedule file \"%s\" line %d: %s\n"),
+ MAX_PARALLEL_TESTS, schedule, line_num, scbuf);
+ exit(2);
+ }
+ sav = *c;
+ *c = '\0';
+ tests[num_tests] = pg_strdup(test);
+ num_tests++;
+ *c = sav;
+ inword = false;
+ }
+ if (*c == '\0')
+ break; /* loop exit is here */
+ }
+ else if (!inword)
+ {
+ /* Start of a test name */
+ test = c;
+ inword = true;
+ }
+ }
+
+ if (num_tests == 0)
+ {
+ fprintf(stderr, _("syntax error in schedule file \"%s\" line %d: %s\n"),
+ schedule, line_num, scbuf);
+ exit(2);
+ }
+
+ if (num_tests == 1)
+ {
+ status(_("test %-28s ... "), tests[0]);
+ pids[0] = (startfunc) (tests[0], &resultfiles[0], &expectfiles[0], &tags[0]);
+ INSTR_TIME_SET_CURRENT(starttimes[0]);
+ wait_for_tests(pids, statuses, stoptimes, NULL, 1);
+ /* status line is finished below */
+ }
+ else if (max_concurrent_tests > 0 && max_concurrent_tests < num_tests)
+ {
+ fprintf(stderr, _("too many parallel tests (more than %d) in schedule file \"%s\" line %d: %s\n"),
+ max_concurrent_tests, schedule, line_num, scbuf);
+ exit(2);
+ }
+ else if (max_connections > 0 && max_connections < num_tests)
+ {
+ int oldest = 0;
+
+ status(_("parallel group (%d tests, in groups of %d): "),
+ num_tests, max_connections);
+ for (i = 0; i < num_tests; i++)
+ {
+ if (i - oldest >= max_connections)
+ {
+ wait_for_tests(pids + oldest, statuses + oldest,
+ stoptimes + oldest,
+ tests + oldest, i - oldest);
+ oldest = i;
+ }
+ pids[i] = (startfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]);
+ INSTR_TIME_SET_CURRENT(starttimes[i]);
+ }
+ wait_for_tests(pids + oldest, statuses + oldest,
+ stoptimes + oldest,
+ tests + oldest, i - oldest);
+ status_end();
+ }
+ else
+ {
+ status(_("parallel group (%d tests): "), num_tests);
+ for (i = 0; i < num_tests; i++)
+ {
+ pids[i] = (startfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]);
+ INSTR_TIME_SET_CURRENT(starttimes[i]);
+ }
+ wait_for_tests(pids, statuses, stoptimes, tests, num_tests);
+ status_end();
+ }
+
+ /* Check results for all tests */
+ for (i = 0; i < num_tests; i++)
+ {
+ _stringlist *rl,
+ *el,
+ *tl;
+ bool differ = false;
+
+ if (num_tests > 1)
+ status(_(" %-28s ... "), tests[i]);
+
+ /*
+ * Advance over all three lists simultaneously.
+ *
+ * Compare resultfiles[j] with expectfiles[j] always. Tags are
+ * optional but if there are tags, the tag list has the same
+ * length as the other two lists.
+ */
+ for (rl = resultfiles[i], el = expectfiles[i], tl = tags[i];
+ rl != NULL; /* rl and el have the same length */
+ rl = rl->next, el = el->next,
+ tl = tl ? tl->next : NULL)
+ {
+ bool newdiff;
+
+ if (postfunc)
+ (*postfunc) (rl->str);
+ newdiff = results_differ(tests[i], rl->str, el->str);
+ if (newdiff && tl)
+ {
+ printf("%s ", tl->str);
+ }
+ differ |= newdiff;
+ }
+
+ if (differ)
+ {
+ bool ignore = false;
+ _stringlist *sl;
+
+ for (sl = ignorelist; sl != NULL; sl = sl->next)
+ {
+ if (strcmp(tests[i], sl->str) == 0)
+ {
+ ignore = true;
+ break;
+ }
+ }
+ if (ignore)
+ {
+ status(_("failed (ignored)"));
+ fail_ignore_count++;
+ }
+ else
+ {
+ status(_("FAILED"));
+ fail_count++;
+ }
+ }
+ else
+ {
+ status(_("ok ")); /* align with FAILED */
+ success_count++;
+ }
+
+ if (statuses[i] != 0)
+ log_child_failure(statuses[i]);
+
+ INSTR_TIME_SUBTRACT(stoptimes[i], starttimes[i]);
+ status(_(" %8.0f ms"), INSTR_TIME_GET_MILLISEC(stoptimes[i]));
+
+ status_end();
+ }
+
+ for (i = 0; i < num_tests; i++)
+ {
+ pg_free(tests[i]);
+ tests[i] = NULL;
+ free_stringlist(&resultfiles[i]);
+ free_stringlist(&expectfiles[i]);
+ free_stringlist(&tags[i]);
+ }
+ }
+
+ free_stringlist(&ignorelist);
+
+ fclose(scf);
+}
+
+/*
+ * Run a single test
+ */
+static void
+run_single_test(const char *test, test_start_function startfunc,
+ postprocess_result_function postfunc)
+{
+ PID_TYPE pid;
+ instr_time starttime;
+ instr_time stoptime;
+ int exit_status;
+ _stringlist *resultfiles = NULL;
+ _stringlist *expectfiles = NULL;
+ _stringlist *tags = NULL;
+ _stringlist *rl,
+ *el,
+ *tl;
+ bool differ = false;
+
+ status(_("test %-28s ... "), test);
+ pid = (startfunc) (test, &resultfiles, &expectfiles, &tags);
+ INSTR_TIME_SET_CURRENT(starttime);
+ wait_for_tests(&pid, &exit_status, &stoptime, NULL, 1);
+
+ /*
+ * Advance over all three lists simultaneously.
+ *
+ * Compare resultfiles[j] with expectfiles[j] always. Tags are optional
+ * but if there are tags, the tag list has the same length as the other
+ * two lists.
+ */
+ for (rl = resultfiles, el = expectfiles, tl = tags;
+ rl != NULL; /* rl and el have the same length */
+ rl = rl->next, el = el->next,
+ tl = tl ? tl->next : NULL)
+ {
+ bool newdiff;
+
+ if (postfunc)
+ (*postfunc) (rl->str);
+ newdiff = results_differ(test, rl->str, el->str);
+ if (newdiff && tl)
+ {
+ printf("%s ", tl->str);
+ }
+ differ |= newdiff;
+ }
+
+ if (differ)
+ {
+ status(_("FAILED"));
+ fail_count++;
+ }
+ else
+ {
+ status(_("ok ")); /* align with FAILED */
+ success_count++;
+ }
+
+ if (exit_status != 0)
+ log_child_failure(exit_status);
+
+ INSTR_TIME_SUBTRACT(stoptime, starttime);
+ status(_(" %8.0f ms"), INSTR_TIME_GET_MILLISEC(stoptime));
+
+ status_end();
+}
+
+/*
+ * Create the summary-output files (making them empty if already existing)
+ */
+static void
+open_result_files(void)
+{
+ char file[MAXPGPATH];
+ FILE *difffile;
+
+ /* create outputdir directory if not present */
+ if (!directory_exists(outputdir))
+ make_directory(outputdir);
+
+ /* create the log file (copy of running status output) */
+ snprintf(file, sizeof(file), "%s/regression.out", outputdir);
+ logfilename = pg_strdup(file);
+ logfile = fopen(logfilename, "w");
+ if (!logfile)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+ progname, logfilename, strerror(errno));
+ exit(2);
+ }
+
+ /* create the diffs file as empty */
+ snprintf(file, sizeof(file), "%s/regression.diffs", outputdir);
+ difffilename = pg_strdup(file);
+ difffile = fopen(difffilename, "w");
+ if (!difffile)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+ progname, difffilename, strerror(errno));
+ exit(2);
+ }
+ /* we don't keep the diffs file open continuously */
+ fclose(difffile);
+
+ /* also create the results directory if not present */
+ snprintf(file, sizeof(file), "%s/results", outputdir);
+ if (!directory_exists(file))
+ make_directory(file);
+}
+
+static void
+drop_database_if_exists(const char *dbname)
+{
+ header(_("dropping database \"%s\""), dbname);
+ psql_command("postgres", "DROP DATABASE IF EXISTS \"%s\"", dbname);
+}
+
+static void
+create_database(const char *dbname)
+{
+ _stringlist *sl;
+
+ /*
+ * We use template0 so that any installation-local cruft in template1 will
+ * not mess up the tests.
+ */
+ header(_("creating database \"%s\""), dbname);
+ if (encoding)
+ psql_command("postgres", "CREATE DATABASE \"%s\" TEMPLATE=template0 ENCODING='%s'%s", dbname, encoding,
+ (nolocale) ? " LC_COLLATE='C' LC_CTYPE='C'" : "");
+ else
+ psql_command("postgres", "CREATE DATABASE \"%s\" TEMPLATE=template0%s", dbname,
+ (nolocale) ? " LC_COLLATE='C' LC_CTYPE='C'" : "");
+ psql_command(dbname,
+ "ALTER DATABASE \"%s\" SET lc_messages TO 'C';"
+ "ALTER DATABASE \"%s\" SET lc_monetary TO 'C';"
+ "ALTER DATABASE \"%s\" SET lc_numeric TO 'C';"
+ "ALTER DATABASE \"%s\" SET lc_time TO 'C';"
+ "ALTER DATABASE \"%s\" SET bytea_output TO 'hex';"
+ "ALTER DATABASE \"%s\" SET timezone_abbreviations TO 'Default';",
+ dbname, dbname, dbname, dbname, dbname, dbname);
+
+ /*
+ * Install any requested extensions. We use CREATE IF NOT EXISTS so that
+ * this will work whether or not the extension is preinstalled.
+ */
+ for (sl = loadextension; sl != NULL; sl = sl->next)
+ {
+ header(_("installing %s"), sl->str);
+ psql_command(dbname, "CREATE EXTENSION IF NOT EXISTS \"%s\"", sl->str);
+ }
+}
+
+static void
+drop_role_if_exists(const char *rolename)
+{
+ header(_("dropping role \"%s\""), rolename);
+ psql_command("postgres", "DROP ROLE IF EXISTS \"%s\"", rolename);
+}
+
+static void
+create_role(const char *rolename, const _stringlist *granted_dbs)
+{
+ header(_("creating role \"%s\""), rolename);
+ psql_command("postgres", "CREATE ROLE \"%s\" WITH LOGIN", rolename);
+ for (; granted_dbs != NULL; granted_dbs = granted_dbs->next)
+ {
+ psql_command("postgres", "GRANT ALL ON DATABASE \"%s\" TO \"%s\"",
+ granted_dbs->str, rolename);
+ }
+}
+
+static void
+help(void)
+{
+ printf(_("PostgreSQL regression test driver\n"));
+ printf(_("\n"));
+ printf(_("Usage:\n %s [OPTION]... [EXTRA-TEST]...\n"), progname);
+ printf(_("\n"));
+ printf(_("Options:\n"));
+ printf(_(" --bindir=BINPATH use BINPATH for programs that are run;\n"));
+ printf(_(" if empty, use PATH from the environment\n"));
+ printf(_(" --config-auth=DATADIR update authentication settings for DATADIR\n"));
+ printf(_(" --create-role=ROLE create the specified role before testing\n"));
+ printf(_(" --dbname=DB use database DB (default \"regression\")\n"));
+ printf(_(" --debug turn on debug mode in programs that are run\n"));
+ printf(_(" --dlpath=DIR look for dynamic libraries in DIR\n"));
+ printf(_(" --encoding=ENCODING use ENCODING as the encoding\n"));
+ printf(_(" -h, --help show this help, then exit\n"));
+ printf(_(" --inputdir=DIR take input files from DIR (default \".\")\n"));
+ printf(_(" --launcher=CMD use CMD as launcher of psql\n"));
+ printf(_(" --load-extension=EXT load the named extension before running the\n"));
+ printf(_(" tests; can appear multiple times\n"));
+ printf(_(" --make-testtablespace-dir create testtablespace directory\n"));
+ printf(_(" --max-connections=N maximum number of concurrent connections\n"));
+ printf(_(" (default is 0, meaning unlimited)\n"));
+ printf(_(" --max-concurrent-tests=N maximum number of concurrent tests in schedule\n"));
+ printf(_(" (default is 0, meaning unlimited)\n"));
+ printf(_(" --outputdir=DIR place output files in DIR (default \".\")\n"));
+ printf(_(" --schedule=FILE use test ordering schedule from FILE\n"));
+ printf(_(" (can be used multiple times to concatenate)\n"));
+ printf(_(" --temp-instance=DIR create a temporary instance in DIR\n"));
+ printf(_(" --use-existing use an existing installation\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
+ printf(_("\n"));
+ printf(_("Options for \"temp-instance\" mode:\n"));
+ printf(_(" --no-locale use C locale\n"));
+ printf(_(" --port=PORT start postmaster on PORT\n"));
+ printf(_(" --temp-config=FILE append contents of FILE to temporary config\n"));
+ printf(_("\n"));
+ printf(_("Options for using an existing installation:\n"));
+ printf(_(" --host=HOST use postmaster running on HOST\n"));
+ printf(_(" --port=PORT use postmaster running at PORT\n"));
+ printf(_(" --user=USER connect as USER\n"));
+ printf(_("\n"));
+ printf(_("The exit status is 0 if all tests passed, 1 if some tests failed, and 2\n"));
+ printf(_("if the tests could not be run for some reason.\n"));
+ printf(_("\n"));
+ printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+}
+
+int
+regression_main(int argc, char *argv[],
+ init_function ifunc,
+ test_start_function startfunc,
+ postprocess_result_function postfunc)
+{
+ static struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'V'},
+ {"dbname", required_argument, NULL, 1},
+ {"debug", no_argument, NULL, 2},
+ {"inputdir", required_argument, NULL, 3},
+ {"max-connections", required_argument, NULL, 5},
+ {"encoding", required_argument, NULL, 6},
+ {"outputdir", required_argument, NULL, 7},
+ {"schedule", required_argument, NULL, 8},
+ {"temp-instance", required_argument, NULL, 9},
+ {"no-locale", no_argument, NULL, 10},
+ {"host", required_argument, NULL, 13},
+ {"port", required_argument, NULL, 14},
+ {"user", required_argument, NULL, 15},
+ {"bindir", required_argument, NULL, 16},
+ {"dlpath", required_argument, NULL, 17},
+ {"create-role", required_argument, NULL, 18},
+ {"temp-config", required_argument, NULL, 19},
+ {"use-existing", no_argument, NULL, 20},
+ {"launcher", required_argument, NULL, 21},
+ {"load-extension", required_argument, NULL, 22},
+ {"config-auth", required_argument, NULL, 24},
+ {"max-concurrent-tests", required_argument, NULL, 25},
+ {"make-testtablespace-dir", no_argument, NULL, 26},
+ {NULL, 0, NULL, 0}
+ };
+
+ bool use_unix_sockets;
+ bool make_testtablespace_dir = false;
+ _stringlist *sl;
+ int c;
+ int i;
+ int option_index;
+ char buf[MAXPGPATH * 4];
+ char buf2[MAXPGPATH * 4];
+
+ pg_logging_init(argv[0]);
+ progname = get_progname(argv[0]);
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_regress"));
+
+ get_restricted_token();
+
+ atexit(stop_postmaster);
+
+#if !defined(HAVE_UNIX_SOCKETS)
+ use_unix_sockets = false;
+#elif defined(WIN32)
+
+ /*
+ * We don't use Unix-domain sockets on Windows by default, even if the
+ * build supports them. (See comment at remove_temp() for a reason.)
+ * Override at your own risk.
+ */
+ use_unix_sockets = getenv("PG_TEST_USE_UNIX_SOCKETS") ? true : false;
+#else
+ use_unix_sockets = true;
+#endif
+
+ if (!use_unix_sockets)
+ hostname = "localhost";
+
+ /*
+ * We call the initialization function here because that way we can set
+ * default parameters and let them be overwritten by the commandline.
+ */
+ ifunc(argc, argv);
+
+ if (getenv("PG_REGRESS_DIFF_OPTS"))
+ pretty_diff_opts = getenv("PG_REGRESS_DIFF_OPTS");
+
+ while ((c = getopt_long(argc, argv, "hV", long_options, &option_index)) != -1)
+ {
+ switch (c)
+ {
+ case 'h':
+ help();
+ exit(0);
+ case 'V':
+ puts("pg_regress (PostgreSQL) " PG_VERSION);
+ exit(0);
+ case 1:
+
+ /*
+ * If a default database was specified, we need to remove it
+ * before we add the specified one.
+ */
+ free_stringlist(&dblist);
+ split_to_stringlist(optarg, ",", &dblist);
+ break;
+ case 2:
+ debug = true;
+ break;
+ case 3:
+ inputdir = pg_strdup(optarg);
+ break;
+ case 5:
+ max_connections = atoi(optarg);
+ break;
+ case 6:
+ encoding = pg_strdup(optarg);
+ break;
+ case 7:
+ outputdir = pg_strdup(optarg);
+ break;
+ case 8:
+ add_stringlist_item(&schedulelist, optarg);
+ break;
+ case 9:
+ temp_instance = make_absolute_path(optarg);
+ break;
+ case 10:
+ nolocale = true;
+ break;
+ case 13:
+ hostname = pg_strdup(optarg);
+ break;
+ case 14:
+ port = atoi(optarg);
+ port_specified_by_user = true;
+ break;
+ case 15:
+ user = pg_strdup(optarg);
+ break;
+ case 16:
+ /* "--bindir=" means to use PATH */
+ if (strlen(optarg))
+ bindir = pg_strdup(optarg);
+ else
+ bindir = NULL;
+ break;
+ case 17:
+ dlpath = pg_strdup(optarg);
+ break;
+ case 18:
+ split_to_stringlist(optarg, ",", &extraroles);
+ break;
+ case 19:
+ add_stringlist_item(&temp_configs, optarg);
+ break;
+ case 20:
+ use_existing = true;
+ break;
+ case 21:
+ launcher = pg_strdup(optarg);
+ break;
+ case 22:
+ add_stringlist_item(&loadextension, optarg);
+ break;
+ case 24:
+ config_auth_datadir = pg_strdup(optarg);
+ break;
+ case 25:
+ max_concurrent_tests = atoi(optarg);
+ break;
+ case 26:
+ make_testtablespace_dir = true;
+ break;
+ default:
+ /* getopt_long already emitted a complaint */
+ fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),
+ progname);
+ exit(2);
+ }
+ }
+
+ /*
+ * if we still have arguments, they are extra tests to run
+ */
+ while (argc - optind >= 1)
+ {
+ add_stringlist_item(&extra_tests, argv[optind]);
+ optind++;
+ }
+
+ if (config_auth_datadir)
+ {
+#ifdef ENABLE_SSPI
+ if (!use_unix_sockets)
+ config_sspi_auth(config_auth_datadir, user);
+#endif
+ exit(0);
+ }
+
+ if (temp_instance && !port_specified_by_user)
+
+ /*
+ * To reduce chances of interference with parallel installations, use
+ * a port number starting in the private range (49152-65535)
+ * calculated from the version number. This aids !HAVE_UNIX_SOCKETS
+ * systems; elsewhere, the use of a private socket directory already
+ * prevents interference.
+ */
+ port = 0xC000 | (PG_VERSION_NUM & 0x3FFF);
+
+ inputdir = make_absolute_path(inputdir);
+ outputdir = make_absolute_path(outputdir);
+ dlpath = make_absolute_path(dlpath);
+
+ /*
+ * Initialization
+ */
+ open_result_files();
+
+ initialize_environment();
+
+#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE)
+ unlimit_core_size();
+#endif
+
+ if (make_testtablespace_dir)
+ prepare_testtablespace_dir();
+
+ if (temp_instance)
+ {
+ FILE *pg_conf;
+ const char *env_wait;
+ int wait_seconds;
+
+ /*
+ * Prepare the temp instance
+ */
+
+ if (directory_exists(temp_instance))
+ {
+ header(_("removing existing temp instance"));
+ if (!rmtree(temp_instance, true))
+ {
+ fprintf(stderr, _("\n%s: could not remove temp instance \"%s\"\n"),
+ progname, temp_instance);
+ exit(2);
+ }
+ }
+
+ header(_("creating temporary instance"));
+
+ /* make the temp instance top directory */
+ make_directory(temp_instance);
+
+ /* and a directory for log files */
+ snprintf(buf, sizeof(buf), "%s/log", outputdir);
+ if (!directory_exists(buf))
+ make_directory(buf);
+
+ /* initdb */
+ header(_("initializing database system"));
+ snprintf(buf, sizeof(buf),
+ "\"%s%sinitdb\" -D \"%s/data\" --no-clean --no-sync%s%s > \"%s/log/initdb.log\" 2>&1",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ temp_instance,
+ debug ? " --debug" : "",
+ nolocale ? " --no-locale" : "",
+ outputdir);
+ if (system(buf))
+ {
+ fprintf(stderr, _("\n%s: initdb failed\nExamine %s/log/initdb.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
+ exit(2);
+ }
+
+ /*
+ * Adjust the default postgresql.conf for regression testing. The user
+ * can specify a file to be appended; in any case we expand logging
+ * and set max_prepared_transactions to enable testing of prepared
+ * xacts. (Note: to reduce the probability of unexpected shmmax
+ * failures, don't set max_prepared_transactions any higher than
+ * actually needed by the prepared_xacts regression test.)
+ */
+ snprintf(buf, sizeof(buf), "%s/data/postgresql.conf", temp_instance);
+ pg_conf = fopen(buf, "a");
+ if (pg_conf == NULL)
+ {
+ fprintf(stderr, _("\n%s: could not open \"%s\" for adding extra config: %s\n"), progname, buf, strerror(errno));
+ exit(2);
+ }
+ fputs("\n# Configuration added by pg_regress\n\n", pg_conf);
+ fputs("log_autovacuum_min_duration = 0\n", pg_conf);
+ fputs("log_checkpoints = on\n", pg_conf);
+ fputs("log_line_prefix = '%m %b[%p] %q%a '\n", pg_conf);
+ fputs("log_lock_waits = on\n", pg_conf);
+ fputs("log_temp_files = 128kB\n", pg_conf);
+ fputs("max_prepared_transactions = 2\n", pg_conf);
+
+ for (sl = temp_configs; sl != NULL; sl = sl->next)
+ {
+ char *temp_config = sl->str;
+ FILE *extra_conf;
+ char line_buf[1024];
+
+ extra_conf = fopen(temp_config, "r");
+ if (extra_conf == NULL)
+ {
+ fprintf(stderr, _("\n%s: could not open \"%s\" to read extra config: %s\n"), progname, temp_config, strerror(errno));
+ exit(2);
+ }
+ while (fgets(line_buf, sizeof(line_buf), extra_conf) != NULL)
+ fputs(line_buf, pg_conf);
+ fclose(extra_conf);
+ }
+
+ fclose(pg_conf);
+
+#ifdef ENABLE_SSPI
+ if (!use_unix_sockets)
+ {
+ /*
+ * Since we successfully used the same buffer for the much-longer
+ * "initdb" command, this can't truncate.
+ */
+ snprintf(buf, sizeof(buf), "%s/data", temp_instance);
+ config_sspi_auth(buf, NULL);
+ }
+#elif !defined(HAVE_UNIX_SOCKETS)
+#error Platform has no means to secure the test installation.
+#endif
+
+ /*
+ * Check if there is a postmaster running already.
+ */
+ snprintf(buf2, sizeof(buf2),
+ "\"%s%spsql\" -X postgres <%s 2>%s",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ DEVNULL, DEVNULL);
+
+ for (i = 0; i < 16; i++)
+ {
+ if (system(buf2) == 0)
+ {
+ char s[16];
+
+ if (port_specified_by_user || i == 15)
+ {
+ fprintf(stderr, _("port %d apparently in use\n"), port);
+ if (!port_specified_by_user)
+ fprintf(stderr, _("%s: could not determine an available port\n"), progname);
+ fprintf(stderr, _("Specify an unused port using the --port option or shut down any conflicting PostgreSQL servers.\n"));
+ exit(2);
+ }
+
+ fprintf(stderr, _("port %d apparently in use, trying %d\n"), port, port + 1);
+ port++;
+ sprintf(s, "%d", port);
+ setenv("PGPORT", s, 1);
+ }
+ else
+ break;
+ }
+
+ /*
+ * Start the temp postmaster
+ */
+ header(_("starting postmaster"));
+ snprintf(buf, sizeof(buf),
+ "\"%s%spostgres\" -D \"%s/data\" -F%s "
+ "-c \"listen_addresses=%s\" -k \"%s\" "
+ "> \"%s/log/postmaster.log\" 2>&1",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ temp_instance, debug ? " -d 5" : "",
+ hostname ? hostname : "", sockdir ? sockdir : "",
+ outputdir);
+ postmaster_pid = spawn_process(buf);
+ if (postmaster_pid == INVALID_PID)
+ {
+ fprintf(stderr, _("\n%s: could not spawn postmaster: %s\n"),
+ progname, strerror(errno));
+ exit(2);
+ }
+
+ /*
+ * Wait till postmaster is able to accept connections; normally this
+ * is only a second or so, but Cygwin is reportedly *much* slower, and
+ * test builds using Valgrind or similar tools might be too. Hence,
+ * allow the default timeout of 60 seconds to be overridden from the
+ * PGCTLTIMEOUT environment variable.
+ */
+ env_wait = getenv("PGCTLTIMEOUT");
+ if (env_wait != NULL)
+ {
+ wait_seconds = atoi(env_wait);
+ if (wait_seconds <= 0)
+ wait_seconds = 60;
+ }
+ else
+ wait_seconds = 60;
+
+ for (i = 0; i < wait_seconds; i++)
+ {
+ /* Done if psql succeeds */
+ if (system(buf2) == 0)
+ break;
+
+ /*
+ * Fail immediately if postmaster has exited
+ */
+#ifndef WIN32
+ if (waitpid(postmaster_pid, NULL, WNOHANG) == postmaster_pid)
+#else
+ if (WaitForSingleObject(postmaster_pid, 0) == WAIT_OBJECT_0)
+#endif
+ {
+ fprintf(stderr, _("\n%s: postmaster failed\nExamine %s/log/postmaster.log for the reason\n"), progname, outputdir);
+ exit(2);
+ }
+
+ pg_usleep(1000000L);
+ }
+ if (i >= wait_seconds)
+ {
+ fprintf(stderr, _("\n%s: postmaster did not respond within %d seconds\nExamine %s/log/postmaster.log for the reason\n"),
+ progname, wait_seconds, outputdir);
+
+ /*
+ * If we get here, the postmaster is probably wedged somewhere in
+ * startup. Try to kill it ungracefully rather than leaving a
+ * stuck postmaster that might interfere with subsequent test
+ * attempts.
+ */
+#ifndef WIN32
+ if (kill(postmaster_pid, SIGKILL) != 0 &&
+ errno != ESRCH)
+ fprintf(stderr, _("\n%s: could not kill failed postmaster: %s\n"),
+ progname, strerror(errno));
+#else
+ if (TerminateProcess(postmaster_pid, 255) == 0)
+ fprintf(stderr, _("\n%s: could not kill failed postmaster: error code %lu\n"),
+ progname, GetLastError());
+#endif
+
+ exit(2);
+ }
+
+ postmaster_running = true;
+
+#ifdef _WIN64
+/* need a series of two casts to convert HANDLE without compiler warning */
+#define ULONGPID(x) (unsigned long) (unsigned long long) (x)
+#else
+#define ULONGPID(x) (unsigned long) (x)
+#endif
+ printf(_("running on port %d with PID %lu\n"),
+ port, ULONGPID(postmaster_pid));
+ }
+ else
+ {
+ /*
+ * Using an existing installation, so may need to get rid of
+ * pre-existing database(s) and role(s)
+ */
+ if (!use_existing)
+ {
+ for (sl = dblist; sl; sl = sl->next)
+ drop_database_if_exists(sl->str);
+ for (sl = extraroles; sl; sl = sl->next)
+ drop_role_if_exists(sl->str);
+ }
+ }
+
+ /*
+ * Create the test database(s) and role(s)
+ */
+ if (!use_existing)
+ {
+ for (sl = dblist; sl; sl = sl->next)
+ create_database(sl->str);
+ for (sl = extraroles; sl; sl = sl->next)
+ create_role(sl->str, dblist);
+ }
+
+ /*
+ * Ready to run the tests
+ */
+ header(_("running regression test queries"));
+
+ for (sl = schedulelist; sl != NULL; sl = sl->next)
+ {
+ run_schedule(sl->str, startfunc, postfunc);
+ }
+
+ for (sl = extra_tests; sl != NULL; sl = sl->next)
+ {
+ run_single_test(sl->str, startfunc, postfunc);
+ }
+
+ /*
+ * Shut down temp installation's postmaster
+ */
+ if (temp_instance)
+ {
+ header(_("shutting down postmaster"));
+ stop_postmaster();
+ }
+
+ /*
+ * If there were no errors, remove the temp instance immediately to
+ * conserve disk space. (If there were errors, we leave the instance in
+ * place for possible manual investigation.)
+ */
+ if (temp_instance && fail_count == 0 && fail_ignore_count == 0)
+ {
+ header(_("removing temporary instance"));
+ if (!rmtree(temp_instance, true))
+ fprintf(stderr, _("\n%s: could not remove temp instance \"%s\"\n"),
+ progname, temp_instance);
+ }
+
+ fclose(logfile);
+
+ /*
+ * Emit nice-looking summary message
+ */
+ if (fail_count == 0 && fail_ignore_count == 0)
+ snprintf(buf, sizeof(buf),
+ _(" All %d tests passed. "),
+ success_count);
+ else if (fail_count == 0) /* fail_count=0, fail_ignore_count>0 */
+ snprintf(buf, sizeof(buf),
+ _(" %d of %d tests passed, %d failed test(s) ignored. "),
+ success_count,
+ success_count + fail_ignore_count,
+ fail_ignore_count);
+ else if (fail_ignore_count == 0) /* fail_count>0 && fail_ignore_count=0 */
+ snprintf(buf, sizeof(buf),
+ _(" %d of %d tests failed. "),
+ fail_count,
+ success_count + fail_count);
+ else
+ /* fail_count>0 && fail_ignore_count>0 */
+ snprintf(buf, sizeof(buf),
+ _(" %d of %d tests failed, %d of these failures ignored. "),
+ fail_count + fail_ignore_count,
+ success_count + fail_count + fail_ignore_count,
+ fail_ignore_count);
+
+ putchar('\n');
+ for (i = strlen(buf); i > 0; i--)
+ putchar('=');
+ printf("\n%s\n", buf);
+ for (i = strlen(buf); i > 0; i--)
+ putchar('=');
+ putchar('\n');
+ putchar('\n');
+
+ if (file_size(difffilename) > 0)
+ {
+ printf(_("The differences that caused some tests to fail can be viewed in the\n"
+ "file \"%s\". A copy of the test summary that you see\n"
+ "above is saved in the file \"%s\".\n\n"),
+ difffilename, logfilename);
+ }
+ else
+ {
+ unlink(difffilename);
+ unlink(logfilename);
+ }
+
+ if (fail_count != 0)
+ exit(1);
+
+ return 0;
+}
diff --git a/src/test/regress/pg_regress.h b/src/test/regress/pg_regress.h
new file mode 100644
index 0000000..c6d015c
--- /dev/null
+++ b/src/test/regress/pg_regress.h
@@ -0,0 +1,70 @@
+/*-------------------------------------------------------------------------
+ * pg_regress.h --- regression test driver
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/test/regress/pg_regress.h
+ *-------------------------------------------------------------------------
+ */
+
+#include <unistd.h>
+
+#ifndef WIN32
+#define PID_TYPE pid_t
+#define INVALID_PID (-1)
+#else
+#define PID_TYPE HANDLE
+#define INVALID_PID INVALID_HANDLE_VALUE
+#endif
+
+struct StringInfoData; /* avoid including stringinfo.h here */
+
+/* simple list of strings */
+typedef struct _stringlist
+{
+ char *str;
+ struct _stringlist *next;
+} _stringlist;
+
+/*
+ * Callback function signatures for test programs that use regression_main()
+ */
+
+/* Initialize at program start */
+typedef void (*init_function) (int argc, char **argv);
+
+/* Launch one test case */
+typedef PID_TYPE(*test_start_function) (const char *testname,
+ _stringlist **resultfiles,
+ _stringlist **expectfiles,
+ _stringlist **tags);
+
+/* Postprocess one result file (optional) */
+typedef void (*postprocess_result_function) (const char *filename);
+
+
+extern char *bindir;
+extern char *libdir;
+extern char *datadir;
+extern char *host_platform;
+
+extern _stringlist *dblist;
+extern bool debug;
+extern char *inputdir;
+extern char *outputdir;
+extern char *launcher;
+
+extern const char *basic_diff_opts;
+extern const char *pretty_diff_opts;
+
+int regression_main(int argc, char *argv[],
+ init_function ifunc,
+ test_start_function startfunc,
+ postprocess_result_function postfunc);
+
+void add_stringlist_item(_stringlist **listhead, const char *str);
+PID_TYPE spawn_process(const char *cmdline);
+void replace_string(struct StringInfoData *string,
+ const char *replace, const char *replacement);
+bool file_exists(const char *file);
diff --git a/src/test/regress/pg_regress_main.c b/src/test/regress/pg_regress_main.c
new file mode 100644
index 0000000..1524676
--- /dev/null
+++ b/src/test/regress/pg_regress_main.c
@@ -0,0 +1,126 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_regress_main --- regression test for the main backend
+ *
+ * This is a C implementation of the previous shell script for running
+ * the regression tests, and should be mostly compatible with it.
+ * Initial author of C translation: Magnus Hagander
+ *
+ * This code is released under the terms of the PostgreSQL License.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/test/regress/pg_regress_main.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "pg_regress.h"
+
+/*
+ * start a psql 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)
+{
+ PID_TYPE pid;
+ char infile[MAXPGPATH];
+ char outfile[MAXPGPATH];
+ char expectfile[MAXPGPATH];
+ char psql_cmd[MAXPGPATH * 3];
+ size_t offset = 0;
+ char *appnameenv;
+
+ /*
+ * Look for files in the output dir first, consistent with a vpath search.
+ * This is mainly to create more reasonable error messages if the file is
+ * not found. It also allows local test overrides when running pg_regress
+ * outside of the source tree.
+ */
+ snprintf(infile, sizeof(infile), "%s/sql/%s.sql",
+ outputdir, testname);
+ if (!file_exists(infile))
+ snprintf(infile, sizeof(infile), "%s/sql/%s.sql",
+ inputdir, testname);
+
+ snprintf(outfile, sizeof(outfile), "%s/results/%s.out",
+ outputdir, testname);
+
+ snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out",
+ outputdir, testname);
+ if (!file_exists(expectfile))
+ snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out",
+ inputdir, testname);
+
+ add_stringlist_item(resultfiles, outfile);
+ add_stringlist_item(expectfiles, expectfile);
+
+ if (launcher)
+ {
+ offset += snprintf(psql_cmd + offset, sizeof(psql_cmd) - offset,
+ "%s ", launcher);
+ if (offset >= sizeof(psql_cmd))
+ {
+ fprintf(stderr, _("command too long\n"));
+ exit(2);
+ }
+ }
+
+ /*
+ * 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",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ dblist->str,
+ "-v HIDE_TABLEAM=on -v HIDE_TOAST_COMPRESSION=on",
+ infile,
+ outfile);
+ if (offset >= sizeof(psql_cmd))
+ {
+ fprintf(stderr, _("command too long\n"));
+ exit(2);
+ }
+
+ appnameenv = psprintf("pg_regress/%s", testname);
+ setenv("PGAPPNAME", appnameenv, 1);
+ free(appnameenv);
+
+ pid = spawn_process(psql_cmd);
+
+ if (pid == INVALID_PID)
+ {
+ fprintf(stderr, _("could not start process for test %s\n"),
+ testname);
+ exit(2);
+ }
+
+ unsetenv("PGAPPNAME");
+
+ return pid;
+}
+
+static void
+psql_init(int argc, char **argv)
+{
+ /* set default regression database name */
+ add_stringlist_item(&dblist, "regression");
+}
+
+int
+main(int argc, char *argv[])
+{
+ return regression_main(argc, argv,
+ psql_init,
+ psql_start_test,
+ NULL /* no postfunc needed */ );
+}
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
new file mode 100644
index 0000000..351d79e
--- /dev/null
+++ b/src/test/regress/regress.c
@@ -0,0 +1,1208 @@
+/*------------------------------------------------------------------------
+ *
+ * regress.c
+ * Code for various C-language functions defined as part of the
+ * regression tests.
+ *
+ * This code is released under the terms of the PostgreSQL License.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/test/regress/regress.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <math.h>
+#include <signal.h>
+
+#include "access/detoast.h"
+#include "access/htup_details.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_type.h"
+#include "commands/sequence.h"
+#include "commands/trigger.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
+#include "funcapi.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "nodes/supportnodes.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
+#include "parser/parse_coerce.h"
+#include "port/atomics.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/geo_decls.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/typcache.h"
+
+#define EXPECT_TRUE(expr) \
+ do { \
+ if (!(expr)) \
+ elog(ERROR, \
+ "%s was unexpectedly false in file \"%s\" line %u", \
+ #expr, __FILE__, __LINE__); \
+ } while (0)
+
+#define EXPECT_EQ_U32(result_expr, expected_expr) \
+ do { \
+ uint32 result = (result_expr); \
+ uint32 expected = (expected_expr); \
+ if (result != expected) \
+ elog(ERROR, \
+ "%s yielded %u, expected %s in file \"%s\" line %u", \
+ #result_expr, result, #expected_expr, __FILE__, __LINE__); \
+ } while (0)
+
+#define EXPECT_EQ_U64(result_expr, expected_expr) \
+ do { \
+ uint64 result = (result_expr); \
+ uint64 expected = (expected_expr); \
+ if (result != expected) \
+ elog(ERROR, \
+ "%s yielded " UINT64_FORMAT ", expected %s in file \"%s\" line %u", \
+ #result_expr, result, #expected_expr, __FILE__, __LINE__); \
+ } while (0)
+
+#define LDELIM '('
+#define RDELIM ')'
+#define DELIM ','
+
+static void regress_lseg_construct(LSEG *lseg, Point *pt1, Point *pt2);
+
+PG_MODULE_MAGIC;
+
+
+/* return the point where two paths intersect, or NULL if no intersection. */
+PG_FUNCTION_INFO_V1(interpt_pp);
+
+Datum
+interpt_pp(PG_FUNCTION_ARGS)
+{
+ PATH *p1 = PG_GETARG_PATH_P(0);
+ PATH *p2 = PG_GETARG_PATH_P(1);
+ int i,
+ j;
+ LSEG seg1,
+ seg2;
+ bool found; /* We've found the intersection */
+
+ found = false; /* Haven't found it yet */
+
+ for (i = 0; i < p1->npts - 1 && !found; i++)
+ {
+ regress_lseg_construct(&seg1, &p1->p[i], &p1->p[i + 1]);
+ for (j = 0; j < p2->npts - 1 && !found; j++)
+ {
+ regress_lseg_construct(&seg2, &p2->p[j], &p2->p[j + 1]);
+ if (DatumGetBool(DirectFunctionCall2(lseg_intersect,
+ LsegPGetDatum(&seg1),
+ LsegPGetDatum(&seg2))))
+ found = true;
+ }
+ }
+
+ if (!found)
+ PG_RETURN_NULL();
+
+ /*
+ * Note: DirectFunctionCall2 will kick out an error if lseg_interpt()
+ * returns NULL, but that should be impossible since we know the two
+ * segments intersect.
+ */
+ PG_RETURN_DATUM(DirectFunctionCall2(lseg_interpt,
+ LsegPGetDatum(&seg1),
+ LsegPGetDatum(&seg2)));
+}
+
+
+/* like lseg_construct, but assume space already allocated */
+static void
+regress_lseg_construct(LSEG *lseg, Point *pt1, Point *pt2)
+{
+ lseg->p[0].x = pt1->x;
+ lseg->p[0].y = pt1->y;
+ lseg->p[1].x = pt2->x;
+ lseg->p[1].y = pt2->y;
+}
+
+PG_FUNCTION_INFO_V1(overpaid);
+
+Datum
+overpaid(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader tuple = PG_GETARG_HEAPTUPLEHEADER(0);
+ bool isnull;
+ int32 salary;
+
+ salary = DatumGetInt32(GetAttributeByName(tuple, "salary", &isnull));
+ if (isnull)
+ PG_RETURN_NULL();
+ PG_RETURN_BOOL(salary > 699);
+}
+
+/* New type "widget"
+ * This used to be "circle", but I added circle to builtins,
+ * so needed to make sure the names do not collide. - tgl 97/04/21
+ */
+
+typedef struct
+{
+ Point center;
+ double radius;
+} WIDGET;
+
+PG_FUNCTION_INFO_V1(widget_in);
+PG_FUNCTION_INFO_V1(widget_out);
+
+#define NARGS 3
+
+Datum
+widget_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+ char *p,
+ *coord[NARGS];
+ int i;
+ WIDGET *result;
+
+ for (i = 0, p = str; *p && i < NARGS && *p != RDELIM; p++)
+ {
+ if (*p == DELIM || (*p == LDELIM && i == 0))
+ coord[i++] = p + 1;
+ }
+
+ if (i < NARGS)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "widget", str)));
+
+ result = (WIDGET *) palloc(sizeof(WIDGET));
+ result->center.x = atof(coord[0]);
+ result->center.y = atof(coord[1]);
+ result->radius = atof(coord[2]);
+
+ PG_RETURN_POINTER(result);
+}
+
+Datum
+widget_out(PG_FUNCTION_ARGS)
+{
+ WIDGET *widget = (WIDGET *) PG_GETARG_POINTER(0);
+ char *str = psprintf("(%g,%g,%g)",
+ widget->center.x, widget->center.y, widget->radius);
+
+ PG_RETURN_CSTRING(str);
+}
+
+PG_FUNCTION_INFO_V1(pt_in_widget);
+
+Datum
+pt_in_widget(PG_FUNCTION_ARGS)
+{
+ Point *point = PG_GETARG_POINT_P(0);
+ WIDGET *widget = (WIDGET *) PG_GETARG_POINTER(1);
+ float8 distance;
+
+ distance = DatumGetFloat8(DirectFunctionCall2(point_distance,
+ PointPGetDatum(point),
+ PointPGetDatum(&widget->center)));
+
+ PG_RETURN_BOOL(distance < widget->radius);
+}
+
+PG_FUNCTION_INFO_V1(reverse_name);
+
+Datum
+reverse_name(PG_FUNCTION_ARGS)
+{
+ char *string = PG_GETARG_CSTRING(0);
+ int i;
+ int len;
+ char *new_string;
+
+ new_string = palloc0(NAMEDATALEN);
+ for (i = 0; i < NAMEDATALEN && string[i]; ++i)
+ ;
+ if (i == NAMEDATALEN || !string[i])
+ --i;
+ len = i;
+ for (; i >= 0; --i)
+ new_string[len - i] = string[i];
+ PG_RETURN_CSTRING(new_string);
+}
+
+PG_FUNCTION_INFO_V1(trigger_return_old);
+
+Datum
+trigger_return_old(PG_FUNCTION_ARGS)
+{
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+ HeapTuple tuple;
+
+ if (!CALLED_AS_TRIGGER(fcinfo))
+ elog(ERROR, "trigger_return_old: not fired by trigger manager");
+
+ tuple = trigdata->tg_trigtuple;
+
+ return PointerGetDatum(tuple);
+}
+
+#define TTDUMMY_INFINITY 999999
+
+static SPIPlanPtr splan = NULL;
+static bool ttoff = false;
+
+PG_FUNCTION_INFO_V1(ttdummy);
+
+Datum
+ttdummy(PG_FUNCTION_ARGS)
+{
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+ Trigger *trigger; /* to get trigger name */
+ char **args; /* arguments */
+ int attnum[2]; /* fnumbers of start/stop columns */
+ Datum oldon,
+ oldoff;
+ Datum newon,
+ newoff;
+ Datum *cvals; /* column values */
+ char *cnulls; /* column nulls */
+ char *relname; /* triggered relation name */
+ Relation rel; /* triggered relation */
+ HeapTuple trigtuple;
+ HeapTuple newtuple = NULL;
+ HeapTuple rettuple;
+ TupleDesc tupdesc; /* tuple description */
+ int natts; /* # of attributes */
+ bool isnull; /* to know is some column NULL or not */
+ int ret;
+ int i;
+
+ if (!CALLED_AS_TRIGGER(fcinfo))
+ elog(ERROR, "ttdummy: not fired by trigger manager");
+ if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
+ elog(ERROR, "ttdummy: must be fired for row");
+ if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
+ elog(ERROR, "ttdummy: must be fired before event");
+ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+ elog(ERROR, "ttdummy: cannot process INSERT event");
+ if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ newtuple = trigdata->tg_newtuple;
+
+ trigtuple = trigdata->tg_trigtuple;
+
+ rel = trigdata->tg_relation;
+ relname = SPI_getrelname(rel);
+
+ /* check if TT is OFF for this relation */
+ if (ttoff) /* OFF - nothing to do */
+ {
+ pfree(relname);
+ return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple);
+ }
+
+ trigger = trigdata->tg_trigger;
+
+ if (trigger->tgnargs != 2)
+ elog(ERROR, "ttdummy (%s): invalid (!= 2) number of arguments %d",
+ relname, trigger->tgnargs);
+
+ args = trigger->tgargs;
+ tupdesc = rel->rd_att;
+ natts = tupdesc->natts;
+
+ for (i = 0; i < 2; i++)
+ {
+ attnum[i] = SPI_fnumber(tupdesc, args[i]);
+ if (attnum[i] <= 0)
+ elog(ERROR, "ttdummy (%s): there is no attribute %s",
+ relname, args[i]);
+ if (SPI_gettypeid(tupdesc, attnum[i]) != INT4OID)
+ elog(ERROR, "ttdummy (%s): attribute %s must be of integer type",
+ relname, args[i]);
+ }
+
+ oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull);
+ if (isnull)
+ elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[0]);
+
+ oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull);
+ if (isnull)
+ elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[1]);
+
+ if (newtuple != NULL) /* UPDATE */
+ {
+ newon = SPI_getbinval(newtuple, tupdesc, attnum[0], &isnull);
+ if (isnull)
+ elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[0]);
+ newoff = SPI_getbinval(newtuple, tupdesc, attnum[1], &isnull);
+ if (isnull)
+ elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[1]);
+
+ if (oldon != newon || oldoff != newoff)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ttdummy (%s): you cannot change %s and/or %s columns (use set_ttdummy)",
+ relname, args[0], args[1])));
+
+ if (newoff != TTDUMMY_INFINITY)
+ {
+ pfree(relname); /* allocated in upper executor context */
+ return PointerGetDatum(NULL);
+ }
+ }
+ else if (oldoff != TTDUMMY_INFINITY) /* DELETE */
+ {
+ pfree(relname);
+ return PointerGetDatum(NULL);
+ }
+
+ newoff = DirectFunctionCall1(nextval, CStringGetTextDatum("ttdummy_seq"));
+ /* nextval now returns int64; coerce down to int32 */
+ newoff = Int32GetDatum((int32) DatumGetInt64(newoff));
+
+ /* Connect to SPI manager */
+ if ((ret = SPI_connect()) < 0)
+ elog(ERROR, "ttdummy (%s): SPI_connect returned %d", relname, ret);
+
+ /* Fetch tuple values and nulls */
+ cvals = (Datum *) palloc(natts * sizeof(Datum));
+ cnulls = (char *) palloc(natts * sizeof(char));
+ for (i = 0; i < natts; i++)
+ {
+ cvals[i] = SPI_getbinval((newtuple != NULL) ? newtuple : trigtuple,
+ tupdesc, i + 1, &isnull);
+ cnulls[i] = (isnull) ? 'n' : ' ';
+ }
+
+ /* change date column(s) */
+ if (newtuple) /* UPDATE */
+ {
+ cvals[attnum[0] - 1] = newoff; /* start_date eq current date */
+ cnulls[attnum[0] - 1] = ' ';
+ cvals[attnum[1] - 1] = TTDUMMY_INFINITY; /* stop_date eq INFINITY */
+ cnulls[attnum[1] - 1] = ' ';
+ }
+ else
+ /* DELETE */
+ {
+ cvals[attnum[1] - 1] = newoff; /* stop_date eq current date */
+ cnulls[attnum[1] - 1] = ' ';
+ }
+
+ /* if there is no plan ... */
+ if (splan == NULL)
+ {
+ SPIPlanPtr pplan;
+ Oid *ctypes;
+ char *query;
+
+ /* allocate space in preparation */
+ ctypes = (Oid *) palloc(natts * sizeof(Oid));
+ query = (char *) palloc(100 + 16 * natts);
+
+ /*
+ * Construct query: INSERT INTO _relation_ VALUES ($1, ...)
+ */
+ sprintf(query, "INSERT INTO %s VALUES (", relname);
+ for (i = 1; i <= natts; i++)
+ {
+ sprintf(query + strlen(query), "$%d%s",
+ i, (i < natts) ? ", " : ")");
+ ctypes[i - 1] = SPI_gettypeid(tupdesc, i);
+ }
+
+ /* Prepare plan for query */
+ pplan = SPI_prepare(query, natts, ctypes);
+ if (pplan == NULL)
+ elog(ERROR, "ttdummy (%s): SPI_prepare returned %s", relname, SPI_result_code_string(SPI_result));
+
+ if (SPI_keepplan(pplan))
+ elog(ERROR, "ttdummy (%s): SPI_keepplan failed", relname);
+
+ splan = pplan;
+ }
+
+ ret = SPI_execp(splan, cvals, cnulls, 0);
+
+ if (ret < 0)
+ elog(ERROR, "ttdummy (%s): SPI_execp returned %d", relname, ret);
+
+ /* Tuple to return to upper Executor ... */
+ if (newtuple) /* UPDATE */
+ rettuple = SPI_modifytuple(rel, trigtuple, 1, &(attnum[1]), &newoff, NULL);
+ else /* DELETE */
+ rettuple = trigtuple;
+
+ SPI_finish(); /* don't forget say Bye to SPI mgr */
+
+ pfree(relname);
+
+ return PointerGetDatum(rettuple);
+}
+
+PG_FUNCTION_INFO_V1(set_ttdummy);
+
+Datum
+set_ttdummy(PG_FUNCTION_ARGS)
+{
+ int32 on = PG_GETARG_INT32(0);
+
+ if (ttoff) /* OFF currently */
+ {
+ if (on == 0)
+ PG_RETURN_INT32(0);
+
+ /* turn ON */
+ ttoff = false;
+ PG_RETURN_INT32(0);
+ }
+
+ /* ON currently */
+ if (on != 0)
+ PG_RETURN_INT32(1);
+
+ /* turn OFF */
+ ttoff = true;
+
+ PG_RETURN_INT32(1);
+}
+
+
+/*
+ * Type int44 has no real-world use, but the regression tests use it
+ * (under the alias "city_budget"). It's a four-element vector of int4's.
+ */
+
+/*
+ * int44in - converts "num, num, ..." to internal form
+ *
+ * Note: Fills any missing positions with zeroes.
+ */
+PG_FUNCTION_INFO_V1(int44in);
+
+Datum
+int44in(PG_FUNCTION_ARGS)
+{
+ char *input_string = PG_GETARG_CSTRING(0);
+ int32 *result = (int32 *) palloc(4 * sizeof(int32));
+ int i;
+
+ i = sscanf(input_string,
+ "%d, %d, %d, %d",
+ &result[0],
+ &result[1],
+ &result[2],
+ &result[3]);
+ while (i < 4)
+ result[i++] = 0;
+
+ PG_RETURN_POINTER(result);
+}
+
+/*
+ * int44out - converts internal form to "num, num, ..."
+ */
+PG_FUNCTION_INFO_V1(int44out);
+
+Datum
+int44out(PG_FUNCTION_ARGS)
+{
+ int32 *an_array = (int32 *) PG_GETARG_POINTER(0);
+ char *result = (char *) palloc(16 * 4);
+
+ snprintf(result, 16 * 4, "%d,%d,%d,%d",
+ an_array[0],
+ an_array[1],
+ an_array[2],
+ an_array[3]);
+
+ PG_RETURN_CSTRING(result);
+}
+
+PG_FUNCTION_INFO_V1(make_tuple_indirect);
+Datum
+make_tuple_indirect(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
+ HeapTupleData tuple;
+ int ncolumns;
+ Datum *values;
+ bool *nulls;
+
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupdesc;
+
+ HeapTuple newtup;
+
+ int i;
+
+ MemoryContext old_context;
+
+ /* Extract type info from the tuple itself */
+ tupType = HeapTupleHeaderGetTypeId(rec);
+ tupTypmod = HeapTupleHeaderGetTypMod(rec);
+ tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+ ncolumns = tupdesc->natts;
+
+ /* Build a temporary HeapTuple control structure */
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ ItemPointerSetInvalid(&(tuple.t_self));
+ tuple.t_tableOid = InvalidOid;
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(ncolumns * sizeof(Datum));
+ nulls = (bool *) palloc(ncolumns * sizeof(bool));
+
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ old_context = MemoryContextSwitchTo(TopTransactionContext);
+
+ for (i = 0; i < ncolumns; i++)
+ {
+ struct varlena *attr;
+ struct varlena *new_attr;
+ struct varatt_indirect redirect_pointer;
+
+ /* only work on existing, not-null varlenas */
+ if (TupleDescAttr(tupdesc, i)->attisdropped ||
+ nulls[i] ||
+ TupleDescAttr(tupdesc, i)->attlen != -1)
+ continue;
+
+ attr = (struct varlena *) DatumGetPointer(values[i]);
+
+ /* don't recursively indirect */
+ if (VARATT_IS_EXTERNAL_INDIRECT(attr))
+ continue;
+
+ /* copy datum, so it still lives later */
+ if (VARATT_IS_EXTERNAL_ONDISK(attr))
+ attr = detoast_external_attr(attr);
+ else
+ {
+ struct varlena *oldattr = attr;
+
+ attr = palloc0(VARSIZE_ANY(oldattr));
+ memcpy(attr, oldattr, VARSIZE_ANY(oldattr));
+ }
+
+ /* build indirection Datum */
+ new_attr = (struct varlena *) palloc0(INDIRECT_POINTER_SIZE);
+ redirect_pointer.pointer = attr;
+ SET_VARTAG_EXTERNAL(new_attr, VARTAG_INDIRECT);
+ memcpy(VARDATA_EXTERNAL(new_attr), &redirect_pointer,
+ sizeof(redirect_pointer));
+
+ values[i] = PointerGetDatum(new_attr);
+ }
+
+ newtup = heap_form_tuple(tupdesc, values, nulls);
+ pfree(values);
+ pfree(nulls);
+ ReleaseTupleDesc(tupdesc);
+
+ MemoryContextSwitchTo(old_context);
+
+ /*
+ * We intentionally don't use PG_RETURN_HEAPTUPLEHEADER here, because that
+ * would cause the indirect toast pointers to be flattened out of the
+ * tuple immediately, rendering subsequent testing irrelevant. So just
+ * return the HeapTupleHeader pointer as-is. This violates the general
+ * rule that composite Datums shouldn't contain toast pointers, but so
+ * long as the regression test scripts don't insert the result of this
+ * function into a container type (record, array, etc) it should be OK.
+ */
+ PG_RETURN_POINTER(newtup->t_data);
+}
+
+PG_FUNCTION_INFO_V1(regress_setenv);
+
+Datum
+regress_setenv(PG_FUNCTION_ARGS)
+{
+ char *envvar = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ char *envval = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+ if (!superuser())
+ elog(ERROR, "must be superuser to change environment variables");
+
+ if (setenv(envvar, envval, 1) != 0)
+ elog(ERROR, "could not set environment variable: %m");
+
+ PG_RETURN_VOID();
+}
+
+/* Sleep until no process has a given PID. */
+PG_FUNCTION_INFO_V1(wait_pid);
+
+Datum
+wait_pid(PG_FUNCTION_ARGS)
+{
+ int pid = PG_GETARG_INT32(0);
+
+ if (!superuser())
+ elog(ERROR, "must be superuser to check PID liveness");
+
+ while (kill(pid, 0) == 0)
+ {
+ CHECK_FOR_INTERRUPTS();
+ pg_usleep(50000);
+ }
+
+ if (errno != ESRCH)
+ elog(ERROR, "could not check PID %d liveness: %m", pid);
+
+ PG_RETURN_VOID();
+}
+
+static void
+test_atomic_flag(void)
+{
+ pg_atomic_flag flag;
+
+ pg_atomic_init_flag(&flag);
+ EXPECT_TRUE(pg_atomic_unlocked_test_flag(&flag));
+ EXPECT_TRUE(pg_atomic_test_set_flag(&flag));
+ EXPECT_TRUE(!pg_atomic_unlocked_test_flag(&flag));
+ EXPECT_TRUE(!pg_atomic_test_set_flag(&flag));
+ pg_atomic_clear_flag(&flag);
+ EXPECT_TRUE(pg_atomic_unlocked_test_flag(&flag));
+ EXPECT_TRUE(pg_atomic_test_set_flag(&flag));
+ pg_atomic_clear_flag(&flag);
+}
+
+static void
+test_atomic_uint32(void)
+{
+ pg_atomic_uint32 var;
+ uint32 expected;
+ int i;
+
+ pg_atomic_init_u32(&var, 0);
+ EXPECT_EQ_U32(pg_atomic_read_u32(&var), 0);
+ pg_atomic_write_u32(&var, 3);
+ EXPECT_EQ_U32(pg_atomic_read_u32(&var), 3);
+ EXPECT_EQ_U32(pg_atomic_fetch_add_u32(&var, pg_atomic_read_u32(&var) - 2),
+ 3);
+ EXPECT_EQ_U32(pg_atomic_fetch_sub_u32(&var, 1), 4);
+ EXPECT_EQ_U32(pg_atomic_sub_fetch_u32(&var, 3), 0);
+ EXPECT_EQ_U32(pg_atomic_add_fetch_u32(&var, 10), 10);
+ EXPECT_EQ_U32(pg_atomic_exchange_u32(&var, 5), 10);
+ EXPECT_EQ_U32(pg_atomic_exchange_u32(&var, 0), 5);
+
+ /* test around numerical limits */
+ EXPECT_EQ_U32(pg_atomic_fetch_add_u32(&var, INT_MAX), 0);
+ EXPECT_EQ_U32(pg_atomic_fetch_add_u32(&var, INT_MAX), INT_MAX);
+ pg_atomic_fetch_add_u32(&var, 2); /* wrap to 0 */
+ EXPECT_EQ_U32(pg_atomic_fetch_add_u32(&var, PG_INT16_MAX), 0);
+ EXPECT_EQ_U32(pg_atomic_fetch_add_u32(&var, PG_INT16_MAX + 1),
+ PG_INT16_MAX);
+ EXPECT_EQ_U32(pg_atomic_fetch_add_u32(&var, PG_INT16_MIN),
+ 2 * PG_INT16_MAX + 1);
+ EXPECT_EQ_U32(pg_atomic_fetch_add_u32(&var, PG_INT16_MIN - 1),
+ PG_INT16_MAX);
+ pg_atomic_fetch_add_u32(&var, 1); /* top up to UINT_MAX */
+ EXPECT_EQ_U32(pg_atomic_read_u32(&var), UINT_MAX);
+ EXPECT_EQ_U32(pg_atomic_fetch_sub_u32(&var, INT_MAX), UINT_MAX);
+ EXPECT_EQ_U32(pg_atomic_read_u32(&var), (uint32) INT_MAX + 1);
+ EXPECT_EQ_U32(pg_atomic_sub_fetch_u32(&var, INT_MAX), 1);
+ pg_atomic_sub_fetch_u32(&var, 1);
+ expected = PG_INT16_MAX;
+ EXPECT_TRUE(!pg_atomic_compare_exchange_u32(&var, &expected, 1));
+ expected = PG_INT16_MAX + 1;
+ EXPECT_TRUE(!pg_atomic_compare_exchange_u32(&var, &expected, 1));
+ expected = PG_INT16_MIN;
+ EXPECT_TRUE(!pg_atomic_compare_exchange_u32(&var, &expected, 1));
+ expected = PG_INT16_MIN - 1;
+ EXPECT_TRUE(!pg_atomic_compare_exchange_u32(&var, &expected, 1));
+
+ /* fail exchange because of old expected */
+ expected = 10;
+ EXPECT_TRUE(!pg_atomic_compare_exchange_u32(&var, &expected, 1));
+
+ /* CAS is allowed to fail due to interrupts, try a couple of times */
+ for (i = 0; i < 1000; i++)
+ {
+ expected = 0;
+ if (!pg_atomic_compare_exchange_u32(&var, &expected, 1))
+ break;
+ }
+ if (i == 1000)
+ elog(ERROR, "atomic_compare_exchange_u32() never succeeded");
+ EXPECT_EQ_U32(pg_atomic_read_u32(&var), 1);
+ pg_atomic_write_u32(&var, 0);
+
+ /* try setting flagbits */
+ EXPECT_TRUE(!(pg_atomic_fetch_or_u32(&var, 1) & 1));
+ EXPECT_TRUE(pg_atomic_fetch_or_u32(&var, 2) & 1);
+ EXPECT_EQ_U32(pg_atomic_read_u32(&var), 3);
+ /* try clearing flagbits */
+ EXPECT_EQ_U32(pg_atomic_fetch_and_u32(&var, ~2) & 3, 3);
+ EXPECT_EQ_U32(pg_atomic_fetch_and_u32(&var, ~1), 1);
+ /* no bits set anymore */
+ EXPECT_EQ_U32(pg_atomic_fetch_and_u32(&var, ~0), 0);
+}
+
+static void
+test_atomic_uint64(void)
+{
+ pg_atomic_uint64 var;
+ uint64 expected;
+ int i;
+
+ pg_atomic_init_u64(&var, 0);
+ EXPECT_EQ_U64(pg_atomic_read_u64(&var), 0);
+ pg_atomic_write_u64(&var, 3);
+ EXPECT_EQ_U64(pg_atomic_read_u64(&var), 3);
+ EXPECT_EQ_U64(pg_atomic_fetch_add_u64(&var, pg_atomic_read_u64(&var) - 2),
+ 3);
+ EXPECT_EQ_U64(pg_atomic_fetch_sub_u64(&var, 1), 4);
+ EXPECT_EQ_U64(pg_atomic_sub_fetch_u64(&var, 3), 0);
+ EXPECT_EQ_U64(pg_atomic_add_fetch_u64(&var, 10), 10);
+ EXPECT_EQ_U64(pg_atomic_exchange_u64(&var, 5), 10);
+ EXPECT_EQ_U64(pg_atomic_exchange_u64(&var, 0), 5);
+
+ /* fail exchange because of old expected */
+ expected = 10;
+ EXPECT_TRUE(!pg_atomic_compare_exchange_u64(&var, &expected, 1));
+
+ /* CAS is allowed to fail due to interrupts, try a couple of times */
+ for (i = 0; i < 100; i++)
+ {
+ expected = 0;
+ if (!pg_atomic_compare_exchange_u64(&var, &expected, 1))
+ break;
+ }
+ if (i == 100)
+ elog(ERROR, "atomic_compare_exchange_u64() never succeeded");
+ EXPECT_EQ_U64(pg_atomic_read_u64(&var), 1);
+
+ pg_atomic_write_u64(&var, 0);
+
+ /* try setting flagbits */
+ EXPECT_TRUE(!(pg_atomic_fetch_or_u64(&var, 1) & 1));
+ EXPECT_TRUE(pg_atomic_fetch_or_u64(&var, 2) & 1);
+ EXPECT_EQ_U64(pg_atomic_read_u64(&var), 3);
+ /* try clearing flagbits */
+ EXPECT_EQ_U64((pg_atomic_fetch_and_u64(&var, ~2) & 3), 3);
+ EXPECT_EQ_U64(pg_atomic_fetch_and_u64(&var, ~1), 1);
+ /* no bits set anymore */
+ EXPECT_EQ_U64(pg_atomic_fetch_and_u64(&var, ~0), 0);
+}
+
+/*
+ * Perform, fairly minimal, testing of the spinlock implementation.
+ *
+ * It's likely worth expanding these to actually test concurrency etc, but
+ * having some regularly run tests is better than none.
+ */
+static void
+test_spinlock(void)
+{
+ /*
+ * Basic tests for spinlocks, as well as the underlying operations.
+ *
+ * We embed the spinlock in a struct with other members to test that the
+ * spinlock operations don't perform too wide writes.
+ */
+ {
+ struct test_lock_struct
+ {
+ char data_before[4];
+ slock_t lock;
+ char data_after[4];
+ } struct_w_lock;
+
+ memcpy(struct_w_lock.data_before, "abcd", 4);
+ memcpy(struct_w_lock.data_after, "ef12", 4);
+
+ /* test basic operations via the SpinLock* API */
+ SpinLockInit(&struct_w_lock.lock);
+ SpinLockAcquire(&struct_w_lock.lock);
+ SpinLockRelease(&struct_w_lock.lock);
+
+ /* test basic operations via underlying S_* API */
+ S_INIT_LOCK(&struct_w_lock.lock);
+ S_LOCK(&struct_w_lock.lock);
+ S_UNLOCK(&struct_w_lock.lock);
+
+ /* and that "contended" acquisition works */
+ s_lock(&struct_w_lock.lock, "testfile", 17, "testfunc");
+ S_UNLOCK(&struct_w_lock.lock);
+
+ /*
+ * Check, using TAS directly, that a single spin cycle doesn't block
+ * when acquiring an already acquired lock.
+ */
+#ifdef TAS
+ S_LOCK(&struct_w_lock.lock);
+
+ if (!TAS(&struct_w_lock.lock))
+ elog(ERROR, "acquired already held spinlock");
+
+#ifdef TAS_SPIN
+ if (!TAS_SPIN(&struct_w_lock.lock))
+ elog(ERROR, "acquired already held spinlock");
+#endif /* defined(TAS_SPIN) */
+
+ S_UNLOCK(&struct_w_lock.lock);
+#endif /* defined(TAS) */
+
+ /*
+ * Verify that after all of this the non-lock contents are still
+ * correct.
+ */
+ if (memcmp(struct_w_lock.data_before, "abcd", 4) != 0)
+ elog(ERROR, "padding before spinlock modified");
+ if (memcmp(struct_w_lock.data_after, "ef12", 4) != 0)
+ elog(ERROR, "padding after spinlock modified");
+ }
+
+ /*
+ * Ensure that allocating more than INT32_MAX emulated spinlocks works.
+ * That's interesting because the spinlock emulation uses a 32bit integer
+ * to map spinlocks onto semaphores. There've been bugs...
+ */
+#ifndef HAVE_SPINLOCKS
+ {
+ /*
+ * Initialize enough spinlocks to advance counter close to wraparound.
+ * It's too expensive to perform acquire/release for each, as those
+ * may be syscalls when the spinlock emulation is used (and even just
+ * atomic TAS would be expensive).
+ */
+ for (uint32 i = 0; i < INT32_MAX - 100000; i++)
+ {
+ slock_t lock;
+
+ SpinLockInit(&lock);
+ }
+
+ for (uint32 i = 0; i < 200000; i++)
+ {
+ slock_t lock;
+
+ SpinLockInit(&lock);
+
+ SpinLockAcquire(&lock);
+ SpinLockRelease(&lock);
+ SpinLockAcquire(&lock);
+ SpinLockRelease(&lock);
+ }
+ }
+#endif
+}
+
+/*
+ * Verify that performing atomic ops inside a spinlock isn't a
+ * problem. Realistically that's only going to be a problem when both
+ * --disable-spinlocks and --disable-atomics are used, but it's cheap enough
+ * to just always test.
+ *
+ * The test works by initializing enough atomics that we'd conflict if there
+ * were an overlap between a spinlock and an atomic by holding a spinlock
+ * while manipulating more than NUM_SPINLOCK_SEMAPHORES atomics.
+ *
+ * NUM_TEST_ATOMICS doesn't really need to be more than
+ * NUM_SPINLOCK_SEMAPHORES, but it seems better to test a bit more
+ * extensively.
+ */
+static void
+test_atomic_spin_nest(void)
+{
+ slock_t lock;
+#define NUM_TEST_ATOMICS (NUM_SPINLOCK_SEMAPHORES + NUM_ATOMICS_SEMAPHORES + 27)
+ pg_atomic_uint32 atomics32[NUM_TEST_ATOMICS];
+ pg_atomic_uint64 atomics64[NUM_TEST_ATOMICS];
+
+ SpinLockInit(&lock);
+
+ for (int i = 0; i < NUM_TEST_ATOMICS; i++)
+ {
+ pg_atomic_init_u32(&atomics32[i], 0);
+ pg_atomic_init_u64(&atomics64[i], 0);
+ }
+
+ /* just so it's not all zeroes */
+ for (int i = 0; i < NUM_TEST_ATOMICS; i++)
+ {
+ EXPECT_EQ_U32(pg_atomic_fetch_add_u32(&atomics32[i], i), 0);
+ EXPECT_EQ_U64(pg_atomic_fetch_add_u64(&atomics64[i], i), 0);
+ }
+
+ /* test whether we can do atomic op with lock held */
+ SpinLockAcquire(&lock);
+ for (int i = 0; i < NUM_TEST_ATOMICS; i++)
+ {
+ EXPECT_EQ_U32(pg_atomic_fetch_sub_u32(&atomics32[i], i), i);
+ EXPECT_EQ_U32(pg_atomic_read_u32(&atomics32[i]), 0);
+ EXPECT_EQ_U64(pg_atomic_fetch_sub_u64(&atomics64[i], i), i);
+ EXPECT_EQ_U64(pg_atomic_read_u64(&atomics64[i]), 0);
+ }
+ SpinLockRelease(&lock);
+}
+#undef NUM_TEST_ATOMICS
+
+PG_FUNCTION_INFO_V1(test_atomic_ops);
+Datum
+test_atomic_ops(PG_FUNCTION_ARGS)
+{
+ test_atomic_flag();
+
+ test_atomic_uint32();
+
+ test_atomic_uint64();
+
+ /*
+ * Arguably this shouldn't be tested as part of this function, but it's
+ * closely enough related that that seems ok for now.
+ */
+ test_spinlock();
+
+ test_atomic_spin_nest();
+
+ PG_RETURN_BOOL(true);
+}
+
+PG_FUNCTION_INFO_V1(test_fdw_handler);
+Datum
+test_fdw_handler(PG_FUNCTION_ARGS)
+{
+ elog(ERROR, "test_fdw_handler is not implemented");
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(test_support_func);
+Datum
+test_support_func(PG_FUNCTION_ARGS)
+{
+ Node *rawreq = (Node *) PG_GETARG_POINTER(0);
+ Node *ret = NULL;
+
+ if (IsA(rawreq, SupportRequestSelectivity))
+ {
+ /*
+ * Assume that the target is int4eq; that's safe as long as we don't
+ * attach this to any other boolean-returning function.
+ */
+ SupportRequestSelectivity *req = (SupportRequestSelectivity *) rawreq;
+ Selectivity s1;
+
+ if (req->is_join)
+ s1 = join_selectivity(req->root, Int4EqualOperator,
+ req->args,
+ req->inputcollid,
+ req->jointype,
+ req->sjinfo);
+ else
+ s1 = restriction_selectivity(req->root, Int4EqualOperator,
+ req->args,
+ req->inputcollid,
+ req->varRelid);
+
+ req->selectivity = s1;
+ ret = (Node *) req;
+ }
+
+ if (IsA(rawreq, SupportRequestCost))
+ {
+ /* Provide some generic estimate */
+ SupportRequestCost *req = (SupportRequestCost *) rawreq;
+
+ req->startup = 0;
+ req->per_tuple = 2 * cpu_operator_cost;
+ ret = (Node *) req;
+ }
+
+ if (IsA(rawreq, SupportRequestRows))
+ {
+ /*
+ * Assume that the target is generate_series_int4; that's safe as long
+ * as we don't attach this to any other set-returning function.
+ */
+ SupportRequestRows *req = (SupportRequestRows *) rawreq;
+
+ if (req->node && IsA(req->node, FuncExpr)) /* be paranoid */
+ {
+ List *args = ((FuncExpr *) req->node)->args;
+ Node *arg1 = linitial(args);
+ Node *arg2 = lsecond(args);
+
+ if (IsA(arg1, Const) &&
+ !((Const *) arg1)->constisnull &&
+ IsA(arg2, Const) &&
+ !((Const *) arg2)->constisnull)
+ {
+ int32 val1 = DatumGetInt32(((Const *) arg1)->constvalue);
+ int32 val2 = DatumGetInt32(((Const *) arg2)->constvalue);
+
+ req->rows = val2 - val1 + 1;
+ ret = (Node *) req;
+ }
+ }
+ }
+
+ PG_RETURN_POINTER(ret);
+}
+
+PG_FUNCTION_INFO_V1(test_opclass_options_func);
+Datum
+test_opclass_options_func(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+/*
+ * Call an encoding conversion or verification function.
+ *
+ * Arguments:
+ * string bytea -- string to convert
+ * src_enc name -- source encoding
+ * dest_enc name -- destination encoding
+ * noError bool -- if set, don't ereport() on invalid or untranslatable
+ * input
+ *
+ * Result is a tuple with two attributes:
+ * int4 -- number of input bytes successfully converted
+ * bytea -- converted string
+ */
+PG_FUNCTION_INFO_V1(test_enc_conversion);
+Datum
+test_enc_conversion(PG_FUNCTION_ARGS)
+{
+ bytea *string = PG_GETARG_BYTEA_PP(0);
+ char *src_encoding_name = NameStr(*PG_GETARG_NAME(1));
+ int src_encoding = pg_char_to_encoding(src_encoding_name);
+ char *dest_encoding_name = NameStr(*PG_GETARG_NAME(2));
+ int dest_encoding = pg_char_to_encoding(dest_encoding_name);
+ bool noError = PG_GETARG_BOOL(3);
+ TupleDesc tupdesc;
+ char *src;
+ char *dst;
+ bytea *retval;
+ Size srclen;
+ Size dstsize;
+ Oid proc;
+ int convertedbytes;
+ int dstlen;
+ Datum values[2];
+ bool nulls[2];
+ HeapTuple tuple;
+
+ if (src_encoding < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid source encoding name \"%s\"",
+ src_encoding_name)));
+ if (dest_encoding < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid destination encoding name \"%s\"",
+ dest_encoding_name)));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ tupdesc = BlessTupleDesc(tupdesc);
+
+ srclen = VARSIZE_ANY_EXHDR(string);
+ src = VARDATA_ANY(string);
+
+ if (src_encoding == dest_encoding)
+ {
+ /* just check that the source string is valid */
+ int oklen;
+
+ oklen = pg_encoding_verifymbstr(src_encoding, src, srclen);
+
+ if (oklen == srclen)
+ {
+ convertedbytes = oklen;
+ retval = string;
+ }
+ else if (!noError)
+ {
+ report_invalid_encoding(src_encoding, src + oklen, srclen - oklen);
+ }
+ else
+ {
+ /*
+ * build bytea data type structure.
+ */
+ Assert(oklen < srclen);
+ convertedbytes = oklen;
+ retval = (bytea *) palloc(oklen + VARHDRSZ);
+ SET_VARSIZE(retval, oklen + VARHDRSZ);
+ memcpy(VARDATA(retval), src, oklen);
+ }
+ }
+ else
+ {
+ proc = FindDefaultConversionProc(src_encoding, dest_encoding);
+ if (!OidIsValid(proc))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("default conversion function for encoding \"%s\" to \"%s\" does not exist",
+ pg_encoding_to_char(src_encoding),
+ pg_encoding_to_char(dest_encoding))));
+
+ if (srclen >= (MaxAllocSize / (Size) MAX_CONVERSION_GROWTH))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("out of memory"),
+ errdetail("String of %d bytes is too long for encoding conversion.",
+ (int) srclen)));
+
+ dstsize = (Size) srclen * MAX_CONVERSION_GROWTH + 1;
+ dst = MemoryContextAlloc(CurrentMemoryContext, dstsize);
+
+ /* perform conversion */
+ convertedbytes = pg_do_encoding_conversion_buf(proc,
+ src_encoding,
+ dest_encoding,
+ (unsigned char *) src, srclen,
+ (unsigned char *) dst, dstsize,
+ noError);
+ dstlen = strlen(dst);
+
+ /*
+ * build bytea data type structure.
+ */
+ retval = (bytea *) palloc(dstlen + VARHDRSZ);
+ SET_VARSIZE(retval, dstlen + VARHDRSZ);
+ memcpy(VARDATA(retval), dst, dstlen);
+
+ pfree(dst);
+ }
+
+ MemSet(nulls, 0, sizeof(nulls));
+ values[0] = Int32GetDatum(convertedbytes);
+ values[1] = PointerGetDatum(retval);
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+
+ PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
+}
+
+/* Provide SQL access to IsBinaryCoercible() */
+PG_FUNCTION_INFO_V1(binary_coercible);
+Datum
+binary_coercible(PG_FUNCTION_ARGS)
+{
+ Oid srctype = PG_GETARG_OID(0);
+ Oid targettype = PG_GETARG_OID(1);
+
+ PG_RETURN_BOOL(IsBinaryCoercible(srctype, targettype));
+}
diff --git a/src/test/regress/regressplans.sh b/src/test/regress/regressplans.sh
new file mode 100755
index 0000000..31e7876
--- /dev/null
+++ b/src/test/regress/regressplans.sh
@@ -0,0 +1,101 @@
+#! /bin/sh
+
+# This script runs the Postgres regression tests with all useful combinations
+# of the backend options that disable various query plan types. If the
+# results are not all the same, it may indicate a bug in a particular
+# plan type, or perhaps just a regression test whose results aren't fully
+# determinate (eg, due to lack of an ORDER BY keyword).
+#
+# Run this in the src/test/regress directory, after doing the usual setup
+# for a regular regression test, ie, "make clean all" (you should be ready
+# to do "make runtest").
+#
+# The backend option switches that we use here are:
+# -fs disable sequential scans
+# -fi disable index scans
+# -fn disable nestloop joins
+# -fm disable merge joins
+# -fh disable hash joins
+# Only mergejoin and hashjoin are really guaranteed to turn off; the others
+# just bias the optimizer's cost calculations heavily against that choice.
+# There's no point in trying to turn off both scan types or all three join
+# types simultaneously; ergo, we have 3*7 = 21 interesting combinations.
+#
+# Note that this will take *more than* 21 times longer than a regular
+# regression test, since we are preventing the system from using the most
+# efficient available query plans! Have patience.
+
+
+# Select make to use --- default 'make', can be overridden by env var
+MAKE="${MAKE:-make}"
+
+# If PGOPTIONS is already defined, we'll add the -f switches to it.
+PGOPTIONS="${PGOPTIONS:-}"
+
+mkdir planregress
+
+PGOPTIONS="$PGOPTIONS " $MAKE runtest
+mv -f regression.out planregress/out.normal
+mv -f regression.diffs planregress/diffs.normal
+PGOPTIONS="$PGOPTIONS -fh" $MAKE runtest
+mv -f regression.out planregress/out.h
+mv -f regression.diffs planregress/diffs.h
+PGOPTIONS="$PGOPTIONS -fm " $MAKE runtest
+mv -f regression.out planregress/out.m
+mv -f regression.diffs planregress/diffs.m
+PGOPTIONS="$PGOPTIONS -fm -fh" $MAKE runtest
+mv -f regression.out planregress/out.mh
+mv -f regression.diffs planregress/diffs.mh
+PGOPTIONS="$PGOPTIONS -fn " $MAKE runtest
+mv -f regression.out planregress/out.n
+mv -f regression.diffs planregress/diffs.n
+PGOPTIONS="$PGOPTIONS -fn -fh" $MAKE runtest
+mv -f regression.out planregress/out.nh
+mv -f regression.diffs planregress/diffs.nh
+PGOPTIONS="$PGOPTIONS -fn -fm " $MAKE runtest
+mv -f regression.out planregress/out.nm
+mv -f regression.diffs planregress/diffs.nm
+PGOPTIONS="$PGOPTIONS -fi " $MAKE runtest
+mv -f regression.out planregress/out.i
+mv -f regression.diffs planregress/diffs.i
+PGOPTIONS="$PGOPTIONS -fi -fh" $MAKE runtest
+mv -f regression.out planregress/out.ih
+mv -f regression.diffs planregress/diffs.ih
+PGOPTIONS="$PGOPTIONS -fi -fm " $MAKE runtest
+mv -f regression.out planregress/out.im
+mv -f regression.diffs planregress/diffs.im
+PGOPTIONS="$PGOPTIONS -fi -fm -fh" $MAKE runtest
+mv -f regression.out planregress/out.imh
+mv -f regression.diffs planregress/diffs.imh
+PGOPTIONS="$PGOPTIONS -fi -fn " $MAKE runtest
+mv -f regression.out planregress/out.in
+mv -f regression.diffs planregress/diffs.in
+PGOPTIONS="$PGOPTIONS -fi -fn -fh" $MAKE runtest
+mv -f regression.out planregress/out.inh
+mv -f regression.diffs planregress/diffs.inh
+PGOPTIONS="$PGOPTIONS -fi -fn -fm " $MAKE runtest
+mv -f regression.out planregress/out.inm
+mv -f regression.diffs planregress/diffs.inm
+PGOPTIONS="$PGOPTIONS -fs " $MAKE runtest
+mv -f regression.out planregress/out.s
+mv -f regression.diffs planregress/diffs.s
+PGOPTIONS="$PGOPTIONS -fs -fh" $MAKE runtest
+mv -f regression.out planregress/out.sh
+mv -f regression.diffs planregress/diffs.sh
+PGOPTIONS="$PGOPTIONS -fs -fm " $MAKE runtest
+mv -f regression.out planregress/out.sm
+mv -f regression.diffs planregress/diffs.sm
+PGOPTIONS="$PGOPTIONS -fs -fm -fh" $MAKE runtest
+mv -f regression.out planregress/out.smh
+mv -f regression.diffs planregress/diffs.smh
+PGOPTIONS="$PGOPTIONS -fs -fn " $MAKE runtest
+mv -f regression.out planregress/out.sn
+mv -f regression.diffs planregress/diffs.sn
+PGOPTIONS="$PGOPTIONS -fs -fn -fh" $MAKE runtest
+mv -f regression.out planregress/out.snh
+mv -f regression.diffs planregress/diffs.snh
+PGOPTIONS="$PGOPTIONS -fs -fn -fm " $MAKE runtest
+mv -f regression.out planregress/out.snm
+mv -f regression.diffs planregress/diffs.snm
+
+exit 0
diff --git a/src/test/regress/resultmap b/src/test/regress/resultmap
new file mode 100644
index 0000000..c766d03
--- /dev/null
+++ b/src/test/regress/resultmap
@@ -0,0 +1,3 @@
+float4:out:.*-.*-cygwin.*=float4-misrounded-input.out
+float4:out:.*-.*-mingw.*=float4-misrounded-input.out
+float4:out:hppa.*-hp-hpux10.*=float4-misrounded-input.out
diff --git a/src/test/regress/sql/.gitignore b/src/test/regress/sql/.gitignore
new file mode 100644
index 0000000..fe14af6
--- /dev/null
+++ b/src/test/regress/sql/.gitignore
@@ -0,0 +1,9 @@
+/constraints.sql
+/copy.sql
+/create_function_0.sql
+/create_function_1.sql
+/create_function_2.sql
+/largeobject.sql
+/misc.sql
+/security_label.sql
+/tablespace.sql
diff --git a/src/test/regress/sql/advisory_lock.sql b/src/test/regress/sql/advisory_lock.sql
new file mode 100644
index 0000000..57c47c0
--- /dev/null
+++ b/src/test/regress/sql/advisory_lock.sql
@@ -0,0 +1,146 @@
+--
+-- ADVISORY LOCKS
+--
+
+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 locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ ORDER BY classid, objid, objsubid;
+
+
+-- pg_advisory_unlock_all() shouldn't release xact locks
+SELECT pg_advisory_unlock_all();
+
+SELECT count(*) FROM pg_locks WHERE locktype = 'advisory';
+
+
+-- 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 count(*) FROM pg_locks WHERE locktype = 'advisory';
+
+
+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 locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ 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 locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ 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 count(*) FROM pg_locks WHERE locktype = 'advisory';
+
+
+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 locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ 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 locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ ORDER BY classid, objid, objsubid;
+
+
+-- releasing all session locks
+SELECT pg_advisory_unlock_all();
+
+SELECT count(*) FROM pg_locks WHERE locktype = 'advisory';
+
+
+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 locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ ORDER BY classid, objid, objsubid;
+
+COMMIT;
+
+SELECT count(*) FROM pg_locks WHERE locktype = 'advisory';
+
+-- 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 locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ 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 count(*) FROM pg_locks WHERE locktype = 'advisory';
+
+-- .. 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 locktype, classid, objid, objsubid, mode, granted
+ FROM pg_locks WHERE locktype = 'advisory'
+ ORDER BY classid, objid, objsubid;
+
+SELECT pg_advisory_unlock_all();
+
+SELECT count(*) FROM pg_locks WHERE locktype = 'advisory';
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
new file mode 100644
index 0000000..7cf8646
--- /dev/null
+++ b/src/test/regress/sql/aggregates.sql
@@ -0,0 +1,1247 @@
+--
+-- AGGREGATES
+--
+
+-- avoid bit-exact output here because operations may not be bit-exact.
+SET extra_float_digits = 0;
+
+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;
+select f1, (select min(unique1) from tenk1 where unique1 > f1) AS gt
+ from int4_tbl;
+
+-- 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
+explain (costs off)
+ select distinct min(f1), max(f1) from minmaxtest;
+select distinct min(f1), max(f1) from minmaxtest;
+
+drop table minmaxtest cascade;
+
+-- 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
+explain (costs off) select *
+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
+explain (costs off) select t1.*,t2.x,t2.z
+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 aggregate my_sum(int4)
+(
+ 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;
+
+-- 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;
+
+-- 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;
+
+-- 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;
+
+-- 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/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
new file mode 100644
index 0000000..8c5d0e5
--- /dev/null
+++ b/src/test/regress/sql/alter_generic.sql
@@ -0,0 +1,605 @@
+--
+-- Test for ALTER some_object {RENAME TO, OWNER TO, SET SCHEMA}
+--
+
+-- 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;
+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)
+
+RESET SESSION AUTHORIZATION;
+
+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;
+
+--
+-- 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%';
+SELECT srvname FROM pg_foreign_server WHERE srvname like 'alt_fserv%';
+
+--
+-- 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)
+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
+
+RESET SESSION AUTHORIZATION;
+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;
+
+--
+-- 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)
+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)
+
+RESET SESSION AUTHORIZATION;
+
+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;
+
+-- 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;
+
+-- 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;
+
+-- 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;
+
+-- 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;
+
+-- 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;
+
+-- 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;
+
+DROP SCHEMA alt_nsp1 CASCADE;
+DROP SCHEMA alt_nsp2 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/alter_operator.sql b/src/test/regress/sql/alter_operator.sql
new file mode 100644
index 0000000..fd40370
--- /dev/null
+++ b/src/test/regress/sql/alter_operator.sql
@@ -0,0 +1,100 @@
+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
+);
+
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+ objid = '===(bool,bool)'::regoperator
+ORDER BY 1;
+
+--
+-- Reset and set params
+--
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = NONE);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+ objid = '===(bool,bool)'::regoperator
+ORDER BY 1;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+ objid = '===(bool,bool)'::regoperator
+ORDER BY 1;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE, JOIN = NONE);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+ objid = '===(bool,bool)'::regoperator
+ORDER BY 1;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = customcontsel, JOIN = contjoinsel);
+
+SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+
+SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
+FROM pg_depend
+WHERE classid = 'pg_operator'::regclass AND
+ objid = '===(bool,bool)'::regoperator
+ORDER BY 1;
+
+--
+-- Test invalid options.
+--
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = non_existent_func);
+ALTER OPERATOR === (boolean, boolean) SET (JOIN = non_existent_func);
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
+ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
+
+-- invalid: non-lowercase quoted identifiers
+ALTER OPERATOR & (bit, bit) SET ("Restrict" = _int_contsel, "Join" = _int_contjoinsel);
+
+--
+-- Test permission check. Must be owner to ALTER OPERATOR.
+--
+CREATE USER regress_alter_op_user;
+SET SESSION AUTHORIZATION regress_alter_op_user;
+
+ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
+
+-- Clean up
+RESET SESSION AUTHORIZATION;
+DROP USER regress_alter_op_user;
+DROP OPERATOR === (boolean, boolean);
+DROP FUNCTION customcontsel(internal, oid, internal, integer);
+DROP FUNCTION alter_op_test_fn(boolean, boolean);
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
new file mode 100644
index 0000000..6ef9dd4
--- /dev/null
+++ b/src/test/regress/sql/alter_table.sql
@@ -0,0 +1,3030 @@
+--
+-- 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;
+
+-- fails with incorrect object type
+CREATE VIEW at_v1 AS SELECT 1 as a;
+ALTER TABLE at_v1 ALTER COLUMN a SET STATISTICS 0;
+DROP VIEW at_v1;
+
+--
+-- 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;
+
+-- rename statements with mismatching statement and object types
+CREATE TABLE alter_idx_rename_test (a INT);
+CREATE INDEX alter_idx_rename_test_idx ON alter_idx_rename_test (a);
+CREATE TABLE alter_idx_rename_test_parted (a INT) PARTITION BY LIST (a);
+CREATE INDEX alter_idx_rename_test_parted_idx ON alter_idx_rename_test_parted (a);
+BEGIN;
+ALTER INDEX alter_idx_rename_test RENAME TO alter_idx_rename_test_2;
+ALTER INDEX alter_idx_rename_test_parted RENAME TO alter_idx_rename_test_parted_2;
+SELECT relation::regclass, mode FROM pg_locks
+WHERE pid = pg_backend_pid() AND locktype = 'relation'
+ AND relation::regclass::text LIKE 'alter\_idx%'
+ORDER BY relation::regclass::text COLLATE "C";
+COMMIT;
+BEGIN;
+ALTER INDEX alter_idx_rename_test_idx RENAME TO alter_idx_rename_test_idx_2;
+ALTER INDEX alter_idx_rename_test_parted_idx RENAME TO alter_idx_rename_test_parted_idx_2;
+SELECT relation::regclass, mode FROM pg_locks
+WHERE pid = pg_backend_pid() AND locktype = 'relation'
+ AND relation::regclass::text LIKE 'alter\_idx%'
+ORDER BY relation::regclass::text COLLATE "C";
+COMMIT;
+BEGIN;
+ALTER TABLE alter_idx_rename_test_idx_2 RENAME TO alter_idx_rename_test_idx_3;
+ALTER TABLE alter_idx_rename_test_parted_idx_2 RENAME TO alter_idx_rename_test_parted_idx_3;
+SELECT relation::regclass, mode FROM pg_locks
+WHERE pid = pg_backend_pid() AND locktype = 'relation'
+ AND relation::regclass::text LIKE 'alter\_idx%'
+ORDER BY relation::regclass::text COLLATE "C";
+COMMIT;
+DROP TABLE alter_idx_rename_test_2;
+
+-- renaming views
+CREATE VIEW attmp_view (unique1) AS SELECT unique1 FROM tenk1;
+ALTER TABLE attmp_view RENAME TO attmp_view_new;
+
+SET ROLE regress_alter_table_user1;
+ALTER VIEW attmp_view_new RENAME TO fail; -- permission denied
+RESET ROLE;
+
+-- hack to ensure we get an indexscan here
+set enable_seqscan to off;
+set enable_bitmapscan to off;
+-- 5 values, sorted
+SELECT unique1 FROM tenk1 WHERE unique1 < 5;
+reset enable_seqscan;
+reset enable_bitmapscan;
+
+DROP VIEW attmp_view_new;
+-- toast-like relation name
+alter table stud_emp rename to pg_toast_stud_emp;
+alter table pg_toast_stud_emp rename to stud_emp;
+
+-- 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+test2<test3*4);
+-- should fail
+insert into atacc1 (test,test2,test3) values (4,4,2);
+-- should succeed
+insert into atacc1 (test,test2,test3) values (4,4,5);
+drop table atacc1;
+
+-- lets do some naming tests
+create table atacc1 (test int check (test>3), 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;
+-- 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;
+
+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.
+alter table p2 drop column name;
+-- should be rejected since its inherited
+alter table gc1 drop column name;
+-- 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;
+-- 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;
+
+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);
+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;
+
+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;
+
+insert into anothertab (atcol1, atcol2) values (45, null); -- fails
+insert into anothertab (atcol1, atcol2) values (default, null);
+
+select * from anothertab;
+
+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;
+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;
+
+drop table anothertab;
+
+-- Test index handling in alter table column type (cf. bugs #15835, #15865)
+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;
+
+-- 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;
+
+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;
+
+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 :-(
+
+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 table test_storage (a text);
+alter table test_storage alter a set storage plain;
+alter table test_storage add b int default 0; -- rewrite table to remove its TOAST table
+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;
+\d+ test_storage
+\d+ test_storage_idx
+
+-- 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)
+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;
+
+-- 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;
+
+-- check adding a column not iself 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
+ where transactionid = pg_current_xact_id()::xid)
+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;
+
+begin; alter table alterlock alter column f2 set default 'x';
+select * from my_locks order by 1;
+rollback;
+
+begin;
+create trigger ttdummy
+ before delete or update on alterlock
+ for each row
+ execute procedure
+ ttdummy (1, 1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+select * from my_locks order by 1;
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+rollback;
+
+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
+ where transactionid = pg_current_xact_id()::xid)
+and locktype = 'relation'
+and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
+and c.relname = 'my_locks'
+group by c.relname;
+
+-- raise exception
+alter table my_locks set (autovacuum_enabled = false);
+alter view my_locks set (autovacuum_enabled = false);
+alter table my_locks reset (autovacuum_enabled);
+alter view my_locks reset (autovacuum_enabled);
+
+begin;
+alter view my_locks set (security_barrier=off);
+select * from my_locks order by 1;
+alter view my_locks reset (security_barrier);
+rollback;
+
+-- this test intentionally applies the ALTER TABLE command against a view, but
+-- uses a view option so we expect this to succeed. This form of SQL is
+-- accepted for historical reasons, as shown in the docs for ALTER VIEW
+begin;
+alter table my_locks set (security_barrier=off);
+select * from my_locks order by 1;
+alter table my_locks reset (security_barrier);
+rollback;
+
+-- cleanup
+drop table alterlock2;
+drop table alterlock;
+drop view my_locks;
+drop type lockmodes;
+
+--
+-- alter function
+--
+create function test_strict(text) returns text as
+ 'select coalesce($1, ''got passed a null'');'
+ language sql returns null on null input;
+select test_strict(NULL);
+alter function test_strict(text) called on null input;
+select test_strict(NULL);
+
+create function non_strict(text) returns text as
+ 'select coalesce($1, ''got passed a null'');'
+ language sql called on null input;
+select non_strict(NULL);
+alter function non_strict(text) returns null on null input;
+select non_strict(NULL);
+
+--
+-- alter object set schema
+--
+
+create schema alter1;
+create schema alter2;
+
+create table alter1.t1(f1 serial primary key, f2 int check (f2 > 0));
+
+create view alter1.v1 as select * from alter1.t1;
+
+create function alter1.plus1(int) returns int as 'select $1+1' language sql;
+
+create domain alter1.posint integer check (value > 0);
+
+create type alter1.ctype as (f1 int, f2 text);
+
+create function alter1.same(alter1.ctype, alter1.ctype) returns boolean language sql
+as 'select $1.f1 is not distinct from $2.f1 and $1.f2 is not distinct from $2.f2';
+
+create operator alter1.=(procedure = alter1.same, leftarg = alter1.ctype, rightarg = alter1.ctype);
+
+create operator class alter1.ctype_hash_ops default for type alter1.ctype using hash as
+ operator 1 alter1.=(alter1.ctype, alter1.ctype);
+
+create conversion alter1.latin1_to_utf8 for 'latin1' to 'utf8' from iso8859_1_to_utf8;
+
+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);
+create text search template alter1.tmpl(init = dsimple_init, lexize = dsimple_lexize);
+create text search dictionary alter1.dict(template = alter1.tmpl);
+
+insert into alter1.t1(f2) values(11);
+insert into alter1.t1(f2) values(12);
+
+alter table alter1.t1 set schema alter1; -- no-op, same schema
+alter table alter1.t1 set schema alter2;
+alter table alter1.v1 set schema alter2;
+alter function alter1.plus1(int) set schema alter2;
+alter domain alter1.posint set schema alter2;
+alter operator class alter1.ctype_hash_ops using hash set schema alter2;
+alter operator family alter1.ctype_hash_ops using hash set schema alter2;
+alter operator alter1.=(alter1.ctype, alter1.ctype) set schema alter2;
+alter function alter1.same(alter1.ctype, alter1.ctype) set schema alter2;
+alter type alter1.ctype set schema alter1; -- no-op, same schema
+alter type alter1.ctype set schema alter2;
+alter conversion alter1.latin1_to_utf8 set schema alter2;
+alter text search parser alter1.prs set schema alter2;
+alter text search configuration alter1.cfg set schema alter2;
+alter text search template alter1.tmpl set schema alter2;
+alter text search dictionary alter1.dict set schema alter2;
+
+-- this should succeed because nothing is left in alter1
+drop schema alter1;
+
+insert into alter2.t1(f2) values(13);
+insert into alter2.t1(f2) values(14);
+
+select * from alter2.t1;
+
+select * from alter2.v1;
+
+select alter2.plus1(41);
+
+-- clean up
+drop schema alter2 cascade;
+
+--
+-- composite types
+--
+
+CREATE TYPE test_type AS (a int);
+\d test_type
+
+ALTER TYPE nosuchtype ADD ATTRIBUTE b text; -- fails
+
+ALTER TYPE test_type ADD ATTRIBUTE b text;
+\d test_type
+
+ALTER TYPE test_type ADD ATTRIBUTE b text; -- fails
+
+ALTER TYPE test_type ALTER ATTRIBUTE b SET DATA TYPE varchar;
+\d test_type
+
+ALTER TYPE test_type ALTER ATTRIBUTE b SET DATA TYPE integer;
+\d test_type
+
+ALTER TYPE test_type DROP ATTRIBUTE b;
+\d test_type
+
+ALTER TYPE test_type DROP ATTRIBUTE c; -- fails
+
+ALTER TYPE test_type DROP ATTRIBUTE IF EXISTS c;
+
+ALTER TYPE test_type DROP ATTRIBUTE a, ADD ATTRIBUTE d boolean;
+\d test_type
+
+ALTER TYPE test_type RENAME ATTRIBUTE a TO aa;
+ALTER TYPE test_type RENAME ATTRIBUTE d TO dd;
+\d test_type
+
+DROP TYPE test_type;
+
+CREATE TYPE test_type1 AS (a int, b text);
+CREATE TABLE test_tbl1 (x int, y test_type1);
+ALTER TYPE test_type1 ALTER ATTRIBUTE b TYPE varchar; -- fails
+
+CREATE TYPE test_type2 AS (a int, b text);
+CREATE TABLE test_tbl2 OF test_type2;
+CREATE TABLE test_tbl2_subclass () INHERITS (test_tbl2);
+\d test_type2
+\d test_tbl2
+
+ALTER TYPE test_type2 ADD ATTRIBUTE c text; -- fails
+ALTER TYPE test_type2 ADD ATTRIBUTE c text CASCADE;
+\d test_type2
+\d test_tbl2
+
+ALTER TYPE test_type2 ALTER ATTRIBUTE b TYPE varchar; -- fails
+ALTER TYPE test_type2 ALTER ATTRIBUTE b TYPE varchar CASCADE;
+\d test_type2
+\d test_tbl2
+
+ALTER TYPE test_type2 DROP ATTRIBUTE b; -- fails
+ALTER TYPE test_type2 DROP ATTRIBUTE b CASCADE;
+\d test_type2
+\d test_tbl2
+
+ALTER TYPE test_type2 RENAME ATTRIBUTE a TO aa; -- fails
+ALTER TYPE test_type2 RENAME ATTRIBUTE a TO aa CASCADE;
+\d test_type2
+\d test_tbl2
+\d test_tbl2_subclass
+
+DROP TABLE test_tbl2_subclass;
+
+CREATE TYPE test_typex AS (a int, b text);
+CREATE TABLE test_tblx (x int, y test_typex check ((y).a > 0));
+ALTER TYPE test_typex DROP ATTRIBUTE a; -- fails
+ALTER TYPE test_typex DROP ATTRIBUTE a CASCADE;
+\d test_tblx
+DROP TABLE test_tblx;
+DROP TYPE test_typex;
+
+-- This test isn't that interesting on its own, but the purpose is to leave
+-- behind a table to test pg_upgrade with. The table has a composite type
+-- column in it, and the composite type has a dropped attribute.
+CREATE TYPE test_type3 AS (a int);
+CREATE TABLE test_tbl3 (c) AS SELECT '(1)'::test_type3;
+ALTER TYPE test_type3 DROP ATTRIBUTE a, ADD ATTRIBUTE b int;
+
+CREATE TYPE test_type_empty AS ();
+DROP TYPE test_type_empty;
+
+--
+-- 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;
+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
+
+-- 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;
+
+--
+-- 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;
+
+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;
+
+-- Checks on creating and manipulation of user defined relations in
+-- pg_catalog.
+
+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;
+
+-- set logged
+CREATE UNLOGGED TABLE unlogged1(f1 SERIAL PRIMARY KEY, f2 TEXT);
+-- check relpersistence of an unlogged table
+SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged1'
+UNION ALL
+SELECT '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 '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;
+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
+ALTER TABLE unlogged2 SET LOGGED; -- fails because a foreign key to an unlogged table exists
+ALTER TABLE unlogged1 SET LOGGED;
+-- check relpersistence of an unlogged table after changing to permanent
+SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged1'
+UNION ALL
+SELECT '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 '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;
+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);
+-- check relpersistence of a permanent table
+SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^logged1'
+UNION ALL
+SELECT '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 '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;
+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;
+-- check relpersistence of a permanent table after changing to unlogged
+SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^logged1'
+UNION ALL
+SELECT '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 '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
+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;
+
+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;
+
+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));
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- 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);
+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";
+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;
+
+-- fails with incorrect object type
+CREATE VIEW at_v1 AS SELECT 1 as a;
+ALTER TABLE at_v1 ATTACH PARTITION dummy default;
+DROP VIEW at_v1;
+
+--
+-- DETACH PARTITION
+--
+
+-- check that the table is partitioned at all
+CREATE TABLE regular_table (a int);
+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;
+
+-- concurrent detach
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE(a);
+CREATE TABLE part_rp PARTITION OF range_parted2 FOR VALUES FROM (0) to (100);
+BEGIN;
+-- doesn't work in a partition block
+ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
+COMMIT;
+CREATE TABLE part_rpd PARTITION OF range_parted2 DEFAULT;
+-- doesn't work if there's a default partition
+ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
+-- doesn't work for the default partition
+ALTER TABLE range_parted2 DETACH PARTITION part_rpd CONCURRENTLY;
+DROP TABLE part_rpd;
+-- works fine
+ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
+\d+ range_parted2
+-- constraint should be created
+\d part_rp
+CREATE TABLE part_rp100 PARTITION OF range_parted2 (CHECK (a>=123 AND a<133 AND a IS NOT NULL)) FOR VALUES FROM (100) to (200);
+ALTER TABLE range_parted2 DETACH PARTITION part_rp100 CONCURRENTLY;
+-- redundant constraint should not be created
+\d part_rp100
+DROP TABLE range_parted2;
+
+-- 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');
+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);
+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);
+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;
+
+
+/* Test case for bug #16242 */
+
+-- We create a parent and child where the child has missing
+-- non-null attribute values, and arrange to pass them through
+-- tuple conversion from the child to the parent tupdesc
+create table bar1 (a integer, b integer not null default 1)
+ partition by range (a);
+create table bar2 (a integer);
+insert into bar2 values (1);
+alter table bar2 add column b integer not null default 1;
+-- (at this point bar2 contains tuple with natts=1)
+alter table bar1 attach partition bar2 default;
+
+-- this works:
+select * from bar1;
+
+-- this exercises tuple conversion:
+create function xtrig()
+ returns trigger language plpgsql
+as $$
+ declare
+ r record;
+ begin
+ for r in select * from old loop
+ raise info 'a=%, b=%', r.a, r.b;
+ end loop;
+ return NULL;
+ end;
+$$;
+create trigger xtrig
+ after update on bar1
+ referencing old table as old
+ for each statement execute procedure xtrig();
+
+update bar1 set a = a + 1;
+
+/* End test case for bug #16242 */
+
+/* Test case for bug #17409 */
+
+create table attbl (p1 int constraint pk_attbl primary key);
+create table atref (c1 int references attbl(p1));
+cluster attbl using pk_attbl;
+alter table attbl alter column p1 set data type bigint;
+alter table atref alter column c1 set data type bigint;
+drop table attbl, atref;
+
+create table attbl (p1 int constraint pk_attbl primary key);
+alter table attbl replica identity using index pk_attbl;
+create table atref (c1 int references attbl(p1));
+alter table attbl alter column p1 set data type bigint;
+alter table atref alter column c1 set data type bigint;
+drop table attbl, atref;
+
+/* End test case for bug #17409 */
+
+-- Test that ALTER TABLE rewrite preserves a clustered index
+-- for normal indexes and indexes on constraints.
+create table alttype_cluster (a int);
+alter table alttype_cluster add primary key (a);
+create index alttype_cluster_ind on alttype_cluster (a);
+alter table alttype_cluster cluster on alttype_cluster_ind;
+-- Normal index remains clustered.
+select indexrelid::regclass, indisclustered from pg_index
+ where indrelid = 'alttype_cluster'::regclass
+ order by indexrelid::regclass::text;
+alter table alttype_cluster alter a type bigint;
+select indexrelid::regclass, indisclustered from pg_index
+ where indrelid = 'alttype_cluster'::regclass
+ order by indexrelid::regclass::text;
+-- Constraint index remains clustered.
+alter table alttype_cluster cluster on alttype_cluster_pkey;
+select indexrelid::regclass, indisclustered from pg_index
+ where indrelid = 'alttype_cluster'::regclass
+ order by indexrelid::regclass::text;
+alter table alttype_cluster alter a type int;
+select indexrelid::regclass, indisclustered from pg_index
+ where indrelid = 'alttype_cluster'::regclass
+ order by indexrelid::regclass::text;
+drop table alttype_cluster;
+
+--
+-- 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/amutils.sql b/src/test/regress/sql/amutils.sql
new file mode 100644
index 0000000..06e7fa1
--- /dev/null
+++ b/src/test/regress/sql/amutils.sql
@@ -0,0 +1,99 @@
+--
+-- Test index AM property-reporting functions
+--
+
+select prop,
+ pg_indexam_has_property(a.oid, prop) as "AM",
+ pg_index_has_property('onek_hundred'::regclass, prop) as "Index",
+ pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as "Column"
+ from pg_am a,
+ unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
+ 'orderable', 'distance_orderable', 'returnable',
+ 'search_array', 'search_nulls',
+ 'clusterable', 'index_scan', 'bitmap_scan',
+ 'backward_scan',
+ 'can_order', 'can_unique', 'can_multi_col',
+ 'can_exclude', 'can_include',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ where a.amname = 'btree'
+ order by ord;
+
+select prop,
+ pg_indexam_has_property(a.oid, prop) as "AM",
+ pg_index_has_property('gcircleind'::regclass, prop) as "Index",
+ pg_index_column_has_property('gcircleind'::regclass, 1, prop) as "Column"
+ from pg_am a,
+ unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
+ 'orderable', 'distance_orderable', 'returnable',
+ 'search_array', 'search_nulls',
+ 'clusterable', 'index_scan', 'bitmap_scan',
+ 'backward_scan',
+ 'can_order', 'can_unique', 'can_multi_col',
+ 'can_exclude', 'can_include',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ where a.amname = 'gist'
+ order by ord;
+
+select prop,
+ pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree,
+ pg_index_column_has_property('hash_i4_index'::regclass, 1, prop) as hash,
+ pg_index_column_has_property('gcircleind'::regclass, 1, prop) as gist,
+ pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist_radix,
+ pg_index_column_has_property('sp_quad_ind'::regclass, 1, prop) as spgist_quad,
+ pg_index_column_has_property('botharrayidx'::regclass, 1, prop) as gin,
+ pg_index_column_has_property('brinidx'::regclass, 1, prop) as brin
+ from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
+ 'orderable', 'distance_orderable', 'returnable',
+ 'search_array', 'search_nulls',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ order by ord;
+
+select prop,
+ pg_index_has_property('onek_hundred'::regclass, prop) as btree,
+ pg_index_has_property('hash_i4_index'::regclass, prop) as hash,
+ pg_index_has_property('gcircleind'::regclass, prop) as gist,
+ pg_index_has_property('sp_radix_ind'::regclass, prop) as spgist,
+ pg_index_has_property('botharrayidx'::regclass, prop) as gin,
+ pg_index_has_property('brinidx'::regclass, prop) as brin
+ from unnest(array['clusterable', 'index_scan', 'bitmap_scan',
+ 'backward_scan',
+ 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ order by ord;
+
+select amname, prop, pg_indexam_has_property(a.oid, prop) as p
+ from pg_am a,
+ unnest(array['can_order', 'can_unique', 'can_multi_col',
+ 'can_exclude', 'can_include', 'bogus']::text[])
+ with ordinality as u(prop,ord)
+ where amtype = 'i'
+ order by amname, ord;
+
+--
+-- additional checks for pg_index_column_has_property
+--
+CREATE TEMP TABLE foo (f1 int, f2 int, f3 int, f4 int);
+
+CREATE INDEX fooindex ON foo (f1 desc, f2 asc, f3 nulls first, f4 nulls last);
+
+select col, prop, pg_index_column_has_property(o, col, prop)
+ from (values ('fooindex'::regclass)) v1(o),
+ (values (1,'orderable'),(2,'asc'),(3,'desc'),
+ (4,'nulls_first'),(5,'nulls_last'),
+ (6, 'bogus')) v2(idx,prop),
+ generate_series(1,4) col
+ order by col, idx;
+
+CREATE INDEX foocover ON foo (f1) INCLUDE (f2,f3);
+
+select col, prop, pg_index_column_has_property(o, col, prop)
+ from (values ('foocover'::regclass)) v1(o),
+ (values (1,'orderable'),(2,'asc'),(3,'desc'),
+ (4,'nulls_first'),(5,'nulls_last'),
+ (6,'distance_orderable'),(7,'returnable'),
+ (8, 'bogus')) v2(idx,prop),
+ generate_series(1,3) col
+ order by col, idx;
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
new file mode 100644
index 0000000..5eedc4c
--- /dev/null
+++ b/src/test/regress/sql/arrays.sql
@@ -0,0 +1,740 @@
+--
+-- ARRAYS
+--
+
+CREATE TABLE arrtest (
+ a int2[],
+ b int4[][][],
+ c name[],
+ d text[][],
+ e float8[],
+ f char(5)[],
+ g varchar(5)[]
+);
+
+--
+-- 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
+
+-- 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 *;
+
+--
+-- 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
+
+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/async.sql b/src/test/regress/sql/async.sql
new file mode 100644
index 0000000..40f6e01
--- /dev/null
+++ b/src/test/regress/sql/async.sql
@@ -0,0 +1,23 @@
+--
+-- ASYNC
+--
+
+--Should work. Send a valid message via a valid channel name
+SELECT pg_notify('notify_async1','sample message1');
+SELECT pg_notify('notify_async1','');
+SELECT pg_notify('notify_async1',NULL);
+
+-- Should fail. Send a valid message via an invalid channel name
+SELECT pg_notify('','sample message1');
+SELECT pg_notify(NULL,'sample message1');
+SELECT pg_notify('notify_async_channel_name_too_long______________________________','sample_message1');
+
+--Should work. Valid NOTIFY/LISTEN/UNLISTEN commands
+NOTIFY notify_async2;
+LISTEN notify_async2;
+UNLISTEN notify_async2;
+UNLISTEN *;
+
+-- Should return zero while there are no pending notifications.
+-- src/test/isolation/specs/async-notify.spec tests for actual usage.
+SELECT pg_notification_queue_usage();
diff --git a/src/test/regress/sql/bit.sql b/src/test/regress/sql/bit.sql
new file mode 100644
index 0000000..0a424e7
--- /dev/null
+++ b/src/test/regress/sql/bit.sql
@@ -0,0 +1,231 @@
+--
+-- BIT types
+--
+
+--
+-- Build tables for testing
+--
+
+CREATE TABLE BIT_TABLE(b BIT(11));
+
+INSERT INTO BIT_TABLE VALUES (B'10'); -- too short
+INSERT INTO BIT_TABLE VALUES (B'00000000000');
+INSERT INTO BIT_TABLE VALUES (B'11011000000');
+INSERT INTO BIT_TABLE VALUES (B'01010101010');
+INSERT INTO BIT_TABLE VALUES (B'101011111010'); -- too long
+--INSERT INTO BIT_TABLE VALUES ('X554');
+--INSERT INTO BIT_TABLE VALUES ('X555');
+
+SELECT * FROM BIT_TABLE;
+
+CREATE TABLE VARBIT_TABLE(v BIT VARYING(11));
+
+INSERT INTO VARBIT_TABLE VALUES (B'');
+INSERT INTO VARBIT_TABLE VALUES (B'0');
+INSERT INTO VARBIT_TABLE VALUES (B'010101');
+INSERT INTO VARBIT_TABLE VALUES (B'01010101010');
+INSERT INTO VARBIT_TABLE VALUES (B'101011111010'); -- too long
+--INSERT INTO VARBIT_TABLE VALUES ('X554');
+--INSERT INTO VARBIT_TABLE VALUES ('X555');
+SELECT * FROM VARBIT_TABLE;
+
+
+-- Concatenation
+SELECT v, b, (v || b) AS concat
+ FROM BIT_TABLE, VARBIT_TABLE
+ ORDER BY 3;
+
+-- Length
+SELECT b, length(b) AS lb
+ FROM BIT_TABLE;
+SELECT v, length(v) AS lv
+ FROM VARBIT_TABLE;
+
+-- Substring
+SELECT b,
+ SUBSTRING(b FROM 2 FOR 4) AS sub_2_4,
+ SUBSTRING(b FROM 7 FOR 13) AS sub_7_13,
+ SUBSTRING(b FROM 6) AS sub_6
+ FROM BIT_TABLE;
+SELECT v,
+ SUBSTRING(v FROM 2 FOR 4) AS sub_2_4,
+ SUBSTRING(v FROM 7 FOR 13) AS sub_7_13,
+ SUBSTRING(v FROM 6) AS sub_6
+ FROM VARBIT_TABLE;
+
+-- test overflow cases
+SELECT SUBSTRING('01010101'::bit(8) FROM 2 FOR 2147483646) AS "1010101";
+SELECT SUBSTRING('01010101'::bit(8) FROM -10 FOR 2147483646) AS "01010101";
+SELECT SUBSTRING('01010101'::bit(8) FROM -10 FOR -2147483646) AS "error";
+SELECT SUBSTRING('01010101'::varbit FROM 2 FOR 2147483646) AS "1010101";
+SELECT SUBSTRING('01010101'::varbit FROM -10 FOR 2147483646) AS "01010101";
+SELECT SUBSTRING('01010101'::varbit FROM -10 FOR -2147483646) AS "error";
+
+--- Bit operations
+DROP TABLE varbit_table;
+CREATE TABLE varbit_table (a BIT VARYING(16), b BIT VARYING(16));
+COPY varbit_table FROM stdin;
+X0F X10
+X1F X11
+X2F X12
+X3F X13
+X8F X04
+X000F X0010
+X0123 XFFFF
+X2468 X2468
+XFA50 X05AF
+X1234 XFFF5
+\.
+
+SELECT a, b, ~a AS "~ a", a & b AS "a & b",
+ a | b AS "a | b", a # b AS "a # b" FROM varbit_table;
+SELECT a,b,a<b AS "a<b",a<=b AS "a<=b",a=b AS "a=b",
+ a>=b AS "a>=b",a>b AS "a>b",a<>b AS "a<>b" FROM varbit_table;
+SELECT a,a<<4 AS "a<<4",b,b>>2 AS "b>>2" FROM varbit_table;
+
+DROP TABLE varbit_table;
+
+--- Bit operations
+DROP TABLE bit_table;
+CREATE TABLE bit_table (a BIT(16), b BIT(16));
+COPY bit_table FROM stdin;
+X0F00 X1000
+X1F00 X1100
+X2F00 X1200
+X3F00 X1300
+X8F00 X0400
+X000F X0010
+X0123 XFFFF
+X2468 X2468
+XFA50 X05AF
+X1234 XFFF5
+\.
+
+SELECT a,b,~a AS "~ a",a & b AS "a & b",
+ a|b AS "a | b", a # b AS "a # b" FROM bit_table;
+SELECT a,b,a<b AS "a<b",a<=b AS "a<=b",a=b AS "a=b",
+ a>=b AS "a>=b",a>b AS "a>b",a<>b AS "a<>b" FROM bit_table;
+SELECT a,a<<4 AS "a<<4",b,b>>2 AS "b>>2" FROM bit_table;
+
+DROP TABLE bit_table;
+
+
+-- The following should fail
+select B'001' & B'10';
+select B'0111' | B'011';
+select B'0010' # B'011101';
+
+-- More position tests, checking all the boundary cases
+SELECT POSITION(B'1010' IN B'0000101'); -- 0
+SELECT POSITION(B'1010' IN B'00001010'); -- 5
+SELECT POSITION(B'1010' IN B'00000101'); -- 0
+SELECT POSITION(B'1010' IN B'000001010'); -- 6
+
+SELECT POSITION(B'' IN B'00001010'); -- 1
+SELECT POSITION(B'0' IN B''); -- 0
+SELECT POSITION(B'' IN B''); -- 0
+SELECT POSITION(B'101101' IN B'001011011011011000'); -- 3
+SELECT POSITION(B'10110110' IN B'001011011011010'); -- 3
+SELECT POSITION(B'1011011011011' IN B'001011011011011'); -- 3
+SELECT POSITION(B'1011011011011' IN B'00001011011011011'); -- 5
+
+SELECT POSITION(B'11101011' IN B'11101011'); -- 1
+SELECT POSITION(B'11101011' IN B'011101011'); -- 2
+SELECT POSITION(B'11101011' IN B'00011101011'); -- 4
+SELECT POSITION(B'11101011' IN B'0000011101011'); -- 6
+
+SELECT POSITION(B'111010110' IN B'111010110'); -- 1
+SELECT POSITION(B'111010110' IN B'0111010110'); -- 2
+SELECT POSITION(B'111010110' IN B'000111010110'); -- 4
+SELECT POSITION(B'111010110' IN B'00000111010110'); -- 6
+
+SELECT POSITION(B'111010110' IN B'11101011'); -- 0
+SELECT POSITION(B'111010110' IN B'011101011'); -- 0
+SELECT POSITION(B'111010110' IN B'00011101011'); -- 0
+SELECT POSITION(B'111010110' IN B'0000011101011'); -- 0
+
+SELECT POSITION(B'111010110' IN B'111010110'); -- 1
+SELECT POSITION(B'111010110' IN B'0111010110'); -- 2
+SELECT POSITION(B'111010110' IN B'000111010110'); -- 4
+SELECT POSITION(B'111010110' IN B'00000111010110'); -- 6
+
+SELECT POSITION(B'111010110' IN B'000001110101111101011'); -- 0
+SELECT POSITION(B'111010110' IN B'0000001110101111101011'); -- 0
+SELECT POSITION(B'111010110' IN B'000000001110101111101011'); -- 0
+SELECT POSITION(B'111010110' IN B'00000000001110101111101011'); -- 0
+
+SELECT POSITION(B'111010110' IN B'0000011101011111010110'); -- 14
+SELECT POSITION(B'111010110' IN B'00000011101011111010110'); -- 15
+SELECT POSITION(B'111010110' IN B'0000000011101011111010110'); -- 17
+SELECT POSITION(B'111010110' IN B'000000000011101011111010110'); -- 19
+
+SELECT POSITION(B'000000000011101011111010110' IN B'000000000011101011111010110'); -- 1
+SELECT POSITION(B'00000000011101011111010110' IN B'000000000011101011111010110'); -- 2
+SELECT POSITION(B'0000000000011101011111010110' IN B'000000000011101011111010110'); -- 0
+
+
+-- Shifting
+
+CREATE TABLE BIT_SHIFT_TABLE(b BIT(16));
+INSERT INTO BIT_SHIFT_TABLE VALUES (B'1101100000000000');
+INSERT INTO BIT_SHIFT_TABLE SELECT b>>1 FROM BIT_SHIFT_TABLE;
+INSERT INTO BIT_SHIFT_TABLE SELECT b>>2 FROM BIT_SHIFT_TABLE;
+INSERT INTO BIT_SHIFT_TABLE SELECT b>>4 FROM BIT_SHIFT_TABLE;
+INSERT INTO BIT_SHIFT_TABLE SELECT b>>8 FROM BIT_SHIFT_TABLE;
+SELECT POSITION(B'1101' IN b),
+ POSITION(B'11011' IN b),
+ b
+ FROM BIT_SHIFT_TABLE ;
+SELECT b, b >> 1 AS bsr, b << 1 AS bsl
+ FROM BIT_SHIFT_TABLE ;
+SELECT b, b >> 8 AS bsr8, b << 8 AS bsl8
+ FROM BIT_SHIFT_TABLE ;
+SELECT b::bit(15), b::bit(15) >> 1 AS bsr, b::bit(15) << 1 AS bsl
+ FROM BIT_SHIFT_TABLE ;
+SELECT b::bit(15), b::bit(15) >> 8 AS bsr8, b::bit(15) << 8 AS bsl8
+ FROM BIT_SHIFT_TABLE ;
+
+
+CREATE TABLE VARBIT_SHIFT_TABLE(v BIT VARYING(20));
+INSERT INTO VARBIT_SHIFT_TABLE VALUES (B'11011');
+INSERT INTO VARBIT_SHIFT_TABLE SELECT CAST(v || B'0' AS BIT VARYING(6)) >>1 FROM VARBIT_SHIFT_TABLE;
+INSERT INTO VARBIT_SHIFT_TABLE SELECT CAST(v || B'00' AS BIT VARYING(8)) >>2 FROM VARBIT_SHIFT_TABLE;
+INSERT INTO VARBIT_SHIFT_TABLE SELECT CAST(v || B'0000' AS BIT VARYING(12)) >>4 FROM VARBIT_SHIFT_TABLE;
+INSERT INTO VARBIT_SHIFT_TABLE SELECT CAST(v || B'00000000' AS BIT VARYING(20)) >>8 FROM VARBIT_SHIFT_TABLE;
+SELECT POSITION(B'1101' IN v),
+ POSITION(B'11011' IN v),
+ v
+ FROM VARBIT_SHIFT_TABLE ;
+SELECT v, v >> 1 AS vsr, v << 1 AS vsl
+ FROM VARBIT_SHIFT_TABLE ;
+SELECT v, v >> 8 AS vsr8, v << 8 AS vsl8
+ FROM VARBIT_SHIFT_TABLE ;
+
+DROP TABLE BIT_SHIFT_TABLE;
+DROP TABLE VARBIT_SHIFT_TABLE;
+
+-- Get/Set bit
+SELECT get_bit(B'0101011000100', 10);
+SELECT set_bit(B'0101011000100100', 15, 1);
+SELECT set_bit(B'0101011000100100', 16, 1); -- fail
+
+-- Overlay
+SELECT overlay(B'0101011100' placing '001' from 2 for 3);
+SELECT overlay(B'0101011100' placing '101' from 6);
+SELECT overlay(B'0101011100' placing '001' from 11);
+SELECT overlay(B'0101011100' placing '001' from 20);
+
+-- bit_count
+SELECT bit_count(B'0101011100'::bit(10));
+SELECT bit_count(B'1111111111'::bit(10));
+
+-- This table is intentionally left around to exercise pg_dump/pg_upgrade
+CREATE TABLE bit_defaults(
+ b1 bit(4) DEFAULT '1001',
+ b2 bit(4) DEFAULT B'0101',
+ b3 bit varying(5) DEFAULT '1001',
+ b4 bit varying(5) DEFAULT B'0101'
+);
+\d bit_defaults
+INSERT INTO bit_defaults DEFAULT VALUES;
+TABLE bit_defaults;
diff --git a/src/test/regress/sql/bitmapops.sql b/src/test/regress/sql/bitmapops.sql
new file mode 100644
index 0000000..498f472
--- /dev/null
+++ b/src/test/regress/sql/bitmapops.sql
@@ -0,0 +1,41 @@
+-- Test bitmap AND and OR
+
+
+-- Generate enough data that we can test the lossy bitmaps.
+
+-- There's 55 tuples per page in the table. 53 is just
+-- below 55, so that an index scan with qual a = constant
+-- will return at least one hit per page. 59 is just above
+-- 55, so that an index scan with qual b = constant will return
+-- hits on most but not all pages. 53 and 59 are prime, so that
+-- there's a maximum number of a,b combinations in the table.
+-- That allows us to test all the different combinations of
+-- lossy and non-lossy pages with the minimum amount of data
+
+CREATE TABLE bmscantest (a int, b int, t text);
+
+INSERT INTO bmscantest
+ SELECT (r%53), (r%59), 'foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo'
+ FROM generate_series(1,70000) r;
+
+CREATE INDEX i_bmtest_a ON bmscantest(a);
+CREATE INDEX i_bmtest_b ON bmscantest(b);
+
+-- We want to use bitmapscans. With default settings, the planner currently
+-- chooses a bitmap scan for the queries below anyway, but let's make sure.
+set enable_indexscan=false;
+set enable_seqscan=false;
+
+-- Lower work_mem to trigger use of lossy bitmaps
+set work_mem = 64;
+
+
+-- Test bitmap-and.
+SELECT count(*) FROM bmscantest WHERE a = 1 AND b = 1;
+
+-- Test bitmap-or.
+SELECT count(*) FROM bmscantest WHERE a = 1 OR b = 1;
+
+
+-- clean up
+DROP TABLE bmscantest;
diff --git a/src/test/regress/sql/boolean.sql b/src/test/regress/sql/boolean.sql
new file mode 100644
index 0000000..4dd47aa
--- /dev/null
+++ b/src/test/regress/sql/boolean.sql
@@ -0,0 +1,262 @@
+--
+-- 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 (f1 bool);
+
+INSERT INTO BOOLTBL1 (f1) VALUES (bool 't');
+
+INSERT INTO BOOLTBL1 (f1) VALUES (bool 'True');
+
+INSERT INTO BOOLTBL1 (f1) VALUES (bool 'true');
+
+
+-- BOOLTBL1 should be full of true's at this point
+SELECT BOOLTBL1.* FROM BOOLTBL1;
+
+
+SELECT BOOLTBL1.*
+ FROM BOOLTBL1
+ WHERE f1 = bool 'true';
+
+
+SELECT BOOLTBL1.*
+ FROM BOOLTBL1
+ WHERE f1 <> bool 'false';
+
+SELECT BOOLTBL1.*
+ FROM BOOLTBL1
+ WHERE booleq(bool 'false', f1);
+
+INSERT INTO BOOLTBL1 (f1) VALUES (bool 'f');
+
+SELECT BOOLTBL1.*
+ FROM BOOLTBL1
+ WHERE f1 = bool 'false';
+
+
+CREATE TABLE BOOLTBL2 (f1 bool);
+
+INSERT INTO BOOLTBL2 (f1) VALUES (bool 'f');
+
+INSERT INTO BOOLTBL2 (f1) VALUES (bool 'false');
+
+INSERT INTO BOOLTBL2 (f1) VALUES (bool 'False');
+
+INSERT INTO BOOLTBL2 (f1) VALUES (bool 'FALSE');
+
+-- This is now an invalid expression
+-- For pre-v6.3 this evaluated to false - thomas 1997-10-23
+INSERT INTO BOOLTBL2 (f1)
+ VALUES (bool 'XXX');
+
+-- BOOLTBL2 should be full of false's at this point
+SELECT BOOLTBL2.* FROM BOOLTBL2;
+
+
+SELECT BOOLTBL1.*, BOOLTBL2.*
+ FROM BOOLTBL1, BOOLTBL2
+ WHERE BOOLTBL2.f1 <> BOOLTBL1.f1;
+
+
+SELECT BOOLTBL1.*, BOOLTBL2.*
+ FROM BOOLTBL1, BOOLTBL2
+ WHERE boolne(BOOLTBL2.f1,BOOLTBL1.f1);
+
+
+SELECT BOOLTBL1.*, BOOLTBL2.*
+ FROM BOOLTBL1, BOOLTBL2
+ WHERE BOOLTBL2.f1 = BOOLTBL1.f1 and BOOLTBL1.f1 = bool 'false';
+
+
+SELECT BOOLTBL1.*, BOOLTBL2.*
+ FROM BOOLTBL1, BOOLTBL2
+ WHERE BOOLTBL2.f1 = BOOLTBL1.f1 or BOOLTBL1.f1 = bool 'true'
+ ORDER BY BOOLTBL1.f1, BOOLTBL2.f1;
+
+--
+-- SQL syntax
+-- Try all combinations to ensure that we get nothing when we expect nothing
+-- - thomas 2000-01-04
+--
+
+SELECT f1
+ FROM BOOLTBL1
+ WHERE f1 IS TRUE;
+
+SELECT f1
+ FROM BOOLTBL1
+ WHERE f1 IS NOT FALSE;
+
+SELECT f1
+ FROM BOOLTBL1
+ WHERE f1 IS FALSE;
+
+SELECT f1
+ FROM BOOLTBL1
+ WHERE f1 IS NOT TRUE;
+
+SELECT f1
+ FROM BOOLTBL2
+ WHERE f1 IS TRUE;
+
+SELECT f1
+ FROM BOOLTBL2
+ WHERE f1 IS NOT FALSE;
+
+SELECT f1
+ FROM BOOLTBL2
+ WHERE f1 IS FALSE;
+
+SELECT f1
+ FROM BOOLTBL2
+ WHERE f1 IS NOT TRUE;
+
+--
+-- Tests for BooleanTest
+--
+CREATE TABLE BOOLTBL3 (d text, b bool, o int);
+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;
+
+
+-- Test to make sure short-circuiting and NULL handling is
+-- correct. Use a table as source to prevent constant simplification
+-- to interfer.
+CREATE TABLE booltbl4(isfalse bool, istrue bool, isnul bool);
+INSERT INTO booltbl4 VALUES (false, true, null);
+\pset null '(null)'
+
+-- AND expression need to return null if there's any nulls and not all
+-- of the value are true
+SELECT istrue AND isnul AND istrue FROM booltbl4;
+SELECT istrue AND istrue AND isnul FROM booltbl4;
+SELECT isnul AND istrue AND istrue FROM booltbl4;
+SELECT isfalse AND isnul AND istrue FROM booltbl4;
+SELECT istrue AND isfalse AND isnul FROM booltbl4;
+SELECT isnul AND istrue AND isfalse FROM booltbl4;
+
+-- OR expression need to return null if there's any nulls and none
+-- of the value is true
+SELECT isfalse OR isnul OR isfalse FROM booltbl4;
+SELECT isfalse OR isfalse OR isnul FROM booltbl4;
+SELECT isnul OR isfalse OR isfalse FROM booltbl4;
+SELECT isfalse OR isnul OR istrue FROM booltbl4;
+SELECT istrue OR isfalse OR isnul FROM booltbl4;
+SELECT isnul OR istrue OR isfalse FROM booltbl4;
+
+
+--
+-- 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;
+
+DROP TABLE BOOLTBL4;
diff --git a/src/test/regress/sql/box.sql b/src/test/regress/sql/box.sql
new file mode 100644
index 0000000..ceae58f
--- /dev/null
+++ b/src/test/regress/sql/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/brin.sql b/src/test/regress/sql/brin.sql
new file mode 100644
index 0000000..3bd866d
--- /dev/null
+++ b/src/test/regress/sql/brin.sql
@@ -0,0 +1,511 @@
+CREATE TABLE brintest (byteacol bytea,
+ charcol "char",
+ namecol name,
+ int8col bigint,
+ int2col smallint,
+ int4col integer,
+ textcol text,
+ oidcol oid,
+ tidcol tid,
+ float4col real,
+ float8col double precision,
+ macaddrcol macaddr,
+ inetcol inet,
+ cidrcol cidr,
+ bpcharcol character,
+ datecol date,
+ timecol time without time zone,
+ timestampcol timestamp without time zone,
+ timestamptzcol timestamp with time zone,
+ intervalcol interval,
+ timetzcol time with time zone,
+ bitcol bit(10),
+ varbitcol bit varying(16),
+ numericcol numeric,
+ uuidcol uuid,
+ int4rangecol int4range,
+ lsncol pg_lsn,
+ boxcol box
+) WITH (fillfactor=10, autovacuum_enabled=off);
+
+INSERT INTO brintest SELECT
+ repeat(stringu1, 8)::bytea,
+ substr(stringu1, 1, 1)::"char",
+ stringu1::name, 142857 * tenthous,
+ thousand,
+ twothousand,
+ repeat(stringu1, 8),
+ unique1::oid,
+ format('(%s,%s)', tenthous, twenty)::tid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ inet '10.2.3.4/24' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ substr(stringu1, 1, 1)::bpchar,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20+02' + hundred * interval '15 seconds',
+ thousand::bit(10),
+ tenthous::bit(16)::varbit,
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ int4range(thousand, twothousand),
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn,
+ box(point(odd, even), point(thousand, twothousand))
+FROM tenk1 ORDER BY unique2 LIMIT 100;
+
+-- throw in some NULL's and different values
+INSERT INTO brintest (inetcol, cidrcol, int4rangecol) SELECT
+ inet 'fe80::6e40:8ff:fea9:8c46' + tenthous,
+ cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous,
+ 'empty'::int4range
+FROM tenk1 ORDER BY thousand, tenthous LIMIT 25;
+
+CREATE INDEX brinidx ON brintest USING brin (
+ byteacol,
+ charcol,
+ namecol,
+ int8col,
+ int2col,
+ int4col,
+ textcol,
+ oidcol,
+ tidcol,
+ float4col,
+ float8col,
+ macaddrcol,
+ inetcol inet_inclusion_ops,
+ inetcol inet_minmax_ops,
+ cidrcol inet_inclusion_ops,
+ cidrcol inet_minmax_ops,
+ bpcharcol,
+ datecol,
+ timecol,
+ timestampcol,
+ timestamptzcol,
+ intervalcol,
+ timetzcol,
+ bitcol,
+ varbitcol,
+ numericcol,
+ uuidcol,
+ int4rangecol,
+ lsncol,
+ boxcol
+) with (pages_per_range = 1);
+
+CREATE TABLE brinopers (colname name, typ text,
+ op text[], value text[], matches int[],
+ check (cardinality(op) = cardinality(value)),
+ check (cardinality(op) = cardinality(matches)));
+
+INSERT INTO brinopers VALUES
+ ('byteacol', 'bytea',
+ '{>, >=, =, <=, <}',
+ '{AAAAAA, AAAAAA, BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA, ZZZZZZ, ZZZZZZ}',
+ '{100, 100, 1, 100, 100}'),
+ ('charcol', '"char"',
+ '{>, >=, =, <=, <}',
+ '{A, A, M, Z, Z}',
+ '{97, 100, 6, 100, 98}'),
+ ('namecol', 'name',
+ '{>, >=, =, <=, <}',
+ '{AAAAAA, AAAAAA, MAAAAA, ZZAAAA, ZZAAAA}',
+ '{100, 100, 2, 100, 100}'),
+ ('int2col', 'int2',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 999, 999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int2col', 'int4',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 999, 1999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int2col', 'int8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 999, 1428427143}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4col', 'int2',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 1999, 1999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4col', 'int4',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 1999, 1999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4col', 'int8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 1999, 1428427143}',
+ '{100, 100, 1, 100, 100}'),
+ ('int8col', 'int2',
+ '{>, >=}',
+ '{0, 0}',
+ '{100, 100}'),
+ ('int8col', 'int4',
+ '{>, >=}',
+ '{0, 0}',
+ '{100, 100}'),
+ ('int8col', 'int8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 1257141600, 1428427143, 1428427143}',
+ '{100, 100, 1, 100, 100}'),
+ ('textcol', 'text',
+ '{>, >=, =, <=, <}',
+ '{ABABAB, ABABAB, BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA, ZZAAAA, ZZAAAA}',
+ '{100, 100, 1, 100, 100}'),
+ ('oidcol', 'oid',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 8800, 9999, 9999}',
+ '{100, 100, 1, 100, 100}'),
+ ('tidcol', 'tid',
+ '{>, >=, =, <=, <}',
+ '{"(0,0)", "(0,0)", "(8800,0)", "(9999,19)", "(9999,19)"}',
+ '{100, 100, 1, 100, 100}'),
+ ('float4col', 'float4',
+ '{>, >=, =, <=, <}',
+ '{0.0103093, 0.0103093, 1, 1, 1}',
+ '{100, 100, 4, 100, 96}'),
+ ('float4col', 'float8',
+ '{>, >=, =, <=, <}',
+ '{0.0103093, 0.0103093, 1, 1, 1}',
+ '{100, 100, 4, 100, 96}'),
+ ('float8col', 'float4',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 0, 1.98, 1.98}',
+ '{99, 100, 1, 100, 100}'),
+ ('float8col', 'float8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 0, 1.98, 1.98}',
+ '{99, 100, 1, 100, 100}'),
+ ('macaddrcol', 'macaddr',
+ '{>, >=, =, <=, <}',
+ '{00:00:01:00:00:00, 00:00:01:00:00:00, 2c:00:2d:00:16:00, ff:fe:00:00:00:00, ff:fe:00:00:00:00}',
+ '{99, 100, 2, 100, 100}'),
+ ('inetcol', 'inet',
+ '{&&, =, <, <=, >, >=, >>=, >>, <<=, <<}',
+ '{10/8, 10.2.14.231/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0, 10.2.14.231/24, 10.2.14.231/25, 10.2.14.231/8, 0/0}',
+ '{100, 1, 100, 100, 125, 125, 2, 2, 100, 100}'),
+ ('inetcol', 'inet',
+ '{&&, >>=, <<=, =}',
+ '{fe80::6e40:8ff:fea9:a673/32, fe80::6e40:8ff:fea9:8c46, fe80::6e40:8ff:fea9:a673/32, fe80::6e40:8ff:fea9:8c46}',
+ '{25, 1, 25, 1}'),
+ ('inetcol', 'cidr',
+ '{&&, <, <=, >, >=, >>=, >>, <<=, <<}',
+ '{10/8, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0, 10.2.14/24, 10.2.14/25, 10/8, 0/0}',
+ '{100, 100, 100, 125, 125, 2, 2, 100, 100}'),
+ ('inetcol', 'cidr',
+ '{&&, >>=, <<=, =}',
+ '{fe80::/32, fe80::6e40:8ff:fea9:8c46, fe80::/32, fe80::6e40:8ff:fea9:8c46}',
+ '{25, 1, 25, 1}'),
+ ('cidrcol', 'inet',
+ '{&&, =, <, <=, >, >=, >>=, >>, <<=, <<}',
+ '{10/8, 10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0, 10.2.14.231/24, 10.2.14.231/25, 10.2.14.231/8, 0/0}',
+ '{100, 2, 100, 100, 125, 125, 2, 2, 100, 100}'),
+ ('cidrcol', 'inet',
+ '{&&, >>=, <<=, =}',
+ '{fe80::6e40:8ff:fea9:a673/32, fe80::6e40:8ff:fea9:8c46, fe80::6e40:8ff:fea9:a673/32, fe80::6e40:8ff:fea9:8c46}',
+ '{25, 1, 25, 1}'),
+ ('cidrcol', 'cidr',
+ '{&&, =, <, <=, >, >=, >>=, >>, <<=, <<}',
+ '{10/8, 10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0, 10.2.14/24, 10.2.14/25, 10/8, 0/0}',
+ '{100, 2, 100, 100, 125, 125, 2, 2, 100, 100}'),
+ ('cidrcol', 'cidr',
+ '{&&, >>=, <<=, =}',
+ '{fe80::/32, fe80::6e40:8ff:fea9:8c46, fe80::/32, fe80::6e40:8ff:fea9:8c46}',
+ '{25, 1, 25, 1}'),
+ ('bpcharcol', 'bpchar',
+ '{>, >=, =, <=, <}',
+ '{A, A, W, Z, Z}',
+ '{97, 100, 6, 100, 98}'),
+ ('datecol', 'date',
+ '{>, >=, =, <=, <}',
+ '{1995-08-15, 1995-08-15, 2009-12-01, 2022-12-30, 2022-12-30}',
+ '{100, 100, 1, 100, 100}'),
+ ('timecol', 'time',
+ '{>, >=, =, <=, <}',
+ '{01:20:30, 01:20:30, 02:28:57, 06:28:31.5, 06:28:31.5}',
+ '{100, 100, 1, 100, 100}'),
+ ('timestampcol', 'timestamp',
+ '{>, >=, =, <=, <}',
+ '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}',
+ '{100, 100, 1, 100, 100}'),
+ ('timestampcol', 'timestamptz',
+ '{>, >=, =, <=, <}',
+ '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}',
+ '{100, 100, 1, 100, 100}'),
+ ('timestamptzcol', 'timestamptz',
+ '{>, >=, =, <=, <}',
+ '{1972-10-10 03:00:00-04, 1972-10-10 03:00:00-04, 1972-10-19 09:00:00-07, 1972-11-20 19:00:00-03, 1972-11-20 19:00:00-03}',
+ '{100, 100, 1, 100, 100}'),
+ ('intervalcol', 'interval',
+ '{>, >=, =, <=, <}',
+ '{00:00:00, 00:00:00, 1 mons 13 days 12:24, 2 mons 23 days 07:48:00, 1 year}',
+ '{100, 100, 1, 100, 100}'),
+ ('timetzcol', 'timetz',
+ '{>, >=, =, <=, <}',
+ '{01:30:20+02, 01:30:20+02, 01:35:50+02, 23:55:05+02, 23:55:05+02}',
+ '{99, 100, 2, 100, 100}'),
+ ('bitcol', 'bit(10)',
+ '{>, >=, =, <=, <}',
+ '{0000000010, 0000000010, 0011011110, 1111111000, 1111111000}',
+ '{100, 100, 1, 100, 100}'),
+ ('varbitcol', 'varbit(16)',
+ '{>, >=, =, <=, <}',
+ '{0000000000000100, 0000000000000100, 0001010001100110, 1111111111111000, 1111111111111000}',
+ '{100, 100, 1, 100, 100}'),
+ ('numericcol', 'numeric',
+ '{>, >=, =, <=, <}',
+ '{0.00, 0.01, 2268164.347826086956521739130434782609, 99470151.9, 99470151.9}',
+ '{100, 100, 1, 100, 100}'),
+ ('uuidcol', 'uuid',
+ '{>, >=, =, <=, <}',
+ '{00040004-0004-0004-0004-000400040004, 00040004-0004-0004-0004-000400040004, 52225222-5222-5222-5222-522252225222, 99989998-9998-9998-9998-999899989998, 99989998-9998-9998-9998-999899989998}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4rangecol', 'int4range',
+ '{<<, &<, &&, &>, >>, @>, <@, =, <, <=, >, >=}',
+ '{"[10000,)","[10000,)","(,]","[3,4)","[36,44)","(1500,1501]","[3,4)","[222,1222)","[36,44)","[43,1043)","[367,4466)","[519,)"}',
+ '{53, 53, 53, 53, 50, 22, 72, 1, 74, 75, 34, 21}'),
+ ('int4rangecol', 'int4range',
+ '{@>, <@, =, <=, >, >=}',
+ '{empty, empty, empty, empty, empty, empty}',
+ '{125, 72, 72, 72, 53, 125}'),
+ ('int4rangecol', 'int4',
+ '{@>}',
+ '{1500}',
+ '{22}'),
+ ('lsncol', 'pg_lsn',
+ '{>, >=, =, <=, <, IS, IS NOT}',
+ '{0/1200, 0/1200, 44/455222, 198/1999799, 198/1999799, NULL, NULL}',
+ '{100, 100, 1, 100, 100, 25, 100}'),
+ ('boxcol', 'point',
+ '{@>}',
+ '{"(500,43)"}',
+ '{11}'),
+ ('boxcol', 'box',
+ '{<<, &<, &&, &>, >>, <<|, &<|, |&>, |>>, @>, <@, ~=}',
+ '{"((1000,2000),(3000,4000))","((1,2),(3000,4000))","((1,2),(3000,4000))","((1,2),(3000,4000))","((1,2),(3,4))","((1000,2000),(3000,4000))","((1,2000),(3,4000))","((1000,2),(3000,4))","((1,2),(3,4))","((1,2),(300,400))","((1,2),(3000,4000))","((222,1222),(44,45))"}',
+ '{100, 100, 100, 99, 96, 100, 100, 99, 96, 1, 99, 1}');
+
+DO $x$
+DECLARE
+ r record;
+ r2 record;
+ cond text;
+ idx_ctids tid[];
+ ss_ctids tid[];
+ count int;
+ plan_ok bool;
+ plan_line text;
+BEGIN
+ FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers, unnest(op) WITH ORDINALITY AS oper LOOP
+
+ -- prepare the condition
+ IF r.value IS NULL THEN
+ cond := format('%I %s %L', r.colname, r.oper, r.value);
+ ELSE
+ cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ);
+ END IF;
+
+ -- run the query using the brin index
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Bitmap Heap Scan on brintest%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get bitmap indexscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest WHERE %s $y$, cond)
+ INTO idx_ctids;
+
+ -- run the query using a seqscan
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Seq Scan on brintest%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get seqscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest WHERE %s $y$, cond)
+ INTO ss_ctids;
+
+ -- make sure both return the same results
+ count := array_length(idx_ctids, 1);
+
+ IF NOT (count = array_length(ss_ctids, 1) AND
+ idx_ctids @> ss_ctids AND
+ idx_ctids <@ ss_ctids) THEN
+ -- report the results of each scan to make the differences obvious
+ RAISE WARNING 'something not right in %: count %', r, count;
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest WHERE ' || cond LOOP
+ RAISE NOTICE 'seqscan: %', r2;
+ END LOOP;
+
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest WHERE ' || cond LOOP
+ RAISE NOTICE 'bitmapscan: %', r2;
+ END LOOP;
+ END IF;
+
+ -- make sure we found expected number of matches
+ IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF;
+ END LOOP;
+END;
+$x$;
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+
+INSERT INTO brintest SELECT
+ repeat(stringu1, 42)::bytea,
+ substr(stringu1, 1, 1)::"char",
+ stringu1::name, 142857 * tenthous,
+ thousand,
+ twothousand,
+ repeat(stringu1, 42),
+ unique1::oid,
+ format('(%s,%s)', tenthous, twenty)::tid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ inet '10.2.3.4' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ substr(stringu1, 1, 1)::bpchar,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20' + hundred * interval '15 seconds',
+ thousand::bit(10),
+ tenthous::bit(16)::varbit,
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ int4range(thousand, twothousand),
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn,
+ box(point(odd, even), point(thousand, twothousand))
+FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5;
+
+SELECT brin_desummarize_range('brinidx', 0);
+VACUUM brintest; -- force a summarization cycle in brinidx
+
+UPDATE brintest SET int8col = int8col * int4col;
+UPDATE brintest SET textcol = '' WHERE textcol IS NOT NULL;
+
+-- Tests for brin_summarize_new_values
+SELECT brin_summarize_new_values('brintest'); -- error, not an index
+SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index
+SELECT brin_summarize_new_values('brinidx'); -- ok, no change expected
+
+-- Tests for brin_desummarize_range
+SELECT brin_desummarize_range('brinidx', -1); -- error, invalid range
+SELECT brin_desummarize_range('brinidx', 0);
+SELECT brin_desummarize_range('brinidx', 0);
+SELECT brin_desummarize_range('brinidx', 100000000);
+
+-- Test brin_summarize_range
+CREATE TABLE brin_summarize (
+ value int
+) WITH (fillfactor=10, autovacuum_enabled=false);
+CREATE INDEX brin_summarize_idx ON brin_summarize USING brin (value) WITH (pages_per_range=2);
+-- Fill a few pages
+DO $$
+DECLARE curtid tid;
+BEGIN
+ LOOP
+ INSERT INTO brin_summarize VALUES (1) RETURNING ctid INTO curtid;
+ EXIT WHEN curtid > tid '(2, 0)';
+ END LOOP;
+END;
+$$;
+
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_idx', 0);
+-- nothing: already summarized
+SELECT brin_summarize_range('brin_summarize_idx', 1);
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_idx', 2);
+-- nothing: page doesn't exist in table
+SELECT brin_summarize_range('brin_summarize_idx', 4294967295);
+-- invalid block number values
+SELECT brin_summarize_range('brin_summarize_idx', -1);
+SELECT brin_summarize_range('brin_summarize_idx', 4294967296);
+
+-- test value merging in add_value
+CREATE TABLE brintest_2 (n numrange);
+CREATE INDEX brinidx_2 ON brintest_2 USING brin (n);
+INSERT INTO brintest_2 VALUES ('empty');
+INSERT INTO brintest_2 VALUES (numrange(0, 2^1000::numeric));
+INSERT INTO brintest_2 VALUES ('(-1, 0)');
+
+SELECT brin_desummarize_range('brinidx', 0);
+SELECT brin_summarize_range('brinidx', 0);
+DROP TABLE brintest_2;
+
+-- test brin cost estimates behave sanely based on correlation of values
+CREATE TABLE brin_test (a INT, b INT);
+INSERT INTO brin_test SELECT x/100,x%100 FROM generate_series(1,10000) x(x);
+CREATE INDEX brin_test_a_idx ON brin_test USING brin (a) WITH (pages_per_range = 2);
+CREATE INDEX brin_test_b_idx ON brin_test USING brin (b) WITH (pages_per_range = 2);
+VACUUM ANALYZE brin_test;
+
+-- Ensure brin index is used when columns are perfectly correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test WHERE a = 1;
+-- Ensure brin index is not used when values are not correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test WHERE b = 1;
+
+-- make sure data are properly de-toasted in BRIN index
+CREATE TABLE brintest_3 (a text, b text, c text, d text);
+
+-- long random strings (~2000 chars each, so ~6kB for min/max on two
+-- columns) to trigger toasting
+WITH rand_value AS (SELECT string_agg(md5(i::text),'') AS val FROM generate_series(1,60) s(i))
+INSERT INTO brintest_3
+SELECT val, val, val, val FROM rand_value;
+
+CREATE INDEX brin_test_toast_idx ON brintest_3 USING brin (b, c);
+DELETE FROM brintest_3;
+
+-- We need to wait a bit for all transactions to complete, so that the
+-- vacuum actually removes the TOAST rows. Creating an index concurrently
+-- is a one way to achieve that, because it does exactly such wait.
+CREATE INDEX CONCURRENTLY brin_test_temp_idx ON brintest_3(a);
+DROP INDEX brin_test_temp_idx;
+
+-- vacuum the table, to discard TOAST data
+VACUUM brintest_3;
+
+-- retry insert with a different random-looking (but deterministic) value
+-- the value is different, and so should replace either min or max in the
+-- brin summary
+WITH rand_value AS (SELECT string_agg(md5((-i)::text),'') AS val FROM generate_series(1,60) s(i))
+INSERT INTO brintest_3
+SELECT val, val, val, val FROM rand_value;
+
+-- now try some queries, accessing the brin index
+SET enable_seqscan = off;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM brintest_3 WHERE b < '0';
+
+SELECT * FROM brintest_3 WHERE b < '0';
+
+DROP TABLE brintest_3;
+RESET enable_seqscan;
diff --git a/src/test/regress/sql/brin_bloom.sql b/src/test/regress/sql/brin_bloom.sql
new file mode 100644
index 0000000..5d49920
--- /dev/null
+++ b/src/test/regress/sql/brin_bloom.sql
@@ -0,0 +1,376 @@
+CREATE TABLE brintest_bloom (byteacol bytea,
+ charcol "char",
+ namecol name,
+ int8col bigint,
+ int2col smallint,
+ int4col integer,
+ textcol text,
+ oidcol oid,
+ float4col real,
+ float8col double precision,
+ macaddrcol macaddr,
+ inetcol inet,
+ cidrcol cidr,
+ bpcharcol character,
+ datecol date,
+ timecol time without time zone,
+ timestampcol timestamp without time zone,
+ timestamptzcol timestamp with time zone,
+ intervalcol interval,
+ timetzcol time with time zone,
+ numericcol numeric,
+ uuidcol uuid,
+ lsncol pg_lsn
+) WITH (fillfactor=10);
+
+INSERT INTO brintest_bloom SELECT
+ repeat(stringu1, 8)::bytea,
+ substr(stringu1, 1, 1)::"char",
+ stringu1::name, 142857 * tenthous,
+ thousand,
+ twothousand,
+ repeat(stringu1, 8),
+ unique1::oid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ inet '10.2.3.4/24' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ substr(stringu1, 1, 1)::bpchar,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20+02' + hundred * interval '15 seconds',
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn
+FROM tenk1 ORDER BY unique2 LIMIT 100;
+
+-- throw in some NULL's and different values
+INSERT INTO brintest_bloom (inetcol, cidrcol) SELECT
+ inet 'fe80::6e40:8ff:fea9:8c46' + tenthous,
+ cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous
+FROM tenk1 ORDER BY thousand, tenthous LIMIT 25;
+
+-- test bloom specific index options
+-- ndistinct must be >= -1.0
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops(n_distinct_per_range = -1.1)
+);
+-- false_positive_rate must be between 0.0001 and 0.25
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops(false_positive_rate = 0.00009)
+);
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops(false_positive_rate = 0.26)
+);
+
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops,
+ charcol char_bloom_ops,
+ namecol name_bloom_ops,
+ int8col int8_bloom_ops,
+ int2col int2_bloom_ops,
+ int4col int4_bloom_ops,
+ textcol text_bloom_ops,
+ oidcol oid_bloom_ops,
+ float4col float4_bloom_ops,
+ float8col float8_bloom_ops,
+ macaddrcol macaddr_bloom_ops,
+ inetcol inet_bloom_ops,
+ cidrcol inet_bloom_ops,
+ bpcharcol bpchar_bloom_ops,
+ datecol date_bloom_ops,
+ timecol time_bloom_ops,
+ timestampcol timestamp_bloom_ops,
+ timestamptzcol timestamptz_bloom_ops,
+ intervalcol interval_bloom_ops,
+ timetzcol timetz_bloom_ops,
+ numericcol numeric_bloom_ops,
+ uuidcol uuid_bloom_ops,
+ lsncol pg_lsn_bloom_ops
+) with (pages_per_range = 1);
+
+CREATE TABLE brinopers_bloom (colname name, typ text,
+ op text[], value text[], matches int[],
+ check (cardinality(op) = cardinality(value)),
+ check (cardinality(op) = cardinality(matches)));
+
+INSERT INTO brinopers_bloom VALUES
+ ('byteacol', 'bytea',
+ '{=}',
+ '{BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA}',
+ '{1}'),
+ ('charcol', '"char"',
+ '{=}',
+ '{M}',
+ '{6}'),
+ ('namecol', 'name',
+ '{=}',
+ '{MAAAAA}',
+ '{2}'),
+ ('int2col', 'int2',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int4col', 'int4',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int8col', 'int8',
+ '{=}',
+ '{1257141600}',
+ '{1}'),
+ ('textcol', 'text',
+ '{=}',
+ '{BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA}',
+ '{1}'),
+ ('oidcol', 'oid',
+ '{=}',
+ '{8800}',
+ '{1}'),
+ ('float4col', 'float4',
+ '{=}',
+ '{1}',
+ '{4}'),
+ ('float8col', 'float8',
+ '{=}',
+ '{0}',
+ '{1}'),
+ ('macaddrcol', 'macaddr',
+ '{=}',
+ '{2c:00:2d:00:16:00}',
+ '{2}'),
+ ('inetcol', 'inet',
+ '{=}',
+ '{10.2.14.231/24}',
+ '{1}'),
+ ('inetcol', 'cidr',
+ '{=}',
+ '{fe80::6e40:8ff:fea9:8c46}',
+ '{1}'),
+ ('cidrcol', 'inet',
+ '{=}',
+ '{10.2.14/24}',
+ '{2}'),
+ ('cidrcol', 'inet',
+ '{=}',
+ '{fe80::6e40:8ff:fea9:8c46}',
+ '{1}'),
+ ('cidrcol', 'cidr',
+ '{=}',
+ '{10.2.14/24}',
+ '{2}'),
+ ('cidrcol', 'cidr',
+ '{=}',
+ '{fe80::6e40:8ff:fea9:8c46}',
+ '{1}'),
+ ('bpcharcol', 'bpchar',
+ '{=}',
+ '{W}',
+ '{6}'),
+ ('datecol', 'date',
+ '{=}',
+ '{2009-12-01}',
+ '{1}'),
+ ('timecol', 'time',
+ '{=}',
+ '{02:28:57}',
+ '{1}'),
+ ('timestampcol', 'timestamp',
+ '{=}',
+ '{1964-03-24 19:26:45}',
+ '{1}'),
+ ('timestamptzcol', 'timestamptz',
+ '{=}',
+ '{1972-10-19 09:00:00-07}',
+ '{1}'),
+ ('intervalcol', 'interval',
+ '{=}',
+ '{1 mons 13 days 12:24}',
+ '{1}'),
+ ('timetzcol', 'timetz',
+ '{=}',
+ '{01:35:50+02}',
+ '{2}'),
+ ('numericcol', 'numeric',
+ '{=}',
+ '{2268164.347826086956521739130434782609}',
+ '{1}'),
+ ('uuidcol', 'uuid',
+ '{=}',
+ '{52225222-5222-5222-5222-522252225222}',
+ '{1}'),
+ ('lsncol', 'pg_lsn',
+ '{=, IS, IS NOT}',
+ '{44/455222, NULL, NULL}',
+ '{1, 25, 100}');
+
+DO $x$
+DECLARE
+ r record;
+ r2 record;
+ cond text;
+ idx_ctids tid[];
+ ss_ctids tid[];
+ count int;
+ plan_ok bool;
+ plan_line text;
+BEGIN
+ FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers_bloom, unnest(op) WITH ORDINALITY AS oper LOOP
+
+ -- prepare the condition
+ IF r.value IS NULL THEN
+ cond := format('%I %s %L', r.colname, r.oper, r.value);
+ ELSE
+ cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ);
+ END IF;
+
+ -- run the query using the brin index
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Bitmap Heap Scan on brintest_bloom%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get bitmap indexscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond)
+ INTO idx_ctids;
+
+ -- run the query using a seqscan
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Seq Scan on brintest_bloom%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get seqscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond)
+ INTO ss_ctids;
+
+ -- make sure both return the same results
+ count := array_length(idx_ctids, 1);
+
+ IF NOT (count = array_length(ss_ctids, 1) AND
+ idx_ctids @> ss_ctids AND
+ idx_ctids <@ ss_ctids) THEN
+ -- report the results of each scan to make the differences obvious
+ RAISE WARNING 'something not right in %: count %', r, count;
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_bloom WHERE ' || cond LOOP
+ RAISE NOTICE 'seqscan: %', r2;
+ END LOOP;
+
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_bloom WHERE ' || cond LOOP
+ RAISE NOTICE 'bitmapscan: %', r2;
+ END LOOP;
+ END IF;
+
+ -- make sure we found expected number of matches
+ IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF;
+ END LOOP;
+END;
+$x$;
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+
+INSERT INTO brintest_bloom SELECT
+ repeat(stringu1, 42)::bytea,
+ substr(stringu1, 1, 1)::"char",
+ stringu1::name, 142857 * tenthous,
+ thousand,
+ twothousand,
+ repeat(stringu1, 42),
+ unique1::oid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ inet '10.2.3.4' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ substr(stringu1, 1, 1)::bpchar,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20' + hundred * interval '15 seconds',
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn
+FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5;
+
+SELECT brin_desummarize_range('brinidx_bloom', 0);
+VACUUM brintest_bloom; -- force a summarization cycle in brinidx
+
+UPDATE brintest_bloom SET int8col = int8col * int4col;
+UPDATE brintest_bloom SET textcol = '' WHERE textcol IS NOT NULL;
+
+-- Tests for brin_summarize_new_values
+SELECT brin_summarize_new_values('brintest_bloom'); -- error, not an index
+SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index
+SELECT brin_summarize_new_values('brinidx_bloom'); -- ok, no change expected
+
+-- Tests for brin_desummarize_range
+SELECT brin_desummarize_range('brinidx_bloom', -1); -- error, invalid range
+SELECT brin_desummarize_range('brinidx_bloom', 0);
+SELECT brin_desummarize_range('brinidx_bloom', 0);
+SELECT brin_desummarize_range('brinidx_bloom', 100000000);
+
+-- Test brin_summarize_range
+CREATE TABLE brin_summarize_bloom (
+ value int
+) WITH (fillfactor=10, autovacuum_enabled=false);
+CREATE INDEX brin_summarize_bloom_idx ON brin_summarize_bloom USING brin (value) WITH (pages_per_range=2);
+-- Fill a few pages
+DO $$
+DECLARE curtid tid;
+BEGIN
+ LOOP
+ INSERT INTO brin_summarize_bloom VALUES (1) RETURNING ctid INTO curtid;
+ EXIT WHEN curtid > tid '(2, 0)';
+ END LOOP;
+END;
+$$;
+
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 0);
+-- nothing: already summarized
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 1);
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 2);
+-- nothing: page doesn't exist in table
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 4294967295);
+-- invalid block number values
+SELECT brin_summarize_range('brin_summarize_bloom_idx', -1);
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 4294967296);
+
+
+-- test brin cost estimates behave sanely based on correlation of values
+CREATE TABLE brin_test_bloom (a INT, b INT);
+INSERT INTO brin_test_bloom SELECT x/100,x%100 FROM generate_series(1,10000) x(x);
+CREATE INDEX brin_test_bloom_a_idx ON brin_test_bloom USING brin (a) WITH (pages_per_range = 2);
+CREATE INDEX brin_test_bloom_b_idx ON brin_test_bloom USING brin (b) WITH (pages_per_range = 2);
+VACUUM ANALYZE brin_test_bloom;
+
+-- Ensure brin index is used when columns are perfectly correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE a = 1;
+-- Ensure brin index is not used when values are not correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE b = 1;
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
new file mode 100644
index 0000000..2189b6c
--- /dev/null
+++ b/src/test/regress/sql/brin_multi.sql
@@ -0,0 +1,416 @@
+CREATE TABLE brintest_multi (
+ int8col bigint,
+ int2col smallint,
+ int4col integer,
+ oidcol oid,
+ tidcol tid,
+ float4col real,
+ float8col double precision,
+ macaddrcol macaddr,
+ macaddr8col macaddr8,
+ inetcol inet,
+ cidrcol cidr,
+ datecol date,
+ timecol time without time zone,
+ timestampcol timestamp without time zone,
+ timestamptzcol timestamp with time zone,
+ intervalcol interval,
+ timetzcol time with time zone,
+ numericcol numeric,
+ uuidcol uuid,
+ lsncol pg_lsn
+) WITH (fillfactor=10);
+
+INSERT INTO brintest_multi SELECT
+ 142857 * tenthous,
+ thousand,
+ twothousand,
+ unique1::oid,
+ format('(%s,%s)', tenthous, twenty)::tid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ substr(md5(unique1::text), 1, 16)::macaddr8,
+ inet '10.2.3.4/24' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20+02' + hundred * interval '15 seconds',
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn
+FROM tenk1 ORDER BY unique2 LIMIT 100;
+
+-- throw in some NULL's and different values
+INSERT INTO brintest_multi (inetcol, cidrcol) SELECT
+ inet 'fe80::6e40:8ff:fea9:8c46' + tenthous,
+ cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous
+FROM tenk1 ORDER BY thousand, tenthous LIMIT 25;
+
+-- test minmax-multi specific index options
+-- number of values must be >= 16
+CREATE INDEX brinidx_multi ON brintest_multi USING brin (
+ int8col int8_minmax_multi_ops(values_per_range = 7)
+);
+-- number of values must be <= 256
+CREATE INDEX brinidx_multi ON brintest_multi USING brin (
+ int8col int8_minmax_multi_ops(values_per_range = 257)
+);
+
+-- first create an index with a single page range, to force compaction
+-- due to exceeding the number of values per summary
+CREATE INDEX brinidx_multi ON brintest_multi USING brin (
+ int8col int8_minmax_multi_ops,
+ int2col int2_minmax_multi_ops,
+ int4col int4_minmax_multi_ops,
+ oidcol oid_minmax_multi_ops,
+ tidcol tid_minmax_multi_ops,
+ float4col float4_minmax_multi_ops,
+ float8col float8_minmax_multi_ops,
+ macaddrcol macaddr_minmax_multi_ops,
+ macaddr8col macaddr8_minmax_multi_ops,
+ inetcol inet_minmax_multi_ops,
+ cidrcol inet_minmax_multi_ops,
+ datecol date_minmax_multi_ops,
+ timecol time_minmax_multi_ops,
+ timestampcol timestamp_minmax_multi_ops,
+ timestamptzcol timestamptz_minmax_multi_ops,
+ intervalcol interval_minmax_multi_ops,
+ timetzcol timetz_minmax_multi_ops,
+ numericcol numeric_minmax_multi_ops,
+ uuidcol uuid_minmax_multi_ops,
+ lsncol pg_lsn_minmax_multi_ops
+);
+
+DROP INDEX brinidx_multi;
+
+CREATE INDEX brinidx_multi ON brintest_multi USING brin (
+ int8col int8_minmax_multi_ops,
+ int2col int2_minmax_multi_ops,
+ int4col int4_minmax_multi_ops,
+ oidcol oid_minmax_multi_ops,
+ tidcol tid_minmax_multi_ops,
+ float4col float4_minmax_multi_ops,
+ float8col float8_minmax_multi_ops,
+ macaddrcol macaddr_minmax_multi_ops,
+ macaddr8col macaddr8_minmax_multi_ops,
+ inetcol inet_minmax_multi_ops,
+ cidrcol inet_minmax_multi_ops,
+ datecol date_minmax_multi_ops,
+ timecol time_minmax_multi_ops,
+ timestampcol timestamp_minmax_multi_ops,
+ timestamptzcol timestamptz_minmax_multi_ops,
+ intervalcol interval_minmax_multi_ops,
+ timetzcol timetz_minmax_multi_ops,
+ numericcol numeric_minmax_multi_ops,
+ uuidcol uuid_minmax_multi_ops,
+ lsncol pg_lsn_minmax_multi_ops
+) with (pages_per_range = 1);
+
+CREATE TABLE brinopers_multi (colname name, typ text,
+ op text[], value text[], matches int[],
+ check (cardinality(op) = cardinality(value)),
+ check (cardinality(op) = cardinality(matches)));
+
+INSERT INTO brinopers_multi VALUES
+ ('int2col', 'int2',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 999, 999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int2col', 'int4',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 999, 1999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int2col', 'int8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 999, 1428427143}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4col', 'int2',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 1999, 1999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4col', 'int4',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 1999, 1999}',
+ '{100, 100, 1, 100, 100}'),
+ ('int4col', 'int8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 800, 1999, 1428427143}',
+ '{100, 100, 1, 100, 100}'),
+ ('int8col', 'int2',
+ '{>, >=}',
+ '{0, 0}',
+ '{100, 100}'),
+ ('int8col', 'int4',
+ '{>, >=}',
+ '{0, 0}',
+ '{100, 100}'),
+ ('int8col', 'int8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 1257141600, 1428427143, 1428427143}',
+ '{100, 100, 1, 100, 100}'),
+ ('oidcol', 'oid',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 8800, 9999, 9999}',
+ '{100, 100, 1, 100, 100}'),
+ ('tidcol', 'tid',
+ '{>, >=, =, <=, <}',
+ '{"(0,0)", "(0,0)", "(8800,0)", "(9999,19)", "(9999,19)"}',
+ '{100, 100, 1, 100, 100}'),
+ ('float4col', 'float4',
+ '{>, >=, =, <=, <}',
+ '{0.0103093, 0.0103093, 1, 1, 1}',
+ '{100, 100, 4, 100, 96}'),
+ ('float4col', 'float8',
+ '{>, >=, =, <=, <}',
+ '{0.0103093, 0.0103093, 1, 1, 1}',
+ '{100, 100, 4, 100, 96}'),
+ ('float8col', 'float4',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 0, 1.98, 1.98}',
+ '{99, 100, 1, 100, 100}'),
+ ('float8col', 'float8',
+ '{>, >=, =, <=, <}',
+ '{0, 0, 0, 1.98, 1.98}',
+ '{99, 100, 1, 100, 100}'),
+ ('macaddrcol', 'macaddr',
+ '{>, >=, =, <=, <}',
+ '{00:00:01:00:00:00, 00:00:01:00:00:00, 2c:00:2d:00:16:00, ff:fe:00:00:00:00, ff:fe:00:00:00:00}',
+ '{99, 100, 2, 100, 100}'),
+ ('macaddr8col', 'macaddr8',
+ '{>, >=, =, <=, <}',
+ '{b1:d1:0e:7b:af:a4:42:12, d9:35:91:bd:f7:86:0e:1e, 72:8f:20:6c:2a:01:bf:57, 23:e8:46:63:86:07:ad:cb, 13:16:8e:6a:2e:6c:84:b4}',
+ '{33, 15, 1, 13, 6}'),
+ ('inetcol', 'inet',
+ '{=, <, <=, >, >=}',
+ '{10.2.14.231/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}',
+ '{1, 100, 100, 125, 125}'),
+ ('inetcol', 'cidr',
+ '{<, <=, >, >=}',
+ '{255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}',
+ '{100, 100, 125, 125}'),
+ ('cidrcol', 'inet',
+ '{=, <, <=, >, >=}',
+ '{10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}',
+ '{2, 100, 100, 125, 125}'),
+ ('cidrcol', 'cidr',
+ '{=, <, <=, >, >=}',
+ '{10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}',
+ '{2, 100, 100, 125, 125}'),
+ ('datecol', 'date',
+ '{>, >=, =, <=, <}',
+ '{1995-08-15, 1995-08-15, 2009-12-01, 2022-12-30, 2022-12-30}',
+ '{100, 100, 1, 100, 100}'),
+ ('timecol', 'time',
+ '{>, >=, =, <=, <}',
+ '{01:20:30, 01:20:30, 02:28:57, 06:28:31.5, 06:28:31.5}',
+ '{100, 100, 1, 100, 100}'),
+ ('timestampcol', 'timestamp',
+ '{>, >=, =, <=, <}',
+ '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}',
+ '{100, 100, 1, 100, 100}'),
+ ('timestampcol', 'timestamptz',
+ '{>, >=, =, <=, <}',
+ '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}',
+ '{100, 100, 1, 100, 100}'),
+ ('timestamptzcol', 'timestamptz',
+ '{>, >=, =, <=, <}',
+ '{1972-10-10 03:00:00-04, 1972-10-10 03:00:00-04, 1972-10-19 09:00:00-07, 1972-11-20 19:00:00-03, 1972-11-20 19:00:00-03}',
+ '{100, 100, 1, 100, 100}'),
+ ('intervalcol', 'interval',
+ '{>, >=, =, <=, <}',
+ '{00:00:00, 00:00:00, 1 mons 13 days 12:24, 2 mons 23 days 07:48:00, 1 year}',
+ '{100, 100, 1, 100, 100}'),
+ ('timetzcol', 'timetz',
+ '{>, >=, =, <=, <}',
+ '{01:30:20+02, 01:30:20+02, 01:35:50+02, 23:55:05+02, 23:55:05+02}',
+ '{99, 100, 2, 100, 100}'),
+ ('numericcol', 'numeric',
+ '{>, >=, =, <=, <}',
+ '{0.00, 0.01, 2268164.347826086956521739130434782609, 99470151.9, 99470151.9}',
+ '{100, 100, 1, 100, 100}'),
+ ('uuidcol', 'uuid',
+ '{>, >=, =, <=, <}',
+ '{00040004-0004-0004-0004-000400040004, 00040004-0004-0004-0004-000400040004, 52225222-5222-5222-5222-522252225222, 99989998-9998-9998-9998-999899989998, 99989998-9998-9998-9998-999899989998}',
+ '{100, 100, 1, 100, 100}'),
+ ('lsncol', 'pg_lsn',
+ '{>, >=, =, <=, <, IS, IS NOT}',
+ '{0/1200, 0/1200, 44/455222, 198/1999799, 198/1999799, NULL, NULL}',
+ '{100, 100, 1, 100, 100, 25, 100}');
+
+DO $x$
+DECLARE
+ r record;
+ r2 record;
+ cond text;
+ idx_ctids tid[];
+ ss_ctids tid[];
+ count int;
+ plan_ok bool;
+ plan_line text;
+BEGIN
+ FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers_multi, unnest(op) WITH ORDINALITY AS oper LOOP
+
+ -- prepare the condition
+ IF r.value IS NULL THEN
+ cond := format('%I %s %L', r.colname, r.oper, r.value);
+ ELSE
+ cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ);
+ END IF;
+
+ -- run the query using the brin index
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Bitmap Heap Scan on brintest_multi%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get bitmap indexscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond)
+ INTO idx_ctids;
+
+ -- run the query using a seqscan
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Seq Scan on brintest_multi%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get seqscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond)
+ INTO ss_ctids;
+
+ -- make sure both return the same results
+ count := array_length(idx_ctids, 1);
+
+ IF NOT (count = array_length(ss_ctids, 1) AND
+ idx_ctids @> ss_ctids AND
+ idx_ctids <@ ss_ctids) THEN
+ -- report the results of each scan to make the differences obvious
+ RAISE WARNING 'something not right in %: count %', r, count;
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_multi WHERE ' || cond LOOP
+ RAISE NOTICE 'seqscan: %', r2;
+ END LOOP;
+
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_multi WHERE ' || cond LOOP
+ RAISE NOTICE 'bitmapscan: %', r2;
+ END LOOP;
+ END IF;
+
+ -- make sure we found expected number of matches
+ IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF;
+ END LOOP;
+END;
+$x$;
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+
+INSERT INTO brintest_multi SELECT
+ 142857 * tenthous,
+ thousand,
+ twothousand,
+ unique1::oid,
+ format('(%s,%s)', tenthous, twenty)::tid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ substr(md5(unique1::text), 1, 16)::macaddr8,
+ inet '10.2.3.4' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20' + hundred * interval '15 seconds',
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn
+FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5;
+
+SELECT brin_desummarize_range('brinidx_multi', 0);
+VACUUM brintest_multi; -- force a summarization cycle in brinidx
+
+-- Try inserting a values with NaN, to test distance calculation.
+insert into public.brintest_multi (float4col) values (real 'nan');
+insert into public.brintest_multi (float8col) values (real 'nan');
+
+UPDATE brintest_multi SET int8col = int8col * int4col;
+
+-- Tests for brin_summarize_new_values
+SELECT brin_summarize_new_values('brintest_multi'); -- error, not an index
+SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index
+SELECT brin_summarize_new_values('brinidx_multi'); -- ok, no change expected
+
+-- Tests for brin_desummarize_range
+SELECT brin_desummarize_range('brinidx_multi', -1); -- error, invalid range
+SELECT brin_desummarize_range('brinidx_multi', 0);
+SELECT brin_desummarize_range('brinidx_multi', 0);
+SELECT brin_desummarize_range('brinidx_multi', 100000000);
+
+-- test building an index with many values, to force compaction of the buffer
+CREATE TABLE brin_large_range (a int4);
+INSERT INTO brin_large_range SELECT i FROM generate_series(1,10000) s(i);
+CREATE INDEX brin_large_range_idx ON brin_large_range USING brin (a int4_minmax_multi_ops);
+DROP TABLE brin_large_range;
+
+-- Test brin_summarize_range
+CREATE TABLE brin_summarize_multi (
+ value int
+) WITH (fillfactor=10, autovacuum_enabled=false);
+CREATE INDEX brin_summarize_multi_idx ON brin_summarize_multi USING brin (value) WITH (pages_per_range=2);
+-- Fill a few pages
+DO $$
+DECLARE curtid tid;
+BEGIN
+ LOOP
+ INSERT INTO brin_summarize_multi VALUES (1) RETURNING ctid INTO curtid;
+ EXIT WHEN curtid > tid '(2, 0)';
+ END LOOP;
+END;
+$$;
+
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_multi_idx', 0);
+-- nothing: already summarized
+SELECT brin_summarize_range('brin_summarize_multi_idx', 1);
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_multi_idx', 2);
+-- nothing: page doesn't exist in table
+SELECT brin_summarize_range('brin_summarize_multi_idx', 4294967295);
+-- invalid block number values
+SELECT brin_summarize_range('brin_summarize_multi_idx', -1);
+SELECT brin_summarize_range('brin_summarize_multi_idx', 4294967296);
+
+
+-- test brin cost estimates behave sanely based on correlation of values
+CREATE TABLE brin_test_multi (a INT, b INT);
+INSERT INTO brin_test_multi SELECT x/100,x%100 FROM generate_series(1,10000) x(x);
+CREATE INDEX brin_test_multi_a_idx ON brin_test_multi USING brin (a) WITH (pages_per_range = 2);
+CREATE INDEX brin_test_multi_b_idx ON brin_test_multi USING brin (b) WITH (pages_per_range = 2);
+VACUUM ANALYZE brin_test_multi;
+
+-- Ensure brin index is used when columns are perfectly correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE a = 1;
+-- Ensure brin index is not used when values are not correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
new file mode 100644
index 0000000..c345022
--- /dev/null
+++ b/src/test/regress/sql/btree_index.sql
@@ -0,0 +1,185 @@
+--
+-- BTREE_INDEX
+-- test retrieval of min/max keys for each index
+--
+
+SELECT b.*
+ FROM bt_i4_heap b
+ WHERE b.seqno < 1;
+
+SELECT b.*
+ FROM bt_i4_heap b
+ WHERE b.seqno >= 9999;
+
+SELECT b.*
+ FROM bt_i4_heap b
+ WHERE b.seqno = 4500;
+
+SELECT b.*
+ FROM bt_name_heap b
+ WHERE b.seqno < '1'::name;
+
+SELECT b.*
+ FROM bt_name_heap b
+ WHERE b.seqno >= '9999'::name;
+
+SELECT b.*
+ FROM bt_name_heap b
+ WHERE b.seqno = '4500'::name;
+
+SELECT b.*
+ FROM bt_txt_heap b
+ WHERE b.seqno < '1'::text;
+
+SELECT b.*
+ FROM bt_txt_heap b
+ WHERE b.seqno >= '9999'::text;
+
+SELECT b.*
+ FROM bt_txt_heap b
+ WHERE b.seqno = '4500'::text;
+
+SELECT b.*
+ FROM bt_f8_heap b
+ WHERE b.seqno < '1'::float8;
+
+SELECT b.*
+ FROM bt_f8_heap b
+ WHERE b.seqno >= '9999'::float8;
+
+SELECT b.*
+ FROM bt_f8_heap b
+ WHERE b.seqno = '4500'::float8;
+
+--
+-- Check correct optimization of LIKE (special index operator support)
+-- for both indexscan and bitmapscan cases
+--
+
+set enable_seqscan to false;
+set enable_indexscan to true;
+set enable_bitmapscan to false;
+explain (costs off)
+select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
+select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
+explain (costs off)
+select proname from pg_proc where proname ilike '00%foo' order by 1;
+select proname from pg_proc where proname ilike '00%foo' order by 1;
+explain (costs off)
+select proname from pg_proc where proname ilike 'ri%foo' order by 1;
+
+set enable_indexscan to false;
+set enable_bitmapscan to true;
+explain (costs off)
+select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
+select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
+explain (costs off)
+select proname from pg_proc where proname ilike '00%foo' order by 1;
+select proname from pg_proc where proname ilike '00%foo' order by 1;
+explain (costs off)
+select proname from pg_proc where proname ilike 'ri%foo' order by 1;
+
+reset enable_seqscan;
+reset enable_indexscan;
+reset enable_bitmapscan;
+
+-- Also check LIKE optimization with binary-compatible cases
+
+create temp table btree_bpchar (f1 text collate "C");
+create index on btree_bpchar(f1 bpchar_ops) WITH (deduplicate_items=on);
+insert into btree_bpchar values ('foo'), ('fool'), ('bar'), ('quux');
+-- doesn't match index:
+explain (costs off)
+select * from btree_bpchar where f1 like 'foo';
+select * from btree_bpchar where f1 like 'foo';
+explain (costs off)
+select * from btree_bpchar where f1 like 'foo%';
+select * from btree_bpchar where f1 like 'foo%';
+-- these do match the index:
+explain (costs off)
+select * from btree_bpchar where f1::bpchar like 'foo';
+select * from btree_bpchar where f1::bpchar like 'foo';
+explain (costs off)
+select * from btree_bpchar where f1::bpchar like 'foo%';
+select * from btree_bpchar where f1::bpchar like 'foo%';
+
+-- get test coverage for "single value" deduplication strategy:
+insert into btree_bpchar select 'foo' from generate_series(1,1500);
+
+--
+-- Perform unique checking, with and without the use of deduplication
+--
+CREATE TABLE dedup_unique_test_table (a int) WITH (autovacuum_enabled=false);
+CREATE UNIQUE INDEX dedup_unique ON dedup_unique_test_table (a) WITH (deduplicate_items=on);
+CREATE UNIQUE INDEX plain_unique ON dedup_unique_test_table (a) WITH (deduplicate_items=off);
+-- Generate enough garbage tuples in index to ensure that even the unique index
+-- with deduplication enabled has to check multiple leaf pages during unique
+-- checking (at least with a BLCKSZ of 8192 or less)
+DO $$
+BEGIN
+ FOR r IN 1..1350 LOOP
+ DELETE FROM dedup_unique_test_table;
+ INSERT INTO dedup_unique_test_table SELECT 1;
+ END LOOP;
+END$$;
+
+-- Exercise the LP_DEAD-bit-set tuple deletion code with a posting list tuple.
+-- The implementation prefers deleting existing items to merging any duplicate
+-- tuples into a posting list, so we need an explicit test to make sure we get
+-- coverage (note that this test also assumes BLCKSZ is 8192 or less):
+DROP INDEX plain_unique;
+DELETE FROM dedup_unique_test_table WHERE a = 1;
+INSERT INTO dedup_unique_test_table SELECT i FROM generate_series(0,450) i;
+
+--
+-- Test B-tree fast path (cache rightmost leaf page) optimization.
+--
+
+-- First create a tree that's at least three levels deep (i.e. has one level
+-- between the root and leaf levels). The text inserted is long. It won't be
+-- TOAST compressed because we use plain storage in the table. Only a few
+-- index tuples fit on each internal page, allowing us to get a tall tree with
+-- few pages. (A tall tree is required to trigger caching.)
+--
+-- The text column must be the leading column in the index, since suffix
+-- truncation would otherwise truncate tuples on internal pages, leaving us
+-- with a short tree.
+create table btree_tall_tbl(id int4, t text);
+alter table btree_tall_tbl alter COLUMN t set storage plain;
+create index btree_tall_idx on btree_tall_tbl (t, id) with (fillfactor = 10);
+insert into btree_tall_tbl select g, repeat('x', 250)
+from generate_series(1, 130) g;
+
+--
+-- Test for multilevel page deletion
+--
+CREATE TABLE delete_test_table (a bigint, b bigint, c bigint, d bigint);
+INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,80000) i;
+ALTER TABLE delete_test_table ADD PRIMARY KEY (a,b,c,d);
+-- Delete most entries, and vacuum, deleting internal pages and creating "fast
+-- root"
+DELETE FROM delete_test_table WHERE a < 79990;
+VACUUM delete_test_table;
+
+--
+-- Test B-tree insertion with a metapage update (XLOG_BTREE_INSERT_META
+-- WAL record type). This happens when a "fast root" page is split. This
+-- also creates coverage for nbtree FSM page recycling.
+--
+-- The vacuum above should've turned the leaf page into a fast root. We just
+-- need to insert some rows to cause the fast root page to split.
+INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,1000) i;
+
+-- Test unsupported btree opclass parameters
+create index on btree_tall_tbl (id int4_ops(foo=1));
+
+-- Test case of ALTER INDEX with abuse of column names for indexes.
+-- This grammar is not officially supported, but the parser allows it.
+CREATE INDEX btree_tall_idx2 ON btree_tall_tbl (id);
+ALTER INDEX btree_tall_idx2 ALTER COLUMN id SET (n_distinct=100);
+DROP INDEX btree_tall_idx2;
+-- Partitioned index
+CREATE TABLE btree_part (id int4) PARTITION BY RANGE (id);
+CREATE INDEX btree_part_idx ON btree_part(id);
+ALTER INDEX btree_part_idx ALTER COLUMN id SET (n_distinct=100);
+DROP TABLE btree_part;
diff --git a/src/test/regress/sql/case.sql b/src/test/regress/sql/case.sql
new file mode 100644
index 0000000..83fe43b
--- /dev/null
+++ b/src/test/regress/sql/case.sql
@@ -0,0 +1,265 @@
+--
+-- 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 '<NULL>' 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;
+
+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;
+
+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/char.sql b/src/test/regress/sql/char.sql
new file mode 100644
index 0000000..79e6e56
--- /dev/null
+++ b/src/test/regress/sql/char.sql
@@ -0,0 +1,75 @@
+--
+-- CHAR
+--
+
+-- fixed-length by value
+-- internally passed by value if <= 4 bytes in storage
+
+SELECT char 'c' = char 'c' AS true;
+
+--
+-- Build a table for testing
+--
+
+CREATE TABLE CHAR_TBL(f1 char);
+
+INSERT INTO CHAR_TBL (f1) VALUES ('a');
+
+INSERT INTO CHAR_TBL (f1) VALUES ('A');
+
+-- any of the following three input formats are acceptable
+INSERT INTO CHAR_TBL (f1) VALUES ('1');
+
+INSERT INTO CHAR_TBL (f1) VALUES (2);
+
+INSERT INTO CHAR_TBL (f1) VALUES ('3');
+
+-- zero-length char
+INSERT INTO CHAR_TBL (f1) VALUES ('');
+
+-- try char's of greater than 1 length
+INSERT INTO CHAR_TBL (f1) VALUES ('cd');
+INSERT INTO CHAR_TBL (f1) VALUES ('c ');
+
+
+SELECT * FROM CHAR_TBL;
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 <> 'a';
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 = 'a';
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 < 'a';
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 <= 'a';
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 > 'a';
+
+SELECT c.*
+ FROM CHAR_TBL c
+ WHERE c.f1 >= 'a';
+
+DROP TABLE CHAR_TBL;
+
+--
+-- Now test longer arrays of char
+--
+
+CREATE TABLE CHAR_TBL(f1 char(4));
+
+INSERT INTO CHAR_TBL (f1) VALUES ('a');
+INSERT INTO CHAR_TBL (f1) VALUES ('ab');
+INSERT INTO CHAR_TBL (f1) VALUES ('abcd');
+INSERT INTO CHAR_TBL (f1) VALUES ('abcde');
+INSERT INTO CHAR_TBL (f1) VALUES ('abcd ');
+
+SELECT * FROM CHAR_TBL;
diff --git a/src/test/regress/sql/circle.sql b/src/test/regress/sql/circle.sql
new file mode 100644
index 0000000..8c8a0f8
--- /dev/null
+++ b/src/test/regress/sql/circle.sql
@@ -0,0 +1,57 @@
+--
+-- CIRCLE
+--
+
+-- Back off displayed precision a little bit to reduce platform-to-platform
+-- variation in results.
+SET extra_float_digits = -1;
+
+CREATE TABLE CIRCLE_TBL (f1 circle);
+
+INSERT INTO CIRCLE_TBL VALUES ('<(5,1),3>');
+
+INSERT INTO CIRCLE_TBL VALUES ('((1,2),100)');
+
+INSERT INTO CIRCLE_TBL VALUES (' 1 , 3 , 5 ');
+
+INSERT INTO CIRCLE_TBL VALUES (' ( ( 1 , 2 ) , 3 ) ');
+
+INSERT INTO CIRCLE_TBL VALUES (' ( 100 , 200 ) , 10 ');
+
+INSERT INTO CIRCLE_TBL VALUES (' < ( 100 , 1 ) , 115 > ');
+
+INSERT INTO CIRCLE_TBL VALUES ('<(3,5),0>'); -- Zero radius
+
+INSERT INTO CIRCLE_TBL VALUES ('<(3,5),NaN>'); -- NaN radius
+
+-- bad values
+
+INSERT INTO CIRCLE_TBL VALUES ('<(-100,0),-100>');
+
+INSERT INTO CIRCLE_TBL VALUES ('<(100,200),10');
+
+INSERT INTO CIRCLE_TBL VALUES ('<(100,200),10> x');
+
+INSERT INTO CIRCLE_TBL VALUES ('1abc,3,5');
+
+INSERT INTO CIRCLE_TBL VALUES ('(3,(1,2),3)');
+
+SELECT * FROM CIRCLE_TBL;
+
+SELECT center(f1) AS center
+ FROM CIRCLE_TBL;
+
+SELECT radius(f1) AS radius
+ FROM CIRCLE_TBL;
+
+SELECT diameter(f1) AS diameter
+ FROM CIRCLE_TBL;
+
+SELECT f1 FROM CIRCLE_TBL WHERE radius(f1) < 5;
+
+SELECT f1 FROM CIRCLE_TBL WHERE diameter(f1) >= 10;
+
+SELECT c1.f1 AS one, c2.f1 AS two, (c1.f1 <-> c2.f1) AS distance
+ FROM CIRCLE_TBL c1, CIRCLE_TBL c2
+ WHERE (c1.f1 < c2.f1) AND ((c1.f1 <-> c2.f1) > 0)
+ ORDER BY distance, area(c1.f1), area(c2.f1);
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
new file mode 100644
index 0000000..99ee533
--- /dev/null
+++ b/src/test/regress/sql/cluster.sql
@@ -0,0 +1,280 @@
+--
+-- CLUSTER
+--
+
+CREATE TABLE clstr_tst_s (rf_a SERIAL PRIMARY KEY,
+ b INT);
+
+CREATE TABLE clstr_tst (a SERIAL PRIMARY KEY,
+ b INT,
+ c TEXT,
+ d TEXT,
+ CONSTRAINT clstr_tst_con FOREIGN KEY (b) REFERENCES clstr_tst_s);
+
+CREATE INDEX clstr_tst_b ON clstr_tst (b);
+CREATE INDEX clstr_tst_c ON clstr_tst (c);
+CREATE INDEX clstr_tst_c_b ON clstr_tst (c,b);
+CREATE INDEX clstr_tst_b_c ON clstr_tst (b,c);
+
+INSERT INTO clstr_tst_s (b) VALUES (0);
+INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s;
+INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s;
+INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s;
+INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s;
+INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s;
+
+CREATE TABLE clstr_tst_inh () INHERITS (clstr_tst);
+
+INSERT INTO clstr_tst (b, c) VALUES (11, 'once');
+INSERT INTO clstr_tst (b, c) VALUES (10, 'diez');
+INSERT INTO clstr_tst (b, c) VALUES (31, 'treinta y uno');
+INSERT INTO clstr_tst (b, c) VALUES (22, 'veintidos');
+INSERT INTO clstr_tst (b, c) VALUES (3, 'tres');
+INSERT INTO clstr_tst (b, c) VALUES (20, 'veinte');
+INSERT INTO clstr_tst (b, c) VALUES (23, 'veintitres');
+INSERT INTO clstr_tst (b, c) VALUES (21, 'veintiuno');
+INSERT INTO clstr_tst (b, c) VALUES (4, 'cuatro');
+INSERT INTO clstr_tst (b, c) VALUES (14, 'catorce');
+INSERT INTO clstr_tst (b, c) VALUES (2, 'dos');
+INSERT INTO clstr_tst (b, c) VALUES (18, 'dieciocho');
+INSERT INTO clstr_tst (b, c) VALUES (27, 'veintisiete');
+INSERT INTO clstr_tst (b, c) VALUES (25, 'veinticinco');
+INSERT INTO clstr_tst (b, c) VALUES (13, 'trece');
+INSERT INTO clstr_tst (b, c) VALUES (28, 'veintiocho');
+INSERT INTO clstr_tst (b, c) VALUES (32, 'treinta y dos');
+INSERT INTO clstr_tst (b, c) VALUES (5, 'cinco');
+INSERT INTO clstr_tst (b, c) VALUES (29, 'veintinueve');
+INSERT INTO clstr_tst (b, c) VALUES (1, 'uno');
+INSERT INTO clstr_tst (b, c) VALUES (24, 'veinticuatro');
+INSERT INTO clstr_tst (b, c) VALUES (30, 'treinta');
+INSERT INTO clstr_tst (b, c) VALUES (12, 'doce');
+INSERT INTO clstr_tst (b, c) VALUES (17, 'diecisiete');
+INSERT INTO clstr_tst (b, c) VALUES (9, 'nueve');
+INSERT INTO clstr_tst (b, c) VALUES (19, 'diecinueve');
+INSERT INTO clstr_tst (b, c) VALUES (26, 'veintiseis');
+INSERT INTO clstr_tst (b, c) VALUES (15, 'quince');
+INSERT INTO clstr_tst (b, c) VALUES (7, 'siete');
+INSERT INTO clstr_tst (b, c) VALUES (16, 'dieciseis');
+INSERT INTO clstr_tst (b, c) VALUES (8, 'ocho');
+-- This entry is needed to test that TOASTED values are copied correctly.
+INSERT INTO clstr_tst (b, c, d) VALUES (6, 'seis', repeat('xyzzy', 100000));
+
+CLUSTER clstr_tst_c ON clstr_tst;
+
+SELECT a,b,c,substring(d for 30), length(d) from clstr_tst;
+SELECT a,b,c,substring(d for 30), length(d) from clstr_tst ORDER BY a;
+SELECT a,b,c,substring(d for 30), length(d) from clstr_tst ORDER BY b;
+SELECT a,b,c,substring(d for 30), length(d) from clstr_tst ORDER BY c;
+
+-- Verify that inheritance link still works
+INSERT INTO clstr_tst_inh VALUES (0, 100, 'in child table');
+SELECT a,b,c,substring(d for 30), length(d) from clstr_tst;
+
+-- Verify that foreign key link still works
+INSERT INTO clstr_tst (b, c) VALUES (1111, 'this should fail');
+
+SELECT conname FROM pg_constraint WHERE conrelid = 'clstr_tst'::regclass
+ORDER BY 1;
+
+
+SELECT relname, relkind,
+ EXISTS(SELECT 1 FROM pg_class WHERE oid = c.reltoastrelid) AS hastoast
+FROM pg_class c WHERE relname LIKE 'clstr_tst%' ORDER BY relname;
+
+-- Verify that indisclustered is correctly set
+SELECT pg_class.relname FROM pg_index, pg_class, pg_class AS pg_class_2
+WHERE pg_class.oid=indexrelid
+ AND indrelid=pg_class_2.oid
+ AND pg_class_2.relname = 'clstr_tst'
+ AND indisclustered;
+
+-- Try changing indisclustered
+ALTER TABLE clstr_tst CLUSTER ON clstr_tst_b_c;
+SELECT pg_class.relname FROM pg_index, pg_class, pg_class AS pg_class_2
+WHERE pg_class.oid=indexrelid
+ AND indrelid=pg_class_2.oid
+ AND pg_class_2.relname = 'clstr_tst'
+ AND indisclustered;
+
+-- Try turning off all clustering
+ALTER TABLE clstr_tst SET WITHOUT CLUSTER;
+SELECT pg_class.relname FROM pg_index, pg_class, pg_class AS pg_class_2
+WHERE pg_class.oid=indexrelid
+ AND indrelid=pg_class_2.oid
+ AND pg_class_2.relname = 'clstr_tst'
+ AND indisclustered;
+
+-- Verify that clustering all tables does in fact cluster the right ones
+CREATE USER regress_clstr_user;
+CREATE TABLE clstr_1 (a INT PRIMARY KEY);
+CREATE TABLE clstr_2 (a INT PRIMARY KEY);
+CREATE TABLE clstr_3 (a INT PRIMARY KEY);
+ALTER TABLE clstr_1 OWNER TO regress_clstr_user;
+ALTER TABLE clstr_3 OWNER TO regress_clstr_user;
+GRANT SELECT ON clstr_2 TO regress_clstr_user;
+INSERT INTO clstr_1 VALUES (2);
+INSERT INTO clstr_1 VALUES (1);
+INSERT INTO clstr_2 VALUES (2);
+INSERT INTO clstr_2 VALUES (1);
+INSERT INTO clstr_3 VALUES (2);
+INSERT INTO clstr_3 VALUES (1);
+
+-- "CLUSTER <tablename>" on a table that hasn't been clustered
+CLUSTER clstr_2;
+
+CLUSTER clstr_1_pkey ON clstr_1;
+CLUSTER clstr_2 USING clstr_2_pkey;
+SELECT * FROM clstr_1 UNION ALL
+ SELECT * FROM clstr_2 UNION ALL
+ SELECT * FROM clstr_3;
+
+-- revert to the original state
+DELETE FROM clstr_1;
+DELETE FROM clstr_2;
+DELETE FROM clstr_3;
+INSERT INTO clstr_1 VALUES (2);
+INSERT INTO clstr_1 VALUES (1);
+INSERT INTO clstr_2 VALUES (2);
+INSERT INTO clstr_2 VALUES (1);
+INSERT INTO clstr_3 VALUES (2);
+INSERT INTO clstr_3 VALUES (1);
+
+-- this user can only cluster clstr_1 and clstr_3, but the latter
+-- has not been clustered
+SET SESSION AUTHORIZATION regress_clstr_user;
+CLUSTER;
+SELECT * FROM clstr_1 UNION ALL
+ SELECT * FROM clstr_2 UNION ALL
+ SELECT * FROM clstr_3;
+
+-- cluster a single table using the indisclustered bit previously set
+DELETE FROM clstr_1;
+INSERT INTO clstr_1 VALUES (2);
+INSERT INTO clstr_1 VALUES (1);
+CLUSTER clstr_1;
+SELECT * FROM clstr_1;
+
+-- Test MVCC-safety of cluster. There isn't much we can do to verify the
+-- results with a single backend...
+
+CREATE TABLE clustertest (key int PRIMARY KEY);
+
+INSERT INTO clustertest VALUES (10);
+INSERT INTO clustertest VALUES (20);
+INSERT INTO clustertest VALUES (30);
+INSERT INTO clustertest VALUES (40);
+INSERT INTO clustertest VALUES (50);
+
+-- Use a transaction so that updates are not committed when CLUSTER sees 'em
+BEGIN;
+
+-- Test update where the old row version is found first in the scan
+UPDATE clustertest SET key = 100 WHERE key = 10;
+
+-- Test update where the new row version is found first in the scan
+UPDATE clustertest SET key = 35 WHERE key = 40;
+
+-- Test longer update chain
+UPDATE clustertest SET key = 60 WHERE key = 50;
+UPDATE clustertest SET key = 70 WHERE key = 60;
+UPDATE clustertest SET key = 80 WHERE key = 70;
+
+SELECT * FROM clustertest;
+CLUSTER clustertest_pkey ON clustertest;
+SELECT * FROM clustertest;
+
+COMMIT;
+
+SELECT * FROM clustertest;
+
+-- check that temp tables can be clustered
+create temp table clstr_temp (col1 int primary key, col2 text);
+insert into clstr_temp values (2, 'two'), (1, 'one');
+cluster clstr_temp using clstr_temp_pkey;
+select * from clstr_temp;
+drop table clstr_temp;
+
+RESET SESSION AUTHORIZATION;
+
+-- check clustering an empty table
+DROP TABLE clustertest;
+CREATE TABLE clustertest (f1 int PRIMARY KEY);
+CLUSTER clustertest USING clustertest_pkey;
+CLUSTER clustertest;
+
+-- Check that partitioned tables cannot be clustered
+CREATE TABLE clstrpart (a int) PARTITION BY RANGE (a);
+CREATE INDEX clstrpart_idx ON clstrpart (a);
+ALTER TABLE clstrpart CLUSTER ON clstrpart_idx;
+CLUSTER clstrpart USING clstrpart_idx;
+DROP TABLE clstrpart;
+
+-- Test CLUSTER with external tuplesorting
+
+create table clstr_4 as select * from tenk1;
+create index cluster_sort on clstr_4 (hundred, thousand, tenthous);
+-- ensure we don't use the index in CLUSTER nor the checking SELECTs
+set enable_indexscan = off;
+
+-- Use external sort:
+set maintenance_work_mem = '1MB';
+cluster clstr_4 using cluster_sort;
+select * from
+(select hundred, lag(hundred) over () as lhundred,
+ thousand, lag(thousand) over () as lthousand,
+ tenthous, lag(tenthous) over () as ltenthous from clstr_4) ss
+where row(hundred, thousand, tenthous) <= row(lhundred, lthousand, ltenthous);
+
+reset enable_indexscan;
+reset maintenance_work_mem;
+
+-- test CLUSTER on expression index
+CREATE TABLE clstr_expression(id serial primary key, a int, b text COLLATE "C");
+INSERT INTO clstr_expression(a, b) SELECT g.i % 42, 'prefix'||g.i FROM generate_series(1, 133) g(i);
+CREATE INDEX clstr_expression_minus_a ON clstr_expression ((-a), b);
+CREATE INDEX clstr_expression_upper_b ON clstr_expression ((upper(b)));
+
+-- verify indexes work before cluster
+BEGIN;
+SET LOCAL enable_seqscan = false;
+EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
+SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
+EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
+SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
+COMMIT;
+
+-- and after clustering on clstr_expression_minus_a
+CLUSTER clstr_expression USING clstr_expression_minus_a;
+WITH rows AS
+ (SELECT ctid, lag(a) OVER (ORDER BY ctid) AS la, a FROM clstr_expression)
+SELECT * FROM rows WHERE la < a;
+BEGIN;
+SET LOCAL enable_seqscan = false;
+EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
+SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
+EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
+SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
+COMMIT;
+
+-- and after clustering on clstr_expression_upper_b
+CLUSTER clstr_expression USING clstr_expression_upper_b;
+WITH rows AS
+ (SELECT ctid, lag(b) OVER (ORDER BY ctid) AS lb, b FROM clstr_expression)
+SELECT * FROM rows WHERE upper(lb) > upper(b);
+BEGIN;
+SET LOCAL enable_seqscan = false;
+EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
+SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3';
+EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
+SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b;
+COMMIT;
+
+-- clean up
+DROP TABLE clustertest;
+DROP TABLE clstr_1;
+DROP TABLE clstr_2;
+DROP TABLE clstr_3;
+DROP TABLE clstr_4;
+DROP TABLE clstr_expression;
+
+DROP USER regress_clstr_user;
diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql
new file mode 100644
index 0000000..9cee3d0
--- /dev/null
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -0,0 +1,758 @@
+/*
+ * 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, lc_collate = ' ||
+ quote_literal(current_setting('lc_collate')) ||
+ ', lc_ctype = ' ||
+ quote_literal(current_setting('lc_ctype')) || ');';
+END
+$$;
+CREATE COLLATION test3 (provider = icu, lc_collate = 'en_US.utf8'); -- fail, need lc_ctype
+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; -- 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;
+
+
+-- 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);
+
+CREATE TABLE test6 (a int, b text);
+-- same string in different normal forms
+INSERT INTO test6 VALUES (1, U&'\00E4bc');
+INSERT INTO test6 VALUES (2, U&'\0061\0308bc');
+SELECT * FROM test6;
+SELECT * FROM test6 WHERE b = 'äbc' COLLATE ctest_det;
+SELECT * FROM test6 WHERE b = 'äbc' COLLATE ctest_nondet;
+
+-- same with arrays
+CREATE TABLE test6a (a int, b text[]);
+INSERT INTO test6a VALUES (1, ARRAY[U&'\00E4bc']);
+INSERT INTO test6a VALUES (2, ARRAY[U&'\0061\0308bc']);
+SELECT * FROM test6a;
+SELECT * FROM test6a WHERE b = ARRAY['äbc'] COLLATE ctest_det;
+SELECT * FROM test6a WHERE b = ARRAY['äbc'] COLLATE ctest_nondet;
+
+CREATE COLLATION case_sensitive (provider = icu, locale = '');
+CREATE COLLATION case_insensitive (provider = icu, locale = '@colStrength=secondary', deterministic = false);
+
+SELECT 'abc' <= 'ABC' COLLATE case_sensitive, 'abc' >= 'ABC' COLLATE case_sensitive;
+SELECT 'abc' <= 'ABC' COLLATE case_insensitive, 'abc' >= 'ABC' COLLATE case_insensitive;
+
+CREATE TABLE test1cs (x text COLLATE case_sensitive);
+CREATE TABLE test2cs (x text COLLATE case_sensitive);
+CREATE TABLE test3cs (x text COLLATE case_sensitive);
+INSERT INTO test1cs VALUES ('abc'), ('def'), ('ghi');
+INSERT INTO test2cs VALUES ('ABC'), ('ghi');
+INSERT INTO test3cs VALUES ('abc'), ('ABC'), ('def'), ('ghi');
+
+SELECT x FROM test3cs WHERE x = 'abc';
+SELECT x FROM test3cs WHERE x <> 'abc';
+SELECT x FROM test3cs WHERE x LIKE 'a%';
+SELECT x FROM test3cs WHERE x ILIKE 'a%';
+SELECT x FROM test3cs WHERE x SIMILAR TO 'a%';
+SELECT x FROM test3cs WHERE x ~ 'a';
+SELECT x FROM test1cs UNION SELECT x FROM test2cs ORDER BY x;
+SELECT x FROM test2cs UNION SELECT x FROM test1cs ORDER BY x;
+SELECT x FROM test1cs INTERSECT SELECT x FROM test2cs;
+SELECT x FROM test2cs INTERSECT SELECT x FROM test1cs;
+SELECT x FROM test1cs EXCEPT SELECT x FROM test2cs;
+SELECT x FROM test2cs EXCEPT SELECT x FROM test1cs;
+SELECT DISTINCT x FROM test3cs ORDER BY x;
+SELECT count(DISTINCT x) FROM test3cs;
+SELECT x, count(*) FROM test3cs GROUP BY x ORDER BY x;
+SELECT x, row_number() OVER (ORDER BY x), rank() OVER (ORDER BY x) FROM test3cs ORDER BY x;
+CREATE UNIQUE INDEX ON test1cs (x); -- ok
+INSERT INTO test1cs VALUES ('ABC'); -- ok
+CREATE UNIQUE INDEX ON test3cs (x); -- ok
+SELECT string_to_array('ABC,DEF,GHI' COLLATE case_sensitive, ',', 'abc');
+SELECT string_to_array('ABCDEFGHI' COLLATE case_sensitive, NULL, 'b');
+
+CREATE TABLE test1ci (x text COLLATE case_insensitive);
+CREATE TABLE test2ci (x text COLLATE case_insensitive);
+CREATE TABLE test3ci (x text COLLATE case_insensitive);
+CREATE INDEX ON test3ci (x text_pattern_ops); -- error
+INSERT INTO test1ci VALUES ('abc'), ('def'), ('ghi');
+INSERT INTO test2ci VALUES ('ABC'), ('ghi');
+INSERT INTO test3ci VALUES ('abc'), ('ABC'), ('def'), ('ghi');
+
+SELECT x FROM test3ci WHERE x = 'abc';
+SELECT x FROM test3ci WHERE x <> 'abc';
+SELECT x FROM test3ci WHERE x LIKE 'a%';
+SELECT x FROM test3ci WHERE x ILIKE 'a%';
+SELECT x FROM test3ci WHERE x SIMILAR TO 'a%';
+SELECT x FROM test3ci WHERE x ~ 'a';
+SELECT x FROM test1ci UNION SELECT x FROM test2ci ORDER BY x;
+SELECT x FROM test2ci UNION SELECT x FROM test1ci ORDER BY x;
+SELECT x FROM test1ci INTERSECT SELECT x FROM test2ci ORDER BY x;
+SELECT x FROM test2ci INTERSECT SELECT x FROM test1ci ORDER BY x;
+SELECT x FROM test1ci EXCEPT SELECT x FROM test2ci;
+SELECT x FROM test2ci EXCEPT SELECT x FROM test1ci;
+SELECT DISTINCT x FROM test3ci ORDER BY x;
+SELECT count(DISTINCT x) FROM test3ci;
+SELECT x, count(*) FROM test3ci GROUP BY x ORDER BY x;
+SELECT x, row_number() OVER (ORDER BY x), rank() OVER (ORDER BY x) FROM test3ci ORDER BY x;
+CREATE UNIQUE INDEX ON test1ci (x); -- ok
+INSERT INTO test1ci VALUES ('ABC'); -- error
+CREATE UNIQUE INDEX ON test3ci (x); -- error
+SELECT string_to_array('ABC,DEF,GHI' COLLATE case_insensitive, ',', 'abc');
+SELECT string_to_array('ABCDEFGHI' COLLATE case_insensitive, NULL, 'b');
+
+-- bpchar
+CREATE TABLE test1bpci (x char(3) COLLATE case_insensitive);
+CREATE TABLE test2bpci (x char(3) COLLATE case_insensitive);
+CREATE TABLE test3bpci (x char(3) COLLATE case_insensitive);
+CREATE INDEX ON test3bpci (x bpchar_pattern_ops); -- error
+INSERT INTO test1bpci VALUES ('abc'), ('def'), ('ghi');
+INSERT INTO test2bpci VALUES ('ABC'), ('ghi');
+INSERT INTO test3bpci VALUES ('abc'), ('ABC'), ('def'), ('ghi');
+
+SELECT x FROM test3bpci WHERE x = 'abc';
+SELECT x FROM test3bpci WHERE x <> 'abc';
+SELECT x FROM test3bpci WHERE x LIKE 'a%';
+SELECT x FROM test3bpci WHERE x ILIKE 'a%';
+SELECT x FROM test3bpci WHERE x SIMILAR TO 'a%';
+SELECT x FROM test3bpci WHERE x ~ 'a';
+SELECT x FROM test1bpci UNION SELECT x FROM test2bpci ORDER BY x;
+SELECT x FROM test2bpci UNION SELECT x FROM test1bpci ORDER BY x;
+SELECT x FROM test1bpci INTERSECT SELECT x FROM test2bpci ORDER BY x;
+SELECT x FROM test2bpci INTERSECT SELECT x FROM test1bpci ORDER BY x;
+SELECT x FROM test1bpci EXCEPT SELECT x FROM test2bpci;
+SELECT x FROM test2bpci EXCEPT SELECT x FROM test1bpci;
+SELECT DISTINCT x FROM test3bpci ORDER BY x;
+SELECT count(DISTINCT x) FROM test3bpci;
+SELECT x, count(*) FROM test3bpci GROUP BY x ORDER BY x;
+SELECT x, row_number() OVER (ORDER BY x), rank() OVER (ORDER BY x) FROM test3bpci ORDER BY x;
+CREATE UNIQUE INDEX ON test1bpci (x); -- ok
+INSERT INTO test1bpci VALUES ('ABC'); -- error
+CREATE UNIQUE INDEX ON test3bpci (x); -- error
+SELECT string_to_array('ABC,DEF,GHI'::char(11) COLLATE case_insensitive, ',', 'abc');
+SELECT string_to_array('ABCDEFGHI'::char(9) COLLATE case_insensitive, NULL, 'b');
+
+-- This tests the issue described in match_pattern_prefix(). In the
+-- absence of that check, the case_insensitive tests below would
+-- return no rows where they should logically return one.
+CREATE TABLE test4c (x text COLLATE "C");
+INSERT INTO test4c VALUES ('abc');
+CREATE INDEX ON test4c (x);
+SET enable_seqscan = off;
+SELECT x FROM test4c WHERE x LIKE 'ABC' COLLATE case_sensitive; -- ok, no rows
+SELECT x FROM test4c WHERE x LIKE 'ABC%' COLLATE case_sensitive; -- ok, no rows
+SELECT x FROM test4c WHERE x LIKE 'ABC' COLLATE case_insensitive; -- error
+SELECT x FROM test4c WHERE x LIKE 'ABC%' COLLATE case_insensitive; -- error
+RESET enable_seqscan;
+
+-- Unicode special case: different variants of Greek lower case sigma.
+-- A naive implementation like citext that just does lower(x) =
+-- lower(y) will do the wrong thing here, because lower('Σ') is 'σ'
+-- but upper('ς') is 'Σ'.
+SELECT 'ὀδυσσεÏÏ‚' = 'ὈΔΥΣΣΕΎΣ' COLLATE case_sensitive;
+SELECT 'ὀδυσσεÏÏ‚' = 'ὈΔΥΣΣΕΎΣ' COLLATE case_insensitive;
+
+-- name vs. text comparison operators
+SELECT relname FROM pg_class WHERE relname = 'PG_CLASS'::text COLLATE case_insensitive;
+SELECT relname FROM pg_class WHERE 'PG_CLASS'::text = relname COLLATE case_insensitive;
+
+SELECT typname FROM pg_type WHERE typname LIKE 'int_' AND typname <> 'INT2'::text
+ COLLATE case_insensitive ORDER BY typname;
+SELECT typname FROM pg_type WHERE typname LIKE 'int_' AND 'INT2'::text <> typname
+ COLLATE case_insensitive ORDER BY typname;
+
+-- test case adapted from subselect.sql
+CREATE TEMP TABLE outer_text (f1 text COLLATE case_insensitive, f2 text);
+INSERT INTO outer_text VALUES ('a', 'a');
+INSERT INTO outer_text VALUES ('b', 'a');
+INSERT INTO outer_text VALUES ('A', NULL);
+INSERT INTO outer_text VALUES ('B', NULL);
+
+CREATE TEMP TABLE inner_text (c1 text COLLATE case_insensitive, c2 text);
+INSERT INTO inner_text VALUES ('a', NULL);
+
+SELECT * FROM outer_text WHERE (f1, f2) NOT IN (SELECT * FROM inner_text);
+
+-- accents
+CREATE COLLATION ignore_accents (provider = icu, locale = '@colStrength=primary;colCaseLevel=yes', deterministic = false);
+
+CREATE TABLE test4 (a int, b text);
+INSERT INTO test4 VALUES (1, 'cote'), (2, 'côte'), (3, 'coté'), (4, 'côté');
+SELECT * FROM test4 WHERE b = 'cote';
+SELECT * FROM test4 WHERE b = 'cote' COLLATE ignore_accents;
+SELECT * FROM test4 WHERE b = 'Cote' COLLATE ignore_accents; -- still case-sensitive
+SELECT * FROM test4 WHERE b = 'Cote' COLLATE case_insensitive;
+
+-- foreign keys (should use collation of primary key)
+
+-- PK is case-sensitive, FK is case-insensitive
+CREATE TABLE test10pk (x text COLLATE case_sensitive PRIMARY KEY);
+INSERT INTO test10pk VALUES ('abc'), ('def'), ('ghi');
+CREATE TABLE test10fk (x text COLLATE case_insensitive REFERENCES test10pk (x) ON UPDATE CASCADE ON DELETE CASCADE);
+INSERT INTO test10fk VALUES ('abc'); -- ok
+INSERT INTO test10fk VALUES ('ABC'); -- error
+INSERT INTO test10fk VALUES ('xyz'); -- error
+SELECT * FROM test10pk;
+SELECT * FROM test10fk;
+-- restrict update even though the values are "equal" in the FK table
+UPDATE test10fk SET x = 'ABC' WHERE x = 'abc'; -- error
+SELECT * FROM test10fk;
+DELETE FROM test10pk WHERE x = 'abc';
+SELECT * FROM test10pk;
+SELECT * FROM test10fk;
+
+-- PK is case-insensitive, FK is case-sensitive
+CREATE TABLE test11pk (x text COLLATE case_insensitive PRIMARY KEY);
+INSERT INTO test11pk VALUES ('abc'), ('def'), ('ghi');
+CREATE TABLE test11fk (x text COLLATE case_sensitive REFERENCES test11pk (x) ON UPDATE CASCADE ON DELETE CASCADE);
+INSERT INTO test11fk VALUES ('abc'); -- ok
+INSERT INTO test11fk VALUES ('ABC'); -- ok
+INSERT INTO test11fk VALUES ('xyz'); -- error
+SELECT * FROM test11pk;
+SELECT * FROM test11fk;
+-- cascade update even though the values are "equal" in the PK table
+UPDATE test11pk SET x = 'ABC' WHERE x = 'abc';
+SELECT * FROM test11fk;
+DELETE FROM test11pk WHERE x = 'abc';
+SELECT * FROM test11pk;
+SELECT * FROM test11fk;
+
+-- partitioning
+CREATE TABLE test20 (a int, b text COLLATE case_insensitive) PARTITION BY LIST (b);
+CREATE TABLE test20_1 PARTITION OF test20 FOR VALUES IN ('abc');
+INSERT INTO test20 VALUES (1, 'abc');
+INSERT INTO test20 VALUES (2, 'ABC');
+SELECT * FROM test20_1;
+
+CREATE TABLE test21 (a int, b text COLLATE case_insensitive) PARTITION BY RANGE (b);
+CREATE TABLE test21_1 PARTITION OF test21 FOR VALUES FROM ('ABC') TO ('DEF');
+INSERT INTO test21 VALUES (1, 'abc');
+INSERT INTO test21 VALUES (2, 'ABC');
+SELECT * FROM test21_1;
+
+CREATE TABLE test22 (a int, b text COLLATE case_sensitive) PARTITION BY HASH (b);
+CREATE TABLE test22_0 PARTITION OF test22 FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE test22_1 PARTITION OF test22 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO test22 VALUES (1, 'def');
+INSERT INTO test22 VALUES (2, 'DEF');
+-- they end up in different partitions
+SELECT (SELECT count(*) FROM test22_0) = (SELECT count(*) FROM test22_1);
+
+-- same with arrays
+CREATE TABLE test22a (a int, b text[] COLLATE case_sensitive) PARTITION BY HASH (b);
+CREATE TABLE test22a_0 PARTITION OF test22a FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE test22a_1 PARTITION OF test22a FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO test22a VALUES (1, ARRAY['def']);
+INSERT INTO test22a VALUES (2, ARRAY['DEF']);
+-- they end up in different partitions
+SELECT (SELECT count(*) FROM test22a_0) = (SELECT count(*) FROM test22a_1);
+
+CREATE TABLE test23 (a int, b text COLLATE case_insensitive) PARTITION BY HASH (b);
+CREATE TABLE test23_0 PARTITION OF test23 FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE test23_1 PARTITION OF test23 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO test23 VALUES (1, 'def');
+INSERT INTO test23 VALUES (2, 'DEF');
+-- they end up in the same partition (but it's platform-dependent which one)
+SELECT (SELECT count(*) FROM test23_0) <> (SELECT count(*) FROM test23_1);
+
+-- same with arrays
+CREATE TABLE test23a (a int, b text[] COLLATE case_insensitive) PARTITION BY HASH (b);
+CREATE TABLE test23a_0 PARTITION OF test23a FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE test23a_1 PARTITION OF test23a FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO test23a VALUES (1, ARRAY['def']);
+INSERT INTO test23a VALUES (2, ARRAY['DEF']);
+-- they end up in the same partition (but it's platform-dependent which one)
+SELECT (SELECT count(*) FROM test23a_0) <> (SELECT count(*) FROM test23a_1);
+
+CREATE TABLE test30 (a int, b char(3) COLLATE case_insensitive) PARTITION BY LIST (b);
+CREATE TABLE test30_1 PARTITION OF test30 FOR VALUES IN ('abc');
+INSERT INTO test30 VALUES (1, 'abc');
+INSERT INTO test30 VALUES (2, 'ABC');
+SELECT * FROM test30_1;
+
+CREATE TABLE test31 (a int, b char(3) COLLATE case_insensitive) PARTITION BY RANGE (b);
+CREATE TABLE test31_1 PARTITION OF test31 FOR VALUES FROM ('ABC') TO ('DEF');
+INSERT INTO test31 VALUES (1, 'abc');
+INSERT INTO test31 VALUES (2, 'ABC');
+SELECT * FROM test31_1;
+
+CREATE TABLE test32 (a int, b char(3) COLLATE case_sensitive) PARTITION BY HASH (b);
+CREATE TABLE test32_0 PARTITION OF test32 FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE test32_1 PARTITION OF test32 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO test32 VALUES (1, 'def');
+INSERT INTO test32 VALUES (2, 'DEF');
+-- they end up in different partitions
+SELECT (SELECT count(*) FROM test32_0) = (SELECT count(*) FROM test32_1);
+
+CREATE TABLE test33 (a int, b char(3) COLLATE case_insensitive) PARTITION BY HASH (b);
+CREATE TABLE test33_0 PARTITION OF test33 FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE test33_1 PARTITION OF test33 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO test33 VALUES (1, 'def');
+INSERT INTO test33 VALUES (2, 'DEF');
+-- they end up in the same partition (but it's platform-dependent which one)
+SELECT (SELECT count(*) FROM test33_0) <> (SELECT count(*) FROM test33_1);
+
+
+-- 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/collate.linux.utf8.sql b/src/test/regress/sql/collate.linux.utf8.sql
new file mode 100644
index 0000000..cbbd220
--- /dev/null
+++ b/src/test/regress/sql/collate.linux.utf8.sql
@@ -0,0 +1,455 @@
+/*
+ * This test is for Linux/glibc systems and assumes that a full set of
+ * locales is installed. It must be run in a database with UTF-8 encoding,
+ * because other encodings don't support all the characters used.
+ */
+
+SELECT getdatabaseencoding() <> 'UTF8' OR
+ (SELECT count(*) FROM pg_collation WHERE collname IN ('de_DE', 'en_US', 'sv_SE', 'tr_TR') AND collencoding = pg_char_to_encoding('UTF8')) <> 4 OR
+ version() !~ 'linux-gnu'
+ 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_US" NOT NULL
+);
+
+\d collate_test1
+
+CREATE TABLE collate_test_fail (
+ a int,
+ b text COLLATE "ja_JP.eucjp"
+);
+
+CREATE TABLE collate_test_fail (
+ a int,
+ b text COLLATE "foo"
+);
+
+CREATE TABLE collate_test_fail (
+ a int COLLATE "en_US",
+ b text
+);
+
+CREATE TABLE collate_test_like (
+ LIKE collate_test1
+);
+
+\d collate_test_like
+
+CREATE TABLE collate_test2 (
+ a int,
+ b text COLLATE "sv_SE"
+);
+
+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_US";
+
+
+CREATE DOMAIN testdomain_sv AS text COLLATE "sv_SE";
+CREATE DOMAIN testdomain_i AS int COLLATE "sv_SE"; -- 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_US"
+);
+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_US" > 'äbc' COLLATE "en_US" AS "true";
+SELECT 'bbc' COLLATE "sv_SE" > 'äbc' COLLATE "sv_SE" AS "false";
+
+-- upper/lower
+
+CREATE TABLE collate_test10 (
+ a int,
+ x text COLLATE "en_US",
+ y text COLLATE "tr_TR"
+);
+
+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_US" ILIKE '%KI%' AS "true";
+SELECT 'Türkiye' COLLATE "tr_TR" ILIKE '%KI%' AS "false";
+
+SELECT 'bıt' ILIKE 'BIT' COLLATE "en_US" AS "false";
+SELECT 'bıt' ILIKE 'BIT' COLLATE "tr_TR" 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_US"
+);
+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_US" ~* 'KI' AS "true";
+SELECT 'Türkiye' COLLATE "tr_TR" ~* 'KI' AS "false";
+
+SELECT 'bıt' ~* 'BIT' COLLATE "en_US" AS "false";
+SELECT 'bıt' ~* 'BIT' COLLATE "tr_TR" AS "true";
+
+-- The following actually exercises the selectivity estimation for ~*.
+SELECT relname FROM pg_class WHERE relname ~* '^abc';
+
+
+-- to_char
+
+SET lc_time TO 'tr_TR';
+SELECT to_char(date '2010-02-01', 'DD TMMON YYYY');
+SELECT to_char(date '2010-02-01', 'DD TMMON YYYY' COLLATE "tr_TR");
+SELECT to_char(date '2010-04-01', 'DD TMMON YYYY');
+SELECT to_char(date '2010-04-01', 'DD TMMON YYYY' COLLATE "tr_TR");
+
+-- to_date
+
+SELECT to_date('01 ÅžUB 2010', 'DD TMMON YYYY');
+SELECT to_date('01 Åžub 2010', 'DD TMMON YYYY');
+SELECT to_date('1234567890ab 2010', 'TMMONTH YYYY'); -- fail
+
+
+-- 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_US"),('b')) t(x)
+ UNION ALL
+ SELECT (x || 'c') COLLATE "de_DE" 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_US") 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;
+
+
+-- 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 (locale = ' ||
+ quote_literal(current_setting('lc_collate')) || ');';
+END
+$$;
+CREATE COLLATION test0 FROM "C"; -- fail, duplicate name
+CREATE COLLATION IF NOT EXISTS test0 FROM "C"; -- ok, skipped
+CREATE COLLATION IF NOT EXISTS test0 (locale = 'foo'); -- ok, skipped
+do $$
+BEGIN
+ EXECUTE 'CREATE COLLATION test1 (lc_collate = ' ||
+ quote_literal(current_setting('lc_collate')) ||
+ ', lc_ctype = ' ||
+ quote_literal(current_setting('lc_ctype')) || ');';
+END
+$$;
+CREATE COLLATION test3 (lc_collate = 'en_US.utf8'); -- fail, need lc_ctype
+CREATE COLLATION testx (locale = 'nonsense'); -- fail
+
+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; -- 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_US" REFRESH 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_US");
+
+select textrange_c('A','Z') @> 'b'::text;
+select textrange_en_us('A','Z') @> 'b'::text;
+
+drop type textrange_c;
+drop type textrange_en_us;
+
+
+-- nondeterministic collations
+-- (not supported with libc provider)
+
+CREATE COLLATION ctest_det (locale = 'en_US.utf8', deterministic = true);
+CREATE COLLATION ctest_nondet (locale = 'en_US.utf8', deterministic = false);
+
+
+-- cleanup
+SET client_min_messages TO warning;
+DROP SCHEMA collate_tests CASCADE;
diff --git a/src/test/regress/sql/collate.sql b/src/test/regress/sql/collate.sql
new file mode 100644
index 0000000..30f811b
--- /dev/null
+++ b/src/test/regress/sql/collate.sql
@@ -0,0 +1,281 @@
+/*
+ * 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);
+-- 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
+
+
+--
+-- 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/combocid.sql b/src/test/regress/sql/combocid.sql
new file mode 100644
index 0000000..a5cdf6d
--- /dev/null
+++ b/src/test/regress/sql/combocid.sql
@@ -0,0 +1,111 @@
+--
+-- 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;
+
+SAVEPOINT s1;
+
+UPDATE combocidtest SET foobar = foobar + 10;
+
+-- here we should see only updated tuples
+SELECT ctid,cmin,* FROM combocidtest;
+
+ROLLBACK TO s1;
+
+-- now we should see old tuples, but with combo CIDs starting at 0
+SELECT ctid,cmin,* FROM combocidtest;
+
+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;
+
+SAVEPOINT s1;
+
+-- this doesn't 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;
+
+ROLLBACK TO s1;
+
+SELECT ctid,cmin,* FROM combocidtest;
+
+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/comments.sql b/src/test/regress/sql/comments.sql
new file mode 100644
index 0000000..e47db1a
--- /dev/null
+++ b/src/test/regress/sql/comments.sql
@@ -0,0 +1,42 @@
+--
+-- COMMENTS
+--
+
+SELECT 'trailing' AS first; -- trailing single line
+SELECT /* embedded single line */ 'embedded' AS second;
+SELECT /* both embedded and trailing single line */ 'both' AS third; -- trailing single line
+
+SELECT 'before multi-line' AS fourth;
+/* This is an example of SQL which should not execute:
+ * select 'multi-line';
+ */
+SELECT 'after multi-line' AS fifth;
+
+--
+-- Nested comments
+--
+
+/*
+SELECT 'trailing' as x1; -- inside block comment
+*/
+
+/* This block comment surrounds a query which itself has a block comment...
+SELECT /* embedded single line */ 'embedded' AS x2;
+*/
+
+SELECT -- continued after the following block comments...
+/* Deeply nested comment.
+ This includes a single apostrophe to make sure we aren't decoding this part as a string.
+SELECT 'deep nest' AS n1;
+/* Second level of nesting...
+SELECT 'deeper nest' as n2;
+/* Third level of nesting...
+SELECT 'deepest nest' as n3;
+*/
+Hoo boy. Still two deep...
+*/
+Now just one deep...
+*/
+'deeply nested example' AS sixth;
+
+/* and this is the end of the file */
diff --git a/src/test/regress/sql/compression.sql b/src/test/regress/sql/compression.sql
new file mode 100644
index 0000000..86332dc
--- /dev/null
+++ b/src/test/regress/sql/compression.sql
@@ -0,0 +1,153 @@
+\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 TABLE cmdata(f1 text COMPRESSION pglz);
+CREATE INDEX idx ON cmdata(f1);
+INSERT INTO cmdata VALUES(repeat('1234567890', 1000));
+\d+ cmdata
+CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
+INSERT INTO cmdata1 VALUES(repeat('1234567890', 1004));
+\d+ cmdata1
+
+-- 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;
+\d+ cmmove1
+SELECT pg_column_compression(f1) FROM cmmove1;
+
+-- copy to existing table
+CREATE TABLE cmmove3(f1 text COMPRESSION pglz);
+INSERT INTO cmmove3 SELECT * FROM cmdata;
+INSERT INTO cmmove3 SELECT * FROM cmdata1;
+SELECT pg_column_compression(f1) FROM cmmove3;
+
+-- test LIKE INCLUDING COMPRESSION
+CREATE TABLE cmdata2 (LIKE cmdata1 INCLUDING COMPRESSION);
+\d+ cmdata2
+DROP TABLE cmdata2;
+
+-- try setting compression for incompressible data type
+CREATE TABLE cmdata2 (f1 int COMPRESSION pglz);
+
+-- update using datum from different table
+CREATE TABLE cmmove2(f1 text COMPRESSION pglz);
+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 TABLE cmdata2 (f1 text COMPRESSION pglz);
+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 TABLE cmdata2 (f1 int);
+\d+ cmdata2
+ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
+\d+ cmdata2
+ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
+\d+ cmdata2
+
+--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;
+\d+ cmdata2
+ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
+\d+ cmdata2
+INSERT INTO cmdata2 VALUES (repeat('123456789', 800));
+SELECT pg_column_compression(f1) FROM cmdata2;
+
+-- 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;
+
+-- test compression with partition
+CREATE TABLE cmpart(f1 text COMPRESSION lz4) PARTITION BY HASH(f1);
+CREATE TABLE cmpart1 PARTITION OF cmpart FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE cmpart2(f1 text COMPRESSION pglz);
+
+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 TABLE cminh() INHERITS(cmdata, cmdata1);
+CREATE TABLE cminh(f1 TEXT COMPRESSION lz4) INHERITS(cmdata);
+
+-- 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));
+\d+ cmdata
+SELECT pg_column_compression(f1) FROM cmdata;
+
+ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default;
+\d+ cmdata2
+
+-- test alter compression method for materialized views
+ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
+\d+ compressmv
+
+-- 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 TABLE cmdata2 (f1 TEXT COMPRESSION pglz, f2 TEXT COMPRESSION lz4);
+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 TABLE badcompresstbl (a text COMPRESSION I_Do_Not_Exist_Compression); -- fails
+CREATE TABLE badcompresstbl (a text);
+ALTER TABLE badcompresstbl ALTER a SET COMPRESSION I_Do_Not_Exist_Compression; -- fails
+DROP TABLE badcompresstbl;
+
+\set HIDE_TOAST_COMPRESSION true
diff --git a/src/test/regress/sql/conversion.sql b/src/test/regress/sql/conversion.sql
new file mode 100644
index 0000000..8358682
--- /dev/null
+++ b/src/test/regress/sql/conversion.sql
@@ -0,0 +1,221 @@
+--
+-- create user defined conversion
+--
+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;
+--
+-- cannot make same name conversion in same schema
+--
+CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
+--
+-- create default conversion with qualified name
+--
+CREATE DEFAULT CONVERSION public.mydef FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
+--
+-- cannot make default conversion with same schema/for_encoding/to_encoding
+--
+CREATE DEFAULT CONVERSION public.mydef2 FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
+-- test comments
+COMMENT ON CONVERSION myconv_bad IS 'foo';
+COMMENT ON CONVERSION myconv IS 'bar';
+COMMENT ON CONVERSION myconv IS NULL;
+--
+-- drop user defined conversion
+--
+DROP CONVERSION myconv;
+DROP CONVERSION mydef;
+--
+-- Note: the built-in conversions are exercised in opr_sanity.sql,
+-- so there's no need to do that here.
+--
+--
+-- return to the super user
+--
+RESET SESSION AUTHORIZATION;
+DROP USER regress_conversion_user;
+
+--
+-- Test built-in conversion functions.
+--
+
+-- Helper function to test a conversion. Uses the test_enc_conversion function
+-- that was created in the create_function_0 test.
+create or replace function test_conv(
+ input IN bytea,
+ src_encoding IN text,
+ dst_encoding IN text,
+
+ result OUT bytea,
+ errorat OUT bytea,
+ error OUT text)
+language plpgsql as
+$$
+declare
+ validlen int;
+begin
+ -- First try to perform the conversion with noError = false. If that errors out,
+ -- capture the error message, and try again with noError = true. The second call
+ -- should succeed and return the position of the error, return that too.
+ begin
+ select * into validlen, result from test_enc_conversion(input, src_encoding, dst_encoding, false);
+ errorat = NULL;
+ error := NULL;
+ exception when others then
+ error := sqlerrm;
+ select * into validlen, result from test_enc_conversion(input, src_encoding, dst_encoding, true);
+ errorat = substr(input, validlen + 1);
+ end;
+ return;
+end;
+$$;
+
+
+--
+-- UTF-8
+--
+CREATE TABLE utf8_inputs (inbytes bytea, description text);
+insert into utf8_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\xc3a4c3b6', 'valid, extra latin chars'),
+ ('\xd184d0bed0be', 'valid, cyrillic'),
+ ('\x666f6fe8b1a1', 'valid, kanji/Chinese'),
+ ('\xe382abe3829a', 'valid, two chars that combine to one in EUC_JIS_2004'),
+ ('\xe382ab', 'only first half of combined char in EUC_JIS_2004'),
+ ('\xe382abe382', 'incomplete combination when converted EUC_JIS_2004'),
+ ('\xecbd94eb81bceba6ac', 'valid, Hangul, Korean'),
+ ('\x666f6fefa8aa', 'valid, needs mapping function to convert to GB18030'),
+ ('\x66e8b1ff6f6f', 'invalid byte sequence'),
+ ('\x66006f', 'invalid, NUL byte'),
+ ('\x666f6fe8b100', 'invalid, NUL byte'),
+ ('\x666f6fe8b1', 'incomplete character at end');
+
+-- Test UTF-8 verification
+select description, (test_conv(inbytes, 'utf8', 'utf8')).* from utf8_inputs;
+-- Test conversions from UTF-8
+select description, inbytes, (test_conv(inbytes, 'utf8', 'euc_jis_2004')).* from utf8_inputs;
+select description, inbytes, (test_conv(inbytes, 'utf8', 'latin1')).* from utf8_inputs;
+select description, inbytes, (test_conv(inbytes, 'utf8', 'latin2')).* from utf8_inputs;
+select description, inbytes, (test_conv(inbytes, 'utf8', 'latin5')).* from utf8_inputs;
+select description, inbytes, (test_conv(inbytes, 'utf8', 'koi8r')).* from utf8_inputs;
+select description, inbytes, (test_conv(inbytes, 'utf8', 'gb18030')).* from utf8_inputs;
+
+--
+-- EUC_JIS_2004
+--
+CREATE TABLE euc_jis_2004_inputs (inbytes bytea, description text);
+insert into euc_jis_2004_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\x666f6fbedd', 'valid'),
+ ('\xa5f7', 'valid, translates to two UTF-8 chars '),
+ ('\xbeddbe', 'incomplete char '),
+ ('\x666f6f00bedd', 'invalid, NUL byte'),
+ ('\x666f6fbe00dd', 'invalid, NUL byte'),
+ ('\x666f6fbedd00', 'invalid, NUL byte'),
+ ('\xbe04', 'invalid byte sequence');
+
+-- Test EUC_JIS_2004 verification
+select description, inbytes, (test_conv(inbytes, 'euc_jis_2004', 'euc_jis_2004')).* from euc_jis_2004_inputs;
+-- Test conversions from EUC_JIS_2004
+select description, inbytes, (test_conv(inbytes, 'euc_jis_2004', 'utf8')).* from euc_jis_2004_inputs;
+
+--
+-- SHIFT-JIS-2004
+--
+CREATE TABLE shiftjis2004_inputs (inbytes bytea, description text);
+insert into shiftjis2004_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\x666f6f8fdb', 'valid'),
+ ('\x666f6f81c0', 'valid, no translation to UTF-8'),
+ ('\x666f6f82f5', 'valid, translates to two UTF-8 chars '),
+ ('\x666f6f8fdb8f', 'incomplete char '),
+ ('\x666f6f820a', 'incomplete char, followed by newline '),
+ ('\x666f6f008fdb', 'invalid, NUL byte'),
+ ('\x666f6f8f00db', 'invalid, NUL byte'),
+ ('\x666f6f8fdb00', 'invalid, NUL byte');
+
+-- Test SHIFT-JIS-2004 verification
+select description, inbytes, (test_conv(inbytes, 'shiftjis2004', 'shiftjis2004')).* from shiftjis2004_inputs;
+-- Test conversions from SHIFT-JIS-2004
+select description, inbytes, (test_conv(inbytes, 'shiftjis2004', 'utf8')).* from shiftjis2004_inputs;
+select description, inbytes, (test_conv(inbytes, 'shiftjis2004', 'euc_jis_2004')).* from shiftjis2004_inputs;
+
+--
+-- GB18030
+--
+CREATE TABLE gb18030_inputs (inbytes bytea, description text);
+insert into gb18030_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\x666f6fcff3', 'valid'),
+ ('\x666f6f8431a530', 'valid, no translation to UTF-8'),
+ ('\x666f6f84309c38', 'valid, translates to UTF-8 by mapping function'),
+ ('\x666f6f84309c', 'incomplete char '),
+ ('\x666f6f84309c0a', 'incomplete char, followed by newline '),
+ ('\x666f6f84309c3800', 'invalid, NUL byte'),
+ ('\x666f6f84309c0038', 'invalid, NUL byte');
+
+-- Test GB18030 verification
+select description, inbytes, (test_conv(inbytes, 'gb18030', 'gb18030')).* from gb18030_inputs;
+-- Test conversions from GB18030
+select description, inbytes, (test_conv(inbytes, 'gb18030', 'utf8')).* from gb18030_inputs;
+
+
+--
+-- ISO-8859-5
+--
+CREATE TABLE iso8859_5_inputs (inbytes bytea, description text);
+insert into iso8859_5_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\xe4dede', 'valid'),
+ ('\x00', 'invalid, NUL byte'),
+ ('\xe400dede', 'invalid, NUL byte'),
+ ('\xe4dede00', 'invalid, NUL byte');
+
+-- Test ISO-8859-5 verification
+select description, inbytes, (test_conv(inbytes, 'iso8859-5', 'iso8859-5')).* from iso8859_5_inputs;
+-- Test conversions from ISO-8859-5
+select description, inbytes, (test_conv(inbytes, 'iso8859-5', 'utf8')).* from iso8859_5_inputs;
+select description, inbytes, (test_conv(inbytes, 'iso8859-5', 'koi8r')).* from iso8859_5_inputs;
+select description, inbytes, (test_conv(inbytes, 'iso8859_5', 'mule_internal')).* from iso8859_5_inputs;
+
+--
+-- Big5
+--
+CREATE TABLE big5_inputs (inbytes bytea, description text);
+insert into big5_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\x666f6fb648', 'valid'),
+ ('\x666f6fa27f', 'valid, no translation to UTF-8'),
+ ('\x666f6fb60048', 'invalid, NUL byte'),
+ ('\x666f6fb64800', 'invalid, NUL byte');
+
+-- Test Big5 verification
+select description, inbytes, (test_conv(inbytes, 'big5', 'big5')).* from big5_inputs;
+-- Test conversions from Big5
+select description, inbytes, (test_conv(inbytes, 'big5', 'utf8')).* from big5_inputs;
+select description, inbytes, (test_conv(inbytes, 'big5', 'mule_internal')).* from big5_inputs;
+
+--
+-- MULE_INTERNAL
+--
+CREATE TABLE mic_inputs (inbytes bytea, description text);
+insert into mic_inputs values
+ ('\x666f6f', 'valid, pure ASCII'),
+ ('\x8bc68bcf8bcf', 'valid (in KOI8R)'),
+ ('\x8bc68bcf8b', 'invalid,incomplete char'),
+ ('\x92bedd', 'valid (in SHIFT_JIS)'),
+ ('\x92be', 'invalid, incomplete char)'),
+ ('\x666f6f95a3c1', 'valid (in Big5)'),
+ ('\x666f6f95a3', 'invalid, incomplete char'),
+ ('\x9200bedd', 'invalid, NUL byte'),
+ ('\x92bedd00', 'invalid, NUL byte'),
+ ('\x8b00c68bcf8bcf', 'invalid, NUL byte');
+
+-- Test MULE_INTERNAL verification
+select description, inbytes, (test_conv(inbytes, 'mule_internal', 'mule_internal')).* from mic_inputs;
+-- Test conversions from MULE_INTERNAL
+select description, inbytes, (test_conv(inbytes, 'mule_internal', 'koi8r')).* from mic_inputs;
+select description, inbytes, (test_conv(inbytes, 'mule_internal', 'iso8859-5')).* from mic_inputs;
+select description, inbytes, (test_conv(inbytes, 'mule_internal', 'sjis')).* from mic_inputs;
+select description, inbytes, (test_conv(inbytes, 'mule_internal', 'big5')).* from mic_inputs;
+select description, inbytes, (test_conv(inbytes, 'mule_internal', 'euc_jp')).* from mic_inputs;
diff --git a/src/test/regress/sql/copy2.sql b/src/test/regress/sql/copy2.sql
new file mode 100644
index 0000000..b3c16af
--- /dev/null
+++ b/src/test/regress/sql/copy2.sql
@@ -0,0 +1,470 @@
+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;
+TRUNCATE vistest;
+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;
+TRUNCATE vistest;
+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;
+TRUNCATE vistest;
+ROLLBACK TO SAVEPOINT s1;
+COPY vistest FROM stdin CSV FREEZE;
+d3
+e
+\.
+COMMIT;
+CREATE FUNCTION truncate_in_subxact() RETURNS VOID AS
+$$
+BEGIN
+ TRUNCATE vistest;
+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;
+
+-- 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;
+
+-- 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/copydml.sql b/src/test/regress/sql/copydml.sql
new file mode 100644
index 0000000..4578342
--- /dev/null
+++ b/src/test/regress/sql/copydml.sql
@@ -0,0 +1,91 @@
+--
+-- Test cases for COPY (INSERT/UPDATE/DELETE) TO
+--
+create table copydml_test (id serial, t text);
+insert into copydml_test (t) values ('a');
+insert into copydml_test (t) values ('b');
+insert into copydml_test (t) values ('c');
+insert into copydml_test (t) values ('d');
+insert into copydml_test (t) values ('e');
+
+--
+-- Test COPY (insert/update/delete ...)
+--
+copy (insert into copydml_test (t) values ('f') returning id) to stdout;
+copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
+copy (delete from copydml_test where t = 'g' returning id) to stdout;
+
+--
+-- Test \copy (insert/update/delete ...)
+--
+\copy (insert into copydml_test (t) values ('f') returning id) to stdout;
+\copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
+\copy (delete from copydml_test where t = 'g' returning id) to stdout;
+
+-- Error cases
+copy (insert into copydml_test default values) to stdout;
+copy (update copydml_test set t = 'g') to stdout;
+copy (delete from copydml_test) to stdout;
+
+create rule qqq as on insert to copydml_test do instead nothing;
+copy (insert into copydml_test default values) to stdout;
+drop rule qqq on copydml_test;
+create rule qqq as on insert to copydml_test do also delete from copydml_test;
+copy (insert into copydml_test default values) to stdout;
+drop rule qqq on copydml_test;
+create rule qqq as on insert to copydml_test do instead (delete from copydml_test; delete from copydml_test);
+copy (insert into copydml_test default values) to stdout;
+drop rule qqq on copydml_test;
+create rule qqq as on insert to copydml_test where new.t <> 'f' do instead delete from copydml_test;
+copy (insert into copydml_test default values) to stdout;
+drop rule qqq on copydml_test;
+
+create rule qqq as on update to copydml_test do instead nothing;
+copy (update copydml_test set t = 'f') to stdout;
+drop rule qqq on copydml_test;
+create rule qqq as on update to copydml_test do also delete from copydml_test;
+copy (update copydml_test set t = 'f') to stdout;
+drop rule qqq on copydml_test;
+create rule qqq as on update to copydml_test do instead (delete from copydml_test; delete from copydml_test);
+copy (update copydml_test set t = 'f') to stdout;
+drop rule qqq on copydml_test;
+create rule qqq as on update to copydml_test where new.t <> 'f' do instead delete from copydml_test;
+copy (update copydml_test set t = 'f') to stdout;
+drop rule qqq on copydml_test;
+
+create rule qqq as on delete to copydml_test do instead nothing;
+copy (delete from copydml_test) to stdout;
+drop rule qqq on copydml_test;
+create rule qqq as on delete to copydml_test do also insert into copydml_test default values;
+copy (delete from copydml_test) to stdout;
+drop rule qqq on copydml_test;
+create rule qqq as on delete to copydml_test do instead (insert into copydml_test default values; insert into copydml_test default values);
+copy (delete from copydml_test) to stdout;
+drop rule qqq on copydml_test;
+create rule qqq as on delete to copydml_test where old.t <> 'f' do instead insert into copydml_test default values;
+copy (delete from copydml_test) to stdout;
+drop rule qqq on copydml_test;
+
+-- triggers
+create function qqq_trig() returns trigger as $$
+begin
+if tg_op in ('INSERT', 'UPDATE') then
+ raise notice '% % %', tg_when, tg_op, new.id;
+ return new;
+else
+ raise notice '% % %', tg_when, tg_op, old.id;
+ return old;
+end if;
+end
+$$ language plpgsql;
+create trigger qqqbef before insert or update or delete on copydml_test
+ for each row execute procedure qqq_trig();
+create trigger qqqaf after insert or update or delete on copydml_test
+ for each row execute procedure qqq_trig();
+
+copy (insert into copydml_test (t) values ('f') returning id) to stdout;
+copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
+copy (delete from copydml_test where t = 'g' returning id) to stdout;
+
+drop table copydml_test;
+drop function qqq_trig();
diff --git a/src/test/regress/sql/copyselect.sql b/src/test/regress/sql/copyselect.sql
new file mode 100644
index 0000000..1d98dad
--- /dev/null
+++ b/src/test/regress/sql/copyselect.sql
@@ -0,0 +1,96 @@
+--
+-- Test cases for COPY (select) TO
+--
+create table test1 (id serial, t text);
+insert into test1 (t) values ('a');
+insert into test1 (t) values ('b');
+insert into test1 (t) values ('c');
+insert into test1 (t) values ('d');
+insert into test1 (t) values ('e');
+
+create table test2 (id serial, t text);
+insert into test2 (t) values ('A');
+insert into test2 (t) values ('B');
+insert into test2 (t) values ('C');
+insert into test2 (t) values ('D');
+insert into test2 (t) values ('E');
+
+create view v_test1
+as select 'v_'||t from test1;
+
+--
+-- Test COPY table TO
+--
+copy test1 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 for update) 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)) 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 test1 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;
+drop table test3;
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
new file mode 100644
index 0000000..d4b4036
--- /dev/null
+++ b/src/test/regress/sql/create_aggregate.sql
@@ -0,0 +1,330 @@
+--
+-- CREATE_AGGREGATE
+--
+
+-- all functions CREATEd
+CREATE AGGREGATE newavg (
+ sfunc = int4_avg_accum, basetype = int4, stype = _int8,
+ finalfunc = int8_avg,
+ initcond1 = '{0,0}'
+);
+
+-- test comments
+COMMENT ON AGGREGATE newavg_wrong (int4) IS 'an agg comment';
+COMMENT ON AGGREGATE newavg (int4) IS 'an agg comment';
+COMMENT ON AGGREGATE newavg (int4) IS NULL;
+
+-- without finalfunc; test obsolete spellings 'sfunc1' etc
+CREATE AGGREGATE newsum (
+ sfunc1 = int4pl, basetype = int4, stype1 = int4,
+ initcond1 = '0'
+);
+
+-- zero-argument aggregate
+CREATE AGGREGATE newcnt (*) (
+ sfunc = int8inc, stype = int8,
+ initcond = '0', parallel = safe
+);
+
+-- old-style spelling of same (except without parallel-safe; that's too new)
+CREATE AGGREGATE oldcnt (
+ sfunc = int8inc, basetype = 'ANY', stype = int8,
+ initcond = '0'
+);
+
+-- aggregate that only cares about null/nonnull input
+CREATE AGGREGATE newcnt ("any") (
+ sfunc = int8inc_any, stype = int8,
+ initcond = '0'
+);
+
+COMMENT ON AGGREGATE nosuchagg (*) IS 'should fail';
+COMMENT ON AGGREGATE newcnt (*) IS 'an agg(*) comment';
+COMMENT ON AGGREGATE newcnt ("any") IS 'an agg(any) comment';
+
+-- multi-argument aggregate
+create function sum3(int8,int8,int8) returns int8 as
+'select $1 + $2 + $3' language sql strict immutable;
+
+create aggregate sum2(int8,int8) (
+ sfunc = sum3, stype = int8,
+ initcond = '0'
+);
+
+-- multi-argument aggregates sensitive to distinct/order, strict/nonstrict
+create type aggtype as (a integer, b integer, c text);
+
+create function aggf_trans(aggtype[],integer,integer,text) returns aggtype[]
+as 'select array_append($1,ROW($2,$3,$4)::aggtype)'
+language sql strict immutable;
+
+create function aggfns_trans(aggtype[],integer,integer,text) returns aggtype[]
+as 'select array_append($1,ROW($2,$3,$4)::aggtype)'
+language sql immutable;
+
+create aggregate aggfstr(integer,integer,text) (
+ sfunc = aggf_trans, stype = aggtype[],
+ initcond = '{}'
+);
+
+create aggregate aggfns(integer,integer,text) (
+ sfunc = aggfns_trans, stype = aggtype[], sspace = 10000,
+ initcond = '{}'
+);
+
+-- check error cases that would require run-time type coercion
+create function least_accum(int8, int8) returns int8 language sql as
+ 'select least($1, $2)';
+
+create aggregate least_agg(int4) (
+ stype = int8, sfunc = least_accum
+); -- fails
+
+drop function least_accum(int8, int8);
+
+create function least_accum(anycompatible, anycompatible)
+returns anycompatible language sql as
+ 'select least($1, $2)';
+
+create aggregate least_agg(int4) (
+ stype = int8, sfunc = least_accum
+); -- fails
+
+create aggregate least_agg(int8) (
+ stype = int8, sfunc = least_accum
+);
+
+drop function least_accum(anycompatible, anycompatible) cascade;
+
+-- variadic aggregates
+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
+);
+
+create function cleast_accum(anycompatible, variadic anycompatiblearray)
+returns anycompatible language sql as
+ 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)';
+
+create aggregate cleast_agg(variadic items anycompatiblearray) (
+ stype = anycompatible, sfunc = cleast_accum
+);
+
+-- test ordered-set aggs using built-in support functions
+create aggregate my_percentile_disc(float8 ORDER BY anyelement) (
+ stype = internal,
+ sfunc = ordered_set_transition,
+ finalfunc = percentile_disc_final,
+ finalfunc_extra = true,
+ finalfunc_modify = read_write
+);
+
+create aggregate my_rank(VARIADIC "any" ORDER BY VARIADIC "any") (
+ stype = internal,
+ sfunc = ordered_set_transition_multi,
+ finalfunc = rank_final,
+ finalfunc_extra = true,
+ hypothetical
+);
+
+alter aggregate my_percentile_disc(float8 ORDER BY anyelement)
+ rename to test_percentile_disc;
+alter aggregate my_rank(VARIADIC "any" ORDER BY VARIADIC "any")
+ rename to test_rank;
+
+\da test_*
+
+-- moving-aggregate options
+
+CREATE AGGREGATE sumdouble (float8)
+(
+ stype = float8,
+ sfunc = float8pl,
+ mstype = float8,
+ msfunc = float8pl,
+ minvfunc = float8mi
+);
+
+-- aggregate combine and serialization functions
+
+-- can't specify just one of serialfunc and deserialfunc
+CREATE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ serialfunc = numeric_avg_serialize
+);
+
+-- serialfunc must have correct parameters
+CREATE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ serialfunc = numeric_avg_deserialize,
+ deserialfunc = numeric_avg_deserialize
+);
+
+-- deserialfunc must have correct parameters
+CREATE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ serialfunc = numeric_avg_serialize,
+ deserialfunc = numeric_avg_serialize
+);
+
+-- ensure combine function parameters are checked
+CREATE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ serialfunc = numeric_avg_serialize,
+ deserialfunc = numeric_avg_deserialize,
+ combinefunc = int4larger
+);
+
+-- ensure create aggregate works.
+CREATE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ finalfunc = numeric_avg,
+ serialfunc = numeric_avg_serialize,
+ deserialfunc = numeric_avg_deserialize,
+ combinefunc = numeric_avg_combine,
+ finalfunc_modify = shareable -- just to test a non-default setting
+);
+
+-- Ensure all these functions made it into the catalog
+SELECT aggfnoid, aggtransfn, aggcombinefn, aggtranstype::regtype,
+ aggserialfn, aggdeserialfn, aggfinalmodify
+FROM pg_aggregate
+WHERE aggfnoid = 'myavg'::REGPROC;
+
+DROP AGGREGATE myavg (numeric);
+
+-- create or replace aggregate
+CREATE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ finalfunc = numeric_avg
+);
+
+CREATE OR REPLACE AGGREGATE myavg (numeric)
+(
+ stype = internal,
+ sfunc = numeric_avg_accum,
+ finalfunc = numeric_avg,
+ serialfunc = numeric_avg_serialize,
+ deserialfunc = numeric_avg_deserialize,
+ combinefunc = numeric_avg_combine,
+ finalfunc_modify = shareable -- just to test a non-default setting
+);
+
+-- Ensure all these functions made it into the catalog again
+SELECT aggfnoid, aggtransfn, aggcombinefn, aggtranstype::regtype,
+ aggserialfn, aggdeserialfn, aggfinalmodify
+FROM pg_aggregate
+WHERE aggfnoid = 'myavg'::REGPROC;
+
+-- can change stype:
+CREATE OR REPLACE AGGREGATE myavg (numeric)
+(
+ stype = numeric,
+ sfunc = numeric_add
+);
+SELECT aggfnoid, aggtransfn, aggcombinefn, aggtranstype::regtype,
+ aggserialfn, aggdeserialfn, aggfinalmodify
+FROM pg_aggregate
+WHERE aggfnoid = 'myavg'::REGPROC;
+
+-- can't change return type:
+CREATE OR REPLACE AGGREGATE myavg (numeric)
+(
+ stype = numeric,
+ sfunc = numeric_add,
+ finalfunc = numeric_out
+);
+
+-- can't change to a different kind:
+CREATE OR REPLACE AGGREGATE myavg (order by numeric)
+(
+ stype = numeric,
+ sfunc = numeric_add
+);
+
+-- can't change plain function to aggregate:
+create function sum4(int8,int8,int8,int8) returns int8 as
+'select $1 + $2 + $3 + $4' language sql strict immutable;
+
+CREATE OR REPLACE AGGREGATE sum3 (int8,int8,int8)
+(
+ stype = int8,
+ sfunc = sum4
+);
+
+drop function sum4(int8,int8,int8,int8);
+
+DROP AGGREGATE myavg (numeric);
+
+-- invalid: bad parallel-safety marking
+CREATE AGGREGATE mysum (int)
+(
+ stype = int,
+ sfunc = int4pl,
+ parallel = pear
+);
+
+-- invalid: nonstrict inverse with strict forward function
+
+CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+$$ SELECT $1 - $2; $$
+LANGUAGE SQL;
+
+CREATE AGGREGATE invalidsumdouble (float8)
+(
+ stype = float8,
+ sfunc = float8pl,
+ mstype = float8,
+ msfunc = float8pl,
+ minvfunc = float8mi_n
+);
+
+-- invalid: non-matching result types
+
+CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+$$ SELECT CAST($1 - $2 AS INT); $$
+LANGUAGE SQL;
+
+CREATE AGGREGATE wrongreturntype (float8)
+(
+ stype = float8,
+ sfunc = float8pl,
+ mstype = float8,
+ msfunc = float8pl,
+ minvfunc = float8mi_int
+);
+
+-- invalid: non-lowercase quoted identifiers
+
+CREATE AGGREGATE case_agg ( -- old syntax
+ "Sfunc1" = int4pl,
+ "Basetype" = int4,
+ "Stype1" = int4,
+ "Initcond1" = '0',
+ "Parallel" = safe
+);
+
+CREATE AGGREGATE case_agg(float8)
+(
+ "Stype" = internal,
+ "Sfunc" = ordered_set_transition,
+ "Finalfunc" = percentile_disc_final,
+ "Finalfunc_extra" = true,
+ "Finalfunc_modify" = read_write,
+ "Parallel" = safe
+);
diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql
new file mode 100644
index 0000000..9a35946
--- /dev/null
+++ b/src/test/regress/sql/create_am.sql
@@ -0,0 +1,214 @@
+--
+-- 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);
+
+-- 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;
+DROP INDEX grect2ind;
+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;
+
+-- Try to drop access method: fail because of dependent objects
+DROP ACCESS METHOD gist2;
+
+-- Drop access method cascade
+-- To prevent a (rare) deadlock against autovacuum,
+-- we must lock the table that owns the index that will be dropped
+BEGIN;
+LOCK TABLE fast_emp4000;
+DROP ACCESS METHOD gist2 CASCADE;
+COMMIT;
+
+
+--
+-- Test table access methods
+--
+
+-- prevent empty values
+SET default_table_access_method = '';
+
+-- prevent nonexistent values
+SET default_table_access_method = 'I do not exist AM';
+
+-- prevent setting it to an index AM
+SET default_table_access_method = 'btree';
+
+
+-- Create a heap2 table am handler with heapam handler
+CREATE ACCESS METHOD heap2 TYPE TABLE HANDLER heap_tableam_handler;
+
+-- Verify return type checks for handlers
+CREATE ACCESS METHOD bogus TYPE TABLE HANDLER int4in;
+CREATE ACCESS METHOD bogus TYPE TABLE HANDLER bthandler;
+
+SELECT amname, amhandler, amtype FROM pg_am where amtype = 't' ORDER BY 1, 2;
+
+
+-- First create tables employing the new AM using USING
+
+-- plain CREATE TABLE
+CREATE TABLE tableam_tbl_heap2(f1 int) USING heap2;
+INSERT INTO tableam_tbl_heap2 VALUES(1);
+SELECT f1 FROM tableam_tbl_heap2 ORDER BY f1;
+
+-- CREATE TABLE AS
+CREATE TABLE tableam_tblas_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
+SELECT f1 FROM tableam_tbl_heap2 ORDER BY f1;
+
+-- SELECT INTO doesn't support USING
+SELECT INTO tableam_tblselectinto_heap2 USING heap2 FROM tableam_tbl_heap2;
+
+-- CREATE VIEW doesn't support USING
+CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
+
+-- CREATE SEQUENCE doesn't support USING
+CREATE SEQUENCE tableam_seq_heap2 USING heap2;
+
+-- CREATE MATERIALIZED VIEW does support USING
+CREATE MATERIALIZED VIEW tableam_tblmv_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
+SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1;
+
+-- CREATE TABLE .. PARTITION BY doesn't not support USING
+CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a) USING heap2;
+
+CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a);
+-- new partitions will inherit from the current default, rather the partition root
+SET default_table_access_method = 'heap';
+CREATE TABLE tableam_parted_a_heap2 PARTITION OF tableam_parted_heap2 FOR VALUES IN ('a');
+SET default_table_access_method = 'heap2';
+CREATE TABLE tableam_parted_b_heap2 PARTITION OF tableam_parted_heap2 FOR VALUES IN ('b');
+RESET default_table_access_method;
+-- but the method can be explicitly specified
+CREATE TABLE tableam_parted_c_heap2 PARTITION OF tableam_parted_heap2 FOR VALUES IN ('c') USING heap;
+CREATE TABLE tableam_parted_d_heap2 PARTITION OF tableam_parted_heap2 FOR VALUES IN ('d') USING heap2;
+
+-- List all objects in AM
+SELECT
+ pc.relkind,
+ pa.amname,
+ CASE WHEN relkind = 't' THEN
+ (SELECT 'toast for ' || relname::regclass FROM pg_class pcm WHERE pcm.reltoastrelid = pc.oid)
+ ELSE
+ relname::regclass::text
+ END COLLATE "C" AS relname
+FROM pg_class AS pc,
+ pg_am AS pa
+WHERE pa.oid = pc.relam
+ AND pa.amname = 'heap2'
+ORDER BY 3, 1, 2;
+
+-- Show dependencies onto AM - there shouldn't be any for toast
+SELECT pg_describe_object(classid,objid,objsubid) AS obj
+FROM pg_depend, pg_am
+WHERE pg_depend.refclassid = 'pg_am'::regclass
+ AND pg_am.oid = pg_depend.refobjid
+ AND pg_am.amname = 'heap2'
+ORDER BY classid, objid, objsubid;
+
+
+-- Second, create objects in the new AM by changing the default AM
+BEGIN;
+SET LOCAL default_table_access_method = 'heap2';
+
+-- following tests should all respect the default AM
+CREATE TABLE tableam_tbl_heapx(f1 int);
+CREATE TABLE tableam_tblas_heapx AS SELECT * FROM tableam_tbl_heapx;
+SELECT INTO tableam_tblselectinto_heapx FROM tableam_tbl_heapx;
+CREATE MATERIALIZED VIEW tableam_tblmv_heapx USING heap2 AS SELECT * FROM tableam_tbl_heapx;
+CREATE TABLE tableam_parted_heapx (a text, b int) PARTITION BY list (a);
+CREATE TABLE tableam_parted_1_heapx PARTITION OF tableam_parted_heapx FOR VALUES IN ('a', 'b');
+
+-- but an explicitly set AM overrides it
+CREATE TABLE tableam_parted_2_heapx PARTITION OF tableam_parted_heapx FOR VALUES IN ('c', 'd') USING heap;
+
+-- sequences, views and foreign servers shouldn't have an AM
+CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx;
+CREATE SEQUENCE tableam_seq_heapx;
+CREATE FOREIGN DATA WRAPPER fdw_heap2 VALIDATOR postgresql_fdw_validator;
+CREATE SERVER fs_heap2 FOREIGN DATA WRAPPER fdw_heap2 ;
+CREATE FOREIGN table tableam_fdw_heapx () SERVER fs_heap2;
+
+-- Verify that new AM was used for tables, matviews, but not for sequences, views and fdws
+SELECT
+ pc.relkind,
+ pa.amname,
+ CASE WHEN relkind = 't' THEN
+ (SELECT 'toast for ' || relname::regclass FROM pg_class pcm WHERE pcm.reltoastrelid = pc.oid)
+ ELSE
+ relname::regclass::text
+ END COLLATE "C" AS relname
+FROM pg_class AS pc
+ LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam)
+WHERE pc.relname LIKE 'tableam_%_heapx'
+ORDER BY 3, 1, 2;
+
+-- don't want to keep those tables, nor the default
+ROLLBACK;
+
+-- Third, check that we can neither create a table using a nonexistent
+-- AM, nor using an index AM
+CREATE TABLE i_am_a_failure() USING "";
+CREATE TABLE i_am_a_failure() USING i_do_not_exist_am;
+CREATE TABLE i_am_a_failure() USING "I do not exist AM";
+CREATE TABLE i_am_a_failure() USING "btree";
+
+-- Drop table access method, which fails as objects depends on it
+DROP ACCESS METHOD heap2;
+
+-- we intentionally leave the objects created above alive, to verify pg_dump support
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
new file mode 100644
index 0000000..b11cf88
--- /dev/null
+++ b/src/test/regress/sql/create_cast.sql
@@ -0,0 +1,54 @@
+--
+-- CREATE_CAST
+--
+
+-- Create some types to test with
+CREATE TYPE casttesttype;
+
+CREATE FUNCTION casttesttype_in(cstring)
+ RETURNS casttesttype
+ AS 'textin'
+ LANGUAGE internal STRICT IMMUTABLE;
+CREATE FUNCTION casttesttype_out(casttesttype)
+ RETURNS cstring
+ AS 'textout'
+ LANGUAGE internal STRICT IMMUTABLE;
+
+CREATE TYPE casttesttype (
+ internallength = variable,
+ input = casttesttype_in,
+ output = casttesttype_out,
+ alignment = int4
+);
+
+-- a dummy function to test with
+CREATE FUNCTION casttestfunc(casttesttype) RETURNS int4 LANGUAGE SQL AS
+$$ SELECT 1; $$;
+
+SELECT casttestfunc('foo'::text); -- fails, as there's no cast
+
+-- Try binary coercion cast
+CREATE CAST (text AS casttesttype) WITHOUT FUNCTION;
+SELECT casttestfunc('foo'::text); -- doesn't work, as the cast is explicit
+SELECT casttestfunc('foo'::text::casttesttype); -- should work
+DROP CAST (text AS casttesttype); -- cleanup
+
+-- Try IMPLICIT binary coercion cast
+CREATE CAST (text AS casttesttype) WITHOUT FUNCTION AS IMPLICIT;
+SELECT casttestfunc('foo'::text); -- Should work now
+
+-- Try I/O conversion cast.
+SELECT 1234::int4::casttesttype; -- No cast yet, should fail
+
+CREATE CAST (int4 AS casttesttype) WITH INOUT;
+SELECT 1234::int4::casttesttype; -- Should work now
+
+DROP CAST (int4 AS casttesttype);
+
+-- Try cast with a function
+
+CREATE FUNCTION int4_casttesttype(int4) RETURNS casttesttype LANGUAGE SQL AS
+$$ SELECT ('foo'::text || $1::text)::casttesttype; $$;
+
+CREATE CAST (int4 AS casttesttype) WITH FUNCTION int4_casttesttype(int4) AS IMPLICIT;
+SELECT 1234::int4::casttesttype; -- Should work now
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
new file mode 100644
index 0000000..7edd757
--- /dev/null
+++ b/src/test/regress/sql/create_function_3.sql
@@ -0,0 +1,391 @@
+--
+-- CREATE FUNCTION
+--
+-- 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 argments 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);
+
+-- Cleanup
+DROP SCHEMA temp_func_test CASCADE;
+DROP USER regress_unpriv_user;
+RESET search_path;
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
new file mode 100644
index 0000000..7fa250e
--- /dev/null
+++ b/src/test/regress/sql/create_index.sql
@@ -0,0 +1,1242 @@
+--
+-- CREATE_INDEX
+-- Create ancillary data structures (i.e. indices)
+--
+
+--
+-- BTREE
+--
+CREATE INDEX onek_unique1 ON onek USING btree(unique1 int4_ops);
+
+CREATE INDEX IF NOT EXISTS onek_unique1 ON onek USING btree(unique1 int4_ops);
+
+CREATE INDEX IF NOT EXISTS ON onek USING btree(unique1 int4_ops);
+
+CREATE INDEX onek_unique2 ON onek USING btree(unique2 int4_ops);
+
+CREATE INDEX onek_hundred ON onek USING btree(hundred int4_ops);
+
+CREATE INDEX onek_stringu1 ON onek USING btree(stringu1 name_ops);
+
+CREATE INDEX tenk1_unique1 ON tenk1 USING btree(unique1 int4_ops);
+
+CREATE INDEX tenk1_unique2 ON tenk1 USING btree(unique2 int4_ops);
+
+CREATE INDEX tenk1_hundred ON tenk1 USING btree(hundred int4_ops);
+
+CREATE INDEX tenk1_thous_tenthous ON tenk1 (thousand, tenthous);
+
+CREATE INDEX tenk2_unique1 ON tenk2 USING btree(unique1 int4_ops);
+
+CREATE INDEX tenk2_unique2 ON tenk2 USING btree(unique2 int4_ops);
+
+CREATE INDEX tenk2_hundred ON tenk2 USING btree(hundred int4_ops);
+
+CREATE INDEX rix ON road USING btree (name text_ops);
+
+CREATE INDEX iix ON ihighway USING btree (name text_ops);
+
+CREATE INDEX six ON shighway USING btree (name text_ops);
+
+-- test comments
+COMMENT ON INDEX six_wrong IS 'bad index';
+COMMENT ON INDEX six IS 'good index';
+COMMENT ON INDEX six IS NULL;
+
+--
+-- BTREE ascending/descending cases
+--
+-- we load int4/text from pure descending data (each key is a new
+-- low key) and name/f8 from pure ascending data (each key is a new
+-- high key). we had a bug where new low keys would sometimes be
+-- "lost".
+--
+CREATE INDEX bt_i4_index ON bt_i4_heap USING btree (seqno int4_ops);
+
+CREATE INDEX bt_name_index ON bt_name_heap USING btree (seqno name_ops);
+
+CREATE INDEX bt_txt_index ON bt_txt_heap USING btree (seqno text_ops);
+
+CREATE INDEX bt_f8_index ON bt_f8_heap USING btree (seqno float8_ops);
+
+--
+-- BTREE partial indices
+--
+CREATE INDEX onek2_u1_prtl ON onek2 USING btree(unique1 int4_ops)
+ where unique1 < 20 or unique1 > 980;
+
+CREATE INDEX onek2_u2_prtl ON onek2 USING btree(unique2 int4_ops)
+ where stringu1 < 'B';
+
+CREATE INDEX onek2_stu1_prtl ON onek2 USING btree(stringu1 name_ops)
+ where onek2.stringu1 >= 'J' and onek2.stringu1 < 'K';
+
+--
+-- GiST (rtree-equivalent opclasses only)
+--
+CREATE INDEX grect2ind ON fast_emp4000 USING gist (home_base);
+
+CREATE INDEX gpolygonind ON polygon_tbl USING gist (f1);
+
+CREATE INDEX gcircleind ON circle_tbl USING gist (f1);
+
+INSERT INTO POINT_TBL(f1) VALUES (NULL);
+
+CREATE INDEX gpointind ON point_tbl USING gist (f1);
+
+CREATE TEMP TABLE gpolygon_tbl AS
+ SELECT polygon(home_base) AS f1 FROM slow_emp4000;
+INSERT INTO gpolygon_tbl VALUES ( '(1000,0,0,1000)' );
+INSERT INTO gpolygon_tbl VALUES ( '(0,1000,1000,1000)' );
+
+CREATE TEMP TABLE gcircle_tbl AS
+ SELECT circle(home_base) AS f1 FROM slow_emp4000;
+
+CREATE INDEX ggpolygonind ON gpolygon_tbl USING gist (f1);
+
+CREATE INDEX ggcircleind ON gcircle_tbl USING gist (f1);
+
+--
+-- Test GiST indexes
+--
+
+-- get non-indexed results for comparison purposes
+
+SET enable_seqscan = ON;
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = OFF;
+
+SELECT * FROM fast_emp4000
+ WHERE home_base <@ '(200,200),(2000,1000)'::box
+ ORDER BY (home_base[0])[0];
+
+SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
+
+SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
+
+SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon
+ ORDER BY (poly_center(f1))[0];
+
+SELECT * FROM circle_tbl WHERE f1 && circle(point(1,-2), 1)
+ ORDER BY area(f1);
+
+SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon;
+
+SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle;
+
+SELECT count(*) FROM point_tbl WHERE f1 <@ box '(0,0,100,100)';
+
+SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1;
+
+SELECT count(*) FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
+
+SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>';
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)';
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)';
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 <<| '(0.0, 0.0)';
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 |>> '(0.0, 0.0)';
+
+SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
+
+SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
+
+SELECT * FROM point_tbl WHERE f1 IS NULL;
+
+SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
+
+SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
+
+SELECT * FROM gpolygon_tbl ORDER BY f1 <-> '(0,0)'::point LIMIT 10;
+
+SELECT circle_center(f1), round(radius(f1)) as radius FROM gcircle_tbl ORDER BY f1 <-> '(200,300)'::point LIMIT 10;
+
+-- Now check the results from plain indexscan
+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;
+
+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];
+
+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);
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon;
+SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle;
+SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl WHERE f1 <@ box '(0,0,100,100)';
+SELECT count(*) FROM point_tbl WHERE f1 <@ box '(0,0,100,100)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1;
+SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
+SELECT count(*) FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>';
+SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)';
+SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)';
+SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl p WHERE p.f1 <<| '(0.0, 0.0)';
+SELECT count(*) FROM point_tbl p WHERE p.f1 <<| '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl p WHERE p.f1 |>> '(0.0, 0.0)';
+SELECT count(*) FROM point_tbl p WHERE p.f1 |>> '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
+SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
+SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM point_tbl WHERE f1 IS NULL;
+SELECT * FROM point_tbl WHERE f1 IS NULL;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
+SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
+SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM gpolygon_tbl ORDER BY f1 <-> '(0,0)'::point LIMIT 10;
+SELECT * FROM gpolygon_tbl ORDER BY f1 <-> '(0,0)'::point LIMIT 10;
+
+EXPLAIN (COSTS OFF)
+SELECT circle_center(f1), round(radius(f1)) as radius FROM gcircle_tbl ORDER BY f1 <-> '(200,300)'::point LIMIT 10;
+SELECT circle_center(f1), round(radius(f1)) as radius FROM gcircle_tbl ORDER BY f1 <-> '(200,300)'::point LIMIT 10;
+
+EXPLAIN (COSTS OFF)
+SELECT point(x,x), (SELECT f1 FROM gpolygon_tbl ORDER BY f1 <-> point(x,x) LIMIT 1) as c FROM generate_series(0,10,1) x;
+SELECT point(x,x), (SELECT f1 FROM gpolygon_tbl ORDER BY f1 <-> point(x,x) LIMIT 1) as c FROM generate_series(0,10,1) x;
+
+-- Now check the results from bitmap indexscan
+SET enable_seqscan = OFF;
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = ON;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
+SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+
+--
+-- GIN over int[] and text[]
+--
+-- Note: GIN currently supports only bitmap scans, not plain indexscans
+--
+
+SET enable_seqscan = OFF;
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = ON;
+
+CREATE INDEX intarrayidx ON array_index_op_test USING gin (i);
+
+explain (costs off)
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{17}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i @> '{32,17}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{32,17}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i = '{47,77}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i = '{}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i @> '{}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{}' ORDER BY seqno;
+SELECT * FROM array_index_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;
+
+CREATE INDEX textarrayidx ON array_index_op_test USING gin (t);
+
+explain (costs off)
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t <@ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t = '{AAAAAAAAAA646,A87088}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t = '{}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t @> '{}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t && '{}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t <@ '{}' ORDER BY seqno;
+
+-- And try it with a multicolumn GIN index
+
+DROP INDEX intarrayidx, textarrayidx;
+
+CREATE INDEX botharrayidx ON array_index_op_test USING gin (i, t);
+
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t = '{}' 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;
+
+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)
+--
+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
+
+--
+-- HASH
+--
+CREATE INDEX hash_i4_index ON hash_i4_heap USING hash (random int4_ops);
+
+CREATE INDEX hash_name_index ON hash_name_heap USING hash (random name_ops);
+
+CREATE INDEX hash_txt_index ON hash_txt_heap USING hash (random text_ops);
+
+CREATE INDEX hash_f8_index ON hash_f8_heap USING hash (random float8_ops) WITH (fillfactor=60);
+
+CREATE UNLOGGED TABLE unlogged_hash_table (id int4);
+CREATE INDEX unlogged_hash_index ON unlogged_hash_table USING hash (id int4_ops);
+DROP TABLE unlogged_hash_table;
+
+-- CREATE INDEX hash_ovfl_index ON hash_ovfl_heap USING hash (x int4_ops);
+
+-- Test hash index build tuplesorting. Force hash tuplesort using low
+-- maintenance_work_mem setting and fillfactor:
+SET maintenance_work_mem = '1MB';
+CREATE INDEX hash_tuplesort_idx ON tenk1 USING hash (stringu1 name_ops) WITH (fillfactor = 10);
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1 WHERE stringu1 = 'TVAAAA';
+SELECT count(*) FROM tenk1 WHERE stringu1 = 'TVAAAA';
+DROP INDEX hash_tuplesort_idx;
+RESET maintenance_work_mem;
+
+
+--
+-- Test functional index
+--
+CREATE TABLE func_index_heap (f1 text, f2 text);
+CREATE UNIQUE INDEX func_index_index on func_index_heap (textcat(f1,f2));
+
+INSERT INTO func_index_heap VALUES('ABC','DEF');
+INSERT INTO func_index_heap VALUES('AB','CDEFG');
+INSERT INTO func_index_heap VALUES('QWE','RTY');
+-- this should fail because of unique index:
+INSERT INTO func_index_heap VALUES('ABCD', 'EF');
+-- but this shouldn't:
+INSERT INTO func_index_heap VALUES('QWERTY');
+
+-- while we're here, see that the metadata looks sane
+\d func_index_heap
+\d func_index_index
+
+
+--
+-- Same test, expressional index
+--
+DROP TABLE func_index_heap;
+CREATE TABLE func_index_heap (f1 text, f2 text);
+CREATE UNIQUE INDEX func_index_index on func_index_heap ((f1 || f2) text_ops);
+
+INSERT INTO func_index_heap VALUES('ABC','DEF');
+INSERT INTO func_index_heap VALUES('AB','CDEFG');
+INSERT INTO func_index_heap VALUES('QWE','RTY');
+-- this should fail because of unique index:
+INSERT INTO func_index_heap VALUES('ABCD', 'EF');
+-- but this shouldn't:
+INSERT INTO func_index_heap VALUES('QWERTY');
+
+-- while we're here, see that the metadata looks sane
+\d func_index_heap
+\d func_index_index
+
+-- this should fail because of unsafe column type (anonymous record)
+create index on func_index_heap ((f1 || f2), (row(f1, f2)));
+
+
+--
+-- Test unique index with included columns
+--
+CREATE TABLE covering_index_heap (f1 int, f2 int, f3 text);
+CREATE UNIQUE INDEX covering_index_index on covering_index_heap (f1,f2) INCLUDE(f3);
+
+INSERT INTO covering_index_heap VALUES(1,1,'AAA');
+INSERT INTO covering_index_heap VALUES(1,2,'AAA');
+-- this should fail because of unique index on f1,f2:
+INSERT INTO covering_index_heap VALUES(1,2,'BBB');
+-- and this shouldn't:
+INSERT INTO covering_index_heap VALUES(1,4,'AAA');
+-- Try to build index on table that already contains data
+CREATE UNIQUE INDEX covering_pkey on covering_index_heap (f1,f2) INCLUDE(f3);
+-- Try to use existing covering index as primary key
+ALTER TABLE covering_index_heap ADD CONSTRAINT covering_pkey PRIMARY KEY USING INDEX
+covering_pkey;
+DROP TABLE covering_index_heap;
+
+
+--
+-- Also try building functional, expressional, and partial indexes on
+-- tables that already contain data.
+--
+create unique index hash_f8_index_1 on hash_f8_heap(abs(random));
+create unique index hash_f8_index_2 on hash_f8_heap((seqno + 1), random);
+create unique index hash_f8_index_3 on hash_f8_heap(random) where seqno > 1000;
+
+--
+-- 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;
+-- test where predicate is able to do a transactional update during
+-- a concurrent build before switching pg_index state flags.
+CREATE FUNCTION predicate_stable() RETURNS bool IMMUTABLE
+LANGUAGE plpgsql AS $$
+BEGIN
+ EXECUTE 'SELECT txid_current()';
+ RETURN true;
+END; $$;
+CREATE INDEX CONCURRENTLY concur_index8 ON concur_heap (f1)
+ WHERE predicate_stable();
+DROP INDEX concur_index8;
+DROP FUNCTION predicate_stable();
+
+-- 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
+VACUUM FULL concur_heap;
+REINDEX TABLE concur_heap;
+DELETE FROM concur_heap WHERE f1 = 'b';
+VACUUM FULL concur_heap;
+\d concur_heap
+REINDEX TABLE concur_heap;
+\d concur_heap
+
+-- Temporary tables with concurrent builds and on-commit actions
+-- CONCURRENTLY used with CREATE INDEX and DROP INDEX is ignored.
+-- PRESERVE ROWS, the default.
+CREATE TEMP TABLE concur_temp (f1 int, f2 text)
+ ON COMMIT PRESERVE ROWS;
+INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar');
+CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1);
+DROP INDEX CONCURRENTLY concur_temp_ind;
+DROP TABLE concur_temp;
+-- ON COMMIT DROP
+BEGIN;
+CREATE TEMP TABLE concur_temp (f1 int, f2 text)
+ ON COMMIT DROP;
+INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar');
+-- Fails when running in a transaction.
+CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1);
+COMMIT;
+-- ON COMMIT DELETE ROWS
+CREATE TEMP TABLE concur_temp (f1 int, f2 text)
+ ON COMMIT DELETE ROWS;
+INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar');
+CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1);
+DROP INDEX CONCURRENTLY concur_temp_ind;
+DROP TABLE concur_temp;
+
+--
+-- Try some concurrent index drops
+--
+DROP INDEX CONCURRENTLY "concur_index2"; -- works
+DROP INDEX CONCURRENTLY IF EXISTS "concur_index2"; -- notice
+
+-- failures
+DROP INDEX CONCURRENTLY "concur_index2", "concur_index3";
+BEGIN;
+DROP INDEX CONCURRENTLY "concur_index5";
+ROLLBACK;
+
+-- successes
+DROP INDEX CONCURRENTLY IF EXISTS "concur_index3";
+DROP INDEX CONCURRENTLY "concur_index4";
+DROP INDEX CONCURRENTLY "concur_index5";
+DROP INDEX CONCURRENTLY "concur_index1";
+DROP INDEX CONCURRENTLY "concur_heap_expr_idx";
+
+\d concur_heap
+
+DROP 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
+
+-- Check that non-default index options are rejected
+CREATE UNIQUE INDEX cwi_uniq3_idx ON cwi_test(a desc);
+ALTER TABLE cwi_test ADD UNIQUE USING INDEX cwi_uniq3_idx; -- fail
+CREATE UNIQUE INDEX cwi_uniq4_idx ON cwi_test(b collate "POSIX");
+ALTER TABLE cwi_test ADD UNIQUE USING INDEX cwi_uniq4_idx; -- fail
+
+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;
+
+--
+-- Check handling of indexes on system columns
+--
+CREATE TABLE syscol_table (a INT);
+
+-- System columns cannot be indexed
+CREATE INDEX ON syscolcol_table (ctid);
+
+-- nor used in expressions
+CREATE INDEX ON syscol_table ((ctid >= '(1000,0)'));
+
+-- nor used in predicates
+CREATE INDEX ON syscol_table (a) WHERE ctid >= '(1000,0)';
+
+DROP TABLE syscol_table;
+
+--
+-- Tests for IS NULL/IS NOT NULL with b-tree indexes
+--
+
+CREATE TABLE onek_with_null AS SELECT unique1, unique2 FROM onek;
+INSERT INTO onek_with_null (unique1,unique2) VALUES (NULL, -1), (NULL, NULL);
+CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2,unique1);
+
+SET enable_seqscan = OFF;
+SET enable_indexscan = ON;
+SET enable_bitmapscan = ON;
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500;
+
+DROP INDEX onek_nulltest;
+
+CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2 desc,unique1);
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500;
+
+DROP INDEX onek_nulltest;
+
+CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2 desc nulls last,unique1);
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500;
+
+DROP INDEX onek_nulltest;
+
+CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2 nulls first,unique1);
+
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500;
+SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500;
+
+DROP INDEX onek_nulltest;
+
+-- Check initial-positioning logic too
+
+CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2);
+
+SET enable_seqscan = OFF;
+SET enable_indexscan = ON;
+SET enable_bitmapscan = OFF;
+
+SELECT unique1, unique2 FROM onek_with_null
+ ORDER BY unique2 LIMIT 2;
+SELECT unique1, unique2 FROM onek_with_null WHERE unique2 >= -1
+ ORDER BY unique2 LIMIT 2;
+SELECT unique1, unique2 FROM onek_with_null WHERE unique2 >= 0
+ ORDER BY unique2 LIMIT 2;
+
+SELECT unique1, unique2 FROM onek_with_null
+ ORDER BY unique2 DESC LIMIT 2;
+SELECT unique1, unique2 FROM onek_with_null WHERE unique2 >= -1
+ ORDER BY unique2 DESC LIMIT 2;
+SELECT unique1, unique2 FROM onek_with_null WHERE unique2 < 999
+ ORDER BY unique2 DESC LIMIT 2;
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+
+DROP TABLE onek_with_null;
+
+--
+-- Check bitmap index path planning
+--
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+ WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+SELECT * FROM tenk1
+ WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1
+ WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+SELECT count(*) FROM tenk1
+ WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+
+--
+-- Check behavior with duplicate index column contents
+--
+
+CREATE TABLE dupindexcols AS
+ SELECT unique1 as id, stringu2::text as f1 FROM tenk1;
+CREATE INDEX dupindexcols_i ON dupindexcols (f1, id, f1 text_pattern_ops);
+ANALYZE dupindexcols;
+
+EXPLAIN (COSTS OFF)
+ SELECT count(*) FROM dupindexcols
+ WHERE f1 BETWEEN 'WA' AND 'ZZZ' and id < 1000 and f1 ~<~ 'YX';
+SELECT count(*) FROM dupindexcols
+ WHERE f1 BETWEEN 'WA' AND 'ZZZ' and id < 1000 and f1 ~<~ 'YX';
+
+--
+-- Check ordering of =ANY indexqual results (bug in 9.2.0)
+--
+
+vacuum tenk1; -- ensure we get consistent plans here
+
+explain (costs off)
+SELECT unique1 FROM tenk1
+WHERE unique1 IN (1,42,7)
+ORDER BY unique1;
+
+SELECT unique1 FROM tenk1
+WHERE unique1 IN (1,42,7)
+ORDER BY unique1;
+
+explain (costs off)
+SELECT thousand, tenthous FROM tenk1
+WHERE thousand < 2 AND tenthous IN (1001,3000)
+ORDER BY thousand;
+
+SELECT thousand, tenthous FROM tenk1
+WHERE thousand < 2 AND tenthous IN (1001,3000)
+ORDER BY thousand;
+
+SET enable_indexonlyscan = OFF;
+
+explain (costs off)
+SELECT thousand, tenthous FROM tenk1
+WHERE thousand < 2 AND tenthous IN (1001,3000)
+ORDER BY thousand;
+
+SELECT thousand, tenthous FROM tenk1
+WHERE thousand < 2 AND tenthous IN (1001,3000)
+ORDER BY thousand;
+
+RESET enable_indexonlyscan;
+
+--
+-- Check elimination of constant-NULL subexpressions
+--
+
+explain (costs off)
+ select * from tenk1 where (thousand, tenthous) in ((1,1001), (null,null));
+
+--
+-- Check matching of boolean index columns to WHERE conditions and sort keys
+--
+
+create temp table boolindex (b bool, i int, unique(b, i), junk float);
+
+explain (costs off)
+ select * from boolindex order by b, i limit 10;
+explain (costs off)
+ select * from boolindex where b order by i limit 10;
+explain (costs off)
+ select * from boolindex where b = true order by i desc limit 10;
+explain (costs off)
+ select * from boolindex where not b order by i limit 10;
+explain (costs off)
+ select * from boolindex where b is true order by i desc limit 10;
+explain (costs off)
+ select * from boolindex where b is false order by i desc limit 10;
+
+--
+-- REINDEX (VERBOSE)
+--
+CREATE TABLE reindex_verbose(id integer primary key);
+\set VERBOSITY terse \\ -- suppress machine-dependent details
+REINDEX (VERBOSE) TABLE reindex_verbose;
+\set VERBOSITY default
+DROP TABLE reindex_verbose;
+
+--
+-- REINDEX CONCURRENTLY
+--
+CREATE TABLE concur_reindex_tab (c1 int);
+-- REINDEX
+REINDEX TABLE concur_reindex_tab; -- notice
+REINDEX (CONCURRENTLY) TABLE concur_reindex_tab; -- notice
+ALTER TABLE concur_reindex_tab ADD COLUMN c2 text; -- add toast index
+-- Normal index with integer column
+CREATE UNIQUE INDEX concur_reindex_ind1 ON concur_reindex_tab(c1);
+-- Normal index with text column
+CREATE INDEX concur_reindex_ind2 ON concur_reindex_tab(c2);
+-- UNIQUE index with expression
+CREATE UNIQUE INDEX concur_reindex_ind3 ON concur_reindex_tab(abs(c1));
+-- Duplicate column names
+CREATE INDEX concur_reindex_ind4 ON concur_reindex_tab(c1, c1, c2);
+-- Create table for check on foreign key dependence switch with indexes swapped
+ALTER TABLE concur_reindex_tab ADD PRIMARY KEY USING INDEX concur_reindex_ind1;
+CREATE TABLE concur_reindex_tab2 (c1 int REFERENCES concur_reindex_tab);
+INSERT INTO concur_reindex_tab VALUES (1, 'a');
+INSERT INTO concur_reindex_tab VALUES (2, 'a');
+-- Reindex concurrently of exclusion constraint currently not supported
+CREATE TABLE concur_reindex_tab3 (c1 int, c2 int4range, EXCLUDE USING gist (c2 WITH &&));
+INSERT INTO concur_reindex_tab3 VALUES (3, '[1,2]');
+REINDEX INDEX CONCURRENTLY concur_reindex_tab3_c2_excl; -- error
+REINDEX TABLE CONCURRENTLY concur_reindex_tab3; -- succeeds with warning
+INSERT INTO concur_reindex_tab3 VALUES (4, '[2,4]');
+-- Check materialized views
+CREATE MATERIALIZED VIEW concur_reindex_matview AS SELECT * FROM concur_reindex_tab;
+-- Dependency lookup before and after the follow-up REINDEX commands.
+-- These should remain consistent.
+SELECT pg_describe_object(classid, objid, objsubid) as obj,
+ pg_describe_object(refclassid,refobjid,refobjsubid) as objref,
+ deptype
+FROM pg_depend
+WHERE classid = 'pg_class'::regclass AND
+ objid in ('concur_reindex_tab'::regclass,
+ 'concur_reindex_ind1'::regclass,
+ 'concur_reindex_ind2'::regclass,
+ 'concur_reindex_ind3'::regclass,
+ 'concur_reindex_ind4'::regclass,
+ 'concur_reindex_matview'::regclass)
+ ORDER BY 1, 2;
+REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
+REINDEX TABLE CONCURRENTLY concur_reindex_tab;
+REINDEX TABLE CONCURRENTLY concur_reindex_matview;
+SELECT pg_describe_object(classid, objid, objsubid) as obj,
+ pg_describe_object(refclassid,refobjid,refobjsubid) as objref,
+ deptype
+FROM pg_depend
+WHERE classid = 'pg_class'::regclass AND
+ objid in ('concur_reindex_tab'::regclass,
+ 'concur_reindex_ind1'::regclass,
+ 'concur_reindex_ind2'::regclass,
+ 'concur_reindex_ind3'::regclass,
+ 'concur_reindex_ind4'::regclass,
+ 'concur_reindex_matview'::regclass)
+ ORDER BY 1, 2;
+-- Check that comments are preserved
+CREATE TABLE testcomment (i int);
+CREATE INDEX testcomment_idx1 ON testcomment (i);
+COMMENT ON INDEX testcomment_idx1 IS 'test comment';
+SELECT obj_description('testcomment_idx1'::regclass, 'pg_class');
+REINDEX TABLE testcomment;
+SELECT obj_description('testcomment_idx1'::regclass, 'pg_class');
+REINDEX TABLE CONCURRENTLY testcomment ;
+SELECT obj_description('testcomment_idx1'::regclass, 'pg_class');
+DROP TABLE testcomment;
+-- Check that indisclustered updates are preserved
+CREATE TABLE concur_clustered(i int);
+CREATE INDEX concur_clustered_i_idx ON concur_clustered(i);
+ALTER TABLE concur_clustered CLUSTER ON concur_clustered_i_idx;
+REINDEX TABLE CONCURRENTLY concur_clustered;
+SELECT indexrelid::regclass, indisclustered FROM pg_index
+ WHERE indrelid = 'concur_clustered'::regclass;
+DROP TABLE concur_clustered;
+-- Check that indisreplident updates are preserved.
+CREATE TABLE concur_replident(i int NOT NULL);
+CREATE UNIQUE INDEX concur_replident_i_idx ON concur_replident(i);
+ALTER TABLE concur_replident REPLICA IDENTITY
+ USING INDEX concur_replident_i_idx;
+SELECT indexrelid::regclass, indisreplident FROM pg_index
+ WHERE indrelid = 'concur_replident'::regclass;
+REINDEX TABLE CONCURRENTLY concur_replident;
+SELECT indexrelid::regclass, indisreplident FROM pg_index
+ WHERE indrelid = 'concur_replident'::regclass;
+DROP TABLE concur_replident;
+-- Check that opclass parameters are preserved
+CREATE TABLE concur_appclass_tab(i tsvector, j tsvector, k tsvector);
+CREATE INDEX concur_appclass_ind on concur_appclass_tab
+ USING gist (i tsvector_ops (siglen='1000'), j tsvector_ops (siglen='500'));
+CREATE INDEX concur_appclass_ind_2 on concur_appclass_tab
+ USING gist (k tsvector_ops (siglen='300'), j tsvector_ops);
+REINDEX TABLE CONCURRENTLY concur_appclass_tab;
+\d concur_appclass_tab
+DROP TABLE concur_appclass_tab;
+
+-- Partitions
+-- Create some partitioned tables
+CREATE TABLE concur_reindex_part (c1 int, c2 int) PARTITION BY RANGE (c1);
+CREATE TABLE concur_reindex_part_0 PARTITION OF concur_reindex_part
+ FOR VALUES FROM (0) TO (10) PARTITION BY list (c2);
+CREATE TABLE concur_reindex_part_0_1 PARTITION OF concur_reindex_part_0
+ FOR VALUES IN (1);
+CREATE TABLE concur_reindex_part_0_2 PARTITION OF concur_reindex_part_0
+ FOR VALUES IN (2);
+-- This partitioned table will have no partitions.
+CREATE TABLE concur_reindex_part_10 PARTITION OF concur_reindex_part
+ FOR VALUES FROM (10) TO (20) PARTITION BY list (c2);
+-- Create some partitioned indexes
+CREATE INDEX concur_reindex_part_index ON ONLY concur_reindex_part (c1);
+CREATE INDEX concur_reindex_part_index_0 ON ONLY concur_reindex_part_0 (c1);
+ALTER INDEX concur_reindex_part_index ATTACH PARTITION concur_reindex_part_index_0;
+-- This partitioned index will have no partitions.
+CREATE INDEX concur_reindex_part_index_10 ON ONLY concur_reindex_part_10 (c1);
+ALTER INDEX concur_reindex_part_index ATTACH PARTITION concur_reindex_part_index_10;
+CREATE INDEX concur_reindex_part_index_0_1 ON ONLY concur_reindex_part_0_1 (c1);
+ALTER INDEX concur_reindex_part_index_0 ATTACH PARTITION concur_reindex_part_index_0_1;
+CREATE INDEX concur_reindex_part_index_0_2 ON ONLY concur_reindex_part_0_2 (c1);
+ALTER INDEX concur_reindex_part_index_0 ATTACH PARTITION concur_reindex_part_index_0_2;
+SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index')
+ ORDER BY relid, level;
+SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index')
+ ORDER BY relid, level;
+-- REINDEX should preserve dependencies of partition tree.
+SELECT pg_describe_object(classid, objid, objsubid) as obj,
+ pg_describe_object(refclassid,refobjid,refobjsubid) as objref,
+ deptype
+FROM pg_depend
+WHERE classid = 'pg_class'::regclass AND
+ objid in ('concur_reindex_part'::regclass,
+ 'concur_reindex_part_0'::regclass,
+ 'concur_reindex_part_0_1'::regclass,
+ 'concur_reindex_part_0_2'::regclass,
+ 'concur_reindex_part_index'::regclass,
+ 'concur_reindex_part_index_0'::regclass,
+ 'concur_reindex_part_index_0_1'::regclass,
+ 'concur_reindex_part_index_0_2'::regclass)
+ ORDER BY 1, 2;
+REINDEX INDEX CONCURRENTLY concur_reindex_part_index_0_1;
+REINDEX INDEX CONCURRENTLY concur_reindex_part_index_0_2;
+SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index')
+ ORDER BY relid, level;
+REINDEX TABLE CONCURRENTLY concur_reindex_part_0_1;
+REINDEX TABLE CONCURRENTLY concur_reindex_part_0_2;
+SELECT pg_describe_object(classid, objid, objsubid) as obj,
+ pg_describe_object(refclassid,refobjid,refobjsubid) as objref,
+ deptype
+FROM pg_depend
+WHERE classid = 'pg_class'::regclass AND
+ objid in ('concur_reindex_part'::regclass,
+ 'concur_reindex_part_0'::regclass,
+ 'concur_reindex_part_0_1'::regclass,
+ 'concur_reindex_part_0_2'::regclass,
+ 'concur_reindex_part_index'::regclass,
+ 'concur_reindex_part_index_0'::regclass,
+ 'concur_reindex_part_index_0_1'::regclass,
+ 'concur_reindex_part_index_0_2'::regclass)
+ ORDER BY 1, 2;
+SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index')
+ ORDER BY relid, level;
+
+-- REINDEX for partitioned indexes
+-- REINDEX TABLE fails for partitioned indexes
+-- Top-most parent index
+REINDEX TABLE concur_reindex_part_index; -- error
+REINDEX TABLE CONCURRENTLY concur_reindex_part_index; -- error
+-- Partitioned index with no leaves
+REINDEX TABLE concur_reindex_part_index_10; -- error
+REINDEX TABLE CONCURRENTLY concur_reindex_part_index_10; -- error
+-- Cannot run in a transaction block
+BEGIN;
+REINDEX INDEX concur_reindex_part_index;
+ROLLBACK;
+-- Helper functions to track changes of relfilenodes in a partition tree.
+-- Create a table tracking the relfilenode state.
+CREATE OR REPLACE FUNCTION create_relfilenode_part(relname text, indname text)
+ RETURNS VOID AS
+ $func$
+ BEGIN
+ EXECUTE format('
+ CREATE TABLE %I AS
+ SELECT oid, relname, relfilenode, relkind, reltoastrelid
+ FROM pg_class
+ WHERE oid IN
+ (SELECT relid FROM pg_partition_tree(''%I''));',
+ relname, indname);
+ END
+ $func$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION compare_relfilenode_part(tabname text)
+ RETURNS TABLE (relname name, relkind "char", state text) AS
+ $func$
+ BEGIN
+ RETURN QUERY EXECUTE
+ format(
+ 'SELECT b.relname,
+ b.relkind,
+ CASE WHEN a.relfilenode = b.relfilenode THEN ''relfilenode is unchanged''
+ ELSE ''relfilenode has changed'' END
+ -- Do not join with OID here as CONCURRENTLY changes it.
+ FROM %I b JOIN pg_class a ON b.relname = a.relname
+ ORDER BY 1;', tabname);
+ END
+ $func$ LANGUAGE plpgsql;
+-- Check that expected relfilenodes are changed, non-concurrent case.
+SELECT create_relfilenode_part('reindex_index_status', 'concur_reindex_part_index');
+REINDEX INDEX concur_reindex_part_index;
+SELECT * FROM compare_relfilenode_part('reindex_index_status');
+DROP TABLE reindex_index_status;
+-- concurrent case.
+SELECT create_relfilenode_part('reindex_index_status', 'concur_reindex_part_index');
+REINDEX INDEX CONCURRENTLY concur_reindex_part_index;
+SELECT * FROM compare_relfilenode_part('reindex_index_status');
+DROP TABLE reindex_index_status;
+
+-- REINDEX for partitioned tables
+-- REINDEX INDEX fails for partitioned tables
+-- Top-most parent
+REINDEX INDEX concur_reindex_part; -- error
+REINDEX INDEX CONCURRENTLY concur_reindex_part; -- error
+-- Partitioned with no leaves
+REINDEX INDEX concur_reindex_part_10; -- error
+REINDEX INDEX CONCURRENTLY concur_reindex_part_10; -- error
+-- Cannot run in a transaction block
+BEGIN;
+REINDEX TABLE concur_reindex_part;
+ROLLBACK;
+-- Check that expected relfilenodes are changed, non-concurrent case.
+-- Note that the partition tree changes of the *indexes* need to be checked.
+SELECT create_relfilenode_part('reindex_index_status', 'concur_reindex_part_index');
+REINDEX TABLE concur_reindex_part;
+SELECT * FROM compare_relfilenode_part('reindex_index_status');
+DROP TABLE reindex_index_status;
+-- concurrent case.
+SELECT create_relfilenode_part('reindex_index_status', 'concur_reindex_part_index');
+REINDEX TABLE CONCURRENTLY concur_reindex_part;
+SELECT * FROM compare_relfilenode_part('reindex_index_status');
+DROP TABLE reindex_index_status;
+
+DROP FUNCTION create_relfilenode_part;
+DROP FUNCTION compare_relfilenode_part;
+
+-- Cleanup of partition tree used for REINDEX test.
+DROP TABLE concur_reindex_part;
+
+-- Check errors
+-- Cannot run inside a transaction block
+BEGIN;
+REINDEX TABLE CONCURRENTLY concur_reindex_tab;
+COMMIT;
+REINDEX TABLE CONCURRENTLY pg_class; -- no catalog relation
+REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index
+-- These are the toast table and index of pg_authid.
+REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table
+REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast index
+REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
+-- Warns about catalog relations
+REINDEX SCHEMA CONCURRENTLY pg_catalog;
+
+-- Check the relation status, there should not be invalid indexes
+\d concur_reindex_tab
+DROP MATERIALIZED VIEW concur_reindex_matview;
+DROP TABLE concur_reindex_tab, concur_reindex_tab2, concur_reindex_tab3;
+
+-- Check handling of invalid indexes
+CREATE TABLE concur_reindex_tab4 (c1 int);
+INSERT INTO concur_reindex_tab4 VALUES (1), (1), (2);
+-- This trick creates an invalid index.
+CREATE UNIQUE INDEX CONCURRENTLY concur_reindex_ind5 ON concur_reindex_tab4 (c1);
+-- Reindexing concurrently this index fails with the same failure.
+-- The extra index created is itself invalid, and can be dropped.
+REINDEX INDEX CONCURRENTLY concur_reindex_ind5;
+\d concur_reindex_tab4
+DROP INDEX concur_reindex_ind5_ccnew;
+-- This makes the previous failure go away, so the index can become valid.
+DELETE FROM concur_reindex_tab4 WHERE c1 = 1;
+-- The invalid index is not processed when running REINDEX TABLE.
+REINDEX TABLE CONCURRENTLY concur_reindex_tab4;
+\d concur_reindex_tab4
+-- But it is fixed with REINDEX INDEX.
+REINDEX INDEX CONCURRENTLY concur_reindex_ind5;
+\d concur_reindex_tab4
+DROP TABLE concur_reindex_tab4;
+
+-- Check handling of indexes with expressions and predicates. The
+-- definitions of the rebuilt indexes should match the original
+-- definitions.
+CREATE TABLE concur_exprs_tab (c1 int , c2 boolean);
+INSERT INTO concur_exprs_tab (c1, c2) VALUES (1369652450, FALSE),
+ (414515746, TRUE),
+ (897778963, FALSE);
+CREATE UNIQUE INDEX concur_exprs_index_expr
+ ON concur_exprs_tab ((c1::text COLLATE "C"));
+CREATE UNIQUE INDEX concur_exprs_index_pred ON concur_exprs_tab (c1)
+ WHERE (c1::text > 500000000::text COLLATE "C");
+CREATE UNIQUE INDEX concur_exprs_index_pred_2
+ ON concur_exprs_tab ((1 / c1))
+ WHERE ('-H') >= (c2::TEXT) COLLATE "C";
+ALTER INDEX concur_exprs_index_expr ALTER COLUMN 1 SET STATISTICS 100;
+ANALYZE concur_exprs_tab;
+SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN (
+ 'concur_exprs_index_expr'::regclass,
+ 'concur_exprs_index_pred'::regclass,
+ 'concur_exprs_index_pred_2'::regclass)
+ GROUP BY starelid ORDER BY starelid::regclass::text;
+SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass);
+SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass);
+SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass);
+REINDEX TABLE CONCURRENTLY concur_exprs_tab;
+SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass);
+SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass);
+SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass);
+-- ALTER TABLE recreates the indexes, which should keep their collations.
+ALTER TABLE concur_exprs_tab ALTER c2 TYPE TEXT;
+SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass);
+SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass);
+SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass);
+-- Statistics should remain intact.
+SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN (
+ 'concur_exprs_index_expr'::regclass,
+ 'concur_exprs_index_pred'::regclass,
+ 'concur_exprs_index_pred_2'::regclass)
+ GROUP BY starelid ORDER BY starelid::regclass::text;
+-- attstattarget should remain intact
+SELECT attrelid::regclass, attnum, attstattarget
+ FROM pg_attribute WHERE attrelid IN (
+ 'concur_exprs_index_expr'::regclass,
+ 'concur_exprs_index_pred'::regclass,
+ 'concur_exprs_index_pred_2'::regclass)
+ ORDER BY attrelid::regclass::text, attnum;
+DROP TABLE concur_exprs_tab;
+
+-- Temporary tables and on-commit actions, where CONCURRENTLY is ignored.
+-- ON COMMIT PRESERVE ROWS, the default.
+CREATE TEMP TABLE concur_temp_tab_1 (c1 int, c2 text)
+ ON COMMIT PRESERVE ROWS;
+INSERT INTO concur_temp_tab_1 VALUES (1, 'foo'), (2, 'bar');
+CREATE INDEX concur_temp_ind_1 ON concur_temp_tab_1(c2);
+REINDEX TABLE CONCURRENTLY concur_temp_tab_1;
+REINDEX INDEX CONCURRENTLY concur_temp_ind_1;
+-- Still fails in transaction blocks
+BEGIN;
+REINDEX INDEX CONCURRENTLY concur_temp_ind_1;
+COMMIT;
+-- ON COMMIT DELETE ROWS
+CREATE TEMP TABLE concur_temp_tab_2 (c1 int, c2 text)
+ ON COMMIT DELETE ROWS;
+CREATE INDEX concur_temp_ind_2 ON concur_temp_tab_2(c2);
+REINDEX TABLE CONCURRENTLY concur_temp_tab_2;
+REINDEX INDEX CONCURRENTLY concur_temp_ind_2;
+-- ON COMMIT DROP
+BEGIN;
+CREATE TEMP TABLE concur_temp_tab_3 (c1 int, c2 text)
+ ON COMMIT PRESERVE ROWS;
+INSERT INTO concur_temp_tab_3 VALUES (1, 'foo'), (2, 'bar');
+CREATE INDEX concur_temp_ind_3 ON concur_temp_tab_3(c2);
+-- Fails when running in a transaction
+REINDEX INDEX CONCURRENTLY concur_temp_ind_3;
+COMMIT;
+-- REINDEX SCHEMA processes all temporary relations
+CREATE TABLE reindex_temp_before AS
+SELECT oid, relname, relfilenode, relkind, reltoastrelid
+ FROM pg_class
+ WHERE relname IN ('concur_temp_ind_1', 'concur_temp_ind_2');
+SELECT pg_my_temp_schema()::regnamespace as temp_schema_name \gset
+REINDEX SCHEMA CONCURRENTLY :temp_schema_name;
+SELECT b.relname,
+ b.relkind,
+ CASE WHEN a.relfilenode = b.relfilenode THEN 'relfilenode is unchanged'
+ ELSE 'relfilenode has changed' END
+ FROM reindex_temp_before b JOIN pg_class a ON b.oid = a.oid
+ ORDER BY 1;
+DROP TABLE concur_temp_tab_1, concur_temp_tab_2, reindex_temp_before;
+
+--
+-- REINDEX SCHEMA
+--
+REINDEX SCHEMA schema_to_reindex; -- failure, schema does not exist
+CREATE SCHEMA schema_to_reindex;
+SET search_path = 'schema_to_reindex';
+CREATE TABLE table1(col1 SERIAL PRIMARY KEY);
+INSERT INTO table1 SELECT generate_series(1,400);
+CREATE TABLE table2(col1 SERIAL PRIMARY KEY, col2 TEXT NOT NULL);
+INSERT INTO table2 SELECT generate_series(1,400), 'abc';
+CREATE INDEX ON table2(col2);
+CREATE MATERIALIZED VIEW matview AS SELECT col1 FROM table2;
+CREATE INDEX ON matview(col1);
+CREATE VIEW view AS SELECT col2 FROM table2;
+CREATE TABLE reindex_before AS
+SELECT oid, relname, relfilenode, relkind, reltoastrelid
+ FROM pg_class
+ where relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'schema_to_reindex');
+INSERT INTO reindex_before
+SELECT oid, 'pg_toast_TABLE', relfilenode, relkind, reltoastrelid
+FROM pg_class WHERE oid IN
+ (SELECT reltoastrelid FROM reindex_before WHERE reltoastrelid > 0);
+INSERT INTO reindex_before
+SELECT oid, 'pg_toast_TABLE_index', relfilenode, relkind, reltoastrelid
+FROM pg_class where oid in
+ (select indexrelid from pg_index where indrelid in
+ (select reltoastrelid from reindex_before where reltoastrelid > 0));
+REINDEX SCHEMA schema_to_reindex;
+CREATE TABLE reindex_after AS SELECT oid, relname, relfilenode, relkind
+ FROM pg_class
+ where relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'schema_to_reindex');
+SELECT b.relname,
+ b.relkind,
+ CASE WHEN a.relfilenode = b.relfilenode THEN 'relfilenode is unchanged'
+ ELSE 'relfilenode has changed' END
+ FROM reindex_before b JOIN pg_class a ON b.oid = a.oid
+ ORDER BY 1;
+REINDEX SCHEMA schema_to_reindex;
+BEGIN;
+REINDEX SCHEMA schema_to_reindex; -- failure, cannot run in a transaction
+END;
+
+-- concurrently
+REINDEX SCHEMA CONCURRENTLY schema_to_reindex;
+
+-- Failure for unauthorized user
+CREATE ROLE regress_reindexuser NOLOGIN;
+SET SESSION ROLE regress_reindexuser;
+REINDEX SCHEMA schema_to_reindex;
+-- Permission failures with toast tables and indexes (pg_authid here)
+RESET ROLE;
+GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
+SET SESSION ROLE regress_reindexuser;
+REINDEX TABLE pg_toast.pg_toast_1260;
+REINDEX INDEX pg_toast.pg_toast_1260_index;
+
+-- Clean up
+RESET ROLE;
+REVOKE USAGE ON SCHEMA pg_toast FROM regress_reindexuser;
+DROP ROLE regress_reindexuser;
+DROP SCHEMA schema_to_reindex CASCADE;
diff --git a/src/test/regress/sql/create_index_spgist.sql b/src/test/regress/sql/create_index_spgist.sql
new file mode 100644
index 0000000..b126dae
--- /dev/null
+++ b/src/test/regress/sql/create_index_spgist.sql
@@ -0,0 +1,429 @@
+--
+-- SP-GiST index tests
+--
+
+CREATE TABLE quad_point_tbl AS
+ SELECT point(unique1,unique2) AS p FROM tenk1;
+
+INSERT INTO quad_point_tbl
+ SELECT '(333.0,400.0)'::point FROM generate_series(1,1000);
+
+INSERT INTO quad_point_tbl VALUES (NULL), (NULL), (NULL);
+
+CREATE INDEX sp_quad_ind ON quad_point_tbl USING spgist (p);
+
+CREATE TABLE kd_point_tbl AS SELECT * FROM quad_point_tbl;
+
+CREATE INDEX sp_kd_ind ON kd_point_tbl USING spgist (p kd_point_ops);
+
+CREATE TABLE radix_text_tbl AS
+ SELECT name AS t FROM road WHERE name !~ '^[0-9]';
+
+INSERT INTO radix_text_tbl
+ SELECT 'P0123456789abcdef' FROM generate_series(1,1000);
+INSERT INTO radix_text_tbl VALUES ('P0123456789abcde');
+INSERT INTO radix_text_tbl VALUES ('P0123456789abcdefF');
+
+CREATE INDEX sp_radix_ind ON radix_text_tbl USING spgist (t);
+
+-- get non-indexed results for comparison purposes
+
+SET enable_seqscan = ON;
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = OFF;
+
+SELECT count(*) FROM quad_point_tbl WHERE p IS NULL;
+
+SELECT count(*) FROM quad_point_tbl WHERE p IS NOT NULL;
+
+SELECT count(*) FROM quad_point_tbl;
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+
+SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+
+SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
+
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <<| '(5000, 4000)';
+
+SELECT count(*) FROM quad_point_tbl WHERE p |>> '(5000, 4000)';
+
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
+
+CREATE TEMP TABLE quad_point_tbl_ord_seq1 AS
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM quad_point_tbl;
+
+CREATE TEMP TABLE quad_point_tbl_ord_seq2 AS
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+
+CREATE TEMP TABLE quad_point_tbl_ord_seq3 AS
+SELECT row_number() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
+FROM quad_point_tbl WHERE p IS NOT NULL;
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde';
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdefF';
+
+SELECT count(*) FROM radix_text_tbl WHERE t < 'Aztec Ct ';
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~<~ 'Aztec Ct ';
+
+SELECT count(*) FROM radix_text_tbl WHERE t <= 'Aztec Ct ';
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~<=~ 'Aztec Ct ';
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Aztec Ct ';
+
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Worth St ';
+
+SELECT count(*) FROM radix_text_tbl WHERE t >= 'Worth St ';
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~>=~ 'Worth St ';
+
+SELECT count(*) FROM radix_text_tbl WHERE t > 'Worth St ';
+
+SELECT count(*) FROM radix_text_tbl WHERE t ~>~ 'Worth St ';
+
+SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
+
+-- Now check the results from plain indexscan
+SET enable_seqscan = OFF;
+SET enable_indexscan = ON;
+SET enable_bitmapscan = OFF;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p IS NULL;
+SELECT count(*) FROM quad_point_tbl WHERE p IS NULL;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p IS NOT NULL;
+SELECT count(*) FROM quad_point_tbl WHERE p IS NOT NULL;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl;
+SELECT count(*) FROM quad_point_tbl;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
+SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <<| '(5000, 4000)';
+SELECT count(*) FROM quad_point_tbl WHERE p <<| '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p |>> '(5000, 4000)';
+SELECT count(*) FROM quad_point_tbl WHERE p |>> '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
+
+EXPLAIN (COSTS OFF)
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM quad_point_tbl;
+CREATE TEMP TABLE quad_point_tbl_ord_idx1 AS
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM quad_point_tbl;
+SELECT * FROM quad_point_tbl_ord_seq1 seq FULL JOIN quad_point_tbl_ord_idx1 idx
+ON seq.n = idx.n
+WHERE seq.dist IS DISTINCT FROM idx.dist;
+
+EXPLAIN (COSTS OFF)
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+CREATE TEMP TABLE quad_point_tbl_ord_idx2 AS
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+SELECT * FROM quad_point_tbl_ord_seq2 seq FULL JOIN quad_point_tbl_ord_idx2 idx
+ON seq.n = idx.n
+WHERE seq.dist IS DISTINCT FROM idx.dist;
+
+EXPLAIN (COSTS OFF)
+SELECT row_number() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
+FROM quad_point_tbl WHERE p IS NOT NULL;
+CREATE TEMP TABLE quad_point_tbl_ord_idx3 AS
+SELECT row_number() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
+FROM quad_point_tbl WHERE p IS NOT NULL;
+SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN quad_point_tbl_ord_idx3 idx
+ON seq.n = idx.n
+WHERE seq.dist IS DISTINCT FROM idx.dist;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)';
+SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)';
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <<| '(5000, 4000)';
+SELECT count(*) FROM kd_point_tbl WHERE p <<| '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p |>> '(5000, 4000)';
+SELECT count(*) FROM kd_point_tbl WHERE p |>> '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
+
+EXPLAIN (COSTS OFF)
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM kd_point_tbl;
+CREATE TEMP TABLE kd_point_tbl_ord_idx1 AS
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM kd_point_tbl;
+SELECT * FROM quad_point_tbl_ord_seq1 seq FULL JOIN kd_point_tbl_ord_idx1 idx
+ON seq.n = idx.n
+WHERE seq.dist IS DISTINCT FROM idx.dist;
+
+EXPLAIN (COSTS OFF)
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+CREATE TEMP TABLE kd_point_tbl_ord_idx2 AS
+SELECT row_number() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
+FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+SELECT * FROM quad_point_tbl_ord_seq2 seq FULL JOIN kd_point_tbl_ord_idx2 idx
+ON seq.n = idx.n
+WHERE seq.dist IS DISTINCT FROM idx.dist;
+
+EXPLAIN (COSTS OFF)
+SELECT row_number() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
+FROM kd_point_tbl WHERE p IS NOT NULL;
+CREATE TEMP TABLE kd_point_tbl_ord_idx3 AS
+SELECT row_number() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
+FROM kd_point_tbl WHERE p IS NOT NULL;
+SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN kd_point_tbl_ord_idx3 idx
+ON seq.n = idx.n
+WHERE seq.dist IS DISTINCT FROM idx.dist;
+
+-- test KNN scan with included columns
+-- the distance numbers are not exactly the same across platforms
+SET extra_float_digits = 0;
+CREATE INDEX ON quad_point_tbl_ord_seq1 USING spgist(p) INCLUDE(dist);
+EXPLAIN (COSTS OFF)
+SELECT p, dist FROM quad_point_tbl_ord_seq1 ORDER BY p <-> '0,0' LIMIT 10;
+SELECT p, dist FROM quad_point_tbl_ord_seq1 ORDER BY p <-> '0,0' LIMIT 10;
+RESET extra_float_digits;
+
+-- check ORDER BY distance to NULL
+SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt, p <-> '0,0' LIMIT 1)
+FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt);
+
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde';
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdefF';
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdefF';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t < 'Aztec Ct ';
+SELECT count(*) FROM radix_text_tbl WHERE t < 'Aztec Ct ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~<~ 'Aztec Ct ';
+SELECT count(*) FROM radix_text_tbl WHERE t ~<~ 'Aztec Ct ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t <= 'Aztec Ct ';
+SELECT count(*) FROM radix_text_tbl WHERE t <= 'Aztec Ct ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~<=~ 'Aztec Ct ';
+SELECT count(*) FROM radix_text_tbl WHERE t ~<=~ 'Aztec Ct ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Aztec Ct ';
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Aztec Ct ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Worth St ';
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Worth St ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t >= 'Worth St ';
+SELECT count(*) FROM radix_text_tbl WHERE t >= 'Worth St ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~>=~ 'Worth St ';
+SELECT count(*) FROM radix_text_tbl WHERE t ~>=~ 'Worth St ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t > 'Worth St ';
+SELECT count(*) FROM radix_text_tbl WHERE t > 'Worth St ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~>~ 'Worth St ';
+SELECT count(*) FROM radix_text_tbl WHERE t ~>~ 'Worth St ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
+SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
+
+-- Now check the results from bitmap indexscan
+SET enable_seqscan = OFF;
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = ON;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p IS NULL;
+SELECT count(*) FROM quad_point_tbl WHERE p IS NULL;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p IS NOT NULL;
+SELECT count(*) FROM quad_point_tbl WHERE p IS NOT NULL;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl;
+SELECT count(*) FROM quad_point_tbl;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
+SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <<| '(5000, 4000)';
+SELECT count(*) FROM quad_point_tbl WHERE p <<| '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p |>> '(5000, 4000)';
+SELECT count(*) FROM quad_point_tbl WHERE p |>> '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)';
+SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)';
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <<| '(5000, 4000)';
+SELECT count(*) FROM kd_point_tbl WHERE p <<| '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p |>> '(5000, 4000)';
+SELECT count(*) FROM kd_point_tbl WHERE p |>> '(5000, 4000)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde';
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdefF';
+SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdefF';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t < 'Aztec Ct ';
+SELECT count(*) FROM radix_text_tbl WHERE t < 'Aztec Ct ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~<~ 'Aztec Ct ';
+SELECT count(*) FROM radix_text_tbl WHERE t ~<~ 'Aztec Ct ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t <= 'Aztec Ct ';
+SELECT count(*) FROM radix_text_tbl WHERE t <= 'Aztec Ct ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~<=~ 'Aztec Ct ';
+SELECT count(*) FROM radix_text_tbl WHERE t ~<=~ 'Aztec Ct ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Aztec Ct ';
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Aztec Ct ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Worth St ';
+SELECT count(*) FROM radix_text_tbl WHERE t = 'Worth St ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t >= 'Worth St ';
+SELECT count(*) FROM radix_text_tbl WHERE t >= 'Worth St ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~>=~ 'Worth St ';
+SELECT count(*) FROM radix_text_tbl WHERE t ~>=~ 'Worth St ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t > 'Worth St ';
+SELECT count(*) FROM radix_text_tbl WHERE t > 'Worth St ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ~>~ 'Worth St ';
+SELECT count(*) FROM radix_text_tbl WHERE t ~>~ 'Worth St ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
+SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/create_misc.sql b/src/test/regress/sql/create_misc.sql
new file mode 100644
index 0000000..c7d0d06
--- /dev/null
+++ b/src/test/regress/sql/create_misc.sql
@@ -0,0 +1,217 @@
+--
+-- CREATE_MISC
+--
+
+-- CLASS POPULATION
+-- (any resemblance to real life is purely coincidental)
+--
+
+INSERT INTO tenk2 SELECT * FROM tenk1;
+
+CREATE TABLE onek2 AS SELECT * FROM onek;
+
+INSERT INTO fast_emp4000 SELECT * FROM slow_emp4000;
+
+SELECT *
+ INTO TABLE Bprime
+ FROM tenk1
+ WHERE unique2 < 1000;
+
+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');
+
+INSERT INTO city VALUES
+('Podunk', '(1,2),(3,4)', '100,127,1000'),
+('Gotham', '(1000,34),(1100,334)', '123456,127,-1000,6789');
+TABLE city;
+
+SELECT *
+ INTO TABLE ramp
+ FROM road
+ WHERE name ~ '.*Ramp';
+
+INSERT INTO ihighway
+ SELECT *
+ FROM road
+ WHERE name ~ 'I- .*';
+
+INSERT INTO shighway
+ SELECT *
+ FROM road
+ WHERE name ~ 'State Hwy.*';
+
+UPDATE shighway
+ SET surface = 'asphalt';
+
+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;
+
+
+--
+-- for internal portal (cursor) tests
+--
+CREATE TABLE iportaltest (
+ i int4,
+ d float4,
+ p polygon
+);
+
+INSERT INTO iportaltest (i, d, p)
+ VALUES (1, 3.567, '(3.0,1.0),(4.0,2.0)'::polygon);
+
+INSERT INTO iportaltest (i, d, p)
+ VALUES (2, 89.05, '(4.0,2.0),(3.0,1.0)'::polygon);
diff --git a/src/test/regress/sql/create_operator.sql b/src/test/regress/sql/create_operator.sql
new file mode 100644
index 0000000..4ff2c0f
--- /dev/null
+++ b/src/test/regress/sql/create_operator.sql
@@ -0,0 +1,234 @@
+--
+-- CREATE_OPERATOR
+--
+
+CREATE OPERATOR ## (
+ leftarg = path,
+ rightarg = path,
+ function = path_inter,
+ commutator = ##
+);
+
+CREATE OPERATOR <% (
+ leftarg = point,
+ rightarg = widget,
+ procedure = pt_in_widget,
+ commutator = >% ,
+ negator = >=%
+);
+
+CREATE OPERATOR @#@ (
+ rightarg = int8, -- prefix
+ procedure = factorial
+);
+
+CREATE OPERATOR #%# (
+ leftarg = int8, -- fail, postfix is no longer supported
+ procedure = factorial
+);
+
+-- Test operator created above
+SELECT point '(1,2)' <% widget '(0,0,3)' AS t,
+ point '(1,2)' <% widget '(0,0,1)' AS f;
+
+-- Test comments
+COMMENT ON OPERATOR ###### (NONE, int4) IS 'bad prefix';
+COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad postfix';
+COMMENT ON OPERATOR ###### (int4, int8) IS 'bad infix';
+
+-- Check that DROP on a nonexistent op behaves sanely, too
+DROP OPERATOR ###### (NONE, int4);
+DROP OPERATOR ###### (int4, NONE);
+DROP OPERATOR ###### (int4, int8);
+
+-- => is disallowed as an operator name now
+CREATE OPERATOR => (
+ rightarg = int8,
+ procedure = factorial
+);
+
+-- lexing of <=, >=, <>, != has a number of edge cases
+-- (=> is tested elsewhere)
+
+-- this is legal because ! is not allowed in sql ops
+CREATE OPERATOR !=- (
+ rightarg = int8,
+ procedure = factorial
+);
+SELECT !=- 10;
+-- postfix operators don't work anymore
+SELECT 10 !=-;
+-- make sure lexer returns != as <> even in edge cases
+SELECT 2 !=/**/ 1, 2 !=/**/ 2;
+SELECT 2 !=-- comment to be removed by psql
+ 1;
+DO $$ -- use DO to protect -- from psql
+ declare r boolean;
+ begin
+ execute $e$ select 2 !=-- comment
+ 1 $e$ into r;
+ raise info 'r = %', r;
+ end;
+$$;
+
+-- check that <= etc. followed by more operator characters are returned
+-- as the correct token with correct precedence
+SELECT true<>-1 BETWEEN 1 AND 1; -- BETWEEN has prec. above <> but below Op
+SELECT false<>/**/1 BETWEEN 1 AND 1;
+SELECT false<=-1 BETWEEN 1 AND 1;
+SELECT false>=-1 BETWEEN 1 AND 1;
+SELECT 2<=/**/3, 3>=/**/2, 2<>/**/3;
+SELECT 3<=/**/2, 2>=/**/3, 2<>/**/2;
+
+-- Should fail. CREATE OPERATOR requires USAGE on SCHEMA
+BEGIN TRANSACTION;
+CREATE ROLE regress_rol_op1;
+CREATE SCHEMA schema_op1;
+GRANT USAGE ON SCHEMA schema_op1 TO PUBLIC;
+REVOKE USAGE ON SCHEMA schema_op1 FROM regress_rol_op1;
+SET ROLE regress_rol_op1;
+CREATE OPERATOR schema_op1.#*# (
+ rightarg = int8,
+ procedure = factorial
+);
+ROLLBACK;
+
+
+-- Should fail. SETOF type functions not allowed as argument (testing leftarg)
+BEGIN TRANSACTION;
+CREATE OPERATOR #*# (
+ leftarg = SETOF int8,
+ procedure = factorial
+);
+ROLLBACK;
+
+
+-- Should fail. SETOF type functions not allowed as argument (testing rightarg)
+BEGIN TRANSACTION;
+CREATE OPERATOR #*# (
+ rightarg = SETOF int8,
+ procedure = factorial
+);
+ROLLBACK;
+
+
+-- Should work. Sample text-book case
+BEGIN TRANSACTION;
+CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
+RETURNS boolean AS $$
+ SELECT NULL::BOOLEAN;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+ LEFTARG = boolean,
+ RIGHTARG = boolean,
+ PROCEDURE = fn_op2,
+ COMMUTATOR = ===,
+ NEGATOR = !==,
+ RESTRICT = contsel,
+ JOIN = contjoinsel,
+ SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
+);
+ROLLBACK;
+
+-- Should fail. Invalid attribute
+CREATE OPERATOR #@%# (
+ rightarg = int8,
+ procedure = factorial,
+ invalid_att = int8
+);
+
+-- Should fail. At least rightarg should be mandatorily specified
+CREATE OPERATOR #@%# (
+ procedure = factorial
+);
+
+-- Should fail. Procedure should be mandatorily specified
+CREATE OPERATOR #@%# (
+ rightarg = int8
+);
+
+-- Should fail. CREATE OPERATOR requires USAGE on TYPE
+BEGIN TRANSACTION;
+CREATE ROLE regress_rol_op3;
+CREATE TYPE type_op3 AS ENUM ('new', 'open', 'closed');
+CREATE FUNCTION fn_op3(type_op3, int8)
+RETURNS int8 AS $$
+ SELECT NULL::int8;
+$$ LANGUAGE sql IMMUTABLE;
+REVOKE USAGE ON TYPE type_op3 FROM regress_rol_op3;
+REVOKE USAGE ON TYPE type_op3 FROM PUBLIC; -- Need to do this so that regress_rol_op3 is not allowed USAGE via PUBLIC
+SET ROLE regress_rol_op3;
+CREATE OPERATOR #*# (
+ leftarg = type_op3,
+ rightarg = int8,
+ procedure = fn_op3
+);
+ROLLBACK;
+
+-- Should fail. CREATE OPERATOR requires USAGE on TYPE (need to check separately for rightarg)
+BEGIN TRANSACTION;
+CREATE ROLE regress_rol_op4;
+CREATE TYPE type_op4 AS ENUM ('new', 'open', 'closed');
+CREATE FUNCTION fn_op4(int8, type_op4)
+RETURNS int8 AS $$
+ SELECT NULL::int8;
+$$ LANGUAGE sql IMMUTABLE;
+REVOKE USAGE ON TYPE type_op4 FROM regress_rol_op4;
+REVOKE USAGE ON TYPE type_op4 FROM PUBLIC; -- Need to do this so that regress_rol_op3 is not allowed USAGE via PUBLIC
+SET ROLE regress_rol_op4;
+CREATE OPERATOR #*# (
+ leftarg = int8,
+ rightarg = type_op4,
+ procedure = fn_op4
+);
+ROLLBACK;
+
+-- Should fail. CREATE OPERATOR requires EXECUTE on function
+BEGIN TRANSACTION;
+CREATE ROLE regress_rol_op5;
+CREATE TYPE type_op5 AS ENUM ('new', 'open', 'closed');
+CREATE FUNCTION fn_op5(int8, int8)
+RETURNS int8 AS $$
+ SELECT NULL::int8;
+$$ LANGUAGE sql IMMUTABLE;
+REVOKE EXECUTE ON FUNCTION fn_op5(int8, int8) FROM regress_rol_op5;
+REVOKE EXECUTE ON FUNCTION fn_op5(int8, int8) FROM PUBLIC;-- Need to do this so that regress_rol_op3 is not allowed EXECUTE via PUBLIC
+SET ROLE regress_rol_op5;
+CREATE OPERATOR #*# (
+ leftarg = int8,
+ rightarg = int8,
+ procedure = fn_op5
+);
+ROLLBACK;
+
+-- Should fail. CREATE OPERATOR requires USAGE on return TYPE
+BEGIN TRANSACTION;
+CREATE ROLE regress_rol_op6;
+CREATE TYPE type_op6 AS ENUM ('new', 'open', 'closed');
+CREATE FUNCTION fn_op6(int8, int8)
+RETURNS type_op6 AS $$
+ SELECT NULL::type_op6;
+$$ LANGUAGE sql IMMUTABLE;
+REVOKE USAGE ON TYPE type_op6 FROM regress_rol_op6;
+REVOKE USAGE ON TYPE type_op6 FROM PUBLIC; -- Need to do this so that regress_rol_op3 is not allowed USAGE via PUBLIC
+SET ROLE regress_rol_op6;
+CREATE OPERATOR #*# (
+ leftarg = int8,
+ rightarg = int8,
+ procedure = fn_op6
+);
+ROLLBACK;
+
+-- invalid: non-lowercase quoted identifiers
+CREATE OPERATOR ===
+(
+ "Leftarg" = box,
+ "Rightarg" = box,
+ "Procedure" = area_equal_function,
+ "Commutator" = ===,
+ "Negator" = !==,
+ "Restrict" = area_restriction_function,
+ "Join" = area_join_function,
+ "Hashes",
+ "Merges"
+);
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
new file mode 100644
index 0000000..75cc0fc
--- /dev/null
+++ b/src/test/regress/sql/create_procedure.sql
@@ -0,0 +1,253 @@
+CALL nonexistent(); -- error
+CALL random(); -- error
+
+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 ptest1
+SELECT pg_get_functiondef('ptest1'::regproc);
+
+-- show only normal functions
+\dfn public.*test*1
+
+-- show only procedures
+\dfp public.*test*1
+
+SELECT ptest1('x'); -- error
+CALL ptest1('a'); -- ok
+CALL ptest1('xy' || 'zzy'); -- ok, constant-folded arg
+CALL ptest1(substring(random()::numeric(20,15)::text, 1, 1)); -- ok, volatile arg
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+
+\df ptest1s
+SELECT pg_get_functiondef('ptest1s'::regproc);
+
+CALL ptest1s('b');
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+-- utitlity functions currently not supported here
+CREATE PROCEDURE ptestx()
+LANGUAGE SQL
+BEGIN ATOMIC
+ CREATE TABLE x (a int);
+END;
+
+
+CREATE PROCEDURE ptest2()
+LANGUAGE SQL
+AS $$
+SELECT 5;
+$$;
+
+CALL ptest2();
+
+
+-- nested CALL
+TRUNCATE cp_test;
+
+CREATE PROCEDURE ptest3(y text)
+LANGUAGE SQL
+AS $$
+CALL ptest1(y);
+CALL ptest1($1);
+$$;
+
+CALL ptest3('b');
+
+SELECT * FROM cp_test;
+
+
+-- output arguments
+
+CREATE PROCEDURE ptest4a(INOUT a int, INOUT b int)
+LANGUAGE SQL
+AS $$
+SELECT 1, 2;
+$$;
+
+CALL ptest4a(NULL, NULL);
+
+CREATE PROCEDURE ptest4b(INOUT b int, INOUT a int)
+LANGUAGE SQL
+AS $$
+CALL ptest4a(a, b); -- error, not supported
+$$;
+
+DROP PROCEDURE ptest4a;
+
+
+-- named and default parameters
+
+CREATE OR REPLACE PROCEDURE ptest5(a int, b text, c int default 100)
+LANGUAGE SQL
+AS $$
+INSERT INTO cp_test VALUES(a, b);
+INSERT INTO cp_test VALUES(c, b);
+$$;
+
+TRUNCATE cp_test;
+
+CALL ptest5(10, 'Hello', 20);
+CALL ptest5(10, 'Hello');
+CALL ptest5(10, b => 'Hello');
+CALL ptest5(b => 'Hello', a => 10);
+
+SELECT * FROM cp_test;
+
+
+-- polymorphic types
+
+CREATE PROCEDURE ptest6(a int, b anyelement)
+LANGUAGE SQL
+AS $$
+SELECT NULL::int;
+$$;
+
+CALL ptest6(1, 2);
+
+
+-- collation assignment
+
+CREATE PROCEDURE ptest7(a text, b text)
+LANGUAGE SQL
+AS $$
+SELECT a = b;
+$$;
+
+CALL ptest7(least('a', 'b'), 'a');
+
+
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+
+\df ptest8
+SELECT pg_get_functiondef('ptest8'::regproc);
+CALL ptest8('');
+
+
+-- OUT parameters
+
+CREATE PROCEDURE ptest9(OUT a int)
+LANGUAGE SQL
+AS $$
+INSERT INTO cp_test VALUES (1, 'a');
+SELECT 1;
+$$;
+
+-- standard way to do a call:
+CALL ptest9(NULL);
+-- you can write an expression, but it's not evaluated
+CALL ptest9(1/0); -- no error
+-- ... and it had better match the type of the parameter
+CALL ptest9(1./0.); -- error
+
+-- check named-parameter matching
+CREATE PROCEDURE ptest10(OUT a int, IN b int, IN c int)
+LANGUAGE SQL AS $$ SELECT b - c $$;
+
+CALL ptest10(null, 7, 4);
+CALL ptest10(a => null, b => 8, c => 2);
+CALL ptest10(null, 7, c => 2);
+CALL ptest10(null, c => 4, b => 11);
+CALL ptest10(b => 8, c => 2, a => 0);
+
+CREATE PROCEDURE ptest11(a OUT int, VARIADIC b int[]) LANGUAGE SQL
+ AS $$ SELECT b[1] + b[2] $$;
+
+CALL ptest11(null, 11, 12, 13);
+
+-- check resolution of ambiguous DROP commands
+
+CREATE PROCEDURE ptest10(IN a int, IN b int, IN c int)
+LANGUAGE SQL AS $$ SELECT a + b - c $$;
+
+\df ptest10
+
+drop procedure ptest10; -- fail
+drop procedure ptest10(int, int, int); -- fail
+begin;
+drop procedure ptest10(out int, int, int);
+\df ptest10
+drop procedure ptest10(int, int, int); -- now this would work
+rollback;
+begin;
+drop procedure ptest10(in int, int, int);
+\df ptest10
+drop procedure ptest10(int, int, int); -- now this would work
+rollback;
+
+-- various error cases
+
+CALL version(); -- error: not a procedure
+CALL sum(1); -- error: not a procedure
+
+CREATE PROCEDURE ptestx() LANGUAGE SQL WINDOW AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
+CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
+CREATE PROCEDURE ptestx(a VARIADIC int[], b OUT int) LANGUAGE SQL
+ AS $$ SELECT a[1] $$;
+CREATE PROCEDURE ptestx(a int DEFAULT 42, b OUT int) LANGUAGE SQL
+ AS $$ SELECT a $$;
+
+ALTER PROCEDURE ptest1(text) STRICT;
+ALTER FUNCTION ptest1(text) VOLATILE; -- error: not a function
+ALTER PROCEDURE cp_testfunc1(int) VOLATILE; -- error: not a procedure
+ALTER PROCEDURE nonexistent() VOLATILE;
+
+DROP FUNCTION ptest1(text); -- error: not a function
+DROP PROCEDURE cp_testfunc1(int); -- error: not a procedure
+DROP PROCEDURE nonexistent();
+
+
+-- privileges
+
+CREATE USER regress_cp_user1;
+GRANT INSERT ON cp_test TO regress_cp_user1;
+REVOKE EXECUTE ON PROCEDURE ptest1(text) FROM PUBLIC;
+SET ROLE regress_cp_user1;
+CALL ptest1('a'); -- error
+RESET ROLE;
+GRANT EXECUTE ON PROCEDURE ptest1(text) TO regress_cp_user1;
+SET ROLE regress_cp_user1;
+CALL ptest1('a'); -- ok
+RESET ROLE;
+
+
+-- ROUTINE syntax
+
+ALTER ROUTINE cp_testfunc1(int) RENAME TO cp_testfunc1a;
+ALTER ROUTINE cp_testfunc1a RENAME TO cp_testfunc1;
+
+ALTER ROUTINE ptest1(text) RENAME TO ptest1a;
+ALTER ROUTINE ptest1a RENAME TO ptest1;
+
+DROP ROUTINE cp_testfunc1(int);
+
+
+-- cleanup
+
+DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
+DROP PROCEDURE ptest2;
+
+DROP TABLE cp_test;
+
+DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
new file mode 100644
index 0000000..cc41f58
--- /dev/null
+++ b/src/test/regress/sql/create_table.sql
@@ -0,0 +1,977 @@
+--
+-- CREATE_TABLE
+--
+
+--
+-- CLASS DEFINITIONS
+--
+CREATE TABLE hobbies_r (
+ name text,
+ person text
+);
+
+CREATE TABLE equipment_r (
+ name text,
+ hobby text
+);
+
+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
+);
+
+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
+);
+
+CREATE TABLE tenk2 (
+ 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
+);
+
+
+CREATE TABLE person (
+ name text,
+ age int4,
+ location point
+);
+
+
+CREATE TABLE emp (
+ salary int4,
+ manager name
+) INHERITS (person);
+
+
+CREATE TABLE student (
+ gpa float8
+) INHERITS (person);
+
+
+CREATE TABLE stud_emp (
+ percent int4
+) INHERITS (emp, student);
+
+
+CREATE TABLE city (
+ name name,
+ location box,
+ budget city_budget
+);
+
+CREATE TABLE dept (
+ dname name,
+ mgrname text
+);
+
+CREATE TABLE slow_emp4000 (
+ home_base box
+);
+
+CREATE TABLE fast_emp4000 (
+ home_base box
+);
+
+CREATE TABLE road (
+ name text,
+ thepath path
+);
+
+CREATE TABLE ihighway () INHERITS (road);
+
+CREATE TABLE shighway (
+ surface text
+) INHERITS (road);
+
+CREATE TABLE real_city (
+ pop int4,
+ cname text,
+ outline path
+);
+
+--
+-- test the "star" operators a bit more thoroughly -- this time,
+-- throw in lots of NULL fields...
+--
+-- 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);
+
+CREATE TABLE aggtest (
+ a int2,
+ b float4
+);
+
+CREATE TABLE hash_i4_heap (
+ seqno int4,
+ random int4
+);
+
+CREATE TABLE hash_name_heap (
+ seqno int4,
+ random name
+);
+
+CREATE TABLE hash_txt_heap (
+ seqno int4,
+ random text
+);
+
+CREATE TABLE hash_f8_heap (
+ seqno int4,
+ random float8
+);
+
+-- don't include the hash_ovfl_heap stuff in the distribution
+-- the data set is too large for what it's worth
+--
+-- CREATE TABLE hash_ovfl_heap (
+-- x int4,
+-- y int4
+-- );
+
+CREATE TABLE bt_i4_heap (
+ seqno int4,
+ random int4
+);
+
+CREATE TABLE bt_name_heap (
+ seqno name,
+ random int4
+);
+
+CREATE TABLE bt_txt_heap (
+ seqno text,
+ random int4
+);
+
+CREATE TABLE bt_f8_heap (
+ seqno float8,
+ random int4
+);
+
+CREATE TABLE array_op_test (
+ seqno int4,
+ i int4[],
+ t text[]
+);
+
+CREATE TABLE array_index_op_test (
+ seqno int4,
+ i int4[],
+ t text[]
+);
+
+CREATE TABLE testjsonb (
+ j jsonb
+);
+
+CREATE TABLE unknowntab (
+ u unknown -- fail
+);
+
+CREATE TYPE unknown_comptype AS (
+ u unknown -- fail
+);
+
+CREATE TABLE IF NOT EXISTS test_tsvector(
+ t text,
+ a tsvector
+);
+
+CREATE TABLE IF NOT EXISTS test_tsvector(
+ t text
+);
+
+-- invalid: non-lowercase quoted reloptions identifiers
+CREATE TABLE tas_case WITH ("Fillfactor" = 10) AS SELECT 1 a;
+
+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 unlogged1_pkey;
+REINDEX INDEX unlogged2_pkey;
+SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged\d' ORDER BY relname;
+DROP TABLE unlogged2;
+INSERT INTO unlogged1 VALUES (42);
+CREATE UNLOGGED TABLE public.unlogged2 (a int primary key); -- also OK
+CREATE UNLOGGED TABLE pg_temp.unlogged3 (a int primary key); -- not OK
+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
+DROP TABLE unlogged1, public.unlogged2;
+
+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;
+
+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;
+
+-- 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;
+
+-- 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);
+
+-- 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)));
+
+-- 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;
+
+--
+-- 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
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- 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 overriden, 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 btree AS
+ 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 btree;
+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/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
new file mode 100644
index 0000000..4929d37
--- /dev/null
+++ b/src/test/regress/sql/create_table_like.sql
@@ -0,0 +1,217 @@
+/* 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;
+
+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
+BEGIN;
+CREATE SCHEMA ctl_schema;
+SET LOCAL search_path = ctl_schema, public;
+CREATE TABLE ctlt1 (LIKE ctlt1 INCLUDING ALL);
+\d+ ctlt1
+ROLLBACK;
+
+DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
+
+-- 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/create_type.sql b/src/test/regress/sql/create_type.sql
new file mode 100644
index 0000000..a32a9e6
--- /dev/null
+++ b/src/test/regress/sql/create_type.sql
@@ -0,0 +1,235 @@
+--
+-- CREATE_TYPE
+--
+
+--
+-- Note: widget_in/out were created in create_function_1, without any
+-- prior shell-type creation. These commands therefore complete a test
+-- of the "old style" approach of making the functions first.
+--
+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 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/create_view.sql b/src/test/regress/sql/create_view.sql
new file mode 100644
index 0000000..843a6d2
--- /dev/null
+++ b/src/test/regress/sql/create_view.sql
@@ -0,0 +1,690 @@
+--
+-- CREATE_VIEW
+-- Virtual class definitions
+-- (this also tests the query rewrite system)
+--
+
+CREATE VIEW street AS
+ SELECT r.name, r.thepath, c.cname AS cname
+ FROM ONLY road r, real_city c
+ WHERE c.outline ## r.thepath;
+
+CREATE VIEW iexit AS
+ SELECT ih.name, ih.thepath,
+ interpt_pp(ih.thepath, r.thepath) AS exit
+ FROM ihighway ih, ramp r
+ WHERE ih.thepath ## r.thepath;
+
+CREATE VIEW toyemp AS
+ SELECT name, age, location, 12*salary AS annualsal
+ FROM emp;
+
+-- Test comments
+COMMENT ON VIEW noview IS 'no view';
+COMMENT ON VIEW toyemp IS 'is a view';
+COMMENT ON VIEW toyemp IS NULL;
+
+-- These views are left around mainly to exercise special cases in pg_dump.
+
+CREATE TABLE view_base_table (key int PRIMARY KEY, data varchar(20));
+
+CREATE VIEW key_dependent_view AS
+ SELECT * FROM view_base_table GROUP BY key;
+
+ALTER TABLE view_base_table DROP CONSTRAINT view_base_table_pkey; -- fails
+
+CREATE VIEW key_dependent_view_no_cols AS
+ SELECT FROM view_base_table GROUP BY key HAVING length(data) > 0;
+
+--
+-- CREATE OR REPLACE VIEW
+--
+
+CREATE TABLE viewtest_tbl (a int, b int);
+COPY viewtest_tbl FROM stdin;
+5 10
+10 15
+15 20
+20 25
+\.
+
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT * FROM viewtest_tbl;
+
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT * FROM viewtest_tbl WHERE a > 10;
+
+SELECT * FROM viewtest;
+
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT a, b FROM viewtest_tbl WHERE a > 5 ORDER BY b DESC;
+
+SELECT * FROM viewtest;
+
+-- should fail
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT a FROM viewtest_tbl WHERE a <> 20;
+
+-- should fail
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT 1, * FROM viewtest_tbl;
+
+-- should fail
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT a, b::numeric FROM viewtest_tbl;
+
+-- should work
+CREATE OR REPLACE VIEW viewtest AS
+ SELECT a, b, 0 AS c FROM viewtest_tbl;
+
+DROP VIEW viewtest;
+DROP TABLE viewtest_tbl;
+
+-- tests for temporary views
+
+CREATE SCHEMA temp_view_test
+ CREATE TABLE base_table (a int, id int)
+ CREATE TABLE base_table2 (a int, id int);
+
+SET search_path TO temp_view_test, public;
+
+CREATE TEMPORARY TABLE temp_table (a int, id int);
+
+-- should be created in temp_view_test schema
+CREATE VIEW v1 AS SELECT * FROM base_table;
+-- should be created in temp object schema
+CREATE VIEW v1_temp AS SELECT * FROM temp_table;
+-- should be created in temp object schema
+CREATE TEMP VIEW v2_temp AS SELECT * FROM base_table;
+-- should be created in temp_views schema
+CREATE VIEW temp_view_test.v2 AS SELECT * FROM base_table;
+-- should fail
+CREATE VIEW temp_view_test.v3_temp AS SELECT * FROM temp_table;
+-- should fail
+CREATE SCHEMA test_view_schema
+ CREATE TEMP VIEW testview AS SELECT 1;
+
+-- joins: if any of the join relations are temporary, the view
+-- should also be temporary
+
+-- should be non-temp
+CREATE VIEW v3 AS
+ SELECT t1.a AS t1_a, t2.a AS t2_a
+ FROM base_table t1, base_table2 t2
+ WHERE t1.id = t2.id;
+-- should be temp (one join rel is temp)
+CREATE VIEW v4_temp AS
+ SELECT t1.a AS t1_a, t2.a AS t2_a
+ FROM base_table t1, temp_table t2
+ WHERE t1.id = t2.id;
+-- should be temp
+CREATE VIEW v5_temp AS
+ SELECT t1.a AS t1_a, t2.a AS t2_a, t3.a AS t3_a
+ FROM base_table t1, base_table2 t2, temp_table t3
+ WHERE t1.id = t2.id and t2.id = t3.id;
+
+-- subqueries
+CREATE VIEW v4 AS SELECT * FROM base_table WHERE id IN (SELECT id FROM base_table2);
+CREATE VIEW v5 AS SELECT t1.id, t2.a FROM base_table t1, (SELECT * FROM base_table2) t2;
+CREATE VIEW v6 AS SELECT * FROM base_table WHERE EXISTS (SELECT 1 FROM base_table2);
+CREATE VIEW v7 AS SELECT * FROM base_table WHERE NOT EXISTS (SELECT 1 FROM base_table2);
+CREATE VIEW v8 AS SELECT * FROM base_table WHERE EXISTS (SELECT 1);
+
+CREATE VIEW v6_temp AS SELECT * FROM base_table WHERE id IN (SELECT id FROM temp_table);
+CREATE VIEW v7_temp AS SELECT t1.id, t2.a FROM base_table t1, (SELECT * FROM temp_table) t2;
+CREATE VIEW v8_temp AS SELECT * FROM base_table WHERE EXISTS (SELECT 1 FROM temp_table);
+CREATE VIEW v9_temp AS SELECT * FROM base_table WHERE NOT EXISTS (SELECT 1 FROM temp_table);
+
+-- a view should also be temporary if it references a temporary view
+CREATE VIEW v10_temp AS SELECT * FROM v7_temp;
+CREATE VIEW v11_temp AS SELECT t1.id, t2.a FROM base_table t1, v10_temp t2;
+CREATE VIEW v12_temp AS SELECT true FROM v11_temp;
+
+-- a view should also be temporary if it references a temporary sequence
+CREATE SEQUENCE seq1;
+CREATE TEMPORARY SEQUENCE seq1_temp;
+CREATE VIEW v9 AS SELECT seq1.is_called FROM seq1;
+CREATE VIEW v13_temp AS SELECT seq1_temp.is_called FROM seq1_temp;
+
+SELECT relname FROM pg_class
+ WHERE relname LIKE 'v_'
+ AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'temp_view_test')
+ ORDER BY relname;
+SELECT relname FROM pg_class
+ WHERE relname LIKE 'v%'
+ AND relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname LIKE 'pg_temp%')
+ ORDER BY relname;
+
+CREATE SCHEMA testviewschm2;
+SET search_path TO testviewschm2, public;
+
+CREATE TABLE t1 (num int, name text);
+CREATE TABLE t2 (num2 int, value text);
+CREATE TEMP TABLE tt (num2 int, value text);
+
+CREATE VIEW nontemp1 AS SELECT * FROM t1 CROSS JOIN t2;
+CREATE VIEW temporal1 AS SELECT * FROM t1 CROSS JOIN tt;
+CREATE VIEW nontemp2 AS SELECT * FROM t1 INNER JOIN t2 ON t1.num = t2.num2;
+CREATE VIEW temporal2 AS SELECT * FROM t1 INNER JOIN tt ON t1.num = tt.num2;
+CREATE VIEW nontemp3 AS SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num2;
+CREATE VIEW temporal3 AS SELECT * FROM t1 LEFT JOIN tt ON t1.num = tt.num2;
+CREATE VIEW nontemp4 AS SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num2 AND t2.value = 'xxx';
+CREATE VIEW temporal4 AS SELECT * FROM t1 LEFT JOIN tt ON t1.num = tt.num2 AND tt.value = 'xxx';
+
+SELECT relname FROM pg_class
+ WHERE relname LIKE 'nontemp%'
+ AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'testviewschm2')
+ ORDER BY relname;
+SELECT relname FROM pg_class
+ WHERE relname LIKE 'temporal%'
+ AND relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname LIKE 'pg_temp%')
+ ORDER BY relname;
+
+CREATE TABLE tbl1 ( a int, b int);
+CREATE TABLE tbl2 (c int, d int);
+CREATE TABLE tbl3 (e int, f int);
+CREATE TABLE tbl4 (g int, h int);
+CREATE TEMP TABLE tmptbl (i int, j int);
+
+--Should be in testviewschm2
+CREATE VIEW pubview AS SELECT * FROM tbl1 WHERE tbl1.a
+BETWEEN (SELECT d FROM tbl2 WHERE c = 1) AND (SELECT e FROM tbl3 WHERE f = 2)
+AND EXISTS (SELECT g FROM tbl4 LEFT JOIN tbl3 ON tbl4.h = tbl3.f);
+
+SELECT count(*) FROM pg_class where relname = 'pubview'
+AND relnamespace IN (SELECT OID FROM pg_namespace WHERE nspname = 'testviewschm2');
+
+--Should be in temp object schema
+CREATE VIEW mytempview AS SELECT * FROM tbl1 WHERE tbl1.a
+BETWEEN (SELECT d FROM tbl2 WHERE c = 1) AND (SELECT e FROM tbl3 WHERE f = 2)
+AND EXISTS (SELECT g FROM tbl4 LEFT JOIN tbl3 ON tbl4.h = tbl3.f)
+AND NOT EXISTS (SELECT g FROM tbl4 LEFT JOIN tmptbl ON tbl4.h = tmptbl.j);
+
+SELECT count(*) FROM pg_class where relname LIKE 'mytempview'
+And relnamespace IN (SELECT OID FROM pg_namespace WHERE nspname LIKE 'pg_temp%');
+
+--
+-- CREATE VIEW and WITH(...) clause
+--
+CREATE VIEW mysecview1
+ AS SELECT * FROM tbl1 WHERE a = 0;
+CREATE VIEW mysecview2 WITH (security_barrier=true)
+ AS SELECT * FROM tbl1 WHERE a > 0;
+CREATE VIEW mysecview3 WITH (security_barrier=false)
+ AS SELECT * FROM tbl1 WHERE a < 0;
+CREATE VIEW mysecview4 WITH (security_barrier)
+ AS SELECT * FROM tbl1 WHERE a <> 0;
+CREATE VIEW mysecview5 WITH (security_barrier=100) -- Error
+ AS SELECT * FROM tbl1 WHERE a > 100;
+CREATE VIEW mysecview6 WITH (invalid_option) -- Error
+ AS SELECT * FROM tbl1 WHERE a < 100;
+SELECT relname, relkind, reloptions FROM pg_class
+ WHERE oid in ('mysecview1'::regclass, 'mysecview2'::regclass,
+ 'mysecview3'::regclass, 'mysecview4'::regclass)
+ ORDER BY relname;
+
+CREATE OR REPLACE VIEW mysecview1
+ AS SELECT * FROM tbl1 WHERE a = 256;
+CREATE OR REPLACE VIEW mysecview2
+ AS SELECT * FROM tbl1 WHERE a > 256;
+CREATE OR REPLACE VIEW mysecview3 WITH (security_barrier=true)
+ AS SELECT * FROM tbl1 WHERE a < 256;
+CREATE OR REPLACE VIEW mysecview4 WITH (security_barrier=false)
+ AS SELECT * FROM tbl1 WHERE a <> 256;
+SELECT relname, relkind, reloptions FROM pg_class
+ WHERE oid in ('mysecview1'::regclass, 'mysecview2'::regclass,
+ 'mysecview3'::regclass, 'mysecview4'::regclass)
+ ORDER BY relname;
+
+-- Check that unknown literals are converted to "text" in CREATE VIEW,
+-- so that we don't end up with unknown-type columns.
+
+CREATE VIEW unspecified_types AS
+ SELECT 42 as i, 42.5 as num, 'foo' as u, 'foo'::unknown as u2, null as n;
+\d+ unspecified_types
+SELECT * FROM unspecified_types;
+
+-- This test checks that proper typmods are assigned in a multi-row VALUES
+
+CREATE VIEW tt1 AS
+ SELECT * FROM (
+ VALUES
+ ('abc'::varchar(3), '0123456789', 42, 'abcd'::varchar(4)),
+ ('0123456789', 'abc'::varchar(3), 42.12, 'abc'::varchar(4))
+ ) vv(a,b,c,d);
+\d+ tt1
+SELECT * FROM tt1;
+SELECT a::varchar(3) FROM tt1;
+DROP VIEW tt1;
+
+-- Test view decompilation in the face of relation renaming conflicts
+
+CREATE TABLE tt1 (f1 int, f2 int, f3 text);
+CREATE TABLE tx1 (x1 int, x2 int, x3 text);
+CREATE TABLE temp_view_test.tt1 (y1 int, f2 int, f3 text);
+
+CREATE VIEW aliased_view_1 AS
+ select * from tt1
+ where exists (select 1 from tx1 where tt1.f1 = tx1.x1);
+CREATE VIEW aliased_view_2 AS
+ select * from tt1 a1
+ where exists (select 1 from tx1 where a1.f1 = tx1.x1);
+CREATE VIEW aliased_view_3 AS
+ select * from tt1
+ where exists (select 1 from tx1 a2 where tt1.f1 = a2.x1);
+CREATE VIEW aliased_view_4 AS
+ select * from temp_view_test.tt1
+ where exists (select 1 from tt1 where temp_view_test.tt1.y1 = tt1.f1);
+
+\d+ aliased_view_1
+\d+ aliased_view_2
+\d+ aliased_view_3
+\d+ aliased_view_4
+
+ALTER TABLE tx1 RENAME TO a1;
+
+\d+ aliased_view_1
+\d+ aliased_view_2
+\d+ aliased_view_3
+\d+ aliased_view_4
+
+ALTER TABLE tt1 RENAME TO a2;
+
+\d+ aliased_view_1
+\d+ aliased_view_2
+\d+ aliased_view_3
+\d+ aliased_view_4
+
+ALTER TABLE a1 RENAME TO tt1;
+
+\d+ aliased_view_1
+\d+ aliased_view_2
+\d+ aliased_view_3
+\d+ aliased_view_4
+
+ALTER TABLE a2 RENAME TO tx1;
+ALTER TABLE tx1 SET SCHEMA temp_view_test;
+
+\d+ aliased_view_1
+\d+ aliased_view_2
+\d+ aliased_view_3
+\d+ aliased_view_4
+
+ALTER TABLE temp_view_test.tt1 RENAME TO tmp1;
+ALTER TABLE temp_view_test.tmp1 SET SCHEMA testviewschm2;
+ALTER TABLE tmp1 RENAME TO tx1;
+
+\d+ aliased_view_1
+\d+ aliased_view_2
+\d+ aliased_view_3
+\d+ aliased_view_4
+
+-- Test aliasing of joins
+
+create view view_of_joins as
+select * from
+ (select * from (tbl1 cross join tbl2) same) ss,
+ (tbl3 cross join tbl4) same;
+
+\d+ view_of_joins
+
+create table tbl1a (a int, c int);
+create view view_of_joins_2a as select * from tbl1 join tbl1a using (a);
+create view view_of_joins_2b as select * from tbl1 join tbl1a using (a) as x;
+create view view_of_joins_2c as select * from (tbl1 join tbl1a using (a)) as y;
+create view view_of_joins_2d as select * from (tbl1 join tbl1a using (a) as x) as y;
+
+select pg_get_viewdef('view_of_joins_2a', true);
+select pg_get_viewdef('view_of_joins_2b', true);
+select pg_get_viewdef('view_of_joins_2c', true);
+select pg_get_viewdef('view_of_joins_2d', true);
+
+-- Test view decompilation in the face of column addition/deletion/renaming
+
+create table tt2 (a int, b int, c int);
+create table tt3 (ax int8, b int2, c numeric);
+create table tt4 (ay int, b int, q int);
+
+create view v1 as select * from tt2 natural join tt3;
+create view v1a as select * from (tt2 natural join tt3) j;
+create view v2 as select * from tt2 join tt3 using (b,c) join tt4 using (b);
+create view v2a as select * from (tt2 join tt3 using (b,c) join tt4 using (b)) j;
+create view v3 as select * from tt2 join tt3 using (b,c) full join tt4 using (b);
+
+select pg_get_viewdef('v1', true);
+select pg_get_viewdef('v1a', true);
+select pg_get_viewdef('v2', true);
+select pg_get_viewdef('v2a', true);
+select pg_get_viewdef('v3', true);
+
+alter table tt2 add column d int;
+alter table tt2 add column e int;
+
+select pg_get_viewdef('v1', true);
+select pg_get_viewdef('v1a', true);
+select pg_get_viewdef('v2', true);
+select pg_get_viewdef('v2a', true);
+select pg_get_viewdef('v3', true);
+
+alter table tt3 rename c to d;
+
+select pg_get_viewdef('v1', true);
+select pg_get_viewdef('v1a', true);
+select pg_get_viewdef('v2', true);
+select pg_get_viewdef('v2a', true);
+select pg_get_viewdef('v3', true);
+
+alter table tt3 add column c int;
+alter table tt3 add column e int;
+
+select pg_get_viewdef('v1', true);
+select pg_get_viewdef('v1a', true);
+select pg_get_viewdef('v2', true);
+select pg_get_viewdef('v2a', true);
+select pg_get_viewdef('v3', true);
+
+alter table tt2 drop column d;
+
+select pg_get_viewdef('v1', true);
+select pg_get_viewdef('v1a', true);
+select pg_get_viewdef('v2', true);
+select pg_get_viewdef('v2a', true);
+select pg_get_viewdef('v3', true);
+
+create table tt5 (a int, b int);
+create table tt6 (c int, d int);
+create view vv1 as select * from (tt5 cross join tt6) j(aa,bb,cc,dd);
+select pg_get_viewdef('vv1', true);
+alter table tt5 add column c int;
+select pg_get_viewdef('vv1', true);
+alter table tt5 add column cc int;
+select pg_get_viewdef('vv1', true);
+alter table tt5 drop column c;
+select pg_get_viewdef('vv1', true);
+
+create view v4 as select * from v1;
+alter view v1 rename column a to x;
+select pg_get_viewdef('v1', true);
+select pg_get_viewdef('v4', true);
+
+
+-- Unnamed FULL JOIN USING is lots of fun too
+
+create table tt7 (x int, xx int, y int);
+alter table tt7 drop column xx;
+create table tt8 (x int, z int);
+
+create view vv2 as
+select * from (values(1,2,3,4,5)) v(a,b,c,d,e)
+union all
+select * from tt7 full join tt8 using (x), tt8 tt8x;
+
+select pg_get_viewdef('vv2', true);
+
+create view vv3 as
+select * from (values(1,2,3,4,5,6)) v(a,b,c,x,e,f)
+union all
+select * from
+ tt7 full join tt8 using (x),
+ tt7 tt7x full join tt8 tt8x using (x);
+
+select pg_get_viewdef('vv3', true);
+
+create view vv4 as
+select * from (values(1,2,3,4,5,6,7)) v(a,b,c,x,e,f,g)
+union all
+select * from
+ tt7 full join tt8 using (x),
+ tt7 tt7x full join tt8 tt8x using (x) full join tt8 tt8y using (x);
+
+select pg_get_viewdef('vv4', true);
+
+alter table tt7 add column zz int;
+alter table tt7 add column z int;
+alter table tt7 drop column zz;
+alter table tt8 add column z2 int;
+
+select pg_get_viewdef('vv2', true);
+select pg_get_viewdef('vv3', true);
+select pg_get_viewdef('vv4', true);
+
+-- Implicit coercions in a JOIN USING create issues similar to FULL JOIN
+
+create table tt7a (x date, xx int, y int);
+alter table tt7a drop column xx;
+create table tt8a (x timestamptz, z int);
+
+create view vv2a as
+select * from (values(now(),2,3,now(),5)) v(a,b,c,d,e)
+union all
+select * from tt7a left join tt8a using (x), tt8a tt8ax;
+
+select pg_get_viewdef('vv2a', true);
+
+--
+-- Also check dropping a column that existed when the view was made
+--
+
+create table tt9 (x int, xx int, y int);
+create table tt10 (x int, z int);
+
+create view vv5 as select x,y,z from tt9 join tt10 using(x);
+
+select pg_get_viewdef('vv5', true);
+
+alter table tt9 drop column xx;
+
+select pg_get_viewdef('vv5', true);
+
+--
+-- Another corner case is that we might add a column to a table below a
+-- JOIN USING, and thereby make the USING column name ambiguous
+--
+
+create table tt11 (x int, y int);
+create table tt12 (x int, z int);
+create table tt13 (z int, q int);
+
+create view vv6 as select x,y,z,q from
+ (tt11 join tt12 using(x)) join tt13 using(z);
+
+select pg_get_viewdef('vv6', true);
+
+alter table tt11 add column z int;
+
+select pg_get_viewdef('vv6', true);
+
+--
+-- Check cases involving dropped/altered columns in a function's rowtype result
+--
+
+create table tt14t (f1 text, f2 text, f3 text, f4 text);
+insert into tt14t values('foo', 'bar', 'baz', '42');
+
+alter table tt14t drop column f2;
+
+create function tt14f() returns setof tt14t as
+$$
+declare
+ rec1 record;
+begin
+ for rec1 in select * from tt14t
+ loop
+ return next rec1;
+ end loop;
+end;
+$$
+language plpgsql;
+
+create view tt14v as select t.* from tt14f() t;
+
+select pg_get_viewdef('tt14v', true);
+select * from tt14v;
+
+begin;
+
+-- this perhaps should be rejected, but it isn't:
+alter table tt14t drop column f3;
+
+-- column f3 is still in the view, sort of ...
+select pg_get_viewdef('tt14v', true);
+-- ... and you can even EXPLAIN it ...
+explain (verbose, costs off) select * from tt14v;
+-- but it will fail at execution
+select f1, f4 from tt14v;
+select * from tt14v;
+
+rollback;
+
+begin;
+
+-- this perhaps should be rejected, but it isn't:
+alter table tt14t alter column f4 type integer using f4::integer;
+
+-- f4 is still in the view ...
+select pg_get_viewdef('tt14v', true);
+-- but will fail at execution
+select f1, f3 from tt14v;
+select * from tt14v;
+
+rollback;
+
+-- check display of whole-row variables in some corner cases
+
+create type nestedcomposite as (x int8_tbl);
+create view tt15v as select row(i)::nestedcomposite from int8_tbl i;
+select * from tt15v;
+select pg_get_viewdef('tt15v', true);
+select row(i.*::int8_tbl)::nestedcomposite from int8_tbl i;
+
+create view tt16v as select * from int8_tbl i, lateral(values(i)) ss;
+select * from tt16v;
+select pg_get_viewdef('tt16v', true);
+select * from int8_tbl i, lateral(values(i.*::int8_tbl)) ss;
+
+create view tt17v as select * from int8_tbl i where i in (values(i));
+select * from tt17v;
+select pg_get_viewdef('tt17v', true);
+select * from int8_tbl i where i.* in (values(i.*::int8_tbl));
+
+create table tt15v_log(o tt15v, n tt15v, incr bool);
+create rule updlog as on update to tt15v do also
+ insert into tt15v_log values(old, new, row(old,old) < row(new,new));
+\d+ tt15v
+
+-- check unique-ification of overlength names
+
+create view tt18v as
+ select * from int8_tbl xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxy
+ union all
+ select * from int8_tbl xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxz;
+select pg_get_viewdef('tt18v', true);
+explain (costs off) select * from tt18v;
+
+-- check display of ScalarArrayOp with a sub-select
+
+select 'foo'::text = any(array['abc','def','foo']::text[]);
+select 'foo'::text = any((select array['abc','def','foo']::text[])); -- fail
+select 'foo'::text = any((select array['abc','def','foo']::text[])::text[]);
+
+create view tt19v as
+select 'foo'::text = any(array['abc','def','foo']::text[]) c1,
+ 'foo'::text = any((select array['abc','def','foo']::text[])::text[]) c2;
+select pg_get_viewdef('tt19v', true);
+
+-- check display of assorted RTE_FUNCTION expressions
+
+create view tt20v as
+select * from
+ coalesce(1,2) as c,
+ collation for ('x'::text) col,
+ current_date as d,
+ localtimestamp(3) as t,
+ cast(1+2 as int4) as i4,
+ cast(1+2 as int8) as i8;
+select pg_get_viewdef('tt20v', true);
+
+-- reverse-listing of various special function syntaxes required by SQL
+
+create view tt201v as
+select
+ extract(day from now()) as extr,
+ (now(), '1 day'::interval) overlaps
+ (current_timestamp(2), '1 day'::interval) as o,
+ 'foo' is normalized isn,
+ 'foo' is nfkc normalized isnn,
+ normalize('foo') as n,
+ normalize('foo', nfkd) as nfkd,
+ overlay('foo' placing 'bar' from 2) as ovl,
+ overlay('foo' placing 'bar' from 2 for 3) as ovl2,
+ position('foo' in 'foobar') as p,
+ substring('foo' from 2 for 3) as s,
+ substring('foo' similar 'f' escape '#') as ss,
+ substring('foo' from 'oo') as ssf, -- historically-permitted abuse
+ trim(' ' from ' foo ') as bt,
+ trim(leading ' ' from ' foo ') as lt,
+ trim(trailing ' foo ') as rt,
+ trim(E'\\000'::bytea from E'\\000Tom\\000'::bytea) as btb,
+ trim(leading E'\\000'::bytea from E'\\000Tom\\000'::bytea) as ltb,
+ trim(trailing E'\\000'::bytea from E'\\000Tom\\000'::bytea) as rtb;
+select pg_get_viewdef('tt201v', true);
+
+-- corner cases with empty join conditions
+
+create view tt21v as
+select * from tt5 natural inner join tt6;
+select pg_get_viewdef('tt21v', true);
+
+create view tt22v as
+select * from tt5 natural left join tt6;
+select pg_get_viewdef('tt22v', true);
+
+-- check handling of views with immediately-renamed columns
+
+create view tt23v (col_a, col_b) as
+select q1 as other_name1, q2 as other_name2 from int8_tbl
+union
+select 42, 43;
+
+select pg_get_viewdef('tt23v', true);
+select pg_get_ruledef(oid, true) from pg_rewrite
+ where ev_class = 'tt23v'::regclass and ev_type = '1';
+
+-- test extraction of FieldSelect field names (get_name_for_var_field)
+
+create view tt24v as
+with cte as materialized (select r from (values(1,2),(3,4)) r)
+select (r).column2 as col_a, (rr).column2 as col_b from
+ cte join (select rr from (values(1,7),(3,8)) rr limit 2) ss
+ on (r).column1 = (rr).column1;
+select pg_get_viewdef('tt24v', true);
+create view tt25v as
+with cte as materialized (select pg_get_keywords() k)
+select (k).word from cte;
+select pg_get_viewdef('tt25v', true);
+-- also check cases seen only in EXPLAIN
+explain (verbose, costs off)
+select * from tt24v;
+explain (verbose, costs off)
+select (r).column2 from (select r from (values(1,2),(3,4)) r limit 1) ss;
+
+-- test pretty-print parenthesization rules, and SubLink deparsing
+
+create view tt26v as
+select x + y + z as c1,
+ (x * y) + z as c2,
+ x + (y * z) as c3,
+ (x + y) * z as c4,
+ x * (y + z) as c5,
+ x + (y + z) as c6,
+ x + (y # z) as c7,
+ (x > y) AND (y > z OR x > z) as c8,
+ (x > y) OR (y > z AND NOT (x > z)) as c9,
+ (x,y) <> ALL (values(1,2),(3,4)) as c10,
+ (x,y) <= ANY (values(1,2),(3,4)) as c11
+from (values(1,2,3)) v(x,y,z);
+select pg_get_viewdef('tt26v', true);
+
+-- clean up all the random objects we made above
+DROP SCHEMA temp_view_test CASCADE;
+DROP SCHEMA testviewschm2 CASCADE;
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
new file mode 100644
index 0000000..8f7435b
--- /dev/null
+++ b/src/test/regress/sql/date.sql
@@ -0,0 +1,366 @@
+--
+-- DATE
+--
+
+CREATE TABLE DATE_TBL (f1 date);
+
+INSERT INTO DATE_TBL VALUES ('1957-04-09');
+INSERT INTO DATE_TBL VALUES ('1957-06-13');
+INSERT INTO DATE_TBL VALUES ('1996-02-28');
+INSERT INTO DATE_TBL VALUES ('1996-02-29');
+INSERT INTO DATE_TBL VALUES ('1996-03-01');
+INSERT INTO DATE_TBL VALUES ('1996-03-02');
+INSERT INTO DATE_TBL VALUES ('1997-02-28');
+INSERT INTO DATE_TBL VALUES ('1997-02-29');
+INSERT INTO DATE_TBL VALUES ('1997-03-01');
+INSERT INTO DATE_TBL VALUES ('1997-03-02');
+INSERT INTO DATE_TBL VALUES ('2000-04-01');
+INSERT INTO DATE_TBL VALUES ('2000-04-02');
+INSERT INTO DATE_TBL VALUES ('2000-04-03');
+INSERT INTO DATE_TBL VALUES ('2038-04-08');
+INSERT INTO DATE_TBL VALUES ('2039-04-09');
+INSERT INTO DATE_TBL VALUES ('2040-04-10');
+INSERT INTO DATE_TBL VALUES ('2040-04-10 BC');
+
+SELECT f1 FROM DATE_TBL;
+
+SELECT f1 FROM DATE_TBL WHERE f1 < '2000-01-01';
+
+SELECT f1 FROM DATE_TBL
+ WHERE f1 BETWEEN '2000-01-01' AND '2001-01-01';
+
+--
+-- Check all the documented input formats
+--
+SET datestyle TO iso; -- display results in ISO
+
+SET datestyle TO ymd;
+
+SELECT date 'January 8, 1999';
+SELECT date '1999-01-08';
+SELECT date '1999-01-18';
+SELECT date '1/8/1999';
+SELECT date '1/18/1999';
+SELECT date '18/1/1999';
+SELECT date '01/02/03';
+SELECT date '19990108';
+SELECT date '990108';
+SELECT date '1999.008';
+SELECT date 'J2451187';
+SELECT date 'January 8, 99 BC';
+
+SELECT date '99-Jan-08';
+SELECT date '1999-Jan-08';
+SELECT date '08-Jan-99';
+SELECT date '08-Jan-1999';
+SELECT date 'Jan-08-99';
+SELECT date 'Jan-08-1999';
+SELECT date '99-08-Jan';
+SELECT date '1999-08-Jan';
+
+SELECT date '99 Jan 08';
+SELECT date '1999 Jan 08';
+SELECT date '08 Jan 99';
+SELECT date '08 Jan 1999';
+SELECT date 'Jan 08 99';
+SELECT date 'Jan 08 1999';
+SELECT date '99 08 Jan';
+SELECT date '1999 08 Jan';
+
+SELECT date '99-01-08';
+SELECT date '1999-01-08';
+SELECT date '08-01-99';
+SELECT date '08-01-1999';
+SELECT date '01-08-99';
+SELECT date '01-08-1999';
+SELECT date '99-08-01';
+SELECT date '1999-08-01';
+
+SELECT date '99 01 08';
+SELECT date '1999 01 08';
+SELECT date '08 01 99';
+SELECT date '08 01 1999';
+SELECT date '01 08 99';
+SELECT date '01 08 1999';
+SELECT date '99 08 01';
+SELECT date '1999 08 01';
+
+SET datestyle TO dmy;
+
+SELECT date 'January 8, 1999';
+SELECT date '1999-01-08';
+SELECT date '1999-01-18';
+SELECT date '1/8/1999';
+SELECT date '1/18/1999';
+SELECT date '18/1/1999';
+SELECT date '01/02/03';
+SELECT date '19990108';
+SELECT date '990108';
+SELECT date '1999.008';
+SELECT date 'J2451187';
+SELECT date 'January 8, 99 BC';
+
+SELECT date '99-Jan-08';
+SELECT date '1999-Jan-08';
+SELECT date '08-Jan-99';
+SELECT date '08-Jan-1999';
+SELECT date 'Jan-08-99';
+SELECT date 'Jan-08-1999';
+SELECT date '99-08-Jan';
+SELECT date '1999-08-Jan';
+
+SELECT date '99 Jan 08';
+SELECT date '1999 Jan 08';
+SELECT date '08 Jan 99';
+SELECT date '08 Jan 1999';
+SELECT date 'Jan 08 99';
+SELECT date 'Jan 08 1999';
+SELECT date '99 08 Jan';
+SELECT date '1999 08 Jan';
+
+SELECT date '99-01-08';
+SELECT date '1999-01-08';
+SELECT date '08-01-99';
+SELECT date '08-01-1999';
+SELECT date '01-08-99';
+SELECT date '01-08-1999';
+SELECT date '99-08-01';
+SELECT date '1999-08-01';
+
+SELECT date '99 01 08';
+SELECT date '1999 01 08';
+SELECT date '08 01 99';
+SELECT date '08 01 1999';
+SELECT date '01 08 99';
+SELECT date '01 08 1999';
+SELECT date '99 08 01';
+SELECT date '1999 08 01';
+
+SET datestyle TO mdy;
+
+SELECT date 'January 8, 1999';
+SELECT date '1999-01-08';
+SELECT date '1999-01-18';
+SELECT date '1/8/1999';
+SELECT date '1/18/1999';
+SELECT date '18/1/1999';
+SELECT date '01/02/03';
+SELECT date '19990108';
+SELECT date '990108';
+SELECT date '1999.008';
+SELECT date 'J2451187';
+SELECT date 'January 8, 99 BC';
+
+SELECT date '99-Jan-08';
+SELECT date '1999-Jan-08';
+SELECT date '08-Jan-99';
+SELECT date '08-Jan-1999';
+SELECT date 'Jan-08-99';
+SELECT date 'Jan-08-1999';
+SELECT date '99-08-Jan';
+SELECT date '1999-08-Jan';
+
+SELECT date '99 Jan 08';
+SELECT date '1999 Jan 08';
+SELECT date '08 Jan 99';
+SELECT date '08 Jan 1999';
+SELECT date 'Jan 08 99';
+SELECT date 'Jan 08 1999';
+SELECT date '99 08 Jan';
+SELECT date '1999 08 Jan';
+
+SELECT date '99-01-08';
+SELECT date '1999-01-08';
+SELECT date '08-01-99';
+SELECT date '08-01-1999';
+SELECT date '01-08-99';
+SELECT date '01-08-1999';
+SELECT date '99-08-01';
+SELECT date '1999-08-01';
+
+SELECT date '99 01 08';
+SELECT date '1999 01 08';
+SELECT date '08 01 99';
+SELECT date '08 01 1999';
+SELECT date '01 08 99';
+SELECT date '01 08 1999';
+SELECT date '99 08 01';
+SELECT date '1999 08 01';
+
+-- Check upper and lower limits of date range
+SELECT date '4714-11-24 BC';
+SELECT date '4714-11-23 BC'; -- out of range
+SELECT date '5874897-12-31';
+SELECT date '5874898-01-01'; -- out of range
+
+RESET datestyle;
+
+--
+-- Simple math
+-- Leave most of it for the horology tests
+--
+
+SELECT f1 - date '2000-01-01' AS "Days From 2K" FROM DATE_TBL;
+
+SELECT f1 - date 'epoch' AS "Days From Epoch" FROM DATE_TBL;
+
+SELECT date 'yesterday' - date 'today' AS "One day";
+
+SELECT date 'today' - date 'tomorrow' AS "One day";
+
+SELECT date 'yesterday' - date 'tomorrow' AS "Two days";
+
+SELECT date 'tomorrow' - date 'today' AS "One day";
+
+SELECT date 'today' - date 'yesterday' AS "One day";
+
+SELECT date 'tomorrow' - date 'yesterday' AS "Two days";
+
+--
+-- test extract!
+--
+SELECT f1 as "date",
+ date_part('year', f1) AS year,
+ date_part('month', f1) AS month,
+ date_part('day', f1) AS day,
+ date_part('quarter', f1) AS quarter,
+ date_part('decade', f1) AS decade,
+ date_part('century', f1) AS century,
+ date_part('millennium', f1) AS millennium,
+ date_part('isoyear', f1) AS isoyear,
+ date_part('week', f1) AS week,
+ date_part('dow', f1) AS dow,
+ date_part('isodow', f1) AS isodow,
+ date_part('doy', f1) AS doy,
+ date_part('julian', f1) AS julian,
+ date_part('epoch', f1) AS epoch
+ FROM date_tbl;
+--
+-- epoch
+--
+SELECT EXTRACT(EPOCH FROM DATE '1970-01-01'); -- 0
+--
+-- century
+--
+SELECT EXTRACT(CENTURY FROM DATE '0101-12-31 BC'); -- -2
+SELECT EXTRACT(CENTURY FROM DATE '0100-12-31 BC'); -- -1
+SELECT EXTRACT(CENTURY FROM DATE '0001-12-31 BC'); -- -1
+SELECT EXTRACT(CENTURY FROM DATE '0001-01-01'); -- 1
+SELECT EXTRACT(CENTURY FROM DATE '0001-01-01 AD'); -- 1
+SELECT EXTRACT(CENTURY FROM DATE '1900-12-31'); -- 19
+SELECT EXTRACT(CENTURY FROM DATE '1901-01-01'); -- 20
+SELECT EXTRACT(CENTURY FROM DATE '2000-12-31'); -- 20
+SELECT EXTRACT(CENTURY FROM DATE '2001-01-01'); -- 21
+SELECT EXTRACT(CENTURY FROM CURRENT_DATE)>=21 AS True; -- true
+--
+-- millennium
+--
+SELECT EXTRACT(MILLENNIUM FROM DATE '0001-12-31 BC'); -- -1
+SELECT EXTRACT(MILLENNIUM FROM DATE '0001-01-01 AD'); -- 1
+SELECT EXTRACT(MILLENNIUM FROM DATE '1000-12-31'); -- 1
+SELECT EXTRACT(MILLENNIUM FROM DATE '1001-01-01'); -- 2
+SELECT EXTRACT(MILLENNIUM FROM DATE '2000-12-31'); -- 2
+SELECT EXTRACT(MILLENNIUM FROM DATE '2001-01-01'); -- 3
+-- next test to be fixed on the turn of the next millennium;-)
+SELECT EXTRACT(MILLENNIUM FROM CURRENT_DATE); -- 3
+--
+-- decade
+--
+SELECT EXTRACT(DECADE FROM DATE '1994-12-25'); -- 199
+SELECT EXTRACT(DECADE FROM DATE '0010-01-01'); -- 1
+SELECT EXTRACT(DECADE FROM DATE '0009-12-31'); -- 0
+SELECT EXTRACT(DECADE FROM DATE '0001-01-01 BC'); -- 0
+SELECT EXTRACT(DECADE FROM DATE '0002-12-31 BC'); -- -1
+SELECT EXTRACT(DECADE FROM DATE '0011-01-01 BC'); -- -1
+SELECT EXTRACT(DECADE FROM DATE '0012-12-31 BC'); -- -2
+--
+-- all possible fields
+--
+SELECT EXTRACT(MICROSECONDS FROM DATE '2020-08-11');
+SELECT EXTRACT(MILLISECONDS FROM DATE '2020-08-11');
+SELECT EXTRACT(SECOND FROM DATE '2020-08-11');
+SELECT EXTRACT(MINUTE FROM DATE '2020-08-11');
+SELECT EXTRACT(HOUR FROM DATE '2020-08-11');
+SELECT EXTRACT(DAY FROM DATE '2020-08-11');
+SELECT EXTRACT(MONTH FROM DATE '2020-08-11');
+SELECT EXTRACT(YEAR FROM DATE '2020-08-11');
+SELECT EXTRACT(YEAR FROM DATE '2020-08-11 BC');
+SELECT EXTRACT(DECADE FROM DATE '2020-08-11');
+SELECT EXTRACT(CENTURY FROM DATE '2020-08-11');
+SELECT EXTRACT(MILLENNIUM FROM DATE '2020-08-11');
+SELECT EXTRACT(ISOYEAR FROM DATE '2020-08-11');
+SELECT EXTRACT(ISOYEAR FROM DATE '2020-08-11 BC');
+SELECT EXTRACT(QUARTER FROM DATE '2020-08-11');
+SELECT EXTRACT(WEEK FROM DATE '2020-08-11');
+SELECT EXTRACT(DOW FROM DATE '2020-08-11');
+SELECT EXTRACT(DOW FROM DATE '2020-08-16');
+SELECT EXTRACT(ISODOW FROM DATE '2020-08-11');
+SELECT EXTRACT(ISODOW FROM DATE '2020-08-16');
+SELECT EXTRACT(DOY FROM DATE '2020-08-11');
+SELECT EXTRACT(TIMEZONE FROM DATE '2020-08-11');
+SELECT EXTRACT(TIMEZONE_M FROM DATE '2020-08-11');
+SELECT EXTRACT(TIMEZONE_H FROM DATE '2020-08-11');
+SELECT EXTRACT(EPOCH FROM DATE '2020-08-11');
+SELECT EXTRACT(JULIAN FROM DATE '2020-08-11');
+--
+-- test trunc function!
+--
+SELECT DATE_TRUNC('MILLENNIUM', TIMESTAMP '1970-03-20 04:30:00.00000'); -- 1001
+SELECT DATE_TRUNC('MILLENNIUM', DATE '1970-03-20'); -- 1001-01-01
+SELECT DATE_TRUNC('CENTURY', TIMESTAMP '1970-03-20 04:30:00.00000'); -- 1901
+SELECT DATE_TRUNC('CENTURY', DATE '1970-03-20'); -- 1901
+SELECT DATE_TRUNC('CENTURY', DATE '2004-08-10'); -- 2001-01-01
+SELECT DATE_TRUNC('CENTURY', DATE '0002-02-04'); -- 0001-01-01
+SELECT DATE_TRUNC('CENTURY', DATE '0055-08-10 BC'); -- 0100-01-01 BC
+SELECT DATE_TRUNC('DECADE', DATE '1993-12-25'); -- 1990-01-01
+SELECT DATE_TRUNC('DECADE', DATE '0004-12-25'); -- 0001-01-01 BC
+SELECT DATE_TRUNC('DECADE', DATE '0002-12-31 BC'); -- 0011-01-01 BC
+--
+-- test infinity
+--
+select 'infinity'::date, '-infinity'::date;
+select 'infinity'::date > 'today'::date as t;
+select '-infinity'::date < 'today'::date as t;
+select isfinite('infinity'::date), isfinite('-infinity'::date), isfinite('today'::date);
+--
+-- oscillating fields from non-finite date:
+--
+SELECT EXTRACT(DAY FROM DATE 'infinity'); -- NULL
+SELECT EXTRACT(DAY FROM DATE '-infinity'); -- NULL
+-- all supported fields
+SELECT EXTRACT(DAY FROM DATE 'infinity'); -- NULL
+SELECT EXTRACT(MONTH FROM DATE 'infinity'); -- NULL
+SELECT EXTRACT(QUARTER FROM DATE 'infinity'); -- NULL
+SELECT EXTRACT(WEEK FROM DATE 'infinity'); -- NULL
+SELECT EXTRACT(DOW FROM DATE 'infinity'); -- NULL
+SELECT EXTRACT(ISODOW FROM DATE 'infinity'); -- NULL
+SELECT EXTRACT(DOY FROM DATE 'infinity'); -- NULL
+--
+-- monotonic fields from non-finite date:
+--
+SELECT EXTRACT(EPOCH FROM DATE 'infinity'); -- Infinity
+SELECT EXTRACT(EPOCH FROM DATE '-infinity'); -- -Infinity
+-- all supported fields
+SELECT EXTRACT(YEAR FROM DATE 'infinity'); -- Infinity
+SELECT EXTRACT(DECADE FROM DATE 'infinity'); -- Infinity
+SELECT EXTRACT(CENTURY FROM DATE 'infinity'); -- Infinity
+SELECT EXTRACT(MILLENNIUM FROM DATE 'infinity'); -- Infinity
+SELECT EXTRACT(JULIAN FROM DATE 'infinity'); -- Infinity
+SELECT EXTRACT(ISOYEAR FROM DATE 'infinity'); -- Infinity
+SELECT EXTRACT(EPOCH FROM DATE 'infinity'); -- Infinity
+--
+-- wrong fields from non-finite date:
+--
+SELECT EXTRACT(MICROSEC FROM DATE 'infinity'); -- error
+
+-- test constructors
+select make_date(2013, 7, 15);
+select make_date(-44, 3, 15);
+select make_time(8, 20, 0.0);
+-- should fail
+select make_date(0, 7, 15);
+select make_date(2013, 2, 30);
+select make_date(2013, 13, 1);
+select make_date(2013, 11, -1);
+select make_time(10, 55, 100.1);
+select make_time(24, 0, 2.1);
diff --git a/src/test/regress/sql/dbsize.sql b/src/test/regress/sql/dbsize.sql
new file mode 100644
index 0000000..6a45c5e
--- /dev/null
+++ b/src/test/regress/sql/dbsize.sql
@@ -0,0 +1,67 @@
+SELECT size, pg_size_pretty(size), pg_size_pretty(-1 * size) FROM
+ (VALUES (10::bigint), (1000::bigint), (1000000::bigint),
+ (1000000000::bigint), (1000000000000::bigint),
+ (1000000000000000::bigint)) x(size);
+
+SELECT size, pg_size_pretty(size), pg_size_pretty(-1 * size) FROM
+ (VALUES (10::numeric), (1000::numeric), (1000000::numeric),
+ (1000000000::numeric), (1000000000000::numeric),
+ (1000000000000000::numeric),
+ (10.5::numeric), (1000.5::numeric), (1000000.5::numeric),
+ (1000000000.5::numeric), (1000000000000.5::numeric),
+ (1000000000000000.5::numeric)) x(size);
+
+-- test where units change up
+SELECT size, pg_size_pretty(size), pg_size_pretty(-1 * size) FROM
+ (VALUES (10239::bigint), (10240::bigint),
+ (10485247::bigint), (10485248::bigint),
+ (10736893951::bigint), (10736893952::bigint),
+ (10994579406847::bigint), (10994579406848::bigint),
+ (11258449312612351::bigint), (11258449312612352::bigint)) x(size);
+
+SELECT size, pg_size_pretty(size), pg_size_pretty(-1 * size) FROM
+ (VALUES (10239::numeric), (10240::numeric),
+ (10485247::numeric), (10485248::numeric),
+ (10736893951::numeric), (10736893952::numeric),
+ (10994579406847::numeric), (10994579406848::numeric),
+ (11258449312612351::numeric), (11258449312612352::numeric)) x(size);
+
+-- pg_size_bytes() tests
+SELECT size, pg_size_bytes(size) FROM
+ (VALUES ('1'), ('123bytes'), ('1kB'), ('1MB'), (' 1 GB'), ('1.5 GB '),
+ ('1TB'), ('3000 TB'), ('1e6 MB')) x(size);
+
+-- case-insensitive units are supported
+SELECT size, pg_size_bytes(size) FROM
+ (VALUES ('1'), ('123bYteS'), ('1kb'), ('1mb'), (' 1 Gb'), ('1.5 gB '),
+ ('1tb'), ('3000 tb'), ('1e6 mb')) x(size);
+
+-- negative numbers are supported
+SELECT size, pg_size_bytes(size) FROM
+ (VALUES ('-1'), ('-123bytes'), ('-1kb'), ('-1mb'), (' -1 Gb'), ('-1.5 gB '),
+ ('-1tb'), ('-3000 TB'), ('-10e-1 MB')) x(size);
+
+-- different cases with allowed points
+SELECT size, pg_size_bytes(size) FROM
+ (VALUES ('-1.'), ('-1.kb'), ('-1. kb'), ('-0. gb'),
+ ('-.1'), ('-.1kb'), ('-.1 kb'), ('-.0 gb')) x(size);
+
+-- invalid inputs
+SELECT pg_size_bytes('1 AB');
+SELECT pg_size_bytes('1 AB A');
+SELECT pg_size_bytes('1 AB A ');
+SELECT pg_size_bytes('9223372036854775807.9');
+SELECT pg_size_bytes('1e100');
+SELECT pg_size_bytes('1e1000000000000000000');
+SELECT pg_size_bytes('1 byte'); -- the singular "byte" is not supported
+SELECT pg_size_bytes('');
+
+SELECT pg_size_bytes('kb');
+SELECT pg_size_bytes('..');
+SELECT pg_size_bytes('-.');
+SELECT pg_size_bytes('-.kb');
+SELECT pg_size_bytes('-. kb');
+
+SELECT pg_size_bytes('.+912');
+SELECT pg_size_bytes('+912+ kB');
+SELECT pg_size_bytes('++123 kB');
diff --git a/src/test/regress/sql/delete.sql b/src/test/regress/sql/delete.sql
new file mode 100644
index 0000000..d8cb99e
--- /dev/null
+++ b/src/test/regress/sql/delete.sql
@@ -0,0 +1,25 @@
+CREATE TABLE delete_test (
+ id SERIAL PRIMARY KEY,
+ a INT,
+ b text
+);
+
+INSERT INTO delete_test (a) VALUES (10);
+INSERT INTO delete_test (a, b) VALUES (50, repeat('x', 10000));
+INSERT INTO delete_test (a) VALUES (100);
+
+-- allow an alias to be specified for DELETE's target table
+DELETE FROM delete_test AS dt WHERE dt.a > 75;
+
+-- if an alias is specified, don't allow the original table name
+-- to be referenced
+DELETE FROM delete_test dt WHERE delete_test.a > 25;
+
+SELECT id, a, char_length(b) FROM delete_test;
+
+-- delete a row with a TOASTed value
+DELETE FROM delete_test WHERE a > 25;
+
+SELECT id, a, char_length(b) FROM delete_test;
+
+DROP TABLE delete_test;
diff --git a/src/test/regress/sql/dependency.sql b/src/test/regress/sql/dependency.sql
new file mode 100644
index 0000000..2559c62
--- /dev/null
+++ b/src/test/regress/sql/dependency.sql
@@ -0,0 +1,116 @@
+--
+-- DEPENDENCIES
+--
+
+CREATE USER regress_dep_user;
+CREATE USER regress_dep_user2;
+CREATE USER regress_dep_user3;
+CREATE GROUP regress_dep_group;
+
+CREATE TABLE deptest (f1 serial primary key, f2 text);
+
+GRANT SELECT ON TABLE deptest TO GROUP regress_dep_group;
+GRANT ALL ON TABLE deptest TO regress_dep_user, regress_dep_user2;
+
+-- can't drop neither because they have privileges somewhere
+DROP USER regress_dep_user;
+DROP GROUP regress_dep_group;
+
+-- if we revoke the privileges we can drop the group
+REVOKE SELECT ON deptest FROM GROUP regress_dep_group;
+DROP GROUP regress_dep_group;
+
+-- can't drop the user if we revoke the privileges partially
+REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON deptest FROM regress_dep_user;
+DROP USER regress_dep_user;
+
+-- now we are OK to drop him
+REVOKE TRIGGER ON deptest FROM regress_dep_user;
+DROP USER regress_dep_user;
+
+-- we are OK too if we drop the privileges all at once
+REVOKE ALL ON deptest FROM regress_dep_user2;
+DROP USER regress_dep_user2;
+
+-- can't drop the owner of an object
+-- the error message detail here would include a pg_toast_nnn name that
+-- is not constant, so suppress it
+\set VERBOSITY terse
+ALTER TABLE deptest OWNER TO regress_dep_user3;
+DROP USER regress_dep_user3;
+\set VERBOSITY default
+
+-- if we drop the object, we can drop the user too
+DROP TABLE deptest;
+DROP USER regress_dep_user3;
+
+-- Test DROP OWNED
+CREATE USER regress_dep_user0;
+CREATE USER regress_dep_user1;
+CREATE USER regress_dep_user2;
+SET SESSION AUTHORIZATION regress_dep_user0;
+-- permission denied
+DROP OWNED BY regress_dep_user1;
+DROP OWNED BY regress_dep_user0, regress_dep_user2;
+REASSIGN OWNED BY regress_dep_user0 TO regress_dep_user1;
+REASSIGN OWNED BY regress_dep_user1 TO regress_dep_user0;
+-- this one is allowed
+DROP OWNED BY regress_dep_user0;
+
+CREATE TABLE deptest1 (f1 int unique);
+GRANT ALL ON deptest1 TO regress_dep_user1 WITH GRANT OPTION;
+
+SET SESSION AUTHORIZATION regress_dep_user1;
+CREATE TABLE deptest (a serial primary key, b text);
+GRANT ALL ON deptest1 TO regress_dep_user2;
+RESET SESSION AUTHORIZATION;
+\z deptest1
+
+DROP OWNED BY regress_dep_user1;
+-- all grants revoked
+\z deptest1
+-- table was dropped
+\d deptest
+
+-- Test REASSIGN OWNED
+GRANT ALL ON deptest1 TO regress_dep_user1;
+GRANT CREATE ON DATABASE regression TO regress_dep_user1;
+
+SET SESSION AUTHORIZATION regress_dep_user1;
+CREATE SCHEMA deptest;
+CREATE TABLE deptest (a serial primary key, b text);
+ALTER DEFAULT PRIVILEGES FOR ROLE regress_dep_user1 IN SCHEMA deptest
+ GRANT ALL ON TABLES TO regress_dep_user2;
+CREATE FUNCTION deptest_func() RETURNS void LANGUAGE plpgsql
+ AS $$ BEGIN END; $$;
+CREATE TYPE deptest_enum AS ENUM ('red');
+CREATE TYPE deptest_range AS RANGE (SUBTYPE = int4);
+
+CREATE TABLE deptest2 (f1 int);
+-- make a serial column the hard way
+CREATE SEQUENCE ss1;
+ALTER TABLE deptest2 ALTER f1 SET DEFAULT nextval('ss1');
+ALTER SEQUENCE ss1 OWNED BY deptest2.f1;
+
+-- When reassigning ownership of a composite type, its pg_class entry
+-- should match
+CREATE TYPE deptest_t AS (a int);
+SELECT typowner = relowner
+FROM pg_type JOIN pg_class c ON typrelid = c.oid WHERE typname = 'deptest_t';
+
+RESET SESSION AUTHORIZATION;
+REASSIGN OWNED BY regress_dep_user1 TO regress_dep_user2;
+\dt deptest
+
+SELECT typowner = relowner
+FROM pg_type JOIN pg_class c ON typrelid = c.oid WHERE typname = 'deptest_t';
+
+-- doesn't work: grant still exists
+DROP USER regress_dep_user1;
+DROP OWNED BY regress_dep_user1;
+DROP USER regress_dep_user1;
+
+DROP USER regress_dep_user2;
+DROP OWNED BY regress_dep_user2, regress_dep_user0;
+DROP USER regress_dep_user2;
+DROP USER regress_dep_user0;
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
new file mode 100644
index 0000000..f2ca1fb
--- /dev/null
+++ b/src/test/regress/sql/domain.sql
@@ -0,0 +1,788 @@
+--
+-- Test domains.
+--
+
+-- Test Comment / Drop
+create domain domaindroptest int4;
+comment on domain domaindroptest is 'About to drop this..';
+
+create domain dependenttypetest domaindroptest;
+
+-- fail because of dependent type
+drop domain domaindroptest;
+
+drop domain domaindroptest cascade;
+
+-- this should fail because already gone
+drop domain domaindroptest cascade;
+
+
+-- Test domain input.
+
+-- Note: the point of checking both INSERT and COPY FROM is that INSERT
+-- exercises CoerceToDomain while COPY exercises domain_in.
+
+create domain domainvarchar varchar(5);
+create domain domainnumeric numeric(8,2);
+create domain domainint4 int4;
+create domain domaintext text;
+
+-- Test explicit coercions --- these should succeed (and truncate)
+SELECT cast('123456' as domainvarchar);
+SELECT cast('12345' as domainvarchar);
+
+-- Test tables using domains
+create table basictest
+ ( testint4 domainint4
+ , testtext domaintext
+ , testvarchar domainvarchar
+ , testnumeric domainnumeric
+ );
+
+INSERT INTO basictest values ('88', 'haha', 'short', '123.12'); -- Good
+INSERT INTO basictest values ('88', 'haha', 'short text', '123.12'); -- Bad varchar
+INSERT INTO basictest values ('88', 'haha', 'short', '123.1212'); -- Truncate numeric
+
+-- Test copy
+COPY basictest (testvarchar) FROM stdin; -- fail
+notsoshorttext
+\.
+
+COPY basictest (testvarchar) FROM stdin;
+short
+\.
+
+select * from basictest;
+
+-- check that domains inherit operations from base types
+select testtext || testvarchar as concat, testnumeric + 42 as sum
+from basictest;
+
+-- check that union/case/coalesce type resolution handles domains properly
+select pg_typeof(coalesce(4::domainint4, 7));
+select pg_typeof(coalesce(4::domainint4, 7::domainint4));
+
+drop table basictest;
+drop domain domainvarchar restrict;
+drop domain domainnumeric restrict;
+drop domain domainint4 restrict;
+drop domain domaintext;
+
+
+-- Test domains over array types
+
+create domain domainint4arr int4[1];
+create domain domainchar4arr varchar(4)[2][3];
+
+create table domarrtest
+ ( testint4arr domainint4arr
+ , testchar4arr domainchar4arr
+ );
+INSERT INTO domarrtest values ('{2,2}', '{{"a","b"},{"c","d"}}');
+INSERT INTO domarrtest values ('{{2,2},{2,2}}', '{{"a","b"}}');
+INSERT INTO domarrtest values ('{2,2}', '{{"a","b"},{"c","d"},{"e","f"}}');
+INSERT INTO domarrtest values ('{2,2}', '{{"a"},{"c"}}');
+INSERT INTO domarrtest values (NULL, '{{"a","b","c"},{"d","e","f"}}');
+INSERT INTO domarrtest values (NULL, '{{"toolong","b","c"},{"d","e","f"}}');
+INSERT INTO domarrtest (testint4arr[1], testint4arr[3]) values (11,22);
+select * from domarrtest;
+select testint4arr[1], testchar4arr[2:2] from domarrtest;
+select array_dims(testint4arr), array_dims(testchar4arr) from domarrtest;
+
+COPY domarrtest FROM stdin;
+{3,4} {q,w,e}
+\N \N
+\.
+
+COPY domarrtest FROM stdin; -- fail
+{3,4} {qwerty,w,e}
+\.
+
+select * from domarrtest;
+
+update domarrtest set
+ testint4arr[1] = testint4arr[1] + 1,
+ testint4arr[3] = testint4arr[3] - 1
+where testchar4arr is null;
+
+select * from domarrtest where testchar4arr is null;
+
+drop table domarrtest;
+drop domain domainint4arr restrict;
+drop domain domainchar4arr restrict;
+
+create domain dia as int[];
+select '{1,2,3}'::dia;
+select array_dims('{1,2,3}'::dia);
+select pg_typeof('{1,2,3}'::dia);
+select pg_typeof('{1,2,3}'::dia || 42); -- should be int[] not dia
+drop domain dia;
+
+
+-- Test domains over composites
+
+create type comptype as (r float8, i float8);
+create domain dcomptype as comptype;
+create table dcomptable (d1 dcomptype unique);
+
+insert into dcomptable values (row(1,2)::dcomptype);
+insert into dcomptable values (row(3,4)::comptype);
+insert into dcomptable values (row(1,2)::dcomptype); -- fail on uniqueness
+insert into dcomptable (d1.r) values(11);
+
+select * from dcomptable;
+select (d1).r, (d1).i, (d1).* from dcomptable;
+update dcomptable set d1.r = (d1).r + 1 where (d1).i > 0;
+select * from dcomptable;
+
+alter domain dcomptype add constraint c1 check ((value).r <= (value).i);
+alter domain dcomptype add constraint c2 check ((value).r > (value).i); -- fail
+
+select row(2,1)::dcomptype; -- fail
+insert into dcomptable values (row(1,2)::comptype);
+insert into dcomptable values (row(2,1)::comptype); -- fail
+insert into dcomptable (d1.r) values(99);
+insert into dcomptable (d1.r, d1.i) values(99, 100);
+insert into dcomptable (d1.r, d1.i) values(100, 99); -- fail
+update dcomptable set d1.r = (d1).r + 1 where (d1).i > 0; -- fail
+update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
+select * from dcomptable;
+
+explain (verbose, costs off)
+ update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
+create rule silly as on delete to dcomptable do instead
+ update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
+\d+ dcomptable
+
+create function makedcomp(r float8, i float8) returns dcomptype
+as 'select row(r, i)' language sql;
+
+select makedcomp(1,2);
+select makedcomp(2,1); -- fail
+select * from makedcomp(1,2) m;
+select m, m is not null from makedcomp(1,2) m;
+
+drop function makedcomp(float8, float8);
+drop table dcomptable;
+drop type comptype cascade;
+
+
+-- check altering and dropping columns used by domain constraints
+create type comptype as (r float8, i float8);
+create domain dcomptype as comptype;
+alter domain dcomptype add constraint c1 check ((value).r > 0);
+comment on constraint c1 on domain dcomptype is 'random commentary';
+
+select row(0,1)::dcomptype; -- fail
+
+alter type comptype alter attribute r type varchar; -- fail
+alter type comptype alter attribute r type bigint;
+
+alter type comptype drop attribute r; -- fail
+alter type comptype drop attribute i;
+
+select conname, obj_description(oid, 'pg_constraint') from pg_constraint
+ where contypid = 'dcomptype'::regtype; -- check comment is still there
+
+drop type comptype cascade;
+
+
+-- Test domains over arrays of composite
+
+create type comptype as (r float8, i float8);
+create domain dcomptypea as comptype[];
+create table dcomptable (d1 dcomptypea unique);
+
+insert into dcomptable values (array[row(1,2)]::dcomptypea);
+insert into dcomptable values (array[row(3,4), row(5,6)]::comptype[]);
+insert into dcomptable values (array[row(7,8)::comptype, row(9,10)::comptype]);
+insert into dcomptable values (array[row(1,2)]::dcomptypea); -- fail on uniqueness
+insert into dcomptable (d1[1]) values(row(9,10));
+insert into dcomptable (d1[1].r) values(11);
+
+select * from dcomptable;
+select d1[2], d1[1].r, d1[1].i from dcomptable;
+update dcomptable set d1[2] = row(d1[2].i, d1[2].r);
+select * from dcomptable;
+update dcomptable set d1[1].r = d1[1].r + 1 where d1[1].i > 0;
+select * from dcomptable;
+
+alter domain dcomptypea add constraint c1 check (value[1].r <= value[1].i);
+alter domain dcomptypea add constraint c2 check (value[1].r > value[1].i); -- fail
+
+select array[row(2,1)]::dcomptypea; -- fail
+insert into dcomptable values (array[row(1,2)]::comptype[]);
+insert into dcomptable values (array[row(2,1)]::comptype[]); -- fail
+insert into dcomptable (d1[1].r) values(99);
+insert into dcomptable (d1[1].r, d1[1].i) values(99, 100);
+insert into dcomptable (d1[1].r, d1[1].i) values(100, 99); -- fail
+update dcomptable set d1[1].r = d1[1].r + 1 where d1[1].i > 0; -- fail
+update dcomptable set d1[1].r = d1[1].r - 1, d1[1].i = d1[1].i + 1
+ where d1[1].i > 0;
+select * from dcomptable;
+
+explain (verbose, costs off)
+ update dcomptable set d1[1].r = d1[1].r - 1, d1[1].i = d1[1].i + 1
+ where d1[1].i > 0;
+create rule silly as on delete to dcomptable do instead
+ update dcomptable set d1[1].r = d1[1].r - 1, d1[1].i = d1[1].i + 1
+ where d1[1].i > 0;
+\d+ dcomptable
+
+drop table dcomptable;
+drop type comptype cascade;
+
+
+-- Test arrays over domains
+
+create domain posint as int check (value > 0);
+
+create table pitable (f1 posint[]);
+insert into pitable values(array[42]);
+insert into pitable values(array[-1]); -- fail
+insert into pitable values('{0}'); -- fail
+update pitable set f1[1] = f1[1] + 1;
+update pitable set f1[1] = 0; -- fail
+select * from pitable;
+drop table pitable;
+
+create domain vc4 as varchar(4);
+create table vc4table (f1 vc4[]);
+insert into vc4table values(array['too long']); -- fail
+insert into vc4table values(array['too long']::vc4[]); -- cast truncates
+select * from vc4table;
+drop table vc4table;
+drop type vc4;
+
+-- You can sort of fake arrays-of-arrays by putting a domain in between
+create domain dposinta as posint[];
+create table dposintatable (f1 dposinta[]);
+insert into dposintatable values(array[array[42]]); -- fail
+insert into dposintatable values(array[array[42]::posint[]]); -- still fail
+insert into dposintatable values(array[array[42]::dposinta]); -- but this works
+select f1, f1[1], (f1[1])[1] from dposintatable;
+select pg_typeof(f1) from dposintatable;
+select pg_typeof(f1[1]) from dposintatable;
+select pg_typeof(f1[1][1]) from dposintatable;
+select pg_typeof((f1[1])[1]) from dposintatable;
+update dposintatable set f1[2] = array[99];
+select f1, f1[1], (f1[2])[1] from dposintatable;
+-- it'd be nice if you could do something like this, but for now you can't:
+update dposintatable set f1[2][1] = array[97];
+-- maybe someday we can make this syntax work:
+update dposintatable set (f1[2])[1] = array[98];
+
+drop table dposintatable;
+drop domain posint cascade;
+
+
+-- Test arrays over domains of composite
+
+create type comptype as (cf1 int, cf2 int);
+create domain dcomptype as comptype check ((value).cf1 > 0);
+
+create table dcomptable (f1 dcomptype[]);
+insert into dcomptable values (null);
+update dcomptable set f1[1].cf2 = 5;
+table dcomptable;
+update dcomptable set f1[1].cf1 = -1; -- fail
+update dcomptable set f1[1].cf1 = 1;
+table dcomptable;
+
+drop table dcomptable;
+drop type comptype cascade;
+
+
+-- Test not-null restrictions
+
+create domain dnotnull varchar(15) NOT NULL;
+create domain dnull varchar(15);
+create domain dcheck varchar(15) NOT NULL CHECK (VALUE = 'a' OR VALUE = 'c' OR VALUE = 'd');
+
+create table nulltest
+ ( col1 dnotnull
+ , col2 dnotnull NULL -- NOT NULL in the domain cannot be overridden
+ , col3 dnull NOT NULL
+ , col4 dnull
+ , col5 dcheck CHECK (col5 IN ('c', 'd'))
+ );
+INSERT INTO nulltest DEFAULT VALUES;
+INSERT INTO nulltest values ('a', 'b', 'c', 'd', 'c'); -- Good
+insert into nulltest values ('a', 'b', 'c', 'd', NULL);
+insert into nulltest values ('a', 'b', 'c', 'd', 'a');
+INSERT INTO nulltest values (NULL, 'b', 'c', 'd', 'd');
+INSERT INTO nulltest values ('a', NULL, 'c', 'd', 'c');
+INSERT INTO nulltest values ('a', 'b', NULL, 'd', 'c');
+INSERT INTO nulltest values ('a', 'b', 'c', NULL, 'd'); -- Good
+
+-- Test copy
+COPY nulltest FROM stdin; --fail
+a b \N d d
+\.
+
+COPY nulltest FROM stdin; --fail
+a b c d \N
+\.
+
+-- Last row is bad
+COPY nulltest FROM stdin;
+a b c \N c
+a b c \N d
+a b c \N a
+\.
+
+select * from nulltest;
+
+-- Test out coerced (casted) constraints
+SELECT cast('1' as dnotnull);
+SELECT cast(NULL as dnotnull); -- fail
+SELECT cast(cast(NULL as dnull) as dnotnull); -- fail
+SELECT cast(col4 as dnotnull) from nulltest; -- fail
+
+-- cleanup
+drop table nulltest;
+drop domain dnotnull restrict;
+drop domain dnull restrict;
+drop domain dcheck restrict;
+
+
+create domain ddef1 int4 DEFAULT 3;
+create domain ddef2 oid DEFAULT '12';
+-- Type mixing, function returns int8
+create domain ddef3 text DEFAULT 5;
+create sequence ddef4_seq;
+create domain ddef4 int4 DEFAULT nextval('ddef4_seq');
+create domain ddef5 numeric(8,2) NOT NULL DEFAULT '12.12';
+
+create table defaulttest
+ ( col1 ddef1
+ , col2 ddef2
+ , col3 ddef3
+ , col4 ddef4 PRIMARY KEY
+ , col5 ddef1 NOT NULL DEFAULT NULL
+ , col6 ddef2 DEFAULT '88'
+ , col7 ddef4 DEFAULT 8000
+ , col8 ddef5
+ );
+insert into defaulttest(col4) values(0); -- fails, col5 defaults to null
+alter table defaulttest alter column col5 drop default;
+insert into defaulttest default values; -- succeeds, inserts domain default
+-- We used to treat SET DEFAULT NULL as equivalent to DROP DEFAULT; wrong
+alter table defaulttest alter column col5 set default null;
+insert into defaulttest(col4) values(0); -- fails
+alter table defaulttest alter column col5 drop default;
+insert into defaulttest default values;
+insert into defaulttest default values;
+
+-- Test defaults with copy
+COPY defaulttest(col5) FROM stdin;
+42
+\.
+
+select * from defaulttest;
+
+drop table defaulttest cascade;
+
+-- Test ALTER DOMAIN .. NOT NULL
+create domain dnotnulltest integer;
+create table domnotnull
+( col1 dnotnulltest
+, col2 dnotnulltest
+);
+
+insert into domnotnull default values;
+alter domain dnotnulltest set not null; -- fails
+
+update domnotnull set col1 = 5;
+alter domain dnotnulltest set not null; -- fails
+
+update domnotnull set col2 = 6;
+
+alter domain dnotnulltest set not null;
+
+update domnotnull set col1 = null; -- fails
+
+alter domain dnotnulltest drop not null;
+
+update domnotnull set col1 = null;
+
+drop domain dnotnulltest cascade;
+
+-- Test ALTER DOMAIN .. DEFAULT ..
+create table domdeftest (col1 ddef1);
+
+insert into domdeftest default values;
+select * from domdeftest;
+
+alter domain ddef1 set default '42';
+insert into domdeftest default values;
+select * from domdeftest;
+
+alter domain ddef1 drop default;
+insert into domdeftest default values;
+select * from domdeftest;
+
+drop table domdeftest;
+
+-- Test ALTER DOMAIN .. CONSTRAINT ..
+create domain con as integer;
+create table domcontest (col1 con);
+
+insert into domcontest values (1);
+insert into domcontest values (2);
+alter domain con add constraint t check (VALUE < 1); -- fails
+
+alter domain con add constraint t check (VALUE < 34);
+alter domain con add check (VALUE > 0);
+
+insert into domcontest values (-5); -- fails
+insert into domcontest values (42); -- fails
+insert into domcontest values (5);
+
+alter domain con drop constraint t;
+insert into domcontest values (-5); --fails
+insert into domcontest values (42);
+
+alter domain con drop constraint nonexistent;
+alter domain con drop constraint if exists nonexistent;
+
+-- Test ALTER DOMAIN .. CONSTRAINT .. NOT VALID
+create domain things AS INT;
+CREATE TABLE thethings (stuff things);
+INSERT INTO thethings (stuff) VALUES (55);
+ALTER DOMAIN things ADD CONSTRAINT meow CHECK (VALUE < 11);
+ALTER DOMAIN things ADD CONSTRAINT meow CHECK (VALUE < 11) NOT VALID;
+ALTER DOMAIN things VALIDATE CONSTRAINT meow;
+UPDATE thethings SET stuff = 10;
+ALTER DOMAIN things VALIDATE CONSTRAINT meow;
+
+-- Confirm ALTER DOMAIN with RULES.
+create table domtab (col1 integer);
+create domain dom as integer;
+create view domview as select cast(col1 as dom) from domtab;
+insert into domtab (col1) values (null);
+insert into domtab (col1) values (5);
+select * from domview;
+
+alter domain dom set not null;
+select * from domview; -- fail
+
+alter domain dom drop not null;
+select * from domview;
+
+alter domain dom add constraint domchkgt6 check(value > 6);
+select * from domview; --fail
+
+alter domain dom drop constraint domchkgt6 restrict;
+select * from domview;
+
+-- cleanup
+drop domain ddef1 restrict;
+drop domain ddef2 restrict;
+drop domain ddef3 restrict;
+drop domain ddef4 restrict;
+drop domain ddef5 restrict;
+drop sequence ddef4_seq;
+
+-- Test domains over domains
+create domain vchar4 varchar(4);
+create domain dinter vchar4 check (substring(VALUE, 1, 1) = 'x');
+create domain dtop dinter check (substring(VALUE, 2, 1) = '1');
+
+select 'x123'::dtop;
+select 'x1234'::dtop; -- explicit coercion should truncate
+select 'y1234'::dtop; -- fail
+select 'y123'::dtop; -- fail
+select 'yz23'::dtop; -- fail
+select 'xz23'::dtop; -- fail
+
+create temp table dtest(f1 dtop);
+
+insert into dtest values('x123');
+insert into dtest values('x1234'); -- fail, implicit coercion
+insert into dtest values('y1234'); -- fail, implicit coercion
+insert into dtest values('y123'); -- fail
+insert into dtest values('yz23'); -- fail
+insert into dtest values('xz23'); -- fail
+
+drop table dtest;
+drop domain vchar4 cascade;
+
+-- Make sure that constraints of newly-added domain columns are
+-- enforced correctly, even if there's no default value for the new
+-- column. Per bug #1433
+create domain str_domain as text not null;
+
+create table domain_test (a int, b int);
+
+insert into domain_test values (1, 2);
+insert into domain_test values (1, 2);
+
+-- should fail
+alter table domain_test add column c str_domain;
+
+create domain str_domain2 as text check (value <> 'foo') default 'foo';
+
+-- should fail
+alter table domain_test add column d str_domain2;
+
+-- Check that domain constraints on prepared statement parameters of
+-- unknown type are enforced correctly.
+create domain pos_int as int4 check (value > 0) not null;
+prepare s1 as select $1::pos_int = 10 as "is_ten";
+
+execute s1(10);
+execute s1(0); -- should fail
+execute s1(NULL); -- should fail
+
+-- Check that domain constraints on plpgsql function parameters, results,
+-- and local variables are enforced correctly.
+
+create function doubledecrement(p1 pos_int) returns pos_int as $$
+declare v pos_int;
+begin
+ return p1;
+end$$ language plpgsql;
+
+select doubledecrement(3); -- fail because of implicit null assignment
+
+create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
+declare v pos_int := 0;
+begin
+ return p1;
+end$$ language plpgsql;
+
+select doubledecrement(3); -- fail at initialization assignment
+
+create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
+declare v pos_int := 1;
+begin
+ v := p1 - 1;
+ return v - 1;
+end$$ language plpgsql;
+
+select doubledecrement(null); -- fail before call
+select doubledecrement(0); -- fail before call
+select doubledecrement(1); -- fail at assignment to v
+select doubledecrement(2); -- fail at return
+select doubledecrement(3); -- good
+
+-- Check that ALTER DOMAIN tests columns of derived types
+
+create domain posint as int4;
+
+-- Currently, this doesn't work for composite types, but verify it complains
+create type ddtest1 as (f1 posint);
+create table ddtest2(f1 ddtest1);
+insert into ddtest2 values(row(-1));
+alter domain posint add constraint c1 check(value >= 0);
+drop table ddtest2;
+
+-- Likewise for domains within arrays of composite
+create table ddtest2(f1 ddtest1[]);
+insert into ddtest2 values('{(-1)}');
+alter domain posint add constraint c1 check(value >= 0);
+drop table ddtest2;
+
+-- Likewise for domains within domains over composite
+create domain ddtest1d as ddtest1;
+create table ddtest2(f1 ddtest1d);
+insert into ddtest2 values('(-1)');
+alter domain posint add constraint c1 check(value >= 0);
+drop table ddtest2;
+drop domain ddtest1d;
+
+-- Likewise for domains within domains over array of composite
+create domain ddtest1d as ddtest1[];
+create table ddtest2(f1 ddtest1d);
+insert into ddtest2 values('{(-1)}');
+alter domain posint add constraint c1 check(value >= 0);
+drop table ddtest2;
+drop domain ddtest1d;
+
+-- Doesn't work for ranges, either
+create type rposint as range (subtype = posint);
+create table ddtest2(f1 rposint);
+insert into ddtest2 values('(-1,3]');
+alter domain posint add constraint c1 check(value >= 0);
+drop table ddtest2;
+drop type rposint;
+
+alter domain posint add constraint c1 check(value >= 0);
+
+create domain posint2 as posint check (value % 2 = 0);
+create table ddtest2(f1 posint2);
+insert into ddtest2 values(11); -- fail
+insert into ddtest2 values(-2); -- fail
+insert into ddtest2 values(2);
+
+alter domain posint add constraint c2 check(value >= 10); -- fail
+alter domain posint add constraint c2 check(value > 0); -- OK
+
+drop table ddtest2;
+drop type ddtest1;
+drop domain posint cascade;
+
+--
+-- Check enforcement of domain-related typmod in plpgsql (bug #5717)
+--
+
+create or replace function array_elem_check(numeric) returns numeric as $$
+declare
+ x numeric(4,2)[1];
+begin
+ x[1] := $1;
+ return x[1];
+end$$ language plpgsql;
+
+select array_elem_check(121.00);
+select array_elem_check(1.23456);
+
+create domain mynums as numeric(4,2)[1];
+
+create or replace function array_elem_check(numeric) returns numeric as $$
+declare
+ x mynums;
+begin
+ x[1] := $1;
+ return x[1];
+end$$ language plpgsql;
+
+select array_elem_check(121.00);
+select array_elem_check(1.23456);
+
+create domain mynums2 as mynums;
+
+create or replace function array_elem_check(numeric) returns numeric as $$
+declare
+ x mynums2;
+begin
+ x[1] := $1;
+ return x[1];
+end$$ language plpgsql;
+
+select array_elem_check(121.00);
+select array_elem_check(1.23456);
+
+drop function array_elem_check(numeric);
+
+--
+-- Check enforcement of array-level domain constraints
+--
+
+create domain orderedpair as int[2] check (value[1] < value[2]);
+
+select array[1,2]::orderedpair;
+select array[2,1]::orderedpair; -- fail
+
+create temp table op (f1 orderedpair);
+insert into op values (array[1,2]);
+insert into op values (array[2,1]); -- fail
+
+update op set f1[2] = 3;
+update op set f1[2] = 0; -- fail
+select * from op;
+
+create or replace function array_elem_check(int) returns int as $$
+declare
+ x orderedpair := '{1,2}';
+begin
+ x[2] := $1;
+ return x[2];
+end$$ language plpgsql;
+
+select array_elem_check(3);
+select array_elem_check(-1);
+
+drop function array_elem_check(int);
+
+--
+-- Check enforcement of changing constraints in plpgsql
+--
+
+create domain di as int;
+
+create function dom_check(int) returns di as $$
+declare d di;
+begin
+ d := $1::di;
+ return d;
+end
+$$ language plpgsql immutable;
+
+select dom_check(0);
+
+alter domain di add constraint pos check (value > 0);
+
+select dom_check(0); -- fail
+
+alter domain di drop constraint pos;
+
+select dom_check(0);
+
+-- implicit cast during assignment is a separate code path, test that too
+
+create or replace function dom_check(int) returns di as $$
+declare d di;
+begin
+ d := $1;
+ return d;
+end
+$$ language plpgsql immutable;
+
+select dom_check(0);
+
+alter domain di add constraint pos check (value > 0);
+
+select dom_check(0); -- fail
+
+alter domain di drop constraint pos;
+
+select dom_check(0);
+
+drop function dom_check(int);
+
+drop domain di;
+
+--
+-- Check use of a (non-inline-able) SQL function in a domain constraint;
+-- this has caused issues in the past
+--
+
+create function sql_is_distinct_from(anyelement, anyelement)
+returns boolean language sql
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull int
+ check (sql_is_distinct_from(value, null));
+
+select 1::inotnull;
+select null::inotnull;
+
+create table dom_table (x inotnull);
+insert into dom_table values ('1');
+insert into dom_table values (1);
+insert into dom_table values (null);
+
+drop table dom_table;
+drop domain inotnull;
+drop function sql_is_distinct_from(anyelement, anyelement);
+
+--
+-- Renaming
+--
+
+create domain testdomain1 as int;
+alter domain testdomain1 rename to testdomain2;
+alter type testdomain2 rename to testdomain3; -- alter type also works
+drop domain testdomain3;
+
+
+--
+-- Renaming domain constraints
+--
+
+create domain testdomain1 as int constraint unsigned check (value > 0);
+alter domain testdomain1 rename constraint unsigned to unsigned_foo;
+alter domain testdomain1 drop constraint unsigned_foo;
+drop domain testdomain1;
diff --git a/src/test/regress/sql/drop_if_exists.sql b/src/test/regress/sql/drop_if_exists.sql
new file mode 100644
index 0000000..ac6168b
--- /dev/null
+++ b/src/test/regress/sql/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/drop_operator.sql b/src/test/regress/sql/drop_operator.sql
new file mode 100644
index 0000000..cc62cfa
--- /dev/null
+++ b/src/test/regress/sql/drop_operator.sql
@@ -0,0 +1,56 @@
+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 ctid, oprnegate
+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 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 ctid, oprnegate
+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/enum.sql b/src/test/regress/sql/enum.sql
new file mode 100644
index 0000000..6affd0d
--- /dev/null
+++ b/src/test/regress/sql/enum.sql
@@ -0,0 +1,341 @@
+--
+-- 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
+--
+
+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 enumsortorder 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
+\.
+SELECT * FROM enumtest;
+
+--
+-- 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/equivclass.sql b/src/test/regress/sql/equivclass.sql
new file mode 100644
index 0000000..247b0a3
--- /dev/null
+++ b/src/test/regress/sql/equivclass.sql
@@ -0,0 +1,271 @@
+--
+-- Tests for the planner's "equivalence class" mechanism
+--
+
+-- One thing that's not tested well during normal querying is the logic
+-- for handling "broken" ECs. This is because an EC can only become broken
+-- if its underlying btree operator family doesn't include a complete set
+-- of cross-type equality operators. There are not (and should not be)
+-- any such families built into Postgres; so we have to hack things up
+-- to create one. We do this by making two alias types that are really
+-- int8 (so we need no new C code) and adding only some operators for them
+-- into the standard integer_ops opfamily.
+
+create type int8alias1;
+create function int8alias1in(cstring) returns int8alias1
+ strict immutable language internal as 'int8in';
+create function int8alias1out(int8alias1) returns cstring
+ strict immutable language internal as 'int8out';
+create type int8alias1 (
+ input = int8alias1in,
+ output = int8alias1out,
+ like = int8
+);
+
+create type int8alias2;
+create function int8alias2in(cstring) returns int8alias2
+ strict immutable language internal as 'int8in';
+create function int8alias2out(int8alias2) returns cstring
+ strict immutable language internal as 'int8out';
+create type int8alias2 (
+ input = int8alias2in,
+ output = int8alias2out,
+ like = int8
+);
+
+create cast (int8 as int8alias1) without function;
+create cast (int8 as int8alias2) without function;
+create cast (int8alias1 as int8) without function;
+create cast (int8alias2 as int8) without function;
+
+create function int8alias1eq(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias1,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias1);
+
+create function int8alias2eq(int8alias2, int8alias2) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias2eq,
+ leftarg = int8alias2, rightarg = int8alias2,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias2, int8alias2);
+
+create function int8alias1eq(int8, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8, rightarg = int8alias1,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8, int8alias1);
+
+create function int8alias1eq(int8alias1, int8alias2) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias2,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias2);
+
+create function int8alias1lt(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8lt';
+create operator < (
+ procedure = int8alias1lt,
+ leftarg = int8alias1, rightarg = int8alias1
+);
+alter operator family integer_ops using btree add
+ operator 1 < (int8alias1, int8alias1);
+
+create function int8alias1cmp(int8, int8alias1) returns int
+ strict immutable language internal as 'btint8cmp';
+alter operator family integer_ops using btree add
+ function 1 int8alias1cmp (int8, int8alias1);
+
+create table ec0 (ff int8 primary key, f1 int8, f2 int8);
+create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
+create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
+
+-- for the moment we only want to look at nestloop plans
+set enable_hashjoin = off;
+set enable_mergejoin = off;
+
+--
+-- Note that for cases where there's a missing operator, we don't care so
+-- much whether the plan is ideal as that we don't fail or generate an
+-- outright incorrect plan.
+--
+
+explain (costs off)
+ select * from ec0 where ff = f1 and f1 = '42'::int8;
+explain (costs off)
+ select * from ec0 where ff = f1 and f1 = '42'::int8alias1;
+explain (costs off)
+ select * from ec1 where ff = f1 and f1 = '42'::int8alias1;
+explain (costs off)
+ select * from ec1 where ff = f1 and f1 = '42'::int8alias2;
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and ff = '42'::int8;
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and ff = '42'::int8alias1;
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and '42'::int8 = x1;
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias1;
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias2;
+
+create unique index ec1_expr1 on ec1((ff + 1));
+create unique index ec1_expr2 on ec1((ff + 2 + 1));
+create unique index ec1_expr3 on ec1((ff + 3 + 1));
+create unique index ec1_expr4 on ec1((ff + 4));
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8 and ec1.ff = ec1.f1;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss2
+ where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+
+-- let's try that as a mergejoin
+set enable_mergejoin = on;
+set enable_nestloop = off;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss2
+ where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+
+-- check partially indexed scan
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+drop index ec1_expr3;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+
+-- let's try that as a mergejoin
+set enable_mergejoin = on;
+set enable_nestloop = off;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+
+-- check effects of row-level security
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+alter table ec1 enable row level security;
+create policy p1 on ec1 using (f1 < '5'::int8alias1);
+
+create user regress_user_ectest;
+grant select on ec0 to regress_user_ectest;
+grant select on ec1 to regress_user_ectest;
+
+-- without any RLS, we'll treat {a.ff, b.ff, 43} as an EquivalenceClass
+explain (costs off)
+ select * from ec0 a, ec1 b
+ where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
+
+set session authorization regress_user_ectest;
+
+-- with RLS active, the non-leakproof a.ff = 43 clause is not treated
+-- as a suitable source for an EquivalenceClass; currently, this is true
+-- even though the RLS clause has nothing to do directly with the EC
+explain (costs off)
+ select * from ec0 a, ec1 b
+ where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
+
+reset session authorization;
+
+revoke select on ec0 from regress_user_ectest;
+revoke select on ec1 from regress_user_ectest;
+
+drop user regress_user_ectest;
+
+-- check that X=X is converted to X IS NOT NULL when appropriate
+explain (costs off)
+ select * from tenk1 where unique1 = unique1 and unique2 = unique2;
+
+-- this could be converted, but isn't at present
+explain (costs off)
+ select * from tenk1 where unique1 = unique1 or unique2 = unique2;
+
+-- check that we recognize equivalence with dummy domains in the way
+create temp table undername (f1 name, f2 int);
+create temp view overview as
+ select f1::information_schema.sql_identifier as sqli, f2 from undername;
+explain (costs off) -- this should not require a sort
+ select * from overview where sqli = 'foo' order by sqli;
diff --git a/src/test/regress/sql/errors.sql b/src/test/regress/sql/errors.sql
new file mode 100644
index 0000000..a7fcf72
--- /dev/null
+++ b/src/test/regress/sql/errors.sql
@@ -0,0 +1,370 @@
+--
+-- 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 aggtest;
+
+-- 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);
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
new file mode 100644
index 0000000..5e45e3f
--- /dev/null
+++ b/src/test/regress/sql/event_trigger.sql
@@ -0,0 +1,468 @@
+-- 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, can't call it as a plain function
+SELECT test_event_trigger();
+
+-- 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
+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;
+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);
+-- non-top-level command
+create function f1() returns int
+language plpgsql
+as $$
+begin
+ create table event_trigger_fire6 (a int);
+ return 0;
+end $$;
+select f1();
+-- non-top-level command
+create procedure p1()
+language plpgsql
+as $$
+begin
+ create table event_trigger_fire7 (a int);
+end $$;
+call p1();
+
+-- clean up
+alter event trigger regress_event_trigger disable;
+drop table event_trigger_fire2, event_trigger_fire3, event_trigger_fire4, event_trigger_fire5, event_trigger_fire6, event_trigger_fire7;
+drop routine f1(), p1();
+
+-- 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 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;
+
+-- should fail, regress_evt_user owns some objects
+drop role regress_evt_user;
+
+-- 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;
+
+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';
+
+DROP ROLE regress_evt_user;
+
+DROP EVENT TRIGGER regress_event_trigger_drop_objects;
+DROP EVENT TRIGGER undroppable;
+
+-- Event triggers on relations.
+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 'NORMAL: orig=% normal=% istemp=% type=% identity=% name=% args=%',
+ r.original, r.normal, r.is_temporary, r.object_type,
+ r.object_identity, r.address_names, r.address_args;
+ END LOOP;
+END; $$;
+CREATE EVENT TRIGGER regress_event_trigger_report_dropped ON sql_drop
+ EXECUTE PROCEDURE event_trigger_report_dropped();
+CREATE OR REPLACE FUNCTION event_trigger_report_end()
+ RETURNS event_trigger
+ LANGUAGE plpgsql
+AS $$
+DECLARE r RECORD;
+BEGIN
+ FOR r IN SELECT * FROM pg_event_trigger_ddl_commands()
+ LOOP
+ RAISE NOTICE 'END: command_tag=% type=% identity=%',
+ r.command_tag, r.object_type, r.object_identity;
+ END LOOP;
+END; $$;
+CREATE EVENT TRIGGER regress_event_trigger_report_end ON ddl_command_end
+ EXECUTE PROCEDURE event_trigger_report_end();
+
+CREATE SCHEMA evttrig
+ CREATE TABLE one (col_a SERIAL PRIMARY KEY, col_b text DEFAULT 'forty two', col_c SERIAL)
+ CREATE INDEX one_idx ON one (col_b)
+ CREATE TABLE two (col_c INTEGER CHECK (col_c > 0) REFERENCES one DEFAULT 42)
+ CREATE TABLE id (col_d int NOT NULL GENERATED ALWAYS AS IDENTITY);
+
+-- 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;
+ALTER TABLE evttrig.one DROP COLUMN col_c;
+ALTER TABLE evttrig.id ALTER COLUMN col_d SET DATA TYPE bigint;
+ALTER TABLE evttrig.id ALTER COLUMN col_d DROP IDENTITY,
+ ALTER COLUMN col_d SET DATA TYPE int;
+DROP INDEX evttrig.one_idx;
+DROP SCHEMA evttrig CASCADE;
+DROP TABLE a_temp_tbl;
+
+-- CREATE OPERATOR CLASS without FAMILY clause should report
+-- both CREATE OPERATOR FAMILY and CREATE OPERATOR CLASS
+CREATE OPERATOR CLASS evttrigopclass FOR TYPE int USING btree AS STORAGE int;
+
+DROP EVENT TRIGGER regress_event_trigger_report_dropped;
+DROP EVENT TRIGGER regress_event_trigger_report_end;
+
+-- 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;
+
+-- Check the object addresses of all the event triggers.
+SELECT
+ e.evtname,
+ pg_describe_object('pg_event_trigger'::regclass, e.oid, 0) as descr,
+ b.type, b.object_names, b.object_args,
+ pg_identify_object(a.classid, a.objid, a.objsubid) as ident
+ FROM pg_event_trigger as e,
+ LATERAL pg_identify_object_as_address('pg_event_trigger'::regclass, e.oid, 0) as b,
+ LATERAL pg_get_object_address(b.type, b.object_names, b.object_args) as a
+ ORDER BY e.evtname;
+
+DROP EVENT TRIGGER start_rls_command;
+DROP EVENT TRIGGER end_rls_command;
+DROP EVENT TRIGGER sql_drop_command;
diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql
new file mode 100644
index 0000000..3f9ae98
--- /dev/null
+++ b/src/test/regress/sql/explain.sql
@@ -0,0 +1,108 @@
+--
+-- EXPLAIN
+--
+-- There are many test cases elsewhere that use EXPLAIN as a vehicle for
+-- checking something else (usually planner behavior). This file is
+-- concerned with testing EXPLAIN in its own right.
+--
+
+-- 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.
+
+create function explain_filter(text) returns setof text
+language plpgsql as
+$$
+declare
+ ln text;
+begin
+ for ln in execute $1
+ loop
+ -- Replace any numeric word with just 'N'
+ ln := regexp_replace(ln, '-?\m\d+\M', 'N', 'g');
+ -- In sort output, the above won't match units-suffixed numbers
+ ln := regexp_replace(ln, '\m\d+kB', 'NkB', 'g');
+ -- Ignore text-mode buffers output because it varies depending
+ -- on the system state
+ CONTINUE WHEN (ln ~ ' +Buffers: .*');
+ -- Ignore text-mode "Planning:" line because whether it's output
+ -- varies depending on the system state
+ CONTINUE WHEN (ln = 'Planning:');
+ return next ln;
+ end loop;
+end;
+$$;
+
+-- 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;
+$$;
+
+-- Simple cases
+
+select explain_filter('explain select * from int8_tbl i8');
+select explain_filter('explain (analyze) select * from int8_tbl i8');
+select explain_filter('explain (analyze, verbose) select * from int8_tbl i8');
+select explain_filter('explain (analyze, buffers, format text) select * from int8_tbl i8');
+select explain_filter('explain (analyze, buffers, format json) select * from int8_tbl i8');
+select explain_filter('explain (analyze, buffers, format xml) select * from int8_tbl i8');
+select explain_filter('explain (analyze, buffers, format yaml) select * from int8_tbl i8');
+select explain_filter('explain (buffers, format text) select * from int8_tbl i8');
+select explain_filter('explain (buffers, format json) select * from int8_tbl i8');
+
+-- SETTINGS option
+-- We have to ignore other settings that might be imposed by the environment,
+-- so printing the whole Settings field unfortunately won't do.
+
+begin;
+set local plan_cache_mode = force_generic_plan;
+select true as "OK"
+ from explain_filter('explain (settings) select * from int8_tbl i8') ln
+ where ln ~ '^ *Settings: .*plan_cache_mode = ''force_generic_plan''';
+select explain_filter_to_json('explain (settings, format json) select * from int8_tbl i8') #> '{0,Settings,plan_cache_mode}';
+rollback;
+
+--
+-- Test production of per-worker data
+--
+-- Unfortunately, because we don't know how many worker processes we'll
+-- actually get (maybe none at all), we can't examine the "Workers" output
+-- in any detail. We can check that it parses correctly as JSON, and then
+-- remove it from the displayed results.
+
+begin;
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+
+select jsonb_pretty(
+ explain_filter_to_json('explain (analyze, verbose, buffers, format json)
+ select * from tenk1 order by tenthous')
+ -- remove "Workers" node of the Seq Scan plan node
+ #- '{0,Plan,Plans,0,Plans,0,Workers}'
+ -- remove "Workers" node of the Sort plan node
+ #- '{0,Plan,Plans,0,Workers}'
+ -- Also remove its sort-type fields, as those aren't 100% stable
+ #- '{0,Plan,Plans,0,Sort Method}'
+ #- '{0,Plan,Plans,0,Sort Space Type}'
+);
+
+rollback;
+
+set compute_query_id = on;
+select explain_filter('explain (verbose) select * from int8_tbl i8');
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
new file mode 100644
index 0000000..f9f9f97
--- /dev/null
+++ b/src/test/regress/sql/expressions.sql
@@ -0,0 +1,205 @@
+--
+-- expression evaluation 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()::timetz(4)::text = current_time(4)::text;
+SELECT now()::time::text = localtime::text;
+SELECT now()::time(3)::text = localtime(3)::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;
+
+
+--
+-- 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';
+
+
+--
+-- Test parsing of a no-op cast to a type with unspecified typmod
+--
+begin;
+
+create table numeric_tbl (f1 numeric(18,3), f2 numeric);
+
+create view numeric_view as
+ select
+ f1, f1::numeric(16,4) as f1164, f1::numeric as f1n,
+ f2, f2::numeric(16,4) as f2164, f2::numeric as f2n
+ from numeric_tbl;
+
+\d+ numeric_view
+
+explain (verbose, costs off) select * from numeric_view;
+
+-- bpchar, lacking planner support for its length coercion function,
+-- could behave differently
+
+create table bpchar_tbl (f1 character(16) unique, f2 bpchar);
+
+create view bpchar_view as
+ select
+ f1, f1::character(14) as f114, f1::bpchar as f1n,
+ f2, f2::character(14) as f214, f2::bpchar as f2n
+ from bpchar_tbl;
+
+\d+ bpchar_view
+
+explain (verbose, costs off) select * from bpchar_view
+ where f1::bpchar = 'foo';
+
+rollback;
+
+
+--
+-- Ordinarily, IN/NOT IN can be converted to a ScalarArrayOpExpr
+-- with a suitably-chosen array type.
+--
+explain (verbose, costs off)
+select random() IN (1, 4, 8.0);
+explain (verbose, costs off)
+select random()::int IN (1, 4, 8.0);
+-- However, if there's not a common supertype for the IN elements,
+-- we should instead try to produce "x = v1 OR x = v2 OR ...".
+-- In most cases that'll fail for lack of all the requisite = operators,
+-- but it can succeed sometimes. So this should complain about lack of
+-- an = operator, not about cast failure.
+select '(0,0)'::point in ('(0,0,0,0)'::box, point(0,0));
+
+
+--
+-- Tests for ScalarArrayOpExpr with a hashfn
+--
+
+-- create a stable function so that the tests below are not
+-- evaluated using the planner's constant folding.
+begin;
+
+create function return_int_input(int) returns int as $$
+begin
+ return $1;
+end;
+$$ language plpgsql stable;
+
+create function return_text_input(text) returns text as $$
+begin
+ return $1;
+end;
+$$ language plpgsql stable;
+
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select return_int_input(1) in (null, null, null, null, null, null, null, null, null, null, null);
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+
+rollback;
+
+-- Test with non-strict equality function.
+-- We need to create our own type for this.
+
+begin;
+
+create type myint;
+create function myintin(cstring) returns myint strict immutable language
+ internal as 'int4in';
+create function myintout(myint) returns cstring strict immutable language
+ internal as 'int4out';
+create function myinthash(myint) returns integer strict immutable language
+ internal as 'hashint4';
+
+create type myint (input = myintin, output = myintout, like = int4);
+
+create cast (int4 as myint) without function;
+create cast (myint as int4) without function;
+
+create function myinteq(myint, myint) returns bool as $$
+begin
+ if $1 is null and $2 is null then
+ return true;
+ else
+ return $1::int = $2::int;
+ end if;
+end;
+$$ language plpgsql immutable;
+
+create operator = (
+ leftarg = myint,
+ rightarg = myint,
+ commutator = =,
+ negator = <>,
+ procedure = myinteq,
+ restrict = eqsel,
+ join = eqjoinsel,
+ merges
+);
+
+create operator class myint_ops
+default for type myint using hash as
+ operator 1 = (myint, myint),
+ function 1 myinthash(myint);
+
+create table inttest (a myint);
+insert into inttest values(1::myint),(null);
+
+-- try an array with enough elements to cause hashing
+select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+-- ensure the result matched with the non-hashed version. We simply remove
+-- some array elements so that we don't reach the hashing threshold.
+select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+
+rollback;
diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql
new file mode 100644
index 0000000..16a3b7c
--- /dev/null
+++ b/src/test/regress/sql/fast_default.sql
@@ -0,0 +1,562 @@
+--
+-- 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;
+
+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 querie
+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;
+
+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
+DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *;
+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;
+
+
+-- 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;
+
+-- verify that a default set on a non-plain table doesn't set a missing
+-- value on the attribute
+CREATE FOREIGN DATA WRAPPER dummy;
+CREATE SERVER s0 FOREIGN DATA WRAPPER dummy;
+CREATE FOREIGN TABLE ft1 (c1 integer NOT NULL) SERVER s0;
+ALTER FOREIGN TABLE ft1 ADD COLUMN c8 integer DEFAULT 0;
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10);
+SELECT count(*)
+ FROM pg_attribute
+ WHERE attrelid = 'ft1'::regclass AND
+ (attmissingval IS NOT NULL OR atthasmissing);
+
+-- cleanup
+DROP FOREIGN TABLE ft1;
+DROP SERVER s0;
+DROP FOREIGN DATA WRAPPER dummy;
+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/float4.sql b/src/test/regress/sql/float4.sql
new file mode 100644
index 0000000..612486e
--- /dev/null
+++ b/src/test/regress/sql/float4.sql
@@ -0,0 +1,354 @@
+--
+-- 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;
+
+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
+drop type xfloat4 cascade;
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
new file mode 100644
index 0000000..97f0c3b
--- /dev/null
+++ b/src/test/regress/sql/float8.sql
@@ -0,0 +1,500 @@
+--
+-- FLOAT8
+--
+
+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;
+
+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');
+
+-- maintain external table consistency across platforms
+-- delete all values and reinsert well-behaved ones
+
+DELETE FROM FLOAT8_TBL;
+
+INSERT INTO FLOAT8_TBL(f1) VALUES ('0.0');
+
+INSERT INTO FLOAT8_TBL(f1) VALUES ('-34.84');
+
+INSERT INTO FLOAT8_TBL(f1) VALUES ('-1004.30');
+
+INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2345678901234e+200');
+
+INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2345678901234e-200');
+
+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
+drop type xfloat8 cascade;
diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql
new file mode 100644
index 0000000..279786f
--- /dev/null
+++ b/src/test/regress/sql/foreign_data.sql
@@ -0,0 +1,862 @@
+--
+-- Test foreign-data wrapper and server management.
+--
+
+-- 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; -- 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;
+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;
+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 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
+
+-- 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
+DROP FOREIGN TABLE ft2 CASCADE;
+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
+
+-- TRUNCATE doesn't work on foreign tables, either directly or recursively
+TRUNCATE ft2; -- ERROR
+TRUNCATE fd_pt1; -- ERROR
+
+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);
+
+-- TRUNCATE doesn't work on foreign tables, either directly or recursively
+TRUNCATE fd_pt2_1; -- ERROR
+TRUNCATE fd_pt2; -- ERROR
+
+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/foreign_key.sql b/src/test/regress/sql/foreign_key.sql
new file mode 100644
index 0000000..de417b6
--- /dev/null
+++ b/src/test/regress/sql/foreign_key.sql
@@ -0,0 +1,1831 @@
+--
+-- FOREIGN KEY
+--
+
+-- 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;
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLE WHERE ptest1=1;
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLE;
+
+-- Update a row from PK TABLE
+UPDATE PKTABLE SET ptest1=1 WHERE ptest1=2;
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLE;
+
+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;
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLE WHERE ptest1=1 and ptest2=2;
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLE;
+
+-- Delete another row from PK TABLE
+DELETE FROM PKTABLE WHERE ptest1=5 and ptest2=10;
+
+-- Check FKTABLE (should be no change)
+SELECT * FROM FKTABLE;
+
+-- Update a row from PK TABLE
+UPDATE PKTABLE SET ptest1=1 WHERE ptest1=2;
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLE;
+
+-- Check update with part of key null
+UPDATE FKTABLE SET ftest1 = NULL WHERE ftest1 = 1;
+
+-- Check update with old and new key values equal
+UPDATE FKTABLE SET ftest1 = 1 WHERE ftest1 = 1;
+
+-- 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;
+SELECT * FROM FKTABLE;
+
+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;
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLE WHERE ptest1=1 and ptest2=2;
+
+-- Check FKTABLE to check for removal
+SELECT * FROM FKTABLE;
+
+-- Delete another row from PK TABLE
+DELETE FROM PKTABLE WHERE ptest1=5 and ptest2=10;
+
+-- Check FKTABLE (should be no change)
+SELECT * FROM FKTABLE;
+
+-- Update a row from PK TABLE
+UPDATE PKTABLE SET ptest1=1 WHERE ptest1=2;
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLE;
+
+-- this should fail for lack of CASCADE
+DROP TABLE PKTABLE;
+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;
+
+-- Check PKTABLE
+SELECT * FROM PKTABLE;
+
+-- 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;
+
+-- 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;
+
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
+
+--
+-- Check initial check upon ALTER TABLE
+--
+CREATE TABLE PKTABLE ( ptest1 int, ptest2 int, PRIMARY KEY(ptest1, ptest2) );
+CREATE TABLE FKTABLE ( ftest1 int, ftest2 int );
+
+INSERT INTO PKTABLE VALUES (1, 2);
+INSERT INTO FKTABLE VALUES (1, NULL);
+
+ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1, ftest2) REFERENCES PKTABLE MATCH FULL;
+
+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;
+
+-- 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;
+
+SELECT * from FKTABLE;
+
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
+
+-- restrict with null values
+CREATE TABLE PKTABLE ( ptest1 int, ptest2 int, ptest3 int, ptest4 text, UNIQUE(ptest1, ptest2, ptest3) );
+CREATE TABLE FKTABLE ( ftest1 int, ftest2 int, ftest3 int, ftest4 int, CONSTRAINT constrname3
+ FOREIGN KEY(ftest1, ftest2, ftest3) REFERENCES PKTABLE (ptest1, ptest2, ptest3));
+
+INSERT INTO PKTABLE VALUES (1, 2, 3, 'test1');
+INSERT INTO PKTABLE VALUES (1, 3, NULL, 'test2');
+INSERT INTO PKTABLE VALUES (2, NULL, 4, 'test3');
+
+INSERT INTO FKTABLE VALUES (1, 2, 3, 1);
+
+DELETE FROM PKTABLE WHERE ptest1 = 2;
+
+SELECT * FROM PKTABLE;
+SELECT * FROM FKTABLE;
+
+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;
+
+-- 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;
+SELECT * from FKTABLE;
+
+-- 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;
+SELECT * from FKTABLE;
+
+-- Try to delete something that should not have a cascade
+DELETE FROM PKTABLE where ptest1=2;
+
+-- Show PKTABLE and FKTABLE
+SELECT * from PKTABLE;
+SELECT * from FKTABLE;
+
+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;
+
+-- 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;
+SELECT * from FKTABLE;
+
+-- 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;
+SELECT * from FKTABLE;
+
+-- Try to delete something that should not set default
+DELETE FROM PKTABLE where ptest2=5;
+
+-- Show PKTABLE and FKTABLE
+SELECT * from PKTABLE;
+SELECT * from FKTABLE;
+
+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;
+
+-- 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;
+SELECT * from FKTABLE;
+
+-- 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;
+SELECT * from FKTABLE;
+
+-- 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;
+SELECT * from FKTABLE;
+
+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));
+
+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));
+
+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;
+
+-- Two columns, two tables
+CREATE TABLE PKTABLE (ptest1 int, ptest2 inet, 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 inet, FOREIGN KEY(ftest2, ftest1) REFERENCES pktable);
+-- As does this...
+CREATE TABLE FKTABLE (ftest1 int, ftest2 inet, FOREIGN KEY(ftest2, ftest1) REFERENCES pktable(ptest1, ptest2));
+-- And again..
+CREATE TABLE FKTABLE (ftest1 int, ftest2 inet, FOREIGN KEY(ftest1, ftest2) REFERENCES pktable(ptest2, ptest1));
+-- This works...
+CREATE TABLE FKTABLE (ftest1 int, ftest2 inet, FOREIGN KEY(ftest2, ftest1) REFERENCES pktable(ptest2, ptest1));
+DROP TABLE FKTABLE;
+-- As does this
+CREATE TABLE FKTABLE (ftest1 int, ftest2 inet, 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 inet, ptest3 int, ptest4 inet, PRIMARY KEY(ptest1, ptest2), FOREIGN KEY(ptest3,
+ptest4) REFERENCES pktable(ptest1, ptest2));
+DROP TABLE PKTABLE;
+-- And this,
+CREATE TABLE PKTABLE (ptest1 int, ptest2 inet, ptest3 int, ptest4 inet, 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 inet, ptest3 int, ptest4 inet, 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 inet, ptest3 int, ptest4 inet, 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 inet, ptest3 int, ptest4 inet, 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 inet, primary key(base1, ptest1)) inherits (pktable_base);
+-- 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 inet, ptest2 inet[], primary key(base1, ptest1), foreign key(base2, ptest2) references
+ pktable(base1, ptest1)) inherits (pktable_base);
+create table pktable(ptest1 inet, ptest2 inet, primary key(base1, ptest1), foreign key(base2, ptest2) references
+ pktable(ptest1, base1)) inherits (pktable_base);
+create table pktable(ptest1 inet, ptest2 inet, primary key(base1, ptest1), foreign key(ptest2, base2) references
+ pktable(base1, ptest1)) inherits (pktable_base);
+create table pktable(ptest1 inet, ptest2 inet, primary key(base1, ptest1), foreign key(ptest2, base2) references
+ pktable(base1, ptest1)) inherits (pktable_base);
+drop table pktable;
+drop table pktable_base;
+
+--
+-- Deferrable constraints
+--
+
+-- 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;
+
+DROP TABLE pktable, fktable;
+
+-- 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 on delete 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
+delete from pp where f1 = 13; -- fail
+drop table pp, cc;
+
+create temp table pp (f1 int primary key);
+create temp table cc (f1 int references pp on update restrict on delete 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
+delete from pp where f1 = 13; -- 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;
+
+--
+-- Test keys that "look" different but compare as equal
+--
+create table pktable2 (a float8, b float8, primary key (a, b));
+create table fktable2 (x float8, y float8, foreign key (x, y) references pktable2 (a, b) on update cascade);
+
+insert into pktable2 values ('-0', '-0');
+insert into fktable2 values ('-0', '-0');
+
+select * from pktable2;
+select * from fktable2;
+
+update pktable2 set a = '0' where a = '-0';
+
+select * from pktable2;
+-- should have updated fktable2.x
+select * from fktable2;
+
+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);
+
+-- MATCH FULL
+INSERT INTO fk_notpartitioned_pk VALUES (1, 2);
+CREATE TABLE fk_partitioned_fk_full (x int, y int) PARTITION BY RANGE (x);
+CREATE TABLE fk_partitioned_fk_full_1 PARTITION OF fk_partitioned_fk_full DEFAULT;
+INSERT INTO fk_partitioned_fk_full VALUES (1, NULL);
+ALTER TABLE fk_partitioned_fk_full ADD FOREIGN KEY (x, y) REFERENCES fk_notpartitioned_pk MATCH FULL; -- fails
+TRUNCATE fk_partitioned_fk_full;
+ALTER TABLE fk_partitioned_fk_full ADD FOREIGN KEY (x, y) REFERENCES fk_notpartitioned_pk MATCH FULL;
+INSERT INTO fk_partitioned_fk_full VALUES (1, NULL); -- fails
+DROP TABLE fk_partitioned_fk_full;
+
+-- 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;
+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 the case when the referenced table is owned by a different user
+create role regress_other_partitioned_fk_owner;
+grant references on fk_notpartitioned_pk to regress_other_partitioned_fk_owner;
+set role regress_other_partitioned_fk_owner;
+create table other_partitioned_fk(a int, b int) partition by list (a);
+create table other_partitioned_fk_1 partition of other_partitioned_fk
+ for values in (2048);
+insert into other_partitioned_fk
+ select 2048, x from generate_series(1,10) x;
+-- this should fail
+alter table other_partitioned_fk add foreign key (a, b)
+ references fk_notpartitioned_pk(a, b);
+-- add the missing keys and retry
+reset role;
+insert into fk_notpartitioned_pk (a, b)
+ select 2048, x from generate_series(1,10) x;
+set role regress_other_partitioned_fk_owner;
+alter table other_partitioned_fk add foreign key (a, b)
+ references fk_notpartitioned_pk(a, b);
+-- clean up
+drop table other_partitioned_fk;
+reset role;
+revoke all on fk_notpartitioned_pk from regress_other_partitioned_fk_owner;
+drop role regress_other_partitioned_fk_owner;
+
+-- 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);
+
+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
+
+-- verify constraint deferrability
+create schema fkpart3
+ create table pkey (a int primary key)
+ create table fk_part (a int, constraint fkey foreign key (a) references fkpart3.pkey deferrable initially immediate) partition by list (a)
+ create table fk_part_1 partition of fkpart3.fk_part for values in (1) partition by list (a)
+ create table fk_part_1_1 partition of fkpart3.fk_part_1 for values in (1)
+ create table fk_part_2 partition of fkpart3.fk_part for values in (2);
+begin;
+set constraints fkpart3.fkey deferred;
+insert into fkpart3.fk_part values (1);
+insert into fkpart3.pkey values (1);
+commit;
+begin;
+set constraints fkpart3.fkey deferred;
+delete from fkpart3.pkey;
+delete from fkpart3.fk_part;
+commit;
+
+drop schema fkpart0, fkpart1, fkpart2, fkpart3 cascade;
+
+-- 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;
+
+-- test for reported bug: relispartition not set
+-- https://postgr.es/m/CA+HiwqHMsRtRYRWYTWavKJ8x14AFsv7bmAV46mYwnfD3vy8goQ@mail.gmail.com
+CREATE SCHEMA fkpart7
+ CREATE TABLE pkpart (a int) PARTITION BY LIST (a)
+ CREATE TABLE pkpart1 PARTITION OF pkpart FOR VALUES IN (1);
+ALTER TABLE fkpart7.pkpart1 ADD PRIMARY KEY (a);
+ALTER TABLE fkpart7.pkpart ADD PRIMARY KEY (a);
+CREATE TABLE fkpart7.fk (a int REFERENCES fkpart7.pkpart);
+DROP SCHEMA fkpart7 CASCADE;
+
+-- ensure we check partitions are "not used" when dropping constraints
+CREATE SCHEMA fkpart8
+ CREATE TABLE tbl1(f1 int PRIMARY KEY)
+ CREATE TABLE tbl2(f1 int REFERENCES tbl1 DEFERRABLE INITIALLY DEFERRED) PARTITION BY RANGE(f1)
+ CREATE TABLE tbl2_p1 PARTITION OF tbl2 FOR VALUES FROM (minvalue) TO (maxvalue);
+INSERT INTO fkpart8.tbl1 VALUES(1);
+BEGIN;
+INSERT INTO fkpart8.tbl2 VALUES(1);
+ALTER TABLE fkpart8.tbl2 DROP CONSTRAINT tbl2_f1_fkey;
+COMMIT;
+DROP SCHEMA fkpart8 CASCADE;
+
+-- ensure FK referencing a multi-level partitioned table are
+-- enforce reference to sub-children.
+CREATE SCHEMA fkpart9
+ CREATE TABLE pk (a INT PRIMARY KEY) PARTITION BY RANGE (a)
+ CREATE TABLE fk (
+ fk_a INT REFERENCES pk(a) ON DELETE CASCADE
+ )
+ CREATE TABLE pk1 PARTITION OF pk FOR VALUES FROM (30) TO (50) PARTITION BY RANGE (a)
+ CREATE TABLE pk11 PARTITION OF pk1 FOR VALUES FROM (30) TO (40);
+INSERT INTO fkpart9.pk VALUES (35);
+INSERT INTO fkpart9.fk VALUES (35);
+DELETE FROM fkpart9.pk WHERE a=35;
+SELECT * FROM fkpart9.pk;
+SELECT * FROM fkpart9.fk;
+DROP SCHEMA fkpart9 CASCADE;
+
+-- test that ri_Check_Pk_Match() scans the correct partition for a deferred
+-- ON DELETE/UPDATE NO ACTION constraint
+CREATE SCHEMA fkpart10
+ CREATE TABLE tbl1(f1 int PRIMARY KEY) PARTITION BY RANGE(f1)
+ CREATE TABLE tbl1_p1 PARTITION OF tbl1 FOR VALUES FROM (minvalue) TO (1)
+ CREATE TABLE tbl1_p2 PARTITION OF tbl1 FOR VALUES FROM (1) TO (maxvalue)
+ CREATE TABLE tbl2(f1 int REFERENCES tbl1 DEFERRABLE INITIALLY DEFERRED);
+INSERT INTO fkpart10.tbl1 VALUES (0), (1);
+INSERT INTO fkpart10.tbl2 VALUES (0), (1);
+BEGIN;
+DELETE FROM fkpart10.tbl1 WHERE f1 = 0;
+UPDATE fkpart10.tbl1 SET f1 = 2 WHERE f1 = 1;
+INSERT INTO fkpart10.tbl1 VALUES (0), (1);
+COMMIT;
+DROP SCHEMA fkpart10 CASCADE;
diff --git a/src/test/regress/sql/functional_deps.sql b/src/test/regress/sql/functional_deps.sql
new file mode 100644
index 0000000..406490b
--- /dev/null
+++ b/src/test/regress/sql/functional_deps.sql
@@ -0,0 +1,210 @@
+-- from http://www.depesz.com/index.php/2010/04/19/getting-unique-elements/
+
+CREATE TEMP TABLE articles (
+ id int CONSTRAINT articles_pkey PRIMARY KEY,
+ keywords text,
+ title text UNIQUE NOT NULL,
+ body text UNIQUE,
+ created date
+);
+
+CREATE TEMP TABLE articles_in_category (
+ article_id int,
+ category_id int,
+ changed date,
+ PRIMARY KEY (article_id, category_id)
+);
+
+-- test functional dependencies based on primary keys/unique constraints
+
+-- base tables
+
+-- group by primary key (OK)
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY id;
+
+-- group by unique not null (fail/todo)
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY title;
+
+-- group by unique nullable (fail)
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY body;
+
+-- group by something else (fail)
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY keywords;
+
+-- multiple tables
+
+-- group by primary key (OK)
+SELECT a.id, a.keywords, a.title, a.body, a.created
+FROM articles AS a, articles_in_category AS aic
+WHERE a.id = aic.article_id AND aic.category_id in (14,62,70,53,138)
+GROUP BY a.id;
+
+-- group by something else (fail)
+SELECT a.id, a.keywords, a.title, a.body, a.created
+FROM articles AS a, articles_in_category AS aic
+WHERE a.id = aic.article_id AND aic.category_id in (14,62,70,53,138)
+GROUP BY aic.article_id, aic.category_id;
+
+-- JOIN syntax
+
+-- group by left table's primary key (OK)
+SELECT a.id, a.keywords, a.title, a.body, a.created
+FROM articles AS a JOIN articles_in_category AS aic ON a.id = aic.article_id
+WHERE aic.category_id in (14,62,70,53,138)
+GROUP BY a.id;
+
+-- group by something else (fail)
+SELECT a.id, a.keywords, a.title, a.body, a.created
+FROM articles AS a JOIN articles_in_category AS aic ON a.id = aic.article_id
+WHERE aic.category_id in (14,62,70,53,138)
+GROUP BY aic.article_id, aic.category_id;
+
+-- group by right table's (composite) primary key (OK)
+SELECT aic.changed
+FROM articles AS a JOIN articles_in_category AS aic ON a.id = aic.article_id
+WHERE aic.category_id in (14,62,70,53,138)
+GROUP BY aic.category_id, aic.article_id;
+
+-- group by right table's partial primary key (fail)
+SELECT aic.changed
+FROM articles AS a JOIN articles_in_category AS aic ON a.id = aic.article_id
+WHERE aic.category_id in (14,62,70,53,138)
+GROUP BY aic.article_id;
+
+
+-- example from documentation
+
+CREATE TEMP TABLE products (product_id int, name text, price numeric);
+CREATE TEMP TABLE sales (product_id int, units int);
+
+-- OK
+SELECT product_id, p.name, (sum(s.units) * p.price) AS sales
+ FROM products p LEFT JOIN sales s USING (product_id)
+ GROUP BY product_id, p.name, p.price;
+
+-- fail
+SELECT product_id, p.name, (sum(s.units) * p.price) AS sales
+ FROM products p LEFT JOIN sales s USING (product_id)
+ GROUP BY product_id;
+
+ALTER TABLE products ADD PRIMARY KEY (product_id);
+
+-- OK now
+SELECT product_id, p.name, (sum(s.units) * p.price) AS sales
+ FROM products p LEFT JOIN sales s USING (product_id)
+ GROUP BY product_id;
+
+
+-- Drupal example, http://drupal.org/node/555530
+
+CREATE TEMP TABLE node (
+ nid SERIAL,
+ vid integer NOT NULL default '0',
+ type varchar(32) NOT NULL default '',
+ title varchar(128) NOT NULL default '',
+ uid integer NOT NULL default '0',
+ status integer NOT NULL default '1',
+ created integer NOT NULL default '0',
+ -- snip
+ PRIMARY KEY (nid, vid)
+);
+
+CREATE TEMP TABLE users (
+ uid integer NOT NULL default '0',
+ name varchar(60) NOT NULL default '',
+ pass varchar(32) NOT NULL default '',
+ -- snip
+ PRIMARY KEY (uid),
+ UNIQUE (name)
+);
+
+-- OK
+SELECT u.uid, u.name FROM node n
+INNER JOIN users u ON u.uid = n.uid
+WHERE n.type = 'blog' AND n.status = 1
+GROUP BY u.uid, u.name;
+
+-- OK
+SELECT u.uid, u.name FROM node n
+INNER JOIN users u ON u.uid = n.uid
+WHERE n.type = 'blog' AND n.status = 1
+GROUP BY u.uid;
+
+
+-- Check views and dependencies
+
+-- fail
+CREATE TEMP VIEW fdv1 AS
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY body;
+
+-- OK
+CREATE TEMP VIEW fdv1 AS
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY id;
+
+-- fail
+ALTER TABLE articles DROP CONSTRAINT articles_pkey RESTRICT;
+
+DROP VIEW fdv1;
+
+
+-- multiple dependencies
+CREATE TEMP VIEW fdv2 AS
+SELECT a.id, a.keywords, a.title, aic.category_id, aic.changed
+FROM articles AS a JOIN articles_in_category AS aic ON a.id = aic.article_id
+WHERE aic.category_id in (14,62,70,53,138)
+GROUP BY a.id, aic.category_id, aic.article_id;
+
+ALTER TABLE articles DROP CONSTRAINT articles_pkey RESTRICT; -- fail
+ALTER TABLE articles_in_category DROP CONSTRAINT articles_in_category_pkey RESTRICT; --fail
+
+DROP VIEW fdv2;
+
+
+-- nested queries
+
+CREATE TEMP VIEW fdv3 AS
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY id
+UNION
+SELECT id, keywords, title, body, created
+FROM articles
+GROUP BY id;
+
+ALTER TABLE articles DROP CONSTRAINT articles_pkey RESTRICT; -- fail
+
+DROP VIEW fdv3;
+
+
+CREATE TEMP VIEW fdv4 AS
+SELECT * FROM articles WHERE title IN (SELECT title FROM articles GROUP BY id);
+
+ALTER TABLE articles DROP CONSTRAINT articles_pkey RESTRICT; -- fail
+
+DROP VIEW fdv4;
+
+
+-- prepared query plans: this results in failure on reuse
+
+PREPARE foo AS
+ SELECT id, keywords, title, body, created
+ FROM articles
+ GROUP BY id;
+
+EXECUTE foo;
+
+ALTER TABLE articles DROP CONSTRAINT articles_pkey RESTRICT;
+
+EXECUTE foo; -- fail
diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql
new file mode 100644
index 0000000..b7eb072
--- /dev/null
+++ b/src/test/regress/sql/generated.sql
@@ -0,0 +1,581 @@
+-- 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);
+
+-- 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;
+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;
+
+-- 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 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;
+
+\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 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;
+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;
+\d gtest27
+-- Ideally you could just do this, but not today (and should x change type?):
+ALTER TABLE gtest27
+ ALTER COLUMN a TYPE float8,
+ ALTER COLUMN b TYPE float8; -- error
+\d gtest27
+SELECT * FROM gtest27;
+
+-- 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/geometry.sql b/src/test/regress/sql/geometry.sql
new file mode 100644
index 0000000..bbb6acd
--- /dev/null
+++ b/src/test/regress/sql/geometry.sql
@@ -0,0 +1,512 @@
+--
+-- 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
+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;
+
+-- Distance to box
+SELECT l.s, b.f1, l.s <-> b.f1 FROM LINE_TBL l, BOX_TBL b;
+SELECT l.s, b.f1, b.f1 <-> l.s FROM LINE_TBL l, BOX_TBL b;
+
+-- 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;
+
+-- Closest point to box
+SELECT l.s, b.f1, l.s ## b.f1 FROM LINE_TBL l, BOX_TBL b;
+
+--
+-- 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
+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
+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
+SELECT l.s, l1.s, l.s ## l1.s FROM LSEG_TBL l, LINE_TBL l1;
+
+-- 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
+SELECT b.f1 + p.f1 AS translation
+ FROM BOX_TBL b, POINT_TBL p;
+
+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;
+
+-- Center
+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
+SELECT p.f1, p1.f1, p.f1 + p1.f1 FROM PATH_TBL p, POINT_TBL p1;
+
+-- Subtract point
+SELECT p.f1, p1.f1, p.f1 - p1.f1 FROM PATH_TBL p, POINT_TBL p1;
+
+-- Multiply with point
+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
+SELECT p.f1, poly.f1, poly.f1 @> p.f1 AS contains
+ FROM POLYGON_TBL poly, POINT_TBL p;
+
+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;
+
+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
+SELECT c.f1, p.f1, c.f1 + p.f1 FROM CIRCLE_TBL c, POINT_TBL p;
+
+-- Subtract point
+SELECT c.f1, p.f1, c.f1 - p.f1 FROM CIRCLE_TBL c, POINT_TBL p;
+
+-- Multiply with point
+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;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
new file mode 100644
index 0000000..5194afc
--- /dev/null
+++ b/src/test/regress/sql/gin.sql
@@ -0,0 +1,173 @@
+--
+-- Test GIN indexes.
+--
+-- There are other tests to test different GIN opclasses. 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);
+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;
+
+-- Test for "rare && frequent" searches
+explain (costs off)
+select count(*) from gin_test_tbl where i @> array[1, 999];
+
+select count(*) from gin_test_tbl where i @> array[1, 999];
+
+-- Very weak test for gin_fuzzy_search_limit
+set gin_fuzzy_search_limit = 1000;
+
+explain (costs off)
+select count(*) > 0 as ok from gin_test_tbl where i @> array[1];
+
+select count(*) > 0 as ok from gin_test_tbl where i @> array[1];
+
+reset gin_fuzzy_search_limit;
+
+-- Test optimization of empty queries
+create temp table t_gin_test_tbl(i int4[], j int4[]);
+create index on t_gin_test_tbl using gin (i, j);
+insert into t_gin_test_tbl
+values
+ (null, null),
+ ('{}', null),
+ ('{1}', null),
+ ('{1,2}', null),
+ (null, '{}'),
+ (null, '{10}'),
+ ('{1,2}', '{10}'),
+ ('{2}', '{10}'),
+ ('{1,3}', '{}'),
+ ('{1,1}', '{10}');
+
+set enable_seqscan = off;
+explain (costs off)
+select * from t_gin_test_tbl where array[0] <@ i;
+select * from t_gin_test_tbl where array[0] <@ i;
+select * from t_gin_test_tbl where array[0] <@ i and '{}'::int4[] <@ j;
+
+explain (costs off)
+select * from t_gin_test_tbl where i @> '{}';
+select * from t_gin_test_tbl where i @> '{}';
+
+create function explain_query_json(query_sql text)
+returns table (explain_line json)
+language plpgsql as
+$$
+begin
+ set enable_seqscan = off;
+ set enable_bitmapscan = on;
+ return query execute 'EXPLAIN (ANALYZE, FORMAT json) ' || query_sql;
+end;
+$$;
+
+create function execute_text_query_index(query_sql text)
+returns setof text
+language plpgsql
+as
+$$
+begin
+ set enable_seqscan = off;
+ set enable_bitmapscan = on;
+ return query execute query_sql;
+end;
+$$;
+
+create function execute_text_query_heap(query_sql text)
+returns setof text
+language plpgsql
+as
+$$
+begin
+ set enable_seqscan = on;
+ set enable_bitmapscan = off;
+ return query execute query_sql;
+end;
+$$;
+
+-- check number of rows returned by index and removed by recheck
+select
+ query,
+ js->0->'Plan'->'Plans'->0->'Actual Rows' as "return by index",
+ js->0->'Plan'->'Rows Removed by Index Recheck' as "removed by recheck",
+ (res_index = res_heap) as "match"
+from
+ (values
+ ($$ i @> '{}' $$),
+ ($$ j @> '{}' $$),
+ ($$ i @> '{}' and j @> '{}' $$),
+ ($$ i @> '{1}' $$),
+ ($$ i @> '{1}' and j @> '{}' $$),
+ ($$ i @> '{1}' and i @> '{}' and j @> '{}' $$),
+ ($$ j @> '{10}' $$),
+ ($$ j @> '{10}' and i @> '{}' $$),
+ ($$ j @> '{10}' and j @> '{}' and i @> '{}' $$),
+ ($$ i @> '{1}' and j @> '{10}' $$)
+ ) q(query),
+ lateral explain_query_json($$select * from t_gin_test_tbl where $$ || query) js,
+ lateral execute_text_query_index($$select string_agg((i, j)::text, ' ') from t_gin_test_tbl where $$ || query) res_index,
+ lateral execute_text_query_heap($$select string_agg((i, j)::text, ' ') from t_gin_test_tbl where $$ || query) res_heap;
+
+reset enable_seqscan;
+reset enable_bitmapscan;
+
+-- re-purpose t_gin_test_tbl to test scans involving posting trees
+insert into t_gin_test_tbl select array[1, g, g/10], array[2, g, g/10]
+ from generate_series(1, 20000) g;
+
+select gin_clean_pending_list('t_gin_test_tbl_i_j_idx') is not null;
+
+analyze t_gin_test_tbl;
+
+set enable_seqscan = off;
+set enable_bitmapscan = on;
+
+explain (costs off)
+select count(*) from t_gin_test_tbl where j @> array[50];
+select count(*) from t_gin_test_tbl where j @> array[50];
+explain (costs off)
+select count(*) from t_gin_test_tbl where j @> array[2];
+select count(*) from t_gin_test_tbl where j @> array[2];
+explain (costs off)
+select count(*) from t_gin_test_tbl where j @> '{}'::int[];
+select count(*) from t_gin_test_tbl where j @> '{}'::int[];
+
+-- test vacuuming of posting trees
+delete from t_gin_test_tbl where j @> array[2];
+vacuum t_gin_test_tbl;
+
+select count(*) from t_gin_test_tbl where j @> array[50];
+select count(*) from t_gin_test_tbl where j @> array[2];
+select count(*) from t_gin_test_tbl where j @> '{}'::int[];
+
+reset enable_seqscan;
+reset enable_bitmapscan;
+
+drop table t_gin_test_tbl;
diff --git a/src/test/regress/sql/gist.sql b/src/test/regress/sql/gist.sql
new file mode 100644
index 0000000..d94a0bb
--- /dev/null
+++ b/src/test/regress/sql/gist.sql
@@ -0,0 +1,177 @@
+--
+-- Test GiST indexes.
+--
+-- There are other tests to test different GiST opclasses. This is for
+-- testing GiST code itself. Vacuuming in particular.
+
+create table gist_point_tbl(id int4, p point);
+create index gist_pointidx on gist_point_tbl using gist(p);
+
+-- Verify the fillfactor and buffering options
+create index gist_pointidx2 on gist_point_tbl using gist(p) with (buffering = on, fillfactor=50);
+create index gist_pointidx3 on gist_point_tbl using gist(p) with (buffering = off);
+create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = auto);
+drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
+
+-- Make sure bad values are refused
+create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
+create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
+create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=101);
+
+-- Insert enough data to create a tree that's a couple of levels deep.
+insert into gist_point_tbl (id, p)
+select g, point(g*10, g*10) from generate_series(1, 10000) g;
+
+insert into gist_point_tbl (id, p)
+select g+100000, point(g*10+1, g*10+1) from generate_series(1, 10000) g;
+
+-- To test vacuum, delete some entries from all over the index.
+delete from gist_point_tbl where id % 2 = 1;
+
+-- And also delete some concentration of values.
+delete from gist_point_tbl where id > 5000;
+
+vacuum analyze gist_point_tbl;
+
+-- rebuild the index with a different fillfactor
+alter index gist_pointidx SET (fillfactor = 40);
+reindex index gist_pointidx;
+
+--
+-- Test Index-only plans on GiST indexes
+--
+
+create table gist_tbl (b box, p point, c circle);
+
+insert into gist_tbl
+select box(point(0.05*i, 0.05*i), point(0.05*i, 0.05*i)),
+ point(0.05*i, 0.05*i),
+ circle(point(0.05*i, 0.05*i), 1.0)
+from generate_series(0,10000) as i;
+
+vacuum analyze gist_tbl;
+
+set enable_seqscan=off;
+set enable_bitmapscan=off;
+set enable_indexonlyscan=on;
+
+-- Test index-only scan with point opclass
+create index gist_tbl_point_index on gist_tbl using gist (p);
+
+-- check that the planner chooses an index-only scan
+explain (costs off)
+select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5));
+
+-- execute the same
+select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5));
+
+-- Also test an index-only knn-search
+explain (costs off)
+select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5))
+order by p <-> point(0.201, 0.201);
+
+select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5))
+order by p <-> point(0.201, 0.201);
+
+-- Check commuted case as well
+explain (costs off)
+select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5))
+order by point(0.101, 0.101) <-> p;
+
+select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5))
+order by point(0.101, 0.101) <-> p;
+
+-- Check case with multiple rescans (bug #14641)
+explain (costs off)
+select p from
+ (values (box(point(0,0), point(0.5,0.5))),
+ (box(point(0.5,0.5), point(0.75,0.75))),
+ (box(point(0.8,0.8), point(1.0,1.0)))) as v(bb)
+cross join lateral
+ (select p from gist_tbl where p <@ bb order by p <-> bb[0] limit 2) ss;
+
+select p from
+ (values (box(point(0,0), point(0.5,0.5))),
+ (box(point(0.5,0.5), point(0.75,0.75))),
+ (box(point(0.8,0.8), point(1.0,1.0)))) as v(bb)
+cross join lateral
+ (select p from gist_tbl where p <@ bb order by p <-> bb[0] limit 2) ss;
+
+drop index gist_tbl_point_index;
+
+-- Test index-only scan with box opclass
+create index gist_tbl_box_index on gist_tbl using gist (b);
+
+-- check that the planner chooses an index-only scan
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6));
+
+-- execute the same
+select b from gist_tbl where b <@ box(point(5,5), point(6,6));
+
+-- Also test an index-only knn-search
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+
+-- Check commuted case as well
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+
+drop index gist_tbl_box_index;
+
+-- Test that an index-only scan is not chosen, when the query involves the
+-- circle column (the circle opclass does not support index-only scans).
+create index gist_tbl_multi_index on gist_tbl using gist (p, c);
+
+explain (costs off)
+select p, c from gist_tbl
+where p <@ box(point(5,5), point(6, 6));
+
+-- execute the same
+select b, p from gist_tbl
+where b <@ box(point(4.5, 4.5), point(5.5, 5.5))
+and p <@ box(point(5,5), point(6, 6));
+
+drop index gist_tbl_multi_index;
+
+-- Test that we don't try to return the value of a non-returnable
+-- column in an index-only scan. (This isn't GIST-specific, but
+-- it only applies to index AMs that can return some columns and not
+-- others, so GIST with appropriate opclasses is a convenient test case.)
+create index gist_tbl_multi_index on gist_tbl using gist (circle(p,1), p);
+explain (verbose, costs off)
+select circle(p,1) from gist_tbl
+where p <@ box(point(5, 5), point(5.3, 5.3));
+select circle(p,1) from gist_tbl
+where p <@ box(point(5, 5), point(5.3, 5.3));
+
+-- Similarly, test that index rechecks involving a non-returnable column
+-- are done correctly.
+explain (verbose, costs off)
+select p from gist_tbl where circle(p,1) @> circle(point(0,0),0.95);
+select p from gist_tbl where circle(p,1) @> circle(point(0,0),0.95);
+
+-- Also check that use_physical_tlist doesn't trigger in such cases.
+explain (verbose, costs off)
+select count(*) from gist_tbl;
+select count(*) from gist_tbl;
+
+-- This case isn't supported, but it should at least EXPLAIN correctly.
+explain (verbose, costs off)
+select p from gist_tbl order by circle(p,1) <-> point(0,0) limit 1;
+select p from gist_tbl order by circle(p,1) <-> point(0,0) limit 1;
+
+-- Clean up
+reset enable_seqscan;
+reset enable_bitmapscan;
+reset enable_indexonlyscan;
+
+drop table gist_tbl;
diff --git a/src/test/regress/sql/groupingsets.sql b/src/test/regress/sql/groupingsets.sql
new file mode 100644
index 0000000..71d2ae9
--- /dev/null
+++ b/src/test/regress/sql/groupingsets.sql
@@ -0,0 +1,590 @@
+--
+-- 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 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');
+update pg_class set reltuples = 10 where relname='bug_16784';
+
+insert into bug_16784 select g/10, g from generate_series(1,40) g;
+
+set work_mem='64kB';
+set enable_sort = false;
+
+select * from
+ (values (1),(2)) v(a),
+ lateral (select a, i, j, count(*) from
+ bug_16784 group by cube(i,j)) s
+ order by v.a, i, j;
+
+--
+-- Compare results between plans using sorting and plans using hash
+-- aggregation. Force spilling in both cases by setting work_mem low
+-- and altering the statistics.
+--
+
+create table gs_data_1 as
+select g%1000 as g1000, g%100 as g100, g%10 as g10, g
+ from generate_series(0,1999) g;
+
+analyze gs_data_1;
+alter table gs_data_1 set (autovacuum_enabled = 'false');
+update pg_class set reltuples = 10 where relname='gs_data_1';
+
+set work_mem='64kB';
+
+-- Produce results with sorting.
+
+set enable_sort = true;
+set enable_hashagg = false;
+set jit_above_cost = 0;
+
+explain (costs off)
+select g100, g10, sum(g::numeric), count(*), max(g::text)
+from gs_data_1 group by cube (g1000, g100,g10);
+
+create table gs_group_1 as
+select g100, g10, sum(g::numeric), count(*), max(g::text)
+from gs_data_1 group by cube (g1000, g100,g10);
+
+-- Produce results with hash aggregation.
+
+set enable_hashagg = true;
+set enable_sort = false;
+
+explain (costs off)
+select g100, g10, sum(g::numeric), count(*), max(g::text)
+from gs_data_1 group by cube (g1000, g100,g10);
+
+create table gs_hash_1 as
+select g100, g10, sum(g::numeric), count(*), max(g::text)
+from gs_data_1 group by cube (g1000, g100,g10);
+
+set enable_sort = true;
+set work_mem to default;
+
+-- Compare results
+
+(select * from gs_hash_1 except select * from gs_group_1)
+ union all
+(select * from gs_group_1 except select * from gs_hash_1);
+
+drop table gs_group_1;
+drop table gs_hash_1;
+
+-- GROUP BY DISTINCT
+
+-- "normal" behavior...
+select a, b, c
+from (values (1, 2, 3), (4, null, 6), (7, 8, 9)) as t (a, b, c)
+group by all rollup(a, b), rollup(a, c)
+order by a, b, c;
+
+-- ...which is also the default
+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;
+
+-- "group by distinct" behavior...
+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;
+
+-- ...which is not the same as "select distinct"
+select distinct 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;
+
+-- 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/guc.sql b/src/test/regress/sql/guc.sql
new file mode 100644
index 0000000..c39c113
--- /dev/null
+++ b/src/test/regress/sql/guc.sql
@@ -0,0 +1,313 @@
+-- pg_regress should ensure that this default value applies; however
+-- we can't rely on any specific default value of vacuum_cost_delay
+SHOW datestyle;
+
+-- SET to some nondefault value
+SET vacuum_cost_delay TO 40;
+SET datestyle = 'ISO, YMD';
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+
+-- SET LOCAL has no effect outside of a transaction
+SET LOCAL vacuum_cost_delay TO 50;
+SHOW vacuum_cost_delay;
+SET LOCAL datestyle = 'SQL';
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+
+-- SET LOCAL within a transaction that commits
+BEGIN;
+SET LOCAL vacuum_cost_delay TO 50;
+SHOW vacuum_cost_delay;
+SET LOCAL datestyle = 'SQL';
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+COMMIT;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+
+-- SET should be reverted after ROLLBACK
+BEGIN;
+SET vacuum_cost_delay TO 60;
+SHOW vacuum_cost_delay;
+SET datestyle = 'German';
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ROLLBACK;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+
+-- Some tests with subtransactions
+BEGIN;
+SET vacuum_cost_delay TO 70;
+SET datestyle = 'MDY';
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+SAVEPOINT first_sp;
+SET vacuum_cost_delay TO 80.1;
+SHOW vacuum_cost_delay;
+SET datestyle = 'German, DMY';
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ROLLBACK TO first_sp;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+SAVEPOINT second_sp;
+SET vacuum_cost_delay TO '900us';
+SET datestyle = 'SQL, YMD';
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+SAVEPOINT third_sp;
+SET vacuum_cost_delay TO 100;
+SHOW vacuum_cost_delay;
+SET datestyle = 'Postgres, MDY';
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ROLLBACK TO third_sp;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ROLLBACK TO second_sp;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ROLLBACK;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+
+-- SET LOCAL with Savepoints
+BEGIN;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+SAVEPOINT sp;
+SET LOCAL vacuum_cost_delay TO 30;
+SHOW vacuum_cost_delay;
+SET LOCAL datestyle = 'Postgres, MDY';
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ROLLBACK TO sp;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ROLLBACK;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+
+-- SET LOCAL persists through RELEASE (which was not true in 8.0-8.2)
+BEGIN;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+SAVEPOINT sp;
+SET LOCAL vacuum_cost_delay TO 30;
+SHOW vacuum_cost_delay;
+SET LOCAL datestyle = 'Postgres, MDY';
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+RELEASE SAVEPOINT sp;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ROLLBACK;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+
+-- SET followed by SET LOCAL
+BEGIN;
+SET vacuum_cost_delay TO 40;
+SET LOCAL vacuum_cost_delay TO 50;
+SHOW vacuum_cost_delay;
+SET datestyle = 'ISO, DMY';
+SET LOCAL datestyle = 'Postgres, MDY';
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+COMMIT;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+
+--
+-- Test RESET. We use datestyle because the reset value is forced by
+-- pg_regress, so it doesn't depend on the installation's configuration.
+--
+SET datestyle = iso, ymd;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+RESET datestyle;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+
+-- Test some simple error cases
+SET seq_page_cost TO 'NaN';
+SET vacuum_cost_delay TO '10s';
+SET no_such_variable TO 42;
+
+-- Test "custom" GUCs created on the fly (which aren't really an
+-- intended feature, but many people use them).
+SHOW custom.my_guc; -- error, not known yet
+SET custom.my_guc = 42;
+SHOW custom.my_guc;
+RESET custom.my_guc; -- this makes it go to empty, not become unknown again
+SHOW custom.my_guc;
+SET custom.my.qualified.guc = 'foo';
+SHOW custom.my.qualified.guc;
+SET custom."bad-guc" = 42; -- disallowed because -c cannot set this name
+SHOW custom."bad-guc";
+SET special."weird name" = 'foo'; -- could be allowed, but we choose not to
+SHOW special."weird name";
+
+--
+-- Test DISCARD TEMP
+--
+CREATE TEMP TABLE reset_test ( data text ) ON COMMIT DELETE ROWS;
+SELECT relname FROM pg_class WHERE relname = 'reset_test';
+DISCARD TEMP;
+SELECT relname FROM pg_class WHERE relname = 'reset_test';
+
+--
+-- Test DISCARD ALL
+--
+
+-- do changes
+DECLARE foo CURSOR WITH HOLD FOR SELECT 1;
+PREPARE foo AS SELECT 1;
+LISTEN foo_event;
+SET vacuum_cost_delay = 13;
+CREATE TEMP TABLE tmp_foo (data text) ON COMMIT DELETE ROWS;
+CREATE ROLE regress_guc_user;
+SET SESSION AUTHORIZATION regress_guc_user;
+-- look changes
+SELECT pg_listening_channels();
+SELECT name FROM pg_prepared_statements;
+SELECT name FROM pg_cursors;
+SHOW vacuum_cost_delay;
+SELECT relname from pg_class where relname = 'tmp_foo';
+SELECT current_user = 'regress_guc_user';
+-- discard everything
+DISCARD ALL;
+-- look again
+SELECT pg_listening_channels();
+SELECT name FROM pg_prepared_statements;
+SELECT name FROM pg_cursors;
+SHOW vacuum_cost_delay;
+SELECT relname from pg_class where relname = 'tmp_foo';
+SELECT current_user = 'regress_guc_user';
+DROP ROLE regress_guc_user;
+
+--
+-- search_path should react to changes in pg_namespace
+--
+
+set search_path = foo, public, not_there_initially;
+select current_schemas(false);
+create schema not_there_initially;
+select current_schemas(false);
+drop schema not_there_initially;
+select current_schemas(false);
+reset search_path;
+
+--
+-- Tests for function-local GUC settings
+--
+
+set work_mem = '3MB';
+
+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 function report_guc(text) set work_mem = '2MB';
+
+select report_guc('work_mem'), current_setting('work_mem');
+
+alter function report_guc(text) reset all;
+
+select report_guc('work_mem'), current_setting('work_mem');
+
+-- 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');
+
+alter function myfunc(int) reset all;
+
+select myfunc(0), current_setting('work_mem');
+
+set work_mem = '3MB';
+
+-- but SET isn't
+create or replace function myfunc(int) returns text as $$
+begin
+ set work_mem = '2MB';
+ return current_setting('work_mem');
+end $$
+language plpgsql
+set work_mem = '1MB';
+
+select myfunc(0), current_setting('work_mem');
+
+set work_mem = '3MB';
+
+-- it should roll back on error, though
+create or replace function myfunc(int) returns text as $$
+begin
+ set work_mem = '2MB';
+ perform 1/$1;
+ return current_setting('work_mem');
+end $$
+language plpgsql
+set work_mem = '1MB';
+
+select myfunc(0);
+select current_setting('work_mem');
+select myfunc(1), current_setting('work_mem');
+
+-- check current_setting()'s behavior with invalid setting name
+
+select current_setting('nosuch.setting'); -- FAIL
+select current_setting('nosuch.setting', false); -- FAIL
+select current_setting('nosuch.setting', true) is null;
+
+-- after this, all three cases should yield 'nada'
+set nosuch.setting = 'nada';
+
+select current_setting('nosuch.setting');
+select current_setting('nosuch.setting', false);
+select current_setting('nosuch.setting', true);
+
+-- Normally, CREATE FUNCTION should complain about invalid values in
+-- function SET options; but not if check_function_bodies is off,
+-- because that creates ordering hazards for pg_dump
+
+create function func_with_bad_set() returns int as $$ select 1 $$
+language sql
+set default_text_search_config = no_such_config;
+
+set check_function_bodies = off;
+
+create function func_with_bad_set() returns int as $$ select 1 $$
+language sql
+set default_text_search_config = no_such_config;
+
+select func_with_bad_set();
+
+reset check_function_bodies;
+
+set default_with_oids to f;
+-- Should not allow to set it to true.
+set default_with_oids to t;
diff --git a/src/test/regress/sql/hash_func.sql b/src/test/regress/sql/hash_func.sql
new file mode 100644
index 0000000..5ad33c1
--- /dev/null
+++ b/src/test/regress/sql/hash_func.sql
@@ -0,0 +1,264 @@
+--
+-- 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);
+
+-- array hashing with non-hashable element type
+SELECT v as value, hash_array(v)::bit(32) as standard
+FROM (VALUES ('{0}'::money[])) x(v);
+SELECT v as value, hash_array_extended(v, 0)::bit(32) as extended0
+FROM (VALUES ('{0}'::money[])) x(v);
+
+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);
+
+SELECT v as value, hash_multirange(v)::bit(32) as standard,
+ hash_multirange_extended(v, 0)::bit(32) as extended0,
+ hash_multirange_extended(v, 1)::bit(32) as extended1
+FROM (VALUES ('{[10,20)}'::int4multirange), ('{[23, 43]}'::int4multirange),
+ ('{[5675, 550273)}'::int4multirange),
+ ('{[550274, 1550274)}'::int4multirange),
+ ('{[1550275, 208112489)}'::int4multirange)) x(v)
+WHERE hash_multirange(v)::bit(32) != hash_multirange_extended(v, 0)::bit(32)
+ OR hash_multirange(v)::bit(32) = hash_multirange_extended(v, 1)::bit(32);
+
+CREATE TYPE hash_test_t1 AS (a int, b text);
+SELECT v as value, hash_record(v)::bit(32) as standard,
+ hash_record_extended(v, 0)::bit(32) as extended0,
+ hash_record_extended(v, 1)::bit(32) as extended1
+FROM (VALUES (row(1, 'aaa')::hash_test_t1, row(2, 'bbb'), row(-1, 'ccc'))) x(v)
+WHERE hash_record(v)::bit(32) != hash_record_extended(v, 0)::bit(32)
+ OR hash_record(v)::bit(32) = hash_record_extended(v, 1)::bit(32);
+DROP TYPE hash_test_t1;
+
+-- record hashing with non-hashable field type
+CREATE TYPE hash_test_t2 AS (a money, b text);
+SELECT v as value, hash_record(v)::bit(32) as standard
+FROM (VALUES (row(1, 'aaa')::hash_test_t2)) x(v);
+SELECT v as value, hash_record_extended(v, 0)::bit(32) as extended0
+FROM (VALUES (row(1, 'aaa')::hash_test_t2)) x(v);
+DROP TYPE hash_test_t2;
+
+--
+-- 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/hash_index.sql b/src/test/regress/sql/hash_index.sql
new file mode 100644
index 0000000..4d1aa02
--- /dev/null
+++ b/src/test/regress/sql/hash_index.sql
@@ -0,0 +1,204 @@
+--
+-- HASH_INDEX
+-- grep 843938989 hash.data
+--
+
+SELECT * FROM hash_i4_heap
+ WHERE hash_i4_heap.random = 843938989;
+
+--
+-- hash index
+-- grep 66766766 hash.data
+--
+SELECT * FROM hash_i4_heap
+ WHERE hash_i4_heap.random = 66766766;
+
+--
+-- hash index
+-- grep 1505703298 hash.data
+--
+SELECT * FROM hash_name_heap
+ WHERE hash_name_heap.random = '1505703298'::name;
+
+--
+-- hash index
+-- grep 7777777 hash.data
+--
+SELECT * FROM hash_name_heap
+ WHERE hash_name_heap.random = '7777777'::name;
+
+--
+-- hash index
+-- grep 1351610853 hash.data
+--
+SELECT * FROM hash_txt_heap
+ WHERE hash_txt_heap.random = '1351610853'::text;
+
+--
+-- hash index
+-- grep 111111112222222233333333 hash.data
+--
+SELECT * FROM hash_txt_heap
+ WHERE hash_txt_heap.random = '111111112222222233333333'::text;
+
+--
+-- hash index
+-- grep 444705537 hash.data
+--
+SELECT * FROM hash_f8_heap
+ WHERE hash_f8_heap.random = '444705537'::float8;
+
+--
+-- hash index
+-- grep 88888888 hash.data
+--
+SELECT * FROM hash_f8_heap
+ WHERE hash_f8_heap.random = '88888888'::float8;
+
+--
+-- hash index
+-- grep '^90[^0-9]' hashovfl.data
+--
+-- SELECT count(*) AS i988 FROM hash_ovfl_heap
+-- WHERE x = 90;
+
+--
+-- hash index
+-- grep '^1000[^0-9]' hashovfl.data
+--
+-- SELECT count(*) AS i0 FROM hash_ovfl_heap
+-- WHERE x = 1000;
+
+--
+-- HASH
+--
+UPDATE hash_i4_heap
+ SET random = 1
+ WHERE hash_i4_heap.seqno = 1492;
+
+SELECT h.seqno AS i1492, h.random AS i1
+ FROM hash_i4_heap h
+ WHERE h.random = 1;
+
+UPDATE hash_i4_heap
+ SET seqno = 20000
+ WHERE hash_i4_heap.random = 1492795354;
+
+SELECT h.seqno AS i20000
+ FROM hash_i4_heap h
+ WHERE h.random = 1492795354;
+
+UPDATE hash_name_heap
+ SET random = '0123456789abcdef'::name
+ WHERE hash_name_heap.seqno = 6543;
+
+SELECT h.seqno AS i6543, h.random AS c0_to_f
+ FROM hash_name_heap h
+ WHERE h.random = '0123456789abcdef'::name;
+
+UPDATE hash_name_heap
+ SET seqno = 20000
+ WHERE hash_name_heap.random = '76652222'::name;
+
+--
+-- this is the row we just replaced; index scan should return zero rows
+--
+SELECT h.seqno AS emptyset
+ FROM hash_name_heap h
+ WHERE h.random = '76652222'::name;
+
+UPDATE hash_txt_heap
+ SET random = '0123456789abcdefghijklmnop'::text
+ WHERE hash_txt_heap.seqno = 4002;
+
+SELECT h.seqno AS i4002, h.random AS c0_to_p
+ FROM hash_txt_heap h
+ WHERE h.random = '0123456789abcdefghijklmnop'::text;
+
+UPDATE hash_txt_heap
+ SET seqno = 20000
+ WHERE hash_txt_heap.random = '959363399'::text;
+
+SELECT h.seqno AS t20000
+ FROM hash_txt_heap h
+ WHERE h.random = '959363399'::text;
+
+UPDATE hash_f8_heap
+ SET random = '-1234.1234'::float8
+ WHERE hash_f8_heap.seqno = 8906;
+
+SELECT h.seqno AS i8096, h.random AS f1234_1234
+ FROM hash_f8_heap h
+ WHERE h.random = '-1234.1234'::float8;
+
+UPDATE hash_f8_heap
+ SET seqno = 20000
+ WHERE hash_f8_heap.random = '488912369'::float8;
+
+SELECT h.seqno AS f20000
+ FROM hash_f8_heap h
+ WHERE h.random = '488912369'::float8;
+
+-- UPDATE hash_ovfl_heap
+-- SET x = 1000
+-- WHERE x = 90;
+
+-- this vacuums the index as well
+-- VACUUM hash_ovfl_heap;
+
+-- SELECT count(*) AS i0 FROM hash_ovfl_heap
+-- WHERE x = 90;
+
+-- SELECT count(*) AS i988 FROM hash_ovfl_heap
+-- WHERE x = 1000;
+
+--
+-- Cause some overflow insert and splits.
+--
+CREATE TABLE hash_split_heap (keycol INT);
+INSERT INTO hash_split_heap SELECT 1 FROM generate_series(1, 500) a;
+CREATE INDEX hash_split_index on hash_split_heap USING HASH (keycol);
+INSERT INTO hash_split_heap SELECT 1 FROM generate_series(1, 5000) a;
+
+-- Let's do a backward scan.
+BEGIN;
+SET enable_seqscan = OFF;
+SET enable_bitmapscan = OFF;
+
+DECLARE c CURSOR FOR SELECT * from hash_split_heap WHERE keycol = 1;
+MOVE FORWARD ALL FROM c;
+MOVE BACKWARD 10000 FROM c;
+MOVE BACKWARD ALL FROM c;
+CLOSE c;
+END;
+
+-- DELETE, INSERT, VACUUM.
+DELETE FROM hash_split_heap WHERE keycol = 1;
+INSERT INTO hash_split_heap SELECT a/2 FROM generate_series(1, 25000) a;
+
+VACUUM hash_split_heap;
+
+-- Rebuild the index using a different fillfactor
+ALTER INDEX hash_split_index SET (fillfactor = 10);
+REINDEX INDEX hash_split_index;
+
+-- Clean up.
+DROP TABLE hash_split_heap;
+
+-- Index on temp table.
+CREATE TEMP TABLE hash_temp_heap (x int, y int);
+INSERT INTO hash_temp_heap VALUES (1,1);
+CREATE INDEX hash_idx ON hash_temp_heap USING hash (x);
+DROP TABLE hash_temp_heap CASCADE;
+
+-- Float4 type.
+CREATE TABLE hash_heap_float4 (x float4, y int);
+INSERT INTO hash_heap_float4 VALUES (1.1,1);
+CREATE INDEX hash_idx ON hash_heap_float4 USING hash (x);
+DROP TABLE hash_heap_float4 CASCADE;
+
+-- Test out-of-range fillfactor values
+CREATE INDEX hash_f8_index2 ON hash_f8_heap USING hash (random float8_ops)
+ WITH (fillfactor=9);
+CREATE INDEX hash_f8_index2 ON hash_f8_heap USING hash (random float8_ops)
+ WITH (fillfactor=101);
diff --git a/src/test/regress/sql/hash_part.sql b/src/test/regress/sql/hash_part.sql
new file mode 100644
index 0000000..e7eb365
--- /dev/null
+++ b/src/test/regress/sql/hash_part.sql
@@ -0,0 +1,90 @@
+--
+-- 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 TABLE mchash (a int, b text, c jsonb)
+ PARTITION BY HASH (a part_test_int4_ops, b part_test_text_ops);
+CREATE TABLE mchash1
+ PARTITION OF mchash FOR VALUES WITH (MODULUS 4, REMAINDER 0);
+
+-- invalid OID, no such table
+SELECT satisfies_hash_partition(0, 4, 0, NULL);
+
+-- not partitioned
+SELECT satisfies_hash_partition('tenk1'::regclass, 4, 0, NULL);
+
+-- partition rather than the parent
+SELECT satisfies_hash_partition('mchash1'::regclass, 4, 0, NULL);
+
+-- invalid modulus
+SELECT satisfies_hash_partition('mchash'::regclass, 0, 0, NULL);
+
+-- remainder too small
+SELECT satisfies_hash_partition('mchash'::regclass, 1, -1, NULL);
+
+-- remainder too large
+SELECT satisfies_hash_partition('mchash'::regclass, 1, 1, NULL);
+
+-- modulus is null
+SELECT satisfies_hash_partition('mchash'::regclass, NULL, 0, NULL);
+
+-- remainder is null
+SELECT satisfies_hash_partition('mchash'::regclass, 4, NULL, NULL);
+
+-- too many arguments
+SELECT satisfies_hash_partition('mchash'::regclass, 4, 0, NULL::int, NULL::text, NULL::json);
+
+-- too few arguments
+SELECT satisfies_hash_partition('mchash'::regclass, 3, 1, NULL::int);
+
+-- wrong argument type
+SELECT satisfies_hash_partition('mchash'::regclass, 2, 1, NULL::int, NULL::int);
+
+-- ok, should be false
+SELECT satisfies_hash_partition('mchash'::regclass, 4, 0, 0, ''::text);
+
+-- ok, should be true
+SELECT satisfies_hash_partition('mchash'::regclass, 4, 0, 2, ''::text);
+
+-- argument via variadic syntax, should fail because not all partitioning
+-- columns are of the correct type
+SELECT satisfies_hash_partition('mchash'::regclass, 2, 1,
+ variadic array[1,2]::int[]);
+
+-- multiple partitioning columns of the same type
+CREATE TABLE mcinthash (a int, b int, c jsonb)
+ PARTITION BY HASH (a part_test_int4_ops, b part_test_int4_ops);
+
+-- now variadic should work, should be false
+SELECT satisfies_hash_partition('mcinthash'::regclass, 4, 0,
+ variadic array[0, 0]);
+
+-- should be true
+SELECT satisfies_hash_partition('mcinthash'::regclass, 4, 0,
+ variadic array[0, 1]);
+
+-- wrong length
+SELECT satisfies_hash_partition('mcinthash'::regclass, 4, 0,
+ variadic array[]::int[]);
+
+-- wrong type
+SELECT satisfies_hash_partition('mcinthash'::regclass, 4, 0,
+ variadic array[now(), now()]);
+
+-- check satisfies_hash_partition passes correct collation
+create table text_hashp (a text) partition by hash (a);
+create table text_hashp0 partition of text_hashp for values with (modulus 2, remainder 0);
+create table text_hashp1 partition of text_hashp for values with (modulus 2, remainder 1);
+-- The result here should always be true, because 'xxx' must belong to
+-- one of the two defined partitions
+select satisfies_hash_partition('text_hashp'::regclass, 2, 0, 'xxx'::text) OR
+ satisfies_hash_partition('text_hashp'::regclass, 2, 1, 'xxx'::text) AS satisfies;
+
+-- cleanup
+DROP TABLE mchash;
+DROP TABLE mcinthash;
+DROP TABLE text_hashp;
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
new file mode 100644
index 0000000..7809111
--- /dev/null
+++ b/src/test/regress/sql/horology.sql
@@ -0,0 +1,570 @@
+--
+-- HOROLOGY
+--
+SET DateStyle = 'Postgres, MDY';
+
+SHOW TimeZone; -- Many of these tests depend on the prevailing setting
+
+--
+-- 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 '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 '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;
+
+--
+-- 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 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/hs_primary_extremes.sql b/src/test/regress/sql/hs_primary_extremes.sql
new file mode 100644
index 0000000..2051e2e
--- /dev/null
+++ b/src/test/regress/sql/hs_primary_extremes.sql
@@ -0,0 +1,73 @@
+--
+-- Hot Standby tests
+--
+-- hs_primary_extremes.sql
+--
+
+drop table if exists hs_extreme;
+create table hs_extreme (col1 integer);
+
+CREATE OR REPLACE FUNCTION hs_subxids (n integer)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+ BEGIN
+ IF n <= 0 THEN RETURN; END IF;
+ INSERT INTO hs_extreme VALUES (n);
+ PERFORM hs_subxids(n - 1);
+ RETURN;
+ EXCEPTION WHEN raise_exception THEN NULL; END;
+$$;
+
+BEGIN;
+SELECT hs_subxids(257);
+ROLLBACK;
+BEGIN;
+SELECT hs_subxids(257);
+COMMIT;
+
+set client_min_messages = 'warning';
+
+CREATE OR REPLACE FUNCTION hs_locks_create (n integer)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+ BEGIN
+ IF n <= 0 THEN
+ CHECKPOINT;
+ RETURN;
+ END IF;
+ EXECUTE 'CREATE TABLE hs_locks_' || n::text || ' ()';
+ PERFORM hs_locks_create(n - 1);
+ RETURN;
+ EXCEPTION WHEN raise_exception THEN NULL; END;
+$$;
+
+CREATE OR REPLACE FUNCTION hs_locks_drop (n integer)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+ BEGIN
+ IF n <= 0 THEN
+ CHECKPOINT;
+ RETURN;
+ END IF;
+ EXECUTE 'DROP TABLE IF EXISTS hs_locks_' || n::text;
+ PERFORM hs_locks_drop(n - 1);
+ RETURN;
+ EXCEPTION WHEN raise_exception THEN NULL; END;
+$$;
+
+BEGIN;
+SELECT hs_locks_drop(257);
+SELECT hs_locks_create(257);
+SELECT count(*) > 257 FROM pg_locks;
+ROLLBACK;
+BEGIN;
+SELECT hs_locks_drop(257);
+SELECT hs_locks_create(257);
+SELECT count(*) > 257 FROM pg_locks;
+COMMIT;
+SELECT hs_locks_drop(257);
+
+SELECT pg_switch_wal();
diff --git a/src/test/regress/sql/hs_primary_setup.sql b/src/test/regress/sql/hs_primary_setup.sql
new file mode 100644
index 0000000..eeb4421
--- /dev/null
+++ b/src/test/regress/sql/hs_primary_setup.sql
@@ -0,0 +1,25 @@
+--
+-- Hot Standby tests
+--
+-- hs_primary_setup.sql
+--
+
+drop table if exists hs1;
+create table hs1 (col1 integer primary key);
+insert into hs1 values (1);
+
+drop table if exists hs2;
+create table hs2 (col1 integer primary key);
+insert into hs2 values (12);
+insert into hs2 values (13);
+
+drop table if exists hs3;
+create table hs3 (col1 integer primary key);
+insert into hs3 values (113);
+insert into hs3 values (114);
+insert into hs3 values (115);
+
+DROP sequence if exists hsseq;
+create sequence hsseq;
+
+SELECT pg_switch_wal();
diff --git a/src/test/regress/sql/hs_standby_allowed.sql b/src/test/regress/sql/hs_standby_allowed.sql
new file mode 100644
index 0000000..6debddc
--- /dev/null
+++ b/src/test/regress/sql/hs_standby_allowed.sql
@@ -0,0 +1,125 @@
+--
+-- Hot Standby tests
+--
+-- hs_standby_allowed.sql
+--
+
+-- SELECT
+
+select count(*) as should_be_1 from hs1;
+
+select count(*) as should_be_2 from hs2;
+
+select count(*) as should_be_3 from hs3;
+
+COPY hs1 TO '/tmp/copy_test';
+\! cat /tmp/copy_test
+
+-- Access sequence directly
+select is_called from hsseq;
+
+-- Transactions
+
+begin;
+select count(*) as should_be_1 from hs1;
+end;
+
+begin transaction read only;
+select count(*) as should_be_1 from hs1;
+end;
+
+begin transaction isolation level repeatable read;
+select count(*) as should_be_1 from hs1;
+select count(*) as should_be_1 from hs1;
+select count(*) as should_be_1 from hs1;
+commit;
+
+begin;
+select count(*) as should_be_1 from hs1;
+commit;
+
+begin;
+select count(*) as should_be_1 from hs1;
+abort;
+
+start transaction;
+select count(*) as should_be_1 from hs1;
+commit;
+
+begin;
+select count(*) as should_be_1 from hs1;
+rollback;
+
+begin;
+select count(*) as should_be_1 from hs1;
+savepoint s;
+select count(*) as should_be_2 from hs2;
+commit;
+
+begin;
+select count(*) as should_be_1 from hs1;
+savepoint s;
+select count(*) as should_be_2 from hs2;
+release savepoint s;
+select count(*) as should_be_2 from hs2;
+savepoint s;
+select count(*) as should_be_3 from hs3;
+rollback to savepoint s;
+select count(*) as should_be_2 from hs2;
+commit;
+
+-- SET parameters
+
+-- has no effect on read only transactions, but we can still set it
+set synchronous_commit = on;
+show synchronous_commit;
+reset synchronous_commit;
+
+discard temp;
+discard all;
+
+-- CURSOR commands
+
+BEGIN;
+
+DECLARE hsc CURSOR FOR select * from hs3;
+
+FETCH next from hsc;
+fetch first from hsc;
+fetch last from hsc;
+fetch 1 from hsc;
+
+CLOSE hsc;
+
+COMMIT;
+
+-- Prepared plans
+
+PREPARE hsp AS select count(*) from hs1;
+PREPARE hsp_noexec (integer) AS insert into hs1 values ($1);
+
+EXECUTE hsp;
+
+DEALLOCATE hsp;
+
+-- LOCK
+
+BEGIN;
+LOCK hs1 IN ACCESS SHARE MODE;
+LOCK hs1 IN ROW SHARE MODE;
+LOCK hs1 IN ROW EXCLUSIVE MODE;
+COMMIT;
+
+-- UNLISTEN
+UNLISTEN a;
+UNLISTEN *;
+
+-- LOAD
+-- should work, easier if there is no test for that...
+
+
+-- ALLOWED COMMANDS
+
+CHECKPOINT;
+
+discard all;
diff --git a/src/test/regress/sql/hs_standby_check.sql b/src/test/regress/sql/hs_standby_check.sql
new file mode 100644
index 0000000..3fe8a02
--- /dev/null
+++ b/src/test/regress/sql/hs_standby_check.sql
@@ -0,0 +1,16 @@
+--
+-- Hot Standby tests
+--
+-- hs_standby_check.sql
+--
+
+--
+-- If the query below returns false then all other tests will fail after it.
+--
+select case pg_is_in_recovery() when false then
+ 'These tests are intended only for execution on a standby server that is reading ' ||
+ 'WAL from a server upon which the regression database is already created and into ' ||
+ 'which src/test/regress/sql/hs_primary_setup.sql has been run'
+else
+ 'Tests are running on a standby server during recovery'
+end;
diff --git a/src/test/regress/sql/hs_standby_disallowed.sql b/src/test/regress/sql/hs_standby_disallowed.sql
new file mode 100644
index 0000000..a470600
--- /dev/null
+++ b/src/test/regress/sql/hs_standby_disallowed.sql
@@ -0,0 +1,103 @@
+--
+-- Hot Standby tests
+--
+-- hs_standby_disallowed.sql
+--
+
+SET transaction_read_only = off;
+
+begin transaction read write;
+commit;
+
+-- SELECT
+
+select * from hs1 FOR SHARE;
+select * from hs1 FOR UPDATE;
+
+-- DML
+BEGIN;
+insert into hs1 values (37);
+ROLLBACK;
+BEGIN;
+delete from hs1 where col1 = 1;
+ROLLBACK;
+BEGIN;
+update hs1 set col1 = NULL where col1 > 0;
+ROLLBACK;
+BEGIN;
+truncate hs3;
+ROLLBACK;
+
+-- DDL
+
+create temporary table hstemp1 (col1 integer);
+BEGIN;
+drop table hs2;
+ROLLBACK;
+BEGIN;
+create table hs4 (col1 integer);
+ROLLBACK;
+
+-- Sequences
+
+SELECT nextval('hsseq');
+
+-- Two-phase commit transaction stuff
+
+BEGIN;
+SELECT count(*) FROM hs1;
+PREPARE TRANSACTION 'foobar';
+ROLLBACK;
+BEGIN;
+SELECT count(*) FROM hs1;
+COMMIT PREPARED 'foobar';
+ROLLBACK;
+
+BEGIN;
+SELECT count(*) FROM hs1;
+PREPARE TRANSACTION 'foobar';
+ROLLBACK PREPARED 'foobar';
+ROLLBACK;
+
+BEGIN;
+SELECT count(*) FROM hs1;
+ROLLBACK PREPARED 'foobar';
+ROLLBACK;
+
+
+-- Locks
+BEGIN;
+LOCK hs1;
+COMMIT;
+BEGIN;
+LOCK hs1 IN SHARE UPDATE EXCLUSIVE MODE;
+COMMIT;
+BEGIN;
+LOCK hs1 IN SHARE MODE;
+COMMIT;
+BEGIN;
+LOCK hs1 IN SHARE ROW EXCLUSIVE MODE;
+COMMIT;
+BEGIN;
+LOCK hs1 IN EXCLUSIVE MODE;
+COMMIT;
+BEGIN;
+LOCK hs1 IN ACCESS EXCLUSIVE MODE;
+COMMIT;
+
+-- Listen
+listen a;
+notify a;
+
+-- disallowed commands
+
+ANALYZE hs1;
+
+VACUUM hs2;
+
+CLUSTER hs2 using hs1_pkey;
+
+REINDEX TABLE hs2;
+
+REVOKE SELECT ON hs1 FROM PUBLIC;
+GRANT SELECT ON hs1 TO PUBLIC;
diff --git a/src/test/regress/sql/hs_standby_functions.sql b/src/test/regress/sql/hs_standby_functions.sql
new file mode 100644
index 0000000..b57f67f
--- /dev/null
+++ b/src/test/regress/sql/hs_standby_functions.sql
@@ -0,0 +1,24 @@
+--
+-- Hot Standby tests
+--
+-- hs_standby_functions.sql
+--
+
+-- should fail
+select pg_current_xact_id();
+
+select length(pg_current_snapshot()::text) >= 4;
+
+select pg_start_backup('should fail');
+select pg_switch_wal();
+select pg_stop_backup();
+
+-- should return no rows
+select * from pg_prepared_xacts;
+
+-- just the startup process
+select locktype, virtualxid, virtualtransaction, mode, granted
+from pg_locks where virtualxid = '1/1';
+
+-- suicide is painless
+select pg_cancel_backend(pg_backend_pid());
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
new file mode 100644
index 0000000..52800f2
--- /dev/null
+++ b/src/test/regress/sql/identity.sql
@@ -0,0 +1,357 @@
+-- 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
+
+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;
+SELECT * FROM itest2;
+SELECT * FROM itest3;
+SELECT * FROM itest4;
+
+
+-- VALUES RTEs
+
+CREATE TABLE itest5 (a int generated always as identity, b text);
+INSERT INTO itest5 VALUES (1, 'a'); -- error
+INSERT INTO itest5 VALUES (DEFAULT, 'a'); -- ok
+INSERT INTO itest5 VALUES (2, 'b'), (3, 'c'); -- error
+INSERT INTO itest5 VALUES (DEFAULT, 'b'), (3, 'c'); -- error
+INSERT INTO itest5 VALUES (2, 'b'), (DEFAULT, 'c'); -- error
+INSERT INTO itest5 VALUES (DEFAULT, 'b'), (DEFAULT, 'c'); -- ok
+
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-1, 'aa');
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-2, 'bb'), (-3, 'cc');
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (DEFAULT, 'dd'), (-4, 'ee');
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-5, 'ff'), (DEFAULT, 'gg');
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (DEFAULT, 'hh'), (DEFAULT, 'ii');
+
+INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-1, 'aaa');
+INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-2, 'bbb'), (-3, 'ccc');
+INSERT INTO itest5 OVERRIDING USER VALUE VALUES (DEFAULT, 'ddd'), (-4, 'eee');
+INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-5, 'fff'), (DEFAULT, 'ggg');
+INSERT INTO itest5 OVERRIDING USER VALUE VALUES (DEFAULT, 'hhh'), (DEFAULT, 'iii');
+
+SELECT * FROM itest5;
+DROP TABLE itest5;
+
+INSERT INTO itest3 VALUES (DEFAULT, 'a');
+INSERT INTO itest3 VALUES (DEFAULT, 'b'), (DEFAULT, 'c');
+
+SELECT * FROM itest3;
+
+
+-- OVERRIDING tests
+
+-- GENERATED BY DEFAULT
+
+-- This inserts the row as presented:
+INSERT INTO itest1 VALUES (10, 'xyz');
+-- With GENERATED BY DEFAULT, OVERRIDING SYSTEM VALUE is not allowed
+-- by the standard, but we allow it as a no-op, since it is of use if
+-- there are multiple identity columns in a table, which is also an
+-- extension.
+INSERT INTO itest1 OVERRIDING SYSTEM VALUE VALUES (20, 'xyz');
+-- This ignores the 30 and uses the sequence value instead:
+INSERT INTO itest1 OVERRIDING USER VALUE VALUES (30, 'xyz');
+
+SELECT * FROM itest1;
+
+-- GENERATED ALWAYS
+
+-- This is an error:
+INSERT INTO itest2 VALUES (10, 'xyz');
+-- This inserts the row as presented:
+INSERT INTO itest2 OVERRIDING SYSTEM VALUE VALUES (20, 'xyz');
+-- This ignores the 30 and uses the sequence value instead:
+INSERT INTO itest2 OVERRIDING USER VALUE VALUES (30, 'xyz');
+
+SELECT * FROM itest2;
+
+
+-- UPDATE tests
+
+-- GENERATED BY DEFAULT is not restricted.
+UPDATE itest1 SET a = 101 WHERE a = 1;
+UPDATE itest1 SET a = DEFAULT WHERE a = 2;
+SELECT * FROM itest1;
+
+-- GENERATED ALWAYS allows only DEFAULT.
+UPDATE itest2 SET a = 101 WHERE a = 1; -- error
+UPDATE itest2 SET a = DEFAULT WHERE a = 2; -- ok
+SELECT * FROM itest2;
+
+
+-- 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;
+
+-- 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;
+SELECT * FROM itestv11;
+
+INSERT INTO itestv10 VALUES (10, 'xyz');
+INSERT INTO itestv10 OVERRIDING USER VALUE VALUES (11, 'xyz');
+
+SELECT * FROM itestv10;
+
+INSERT INTO itestv11 VALUES (10, 'xyz');
+INSERT INTO itestv11 OVERRIDING SYSTEM VALUE VALUES (11, 'xyz');
+
+SELECT * FROM itestv11;
+
+DROP VIEW itestv10, itestv11;
+
+
+-- 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
+
+-- kinda silly to change property in the same command, but it should work
+ALTER TABLE itest3
+ ADD COLUMN c int GENERATED BY DEFAULT AS IDENTITY,
+ ALTER COLUMN c SET GENERATED ALWAYS;
+\d itest3
+
+
+-- 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' ORDER BY 1, 2;
+
+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;
+
+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+
+ALTER TABLE itest8
+ ADD COLUMN f2 int NOT NULL,
+ ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+ ADD COLUMN f3 int NOT NULL,
+ ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+ ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+
+ALTER TABLE itest8
+ ADD COLUMN f4 int;
+
+ALTER TABLE itest8
+ ALTER COLUMN f4 SET NOT NULL,
+ ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+ ALTER COLUMN f4 SET DATA TYPE bigint;
+
+ALTER TABLE itest8
+ ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+ ALTER COLUMN f5 DROP IDENTITY,
+ ALTER COLUMN f5 DROP NOT NULL,
+ ALTER COLUMN f5 SET DATA TYPE bigint;
+
+INSERT INTO itest8 VALUES(0), (1);
+
+-- This does not work when the table isn't empty. That's intentional,
+-- since ADD GENERATED should only affect later insertions:
+ALTER TABLE itest8
+ ADD COLUMN f22 int NOT NULL,
+ ALTER COLUMN f22 ADD GENERATED ALWAYS AS IDENTITY;
+
+TABLE itest8;
+\d+ itest8
+\d itest8_f2_seq
+\d itest8_f3_seq
+\d itest8_f4_seq
+\d itest8_f5_seq
+DROP TABLE itest8;
+
+
+-- 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 (currently not supported)
+
+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;
+
+
+-- test that sequence of half-dropped serial column is properly ignored
+
+CREATE TABLE itest14 (id serial);
+ALTER TABLE itest14 ALTER id DROP DEFAULT;
+ALTER TABLE itest14 ALTER id ADD GENERATED BY DEFAULT AS IDENTITY;
+INSERT INTO itest14 (id) VALUES (DEFAULT);
+
+-- 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/incremental_sort.sql b/src/test/regress/sql/incremental_sort.sql
new file mode 100644
index 0000000..284a354
--- /dev/null
+++ b/src/test/regress/sql/incremental_sort.sql
@@ -0,0 +1,283 @@
+-- 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;
+
+-- Incremental sort vs. parallel queries
+set min_parallel_table_scan_size = '1kB';
+set min_parallel_index_scan_size = '1kB';
+set parallel_setup_cost = 0;
+set parallel_tuple_cost = 0;
+set max_parallel_workers_per_gather = 2;
+
+create table t (a int, b int, c int);
+insert into t select mod(i,10),mod(i,10),i from generate_series(1,10000) s(i);
+create index on t (a);
+analyze t;
+
+set enable_incremental_sort = off;
+explain (costs off) select a,b,sum(c) from t group by 1,2 order by 1,2,3 limit 1;
+
+set enable_incremental_sort = on;
+explain (costs off) select a,b,sum(c) from t group by 1,2 order by 1,2,3 limit 1;
+
+-- Incremental sort vs. set operations with varno 0
+set enable_hashagg to off;
+explain (costs off) select * from t union select * from t order by 1,3;
+
+-- Full sort, not just incremental sort can be pushed below a gather merge path
+-- by generate_useful_gather_paths.
+explain (costs off) select distinct a,b from t;
+
+drop table t;
+
+-- Sort pushdown can't go below where expressions are part of the rel target.
+-- In particular this is interesting for volatile expressions which have to
+-- go above joins since otherwise we'll incorrectly use expression evaluations
+-- across multiple rows.
+set enable_hashagg=off;
+set enable_seqscan=off;
+set enable_incremental_sort = off;
+set parallel_tuple_cost=0;
+set parallel_setup_cost=0;
+set min_parallel_table_scan_size = 0;
+set min_parallel_index_scan_size = 0;
+
+-- Parallel sort below join.
+explain (costs off) select distinct sub.unique1, stringu1
+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;
+explain (costs off) select sub.unique1, stringu1
+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub
+order by 1, 2;
+-- Parallel sort but with expression that can be safely generated at the base rel.
+explain (costs off) select distinct sub.unique1, md5(stringu1)
+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;
+explain (costs off) select sub.unique1, md5(stringu1)
+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub
+order by 1, 2;
+-- Parallel sort with an aggregate that can be safely generated in parallel,
+-- but we can't sort by partial aggregate values.
+explain (costs off) select count(*)
+from tenk1 t1
+join tenk1 t2 on t1.unique1 = t2.unique2
+join tenk1 t3 on t2.unique1 = t3.unique1
+order by count(*);
+-- Parallel sort but with expression (correlated subquery) that
+-- is prohibited in parallel plans.
+explain (costs off) select distinct
+ unique1,
+ (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
+from tenk1 t, generate_series(1, 1000);
+explain (costs off) select
+ unique1,
+ (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
+from tenk1 t, generate_series(1, 1000)
+order by 1, 2;
+-- Parallel sort but with expression not available until the upper rel.
+explain (costs off) select distinct sub.unique1, stringu1 || random()::text
+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;
+explain (costs off) select sub.unique1, stringu1 || random()::text
+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub
+order by 1, 2;
diff --git a/src/test/regress/sql/index_including.sql b/src/test/regress/sql/index_including.sql
new file mode 100644
index 0000000..44b3400
--- /dev/null
+++ b/src/test/regress/sql/index_including.sql
@@ -0,0 +1,219 @@
+/*
+ * 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);
+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;
+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));
+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;
+
+/*
+ * 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, gist and spgist must fail.
+ */
+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/index_including_gist.sql b/src/test/regress/sql/index_including_gist.sql
new file mode 100644
index 0000000..7d5c99b
--- /dev/null
+++ b/src/test/regress/sql/index_including_gist.sql
@@ -0,0 +1,90 @@
+/*
+ * 1.1. test CREATE INDEX with buffered build
+ */
+
+-- Regular index with included columns
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+-- size is chosen to exceed page size and trigger actual truncation
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3);
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl_gist'::regclass ORDER BY c.relname;
+SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+SET enable_bitmapscan TO off;
+EXPLAIN (costs off) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+SET enable_bitmapscan TO default;
+DROP TABLE tbl_gist;
+
+/*
+ * 1.2. test CREATE INDEX with inserts
+ */
+
+-- Regular index with included columns
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+-- size is chosen to exceed page size and trigger actual truncation
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x;
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl_gist'::regclass ORDER BY c.relname;
+SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+SET enable_bitmapscan TO off;
+EXPLAIN (costs off) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+SET enable_bitmapscan TO default;
+DROP TABLE tbl_gist;
+
+/*
+ * 2. CREATE INDEX CONCURRENTLY
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX CONCURRENTLY tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+DROP TABLE tbl_gist;
+
+
+/*
+ * 3. REINDEX
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c3);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+REINDEX INDEX tbl_gist_idx;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+ALTER TABLE tbl_gist DROP COLUMN c1;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+DROP TABLE tbl_gist;
+
+/*
+ * 4. Update, delete values in indexed table.
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c3);
+UPDATE tbl_gist SET c1 = 100 WHERE c1 = 2;
+UPDATE tbl_gist SET c1 = 1 WHERE c1 = 3;
+DELETE FROM tbl_gist WHERE c1 = 5 OR c3 = 12;
+DROP TABLE tbl_gist;
+
+/*
+ * 5. Alter column type.
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c3);
+ALTER TABLE tbl_gist ALTER c1 TYPE bigint;
+ALTER TABLE tbl_gist ALTER c3 TYPE bigint;
+\d tbl_gist
+DROP TABLE tbl_gist;
+
+/*
+ * 6. EXCLUDE constraint.
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box, EXCLUDE USING gist (c4 WITH &&) INCLUDE (c1, c2, c3));
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(3*x,2*x),point(3*x+1,2*x+1)) FROM generate_series(1,10) AS x;
+EXPLAIN (costs off) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+\d tbl_gist
+DROP TABLE tbl_gist;
diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql
new file mode 100644
index 0000000..42f398b
--- /dev/null
+++ b/src/test/regress/sql/indexing.sql
@@ -0,0 +1,749 @@
+-- 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);
+
+-- relhassubclass of a partitioned index is false before creating any partition.
+-- It will be set after the first partition is created.
+create index idxpart_idx on idxpart (a);
+select relhassubclass from pg_class where relname = 'idxpart_idx';
+
+-- Check that partitioned indexes are present in pg_indexes.
+select indexdef from pg_indexes where indexname like 'idxpart_idx%';
+drop index idxpart_idx;
+
+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);
+
+-- Even with partitions, relhassubclass should not be set if a partitioned
+-- index is created only on the parent.
+create index idxpart_idx on only idxpart(a);
+select relhassubclass from pg_class where relname = 'idxpart_idx';
+drop index idxpart_idx;
+
+create index on idxpart (a);
+select relname, relkind, relhassubclass, 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
+
+-- Forbid ALTER TABLE when attaching or detaching an index to a partition.
+create index idxpart_c on only idxpart (c);
+create index idxpart1_c on idxpart1 (c);
+alter table idxpart_c attach partition idxpart1_c for values from (10) to (20);
+alter index idxpart_c attach partition idxpart1_c;
+select relname, relpartbound from pg_class
+ where relname in ('idxpart_c', 'idxpart1_c')
+ order by relname;
+alter table idxpart_c detach partition idxpart1_c;
+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, relhassubclass, 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);
+drop index idxpart1_a_idx; -- no way
+drop index concurrently idxpart_a_idx; -- unsupported
+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;
+
+-- DROP behavior with temporary partitioned indexes
+create temp table idxpart_temp (a int) partition by range (a);
+create index on idxpart_temp(a);
+create temp table idxpart1_temp partition of idxpart_temp
+ for values from (0) to (10);
+drop index idxpart1_temp_a_idx; -- error
+-- non-concurrent drop is enforced here, so it is a valid case.
+drop index concurrently idxpart_temp_a_idx;
+select relname, relkind from pg_class
+ where relname like 'idxpart_temp%' order by relname;
+drop table idxpart_temp;
+
+-- 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);
+create index on idxpart1 using hash (a);
+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);
+alter table idxpart attach partition idxpart1 for values from (0) to (1000);
+\d idxpart1
+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;
+
+create table idxpart (a int, b int, c int) partition by range(a);
+create index on idxpart(c);
+create table idxpart1 partition of idxpart for values from (0) to (250);
+create table idxpart2 partition of idxpart for values from (250) to (500);
+alter table idxpart detach partition idxpart2;
+\d idxpart2
+alter table idxpart2 drop column c;
+\d idxpart2
+drop table idxpart, idxpart2;
+
+-- 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;
+
+-- Related to the above scenario: ADD PRIMARY KEY on the parent mustn't
+-- automatically propagate NOT NULL to child columns.
+create table idxpart (a int) partition by range (a);
+create table idxpart0 (like idxpart);
+alter table idxpart0 add unique (a);
+alter table idxpart attach partition idxpart0 default;
+alter table only idxpart add primary key (a); -- fail, no NOT NULL constraint
+alter table idxpart0 alter column a set not null;
+alter table only idxpart add primary key (a); -- now it works
+alter table idxpart0 alter column a drop not null; -- fail, pkey needs it
+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;
+
+-- 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);
+create table idxpart3 (c int, b int, a int) partition by range (a);
+alter table idxpart3 drop column b, drop column c;
+create table idxpart31 partition of idxpart3 for values from (1000) to (1200);
+create table idxpart32 partition of idxpart3 for values from (1200) to (1400);
+alter table idxpart attach partition idxpart3 for values from (1000) to (2000);
+
+-- More objects intentionally left behind, to verify some pg_dump/pg_upgrade
+-- behavior; see https://postgr.es/m/20190321204928.GA17535@alvherre.pgsql
+create schema regress_indexing;
+set search_path to regress_indexing;
+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 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 (like pk) partition by range (a);
+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);
+reset search_path;
+
+-- 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;
+
+-- check that dropping a column takes with it any partitioned indexes
+-- depending on it.
+create table parted_index_col_drop(a int, b int, c int)
+ partition by list (a);
+create table parted_index_col_drop1 partition of parted_index_col_drop
+ for values in (1) partition by list (a);
+-- leave this partition without children.
+create table parted_index_col_drop2 partition of parted_index_col_drop
+ for values in (2) partition by list (a);
+create table parted_index_col_drop11 partition of parted_index_col_drop1
+ for values in (1);
+create index on parted_index_col_drop (b);
+create index on parted_index_col_drop (c);
+create index on parted_index_col_drop (b, c);
+alter table parted_index_col_drop drop column c;
+\d parted_index_col_drop
+\d parted_index_col_drop1
+\d parted_index_col_drop2
+\d parted_index_col_drop11
+drop table parted_index_col_drop;
diff --git a/src/test/regress/sql/indirect_toast.sql b/src/test/regress/sql/indirect_toast.sql
new file mode 100644
index 0000000..9156a44
--- /dev/null
+++ b/src/test/regress/sql/indirect_toast.sql
@@ -0,0 +1,71 @@
+--
+-- Tests for external toast datums
+--
+
+-- Other compression algorithms may cause the compressed data to be stored
+-- inline. pglz guarantees that the data is externalized, so stick to it.
+SET default_toast_compression = 'pglz';
+
+CREATE TABLE indtoasttest(descr text, cnt int DEFAULT 0, f1 text, f2 text);
+
+INSERT INTO indtoasttest(descr, f1, f2) VALUES('two-compressed', repeat('1234567890',1000), repeat('1234567890',1000));
+INSERT INTO indtoasttest(descr, f1, f2) VALUES('two-toasted', repeat('1234567890',30000), repeat('1234567890',50000));
+INSERT INTO indtoasttest(descr, f1, f2) VALUES('one-compressed,one-null', NULL, repeat('1234567890',1000));
+INSERT INTO indtoasttest(descr, f1, f2) VALUES('one-toasted,one-null', NULL, repeat('1234567890',50000));
+
+-- check whether indirect tuples works on the most basic level
+SELECT descr, substring(make_tuple_indirect(indtoasttest)::text, 1, 200) FROM indtoasttest;
+
+-- modification without changing varlenas
+UPDATE indtoasttest SET cnt = cnt +1 RETURNING substring(indtoasttest::text, 1, 200);
+
+-- modification without modifying assigned value
+UPDATE indtoasttest SET cnt = cnt +1, f1 = f1 RETURNING substring(indtoasttest::text, 1, 200);
+
+-- modification modifying, but effectively not changing
+UPDATE indtoasttest SET cnt = cnt +1, f1 = f1||'' RETURNING substring(indtoasttest::text, 1, 200);
+
+UPDATE indtoasttest SET cnt = cnt +1, f1 = '-'||f1||'-' RETURNING substring(indtoasttest::text, 1, 200);
+
+SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
+-- check we didn't screw with main/toast tuple visibility
+VACUUM FREEZE indtoasttest;
+SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
+
+-- now create a trigger that forces all Datums to be indirect ones
+CREATE FUNCTION update_using_indirect()
+ RETURNS trigger
+ LANGUAGE plpgsql AS $$
+BEGIN
+ NEW := make_tuple_indirect(NEW);
+ RETURN NEW;
+END$$;
+
+CREATE TRIGGER indtoasttest_update_indirect
+ BEFORE INSERT OR UPDATE
+ ON indtoasttest
+ FOR EACH ROW
+ EXECUTE PROCEDURE update_using_indirect();
+
+-- modification without changing varlenas
+UPDATE indtoasttest SET cnt = cnt +1 RETURNING substring(indtoasttest::text, 1, 200);
+
+-- modification without modifying assigned value
+UPDATE indtoasttest SET cnt = cnt +1, f1 = f1 RETURNING substring(indtoasttest::text, 1, 200);
+
+-- modification modifying, but effectively not changing
+UPDATE indtoasttest SET cnt = cnt +1, f1 = f1||'' RETURNING substring(indtoasttest::text, 1, 200);
+
+UPDATE indtoasttest SET cnt = cnt +1, f1 = '-'||f1||'-' RETURNING substring(indtoasttest::text, 1, 200);
+
+INSERT INTO indtoasttest(descr, f1, f2) VALUES('one-toasted,one-null, via indirect', repeat('1234567890',30000), NULL);
+
+SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
+-- check we didn't screw with main/toast tuple visibility
+VACUUM FREEZE indtoasttest;
+SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
+
+DROP TABLE indtoasttest;
+DROP FUNCTION update_using_indirect();
+
+RESET default_toast_compression;
diff --git a/src/test/regress/sql/inet.sql b/src/test/regress/sql/inet.sql
new file mode 100644
index 0000000..d2ac85b
--- /dev/null
+++ b/src/test/regress/sql/inet.sql
@@ -0,0 +1,254 @@
+--
+-- 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 c AS cidr, i AS inet FROM INET_TBL;
+
+-- now test some support functions
+
+SELECT i AS inet, host(i), text(i), family(i) FROM INET_TBL;
+SELECT c AS cidr, abbrev(c) FROM INET_TBL;
+SELECT c AS cidr, broadcast(c),
+ i AS inet, broadcast(i) FROM INET_TBL;
+SELECT c AS cidr, network(c) AS "network(cidr)",
+ i AS inet, network(i) AS "network(inet)" FROM INET_TBL;
+SELECT c AS cidr, masklen(c) AS "masklen(cidr)",
+ i AS inet, masklen(i) AS "masklen(inet)" FROM INET_TBL;
+
+SELECT c AS cidr, masklen(c) AS "masklen(cidr)",
+ i AS inet, masklen(i) AS "masklen(inet)" FROM INET_TBL
+ WHERE masklen(c) <= 8;
+
+SELECT c AS cidr, i AS inet FROM INET_TBL
+ WHERE c = i;
+
+SELECT 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;
+
+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 set_masklen(inet(text(i)), 24) FROM INET_TBL;
+
+-- check that btree index works correctly
+CREATE INDEX inet_idx1 ON inet_tbl(i);
+SET enable_seqscan TO off;
+EXPLAIN (COSTS OFF)
+SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr;
+SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr;
+EXPLAIN (COSTS OFF)
+SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr;
+SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr;
+EXPLAIN (COSTS OFF)
+SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >>= i;
+SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >>= i;
+EXPLAIN (COSTS OFF)
+SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >> i;
+SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >> i;
+SET enable_seqscan TO on;
+DROP INDEX inet_idx1;
+
+-- check that gist index works correctly
+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 i;
+SELECT * FROM inet_tbl WHERE i <<= '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i && '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i >>= '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i >> '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i < '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i <= '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i = '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i >= '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i > '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i <> '192.168.1.0/24'::cidr ORDER BY i;
+
+-- test index-only scans
+EXPLAIN (COSTS OFF)
+SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY i;
+SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY i;
+
+SET enable_seqscan TO on;
+DROP INDEX inet_idx2;
+
+-- check that spgist index works correctly
+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 i;
+SELECT * FROM inet_tbl WHERE i <<= '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i && '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i >>= '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i >> '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i < '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i <= '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i = '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i >= '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i > '192.168.1.0/24'::cidr ORDER BY i;
+SELECT * FROM inet_tbl WHERE i <> '192.168.1.0/24'::cidr ORDER BY i;
+
+-- test index-only scans
+EXPLAIN (COSTS OFF)
+SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY i;
+SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY i;
+
+SET enable_seqscan TO on;
+DROP INDEX inet_idx3;
+
+-- simple tests of inet boolean and arithmetic operators
+SELECT i, ~i AS "~i" FROM inet_tbl;
+SELECT i, c, i & c AS "and" FROM inet_tbl;
+SELECT i, c, i | c AS "or" FROM inet_tbl;
+SELECT i, i + 500 AS "i+500" FROM inet_tbl;
+SELECT i, i - 500 AS "i-500" FROM inet_tbl;
+SELECT i, c, i - c AS "minus" FROM inet_tbl;
+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;
+-- fix it by inet_same_family() condition
+SELECT inet_merge(c, i) FROM INET_TBL WHERE inet_same_family(c, i);
+
+-- Test inet sortsupport with a variety of boundary inputs:
+SELECT a FROM (VALUES
+ ('0.0.0.0/0'::inet),
+ ('0.0.0.0/1'::inet),
+ ('0.0.0.0/32'::inet),
+ ('0.0.0.1/0'::inet),
+ ('0.0.0.1/1'::inet),
+ ('127.126.127.127/0'::inet),
+ ('127.127.127.127/0'::inet),
+ ('127.128.127.127/0'::inet),
+ ('192.168.1.0/24'::inet),
+ ('192.168.1.0/25'::inet),
+ ('192.168.1.1/23'::inet),
+ ('192.168.1.1/5'::inet),
+ ('192.168.1.1/6'::inet),
+ ('192.168.1.1/25'::inet),
+ ('192.168.1.2/25'::inet),
+ ('192.168.1.1/26'::inet),
+ ('192.168.1.2/26'::inet),
+ ('192.168.1.2/23'::inet),
+ ('192.168.1.255/5'::inet),
+ ('192.168.1.255/6'::inet),
+ ('192.168.1.3/1'::inet),
+ ('192.168.1.3/23'::inet),
+ ('192.168.1.4/0'::inet),
+ ('192.168.1.5/0'::inet),
+ ('255.0.0.0/0'::inet),
+ ('255.1.0.0/0'::inet),
+ ('255.2.0.0/0'::inet),
+ ('255.255.000.000/0'::inet),
+ ('255.255.000.000/0'::inet),
+ ('255.255.000.000/15'::inet),
+ ('255.255.000.000/16'::inet),
+ ('255.255.255.254/32'::inet),
+ ('255.255.255.000/32'::inet),
+ ('255.255.255.001/31'::inet),
+ ('255.255.255.002/31'::inet),
+ ('255.255.255.003/31'::inet),
+ ('255.255.255.003/32'::inet),
+ ('255.255.255.001/32'::inet),
+ ('255.255.255.255/0'::inet),
+ ('255.255.255.255/0'::inet),
+ ('255.255.255.255/0'::inet),
+ ('255.255.255.255/1'::inet),
+ ('255.255.255.255/16'::inet),
+ ('255.255.255.255/16'::inet),
+ ('255.255.255.255/31'::inet),
+ ('255.255.255.255/32'::inet),
+ ('255.255.255.253/32'::inet),
+ ('255.255.255.252/32'::inet),
+ ('255.3.0.0/0'::inet),
+ ('0000:0000:0000:0000:0000:0000:0000:0000/0'::inet),
+ ('0000:0000:0000:0000:0000:0000:0000:0000/128'::inet),
+ ('0000:0000:0000:0000:0000:0000:0000:0001/128'::inet),
+ ('10:23::f1/64'::inet),
+ ('10:23::f1/65'::inet),
+ ('10:23::ffff'::inet),
+ ('127::1'::inet),
+ ('127::2'::inet),
+ ('8000:0000:0000:0000:0000:0000:0000:0000/1'::inet),
+ ('::1:ffff:ffff:ffff:ffff/128'::inet),
+ ('::2:ffff:ffff:ffff:ffff/128'::inet),
+ ('::4:3:2:0/24'::inet),
+ ('::4:3:2:1/24'::inet),
+ ('::4:3:2:2/24'::inet),
+ ('ffff:83e7:f118:57dc:6093:6d92:689d:58cf/70'::inet),
+ ('ffff:84b0:4775:536e:c3ed:7116:a6d6:34f0/44'::inet),
+ ('ffff:8566:f84:5867:47f1:7867:d2ba:8a1a/69'::inet),
+ ('ffff:8883:f028:7d2:4d68:d510:7d6b:ac43/73'::inet),
+ ('ffff:8ae8:7c14:65b3:196:8e4a:89ae:fb30/89'::inet),
+ ('ffff:8dd0:646:694c:7c16:7e35:6a26:171/104'::inet),
+ ('ffff:8eef:cbf:700:eda3:ae32:f4b4:318b/121'::inet),
+ ('ffff:90e7:e744:664:a93:8efe:1f25:7663/122'::inet),
+ ('ffff:9597:c69c:8b24:57a:8639:ec78:6026/111'::inet),
+ ('ffff:9e86:79ea:f16e:df31:8e4d:7783:532e/88'::inet),
+ ('ffff:a0c7:82d3:24de:f762:6e1f:316d:3fb2/23'::inet),
+ ('ffff:fffa:ffff:ffff:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:fffb:ffff:ffff:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:fffc:ffff:ffff:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:fffd:ffff:ffff:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:fffe:ffff:ffff:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:fffa:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:fffb:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:fffc:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:fffd::/128'::inet),
+ ('ffff:ffff:ffff:fffd:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:fffe::/128'::inet),
+ ('ffff:ffff:ffff:fffe:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:ffff:4:3:2:0/24'::inet),
+ ('ffff:ffff:ffff:ffff:4:3:2:1/24'::inet),
+ ('ffff:ffff:ffff:ffff:4:3:2:2/24'::inet),
+ ('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/0'::inet),
+ ('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128'::inet)
+) AS i(a) ORDER BY a;
diff --git a/src/test/regress/sql/infinite_recurse.sql b/src/test/regress/sql/infinite_recurse.sql
new file mode 100644
index 0000000..151dba4
--- /dev/null
+++ b/src/test/regress/sql/infinite_recurse.sql
@@ -0,0 +1,29 @@
+-- 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;
+
+-- Unfortunately, up till mid 2020 the Linux kernel had a bug in PPC64
+-- signal handling that would cause this test to crash if it happened
+-- to receive an sinval catchup interrupt while the stack is deep:
+-- https://bugzilla.kernel.org/show_bug.cgi?id=205183
+-- It is likely to be many years before that bug disappears from all
+-- production kernels, so disable this test on such platforms.
+-- (We still create the function, so as not to have a cross-platform
+-- difference in the end state of the regression database.)
+
+SELECT version() ~ 'powerpc64[^,]*-linux-gnu'
+ AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+
+-- The full error report is not very stable, so we show only SQLSTATE
+-- and primary error message.
+
+\set VERBOSITY sqlstate
+
+select infinite_recurse();
+
+\echo :LAST_ERROR_MESSAGE
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
new file mode 100644
index 0000000..64173a8
--- /dev/null
+++ b/src/test/regress/sql/inherit.sql
@@ -0,0 +1,988 @@
+--
+-- 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;
+
+UPDATE a SET aa='zzzz' WHERE aa='aaaa';
+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';
+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 TEMP TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a);
+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
+alter table parent1 alter column f2 type bigint; -- ok
+
+-- 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;
+
+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;
+
+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;
+
+
+-- 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;
+
+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;
+
+-- 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;
+
+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;
+
+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');
+
+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;
+
+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);
+
+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;
+
+drop table matest0 cascade;
+
+--
+-- 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;
+
+--
+-- 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');
+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';
+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);
+
+-- MergeAppend must be used when a default partition exists
+explain (costs off) select * from mcrparted order by a, abs(b), c;
+
+drop table mcrparted_def;
+
+-- Append is used for a RANGE partitioned table with no default
+-- and no subpartitions
+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;
+
+-- 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;
+
+explain (costs off) select * from mcrparted order by a, abs(b), c;
+
+drop table mcrparted5_def;
+
+-- 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;
+
+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.
+explain (costs off) select * from mclparted order by a;
+
+-- 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);
+
+explain (costs off) select * from mclparted order by a;
+
+drop table mclparted;
+
+-- 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);
+
+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;
+
+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);
+
+explain (costs off) select * from bool_lp order by b;
+
+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);
+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;
+
+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);
+
+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;
+
+drop table range_parted;
+
+-- 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;
+
+-- 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;
diff --git a/src/test/regress/sql/init_privs.sql b/src/test/regress/sql/init_privs.sql
new file mode 100644
index 0000000..4a31af2
--- /dev/null
+++ b/src/test/regress/sql/init_privs.sql
@@ -0,0 +1,10 @@
+-- Test initial privileges
+
+-- There should always be some initial privileges, set up by initdb
+SELECT count(*) > 0 FROM pg_init_privs;
+
+-- Intentionally include some non-initial privs for pg_dump to dump out
+GRANT SELECT ON pg_proc TO CURRENT_USER;
+GRANT SELECT (prosrc) ON pg_proc TO CURRENT_USER;
+
+GRANT SELECT (rolname, rolsuper) ON pg_authid TO CURRENT_USER;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
new file mode 100644
index 0000000..bfaa8a3
--- /dev/null
+++ b/src/test/regress/sql/insert.sql
@@ -0,0 +1,626 @@
+--
+-- 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
+--
+CREATE 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;
+
+--
+-- 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
+
+-- 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 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 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/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
new file mode 100644
index 0000000..23d5778
--- /dev/null
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -0,0 +1,582 @@
+--
+-- 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 <table>.* 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;
+-- 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;
+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;
+
+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;
+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;
+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;
+
+-- 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;
+
+-- 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
+alter table parted_conflict_test drop b, 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();
+
+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;
+
+drop table parted_conflict;
+drop function parted_conflict_update_func();
diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql
new file mode 100644
index 0000000..2f5ea64
--- /dev/null
+++ b/src/test/regress/sql/int2.sql
@@ -0,0 +1,118 @@
+--
+-- INT2
+--
+
+CREATE TABLE INT2_TBL(f1 int2);
+
+INSERT INTO INT2_TBL(f1) VALUES ('0 ');
+
+INSERT INTO INT2_TBL(f1) VALUES (' 1234 ');
+
+INSERT INTO INT2_TBL(f1) VALUES (' -1234');
+
+INSERT INTO INT2_TBL(f1) VALUES ('34.5');
+
+-- largest and smallest values
+INSERT INTO INT2_TBL(f1) VALUES ('32767');
+
+INSERT INTO INT2_TBL(f1) VALUES ('-32767');
+
+-- bad input values -- should give errors
+INSERT INTO INT2_TBL(f1) VALUES ('100000');
+INSERT INTO INT2_TBL(f1) VALUES ('asdf');
+INSERT INTO INT2_TBL(f1) VALUES (' ');
+INSERT INTO INT2_TBL(f1) VALUES ('- 1234');
+INSERT INTO INT2_TBL(f1) VALUES ('4 444');
+INSERT INTO INT2_TBL(f1) VALUES ('123 dt');
+INSERT INTO INT2_TBL(f1) VALUES ('');
+
+
+SELECT * FROM INT2_TBL;
+
+SELECT * FROM INT2_TBL AS f(a, b);
+
+SELECT * FROM (TABLE int2_tbl) AS s (a, b);
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 <> int2 '0';
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 <> int4 '0';
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 = int2 '0';
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 = int4 '0';
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 < int2 '0';
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 < int4 '0';
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 <= int2 '0';
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 <= int4 '0';
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 > int2 '0';
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 > int4 '0';
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 >= int2 '0';
+
+SELECT i.* FROM INT2_TBL i WHERE i.f1 >= int4 '0';
+
+-- positive odds
+SELECT i.* FROM INT2_TBL i WHERE (i.f1 % int2 '2') = int2 '1';
+
+-- any evens
+SELECT i.* FROM INT2_TBL i WHERE (i.f1 % int4 '2') = int2 '0';
+
+SELECT i.f1, i.f1 * int2 '2' AS x FROM INT2_TBL i;
+
+SELECT i.f1, i.f1 * int2 '2' AS x FROM INT2_TBL i
+WHERE abs(f1) < 16384;
+
+SELECT i.f1, i.f1 * int4 '2' AS x FROM INT2_TBL i;
+
+SELECT i.f1, i.f1 + int2 '2' AS x FROM INT2_TBL i;
+
+SELECT i.f1, i.f1 + int2 '2' AS x FROM INT2_TBL i
+WHERE f1 < 32766;
+
+SELECT i.f1, i.f1 + int4 '2' AS x FROM INT2_TBL i;
+
+SELECT i.f1, i.f1 - int2 '2' AS x FROM INT2_TBL i;
+
+SELECT i.f1, i.f1 - int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+
+SELECT i.f1, i.f1 - int4 '2' AS x FROM INT2_TBL i;
+
+SELECT i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i;
+
+SELECT i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
+
+-- corner cases
+SELECT (-1::int2<<15)::text;
+SELECT ((-1::int2<<15)+1::int2)::text;
+
+-- check sane handling of INT16_MIN overflow cases
+SELECT (-32768)::int2 * (-1)::int2;
+SELECT (-32768)::int2 / (-1)::int2;
+SELECT (-32768)::int2 % (-1)::int2;
+
+-- check rounding when casting from float
+SELECT x, x::int2 AS int2_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::int2 AS int2_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);
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
new file mode 100644
index 0000000..55ec07a
--- /dev/null
+++ b/src/test/regress/sql/int4.sql
@@ -0,0 +1,178 @@
+--
+-- INT4
+--
+
+CREATE TABLE INT4_TBL(f1 int4);
+
+INSERT INTO INT4_TBL(f1) VALUES (' 0 ');
+
+INSERT INTO INT4_TBL(f1) VALUES ('123456 ');
+
+INSERT INTO INT4_TBL(f1) VALUES (' -123456');
+
+INSERT INTO INT4_TBL(f1) VALUES ('34.5');
+
+-- largest and smallest values
+INSERT INTO INT4_TBL(f1) VALUES ('2147483647');
+
+INSERT INTO INT4_TBL(f1) VALUES ('-2147483647');
+
+-- bad input values -- should give errors
+INSERT INTO INT4_TBL(f1) VALUES ('1000000000000');
+INSERT INTO INT4_TBL(f1) VALUES ('asdf');
+INSERT INTO INT4_TBL(f1) VALUES (' ');
+INSERT INTO INT4_TBL(f1) VALUES (' asdf ');
+INSERT INTO INT4_TBL(f1) VALUES ('- 1234');
+INSERT INTO INT4_TBL(f1) VALUES ('123 5');
+INSERT INTO INT4_TBL(f1) VALUES ('');
+
+
+SELECT * FROM INT4_TBL;
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int2 '0';
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int4 '0';
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 = int2 '0';
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 = int4 '0';
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 < int2 '0';
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 < int4 '0';
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 <= int2 '0';
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 <= int4 '0';
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 > int2 '0';
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 > int4 '0';
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 >= int2 '0';
+
+SELECT i.* FROM INT4_TBL i WHERE i.f1 >= int4 '0';
+
+-- positive odds
+SELECT i.* FROM INT4_TBL i WHERE (i.f1 % int2 '2') = int2 '1';
+
+-- any evens
+SELECT i.* FROM INT4_TBL i WHERE (i.f1 % int4 '2') = int2 '0';
+
+SELECT i.f1, i.f1 * int2 '2' AS x FROM INT4_TBL i;
+
+SELECT i.f1, i.f1 * int2 '2' AS x FROM INT4_TBL i
+WHERE abs(f1) < 1073741824;
+
+SELECT i.f1, i.f1 * int4 '2' AS x FROM INT4_TBL i;
+
+SELECT i.f1, i.f1 * int4 '2' AS x FROM INT4_TBL i
+WHERE abs(f1) < 1073741824;
+
+SELECT i.f1, i.f1 + int2 '2' AS x FROM INT4_TBL i;
+
+SELECT i.f1, i.f1 + int2 '2' AS x FROM INT4_TBL i
+WHERE f1 < 2147483646;
+
+SELECT i.f1, i.f1 + int4 '2' AS x FROM INT4_TBL i;
+
+SELECT i.f1, i.f1 + int4 '2' AS x FROM INT4_TBL i
+WHERE f1 < 2147483646;
+
+SELECT i.f1, i.f1 - int2 '2' AS x FROM INT4_TBL i;
+
+SELECT i.f1, i.f1 - int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT i.f1, i.f1 - int4 '2' AS x FROM INT4_TBL i;
+
+SELECT i.f1, i.f1 - int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT i.f1, i.f1 / int2 '2' AS x FROM INT4_TBL i;
+
+SELECT i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
+
+--
+-- more complex expressions
+--
+
+-- variations on unary minus parsing
+SELECT -2+3 AS one;
+
+SELECT 4-2 AS two;
+
+SELECT 2- -1 AS three;
+
+SELECT 2 - -2 AS four;
+
+SELECT int2 '2' * int2 '2' = int2 '16' / int2 '4' AS true;
+
+SELECT int4 '2' * int2 '2' = int2 '16' / int4 '4' AS true;
+
+SELECT int2 '2' * int4 '2' = int4 '16' / int2 '4' AS true;
+
+SELECT int4 '1000' < int4 '999' AS false;
+
+SELECT 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 AS ten;
+
+SELECT 2 + 2 / 2 AS three;
+
+SELECT (2 + 2) / 2 AS two;
+
+-- corner case
+SELECT (-1::int4<<31)::text;
+SELECT ((-1::int4<<31)+1)::text;
+
+-- check sane handling of INT_MIN overflow cases
+SELECT (-2147483648)::int4 * (-1)::int4;
+SELECT (-2147483648)::int4 / (-1)::int4;
+SELECT (-2147483648)::int4 % (-1)::int4;
+SELECT (-2147483648)::int4 * (-1)::int2;
+SELECT (-2147483648)::int4 / (-1)::int2;
+SELECT (-2147483648)::int4 % (-1)::int2;
+
+-- check rounding when casting from float
+SELECT x, x::int4 AS int4_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::int4 AS int4_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::int4, 0::int4),
+ (0::int4, 6410818::int4),
+ (61866666::int4, 6410818::int4),
+ (-61866666::int4, 6410818::int4),
+ ((-2147483648)::int4, 1::int4),
+ ((-2147483648)::int4, 2147483647::int4),
+ ((-2147483648)::int4, 1073741824::int4)) AS v(a, b);
+
+SELECT gcd((-2147483648)::int4, 0::int4); -- overflow
+SELECT gcd((-2147483648)::int4, (-2147483648)::int4); -- overflow
+
+-- test lcm()
+SELECT a, b, lcm(a, b), lcm(a, -b), lcm(b, a), lcm(-b, a)
+FROM (VALUES (0::int4, 0::int4),
+ (0::int4, 42::int4),
+ (42::int4, 42::int4),
+ (330::int4, 462::int4),
+ (-330::int4, 462::int4),
+ ((-2147483648)::int4, 0::int4)) AS v(a, b);
+
+SELECT lcm((-2147483648)::int4, 1::int4); -- overflow
+SELECT lcm(2147483647::int4, 2147483646::int4); -- overflow
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
new file mode 100644
index 0000000..32940b4
--- /dev/null
+++ b/src/test/regress/sql/int8.sql
@@ -0,0 +1,252 @@
+--
+-- INT8
+-- Test int8 64-bit integers.
+--
+CREATE TABLE INT8_TBL(q1 int8, q2 int8);
+
+INSERT INTO INT8_TBL VALUES(' 123 ',' 456');
+INSERT INTO INT8_TBL VALUES('123 ','4567890123456789');
+INSERT INTO INT8_TBL VALUES('4567890123456789','123');
+INSERT INTO INT8_TBL VALUES(+4567890123456789,'4567890123456789');
+INSERT INTO INT8_TBL VALUES('+4567890123456789','-4567890123456789');
+
+-- bad inputs
+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;
+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/interval.sql b/src/test/regress/sql/interval.sql
new file mode 100644
index 0000000..6d53239
--- /dev/null
+++ b/src/test/regress/sql/interval.sql
@@ -0,0 +1,357 @@
+--
+-- INTERVAL
+--
+
+SET DATESTYLE = 'ISO';
+SET IntervalStyle to postgres;
+
+-- check acceptance of "time zone style"
+SELECT INTERVAL '01:00' AS "One hour";
+SELECT INTERVAL '+02:00' AS "Two hours";
+SELECT INTERVAL '-08:00' AS "Eight hours";
+SELECT INTERVAL '-1 +02:03' AS "22 hours ago...";
+SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
+SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
+SELECT INTERVAL '1.5 months' AS "One month 15 days";
+SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+
+CREATE TABLE INTERVAL_TBL (f1 interval);
+
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 10 day');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 34 year');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 3 months');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 14 seconds ago');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+
+-- badly formatted interval
+INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 30 eons ago');
+
+-- test interval operators
+
+SELECT * FROM INTERVAL_TBL;
+
+SELECT * FROM INTERVAL_TBL
+ WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
+
+SELECT * FROM INTERVAL_TBL
+ WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
+
+SELECT * FROM INTERVAL_TBL
+ WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
+
+SELECT * FROM INTERVAL_TBL
+ WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
+
+SELECT * FROM INTERVAL_TBL
+ WHERE INTERVAL_TBL.f1 >= interval '@ 1 month';
+
+SELECT * FROM INTERVAL_TBL
+ WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
+
+SELECT r1.*, r2.*
+ FROM INTERVAL_TBL r1, INTERVAL_TBL r2
+ WHERE r1.f1 > r2.f1
+ ORDER BY r1.f1, r2.f1;
+
+-- Test intervals that are large enough to overflow 64 bits in comparisons
+CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
+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');
+-- these should fail as out-of-range
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 years');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
+
+-- Test edge-case overflow detection in interval multiplication
+select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
+
+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;
+
+-- Test multiplication and division with intervals.
+-- Floating point arithmetic rounding errors can lead to unexpected results,
+-- though the code attempts to do the right thing and round up to days and
+-- minutes to avoid results such as '3 days 24:00 hours' or '14:20:60'.
+-- Note that it is expected for some day components to be greater than 29 and
+-- some time components be greater than 23:59:59 due to how intervals are
+-- stored internally.
+
+CREATE TABLE INTERVAL_MULDIV_TBL (span interval);
+COPY INTERVAL_MULDIV_TBL FROM STDIN;
+41 mon 12 days 360:00
+-41 mon -12 days +360:00
+-12 days
+9 mon -27 days 12:34:56
+-3 years 482 days 76:54:32.189
+4 mon
+14 mon
+999 mon 999 days
+\.
+
+SELECT span * 0.3 AS product
+FROM INTERVAL_MULDIV_TBL;
+
+SELECT span * 8.2 AS product
+FROM INTERVAL_MULDIV_TBL;
+
+SELECT span / 10 AS quotient
+FROM INTERVAL_MULDIV_TBL;
+
+SELECT span / 100 AS quotient
+FROM INTERVAL_MULDIV_TBL;
+
+DROP TABLE INTERVAL_MULDIV_TBL;
+
+SET DATESTYLE = 'postgres';
+SET IntervalStyle to postgres_verbose;
+
+SELECT * FROM INTERVAL_TBL;
+
+-- test avg(interval), which is somewhat fragile since people have been
+-- known to change the allowed input syntax for type interval without
+-- updating pg_aggregate.agginitval
+
+select avg(f1) from interval_tbl;
+
+-- test long interval input
+select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
+
+-- test long interval output
+-- Note: the actual maximum length of the interval output is longer,
+-- but we need the test to work for both integer and floating-point
+-- timestamps.
+select '100000000y 10mon -1000000000d -100000h -10min -10.000001s ago'::interval;
+
+-- test justify_hours() and justify_days()
+
+SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as "6 mons 5 days 4 hours 3 mins 2 seconds";
+SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
+
+-- test justify_interval()
+
+SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
+
+-- test fractional second input, and detection of duplicate units
+SET DATESTYLE = 'ISO';
+SET IntervalStyle TO postgres;
+
+SELECT '1 millisecond'::interval, '1 microsecond'::interval,
+ '500 seconds 99 milliseconds 51 microseconds'::interval;
+SELECT '3 days 5 milliseconds'::interval;
+
+SELECT '1 second 2 seconds'::interval; -- error
+SELECT '10 milliseconds 20 milliseconds'::interval; -- error
+SELECT '5.5 seconds 3 milliseconds'::interval; -- error
+SELECT '1:20:05 5 microseconds'::interval; -- error
+SELECT '1 day 1 day'::interval; -- error
+SELECT interval '1-2'; -- SQL year-month literal
+SELECT interval '999' second; -- oversize leading field is ok
+SELECT interval '999' minute;
+SELECT interval '999' hour;
+SELECT interval '999' day;
+SELECT interval '999' month;
+
+-- test SQL-spec syntaxes for restricted field sets
+SELECT interval '1' year;
+SELECT interval '2' month;
+SELECT interval '3' day;
+SELECT interval '4' hour;
+SELECT interval '5' minute;
+SELECT interval '6' second;
+SELECT interval '1' year to month;
+SELECT interval '1-2' year to month;
+SELECT interval '1 2' day to hour;
+SELECT interval '1 2:03' day to hour;
+SELECT interval '1 2:03:04' day to hour;
+SELECT interval '1 2' day to minute;
+SELECT interval '1 2:03' day to minute;
+SELECT interval '1 2:03:04' day to minute;
+SELECT interval '1 2' day to second;
+SELECT interval '1 2:03' day to second;
+SELECT interval '1 2:03:04' day to second;
+SELECT interval '1 2' hour to minute;
+SELECT interval '1 2:03' hour to minute;
+SELECT interval '1 2:03:04' hour to minute;
+SELECT interval '1 2' hour to second;
+SELECT interval '1 2:03' hour to second;
+SELECT interval '1 2:03:04' hour to second;
+SELECT interval '1 2' minute to second;
+SELECT interval '1 2:03' minute to second;
+SELECT interval '1 2:03:04' minute to second;
+SELECT interval '1 +2:03' minute to second;
+SELECT interval '1 +2:03:04' minute to second;
+SELECT interval '1 -2:03' minute to second;
+SELECT interval '1 -2:03:04' minute to second;
+SELECT interval '123 11' day to hour; -- ok
+SELECT interval '123 11' day; -- not ok
+SELECT interval '123 11'; -- not ok, too ambiguous
+SELECT interval '123 2:03 -2:04'; -- not ok, redundant hh:mm fields
+
+-- test syntaxes for restricted precision
+SELECT interval(0) '1 day 01:23:45.6789';
+SELECT interval(2) '1 day 01:23:45.6789';
+SELECT interval '12:34.5678' minute to second(2); -- per SQL spec
+SELECT interval '1.234' second;
+SELECT interval '1.234' second(2);
+SELECT interval '1 2.345' day to second(2);
+SELECT interval '1 2:03' day to second(2);
+SELECT interval '1 2:03.4567' day to second(2);
+SELECT interval '1 2:03:04.5678' day to second(2);
+SELECT interval '1 2.345' hour to second(2);
+SELECT interval '1 2:03.45678' hour to second(2);
+SELECT interval '1 2:03:04.5678' hour to second(2);
+SELECT interval '1 2.3456' minute to second(2);
+SELECT interval '1 2:03.5678' minute to second(2);
+SELECT interval '1 2:03:04.5678' minute to second(2);
+
+-- test casting to restricted precision (bug #14479)
+SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
+ (f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years"
+ FROM interval_tbl;
+
+-- test inputting and outputting SQL standard interval literals
+SET IntervalStyle TO sql_standard;
+SELECT interval '0' AS "zero",
+ interval '1-2' year to month AS "year-month",
+ interval '1 2:03:04' day to second AS "day-time",
+ - interval '1-2' AS "negative year-month",
+ - interval '1 2:03:04' AS "negative day-time";
+
+-- test input of some not-quite-standard interval values in the sql style
+SET IntervalStyle TO postgres;
+SELECT interval '+1 -1:00:00',
+ interval '-1 +1:00:00',
+ interval '+1-2 -3 +4:05:06.789',
+ interval '-1-2 +3 -4:05:06.789';
+
+-- test output of couple non-standard interval values in the sql style
+SET IntervalStyle TO sql_standard;
+SELECT interval '1 day -1 hours',
+ interval '-1 days +1 hours',
+ interval '1 years 2 months -3 days 4 hours 5 minutes 6.789 seconds',
+ - interval '1 years 2 months -3 days 4 hours 5 minutes 6.789 seconds';
+
+-- test outputting iso8601 intervals
+SET IntervalStyle to iso_8601;
+select interval '0' AS "zero",
+ interval '1-2' AS "a year 2 months",
+ interval '1 2:03:04' AS "a bit over a day",
+ interval '2:03:04.45679' AS "a bit over 2 hours",
+ (interval '1-2' + interval '3 4:05:06.7') AS "all fields",
+ (interval '1-2' - interval '3 4:05:06.7') AS "mixed sign",
+ (- interval '1-2' + interval '3 4:05:06.7') AS "negative";
+
+-- test inputting ISO 8601 4.4.2.1 "Format With Time Unit Designators"
+SET IntervalStyle to sql_standard;
+select interval 'P0Y' AS "zero",
+ interval 'P1Y2M' AS "a year 2 months",
+ interval 'P1W' AS "a week",
+ interval 'P1DT2H3M4S' AS "a bit over a day",
+ interval 'P1Y2M3DT4H5M6.7S' AS "all fields",
+ interval 'P-1Y-2M-3DT-4H-5M-6.7S' AS "negative",
+ interval 'PT-0.1S' AS "fractional second";
+
+-- test inputting ISO 8601 4.4.2.2 "Alternative Format"
+SET IntervalStyle to postgres;
+select interval 'P00021015T103020' AS "ISO8601 Basic Format",
+ interval 'P0002-10-15T10:30:20' AS "ISO8601 Extended Format";
+
+-- Make sure optional ISO8601 alternative format fields are optional.
+select interval 'P0002' AS "year only",
+ interval 'P0002-10' AS "year month",
+ interval 'P0002-10-15' AS "year month day",
+ interval 'P0002T1S' AS "year only plus time",
+ interval 'P0002-10T1S' AS "year month plus time",
+ interval 'P0002-10-15T1S' AS "year month day plus time",
+ interval 'PT10' AS "hour only",
+ interval 'PT10:30' AS "hour minute";
+
+-- test a couple rounding cases that changed since 8.3 w/ HAVE_INT64_TIMESTAMP.
+SET IntervalStyle to postgres_verbose;
+select interval '-10 mons -3 days +03:55:06.70';
+select interval '1 year 2 mons 3 days 04:05:06.699999';
+select interval '0:0:0.7', interval '@ 0.70 secs', interval '0.7 seconds';
+
+-- check that '30 days' equals '1 month' according to the hash function
+select '30 days'::interval = '1 month'::interval as t;
+select interval_hash('30 days'::interval) = interval_hash('1 month'::interval) as t;
+
+-- numeric constructor
+select make_interval(years := 2);
+select make_interval(years := 1, months := 6);
+select make_interval(years := 1, months := -1, weeks := 5, days := -7, hours := 25, mins := -180);
+
+select make_interval() = make_interval(years := 0, months := 0, weeks := 0, days := 0, mins := 0, secs := 0.0);
+select make_interval(hours := -2, mins := -10, secs := -25.3);
+
+select make_interval(years := 'inf'::float::int);
+select make_interval(months := 'NaN'::float::int);
+select make_interval(secs := 'inf');
+select make_interval(secs := 'NaN');
+select make_interval(secs := 7e12);
+
+--
+-- test EXTRACT
+--
+SELECT f1,
+ EXTRACT(MICROSECOND FROM f1) AS MICROSECOND,
+ EXTRACT(MILLISECOND FROM f1) AS MILLISECOND,
+ EXTRACT(SECOND FROM f1) AS SECOND,
+ EXTRACT(MINUTE FROM f1) AS MINUTE,
+ EXTRACT(HOUR FROM f1) AS HOUR,
+ EXTRACT(DAY FROM f1) AS DAY,
+ EXTRACT(MONTH FROM f1) AS MONTH,
+ EXTRACT(QUARTER FROM f1) AS QUARTER,
+ EXTRACT(YEAR FROM f1) AS YEAR,
+ EXTRACT(DECADE FROM f1) AS DECADE,
+ EXTRACT(CENTURY FROM f1) AS CENTURY,
+ EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
+ EXTRACT(EPOCH FROM f1) AS EPOCH
+ FROM INTERVAL_TBL;
+
+SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
+SELECT EXTRACT(TIMEZONE FROM INTERVAL '2 days'); -- error
+
+SELECT EXTRACT(DECADE FROM INTERVAL '100 y');
+SELECT EXTRACT(DECADE FROM INTERVAL '99 y');
+SELECT EXTRACT(DECADE FROM INTERVAL '-99 y');
+SELECT EXTRACT(DECADE FROM INTERVAL '-100 y');
+
+SELECT EXTRACT(CENTURY FROM INTERVAL '100 y');
+SELECT EXTRACT(CENTURY FROM INTERVAL '99 y');
+SELECT EXTRACT(CENTURY FROM INTERVAL '-99 y');
+SELECT EXTRACT(CENTURY FROM INTERVAL '-100 y');
+
+-- date_part implementation is mostly the same as extract, so only
+-- test a few cases for additional coverage.
+SELECT f1,
+ date_part('microsecond', f1) AS microsecond,
+ date_part('millisecond', f1) AS millisecond,
+ date_part('second', f1) AS second,
+ date_part('epoch', f1) AS epoch
+ FROM INTERVAL_TBL;
+
+-- internal overflow test case
+SELECT extract(epoch from interval '1000000000 days');
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
new file mode 100644
index 0000000..eb24a72
--- /dev/null
+++ b/src/test/regress/sql/join.sql
@@ -0,0 +1,2325 @@
+--
+-- 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);
+
+-- useful in some tests below
+create temp table onerow();
+insert into onerow default values;
+analyze onerow;
+
+
+--
+-- CORRELATION NAMES
+-- Make sure that table/column aliases are supported
+-- before diving into more complex join syntax.
+--
+
+SELECT *
+ FROM J1_TBL AS tx;
+
+SELECT *
+ FROM J1_TBL tx;
+
+SELECT *
+ FROM J1_TBL AS t1 (a, b, c);
+
+SELECT *
+ FROM J1_TBL t1 (a, b, c);
+
+SELECT *
+ FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e);
+
+SELECT t1.a, t2.e
+ FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e)
+ WHERE t1.a = t2.d;
+
+
+--
+-- CROSS JOIN
+-- Qualifications are not allowed on cross joins,
+-- which degenerate into a standard unqualified inner join.
+--
+
+SELECT *
+ FROM J1_TBL CROSS JOIN J2_TBL;
+
+-- ambiguous column
+SELECT i, k, t
+ FROM J1_TBL CROSS JOIN J2_TBL;
+
+-- resolve previous ambiguity by specifying the table name
+SELECT t1.i, k, t
+ FROM J1_TBL t1 CROSS JOIN J2_TBL t2;
+
+SELECT ii, tt, kk
+ FROM (J1_TBL CROSS JOIN J2_TBL)
+ AS tx (ii, jj, tt, ii2, kk);
+
+SELECT 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);
+
+SELECT *
+ FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b;
+
+
+--
+--
+-- 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 *
+ FROM J1_TBL INNER JOIN J2_TBL USING (i);
+
+-- Same as above, slightly different syntax
+SELECT *
+ FROM J1_TBL JOIN J2_TBL USING (i);
+
+SELECT *
+ FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, d) USING (a)
+ ORDER BY a, d;
+
+SELECT *
+ FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, b) USING (b)
+ ORDER BY b, t1.a;
+
+-- test join using aliases
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) WHERE J1_TBL.t = 'one'; -- ok
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one'; -- ok
+SELECT * FROM (J1_TBL JOIN J2_TBL USING (i)) AS x WHERE J1_TBL.t = 'one'; -- error
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.i = 1; -- ok
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.t = 'one'; -- error
+SELECT * FROM (J1_TBL JOIN J2_TBL USING (i) AS x) AS xx WHERE x.i = 1; -- error (XXX could use better hint)
+SELECT * FROM J1_TBL a1 JOIN J2_TBL a2 USING (i) AS a1; -- error
+SELECT x.* FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one';
+SELECT ROW(x.*) FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one';
+SELECT row_to_json(x.*) FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one';
+
+--
+-- NATURAL JOIN
+-- Inner equi-join on all columns with the same name
+--
+
+SELECT *
+ FROM J1_TBL NATURAL JOIN J2_TBL;
+
+SELECT *
+ FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (a, d);
+
+SELECT *
+ FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
+
+-- mismatch number of columns
+-- currently, Postgres will fill in with underlying names
+SELECT *
+ FROM J1_TBL t1 (a, b) NATURAL JOIN J2_TBL t2 (a);
+
+
+--
+-- Inner joins (equi-joins)
+--
+
+SELECT *
+ FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.i);
+
+SELECT *
+ FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k);
+
+
+--
+-- Non-equi-joins
+--
+
+SELECT *
+ FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i <= J2_TBL.k);
+
+
+--
+-- Outer joins
+-- Note that OUTER is a noise word
+--
+
+SELECT *
+ FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i)
+ ORDER BY i, k, t;
+
+SELECT *
+ FROM J1_TBL LEFT JOIN J2_TBL USING (i)
+ ORDER BY i, k, t;
+
+SELECT *
+ FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i);
+
+SELECT *
+ FROM J1_TBL RIGHT JOIN J2_TBL USING (i);
+
+SELECT *
+ FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i)
+ ORDER BY i, k, t;
+
+SELECT *
+ FROM J1_TBL FULL JOIN J2_TBL USING (i)
+ ORDER BY i, k, t;
+
+SELECT *
+ FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (k = 1);
+
+SELECT *
+ FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (i = 1);
+
+--
+-- 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);
+
+--
+-- 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);
+
+SELECT * FROM
+(SELECT * FROM t2) as s2
+LEFT JOIN
+(SELECT * FROM t3) s3
+USING (name);
+
+SELECT * FROM
+(SELECT * FROM t2) as s2
+FULL JOIN
+(SELECT * FROM t3) s3
+USING (name);
+
+-- 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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+-- Constants as join keys can also be problematic
+SELECT * FROM
+ (SELECT name, n as s1_n FROM t1) as s1
+FULL JOIN
+ (SELECT name, 2 as s2_n FROM t2) as s2
+ON (s1_n = s2_n);
+
+
+-- 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;
+select * from y;
+
+select * from x left join y on (x1 = y1 and x2 is not null);
+select * from x left join y on (x1 = y1 and y2 is not null);
+
+select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)
+on (x1 = xx1);
+select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)
+on (x1 = xx1 and x2 is not null);
+select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)
+on (x1 = xx1 and y2 is not null);
+select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)
+on (x1 = xx1 and xx2 is not null);
+-- 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);
+select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)
+on (x1 = xx1) where (y2 is not null);
+select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)
+on (x1 = xx1) where (xx2 is not null);
+
+--
+-- 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;
+
+--
+-- regression test: be sure we cope with proven-dummy append rels
+--
+explain (costs off)
+select aa, bb, unique1, unique1
+ from tenk1 right join b on aa = unique1
+ where bb < bb and bb is null;
+
+select aa, bb, unique1, unique1
+ from tenk1 right join b on aa = unique1
+ where bb < bb and bb is null;
+
+--
+-- regression test: check handling of empty-FROM subquery underneath outer join
+--
+explain (costs off)
+select * from int8_tbl i1 left join (int8_tbl i2 join
+ (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2
+order by 1, 2;
+
+select * from int8_tbl i1 left join (int8_tbl i2 join
+ (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2
+order by 1, 2;
+
+--
+-- regression test: check a case where join_clause_is_movable_into() gives
+-- an imprecise result, causing an assertion failure
+--
+select count(*)
+from
+ (select t3.tenthous as x1, coalesce(t1.stringu1, t2.stringu1) as x2
+ from tenk1 t1
+ left join tenk1 t2 on t1.unique1 = t2.unique1
+ join tenk1 t3 on t1.unique2 = t3.unique2) ss,
+ tenk1 t4,
+ tenk1 t5
+where t4.thousand = t5.unique1 and ss.x1 = t4.tenthous and ss.x2 = t5.stringu1;
+
+--
+-- regression test: check a case where we formerly missed including an EC
+-- enforcement clause because it was expected to be handled at scan level
+--
+explain (costs off)
+select a.f1, b.f1, t.thousand, t.tenthous from
+ tenk1 t,
+ (select sum(f1)+1 as f1 from int4_tbl i4a) a,
+ (select sum(f1) as f1 from int4_tbl i4b) b
+where b.f1 = t.thousand and a.f1 = b.f1 and (a.f1+b.f1+999) = t.tenthous;
+
+select a.f1, b.f1, t.thousand, t.tenthous from
+ tenk1 t,
+ (select sum(f1)+1 as f1 from int4_tbl i4a) a,
+ (select sum(f1) as f1 from int4_tbl i4b) b
+where b.f1 = t.thousand and a.f1 = b.f1 and (a.f1+b.f1+999) = t.tenthous;
+
+--
+-- check a case where we formerly got confused by conflicting sort orders
+-- in redundant merge join path keys
+--
+explain (costs off)
+select * from
+ j1_tbl full join
+ (select * from j2_tbl order by j2_tbl.i desc, j2_tbl.k asc) j2_tbl
+ on j1_tbl.i = j2_tbl.i and j1_tbl.i = j2_tbl.k;
+
+select * from
+ j1_tbl full join
+ (select * from j2_tbl order by j2_tbl.i desc, j2_tbl.k asc) j2_tbl
+ on j1_tbl.i = j2_tbl.i and j1_tbl.i = j2_tbl.k;
+
+--
+-- a different check for handling of redundant sort keys in merge joins
+--
+explain (costs off)
+select count(*) from
+ (select * from tenk1 x order by x.thousand, x.twothousand, x.fivethous) x
+ left join
+ (select * from tenk1 y order by y.unique2) y
+ on x.thousand = y.unique2 and x.twothousand = y.hundred and x.fivethous = y.unique2;
+
+select count(*) from
+ (select * from tenk1 x order by x.thousand, x.twothousand, x.fivethous) x
+ left join
+ (select * from tenk1 y order by y.unique2) y
+ on x.thousand = y.unique2 and x.twothousand = y.hundred and x.fivethous = y.unique2;
+
+
+--
+-- Clean up
+--
+
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
+
+DROP TABLE J1_TBL;
+DROP TABLE J2_TBL;
+
+-- Both DELETE and UPDATE allow the specification of additional tables
+-- to "join" against to determine which rows should be modified.
+
+CREATE TEMP TABLE t1 (a int, b int);
+CREATE TEMP TABLE t2 (a int, b int);
+CREATE TEMP TABLE t3 (x int, y int);
+
+INSERT INTO t1 VALUES (5, 10);
+INSERT INTO t1 VALUES (15, 20);
+INSERT INTO t1 VALUES (100, 100);
+INSERT INTO t1 VALUES (200, 1000);
+INSERT INTO t2 VALUES (200, 2000);
+INSERT INTO t3 VALUES (5, 20);
+INSERT INTO t3 VALUES (6, 7);
+INSERT INTO t3 VALUES (7, 8);
+INSERT INTO t3 VALUES (500, 100);
+
+DELETE FROM t3 USING t1 table1 WHERE t3.x = table1.a;
+SELECT * FROM t3;
+DELETE FROM t3 USING t1 JOIN t2 USING (a) WHERE t3.x > t1.a;
+SELECT * FROM t3;
+DELETE FROM t3 USING t3 t3_other WHERE t3.x = t3_other.x AND t3.y = t3_other.y;
+SELECT * FROM t3;
+
+-- Test join against inheritance tree
+
+create temp table t2a () inherits (t2);
+
+insert into t2a values (200, 2001);
+
+select * from t1 left join t2 on (t1.a = t2.a);
+
+-- Test matching of column name with wrong alias
+
+select t1.x from t1 join t3 on (t1.a = t3.x);
+
+-- Test matching of locking clause with wrong alias
+
+select t1.*, t2.*, unnamed_join.* from
+ t1 join t2 on (t1.a = t2.a), t3 as unnamed_join
+ for update of unnamed_join;
+
+select foo.*, unnamed_join.* from
+ t1 join t2 using (a) as foo, t3 as unnamed_join
+ for update of unnamed_join;
+
+select foo.*, unnamed_join.* from
+ t1 join t2 using (a) as foo, t3 as unnamed_join
+ for update of foo;
+
+select bar.*, unnamed_join.* from
+ (t1 join t2 using (a) as foo) as bar, t3 as unnamed_join
+ for update of foo;
+
+select bar.*, unnamed_join.* from
+ (t1 join t2 using (a) as foo) as bar, t3 as unnamed_join
+ for update of bar;
+
+--
+-- regression test for 8.1 merge right join bug
+--
+
+CREATE TEMP TABLE tt1 ( tt1_id int4, joincol int4 );
+INSERT INTO tt1 VALUES (1, 11);
+INSERT INTO tt1 VALUES (2, NULL);
+
+CREATE TEMP TABLE tt2 ( tt2_id int4, joincol int4 );
+INSERT INTO tt2 VALUES (21, 11);
+INSERT INTO tt2 VALUES (22, 11);
+
+set enable_hashjoin to off;
+set enable_nestloop to off;
+
+-- these should give the same results
+
+select tt1.*, tt2.* from tt1 left join tt2 on tt1.joincol = tt2.joincol;
+
+select tt1.*, tt2.* from tt2 right join tt1 on tt1.joincol = tt2.joincol;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+
+--
+-- regression test for bug #13908 (hash join with skew tuples & nbatch increase)
+--
+
+set work_mem to '64kB';
+set enable_mergejoin to off;
+set enable_memoize to off;
+
+explain (costs off)
+select count(*) from tenk1 a, tenk1 b
+ where a.hundred = b.thousand and (b.fivethous % 10) < 10;
+select count(*) from tenk1 a, tenk1 b
+ where a.hundred = b.thousand and (b.fivethous % 10) < 10;
+
+reset work_mem;
+reset enable_mergejoin;
+reset enable_memoize;
+
+--
+-- regression test for 8.2 bug with improper re-ordering of left joins
+--
+
+create temp table tt3(f1 int, f2 text);
+insert into tt3 select x, repeat('xyzzy', 100) from generate_series(1,10000) x;
+create index tt3i on tt3(f1);
+analyze tt3;
+
+create temp table tt4(f1 int);
+insert into tt4 values (0),(1),(9999);
+analyze tt4;
+
+SELECT a.f1
+FROM tt4 a
+LEFT JOIN (
+ SELECT b.f1
+ FROM tt3 b LEFT JOIN tt3 c ON (b.f1 = c.f1)
+ WHERE c.f1 IS NULL
+) AS d ON (a.f1 = d.f1)
+WHERE d.f1 IS NULL;
+
+--
+-- regression test for proper handling of outer joins within antijoins
+--
+
+create temp table tt4x(c1 int, c2 int, c3 int);
+
+explain (costs off)
+select * from tt4x t1
+where not exists (
+ select 1 from tt4x t2
+ left join tt4x t3 on t2.c3 = t3.c1
+ left join ( select t5.c1 as c1
+ from tt4x t4 left join tt4x t5 on t4.c2 = t5.c1
+ ) a1 on t3.c2 = a1.c1
+ where t1.c1 = t2.c2
+);
+
+--
+-- regression test for problems of the sort depicted in bug #3494
+--
+
+create temp table tt5(f1 int, f2 int);
+create temp table tt6(f1 int, f2 int);
+
+insert into tt5 values(1, 10);
+insert into tt5 values(1, 11);
+
+insert into tt6 values(1, 9);
+insert into tt6 values(1, 2);
+insert into tt6 values(2, 9);
+
+select * from tt5,tt6 where tt5.f1 = tt6.f1 and tt5.f1 = tt5.f2 - tt6.f2;
+
+--
+-- regression test for problems of the sort depicted in bug #3588
+--
+
+create temp table xx (pkxx int);
+create temp table yy (pkyy int, pkxx int);
+
+insert into xx values (1);
+insert into xx values (2);
+insert into xx values (3);
+
+insert into yy values (101, 1);
+insert into yy values (201, 2);
+insert into yy values (301, NULL);
+
+select yy.pkyy as yy_pkyy, yy.pkxx as yy_pkxx, yya.pkyy as yya_pkyy,
+ xxa.pkxx as xxa_pkxx, xxb.pkxx as xxb_pkxx
+from yy
+ left join (SELECT * FROM yy where pkyy = 101) as yya ON yy.pkyy = yya.pkyy
+ left join xx xxa on yya.pkxx = xxa.pkxx
+ left join xx xxb on coalesce (xxa.pkxx, 1) = xxb.pkxx;
+
+--
+-- regression test for improper pushing of constants across outer-join clauses
+-- (as seen in early 8.2.x releases)
+--
+
+create temp table zt1 (f1 int primary key);
+create temp table zt2 (f2 int primary key);
+create temp table zt3 (f3 int primary key);
+insert into zt1 values(53);
+insert into zt2 values(53);
+
+select * from
+ zt2 left join zt3 on (f2 = f3)
+ left join zt1 on (f3 = f1)
+where f2 = 53;
+
+create temp view zv1 as select *,'dummy'::text AS junk from zt1;
+
+select * from
+ zt2 left join zt3 on (f2 = f3)
+ left join zv1 on (f3 = f1)
+where f2 = 53;
+
+--
+-- regression test for improper extraction of OR indexqual conditions
+-- (as seen in early 8.3.x releases)
+--
+
+select a.unique2, a.ten, b.tenthous, b.unique2, b.hundred
+from tenk1 a left join tenk1 b on a.unique2 = b.tenthous
+where a.unique1 = 42 and
+ ((b.unique2 is null and a.ten = 2) or b.hundred = 3);
+
+--
+-- test proper positioning of one-time quals in EXISTS (8.4devel bug)
+--
+prepare foo(bool) as
+ select count(*) from tenk1 a left join tenk1 b
+ on (a.unique2 = b.unique1 and exists
+ (select 1 from tenk1 c where c.thousand = b.unique2 and $1));
+execute foo(true);
+execute foo(false);
+
+--
+-- test for sane behavior with noncanonical merge clauses, per bug #4926
+--
+
+begin;
+
+set enable_mergejoin = 1;
+set enable_hashjoin = 0;
+set enable_nestloop = 0;
+
+create temp table a (i integer);
+create temp table b (x integer, y integer);
+
+select * from a left join b on i = x and i = y and x = i;
+
+rollback;
+
+--
+-- test handling of merge clauses using record_ops
+--
+begin;
+
+create type mycomptype as (id int, v bigint);
+
+create temp table tidv (idv mycomptype);
+create index on tidv (idv);
+
+explain (costs off)
+select a.idv, b.idv from tidv a, tidv b where a.idv = b.idv;
+
+set enable_mergejoin = 0;
+set enable_hashjoin = 0;
+
+explain (costs off)
+select a.idv, b.idv from tidv a, tidv b where a.idv = b.idv;
+
+rollback;
+
+--
+-- test NULL behavior of whole-row Vars, per bug #5025
+--
+select t1.q2, count(t2.*)
+from int8_tbl t1 left join int8_tbl t2 on (t1.q2 = t2.q1)
+group by t1.q2 order by 1;
+
+select t1.q2, count(t2.*)
+from int8_tbl t1 left join (select * from int8_tbl) t2 on (t1.q2 = t2.q1)
+group by t1.q2 order by 1;
+
+select t1.q2, count(t2.*)
+from int8_tbl t1 left join (select * from int8_tbl offset 0) t2 on (t1.q2 = t2.q1)
+group by t1.q2 order by 1;
+
+select t1.q2, count(t2.*)
+from int8_tbl t1 left join
+ (select q1, case when q2=1 then 1 else q2 end as q2 from int8_tbl) t2
+ on (t1.q2 = t2.q1)
+group by t1.q2 order by 1;
+
+--
+-- test incorrect failure to NULL pulled-up subexpressions
+--
+begin;
+
+create temp table a (
+ code char not null,
+ constraint a_pk primary key (code)
+);
+create temp table b (
+ a char not null,
+ num integer not null,
+ constraint b_pk primary key (a, num)
+);
+create temp table c (
+ name char not null,
+ a char,
+ constraint c_pk primary key (name)
+);
+
+insert into a (code) values ('p');
+insert into a (code) values ('q');
+insert into b (a, num) values ('p', 1);
+insert into b (a, num) values ('p', 2);
+insert into c (name, a) values ('A', 'p');
+insert into c (name, a) values ('B', 'q');
+insert into c (name, a) values ('C', null);
+
+select c.name, ss.code, ss.b_cnt, ss.const
+from c left join
+ (select a.code, coalesce(b_grp.cnt, 0) as b_cnt, -1 as const
+ from a left join
+ (select count(1) as cnt, b.a from b group by b.a) as b_grp
+ on a.code = b_grp.a
+ ) as ss
+ on (c.a = ss.code)
+order by c.name;
+
+rollback;
+
+--
+-- test incorrect handling of placeholders that only appear in targetlists,
+-- per bug #6154
+--
+SELECT * FROM
+( SELECT 1 as key1 ) sub1
+LEFT JOIN
+( SELECT sub3.key3, sub4.value2, COALESCE(sub4.value2, 66) as value3 FROM
+ ( SELECT 1 as key3 ) sub3
+ LEFT JOIN
+ ( SELECT sub5.key5, COALESCE(sub6.value1, 1) as value2 FROM
+ ( SELECT 1 as key5 ) sub5
+ LEFT JOIN
+ ( SELECT 2 as key6, 42 as value1 ) sub6
+ ON sub5.key5 = sub6.key6
+ ) sub4
+ ON sub4.key5 = sub3.key3
+) sub2
+ON sub1.key1 = sub2.key3;
+
+-- test the path using join aliases, too
+SELECT * FROM
+( SELECT 1 as key1 ) sub1
+LEFT JOIN
+( SELECT sub3.key3, value2, COALESCE(value2, 66) as value3 FROM
+ ( SELECT 1 as key3 ) sub3
+ LEFT JOIN
+ ( SELECT sub5.key5, COALESCE(sub6.value1, 1) as value2 FROM
+ ( SELECT 1 as key5 ) sub5
+ LEFT JOIN
+ ( SELECT 2 as key6, 42 as value1 ) sub6
+ ON sub5.key5 = sub6.key6
+ ) sub4
+ ON sub4.key5 = sub3.key3
+) sub2
+ON sub1.key1 = sub2.key3;
+
+--
+-- test case where a PlaceHolderVar is used as a nestloop parameter
+--
+
+EXPLAIN (COSTS OFF)
+SELECT qq, unique1
+ FROM
+ ( SELECT COALESCE(q1, 0) AS qq FROM int8_tbl a ) AS ss1
+ FULL OUTER JOIN
+ ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2
+ USING (qq)
+ INNER JOIN tenk1 c ON qq = unique2;
+
+SELECT qq, unique1
+ FROM
+ ( SELECT COALESCE(q1, 0) AS qq FROM int8_tbl a ) AS ss1
+ FULL OUTER JOIN
+ ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2
+ USING (qq)
+ INNER JOIN tenk1 c ON qq = unique2;
+
+--
+-- nested nestloops can require nested PlaceHolderVars
+--
+
+create temp table nt1 (
+ id int primary key,
+ a1 boolean,
+ a2 boolean
+);
+create temp table nt2 (
+ id int primary key,
+ nt1_id int,
+ b1 boolean,
+ b2 boolean,
+ foreign key (nt1_id) references nt1(id)
+);
+create temp table nt3 (
+ id int primary key,
+ nt2_id int,
+ c1 boolean,
+ foreign key (nt2_id) references nt2(id)
+);
+
+insert into nt1 values (1,true,true);
+insert into nt1 values (2,true,false);
+insert into nt1 values (3,false,false);
+insert into nt2 values (1,1,true,true);
+insert into nt2 values (2,2,true,false);
+insert into nt2 values (3,3,false,false);
+insert into nt3 values (1,1,true);
+insert into nt3 values (2,2,false);
+insert into nt3 values (3,3,true);
+
+explain (costs off)
+select nt3.id
+from nt3 as nt3
+ left join
+ (select nt2.*, (nt2.b1 and ss1.a3) AS b3
+ from nt2 as nt2
+ left join
+ (select nt1.*, (nt1.id is not null) as a3 from nt1) as ss1
+ on ss1.id = nt2.nt1_id
+ ) as ss2
+ on ss2.id = nt3.nt2_id
+where nt3.id = 1 and ss2.b3;
+
+select nt3.id
+from nt3 as nt3
+ left join
+ (select nt2.*, (nt2.b1 and ss1.a3) AS b3
+ from nt2 as nt2
+ left join
+ (select nt1.*, (nt1.id is not null) as a3 from nt1) as ss1
+ on ss1.id = nt2.nt1_id
+ ) as ss2
+ on ss2.id = nt3.nt2_id
+where nt3.id = 1 and ss2.b3;
+
+--
+-- test case where a PlaceHolderVar is propagated into a subquery
+--
+
+explain (costs off)
+select * from
+ int8_tbl t1 left join
+ (select q1 as x, 42 as y from int8_tbl t2) ss
+ on t1.q2 = ss.x
+where
+ 1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1)
+order by 1,2;
+
+select * from
+ int8_tbl t1 left join
+ (select q1 as x, 42 as y from int8_tbl t2) ss
+ on t1.q2 = ss.x
+where
+ 1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1)
+order by 1,2;
+
+--
+-- variant where a PlaceHolderVar is needed at a join, but not above the join
+--
+
+explain (costs off)
+select * from
+ int4_tbl as i41,
+ lateral
+ (select 1 as x from
+ (select i41.f1 as lat,
+ i42.f1 as loc from
+ int8_tbl as i81, int4_tbl as i42) as ss1
+ right join int4_tbl as i43 on (i43.f1 > 1)
+ where ss1.loc = ss1.lat) as ss2
+where i41.f1 > 0;
+
+select * from
+ int4_tbl as i41,
+ lateral
+ (select 1 as x from
+ (select i41.f1 as lat,
+ i42.f1 as loc from
+ int8_tbl as i81, int4_tbl as i42) as ss1
+ right join int4_tbl as i43 on (i43.f1 > 1)
+ where ss1.loc = ss1.lat) as ss2
+where i41.f1 > 0;
+
+--
+-- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE
+--
+select * from int4_tbl a full join int4_tbl b on true;
+select * from int4_tbl a full join int4_tbl b on false;
+
+--
+-- test for ability to use a cartesian join when necessary
+--
+
+create temp table q1 as select 1 as q1;
+create temp table q2 as select 0 as q2;
+analyze q1;
+analyze q2;
+
+explain (costs off)
+select * from
+ tenk1 join int4_tbl on f1 = twothousand,
+ q1, q2
+where q1 = thousand or q2 = thousand;
+
+explain (costs off)
+select * from
+ tenk1 join int4_tbl on f1 = twothousand,
+ q1, q2
+where thousand = (q1 + q2);
+
+--
+-- test ability to generate a suitable plan for a star-schema query
+--
+
+explain (costs off)
+select * from
+ tenk1, int8_tbl a, int8_tbl b
+where thousand = a.q1 and tenthous = b.q1 and a.q2 = 1 and b.q2 = 2;
+
+--
+-- test a corner case in which we shouldn't apply the star-schema optimization
+--
+
+explain (costs off)
+select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
+ tenk1 t1
+ inner join int4_tbl i1
+ left join (select v1.x2, v2.y1, 11 AS d1
+ from (select 1,0 from onerow) v1(x1,x2)
+ left join (select 3,1 from onerow) v2(y1,y2)
+ on v1.x1 = v2.y2) subq1
+ on (i1.f1 = subq1.x2)
+ on (t1.unique2 = subq1.d1)
+ left join tenk1 t2
+ on (subq1.y1 = t2.unique1)
+where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
+
+select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
+ tenk1 t1
+ inner join int4_tbl i1
+ left join (select v1.x2, v2.y1, 11 AS d1
+ from (select 1,0 from onerow) v1(x1,x2)
+ left join (select 3,1 from onerow) v2(y1,y2)
+ on v1.x1 = v2.y2) subq1
+ on (i1.f1 = subq1.x2)
+ on (t1.unique2 = subq1.d1)
+ left join tenk1 t2
+ on (subq1.y1 = t2.unique1)
+where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
+
+-- variant that isn't quite a star-schema case
+
+select ss1.d1 from
+ tenk1 as t1
+ inner join tenk1 as t2
+ on t1.tenthous = t2.ten
+ inner join
+ int8_tbl as i8
+ left join int4_tbl as i4
+ inner join (select 64::information_schema.cardinal_number as d1
+ from tenk1 t3,
+ lateral (select abs(t3.unique1) + random()) ss0(x)
+ where t3.fivethous < 0) as ss1
+ on i4.f1 = ss1.d1
+ on i8.q1 = i4.f1
+ on t1.tenthous = ss1.d1
+where t1.unique1 < i4.f1;
+
+-- this variant is foldable by the remove-useless-RESULT-RTEs code
+
+explain (costs off)
+select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
+ tenk1 t1
+ inner join int4_tbl i1
+ 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 (i1.f1 = subq1.x2)
+ on (t1.unique2 = subq1.d1)
+ left join tenk1 t2
+ on (subq1.y1 = t2.unique1)
+where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
+
+select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
+ tenk1 t1
+ inner join int4_tbl i1
+ 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 (i1.f1 = subq1.x2)
+ on (t1.unique2 = subq1.d1)
+ left join tenk1 t2
+ on (subq1.y1 = t2.unique1)
+where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
+
+-- Here's a variant that we can't fold too aggressively, though,
+-- or we end up with noplace to evaluate the lateral PHV
+explain (verbose, costs off)
+select * from
+ (select 1 as x) ss1 left join (select 2 as y) ss2 on (true),
+ lateral (select ss2.y as z limit 1) ss3;
+select * from
+ (select 1 as x) ss1 left join (select 2 as y) ss2 on (true),
+ lateral (select ss2.y as z limit 1) ss3;
+
+-- Test proper handling of appendrel PHVs during useless-RTE removal
+explain (costs off)
+select * from
+ (select 0 as z) as t1
+ left join
+ (select true as a) as t2
+ on true,
+ lateral (select true as b
+ union all
+ select a as b) as t3
+where b;
+
+select * from
+ (select 0 as z) as t1
+ left join
+ (select true as a) as t2
+ on true,
+ lateral (select true as b
+ union all
+ select a as b) as t3
+where b;
+
+--
+-- test inlining of immutable functions
+--
+create function f_immutable_int4(i integer) returns integer as
+$$ begin return i; end; $$ language plpgsql immutable;
+
+-- check optimization of function scan with join
+explain (costs off)
+select unique1 from tenk1, (select * from f_immutable_int4(1) x) x
+where x = unique1;
+
+explain (verbose, costs off)
+select unique1, x.*
+from tenk1, (select *, random() from f_immutable_int4(1) x) x
+where x = unique1;
+
+explain (costs off)
+select unique1 from tenk1, f_immutable_int4(1) x where x = unique1;
+
+explain (costs off)
+select unique1 from tenk1, lateral f_immutable_int4(1) x where x = unique1;
+
+explain (costs off)
+select unique1 from tenk1, lateral f_immutable_int4(1) x where x in (select 17);
+
+explain (costs off)
+select unique1, x from tenk1 join f_immutable_int4(1) x on unique1 = x;
+
+explain (costs off)
+select unique1, x from tenk1 left join f_immutable_int4(1) x on unique1 = x;
+
+explain (costs off)
+select unique1, x from tenk1 right join f_immutable_int4(1) x on unique1 = x;
+
+explain (costs off)
+select unique1, x from tenk1 full join f_immutable_int4(1) x on unique1 = x;
+
+-- check that pullup of a const function allows further const-folding
+explain (costs off)
+select unique1 from tenk1, f_immutable_int4(1) x where x = 42;
+
+-- test inlining of immutable functions with PlaceHolderVars
+explain (costs off)
+select nt3.id
+from nt3 as nt3
+ left join
+ (select nt2.*, (nt2.b1 or i4 = 42) AS b3
+ from nt2 as nt2
+ left join
+ f_immutable_int4(0) i4
+ on i4 = nt2.nt1_id
+ ) as ss2
+ on ss2.id = nt3.nt2_id
+where nt3.id = 1 and ss2.b3;
+
+drop function f_immutable_int4(int);
+
+-- test inlining when function returns composite
+
+create function mki8(bigint, bigint) returns int8_tbl as
+$$select row($1,$2)::int8_tbl$$ language sql;
+
+create function mki4(int) returns int4_tbl as
+$$select row($1)::int4_tbl$$ language sql;
+
+explain (verbose, costs off)
+select * from mki8(1,2);
+select * from mki8(1,2);
+
+explain (verbose, costs off)
+select * from mki4(42);
+select * from mki4(42);
+
+drop function mki8(bigint, bigint);
+drop function mki4(int);
+
+--
+-- test extraction of restriction OR clauses from join OR clause
+-- (we used to only do this for indexable clauses)
+--
+
+explain (costs off)
+select * from tenk1 a join tenk1 b on
+ (a.unique1 = 1 and b.unique1 = 2) or (a.unique2 = 3 and b.hundred = 4);
+explain (costs off)
+select * from tenk1 a join tenk1 b on
+ (a.unique1 = 1 and b.unique1 = 2) or (a.unique2 = 3 and b.ten = 4);
+explain (costs off)
+select * from tenk1 a join tenk1 b on
+ (a.unique1 = 1 and b.unique1 = 2) or
+ ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4);
+
+--
+-- test placement of movable quals in a parameterized join tree
+--
+
+explain (costs off)
+select * from tenk1 t1 left join
+ (tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2)
+ on t1.hundred = t2.hundred and t1.ten = t3.ten
+where t1.unique1 = 1;
+
+explain (costs off)
+select * from tenk1 t1 left join
+ (tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2)
+ on t1.hundred = t2.hundred and t1.ten + t2.ten = t3.ten
+where t1.unique1 = 1;
+
+explain (costs off)
+select count(*) from
+ tenk1 a join tenk1 b on a.unique1 = b.unique2
+ left join tenk1 c on a.unique2 = b.unique1 and c.thousand = a.thousand
+ join int4_tbl on b.thousand = f1;
+
+select count(*) from
+ tenk1 a join tenk1 b on a.unique1 = b.unique2
+ left join tenk1 c on a.unique2 = b.unique1 and c.thousand = a.thousand
+ join int4_tbl on b.thousand = f1;
+
+explain (costs off)
+select b.unique1 from
+ tenk1 a join tenk1 b on a.unique1 = b.unique2
+ left join tenk1 c on b.unique1 = 42 and c.thousand = a.thousand
+ join int4_tbl i1 on b.thousand = f1
+ right join int4_tbl i2 on i2.f1 = b.tenthous
+ order by 1;
+
+select b.unique1 from
+ tenk1 a join tenk1 b on a.unique1 = b.unique2
+ left join tenk1 c on b.unique1 = 42 and c.thousand = a.thousand
+ join int4_tbl i1 on b.thousand = f1
+ right join int4_tbl i2 on i2.f1 = b.tenthous
+ order by 1;
+
+explain (costs off)
+select * from
+(
+ select unique1, q1, coalesce(unique1, -1) + q1 as fault
+ from int8_tbl left join tenk1 on (q2 = unique2)
+) ss
+where fault = 122
+order by fault;
+
+select * from
+(
+ select unique1, q1, coalesce(unique1, -1) + q1 as fault
+ from int8_tbl left join tenk1 on (q2 = unique2)
+) ss
+where fault = 122
+order by fault;
+
+explain (costs off)
+select * from
+(values (1, array[10,20]), (2, array[20,30])) as v1(v1x,v1ys)
+left join (values (1, 10), (2, 20)) as v2(v2x,v2y) on v2x = v1x
+left join unnest(v1ys) as u1(u1y) on u1y = v2y;
+
+select * from
+(values (1, array[10,20]), (2, array[20,30])) as v1(v1x,v1ys)
+left join (values (1, 10), (2, 20)) as v2(v2x,v2y) on v2x = v1x
+left join unnest(v1ys) as u1(u1y) on u1y = v2y;
+
+--
+-- test handling of potential equivalence clauses above outer joins
+--
+
+explain (costs off)
+select q1, unique2, thousand, hundred
+ from int8_tbl a left join tenk1 b on q1 = unique2
+ where coalesce(thousand,123) = q1 and q1 = coalesce(hundred,123);
+
+select q1, unique2, thousand, hundred
+ from int8_tbl a left join tenk1 b on q1 = unique2
+ where coalesce(thousand,123) = q1 and q1 = coalesce(hundred,123);
+
+explain (costs off)
+select f1, unique2, case when unique2 is null then f1 else 0 end
+ from int4_tbl a left join tenk1 b on f1 = unique2
+ where (case when unique2 is null then f1 else 0 end) = 0;
+
+select f1, unique2, case when unique2 is null then f1 else 0 end
+ from int4_tbl a left join tenk1 b on f1 = unique2
+ where (case when unique2 is null then f1 else 0 end) = 0;
+
+--
+-- another case with equivalence clauses above outer joins (bug #8591)
+--
+
+explain (costs off)
+select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand)
+ from tenk1 a left join tenk1 b on b.thousand = a.unique1 left join tenk1 c on c.unique2 = coalesce(b.twothousand, a.twothousand)
+ where a.unique2 < 10 and coalesce(b.twothousand, a.twothousand) = 44;
+
+select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand)
+ from tenk1 a left join tenk1 b on b.thousand = a.unique1 left join tenk1 c on c.unique2 = coalesce(b.twothousand, a.twothousand)
+ where a.unique2 < 10 and coalesce(b.twothousand, a.twothousand) = 44;
+
+--
+-- check handling of join aliases when flattening multiple levels of subquery
+--
+
+explain (verbose, costs off)
+select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from
+ (values (0),(1)) foo1(join_key)
+left join
+ (select join_key, bug_field from
+ (select ss1.join_key, ss1.bug_field from
+ (select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1
+ ) foo2
+ left join
+ (select unique2 as join_key from tenk1 i2) ss2
+ using (join_key)
+ ) foo3
+using (join_key);
+
+select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from
+ (values (0),(1)) foo1(join_key)
+left join
+ (select join_key, bug_field from
+ (select ss1.join_key, ss1.bug_field from
+ (select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1
+ ) foo2
+ left join
+ (select unique2 as join_key from tenk1 i2) ss2
+ using (join_key)
+ ) foo3
+using (join_key);
+
+--
+-- test successful handling of nested outer joins with degenerate join quals
+--
+
+explain (verbose, costs off)
+select t1.* from
+ text_tbl t1
+ left join (select *, '***'::text as d1 from int8_tbl i8b1) b1
+ left join int8_tbl i8
+ left join (select *, null::int as d2 from int8_tbl i8b2) b2
+ on (i8.q1 = b2.q1)
+ on (b2.d2 = b1.q2)
+ on (t1.f1 = b1.d1)
+ left join int4_tbl i4
+ on (i8.q2 = i4.f1);
+
+select t1.* from
+ text_tbl t1
+ left join (select *, '***'::text as d1 from int8_tbl i8b1) b1
+ left join int8_tbl i8
+ left join (select *, null::int as d2 from int8_tbl i8b2) b2
+ on (i8.q1 = b2.q1)
+ on (b2.d2 = b1.q2)
+ on (t1.f1 = b1.d1)
+ left join int4_tbl i4
+ on (i8.q2 = i4.f1);
+
+explain (verbose, costs off)
+select t1.* from
+ text_tbl t1
+ left join (select *, '***'::text as d1 from int8_tbl i8b1) b1
+ left join int8_tbl i8
+ left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2) b2
+ on (i8.q1 = b2.q1)
+ on (b2.d2 = b1.q2)
+ on (t1.f1 = b1.d1)
+ left join int4_tbl i4
+ on (i8.q2 = i4.f1);
+
+select t1.* from
+ text_tbl t1
+ left join (select *, '***'::text as d1 from int8_tbl i8b1) b1
+ left join int8_tbl i8
+ left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2) b2
+ on (i8.q1 = b2.q1)
+ on (b2.d2 = b1.q2)
+ on (t1.f1 = b1.d1)
+ left join int4_tbl i4
+ on (i8.q2 = i4.f1);
+
+explain (verbose, costs off)
+select t1.* from
+ text_tbl t1
+ left join (select *, '***'::text as d1 from int8_tbl i8b1) b1
+ left join int8_tbl i8
+ left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2
+ where q1 = f1) b2
+ on (i8.q1 = b2.q1)
+ on (b2.d2 = b1.q2)
+ on (t1.f1 = b1.d1)
+ left join int4_tbl i4
+ on (i8.q2 = i4.f1);
+
+select t1.* from
+ text_tbl t1
+ left join (select *, '***'::text as d1 from int8_tbl i8b1) b1
+ left join int8_tbl i8
+ left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2
+ where q1 = f1) b2
+ on (i8.q1 = b2.q1)
+ on (b2.d2 = b1.q2)
+ on (t1.f1 = b1.d1)
+ left join int4_tbl i4
+ on (i8.q2 = i4.f1);
+
+explain (verbose, costs off)
+select * from
+ text_tbl t1
+ inner join int8_tbl i8
+ on i8.q2 = 456
+ right join text_tbl t2
+ on t1.f1 = 'doh!'
+ left join int4_tbl i4
+ on i8.q1 = i4.f1;
+
+select * from
+ text_tbl t1
+ inner join int8_tbl i8
+ on i8.q2 = 456
+ right join text_tbl t2
+ on t1.f1 = 'doh!'
+ left join int4_tbl i4
+ on i8.q1 = i4.f1;
+
+--
+-- test for appropriate join order in the presence of lateral references
+--
+
+explain (verbose, costs off)
+select * from
+ text_tbl t1
+ left join int8_tbl i8
+ on i8.q2 = 123,
+ lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss
+where t1.f1 = ss.f1;
+
+select * from
+ text_tbl t1
+ left join int8_tbl i8
+ on i8.q2 = 123,
+ lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss
+where t1.f1 = ss.f1;
+
+explain (verbose, costs off)
+select * from
+ text_tbl t1
+ left join int8_tbl i8
+ on i8.q2 = 123,
+ lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss1,
+ lateral (select ss1.* from text_tbl t3 limit 1) as ss2
+where t1.f1 = ss2.f1;
+
+select * from
+ text_tbl t1
+ left join int8_tbl i8
+ on i8.q2 = 123,
+ lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss1,
+ lateral (select ss1.* from text_tbl t3 limit 1) as ss2
+where t1.f1 = ss2.f1;
+
+explain (verbose, costs off)
+select 1 from
+ text_tbl as tt1
+ inner join text_tbl as tt2 on (tt1.f1 = 'foo')
+ left join text_tbl as tt3 on (tt3.f1 = 'foo')
+ left join text_tbl as tt4 on (tt3.f1 = tt4.f1),
+ lateral (select tt4.f1 as c0 from text_tbl as tt5 limit 1) as ss1
+where tt1.f1 = ss1.c0;
+
+select 1 from
+ text_tbl as tt1
+ inner join text_tbl as tt2 on (tt1.f1 = 'foo')
+ left join text_tbl as tt3 on (tt3.f1 = 'foo')
+ left join text_tbl as tt4 on (tt3.f1 = tt4.f1),
+ lateral (select tt4.f1 as c0 from text_tbl as tt5 limit 1) as ss1
+where tt1.f1 = ss1.c0;
+
+--
+-- check a case in which a PlaceHolderVar forces join order
+--
+
+explain (verbose, costs off)
+select ss2.* from
+ int4_tbl i41
+ left join int8_tbl i8
+ join (select i42.f1 as c1, i43.f1 as c2, 42 as c3
+ from int4_tbl i42, int4_tbl i43) ss1
+ on i8.q1 = ss1.c2
+ on i41.f1 = ss1.c1,
+ lateral (select i41.*, i8.*, ss1.* from text_tbl limit 1) ss2
+where ss1.c2 = 0;
+
+select ss2.* from
+ int4_tbl i41
+ left join int8_tbl i8
+ join (select i42.f1 as c1, i43.f1 as c2, 42 as c3
+ from int4_tbl i42, int4_tbl i43) ss1
+ on i8.q1 = ss1.c2
+ on i41.f1 = ss1.c1,
+ lateral (select i41.*, i8.*, ss1.* from text_tbl limit 1) ss2
+where ss1.c2 = 0;
+
+--
+-- test successful handling of full join underneath left join (bug #14105)
+--
+
+explain (costs off)
+select * from
+ (select 1 as id) as xx
+ left join
+ (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))
+ on (xx.id = coalesce(yy.id));
+
+select * from
+ (select 1 as id) as xx
+ left join
+ (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))
+ on (xx.id = coalesce(yy.id));
+
+--
+-- test ability to push constants through outer join clauses
+--
+
+explain (costs off)
+ select * from int4_tbl a left join tenk1 b on f1 = unique2 where f1 = 0;
+
+explain (costs off)
+ select * from tenk1 a full join tenk1 b using(unique2) where unique2 = 42;
+
+--
+-- test that quals attached to an outer join have correct semantics,
+-- specifically that they don't re-use expressions computed below the join;
+-- we force a mergejoin so that coalesce(b.q1, 1) appears as a join input
+--
+
+set enable_hashjoin to off;
+set enable_nestloop to off;
+
+explain (verbose, costs off)
+ select a.q2, b.q1
+ from int8_tbl a left join int8_tbl b on a.q2 = coalesce(b.q1, 1)
+ where coalesce(b.q1, 1) > 0;
+select a.q2, b.q1
+ from int8_tbl a left join int8_tbl b on a.q2 = coalesce(b.q1, 1)
+ where coalesce(b.q1, 1) > 0;
+
+reset enable_hashjoin;
+reset enable_nestloop;
+
+--
+-- test join removal
+--
+
+begin;
+
+CREATE TEMP TABLE a (id int PRIMARY KEY, b_id int);
+CREATE TEMP TABLE b (id int PRIMARY KEY, c_id int);
+CREATE TEMP TABLE c (id int PRIMARY KEY);
+CREATE TEMP TABLE d (a int, b int);
+INSERT INTO a VALUES (0, 0), (1, NULL);
+INSERT INTO b VALUES (0, 0), (1, NULL);
+INSERT INTO c VALUES (0), (1);
+INSERT INTO d VALUES (1,3), (2,2), (3,1);
+
+-- all three cases should be optimizable into a simple seqscan
+explain (costs off) SELECT a.* FROM a LEFT JOIN b ON a.b_id = b.id;
+explain (costs off) SELECT b.* FROM b LEFT JOIN c ON b.c_id = c.id;
+explain (costs off)
+ SELECT a.* FROM a LEFT JOIN (b left join c on b.c_id = c.id)
+ ON (a.b_id = b.id);
+
+-- check optimization of outer join within another special join
+explain (costs off)
+select id from a where id in (
+ select b.id from b left join c on b.id = c.id
+);
+
+-- check that join removal works for a left join when joining a subquery
+-- that is guaranteed to be unique by its GROUP BY clause
+explain (costs off)
+select d.* from d left join (select * from b group by b.id, b.c_id) s
+ on d.a = s.id and d.b = s.c_id;
+
+-- similarly, but keying off a DISTINCT clause
+explain (costs off)
+select d.* from d left join (select distinct * from b) s
+ on d.a = s.id and d.b = s.c_id;
+
+-- join removal is not possible when the GROUP BY contains a column that is
+-- not in the join condition. (Note: as of 9.6, we notice that b.id is a
+-- primary key and so drop b.c_id from the GROUP BY of the resulting plan;
+-- but this happens too late for join removal in the outer plan level.)
+explain (costs off)
+select d.* from d left join (select * from b group by b.id, b.c_id) s
+ on d.a = s.id;
+
+-- similarly, but keying off a DISTINCT clause
+explain (costs off)
+select d.* from d left join (select distinct * from b) s
+ on d.a = s.id;
+
+-- check join removal works when uniqueness of the join condition is enforced
+-- by a UNION
+explain (costs off)
+select d.* from d left join (select id from a union select id from b) s
+ on d.a = s.id;
+
+-- check join removal with a cross-type comparison operator
+explain (costs off)
+select i8.* from int8_tbl i8 left join (select f1 from int4_tbl group by f1) i4
+ on i8.q1 = i4.f1;
+
+-- check join removal with lateral references
+explain (costs off)
+select 1 from (select a.id FROM a left join b on a.b_id = b.id) q,
+ lateral generate_series(1, q.id) gs(i) where q.id = gs.i;
+
+rollback;
+
+create temp table parent (k int primary key, pd int);
+create temp table child (k int unique, cd int);
+insert into parent values (1, 10), (2, 20), (3, 30);
+insert into child values (1, 100), (4, 400);
+
+-- this case is optimizable
+select p.* from parent p left join child c on (p.k = c.k);
+explain (costs off)
+ select p.* from parent p left join child c on (p.k = c.k);
+
+-- this case is not
+select p.*, linked from parent p
+ left join (select c.*, true as linked from child c) as ss
+ on (p.k = ss.k);
+explain (costs off)
+ select p.*, linked from parent p
+ left join (select c.*, true as linked from child c) as ss
+ on (p.k = ss.k);
+
+-- check for a 9.0rc1 bug: join removal breaks pseudoconstant qual handling
+select p.* from
+ parent p left join child c on (p.k = c.k)
+ where p.k = 1 and p.k = 2;
+explain (costs off)
+select p.* from
+ parent p left join child c on (p.k = c.k)
+ where p.k = 1 and p.k = 2;
+
+select p.* from
+ (parent p left join child c on (p.k = c.k)) join parent x on p.k = x.k
+ where p.k = 1 and p.k = 2;
+explain (costs off)
+select p.* from
+ (parent p left join child c on (p.k = c.k)) join parent x on p.k = x.k
+ where p.k = 1 and p.k = 2;
+
+-- bug 5255: this is not optimizable by join removal
+begin;
+
+CREATE TEMP TABLE a (id int PRIMARY KEY);
+CREATE TEMP TABLE b (id int PRIMARY KEY, a_id int);
+INSERT INTO a VALUES (0), (1);
+INSERT INTO b VALUES (0, 0), (1, NULL);
+
+SELECT * FROM b LEFT JOIN a ON (b.a_id = a.id) WHERE (a.id IS NULL OR a.id > 0);
+SELECT b.* FROM b LEFT JOIN a ON (b.a_id = a.id) WHERE (a.id IS NULL OR a.id > 0);
+
+rollback;
+
+-- another join removal bug: this is not optimizable, either
+begin;
+
+create temp table innertab (id int8 primary key, dat1 int8);
+insert into innertab values(123, 42);
+
+SELECT * FROM
+ (SELECT 1 AS x) ss1
+ LEFT JOIN
+ (SELECT q1, q2, COALESCE(dat1, q1) AS y
+ FROM int8_tbl LEFT JOIN innertab ON q2 = id) ss2
+ ON true;
+
+rollback;
+
+-- another join removal bug: we must clean up correctly when removing a PHV
+begin;
+
+create temp table uniquetbl (f1 text unique);
+
+explain (costs off)
+select t1.* from
+ uniquetbl as t1
+ left join (select *, '***'::text as d1 from uniquetbl) t2
+ on t1.f1 = t2.f1
+ left join uniquetbl t3
+ on t2.d1 = t3.f1;
+
+explain (costs off)
+select t0.*
+from
+ text_tbl t0
+ left join
+ (select case t1.ten when 0 then 'doh!'::text else null::text end as case1,
+ t1.stringu2
+ from tenk1 t1
+ join int4_tbl i4 ON i4.f1 = t1.unique2
+ left join uniquetbl u1 ON u1.f1 = t1.string4) ss
+ on t0.f1 = ss.case1
+where ss.stringu2 !~* ss.case1;
+
+select t0.*
+from
+ text_tbl t0
+ left join
+ (select case t1.ten when 0 then 'doh!'::text else null::text end as case1,
+ t1.stringu2
+ from tenk1 t1
+ join int4_tbl i4 ON i4.f1 = t1.unique2
+ left join uniquetbl u1 ON u1.f1 = t1.string4) ss
+ on t0.f1 = ss.case1
+where ss.stringu2 !~* ss.case1;
+
+rollback;
+
+-- test case to expose miscomputation of required relid set for a PHV
+explain (verbose, costs off)
+select i8.*, ss.v, t.unique2
+ from int8_tbl i8
+ left join int4_tbl i4 on i4.f1 = 1
+ left join lateral (select i4.f1 + 1 as v) as ss on true
+ left join tenk1 t on t.unique2 = ss.v
+where q2 = 456;
+
+select i8.*, ss.v, t.unique2
+ from int8_tbl i8
+ left join int4_tbl i4 on i4.f1 = 1
+ left join lateral (select i4.f1 + 1 as v) as ss on true
+ left join tenk1 t on t.unique2 = ss.v
+where q2 = 456;
+
+-- and check a related issue where we miscompute required relids for
+-- a PHV that's been translated to a child rel
+create temp table parttbl (a integer primary key) partition by range (a);
+create temp table parttbl1 partition of parttbl for values from (1) to (100);
+insert into parttbl values (11), (12);
+explain (costs off)
+select * from
+ (select *, 12 as phv from parttbl) as ss
+ right join int4_tbl on true
+where ss.a = ss.phv and f1 = 0;
+
+select * from
+ (select *, 12 as phv from parttbl) as ss
+ right join int4_tbl on true
+where ss.a = ss.phv and f1 = 0;
+
+-- bug #8444: we've historically allowed duplicate aliases within aliased JOINs
+
+select * from
+ int8_tbl x join (int4_tbl x cross join int4_tbl y) j on q1 = f1; -- error
+select * from
+ int8_tbl x join (int4_tbl x cross join int4_tbl y) j on q1 = y.f1; -- error
+select * from
+ int8_tbl x join (int4_tbl x cross join int4_tbl y(ff)) j on q1 = f1; -- ok
+
+--
+-- 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
+
+--
+-- Take care to reference the correct RTE
+--
+
+select atts.relid::regclass, s.* from pg_stats s join
+ pg_attribute a on s.attname = a.attname and s.tablename =
+ a.attrelid::regclass::text join (select unnest(indkey) attnum,
+ indexrelid from pg_index i) atts on atts.attnum = a.attnum where
+ schemaname != 'pg_catalog';
+
+--
+-- Test LATERAL
+--
+
+select unique2, x.*
+from tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x;
+explain (costs off)
+ select unique2, x.*
+ from tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x;
+select unique2, x.*
+from int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss;
+explain (costs off)
+ select unique2, x.*
+ from int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss;
+explain (costs off)
+ select unique2, x.*
+ from int4_tbl x cross join lateral (select unique2 from tenk1 where f1 = unique1) ss;
+select unique2, x.*
+from int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on true;
+explain (costs off)
+ select unique2, x.*
+ from int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on true;
+
+-- check scoping of lateral versus parent references
+-- the first of these should return int8_tbl.q2, the second int8_tbl.q1
+select *, (select r from (select q1 as q2) x, (select q2 as r) y) from int8_tbl;
+select *, (select r from (select q1 as q2) x, lateral (select q2 as r) y) from int8_tbl;
+
+-- lateral with function in FROM
+select count(*) from tenk1 a, lateral generate_series(1,two) g;
+explain (costs off)
+ select count(*) from tenk1 a, lateral generate_series(1,two) g;
+explain (costs off)
+ select count(*) from tenk1 a cross join lateral generate_series(1,two) g;
+-- don't need the explicit LATERAL keyword for functions
+explain (costs off)
+ select count(*) from tenk1 a, generate_series(1,two) g;
+
+-- lateral with UNION ALL subselect
+explain (costs off)
+ select * from generate_series(100,200) g,
+ lateral (select * from int8_tbl a where g = q1 union all
+ select * from int8_tbl b where g = q2) ss;
+select * from generate_series(100,200) g,
+ lateral (select * from int8_tbl a where g = q1 union all
+ select * from int8_tbl b where g = q2) ss;
+
+-- lateral with VALUES
+explain (costs off)
+ select count(*) from tenk1 a,
+ tenk1 b join lateral (values(a.unique1)) ss(x) on b.unique2 = ss.x;
+select count(*) from tenk1 a,
+ tenk1 b join lateral (values(a.unique1)) ss(x) on b.unique2 = ss.x;
+
+-- lateral with VALUES, no flattening possible
+explain (costs off)
+ select count(*) from tenk1 a,
+ tenk1 b join lateral (values(a.unique1),(-1)) ss(x) on b.unique2 = ss.x;
+select count(*) from tenk1 a,
+ tenk1 b join lateral (values(a.unique1),(-1)) ss(x) on b.unique2 = ss.x;
+
+-- lateral injecting a strange outer join condition
+explain (costs off)
+ select * from int8_tbl a,
+ int8_tbl x left join lateral (select a.q1 from int4_tbl y) ss(z)
+ on x.q2 = ss.z
+ order by a.q1, a.q2, x.q1, x.q2, ss.z;
+select * from int8_tbl a,
+ int8_tbl x left join lateral (select a.q1 from int4_tbl y) ss(z)
+ on x.q2 = ss.z
+ order by a.q1, a.q2, x.q1, x.q2, ss.z;
+
+-- lateral reference to a join alias variable
+select * from (select f1/2 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1,
+ lateral (select x) ss2(y);
+select * from (select f1 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1,
+ lateral (values(x)) ss2(y);
+select * from ((select f1/2 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1) j,
+ lateral (select x) ss2(y);
+
+-- lateral references requiring pullup
+select * from (values(1)) x(lb),
+ lateral generate_series(lb,4) x4;
+select * from (select f1/1000000000 from int4_tbl) x(lb),
+ lateral generate_series(lb,4) x4;
+select * from (values(1)) x(lb),
+ lateral (values(lb)) y(lbcopy);
+select * from (values(1)) x(lb),
+ lateral (select lb from int4_tbl) y(lbcopy);
+select * from
+ int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1,
+ lateral (values(x.q1,y.q1,y.q2)) v(xq1,yq1,yq2);
+select * from
+ int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1,
+ lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2);
+select x.* from
+ int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1,
+ lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2);
+select v.* from
+ (int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1)
+ left join int4_tbl z on z.f1 = x.q2,
+ lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy);
+select v.* from
+ (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1)
+ left join int4_tbl z on z.f1 = x.q2,
+ lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy);
+select v.* from
+ (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1)
+ left join int4_tbl z on z.f1 = x.q2,
+ lateral (select x.q1,y.q1 from onerow union all select x.q2,y.q2 from onerow) v(vx,vy);
+
+explain (verbose, costs off)
+select * from
+ int8_tbl a left join
+ lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;
+select * from
+ int8_tbl a left join
+ lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;
+explain (verbose, costs off)
+select * from
+ int8_tbl a left join
+ lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;
+select * from
+ int8_tbl a left join
+ lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;
+
+-- lateral can result in join conditions appearing below their
+-- real semantic level
+explain (verbose, costs off)
+select * from int4_tbl i left join
+ lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
+select * from int4_tbl i left join
+ lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
+explain (verbose, costs off)
+select * from int4_tbl i left join
+ lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
+select * from int4_tbl i left join
+ lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
+explain (verbose, costs off)
+select * from int4_tbl a,
+ lateral (
+ select * from int4_tbl b left join int8_tbl c on (b.f1 = q1 and a.f1 = q2)
+ ) ss;
+select * from int4_tbl a,
+ lateral (
+ select * from int4_tbl b left join int8_tbl c on (b.f1 = q1 and a.f1 = q2)
+ ) ss;
+
+-- lateral reference in a PlaceHolderVar evaluated at join level
+explain (verbose, costs off)
+select * from
+ int8_tbl a left join lateral
+ (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from
+ int8_tbl b cross join int8_tbl c) ss
+ on a.q2 = ss.bq1;
+select * from
+ int8_tbl a left join lateral
+ (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from
+ int8_tbl b cross join int8_tbl c) ss
+ on a.q2 = ss.bq1;
+
+-- case requiring nested PlaceHolderVars
+explain (verbose, costs off)
+select * from
+ int8_tbl c left join (
+ int8_tbl a left join (select q1, coalesce(q2,42) as x from int8_tbl b) ss1
+ on a.q2 = ss1.q1
+ cross join
+ lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2
+ ) on c.q2 = ss2.q1,
+ lateral (select ss2.y offset 0) ss3;
+
+-- case that breaks the old ph_may_need optimization
+explain (verbose, costs off)
+select c.*,a.*,ss1.q1,ss2.q1,ss3.* from
+ int8_tbl c left join (
+ int8_tbl a left join
+ (select q1, coalesce(q2,f1) as x from int8_tbl b, int4_tbl b2
+ where q1 < f1) ss1
+ on a.q2 = ss1.q1
+ cross join
+ lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2
+ ) on c.q2 = ss2.q1,
+ lateral (select * from int4_tbl i where ss2.y > f1) ss3;
+
+-- check processing of postponed quals (bug #9041)
+explain (verbose, costs off)
+select * from
+ (select 1 as x offset 0) x cross join (select 2 as y offset 0) y
+ left join lateral (
+ select * from (select 3 as z offset 0) z where z.z = x.x
+ ) zz on zz.z = y.y;
+
+-- check dummy rels with lateral references (bug #15694)
+explain (verbose, costs off)
+select * from int8_tbl i8 left join lateral
+ (select *, i8.q2 from int4_tbl where false) ss on true;
+explain (verbose, costs off)
+select * from int8_tbl i8 left join lateral
+ (select *, i8.q2 from int4_tbl i1, int4_tbl i2 where false) ss on true;
+
+-- check handling of nested appendrels inside LATERAL
+select * from
+ ((select 2 as v) union all (select 3 as v)) as q1
+ cross join lateral
+ ((select * from
+ ((select 4 as v) union all (select 5 as v)) as q3)
+ union all
+ (select q1.v)
+ ) as q2;
+
+-- check the number of columns specified
+SELECT * FROM (int8_tbl i cross join int4_tbl j) ss(a,b,c,d);
+
+-- check we don't try to do a unique-ified semijoin with LATERAL
+explain (verbose, costs off)
+select * from
+ (values (0,9998), (1,1000)) v(id,x),
+ lateral (select f1 from int4_tbl
+ where f1 = any (select unique1 from tenk1
+ where unique2 = v.x offset 0)) ss;
+select * from
+ (values (0,9998), (1,1000)) v(id,x),
+ lateral (select f1 from int4_tbl
+ where f1 = any (select unique1 from tenk1
+ where unique2 = v.x offset 0)) ss;
+
+-- check proper extParam/allParam handling (this isn't exactly a LATERAL issue,
+-- but we can make the test case much more compact with LATERAL)
+explain (verbose, costs off)
+select * from (values (0), (1)) v(id),
+lateral (select * from int8_tbl t1,
+ lateral (select * from
+ (select * from int8_tbl t2
+ where q1 = any (select q2 from int8_tbl t3
+ where q2 = (select greatest(t1.q1,t2.q2))
+ and (select v.id=0)) offset 0) ss2) ss
+ where t1.q1 = ss.q2) ss0;
+
+select * from (values (0), (1)) v(id),
+lateral (select * from int8_tbl t1,
+ lateral (select * from
+ (select * from int8_tbl t2
+ where q1 = any (select q2 from int8_tbl t3
+ where q2 = (select greatest(t1.q1,t2.q2))
+ and (select v.id=0)) offset 0) ss2) ss
+ where t1.q1 = ss.q2) ss0;
+
+-- test some error cases where LATERAL should have been used but wasn't
+select f1,g from int4_tbl a, (select f1 as g) ss;
+select f1,g from int4_tbl a, (select a.f1 as g) ss;
+select f1,g from int4_tbl a cross join (select f1 as g) ss;
+select f1,g from int4_tbl a cross join (select a.f1 as g) ss;
+-- SQL:2008 says the left table is in scope but illegal to access here
+select f1,g from int4_tbl a right join lateral generate_series(0, a.f1) g on true;
+select f1,g from int4_tbl a full join lateral generate_series(0, a.f1) g on true;
+-- check we complain about ambiguous table references
+select * from
+ int8_tbl x cross join (int4_tbl x cross join lateral (select x.f1) ss);
+-- LATERAL can be used to put an aggregate into the FROM clause of its query
+select 1 from tenk1 a, lateral (select max(a.unique1) from int4_tbl b) ss;
+
+-- check behavior of LATERAL in UPDATE/DELETE
+
+create temp table xx1 as select f1 as x1, -f1 as x2 from int4_tbl;
+
+-- error, can't do this:
+update xx1 set x2 = f1 from (select * from int4_tbl where f1 = x1) ss;
+update xx1 set x2 = f1 from (select * from int4_tbl where f1 = xx1.x1) ss;
+-- can't do it even with LATERAL:
+update xx1 set x2 = f1 from lateral (select * from int4_tbl where f1 = x1) ss;
+-- we might in future allow something like this, but for now it's an error:
+update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1) ss;
+
+-- also errors:
+delete from xx1 using (select * from int4_tbl where f1 = x1) ss;
+delete from xx1 using (select * from int4_tbl where f1 = xx1.x1) ss;
+delete from xx1 using lateral (select * from int4_tbl where f1 = x1) ss;
+
+--
+-- test LATERAL reference propagation down a multi-level inheritance hierarchy
+-- produced for a multi-level partitioned table hierarchy.
+--
+create table join_pt1 (a int, b int, c varchar) partition by range(a);
+create table join_pt1p1 partition of join_pt1 for values from (0) to (100) partition by range(b);
+create table join_pt1p2 partition of join_pt1 for values from (100) to (200);
+create table join_pt1p1p1 partition of join_pt1p1 for values from (0) to (100);
+insert into join_pt1 values (1, 1, 'x'), (101, 101, 'y');
+create table join_ut1 (a int, b int, c varchar);
+insert into join_ut1 values (101, 101, 'y'), (2, 2, 'z');
+explain (verbose, costs off)
+select t1.b, ss.phv from join_ut1 t1 left join lateral
+ (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv
+ from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss
+ on t1.a = ss.t2a order by t1.a;
+select t1.b, ss.phv from join_ut1 t1 left join lateral
+ (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv
+ from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss
+ on t1.a = ss.t2a order by t1.a;
+
+drop table join_pt1;
+drop table join_ut1;
+
+--
+-- test estimation behavior with multi-column foreign key and constant qual
+--
+
+begin;
+
+create table fkest (x integer, x10 integer, x10b integer, x100 integer);
+insert into fkest select x, x/10, x/10, x/100 from generate_series(1,1000) x;
+create unique index on fkest(x, x10, x100);
+analyze fkest;
+
+explain (costs off)
+select * from fkest f1
+ join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100)
+ join fkest f3 on f1.x = f3.x
+ where f1.x100 = 2;
+
+alter table fkest add constraint fk
+ foreign key (x, x10b, x100) references fkest (x, x10, x100);
+
+explain (costs off)
+select * from fkest f1
+ join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100)
+ join fkest f3 on f1.x = f3.x
+ where f1.x100 = 2;
+
+rollback;
+
+--
+-- test that foreign key join estimation performs sanely for outer joins
+--
+
+begin;
+
+create table fkest (a int, b int, c int unique, primary key(a,b));
+create table fkest1 (a int, b int, primary key(a,b));
+
+insert into fkest select x/10, x%10, x from generate_series(1,1000) x;
+insert into fkest1 select x/10, x%10 from generate_series(1,1000) x;
+
+alter table fkest1
+ add constraint fkest1_a_b_fkey foreign key (a,b) references fkest;
+
+analyze fkest;
+analyze fkest1;
+
+explain (costs off)
+select *
+from fkest f
+ left join fkest1 f1 on f.a = f1.a and f.b = f1.b
+ left join fkest1 f2 on f.a = f2.a and f.b = f2.b
+ left join fkest1 f3 on f.a = f3.a and f.b = f3.b
+where f.c = 1;
+
+rollback;
+
+--
+-- test planner's ability to mark joins as unique
+--
+
+create table j1 (id int primary key);
+create table j2 (id int primary key);
+create table j3 (id int);
+
+insert into j1 values(1),(2),(3);
+insert into j2 values(1),(2),(3);
+insert into j3 values(1),(1);
+
+analyze j1;
+analyze j2;
+analyze j3;
+
+-- ensure join is properly marked as unique
+explain (verbose, costs off)
+select * from j1 inner join j2 on j1.id = j2.id;
+
+-- ensure join is not unique when not an equi-join
+explain (verbose, costs off)
+select * from j1 inner join j2 on j1.id > j2.id;
+
+-- ensure non-unique rel is not chosen as inner
+explain (verbose, costs off)
+select * from j1 inner join j3 on j1.id = j3.id;
+
+-- ensure left join is marked as unique
+explain (verbose, costs off)
+select * from j1 left join j2 on j1.id = j2.id;
+
+-- ensure right join is marked as unique
+explain (verbose, costs off)
+select * from j1 right join j2 on j1.id = j2.id;
+
+-- ensure full join is marked as unique
+explain (verbose, costs off)
+select * from j1 full join j2 on j1.id = j2.id;
+
+-- a clauseless (cross) join can't be unique
+explain (verbose, costs off)
+select * from j1 cross join j2;
+
+-- ensure a natural join is marked as unique
+explain (verbose, costs off)
+select * from j1 natural join j2;
+
+-- ensure a distinct clause allows the inner to become unique
+explain (verbose, costs off)
+select * from j1
+inner join (select distinct id from j3) j3 on j1.id = j3.id;
+
+-- ensure group by clause allows the inner to become unique
+explain (verbose, costs off)
+select * from j1
+inner join (select id from j3 group by id) j3 on j1.id = j3.id;
+
+drop table j1;
+drop table j2;
+drop table j3;
+
+-- test more complex permutations of unique joins
+
+create table j1 (id1 int, id2 int, primary key(id1,id2));
+create table j2 (id1 int, id2 int, primary key(id1,id2));
+create table j3 (id1 int, id2 int, primary key(id1,id2));
+
+insert into j1 values(1,1),(1,2);
+insert into j2 values(1,1);
+insert into j3 values(1,1);
+
+analyze j1;
+analyze j2;
+analyze j3;
+
+-- ensure there's no unique join when not all columns which are part of the
+-- unique index are seen in the join clause
+explain (verbose, costs off)
+select * from j1
+inner join j2 on j1.id1 = j2.id1;
+
+-- ensure proper unique detection with multiple join quals
+explain (verbose, costs off)
+select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2;
+
+-- ensure we don't detect the join to be unique when quals are not part of the
+-- join condition
+explain (verbose, costs off)
+select * from j1
+inner join j2 on j1.id1 = j2.id1 where j1.id2 = 1;
+
+-- as above, but for left joins.
+explain (verbose, costs off)
+select * from j1
+left join j2 on j1.id1 = j2.id1 where j1.id2 = 1;
+
+-- validate logic in merge joins which skips mark and restore.
+-- it should only do this if all quals which were used to detect the unique
+-- are present as join quals, and not plain quals.
+set enable_nestloop to 0;
+set enable_hashjoin to 0;
+set enable_sort to 0;
+
+-- create indexes that will be preferred over the PKs to perform the join
+create index j1_id1_idx on j1 (id1) where id1 % 1000 = 1;
+create index j2_id1_idx on j2 (id1) where id1 % 1000 = 1;
+
+-- need an additional row in j2, if we want j2_id1_idx to be preferred
+insert into j2 values(1,2);
+analyze j2;
+
+explain (costs off) select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
+where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1;
+
+select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
+where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1;
+
+-- Exercise array keys mark/restore B-Tree code
+explain (costs off) select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
+where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]);
+
+select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
+where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]);
+
+-- Exercise array keys "find extreme element" B-Tree code
+explain (costs off) select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
+where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]);
+
+select * from j1
+inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
+where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]);
+
+reset enable_nestloop;
+reset enable_hashjoin;
+reset enable_sort;
+
+drop table j1;
+drop table j2;
+drop table j3;
+
+-- check that semijoin inner is not seen as unique for a portion of the outerrel
+explain (verbose, costs off)
+select t1.unique1, t2.hundred
+from onek t1, tenk1 t2
+where exists (select 1 from tenk1 t3
+ where t3.thousand = t1.unique1 and t3.tenthous = t2.hundred)
+ and t1.unique1 < 1;
+
+-- ... unless it actually is unique
+create table j3 as select unique1, tenthous from onek;
+vacuum analyze j3;
+create unique index on j3(unique1, tenthous);
+
+explain (verbose, costs off)
+select t1.unique1, t2.hundred
+from onek t1, tenk1 t2
+where exists (select 1 from j3
+ where j3.unique1 = t1.unique1 and j3.tenthous = t2.hundred)
+ and t1.unique1 < 1;
+
+drop table j3;
diff --git a/src/test/regress/sql/join_hash.sql b/src/test/regress/sql/join_hash.sql
new file mode 100644
index 0000000..68c1a8c
--- /dev/null
+++ b/src/test/regress/sql/join_hash.sql
@@ -0,0 +1,540 @@
+--
+-- exercises for the hash join code
+--
+
+begin;
+
+set local min_parallel_table_scan_size = 0;
+set local parallel_setup_cost = 0;
+set local enable_hashjoin = on;
+
+-- Extract bucket and batch counts from an explain analyze plan. In
+-- general we can't make assertions about how many batches (or
+-- buckets) will be required because it can vary, but we can in some
+-- special cases and we can check for growth.
+create or replace function find_hash(node json)
+returns json language plpgsql
+as
+$$
+declare
+ x json;
+ child json;
+begin
+ if node->>'Node Type' = 'Hash' then
+ return node;
+ else
+ for child in select json_array_elements(node->'Plans')
+ loop
+ x := find_hash(child);
+ if x is not null then
+ return x;
+ end if;
+ end loop;
+ return null;
+ end if;
+end;
+$$;
+create or replace function hash_join_batches(query text)
+returns table (original int, final int) language plpgsql
+as
+$$
+declare
+ whole_plan json;
+ hash_node json;
+begin
+ for whole_plan in
+ execute 'explain (analyze, format ''json'') ' || query
+ loop
+ hash_node := find_hash(json_extract_path(whole_plan, '0', 'Plan'));
+ original := hash_node->>'Original Hash Batches';
+ final := hash_node->>'Hash Batches';
+ return next;
+ end loop;
+end;
+$$;
+
+-- Make a simple relation with well distributed keys and correctly
+-- estimated size.
+create table simple as
+ select generate_series(1, 20000) AS id, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
+alter table simple set (parallel_workers = 2);
+analyze simple;
+
+-- Make a relation whose size we will under-estimate. We want stats
+-- to say 1000 rows, but actually there are 20,000 rows.
+create table bigger_than_it_looks as
+ select generate_series(1, 20000) as id, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
+alter table bigger_than_it_looks set (autovacuum_enabled = 'false');
+alter table bigger_than_it_looks set (parallel_workers = 2);
+analyze bigger_than_it_looks;
+update pg_class set reltuples = 1000 where relname = 'bigger_than_it_looks';
+
+-- Make a relation whose size we underestimate and that also has a
+-- kind of skew that breaks our batching scheme. We want stats to say
+-- 2 rows, but actually there are 20,000 rows with the same key.
+create table extremely_skewed (id int, t text);
+alter table extremely_skewed set (autovacuum_enabled = 'false');
+alter table extremely_skewed set (parallel_workers = 2);
+analyze extremely_skewed;
+insert into extremely_skewed
+ select 42 as id, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+ from generate_series(1, 20000);
+update pg_class
+ set reltuples = 2, relpages = pg_relation_size('extremely_skewed') / 8192
+ where relname = 'extremely_skewed';
+
+-- Make a relation with a couple of enormous tuples.
+create table wide as select generate_series(1, 2) as id, rpad('', 320000, 'x') as t;
+alter table wide set (parallel_workers = 2);
+
+-- The "optimal" case: the hash table fits in memory; we plan for 1
+-- batch, we stick to that number, and peak memory usage stays within
+-- our work_mem budget
+
+-- non-parallel
+savepoint settings;
+set local max_parallel_workers_per_gather = 0;
+set local work_mem = '4MB';
+explain (costs off)
+ select count(*) from simple r join simple s using (id);
+select count(*) from simple r join simple s using (id);
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+rollback to settings;
+
+-- parallel with parallel-oblivious hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '4MB';
+set local enable_parallel_hash = off;
+explain (costs off)
+ select count(*) from simple r join simple s using (id);
+select count(*) from simple r join simple s using (id);
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+rollback to settings;
+
+-- parallel with parallel-aware hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '4MB';
+set local enable_parallel_hash = on;
+explain (costs off)
+ select count(*) from simple r join simple s using (id);
+select count(*) from simple r join simple s using (id);
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+rollback to settings;
+
+-- The "good" case: batches required, but we plan the right number; we
+-- plan for some number of batches, and we stick to that number, and
+-- peak memory usage says within our work_mem budget
+
+-- non-parallel
+savepoint settings;
+set local max_parallel_workers_per_gather = 0;
+set local work_mem = '128kB';
+explain (costs off)
+ select count(*) from simple r join simple s using (id);
+select count(*) from simple r join simple s using (id);
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+rollback to settings;
+
+-- parallel with parallel-oblivious hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '128kB';
+set local enable_parallel_hash = off;
+explain (costs off)
+ select count(*) from simple r join simple s using (id);
+select count(*) from simple r join simple s using (id);
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+rollback to settings;
+
+-- parallel with parallel-aware hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '192kB';
+set local enable_parallel_hash = on;
+explain (costs off)
+ select count(*) from simple r join simple s using (id);
+select count(*) from simple r join simple s using (id);
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+rollback to settings;
+
+-- The "bad" case: during execution we need to increase number of
+-- batches; in this case we plan for 1 batch, and increase at least a
+-- couple of times, and peak memory usage stays within our work_mem
+-- budget
+
+-- non-parallel
+savepoint settings;
+set local max_parallel_workers_per_gather = 0;
+set local work_mem = '128kB';
+explain (costs off)
+ select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id);
+select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id);
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id);
+$$);
+rollback to settings;
+
+-- parallel with parallel-oblivious hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '128kB';
+set local enable_parallel_hash = off;
+explain (costs off)
+ select count(*) from simple r join bigger_than_it_looks s using (id);
+select count(*) from simple r join bigger_than_it_looks s using (id);
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join bigger_than_it_looks s using (id);
+$$);
+rollback to settings;
+
+-- parallel with parallel-aware hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 1;
+set local work_mem = '192kB';
+set local enable_parallel_hash = on;
+explain (costs off)
+ select count(*) from simple r join bigger_than_it_looks s using (id);
+select count(*) from simple r join bigger_than_it_looks s using (id);
+select original > 1 as initially_multibatch, final > original as increased_batches
+ from hash_join_batches(
+$$
+ select count(*) from simple r join bigger_than_it_looks s using (id);
+$$);
+rollback to settings;
+
+-- The "ugly" case: increasing the number of batches during execution
+-- doesn't help, so stop trying to fit in work_mem and hope for the
+-- best; in this case we plan for 1 batch, increases just once and
+-- then stop increasing because that didn't help at all, so we blow
+-- right through the work_mem budget and hope for the best...
+
+-- non-parallel
+savepoint settings;
+set local max_parallel_workers_per_gather = 0;
+set local work_mem = '128kB';
+explain (costs off)
+ select count(*) from simple r join extremely_skewed s using (id);
+select count(*) from simple r join extremely_skewed s using (id);
+select * from hash_join_batches(
+$$
+ select count(*) from simple r join extremely_skewed s using (id);
+$$);
+rollback to settings;
+
+-- parallel with parallel-oblivious hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '128kB';
+set local enable_parallel_hash = off;
+explain (costs off)
+ select count(*) from simple r join extremely_skewed s using (id);
+select count(*) from simple r join extremely_skewed s using (id);
+select * from hash_join_batches(
+$$
+ select count(*) from simple r join extremely_skewed s using (id);
+$$);
+rollback to settings;
+
+-- parallel with parallel-aware hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 1;
+set local work_mem = '128kB';
+set local enable_parallel_hash = on;
+explain (costs off)
+ select count(*) from simple r join extremely_skewed s using (id);
+select count(*) from simple r join extremely_skewed s using (id);
+select * from hash_join_batches(
+$$
+ select count(*) from simple r join extremely_skewed s using (id);
+$$);
+rollback to settings;
+
+-- A couple of other hash join tests unrelated to work_mem management.
+
+-- Check that EXPLAIN ANALYZE has data even if the leader doesn't participate
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+set local work_mem = '4MB';
+set local parallel_leader_participation = off;
+select * from hash_join_batches(
+$$
+ select count(*) from simple r join simple s using (id);
+$$);
+rollback to settings;
+
+-- Exercise rescans. We'll turn off parallel_leader_participation so
+-- that we can check that instrumentation comes back correctly.
+
+create table join_foo as select generate_series(1, 3) as id, 'xxxxx'::text as t;
+alter table join_foo set (parallel_workers = 0);
+create table join_bar as select generate_series(1, 10000) as id, 'xxxxx'::text as t;
+alter table join_bar set (parallel_workers = 2);
+
+-- multi-batch with rescan, parallel-oblivious
+savepoint settings;
+set enable_parallel_hash = off;
+set parallel_leader_participation = off;
+set min_parallel_table_scan_size = 0;
+set parallel_setup_cost = 0;
+set parallel_tuple_cost = 0;
+set max_parallel_workers_per_gather = 2;
+set enable_material = off;
+set enable_mergejoin = off;
+set work_mem = '64kB';
+explain (costs off)
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+select final > 1 as multibatch
+ from hash_join_batches(
+$$
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+$$);
+rollback to settings;
+
+-- single-batch with rescan, parallel-oblivious
+savepoint settings;
+set enable_parallel_hash = off;
+set parallel_leader_participation = off;
+set min_parallel_table_scan_size = 0;
+set parallel_setup_cost = 0;
+set parallel_tuple_cost = 0;
+set max_parallel_workers_per_gather = 2;
+set enable_material = off;
+set enable_mergejoin = off;
+set work_mem = '4MB';
+explain (costs off)
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+select final > 1 as multibatch
+ from hash_join_batches(
+$$
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+$$);
+rollback to settings;
+
+-- multi-batch with rescan, parallel-aware
+savepoint settings;
+set enable_parallel_hash = on;
+set parallel_leader_participation = off;
+set min_parallel_table_scan_size = 0;
+set parallel_setup_cost = 0;
+set parallel_tuple_cost = 0;
+set max_parallel_workers_per_gather = 2;
+set enable_material = off;
+set enable_mergejoin = off;
+set work_mem = '64kB';
+explain (costs off)
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+select final > 1 as multibatch
+ from hash_join_batches(
+$$
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+$$);
+rollback to settings;
+
+-- single-batch with rescan, parallel-aware
+savepoint settings;
+set enable_parallel_hash = on;
+set parallel_leader_participation = off;
+set min_parallel_table_scan_size = 0;
+set parallel_setup_cost = 0;
+set parallel_tuple_cost = 0;
+set max_parallel_workers_per_gather = 2;
+set enable_material = off;
+set enable_mergejoin = off;
+set work_mem = '4MB';
+explain (costs off)
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+select final > 1 as multibatch
+ from hash_join_batches(
+$$
+ select count(*) from join_foo
+ left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss
+ on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;
+$$);
+rollback to settings;
+
+-- A full outer join where every record is matched.
+
+-- non-parallel
+savepoint settings;
+set local max_parallel_workers_per_gather = 0;
+explain (costs off)
+ select count(*) from simple r full outer join simple s using (id);
+select count(*) from simple r full outer join simple s using (id);
+rollback to settings;
+
+-- parallelism not possible with parallel-oblivious outer hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+explain (costs off)
+ select count(*) from simple r full outer join simple s using (id);
+select count(*) from simple r full outer join simple s using (id);
+rollback to settings;
+
+-- An full outer join where every record is not matched.
+
+-- non-parallel
+savepoint settings;
+set local max_parallel_workers_per_gather = 0;
+explain (costs off)
+ select count(*) from simple r full outer join simple s on (r.id = 0 - s.id);
+select count(*) from simple r full outer join simple s on (r.id = 0 - s.id);
+rollback to settings;
+
+-- parallelism not possible with parallel-oblivious outer hash join
+savepoint settings;
+set local max_parallel_workers_per_gather = 2;
+explain (costs off)
+ select count(*) from simple r full outer join simple s on (r.id = 0 - s.id);
+select count(*) from simple r full outer join simple s on (r.id = 0 - s.id);
+rollback to settings;
+
+-- exercise special code paths for huge tuples (note use of non-strict
+-- expression and left join required to get the detoasted tuple into
+-- the hash table)
+
+-- parallel with parallel-aware hash join (hits ExecParallelHashLoadTuple and
+-- sts_puttuple oversized tuple cases because it's multi-batch)
+savepoint settings;
+set max_parallel_workers_per_gather = 2;
+set enable_parallel_hash = on;
+set work_mem = '128kB';
+explain (costs off)
+ select length(max(s.t))
+ from wide left join (select id, coalesce(t, '') || '' as t from wide) s using (id);
+select length(max(s.t))
+from wide left join (select id, coalesce(t, '') || '' as t from wide) s using (id);
+select final > 1 as multibatch
+ from hash_join_batches(
+$$
+ select length(max(s.t))
+ from wide left join (select id, coalesce(t, '') || '' as t from wide) s using (id);
+$$);
+rollback to settings;
+
+rollback;
+
+
+-- Verify that hash key expressions reference the correct
+-- nodes. Hashjoin's hashkeys need to reference its outer plan, Hash's
+-- need to reference Hash's outer plan (which is below HashJoin's
+-- inner plan). It's not trivial to verify that the references are
+-- correct (we don't display the hashkeys themselves), but if the
+-- hashkeys contain subplan references, those will be displayed. Force
+-- subplans to appear just about everywhere.
+--
+-- Bug report:
+-- https://www.postgresql.org/message-id/CAPpHfdvGVegF_TKKRiBrSmatJL2dR9uwFCuR%2BteQ_8tEXU8mxg%40mail.gmail.com
+--
+BEGIN;
+SET LOCAL enable_sort = OFF; -- avoid mergejoins
+SET LOCAL from_collapse_limit = 1; -- allows easy changing of join order
+
+CREATE TABLE hjtest_1 (a text, b int, id int, c bool);
+CREATE TABLE hjtest_2 (a bool, id int, b text, c int);
+
+INSERT INTO hjtest_1(a, b, id, c) VALUES ('text', 2, 1, false); -- matches
+INSERT INTO hjtest_1(a, b, id, c) VALUES ('text', 1, 2, false); -- fails id join condition
+INSERT INTO hjtest_1(a, b, id, c) VALUES ('text', 20, 1, false); -- fails < 50
+INSERT INTO hjtest_1(a, b, id, c) VALUES ('text', 1, 1, false); -- fails (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5)
+
+INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 1, 'another', 2); -- matches
+INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 3, 'another', 7); -- fails id join condition
+INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 1, 'another', 90); -- fails < 55
+INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 1, 'another', 3); -- fails (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5)
+INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 1, 'text', 1); -- fails hjtest_1.a <> hjtest_2.b;
+
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT hjtest_1.a a1, hjtest_2.a a2,hjtest_1.tableoid::regclass t1, hjtest_2.tableoid::regclass t2
+FROM hjtest_1, hjtest_2
+WHERE
+ hjtest_1.id = (SELECT 1 WHERE hjtest_2.id = 1)
+ AND (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5)
+ AND (SELECT hjtest_1.b * 5) < 50
+ AND (SELECT hjtest_2.c * 5) < 55
+ AND hjtest_1.a <> hjtest_2.b;
+
+SELECT hjtest_1.a a1, hjtest_2.a a2,hjtest_1.tableoid::regclass t1, hjtest_2.tableoid::regclass t2
+FROM hjtest_1, hjtest_2
+WHERE
+ hjtest_1.id = (SELECT 1 WHERE hjtest_2.id = 1)
+ AND (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5)
+ AND (SELECT hjtest_1.b * 5) < 50
+ AND (SELECT hjtest_2.c * 5) < 55
+ AND hjtest_1.a <> hjtest_2.b;
+
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT hjtest_1.a a1, hjtest_2.a a2,hjtest_1.tableoid::regclass t1, hjtest_2.tableoid::regclass t2
+FROM hjtest_2, hjtest_1
+WHERE
+ hjtest_1.id = (SELECT 1 WHERE hjtest_2.id = 1)
+ AND (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5)
+ AND (SELECT hjtest_1.b * 5) < 50
+ AND (SELECT hjtest_2.c * 5) < 55
+ AND hjtest_1.a <> hjtest_2.b;
+
+SELECT hjtest_1.a a1, hjtest_2.a a2,hjtest_1.tableoid::regclass t1, hjtest_2.tableoid::regclass t2
+FROM hjtest_2, hjtest_1
+WHERE
+ hjtest_1.id = (SELECT 1 WHERE hjtest_2.id = 1)
+ AND (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5)
+ AND (SELECT hjtest_1.b * 5) < 50
+ AND (SELECT hjtest_2.c * 5) < 55
+ AND hjtest_1.a <> hjtest_2.b;
+
+ROLLBACK;
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
new file mode 100644
index 0000000..e366c6f
--- /dev/null
+++ b/src/test/regress/sql/json.sql
@@ -0,0 +1,852 @@
+-- Strings.
+SELECT '""'::json; -- OK.
+SELECT $$''$$::json; -- ERROR, single quotes are not allowed
+SELECT '"abc"'::json; -- OK
+SELECT '"abc'::json; -- ERROR, quotes not closed
+SELECT '"abc
+def"'::json; -- ERROR, unescaped newline in string constant
+SELECT '"\n\"\\"'::json; -- OK, legal escapes
+SELECT '"\v"'::json; -- ERROR, not a valid JSON escape
+-- see json_encoding test for input with unicode escapes
+
+-- Numbers.
+SELECT '1'::json; -- OK
+SELECT '0'::json; -- OK
+SELECT '01'::json; -- ERROR, not valid according to JSON spec
+SELECT '0.1'::json; -- OK
+SELECT '9223372036854775808'::json; -- OK, even though it's too large for int8
+SELECT '1e100'::json; -- OK
+SELECT '1.3e100'::json; -- OK
+SELECT '1f2'::json; -- ERROR
+SELECT '0.x1'::json; -- ERROR
+SELECT '1.3ex100'::json; -- ERROR
+
+-- Arrays.
+SELECT '[]'::json; -- OK
+SELECT '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'::json; -- OK
+SELECT '[1,2]'::json; -- OK
+SELECT '[1,2,]'::json; -- ERROR, trailing comma
+SELECT '[1,2'::json; -- ERROR, no closing bracket
+SELECT '[1,[2]'::json; -- ERROR, no closing bracket
+
+-- Objects.
+SELECT '{}'::json; -- OK
+SELECT '{"abc"}'::json; -- ERROR, no value
+SELECT '{"abc":1}'::json; -- OK
+SELECT '{1:"abc"}'::json; -- ERROR, keys must be strings
+SELECT '{"abc",1}'::json; -- ERROR, wrong separator
+SELECT '{"abc"=1}'::json; -- ERROR, totally wrong separator
+SELECT '{"abc"::1}'::json; -- ERROR, another wrong separator
+SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::json; -- OK
+SELECT '{"abc":1:2}'::json; -- ERROR, colon in wrong spot
+SELECT '{"abc":1,3}'::json; -- ERROR, no value
+
+-- Recursion.
+SET max_stack_depth = '100kB';
+SELECT repeat('[', 10000)::json;
+SELECT repeat('{"a":', 10000)::json;
+RESET max_stack_depth;
+
+-- Miscellaneous stuff.
+SELECT 'true'::json; -- OK
+SELECT 'false'::json; -- OK
+SELECT 'null'::json; -- OK
+SELECT ' true '::json; -- OK, even with extra whitespace
+SELECT 'true false'::json; -- ERROR, too many values
+SELECT 'true, false'::json; -- ERROR, too many values
+SELECT 'truf'::json; -- ERROR, not a keyword
+SELECT 'trues'::json; -- ERROR, not a keyword
+SELECT ''::json; -- ERROR, no value
+SELECT ' '::json; -- ERROR, no value
+
+-- Multi-line JSON input to check ERROR reporting
+SELECT '{
+ "one": 1,
+ "two":"two",
+ "three":
+ true}'::json; -- OK
+SELECT '{
+ "one": 1,
+ "two":,"two", -- ERROR extraneous comma before field "two"
+ "three":
+ true}'::json;
+SELECT '{
+ "one": 1,
+ "two":"two",
+ "averyveryveryveryveryveryveryveryveryverylongfieldname":}'::json;
+-- ERROR missing value for last field
+
+--constructors
+-- array_to_json
+
+SELECT array_to_json(array(select 1 as a));
+SELECT array_to_json(array_agg(q),false) from (select x as b, x * 2 as c from generate_series(1,3) x) q;
+SELECT array_to_json(array_agg(q),true) from (select x as b, x * 2 as c from generate_series(1,3) x) q;
+SELECT array_to_json(array_agg(q),false)
+ 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 array_to_json(array_agg(x),false) from generate_series(5,10) x;
+SELECT array_to_json('{{1,5},{99,100}}'::int[]);
+
+-- row_to_json
+SELECT row_to_json(row(1,'foo'));
+
+SELECT row_to_json(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 row_to_json(q,true)
+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;
+
+CREATE TEMP TABLE rows AS
+SELECT x, 'txt' || x as y
+FROM generate_series(1,3) AS x;
+
+SELECT row_to_json(q,true)
+FROM rows q;
+
+SELECT row_to_json(row((select array_agg(x) as d from generate_series(5,10) x)),false);
+
+-- anyarray column
+
+analyze rows;
+
+select attname, to_json(histogram_bounds) histogram_bounds
+from pg_stats
+where tablename = 'rows' and
+ schemaname = pg_my_temp_schema()::regnamespace::text
+order by 1;
+
+-- to_json, timestamps
+
+select to_json(timestamp '2014-05-28 12:22:35.614298');
+
+BEGIN;
+SET LOCAL TIME ZONE 10.5;
+select to_json(timestamptz '2014-05-28 12:22:35.614298-04');
+SET LOCAL TIME ZONE -8;
+select to_json(timestamptz '2014-05-28 12:22:35.614298-04');
+COMMIT;
+
+select to_json(date '2014-05-28');
+
+select to_json(date 'Infinity');
+select to_json(date '-Infinity');
+select to_json(timestamp 'Infinity');
+select to_json(timestamp '-Infinity');
+select to_json(timestamptz 'Infinity');
+select to_json(timestamptz '-Infinity');
+
+--json_agg
+
+SELECT json_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 json_agg(q ORDER BY x, y)
+ FROM rows q;
+
+UPDATE rows SET x = NULL WHERE x = 1;
+
+SELECT json_agg(q ORDER BY x NULLS FIRST, y)
+ FROM rows q;
+
+-- non-numeric output
+SELECT row_to_json(q)
+FROM (SELECT 'NaN'::float8 AS "float8field") q;
+
+SELECT row_to_json(q)
+FROM (SELECT 'Infinity'::float8 AS "float8field") q;
+
+SELECT row_to_json(q)
+FROM (SELECT '-Infinity'::float8 AS "float8field") q;
+
+-- json input
+SELECT row_to_json(q)
+FROM (SELECT '{"a":1,"b": [2,3,4,"d","e","f"],"c":{"p":1,"q":2}}'::json AS "jsonfield") q;
+
+
+-- json extraction functions
+
+CREATE TEMP TABLE test_json (
+ json_type text,
+ test_json json
+);
+
+INSERT INTO test_json 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_json
+WHERE json_type = 'scalar';
+
+SELECT test_json -> 'x'
+FROM test_json
+WHERE json_type = 'array';
+
+SELECT test_json -> 'x'
+FROM test_json
+WHERE json_type = 'object';
+
+SELECT test_json->'field2'
+FROM test_json
+WHERE json_type = 'object';
+
+SELECT test_json->>'field2'
+FROM test_json
+WHERE json_type = 'object';
+
+SELECT test_json -> 2
+FROM test_json
+WHERE json_type = 'scalar';
+
+SELECT test_json -> 2
+FROM test_json
+WHERE json_type = 'array';
+
+SELECT test_json -> -1
+FROM test_json
+WHERE json_type = 'array';
+
+SELECT test_json -> 2
+FROM test_json
+WHERE json_type = 'object';
+
+SELECT test_json->>2
+FROM test_json
+WHERE json_type = 'array';
+
+SELECT test_json ->> 6 FROM test_json WHERE json_type = 'array';
+SELECT test_json ->> 7 FROM test_json WHERE json_type = 'array';
+
+SELECT test_json ->> 'field4' FROM test_json WHERE json_type = 'object';
+SELECT test_json ->> 'field5' FROM test_json WHERE json_type = 'object';
+SELECT test_json ->> 'field6' FROM test_json WHERE json_type = 'object';
+
+SELECT json_object_keys(test_json)
+FROM test_json
+WHERE json_type = 'scalar';
+
+SELECT json_object_keys(test_json)
+FROM test_json
+WHERE json_type = 'array';
+
+SELECT json_object_keys(test_json)
+FROM test_json
+WHERE json_type = 'object';
+
+-- test extending object_keys resultset - initial resultset size is 256
+
+select count(*) from
+ (select json_object_keys(json_object(array_agg(g)))
+ from (select unnest(array['f'||n,n::text])as g
+ from generate_series(1,300) as n) x ) y;
+
+-- nulls
+
+select (test_json->'field3') is null as expect_false
+from test_json
+where json_type = 'object';
+
+select (test_json->>'field3') is null as expect_true
+from test_json
+where json_type = 'object';
+
+select (test_json->3) is null as expect_false
+from test_json
+where json_type = 'array';
+
+select (test_json->>3) is null as expect_true
+from test_json
+where json_type = 'array';
+
+-- corner cases
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json -> null::text;
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json -> null::int;
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json -> 1;
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json -> -1;
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json -> 'z';
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json -> '';
+select '[{"b": "c"}, {"b": "cc"}]'::json -> 1;
+select '[{"b": "c"}, {"b": "cc"}]'::json -> 3;
+select '[{"b": "c"}, {"b": "cc"}]'::json -> 'z';
+select '{"a": "c", "b": null}'::json -> 'b';
+select '"foo"'::json -> 1;
+select '"foo"'::json -> 'z';
+
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json ->> null::text;
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json ->> null::int;
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json ->> 1;
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json ->> 'z';
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json ->> '';
+select '[{"b": "c"}, {"b": "cc"}]'::json ->> 1;
+select '[{"b": "c"}, {"b": "cc"}]'::json ->> 3;
+select '[{"b": "c"}, {"b": "cc"}]'::json ->> 'z';
+select '{"a": "c", "b": null}'::json ->> 'b';
+select '"foo"'::json ->> 1;
+select '"foo"'::json ->> 'z';
+
+-- array length
+
+SELECT json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]');
+
+SELECT json_array_length('[]');
+
+SELECT json_array_length('{"f1":1,"f2":[5,6]}');
+
+SELECT json_array_length('4');
+
+-- each
+
+select json_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}');
+select * from json_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+
+select json_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}');
+select * from json_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q;
+
+-- extract_path, extract_path_as_text
+
+select json_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+select json_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+select json_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+select json_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+select json_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6');
+select json_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2');
+select json_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text);
+select json_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text);
+
+-- extract_path nulls
+
+select json_extract_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') is null as expect_false;
+select json_extract_path_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') is null as expect_true;
+select json_extract_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') is null as expect_false;
+select json_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"}}'::json#>array['f4','f6'];
+select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f2'];
+select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f2','0'];
+select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>array['f2','1'];
+
+select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f4','f6'];
+select '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f2'];
+select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f2','0'];
+select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>array['f2','1'];
+
+-- corner cases for same
+select '{"a": {"b":{"c": "foo"}}}'::json #> '{}';
+select '[1,2,3]'::json #> '{}';
+select '"foo"'::json #> '{}';
+select '42'::json #> '{}';
+select 'null'::json #> '{}';
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a'];
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a', null];
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a', ''];
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a','b'];
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a','b','c'];
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a','b','c','d'];
+select '{"a": {"b":{"c": "foo"}}}'::json #> array['a','z','c'];
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json #> array['a','1','b'];
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json #> array['a','z','b'];
+select '[{"b": "c"}, {"b": "cc"}]'::json #> array['1','b'];
+select '[{"b": "c"}, {"b": "cc"}]'::json #> array['z','b'];
+select '[{"b": "c"}, {"b": null}]'::json #> array['1','b'];
+select '"foo"'::json #> array['z'];
+select '42'::json #> array['f2'];
+select '42'::json #> array['0'];
+
+select '{"a": {"b":{"c": "foo"}}}'::json #>> '{}';
+select '[1,2,3]'::json #>> '{}';
+select '"foo"'::json #>> '{}';
+select '42'::json #>> '{}';
+select 'null'::json #>> '{}';
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a'];
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a', null];
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a', ''];
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a','b'];
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a','b','c'];
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a','b','c','d'];
+select '{"a": {"b":{"c": "foo"}}}'::json #>> array['a','z','c'];
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json #>> array['a','1','b'];
+select '{"a": [{"b": "c"}, {"b": "cc"}]}'::json #>> array['a','z','b'];
+select '[{"b": "c"}, {"b": "cc"}]'::json #>> array['1','b'];
+select '[{"b": "c"}, {"b": "cc"}]'::json #>> array['z','b'];
+select '[{"b": "c"}, {"b": null}]'::json #>> array['1','b'];
+select '"foo"'::json #>> array['z'];
+select '42'::json #>> array['f2'];
+select '42'::json #>> array['0'];
+
+-- array_elements
+
+select json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
+select * from json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q;
+select json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
+select * from json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q;
+
+-- populate_record
+create type jpop as (a text, b int, c timestamp);
+
+CREATE DOMAIN js_int_not_null AS int NOT NULL;
+CREATE DOMAIN js_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3);
+CREATE DOMAIN js_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3);
+
+create type j_unordered_pair as (x int, y int);
+create domain j_ordered_pair as j_unordered_pair check((value).x <= (value).y);
+
+CREATE TYPE jsrec AS (
+ i int,
+ ia _int4,
+ ia1 int[],
+ ia2 int[][],
+ ia3 int[][][],
+ ia1d js_int_array_1d,
+ ia2d js_int_array_2d,
+ t text,
+ ta text[],
+ c char(10),
+ ca char(10)[],
+ ts timestamp,
+ js json,
+ jsb jsonb,
+ jsa json[],
+ rec jpop,
+ reca jpop[]
+);
+
+CREATE TYPE jsrec_i_not_null AS (
+ i js_int_not_null
+);
+
+select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q;
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}') q;
+
+select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q;
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}') q;
+
+select * from json_populate_record(null::jpop,'{"a":[100,200,false],"x":43.2}') q;
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":[100,200,false],"x":43.2}') q;
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"c":[100,200,false],"x":43.2}') q;
+
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{}') q;
+
+SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"x": 43.2}') q;
+SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"i": null}') q;
+SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"i": 12345}') q;
+
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": null}') q;
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": 123}') q;
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [1, "2", null, 4]}') q;
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1, 2], [3, 4]]}') q;
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1], 2]}') q;
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1], [2, 3]]}') q;
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": "{1,2,3}"}') q;
+
+SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": null}') q;
+SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": 123}') q;
+SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": [1, "2", null, 4]}') q;
+SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": [[1, 2, 3]]}') q;
+
+SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": null}') q;
+SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": 123}') q;
+SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": [1, "2", null, 4]}') q;
+SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": [1, "2", null]}') q;
+
+SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [1, "2", null, 4]}') q;
+SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[1, 2], [null, 4]]}') q;
+SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[], []]}') q;
+SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[1, 2], [3]]}') q;
+SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[1, 2], 3, 4]}') q;
+
+SELECT ia2d FROM json_populate_record(NULL::jsrec, '{"ia2d": [[1, "2"], [null, 4]]}') q;
+SELECT ia2d FROM json_populate_record(NULL::jsrec, '{"ia2d": [[1, "2", 3], [null, 5, 6]]}') q;
+
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [1, "2", null, 4]}') q;
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [[1, 2], [null, 4]]}') q;
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[], []], [[], []], [[], []] ]}') q;
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[1, 2]], [[3, 4]] ]}') q;
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8]] ]}') q;
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8], [9, 10]] ]}') q;
+
+SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": null}') q;
+SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": 123}') q;
+SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": [1, "2", null, 4]}') q;
+SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": [[1, 2, 3], {"k": "v"}]}') q;
+
+SELECT c FROM json_populate_record(NULL::jsrec, '{"c": null}') q;
+SELECT c FROM json_populate_record(NULL::jsrec, '{"c": "aaa"}') q;
+SELECT c FROM json_populate_record(NULL::jsrec, '{"c": "aaaaaaaaaa"}') q;
+SELECT c FROM json_populate_record(NULL::jsrec, '{"c": "aaaaaaaaaaaaa"}') q;
+
+SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": null}') q;
+SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": 123}') q;
+SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": [1, "2", null, 4]}') q;
+SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": ["aaaaaaaaaaaaaaaa"]}') q;
+SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": [[1, 2, 3], {"k": "v"}]}') q;
+
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": null}') q;
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": true}') q;
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": 123.45}') q;
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": "123.45"}') q;
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": "abc"}') q;
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": [123, "123", null, {"key": "value"}]}') q;
+SELECT js FROM json_populate_record(NULL::jsrec, '{"js": {"a": "bbb", "b": null, "c": 123.45}}') q;
+
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": null}') q;
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": true}') q;
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": 123.45}') q;
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": "123.45"}') q;
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": "abc"}') q;
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": [123, "123", null, {"key": "value"}]}') q;
+SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": {"a": "bbb", "b": null, "c": 123.45}}') q;
+
+SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": null}') q;
+SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": 123}') q;
+SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": [1, "2", null, 4]}') q;
+SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": ["aaa", null, [1, 2, "3", {}], { "k" : "v" }]}') q;
+
+SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": 123}') q;
+SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": [1, 2]}') q;
+SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}') q;
+SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": "(abc,42,01.02.2003)"}') q;
+
+SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": 123}') q;
+SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": [1, 2]}') q;
+SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}]}') q;
+SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": ["(abc,42,01.02.2003)"]}') q;
+SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": "{\"(abc,42,01.02.2003)\"}"}') q;
+
+SELECT rec FROM json_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')::jpop,NULL)::jsrec,
+ '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}'
+) q;
+
+-- anonymous record type
+SELECT json_populate_record(null::record, '{"x": 0, "y": 1}');
+SELECT json_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
+SELECT * FROM
+ json_populate_record(null::record, '{"x": 776}') AS (x int, y int);
+
+-- composite domain
+SELECT json_populate_record(null::j_ordered_pair, '{"x": 0, "y": 1}');
+SELECT json_populate_record(row(1,2)::j_ordered_pair, '{"x": 0}');
+SELECT json_populate_record(row(1,2)::j_ordered_pair, '{"x": 1, "y": 0}');
+
+-- populate_recordset
+
+select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+
+create type jpop2 as (a int, b json, c int, d int);
+select * from json_populate_recordset(null::jpop2, '[{"a":2,"c":3,"b":{"z":4},"d":6}]') q;
+
+select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
+select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
+
+-- anonymous record type
+SELECT json_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
+SELECT json_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
+SELECT i, json_populate_recordset(row(i,50), '[{"f1":"42"},{"f2":"43"}]')
+FROM (VALUES (1),(2)) v(i);
+SELECT * FROM
+ json_populate_recordset(null::record, '[{"x": 776}]') AS (x int, y int);
+
+-- empty array is a corner case
+SELECT json_populate_recordset(null::record, '[]');
+SELECT json_populate_recordset(row(1,2), '[]');
+SELECT * FROM json_populate_recordset(NULL::jpop,'[]') q;
+SELECT * FROM
+ json_populate_recordset(null::record, '[]') AS (x int, y int);
+
+-- composite domain
+SELECT json_populate_recordset(null::j_ordered_pair, '[{"x": 0, "y": 1}]');
+SELECT json_populate_recordset(row(1,2)::j_ordered_pair, '[{"x": 0}, {"y": 3}]');
+SELECT json_populate_recordset(row(1,2)::j_ordered_pair, '[{"x": 1, "y": 0}]');
+
+-- negative cases where the wrong record type is supplied
+select * from json_populate_recordset(row(0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+select * from json_populate_recordset(row(0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+select * from json_populate_recordset(row(0::int,0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+select * from json_populate_recordset(row(1000000000::int,50::int),'[{"b":"2"},{"a":"3"}]') q (a text, b text);
+
+-- test type info caching in json_populate_record()
+CREATE TEMP TABLE jspoptest (js json);
+
+INSERT INTO jspoptest
+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}]
+}'::json
+FROM generate_series(1, 3);
+
+SELECT (json_populate_record(NULL::jsrec, js)).* FROM jspoptest;
+
+DROP TYPE jsrec;
+DROP TYPE jsrec_i_not_null;
+DROP DOMAIN js_int_not_null;
+DROP DOMAIN js_int_array_1d;
+DROP DOMAIN js_int_array_2d;
+DROP DOMAIN j_ordered_pair;
+DROP TYPE j_unordered_pair;
+
+--json_typeof() function
+select value, json_typeof(value)
+ from (values (json '123.4'),
+ (json '-1'),
+ (json '"foo"'),
+ (json 'true'),
+ (json 'false'),
+ (json 'null'),
+ (json '[1, 2, 3]'),
+ (json '[]'),
+ (json '{"x":"foo", "y":123}'),
+ (json '{}'),
+ (NULL::json))
+ as data(value);
+
+-- json_build_array, json_build_object, json_object_agg
+
+SELECT json_build_array('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}');
+SELECT json_build_array('a', NULL); -- ok
+SELECT json_build_array(VARIADIC NULL::text[]); -- ok
+SELECT json_build_array(VARIADIC '{}'::text[]); -- ok
+SELECT json_build_array(VARIADIC '{a,b,c}'::text[]); -- ok
+SELECT json_build_array(VARIADIC ARRAY['a', NULL]::text[]); -- ok
+SELECT json_build_array(VARIADIC '{1,2,3,4}'::text[]); -- ok
+SELECT json_build_array(VARIADIC '{1,2,3,4}'::int[]); -- ok
+SELECT json_build_array(VARIADIC '{{1,4},{2,5},{3,6}}'::int[][]); -- ok
+
+SELECT json_build_object('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}');
+
+SELECT json_build_object(
+ 'a', json_build_object('b',false,'c',99),
+ 'd', json_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 json_build_object('{a,b,c}'::text[]); -- error
+SELECT json_build_object('{a,b,c}'::text[], '{d,e,f}'::text[]); -- error, key cannot be array
+SELECT json_build_object('a', 'b', 'c'); -- error
+SELECT json_build_object(NULL, 'a'); -- error, key cannot be NULL
+SELECT json_build_object('a', NULL); -- ok
+SELECT json_build_object(VARIADIC NULL::text[]); -- ok
+SELECT json_build_object(VARIADIC '{}'::text[]); -- ok
+SELECT json_build_object(VARIADIC '{a,b,c}'::text[]); -- error
+SELECT json_build_object(VARIADIC ARRAY['a', NULL]::text[]); -- ok
+SELECT json_build_object(VARIADIC ARRAY[NULL, 'a']::text[]); -- error, key cannot be NULL
+SELECT json_build_object(VARIADIC '{1,2,3,4}'::text[]); -- ok
+SELECT json_build_object(VARIADIC '{1,2,3,4}'::int[]); -- ok
+SELECT json_build_object(VARIADIC '{{1,4},{2,5},{3,6}}'::int[][]); -- ok
+
+-- empty objects/arrays
+SELECT json_build_array();
+
+SELECT json_build_object();
+
+-- make sure keys are quoted
+SELECT json_build_object(1,2);
+
+-- keys must be scalar and not null
+SELECT json_build_object(null,2);
+
+SELECT json_build_object(r,2) FROM (SELECT 1 AS a, 2 AS b) r;
+
+SELECT json_build_object(json '{"a":1,"b":2}', 3);
+
+SELECT json_build_object('{1,2,3}'::int[], 3);
+
+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 json_build_object('turbines',json_object_agg(serial_num,json_build_object('name',name,'type',type)))
+FROM foo;
+
+SELECT json_object_agg(name, type) FROM foo;
+
+INSERT INTO foo VALUES (999999, NULL, 'bar');
+SELECT json_object_agg(name, type) FROM foo;
+
+-- json_object
+
+-- empty object, one dimension
+SELECT json_object('{}');
+
+-- empty object, two dimensions
+SELECT json_object('{}', '{}');
+
+-- one dimension
+SELECT json_object('{a,1,b,2,3,NULL,"d e f","a b c"}');
+
+-- same but with two dimensions
+SELECT json_object('{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}');
+
+-- odd number error
+SELECT json_object('{a,b,c}');
+
+-- one column error
+SELECT json_object('{{a},{b}}');
+
+-- too many columns error
+SELECT json_object('{{a,b,c},{b,c,d}}');
+
+-- too many dimensions error
+SELECT json_object('{{{a,b},{c,d}},{{b,c},{d,e}}}');
+
+--two argument form of json_object
+
+select json_object('{a,b,c,"d e f"}','{1,2,3,"a b c"}');
+
+-- too many dimensions
+SELECT json_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 json_object('{a,b,c,"d e f",g}','{1,2,3,"a b c"}');
+
+select json_object('{a,b,c,"d e f"}','{1,2,3,"a b c",g}');
+
+-- null key error
+
+select json_object('{a,b,NULL,"d e f"}','{1,2,3,"a b c"}');
+
+-- empty key is allowed
+
+select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}');
+
+
+-- json_to_record and json_to_recordset
+
+select * from json_to_record('{"a":1,"b":"foo","c":"bar"}')
+ as x(a int, b text, d text);
+
+select * from json_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar","c":true}]')
+ as x(a int, b text, c boolean);
+
+select * from json_to_recordset('[{"a":1,"b":{"d":"foo"},"c":true},{"a":2,"c":false,"b":{"d":"bar"}}]')
+ as x(a int, b json, c boolean);
+
+select *, c is null as c_is_null
+from json_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}}'::json)
+ as t(a int, b json, c text, x int, ca char(5)[], ia int[][], r jpop);
+
+select *, c is null as c_is_null
+from json_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::json)
+ as t(a int, b json, c text, x int);
+
+select * from json_to_record('{"ia": null}') as x(ia _int4);
+select * from json_to_record('{"ia": 123}') as x(ia _int4);
+select * from json_to_record('{"ia": [1, "2", null, 4]}') as x(ia _int4);
+select * from json_to_record('{"ia": [[1, 2], [3, 4]]}') as x(ia _int4);
+select * from json_to_record('{"ia": [[1], 2]}') as x(ia _int4);
+select * from json_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4);
+
+select * from json_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]);
+select * from json_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]);
+select * from json_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]);
+
+select * from json_to_record('{"out": {"key": 1}}') as x(out json);
+select * from json_to_record('{"out": [{"key": 1}]}') as x(out json);
+select * from json_to_record('{"out": "{\"key\": 1}"}') as x(out json);
+select * from json_to_record('{"out": {"key": 1}}') as x(out jsonb);
+select * from json_to_record('{"out": [{"key": 1}]}') as x(out jsonb);
+select * from json_to_record('{"out": "{\"key\": 1}"}') as x(out jsonb);
+
+-- json_strip_nulls
+
+select json_strip_nulls(null);
+
+select json_strip_nulls('1');
+
+select json_strip_nulls('"a string"');
+
+select json_strip_nulls('null');
+
+select json_strip_nulls('[1,2,null,3,4]');
+
+select json_strip_nulls('{"a":1,"b":null,"c":[2,null,3],"d":{"e":4,"f":null}}');
+
+select json_strip_nulls('[1,{"a":1,"b":null,"c":2},3]');
+
+-- an empty object is not null and should not be stripped
+select json_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }');
+
+-- json to tsvector
+select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::json);
+
+-- json to tsvector with config
+select to_tsvector('simple', '{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::json);
+
+-- json to tsvector with stop words
+select to_tsvector('english', '{"a": "aaa in bbb ddd ccc", "b": ["the eee fff ggg"], "c": {"d": "hhh. iii"}}'::json);
+
+-- json to tsvector with numeric values
+select to_tsvector('english', '{"a": "aaa in bbb ddd ccc", "b": 123, "c": 456}'::json);
+
+-- json_to_tsvector
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"all"');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"key"');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"string"');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"numeric"');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"boolean"');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '["string", "numeric"]');
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"all"');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"key"');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"string"');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"numeric"');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '"boolean"');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '["string", "numeric"]');
+
+-- to_tsvector corner cases
+select to_tsvector('""'::json);
+select to_tsvector('{}'::json);
+select to_tsvector('[]'::json);
+select to_tsvector('null'::json);
+
+-- json_to_tsvector corner cases
+select json_to_tsvector('""'::json, '"all"');
+select json_to_tsvector('{}'::json, '"all"');
+select json_to_tsvector('[]'::json, '"all"');
+select json_to_tsvector('null'::json, '"all"');
+
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '""');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '{}');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '[]');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, 'null');
+select json_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::json, '["all", null]');
+
+-- ts_headline for json
+select ts_headline('{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]}'::json, tsquery('bbb & ddd & hhh'));
+select ts_headline('english', '{"a": "aaa bbb", "b": {"c": "ccc ddd fff"}, "d": ["ggg hhh", "iii jjj"]}'::json, tsquery('bbb & ddd & hhh'));
+select ts_headline('{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]}'::json, 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"]}'::json, tsquery('bbb & ddd & hhh'), 'StartSel = <, StopSel = >');
+
+-- corner cases for ts_headline with json
+select ts_headline('null'::json, tsquery('aaa & bbb'));
+select ts_headline('{}'::json, tsquery('aaa & bbb'));
+select ts_headline('[]'::json, tsquery('aaa & bbb'));
diff --git a/src/test/regress/sql/json_encoding.sql b/src/test/regress/sql/json_encoding.sql
new file mode 100644
index 0000000..d7fac69
--- /dev/null
+++ b/src/test/regress/sql/json_encoding.sql
@@ -0,0 +1,78 @@
+--
+-- encoding-sensitive tests for json and jsonb
+--
+
+-- We provide expected-results files for UTF8 (json_encoding.out)
+-- and for SQL_ASCII (json_encoding_1.out). Skip otherwise.
+SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
+ AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+
+SELECT getdatabaseencoding(); -- just to label the results files
+
+-- first json
+
+-- basic unicode input
+SELECT '"\u"'::json; -- ERROR, incomplete escape
+SELECT '"\u00"'::json; -- ERROR, incomplete escape
+SELECT '"\u000g"'::json; -- ERROR, g is not a hex digit
+SELECT '"\u0000"'::json; -- OK, legal escape
+SELECT '"\uaBcD"'::json; -- OK, uppercase and lower case both OK
+
+-- handling of unicode surrogate pairs
+
+select json '{ "a": "\ud83d\ude04\ud83d\udc36" }' -> 'a' as correct_in_utf8;
+select json '{ "a": "\ud83d\ud83d" }' -> 'a'; -- 2 high surrogates in a row
+select json '{ "a": "\ude04\ud83d" }' -> 'a'; -- surrogates in wrong order
+select json '{ "a": "\ud83dX" }' -> 'a'; -- orphan high surrogate
+select json '{ "a": "\ude04X" }' -> 'a'; -- orphan low surrogate
+
+--handling of simple unicode escapes
+
+select json '{ "a": "the Copyright \u00a9 sign" }' as correct_in_utf8;
+select json '{ "a": "dollar \u0024 character" }' as correct_everywhere;
+select json '{ "a": "dollar \\u0024 character" }' as not_an_escape;
+select json '{ "a": "null \u0000 escape" }' as not_unescaped;
+select json '{ "a": "null \\u0000 escape" }' as not_an_escape;
+
+select json '{ "a": "the Copyright \u00a9 sign" }' ->> 'a' as correct_in_utf8;
+select json '{ "a": "dollar \u0024 character" }' ->> 'a' as correct_everywhere;
+select json '{ "a": "dollar \\u0024 character" }' ->> 'a' as not_an_escape;
+select json '{ "a": "null \u0000 escape" }' ->> 'a' as fails;
+select json '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape;
+
+-- then jsonb
+
+-- basic unicode input
+SELECT '"\u"'::jsonb; -- ERROR, incomplete escape
+SELECT '"\u00"'::jsonb; -- ERROR, incomplete escape
+SELECT '"\u000g"'::jsonb; -- ERROR, g is not a hex digit
+SELECT '"\u0045"'::jsonb; -- OK, legal escape
+SELECT '"\u0000"'::jsonb; -- ERROR, we don't support U+0000
+-- use octet_length here so we don't get an odd unicode char in the
+-- output
+SELECT octet_length('"\uaBcD"'::jsonb::text); -- OK, uppercase and lower case both OK
+
+-- handling of unicode surrogate pairs
+
+SELECT octet_length((jsonb '{ "a": "\ud83d\ude04\ud83d\udc36" }' -> 'a')::text) AS correct_in_utf8;
+SELECT jsonb '{ "a": "\ud83d\ud83d" }' -> 'a'; -- 2 high surrogates in a row
+SELECT jsonb '{ "a": "\ude04\ud83d" }' -> 'a'; -- surrogates in wrong order
+SELECT jsonb '{ "a": "\ud83dX" }' -> 'a'; -- orphan high surrogate
+SELECT jsonb '{ "a": "\ude04X" }' -> 'a'; -- orphan low surrogate
+
+-- handling of simple unicode escapes
+
+SELECT jsonb '{ "a": "the Copyright \u00a9 sign" }' as correct_in_utf8;
+SELECT jsonb '{ "a": "dollar \u0024 character" }' as correct_everywhere;
+SELECT jsonb '{ "a": "dollar \\u0024 character" }' as not_an_escape;
+SELECT jsonb '{ "a": "null \u0000 escape" }' as fails;
+SELECT jsonb '{ "a": "null \\u0000 escape" }' as not_an_escape;
+
+SELECT jsonb '{ "a": "the Copyright \u00a9 sign" }' ->> 'a' as correct_in_utf8;
+SELECT jsonb '{ "a": "dollar \u0024 character" }' ->> 'a' as correct_everywhere;
+SELECT jsonb '{ "a": "dollar \\u0024 character" }' ->> 'a' as not_an_escape;
+SELECT jsonb '{ "a": "null \u0000 escape" }' ->> 'a' as fails;
+SELECT jsonb '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
new file mode 100644
index 0000000..5016f29
--- /dev/null
+++ b/src/test/regress/sql/jsonb.sql
@@ -0,0 +1,1484 @@
+-- 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;
+
+-- 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;
+
+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 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;
+
+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 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';
+
+-- 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/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
new file mode 100644
index 0000000..60f73cb
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,587 @@
+select jsonb '{"a": 12}' @? '$';
+select jsonb '{"a": 12}' @? '1';
+select jsonb '{"a": 12}' @? '$.a.b';
+select jsonb '{"a": 12}' @? '$.b';
+select jsonb '{"a": 12}' @? '$.a + 2';
+select jsonb '{"a": 12}' @? '$.b + 2';
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+select jsonb '{"b": {"a": 12}}' @? '$.*.b';
+select jsonb '{"b": {"a": 12}}' @? 'strict $.*.b';
+select jsonb '{}' @? '$.*';
+select jsonb '{"a": 1}' @? '$.*';
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+select jsonb '[]' @? '$[*]';
+select jsonb '[1]' @? '$[*]';
+select jsonb '[1]' @? '$[1]';
+select jsonb '[1]' @? 'strict $[1]';
+select jsonb_path_query('[1]', 'strict $[1]');
+select jsonb_path_query('[1]', 'strict $[1]', silent => true);
+select jsonb '[1]' @? 'lax $[10000000000000000]';
+select jsonb '[1]' @? 'strict $[10000000000000000]';
+select jsonb_path_query('[1]', 'lax $[10000000000000000]');
+select jsonb_path_query('[1]', 'strict $[10000000000000000]');
+select jsonb '[1]' @? '$[0]';
+select jsonb '[1]' @? '$[0.3]';
+select jsonb '[1]' @? '$[0.5]';
+select jsonb '[1]' @? '$[0.9]';
+select jsonb '[1]' @? '$[1.2]';
+select jsonb '[1]' @? 'strict $[1.2]';
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+
+select jsonb_path_exists('[{"a": 1}, {"a": 2}, 3]', 'lax $[*].a', silent => false);
+select jsonb_path_exists('[{"a": 1}, {"a": 2}, 3]', 'lax $[*].a', silent => true);
+select jsonb_path_exists('[{"a": 1}, {"a": 2}, 3]', 'strict $[*].a', silent => false);
+select jsonb_path_exists('[{"a": 1}, {"a": 2}, 3]', 'strict $[*].a', silent => true);
+
+select jsonb_path_query('1', 'lax $.a');
+select jsonb_path_query('1', 'strict $.a');
+select jsonb_path_query('1', 'strict $.*');
+select jsonb_path_query('1', 'strict $.a', silent => true);
+select jsonb_path_query('1', 'strict $.*', silent => true);
+select jsonb_path_query('[]', 'lax $.a');
+select jsonb_path_query('[]', 'strict $.a');
+select jsonb_path_query('[]', 'strict $.a', silent => true);
+select jsonb_path_query('{}', 'lax $.a');
+select jsonb_path_query('{}', 'strict $.a');
+select jsonb_path_query('{}', 'strict $.a', silent => true);
+
+select jsonb_path_query('1', 'strict $[1]');
+select jsonb_path_query('1', 'strict $[*]');
+select jsonb_path_query('[]', 'strict $[1]');
+select jsonb_path_query('[]', 'strict $["a"]');
+select jsonb_path_query('1', 'strict $[1]', silent => true);
+select jsonb_path_query('1', 'strict $[*]', silent => true);
+select jsonb_path_query('[]', 'strict $[1]', silent => true);
+select jsonb_path_query('[]', 'strict $["a"]', silent => true);
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.a');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.b');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.*');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10 / 0].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+select jsonb_path_query('1', 'lax $[0]');
+select jsonb_path_query('1', 'lax $[*]');
+select jsonb_path_query('[1]', 'lax $[0]');
+select jsonb_path_query('[1]', 'lax $[*]');
+select jsonb_path_query('[1,2,3]', 'lax $[*]');
+select jsonb_path_query('[1,2,3]', 'strict $[*].a');
+select jsonb_path_query('[1,2,3]', 'strict $[*].a', silent => true);
+select jsonb_path_query('[]', '$[last]');
+select jsonb_path_query('[]', '$[last ? (exists(last))]');
+select jsonb_path_query('[]', 'strict $[last]');
+select jsonb_path_query('[]', 'strict $[last]', silent => true);
+select jsonb_path_query('[1]', '$[last]');
+select jsonb_path_query('[1,2,3]', '$[last]');
+select jsonb_path_query('[1,2,3]', '$[last - 1]');
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "string")]');
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "string")]', silent => true);
+
+select * from jsonb_path_query('{"a": 10}', '$');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '1');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '[{"value" : 13}]');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 13}');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 8}');
+select * from jsonb_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $x.value)', '{"x": {"value" : 13}}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : null}');
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ != null)');
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ == null)');
+select * from jsonb_path_query('{}', '$ ? (@ == @)');
+select * from jsonb_path_query('[]', 'strict $ ? (@ == @)');
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))');
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))');
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? (exists (@.x))');
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? (exists (@.x + "3"))');
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? ((exists (@.x + "3")) is unknown)');
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g[*] ? (exists (@.x))');
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g[*] ? ((exists (@.x)) is unknown)');
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g ? (exists (@[*].x))');
+select jsonb_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g ? ((exists (@[*].x)) is unknown)');
+
+--test ternary logic
+select
+ x, y,
+ jsonb_path_query(
+ '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true && $y == true) ||
+ @ == false && !($x == true && $y == true) ||
+ @ == null && ($x == true && $y == true) is unknown)',
+ jsonb_build_object('x', x, 'y', y)
+ ) as "x && y"
+from
+ (values (jsonb 'true'), ('false'), ('"null"')) x(x),
+ (values (jsonb 'true'), ('false'), ('"null"')) y(y);
+
+select
+ x, y,
+ jsonb_path_query(
+ '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true || $y == true) ||
+ @ == false && !($x == true || $y == true) ||
+ @ == null && ($x == true || $y == true) is unknown)',
+ jsonb_build_object('x', x, 'y', y)
+ ) as "x || y"
+from
+ (values (jsonb 'true'), ('false'), ('"null"')) x(x),
+ (values (jsonb 'true'), ('false'), ('"null"')) y(y);
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (@.a == @.b)';
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (@.a == @.b)';
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (@.a == @.b)';
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == @.b)';
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (@.a == @.b)';
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (@.a == @.b)';
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (@.a == @.b)';
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == 1 + 1)');
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (1 + 1))');
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == @.b + 1)');
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (@.b + 1))');
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - 1)';
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -1)';
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -@.b)';
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - @.b)';
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - @.b)';
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (@.a == 1 - - @.b)';
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - +@.b)';
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+select jsonb '1' @? '$ ? ($ > 0)';
+
+-- arithmetic errors
+select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+select jsonb_path_query('0', '1 / $');
+select jsonb_path_query('0', '1 / $ + 2');
+select jsonb_path_query('0', '-(3 + 1 % $)');
+select jsonb_path_query('1', '$ + "2"');
+select jsonb_path_query('[1, 2]', '3 * $');
+select jsonb_path_query('"a"', '-$');
+select jsonb_path_query('[1,"2",3]', '+$');
+select jsonb_path_query('1', '$ + "2"', silent => true);
+select jsonb_path_query('[1, 2]', '3 * $', silent => true);
+select jsonb_path_query('"a"', '-$', silent => true);
+select jsonb_path_query('[1,"2",3]', '+$', silent => true);
+select jsonb '["1",2,0,3]' @? '-$[*]';
+select jsonb '[1,"2",0,3]' @? '-$[*]';
+select jsonb '["1",2,0,3]' @? 'strict -$[*]';
+select jsonb '[1,"2",0,3]' @? 'strict -$[*]';
+
+-- unwrapping of operator arguments in lax mode
+select jsonb_path_query('{"a": [2]}', 'lax $.a * 3');
+select jsonb_path_query('{"a": [2]}', 'lax $.a + 3');
+select jsonb_path_query('{"a": [2, 3, 4]}', 'lax -$.a');
+-- should fail
+select jsonb_path_query('{"a": [1, 2]}', 'lax $.a * 3');
+select jsonb_path_query('{"a": [1, 2]}', 'lax $.a * 3', silent => true);
+
+-- extension: boolean expressions
+select jsonb_path_query('2', '$ > 1');
+select jsonb_path_query('2', '$ <= 1');
+select jsonb_path_query('2', '$ == "2"');
+select jsonb '2' @? '$ == "2"';
+
+select jsonb '2' @@ '$ > 1';
+select jsonb '2' @@ '$ <= 1';
+select jsonb '2' @@ '$ == "2"';
+select jsonb '2' @@ '1';
+select jsonb '{}' @@ '$';
+select jsonb '[]' @@ '$';
+select jsonb '[1,2,3]' @@ '$[*]';
+select jsonb '[]' @@ '$[*]';
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+
+select jsonb_path_match('[{"a": 1}, {"a": 2}, 3]', 'lax exists($[*].a)', silent => false);
+select jsonb_path_match('[{"a": 1}, {"a": 2}, 3]', 'lax exists($[*].a)', silent => true);
+select jsonb_path_match('[{"a": 1}, {"a": 2}, 3]', 'strict exists($[*].a)', silent => false);
+select jsonb_path_match('[{"a": 1}, {"a": 2}, 3]', 'strict exists($[*].a)', silent => true);
+
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$.type()');
+select jsonb_path_query('[null,1,true,"a",[],{}]', 'lax $.type()');
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$[*].type()');
+select jsonb_path_query('null', 'null.type()');
+select jsonb_path_query('null', 'true.type()');
+select jsonb_path_query('null', '(123).type()');
+select jsonb_path_query('null', '"123".type()');
+
+select jsonb_path_query('{"a": 2}', '($.a - 5).abs() + 10');
+select jsonb_path_query('{"a": 2.5}', '-($.a * $.a).floor() % 4.3');
+select jsonb_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)');
+select jsonb_path_query('[1, 2, 3]', '($[*] > 3).type()');
+select jsonb_path_query('[1, 2, 3]', '($[*].a > 3).type()');
+select jsonb_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()');
+
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()', silent => true);
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()');
+
+select jsonb_path_query('[{},1]', '$[*].keyvalue()');
+select jsonb_path_query('[{},1]', '$[*].keyvalue()', silent => true);
+select jsonb_path_query('{}', '$.keyvalue()');
+select jsonb_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue().a');
+select jsonb '{"a": 1, "b": [1, 2]}' @? 'lax $.keyvalue()';
+select jsonb '{"a": 1, "b": [1, 2]}' @? 'lax $.keyvalue().key';
+
+select jsonb_path_query('null', '$.double()');
+select jsonb_path_query('true', '$.double()');
+select jsonb_path_query('null', '$.double()', silent => true);
+select jsonb_path_query('true', '$.double()', silent => true);
+select jsonb_path_query('[]', '$.double()');
+select jsonb_path_query('[]', 'strict $.double()');
+select jsonb_path_query('{}', '$.double()');
+select jsonb_path_query('[]', 'strict $.double()', silent => true);
+select jsonb_path_query('{}', '$.double()', silent => true);
+select jsonb_path_query('1.23', '$.double()');
+select jsonb_path_query('"1.23"', '$.double()');
+select jsonb_path_query('"1.23aaa"', '$.double()');
+select jsonb_path_query('1e1000', '$.double()');
+select jsonb_path_query('"nan"', '$.double()');
+select jsonb_path_query('"NaN"', '$.double()');
+select jsonb_path_query('"inf"', '$.double()');
+select jsonb_path_query('"-inf"', '$.double()');
+select jsonb_path_query('"inf"', '$.double()', silent => true);
+select jsonb_path_query('"-inf"', '$.double()', silent => true);
+
+select jsonb_path_query('{}', '$.abs()');
+select jsonb_path_query('true', '$.floor()');
+select jsonb_path_query('"1.2"', '$.ceiling()');
+select jsonb_path_query('{}', '$.abs()', silent => true);
+select jsonb_path_query('true', '$.floor()', silent => true);
+select jsonb_path_query('"1.2"', '$.ceiling()', silent => true);
+
+select jsonb_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
+select jsonb_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
+select jsonb_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
+select jsonb_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
+select jsonb_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc", "adc\nabc", "ab\nadc"]', 'lax $[*] ? (@ like_regex "^ab.*c")');
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc", "adc\nabc", "ab\nadc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")');
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc", "adc\nabc", "ab\nadc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "m")');
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc", "adc\nabc", "ab\nadc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "s")');
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "a\\b" flag "q")');
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "a\\b" flag "")');
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "q")');
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "q")');
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
+select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
+
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('"bogus"', '$.datetime()');
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+select jsonb_path_query('"10-03-2017T12:34:56"', '$.datetime("dd-mm-yyyy\"T\"HH24:MI:SS")');
+select jsonb_path_query('"10-03-2017t12:34:56"', '$.datetime("dd-mm-yyyy\"T\"HH24:MI:SS")');
+select jsonb_path_query('"10-03-2017 12:34:56"', '$.datetime("dd-mm-yyyy\"T\"HH24:MI:SS")');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56+3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56+3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56+3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56+3:10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10T12:34:56+3:10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10t12:34:56+3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56+3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56+3"', '$.datetime()');
+select jsonb_path_query('"12:34:56+3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56+3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+ '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+ '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+ '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+ '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+ '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+ '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+ '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+ '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+ '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+-- overflow during comparison
+select jsonb_path_query('"1000000-01-01"', '$.datetime() > "2020-01-01 12:00:00".datetime()'::jsonpath);
+
+set time zone default;
+
+-- jsonpath operators
+
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 1, "max": 4}');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 3, "max": 4}');
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a', silent => true);
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 1, "max": 4}');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 3, "max": 4}');
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 1)');
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', vars => '{"min": 1, "max": 4}');
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', vars => '{"min": 3, "max": 4}');
+
+SELECT jsonb_path_match('true', '$', silent => false);
+SELECT jsonb_path_match('false', '$', silent => false);
+SELECT jsonb_path_match('null', '$', silent => false);
+SELECT jsonb_path_match('1', '$', silent => true);
+SELECT jsonb_path_match('1', '$', silent => false);
+SELECT jsonb_path_match('"a"', '$', silent => false);
+SELECT jsonb_path_match('{}', '$', silent => false);
+SELECT jsonb_path_match('[true]', '$', silent => false);
+SELECT jsonb_path_match('{}', 'lax $.a', silent => false);
+SELECT jsonb_path_match('{}', 'strict $.a', silent => false);
+SELECT jsonb_path_match('{}', 'strict $.a', silent => true);
+SELECT jsonb_path_match('[true, true]', '$[*]', silent => false);
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2';
+SELECT jsonb_path_match('[{"a": 1}, {"a": 2}]', '$[*].a > 1');
+
+-- test string comparison (Unicode codepoint collation)
+WITH str(j, num) AS
+(
+ SELECT jsonb_build_object('s', s), num
+ FROM unnest('{"", "a", "ab", "abc", "abcd", "b", "A", "AB", "ABC", "ABc", "ABcD", "B"}'::text[]) WITH ORDINALITY AS a(s, num)
+)
+SELECT
+ s1.j, s2.j,
+ jsonb_path_query_first(s1.j, '$.s < $s', vars => s2.j) lt,
+ jsonb_path_query_first(s1.j, '$.s <= $s', vars => s2.j) le,
+ jsonb_path_query_first(s1.j, '$.s == $s', vars => s2.j) eq,
+ jsonb_path_query_first(s1.j, '$.s >= $s', vars => s2.j) ge,
+ jsonb_path_query_first(s1.j, '$.s > $s', vars => s2.j) gt
+FROM str s1, str s2
+ORDER BY s1.num, s2.num;
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000..17ab775
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,181 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+select '$[0]'::jsonpath;
+select '$[*][0]'::jsonpath;
+select '$[*].a'::jsonpath;
+select '$[*][0].a.b'::jsonpath;
+select '$.a.**.b'::jsonpath;
+select '$.a.**{2}.b'::jsonpath;
+select '$.a.**{2 to 2}.b'::jsonpath;
+select '$.a.**{2 to 5}.b'::jsonpath;
+select '$.a.**{0 to 5}.b'::jsonpath;
+select '$.a.**{5 to last}.b'::jsonpath;
+select '$.a.**{last}.b'::jsonpath;
+select '$.a.**{last to 5}.b'::jsonpath;
+select '$+1'::jsonpath;
+select '$-1'::jsonpath;
+select '$--+1'::jsonpath;
+select '$.a/+-1'::jsonpath;
+select '1 * 2 + 4 % -3 != false'::jsonpath;
+
+select '"\b\f\r\n\t\v\"\''\\"'::jsonpath;
+select '"\x50\u0067\u{53}\u{051}\u{00004C}"'::jsonpath;
+select '$.foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar'::jsonpath;
+select '"\z"'::jsonpath; -- unrecognized escape is just the literal char
+
+select '$.g ? ($.a == 1)'::jsonpath;
+select '$.g ? (@ == 1)'::jsonpath;
+select '$.g ? (@.a == 1)'::jsonpath;
+select '$.g ? (@.a == 1 || @.a == 4)'::jsonpath;
+select '$.g ? (@.a == 1 && @.a == 4)'::jsonpath;
+select '$.g ? (@.a == 1 || @.a == 4 && @.b == 7)'::jsonpath;
+select '$.g ? (@.a == 1 || !(@.a == 4) && @.b == 7)'::jsonpath;
+select '$.g ? (@.a == 1 || !(@.x >= 123 || @.a == 4) && @.b == 7)'::jsonpath;
+select '$.g ? (@.x >= @[*]?(@.a > "abc"))'::jsonpath;
+select '$.g ? ((@.x >= 123 || @.a == 4) is unknown)'::jsonpath;
+select '$.g ? (exists (@.x))'::jsonpath;
+select '$.g ? (exists (@.x ? (@ == 14)))'::jsonpath;
+select '$.g ? ((@.x >= 123 || @.a == 4) && exists (@.x ? (@ == 14)))'::jsonpath;
+select '$.g ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+
+select '$a'::jsonpath;
+select '$a.b'::jsonpath;
+select '$a[*]'::jsonpath;
+select '$.g ? (@.zip == $zip)'::jsonpath;
+select '$.a[1,2, 3 to 16]'::jsonpath;
+select '$.a[$a + 1, ($b[*]) to -($[0] * 2)]'::jsonpath;
+select '$.a[$.a.size() - 3]'::jsonpath;
+select 'last'::jsonpath;
+select '"last"'::jsonpath;
+select '$.last'::jsonpath;
+select '$ ? (last > 0)'::jsonpath;
+select '$[last]'::jsonpath;
+select '$[$[0] ? (last > 0)]'::jsonpath;
+
+select 'null.type()'::jsonpath;
+select '1.type()'::jsonpath;
+select '(1).type()'::jsonpath;
+select '1.2.type()'::jsonpath;
+select '"aaa".type()'::jsonpath;
+select 'true.type()'::jsonpath;
+select '$.double().floor().ceiling().abs()'::jsonpath;
+select '$.keyvalue().key'::jsonpath;
+select '$.datetime()'::jsonpath;
+select '$.datetime("datetime template")'::jsonpath;
+
+select '$ ? (@ starts with "abc")'::jsonpath;
+select '$ ? (@ starts with $var)'::jsonpath;
+
+select '$ ? (@ like_regex "(invalid pattern")'::jsonpath;
+select '$ ? (@ like_regex "pattern")'::jsonpath;
+select '$ ? (@ like_regex "pattern" flag "")'::jsonpath;
+select '$ ? (@ like_regex "pattern" flag "i")'::jsonpath;
+select '$ ? (@ like_regex "pattern" flag "is")'::jsonpath;
+select '$ ? (@ like_regex "pattern" flag "isim")'::jsonpath;
+select '$ ? (@ like_regex "pattern" flag "xsms")'::jsonpath;
+select '$ ? (@ like_regex "pattern" flag "q")'::jsonpath;
+select '$ ? (@ like_regex "pattern" flag "iq")'::jsonpath;
+select '$ ? (@ like_regex "pattern" flag "smixq")'::jsonpath;
+select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath;
+
+select '$ < 1'::jsonpath;
+select '($ < 1) || $.a.b <= $x'::jsonpath;
+select '@ + 1'::jsonpath;
+
+select '($).a.b'::jsonpath;
+select '($.a.b).c.d'::jsonpath;
+select '($.a.b + -$.x.y).c.d'::jsonpath;
+select '(-+$.a.b).c.d'::jsonpath;
+select '1 + ($.a.b + 2).c.d'::jsonpath;
+select '1 + ($.a.b > 2).c.d'::jsonpath;
+select '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
+
+select '$ ? (@.a < 1)'::jsonpath;
+select '$ ? (@.a < -1)'::jsonpath;
+select '$ ? (@.a < +1)'::jsonpath;
+select '$ ? (@.a < .1)'::jsonpath;
+select '$ ? (@.a < -.1)'::jsonpath;
+select '$ ? (@.a < +.1)'::jsonpath;
+select '$ ? (@.a < 0.1)'::jsonpath;
+select '$ ? (@.a < -0.1)'::jsonpath;
+select '$ ? (@.a < +0.1)'::jsonpath;
+select '$ ? (@.a < 10.1)'::jsonpath;
+select '$ ? (@.a < -10.1)'::jsonpath;
+select '$ ? (@.a < +10.1)'::jsonpath;
+select '$ ? (@.a < 1e1)'::jsonpath;
+select '$ ? (@.a < -1e1)'::jsonpath;
+select '$ ? (@.a < +1e1)'::jsonpath;
+select '$ ? (@.a < .1e1)'::jsonpath;
+select '$ ? (@.a < -.1e1)'::jsonpath;
+select '$ ? (@.a < +.1e1)'::jsonpath;
+select '$ ? (@.a < 0.1e1)'::jsonpath;
+select '$ ? (@.a < -0.1e1)'::jsonpath;
+select '$ ? (@.a < +0.1e1)'::jsonpath;
+select '$ ? (@.a < 10.1e1)'::jsonpath;
+select '$ ? (@.a < -10.1e1)'::jsonpath;
+select '$ ? (@.a < +10.1e1)'::jsonpath;
+select '$ ? (@.a < 1e-1)'::jsonpath;
+select '$ ? (@.a < -1e-1)'::jsonpath;
+select '$ ? (@.a < +1e-1)'::jsonpath;
+select '$ ? (@.a < .1e-1)'::jsonpath;
+select '$ ? (@.a < -.1e-1)'::jsonpath;
+select '$ ? (@.a < +.1e-1)'::jsonpath;
+select '$ ? (@.a < 0.1e-1)'::jsonpath;
+select '$ ? (@.a < -0.1e-1)'::jsonpath;
+select '$ ? (@.a < +0.1e-1)'::jsonpath;
+select '$ ? (@.a < 10.1e-1)'::jsonpath;
+select '$ ? (@.a < -10.1e-1)'::jsonpath;
+select '$ ? (@.a < +10.1e-1)'::jsonpath;
+select '$ ? (@.a < 1e+1)'::jsonpath;
+select '$ ? (@.a < -1e+1)'::jsonpath;
+select '$ ? (@.a < +1e+1)'::jsonpath;
+select '$ ? (@.a < .1e+1)'::jsonpath;
+select '$ ? (@.a < -.1e+1)'::jsonpath;
+select '$ ? (@.a < +.1e+1)'::jsonpath;
+select '$ ? (@.a < 0.1e+1)'::jsonpath;
+select '$ ? (@.a < -0.1e+1)'::jsonpath;
+select '$ ? (@.a < +0.1e+1)'::jsonpath;
+select '$ ? (@.a < 10.1e+1)'::jsonpath;
+select '$ ? (@.a < -10.1e+1)'::jsonpath;
+select '$ ? (@.a < +10.1e+1)'::jsonpath;
+
+select '0'::jsonpath;
+select '00'::jsonpath;
+select '0.0'::jsonpath;
+select '0.000'::jsonpath;
+select '0.000e1'::jsonpath;
+select '0.000e2'::jsonpath;
+select '0.000e3'::jsonpath;
+select '0.0010'::jsonpath;
+select '0.0010e-1'::jsonpath;
+select '0.0010e+1'::jsonpath;
+select '0.0010e+2'::jsonpath;
+select '1e'::jsonpath;
+select '1.e'::jsonpath;
+select '1.2e'::jsonpath;
+select '1.2.e'::jsonpath;
+select '(1.2).e'::jsonpath;
+select '1e3'::jsonpath;
+select '1.e3'::jsonpath;
+select '1.e3.e'::jsonpath;
+select '1.e3.e4'::jsonpath;
+select '1.2e3'::jsonpath;
+select '1.2.e3'::jsonpath;
+select '(1.2).e3'::jsonpath;
+select '1..e'::jsonpath;
+select '1..e3'::jsonpath;
+select '(1.).e'::jsonpath;
+select '(1.).e3'::jsonpath;
diff --git a/src/test/regress/sql/jsonpath_encoding.sql b/src/test/regress/sql/jsonpath_encoding.sql
new file mode 100644
index 0000000..55d9e30
--- /dev/null
+++ b/src/test/regress/sql/jsonpath_encoding.sql
@@ -0,0 +1,59 @@
+--
+-- encoding-sensitive tests for jsonpath
+--
+
+-- We provide expected-results files for UTF8 (jsonpath_encoding.out)
+-- and for SQL_ASCII (jsonpath_encoding_1.out). Skip otherwise.
+SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
+ AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+
+SELECT getdatabaseencoding(); -- just to label the results files
+
+-- checks for double-quoted values
+
+-- basic unicode input
+SELECT '"\u"'::jsonpath; -- ERROR, incomplete escape
+SELECT '"\u00"'::jsonpath; -- ERROR, incomplete escape
+SELECT '"\u000g"'::jsonpath; -- ERROR, g is not a hex digit
+SELECT '"\u0000"'::jsonpath; -- OK, legal escape
+SELECT '"\uaBcD"'::jsonpath; -- OK, uppercase and lower case both OK
+
+-- handling of unicode surrogate pairs
+select '"\ud83d\ude04\ud83d\udc36"'::jsonpath as correct_in_utf8;
+select '"\ud83d\ud83d"'::jsonpath; -- 2 high surrogates in a row
+select '"\ude04\ud83d"'::jsonpath; -- surrogates in wrong order
+select '"\ud83dX"'::jsonpath; -- orphan high surrogate
+select '"\ude04X"'::jsonpath; -- orphan low surrogate
+
+--handling of simple unicode escapes
+select '"the Copyright \u00a9 sign"'::jsonpath as correct_in_utf8;
+select '"dollar \u0024 character"'::jsonpath as correct_everywhere;
+select '"dollar \\u0024 character"'::jsonpath as not_an_escape;
+select '"null \u0000 escape"'::jsonpath as not_unescaped;
+select '"null \\u0000 escape"'::jsonpath as not_an_escape;
+
+-- checks for quoted key names
+
+-- basic unicode input
+SELECT '$."\u"'::jsonpath; -- ERROR, incomplete escape
+SELECT '$."\u00"'::jsonpath; -- ERROR, incomplete escape
+SELECT '$."\u000g"'::jsonpath; -- ERROR, g is not a hex digit
+SELECT '$."\u0000"'::jsonpath; -- OK, legal escape
+SELECT '$."\uaBcD"'::jsonpath; -- OK, uppercase and lower case both OK
+
+-- handling of unicode surrogate pairs
+select '$."\ud83d\ude04\ud83d\udc36"'::jsonpath as correct_in_utf8;
+select '$."\ud83d\ud83d"'::jsonpath; -- 2 high surrogates in a row
+select '$."\ude04\ud83d"'::jsonpath; -- surrogates in wrong order
+select '$."\ud83dX"'::jsonpath; -- orphan high surrogate
+select '$."\ude04X"'::jsonpath; -- orphan low surrogate
+
+--handling of simple unicode escapes
+select '$."the Copyright \u00a9 sign"'::jsonpath as correct_in_utf8;
+select '$."dollar \u0024 character"'::jsonpath as correct_everywhere;
+select '$."dollar \\u0024 character"'::jsonpath as not_an_escape;
+select '$."null \u0000 escape"'::jsonpath as not_unescaped;
+select '$."null \\u0000 escape"'::jsonpath as not_an_escape;
diff --git a/src/test/regress/sql/limit.sql b/src/test/regress/sql/limit.sql
new file mode 100644
index 0000000..6f0cda9
--- /dev/null
+++ b/src/test/regress/sql/limit.sql
@@ -0,0 +1,201 @@
+--
+-- 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;
+fetch backward 1 in c1;
+fetch backward all in c1;
+fetch backward 1 in c1;
+fetch all in c1;
+
+declare c2 cursor for select * from int8_tbl limit 3;
+fetch all in c2;
+fetch 1 in c2;
+fetch backward 1 in c2;
+fetch backward all in c2;
+fetch backward 1 in c2;
+fetch all in c2;
+
+declare c3 cursor for select * from int8_tbl offset 3;
+fetch all in c3;
+fetch 1 in c3;
+fetch backward 1 in c3;
+fetch backward all in c3;
+fetch backward 1 in c3;
+fetch all in c3;
+
+declare c4 cursor for select * from int8_tbl offset 10;
+fetch all in c4;
+fetch 1 in c4;
+fetch backward 1 in c4;
+fetch backward all in c4;
+fetch backward 1 in c4;
+fetch all in c4;
+
+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;
+fetch backward 1 in c5;
+fetch all in c5;
+fetch backward all in c5;
+fetch all in c5;
+fetch backward all in c5;
+
+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/line.sql b/src/test/regress/sql/line.sql
new file mode 100644
index 0000000..f589ffe
--- /dev/null
+++ b/src/test/regress/sql/line.sql
@@ -0,0 +1,42 @@
+--
+-- LINE
+-- Infinite lines
+--
+
+--DROP TABLE LINE_TBL;
+CREATE TABLE LINE_TBL (s line);
+
+INSERT INTO LINE_TBL VALUES ('{0,-1,5}'); -- A == 0
+INSERT INTO LINE_TBL VALUES ('{1,0,5}'); -- B == 0
+INSERT INTO LINE_TBL VALUES ('{0,3,0}'); -- A == C == 0
+INSERT INTO LINE_TBL VALUES (' (0,0), (6,6)');
+INSERT INTO LINE_TBL VALUES ('10,-10 ,-5,-4');
+INSERT INTO LINE_TBL VALUES ('[-1e6,2e2,3e5, -4e1]');
+
+INSERT INTO LINE_TBL VALUES ('{3,NaN,5}');
+INSERT INTO LINE_TBL VALUES ('{NaN,NaN,NaN}');
+
+-- horizontal
+INSERT INTO LINE_TBL VALUES ('[(1,3),(2,3)]');
+-- vertical
+INSERT INTO LINE_TBL VALUES (line(point '(3,1)', point '(3,2)'));
+
+-- bad values for parser testing
+INSERT INTO LINE_TBL VALUES ('{}');
+INSERT INTO LINE_TBL VALUES ('{0');
+INSERT INTO LINE_TBL VALUES ('{0,0}');
+INSERT INTO LINE_TBL VALUES ('{0,0,1');
+INSERT INTO LINE_TBL VALUES ('{0,0,1}');
+INSERT INTO LINE_TBL VALUES ('{0,0,1} x');
+INSERT INTO LINE_TBL VALUES ('(3asdf,2 ,3,4r2)');
+INSERT INTO LINE_TBL VALUES ('[1,2,3, 4');
+INSERT INTO LINE_TBL VALUES ('[(,2),(3,4)]');
+INSERT INTO LINE_TBL VALUES ('[(1,2),(3,4)');
+INSERT INTO LINE_TBL VALUES ('[(1,2),(1,2)]');
+
+INSERT INTO LINE_TBL VALUES (line(point '(1,0)', point '(1,0)'));
+
+select * from LINE_TBL;
+
+select '{nan, 1, nan}'::line = '{nan, 1, nan}'::line as true,
+ '{nan, 1, nan}'::line = '{nan, 2, nan}'::line as false;
diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql
new file mode 100644
index 0000000..05bdb8a
--- /dev/null
+++ b/src/test/regress/sql/lock.sql
@@ -0,0 +1,139 @@
+--
+-- Test the LOCK statement
+--
+
+-- 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;
+-- 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;
+-- 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;
+-- 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;
+-- 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;
+-- 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;
+-- 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;
+
+--
+-- Clean up
+--
+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;
+SELECT test_atomic_ops();
diff --git a/src/test/regress/sql/lseg.sql b/src/test/regress/sql/lseg.sql
new file mode 100644
index 0000000..f266ca3
--- /dev/null
+++ b/src/test/regress/sql/lseg.sql
@@ -0,0 +1,24 @@
+--
+-- LSEG
+-- Line segments
+--
+
+--DROP TABLE LSEG_TBL;
+CREATE TABLE LSEG_TBL (s lseg);
+
+INSERT INTO LSEG_TBL VALUES ('[(1,2),(3,4)]');
+INSERT INTO LSEG_TBL VALUES ('(0,0),(6,6)');
+INSERT INTO LSEG_TBL VALUES ('10,-10 ,-3,-4');
+INSERT INTO LSEG_TBL VALUES ('[-1e6,2e2,3e5, -4e1]');
+INSERT INTO LSEG_TBL VALUES (lseg(point(11, 22), point(33,44)));
+INSERT INTO LSEG_TBL VALUES ('[(-10,2),(-10,3)]'); -- vertical
+INSERT INTO LSEG_TBL VALUES ('[(0,-20),(30,-20)]'); -- horizontal
+INSERT INTO LSEG_TBL VALUES ('[(NaN,1),(NaN,90)]'); -- NaN
+
+-- bad values for parser testing
+INSERT INTO LSEG_TBL VALUES ('(3asdf,2 ,3,4r2)');
+INSERT INTO LSEG_TBL VALUES ('[1,2,3, 4');
+INSERT INTO LSEG_TBL VALUES ('[(,2),(3,4)]');
+INSERT INTO LSEG_TBL VALUES ('[(1,2),(3,4)');
+
+select * from LSEG_TBL;
diff --git a/src/test/regress/sql/macaddr.sql b/src/test/regress/sql/macaddr.sql
new file mode 100644
index 0000000..7bad8f5
--- /dev/null
+++ b/src/test/regress/sql/macaddr.sql
@@ -0,0 +1,43 @@
+--
+-- 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;
+
+CREATE INDEX macaddr_data_btree ON macaddr_data USING btree (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;
+SELECT b & '00:00:00:ff:ff:ff' FROM macaddr_data;
+SELECT b | '01:02:03:04:05:06' FROM macaddr_data;
+
+DROP TABLE macaddr_data;
diff --git a/src/test/regress/sql/macaddr8.sql b/src/test/regress/sql/macaddr8.sql
new file mode 100644
index 0000000..57a227c
--- /dev/null
+++ b/src/test/regress/sql/macaddr8.sql
@@ -0,0 +1,89 @@
+--
+-- 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;
+
+CREATE INDEX macaddr8_data_btree ON macaddr8_data USING btree (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;
+SELECT b & '00:00:00:ff:ff:ff' FROM macaddr8_data;
+SELECT b | '01:02:03:04:05:06' FROM macaddr8_data;
+
+DROP TABLE macaddr8_data;
diff --git a/src/test/regress/sql/matview.sql b/src/test/regress/sql/matview.sql
new file mode 100644
index 0000000..68b9ccf
--- /dev/null
+++ b/src/test/regress/sql/matview.sql
@@ -0,0 +1,297 @@
+-- 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;
+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;
+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;
+SELECT * FROM mvtest_mv_v;
+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/memoize.sql b/src/test/regress/sql/memoize.sql
new file mode 100644
index 0000000..c6ed5a2
--- /dev/null
+++ b/src/test/regress/sql/memoize.sql
@@ -0,0 +1,152 @@
+-- Perform tests on the Memoize node.
+
+-- The cache hits/misses/evictions from the Memoize node can vary between
+-- machines. Let's just replace the number with an 'N'. In order to allow us
+-- to perform validation when the measure was zero, we replace a zero value
+-- with "Zero". All other numbers are replaced with 'N'.
+create function explain_memoize(query text, hide_hitmiss bool) returns setof text
+language plpgsql as
+$$
+declare
+ ln text;
+begin
+ for ln in
+ execute format('explain (analyze, costs off, summary off, timing off) %s',
+ query)
+ loop
+ if hide_hitmiss = true then
+ ln := regexp_replace(ln, 'Hits: 0', 'Hits: Zero');
+ ln := regexp_replace(ln, 'Hits: \d+', 'Hits: N');
+ ln := regexp_replace(ln, 'Misses: 0', 'Misses: Zero');
+ ln := regexp_replace(ln, 'Misses: \d+', 'Misses: N');
+ end if;
+ ln := regexp_replace(ln, 'Evictions: 0', 'Evictions: Zero');
+ ln := regexp_replace(ln, 'Evictions: \d+', 'Evictions: N');
+ ln := regexp_replace(ln, 'Memory Usage: \d+', 'Memory Usage: N');
+ ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N');
+ ln := regexp_replace(ln, 'loops=\d+', 'loops=N');
+ return next ln;
+ end loop;
+end;
+$$;
+
+-- Ensure we get a memoize node on the inner side of the nested loop
+SET enable_hashjoin TO off;
+SET enable_bitmapscan TO off;
+
+SELECT explain_memoize('
+SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
+INNER JOIN tenk1 t2 ON t1.unique1 = t2.twenty
+WHERE t2.unique1 < 1000;', false);
+
+-- And check we get the expected results.
+SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
+INNER JOIN tenk1 t2 ON t1.unique1 = t2.twenty
+WHERE t2.unique1 < 1000;
+
+-- Try with LATERAL joins
+SELECT explain_memoize('
+SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
+LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2
+WHERE t1.unique1 < 1000;', false);
+
+-- And check we get the expected results.
+SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
+LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2
+WHERE t1.unique1 < 1000;
+
+-- Reduce work_mem so that we see some cache evictions
+SET work_mem TO '64kB';
+SET enable_mergejoin TO off;
+-- Ensure we get some evictions. We're unable to validate the hits and misses
+-- here as the number of entries that fit in the cache at once will vary
+-- between different machines.
+SELECT explain_memoize('
+SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
+INNER JOIN tenk1 t2 ON t1.unique1 = t2.thousand
+WHERE t2.unique1 < 1200;', true);
+
+CREATE TABLE flt (f float);
+CREATE INDEX flt_f_idx ON flt (f);
+INSERT INTO flt VALUES('-0.0'::float),('+0.0'::float);
+ANALYZE flt;
+
+SET enable_seqscan TO off;
+
+-- Ensure memoize operates in logical mode
+SELECT explain_memoize('
+SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f = f2.f;', false);
+
+-- Ensure memoize operates in binary mode
+SELECT explain_memoize('
+SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f >= f2.f;', false);
+
+DROP TABLE flt;
+
+-- Exercise Memoize in binary mode with a large fixed width type and a
+-- varlena type.
+CREATE TABLE strtest (n name, t text);
+CREATE INDEX strtest_n_idx ON strtest (n);
+CREATE INDEX strtest_t_idx ON strtest (t);
+INSERT INTO strtest VALUES('one','one'),('two','two'),('three',repeat(md5('three'),100));
+-- duplicate rows so we get some cache hits
+INSERT INTO strtest SELECT * FROM strtest;
+ANALYZE strtest;
+
+-- Ensure we get 3 hits and 3 misses
+SELECT explain_memoize('
+SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.n >= s2.n;', false);
+
+-- Ensure we get 3 hits and 3 misses
+SELECT explain_memoize('
+SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.t >= s2.t;', false);
+
+DROP TABLE strtest;
+
+-- Exercise Memoize code that flushes the cache when a parameter changes which
+-- is not part of the cache key.
+
+-- Ensure we get a Memoize plan
+EXPLAIN (COSTS OFF)
+SELECT unique1 FROM tenk1 t0
+WHERE unique1 < 3
+ AND EXISTS (
+ SELECT 1 FROM tenk1 t1
+ INNER JOIN tenk1 t2 ON t1.unique1 = t2.hundred
+ WHERE t0.ten = t1.twenty AND t0.two <> t2.four OFFSET 0);
+
+-- Ensure the above query returns the correct result
+SELECT unique1 FROM tenk1 t0
+WHERE unique1 < 3
+ AND EXISTS (
+ SELECT 1 FROM tenk1 t1
+ INNER JOIN tenk1 t2 ON t1.unique1 = t2.hundred
+ WHERE t0.ten = t1.twenty AND t0.two <> t2.four OFFSET 0);
+
+RESET enable_seqscan;
+RESET enable_mergejoin;
+RESET work_mem;
+RESET enable_bitmapscan;
+RESET enable_hashjoin;
+
+-- Test parallel plans with Memoize
+SET min_parallel_table_scan_size TO 0;
+SET parallel_setup_cost TO 0;
+SET parallel_tuple_cost TO 0;
+SET max_parallel_workers_per_gather TO 2;
+
+-- Ensure we get a parallel plan.
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
+LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2
+WHERE t1.unique1 < 1000;
+
+-- And ensure the parallel plan gives us the correct results.
+SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
+LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2
+WHERE t1.unique1 < 1000;
+
+RESET max_parallel_workers_per_gather;
+RESET parallel_tuple_cost;
+RESET parallel_setup_cost;
+RESET min_parallel_table_scan_size;
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
new file mode 100644
index 0000000..a398349
--- /dev/null
+++ b/src/test/regress/sql/misc_functions.sql
@@ -0,0 +1,102 @@
+--
+-- 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();
+
+--
+-- 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.
+--
+SELECT * FROM pg_log_backend_memory_contexts(pg_backend_pid());
+
+--
+-- 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;
+
+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 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);
+
+-- With support function that knows it's int4eq, we get a different plan
+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);
+
+-- 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/misc_sanity.sql b/src/test/regress/sql/misc_sanity.sql
new file mode 100644
index 0000000..9699f5c
--- /dev/null
+++ b/src/test/regress/sql/misc_sanity.sql
@@ -0,0 +1,120 @@
+--
+-- MISC_SANITY
+-- Sanity checks for common errors in making system tables that don't fit
+-- comfortably into either opr_sanity or type_sanity.
+--
+-- Every test failure in this file should be closely inspected.
+-- The description of the failing test should be read carefully before
+-- adjusting the expected output. In most cases, the queries should
+-- not find *any* matching entries.
+--
+-- NB: run this test early, because some later tests create bogus entries.
+
+
+-- **************** pg_depend ****************
+
+-- Look for illegal values in pg_depend fields.
+-- classid/objid can be zero, but only in 'p' entries
+
+SELECT *
+FROM pg_depend as d1
+WHERE refclassid = 0 OR refobjid = 0 OR
+ deptype NOT IN ('a', 'e', 'i', 'n', 'p') OR
+ (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
+ (deptype = 'p' AND (classid != 0 OR objid != 0 OR objsubid != 0));
+
+-- **************** pg_shdepend ****************
+
+-- Look for illegal values in pg_shdepend fields.
+-- classid/objid can be zero, but only in 'p' entries
+
+SELECT *
+FROM pg_shdepend as d1
+WHERE refclassid = 0 OR refobjid = 0 OR
+ deptype NOT IN ('a', 'o', 'p', 'r') OR
+ (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
+ (deptype = 'p' AND (dbid != 0 OR classid != 0 OR objid != 0 OR objsubid != 0));
+
+
+-- Check each OID-containing system catalog to see if its lowest-numbered OID
+-- is pinned. If not, and if that OID was generated during initdb, then
+-- perhaps initdb forgot to scan that catalog for pinnable entries.
+-- Generally, it's okay for a catalog to be listed in the output of this
+-- test if that catalog is scanned by initdb.c's setup_depend() function;
+-- whatever OID the test is complaining about must have been added later
+-- in initdb, where it intentionally isn't pinned. Legitimate exceptions
+-- to that rule are listed in the comments in setup_depend().
+-- Currently, pg_rewrite is also listed by this check, even though it is
+-- covered by setup_depend(). That happens because there are no rules in
+-- the pinned data, but initdb creates some intentionally-not-pinned views.
+
+do $$
+declare relnm text;
+ reloid oid;
+ shared bool;
+ lowoid oid;
+ pinned bool;
+begin
+for relnm, reloid, shared in
+ select relname, oid, relisshared from pg_class
+ where EXISTS(
+ SELECT * FROM pg_attribute
+ WHERE attrelid = pg_class.oid AND attname = 'oid')
+ and relkind = 'r' and oid < 16384 order by 1
+loop
+ execute 'select min(oid) from ' || relnm into lowoid;
+ continue when lowoid is null or lowoid >= 16384;
+ if shared then
+ pinned := exists(select 1 from pg_shdepend
+ where refclassid = reloid and refobjid = lowoid
+ and deptype = 'p');
+ else
+ pinned := exists(select 1 from pg_depend
+ where refclassid = reloid and refobjid = lowoid
+ and deptype = 'p');
+ end if;
+ if not pinned then
+ raise notice '% contains unpinned initdb-created object(s)', relnm;
+ end if;
+end loop;
+end$$;
+
+-- **************** pg_class ****************
+
+-- Look for system tables with varlena columns but no toast table. All
+-- system tables with toastable columns should have toast tables, with
+-- the following exceptions:
+-- 1. pg_class, pg_attribute, and pg_index, due to fear of recursive
+-- dependencies as toast tables depend on them.
+-- 2. pg_largeobject and pg_largeobject_metadata. Large object catalogs
+-- and toast tables are mutually exclusive and large object data is handled
+-- as user data by pg_upgrade, which would cause failures.
+
+SELECT relname, attname, atttypid::regtype
+FROM pg_class c JOIN pg_attribute a ON c.oid = attrelid
+WHERE c.oid < 16384 AND
+ reltoastrelid = 0 AND
+ relkind = 'r' AND
+ attstorage != 'p'
+ORDER BY 1, 2;
+
+
+-- system catalogs without primary keys
+--
+-- Current exceptions:
+-- * pg_depend, pg_shdepend don't have a unique key
+SELECT relname
+FROM pg_class
+WHERE relnamespace = 'pg_catalog'::regnamespace AND relkind = 'r'
+ AND pg_class.oid NOT IN (SELECT indrelid FROM pg_index WHERE indisprimary)
+ORDER BY 1;
+
+
+-- system catalog unique indexes not wrapped in a constraint
+-- (There should be none.)
+SELECT relname
+FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid
+WHERE relnamespace = 'pg_catalog'::regnamespace AND relkind = 'i'
+ AND i.indisunique
+ AND c.oid NOT IN (SELECT conindid FROM pg_constraint)
+ORDER BY 1;
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
new file mode 100644
index 0000000..5e74628
--- /dev/null
+++ b/src/test/regress/sql/money.sql
@@ -0,0 +1,131 @@
+--
+-- MONEY
+--
+-- Note that we assume lc_monetary has been set to C.
+--
+
+CREATE TABLE money_data (m money);
+
+INSERT INTO money_data VALUES ('123');
+SELECT * FROM money_data;
+SELECT m + '123' FROM money_data;
+SELECT m + '123.45' FROM money_data;
+SELECT m - '123.45' FROM money_data;
+SELECT m / '2'::money FROM money_data;
+SELECT m * 2 FROM money_data;
+SELECT 2 * m FROM money_data;
+SELECT m / 2 FROM money_data;
+SELECT m * 2::int2 FROM money_data;
+SELECT 2::int2 * m FROM money_data;
+SELECT m / 2::int2 FROM money_data;
+SELECT m * 2::int8 FROM money_data;
+SELECT 2::int8 * m FROM money_data;
+SELECT m / 2::int8 FROM money_data;
+SELECT m * 2::float8 FROM money_data;
+SELECT 2::float8 * m FROM money_data;
+SELECT m / 2::float8 FROM money_data;
+SELECT m * 2::float4 FROM money_data;
+SELECT 2::float4 * m FROM money_data;
+SELECT m / 2::float4 FROM money_data;
+
+-- All true
+SELECT m = '$123.00' FROM money_data;
+SELECT m != '$124.00' FROM money_data;
+SELECT m <= '$123.00' FROM money_data;
+SELECT m >= '$123.00' FROM money_data;
+SELECT m < '$124.00' FROM money_data;
+SELECT m > '$122.00' FROM money_data;
+
+-- All false
+SELECT m = '$123.01' FROM money_data;
+SELECT m != '$123.00' FROM money_data;
+SELECT m <= '$122.99' FROM money_data;
+SELECT m >= '$123.01' FROM money_data;
+SELECT m > '$124.00' FROM money_data;
+SELECT m < '$122.00' FROM money_data;
+
+SELECT cashlarger(m, '$124.00') FROM money_data;
+SELECT cashsmaller(m, '$124.00') FROM money_data;
+SELECT cash_words(m) FROM money_data;
+SELECT cash_words(m + '1.23') FROM money_data;
+
+DELETE FROM money_data;
+INSERT INTO money_data VALUES ('$123.45');
+SELECT * FROM money_data;
+
+DELETE FROM money_data;
+INSERT INTO money_data VALUES ('$123.451');
+SELECT * FROM money_data;
+
+DELETE FROM money_data;
+INSERT INTO money_data VALUES ('$123.454');
+SELECT * FROM money_data;
+
+DELETE FROM money_data;
+INSERT INTO money_data VALUES ('$123.455');
+SELECT * FROM money_data;
+
+DELETE FROM money_data;
+INSERT INTO money_data VALUES ('$123.456');
+SELECT * FROM money_data;
+
+DELETE FROM money_data;
+INSERT INTO money_data VALUES ('$123.459');
+SELECT * FROM money_data;
+
+-- input checks
+SELECT '1234567890'::money;
+SELECT '12345678901234567'::money;
+SELECT '123456789012345678'::money;
+SELECT '9223372036854775807'::money;
+SELECT '-12345'::money;
+SELECT '-1234567890'::money;
+SELECT '-12345678901234567'::money;
+SELECT '-123456789012345678'::money;
+SELECT '-9223372036854775808'::money;
+
+-- special characters
+SELECT '(1)'::money;
+SELECT '($123,456.78)'::money;
+
+-- documented minimums and maximums
+SELECT '-92233720368547758.08'::money;
+SELECT '92233720368547758.07'::money;
+
+SELECT '-92233720368547758.09'::money;
+SELECT '92233720368547758.08'::money;
+
+-- rounding
+SELECT '-92233720368547758.085'::money;
+SELECT '92233720368547758.075'::money;
+
+-- rounding vs. truncation in division
+SELECT '878.08'::money / 11::float8;
+SELECT '878.08'::money / 11::float4;
+SELECT '878.08'::money / 11::bigint;
+SELECT '878.08'::money / 11::int;
+SELECT '878.08'::money / 11::smallint;
+
+-- check for precision loss in division
+SELECT '90000000000000099.00'::money / 10::bigint;
+SELECT '90000000000000099.00'::money / 10::int;
+SELECT '90000000000000099.00'::money / 10::smallint;
+
+-- Cast int4/int8/numeric to money
+SELECT 1234567890::money;
+SELECT 12345678901234567::money;
+SELECT (-12345)::money;
+SELECT (-1234567890)::money;
+SELECT (-12345678901234567)::money;
+SELECT 1234567890::int4::money;
+SELECT 12345678901234567::int8::money;
+SELECT 12345678901234567::numeric::money;
+SELECT (-1234567890)::int4::money;
+SELECT (-12345678901234567)::int8::money;
+SELECT (-12345678901234567)::numeric::money;
+
+-- Cast from money to numeric
+SELECT '12345678901234567'::money::numeric;
+SELECT '-12345678901234567'::money::numeric;
+SELECT '92233720368547758.07'::money::numeric;
+SELECT '-92233720368547758.08'::money::numeric;
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
new file mode 100644
index 0000000..df1edb4
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,835 @@
+-- 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);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::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);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- 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/mvcc.sql b/src/test/regress/sql/mvcc.sql
new file mode 100644
index 0000000..b22a86d
--- /dev/null
+++ b/src/test/regress/sql/mvcc.sql
@@ -0,0 +1,44 @@
+--
+-- Verify that index scans encountering dead rows produced by an
+-- aborted subtransaction of the current transaction can utilize the
+-- kill_prio_tuple optimization
+--
+-- NB: The table size is currently *not* expected to stay the same, we
+-- don't have logic to trigger opportunistic pruning in cases like
+-- this.
+BEGIN;
+
+SET LOCAL enable_seqscan = false;
+SET LOCAL enable_indexonlyscan = false;
+SET LOCAL enable_bitmapscan = false;
+
+-- Can't easily use a unique index, since dead tuples can be found
+-- independent of the kill_prior_tuples optimization.
+CREATE TABLE clean_aborted_self(key int, data text);
+CREATE INDEX clean_aborted_self_key ON clean_aborted_self(key);
+INSERT INTO clean_aborted_self (key, data) VALUES (-1, 'just to allocate metapage');
+
+-- save index size from before the changes, for comparison
+SELECT pg_relation_size('clean_aborted_self_key') AS clean_aborted_self_key_before \gset
+
+DO $$
+BEGIN
+ -- iterate often enough to see index growth even on larger-than-default page sizes
+ FOR i IN 1..100 LOOP
+ BEGIN
+ -- perform index scan over all the inserted keys to get them to be seen as dead
+ IF EXISTS(SELECT * FROM clean_aborted_self WHERE key > 0 AND key < 100) THEN
+ RAISE data_corrupted USING MESSAGE = 'these rows should not exist';
+ END IF;
+ INSERT INTO clean_aborted_self SELECT g.i, 'rolling back in a sec' FROM generate_series(1, 100) g(i);
+ -- just some error that's not normally thrown
+ RAISE reading_sql_data_not_permitted USING MESSAGE = 'round and round again';
+ EXCEPTION WHEN reading_sql_data_not_permitted THEN END;
+ END LOOP;
+END;$$;
+
+-- show sizes only if they differ
+SELECT :clean_aborted_self_key_before AS size_before, pg_relation_size('clean_aborted_self_key') size_after
+WHERE :clean_aborted_self_key_before != pg_relation_size('clean_aborted_self_key');
+
+ROLLBACK;
diff --git a/src/test/regress/sql/name.sql b/src/test/regress/sql/name.sql
new file mode 100644
index 0000000..29a5d97
--- /dev/null
+++ b/src/test/regress/sql/name.sql
@@ -0,0 +1,87 @@
+--
+-- NAME
+-- all inputs are silently truncated at NAMEDATALEN-1 (63) characters
+--
+
+-- fixed-length by reference
+SELECT name 'name string' = name 'name string' AS "True";
+
+SELECT name 'name string' = name 'name string ' AS "False";
+
+--
+--
+--
+
+CREATE TABLE NAME_TBL(f1 name);
+
+INSERT INTO NAME_TBL(f1) VALUES ('1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR');
+
+INSERT INTO NAME_TBL(f1) VALUES ('1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqr');
+
+INSERT INTO NAME_TBL(f1) VALUES ('asdfghjkl;');
+
+INSERT INTO NAME_TBL(f1) VALUES ('343f%2a');
+
+INSERT INTO NAME_TBL(f1) VALUES ('d34aaasdf');
+
+INSERT INTO NAME_TBL(f1) VALUES ('');
+
+INSERT INTO NAME_TBL(f1) VALUES ('1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ');
+
+
+SELECT * FROM NAME_TBL;
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 <> '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR';
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR';
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 < '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR';
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 <= '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR';
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 > '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR';
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 >= '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR';
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 ~ '.*';
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 !~ '.*';
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 ~ '[0-9]';
+
+SELECT c.f1 FROM NAME_TBL c WHERE c.f1 ~ '.*asdf.*';
+
+DROP TABLE NAME_TBL;
+
+DO $$
+DECLARE r text[];
+BEGIN
+ r := parse_ident('Schemax.Tabley');
+ RAISE NOTICE '%', format('%I.%I', r[1], r[2]);
+ r := parse_ident('"SchemaX"."TableY"');
+ RAISE NOTICE '%', format('%I.%I', r[1], r[2]);
+END;
+$$;
+
+SELECT parse_ident('foo.boo');
+SELECT parse_ident('foo.boo[]'); -- should fail
+SELECT parse_ident('foo.boo[]', strict => false); -- ok
+
+-- should fail
+SELECT parse_ident(' ');
+SELECT parse_ident(' .aaa');
+SELECT parse_ident(' aaa . ');
+SELECT parse_ident('aaa.a%b');
+SELECT parse_ident(E'X\rXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
+
+SELECT length(a[1]), length(a[2]) from parse_ident('"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy') as a ;
+
+SELECT parse_ident(' first . " second " ." third ". " ' || repeat('x',66) || '"');
+SELECT parse_ident(' first . " second " ." third ". " ' || repeat('x',66) || '"')::name[];
+
+SELECT parse_ident(E'"c".X XXXX\002XXXXXX');
+SELECT parse_ident('1020');
+SELECT parse_ident('10.20');
+SELECT parse_ident('.');
+SELECT parse_ident('.1020');
+SELECT parse_ident('xxx.1020');
diff --git a/src/test/regress/sql/namespace.sql b/src/test/regress/sql/namespace.sql
new file mode 100644
index 0000000..6b12c96
--- /dev/null
+++ b/src/test/regress/sql/namespace.sql
@@ -0,0 +1,44 @@
+--
+-- Regression tests for schemas (namespaces)
+--
+
+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
+ );
+
+-- 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/numeric.sql b/src/test/regress/sql/numeric.sql
new file mode 100644
index 0000000..9233c66
--- /dev/null
+++ b/src/test/regress/sql/numeric.sql
@@ -0,0 +1,1365 @@
+--
+-- 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;
+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;
+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;
+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;
+BEGIN TRANSACTION;
+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;
+BEGIN TRANSACTION;
+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;
+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;
+SELECT to_char(val, 'L9999999999999999.099999999999999') FROM num_data;
+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 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 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/numeric_big.sql b/src/test/regress/sql/numeric_big.sql
new file mode 100644
index 0000000..c9d52d0
--- /dev/null
+++ b/src/test/regress/sql/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;
+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;
+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;
+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;
+BEGIN TRANSACTION;
+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;
+BEGIN TRANSACTION;
+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;
+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/numerology.sql b/src/test/regress/sql/numerology.sql
new file mode 100644
index 0000000..fddb58f
--- /dev/null
+++ b/src/test/regress/sql/numerology.sql
@@ -0,0 +1,98 @@
+--
+-- NUMEROLOGY
+-- Test various combinations of numeric types and functions.
+--
+
+--
+-- Test implicit type conversions
+-- This fails for Postgres v6.1 (and earlier?)
+-- so let's try explicit conversions for now - tgl 97/05/07
+--
+
+CREATE TABLE TEMP_FLOAT (f1 FLOAT8);
+
+INSERT INTO TEMP_FLOAT (f1)
+ SELECT float8(f1) FROM INT4_TBL;
+
+INSERT INTO TEMP_FLOAT (f1)
+ SELECT float8(f1) FROM INT2_TBL;
+
+SELECT f1 FROM TEMP_FLOAT
+ ORDER BY f1;
+
+-- int4
+
+CREATE TABLE TEMP_INT4 (f1 INT4);
+
+INSERT INTO TEMP_INT4 (f1)
+ SELECT int4(f1) FROM FLOAT8_TBL
+ WHERE (f1 > -2147483647) AND (f1 < 2147483647);
+
+INSERT INTO TEMP_INT4 (f1)
+ SELECT int4(f1) FROM INT2_TBL;
+
+SELECT f1 FROM TEMP_INT4
+ ORDER BY f1;
+
+-- int2
+
+CREATE TABLE TEMP_INT2 (f1 INT2);
+
+INSERT INTO TEMP_INT2 (f1)
+ SELECT int2(f1) FROM FLOAT8_TBL
+ WHERE (f1 >= -32767) AND (f1 <= 32767);
+
+INSERT INTO TEMP_INT2 (f1)
+ SELECT int2(f1) FROM INT4_TBL
+ WHERE (f1 >= -32767) AND (f1 <= 32767);
+
+SELECT f1 FROM TEMP_INT2
+ ORDER BY f1;
+
+--
+-- Group-by combinations
+--
+
+CREATE TABLE TEMP_GROUP (f1 INT4, f2 INT4, f3 FLOAT8);
+
+INSERT INTO TEMP_GROUP
+ SELECT 1, (- i.f1), (- f.f1)
+ FROM INT4_TBL i, FLOAT8_TBL f;
+
+INSERT INTO TEMP_GROUP
+ SELECT 2, i.f1, f.f1
+ FROM INT4_TBL i, FLOAT8_TBL f;
+
+SELECT DISTINCT f1 AS two FROM TEMP_GROUP ORDER BY 1;
+
+SELECT f1 AS two, max(f3) AS max_float, min(f3) as min_float
+ FROM TEMP_GROUP
+ GROUP BY f1
+ ORDER BY two, max_float, min_float;
+
+-- GROUP BY a result column name is not legal per SQL92, but we accept it
+-- anyway (if the name is not the name of any column exposed by FROM).
+SELECT f1 AS two, max(f3) AS max_float, min(f3) AS min_float
+ FROM TEMP_GROUP
+ GROUP BY two
+ ORDER BY two, max_float, min_float;
+
+SELECT f1 AS two, (max(f3) + 1) AS max_plus_1, (min(f3) - 1) AS min_minus_1
+ FROM TEMP_GROUP
+ GROUP BY f1
+ ORDER BY two, min_minus_1;
+
+SELECT f1 AS two,
+ max(f2) + min(f2) AS max_plus_min,
+ min(f3) - 1 AS min_minus_1
+ FROM TEMP_GROUP
+ GROUP BY f1
+ ORDER BY two, min_minus_1;
+
+DROP TABLE TEMP_INT2;
+
+DROP TABLE TEMP_INT4;
+
+DROP TABLE TEMP_FLOAT;
+
+DROP TABLE TEMP_GROUP;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
new file mode 100644
index 0000000..2f4f66e
--- /dev/null
+++ b/src/test/regress/sql/object_address.sql
@@ -0,0 +1,287 @@
+--
+-- Test for pg_get_object_address
+--
+
+-- Clean up in case a prior regression run failed
+SET client_min_messages TO 'warning';
+DROP ROLE IF EXISTS regress_addr_user;
+RESET client_min_messages;
+
+CREATE USER regress_addr_user;
+
+-- Test generic object addressing/identification functions
+CREATE SCHEMA addr_nsp;
+SET search_path TO 'addr_nsp';
+CREATE FOREIGN DATA WRAPPER addr_fdw;
+CREATE SERVER addr_fserv FOREIGN DATA WRAPPER addr_fdw;
+CREATE TEXT SEARCH DICTIONARY addr_ts_dict (template=simple);
+CREATE TEXT SEARCH CONFIGURATION addr_ts_conf (copy=english);
+CREATE TEXT SEARCH TEMPLATE addr_ts_temp (lexize=dsimple_lexize);
+CREATE TEXT SEARCH PARSER addr_ts_prs
+ (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype);
+CREATE TABLE addr_nsp.gentable (
+ a serial primary key CONSTRAINT a_chk CHECK (a > 0),
+ b text DEFAULT 'hello');
+CREATE TABLE addr_nsp.parttable (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE VIEW addr_nsp.genview AS SELECT * from addr_nsp.gentable;
+CREATE MATERIALIZED VIEW addr_nsp.genmatview AS SELECT * FROM addr_nsp.gentable;
+CREATE TYPE addr_nsp.gencomptype AS (a int);
+CREATE TYPE addr_nsp.genenum AS ENUM ('one', 'two');
+CREATE FOREIGN TABLE addr_nsp.genftable (a int) SERVER addr_fserv;
+CREATE AGGREGATE addr_nsp.genaggr(int4) (sfunc = int4pl, stype = int4);
+CREATE DOMAIN addr_nsp.gendomain AS int4 CONSTRAINT domconstr CHECK (value > 0);
+CREATE FUNCTION addr_nsp.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END; $$;
+CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDURE addr_nsp.trig();
+CREATE POLICY genpol ON addr_nsp.gentable;
+CREATE PROCEDURE addr_nsp.proc(int4) LANGUAGE SQL AS $$ $$;
+CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw;
+CREATE USER MAPPING FOR regress_addr_user SERVER "integer";
+ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user;
+ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM regress_addr_user;
+-- this transform would be quite unsafe to leave lying around,
+-- except that the SQL language pays no attention to transforms:
+CREATE TRANSFORM FOR int LANGUAGE SQL (
+ FROM SQL WITH FUNCTION prsd_lextype(internal),
+ TO SQL WITH FUNCTION int4recv(internal));
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+RESET client_min_messages;
+CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
+CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
+
+-- test some error cases
+SELECT pg_get_object_address('stone', '{}', '{}');
+SELECT pg_get_object_address('table', '{}', '{}');
+SELECT pg_get_object_address('table', '{NULL}', '{}');
+
+-- unrecognized object types
+DO $$
+DECLARE
+ objtype text;
+BEGIN
+ FOR objtype IN VALUES ('toast table'), ('index column'), ('sequence column'),
+ ('toast table column'), ('view column'), ('materialized view column')
+ LOOP
+ BEGIN
+ PERFORM pg_get_object_address(objtype, '{one}', '{}');
+ EXCEPTION WHEN invalid_parameter_value THEN
+ RAISE WARNING 'error for %: %', objtype, sqlerrm;
+ END;
+ END LOOP;
+END;
+$$;
+
+-- miscellaneous other errors
+select * from pg_get_object_address('operator of access method', '{btree,integer_ops,1}', '{int4,bool}');
+select * from pg_get_object_address('operator of access method', '{btree,integer_ops,99}', '{int4,int4}');
+select * from pg_get_object_address('function of access method', '{btree,integer_ops,1}', '{int4,bool}');
+select * from pg_get_object_address('function of access method', '{btree,integer_ops,99}', '{int4,int4}');
+
+DO $$
+DECLARE
+ objtype text;
+ names text[];
+ args text[];
+BEGIN
+ FOR objtype IN VALUES
+ ('table'), ('index'), ('sequence'), ('view'),
+ ('materialized view'), ('foreign table'),
+ ('table column'), ('foreign table column'),
+ ('aggregate'), ('function'), ('procedure'), ('type'), ('cast'),
+ ('table constraint'), ('domain constraint'), ('conversion'), ('default value'),
+ ('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'),
+ ('text search parser'), ('text search dictionary'),
+ ('text search template'), ('text search configuration'),
+ ('policy'), ('user mapping'), ('default acl'), ('transform'),
+ ('operator of access method'), ('function of access method'),
+ ('publication relation')
+ LOOP
+ FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}')
+ LOOP
+ FOR args IN VALUES ('{}'), ('{integer}')
+ LOOP
+ BEGIN
+ PERFORM pg_get_object_address(objtype, names, args);
+ EXCEPTION WHEN OTHERS THEN
+ RAISE WARNING 'error for %,%,%: %', objtype, names, args, sqlerrm;
+ END;
+ END LOOP;
+ END LOOP;
+ END LOOP;
+END;
+$$;
+
+-- these object types cannot be qualified names
+SELECT pg_get_object_address('language', '{one}', '{}');
+SELECT pg_get_object_address('language', '{one,two}', '{}');
+SELECT pg_get_object_address('large object', '{123}', '{}');
+SELECT pg_get_object_address('large object', '{123,456}', '{}');
+SELECT pg_get_object_address('large object', '{blargh}', '{}');
+SELECT pg_get_object_address('schema', '{one}', '{}');
+SELECT pg_get_object_address('schema', '{one,two}', '{}');
+SELECT pg_get_object_address('role', '{one}', '{}');
+SELECT pg_get_object_address('role', '{one,two}', '{}');
+SELECT pg_get_object_address('database', '{one}', '{}');
+SELECT pg_get_object_address('database', '{one,two}', '{}');
+SELECT pg_get_object_address('tablespace', '{one}', '{}');
+SELECT pg_get_object_address('tablespace', '{one,two}', '{}');
+SELECT pg_get_object_address('foreign-data wrapper', '{one}', '{}');
+SELECT pg_get_object_address('foreign-data wrapper', '{one,two}', '{}');
+SELECT pg_get_object_address('server', '{one}', '{}');
+SELECT pg_get_object_address('server', '{one,two}', '{}');
+SELECT pg_get_object_address('extension', '{one}', '{}');
+SELECT pg_get_object_address('extension', '{one,two}', '{}');
+SELECT pg_get_object_address('event trigger', '{one}', '{}');
+SELECT pg_get_object_address('event trigger', '{one,two}', '{}');
+SELECT pg_get_object_address('access method', '{one}', '{}');
+SELECT pg_get_object_address('access method', '{one,two}', '{}');
+SELECT pg_get_object_address('publication', '{one}', '{}');
+SELECT pg_get_object_address('publication', '{one,two}', '{}');
+SELECT pg_get_object_address('subscription', '{one}', '{}');
+SELECT pg_get_object_address('subscription', '{one,two}', '{}');
+
+-- test successful cases
+WITH objects (type, name, args) AS (VALUES
+ ('table', '{addr_nsp, gentable}'::text[], '{}'::text[]),
+ ('table', '{addr_nsp, parttable}'::text[], '{}'::text[]),
+ ('index', '{addr_nsp, gentable_pkey}', '{}'),
+ ('index', '{addr_nsp, parttable_pkey}', '{}'),
+ ('sequence', '{addr_nsp, gentable_a_seq}', '{}'),
+ -- toast table
+ ('view', '{addr_nsp, genview}', '{}'),
+ ('materialized view', '{addr_nsp, genmatview}', '{}'),
+ ('foreign table', '{addr_nsp, genftable}', '{}'),
+ ('table column', '{addr_nsp, gentable, b}', '{}'),
+ ('foreign table column', '{addr_nsp, genftable, a}', '{}'),
+ ('aggregate', '{addr_nsp, genaggr}', '{int4}'),
+ ('function', '{pg_catalog, pg_identify_object}', '{pg_catalog.oid, pg_catalog.oid, int4}'),
+ ('procedure', '{addr_nsp, proc}', '{int4}'),
+ ('type', '{pg_catalog._int4}', '{}'),
+ ('type', '{addr_nsp.gendomain}', '{}'),
+ ('type', '{addr_nsp.gencomptype}', '{}'),
+ ('type', '{addr_nsp.genenum}', '{}'),
+ ('cast', '{int8}', '{int4}'),
+ ('collation', '{default}', '{}'),
+ ('table constraint', '{addr_nsp, gentable, a_chk}', '{}'),
+ ('domain constraint', '{addr_nsp.gendomain}', '{domconstr}'),
+ ('conversion', '{pg_catalog, koi8_r_to_mic}', '{}'),
+ ('default value', '{addr_nsp, gentable, b}', '{}'),
+ ('language', '{plpgsql}', '{}'),
+ -- large object
+ ('operator', '{+}', '{int4, int4}'),
+ ('operator class', '{btree, int4_ops}', '{}'),
+ ('operator family', '{btree, integer_ops}', '{}'),
+ ('operator of access method', '{btree,integer_ops,1}', '{integer,integer}'),
+ ('function of access method', '{btree,integer_ops,2}', '{integer,integer}'),
+ ('rule', '{addr_nsp, genview, _RETURN}', '{}'),
+ ('trigger', '{addr_nsp, gentable, t}', '{}'),
+ ('schema', '{addr_nsp}', '{}'),
+ ('text search parser', '{addr_ts_prs}', '{}'),
+ ('text search dictionary', '{addr_ts_dict}', '{}'),
+ ('text search template', '{addr_ts_temp}', '{}'),
+ ('text search configuration', '{addr_ts_conf}', '{}'),
+ ('role', '{regress_addr_user}', '{}'),
+ -- database
+ -- tablespace
+ ('foreign-data wrapper', '{addr_fdw}', '{}'),
+ ('server', '{addr_fserv}', '{}'),
+ ('user mapping', '{regress_addr_user}', '{integer}'),
+ ('default acl', '{regress_addr_user,public}', '{r}'),
+ ('default acl', '{regress_addr_user}', '{r}'),
+ -- extension
+ -- event trigger
+ ('policy', '{addr_nsp, gentable, genpol}', '{}'),
+ ('transform', '{int}', '{sql}'),
+ ('access method', '{btree}', '{}'),
+ ('publication', '{addr_pub}', '{}'),
+ ('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('subscription', '{regress_addr_sub}', '{}'),
+ ('statistics object', '{addr_nsp, gentable_stat}', '{}')
+ )
+SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
+ -- test roundtrip through pg_identify_object_as_address
+ ROW(pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)) =
+ ROW(pg_identify_object(addr2.classid, addr2.objid, addr2.objsubid))
+ FROM objects, pg_get_object_address(type, name, args) addr1,
+ pg_identify_object_as_address(classid, objid, objsubid) ioa(typ,nms,args),
+ pg_get_object_address(typ, nms, ioa.args) as addr2
+ ORDER BY addr1.classid, addr1.objid, addr1.objsubid;
+
+---
+--- Cleanup resources
+---
+DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
+DROP PUBLICATION addr_pub;
+DROP SUBSCRIPTION regress_addr_sub;
+
+DROP SCHEMA addr_nsp CASCADE;
+
+DROP OWNED BY regress_addr_user;
+DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Make sure that NULL handling is correct.
+\pset null 'NULL'
+-- Temporarily disable fancy output, so as future additions never create
+-- a large amount of diffs.
+\a\t
+
+-- Keep this list in the same order as getObjectIdentityParts()
+-- in objectaddress.c.
+WITH objects (classid, objid, objsubid) AS (VALUES
+ ('pg_class'::regclass, 0, 0), -- no relation
+ ('pg_class'::regclass, 'pg_class'::regclass, 100), -- no column for relation
+ ('pg_proc'::regclass, 0, 0), -- no function
+ ('pg_type'::regclass, 0, 0), -- no type
+ ('pg_cast'::regclass, 0, 0), -- no cast
+ ('pg_collation'::regclass, 0, 0), -- no collation
+ ('pg_constraint'::regclass, 0, 0), -- no constraint
+ ('pg_conversion'::regclass, 0, 0), -- no conversion
+ ('pg_attrdef'::regclass, 0, 0), -- no default attribute
+ ('pg_language'::regclass, 0, 0), -- no language
+ ('pg_largeobject'::regclass, 0, 0), -- no large object, no error
+ ('pg_operator'::regclass, 0, 0), -- no operator
+ ('pg_opclass'::regclass, 0, 0), -- no opclass, no need to check for no access method
+ ('pg_opfamily'::regclass, 0, 0), -- no opfamily
+ ('pg_am'::regclass, 0, 0), -- no access method
+ ('pg_amop'::regclass, 0, 0), -- no AM operator
+ ('pg_amproc'::regclass, 0, 0), -- no AM proc
+ ('pg_rewrite'::regclass, 0, 0), -- no rewrite
+ ('pg_trigger'::regclass, 0, 0), -- no trigger
+ ('pg_namespace'::regclass, 0, 0), -- no schema
+ ('pg_statistic_ext'::regclass, 0, 0), -- no statistics
+ ('pg_ts_parser'::regclass, 0, 0), -- no TS parser
+ ('pg_ts_dict'::regclass, 0, 0), -- no TS dictionnary
+ ('pg_ts_template'::regclass, 0, 0), -- no TS template
+ ('pg_ts_config'::regclass, 0, 0), -- no TS configuration
+ ('pg_authid'::regclass, 0, 0), -- no role
+ ('pg_database'::regclass, 0, 0), -- no database
+ ('pg_tablespace'::regclass, 0, 0), -- no tablespace
+ ('pg_foreign_data_wrapper'::regclass, 0, 0), -- no FDW
+ ('pg_foreign_server'::regclass, 0, 0), -- no server
+ ('pg_user_mapping'::regclass, 0, 0), -- no user mapping
+ ('pg_default_acl'::regclass, 0, 0), -- no default ACL
+ ('pg_extension'::regclass, 0, 0), -- no extension
+ ('pg_event_trigger'::regclass, 0, 0), -- no event trigger
+ ('pg_policy'::regclass, 0, 0), -- no policy
+ ('pg_publication'::regclass, 0, 0), -- no publication
+ ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
+ ('pg_subscription'::regclass, 0, 0), -- no subscription
+ ('pg_transform'::regclass, 0, 0) -- no transformation
+ )
+SELECT ROW(pg_identify_object(objects.classid, objects.objid, objects.objsubid))
+ AS ident,
+ ROW(pg_identify_object_as_address(objects.classid, objects.objid, objects.objsubid))
+ AS addr,
+ pg_describe_object(objects.classid, objects.objid, objects.objsubid)
+ AS descr
+FROM objects
+ORDER BY objects.classid, objects.objid, objects.objsubid;
+
+-- restore normal output mode
+\a\t
diff --git a/src/test/regress/sql/oid.sql b/src/test/regress/sql/oid.sql
new file mode 100644
index 0000000..25b4b68
--- /dev/null
+++ b/src/test/regress/sql/oid.sql
@@ -0,0 +1,43 @@
+--
+-- OID
+--
+
+CREATE TABLE OID_TBL(f1 oid);
+
+INSERT INTO OID_TBL(f1) VALUES ('1234');
+INSERT INTO OID_TBL(f1) VALUES ('1235');
+INSERT INTO OID_TBL(f1) VALUES ('987');
+INSERT INTO OID_TBL(f1) VALUES ('-1040');
+INSERT INTO OID_TBL(f1) VALUES ('99999999');
+INSERT INTO OID_TBL(f1) VALUES ('5 ');
+INSERT INTO OID_TBL(f1) VALUES (' 10 ');
+-- leading/trailing hard tab is also allowed
+INSERT INTO OID_TBL(f1) VALUES (' 15 ');
+
+-- bad inputs
+INSERT INTO OID_TBL(f1) VALUES ('');
+INSERT INTO OID_TBL(f1) VALUES (' ');
+INSERT INTO OID_TBL(f1) VALUES ('asdfasd');
+INSERT INTO OID_TBL(f1) VALUES ('99asdfasd');
+INSERT INTO OID_TBL(f1) VALUES ('5 d');
+INSERT INTO OID_TBL(f1) VALUES (' 5d');
+INSERT INTO OID_TBL(f1) VALUES ('5 5');
+INSERT INTO OID_TBL(f1) VALUES (' - 500');
+INSERT INTO OID_TBL(f1) VALUES ('32958209582039852935');
+INSERT INTO OID_TBL(f1) VALUES ('-23582358720398502385');
+
+SELECT * FROM OID_TBL;
+
+SELECT o.* FROM OID_TBL o WHERE o.f1 = 1234;
+
+SELECT o.* FROM OID_TBL o WHERE o.f1 <> '1234';
+
+SELECT o.* FROM OID_TBL o WHERE o.f1 <= '1234';
+
+SELECT o.* FROM OID_TBL o WHERE o.f1 < '1234';
+
+SELECT o.* FROM OID_TBL o WHERE o.f1 >= '1234';
+
+SELECT o.* FROM OID_TBL o WHERE o.f1 > '1234';
+
+DROP TABLE OID_TBL;
diff --git a/src/test/regress/sql/oidjoins.sql b/src/test/regress/sql/oidjoins.sql
new file mode 100644
index 0000000..8b22e6d
--- /dev/null
+++ b/src/test/regress/sql/oidjoins.sql
@@ -0,0 +1,49 @@
+--
+-- Verify system catalog foreign key relationships
+--
+DO $doblock$
+declare
+ fk record;
+ nkeys integer;
+ cmd text;
+ err record;
+begin
+ for fk in select * from pg_get_catalog_foreign_keys()
+ loop
+ raise notice 'checking % % => % %',
+ fk.fktable, fk.fkcols, fk.pktable, fk.pkcols;
+ nkeys := array_length(fk.fkcols, 1);
+ cmd := 'SELECT ctid';
+ for i in 1 .. nkeys loop
+ cmd := cmd || ', ' || quote_ident(fk.fkcols[i]);
+ end loop;
+ if fk.is_array then
+ cmd := cmd || ' FROM (SELECT ctid';
+ for i in 1 .. nkeys-1 loop
+ cmd := cmd || ', ' || quote_ident(fk.fkcols[i]);
+ end loop;
+ cmd := cmd || ', unnest(' || quote_ident(fk.fkcols[nkeys]);
+ cmd := cmd || ') as ' || quote_ident(fk.fkcols[nkeys]);
+ cmd := cmd || ' FROM ' || fk.fktable::text || ') fk WHERE ';
+ else
+ cmd := cmd || ' FROM ' || fk.fktable::text || ' fk WHERE ';
+ end if;
+ if fk.is_opt then
+ for i in 1 .. nkeys loop
+ cmd := cmd || quote_ident(fk.fkcols[i]) || ' != 0 AND ';
+ end loop;
+ end if;
+ cmd := cmd || 'NOT EXISTS(SELECT 1 FROM ' || fk.pktable::text || ' pk WHERE ';
+ for i in 1 .. nkeys loop
+ if i > 1 then cmd := cmd || ' AND '; end if;
+ cmd := cmd || 'pk.' || quote_ident(fk.pkcols[i]);
+ cmd := cmd || ' = fk.' || quote_ident(fk.fkcols[i]);
+ end loop;
+ cmd := cmd || ')';
+ -- raise notice 'cmd = %', cmd;
+ for err in execute cmd loop
+ raise warning 'FK VIOLATION IN %(%): %', fk.fktable, fk.fkcols, err;
+ end loop;
+ end loop;
+end
+$doblock$;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
new file mode 100644
index 0000000..5a9c479
--- /dev/null
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -0,0 +1,1393 @@
+--
+-- OPR_SANITY
+-- Sanity checks for common errors in making operator/procedure system tables:
+-- pg_operator, pg_proc, pg_cast, pg_conversion, pg_aggregate, pg_am,
+-- pg_amop, pg_amproc, pg_opclass, pg_opfamily, pg_index.
+--
+-- Every test failure in this file should be closely inspected.
+-- The description of the failing test should be read carefully before
+-- adjusting the expected output. In most cases, the queries should
+-- not find *any* matching entries.
+--
+-- 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.
+--
+-- NB: run this test earlier than the create_operator test, because
+-- that test creates some bogus operators...
+
+
+-- **************** pg_proc ****************
+
+-- Look for illegal values in pg_proc fields.
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prolang = 0 OR p1.prorettype = 0 OR
+ p1.pronargs < 0 OR
+ p1.pronargdefaults < 0 OR
+ p1.pronargdefaults > p1.pronargs OR
+ array_lower(p1.proargtypes, 1) != 0 OR
+ array_upper(p1.proargtypes, 1) != p1.pronargs-1 OR
+ 0::oid = ANY (p1.proargtypes) OR
+ procost <= 0 OR
+ CASE WHEN proretset THEN prorows <= 0 ELSE prorows != 0 END OR
+ prokind NOT IN ('f', 'a', 'w', 'p') OR
+ provolatile NOT IN ('i', 's', 'v') OR
+ proparallel NOT IN ('s', 'r', 'u');
+
+-- prosrc should never be null; it can be empty only if prosqlbody isn't null
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE prosrc IS NULL;
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE (prosrc = '' OR prosrc = '-') AND prosqlbody IS NULL;
+
+-- proretset should only be set for normal functions
+SELECT p1.oid, p1.proname
+FROM pg_proc AS p1
+WHERE proretset AND prokind != 'f';
+
+-- currently, no built-in functions should be SECURITY DEFINER;
+-- this might change in future, but there will probably never be many.
+SELECT p1.oid, p1.proname
+FROM pg_proc AS p1
+WHERE prosecdef
+ORDER BY 1;
+
+-- pronargdefaults should be 0 iff proargdefaults is null
+SELECT p1.oid, p1.proname
+FROM pg_proc AS p1
+WHERE (pronargdefaults <> 0) != (proargdefaults IS NOT NULL);
+
+-- probin should be non-empty for C functions, null everywhere else
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE prolang = 13 AND (probin IS NULL OR probin = '' OR probin = '-');
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE prolang != 13 AND probin IS NOT NULL;
+
+-- Look for conflicting proc definitions (same names and input datatypes).
+-- (This test should be dead code now that we have the unique index
+-- pg_proc_proname_args_nsp_index, but I'll leave it in anyway.)
+
+SELECT p1.oid, p1.proname, p2.oid, p2.proname
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.proname = p2.proname AND
+ p1.pronargs = p2.pronargs AND
+ p1.proargtypes = p2.proargtypes;
+
+-- Considering only built-in procs (prolang = 12), look for multiple uses
+-- of the same internal function (ie, matching prosrc fields). It's OK to
+-- have several entries with different pronames for the same internal function,
+-- but conflicts in the number of arguments and other critical items should
+-- be complained of. (We don't check data types here; see next query.)
+-- Note: ignore aggregate functions here, since they all point to the same
+-- dummy built-in function.
+
+SELECT p1.oid, p1.proname, p2.oid, p2.proname
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid < p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ (p1.prokind != 'a' OR p2.prokind != 'a') AND
+ (p1.prolang != p2.prolang OR
+ p1.prokind != p2.prokind OR
+ p1.prosecdef != p2.prosecdef OR
+ p1.proleakproof != p2.proleakproof OR
+ p1.proisstrict != p2.proisstrict OR
+ p1.proretset != p2.proretset OR
+ p1.provolatile != p2.provolatile OR
+ p1.pronargs != p2.pronargs);
+
+-- Look for uses of different type OIDs in the argument/result type fields
+-- for different aliases of the same built-in function.
+-- This indicates that the types are being presumed to be binary-equivalent,
+-- or that the built-in function is prepared to deal with different types.
+-- That's not wrong, necessarily, but we make lists of all the types being
+-- so treated. Note that the expected output of this part of the test will
+-- need to be modified whenever new pairs of types are made binary-equivalent,
+-- or when new polymorphic built-in functions are added!
+-- Note: ignore aggregate functions here, since they all point to the same
+-- dummy built-in function. Likewise, ignore range and multirange constructor
+-- functions.
+
+SELECT DISTINCT p1.prorettype::regtype, p2.prorettype::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ p1.prosrc NOT LIKE E'range\\_constructor_' AND
+ p2.prosrc NOT LIKE E'range\\_constructor_' AND
+ p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
+ p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
+ (p1.prorettype < p2.prorettype)
+ORDER BY 1, 2;
+
+SELECT DISTINCT p1.proargtypes[0]::regtype, p2.proargtypes[0]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ p1.prosrc NOT LIKE E'range\\_constructor_' AND
+ p2.prosrc NOT LIKE E'range\\_constructor_' AND
+ p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
+ p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
+ (p1.proargtypes[0] < p2.proargtypes[0])
+ORDER BY 1, 2;
+
+SELECT DISTINCT p1.proargtypes[1]::regtype, p2.proargtypes[1]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ p1.prosrc NOT LIKE E'range\\_constructor_' AND
+ p2.prosrc NOT LIKE E'range\\_constructor_' AND
+ p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
+ p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
+ (p1.proargtypes[1] < p2.proargtypes[1])
+ORDER BY 1, 2;
+
+SELECT DISTINCT p1.proargtypes[2]::regtype, p2.proargtypes[2]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ (p1.proargtypes[2] < p2.proargtypes[2])
+ORDER BY 1, 2;
+
+SELECT DISTINCT p1.proargtypes[3]::regtype, p2.proargtypes[3]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ (p1.proargtypes[3] < p2.proargtypes[3])
+ORDER BY 1, 2;
+
+SELECT DISTINCT p1.proargtypes[4]::regtype, p2.proargtypes[4]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ (p1.proargtypes[4] < p2.proargtypes[4])
+ORDER BY 1, 2;
+
+SELECT DISTINCT p1.proargtypes[5]::regtype, p2.proargtypes[5]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ (p1.proargtypes[5] < p2.proargtypes[5])
+ORDER BY 1, 2;
+
+SELECT DISTINCT p1.proargtypes[6]::regtype, p2.proargtypes[6]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ (p1.proargtypes[6] < p2.proargtypes[6])
+ORDER BY 1, 2;
+
+SELECT DISTINCT p1.proargtypes[7]::regtype, p2.proargtypes[7]::regtype
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid != p2.oid AND
+ p1.prosrc = p2.prosrc AND
+ p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.prokind != 'a' AND p2.prokind != 'a' AND
+ (p1.proargtypes[7] < p2.proargtypes[7])
+ORDER BY 1, 2;
+
+-- Look for functions that return type "internal" and do not have any
+-- "internal" argument. Such a function would be a security hole since
+-- it might be used to call an internal function from an SQL command.
+-- As of 7.3 this query should find only internal_in, which is safe because
+-- it always throws an error when called.
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prorettype = 'internal'::regtype AND NOT
+ 'internal'::regtype = ANY (p1.proargtypes);
+
+-- Look for functions that return a polymorphic type and do not have any
+-- polymorphic argument. Calls of such functions would be unresolvable
+-- at parse time. As of 9.6 this query should find only some input functions
+-- and GiST support functions associated with these pseudotypes.
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prorettype IN
+ ('anyelement'::regtype, 'anyarray'::regtype, 'anynonarray'::regtype,
+ 'anyenum'::regtype)
+ AND NOT
+ ('anyelement'::regtype = ANY (p1.proargtypes) OR
+ 'anyarray'::regtype = ANY (p1.proargtypes) OR
+ 'anynonarray'::regtype = ANY (p1.proargtypes) OR
+ 'anyenum'::regtype = ANY (p1.proargtypes) OR
+ 'anyrange'::regtype = ANY (p1.proargtypes) OR
+ 'anymultirange'::regtype = ANY (p1.proargtypes))
+ORDER BY 2;
+
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
+ AND NOT
+ ('anyrange'::regtype = ANY (p1.proargtypes) OR
+ 'anymultirange'::regtype = ANY (p1.proargtypes))
+ORDER BY 2;
+
+-- similarly for the anycompatible family
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prorettype IN
+ ('anycompatible'::regtype, 'anycompatiblearray'::regtype,
+ 'anycompatiblenonarray'::regtype)
+ AND NOT
+ ('anycompatible'::regtype = ANY (p1.proargtypes) OR
+ 'anycompatiblearray'::regtype = ANY (p1.proargtypes) OR
+ 'anycompatiblenonarray'::regtype = ANY (p1.proargtypes) OR
+ 'anycompatiblerange'::regtype = ANY (p1.proargtypes))
+ORDER BY 2;
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prorettype = 'anycompatiblerange'::regtype
+ AND NOT
+ 'anycompatiblerange'::regtype = ANY (p1.proargtypes)
+ORDER BY 2;
+
+
+-- Look for functions that accept cstring and are neither datatype input
+-- functions nor encoding conversion functions. It's almost never a good
+-- idea to use cstring input for a function meant to be called from SQL;
+-- text should be used instead, because cstring lacks suitable casts.
+-- As of 9.6 this query should find only cstring_out and cstring_send.
+-- However, we must manually exclude shell_in, which might or might not be
+-- rejected by the EXISTS clause depending on whether there are currently
+-- any shell types.
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE 'cstring'::regtype = ANY (p1.proargtypes)
+ AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typinput = p1.oid)
+ AND NOT EXISTS(SELECT 1 FROM pg_conversion WHERE conproc = p1.oid)
+ AND p1.oid != 'shell_in(cstring)'::regprocedure
+ORDER BY 1;
+
+-- Likewise, look for functions that return cstring and aren't datatype output
+-- functions nor typmod output functions.
+-- As of 9.6 this query should find only cstring_in and cstring_recv.
+-- However, we must manually exclude shell_out.
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE p1.prorettype = 'cstring'::regtype
+ AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typoutput = p1.oid)
+ AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typmodout = p1.oid)
+ AND p1.oid != 'shell_out(void)'::regprocedure
+ORDER BY 1;
+
+-- Check for length inconsistencies between the various argument-info arrays.
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE proallargtypes IS NOT NULL AND
+ array_length(proallargtypes,1) < array_length(proargtypes,1);
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE proargmodes IS NOT NULL AND
+ array_length(proargmodes,1) < array_length(proargtypes,1);
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE proargnames IS NOT NULL AND
+ array_length(proargnames,1) < array_length(proargtypes,1);
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE proallargtypes IS NOT NULL AND proargmodes IS NOT NULL AND
+ array_length(proallargtypes,1) <> array_length(proargmodes,1);
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE proallargtypes IS NOT NULL AND proargnames IS NOT NULL AND
+ array_length(proallargtypes,1) <> array_length(proargnames,1);
+
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1
+WHERE proargmodes IS NOT NULL AND proargnames IS NOT NULL AND
+ array_length(proargmodes,1) <> array_length(proargnames,1);
+
+-- Check that proallargtypes matches proargtypes
+SELECT p1.oid, p1.proname, p1.proargtypes, p1.proallargtypes, p1.proargmodes
+FROM pg_proc as p1
+WHERE proallargtypes IS NOT NULL AND
+ ARRAY(SELECT unnest(proargtypes)) <>
+ ARRAY(SELECT proallargtypes[i]
+ FROM generate_series(1, array_length(proallargtypes, 1)) g(i)
+ WHERE proargmodes IS NULL OR proargmodes[i] IN ('i', 'b', 'v'));
+
+-- Check for prosupport functions with the wrong signature
+SELECT p1.oid, p1.proname, p2.oid, p2.proname
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p2.oid = p1.prosupport AND
+ (p2.prorettype != 'internal'::regtype OR p2.proretset OR p2.pronargs != 1
+ OR p2.proargtypes[0] != 'internal'::regtype);
+
+-- Insist that all built-in pg_proc entries have descriptions
+SELECT p1.oid, p1.proname
+FROM pg_proc as p1 LEFT JOIN pg_description as d
+ ON p1.tableoid = d.classoid and p1.oid = d.objoid and d.objsubid = 0
+WHERE d.classoid IS NULL AND p1.oid <= 9999;
+
+-- List of built-in leakproof functions
+--
+-- Leakproof functions should only be added after carefully
+-- scrutinizing all possibly executed codepaths for possible
+-- information leaks. Don't add functions here unless you know what a
+-- leakproof function is. If unsure, don't mark it as such.
+
+-- temporarily disable fancy output, so catalog changes create less diff noise
+\a\t
+
+SELECT p1.oid::regprocedure
+FROM pg_proc p1 JOIN pg_namespace pn
+ ON pronamespace = pn.oid
+WHERE nspname = 'pg_catalog' AND proleakproof
+ORDER BY 1;
+
+-- restore normal output mode
+\a\t
+
+-- List of functions used by libpq's fe-lobj.c
+--
+-- If the output of this query changes, you probably broke libpq.
+-- lo_initialize() assumes that there will be at most one match for
+-- each listed name.
+select proname, oid from pg_catalog.pg_proc
+where proname in (
+ 'lo_open',
+ 'lo_close',
+ 'lo_creat',
+ 'lo_create',
+ 'lo_unlink',
+ 'lo_lseek',
+ 'lo_lseek64',
+ 'lo_tell',
+ 'lo_tell64',
+ 'lo_truncate',
+ 'lo_truncate64',
+ 'loread',
+ 'lowrite')
+and pronamespace = (select oid from pg_catalog.pg_namespace
+ where nspname = 'pg_catalog')
+order by 1;
+
+-- Check that all immutable functions are marked parallel safe
+SELECT p1.oid, p1.proname
+FROM pg_proc AS p1
+WHERE provolatile = 'i' AND proparallel = 'u';
+
+
+-- **************** pg_cast ****************
+
+-- Catch bogus values in pg_cast columns (other than cases detected by
+-- oidjoins test).
+
+SELECT *
+FROM pg_cast c
+WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
+ OR castmethod NOT IN ('f', 'b' ,'i');
+
+-- Check that castfunc is nonzero only for cast methods that need a function,
+-- and zero otherwise
+
+SELECT *
+FROM pg_cast c
+WHERE (castmethod = 'f' AND castfunc = 0)
+ OR (castmethod IN ('b', 'i') AND castfunc <> 0);
+
+-- Look for casts to/from the same type that aren't length coercion functions.
+-- (We assume they are length coercions if they take multiple arguments.)
+-- Such entries are not necessarily harmful, but they are useless.
+
+SELECT *
+FROM pg_cast c
+WHERE castsource = casttarget AND castfunc = 0;
+
+SELECT c.*
+FROM pg_cast c, pg_proc p
+WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
+
+-- Look for cast functions that don't have the right signature. The
+-- argument and result types in pg_proc must be the same as, or binary
+-- compatible with, what it says in pg_cast.
+-- As a special case, we allow casts from CHAR(n) that use functions
+-- declared to take TEXT. This does not pass the binary-coercibility test
+-- because CHAR(n)-to-TEXT normally invokes rtrim(). However, the results
+-- are the same, so long as the function is one that ignores trailing blanks.
+
+SELECT c.*
+FROM pg_cast c, pg_proc p
+WHERE c.castfunc = p.oid AND
+ (p.pronargs < 1 OR p.pronargs > 3
+ OR NOT (binary_coercible(c.castsource, p.proargtypes[0])
+ OR (c.castsource = 'character'::regtype AND
+ p.proargtypes[0] = 'text'::regtype))
+ OR NOT binary_coercible(p.prorettype, c.casttarget));
+
+SELECT c.*
+FROM pg_cast c, pg_proc p
+WHERE c.castfunc = p.oid AND
+ ((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
+ (p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
+
+-- Look for binary compatible casts that do not have the reverse
+-- direction registered as well, or where the reverse direction is not
+-- also binary compatible. This is legal, but usually not intended.
+
+-- As of 7.4, this finds the casts from text and varchar to bpchar, because
+-- those are binary-compatible while the reverse way goes through rtrim().
+
+-- As of 8.2, this finds the cast from cidr to inet, because that is a
+-- trivial binary coercion while the other way goes through inet_to_cidr().
+
+-- As of 8.3, this finds the casts from xml to text, varchar, and bpchar,
+-- because those are binary-compatible while the reverse goes through
+-- texttoxml(), which does an XML syntax check.
+
+-- As of 9.1, this finds the cast from pg_node_tree to text, which we
+-- intentionally do not provide a reverse pathway for.
+
+SELECT castsource::regtype, casttarget::regtype, castfunc, castcontext
+FROM pg_cast c
+WHERE c.castmethod = 'b' AND
+ NOT EXISTS (SELECT 1 FROM pg_cast k
+ WHERE k.castmethod = 'b' AND
+ k.castsource = c.casttarget AND
+ k.casttarget = c.castsource);
+
+
+-- **************** pg_conversion ****************
+
+-- Look for illegal values in pg_conversion fields.
+
+SELECT p1.oid, p1.conname
+FROM pg_conversion as p1
+WHERE p1.conproc = 0 OR
+ pg_encoding_to_char(conforencoding) = '' OR
+ pg_encoding_to_char(contoencoding) = '';
+
+-- Look for conprocs that don't have the expected signature.
+
+SELECT p.oid, p.proname, c.oid, c.conname
+FROM pg_proc p, pg_conversion c
+WHERE p.oid = c.conproc AND
+ (p.prorettype != 'int4'::regtype OR p.proretset OR
+ p.pronargs != 6 OR
+ p.proargtypes[0] != 'int4'::regtype OR
+ p.proargtypes[1] != 'int4'::regtype OR
+ p.proargtypes[2] != 'cstring'::regtype OR
+ p.proargtypes[3] != 'internal'::regtype OR
+ p.proargtypes[4] != 'int4'::regtype OR
+ p.proargtypes[5] != 'bool'::regtype);
+
+-- Check for conprocs that don't perform the specific conversion that
+-- pg_conversion alleges they do, by trying to invoke each conversion
+-- on some simple ASCII data. (The conproc should throw an error if
+-- it doesn't accept the encodings that are passed to it.)
+-- Unfortunately, we can't test non-default conprocs this way, because
+-- there is no way to ask convert() to invoke them, and we cannot call
+-- them directly from SQL. But there are no non-default built-in
+-- conversions anyway.
+-- (Similarly, this doesn't cope with any search path issues.)
+
+SELECT p1.oid, p1.conname
+FROM pg_conversion as p1
+WHERE condefault AND
+ convert('ABC'::bytea, pg_encoding_to_char(conforencoding),
+ pg_encoding_to_char(contoencoding)) != 'ABC';
+
+
+-- **************** pg_operator ****************
+
+-- Look for illegal values in pg_operator fields.
+
+SELECT p1.oid, p1.oprname
+FROM pg_operator as p1
+WHERE (p1.oprkind != 'b' AND p1.oprkind != 'l') OR
+ p1.oprresult = 0 OR p1.oprcode = 0;
+
+-- Look for missing or unwanted operand types
+
+SELECT p1.oid, p1.oprname
+FROM pg_operator as p1
+WHERE (p1.oprleft = 0 and p1.oprkind != 'l') OR
+ (p1.oprleft != 0 and p1.oprkind = 'l') OR
+ p1.oprright = 0;
+
+-- Look for conflicting operator definitions (same names and input datatypes).
+
+SELECT p1.oid, p1.oprcode, p2.oid, p2.oprcode
+FROM pg_operator AS p1, pg_operator AS p2
+WHERE p1.oid != p2.oid AND
+ p1.oprname = p2.oprname AND
+ p1.oprkind = p2.oprkind AND
+ p1.oprleft = p2.oprleft AND
+ p1.oprright = p2.oprright;
+
+-- Look for commutative operators that don't commute.
+-- DEFINITIONAL NOTE: If A.oprcom = B, then x A y has the same result as y B x.
+-- We expect that B will always say that B.oprcom = A as well; that's not
+-- inherently essential, but it would be inefficient not to mark it so.
+
+SELECT p1.oid, p1.oprcode, p2.oid, p2.oprcode
+FROM pg_operator AS p1, pg_operator AS p2
+WHERE p1.oprcom = p2.oid AND
+ (p1.oprkind != 'b' OR
+ p1.oprleft != p2.oprright OR
+ p1.oprright != p2.oprleft OR
+ p1.oprresult != p2.oprresult OR
+ p1.oid != p2.oprcom);
+
+-- Look for negatory operators that don't agree.
+-- DEFINITIONAL NOTE: If A.oprnegate = B, then both A and B must yield
+-- boolean results, and (x A y) == ! (x B y), or the equivalent for
+-- single-operand operators.
+-- We expect that B will always say that B.oprnegate = A as well; that's not
+-- inherently essential, but it would be inefficient not to mark it so.
+-- Also, A and B had better not be the same operator.
+
+SELECT p1.oid, p1.oprcode, p2.oid, p2.oprcode
+FROM pg_operator AS p1, pg_operator AS p2
+WHERE p1.oprnegate = p2.oid AND
+ (p1.oprkind != p2.oprkind OR
+ p1.oprleft != p2.oprleft OR
+ p1.oprright != p2.oprright OR
+ p1.oprresult != 'bool'::regtype OR
+ p2.oprresult != 'bool'::regtype OR
+ p1.oid != p2.oprnegate OR
+ p1.oid = p2.oid);
+
+-- Make a list of the names of operators that are claimed to be commutator
+-- pairs. This list will grow over time, but before accepting a new entry
+-- make sure you didn't link the wrong operators.
+
+SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
+FROM pg_operator o1, pg_operator o2
+WHERE o1.oprcom = o2.oid AND o1.oprname <= o2.oprname
+ORDER BY 1, 2;
+
+-- Likewise for negator pairs.
+
+SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
+FROM pg_operator o1, pg_operator o2
+WHERE o1.oprnegate = o2.oid AND o1.oprname <= o2.oprname
+ORDER BY 1, 2;
+
+-- A mergejoinable or hashjoinable operator must be binary, must return
+-- boolean, and must have a commutator (itself, unless it's a cross-type
+-- operator).
+
+SELECT p1.oid, p1.oprname FROM pg_operator AS p1
+WHERE (p1.oprcanmerge OR p1.oprcanhash) AND NOT
+ (p1.oprkind = 'b' AND p1.oprresult = 'bool'::regtype AND p1.oprcom != 0);
+
+-- What's more, the commutator had better be mergejoinable/hashjoinable too.
+
+SELECT p1.oid, p1.oprname, p2.oid, p2.oprname
+FROM pg_operator AS p1, pg_operator AS p2
+WHERE p1.oprcom = p2.oid AND
+ (p1.oprcanmerge != p2.oprcanmerge OR
+ p1.oprcanhash != p2.oprcanhash);
+
+-- Mergejoinable operators should appear as equality members of btree index
+-- opfamilies.
+
+SELECT p1.oid, p1.oprname
+FROM pg_operator AS p1
+WHERE p1.oprcanmerge AND NOT EXISTS
+ (SELECT 1 FROM pg_amop
+ WHERE amopmethod = (SELECT oid FROM pg_am WHERE amname = 'btree') AND
+ amopopr = p1.oid AND amopstrategy = 3);
+
+-- And the converse.
+
+SELECT p1.oid, p1.oprname, p.amopfamily
+FROM pg_operator AS p1, pg_amop p
+WHERE amopopr = p1.oid
+ AND amopmethod = (SELECT oid FROM pg_am WHERE amname = 'btree')
+ AND amopstrategy = 3
+ AND NOT p1.oprcanmerge;
+
+-- Hashable operators should appear as members of hash index opfamilies.
+
+SELECT p1.oid, p1.oprname
+FROM pg_operator AS p1
+WHERE p1.oprcanhash AND NOT EXISTS
+ (SELECT 1 FROM pg_amop
+ WHERE amopmethod = (SELECT oid FROM pg_am WHERE amname = 'hash') AND
+ amopopr = p1.oid AND amopstrategy = 1);
+
+-- And the converse.
+
+SELECT p1.oid, p1.oprname, p.amopfamily
+FROM pg_operator AS p1, pg_amop p
+WHERE amopopr = p1.oid
+ AND amopmethod = (SELECT oid FROM pg_am WHERE amname = 'hash')
+ AND NOT p1.oprcanhash;
+
+-- Check that each operator defined in pg_operator matches its oprcode entry
+-- in pg_proc. Easiest to do this separately for each oprkind.
+
+SELECT p1.oid, p1.oprname, p2.oid, p2.proname
+FROM pg_operator AS p1, pg_proc AS p2
+WHERE p1.oprcode = p2.oid AND
+ p1.oprkind = 'b' AND
+ (p2.pronargs != 2
+ OR NOT binary_coercible(p2.prorettype, p1.oprresult)
+ OR NOT binary_coercible(p1.oprleft, p2.proargtypes[0])
+ OR NOT binary_coercible(p1.oprright, p2.proargtypes[1]));
+
+SELECT p1.oid, p1.oprname, p2.oid, p2.proname
+FROM pg_operator AS p1, pg_proc AS p2
+WHERE p1.oprcode = p2.oid AND
+ p1.oprkind = 'l' AND
+ (p2.pronargs != 1
+ OR NOT binary_coercible(p2.prorettype, p1.oprresult)
+ OR NOT binary_coercible(p1.oprright, p2.proargtypes[0])
+ OR p1.oprleft != 0);
+
+-- If the operator is mergejoinable or hashjoinable, its underlying function
+-- should not be volatile.
+
+SELECT p1.oid, p1.oprname, p2.oid, p2.proname
+FROM pg_operator AS p1, pg_proc AS p2
+WHERE p1.oprcode = p2.oid AND
+ (p1.oprcanmerge OR p1.oprcanhash) AND
+ p2.provolatile = 'v';
+
+-- If oprrest is set, the operator must return boolean,
+-- and it must link to a proc with the right signature
+-- to be a restriction selectivity estimator.
+-- The proc signature we want is: float8 proc(internal, oid, internal, int4)
+
+SELECT p1.oid, p1.oprname, p2.oid, p2.proname
+FROM pg_operator AS p1, pg_proc AS p2
+WHERE p1.oprrest = p2.oid AND
+ (p1.oprresult != 'bool'::regtype OR
+ p2.prorettype != 'float8'::regtype OR p2.proretset OR
+ p2.pronargs != 4 OR
+ p2.proargtypes[0] != 'internal'::regtype OR
+ p2.proargtypes[1] != 'oid'::regtype OR
+ p2.proargtypes[2] != 'internal'::regtype OR
+ p2.proargtypes[3] != 'int4'::regtype);
+
+-- If oprjoin is set, the operator must be a binary boolean op,
+-- and it must link to a proc with the right signature
+-- to be a join selectivity estimator.
+-- The proc signature we want is: float8 proc(internal, oid, internal, int2, internal)
+-- (Note: the old signature with only 4 args is still allowed, but no core
+-- estimator should be using it.)
+
+SELECT p1.oid, p1.oprname, p2.oid, p2.proname
+FROM pg_operator AS p1, pg_proc AS p2
+WHERE p1.oprjoin = p2.oid AND
+ (p1.oprkind != 'b' OR p1.oprresult != 'bool'::regtype OR
+ p2.prorettype != 'float8'::regtype OR p2.proretset OR
+ p2.pronargs != 5 OR
+ p2.proargtypes[0] != 'internal'::regtype OR
+ p2.proargtypes[1] != 'oid'::regtype OR
+ p2.proargtypes[2] != 'internal'::regtype OR
+ p2.proargtypes[3] != 'int2'::regtype OR
+ p2.proargtypes[4] != 'internal'::regtype);
+
+-- Insist that all built-in pg_operator entries have descriptions
+SELECT p1.oid, p1.oprname
+FROM pg_operator as p1 LEFT JOIN pg_description as d
+ ON p1.tableoid = d.classoid and p1.oid = d.objoid and d.objsubid = 0
+WHERE d.classoid IS NULL AND p1.oid <= 9999;
+
+-- Check that operators' underlying functions have suitable comments,
+-- namely 'implementation of XXX operator'. (Note: it's not necessary to
+-- put such comments into pg_proc.dat; initdb will generate them as needed.)
+-- In some cases involving legacy names for operators, there are multiple
+-- operators referencing the same pg_proc entry, so ignore operators whose
+-- comments say they are deprecated.
+-- We also have a few functions that are both operator support and meant to
+-- be called directly; those should have comments matching their operator.
+WITH funcdescs AS (
+ SELECT p.oid as p_oid, proname, o.oid as o_oid,
+ pd.description as prodesc,
+ 'implementation of ' || oprname || ' operator' as expecteddesc,
+ od.description as oprdesc
+ FROM pg_proc p JOIN pg_operator o ON oprcode = p.oid
+ LEFT JOIN pg_description pd ON
+ (pd.objoid = p.oid and pd.classoid = p.tableoid and pd.objsubid = 0)
+ LEFT JOIN pg_description od ON
+ (od.objoid = o.oid and od.classoid = o.tableoid and od.objsubid = 0)
+ WHERE o.oid <= 9999
+)
+SELECT * FROM funcdescs
+ WHERE prodesc IS DISTINCT FROM expecteddesc
+ AND oprdesc NOT LIKE 'deprecated%'
+ AND prodesc IS DISTINCT FROM oprdesc;
+
+-- Show all the operator-implementation functions that have their own
+-- comments. This should happen only in cases where the function and
+-- operator syntaxes are both documented at the user level.
+-- This should be a pretty short list; it's mostly legacy cases.
+WITH funcdescs AS (
+ SELECT p.oid as p_oid, proname, o.oid as o_oid,
+ pd.description as prodesc,
+ 'implementation of ' || oprname || ' operator' as expecteddesc,
+ od.description as oprdesc
+ FROM pg_proc p JOIN pg_operator o ON oprcode = p.oid
+ LEFT JOIN pg_description pd ON
+ (pd.objoid = p.oid and pd.classoid = p.tableoid and pd.objsubid = 0)
+ LEFT JOIN pg_description od ON
+ (od.objoid = o.oid and od.classoid = o.tableoid and od.objsubid = 0)
+ WHERE o.oid <= 9999
+)
+SELECT p_oid, proname, prodesc FROM funcdescs
+ WHERE prodesc IS DISTINCT FROM expecteddesc
+ AND oprdesc NOT LIKE 'deprecated%'
+ORDER BY 1;
+
+-- Operators that are commutator pairs should have identical volatility
+-- and leakproofness markings on their implementation functions.
+SELECT o1.oid, o1.oprcode, o2.oid, o2.oprcode
+FROM pg_operator AS o1, pg_operator AS o2, pg_proc AS p1, pg_proc AS p2
+WHERE o1.oprcom = o2.oid AND p1.oid = o1.oprcode AND p2.oid = o2.oprcode AND
+ (p1.provolatile != p2.provolatile OR
+ p1.proleakproof != p2.proleakproof);
+
+-- Likewise for negator pairs.
+SELECT o1.oid, o1.oprcode, o2.oid, o2.oprcode
+FROM pg_operator AS o1, pg_operator AS o2, pg_proc AS p1, pg_proc AS p2
+WHERE o1.oprnegate = o2.oid AND p1.oid = o1.oprcode AND p2.oid = o2.oprcode AND
+ (p1.provolatile != p2.provolatile OR
+ p1.proleakproof != p2.proleakproof);
+
+-- Btree comparison operators' functions should have the same volatility
+-- and leakproofness markings as the associated comparison support function.
+SELECT pp.oid::regprocedure as proc, pp.provolatile as vp, pp.proleakproof as lp,
+ po.oid::regprocedure as opr, po.provolatile as vo, po.proleakproof as lo
+FROM pg_proc pp, pg_proc po, pg_operator o, pg_amproc ap, pg_amop ao
+WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
+ ao.amopmethod = (SELECT oid FROM pg_am WHERE amname = 'btree') AND
+ ao.amopfamily = ap.amprocfamily AND
+ ao.amoplefttype = ap.amproclefttype AND
+ ao.amoprighttype = ap.amprocrighttype AND
+ ap.amprocnum = 1 AND
+ (pp.provolatile != po.provolatile OR
+ pp.proleakproof != po.proleakproof)
+ORDER BY 1;
+
+
+-- **************** pg_aggregate ****************
+
+-- Look for illegal values in pg_aggregate fields.
+
+SELECT ctid, aggfnoid::oid
+FROM pg_aggregate as p1
+WHERE aggfnoid = 0 OR aggtransfn = 0 OR
+ aggkind NOT IN ('n', 'o', 'h') OR
+ aggnumdirectargs < 0 OR
+ (aggkind = 'n' AND aggnumdirectargs > 0) OR
+ aggfinalmodify NOT IN ('r', 's', 'w') OR
+ aggmfinalmodify NOT IN ('r', 's', 'w') OR
+ aggtranstype = 0 OR aggtransspace < 0 OR aggmtransspace < 0;
+
+-- Make sure the matching pg_proc entry is sensible, too.
+
+SELECT a.aggfnoid::oid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggfnoid = p.oid AND
+ (p.prokind != 'a' OR p.proretset OR p.pronargs < a.aggnumdirectargs);
+
+-- Make sure there are no prokind = PROKIND_AGGREGATE pg_proc entries without matches.
+
+SELECT oid, proname
+FROM pg_proc as p
+WHERE p.prokind = 'a' AND
+ NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid);
+
+-- If there is no finalfn then the output type must be the transtype.
+
+SELECT a.aggfnoid::oid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggfnoid = p.oid AND
+ a.aggfinalfn = 0 AND p.prorettype != a.aggtranstype;
+
+-- Cross-check transfn against its entry in pg_proc.
+SELECT a.aggfnoid::oid, p.proname, ptr.oid, ptr.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr
+WHERE a.aggfnoid = p.oid AND
+ a.aggtransfn = ptr.oid AND
+ (ptr.proretset
+ OR NOT (ptr.pronargs =
+ CASE WHEN a.aggkind = 'n' THEN p.pronargs + 1
+ ELSE greatest(p.pronargs - a.aggnumdirectargs, 1) + 1 END)
+ OR NOT binary_coercible(ptr.prorettype, a.aggtranstype)
+ OR NOT binary_coercible(a.aggtranstype, ptr.proargtypes[0])
+ OR (p.pronargs > 0 AND
+ NOT binary_coercible(p.proargtypes[0], ptr.proargtypes[1]))
+ OR (p.pronargs > 1 AND
+ NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2]))
+ OR (p.pronargs > 2 AND
+ NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3]))
+ -- we could carry the check further, but 3 args is enough for now
+ OR (p.pronargs > 3)
+ );
+
+-- Cross-check finalfn (if present) against its entry in pg_proc.
+
+SELECT a.aggfnoid::oid, p.proname, pfn.oid, pfn.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS pfn
+WHERE a.aggfnoid = p.oid AND
+ a.aggfinalfn = pfn.oid AND
+ (pfn.proretset OR
+ NOT binary_coercible(pfn.prorettype, p.prorettype) OR
+ NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0]) OR
+ CASE WHEN a.aggfinalextra THEN pfn.pronargs != p.pronargs + 1
+ ELSE pfn.pronargs != a.aggnumdirectargs + 1 END
+ OR (pfn.pronargs > 1 AND
+ NOT binary_coercible(p.proargtypes[0], pfn.proargtypes[1]))
+ OR (pfn.pronargs > 2 AND
+ NOT binary_coercible(p.proargtypes[1], pfn.proargtypes[2]))
+ OR (pfn.pronargs > 3 AND
+ NOT binary_coercible(p.proargtypes[2], pfn.proargtypes[3]))
+ -- we could carry the check further, but 4 args is enough for now
+ OR (pfn.pronargs > 4)
+ );
+
+-- If transfn is strict then either initval should be non-NULL, or
+-- input type should match transtype so that the first non-null input
+-- can be assigned as the state value.
+
+SELECT a.aggfnoid::oid, p.proname, ptr.oid, ptr.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr
+WHERE a.aggfnoid = p.oid AND
+ a.aggtransfn = ptr.oid AND ptr.proisstrict AND
+ a.agginitval IS NULL AND
+ NOT binary_coercible(p.proargtypes[0], a.aggtranstype);
+
+-- Check for inconsistent specifications of moving-aggregate columns.
+
+SELECT ctid, aggfnoid::oid
+FROM pg_aggregate as p1
+WHERE aggmtranstype != 0 AND
+ (aggmtransfn = 0 OR aggminvtransfn = 0);
+
+SELECT ctid, aggfnoid::oid
+FROM pg_aggregate as p1
+WHERE aggmtranstype = 0 AND
+ (aggmtransfn != 0 OR aggminvtransfn != 0 OR aggmfinalfn != 0 OR
+ aggmtransspace != 0 OR aggminitval IS NOT NULL);
+
+-- If there is no mfinalfn then the output type must be the mtranstype.
+
+SELECT a.aggfnoid::oid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggfnoid = p.oid AND
+ a.aggmtransfn != 0 AND
+ a.aggmfinalfn = 0 AND p.prorettype != a.aggmtranstype;
+
+-- Cross-check mtransfn (if present) against its entry in pg_proc.
+SELECT a.aggfnoid::oid, p.proname, ptr.oid, ptr.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr
+WHERE a.aggfnoid = p.oid AND
+ a.aggmtransfn = ptr.oid AND
+ (ptr.proretset
+ OR NOT (ptr.pronargs =
+ CASE WHEN a.aggkind = 'n' THEN p.pronargs + 1
+ ELSE greatest(p.pronargs - a.aggnumdirectargs, 1) + 1 END)
+ OR NOT binary_coercible(ptr.prorettype, a.aggmtranstype)
+ OR NOT binary_coercible(a.aggmtranstype, ptr.proargtypes[0])
+ OR (p.pronargs > 0 AND
+ NOT binary_coercible(p.proargtypes[0], ptr.proargtypes[1]))
+ OR (p.pronargs > 1 AND
+ NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2]))
+ OR (p.pronargs > 2 AND
+ NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3]))
+ -- we could carry the check further, but 3 args is enough for now
+ OR (p.pronargs > 3)
+ );
+
+-- Cross-check minvtransfn (if present) against its entry in pg_proc.
+SELECT a.aggfnoid::oid, p.proname, ptr.oid, ptr.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr
+WHERE a.aggfnoid = p.oid AND
+ a.aggminvtransfn = ptr.oid AND
+ (ptr.proretset
+ OR NOT (ptr.pronargs =
+ CASE WHEN a.aggkind = 'n' THEN p.pronargs + 1
+ ELSE greatest(p.pronargs - a.aggnumdirectargs, 1) + 1 END)
+ OR NOT binary_coercible(ptr.prorettype, a.aggmtranstype)
+ OR NOT binary_coercible(a.aggmtranstype, ptr.proargtypes[0])
+ OR (p.pronargs > 0 AND
+ NOT binary_coercible(p.proargtypes[0], ptr.proargtypes[1]))
+ OR (p.pronargs > 1 AND
+ NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2]))
+ OR (p.pronargs > 2 AND
+ NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3]))
+ -- we could carry the check further, but 3 args is enough for now
+ OR (p.pronargs > 3)
+ );
+
+-- Cross-check mfinalfn (if present) against its entry in pg_proc.
+
+SELECT a.aggfnoid::oid, p.proname, pfn.oid, pfn.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS pfn
+WHERE a.aggfnoid = p.oid AND
+ a.aggmfinalfn = pfn.oid AND
+ (pfn.proretset OR
+ NOT binary_coercible(pfn.prorettype, p.prorettype) OR
+ NOT binary_coercible(a.aggmtranstype, pfn.proargtypes[0]) OR
+ CASE WHEN a.aggmfinalextra THEN pfn.pronargs != p.pronargs + 1
+ ELSE pfn.pronargs != a.aggnumdirectargs + 1 END
+ OR (pfn.pronargs > 1 AND
+ NOT binary_coercible(p.proargtypes[0], pfn.proargtypes[1]))
+ OR (pfn.pronargs > 2 AND
+ NOT binary_coercible(p.proargtypes[1], pfn.proargtypes[2]))
+ OR (pfn.pronargs > 3 AND
+ NOT binary_coercible(p.proargtypes[2], pfn.proargtypes[3]))
+ -- we could carry the check further, but 4 args is enough for now
+ OR (pfn.pronargs > 4)
+ );
+
+-- If mtransfn is strict then either minitval should be non-NULL, or
+-- input type should match mtranstype so that the first non-null input
+-- can be assigned as the state value.
+
+SELECT a.aggfnoid::oid, p.proname, ptr.oid, ptr.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr
+WHERE a.aggfnoid = p.oid AND
+ a.aggmtransfn = ptr.oid AND ptr.proisstrict AND
+ a.aggminitval IS NULL AND
+ NOT binary_coercible(p.proargtypes[0], a.aggmtranstype);
+
+-- mtransfn and minvtransfn should have same strictness setting.
+
+SELECT a.aggfnoid::oid, p.proname, ptr.oid, ptr.proname, iptr.oid, iptr.proname
+FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr, pg_proc AS iptr
+WHERE a.aggfnoid = p.oid AND
+ a.aggmtransfn = ptr.oid AND
+ a.aggminvtransfn = iptr.oid AND
+ ptr.proisstrict != iptr.proisstrict;
+
+-- Check that all combine functions have signature
+-- combine(transtype, transtype) returns transtype
+
+SELECT a.aggfnoid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggcombinefn = p.oid AND
+ (p.pronargs != 2 OR
+ p.prorettype != p.proargtypes[0] OR
+ p.prorettype != p.proargtypes[1] OR
+ NOT binary_coercible(a.aggtranstype, p.proargtypes[0]));
+
+-- Check that no combine function for an INTERNAL transtype is strict.
+
+SELECT a.aggfnoid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggcombinefn = p.oid AND
+ a.aggtranstype = 'internal'::regtype AND p.proisstrict;
+
+-- serialize/deserialize functions should be specified only for aggregates
+-- with transtype internal and a combine function, and we should have both
+-- or neither of them.
+
+SELECT aggfnoid, aggtranstype, aggserialfn, aggdeserialfn
+FROM pg_aggregate
+WHERE (aggserialfn != 0 OR aggdeserialfn != 0)
+ AND (aggtranstype != 'internal'::regtype OR aggcombinefn = 0 OR
+ aggserialfn = 0 OR aggdeserialfn = 0);
+
+-- Check that all serialization functions have signature
+-- serialize(internal) returns bytea
+-- Also insist that they be strict; it's wasteful to run them on NULLs.
+
+SELECT a.aggfnoid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggserialfn = p.oid AND
+ (p.prorettype != 'bytea'::regtype OR p.pronargs != 1 OR
+ p.proargtypes[0] != 'internal'::regtype OR
+ NOT p.proisstrict);
+
+-- Check that all deserialization functions have signature
+-- deserialize(bytea, internal) returns internal
+-- Also insist that they be strict; it's wasteful to run them on NULLs.
+
+SELECT a.aggfnoid, p.proname
+FROM pg_aggregate as a, pg_proc as p
+WHERE a.aggdeserialfn = p.oid AND
+ (p.prorettype != 'internal'::regtype OR p.pronargs != 2 OR
+ p.proargtypes[0] != 'bytea'::regtype OR
+ p.proargtypes[1] != 'internal'::regtype OR
+ NOT p.proisstrict);
+
+-- Check that aggregates which have the same transition function also have
+-- the same combine, serialization, and deserialization functions.
+-- While that isn't strictly necessary, it's fishy if they don't.
+
+SELECT a.aggfnoid, a.aggcombinefn, a.aggserialfn, a.aggdeserialfn,
+ b.aggfnoid, b.aggcombinefn, b.aggserialfn, b.aggdeserialfn
+FROM
+ pg_aggregate a, pg_aggregate b
+WHERE
+ a.aggfnoid < b.aggfnoid AND a.aggtransfn = b.aggtransfn AND
+ (a.aggcombinefn != b.aggcombinefn OR a.aggserialfn != b.aggserialfn
+ OR a.aggdeserialfn != b.aggdeserialfn);
+
+-- Cross-check aggsortop (if present) against pg_operator.
+-- We expect to find entries for bool_and, bool_or, every, max, and min.
+
+SELECT DISTINCT proname, oprname
+FROM pg_operator AS o, pg_aggregate AS a, pg_proc AS p
+WHERE a.aggfnoid = p.oid AND a.aggsortop = o.oid
+ORDER BY 1, 2;
+
+-- Check datatypes match
+
+SELECT a.aggfnoid::oid, o.oid
+FROM pg_operator AS o, pg_aggregate AS a, pg_proc AS p
+WHERE a.aggfnoid = p.oid AND a.aggsortop = o.oid AND
+ (oprkind != 'b' OR oprresult != 'boolean'::regtype
+ OR oprleft != p.proargtypes[0] OR oprright != p.proargtypes[0]);
+
+-- Check operator is a suitable btree opfamily member
+
+SELECT a.aggfnoid::oid, o.oid
+FROM pg_operator AS o, pg_aggregate AS a, pg_proc AS p
+WHERE a.aggfnoid = p.oid AND a.aggsortop = o.oid AND
+ NOT EXISTS(SELECT 1 FROM pg_amop
+ WHERE amopmethod = (SELECT oid FROM pg_am WHERE amname = 'btree')
+ AND amopopr = o.oid
+ AND amoplefttype = o.oprleft
+ AND amoprighttype = o.oprright);
+
+-- Check correspondence of btree strategies and names
+
+SELECT DISTINCT proname, oprname, amopstrategy
+FROM pg_operator AS o, pg_aggregate AS a, pg_proc AS p,
+ pg_amop as ao
+WHERE a.aggfnoid = p.oid AND a.aggsortop = o.oid AND
+ amopopr = o.oid AND
+ amopmethod = (SELECT oid FROM pg_am WHERE amname = 'btree')
+ORDER BY 1, 2;
+
+-- Check that there are not aggregates with the same name and different
+-- numbers of arguments. While not technically wrong, we have a project policy
+-- to avoid this because it opens the door for confusion in connection with
+-- ORDER BY: novices frequently put the ORDER BY in the wrong place.
+-- See the fate of the single-argument form of string_agg() for history.
+-- (Note: we don't forbid users from creating such aggregates; the policy is
+-- just to think twice before creating built-in aggregates like this.)
+-- The only aggregates that should show up here are count(x) and count(*).
+
+SELECT p1.oid::regprocedure, p2.oid::regprocedure
+FROM pg_proc AS p1, pg_proc AS p2
+WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND
+ p1.prokind = 'a' AND p2.prokind = 'a' AND
+ array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
+ORDER BY 1;
+
+-- For the same reason, built-in aggregates with default arguments are no good.
+
+SELECT oid, proname
+FROM pg_proc AS p
+WHERE prokind = 'a' AND proargdefaults IS NOT NULL;
+
+-- For the same reason, we avoid creating built-in variadic aggregates, except
+-- that variadic ordered-set aggregates are OK (since they have special syntax
+-- that is not subject to the misplaced ORDER BY issue).
+
+SELECT p.oid, proname
+FROM pg_proc AS p JOIN pg_aggregate AS a ON a.aggfnoid = p.oid
+WHERE prokind = 'a' AND provariadic != 0 AND a.aggkind = 'n';
+
+
+-- **************** pg_opfamily ****************
+
+-- Look for illegal values in pg_opfamily fields
+
+SELECT p1.oid
+FROM pg_opfamily as p1
+WHERE p1.opfmethod = 0 OR p1.opfnamespace = 0;
+
+-- Look for opfamilies having no opclasses. While most validation of
+-- opfamilies is now handled by AM-specific amvalidate functions, that's
+-- driven from pg_opclass entries below, so an empty opfamily would not
+-- get noticed.
+
+SELECT oid, opfname FROM pg_opfamily f
+WHERE NOT EXISTS (SELECT 1 FROM pg_opclass WHERE opcfamily = f.oid);
+
+
+-- **************** pg_opclass ****************
+
+-- Look for illegal values in pg_opclass fields
+
+SELECT p1.oid
+FROM pg_opclass AS p1
+WHERE p1.opcmethod = 0 OR p1.opcnamespace = 0 OR p1.opcfamily = 0
+ OR p1.opcintype = 0;
+
+-- opcmethod must match owning opfamily's opfmethod
+
+SELECT p1.oid, p2.oid
+FROM pg_opclass AS p1, pg_opfamily AS p2
+WHERE p1.opcfamily = p2.oid AND p1.opcmethod != p2.opfmethod;
+
+-- There should not be multiple entries in pg_opclass with opcdefault true
+-- and the same opcmethod/opcintype combination.
+
+SELECT p1.oid, p2.oid
+FROM pg_opclass AS p1, pg_opclass AS p2
+WHERE p1.oid != p2.oid AND
+ p1.opcmethod = p2.opcmethod AND p1.opcintype = p2.opcintype AND
+ p1.opcdefault AND p2.opcdefault;
+
+-- Ask access methods to validate opclasses
+-- (this replaces a lot of SQL-level checks that used to be done in this file)
+
+SELECT oid, opcname FROM pg_opclass WHERE NOT amvalidate(oid);
+
+
+-- **************** pg_am ****************
+
+-- Look for illegal values in pg_am fields
+
+SELECT p1.oid, p1.amname
+FROM pg_am AS p1
+WHERE p1.amhandler = 0;
+
+-- Check for index amhandler functions with the wrong signature
+
+SELECT p1.oid, p1.amname, p2.oid, p2.proname
+FROM pg_am AS p1, pg_proc AS p2
+WHERE p2.oid = p1.amhandler AND p1.amtype = 'i' AND
+ (p2.prorettype != 'index_am_handler'::regtype
+ OR p2.proretset
+ OR p2.pronargs != 1
+ OR p2.proargtypes[0] != 'internal'::regtype);
+
+-- Check for table amhandler functions with the wrong signature
+
+SELECT p1.oid, p1.amname, p2.oid, p2.proname
+FROM pg_am AS p1, pg_proc AS p2
+WHERE p2.oid = p1.amhandler AND p1.amtype = 's' AND
+ (p2.prorettype != 'table_am_handler'::regtype
+ OR p2.proretset
+ OR p2.pronargs != 1
+ OR p2.proargtypes[0] != 'internal'::regtype);
+
+-- **************** pg_amop ****************
+
+-- Look for illegal values in pg_amop fields
+
+SELECT p1.amopfamily, p1.amopstrategy
+FROM pg_amop as p1
+WHERE p1.amopfamily = 0 OR p1.amoplefttype = 0 OR p1.amoprighttype = 0
+ OR p1.amopopr = 0 OR p1.amopmethod = 0 OR p1.amopstrategy < 1;
+
+SELECT p1.amopfamily, p1.amopstrategy
+FROM pg_amop as p1
+WHERE NOT ((p1.amoppurpose = 's' AND p1.amopsortfamily = 0) OR
+ (p1.amoppurpose = 'o' AND p1.amopsortfamily <> 0));
+
+-- amopmethod must match owning opfamily's opfmethod
+
+SELECT p1.oid, p2.oid
+FROM pg_amop AS p1, pg_opfamily AS p2
+WHERE p1.amopfamily = p2.oid AND p1.amopmethod != p2.opfmethod;
+
+-- Make a list of all the distinct operator names being used in particular
+-- strategy slots. This is a bit hokey, since the list might need to change
+-- in future releases, but it's an effective way of spotting mistakes such as
+-- swapping two operators within a family.
+
+SELECT DISTINCT amopmethod, amopstrategy, oprname
+FROM pg_amop p1 LEFT JOIN pg_operator p2 ON amopopr = p2.oid
+ORDER BY 1, 2, 3;
+
+-- Check that all opclass search operators have selectivity estimators.
+-- This is not absolutely required, but it seems a reasonable thing
+-- to insist on for all standard datatypes.
+
+SELECT p1.amopfamily, p1.amopopr, p2.oid, p2.oprname
+FROM pg_amop AS p1, pg_operator AS p2
+WHERE p1.amopopr = p2.oid AND p1.amoppurpose = 's' AND
+ (p2.oprrest = 0 OR p2.oprjoin = 0);
+
+-- Check that each opclass in an opfamily has associated operators, that is
+-- ones whose oprleft matches opcintype (possibly by coercion).
+
+SELECT p1.opcname, p1.opcfamily
+FROM pg_opclass AS p1
+WHERE NOT EXISTS(SELECT 1 FROM pg_amop AS p2
+ WHERE p2.amopfamily = p1.opcfamily
+ AND binary_coercible(p1.opcintype, p2.amoplefttype));
+
+-- Check that each operator listed in pg_amop has an associated opclass,
+-- that is one whose opcintype matches oprleft (possibly by coercion).
+-- Otherwise the operator is useless because it cannot be matched to an index.
+-- (In principle it could be useful to list such operators in multiple-datatype
+-- btree opfamilies, but in practice you'd expect there to be an opclass for
+-- every datatype the family knows about.)
+
+SELECT p1.amopfamily, p1.amopstrategy, p1.amopopr
+FROM pg_amop AS p1
+WHERE NOT EXISTS(SELECT 1 FROM pg_opclass AS p2
+ WHERE p2.opcfamily = p1.amopfamily
+ AND binary_coercible(p2.opcintype, p1.amoplefttype));
+
+-- Operators that are primary members of opclasses must be immutable (else
+-- it suggests that the index ordering isn't fixed). Operators that are
+-- cross-type members need only be stable, since they are just shorthands
+-- for index probe queries.
+
+SELECT p1.amopfamily, p1.amopopr, p2.oprname, p3.prosrc
+FROM pg_amop AS p1, pg_operator AS p2, pg_proc AS p3
+WHERE p1.amopopr = p2.oid AND p2.oprcode = p3.oid AND
+ p1.amoplefttype = p1.amoprighttype AND
+ p3.provolatile != 'i';
+
+SELECT p1.amopfamily, p1.amopopr, p2.oprname, p3.prosrc
+FROM pg_amop AS p1, pg_operator AS p2, pg_proc AS p3
+WHERE p1.amopopr = p2.oid AND p2.oprcode = p3.oid AND
+ p1.amoplefttype != p1.amoprighttype AND
+ p3.provolatile = 'v';
+
+
+-- **************** pg_amproc ****************
+
+-- Look for illegal values in pg_amproc fields
+
+SELECT p1.amprocfamily, p1.amprocnum
+FROM pg_amproc as p1
+WHERE p1.amprocfamily = 0 OR p1.amproclefttype = 0 OR p1.amprocrighttype = 0
+ OR p1.amprocnum < 0 OR p1.amproc = 0;
+
+-- Support routines that are primary members of opfamilies must be immutable
+-- (else it suggests that the index ordering isn't fixed). But cross-type
+-- members need only be stable, since they are just shorthands
+-- for index probe queries.
+
+SELECT p1.amprocfamily, p1.amproc, p2.prosrc
+FROM pg_amproc AS p1, pg_proc AS p2
+WHERE p1.amproc = p2.oid AND
+ p1.amproclefttype = p1.amprocrighttype AND
+ p2.provolatile != 'i';
+
+SELECT p1.amprocfamily, p1.amproc, p2.prosrc
+FROM pg_amproc AS p1, pg_proc AS p2
+WHERE p1.amproc = p2.oid AND
+ p1.amproclefttype != p1.amprocrighttype AND
+ p2.provolatile = 'v';
+
+-- Almost all of the core distribution's Btree opclasses can use one of the
+-- two generic "equalimage" functions as their support function 4. Look for
+-- opclasses that don't allow deduplication unconditionally here.
+--
+-- Newly added Btree opclasses don't have to support deduplication. It will
+-- usually be trivial to add support, though. Note that the expected output
+-- of this part of the test will need to be updated when a new opclass cannot
+-- support deduplication (by using btequalimage).
+SELECT amp.amproc::regproc AS proc, opf.opfname AS opfamily_name,
+ opc.opcname AS opclass_name, opc.opcintype::regtype AS opcintype
+FROM pg_am AS am
+JOIN pg_opclass AS opc ON opc.opcmethod = am.oid
+JOIN pg_opfamily AS opf ON opc.opcfamily = opf.oid
+LEFT JOIN pg_amproc AS amp ON amp.amprocfamily = opf.oid AND
+ amp.amproclefttype = opc.opcintype AND amp.amprocnum = 4
+WHERE am.amname = 'btree' AND
+ amp.amproc IS DISTINCT FROM 'btequalimage'::regproc
+ORDER BY 1, 2, 3;
+
+-- **************** pg_index ****************
+
+-- Look for illegal values in pg_index fields.
+
+SELECT p1.indexrelid, p1.indrelid
+FROM pg_index as p1
+WHERE p1.indexrelid = 0 OR p1.indrelid = 0 OR
+ p1.indnatts <= 0 OR p1.indnatts > 32;
+
+-- oidvector and int2vector fields should be of length indnatts.
+
+SELECT p1.indexrelid, p1.indrelid
+FROM pg_index as p1
+WHERE array_lower(indkey, 1) != 0 OR array_upper(indkey, 1) != indnatts-1 OR
+ array_lower(indclass, 1) != 0 OR array_upper(indclass, 1) != indnatts-1 OR
+ array_lower(indcollation, 1) != 0 OR array_upper(indcollation, 1) != indnatts-1 OR
+ array_lower(indoption, 1) != 0 OR array_upper(indoption, 1) != indnatts-1;
+
+-- Check that opclasses and collations match the underlying columns.
+-- (As written, this test ignores expression indexes.)
+
+SELECT indexrelid::regclass, indrelid::regclass, attname, atttypid::regtype, opcname
+FROM (SELECT indexrelid, indrelid, unnest(indkey) as ikey,
+ unnest(indclass) as iclass, unnest(indcollation) as icoll
+ FROM pg_index) ss,
+ pg_attribute a,
+ pg_opclass opc
+WHERE a.attrelid = indrelid AND a.attnum = ikey AND opc.oid = iclass AND
+ (NOT binary_coercible(atttypid, opcintype) OR icoll != attcollation);
+
+-- For system catalogs, be even tighter: nearly all indexes should be
+-- exact type matches not binary-coercible matches. At this writing
+-- the only exception is an OID index on a regproc column.
+
+SELECT indexrelid::regclass, indrelid::regclass, attname, atttypid::regtype, opcname
+FROM (SELECT indexrelid, indrelid, unnest(indkey) as ikey,
+ unnest(indclass) as iclass, unnest(indcollation) as icoll
+ FROM pg_index
+ WHERE indrelid < 16384) ss,
+ pg_attribute a,
+ pg_opclass opc
+WHERE a.attrelid = indrelid AND a.attnum = ikey AND opc.oid = iclass AND
+ (opcintype != atttypid OR icoll != attcollation)
+ORDER BY 1;
+
+-- Check for system catalogs with collation-sensitive ordering. This is not
+-- a representational error in pg_index, but simply wrong catalog design.
+-- It's bad because we expect to be able to clone template0 and assign the
+-- copy a different database collation. It would especially not work for
+-- shared catalogs.
+
+SELECT relname, attname, attcollation
+FROM pg_class c, pg_attribute a
+WHERE c.oid = attrelid AND c.oid < 16384 AND
+ c.relkind != 'v' AND -- we don't care about columns in views
+ attcollation != 0 AND
+ attcollation != (SELECT oid FROM pg_collation WHERE collname = 'C');
+
+-- Double-check that collation-sensitive indexes have "C" collation, too.
+
+SELECT indexrelid::regclass, indrelid::regclass, iclass, icoll
+FROM (SELECT indexrelid, indrelid,
+ unnest(indclass) as iclass, unnest(indcollation) as icoll
+ FROM pg_index
+ WHERE indrelid < 16384) ss
+WHERE icoll != 0 AND
+ icoll != (SELECT oid FROM pg_collation WHERE collname = 'C');
diff --git a/src/test/regress/sql/partition_aggregate.sql b/src/test/regress/sql/partition_aggregate.sql
new file mode 100644
index 0000000..c17294b
--- /dev/null
+++ b/src/test/regress/sql/partition_aggregate.sql
@@ -0,0 +1,333 @@
+--
+-- 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.
+--
+
+-- 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;
+
+-- 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;
+
+-- 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;
+
+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;
+
+-- 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/partition_info.sql b/src/test/regress/sql/partition_info.sql
new file mode 100644
index 0000000..b5060be
--- /dev/null
+++ b/src/test/regress/sql/partition_info.sql
@@ -0,0 +1,129 @@
+--
+-- Tests for functions providing information about partitions
+--
+SELECT * FROM pg_partition_tree(NULL);
+SELECT * FROM pg_partition_tree(0);
+SELECT * FROM pg_partition_ancestors(NULL);
+SELECT * FROM pg_partition_ancestors(0);
+SELECT pg_partition_root(NULL);
+SELECT pg_partition_root(0);
+
+-- Test table partition trees
+CREATE TABLE ptif_test (a int, b int) PARTITION BY range (a);
+CREATE TABLE ptif_test0 PARTITION OF ptif_test
+ FOR VALUES FROM (minvalue) TO (0) PARTITION BY list (b);
+CREATE TABLE ptif_test01 PARTITION OF ptif_test0 FOR VALUES IN (1);
+CREATE TABLE ptif_test1 PARTITION OF ptif_test
+ FOR VALUES FROM (0) TO (100) PARTITION BY list (b);
+CREATE TABLE ptif_test11 PARTITION OF ptif_test1 FOR VALUES IN (1);
+CREATE TABLE ptif_test2 PARTITION OF ptif_test
+ FOR VALUES FROM (100) TO (200);
+-- This partitioned table should remain with no partitions.
+CREATE TABLE ptif_test3 PARTITION OF ptif_test
+ FOR VALUES FROM (200) TO (maxvalue) PARTITION BY list (b);
+
+-- Test pg_partition_root for tables
+SELECT pg_partition_root('ptif_test');
+SELECT pg_partition_root('ptif_test0');
+SELECT pg_partition_root('ptif_test01');
+SELECT pg_partition_root('ptif_test3');
+
+-- Test index partition tree
+CREATE INDEX ptif_test_index ON ONLY ptif_test (a);
+CREATE INDEX ptif_test0_index ON ONLY ptif_test0 (a);
+ALTER INDEX ptif_test_index ATTACH PARTITION ptif_test0_index;
+CREATE INDEX ptif_test01_index ON ptif_test01 (a);
+ALTER INDEX ptif_test0_index ATTACH PARTITION ptif_test01_index;
+CREATE INDEX ptif_test1_index ON ONLY ptif_test1 (a);
+ALTER INDEX ptif_test_index ATTACH PARTITION ptif_test1_index;
+CREATE INDEX ptif_test11_index ON ptif_test11 (a);
+ALTER INDEX ptif_test1_index ATTACH PARTITION ptif_test11_index;
+CREATE INDEX ptif_test2_index ON ptif_test2 (a);
+ALTER INDEX ptif_test_index ATTACH PARTITION ptif_test2_index;
+CREATE INDEX ptif_test3_index ON ptif_test3 (a);
+ALTER INDEX ptif_test_index ATTACH PARTITION ptif_test3_index;
+
+-- Test pg_partition_root for indexes
+SELECT pg_partition_root('ptif_test_index');
+SELECT pg_partition_root('ptif_test0_index');
+SELECT pg_partition_root('ptif_test01_index');
+SELECT pg_partition_root('ptif_test3_index');
+
+-- List all tables members of the tree
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test');
+-- List tables from an intermediate level
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test0') p
+ JOIN pg_class c ON (p.relid = c.oid);
+-- List from leaf table
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test01') p
+ JOIN pg_class c ON (p.relid = c.oid);
+-- List from partitioned table with no partitions
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test3') p
+ JOIN pg_class c ON (p.relid = c.oid);
+-- List all ancestors of root and leaf tables
+SELECT * FROM pg_partition_ancestors('ptif_test01');
+SELECT * FROM pg_partition_ancestors('ptif_test');
+-- List all members using pg_partition_root with leaf table reference
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree(pg_partition_root('ptif_test01')) p
+ JOIN pg_class c ON (p.relid = c.oid);
+
+-- List all indexes members of the tree
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test_index');
+-- List indexes from an intermediate level
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test0_index') p
+ JOIN pg_class c ON (p.relid = c.oid);
+-- List from leaf index
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test01_index') p
+ JOIN pg_class c ON (p.relid = c.oid);
+-- List from partitioned index with no partitions
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_test3_index') p
+ JOIN pg_class c ON (p.relid = c.oid);
+-- List all members using pg_partition_root with leaf index reference
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree(pg_partition_root('ptif_test01_index')) p
+ JOIN pg_class c ON (p.relid = c.oid);
+-- List all ancestors of root and leaf indexes
+SELECT * FROM pg_partition_ancestors('ptif_test01_index');
+SELECT * FROM pg_partition_ancestors('ptif_test_index');
+
+DROP TABLE ptif_test;
+
+-- Table that is not part of any partition tree is not listed.
+CREATE TABLE ptif_normal_table(a int);
+SELECT relid, parentrelid, level, isleaf
+ FROM pg_partition_tree('ptif_normal_table');
+SELECT * FROM pg_partition_ancestors('ptif_normal_table');
+SELECT pg_partition_root('ptif_normal_table');
+DROP TABLE ptif_normal_table;
+
+-- Various partitioning-related functions return empty/NULL if passed relations
+-- of types that cannot be part of a partition tree; for example, views,
+-- materialized views, legacy inheritance children or parents, etc.
+CREATE VIEW ptif_test_view AS SELECT 1;
+CREATE MATERIALIZED VIEW ptif_test_matview AS SELECT 1;
+CREATE TABLE ptif_li_parent ();
+CREATE TABLE ptif_li_child () INHERITS (ptif_li_parent);
+SELECT * FROM pg_partition_tree('ptif_test_view');
+SELECT * FROM pg_partition_tree('ptif_test_matview');
+SELECT * FROM pg_partition_tree('ptif_li_parent');
+SELECT * FROM pg_partition_tree('ptif_li_child');
+SELECT * FROM pg_partition_ancestors('ptif_test_view');
+SELECT * FROM pg_partition_ancestors('ptif_test_matview');
+SELECT * FROM pg_partition_ancestors('ptif_li_parent');
+SELECT * FROM pg_partition_ancestors('ptif_li_child');
+SELECT pg_partition_root('ptif_test_view');
+SELECT pg_partition_root('ptif_test_matview');
+SELECT pg_partition_root('ptif_li_parent');
+SELECT pg_partition_root('ptif_li_child');
+DROP VIEW ptif_test_view;
+DROP MATERIALIZED VIEW ptif_test_matview;
+DROP TABLE ptif_li_parent, ptif_li_child;
diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql
new file mode 100644
index 0000000..d97b5b6
--- /dev/null
+++ b/src/test/regress/sql/partition_join.sql
@@ -0,0 +1,1144 @@
+--
+-- PARTITION_JOIN
+-- Test partitionwise join between partitioned tables
+--
+
+-- Enable partitionwise join, which by default is disabled.
+SET enable_partitionwise_join to true;
+
+--
+-- 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
+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;
+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
+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;
+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
+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;
+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
+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;
+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
+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;
+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
+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;
+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
+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);
+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;
+
+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;
+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;
+
+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;
+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;
+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;
+
+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;
+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
+--
+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;
+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;
+
+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;
+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;
+
+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;
+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
+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;
+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;
+
+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;
+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;
+
+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;
+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;
+
+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;
+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
+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;
+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
+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;
+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
+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;
+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;
+
+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;
+
+--
+-- 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
+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;
+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;
+
+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;
+
+-- 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;
+
+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;
+--
+-- 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
+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;
+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
+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;
+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
+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;
+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
+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;
+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.
+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);
+
+--
+-- 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;
+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;
+
+-- 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
+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);
+
+-- 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;
+
+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
+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;
+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
+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;
+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
+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;
+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
+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;
+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
+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;
+
+-- 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
+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;
+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
+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;
+
+-- 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
+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;
+
+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
+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;
+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
+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;
+
+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
+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;
+
+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
+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;
+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;
+
+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;
+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;
+
+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;
+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
+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;
+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
+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;
+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
+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;
+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
+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;
+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
+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;
+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
+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;
+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
+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;
+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
+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;
+
+-- 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;
+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
+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;
+
+-- 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
+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;
+
+-- 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
+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_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
+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;
+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
+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;
+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
+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;
+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
+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;
+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
+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;
+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
+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;
+
+-- 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
+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;
+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
+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;
+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
+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;
+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
+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;
+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
+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;
+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;
+
+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;
+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;
+
+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;
+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;
+
+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;
+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;
+
+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;
+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;
+
+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;
+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;
+
+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;
+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;
+
+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;
+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;
+
+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;
+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;
+
+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;
+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;
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
new file mode 100644
index 0000000..d70bd86
--- /dev/null
+++ b/src/test/regress/sql/partition_prune.sql
@@ -0,0 +1,1189 @@
+--
+-- Test partitioning planner code
+--
+
+-- Force generic plans to be used for all prepared statements in this file.
+set plan_cache_mode = force_generic_plan;
+
+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 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);
+
+-- move beyond the final row
+move 3 from cur;
+
+-- Ensure we get two rows.
+fetch backward all from cur;
+
+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
+ 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;
+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;
+
+-- 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;
+
+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;
+
+-- 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;
+
+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;
+
+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);
+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;
+
+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);
+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;
+
+-- No matching partition
+delete from tbl1;
+insert into tbl1 values (10000);
+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);
+
+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;
+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);
+
+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);
+
+-- 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/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..98f4991
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,109 @@
+--
+-- Tests for password types
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- error
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'scram-sha-256'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'scram-sha-256';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+CREATE ROLE regress_passwd4 PASSWORD NULL;
+
+-- check list of created entries
+--
+-- The scram secret will look something like:
+-- SCRAM-SHA-256$4096:E4HxLGtnRzsYwg==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo=
+--
+-- Since the salt is random, the exact value stored will be different on every test
+-- run. Use a regular expression to mask the changing parts.
+SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- Rename a role
+ALTER ROLE regress_passwd2 RENAME TO regress_passwd2_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd2_new'
+ ORDER BY rolname, rolpassword;
+ALTER ROLE regress_passwd2_new RENAME TO regress_passwd2;
+
+-- Change passwords with ALTER USER. With plaintext or already-encrypted
+-- passwords.
+SET password_encryption = 'md5';
+
+-- encrypt with MD5
+ALTER ROLE regress_passwd2 PASSWORD 'foo';
+-- already encrypted, use as they are
+ALTER ROLE regress_passwd1 PASSWORD 'md5cd3578025fe2c3d7ed1b9a9b26238b70';
+ALTER ROLE regress_passwd3 PASSWORD 'SCRAM-SHA-256$4096:VLK4RMaQLCvNtQ==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo=';
+
+SET password_encryption = 'scram-sha-256';
+-- create SCRAM secret
+ALTER ROLE regress_passwd4 PASSWORD 'foo';
+-- already encrypted with MD5, use as it is
+CREATE ROLE regress_passwd5 PASSWORD 'md5e73a4b11df52a6068f8b39f90be36023';
+
+-- This looks like a valid SCRAM-SHA-256 secret, but it is not
+-- so it should be hashed with SCRAM-SHA-256.
+CREATE ROLE regress_passwd6 PASSWORD 'SCRAM-SHA-256$1234';
+-- These may look like valid MD5 secrets, but they are not, so they
+-- should be hashed with SCRAM-SHA-256.
+-- trailing garbage at the end
+CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
+-- invalid length
+CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
+
+SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- An empty password is not allowed, in any form
+CREATE ROLE regress_passwd_empty PASSWORD '';
+ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a';
+ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4=';
+SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty';
+
+-- Test with invalid stored and server keys.
+--
+-- The first is valid, to act as a control. The others have too long
+-- stored/server keys. They will be re-hashed.
+CREATE ROLE regress_passwd_sha_len0 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI=';
+CREATE ROLE regress_passwd_sha_len1 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96RqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI=';
+CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
+
+-- Check that the invalid secrets were re-hashed. A re-hashed secret
+-- should not contain the original salt.
+SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd_sha_len%'
+ ORDER BY rolname;
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+DROP ROLE regress_passwd6;
+DROP ROLE regress_passwd7;
+DROP ROLE regress_passwd8;
+DROP ROLE regress_passwd_empty;
+DROP ROLE regress_passwd_sha_len0;
+DROP ROLE regress_passwd_sha_len1;
+DROP ROLE regress_passwd_sha_len2;
+
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
diff --git a/src/test/regress/sql/path.sql b/src/test/regress/sql/path.sql
new file mode 100644
index 0000000..89f1aa9
--- /dev/null
+++ b/src/test/regress/sql/path.sql
@@ -0,0 +1,44 @@
+--
+-- PATH
+--
+
+--DROP TABLE PATH_TBL;
+
+CREATE TABLE PATH_TBL (f1 path);
+
+INSERT INTO PATH_TBL VALUES ('[(1,2),(3,4)]');
+
+INSERT INTO PATH_TBL VALUES (' ( ( 1 , 2 ) , ( 3 , 4 ) ) ');
+
+INSERT INTO PATH_TBL VALUES ('[ (0,0),(3,0),(4,5),(1,6) ]');
+
+INSERT INTO PATH_TBL VALUES ('((1,2) ,(3,4 ))');
+
+INSERT INTO PATH_TBL VALUES ('1,2 ,3,4 ');
+
+INSERT INTO PATH_TBL VALUES (' [1,2,3, 4] ');
+
+INSERT INTO PATH_TBL VALUES ('((10,20))'); -- Only one point
+
+INSERT INTO PATH_TBL VALUES ('[ 11,12,13,14 ]');
+
+INSERT INTO PATH_TBL VALUES ('( 11,12,13,14) ');
+
+-- bad values for parser testing
+INSERT INTO PATH_TBL VALUES ('[]');
+
+INSERT INTO PATH_TBL VALUES ('[(,2),(3,4)]');
+
+INSERT INTO PATH_TBL VALUES ('[(1,2),(3,4)');
+
+INSERT INTO PATH_TBL VALUES ('(1,2,3,4');
+
+INSERT INTO PATH_TBL VALUES ('(1,2),(3,4)]');
+
+SELECT f1 AS open_path FROM PATH_TBL WHERE isopen(f1);
+
+SELECT f1 AS closed_path FROM PATH_TBL WHERE isclosed(f1);
+
+SELECT pclose(f1) AS closed_path FROM PATH_TBL;
+
+SELECT popen(f1) AS open_path FROM PATH_TBL;
diff --git a/src/test/regress/sql/pg_lsn.sql b/src/test/regress/sql/pg_lsn.sql
new file mode 100644
index 0000000..615368b
--- /dev/null
+++ b/src/test/regress/sql/pg_lsn.sql
@@ -0,0 +1,56 @@
+--
+-- 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');
+
+-- Min/Max aggregation
+SELECT MIN(f1), MAX(f1) FROM PG_LSN_TBL;
+
+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;
+SELECT '0/16AE7F7'::pg_lsn + 16::numeric;
+SELECT 16::numeric + '0/16AE7F7'::pg_lsn;
+SELECT '0/16AE7F7'::pg_lsn - 16::numeric;
+SELECT 'FFFFFFFF/FFFFFFFE'::pg_lsn + 1::numeric;
+SELECT 'FFFFFFFF/FFFFFFFE'::pg_lsn + 2::numeric; -- out of range error
+SELECT '0/1'::pg_lsn - 1::numeric;
+SELECT '0/1'::pg_lsn - 2::numeric; -- out of range error
+SELECT '0/0'::pg_lsn + ('FFFFFFFF/FFFFFFFF'::pg_lsn - '0/0'::pg_lsn);
+SELECT 'FFFFFFFF/FFFFFFFF'::pg_lsn - ('FFFFFFFF/FFFFFFFF'::pg_lsn - '0/0'::pg_lsn);
+SELECT '0/16AE7F7'::pg_lsn + 'NaN'::numeric;
+SELECT '0/16AE7F7'::pg_lsn - 'NaN'::numeric;
+
+-- Check btree 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;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
new file mode 100644
index 0000000..4b2f11d
--- /dev/null
+++ b/src/test/regress/sql/plancache.sql
@@ -0,0 +1,225 @@
+--
+-- Tests to exercise the plan caching/invalidation mechanism
+--
+
+CREATE TEMP TABLE pcachetest AS SELECT * FROM int8_tbl;
+
+-- create and use a cached plan
+PREPARE prepstmt AS SELECT * FROM pcachetest;
+
+EXECUTE prepstmt;
+
+-- and one with parameters
+PREPARE prepstmt2(bigint) AS SELECT * FROM pcachetest WHERE q1 = $1;
+
+EXECUTE prepstmt2(123);
+
+-- invalidate the plans and see what happens
+DROP TABLE pcachetest;
+
+EXECUTE prepstmt;
+EXECUTE prepstmt2(123);
+
+-- recreate the temp table (this demonstrates that the raw plan is
+-- purely textual and doesn't depend on OIDs, for instance)
+CREATE TEMP TABLE pcachetest AS SELECT * FROM int8_tbl ORDER BY 2;
+
+EXECUTE prepstmt;
+EXECUTE prepstmt2(123);
+
+-- prepared statements should prevent change in output tupdesc,
+-- since clients probably aren't expecting that to change on the fly
+ALTER TABLE pcachetest ADD COLUMN q3 bigint;
+
+EXECUTE prepstmt;
+EXECUTE prepstmt2(123);
+
+-- but we're nice guys and will let you undo your mistake
+ALTER TABLE pcachetest DROP COLUMN q3;
+
+EXECUTE prepstmt;
+EXECUTE prepstmt2(123);
+
+-- Try it with a view, which isn't directly used in the resulting plan
+-- but should trigger invalidation anyway
+CREATE TEMP VIEW pcacheview AS
+ SELECT * FROM pcachetest;
+
+PREPARE vprep AS SELECT * FROM pcacheview;
+
+EXECUTE vprep;
+
+CREATE OR REPLACE TEMP VIEW pcacheview AS
+ SELECT q1, q2/2 AS q2 FROM pcachetest;
+
+EXECUTE vprep;
+
+-- Check basic SPI plan invalidation
+
+create function cache_test(int) returns int as $$
+declare total int;
+begin
+ create temp table t1(f1 int);
+ insert into t1 values($1);
+ insert into t1 values(11);
+ insert into t1 values(12);
+ insert into t1 values(13);
+ select sum(f1) into total from t1;
+ drop table t1;
+ return total;
+end
+$$ language plpgsql;
+
+select cache_test(1);
+select cache_test(2);
+select cache_test(3);
+
+-- Check invalidation of plpgsql "simple expression"
+
+create temp view v1 as
+ select 2+2 as f1;
+
+create function cache_test_2() returns int as $$
+begin
+ return f1 from v1;
+end$$ language plpgsql;
+
+select cache_test_2();
+
+create or replace temp view v1 as
+ select 2+2+4 as f1;
+select cache_test_2();
+
+create or replace temp view v1 as
+ select 2+2+4+(select max(unique1) from tenk1) as f1;
+select cache_test_2();
+
+--- Check that change of search_path is honored when re-using cached plan
+
+create schema s1
+ create table abc (f1 int);
+
+create schema s2
+ create table abc (f1 int);
+
+insert into s1.abc values(123);
+insert into s2.abc values(456);
+
+set search_path = s1;
+
+prepare p1 as select f1 from abc;
+
+execute p1;
+
+set search_path = s2;
+
+select f1 from abc;
+
+execute p1;
+
+alter table s1.abc add column f2 float8; -- force replan
+
+execute p1;
+
+drop schema s1 cascade;
+drop schema s2 cascade;
+
+reset search_path;
+
+-- Check that invalidation deals with regclass constants
+
+create temp sequence seq;
+
+prepare p2 as select nextval('seq');
+
+execute p2;
+
+drop sequence seq;
+
+create temp sequence seq;
+
+execute p2;
+
+-- Check DDL via SPI, immediately followed by SPI plan re-use
+-- (bug in original coding)
+
+create function cachebug() returns void as $$
+declare r int;
+begin
+ drop table if exists temptable cascade;
+ create temp table temptable as select * from generate_series(1,3) as f1;
+ create temp view vv as select * from temptable;
+ for r in select * from vv loop
+ raise notice '%', r;
+ end loop;
+end$$ language plpgsql;
+
+select cachebug();
+select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table pc_list_parted (a int) partition by list(a);
+create table pc_list_part_null partition of pc_list_parted for values in (null);
+create table pc_list_part_1 partition of pc_list_parted for values in (1);
+create table pc_list_part_def partition of pc_list_parted default;
+prepare pstmt_def_insert (int) as insert into pc_list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+create table pc_list_part_2 partition of pc_list_parted for values in (2);
+execute pstmt_def_insert(2);
+alter table pc_list_parted detach partition pc_list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table pc_list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table pc_list_parted, pc_list_part_null;
+deallocate pstmt_def_insert;
+
+-- Test plan_cache_mode
+
+create table test_mode (a int);
+insert into test_mode select 1 from generate_series(1,1000) union all select 2;
+create index on test_mode (a);
+analyze test_mode;
+
+prepare test_mode_pp (int) as select count(*) from test_mode where a = $1;
+select name, generic_plans, custom_plans from pg_prepared_statements
+ where name = 'test_mode_pp';
+
+-- up to 5 executions, custom plan is used
+set plan_cache_mode to auto;
+explain (costs off) execute test_mode_pp(2);
+select name, generic_plans, custom_plans from pg_prepared_statements
+ where name = 'test_mode_pp';
+
+-- force generic plan
+set plan_cache_mode to force_generic_plan;
+explain (costs off) execute test_mode_pp(2);
+select name, generic_plans, custom_plans from pg_prepared_statements
+ where name = 'test_mode_pp';
+
+-- get to generic plan by 5 executions
+set plan_cache_mode to auto;
+execute test_mode_pp(1); -- 1x
+execute test_mode_pp(1); -- 2x
+execute test_mode_pp(1); -- 3x
+execute test_mode_pp(1); -- 4x
+select name, generic_plans, custom_plans from pg_prepared_statements
+ where name = 'test_mode_pp';
+execute test_mode_pp(1); -- 5x
+select name, generic_plans, custom_plans from pg_prepared_statements
+ where name = 'test_mode_pp';
+
+-- we should now get a really bad plan
+explain (costs off) execute test_mode_pp(2);
+
+-- but we can force a custom plan
+set plan_cache_mode to force_custom_plan;
+explain (costs off) execute test_mode_pp(2);
+select name, generic_plans, custom_plans from pg_prepared_statements
+ where name = 'test_mode_pp';
+
+drop table test_mode;
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
new file mode 100644
index 0000000..7e52d47
--- /dev/null
+++ b/src/test/regress/sql/plpgsql.sql
@@ -0,0 +1,4669 @@
+--
+-- 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 '
+<<outer>>
+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();
+select * from found_test_tbl;
+
+--
+-- 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;
+
+select * from test_table_func_rec();
+
+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;
+
+select * from test_table_func_row();
+
+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();
+SELECT * FROM perform_test;
+
+drop table perform_test;
+
+--
+-- 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";
+
+--
+-- 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();
+
+select * from foo;
+
+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;
+
+select * from 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;
+
+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;
+ 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;
+
+select * from sc_test();
+
+create or replace function sc_test() returns setof integer as $$
+declare
+ c cursor for select * from generate_series(1, 10);
+ x integer;
+begin
+ open c;
+ loop
+ move relative 2 in c;
+ if not found then
+ exit;
+ end if;
+ fetch next from c into x;
+ if found then
+ return next x;
+ end if;
+ end loop;
+ close c;
+end;
+$$ language plpgsql;
+
+select * from sc_test();
+
+create or replace function sc_test() returns setof integer as $$
+declare
+ c cursor for select * from generate_series(1, 10);
+ x integer;
+begin
+ open c;
+ move forward all in c;
+ fetch backward from c into x;
+ if found then
+ return next x;
+ end if;
+ close c;
+end;
+$$ language plpgsql;
+
+select * from sc_test();
+
+drop function sc_test();
+
+-- test qualified variable names
+
+create function pl_qual_names (param1 int) returns void as $$
+<<outerblock>>
+declare
+ param1 int := 1;
+begin
+ <<innerblock>>
+ 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;
+
+select forc01();
+
+select * from forc_test;
+
+-- same, with a cursor whose portal name doesn't match variable name
+create or replace function forc01() returns void as $$
+declare
+ c refcursor := 'fooled_ya';
+ r record;
+begin
+ open c for select * from forc_test;
+ loop
+ fetch c into r;
+ exit when not found;
+ 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;
+
+select forc01();
+
+select * from forc_test;
+
+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
+ return query select * from tabwithcols;
+ return query execute 'select * from tabwithcols';
+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.
+
+create function scope_test() returns int as $$
+declare x int := 42;
+begin
+ declare y int := x + 1;
+ x int := x + 2;
+ begin
+ return x * 100 + y;
+ end;
+end;
+$$ language plpgsql;
+
+select scope_test();
+
+drop function scope_test();
+
+-- 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;
diff --git a/src/test/regress/sql/point.sql b/src/test/regress/sql/point.sql
new file mode 100644
index 0000000..f853791
--- /dev/null
+++ b/src/test/regress/sql/point.sql
@@ -0,0 +1,118 @@
+--
+-- POINT
+--
+
+-- avoid bit-exact output here because operations may not be bit-exact.
+SET extra_float_digits = 0;
+
+CREATE TABLE POINT_TBL(f1 point);
+
+INSERT INTO POINT_TBL(f1) VALUES ('(0.0,0.0)');
+
+INSERT INTO POINT_TBL(f1) VALUES ('(-10.0,0.0)');
+
+INSERT INTO POINT_TBL(f1) VALUES ('(-3.0,4.0)');
+
+INSERT INTO POINT_TBL(f1) VALUES ('(5.1, 34.5)');
+
+INSERT INTO POINT_TBL(f1) VALUES ('(-5.0,-12.0)');
+
+INSERT INTO POINT_TBL(f1) VALUES ('(1e-300,-1e-300)'); -- To underflow
+
+INSERT INTO POINT_TBL(f1) VALUES ('(1e+300,Inf)'); -- To overflow
+
+INSERT INTO POINT_TBL(f1) VALUES ('(Inf,1e+300)'); -- Transposed
+
+INSERT INTO POINT_TBL(f1) VALUES (' ( Nan , NaN ) ');
+
+-- bad format points
+INSERT INTO POINT_TBL(f1) VALUES ('asdfasdf');
+
+INSERT INTO POINT_TBL(f1) VALUES ('10.0,10.0');
+
+INSERT INTO POINT_TBL(f1) VALUES ('(10.0 10.0)');
+
+INSERT INTO POINT_TBL(f1) VALUES ('(10.0, 10.0) x');
+
+INSERT INTO POINT_TBL(f1) VALUES ('(10.0,10.0');
+
+INSERT INTO POINT_TBL(f1) VALUES ('(10.0, 1e+500)'); -- Out of range
+
+
+SELECT * FROM POINT_TBL;
+
+-- left of
+SELECT p.* FROM POINT_TBL p WHERE p.f1 << '(0.0, 0.0)';
+
+-- right of
+SELECT p.* FROM POINT_TBL p WHERE '(0.0,0.0)' >> p.f1;
+
+-- above
+SELECT p.* FROM POINT_TBL p WHERE '(0.0,0.0)' |>> p.f1;
+
+-- below
+SELECT p.* FROM POINT_TBL p WHERE p.f1 <<| '(0.0, 0.0)';
+
+-- equal
+SELECT p.* FROM POINT_TBL p WHERE p.f1 ~= '(5.1, 34.5)';
+
+-- point in box
+SELECT p.* FROM POINT_TBL p
+ WHERE p.f1 <@ box '(0,0,100,100)';
+
+SELECT p.* FROM POINT_TBL p
+ WHERE box '(0,0,100,100)' @> p.f1;
+
+SELECT p.* FROM POINT_TBL p
+ WHERE not p.f1 <@ box '(0,0,100,100)';
+
+SELECT p.* FROM POINT_TBL p
+ WHERE p.f1 <@ path '[(0,0),(-10,0),(-10,10)]';
+
+SELECT p.* FROM POINT_TBL p
+ WHERE not box '(0,0,100,100)' @> p.f1;
+
+SELECT p.f1, p.f1 <-> point '(0,0)' AS dist
+ FROM POINT_TBL p
+ ORDER BY dist;
+
+SELECT p1.f1 AS point1, p2.f1 AS point2, p1.f1 <-> p2.f1 AS dist
+ FROM POINT_TBL p1, POINT_TBL p2
+ ORDER BY dist, p1.f1[0], p2.f1[0];
+
+SELECT p1.f1 AS point1, p2.f1 AS point2
+ FROM POINT_TBL p1, POINT_TBL p2
+ WHERE (p1.f1 <-> p2.f1) > 3;
+
+-- put distance result into output to allow sorting with GEQ optimizer - tgl 97/05/10
+SELECT p1.f1 AS point1, p2.f1 AS point2, (p1.f1 <-> p2.f1) AS distance
+ FROM POINT_TBL p1, POINT_TBL p2
+ WHERE (p1.f1 <-> p2.f1) > 3 and p1.f1 << p2.f1
+ ORDER BY distance, p1.f1[0], p2.f1[0];
+
+-- put distance result into output to allow sorting with GEQ optimizer - tgl 97/05/10
+SELECT p1.f1 AS point1, p2.f1 AS point2, (p1.f1 <-> p2.f1) AS distance
+ FROM POINT_TBL p1, POINT_TBL p2
+ WHERE (p1.f1 <-> p2.f1) > 3 and p1.f1 << p2.f1 and p1.f1 |>> p2.f1
+ ORDER BY distance;
+
+-- Test that GiST indexes provide same behavior as sequential scan
+CREATE TEMP TABLE point_gist_tbl(f1 point);
+INSERT INTO point_gist_tbl SELECT '(0,0)' FROM generate_series(0,1000);
+CREATE INDEX point_gist_tbl_index ON point_gist_tbl USING gist (f1);
+INSERT INTO point_gist_tbl VALUES ('(0.0000009,0.0000009)');
+SET enable_seqscan TO true;
+SET enable_indexscan TO false;
+SET enable_bitmapscan TO false;
+SELECT COUNT(*) FROM point_gist_tbl WHERE f1 ~= '(0.0000009,0.0000009)'::point;
+SELECT COUNT(*) FROM point_gist_tbl WHERE f1 <@ '(0.0000009,0.0000009),(0.0000009,0.0000009)'::box;
+SELECT COUNT(*) FROM point_gist_tbl WHERE f1 ~= '(0.0000018,0.0000018)'::point;
+SET enable_seqscan TO false;
+SET enable_indexscan TO true;
+SET enable_bitmapscan TO true;
+SELECT COUNT(*) FROM point_gist_tbl WHERE f1 ~= '(0.0000009,0.0000009)'::point;
+SELECT COUNT(*) FROM point_gist_tbl WHERE f1 <@ '(0.0000009,0.0000009),(0.0000009,0.0000009)'::box;
+SELECT COUNT(*) FROM point_gist_tbl WHERE f1 ~= '(0.0000018,0.0000018)'::point;
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/polygon.sql b/src/test/regress/sql/polygon.sql
new file mode 100644
index 0000000..f53b2cb
--- /dev/null
+++ b/src/test/regress/sql/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/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
new file mode 100644
index 0000000..fa57db6
--- /dev/null
+++ b/src/test/regress/sql/polymorphism.sql
@@ -0,0 +1,1137 @@
+--
+-- Tests for polymorphic SQL functions and aggregates based on them.
+-- Tests for other features related to function-calling have snuck in, too.
+--
+
+create function polyf(x anyelement) returns anyelement as $$
+ select x + 1
+$$ language sql;
+
+select polyf(42) as int, polyf(4.5) as num;
+select polyf(point(3,4)); -- fail for lack of + operator
+
+drop function polyf(x anyelement);
+
+create function polyf(x anyelement) returns anyarray as $$
+ select array[x + 1, x + 2]
+$$ language sql;
+
+select polyf(42) as int, polyf(4.5) as num;
+
+drop function polyf(x anyelement);
+
+create function polyf(x anyarray) returns anyelement as $$
+ select x[1]
+$$ language sql;
+
+select polyf(array[2,4]) as int, polyf(array[4.5, 7.7]) as num;
+
+select polyf(stavalues1) from pg_statistic; -- fail, can't infer element type
+
+drop function polyf(x anyarray);
+
+create function polyf(x anyarray) returns anyarray as $$
+ select x
+$$ language sql;
+
+select polyf(array[2,4]) as int, polyf(array[4.5, 7.7]) as num;
+
+select polyf(stavalues1) from pg_statistic; -- fail, can't infer element type
+
+drop function polyf(x anyarray);
+
+-- fail, can't infer type:
+create function polyf(x anyelement) returns anyrange as $$
+ select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anyrange) returns anyarray as $$
+ select array[lower(x), upper(x)]
+$$ language sql;
+
+select polyf(int4range(42, 49)) as int, polyf(float8range(4.5, 7.8)) as num;
+
+drop function polyf(x anyrange);
+
+create function polyf(x anycompatible, y anycompatible) returns anycompatiblearray as $$
+ select array[x, y]
+$$ language sql;
+
+select polyf(2, 4) as int, polyf(2, 4.5) as num;
+
+drop function polyf(x anycompatible, y anycompatible);
+
+create function polyf(x anycompatiblerange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+ select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(int4range(42, 49), 11, 2::smallint) as int, polyf(float8range(4.5, 7.8), 7.8, 11::real) as num;
+
+select polyf(int4range(42, 49), 11, 4.5) as fail; -- range type doesn't fit
+
+drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+ select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail; -- range type doesn't fit
+
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblerange as $$
+ select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
+ select x
+$$ language sql;
+
+select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8), array[7]) as num;
+
+drop function polyf(x anycompatiblerange, y anycompatiblearray);
+
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+ select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+ select x
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+
+create function polyf(a anyelement, b anyarray,
+ c anycompatible, d anycompatible,
+ OUT x anyarray, OUT y anycompatiblearray)
+as $$
+ select a || b, array[c, d]
+$$ language sql;
+
+select x, pg_typeof(x), y, pg_typeof(y)
+ from polyf(11, array[1, 2], 42, 34.5);
+select x, pg_typeof(x), y, pg_typeof(y)
+ from polyf(11, array[1, 2], point(1,2), point(3,4));
+select x, pg_typeof(x), y, pg_typeof(y)
+ from polyf(11, '{1,2}', point(1,2), '(3,4)');
+select x, pg_typeof(x), y, pg_typeof(y)
+ from polyf(11, array[1, 2.2], 42, 34.5); -- fail
+
+drop function polyf(a anyelement, b anyarray,
+ c anycompatible, d anycompatible);
+
+create function polyf(anyrange) returns anymultirange
+as 'select multirange($1);' language sql;
+
+select polyf(int4range(1,10));
+select polyf(null);
+
+drop function polyf(anyrange);
+
+create function polyf(anymultirange) returns anyelement
+as 'select lower($1);' language sql;
+
+select polyf(int4multirange(int4range(1,10), int4range(20,30)));
+select polyf(null);
+
+drop function polyf(anymultirange);
+
+create function polyf(anycompatiblerange) returns anycompatiblemultirange
+as 'select multirange($1);' language sql;
+
+select polyf(int4range(1,10));
+select polyf(null);
+
+drop function polyf(anycompatiblerange);
+
+create function polyf(anymultirange) returns anyrange
+as 'select range_merge($1);' language sql;
+
+select polyf(int4multirange(int4range(1,10), int4range(20,30)));
+select polyf(null);
+
+drop function polyf(anymultirange);
+
+create function polyf(anycompatiblemultirange) returns anycompatiblerange
+as 'select range_merge($1);' language sql;
+
+select polyf(int4multirange(int4range(1,10), int4range(20,30)));
+select polyf(null);
+
+drop function polyf(anycompatiblemultirange);
+
+create function polyf(anycompatiblemultirange) returns anycompatible
+as 'select lower($1);' language sql;
+
+select polyf(int4multirange(int4range(1,10), int4range(20,30)));
+select polyf(null);
+
+drop function polyf(anycompatiblemultirange);
+
+
+--
+-- Polymorphic aggregate tests
+--
+-- Legend:
+-----------
+-- A = type is ANY
+-- P = type is polymorphic
+-- N = type is non-polymorphic
+-- B = aggregate base type
+-- S = aggregate state type
+-- R = aggregate return type
+-- 1 = arg1 of a function
+-- 2 = arg2 of a function
+-- ag = aggregate
+-- tf = trans (state) function
+-- ff = final function
+-- rt = return type of a function
+-- -> = implies
+-- => = allowed
+-- !> = not allowed
+-- E = exists
+-- NE = not-exists
+--
+-- Possible states:
+-- ----------------
+-- B = (A || P || N)
+-- when (B = A) -> (tf2 = NE)
+-- S = (P || N)
+-- ff = (E || NE)
+-- tf1 = (P || N)
+-- tf2 = (NE || P || N)
+-- R = (P || N)
+
+-- create functions for use as tf and ff with the needed combinations of
+-- argument polymorphism, but within the constraints of valid aggregate
+-- functions, i.e. tf arg1 and tf return type must match
+
+-- polymorphic single arg transfn
+CREATE FUNCTION stfp(anyarray) RETURNS anyarray AS
+'select $1' LANGUAGE SQL;
+-- non-polymorphic single arg transfn
+CREATE FUNCTION stfnp(int[]) RETURNS int[] AS
+'select $1' LANGUAGE SQL;
+
+-- dual polymorphic transfn
+CREATE FUNCTION tfp(anyarray,anyelement) RETURNS anyarray AS
+'select $1 || $2' LANGUAGE SQL;
+-- dual non-polymorphic transfn
+CREATE FUNCTION tfnp(int[],int) RETURNS int[] AS
+'select $1 || $2' LANGUAGE SQL;
+
+-- arg1 only polymorphic transfn
+CREATE FUNCTION tf1p(anyarray,int) RETURNS anyarray AS
+'select $1' LANGUAGE SQL;
+-- arg2 only polymorphic transfn
+CREATE FUNCTION tf2p(int[],anyelement) RETURNS int[] AS
+'select $1' LANGUAGE SQL;
+
+-- multi-arg polymorphic
+CREATE FUNCTION sum3(anyelement,anyelement,anyelement) returns anyelement AS
+'select $1+$2+$3' language sql strict;
+
+-- finalfn polymorphic
+CREATE FUNCTION ffp(anyarray) RETURNS anyarray AS
+'select $1' LANGUAGE SQL;
+-- finalfn non-polymorphic
+CREATE FUNCTION ffnp(int[]) returns int[] as
+'select $1' LANGUAGE SQL;
+
+-- Try to cover all the possible states:
+--
+-- Note: in Cases 1 & 2, we are trying to return P. Therefore, if the transfn
+-- is stfnp, tfnp, or tf2p, we must use ffp as finalfn, because stfnp, tfnp,
+-- and tf2p do not return P. Conversely, in Cases 3 & 4, we are trying to
+-- return N. Therefore, if the transfn is stfp, tfp, or tf1p, we must use ffnp
+-- as finalfn, because stfp, tfp, and tf1p do not return N.
+--
+-- Case1 (R = P) && (B = A)
+-- ------------------------
+-- S tf1
+-- -------
+-- N N
+-- should CREATE
+CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
+ FINALFUNC = ffp, INITCOND = '{}');
+
+-- P N
+-- should ERROR: stfnp(anyarray) not matched by stfnp(int[])
+CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
+ FINALFUNC = ffp, INITCOND = '{}');
+
+-- N P
+-- should CREATE
+CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
+ FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
+ INITCOND = '{}');
+
+-- P P
+-- should ERROR: we have no way to resolve S
+CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
+ FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
+ INITCOND = '{}');
+
+
+-- Case2 (R = P) && ((B = P) || (B = N))
+-- -------------------------------------
+-- S tf1 B tf2
+-- -----------------------
+-- N N N N
+-- should CREATE
+CREATE AGGREGATE myaggp05a(BASETYPE = int, SFUNC = tfnp, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+
+-- N N N P
+-- should CREATE
+CREATE AGGREGATE myaggp06a(BASETYPE = int, SFUNC = tf2p, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+
+-- N N P N
+-- should ERROR: tfnp(int[], anyelement) not matched by tfnp(int[], int)
+CREATE AGGREGATE myaggp07a(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+
+-- N N P P
+-- should CREATE
+CREATE AGGREGATE myaggp08a(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+
+-- N P N N
+-- should CREATE
+CREATE AGGREGATE myaggp09a(BASETYPE = int, SFUNC = tf1p, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp09b(BASETYPE = int, SFUNC = tf1p, STYPE = int[],
+ INITCOND = '{}');
+
+-- N P N P
+-- should CREATE
+CREATE AGGREGATE myaggp10a(BASETYPE = int, SFUNC = tfp, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp10b(BASETYPE = int, SFUNC = tfp, STYPE = int[],
+ INITCOND = '{}');
+
+-- N P P N
+-- should ERROR: tf1p(int[],anyelement) not matched by tf1p(anyarray,int)
+CREATE AGGREGATE myaggp11a(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp11b(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[],
+ INITCOND = '{}');
+
+-- N P P P
+-- should ERROR: tfp(int[],anyelement) not matched by tfp(anyarray,anyelement)
+CREATE AGGREGATE myaggp12a(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[],
+ FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp12b(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[],
+ INITCOND = '{}');
+
+-- P N N N
+-- should ERROR: tfnp(anyarray, int) not matched by tfnp(int[],int)
+CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
+ FINALFUNC = ffp, INITCOND = '{}');
+
+-- P N N P
+-- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
+CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
+ FINALFUNC = ffp, INITCOND = '{}');
+
+-- P N P N
+-- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
+CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
+ STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}');
+
+-- P N P P
+-- should ERROR: tf2p(anyarray, anyelement) not matched by tf2p(int[],anyelement)
+CREATE AGGREGATE myaggp16a(BASETYPE = anyelement, SFUNC = tf2p,
+ STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}');
+
+-- P P N N
+-- should ERROR: we have no way to resolve S
+CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
+ FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
+ INITCOND = '{}');
+
+-- P P N P
+-- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
+CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
+ FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
+ INITCOND = '{}');
+
+-- P P P N
+-- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
+CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
+ STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp19b(BASETYPE = anyelement, SFUNC = tf1p,
+ STYPE = anyarray, INITCOND = '{}');
+
+-- P P P P
+-- should CREATE
+CREATE AGGREGATE myaggp20a(BASETYPE = anyelement, SFUNC = tfp,
+ STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}');
+CREATE AGGREGATE myaggp20b(BASETYPE = anyelement, SFUNC = tfp,
+ STYPE = anyarray, INITCOND = '{}');
+
+-- Case3 (R = N) && (B = A)
+-- ------------------------
+-- S tf1
+-- -------
+-- N N
+-- should CREATE
+CREATE AGGREGATE myaggn01a(*) (SFUNC = stfnp, STYPE = int4[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
+ INITCOND = '{}');
+
+-- P N
+-- should ERROR: stfnp(anyarray) not matched by stfnp(int[])
+CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
+ FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
+ INITCOND = '{}');
+
+-- N P
+-- should CREATE
+CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+
+-- P P
+-- should ERROR: ffnp(anyarray) not matched by ffnp(int[])
+CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
+ FINALFUNC = ffnp, INITCOND = '{}');
+
+
+-- Case4 (R = N) && ((B = P) || (B = N))
+-- -------------------------------------
+-- S tf1 B tf2
+-- -----------------------
+-- N N N N
+-- should CREATE
+CREATE AGGREGATE myaggn05a(BASETYPE = int, SFUNC = tfnp, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn05b(BASETYPE = int, SFUNC = tfnp, STYPE = int[],
+ INITCOND = '{}');
+
+-- N N N P
+-- should CREATE
+CREATE AGGREGATE myaggn06a(BASETYPE = int, SFUNC = tf2p, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn06b(BASETYPE = int, SFUNC = tf2p, STYPE = int[],
+ INITCOND = '{}');
+
+-- N N P N
+-- should ERROR: tfnp(int[], anyelement) not matched by tfnp(int[], int)
+CREATE AGGREGATE myaggn07a(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn07b(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[],
+ INITCOND = '{}');
+
+-- N N P P
+-- should CREATE
+CREATE AGGREGATE myaggn08a(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn08b(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[],
+ INITCOND = '{}');
+
+-- N P N N
+-- should CREATE
+CREATE AGGREGATE myaggn09a(BASETYPE = int, SFUNC = tf1p, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+
+-- N P N P
+-- should CREATE
+CREATE AGGREGATE myaggn10a(BASETYPE = int, SFUNC = tfp, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+
+-- N P P N
+-- should ERROR: tf1p(int[],anyelement) not matched by tf1p(anyarray,int)
+CREATE AGGREGATE myaggn11a(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+
+-- N P P P
+-- should ERROR: tfp(int[],anyelement) not matched by tfp(anyarray,anyelement)
+CREATE AGGREGATE myaggn12a(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[],
+ FINALFUNC = ffnp, INITCOND = '{}');
+
+-- P N N N
+-- should ERROR: tfnp(anyarray, int) not matched by tfnp(int[],int)
+CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
+ FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
+ INITCOND = '{}');
+
+-- P N N P
+-- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
+CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
+ FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
+ INITCOND = '{}');
+
+-- P N P N
+-- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
+CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
+ STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn15b(BASETYPE = anyelement, SFUNC = tfnp,
+ STYPE = anyarray, INITCOND = '{}');
+
+-- P N P P
+-- should ERROR: tf2p(anyarray, anyelement) not matched by tf2p(int[],anyelement)
+CREATE AGGREGATE myaggn16a(BASETYPE = anyelement, SFUNC = tf2p,
+ STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
+CREATE AGGREGATE myaggn16b(BASETYPE = anyelement, SFUNC = tf2p,
+ STYPE = anyarray, INITCOND = '{}');
+
+-- P P N N
+-- should ERROR: ffnp(anyarray) not matched by ffnp(int[])
+CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
+ FINALFUNC = ffnp, INITCOND = '{}');
+
+-- P P N P
+-- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
+CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
+ FINALFUNC = ffnp, INITCOND = '{}');
+
+-- P P P N
+-- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
+CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
+ STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
+
+-- P P P P
+-- should ERROR: ffnp(anyarray) not matched by ffnp(int[])
+CREATE AGGREGATE myaggn20a(BASETYPE = anyelement, SFUNC = tfp,
+ STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
+
+-- multi-arg polymorphic
+CREATE AGGREGATE mysum2(anyelement,anyelement) (SFUNC = sum3,
+ STYPE = anyelement, INITCOND = '0');
+
+-- create test data for polymorphic aggregates
+create temp table t(f1 int, f2 int[], f3 text);
+insert into t values(1,array[1],'a');
+insert into t values(1,array[11],'b');
+insert into t values(1,array[111],'c');
+insert into t values(2,array[2],'a');
+insert into t values(2,array[22],'b');
+insert into t values(2,array[222],'c');
+insert into t values(3,array[3],'a');
+insert into t values(3,array[3],'b');
+
+-- test the successfully created polymorphic aggregates
+select f3, myaggp01a(*) from t group by f3 order by f3;
+select f3, myaggp03a(*) from t group by f3 order by f3;
+select f3, myaggp03b(*) from t group by f3 order by f3;
+select f3, myaggp05a(f1) from t group by f3 order by f3;
+select f3, myaggp06a(f1) from t group by f3 order by f3;
+select f3, myaggp08a(f1) from t group by f3 order by f3;
+select f3, myaggp09a(f1) from t group by f3 order by f3;
+select f3, myaggp09b(f1) from t group by f3 order by f3;
+select f3, myaggp10a(f1) from t group by f3 order by f3;
+select f3, myaggp10b(f1) from t group by f3 order by f3;
+select f3, myaggp20a(f1) from t group by f3 order by f3;
+select f3, myaggp20b(f1) from t group by f3 order by f3;
+select f3, myaggn01a(*) from t group by f3 order by f3;
+select f3, myaggn01b(*) from t group by f3 order by f3;
+select f3, myaggn03a(*) from t group by f3 order by f3;
+select f3, myaggn05a(f1) from t group by f3 order by f3;
+select f3, myaggn05b(f1) from t group by f3 order by f3;
+select f3, myaggn06a(f1) from t group by f3 order by f3;
+select f3, myaggn06b(f1) from t group by f3 order by f3;
+select f3, myaggn08a(f1) from t group by f3 order by f3;
+select f3, myaggn08b(f1) from t group by f3 order by f3;
+select f3, myaggn09a(f1) from t group by f3 order by f3;
+select f3, myaggn10a(f1) from t group by f3 order by f3;
+select mysum2(f1, f1 + 1) from t;
+
+-- test inlining of polymorphic SQL functions
+create function bleat(int) returns int as $$
+begin
+ raise notice 'bleat %', $1;
+ return $1;
+end$$ language plpgsql;
+
+create function sql_if(bool, anyelement, anyelement) returns anyelement as $$
+select case when $1 then $2 else $3 end $$ language sql;
+
+-- Note this would fail with integer overflow, never mind wrong bleat() output,
+-- if the CASE expression were not successfully inlined
+select f1, sql_if(f1 > 0, bleat(f1), bleat(f1 + 1)) from int4_tbl;
+
+select q2, sql_if(q2 > 0, q2, q2 + 1) from int8_tbl;
+
+-- another sort of polymorphic aggregate
+
+CREATE AGGREGATE array_larger_accum (anyarray)
+(
+ sfunc = array_larger,
+ stype = anyarray,
+ initcond = '{}'
+);
+
+SELECT array_larger_accum(i)
+FROM (VALUES (ARRAY[1,2]), (ARRAY[3,4])) as t(i);
+
+SELECT array_larger_accum(i)
+FROM (VALUES (ARRAY[row(1,2),row(3,4)]), (ARRAY[row(5,6),row(7,8)])) as t(i);
+
+-- another kind of polymorphic aggregate
+
+create function add_group(grp anyarray, ad anyelement, size integer)
+ returns anyarray
+ as $$
+begin
+ if grp is null then
+ return array[ad];
+ end if;
+ if array_upper(grp, 1) < size then
+ return grp || ad;
+ end if;
+ return grp;
+end;
+$$
+ language plpgsql immutable;
+
+create aggregate build_group(anyelement, integer) (
+ SFUNC = add_group,
+ STYPE = anyarray
+);
+
+select build_group(q1,3) from int8_tbl;
+
+-- this should fail because stype isn't compatible with arg
+create aggregate build_group(int8, integer) (
+ SFUNC = add_group,
+ STYPE = int2[]
+);
+
+-- but we can make a non-poly agg from a poly sfunc if types are OK
+create aggregate build_group(int8, integer) (
+ SFUNC = add_group,
+ STYPE = int8[]
+);
+
+-- check proper resolution of data types for polymorphic transfn/finalfn
+
+create function first_el_transfn(anyarray, anyelement) returns anyarray as
+'select $1 || $2' language sql immutable;
+
+create function first_el(anyarray) returns anyelement as
+'select $1[1]' language sql strict immutable;
+
+create aggregate first_el_agg_f8(float8) (
+ SFUNC = array_append,
+ STYPE = float8[],
+ FINALFUNC = first_el
+);
+
+create aggregate first_el_agg_any(anyelement) (
+ SFUNC = first_el_transfn,
+ STYPE = anyarray,
+ FINALFUNC = first_el
+);
+
+select first_el_agg_f8(x::float8) from generate_series(1,10) x;
+select first_el_agg_any(x) from generate_series(1,10) x;
+select first_el_agg_f8(x::float8) over(order by x) from generate_series(1,10) x;
+select first_el_agg_any(x) over(order by x) from generate_series(1,10) x;
+
+-- check that we can apply functions taking ANYARRAY to pg_stats
+select distinct array_ndims(histogram_bounds) from pg_stats
+where histogram_bounds is not null;
+
+-- such functions must protect themselves if varying element type isn't OK
+-- (WHERE clause here is to avoid possibly getting a collation error instead)
+select max(histogram_bounds) from pg_stats where tablename = 'pg_am';
+
+-- another corner case is the input functions for polymorphic pseudotypes
+select array_in('{1,2,3}','int4'::regtype,-1); -- this has historically worked
+select * from array_in('{1,2,3}','int4'::regtype,-1); -- this not
+select anyrange_in('[10,20)','int4range'::regtype,-1);
+
+-- test variadic polymorphic functions
+
+create function myleast(variadic anyarray) returns anyelement as $$
+ select min($1[i]) from generate_subscripts($1,1) g(i)
+$$ language sql immutable strict;
+
+select myleast(10, 1, 20, 33);
+select myleast(1.1, 0.22, 0.55);
+select myleast('z'::text);
+select myleast(); -- fail
+
+-- test with variadic call parameter
+select myleast(variadic array[1,2,3,4,-1]);
+select myleast(variadic array[1.1, -5.5]);
+
+--test with empty variadic call parameter
+select myleast(variadic array[]::int[]);
+
+-- an example with some ordinary arguments too
+create function concat(text, variadic anyarray) returns text as $$
+ select array_to_string($2, $1);
+$$ language sql immutable strict;
+
+select concat('%', 1, 2, 3, 4, 5);
+select concat('|', 'a'::text, 'b', 'c');
+select concat('|', variadic array[1,2,33]);
+select concat('|', variadic array[]::int[]);
+
+drop function concat(text, anyarray);
+
+-- mix variadic with anyelement
+create function formarray(anyelement, variadic anyarray) returns anyarray as $$
+ select array_prepend($1, $2);
+$$ language sql immutable strict;
+
+select formarray(1,2,3,4,5);
+select formarray(1.1, variadic array[1.2,55.5]);
+select formarray(1.1, array[1.2,55.5]); -- fail without variadic
+select formarray(1, 'x'::text); -- fail, type mismatch
+select formarray(1, variadic array['x'::text]); -- fail, type mismatch
+
+drop function formarray(anyelement, variadic anyarray);
+
+-- test pg_typeof() function
+select pg_typeof(null); -- unknown
+select pg_typeof(0); -- integer
+select pg_typeof(0.0); -- numeric
+select pg_typeof(1+1 = 2); -- boolean
+select pg_typeof('x'); -- unknown
+select pg_typeof('' || ''); -- text
+select pg_typeof(pg_typeof(0)); -- regtype
+select pg_typeof(array[1.2,55.5]); -- numeric[]
+select pg_typeof(myleast(10, 1, 20, 33)); -- polymorphic input
+
+-- test functions with default parameters
+
+-- test basic functionality
+create function dfunc(a int = 1, int = 2) returns int as $$
+ select $1 + $2;
+$$ language sql;
+
+select dfunc();
+select dfunc(10);
+select dfunc(10, 20);
+select dfunc(10, 20, 30); -- fail
+
+drop function dfunc(); -- fail
+drop function dfunc(int); -- fail
+drop function dfunc(int, int); -- ok
+
+-- fail: defaults must be at end of argument list
+create function dfunc(a int = 1, b int) returns int as $$
+ select $1 + $2;
+$$ language sql;
+
+-- however, this should work:
+create function dfunc(a int = 1, out sum int, b int = 2) as $$
+ select $1 + $2;
+$$ language sql;
+
+select dfunc();
+
+-- verify it lists properly
+\df dfunc
+
+drop function dfunc(int, int);
+
+-- check implicit coercion
+create function dfunc(a int DEFAULT 1.0, int DEFAULT '-1') returns int as $$
+ select $1 + $2;
+$$ language sql;
+select dfunc();
+
+create function dfunc(a text DEFAULT 'Hello', b text DEFAULT 'World') returns text as $$
+ select $1 || ', ' || $2;
+$$ language sql;
+
+select dfunc(); -- fail: which dfunc should be called? int or text
+select dfunc('Hi'); -- ok
+select dfunc('Hi', 'City'); -- ok
+select dfunc(0); -- ok
+select dfunc(10, 20); -- ok
+
+drop function dfunc(int, int);
+drop function dfunc(text, text);
+
+create function dfunc(int = 1, int = 2) returns int as $$
+ select 2;
+$$ language sql;
+
+create function dfunc(int = 1, int = 2, int = 3, int = 4) returns int as $$
+ select 4;
+$$ language sql;
+
+-- Now, dfunc(nargs = 2) and dfunc(nargs = 4) are ambiguous when called
+-- with 0 to 2 arguments.
+
+select dfunc(); -- fail
+select dfunc(1); -- fail
+select dfunc(1, 2); -- fail
+select dfunc(1, 2, 3); -- ok
+select dfunc(1, 2, 3, 4); -- ok
+
+drop function dfunc(int, int);
+drop function dfunc(int, int, int, int);
+
+-- default values are not allowed for output parameters
+create function dfunc(out int = 20) returns int as $$
+ select 1;
+$$ language sql;
+
+-- polymorphic parameter test
+create function dfunc(anyelement = 'World'::text) returns text as $$
+ select 'Hello, ' || $1::text;
+$$ language sql;
+
+select dfunc();
+select dfunc(0);
+select dfunc(to_date('20081215','YYYYMMDD'));
+select dfunc('City'::text);
+
+drop function dfunc(anyelement);
+
+-- check defaults for variadics
+
+create function dfunc(a variadic int[]) returns int as
+$$ select array_upper($1, 1) $$ language sql;
+
+select dfunc(); -- fail
+select dfunc(10);
+select dfunc(10,20);
+
+create or replace function dfunc(a variadic int[] default array[]::int[]) returns int as
+$$ select array_upper($1, 1) $$ language sql;
+
+select dfunc(); -- now ok
+select dfunc(10);
+select dfunc(10,20);
+
+-- can't remove the default once it exists
+create or replace function dfunc(a variadic int[]) returns int as
+$$ select array_upper($1, 1) $$ language sql;
+
+\df dfunc
+
+drop function dfunc(a variadic int[]);
+
+-- Ambiguity should be reported only if there's not a better match available
+
+create function dfunc(int = 1, int = 2, int = 3) returns int as $$
+ select 3;
+$$ language sql;
+
+create function dfunc(int = 1, int = 2) returns int as $$
+ select 2;
+$$ language sql;
+
+create function dfunc(text) returns text as $$
+ select $1;
+$$ language sql;
+
+-- dfunc(narg=2) and dfunc(narg=3) are ambiguous
+select dfunc(1); -- fail
+
+-- but this works since the ambiguous functions aren't preferred anyway
+select dfunc('Hi');
+
+drop function dfunc(int, int, int);
+drop function dfunc(int, int);
+drop function dfunc(text);
+
+--
+-- Tests for named- and mixed-notation function calling
+--
+
+create function dfunc(a int, b int, c int = 0, d int = 0)
+ returns table (a int, b int, c int, d int) as $$
+ select $1, $2, $3, $4;
+$$ language sql;
+
+select (dfunc(10,20,30)).*;
+select (dfunc(a := 10, b := 20, c := 30)).*;
+select * from dfunc(a := 10, b := 20);
+select * from dfunc(b := 10, a := 20);
+select * from dfunc(0); -- fail
+select * from dfunc(1,2);
+select * from dfunc(1,2,c := 3);
+select * from dfunc(1,2,d := 3);
+
+select * from dfunc(x := 20, b := 10, x := 30); -- fail, duplicate name
+select * from dfunc(10, b := 20, 30); -- fail, named args must be last
+select * from dfunc(x := 10, b := 20, c := 30); -- fail, unknown param
+select * from dfunc(10, 10, a := 20); -- fail, a overlaps positional parameter
+select * from dfunc(1,c := 2,d := 3); -- fail, no value for b
+
+drop function dfunc(int, int, int, int);
+
+-- test with different parameter types
+create function dfunc(a varchar, b numeric, c date = current_date)
+ returns table (a varchar, b numeric, c date) as $$
+ select $1, $2, $3;
+$$ language sql;
+
+select (dfunc('Hello World', 20, '2009-07-25'::date)).*;
+select * from dfunc('Hello World', 20, '2009-07-25'::date);
+select * from dfunc(c := '2009-07-25'::date, a := 'Hello World', b := 20);
+select * from dfunc('Hello World', b := 20, c := '2009-07-25'::date);
+select * from dfunc('Hello World', c := '2009-07-25'::date, b := 20);
+select * from dfunc('Hello World', c := 20, b := '2009-07-25'::date); -- fail
+
+drop function dfunc(varchar, numeric, date);
+
+-- test out parameters with named params
+create function dfunc(a varchar = 'def a', out _a varchar, c numeric = NULL, out _c numeric)
+returns record as $$
+ select $1, $2;
+$$ language sql;
+
+select (dfunc()).*;
+select * from dfunc();
+select * from dfunc('Hello', 100);
+select * from dfunc(a := 'Hello', c := 100);
+select * from dfunc(c := 100, a := 'Hello');
+select * from dfunc('Hello');
+select * from dfunc('Hello', c := 100);
+select * from dfunc(c := 100);
+
+-- fail, can no longer change an input parameter's name
+create or replace function dfunc(a varchar = 'def a', out _a varchar, x numeric = NULL, out _c numeric)
+returns record as $$
+ select $1, $2;
+$$ language sql;
+
+create or replace function dfunc(a varchar = 'def a', out _a varchar, numeric = NULL, out _c numeric)
+returns record as $$
+ select $1, $2;
+$$ language sql;
+
+drop function dfunc(varchar, numeric);
+
+--fail, named parameters are not unique
+create function testpolym(a int, a int) returns int as $$ select 1;$$ language sql;
+create function testpolym(int, out a int, out a int) returns int as $$ select 1;$$ language sql;
+create function testpolym(out a int, inout a int) returns int as $$ select 1;$$ language sql;
+create function testpolym(a int, inout a int) returns int as $$ select 1;$$ language sql;
+
+-- valid
+create function testpolym(a int, out a int) returns int as $$ select $1;$$ language sql;
+select testpolym(37);
+drop function testpolym(int);
+create function testpolym(a int) returns table(a int) as $$ select $1;$$ language sql;
+select * from testpolym(37);
+drop function testpolym(int);
+
+-- test polymorphic params and defaults
+create function dfunc(a anyelement, b anyelement = null, flag bool = true)
+returns anyelement as $$
+ select case when $3 then $1 else $2 end;
+$$ language sql;
+
+select dfunc(1,2);
+select dfunc('a'::text, 'b'); -- positional notation with default
+
+select dfunc(a := 1, b := 2);
+select dfunc(a := 'a'::text, b := 'b');
+select dfunc(a := 'a'::text, b := 'b', flag := false); -- named notation
+
+select dfunc(b := 'b'::text, a := 'a'); -- named notation with default
+select dfunc(a := 'a'::text, flag := true); -- named notation with default
+select dfunc(a := 'a'::text, flag := false); -- named notation with default
+select dfunc(b := 'b'::text, a := 'a', flag := true); -- named notation
+
+select dfunc('a'::text, 'b', false); -- full positional notation
+select dfunc('a'::text, 'b', flag := false); -- mixed notation
+select dfunc('a'::text, 'b', true); -- full positional notation
+select dfunc('a'::text, 'b', flag := true); -- mixed notation
+
+-- ansi/sql syntax
+select dfunc(a => 1, b => 2);
+select dfunc(a => 'a'::text, b => 'b');
+select dfunc(a => 'a'::text, b => 'b', flag => false); -- named notation
+
+select dfunc(b => 'b'::text, a => 'a'); -- named notation with default
+select dfunc(a => 'a'::text, flag => true); -- named notation with default
+select dfunc(a => 'a'::text, flag => false); -- named notation with default
+select dfunc(b => 'b'::text, a => 'a', flag => true); -- named notation
+
+select dfunc('a'::text, 'b', false); -- full positional notation
+select dfunc('a'::text, 'b', flag => false); -- mixed notation
+select dfunc('a'::text, 'b', true); -- full positional notation
+select dfunc('a'::text, 'b', flag => true); -- mixed notation
+
+-- this tests lexer edge cases around =>
+select dfunc(a =>-1);
+select dfunc(a =>+1);
+select dfunc(a =>/**/1);
+select dfunc(a =>--comment to be removed by psql
+ 1);
+-- need DO to protect the -- from psql
+do $$
+ declare r integer;
+ begin
+ select dfunc(a=>-- comment
+ 1) into r;
+ raise info 'r = %', r;
+ end;
+$$;
+
+-- check reverse-listing of named-arg calls
+CREATE VIEW dfview AS
+ SELECT q1, q2,
+ dfunc(q1,q2, flag := q1>q2) as c3,
+ dfunc(q1, flag := q1<q2, b := q2) as c4
+ FROM int8_tbl;
+
+select * from dfview;
+
+\d+ dfview
+
+drop view dfview;
+drop function dfunc(anyelement, anyelement, bool);
+
+--
+-- Tests for ANYCOMPATIBLE polymorphism family
+--
+
+create function anyctest(anycompatible, anycompatible)
+returns anycompatible as $$
+ select greatest($1, $2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, 12) x;
+select x, pg_typeof(x) from anyctest(11, 12.3) x;
+select x, pg_typeof(x) from anyctest(11, point(1,2)) x; -- fail
+select x, pg_typeof(x) from anyctest('11', '12.3') x; -- defaults to text
+
+drop function anyctest(anycompatible, anycompatible);
+
+create function anyctest(anycompatible, anycompatible)
+returns anycompatiblearray as $$
+ select array[$1, $2]
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, 12) x;
+select x, pg_typeof(x) from anyctest(11, 12.3) x;
+select x, pg_typeof(x) from anyctest(11, array[1,2]) x; -- fail
+
+drop function anyctest(anycompatible, anycompatible);
+
+create function anyctest(anycompatible, anycompatiblearray)
+returns anycompatiblearray as $$
+ select array[$1] || $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, array[12]) x;
+select x, pg_typeof(x) from anyctest(11, array[12.3]) x;
+select x, pg_typeof(x) from anyctest(12.3, array[13]) x;
+select x, pg_typeof(x) from anyctest(12.3, '{13,14.4}') x;
+select x, pg_typeof(x) from anyctest(11, array[point(1,2)]) x; -- fail
+select x, pg_typeof(x) from anyctest(11, 12) x; -- fail
+
+drop function anyctest(anycompatible, anycompatiblearray);
+
+create function anyctest(anycompatible, anycompatiblerange)
+returns anycompatiblerange as $$
+ select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, int4range(4,7)) x;
+select x, pg_typeof(x) from anyctest(11, numrange(4,7)) x;
+select x, pg_typeof(x) from anyctest(11, 12) x; -- fail
+select x, pg_typeof(x) from anyctest(11.2, int4range(4,7)) x; -- fail
+select x, pg_typeof(x) from anyctest(11.2, '[4,7)') x; -- fail
+
+drop function anyctest(anycompatible, anycompatiblerange);
+
+create function anyctest(anycompatiblerange, anycompatiblerange)
+returns anycompatible as $$
+ select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(int4range(11,12), int4range(4,7)) x;
+select x, pg_typeof(x) from anyctest(int4range(11,12), numrange(4,7)) x; -- fail
+
+drop function anyctest(anycompatiblerange, anycompatiblerange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblerange as $$
+ select $1
+$$ language sql;
+
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+ select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, 12) x; -- fail
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x; -- fail
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x; -- fail
+
+drop function anyctest(anycompatible, anycompatiblemultirange);
+
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+ select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+ select $1
+$$ language sql;
+
+create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
+returns anycompatiblearray as $$
+ select array[$1, $2]
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, 12) x;
+select x, pg_typeof(x) from anyctest(11, 12.3) x;
+select x, pg_typeof(x) from anyctest(array[11], array[1,2]) x; -- fail
+
+drop function anyctest(anycompatiblenonarray, anycompatiblenonarray);
+
+create function anyctest(a anyelement, b anyarray,
+ c anycompatible, d anycompatible)
+returns anycompatiblearray as $$
+ select array[c, d]
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, array[1, 2], 42, 34.5) x;
+select x, pg_typeof(x) from anyctest(11, array[1, 2], point(1,2), point(3,4)) x;
+select x, pg_typeof(x) from anyctest(11, '{1,2}', point(1,2), '(3,4)') x;
+select x, pg_typeof(x) from anyctest(11, array[1, 2.2], 42, 34.5) x; -- fail
+
+drop function anyctest(a anyelement, b anyarray,
+ c anycompatible, d anycompatible);
+
+create function anyctest(variadic anycompatiblearray)
+returns anycompatiblearray as $$
+ select $1
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, 12) x;
+select x, pg_typeof(x) from anyctest(11, 12.2) x;
+select x, pg_typeof(x) from anyctest(11, '12') x;
+select x, pg_typeof(x) from anyctest(11, '12.2') x; -- fail
+select x, pg_typeof(x) from anyctest(variadic array[11, 12]) x;
+select x, pg_typeof(x) from anyctest(variadic array[11, 12.2]) x;
+
+drop function anyctest(variadic anycompatiblearray);
diff --git a/src/test/regress/sql/portals.sql b/src/test/regress/sql/portals.sql
new file mode 100644
index 0000000..5d1ef52
--- /dev/null
+++ b/src/test/regress/sql/portals.sql
@@ -0,0 +1,571 @@
+--
+-- Cursor regression tests
+--
+
+BEGIN;
+
+DECLARE foo1 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+
+DECLARE foo2 SCROLL CURSOR FOR SELECT * FROM tenk2;
+
+DECLARE foo3 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+
+DECLARE foo4 SCROLL CURSOR FOR SELECT * FROM tenk2;
+
+DECLARE foo5 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+
+DECLARE foo6 SCROLL CURSOR FOR SELECT * FROM tenk2;
+
+DECLARE foo7 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+
+DECLARE foo8 SCROLL CURSOR FOR SELECT * FROM tenk2;
+
+DECLARE foo9 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+
+DECLARE foo10 SCROLL CURSOR FOR SELECT * FROM tenk2;
+
+DECLARE foo11 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+
+DECLARE foo12 SCROLL CURSOR FOR SELECT * FROM tenk2;
+
+DECLARE foo13 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+
+DECLARE foo14 SCROLL CURSOR FOR SELECT * FROM tenk2;
+
+DECLARE foo15 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+
+DECLARE foo16 SCROLL CURSOR FOR SELECT * FROM tenk2;
+
+DECLARE foo17 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+
+DECLARE foo18 SCROLL CURSOR FOR SELECT * FROM tenk2;
+
+DECLARE foo19 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+
+DECLARE foo20 SCROLL CURSOR FOR SELECT * FROM tenk2;
+
+DECLARE foo21 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+
+DECLARE foo22 SCROLL CURSOR FOR SELECT * FROM tenk2;
+
+DECLARE foo23 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2;
+
+FETCH 1 in foo1;
+
+FETCH 2 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 (don't 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;
+
+FETCH FROM foo25;
+
+FETCH FROM foo25;
+
+COMMIT;
+
+FETCH FROM foo25;
+
+FETCH BACKWARD FROM foo25;
+
+FETCH ABSOLUTE -1 FROM foo25;
+
+SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors;
+
+CLOSE foo25;
+
+BEGIN;
+
+DECLARE foo25ns NO SCROLL CURSOR WITH HOLD FOR SELECT * FROM tenk2;
+
+FETCH FROM foo25ns;
+
+FETCH FROM foo25ns;
+
+COMMIT;
+
+FETCH FROM foo25ns;
+
+FETCH ABSOLUTE 4 FROM foo25ns;
+
+FETCH ABSOLUTE 4 FROM foo25ns; -- fail
+
+SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors;
+
+CLOSE foo25ns;
+
+--
+-- 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;
+
+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;
+
+-- Check DELETE WHERE CURRENT
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+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;
+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;
+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;
+--- FOR UPDATE cursors can't currently scroll back, so this is an error:
+FETCH RELATIVE 0 FROM c1;
+ROLLBACK;
+SELECT * FROM uctest;
+
+-- Check insensitive cursor with INSERT
+-- (The above tests don't test the SQL notion of an insensitive cursor
+-- correctly, because per SQL standard, changes from WHERE CURRENT OF
+-- commands should be visible in the cursor. So here we make the
+-- changes with a command that is independent of the cursor.)
+BEGIN;
+DECLARE c1 INSENSITIVE CURSOR FOR SELECT * FROM uctest;
+INSERT INTO uctest VALUES (10, 'ten');
+FETCH NEXT FROM c1;
+FETCH NEXT FROM c1;
+FETCH NEXT FROM c1; -- insert not visible
+COMMIT;
+SELECT * FROM uctest;
+DELETE FROM uctest WHERE f1 = 10; -- restore test table state
+
+-- 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;
+FETCH 1 FROM c1;
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+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;
+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;
+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;
+
+-- 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 *;
+
+SELECT * FROM current_check;
+ROLLBACK;
+
+-- Make sure snapshot management works okay, per bug report in
+-- 235395b90909301035v7228ce63q392931f15aa74b31@mail.gmail.com
+BEGIN;
+SET 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;
+UPDATE cursor SET a = 2;
+FETCH ALL FROM c1;
+COMMIT;
+DROP TABLE cursor;
+
+-- 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/portals_p2.sql b/src/test/regress/sql/portals_p2.sql
new file mode 100644
index 0000000..555820d
--- /dev/null
+++ b/src/test/regress/sql/portals_p2.sql
@@ -0,0 +1,98 @@
+--
+-- PORTALS_P2
+--
+
+BEGIN;
+
+DECLARE foo13 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 50;
+
+DECLARE foo14 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 51;
+
+DECLARE foo15 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 52;
+
+DECLARE foo16 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 53;
+
+DECLARE foo17 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 54;
+
+DECLARE foo18 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 55;
+
+DECLARE foo19 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 56;
+
+DECLARE foo20 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 57;
+
+DECLARE foo21 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 58;
+
+DECLARE foo22 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 59;
+
+DECLARE foo23 CURSOR FOR
+ SELECT * FROM onek WHERE unique1 = 60;
+
+DECLARE foo24 CURSOR FOR
+ SELECT * FROM onek2 WHERE unique1 = 50;
+
+DECLARE foo25 CURSOR FOR
+ SELECT * FROM onek2 WHERE unique1 = 60;
+
+FETCH all in foo13;
+
+FETCH all in foo14;
+
+FETCH all in foo15;
+
+FETCH all in foo16;
+
+FETCH all in foo17;
+
+FETCH all in foo18;
+
+FETCH all in foo19;
+
+FETCH all in foo20;
+
+FETCH all in foo21;
+
+FETCH all in foo22;
+
+FETCH all in foo23;
+
+FETCH all in foo24;
+
+FETCH all in foo25;
+
+CLOSE foo13;
+
+CLOSE foo14;
+
+CLOSE foo15;
+
+CLOSE foo16;
+
+CLOSE foo17;
+
+CLOSE foo18;
+
+CLOSE foo19;
+
+CLOSE foo20;
+
+CLOSE foo21;
+
+CLOSE foo22;
+
+CLOSE foo23;
+
+CLOSE foo24;
+
+CLOSE foo25;
+
+END;
diff --git a/src/test/regress/sql/prepare.sql b/src/test/regress/sql/prepare.sql
new file mode 100644
index 0000000..985d0f0
--- /dev/null
+++ b/src/test/regress/sql/prepare.sql
@@ -0,0 +1,80 @@
+-- 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');
+SELECT * FROM q5_prep_results;
+CREATE TEMPORARY TABLE q5_prep_nodata AS EXECUTE q5(200, 'DTAAAA')
+ WITH NO DATA;
+SELECT * FROM q5_prep_nodata;
+
+-- 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/prepared_xacts.sql b/src/test/regress/sql/prepared_xacts.sql
new file mode 100644
index 0000000..2f0bb55
--- /dev/null
+++ b/src/test/regress/sql/prepared_xacts.sql
@@ -0,0 +1,164 @@
+--
+-- PREPARED TRANSACTIONS (two-phase commit)
+--
+-- We can't readily test persistence of prepared xacts within the
+-- regression script framework, unfortunately. Note that a crash
+-- isn't really needed ... stopping and starting the postmaster would
+-- be enough, but we can't even do that here.
+
+
+-- create a simple table that we'll use in the tests
+CREATE TABLE pxtest1 (foobar VARCHAR(10));
+
+INSERT INTO pxtest1 VALUES ('aaa');
+
+
+-- Test PREPARE TRANSACTION
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+UPDATE pxtest1 SET foobar = 'bbb' WHERE foobar = 'aaa';
+SELECT * FROM pxtest1;
+PREPARE TRANSACTION 'foo1';
+
+SELECT * FROM pxtest1;
+
+-- Test pg_prepared_xacts system view
+SELECT gid FROM pg_prepared_xacts;
+
+-- Test ROLLBACK PREPARED
+ROLLBACK PREPARED 'foo1';
+
+SELECT * FROM pxtest1;
+
+SELECT gid FROM pg_prepared_xacts;
+
+
+-- Test COMMIT PREPARED
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+INSERT INTO pxtest1 VALUES ('ddd');
+SELECT * FROM pxtest1;
+PREPARE TRANSACTION 'foo2';
+
+SELECT * FROM pxtest1;
+
+COMMIT PREPARED 'foo2';
+
+SELECT * FROM pxtest1;
+
+-- Test duplicate gids
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd';
+SELECT * FROM pxtest1;
+PREPARE TRANSACTION 'foo3';
+
+SELECT gid FROM pg_prepared_xacts;
+
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+INSERT INTO pxtest1 VALUES ('fff');
+
+-- This should fail, because the gid foo3 is already in use
+PREPARE TRANSACTION 'foo3';
+
+SELECT * FROM pxtest1;
+
+ROLLBACK PREPARED 'foo3';
+
+SELECT * FROM pxtest1;
+
+-- Test serialization failure (SSI)
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd';
+SELECT * FROM pxtest1;
+PREPARE TRANSACTION 'foo4';
+
+SELECT gid FROM pg_prepared_xacts;
+
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+SELECT * FROM pxtest1;
+
+-- This should fail, because the two transactions have a write-skew anomaly
+INSERT INTO pxtest1 VALUES ('fff');
+PREPARE TRANSACTION 'foo5';
+
+SELECT gid FROM pg_prepared_xacts;
+
+ROLLBACK PREPARED 'foo4';
+
+SELECT gid FROM pg_prepared_xacts;
+
+-- Clean up
+DROP TABLE pxtest1;
+
+-- Test detection of session-level and xact-level locks on same object
+BEGIN;
+SELECT pg_advisory_lock(1);
+SELECT pg_advisory_xact_lock_shared(1);
+PREPARE TRANSACTION 'foo6'; -- fails
+
+-- Test subtransactions
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+ CREATE TABLE pxtest2 (a int);
+ INSERT INTO pxtest2 VALUES (1);
+ SAVEPOINT a;
+ INSERT INTO pxtest2 VALUES (2);
+ ROLLBACK TO a;
+ SAVEPOINT b;
+ INSERT INTO pxtest2 VALUES (3);
+PREPARE TRANSACTION 'regress-one';
+
+CREATE TABLE pxtest3(fff int);
+
+-- Test shared invalidation
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+ DROP TABLE pxtest3;
+ CREATE TABLE pxtest4 (a int);
+ INSERT INTO pxtest4 VALUES (1);
+ INSERT INTO pxtest4 VALUES (2);
+ DECLARE foo CURSOR FOR SELECT * FROM pxtest4;
+ -- Fetch 1 tuple, keeping the cursor open
+ FETCH 1 FROM foo;
+PREPARE TRANSACTION 'regress-two';
+
+-- No such cursor
+FETCH 1 FROM foo;
+
+-- Table doesn't exist, the creation hasn't been committed yet
+SELECT * FROM pxtest2;
+
+-- There should be two prepared transactions
+SELECT gid FROM pg_prepared_xacts;
+
+-- pxtest3 should be locked because of the pending DROP
+begin;
+lock table pxtest3 in access share mode nowait;
+rollback;
+
+-- Disconnect, we will continue testing in a different backend
+\c -
+
+-- There should still be two prepared transactions
+SELECT gid FROM pg_prepared_xacts;
+
+-- pxtest3 should still be locked because of the pending DROP
+begin;
+lock table pxtest3 in access share mode nowait;
+rollback;
+
+-- Commit table creation
+COMMIT PREPARED 'regress-one';
+\d pxtest2
+SELECT * FROM pxtest2;
+
+-- There should be one prepared transaction
+SELECT gid FROM pg_prepared_xacts;
+
+-- Commit table drop
+COMMIT PREPARED 'regress-two';
+SELECT * FROM pxtest3;
+
+-- There should be no prepared transactions
+SELECT gid FROM pg_prepared_xacts;
+
+-- Clean up
+DROP TABLE pxtest2;
+DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled
+DROP TABLE pxtest4;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
new file mode 100644
index 0000000..af05f95
--- /dev/null
+++ b/src/test/regress/sql/privileges.sql
@@ -0,0 +1,1550 @@
+--
+-- 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;
+
+SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid;
+
+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 ROLE regress_priv_role;
+
+GRANT pg_read_all_data TO regress_priv_user6;
+GRANT pg_write_all_data TO regress_priv_user7;
+
+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;
+
+-- 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;
+
+-- 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;
+
+-- 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;
+
+-- 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;
+
+-- 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;
+
+-- 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
+
+BEGIN;
+RESET SESSION AUTHORIZATION;
+ALTER ROLE regress_priv_user1 NOINHERIT;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3;
+ROLLBACK;
+
+-- 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
+
+-- 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
+BEGIN;
+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
+ROLLBACK;
+
+-- 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)));
+SELECT brin_desummarize_range('sro_brin', 0);
+SELECT brin_summarize_range('sro_brin', 0);
+DROP TABLE sro_tab;
+-- 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();
+-- Now, REFRESH will issue such an INSERT, queueing the GRANT
+CREATE OR REPLACE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS
+ 'INSERT INTO sro_trojan_table DEFAULT VALUES; SELECT true';
+REFRESH MATERIALIZED VIEW sro_mv;
+\c -
+REFRESH MATERIALIZED VIEW sro_mv;
+BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
+
+-- REFRESH MATERIALIZED VIEW CONCURRENTLY use of eval_const_expressions()
+SET SESSION AUTHORIZATION regress_sro_user;
+CREATE FUNCTION unwanted_grant_nofail(int) RETURNS int
+ IMMUTABLE LANGUAGE plpgsql AS $$
+BEGIN
+ PERFORM unwanted_grant();
+ RAISE WARNING 'owned';
+ RETURN 1;
+EXCEPTION WHEN OTHERS THEN
+ RETURN 2;
+END$$;
+CREATE MATERIALIZED VIEW sro_index_mv AS SELECT 1 AS c;
+CREATE UNIQUE INDEX ON sro_index_mv (c) WHERE unwanted_grant_nofail(1) > 0;
+\c -
+REFRESH MATERIALIZED VIEW CONCURRENTLY sro_index_mv;
+REFRESH MATERIALIZED VIEW sro_index_mv;
+
+DROP OWNED BY regress_sro_user;
+DROP ROLE regress_sro_user;
+
+
+-- 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; -- ok: a role can self-admin
+CREATE FUNCTION dogrant_fails() RETURNS void LANGUAGE sql SECURITY DEFINER AS
+ 'GRANT regress_priv_group2 TO regress_priv_user5';
+SELECT dogrant_fails(); -- fails: no self-admin in SECURITY DEFINER
+DROP FUNCTION dogrant_fails();
+
+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;
+
+SELECT lo_create(1001);
+SELECT lo_create(1002);
+SELECT lo_create(1003);
+SELECT lo_create(1004);
+SELECT lo_create(1005);
+
+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;
+
+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');
+
+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;
+
+SELECT lo_unlink(1001); -- to be denied
+SELECT lo_unlink(2002);
+
+\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;
+
+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);
+
+-- compatibility mode in largeobject permission
+\c -
+SET lo_compat_privileges = false; -- default setting
+SET SESSION AUTHORIZATION regress_priv_user4;
+
+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
+
+\c -
+SET lo_compat_privileges = true; -- compatibility mode
+SET SESSION AUTHORIZATION regress_priv_user4;
+
+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
+
+-- 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
+
+-- 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;
+
+BEGIN;
+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;
+SAVEPOINT q; DELETE FROM datdba_only; ROLLBACK TO q;
+SET SESSION AUTHORIZATION regress_priv_user2;
+TABLE information_schema.enabled_roles;
+INSERT INTO datdba_only DEFAULT VALUES;
+ROLLBACK;
+
+-- 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.
+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
+
+DROP SCHEMA testns CASCADE;
+
+
+-- 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;
+DROP SCHEMA testns CASCADE;
+
+-- 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;
+
+SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid;
+
+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;
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
new file mode 100644
index 0000000..306c1d5
--- /dev/null
+++ b/src/test/regress/sql/psql.sql
@@ -0,0 +1,1485 @@
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+
+-- \set
+
+-- fail: invalid name
+\set invalid/name foo
+-- fail: invalid value for special variable
+\set AUTOCOMMIT foo
+\set FETCH_COUNT foo
+-- check handling of built-in boolean variable
+\echo :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK
+\echo :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK foo
+\echo :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK on
+\echo :ON_ERROR_ROLLBACK
+\unset ON_ERROR_ROLLBACK
+\echo :ON_ERROR_ROLLBACK
+
+-- \g and \gx
+
+SELECT 1 as one, 2 as two \g
+\gx
+SELECT 3 as three, 4 as four \gx
+\g
+
+-- \gx should work in FETCH_COUNT mode too
+\set FETCH_COUNT 1
+
+SELECT 1 as one, 2 as two \g
+\gx
+SELECT 3 as three, 4 as four \gx
+\g
+
+\unset FETCH_COUNT
+
+-- \g/\gx with pset options
+
+SELECT 1 as one, 2 as two \g (format=csv csv_fieldsep='\t')
+\g
+SELECT 1 as one, 2 as two \gx (title='foo bar')
+\g
+
+-- \gset
+
+select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_
+
+\echo :pref01_test01 :pref01_test02 :pref01_test03
+
+-- should fail: bad variable name
+select 10 as "bad name"
+\gset
+
+select 97 as "EOF", 'ok' as _foo \gset IGNORE
+\echo :IGNORE_foo :IGNOREEOF
+
+-- multiple backslash commands in one line
+select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+select 5 as x, 6 as y \gset pref01_ \\ \g \echo :pref01_x :pref01_y
+select 7 as x, 8 as y \g \gset pref01_ \echo :pref01_x :pref01_y
+
+-- NULL should unset the variable
+\set var2 xyz
+select 1 as var1, NULL as var2, 3 as var3 \gset
+\echo :var1 :var2 :var3
+
+-- \gset requires just one tuple
+select 10 as test01, 20 as test02 from generate_series(1,3) \gset
+select 10 as test01, 20 as test02 from generate_series(1,0) \gset
+
+-- \gset should work in FETCH_COUNT mode too
+\set FETCH_COUNT 1
+
+select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+select 10 as test01, 20 as test02 from generate_series(1,3) \gset
+select 10 as test01, 20 as test02 from generate_series(1,0) \gset
+
+\unset FETCH_COUNT
+
+-- \gdesc
+
+SELECT
+ NULL AS zero,
+ 1 AS one,
+ 2.0 AS two,
+ 'three' AS three,
+ $1 AS four,
+ sin($2) as five,
+ 'foo'::varchar(4) as six,
+ CURRENT_DATE AS now
+\gdesc
+
+-- should work with tuple-returning utilities, such as EXECUTE
+PREPARE test AS SELECT 1 AS first, 2 AS second;
+EXECUTE test \gdesc
+EXPLAIN EXECUTE test \gdesc
+
+-- should fail cleanly - syntax error
+SELECT 1 + \gdesc
+
+-- check behavior with empty results
+SELECT \gdesc
+CREATE TABLE bububu(a int) \gdesc
+
+-- subject command should not have executed
+TABLE bububu; -- fail
+
+-- query buffer should remain unchanged
+SELECT 1 AS x, 'Hello', 2 AS y, true AS "dirty\name"
+\gdesc
+\g
+
+-- all on one line
+SELECT 3 AS x, 'Hello', 4 AS y, true AS "dirty\name" \gdesc \g
+
+-- \gexec
+
+create temporary table gexec_test(a int, b text, c date, d float);
+select format('create index on gexec_test(%I)', attname)
+from pg_attribute
+where attrelid = 'gexec_test'::regclass and attnum > 0
+order by attnum
+\gexec
+
+-- \gexec should work in FETCH_COUNT mode too
+-- (though the fetch limit applies to the executed queries not the meta query)
+\set FETCH_COUNT 1
+
+select 'select 1 as ones', 'select x.y, x.y*2 as double from generate_series(1,4) as x(y)'
+union all
+select 'drop table gexec_test', NULL
+union all
+select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
+\gexec
+
+\unset FETCH_COUNT
+
+-- show all pset options
+\pset
+
+-- test multi-line headers, wrapping, and newline indicators
+-- in aligned, unaligned, and wrapped formats
+prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
+
+c", array_to_string(array_agg(repeat('y',20-2*n)),E'\n') as "a
+bc" from generate_series(1,10) as n(n) group by n>1 order by n>1;
+
+\pset linestyle ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 20
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset linestyle old-ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 20
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+deallocate q;
+
+-- test single-line header and data
+prepare q as select repeat('x',2*n) as "0123456789abcdef", repeat('y',20-2*n) as "0123456789" from generate_series(1,10) as n;
+
+\pset linestyle ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 30
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 20
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset linestyle old-ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+deallocate q;
+
+\pset linestyle ascii
+\pset border 1
+
+-- support table for output-format tests (useful to create a footer)
+
+create table psql_serial_tab (id serial);
+
+-- test header/footer/tuples_only behavior in aligned/unaligned/wrapped cases
+
+\pset format aligned
+
+\pset expanded off
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+-- empty table is a special case for this format
+select 1 where false;
+
+\pset format unaligned
+
+\pset expanded off
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+
+\pset format wrapped
+
+\pset expanded off
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+
+-- check conditional am display
+\pset expanded off
+
+CREATE SCHEMA tableam_display;
+CREATE ROLE regress_display_role;
+ALTER SCHEMA tableam_display OWNER TO regress_display_role;
+SET search_path TO tableam_display;
+CREATE ACCESS METHOD heap_psql TYPE TABLE HANDLER heap_tableam_handler;
+SET ROLE TO regress_display_role;
+-- Use only relations with a physical size of zero.
+CREATE TABLE tbl_heap_psql(f1 int, f2 char(100)) using heap_psql;
+CREATE TABLE tbl_heap(f1 int, f2 char(100)) using heap;
+CREATE VIEW view_heap_psql AS SELECT f1 from tbl_heap_psql;
+CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tbl_heap_psql;
+\d+ tbl_heap_psql
+\d+ tbl_heap
+\set HIDE_TABLEAM off
+\d+ tbl_heap_psql
+\d+ tbl_heap
+-- AM is displayed for tables, indexes and materialized views.
+\d+
+\dt+
+\dm+
+-- But not for views and sequences.
+\dv+
+\set HIDE_TABLEAM on
+\d+
+RESET ROLE;
+RESET search_path;
+DROP SCHEMA tableam_display CASCADE;
+DROP ACCESS METHOD heap_psql;
+DROP ROLE regress_display_role;
+
+-- test numericlocale (as best we can without control of psql's locale)
+
+\pset format aligned
+\pset expanded off
+\pset numericlocale true
+
+select n, -n as m, n * 111 as x, '1e90'::float8 as f
+from generate_series(0,3) n;
+
+\pset numericlocale false
+
+-- test asciidoc output format
+
+\pset format asciidoc
+
+\pset border 1
+\pset expanded off
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+
+prepare q as
+ select 'some|text' as "a|title", ' ' as "empty ", n as int
+ from generate_series(1,2) as n;
+
+\pset expanded off
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+\pset expanded on
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+deallocate q;
+
+-- test csv output format
+
+\pset format csv
+
+\pset border 1
+\pset expanded off
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+
+prepare q as
+ select 'some"text' as "a""title", E' <foo>\n<bar>' as "junk",
+ ' ' as "empty", n as int
+ from generate_series(1,2) as n;
+
+\pset expanded off
+execute q;
+
+\pset expanded on
+execute q;
+
+deallocate q;
+
+-- special cases
+\pset expanded off
+select 'comma,comma' as comma, 'semi;semi' as semi;
+\pset csv_fieldsep ';'
+select 'comma,comma' as comma, 'semi;semi' as semi;
+select '\.' as data;
+\pset csv_fieldsep '.'
+select '\' as d1, '' as d2;
+
+-- illegal csv separators
+\pset csv_fieldsep ''
+\pset csv_fieldsep '\0'
+\pset csv_fieldsep '\n'
+\pset csv_fieldsep '\r'
+\pset csv_fieldsep '"'
+\pset csv_fieldsep ',,'
+
+\pset csv_fieldsep ','
+
+-- test html output format
+
+\pset format html
+
+\pset border 1
+\pset expanded off
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+
+prepare q as
+ select 'some"text' as "a&title", E' <foo>\n<bar>' as "junk",
+ ' ' as "empty", n as int
+ from generate_series(1,2) as n;
+
+\pset expanded off
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset tableattr foobar
+execute q;
+\pset tableattr
+
+\pset expanded on
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset tableattr foobar
+execute q;
+\pset tableattr
+
+deallocate q;
+
+-- test latex output format
+
+\pset format latex
+
+\pset border 1
+\pset expanded off
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+
+prepare q as
+ select 'some\more_text' as "a$title", E' #<foo>%&^~|\n{bar}' as "junk",
+ ' ' as "empty", n as int
+ from generate_series(1,2) as n;
+
+\pset expanded off
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+\pset border 3
+execute q;
+
+\pset expanded on
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+\pset border 3
+execute q;
+
+deallocate q;
+
+-- test latex-longtable output format
+
+\pset format latex-longtable
+
+\pset border 1
+\pset expanded off
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+
+prepare q as
+ select 'some\more_text' as "a$title", E' #<foo>%&^~|\n{bar}' as "junk",
+ ' ' as "empty", n as int
+ from generate_series(1,2) as n;
+
+\pset expanded off
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+\pset border 3
+execute q;
+
+\pset tableattr lr
+execute q;
+\pset tableattr
+
+\pset expanded on
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+\pset border 3
+execute q;
+
+\pset tableattr lr
+execute q;
+\pset tableattr
+
+deallocate q;
+
+-- test troff-ms output format
+
+\pset format troff-ms
+
+\pset border 1
+\pset expanded off
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+\pset expanded on
+\d psql_serial_tab_id_seq
+\pset tuples_only true
+\df exp
+\pset tuples_only false
+
+prepare q as
+ select 'some\text' as "a\title", E' <foo>\n<bar>' as "junk",
+ ' ' as "empty", n as int
+ from generate_series(1,2) as n;
+
+\pset expanded off
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+\pset expanded on
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+deallocate q;
+
+-- check ambiguous format requests
+
+\pset format a
+\pset format l
+
+-- clean up after output format tests
+
+drop table psql_serial_tab;
+
+\pset format aligned
+\pset expanded off
+\pset border 1
+
+-- \echo and allied features
+
+\echo this is a test
+\echo -n without newline
+\echo with -n newline
+\echo '-n' with newline
+
+\set foo bar
+\echo foo = :foo
+
+\qecho this is a test
+\qecho foo = :foo
+
+\warn this is a test
+\warn foo = :foo
+
+-- tests for \if ... \endif
+
+\if true
+ select 'okay';
+ select 'still okay';
+\else
+ not okay;
+ still not okay
+\endif
+
+-- at this point query buffer should still have last valid line
+\g
+
+-- \if should work okay on part of a query
+select
+ \if true
+ 42
+ \else
+ (bogus
+ \endif
+ forty_two;
+
+select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
+
+-- test a large nested if using a variety of true-equivalents
+\if true
+ \if 1
+ \if yes
+ \if on
+ \echo 'all true'
+ \else
+ \echo 'should not print #1-1'
+ \endif
+ \else
+ \echo 'should not print #1-2'
+ \endif
+ \else
+ \echo 'should not print #1-3'
+ \endif
+\else
+ \echo 'should not print #1-4'
+\endif
+
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+ \echo 'should not print #2-1'
+\elif 0
+ \echo 'should not print #2-2'
+\elif no
+ \echo 'should not print #2-3'
+\elif off
+ \echo 'should not print #2-4'
+\else
+ \echo 'all false'
+\endif
+
+-- test true-false elif after initial true branch
+\if true
+ \echo 'should print #2-5'
+\elif true
+ \echo 'should not print #2-6'
+\elif false
+ \echo 'should not print #2-7'
+\else
+ \echo 'should not print #2-8'
+\endif
+
+-- test simple true-then-else
+\if true
+ \echo 'first thing true'
+\else
+ \echo 'should not print #3-1'
+\endif
+
+-- test simple false-true-else
+\if false
+ \echo 'should not print #4-1'
+\elif true
+ \echo 'second thing true'
+\else
+ \echo 'should not print #5-1'
+\endif
+
+-- invalid boolean expressions are false
+\if invalid boolean expression
+ \echo 'will not print #6-1'
+\else
+ \echo 'will print anyway #6-2'
+\endif
+
+-- test un-matched endif
+\endif
+
+-- test un-matched else
+\else
+
+-- test un-matched elif
+\elif
+
+-- test double-else error
+\if true
+\else
+\else
+\endif
+
+-- test elif out-of-order
+\if false
+\else
+\elif
+\endif
+
+-- test if-endif matching in a false branch
+\if false
+ \if false
+ \echo 'should not print #7-1'
+ \else
+ \echo 'should not print #7-2'
+ \endif
+ \echo 'should not print #7-3'
+\else
+ \echo 'should print #7-4'
+\endif
+
+-- show that vars and backticks are not expanded when ignoring extra args
+\set foo bar
+\echo :foo :'foo' :"foo"
+\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
+
+-- show that vars and backticks are not expanded and commands are ignored
+-- when in a false if-branch
+\set try_to_quit '\\q'
+\if false
+ :try_to_quit
+ \echo `nosuchcommand` :foo :'foo' :"foo"
+ \pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
+ \a
+ \C arg1
+ \c arg1 arg2 arg3 arg4
+ \cd arg1
+ \conninfo
+ \copy arg1 arg2 arg3 arg4 arg5 arg6
+ \copyright
+ SELECT 1 as one, 2, 3 \crosstabview
+ \dt arg1
+ \e arg1 arg2
+ \ef whole_line
+ \ev whole_line
+ \echo arg1 arg2 arg3 arg4 arg5
+ \echo arg1
+ \encoding arg1
+ \errverbose
+ \f arg1
+ \g arg1
+ \gx arg1
+ \gexec
+ SELECT 1 AS one \gset
+ \h
+ \?
+ \html
+ \i arg1
+ \ir arg1
+ \l arg1
+ \lo arg1 arg2
+ \lo_list
+ \o arg1
+ \p
+ \password arg1
+ \prompt arg1 arg2
+ \pset arg1 arg2
+ \q
+ \reset
+ \s arg1
+ \set arg1 arg2 arg3 arg4 arg5 arg6 arg7
+ \setenv arg1 arg2
+ \sf whole_line
+ \sv whole_line
+ \t arg1
+ \T arg1
+ \timing arg1
+ \unset arg1
+ \w arg1
+ \watch arg1
+ \x arg1
+ -- \else here is eaten as part of OT_FILEPIPE argument
+ \w |/no/such/file \else
+ -- \endif here is eaten as part of whole-line argument
+ \! whole_line \endif
+ \z
+\else
+ \echo 'should print #8-1'
+\endif
+
+-- :{?...} defined variable test
+\set i 1
+\if :{?i}
+ \echo '#9-1 ok, variable i is defined'
+\else
+ \echo 'should not print #9-2'
+\endif
+
+\if :{?no_such_variable}
+ \echo 'should not print #10-1'
+\else
+ \echo '#10-2 ok, variable no_such_variable is not defined'
+\endif
+
+SELECT :{?i} AS i_is_defined;
+
+SELECT NOT :{?no_such_var} AS no_such_var_is_not_defined;
+
+-- SHOW_CONTEXT
+
+\set SHOW_CONTEXT never
+do $$
+begin
+ raise notice 'foo';
+ raise exception 'bar';
+end $$;
+
+\set SHOW_CONTEXT errors
+do $$
+begin
+ raise notice 'foo';
+ raise exception 'bar';
+end $$;
+
+\set SHOW_CONTEXT always
+do $$
+begin
+ raise notice 'foo';
+ raise exception 'bar';
+end $$;
+
+-- test printing and clearing the query buffer
+SELECT 1;
+\p
+SELECT 2 \r
+\p
+SELECT 3 \p
+UNION SELECT 4 \p
+UNION SELECT 5
+ORDER BY 1;
+\r
+\p
+
+-- tests for special result variables
+
+-- working query, 2 rows selected
+SELECT 1 AS stuff UNION SELECT 2;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+
+-- syntax error
+SELECT 1 UNION;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+
+-- empty query
+;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+-- must have kept previous values
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+
+-- other query error
+DROP TABLE this_table_does_not_exist;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+
+-- nondefault verbosity error settings (except verbose, which is too unstable)
+\set VERBOSITY terse
+SELECT 1 UNION;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+
+\set VERBOSITY sqlstate
+SELECT 1/0;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+
+\set VERBOSITY default
+
+-- working \gdesc
+SELECT 3 AS three, 4 AS four \gdesc
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+
+-- \gdesc with an error
+SELECT 4 AS \gdesc
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+
+-- check row count for a cursor-fetched query
+\set FETCH_COUNT 10
+select unique2 from tenk1 order by unique2 limit 19;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+
+-- cursor-fetched query with an error after the first group
+select 1/(15-unique2) from tenk1 order by unique2 limit 19;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+
+\unset FETCH_COUNT
+
+create schema testpart;
+create role regress_partitioning_role;
+
+alter schema testpart owner to regress_partitioning_role;
+
+set role to regress_partitioning_role;
+
+-- run test inside own schema and hide other partitions
+set search_path to testpart;
+
+create table testtable_apple(logdate date);
+create table testtable_orange(logdate date);
+create index testtable_apple_index on testtable_apple(logdate);
+create index testtable_orange_index on testtable_orange(logdate);
+
+create table testpart_apple(logdate date) partition by range(logdate);
+create table testpart_orange(logdate date) partition by range(logdate);
+
+create index testpart_apple_index on testpart_apple(logdate);
+create index testpart_orange_index on testpart_orange(logdate);
+
+-- only partition related object should be displayed
+\dP test*apple*
+\dPt test*apple*
+\dPi test*apple*
+
+drop table testtable_apple;
+drop table testtable_orange;
+drop table testpart_apple;
+drop table testpart_orange;
+
+create table parent_tab (id int) partition by range (id);
+create index parent_index on parent_tab (id);
+create table child_0_10 partition of parent_tab
+ for values from (0) to (10);
+create table child_10_20 partition of parent_tab
+ for values from (10) to (20);
+create table child_20_30 partition of parent_tab
+ for values from (20) to (30);
+insert into parent_tab values (generate_series(0,29));
+create table child_30_40 partition of parent_tab
+for values from (30) to (40)
+ partition by range(id);
+create table child_30_35 partition of child_30_40
+ for values from (30) to (35);
+create table child_35_40 partition of child_30_40
+ for values from (35) to (40);
+insert into parent_tab values (generate_series(30,39));
+
+\dPt
+\dPi
+
+\dP testpart.*
+\dP
+
+\dPtn
+\dPin
+\dPn
+\dPn testpart.*
+
+drop table parent_tab cascade;
+
+drop schema testpart;
+
+set search_path to default;
+
+set role to default;
+drop role regress_partitioning_role;
+
+-- \d on toast table (use pg_statistic's toast table, which has a known name)
+\d pg_toast.pg_toast_2619
+
+-- check printing info about access methods
+\dA
+\dA *
+\dA h*
+\dA foo
+\dA foo bar
+\dA+
+\dA+ *
+\dA+ h*
+\dA+ foo
+\dAc brin pg*.oid*
+\dAf spgist
+\dAf btree int4
+\dAo+ btree float_ops
+\dAo * pg_catalog.jsonb_path_ops
+\dAp+ btree float_ops
+\dAp * pg_catalog.uuid_ops
+
+-- check \df, \do with argument specifications
+\df *sqrt
+\df *sqrt num*
+\df int*pl
+\df int*pl int4
+\df int*pl * pg_catalog.int8
+\df acl* aclitem[]
+\df has_database_privilege oid text
+\df has_database_privilege oid text -
+\dfa bit* small*
+\do - pg_catalog.int4
+\do && anyarray *
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
+
+-- check that dots within quoted name segments are not counted
+\dA "no.such.access.method"
+\dt "no.such.table.relation"
+\da "no.such.aggregate.function"
+\dAc "no.such.operator.class"
+\dAf "no.such.operator.family"
+\dAo "no.such.operator.of.operator.family"
+\dAp "no.such.operator.support.function.of.operator.family"
+\db "no.such.tablespace"
+\dc "no.such.conversion"
+\dC "no.such.cast"
+\dd "no.such.object.description"
+\dD "no.such.domain"
+\ddp "no.such.default.access.privilege"
+\di "no.such.index.relation"
+\dm "no.such.materialized.view"
+\ds "no.such.relation"
+\dt "no.such.relation"
+\dv "no.such.relation"
+\des "no.such.foreign.server"
+\dew "no.such.foreign.data.wrapper"
+\df "no.such.function"
+\dF "no.such.text.search.configuration"
+\dFd "no.such.text.search.dictionary"
+\dFp "no.such.text.search.parser"
+\dFt "no.such.text.search.template"
+\dg "no.such.role"
+\dL "no.such.language"
+\dn "no.such.schema"
+\do "no.such.operator"
+\dO "no.such.collation"
+\dp "no.such.access.privilege"
+\dP "no.such.partitioned.relation"
+\drds "no.such.setting"
+\dRp "no.such.publication"
+\dRs "no.such.subscription"
+\dT "no.such.data.type"
+\dx "no.such.installed.extension"
+\dX "no.such.extended.statistics"
+\dy "no.such.event.trigger"
+
+-- again, but with dotted schema qualifications.
+\dA "no.such.schema"."no.such.access.method"
+\dt "no.such.schema"."no.such.table.relation"
+\da "no.such.schema"."no.such.aggregate.function"
+\dAc "no.such.schema"."no.such.operator.class"
+\dAf "no.such.schema"."no.such.operator.family"
+\dAo "no.such.schema"."no.such.operator.of.operator.family"
+\dAp "no.such.schema"."no.such.operator.support.function.of.operator.family"
+\db "no.such.schema"."no.such.tablespace"
+\dc "no.such.schema"."no.such.conversion"
+\dC "no.such.schema"."no.such.cast"
+\dd "no.such.schema"."no.such.object.description"
+\dD "no.such.schema"."no.such.domain"
+\ddp "no.such.schema"."no.such.default.access.privilege"
+\di "no.such.schema"."no.such.index.relation"
+\dm "no.such.schema"."no.such.materialized.view"
+\ds "no.such.schema"."no.such.relation"
+\dt "no.such.schema"."no.such.relation"
+\dv "no.such.schema"."no.such.relation"
+\des "no.such.schema"."no.such.foreign.server"
+\dew "no.such.schema"."no.such.foreign.data.wrapper"
+\df "no.such.schema"."no.such.function"
+\dF "no.such.schema"."no.such.text.search.configuration"
+\dFd "no.such.schema"."no.such.text.search.dictionary"
+\dFp "no.such.schema"."no.such.text.search.parser"
+\dFt "no.such.schema"."no.such.text.search.template"
+\dg "no.such.schema"."no.such.role"
+\dL "no.such.schema"."no.such.language"
+\do "no.such.schema"."no.such.operator"
+\dO "no.such.schema"."no.such.collation"
+\dp "no.such.schema"."no.such.access.privilege"
+\dP "no.such.schema"."no.such.partitioned.relation"
+\drds "no.such.schema"."no.such.setting"
+\dRp "no.such.schema"."no.such.publication"
+\dRs "no.such.schema"."no.such.subscription"
+\dT "no.such.schema"."no.such.data.type"
+\dx "no.such.schema"."no.such.installed.extension"
+\dX "no.such.schema"."no.such.extended.statistics"
+\dy "no.such.schema"."no.such.event.trigger"
+
+-- again, but with current database and dotted schema qualifications.
+\dt regression."no.such.schema"."no.such.table.relation"
+\da regression."no.such.schema"."no.such.aggregate.function"
+\dc regression."no.such.schema"."no.such.conversion"
+\dC regression."no.such.schema"."no.such.cast"
+\dd regression."no.such.schema"."no.such.object.description"
+\dD regression."no.such.schema"."no.such.domain"
+\di regression."no.such.schema"."no.such.index.relation"
+\dm regression."no.such.schema"."no.such.materialized.view"
+\ds regression."no.such.schema"."no.such.relation"
+\dt regression."no.such.schema"."no.such.relation"
+\dv regression."no.such.schema"."no.such.relation"
+\df regression."no.such.schema"."no.such.function"
+\dF regression."no.such.schema"."no.such.text.search.configuration"
+\dFd regression."no.such.schema"."no.such.text.search.dictionary"
+\dFp regression."no.such.schema"."no.such.text.search.parser"
+\dFt regression."no.such.schema"."no.such.text.search.template"
+\do regression."no.such.schema"."no.such.operator"
+\dO regression."no.such.schema"."no.such.collation"
+\dp regression."no.such.schema"."no.such.access.privilege"
+\dP regression."no.such.schema"."no.such.partitioned.relation"
+\dT regression."no.such.schema"."no.such.data.type"
+\dX regression."no.such.schema"."no.such.extended.statistics"
+
+-- again, but with dotted database and dotted schema qualifications.
+\dt "no.such.database"."no.such.schema"."no.such.table.relation"
+\da "no.such.database"."no.such.schema"."no.such.aggregate.function"
+\dc "no.such.database"."no.such.schema"."no.such.conversion"
+\dC "no.such.database"."no.such.schema"."no.such.cast"
+\dd "no.such.database"."no.such.schema"."no.such.object.description"
+\dD "no.such.database"."no.such.schema"."no.such.domain"
+\ddp "no.such.database"."no.such.schema"."no.such.default.access.privilege"
+\di "no.such.database"."no.such.schema"."no.such.index.relation"
+\dm "no.such.database"."no.such.schema"."no.such.materialized.view"
+\ds "no.such.database"."no.such.schema"."no.such.relation"
+\dt "no.such.database"."no.such.schema"."no.such.relation"
+\dv "no.such.database"."no.such.schema"."no.such.relation"
+\df "no.such.database"."no.such.schema"."no.such.function"
+\dF "no.such.database"."no.such.schema"."no.such.text.search.configuration"
+\dFd "no.such.database"."no.such.schema"."no.such.text.search.dictionary"
+\dFp "no.such.database"."no.such.schema"."no.such.text.search.parser"
+\dFt "no.such.database"."no.such.schema"."no.such.text.search.template"
+\do "no.such.database"."no.such.schema"."no.such.operator"
+\dO "no.such.database"."no.such.schema"."no.such.collation"
+\dp "no.such.database"."no.such.schema"."no.such.access.privilege"
+\dP "no.such.database"."no.such.schema"."no.such.partitioned.relation"
+\dT "no.such.database"."no.such.schema"."no.such.data.type"
+\dX "no.such.database"."no.such.schema"."no.such.extended.statistics"
diff --git a/src/test/regress/sql/psql_crosstab.sql b/src/test/regress/sql/psql_crosstab.sql
new file mode 100644
index 0000000..5a45113
--- /dev/null
+++ b/src/test/regress/sql/psql_crosstab.sql
@@ -0,0 +1,124 @@
+--
+-- \crosstabview
+--
+
+CREATE TABLE ctv_data (v, h, c, i, d) AS
+VALUES
+ ('v1','h2','foo', 3, '2015-04-01'::date),
+ ('v2','h1','bar', 3, '2015-01-02'),
+ ('v1','h0','baz', NULL, '2015-07-12'),
+ ('v0','h4','qux', 4, '2015-07-15'),
+ ('v0','h4','dbl', -3, '2014-12-15'),
+ ('v0',NULL,'qux', 5, '2014-07-15'),
+ ('v1','h2','quux',7, '2015-04-04');
+
+-- make plans more stable
+ANALYZE ctv_data;
+
+-- running \crosstabview after query uses query in buffer
+SELECT v, EXTRACT(year FROM d), count(*)
+ FROM ctv_data
+ GROUP BY 1, 2
+ ORDER BY 1, 2;
+-- basic usage with 3 columns
+ \crosstabview
+
+-- ordered months in horizontal header, quoted column name
+SELECT v, to_char(d, 'Mon') AS "month name", EXTRACT(month FROM d) AS num,
+ count(*) FROM ctv_data GROUP BY 1,2,3 ORDER BY 1
+ \crosstabview v "month name" 4 num
+
+-- ordered months in vertical header, ordered years in horizontal header
+SELECT EXTRACT(year FROM d) AS year, to_char(d,'Mon') AS """month"" name",
+ EXTRACT(month FROM d) AS month,
+ format('sum=%s avg=%s', sum(i), avg(i)::numeric(2,1))
+ FROM ctv_data
+ GROUP BY EXTRACT(year FROM d), to_char(d,'Mon'), EXTRACT(month FROM d)
+ORDER BY month
+\crosstabview """month"" name" year format year
+
+-- combine contents vertically into the same cell (V/H duplicates)
+SELECT v, h, string_agg(c, E'\n') FROM ctv_data GROUP BY v, h ORDER BY 1,2,3
+ \crosstabview 1 2 3
+
+-- horizontal ASC order from window function
+SELECT v,h, string_agg(c, E'\n') AS c, row_number() OVER(ORDER BY h) AS r
+FROM ctv_data GROUP BY v, h ORDER BY 1,3,2
+ \crosstabview v h c r
+
+-- horizontal DESC order from window function
+SELECT v, h, string_agg(c, E'\n') AS c, row_number() OVER(ORDER BY h DESC) AS r
+FROM ctv_data GROUP BY v, h ORDER BY 1,3,2
+ \crosstabview v h c r
+
+-- horizontal ASC order from window function, NULLs pushed rightmost
+SELECT v,h, string_agg(c, E'\n') AS c, row_number() OVER(ORDER BY h NULLS LAST) AS r
+FROM ctv_data GROUP BY v, h ORDER BY 1,3,2
+ \crosstabview v h c r
+
+-- only null, no column name, 2 columns: error
+SELECT null,null \crosstabview
+
+-- only null, no column name, 3 columns: works
+SELECT null,null,null \crosstabview
+
+-- null display
+\pset null '#null#'
+SELECT v,h, string_agg(i::text, E'\n') AS i FROM ctv_data
+GROUP BY v, h ORDER BY h,v
+ \crosstabview v h i
+\pset null ''
+
+-- refer to columns by position
+SELECT v,h,string_agg(i::text, E'\n'), string_agg(c, E'\n')
+FROM ctv_data GROUP BY v, h ORDER BY h,v
+ \crosstabview 2 1 4
+
+-- refer to columns by positions and names mixed
+SELECT v,h, string_agg(i::text, E'\n') AS i, string_agg(c, E'\n') AS c
+FROM ctv_data GROUP BY v, h ORDER BY h,v
+ \crosstabview 1 "h" 4
+
+-- refer to columns by quoted names, check downcasing of unquoted name
+SELECT 1 as "22", 2 as b, 3 as "Foo"
+ \crosstabview "22" B "Foo"
+
+-- error: bad column name
+SELECT v,h,c,i FROM ctv_data
+ \crosstabview v h j
+
+-- error: need to quote name
+SELECT 1 as "22", 2 as b, 3 as "Foo"
+ \crosstabview 1 2 Foo
+
+-- error: need to not quote name
+SELECT 1 as "22", 2 as b, 3 as "Foo"
+ \crosstabview 1 "B" "Foo"
+
+-- error: bad column number
+SELECT v,h,i,c FROM ctv_data
+ \crosstabview 2 1 5
+
+-- error: same H and V columns
+SELECT v,h,i,c FROM ctv_data
+ \crosstabview 2 h 4
+
+-- error: too many columns
+SELECT a,a,1 FROM generate_series(1,3000) AS a
+ \crosstabview
+
+-- error: only one column
+SELECT 1 \crosstabview
+
+DROP TABLE ctv_data;
+
+-- check error reporting (bug #14476)
+CREATE TABLE ctv_data (x int, y int, v text);
+
+INSERT INTO ctv_data SELECT 1, x, '*' || x FROM generate_series(1,10) x;
+SELECT * FROM ctv_data \crosstabview
+
+INSERT INTO ctv_data VALUES (1, 10, '*'); -- duplicate data to cause error
+SELECT * FROM ctv_data \crosstabview
+
+DROP TABLE ctv_data;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
new file mode 100644
index 0000000..4b77383
--- /dev/null
+++ b/src/test/regress/sql/publication.sql
@@ -0,0 +1,218 @@
+--
+-- 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');
+CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
+
+\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;
+ALTER PUBLICATION testpub_foralltables SET (publish = 'insert, update');
+
+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;
+
+-- 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;
+\dRp+ testpub_forparted
+-- should now fail, because parent's publication replicates updates
+UPDATE testpub_parted1 SET a = 1;
+ALTER TABLE testpub_parted DETACH PARTITION testpub_parted1;
+-- works again, because parent's publication is no longer considered
+UPDATE testpub_parted1 SET a = 1;
+ALTER PUBLICATION testpub_forparted SET (publish_via_partition_root = true);
+\dRp+ testpub_forparted
+-- still fail, because parent's publication replicates updates
+UPDATE testpub_parted2 SET a = 2;
+ALTER PUBLICATION testpub_forparted DROP TABLE testpub_parted;
+-- works again, because update is no longer replicated
+UPDATE testpub_parted2 SET a = 2;
+-- publication includes both the parent table and the child table
+ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted, testpub_parted2;
+-- only parent is listed as being in publication, not the partition
+SELECT * FROM pg_publication_tables;
+DROP TABLE testpub_parted1, testpub_parted2;
+DROP PUBLICATION testpub_forparted, testpub_forparted1;
+
+-- Test cache invalidation FOR ALL TABLES publication
+SET client_min_messages = 'ERROR';
+CREATE TABLE testpub_tbl4(a int);
+INSERT INTO testpub_tbl4 values(1);
+UPDATE testpub_tbl4 set a = 2;
+CREATE PUBLICATION testpub_foralltables FOR ALL TABLES;
+RESET client_min_messages;
+-- fail missing REPLICA IDENTITY
+UPDATE testpub_tbl4 set a = 3;
+DROP PUBLICATION testpub_foralltables;
+-- should pass after dropping the publication
+UPDATE testpub_tbl4 set a = 3;
+DROP TABLE testpub_tbl4;
+
+-- 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;
+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;
+-- fail - nonexistent
+ALTER PUBLICATION testpub_default DROP TABLE pub_test.testpub_nopk;
+
+\d+ testpub_tbl1
+
+-- verify relation cache invalidation when a primary key is added using
+-- an existing index
+CREATE TABLE pub_test.testpub_addpk (id int not null, data int);
+ALTER PUBLICATION testpub_default ADD TABLE pub_test.testpub_addpk;
+INSERT INTO pub_test.testpub_addpk VALUES(1, 11);
+CREATE UNIQUE INDEX testpub_addpk_id_idx ON pub_test.testpub_addpk(id);
+-- fail:
+UPDATE pub_test.testpub_addpk SET id = 2;
+ALTER TABLE pub_test.testpub_addpk ADD PRIMARY KEY USING INDEX testpub_addpk_id_idx;
+-- now it should work:
+UPDATE pub_test.testpub_addpk SET id = 2;
+DROP TABLE pub_test.testpub_addpk;
+
+-- permissions
+SET ROLE regress_publication_user2;
+CREATE PUBLICATION testpub2; -- fail
+
+SET ROLE regress_publication_user;
+GRANT CREATE ON DATABASE regression 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 regression FROM regress_publication_user2;
+
+DROP TABLE testpub_parted;
+DROP VIEW testpub_view;
+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/random.sql b/src/test/regress/sql/random.sql
new file mode 100644
index 0000000..8187b2c
--- /dev/null
+++ b/src/test/regress/sql/random.sql
@@ -0,0 +1,44 @@
+--
+-- RANDOM
+-- Test the random function
+--
+
+-- count the number of tuples originally, should be 1000
+SELECT count(*) FROM onek;
+
+-- pick three random rows, they shouldn't match
+(SELECT unique1 AS random
+ FROM onek ORDER BY random() LIMIT 1)
+INTERSECT
+(SELECT unique1 AS random
+ FROM onek ORDER BY random() LIMIT 1)
+INTERSECT
+(SELECT unique1 AS random
+ FROM onek ORDER BY random() LIMIT 1);
+
+-- count roughly 1/10 of the tuples
+CREATE TABLE RANDOM_TBL AS
+ SELECT count(*) AS random
+ FROM onek WHERE random() < 1.0/10;
+
+-- select again, the count should be different
+INSERT INTO RANDOM_TBL (random)
+ SELECT count(*)
+ FROM onek WHERE random() < 1.0/10;
+
+-- select again, the count should be different
+INSERT INTO RANDOM_TBL (random)
+ SELECT count(*)
+ FROM onek WHERE random() < 1.0/10;
+
+-- select again, the count should be different
+INSERT INTO RANDOM_TBL (random)
+ SELECT count(*)
+ FROM onek WHERE random() < 1.0/10;
+
+-- now test that they are different counts
+SELECT random, count(random) FROM RANDOM_TBL
+ GROUP BY random HAVING count(random) > 3;
+
+SELECT AVG(random) FROM RANDOM_TBL
+ HAVING AVG(random) NOT BETWEEN 80 AND 120;
diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql
new file mode 100644
index 0000000..7e5cde1
--- /dev/null
+++ b/src/test/regress/sql/rangefuncs.sql
@@ -0,0 +1,784 @@
+CREATE TABLE rngfunc2(rngfuncid int, f2 int);
+INSERT INTO rngfunc2 VALUES(1, 11);
+INSERT INTO rngfunc2 VALUES(2, 22);
+INSERT INTO rngfunc2 VALUES(1, 111);
+
+CREATE FUNCTION rngfunct(int) returns setof rngfunc2 as 'SELECT * FROM rngfunc2 WHERE rngfuncid = $1 ORDER BY f2;' LANGUAGE SQL;
+
+-- function with ORDINALITY
+select * from rngfunct(1) with ordinality as z(a,b,ord);
+select * from rngfunct(1) with ordinality as z(a,b,ord) where b > 100; -- ordinal 2, not 1
+-- ordinality vs. column names and types
+select a,b,ord from rngfunct(1) with ordinality as z(a,b,ord);
+select a,ord from unnest(array['a','b']) with ordinality as z(a,ord);
+select * from unnest(array['a','b']) with ordinality as z(a,ord);
+select a,ord from unnest(array[1.0::float8]) with ordinality as z(a,ord);
+select * from unnest(array[1.0::float8]) with ordinality as z(a,ord);
+select row_to_json(s.*) from generate_series(11,14) with ordinality s;
+-- ordinality vs. views
+create temporary view vw_ord as select * from (values (1)) v(n) join rngfunct(1) with ordinality as z(a,b,ord) on (n=ord);
+select * from vw_ord;
+select definition from pg_views where viewname='vw_ord';
+drop view vw_ord;
+
+-- multiple functions
+select * from rows from(rngfunct(1),rngfunct(2)) with ordinality as z(a,b,c,d,ord);
+create temporary view vw_ord as select * from (values (1)) v(n) join rows from(rngfunct(1),rngfunct(2)) with ordinality as z(a,b,c,d,ord) on (n=ord);
+select * from vw_ord;
+select definition from pg_views where viewname='vw_ord';
+drop view vw_ord;
+
+-- expansions of unnest()
+select * from unnest(array[10,20],array['foo','bar'],array[1.0]);
+select * from unnest(array[10,20],array['foo','bar'],array[1.0]) with ordinality as z(a,b,c,ord);
+select * from rows from(unnest(array[10,20],array['foo','bar'],array[1.0])) with ordinality as z(a,b,c,ord);
+select * from rows from(unnest(array[10,20],array['foo','bar']), generate_series(101,102)) with ordinality as z(a,b,c,ord);
+create temporary view vw_ord as select * from unnest(array[10,20],array['foo','bar'],array[1.0]) as z(a,b,c);
+select * from vw_ord;
+select definition from pg_views where viewname='vw_ord';
+drop view vw_ord;
+create temporary view vw_ord as select * from rows from(unnest(array[10,20],array['foo','bar'],array[1.0])) as z(a,b,c);
+select * from vw_ord;
+select definition from pg_views where viewname='vw_ord';
+drop view vw_ord;
+create temporary view vw_ord as select * from rows from(unnest(array[10,20],array['foo','bar']), generate_series(1,2)) as z(a,b,c);
+select * from vw_ord;
+select definition from pg_views where viewname='vw_ord';
+drop view vw_ord;
+
+-- ordinality and multiple functions vs. rewind and reverse scan
+begin;
+declare rf_cur scroll cursor for select * from rows from(generate_series(1,5),generate_series(1,2)) with ordinality as g(i,j,o);
+fetch all from rf_cur;
+fetch backward all from rf_cur;
+fetch all from rf_cur;
+fetch next from rf_cur;
+fetch next from rf_cur;
+fetch prior from rf_cur;
+fetch absolute 1 from rf_cur;
+fetch next from rf_cur;
+fetch next from rf_cur;
+fetch next from rf_cur;
+fetch prior from rf_cur;
+fetch prior from rf_cur;
+fetch prior from rf_cur;
+commit;
+
+-- function with implicit LATERAL
+select * from rngfunc2, rngfunct(rngfunc2.rngfuncid) z where rngfunc2.f2 = z.f2;
+
+-- function with implicit LATERAL and explicit ORDINALITY
+select * from rngfunc2, rngfunct(rngfunc2.rngfuncid) with ordinality as z(rngfuncid,f2,ord) where rngfunc2.f2 = z.f2;
+
+-- function in subselect
+select * from rngfunc2 where f2 in (select f2 from rngfunct(rngfunc2.rngfuncid) z where z.rngfuncid = rngfunc2.rngfuncid) ORDER BY 1,2;
+
+-- function in subselect
+select * from rngfunc2 where f2 in (select f2 from rngfunct(1) z where z.rngfuncid = rngfunc2.rngfuncid) ORDER BY 1,2;
+
+-- function in subselect
+select * from rngfunc2 where f2 in (select f2 from rngfunct(rngfunc2.rngfuncid) z where z.rngfuncid = 1) ORDER BY 1,2;
+
+-- nested functions
+select rngfunct.rngfuncid, rngfunct.f2 from rngfunct(sin(pi()/2)::int) ORDER BY 1,2;
+
+CREATE TABLE rngfunc (rngfuncid int, rngfuncsubid int, rngfuncname text, primary key(rngfuncid,rngfuncsubid));
+INSERT INTO rngfunc VALUES(1,1,'Joe');
+INSERT INTO rngfunc VALUES(1,2,'Ed');
+INSERT INTO rngfunc VALUES(2,1,'Mary');
+
+-- sql, proretset = f, prorettype = b
+CREATE FUNCTION getrngfunc1(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc1(1) AS t1;
+SELECT * FROM getrngfunc1(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc1(1);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc1(1) WITH ORDINALITY as t1(v,o);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+
+-- sql, proretset = t, prorettype = b
+CREATE FUNCTION getrngfunc2(int) RETURNS setof int AS 'SELECT rngfuncid FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc2(1) AS t1;
+SELECT * FROM getrngfunc2(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc2(1);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc2(1) WITH ORDINALITY AS t1(v,o);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+
+-- sql, proretset = t, prorettype = b
+CREATE FUNCTION getrngfunc3(int) RETURNS setof text AS 'SELECT rngfuncname FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc3(1) AS t1;
+SELECT * FROM getrngfunc3(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc3(1);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc3(1) WITH ORDINALITY AS t1(v,o);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+
+-- sql, proretset = f, prorettype = c
+CREATE FUNCTION getrngfunc4(int) RETURNS rngfunc AS 'SELECT * FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc4(1) AS t1;
+SELECT * FROM getrngfunc4(1) WITH ORDINALITY AS t1(a,b,c,o);
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc4(1);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc4(1) WITH ORDINALITY AS t1(a,b,c,o);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+
+-- sql, proretset = t, prorettype = c
+CREATE FUNCTION getrngfunc5(int) RETURNS setof rngfunc AS 'SELECT * FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc5(1) AS t1;
+SELECT * FROM getrngfunc5(1) WITH ORDINALITY AS t1(a,b,c,o);
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc5(1);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc5(1) WITH ORDINALITY AS t1(a,b,c,o);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+
+-- sql, proretset = f, prorettype = record
+CREATE FUNCTION getrngfunc6(int) RETURNS RECORD AS 'SELECT * FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc6(1) AS t1(rngfuncid int, rngfuncsubid int, rngfuncname text);
+SELECT * FROM ROWS FROM( getrngfunc6(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text) ) WITH ORDINALITY;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc6(1) AS
+(rngfuncid int, rngfuncsubid int, rngfuncname text);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS
+ SELECT * FROM ROWS FROM( getrngfunc6(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text) )
+ WITH ORDINALITY;
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+
+-- sql, proretset = t, prorettype = record
+CREATE FUNCTION getrngfunc7(int) RETURNS setof record AS 'SELECT * FROM rngfunc WHERE rngfuncid = $1;' LANGUAGE SQL;
+SELECT * FROM getrngfunc7(1) AS t1(rngfuncid int, rngfuncsubid int, rngfuncname text);
+SELECT * FROM ROWS FROM( getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text) ) WITH ORDINALITY;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc7(1) AS
+(rngfuncid int, rngfuncsubid int, rngfuncname text);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS
+ SELECT * FROM ROWS FROM( getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text) )
+ WITH ORDINALITY;
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+
+-- plpgsql, proretset = f, prorettype = b
+CREATE FUNCTION getrngfunc8(int) RETURNS int AS 'DECLARE rngfuncint int; BEGIN SELECT rngfuncid into rngfuncint FROM rngfunc WHERE rngfuncid = $1; RETURN rngfuncint; END;' LANGUAGE plpgsql;
+SELECT * FROM getrngfunc8(1) AS t1;
+SELECT * FROM getrngfunc8(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc8(1);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc8(1) WITH ORDINALITY AS t1(v,o);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+
+-- plpgsql, proretset = f, prorettype = c
+CREATE FUNCTION getrngfunc9(int) RETURNS rngfunc AS 'DECLARE rngfunctup rngfunc%ROWTYPE; BEGIN SELECT * into rngfunctup FROM rngfunc WHERE rngfuncid = $1; RETURN rngfunctup; END;' LANGUAGE plpgsql;
+SELECT * FROM getrngfunc9(1) AS t1;
+SELECT * FROM getrngfunc9(1) WITH ORDINALITY AS t1(a,b,c,o);
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc9(1);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc9(1) WITH ORDINALITY AS t1(a,b,c,o);
+SELECT * FROM vw_getrngfunc;
+DROP VIEW vw_getrngfunc;
+
+-- mix 'n match kinds, to exercise expandRTE and related logic
+
+select * from rows from(getrngfunc1(1),getrngfunc2(1),getrngfunc3(1),getrngfunc4(1),getrngfunc5(1),
+ getrngfunc6(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text),
+ getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text),
+ getrngfunc8(1),getrngfunc9(1))
+ with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o,p,q,r,s,t,u);
+select * from rows from(getrngfunc9(1),getrngfunc8(1),
+ getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text),
+ getrngfunc6(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text),
+ getrngfunc5(1),getrngfunc4(1),getrngfunc3(1),getrngfunc2(1),getrngfunc1(1))
+ with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o,p,q,r,s,t,u);
+
+create temporary view vw_rngfunc as
+ select * from rows from(getrngfunc9(1),
+ getrngfunc7(1) AS (rngfuncid int, rngfuncsubid int, rngfuncname text),
+ getrngfunc1(1))
+ with ordinality as t1(a,b,c,d,e,f,g,n);
+select * from vw_rngfunc;
+select pg_get_viewdef('vw_rngfunc');
+drop view vw_rngfunc;
+
+DROP FUNCTION getrngfunc1(int);
+DROP FUNCTION getrngfunc2(int);
+DROP FUNCTION getrngfunc3(int);
+DROP FUNCTION getrngfunc4(int);
+DROP FUNCTION getrngfunc5(int);
+DROP FUNCTION getrngfunc6(int);
+DROP FUNCTION getrngfunc7(int);
+DROP FUNCTION getrngfunc8(int);
+DROP FUNCTION getrngfunc9(int);
+DROP FUNCTION rngfunct(int);
+DROP TABLE rngfunc2;
+DROP TABLE rngfunc;
+
+-- Rescan tests --
+CREATE TEMPORARY SEQUENCE rngfunc_rescan_seq1;
+CREATE TEMPORARY SEQUENCE rngfunc_rescan_seq2;
+CREATE TYPE rngfunc_rescan_t AS (i integer, s bigint);
+
+CREATE FUNCTION rngfunc_sql(int,int) RETURNS setof rngfunc_rescan_t AS 'SELECT i, nextval(''rngfunc_rescan_seq1'') FROM generate_series($1,$2) i;' LANGUAGE SQL;
+-- plpgsql functions use materialize mode
+CREATE FUNCTION rngfunc_mat(int,int) RETURNS setof rngfunc_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''rngfunc_rescan_seq2'')); end loop; end;' LANGUAGE plpgsql;
+
+--invokes ExecReScanFunctionScan - all these cases should materialize the function only once
+-- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function
+-- is on the inner path of a nestloop join
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN rngfunc_sql(11,13) ON (r+i)<100;
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN rngfunc_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN rngfunc_mat(11,13) ON (r+i)<100;
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN rngfunc_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN ROWS FROM( rngfunc_sql(11,13), rngfunc_mat(11,13) ) WITH ORDINALITY AS f(i1,s1,i2,s2,o) ON (r+i1+i2)<100;
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100;
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) f(i) ON (r+i)<100;
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
+
+--invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_sql(10+r,13);
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_sql(10+r,13) WITH ORDINALITY AS f(i,s,o);
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_sql(11,10+r);
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_sql(11,10+r) WITH ORDINALITY AS f(i,s,o);
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), rngfunc_sql(r1,r2);
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), rngfunc_sql(r1,r2) WITH ORDINALITY AS f(i,s,o);
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_mat(10+r,13);
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_mat(10+r,13) WITH ORDINALITY AS f(i,s,o);
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_mat(11,10+r);
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), rngfunc_mat(11,10+r) WITH ORDINALITY AS f(i,s,o);
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), rngfunc_mat(r1,r2);
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), rngfunc_mat(r1,r2) WITH ORDINALITY AS f(i,s,o);
+
+-- selective rescan of multiple functions:
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( rngfunc_sql(11,11), rngfunc_mat(10+r,13) );
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( rngfunc_sql(10+r,13), rngfunc_mat(11,11) );
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( rngfunc_sql(10+r,13), rngfunc_mat(10+r,13) );
+
+SELECT setval('rngfunc_rescan_seq1',1,false),setval('rngfunc_rescan_seq2',1,false);
+SELECT * FROM generate_series(1,2) r1, generate_series(r1,3) r2, ROWS FROM( rngfunc_sql(10+r1,13), rngfunc_mat(10+r2,13) );
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) WITH ORDINALITY AS f(i,o);
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) f(i);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) WITH ORDINALITY AS f(i,o);
+
+-- deep nesting
+
+SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
+ LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
+ LEFT JOIN generate_series(21,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
+SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
+ LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
+ LEFT JOIN generate_series(20+r1,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
+SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
+ LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
+ LEFT JOIN generate_series(r2,r2+3) f(i) ON ((r2+i)<100) OFFSET 0) s1;
+SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
+ LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
+ LEFT JOIN generate_series(r1,2+r2/5) f(i) ON ((r2+i)<100) OFFSET 0) s1;
+
+-- check handling of FULL JOIN with multiple lateral references (bug #15741)
+
+SELECT *
+FROM (VALUES (1),(2)) v1(r1)
+ LEFT JOIN LATERAL (
+ SELECT *
+ FROM generate_series(1, v1.r1) AS gs1
+ LEFT JOIN LATERAL (
+ SELECT *
+ FROM generate_series(1, gs1) AS gs2
+ LEFT JOIN generate_series(1, gs2) AS gs3 ON TRUE
+ ) AS ss1 ON TRUE
+ FULL JOIN generate_series(1, v1.r1) AS gs4 ON FALSE
+ ) AS ss0 ON TRUE;
+
+DROP FUNCTION rngfunc_sql(int,int);
+DROP FUNCTION rngfunc_mat(int,int);
+DROP SEQUENCE rngfunc_rescan_seq1;
+DROP SEQUENCE rngfunc_rescan_seq2;
+
+--
+-- Test cases involving OUT parameters
+--
+
+CREATE FUNCTION rngfunc(in f1 int, out f2 int)
+AS 'select $1+1' LANGUAGE sql;
+SELECT rngfunc(42);
+SELECT * FROM rngfunc(42);
+SELECT * FROM rngfunc(42) AS p(x);
+
+-- explicit spec of return type is OK
+CREATE OR REPLACE FUNCTION rngfunc(in f1 int, out f2 int) RETURNS int
+AS 'select $1+1' LANGUAGE sql;
+-- error, wrong result type
+CREATE OR REPLACE FUNCTION rngfunc(in f1 int, out f2 int) RETURNS float
+AS 'select $1+1' LANGUAGE sql;
+-- with multiple OUT params you must get a RECORD result
+CREATE OR REPLACE FUNCTION rngfunc(in f1 int, out f2 int, out f3 text) RETURNS int
+AS 'select $1+1' LANGUAGE sql;
+CREATE OR REPLACE FUNCTION rngfunc(in f1 int, out f2 int, out f3 text)
+RETURNS record
+AS 'select $1+1' LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION rngfuncr(in f1 int, out f2 int, out text)
+AS $$select $1-1, $1::text || 'z'$$ LANGUAGE sql;
+SELECT f1, rngfuncr(f1) FROM int4_tbl;
+SELECT * FROM rngfuncr(42);
+SELECT * FROM rngfuncr(42) AS p(a,b);
+
+CREATE OR REPLACE FUNCTION rngfuncb(in f1 int, inout f2 int, out text)
+AS $$select $2-1, $1::text || 'z'$$ LANGUAGE sql;
+SELECT f1, rngfuncb(f1, f1/2) FROM int4_tbl;
+SELECT * FROM rngfuncb(42, 99);
+SELECT * FROM rngfuncb(42, 99) AS p(a,b);
+
+-- Can reference function with or without OUT params for DROP, etc
+DROP FUNCTION rngfunc(int);
+DROP FUNCTION rngfuncr(in f2 int, out f1 int, out text);
+DROP FUNCTION rngfuncb(in f1 int, inout f2 int);
+
+--
+-- For my next trick, polymorphic OUT parameters
+--
+
+CREATE FUNCTION dup (f1 anyelement, f2 out anyelement, f3 out anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+SELECT dup(22);
+SELECT dup('xyz'); -- fails
+SELECT dup('xyz'::text);
+SELECT * FROM dup('xyz'::text);
+
+-- fails, as we are attempting to rename first argument
+CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+
+DROP FUNCTION dup(anyelement);
+
+-- equivalent behavior, though different name exposed for input arg
+CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+SELECT dup(22);
+
+DROP FUNCTION dup(anyelement);
+
+-- fails, no way to deduce outputs
+CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+
+CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
+AS 'select $1, $2' LANGUAGE sql;
+SELECT dup(22, array[44]);
+SELECT dup(4.5, array[44]);
+SELECT dup(22, array[44::bigint]);
+SELECT *, pg_typeof(f3), pg_typeof(f4) FROM dup(22, array[44::bigint]);
+
+DROP FUNCTION dup(f1 anycompatible, f2 anycompatiblearray);
+
+CREATE FUNCTION dup (f1 anycompatiblerange, f2 out anycompatible, f3 out anycompatiblearray, f4 out anycompatiblerange)
+AS 'select lower($1), array[lower($1), upper($1)], $1' LANGUAGE sql;
+SELECT dup(int4range(4,7));
+SELECT dup(numrange(4,7));
+SELECT dup(textrange('aaa', 'bbb'));
+
+DROP FUNCTION dup(f1 anycompatiblerange);
+
+-- fails, no way to deduce outputs
+CREATE FUNCTION bad (f1 anyarray, out f2 anycompatible, out f3 anycompatiblearray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+
+--
+-- table functions
+--
+
+CREATE OR REPLACE FUNCTION rngfunc()
+RETURNS TABLE(a int)
+AS $$ SELECT a FROM generate_series(1,5) a(a) $$ LANGUAGE sql;
+SELECT * FROM rngfunc();
+DROP FUNCTION rngfunc();
+
+CREATE OR REPLACE FUNCTION rngfunc(int)
+RETURNS TABLE(a int, b int)
+AS $$ SELECT a, b
+ FROM generate_series(1,$1) a(a),
+ generate_series(1,$1) b(b) $$ LANGUAGE sql;
+SELECT * FROM rngfunc(3);
+DROP FUNCTION rngfunc(int);
+
+-- case that causes change of typmod knowledge during inlining
+CREATE OR REPLACE FUNCTION rngfunc()
+RETURNS TABLE(a varchar(5))
+AS $$ SELECT 'hello'::varchar(5) $$ LANGUAGE sql STABLE;
+SELECT * FROM rngfunc() GROUP BY 1;
+DROP FUNCTION rngfunc();
+
+--
+-- some tests on SQL functions with RETURNING
+--
+
+create temp table tt(f1 serial, data text);
+
+create function insert_tt(text) returns int as
+$$ insert into tt(data) values($1) returning f1 $$
+language sql;
+
+select insert_tt('foo');
+select insert_tt('bar');
+select * from tt;
+
+-- insert will execute to completion even if function needs just 1 row
+create or replace function insert_tt(text) returns int as
+$$ insert into tt(data) values($1),($1||$1) returning f1 $$
+language sql;
+
+select insert_tt('fool');
+select * from tt;
+
+-- setof does what's expected
+create or replace function insert_tt2(text,text) returns setof int as
+$$ insert into tt(data) values($1),($2) returning f1 $$
+language sql;
+
+select insert_tt2('foolish','barrish');
+select * from insert_tt2('baz','quux');
+select * from tt;
+
+-- limit doesn't prevent execution to completion
+select insert_tt2('foolish','barrish') limit 1;
+select * from tt;
+
+-- triggers will fire, too
+create function noticetrigger() returns trigger as $$
+begin
+ raise notice 'noticetrigger % %', new.f1, new.data;
+ return null;
+end $$ language plpgsql;
+create trigger tnoticetrigger after insert on tt for each row
+execute procedure noticetrigger();
+
+select insert_tt2('foolme','barme') limit 1;
+select * from tt;
+
+-- and rules work
+create temp table tt_log(f1 int, data text);
+
+create rule insert_tt_rule as on insert to tt do also
+ insert into tt_log values(new.*);
+
+select insert_tt2('foollog','barlog') limit 1;
+select * from tt;
+-- note that nextval() gets executed a second time in the rule expansion,
+-- which is expected.
+select * from tt_log;
+
+-- test case for a whole-row-variable bug
+create function rngfunc1(n integer, out a text, out b text)
+ returns setof record
+ language sql
+ as $$ select 'foo ' || i, 'bar ' || i from generate_series(1,$1) i $$;
+
+set work_mem='64kB';
+select t.a, t, t.a from rngfunc1(10000) t limit 1;
+reset work_mem;
+select t.a, t, t.a from rngfunc1(10000) t limit 1;
+
+drop function rngfunc1(n integer);
+
+-- test use of SQL functions returning record
+-- this is supported in some cases where the query doesn't specify
+-- the actual record type ...
+
+create function array_to_set(anyarray) returns setof record as $$
+ select i AS "index", $1[i] AS "value" from generate_subscripts($1, 1) i
+$$ language sql strict immutable;
+
+select array_to_set(array['one', 'two']);
+select * from array_to_set(array['one', 'two']) as t(f1 int,f2 text);
+select * from array_to_set(array['one', 'two']); -- fail
+-- after-the-fact coercion of the columns is now possible, too
+select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text);
+-- and if it doesn't work, you get a compile-time not run-time error
+select * from array_to_set(array['one', 'two']) as t(f1 point,f2 text);
+
+-- with "strict", this function can't be inlined in FROM
+explain (verbose, costs off)
+ select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text);
+
+-- but without, it can be:
+
+create or replace function array_to_set(anyarray) returns setof record as $$
+ select i AS "index", $1[i] AS "value" from generate_subscripts($1, 1) i
+$$ language sql immutable;
+
+select array_to_set(array['one', 'two']);
+select * from array_to_set(array['one', 'two']) as t(f1 int,f2 text);
+select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text);
+select * from array_to_set(array['one', 'two']) as t(f1 point,f2 text);
+explain (verbose, costs off)
+ select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text);
+
+create temp table rngfunc(f1 int8, f2 int8);
+
+create function testrngfunc() returns record as $$
+ insert into rngfunc values (1,2) returning *;
+$$ language sql;
+
+select testrngfunc();
+select * from testrngfunc() as t(f1 int8,f2 int8);
+select * from testrngfunc(); -- fail
+
+drop function testrngfunc();
+
+create function testrngfunc() returns setof record as $$
+ insert into rngfunc values (1,2), (3,4) returning *;
+$$ language sql;
+
+select testrngfunc();
+select * from testrngfunc() as t(f1 int8,f2 int8);
+select * from testrngfunc(); -- fail
+
+drop function testrngfunc();
+
+-- Check that typmod imposed by a composite type is honored
+create type rngfunc_type as (f1 numeric(35,6), f2 numeric(35,2));
+
+create function testrngfunc() returns rngfunc_type as $$
+ select 7.136178319899999964, 7.136178319899999964;
+$$ language sql immutable;
+
+explain (verbose, costs off)
+select testrngfunc();
+select testrngfunc();
+explain (verbose, costs off)
+select * from testrngfunc();
+select * from testrngfunc();
+
+create or replace function testrngfunc() returns rngfunc_type as $$
+ select 7.136178319899999964, 7.136178319899999964;
+$$ language sql volatile;
+
+explain (verbose, costs off)
+select testrngfunc();
+select testrngfunc();
+explain (verbose, costs off)
+select * from testrngfunc();
+select * from testrngfunc();
+
+drop function testrngfunc();
+
+create function testrngfunc() returns setof rngfunc_type as $$
+ select 7.136178319899999964, 7.136178319899999964;
+$$ language sql immutable;
+
+explain (verbose, costs off)
+select testrngfunc();
+select testrngfunc();
+explain (verbose, costs off)
+select * from testrngfunc();
+select * from testrngfunc();
+
+create or replace function testrngfunc() returns setof rngfunc_type as $$
+ select 7.136178319899999964, 7.136178319899999964;
+$$ language sql volatile;
+
+explain (verbose, costs off)
+select testrngfunc();
+select testrngfunc();
+explain (verbose, costs off)
+select * from testrngfunc();
+select * from testrngfunc();
+
+create or replace function testrngfunc() returns setof rngfunc_type as $$
+ select 1, 2 union select 3, 4 order by 1;
+$$ language sql immutable;
+
+explain (verbose, costs off)
+select testrngfunc();
+select testrngfunc();
+explain (verbose, costs off)
+select * from testrngfunc();
+select * from testrngfunc();
+
+-- Check a couple of error cases while we're here
+select * from testrngfunc() as t(f1 int8,f2 int8); -- fail, composite result
+select * from pg_get_keywords() as t(f1 int8,f2 int8); -- fail, OUT params
+select * from sin(3) as t(f1 int8,f2 int8); -- fail, scalar result type
+
+drop type rngfunc_type cascade;
+
+--
+-- Check some cases involving added/dropped columns in a rowtype result
+--
+
+create temp table users (userid text, seq int, email text, todrop bool, moredrop int, enabled bool);
+insert into users values ('id',1,'email',true,11,true);
+insert into users values ('id2',2,'email2',true,12,true);
+alter table users drop column todrop;
+
+create or replace function get_first_user() returns users as
+$$ SELECT * FROM users ORDER BY userid LIMIT 1; $$
+language sql stable;
+
+SELECT get_first_user();
+SELECT * FROM get_first_user();
+
+create or replace function get_users() returns setof users as
+$$ SELECT * FROM users ORDER BY userid; $$
+language sql stable;
+
+SELECT get_users();
+SELECT * FROM get_users();
+SELECT * FROM get_users() WITH ORDINALITY; -- make sure ordinality copes
+
+-- multiple functions vs. dropped columns
+SELECT * FROM ROWS FROM(generate_series(10,11), get_users()) WITH ORDINALITY;
+SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY;
+
+-- check that we can cope with post-parsing changes in rowtypes
+create temp view usersview as
+SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY;
+
+select * from usersview;
+alter table users add column junk text;
+select * from usersview;
+begin;
+alter table users drop column moredrop;
+select * from usersview; -- expect clean failure
+rollback;
+alter table users alter column seq type numeric;
+select * from usersview; -- expect clean failure
+
+drop view usersview;
+drop function get_first_user();
+drop function get_users();
+drop table users;
+
+-- check behavior with type coercion required for a set-op
+
+create or replace function rngfuncbar() returns setof text as
+$$ select 'foo'::varchar union all select 'bar'::varchar ; $$
+language sql stable;
+
+select rngfuncbar();
+select * from rngfuncbar();
+-- this function is now inlinable, too:
+explain (verbose, costs off) select * from rngfuncbar();
+
+drop function rngfuncbar();
+
+-- check handling of a SQL function with multiple OUT params (bug #5777)
+
+create or replace function rngfuncbar(out integer, out numeric) as
+$$ select (1, 2.1) $$ language sql;
+
+select * from rngfuncbar();
+
+create or replace function rngfuncbar(out integer, out numeric) as
+$$ select (1, 2) $$ language sql;
+
+select * from rngfuncbar(); -- fail
+
+create or replace function rngfuncbar(out integer, out numeric) as
+$$ select (1, 2.1, 3) $$ language sql;
+
+select * from rngfuncbar(); -- fail
+
+drop function rngfuncbar();
+
+-- check whole-row-Var handling in nested lateral functions (bug #11703)
+
+create function extractq2(t int8_tbl) returns int8 as $$
+ select t.q2
+$$ language sql immutable;
+
+explain (verbose, costs off)
+select x from int8_tbl, extractq2(int8_tbl) f(x);
+
+select x from int8_tbl, extractq2(int8_tbl) f(x);
+
+create function extractq2_2(t int8_tbl) returns table(ret1 int8) as $$
+ select extractq2(t) offset 0
+$$ language sql immutable;
+
+explain (verbose, costs off)
+select x from int8_tbl, extractq2_2(int8_tbl) f(x);
+
+select x from int8_tbl, extractq2_2(int8_tbl) f(x);
+
+-- without the "offset 0", this function gets optimized quite differently
+
+create function extractq2_2_opt(t int8_tbl) returns table(ret1 int8) as $$
+ select extractq2(t)
+$$ language sql immutable;
+
+explain (verbose, costs off)
+select x from int8_tbl, extractq2_2_opt(int8_tbl) f(x);
+
+select x from int8_tbl, extractq2_2_opt(int8_tbl) f(x);
+
+-- check handling of nulls in SRF results (bug #7808)
+
+create type rngfunc2 as (a integer, b text);
+
+select *, row_to_json(u) from unnest(array[(1,'foo')::rngfunc2, null::rngfunc2]) u;
+select *, row_to_json(u) from unnest(array[null::rngfunc2, null::rngfunc2]) u;
+select *, row_to_json(u) from unnest(array[null::rngfunc2, (1,'foo')::rngfunc2, null::rngfunc2]) u;
+select *, row_to_json(u) from unnest(array[]::rngfunc2[]) u;
+
+drop type rngfunc2;
+
+-- check handling of functions pulled up into function RTEs (bug #17227)
+
+explain (verbose, costs off)
+select * from
+ (select jsonb_path_query_array(module->'lectures', '$[*]') as lecture
+ from unnest(array['{"lectures": [{"id": "1"}]}'::jsonb])
+ as unnested_modules(module)) as ss,
+ jsonb_to_recordset(ss.lecture) as j (id text);
+
+select * from
+ (select jsonb_path_query_array(module->'lectures', '$[*]') as lecture
+ from unnest(array['{"lectures": [{"id": "1"}]}'::jsonb])
+ as unnested_modules(module)) as ss,
+ jsonb_to_recordset(ss.lecture) as j (id text);
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
new file mode 100644
index 0000000..a2d411d
--- /dev/null
+++ b/src/test/regress/sql/rangetypes.sql
@@ -0,0 +1,620 @@
+-- Tests for range data types.
+
+create type textrange as range (subtype=text, collation="C");
+
+--
+-- test input parser
+--
+
+-- 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);
+
+insert into test_range_gist select int4range(g, g+10) from generate_series(1,2000) g;
+insert into test_range_gist select 'empty'::int4range from generate_series(1,500) g;
+insert into test_range_gist select int4range(g, g+10000) from generate_series(1,1000) g;
+insert into test_range_gist select 'empty'::int4range from generate_series(1,500) g;
+insert into test_range_gist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+insert into test_range_gist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+insert into test_range_gist select int4range(g, g+10) from generate_series(1,2000) g;
+
+-- 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_range_gist;
+
+-- first, verify non-indexed results
+SET enable_seqscan = t;
+SET enable_indexscan = f;
+SET enable_bitmapscan = f;
+
+select count(*) from test_range_gist where ir @> 'empty'::int4range;
+select count(*) from test_range_gist where ir = int4range(10,20);
+select count(*) from test_range_gist where ir @> 10;
+select count(*) from test_range_gist where ir @> int4range(10,20);
+select count(*) from test_range_gist where ir && int4range(10,20);
+select count(*) from test_range_gist where ir <@ int4range(10,50);
+select count(*) from test_range_gist where ir << int4range(100,500);
+select count(*) from test_range_gist where ir >> int4range(100,500);
+select count(*) from test_range_gist where ir &< int4range(100,500);
+select count(*) from test_range_gist where ir &> int4range(100,500);
+select count(*) from test_range_gist where ir -|- int4range(100,500);
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir -|- 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_range_gist where ir @> 'empty'::int4range;
+select count(*) from test_range_gist where ir = int4range(10,20);
+select count(*) from test_range_gist where ir @> 10;
+select count(*) from test_range_gist where ir @> int4range(10,20);
+select count(*) from test_range_gist where ir && int4range(10,20);
+select count(*) from test_range_gist where ir <@ int4range(10,50);
+select count(*) from test_range_gist where ir << int4range(100,500);
+select count(*) from test_range_gist where ir >> int4range(100,500);
+select count(*) from test_range_gist where ir &< int4range(100,500);
+select count(*) from test_range_gist where ir &> int4range(100,500);
+select count(*) from test_range_gist where ir -|- int4range(100,500);
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
+
+-- now check same queries using a bulk-loaded index
+drop index test_range_gist_idx;
+create index test_range_gist_idx on test_range_gist using gist (ir);
+
+select count(*) from test_range_gist where ir @> 'empty'::int4range;
+select count(*) from test_range_gist where ir = int4range(10,20);
+select count(*) from test_range_gist where ir @> 10;
+select count(*) from test_range_gist where ir @> int4range(10,20);
+select count(*) from test_range_gist where ir && int4range(10,20);
+select count(*) from test_range_gist where ir <@ int4range(10,50);
+select count(*) from test_range_gist where ir << int4range(100,500);
+select count(*) from test_range_gist where ir >> int4range(100,500);
+select count(*) from test_range_gist where ir &< int4range(100,500);
+select count(*) from test_range_gist where ir &> int4range(100,500);
+select count(*) from test_range_gist where ir -|- int4range(100,500);
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
+
+-- 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);
+
+insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+insert into test_range_spgist select int4range(g, g+10000) from generate_series(1,1000) g;
+insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+insert into test_range_spgist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+insert into test_range_spgist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+
+-- first, verify non-indexed results
+SET enable_seqscan = t;
+SET enable_indexscan = f;
+SET enable_bitmapscan = f;
+
+select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+select count(*) from test_range_spgist where ir = int4range(10,20);
+select count(*) from test_range_spgist where ir @> 10;
+select count(*) from test_range_spgist where ir @> int4range(10,20);
+select count(*) from test_range_spgist where ir && int4range(10,20);
+select count(*) from test_range_spgist where ir <@ int4range(10,50);
+select count(*) from test_range_spgist where ir << int4range(100,500);
+select count(*) from test_range_spgist where ir >> int4range(100,500);
+select count(*) from test_range_spgist where ir &< int4range(100,500);
+select count(*) from test_range_spgist where ir &> int4range(100,500);
+select count(*) from test_range_spgist where ir -|- int4range(100,500);
+
+-- now check same queries using index
+SET enable_seqscan = f;
+SET enable_indexscan = t;
+SET enable_bitmapscan = f;
+
+select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+select count(*) from test_range_spgist where ir = int4range(10,20);
+select count(*) from test_range_spgist where ir @> 10;
+select count(*) from test_range_spgist where ir @> int4range(10,20);
+select count(*) from test_range_spgist where ir && int4range(10,20);
+select count(*) from test_range_spgist where ir <@ int4range(10,50);
+select count(*) from test_range_spgist where ir << int4range(100,500);
+select count(*) from test_range_spgist where ir >> int4range(100,500);
+select count(*) from test_range_spgist where ir &< int4range(100,500);
+select count(*) from test_range_spgist where ir &> int4range(100,500);
+select count(*) from test_range_spgist where ir -|- int4range(100,500);
+
+-- now check same queries using a bulk-loaded index
+drop index test_range_spgist_idx;
+create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+
+select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+select count(*) from test_range_spgist where ir = int4range(10,20);
+select count(*) from test_range_spgist where ir @> 10;
+select count(*) from test_range_spgist where ir @> int4range(10,20);
+select count(*) from test_range_spgist where ir && int4range(10,20);
+select count(*) from test_range_spgist where ir <@ int4range(10,50);
+select count(*) from test_range_spgist where ir << int4range(100,500);
+select count(*) from test_range_spgist where ir >> int4range(100,500);
+select count(*) from test_range_spgist where ir &< int4range(100,500);
+select count(*) from test_range_spgist where ir &> int4range(100,500);
+select count(*) from test_range_spgist where ir -|- int4range(100,500);
+
+-- test index-only scans
+explain (costs off)
+select ir from test_range_spgist where ir -|- int4range(10,20) order by ir;
+select ir from test_range_spgist where ir -|- int4range(10,20) order by ir;
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+
+-- 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 &&)
+);
+
+insert into test_range_excl
+ values(int4range(123, 123, '[]'), int4range(1, 1, '[]'), '[2010-01-02 10:00, 2010-01-02 11:00)');
+insert into test_range_excl
+ values(int4range(123, 123, '[]'), int4range(2, 2, '[]'), '[2010-01-02 11:00, 2010-01-02 12:00)');
+insert into test_range_excl
+ values(int4range(123, 123, '[]'), int4range(3, 3, '[]'), '[2010-01-02 10:10, 2010-01-02 11:00)');
+insert into test_range_excl
+ values(int4range(124, 124, '[]'), int4range(3, 3, '[]'), '[2010-01-02 10:10, 2010-01-02 11:10)');
+insert into test_range_excl
+ values(int4range(125, 125, '[]'), int4range(1, 1, '[]'), '[2010-01-02 10:10, 2010-01-02 11:00)');
+
+-- 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
+--
+
+--should fail
+create type float8range as range (subtype=float8, subtype_diff=float4mi);
+
+--should succeed
+create type float8range as range (subtype=float8, subtype_diff=float8mi);
+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/regex.linux.utf8.sql b/src/test/regress/sql/regex.linux.utf8.sql
new file mode 100644
index 0000000..4577811
--- /dev/null
+++ b/src/test/regress/sql/regex.linux.utf8.sql
@@ -0,0 +1,46 @@
+/*
+ * This test is for Linux/glibc systems and others that implement proper
+ * locale classification of Unicode characters with high code values.
+ * It must be run in a database with UTF8 encoding and a Unicode-aware locale.
+ */
+
+SET client_encoding TO UTF8;
+
+--
+-- Test the "high colormap" logic with single characters and ranges that
+-- exceed the MAX_SIMPLE_CHR cutoff, here assumed to be less than U+2000.
+--
+
+-- trivial cases:
+SELECT 'aâ“' ~ U&'a\24D0' AS t;
+SELECT 'aâ“' ~ U&'a\24D1' AS f;
+SELECT 'aâ“•' ~ 'a[â“-â“©]' AS t;
+SELECT 'aâ’»' ~ 'a[â“-â“©]' AS f;
+-- cases requiring splitting of ranges:
+SELECT 'aâ“•â“•' ~ 'aâ“•[â“-â“©]' AS t;
+SELECT 'aâ“•â“' ~ 'aâ“•[â“-â“©]' AS t;
+SELECT 'aâ“â“•' ~ 'aâ“•[â“-â“©]' AS f;
+SELECT 'aâ“•â“•' ~ 'a[â“-â“©]â“•' AS t;
+SELECT 'aâ“•â“' ~ 'a[â“-â“©]â“•' AS f;
+SELECT 'aâ“â“•' ~ 'a[â“-â“©]â“•' AS t;
+SELECT 'aⒶⓜ' ~ 'a[Ⓐ-ⓜ][ⓜ-ⓩ]' AS t;
+SELECT 'aⓜⓜ' ~ 'a[Ⓐ-ⓜ][ⓜ-ⓩ]' AS t;
+SELECT 'aⓜⓩ' ~ 'a[Ⓐ-ⓜ][ⓜ-ⓩ]' AS t;
+SELECT 'aⓩⓩ' ~ 'a[Ⓐ-ⓜ][ⓜ-ⓩ]' AS f;
+SELECT 'aⓜ⓪' ~ 'a[Ⓐ-ⓜ][ⓜ-ⓩ]' AS f;
+SELECT 'a0' ~ 'a[a-â“©]' AS f;
+SELECT 'aq' ~ 'a[a-â“©]' AS t;
+SELECT 'aⓜ' ~ 'a[a-ⓩ]' AS t;
+SELECT 'a⓪' ~ 'a[a-ⓩ]' AS f;
+
+-- Locale-dependent character classes
+
+SELECT 'aⒶⓜ⓪' ~ '[[:alpha:]][[:alpha:]][[:alpha:]][[:graph:]]' AS t;
+SELECT 'aⒶⓜ⓪' ~ '[[:alpha:]][[:alpha:]][[:alpha:]][[:alpha:]]' AS f;
+
+-- Locale-dependent character classes with high ranges
+
+SELECT 'aⒶⓜ⓪' ~ '[a-z][[:alpha:]][â“-â“©][[:graph:]]' AS t;
+SELECT 'aⓜⒶ⓪' ~ '[a-z][[:alpha:]][â“-â“©][[:graph:]]' AS f;
+SELECT 'aⓜⒶ⓪' ~ '[a-z][â“-â“©][[:alpha:]][[:graph:]]' AS t;
+SELECT 'aⒶⓜ⓪' ~ '[a-z][â“-â“©][[:alpha:]][[:graph:]]' AS f;
diff --git a/src/test/regress/sql/regex.sql b/src/test/regress/sql/regex.sql
new file mode 100644
index 0000000..5621710
--- /dev/null
+++ b/src/test/regress/sql/regex.sql
@@ -0,0 +1,158 @@
+--
+-- Regular expression tests
+--
+
+-- Don't want to have to double backslashes in regexes
+set standard_conforming_strings = on;
+
+-- Test simple quantified backrefs
+select 'bbbbb' ~ '^([bc])\1*$' as t;
+select 'ccc' ~ '^([bc])\1*$' as t;
+select 'xxx' ~ '^([bc])\1*$' as f;
+select 'bbc' ~ '^([bc])\1*$' as f;
+select 'b' ~ '^([bc])\1*$' as t;
+
+-- Test quantified backref within a larger expression
+select 'abc abc abc' ~ '^(\w+)( \1)+$' as t;
+select 'abc abd abc' ~ '^(\w+)( \1)+$' as f;
+select 'abc abc abd' ~ '^(\w+)( \1)+$' as f;
+select 'abc abc abc' ~ '^(.+)( \1)+$' as t;
+select 'abc abd abc' ~ '^(.+)( \1)+$' as f;
+select 'abc abc abd' ~ '^(.+)( \1)+$' as f;
+
+-- Test some cases that crashed in 9.2beta1 due to pmatch[] array overrun
+select substring('asd TO foo' from ' TO (([a-z0-9._]+|"([^"]+|"")+")+)');
+select substring('a' from '((a))+');
+select substring('a' from '((a)+)');
+
+-- Test regexp_match()
+select regexp_match('abc', '');
+select regexp_match('abc', 'bc');
+select regexp_match('abc', 'd') is null;
+select regexp_match('abc', '(B)(c)', 'i');
+select regexp_match('abc', 'Bd', 'ig'); -- error
+
+-- Test lookahead constraints
+select regexp_matches('ab', 'a(?=b)b*');
+select regexp_matches('a', 'a(?=b)b*');
+select regexp_matches('abc', 'a(?=b)b*(?=c)c*');
+select regexp_matches('ab', 'a(?=b)b*(?=c)c*');
+select regexp_matches('ab', 'a(?!b)b*');
+select regexp_matches('a', 'a(?!b)b*');
+select regexp_matches('b', '(?=b)b');
+select regexp_matches('a', '(?=b)b');
+
+-- Test lookbehind constraints
+select regexp_matches('abb', '(?<=a)b*');
+select regexp_matches('a', 'a(?<=a)b*');
+select regexp_matches('abc', 'a(?<=a)b*(?<=b)c*');
+select regexp_matches('ab', 'a(?<=a)b*(?<=b)c*');
+select regexp_matches('ab', 'a*(?<!a)b*');
+select regexp_matches('ab', 'a*(?<!a)b+');
+select regexp_matches('b', 'a*(?<!a)b+');
+select regexp_matches('a', 'a(?<!a)b*');
+select regexp_matches('b', '(?<=b)b');
+select regexp_matches('foobar', '(?<=f)b+');
+select regexp_matches('foobar', '(?<=foo)b+');
+select regexp_matches('foobar', '(?<=oo)b+');
+
+-- Test optimization of single-chr-or-bracket-expression lookaround constraints
+select 'xz' ~ 'x(?=[xy])';
+select 'xy' ~ 'x(?=[xy])';
+select 'xz' ~ 'x(?![xy])';
+select 'xy' ~ 'x(?![xy])';
+select 'x' ~ 'x(?![xy])';
+select 'xyy' ~ '(?<=[xy])yy+';
+select 'zyy' ~ '(?<=[xy])yy+';
+select 'xyy' ~ '(?<![xy])yy+';
+select 'zyy' ~ '(?<![xy])yy+';
+
+-- Test conversion of regex patterns to indexable conditions
+explain (costs off) select * from pg_proc where proname ~ 'abc';
+explain (costs off) select * from pg_proc where proname ~ '^abc';
+explain (costs off) select * from pg_proc where proname ~ '^abc$';
+explain (costs off) select * from pg_proc where proname ~ '^abcd*e';
+explain (costs off) select * from pg_proc where proname ~ '^abc+d';
+explain (costs off) select * from pg_proc where proname ~ '^(abc)(def)';
+explain (costs off) select * from pg_proc where proname ~ '^(abc)$';
+explain (costs off) select * from pg_proc where proname ~ '^(abc)?d';
+explain (costs off) select * from pg_proc where proname ~ '^abcd(x|(?=\w\w)q)';
+
+-- Test for infinite loop in pullback() (CVE-2007-4772)
+select 'a' ~ '($|^)*';
+
+-- These cases expose a bug in the original fix for CVE-2007-4772
+select 'a' ~ '(^)+^';
+select 'a' ~ '$($$)+';
+
+-- More cases of infinite loop in pullback(), not fixed by CVE-2007-4772 fix
+select 'a' ~ '($^)+';
+select 'a' ~ '(^$)*';
+select 'aa bb cc' ~ '(^(?!aa))+';
+select 'aa x' ~ '(^(?!aa)(?!bb)(?!cc))+';
+select 'bb x' ~ '(^(?!aa)(?!bb)(?!cc))+';
+select 'cc x' ~ '(^(?!aa)(?!bb)(?!cc))+';
+select 'dd x' ~ '(^(?!aa)(?!bb)(?!cc))+';
+
+-- Test for infinite loop in fixempties() (Tcl bugs 3604074, 3606683)
+select 'a' ~ '((((((a)*)*)*)*)*)*';
+select 'a' ~ '((((((a+|)+|)+|)+|)+|)+|)';
+
+-- These cases used to give too-many-states failures
+select 'x' ~ 'abcd(\m)+xyz';
+select 'a' ~ '^abcd*(((((^(a c(e?d)a+|)+|)+|)+|)+|a)+|)';
+select 'x' ~ 'a^(^)bcd*xy(((((($a+|)+|)+|)+$|)+|)+|)^$';
+select 'x' ~ 'xyz(\Y\Y)+';
+select 'x' ~ 'x|(?:\M)+';
+
+-- This generates O(N) states but O(N^2) arcs, so it causes problems
+-- if arc count is not constrained
+select 'x' ~ repeat('x*y*z*', 1000);
+
+-- Test backref in combination with non-greedy quantifier
+-- https://core.tcl.tk/tcl/tktview/6585b21ca8fa6f3678d442b97241fdd43dba2ec0
+select 'Programmer' ~ '(\w).*?\1' as t;
+select regexp_matches('Programmer', '(\w)(.*?\1)', 'g');
+
+-- Test for proper matching of non-greedy iteration (bug #11478)
+select regexp_matches('foo/bar/baz',
+ '^([^/]+?)(?:/([^/]+?))(?:/([^/]+?))?$', '');
+
+-- Test that greediness can be overridden by outer quantifier
+select regexp_matches('llmmmfff', '^(l*)(.*)(f*)$');
+select regexp_matches('llmmmfff', '^(l*){1,1}(.*)(f*)$');
+select regexp_matches('llmmmfff', '^(l*){1,1}?(.*)(f*)$');
+select regexp_matches('llmmmfff', '^(l*){1,1}?(.*){1,1}?(f*)$');
+select regexp_matches('llmmmfff', '^(l*?)(.*)(f*)$');
+select regexp_matches('llmmmfff', '^(l*?){1,1}(.*)(f*)$');
+select regexp_matches('llmmmfff', '^(l*?){1,1}?(.*)(f*)$');
+select regexp_matches('llmmmfff', '^(l*?){1,1}?(.*){1,1}?(f*)$');
+
+-- Test for infinite loop in cfindloop with zero-length possible match
+-- but no actual match (can only happen in the presence of backrefs)
+select 'a' ~ '$()|^\1';
+select 'a' ~ '.. ()|\1';
+select 'a' ~ '()*\1';
+select 'a' ~ '()+\1';
+
+-- Test incorrect removal of capture groups within {0}
+select 'xxx' ~ '(.){0}(\1)' as f;
+select 'xxx' ~ '((.)){0}(\2)' as f;
+select 'xyz' ~ '((.)){0}(\2){0}' as t;
+
+-- Test ancient oversight in when to apply zaptreesubs
+select 'abcdef' ~ '^(.)\1|\1.' as f;
+select 'abadef' ~ '^((.)\2|..)\2' as f;
+
+-- Add coverage for some cases in checkmatchall
+select regexp_match('xy', '.|...');
+select regexp_match('xyz', '.|...');
+select regexp_match('xy', '.*');
+select regexp_match('fooba', '(?:..)*');
+select regexp_match('xyz', repeat('.', 260));
+select regexp_match('foo', '(?:.|){99}');
+
+-- Error conditions
+select 'xyz' ~ 'x(\w)(?=\1)'; -- no backrefs in LACONs
+select 'xyz' ~ 'x(\w)(?=(\1))';
+select 'a' ~ '\x7fffffff'; -- invalid chr code
diff --git a/src/test/regress/sql/regproc.sql b/src/test/regress/sql/regproc.sql
new file mode 100644
index 0000000..faab0c1
--- /dev/null
+++ b/src/test/regress/sql/regproc.sql
@@ -0,0 +1,122 @@
+--
+-- regproc
+--
+
+/* If objects exist, return oids */
+
+CREATE ROLE regress_regrole_test;
+
+-- without schemaname
+
+SELECT regoper('||/');
+SELECT regoperator('+(int4,int4)');
+SELECT regproc('now');
+SELECT regprocedure('abs(numeric)');
+SELECT regclass('pg_class');
+SELECT regtype('int4');
+SELECT regcollation('"POSIX"');
+
+SELECT to_regoper('||/');
+SELECT to_regoperator('+(int4,int4)');
+SELECT to_regproc('now');
+SELECT to_regprocedure('abs(numeric)');
+SELECT to_regclass('pg_class');
+SELECT to_regtype('int4');
+SELECT to_regcollation('"POSIX"');
+
+-- with schemaname
+
+SELECT regoper('pg_catalog.||/');
+SELECT regoperator('pg_catalog.+(int4,int4)');
+SELECT regproc('pg_catalog.now');
+SELECT regprocedure('pg_catalog.abs(numeric)');
+SELECT regclass('pg_catalog.pg_class');
+SELECT regtype('pg_catalog.int4');
+SELECT regcollation('pg_catalog."POSIX"');
+
+SELECT to_regoper('pg_catalog.||/');
+SELECT to_regproc('pg_catalog.now');
+SELECT to_regprocedure('pg_catalog.abs(numeric)');
+SELECT to_regclass('pg_catalog.pg_class');
+SELECT to_regtype('pg_catalog.int4');
+SELECT to_regcollation('pg_catalog."POSIX"');
+
+-- schemaname not applicable
+
+SELECT regrole('regress_regrole_test');
+SELECT regrole('"regress_regrole_test"');
+SELECT regnamespace('pg_catalog');
+SELECT regnamespace('"pg_catalog"');
+
+SELECT to_regrole('regress_regrole_test');
+SELECT to_regrole('"regress_regrole_test"');
+SELECT to_regnamespace('pg_catalog');
+SELECT to_regnamespace('"pg_catalog"');
+
+/* If objects don't exist, raise errors. */
+
+DROP ROLE regress_regrole_test;
+
+-- without schemaname
+
+SELECT regoper('||//');
+SELECT regoperator('++(int4,int4)');
+SELECT regproc('know');
+SELECT regprocedure('absinthe(numeric)');
+SELECT regclass('pg_classes');
+SELECT regtype('int3');
+
+-- with schemaname
+
+SELECT regoper('ng_catalog.||/');
+SELECT regoperator('ng_catalog.+(int4,int4)');
+SELECT regproc('ng_catalog.now');
+SELECT regprocedure('ng_catalog.abs(numeric)');
+SELECT regclass('ng_catalog.pg_class');
+SELECT regtype('ng_catalog.int4');
+SELECT regcollation('ng_catalog."POSIX"');
+
+-- schemaname not applicable
+
+SELECT regrole('regress_regrole_test');
+SELECT regrole('"regress_regrole_test"');
+SELECT regrole('Nonexistent');
+SELECT regrole('"Nonexistent"');
+SELECT regrole('foo.bar');
+SELECT regnamespace('Nonexistent');
+SELECT regnamespace('"Nonexistent"');
+SELECT regnamespace('foo.bar');
+
+/* If objects don't exist, return NULL with no error. */
+
+-- without schemaname
+
+SELECT to_regoper('||//');
+SELECT to_regoperator('++(int4,int4)');
+SELECT to_regproc('know');
+SELECT to_regprocedure('absinthe(numeric)');
+SELECT to_regclass('pg_classes');
+SELECT to_regtype('int3');
+SELECT to_regcollation('notacollation');
+
+-- with schemaname
+
+SELECT to_regoper('ng_catalog.||/');
+SELECT to_regoperator('ng_catalog.+(int4,int4)');
+SELECT to_regproc('ng_catalog.now');
+SELECT to_regprocedure('ng_catalog.abs(numeric)');
+SELECT to_regclass('ng_catalog.pg_class');
+SELECT to_regtype('ng_catalog.int4');
+SELECT to_regcollation('ng_catalog."POSIX"');
+
+-- schemaname not applicable
+
+SELECT to_regrole('regress_regrole_test');
+SELECT to_regrole('"regress_regrole_test"');
+SELECT to_regrole('foo.bar');
+SELECT to_regrole('Nonexistent');
+SELECT to_regrole('"Nonexistent"');
+SELECT to_regrole('foo.bar');
+SELECT to_regnamespace('Nonexistent');
+SELECT to_regnamespace('"Nonexistent"');
+SELECT to_regnamespace('foo.bar');
diff --git a/src/test/regress/sql/reindex_catalog.sql b/src/test/regress/sql/reindex_catalog.sql
new file mode 100644
index 0000000..8203641
--- /dev/null
+++ b/src/test/regress/sql/reindex_catalog.sql
@@ -0,0 +1,52 @@
+--
+-- Check that system tables can be reindexed.
+--
+-- Note that this test currently is not included in the default
+-- schedules, as currently reindexing catalog tables can cause
+-- deadlocks:
+--
+-- * The lock upgrade between the ShareLock acquired for the reindex
+-- and RowExclusiveLock needed for pg_class/pg_index locks can
+-- trigger deadlocks.
+--
+-- * The uniqueness checks performed when reindexing a unique/primary
+-- key index possibly need to wait for the transaction of a
+-- about-to-deleted row in pg_class to commit. That can cause
+-- deadlocks because, in contrast to user tables, locks on catalog
+-- tables are routinely released before commit - therefore the lock
+-- held for reindexing doesn't guarantee that no running transaction
+-- performed modifications in the table underlying the index.
+--
+-- This is particularly problematic as such conflicts can be
+-- triggered even when run in isolation, as a previous session's
+-- temporary table cleanup might still be running (even when the
+-- session ended from a client perspective).
+
+
+-- Check reindexing of whole tables
+REINDEX TABLE pg_class; -- mapped, non-shared, critical
+REINDEX TABLE pg_index; -- non-mapped, non-shared, critical
+REINDEX TABLE pg_operator; -- non-mapped, non-shared, critical
+REINDEX TABLE pg_database; -- mapped, shared, critical
+REINDEX TABLE pg_shdescription; -- mapped, shared non-critical
+
+-- Check that individual system indexes can be reindexed. That's a bit
+-- different from the entire-table case because reindex_relation
+-- treats e.g. pg_class special.
+REINDEX INDEX pg_class_oid_index; -- mapped, non-shared, critical
+REINDEX INDEX pg_class_relname_nsp_index; -- mapped, non-shared, non-critical
+REINDEX INDEX pg_index_indexrelid_index; -- non-mapped, non-shared, critical
+REINDEX INDEX pg_index_indrelid_index; -- non-mapped, non-shared, non-critical
+REINDEX INDEX pg_database_oid_index; -- mapped, shared, critical
+REINDEX INDEX pg_shdescription_o_c_index; -- mapped, shared, non-critical
+
+-- Check the same REINDEX INDEX statements under parallelism.
+BEGIN;
+SET min_parallel_table_scan_size = 0;
+REINDEX INDEX pg_class_oid_index; -- mapped, non-shared, critical
+REINDEX INDEX pg_class_relname_nsp_index; -- mapped, non-shared, non-critical
+REINDEX INDEX pg_index_indexrelid_index; -- non-mapped, non-shared, critical
+REINDEX INDEX pg_index_indrelid_index; -- non-mapped, non-shared, non-critical
+REINDEX INDEX pg_database_oid_index; -- mapped, shared, critical
+REINDEX INDEX pg_shdescription_o_c_index; -- mapped, shared, non-critical
+ROLLBACK;
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
new file mode 100644
index 0000000..4252b02
--- /dev/null
+++ b/src/test/regress/sql/reloptions.sql
@@ -0,0 +1,132 @@
+
+-- Simple create
+CREATE TABLE reloptions_test(i INT) WITH (FiLLFaCToR=30,
+ autovacuum_enabled = false, autovacuum_analyze_scale_factor = 0.2);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- Fail min/max values check
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=2);
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=110);
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor = -10.0);
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor = 110.0);
+
+-- Fail when option and namespace do not exist
+CREATE TABLE reloptions_test2(i INT) WITH (not_existing_option=2);
+CREATE TABLE reloptions_test2(i INT) WITH (not_existing_namespace.fillfactor=2);
+
+-- Fail while setting improper values
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=-30.1);
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor='string');
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=true);
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=12);
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=30.5);
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled='string');
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor='string');
+CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor=true);
+
+-- Fail if option is specified twice
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=30, fillfactor=40);
+
+-- Specifying name only for a non-Boolean option should fail
+CREATE TABLE reloptions_test2(i INT) WITH (fillfactor);
+
+-- Simple ALTER TABLE
+ALTER TABLE reloptions_test SET (fillfactor=31,
+ autovacuum_analyze_scale_factor = 0.3);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- Set boolean option to true without specifying value
+ALTER TABLE reloptions_test SET (autovacuum_enabled, fillfactor=32);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- Check that RESET works well
+ALTER TABLE reloptions_test RESET (fillfactor);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- Resetting all values causes the column to become null
+ALTER TABLE reloptions_test RESET (autovacuum_enabled,
+ autovacuum_analyze_scale_factor);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass AND
+ reloptions IS NULL;
+
+-- RESET fails if a value is specified
+ALTER TABLE reloptions_test RESET (fillfactor=12);
+
+-- Test vacuum_truncate option
+DROP TABLE reloptions_test;
+
+CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
+ WITH (vacuum_truncate=false,
+ toast.vacuum_truncate=false,
+ autovacuum_enabled=false);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL);
+-- Do an aggressive vacuum to prevent page-skipping.
+VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) reloptions_test;
+SELECT pg_relation_size('reloptions_test') > 0;
+
+SELECT reloptions FROM pg_class WHERE oid =
+ (SELECT reltoastrelid FROM pg_class
+ WHERE oid = 'reloptions_test'::regclass);
+
+ALTER TABLE reloptions_test RESET (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL);
+-- Do an aggressive vacuum to prevent page-skipping.
+VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) reloptions_test;
+SELECT pg_relation_size('reloptions_test') = 0;
+
+-- Test toast.* options
+DROP TABLE reloptions_test;
+
+CREATE TABLE reloptions_test (s VARCHAR)
+ WITH (toast.autovacuum_vacuum_cost_delay = 23);
+SELECT reltoastrelid as toast_oid
+ FROM pg_class WHERE oid = 'reloptions_test'::regclass \gset
+SELECT reloptions FROM pg_class WHERE oid = :toast_oid;
+
+ALTER TABLE reloptions_test SET (toast.autovacuum_vacuum_cost_delay = 24);
+SELECT reloptions FROM pg_class WHERE oid = :toast_oid;
+
+ALTER TABLE reloptions_test RESET (toast.autovacuum_vacuum_cost_delay);
+SELECT reloptions FROM pg_class WHERE oid = :toast_oid;
+
+-- Fail on non-existent options in toast namespace
+CREATE TABLE reloptions_test2 (i int) WITH (toast.not_existing_option = 42);
+
+-- Mix TOAST & heap
+DROP TABLE reloptions_test;
+
+CREATE TABLE reloptions_test (s VARCHAR) WITH
+ (toast.autovacuum_vacuum_cost_delay = 23,
+ autovacuum_vacuum_cost_delay = 24, fillfactor = 40);
+
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+SELECT reloptions FROM pg_class WHERE oid = (
+ SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass);
+
+--
+-- CREATE INDEX, ALTER INDEX for btrees
+--
+
+CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (fillfactor=30);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx'::regclass;
+
+-- Fail when option and namespace do not exist
+CREATE INDEX reloptions_test_idx ON reloptions_test (s)
+ WITH (not_existing_option=2);
+CREATE INDEX reloptions_test_idx ON reloptions_test (s)
+ WITH (not_existing_ns.fillfactor=2);
+
+-- Check allowed ranges
+CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor=1);
+CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor=130);
+
+-- Check ALTER
+ALTER INDEX reloptions_test_idx SET (fillfactor=40);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx'::regclass;
+
+-- Check ALTER on empty reloption list
+CREATE INDEX reloptions_test_idx3 ON reloptions_test (s);
+ALTER INDEX reloptions_test_idx3 SET (fillfactor=40);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx3'::regclass;
diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql
new file mode 100644
index 0000000..33da829
--- /dev/null
+++ b/src/test/regress/sql/replica_identity.sql
@@ -0,0 +1,104 @@
+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)
+) ;
+
+CREATE TABLE test_replica_identity_othertable (id serial primary key);
+
+CREATE INDEX test_replica_identity_keyab ON test_replica_identity (keya, keyb);
+CREATE UNIQUE INDEX test_replica_identity_keyab_key ON test_replica_identity (keya, keyb);
+CREATE UNIQUE INDEX test_replica_identity_nonkey ON test_replica_identity (keya, nonkey);
+CREATE INDEX test_replica_identity_hash ON test_replica_identity USING hash (nonkey);
+CREATE UNIQUE INDEX test_replica_identity_expr ON test_replica_identity (keya, keyb, (3));
+CREATE UNIQUE INDEX test_replica_identity_partial ON test_replica_identity (keya, keyb) WHERE keyb != '3';
+
+-- default is 'd'/DEFAULT 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;
+
+----
+-- Make sure we detect ineligible indexes
+----
+
+-- fail, not unique
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab;
+-- fail, not a candidate key, nullable column
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_nonkey;
+-- fail, hash indexes cannot do uniqueness
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_hash;
+-- fail, expression index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_expr;
+-- fail, partial index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_partial;
+-- fail, not our index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_othertable_pkey;
+-- fail, deferrable
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_defer;
+
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+
+----
+-- Make sure index cases succeed
+----
+
+-- succeed, primary key
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_pkey;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+\d test_replica_identity
+
+-- succeed, nondeferrable unique constraint over nonnullable cols
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_nondefer;
+
+-- succeed unique index over nonnullable cols
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key;
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+\d test_replica_identity
+SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident;
+
+----
+-- 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;
+
+---
+-- Test that ALTER TABLE rewrite preserves nondefault replica identity
+---
+
+-- constraint variant
+CREATE TABLE test_replica_identity2 (id int UNIQUE NOT NULL);
+ALTER TABLE test_replica_identity2 REPLICA IDENTITY USING INDEX test_replica_identity2_id_key;
+\d test_replica_identity2
+ALTER TABLE test_replica_identity2 ALTER COLUMN id TYPE bigint;
+\d test_replica_identity2
+
+-- straight index variant
+CREATE TABLE test_replica_identity3 (id int NOT NULL);
+CREATE UNIQUE INDEX test_replica_identity3_id_key ON test_replica_identity3 (id);
+ALTER TABLE test_replica_identity3 REPLICA IDENTITY USING INDEX test_replica_identity3_id_key;
+\d test_replica_identity3
+ALTER TABLE test_replica_identity3 ALTER COLUMN id TYPE bigint;
+\d test_replica_identity3
+
+-- ALTER TABLE DROP NOT NULL is not allowed for columns part of an index
+-- used as replica identity.
+ALTER TABLE test_replica_identity3 ALTER COLUMN id DROP NOT NULL;
+
+DROP TABLE test_replica_identity;
+DROP TABLE test_replica_identity2;
+DROP TABLE test_replica_identity3;
+DROP TABLE test_replica_identity_othertable;
diff --git a/src/test/regress/sql/returning.sql b/src/test/regress/sql/returning.sql
new file mode 100644
index 0000000..a460f82
--- /dev/null
+++ b/src/test/regress/sql/returning.sql
@@ -0,0 +1,162 @@
+--
+-- Test INSERT/UPDATE/DELETE RETURNING
+--
+
+-- Simple cases
+
+CREATE TEMP TABLE foo (f1 serial, f2 text, f3 int default 42);
+
+INSERT INTO foo (f2,f3)
+ VALUES ('test', DEFAULT), ('More', 11), (upper('more'), 7+9)
+ RETURNING *, f1+f3 AS sum;
+
+SELECT * FROM foo;
+
+UPDATE foo SET f2 = lower(f2), f3 = DEFAULT RETURNING foo.*, f1+f3 AS sum13;
+
+SELECT * FROM foo;
+
+DELETE FROM foo WHERE f1 > 2 RETURNING f3, f2, f1, least(f1,f3);
+
+SELECT * FROM foo;
+
+-- Subplans and initplans in the RETURNING list
+
+INSERT INTO foo SELECT f1+10, f2, f3+99 FROM foo
+ RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan,
+ EXISTS(SELECT * FROM int4_tbl) AS initplan;
+
+UPDATE foo SET f3 = f3 * 2
+ WHERE f1 > 10
+ RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan,
+ EXISTS(SELECT * FROM int4_tbl) AS initplan;
+
+DELETE FROM foo
+ WHERE f1 > 10
+ RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan,
+ EXISTS(SELECT * FROM int4_tbl) AS initplan;
+
+-- Joins
+
+UPDATE foo SET f3 = f3*2
+ FROM int4_tbl i
+ WHERE foo.f1 + 123455 = i.f1
+ RETURNING foo.*, i.f1 as "i.f1";
+
+SELECT * FROM foo;
+
+DELETE FROM foo
+ USING int4_tbl i
+ WHERE foo.f1 + 123455 = i.f1
+ RETURNING foo.*, i.f1 as "i.f1";
+
+SELECT * FROM foo;
+
+-- Check inheritance cases
+
+CREATE TEMP TABLE foochild (fc int) INHERITS (foo);
+
+INSERT INTO foochild VALUES(123,'child',999,-123);
+
+ALTER TABLE foo ADD COLUMN f4 int8 DEFAULT 99;
+
+SELECT * FROM foo;
+SELECT * FROM foochild;
+
+UPDATE foo SET f4 = f4 + f3 WHERE f4 = 99 RETURNING *;
+
+SELECT * FROM foo;
+SELECT * FROM foochild;
+
+UPDATE foo SET f3 = f3*2
+ FROM int8_tbl i
+ WHERE foo.f1 = i.q2
+ RETURNING *;
+
+SELECT * FROM foo;
+SELECT * FROM foochild;
+
+DELETE FROM foo
+ USING int8_tbl i
+ WHERE foo.f1 = i.q2
+ RETURNING *;
+
+SELECT * FROM foo;
+SELECT * FROM foochild;
+
+DROP TABLE foochild;
+
+-- Rules and views
+
+CREATE TEMP VIEW voo AS SELECT f1, f2 FROM foo;
+
+CREATE RULE voo_i AS ON INSERT TO voo DO INSTEAD
+ INSERT INTO foo VALUES(new.*, 57);
+
+INSERT INTO voo VALUES(11,'zit');
+-- fails:
+INSERT INTO voo VALUES(12,'zoo') RETURNING *, f1*2;
+
+-- fails, incompatible list:
+CREATE OR REPLACE RULE voo_i AS ON INSERT TO voo DO INSTEAD
+ INSERT INTO foo VALUES(new.*, 57) RETURNING *;
+
+CREATE OR REPLACE RULE voo_i AS ON INSERT TO voo DO INSTEAD
+ INSERT INTO foo VALUES(new.*, 57) RETURNING f1, f2;
+
+-- should still work
+INSERT INTO voo VALUES(13,'zit2');
+-- works now
+INSERT INTO voo VALUES(14,'zoo2') RETURNING *;
+
+SELECT * FROM foo;
+SELECT * FROM voo;
+
+CREATE OR REPLACE RULE voo_u AS ON UPDATE TO voo DO INSTEAD
+ UPDATE foo SET f1 = new.f1, f2 = new.f2 WHERE f1 = old.f1
+ RETURNING f1, f2;
+
+update voo set f1 = f1 + 1 where f2 = 'zoo2';
+update voo set f1 = f1 + 1 where f2 = 'zoo2' RETURNING *, f1*2;
+
+SELECT * FROM foo;
+SELECT * FROM voo;
+
+CREATE OR REPLACE RULE voo_d AS ON DELETE TO voo DO INSTEAD
+ DELETE FROM foo WHERE f1 = old.f1
+ RETURNING f1, f2;
+
+DELETE FROM foo WHERE f1 = 13;
+DELETE FROM foo WHERE f2 = 'zit' RETURNING *;
+
+SELECT * FROM foo;
+SELECT * FROM voo;
+
+-- Try a join case
+
+CREATE TEMP TABLE joinme (f2j text, other int);
+INSERT INTO joinme VALUES('more', 12345);
+INSERT INTO joinme VALUES('zoo2', 54321);
+INSERT INTO joinme VALUES('other', 0);
+
+CREATE TEMP VIEW joinview AS
+ SELECT foo.*, other FROM foo JOIN joinme ON (f2 = f2j);
+
+SELECT * FROM joinview;
+
+CREATE RULE joinview_u AS ON UPDATE TO joinview DO INSTEAD
+ UPDATE foo SET f1 = new.f1, f3 = new.f3
+ FROM joinme WHERE f2 = f2j AND f2 = old.f2
+ RETURNING foo.*, other;
+
+UPDATE joinview SET f1 = f1 + 1 WHERE f3 = 57 RETURNING *, other + 1;
+
+SELECT * FROM joinview;
+SELECT * FROM foo;
+SELECT * FROM voo;
+
+-- Check aliased target relation
+INSERT INTO foo AS bar DEFAULT VALUES RETURNING *; -- ok
+INSERT INTO foo AS bar DEFAULT VALUES RETURNING foo.*; -- fails, wrong name
+INSERT INTO foo AS bar DEFAULT VALUES RETURNING bar.*; -- ok
+INSERT INTO foo AS bar DEFAULT VALUES RETURNING bar.f3; -- ok
diff --git a/src/test/regress/sql/roleattributes.sql b/src/test/regress/sql/roleattributes.sql
new file mode 100644
index 0000000..c961b2d
--- /dev/null
+++ b/src/test/regress/sql/roleattributes.sql
@@ -0,0 +1,98 @@
+-- default for superuser is false
+CREATE ROLE regress_test_def_superuser;
+
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
+CREATE ROLE regress_test_superuser WITH SUPERUSER;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ALTER ROLE regress_test_superuser WITH NOSUPERUSER;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ALTER ROLE regress_test_superuser WITH SUPERUSER;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+
+-- default for inherit is true
+CREATE ROLE regress_test_def_inherit;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
+CREATE ROLE regress_test_inherit WITH NOINHERIT;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ALTER ROLE regress_test_inherit WITH INHERIT;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ALTER ROLE regress_test_inherit WITH NOINHERIT;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+
+-- default for create role is false
+CREATE ROLE regress_test_def_createrole;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
+CREATE ROLE regress_test_createrole WITH CREATEROLE;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ALTER ROLE regress_test_createrole WITH NOCREATEROLE;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ALTER ROLE regress_test_createrole WITH CREATEROLE;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+
+-- default for create database is false
+CREATE ROLE regress_test_def_createdb;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
+CREATE ROLE regress_test_createdb WITH CREATEDB;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ALTER ROLE regress_test_createdb WITH NOCREATEDB;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ALTER ROLE regress_test_createdb WITH CREATEDB;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+
+-- default for can login is false for role
+CREATE ROLE regress_test_def_role_canlogin;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
+CREATE ROLE regress_test_role_canlogin WITH LOGIN;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ALTER ROLE regress_test_role_canlogin WITH NOLOGIN;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ALTER ROLE regress_test_role_canlogin WITH LOGIN;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+
+-- default for can login is true for user
+CREATE USER regress_test_def_user_canlogin;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
+CREATE USER regress_test_user_canlogin WITH NOLOGIN;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ALTER USER regress_test_user_canlogin WITH LOGIN;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ALTER USER regress_test_user_canlogin WITH NOLOGIN;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+
+-- default for replication is false
+CREATE ROLE regress_test_def_replication;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
+CREATE ROLE regress_test_replication WITH REPLICATION;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ALTER ROLE regress_test_replication WITH NOREPLICATION;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ALTER ROLE regress_test_replication WITH REPLICATION;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+
+-- default for bypassrls is false
+CREATE ROLE regress_test_def_bypassrls;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
+CREATE ROLE regress_test_bypassrls WITH BYPASSRLS;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ALTER ROLE regress_test_bypassrls WITH NOBYPASSRLS;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ALTER ROLE regress_test_bypassrls WITH BYPASSRLS;
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+
+-- clean up roles
+DROP ROLE regress_test_def_superuser;
+DROP ROLE regress_test_superuser;
+DROP ROLE regress_test_def_inherit;
+DROP ROLE regress_test_inherit;
+DROP ROLE regress_test_def_createrole;
+DROP ROLE regress_test_createrole;
+DROP ROLE regress_test_def_createdb;
+DROP ROLE regress_test_createdb;
+DROP ROLE regress_test_def_role_canlogin;
+DROP ROLE regress_test_role_canlogin;
+DROP USER regress_test_def_user_canlogin;
+DROP USER regress_test_user_canlogin;
+DROP ROLE regress_test_def_replication;
+DROP ROLE regress_test_replication;
+DROP ROLE regress_test_def_bypassrls;
+DROP ROLE regress_test_bypassrls;
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index 0000000..44deb42
--- /dev/null
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -0,0 +1,1873 @@
+--
+-- 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;
+
+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);
+
+DELETE FROM only t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
+DELETE FROM t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
+
+--
+-- 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;
+GRANT ALL ON bv1 TO regress_rls_carol;
+
+SET SESSION AUTHORIZATION regress_rls_carol;
+
+EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);
+SELECT * FROM bv1 WHERE f_leak(b);
+
+INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO
+INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check
+INSERT INTO bv1 VALUES (12, 'xxx'); -- ok
+
+EXPLAIN (COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+
+EXPLAIN (COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM b1;
+--
+-- 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';
+
+--
+-- 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;
+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');
+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;
+
+DROP TABLE t1 CASCADE;
+
+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 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.
+SELECT id, author, message FROM blog JOIN comment ON id = blog_id;
+-- Check Non-RLS JOIN with RLS.
+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;
+-- Still cannot UPDATE row 2 through cursor
+UPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;
+-- Can update row 4 through cursor, which is the next visible row
+FETCH RELATIVE 1 FROM current_check_cursor;
+UPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;
+SELECT * FROM current_check;
+-- Plan should be a subquery TID scan
+EXPLAIN (COSTS OFF) UPDATE current_check SET payload = payload WHERE CURRENT OF current_check_cursor;
+-- Similarly can only delete row 4
+FETCH ABSOLUTE 1 FROM current_check_cursor;
+DELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;
+FETCH RELATIVE 1 FROM current_check_cursor;
+DELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;
+SELECT * FROM current_check;
+
+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;
+
+--
+-- 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
+
+--
+-- 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;
+
+--
+-- 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;
+
+--
+-- 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;
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+
+DROP SCHEMA regress_rls_schema CASCADE;
+
+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/rowtypes.sql b/src/test/regress/sql/rowtypes.sql
new file mode 100644
index 0000000..ad5b7e1
--- /dev/null
+++ b/src/test/regress/sql/rowtypes.sql
@@ -0,0 +1,506 @@
+--
+-- 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));
+
+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;
+
+-- 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;
+
+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;
+
+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;
+
+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);
+
+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;
+
+--
+-- 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/rules.sql b/src/test/regress/sql/rules.sql
new file mode 100644
index 0000000..b732833
--- /dev/null
+++ b/src/test/regress/sql/rules.sql
@@ -0,0 +1,1259 @@
+--
+-- 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;
+
+-- 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;
+** 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;
+delete from rtest_v1;
+
+-- same with swapped targetlist
+insert into rtest_v1 (b, a) select b, a from rtest_t2;
+select * from rtest_v1;
+
+-- now with only one target attribute
+insert into rtest_v1 (a) select a from rtest_t3;
+select * from rtest_v1;
+select * from rtest_v1 where b isnull;
+
+-- 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;
+
+-- now updates with constant expression
+update rtest_v1 set b = 42 where a = 2;
+select * from rtest_v1;
+update rtest_v1 set b = 99 where b = 42;
+select * from rtest_v1;
+update rtest_v1 set b = 88 where b < 50;
+select * from rtest_v1;
+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;
+
+-- 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;
+insert into rtest_v1 select * from rtest_t3;
+select * from rtest_v1;
+update rtest_t1 set a = a + 10 where b > 30;
+select * from rtest_v1;
+update rtest_v1 set a = rtest_t3.a + 20 from rtest_t3 where rtest_v1.b = rtest_t3.b;
+select * from rtest_v1;
+
+--
+-- 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;
+select * from rtest_admin;
+
+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;
+select * from rtest_admin;
+
+--
+-- 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;
+select * from rtest_t5;
+select * from rtest_t6;
+select * from rtest_t7;
+select * from rtest_t8;
+
+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;
+select * from rtest_t5;
+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;
+select * from rtest_t5;
+select * from rtest_t6;
+select * from rtest_t7;
+select * from rtest_t8;
+
+insert into rtest_t4 select a + 1, b from rtest_t9 where a in (20, 30, 40);
+
+select * from rtest_t4;
+select * from rtest_t5;
+select * from rtest_t6;
+select * from rtest_t7;
+select * from rtest_t8;
+
+--
+-- Check that the ordering of rules fired is correct
+--
+insert into rtest_order1 values (1);
+select * from rtest_order2;
+
+--
+-- 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;
+
+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;
+
+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;
+
+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;
+
+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;
+select * from rtest_vview2;
+select * from rtest_vview3;
+select * from rtest_vview4 order by a, b;
+select * from rtest_vview5;
+
+insert into rtest_view3 select * from rtest_vview1 where a < 7;
+select * from rtest_view3;
+delete from rtest_view3;
+
+insert into rtest_view3 select * from rtest_vview2 where a != 5 and b !~ '2';
+select * from rtest_view3;
+delete from rtest_view3;
+
+insert into rtest_view3 select * from rtest_vview3;
+select * from rtest_view3;
+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;
+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;
+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;
+update vview set descrip='test1' where pid=1;
+select * from vview;
+update vview set descrip='test2' where pid=2;
+select * from vview;
+update vview set descrip='test3' where pid=3;
+select * from vview;
+select * from cchild;
+
+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
+
+SELECT viewname, definition FROM pg_views
+WHERE schemaname IN ('pg_catalog', 'public')
+ORDER BY viewname;
+
+SELECT tablename, rulename, definition FROM pg_rules
+WHERE schemaname IN ('pg_catalog', 'public')
+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;
+
+-- Check that rewrite rules splitting one INSERT into multiple
+-- conditional statements does not disable FK checking.
+create table rule_and_refint_t1 (
+ id1a integer,
+ id1b integer,
+
+ primary key (id1a, id1b)
+);
+
+create table rule_and_refint_t2 (
+ id2a integer,
+ id2c integer,
+
+ primary key (id2a, id2c)
+);
+
+create table rule_and_refint_t3 (
+ id3a integer,
+ id3b integer,
+ id3c integer,
+ data text,
+
+ primary key (id3a, id3b, id3c),
+
+ foreign key (id3a, id3b) references rule_and_refint_t1 (id1a, id1b),
+ foreign key (id3a, id3c) references rule_and_refint_t2 (id2a, id2c)
+);
+
+
+insert into rule_and_refint_t1 values (1, 11);
+insert into rule_and_refint_t1 values (1, 12);
+insert into rule_and_refint_t1 values (2, 21);
+insert into rule_and_refint_t1 values (2, 22);
+
+insert into rule_and_refint_t2 values (1, 11);
+insert into rule_and_refint_t2 values (1, 12);
+insert into rule_and_refint_t2 values (2, 21);
+insert into rule_and_refint_t2 values (2, 22);
+
+insert into rule_and_refint_t3 values (1, 11, 11, 'row1');
+insert into rule_and_refint_t3 values (1, 11, 12, 'row2');
+insert into rule_and_refint_t3 values (1, 12, 11, 'row3');
+insert into rule_and_refint_t3 values (1, 12, 12, 'row4');
+insert into rule_and_refint_t3 values (1, 11, 13, 'row5');
+insert into rule_and_refint_t3 values (1, 13, 11, 'row6');
+-- Ordinary table
+insert into rule_and_refint_t3 values (1, 13, 11, 'row6')
+ on conflict do nothing;
+-- rule not fired, so fk violation
+insert into rule_and_refint_t3 values (1, 13, 11, 'row6')
+ on conflict (id3a, id3b, id3c) do update
+ set id3b = excluded.id3b;
+-- rule fired, so unsupported
+insert into shoelace values ('sl9', 0, 'pink', 35.0, 'inch', 0.0)
+ on conflict (sl_name) do update
+ set sl_avail = excluded.sl_avail;
+
+create rule rule_and_refint_t3_ins as on insert to rule_and_refint_t3
+ where (exists (select 1 from rule_and_refint_t3
+ where (((rule_and_refint_t3.id3a = new.id3a)
+ and (rule_and_refint_t3.id3b = new.id3b))
+ and (rule_and_refint_t3.id3c = new.id3c))))
+ do instead update rule_and_refint_t3 set data = new.data
+ where (((rule_and_refint_t3.id3a = new.id3a)
+ and (rule_and_refint_t3.id3b = new.id3b))
+ and (rule_and_refint_t3.id3c = new.id3c));
+
+insert into rule_and_refint_t3 values (1, 11, 13, 'row7');
+insert into rule_and_refint_t3 values (1, 13, 11, 'row8');
+
+--
+-- disallow dropping a view's rule (bug #5072)
+--
+
+create view rules_fooview as select 'rules_foo'::text;
+drop rule "_RETURN" on rules_fooview;
+drop view rules_fooview;
+
+--
+-- test conversion of table to view (needed to load some pg_dump files)
+--
+
+create table rules_fooview (x int, y text);
+select xmin, * from rules_fooview;
+
+create rule "_RETURN" as on select to rules_fooview do instead
+ select 1 as x, 'aaa'::text as y;
+
+select * from rules_fooview;
+select xmin, * from rules_fooview; -- fail, views don't have such a column
+
+select reltoastrelid, relkind, relfrozenxid
+ from pg_class where oid = 'rules_fooview'::regclass;
+
+drop view rules_fooview;
+
+-- cannot convert an inheritance parent or child to a view, though
+create table rules_fooview (x int, y text);
+create table rules_fooview_child () inherits (rules_fooview);
+
+create rule "_RETURN" as on select to rules_fooview do instead
+ select 1 as x, 'aaa'::text as y;
+create rule "_RETURN" as on select to rules_fooview_child do instead
+ select 1 as x, 'aaa'::text as y;
+
+drop table rules_fooview cascade;
+
+-- likewise, converting a partitioned table or partition to view is not allowed
+create table rules_fooview (x int, y text) partition by list (x);
+create rule "_RETURN" as on select to rules_fooview do instead
+ select 1 as x, 'aaa'::text as y;
+
+create table rules_fooview_part partition of rules_fooview for values in (1);
+create rule "_RETURN" as on select to rules_fooview_part do instead
+ select 1 as x, 'aaa'::text as y;
+
+drop table rules_fooview;
+
+--
+-- check for planner problems with complex inherited UPDATES
+--
+
+create table id (id serial primary key, name text);
+-- currently, must respecify PKEY for each inherited subtable
+create table test_1 (id integer primary key) inherits (id);
+create table test_2 (id integer primary key) inherits (id);
+create table test_3 (id integer primary key) inherits (id);
+
+insert into test_1 (name) values ('Test 1');
+insert into test_1 (name) values ('Test 2');
+insert into test_2 (name) values ('Test 3');
+insert into test_2 (name) values ('Test 4');
+insert into test_3 (name) values ('Test 5');
+insert into test_3 (name) values ('Test 6');
+
+create view id_ordered as select * from id order by id;
+
+create rule update_id_ordered as on update to id_ordered
+ do instead update id set name = new.name where id = old.id;
+
+select * from id_ordered;
+update id_ordered set name = 'update 2' where id = 2;
+update id_ordered set name = 'update 4' where id = 4;
+update id_ordered set name = 'update 5' where id = 5;
+select * from id_ordered;
+
+drop table id cascade;
+
+--
+-- check corner case where an entirely-dummy subplan is created by
+-- constraint exclusion
+--
+
+create temp table t1 (a integer primary key);
+
+create temp table t1_1 (check (a >= 0 and a < 10)) inherits (t1);
+create temp table t1_2 (check (a >= 10 and a < 20)) inherits (t1);
+
+create rule t1_ins_1 as on insert to t1
+ where new.a >= 0 and new.a < 10
+ do instead
+ insert into t1_1 values (new.a);
+create rule t1_ins_2 as on insert to t1
+ where new.a >= 10 and new.a < 20
+ do instead
+ insert into t1_2 values (new.a);
+
+create rule t1_upd_1 as on update to t1
+ where old.a >= 0 and old.a < 10
+ do instead
+ update t1_1 set a = new.a where a = old.a;
+create rule t1_upd_2 as on update to t1
+ where old.a >= 10 and old.a < 20
+ do instead
+ update t1_2 set a = new.a where a = old.a;
+
+set constraint_exclusion = on;
+
+insert into t1 select * from generate_series(5,19,1) g;
+update t1 set a = 4 where a = 5;
+
+select * from only t1;
+select * from only t1_1;
+select * from only t1_2;
+
+reset constraint_exclusion;
+
+-- 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 various flavors of pg_get_viewdef()
+
+select pg_get_viewdef('shoe'::regclass) as unpretty;
+select pg_get_viewdef('shoe'::regclass,true) as pretty;
+select pg_get_viewdef('shoe'::regclass,0) as prettier;
+
+--
+-- check multi-row VALUES in rules
+--
+
+create table rules_src(f1 int, f2 int);
+create table rules_log(f1 int, f2 int, tag text);
+insert into rules_src values(1,2), (11,12);
+create rule r1 as on update to rules_src do also
+ insert into rules_log values(old.*, 'old'), (new.*, 'new');
+update rules_src set f2 = f2 + 1;
+update rules_src set f2 = f2 * 10;
+select * from rules_src;
+select * from rules_log;
+create rule r2 as on update to rules_src do also
+ values(old.*, 'old'), (new.*, 'new');
+update rules_src set f2 = f2 / 10;
+select * from rules_src;
+select * from rules_log;
+create rule r3 as on delete to rules_src do notify rules_src_deletion;
+\d+ rules_src
+
+--
+-- Ensure an aliased target relation for insert is correctly deparsed.
+--
+create rule r4 as on insert to rules_src do instead insert into rules_log AS trgt SELECT NEW.* RETURNING trgt.f1, trgt.f2;
+create rule r5 as on update to rules_src do instead UPDATE rules_log AS trgt SET tag = 'updated' WHERE trgt.f1 = new.f1;
+\d+ rules_src
+
+--
+-- Also check multiassignment deparsing.
+--
+create table rule_t1(f1 int, f2 int);
+create table rule_dest(f1 int, f2 int[], tag text);
+create rule rr as on update to rule_t1 do instead UPDATE rule_dest trgt
+ SET (f2[1], f1, tag) = (SELECT new.f2, new.f1, 'updated'::varchar)
+ WHERE trgt.f1 = new.f1 RETURNING new.*;
+\d+ rule_t1
+drop table rule_t1, rule_dest;
+
+--
+-- check alter rename rule
+--
+CREATE TABLE rule_t1 (a INT);
+CREATE VIEW rule_v1 AS SELECT * FROM rule_t1;
+
+CREATE RULE InsertRule AS
+ ON INSERT TO rule_v1
+ DO INSTEAD
+ INSERT INTO rule_t1 VALUES(new.a);
+
+ALTER RULE InsertRule ON rule_v1 RENAME to NewInsertRule;
+
+INSERT INTO rule_v1 VALUES(1);
+SELECT * FROM rule_v1;
+
+\d+ rule_v1
+
+--
+-- error conditions for alter rename rule
+--
+ALTER RULE InsertRule ON rule_v1 RENAME TO NewInsertRule; -- doesn't exist
+ALTER RULE NewInsertRule ON rule_v1 RENAME TO "_RETURN"; -- already exists
+ALTER RULE "_RETURN" ON rule_v1 RENAME TO abc; -- ON SELECT rule cannot be renamed
+
+DROP VIEW rule_v1;
+DROP TABLE rule_t1;
+
+--
+-- check display of VALUES in view definitions
+--
+create view rule_v1 as values(1,2);
+\d+ rule_v1
+alter table rule_v1 rename column column2 to q2;
+\d+ rule_v1
+drop view rule_v1;
+create view rule_v1(x) as values(1,2);
+\d+ rule_v1
+drop view rule_v1;
+create view rule_v1(x) as select * from (values(1,2)) v;
+\d+ rule_v1
+drop view rule_v1;
+create view rule_v1(x) as select * from (values(1,2)) v(q,w);
+\d+ rule_v1
+drop view rule_v1;
+
+--
+-- Check DO INSTEAD rules with ON CONFLICT
+--
+CREATE TABLE hats (
+ hat_name char(10) primary key,
+ hat_color char(10) -- hat color
+);
+
+CREATE TABLE hat_data (
+ hat_name char(10),
+ hat_color char(10) -- hat color
+);
+create unique index hat_data_unique_idx
+ on hat_data (hat_name COLLATE "C" bpchar_pattern_ops);
+
+-- DO NOTHING with ON CONFLICT
+CREATE RULE hat_nosert AS ON INSERT TO hats
+ DO INSTEAD
+ INSERT INTO hat_data VALUES (
+ NEW.hat_name,
+ NEW.hat_color)
+ ON CONFLICT (hat_name COLLATE "C" bpchar_pattern_ops) WHERE hat_color = 'green'
+ DO NOTHING
+ RETURNING *;
+SELECT definition FROM pg_rules WHERE tablename = 'hats' ORDER BY rulename;
+
+-- Works (projects row)
+INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
+-- Works (does nothing)
+INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
+SELECT tablename, rulename, definition FROM pg_rules
+ WHERE tablename = 'hats';
+DROP RULE hat_nosert ON hats;
+
+-- DO NOTHING without ON CONFLICT
+CREATE RULE hat_nosert_all AS ON INSERT TO hats
+ DO INSTEAD
+ INSERT INTO hat_data VALUES (
+ NEW.hat_name,
+ NEW.hat_color)
+ ON CONFLICT
+ DO NOTHING
+ RETURNING *;
+SELECT definition FROM pg_rules WHERE tablename = 'hats' ORDER BY rulename;
+DROP RULE hat_nosert_all ON hats;
+
+-- Works (does nothing)
+INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
+
+-- DO UPDATE with a WHERE clause
+CREATE RULE hat_upsert AS ON INSERT TO hats
+ DO INSTEAD
+ INSERT INTO hat_data VALUES (
+ NEW.hat_name,
+ NEW.hat_color)
+ ON CONFLICT (hat_name)
+ DO UPDATE
+ SET hat_name = hat_data.hat_name, hat_color = excluded.hat_color
+ WHERE excluded.hat_color <> 'forbidden' AND hat_data.* != excluded.*
+ RETURNING *;
+SELECT definition FROM pg_rules WHERE tablename = 'hats' ORDER BY rulename;
+
+-- Works (does upsert)
+INSERT INTO hats VALUES ('h8', 'black') RETURNING *;
+SELECT * FROM hat_data WHERE hat_name = 'h8';
+INSERT INTO hats VALUES ('h8', 'white') RETURNING *;
+SELECT * FROM hat_data WHERE hat_name = 'h8';
+INSERT INTO hats VALUES ('h8', 'forbidden') RETURNING *;
+SELECT * FROM hat_data WHERE hat_name = 'h8';
+SELECT tablename, rulename, definition FROM pg_rules
+ WHERE tablename = 'hats';
+-- ensure explain works for on insert conflict rules
+explain (costs off) INSERT INTO hats VALUES ('h8', 'forbidden') RETURNING *;
+
+-- ensure upserting into a rule, with a CTE (different offsets!) works
+WITH data(hat_name, hat_color) AS MATERIALIZED (
+ VALUES ('h8', 'green'),
+ ('h9', 'blue'),
+ ('h7', 'forbidden')
+)
+INSERT INTO hats
+ SELECT * FROM data
+RETURNING *;
+EXPLAIN (costs off)
+WITH data(hat_name, hat_color) AS MATERIALIZED (
+ VALUES ('h8', 'green'),
+ ('h9', 'blue'),
+ ('h7', 'forbidden')
+)
+INSERT INTO hats
+ SELECT * FROM data
+RETURNING *;
+SELECT * FROM hat_data WHERE hat_name IN ('h8', 'h9', 'h7') ORDER BY hat_name;
+
+DROP RULE hat_upsert ON hats;
+
+drop table hats;
+drop table hat_data;
+
+-- test for pg_get_functiondef properly regurgitating SET parameters
+-- Note that the function is kept around to stress pg_dump.
+CREATE FUNCTION func_with_set_params() RETURNS integer
+ AS 'select 1;'
+ LANGUAGE SQL
+ SET search_path TO PG_CATALOG
+ SET extra_float_digits TO 2
+ SET work_mem TO '4MB'
+ SET datestyle to iso, mdy
+ SET local_preload_libraries TO "Mixed/Case", 'c:/''a"/path', '', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789'
+ IMMUTABLE STRICT;
+SELECT pg_get_functiondef('func_with_set_params()'::regprocedure);
+
+-- tests for pg_get_*def with invalid objects
+SELECT pg_get_constraintdef(0);
+SELECT pg_get_functiondef(0);
+SELECT pg_get_indexdef(0);
+SELECT pg_get_ruledef(0);
+SELECT pg_get_statisticsobjdef(0);
+SELECT pg_get_triggerdef(0);
+SELECT pg_get_viewdef(0);
+SELECT pg_get_function_arguments(0);
+SELECT pg_get_function_identity_arguments(0);
+SELECT pg_get_function_result(0);
+SELECT pg_get_function_arg_default(0, 0);
+SELECT pg_get_function_arg_default('pg_class'::regclass, 0);
+SELECT pg_get_partkeydef(0);
+
+-- test rename for a rule defined on a partitioned table
+CREATE TABLE rules_parted_table (a int) PARTITION BY LIST (a);
+CREATE TABLE rules_parted_table_1 PARTITION OF rules_parted_table FOR VALUES IN (1);
+CREATE RULE rules_parted_table_insert AS ON INSERT to rules_parted_table
+ DO INSTEAD INSERT INTO rules_parted_table_1 VALUES (NEW.*);
+ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parted_table_insert_redirect;
+DROP TABLE rules_parted_table;
+
+--
+-- Test enabling/disabling
+--
+CREATE TABLE ruletest1 (a int);
+CREATE TABLE ruletest2 (b int);
+
+CREATE RULE rule1 AS ON INSERT TO ruletest1
+ DO INSTEAD INSERT INTO ruletest2 VALUES (NEW.*);
+
+INSERT INTO ruletest1 VALUES (1);
+ALTER TABLE ruletest1 DISABLE RULE rule1;
+INSERT INTO ruletest1 VALUES (2);
+ALTER TABLE ruletest1 ENABLE RULE rule1;
+SET session_replication_role = replica;
+INSERT INTO ruletest1 VALUES (3);
+ALTER TABLE ruletest1 ENABLE REPLICA RULE rule1;
+INSERT INTO ruletest1 VALUES (4);
+RESET session_replication_role;
+INSERT INTO ruletest1 VALUES (5);
+
+SELECT * FROM ruletest1;
+SELECT * FROM ruletest2;
+
+DROP TABLE ruletest1;
+DROP TABLE ruletest2;
diff --git a/src/test/regress/sql/sanity_check.sql b/src/test/regress/sql/sanity_check.sql
new file mode 100644
index 0000000..a4ec003
--- /dev/null
+++ b/src/test/regress/sql/sanity_check.sql
@@ -0,0 +1,39 @@
+VACUUM;
+
+--
+-- sanity check, if we don't have indices the test will take years to
+-- complete. But skip TOAST relations (since they will have varying
+-- names depending on the current OID counter) as well as temp tables
+-- of other backends (to avoid timing-dependent behavior).
+--
+
+-- temporarily disable fancy output, so catalog changes create less diff noise
+\a\t
+
+SELECT relname, relhasindex
+ FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace
+ WHERE relkind IN ('r', 'p') AND (nspname ~ '^pg_temp_') IS NOT TRUE
+ ORDER BY relname;
+
+-- restore normal output mode
+\a\t
+
+--
+-- another sanity check: every system catalog that has OIDs should have
+-- a unique index on OID. This ensures that the OIDs will be unique,
+-- even after the OID counter wraps around.
+-- We exclude non-system tables from the check by looking at nspname.
+--
+SELECT relname, nspname
+ FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace JOIN pg_attribute a ON (attrelid = c.oid AND attname = 'oid')
+ WHERE relkind = 'r' and c.oid < 16384
+ AND ((nspname ~ '^pg_') IS NOT FALSE)
+ AND NOT EXISTS (SELECT 1 FROM pg_index i WHERE indrelid = c.oid
+ AND indkey[0] = a.attnum AND indnatts = 1
+ AND indisunique AND indimmediate);
+
+-- check that relations without storage don't have relfilenode
+SELECT relname, relkind
+ FROM pg_class
+ WHERE relkind IN ('v', 'c', 'f', 'p', 'I')
+ AND relfilenode <> 0;
diff --git a/src/test/regress/sql/security_label.sql b/src/test/regress/sql/security_label.sql
new file mode 100644
index 0000000..98e6a5f
--- /dev/null
+++ b/src/test/regress/sql/security_label.sql
@@ -0,0 +1,45 @@
+--
+-- Test for facilities of security label
+--
+
+-- initial setups
+SET client_min_messages TO 'warning';
+
+DROP ROLE IF EXISTS regress_seclabel_user1;
+DROP ROLE IF EXISTS regress_seclabel_user2;
+
+RESET client_min_messages;
+
+CREATE USER regress_seclabel_user1 WITH CREATEROLE;
+CREATE USER regress_seclabel_user2;
+
+CREATE TABLE seclabel_tbl1 (a int, b text);
+CREATE TABLE seclabel_tbl2 (x int, y text);
+CREATE VIEW seclabel_view1 AS SELECT * FROM seclabel_tbl2;
+CREATE FUNCTION seclabel_four() RETURNS integer AS $$SELECT 4$$ language sql;
+CREATE DOMAIN seclabel_domain AS text;
+
+ALTER TABLE seclabel_tbl1 OWNER TO regress_seclabel_user1;
+ALTER TABLE seclabel_tbl2 OWNER TO regress_seclabel_user2;
+
+--
+-- Test of SECURITY LABEL statement without a plugin
+--
+SECURITY LABEL ON TABLE seclabel_tbl1 IS 'classified'; -- fail
+SECURITY LABEL FOR 'dummy' ON TABLE seclabel_tbl1 IS 'classified'; -- fail
+SECURITY LABEL ON TABLE seclabel_tbl1 IS '...invalid label...'; -- fail
+SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified'; -- fail
+
+SECURITY LABEL ON ROLE regress_seclabel_user1 IS 'classified'; -- fail
+SECURITY LABEL FOR 'dummy' ON ROLE regress_seclabel_user1 IS 'classified'; -- fail
+SECURITY LABEL ON ROLE regress_seclabel_user1 IS '...invalid label...'; -- fail
+SECURITY LABEL ON ROLE regress_seclabel_user3 IS 'unclassified'; -- fail
+
+-- clean up objects
+DROP FUNCTION seclabel_four();
+DROP DOMAIN seclabel_domain;
+DROP VIEW seclabel_view1;
+DROP TABLE seclabel_tbl1;
+DROP TABLE seclabel_tbl2;
+DROP USER regress_seclabel_user1;
+DROP USER regress_seclabel_user2;
diff --git a/src/test/regress/sql/select.sql b/src/test/regress/sql/select.sql
new file mode 100644
index 0000000..d6f42aa
--- /dev/null
+++ b/src/test/regress/sql/select.sql
@@ -0,0 +1,269 @@
+--
+-- SELECT
+--
+
+-- 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;
+
+
+SELECT two, stringu1, ten, string4
+ INTO TABLE tmp
+ FROM onek;
+
+--
+-- 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;
+
+--
+-- 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;
+
+--
+-- 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/select_distinct.sql b/src/test/regress/sql/select_distinct.sql
new file mode 100644
index 0000000..3310274
--- /dev/null
+++ b/src/test/regress/sql/select_distinct.sql
@@ -0,0 +1,137 @@
+--
+-- SELECT_DISTINCT
+--
+
+--
+-- awk '{print $3;}' onek.data | sort -n | uniq
+--
+SELECT DISTINCT two FROM tmp ORDER BY 1;
+
+--
+-- awk '{print $5;}' onek.data | sort -n | uniq
+--
+SELECT DISTINCT ten FROM tmp ORDER BY 1;
+
+--
+-- awk '{print $16;}' onek.data | sort -d | uniq
+--
+SELECT DISTINCT string4 FROM tmp 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 tmp
+ 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
+--
+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;
+
+--
+-- 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';
+
+-- Produce results with sorting.
+
+SET enable_hashagg=FALSE;
+
+SET jit_above_cost=0;
+
+EXPLAIN (costs off)
+SELECT DISTINCT g%1000 FROM generate_series(0,9999) g;
+
+CREATE TABLE distinct_group_1 AS
+SELECT DISTINCT g%1000 FROM generate_series(0,9999) g;
+
+SET jit_above_cost TO DEFAULT;
+
+CREATE TABLE distinct_group_2 AS
+SELECT DISTINCT (g%1000)::text FROM generate_series(0,9999) g;
+
+SET enable_hashagg=TRUE;
+
+-- Produce results with hash aggregation.
+
+SET enable_sort=FALSE;
+
+SET jit_above_cost=0;
+
+EXPLAIN (costs off)
+SELECT DISTINCT g%1000 FROM generate_series(0,9999) g;
+
+CREATE TABLE distinct_hash_1 AS
+SELECT DISTINCT g%1000 FROM generate_series(0,9999) g;
+
+SET jit_above_cost TO DEFAULT;
+
+CREATE TABLE distinct_hash_2 AS
+SELECT DISTINCT (g%1000)::text FROM generate_series(0,9999) g;
+
+SET enable_sort=TRUE;
+
+SET work_mem TO DEFAULT;
+
+-- Compare results
+
+(SELECT * FROM distinct_hash_1 EXCEPT SELECT * FROM distinct_group_1)
+ UNION ALL
+(SELECT * FROM distinct_group_1 EXCEPT SELECT * FROM distinct_hash_1);
+
+(SELECT * FROM distinct_hash_1 EXCEPT SELECT * FROM distinct_group_1)
+ UNION ALL
+(SELECT * FROM distinct_group_1 EXCEPT SELECT * FROM distinct_hash_1);
+
+DROP TABLE distinct_hash_1;
+DROP TABLE distinct_hash_2;
+DROP TABLE distinct_group_1;
+DROP TABLE distinct_group_2;
+
+--
+-- 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/select_distinct_on.sql b/src/test/regress/sql/select_distinct_on.sql
new file mode 100644
index 0000000..d18733d
--- /dev/null
+++ b/src/test/regress/sql/select_distinct_on.sql
@@ -0,0 +1,19 @@
+--
+-- SELECT_DISTINCT_ON
+--
+
+SELECT DISTINCT ON (string4) string4, two, ten
+ FROM tmp
+ ORDER BY string4 using <, two using >, ten using <;
+
+-- this will fail due to conflict of ordering requirements
+SELECT DISTINCT ON (string4, ten) string4, two, ten
+ FROM tmp
+ ORDER BY string4 using <, two using <, ten using <;
+
+SELECT DISTINCT ON (string4, ten) string4, ten, two
+ FROM tmp
+ ORDER BY string4 using <, ten using >, two using <;
+
+-- bug #5049: early 8.4.x chokes on volatile DISTINCT ON clauses
+select distinct on (1) floor(random()) as r, f1 from int4_tbl order by 1,2;
diff --git a/src/test/regress/sql/select_having.sql b/src/test/regress/sql/select_having.sql
new file mode 100644
index 0000000..bc0cdc0
--- /dev/null
+++ b/src/test/regress/sql/select_having.sql
@@ -0,0 +1,50 @@
+--
+-- SELECT_HAVING
+--
+
+-- load test data
+CREATE TABLE test_having (a int, b int, c char(8), d char);
+INSERT INTO test_having VALUES (0, 1, 'XXXX', 'A');
+INSERT INTO test_having VALUES (1, 2, 'AAAA', 'b');
+INSERT INTO test_having VALUES (2, 2, 'AAAA', 'c');
+INSERT INTO test_having VALUES (3, 3, 'BBBB', 'D');
+INSERT INTO test_having VALUES (4, 3, 'BBBB', 'e');
+INSERT INTO test_having VALUES (5, 3, 'bbbb', 'F');
+INSERT INTO test_having VALUES (6, 4, 'cccc', 'g');
+INSERT INTO test_having VALUES (7, 4, 'cccc', 'h');
+INSERT INTO test_having VALUES (8, 4, 'CCCC', 'I');
+INSERT INTO test_having VALUES (9, 4, 'CCCC', 'j');
+
+SELECT b, c FROM test_having
+ GROUP BY b, c HAVING count(*) = 1 ORDER BY b, c;
+
+-- HAVING is effectively equivalent to WHERE in this case
+SELECT b, c FROM test_having
+ GROUP BY b, c HAVING b = 3 ORDER BY b, c;
+
+SELECT lower(c), count(c) FROM test_having
+ GROUP BY lower(c) HAVING count(*) > 2 OR min(a) = max(a)
+ ORDER BY lower(c);
+
+SELECT c, max(a) FROM test_having
+ GROUP BY c HAVING count(*) > 2 OR min(a) = max(a)
+ ORDER BY c;
+
+-- test degenerate cases involving HAVING without GROUP BY
+-- Per SQL spec, these should generate 0 or 1 row, even without aggregates
+
+SELECT min(a), max(a) FROM test_having HAVING min(a) = max(a);
+SELECT min(a), max(a) FROM test_having HAVING min(a) < max(a);
+
+-- errors: ungrouped column references
+SELECT a FROM test_having HAVING min(a) < max(a);
+SELECT 1 AS one FROM test_having HAVING a > 1;
+
+-- the really degenerate case: need not scan table at all
+SELECT 1 AS one FROM test_having HAVING 1 > 2;
+SELECT 1 AS one FROM test_having HAVING 1 < 2;
+
+-- and just to prove that we aren't scanning the table:
+SELECT 1 AS one FROM test_having WHERE 1/a = 1 HAVING 1 < 2;
+
+DROP TABLE test_having;
diff --git a/src/test/regress/sql/select_implicit.sql b/src/test/regress/sql/select_implicit.sql
new file mode 100644
index 0000000..de3aef8
--- /dev/null
+++ b/src/test/regress/sql/select_implicit.sql
@@ -0,0 +1,156 @@
+--
+-- SELECT_IMPLICIT
+-- Test cases for queries with ordering terms missing from the target list.
+-- This used to be called "junkfilter.sql".
+-- The parser uses the term "resjunk" to handle these cases.
+-- - thomas 1998-07-09
+--
+
+-- load test data
+CREATE TABLE test_missing_target (a int, b int, c char(8), d char);
+INSERT INTO test_missing_target VALUES (0, 1, 'XXXX', 'A');
+INSERT INTO test_missing_target VALUES (1, 2, 'ABAB', 'b');
+INSERT INTO test_missing_target VALUES (2, 2, 'ABAB', 'c');
+INSERT INTO test_missing_target VALUES (3, 3, 'BBBB', 'D');
+INSERT INTO test_missing_target VALUES (4, 3, 'BBBB', 'e');
+INSERT INTO test_missing_target VALUES (5, 3, 'bbbb', 'F');
+INSERT INTO test_missing_target VALUES (6, 4, 'cccc', 'g');
+INSERT INTO test_missing_target VALUES (7, 4, 'cccc', 'h');
+INSERT INTO test_missing_target VALUES (8, 4, 'CCCC', 'I');
+INSERT INTO test_missing_target VALUES (9, 4, 'CCCC', 'j');
+
+
+-- w/ existing GROUP BY target
+SELECT c, count(*) FROM test_missing_target GROUP BY test_missing_target.c ORDER BY c;
+
+-- w/o existing GROUP BY target using a relation name in GROUP BY clause
+SELECT count(*) FROM test_missing_target GROUP BY test_missing_target.c ORDER BY c;
+
+-- w/o existing GROUP BY target and w/o existing a different ORDER BY target
+-- failure expected
+SELECT count(*) FROM test_missing_target GROUP BY a ORDER BY b;
+
+-- w/o existing GROUP BY target and w/o existing same ORDER BY target
+SELECT count(*) FROM test_missing_target GROUP BY b ORDER BY b;
+
+-- w/ existing GROUP BY target using a relation name in target
+SELECT test_missing_target.b, count(*)
+ FROM test_missing_target GROUP BY b ORDER BY b;
+
+-- w/o existing GROUP BY target
+SELECT c FROM test_missing_target ORDER BY a;
+
+-- w/o existing ORDER BY target
+SELECT count(*) FROM test_missing_target GROUP BY b ORDER BY b desc;
+
+-- group using reference number
+SELECT count(*) FROM test_missing_target ORDER BY 1 desc;
+
+-- order using reference number
+SELECT c, count(*) FROM test_missing_target GROUP BY 1 ORDER BY 1;
+
+-- group using reference number out of range
+-- failure expected
+SELECT c, count(*) FROM test_missing_target GROUP BY 3;
+
+-- group w/o existing GROUP BY and ORDER BY target under ambiguous condition
+-- failure expected
+SELECT count(*) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY b ORDER BY b;
+
+-- order w/ target under ambiguous condition
+-- failure NOT expected
+SELECT a, a FROM test_missing_target
+ ORDER BY a;
+
+-- order expression w/ target under ambiguous condition
+-- failure NOT expected
+SELECT a/2, a/2 FROM test_missing_target
+ ORDER BY a/2;
+
+-- group expression w/ target under ambiguous condition
+-- failure NOT expected
+SELECT a/2, a/2 FROM test_missing_target
+ GROUP BY a/2 ORDER BY a/2;
+
+-- group w/ existing GROUP BY target under ambiguous condition
+SELECT x.b, count(*) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b ORDER BY x.b;
+
+-- group w/o existing GROUP BY target under ambiguous condition
+SELECT count(*) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b ORDER BY x.b;
+
+-- group w/o existing GROUP BY target under ambiguous condition
+-- into a table
+CREATE TABLE test_missing_target2 AS
+SELECT count(*)
+FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b ORDER BY x.b;
+SELECT * FROM test_missing_target2;
+
+
+-- Functions and expressions
+
+-- w/ existing GROUP BY target
+SELECT a%2, count(b) FROM test_missing_target
+GROUP BY test_missing_target.a%2
+ORDER BY test_missing_target.a%2;
+
+-- w/o existing GROUP BY target using a relation name in GROUP BY clause
+SELECT count(c) FROM test_missing_target
+GROUP BY lower(test_missing_target.c)
+ORDER BY lower(test_missing_target.c);
+
+-- w/o existing GROUP BY target and w/o existing a different ORDER BY target
+-- failure expected
+SELECT count(a) FROM test_missing_target GROUP BY a ORDER BY b;
+
+-- w/o existing GROUP BY target and w/o existing same ORDER BY target
+SELECT count(b) FROM test_missing_target GROUP BY b/2 ORDER BY b/2;
+
+-- w/ existing GROUP BY target using a relation name in target
+SELECT lower(test_missing_target.c), count(c)
+ FROM test_missing_target GROUP BY lower(c) ORDER BY lower(c);
+
+-- w/o existing GROUP BY target
+SELECT a FROM test_missing_target ORDER BY upper(d);
+
+-- w/o existing ORDER BY target
+SELECT count(b) FROM test_missing_target
+ GROUP BY (b + 1) / 2 ORDER BY (b + 1) / 2 desc;
+
+-- group w/o existing GROUP BY and ORDER BY target under ambiguous condition
+-- failure expected
+SELECT count(x.a) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY b/2 ORDER BY b/2;
+
+-- group w/ existing GROUP BY target under ambiguous condition
+SELECT x.b/2, count(x.b) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b/2 ORDER BY x.b/2;
+
+-- group w/o existing GROUP BY target under ambiguous condition
+-- failure expected due to ambiguous b in count(b)
+SELECT count(b) FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b/2;
+
+-- group w/o existing GROUP BY target under ambiguous condition
+-- into a table
+CREATE TABLE test_missing_target3 AS
+SELECT count(x.b)
+FROM test_missing_target x, test_missing_target y
+ WHERE x.a = y.a
+ GROUP BY x.b/2 ORDER BY x.b/2;
+SELECT * FROM test_missing_target3;
+
+-- Cleanup
+DROP TABLE test_missing_target;
+DROP TABLE test_missing_target2;
+DROP TABLE test_missing_target3;
diff --git a/src/test/regress/sql/select_into.sql b/src/test/regress/sql/select_into.sql
new file mode 100644
index 0000000..7e903c3
--- /dev/null
+++ b/src/test/regress/sql/select_into.sql
@@ -0,0 +1,138 @@
+--
+-- SELECT_INTO
+--
+
+SELECT *
+ INTO TABLE sitmp1
+ FROM onek
+ WHERE onek.unique1 < 2;
+
+DROP TABLE sitmp1;
+
+SELECT *
+ INTO TABLE sitmp1
+ FROM onek2
+ WHERE onek2.unique1 < 2;
+
+DROP TABLE sitmp1;
+
+--
+-- SELECT INTO and INSERT permission, if owner is not allowed to insert.
+--
+CREATE SCHEMA selinto_schema;
+CREATE USER regress_selinto_user;
+ALTER DEFAULT PRIVILEGES FOR ROLE regress_selinto_user
+ REVOKE INSERT ON TABLES FROM regress_selinto_user;
+GRANT ALL ON SCHEMA selinto_schema TO public;
+
+SET SESSION AUTHORIZATION regress_selinto_user;
+-- WITH DATA, passes.
+CREATE TABLE selinto_schema.tbl_withdata1 (a)
+ AS SELECT generate_series(1,3) WITH DATA;
+INSERT INTO selinto_schema.tbl_withdata1 VALUES (4);
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE selinto_schema.tbl_withdata2 (a) AS
+ SELECT generate_series(1,3) WITH DATA;
+-- WITH NO DATA, passes.
+CREATE TABLE selinto_schema.tbl_nodata1 (a) AS
+ SELECT generate_series(1,3) WITH NO DATA;
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE selinto_schema.tbl_nodata2 (a) AS
+ SELECT generate_series(1,3) WITH NO DATA;
+-- EXECUTE and WITH DATA, passes.
+PREPARE data_sel AS SELECT generate_series(1,3);
+CREATE TABLE selinto_schema.tbl_withdata3 (a) AS
+ EXECUTE data_sel WITH DATA;
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE selinto_schema.tbl_withdata4 (a) AS
+ EXECUTE data_sel WITH DATA;
+-- EXECUTE and WITH NO DATA, passes.
+CREATE TABLE selinto_schema.tbl_nodata3 (a) AS
+ EXECUTE data_sel WITH NO DATA;
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE selinto_schema.tbl_nodata4 (a) AS
+ EXECUTE data_sel WITH NO DATA;
+RESET SESSION AUTHORIZATION;
+
+ALTER DEFAULT PRIVILEGES FOR ROLE regress_selinto_user
+ GRANT INSERT ON TABLES TO regress_selinto_user;
+
+SET SESSION AUTHORIZATION regress_selinto_user;
+RESET SESSION AUTHORIZATION;
+
+DEALLOCATE data_sel;
+DROP SCHEMA selinto_schema CASCADE;
+DROP USER regress_selinto_user;
+
+-- Tests for WITH NO DATA and column name consistency
+CREATE TABLE ctas_base (i int, j int);
+INSERT INTO ctas_base VALUES (1, 2);
+CREATE TABLE ctas_nodata (ii, jj, kk) AS SELECT i, j FROM ctas_base; -- Error
+CREATE TABLE ctas_nodata (ii, jj, kk) AS SELECT i, j FROM ctas_base WITH NO DATA; -- Error
+CREATE TABLE ctas_nodata (ii, jj) AS SELECT i, j FROM ctas_base; -- OK
+CREATE TABLE ctas_nodata_2 (ii, jj) AS SELECT i, j FROM ctas_base WITH NO DATA; -- OK
+CREATE TABLE ctas_nodata_3 (ii) AS SELECT i, j FROM ctas_base; -- OK
+CREATE TABLE ctas_nodata_4 (ii) AS SELECT i, j FROM ctas_base WITH NO DATA; -- OK
+SELECT * FROM ctas_nodata;
+SELECT * FROM ctas_nodata_2;
+SELECT * FROM ctas_nodata_3;
+SELECT * FROM ctas_nodata_4;
+DROP TABLE ctas_base;
+DROP TABLE ctas_nodata;
+DROP TABLE ctas_nodata_2;
+DROP TABLE ctas_nodata_3;
+DROP TABLE ctas_nodata_4;
+
+--
+-- CREATE TABLE AS/SELECT INTO as last command in a SQL function
+-- have been known to cause problems
+--
+CREATE FUNCTION make_table() RETURNS VOID
+AS $$
+ CREATE TABLE created_table AS SELECT * FROM int8_tbl;
+$$ LANGUAGE SQL;
+
+SELECT make_table();
+
+SELECT * FROM created_table;
+
+-- Try EXPLAIN ANALYZE SELECT INTO and EXPLAIN ANALYZE CREATE TABLE AS
+-- WITH NO DATA, but hide the outputs since they won't be stable.
+DO $$
+BEGIN
+ EXECUTE 'EXPLAIN ANALYZE SELECT * INTO TABLE easi FROM int8_tbl';
+ EXECUTE 'EXPLAIN ANALYZE CREATE TABLE easi2 AS SELECT * FROM int8_tbl WITH NO DATA';
+END$$;
+
+DROP TABLE created_table;
+DROP TABLE easi, easi2;
+
+--
+-- Disallowed uses of SELECT ... INTO. All should fail
+--
+DECLARE foo CURSOR FOR SELECT 1 INTO b;
+COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob';
+SELECT * FROM (SELECT 1 INTO f) bar;
+CREATE VIEW foo AS SELECT 1 INTO b;
+INSERT INTO b SELECT 1 INTO f;
+
+-- Test CREATE TABLE AS ... IF NOT EXISTS
+CREATE TABLE ctas_ine_tbl AS SELECT 1;
+CREATE TABLE ctas_ine_tbl AS SELECT 1 / 0; -- error
+CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS SELECT 1 / 0; -- ok
+CREATE TABLE ctas_ine_tbl AS SELECT 1 / 0 WITH NO DATA; -- error
+CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS SELECT 1 / 0 WITH NO DATA; -- ok
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE ctas_ine_tbl AS SELECT 1 / 0; -- error
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS SELECT 1 / 0; -- ok
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE ctas_ine_tbl AS SELECT 1 / 0 WITH NO DATA; -- error
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS SELECT 1 / 0 WITH NO DATA; -- ok
+PREPARE ctas_ine_query AS SELECT 1 / 0;
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE ctas_ine_tbl AS EXECUTE ctas_ine_query; -- error
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+ CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS EXECUTE ctas_ine_query; -- ok
+DROP TABLE ctas_ine_tbl;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
new file mode 100644
index 0000000..62fb68c
--- /dev/null
+++ b/src/test/regress/sql/select_parallel.sql
@@ -0,0 +1,464 @@
+--
+-- PARALLEL
+--
+
+create function sp_parallel_restricted(int) returns int as
+ $$begin return $1; end$$ language plpgsql parallel restricted;
+
+begin;
+
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+
+-- Parallel Append with partial-subplans
+explain (costs off)
+ select round(avg(aa)), sum(aa) from a_star;
+select round(avg(aa)), sum(aa) from a_star a1;
+
+-- Parallel Append with both partial and non-partial subplans
+alter table c_star set (parallel_workers = 0);
+alter table d_star set (parallel_workers = 0);
+explain (costs off)
+ select round(avg(aa)), sum(aa) from a_star;
+select round(avg(aa)), sum(aa) from a_star a2;
+
+-- Parallel Append with only non-partial subplans
+alter table a_star set (parallel_workers = 0);
+alter table b_star set (parallel_workers = 0);
+alter table e_star set (parallel_workers = 0);
+alter table f_star set (parallel_workers = 0);
+explain (costs off)
+ select round(avg(aa)), sum(aa) from a_star;
+select round(avg(aa)), sum(aa) from a_star a3;
+
+-- Disable Parallel Append
+alter table a_star reset (parallel_workers);
+alter table b_star reset (parallel_workers);
+alter table c_star reset (parallel_workers);
+alter table d_star reset (parallel_workers);
+alter table e_star reset (parallel_workers);
+alter table f_star reset (parallel_workers);
+set enable_parallel_append to off;
+explain (costs off)
+ select round(avg(aa)), sum(aa) from a_star;
+select round(avg(aa)), sum(aa) from a_star a4;
+reset enable_parallel_append;
+
+-- Parallel Append that runs serially
+create function sp_test_func() returns setof text as
+$$ select 'foo'::varchar union all select 'bar'::varchar $$
+language sql stable;
+select sp_test_func() order by 1;
+
+-- 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
+alter table tenk1 set (parallel_workers = 4);
+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.
+alter table tenk2 set (parallel_workers = 0);
+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);
+alter table tenk2 reset (parallel_workers);
+
+-- test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+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;
+alter table tenk2 reset (parallel_workers);
+
+-- 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;
+
+-- test rescans for a Limit node with a parallel node beneath it.
+reset enable_seqscan;
+set enable_indexonlyscan to off;
+set enable_indexscan to off;
+alter table tenk1 set (parallel_workers = 0);
+alter table tenk2 set (parallel_workers = 1);
+explain (costs off)
+select count(*) from tenk1
+ left join (select tenk2.unique1 from tenk2 order by 1 limit 1000) ss
+ on tenk1.unique1 < ss.unique1 + 1
+ where tenk1.unique1 < 2;
+select count(*) from tenk1
+ left join (select tenk2.unique1 from tenk2 order by 1 limit 1000) ss
+ on tenk1.unique1 < ss.unique1 + 1
+ where tenk1.unique1 < 2;
+--reset the value of workers for each table as it was before this test.
+alter table tenk1 set (parallel_workers = 4);
+alter table tenk2 reset (parallel_workers);
+
+reset enable_material;
+reset enable_bitmapscan;
+reset enable_indexonlyscan;
+reset enable_indexscan;
+
+-- test parallel bitmap heap scan.
+set enable_seqscan to off;
+set enable_indexscan to off;
+set enable_hashjoin to off;
+set enable_mergejoin to off;
+set enable_material to off;
+-- test prefetching, if the platform allows it
+DO $$
+BEGIN
+ SET effective_io_concurrency = 50;
+EXCEPTION WHEN invalid_parameter_value THEN
+END $$;
+set work_mem='64kB'; --set small work mem to force lossy pages
+explain (costs off)
+ select count(*) from tenk1, tenk2 where tenk1.hundred > 1 and tenk2.thousand=0;
+select count(*) from tenk1, tenk2 where tenk1.hundred > 1 and tenk2.thousand=0;
+
+create table bmscantest (a int, t text);
+insert into bmscantest select r, 'fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo' FROM generate_series(1,100000) r;
+create index i_bmtest ON bmscantest(a);
+select count(*) from bmscantest where a>1;
+
+-- test accumulation of stats for parallel nodes
+reset enable_seqscan;
+alter table tenk2 set (parallel_workers = 0);
+explain (analyze, timing off, summary off, costs off)
+ select count(*) from tenk1, tenk2 where tenk1.hundred > 1
+ and tenk2.thousand=0;
+alter table tenk2 reset (parallel_workers);
+
+reset work_mem;
+create function explain_parallel_sort_stats() returns setof text
+language plpgsql as
+$$
+declare ln text;
+begin
+ for ln in
+ explain (analyze, timing off, summary off, costs off)
+ select * from
+ (select ten from tenk1 where ten < 100 order by ten) ss
+ right join (values (1),(2),(3)) v(x) on true
+ loop
+ ln := regexp_replace(ln, 'Memory: \S*', 'Memory: xxx');
+ return next ln;
+ end loop;
+end;
+$$;
+select * from explain_parallel_sort_stats();
+
+reset enable_indexscan;
+reset enable_hashjoin;
+reset enable_mergejoin;
+reset enable_material;
+reset effective_io_concurrency;
+drop table bmscantest;
+drop function explain_parallel_sort_stats();
+
+-- 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;
+
+SAVEPOINT settings;
+SET LOCAL force_parallel_mode = 1;
+explain (costs off)
+ select stringu1::int2 from tenk1 where unique1 = 1;
+ROLLBACK TO SAVEPOINT settings;
+
+-- 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 interaction with SRFs
+SELECT * FROM information_schema.foreign_data_wrapper_options
+ORDER BY 1, 2, 3;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT generate_series(1, two), array(select generate_series(1, two))
+ FROM tenk1 ORDER BY tenthous;
+
+-- must disallow pushing sort below gather when pathkey contains an SRF
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT unnest(ARRAY[]::integer[]) + 1 AS pathkey
+ FROM tenk1 t1 JOIN tenk1 t2 ON TRUE
+ ORDER BY pathkey;
+
+-- 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
+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;
diff --git a/src/test/regress/sql/select_views.sql b/src/test/regress/sql/select_views.sql
new file mode 100644
index 0000000..e742f13
--- /dev/null
+++ b/src/test/regress/sql/select_views.sql
@@ -0,0 +1,155 @@
+--
+-- SELECT_VIEWS
+-- test the views defined in CREATE_VIEWS
+--
+
+SELECT * FROM street;
+
+SELECT name, #thepath FROM iexit ORDER BY name COLLATE "C", 2;
+
+SELECT * FROM toyemp WHERE name = 'sharon';
+
+--
+-- Test for Leaky view scenario
+--
+CREATE ROLE regress_alice;
+
+CREATE FUNCTION f_leak (text)
+ RETURNS bool LANGUAGE 'plpgsql' COST 0.0000001
+ AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';
+
+CREATE TABLE customer (
+ cid int primary key,
+ name text not null,
+ tel text,
+ passwd text
+);
+
+CREATE TABLE credit_card (
+ cid int references customer(cid),
+ cnum text,
+ climit int
+);
+
+CREATE TABLE credit_usage (
+ cid int references customer(cid),
+ ymd date,
+ usage int
+);
+
+INSERT INTO customer
+ VALUES (101, 'regress_alice', '+81-12-3456-7890', 'passwd123'),
+ (102, 'regress_bob', '+01-234-567-8901', 'beafsteak'),
+ (103, 'regress_eve', '+49-8765-43210', 'hamburger');
+INSERT INTO credit_card
+ VALUES (101, '1111-2222-3333-4444', 4000),
+ (102, '5555-6666-7777-8888', 3000),
+ (103, '9801-2345-6789-0123', 2000);
+INSERT INTO credit_usage
+ VALUES (101, '2011-09-15', 120),
+ (101, '2011-10-05', 90),
+ (101, '2011-10-18', 110),
+ (101, '2011-10-21', 200),
+ (101, '2011-11-10', 80),
+ (102, '2011-09-22', 300),
+ (102, '2011-10-12', 120),
+ (102, '2011-10-28', 200),
+ (103, '2011-10-15', 480);
+
+CREATE VIEW my_property_normal AS
+ SELECT * FROM customer WHERE name = current_user;
+CREATE VIEW my_property_secure WITH (security_barrier) AS
+ SELECT * FROM customer WHERE name = current_user;
+
+CREATE VIEW my_credit_card_normal AS
+ SELECT * FROM customer l NATURAL JOIN credit_card r
+ WHERE l.name = current_user;
+CREATE VIEW my_credit_card_secure WITH (security_barrier) AS
+ SELECT * FROM customer l NATURAL JOIN credit_card r
+ WHERE l.name = current_user;
+
+CREATE VIEW my_credit_card_usage_normal AS
+ SELECT * FROM my_credit_card_secure l NATURAL JOIN credit_usage r;
+CREATE VIEW my_credit_card_usage_secure WITH (security_barrier) AS
+ SELECT * FROM my_credit_card_secure l NATURAL JOIN credit_usage r;
+
+GRANT SELECT ON my_property_normal TO public;
+GRANT SELECT ON my_property_secure TO public;
+GRANT SELECT ON my_credit_card_normal TO public;
+GRANT SELECT ON my_credit_card_secure TO public;
+GRANT SELECT ON my_credit_card_usage_normal TO public;
+GRANT SELECT ON my_credit_card_usage_secure TO public;
+
+--
+-- Run leaky view scenarios
+--
+SET SESSION AUTHORIZATION regress_alice;
+
+--
+-- scenario: if a qualifier with tiny-cost is given, it shall be launched
+-- prior to the security policy of the view.
+--
+SELECT * FROM my_property_normal WHERE f_leak(passwd);
+EXPLAIN (COSTS OFF) SELECT * FROM my_property_normal WHERE f_leak(passwd);
+
+SELECT * FROM my_property_secure WHERE f_leak(passwd);
+EXPLAIN (COSTS OFF) SELECT * FROM my_property_secure WHERE f_leak(passwd);
+
+--
+-- scenario: qualifiers can be pushed down if they contain leaky functions,
+-- provided they aren't passed data from inside the view.
+--
+SELECT * FROM my_property_normal v
+ WHERE f_leak('passwd') AND f_leak(passwd);
+EXPLAIN (COSTS OFF) SELECT * FROM my_property_normal v
+ WHERE f_leak('passwd') AND f_leak(passwd);
+
+SELECT * FROM my_property_secure v
+ WHERE f_leak('passwd') AND f_leak(passwd);
+EXPLAIN (COSTS OFF) SELECT * FROM my_property_secure v
+ WHERE f_leak('passwd') AND f_leak(passwd);
+
+--
+-- scenario: if a qualifier references only one-side of a particular join-
+-- tree, it shall be distributed to the most deep scan plan as
+-- possible as we can.
+--
+SELECT * FROM my_credit_card_normal WHERE f_leak(cnum);
+EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_normal WHERE f_leak(cnum);
+
+SELECT * FROM my_credit_card_secure WHERE f_leak(cnum);
+EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_secure WHERE f_leak(cnum);
+
+--
+-- scenario: an external qualifier can be pushed-down by in-front-of the
+-- views with "security_barrier" attribute, except for operators
+-- implemented with leakproof functions.
+--
+SELECT * FROM my_credit_card_usage_normal
+ WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
+EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_usage_normal
+ WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
+
+SELECT * FROM my_credit_card_usage_secure
+ WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
+EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_usage_secure
+ WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
+
+--
+-- Test for the case when security_barrier gets changed between rewriter
+-- and planner stage.
+--
+PREPARE p1 AS SELECT * FROM my_property_normal WHERE f_leak(passwd);
+PREPARE p2 AS SELECT * FROM my_property_secure WHERE f_leak(passwd);
+EXECUTE p1;
+EXECUTE p2;
+RESET SESSION AUTHORIZATION;
+ALTER VIEW my_property_normal SET (security_barrier=true);
+ALTER VIEW my_property_secure SET (security_barrier=false);
+SET SESSION AUTHORIZATION regress_alice;
+EXECUTE p1; -- To be perform as a view with security-barrier
+EXECUTE p2; -- To be perform as a view without security-barrier
+
+-- Cleanup.
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_alice;
diff --git a/src/test/regress/sql/sequence.sql b/src/test/regress/sql/sequence.sql
new file mode 100644
index 0000000..7928ee2
--- /dev/null
+++ b/src/test/regress/sql/sequence.sql
@@ -0,0 +1,408 @@
+--
+-- CREATE SEQUENCE
+--
+
+-- various error cases
+CREATE UNLOGGED SEQUENCE sequence_testx;
+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
+CREATE SEQUENCE sequence_testx OWNED BY pg_class.relname; -- not same schema
+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;
+
+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 (31, 32) AS log_cnt_ok, is_called FROM foo_seq_new;
+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;
+
+--
+-- 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, last_value
+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();
+
+-- 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;
+
+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;
+
+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;
+
+-- 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;
+
+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;
+
+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;
+
+-- 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;
+
+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;
+
+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;
+
+-- 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;
+
+-- 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/spgist.sql b/src/test/regress/sql/spgist.sql
new file mode 100644
index 0000000..7644f34
--- /dev/null
+++ b/src/test/regress/sql/spgist.sql
@@ -0,0 +1,82 @@
+--
+-- Test SP-GiST indexes.
+--
+-- There are other tests to test different SP-GiST opclasses. This is for
+-- testing SP-GiST code itself.
+
+create table spgist_point_tbl(id int4, p point);
+create index spgist_point_idx on spgist_point_tbl using spgist(p) with (fillfactor = 75);
+
+-- Test vacuum-root operation. It gets invoked when the root is also a leaf,
+-- i.e. the index is very small.
+insert into spgist_point_tbl (id, p)
+select g, point(g*10, g*10) from generate_series(1, 10) g;
+delete from spgist_point_tbl where id < 5;
+vacuum spgist_point_tbl;
+
+-- Insert more data, to make the index a few levels deep.
+insert into spgist_point_tbl (id, p)
+select g, point(g*10, g*10) from generate_series(1, 10000) g;
+insert into spgist_point_tbl (id, p)
+select g+100000, point(g*10+1, g*10+1) from generate_series(1, 10000) g;
+
+-- To test vacuum, delete some entries from all over the index.
+delete from spgist_point_tbl where id % 2 = 1;
+
+-- And also delete some concentration of values. (SP-GiST doesn't currently
+-- attempt to delete pages even when they become empty, but if it did, this
+-- would exercise it)
+delete from spgist_point_tbl where id < 10000;
+
+vacuum spgist_point_tbl;
+
+-- Test rescan paths (cf. bug #15378)
+-- use box and && rather than point, so that rescan happens when the
+-- traverse stack is non-empty
+
+create table spgist_box_tbl(id serial, b box);
+insert into spgist_box_tbl(b)
+select box(point(i,j),point(i+s,j+s))
+ from generate_series(1,100,5) i,
+ generate_series(1,100,5) j,
+ generate_series(1,10) s;
+create index spgist_box_idx on spgist_box_tbl using spgist (b);
+
+select count(*)
+ from (values (point(5,5)),(point(8,8)),(point(12,12))) v(p)
+ where exists(select * from spgist_box_tbl b where b.b && box(v.p,v.p));
+
+-- The point opclass's choose method only uses the spgMatchNode action,
+-- so the other actions are not tested by the above. Create an index using
+-- text opclass, which uses the others actions.
+
+create table spgist_text_tbl(id int4, t text);
+create index spgist_text_idx on spgist_text_tbl using spgist(t);
+
+insert into spgist_text_tbl (id, t)
+select g, 'f' || repeat('o', 100) || g from generate_series(1, 10000) g
+union all
+select g, 'baaaaaaaaaaaaaar' || g from generate_series(1, 1000) g;
+
+-- Do a lot of insertions that have to split an existing node. Hopefully
+-- one of these will cause the page to run out of space, causing the inner
+-- tuple to be moved to another page.
+insert into spgist_text_tbl (id, t)
+select -g, 'f' || repeat('o', 100-g) || 'surprise' from generate_series(1, 100) g;
+
+-- Test out-of-range fillfactor values
+create index spgist_point_idx2 on spgist_point_tbl using spgist(p) with (fillfactor = 9);
+create index spgist_point_idx2 on spgist_point_tbl using spgist(p) with (fillfactor = 101);
+
+-- Modify fillfactor in existing index
+alter index spgist_point_idx set (fillfactor = 90);
+reindex index spgist_point_idx;
+
+-- Test index over a domain
+create domain spgist_text as varchar;
+create table spgist_domain_tbl (f1 spgist_text);
+create index spgist_domain_idx on spgist_domain_tbl using spgist(f1);
+insert into spgist_domain_tbl values('fee'), ('fi'), ('fo'), ('fum');
+explain (costs off)
+select * from spgist_domain_tbl where f1 = 'fo';
+select * from spgist_domain_tbl where f1 = 'fo';
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
new file mode 100644
index 0000000..dda0717
--- /dev/null
+++ b/src/test/regress/sql/stats.sql
@@ -0,0 +1,185 @@
+--
+-- Test Statistics Collector
+--
+-- Must be run after tenk2 has been created (by create_table),
+-- populated (by create_misc) and indexed (by create_index).
+--
+
+-- conditio sine qua non
+SHOW track_counts; -- must be on
+
+-- ensure that both seqscan and indexscan plans are allowed
+SET enable_seqscan TO on;
+SET enable_indexscan TO on;
+-- for the moment, we don't want index-only scans here
+SET enable_indexonlyscan TO off;
+
+-- save counters
+CREATE TABLE prevstats AS
+SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch,
+ (b.heap_blks_read + b.heap_blks_hit) AS heap_blks,
+ (b.idx_blks_read + b.idx_blks_hit) AS idx_blks,
+ pg_stat_get_snapshot_timestamp() as snap_ts
+ FROM pg_catalog.pg_stat_user_tables AS t,
+ pg_catalog.pg_statio_user_tables AS b
+ WHERE t.relname='tenk2' AND b.relname='tenk2';
+
+-- function to wait for counters to advance
+create function wait_for_stats() returns void as $$
+declare
+ start_time timestamptz := clock_timestamp();
+ updated1 bool;
+ updated2 bool;
+ updated3 bool;
+ updated4 bool;
+begin
+ -- we don't want to wait forever; loop will exit after 30 seconds
+ for i in 1 .. 300 loop
+
+ -- With parallel query, the seqscan and indexscan on tenk2 might be done
+ -- in parallel worker processes, which will send their stats counters
+ -- asynchronously to what our own session does. So we must check for
+ -- those counts to be registered separately from the update counts.
+
+ -- check to see if seqscan has been sensed
+ SELECT (st.seq_scan >= pr.seq_scan + 1) INTO updated1
+ FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
+ WHERE st.relname='tenk2' AND cl.relname='tenk2';
+
+ -- check to see if indexscan has been sensed
+ SELECT (st.idx_scan >= pr.idx_scan + 1) INTO updated2
+ FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
+ WHERE st.relname='tenk2' AND cl.relname='tenk2';
+
+ -- check to see if all updates have been sensed
+ SELECT (n_tup_ins > 0) INTO updated3
+ FROM pg_stat_user_tables WHERE relname='trunc_stats_test4';
+
+ -- We must also check explicitly that pg_stat_get_snapshot_timestamp has
+ -- advanced, because that comes from the global stats file which might
+ -- be older than the per-DB stats file we got the other values from.
+ SELECT (pr.snap_ts < pg_stat_get_snapshot_timestamp()) INTO updated4
+ FROM prevstats AS pr;
+
+ exit when updated1 and updated2 and updated3 and updated4;
+
+ -- wait a little
+ perform pg_sleep_for('100 milliseconds');
+
+ -- reset stats snapshot so we can test again
+ perform pg_stat_clear_snapshot();
+
+ end loop;
+
+ -- report time waited in postmaster log (where it won't change test output)
+ raise log 'wait_for_stats delayed % seconds',
+ extract(epoch from clock_timestamp() - start_time);
+end
+$$ language plpgsql;
+
+-- test effects of TRUNCATE on n_live_tup/n_dead_tup counters
+CREATE TABLE trunc_stats_test(id serial);
+CREATE TABLE trunc_stats_test1(id serial, stuff text);
+CREATE TABLE trunc_stats_test2(id serial);
+CREATE TABLE trunc_stats_test3(id serial, stuff text);
+CREATE TABLE trunc_stats_test4(id serial);
+
+-- check that n_live_tup is reset to 0 after truncate
+INSERT INTO trunc_stats_test DEFAULT VALUES;
+INSERT INTO trunc_stats_test DEFAULT VALUES;
+INSERT INTO trunc_stats_test DEFAULT VALUES;
+TRUNCATE trunc_stats_test;
+
+-- test involving a truncate in a transaction; 4 ins but only 1 live
+INSERT INTO trunc_stats_test1 DEFAULT VALUES;
+INSERT INTO trunc_stats_test1 DEFAULT VALUES;
+INSERT INTO trunc_stats_test1 DEFAULT VALUES;
+UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
+DELETE FROM trunc_stats_test1 WHERE id = 3;
+
+BEGIN;
+UPDATE trunc_stats_test1 SET id = id + 100;
+TRUNCATE trunc_stats_test1;
+INSERT INTO trunc_stats_test1 DEFAULT VALUES;
+COMMIT;
+
+-- use a savepoint: 1 insert, 1 live
+BEGIN;
+INSERT INTO trunc_stats_test2 DEFAULT VALUES;
+INSERT INTO trunc_stats_test2 DEFAULT VALUES;
+SAVEPOINT p1;
+INSERT INTO trunc_stats_test2 DEFAULT VALUES;
+TRUNCATE trunc_stats_test2;
+INSERT INTO trunc_stats_test2 DEFAULT VALUES;
+RELEASE SAVEPOINT p1;
+COMMIT;
+
+-- rollback a savepoint: this should count 4 inserts and have 2
+-- live tuples after commit (and 2 dead ones due to aborted subxact)
+BEGIN;
+INSERT INTO trunc_stats_test3 DEFAULT VALUES;
+INSERT INTO trunc_stats_test3 DEFAULT VALUES;
+SAVEPOINT p1;
+INSERT INTO trunc_stats_test3 DEFAULT VALUES;
+INSERT INTO trunc_stats_test3 DEFAULT VALUES;
+TRUNCATE trunc_stats_test3;
+INSERT INTO trunc_stats_test3 DEFAULT VALUES;
+ROLLBACK TO SAVEPOINT p1;
+COMMIT;
+
+-- rollback a truncate: this should count 2 inserts and produce 2 dead tuples
+BEGIN;
+INSERT INTO trunc_stats_test4 DEFAULT VALUES;
+INSERT INTO trunc_stats_test4 DEFAULT VALUES;
+TRUNCATE trunc_stats_test4;
+INSERT INTO trunc_stats_test4 DEFAULT VALUES;
+ROLLBACK;
+
+-- do a seqscan
+SELECT count(*) FROM tenk2;
+-- do an indexscan
+-- make sure it is not a bitmap scan, which might skip fetching heap tuples
+SET enable_bitmapscan TO off;
+SELECT count(*) FROM tenk2 WHERE unique1 = 1;
+RESET enable_bitmapscan;
+
+-- We can't just call wait_for_stats() at this point, because we only
+-- transmit stats when the session goes idle, and we probably didn't
+-- transmit the last couple of counts yet thanks to the rate-limiting logic
+-- in pgstat_report_stat(). But instead of waiting for the rate limiter's
+-- timeout to elapse, let's just start a new session. The old one will
+-- then send its stats before dying.
+\c -
+
+-- wait for stats collector to update
+SELECT wait_for_stats();
+
+-- check effects
+SELECT relname, n_tup_ins, n_tup_upd, n_tup_del, n_live_tup, n_dead_tup
+ FROM pg_stat_user_tables
+ WHERE relname like 'trunc_stats_test%' order by relname;
+
+SELECT st.seq_scan >= pr.seq_scan + 1,
+ st.seq_tup_read >= pr.seq_tup_read + cl.reltuples,
+ st.idx_scan >= pr.idx_scan + 1,
+ st.idx_tup_fetch >= pr.idx_tup_fetch + 1
+ FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
+ WHERE st.relname='tenk2' AND cl.relname='tenk2';
+
+SELECT st.heap_blks_read + st.heap_blks_hit >= pr.heap_blks + cl.relpages,
+ st.idx_blks_read + st.idx_blks_hit >= pr.idx_blks + 1
+ FROM pg_statio_user_tables AS st, pg_class AS cl, prevstats AS pr
+ WHERE st.relname='tenk2' AND cl.relname='tenk2';
+
+SELECT pr.snap_ts < pg_stat_get_snapshot_timestamp() as snapshot_newer
+FROM prevstats AS pr;
+
+DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
+DROP TABLE prevstats;
+
+
+-- ensure that stats accessors handle NULL input correctly
+SELECT pg_stat_get_replication_slot(NULL);
+
+
+-- End of Stats Test
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
new file mode 100644
index 0000000..14b6253
--- /dev/null
+++ b/src/test/regress/sql/stats_ext.sql
@@ -0,0 +1,1655 @@
+-- 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;
+$$;
+
+-- 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
+ FROM pg_statistic_ext s, pg_statistic_ext_data d
+ WHERE s.stxname = 'ab1_a_b_stats'
+ AND d.stxoid = s.oid;
+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;
+
+-- 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;
+-- Since the stats object does not include inherited stats, it should not
+-- 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');
+DROP TABLE stxdinh, stxdinh1, stxdinh2;
+
+-- 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, b FROM stxdinp;
+VACUUM ANALYZE stxdinp; -- partitions are processed recursively
+SELECT 1 FROM pg_statistic_ext WHERE stxrelid = 'stxdinp'::regclass;
+SELECT * FROM check_estimated_rows('SELECT a, b FROM 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;
+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;
+$$;
+
+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;
+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/strings.sql b/src/test/regress/sql/strings.sql
new file mode 100644
index 0000000..2c50253
--- /dev/null
+++ b/src/test/regress/sql/strings.sql
@@ -0,0 +1,761 @@
+--
+-- 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 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 back reference in regexp_replace
+SELECT regexp_replace('1112223333', E'(\\d{3})(\\d{3})(\\d{4})', E'(\\1) \\2-\\3');
+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');
+
+-- 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/subscription.sql b/src/test/regress/sql/subscription.sql
new file mode 100644
index 0000000..855a341
--- /dev/null
+++ b/src/test/regress/sql/subscription.sql
@@ -0,0 +1,208 @@
+--
+-- SUBSCRIPTION
+--
+
+CREATE ROLE regress_subscription_user LOGIN SUPERUSER;
+CREATE ROLE regress_subscription_user2;
+CREATE ROLE regress_subscription_user_dummy LOGIN NOSUPERUSER;
+SET SESSION AUTHORIZATION 'regress_subscription_user';
+
+-- fail - no publications
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'foo';
+
+-- fail - no connection
+CREATE SUBSCRIPTION regress_testsub PUBLICATION foo;
+
+-- fail - cannot do CREATE SUBSCRIPTION CREATE SLOT inside transaction block
+BEGIN;
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'testconn' PUBLICATION testpub WITH (create_slot);
+COMMIT;
+
+-- fail - invalid connection string
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'testconn' PUBLICATION testpub;
+
+-- fail - duplicate publications
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION foo, testpub, foo WITH (connect = false);
+
+-- ok
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false);
+
+COMMENT ON SUBSCRIPTION regress_testsub IS 'test subscription';
+SELECT obj_description(s.oid, 'pg_subscription') FROM pg_subscription s;
+
+-- fail - name already exists
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false);
+
+-- fail - must be superuser
+SET SESSION AUTHORIZATION 'regress_subscription_user2';
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION foo WITH (connect = false);
+SET SESSION AUTHORIZATION 'regress_subscription_user';
+
+-- fail - invalid option combinations
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, copy_data = true);
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, enabled = true);
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, create_slot = true);
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, enabled = true);
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, create_slot = true);
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE);
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, enabled = false);
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, create_slot = false);
+
+-- ok - with slot_name = NONE
+CREATE SUBSCRIPTION regress_testsub3 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, connect = false);
+-- fail
+ALTER SUBSCRIPTION regress_testsub3 ENABLE;
+ALTER SUBSCRIPTION regress_testsub3 REFRESH PUBLICATION;
+
+DROP SUBSCRIPTION regress_testsub3;
+
+-- fail - invalid connection string
+ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
+ALTER SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist2';
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
+
+-- fail
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = '');
+
+-- fail
+ALTER SUBSCRIPTION regress_doesnotexist CONNECTION 'dbname=regress_doesnotexist2';
+ALTER SUBSCRIPTION regress_testsub SET (create_slot = false);
+
+\dRs+
+
+BEGIN;
+ALTER SUBSCRIPTION regress_testsub ENABLE;
+
+\dRs
+
+ALTER SUBSCRIPTION regress_testsub DISABLE;
+
+\dRs
+
+COMMIT;
+
+-- fail - must be owner of subscription
+SET ROLE regress_subscription_user_dummy;
+ALTER SUBSCRIPTION regress_testsub RENAME TO regress_testsub_dummy;
+RESET ROLE;
+
+ALTER SUBSCRIPTION regress_testsub RENAME TO regress_testsub_foo;
+ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = local);
+ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
+
+\dRs+
+
+-- rename back to keep the rest simple
+ALTER SUBSCRIPTION regress_testsub_foo RENAME TO regress_testsub;
+
+-- fail - new owner must be superuser
+ALTER SUBSCRIPTION regress_testsub OWNER TO regress_subscription_user2;
+ALTER ROLE regress_subscription_user2 SUPERUSER;
+-- now it works
+ALTER SUBSCRIPTION regress_testsub OWNER TO regress_subscription_user2;
+
+-- fail - cannot do DROP SUBSCRIPTION inside transaction block with slot name
+BEGIN;
+DROP SUBSCRIPTION regress_testsub;
+COMMIT;
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+
+-- now it works
+BEGIN;
+DROP SUBSCRIPTION regress_testsub;
+COMMIT;
+
+DROP SUBSCRIPTION IF EXISTS regress_testsub;
+DROP SUBSCRIPTION regress_testsub; -- fail
+
+-- fail - binary must be boolean
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, binary = foo);
+
+-- now it works
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, binary = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (binary = false);
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+
+\dRs+
+
+DROP SUBSCRIPTION regress_testsub;
+
+-- fail - streaming must be boolean
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, streaming = foo);
+
+-- now it works
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, streaming = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+
+\dRs+
+
+-- fail - publication already exists
+ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub WITH (refresh = false);
+
+-- fail - publication used more than once
+ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub1 WITH (refresh = false);
+
+-- ok - add two publications into subscription
+ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
+
+-- fail - publications already exist
+ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
+
+\dRs+
+
+-- fail - publication used more then once
+ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub1 WITH (refresh = false);
+
+-- fail - all publications are deleted
+ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub, testpub1, testpub2 WITH (refresh = false);
+
+-- fail - publication does not exist in subscription
+ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub3 WITH (refresh = false);
+
+-- ok - delete publications
+ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
+
+\dRs+
+
+DROP SUBSCRIPTION regress_testsub;
+
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION mypub
+ WITH (connect = false, create_slot = false, copy_data = false);
+
+ALTER SUBSCRIPTION regress_testsub ENABLE;
+
+-- fail - ALTER SUBSCRIPTION with refresh is not allowed in a transaction
+-- block or function
+BEGIN;
+ALTER SUBSCRIPTION regress_testsub SET PUBLICATION mypub WITH (refresh = true);
+END;
+
+BEGIN;
+ALTER SUBSCRIPTION regress_testsub REFRESH PUBLICATION;
+END;
+
+CREATE FUNCTION func() RETURNS VOID AS
+$$ ALTER SUBSCRIPTION regress_testsub SET PUBLICATION mypub WITH (refresh = true) $$ LANGUAGE SQL;
+SELECT func();
+
+ALTER SUBSCRIPTION regress_testsub DISABLE;
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+DROP FUNCTION func;
+
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_subscription_user;
+DROP ROLE regress_subscription_user2;
+DROP ROLE regress_subscription_user_dummy;
diff --git a/src/test/regress/sql/subselect.sql b/src/test/regress/sql/subselect.sql
new file mode 100644
index 0000000..94ba91f
--- /dev/null
+++ b/src/test/regress/sql/subselect.sql
@@ -0,0 +1,939 @@
+--
+-- SUBSELECT
+--
+
+SELECT 1 AS one WHERE 1 IN (SELECT 1);
+
+SELECT 1 AS zero WHERE 1 NOT IN (SELECT 1);
+
+SELECT 1 AS zero WHERE 1 IN (SELECT 2);
+
+-- Check grammar's handling of extra parens in assorted contexts
+
+SELECT * FROM (SELECT 1 AS x) ss;
+SELECT * FROM ((SELECT 1 AS x)) ss;
+
+(SELECT 2) UNION SELECT 2;
+((SELECT 2)) UNION SELECT 2;
+
+SELECT ((SELECT 2) UNION SELECT 2);
+SELECT (((SELECT 2)) UNION SELECT 2);
+
+SELECT (SELECT ARRAY[1,2,3])[1];
+SELECT ((SELECT ARRAY[1,2,3]))[2];
+SELECT (((SELECT ARRAY[1,2,3])))[3];
+
+-- Set up some simple test tables
+
+CREATE TABLE SUBSELECT_TBL (
+ f1 integer,
+ f2 integer,
+ f3 float
+);
+
+INSERT INTO SUBSELECT_TBL VALUES (1, 2, 3);
+INSERT INTO SUBSELECT_TBL VALUES (2, 3, 4);
+INSERT INTO SUBSELECT_TBL VALUES (3, 4, 5);
+INSERT INTO SUBSELECT_TBL VALUES (1, 1, 1);
+INSERT INTO SUBSELECT_TBL VALUES (2, 2, 2);
+INSERT INTO SUBSELECT_TBL VALUES (3, 3, 3);
+INSERT INTO SUBSELECT_TBL VALUES (6, 7, 8);
+INSERT INTO SUBSELECT_TBL VALUES (8, 9, NULL);
+
+SELECT * FROM SUBSELECT_TBL;
+
+-- Uncorrelated subselects
+
+SELECT f1 AS "Constant Select" FROM SUBSELECT_TBL
+ WHERE f1 IN (SELECT 1);
+
+SELECT f1 AS "Uncorrelated Field" FROM SUBSELECT_TBL
+ WHERE f1 IN (SELECT f2 FROM SUBSELECT_TBL);
+
+SELECT f1 AS "Uncorrelated Field" FROM SUBSELECT_TBL
+ WHERE f1 IN (SELECT f2 FROM SUBSELECT_TBL WHERE
+ f2 IN (SELECT f1 FROM SUBSELECT_TBL));
+
+SELECT f1, f2
+ FROM SUBSELECT_TBL
+ WHERE (f1, f2) NOT IN (SELECT f2, CAST(f3 AS int4) FROM SUBSELECT_TBL
+ WHERE f3 IS NOT NULL);
+
+-- Correlated subselects
+
+SELECT f1 AS "Correlated Field", f2 AS "Second Field"
+ FROM SUBSELECT_TBL upper
+ WHERE f1 IN (SELECT f2 FROM SUBSELECT_TBL WHERE f1 = upper.f1);
+
+SELECT f1 AS "Correlated Field", f3 AS "Second Field"
+ FROM SUBSELECT_TBL upper
+ WHERE f1 IN
+ (SELECT f2 FROM SUBSELECT_TBL WHERE CAST(upper.f2 AS float) = f3);
+
+SELECT f1 AS "Correlated Field", f3 AS "Second Field"
+ FROM SUBSELECT_TBL upper
+ WHERE f3 IN (SELECT upper.f1 + f2 FROM SUBSELECT_TBL
+ WHERE f2 = CAST(f3 AS integer));
+
+SELECT f1 AS "Correlated Field"
+ FROM SUBSELECT_TBL
+ WHERE (f1, f2) IN (SELECT f2, CAST(f3 AS int4) FROM SUBSELECT_TBL
+ WHERE f3 IS NOT NULL);
+
+--
+-- Use some existing tables in the regression test
+--
+
+SELECT ss.f1 AS "Correlated Field", ss.f3 AS "Second Field"
+ FROM SUBSELECT_TBL ss
+ WHERE f1 NOT IN (SELECT f1+1 FROM INT4_TBL
+ WHERE f1 != ss.f1 AND f1 < 2147483647);
+
+select q1, float8(count(*)) / (select count(*) from int8_tbl)
+from int8_tbl group by q1 order by q1;
+
+-- Unspecified-type literals in output columns should resolve as text
+
+SELECT *, pg_typeof(f1) FROM
+ (SELECT 'foo' AS f1 FROM generate_series(1,3)) ss ORDER BY 1;
+
+-- ... unless there's context to suggest differently
+
+explain (verbose, costs off) select '42' union all select '43';
+explain (verbose, costs off) select '42' union all select 43;
+
+-- check materialization of an initplan reference (bug #14524)
+explain (verbose, costs off)
+select 1 = all (select (select 1));
+select 1 = all (select (select 1));
+
+--
+-- Check EXISTS simplification with LIMIT
+--
+explain (costs off)
+select * from int4_tbl o where exists
+ (select 1 from int4_tbl i where i.f1=o.f1 limit null);
+explain (costs off)
+select * from int4_tbl o where not exists
+ (select 1 from int4_tbl i where i.f1=o.f1 limit 1);
+explain (costs off)
+select * from int4_tbl o where exists
+ (select 1 from int4_tbl i where i.f1=o.f1 limit 0);
+
+--
+-- Test cases to catch unpleasant interactions between IN-join processing
+-- and subquery pullup.
+--
+
+select count(*) from
+ (select 1 from tenk1 a
+ where unique1 IN (select hundred from tenk1 b)) ss;
+select count(distinct ss.ten) from
+ (select ten from tenk1 a
+ where unique1 IN (select hundred from tenk1 b)) ss;
+select count(*) from
+ (select 1 from tenk1 a
+ where unique1 IN (select distinct hundred from tenk1 b)) ss;
+select count(distinct ss.ten) from
+ (select ten from tenk1 a
+ where unique1 IN (select distinct hundred from tenk1 b)) ss;
+
+--
+-- Test cases to check for overenthusiastic optimization of
+-- "IN (SELECT DISTINCT ...)" and related cases. Per example from
+-- Luca Pireddu and Michael Fuhr.
+--
+
+CREATE TEMP TABLE foo (id integer);
+CREATE TEMP TABLE bar (id1 integer, id2 integer);
+
+INSERT INTO foo VALUES (1);
+
+INSERT INTO bar VALUES (1, 1);
+INSERT INTO bar VALUES (2, 2);
+INSERT INTO bar VALUES (3, 1);
+
+-- These cases require an extra level of distinct-ing above subquery s
+SELECT * FROM foo WHERE id IN
+ (SELECT id2 FROM (SELECT DISTINCT id1, id2 FROM bar) AS s);
+SELECT * FROM foo WHERE id IN
+ (SELECT id2 FROM (SELECT id1,id2 FROM bar GROUP BY id1,id2) AS s);
+SELECT * FROM foo WHERE id IN
+ (SELECT id2 FROM (SELECT id1, id2 FROM bar UNION
+ SELECT id1, id2 FROM bar) AS s);
+
+-- These cases do not
+SELECT * FROM foo WHERE id IN
+ (SELECT id2 FROM (SELECT DISTINCT ON (id2) id1, id2 FROM bar) AS s);
+SELECT * FROM foo WHERE id IN
+ (SELECT id2 FROM (SELECT id2 FROM bar GROUP BY id2) AS s);
+SELECT * FROM foo WHERE id IN
+ (SELECT id2 FROM (SELECT id2 FROM bar UNION
+ SELECT id2 FROM bar) AS s);
+
+--
+-- Test case to catch problems with multiply nested sub-SELECTs not getting
+-- recalculated properly. Per bug report from Didier Moens.
+--
+
+CREATE TABLE orderstest (
+ approver_ref integer,
+ po_ref integer,
+ ordercanceled boolean
+);
+
+INSERT INTO orderstest VALUES (1, 1, false);
+INSERT INTO orderstest VALUES (66, 5, false);
+INSERT INTO orderstest VALUES (66, 6, false);
+INSERT INTO orderstest VALUES (66, 7, false);
+INSERT INTO orderstest VALUES (66, 1, true);
+INSERT INTO orderstest VALUES (66, 8, false);
+INSERT INTO orderstest VALUES (66, 1, false);
+INSERT INTO orderstest VALUES (77, 1, false);
+INSERT INTO orderstest VALUES (1, 1, false);
+INSERT INTO orderstest VALUES (66, 1, false);
+INSERT INTO orderstest VALUES (1, 1, false);
+
+CREATE VIEW orders_view AS
+SELECT *,
+(SELECT CASE
+ WHEN ord.approver_ref=1 THEN '---' ELSE 'Approved'
+ END) AS "Approved",
+(SELECT CASE
+ WHEN ord.ordercanceled
+ THEN 'Canceled'
+ ELSE
+ (SELECT CASE
+ WHEN ord.po_ref=1
+ THEN
+ (SELECT CASE
+ WHEN ord.approver_ref=1
+ THEN '---'
+ ELSE 'Approved'
+ END)
+ ELSE 'PO'
+ END)
+END) AS "Status",
+(CASE
+ WHEN ord.ordercanceled
+ THEN 'Canceled'
+ ELSE
+ (CASE
+ WHEN ord.po_ref=1
+ THEN
+ (CASE
+ WHEN ord.approver_ref=1
+ THEN '---'
+ ELSE 'Approved'
+ END)
+ ELSE 'PO'
+ END)
+END) AS "Status_OK"
+FROM orderstest ord;
+
+SELECT * FROM orders_view;
+
+DROP TABLE orderstest cascade;
+
+--
+-- Test cases to catch situations where rule rewriter fails to propagate
+-- hasSubLinks flag correctly. Per example from Kyle Bateman.
+--
+
+create temp table parts (
+ partnum text,
+ cost float8
+);
+
+create temp table shipped (
+ ttype char(2),
+ ordnum int4,
+ partnum text,
+ value float8
+);
+
+create temp view shipped_view as
+ select * from shipped where ttype = 'wt';
+
+create rule shipped_view_insert as on insert to shipped_view do instead
+ insert into shipped values('wt', new.ordnum, new.partnum, new.value);
+
+insert into parts (partnum, cost) values (1, 1234.56);
+
+insert into shipped_view (ordnum, partnum, value)
+ values (0, 1, (select cost from parts where partnum = '1'));
+
+select * from shipped_view;
+
+create rule shipped_view_update as on update to shipped_view do instead
+ update shipped set partnum = new.partnum, value = new.value
+ where ttype = new.ttype and ordnum = new.ordnum;
+
+update shipped_view set value = 11
+ from int4_tbl a join int4_tbl b
+ on (a.f1 = (select f1 from int4_tbl c where c.f1=b.f1))
+ where ordnum = a.f1;
+
+select * from shipped_view;
+
+select f1, ss1 as relabel from
+ (select *, (select sum(f1) from int4_tbl b where f1 >= a.f1) as ss1
+ from int4_tbl a) ss;
+
+--
+-- Test cases involving PARAM_EXEC parameters and min/max index optimizations.
+-- Per bug report from David Sanchez i Gregori.
+--
+
+select * from (
+ select max(unique1) from tenk1 as a
+ where exists (select 1 from tenk1 as b where b.thousand = a.unique2)
+) ss;
+
+select * from (
+ select min(unique1) from tenk1 as a
+ where not exists (select 1 from tenk1 as b where b.unique2 = 10000)
+) ss;
+
+--
+-- Test that an IN implemented using a UniquePath does unique-ification
+-- with the right semantics, as per bug #4113. (Unfortunately we have
+-- no simple way to ensure that this test case actually chooses that type
+-- of plan, but it does in releases 7.4-8.3. Note that an ordering difference
+-- here might mean that some other plan type is being used, rendering the test
+-- pointless.)
+--
+
+create temp table numeric_table (num_col numeric);
+insert into numeric_table values (1), (1.000000000000000000001), (2), (3);
+
+create temp table float_table (float_col float8);
+insert into float_table values (1), (2), (3);
+
+select * from float_table
+ where float_col in (select num_col from numeric_table);
+
+select * from numeric_table
+ where num_col in (select float_col from float_table);
+
+--
+-- Test case for bug #4290: bogus calculation of subplan param sets
+--
+
+create temp table ta (id int primary key, val int);
+
+insert into ta values(1,1);
+insert into ta values(2,2);
+
+create temp table tb (id int primary key, aval int);
+
+insert into tb values(1,1);
+insert into tb values(2,1);
+insert into tb values(3,2);
+insert into tb values(4,2);
+
+create temp table tc (id int primary key, aid int);
+
+insert into tc values(1,1);
+insert into tc values(2,2);
+
+select
+ ( select min(tb.id) from tb
+ where tb.aval = (select ta.val from ta where ta.id = tc.aid) ) as min_tb_id
+from tc;
+
+--
+-- Test case for 8.3 "failed to locate grouping columns" bug
+--
+
+create temp table t1 (f1 numeric(14,0), f2 varchar(30));
+
+select * from
+ (select distinct f1, f2, (select f2 from t1 x where x.f1 = up.f1) as fs
+ from t1 up) ss
+group by f1,f2,fs;
+
+--
+-- Test case for bug #5514 (mishandling of whole-row Vars in subselects)
+--
+
+create temp table table_a(id integer);
+insert into table_a values (42);
+
+create temp view view_a as select * from table_a;
+
+select view_a from view_a;
+select (select view_a) from view_a;
+select (select (select view_a)) from view_a;
+select (select (a.*)::text) from view_a a;
+
+--
+-- Check that whole-row Vars reading the result of a subselect don't include
+-- any junk columns therein
+--
+
+select q from (select max(f1) from int4_tbl group by f1 order by f1) q;
+with q as (select max(f1) from int4_tbl group by f1 order by f1)
+ select q from q;
+
+--
+-- Test case for sublinks pulled up into joinaliasvars lists in an
+-- inherited update/delete query
+--
+
+begin; -- this shouldn't delete anything, but be safe
+
+delete from road
+where exists (
+ select 1
+ from
+ int4_tbl cross join
+ ( select f1, array(select q1 from int8_tbl) as arr
+ from text_tbl ) ss
+ where road.name = ss.f1 );
+
+rollback;
+
+--
+-- Test case for sublinks pushed down into subselects via join alias expansion
+--
+
+select
+ (select sq1) as qq1
+from
+ (select exists(select 1 from int4_tbl where f1 = q2) as sq1, 42 as dummy
+ from int8_tbl) sq0
+ join
+ int4_tbl i4 on dummy = i4.f1;
+
+--
+-- Test case for subselect within UPDATE of INSERT...ON CONFLICT DO UPDATE
+--
+create temp table upsert(key int4 primary key, val text);
+insert into upsert values(1, 'val') on conflict (key) do update set val = 'not seen';
+insert into upsert values(1, 'val') on conflict (key) do update set val = 'seen with subselect ' || (select f1 from int4_tbl where f1 != 0 limit 1)::text;
+
+select * from upsert;
+
+with aa as (select 'int4_tbl' u from int4_tbl limit 1)
+insert into upsert values (1, 'x'), (999, 'y')
+on conflict (key) do update set val = (select u from aa)
+returning *;
+
+--
+-- Test case for cross-type partial matching in hashed subplan (bug #7597)
+--
+
+create temp table outer_7597 (f1 int4, f2 int4);
+insert into outer_7597 values (0, 0);
+insert into outer_7597 values (1, 0);
+insert into outer_7597 values (0, null);
+insert into outer_7597 values (1, null);
+
+create temp table inner_7597(c1 int8, c2 int8);
+insert into inner_7597 values(0, null);
+
+select * from outer_7597 where (f1, f2) not in (select * from inner_7597);
+
+--
+-- Similar test case using text that verifies that collation
+-- information is passed through by execTuplesEqual() in nodeSubplan.c
+-- (otherwise it would error in texteq())
+--
+
+create temp table outer_text (f1 text, f2 text);
+insert into outer_text values ('a', 'a');
+insert into outer_text values ('b', 'a');
+insert into outer_text values ('a', null);
+insert into outer_text values ('b', null);
+
+create temp table inner_text (c1 text, c2 text);
+insert into inner_text values ('a', null);
+insert into inner_text values ('123', '456');
+
+select * from outer_text where (f1, f2) not in (select * from inner_text);
+
+--
+-- Another test case for cross-type hashed subplans: comparison of
+-- inner-side values must be done with appropriate operator
+--
+
+explain (verbose, costs off)
+select 'foo'::text in (select 'bar'::name union all select 'bar'::name);
+
+select 'foo'::text in (select 'bar'::name union all select 'bar'::name);
+
+--
+-- Test that we don't try to hash nested records (bug #17363)
+-- (Hashing could be supported, but for now we don't)
+--
+
+explain (verbose, costs off)
+select row(row(row(1))) = any (select row(row(1)));
+
+select row(row(row(1))) = any (select row(row(1)));
+
+--
+-- Test case for premature memory release during hashing of subplan output
+--
+
+select '1'::text in (select '1'::name union all select '1'::name);
+
+--
+-- Test that we don't try to use a hashed subplan if the simplified
+-- testexpr isn't of the right shape
+--
+
+-- this fails by default, of course
+select * from int8_tbl where q1 in (select c1 from inner_text);
+
+begin;
+
+-- make an operator to allow it to succeed
+create function bogus_int8_text_eq(int8, text) returns boolean
+language sql as 'select $1::text = $2';
+
+create operator = (procedure=bogus_int8_text_eq, leftarg=int8, rightarg=text);
+
+explain (costs off)
+select * from int8_tbl where q1 in (select c1 from inner_text);
+select * from int8_tbl where q1 in (select c1 from inner_text);
+
+-- inlining of this function results in unusual number of hash clauses,
+-- which we can still cope with
+create or replace function bogus_int8_text_eq(int8, text) returns boolean
+language sql as 'select $1::text = $2 and $1::text = $2';
+
+explain (costs off)
+select * from int8_tbl where q1 in (select c1 from inner_text);
+select * from int8_tbl where q1 in (select c1 from inner_text);
+
+-- inlining of this function causes LHS and RHS to be switched,
+-- which we can't cope with, so hashing should be abandoned
+create or replace function bogus_int8_text_eq(int8, text) returns boolean
+language sql as 'select $2 = $1::text';
+
+explain (costs off)
+select * from int8_tbl where q1 in (select c1 from inner_text);
+select * from int8_tbl where q1 in (select c1 from inner_text);
+
+rollback; -- to get rid of the bogus operator
+
+--
+-- Test resolution of hashed vs non-hashed implementation of EXISTS subplan
+--
+explain (costs off)
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0);
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0);
+
+explain (costs off)
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0)
+ and thousand = 1;
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0)
+ and thousand = 1;
+
+-- It's possible for the same EXISTS to get resolved both ways
+create temp table exists_tbl (c1 int, c2 int, c3 int) partition by list (c1);
+create temp table exists_tbl_null partition of exists_tbl for values in (null);
+create temp table exists_tbl_def partition of exists_tbl default;
+insert into exists_tbl select x, x/2, x+1 from generate_series(0,10) x;
+analyze exists_tbl;
+explain (costs off)
+select * from exists_tbl t1
+ where (exists(select 1 from exists_tbl t2 where t1.c1 = t2.c2) or c3 < 0);
+select * from exists_tbl t1
+ where (exists(select 1 from exists_tbl t2 where t1.c1 = t2.c2) or c3 < 0);
+
+--
+-- Test case for planner bug with nested EXISTS handling
+--
+select a.thousand from tenk1 a, tenk1 b
+where a.thousand = b.thousand
+ and exists ( select 1 from tenk1 c where b.hundred = c.hundred
+ and not exists ( select 1 from tenk1 d
+ where a.thousand = d.thousand ) );
+
+--
+-- Check that nested sub-selects are not pulled up if they contain volatiles
+--
+explain (verbose, costs off)
+ select x, x from
+ (select (select now()) as x from (values(1),(2)) v(y)) ss;
+explain (verbose, costs off)
+ select x, x from
+ (select (select random()) as x from (values(1),(2)) v(y)) ss;
+explain (verbose, costs off)
+ select x, x from
+ (select (select now() where y=y) as x from (values(1),(2)) v(y)) ss;
+explain (verbose, costs off)
+ select x, x from
+ (select (select random() where y=y) as x from (values(1),(2)) v(y)) ss;
+
+--
+-- Test rescan of a hashed subplan (the use of random() is to prevent the
+-- sub-select from being pulled up, which would result in not hashing)
+--
+explain (verbose, costs off)
+select sum(ss.tst::int) from
+ onek o cross join lateral (
+ select i.ten in (select f1 from int4_tbl where f1 <= o.hundred) as tst,
+ random() as r
+ from onek i where i.unique1 = o.unique1 ) ss
+where o.ten = 0;
+
+select sum(ss.tst::int) from
+ onek o cross join lateral (
+ select i.ten in (select f1 from int4_tbl where f1 <= o.hundred) as tst,
+ random() as r
+ from onek i where i.unique1 = o.unique1 ) ss
+where o.ten = 0;
+
+--
+-- Test rescan of a SetOp node
+--
+explain (costs off)
+select count(*) from
+ onek o cross join lateral (
+ select * from onek i1 where i1.unique1 = o.unique1
+ except
+ select * from onek i2 where i2.unique1 = o.unique2
+ ) ss
+where o.ten = 1;
+
+select count(*) from
+ onek o cross join lateral (
+ select * from onek i1 where i1.unique1 = o.unique1
+ except
+ select * from onek i2 where i2.unique1 = o.unique2
+ ) ss
+where o.ten = 1;
+
+--
+-- Test rescan of a RecursiveUnion node
+--
+explain (costs off)
+select sum(o.four), sum(ss.a) from
+ onek o cross join lateral (
+ with recursive x(a) as
+ (select o.four as a
+ union
+ select a + 1 from x
+ where a < 10)
+ select * from x
+ ) ss
+where o.ten = 1;
+
+select sum(o.four), sum(ss.a) from
+ onek o cross join lateral (
+ with recursive x(a) as
+ (select o.four as a
+ union
+ select a + 1 from x
+ where a < 10)
+ select * from x
+ ) ss
+where o.ten = 1;
+
+--
+-- Check we don't misoptimize a NOT IN where the subquery returns no rows.
+--
+create temp table notinouter (a int);
+create temp table notininner (b int not null);
+insert into notinouter values (null), (1);
+
+select * from notinouter where a not in (select b from notininner);
+
+--
+-- Check we behave sanely in corner case of empty SELECT list (bug #8648)
+--
+create temp table nocolumns();
+select exists(select * from nocolumns);
+
+--
+-- Check behavior with a SubPlan in VALUES (bug #14924)
+--
+select val.x
+ from generate_series(1,10) as s(i),
+ lateral (
+ values ((select s.i + 1)), (s.i + 101)
+ ) as val(x)
+where s.i < 10 and (select val.x) < 110;
+
+-- 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;
+
+--
+-- Check sane behavior with nested IN SubLinks
+--
+explain (verbose, costs off)
+select * from int4_tbl where
+ (case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
+ (select ten from tenk1 b);
+select * from int4_tbl where
+ (case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
+ (select ten from tenk1 b);
+
+--
+-- Check for incorrect optimization when IN subquery contains a SRF
+--
+explain (verbose, costs off)
+select * from int4_tbl o where (f1, f1) in
+ (select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1);
+select * from int4_tbl o where (f1, f1) in
+ (select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1);
+
+--
+-- check for over-optimization of whole-row Var referencing an Append plan
+--
+select (select q from
+ (select 1,2,3 where f1 > 0
+ union all
+ select 4,5,6.0 where f1 <= 0
+ ) q )
+from int4_tbl;
+
+--
+-- Check for sane handling of a lateral reference in a subquery's quals
+-- (most of the complication here is to prevent the test case from being
+-- flattened too much)
+--
+explain (verbose, costs off)
+select * from
+ int4_tbl i4,
+ lateral (
+ select i4.f1 > 1 as b, 1 as id
+ from (select random() order by 1) as t1
+ union all
+ select true as b, 2 as id
+ ) as t2
+where b and f1 >= 0;
+
+select * from
+ int4_tbl i4,
+ lateral (
+ select i4.f1 > 1 as b, 1 as id
+ from (select random() order by 1) as t1
+ union all
+ select true as b, 2 as id
+ ) as t2
+where b and f1 >= 0;
+
+--
+-- Check that volatile quals aren't pushed down past a DISTINCT:
+-- nextval() should not be called more than the nominal number of times
+--
+create temp sequence ts1;
+
+select * from
+ (select distinct ten from tenk1) ss
+ where ten < 10 + nextval('ts1')
+ order by 1;
+
+select nextval('ts1');
+
+--
+-- Check that volatile quals aren't pushed down past a set-returning function;
+-- while a nonvolatile qual can be, if it doesn't reference the SRF.
+--
+create function tattle(x int, y int) returns bool
+volatile language plpgsql as $$
+begin
+ raise notice 'x = %, y = %', x, y;
+ return x > y;
+end$$;
+
+explain (verbose, costs off)
+select * from
+ (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
+ where tattle(x, 8);
+
+select * from
+ (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
+ where tattle(x, 8);
+
+-- if we pretend it's stable, we get different results:
+alter function tattle(x int, y int) stable;
+
+explain (verbose, costs off)
+select * from
+ (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
+ where tattle(x, 8);
+
+select * from
+ (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
+ where tattle(x, 8);
+
+-- although even a stable qual should not be pushed down if it references SRF
+explain (verbose, costs off)
+select * from
+ (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
+ where tattle(x, u);
+
+select * from
+ (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
+ where tattle(x, u);
+
+drop function tattle(x int, y int);
+
+--
+-- Test that LIMIT can be pushed to SORT through a subquery that just projects
+-- columns. We check for that having happened by looking to see if EXPLAIN
+-- ANALYZE shows that a top-N sort was used. We must suppress or filter away
+-- all the non-invariant parts of the EXPLAIN ANALYZE output.
+--
+create table sq_limit (pk int primary key, c1 int, c2 int);
+insert into sq_limit values
+ (1, 1, 1),
+ (2, 2, 2),
+ (3, 3, 3),
+ (4, 4, 4),
+ (5, 1, 1),
+ (6, 2, 2),
+ (7, 3, 3),
+ (8, 4, 4);
+
+create function explain_sq_limit() returns setof text language plpgsql as
+$$
+declare ln text;
+begin
+ for ln in
+ explain (analyze, summary off, timing off, costs off)
+ select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3
+ loop
+ ln := regexp_replace(ln, 'Memory: \S*', 'Memory: xxx');
+ return next ln;
+ end loop;
+end;
+$$;
+
+select * from explain_sq_limit();
+
+select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3;
+
+drop function explain_sq_limit();
+
+drop table sq_limit;
+
+--
+-- Ensure that backward scan direction isn't propagated into
+-- expression subqueries (bug #15336)
+--
+
+begin;
+
+declare c1 scroll cursor for
+ select * from generate_series(1,4) i
+ where i <> all (values (2),(3));
+
+move forward all in c1;
+fetch backward all in c1;
+
+commit;
+
+--
+-- Tests for CTE inlining behavior
+--
+
+-- Basic subquery that can be inlined
+explain (verbose, costs off)
+with x as (select * from (select f1 from subselect_tbl) ss)
+select * from x where f1 = 1;
+
+-- Explicitly request materialization
+explain (verbose, costs off)
+with x as materialized (select * from (select f1 from subselect_tbl) ss)
+select * from x where f1 = 1;
+
+-- Stable functions are safe to inline
+explain (verbose, costs off)
+with x as (select * from (select f1, now() from subselect_tbl) ss)
+select * from x where f1 = 1;
+
+-- Volatile functions prevent inlining
+explain (verbose, costs off)
+with x as (select * from (select f1, random() from subselect_tbl) ss)
+select * from x where f1 = 1;
+
+-- SELECT FOR UPDATE cannot be inlined
+explain (verbose, costs off)
+with x as (select * from (select f1 from subselect_tbl for update) ss)
+select * from x where f1 = 1;
+
+-- Multiply-referenced CTEs are inlined only when requested
+explain (verbose, costs off)
+with x as (select * from (select f1, now() as n from subselect_tbl) ss)
+select * from x, x x2 where x.n = x2.n;
+
+explain (verbose, costs off)
+with x as not materialized (select * from (select f1, now() as n from subselect_tbl) ss)
+select * from x, x x2 where x.n = x2.n;
+
+-- Multiply-referenced CTEs can't be inlined if they contain outer self-refs
+explain (verbose, costs off)
+with recursive x(a) as
+ ((values ('a'), ('b'))
+ union all
+ (with z as not materialized (select * from x)
+ select z.a || z1.a as a from z cross join z as z1
+ where length(z.a || z1.a) < 5))
+select * from x;
+
+with recursive x(a) as
+ ((values ('a'), ('b'))
+ union all
+ (with z as not materialized (select * from x)
+ select z.a || z1.a as a from z cross join z as z1
+ where length(z.a || z1.a) < 5))
+select * from x;
+
+explain (verbose, costs off)
+with recursive x(a) as
+ ((values ('a'), ('b'))
+ union all
+ (with z as not materialized (select * from x)
+ select z.a || z.a as a from z
+ where length(z.a || z.a) < 5))
+select * from x;
+
+with recursive x(a) as
+ ((values ('a'), ('b'))
+ union all
+ (with z as not materialized (select * from x)
+ select z.a || z.a as a from z
+ where length(z.a || z.a) < 5))
+select * from x;
+
+-- Check handling of outer references
+explain (verbose, costs off)
+with x as (select * from int4_tbl)
+select * from (with y as (select * from x) select * from y) ss;
+
+explain (verbose, costs off)
+with x as materialized (select * from int4_tbl)
+select * from (with y as (select * from x) select * from y) ss;
+
+-- Ensure that we inline the currect CTE when there are
+-- multiple CTEs with the same name
+explain (verbose, costs off)
+with x as (select 1 as y)
+select * from (with x as (select 2 as y) select * from x) ss;
+
+-- Row marks are not pushed into CTEs
+explain (verbose, costs off)
+with x as (select * from subselect_tbl)
+select * from x for update;
diff --git a/src/test/regress/sql/sysviews.sql b/src/test/regress/sql/sysviews.sql
new file mode 100644
index 0000000..b24816e
--- /dev/null
+++ b/src/test/regress/sql/sysviews.sql
@@ -0,0 +1,65 @@
+--
+-- Test assorted system views
+--
+-- This test is mainly meant to provide some code coverage for the
+-- set-returning functions that underlie certain system views.
+-- The output of most of these functions is very environment-dependent,
+-- so our ability to test with fixed expected output is pretty limited;
+-- but even a trivial check of count(*) will exercise the normal code path
+-- through the SRF.
+
+select count(*) >= 0 as ok from pg_available_extension_versions;
+
+select count(*) >= 0 as ok from pg_available_extensions;
+
+-- The entire output of pg_backend_memory_contexts is not stable,
+-- we test only the existance and basic condition of TopMemoryContext.
+select name, ident, parent, level, total_bytes >= free_bytes
+ from pg_backend_memory_contexts where level = 0;
+
+-- At introduction, pg_config had 23 entries; it may grow
+select count(*) > 20 as ok from pg_config;
+
+-- We expect no cursors in this test; see also portals.sql
+select count(*) = 0 as ok from pg_cursors;
+
+select count(*) >= 0 as ok from pg_file_settings;
+
+-- There will surely be at least one rule
+select count(*) > 0 as ok from pg_hba_file_rules;
+
+-- There will surely be at least one active lock
+select count(*) > 0 as ok from pg_locks;
+
+-- We expect no prepared statements in this test; see also prepare.sql
+select count(*) = 0 as ok from pg_prepared_statements;
+
+-- See also prepared_xacts.sql
+select count(*) >= 0 as ok from pg_prepared_xacts;
+
+-- There will surely be at least one SLRU cache
+select count(*) > 0 as ok from pg_stat_slru;
+
+-- There must be only one record
+select count(*) = 1 as ok from pg_stat_wal;
+
+-- We expect no walreceiver running in this test
+select count(*) = 0 as ok from pg_stat_wal_receiver;
+
+-- This is to record the prevailing planner enable_foo settings during
+-- a regression test run.
+select name, setting from pg_settings where name like 'enable%';
+
+-- Test that the pg_timezone_names and pg_timezone_abbrevs views are
+-- more-or-less working. We can't test their contents in any great detail
+-- without the outputs changing anytime IANA updates the underlying data,
+-- but it seems reasonable to expect at least one entry per major meridian.
+-- (At the time of writing, the actual counts are around 38 because of
+-- zones using fractional GMT offsets, so this is a pretty loose test.)
+select count(distinct utc_offset) >= 24 as ok from pg_timezone_names;
+select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
+-- Let's check the non-default timezone abbreviation sets, too
+set timezone_abbreviations = 'Australia';
+select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
+set timezone_abbreviations = 'India';
+select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
diff --git a/src/test/regress/sql/tablesample.sql b/src/test/regress/sql/tablesample.sql
new file mode 100644
index 0000000..c39fe4b
--- /dev/null
+++ b/src/test/regress/sql/tablesample.sql
@@ -0,0 +1,110 @@
+CREATE TABLE test_tablesample (id int, name text) WITH (fillfactor=10);
+-- use fillfactor so we don't have to load too much data to get multiple pages
+
+INSERT INTO test_tablesample
+ SELECT i, repeat(i::text, 200) FROM generate_series(0, 9) s(i);
+
+SELECT t.id FROM test_tablesample AS t TABLESAMPLE SYSTEM (50) REPEATABLE (0);
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (100.0/11) REPEATABLE (0);
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (0);
+SELECT id FROM test_tablesample TABLESAMPLE BERNOULLI (50) REPEATABLE (0);
+SELECT id FROM test_tablesample TABLESAMPLE BERNOULLI (5.5) REPEATABLE (0);
+
+-- 100% should give repeatable count results (ie, all rows) in any case
+SELECT count(*) FROM test_tablesample TABLESAMPLE SYSTEM (100);
+SELECT count(*) FROM test_tablesample TABLESAMPLE SYSTEM (100) REPEATABLE (1+2);
+SELECT count(*) FROM test_tablesample TABLESAMPLE SYSTEM (100) REPEATABLE (0.4);
+
+CREATE VIEW test_tablesample_v1 AS
+ SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (10*2) REPEATABLE (2);
+CREATE VIEW test_tablesample_v2 AS
+ SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (99);
+\d+ test_tablesample_v1
+\d+ test_tablesample_v2
+
+-- check a sampled query doesn't affect cursor in progress
+BEGIN;
+DECLARE tablesample_cur CURSOR FOR
+ SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (0);
+
+FETCH FIRST FROM tablesample_cur;
+FETCH NEXT FROM tablesample_cur;
+FETCH NEXT FROM tablesample_cur;
+
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (0);
+
+FETCH NEXT FROM tablesample_cur;
+FETCH NEXT FROM tablesample_cur;
+FETCH NEXT FROM tablesample_cur;
+
+FETCH FIRST FROM tablesample_cur;
+FETCH NEXT FROM tablesample_cur;
+FETCH NEXT FROM tablesample_cur;
+FETCH NEXT FROM tablesample_cur;
+FETCH NEXT FROM tablesample_cur;
+FETCH NEXT FROM tablesample_cur;
+
+CLOSE tablesample_cur;
+END;
+
+EXPLAIN (COSTS OFF)
+ SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (2);
+EXPLAIN (COSTS OFF)
+ SELECT * FROM test_tablesample_v1;
+
+-- check inheritance behavior
+explain (costs off)
+ select count(*) from person tablesample bernoulli (100);
+select count(*) from person tablesample bernoulli (100);
+select count(*) from person;
+
+-- check that collations get assigned within the tablesample arguments
+SELECT count(*) FROM test_tablesample TABLESAMPLE bernoulli (('1'::text < '0'::text)::int);
+
+-- check behavior during rescans, as well as correct handling of min/max pct
+select * from
+ (values (0),(100)) v(pct),
+ lateral (select count(*) from tenk1 tablesample bernoulli (pct)) ss;
+select * from
+ (values (0),(100)) v(pct),
+ lateral (select count(*) from tenk1 tablesample system (pct)) ss;
+explain (costs off)
+select pct, count(unique1) from
+ (values (0),(100)) v(pct),
+ lateral (select * from tenk1 tablesample bernoulli (pct)) ss
+ group by pct;
+select pct, count(unique1) from
+ (values (0),(100)) v(pct),
+ lateral (select * from tenk1 tablesample bernoulli (pct)) ss
+ group by pct;
+select pct, count(unique1) from
+ (values (0),(100)) v(pct),
+ lateral (select * from tenk1 tablesample system (pct)) ss
+ group by pct;
+
+-- errors
+SELECT id FROM test_tablesample TABLESAMPLE FOOBAR (1);
+
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (NULL);
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (NULL);
+
+SELECT id FROM test_tablesample TABLESAMPLE BERNOULLI (-1);
+SELECT id FROM test_tablesample TABLESAMPLE BERNOULLI (200);
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (-1);
+SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (200);
+
+SELECT id FROM test_tablesample_v1 TABLESAMPLE BERNOULLI (1);
+INSERT INTO test_tablesample_v1 VALUES(1);
+
+WITH query_select AS (SELECT * FROM test_tablesample)
+SELECT * FROM query_select TABLESAMPLE BERNOULLI (5.5) REPEATABLE (1);
+
+SELECT q.* FROM (SELECT * FROM test_tablesample) as q TABLESAMPLE BERNOULLI (5);
+
+-- check partitioned tables support tablesample
+create table parted_sample (a int) partition by list (a);
+create table parted_sample_1 partition of parted_sample for values in (1);
+create table parted_sample_2 partition of parted_sample for values in (2);
+explain (costs off)
+ select * from parted_sample tablesample bernoulli (100);
+drop table parted_sample, parted_sample_1, parted_sample_2;
diff --git a/src/test/regress/sql/temp.sql b/src/test/regress/sql/temp.sql
new file mode 100644
index 0000000..424d12b
--- /dev/null
+++ b/src/test/regress/sql/temp.sql
@@ -0,0 +1,297 @@
+--
+-- 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 INDEX i_temptest;
+
+DROP TABLE temptest;
+
+SELECT * FROM temptest;
+
+DROP INDEX i_temptest;
+
+DROP TABLE temptest;
+
+-- 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;
+
+DROP TABLE temptest;
+
+SELECT * FROM temptest;
+
+DROP TABLE temptest;
+
+-- test temp table deletion
+
+CREATE TEMP TABLE temptest(col int);
+
+\c
+
+SELECT * FROM temptest;
+
+-- Test ON COMMIT DELETE ROWS
+
+CREATE TEMP TABLE temptest(col int) ON COMMIT DELETE ROWS;
+
+-- while we're here, verify successful truncation of index with SQL function
+CREATE INDEX ON temptest(bit_length(''));
+
+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;
+
+-- 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 foreign keys
+BEGIN;
+CREATE TEMP TABLE temptest1(col int PRIMARY KEY);
+CREATE TEMP TABLE temptest2(col int REFERENCES temptest1)
+ ON COMMIT DELETE ROWS;
+INSERT INTO temptest1 VALUES (1);
+INSERT INTO temptest2 VALUES (1);
+COMMIT;
+SELECT * FROM temptest1;
+SELECT * FROM temptest2;
+
+BEGIN;
+CREATE TEMP TABLE temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE TEMP TABLE temptest4(col int REFERENCES temptest3);
+COMMIT;
+
+-- Test manipulation of temp schema's placement in search path
+
+create table public.whereami (f1 text);
+insert into public.whereami values ('public');
+
+create temp table whereami (f1 text);
+insert into whereami values ('temp');
+
+create function public.whoami() returns text
+ as $$select 'public'::text$$ language sql;
+
+create function pg_temp.whoami() returns text
+ as $$select 'temp'::text$$ language sql;
+
+-- default should have pg_temp implicitly first, but only for tables
+select * from whereami;
+select whoami();
+
+-- can list temp first explicitly, but it still doesn't affect functions
+set search_path = pg_temp, public;
+select * from whereami;
+select whoami();
+
+-- or put it last for security
+set search_path = public, pg_temp;
+select * from whereami;
+select whoami();
+
+-- you can invoke a temp function explicitly, though
+select pg_temp.whoami();
+
+drop table public.whereami;
+
+-- 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;
+
+-- For partitioned temp tables, ON COMMIT actions ignore storage-less
+-- partitioned tables.
+begin;
+create temp table temp_parted_oncommit (a int)
+ partition by list (a) on commit delete rows;
+create temp table temp_parted_oncommit_1
+ partition of temp_parted_oncommit
+ for values in (1) on commit delete rows;
+insert into temp_parted_oncommit values (1);
+commit;
+-- partitions are emptied by the previous commit
+select * from temp_parted_oncommit;
+drop table temp_parted_oncommit;
+
+-- Check dependencies between ON COMMIT actions with a partitioned
+-- table and its partitions. Using ON COMMIT DROP on a parent removes
+-- the whole set.
+begin;
+create temp table temp_parted_oncommit_test (a int)
+ partition by list (a) on commit drop;
+create temp table temp_parted_oncommit_test1
+ partition of temp_parted_oncommit_test
+ for values in (1) on commit delete rows;
+create temp table temp_parted_oncommit_test2
+ partition of temp_parted_oncommit_test
+ for values in (2) on commit drop;
+insert into temp_parted_oncommit_test values (1), (2);
+commit;
+-- no relations remain in this case.
+select relname from pg_class where relname ~ '^temp_parted_oncommit_test';
+-- Using ON COMMIT DELETE on a partitioned table does not remove
+-- all rows if partitions preserve their data.
+begin;
+create temp table temp_parted_oncommit_test (a int)
+ partition by list (a) on commit delete rows;
+create temp table temp_parted_oncommit_test1
+ partition of temp_parted_oncommit_test
+ for values in (1) on commit preserve rows;
+create temp table temp_parted_oncommit_test2
+ partition of temp_parted_oncommit_test
+ for values in (2) on commit drop;
+insert into temp_parted_oncommit_test values (1), (2);
+commit;
+-- Data from the remaining partition is still here as its rows are
+-- preserved.
+select * from temp_parted_oncommit_test;
+-- two relations remain in this case.
+select relname from pg_class where relname ~ '^temp_parted_oncommit_test'
+ order by relname;
+drop table temp_parted_oncommit_test;
+
+-- Check dependencies between ON COMMIT actions with inheritance trees.
+-- Using ON COMMIT DROP on a parent removes the whole set.
+begin;
+create temp table temp_inh_oncommit_test (a int) on commit drop;
+create temp table temp_inh_oncommit_test1 ()
+ inherits(temp_inh_oncommit_test) on commit delete rows;
+insert into temp_inh_oncommit_test1 values (1);
+commit;
+-- no relations remain in this case
+select relname from pg_class where relname ~ '^temp_inh_oncommit_test';
+-- Data on the parent is removed, and the child goes away.
+begin;
+create temp table temp_inh_oncommit_test (a int) on commit delete rows;
+create temp table temp_inh_oncommit_test1 ()
+ inherits(temp_inh_oncommit_test) on commit drop;
+insert into temp_inh_oncommit_test1 values (1);
+insert into temp_inh_oncommit_test values (1);
+commit;
+select * from temp_inh_oncommit_test;
+-- one relation remains
+select relname from pg_class where relname ~ '^temp_inh_oncommit_test';
+drop table temp_inh_oncommit_test;
+
+-- Tests with two-phase commit
+-- Transactions creating objects in a temporary namespace cannot be used
+-- with two-phase commit.
+
+-- These cases generate errors about temporary namespace.
+-- Function creation
+begin;
+create function pg_temp.twophase_func() returns void as
+ $$ select '2pc_func'::text $$ language sql;
+prepare transaction 'twophase_func';
+-- Function drop
+create function pg_temp.twophase_func() returns void as
+ $$ select '2pc_func'::text $$ language sql;
+begin;
+drop function pg_temp.twophase_func();
+prepare transaction 'twophase_func';
+-- Operator creation
+begin;
+create operator pg_temp.@@ (leftarg = int4, rightarg = int4, procedure = int4mi);
+prepare transaction 'twophase_operator';
+
+-- These generate errors about temporary tables.
+begin;
+create type pg_temp.twophase_type as (a int);
+prepare transaction 'twophase_type';
+begin;
+create view pg_temp.twophase_view as select 1;
+prepare transaction 'twophase_view';
+begin;
+create sequence pg_temp.twophase_seq;
+prepare transaction 'twophase_sequence';
+
+-- Temporary tables cannot be used with two-phase commit.
+create temp table twophase_tab (a int);
+begin;
+select a from twophase_tab;
+prepare transaction 'twophase_tab';
+begin;
+insert into twophase_tab values (1);
+prepare transaction 'twophase_tab';
+begin;
+lock twophase_tab in access exclusive mode;
+prepare transaction 'twophase_tab';
+begin;
+drop table twophase_tab;
+prepare transaction 'twophase_tab';
+
+-- Corner case: current_schema may create a temporary schema if namespace
+-- creation is pending, so check after that. First reset the connection
+-- to remove the temporary namespace.
+\c -
+SET search_path TO 'pg_temp';
+BEGIN;
+SELECT current_schema() ~ 'pg_temp' AS is_temp_schema;
+PREPARE TRANSACTION 'twophase_search';
diff --git a/src/test/regress/sql/text.sql b/src/test/regress/sql/text.sql
new file mode 100644
index 0000000..56eee69
--- /dev/null
+++ b/src/test/regress/sql/text.sql
@@ -0,0 +1,118 @@
+--
+-- 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;
+
+CREATE TABLE TEXT_TBL (f1 text);
+
+INSERT INTO TEXT_TBL VALUES ('doh!');
+INSERT INTO TEXT_TBL VALUES ('hi de ho neighbor');
+
+SELECT * 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/tid.sql b/src/test/regress/sql/tid.sql
new file mode 100644
index 0000000..34546a3
--- /dev/null
+++ b/src/test/regress/sql/tid.sql
@@ -0,0 +1,54 @@
+-- tests for functions related to TID handling
+
+CREATE TABLE tid_tab (a int);
+
+-- min() and max() for TIDs
+INSERT INTO tid_tab VALUES (1), (2);
+SELECT min(ctid) FROM tid_tab;
+SELECT max(ctid) FROM tid_tab;
+TRUNCATE tid_tab;
+
+-- Tests for currtid2() with various relation kinds
+
+-- Materialized view
+CREATE MATERIALIZED VIEW tid_matview AS SELECT a FROM tid_tab;
+SELECT currtid2('tid_matview'::text, '(0,1)'::tid); -- fails
+INSERT INTO tid_tab VALUES (1);
+REFRESH MATERIALIZED VIEW tid_matview;
+SELECT currtid2('tid_matview'::text, '(0,1)'::tid); -- ok
+DROP MATERIALIZED VIEW tid_matview;
+TRUNCATE tid_tab;
+
+-- Sequence
+CREATE SEQUENCE tid_seq;
+SELECT currtid2('tid_seq'::text, '(0,1)'::tid); -- ok
+DROP SEQUENCE tid_seq;
+
+-- Index, fails with incorrect relation type
+CREATE INDEX tid_ind ON tid_tab(a);
+SELECT currtid2('tid_ind'::text, '(0,1)'::tid); -- fails
+DROP INDEX tid_ind;
+
+-- Partitioned table, no storage
+CREATE TABLE tid_part (a int) PARTITION BY RANGE (a);
+SELECT currtid2('tid_part'::text, '(0,1)'::tid); -- fails
+DROP TABLE tid_part;
+
+-- Views
+-- ctid not defined in the view
+CREATE VIEW tid_view_no_ctid AS SELECT a FROM tid_tab;
+SELECT currtid2('tid_view_no_ctid'::text, '(0,1)'::tid); -- fails
+DROP VIEW tid_view_no_ctid;
+-- ctid fetched directly from the source table.
+CREATE VIEW tid_view_with_ctid AS SELECT ctid, a FROM tid_tab;
+SELECT currtid2('tid_view_with_ctid'::text, '(0,1)'::tid); -- fails
+INSERT INTO tid_tab VALUES (1);
+SELECT currtid2('tid_view_with_ctid'::text, '(0,1)'::tid); -- ok
+DROP VIEW tid_view_with_ctid;
+TRUNCATE tid_tab;
+-- ctid attribute with incorrect data type
+CREATE VIEW tid_view_fake_ctid AS SELECT 1 AS ctid, 2 AS a;
+SELECT currtid2('tid_view_fake_ctid'::text, '(0,1)'::tid); -- fails
+DROP VIEW tid_view_fake_ctid;
+
+DROP TABLE tid_tab CASCADE;
diff --git a/src/test/regress/sql/tidrangescan.sql b/src/test/regress/sql/tidrangescan.sql
new file mode 100644
index 0000000..ac09ebb
--- /dev/null
+++ b/src/test/regress/sql/tidrangescan.sql
@@ -0,0 +1,101 @@
+-- tests for tidrangescans
+
+SET enable_seqscan TO off;
+CREATE TABLE tidrangescan(id integer, data text);
+
+-- empty table
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid < '(1, 0)';
+SELECT ctid FROM tidrangescan WHERE ctid < '(1, 0)';
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid > '(9, 0)';
+SELECT ctid FROM tidrangescan WHERE ctid > '(9, 0)';
+
+-- insert enough tuples to fill at least two pages
+INSERT INTO tidrangescan SELECT i,repeat('x', 100) FROM generate_series(1,200) AS s(i);
+
+-- remove all tuples after the 10th tuple on each page. Trying to ensure
+-- we get the same layout with all CPU architectures and smaller than standard
+-- page sizes.
+DELETE FROM tidrangescan
+WHERE substring(ctid::text FROM ',(\d+)\)')::integer > 10 OR substring(ctid::text FROM '\((\d+),')::integer > 2;
+VACUUM tidrangescan;
+
+-- range scans with upper bound
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)';
+SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)';
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid <= '(1,5)';
+SELECT ctid FROM tidrangescan WHERE ctid <= '(1,5)';
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)';
+SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)';
+
+-- range scans with lower bound
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid > '(2,8)';
+SELECT ctid FROM tidrangescan WHERE ctid > '(2,8)';
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE '(2,8)' < ctid;
+SELECT ctid FROM tidrangescan WHERE '(2,8)' < ctid;
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid >= '(2,8)';
+SELECT ctid FROM tidrangescan WHERE ctid >= '(2,8)';
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid >= '(100,0)';
+SELECT ctid FROM tidrangescan WHERE ctid >= '(100,0)';
+
+-- range scans with both bounds
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE ctid > '(1,4)' AND '(1,7)' >= ctid;
+SELECT ctid FROM tidrangescan WHERE ctid > '(1,4)' AND '(1,7)' >= ctid;
+
+EXPLAIN (COSTS OFF)
+SELECT ctid FROM tidrangescan WHERE '(1,7)' >= ctid AND ctid > '(1,4)';
+SELECT ctid FROM tidrangescan WHERE '(1,7)' >= ctid AND ctid > '(1,4)';
+
+-- extreme offsets
+SELECT ctid FROM tidrangescan WHERE ctid > '(0,65535)' AND ctid < '(1,0)' LIMIT 1;
+SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)' LIMIT 1;
+
+SELECT ctid FROM tidrangescan WHERE ctid > '(4294967295,65535)';
+SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)';
+
+-- NULLs in the range cannot return tuples
+SELECT ctid FROM tidrangescan WHERE ctid >= (SELECT NULL::tid);
+
+-- rescans
+EXPLAIN (COSTS OFF)
+SELECT t.ctid,t2.c FROM tidrangescan t,
+LATERAL (SELECT count(*) c FROM tidrangescan t2 WHERE t2.ctid <= t.ctid) t2
+WHERE t.ctid < '(1,0)';
+
+SELECT t.ctid,t2.c FROM tidrangescan t,
+LATERAL (SELECT count(*) c FROM tidrangescan t2 WHERE t2.ctid <= t.ctid) t2
+WHERE t.ctid < '(1,0)';
+
+-- cursors
+
+-- Ensure we get a TID Range scan without a Materialize node.
+EXPLAIN (COSTS OFF)
+DECLARE c SCROLL CURSOR FOR SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)';
+
+BEGIN;
+DECLARE c SCROLL CURSOR FOR SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)';
+FETCH NEXT c;
+FETCH NEXT c;
+FETCH PRIOR c;
+FETCH FIRST c;
+FETCH LAST c;
+COMMIT;
+
+DROP TABLE tidrangescan;
+
+RESET enable_seqscan;
diff --git a/src/test/regress/sql/tidscan.sql b/src/test/regress/sql/tidscan.sql
new file mode 100644
index 0000000..313e0fb
--- /dev/null
+++ b/src/test/regress/sql/tidscan.sql
@@ -0,0 +1,104 @@
+-- tests for tidscans
+
+CREATE TABLE tidscan(id integer);
+
+-- only insert a few rows, we don't want to spill onto a second table page
+INSERT INTO tidscan VALUES (1), (2), (3);
+
+-- show ctids
+SELECT ctid, * FROM tidscan;
+
+-- ctid equality - implemented as tidscan
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,1)';
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,1)';
+
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE '(0,1)' = ctid;
+SELECT ctid, * FROM tidscan WHERE '(0,1)' = ctid;
+
+-- OR'd clauses
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+
+-- ctid = ScalarArrayOp - implemented as tidscan
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
+SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
+
+-- ctid != ScalarArrayOp - can't be implemented as tidscan
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE ctid != ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
+SELECT ctid, * FROM tidscan WHERE ctid != ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
+
+-- tid equality extracted from sub-AND clauses
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan
+WHERE (id = 3 AND ctid IN ('(0,2)', '(0,3)')) OR (ctid = '(0,1)' AND id = 1);
+SELECT ctid, * FROM tidscan
+WHERE (id = 3 AND ctid IN ('(0,2)', '(0,3)')) OR (ctid = '(0,1)' AND id = 1);
+
+-- nestloop-with-inner-tidscan joins on tid
+SET enable_hashjoin TO off; -- otherwise hash join might win
+EXPLAIN (COSTS OFF)
+SELECT t1.ctid, t1.*, t2.ctid, t2.*
+FROM tidscan t1 JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1;
+SELECT t1.ctid, t1.*, t2.ctid, t2.*
+FROM tidscan t1 JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1;
+EXPLAIN (COSTS OFF)
+SELECT t1.ctid, t1.*, t2.ctid, t2.*
+FROM tidscan t1 LEFT JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1;
+SELECT t1.ctid, t1.*, t2.ctid, t2.*
+FROM tidscan t1 LEFT JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1;
+RESET enable_hashjoin;
+
+-- exercise backward scan and rewind
+BEGIN;
+DECLARE c CURSOR FOR
+SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
+FETCH ALL FROM c;
+FETCH BACKWARD 1 FROM c;
+FETCH FIRST FROM c;
+ROLLBACK;
+
+-- tidscan via CURRENT OF
+BEGIN;
+DECLARE c CURSOR FOR SELECT ctid, * FROM tidscan;
+FETCH NEXT FROM c; -- skip one row
+FETCH NEXT FROM c;
+-- perform update
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+UPDATE tidscan SET id = -id WHERE CURRENT OF c RETURNING *;
+FETCH NEXT FROM c;
+-- perform update
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+UPDATE tidscan SET id = -id WHERE CURRENT OF c RETURNING *;
+SELECT * FROM tidscan;
+-- position cursor past any rows
+FETCH NEXT FROM c;
+-- should error out
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
+UPDATE tidscan SET id = -id WHERE CURRENT OF c RETURNING *;
+ROLLBACK;
+
+-- bulk joins on CTID
+-- (these plans don't use TID scans, but this still seems like an
+-- appropriate place for these tests)
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
+SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
+SET enable_hashjoin TO off;
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
+SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
+RESET enable_hashjoin;
+
+-- check predicate lock on CTID
+BEGIN ISOLATION LEVEL SERIALIZABLE;
+SELECT * FROM tidscan WHERE ctid = '(0,1)';
+-- locktype should be 'tuple'
+SELECT locktype, mode FROM pg_locks WHERE pid = pg_backend_pid() AND mode = 'SIReadLock';
+ROLLBACK;
+
+DROP TABLE tidscan;
diff --git a/src/test/regress/sql/time.sql b/src/test/regress/sql/time.sql
new file mode 100644
index 0000000..3637f28
--- /dev/null
+++ b/src/test/regress/sql/time.sql
@@ -0,0 +1,72 @@
+--
+-- TIME
+--
+
+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 ('2003-03-07 15:36:39 America/New_York');
+INSERT INTO TIME_TBL VALUES ('2003-07-07 15:36:39 America/New_York');
+-- this should fail (the timezone offset is not known)
+INSERT INTO TIME_TBL VALUES ('15:36:39 America/New_York');
+
+
+SELECT f1 AS "Time" FROM TIME_TBL;
+
+SELECT f1 AS "Three" FROM TIME_TBL WHERE f1 < '05:06:07';
+
+SELECT f1 AS "Five" FROM TIME_TBL WHERE f1 > '05:06:07';
+
+SELECT f1 AS "None" FROM TIME_TBL WHERE f1 < '00:00';
+
+SELECT f1 AS "Eight" FROM TIME_TBL WHERE f1 >= '00:00';
+
+-- Check edge cases
+SELECT '23:59:59.999999'::time;
+SELECT '23:59:59.9999999'::time; -- rounds up
+SELECT '23:59:60'::time; -- rounds up
+SELECT '24:00:00'::time; -- allowed
+SELECT '24:00:00.01'::time; -- not allowed
+SELECT '23:59:60.01'::time; -- not allowed
+SELECT '24:01:00'::time; -- not allowed
+SELECT '25:00:00'::time; -- not allowed
+
+--
+-- TIME simple math
+--
+-- We now make a distinction between time and intervals,
+-- and adding two times together makes no sense at all.
+-- Leave in one query to show that it is rejected,
+-- and do the rest of the testing in horology.sql
+-- where we do mixed-type arithmetic. - thomas 2000-12-02
+
+SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
+
+--
+-- test EXTRACT
+--
+SELECT EXTRACT(MICROSECOND FROM TIME '2020-05-26 13:30:25.575401');
+SELECT EXTRACT(MILLISECOND FROM TIME '2020-05-26 13:30:25.575401');
+SELECT EXTRACT(SECOND FROM TIME '2020-05-26 13:30:25.575401');
+SELECT EXTRACT(MINUTE FROM TIME '2020-05-26 13:30:25.575401');
+SELECT EXTRACT(HOUR FROM TIME '2020-05-26 13:30:25.575401');
+SELECT EXTRACT(DAY FROM TIME '2020-05-26 13:30:25.575401'); -- error
+SELECT EXTRACT(FORTNIGHT FROM TIME '2020-05-26 13:30:25.575401'); -- error
+SELECT EXTRACT(TIMEZONE FROM TIME '2020-05-26 13:30:25.575401'); -- error
+SELECT EXTRACT(EPOCH FROM TIME '2020-05-26 13:30:25.575401');
+
+-- date_part implementation is mostly the same as extract, so only
+-- test a few cases for additional coverage.
+SELECT date_part('microsecond', TIME '2020-05-26 13:30:25.575401');
+SELECT date_part('millisecond', TIME '2020-05-26 13:30:25.575401');
+SELECT date_part('second', TIME '2020-05-26 13:30:25.575401');
+SELECT date_part('epoch', TIME '2020-05-26 13:30:25.575401');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
new file mode 100644
index 0000000..ebc969f
--- /dev/null
+++ b/src/test/regress/sql/timestamp.sql
@@ -0,0 +1,386 @@
+--
+-- TIMESTAMP
+--
+
+CREATE TABLE TIMESTAMP_TBL (d1 timestamp(2) without time zone);
+
+-- Test shorthand input values
+-- We can't just "select" the results since they aren't constants; test for
+-- equality instead. We can do that by running the test inside a transaction
+-- block, within which the value of 'now' shouldn't change, and so these
+-- related values shouldn't either.
+
+BEGIN;
+
+INSERT INTO TIMESTAMP_TBL VALUES ('today');
+INSERT INTO TIMESTAMP_TBL VALUES ('yesterday');
+INSERT INTO TIMESTAMP_TBL VALUES ('tomorrow');
+-- time zone should be ignored by this data type
+INSERT INTO TIMESTAMP_TBL VALUES ('tomorrow EST');
+INSERT INTO TIMESTAMP_TBL VALUES ('tomorrow zulu');
+
+SELECT count(*) AS One FROM TIMESTAMP_TBL WHERE d1 = timestamp without time zone 'today';
+SELECT count(*) AS Three FROM TIMESTAMP_TBL WHERE d1 = timestamp without time zone 'tomorrow';
+SELECT count(*) AS One FROM TIMESTAMP_TBL WHERE d1 = timestamp without time zone 'yesterday';
+
+COMMIT;
+
+DELETE FROM TIMESTAMP_TBL;
+
+-- Verify that 'now' *does* change over a reasonable interval such as 100 msec,
+-- and that it doesn't change over the same interval within a transaction block
+
+INSERT INTO TIMESTAMP_TBL VALUES ('now');
+SELECT pg_sleep(0.1);
+
+BEGIN;
+INSERT INTO TIMESTAMP_TBL VALUES ('now');
+SELECT pg_sleep(0.1);
+INSERT INTO TIMESTAMP_TBL VALUES ('now');
+SELECT pg_sleep(0.1);
+SELECT count(*) AS two FROM TIMESTAMP_TBL WHERE d1 = timestamp(2) without time zone 'now';
+SELECT count(d1) AS three, count(DISTINCT d1) AS two FROM TIMESTAMP_TBL;
+COMMIT;
+
+TRUNCATE TIMESTAMP_TBL;
+
+-- Special values
+INSERT INTO TIMESTAMP_TBL VALUES ('-infinity');
+INSERT INTO TIMESTAMP_TBL VALUES ('infinity');
+INSERT INTO TIMESTAMP_TBL VALUES ('epoch');
+
+-- Postgres v6.0 standard output format
+INSERT INTO TIMESTAMP_TBL VALUES ('Mon Feb 10 17:32:01 1997 PST');
+
+-- Variations on Postgres v6.1 standard output format
+INSERT INTO TIMESTAMP_TBL VALUES ('Mon Feb 10 17:32:01.000001 1997 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('Mon Feb 10 17:32:01.999999 1997 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('Mon Feb 10 17:32:01.4 1997 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('Mon Feb 10 17:32:01.5 1997 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('Mon Feb 10 17:32:01.6 1997 PST');
+
+-- ISO 8601 format
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-01-02');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-01-02 03:04:05');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-02-10 17:32:01-08');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-02-10 17:32:01-0800');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-02-10 17:32:01 -08:00');
+INSERT INTO TIMESTAMP_TBL VALUES ('19970210 173201 -0800');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-06-10 17:32:01 -07:00');
+INSERT INTO TIMESTAMP_TBL VALUES ('2001-09-22T18:19:20');
+
+-- POSIX format (note that the timezone abbrev is just decoration here)
+INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 08:14:01 GMT+8');
+INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 13:14:02 GMT-1');
+INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 12:14:03 GMT-2');
+INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 03:14:04 PST+8');
+INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 02:14:05 MST+7:00');
+
+-- Variations for acceptable input formats
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 17:32:01 1997 -0800');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 5:32PM 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997/02/10 17:32:01-0800');
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-02-10 17:32:01 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb-10-1997 17:32:01 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('02-10-1997 17:32:01 PST');
+INSERT INTO TIMESTAMP_TBL VALUES ('19970210 173201 PST');
+set datestyle to ymd;
+INSERT INTO TIMESTAMP_TBL VALUES ('97FEB10 5:32:01PM UTC');
+INSERT INTO TIMESTAMP_TBL VALUES ('97/02/10 17:32:01 UTC');
+reset datestyle;
+INSERT INTO TIMESTAMP_TBL VALUES ('1997.041 17:32:01 UTC');
+INSERT INTO TIMESTAMP_TBL VALUES ('19970210 173201 America/New_York');
+-- this fails (even though TZ is a no-op, we still look it up)
+INSERT INTO TIMESTAMP_TBL VALUES ('19970710 173201 America/Does_not_exist');
+
+-- Check date conversion and date arithmetic
+INSERT INTO TIMESTAMP_TBL VALUES ('1997-06-10 18:32:01 PDT');
+
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 11 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 12 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 13 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 14 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 15 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 1997');
+
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 0097 BC');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 0097');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 0597');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 1097');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 1697');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 1797');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 1897');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 2097');
+
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 28 17:32:01 1996');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 29 17:32:01 1996');
+INSERT INTO TIMESTAMP_TBL VALUES ('Mar 01 17:32:01 1996');
+INSERT INTO TIMESTAMP_TBL VALUES ('Dec 30 17:32:01 1996');
+INSERT INTO TIMESTAMP_TBL VALUES ('Dec 31 17:32:01 1996');
+INSERT INTO TIMESTAMP_TBL VALUES ('Jan 01 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 28 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 29 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Mar 01 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Dec 30 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Dec 31 17:32:01 1997');
+INSERT INTO TIMESTAMP_TBL VALUES ('Dec 31 17:32:01 1999');
+INSERT INTO TIMESTAMP_TBL VALUES ('Jan 01 17:32:01 2000');
+INSERT INTO TIMESTAMP_TBL VALUES ('Dec 31 17:32:01 2000');
+INSERT INTO TIMESTAMP_TBL VALUES ('Jan 01 17:32:01 2001');
+
+-- Currently unsupported syntax and ranges
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 -0097');
+INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 5097 BC');
+
+SELECT d1 FROM TIMESTAMP_TBL;
+
+-- Check behavior at the boundaries of the timestamp range
+SELECT '4714-11-24 00:00:00 BC'::timestamp;
+SELECT '4714-11-23 23:59:59 BC'::timestamp; -- out of range
+SELECT '294276-12-31 23:59:59'::timestamp;
+SELECT '294277-01-01 00:00:00'::timestamp; -- out of range
+
+-- Demonstrate functions and operators
+SELECT d1 FROM TIMESTAMP_TBL
+ WHERE d1 > timestamp without time zone '1997-01-02';
+
+SELECT d1 FROM TIMESTAMP_TBL
+ WHERE d1 < timestamp without time zone '1997-01-02';
+
+SELECT d1 FROM TIMESTAMP_TBL
+ WHERE d1 = timestamp without time zone '1997-01-02';
+
+SELECT d1 FROM TIMESTAMP_TBL
+ WHERE d1 != timestamp without time zone '1997-01-02';
+
+SELECT d1 FROM TIMESTAMP_TBL
+ WHERE d1 <= timestamp without time zone '1997-01-02';
+
+SELECT d1 FROM TIMESTAMP_TBL
+ WHERE d1 >= timestamp without time zone '1997-01-02';
+
+SELECT d1 - timestamp without time zone '1997-01-02' AS diff
+ FROM TIMESTAMP_TBL WHERE d1 BETWEEN '1902-01-01' AND '2038-01-01';
+
+SELECT date_trunc( 'week', timestamp '2004-02-29 15:44:17.71393' ) AS week_trunc;
+
+-- verify date_bin behaves the same as date_trunc for relevant intervals
+
+-- case 1: AD dates, origin < input
+SELECT
+ str,
+ interval,
+ date_trunc(str, ts) = date_bin(interval::interval, ts, timestamp '2001-01-01') AS equal
+FROM (
+ VALUES
+ ('week', '7 d'),
+ ('day', '1 d'),
+ ('hour', '1 h'),
+ ('minute', '1 m'),
+ ('second', '1 s'),
+ ('millisecond', '1 ms'),
+ ('microsecond', '1 us')
+) intervals (str, interval),
+(VALUES (timestamp '2020-02-29 15:44:17.71393')) ts (ts);
+
+-- case 2: BC dates, origin < input
+SELECT
+ str,
+ interval,
+ date_trunc(str, ts) = date_bin(interval::interval, ts, timestamp '2000-01-01 BC') AS equal
+FROM (
+ VALUES
+ ('week', '7 d'),
+ ('day', '1 d'),
+ ('hour', '1 h'),
+ ('minute', '1 m'),
+ ('second', '1 s'),
+ ('millisecond', '1 ms'),
+ ('microsecond', '1 us')
+) intervals (str, interval),
+(VALUES (timestamp '0055-6-10 15:44:17.71393 BC')) ts (ts);
+
+-- case 3: AD dates, origin > input
+SELECT
+ str,
+ interval,
+ date_trunc(str, ts) = date_bin(interval::interval, ts, timestamp '2020-03-02') AS equal
+FROM (
+ VALUES
+ ('week', '7 d'),
+ ('day', '1 d'),
+ ('hour', '1 h'),
+ ('minute', '1 m'),
+ ('second', '1 s'),
+ ('millisecond', '1 ms'),
+ ('microsecond', '1 us')
+) intervals (str, interval),
+(VALUES (timestamp '2020-02-29 15:44:17.71393')) ts (ts);
+
+-- case 4: BC dates, origin > input
+SELECT
+ str,
+ interval,
+ date_trunc(str, ts) = date_bin(interval::interval, ts, timestamp '0055-06-17 BC') AS equal
+FROM (
+ VALUES
+ ('week', '7 d'),
+ ('day', '1 d'),
+ ('hour', '1 h'),
+ ('minute', '1 m'),
+ ('second', '1 s'),
+ ('millisecond', '1 ms'),
+ ('microsecond', '1 us')
+) intervals (str, interval),
+(VALUES (timestamp '0055-6-10 15:44:17.71393 BC')) ts (ts);
+
+-- bin timestamps into arbitrary intervals
+SELECT
+ interval,
+ ts,
+ origin,
+ date_bin(interval::interval, ts, origin)
+FROM (
+ VALUES
+ ('15 days'),
+ ('2 hours'),
+ ('1 hour 30 minutes'),
+ ('15 minutes'),
+ ('10 seconds'),
+ ('100 milliseconds'),
+ ('250 microseconds')
+) intervals (interval),
+(VALUES (timestamp '2020-02-11 15:44:17.71393')) ts (ts),
+(VALUES (timestamp '2001-01-01')) origin (origin);
+
+-- shift bins using the origin parameter:
+SELECT date_bin('5 min'::interval, timestamp '2020-02-01 01:01:01', timestamp '2020-02-01 00:02:30');
+
+-- disallow intervals with months or years
+SELECT date_bin('5 months'::interval, timestamp '2020-02-01 01:01:01', timestamp '2001-01-01');
+SELECT date_bin('5 years'::interval, timestamp '2020-02-01 01:01:01', timestamp '2001-01-01');
+
+-- disallow zero intervals
+SELECT date_bin('0 days'::interval, timestamp '1970-01-01 01:00:00' , timestamp '1970-01-01 00:00:00');
+
+-- disallow negative intervals
+SELECT date_bin('-2 days'::interval, timestamp '1970-01-01 01:00:00' , timestamp '1970-01-01 00:00:00');
+
+-- Test casting within a BETWEEN qualifier
+SELECT d1 - timestamp without time zone '1997-01-02' AS diff
+ FROM TIMESTAMP_TBL
+ WHERE d1 BETWEEN timestamp without time zone '1902-01-01'
+ AND timestamp without time zone '2038-01-01';
+
+-- DATE_PART (timestamp_part)
+SELECT d1 as "timestamp",
+ date_part( 'year', d1) AS year, date_part( 'month', d1) AS month,
+ date_part( 'day', d1) AS day, date_part( 'hour', d1) AS hour,
+ date_part( 'minute', d1) AS minute, date_part( 'second', d1) AS second
+ FROM TIMESTAMP_TBL;
+
+SELECT d1 as "timestamp",
+ date_part( 'quarter', d1) AS quarter, date_part( 'msec', d1) AS msec,
+ date_part( 'usec', d1) AS usec
+ FROM TIMESTAMP_TBL;
+
+SELECT d1 as "timestamp",
+ date_part( 'isoyear', d1) AS isoyear, date_part( 'week', d1) AS week,
+ date_part( 'isodow', d1) AS isodow, date_part( 'dow', d1) AS dow,
+ date_part( 'doy', d1) AS doy
+ FROM TIMESTAMP_TBL;
+
+SELECT d1 as "timestamp",
+ date_part( 'decade', d1) AS decade,
+ date_part( 'century', d1) AS century,
+ date_part( 'millennium', d1) AS millennium,
+ round(date_part( 'julian', d1)) AS julian,
+ date_part( 'epoch', d1) AS epoch
+ FROM TIMESTAMP_TBL;
+
+-- extract implementation is mostly the same as date_part, so only
+-- test a few cases for additional coverage.
+SELECT d1 as "timestamp",
+ extract(microseconds from d1) AS microseconds,
+ extract(milliseconds from d1) AS milliseconds,
+ extract(seconds from d1) AS seconds,
+ round(extract(julian from d1)) AS julian,
+ extract(epoch from d1) AS epoch
+ FROM TIMESTAMP_TBL;
+
+-- value near upper bound uses special case in code
+SELECT date_part('epoch', '294270-01-01 00:00:00'::timestamp);
+SELECT extract(epoch from '294270-01-01 00:00:00'::timestamp);
+-- another internal overflow test case
+SELECT extract(epoch from '5000-01-01 00:00:00'::timestamp);
+
+-- TO_CHAR()
+SELECT to_char(d1, 'DAY Day day DY Dy dy MONTH Month month RM MON Mon mon')
+ FROM TIMESTAMP_TBL;
+
+SELECT to_char(d1, 'FMDAY FMDay FMday FMMONTH FMMonth FMmonth FMRM')
+ FROM TIMESTAMP_TBL;
+
+SELECT to_char(d1, 'Y,YYY YYYY YYY YY Y CC Q MM WW DDD DD D J')
+ FROM TIMESTAMP_TBL;
+
+SELECT to_char(d1, 'FMY,YYY FMYYYY FMYYY FMYY FMY FMCC FMQ FMMM FMWW FMDDD FMDD FMD FMJ')
+ FROM TIMESTAMP_TBL;
+
+SELECT to_char(d1, 'HH HH12 HH24 MI SS SSSS')
+ FROM TIMESTAMP_TBL;
+
+SELECT to_char(d1, E'"HH:MI:SS is" HH:MI:SS "\\"text between quote marks\\""')
+ FROM TIMESTAMP_TBL;
+
+SELECT to_char(d1, 'HH24--text--MI--text--SS')
+ FROM TIMESTAMP_TBL;
+
+SELECT to_char(d1, 'YYYYTH YYYYth Jth')
+ FROM TIMESTAMP_TBL;
+
+SELECT to_char(d1, 'YYYY A.D. YYYY a.d. YYYY bc HH:MI:SS P.M. HH:MI:SS p.m. HH:MI:SS pm')
+ FROM TIMESTAMP_TBL;
+
+SELECT to_char(d1, 'IYYY IYY IY I IW IDDD ID')
+ FROM TIMESTAMP_TBL;
+
+SELECT to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
+ FROM TIMESTAMP_TBL;
+
+SELECT to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
+-- Roman months, with upper and lower case.
+SELECT i,
+ to_char(i * interval '1mon', 'rm'),
+ to_char(i * interval '1mon', 'RM')
+ FROM generate_series(-13, 13) i;
+
+-- timestamp numeric fields constructor
+SELECT make_timestamp(2014, 12, 28, 6, 30, 45.887);
+SELECT make_timestamp(-44, 3, 15, 12, 30, 15);
+-- should fail
+select make_timestamp(0, 7, 15, 12, 30, 15);
+
+-- generate_series for timestamp
+select * from generate_series('2020-01-01 00:00'::timestamp,
+ '2020-01-02 03:00'::timestamp,
+ '1 hour'::interval);
+-- the LIMIT should allow this to terminate in a reasonable amount of time
+-- (but that unfortunately doesn't work yet for SELECT * FROM ...)
+select generate_series('2022-01-01 00:00'::timestamp,
+ 'infinity'::timestamp,
+ '1 month'::interval) limit 10;
+-- errors
+select * from generate_series('2020-01-01 00:00'::timestamp,
+ '2020-01-02 03:00'::timestamp,
+ '0 hour'::interval);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
new file mode 100644
index 0000000..bce70fb
--- /dev/null
+++ b/src/test/regress/sql/timestamptz.sql
@@ -0,0 +1,567 @@
+--
+-- TIMESTAMPTZ
+--
+
+CREATE TABLE TIMESTAMPTZ_TBL (d1 timestamp(2) with time zone);
+
+-- Test shorthand input values
+-- We can't just "select" the results since they aren't constants; test for
+-- equality instead. We can do that by running the test inside a transaction
+-- block, within which the value of 'now' shouldn't change, and so these
+-- related values shouldn't either.
+
+BEGIN;
+
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('today');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('yesterday');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('tomorrow');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('tomorrow EST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('tomorrow zulu');
+
+SELECT count(*) AS One FROM TIMESTAMPTZ_TBL WHERE d1 = timestamp with time zone 'today';
+SELECT count(*) AS One FROM TIMESTAMPTZ_TBL WHERE d1 = timestamp with time zone 'tomorrow';
+SELECT count(*) AS One FROM TIMESTAMPTZ_TBL WHERE d1 = timestamp with time zone 'yesterday';
+SELECT count(*) AS One FROM TIMESTAMPTZ_TBL WHERE d1 = timestamp with time zone 'tomorrow EST';
+SELECT count(*) AS One FROM TIMESTAMPTZ_TBL WHERE d1 = timestamp with time zone 'tomorrow zulu';
+
+COMMIT;
+
+DELETE FROM TIMESTAMPTZ_TBL;
+
+-- Verify that 'now' *does* change over a reasonable interval such as 100 msec,
+-- and that it doesn't change over the same interval within a transaction block
+
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('now');
+SELECT pg_sleep(0.1);
+
+BEGIN;
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('now');
+SELECT pg_sleep(0.1);
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('now');
+SELECT pg_sleep(0.1);
+SELECT count(*) AS two FROM TIMESTAMPTZ_TBL WHERE d1 = timestamp(2) with time zone 'now';
+SELECT count(d1) AS three, count(DISTINCT d1) AS two FROM TIMESTAMPTZ_TBL;
+COMMIT;
+
+TRUNCATE TIMESTAMPTZ_TBL;
+
+-- Special values
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('-infinity');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('infinity');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('epoch');
+
+-- Postgres v6.0 standard output format
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mon Feb 10 17:32:01 1997 PST');
+
+-- Variations on Postgres v6.1 standard output format
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mon Feb 10 17:32:01.000001 1997 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mon Feb 10 17:32:01.999999 1997 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mon Feb 10 17:32:01.4 1997 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mon Feb 10 17:32:01.5 1997 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mon Feb 10 17:32:01.6 1997 PST');
+
+-- ISO 8601 format
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-01-02');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-01-02 03:04:05');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-02-10 17:32:01-08');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-02-10 17:32:01-0800');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-02-10 17:32:01 -08:00');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970210 173201 -0800');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-06-10 17:32:01 -07:00');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('2001-09-22T18:19:20');
+
+-- POSIX format (note that the timezone abbrev is just decoration here)
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 08:14:01 GMT+8');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 13:14:02 GMT-1');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 12:14:03 GMT-2');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 03:14:04 PST+8');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 02:14:05 MST+7:00');
+
+-- Variations for acceptable input formats
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 17:32:01 1997 -0800');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 5:32PM 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997/02/10 17:32:01-0800');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-02-10 17:32:01 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb-10-1997 17:32:01 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('02-10-1997 17:32:01 PST');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970210 173201 PST');
+set datestyle to ymd;
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('97FEB10 5:32:01PM UTC');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('97/02/10 17:32:01 UTC');
+reset datestyle;
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997.041 17:32:01 UTC');
+
+-- timestamps at different timezones
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970210 173201 America/New_York');
+SELECT '19970210 173201' AT TIME ZONE 'America/New_York';
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970710 173201 America/New_York');
+SELECT '19970710 173201' AT TIME ZONE 'America/New_York';
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970710 173201 America/Does_not_exist');
+SELECT '19970710 173201' AT TIME ZONE 'America/Does_not_exist';
+
+-- Daylight saving time for timestamps beyond 32-bit time_t range.
+SELECT '20500710 173201 Europe/Helsinki'::timestamptz; -- DST
+SELECT '20500110 173201 Europe/Helsinki'::timestamptz; -- non-DST
+
+SELECT '205000-07-10 17:32:01 Europe/Helsinki'::timestamptz; -- DST
+SELECT '205000-01-10 17:32:01 Europe/Helsinki'::timestamptz; -- non-DST
+
+-- Check date conversion and date arithmetic
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-06-10 18:32:01 PDT');
+
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 11 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 12 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 13 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 14 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 15 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 1997');
+
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 0097 BC');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 0097');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 0597');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 1097');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 1697');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 1797');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 1897');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 2097');
+
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 28 17:32:01 1996');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 29 17:32:01 1996');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mar 01 17:32:01 1996');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 30 17:32:01 1996');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 31 17:32:01 1996');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Jan 01 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 28 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 29 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mar 01 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 30 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 31 17:32:01 1997');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 31 17:32:01 1999');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Jan 01 17:32:01 2000');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 31 17:32:01 2000');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Jan 01 17:32:01 2001');
+
+-- Currently unsupported syntax and ranges
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 -0097');
+INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 5097 BC');
+
+-- Alternative field order that we've historically supported (sort of)
+-- with regular and POSIXy timezone specs
+SELECT 'Wed Jul 11 10:51:14 America/New_York 2001'::timestamptz;
+SELECT 'Wed Jul 11 10:51:14 GMT-4 2001'::timestamptz;
+SELECT 'Wed Jul 11 10:51:14 GMT+4 2001'::timestamptz;
+SELECT 'Wed Jul 11 10:51:14 PST-03:00 2001'::timestamptz;
+SELECT 'Wed Jul 11 10:51:14 PST+03:00 2001'::timestamptz;
+
+SELECT d1 FROM TIMESTAMPTZ_TBL;
+
+-- Check behavior at the boundaries of the timestamp range
+SELECT '4714-11-24 00:00:00+00 BC'::timestamptz;
+SELECT '4714-11-23 16:00:00-08 BC'::timestamptz;
+SELECT 'Sun Nov 23 16:00:00 4714 PST BC'::timestamptz;
+SELECT '4714-11-23 23:59:59+00 BC'::timestamptz; -- out of range
+SELECT '294276-12-31 23:59:59+00'::timestamptz;
+SELECT '294276-12-31 15:59:59-08'::timestamptz;
+SELECT '294277-01-01 00:00:00+00'::timestamptz; -- out of range
+SELECT '294277-12-31 16:00:00-08'::timestamptz; -- out of range
+
+-- Demonstrate functions and operators
+SELECT d1 FROM TIMESTAMPTZ_TBL
+ WHERE d1 > timestamp with time zone '1997-01-02';
+
+SELECT d1 FROM TIMESTAMPTZ_TBL
+ WHERE d1 < timestamp with time zone '1997-01-02';
+
+SELECT d1 FROM TIMESTAMPTZ_TBL
+ WHERE d1 = timestamp with time zone '1997-01-02';
+
+SELECT d1 FROM TIMESTAMPTZ_TBL
+ WHERE d1 != timestamp with time zone '1997-01-02';
+
+SELECT d1 FROM TIMESTAMPTZ_TBL
+ WHERE d1 <= timestamp with time zone '1997-01-02';
+
+SELECT d1 FROM TIMESTAMPTZ_TBL
+ WHERE d1 >= timestamp with time zone '1997-01-02';
+
+SELECT d1 - timestamp with time zone '1997-01-02' AS diff
+ FROM TIMESTAMPTZ_TBL WHERE d1 BETWEEN '1902-01-01' AND '2038-01-01';
+
+SELECT date_trunc( 'week', timestamp with time zone '2004-02-29 15:44:17.71393' ) AS week_trunc;
+
+SELECT date_trunc('day', timestamp with time zone '2001-02-16 20:38:40+00', 'Australia/Sydney') as sydney_trunc; -- zone name
+SELECT date_trunc('day', timestamp with time zone '2001-02-16 20:38:40+00', 'GMT') as gmt_trunc; -- fixed-offset abbreviation
+SELECT date_trunc('day', timestamp with time zone '2001-02-16 20:38:40+00', 'VET') as vet_trunc; -- variable-offset abbreviation
+
+-- verify date_bin behaves the same as date_trunc for relevant intervals
+SELECT
+ str,
+ interval,
+ date_trunc(str, ts, 'Australia/Sydney') = date_bin(interval::interval, ts, timestamp with time zone '2001-01-01+11') AS equal
+FROM (
+ VALUES
+ ('day', '1 d'),
+ ('hour', '1 h'),
+ ('minute', '1 m'),
+ ('second', '1 s'),
+ ('millisecond', '1 ms'),
+ ('microsecond', '1 us')
+) intervals (str, interval),
+(VALUES (timestamptz '2020-02-29 15:44:17.71393+00')) ts (ts);
+
+-- bin timestamps into arbitrary intervals
+SELECT
+ interval,
+ ts,
+ origin,
+ date_bin(interval::interval, ts, origin)
+FROM (
+ VALUES
+ ('15 days'),
+ ('2 hours'),
+ ('1 hour 30 minutes'),
+ ('15 minutes'),
+ ('10 seconds'),
+ ('100 milliseconds'),
+ ('250 microseconds')
+) intervals (interval),
+(VALUES (timestamptz '2020-02-11 15:44:17.71393')) ts (ts),
+(VALUES (timestamptz '2001-01-01')) origin (origin);
+
+-- shift bins using the origin parameter:
+SELECT date_bin('5 min'::interval, timestamptz '2020-02-01 01:01:01+00', timestamptz '2020-02-01 00:02:30+00');
+
+-- disallow intervals with months or years
+SELECT date_bin('5 months'::interval, timestamp with time zone '2020-02-01 01:01:01+00', timestamp with time zone '2001-01-01+00');
+SELECT date_bin('5 years'::interval, timestamp with time zone '2020-02-01 01:01:01+00', timestamp with time zone '2001-01-01+00');
+
+-- disallow zero intervals
+SELECT date_bin('0 days'::interval, timestamp with time zone '1970-01-01 01:00:00+00' , timestamp with time zone '1970-01-01 00:00:00+00');
+
+-- disallow negative intervals
+SELECT date_bin('-2 days'::interval, timestamp with time zone '1970-01-01 01:00:00+00' , timestamp with time zone '1970-01-01 00:00:00+00');
+
+-- Test casting within a BETWEEN qualifier
+SELECT d1 - timestamp with time zone '1997-01-02' AS diff
+ FROM TIMESTAMPTZ_TBL
+ WHERE d1 BETWEEN timestamp with time zone '1902-01-01' AND timestamp with time zone '2038-01-01';
+
+-- DATE_PART (timestamptz_part)
+SELECT d1 as timestamptz,
+ date_part( 'year', d1) AS year, date_part( 'month', d1) AS month,
+ date_part( 'day', d1) AS day, date_part( 'hour', d1) AS hour,
+ date_part( 'minute', d1) AS minute, date_part( 'second', d1) AS second
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT d1 as timestamptz,
+ date_part( 'quarter', d1) AS quarter, date_part( 'msec', d1) AS msec,
+ date_part( 'usec', d1) AS usec
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT d1 as timestamptz,
+ date_part( 'isoyear', d1) AS isoyear, date_part( 'week', d1) AS week,
+ date_part( 'isodow', d1) AS isodow, date_part( 'dow', d1) AS dow,
+ date_part( 'doy', d1) AS doy
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT d1 as timestamptz,
+ date_part( 'decade', d1) AS decade,
+ date_part( 'century', d1) AS century,
+ date_part( 'millennium', d1) AS millennium,
+ round(date_part( 'julian', d1)) AS julian,
+ date_part( 'epoch', d1) AS epoch
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT d1 as timestamptz,
+ date_part( 'timezone', d1) AS timezone,
+ date_part( 'timezone_hour', d1) AS timezone_hour,
+ date_part( 'timezone_minute', d1) AS timezone_minute
+ FROM TIMESTAMPTZ_TBL;
+
+-- extract implementation is mostly the same as date_part, so only
+-- test a few cases for additional coverage.
+SELECT d1 as "timestamp",
+ extract(microseconds from d1) AS microseconds,
+ extract(milliseconds from d1) AS milliseconds,
+ extract(seconds from d1) AS seconds,
+ round(extract(julian from d1)) AS julian,
+ extract(epoch from d1) AS epoch
+ FROM TIMESTAMPTZ_TBL;
+
+-- value near upper bound uses special case in code
+SELECT date_part('epoch', '294270-01-01 00:00:00+00'::timestamptz);
+SELECT extract(epoch from '294270-01-01 00:00:00+00'::timestamptz);
+-- another internal overflow test case
+SELECT extract(epoch from '5000-01-01 00:00:00+00'::timestamptz);
+
+-- TO_CHAR()
+SELECT to_char(d1, 'DAY Day day DY Dy dy MONTH Month month RM MON Mon mon')
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT to_char(d1, 'FMDAY FMDay FMday FMMONTH FMMonth FMmonth FMRM')
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT to_char(d1, 'Y,YYY YYYY YYY YY Y CC Q MM WW DDD DD D J')
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT to_char(d1, 'FMY,YYY FMYYYY FMYYY FMYY FMY FMCC FMQ FMMM FMWW FMDDD FMDD FMD FMJ')
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT to_char(d1, 'HH HH12 HH24 MI SS SSSS')
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT to_char(d1, E'"HH:MI:SS is" HH:MI:SS "\\"text between quote marks\\""')
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT to_char(d1, 'HH24--text--MI--text--SS')
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT to_char(d1, 'YYYYTH YYYYth Jth')
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT to_char(d1, 'YYYY A.D. YYYY a.d. YYYY bc HH:MI:SS P.M. HH:MI:SS p.m. HH:MI:SS pm')
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT to_char(d1, 'IYYY IYY IY I IW IDDD ID')
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
+ FROM TIMESTAMPTZ_TBL;
+
+SELECT to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
+-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
+SET timezone = '00:00';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+SET timezone = '+02:00';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+SET timezone = '-13:00';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+SET timezone = '-00:30';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+SET timezone = '00:30';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+SET timezone = '-04:30';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+SET timezone = '04:30';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+SET timezone = '-04:15';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+SET timezone = '04:15';
+SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
+RESET timezone;
+
+CREATE TABLE TIMESTAMPTZ_TST (a int , b timestamptz);
+
+-- Test year field value with len > 4
+INSERT INTO TIMESTAMPTZ_TST VALUES(1, 'Sat Mar 12 23:58:48 1000 IST');
+INSERT INTO TIMESTAMPTZ_TST VALUES(2, 'Sat Mar 12 23:58:48 10000 IST');
+INSERT INTO TIMESTAMPTZ_TST VALUES(3, 'Sat Mar 12 23:58:48 100000 IST');
+INSERT INTO TIMESTAMPTZ_TST VALUES(3, '10000 Mar 12 23:58:48 IST');
+INSERT INTO TIMESTAMPTZ_TST VALUES(4, '100000312 23:58:48 IST');
+INSERT INTO TIMESTAMPTZ_TST VALUES(4, '1000000312 23:58:48 IST');
+--Verify data
+SELECT * FROM TIMESTAMPTZ_TST ORDER BY a;
+--Cleanup
+DROP TABLE TIMESTAMPTZ_TST;
+
+-- test timestamptz constructors
+set TimeZone to 'America/New_York';
+
+-- numeric timezone
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33);
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '+2');
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '-2');
+WITH tzs (tz) AS (VALUES
+ ('+1'), ('+1:'), ('+1:0'), ('+100'), ('+1:00'), ('+01:00'),
+ ('+10'), ('+1000'), ('+10:'), ('+10:0'), ('+10:00'), ('+10:00:'),
+ ('+10:00:1'), ('+10:00:01'),
+ ('+10:00:10'))
+ SELECT make_timestamptz(2010, 2, 27, 3, 45, 00, tz), tz FROM tzs;
+
+-- these should fail
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '2');
+SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, '+16');
+SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, '-16');
+
+-- should be true
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '+2') = '1973-07-15 08:15:55.33+02'::timestamptz;
+
+-- full timezone names
+SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Europe/Prague') = timestamptz '2014-12-10 00:00:00 Europe/Prague';
+SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Europe/Prague') AT TIME ZONE 'UTC';
+SELECT make_timestamptz(1846, 12, 10, 0, 0, 0, 'Asia/Manila') AT TIME ZONE 'UTC';
+SELECT make_timestamptz(1881, 12, 10, 0, 0, 0, 'Europe/Paris') AT TIME ZONE 'UTC';
+SELECT make_timestamptz(1910, 12, 24, 0, 0, 0, 'Nehwon/Lankhmar');
+
+-- abbreviations
+SELECT make_timestamptz(2008, 12, 10, 10, 10, 10, 'EST');
+SELECT make_timestamptz(2008, 12, 10, 10, 10, 10, 'EDT');
+SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, 'PST8PDT');
+
+RESET TimeZone;
+
+-- generate_series for timestamptz
+select * from generate_series('2020-01-01 00:00'::timestamptz,
+ '2020-01-02 03:00'::timestamptz,
+ '1 hour'::interval);
+-- the LIMIT should allow this to terminate in a reasonable amount of time
+-- (but that unfortunately doesn't work yet for SELECT * FROM ...)
+select generate_series('2022-01-01 00:00'::timestamptz,
+ 'infinity'::timestamptz,
+ '1 month'::interval) limit 10;
+-- errors
+select * from generate_series('2020-01-01 00:00'::timestamptz,
+ '2020-01-02 03:00'::timestamptz,
+ '0 hour'::interval);
+
+--
+-- Test behavior with a dynamic (time-varying) timezone abbreviation.
+-- These tests rely on the knowledge that MSK (Europe/Moscow standard time)
+-- moved forwards in Mar 2011 and backwards again in Oct 2014.
+--
+
+SET TimeZone to 'UTC';
+
+SELECT '2011-03-27 00:00:00 Europe/Moscow'::timestamptz;
+SELECT '2011-03-27 01:00:00 Europe/Moscow'::timestamptz;
+SELECT '2011-03-27 01:59:59 Europe/Moscow'::timestamptz;
+SELECT '2011-03-27 02:00:00 Europe/Moscow'::timestamptz;
+SELECT '2011-03-27 02:00:01 Europe/Moscow'::timestamptz;
+SELECT '2011-03-27 02:59:59 Europe/Moscow'::timestamptz;
+SELECT '2011-03-27 03:00:00 Europe/Moscow'::timestamptz;
+SELECT '2011-03-27 03:00:01 Europe/Moscow'::timestamptz;
+SELECT '2011-03-27 04:00:00 Europe/Moscow'::timestamptz;
+
+SELECT '2011-03-27 00:00:00 MSK'::timestamptz;
+SELECT '2011-03-27 01:00:00 MSK'::timestamptz;
+SELECT '2011-03-27 01:59:59 MSK'::timestamptz;
+SELECT '2011-03-27 02:00:00 MSK'::timestamptz;
+SELECT '2011-03-27 02:00:01 MSK'::timestamptz;
+SELECT '2011-03-27 02:59:59 MSK'::timestamptz;
+SELECT '2011-03-27 03:00:00 MSK'::timestamptz;
+SELECT '2011-03-27 03:00:01 MSK'::timestamptz;
+SELECT '2011-03-27 04:00:00 MSK'::timestamptz;
+
+SELECT '2014-10-26 00:00:00 Europe/Moscow'::timestamptz;
+SELECT '2014-10-26 00:59:59 Europe/Moscow'::timestamptz;
+SELECT '2014-10-26 01:00:00 Europe/Moscow'::timestamptz;
+SELECT '2014-10-26 01:00:01 Europe/Moscow'::timestamptz;
+SELECT '2014-10-26 02:00:00 Europe/Moscow'::timestamptz;
+
+SELECT '2014-10-26 00:00:00 MSK'::timestamptz;
+SELECT '2014-10-26 00:59:59 MSK'::timestamptz;
+SELECT '2014-10-26 01:00:00 MSK'::timestamptz;
+SELECT '2014-10-26 01:00:01 MSK'::timestamptz;
+SELECT '2014-10-26 02:00:00 MSK'::timestamptz;
+
+SELECT '2011-03-27 00:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-27 01:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-27 01:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-27 02:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-27 02:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-27 02:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-27 03:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-27 03:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-27 04:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+
+SELECT '2011-03-27 00:00:00'::timestamp AT TIME ZONE 'MSK';
+SELECT '2011-03-27 01:00:00'::timestamp AT TIME ZONE 'MSK';
+SELECT '2011-03-27 01:59:59'::timestamp AT TIME ZONE 'MSK';
+SELECT '2011-03-27 02:00:00'::timestamp AT TIME ZONE 'MSK';
+SELECT '2011-03-27 02:00:01'::timestamp AT TIME ZONE 'MSK';
+SELECT '2011-03-27 02:59:59'::timestamp AT TIME ZONE 'MSK';
+SELECT '2011-03-27 03:00:00'::timestamp AT TIME ZONE 'MSK';
+SELECT '2011-03-27 03:00:01'::timestamp AT TIME ZONE 'MSK';
+SELECT '2011-03-27 04:00:00'::timestamp AT TIME ZONE 'MSK';
+
+SELECT '2014-10-26 00:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+SELECT '2014-10-26 00:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+SELECT '2014-10-26 01:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+SELECT '2014-10-26 01:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+SELECT '2014-10-26 02:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+
+SELECT '2014-10-26 00:00:00'::timestamp AT TIME ZONE 'MSK';
+SELECT '2014-10-26 00:59:59'::timestamp AT TIME ZONE 'MSK';
+SELECT '2014-10-26 01:00:00'::timestamp AT TIME ZONE 'MSK';
+SELECT '2014-10-26 01:00:01'::timestamp AT TIME ZONE 'MSK';
+SELECT '2014-10-26 02:00:00'::timestamp AT TIME ZONE 'MSK';
+
+SELECT make_timestamptz(2014, 10, 26, 0, 0, 0, 'MSK');
+SELECT make_timestamptz(2014, 10, 26, 1, 0, 0, 'MSK');
+
+SELECT to_timestamp( 0); -- 1970-01-01 00:00:00+00
+SELECT to_timestamp( 946684800); -- 2000-01-01 00:00:00+00
+SELECT to_timestamp(1262349296.7890123); -- 2010-01-01 12:34:56.789012+00
+-- edge cases
+SELECT to_timestamp(-210866803200); -- 4714-11-24 00:00:00+00 BC
+-- upper limit varies between integer and float timestamps, so hard to test
+-- nonfinite values
+SELECT to_timestamp(' Infinity'::float);
+SELECT to_timestamp('-Infinity'::float);
+SELECT to_timestamp('NaN'::float);
+
+
+SET TimeZone to 'Europe/Moscow';
+
+SELECT '2011-03-26 21:00:00 UTC'::timestamptz;
+SELECT '2011-03-26 22:00:00 UTC'::timestamptz;
+SELECT '2011-03-26 22:59:59 UTC'::timestamptz;
+SELECT '2011-03-26 23:00:00 UTC'::timestamptz;
+SELECT '2011-03-26 23:00:01 UTC'::timestamptz;
+SELECT '2011-03-26 23:59:59 UTC'::timestamptz;
+SELECT '2011-03-27 00:00:00 UTC'::timestamptz;
+
+SELECT '2014-10-25 21:00:00 UTC'::timestamptz;
+SELECT '2014-10-25 21:59:59 UTC'::timestamptz;
+SELECT '2014-10-25 22:00:00 UTC'::timestamptz;
+SELECT '2014-10-25 22:00:01 UTC'::timestamptz;
+SELECT '2014-10-25 23:00:00 UTC'::timestamptz;
+
+RESET TimeZone;
+
+SELECT '2011-03-26 21:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-26 22:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-26 22:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-26 23:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-26 23:00:01 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-26 23:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+SELECT '2011-03-27 00:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+
+SELECT '2014-10-25 21:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+SELECT '2014-10-25 21:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+SELECT '2014-10-25 22:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+
+SELECT '2011-03-26 21:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+SELECT '2011-03-26 22:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+SELECT '2011-03-26 22:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+SELECT '2011-03-26 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+SELECT '2011-03-26 23:00:01 UTC'::timestamptz AT TIME ZONE 'MSK';
+SELECT '2011-03-26 23:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+SELECT '2011-03-27 00:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+
+SELECT '2014-10-25 21:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+SELECT '2014-10-25 21:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+SELECT '2014-10-25 22:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'MSK';
+SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+
+--
+-- Test that AT TIME ZONE isn't misoptimized when using an index (bug #14504)
+--
+create temp table tmptz (f1 timestamptz primary key);
+insert into tmptz values ('2017-01-18 00:00+00');
+explain (costs off)
+select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
diff --git a/src/test/regress/sql/timetz.sql b/src/test/regress/sql/timetz.sql
new file mode 100644
index 0000000..7b70f46
--- /dev/null
+++ b/src/test/regress/sql/timetz.sql
@@ -0,0 +1,79 @@
+--
+-- TIMETZ
+--
+
+CREATE TABLE TIMETZ_TBL (f1 time(2) with time zone);
+
+INSERT INTO TIMETZ_TBL VALUES ('00:01 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('01:00 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('02:03 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('07:07 PST');
+INSERT INTO TIMETZ_TBL VALUES ('08:08 EDT');
+INSERT INTO TIMETZ_TBL VALUES ('11:59 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('12:00 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('12:01 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('23:59 PDT');
+INSERT INTO TIMETZ_TBL VALUES ('11:59:59.99 PM PDT');
+
+INSERT INTO TIMETZ_TBL VALUES ('2003-03-07 15:36:39 America/New_York');
+INSERT INTO TIMETZ_TBL VALUES ('2003-07-07 15:36:39 America/New_York');
+-- this should fail (the timezone offset is not known)
+INSERT INTO TIMETZ_TBL VALUES ('15:36:39 America/New_York');
+-- this should fail (timezone not specified without a date)
+INSERT INTO TIMETZ_TBL VALUES ('15:36:39 m2');
+-- this should fail (dynamic timezone abbreviation without a date)
+INSERT INTO TIMETZ_TBL VALUES ('15:36:39 MSK m2');
+
+
+SELECT f1 AS "Time TZ" FROM TIMETZ_TBL;
+
+SELECT f1 AS "Three" FROM TIMETZ_TBL WHERE f1 < '05:06:07-07';
+
+SELECT f1 AS "Seven" FROM TIMETZ_TBL WHERE f1 > '05:06:07-07';
+
+SELECT f1 AS "None" FROM TIMETZ_TBL WHERE f1 < '00:00-07';
+
+SELECT f1 AS "Ten" FROM TIMETZ_TBL WHERE f1 >= '00:00-07';
+
+-- Check edge cases
+SELECT '23:59:59.999999 PDT'::timetz;
+SELECT '23:59:59.9999999 PDT'::timetz; -- rounds up
+SELECT '23:59:60 PDT'::timetz; -- rounds up
+SELECT '24:00:00 PDT'::timetz; -- allowed
+SELECT '24:00:00.01 PDT'::timetz; -- not allowed
+SELECT '23:59:60.01 PDT'::timetz; -- not allowed
+SELECT '24:01:00 PDT'::timetz; -- not allowed
+SELECT '25:00:00 PDT'::timetz; -- not allowed
+
+--
+-- TIME simple math
+--
+-- We now make a distinction between time and intervals,
+-- and adding two times together makes no sense at all.
+-- Leave in one query to show that it is rejected,
+-- and do the rest of the testing in horology.sql
+-- where we do mixed-type arithmetic. - thomas 2000-12-02
+
+SELECT f1 + time with time zone '00:01' AS "Illegal" FROM TIMETZ_TBL;
+
+--
+-- test EXTRACT
+--
+SELECT EXTRACT(MICROSECOND FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+SELECT EXTRACT(MILLISECOND FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+SELECT EXTRACT(SECOND FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+SELECT EXTRACT(MINUTE FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+SELECT EXTRACT(HOUR FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+SELECT EXTRACT(DAY FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04'); -- error
+SELECT EXTRACT(FORTNIGHT FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04'); -- error
+SELECT EXTRACT(TIMEZONE FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04:30');
+SELECT EXTRACT(TIMEZONE_HOUR FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04:30');
+SELECT EXTRACT(TIMEZONE_MINUTE FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04:30');
+SELECT EXTRACT(EPOCH FROM TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+
+-- date_part implementation is mostly the same as extract, so only
+-- test a few cases for additional coverage.
+SELECT date_part('microsecond', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+SELECT date_part('millisecond', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+SELECT date_part('second', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
+SELECT date_part('epoch', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04');
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
new file mode 100644
index 0000000..8886280
--- /dev/null
+++ b/src/test/regress/sql/transactions.sql
@@ -0,0 +1,604 @@
+--
+-- TRANSACTIONS
+--
+
+BEGIN;
+
+SELECT *
+ INTO TABLE xacttest
+ FROM aggtest;
+
+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 aggtest;
+
+-- should be empty
+SELECT * FROM aggtest;
+
+ABORT;
+
+-- should not exist
+SELECT oid FROM pg_class WHERE relname = 'disappear';
+
+-- should have members again
+SELECT * FROM aggtest;
+
+
+-- Read-only tests
+
+CREATE TABLE writetest (a int);
+CREATE TEMPORARY TABLE temptest (a int);
+
+BEGIN;
+SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE; -- ok
+SELECT * FROM writetest; -- ok
+SET TRANSACTION READ WRITE; --fail
+COMMIT;
+
+BEGIN;
+SET TRANSACTION READ ONLY; -- ok
+SET TRANSACTION READ WRITE; -- ok
+SET TRANSACTION READ ONLY; -- ok
+SELECT * FROM writetest; -- ok
+SAVEPOINT x;
+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 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;
+ DROP TABLE trans_foo;
+ CREATE TABLE trans_bar (a int);
+ ROLLBACK TO SAVEPOINT one;
+ 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;
+
+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;
+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;
+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;
+
+ CREATE TABLE koju (a INT UNIQUE);
+ 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;
+
+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;
+
+
+-- 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 only the last result in 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/triggers.sql b/src/test/regress/sql/triggers.sql
new file mode 100644
index 0000000..626608a
--- /dev/null
+++ b/src/test/regress/sql/triggers.sql
@@ -0,0 +1,2588 @@
+--
+-- TRIGGERS
+--
+
+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;
+
+-- Also check what happens when such a trigger runs before or after others
+create function f1_times_10() returns trigger as
+$$ begin new.f1 := new.f1 * 10; return new; end $$ language plpgsql;
+
+create trigger trigger_alpha
+ before insert or update on trigtest
+ for each row execute procedure f1_times_10();
+
+insert into trigtest values(1, 'foo');
+select * from trigtest;
+update trigtest set f2 = f2 || 'bar';
+select * from trigtest;
+delete from trigtest;
+select * from trigtest;
+
+create trigger trigger_zed
+ before insert or update on trigtest
+ for each row execute procedure f1_times_10();
+
+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 trigger trigger_alpha on trigtest;
+
+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;
+
+-- Check behavior with an implicit column default, too (bug #16644)
+create table trigtest (
+ a integer,
+ b bool default true not null,
+ c text default 'xyzzy' not null);
+
+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);
+select * from trigtest;
+
+alter table trigtest add column d integer default 42 not null;
+
+select * from trigtest;
+update trigtest set a = 2 where a = 1 returning *;
+select * from trigtest;
+
+alter table trigtest drop column b;
+
+select * from trigtest;
+update trigtest set a = 2 where a = 1 returning *;
+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;
+delete from tttest where price_id = 2;
+select * from tttest;
+-- what do we see ?
+
+-- get current prices
+select * from tttest where price_off = 999999;
+
+-- change price for price_id == 3
+update tttest set price_val = 30 where price_id = 3;
+select * from tttest;
+
+-- now we want to change pric_id in ALL tuples
+-- this gets us not what we need
+update tttest set price_id = 5 where price_id = 3;
+select * from tttest;
+
+-- 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;
+
+-- and try change price_id now!
+update tttest set price_id = 5 where price_id = 3;
+select * from tttest;
+-- 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;
+-- 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);
+UPDATE main_table SET a = 50, b = 60;
+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';
+
+-- Test RENAME TRIGGER
+ALTER TRIGGER modified_a ON main_table RENAME TO modified_modified_a;
+SELECT count(*) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a';
+SELECT count(*) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_modified_a';
+
+DROP TRIGGER modified_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 WHEN condition accessing system columns.
+create table table_with_oids(a int);
+insert into table_with_oids values (1);
+create trigger oid_unchanged_trig after update on table_with_oids
+ for each row
+ when (new.tableoid = old.tableoid AND new.tableoid <> 0)
+ execute procedure trigger_func('after_upd_oid_unchanged');
+update table_with_oids set a = a + 1;
+drop table table_with_oids;
+
+-- 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
+ALTER TABLE main_table DROP COLUMN b;
+-- 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_TABLE_NAME, 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;
+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
+--
+
+CREATE TABLE trigger_test (f1 int, f2 text, f3 text);
+
+-- 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, 'foo', 'bar');
+INSERT INTO trigger_test VALUES(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;
+SET 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;
+
+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_TABLE_NAME, 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;
+
+-- 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 (20,21);
+DELETE FROM main_view WHERE a = 31 RETURNING 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;
+
+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, depth_b, 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, 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;
+
+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;
+drop trigger trg1 on trigpart1; -- fail
+drop trigger trg1 on trigpart2; -- fail
+drop trigger trg1 on trigpart3; -- 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;
+
+-- check display of unrelated triggers
+create trigger samename after delete on trigpart execute function trigger_nothing();
+create trigger samename after delete on trigpart1 execute function trigger_nothing();
+\d trigpart1
+
+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;
+
+-- Verify propagation of trigger arguments to partitions
+create table parted_trig (a int) partition by list (a);
+create table parted_trig1 partition of parted_trig for values in (1);
+create or replace function trigger_notice() returns trigger as $$
+ declare
+ arg1 text = TG_ARGV[0];
+ arg2 integer = TG_ARGV[1];
+ begin
+ raise notice 'trigger % on % % % for % args % %',
+ TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL, arg1, arg2;
+ return null;
+ end;
+ $$ language plpgsql;
+create trigger aaa after insert on parted_trig
+ for each row execute procedure trigger_notice('quirky', 1);
+
+-- Verify propagation of trigger arguments to partitions attached after creating trigger
+create table parted_trig2 partition of parted_trig for values in (2);
+create table parted_trig3 (like parted_trig);
+alter table parted_trig attach partition parted_trig3 for values in (3);
+insert into parted_trig values (1), (2), (3);
+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
+select tableoid::regclass, * from parted;
+
+-- 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;
+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).
+--
+
+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();
+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 copyfrom.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 child, parent;
+
+--
+-- 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();
+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();
+
+--
+-- 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;
+
+-- verify transition table conversion slot's lifetime
+-- https://postgr.es/m/39a71864-b120-5a5c-8cc5-c632b6f16761@amazon.com
+create table convslot_test_parent (col1 text primary key);
+create table convslot_test_child (col1 text primary key,
+ foreign key (col1) references convslot_test_parent(col1) on delete cascade on update cascade
+);
+
+alter table convslot_test_child add column col2 text not null default 'tutu';
+insert into convslot_test_parent(col1) values ('1');
+insert into convslot_test_child(col1) values ('1');
+insert into convslot_test_parent(col1) values ('3');
+insert into convslot_test_child(col1) values ('3');
+
+create or replace function trigger_function1()
+returns trigger
+language plpgsql
+AS $$
+begin
+raise notice 'trigger = %, old_table = %',
+ TG_NAME,
+ (select string_agg(old_table::text, ', ' order by col1) from old_table);
+return null;
+end; $$;
+
+create or replace function trigger_function2()
+returns trigger
+language plpgsql
+AS $$
+begin
+raise notice 'trigger = %, new table = %',
+ TG_NAME,
+ (select string_agg(new_table::text, ', ' order by col1) from new_table);
+return null;
+end; $$;
+
+create trigger but_trigger after update on convslot_test_child
+referencing new table as new_table
+for each statement execute function trigger_function2();
+
+update convslot_test_parent set col1 = col1 || '1';
+
+create or replace function trigger_function3()
+returns trigger
+language plpgsql
+AS $$
+begin
+raise notice 'trigger = %, old_table = %, new table = %',
+ TG_NAME,
+ (select string_agg(old_table::text, ', ' order by col1) from old_table),
+ (select string_agg(new_table::text, ', ' order by col1) from new_table);
+return null;
+end; $$;
+
+create trigger but_trigger2 after update on convslot_test_child
+referencing old table as old_table new table as new_table
+for each statement execute function trigger_function3();
+update convslot_test_parent set col1 = col1 || '1';
+
+create trigger bdt_trigger after delete on convslot_test_child
+referencing old table as old_table
+for each statement execute function trigger_function1();
+delete from convslot_test_parent;
+
+drop table convslot_test_child, convslot_test_parent;
diff --git a/src/test/regress/sql/truncate.sql b/src/test/regress/sql/truncate.sql
new file mode 100644
index 0000000..54f26e3
--- /dev/null
+++ b/src/test/regress/sql/truncate.sql
@@ -0,0 +1,329 @@
+-- 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;
+-- 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');
+
+BEGIN;
+SELECT * FROM trunc_f;
+TRUNCATE trunc_f;
+SELECT * FROM trunc_f;
+ROLLBACK;
+
+BEGIN;
+SELECT * FROM trunc_f;
+TRUNCATE ONLY trunc_f;
+SELECT * FROM trunc_f;
+ROLLBACK;
+
+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;
+
+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;
+
+DROP TABLE trunc_f CASCADE;
+
+-- 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'));
+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));
+
+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;
+
+-- check rollback of a RESTART IDENTITY operation
+BEGIN;
+TRUNCATE truncate_a RESTART IDENTITY;
+INSERT INTO truncate_a DEFAULT VALUES;
+SELECT * FROM truncate_a;
+ROLLBACK;
+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/tsdicts.sql b/src/test/regress/sql/tsdicts.sql
new file mode 100644
index 0000000..ddc6c7f
--- /dev/null
+++ b/src/test/regress/sql/tsdicts.sql
@@ -0,0 +1,253 @@
+--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');
+
+-- Test suitability of affix and dict files
+CREATE TEXT SEARCH DICTIONARY hunspell_err (
+ Template=ispell,
+ DictFile=ispell_sample,
+ AffFile=hunspell_sample_long
+);
+
+CREATE TEXT SEARCH DICTIONARY hunspell_err (
+ Template=ispell,
+ DictFile=ispell_sample,
+ AffFile=hunspell_sample_num
+);
+
+CREATE TEXT SEARCH DICTIONARY hunspell_invalid_1 (
+ Template=ispell,
+ DictFile=hunspell_sample_long,
+ AffFile=ispell_sample
+);
+
+CREATE TEXT SEARCH DICTIONARY hunspell_invalid_2 (
+ Template=ispell,
+ DictFile=hunspell_sample_long,
+ AffFile=hunspell_sample_num
+);
+
+CREATE TEXT SEARCH DICTIONARY hunspell_invalid_3 (
+ Template=ispell,
+ DictFile=hunspell_sample_num,
+ AffFile=ispell_sample
+);
+
+CREATE TEXT SEARCH DICTIONARY hunspell_err (
+ Template=ispell,
+ DictFile=hunspell_sample_num,
+ AffFile=hunspell_sample_long
+);
+
+-- 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');
+
+-- test altering boolean parameters
+SELECT dictinitoption FROM pg_ts_dict WHERE dictname = 'synonym';
+
+ALTER TEXT SEARCH DICTIONARY synonym (CaseSensitive = 1);
+SELECT ts_lexize('synonym', 'PoStGrEs');
+SELECT dictinitoption FROM pg_ts_dict WHERE dictname = 'synonym';
+
+ALTER TEXT SEARCH DICTIONARY synonym (CaseSensitive = 2); -- fail
+
+ALTER TEXT SEARCH DICTIONARY synonym (CaseSensitive = off);
+SELECT ts_lexize('synonym', 'PoStGrEs');
+SELECT dictinitoption FROM pg_ts_dict WHERE dictname = 'synonym';
+
+-- 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/tsearch.sql b/src/test/regress/sql/tsearch.sql
new file mode 100644
index 0000000..d929210
--- /dev/null
+++ b/src/test/regress/sql/tsearch.sql
@@ -0,0 +1,795 @@
+--
+-- 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;
+
+-- 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);
+
+SET enable_seqscan=OFF;
+SET enable_indexscan=ON;
+SET enable_bitmapscan=OFF;
+
+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';
+
+SET enable_indexscan=OFF;
+SET enable_bitmapscan=ON;
+
+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 siglen parameter of GiST tsvector_ops
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(foo=1));
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=0));
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=2048));
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100,foo='bar'));
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100, siglen = 200));
+
+CREATE INDEX wowidx2 ON test_tsvector USING gist (a tsvector_ops(siglen=1));
+
+\d test_tsvector
+
+DROP INDEX wowidx;
+
+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';
+
+DROP INDEX wowidx2;
+
+CREATE INDEX wowidx ON test_tsvector USING gist (a tsvector_ops(siglen=484));
+
+\d test_tsvector
+
+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';
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+
+DROP INDEX wowidx;
+
+CREATE INDEX wowidx ON test_tsvector USING gin (a);
+
+SET enable_seqscan=OFF;
+-- GIN only supports bitmapscan, so no need to test plain indexscan
+
+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 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 <fr>qwer jf sdjk<we hjwer <werrwe> ewr1> ewri2 <a href="qwe<qwe>">
+/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
+<i <b> 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 <fr>qwer jf sdjk<we hjwer <werrwe> ewr1> ewri2 <a href="qwe<qwe>">
+/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
+<i <b> 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 <fr>qwer jf sdjk<we hjwer <werrwe> ewr1> ewri2 <a href="qwe<qwe>">
+/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
+<i <b> wow < jqw <> qwerty'));
+
+-- ts_debug
+
+SELECT * from ts_debug('english', '<myns:foo-bar_baz.blurfl>abc&nm1;def&#xa9;ghi&#245;jkl</myns:foo-bar_baz.blurfl>');
+
+-- check parsing of URLs
+SELECT * from ts_debug('english', 'http://www.harewoodsolutions.co.uk/press.aspx</span>');
+SELECT * from ts_debug('english', 'http://aew.wer0c.ewr/id?ad=qwe&dw<span>');
+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', '
+<html>
+<!-- some comment -->
+<body>
+Sea view wow <u>foo bar</u> <i>qq</i>
+<a href="http://www.google.com/foo.bar.html" target="_blank">YES &nbsp;</a>
+ff-bg
+<script>
+ document.write(15);
+</script>
+</body>
+</html>',
+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);
+
+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;
+
+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);
+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;
+
+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');
+
+RESET enable_seqscan;
+
+--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 or<rat');
+select websearch_to_tsquery('simple', 'fat or>rat');
+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/tsrf.sql b/src/test/regress/sql/tsrf.sql
new file mode 100644
index 0000000..7c22529
--- /dev/null
+++ b/src/test/regress/sql/tsrf.sql
@@ -0,0 +1,185 @@
+--
+-- tsrf - targetlist set returning function tests
+--
+
+-- simple srf
+SELECT generate_series(1, 3);
+
+-- parallel iteration
+SELECT generate_series(1, 3), generate_series(3,5);
+
+-- parallel iteration, different number of rows
+SELECT generate_series(1, 2), generate_series(1,4);
+
+-- srf, with SRF argument
+SELECT generate_series(1, generate_series(1, 3));
+
+-- but we've traditionally rejected the same in FROM
+SELECT * FROM generate_series(1, generate_series(1, 3));
+
+-- srf, with two SRF arguments
+SELECT generate_series(generate_series(1,3), generate_series(2, 4));
+
+-- check proper nesting of SRFs in different expressions
+explain (verbose, costs off)
+SELECT generate_series(1, generate_series(1, 3)), generate_series(2, 4);
+SELECT generate_series(1, generate_series(1, 3)), generate_series(2, 4);
+
+CREATE TABLE few(id int, dataa text, datab text);
+INSERT INTO few VALUES(1, 'a', 'foo'),(2, 'a', 'bar'),(3, 'b', 'bar');
+
+-- SRF with a provably-dummy relation
+explain (verbose, costs off)
+SELECT unnest(ARRAY[1, 2]) FROM few WHERE false;
+SELECT unnest(ARRAY[1, 2]) FROM few WHERE false;
+
+-- SRF shouldn't prevent upper query from recognizing lower as dummy
+explain (verbose, costs off)
+SELECT * FROM few f1,
+ (SELECT unnest(ARRAY[1,2]) FROM few f2 WHERE false OFFSET 0) ss;
+SELECT * FROM few f1,
+ (SELECT unnest(ARRAY[1,2]) FROM few f2 WHERE false OFFSET 0) ss;
+
+-- SRF output order of sorting is maintained, if SRF is not referenced
+SELECT few.id, generate_series(1,3) g FROM few ORDER BY id DESC;
+
+-- but SRFs can be referenced in sort
+SELECT few.id, generate_series(1,3) g FROM few ORDER BY id, g DESC;
+SELECT few.id, generate_series(1,3) g FROM few ORDER BY id, generate_series(1,3) DESC;
+
+-- it's weird to have ORDER BYs that increase the number of results
+SELECT few.id FROM few ORDER BY id, generate_series(1,3) DESC;
+
+-- SRFs are computed after aggregation
+SET enable_hashagg TO 0; -- stable output order
+SELECT few.dataa, count(*), min(id), max(id), unnest('{1,1,3}'::int[]) FROM few WHERE few.id = 1 GROUP BY few.dataa;
+-- unless referenced in GROUP BY clause
+SELECT few.dataa, count(*), min(id), max(id), unnest('{1,1,3}'::int[]) FROM few WHERE few.id = 1 GROUP BY few.dataa, unnest('{1,1,3}'::int[]);
+SELECT few.dataa, count(*), min(id), max(id), unnest('{1,1,3}'::int[]) FROM few WHERE few.id = 1 GROUP BY few.dataa, 5;
+RESET enable_hashagg;
+
+-- check HAVING works when GROUP BY does [not] reference SRF output
+SELECT dataa, generate_series(1,1), count(*) FROM few GROUP BY 1 HAVING count(*) > 1;
+SELECT dataa, generate_series(1,1), count(*) FROM few GROUP BY 1, 2 HAVING count(*) > 1;
+
+-- it's weird to have GROUP BYs that increase the number of results
+SELECT few.dataa, count(*) FROM few WHERE dataa = 'a' GROUP BY few.dataa ORDER BY 2;
+SELECT few.dataa, count(*) FROM few WHERE dataa = 'a' GROUP BY few.dataa, unnest('{1,1,3}'::int[]) ORDER BY 2;
+
+-- SRFs are not allowed if they'd need to be conditionally executed
+SELECT q1, case when q1 > 0 then generate_series(1,3) else 0 end FROM int8_tbl;
+SELECT q1, coalesce(generate_series(1,3), 0) FROM int8_tbl;
+
+-- SRFs are not allowed in aggregate arguments
+SELECT min(generate_series(1, 3)) FROM few;
+
+-- ... unless they're within a sub-select
+SELECT sum((3 = ANY(SELECT generate_series(1,4)))::int);
+
+SELECT sum((3 = ANY(SELECT lag(x) over(order by x)
+ FROM generate_series(1,4) x))::int);
+
+-- SRFs are not allowed in window function arguments, either
+SELECT min(generate_series(1, 3)) OVER() FROM few;
+
+-- SRFs are normally computed after window functions
+SELECT id,lag(id) OVER(), count(*) OVER(), generate_series(1,3) FROM few;
+-- unless referencing SRFs
+SELECT SUM(count(*)) OVER(PARTITION BY generate_series(1,3) ORDER BY generate_series(1,3)), generate_series(1,3) g FROM few GROUP BY g;
+
+-- sorting + grouping
+SELECT few.dataa, count(*), min(id), max(id), generate_series(1,3) FROM few GROUP BY few.dataa ORDER BY 5, 1;
+
+-- grouping sets are a bit special, they produce NULLs in columns not actually NULL
+set enable_hashagg = false;
+SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab);
+SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab) ORDER BY dataa;
+SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab) ORDER BY g;
+SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g);
+SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g) ORDER BY dataa;
+SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g) ORDER BY g;
+reset enable_hashagg;
+
+-- case with degenerate ORDER BY
+explain (verbose, costs off)
+select 'foo' as f, generate_series(1,2) as g from few order by 1;
+select 'foo' as f, generate_series(1,2) as g from few order by 1;
+
+-- data modification
+CREATE TABLE fewmore AS SELECT generate_series(1,3) AS data;
+INSERT INTO fewmore VALUES(generate_series(4,5));
+SELECT * FROM fewmore;
+
+-- SRFs are not allowed in UPDATE (they once were, but it was nonsense)
+UPDATE fewmore SET data = generate_series(4,9);
+
+-- SRFs are not allowed in RETURNING
+INSERT INTO fewmore VALUES(1) RETURNING generate_series(1,3);
+
+-- nor standalone VALUES (but surely this is a bug?)
+VALUES(1, generate_series(1,2));
+
+-- We allow tSRFs that are not at top level
+SELECT int4mul(generate_series(1,2), 10);
+SELECT generate_series(1,3) IS DISTINCT FROM 2;
+
+-- but SRFs in function RTEs must be at top level (annoying restriction)
+SELECT * FROM int4mul(generate_series(1,2), 10);
+
+-- DISTINCT ON is evaluated before tSRF evaluation if SRF is not
+-- referenced either in ORDER BY or in the DISTINCT ON list. The ORDER
+-- BY reference can be implicitly generated, if there's no other ORDER BY.
+
+-- implicit reference (via implicit ORDER) to all columns
+SELECT DISTINCT ON (a) a, b, generate_series(1,3) g
+FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b);
+
+-- unreferenced in DISTINCT ON or ORDER BY
+SELECT DISTINCT ON (a) a, b, generate_series(1,3) g
+FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b)
+ORDER BY a, b DESC;
+
+-- referenced in ORDER BY
+SELECT DISTINCT ON (a) a, b, generate_series(1,3) g
+FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b)
+ORDER BY a, b DESC, g DESC;
+
+-- referenced in ORDER BY and DISTINCT ON
+SELECT DISTINCT ON (a, b, g) a, b, generate_series(1,3) g
+FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b)
+ORDER BY a, b DESC, g DESC;
+
+-- only SRF mentioned in DISTINCT ON
+SELECT DISTINCT ON (g) a, b, generate_series(1,3) g
+FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b);
+
+-- LIMIT / OFFSET is evaluated after SRF evaluation
+SELECT a, generate_series(1,2) FROM (VALUES(1),(2),(3)) r(a) LIMIT 2 OFFSET 2;
+-- SRFs are not allowed in LIMIT.
+SELECT 1 LIMIT generate_series(1,3);
+
+-- tSRF in correlated subquery, referencing table outside
+SELECT (SELECT generate_series(1,3) LIMIT 1 OFFSET few.id) FROM few;
+-- tSRF in correlated subquery, referencing SRF outside
+SELECT (SELECT generate_series(1,3) LIMIT 1 OFFSET g.i) FROM generate_series(0,3) g(i);
+
+-- Operators can return sets too
+CREATE OPERATOR |@| (PROCEDURE = unnest, RIGHTARG = ANYARRAY);
+SELECT |@|ARRAY[1,2,3];
+
+-- Some fun cases involving duplicate SRF calls
+explain (verbose, costs off)
+select generate_series(1,3) as x, generate_series(1,3) + 1 as xp1;
+select generate_series(1,3) as x, generate_series(1,3) + 1 as xp1;
+explain (verbose, costs off)
+select generate_series(1,3)+1 order by generate_series(1,3);
+select generate_series(1,3)+1 order by generate_series(1,3);
+
+-- Check that SRFs of same nesting level run in lockstep
+explain (verbose, costs off)
+select generate_series(1,3) as x, generate_series(3,6) + 1 as y;
+select generate_series(1,3) as x, generate_series(3,6) + 1 as y;
+
+-- Clean up
+DROP TABLE few;
+DROP TABLE fewmore;
diff --git a/src/test/regress/sql/tstypes.sql b/src/test/regress/sql/tstypes.sql
new file mode 100644
index 0000000..30c8c70
--- /dev/null
+++ b/src/test/regress/sql/tstypes.sql
@@ -0,0 +1,268 @@
+-- deal with numeric instability of ts_rank
+SET extra_float_digits = 0;
+
+--Base tsvector test
+
+SELECT '1'::tsvector;
+SELECT '1 '::tsvector;
+SELECT ' 1'::tsvector;
+SELECT ' 1 '::tsvector;
+SELECT '1 2'::tsvector;
+SELECT '''1 2'''::tsvector;
+SELECT E'''1 \\''2'''::tsvector;
+SELECT E'''1 \\''2''3'::tsvector;
+SELECT E'''1 \\''2'' 3'::tsvector;
+SELECT E'''1 \\''2'' '' 3'' 4 '::tsvector;
+SELECT $$'\\as' ab\c ab\\c AB\\\c ab\\\\c$$::tsvector;
+SELECT tsvectorin(tsvectorout($$'\\as' ab\c ab\\c AB\\\c ab\\\\c$$::tsvector));
+SELECT '''w'':4A,3B,2C,1D,5 a:8';
+SELECT 'a:3A b:2a'::tsvector || 'ba:1234 a:1B';
+
+--Base tsquery test
+SELECT '1'::tsquery;
+SELECT '1 '::tsquery;
+SELECT ' 1'::tsquery;
+SELECT ' 1 '::tsquery;
+SELECT '''1 2'''::tsquery;
+SELECT E'''1 \\''2'''::tsquery;
+SELECT '!1'::tsquery;
+SELECT '1|2'::tsquery;
+SELECT '1|!2'::tsquery;
+SELECT '!1|2'::tsquery;
+SELECT '!1|!2'::tsquery;
+SELECT '!(!1|!2)'::tsquery;
+SELECT '!(!1|2)'::tsquery;
+SELECT '!(1|!2)'::tsquery;
+SELECT '!(1|2)'::tsquery;
+SELECT '1&2'::tsquery;
+SELECT '!1&2'::tsquery;
+SELECT '1&!2'::tsquery;
+SELECT '!1&!2'::tsquery;
+SELECT '(1&2)'::tsquery;
+SELECT '1&(2)'::tsquery;
+SELECT '!(1)&2'::tsquery;
+SELECT '!(1&2)'::tsquery;
+SELECT '1|2&3'::tsquery;
+SELECT '1|(2&3)'::tsquery;
+SELECT '(1|2)&3'::tsquery;
+SELECT '1|2&!3'::tsquery;
+SELECT '1|!2&3'::tsquery;
+SELECT '!1|2&3'::tsquery;
+SELECT '!1|(2&3)'::tsquery;
+SELECT '!(1|2)&3'::tsquery;
+SELECT '(!1|2)&3'::tsquery;
+SELECT '1|(2|(4|(5|6)))'::tsquery;
+SELECT '1|2|4|5|6'::tsquery;
+SELECT '1&(2&(4&(5&6)))'::tsquery;
+SELECT '1&2&4&5&6'::tsquery;
+SELECT '1&(2&(4&(5|6)))'::tsquery;
+SELECT '1&(2&(4&(5|!6)))'::tsquery;
+SELECT E'1&(''2''&('' 4''&(\\|5 | ''6 \\'' !|&'')))'::tsquery;
+SELECT $$'\\as'$$::tsquery;
+SELECT 'a:* & nbb:*ac | doo:a* | goo'::tsquery;
+SELECT '!!b'::tsquery;
+SELECT '!!!b'::tsquery;
+SELECT '!(!b)'::tsquery;
+SELECT 'a & !!b'::tsquery;
+SELECT '!!a & b'::tsquery;
+SELECT '!!a & !!b'::tsquery;
+
+--comparisons
+SELECT 'a' < 'b & c'::tsquery as "true";
+SELECT 'a' > 'b & c'::tsquery as "false";
+SELECT 'a | f' < 'b & c'::tsquery as "false";
+SELECT 'a | ff' < 'b & c'::tsquery as "false";
+SELECT 'a | f | g' < 'b & c'::tsquery as "false";
+
+--concatenation
+SELECT numnode( 'new'::tsquery );
+SELECT numnode( 'new & york'::tsquery );
+SELECT numnode( 'new & york | qwery'::tsquery );
+
+SELECT 'foo & bar'::tsquery && 'asd';
+SELECT 'foo & bar'::tsquery || 'asd & fg';
+SELECT 'foo & bar'::tsquery || !!'asd & fg'::tsquery;
+SELECT 'foo & bar'::tsquery && 'asd | fg';
+SELECT 'a' <-> 'b & d'::tsquery;
+SELECT 'a & g' <-> 'b & d'::tsquery;
+SELECT 'a & g' <-> 'b | d'::tsquery;
+SELECT 'a & g' <-> 'b <-> d'::tsquery;
+SELECT tsquery_phrase('a <3> g', 'b & d', 10);
+
+-- tsvector-tsquery operations
+
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & ca' as "true";
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & ca:B' as "true";
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & ca:A' as "true";
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & ca:C' as "false";
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & ca:CB' as "true";
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & c:*C' as "false";
+SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & c:*CB' as "true";
+SELECT 'a b:89 ca:23A,64b cb:80c d:34c'::tsvector @@ 'd:AC & c:*C' as "true";
+SELECT 'a b:89 ca:23A,64c cb:80b d:34c'::tsvector @@ 'd:AC & c:*C' as "true";
+SELECT 'a b:89 ca:23A,64c cb:80b d:34c'::tsvector @@ 'd:AC & c:*B' as "true";
+SELECT 'wa:1D wb:2A'::tsvector @@ 'w:*D & w:*A'::tsquery as "true";
+SELECT 'wa:1D wb:2A'::tsvector @@ 'w:*D <-> w:*A'::tsquery as "true";
+SELECT 'wa:1A wb:2D'::tsvector @@ 'w:*D <-> w:*A'::tsquery as "false";
+SELECT 'wa:1A'::tsvector @@ 'w:*A'::tsquery as "true";
+SELECT 'wa:1A'::tsvector @@ 'w:*D'::tsquery as "false";
+SELECT 'wa:1A'::tsvector @@ '!w:*A'::tsquery as "false";
+SELECT 'wa:1A'::tsvector @@ '!w:*D'::tsquery as "true";
+-- historically, a stripped tsvector matches queries ignoring weights:
+SELECT strip('wa:1A'::tsvector) @@ 'w:*A'::tsquery as "true";
+SELECT strip('wa:1A'::tsvector) @@ 'w:*D'::tsquery as "true";
+SELECT strip('wa:1A'::tsvector) @@ '!w:*A'::tsquery as "false";
+SELECT strip('wa:1A'::tsvector) @@ '!w:*D'::tsquery as "false";
+
+SELECT 'supernova'::tsvector @@ 'super'::tsquery AS "false";
+SELECT 'supeanova supernova'::tsvector @@ 'super'::tsquery AS "false";
+SELECT 'supeznova supernova'::tsvector @@ 'super'::tsquery AS "false";
+SELECT 'supernova'::tsvector @@ 'super:*'::tsquery AS "true";
+SELECT 'supeanova supernova'::tsvector @@ 'super:*'::tsquery AS "true";
+SELECT 'supeznova supernova'::tsvector @@ 'super:*'::tsquery AS "true";
+
+--phrase search
+SELECT to_tsvector('simple', '1 2 3 1') @@ '1 <-> 2' AS "true";
+SELECT to_tsvector('simple', '1 2 3 1') @@ '1 <2> 2' AS "false";
+SELECT to_tsvector('simple', '1 2 3 1') @@ '1 <-> 3' AS "false";
+SELECT to_tsvector('simple', '1 2 3 1') @@ '1 <2> 3' AS "true";
+SELECT to_tsvector('simple', '1 2 1 2') @@ '1 <3> 2' AS "true";
+
+SELECT to_tsvector('simple', '1 2 11 3') @@ '1 <-> 3' AS "false";
+SELECT to_tsvector('simple', '1 2 11 3') @@ '1:* <-> 3' AS "true";
+
+SELECT to_tsvector('simple', '1 2 3 4') @@ '1 <-> 2 <-> 3' AS "true";
+SELECT to_tsvector('simple', '1 2 3 4') @@ '(1 <-> 2) <-> 3' AS "true";
+SELECT to_tsvector('simple', '1 2 3 4') @@ '1 <-> (2 <-> 3)' AS "true";
+SELECT to_tsvector('simple', '1 2 3 4') @@ '1 <2> (2 <-> 3)' AS "false";
+SELECT to_tsvector('simple', '1 2 1 2 3 4') @@ '(1 <-> 2) <-> 3' AS "true";
+SELECT to_tsvector('simple', '1 2 1 2 3 4') @@ '1 <-> 2 <-> 3' AS "true";
+-- without position data, phrase search does not match
+SELECT strip(to_tsvector('simple', '1 2 3 4')) @@ '1 <-> 2 <-> 3' AS "false";
+
+select to_tsvector('simple', 'q x q y') @@ 'q <-> (x & y)' AS "false";
+select to_tsvector('simple', 'q x') @@ 'q <-> (x | y <-> z)' AS "true";
+select to_tsvector('simple', 'q y') @@ 'q <-> (x | y <-> z)' AS "false";
+select to_tsvector('simple', 'q y z') @@ 'q <-> (x | y <-> z)' AS "true";
+select to_tsvector('simple', 'q y x') @@ 'q <-> (x | y <-> z)' AS "false";
+select to_tsvector('simple', 'q x y') @@ 'q <-> (x | y <-> z)' AS "true";
+select to_tsvector('simple', 'q x') @@ '(x | y <-> z) <-> q' AS "false";
+select to_tsvector('simple', 'x q') @@ '(x | y <-> z) <-> q' AS "true";
+select to_tsvector('simple', 'x y q') @@ '(x | y <-> z) <-> q' AS "false";
+select to_tsvector('simple', 'x y z') @@ '(x | y <-> z) <-> q' AS "false";
+select to_tsvector('simple', 'x y z q') @@ '(x | y <-> z) <-> q' AS "true";
+select to_tsvector('simple', 'y z q') @@ '(x | y <-> z) <-> q' AS "true";
+select to_tsvector('simple', 'y y q') @@ '(x | y <-> z) <-> q' AS "false";
+select to_tsvector('simple', 'y y q') @@ '(!x | y <-> z) <-> q' AS "true";
+select to_tsvector('simple', 'x y q') @@ '(!x | y <-> z) <-> q' AS "true";
+select to_tsvector('simple', 'y y q') @@ '(x | y <-> !z) <-> q' AS "true";
+select to_tsvector('simple', 'x q') @@ '(x | y <-> !z) <-> q' AS "true";
+select to_tsvector('simple', 'x q') @@ '(!x | y <-> z) <-> q' AS "false";
+select to_tsvector('simple', 'z q') @@ '(!x | y <-> z) <-> q' AS "true";
+select to_tsvector('simple', 'x y q') @@ '(!x | y) <-> y <-> q' AS "false";
+select to_tsvector('simple', 'x y q') @@ '(!x | !y) <-> y <-> q' AS "true";
+select to_tsvector('simple', 'x y q') @@ '(x | !y) <-> y <-> q' AS "true";
+select to_tsvector('simple', 'x y q') @@ '(x | !!z) <-> y <-> q' AS "true";
+select to_tsvector('simple', 'x y q y') @@ '!x <-> y' AS "true";
+select to_tsvector('simple', 'x y q y') @@ '!x <-> !y' AS "true";
+select to_tsvector('simple', 'x y q y') @@ '!x <-> !!y' AS "true";
+select to_tsvector('simple', 'x y q y') @@ '!(x <-> y)' AS "false";
+select to_tsvector('simple', 'x y q y') @@ '!(x <2> y)' AS "true";
+select strip(to_tsvector('simple', 'x y q y')) @@ '!x <-> y' AS "false";
+select strip(to_tsvector('simple', 'x y q y')) @@ '!x <-> !y' AS "false";
+select strip(to_tsvector('simple', 'x y q y')) @@ '!x <-> !!y' AS "false";
+select strip(to_tsvector('simple', 'x y q y')) @@ '!(x <-> y)' AS "true";
+select strip(to_tsvector('simple', 'x y q y')) @@ '!(x <2> y)' AS "true";
+select to_tsvector('simple', 'x y q y') @@ '!foo' AS "true";
+select to_tsvector('simple', '') @@ '!foo' AS "true";
+
+--ranking
+SELECT ts_rank(' a:1 s:2C d g'::tsvector, 'a | s');
+SELECT ts_rank(' a:1 sa:2C d g'::tsvector, 'a | s');
+SELECT ts_rank(' a:1 sa:2C d g'::tsvector, 'a | s:*');
+SELECT ts_rank(' a:1 sa:2C d g'::tsvector, 'a | sa:*');
+SELECT ts_rank(' a:1 s:2B d g'::tsvector, 'a | s');
+SELECT ts_rank(' a:1 s:2 d g'::tsvector, 'a | s');
+SELECT ts_rank(' a:1 s:2C d g'::tsvector, 'a & s');
+SELECT ts_rank(' a:1 s:2B d g'::tsvector, 'a & s');
+SELECT ts_rank(' a:1 s:2 d g'::tsvector, 'a & s');
+
+SELECT ts_rank_cd(' a:1 s:2C d g'::tsvector, 'a | s');
+SELECT ts_rank_cd(' a:1 sa:2C d g'::tsvector, 'a | s');
+SELECT ts_rank_cd(' a:1 sa:2C d g'::tsvector, 'a | s:*');
+SELECT ts_rank_cd(' a:1 sa:2C d g'::tsvector, 'a | sa:*');
+SELECT ts_rank_cd(' a:1 sa:3C sab:2c d g'::tsvector, 'a | sa:*');
+SELECT ts_rank_cd(' a:1 s:2B d g'::tsvector, 'a | s');
+SELECT ts_rank_cd(' a:1 s:2 d g'::tsvector, 'a | s');
+SELECT ts_rank_cd(' a:1 s:2C d g'::tsvector, 'a & s');
+SELECT ts_rank_cd(' a:1 s:2B d g'::tsvector, 'a & s');
+SELECT ts_rank_cd(' a:1 s:2 d g'::tsvector, 'a & s');
+
+SELECT ts_rank_cd(' a:1 s:2A d g'::tsvector, 'a <-> s');
+SELECT ts_rank_cd(' a:1 s:2C d g'::tsvector, 'a <-> s');
+SELECT ts_rank_cd(' a:1 s:2 d g'::tsvector, 'a <-> s');
+SELECT ts_rank_cd(' a:1 s:2 d:2A g'::tsvector, 'a <-> s');
+SELECT ts_rank_cd(' a:1 s:2,3A d:2A g'::tsvector, 'a <2> s:A');
+SELECT ts_rank_cd(' a:1 b:2 s:3A d:2A g'::tsvector, 'a <2> s:A');
+SELECT ts_rank_cd(' a:1 sa:2D sb:2A g'::tsvector, 'a <-> s:*');
+SELECT ts_rank_cd(' a:1 sa:2A sb:2D g'::tsvector, 'a <-> s:*');
+SELECT ts_rank_cd(' a:1 sa:2A sb:2D g'::tsvector, 'a <-> s:* <-> sa:A');
+SELECT ts_rank_cd(' a:1 sa:2A sb:2D g'::tsvector, 'a <-> s:* <-> sa:B');
+
+SELECT 'a:1 b:2'::tsvector @@ 'a <-> b'::tsquery AS "true";
+SELECT 'a:1 b:2'::tsvector @@ 'a <0> b'::tsquery AS "false";
+SELECT 'a:1 b:2'::tsvector @@ 'a <1> b'::tsquery AS "true";
+SELECT 'a:1 b:2'::tsvector @@ 'a <2> b'::tsquery AS "false";
+SELECT 'a:1 b:3'::tsvector @@ 'a <-> b'::tsquery AS "false";
+SELECT 'a:1 b:3'::tsvector @@ 'a <0> b'::tsquery AS "false";
+SELECT 'a:1 b:3'::tsvector @@ 'a <1> b'::tsquery AS "false";
+SELECT 'a:1 b:3'::tsvector @@ 'a <2> b'::tsquery AS "true";
+SELECT 'a:1 b:3'::tsvector @@ 'a <3> b'::tsquery AS "false";
+SELECT 'a:1 b:3'::tsvector @@ 'a <0> a:*'::tsquery AS "true";
+
+-- tsvector editing operations
+
+SELECT strip('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd'::tsvector);
+SELECT strip('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector);
+SELECT strip('base hidden rebel spaceship strike'::tsvector);
+
+SELECT ts_delete(to_tsvector('english', 'Rebel spaceships, striking from a hidden base'), 'spaceship');
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'base');
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'bas');
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'bases');
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'spaceship');
+SELECT ts_delete('base hidden rebel spaceship strike'::tsvector, 'spaceship');
+
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceship','rebel']);
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceships','rebel']);
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceshi','rebel']);
+SELECT ts_delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceship','leya','rebel']);
+SELECT ts_delete('base hidden rebel spaceship strike'::tsvector, ARRAY['spaceship','leya','rebel']);
+SELECT ts_delete('base hidden rebel spaceship strike'::tsvector, ARRAY['spaceship','leya','rebel','rebel']);
+SELECT ts_delete('base hidden rebel spaceship strike'::tsvector, ARRAY['spaceship','leya','rebel', NULL]);
+
+SELECT unnest('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector);
+SELECT unnest('base hidden rebel spaceship strike'::tsvector);
+SELECT * FROM unnest('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector);
+SELECT * FROM unnest('base hidden rebel spaceship strike'::tsvector);
+SELECT lexeme, positions[1] from unnest('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector);
+
+SELECT tsvector_to_array('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector);
+SELECT tsvector_to_array('base hidden rebel spaceship strike'::tsvector);
+
+SELECT array_to_tsvector(ARRAY['base','hidden','rebel','spaceship','strike']);
+SELECT array_to_tsvector(ARRAY['base','hidden','rebel','spaceship', NULL]);
+-- array_to_tsvector must sort and de-dup
+SELECT array_to_tsvector(ARRAY['foo','bar','baz','bar']);
+
+SELECT setweight('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd zxc:81,567,222A'::tsvector, 'c');
+SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c');
+SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c', '{a}');
+SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c', '{a}');
+SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c', '{a,zxc}');
+SELECT setweight('a asd w:5,6,12B,13A zxc'::tsvector, 'c', '{a,zxc}');
+SELECT setweight('a asd w:5,6,12B,13A zxc'::tsvector, 'c', ARRAY['a', 'zxc', NULL]);
+
+SELECT ts_filter('base:7A empir:17 evil:15 first:11 galact:16 hidden:6A rebel:1A spaceship:2A strike:3A victori:12 won:9'::tsvector, '{a}');
+SELECT ts_filter('base hidden rebel spaceship strike'::tsvector, '{a}');
+SELECT ts_filter('base hidden rebel spaceship strike'::tsvector, '{a,b,NULL}');
diff --git a/src/test/regress/sql/tuplesort.sql b/src/test/regress/sql/tuplesort.sql
new file mode 100644
index 0000000..846484d
--- /dev/null
+++ b/src/test/regress/sql/tuplesort.sql
@@ -0,0 +1,298 @@
+-- only use parallelism when explicitly intending to do so
+SET max_parallel_maintenance_workers = 0;
+SET max_parallel_workers = 0;
+
+-- A table with contents that, when sorted, triggers abbreviated
+-- key aborts. One easy way to achieve that is to use uuids that all
+-- have the same prefix, as abbreviated keys for uuids just use the
+-- first sizeof(Datum) bytes.
+CREATE TEMP TABLE abbrev_abort_uuids (
+ id serial not null,
+ abort_increasing uuid,
+ abort_decreasing uuid,
+ noabort_increasing uuid,
+ noabort_decreasing uuid);
+
+INSERT INTO abbrev_abort_uuids (abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing)
+ SELECT
+ ('00000000-0000-0000-0000-'||to_char(g.i, '000000000000FM'))::uuid abort_increasing,
+ ('00000000-0000-0000-0000-'||to_char(20000 - g.i, '000000000000FM'))::uuid abort_decreasing,
+ (to_char(g.i % 10009, '00000000FM')||'-0000-0000-0000-'||to_char(g.i, '000000000000FM'))::uuid noabort_increasing,
+ (to_char(((20000 - g.i) % 10009), '00000000FM')||'-0000-0000-0000-'||to_char(20000 - g.i, '000000000000FM'))::uuid noabort_decreasing
+ FROM generate_series(0, 20000, 1) g(i);
+
+-- and a few NULLs
+INSERT INTO abbrev_abort_uuids(id) VALUES(0);
+INSERT INTO abbrev_abort_uuids DEFAULT VALUES;
+INSERT INTO abbrev_abort_uuids DEFAULT VALUES;
+
+-- add just a few duplicates
+INSERT INTO abbrev_abort_uuids (abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing)
+ SELECT abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+ FROM abbrev_abort_uuids
+ WHERE (id < 10 OR id > 19990) AND id % 3 = 0 AND abort_increasing is not null;
+
+----
+-- Check sort node uses of tuplesort wrt. abbreviated keys
+----
+
+-- plain sort triggering abbreviated abort
+SELECT abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_increasing OFFSET 20000 - 4;
+SELECT abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_decreasing NULLS FIRST OFFSET 20000 - 4;
+
+-- plain sort not triggering abbreviated abort
+SELECT noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_increasing OFFSET 20000 - 4;
+SELECT noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing NULLS FIRST OFFSET 20000 - 4;
+
+-- bounded sort (disables abbreviated keys)
+SELECT abort_increasing, noabort_increasing FROM abbrev_abort_uuids ORDER BY abort_increasing LIMIT 5;
+SELECT abort_increasing, noabort_increasing FROM abbrev_abort_uuids ORDER BY noabort_increasing NULLS FIRST LIMIT 5;
+
+
+----
+-- Check index creation uses of tuplesort wrt. abbreviated keys
+----
+
+-- index creation using abbreviated keys successfully
+CREATE INDEX abbrev_abort_uuids__noabort_increasing_idx ON abbrev_abort_uuids (noabort_increasing);
+CREATE INDEX abbrev_abort_uuids__noabort_decreasing_idx ON abbrev_abort_uuids (noabort_decreasing);
+
+-- verify
+EXPLAIN (COSTS OFF)
+SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_increasing LIMIT 5;
+SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_increasing LIMIT 5;
+EXPLAIN (COSTS OFF)
+SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing LIMIT 5;
+SELECT id, noabort_increasing, noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing LIMIT 5;
+
+-- index creation using abbreviated keys, hitting abort
+CREATE INDEX abbrev_abort_uuids__abort_increasing_idx ON abbrev_abort_uuids (abort_increasing);
+CREATE INDEX abbrev_abort_uuids__abort_decreasing_idx ON abbrev_abort_uuids (abort_decreasing);
+
+-- verify
+EXPLAIN (COSTS OFF)
+SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_increasing LIMIT 5;
+SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_increasing LIMIT 5;
+EXPLAIN (COSTS OFF)
+SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_decreasing LIMIT 5;
+SELECT id, abort_increasing, abort_decreasing FROM abbrev_abort_uuids ORDER BY abort_decreasing LIMIT 5;
+
+
+----
+-- Check CLUSTER uses of tuplesort wrt. abbreviated keys
+----
+
+-- when aborting, increasing order
+BEGIN;
+SET LOCAL enable_indexscan = false;
+CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__abort_increasing_idx;
+
+-- head
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid LIMIT 5;
+
+-- tail
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid DESC LIMIT 5;
+ROLLBACK;
+
+-- when aborting, decreasing order
+BEGIN;
+SET LOCAL enable_indexscan = false;
+CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__abort_decreasing_idx;
+
+-- head
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid LIMIT 5;
+
+-- tail
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid DESC LIMIT 5;
+ROLLBACK;
+
+-- when not aborting, increasing order
+BEGIN;
+SET LOCAL enable_indexscan = false;
+CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__noabort_increasing_idx;
+
+-- head
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid LIMIT 5;
+
+-- tail
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid DESC LIMIT 5;
+ROLLBACK;
+
+-- when no aborting, decreasing order
+BEGIN;
+SET LOCAL enable_indexscan = false;
+CLUSTER abbrev_abort_uuids USING abbrev_abort_uuids__noabort_decreasing_idx;
+
+-- head
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid LIMIT 5;
+
+-- tail
+SELECT id, abort_increasing, abort_decreasing, noabort_increasing, noabort_decreasing
+FROM abbrev_abort_uuids
+ORDER BY ctid DESC LIMIT 5;
+ROLLBACK;
+
+----
+-- test forward and backward scans for in-memory and disk based tuplesort
+----
+
+-- in-memory
+BEGIN;
+SET LOCAL enable_indexscan = false;
+-- unfortunately can't show analyze output confirming sort method,
+-- the memory used output wouldn't be stable
+EXPLAIN (COSTS OFF) DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing;
+DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing;
+
+-- first and second
+FETCH NEXT FROM c;
+FETCH NEXT FROM c;
+
+-- scroll beyond beginning
+FETCH BACKWARD FROM c;
+FETCH BACKWARD FROM c;
+FETCH BACKWARD FROM c;
+FETCH BACKWARD FROM c;
+FETCH NEXT FROM c;
+
+-- scroll beyond end end
+FETCH LAST FROM c;
+FETCH BACKWARD FROM c;
+FETCH NEXT FROM c;
+FETCH NEXT FROM c;
+FETCH NEXT FROM c;
+FETCH BACKWARD FROM c;
+FETCH NEXT FROM c;
+
+COMMIT;
+
+-- disk based
+BEGIN;
+SET LOCAL enable_indexscan = false;
+SET LOCAL work_mem = '100kB';
+-- unfortunately can't show analyze output confirming sort method,
+-- the memory used output wouldn't be stable
+EXPLAIN (COSTS OFF) DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing;
+DECLARE c SCROLL CURSOR FOR SELECT noabort_decreasing FROM abbrev_abort_uuids ORDER BY noabort_decreasing;
+
+-- first and second
+FETCH NEXT FROM c;
+FETCH NEXT FROM c;
+
+-- scroll beyond beginning
+FETCH BACKWARD FROM c;
+FETCH BACKWARD FROM c;
+FETCH BACKWARD FROM c;
+FETCH BACKWARD FROM c;
+FETCH NEXT FROM c;
+
+-- scroll beyond end end
+FETCH LAST FROM c;
+FETCH BACKWARD FROM c;
+FETCH NEXT FROM c;
+FETCH NEXT FROM c;
+FETCH NEXT FROM c;
+FETCH BACKWARD FROM c;
+FETCH NEXT FROM c;
+
+COMMIT;
+
+
+----
+-- test tuplesort using both in-memory and disk sort
+---
+
+-- memory based
+SELECT
+ -- fixed-width by-value datum
+ (array_agg(id ORDER BY id DESC NULLS FIRST))[0:5],
+ -- fixed-width by-ref datum
+ (array_agg(abort_increasing ORDER BY abort_increasing DESC NULLS LAST))[0:5],
+ -- variable-width datum
+ (array_agg(id::text ORDER BY id::text DESC NULLS LAST))[0:5],
+ -- fixed width by-value datum tuplesort
+ percentile_disc(0.99) WITHIN GROUP (ORDER BY id),
+ -- ensure state is shared
+ percentile_disc(0.01) WITHIN GROUP (ORDER BY id),
+ -- fixed width by-ref datum tuplesort
+ percentile_disc(0.8) WITHIN GROUP (ORDER BY abort_increasing),
+ -- variable width by-ref datum tuplesort
+ percentile_disc(0.2) WITHIN GROUP (ORDER BY id::text),
+ -- multi-column tuplesort
+ rank('00000000-0000-0000-0000-000000000000', '2', '2') WITHIN GROUP (ORDER BY noabort_increasing, id, id::text)
+FROM (
+ SELECT * FROM abbrev_abort_uuids
+ UNION ALL
+ SELECT NULL, NULL, NULL, NULL, NULL) s;
+
+-- disk based (see also above)
+BEGIN;
+SET LOCAL work_mem = '100kB';
+
+SELECT
+ (array_agg(id ORDER BY id DESC NULLS FIRST))[0:5],
+ (array_agg(abort_increasing ORDER BY abort_increasing DESC NULLS LAST))[0:5],
+ (array_agg(id::text ORDER BY id::text DESC NULLS LAST))[0:5],
+ percentile_disc(0.99) WITHIN GROUP (ORDER BY id),
+ percentile_disc(0.01) WITHIN GROUP (ORDER BY id),
+ percentile_disc(0.8) WITHIN GROUP (ORDER BY abort_increasing),
+ percentile_disc(0.2) WITHIN GROUP (ORDER BY id::text),
+ rank('00000000-0000-0000-0000-000000000000', '2', '2') WITHIN GROUP (ORDER BY noabort_increasing, id, id::text)
+FROM (
+ SELECT * FROM abbrev_abort_uuids
+ UNION ALL
+ SELECT NULL, NULL, NULL, NULL, NULL) s;
+
+ROLLBACK;
+
+
+----
+-- test tuplesort mark/restore
+---
+
+CREATE TEMP TABLE test_mark_restore(col1 int, col2 int, col12 int);
+-- need a few duplicates for mark/restore to matter
+INSERT INTO test_mark_restore(col1, col2, col12)
+ SELECT a.i, b.i, a.i * b.i FROM generate_series(1, 500) a(i), generate_series(1, 5) b(i);
+
+BEGIN;
+
+SET LOCAL enable_nestloop = off;
+SET LOCAL enable_hashjoin = off;
+SET LOCAL enable_material = off;
+
+-- set query into variable once, to avoid repetition of the fairly long query
+SELECT $$
+ SELECT col12, count(distinct a.col1), count(distinct a.col2), count(distinct b.col1), count(distinct b.col2), count(*)
+ FROM test_mark_restore a
+ JOIN test_mark_restore b USING(col12)
+ GROUP BY 1
+ HAVING count(*) > 1
+ ORDER BY 2 DESC, 1 DESC, 3 DESC, 4 DESC, 5 DESC, 6 DESC
+ LIMIT 10
+$$ AS qry \gset
+
+-- test mark/restore with in-memory sorts
+EXPLAIN (COSTS OFF) :qry;
+:qry;
+
+-- test mark/restore with on-disk sorts
+SET LOCAL work_mem = '100kB';
+EXPLAIN (COSTS OFF) :qry;
+:qry;
+
+COMMIT;
diff --git a/src/test/regress/sql/txid.sql b/src/test/regress/sql/txid.sql
new file mode 100644
index 0000000..8d5ac98
--- /dev/null
+++ b/src/test/regress/sql/txid.sql
@@ -0,0 +1,102 @@
+-- txid_snapshot data type and related functions
+-- Note: these are backward-compatibility functions and types, and have been
+-- replaced by new xid8-based variants. See xid.sql. The txid variants will
+-- be removed in a future release.
+
+-- i/o
+select '12:13:'::txid_snapshot;
+select '12:18:14,16'::txid_snapshot;
+select '12:16:14,14'::txid_snapshot;
+
+-- errors
+select '31:12:'::txid_snapshot;
+select '0:1:'::txid_snapshot;
+select '12:13:0'::txid_snapshot;
+select '12:16:14,13'::txid_snapshot;
+
+create temp table snapshot_test (
+ nr integer,
+ snap txid_snapshot
+);
+
+insert into snapshot_test values (1, '12:13:');
+insert into snapshot_test values (2, '12:20:13,15,18');
+insert into snapshot_test values (3, '100001:100009:100005,100007,100008');
+insert into snapshot_test values (4, '100:150: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');
+select snap from snapshot_test order by nr;
+
+select txid_snapshot_xmin(snap),
+ txid_snapshot_xmax(snap),
+ txid_snapshot_xip(snap)
+from snapshot_test order by nr;
+
+select id, txid_visible_in_snapshot(id, snap)
+from snapshot_test, generate_series(11, 21) id
+where nr = 2;
+
+-- test bsearch
+select id, txid_visible_in_snapshot(id, snap)
+from snapshot_test, generate_series(90, 160) id
+where nr = 4;
+
+-- test current values also
+select txid_current() >= txid_snapshot_xmin(txid_current_snapshot());
+
+-- we can't assume current is always less than xmax, however
+
+select txid_visible_in_snapshot(txid_current(), txid_current_snapshot());
+
+-- test 64bitness
+
+select txid_snapshot '1000100010001000:1000100010001100:1000100010001012,1000100010001013';
+select txid_visible_in_snapshot('1000100010001012', '1000100010001000:1000100010001100:1000100010001012,1000100010001013');
+select txid_visible_in_snapshot('1000100010001015', '1000100010001000:1000100010001100:1000100010001012,1000100010001013');
+
+-- test 64bit overflow
+SELECT txid_snapshot '1:9223372036854775807:3';
+SELECT txid_snapshot '1:9223372036854775808:3';
+
+-- test txid_current_if_assigned
+BEGIN;
+SELECT txid_current_if_assigned() IS NULL;
+SELECT txid_current() \gset
+SELECT txid_current_if_assigned() IS NOT DISTINCT FROM BIGINT :'txid_current';
+COMMIT;
+
+-- test xid status functions
+BEGIN;
+SELECT txid_current() AS committed \gset
+COMMIT;
+
+BEGIN;
+SELECT txid_current() AS rolledback \gset
+ROLLBACK;
+
+BEGIN;
+SELECT txid_current() AS inprogress \gset
+
+SELECT txid_status(:committed) AS committed;
+SELECT txid_status(:rolledback) AS rolledback;
+SELECT txid_status(:inprogress) AS inprogress;
+SELECT txid_status(1); -- BootstrapTransactionId is always committed
+SELECT txid_status(2); -- FrozenTransactionId is always committed
+SELECT txid_status(3); -- in regress testing FirstNormalTransactionId will always be behind oldestXmin
+
+COMMIT;
+
+BEGIN;
+CREATE FUNCTION test_future_xid_status(bigint)
+RETURNS void
+LANGUAGE plpgsql
+AS
+$$
+BEGIN
+ PERFORM txid_status($1);
+ RAISE EXCEPTION 'didn''t ERROR at xid in the future as expected';
+EXCEPTION
+ WHEN invalid_parameter_value THEN
+ RAISE NOTICE 'Got expected error for xid in the future';
+END;
+$$;
+SELECT test_future_xid_status(:inprogress + 10000);
+ROLLBACK;
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
new file mode 100644
index 0000000..8281076
--- /dev/null
+++ b/src/test/regress/sql/type_sanity.sql
@@ -0,0 +1,597 @@
+--
+-- TYPE_SANITY
+-- Sanity checks for common errors in making type-related system tables:
+-- pg_type, pg_class, pg_attribute, pg_range.
+--
+-- None of the SELECTs here should ever find any matching entries,
+-- so the expected output is easy to maintain ;-).
+-- A test failure indicates someone messed up an entry in the system tables.
+--
+-- 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.
+
+-- **************** pg_type ****************
+
+-- Look for illegal values in pg_type fields.
+
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE p1.typnamespace = 0 OR
+ (p1.typlen <= 0 AND p1.typlen != -1 AND p1.typlen != -2) OR
+ (p1.typtype not in ('b', 'c', 'd', 'e', 'p', 'r', 'm')) OR
+ NOT p1.typisdefined OR
+ (p1.typalign not in ('c', 's', 'i', 'd')) OR
+ (p1.typstorage not in ('p', 'x', 'e', 'm'));
+
+-- Look for "pass by value" types that can't be passed by value.
+
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE p1.typbyval AND
+ (p1.typlen != 1 OR p1.typalign != 'c') AND
+ (p1.typlen != 2 OR p1.typalign != 's') AND
+ (p1.typlen != 4 OR p1.typalign != 'i') AND
+ (p1.typlen != 8 OR p1.typalign != 'd');
+
+-- Look for "toastable" types that aren't varlena.
+
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE p1.typstorage != 'p' AND
+ (p1.typbyval OR p1.typlen != -1);
+
+-- Look for complex types that do not have a typrelid entry,
+-- or basic types that do.
+
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE (p1.typtype = 'c' AND p1.typrelid = 0) OR
+ (p1.typtype != 'c' AND p1.typrelid != 0);
+
+-- Look for types that should have an array type but don't.
+-- Generally anything that's not a pseudotype should have an array type.
+-- However, we do have a small number of exceptions.
+
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE p1.typtype not in ('p') AND p1.typname NOT LIKE E'\\_%'
+ AND NOT EXISTS
+ (SELECT 1 FROM pg_type as p2
+ WHERE p2.typname = ('_' || p1.typname)::name AND
+ p2.typelem = p1.oid and p1.typarray = p2.oid)
+ORDER BY p1.oid;
+
+-- Make sure typarray points to a "true" array type of our own base
+SELECT p1.oid, p1.typname as basetype, p2.typname as arraytype,
+ p2.typsubscript
+FROM pg_type p1 LEFT JOIN pg_type p2 ON (p1.typarray = p2.oid)
+WHERE p1.typarray <> 0 AND
+ (p2.oid IS NULL OR
+ p2.typsubscript <> 'array_subscript_handler'::regproc);
+
+-- Look for range types that do not have a pg_range entry
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE p1.typtype = 'r' AND
+ NOT EXISTS(SELECT 1 FROM pg_range r WHERE rngtypid = p1.oid);
+
+-- Look for range types whose typalign isn't sufficient
+SELECT p1.oid, p1.typname, p1.typalign, p2.typname, p2.typalign
+FROM pg_type as p1
+ LEFT JOIN pg_range as r ON rngtypid = p1.oid
+ LEFT JOIN pg_type as p2 ON rngsubtype = p2.oid
+WHERE p1.typtype = 'r' AND
+ (p1.typalign != (CASE WHEN p2.typalign = 'd' THEN 'd'::"char"
+ ELSE 'i'::"char" END)
+ OR p2.oid IS NULL);
+
+-- Text conversion routines must be provided.
+
+SELECT p1.oid, p1.typname
+FROM pg_type as p1
+WHERE (p1.typinput = 0 OR p1.typoutput = 0);
+
+-- Check for bogus typinput routines
+
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typinput = p2.oid AND NOT
+ ((p2.pronargs = 1 AND p2.proargtypes[0] = 'cstring'::regtype) OR
+ (p2.pronargs = 2 AND p2.proargtypes[0] = 'cstring'::regtype AND
+ p2.proargtypes[1] = 'oid'::regtype) OR
+ (p2.pronargs = 3 AND p2.proargtypes[0] = 'cstring'::regtype AND
+ p2.proargtypes[1] = 'oid'::regtype AND
+ p2.proargtypes[2] = 'int4'::regtype));
+
+-- Check for type of the variadic array parameter's elements.
+-- provariadic should be ANYOID if the type of the last element is ANYOID,
+-- ANYELEMENTOID if the type of the last element is ANYARRAYOID,
+-- ANYCOMPATIBLEOID if the type of the last element is ANYCOMPATIBLEARRAYOID,
+-- and otherwise the element type corresponding to the array type.
+
+SELECT oid::regprocedure, provariadic::regtype, proargtypes::regtype[]
+FROM pg_proc
+WHERE provariadic != 0
+AND case proargtypes[array_length(proargtypes, 1)-1]
+ WHEN '"any"'::regtype THEN '"any"'::regtype
+ WHEN 'anyarray'::regtype THEN 'anyelement'::regtype
+ WHEN 'anycompatiblearray'::regtype THEN 'anycompatible'::regtype
+ ELSE (SELECT t.oid
+ FROM pg_type t
+ WHERE t.typarray = proargtypes[array_length(proargtypes, 1)-1])
+ END != provariadic;
+
+-- Check that all and only those functions with a variadic type have
+-- a variadic argument.
+SELECT oid::regprocedure, proargmodes, provariadic
+FROM pg_proc
+WHERE (proargmodes IS NOT NULL AND 'v' = any(proargmodes))
+ IS DISTINCT FROM
+ (provariadic != 0);
+
+-- As of 8.0, this check finds refcursor, which is borrowing
+-- other types' I/O routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typinput = p2.oid AND p1.typtype in ('b', 'p') AND NOT
+ (p1.typelem != 0 AND p1.typlen < 0) AND NOT
+ (p2.prorettype = p1.oid AND NOT p2.proretset)
+ORDER BY 1;
+
+-- Varlena array types will point to array_in
+-- Exception as of 8.1: int2vector and oidvector have their own I/O routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typinput = p2.oid AND
+ (p1.typelem != 0 AND p1.typlen < 0) AND NOT
+ (p2.oid = 'array_in'::regproc)
+ORDER BY 1;
+
+-- typinput routines should not be volatile
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typinput = p2.oid AND p2.provolatile NOT IN ('i', 's');
+
+-- Composites, domains, enums, multiranges, ranges should all use the same input routines
+SELECT DISTINCT typtype, typinput
+FROM pg_type AS p1
+WHERE p1.typtype not in ('b', 'p')
+ORDER BY 1;
+
+-- Check for bogus typoutput routines
+
+-- As of 8.0, this check finds refcursor, which is borrowing
+-- other types' I/O routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typoutput = p2.oid AND p1.typtype in ('b', 'p') AND NOT
+ (p2.pronargs = 1 AND
+ (p2.proargtypes[0] = p1.oid OR
+ (p2.oid = 'array_out'::regproc AND
+ p1.typelem != 0 AND p1.typlen = -1)))
+ORDER BY 1;
+
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typoutput = p2.oid AND NOT
+ (p2.prorettype = 'cstring'::regtype AND NOT p2.proretset);
+
+-- typoutput routines should not be volatile
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typoutput = p2.oid AND p2.provolatile NOT IN ('i', 's');
+
+-- Composites, enums, multiranges, ranges should all use the same output routines
+SELECT DISTINCT typtype, typoutput
+FROM pg_type AS p1
+WHERE p1.typtype not in ('b', 'd', 'p')
+ORDER BY 1;
+
+-- Domains should have same typoutput as their base types
+SELECT p1.oid, p1.typname, p2.oid, p2.typname
+FROM pg_type AS p1 LEFT JOIN pg_type AS p2 ON p1.typbasetype = p2.oid
+WHERE p1.typtype = 'd' AND p1.typoutput IS DISTINCT FROM p2.typoutput;
+
+-- Check for bogus typreceive routines
+
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typreceive = p2.oid AND NOT
+ ((p2.pronargs = 1 AND p2.proargtypes[0] = 'internal'::regtype) OR
+ (p2.pronargs = 2 AND p2.proargtypes[0] = 'internal'::regtype AND
+ p2.proargtypes[1] = 'oid'::regtype) OR
+ (p2.pronargs = 3 AND p2.proargtypes[0] = 'internal'::regtype AND
+ p2.proargtypes[1] = 'oid'::regtype AND
+ p2.proargtypes[2] = 'int4'::regtype));
+
+-- As of 7.4, this check finds refcursor, which is borrowing
+-- other types' I/O routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typreceive = p2.oid AND p1.typtype in ('b', 'p') AND NOT
+ (p1.typelem != 0 AND p1.typlen < 0) AND NOT
+ (p2.prorettype = p1.oid AND NOT p2.proretset)
+ORDER BY 1;
+
+-- Varlena array types will point to array_recv
+-- Exception as of 8.1: int2vector and oidvector have their own I/O routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typreceive = p2.oid AND
+ (p1.typelem != 0 AND p1.typlen < 0) AND NOT
+ (p2.oid = 'array_recv'::regproc)
+ORDER BY 1;
+
+-- Suspicious if typreceive doesn't take same number of args as typinput
+SELECT p1.oid, p1.typname, p2.oid, p2.proname, p3.oid, p3.proname
+FROM pg_type AS p1, pg_proc AS p2, pg_proc AS p3
+WHERE p1.typinput = p2.oid AND p1.typreceive = p3.oid AND
+ p2.pronargs != p3.pronargs;
+
+-- typreceive routines should not be volatile
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typreceive = p2.oid AND p2.provolatile NOT IN ('i', 's');
+
+-- Composites, domains, enums, multiranges, ranges should all use the same receive routines
+SELECT DISTINCT typtype, typreceive
+FROM pg_type AS p1
+WHERE p1.typtype not in ('b', 'p')
+ORDER BY 1;
+
+-- Check for bogus typsend routines
+
+-- As of 7.4, this check finds refcursor, which is borrowing
+-- other types' I/O routines
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typsend = p2.oid AND p1.typtype in ('b', 'p') AND NOT
+ (p2.pronargs = 1 AND
+ (p2.proargtypes[0] = p1.oid OR
+ (p2.oid = 'array_send'::regproc AND
+ p1.typelem != 0 AND p1.typlen = -1)))
+ORDER BY 1;
+
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typsend = p2.oid AND NOT
+ (p2.prorettype = 'bytea'::regtype AND NOT p2.proretset);
+
+-- typsend routines should not be volatile
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typsend = p2.oid AND p2.provolatile NOT IN ('i', 's');
+
+-- Composites, enums, multiranges, ranges should all use the same send routines
+SELECT DISTINCT typtype, typsend
+FROM pg_type AS p1
+WHERE p1.typtype not in ('b', 'd', 'p')
+ORDER BY 1;
+
+-- Domains should have same typsend as their base types
+SELECT p1.oid, p1.typname, p2.oid, p2.typname
+FROM pg_type AS p1 LEFT JOIN pg_type AS p2 ON p1.typbasetype = p2.oid
+WHERE p1.typtype = 'd' AND p1.typsend IS DISTINCT FROM p2.typsend;
+
+-- Check for bogus typmodin routines
+
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typmodin = p2.oid AND NOT
+ (p2.pronargs = 1 AND
+ p2.proargtypes[0] = 'cstring[]'::regtype AND
+ p2.prorettype = 'int4'::regtype AND NOT p2.proretset);
+
+-- typmodin routines should not be volatile
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typmodin = p2.oid AND p2.provolatile NOT IN ('i', 's');
+
+-- Check for bogus typmodout routines
+
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typmodout = p2.oid AND NOT
+ (p2.pronargs = 1 AND
+ p2.proargtypes[0] = 'int4'::regtype AND
+ p2.prorettype = 'cstring'::regtype AND NOT p2.proretset);
+
+-- typmodout routines should not be volatile
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typmodout = p2.oid AND p2.provolatile NOT IN ('i', 's');
+
+-- Array types should have same typmodin/out as their element types
+
+SELECT p1.oid, p1.typname, p2.oid, p2.typname
+FROM pg_type AS p1, pg_type AS p2
+WHERE p1.typelem = p2.oid AND NOT
+ (p1.typmodin = p2.typmodin AND p1.typmodout = p2.typmodout);
+
+-- Array types should have same typdelim as their element types
+
+SELECT p1.oid, p1.typname, p2.oid, p2.typname
+FROM pg_type AS p1, pg_type AS p2
+WHERE p1.typarray = p2.oid AND NOT (p1.typdelim = p2.typdelim);
+
+-- Look for array types whose typalign isn't sufficient
+
+SELECT p1.oid, p1.typname, p1.typalign, p2.typname, p2.typalign
+FROM pg_type AS p1, pg_type AS p2
+WHERE p1.typarray = p2.oid AND
+ p2.typalign != (CASE WHEN p1.typalign = 'd' THEN 'd'::"char"
+ ELSE 'i'::"char" END);
+
+-- Check for typelem set without a handler
+
+SELECT p1.oid, p1.typname, p1.typelem
+FROM pg_type AS p1
+WHERE p1.typelem != 0 AND p1.typsubscript = 0;
+
+-- Check for misuse of standard subscript handlers
+
+SELECT p1.oid, p1.typname,
+ p1.typelem, p1.typlen, p1.typbyval
+FROM pg_type AS p1
+WHERE p1.typsubscript = 'array_subscript_handler'::regproc AND NOT
+ (p1.typelem != 0 AND p1.typlen = -1 AND NOT p1.typbyval);
+
+SELECT p1.oid, p1.typname,
+ p1.typelem, p1.typlen, p1.typbyval
+FROM pg_type AS p1
+WHERE p1.typsubscript = 'raw_array_subscript_handler'::regproc AND NOT
+ (p1.typelem != 0 AND p1.typlen > 0 AND NOT p1.typbyval);
+
+-- Check for bogus typanalyze routines
+
+SELECT p1.oid, p1.typname, p2.oid, p2.proname
+FROM pg_type AS p1, pg_proc AS p2
+WHERE p1.typanalyze = p2.oid AND NOT
+ (p2.pronargs = 1 AND
+ p2.proargtypes[0] = 'internal'::regtype AND
+ p2.prorettype = 'bool'::regtype AND NOT p2.proretset);
+
+-- there does not seem to be a reason to care about volatility of typanalyze
+
+-- domains inherit their base type's typanalyze
+
+SELECT d.oid, d.typname, d.typanalyze, t.oid, t.typname, t.typanalyze
+FROM pg_type d JOIN pg_type t ON d.typbasetype = t.oid
+WHERE d.typanalyze != t.typanalyze;
+
+-- range_typanalyze should be used for all and only range types
+-- (but exclude domains, which we checked above)
+
+SELECT t.oid, t.typname, t.typanalyze
+FROM pg_type t LEFT JOIN pg_range r on t.oid = r.rngtypid
+WHERE t.typbasetype = 0 AND
+ (t.typanalyze = 'range_typanalyze'::regproc) != (r.rngtypid IS NOT NULL);
+
+-- array_typanalyze should be used for all and only array types
+-- (but exclude domains, which we checked above)
+-- As of 9.2 this finds int2vector and oidvector, which are weird anyway
+
+SELECT t.oid, t.typname, t.typanalyze
+FROM pg_type t
+WHERE t.typbasetype = 0 AND
+ (t.typanalyze = 'array_typanalyze'::regproc) !=
+ (t.typsubscript = 'array_subscript_handler'::regproc)
+ORDER BY 1;
+
+-- **************** pg_class ****************
+
+-- Look for illegal values in pg_class fields
+
+SELECT p1.oid, p1.relname
+FROM pg_class as p1
+WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p') OR
+ relpersistence NOT IN ('p', 'u', 't') OR
+ relreplident NOT IN ('d', 'n', 'f', 'i');
+
+-- All tables and indexes should have an access method.
+SELECT p1.oid, p1.relname
+FROM pg_class as p1
+WHERE p1.relkind NOT IN ('S', 'v', 'f', 'c') and
+ p1.relam = 0;
+
+-- Conversely, sequences, views, types shouldn't have them
+SELECT p1.oid, p1.relname
+FROM pg_class as p1
+WHERE p1.relkind IN ('S', 'v', 'f', 'c') and
+ p1.relam != 0;
+
+-- Indexes should have AMs of type 'i'
+SELECT pc.oid, pc.relname, pa.amname, pa.amtype
+FROM pg_class as pc JOIN pg_am AS pa ON (pc.relam = pa.oid)
+WHERE pc.relkind IN ('i') and
+ pa.amtype != 'i';
+
+-- Tables, matviews etc should have AMs of type 't'
+SELECT pc.oid, pc.relname, pa.amname, pa.amtype
+FROM pg_class as pc JOIN pg_am AS pa ON (pc.relam = pa.oid)
+WHERE pc.relkind IN ('r', 't', 'm') and
+ pa.amtype != 't';
+
+-- **************** pg_attribute ****************
+
+-- Look for illegal values in pg_attribute fields
+
+SELECT p1.attrelid, p1.attname
+FROM pg_attribute as p1
+WHERE p1.attrelid = 0 OR p1.atttypid = 0 OR p1.attnum = 0 OR
+ p1.attcacheoff != -1 OR p1.attinhcount < 0 OR
+ (p1.attinhcount = 0 AND NOT p1.attislocal);
+
+-- Cross-check attnum against parent relation
+
+SELECT p1.attrelid, p1.attname, p2.oid, p2.relname
+FROM pg_attribute AS p1, pg_class AS p2
+WHERE p1.attrelid = p2.oid AND p1.attnum > p2.relnatts;
+
+-- Detect missing pg_attribute entries: should have as many non-system
+-- attributes as parent relation expects
+
+SELECT p1.oid, p1.relname
+FROM pg_class AS p1
+WHERE p1.relnatts != (SELECT count(*) FROM pg_attribute AS p2
+ WHERE p2.attrelid = p1.oid AND p2.attnum > 0);
+
+-- Cross-check against pg_type entry
+-- NOTE: we allow attstorage to be 'plain' even when typstorage is not;
+-- this is mainly for toast tables.
+
+SELECT p1.attrelid, p1.attname, p2.oid, p2.typname
+FROM pg_attribute AS p1, pg_type AS p2
+WHERE p1.atttypid = p2.oid AND
+ (p1.attlen != p2.typlen OR
+ p1.attalign != p2.typalign OR
+ p1.attbyval != p2.typbyval OR
+ (p1.attstorage != p2.typstorage AND p1.attstorage != 'p'));
+
+-- **************** pg_range ****************
+
+-- Look for illegal values in pg_range fields.
+
+SELECT p1.rngtypid, p1.rngsubtype
+FROM pg_range as p1
+WHERE p1.rngtypid = 0 OR p1.rngsubtype = 0 OR p1.rngsubopc = 0;
+
+-- rngcollation should be specified iff subtype is collatable
+
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngcollation, t.typcollation
+FROM pg_range p1 JOIN pg_type t ON t.oid = p1.rngsubtype
+WHERE (rngcollation = 0) != (typcollation = 0);
+
+-- opclass had better be a btree opclass accepting the subtype.
+-- We must allow anyarray matches, cf IsBinaryCoercible()
+
+SELECT p1.rngtypid, p1.rngsubtype, o.opcmethod, o.opcname
+FROM pg_range p1 JOIN pg_opclass o ON o.oid = p1.rngsubopc
+WHERE o.opcmethod != 403 OR
+ ((o.opcintype != p1.rngsubtype) AND NOT
+ (o.opcintype = 'pg_catalog.anyarray'::regtype AND
+ EXISTS(select 1 from pg_catalog.pg_type where
+ oid = p1.rngsubtype and typelem != 0 and
+ typsubscript = 'array_subscript_handler'::regproc)));
+
+-- canonical function, if any, had better match the range type
+
+SELECT p1.rngtypid, p1.rngsubtype, p.proname
+FROM pg_range p1 JOIN pg_proc p ON p.oid = p1.rngcanonical
+WHERE pronargs != 1 OR proargtypes[0] != rngtypid OR prorettype != rngtypid;
+
+-- subdiff function, if any, had better match the subtype
+
+SELECT p1.rngtypid, p1.rngsubtype, p.proname
+FROM pg_range p1 JOIN pg_proc p ON p.oid = p1.rngsubdiff
+WHERE pronargs != 2
+ OR proargtypes[0] != rngsubtype OR proargtypes[1] != rngsubtype
+ OR prorettype != 'pg_catalog.float8'::regtype;
+
+-- every range should have a valid multirange
+
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+
+-- Create a table that holds all the known in-core data types and leave it
+-- around so as pg_upgrade is able to test their binary compatibility.
+CREATE TABLE tab_core_types AS SELECT
+ '(11,12)'::point,
+ '(1,1),(2,2)'::line,
+ '((11,11),(12,12))'::lseg,
+ '((11,11),(13,13))'::box,
+ '((11,12),(13,13),(14,14))'::path AS openedpath,
+ '[(11,12),(13,13),(14,14)]'::path AS closedpath,
+ '((11,12),(13,13),(14,14))'::polygon,
+ '1,1,1'::circle,
+ 'today'::date,
+ 'now'::time,
+ 'now'::timestamp,
+ 'now'::timetz,
+ 'now'::timestamptz,
+ '12 seconds'::interval,
+ '{"reason":"because"}'::json,
+ '{"when":"now"}'::jsonb,
+ '$.a[*] ? (@ > 2)'::jsonpath,
+ '127.0.0.1'::inet,
+ '127.0.0.0/8'::cidr,
+ '00:01:03:86:1c:ba'::macaddr8,
+ '00:01:03:86:1c:ba'::macaddr,
+ 2::int2, 4::int4, 8::int8,
+ 4::float4, '8'::float8, pi()::numeric,
+ 'foo'::"char",
+ 'c'::bpchar,
+ 'abc'::varchar,
+ 'name'::name,
+ 'txt'::text,
+ true::bool,
+ E'\\xDEADBEEF'::bytea,
+ B'10001'::bit,
+ B'10001'::varbit AS varbit,
+ '12.34'::money,
+ 'abc'::refcursor,
+ '1 2'::int2vector,
+ '1 2'::oidvector,
+ format('%I=UC/%I', USER, USER)::aclitem AS aclitem,
+ 'a fat cat sat on a mat and ate a fat rat'::tsvector,
+ 'fat & rat'::tsquery,
+ 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid,
+ '11'::xid8,
+ 'pg_class'::regclass,
+ 'regtype'::regtype type,
+ 'pg_monitor'::regrole,
+ 'pg_class'::regclass::oid,
+ '(1,1)'::tid, '2'::xid, '3'::cid,
+ '10:20:10,14,15'::txid_snapshot,
+ '10:20:10,14,15'::pg_snapshot,
+ '16/B374D848'::pg_lsn,
+ 1::information_schema.cardinal_number,
+ 'l'::information_schema.character_data,
+ 'n'::information_schema.sql_identifier,
+ 'now'::information_schema.time_stamp,
+ 'YES'::information_schema.yes_or_no,
+ 'venus'::planets,
+ 'i16'::insenum,
+ '(1,2)'::int4range, '{(1,2)}'::int4multirange,
+ '(3,4)'::int8range, '{(3,4)}'::int8multirange,
+ '(1,2)'::float8range, '{(1,2)}'::float8multirange,
+ '(3,4)'::numrange, '{(3,4)}'::nummultirange,
+ '(a,b)'::textrange, '{(a,b)}'::textmultirange,
+ '(12.34, 56.78)'::cashrange, '{(12.34, 56.78)}'::cashmultirange,
+ '(2020-01-02, 2021-02-03)'::daterange,
+ '{(2020-01-02, 2021-02-03)}'::datemultirange,
+ '(2020-01-02 03:04:05, 2021-02-03 06:07:08)'::tsrange,
+ '{(2020-01-02 03:04:05, 2021-02-03 06:07:08)}'::tsmultirange,
+ '(2020-01-02 03:04:05, 2021-02-03 06:07:08)'::tstzrange,
+ '{(2020-01-02 03:04:05, 2021-02-03 06:07:08)}'::tstzmultirange,
+ arrayrange(ARRAY[1,2], ARRAY[2,1]),
+ arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+
+-- Sanity check on the previous table, checking that all core types are
+-- included in this table.
+SELECT oid, typname, typtype, typelem, typarray, typarray
+ FROM pg_type t
+ WHERE typtype NOT IN ('p', 'c') AND
+ -- reg* types cannot be pg_upgraded, so discard them.
+ oid != ALL(ARRAY['regproc', 'regprocedure', 'regoper',
+ 'regoperator', 'regconfig', 'regdictionary',
+ 'regnamespace', 'regcollation']::regtype[]) AND
+ -- Discard types that do not accept input values as these cannot be
+ -- tested easily.
+ -- Note: XML might be disabled at compile-time.
+ oid != ALL(ARRAY['gtsvector', 'pg_node_tree',
+ 'pg_ndistinct', 'pg_dependencies', 'pg_mcv_list',
+ 'pg_brin_bloom_summary',
+ 'pg_brin_minmax_multi_summary', 'xml']::regtype[]) AND
+ -- Discard arrays.
+ NOT EXISTS (SELECT 1 FROM pg_type u WHERE u.typarray = t.oid)
+ -- Exclude everything from the table created above. This checks
+ -- that no in-core types are missing in tab_core_types.
+ AND NOT EXISTS (SELECT 1
+ FROM pg_attribute a
+ WHERE a.atttypid=t.oid AND
+ a.attnum > 0 AND
+ a.attrelid='tab_core_types'::regclass);
diff --git a/src/test/regress/sql/typed_table.sql b/src/test/regress/sql/typed_table.sql
new file mode 100644
index 0000000..9ef0cdf
--- /dev/null
+++ b/src/test/regress/sql/typed_table.sql
@@ -0,0 +1,75 @@
+CREATE TABLE ttable1 OF nothing;
+
+CREATE TYPE person_type AS (id int, name text);
+CREATE TABLE persons OF person_type;
+CREATE TABLE IF NOT EXISTS persons OF person_type;
+SELECT * FROM persons;
+\d persons
+
+CREATE FUNCTION get_all_persons() RETURNS SETOF person_type
+LANGUAGE SQL
+AS $$
+ SELECT * FROM persons;
+$$;
+
+SELECT * FROM get_all_persons();
+
+-- certain ALTER TABLE operations on typed tables are not allowed
+ALTER TABLE persons ADD COLUMN comment text;
+ALTER TABLE persons DROP COLUMN name;
+ALTER TABLE persons RENAME COLUMN id TO num;
+ALTER TABLE persons ALTER COLUMN name TYPE varchar;
+CREATE TABLE stuff (id int);
+ALTER TABLE persons INHERIT stuff;
+
+CREATE TABLE personsx OF person_type (myname WITH OPTIONS NOT NULL); -- error
+
+CREATE TABLE persons2 OF person_type (
+ id WITH OPTIONS PRIMARY KEY,
+ UNIQUE (name)
+);
+
+\d persons2
+
+CREATE TABLE persons3 OF person_type (
+ PRIMARY KEY (id),
+ name WITH OPTIONS DEFAULT ''
+);
+
+\d persons3
+
+CREATE TABLE persons4 OF person_type (
+ name WITH OPTIONS NOT NULL,
+ name WITH OPTIONS DEFAULT '' -- error, specified more than once
+);
+
+DROP TYPE person_type RESTRICT;
+DROP TYPE person_type CASCADE;
+
+CREATE TABLE persons5 OF stuff; -- only CREATE TYPE AS types may be used
+
+DROP TABLE stuff;
+
+
+-- implicit casting
+
+CREATE TYPE person_type AS (id int, name text);
+CREATE TABLE persons OF person_type;
+INSERT INTO persons VALUES (1, 'test');
+
+CREATE FUNCTION namelen(person_type) RETURNS int LANGUAGE SQL AS $$ SELECT length($1.name) $$;
+SELECT id, namelen(persons) FROM persons;
+
+CREATE TABLE persons2 OF person_type (
+ id WITH OPTIONS PRIMARY KEY,
+ UNIQUE (name)
+);
+
+\d persons2
+
+CREATE TABLE persons3 OF person_type (
+ PRIMARY KEY (id),
+ name NOT NULL DEFAULT ''
+);
+
+\d persons3
diff --git a/src/test/regress/sql/unicode.sql b/src/test/regress/sql/unicode.sql
new file mode 100644
index 0000000..63cd523
--- /dev/null
+++ b/src/test/regress/sql/unicode.sql
@@ -0,0 +1,34 @@
+SELECT getdatabaseencoding() <> 'UTF8' AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+
+SELECT U&'\0061\0308bc' <> U&'\00E4bc' COLLATE "C" AS sanity_check;
+
+SELECT normalize('');
+SELECT normalize(U&'\0061\0308\24D1c') = U&'\00E4\24D1c' COLLATE "C" AS test_default;
+SELECT normalize(U&'\0061\0308\24D1c', NFC) = U&'\00E4\24D1c' COLLATE "C" AS test_nfc;
+SELECT normalize(U&'\00E4bc', NFC) = U&'\00E4bc' COLLATE "C" AS test_nfc_idem;
+SELECT normalize(U&'\00E4\24D1c', NFD) = U&'\0061\0308\24D1c' COLLATE "C" AS test_nfd;
+SELECT normalize(U&'\0061\0308\24D1c', NFKC) = U&'\00E4bc' COLLATE "C" AS test_nfkc;
+SELECT normalize(U&'\00E4\24D1c', NFKD) = U&'\0061\0308bc' COLLATE "C" AS test_nfkd;
+
+SELECT "normalize"('abc', 'def'); -- run-time error
+
+SELECT U&'\00E4\24D1c' IS NORMALIZED AS test_default;
+SELECT U&'\00E4\24D1c' IS NFC NORMALIZED AS test_nfc;
+
+SELECT num, val,
+ val IS NFC NORMALIZED AS NFC,
+ val IS NFD NORMALIZED AS NFD,
+ val IS NFKC NORMALIZED AS NFKC,
+ val IS NFKD NORMALIZED AS NFKD
+FROM
+ (VALUES (1, U&'\00E4bc'),
+ (2, U&'\0061\0308bc'),
+ (3, U&'\00E4\24D1c'),
+ (4, U&'\0061\0308\24D1c'),
+ (5, '')) vals (num, val)
+ORDER BY num;
+
+SELECT is_normalized('abc', 'def'); -- run-time error
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
new file mode 100644
index 0000000..ca8c9b4
--- /dev/null
+++ b/src/test/regress/sql/union.sql
@@ -0,0 +1,542 @@
+--
+-- UNION (also INTERSECT, EXCEPT)
+--
+
+-- Simple UNION constructs
+
+SELECT 1 AS two UNION SELECT 2 ORDER BY 1;
+
+SELECT 1 AS one UNION SELECT 1 ORDER BY 1;
+
+SELECT 1 AS two UNION ALL SELECT 2;
+
+SELECT 1 AS two UNION ALL SELECT 1;
+
+SELECT 1 AS three UNION SELECT 2 UNION SELECT 3 ORDER BY 1;
+
+SELECT 1 AS two UNION SELECT 2 UNION SELECT 2 ORDER BY 1;
+
+SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
+
+SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
+
+-- Mixed types
+
+SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
+
+SELECT 1 AS two UNION SELECT 2.2 ORDER BY 1;
+
+SELECT 1 AS one UNION SELECT 1.0::float8 ORDER BY 1;
+
+SELECT 1.1 AS two UNION ALL SELECT 2 ORDER BY 1;
+
+SELECT 1.0::float8 AS two UNION ALL SELECT 1 ORDER BY 1;
+
+SELECT 1.1 AS three UNION SELECT 2 UNION SELECT 3 ORDER BY 1;
+
+SELECT 1.1::float8 AS two UNION SELECT 2 UNION SELECT 2.0::float8 ORDER BY 1;
+
+SELECT 1.1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
+
+SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
+
+--
+-- Try testing from tables...
+--
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION
+SELECT f1 FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL
+SELECT f1 FROM FLOAT8_TBL;
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION
+SELECT f1 FROM INT4_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL
+SELECT f1 FROM INT4_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+ WHERE f1 BETWEEN -1e6 AND 1e6
+UNION
+SELECT f1 FROM INT4_TBL
+ WHERE f1 BETWEEN 0 AND 1000000
+ORDER BY 1;
+
+SELECT CAST(f1 AS char(4)) AS three FROM VARCHAR_TBL
+UNION
+SELECT f1 FROM CHAR_TBL
+ORDER BY 1;
+
+SELECT f1 AS three FROM VARCHAR_TBL
+UNION
+SELECT CAST(f1 AS varchar) FROM CHAR_TBL
+ORDER BY 1;
+
+SELECT f1 AS eight FROM VARCHAR_TBL
+UNION ALL
+SELECT f1 FROM CHAR_TBL;
+
+SELECT f1 AS five FROM TEXT_TBL
+UNION
+SELECT f1 FROM VARCHAR_TBL
+UNION
+SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
+ORDER BY 1;
+
+--
+-- INTERSECT and EXCEPT
+--
+
+SELECT q2 FROM int8_tbl INTERSECT SELECT q1 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL SELECT q1 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT SELECT q1 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q1 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
+
+-- nested cases
+(SELECT 1,2,3 UNION SELECT 4,5,6) INTERSECT SELECT 4,5,6;
+(SELECT 1,2,3 UNION SELECT 4,5,6 ORDER BY 1,2) INTERSECT SELECT 4,5,6;
+(SELECT 1,2,3 UNION SELECT 4,5,6) EXCEPT SELECT 4,5,6;
+(SELECT 1,2,3 UNION SELECT 4,5,6 ORDER BY 1,2) EXCEPT SELECT 4,5,6;
+
+-- exercise both hashed and sorted implementations of UNION/INTERSECT/EXCEPT
+
+set enable_hashagg to on;
+
+explain (costs off)
+select count(*) from
+ ( select unique1 from tenk1 union select fivethous from tenk1 ) ss;
+select count(*) from
+ ( select unique1 from tenk1 union select fivethous from tenk1 ) ss;
+
+explain (costs off)
+select count(*) from
+ ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss;
+select count(*) from
+ ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss;
+
+explain (costs off)
+select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10;
+select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10;
+
+set enable_hashagg to off;
+
+explain (costs off)
+select count(*) from
+ ( select unique1 from tenk1 union select fivethous from tenk1 ) ss;
+select count(*) from
+ ( select unique1 from tenk1 union select fivethous from tenk1 ) ss;
+
+explain (costs off)
+select count(*) from
+ ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss;
+select count(*) from
+ ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss;
+
+explain (costs off)
+select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10;
+select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10;
+
+reset enable_hashagg;
+
+-- non-hashable type
+set enable_hashagg to on;
+
+explain (costs off)
+select x from (values (100::money), (200::money)) _(x) union select x from (values (100::money), (300::money)) _(x);
+
+set enable_hashagg to off;
+
+explain (costs off)
+select x from (values (100::money), (200::money)) _(x) union select x from (values (100::money), (300::money)) _(x);
+
+reset enable_hashagg;
+
+-- arrays
+set enable_hashagg to on;
+
+explain (costs off)
+select x from (values (array[1, 2]), (array[1, 3])) _(x) union select x from (values (array[1, 2]), (array[1, 4])) _(x);
+select x from (values (array[1, 2]), (array[1, 3])) _(x) union select x from (values (array[1, 2]), (array[1, 4])) _(x);
+explain (costs off)
+select x from (values (array[1, 2]), (array[1, 3])) _(x) intersect select x from (values (array[1, 2]), (array[1, 4])) _(x);
+select x from (values (array[1, 2]), (array[1, 3])) _(x) intersect select x from (values (array[1, 2]), (array[1, 4])) _(x);
+explain (costs off)
+select x from (values (array[1, 2]), (array[1, 3])) _(x) except select x from (values (array[1, 2]), (array[1, 4])) _(x);
+select x from (values (array[1, 2]), (array[1, 3])) _(x) except select x from (values (array[1, 2]), (array[1, 4])) _(x);
+
+-- non-hashable type
+explain (costs off)
+select x from (values (array[100::money]), (array[200::money])) _(x) union select x from (values (array[100::money]), (array[300::money])) _(x);
+select x from (values (array[100::money]), (array[200::money])) _(x) union select x from (values (array[100::money]), (array[300::money])) _(x);
+
+set enable_hashagg to off;
+
+explain (costs off)
+select x from (values (array[1, 2]), (array[1, 3])) _(x) union select x from (values (array[1, 2]), (array[1, 4])) _(x);
+select x from (values (array[1, 2]), (array[1, 3])) _(x) union select x from (values (array[1, 2]), (array[1, 4])) _(x);
+explain (costs off)
+select x from (values (array[1, 2]), (array[1, 3])) _(x) intersect select x from (values (array[1, 2]), (array[1, 4])) _(x);
+select x from (values (array[1, 2]), (array[1, 3])) _(x) intersect select x from (values (array[1, 2]), (array[1, 4])) _(x);
+explain (costs off)
+select x from (values (array[1, 2]), (array[1, 3])) _(x) except select x from (values (array[1, 2]), (array[1, 4])) _(x);
+select x from (values (array[1, 2]), (array[1, 3])) _(x) except select x from (values (array[1, 2]), (array[1, 4])) _(x);
+
+reset enable_hashagg;
+
+-- records
+set enable_hashagg to on;
+
+explain (costs off)
+select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x);
+select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x);
+explain (costs off)
+select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x);
+select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x);
+explain (costs off)
+select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x);
+select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x);
+
+-- non-hashable type
+
+-- With an anonymous row type, the typcache does not report that the
+-- type is hashable. (Otherwise, this would fail at execution time.)
+explain (costs off)
+select x from (values (row(100::money)), (row(200::money))) _(x) union select x from (values (row(100::money)), (row(300::money))) _(x);
+select x from (values (row(100::money)), (row(200::money))) _(x) union select x from (values (row(100::money)), (row(300::money))) _(x);
+
+-- With a defined row type, the typcache can inspect the type's fields
+-- for hashability.
+create type ct1 as (f1 money);
+explain (costs off)
+select x from (values (row(100::money)::ct1), (row(200::money)::ct1)) _(x) union select x from (values (row(100::money)::ct1), (row(300::money)::ct1)) _(x);
+select x from (values (row(100::money)::ct1), (row(200::money)::ct1)) _(x) union select x from (values (row(100::money)::ct1), (row(300::money)::ct1)) _(x);
+drop type ct1;
+
+set enable_hashagg to off;
+
+explain (costs off)
+select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x);
+select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x);
+explain (costs off)
+select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x);
+select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x);
+explain (costs off)
+select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x);
+select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x);
+
+reset enable_hashagg;
+
+--
+-- Mixed types
+--
+
+SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
+
+SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
+
+--
+-- Operator precedence and (((((extra))))) parentheses
+--
+
+SELECT q1 FROM int8_tbl INTERSECT SELECT q2 FROM int8_tbl UNION ALL SELECT q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl INTERSECT (((SELECT q2 FROM int8_tbl UNION ALL SELECT q2 FROM int8_tbl))) ORDER BY 1;
+
+(((SELECT q1 FROM int8_tbl INTERSECT SELECT q2 FROM int8_tbl ORDER BY 1))) UNION ALL SELECT q2 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl UNION ALL SELECT q2 FROM int8_tbl EXCEPT SELECT q1 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl UNION ALL (((SELECT q2 FROM int8_tbl EXCEPT SELECT q1 FROM int8_tbl ORDER BY 1)));
+
+(((SELECT q1 FROM int8_tbl UNION ALL SELECT q2 FROM int8_tbl))) EXCEPT SELECT q1 FROM int8_tbl ORDER BY 1;
+
+--
+-- Subqueries with ORDER BY & LIMIT clauses
+--
+
+-- In this syntax, ORDER BY/LIMIT apply to the result of the EXCEPT
+SELECT q1,q2 FROM int8_tbl EXCEPT SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+-- This should fail, because q2 isn't a name of an EXCEPT output column
+SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
+
+-- But this should work:
+SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
+
+--
+-- New syntaxes (7.1) permit new tests
+--
+
+(((((select * from int8_tbl)))));
+
+--
+-- Check behavior with empty select list (allowed since 9.4)
+--
+
+select union select;
+select intersect select;
+select except select;
+
+-- check hashed implementation
+set enable_hashagg = true;
+set enable_sort = false;
+
+explain (costs off)
+select from generate_series(1,5) union select from generate_series(1,3);
+explain (costs off)
+select from generate_series(1,5) intersect select from generate_series(1,3);
+
+select from generate_series(1,5) union select from generate_series(1,3);
+select from generate_series(1,5) union all select from generate_series(1,3);
+select from generate_series(1,5) intersect select from generate_series(1,3);
+select from generate_series(1,5) intersect all select from generate_series(1,3);
+select from generate_series(1,5) except select from generate_series(1,3);
+select from generate_series(1,5) except all select from generate_series(1,3);
+
+-- check sorted implementation
+set enable_hashagg = false;
+set enable_sort = true;
+
+explain (costs off)
+select from generate_series(1,5) union select from generate_series(1,3);
+explain (costs off)
+select from generate_series(1,5) intersect select from generate_series(1,3);
+
+select from generate_series(1,5) union select from generate_series(1,3);
+select from generate_series(1,5) union all select from generate_series(1,3);
+select from generate_series(1,5) intersect select from generate_series(1,3);
+select from generate_series(1,5) intersect all select from generate_series(1,3);
+select from generate_series(1,5) except select from generate_series(1,3);
+select from generate_series(1,5) except all select from generate_series(1,3);
+
+reset enable_hashagg;
+reset enable_sort;
+
+--
+-- Check handling of a case with unknown constants. We don't guarantee
+-- an undecorated constant will work in all cases, but historically this
+-- usage has worked, so test we don't break it.
+--
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
+-- This should fail, but it should produce an error cursor
+SELECT '3.4'::numeric UNION SELECT 'foo';
+
+--
+-- Test that expression-index constraints can be pushed down through
+-- UNION or UNION ALL
+--
+
+CREATE TEMP TABLE t1 (a text, b text);
+CREATE INDEX t1_ab_idx on t1 ((a || b));
+CREATE TEMP TABLE t2 (ab text primary key);
+INSERT INTO t1 VALUES ('a', 'b'), ('x', 'y');
+INSERT INTO t2 VALUES ('ab'), ('xy');
+
+set enable_seqscan = off;
+set enable_indexscan = on;
+set enable_bitmapscan = off;
+
+explain (costs off)
+ SELECT * FROM
+ (SELECT a || b AS ab FROM t1
+ UNION ALL
+ SELECT * FROM t2) t
+ WHERE ab = 'ab';
+
+explain (costs off)
+ SELECT * FROM
+ (SELECT a || b AS ab FROM t1
+ UNION
+ SELECT * FROM t2) t
+ WHERE ab = 'ab';
+
+--
+-- Test that ORDER BY for UNION ALL can be pushed down to inheritance
+-- children.
+--
+
+CREATE TEMP TABLE t1c (b text, a text);
+ALTER TABLE t1c INHERIT t1;
+CREATE TEMP TABLE t2c (primary key (ab)) INHERITS (t2);
+INSERT INTO t1c VALUES ('v', 'w'), ('c', 'd'), ('m', 'n'), ('e', 'f');
+INSERT INTO t2c VALUES ('vw'), ('cd'), ('mn'), ('ef');
+CREATE INDEX t1c_ab_idx on t1c ((a || b));
+
+set enable_seqscan = on;
+set enable_indexonlyscan = off;
+
+explain (costs off)
+ SELECT * FROM
+ (SELECT a || b AS ab FROM t1
+ UNION ALL
+ SELECT ab FROM t2) t
+ ORDER BY 1 LIMIT 8;
+
+ SELECT * FROM
+ (SELECT a || b AS ab FROM t1
+ UNION ALL
+ SELECT ab FROM t2) t
+ ORDER BY 1 LIMIT 8;
+
+reset enable_seqscan;
+reset enable_indexscan;
+reset enable_bitmapscan;
+
+-- This simpler variant of the above test has been observed to fail differently
+
+create table events (event_id int primary key);
+create table other_events (event_id int primary key);
+create table events_child () inherits (events);
+
+explain (costs off)
+select event_id
+ from (select event_id from events
+ union all
+ select event_id from other_events) ss
+ order by event_id;
+
+drop table events_child, events, other_events;
+
+reset enable_indexonlyscan;
+
+-- Test constraint exclusion of UNION ALL subqueries
+explain (costs off)
+ SELECT * FROM
+ (SELECT 1 AS t, * FROM tenk1 a
+ UNION ALL
+ SELECT 2 AS t, * FROM tenk1 b) c
+ WHERE t = 2;
+
+-- Test that we push quals into UNION sub-selects only when it's safe
+explain (costs off)
+SELECT * FROM
+ (SELECT 1 AS t, 2 AS x
+ UNION
+ SELECT 2 AS t, 4 AS x) ss
+WHERE x < 4
+ORDER BY x;
+
+SELECT * FROM
+ (SELECT 1 AS t, 2 AS x
+ UNION
+ SELECT 2 AS t, 4 AS x) ss
+WHERE x < 4
+ORDER BY x;
+
+explain (costs off)
+SELECT * FROM
+ (SELECT 1 AS t, generate_series(1,10) AS x
+ UNION
+ SELECT 2 AS t, 4 AS x) ss
+WHERE x < 4
+ORDER BY x;
+
+SELECT * FROM
+ (SELECT 1 AS t, generate_series(1,10) AS x
+ UNION
+ SELECT 2 AS t, 4 AS x) ss
+WHERE x < 4
+ORDER BY x;
+
+explain (costs off)
+SELECT * FROM
+ (SELECT 1 AS t, (random()*3)::int AS x
+ UNION
+ SELECT 2 AS t, 4 AS x) ss
+WHERE x > 3
+ORDER BY x;
+
+SELECT * FROM
+ (SELECT 1 AS t, (random()*3)::int AS x
+ UNION
+ SELECT 2 AS t, 4 AS x) ss
+WHERE x > 3
+ORDER BY x;
+
+-- Test cases where the native ordering of a sub-select has more pathkeys
+-- than the outer query cares about
+explain (costs off)
+select distinct q1 from
+ (select distinct * from int8_tbl i81
+ union all
+ select distinct * from int8_tbl i82) ss
+where q2 = q2;
+
+select distinct q1 from
+ (select distinct * from int8_tbl i81
+ union all
+ select distinct * from int8_tbl i82) ss
+where q2 = q2;
+
+explain (costs off)
+select distinct q1 from
+ (select distinct * from int8_tbl i81
+ union all
+ select distinct * from int8_tbl i82) ss
+where -q1 = q2;
+
+select distinct q1 from
+ (select distinct * from int8_tbl i81
+ union all
+ select distinct * from int8_tbl i82) ss
+where -q1 = q2;
+
+-- Test proper handling of parameterized appendrel paths when the
+-- potential join qual is expensive
+create function expensivefunc(int) returns int
+language plpgsql immutable strict cost 10000
+as $$begin return $1; end$$;
+
+create temp table t3 as select generate_series(-1000,1000) as x;
+create index t3i on t3 (expensivefunc(x));
+analyze t3;
+
+explain (costs off)
+select * from
+ (select * from t3 a union all select * from t3 b) ss
+ join int4_tbl on f1 = expensivefunc(x);
+select * from
+ (select * from t3 a union all select * from t3 b) ss
+ join int4_tbl on f1 = expensivefunc(x);
+
+drop table t3;
+drop function expensivefunc(int);
+
+-- Test handling of appendrel quals that const-simplify into an AND
+explain (costs off)
+select * from
+ (select *, 0 as x from int8_tbl a
+ union all
+ select *, 1 as x from int8_tbl b) ss
+where (x = 0) or (q1 >= q2 and q1 <= q2);
+select * from
+ (select *, 0 as x from int8_tbl a
+ union all
+ select *, 1 as x from int8_tbl b) ss
+where (x = 0) or (q1 >= q2 and q1 <= q2);
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
new file mode 100644
index 0000000..09328e5
--- /dev/null
+++ b/src/test/regress/sql/updatable_views.sql
@@ -0,0 +1,1534 @@
+--
+-- UPDATABLE VIEWS
+--
+
+-- avoid bit-exact output here because operations may not be bit-exact.
+SET extra_float_digits = 0;
+
+-- check that non-updatable views and columns are rejected with useful error
+-- messages
+
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
+
+CREATE VIEW ro_view1 AS SELECT DISTINCT a, b FROM base_tbl; -- DISTINCT not supported
+CREATE VIEW ro_view2 AS SELECT a, b FROM base_tbl GROUP BY a, b; -- GROUP BY not supported
+CREATE VIEW ro_view3 AS SELECT 1 FROM base_tbl HAVING max(a) > 0; -- HAVING not supported
+CREATE VIEW ro_view4 AS SELECT count(*) FROM base_tbl; -- Aggregate functions not supported
+CREATE VIEW ro_view5 AS SELECT a, rank() OVER() FROM base_tbl; -- Window functions not supported
+CREATE VIEW ro_view6 AS SELECT a, b FROM base_tbl UNION SELECT -a, b FROM base_tbl; -- Set ops not supported
+CREATE VIEW ro_view7 AS WITH t AS (SELECT a, b FROM base_tbl) SELECT * FROM t; -- WITH not supported
+CREATE VIEW ro_view8 AS SELECT a, b FROM base_tbl ORDER BY a OFFSET 1; -- OFFSET not supported
+CREATE VIEW ro_view9 AS SELECT a, b FROM base_tbl ORDER BY a LIMIT 1; -- LIMIT not supported
+CREATE VIEW ro_view10 AS SELECT 1 AS a; -- No base relations
+CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
+CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
+CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
+CREATE VIEW rw_view14 AS SELECT ctid, a, b FROM base_tbl; -- System columns may be part of an updatable view
+CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
+CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
+CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
+CREATE VIEW ro_view18 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
+CREATE SEQUENCE uv_seq;
+CREATE VIEW ro_view19 AS SELECT * FROM uv_seq; -- View based on a sequence
+CREATE VIEW ro_view20 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE E'r_\\_view%'
+ ORDER BY table_name;
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE E'r_\\_view%'
+ ORDER BY table_name;
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE E'r_\\_view%'
+ ORDER BY table_name, ordinal_position;
+
+-- Read-only views
+DELETE FROM ro_view1;
+DELETE FROM ro_view2;
+DELETE FROM ro_view3;
+DELETE FROM ro_view4;
+DELETE FROM ro_view5;
+DELETE FROM ro_view6;
+UPDATE ro_view7 SET a=a+1;
+UPDATE ro_view8 SET a=a+1;
+UPDATE ro_view9 SET a=a+1;
+UPDATE ro_view10 SET a=a+1;
+UPDATE ro_view11 SET a=a+1;
+UPDATE ro_view12 SET a=a+1;
+INSERT INTO ro_view13 VALUES (3, 'Row 3');
+-- Partially updatable view
+INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
+INSERT INTO rw_view14 (a, b) VALUES (3, 'Row 3'); -- should be OK
+UPDATE rw_view14 SET ctid=null WHERE a=3; -- should fail
+UPDATE rw_view14 SET b='ROW 3' WHERE a=3; -- should be OK
+SELECT * FROM base_tbl;
+DELETE FROM rw_view14 WHERE a=3; -- should be OK
+-- Partially updatable view
+INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
+INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
+INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT DO NOTHING; -- succeeds
+SELECT * FROM rw_view15;
+INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO NOTHING; -- succeeds
+SELECT * FROM rw_view15;
+INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO UPDATE set a = excluded.a; -- succeeds
+SELECT * FROM rw_view15;
+INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO UPDATE set upper = 'blarg'; -- fails
+SELECT * FROM rw_view15;
+SELECT * FROM rw_view15;
+ALTER VIEW rw_view15 ALTER COLUMN upper SET DEFAULT 'NOT SET';
+INSERT INTO rw_view15 (a) VALUES (4); -- should fail
+UPDATE rw_view15 SET upper='ROW 3' WHERE a=3; -- should fail
+UPDATE rw_view15 SET upper=DEFAULT WHERE a=3; -- should fail
+UPDATE rw_view15 SET a=4 WHERE a=3; -- should be OK
+SELECT * FROM base_tbl;
+DELETE FROM rw_view15 WHERE a=4; -- should be OK
+-- Partially updatable view
+INSERT INTO rw_view16 VALUES (3, 'Row 3', 3); -- should fail
+INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should be OK
+UPDATE rw_view16 SET a=3, aa=-3 WHERE a=3; -- should fail
+UPDATE rw_view16 SET aa=-3 WHERE a=3; -- should be OK
+SELECT * FROM base_tbl;
+DELETE FROM rw_view16 WHERE a=-3; -- should be OK
+-- Read-only views
+INSERT INTO ro_view17 VALUES (3, 'ROW 3');
+DELETE FROM ro_view18;
+UPDATE ro_view19 SET last_value=1000;
+UPDATE ro_view20 SET b=upper(b);
+
+-- A view with a conditional INSTEAD rule but no unconditional INSTEAD rules
+-- or INSTEAD OF triggers should be non-updatable and generate useful error
+-- messages with appropriate detail
+CREATE RULE rw_view16_ins_rule AS ON INSERT TO rw_view16
+ WHERE NEW.a > 0 DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, NEW.b);
+CREATE RULE rw_view16_upd_rule AS ON UPDATE TO rw_view16
+ WHERE OLD.a > 0 DO INSTEAD UPDATE base_tbl SET b=NEW.b WHERE a=OLD.a;
+CREATE RULE rw_view16_del_rule AS ON DELETE TO rw_view16
+ WHERE OLD.a > 0 DO INSTEAD DELETE FROM base_tbl WHERE a=OLD.a;
+
+INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should fail
+UPDATE rw_view16 SET b='ROW 2' WHERE a=2; -- should fail
+DELETE FROM rw_view16 WHERE a=2; -- should fail
+
+DROP TABLE base_tbl CASCADE;
+DROP VIEW ro_view10, ro_view12, ro_view18;
+DROP SEQUENCE uv_seq CASCADE;
+
+-- simple updatable view
+
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
+
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a>0;
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name = 'rw_view1';
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name = 'rw_view1';
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name = 'rw_view1'
+ ORDER BY ordinal_position;
+
+INSERT INTO rw_view1 VALUES (3, 'Row 3');
+INSERT INTO rw_view1 (a) VALUES (4);
+UPDATE rw_view1 SET a=5 WHERE a=4;
+DELETE FROM rw_view1 WHERE b='Row 2';
+SELECT * FROM base_tbl;
+
+EXPLAIN (costs off) UPDATE rw_view1 SET a=6 WHERE a=5;
+EXPLAIN (costs off) DELETE FROM rw_view1 WHERE a=5;
+
+DROP TABLE base_tbl CASCADE;
+
+-- view on top of view
+
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
+
+CREATE VIEW rw_view1 AS SELECT b AS bb, a AS aa FROM base_tbl WHERE a>0;
+CREATE VIEW rw_view2 AS SELECT aa AS aaa, bb AS bbb FROM rw_view1 WHERE aa<10;
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name = 'rw_view2';
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name = 'rw_view2';
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name = 'rw_view2'
+ ORDER BY ordinal_position;
+
+INSERT INTO rw_view2 VALUES (3, 'Row 3');
+INSERT INTO rw_view2 (aaa) VALUES (4);
+SELECT * FROM rw_view2;
+UPDATE rw_view2 SET bbb='Row 4' WHERE aaa=4;
+DELETE FROM rw_view2 WHERE aaa=2;
+SELECT * FROM rw_view2;
+
+EXPLAIN (costs off) UPDATE rw_view2 SET aaa=5 WHERE aaa=4;
+EXPLAIN (costs off) DELETE FROM rw_view2 WHERE aaa=4;
+
+DROP TABLE base_tbl CASCADE;
+
+-- view on top of view with rules
+
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
+
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a>0 OFFSET 0; -- not updatable without rules/triggers
+CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a<10;
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+
+CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, NEW.b) RETURNING *;
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+
+CREATE RULE rw_view1_upd_rule AS ON UPDATE TO rw_view1
+ DO INSTEAD UPDATE base_tbl SET b=NEW.b WHERE a=OLD.a RETURNING NEW.*;
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+
+CREATE RULE rw_view1_del_rule AS ON DELETE TO rw_view1
+ DO INSTEAD DELETE FROM base_tbl WHERE a=OLD.a RETURNING OLD.*;
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+
+INSERT INTO rw_view2 VALUES (3, 'Row 3') RETURNING *;
+UPDATE rw_view2 SET b='Row three' WHERE a=3 RETURNING *;
+SELECT * FROM rw_view2;
+DELETE FROM rw_view2 WHERE a=3 RETURNING *;
+SELECT * FROM rw_view2;
+
+EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
+EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
+
+DROP TABLE base_tbl CASCADE;
+
+-- view on top of view with triggers
+
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
+
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a>0 OFFSET 0; -- not updatable without rules/triggers
+CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a<10;
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, is_updatable, is_insertable_into,
+ is_trigger_updatable, is_trigger_deletable,
+ is_trigger_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+
+CREATE FUNCTION rw_view1_trig_fn()
+RETURNS trigger AS
+$$
+BEGIN
+ IF TG_OP = 'INSERT' THEN
+ INSERT INTO base_tbl VALUES (NEW.a, NEW.b);
+ RETURN NEW;
+ ELSIF TG_OP = 'UPDATE' THEN
+ UPDATE base_tbl SET b=NEW.b WHERE a=OLD.a;
+ RETURN NEW;
+ ELSIF TG_OP = 'DELETE' THEN
+ DELETE FROM base_tbl WHERE a=OLD.a;
+ RETURN OLD;
+ END IF;
+END;
+$$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER rw_view1_ins_trig INSTEAD OF INSERT ON rw_view1
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, is_updatable, is_insertable_into,
+ is_trigger_updatable, is_trigger_deletable,
+ is_trigger_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+
+CREATE TRIGGER rw_view1_upd_trig INSTEAD OF UPDATE ON rw_view1
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, is_updatable, is_insertable_into,
+ is_trigger_updatable, is_trigger_deletable,
+ is_trigger_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+
+CREATE TRIGGER rw_view1_del_trig INSTEAD OF DELETE ON rw_view1
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, is_updatable, is_insertable_into,
+ is_trigger_updatable, is_trigger_deletable,
+ is_trigger_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name;
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE 'rw_view%'
+ ORDER BY table_name, ordinal_position;
+
+INSERT INTO rw_view2 VALUES (3, 'Row 3') RETURNING *;
+UPDATE rw_view2 SET b='Row three' WHERE a=3 RETURNING *;
+SELECT * FROM rw_view2;
+DELETE FROM rw_view2 WHERE a=3 RETURNING *;
+SELECT * FROM rw_view2;
+
+EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
+EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
+
+DROP TABLE base_tbl CASCADE;
+DROP FUNCTION rw_view1_trig_fn();
+
+-- update using whole row from view
+
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
+
+CREATE VIEW rw_view1 AS SELECT b AS bb, a AS aa FROM base_tbl;
+
+CREATE FUNCTION rw_view1_aa(x rw_view1)
+ RETURNS int AS $$ SELECT x.aa $$ LANGUAGE sql;
+
+UPDATE rw_view1 v SET bb='Updated row 2' WHERE rw_view1_aa(v)=2
+ RETURNING rw_view1_aa(v), v.bb;
+SELECT * FROM base_tbl;
+
+EXPLAIN (costs off)
+UPDATE rw_view1 v SET bb='Updated row 2' WHERE rw_view1_aa(v)=2
+ RETURNING rw_view1_aa(v), v.bb;
+
+DROP TABLE base_tbl CASCADE;
+
+-- permissions checks
+
+CREATE USER regress_view_user1;
+CREATE USER regress_view_user2;
+
+SET SESSION AUTHORIZATION regress_view_user1;
+CREATE TABLE base_tbl(a int, b text, c float);
+INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0);
+CREATE VIEW rw_view1 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl;
+INSERT INTO rw_view1 VALUES ('Row 2', 2.0, 2);
+
+GRANT SELECT ON base_tbl TO regress_view_user2;
+GRANT SELECT ON rw_view1 TO regress_view_user2;
+GRANT UPDATE (a,c) ON base_tbl TO regress_view_user2;
+GRANT UPDATE (bb,cc) ON rw_view1 TO regress_view_user2;
+RESET SESSION AUTHORIZATION;
+
+SET SESSION AUTHORIZATION regress_view_user2;
+CREATE VIEW rw_view2 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl;
+SELECT * FROM base_tbl; -- ok
+SELECT * FROM rw_view1; -- ok
+SELECT * FROM rw_view2; -- ok
+
+INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- not allowed
+INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
+INSERT INTO rw_view2 VALUES ('Row 3', 3.0, 3); -- not allowed
+
+UPDATE base_tbl SET a=a, c=c; -- ok
+UPDATE base_tbl SET b=b; -- not allowed
+UPDATE rw_view1 SET bb=bb, cc=cc; -- ok
+UPDATE rw_view1 SET aa=aa; -- not allowed
+UPDATE rw_view2 SET aa=aa, cc=cc; -- ok
+UPDATE rw_view2 SET bb=bb; -- not allowed
+
+DELETE FROM base_tbl; -- not allowed
+DELETE FROM rw_view1; -- not allowed
+DELETE FROM rw_view2; -- not allowed
+RESET SESSION AUTHORIZATION;
+
+SET SESSION AUTHORIZATION regress_view_user1;
+GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
+RESET SESSION AUTHORIZATION;
+
+SET SESSION AUTHORIZATION regress_view_user2;
+INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- ok
+INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
+INSERT INTO rw_view2 VALUES ('Row 4', 4.0, 4); -- ok
+DELETE FROM base_tbl WHERE a=1; -- ok
+DELETE FROM rw_view1 WHERE aa=2; -- not allowed
+DELETE FROM rw_view2 WHERE aa=2; -- ok
+SELECT * FROM base_tbl;
+RESET SESSION AUTHORIZATION;
+
+SET SESSION AUTHORIZATION regress_view_user1;
+REVOKE INSERT, DELETE ON base_tbl FROM regress_view_user2;
+GRANT INSERT, DELETE ON rw_view1 TO regress_view_user2;
+RESET SESSION AUTHORIZATION;
+
+SET SESSION AUTHORIZATION regress_view_user2;
+INSERT INTO base_tbl VALUES (5, 'Row 5', 5.0); -- not allowed
+INSERT INTO rw_view1 VALUES ('Row 5', 5.0, 5); -- ok
+INSERT INTO rw_view2 VALUES ('Row 6', 6.0, 6); -- not allowed
+DELETE FROM base_tbl WHERE a=3; -- not allowed
+DELETE FROM rw_view1 WHERE aa=3; -- ok
+DELETE FROM rw_view2 WHERE aa=4; -- not allowed
+SELECT * FROM base_tbl;
+RESET SESSION AUTHORIZATION;
+
+DROP TABLE base_tbl CASCADE;
+
+-- nested-view permissions
+
+CREATE TABLE base_tbl(a int, b text, c float);
+INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0);
+
+SET SESSION AUTHORIZATION regress_view_user1;
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl;
+SELECT * FROM rw_view1; -- not allowed
+SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
+UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
+
+SET SESSION AUTHORIZATION regress_view_user2;
+CREATE VIEW rw_view2 AS SELECT * FROM rw_view1;
+SELECT * FROM rw_view2; -- not allowed
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+
+RESET SESSION AUTHORIZATION;
+GRANT SELECT ON base_tbl TO regress_view_user1;
+
+SET SESSION AUTHORIZATION regress_view_user1;
+SELECT * FROM rw_view1;
+SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
+UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
+
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2; -- not allowed
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+
+SET SESSION AUTHORIZATION regress_view_user1;
+GRANT SELECT ON rw_view1 TO regress_view_user2;
+
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2;
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+
+RESET SESSION AUTHORIZATION;
+GRANT UPDATE ON base_tbl TO regress_view_user1;
+
+SET SESSION AUTHORIZATION regress_view_user1;
+SELECT * FROM rw_view1;
+SELECT * FROM rw_view1 FOR UPDATE;
+UPDATE rw_view1 SET b = 'foo' WHERE a = 1;
+
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2;
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+
+SET SESSION AUTHORIZATION regress_view_user1;
+GRANT UPDATE ON rw_view1 TO regress_view_user2;
+
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2;
+SELECT * FROM rw_view2 FOR UPDATE;
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1;
+
+RESET SESSION AUTHORIZATION;
+REVOKE UPDATE ON base_tbl FROM regress_view_user1;
+
+SET SESSION AUTHORIZATION regress_view_user1;
+SELECT * FROM rw_view1;
+SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
+UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
+
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2;
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+
+RESET SESSION AUTHORIZATION;
+
+DROP TABLE base_tbl CASCADE;
+
+DROP USER regress_view_user1;
+DROP USER regress_view_user2;
+
+-- column defaults
+
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified', c serial);
+INSERT INTO base_tbl VALUES (1, 'Row 1');
+INSERT INTO base_tbl VALUES (2, 'Row 2');
+INSERT INTO base_tbl VALUES (3);
+
+CREATE VIEW rw_view1 AS SELECT a AS aa, b AS bb FROM base_tbl;
+ALTER VIEW rw_view1 ALTER COLUMN bb SET DEFAULT 'View default';
+
+INSERT INTO rw_view1 VALUES (4, 'Row 4');
+INSERT INTO rw_view1 (aa) VALUES (5);
+
+SELECT * FROM base_tbl;
+
+DROP TABLE base_tbl CASCADE;
+
+-- Table having triggers
+
+CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
+INSERT INTO base_tbl VALUES (1, 'Row 1');
+INSERT INTO base_tbl VALUES (2, 'Row 2');
+
+CREATE FUNCTION rw_view1_trig_fn()
+RETURNS trigger AS
+$$
+BEGIN
+ IF TG_OP = 'INSERT' THEN
+ UPDATE base_tbl SET b=NEW.b WHERE a=1;
+ RETURN NULL;
+ END IF;
+ RETURN NULL;
+END;
+$$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER rw_view1_ins_trig AFTER INSERT ON base_tbl
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+
+CREATE VIEW rw_view1 AS SELECT a AS aa, b AS bb FROM base_tbl;
+
+INSERT INTO rw_view1 VALUES (3, 'Row 3');
+select * from base_tbl;
+
+DROP VIEW rw_view1;
+DROP TRIGGER rw_view1_ins_trig on base_tbl;
+DROP FUNCTION rw_view1_trig_fn();
+DROP TABLE base_tbl;
+
+-- view with ORDER BY
+
+CREATE TABLE base_tbl (a int, b int);
+INSERT INTO base_tbl VALUES (1,2), (4,5), (3,-3);
+
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl ORDER BY a+b;
+
+SELECT * FROM rw_view1;
+
+INSERT INTO rw_view1 VALUES (7,-8);
+SELECT * FROM rw_view1;
+
+EXPLAIN (verbose, costs off) UPDATE rw_view1 SET b = b + 1 RETURNING *;
+UPDATE rw_view1 SET b = b + 1 RETURNING *;
+SELECT * FROM rw_view1;
+
+DROP TABLE base_tbl CASCADE;
+
+-- multiple array-column updates
+
+CREATE TABLE base_tbl (a int, arr int[]);
+INSERT INTO base_tbl VALUES (1,ARRAY[2]), (3,ARRAY[4]);
+
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl;
+
+UPDATE rw_view1 SET arr[1] = 42, arr[2] = 77 WHERE a = 3;
+
+SELECT * FROM rw_view1;
+
+DROP TABLE base_tbl CASCADE;
+
+-- views with updatable and non-updatable columns
+
+CREATE TABLE base_tbl(a float);
+INSERT INTO base_tbl SELECT i/10.0 FROM generate_series(1,10) g(i);
+
+CREATE VIEW rw_view1 AS
+ SELECT ctid, sin(a) s, a, cos(a) c
+ FROM base_tbl
+ WHERE a != 0
+ ORDER BY abs(a);
+
+INSERT INTO rw_view1 VALUES (null, null, 1.1, null); -- should fail
+INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail
+INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK
+UPDATE rw_view1 SET s = s WHERE a = 1.1; -- should fail
+UPDATE rw_view1 SET a = 1.05 WHERE a = 1.1 RETURNING s; -- OK
+DELETE FROM rw_view1 WHERE a = 1.05; -- OK
+
+CREATE VIEW rw_view2 AS
+ SELECT s, c, s/c t, a base_a, ctid
+ FROM rw_view1;
+
+INSERT INTO rw_view2 VALUES (null, null, null, 1.1, null); -- should fail
+INSERT INTO rw_view2(s, c, base_a) VALUES (null, null, 1.1); -- should fail
+INSERT INTO rw_view2(base_a) VALUES (1.1) RETURNING t; -- OK
+UPDATE rw_view2 SET s = s WHERE base_a = 1.1; -- should fail
+UPDATE rw_view2 SET t = t WHERE base_a = 1.1; -- should fail
+UPDATE rw_view2 SET base_a = 1.05 WHERE base_a = 1.1; -- OK
+DELETE FROM rw_view2 WHERE base_a = 1.05 RETURNING base_a, s, c, t; -- OK
+
+CREATE VIEW rw_view3 AS
+ SELECT s, c, s/c t, ctid
+ FROM rw_view1;
+
+INSERT INTO rw_view3 VALUES (null, null, null, null); -- should fail
+INSERT INTO rw_view3(s) VALUES (null); -- should fail
+UPDATE rw_view3 SET s = s; -- should fail
+DELETE FROM rw_view3 WHERE s = sin(0.1); -- should be OK
+SELECT * FROM base_tbl ORDER BY a;
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name LIKE E'r_\\_view%'
+ ORDER BY table_name;
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name LIKE E'r_\\_view%'
+ ORDER BY table_name;
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name LIKE E'r_\\_view%'
+ ORDER BY table_name, ordinal_position;
+
+SELECT events & 4 != 0 AS upd,
+ events & 8 != 0 AS ins,
+ events & 16 != 0 AS del
+ FROM pg_catalog.pg_relation_is_updatable('rw_view3'::regclass, false) t(events);
+
+DROP TABLE base_tbl CASCADE;
+
+-- view on table with GENERATED columns
+
+CREATE TABLE base_tbl (id int, idplus1 int GENERATED ALWAYS AS (id + 1) STORED);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl;
+
+INSERT INTO base_tbl (id) VALUES (1);
+INSERT INTO rw_view1 (id) VALUES (2);
+INSERT INTO base_tbl (id, idplus1) VALUES (3, DEFAULT);
+INSERT INTO rw_view1 (id, idplus1) VALUES (4, DEFAULT);
+INSERT INTO base_tbl (id, idplus1) VALUES (5, 6); -- error
+INSERT INTO rw_view1 (id, idplus1) VALUES (6, 7); -- error
+
+SELECT * FROM base_tbl;
+
+UPDATE base_tbl SET id = 2000 WHERE id = 2;
+UPDATE rw_view1 SET id = 3000 WHERE id = 3;
+
+SELECT * FROM base_tbl;
+
+DROP TABLE base_tbl CASCADE;
+
+-- inheritance tests
+
+CREATE TABLE base_tbl_parent (a int);
+CREATE TABLE base_tbl_child (CHECK (a > 0)) INHERITS (base_tbl_parent);
+INSERT INTO base_tbl_parent SELECT * FROM generate_series(-8, -1);
+INSERT INTO base_tbl_child SELECT * FROM generate_series(1, 8);
+
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl_parent;
+CREATE VIEW rw_view2 AS SELECT * FROM ONLY base_tbl_parent;
+
+SELECT * FROM rw_view1 ORDER BY a;
+SELECT * FROM ONLY rw_view1 ORDER BY a;
+SELECT * FROM rw_view2 ORDER BY a;
+
+INSERT INTO rw_view1 VALUES (-100), (100);
+INSERT INTO rw_view2 VALUES (-200), (200);
+
+UPDATE rw_view1 SET a = a*10 WHERE a IN (-1, 1); -- Should produce -10 and 10
+UPDATE ONLY rw_view1 SET a = a*10 WHERE a IN (-2, 2); -- Should produce -20 and 20
+UPDATE rw_view2 SET a = a*10 WHERE a IN (-3, 3); -- Should produce -30 only
+UPDATE ONLY rw_view2 SET a = a*10 WHERE a IN (-4, 4); -- Should produce -40 only
+
+DELETE FROM rw_view1 WHERE a IN (-5, 5); -- Should delete -5 and 5
+DELETE FROM ONLY rw_view1 WHERE a IN (-6, 6); -- Should delete -6 and 6
+DELETE FROM rw_view2 WHERE a IN (-7, 7); -- Should delete -7 only
+DELETE FROM ONLY rw_view2 WHERE a IN (-8, 8); -- Should delete -8 only
+
+SELECT * FROM ONLY base_tbl_parent ORDER BY a;
+SELECT * FROM base_tbl_child ORDER BY a;
+
+CREATE TABLE other_tbl_parent (id int);
+CREATE TABLE other_tbl_child () INHERITS (other_tbl_parent);
+INSERT INTO other_tbl_parent VALUES (7),(200);
+INSERT INTO other_tbl_child VALUES (8),(100);
+
+EXPLAIN (costs off)
+UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
+UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
+
+SELECT * FROM ONLY base_tbl_parent ORDER BY a;
+SELECT * FROM base_tbl_child ORDER BY a;
+
+DROP TABLE base_tbl_parent, base_tbl_child CASCADE;
+DROP TABLE other_tbl_parent CASCADE;
+
+-- simple WITH CHECK OPTION
+
+CREATE TABLE base_tbl (a int, b int DEFAULT 10);
+INSERT INTO base_tbl VALUES (1,2), (2,3), (1,-1);
+
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
+ WITH LOCAL CHECK OPTION;
+\d+ rw_view1
+SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
+
+INSERT INTO rw_view1 VALUES(3,4); -- ok
+INSERT INTO rw_view1 VALUES(4,3); -- should fail
+INSERT INTO rw_view1 VALUES(5,null); -- should fail
+UPDATE rw_view1 SET b = 5 WHERE a = 3; -- ok
+UPDATE rw_view1 SET b = -5 WHERE a = 3; -- should fail
+INSERT INTO rw_view1(a) VALUES (9); -- ok
+INSERT INTO rw_view1(a) VALUES (10); -- should fail
+SELECT * FROM base_tbl;
+
+DROP TABLE base_tbl CASCADE;
+
+-- WITH LOCAL/CASCADED CHECK OPTION
+
+CREATE TABLE base_tbl (a int);
+
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a > 0;
+CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
+ WITH CHECK OPTION; -- implicitly cascaded
+\d+ rw_view2
+SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
+
+INSERT INTO rw_view2 VALUES (-5); -- should fail
+INSERT INTO rw_view2 VALUES (5); -- ok
+INSERT INTO rw_view2 VALUES (15); -- should fail
+SELECT * FROM base_tbl;
+
+UPDATE rw_view2 SET a = a - 10; -- should fail
+UPDATE rw_view2 SET a = a + 10; -- should fail
+
+CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
+ WITH LOCAL CHECK OPTION;
+\d+ rw_view2
+SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
+
+INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
+INSERT INTO rw_view2 VALUES (20); -- should fail
+SELECT * FROM base_tbl;
+
+ALTER VIEW rw_view1 SET (check_option=here); -- invalid
+ALTER VIEW rw_view1 SET (check_option=local);
+
+INSERT INTO rw_view2 VALUES (-20); -- should fail
+INSERT INTO rw_view2 VALUES (30); -- should fail
+
+ALTER VIEW rw_view2 RESET (check_option);
+\d+ rw_view2
+SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
+INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
+SELECT * FROM base_tbl;
+
+DROP TABLE base_tbl CASCADE;
+
+-- WITH CHECK OPTION with no local view qual
+
+CREATE TABLE base_tbl (a int);
+
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
+CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
+CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
+SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
+
+INSERT INTO rw_view1 VALUES (-1); -- ok
+INSERT INTO rw_view1 VALUES (1); -- ok
+INSERT INTO rw_view2 VALUES (-2); -- ok, but not in view
+INSERT INTO rw_view2 VALUES (2); -- ok
+INSERT INTO rw_view3 VALUES (-3); -- should fail
+INSERT INTO rw_view3 VALUES (3); -- ok
+
+DROP TABLE base_tbl CASCADE;
+
+-- WITH CHECK OPTION with scalar array ops
+
+CREATE TABLE base_tbl (a int, b int[]);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a = ANY (b)
+ WITH CHECK OPTION;
+
+INSERT INTO rw_view1 VALUES (1, ARRAY[1,2,3]); -- ok
+INSERT INTO rw_view1 VALUES (10, ARRAY[4,5]); -- should fail
+
+UPDATE rw_view1 SET b[2] = -b[2] WHERE a = 1; -- ok
+UPDATE rw_view1 SET b[1] = -b[1] WHERE a = 1; -- should fail
+
+PREPARE ins(int, int[]) AS INSERT INTO rw_view1 VALUES($1, $2);
+EXECUTE ins(2, ARRAY[1,2,3]); -- ok
+EXECUTE ins(10, ARRAY[4,5]); -- should fail
+DEALLOCATE PREPARE ins;
+
+DROP TABLE base_tbl CASCADE;
+
+-- WITH CHECK OPTION with subquery
+
+CREATE TABLE base_tbl (a int);
+CREATE TABLE ref_tbl (a int PRIMARY KEY);
+INSERT INTO ref_tbl SELECT * FROM generate_series(1,10);
+
+CREATE VIEW rw_view1 AS
+ SELECT * FROM base_tbl b
+ WHERE EXISTS(SELECT 1 FROM ref_tbl r WHERE r.a = b.a)
+ WITH CHECK OPTION;
+
+INSERT INTO rw_view1 VALUES (5); -- ok
+INSERT INTO rw_view1 VALUES (15); -- should fail
+
+UPDATE rw_view1 SET a = a + 5; -- ok
+UPDATE rw_view1 SET a = a + 5; -- should fail
+
+EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
+EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
+
+DROP TABLE base_tbl, ref_tbl CASCADE;
+
+-- WITH CHECK OPTION with BEFORE trigger on base table
+
+CREATE TABLE base_tbl (a int, b int);
+
+CREATE FUNCTION base_tbl_trig_fn()
+RETURNS trigger AS
+$$
+BEGIN
+ NEW.b := 10;
+ RETURN NEW;
+END;
+$$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER base_tbl_trig BEFORE INSERT OR UPDATE ON base_tbl
+ FOR EACH ROW EXECUTE PROCEDURE base_tbl_trig_fn();
+
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b WITH CHECK OPTION;
+
+INSERT INTO rw_view1 VALUES (5,0); -- ok
+INSERT INTO rw_view1 VALUES (15, 20); -- should fail
+UPDATE rw_view1 SET a = 20, b = 30; -- should fail
+
+DROP TABLE base_tbl CASCADE;
+DROP FUNCTION base_tbl_trig_fn();
+
+-- WITH LOCAL CHECK OPTION with INSTEAD OF trigger on base view
+
+CREATE TABLE base_tbl (a int, b int);
+
+CREATE VIEW rw_view1 AS SELECT a FROM base_tbl WHERE a < b;
+
+CREATE FUNCTION rw_view1_trig_fn()
+RETURNS trigger AS
+$$
+BEGIN
+ IF TG_OP = 'INSERT' THEN
+ INSERT INTO base_tbl VALUES (NEW.a, 10);
+ RETURN NEW;
+ ELSIF TG_OP = 'UPDATE' THEN
+ UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
+ RETURN NEW;
+ ELSIF TG_OP = 'DELETE' THEN
+ DELETE FROM base_tbl WHERE a=OLD.a;
+ RETURN OLD;
+ END IF;
+END;
+$$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER rw_view1_trig
+ INSTEAD OF INSERT OR UPDATE OR DELETE ON rw_view1
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+
+CREATE VIEW rw_view2 AS
+ SELECT * FROM rw_view1 WHERE a > 0 WITH LOCAL CHECK OPTION;
+
+INSERT INTO rw_view2 VALUES (-5); -- should fail
+INSERT INTO rw_view2 VALUES (5); -- ok
+INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
+UPDATE rw_view2 SET a = a - 10; -- should fail
+SELECT * FROM base_tbl;
+
+-- Check option won't cascade down to base view with INSTEAD OF triggers
+
+ALTER VIEW rw_view2 SET (check_option=cascaded);
+INSERT INTO rw_view2 VALUES (100); -- ok, but not in view (doesn't fail rw_view1's check)
+UPDATE rw_view2 SET a = 200 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
+SELECT * FROM base_tbl;
+
+-- Neither local nor cascaded check options work with INSTEAD rules
+
+DROP TRIGGER rw_view1_trig ON rw_view1;
+CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, 10);
+CREATE RULE rw_view1_upd_rule AS ON UPDATE TO rw_view1
+ DO INSTEAD UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
+INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view (doesn't fail rw_view2's check)
+INSERT INTO rw_view2 VALUES (5); -- ok
+INSERT INTO rw_view2 VALUES (20); -- ok, but not in view (doesn't fail rw_view1's check)
+UPDATE rw_view2 SET a = 30 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
+INSERT INTO rw_view2 VALUES (5); -- ok
+UPDATE rw_view2 SET a = -5 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view2's check)
+SELECT * FROM base_tbl;
+
+DROP TABLE base_tbl CASCADE;
+DROP FUNCTION rw_view1_trig_fn();
+
+CREATE TABLE base_tbl (a int);
+CREATE VIEW rw_view1 AS SELECT a,10 AS b FROM base_tbl;
+CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a);
+CREATE VIEW rw_view2 AS
+ SELECT * FROM rw_view1 WHERE a > b WITH LOCAL CHECK OPTION;
+INSERT INTO rw_view2 VALUES (2,3); -- ok, but not in view (doesn't fail rw_view2's check)
+DROP TABLE base_tbl CASCADE;
+
+-- security barrier view
+
+CREATE TABLE base_tbl (person text, visibility text);
+INSERT INTO base_tbl VALUES ('Tom', 'public'),
+ ('Dick', 'private'),
+ ('Harry', 'public');
+
+CREATE VIEW rw_view1 AS
+ SELECT person FROM base_tbl WHERE visibility = 'public';
+
+CREATE FUNCTION snoop(anyelement)
+RETURNS boolean AS
+$$
+BEGIN
+ RAISE NOTICE 'snooped value: %', $1;
+ RETURN true;
+END;
+$$
+LANGUAGE plpgsql COST 0.000001;
+
+CREATE OR REPLACE FUNCTION leakproof(anyelement)
+RETURNS boolean AS
+$$
+BEGIN
+ RETURN true;
+END;
+$$
+LANGUAGE plpgsql STRICT IMMUTABLE LEAKPROOF;
+
+SELECT * FROM rw_view1 WHERE snoop(person);
+UPDATE rw_view1 SET person=person WHERE snoop(person);
+DELETE FROM rw_view1 WHERE NOT snoop(person);
+
+ALTER VIEW rw_view1 SET (security_barrier = true);
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name = 'rw_view1';
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name = 'rw_view1';
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name = 'rw_view1'
+ ORDER BY ordinal_position;
+
+SELECT * FROM rw_view1 WHERE snoop(person);
+UPDATE rw_view1 SET person=person WHERE snoop(person);
+DELETE FROM rw_view1 WHERE NOT snoop(person);
+
+EXPLAIN (costs off) SELECT * FROM rw_view1 WHERE snoop(person);
+EXPLAIN (costs off) UPDATE rw_view1 SET person=person WHERE snoop(person);
+EXPLAIN (costs off) DELETE FROM rw_view1 WHERE NOT snoop(person);
+
+-- security barrier view on top of security barrier view
+
+CREATE VIEW rw_view2 WITH (security_barrier = true) AS
+ SELECT * FROM rw_view1 WHERE snoop(person);
+
+SELECT table_name, is_insertable_into
+ FROM information_schema.tables
+ WHERE table_name = 'rw_view2';
+
+SELECT table_name, is_updatable, is_insertable_into
+ FROM information_schema.views
+ WHERE table_name = 'rw_view2';
+
+SELECT table_name, column_name, is_updatable
+ FROM information_schema.columns
+ WHERE table_name = 'rw_view2'
+ ORDER BY ordinal_position;
+
+SELECT * FROM rw_view2 WHERE snoop(person);
+UPDATE rw_view2 SET person=person WHERE snoop(person);
+DELETE FROM rw_view2 WHERE NOT snoop(person);
+
+EXPLAIN (costs off) SELECT * FROM rw_view2 WHERE snoop(person);
+EXPLAIN (costs off) UPDATE rw_view2 SET person=person WHERE snoop(person);
+EXPLAIN (costs off) DELETE FROM rw_view2 WHERE NOT snoop(person);
+
+DROP TABLE base_tbl CASCADE;
+
+-- security barrier view on top of table with rules
+
+CREATE TABLE base_tbl(id int PRIMARY KEY, data text, deleted boolean);
+INSERT INTO base_tbl VALUES (1, 'Row 1', false), (2, 'Row 2', true);
+
+CREATE RULE base_tbl_ins_rule AS ON INSERT TO base_tbl
+ WHERE EXISTS (SELECT 1 FROM base_tbl t WHERE t.id = new.id)
+ DO INSTEAD
+ UPDATE base_tbl SET data = new.data, deleted = false WHERE id = new.id;
+
+CREATE RULE base_tbl_del_rule AS ON DELETE TO base_tbl
+ DO INSTEAD
+ UPDATE base_tbl SET deleted = true WHERE id = old.id;
+
+CREATE VIEW rw_view1 WITH (security_barrier=true) AS
+ SELECT id, data FROM base_tbl WHERE NOT deleted;
+
+SELECT * FROM rw_view1;
+
+EXPLAIN (costs off) DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
+DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
+
+EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (2, 'New row 2');
+INSERT INTO rw_view1 VALUES (2, 'New row 2');
+
+SELECT * FROM base_tbl;
+
+DROP TABLE base_tbl CASCADE;
+
+-- security barrier view based on inheritance set
+CREATE TABLE t1 (a int, b float, c text);
+CREATE INDEX t1_a_idx ON t1(a);
+INSERT INTO t1
+SELECT i,i,'t1' FROM generate_series(1,10) g(i);
+ANALYZE t1;
+
+CREATE TABLE t11 (d text) INHERITS (t1);
+CREATE INDEX t11_a_idx ON t11(a);
+INSERT INTO t11
+SELECT i,i,'t11','t11d' FROM generate_series(1,10) g(i);
+ANALYZE t11;
+
+CREATE TABLE t12 (e int[]) INHERITS (t1);
+CREATE INDEX t12_a_idx ON t12(a);
+INSERT INTO t12
+SELECT i,i,'t12','{1,2}'::int[] FROM generate_series(1,10) g(i);
+ANALYZE t12;
+
+CREATE TABLE t111 () INHERITS (t11, t12);
+CREATE INDEX t111_a_idx ON t111(a);
+INSERT INTO t111
+SELECT i,i,'t111','t111d','{1,1,1}'::int[] FROM generate_series(1,10) g(i);
+ANALYZE t111;
+
+CREATE VIEW v1 WITH (security_barrier=true) AS
+SELECT *, (SELECT d FROM t11 WHERE t11.a = t1.a LIMIT 1) AS d
+FROM t1
+WHERE a > 5 AND EXISTS(SELECT 1 FROM t12 WHERE t12.a = t1.a);
+
+SELECT * FROM v1 WHERE a=3; -- should not see anything
+SELECT * FROM v1 WHERE a=8;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
+UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
+
+SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100
+SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100
+
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
+UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
+
+SELECT * FROM v1 WHERE b=8;
+
+DELETE FROM v1 WHERE snoop(a) AND leakproof(a); -- should not delete everything, just where a>5
+
+TABLE t1; -- verify all a<=5 are intact
+
+DROP TABLE t1, t11, t12, t111 CASCADE;
+DROP FUNCTION snoop(anyelement);
+DROP FUNCTION leakproof(anyelement);
+
+CREATE TABLE tx1 (a integer);
+CREATE TABLE tx2 (b integer);
+CREATE TABLE tx3 (c integer);
+CREATE VIEW vx1 AS SELECT a FROM tx1 WHERE EXISTS(SELECT 1 FROM tx2 JOIN tx3 ON b=c);
+INSERT INTO vx1 values (1);
+SELECT * FROM tx1;
+SELECT * FROM vx1;
+
+DROP VIEW vx1;
+DROP TABLE tx1;
+DROP TABLE tx2;
+DROP TABLE tx3;
+
+CREATE TABLE tx1 (a integer);
+CREATE TABLE tx2 (b integer);
+CREATE TABLE tx3 (c integer);
+CREATE VIEW vx1 AS SELECT a FROM tx1 WHERE EXISTS(SELECT 1 FROM tx2 JOIN tx3 ON b=c);
+INSERT INTO vx1 VALUES (1);
+INSERT INTO vx1 VALUES (1);
+SELECT * FROM tx1;
+SELECT * FROM vx1;
+
+DROP VIEW vx1;
+DROP TABLE tx1;
+DROP TABLE tx2;
+DROP TABLE tx3;
+
+CREATE TABLE tx1 (a integer, b integer);
+CREATE TABLE tx2 (b integer, c integer);
+CREATE TABLE tx3 (c integer, d integer);
+ALTER TABLE tx1 DROP COLUMN b;
+ALTER TABLE tx2 DROP COLUMN c;
+ALTER TABLE tx3 DROP COLUMN d;
+CREATE VIEW vx1 AS SELECT a FROM tx1 WHERE EXISTS(SELECT 1 FROM tx2 JOIN tx3 ON b=c);
+INSERT INTO vx1 VALUES (1);
+INSERT INTO vx1 VALUES (1);
+SELECT * FROM tx1;
+SELECT * FROM vx1;
+
+DROP VIEW vx1;
+DROP TABLE tx1;
+DROP TABLE tx2;
+DROP TABLE tx3;
+
+--
+-- Test handling of vars from correlated subqueries in quals from outer
+-- security barrier views, per bug #13988
+--
+CREATE TABLE t1 (a int, b text, c int);
+INSERT INTO t1 VALUES (1, 'one', 10);
+
+CREATE TABLE t2 (cc int);
+INSERT INTO t2 VALUES (10), (20);
+
+CREATE VIEW v1 WITH (security_barrier = true) AS
+ SELECT * FROM t1 WHERE (a > 0)
+ WITH CHECK OPTION;
+
+CREATE VIEW v2 WITH (security_barrier = true) AS
+ SELECT * FROM v1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.cc = v1.c)
+ WITH CHECK OPTION;
+
+INSERT INTO v2 VALUES (2, 'two', 20); -- ok
+INSERT INTO v2 VALUES (-2, 'minus two', 20); -- not allowed
+INSERT INTO v2 VALUES (3, 'three', 30); -- not allowed
+
+UPDATE v2 SET b = 'ONE' WHERE a = 1; -- ok
+UPDATE v2 SET a = -1 WHERE a = 1; -- not allowed
+UPDATE v2 SET c = 30 WHERE a = 1; -- not allowed
+
+DELETE FROM v2 WHERE a = 2; -- ok
+SELECT * FROM v2;
+
+DROP VIEW v2;
+DROP VIEW v1;
+DROP TABLE t2;
+DROP TABLE t1;
+
+--
+-- Test CREATE OR REPLACE VIEW turning a non-updatable view into an
+-- auto-updatable view and adding check options in a single step
+--
+CREATE TABLE t1 (a int, b text);
+CREATE VIEW v1 AS SELECT null::int AS a;
+CREATE OR REPLACE VIEW v1 AS SELECT * FROM t1 WHERE a > 0 WITH CHECK OPTION;
+
+INSERT INTO v1 VALUES (1, 'ok'); -- ok
+INSERT INTO v1 VALUES (-1, 'invalid'); -- should fail
+
+DROP VIEW v1;
+DROP TABLE t1;
+
+-- check that an auto-updatable view on a partitioned table works correctly
+create table uv_pt (a int, b int, v varchar) partition by range (a, b);
+create table uv_pt1 (b int not null, v varchar, a int not null) partition by range (b);
+create table uv_pt11 (like uv_pt1);
+alter table uv_pt11 drop a;
+alter table uv_pt11 add a int;
+alter table uv_pt11 drop a;
+alter table uv_pt11 add a int not null;
+alter table uv_pt1 attach partition uv_pt11 for values from (2) to (5);
+alter table uv_pt attach partition uv_pt1 for values from (1, 2) to (1, 10);
+
+create view uv_ptv as select * from uv_pt;
+select events & 4 != 0 AS upd,
+ events & 8 != 0 AS ins,
+ events & 16 != 0 AS del
+ from pg_catalog.pg_relation_is_updatable('uv_pt'::regclass, false) t(events);
+select pg_catalog.pg_column_is_updatable('uv_pt'::regclass, 1::smallint, false);
+select pg_catalog.pg_column_is_updatable('uv_pt'::regclass, 2::smallint, false);
+select table_name, is_updatable, is_insertable_into
+ from information_schema.views where table_name = 'uv_ptv';
+select table_name, column_name, is_updatable
+ from information_schema.columns where table_name = 'uv_ptv' order by column_name;
+insert into uv_ptv values (1, 2);
+select tableoid::regclass, * from uv_pt;
+create view uv_ptv_wco as select * from uv_pt where a = 0 with check option;
+insert into uv_ptv_wco values (1, 2);
+drop view uv_ptv, uv_ptv_wco;
+drop table uv_pt, uv_pt1, uv_pt11;
+
+-- check that wholerow vars appearing in WITH CHECK OPTION constraint expressions
+-- work fine with partitioned tables
+create table wcowrtest (a int) partition by list (a);
+create table wcowrtest1 partition of wcowrtest for values in (1);
+create view wcowrtest_v as select * from wcowrtest where wcowrtest = '(2)'::wcowrtest with check option;
+insert into wcowrtest_v values (1);
+
+alter table wcowrtest add b text;
+create table wcowrtest2 (b text, c int, a int);
+alter table wcowrtest2 drop c;
+alter table wcowrtest attach partition wcowrtest2 for values in (2);
+
+create table sometable (a int, b text);
+insert into sometable values (1, 'a'), (2, 'b');
+create view wcowrtest_v2 as
+ select *
+ from wcowrtest r
+ where r in (select s from sometable s where r.a = s.a)
+with check option;
+
+-- WITH CHECK qual will be processed with wcowrtest2's
+-- rowtype after tuple-routing
+insert into wcowrtest_v2 values (2, 'no such row in sometable');
+
+drop view wcowrtest_v, wcowrtest_v2;
+drop table wcowrtest, sometable;
+
+-- Check INSERT .. ON CONFLICT DO UPDATE works correctly when the view's
+-- columns are named and ordered differently than the underlying table's.
+create table uv_iocu_tab (a text unique, b float);
+insert into uv_iocu_tab values ('xyxyxy', 0);
+create view uv_iocu_view as
+ select b, b+1 as c, a, '2.0'::text as two from uv_iocu_tab;
+
+insert into uv_iocu_view (a, b) values ('xyxyxy', 1)
+ on conflict (a) do update set b = uv_iocu_view.b;
+select * from uv_iocu_tab;
+insert into uv_iocu_view (a, b) values ('xyxyxy', 1)
+ on conflict (a) do update set b = excluded.b;
+select * from uv_iocu_tab;
+
+-- OK to access view columns that are not present in underlying base
+-- relation in the ON CONFLICT portion of the query
+insert into uv_iocu_view (a, b) values ('xyxyxy', 3)
+ on conflict (a) do update set b = cast(excluded.two as float);
+select * from uv_iocu_tab;
+
+explain (costs off)
+insert into uv_iocu_view (a, b) values ('xyxyxy', 3)
+ on conflict (a) do update set b = excluded.b where excluded.c > 0;
+
+insert into uv_iocu_view (a, b) values ('xyxyxy', 3)
+ on conflict (a) do update set b = excluded.b where excluded.c > 0;
+select * from uv_iocu_tab;
+
+drop view uv_iocu_view;
+drop table uv_iocu_tab;
+
+-- Test whole-row references to the view
+create table uv_iocu_tab (a int unique, b text);
+create view uv_iocu_view as
+ select b as bb, a as aa, uv_iocu_tab::text as cc from uv_iocu_tab;
+
+insert into uv_iocu_view (aa,bb) values (1,'x');
+explain (costs off)
+insert into uv_iocu_view (aa,bb) values (1,'y')
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*
+ where excluded.aa > 0
+ and excluded.bb != ''
+ and excluded.cc is not null;
+insert into uv_iocu_view (aa,bb) values (1,'y')
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*
+ where excluded.aa > 0
+ and excluded.bb != ''
+ and excluded.cc is not null;
+select * from uv_iocu_view;
+
+-- Test omitting a column of the base relation
+delete from uv_iocu_view;
+insert into uv_iocu_view (aa,bb) values (1,'x');
+insert into uv_iocu_view (aa) values (1)
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*;
+select * from uv_iocu_view;
+
+alter table uv_iocu_tab alter column b set default 'table default';
+insert into uv_iocu_view (aa) values (1)
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*;
+select * from uv_iocu_view;
+
+alter view uv_iocu_view alter column bb set default 'view default';
+insert into uv_iocu_view (aa) values (1)
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*;
+select * from uv_iocu_view;
+
+-- Should fail to update non-updatable columns
+insert into uv_iocu_view (aa) values (1)
+ on conflict (aa) do update set cc = 'XXX';
+
+drop view uv_iocu_view;
+drop table uv_iocu_tab;
+
+-- ON CONFLICT DO UPDATE permissions checks
+create user regress_view_user1;
+create user regress_view_user2;
+
+set session authorization regress_view_user1;
+create table base_tbl(a int unique, b text, c float);
+insert into base_tbl values (1,'xxx',1.0);
+create view rw_view1 as select b as bb, c as cc, a as aa from base_tbl;
+
+grant select (aa,bb) on rw_view1 to regress_view_user2;
+grant insert on rw_view1 to regress_view_user2;
+grant update (bb) on rw_view1 to regress_view_user2;
+
+set session authorization regress_view_user2;
+insert into rw_view1 values ('yyy',2.0,1)
+ on conflict (aa) do update set bb = excluded.cc; -- Not allowed
+insert into rw_view1 values ('yyy',2.0,1)
+ on conflict (aa) do update set bb = rw_view1.cc; -- Not allowed
+insert into rw_view1 values ('yyy',2.0,1)
+ on conflict (aa) do update set bb = excluded.bb; -- OK
+insert into rw_view1 values ('zzz',2.0,1)
+ on conflict (aa) do update set bb = rw_view1.bb||'xxx'; -- OK
+insert into rw_view1 values ('zzz',2.0,1)
+ on conflict (aa) do update set cc = 3.0; -- Not allowed
+reset session authorization;
+select * from base_tbl;
+
+set session authorization regress_view_user1;
+grant select (a,b) on base_tbl to regress_view_user2;
+grant insert (a,b) on base_tbl to regress_view_user2;
+grant update (a,b) on base_tbl to regress_view_user2;
+
+set session authorization regress_view_user2;
+create view rw_view2 as select b as bb, c as cc, a as aa from base_tbl;
+insert into rw_view2 (aa,bb) values (1,'xxx')
+ on conflict (aa) do update set bb = excluded.bb; -- Not allowed
+create view rw_view3 as select b as bb, a as aa from base_tbl;
+insert into rw_view3 (aa,bb) values (1,'xxx')
+ on conflict (aa) do update set bb = excluded.bb; -- OK
+reset session authorization;
+select * from base_tbl;
+
+set session authorization regress_view_user2;
+create view rw_view4 as select aa, bb, cc FROM rw_view1;
+insert into rw_view4 (aa,bb) values (1,'yyy')
+ on conflict (aa) do update set bb = excluded.bb; -- Not allowed
+create view rw_view5 as select aa, bb FROM rw_view1;
+insert into rw_view5 (aa,bb) values (1,'yyy')
+ on conflict (aa) do update set bb = excluded.bb; -- OK
+reset session authorization;
+select * from base_tbl;
+
+drop view rw_view5;
+drop view rw_view4;
+drop view rw_view3;
+drop view rw_view2;
+drop view rw_view1;
+drop table base_tbl;
+drop user regress_view_user1;
+drop user regress_view_user2;
+
+-- Test single- and multi-row inserts with table and view defaults.
+-- Table defaults should be used, unless overridden by view defaults.
+create table base_tab_def (a int, b text default 'Table default',
+ c text default 'Table default', d text, e text);
+create view base_tab_def_view as select * from base_tab_def;
+alter view base_tab_def_view alter b set default 'View default';
+alter view base_tab_def_view alter d set default 'View default';
+insert into base_tab_def values (1);
+insert into base_tab_def values (2), (3);
+insert into base_tab_def values (4, default, default, default, default);
+insert into base_tab_def values (5, default, default, default, default),
+ (6, default, default, default, default);
+insert into base_tab_def_view values (11);
+insert into base_tab_def_view values (12), (13);
+insert into base_tab_def_view values (14, default, default, default, default);
+insert into base_tab_def_view values (15, default, default, default, default),
+ (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
+select * from base_tab_def order by a;
+
+-- Adding an INSTEAD OF trigger should cause NULLs to be inserted instead of
+-- table defaults, where there are no view defaults.
+create function base_tab_def_view_instrig_func() returns trigger
+as
+$$
+begin
+ insert into base_tab_def values (new.a, new.b, new.c, new.d, new.e);
+ return new;
+end;
+$$
+language plpgsql;
+create trigger base_tab_def_view_instrig instead of insert on base_tab_def_view
+ for each row execute function base_tab_def_view_instrig_func();
+truncate base_tab_def;
+insert into base_tab_def values (1);
+insert into base_tab_def values (2), (3);
+insert into base_tab_def values (4, default, default, default, default);
+insert into base_tab_def values (5, default, default, default, default),
+ (6, default, default, default, default);
+insert into base_tab_def_view values (11);
+insert into base_tab_def_view values (12), (13);
+insert into base_tab_def_view values (14, default, default, default, default);
+insert into base_tab_def_view values (15, default, default, default, default),
+ (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
+select * from base_tab_def order by a;
+
+-- Using an unconditional DO INSTEAD rule should also cause NULLs to be
+-- inserted where there are no view defaults.
+drop trigger base_tab_def_view_instrig on base_tab_def_view;
+drop function base_tab_def_view_instrig_func;
+create rule base_tab_def_view_ins_rule as on insert to base_tab_def_view
+ do instead insert into base_tab_def values (new.a, new.b, new.c, new.d, new.e);
+truncate base_tab_def;
+insert into base_tab_def values (1);
+insert into base_tab_def values (2), (3);
+insert into base_tab_def values (4, default, default, default, default);
+insert into base_tab_def values (5, default, default, default, default),
+ (6, default, default, default, default);
+insert into base_tab_def_view values (11);
+insert into base_tab_def_view values (12), (13);
+insert into base_tab_def_view values (14, default, default, default, default);
+insert into base_tab_def_view values (15, default, default, default, default),
+ (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
+select * from base_tab_def order by a;
+
+-- A DO ALSO rule should cause each row to be inserted twice. The first
+-- insert should behave the same as an auto-updatable view (using table
+-- defaults, unless overridden by view defaults). The second insert should
+-- behave the same as a rule-updatable view (inserting NULLs where there are
+-- no view defaults).
+drop rule base_tab_def_view_ins_rule on base_tab_def_view;
+create rule base_tab_def_view_ins_rule as on insert to base_tab_def_view
+ do also insert into base_tab_def values (new.a, new.b, new.c, new.d, new.e);
+truncate base_tab_def;
+insert into base_tab_def values (1);
+insert into base_tab_def values (2), (3);
+insert into base_tab_def values (4, default, default, default, default);
+insert into base_tab_def values (5, default, default, default, default),
+ (6, default, default, default, default);
+insert into base_tab_def_view values (11);
+insert into base_tab_def_view values (12), (13);
+insert into base_tab_def_view values (14, default, default, default, default);
+insert into base_tab_def_view values (15, default, default, default, default),
+ (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
+select * from base_tab_def order by a, c NULLS LAST;
+
+drop view base_tab_def_view;
+drop table base_tab_def;
+
+-- Test defaults with array assignments
+create table base_tab (a serial, b int[], c text, d text default 'Table default');
+create view base_tab_view as select c, a, b from base_tab;
+alter view base_tab_view alter column c set default 'View default';
+insert into base_tab_view (b[1], b[2], c, b[5], b[4], a, b[3])
+values (1, 2, default, 5, 4, default, 3), (10, 11, 'C value', 14, 13, 100, 12);
+select * from base_tab order by a;
+drop view base_tab_view;
+drop table base_tab;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
new file mode 100644
index 0000000..7a7bee7
--- /dev/null
+++ b/src/test/regress/sql/update.sql
@@ -0,0 +1,669 @@
+--
+-- UPDATE syntax tests
+--
+
+CREATE TABLE update_test (
+ a INT DEFAULT 10,
+ b INT,
+ c TEXT
+);
+
+CREATE TABLE upsert_test (
+ a INT PRIMARY KEY,
+ b TEXT
+);
+
+INSERT INTO update_test VALUES (5, 10, 'foo');
+INSERT INTO update_test(b, a) VALUES (15, 10);
+
+SELECT * FROM update_test;
+
+UPDATE update_test SET a = DEFAULT, b = DEFAULT;
+
+SELECT * FROM update_test;
+
+-- aliases for the UPDATE target table
+UPDATE update_test AS t SET b = 10 WHERE t.a = 10;
+
+SELECT * FROM update_test;
+
+UPDATE update_test t SET b = t.b + 10 WHERE t.a = 10;
+
+SELECT * FROM update_test;
+
+--
+-- Test VALUES in FROM
+--
+
+UPDATE update_test SET a=v.i FROM (VALUES(100, 20)) AS v(i, j)
+ WHERE update_test.b = v.j;
+
+SELECT * FROM update_test;
+
+-- fail, wrong data type:
+UPDATE update_test SET a = v.* FROM (VALUES(100, 20)) AS v(i, j)
+ WHERE update_test.b = v.j;
+
+--
+-- Test multiple-set-clause syntax
+--
+
+INSERT INTO update_test SELECT a,b+1,c FROM update_test;
+SELECT * FROM update_test;
+
+UPDATE update_test SET (c,b,a) = ('bugle', b+11, DEFAULT) WHERE c = 'foo';
+SELECT * FROM update_test;
+UPDATE update_test SET (c,b) = ('car', a+b), a = a + 1 WHERE a = 10;
+SELECT * FROM update_test;
+-- fail, multi assignment to same column:
+UPDATE update_test SET (c,b) = ('car', a+b), b = a + 1 WHERE a = 10;
+
+-- uncorrelated sub-select:
+UPDATE update_test
+ SET (b,a) = (select a,b from update_test where b = 41 and c = 'car')
+ WHERE a = 100 AND b = 20;
+SELECT * FROM update_test;
+-- correlated sub-select:
+UPDATE update_test o
+ SET (b,a) = (select a+1,b from update_test i
+ where i.a=o.a and i.b=o.b and i.c is not distinct from o.c);
+SELECT * FROM update_test;
+-- fail, multiple rows supplied:
+UPDATE update_test SET (b,a) = (select a+1,b from update_test);
+-- set to null if no rows supplied:
+UPDATE update_test SET (b,a) = (select a+1,b from update_test where a = 1000)
+ WHERE a = 11;
+SELECT * FROM update_test;
+-- *-expansion should work in this context:
+UPDATE update_test SET (a,b) = ROW(v.*) FROM (VALUES(21, 100)) AS v(i, j)
+ WHERE update_test.a = v.i;
+-- you might expect this to work, but syntactically it's not a RowExpr:
+UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 101)) AS v(i, j)
+ WHERE update_test.a = v.i;
+
+-- if an alias for the target table is specified, don't allow references
+-- to the original table name
+UPDATE update_test AS t SET b = update_test.b + 10 WHERE t.a = 10;
+
+-- Make sure that we can update to a TOASTed value.
+UPDATE update_test SET c = repeat('x', 10000) WHERE c = 'car';
+SELECT a, b, char_length(c) FROM update_test;
+
+-- Check multi-assignment with a Result node to handle a one-time filter.
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE update_test t
+ SET (a, b) = (SELECT b, a FROM update_test s WHERE s.a = t.a)
+ WHERE CURRENT_USER = SESSION_USER;
+UPDATE update_test t
+ SET (a, b) = (SELECT b, a FROM update_test s WHERE s.a = t.a)
+ WHERE CURRENT_USER = SESSION_USER;
+SELECT a, b, char_length(c) FROM update_test;
+
+-- Test ON CONFLICT DO UPDATE
+
+INSERT INTO upsert_test VALUES(1, 'Boo'), (3, '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:
+INSERT INTO upsert_test VALUES (1, 'Baz'), (3, 'Zaz') ON CONFLICT(a)
+ DO UPDATE SET (b, a) = (SELECT b || ', Correlated', a from upsert_test i WHERE i.a = upsert_test.a)
+ RETURNING *;
+-- correlated sub-select (EXCLUDED.* alias):
+INSERT INTO upsert_test VALUES (1, 'Bat'), (3, 'Zot') ON CONFLICT(a)
+ DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
+ RETURNING *;
+
+-- ON CONFLICT using system attributes in RETURNING, testing both the
+-- inserting and updating paths. See bug report at:
+-- https://www.postgresql.org/message-id/73436355-6432-49B1-92ED-1FE4F7E7E100%40finefun.com.au
+INSERT INTO upsert_test VALUES (2, 'Beeble') ON CONFLICT(a)
+ DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
+ RETURNING tableoid::regclass, xmin = pg_current_xact_id()::xid AS xmin_correct, xmax = 0 AS xmax_correct;
+-- currently xmax is set after a conflict - that's probably not good,
+-- but it seems worthwhile to have to be explicit if that changes.
+INSERT INTO upsert_test VALUES (2, 'Brox') ON CONFLICT(a)
+ DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
+ RETURNING tableoid::regclass, xmin = pg_current_xact_id()::xid AS xmin_correct, xmax = pg_current_xact_id()::xid AS xmax_correct;
+
+DROP TABLE update_test;
+DROP TABLE upsert_test;
+
+-- 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;
+-- 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';
+-- 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
+UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
+:show_data;
+
+-- fail, row movement happens only within the partition subtree.
+UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
+-- ok, row movement, with subset of rows moved into different partition.
+UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
+
+: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;
+-- 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
+UPDATE upview set a = 'b', b = 15, c = 120 WHERE b = 4;
+-- ok, row movement, check option passes
+UPDATE upview set a = 'b', b = 15 WHERE b = 4;
+
+:show_data;
+
+-- cleanup
+DROP VIEW upview;
+
+-- RETURNING having whole-row vars.
+:init_range_parted;
+UPDATE range_parted set c = 95 WHERE a = 'b' and b > 10 and c > 100 returning (range_parted), *;
+:show_data;
+
+
+-- 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;
+$$;
+
+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;
+
+-- Test retrieval of system columns with non-consistent partition row types.
+-- This is only partially supported, as seen in the results.
+
+create table utrtest (a int, b text) partition by list (a);
+create table utr1 (a int check (a in (1)), q text, b text);
+create table utr2 (a int check (a in (2)), b text);
+alter table utr1 drop column q;
+alter table utrtest attach partition utr1 for values in (1);
+alter table utrtest attach partition utr2 for values in (2);
+
+insert into utrtest values (1, 'foo')
+ returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok;
+insert into utrtest values (2, 'bar')
+ returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok; -- fails
+insert into utrtest values (2, 'bar')
+ returning *, tableoid::regclass;
+
+update utrtest set b = b || b from (values (1), (2)) s(x) where a = s.x
+ returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok;
+
+update utrtest set a = 3 - a from (values (1), (2)) s(x) where a = s.x
+ returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok; -- fails
+
+update utrtest set a = 3 - a from (values (1), (2)) s(x) where a = s.x
+ returning *, tableoid::regclass;
+
+delete from utrtest
+ returning *, tableoid::regclass, xmax = pg_current_xact_id()::xid as xmax_ok;
+
+drop table utrtest;
+
+
+--------------
+-- 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);
diff --git a/src/test/regress/sql/uuid.sql b/src/test/regress/sql/uuid.sql
new file mode 100644
index 0000000..3bd3b35
--- /dev/null
+++ b/src/test/regress/sql/uuid.sql
@@ -0,0 +1,85 @@
+-- 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');
+
+-- retrieving the inserted data
+SELECT guid_field FROM guid1;
+
+-- 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';
+
+-- btree and hash index creation test
+CREATE INDEX guid1_btree ON guid1 USING BTREE (guid_field);
+CREATE INDEX guid1_hash ON guid1 USING HASH (guid_field);
+
+-- unique index test
+CREATE UNIQUE INDEX guid1_unique_BTREE ON guid1 USING BTREE (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;
+
+-- generation test
+TRUNCATE guid1;
+INSERT INTO guid1 (guid_field) VALUES (gen_random_uuid());
+INSERT INTO guid1 (guid_field) VALUES (gen_random_uuid());
+SELECT count(DISTINCT guid_field) FROM guid1;
+
+-- clean up
+DROP TABLE guid1, guid2 CASCADE;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
new file mode 100644
index 0000000..9faa8a3
--- /dev/null
+++ b/src/test/regress/sql/vacuum.sql
@@ -0,0 +1,320 @@
+--
+-- VACUUM
+--
+
+CREATE TABLE vactst (i INT);
+INSERT INTO vactst VALUES (1);
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst VALUES (0);
+SELECT count(*) FROM vactst;
+DELETE FROM vactst WHERE i != 0;
+SELECT * FROM vactst;
+VACUUM FULL vactst;
+UPDATE vactst SET i = i + 1;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst SELECT * FROM vactst;
+INSERT INTO vactst VALUES (0);
+SELECT count(*) FROM vactst;
+DELETE FROM vactst WHERE i != 0;
+VACUUM (FULL) vactst;
+DELETE FROM vactst;
+SELECT * FROM vactst;
+
+VACUUM (FULL, FREEZE) vactst;
+VACUUM (ANALYZE, FULL) vactst;
+
+CREATE TABLE vaccluster (i INT PRIMARY KEY);
+ALTER TABLE vaccluster CLUSTER ON vaccluster_pkey;
+CLUSTER vaccluster;
+
+CREATE FUNCTION do_analyze() RETURNS VOID VOLATILE LANGUAGE SQL
+ AS 'ANALYZE pg_am';
+CREATE FUNCTION wrap_do_analyze(c INT) RETURNS INT IMMUTABLE LANGUAGE SQL
+ AS 'SELECT $1 FROM do_analyze()';
+CREATE INDEX ON vaccluster(wrap_do_analyze(i));
+INSERT INTO vaccluster VALUES (1), (2);
+ANALYZE vaccluster;
+
+-- Test ANALYZE in transaction, where the transaction surrounding
+-- analyze performed modifications. This tests for the bug at
+-- https://postgr.es/m/c7988239-d42c-ddc4-41db-171b23b35e4f%40ssinger.info
+-- (which hopefully is unlikely to be reintroduced), but also seems
+-- independently worthwhile to cover.
+INSERT INTO vactst SELECT generate_series(1, 300);
+DELETE FROM vactst WHERE i % 7 = 0; -- delete a few rows outside
+BEGIN;
+INSERT INTO vactst SELECT generate_series(301, 400);
+DELETE FROM vactst WHERE i % 5 <> 0; -- delete a few rows inside
+ANALYZE vactst;
+COMMIT;
+
+VACUUM FULL pg_am;
+VACUUM FULL pg_class;
+VACUUM FULL pg_database;
+VACUUM FULL vaccluster;
+VACUUM FULL vactst;
+
+VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
+
+-- PARALLEL option
+CREATE TABLE pvactst (i INT, a INT[], p POINT) with (autovacuum_enabled = off);
+INSERT INTO pvactst SELECT i, array[1,2,3], point(i, i+1) FROM generate_series(1,1000) i;
+CREATE INDEX btree_pvactst ON pvactst USING btree (i);
+CREATE INDEX hash_pvactst ON pvactst USING hash (i);
+CREATE INDEX brin_pvactst ON pvactst USING brin (i);
+CREATE INDEX gin_pvactst ON pvactst USING gin (a);
+CREATE INDEX gist_pvactst ON pvactst USING gist (p);
+CREATE INDEX spgist_pvactst ON pvactst USING spgist (p);
+
+-- VACUUM invokes parallel index cleanup
+SET min_parallel_index_scan_size to 0;
+VACUUM (PARALLEL 2) pvactst;
+
+-- VACUUM invokes parallel bulk-deletion
+UPDATE pvactst SET i = i WHERE i < 1000;
+VACUUM (PARALLEL 2) pvactst;
+
+UPDATE pvactst SET i = i WHERE i < 1000;
+VACUUM (PARALLEL 0) pvactst; -- disable parallel vacuum
+
+VACUUM (PARALLEL -1) pvactst; -- error
+VACUUM (PARALLEL 2, INDEX_CLEANUP FALSE) pvactst;
+VACUUM (PARALLEL 2, FULL TRUE) pvactst; -- error, cannot use both PARALLEL and FULL
+VACUUM (PARALLEL) pvactst; -- error, cannot use PARALLEL option without parallel degree
+
+-- Test different combinations of parallel and full options for temporary tables
+CREATE TEMPORARY TABLE tmp (a int PRIMARY KEY);
+CREATE INDEX tmp_idx1 ON tmp (a);
+VACUUM (PARALLEL 1, FULL FALSE) tmp; -- parallel vacuum disabled for temp tables
+VACUUM (PARALLEL 0, FULL TRUE) tmp; -- can specify parallel disabled (even though that's implied by FULL)
+RESET min_parallel_index_scan_size;
+DROP TABLE pvactst;
+
+-- INDEX_CLEANUP option
+CREATE TABLE no_index_cleanup (i INT PRIMARY KEY, t TEXT);
+-- Use uncompressed data stored in toast.
+CREATE INDEX no_index_cleanup_idx ON no_index_cleanup(t);
+ALTER TABLE no_index_cleanup ALTER COLUMN t SET STORAGE EXTERNAL;
+INSERT INTO no_index_cleanup(i, t) VALUES (generate_series(1,30),
+ repeat('1234567890',269));
+-- index cleanup option is ignored if VACUUM FULL
+VACUUM (INDEX_CLEANUP TRUE, FULL TRUE) no_index_cleanup;
+VACUUM (FULL TRUE) no_index_cleanup;
+-- Toast inherits the value from its parent table.
+ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = false);
+DELETE FROM no_index_cleanup WHERE i < 15;
+-- Nothing is cleaned up.
+VACUUM no_index_cleanup;
+-- Both parent relation and toast are cleaned up.
+ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = true);
+VACUUM no_index_cleanup;
+ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = auto);
+VACUUM no_index_cleanup;
+-- Parameter is set for both the parent table and its toast relation.
+INSERT INTO no_index_cleanup(i, t) VALUES (generate_series(31,60),
+ repeat('1234567890',269));
+DELETE FROM no_index_cleanup WHERE i < 45;
+-- Only toast index is cleaned up.
+ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = off,
+ toast.vacuum_index_cleanup = yes);
+VACUUM no_index_cleanup;
+-- Only parent is cleaned up.
+ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = true,
+ toast.vacuum_index_cleanup = false);
+VACUUM no_index_cleanup;
+-- Test some extra relations.
+VACUUM (INDEX_CLEANUP FALSE) vaccluster;
+VACUUM (INDEX_CLEANUP AUTO) vactst; -- index cleanup option is ignored if no indexes
+VACUUM (INDEX_CLEANUP FALSE, FREEZE TRUE) vaccluster;
+
+-- TRUNCATE option
+CREATE TEMP TABLE vac_truncate_test(i INT NOT NULL, j text)
+ WITH (vacuum_truncate=true, autovacuum_enabled=false);
+INSERT INTO vac_truncate_test VALUES (1, NULL), (NULL, NULL);
+VACUUM (TRUNCATE FALSE, DISABLE_PAGE_SKIPPING) vac_truncate_test;
+SELECT pg_relation_size('vac_truncate_test') > 0;
+VACUUM (DISABLE_PAGE_SKIPPING) vac_truncate_test;
+SELECT pg_relation_size('vac_truncate_test') = 0;
+VACUUM (TRUNCATE FALSE, FULL TRUE) vac_truncate_test;
+DROP TABLE vac_truncate_test;
+
+-- partitioned table
+CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
+CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
+INSERT INTO vacparted VALUES (1, 'a');
+UPDATE vacparted SET b = 'b';
+VACUUM (ANALYZE) vacparted;
+VACUUM (FULL) vacparted;
+VACUUM (FREEZE) vacparted;
+
+-- check behavior with duplicate column mentions
+VACUUM ANALYZE vacparted(a,b,a);
+ANALYZE vacparted(a,b,b);
+
+-- partitioned table with index
+CREATE TABLE vacparted_i (a int primary key, b varchar(100))
+ PARTITION BY HASH (a);
+CREATE TABLE vacparted_i1 PARTITION OF vacparted_i
+ FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE vacparted_i2 PARTITION OF vacparted_i
+ FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO vacparted_i SELECT i, 'test_'|| i from generate_series(1,10) i;
+VACUUM (ANALYZE) vacparted_i;
+VACUUM (FULL) vacparted_i;
+VACUUM (FREEZE) vacparted_i;
+SELECT relname, relhasindex FROM pg_class
+ WHERE relname LIKE 'vacparted_i%' AND relkind IN ('p','r')
+ ORDER BY relname;
+DROP TABLE vacparted_i;
+
+-- multiple tables specified
+VACUUM vaccluster, vactst;
+VACUUM vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) does_not_exist, vaccluster;
+VACUUM ANALYZE vactst, vacparted (a);
+VACUUM ANALYZE vactst (does_not_exist), vacparted (b);
+VACUUM FULL vacparted, vactst;
+VACUUM FULL vactst, vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE vactst, does_not_exist, vacparted;
+ANALYZE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst;
+BEGIN; -- ANALYZE behaves differently inside a transaction block
+ANALYZE vactst, vactst;
+COMMIT;
+
+-- parenthesized syntax for ANALYZE
+ANALYZE (VERBOSE) does_not_exist;
+ANALYZE (nonexistent-arg) does_not_exist;
+ANALYZE (nonexistentarg) does_not_exit;
+
+-- ensure argument order independence, and that SKIP_LOCKED on non-existing
+-- relation still errors out. Suppress WARNING messages caused by concurrent
+-- autovacuums.
+SET client_min_messages TO 'ERROR';
+ANALYZE (SKIP_LOCKED, VERBOSE) does_not_exist;
+ANALYZE (VERBOSE, SKIP_LOCKED) does_not_exist;
+
+-- SKIP_LOCKED option
+VACUUM (SKIP_LOCKED) vactst;
+VACUUM (SKIP_LOCKED, FULL) vactst;
+ANALYZE (SKIP_LOCKED) vactst;
+RESET client_min_messages;
+
+-- ensure VACUUM and ANALYZE don't have a problem with serializable
+SET default_transaction_isolation = serializable;
+VACUUM vactst;
+ANALYZE vactst;
+RESET default_transaction_isolation;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+ANALYZE vactst;
+COMMIT;
+
+-- PROCESS_TOAST option
+ALTER TABLE vactst ADD COLUMN t TEXT;
+ALTER TABLE vactst ALTER COLUMN t SET STORAGE EXTERNAL;
+VACUUM (PROCESS_TOAST FALSE) vactst;
+VACUUM (PROCESS_TOAST FALSE, FULL) vactst;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
+DROP TABLE vacparted;
+DROP TABLE no_index_cleanup;
+
+-- relation ownership, WARNING logs generated as all are skipped.
+CREATE TABLE vacowned (a int);
+CREATE TABLE vacowned_parted (a int) PARTITION BY LIST (a);
+CREATE TABLE vacowned_part1 PARTITION OF vacowned_parted FOR VALUES IN (1);
+CREATE TABLE vacowned_part2 PARTITION OF vacowned_parted FOR VALUES IN (2);
+CREATE ROLE regress_vacuum;
+SET ROLE regress_vacuum;
+-- Simple table
+VACUUM vacowned;
+ANALYZE vacowned;
+VACUUM (ANALYZE) vacowned;
+-- Catalog
+VACUUM pg_catalog.pg_class;
+ANALYZE pg_catalog.pg_class;
+VACUUM (ANALYZE) pg_catalog.pg_class;
+-- Shared catalog
+VACUUM pg_catalog.pg_authid;
+ANALYZE pg_catalog.pg_authid;
+VACUUM (ANALYZE) pg_catalog.pg_authid;
+-- Partitioned table and its partitions, nothing owned by other user.
+-- Relations are not listed in a single command to test ownership
+-- independently.
+VACUUM vacowned_parted;
+VACUUM vacowned_part1;
+VACUUM vacowned_part2;
+ANALYZE vacowned_parted;
+ANALYZE vacowned_part1;
+ANALYZE vacowned_part2;
+VACUUM (ANALYZE) vacowned_parted;
+VACUUM (ANALYZE) vacowned_part1;
+VACUUM (ANALYZE) vacowned_part2;
+RESET ROLE;
+-- Partitioned table and one partition owned by other user.
+ALTER TABLE vacowned_parted OWNER TO regress_vacuum;
+ALTER TABLE vacowned_part1 OWNER TO regress_vacuum;
+SET ROLE regress_vacuum;
+VACUUM vacowned_parted;
+VACUUM vacowned_part1;
+VACUUM vacowned_part2;
+ANALYZE vacowned_parted;
+ANALYZE vacowned_part1;
+ANALYZE vacowned_part2;
+VACUUM (ANALYZE) vacowned_parted;
+VACUUM (ANALYZE) vacowned_part1;
+VACUUM (ANALYZE) vacowned_part2;
+RESET ROLE;
+-- Only one partition owned by other user.
+ALTER TABLE vacowned_parted OWNER TO CURRENT_USER;
+SET ROLE regress_vacuum;
+VACUUM vacowned_parted;
+VACUUM vacowned_part1;
+VACUUM vacowned_part2;
+ANALYZE vacowned_parted;
+ANALYZE vacowned_part1;
+ANALYZE vacowned_part2;
+VACUUM (ANALYZE) vacowned_parted;
+VACUUM (ANALYZE) vacowned_part1;
+VACUUM (ANALYZE) vacowned_part2;
+RESET ROLE;
+-- Only partitioned table owned by other user.
+ALTER TABLE vacowned_parted OWNER TO regress_vacuum;
+ALTER TABLE vacowned_part1 OWNER TO CURRENT_USER;
+SET ROLE regress_vacuum;
+VACUUM vacowned_parted;
+VACUUM vacowned_part1;
+VACUUM vacowned_part2;
+ANALYZE vacowned_parted;
+ANALYZE vacowned_part1;
+ANALYZE vacowned_part2;
+VACUUM (ANALYZE) vacowned_parted;
+VACUUM (ANALYZE) vacowned_part1;
+VACUUM (ANALYZE) vacowned_part2;
+RESET ROLE;
+DROP TABLE vacowned;
+DROP TABLE vacowned_parted;
+DROP ROLE regress_vacuum;
diff --git a/src/test/regress/sql/vacuum_parallel.sql b/src/test/regress/sql/vacuum_parallel.sql
new file mode 100644
index 0000000..1d23f33
--- /dev/null
+++ b/src/test/regress/sql/vacuum_parallel.sql
@@ -0,0 +1,46 @@
+SET max_parallel_maintenance_workers TO 4;
+SET min_parallel_index_scan_size TO '128kB';
+
+-- Bug #17245: Make sure that we don't totally fail to VACUUM individual indexes that
+-- happen to be below min_parallel_index_scan_size during parallel VACUUM:
+CREATE TABLE parallel_vacuum_table (a int) WITH (autovacuum_enabled = off);
+INSERT INTO parallel_vacuum_table SELECT i from generate_series(1, 10000) i;
+
+-- Parallel VACUUM will never be used unless there are at least two indexes
+-- that exceed min_parallel_index_scan_size. Create two such indexes, and
+-- a third index that is smaller than min_parallel_index_scan_size.
+CREATE INDEX regular_sized_index ON parallel_vacuum_table(a);
+CREATE INDEX typically_sized_index ON parallel_vacuum_table(a);
+-- Note: vacuum_in_leader_small_index can apply deduplication, making it ~3x
+-- smaller than the other indexes
+CREATE INDEX vacuum_in_leader_small_index ON parallel_vacuum_table((1));
+
+-- Verify (as best we can) that the cost model for parallel VACUUM
+-- will make our VACUUM run in parallel, while always leaving it up to the
+-- parallel leader to handle the vacuum_in_leader_small_index index:
+SELECT EXISTS (
+SELECT 1
+FROM pg_class
+WHERE oid = 'vacuum_in_leader_small_index'::regclass AND
+ pg_relation_size(oid) <
+ pg_size_bytes(current_setting('min_parallel_index_scan_size'))
+) as leader_will_handle_small_index;
+SELECT count(*) as trigger_parallel_vacuum_nindexes
+FROM pg_class
+WHERE oid in ('regular_sized_index'::regclass, 'typically_sized_index'::regclass) AND
+ pg_relation_size(oid) >=
+ pg_size_bytes(current_setting('min_parallel_index_scan_size'));
+
+-- Parallel VACUUM with B-Tree page deletions, ambulkdelete calls:
+DELETE FROM parallel_vacuum_table;
+VACUUM (PARALLEL 4, INDEX_CLEANUP ON) parallel_vacuum_table;
+
+-- Since vacuum_in_leader_small_index uses deduplication, we expect an
+-- assertion failure with bug #17245 (in the absence of bugfix):
+INSERT INTO parallel_vacuum_table SELECT i FROM generate_series(1, 10000) i;
+
+RESET max_parallel_maintenance_workers;
+RESET min_parallel_index_scan_size;
+
+-- Deliberately don't drop table, to get further coverage from tools like
+-- pg_amcheck in some testing scenarios
diff --git a/src/test/regress/sql/varchar.sql b/src/test/regress/sql/varchar.sql
new file mode 100644
index 0000000..35e24b8
--- /dev/null
+++ b/src/test/regress/sql/varchar.sql
@@ -0,0 +1,66 @@
+--
+-- VARCHAR
+--
+
+CREATE TABLE VARCHAR_TBL(f1 varchar(1));
+
+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 * FROM VARCHAR_TBL;
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 <> 'a';
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 = 'a';
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 < 'a';
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 <= 'a';
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 > 'a';
+
+SELECT c.*
+ FROM VARCHAR_TBL c
+ WHERE c.f1 >= 'a';
+
+DROP TABLE VARCHAR_TBL;
+
+--
+-- Now test longer arrays of char
+--
+
+CREATE TABLE VARCHAR_TBL(f1 varchar(4));
+
+INSERT INTO VARCHAR_TBL (f1) VALUES ('a');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('ab');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('abcd');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('abcde');
+INSERT INTO VARCHAR_TBL (f1) VALUES ('abcd ');
+
+SELECT * FROM VARCHAR_TBL;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
new file mode 100644
index 0000000..eae5fa6
--- /dev/null
+++ b/src/test/regress/sql/window.sql
@@ -0,0 +1,1330 @@
+--
+-- 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;
+
+-- 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);
+
+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;
+
+SELECT COUNT(*) OVER w FROM tenk1 WHERE unique2 < 10 WINDOW w AS ();
+
+-- 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;
+
+SELECT row_number() OVER (ORDER BY unique2) FROM tenk1 WHERE unique2 < 10;
+
+SELECT rank() OVER (PARTITION BY four ORDER BY ten) AS rank_1, ten, four FROM tenk1 WHERE unique2 < 10;
+
+SELECT dense_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+
+SELECT percent_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+
+SELECT cume_dist() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+
+SELECT ntile(3) OVER (ORDER BY ten, four), ten, four FROM tenk1 WHERE unique2 < 10;
+
+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;
+
+SELECT lag(ten, four) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+
+SELECT lag(ten, four, 0) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+SELECT lag(ten, four, 0.7) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten;
+
+SELECT lead(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+
+SELECT lead(ten * 2, 1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+
+SELECT lead(ten * 2, 1, -1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+SELECT lead(ten * 2, 1, -1.4) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten;
+
+SELECT first_value(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+
+-- last_value returns the last row of the frame, which is CURRENT ROW in ORDER BY window.
+SELECT last_value(four) OVER (ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
+
+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;
+
+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;
+
+-- 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;
+
+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);
+
+-- 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);
+
+-- 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;
+
+-- 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);
+
+-- 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;
+
+-- 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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+SELECT sum(unique1) over (order by four range between current row and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between current row and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude no others),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude current row),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude ties),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 following exclude current row),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 following exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 following exclude ties),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 following exclude current row),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 following exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 following exclude ties),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between 2 preceding and 1 preceding),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between 1 following and 3 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between unbounded preceding and 1 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+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);
+
+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);
+
+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);
+
+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);
+
+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 four range between current row and unbounded following);
+
+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;
+
+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');
+
+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');
+
+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');
+
+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');
+
+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');
+
+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;
+
+SELECT sum(unique1) over (order by four desc range between 2::int8 preceding and 1::int2 preceding),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following),
+ salary, enroll_date from empsalary;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+-- 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);
+
+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);
+
+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);
+
+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);
+
+-- 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;
+
+select x, last_value(x) over (order by x::smallint desc range between current row and 2147450885 following)
+from generate_series(-32766, -32764) x;
+
+select x, last_value(x) over (order by x range between current row and 4 following)
+from generate_series(2147483644, 2147483646) x;
+
+select x, last_value(x) over (order by x desc range between current row and 5 following)
+from generate_series(-2147483646, -2147483644) x;
+
+select x, last_value(x) over (order by x range between current row and 4 following)
+from generate_series(9223372036854775804, 9223372036854775806) x;
+
+select x, last_value(x) over (order by x desc range between current row and 5 following)
+from generate_series(-9223372036854775806, -9223372036854775804) x;
+
+-- 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', '-infinity'),
+(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', 'infinity'),
+(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);
+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);
+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);
+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' preceding);
+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' following and 'inf' following);
+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);
+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);
+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);
+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' preceding);
+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' following and 'inf' following);
+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);
+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);
+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
+ 'inf' preceding and 'inf' following);
+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
+ 'inf' preceding and 'inf' preceding);
+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
+ 'inf' following and 'inf' following);
+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);
+
+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);
+
+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);
+
+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);
+
+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);
+
+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);
+
+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);
+
+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);
+
+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);
+
+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);
+
+-- 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;
+
+SELECT sum(unique1) over (order by four groups between unbounded preceding and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between current row and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 1 preceding and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 1 following and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between unbounded preceding and 2 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 preceding),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 0 preceding and 0 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+ exclude current row), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+ exclude group), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+ exclude ties), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+-- 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);
+
+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);
+
+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);
+
+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);
+
+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);
+
+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);
+
+-- 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;
+select f1, sum(f1) over (partition by f1 order by f2
+ range between 1 preceding and 1 following)
+from t1 where f1 = f2;
+select f1, sum(f1) over (partition by f1, f1 order by f2
+ range between 2 preceding and 1 preceding)
+from t1 where f1 = f2;
+select f1, sum(f1) over (partition by f1, f2 order by f2
+ range between 1 following and 2 following)
+from t1 where f1 = f2;
+
+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;
+select f1, sum(f1) over (partition by f1 order by f2
+ groups between 1 preceding and 1 following)
+from t1 where f1 = f2;
+select f1, sum(f1) over (partition by f1, f1 order by f2
+ groups between 2 preceding and 1 preceding)
+from t1 where f1 = f2;
+select f1, sum(f1) over (partition by f1, f2 order by f2
+ groups between 1 following and 2 following)
+from t1 where f1 = f2;
+
+-- ordering by a non-integer constant is allowed
+SELECT rank() OVER (ORDER BY length('abc'));
+
+-- 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;
+
+-- 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';
+
+-- Test Sort node collapsing
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+ (SELECT depname,
+ sum(salary) OVER (PARTITION BY depname order by empno) depsalary,
+ min(salary) OVER (PARTITION BY depname, empno order by enroll_date) depminsalary
+ FROM empsalary) emp
+WHERE depname = 'sales';
+
+-- Test Sort node reordering
+EXPLAIN (COSTS OFF)
+SELECT
+ lead(1) OVER (PARTITION BY depname ORDER BY salary, enroll_date),
+ lag(1) OVER (PARTITION BY depname ORDER BY salary,enroll_date,empno)
+FROM empsalary;
+
+-- Test incremental sorting
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+ (SELECT depname,
+ empno,
+ salary,
+ enroll_date,
+ row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
+ row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+ FROM empsalary) emp
+WHERE first_emp = 1 OR last_emp = 1;
+
+SELECT * FROM
+ (SELECT depname,
+ empno,
+ salary,
+ enroll_date,
+ row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
+ row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+ FROM empsalary) emp
+WHERE first_emp = 1 OR last_emp = 1;
+
+-- 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;
+
+SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
+ FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
+
+--
+-- 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);
+
+-- Tests for problems with failure to walk or mutate expressions
+-- within window frame clauses.
+
+-- test walker (fails with collation error if expressions are not walked)
+SELECT array_agg(i) OVER w
+ FROM generate_series(1,5) i
+WINDOW w AS (ORDER BY i ROWS BETWEEN (('foo' < 'foobar')::integer) PRECEDING AND CURRENT ROW);
+
+-- test mutator (fails when inlined if expressions are not mutated)
+CREATE FUNCTION pg_temp.f(group_size BIGINT) RETURNS SETOF integer[]
+AS $$
+ SELECT array_agg(s) OVER w
+ FROM generate_series(1,5) s
+ WINDOW w AS (ORDER BY s ROWS BETWEEN CURRENT ROW AND GROUP_SIZE FOLLOWING)
+$$ LANGUAGE SQL STABLE;
+
+EXPLAIN (costs off) SELECT * FROM pg_temp.f(2);
+SELECT * FROM pg_temp.f(2);
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
new file mode 100644
index 0000000..97255e6
--- /dev/null
+++ b/src/test/regress/sql/with.sql
@@ -0,0 +1,1506 @@
+--
+-- 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 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;
+
+-- 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;
+
+drop table graph0 cascade;
+
+--
+-- 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
+
+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;
+
+-- 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 TEMPORARY TABLE y (a INTEGER);
+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 ( with cte(foo) as ( values(f1) )
+ select (select foo from cte) )
+from int4_tbl;
+
+select ( with cte(foo) as ( values(f1) )
+ values((select foo from cte)) )
+from int4_tbl;
+
+--
+-- 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 * FROM t;
+
+SELECT * FROM y;
+
+-- UPDATE ... RETURNING
+WITH t AS (
+ UPDATE y
+ SET a=a+1
+ RETURNING *
+)
+SELECT * FROM t;
+
+SELECT * FROM y;
+
+-- DELETE ... RETURNING
+WITH t AS (
+ DELETE FROM y
+ WHERE a <= 10
+ RETURNING *
+)
+SELECT * FROM t;
+
+SELECT * FROM y;
+
+-- 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 RETURNING *
+)
+SELECT * FROM t
+UNION ALL
+SELECT * FROM t2;
+
+SELECT * FROM y;
+
+-- 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 * FROM t;
+
+SELECT * FROM y;
+
+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 * 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 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 * FROM t2 JOIN y USING (a) ORDER BY a;
+
+SELECT * FROM y;
+
+-- 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 *;
+
+SELECT * FROM y;
+
+-- 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 * FROM t LIMIT 10;
+
+SELECT * FROM y;
+
+-- 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 * FROM t JOIN y ON t.k = y.a ORDER BY a, k;
+
+-- 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;
+
+DROP TABLE withz;
+
+-- check that run to completion happens in proper ordering
+
+TRUNCATE TABLE y;
+INSERT INTO y SELECT generate_series(1, 3);
+CREATE TEMPORARY TABLE yy (a INTEGER);
+
+WITH RECURSIVE t1 AS (
+ INSERT INTO y SELECT * FROM y RETURNING *
+), t2 AS (
+ INSERT INTO yy SELECT * FROM t1 RETURNING *
+)
+SELECT 1;
+
+SELECT * FROM y;
+SELECT * FROM yy;
+
+WITH RECURSIVE t1 AS (
+ INSERT INTO yy SELECT * FROM t2 RETURNING *
+), t2 AS (
+ INSERT INTO y SELECT * FROM y RETURNING *
+)
+SELECT 1;
+
+SELECT * FROM y;
+SELECT * FROM yy;
+
+-- triggers
+
+TRUNCATE TABLE y;
+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 * FROM t;
+
+SELECT * FROM y;
+
+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 * FROM t LIMIT 1;
+
+SELECT * FROM y;
+
+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 * FROM t;
+
+SELECT * FROM y;
+
+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 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);
+
+-- 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/write_parallel.sql b/src/test/regress/sql/write_parallel.sql
new file mode 100644
index 0000000..ae660dc
--- /dev/null
+++ b/src/test/regress/sql/write_parallel.sql
@@ -0,0 +1,43 @@
+--
+-- PARALLEL
+--
+
+begin;
+
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+
+--
+-- Test write operations that has an underlying query that is eligible
+-- for parallel plans
+--
+explain (costs off) create table parallel_write as
+ select length(stringu1) from tenk1 group by length(stringu1);
+create table parallel_write as
+ select length(stringu1) from tenk1 group by length(stringu1);
+drop table parallel_write;
+
+explain (costs off) select length(stringu1) into parallel_write
+ from tenk1 group by length(stringu1);
+select length(stringu1) into parallel_write
+ from tenk1 group by length(stringu1);
+drop table parallel_write;
+
+explain (costs off) create materialized view parallel_mat_view as
+ select length(stringu1) from tenk1 group by length(stringu1);
+create materialized view parallel_mat_view as
+ select length(stringu1) from tenk1 group by length(stringu1);
+create unique index on parallel_mat_view(length);
+refresh materialized view parallel_mat_view;
+refresh materialized view concurrently parallel_mat_view;
+drop materialized view parallel_mat_view;
+
+prepare prep_stmt as select length(stringu1) from tenk1 group by length(stringu1);
+explain (costs off) create table parallel_write as execute prep_stmt;
+create table parallel_write as execute prep_stmt;
+drop table parallel_write;
+
+rollback;
diff --git a/src/test/regress/sql/xid.sql b/src/test/regress/sql/xid.sql
new file mode 100644
index 0000000..565714d
--- /dev/null
+++ b/src/test/regress/sql/xid.sql
@@ -0,0 +1,152 @@
+-- xid and xid8
+
+-- values in range, in octal, decimal, hex
+select '010'::xid,
+ '42'::xid,
+ '0xffffffff'::xid,
+ '-1'::xid,
+ '010'::xid8,
+ '42'::xid8,
+ '0xffffffffffffffff'::xid8,
+ '-1'::xid8;
+
+-- garbage values are not yet rejected (perhaps they should be)
+select ''::xid;
+select 'asdf'::xid;
+select ''::xid8;
+select 'asdf'::xid8;
+
+-- equality
+select '1'::xid = '1'::xid;
+select '1'::xid != '1'::xid;
+select '1'::xid8 = '1'::xid8;
+select '1'::xid8 != '1'::xid8;
+
+-- conversion
+select '1'::xid = '1'::xid8::xid;
+select '1'::xid != '1'::xid8::xid;
+
+-- we don't want relational operators for xid, due to use of modular arithmetic
+select '1'::xid < '2'::xid;
+select '1'::xid <= '2'::xid;
+select '1'::xid > '2'::xid;
+select '1'::xid >= '2'::xid;
+
+-- we want them for xid8 though
+select '1'::xid8 < '2'::xid8, '2'::xid8 < '2'::xid8, '2'::xid8 < '1'::xid8;
+select '1'::xid8 <= '2'::xid8, '2'::xid8 <= '2'::xid8, '2'::xid8 <= '1'::xid8;
+select '1'::xid8 > '2'::xid8, '2'::xid8 > '2'::xid8, '2'::xid8 > '1'::xid8;
+select '1'::xid8 >= '2'::xid8, '2'::xid8 >= '2'::xid8, '2'::xid8 >= '1'::xid8;
+
+-- we also have a 3way compare for btrees
+select xid8cmp('1', '2'), xid8cmp('2', '2'), xid8cmp('2', '1');
+
+-- xid8 has btree and hash opclasses
+create table xid8_t1 (x xid8);
+create index on xid8_t1 using btree(x);
+create index on xid8_t1 using hash(x);
+drop table xid8_t1;
+
+
+-- pg_snapshot data type and related functions
+
+-- Note: another set of tests similar to this exists in txid.sql, for a limited
+-- time (the relevant functions share C code)
+
+-- i/o
+select '12:13:'::pg_snapshot;
+select '12:18:14,16'::pg_snapshot;
+select '12:16:14,14'::pg_snapshot;
+
+-- errors
+select '31:12:'::pg_snapshot;
+select '0:1:'::pg_snapshot;
+select '12:13:0'::pg_snapshot;
+select '12:16:14,13'::pg_snapshot;
+
+create temp table snapshot_test (
+ nr integer,
+ snap pg_snapshot
+);
+
+insert into snapshot_test values (1, '12:13:');
+insert into snapshot_test values (2, '12:20:13,15,18');
+insert into snapshot_test values (3, '100001:100009:100005,100007,100008');
+insert into snapshot_test values (4, '100:150: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');
+select snap from snapshot_test order by nr;
+
+select pg_snapshot_xmin(snap),
+ pg_snapshot_xmax(snap),
+ pg_snapshot_xip(snap)
+from snapshot_test order by nr;
+
+select id, pg_visible_in_snapshot(id::text::xid8, snap)
+from snapshot_test, generate_series(11, 21) id
+where nr = 2;
+
+-- test bsearch
+select id, pg_visible_in_snapshot(id::text::xid8, snap)
+from snapshot_test, generate_series(90, 160) id
+where nr = 4;
+
+-- test current values also
+select pg_current_xact_id() >= pg_snapshot_xmin(pg_current_snapshot());
+
+-- we can't assume current is always less than xmax, however
+
+select pg_visible_in_snapshot(pg_current_xact_id(), pg_current_snapshot());
+
+-- test 64bitness
+
+select pg_snapshot '1000100010001000:1000100010001100:1000100010001012,1000100010001013';
+select pg_visible_in_snapshot('1000100010001012', '1000100010001000:1000100010001100:1000100010001012,1000100010001013');
+select pg_visible_in_snapshot('1000100010001015', '1000100010001000:1000100010001100:1000100010001012,1000100010001013');
+
+-- test 64bit overflow
+SELECT pg_snapshot '1:9223372036854775807:3';
+SELECT pg_snapshot '1:9223372036854775808:3';
+
+-- test pg_current_xact_id_if_assigned
+BEGIN;
+SELECT pg_current_xact_id_if_assigned() IS NULL;
+SELECT pg_current_xact_id() \gset
+SELECT pg_current_xact_id_if_assigned() IS NOT DISTINCT FROM xid8 :'pg_current_xact_id';
+COMMIT;
+
+-- test xid status functions
+BEGIN;
+SELECT pg_current_xact_id() AS committed \gset
+COMMIT;
+
+BEGIN;
+SELECT pg_current_xact_id() AS rolledback \gset
+ROLLBACK;
+
+BEGIN;
+SELECT pg_current_xact_id() AS inprogress \gset
+
+SELECT pg_xact_status(:committed::text::xid8) AS committed;
+SELECT pg_xact_status(:rolledback::text::xid8) AS rolledback;
+SELECT pg_xact_status(:inprogress::text::xid8) AS inprogress;
+SELECT pg_xact_status('1'::xid8); -- BootstrapTransactionId is always committed
+SELECT pg_xact_status('2'::xid8); -- FrozenTransactionId is always committed
+SELECT pg_xact_status('3'::xid8); -- in regress testing FirstNormalTransactionId will always be behind oldestXmin
+
+COMMIT;
+
+BEGIN;
+CREATE FUNCTION test_future_xid_status(xid8)
+RETURNS void
+LANGUAGE plpgsql
+AS
+$$
+BEGIN
+ PERFORM pg_xact_status($1);
+ RAISE EXCEPTION 'didn''t ERROR at xid in the future as expected';
+EXCEPTION
+ WHEN invalid_parameter_value THEN
+ RAISE NOTICE 'Got expected error for xid in the future';
+END;
+$$;
+SELECT test_future_xid_status((:inprogress + 10000)::text::xid8);
+ROLLBACK;
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
new file mode 100644
index 0000000..e3f90db
--- /dev/null
+++ b/src/test/regress/sql/xml.sql
@@ -0,0 +1,619 @@
+CREATE TABLE xmltest (
+ id int,
+ data xml
+);
+
+INSERT INTO xmltest VALUES (1, '<value>one</value>');
+INSERT INTO xmltest VALUES (2, '<value>two</value>');
+INSERT INTO xmltest VALUES (3, '<wrong');
+
+SELECT * FROM xmltest;
+
+
+SELECT xmlcomment('test');
+SELECT xmlcomment('-test');
+SELECT xmlcomment('test-');
+SELECT xmlcomment('--test');
+SELECT xmlcomment('te st');
+
+
+SELECT xmlconcat(xmlcomment('hello'),
+ xmlelement(NAME qux, 'foo'),
+ xmlcomment('world'));
+
+SELECT xmlconcat('hello', 'you');
+SELECT xmlconcat(1, 2);
+SELECT xmlconcat('bad', '<syntax');
+SELECT xmlconcat('<foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
+SELECT xmlconcat('<?xml version="1.1"?><foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
+SELECT xmlconcat(NULL);
+SELECT xmlconcat(NULL, NULL);
+
+
+SELECT xmlelement(name element,
+ xmlattributes (1 as one, 'deuce' as two),
+ 'content');
+
+SELECT xmlelement(name element,
+ xmlattributes ('unnamed and wrong'));
+
+SELECT xmlelement(name element, xmlelement(name nested, 'stuff'));
+
+SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
+
+SELECT xmlelement(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
+
+SELECT xmlelement(name num, 37);
+SELECT xmlelement(name foo, text 'bar');
+SELECT xmlelement(name foo, xml 'bar');
+SELECT xmlelement(name foo, text 'b<a/>r');
+SELECT xmlelement(name foo, xml 'b<a/>r');
+SELECT xmlelement(name foo, array[1, 2, 3]);
+SET xmlbinary TO base64;
+SELECT xmlelement(name foo, bytea 'bar');
+SET xmlbinary TO hex;
+SELECT xmlelement(name foo, bytea 'bar');
+
+SELECT xmlelement(name foo, xmlattributes(true as bar));
+SELECT xmlelement(name foo, xmlattributes('2009-04-09 00:24:37'::timestamp as bar));
+SELECT xmlelement(name foo, xmlattributes('infinity'::timestamp as bar));
+SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as funnier));
+
+
+SELECT xmlparse(content '');
+SELECT xmlparse(content ' ');
+SELECT xmlparse(content 'abc');
+SELECT xmlparse(content '<abc>x</abc>');
+SELECT xmlparse(content '<invalidentity>&</invalidentity>');
+SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
+SELECT xmlparse(content '<invalidns xmlns=''&lt;''/>');
+SELECT xmlparse(content '<relativens xmlns=''relative''/>');
+SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
+SELECT xmlparse(content '<nosuchprefix:tag/>');
+
+SELECT xmlparse(document ' ');
+SELECT xmlparse(document 'abc');
+SELECT xmlparse(document '<abc>x</abc>');
+SELECT xmlparse(document '<invalidentity>&</abc>');
+SELECT xmlparse(document '<undefinedentity>&idontexist;</abc>');
+SELECT xmlparse(document '<invalidns xmlns=''&lt;''/>');
+SELECT xmlparse(document '<relativens xmlns=''relative''/>');
+SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
+SELECT xmlparse(document '<nosuchprefix:tag/>');
+
+
+SELECT xmlpi(name foo);
+SELECT xmlpi(name xml);
+SELECT xmlpi(name xmlstuff);
+SELECT xmlpi(name foo, 'bar');
+SELECT xmlpi(name foo, 'in?>valid');
+SELECT xmlpi(name foo, null);
+SELECT xmlpi(name xml, null);
+SELECT xmlpi(name xmlstuff, null);
+SELECT xmlpi(name "xml-stylesheet", 'href="mystyle.css" type="text/css"');
+SELECT xmlpi(name foo, ' bar');
+
+
+SELECT xmlroot(xml '<foo/>', version no value, standalone no value);
+SELECT xmlroot(xml '<foo/>', version '2.0');
+SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
+SELECT xmlroot(xml '<?xml version="1.1"?><foo/>', version no value, standalone yes);
+SELECT xmlroot(xmlroot(xml '<foo/>', version '1.0'), version '1.1', standalone no);
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no);
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no value);
+SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value);
+
+
+SELECT xmlroot (
+ xmlelement (
+ name gazonk,
+ xmlattributes (
+ 'val' AS name,
+ 1 + 1 AS num
+ ),
+ xmlelement (
+ NAME qux,
+ 'foo'
+ )
+ ),
+ version '1.0',
+ standalone yes
+);
+
+
+SELECT xmlserialize(content data as character varying(20)) FROM xmltest;
+SELECT xmlserialize(content 'good' as char(10));
+SELECT xmlserialize(document 'bad' as text);
+
+
+SELECT xml '<foo>bar</foo>' IS DOCUMENT;
+SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
+SELECT xml '<abc/>' IS NOT DOCUMENT;
+SELECT xml 'abc' IS NOT DOCUMENT;
+SELECT '<>' IS NOT DOCUMENT;
+
+
+SELECT xmlagg(data) FROM xmltest;
+SELECT xmlagg(data) FROM xmltest WHERE id > 10;
+SELECT xmlelement(name employees, xmlagg(xmlelement(name name, name))) FROM emp;
+
+
+-- Check mapping SQL identifier to XML name
+
+SELECT xmlpi(name ":::_xml_abc135.%-&_");
+SELECT xmlpi(name "123");
+
+
+PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
+
+SET XML OPTION DOCUMENT;
+EXECUTE foo ('<bar/>');
+EXECUTE foo ('bad');
+SELECT xml '<!DOCTYPE a><a/><b/>';
+
+SET XML OPTION CONTENT;
+EXECUTE foo ('<bar/>');
+EXECUTE foo ('good');
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+SELECT xml '<!DOCTYPE a><a/>';
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+SELECT xml '<!DOCTYPE a><a/><b/>';
+
+
+-- Test backwards parsing
+
+CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
+CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
+CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&');
+CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
+CREATE VIEW xmlview5 AS SELECT xmlparse(content '<abc>x</abc>');
+CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar');
+CREATE VIEW xmlview7 AS SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
+CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
+CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
+
+SELECT table_name, view_definition FROM information_schema.views
+ WHERE table_name LIKE 'xmlview%' ORDER BY 1;
+
+-- Text XPath expressions evaluation
+
+SELECT xpath('/value', data) FROM xmltest;
+SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
+SELECT xpath('', '<!-- error -->');
+SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+SELECT xpath('//loc:piece', '<local:data xmlns:local="http://127.0.0.1" xmlns="http://127.0.0.2"><local:piece id="1"><internal>number one</internal><internal2/></local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
+SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
+SELECT xpath('//text()', '<root>&lt;</root>');
+SELECT xpath('//@value', '<root value="&lt;"/>');
+SELECT xpath('''<<invalid>>''', '<root/>');
+SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
+SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
+SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
+SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
+SELECT xpath('/nosuchtag', '<root/>');
+SELECT xpath('root', '<root/>');
+
+-- Round-trip non-ASCII data through xpath().
+DO $$
+DECLARE
+ xml_declaration text := '<?xml version="1.0" encoding="ISO-8859-1"?>';
+ degree_symbol text;
+ res xml[];
+BEGIN
+ -- Per the documentation, except when the server encoding is UTF8, xpath()
+ -- may not work on non-ASCII data. The untranslatable_character and
+ -- undefined_function traps below, currently dead code, will become relevant
+ -- if we remove this limitation.
+ IF current_setting('server_encoding') <> 'UTF8' THEN
+ RAISE LOG 'skip: encoding % unsupported for xpath',
+ current_setting('server_encoding');
+ RETURN;
+ END IF;
+
+ degree_symbol := convert_from('\xc2b0', 'UTF8');
+ res := xpath('text()', (xml_declaration ||
+ '<x>' || degree_symbol || '</x>')::xml);
+ IF degree_symbol <> res[1]::text THEN
+ RAISE 'expected % (%), got % (%)',
+ degree_symbol, convert_to(degree_symbol, 'UTF8'),
+ res[1], convert_to(res[1]::text, 'UTF8');
+ END IF;
+EXCEPTION
+ -- character with byte sequence 0xc2 0xb0 in encoding "UTF8" has no equivalent in encoding "LATIN8"
+ WHEN untranslatable_character
+ -- default conversion function for encoding "UTF8" to "MULE_INTERNAL" does not exist
+ OR undefined_function
+ -- unsupported XML feature
+ OR feature_not_supported THEN
+ RAISE LOG 'skip: %', SQLERRM;
+END
+$$;
+
+-- Test xmlexists and xpath_exists
+SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
+SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
+SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
+SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
+SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
+
+INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
+INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Budvar</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
+
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data);
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
+SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
+
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data);
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data);
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data);
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]);
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]);
+SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]);
+
+CREATE TABLE query ( expr TEXT );
+INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']');
+SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data);
+
+-- Test xml_is_well_formed and variants
+
+SELECT xml_is_well_formed_document('<foo>bar</foo>');
+SELECT xml_is_well_formed_document('abc');
+SELECT xml_is_well_formed_content('<foo>bar</foo>');
+SELECT xml_is_well_formed_content('abc');
+
+SET xmloption TO DOCUMENT;
+SELECT xml_is_well_formed('abc');
+SELECT xml_is_well_formed('<>');
+SELECT xml_is_well_formed('<abc/>');
+SELECT xml_is_well_formed('<foo>bar</foo>');
+SELECT xml_is_well_formed('<foo>bar</foo');
+SELECT xml_is_well_formed('<foo><bar>baz</foo>');
+SELECT xml_is_well_formed('<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
+SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
+SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
+SELECT xml_is_well_formed('<invalidentity>&</abc>');
+SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
+SELECT xml_is_well_formed('<invalidns xmlns=''&lt;''/>');
+SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
+SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
+
+SET xmloption TO CONTENT;
+SELECT xml_is_well_formed('abc');
+
+-- Since xpath() deals with namespaces, it's a bit stricter about
+-- what's well-formed and what's not. If we don't obey these rules
+-- (i.e. ignore namespace-related errors from libxml), xpath()
+-- fails in subtle ways. The following would for example produce
+-- the xml value
+-- <invalidns xmlns='<'/>
+-- which is invalid because '<' may not appear un-escaped in
+-- attribute values.
+-- Since different libxml versions emit slightly different
+-- error messages, we suppress the DETAIL in this test.
+\set VERBOSITY terse
+SELECT xpath('/*', '<invalidns xmlns=''&lt;''/>');
+\set VERBOSITY default
+
+-- Again, the XML isn't well-formed for namespace purposes
+SELECT xpath('/*', '<nosuchprefix:tag/>');
+
+-- XPath deprecates relative namespaces, but they're not supposed to
+-- throw an error, only a warning.
+SELECT xpath('/*', '<relativens xmlns=''relative''/>');
+
+-- External entity references should not leak filesystem information.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>');
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>');
+-- This might or might not load the requested DTD, but it mustn't throw error.
+SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>');
+
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+ <COUNTRY_ID>AU</COUNTRY_ID>
+ <COUNTRY_NAME>Australia</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+ <COUNTRY_ID>CN</COUNTRY_ID>
+ <COUNTRY_NAME>China</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+ <COUNTRY_ID>HK</COUNTRY_ID>
+ <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+ <COUNTRY_ID>IN</COUNTRY_ID>
+ <COUNTRY_NAME>India</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+ <COUNTRY_ID>JP</COUNTRY_ID>
+ <COUNTRY_NAME>Japan</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+ <COUNTRY_ID>SG</COUNTRY_ID>
+ <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+ <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+
+-- XMLTABLE with columns
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+CREATE VIEW xmltableview1 AS SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME/text()' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+SELECT * FROM xmltableview1;
+
+\sv xmltableview1
+
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
+
+-- errors
+SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2);
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+ '/zz:rows/zz:row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'zz:a');
+
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+ '/zz:rows/zz:row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'zz:a');
+
+SELECT * FROM xmltableview2;
+
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+ '/rows/row'
+ PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'a');
+
+SELECT * FROM XMLTABLE('.'
+ PASSING '<foo/>'
+ COLUMNS a text PATH 'foo/namespace::node()');
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+EXECUTE pp;
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+
+-- CDATA test
+select * from xmltable('d/r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+ <COUNTRY_ID>CZ</COUNTRY_ID>
+ <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+ <COUNTRY_ID>DE</COUNTRY_ID>
+ <COUNTRY_NAME>Germany</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+ <COUNTRY_ID>FR</COUNTRY_ID>
+ <COUNTRY_NAME>France</COUNTRY_NAME>
+ <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+ <COUNTRY_ID>EG</COUNTRY_ID>
+ <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+ <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+ <COUNTRY_ID>SD</COUNTRY_ID>
+ <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+ <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+ WHERE region_id = 2;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+ WHERE region_id = 2;
+
+-- should fail, NULL value
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL XMLTABLE('/ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ _id FOR ORDINALITY,
+ country_name text PATH 'COUNTRY_NAME' NOT NULL,
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE' NOT NULL,
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+ x AS (SELECT proname, proowner, procost::numeric, pronargs,
+ array_to_string(proargnames,',') as proargnames,
+ case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+ FROM pg_proc WHERE proname = 'f_leak'),
+ y AS (SELECT xmlelement(name proc,
+ xmlforest(proname, proowner,
+ procost, pronargs,
+ proargnames, proargtypes)) as proc
+ FROM x),
+ z AS (SELECT xmltable.*
+ FROM y,
+ LATERAL xmltable('/proc' PASSING proc
+ COLUMNS proname name,
+ proowner oid,
+ procost float,
+ pronargs int,
+ proargnames text,
+ proargtypes text))
+ SELECT * FROM z
+ EXCEPT SELECT * FROM x;
+
+-- multi line xml test, result should be empty too
+WITH
+ x AS (SELECT proname, proowner, procost::numeric, pronargs,
+ array_to_string(proargnames,',') as proargnames,
+ case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+ FROM pg_proc),
+ y AS (SELECT xmlelement(name data,
+ xmlagg(xmlelement(name proc,
+ xmlforest(proname, proowner, procost,
+ pronargs, proargnames, proargtypes)))) as doc
+ FROM x),
+ z AS (SELECT xmltable.*
+ FROM y,
+ LATERAL xmltable('/data/proc' PASSING doc
+ COLUMNS proname name,
+ proowner oid,
+ procost float,
+ pronargs int,
+ proargnames text,
+ proargtypes text))
+ SELECT * FROM z
+ EXCEPT SELECT * FROM x;
+
+CREATE TABLE xmltest2(x xml, _path text);
+
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+
+-- XPath result can be boolean or number too
+SELECT * FROM XMLTABLE('*' PASSING '<a>a</a>' COLUMNS a xml PATH '.', b text PATH '.', c text PATH '"hi"', d boolean PATH '. = "a"', e integer PATH 'string-length(.)');
+\x
+SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n2>&amp;deep</n2>post</e>' COLUMNS x xml PATH 'node()', y xml PATH '/');
+\x
+
+SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
diff --git a/src/test/regress/sql/xmlmap.sql b/src/test/regress/sql/xmlmap.sql
new file mode 100644
index 0000000..16582bf
--- /dev/null
+++ b/src/test/regress/sql/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/standby_schedule b/src/test/regress/standby_schedule
new file mode 100644
index 0000000..3ddb961
--- /dev/null
+++ b/src/test/regress/standby_schedule
@@ -0,0 +1,21 @@
+# src/test/regress/standby_schedule
+#
+# Test schedule for Hot Standby
+#
+# First test checks we are on a standby server.
+# Subsequent tests rely upon a setup script having already
+# been executed in the appropriate database on the primary server
+# which is feeding WAL files to target standby.
+#
+# psql -f src/test/regress/sql/hs_primary_setup.sql regression
+#
+test: hs_standby_check
+#
+# These tests will pass on both primary and standby servers
+#
+test: hs_standby_allowed
+#
+# These tests will fail on a non-standby server
+#
+test: hs_standby_disallowed
+test: hs_standby_functions
diff --git a/src/test/ssl/.gitignore b/src/test/ssl/.gitignore
new file mode 100644
index 0000000..a325c2f
--- /dev/null
+++ b/src/test/ssl/.gitignore
@@ -0,0 +1,3 @@
+# Generated by regression tests
+/client-log
+/tmp_check/
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
new file mode 100644
index 0000000..a517756
--- /dev/null
+++ b/src/test/ssl/Makefile
@@ -0,0 +1,192 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/ssl
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/ssl/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/ssl
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+export with_ssl
+
+CERTIFICATES := server_ca server-cn-and-alt-names \
+ server-cn-only server-single-alt-name server-multiple-alt-names \
+ server-no-names server-revoked server-ss \
+ client_ca client client-dn client-revoked \
+ root_ca
+
+SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
+ ssl/server-password.key \
+ ssl/client.crl ssl/server.crl ssl/root.crl \
+ ssl/both-cas-1.crt ssl/both-cas-2.crt \
+ ssl/root+server_ca.crt ssl/root+server.crl \
+ ssl/root+client_ca.crt ssl/root+client.crl \
+ ssl/client+client_ca.crt ssl/client-der.key \
+ ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+
+SSLDIRS := ssl/client-crldir ssl/server-crldir \
+ ssl/root+client-crldir ssl/root+server-crldir
+
+# This target re-generates all the key and certificate files. Usually we just
+# use the ones that are committed to the tree without rebuilding them.
+#
+# This target will fail unless preceded by sslfiles-clean.
+#
+sslfiles: $(SSLFILES) $(SSLDIRS)
+
+# OpenSSL requires a directory to put all generated certificates in. We don't
+# use this for anything, but we need a location.
+ssl/new_certs_dir:
+ mkdir ssl/new_certs_dir
+
+# Rule for creating private/public key pairs.
+ssl/%.key:
+ openssl genrsa -out $@ 2048
+ chmod 0600 $@
+
+# Root CA certificate
+ssl/root_ca.crt: ssl/root_ca.key cas.config
+ touch ssl/root_ca-certindex
+ openssl req -new -out ssl/root_ca.crt -x509 -config cas.config -config root_ca.config -key ssl/root_ca.key -days 10000 -extensions v3_ca
+ echo "01" > ssl/root_ca.srl
+
+# Client and server CAs
+ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
+ touch ssl/$*_ca-certindex
+ echo "unique_subject=no" > ssl/$*_ca-certindex.attr
+ openssl req -new -out ssl/temp_ca.crt -config cas.config -config $*_ca.config -key ssl/$*_ca.key
+# Sign the certificate with the root CA
+ openssl ca -name root_ca -batch -config cas.config -in ssl/temp_ca.crt -out ssl/temp_ca_signed.crt -extensions v3_ca
+ openssl x509 -in ssl/temp_ca_signed.crt -out ssl/$*_ca.crt # to keep just the PEM cert
+ rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
+ echo "01" > ssl/$*_ca.srl
+
+# Server certificates, signed by server CA:
+ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
+ openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
+ openssl ca -name server_ca -batch -config cas.config -in ssl/server-$*.csr -out ssl/temp.crt -extensions v3_req -extfile server-$*.config
+ openssl x509 -in ssl/temp.crt -out ssl/server-$*.crt # to keep just the PEM cert
+ rm ssl/server-$*.csr
+
+# Self-signed version of server-cn-only.crt
+ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.config
+ openssl req -new -key ssl/server-cn-only.key -out ssl/server-ss.csr -config server-cn-only.config
+ openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
+ rm ssl/server-ss.csr
+
+# Password-protected version of server-cn-only.key
+ssl/server-password.key: ssl/server-cn-only.key
+ openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
+
+# Client certificate, signed by the client CA:
+ssl/client.crt: ssl/client.key ssl/client_ca.crt
+ openssl req -new -key ssl/client.key -out ssl/client.csr -config client.config
+ openssl ca -name client_ca -batch -out ssl/temp.crt -config cas.config -infiles ssl/client.csr
+ openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
+ rm ssl/client.csr ssl/temp.crt
+
+# Client certificate with multi-part DN, signed by the client CA:
+ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
+ openssl req -new -key ssl/client-dn.key -out ssl/client-dn.csr -config client-dn.config
+ openssl ca -name client_ca -batch -out ssl/temp.crt -config cas.config -infiles ssl/client-dn.csr
+ openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
+ rm ssl/client-dn.csr ssl/temp.crt
+
+# Another client certificate, signed by the client CA. This one is revoked.
+ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
+ openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
+ openssl ca -name client_ca -batch -out ssl/temp.crt -config cas.config -infiles ssl/client-revoked.csr
+ openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
+ rm ssl/client-revoked.csr ssl/temp.crt
+
+# Convert the key to DER, to test our behaviour there too
+ssl/client-der.key: ssl/client.key
+ openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
+
+# Convert the existing key to encrypted PEM (X.509 text) and DER (X.509 ASN.1) formats
+# to test libpq's support for the sslpassword= option.
+ssl/client-encrypted-pem.key: ssl/client.key
+ openssl rsa -in ssl/client.key -outform PEM -aes128 -passout 'pass:dUmmyP^#+' -out ssl/client-encrypted-pem.key
+
+ssl/client-encrypted-der.key: ssl/client.key
+ openssl rsa -in ssl/client.key -outform DER -aes128 -passout 'pass:dUmmyP^#+' -out ssl/client-encrypted-der.key
+
+# Root certificate files that contains both CA certificates, for testing
+# that multiple certificates can be used.
+ssl/both-cas-1.crt: ssl/root_ca.crt ssl/client_ca.crt ssl/server_ca.crt
+ cat $^ > $@
+
+# The same, but the certs are in different order
+ssl/both-cas-2.crt: ssl/root_ca.crt ssl/server_ca.crt ssl/client_ca.crt
+ cat $^ > $@
+
+# A root certificate file for the client, to validate server certs.
+ssl/root+server_ca.crt: ssl/root_ca.crt ssl/server_ca.crt
+ cat $^ > $@
+
+# and for the server, to validate client certs
+ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
+ cat $^ > $@
+
+ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
+ cat $^ > $@
+
+#### CRLs
+
+ssl/client.crl: ssl/client-revoked.crt
+ openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
+ openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+
+ssl/server.crl: ssl/server-revoked.crt
+ openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
+ openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+
+ssl/root.crl: ssl/root_ca.crt
+ openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+
+# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
+# chain, even if some of them are empty.
+ssl/root+server.crl: ssl/root.crl ssl/server.crl
+ cat $^ > $@
+ssl/root+client.crl: ssl/root.crl ssl/client.crl
+ cat $^ > $@
+
+ssl/root+server-crldir: ssl/server.crl ssl/root.crl
+ mkdir ssl/root+server-crldir
+ cp ssl/server.crl ssl/root+server-crldir/`openssl crl -hash -noout -in ssl/server.crl`.r0
+ cp ssl/root.crl ssl/root+server-crldir/`openssl crl -hash -noout -in ssl/root.crl`.r0
+
+ssl/root+client-crldir: ssl/client.crl ssl/root.crl
+ mkdir ssl/root+client-crldir
+ cp ssl/client.crl ssl/root+client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+ cp ssl/root.crl ssl/root+client-crldir/`openssl crl -hash -noout -in ssl/root.crl`.r0
+
+ssl/server-crldir: ssl/server.crl
+ mkdir ssl/server-crldir
+ cp ssl/server.crl ssl/server-crldir/`openssl crl -hash -noout -in ssl/server.crl`.r0
+
+ssl/client-crldir: ssl/client.crl
+ mkdir ssl/client-crldir
+ cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+
+.PHONY: sslfiles-clean
+sslfiles-clean:
+ rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+ rm -rf $(SSLDIRS)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
+ rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+
+# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/test/ssl/README b/src/test/ssl/README
new file mode 100644
index 0000000..84baa47
--- /dev/null
+++ b/src/test/ssl/README
@@ -0,0 +1,102 @@
+src/test/ssl/README
+
+SSL regression tests
+====================
+
+This directory contains a test suite for SSL support. It tests both
+client-side functionality, i.e. verifying server certificates, and
+server-side functionality, i.e. certificate authorization.
+
+CAUTION: The test server run by this test is configured to listen for
+TCP connections on localhost. Any user on the same host is able to
+log in to the test server while the tests are running. Do not run this
+suite on a multi-user system where you don't trust all local users!
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+
+Run
+ make check
+or
+ make installcheck
+You can use "make installcheck" if you previously did "make install".
+In that case, the code in the installation tree is tested. With
+"make check", a temporary installation tree is built from the current
+sources and then tested.
+
+Either way, this test initializes, starts, and stops a test Postgres
+cluster that is accessible to other local users!
+
+Certificates
+============
+
+The test suite needs a set of public/private key pairs and certificates to
+run:
+
+root_ca
+ root CA, use to sign the server and client CA certificates.
+
+server_ca
+ CA used to sign server certificates.
+
+client_ca
+ CA used to sign client certificates.
+
+server-cn-only
+server-cn-and-alt-names
+server-single-alt-name
+server-multiple-alt-names
+server-no-names
+ server certificates, with small variations in the hostnames present
+ in the certificate. Signed by server_ca.
+
+server-ss
+ same as server-cn-only, but self-signed.
+
+server-password
+ same as server-cn-only, but password-protected.
+
+client
+ a client certificate, for user "ssltestuser". Signed by client_ca.
+
+client-revoked
+ like "client", but marked as revoked in the client CA's CRL.
+
+In addition, there are a few files that combine various certificates together
+in the same file:
+
+both-cas-1
+ Contains root_ca.crt, client_ca.crt and server_ca.crt, in that order.
+
+both-cas-2
+ Contains root_ca.crt, server_ca.crt and client_ca.crt, in that order.
+
+root+server_ca
+ Contains root_crt and server_ca.crt. For use as client's "sslrootcert"
+ option.
+
+root+client_ca
+ Contains root_crt and client_ca.crt. For use as server's "ssl_ca_file".
+
+client+client_ca
+ Contains client.crt and client_ca.crt in that order. For use as client's
+ certificate chain.
+
+There are also CRLs for each of the CAs: root.crl, server.crl and client.crl.
+
+For convenience, all of these keypairs and certificates are included in the
+ssl/ subdirectory. The Makefile also contains a rule, "make sslfiles", to
+recreate them if you need to make changes.
+
+TODO
+====
+
+* Allow the client-side of the tests to be run on different host easily.
+ Currently, you have to manually set up the certificates for the right
+ hostname, and modify the test file to skip setting up the server. And you
+ have to modify the server to accept connections from the client host.
+
+* Test having multiple server certificates, so that the private key chooses
+ the certificate to present to clients. (And the same in the client-side.)
diff --git a/src/test/ssl/cas.config b/src/test/ssl/cas.config
new file mode 100644
index 0000000..8c0ef6d
--- /dev/null
+++ b/src/test/ssl/cas.config
@@ -0,0 +1,64 @@
+# This file contains the configuration for all the CAs.
+
+[ req ]
+prompt = no
+
+# Extensions for CA certs
+[ v3_ca ]
+basicConstraints = CA:true
+
+# Root CA, used to sign the certificates of the intermediary server and
+# client CAs.
+[ root_ca ]
+dir = ./ssl/
+database = ./ssl/root_ca-certindex
+serial = ./ssl/root_ca.srl
+default_md = sha256
+default_days= 10000
+default_crl_days= 10000
+certificate = ./ssl/root_ca.crt
+private_key = ./ssl/root_ca.key
+new_certs_dir = ./ssl/new_certs_dir
+policy = policy_match
+email_in_dn = no
+
+# CA used to sign all the server certificates.
+[ server_ca ]
+dir = ./ssl/
+database = ./ssl/server_ca-certindex
+default_md = sha256
+default_days= 10000
+default_crl_days= 10000
+certificate = ./ssl/server_ca.crt
+private_key = ./ssl/server_ca.key
+new_certs_dir = ./ssl/new_certs_dir
+serial = ./ssl/server_ca.srl
+policy = policy_match
+email_in_dn = no
+unique_subject = no
+crl = ./ssl/server.crl
+
+# CA used to sign all the client certificates.
+[ client_ca ]
+dir = ./ssl/
+database = ./ssl/client_ca-certindex
+default_md = sha256
+default_days= 10000
+default_crl_days= 10000
+certificate = ./ssl/client_ca.crt
+private_key = ./ssl/client_ca.key
+new_certs_dir = ./ssl/new_certs_dir
+serial = ./ssl/client_ca.srl
+policy = policy_match
+email_in_dn = no
+unique_subject = no
+crl = ./ssl/client.crl
+
+# This is common for all CAs.
+[ policy_match ]
+countryName = optional
+stateOrProvinceName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = optional
+emailAddress = optional
diff --git a/src/test/ssl/client-dn.config b/src/test/ssl/client-dn.config
new file mode 100644
index 0000000..ee2df54
--- /dev/null
+++ b/src/test/ssl/client-dn.config
@@ -0,0 +1,16 @@
+# An OpenSSL format CSR config file for creating a client certificate.
+#
+# The certificate is for user "ssltestuser-dn" with a multi-part DN
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+O = PGDG
+0.OU = Engineering
+1.OU = Testing
+CN = ssltestuser-dn
+
+# no extensions in client certs
+[ v3_req ]
diff --git a/src/test/ssl/client.config b/src/test/ssl/client.config
new file mode 100644
index 0000000..2e9d40d
--- /dev/null
+++ b/src/test/ssl/client.config
@@ -0,0 +1,13 @@
+# An OpenSSL format CSR config file for creating a client certificate.
+#
+# The certificate is for user "ssltestuser".
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+CN = ssltestuser
+
+# no extensions in client certs
+[ v3_req ]
diff --git a/src/test/ssl/client_ca.config b/src/test/ssl/client_ca.config
new file mode 100644
index 0000000..ef93d59
--- /dev/null
+++ b/src/test/ssl/client_ca.config
@@ -0,0 +1,11 @@
+# An OpenSSL format CSR config file for creating the client root certificate.
+# This configuration file is also used when operating the CA.
+#
+# This certificate is used to sign client certificates. It is self-signed.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+CN = Test CA for PostgreSQL SSL regression test client certs
diff --git a/src/test/ssl/root_ca.config b/src/test/ssl/root_ca.config
new file mode 100644
index 0000000..187a9b8
--- /dev/null
+++ b/src/test/ssl/root_ca.config
@@ -0,0 +1,13 @@
+# A root certificate authority. The server and client CA's certificates
+# are signed by this root CA.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+CN = Test root CA for PostgreSQL SSL regression test suite
+
+# Extensions for CA certs
+[ v3_ca ]
+basicConstraints = CA:true
diff --git a/src/test/ssl/server-cn-and-alt-names.config b/src/test/ssl/server-cn-and-alt-names.config
new file mode 100644
index 0000000..6734251
--- /dev/null
+++ b/src/test/ssl/server-cn-and-alt-names.config
@@ -0,0 +1,25 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains both a CN, and SANs.
+
+
+[ req ]
+distinguished_name = req_distinguished_name
+req_extensions = v3_req
+prompt = no
+
+[ req_distinguished_name ]
+# Note: According to RFC 2818 and 6125, the CN is ignored, when SANs are
+# present. In practice, the hostname that's put in the CN field is always
+# also listed as a SAN, but we intentionally don't do that here so that we
+# can test adherence to those RFCs.
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/server-cn-only.config b/src/test/ssl/server-cn-only.config
new file mode 100644
index 0000000..1e5d582
--- /dev/null
+++ b/src/test/ssl/server-cn-only.config
@@ -0,0 +1,13 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
diff --git a/src/test/ssl/server-multiple-alt-names.config b/src/test/ssl/server-multiple-alt-names.config
new file mode 100644
index 0000000..fe230df
--- /dev/null
+++ b/src/test/ssl/server-multiple-alt-names.config
@@ -0,0 +1,20 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains multiple SANs, and no CN.
+
+[ req ]
+distinguished_name = req_distinguished_name
+req_extensions = v3_req
+prompt = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = dns1.alt-name.pg-ssltest.test
+DNS.2 = dns2.alt-name.pg-ssltest.test
+DNS.3 = *.wildcard.pg-ssltest.test
diff --git a/src/test/ssl/server-no-names.config b/src/test/ssl/server-no-names.config
new file mode 100644
index 0000000..2a71125
--- /dev/null
+++ b/src/test/ssl/server-no-names.config
@@ -0,0 +1,16 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate contains no CN, nor SANs. Not very useful, but make
+# sure the client can handle it gracefully.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+
+[ alt_names ]
diff --git a/src/test/ssl/server-revoked.config b/src/test/ssl/server-revoked.config
new file mode 100644
index 0000000..47ef646
--- /dev/null
+++ b/src/test/ssl/server-revoked.config
@@ -0,0 +1,15 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This is identical to server-cn-only certificate, but this one is revoked
+# later.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+CN = common-name.pg-ssltest.test
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
diff --git a/src/test/ssl/server-single-alt-name.config b/src/test/ssl/server-single-alt-name.config
new file mode 100644
index 0000000..2b3ddf4
--- /dev/null
+++ b/src/test/ssl/server-single-alt-name.config
@@ -0,0 +1,18 @@
+# An OpenSSL format CSR config file for creating a server certificate.
+#
+# This certificate has a single SAN, and no CN.
+
+[ req ]
+distinguished_name = req_distinguished_name
+req_extensions = v3_req
+prompt = no
+
+[ req_distinguished_name ]
+OU = PostgreSQL test suite
+
+# For Subject Alternative Names
+[ v3_req ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = single.alt-name.pg-ssltest.test
diff --git a/src/test/ssl/server_ca.config b/src/test/ssl/server_ca.config
new file mode 100644
index 0000000..d6126d5
--- /dev/null
+++ b/src/test/ssl/server_ca.config
@@ -0,0 +1,11 @@
+# An OpenSSL format CSR config file for creating the server root certificate.
+# This configuration file is also used when operating the CA.
+#
+# This certificate is used to sign server certificates. It is self-signed.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+CN = Test CA for PostgreSQL SSL regression test server certs
diff --git a/src/test/ssl/ssl/.gitignore b/src/test/ssl/ssl/.gitignore
new file mode 100644
index 0000000..af753d4
--- /dev/null
+++ b/src/test/ssl/ssl/.gitignore
@@ -0,0 +1,3 @@
+/*.old
+/new_certs_dir/
+/client*_tmp.key
diff --git a/src/test/ssl/ssl/both-cas-1.crt b/src/test/ssl/ssl/both-cas-1.crt
new file mode 100644
index 0000000..37ffa10
--- /dev/null
+++ b/src/test/ssl/ssl/both-cas-1.crt
@@ -0,0 +1,57 @@
+-----BEGIN CERTIFICATE-----
+MIIDHjCCAgagAwIBAgIUEAgXJ/ibw6TVTUoomlBsPMfVTlMwDQYJKoZIhvcNAQEL
+BQAwQDE+MDwGA1UEAww1VGVzdCByb290IENBIGZvciBQb3N0Z3JlU1FMIFNTTCBy
+ZWdyZXNzaW9uIHRlc3Qgc3VpdGUwHhcNMTgxMTI3MTM0MDU0WhcNNDYwNDE0MTM0
+MDU0WjBAMT4wPAYDVQQDDDVUZXN0IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NM
+IHJlZ3Jlc3Npb24gdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALZ81vKKBJlxgjwuNoK67I4IE9zfSLb0eHbgZwZxDVzdmFejARrHlWk3
++MK7Nav7RLSJ990am33zb58CTHc7YYVlBp07+PwLXzypqWkhYfok1OYYjyjCrFDs
+sjcJI3hRCZNEz+wYsG+tdYWJ+gRPQOWfh0YfO2rFgXAIMLiF6lyWzf1eOM+OjYrF
+/eyzwbMaJkkGa/AyZKz3wZiPq0jTuYLVmH4MK7MBOsUfSmsBsn/ohyRCQzM+ol0v
+Qlsrulj8usponRPDh9ng4PB5OSgR79YimQZnASQzJxiUvMADrKL5L6KwLxJlzbqY
+R0b5mLh8KBzBQmSh3Aj2e2I7Z17hdaMCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zAN
+BgkqhkiG9w0BAQsFAAOCAQEASzA7ApbuKn8lkC706gRL37a33yTZZ8rjZ4dnvCtq
+6OltlYDJ8IndotKbLH0SUEAxrvcaFnMt7AX9pRf2sGBKbY8zcxqPfsvzVehgx4Ea
+1RYksFW4h97jj1a1RKukTKuEOEEipbxwo0rLxfjvdaf2izqchJsLGtbocIZf0bD8
+djbE9jOLkx7saL08qC8ECrf9utsee+LJCsUYbNgYyIItEy6yVnmF/ICz93Utn1cI
+RfqZr1lM2Ia2LP9eDTmiuR9m+/MzkeRvvJHonNrRJHlcggtYHICvYioh9/jALBcm
+wZ+hTUePVqy4hOCBJ975CXjfKFN4MKQAdeB3EO5eBYAD3Q==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAfWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBAMT4wPAYDVQQDDDVUZXN0
+IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzdWl0
+ZTAeFw0xODExMjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC75x7yuQ1+gK8j
+1aO6D2nGym2x2OFniztlnx4PlWWWivIrYlxy6xAhfrVdjwjc5mMtOCrVKllsdC+S
+1EfqCZTu1+gF0SdG5cHuJ2Rtg4oisLSF221v/msPONAa1dObChWsJwme87VaFrL+
+B2yipfW1PUxM9a7/p19CRBcDQ+LNW+YFqwARByHGq1wfatJzpM8TXe+XEnRfW9KX
+P3a5PqR6evFQOzjcAf+QBJ0hAEddUDhdYECbs1GrApfsEHBuwXabdCH41j0F/0yc
+kctydWfBl2Vbmd3sfsFMHjde+SJhqxyq6xiSL59jnx4ZKmtn9VSOYbGmBCdBdYK7
+RTcnJQv9AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+AB6zh/Jw+t232100Tztr7wJoKH8DL1hnSm3e7omdj0WlKfuZwTqDuMzB3H4LOSnS
+A00XpyMAGAJC6yRjS8pt+pjY5Jt6ouSqf6wNq0mF0jiIDeg1k/GNEjigx+0ITqbE
+lUJ56AcpoBNhOwBjOCRFh4JuspHZqHXgUNYTEicClbV+lZwoMIIK1e6FYRZDqMtL
++34GtZACImqvRkP5alqQg7hJUM1zbDVf2qebY4cfSu4OfTu6Og6KrL8Cu6bqR2et
+0a/TbthHYz1QGDYRoVTSP4uWoG9M1ZbsA/bNE2eqcrQj+dJ4AmIIr8Yl8mrwo/FZ
+SvgeLMlQY7UNwLUDtwy9QkI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAfWgAwIBAgIBATANBgkqhkiG9w0BAQsFADBAMT4wPAYDVQQDDDVUZXN0
+IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzdWl0
+ZTAeFw0xODExMjcxMzQwNTRaFw00NjA0MTQxMzQwNTRaMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiSnYZbmc9vpCt
+Ku1sKV9l663JCceubhMw8Gg16kV0hXEFf/TgGC4zkiYNHN7+G45YD7Nq0kBCq3dH
+t2wPCc6c8pQoI64dfprVqPkvzoe1WBpZNetkUTk20v08jNeRa7XdRbRR6we1s9VG
+/prp8Hs2mmHqEfLuI9lvTT0Dz+VMmfFI8Lf278r+w+qOtVloAkX7AOyoLEJlNS0B
+QW9YWdH9N5ctaUXMG6lLV2OAjs+W1smpKfpIpMCA1lPGlElu70hynon/nQQvBP77
+SfQpZVc0esM18jkZpr5LEKUCw+x6LaMsqmBHpAULfCffxn2r0uMBW4L4VaGg3W6F
+h6iuJwRfAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+AFlcKTaU/Ug3Q0hr3P1UQ6dWyK4aVn9rs4jvVfFl0a0RnbBowqK2C+zQVUWYTcjo
+KHREVje65goj6VzRB6ko/9mAQ6PZP8jRuRhfCmvmvSQ/mWdgPzSRsUh9MwgEm9c2
+vNbqwaznEU8cYZnLpHiR9O5S7/qWWxehjYtxk5Eb4J006YglYfHnhrRFJvPbiqlf
+IOEivZ7gIVfvaOTbLjmN2kLOnzdlwpXGjxxg4Nu9ZhXOhfrplzUvRfmqvVsDiHXb
+USIdX+OFZZqr64IKG4drT4K4Bt2wupOEyX4ZFsUXXd+Hgq83SWmV4wzflcpmGkLC
+JZ3CEMu8/WA5uQBXdQUozlE=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/both-cas-2.crt b/src/test/ssl/ssl/both-cas-2.crt
new file mode 100644
index 0000000..2f2723f
--- /dev/null
+++ b/src/test/ssl/ssl/both-cas-2.crt
@@ -0,0 +1,57 @@
+-----BEGIN CERTIFICATE-----
+MIIDHjCCAgagAwIBAgIUEAgXJ/ibw6TVTUoomlBsPMfVTlMwDQYJKoZIhvcNAQEL
+BQAwQDE+MDwGA1UEAww1VGVzdCByb290IENBIGZvciBQb3N0Z3JlU1FMIFNTTCBy
+ZWdyZXNzaW9uIHRlc3Qgc3VpdGUwHhcNMTgxMTI3MTM0MDU0WhcNNDYwNDE0MTM0
+MDU0WjBAMT4wPAYDVQQDDDVUZXN0IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NM
+IHJlZ3Jlc3Npb24gdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALZ81vKKBJlxgjwuNoK67I4IE9zfSLb0eHbgZwZxDVzdmFejARrHlWk3
++MK7Nav7RLSJ990am33zb58CTHc7YYVlBp07+PwLXzypqWkhYfok1OYYjyjCrFDs
+sjcJI3hRCZNEz+wYsG+tdYWJ+gRPQOWfh0YfO2rFgXAIMLiF6lyWzf1eOM+OjYrF
+/eyzwbMaJkkGa/AyZKz3wZiPq0jTuYLVmH4MK7MBOsUfSmsBsn/ohyRCQzM+ol0v
+Qlsrulj8usponRPDh9ng4PB5OSgR79YimQZnASQzJxiUvMADrKL5L6KwLxJlzbqY
+R0b5mLh8KBzBQmSh3Aj2e2I7Z17hdaMCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zAN
+BgkqhkiG9w0BAQsFAAOCAQEASzA7ApbuKn8lkC706gRL37a33yTZZ8rjZ4dnvCtq
+6OltlYDJ8IndotKbLH0SUEAxrvcaFnMt7AX9pRf2sGBKbY8zcxqPfsvzVehgx4Ea
+1RYksFW4h97jj1a1RKukTKuEOEEipbxwo0rLxfjvdaf2izqchJsLGtbocIZf0bD8
+djbE9jOLkx7saL08qC8ECrf9utsee+LJCsUYbNgYyIItEy6yVnmF/ICz93Utn1cI
+RfqZr1lM2Ia2LP9eDTmiuR9m+/MzkeRvvJHonNrRJHlcggtYHICvYioh9/jALBcm
+wZ+hTUePVqy4hOCBJ975CXjfKFN4MKQAdeB3EO5eBYAD3Q==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAfWgAwIBAgIBATANBgkqhkiG9w0BAQsFADBAMT4wPAYDVQQDDDVUZXN0
+IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzdWl0
+ZTAeFw0xODExMjcxMzQwNTRaFw00NjA0MTQxMzQwNTRaMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiSnYZbmc9vpCt
+Ku1sKV9l663JCceubhMw8Gg16kV0hXEFf/TgGC4zkiYNHN7+G45YD7Nq0kBCq3dH
+t2wPCc6c8pQoI64dfprVqPkvzoe1WBpZNetkUTk20v08jNeRa7XdRbRR6we1s9VG
+/prp8Hs2mmHqEfLuI9lvTT0Dz+VMmfFI8Lf278r+w+qOtVloAkX7AOyoLEJlNS0B
+QW9YWdH9N5ctaUXMG6lLV2OAjs+W1smpKfpIpMCA1lPGlElu70hynon/nQQvBP77
+SfQpZVc0esM18jkZpr5LEKUCw+x6LaMsqmBHpAULfCffxn2r0uMBW4L4VaGg3W6F
+h6iuJwRfAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+AFlcKTaU/Ug3Q0hr3P1UQ6dWyK4aVn9rs4jvVfFl0a0RnbBowqK2C+zQVUWYTcjo
+KHREVje65goj6VzRB6ko/9mAQ6PZP8jRuRhfCmvmvSQ/mWdgPzSRsUh9MwgEm9c2
+vNbqwaznEU8cYZnLpHiR9O5S7/qWWxehjYtxk5Eb4J006YglYfHnhrRFJvPbiqlf
+IOEivZ7gIVfvaOTbLjmN2kLOnzdlwpXGjxxg4Nu9ZhXOhfrplzUvRfmqvVsDiHXb
+USIdX+OFZZqr64IKG4drT4K4Bt2wupOEyX4ZFsUXXd+Hgq83SWmV4wzflcpmGkLC
+JZ3CEMu8/WA5uQBXdQUozlE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAfWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBAMT4wPAYDVQQDDDVUZXN0
+IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzdWl0
+ZTAeFw0xODExMjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC75x7yuQ1+gK8j
+1aO6D2nGym2x2OFniztlnx4PlWWWivIrYlxy6xAhfrVdjwjc5mMtOCrVKllsdC+S
+1EfqCZTu1+gF0SdG5cHuJ2Rtg4oisLSF221v/msPONAa1dObChWsJwme87VaFrL+
+B2yipfW1PUxM9a7/p19CRBcDQ+LNW+YFqwARByHGq1wfatJzpM8TXe+XEnRfW9KX
+P3a5PqR6evFQOzjcAf+QBJ0hAEddUDhdYECbs1GrApfsEHBuwXabdCH41j0F/0yc
+kctydWfBl2Vbmd3sfsFMHjde+SJhqxyq6xiSL59jnx4ZKmtn9VSOYbGmBCdBdYK7
+RTcnJQv9AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+AB6zh/Jw+t232100Tztr7wJoKH8DL1hnSm3e7omdj0WlKfuZwTqDuMzB3H4LOSnS
+A00XpyMAGAJC6yRjS8pt+pjY5Jt6ouSqf6wNq0mF0jiIDeg1k/GNEjigx+0ITqbE
+lUJ56AcpoBNhOwBjOCRFh4JuspHZqHXgUNYTEicClbV+lZwoMIIK1e6FYRZDqMtL
++34GtZACImqvRkP5alqQg7hJUM1zbDVf2qebY4cfSu4OfTu6Og6KrL8Cu6bqR2et
+0a/TbthHYz1QGDYRoVTSP4uWoG9M1ZbsA/bNE2eqcrQj+dJ4AmIIr8Yl8mrwo/FZ
+SvgeLMlQY7UNwLUDtwy9QkI=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client+client_ca.crt b/src/test/ssl/ssl/client+client_ca.crt
new file mode 100644
index 0000000..2804527
--- /dev/null
+++ b/src/test/ssl/ssl/client+client_ca.crt
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIICzDCCAbQCAQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UEAww3VGVzdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IGNsaWVudCBjZXJ0czAe
+Fw0xODExMjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMBYxFDASBgNVBAMMC3NzbHRl
+c3R1c2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtIugLqHywEAE
+vyRZGMVAkdk1zCa5FFaPOEFhHiAMpwFOEIEi4Svk9kSSRecmeJcody1sLNoFqtTA
+b5tYaDoGIVZfc8/kxm8sbsTE/3JOsON3CMqjOQkI1ZKjDSF1gtrGSmatgjqsMnlP
+UJkFEsPhFg6NTf1ZUjFiQeWEli0fQJ2/k+7MI4S0t0pDJJJWrqF4l6eSgu8rsBoX
+XHy4OLAz6j23r2k5FZs6H/poII95ia+E8hG8SrJmMa88naRdq7hHW802Z6lEhnRW
+ND+tDGjt0ZaTfxx+CxN4UjgbboOJifTykVHjuzBR1+IzLHcxoZCLP1cjadSqMz5b
+ziJTGtHzYwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAcIGps6BnsRxkN5sphg6GK
+tzDvp2IUyOu5oeAHdJLT5JFZhKKzhDD4KiOv+XWzdHcSAl3xMqAqnFdSTCt2vtc+
+rk04eyVWJALyf6oPT60Vn5sFaaxlTg1+tnZMCCycDxM6lc/6onzgp6DUWGozlgSh
+eNgCyaNU73VTuMgd+s/QrZ5HCr0OPAb3aWRQy7hVZeOniNBXWrO/CC2Swfwz7jU3
+dvLAWYENUvZlE2S7HnQGclGIJb38qFCnquuSgmO9yT30Lmmwp33k5/evN9cNQMxU
+c4ChYCaabOGXUaBJNzJAYMEUHh+o+LPgFF2iB0mL7FAUip9XsjOiOwcrbusM/g+2
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAfWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBAMT4wPAYDVQQDDDVUZXN0
+IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzdWl0
+ZTAeFw0xODExMjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC75x7yuQ1+gK8j
+1aO6D2nGym2x2OFniztlnx4PlWWWivIrYlxy6xAhfrVdjwjc5mMtOCrVKllsdC+S
+1EfqCZTu1+gF0SdG5cHuJ2Rtg4oisLSF221v/msPONAa1dObChWsJwme87VaFrL+
+B2yipfW1PUxM9a7/p19CRBcDQ+LNW+YFqwARByHGq1wfatJzpM8TXe+XEnRfW9KX
+P3a5PqR6evFQOzjcAf+QBJ0hAEddUDhdYECbs1GrApfsEHBuwXabdCH41j0F/0yc
+kctydWfBl2Vbmd3sfsFMHjde+SJhqxyq6xiSL59jnx4ZKmtn9VSOYbGmBCdBdYK7
+RTcnJQv9AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+AB6zh/Jw+t232100Tztr7wJoKH8DL1hnSm3e7omdj0WlKfuZwTqDuMzB3H4LOSnS
+A00XpyMAGAJC6yRjS8pt+pjY5Jt6ouSqf6wNq0mF0jiIDeg1k/GNEjigx+0ITqbE
+lUJ56AcpoBNhOwBjOCRFh4JuspHZqHXgUNYTEicClbV+lZwoMIIK1e6FYRZDqMtL
++34GtZACImqvRkP5alqQg7hJUM1zbDVf2qebY4cfSu4OfTu6Og6KrL8Cu6bqR2et
+0a/TbthHYz1QGDYRoVTSP4uWoG9M1ZbsA/bNE2eqcrQj+dJ4AmIIr8Yl8mrwo/FZ
+SvgeLMlQY7UNwLUDtwy9QkI=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0 b/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
new file mode 100644
index 0000000..a667680
--- /dev/null
+++ b/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
@@ -0,0 +1,11 @@
+-----BEGIN X509 CRL-----
+MIIBnjCBhzANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0xODEx
+MjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMBQwEgIBAhcNMTgxMTI3MTM0MDU1WjAN
+BgkqhkiG9w0BAQsFAAOCAQEAXjLxA9Qc6gAudwUHBxMIq5EHBcuNEX5e3GNlkyNf
+8I0DtHTPfJPvmAG+i6lYz//hHmmjxK0dR2ucg79XgXI/6OpDqlxS/TG1Xv52wA1p
+xz6GaJ2hC8Lk4/vbJo/Rrzme2QsI7xqBWya0JWVrehttqhFxPzWA5wID8X7G4Kb4
+pjVnzqYzn8A9FBiV9t10oZg60aVLqt3kbyy+U3pefvjhj8NmQc7uyuVjWvYZA0vG
+nnDUo4EKJzHNIYLk+EfpzKWO2XAWBLOT9SyyNCeMuQ5p/2pdAt9jtWHenms2ajo9
+2iUsHS91e3TooP9yNYuNcN8/wXY6H2Xm+dCLcEnkcr7EEw==
+-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/client-der.key b/src/test/ssl/ssl/client-der.key
new file mode 100644
index 0000000..c9be5f9
--- /dev/null
+++ b/src/test/ssl/ssl/client-der.key
Binary files differ
diff --git a/src/test/ssl/ssl/client-dn.crt b/src/test/ssl/ssl/client-dn.crt
new file mode 100644
index 0000000..f2f6a62
--- /dev/null
+++ b/src/test/ssl/ssl/client-dn.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAe4CAQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UEAww3VGVzdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IGNsaWVudCBjZXJ0czAe
+Fw0yMTAzMDUyMDUyNDVaFw00ODA3MjEyMDUyNDVaMFAxDTALBgNVBAoMBFBHREcx
+FDASBgNVBAsMC0VuZ2luZWVyaW5nMRAwDgYDVQQLDAdUZXN0aW5nMRcwFQYDVQQD
+DA5zc2x0ZXN0dXNlci1kbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AMRLriq2Sh8+N4bhVtRUp/MAEsLQK6u/GotMSmiSr9K31YBYOvNzw8liKt4Rmnh5
+zmsdXJBW8erPNpkUAy9tFRCAx0YobhWCSfyX3orEdrhDrLFihA62zXQC69T0u4Yp
+PSXGd0yCAcOZERQ4CQVgqnsh7Kmx5QaQnqxaz4OVPArWFJP4RQBT/l+r+kCeAn6h
+qvbSbxY3FoCElQq0EF5x1F2pjL+HcBvjeI+GP430gVeJJX0RaG14Fp4v9MQT6zv/
+gvvjHC8l7YSJUROjeUzLZpUnj/ik4yrtT4av/TDGTSOpGs5qEATqk4hxAUEWw6TJ
+RoLh3Oq2N5KuzDmKBBskLX0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAL2H54oyx
+pNkcgFF79lwc4c/Jda7j0wrZQIw5CWwO0MdCozJGRIEAA5WXA8b5THo1ZkaWv+sh
+lWnCOflBtGnEpD7dUpMW9lxGL5clMeMf3CoNYBb7zBofm+oTJytCzXHNftB4hCZj
+pvN79bNT4msWbmxDyi75nfbEfzK1BKnfCg+DWBBjEnHC8VzgDq6ACN6FEoyFb+fr
+dlDoof+S7k8jYAzhxwySI5DnMzr9OIwnepWfx9HENsasAighc8vFSEouShvsOlYS
+L0OIb9Tn6M5q1tWoLHulQsQYDPzaO/1M7ubsr5xCx1ReDK4gaNwS3YXn/2KE9Kco
+aKCrL89AjQrJPA==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client-dn.key b/src/test/ssl/ssl/client-dn.key
new file mode 100644
index 0000000..1d67ef0
--- /dev/null
+++ b/src/test/ssl/ssl/client-dn.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAxEuuKrZKHz43huFW1FSn8wASwtArq78ai0xKaJKv0rfVgFg6
+83PDyWIq3hGaeHnOax1ckFbx6s82mRQDL20VEIDHRihuFYJJ/JfeisR2uEOssWKE
+DrbNdALr1PS7hik9JcZ3TIIBw5kRFDgJBWCqeyHsqbHlBpCerFrPg5U8CtYUk/hF
+AFP+X6v6QJ4CfqGq9tJvFjcWgISVCrQQXnHUXamMv4dwG+N4j4Y/jfSBV4klfRFo
+bXgWni/0xBPrO/+C++McLyXthIlRE6N5TMtmlSeP+KTjKu1Phq/9MMZNI6kazmoQ
+BOqTiHEBQRbDpMlGguHc6rY3kq7MOYoEGyQtfQIDAQABAoIBABqL3Zb7JhUJlfrQ
+uKxocnojdWYRPwawBof2Hk38IHkP0XjU9cv8yOqQMxnrKYfHeUn1I5KFn5vQwCJ9
+mVytlN6xe8GaMCEKiLT3WOpNXXzX8h/fIdrXj/tzda9MFZw0MYfNSk73egOYzL1+
+QoIOq5+RW+8rFr0Hi93lPhEeeotAYWDQgx9Ye/NSW6vK2m47hdBKf9SBsWs+Vafa
+mC9Bf4LQqRYSJZee1zDwIh+Om7/JTsjMZYU0/lpycRz7V5uHbamXKlOXF54ow3Wn
+CJ9eVVWo7sb3CaeJ0p2sHIFp89ybMQ2vvmNr6aJNtZWd5WYxsjKs40rVq6DiUlFn
+T6CK7uECgYEA/Ks4/OnZnorhaHwYTs0LqiPSM7oZw4qchCNDMoE3WngsaZoWUKmr
+2JTY6uYP/B+oWgwPBdDiPRDeGqtVNZSAVsZEDMbiqZxwHaLi9OKJ7sKgK8Q6ANV1
+q5qgH1yXXygWhlol/Nf9bbnGWWoN+33zvnADeKRcT/1gZLEQpJ46DHUCgYEAxuIx
+k/EOOT9kyC5WrBDY3l7veb/WGRQgXTXiCJaO4d7IYh8UpUXlg0ZYF4RfeKRsSd07
+n9QdW6ImrtDloNyG6HnDknYsPRUs8JcuuyrxaOsZ/p9LS76ItNV7gzREf4N/7jrD
+c6TJappgXm+dgXg6ENuyk05hzjT6qdvm9V80m+kCgYEA7kfXRYSP61lT/AJTtjTf
+FEQV3xxZYbRdqKvMmluLxTDhyXE8LDPm0SiGbPgsCPwd+1W18SktwqMeoo4DnLUA
+V1VBJb+GUKgsf3Z2jLT7mYRIIx46CUFFaGE5MnpScrXOkEOB4bIb2RfCu94tc4gz
+jtv6GhL+z5zHBA6MAIMLgWUCgYAlynNLPkHKpP4cf5mehnD/CCEPDGG9UDK6I3P4
+18r8pl2DL463vOlYoXQ5u8B8ZxngizY6L48Ii244R59qipzj7cc4vFW5oZ1xdfi+
+PfGzUwEUfeZL1T+axPn8O2FMrYsQlH/xKH3RUNZA+4p9QIAgFe7/yKQTD8QVpKBl
+PZr8iQKBgBjdrgMt1Az98ECXJCjM4uui2S9UenNQVmhmxgZUpHqfNk+WEvIIthDi
+FEJPSTHyhTI9XIrhhwNkW3UZMjMndAiNylXGfJdr/xGwLM57t5HhGgljSboV7Mnw
+RFnh2FZxa3i/8g+4lAPZNwU0W/JU46wgg4C2Eu/Ne7jA8XUXYu9t
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/client-encrypted-der.key b/src/test/ssl/ssl/client-encrypted-der.key
new file mode 100644
index 0000000..c9be5f9
--- /dev/null
+++ b/src/test/ssl/ssl/client-encrypted-der.key
Binary files differ
diff --git a/src/test/ssl/ssl/client-encrypted-pem.key b/src/test/ssl/ssl/client-encrypted-pem.key
new file mode 100644
index 0000000..1e7052a
--- /dev/null
+++ b/src/test/ssl/ssl/client-encrypted-pem.key
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,E619306A930B60F360BF805500BA5659
+
+B9aYmIdIoF7hT9tJARMQWE7Ii7g+KDNaF4U0ljBsxgbtMyi9DQrlrFsbUO0Wy6iO
+UY/h57UA1pk7yF+rwkTK0L2t0j/d+HZc3ddsN3cZ040PmX8+8QZJWRUs2ywTLa4O
+JxPm2rUxLSeVa+FY9Nr1Cl6meQ2JS7MA7KBNuriBWNleGGgkbBMaH7zq98aOJmaz
+l02J2wrJ5STP2UI8uEaT/UtAgLInlAljCSg5oe5cj4u9UyUkRN7fj4mexq1r5YNU
+zTu7GrgcAdXrhsAhg9mAJol4frwsQuEiJbVIurAAvCrJk7Gm8xVjKCN1stDOASAY
+aawO1huIdTzjbGXFHBtJ4YuRClXZr5ij6kN+KeQaS+JLjehsAb6762l9wUPP5Bxv
+8c6CCxc+U4ndN0ZQPsx0UrJ/AYO1s12mebuKZvIdNoYdLIqJLfX/HSrzaXw6XA8b
+gAvVOruKGq12v71OrIdahxSzRs7s6GODGynSayFprn3CK+GZJumwQ0EK+fBzrzB1
+8JTp98qwMYfSuDmGl8VbT9k8OZFZbDD4k5wj8fHx5R4zkdgfNqBNAKXPrwm5uRT8
++0mnYdP3ZnihnZnAoZvGXOE77TcZ/N9fLvwkBpwPmtftbn10HwlwXQgmn1ijMj60
+ZOYo1fvKJMmvCr+NUtyJALIvUdLQmjWx0PoZetIb24KBkTkr2ciU1d1RDEwOfffZ
+jwTfcJU/AXnxPBR6MBT9a+YkaMiOU0JF7vs/x0hG/o8GsXQJB/G7Vzakg0hxQ1WF
+KU0jInXPf2uCiBMEwuWRPHh25wspLjsHgt5pD55vE/M9Q7LFOez/9/RQqmmjDjZH
+sLJtdAjN57aaIhtzbYIYa7K7Eu5v0NrZ5++wP3h82aTy9PIlSmRGY8WiZSDDir0P
+w+PBP7JN/3ifqXURUmSDGbfdArbyuuF79Say6N9ijFeBAZrCgauw3jBs1dhusGJ/
+T6wh8mjdGf8SRm9SQdGuIyK7M657z3P0WRlpHN4beeGpzgGVexqjiyvtwQNH8kps
+3EDNwTe3HJMWf7G2FNjqtM0h3fnaB7d+prfzZIL5Y1Somgfiljp7zG/FfkYEybK6
+8OvW6O8byCSqJzugUa5HCv//iPYFrcALAXtva4KXtfauGhKmWpn3Wa5AW9/034H6
+QW/A8mcKSMKhGixZj5MZKGTMA9cRus3IRTAYnhCd5njJ1N/o67wwTGVuXVu6ExrM
+wY/WjkRrDlRopqo0U3wodHjfZ8/837rINwmcqzXTxasu+ApWUVZFuuQh/q3i8aTv
+BzFVOfLylxpIsoQHBQvNdM/u0HGXbw7wyjs6n+LCjeGwRuxKkoYlKf5cItNLDNvF
+6LYwA44BJ3/XfUSVZRD8PAVp5haUgpesPym1G5QdvYN4rWE6lsAtGSZDatWvaCsI
+S0qTwLFbw9BvclwkvJicvLwAmKiGMDyAwGNCPLnG7nZ48to4dXD93LmgC/mnENbp
+7EgW7fUtMvz0Lt2Xcd26ZTlJdOkT3sdKPSDxhgqsQoI4dQSmB4Fz40HsFvFtTCuF
+FXMFXjSkjiKrdfI+CQ1tJGXKpYAod8PcZ89vN3TjxehwhK6GxS0CiOJ+phh6q22i
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/client-revoked.crt b/src/test/ssl/ssl/client-revoked.crt
new file mode 100644
index 0000000..14857a3
--- /dev/null
+++ b/src/test/ssl/ssl/client-revoked.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICzDCCAbQCAQIwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UEAww3VGVzdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IGNsaWVudCBjZXJ0czAe
+Fw0xODExMjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMBYxFDASBgNVBAMMC3NzbHRl
+c3R1c2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoBcmY2Z+qa+l
+UB5YSYnGLt96S7axkoDvIzLJkwJugGqw1U72A6lAUTyAPVntsmbhoMpDEHK6ylg8
+U4HC3L1hbhSpFriTITJ3TzH4+wdDH1KZYlM2k0gfrKrksJyZ7ftAyuBvzBRlFbBe
+xopR9VQjqgAuNKByJswldOe0KwP0nmb/TtT9lkAt7XjrSut5MUezFVnvTxabm7tQ
+ciDG+8QqE0b8lH3N3VOXWZWCeXPRrwboO3baAmcue4V20N0ALARP+QZNElBa7Jq+
+l77VNjneRk07jjaE7PCGVlWfPggppZos1Ay1sb2JhK0S9pZrynQT/ck3qhG4QuKp
+cmn/Bbe/8wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBySTwOO9zSFCtfRjbbblDx
+AK2ttILR0ZJXnvzjNjuErsT9qeXaq2t/iG/vmhH5XDjaefXFLCLqFunvcg6cIz1A
+HhAw+JInfyk3TUpDaX6M0X8qj184e4kXzVc83Afa3LiP5JkirzCSv6ErqAHw2VVd
+bZbZUwMfQLpWHVqXK89Pb7q791H4VeEx9CLxtZ2PSr2GCdpFbVMJvdBPChD2Re1A
+ELcbMZ9iOq2AUN/gxrt7HnE3dRoGQk6AJOfvhi2eZcVWiLtITScdPk1nYcNxGid3
+BWW+tdLbjmSe2FXNfDwBTvuHh5A9S399X5l/nLAng2iTGSvIm1OgUnC2oWsok3EI
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client-revoked.key b/src/test/ssl/ssl/client-revoked.key
new file mode 100644
index 0000000..a915c6f
--- /dev/null
+++ b/src/test/ssl/ssl/client-revoked.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAoBcmY2Z+qa+lUB5YSYnGLt96S7axkoDvIzLJkwJugGqw1U72
+A6lAUTyAPVntsmbhoMpDEHK6ylg8U4HC3L1hbhSpFriTITJ3TzH4+wdDH1KZYlM2
+k0gfrKrksJyZ7ftAyuBvzBRlFbBexopR9VQjqgAuNKByJswldOe0KwP0nmb/TtT9
+lkAt7XjrSut5MUezFVnvTxabm7tQciDG+8QqE0b8lH3N3VOXWZWCeXPRrwboO3ba
+Amcue4V20N0ALARP+QZNElBa7Jq+l77VNjneRk07jjaE7PCGVlWfPggppZos1Ay1
+sb2JhK0S9pZrynQT/ck3qhG4QuKpcmn/Bbe/8wIDAQABAoIBAQCVmUx8MrlGZCa9
+Gb4y6hZSku87dXu2hdnyMHGBeRI92nVov6LRhQXfZAQKUND4l39cu+WzpyK6F344
+ItgvYqF7Nr9TxiNnMDuhu/cIzZ6B1LQU1+H1+73toryV9aE6bEH904FlWeGkRO4r
+5fH0qS2ynPyQnSZO7xJJjoQkdkvPAMId7ovA5kKRLqRbXORGkbHhDdzoBB5ogRmV
+CInIfO7mI85OJXXtggPNb7L9Tmb39/i+D/pagIWehzb9WOSnMnJ6//ORkEntqEOl
+yaalJVF7uhiQbMcqA+ZJN9WwQPeLOeYowGD48cCsdjCgOhxM0EVWhtkDoC5MeZSz
+0XQ7vrwxAoGBANFVznbReMP914AtDDY3ISrtsU3AZVuTpfi19+jogTOHZakt0ntt
+Ztaymh0CBcE1DSbSdPLLtVpyhkvfmZRRIzv48xV2LHRibVkx5hncXQRrm+85B2vh
+PMJ1/CO5Rky6DOq6RXWEbHKv56ZPUKeaokJsusUgafKkM8DpWDz5Re6VAoGBAMPH
+FBRrTIL6N8vv4IRmCBk8axmAtbAKOcSghSLtGi8oX4wgL0nltoW28aE4uNiLb7Dh
+vDOqluhc6ejaI6tCSo1+f1JSsiCvieFfRR76I0AQCGtdKZTu7hfxT+GQURm09iYX
+T06VqIfpTOgQ6O6ArYSF+nF1DxrzUJdVpywxqzpnAoGBANAiWaMHyORN2lul7pNl
+IwQ0yuo8lkqENixgePpJWlTqlWitl66C6xIjCFo5LZGZdtcXv5G8ezdP0TlVO7Ud
+K0Qw1TiMg8zAJGrf0yH5WT7Q43zqHffkPe43Mxgt2bjl73ve8rrSjKVHQrK3/8B1
+XklfJCBlhxHqs05mdAZD7oU1AoGAeD6BurjcWWXNd1hxkWAJgVZ2gUdoUCM3r+jX
+XMg72NL3PF1YLg8Et8PRTLBF99pMU1uR+DnCTh0jHX09gyZIG/ehw7I+7YxjJyUY
+kxoXJHW0dhzWOT82xUXVRjkZVqyqsmKGt0F8LV3BepdIOZSW/lo7pAu9p1PiH9Df
+yGkJPekCgYEAneTtCsznABsSw/5E2kq3fWQWZ+N9IIHQCNor4YnEWLiTe7Btexqv
++joMPnKWCZ9DqzgltmcNN4r/2nkNdJ9KJwlwkp6bKl9AnDSdSWuXUsxB8pecGylo
+cD4WMBUQqd2IEI8678Pr9fOHA97YzTF87HfBGcsW/g25dzfOfb8A0YE=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/client.crl b/src/test/ssl/ssl/client.crl
new file mode 100644
index 0000000..a667680
--- /dev/null
+++ b/src/test/ssl/ssl/client.crl
@@ -0,0 +1,11 @@
+-----BEGIN X509 CRL-----
+MIIBnjCBhzANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0xODEx
+MjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMBQwEgIBAhcNMTgxMTI3MTM0MDU1WjAN
+BgkqhkiG9w0BAQsFAAOCAQEAXjLxA9Qc6gAudwUHBxMIq5EHBcuNEX5e3GNlkyNf
+8I0DtHTPfJPvmAG+i6lYz//hHmmjxK0dR2ucg79XgXI/6OpDqlxS/TG1Xv52wA1p
+xz6GaJ2hC8Lk4/vbJo/Rrzme2QsI7xqBWya0JWVrehttqhFxPzWA5wID8X7G4Kb4
+pjVnzqYzn8A9FBiV9t10oZg60aVLqt3kbyy+U3pefvjhj8NmQc7uyuVjWvYZA0vG
+nnDUo4EKJzHNIYLk+EfpzKWO2XAWBLOT9SyyNCeMuQ5p/2pdAt9jtWHenms2ajo9
+2iUsHS91e3TooP9yNYuNcN8/wXY6H2Xm+dCLcEnkcr7EEw==
+-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/client.crt b/src/test/ssl/ssl/client.crt
new file mode 100644
index 0000000..4d0a6ef
--- /dev/null
+++ b/src/test/ssl/ssl/client.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICzDCCAbQCAQEwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UEAww3VGVzdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IGNsaWVudCBjZXJ0czAe
+Fw0xODExMjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMBYxFDASBgNVBAMMC3NzbHRl
+c3R1c2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtIugLqHywEAE
+vyRZGMVAkdk1zCa5FFaPOEFhHiAMpwFOEIEi4Svk9kSSRecmeJcody1sLNoFqtTA
+b5tYaDoGIVZfc8/kxm8sbsTE/3JOsON3CMqjOQkI1ZKjDSF1gtrGSmatgjqsMnlP
+UJkFEsPhFg6NTf1ZUjFiQeWEli0fQJ2/k+7MI4S0t0pDJJJWrqF4l6eSgu8rsBoX
+XHy4OLAz6j23r2k5FZs6H/poII95ia+E8hG8SrJmMa88naRdq7hHW802Z6lEhnRW
+ND+tDGjt0ZaTfxx+CxN4UjgbboOJifTykVHjuzBR1+IzLHcxoZCLP1cjadSqMz5b
+ziJTGtHzYwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAcIGps6BnsRxkN5sphg6GK
+tzDvp2IUyOu5oeAHdJLT5JFZhKKzhDD4KiOv+XWzdHcSAl3xMqAqnFdSTCt2vtc+
+rk04eyVWJALyf6oPT60Vn5sFaaxlTg1+tnZMCCycDxM6lc/6onzgp6DUWGozlgSh
+eNgCyaNU73VTuMgd+s/QrZ5HCr0OPAb3aWRQy7hVZeOniNBXWrO/CC2Swfwz7jU3
+dvLAWYENUvZlE2S7HnQGclGIJb38qFCnquuSgmO9yT30Lmmwp33k5/evN9cNQMxU
+c4ChYCaabOGXUaBJNzJAYMEUHh+o+LPgFF2iB0mL7FAUip9XsjOiOwcrbusM/g+2
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client.key b/src/test/ssl/ssl/client.key
new file mode 100644
index 0000000..21e1e9f
--- /dev/null
+++ b/src/test/ssl/ssl/client.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAtIugLqHywEAEvyRZGMVAkdk1zCa5FFaPOEFhHiAMpwFOEIEi
+4Svk9kSSRecmeJcody1sLNoFqtTAb5tYaDoGIVZfc8/kxm8sbsTE/3JOsON3CMqj
+OQkI1ZKjDSF1gtrGSmatgjqsMnlPUJkFEsPhFg6NTf1ZUjFiQeWEli0fQJ2/k+7M
+I4S0t0pDJJJWrqF4l6eSgu8rsBoXXHy4OLAz6j23r2k5FZs6H/poII95ia+E8hG8
+SrJmMa88naRdq7hHW802Z6lEhnRWND+tDGjt0ZaTfxx+CxN4UjgbboOJifTykVHj
+uzBR1+IzLHcxoZCLP1cjadSqMz5bziJTGtHzYwIDAQABAoIBAA7mqzzODvwBDKM9
++8CInzCqbb9AvuvHzSBGfR6AZKrv96JzFg7hkY8lz7DHSCyRxTw42oHFKMyVrKBJ
+gP1xNIpR16T2VppuGIu33855f7cnvu5R0zDk5v7BkIWH6mv3ZIBFgzKJZybvTjWH
+u5x14EDyyITUUSfwfXyU1eGTLc4mU01g4kArSDy7dqWi9xrixfaiTH+bbfmoE96v
+6kiwICZuoYaBLwOi9e7iHenhpF9X+BHNgt+x4dO5FSTtb15G96CnTM1U8xN2rf6y
+pfwkWH0NoeeTvuGvzfFgQMVxk/72VtM21rntGA4z6ig7xHKZLJ2mFSb29/uISZKv
+P1igV5ECgYEA6J2J5vPYLpXGmwthICaPiVSShUfZpZ05w+/p5KmFwtuyENSTtD+0
+nr/TY3mQNqecDTZsN5lillqnlZcAXVFce9A6iyQuIYz1FwO6K+mQLDshl7sXRNrK
+AeThubltaieOtSXbeji8kmmEK95tO//Y7DNjLVn8gZ4Q2hOT97e8hCsCgYEAxrIK
+/RWqU94JuGwJFzUDpgaJYQG5rcc+J0cZejjFl5ppY0+DPMNBKrGaObUrXAPmWBS9
+Gm4qBN3Zd1F8qEnHF+dw5vZY5hcvANcKRqXiPUkdzmpBNtlb/E+bGIEmhlh96gxN
+AKQEPh2UM+Gc4eDfFUgfLnmqTLfPOSzpCXejmakCgYEAyZVMnoKOw8A0PsSbxNrl
+5OMPnsTnTmh0WOKeVPS74GO3anJuFfRnOHOQY1JDsbmKuMCDA6O/FgE13aLgQ/5w
+ITQQp+gQui6Hbwxh2BAuSsZrlCwkPB1GlmGdY+/Xa/kf6MgH7WEhudgLHGFDVI2h
+lP/rYK/s7P7oJ7RztGbbzcUCgYBzZ9wMDXZlyfRZYp6RFSCuYOOQLYFcVvpZs+kv
+XSQfHveRUBCIzVvfYVKTrA+oHTe/9yOy40OSmgyCShkeYeO6lZm0/Ga8FcEeOshk
+KltSf1JJntuL8QmFbfNGc1Ud+O4Bb+2Vrq4sKd/3llYZuBO6d65svwvUDXrV2ajs
+78ldKQKBgAZROsYDYkdwBJmKCipRAwp7qS67zUpGSzjnDdswa0S6ECMcbx46qhcc
+IvFx/2rjLDIZUVjXkt/U9phWIAq/xMd0Euk+zvIdMmiaJeAmT8DzxVyM4iHGWbuY
+qWpoSLJe8d+xDrHkQZHh+Pb4CpJwRNs3c0MGY3+i/PPTjxq4em8p
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/client_ca.crt b/src/test/ssl/ssl/client_ca.crt
new file mode 100644
index 0000000..1ef3771
--- /dev/null
+++ b/src/test/ssl/ssl/client_ca.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAfWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBAMT4wPAYDVQQDDDVUZXN0
+IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzdWl0
+ZTAeFw0xODExMjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC75x7yuQ1+gK8j
+1aO6D2nGym2x2OFniztlnx4PlWWWivIrYlxy6xAhfrVdjwjc5mMtOCrVKllsdC+S
+1EfqCZTu1+gF0SdG5cHuJ2Rtg4oisLSF221v/msPONAa1dObChWsJwme87VaFrL+
+B2yipfW1PUxM9a7/p19CRBcDQ+LNW+YFqwARByHGq1wfatJzpM8TXe+XEnRfW9KX
+P3a5PqR6evFQOzjcAf+QBJ0hAEddUDhdYECbs1GrApfsEHBuwXabdCH41j0F/0yc
+kctydWfBl2Vbmd3sfsFMHjde+SJhqxyq6xiSL59jnx4ZKmtn9VSOYbGmBCdBdYK7
+RTcnJQv9AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+AB6zh/Jw+t232100Tztr7wJoKH8DL1hnSm3e7omdj0WlKfuZwTqDuMzB3H4LOSnS
+A00XpyMAGAJC6yRjS8pt+pjY5Jt6ouSqf6wNq0mF0jiIDeg1k/GNEjigx+0ITqbE
+lUJ56AcpoBNhOwBjOCRFh4JuspHZqHXgUNYTEicClbV+lZwoMIIK1e6FYRZDqMtL
++34GtZACImqvRkP5alqQg7hJUM1zbDVf2qebY4cfSu4OfTu6Og6KrL8Cu6bqR2et
+0a/TbthHYz1QGDYRoVTSP4uWoG9M1ZbsA/bNE2eqcrQj+dJ4AmIIr8Yl8mrwo/FZ
+SvgeLMlQY7UNwLUDtwy9QkI=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client_ca.key b/src/test/ssl/ssl/client_ca.key
new file mode 100644
index 0000000..f79ea97
--- /dev/null
+++ b/src/test/ssl/ssl/client_ca.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAu+ce8rkNfoCvI9Wjug9pxsptsdjhZ4s7ZZ8eD5VlloryK2Jc
+cusQIX61XY8I3OZjLTgq1SpZbHQvktRH6gmU7tfoBdEnRuXB7idkbYOKIrC0hdtt
+b/5rDzjQGtXTmwoVrCcJnvO1Whay/gdsoqX1tT1MTPWu/6dfQkQXA0PizVvmBasA
+EQchxqtcH2rSc6TPE13vlxJ0X1vSlz92uT6kenrxUDs43AH/kASdIQBHXVA4XWBA
+m7NRqwKX7BBwbsF2m3Qh+NY9Bf9MnJHLcnVnwZdlW5nd7H7BTB43XvkiYascqusY
+ki+fY58eGSprZ/VUjmGxpgQnQXWCu0U3JyUL/QIDAQABAoIBAD4TfcrsTcP0GWg6
+RSvLucM9zv2JS/YcLlRFO/YkAfq5DoY8qZQhiiO2q44sGd54kl03CBeCNSa6P5k/
+Xj64SaaaV4HMdjBa6TWXd/siELmjLRBnzIDKSW4u87lZ2N2IeF52SxxjIQ+RHjME
+GuSk9UaZ6KIoLFczYSoQOpYOkFgNKdu0RcxGG+fM+AQMtCP/KjL61suBxeoLG+Cs
+kAB7EUsSbX+PsOy3V9l9n62F/NDPQBuNgwAMOF2qOXnMTsDHTkxlkka8CIHZeRLX
+VBifO6bf2TCpAQ9+cH+lmfXtOWYExvZvgGeMB9WE8gXSxOnSD0uqxSLgCAwr8sM1
+6TDitIECgYEA7K/p0efjOaw9/+hAJniXS+RQ90WXTkq3asIR+NDvpig2jIJAQ9Lx
+ngGQ86PlEIL53O7Ol4Av4589pmSAD1Q5dEXsXOcDod0mx0nidAp/C//fhyfX7xR2
+irIWDxKKuCc4xbiVWNGeXXC0cJufY2b6gbgpSfwC/i5CUlGzkLdoGKECgYEAyzwq
+g4jtU0O90wKl6REQuepCMTL6+/E6sFDl7OXxZdmeeTyLLgWevAwHOQeOhGuX9RMO
+ZYPngLSbrSC40o2i9369AUkJa0Meabji4fir7GvYzwarq/xHQjMvpGV13DVKEUoQ
+JLdZ9uJRih2tVeqlCiD1abCu3eVh2+Sc17iWKd0CgYEA13g19p+R3lkNgEDg1aUG
+p1JM2y8BVYbzfz75uXgME0mcj0GsW5JX364xVXwo9mUmLplAfe92qVO4fhgT9OCK
+BW36hYDRb7Oyr851V4qOqk/gIFyHWeFZIV6KcmJA4vDh4C3v2BHMh+gofDNQYN/I
+wfrzq6S+3MMkIWi7fc1Z+MECgYEApv4nCLGIIDS3Ux0H6nwFPF5KSVbUeBP830d5
+xbAjLEcmOgQPcJ9ZkLZpcOjOp+wojk08NRmvLUg56oXKl+edkrNm5hl3TdV2tfQf
+KQJFchwjp+iZQtYmTzTz3qcnsutukspCfYjSuVO5ID3GYaROPAZc4J028kk6oY41
+eePIL1kCgYEA1ghHZmNhhsjxrkfWFME/nSWFazyEx1XmMagmvXw9rlrnBPFdtLsU
+ZfFHM5gVaW/JMql0OGDjMSwc3fEEH8wd1eLXVxfCVLzvk8XVe81ahthMpndii813
+Cspl0AS9jpOkQK8JTZ38YAIqIr7H057veqAY+TbvIAdxoh+7DyW/aBs=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0 b/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
new file mode 100644
index 0000000..a667680
--- /dev/null
+++ b/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
@@ -0,0 +1,11 @@
+-----BEGIN X509 CRL-----
+MIIBnjCBhzANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0xODEx
+MjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMBQwEgIBAhcNMTgxMTI3MTM0MDU1WjAN
+BgkqhkiG9w0BAQsFAAOCAQEAXjLxA9Qc6gAudwUHBxMIq5EHBcuNEX5e3GNlkyNf
+8I0DtHTPfJPvmAG+i6lYz//hHmmjxK0dR2ucg79XgXI/6OpDqlxS/TG1Xv52wA1p
+xz6GaJ2hC8Lk4/vbJo/Rrzme2QsI7xqBWya0JWVrehttqhFxPzWA5wID8X7G4Kb4
+pjVnzqYzn8A9FBiV9t10oZg60aVLqt3kbyy+U3pefvjhj8NmQc7uyuVjWvYZA0vG
+nnDUo4EKJzHNIYLk+EfpzKWO2XAWBLOT9SyyNCeMuQ5p/2pdAt9jtWHenms2ajo9
+2iUsHS91e3TooP9yNYuNcN8/wXY6H2Xm+dCLcEnkcr7EEw==
+-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+client-crldir/a3d11bff.r0 b/src/test/ssl/ssl/root+client-crldir/a3d11bff.r0
new file mode 100644
index 0000000..e879cf2
--- /dev/null
+++ b/src/test/ssl/ssl/root+client-crldir/a3d11bff.r0
@@ -0,0 +1,11 @@
+-----BEGIN X509 CRL-----
+MIIBhTBvMA0GCSqGSIb3DQEBCwUAMEAxPjA8BgNVBAMMNVRlc3Qgcm9vdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHN1aXRlFw0xODExMjcx
+MzQwNTVaFw00NjA0MTQxMzQwNTVaMA0GCSqGSIb3DQEBCwUAA4IBAQB8OSDym4/a
+qbZOrZvOOhmKrd7AJSTgAadtdK0CX3v58Ym3EmZK7gQFdBuFCXnvbue/x6avZHgz
+4pYFlJmL0IiD4QuTzsoo+LzifrmTzteO9oEJNLd2bjfEnpE5Wdaw6Yuy2Xb5edy5
+lQhNZdc8w3FiXhPOEUAi7EbdfDwn4G/fvEjpzyVb2wCujDUUePUGGayjKIM4PUu4
+pixM6gt9FFL27l47lQ3g0PbvB3TnU3oqcB3Y17FjbxjFc6AsGXholNetoEE2/49E
+PEYzOH7/PtxlZUtoCqZM+741LuI6Q7z4/P2X/IY33lMy6Iiyc41C94l/P7fCkMLG
+AlO+O0a4SpYS
+-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+client.crl b/src/test/ssl/ssl/root+client.crl
new file mode 100644
index 0000000..854d77b
--- /dev/null
+++ b/src/test/ssl/ssl/root+client.crl
@@ -0,0 +1,22 @@
+-----BEGIN X509 CRL-----
+MIIBhTBvMA0GCSqGSIb3DQEBCwUAMEAxPjA8BgNVBAMMNVRlc3Qgcm9vdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHN1aXRlFw0xODExMjcx
+MzQwNTVaFw00NjA0MTQxMzQwNTVaMA0GCSqGSIb3DQEBCwUAA4IBAQB8OSDym4/a
+qbZOrZvOOhmKrd7AJSTgAadtdK0CX3v58Ym3EmZK7gQFdBuFCXnvbue/x6avZHgz
+4pYFlJmL0IiD4QuTzsoo+LzifrmTzteO9oEJNLd2bjfEnpE5Wdaw6Yuy2Xb5edy5
+lQhNZdc8w3FiXhPOEUAi7EbdfDwn4G/fvEjpzyVb2wCujDUUePUGGayjKIM4PUu4
+pixM6gt9FFL27l47lQ3g0PbvB3TnU3oqcB3Y17FjbxjFc6AsGXholNetoEE2/49E
+PEYzOH7/PtxlZUtoCqZM+741LuI6Q7z4/P2X/IY33lMy6Iiyc41C94l/P7fCkMLG
+AlO+O0a4SpYS
+-----END X509 CRL-----
+-----BEGIN X509 CRL-----
+MIIBnjCBhzANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0xODEx
+MjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMBQwEgIBAhcNMTgxMTI3MTM0MDU1WjAN
+BgkqhkiG9w0BAQsFAAOCAQEAXjLxA9Qc6gAudwUHBxMIq5EHBcuNEX5e3GNlkyNf
+8I0DtHTPfJPvmAG+i6lYz//hHmmjxK0dR2ucg79XgXI/6OpDqlxS/TG1Xv52wA1p
+xz6GaJ2hC8Lk4/vbJo/Rrzme2QsI7xqBWya0JWVrehttqhFxPzWA5wID8X7G4Kb4
+pjVnzqYzn8A9FBiV9t10oZg60aVLqt3kbyy+U3pefvjhj8NmQc7uyuVjWvYZA0vG
+nnDUo4EKJzHNIYLk+EfpzKWO2XAWBLOT9SyyNCeMuQ5p/2pdAt9jtWHenms2ajo9
+2iUsHS91e3TooP9yNYuNcN8/wXY6H2Xm+dCLcEnkcr7EEw==
+-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+client_ca.crt b/src/test/ssl/ssl/root+client_ca.crt
new file mode 100644
index 0000000..1867cd9
--- /dev/null
+++ b/src/test/ssl/ssl/root+client_ca.crt
@@ -0,0 +1,38 @@
+-----BEGIN CERTIFICATE-----
+MIIDHjCCAgagAwIBAgIUEAgXJ/ibw6TVTUoomlBsPMfVTlMwDQYJKoZIhvcNAQEL
+BQAwQDE+MDwGA1UEAww1VGVzdCByb290IENBIGZvciBQb3N0Z3JlU1FMIFNTTCBy
+ZWdyZXNzaW9uIHRlc3Qgc3VpdGUwHhcNMTgxMTI3MTM0MDU0WhcNNDYwNDE0MTM0
+MDU0WjBAMT4wPAYDVQQDDDVUZXN0IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NM
+IHJlZ3Jlc3Npb24gdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALZ81vKKBJlxgjwuNoK67I4IE9zfSLb0eHbgZwZxDVzdmFejARrHlWk3
++MK7Nav7RLSJ990am33zb58CTHc7YYVlBp07+PwLXzypqWkhYfok1OYYjyjCrFDs
+sjcJI3hRCZNEz+wYsG+tdYWJ+gRPQOWfh0YfO2rFgXAIMLiF6lyWzf1eOM+OjYrF
+/eyzwbMaJkkGa/AyZKz3wZiPq0jTuYLVmH4MK7MBOsUfSmsBsn/ohyRCQzM+ol0v
+Qlsrulj8usponRPDh9ng4PB5OSgR79YimQZnASQzJxiUvMADrKL5L6KwLxJlzbqY
+R0b5mLh8KBzBQmSh3Aj2e2I7Z17hdaMCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zAN
+BgkqhkiG9w0BAQsFAAOCAQEASzA7ApbuKn8lkC706gRL37a33yTZZ8rjZ4dnvCtq
+6OltlYDJ8IndotKbLH0SUEAxrvcaFnMt7AX9pRf2sGBKbY8zcxqPfsvzVehgx4Ea
+1RYksFW4h97jj1a1RKukTKuEOEEipbxwo0rLxfjvdaf2izqchJsLGtbocIZf0bD8
+djbE9jOLkx7saL08qC8ECrf9utsee+LJCsUYbNgYyIItEy6yVnmF/ICz93Utn1cI
+RfqZr1lM2Ia2LP9eDTmiuR9m+/MzkeRvvJHonNrRJHlcggtYHICvYioh9/jALBcm
+wZ+hTUePVqy4hOCBJ975CXjfKFN4MKQAdeB3EO5eBYAD3Q==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAfWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBAMT4wPAYDVQQDDDVUZXN0
+IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzdWl0
+ZTAeFw0xODExMjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC75x7yuQ1+gK8j
+1aO6D2nGym2x2OFniztlnx4PlWWWivIrYlxy6xAhfrVdjwjc5mMtOCrVKllsdC+S
+1EfqCZTu1+gF0SdG5cHuJ2Rtg4oisLSF221v/msPONAa1dObChWsJwme87VaFrL+
+B2yipfW1PUxM9a7/p19CRBcDQ+LNW+YFqwARByHGq1wfatJzpM8TXe+XEnRfW9KX
+P3a5PqR6evFQOzjcAf+QBJ0hAEddUDhdYECbs1GrApfsEHBuwXabdCH41j0F/0yc
+kctydWfBl2Vbmd3sfsFMHjde+SJhqxyq6xiSL59jnx4ZKmtn9VSOYbGmBCdBdYK7
+RTcnJQv9AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+AB6zh/Jw+t232100Tztr7wJoKH8DL1hnSm3e7omdj0WlKfuZwTqDuMzB3H4LOSnS
+A00XpyMAGAJC6yRjS8pt+pjY5Jt6ouSqf6wNq0mF0jiIDeg1k/GNEjigx+0ITqbE
+lUJ56AcpoBNhOwBjOCRFh4JuspHZqHXgUNYTEicClbV+lZwoMIIK1e6FYRZDqMtL
++34GtZACImqvRkP5alqQg7hJUM1zbDVf2qebY4cfSu4OfTu6Og6KrL8Cu6bqR2et
+0a/TbthHYz1QGDYRoVTSP4uWoG9M1ZbsA/bNE2eqcrQj+dJ4AmIIr8Yl8mrwo/FZ
+SvgeLMlQY7UNwLUDtwy9QkI=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/root+server-crldir/a3d11bff.r0 b/src/test/ssl/ssl/root+server-crldir/a3d11bff.r0
new file mode 100644
index 0000000..e879cf2
--- /dev/null
+++ b/src/test/ssl/ssl/root+server-crldir/a3d11bff.r0
@@ -0,0 +1,11 @@
+-----BEGIN X509 CRL-----
+MIIBhTBvMA0GCSqGSIb3DQEBCwUAMEAxPjA8BgNVBAMMNVRlc3Qgcm9vdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHN1aXRlFw0xODExMjcx
+MzQwNTVaFw00NjA0MTQxMzQwNTVaMA0GCSqGSIb3DQEBCwUAA4IBAQB8OSDym4/a
+qbZOrZvOOhmKrd7AJSTgAadtdK0CX3v58Ym3EmZK7gQFdBuFCXnvbue/x6avZHgz
+4pYFlJmL0IiD4QuTzsoo+LzifrmTzteO9oEJNLd2bjfEnpE5Wdaw6Yuy2Xb5edy5
+lQhNZdc8w3FiXhPOEUAi7EbdfDwn4G/fvEjpzyVb2wCujDUUePUGGayjKIM4PUu4
+pixM6gt9FFL27l47lQ3g0PbvB3TnU3oqcB3Y17FjbxjFc6AsGXholNetoEE2/49E
+PEYzOH7/PtxlZUtoCqZM+741LuI6Q7z4/P2X/IY33lMy6Iiyc41C94l/P7fCkMLG
+AlO+O0a4SpYS
+-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+server-crldir/a836cc2d.r0 b/src/test/ssl/ssl/root+server-crldir/a836cc2d.r0
new file mode 100644
index 0000000..717951c
--- /dev/null
+++ b/src/test/ssl/ssl/root+server-crldir/a836cc2d.r0
@@ -0,0 +1,11 @@
+-----BEGIN X509 CRL-----
+MIIBnjCBhzANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3Qgc2VydmVyIGNlcnRzFw0xODEx
+MjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMBQwEgIBBhcNMTgxMTI3MTM0MDU1WjAN
+BgkqhkiG9w0BAQsFAAOCAQEAbVuJXemxM6HLlIHGWlQvVmsmG4ZTQWiDnZjfmrND
+xB4XsvZNPXnFkjdBENDROrbDRwm60SJDW73AbDbfq1IXAzSpuEyuRz61IyYKo0wq
+nmObJtVdIu3bVlWIlDXaP5Emk3d7ouCj5f8Kyeb8gm4pL3N6e0eI63hCaS39hhE6
+RLGh9HU9ht1kKfgcTwmB5b2HTPb4M6z1AmSIaMVqZTjIspsUgNF2+GBm3fOnOaiZ
+SEXWtgjMRXiIHbtU0va3LhSH5OSW0mh+L9oGUQDYnyuudnWGpulhqIp4qVkJRDDu
+41HpD83dV2uRtBLvc25AFHj7kXBflbO3gvGZVPYf1zVghQ==
+-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+server.crl b/src/test/ssl/ssl/root+server.crl
new file mode 100644
index 0000000..9720b30
--- /dev/null
+++ b/src/test/ssl/ssl/root+server.crl
@@ -0,0 +1,22 @@
+-----BEGIN X509 CRL-----
+MIIBhTBvMA0GCSqGSIb3DQEBCwUAMEAxPjA8BgNVBAMMNVRlc3Qgcm9vdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHN1aXRlFw0xODExMjcx
+MzQwNTVaFw00NjA0MTQxMzQwNTVaMA0GCSqGSIb3DQEBCwUAA4IBAQB8OSDym4/a
+qbZOrZvOOhmKrd7AJSTgAadtdK0CX3v58Ym3EmZK7gQFdBuFCXnvbue/x6avZHgz
+4pYFlJmL0IiD4QuTzsoo+LzifrmTzteO9oEJNLd2bjfEnpE5Wdaw6Yuy2Xb5edy5
+lQhNZdc8w3FiXhPOEUAi7EbdfDwn4G/fvEjpzyVb2wCujDUUePUGGayjKIM4PUu4
+pixM6gt9FFL27l47lQ3g0PbvB3TnU3oqcB3Y17FjbxjFc6AsGXholNetoEE2/49E
+PEYzOH7/PtxlZUtoCqZM+741LuI6Q7z4/P2X/IY33lMy6Iiyc41C94l/P7fCkMLG
+AlO+O0a4SpYS
+-----END X509 CRL-----
+-----BEGIN X509 CRL-----
+MIIBnjCBhzANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3Qgc2VydmVyIGNlcnRzFw0xODEx
+MjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMBQwEgIBBhcNMTgxMTI3MTM0MDU1WjAN
+BgkqhkiG9w0BAQsFAAOCAQEAbVuJXemxM6HLlIHGWlQvVmsmG4ZTQWiDnZjfmrND
+xB4XsvZNPXnFkjdBENDROrbDRwm60SJDW73AbDbfq1IXAzSpuEyuRz61IyYKo0wq
+nmObJtVdIu3bVlWIlDXaP5Emk3d7ouCj5f8Kyeb8gm4pL3N6e0eI63hCaS39hhE6
+RLGh9HU9ht1kKfgcTwmB5b2HTPb4M6z1AmSIaMVqZTjIspsUgNF2+GBm3fOnOaiZ
+SEXWtgjMRXiIHbtU0va3LhSH5OSW0mh+L9oGUQDYnyuudnWGpulhqIp4qVkJRDDu
+41HpD83dV2uRtBLvc25AFHj7kXBflbO3gvGZVPYf1zVghQ==
+-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+server_ca.crt b/src/test/ssl/ssl/root+server_ca.crt
new file mode 100644
index 0000000..861eba8
--- /dev/null
+++ b/src/test/ssl/ssl/root+server_ca.crt
@@ -0,0 +1,38 @@
+-----BEGIN CERTIFICATE-----
+MIIDHjCCAgagAwIBAgIUEAgXJ/ibw6TVTUoomlBsPMfVTlMwDQYJKoZIhvcNAQEL
+BQAwQDE+MDwGA1UEAww1VGVzdCByb290IENBIGZvciBQb3N0Z3JlU1FMIFNTTCBy
+ZWdyZXNzaW9uIHRlc3Qgc3VpdGUwHhcNMTgxMTI3MTM0MDU0WhcNNDYwNDE0MTM0
+MDU0WjBAMT4wPAYDVQQDDDVUZXN0IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NM
+IHJlZ3Jlc3Npb24gdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALZ81vKKBJlxgjwuNoK67I4IE9zfSLb0eHbgZwZxDVzdmFejARrHlWk3
++MK7Nav7RLSJ990am33zb58CTHc7YYVlBp07+PwLXzypqWkhYfok1OYYjyjCrFDs
+sjcJI3hRCZNEz+wYsG+tdYWJ+gRPQOWfh0YfO2rFgXAIMLiF6lyWzf1eOM+OjYrF
+/eyzwbMaJkkGa/AyZKz3wZiPq0jTuYLVmH4MK7MBOsUfSmsBsn/ohyRCQzM+ol0v
+Qlsrulj8usponRPDh9ng4PB5OSgR79YimQZnASQzJxiUvMADrKL5L6KwLxJlzbqY
+R0b5mLh8KBzBQmSh3Aj2e2I7Z17hdaMCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zAN
+BgkqhkiG9w0BAQsFAAOCAQEASzA7ApbuKn8lkC706gRL37a33yTZZ8rjZ4dnvCtq
+6OltlYDJ8IndotKbLH0SUEAxrvcaFnMt7AX9pRf2sGBKbY8zcxqPfsvzVehgx4Ea
+1RYksFW4h97jj1a1RKukTKuEOEEipbxwo0rLxfjvdaf2izqchJsLGtbocIZf0bD8
+djbE9jOLkx7saL08qC8ECrf9utsee+LJCsUYbNgYyIItEy6yVnmF/ICz93Utn1cI
+RfqZr1lM2Ia2LP9eDTmiuR9m+/MzkeRvvJHonNrRJHlcggtYHICvYioh9/jALBcm
+wZ+hTUePVqy4hOCBJ975CXjfKFN4MKQAdeB3EO5eBYAD3Q==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAfWgAwIBAgIBATANBgkqhkiG9w0BAQsFADBAMT4wPAYDVQQDDDVUZXN0
+IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzdWl0
+ZTAeFw0xODExMjcxMzQwNTRaFw00NjA0MTQxMzQwNTRaMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiSnYZbmc9vpCt
+Ku1sKV9l663JCceubhMw8Gg16kV0hXEFf/TgGC4zkiYNHN7+G45YD7Nq0kBCq3dH
+t2wPCc6c8pQoI64dfprVqPkvzoe1WBpZNetkUTk20v08jNeRa7XdRbRR6we1s9VG
+/prp8Hs2mmHqEfLuI9lvTT0Dz+VMmfFI8Lf278r+w+qOtVloAkX7AOyoLEJlNS0B
+QW9YWdH9N5ctaUXMG6lLV2OAjs+W1smpKfpIpMCA1lPGlElu70hynon/nQQvBP77
+SfQpZVc0esM18jkZpr5LEKUCw+x6LaMsqmBHpAULfCffxn2r0uMBW4L4VaGg3W6F
+h6iuJwRfAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+AFlcKTaU/Ug3Q0hr3P1UQ6dWyK4aVn9rs4jvVfFl0a0RnbBowqK2C+zQVUWYTcjo
+KHREVje65goj6VzRB6ko/9mAQ6PZP8jRuRhfCmvmvSQ/mWdgPzSRsUh9MwgEm9c2
+vNbqwaznEU8cYZnLpHiR9O5S7/qWWxehjYtxk5Eb4J006YglYfHnhrRFJvPbiqlf
+IOEivZ7gIVfvaOTbLjmN2kLOnzdlwpXGjxxg4Nu9ZhXOhfrplzUvRfmqvVsDiHXb
+USIdX+OFZZqr64IKG4drT4K4Bt2wupOEyX4ZFsUXXd+Hgq83SWmV4wzflcpmGkLC
+JZ3CEMu8/WA5uQBXdQUozlE=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/root.crl b/src/test/ssl/ssl/root.crl
new file mode 100644
index 0000000..e879cf2
--- /dev/null
+++ b/src/test/ssl/ssl/root.crl
@@ -0,0 +1,11 @@
+-----BEGIN X509 CRL-----
+MIIBhTBvMA0GCSqGSIb3DQEBCwUAMEAxPjA8BgNVBAMMNVRlc3Qgcm9vdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHN1aXRlFw0xODExMjcx
+MzQwNTVaFw00NjA0MTQxMzQwNTVaMA0GCSqGSIb3DQEBCwUAA4IBAQB8OSDym4/a
+qbZOrZvOOhmKrd7AJSTgAadtdK0CX3v58Ym3EmZK7gQFdBuFCXnvbue/x6avZHgz
+4pYFlJmL0IiD4QuTzsoo+LzifrmTzteO9oEJNLd2bjfEnpE5Wdaw6Yuy2Xb5edy5
+lQhNZdc8w3FiXhPOEUAi7EbdfDwn4G/fvEjpzyVb2wCujDUUePUGGayjKIM4PUu4
+pixM6gt9FFL27l47lQ3g0PbvB3TnU3oqcB3Y17FjbxjFc6AsGXholNetoEE2/49E
+PEYzOH7/PtxlZUtoCqZM+741LuI6Q7z4/P2X/IY33lMy6Iiyc41C94l/P7fCkMLG
+AlO+O0a4SpYS
+-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root_ca.crt b/src/test/ssl/ssl/root_ca.crt
new file mode 100644
index 0000000..402d7da
--- /dev/null
+++ b/src/test/ssl/ssl/root_ca.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHjCCAgagAwIBAgIUEAgXJ/ibw6TVTUoomlBsPMfVTlMwDQYJKoZIhvcNAQEL
+BQAwQDE+MDwGA1UEAww1VGVzdCByb290IENBIGZvciBQb3N0Z3JlU1FMIFNTTCBy
+ZWdyZXNzaW9uIHRlc3Qgc3VpdGUwHhcNMTgxMTI3MTM0MDU0WhcNNDYwNDE0MTM0
+MDU0WjBAMT4wPAYDVQQDDDVUZXN0IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NM
+IHJlZ3Jlc3Npb24gdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALZ81vKKBJlxgjwuNoK67I4IE9zfSLb0eHbgZwZxDVzdmFejARrHlWk3
++MK7Nav7RLSJ990am33zb58CTHc7YYVlBp07+PwLXzypqWkhYfok1OYYjyjCrFDs
+sjcJI3hRCZNEz+wYsG+tdYWJ+gRPQOWfh0YfO2rFgXAIMLiF6lyWzf1eOM+OjYrF
+/eyzwbMaJkkGa/AyZKz3wZiPq0jTuYLVmH4MK7MBOsUfSmsBsn/ohyRCQzM+ol0v
+Qlsrulj8usponRPDh9ng4PB5OSgR79YimQZnASQzJxiUvMADrKL5L6KwLxJlzbqY
+R0b5mLh8KBzBQmSh3Aj2e2I7Z17hdaMCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zAN
+BgkqhkiG9w0BAQsFAAOCAQEASzA7ApbuKn8lkC706gRL37a33yTZZ8rjZ4dnvCtq
+6OltlYDJ8IndotKbLH0SUEAxrvcaFnMt7AX9pRf2sGBKbY8zcxqPfsvzVehgx4Ea
+1RYksFW4h97jj1a1RKukTKuEOEEipbxwo0rLxfjvdaf2izqchJsLGtbocIZf0bD8
+djbE9jOLkx7saL08qC8ECrf9utsee+LJCsUYbNgYyIItEy6yVnmF/ICz93Utn1cI
+RfqZr1lM2Ia2LP9eDTmiuR9m+/MzkeRvvJHonNrRJHlcggtYHICvYioh9/jALBcm
+wZ+hTUePVqy4hOCBJ975CXjfKFN4MKQAdeB3EO5eBYAD3Q==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/root_ca.key b/src/test/ssl/ssl/root_ca.key
new file mode 100644
index 0000000..aa5f243
--- /dev/null
+++ b/src/test/ssl/ssl/root_ca.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAtnzW8ooEmXGCPC42grrsjggT3N9ItvR4duBnBnENXN2YV6MB
+GseVaTf4wrs1q/tEtIn33RqbffNvnwJMdzthhWUGnTv4/AtfPKmpaSFh+iTU5hiP
+KMKsUOyyNwkjeFEJk0TP7Biwb611hYn6BE9A5Z+HRh87asWBcAgwuIXqXJbN/V44
+z46NisX97LPBsxomSQZr8DJkrPfBmI+rSNO5gtWYfgwrswE6xR9KawGyf+iHJEJD
+Mz6iXS9CWyu6WPy6ymidE8OH2eDg8Hk5KBHv1iKZBmcBJDMnGJS8wAOsovkvorAv
+EmXNuphHRvmYuHwoHMFCZKHcCPZ7YjtnXuF1owIDAQABAoIBAQCiCzsHhf1NkBi4
+fcTT006JVKzmnbNBGtb5oIx7kNnv06oab9lkQUPwec5AhOLFA8tfkX/y61SVxBwj
+E3R5D9aqECqOZpnSnfqEsJeJjiYlbJ1McRR2el9vQK+D5W6EwVkCV8FWAhpyIJJR
+8VJ8jy+udzk00Dj/t8AXjn5M7EVOzu7fu2CIjJ2csE4NpWnn4eaDqev/brEVaLSP
+DGFdMw4D29AmErByN42d90U5YE2c1UufYoFL480qoA4pW5Tw5+vrIDliEHhnTtSN
+aTHZiOCmHtBFieQzmWCRQqmtlJP+7Z/NqK0i91HpjFrU5AuyLOMbqJPl6TgncNA+
+MSXMXGQBAoGBANlsAQc3poc3MX18++qgRJGqJCBF3MVQNnZQxmO6swydikZmlunN
+kmslheb/JZbE9tkjEhx1RuUeZIvOCxURdODDPnvm9bk0C7LvPGXnYLWTbCknHEwR
+8yIvPGiLhZOuDGAdLpHzG5F1eHQLOWQpTNndptlveSz5Zc0a+B7qmWYBAoGBANbe
+Atvo8JI+ht2tiidwCo5WHR26ux0yA+iHPNyMAEaY5KFblyvjb4rAFziUu6cLzFNl
+N2pdv3ZpIHs82erJMkCSrIbp9RM3TWaTMFgWETDnZ6dNclEsEva/kRJquOw+N/Ag
+LFOn8omVRMHq99G/eFdPeSYc1mDRNZ0vDstOA4OjAoGAd9l/X5kfpN2Z3FCvFRCv
+e5RMQbYBEos62lGAaq0Z0dRtyoz2l38IPSP8Ae+Xqtp8MAmTDDjhkZ8FUcOMfFqZ
+EOTPZsFTpnm4ETSrGIlI2A6hyrWSdaRXX/ql1ANE6LlCfSDY8P8PrUkR0vX09u+F
+O3thY+5833vC0CMTrwcm9AECgYBmVktqTiH2pY06m+MHMZf1fxJTDJL+Lsopv+++
+43dmKIAMUkFICAUiQqdMrZpKz5W7yqOAJ7J/RUbRK4RnDPjARJujjl7JjjdxOX13
+FtuNPUnjJ0HhY2qM12TTLr1w15lw5wH1vjIIUW30JmNuJRG+E/4Rpv58EmjEupsD
+Pd7ynQKBgQC42hGdd3TGe7zYqnPIe/vJyNX05xErKXK1iqwj6ZQWOiurGiePbiIu
+Top5MZcqLetZ9872efPLQFlkB2elCP91yBKNdI+onBcha9rVcczjCdaQswsm5Ws3
+58Dyjci4NqyD8B19AOJLTl5gq8pmj5Nom8ip+Bm2EsQw777Dqq9YrA==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-cn-and-alt-names.crt b/src/test/ssl/ssl/server-cn-and-alt-names.crt
new file mode 100644
index 0000000..2bb206e
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDTjCCAjagAwIBAgIBATANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0
+IENBIGZvciBQb3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3Qgc2VydmVyIGNl
+cnRzMB4XDTE4MTEyNzEzNDA1NFoXDTQ2MDQxNDEzNDA1NFowRjEeMBwGA1UECwwV
+UG9zdGdyZVNRTCB0ZXN0IHN1aXRlMSQwIgYDVQQDDBtjb21tb24tbmFtZS5wZy1z
+c2x0ZXN0LnRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBURL6
+YPWJjVQEZY0uy4HEaTI4ZMjVf+xdwJRntos4aRcdhq2JRNwitI00BLnIK9ur8D8L
+U24PDn2etTZOK1wsHHkAiHPvgRX7G/+78a5fSC8nMNaH/oR4zMMhwksiruISA7x9
+/l+4DO7C42C5mRfnUmMGJdzau1+A8jcIxTWWoJtva9b1k3g+bvBGq9138X+bJKIW
+YsWwHgcuEIZk3z287hxuyU3j5isYoRwd5cZZFrG/qBJdukSBRil5/PP5AHsB6lTl
+pae2bdf4TXB7kActIpyTBR0G5Pm5iCZlxgD+QILj/1FLTaNOW7hV+t1J6YfC6jZR
+Dk4MnHMCFasSXcXhAgMBAAGjSzBJMEcGA1UdEQRAMD6CHWRuczEuYWx0LW5hbWUu
+cGctc3NsdGVzdC50ZXN0gh1kbnMyLmFsdC1uYW1lLnBnLXNzbHRlc3QudGVzdDAN
+BgkqhkiG9w0BAQsFAAOCAQEAQeKHXoyipubldf4HUDCXrcIk6KiEs9DMH1DXRk7L
+z2Hr0TljmKoGG5P6OrF4eP82bhXZlQmwHVclB7Pfo5DvoMYmYjSHxcEAeyJ7etxb
+pV11yEkMkCbQpBVtdMqyTpckXM49GTwqD9US5p1E350snq4Duj3O7fSpE4HMfSd8
+dCkYdaCHq2NWH4/MfEBRy096oOIFxqgm6tRCU95ZI8KeeK4WwPXiGV2mb2rHj1kv
+uBRC+sJGSnsLdbZzkpdAN1qnWrJoLezAMdhTmNRUJ7Cq8hAkroFIp+LE+JyxR9Nw
+m6jD3eEwCAQi0pPGLEn4Rq4B8kxzL5F/jTq7PONRvOAdIg==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-and-alt-names.key b/src/test/ssl/ssl/server-cn-and-alt-names.key
new file mode 100644
index 0000000..485e340
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-and-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAwVES+mD1iY1UBGWNLsuBxGkyOGTI1X/sXcCUZ7aLOGkXHYat
+iUTcIrSNNAS5yCvbq/A/C1NuDw59nrU2TitcLBx5AIhz74EV+xv/u/GuX0gvJzDW
+h/6EeMzDIcJLIq7iEgO8ff5fuAzuwuNguZkX51JjBiXc2rtfgPI3CMU1lqCbb2vW
+9ZN4Pm7wRqvdd/F/mySiFmLFsB4HLhCGZN89vO4cbslN4+YrGKEcHeXGWRaxv6gS
+XbpEgUYpefzz+QB7AepU5aWntm3X+E1we5AHLSKckwUdBuT5uYgmZcYA/kCC4/9R
+S02jTlu4VfrdSemHwuo2UQ5ODJxzAhWrEl3F4QIDAQABAoIBADpFoQ3eKkVzV48X
+uW4QpCY7e4rqPmu06t/7zABTUzYG35Pj4+2L1zuS5zl17zZ6mfYDLk3QsU1SleVA
+RIVdpqQZVRQnDaN1atXNw9G4cVKBZM1QeGp3+yCawHstoQ5sXvMFM01bXykQpOwU
+NDTeBAmTmQviX+eDMa+h05sOLzAe5IfJd54au7jaDgX+J+OYoRcahXZLO3tco/kL
+Mo0OBj71ec8jrbhnViEXPwPUOBDoTJxfRotCZ2Sr0ozXT/sRBZvvK9OGzWf9mA2D
+M7UqbkKUL9AJxB+zTUILdM3+4buzifDZPUWe05dHyP6VAvtgSQJkdReFtUa868pm
+si+7Td0CgYEA+r4dxf880QW1hY/dpTfLXOcLKSHogIHv2Qt3Dinh4AthXZs91MQ9
+DMiT2x4RMGlk7zOu6Ua4HXjTT0s9CqUt9Cyga0zTn+XzNZxzcriRWYnw7pq0O77B
+3AODrK6/VajAjqdwiP3nQfBOyhz3G7YB2yXCpdnVVI179x8pbGnEP2MCgYEAxV64
+CaIW0hgwEZifT4PGINdGShk7ijQ0YhPw4bLJLV7gaPrKYG+qy8/R9AyhcFqz9MrF
+2E+jiD+fylNSLdkuR1/v6se1wWLjDiKip5F9molCAKTUBTkqD/8Ejh5+I7NAuFLA
+9QZYiaRLhIoVocWfzNIPHit7NZftaBtxTCtE8usCgYAmxbso4LzwvWdCTerCH4yM
+wxVQuPOQ24bRExrHz+YjlN7rcJPxEJ84GNP0MAQMbl+zNVS4sbzKoeJbApFf0gb3
+GOd9cBXRReeDxLt9Y9jl9ZSR8M4p5udnNAvqaeMgRcXwySd3p3tZEOW+DxiO6mgD
+ESW2K6b3OiGPJvxqzTgRbQKBgQDCvB/tMUY+6KqU0fdtpuCXio/JkHfUdomws2gZ
+6CLiZxgXvEptOABWs6e9mbC3gGbKAj+Om5UIW243XFpa7kvhFGFNTtqgAgdw7O97
+UeuRzBeZNwgSV0KPIdjGuINQig4zT0Me/rHgrH/uN6f8Q1bV6fQMmm4ohMwyydDR
+jGetHQKBgGLQf4MBNalhfnM7jnUCbA0ygrw7BuJRfwuamdcH9bdfOHMyrbOfZ+hS
+aKcOeaGi0HGOq+D2tIH1hmFYHD0klPgr/KWnNMoi2xTw+1GNKA2u9EZQQkK6XSRn
+Xb6zihNEqY8067K/um/658x1c9SbfvVmzjqo5OwgjSKygk6fXL8i
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-cn-only.crt b/src/test/ssl/ssl/server-cn-only.crt
new file mode 100644
index 0000000..67bf0b1
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeQCAQIwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UEAww3VGVzdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNlcnZlciBjZXJ0czAe
+Fw0xODExMjcxMzQwNTRaFw00NjA0MTQxMzQwNTRaMEYxHjAcBgNVBAsMFVBvc3Rn
+cmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5hbWUucGctc3NsdGVz
+dC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1bNU8yTuLl/5
+bR4Rp1ET5NMC2wgrTwKQsxSeOzvMmTGeebpEYFc/Hq8rcCQAiL7AbhiZeNg/ca4y
+JdouOdaHaTMFJ8hFDI1tNOGeFK7ecOMBWQ97GxKs/KIqKYW42AN+QZ7l1Apr0CDZ
+K5VTi931JjE4wCIgUgLi2zgwtZYl/kP896F1K5zR7kx773U2dvP3SeV2ziUe+4NH
+5oqdmVMeZyHfB+Fe/uU1AiYgHN/CXfop39tYHR8ARUWx7eJlaaKBoj/0UqH/9Yir
+jdM0vGfrw4JCjIx78caNkNH9fCjesTqODr+IDBJaJv3Jpt9g8puqrYagXLppiaYS
+q5oOAu5fuQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCTxkqpNNuO15osb+Lyw2aQ
+RikYZiSJ4uJcOIya6DqSYSNf8wrgGMJAKz9TkCGEG7SszLCASaSIS/s+sR1+bE19
+f6BxoBnppPW8uIkTtQvmGhhcWHO13zMUs8bmg9OY7MvFYJdQAmSqfYebUCYR73So
+OthALxV8h+boW5/xc2XM1NObpcuShQ9/uynm2dL3EbrNjvcoXOwu865FmVMffEn9
++zhE8xl4kMKObQvB3r2utCmlAmJLaU2ejADncS9Y4ZwRMa7x+vfvekF/FvLEXUal
+QcDlfrZ0xsw/HK7n0/UFXf5fUXq3hgUGcXEdWW7/yTA43qNxednfa+fMqlFztupt
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-cn-only.key b/src/test/ssl/ssl/server-cn-only.key
new file mode 100644
index 0000000..672d3f0
--- /dev/null
+++ b/src/test/ssl/ssl/server-cn-only.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1bNU8yTuLl/5bR4Rp1ET5NMC2wgrTwKQsxSeOzvMmTGeebpE
+YFc/Hq8rcCQAiL7AbhiZeNg/ca4yJdouOdaHaTMFJ8hFDI1tNOGeFK7ecOMBWQ97
+GxKs/KIqKYW42AN+QZ7l1Apr0CDZK5VTi931JjE4wCIgUgLi2zgwtZYl/kP896F1
+K5zR7kx773U2dvP3SeV2ziUe+4NH5oqdmVMeZyHfB+Fe/uU1AiYgHN/CXfop39tY
+HR8ARUWx7eJlaaKBoj/0UqH/9YirjdM0vGfrw4JCjIx78caNkNH9fCjesTqODr+I
+DBJaJv3Jpt9g8puqrYagXLppiaYSq5oOAu5fuQIDAQABAoIBAELaJRsjVHehgpAG
+NhOXo5eUA3Kt7Y58CPRc4Ns669iI00DVaoqRAKgCuJ4ORTSCKATJIUnSrJZNnlaF
+GKzzVc0tLtGxLxisLZu7cQ6bXe8GtOc9lo9zmjY2LOZsdNTu0tKIePGKiQvFGust
+fcNlnkliYJSKmH3PdVSLEYHdBOmznMR+M5nif33OmuK+LIQ8Go+jpahvqXSG60ae
+QAKlJCO9DARjhJqpYw1GgtzXSxpiVWBkJzIwnemOecgBtm6W/5GDzYPq9GE5lY7N
+MTjP9BmnpMC1gPQjiDICrd9eWSUv6fuHAClCi5lMDrktWvZcEB0tpoxcQZf2d0/m
+vRZK7uECgYEA8fKpaDaZqPNktgLsrlo35wmHa3tkWR1jmI5DY5BHoO+N+Bm/ISwP
+HHLTObXmgzbycA5OEsIeUdb8ZmO79WINb9Z8aAmhft6bBzY7xZRNSouq0G/IgzZn
+m9D6f4ivBDXs8lZpJe3/SrvfCmnxPBO/vzezX6FqiPV24jSXNYcb+B0CgYEA4hyu
+bbO7Mfd1NmlJO+OWmAJBqJQKmFByP6uraUX9cgS2F+jexsyX12WGMXRoQM4CAo5u
+CN29U9NyokpZmifUbKq7cZ4Hni5ag+Wt9a/kuevmv8ysYEzVY07gjwg51z2uXV54
+wBA0nQsT28RVdNAv36hMhqgMM1ZTNa4AXGdxi00CgYEAzJRqKENaxKAhfUGVzYtd
+j47gIcLxQ+T0zQ8l7i8WUf+dJLbohO0TTfPNpROo+TRh5NxDqdrX9k15mD4mtUMW
+p4VOJk5WbsddgMib2+IdRLY6VgrfGgvLqdYXqfTyP/Y1B2iHeln2rsOSweR45Vqx
+nMdFdcwwH+SmhHkBjnJS9QUCgYBlmm/C+dVvMXwpFAyFbdI4wiLQ5p0QLm34MGLY
+7kth1b4hZlHc0QiWEJfJVz6ViDyc+3V0ZHdz2HsVdAVpYOZyYhHSjylrKfcgd6/A
+y+YiqV9J5mW67Cui8Um03ARptNzKNe5al62ct+KXiVTBJd+tR8oDZDX/R5Yic+rT
+muQJrQKBgDCc6inyAzBN6mwEcFJCj4JBoIBiTfcwX+Rruh4gR8+7KbiG3UqaXSiO
+prONCswe6sXs6s6bFCZSOwgsom/bYloBZRVtTPJv031VDTFUQBo4956/7dhIDdvC
+2ZDBwgHPeywPPK0OrGBuAXzfIS0YqMZeEXtpSdw8A3seQ2398/ir
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-crldir/a836cc2d.r0 b/src/test/ssl/ssl/server-crldir/a836cc2d.r0
new file mode 100644
index 0000000..717951c
--- /dev/null
+++ b/src/test/ssl/ssl/server-crldir/a836cc2d.r0
@@ -0,0 +1,11 @@
+-----BEGIN X509 CRL-----
+MIIBnjCBhzANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3Qgc2VydmVyIGNlcnRzFw0xODEx
+MjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMBQwEgIBBhcNMTgxMTI3MTM0MDU1WjAN
+BgkqhkiG9w0BAQsFAAOCAQEAbVuJXemxM6HLlIHGWlQvVmsmG4ZTQWiDnZjfmrND
+xB4XsvZNPXnFkjdBENDROrbDRwm60SJDW73AbDbfq1IXAzSpuEyuRz61IyYKo0wq
+nmObJtVdIu3bVlWIlDXaP5Emk3d7ouCj5f8Kyeb8gm4pL3N6e0eI63hCaS39hhE6
+RLGh9HU9ht1kKfgcTwmB5b2HTPb4M6z1AmSIaMVqZTjIspsUgNF2+GBm3fOnOaiZ
+SEXWtgjMRXiIHbtU0va3LhSH5OSW0mh+L9oGUQDYnyuudnWGpulhqIp4qVkJRDDu
+41HpD83dV2uRtBLvc25AFHj7kXBflbO3gvGZVPYf1zVghQ==
+-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/server-multiple-alt-names.crt b/src/test/ssl/ssl/server-multiple-alt-names.crt
new file mode 100644
index 0000000..158153d
--- /dev/null
+++ b/src/test/ssl/ssl/server-multiple-alt-names.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDRDCCAiygAwIBAgIBBDANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0
+IENBIGZvciBQb3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3Qgc2VydmVyIGNl
+cnRzMB4XDTE4MTEyNzEzNDA1NFoXDTQ2MDQxNDEzNDA1NFowIDEeMBwGA1UECwwV
+UG9zdGdyZVNRTCB0ZXN0IHN1aXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA10iQpfVf4nCqjkRcLXP9ONQqdhMPMdjHasKqmsFTx83SZLKUzKMOb56j
+3bg83stqGoId4MIxtqnDKaSg1+kseQ1HCi0cu/E3lHLEkPibl9dyVGhXVnPDBdOp
+eUQLCvPNgay3/ua4Mz0Nf7S9cgC9AZu/EEYjQIEzfO0cEANeVIWtgZfM3+2j0pmZ
+46AEYlEZJkmDT0/mnHk3YTj3I6qQhr34wZvqcrwouYRN5NiOVYJLpb6gPSoalm+P
+YXOKjP4+fWr18HdZLrzsa4xPRa9XcsDNyffjWvQTfptR/2vFKN8Ffv3XUqxUx6av
+VCyRKa8xAUS/pHVGnzFQY+oqWREn2wIDAQABo2cwZTBjBgNVHREEXDBagh1kbnMx
+LmFsdC1uYW1lLnBnLXNzbHRlc3QudGVzdIIdZG5zMi5hbHQtbmFtZS5wZy1zc2x0
+ZXN0LnRlc3SCGioud2lsZGNhcmQucGctc3NsdGVzdC50ZXN0MA0GCSqGSIb3DQEB
+CwUAA4IBAQAuV+4TNADB/AinkjQVyzPtmeWDWZJDByRSGIzGlrYtzzrJzdRkohlL
+svZi0QQWbYq3pkRoUncYXXp/JvS48Ft1jRi87RpLRiPRxJC9Eq77kMS5UKCIs86W
+0nuYQ2tNmgHb7gnLHEr2t9gFEXcLwUAnRfNJK56KVmCl/v3/4kcVDLlL6L+pL80r
+WGKGvixNy3bDCJ/YGJu6NH+H7NMlqFcg07nEWUHgMzETGGycTcPy3S6mY5P/1Ep1
+MCSTucUKoBIte2t5p9vM6bsFIioQDAYABhacmC62Z5xNW8evmNVtBDPLR1THsWhq
+UjzdIzjpDgv1KUCVEPc1uVrZ5eju+aoU
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-multiple-alt-names.key b/src/test/ssl/ssl/server-multiple-alt-names.key
new file mode 100644
index 0000000..57f3114
--- /dev/null
+++ b/src/test/ssl/ssl/server-multiple-alt-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA10iQpfVf4nCqjkRcLXP9ONQqdhMPMdjHasKqmsFTx83SZLKU
+zKMOb56j3bg83stqGoId4MIxtqnDKaSg1+kseQ1HCi0cu/E3lHLEkPibl9dyVGhX
+VnPDBdOpeUQLCvPNgay3/ua4Mz0Nf7S9cgC9AZu/EEYjQIEzfO0cEANeVIWtgZfM
+3+2j0pmZ46AEYlEZJkmDT0/mnHk3YTj3I6qQhr34wZvqcrwouYRN5NiOVYJLpb6g
+PSoalm+PYXOKjP4+fWr18HdZLrzsa4xPRa9XcsDNyffjWvQTfptR/2vFKN8Ffv3X
+UqxUx6avVCyRKa8xAUS/pHVGnzFQY+oqWREn2wIDAQABAoIBACtGhP03Y/zq1P4g
+M79XT5G65IYzspw8jWmilBTjw+moMCDZ3Rt9s4swgpQxUtseXMfTXBowLzoeygJ2
++3YrgysaRit/ggUtqhSHNYhG0VAmmO7qwpO4VX24XJrp2KZs9+SXSa1Nx71VCn+f
+X22pRFUsb63fy3pN/oGgUEHPy4iFsDm5K62lszzzwXmcWam30dcK2Ddw07FAwpSR
+2hW0veXpbZs33CX0p0js2imlSBwMLPbYIXriRT8Wpkkp+LvSy7/vDK3hWGhrKflT
+hXZsHINEBwGwfEP7uSgTUiTlm96Fwp8SXLC0A7NJL8uJ2ARxuDqniq0UbqTpvGgY
+RlgkZVECgYEA8rPoMuhJYHkejfh05qqycs3RYAdMIeOZ0wGzbELR+nI0FoAITHOB
+KsdMUaNrjICMKAJBNx7cl4Qb5IP35dcZSyR9pr6W6QDCwnRDYxGSCakn3gEtLgix
+5rcrf7r8QUyDUjCjasAZoql3rlew6q4HtSfBKciNuQ1wYnuaf5QdQAUCgYEA4xQU
+xvTbAYAhpmrDhIMprStBtAHEM/1N2RiV7jlw3WI939WBSRTwqLmDSJNc4he5m43w
+Dew2HPLFW6m/2jsXhYCXkACsgn1E2o5wPPcHIgnKe8eO26HuSIg75OsDaxAOGtMi
+RQbXelxtXDnRbsjiChYRucH13EhU0lsaffNXrl8CgYBEU2WhP0e5AyAY88NlVNTc
+ARlaoXNLbxnVD3uFlOIsUY5cbzrm2vWYJ3dS3GDgsyfB87CMZgHQHf8EPCrD5+RV
+BTbihHFTs0UhHT4DW+TzF04D7+zaMtRykUqLsQZnE7U8pDi9SstswazRxhomV0wQ
+Mdrtemp7mE71SrraA8agSQKBgQDSfC62LQlEXszSQWxyTFI5XjtM68Y+mrGqZouz
+gjMIQqQv8uwgHfTlsO/sOgyC1pMJiYvWm/mc47vkt3hKhTPMX+IdbUJ6wjssi5Om
+LyTBfGngSp41H+iL+xvpmZ5Vg1BPtR2y9iCOH1aPgliLZFGCH+rWUN/hDHrzcdcg
+oIvJ6QKBgQCnYc+oZqcdisoSYPg7IZSWsPz3mWsFtR0Xw54zQ0n+gMjEzSDjO57+
+8nMUpXwnspo/x8qbsrZ39YeZZvwXVTXlpQjywOOGkIua84+EQUnknK+osKBQN62m
+PWfPxXCTkWSBf8V6KKG/LFS6bZZu96h7+uWHkEwcD+bACS+2vGuyLw==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-no-names.crt b/src/test/ssl/ssl/server-no-names.crt
new file mode 100644
index 0000000..97e1117
--- /dev/null
+++ b/src/test/ssl/ssl/server-no-names.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC1jCCAb4CAQUwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UEAww3VGVzdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNlcnZlciBjZXJ0czAe
+Fw0xODExMjcxMzQwNTRaFw00NjA0MTQxMzQwNTRaMCAxHjAcBgNVBAsMFVBvc3Rn
+cmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AJ85/vh52iDZAeQmWt47o0kR7VVlRLGf4sF4cfxPl+eIWIzhib1fajdcqFiy81LC
++t9bAyRqMyR3dve9RGK6IDFjMH/0DBf3tFSRnxN+5TdSAhKIJslmtUdOl8kS/smF
+4+BikCg509aq0U+ac79e/q42OyvH9X/cI6i9SPd4hzJDMCX54LZT1Of/90nSQX6E
+Oc5Hcj/d7psBugmMBW8uXYAGvJpq14e5RoK78F/mYbUNqtc1c8pi4/4quSMeEfQp
+Dgmzee7ts8SIbQT8mYJHjnaPvZYpv4+Ikc7F0wzLO1neTpsYaVvDrSMLBCdQkCU8
+vgb1T6WlVgbp/sfE5okSxx8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAh+erODlf
++mEK2toZhaAmikNJ3Toj9P7I0C0Mo/tl2aVz8jQc/ft5t3blwZHfRzC9WmgnZLdY
+yiCVgUlf9Kwhi836Btbczj3cK6MrngQneFBzSnCzsj40CuQAw5TOI8XRFFGL+fpl
+o7ZnbmMZRhkPqDmNWXfpu7CYOFQyExkDoo0lTfqM+tF8zuKVTmsuWWvZpjuvqWFQ
+/L+XRXi0cvhh+DY9vJiKNRg4exF7/tSedTJmLA8skuaXgAVez4rqzX4k1XnQo6Vi
+YpAIQ4dGiijY24fDq2I/6pO3xlWtN+Lwu44Mnn2vWRtXijT69P5R12W8XS7+ciTU
+NXu/iOo8f7mNDA==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-no-names.key b/src/test/ssl/ssl/server-no-names.key
new file mode 100644
index 0000000..2edea5c
--- /dev/null
+++ b/src/test/ssl/ssl/server-no-names.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAnzn++HnaINkB5CZa3jujSRHtVWVEsZ/iwXhx/E+X54hYjOGJ
+vV9qN1yoWLLzUsL631sDJGozJHd2971EYrogMWMwf/QMF/e0VJGfE37lN1ICEogm
+yWa1R06XyRL+yYXj4GKQKDnT1qrRT5pzv17+rjY7K8f1f9wjqL1I93iHMkMwJfng
+tlPU5//3SdJBfoQ5zkdyP93umwG6CYwFby5dgAa8mmrXh7lGgrvwX+ZhtQ2q1zVz
+ymLj/iq5Ix4R9CkOCbN57u2zxIhtBPyZgkeOdo+9lim/j4iRzsXTDMs7Wd5Omxhp
+W8OtIwsEJ1CQJTy+BvVPpaVWBun+x8TmiRLHHwIDAQABAoIBAAtkaeK7TSkGfcUm
+HWBDIharSrDOcxDGYMH47SbhRvwQ3E0QIfvDpOTbI0xdWV11h9+NMndbhdc5GPD2
+wLrTmFQQRbsR6f+ZAHUAikIp1RqVKoLK7QOB7rxwWhnP2xzuEHTQeIH4STjVte4d
+HeT2VgB+7tLeFqmURZTgHiVeoUWuPgl2/L7ABK8x7wk6/Ho/FyB6bQsX9ixfd99W
+lxAhKr46Sa6ceMygswvqzxUcFm0lCSfMt+VTscZTqtQnqiV8mMiJk5novBUgpS95
+JaLsAKsG2mYKHBaAs+EbIbgk0hBWnGB6DFTd8/62u7fJFWA5BvMiNBnde47wLRJL
+BAHzw7ECgYEA1DVbs4lN/GA8ol+ERGGIhZUa44jLgIFrE7kpYdKoi8AF8R8BaIjp
+xlI6z+sxLcp7Z/jccRToGH20OX1i+x4Vt8NGvzkEOdowX+BwMl92v7vSrr6PGdWg
+X5Nu6/ISVarIDWBSLGmdfqJ/szu+HRBh4CvFlrf+379COkZ9oc9Hh0kCgYEAwBWv
+6J4oi4nbxyzpDpI5YH8wu56A6uNJAD5z3pca7XaR6mOLiJhqhCQscthx901dV2fT
+tECOxX6/DEvI1LZejiyG0Y9LXfhH37ZpEsgY14SNC4SJOW3dzVld2JEcd/3+50A1
+86DXImZoWeZzbiavkAxecZFZ8zoTUifYeh9M0ycCgYAQHZE+PDIo9WIFbr2Lt+B3
+TJCDMRNLSgjIsaob3LSiEE4jNpiTyLoALqR6v8C3WoYuqi6Lg+vwWDOEnioTKgC6
+OOE5imnwvsonrdK3cJqDCw9/58bUTm3kdDzbPEH6MYMJyQPUjZzBTjPmd6YDbQgR
+zyEtRgHcGhk8dbf6vtQOyQKBgQCg0geDhNeBbJybt9gwoPB1AEh27RAWmNDH6YHt
+fSnIYxtr2Ig8hw+3LuogBWP1n8pkocM4CUz/wUyHKPQuU7n64wDFd2msdXEHtptm
+ZC2YU5wbZo3VjUzE3uuZpHTnabr/Nl11atZ0MLVxf2ZpdO5Mdm6kOwPKhncis8Wl
+CYuyxQKBgHPgMjy4OgNclXfZHkk8MrHYJrZsX4NYKCAQC9z5pkHP3Osakt5YPdPq
+Hti6rFMzagHlYY7k8tMx9F3j/kQZrBq4yfQIWsnx+OIr1A/A/0jbmTHMMqlQ8EBN
+p7nfoCzlvm0lHQV2nUb/9UfP/Wc5zDZZnnBm83zOTjBTBZ55twu6
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-password.key b/src/test/ssl/ssl/server-password.key
new file mode 100644
index 0000000..a8e383a
--- /dev/null
+++ b/src/test/ssl/ssl/server-password.key
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-256-CBC,B335CBE53A05F4FC5805FC038BA80BA0
+
+1U4+GwI8FGpcrk+9uzMlQU5UZ9xOJMOZb9xA3IYMw+2BLF7zVbAkYyyiqF2pKUmi
+doOYFOGIXNV1VhVwlw674SMN+PIg72b2F7DDrqEYlicLCU4o7eeGhoiIKzTksRTU
+YV3nYCCDZCEw7V+pFeGCUAc9fc+Y0BGMYIshLVdlTYgVjZScL5kHuD9t8xa6AaTS
+mQp3jInRnHjEJbRSZnFQ9CR1LUtmGE02TOcWzoGshFdwCdtO/lJzBmmMxoL/qV1R
+Cqc0PKSANsbgvTJMriZXYSFjpMYXmxBQXDYNuFfwq67bssAVIpTSvWu9SfcY/JwV
+OqERcb1zPgDmprDvd/L7Vh/cdEWWWewOVoUo89cT7CrLvMINHqE6smM2x1xv91BB
+AOpyoGJliPGAcLDVJINm9zC1ErEjSEcR/VumZKsgSTsBYgyYezTPQYAfe+h820rs
+eC4GMu+zr31U2TVLYcb4j2t19fTgaQBj/LH3OBse9+0quoJhzmDjKelS3O3BaF05
+DM20tJRHANM+1WQ9+aFinXa1ozcGsrLSUa99oFqL4vKgL7jd0+wmCzwxaSp3rHB3
+AFHCdUOayDAdPhnGwathhAZ0AjyEJyWnA47pEpWDr7SytpbiMwOoPcW8/oKid10e
+qBK7uGK1Zc7rtckjK3CrM1VFDbxzwGbF2aKHtFFyrJtUvJwfP0Y1V2DncOsiy5Nx
+gJ3vxfi11gxnhd9VmcoY3JVvTHOsw48xYNFrZXve/X3o9eUDqb9VRs/vV3t5w+xR
+RaUPdz9cdlp2AA4xW/IvIQ7XwuBWPaPVr/g9pUvI9iJ9Z4RdruvjqDAD+ICVx9MM
+8SuN7X3gmg4mF5FEL0ct5ZdP16U8/EYvl7Np7vN3kYqbqucwCJH15R8LckAfbzIH
+yYTXC1iik4GfyN9tTpQtZsZCvV2Uo+Fo3mxP/EzB6tNbfOi3LG/coverSwgZLQsA
+Q6+Kta4PT671xXdaGLT9tEMIai9SiW5acqcdhjYvcaP69J8ZtKpNpP6HTL7IZD8p
+SbMxE9jw+bYXILR3Ie0x98z4Z04Q28/bPbvPTbXK8nv6/YpjKgq4hrRG58psHdbX
+ggS3RNzcJJMDArBka+zvbWL4jfWZhllMyGqc7q/FuoEqC5JlMTUBpru3NTNp6ZgQ
+QXRV1Pc02ff8Dp1H8FP7B7bG3E2D9eTUqR60WvmGnuAqvXgA0+4rEaUKfxELH5qc
+dZgu/yiuMttCha835wMLnOxsOJmHILwrc6/uQWydx3vNEWFx0tbV3FzVBIvqdpME
+LA4iAAz5xqvLgA5ii23Hn18ycZGU7gTERK8RdiALRzPtBW6hPreQjiMTJnBaMhXA
+Xq9opGsNmH/rZgXuk2VZ79bbl9pKN+z9ssRGzbHCVlEckfaxlrYfANwzk8PbOrZJ
+6UW3Gf2PwRRNtiVEabf0upVng7V70KSRzjfC7KBHYwbRIL4nObgTG+vc1SjgNgrx
+Ue/e8h9qiDBmgdH0Uvqfqb19HF+QzmUNoP9TVQFj+4+DuW5zN0D8weF4TuBgyHr6
+Y+Rbmq0WJlIlc8KMwX87nACesmFNSJkI0ftSLDHrLuvXRtB8f7s2cw3hd81i+scE
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-revoked.crt b/src/test/ssl/ssl/server-revoked.crt
new file mode 100644
index 0000000..1ca5e6d
--- /dev/null
+++ b/src/test/ssl/ssl/server-revoked.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeQCAQYwDQYJKoZIhvcNAQELBQAwQjFAMD4GA1UEAww3VGVzdCBDQSBm
+b3IgUG9zdGdyZVNRTCBTU0wgcmVncmVzc2lvbiB0ZXN0IHNlcnZlciBjZXJ0czAe
+Fw0xODExMjcxMzQwNTRaFw00NjA0MTQxMzQwNTRaMEYxHjAcBgNVBAsMFVBvc3Rn
+cmVTUUwgdGVzdCBzdWl0ZTEkMCIGA1UEAwwbY29tbW9uLW5hbWUucGctc3NsdGVz
+dC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAooPO8lSz434p
+4PBYBbTN8jkLW3cHEpTCH4yvC0V40hzGEl30HPLp82e+kxr+Q0+gd82fvc4Yth5I
+PKINznp28GMs5/E9cUU3hMK4jFhKLMiOeIve3M/9ryHK874qpNjJoSxxPz7+s2eq
+WoFc2px0KFIamTTLfi7Ju9aPb/AMlZNsUnbRsj7fQc7EJ8rwOnezw2Wy5VK4soX+
+qpuJ0Nm44ApzT8YmjYX/kAX0yQxgQuYbpcBWr9cOQjegu3FAqHqRh9ye7d8jQzCv
+34Wg/ar4rkqyQDcokuWAE7KQbnk51t7omzhM8eswFOAL1pas/8jWBvy0VjYVU34P
+9aXxP8GiHQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAS9abT/PhJgwAnm46Rzu16
+lL7tDb3SeR1RL25xZLzCexHcYJFi7aDZix3QlLRvf6HPqqUPuPYRICTBF4+fieEh
+r5LotdAnadfYONwoB5GiYy2d93VGqlLosI27R6/tVvImXupviPpIYMDgBBRr1pZc
+ykQOjog6T+xk9TqsfFQDe2/VKF7a5RxOA/V77GZ5qge5Nlx9jSXQ/WUG9vDQj9BA
+d4nOwvjauKlcSqUU/3uVKntXQTNjmyq7S75eBitS920LLfjTL9LInLugDikFa/J/
+yPBkJLa/+rNMPikcnF3ci4Oi/XwLA8kGdGZAADuiIOeyORMuLFoTk7KpOYGKS5/U
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-revoked.key b/src/test/ssl/ssl/server-revoked.key
new file mode 100644
index 0000000..1787754
--- /dev/null
+++ b/src/test/ssl/ssl/server-revoked.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAooPO8lSz434p4PBYBbTN8jkLW3cHEpTCH4yvC0V40hzGEl30
+HPLp82e+kxr+Q0+gd82fvc4Yth5IPKINznp28GMs5/E9cUU3hMK4jFhKLMiOeIve
+3M/9ryHK874qpNjJoSxxPz7+s2eqWoFc2px0KFIamTTLfi7Ju9aPb/AMlZNsUnbR
+sj7fQc7EJ8rwOnezw2Wy5VK4soX+qpuJ0Nm44ApzT8YmjYX/kAX0yQxgQuYbpcBW
+r9cOQjegu3FAqHqRh9ye7d8jQzCv34Wg/ar4rkqyQDcokuWAE7KQbnk51t7omzhM
+8eswFOAL1pas/8jWBvy0VjYVU34P9aXxP8GiHQIDAQABAoIBABp1d0YBAGCzc8IJ
+n2seasFbBDxZ/q7JxWk5kG43W1pqEN2AqnPkIK7eXyq4JFl1J10Z/z35xhAwkfY9
+NB4/1gmBPBhvMF+2szlMMpu27CyqYnfB3gD5ZAYVbGOOvIamPP2erLltWi5/XD7r
+/OAixM6jv2zeKZtbpsCMSEIjRQk8+t4LXdT1ohrhm649QxmMpnYNjpXJ9U9uG0yP
+lr2+Lk2Xjv025kP765iGP3U5fW0/sONxXVM7uUyrvtnMJPhsKOdsNO84ratOVcnM
+BgzSEB0nldTcg7MUQyA+oOfDOXgoTQ5WZuXrx32sEDwN2ceCBg/qreaDwkC+KI7n
+nWazf0ECgYEA0ebcOCWwZlwvM/FNgC7Uqbxn6kjAkNvcETWxiwSaEf6MsTyytCJv
+RaVE7w5FsKKWxdu7ljft5LClNXTbscV5O5HuDMrLNSJFuhTm2dzYECZaUmXgN5gg
+JQAhhxevk3UBkGB/EYEDEBNtGjGWdWn0kFIsY4oN5l946cA27WXmsSUCgYEAxjTA
+XfGgM3+Q6hepN0J9XuVaebEkuTMlFmt82J4pMZxlam9zJDrDbzGVZ4kjCqLTh89v
+Tt+hE3xu+Df5D4Vb/uC+bu4EBpnvlqJRPNz4aQFxt+UILznS+oS4mLV1gK5PbIMA
+gh1+F58XJp3+NKxvUnjERpiUzZRgSh+9gtwWx5kCgYB3cfYzhU8CkMbTuicuIHgo
+NuyzZ78dL9/lczabM30xbDdHzJCs7UOA0HGP0AFcaMl/wnDXJPCdSOBasSsr2IIK
+ohpi8Sv+Coi/QZG0vHW/ivOvHAYh3NG9/HsX0yS4tsazEBZ/MXk6trNJSpqiKi9f
+yUM1SaRrSj0WV6lqIqjKeQKBgDw27nIb4/WBPb9AbPISyw+3UeNCg8uX1B6ZjRYq
+Bo3B27WYIjzRdWokgCUyLmkeynCp/kDSA5dt6DCUoJ+sfiRSlsgQmzx+K6Fxsohx
+AS61d5zMgc4HHSdqhsIt7oKncg2fRtpAp3v5owjiWsYZ1MATXF2uIRbLiu0581L8
+FheRAoGBANFH5W0DtT9kyzdiPddfBMFnrsUYYIPgejAao7PvCCW5tX8CVDcRkLhf
+tC9zZgA9WEm1irXZtbo9m3LYzH3yED8DvBcsfCdA20R/Ed/om2OiLKD7DDFl1W+C
+2NrwzcQlRlhHC8V2D8yRU+6YQ4Eu9dYzFnkGeCGSbiqaa9ZkPYx8
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-single-alt-name.crt b/src/test/ssl/ssl/server-single-alt-name.crt
new file mode 100644
index 0000000..e7403f3
--- /dev/null
+++ b/src/test/ssl/ssl/server-single-alt-name.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCzCCAfOgAwIBAgIBAzANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0
+IENBIGZvciBQb3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3Qgc2VydmVyIGNl
+cnRzMB4XDTE4MTEyNzEzNDA1NFoXDTQ2MDQxNDEzNDA1NFowIDEeMBwGA1UECwwV
+UG9zdGdyZVNRTCB0ZXN0IHN1aXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAxYocLWWuiDsDzJ7wLc0zfwkGJAEy4hlHjTA5GXSEnGPlOnx1fxejZOGL
+1HLff5h8zB+SQXrplHCcwwRrxVgGY7P59kXMXX1akTwXUJHc/EoTtqLO+6fHLygz
+F1d0i5NPO3xrk1wMt7bYLhiPbWpplWiHXzbJy8wf3dXgzCwtxXf8Z1UqjtCnA/Zk
+J/kPWuHJxzH5OvDJvZsq+Fbkl3catFpwUlAV9TKsC78W/K5I+afzppsmSvsIKAWW
+Dp7g71IVjvJeI6Aui2yhDn9iuJMuKe9RMYIwJLFqiX3urHcjaBSkJm6Lsf7gO30v
+kVwIyyGXRNTfZ2yPDoSXVZvOnq+gKwIDAQABoy4wLDAqBgNVHREEIzAhgh9zaW5n
+bGUuYWx0LW5hbWUucGctc3NsdGVzdC50ZXN0MA0GCSqGSIb3DQEBCwUAA4IBAQBU
+8wp8KZfS8vClx2gYSRlbXu3J1oAu4EBh45OuuRuLOJUhQZYcjFB3d/s0R1kcCQkB
+EekV9X1iQSzk/HQq4uWi6ViUzxTR67Q6TXEFo8iuqJ6Rag7R7G6fhRD1upf1lev+
+rz7F9GsoWLyLAg8//DUfq1kfQUyy6TxamoRs0vipZ4s0p4G8rbRCxKT1WTRLJFdd
+fSDVuMNuQQKTQXNdp6cYn+ikEhbUv/gG2S7Xiy2UM8oR7DR54nZBAKxgujWJZPfX
+/ieSwLxnLFyePwtwgk9xMmywFBjHWTxSdyI1UnJwWC917BSw4M00djsRv5COsBX7
+v/Co7oiMyTrCqyCsWOBu
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-single-alt-name.key b/src/test/ssl/ssl/server-single-alt-name.key
new file mode 100644
index 0000000..f719b0d
--- /dev/null
+++ b/src/test/ssl/ssl/server-single-alt-name.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAxYocLWWuiDsDzJ7wLc0zfwkGJAEy4hlHjTA5GXSEnGPlOnx1
+fxejZOGL1HLff5h8zB+SQXrplHCcwwRrxVgGY7P59kXMXX1akTwXUJHc/EoTtqLO
++6fHLygzF1d0i5NPO3xrk1wMt7bYLhiPbWpplWiHXzbJy8wf3dXgzCwtxXf8Z1Uq
+jtCnA/ZkJ/kPWuHJxzH5OvDJvZsq+Fbkl3catFpwUlAV9TKsC78W/K5I+afzppsm
+SvsIKAWWDp7g71IVjvJeI6Aui2yhDn9iuJMuKe9RMYIwJLFqiX3urHcjaBSkJm6L
+sf7gO30vkVwIyyGXRNTfZ2yPDoSXVZvOnq+gKwIDAQABAoIBAEtW0EZUKIuWjm1l
+FM8zGvfRVkE3H9PxtkNX5/8YXFdVFiEHRLyzJEMebnkZUrpUSwyC4gINQba2eGM8
+dWnvl4hBJQ1TM41YeMk5dN7qsrCaBAi88VozdBk9KLc3SKDPDwHuAw1RpxwOJUb3
+YQRm+FveYPrkZ3RNpr2xi6nzE4XjAH3LbF8EqBYC7LUm4GUxBl6Ke2rQiC8XwE0B
+0V0nvBvjkJ4tT4l9RTtInRr2vmqSWXp5bGVuuWqWxA/tBtTHhGbO+PG4i+C+u3NL
+qQgCbboqxyivPaMiMDdDhQL9TjAMCkItVxcwZlGdSA0+d5mraIXhs2ifF45soo4Y
+9vb4++kCgYEA6YpFAqYJnL0p5AIh14yxhWykLaE1YpKkw4P9SpxtGQIfwNDHyXme
+v/S66fLNvOjQyH6Y+gvGBGVHAwpM0RiPAHBADKAue6V9pbUITD0aQL9HOj9LEC+V
+8R2S5VSxNDpnMnd0DVdxiWGuNhzTG2McFXYduC3NpTLurK6ecPhVoI8CgYEA2ImC
+ZN2NS15AxWybddSgYYcL/44t9dfpEJ/4PJ1ISStIibHqpVWkq7vC4P7mMmS3DItr
+N3QDjBGltc1R4UfSCLakzSs5buC0LiO2uoMZFcOiZEd0r73iTLM8d9wOZRysYtHw
+T2tS1NvMrwK4TGZh+GWuUlSfuIbZ505PCQfYnKUCgYBdynoMpkIWAKJiP7j3qDlj
+LE6DRMr724jwPIHtBQWLlZ7LAQ47i+yFivPGIQ0fYSD4ZF9rjG7qNQJf5jMThpln
+w6z1ZR9F6SCc/Cdo9uEkP62LZv/ucC33t7jXATxpjUsEqZSiBwxB8EjW0py10wfk
+Vpt47Gw6fEn+b+KR3CoHYQKBgQDYKe4R41p6Ms1WSOWo62pv8fD4XWdkVPZFsmyN
+ljXjVWJEk2g7RRPunLIfClejVwelbkjKQvaHjdZvd1iWHKyAJlS+vLfJCGjW5rAz
+4UvZfkNO+EZ0aorPJt7miLeWGNm+jPXpUqqN9B6RV5XELzD+WAN+DRyliXlef75G
+tZ54QQKBgQCDXfap0mLGYXbFMLPF4Q1mnML5deVjyoGteAF+qky54rZvf0h2bWN2
+pi8hUOdwTtazgkd+rJp/a7eqQL/72nvCMonsb7vBNHmmNqZgaC9zyTV9AOE0Txc/
+vU6rvU+nQvt7esuTILS1O4SEeNI0JG22Dx01ebJQuKDcyOTOxv56vw==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server-ss.crt b/src/test/ssl/ssl/server-ss.crt
new file mode 100644
index 0000000..d775e60
--- /dev/null
+++ b/src/test/ssl/ssl/server-ss.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGDCCAgCgAwIBAgIUVR71MjsbvBO6T1gJQaL/6hMwhqQwDQYJKoZIhvcNAQEL
+BQAwRjEkMCIGA1UEAwwbY29tbW9uLW5hbWUucGctc3NsdGVzdC50ZXN0MR4wHAYD
+VQQLDBVQb3N0Z3JlU1FMIHRlc3Qgc3VpdGUwHhcNMTgxMTI3MTM0MDU0WhcNNDYw
+NDE0MTM0MDU0WjBGMSQwIgYDVQQDDBtjb21tb24tbmFtZS5wZy1zc2x0ZXN0LnRl
+c3QxHjAcBgNVBAsMFVBvc3RncmVTUUwgdGVzdCBzdWl0ZTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBANWzVPMk7i5f+W0eEadRE+TTAtsIK08CkLMUnjs7
+zJkxnnm6RGBXPx6vK3AkAIi+wG4YmXjYP3GuMiXaLjnWh2kzBSfIRQyNbTThnhSu
+3nDjAVkPexsSrPyiKimFuNgDfkGe5dQKa9Ag2SuVU4vd9SYxOMAiIFIC4ts4MLWW
+Jf5D/PehdSuc0e5Me+91Nnbz90nlds4lHvuDR+aKnZlTHmch3wfhXv7lNQImIBzf
+wl36Kd/bWB0fAEVFse3iZWmigaI/9FKh//WIq43TNLxn68OCQoyMe/HGjZDR/Xwo
+3rE6jg6/iAwSWib9yabfYPKbqq2GoFy6aYmmEquaDgLuX7kCAwEAATANBgkqhkiG
+9w0BAQsFAAOCAQEAtHR6o4UIO/aWEAzcmnJKsQDC999jbQGiqs9+v62mz5TvCk/1
+gL9/s/yfGY+pnDGW1ijI2xiL9KCJzjd8YB+F8iUViVQ6uHBxghxC1H2qOIr2UPFQ
+gQRu7d0DByQBsiXMOdw10luGo1oHhqMe5J7VyMVG/7aRpr6zYKrH7PzsB8ucvxzv
+Lm8ez0WBPebV69sim431iJcVcxxBbFd4qUJ9cHIc7VO2mSaazsIOzbd400POF/vk
+gfpDs48GfnZ+X3hgoQA4u7eudLqttI+j1xV+IHlCtaa1nDHymUrN/FhI1x+6c1SU
+V12eHqVatPMe0d+OCJPqIL9lbe+sGXlxDkMqAQ==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server-ss.key b/src/test/ssl/ssl/server-ss.key
new file mode 100644
index 0000000..a5e7f4b
--- /dev/null
+++ b/src/test/ssl/ssl/server-ss.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAttI8Qu3wJgwp9RqLEqgO5GlYdA4bG+OyB4y+Bx4DQ7fhaJvJ
+vwBfY8ig+nPZU5WJNDyoaPtDg2uQwWbUq9WXkdktAxelrisQ/oJiCscPARlAIM6U
+XPOXai10XQ6QsMaaa6cXIGkMZVfhYEbuN0Q8TJSC1MbSQ/TtCnWxMRmktRv5xx8y
+2DgGbYRSiuBNE0vneQMjBJeRs19k/V8Alk49vOk8T8Ep0PFkAQglpUc7/LFueKry
+sVW+6ptdmqqHUZTdZhU/IojKEhUF8dtyGpGF9bE0kfjB7vXth8LKQ9Utik7VS1tQ
+E8fK2ttvxAqDf8s3MChnXbMN6QrzaGSS44oZKwIDAQABAoIBAQCsMhjQcYxgSdS7
+J+UWDXT5bsABj8e43R4yPj4+NeadQfTtss6zOIagVbhKLhPkxlQU/40gVIminw4Z
+G+H0d5nrpcLfxsYm5il7jxMJU7SuNDpSrEDRzB4xw952R+HAn0+TVi5SOJ+UVNHS
+/Pwi0gJ5nugbwakKmQcrcu8ScDTS34yFScMIRUYiKQ44TDiDtwyL07sc4ob1X/uY
+na1GnyOBjYEg+9nRwmV+QZwPjyP3Qe81rvH7saV2R2oc4uFU151KtoIT96V4FnpD
+CsFPGVul4i428zYDEguzEMWvPkOKrfFA63gfYl3JRbPYE9ISdZXU8UX6XEE3Y6sU
+VmeXK/IBAoGBAPLELsFdMqMhycXxrGuQ4HiH5/vQ2RhFcOf842a8qysopFC5kluf
+Y83dMy9GP8BLgt/NMseBDFcaGgaZPhD10kpW0BCg5GKdkcBfuIE5C/rfe4m82TqC
+VkriGimG8FkTCQS9ghGwbDWYZnMZbqyJ2iRt/+M9Lvfd1tYTCQtNuI/TAoGBAMDJ
+g/vS12j4uJ+SDy7QY6suSOjkQoGO8qonhx9W/2XJSH4KL1ubeOt4eOJksHnwueCW
+YD+2+T4+NDumMnmT7Rl8is1RfnnWNH91tylPB9I5Hyff/+PU5rUSoRfpmeckH3pc
+5EmfoaqT7V436hjkZF1uY1QJjDMopSyg3TSVmtJJAoGBAOAVAAdSDGSR0ppYuktL
+njcDU7mXjFwf9NjeD9UL/39F823j7NmPpWMnc5CGamAIE1SbJu0KDCb2G+HNX4Hu
+Nd6ShVlj+YuhYCDO+hKlYo597J016Nld52MVkwqT2JSPwnpSppXHYAqUkrRZXFP8
+ppOLyW+qUQlQBnbxeR6q7dorAoGAU/gxzYCDJIyY1Z/njwtjLg8l4JJzSTz7AxPe
+bc8VxL7MHWHQSMVKAL3jYTBcEXDBdUnqODY5D1xGoeh8uamtrtkpF860GNss2Its
+MMjkbjNFF7ggG5sCtcGceu7bguqf70sAf/TQlJyD2fLeuuLXuD45c0QJVcsRToPu
+dVXoLkkCgYEA5JkhCPE9a2ssrqi1ZFt3FAayulycM+r+wJY7zNIwv/ydzbE5D4Ar
+s8YOjhwyBoyorrEztcvIKF5XA89ZIwHiKZQxJTWGeFKq/PYTclzwlOXAimkdYNXn
+Nc/5RLjEeophXb4f3WPjMBliYGX0tOPhT+buHep+f0enxgO8Z2a4Omg=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/server.crl b/src/test/ssl/ssl/server.crl
new file mode 100644
index 0000000..717951c
--- /dev/null
+++ b/src/test/ssl/ssl/server.crl
@@ -0,0 +1,11 @@
+-----BEGIN X509 CRL-----
+MIIBnjCBhzANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3Qgc2VydmVyIGNlcnRzFw0xODEx
+MjcxMzQwNTVaFw00NjA0MTQxMzQwNTVaMBQwEgIBBhcNMTgxMTI3MTM0MDU1WjAN
+BgkqhkiG9w0BAQsFAAOCAQEAbVuJXemxM6HLlIHGWlQvVmsmG4ZTQWiDnZjfmrND
+xB4XsvZNPXnFkjdBENDROrbDRwm60SJDW73AbDbfq1IXAzSpuEyuRz61IyYKo0wq
+nmObJtVdIu3bVlWIlDXaP5Emk3d7ouCj5f8Kyeb8gm4pL3N6e0eI63hCaS39hhE6
+RLGh9HU9ht1kKfgcTwmB5b2HTPb4M6z1AmSIaMVqZTjIspsUgNF2+GBm3fOnOaiZ
+SEXWtgjMRXiIHbtU0va3LhSH5OSW0mh+L9oGUQDYnyuudnWGpulhqIp4qVkJRDDu
+41HpD83dV2uRtBLvc25AFHj7kXBflbO3gvGZVPYf1zVghQ==
+-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/server_ca.crt b/src/test/ssl/ssl/server_ca.crt
new file mode 100644
index 0000000..9f727bf
--- /dev/null
+++ b/src/test/ssl/ssl/server_ca.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAfWgAwIBAgIBATANBgkqhkiG9w0BAQsFADBAMT4wPAYDVQQDDDVUZXN0
+IHJvb3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzdWl0
+ZTAeFw0xODExMjcxMzQwNTRaFw00NjA0MTQxMzQwNTRaMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBzZXJ2ZXIg
+Y2VydHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiSnYZbmc9vpCt
+Ku1sKV9l663JCceubhMw8Gg16kV0hXEFf/TgGC4zkiYNHN7+G45YD7Nq0kBCq3dH
+t2wPCc6c8pQoI64dfprVqPkvzoe1WBpZNetkUTk20v08jNeRa7XdRbRR6we1s9VG
+/prp8Hs2mmHqEfLuI9lvTT0Dz+VMmfFI8Lf278r+w+qOtVloAkX7AOyoLEJlNS0B
+QW9YWdH9N5ctaUXMG6lLV2OAjs+W1smpKfpIpMCA1lPGlElu70hynon/nQQvBP77
+SfQpZVc0esM18jkZpr5LEKUCw+x6LaMsqmBHpAULfCffxn2r0uMBW4L4VaGg3W6F
+h6iuJwRfAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+AFlcKTaU/Ug3Q0hr3P1UQ6dWyK4aVn9rs4jvVfFl0a0RnbBowqK2C+zQVUWYTcjo
+KHREVje65goj6VzRB6ko/9mAQ6PZP8jRuRhfCmvmvSQ/mWdgPzSRsUh9MwgEm9c2
+vNbqwaznEU8cYZnLpHiR9O5S7/qWWxehjYtxk5Eb4J006YglYfHnhrRFJvPbiqlf
+IOEivZ7gIVfvaOTbLjmN2kLOnzdlwpXGjxxg4Nu9ZhXOhfrplzUvRfmqvVsDiHXb
+USIdX+OFZZqr64IKG4drT4K4Bt2wupOEyX4ZFsUXXd+Hgq83SWmV4wzflcpmGkLC
+JZ3CEMu8/WA5uQBXdQUozlE=
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/server_ca.key b/src/test/ssl/ssl/server_ca.key
new file mode 100644
index 0000000..0204dcf
--- /dev/null
+++ b/src/test/ssl/ssl/server_ca.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4kp2GW5nPb6QrSrtbClfZeutyQnHrm4TMPBoNepFdIVxBX/0
+4BguM5ImDRze/huOWA+zatJAQqt3R7dsDwnOnPKUKCOuHX6a1aj5L86HtVgaWTXr
+ZFE5NtL9PIzXkWu13UW0UesHtbPVRv6a6fB7Npph6hHy7iPZb009A8/lTJnxSPC3
+9u/K/sPqjrVZaAJF+wDsqCxCZTUtAUFvWFnR/TeXLWlFzBupS1djgI7PltbJqSn6
+SKTAgNZTxpRJbu9Icp6J/50ELwT++0n0KWVXNHrDNfI5Gaa+SxClAsPsei2jLKpg
+R6QFC3wn38Z9q9LjAVuC+FWhoN1uhYeoricEXwIDAQABAoIBAG/c2Ua3Eegu5PwF
+hXp7dUI/4BfKcuBiX7BIl2tXlOAF+xn56AZDTcuGirLeu8knvBUCJfI/Xy7V7lAQ
+dyVz9qQVMnIpOTAxXr8SGaStVt6pX9UL14IuuLbGVBLzmLi1YyCwDXSVV6lV7a97
+uv68N1yFsrNwDgP0ys6/gtL4mWRkouFBbfJS9gDQjgSYTjWUx4wSkMw8h939RPkS
+ccmdXJxwhDBTP8iJIhVVR0FK7fCQvsQCuchsvYPTu5VCExTfNhO5A8oGZncnQRPH
+/RAAdPW2kAZV2oKkz5RAIDTnVkhXv1wQLty9E2PgjE04Fexe4Q7PCGmbrh7nBp73
+ARVuTRECgYEA/U3gwte+2pRzEciuoB4b+8Sod9eBxQGLhcQWOx2o0PAL2FIVWKxO
+exUC/ipmEX0mEwnAvVKGO1MTsOj6LUZELgbnygscLEQc1bNauKuQu/ChVfBgJ9ng
+IQR6z2BhrsOflfU8n01oMOjfVlVzZMdI5Nwy09OQgzu8tA125TUSkPkCgYEA5LL8
+bh5iykAlgXy7pA6WZmZXV9kaSTdnGGZ6GS4sbFJ8ZVw+tWi+xTUL9DES4keQTga7
+uneWu7q8wP7zdHCnKRhhBnbKFz1Voh4Z2VNSW9OjJHS4+lZfd94JZavOdwVMOQK9
+9Zjn2nsZalGb77pP/zDQSHnptwJOAKV9K5X5bhcCgYEA3wCODNtlogepWpHD8yEu
+37Nd4cvv6mIW1Fgyfi75baC3vOVe96cSnNd9wAfRvjngqEgg0Exz8oyMPGaPwgYn
+hiH8NGsdjXcVr6nsB8K7dEo/r2olLVBqOoc4G5Qty29b8uhpS5IZVV3fKaGnKqUr
+7phRgx2yD2crFZT5BId55fkCgYEA0pk39zeoBVL4trrkF/kO6pEHkW/s0stf9oo3
+mYR5h+6VesltPaSNOBDXRzEBkLoR2qg8q0OA3LLjKl4cVnd+l0tJ3af6BViHyNgr
+OYOO7dx/+Qw7Ytuy4jDnOmbxo4yPWlh6EPWTXA/LoaoxDJrnG7oZswQBAnPu3I2I
+LGDY5lUCgYEAp/utnKRE/mXDFryMHJL2/srlg6EffzeM3ncpXhzZDmaKeMUadjKo
+vNmhneHA7CSZM5LjUAiw4LfRx1V4hrKVtYmtizXKBbGYVZDD4CSh52T8w4nxNN6D
++uw478XrZzjjuCWODpdIKfngo9ip9+TysUZuFdmE3xKRitmDvW//aRE=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
new file mode 100644
index 0000000..8cdd0d2
--- /dev/null
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -0,0 +1,600 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+use File::Copy;
+
+use FindBin;
+use lib $FindBin::RealBin;
+
+use SSLServer;
+
+if ($ENV{with_ssl} ne 'openssl')
+{
+ plan skip_all => 'OpenSSL not supported by this build';
+}
+else
+{
+ plan tests => 110;
+}
+
+#### Some configuration
+
+# This is the hostname used to connect to the server. This cannot be a
+# hostname, because the server certificate is always for the domain
+# postgresql-ssl-regression.test.
+my $SERVERHOSTADDR = '127.0.0.1';
+# This is the pattern to use in pg_hba.conf to match incoming connections.
+my $SERVERHOSTCIDR = '127.0.0.1/32';
+
+# Allocation of base connection string shared among multiple tests.
+my $common_connstr;
+
+# The client's private key must not be world-readable, so take a copy
+# of the key stored in the code tree and update its permissions.
+#
+# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+# of the tests.
+my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+foreach my $key (@keys)
+{
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+}
+
+# Also make a copy of that explicitly world-readable. We can't
+# necessarily rely on the file in the source tree having those
+# permissions. Add it to @keys to include it in the final clean
+# up phase.
+copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+chmod 0644, "ssl/client_wrongperms_tmp.key";
+push @keys, 'client_wrongperms';
+
+#### Set up the server.
+
+note "setting up data directory";
+my $node = get_new_node('primary');
+$node->init;
+
+# PGHOST is enforced here to set up the node, subsequent connections
+# will use a dedicated connection string.
+$ENV{PGHOST} = $node->host;
+$ENV{PGPORT} = $node->port;
+$node->start;
+
+# Run this before we lock down access below.
+my $result = $node->safe_psql('postgres', "SHOW ssl_library");
+is($result, 'OpenSSL', 'ssl_library parameter');
+
+configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
+ 'trust');
+
+note "testing password-protected keys";
+
+open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
+print $sslconf "ssl=on\n";
+print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
+print $sslconf "ssl_key_file='server-password.key'\n";
+print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
+close $sslconf;
+
+command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+$node->_update_pid(0);
+
+open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
+print $sslconf "ssl=on\n";
+print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
+print $sslconf "ssl_key_file='server-password.key'\n";
+print $sslconf "ssl_passphrase_command='echo secret1'\n";
+close $sslconf;
+
+command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+$node->_update_pid(1);
+
+# Test compatibility of SSL protocols.
+# TLSv1.1 is lower than TLSv1.2, so it won't work.
+$node->append_conf(
+ 'postgresql.conf',
+ qq{ssl_min_protocol_version='TLSv1.2'
+ssl_max_protocol_version='TLSv1.1'});
+command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with incorrect SSL protocol bounds');
+# Go back to the defaults, this works.
+$node->append_conf(
+ 'postgresql.conf',
+ qq{ssl_min_protocol_version='TLSv1.2'
+ssl_max_protocol_version=''});
+command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with correct SSL protocol bounds');
+
+### Run client-side tests.
+###
+### Test that libpq accepts/rejects the connection correctly, depending
+### on sslmode and whether the server's certificate looks correct. No
+### client certificate is used in these tests.
+
+note "running client tests";
+
+switch_server_cert($node, 'server-cn-only');
+
+# Set of default settings for SSL parameters in connection string. This
+# makes the tests protected against any defaults the environment may have
+# in ~/.postgresql/.
+my $default_ssl_connstr = "sslkey=invalid sslcert=invalid sslrootcert=invalid sslcrl=invalid sslcrldir=invalid";
+
+$common_connstr =
+ "$default_ssl_connstr user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+# The server should not accept non-SSL connections.
+$node->connect_fails(
+ "$common_connstr sslmode=disable",
+ "server doesn't accept non-SSL connections",
+ expected_stderr => qr/\Qno pg_hba.conf entry\E/);
+
+# Try without a root cert. In sslmode=require, this should work. In verify-ca
+# or verify-full mode it should fail.
+$node->connect_ok(
+ "$common_connstr sslrootcert=invalid sslmode=require",
+ "connect without server root cert sslmode=require");
+$node->connect_fails(
+ "$common_connstr sslrootcert=invalid sslmode=verify-ca",
+ "connect without server root cert sslmode=verify-ca",
+ expected_stderr => qr/root certificate file "invalid" does not exist/);
+$node->connect_fails(
+ "$common_connstr sslrootcert=invalid sslmode=verify-full",
+ "connect without server root cert sslmode=verify-full",
+ expected_stderr => qr/root certificate file "invalid" does not exist/);
+
+# Try with wrong root cert, should fail. (We're using the client CA as the
+# root, but the server's key is signed by the server CA.)
+$node->connect_fails(
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "connect with wrong server root cert sslmode=require",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+$node->connect_fails(
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "connect with wrong server root cert sslmode=verify-ca",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+$node->connect_fails(
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "connect with wrong server root cert sslmode=verify-full",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+
+# Try with just the server CA's cert. This fails because the root file
+# must contain the whole chain up to the root CA.
+$node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+
+# And finally, with the correct root cert.
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "connect with correct server CA cert file sslmode=require");
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "connect with correct server CA cert file sslmode=verify-ca");
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "connect with correct server CA cert file sslmode=verify-full");
+
+# Test with cert root file that contains two certificates. The client should
+# be able to pick the right one, regardless of the order in the file.
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+
+# CRL tests
+
+# Invalid CRL filename is the same as no CRL, succeeds
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslcrl option with invalid file name");
+
+# A CRL belonging to a different CA is not accepted, fails
+$node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+
+# The same for CRL directory. sslcrl='' is added here to override the
+# invalid default, so as this does not interfere with this case.
+$node->connect_fails(
+ "$common_connstr sslcrl='' sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+
+# With the correct CRL, succeeds (this cert is not revoked)
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "CRL with a non-revoked cert");
+
+# The same for CRL directory
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "directory CRL with a non-revoked cert");
+
+# Check that connecting with verify-full fails, when the hostname doesn't
+# match the hostname in the server's certificate.
+$common_connstr =
+ "$default_ssl_connstr user=ssltestuser dbname=trustdb sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+
+$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
+ "mismatch between host name and server certificate sslmode=require");
+$node->connect_ok(
+ "$common_connstr sslmode=verify-ca host=wronghost.test",
+ "mismatch between host name and server certificate sslmode=verify-ca");
+$node->connect_fails(
+ "$common_connstr sslmode=verify-full host=wronghost.test",
+ "mismatch between host name and server certificate sslmode=verify-full",
+ expected_stderr =>
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+);
+
+# Test Subject Alternative Names.
+switch_server_cert($node, 'server-multiple-alt-names');
+
+$common_connstr =
+ "$default_ssl_connstr user=ssltestuser dbname=trustdb sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok(
+ "$common_connstr host=dns1.alt-name.pg-ssltest.test",
+ "host name matching with X.509 Subject Alternative Names 1");
+$node->connect_ok(
+ "$common_connstr host=dns2.alt-name.pg-ssltest.test",
+ "host name matching with X.509 Subject Alternative Names 2");
+$node->connect_ok("$common_connstr host=foo.wildcard.pg-ssltest.test",
+ "host name matching with X.509 Subject Alternative Names wildcard");
+
+$node->connect_fails(
+ "$common_connstr host=wronghost.alt-name.pg-ssltest.test",
+ "host name not matching with X.509 Subject Alternative Names",
+ expected_stderr =>
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+);
+$node->connect_fails(
+ "$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
+ "host name not matching with X.509 Subject Alternative Names wildcard",
+ expected_stderr =>
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+);
+
+# Test certificate with a single Subject Alternative Name. (this gives a
+# slightly different error message, that's all)
+switch_server_cert($node, 'server-single-alt-name');
+
+$common_connstr =
+ "$default_ssl_connstr user=ssltestuser dbname=trustdb sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok(
+ "$common_connstr host=single.alt-name.pg-ssltest.test",
+ "host name matching with a single X.509 Subject Alternative Name");
+
+$node->connect_fails(
+ "$common_connstr host=wronghost.alt-name.pg-ssltest.test",
+ "host name not matching with a single X.509 Subject Alternative Name",
+ expected_stderr =>
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+);
+$node->connect_fails(
+ "$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
+ "host name not matching with a single X.509 Subject Alternative Name wildcard",
+ expected_stderr =>
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+);
+
+# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
+# should be ignored when the certificate has both.
+switch_server_cert($node, 'server-cn-and-alt-names');
+
+$common_connstr =
+ "$default_ssl_connstr user=ssltestuser dbname=trustdb sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
+ "certificate with both a CN and SANs 1");
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
+ "certificate with both a CN and SANs 2");
+$node->connect_fails(
+ "$common_connstr host=common-name.pg-ssltest.test",
+ "certificate with both a CN and SANs ignores CN",
+ expected_stderr =>
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+);
+
+# Finally, test a server certificate that has no CN or SANs. Of course, that's
+# not a very sensible certificate, but libpq should handle it gracefully.
+switch_server_cert($node, 'server-no-names');
+$common_connstr =
+ "$default_ssl_connstr user=ssltestuser dbname=trustdb sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+
+$node->connect_ok(
+ "$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
+ "server certificate without CN or SANs sslmode=verify-ca");
+$node->connect_fails(
+ $common_connstr . " "
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ "server certificate without CN or SANs sslmode=verify-full",
+ expected_stderr =>
+ qr/could not get server's host name from server certificate/);
+
+# Test that the CRL works
+switch_server_cert($node, 'server-revoked');
+
+$common_connstr =
+ "$default_ssl_connstr user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+# Without the CRL, succeeds. With it, fails.
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "connects without client-side CRL");
+$node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "does not connect with client-side CRL file",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+# sslcrl='' is added here to override the invalid default, so as this
+# does not interfere with this case.
+$node->connect_fails(
+ "$common_connstr sslcrl='' sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "does not connect with client-side CRL directory",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+
+# pg_stat_ssl
+command_like(
+ [
+ 'psql', '-X',
+ '-A', '-F',
+ ',', '-P',
+ 'null=_null_', '-d',
+ "$common_connstr sslrootcert=invalid", '-c',
+ "SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
+ ],
+ qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,_null_,_null_,_null_\r?$}mx,
+ 'pg_stat_ssl view without client certificate');
+
+# Test min/max SSL protocol versions.
+$node->connect_ok(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "connection success with correct range of TLS protocol versions");
+$node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "connection failure with incorrect range of TLS protocol versions",
+ expected_stderr => qr/invalid SSL protocol version range/);
+$node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "connection failure with an incorrect SSL protocol minimum bound",
+ expected_stderr => qr/invalid ssl_min_protocol_version value/);
+$node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "connection failure with an incorrect SSL protocol maximum bound",
+ expected_stderr => qr/invalid ssl_max_protocol_version value/);
+
+### Server-side tests.
+###
+### Test certificate authorization.
+
+note "running server tests";
+
+$common_connstr =
+ "$default_ssl_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR host=localhost";
+
+# no client cert
+$node->connect_fails(
+ "$common_connstr user=ssltestuser sslcert=invalid",
+ "certificate authorization fails without client cert",
+ expected_stderr => qr/connection requires a valid client certificate/);
+
+# correct client cert in unencrypted PEM
+$node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "certificate authorization succeeds with correct client cert in PEM format"
+);
+
+# correct client cert in unencrypted DER
+$node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+);
+
+# correct client cert in encrypted PEM
+$node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted PEM format"
+);
+
+# correct client cert in encrypted DER
+$node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+);
+
+# correct client cert in encrypted PEM with wrong password
+$node->connect_fails(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
+ expected_stderr =>
+ qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+);
+
+
+# correct client cert using whole DN
+my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+$node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+# same thing but with a regex
+$dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+$node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN regex mapping");
+
+# same thing but using explicit CN
+$dn_connstr = "$common_connstr dbname=certdb_cn";
+
+$node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+
+
+TODO:
+{
+ # these tests are left here waiting on us to get better pty support
+ # so they don't hang. For now they are not performed.
+
+ todo_skip "Need Pty support", 4;
+
+ # correct client cert in encrypted PEM with empty password
+ $node->connect_fails(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword=''",
+ "certificate authorization fails with correct client cert and empty password in encrypted PEM format",
+ expected_stderr =>
+ qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": processing error\E!
+ );
+
+ # correct client cert in encrypted PEM with no password
+ $node->connect_fails(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key",
+ "certificate authorization fails with correct client cert and no password in encrypted PEM format",
+ expected_stderr =>
+ qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": processing error\E!
+ );
+
+}
+
+# pg_stat_ssl
+command_like(
+ [
+ 'psql',
+ '-X',
+ '-A',
+ '-F',
+ ',',
+ '-P',
+ 'null=_null_',
+ '-d',
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ '-c',
+ "SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
+ ],
+ qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ 'pg_stat_ssl with client certificate');
+
+# client key with wrong permissions
+SKIP:
+{
+ skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+
+ $node->connect_fails(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
+ "certificate authorization fails because of file permissions",
+ expected_stderr =>
+ qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!
+ );
+}
+
+# client cert belonging to another user
+$node->connect_fails(
+ "$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "certificate authorization fails with client cert belonging to another user",
+ expected_stderr =>
+ qr/certificate authentication failed for user "anotheruser"/,
+ # certificate authentication should be logged even on failure
+ log_like =>
+ [qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+
+# revoked client cert
+$node->connect_fails(
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "certificate authorization fails with revoked client cert",
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ # revoked certificates should not authenticate the user
+ log_unlike => [qr/connection authenticated:/],);
+
+# Check that connecting with auth-option verify-full in pg_hba:
+# works, iff username matches Common Name
+# fails, iff username doesn't match Common Name.
+$common_connstr =
+ "$default_ssl_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR host=localhost";
+
+$node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "auth_option clientcert=verify-full succeeds with matching username and Common Name",
+ # verify-full does not provide authentication
+ log_unlike => [qr/connection authenticated:/],);
+
+$node->connect_fails(
+ "$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "auth_option clientcert=verify-full fails with mismatching username and Common Name",
+ expected_stderr =>
+ qr/FATAL: .* "trust" authentication failed for user "anotheruser"/,
+ # verify-full does not provide authentication
+ log_unlike => [qr/connection authenticated:/],);
+
+# Check that connecting with auth-optionverify-ca in pg_hba :
+# works, when username doesn't match Common Name
+$node->connect_ok(
+ "$common_connstr user=yetanotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "auth_option clientcert=verify-ca succeeds with mismatching username and Common Name",
+ # verify-full does not provide authentication
+ log_unlike => [qr/connection authenticated:/],);
+
+# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
+switch_server_cert($node, 'server-cn-only', 'root_ca');
+$common_connstr =
+ "$default_ssl_connstr user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR host=localhost";
+
+$node->connect_ok(
+ "$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+$node->connect_fails(
+ $common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
+ "intermediate client certificate is missing",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+
+# test server-side CRL directory
+switch_server_cert($node, 'server-cn-only', undef, undef,
+ 'root+client-crldir');
+
+# revoked client cert
+$node->connect_fails(
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "certificate authorization fails with revoked client cert with server-side CRL directory",
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+
+# clean up
+foreach my $key (@keys)
+{
+ unlink("ssl/${key}_tmp.key");
+}
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
new file mode 100644
index 0000000..a508bbd
--- /dev/null
+++ b/src/test/ssl/t/002_scram.pl
@@ -0,0 +1,119 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test SCRAM authentication and TLS channel binding types
+
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+use File::Copy;
+
+use FindBin;
+use lib $FindBin::RealBin;
+
+use SSLServer;
+
+if ($ENV{with_ssl} ne 'openssl')
+{
+ plan skip_all => 'OpenSSL not supported by this build';
+}
+
+# This is the hostname used to connect to the server.
+my $SERVERHOSTADDR = '127.0.0.1';
+# This is the pattern to use in pg_hba.conf to match incoming connections.
+my $SERVERHOSTCIDR = '127.0.0.1/32';
+
+# Determine whether build supports tls-server-end-point.
+my $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+
+my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
+
+# Allocation of base connection string shared among multiple tests.
+my $common_connstr;
+
+# Set up the server.
+
+note "setting up data directory";
+my $node = get_new_node('primary');
+$node->init;
+
+# PGHOST is enforced here to set up the node, subsequent connections
+# will use a dedicated connection string.
+$ENV{PGHOST} = $node->host;
+$ENV{PGPORT} = $node->port;
+$node->start;
+
+# Configure server for SSL connections, with password handling.
+configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
+ "scram-sha-256", "pass", "scram-sha-256");
+switch_server_cert($node, 'server-cn-only');
+$ENV{PGPASSWORD} = "pass";
+$common_connstr =
+ "dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR host=localhost";
+
+# Default settings
+$node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "Basic SCRAM authentication with SSL");
+
+# Test channel_binding
+$node->connect_fails(
+ "$common_connstr user=ssltestuser channel_binding=invalid_value",
+ "SCRAM with SSL and channel_binding=invalid_value",
+ expected_stderr => qr/invalid channel_binding value: "invalid_value"/);
+$node->connect_ok("$common_connstr user=ssltestuser channel_binding=disable",
+ "SCRAM with SSL and channel_binding=disable");
+if ($supports_tls_server_end_point)
+{
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser channel_binding=require",
+ "SCRAM with SSL and channel_binding=require");
+}
+else
+{
+ $node->connect_fails(
+ "$common_connstr user=ssltestuser channel_binding=require",
+ "SCRAM with SSL and channel_binding=require",
+ expected_stderr =>
+ qr/channel binding is required, but server did not offer an authentication method that supports channel binding/
+ );
+}
+
+# Now test when the user has an MD5-encrypted password; should fail
+$node->connect_fails(
+ "$common_connstr user=md5testuser channel_binding=require",
+ "MD5 with SSL and channel_binding=require",
+ expected_stderr =>
+ qr/channel binding required but not supported by server's authentication request/
+);
+
+# Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
+# because channel binding is not performed. Note that ssl/client.key may
+# be used in a different test, so the name of this temporary client key
+# is chosen here to be unique.
+my $client_tmp_key = "ssl/client_scram_tmp.key";
+copy("ssl/client.key", $client_tmp_key);
+chmod 0600, $client_tmp_key;
+$node->connect_fails(
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR host=localhost dbname=certdb user=ssltestuser channel_binding=require",
+ "Cert authentication and channel_binding=require",
+ expected_stderr =>
+ qr/channel binding required, but server authenticated client without channel binding/
+);
+
+# Certificate verification at the connection level should still work fine.
+$node->connect_ok(
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR host=localhost dbname=verifydb user=ssltestuser",
+ "SCRAM with clientcert=verify-full",
+ log_like => [
+ qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
+ ]);
+
+# clean up
+unlink($client_tmp_key);
+
+done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSLServer.pm
new file mode 100644
index 0000000..804d008
--- /dev/null
+++ b/src/test/ssl/t/SSLServer.pm
@@ -0,0 +1,205 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# This module sets up a test server, for the SSL regression tests.
+#
+# The server is configured as follows:
+#
+# - SSL enabled, with the server certificate specified by argument to
+# switch_server_cert function.
+# - ssl/root+client_ca.crt as the CA root for validating client certs.
+# - reject non-SSL connections
+# - a database called trustdb that lets anyone in
+# - another database called certdb that uses certificate authentication, ie.
+# the client must present a valid certificate signed by the client CA
+#
+# The server is configured to only accept connections from localhost. If you
+# want to run the client from another host, you'll have to configure that
+# manually.
+#
+# Note: Someone running these test could have key or certificate files
+# in their ~/.postgresql/, which would interfere with the tests. The
+# way to override that is to specify sslcert=invalid and/or
+# sslrootcert=invalid if no actual certificate is used for a
+# particular test. libpq will ignore specifications that name
+# nonexisting files. (sslkey and sslcrl do not need to specified
+# explicitly because an invalid sslcert or sslrootcert, respectively,
+# causes those to be ignored.)
+
+package SSLServer;
+
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use File::Basename;
+use File::Copy;
+use Test::More;
+
+use Exporter 'import';
+our @EXPORT = qw(
+ configure_test_server_for_ssl
+ switch_server_cert
+);
+
+# Copy a set of files, taking into account wildcards
+sub copy_files
+{
+ my $orig = shift;
+ my $dest = shift;
+
+ my @orig_files = glob $orig;
+ foreach my $orig_file (@orig_files)
+ {
+ my $base_file = basename($orig_file);
+ copy($orig_file, "$dest/$base_file")
+ or die "Could not copy $orig_file to $dest";
+ }
+ return;
+}
+
+# serverhost: what to put in listen_addresses, e.g. '127.0.0.1'
+# servercidr: what to put in pg_hba.conf, e.g. '127.0.0.1/32'
+sub configure_test_server_for_ssl
+{
+ my ($node, $serverhost, $servercidr, $authmethod, $password,
+ $password_enc) = @_;
+
+ my $pgdata = $node->data_dir;
+
+ # Create test users and databases
+ $node->psql('postgres', "CREATE USER ssltestuser");
+ $node->psql('postgres', "CREATE USER md5testuser");
+ $node->psql('postgres', "CREATE USER anotheruser");
+ $node->psql('postgres', "CREATE USER yetanotheruser");
+ $node->psql('postgres', "CREATE DATABASE trustdb");
+ $node->psql('postgres', "CREATE DATABASE certdb");
+ $node->psql('postgres', "CREATE DATABASE certdb_dn");
+ $node->psql('postgres', "CREATE DATABASE certdb_dn_re");
+ $node->psql('postgres', "CREATE DATABASE certdb_cn");
+ $node->psql('postgres', "CREATE DATABASE verifydb");
+
+ # Update password of each user as needed.
+ if (defined($password))
+ {
+ $node->psql('postgres',
+ "SET password_encryption='$password_enc'; ALTER USER ssltestuser PASSWORD '$password';"
+ );
+ # A special user that always has an md5-encrypted password
+ $node->psql('postgres',
+ "SET password_encryption='md5'; ALTER USER md5testuser PASSWORD '$password';"
+ );
+ $node->psql('postgres',
+ "SET password_encryption='$password_enc'; ALTER USER anotheruser PASSWORD '$password';"
+ );
+ }
+
+ # enable logging etc.
+ open my $conf, '>>', "$pgdata/postgresql.conf";
+ print $conf "fsync=off\n";
+ print $conf "log_connections=on\n";
+ print $conf "log_hostname=on\n";
+ print $conf "listen_addresses='$serverhost'\n";
+ print $conf "log_statement=all\n";
+
+ # enable SSL and set up server key
+ print $conf "include 'sslconfig.conf'\n";
+
+ close $conf;
+
+ # ssl configuration will be placed here
+ open my $sslconf, '>', "$pgdata/sslconfig.conf";
+ close $sslconf;
+
+ # Copy all server certificates and keys, and client root cert, to the data dir
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+
+ # Stop and restart server to load new listen_addresses.
+ $node->restart;
+
+ # Change pg_hba after restart because hostssl requires ssl=on
+ configure_hba_for_ssl($node, $servercidr, $authmethod);
+
+ return;
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $crlfile = "root+client.crl";
+ my $crldir;
+ my $pgdata = $node->data_dir;
+
+ # defaults to use crl file
+ if (defined $_[3] || defined $_[4])
+ {
+ $crlfile = $_[3];
+ $crldir = $_[4];
+ }
+
+ open my $sslconf, '>', "$pgdata/sslconfig.conf";
+ print $sslconf "ssl=on\n";
+ print $sslconf "ssl_ca_file='$cafile.crt'\n";
+ print $sslconf "ssl_cert_file='$certfile.crt'\n";
+ print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
+ print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ close $sslconf;
+
+ $node->restart;
+ return;
+}
+
+sub configure_hba_for_ssl
+{
+ my ($node, $servercidr, $authmethod) = @_;
+ my $pgdata = $node->data_dir;
+
+ # Only accept SSL connections from $servercidr. Our tests don't depend on this
+ # but seems best to keep it as narrow as possible for security reasons.
+ #
+ # When connecting to certdb, also check the client certificate.
+ open my $hba, '>', "$pgdata/pg_hba.conf";
+ print $hba
+ "# TYPE DATABASE USER ADDRESS METHOD OPTIONS\n";
+ print $hba
+ "hostssl trustdb md5testuser $servercidr md5\n";
+ print $hba
+ "hostssl trustdb all $servercidr $authmethod\n";
+ print $hba
+ "hostssl verifydb ssltestuser $servercidr $authmethod clientcert=verify-full\n";
+ print $hba
+ "hostssl verifydb anotheruser $servercidr $authmethod clientcert=verify-full\n";
+ print $hba
+ "hostssl verifydb yetanotheruser $servercidr $authmethod clientcert=verify-ca\n";
+ print $hba
+ "hostssl certdb all $servercidr cert\n";
+ print $hba
+ "hostssl certdb_dn all $servercidr cert clientname=DN map=dn\n",
+ "hostssl certdb_dn_re all $servercidr cert clientname=DN map=dnre\n",
+ "hostssl certdb_cn all $servercidr cert clientname=CN map=cn\n";
+ close $hba;
+
+ # Also set the ident maps. Note: fields with commas must be quoted
+ open my $map, ">", "$pgdata/pg_ident.conf";
+ print $map
+ "# MAPNAME SYSTEM-USERNAME PG-USERNAME\n",
+ "dn \"CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG\" ssltestuser\n",
+ "dnre \"/^.*OU=Testing,.*\$\" ssltestuser\n",
+ "cn ssltestuser-dn ssltestuser\n";
+
+ return;
+}
+
+1;
diff --git a/src/test/subscription/.gitignore b/src/test/subscription/.gitignore
new file mode 100644
index 0000000..871e943
--- /dev/null
+++ b/src/test/subscription/.gitignore
@@ -0,0 +1,2 @@
+# Generated by test suite
+/tmp_check/
diff --git a/src/test/subscription/Makefile b/src/test/subscription/Makefile
new file mode 100644
index 0000000..28fd6f6
--- /dev/null
+++ b/src/test/subscription/Makefile
@@ -0,0 +1,27 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/subscription
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/subscription/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/subscription
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+EXTRA_INSTALL = contrib/hstore
+
+export with_icu
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
diff --git a/src/test/subscription/README b/src/test/subscription/README
new file mode 100644
index 0000000..fb5382e
--- /dev/null
+++ b/src/test/subscription/README
@@ -0,0 +1,23 @@
+src/test/subscription/README
+
+Regression tests for subscription/logical replication
+=====================================================
+
+This directory contains a test suite for subscription/logical replication.
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+
+Run
+ make check
+or
+ make installcheck
+You can use "make installcheck" if you previously did "make install"
+(including installing the hstore extension). In that case, the code
+in the installation tree is tested. With "make check", a temporary
+installation tree is built from the current sources and then tested.
+
+Either way, this test initializes, starts, and stops several test Postgres
+clusters.
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
new file mode 100644
index 0000000..7dd69ca
--- /dev/null
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -0,0 +1,539 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 32;
+
+# Initialize publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create some preexisting content on publisher
+$node_publisher->safe_psql(
+ 'postgres',
+ "CREATE FUNCTION public.pg_get_replica_identity_index(int)
+ RETURNS regclass LANGUAGE sql AS 'SELECT 1/0'"); # shall not call
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_notrep AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_ins AS SELECT generate_series(1,1002) AS a");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_full AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE tab_full2 (x text)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_full2 VALUES ('a'), ('b'), ('b')");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_rep (a int primary key)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_mixed (a int primary key, b text, c numeric)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_mixed (a, b, c) VALUES (1, 'foo', 1.1)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_include (a int, b text, CONSTRAINT covering PRIMARY KEY(a) INCLUDE(b))"
+);
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_full_pk (a int primary key, b text)");
+$node_publisher->safe_psql('postgres',
+ "ALTER TABLE tab_full_pk REPLICA IDENTITY FULL");
+# Let this table with REPLICA IDENTITY NOTHING, allowing only INSERT changes.
+$node_publisher->safe_psql('postgres', "CREATE TABLE tab_nothing (a int)");
+$node_publisher->safe_psql('postgres',
+ "ALTER TABLE tab_nothing REPLICA IDENTITY NOTHING");
+
+# Replicate the changes without replica identity index
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_no_replidentity_index(c1 int)");
+$node_publisher->safe_psql('postgres',
+ "CREATE INDEX idx_no_replidentity_index ON tab_no_replidentity_index(c1)"
+);
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_notrep (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_ins (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_full (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_full2 (x text)");
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab_rep (a int primary key)");
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab_full_pk (a int primary key, b text)");
+$node_subscriber->safe_psql('postgres',
+ "ALTER TABLE tab_full_pk REPLICA IDENTITY FULL");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_nothing (a int)");
+
+# different column count and order than on publisher
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab_mixed (d text default 'local', c numeric, b text, a int primary key)"
+);
+
+# replication of the table with included index
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab_include (a int, b text, CONSTRAINT covering PRIMARY KEY(a) INCLUDE(b))"
+);
+
+# replication of the table without replica identity index
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab_no_replidentity_index(c1 int)");
+$node_subscriber->safe_psql('postgres',
+ "CREATE INDEX idx_no_replidentity_index ON tab_no_replidentity_index(c1)"
+);
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres', "CREATE PUBLICATION tap_pub");
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_ins_only WITH (publish = insert)");
+$node_publisher->safe_psql('postgres',
+ "ALTER PUBLICATION tap_pub ADD TABLE tab_rep, tab_full, tab_full2, tab_mixed, tab_include, tab_nothing, tab_full_pk, tab_no_replidentity_index"
+);
+$node_publisher->safe_psql('postgres',
+ "ALTER PUBLICATION tap_pub_ins_only ADD TABLE tab_ins");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub, tap_pub_ins_only"
+);
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+my $result =
+ $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_notrep");
+is($result, qq(0), 'check non-replicated table is empty on subscriber');
+
+$result =
+ $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_ins");
+is($result, qq(1002), 'check initial data was copied to subscriber');
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_ins SELECT generate_series(1,50)");
+$node_publisher->safe_psql('postgres', "DELETE FROM tab_ins WHERE a > 20");
+$node_publisher->safe_psql('postgres', "UPDATE tab_ins SET a = -a");
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_rep SELECT generate_series(1,50)");
+$node_publisher->safe_psql('postgres', "DELETE FROM tab_rep WHERE a > 20");
+$node_publisher->safe_psql('postgres', "UPDATE tab_rep SET a = -a");
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_mixed VALUES (2, 'bar', 2.2)");
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_full_pk VALUES (1, 'foo'), (2, 'baz')");
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_nothing VALUES (generate_series(1,20))");
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_include SELECT generate_series(1,50)");
+$node_publisher->safe_psql('postgres',
+ "DELETE FROM tab_include WHERE a > 20");
+$node_publisher->safe_psql('postgres', "UPDATE tab_include SET a = -a");
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_no_replidentity_index VALUES(1)");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_ins");
+is($result, qq(1052|1|1002), 'check replicated inserts on subscriber');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_rep");
+is($result, qq(20|-20|-1), 'check replicated changes on subscriber');
+
+$result = $node_subscriber->safe_psql('postgres', "SELECT * FROM tab_mixed");
+is( $result, qq(local|1.1|foo|1
+local|2.2|bar|2), 'check replicated changes with different column order');
+
+$result =
+ $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_nothing");
+is($result, qq(20), 'check replicated changes with REPLICA IDENTITY NOTHING');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_include");
+is($result, qq(20|-20|-1),
+ 'check replicated changes with primary key index with included columns');
+
+is( $node_subscriber->safe_psql(
+ 'postgres', q(SELECT c1 FROM tab_no_replidentity_index)),
+ 1,
+ "value replicated to subscriber without replica identity index");
+
+# insert some duplicate rows
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_full SELECT generate_series(1,10)");
+
+# Test behaviour of ALTER PUBLICATION ... DROP TABLE
+#
+# When a publisher drops a table from publication, it should also stop sending
+# its changes to subscribers. We look at the subscriber whether it receives
+# the row that is inserted to the table in the publisher after it is dropped
+# from the publication.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_ins");
+is($result, qq(1052|1|1002),
+ 'check rows on subscriber before table drop from publication');
+
+# Drop the table from publication
+$node_publisher->safe_psql('postgres',
+ "ALTER PUBLICATION tap_pub_ins_only DROP TABLE tab_ins");
+
+# Insert a row in publisher, but publisher will not send this row to subscriber
+$node_publisher->safe_psql('postgres', "INSERT INTO tab_ins VALUES(8888)");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Subscriber will not receive the inserted row, after table is dropped from
+# publication, so row count should remain the same.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_ins");
+is($result, qq(1052|1|1002),
+ 'check rows on subscriber after table drop from publication');
+
+# Delete the inserted row in publisher
+$node_publisher->safe_psql('postgres', "DELETE FROM tab_ins WHERE a = 8888");
+
+# Add the table to publication again
+$node_publisher->safe_psql('postgres',
+ "ALTER PUBLICATION tap_pub_ins_only ADD TABLE tab_ins");
+
+# Refresh publication after table is added to publication
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub REFRESH PUBLICATION");
+
+# Test replication with multiple publications for a subscription such that the
+# operations are performed on the table from the first publication in the list.
+
+# Create tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE TABLE temp1 (a int)");
+$node_publisher->safe_psql('postgres', "CREATE TABLE temp2 (a int)");
+
+# Create tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE TABLE temp1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE temp2 (a int)");
+
+# Setup logical replication that will only be used for this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_temp1 FOR TABLE temp1 WITH (publish = insert)"
+);
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_temp2 FOR TABLE temp2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_temp1 CONNECTION '$publisher_connstr' PUBLICATION tap_pub_temp1, tap_pub_temp2"
+);
+
+$node_publisher->wait_for_catchup('tap_sub_temp1');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Subscriber table will have no rows initially
+$result =
+ $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM temp1");
+is($result, qq(0),
+ 'check initial rows on subscriber with multiple publications');
+
+# Insert a row into the table that's part of first publication in subscriber
+# list of publications.
+$node_publisher->safe_psql('postgres', "INSERT INTO temp1 VALUES (1)");
+
+$node_publisher->wait_for_catchup('tap_sub_temp1');
+
+# Subscriber should receive the inserted row
+$result =
+ $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM temp1");
+is($result, qq(1), 'check rows on subscriber with multiple publications');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_temp1");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_temp1");
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_temp2");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP TABLE temp1");
+$node_publisher->safe_psql('postgres', "DROP TABLE temp2");
+$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
+$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+
+# add REPLICA IDENTITY FULL so we can update
+$node_publisher->safe_psql('postgres',
+ "ALTER TABLE tab_full REPLICA IDENTITY FULL");
+$node_subscriber->safe_psql('postgres',
+ "ALTER TABLE tab_full REPLICA IDENTITY FULL");
+$node_publisher->safe_psql('postgres',
+ "ALTER TABLE tab_full2 REPLICA IDENTITY FULL");
+$node_subscriber->safe_psql('postgres',
+ "ALTER TABLE tab_full2 REPLICA IDENTITY FULL");
+$node_publisher->safe_psql('postgres',
+ "ALTER TABLE tab_ins REPLICA IDENTITY FULL");
+$node_subscriber->safe_psql('postgres',
+ "ALTER TABLE tab_ins REPLICA IDENTITY FULL");
+# tab_mixed can use DEFAULT, since it has a primary key
+
+# and do the updates
+$node_publisher->safe_psql('postgres', "UPDATE tab_full SET a = a * a");
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab_full2 SET x = 'bb' WHERE x = 'b'");
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab_mixed SET b = 'baz' WHERE a = 1");
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab_full_pk SET b = 'bar' WHERE a = 1");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_full");
+is($result, qq(20|1|100),
+ 'update works with REPLICA IDENTITY FULL and duplicate tuples');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT x FROM tab_full2 ORDER BY 1");
+is( $result, qq(a
+bb
+bb),
+ 'update works with REPLICA IDENTITY FULL and text datums');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT * FROM tab_mixed ORDER BY a");
+is( $result, qq(local|1.1|baz|1
+local|2.2|bar|2),
+ 'update works with different column order and subscriber local values');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT * FROM tab_full_pk ORDER BY a");
+is( $result, qq(1|bar
+2|baz),
+ 'update works with REPLICA IDENTITY FULL and a primary key');
+
+# Check that subscriber handles cases where update/delete target tuple
+# is missing. We have to look for the DEBUG1 log messages about that,
+# so temporarily bump up the log verbosity.
+$node_subscriber->append_conf('postgresql.conf', "log_min_messages = debug1");
+$node_subscriber->reload;
+
+$node_subscriber->safe_psql('postgres', "DELETE FROM tab_full_pk");
+
+# Note that the current location of the log file is not grabbed immediately
+# after reloading the configuration, but after sending one SQL command to
+# the node so as we are sure that the reloading has taken effect.
+my $log_location = -s $node_subscriber->logfile;
+
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab_full_pk SET b = 'quux' WHERE a = 1");
+$node_publisher->safe_psql('postgres', "DELETE FROM tab_full_pk WHERE a = 2");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+my $logfile = slurp_file($node_subscriber->logfile, $log_location);
+ok( $logfile =~
+ qr/logical replication did not find row to be updated in replication target relation "tab_full_pk"/,
+ 'update target row is missing');
+ok( $logfile =~
+ qr/logical replication did not find row to be deleted in replication target relation "tab_full_pk"/,
+ 'delete target row is missing');
+
+$node_subscriber->append_conf('postgresql.conf',
+ "log_min_messages = warning");
+$node_subscriber->reload;
+
+# check behavior with toasted values
+
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab_mixed SET b = repeat('xyzzy', 100000) WHERE a = 2");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT a, length(b), c, d FROM tab_mixed ORDER BY a");
+is( $result, qq(1|3|1.1|local
+2|500000|2.2|local),
+ 'update transmits large column value');
+
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab_mixed SET c = 3.3 WHERE a = 2");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT a, length(b), c, d FROM tab_mixed ORDER BY a");
+is( $result, qq(1|3|1.1|local
+2|500000|3.3|local),
+ 'update with non-transmitted large column value');
+
+# check behavior with dropped columns
+
+# this update should get transmitted before the column goes away
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab_mixed SET b = 'bar', c = 2.2 WHERE a = 2");
+
+$node_publisher->safe_psql('postgres', "ALTER TABLE tab_mixed DROP COLUMN b");
+
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab_mixed SET c = 11.11 WHERE a = 1");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT * FROM tab_mixed ORDER BY a");
+is( $result, qq(local|11.11|baz|1
+local|2.2|bar|2),
+ 'update works with dropped publisher column');
+
+$node_subscriber->safe_psql('postgres',
+ "ALTER TABLE tab_mixed DROP COLUMN d");
+
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab_mixed SET c = 22.22 WHERE a = 2");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT * FROM tab_mixed ORDER BY a");
+is( $result, qq(11.11|baz|1
+22.22|bar|2),
+ 'update works with dropped subscriber column');
+
+# check that change of connection string and/or publication list causes
+# restart of subscription workers. We check the state along with
+# application_name to ensure that the walsender is (re)started.
+#
+# Not all of these are registered as tests as we need to poll for a change
+# but the test suite will fail none the less when something goes wrong.
+my $oldpid = $node_publisher->safe_psql('postgres',
+ "SELECT pid FROM pg_stat_replication WHERE application_name = 'tap_sub' AND state = 'streaming';"
+);
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr sslmode=disable'"
+);
+$node_publisher->poll_query_until('postgres',
+ "SELECT pid != $oldpid FROM pg_stat_replication WHERE application_name = 'tap_sub' AND state = 'streaming';"
+) or die "Timed out while waiting for apply to restart after changing CONNECTION";
+
+$oldpid = $node_publisher->safe_psql('postgres',
+ "SELECT pid FROM pg_stat_replication WHERE application_name = 'tap_sub' AND state = 'streaming';"
+);
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub SET PUBLICATION tap_pub_ins_only WITH (copy_data = false)"
+);
+$node_publisher->poll_query_until('postgres',
+ "SELECT pid != $oldpid FROM pg_stat_replication WHERE application_name = 'tap_sub' AND state = 'streaming';"
+) or die "Timed out while waiting for apply to restart after changing PUBLICATION";
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_ins SELECT generate_series(1001,1100)");
+$node_publisher->safe_psql('postgres', "DELETE FROM tab_rep");
+
+# Restart the publisher and check the state of the subscriber which
+# should be in a streaming state after catching up.
+$node_publisher->stop('fast');
+$node_publisher->start;
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_ins");
+is($result, qq(1152|1|1100),
+ 'check replicated inserts after subscription publication change');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_rep");
+is($result, qq(20|-20|-1),
+ 'check changes skipped after subscription publication change');
+
+# check alter publication (relcache invalidation etc)
+$node_publisher->safe_psql('postgres',
+ "ALTER PUBLICATION tap_pub_ins_only SET (publish = 'insert, delete')");
+$node_publisher->safe_psql('postgres',
+ "ALTER PUBLICATION tap_pub_ins_only ADD TABLE tab_full");
+$node_publisher->safe_psql('postgres', "DELETE FROM tab_ins WHERE a > 0");
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub REFRESH PUBLICATION WITH (copy_data = false)"
+);
+$node_publisher->safe_psql('postgres', "INSERT INTO tab_full VALUES(0)");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# note that data are different on provider and subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_ins");
+is($result, qq(1052|1|1002),
+ 'check replicated deletes after alter publication');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_full");
+is($result, qq(21|0|100), 'check replicated insert after alter publication');
+
+# check restart on rename
+$oldpid = $node_publisher->safe_psql('postgres',
+ "SELECT pid FROM pg_stat_replication WHERE application_name = 'tap_sub' AND state = 'streaming';"
+);
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub RENAME TO tap_sub_renamed");
+$node_publisher->poll_query_until('postgres',
+ "SELECT pid != $oldpid FROM pg_stat_replication WHERE application_name = 'tap_sub_renamed' AND state = 'streaming';"
+) or die "Timed out while waiting for apply to restart after renaming SUBSCRIPTION";
+
+# check all the cleanup
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_renamed");
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription");
+is($result, qq(0), 'check subscription was dropped on subscriber');
+
+$result = $node_publisher->safe_psql('postgres',
+ "SELECT count(*) FROM pg_replication_slots");
+is($result, qq(0), 'check replication slot was dropped on publisher');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel");
+is($result, qq(0),
+ 'check subscription relation status was dropped on subscriber');
+
+$result = $node_publisher->safe_psql('postgres',
+ "SELECT count(*) FROM pg_replication_slots");
+is($result, qq(0), 'check replication slot was dropped on publisher');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_replication_origin");
+is($result, qq(0), 'check replication origin was dropped on subscriber');
+
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
+
+# CREATE PUBLICATION while wal_level=minimal should succeed, with a WARNING
+$node_publisher->append_conf(
+ 'postgresql.conf', qq(
+wal_level=minimal
+max_wal_senders=0
+));
+$node_publisher->start;
+($result, my $retout, my $reterr) = $node_publisher->psql(
+ 'postgres', qq{
+BEGIN;
+CREATE TABLE skip_wal();
+CREATE PUBLICATION tap_pub2 FOR TABLE skip_wal;
+ROLLBACK;
+});
+ok( $reterr =~
+ m/WARNING: wal_level is insufficient to publish logical changes/,
+ 'CREATE PUBLICATION while wal_level=minimal');
diff --git a/src/test/subscription/t/002_types.pl b/src/test/subscription/t/002_types.pl
new file mode 100644
index 0000000..f915fad
--- /dev/null
+++ b/src/test/subscription/t/002_types.pl
@@ -0,0 +1,568 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# This tests that more complex datatypes are replicated correctly
+# by logical replication
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# Initialize publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create some preexisting content on publisher
+my $ddl = qq(
+ CREATE EXTENSION hstore WITH SCHEMA public;
+ CREATE TABLE public.tst_one_array (
+ a INTEGER PRIMARY KEY,
+ b INTEGER[]
+ );
+ CREATE TABLE public.tst_arrays (
+ a INTEGER[] PRIMARY KEY,
+ b TEXT[],
+ c FLOAT[],
+ d INTERVAL[]
+ );
+
+ CREATE TYPE public.tst_enum_t AS ENUM ('a', 'b', 'c', 'd', 'e');
+ CREATE TABLE public.tst_one_enum (
+ a INTEGER PRIMARY KEY,
+ b public.tst_enum_t
+ );
+ CREATE TABLE public.tst_enums (
+ a public.tst_enum_t PRIMARY KEY,
+ b public.tst_enum_t[]
+ );
+
+ CREATE TYPE public.tst_comp_basic_t AS (a FLOAT, b TEXT, c INTEGER);
+ CREATE TYPE public.tst_comp_enum_t AS (a FLOAT, b public.tst_enum_t, c INTEGER);
+ CREATE TYPE public.tst_comp_enum_array_t AS (a FLOAT, b public.tst_enum_t[], c INTEGER);
+ CREATE TABLE public.tst_one_comp (
+ a INTEGER PRIMARY KEY,
+ b public.tst_comp_basic_t
+ );
+ CREATE TABLE public.tst_comps (
+ a public.tst_comp_basic_t PRIMARY KEY,
+ b public.tst_comp_basic_t[]
+ );
+ CREATE TABLE public.tst_comp_enum (
+ a INTEGER PRIMARY KEY,
+ b public.tst_comp_enum_t
+ );
+ CREATE TABLE public.tst_comp_enum_array (
+ a public.tst_comp_enum_t PRIMARY KEY,
+ b public.tst_comp_enum_t[]
+ );
+ CREATE TABLE public.tst_comp_one_enum_array (
+ a INTEGER PRIMARY KEY,
+ b public.tst_comp_enum_array_t
+ );
+ CREATE TABLE public.tst_comp_enum_what (
+ a public.tst_comp_enum_array_t PRIMARY KEY,
+ b public.tst_comp_enum_array_t[]
+ );
+
+ CREATE TYPE public.tst_comp_mix_t AS (
+ a public.tst_comp_basic_t,
+ b public.tst_comp_basic_t[],
+ c public.tst_enum_t,
+ d public.tst_enum_t[]
+ );
+ CREATE TABLE public.tst_comp_mix_array (
+ a public.tst_comp_mix_t PRIMARY KEY,
+ b public.tst_comp_mix_t[]
+ );
+ CREATE TABLE public.tst_range (
+ a INTEGER PRIMARY KEY,
+ b int4range
+ );
+ CREATE TABLE public.tst_range_array (
+ a INTEGER PRIMARY KEY,
+ b TSTZRANGE,
+ c int8range[]
+ );
+ CREATE TABLE public.tst_hstore (
+ a INTEGER PRIMARY KEY,
+ b public.hstore
+ );
+
+ SET check_function_bodies=off;
+ CREATE FUNCTION public.monot_incr(int) RETURNS bool LANGUAGE sql
+ AS ' select \$1 > max(a) from public.tst_dom_constr; ';
+ CREATE DOMAIN monot_int AS int CHECK (monot_incr(VALUE));
+ CREATE TABLE public.tst_dom_constr (a monot_int););
+
+# Setup structure on both nodes
+$node_publisher->safe_psql('postgres', $ddl);
+$node_subscriber->safe_psql('postgres', $ddl);
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub FOR ALL TABLES");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub WITH (slot_name = tap_sub_slot)"
+);
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Wait for initial sync to finish as well
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('s', 'r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Insert initial test data
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ -- test_tbl_one_array_col
+ INSERT INTO tst_one_array (a, b) VALUES
+ (1, '{1, 2, 3}'),
+ (2, '{2, 3, 1}'),
+ (3, '{3, 2, 1}'),
+ (4, '{4, 3, 2}'),
+ (5, '{5, NULL, 3}');
+
+ -- test_tbl_arrays
+ INSERT INTO tst_arrays (a, b, c, d) VALUES
+ ('{1, 2, 3}', '{"a", "b", "c"}', '{1.1, 2.2, 3.3}', '{"1 day", "2 days", "3 days"}'),
+ ('{2, 3, 1}', '{"b", "c", "a"}', '{2.2, 3.3, 1.1}', '{"2 minutes", "3 minutes", "1 minute"}'),
+ ('{3, 1, 2}', '{"c", "a", "b"}', '{3.3, 1.1, 2.2}', '{"3 years", "1 year", "2 years"}'),
+ ('{4, 1, 2}', '{"d", "a", "b"}', '{4.4, 1.1, 2.2}', '{"4 years", "1 year", "2 years"}'),
+ ('{5, NULL, NULL}', '{"e", NULL, "b"}', '{5.5, 1.1, NULL}', '{"5 years", NULL, NULL}');
+
+ -- test_tbl_single_enum
+ INSERT INTO tst_one_enum (a, b) VALUES
+ (1, 'a'),
+ (2, 'b'),
+ (3, 'c'),
+ (4, 'd'),
+ (5, NULL);
+
+ -- test_tbl_enums
+ INSERT INTO tst_enums (a, b) VALUES
+ ('a', '{b, c}'),
+ ('b', '{c, a}'),
+ ('c', '{b, a}'),
+ ('d', '{c, b}'),
+ ('e', '{d, NULL}');
+
+ -- test_tbl_single_composites
+ INSERT INTO tst_one_comp (a, b) VALUES
+ (1, ROW(1.0, 'a', 1)),
+ (2, ROW(2.0, 'b', 2)),
+ (3, ROW(3.0, 'c', 3)),
+ (4, ROW(4.0, 'd', 4)),
+ (5, ROW(NULL, NULL, 5));
+
+ -- test_tbl_composites
+ INSERT INTO tst_comps (a, b) VALUES
+ (ROW(1.0, 'a', 1), ARRAY[ROW(1, 'a', 1)::tst_comp_basic_t]),
+ (ROW(2.0, 'b', 2), ARRAY[ROW(2, 'b', 2)::tst_comp_basic_t]),
+ (ROW(3.0, 'c', 3), ARRAY[ROW(3, 'c', 3)::tst_comp_basic_t]),
+ (ROW(4.0, 'd', 4), ARRAY[ROW(4, 'd', 3)::tst_comp_basic_t]),
+ (ROW(5.0, 'e', NULL), ARRAY[NULL, ROW(5, NULL, 5)::tst_comp_basic_t]);
+
+ -- test_tbl_composite_with_enums
+ INSERT INTO tst_comp_enum (a, b) VALUES
+ (1, ROW(1.0, 'a', 1)),
+ (2, ROW(2.0, 'b', 2)),
+ (3, ROW(3.0, 'c', 3)),
+ (4, ROW(4.0, 'd', 4)),
+ (5, ROW(NULL, 'e', NULL));
+
+ -- test_tbl_composite_with_enums_array
+ INSERT INTO tst_comp_enum_array (a, b) VALUES
+ (ROW(1.0, 'a', 1), ARRAY[ROW(1, 'a', 1)::tst_comp_enum_t]),
+ (ROW(2.0, 'b', 2), ARRAY[ROW(2, 'b', 2)::tst_comp_enum_t]),
+ (ROW(3.0, 'c', 3), ARRAY[ROW(3, 'c', 3)::tst_comp_enum_t]),
+ (ROW(4.0, 'd', 3), ARRAY[ROW(3, 'd', 3)::tst_comp_enum_t]),
+ (ROW(5.0, 'e', 3), ARRAY[ROW(3, 'e', 3)::tst_comp_enum_t, NULL]);
+
+ -- test_tbl_composite_with_single_enums_array_in_composite
+ INSERT INTO tst_comp_one_enum_array (a, b) VALUES
+ (1, ROW(1.0, '{a, b, c}', 1)),
+ (2, ROW(2.0, '{a, b, c}', 2)),
+ (3, ROW(3.0, '{a, b, c}', 3)),
+ (4, ROW(4.0, '{c, b, d}', 4)),
+ (5, ROW(5.0, '{NULL, e, NULL}', 5));
+
+ -- test_tbl_composite_with_enums_array_in_composite
+ INSERT INTO tst_comp_enum_what (a, b) VALUES
+ (ROW(1.0, '{a, b, c}', 1), ARRAY[ROW(1, '{a, b, c}', 1)::tst_comp_enum_array_t]),
+ (ROW(2.0, '{b, c, a}', 2), ARRAY[ROW(2, '{b, c, a}', 1)::tst_comp_enum_array_t]),
+ (ROW(3.0, '{c, a, b}', 1), ARRAY[ROW(3, '{c, a, b}', 1)::tst_comp_enum_array_t]),
+ (ROW(4.0, '{c, b, d}', 4), ARRAY[ROW(4, '{c, b, d}', 4)::tst_comp_enum_array_t]),
+ (ROW(5.0, '{c, NULL, b}', NULL), ARRAY[ROW(5, '{c, e, b}', 1)::tst_comp_enum_array_t]);
+
+ -- test_tbl_mixed_composites
+ INSERT INTO tst_comp_mix_array (a, b) VALUES
+ (ROW(
+ ROW(1,'a',1),
+ ARRAY[ROW(1,'a',1)::tst_comp_basic_t, ROW(2,'b',2)::tst_comp_basic_t],
+ 'a',
+ '{a,b,NULL,c}'),
+ ARRAY[
+ ROW(
+ ROW(1,'a',1),
+ ARRAY[
+ ROW(1,'a',1)::tst_comp_basic_t,
+ ROW(2,'b',2)::tst_comp_basic_t,
+ NULL
+ ],
+ 'a',
+ '{a,b,c}'
+ )::tst_comp_mix_t
+ ]
+ );
+
+ -- test_tbl_range
+ INSERT INTO tst_range (a, b) VALUES
+ (1, '[1, 10]'),
+ (2, '[2, 20]'),
+ (3, '[3, 30]'),
+ (4, '[4, 40]'),
+ (5, '[5, 50]');
+
+ -- test_tbl_range_array
+ INSERT INTO tst_range_array (a, b, c) VALUES
+ (1, tstzrange('Mon Aug 04 00:00:00 2014 CEST'::timestamptz, 'infinity'), '{"[1,2]", "[10,20]"}'),
+ (2, tstzrange('Sat Aug 02 00:00:00 2014 CEST'::timestamptz, 'Mon Aug 04 00:00:00 2014 CEST'::timestamptz), '{"[2,3]", "[20,30]"}'),
+ (3, tstzrange('Fri Aug 01 00:00:00 2014 CEST'::timestamptz, 'Mon Aug 04 00:00:00 2014 CEST'::timestamptz), '{"[3,4]"}'),
+ (4, tstzrange('Thu Jul 31 00:00:00 2014 CEST'::timestamptz, 'Mon Aug 04 00:00:00 2014 CEST'::timestamptz), '{"[4,5]", NULL, "[40,50]"}'),
+ (5, NULL, NULL);
+
+ -- tst_hstore
+ INSERT INTO tst_hstore (a, b) VALUES
+ (1, '"a"=>"1"'),
+ (2, '"zzz"=>"foo"'),
+ (3, '"123"=>"321"'),
+ (4, '"yellow horse"=>"moaned"');
+
+ -- tst_dom_constr
+ INSERT INTO tst_dom_constr VALUES (10);
+));
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Check the data on subscriber
+my $result = $node_subscriber->safe_psql(
+ 'postgres', qq(
+ SET timezone = '+2';
+ SELECT a, b FROM tst_one_array ORDER BY a;
+ SELECT a, b, c, d FROM tst_arrays ORDER BY a;
+ SELECT a, b FROM tst_one_enum ORDER BY a;
+ SELECT a, b FROM tst_enums ORDER BY a;
+ SELECT a, b FROM tst_one_comp ORDER BY a;
+ SELECT a, b FROM tst_comps ORDER BY a;
+ SELECT a, b FROM tst_comp_enum ORDER BY a;
+ SELECT a, b FROM tst_comp_enum_array ORDER BY a;
+ SELECT a, b FROM tst_comp_one_enum_array ORDER BY a;
+ SELECT a, b FROM tst_comp_enum_what ORDER BY a;
+ SELECT a, b FROM tst_comp_mix_array ORDER BY a;
+ SELECT a, b FROM tst_range ORDER BY a;
+ SELECT a, b, c FROM tst_range_array ORDER BY a;
+ SELECT a, b FROM tst_hstore ORDER BY a;
+));
+
+is( $result, '1|{1,2,3}
+2|{2,3,1}
+3|{3,2,1}
+4|{4,3,2}
+5|{5,NULL,3}
+{1,2,3}|{a,b,c}|{1.1,2.2,3.3}|{"1 day","2 days","3 days"}
+{2,3,1}|{b,c,a}|{2.2,3.3,1.1}|{00:02:00,00:03:00,00:01:00}
+{3,1,2}|{c,a,b}|{3.3,1.1,2.2}|{"3 years","1 year","2 years"}
+{4,1,2}|{d,a,b}|{4.4,1.1,2.2}|{"4 years","1 year","2 years"}
+{5,NULL,NULL}|{e,NULL,b}|{5.5,1.1,NULL}|{"5 years",NULL,NULL}
+1|a
+2|b
+3|c
+4|d
+5|
+a|{b,c}
+b|{c,a}
+c|{b,a}
+d|{c,b}
+e|{d,NULL}
+1|(1,a,1)
+2|(2,b,2)
+3|(3,c,3)
+4|(4,d,4)
+5|(,,5)
+(1,a,1)|{"(1,a,1)"}
+(2,b,2)|{"(2,b,2)"}
+(3,c,3)|{"(3,c,3)"}
+(4,d,4)|{"(4,d,3)"}
+(5,e,)|{NULL,"(5,,5)"}
+1|(1,a,1)
+2|(2,b,2)
+3|(3,c,3)
+4|(4,d,4)
+5|(,e,)
+(1,a,1)|{"(1,a,1)"}
+(2,b,2)|{"(2,b,2)"}
+(3,c,3)|{"(3,c,3)"}
+(4,d,3)|{"(3,d,3)"}
+(5,e,3)|{"(3,e,3)",NULL}
+1|(1,"{a,b,c}",1)
+2|(2,"{a,b,c}",2)
+3|(3,"{a,b,c}",3)
+4|(4,"{c,b,d}",4)
+5|(5,"{NULL,e,NULL}",5)
+(1,"{a,b,c}",1)|{"(1,\"{a,b,c}\",1)"}
+(2,"{b,c,a}",2)|{"(2,\"{b,c,a}\",1)"}
+(3,"{c,a,b}",1)|{"(3,\"{c,a,b}\",1)"}
+(4,"{c,b,d}",4)|{"(4,\"{c,b,d}\",4)"}
+(5,"{c,NULL,b}",)|{"(5,\"{c,e,b}\",1)"}
+("(1,a,1)","{""(1,a,1)"",""(2,b,2)""}",a,"{a,b,NULL,c}")|{"(\"(1,a,1)\",\"{\"\"(1,a,1)\"\",\"\"(2,b,2)\"\",NULL}\",a,\"{a,b,c}\")"}
+1|[1,11)
+2|[2,21)
+3|[3,31)
+4|[4,41)
+5|[5,51)
+1|["2014-08-04 00:00:00+02",infinity)|{"[1,3)","[10,21)"}
+2|["2014-08-02 00:00:00+02","2014-08-04 00:00:00+02")|{"[2,4)","[20,31)"}
+3|["2014-08-01 00:00:00+02","2014-08-04 00:00:00+02")|{"[3,5)"}
+4|["2014-07-31 00:00:00+02","2014-08-04 00:00:00+02")|{"[4,6)",NULL,"[40,51)"}
+5||
+1|"a"=>"1"
+2|"zzz"=>"foo"
+3|"123"=>"321"
+4|"yellow horse"=>"moaned"',
+ 'check replicated inserts on subscriber');
+
+# Run batch of updates
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ UPDATE tst_one_array SET b = '{4, 5, 6}' WHERE a = 1;
+ UPDATE tst_one_array SET b = '{4, 5, 6, 1}' WHERE a > 3;
+ UPDATE tst_arrays SET b = '{"1a", "2b", "3c"}', c = '{1.0, 2.0, 3.0}', d = '{"1 day 1 second", "2 days 2 seconds", "3 days 3 second"}' WHERE a = '{1, 2, 3}';
+ UPDATE tst_arrays SET b = '{"c", "d", "e"}', c = '{3.0, 4.0, 5.0}', d = '{"3 day 1 second", "4 days 2 seconds", "5 days 3 second"}' WHERE a[1] > 3;
+ UPDATE tst_one_enum SET b = 'c' WHERE a = 1;
+ UPDATE tst_one_enum SET b = NULL WHERE a > 3;
+ UPDATE tst_enums SET b = '{e, NULL}' WHERE a = 'a';
+ UPDATE tst_enums SET b = '{e, d}' WHERE a > 'c';
+ UPDATE tst_one_comp SET b = ROW(1.0, 'A', 1) WHERE a = 1;
+ UPDATE tst_one_comp SET b = ROW(NULL, 'x', -1) WHERE a > 3;
+ UPDATE tst_comps SET b = ARRAY[ROW(9, 'x', -1)::tst_comp_basic_t] WHERE (a).a = 1.0;
+ UPDATE tst_comps SET b = ARRAY[NULL, ROW(9, 'x', NULL)::tst_comp_basic_t] WHERE (a).a > 3.9;
+ UPDATE tst_comp_enum SET b = ROW(1.0, NULL, NULL) WHERE a = 1;
+ UPDATE tst_comp_enum SET b = ROW(4.0, 'd', 44) WHERE a > 3;
+ UPDATE tst_comp_enum_array SET b = ARRAY[NULL, ROW(3, 'd', 3)::tst_comp_enum_t] WHERE a = ROW(1.0, 'a', 1)::tst_comp_enum_t;
+ UPDATE tst_comp_enum_array SET b = ARRAY[ROW(1, 'a', 1)::tst_comp_enum_t, ROW(2, 'b', 2)::tst_comp_enum_t] WHERE (a).a > 3;
+ UPDATE tst_comp_one_enum_array SET b = ROW(1.0, '{a, e, c}', NULL) WHERE a = 1;
+ UPDATE tst_comp_one_enum_array SET b = ROW(4.0, '{c, b, d}', 4) WHERE a > 3;
+ UPDATE tst_comp_enum_what SET b = ARRAY[NULL, ROW(1, '{a, b, c}', 1)::tst_comp_enum_array_t, ROW(NULL, '{a, e, c}', 2)::tst_comp_enum_array_t] WHERE (a).a = 1;
+ UPDATE tst_comp_enum_what SET b = ARRAY[ROW(5, '{a, b, c}', 5)::tst_comp_enum_array_t] WHERE (a).a > 3;
+ UPDATE tst_comp_mix_array SET b[2] = NULL WHERE ((a).a).a = 1;
+ UPDATE tst_range SET b = '[100, 1000]' WHERE a = 1;
+ UPDATE tst_range SET b = '(1, 90)' WHERE a > 3;
+ UPDATE tst_range_array SET c = '{"[100, 1000]"}' WHERE a = 1;
+ UPDATE tst_range_array SET b = tstzrange('Mon Aug 04 00:00:00 2014 CEST'::timestamptz, 'infinity'), c = '{NULL, "[11,9999999]"}' WHERE a > 3;
+ UPDATE tst_hstore SET b = '"updated"=>"value"' WHERE a < 3;
+ UPDATE tst_hstore SET b = '"also"=>"updated"' WHERE a = 3;
+));
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Check the data on subscriber
+$result = $node_subscriber->safe_psql(
+ 'postgres', qq(
+ SET timezone = '+2';
+ SELECT a, b FROM tst_one_array ORDER BY a;
+ SELECT a, b, c, d FROM tst_arrays ORDER BY a;
+ SELECT a, b FROM tst_one_enum ORDER BY a;
+ SELECT a, b FROM tst_enums ORDER BY a;
+ SELECT a, b FROM tst_one_comp ORDER BY a;
+ SELECT a, b FROM tst_comps ORDER BY a;
+ SELECT a, b FROM tst_comp_enum ORDER BY a;
+ SELECT a, b FROM tst_comp_enum_array ORDER BY a;
+ SELECT a, b FROM tst_comp_one_enum_array ORDER BY a;
+ SELECT a, b FROM tst_comp_enum_what ORDER BY a;
+ SELECT a, b FROM tst_comp_mix_array ORDER BY a;
+ SELECT a, b FROM tst_range ORDER BY a;
+ SELECT a, b, c FROM tst_range_array ORDER BY a;
+ SELECT a, b FROM tst_hstore ORDER BY a;
+));
+
+is( $result, '1|{4,5,6}
+2|{2,3,1}
+3|{3,2,1}
+4|{4,5,6,1}
+5|{4,5,6,1}
+{1,2,3}|{1a,2b,3c}|{1,2,3}|{"1 day 00:00:01","2 days 00:00:02","3 days 00:00:03"}
+{2,3,1}|{b,c,a}|{2.2,3.3,1.1}|{00:02:00,00:03:00,00:01:00}
+{3,1,2}|{c,a,b}|{3.3,1.1,2.2}|{"3 years","1 year","2 years"}
+{4,1,2}|{c,d,e}|{3,4,5}|{"3 days 00:00:01","4 days 00:00:02","5 days 00:00:03"}
+{5,NULL,NULL}|{c,d,e}|{3,4,5}|{"3 days 00:00:01","4 days 00:00:02","5 days 00:00:03"}
+1|c
+2|b
+3|c
+4|
+5|
+a|{e,NULL}
+b|{c,a}
+c|{b,a}
+d|{e,d}
+e|{e,d}
+1|(1,A,1)
+2|(2,b,2)
+3|(3,c,3)
+4|(,x,-1)
+5|(,x,-1)
+(1,a,1)|{"(9,x,-1)"}
+(2,b,2)|{"(2,b,2)"}
+(3,c,3)|{"(3,c,3)"}
+(4,d,4)|{NULL,"(9,x,)"}
+(5,e,)|{NULL,"(9,x,)"}
+1|(1,,)
+2|(2,b,2)
+3|(3,c,3)
+4|(4,d,44)
+5|(4,d,44)
+(1,a,1)|{NULL,"(3,d,3)"}
+(2,b,2)|{"(2,b,2)"}
+(3,c,3)|{"(3,c,3)"}
+(4,d,3)|{"(1,a,1)","(2,b,2)"}
+(5,e,3)|{"(1,a,1)","(2,b,2)"}
+1|(1,"{a,e,c}",)
+2|(2,"{a,b,c}",2)
+3|(3,"{a,b,c}",3)
+4|(4,"{c,b,d}",4)
+5|(4,"{c,b,d}",4)
+(1,"{a,b,c}",1)|{NULL,"(1,\"{a,b,c}\",1)","(,\"{a,e,c}\",2)"}
+(2,"{b,c,a}",2)|{"(2,\"{b,c,a}\",1)"}
+(3,"{c,a,b}",1)|{"(3,\"{c,a,b}\",1)"}
+(4,"{c,b,d}",4)|{"(5,\"{a,b,c}\",5)"}
+(5,"{c,NULL,b}",)|{"(5,\"{a,b,c}\",5)"}
+("(1,a,1)","{""(1,a,1)"",""(2,b,2)""}",a,"{a,b,NULL,c}")|{"(\"(1,a,1)\",\"{\"\"(1,a,1)\"\",\"\"(2,b,2)\"\",NULL}\",a,\"{a,b,c}\")",NULL}
+1|[100,1001)
+2|[2,21)
+3|[3,31)
+4|[2,90)
+5|[2,90)
+1|["2014-08-04 00:00:00+02",infinity)|{"[100,1001)"}
+2|["2014-08-02 00:00:00+02","2014-08-04 00:00:00+02")|{"[2,4)","[20,31)"}
+3|["2014-08-01 00:00:00+02","2014-08-04 00:00:00+02")|{"[3,5)"}
+4|["2014-08-04 00:00:00+02",infinity)|{NULL,"[11,10000000)"}
+5|["2014-08-04 00:00:00+02",infinity)|{NULL,"[11,10000000)"}
+1|"updated"=>"value"
+2|"updated"=>"value"
+3|"also"=>"updated"
+4|"yellow horse"=>"moaned"',
+ 'check replicated updates on subscriber');
+
+# Run batch of deletes
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ DELETE FROM tst_one_array WHERE a = 1;
+ DELETE FROM tst_one_array WHERE b = '{2, 3, 1}';
+ DELETE FROM tst_arrays WHERE a = '{1, 2, 3}';
+ DELETE FROM tst_arrays WHERE a[1] = 2;
+ DELETE FROM tst_one_enum WHERE a = 1;
+ DELETE FROM tst_one_enum WHERE b = 'b';
+ DELETE FROM tst_enums WHERE a = 'a';
+ DELETE FROM tst_enums WHERE b[1] = 'b';
+ DELETE FROM tst_one_comp WHERE a = 1;
+ DELETE FROM tst_one_comp WHERE (b).a = 2.0;
+ DELETE FROM tst_comps WHERE (a).b = 'a';
+ DELETE FROM tst_comps WHERE ROW(3, 'c', 3)::tst_comp_basic_t = ANY(b);
+ DELETE FROM tst_comp_enum WHERE a = 1;
+ DELETE FROM tst_comp_enum WHERE (b).a = 2.0;
+ DELETE FROM tst_comp_enum_array WHERE a = ROW(1.0, 'a', 1)::tst_comp_enum_t;
+ DELETE FROM tst_comp_enum_array WHERE ROW(3, 'c', 3)::tst_comp_enum_t = ANY(b);
+ DELETE FROM tst_comp_one_enum_array WHERE a = 1;
+ DELETE FROM tst_comp_one_enum_array WHERE 'a' = ANY((b).b);
+ DELETE FROM tst_comp_enum_what WHERE (a).a = 1;
+ DELETE FROM tst_comp_enum_what WHERE (b[1]).b = '{c, a, b}';
+ DELETE FROM tst_comp_mix_array WHERE ((a).a).a = 1;
+ DELETE FROM tst_range WHERE a = 1;
+ DELETE FROM tst_range WHERE '[10,20]' && b;
+ DELETE FROM tst_range_array WHERE a = 1;
+ DELETE FROM tst_range_array WHERE tstzrange('Mon Aug 04 00:00:00 2014 CEST'::timestamptz, 'Mon Aug 05 00:00:00 2014 CEST'::timestamptz) && b;
+ DELETE FROM tst_hstore WHERE a = 1;
+));
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Check the data on subscriber
+$result = $node_subscriber->safe_psql(
+ 'postgres', qq(
+ SET timezone = '+2';
+ SELECT a, b FROM tst_one_array ORDER BY a;
+ SELECT a, b, c, d FROM tst_arrays ORDER BY a;
+ SELECT a, b FROM tst_one_enum ORDER BY a;
+ SELECT a, b FROM tst_enums ORDER BY a;
+ SELECT a, b FROM tst_one_comp ORDER BY a;
+ SELECT a, b FROM tst_comps ORDER BY a;
+ SELECT a, b FROM tst_comp_enum ORDER BY a;
+ SELECT a, b FROM tst_comp_enum_array ORDER BY a;
+ SELECT a, b FROM tst_comp_one_enum_array ORDER BY a;
+ SELECT a, b FROM tst_comp_enum_what ORDER BY a;
+ SELECT a, b FROM tst_comp_mix_array ORDER BY a;
+ SELECT a, b FROM tst_range ORDER BY a;
+ SELECT a, b, c FROM tst_range_array ORDER BY a;
+ SELECT a, b FROM tst_hstore ORDER BY a;
+));
+
+is( $result, '3|{3,2,1}
+4|{4,5,6,1}
+5|{4,5,6,1}
+{3,1,2}|{c,a,b}|{3.3,1.1,2.2}|{"3 years","1 year","2 years"}
+{4,1,2}|{c,d,e}|{3,4,5}|{"3 days 00:00:01","4 days 00:00:02","5 days 00:00:03"}
+{5,NULL,NULL}|{c,d,e}|{3,4,5}|{"3 days 00:00:01","4 days 00:00:02","5 days 00:00:03"}
+3|c
+4|
+5|
+b|{c,a}
+d|{e,d}
+e|{e,d}
+3|(3,c,3)
+4|(,x,-1)
+5|(,x,-1)
+(2,b,2)|{"(2,b,2)"}
+(4,d,4)|{NULL,"(9,x,)"}
+(5,e,)|{NULL,"(9,x,)"}
+3|(3,c,3)
+4|(4,d,44)
+5|(4,d,44)
+(2,b,2)|{"(2,b,2)"}
+(4,d,3)|{"(1,a,1)","(2,b,2)"}
+(5,e,3)|{"(1,a,1)","(2,b,2)"}
+4|(4,"{c,b,d}",4)
+5|(4,"{c,b,d}",4)
+(2,"{b,c,a}",2)|{"(2,\"{b,c,a}\",1)"}
+(4,"{c,b,d}",4)|{"(5,\"{a,b,c}\",5)"}
+(5,"{c,NULL,b}",)|{"(5,\"{a,b,c}\",5)"}
+2|["2014-08-02 00:00:00+02","2014-08-04 00:00:00+02")|{"[2,4)","[20,31)"}
+3|["2014-08-01 00:00:00+02","2014-08-04 00:00:00+02")|{"[3,5)"}
+2|"updated"=>"value"
+3|"also"=>"updated"
+4|"yellow horse"=>"moaned"',
+ 'check replicated deletes on subscriber');
+
+# Test a domain with a constraint backed by a SQL-language function,
+# which needs an active snapshot in order to operate.
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tst_dom_constr VALUES (11)");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT sum(a) FROM tst_dom_constr");
+is($result, '21', 'sql-function constraint on domain');
+
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
diff --git a/src/test/subscription/t/003_constraints.pl b/src/test/subscription/t/003_constraints.pl
new file mode 100644
index 0000000..1182a12
--- /dev/null
+++ b/src/test/subscription/t/003_constraints.pl
@@ -0,0 +1,139 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# This test checks that constraints work on subscriber
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 6;
+
+# Initialize publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Setup structure on publisher
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_fk (bid int PRIMARY KEY);");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_fk_ref (id int PRIMARY KEY, junk text, bid int REFERENCES tab_fk (bid));"
+);
+
+# Setup structure on subscriber; column order intentionally different
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab_fk (bid int PRIMARY KEY);");
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab_fk_ref (id int PRIMARY KEY, bid int REFERENCES tab_fk (bid), junk text);"
+);
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub FOR ALL TABLES;");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub WITH (copy_data = false)"
+);
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_fk (bid) VALUES (1);");
+# "junk" value is meant to be large enough to force out-of-line storage
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_fk_ref (id, bid, junk) VALUES (1, 1, repeat(pi()::text,20000));"
+);
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Check data on subscriber
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(bid), max(bid) FROM tab_fk;");
+is($result, qq(1|1|1), 'check replicated tab_fk inserts on subscriber');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref;");
+is($result, qq(1|1|1), 'check replicated tab_fk_ref inserts on subscriber');
+
+# Drop the fk on publisher
+$node_publisher->safe_psql('postgres', "DROP TABLE tab_fk CASCADE;");
+
+# Insert data
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_fk_ref (id, bid) VALUES (2, 2);");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# FK is not enforced on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref;");
+is($result, qq(2|1|2), 'check FK ignored on subscriber');
+
+# Add replica trigger
+$node_subscriber->safe_psql(
+ 'postgres', qq{
+CREATE FUNCTION filter_basic_dml_fn() RETURNS TRIGGER AS \$\$
+BEGIN
+ IF (TG_OP = 'INSERT') THEN
+ IF (NEW.id < 10) THEN
+ RETURN NEW;
+ ELSE
+ RETURN NULL;
+ END IF;
+ ELSIF (TG_OP = 'UPDATE') THEN
+ RETURN NULL;
+ ELSE
+ RAISE WARNING 'Unknown action';
+ RETURN NULL;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+CREATE TRIGGER filter_basic_dml_trg
+ BEFORE INSERT OR UPDATE OF bid ON tab_fk_ref
+ FOR EACH ROW EXECUTE PROCEDURE filter_basic_dml_fn();
+ALTER TABLE tab_fk_ref ENABLE REPLICA TRIGGER filter_basic_dml_trg;
+});
+
+# Insert data
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_fk_ref (id, bid) VALUES (10, 10);");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# The trigger should cause the insert to be skipped on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref;");
+is($result, qq(2|1|2), 'check replica insert trigger applied on subscriber');
+
+# Update data
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab_fk_ref SET bid = 2 WHERE bid = 1;");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# The trigger should cause the update to be skipped on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref;");
+is($result, qq(2|1|2),
+ 'check replica update column trigger applied on subscriber');
+
+# Update on a column not specified in the trigger, but it will trigger
+# anyway because logical replication ships all columns in an update.
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab_fk_ref SET id = 6 WHERE id = 1;");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(id), max(id) FROM tab_fk_ref;");
+is($result, qq(2|1|2),
+ 'check column trigger applied even on update for other column');
+
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
diff --git a/src/test/subscription/t/004_sync.pl b/src/test/subscription/t/004_sync.pl
new file mode 100644
index 0000000..b3c91af
--- /dev/null
+++ b/src/test/subscription/t/004_sync.pl
@@ -0,0 +1,180 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Tests for logical replication table syncing
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 8;
+
+# Initialize publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->append_conf('postgresql.conf',
+ "wal_retrieve_retry_interval = 1ms");
+$node_subscriber->start;
+
+# Create some preexisting content on publisher
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_rep (a int primary key)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_rep SELECT generate_series(1,10)");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab_rep (a int primary key)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub FOR ALL TABLES");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub"
+);
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+my $result =
+ $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_rep");
+is($result, qq(10), 'initial data synced for first sub');
+
+# drop subscription so that there is unreplicated data
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub");
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_rep SELECT generate_series(11,20)");
+
+# recreate the subscription, it will try to do initial copy
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub"
+);
+
+# but it will be stuck on data copy as it will fail on constraint
+my $started_query = "SELECT srsubstate = 'd' FROM pg_subscription_rel;";
+$node_subscriber->poll_query_until('postgres', $started_query)
+ or die "Timed out while waiting for subscriber to start sync";
+
+# remove the conflicting data
+$node_subscriber->safe_psql('postgres', "DELETE FROM tab_rep;");
+
+# wait for sync to finish this time
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# check that all data is synced
+$result =
+ $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_rep");
+is($result, qq(20), 'initial data synced for second sub');
+
+# now check another subscription for the same node pair
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub2 CONNECTION '$publisher_connstr' PUBLICATION tap_pub WITH (copy_data = false)"
+);
+
+# wait for it to start
+$node_subscriber->poll_query_until('postgres',
+ "SELECT pid IS NOT NULL FROM pg_stat_subscription WHERE subname = 'tap_sub2' AND relid IS NULL"
+) or die "Timed out while waiting for subscriber to start";
+
+# and drop both subscriptions
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub");
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub2");
+
+# check subscriptions are removed
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription");
+is($result, qq(0), 'second and third sub are dropped');
+
+# remove the conflicting data
+$node_subscriber->safe_psql('postgres', "DELETE FROM tab_rep;");
+
+# recreate the subscription again
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub"
+);
+
+# and wait for data sync to finish again
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# check that all data is synced
+$result =
+ $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_rep");
+is($result, qq(20), 'initial data synced for fourth sub');
+
+# add new table on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_rep_next (a int)");
+
+# setup structure with existing data on publisher
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_rep_next (a) AS SELECT generate_series(1,10)");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM tab_rep_next");
+is($result, qq(0), 'no data for table added after subscription initialized');
+
+# ask for data sync
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub REFRESH PUBLICATION");
+
+# wait for sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM tab_rep_next");
+is($result, qq(10),
+ 'data for table added after subscription initialized are now synced');
+
+# Add some data
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_rep_next SELECT generate_series(1,10)");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM tab_rep_next");
+is($result, qq(20),
+ 'changes for table added after subscription initialized replicated');
+
+# clean up
+$node_publisher->safe_psql('postgres', "DROP TABLE tab_rep_next");
+$node_subscriber->safe_psql('postgres', "DROP TABLE tab_rep_next");
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub");
+
+# Table tap_rep already has the same records on both publisher and subscriber
+# at this time. Recreate the subscription which will do the initial copy of
+# the table again and fails due to unique constraint violation.
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub"
+);
+
+$result = $node_subscriber->poll_query_until('postgres', $started_query)
+ or die "Timed out while waiting for subscriber to start sync";
+
+# DROP SUBSCRIPTION must clean up slots on the publisher side when the
+# subscriber is stuck on data copy for constraint violation.
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub");
+
+$result = $node_publisher->safe_psql('postgres',
+ "SELECT count(*) FROM pg_replication_slots");
+is($result, qq(0),
+ 'DROP SUBSCRIPTION during error can clean up the slots on the publisher');
+
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
diff --git a/src/test/subscription/t/005_encoding.pl b/src/test/subscription/t/005_encoding.pl
new file mode 100644
index 0000000..a3f56a4
--- /dev/null
+++ b/src/test/subscription/t/005_encoding.pl
@@ -0,0 +1,55 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test replication between databases with different encodings
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 1;
+
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(
+ allows_streaming => 'logical',
+ extra => [ '--locale=C', '--encoding=UTF8' ]);
+$node_publisher->start;
+
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(
+ allows_streaming => 'logical',
+ extra => [ '--locale=C', '--encoding=LATIN1' ]);
+$node_subscriber->start;
+
+my $ddl = "CREATE TABLE test1 (a int, b text);";
+$node_publisher->safe_psql('postgres', $ddl);
+$node_subscriber->safe_psql('postgres', $ddl);
+
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION mypub FOR ALL TABLES;");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION mysub CONNECTION '$publisher_connstr' PUBLICATION mypub;"
+);
+
+$node_publisher->wait_for_catchup('mysub');
+
+# Wait for initial sync to finish as well
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('s', 'r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres',
+ q{INSERT INTO test1 VALUES (1, E'Mot\xc3\xb6rhead')}); # hand-rolled UTF-8
+
+$node_publisher->wait_for_catchup('mysub');
+
+is( $node_subscriber->safe_psql(
+ 'postgres', q{SELECT a FROM test1 WHERE b = E'Mot\xf6rhead'}
+ ), # LATIN1
+ qq(1),
+ 'data replicated to subscriber');
+
+$node_subscriber->stop;
+$node_publisher->stop;
diff --git a/src/test/subscription/t/006_rewrite.pl b/src/test/subscription/t/006_rewrite.pl
new file mode 100644
index 0000000..37e05a4
--- /dev/null
+++ b/src/test/subscription/t/006_rewrite.pl
@@ -0,0 +1,68 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test logical replication behavior with heap rewrites
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 2;
+
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+my $ddl = "CREATE TABLE test1 (a int, b text);";
+$node_publisher->safe_psql('postgres', $ddl);
+$node_subscriber->safe_psql('postgres', $ddl);
+
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION mypub FOR ALL TABLES;");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION mysub CONNECTION '$publisher_connstr' PUBLICATION mypub;"
+);
+
+$node_publisher->wait_for_catchup('mysub');
+
+# Wait for initial sync to finish as well
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('s', 'r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres',
+ q{INSERT INTO test1 (a, b) VALUES (1, 'one'), (2, 'two');});
+
+$node_publisher->wait_for_catchup('mysub');
+
+is( $node_subscriber->safe_psql('postgres', q{SELECT a, b FROM test1}),
+ qq(1|one
+2|two),
+ 'initial data replicated to subscriber');
+
+# DDL that causes a heap rewrite
+my $ddl2 = "ALTER TABLE test1 ADD c int NOT NULL DEFAULT 0;";
+$node_subscriber->safe_psql('postgres', $ddl2);
+$node_publisher->safe_psql('postgres', $ddl2);
+
+$node_publisher->wait_for_catchup('mysub');
+
+$node_publisher->safe_psql('postgres',
+ q{INSERT INTO test1 (a, b, c) VALUES (3, 'three', 33);});
+
+$node_publisher->wait_for_catchup('mysub');
+
+is( $node_subscriber->safe_psql('postgres', q{SELECT a, b, c FROM test1}),
+ qq(1|one|0
+2|two|0
+3|three|33),
+ 'data replicated to subscriber');
+
+$node_subscriber->stop;
+$node_publisher->stop;
diff --git a/src/test/subscription/t/007_ddl.pl b/src/test/subscription/t/007_ddl.pl
new file mode 100644
index 0000000..dd10d5c
--- /dev/null
+++ b/src/test/subscription/t/007_ddl.pl
@@ -0,0 +1,45 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test some logical replication DDL behavior
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 1;
+
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+my $ddl = "CREATE TABLE test1 (a int, b text);";
+$node_publisher->safe_psql('postgres', $ddl);
+$node_subscriber->safe_psql('postgres', $ddl);
+
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION mypub FOR ALL TABLES;");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION mysub CONNECTION '$publisher_connstr' PUBLICATION mypub;"
+);
+
+$node_publisher->wait_for_catchup('mysub');
+
+$node_subscriber->safe_psql(
+ 'postgres', q{
+BEGIN;
+ALTER SUBSCRIPTION mysub DISABLE;
+ALTER SUBSCRIPTION mysub SET (slot_name = NONE);
+DROP SUBSCRIPTION mysub;
+COMMIT;
+});
+
+pass "subscription disable and drop in same transaction did not hang";
+
+$node_subscriber->stop;
+$node_publisher->stop;
diff --git a/src/test/subscription/t/008_diff_schema.pl b/src/test/subscription/t/008_diff_schema.pl
new file mode 100644
index 0000000..a04a798
--- /dev/null
+++ b/src/test/subscription/t/008_diff_schema.pl
@@ -0,0 +1,128 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test behavior with different schema on subscriber
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 5;
+
+# Create publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create some preexisting content on publisher
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE test_tab (a int primary key, b varchar)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE test_tab (a int primary key, b text, c timestamptz DEFAULT now(), d bigint DEFAULT 999, e int GENERATED BY DEFAULT AS IDENTITY)"
+);
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub FOR ALL TABLES");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub"
+);
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+my $result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c), count(d = 999) FROM test_tab");
+is($result, qq(2|2|2), 'check initial data was copied to subscriber');
+
+# Update the rows on the publisher and check the additional columns on
+# subscriber didn't change
+$node_publisher->safe_psql('postgres', "UPDATE test_tab SET b = md5(b)");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c), count(d = 999), count(e) FROM test_tab");
+is($result, qq(2|2|2|2),
+ 'check extra columns contain local defaults after copy');
+
+# Change the local values of the extra columns on the subscriber,
+# update publisher, and check that subscriber retains the expected
+# values
+$node_subscriber->safe_psql('postgres',
+ "UPDATE test_tab SET c = 'epoch'::timestamptz + 987654321 * interval '1s'"
+);
+$node_publisher->safe_psql('postgres',
+ "UPDATE test_tab SET b = md5(a::text)");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(extract(epoch from c) = 987654321), count(d = 999) FROM test_tab"
+);
+is($result, qq(2|2|2), 'check extra columns contain locally changed data');
+
+# Another insert
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO test_tab VALUES (3, 'baz')");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c), count(d = 999), count(e) FROM test_tab");
+is($result, qq(3|3|3|3),
+ 'check extra columns contain local defaults after apply');
+
+
+# Check a bug about adding a replica identity column on the subscriber
+# that was not yet mapped to a column on the publisher. This would
+# result in errors on the subscriber and replication thus not
+# progressing.
+# (https://www.postgresql.org/message-id/flat/a9139c29-7ddd-973b-aa7f-71fed9c38d75%40minerva.info)
+
+$node_publisher->safe_psql('postgres', "CREATE TABLE test_tab2 (a int)");
+
+$node_subscriber->safe_psql('postgres', "CREATE TABLE test_tab2 (a int)");
+
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub REFRESH PUBLICATION");
+
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Add replica identity column. (The serial is not necessary, but it's
+# a convenient way to get a default on the new column so that rows
+# from the publisher that don't have the column yet can be inserted.)
+$node_subscriber->safe_psql('postgres',
+ "ALTER TABLE test_tab2 ADD COLUMN b serial PRIMARY KEY");
+
+$node_publisher->safe_psql('postgres', "INSERT INTO test_tab2 VALUES (1)");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+is( $node_subscriber->safe_psql(
+ 'postgres', "SELECT count(*), min(a), max(a) FROM test_tab2"),
+ qq(1|1|1),
+ 'check replicated inserts on subscriber');
+
+
+$node_subscriber->stop;
+$node_publisher->stop;
diff --git a/src/test/subscription/t/009_matviews.pl b/src/test/subscription/t/009_matviews.pl
new file mode 100644
index 0000000..92c7d18
--- /dev/null
+++ b/src/test/subscription/t/009_matviews.pl
@@ -0,0 +1,52 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test materialized views behavior
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 1;
+
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION mypub FOR ALL TABLES;");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION mysub CONNECTION '$publisher_connstr' PUBLICATION mypub;"
+);
+
+$node_publisher->safe_psql('postgres',
+ q{CREATE TABLE test1 (a int PRIMARY KEY, b text)});
+$node_publisher->safe_psql('postgres',
+ q{INSERT INTO test1 (a, b) VALUES (1, 'one'), (2, 'two');});
+
+$node_subscriber->safe_psql('postgres',
+ q{CREATE TABLE test1 (a int PRIMARY KEY, b text);});
+
+$node_publisher->wait_for_catchup('mysub');
+
+# Materialized views are not supported by logical replication, but
+# logical decoding does produce change information for them, so we
+# need to make sure they are properly ignored. (bug #15044)
+
+# create a MV with some data
+$node_publisher->safe_psql('postgres',
+ q{CREATE MATERIALIZED VIEW testmv1 AS SELECT * FROM test1;});
+$node_publisher->wait_for_catchup('mysub');
+
+# There is no equivalent relation on the subscriber, but MV data is
+# not replicated, so this does not hang.
+
+pass "materialized view data not replicated";
+
+$node_subscriber->stop;
+$node_publisher->stop;
diff --git a/src/test/subscription/t/010_truncate.pl b/src/test/subscription/t/010_truncate.pl
new file mode 100644
index 0000000..5617469
--- /dev/null
+++ b/src/test/subscription/t/010_truncate.pl
@@ -0,0 +1,241 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test TRUNCATE
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# setup
+
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->append_conf('postgresql.conf',
+ qq(max_logical_replication_workers = 6));
+$node_subscriber->start;
+
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab1 (a int PRIMARY KEY)");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab1 (a int PRIMARY KEY)");
+
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab2 (a int PRIMARY KEY)");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab2 (a int PRIMARY KEY)");
+
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab3 (a int PRIMARY KEY)");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab3 (a int PRIMARY KEY)");
+
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab4 (x int PRIMARY KEY, y int REFERENCES tab3)");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab4 (x int PRIMARY KEY, y int REFERENCES tab3)");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE SEQUENCE seq1 OWNED BY tab1.a");
+$node_subscriber->safe_psql('postgres', "ALTER SEQUENCE seq1 START 101");
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION pub1 FOR TABLE tab1");
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION pub2 FOR TABLE tab2 WITH (publish = insert)");
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION pub3 FOR TABLE tab3, tab4");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr' PUBLICATION pub1"
+);
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION sub2 CONNECTION '$publisher_connstr' PUBLICATION pub2"
+);
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION sub3 CONNECTION '$publisher_connstr' PUBLICATION pub3"
+);
+
+# Wait for initial sync of all subscriptions
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# insert data to truncate
+
+$node_subscriber->safe_psql('postgres',
+ "INSERT INTO tab1 VALUES (1), (2), (3)");
+
+$node_publisher->wait_for_catchup('sub1');
+
+# truncate and check
+
+$node_publisher->safe_psql('postgres', "TRUNCATE tab1");
+
+$node_publisher->wait_for_catchup('sub1');
+
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab1");
+is($result, qq(0||), 'truncate replicated');
+
+$result = $node_subscriber->safe_psql('postgres', "SELECT nextval('seq1')");
+is($result, qq(1), 'sequence not restarted');
+
+# truncate with restart identity
+
+$node_publisher->safe_psql('postgres', "TRUNCATE tab1 RESTART IDENTITY");
+
+$node_publisher->wait_for_catchup('sub1');
+
+$result = $node_subscriber->safe_psql('postgres', "SELECT nextval('seq1')");
+is($result, qq(101), 'truncate restarted identities');
+
+# test publication that does not replicate truncate
+
+$node_subscriber->safe_psql('postgres',
+ "INSERT INTO tab2 VALUES (1), (2), (3)");
+
+$node_publisher->safe_psql('postgres', "TRUNCATE tab2");
+
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab2");
+is($result, qq(3|1|3), 'truncate not replicated');
+
+$node_publisher->safe_psql('postgres',
+ "ALTER PUBLICATION pub2 SET (publish = 'insert, truncate')");
+
+$node_publisher->safe_psql('postgres', "TRUNCATE tab2");
+
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab2");
+is($result, qq(0||), 'truncate replicated after publication change');
+
+# test multiple tables connected by foreign keys
+
+$node_subscriber->safe_psql('postgres',
+ "INSERT INTO tab3 VALUES (1), (2), (3)");
+$node_subscriber->safe_psql('postgres',
+ "INSERT INTO tab4 VALUES (11, 1), (111, 1), (22, 2)");
+
+$node_publisher->safe_psql('postgres', "TRUNCATE tab3, tab4");
+
+$node_publisher->wait_for_catchup('sub3');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab3");
+is($result, qq(0||), 'truncate of multiple tables replicated');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(x), max(x) FROM tab4");
+is($result, qq(0||), 'truncate of multiple tables replicated');
+
+# test truncate of multiple tables, some of which are not published
+
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION sub2");
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION pub2");
+
+$node_subscriber->safe_psql('postgres',
+ "INSERT INTO tab1 VALUES (1), (2), (3)");
+$node_subscriber->safe_psql('postgres',
+ "INSERT INTO tab2 VALUES (1), (2), (3)");
+
+$node_publisher->safe_psql('postgres', "TRUNCATE tab1, tab2");
+
+$node_publisher->wait_for_catchup('sub1');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab1");
+is($result, qq(0||), 'truncate of multiple tables some not published');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab2");
+is($result, qq(3|1|3), 'truncate of multiple tables some not published');
+
+# Test that truncate works for synchronous logical replication
+
+$node_publisher->safe_psql('postgres',
+ "ALTER SYSTEM SET synchronous_standby_names TO 'sub1'");
+$node_publisher->safe_psql('postgres', "SELECT pg_reload_conf()");
+
+# insert data to truncate
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab1 VALUES (1), (2), (3)");
+
+$node_publisher->wait_for_catchup('sub1');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab1");
+is($result, qq(3|1|3), 'check synchronous logical replication');
+
+$node_publisher->safe_psql('postgres', "TRUNCATE tab1");
+
+$node_publisher->wait_for_catchup('sub1');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab1");
+is($result, qq(0||),
+ 'truncate replicated in synchronous logical replication');
+
+$node_publisher->safe_psql('postgres',
+ "ALTER SYSTEM RESET synchronous_standby_names");
+$node_publisher->safe_psql('postgres', "SELECT pg_reload_conf()");
+
+# test that truncate works for logical replication when there are multiple
+# subscriptions for a single table
+
+$node_publisher->safe_psql('postgres', "CREATE TABLE tab5 (a int)");
+
+$node_subscriber->safe_psql('postgres', "CREATE TABLE tab5 (a int)");
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION pub5 FOR TABLE tab5");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION sub5_1 CONNECTION '$publisher_connstr' PUBLICATION pub5"
+);
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION sub5_2 CONNECTION '$publisher_connstr' PUBLICATION pub5"
+);
+
+# wait for initial data sync
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# insert data to truncate
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab5 VALUES (1), (2), (3)");
+
+$node_publisher->wait_for_catchup('sub5_1');
+$node_publisher->wait_for_catchup('sub5_2');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab5");
+is($result, qq(6|1|3), 'insert replicated for multiple subscriptions');
+
+$node_publisher->safe_psql('postgres', "TRUNCATE tab5");
+
+$node_publisher->wait_for_catchup('sub5_1');
+$node_publisher->wait_for_catchup('sub5_2');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab5");
+is($result, qq(0||), 'truncate replicated for multiple subscriptions');
+
+# check deadlocks
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT deadlocks FROM pg_stat_database WHERE datname='postgres'");
+is($result, qq(0), 'no deadlocks detected');
diff --git a/src/test/subscription/t/011_generated.pl b/src/test/subscription/t/011_generated.pl
new file mode 100644
index 0000000..29108cb
--- /dev/null
+++ b/src/test/subscription/t/011_generated.pl
@@ -0,0 +1,66 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test generated columns
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 2;
+
+# setup
+
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED)"
+);
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 22) STORED)"
+);
+
+# data for initial sync
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab1 (a) VALUES (1), (2), (3)");
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION pub1 FOR ALL TABLES");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr' PUBLICATION pub1"
+);
+
+# Wait for initial sync of all subscriptions
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+my $result = $node_subscriber->safe_psql('postgres', "SELECT a, b FROM tab1");
+is( $result, qq(1|22
+2|44
+3|66), 'generated columns initial sync');
+
+# data to replicate
+
+$node_publisher->safe_psql('postgres', "INSERT INTO tab1 VALUES (4), (5)");
+
+$node_publisher->safe_psql('postgres', "UPDATE tab1 SET a = 6 WHERE a = 5");
+
+$node_publisher->wait_for_catchup('sub1');
+
+$result = $node_subscriber->safe_psql('postgres', "SELECT a, b FROM tab1");
+is( $result, qq(1|22
+2|44
+3|66
+4|88
+6|132), 'generated columns replicated');
diff --git a/src/test/subscription/t/012_collation.pl b/src/test/subscription/t/012_collation.pl
new file mode 100644
index 0000000..8137a16
--- /dev/null
+++ b/src/test/subscription/t/012_collation.pl
@@ -0,0 +1,110 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test collations, in particular nondeterministic ones
+# (only works with ICU)
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+if ($ENV{with_icu} eq 'yes')
+{
+ plan tests => 2;
+}
+else
+{
+ plan skip_all => 'ICU not supported by this build';
+}
+
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(
+ allows_streaming => 'logical',
+ extra => [ '--locale=C', '--encoding=UTF8' ]);
+$node_publisher->start;
+
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(
+ allows_streaming => 'logical',
+ extra => [ '--locale=C', '--encoding=UTF8' ]);
+$node_subscriber->start;
+
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+
+# Test plan: Create a table with a nondeterministic collation in the
+# primary key column. Pre-insert rows on the publisher and subscriber
+# that are collation-wise equal but byte-wise different. (We use a
+# string in different normal forms for that.) Set up publisher and
+# subscriber. Update the row on the publisher, but don't change the
+# primary key column. The subscriber needs to find the row to be
+# updated using the nondeterministic collation semantics. We need to
+# test for both a replica identity index and for replica identity
+# full, since those have different code paths internally.
+
+$node_subscriber->safe_psql('postgres',
+ q{CREATE COLLATION ctest_nondet (provider = icu, locale = 'und', deterministic = false)}
+);
+
+# table with replica identity index
+
+$node_publisher->safe_psql('postgres',
+ q{CREATE TABLE tab1 (a text PRIMARY KEY, b text)});
+
+$node_publisher->safe_psql('postgres',
+ q{INSERT INTO tab1 VALUES (U&'\00E4bc', 'foo')});
+
+$node_subscriber->safe_psql('postgres',
+ q{CREATE TABLE tab1 (a text COLLATE ctest_nondet PRIMARY KEY, b text)});
+
+$node_subscriber->safe_psql('postgres',
+ q{INSERT INTO tab1 VALUES (U&'\0061\0308bc', 'foo')});
+
+# table with replica identity full
+
+$node_publisher->safe_psql('postgres', q{CREATE TABLE tab2 (a text, b text)});
+$node_publisher->safe_psql('postgres',
+ q{ALTER TABLE tab2 REPLICA IDENTITY FULL});
+
+$node_publisher->safe_psql('postgres',
+ q{INSERT INTO tab2 VALUES (U&'\00E4bc', 'foo')});
+
+$node_subscriber->safe_psql('postgres',
+ q{CREATE TABLE tab2 (a text COLLATE ctest_nondet, b text)});
+$node_subscriber->safe_psql('postgres',
+ q{ALTER TABLE tab2 REPLICA IDENTITY FULL});
+
+$node_subscriber->safe_psql('postgres',
+ q{INSERT INTO tab2 VALUES (U&'\0061\0308bc', 'foo')});
+
+# set up publication, subscription
+
+$node_publisher->safe_psql('postgres',
+ q{CREATE PUBLICATION pub1 FOR ALL TABLES});
+
+$node_subscriber->safe_psql('postgres',
+ qq{CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr' PUBLICATION pub1 WITH (copy_data = false)}
+);
+
+$node_publisher->wait_for_catchup('sub1');
+
+# test with replica identity index
+
+$node_publisher->safe_psql('postgres',
+ q{UPDATE tab1 SET b = 'bar' WHERE b = 'foo'});
+
+$node_publisher->wait_for_catchup('sub1');
+
+is($node_subscriber->safe_psql('postgres', q{SELECT b FROM tab1}),
+ qq(bar), 'update with primary key with nondeterministic collation');
+
+# test with replica identity full
+
+$node_publisher->safe_psql('postgres',
+ q{UPDATE tab2 SET b = 'bar' WHERE b = 'foo'});
+
+$node_publisher->wait_for_catchup('sub1');
+
+is($node_subscriber->safe_psql('postgres', q{SELECT b FROM tab2}),
+ qq(bar),
+ 'update with replica identity full with nondeterministic collation');
diff --git a/src/test/subscription/t/013_partition.pl b/src/test/subscription/t/013_partition.pl
new file mode 100644
index 0000000..dfe2cb6
--- /dev/null
+++ b/src/test/subscription/t/013_partition.pl
@@ -0,0 +1,872 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test logical replication with partitioned tables
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 71;
+
+# setup
+
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+my $node_subscriber1 = get_new_node('subscriber1');
+$node_subscriber1->init(allows_streaming => 'logical');
+$node_subscriber1->start;
+
+my $node_subscriber2 = get_new_node('subscriber2');
+$node_subscriber2->init(allows_streaming => 'logical');
+$node_subscriber2->start;
+
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+
+# publisher
+$node_publisher->safe_psql('postgres', "CREATE PUBLICATION pub1");
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION pub_all FOR ALL TABLES");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab1 (a int PRIMARY KEY, b text) PARTITION BY LIST (a)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab1_1 (b text, a int NOT NULL)");
+$node_publisher->safe_psql('postgres',
+ "ALTER TABLE tab1 ATTACH PARTITION tab1_1 FOR VALUES IN (1, 2, 3)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab1_2 PARTITION OF tab1 FOR VALUES IN (4, 5, 6)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab1_def PARTITION OF tab1 DEFAULT");
+$node_publisher->safe_psql('postgres',
+ "ALTER PUBLICATION pub1 ADD TABLE tab1, tab1_1");
+
+# subscriber1
+#
+# This is partitioned differently from the publisher. tab1_2 is
+# subpartitioned. This tests the tuple routing code on the
+# subscriber.
+$node_subscriber1->safe_psql('postgres',
+ "CREATE TABLE tab1 (c text, a int PRIMARY KEY, b text) PARTITION BY LIST (a)"
+);
+$node_subscriber1->safe_psql('postgres',
+ "CREATE TABLE tab1_1 (b text, c text DEFAULT 'sub1_tab1', a int NOT NULL)"
+);
+$node_subscriber1->safe_psql('postgres',
+ "ALTER TABLE tab1 ATTACH PARTITION tab1_1 FOR VALUES IN (1, 2, 3)");
+$node_subscriber1->safe_psql('postgres',
+ "CREATE TABLE tab1_2 PARTITION OF tab1 (c DEFAULT 'sub1_tab1') FOR VALUES IN (4, 5, 6) PARTITION BY LIST (a)"
+);
+$node_subscriber1->safe_psql('postgres',
+ "CREATE TABLE tab1_2_1 (c text, b text, a int NOT NULL)");
+$node_subscriber1->safe_psql('postgres',
+ "ALTER TABLE tab1_2 ATTACH PARTITION tab1_2_1 FOR VALUES IN (5)");
+$node_subscriber1->safe_psql('postgres',
+ "CREATE TABLE tab1_2_2 PARTITION OF tab1_2 FOR VALUES IN (4, 6)");
+$node_subscriber1->safe_psql('postgres',
+ "CREATE TABLE tab1_def PARTITION OF tab1 (c DEFAULT 'sub1_tab1') DEFAULT"
+);
+$node_subscriber1->safe_psql('postgres',
+ "CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr' PUBLICATION pub1"
+);
+
+# Add set of AFTER replica triggers for testing that they are fired
+# correctly. This uses a table that records details of all trigger
+# activities. Triggers are marked as enabled for a subset of the
+# partition tree.
+$node_subscriber1->safe_psql(
+ 'postgres', qq{
+CREATE TABLE sub1_trigger_activity (tgtab text, tgop text,
+ tgwhen text, tglevel text, olda int, newa int);
+CREATE FUNCTION sub1_trigger_activity_func() RETURNS TRIGGER AS \$\$
+BEGIN
+ IF (TG_OP = 'INSERT') THEN
+ INSERT INTO public.sub1_trigger_activity
+ SELECT TG_RELNAME, TG_OP, TG_WHEN, TG_LEVEL, NULL, NEW.a;
+ ELSIF (TG_OP = 'UPDATE') THEN
+ INSERT INTO public.sub1_trigger_activity
+ SELECT TG_RELNAME, TG_OP, TG_WHEN, TG_LEVEL, OLD.a, NEW.a;
+ END IF;
+ RETURN NULL;
+END;
+\$\$ LANGUAGE plpgsql;
+CREATE TRIGGER sub1_tab1_log_op_trigger
+ AFTER INSERT OR UPDATE ON tab1
+ FOR EACH ROW EXECUTE PROCEDURE sub1_trigger_activity_func();
+ALTER TABLE ONLY tab1 ENABLE REPLICA TRIGGER sub1_tab1_log_op_trigger;
+CREATE TRIGGER sub1_tab1_2_log_op_trigger
+ AFTER INSERT OR UPDATE ON tab1_2
+ FOR EACH ROW EXECUTE PROCEDURE sub1_trigger_activity_func();
+ALTER TABLE ONLY tab1_2 ENABLE REPLICA TRIGGER sub1_tab1_2_log_op_trigger;
+CREATE TRIGGER sub1_tab1_2_2_log_op_trigger
+ AFTER INSERT OR UPDATE ON tab1_2_2
+ FOR EACH ROW EXECUTE PROCEDURE sub1_trigger_activity_func();
+ALTER TABLE ONLY tab1_2_2 ENABLE REPLICA TRIGGER sub1_tab1_2_2_log_op_trigger;
+});
+
+# subscriber 2
+#
+# This does not use partitioning. The tables match the leaf tables on
+# the publisher.
+$node_subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab1 (a int PRIMARY KEY, c text DEFAULT 'sub2_tab1', b text)"
+);
+$node_subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab1_1 (a int PRIMARY KEY, c text DEFAULT 'sub2_tab1_1', b text)"
+);
+$node_subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab1_2 (a int PRIMARY KEY, c text DEFAULT 'sub2_tab1_2', b text)"
+);
+$node_subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab1_def (a int PRIMARY KEY, b text, c text DEFAULT 'sub2_tab1_def')"
+);
+$node_subscriber2->safe_psql('postgres',
+ "CREATE SUBSCRIPTION sub2 CONNECTION '$publisher_connstr' PUBLICATION pub_all"
+);
+
+# Add set of AFTER replica triggers for testing that they are fired
+# correctly, using the same method as the first subscriber.
+$node_subscriber2->safe_psql(
+ 'postgres', qq{
+CREATE TABLE sub2_trigger_activity (tgtab text,
+ tgop text, tgwhen text, tglevel text, olda int, newa int);
+CREATE FUNCTION sub2_trigger_activity_func() RETURNS TRIGGER AS \$\$
+BEGIN
+ IF (TG_OP = 'INSERT') THEN
+ INSERT INTO public.sub2_trigger_activity
+ SELECT TG_RELNAME, TG_OP, TG_WHEN, TG_LEVEL, NULL, NEW.a;
+ ELSIF (TG_OP = 'UPDATE') THEN
+ INSERT INTO public.sub2_trigger_activity
+ SELECT TG_RELNAME, TG_OP, TG_WHEN, TG_LEVEL, OLD.a, NEW.a;
+ END IF;
+ RETURN NULL;
+END;
+\$\$ LANGUAGE plpgsql;
+CREATE TRIGGER sub2_tab1_log_op_trigger
+ AFTER INSERT OR UPDATE ON tab1
+ FOR EACH ROW EXECUTE PROCEDURE sub2_trigger_activity_func();
+ALTER TABLE ONLY tab1 ENABLE REPLICA TRIGGER sub2_tab1_log_op_trigger;
+CREATE TRIGGER sub2_tab1_2_log_op_trigger
+ AFTER INSERT OR UPDATE ON tab1_2
+ FOR EACH ROW EXECUTE PROCEDURE sub2_trigger_activity_func();
+ALTER TABLE ONLY tab1_2 ENABLE REPLICA TRIGGER sub2_tab1_2_log_op_trigger;
+});
+
+# Wait for initial sync of all subscriptions
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber1->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+$node_subscriber2->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Tests for replication using leaf partition identity and schema
+
+# insert
+$node_publisher->safe_psql('postgres', "INSERT INTO tab1 VALUES (1)");
+$node_publisher->safe_psql('postgres', "INSERT INTO tab1_1 (a) VALUES (3)");
+$node_publisher->safe_psql('postgres', "INSERT INTO tab1_2 VALUES (5)");
+$node_publisher->safe_psql('postgres', "INSERT INTO tab1 VALUES (0)");
+
+$node_publisher->wait_for_catchup('sub1');
+$node_publisher->wait_for_catchup('sub2');
+
+my $result = $node_subscriber1->safe_psql('postgres',
+ "SELECT c, a FROM tab1 ORDER BY 1, 2");
+is( $result, qq(sub1_tab1|0
+sub1_tab1|1
+sub1_tab1|3
+sub1_tab1|5), 'inserts into tab1 and its partitions replicated');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT a FROM tab1_2_1 ORDER BY 1");
+is($result, qq(5), 'inserts into tab1_2 replicated into tab1_2_1 correctly');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT a FROM tab1_2_2 ORDER BY 1");
+is($result, qq(), 'inserts into tab1_2 replicated into tab1_2_2 correctly');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab1_1 ORDER BY 1, 2");
+is( $result, qq(sub2_tab1_1|1
+sub2_tab1_1|3), 'inserts into tab1_1 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab1_2 ORDER BY 1, 2");
+is($result, qq(sub2_tab1_2|5), 'inserts into tab1_2 replicated');
+
+# The AFTER trigger of tab1_2 should have recorded one INSERT.
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT * FROM sub2_trigger_activity ORDER BY tgtab, tgop, tgwhen, olda, newa;"
+);
+is( $result,
+ qq(tab1_2|INSERT|AFTER|ROW||5),
+ 'check replica insert after trigger applied on subscriber');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab1_def ORDER BY 1, 2");
+is($result, qq(sub2_tab1_def|0), 'inserts into tab1_def replicated');
+
+# update (replicated as update)
+$node_publisher->safe_psql('postgres', "UPDATE tab1 SET a = 2 WHERE a = 1");
+# All of the following cause an update to be applied to a partitioned
+# table on subscriber1: tab1_2 is leaf partition on publisher, whereas
+# it's sub-partitioned on subscriber1.
+$node_publisher->safe_psql('postgres', "UPDATE tab1 SET a = 6 WHERE a = 5");
+$node_publisher->safe_psql('postgres', "UPDATE tab1 SET a = 4 WHERE a = 6");
+$node_publisher->safe_psql('postgres', "UPDATE tab1 SET a = 6 WHERE a = 4");
+
+$node_publisher->wait_for_catchup('sub1');
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT c, a FROM tab1 ORDER BY 1, 2");
+is( $result, qq(sub1_tab1|0
+sub1_tab1|2
+sub1_tab1|3
+sub1_tab1|6), 'update of tab1_1, tab1_2 replicated');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT a FROM tab1_2_1 ORDER BY 1");
+is($result, qq(), 'updates of tab1_2 replicated into tab1_2_1 correctly');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT a FROM tab1_2_2 ORDER BY 1");
+is($result, qq(6), 'updates of tab1_2 replicated into tab1_2_2 correctly');
+
+# The AFTER trigger should have recorded the UPDATEs of tab1_2_2.
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT * FROM sub1_trigger_activity ORDER BY tgtab, tgop, tgwhen, olda, newa;"
+);
+is( $result, qq(tab1_2_2|INSERT|AFTER|ROW||6
+tab1_2_2|UPDATE|AFTER|ROW|4|6
+tab1_2_2|UPDATE|AFTER|ROW|6|4),
+ 'check replica update after trigger applied on subscriber');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab1_1 ORDER BY 1, 2");
+is( $result, qq(sub2_tab1_1|2
+sub2_tab1_1|3), 'update of tab1_1 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab1_2 ORDER BY 1, 2");
+is($result, qq(sub2_tab1_2|6), 'tab1_2 updated');
+
+# The AFTER trigger should have recorded the updates of tab1_2.
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT * FROM sub2_trigger_activity ORDER BY tgtab, tgop, tgwhen, olda, newa;"
+);
+is( $result, qq(tab1_2|INSERT|AFTER|ROW||5
+tab1_2|UPDATE|AFTER|ROW|4|6
+tab1_2|UPDATE|AFTER|ROW|5|6
+tab1_2|UPDATE|AFTER|ROW|6|4),
+ 'check replica update after trigger applied on subscriber');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab1_def ORDER BY 1");
+is($result, qq(sub2_tab1_def|0), 'tab1_def unchanged');
+
+# update (replicated as delete+insert)
+$node_publisher->safe_psql('postgres', "UPDATE tab1 SET a = 1 WHERE a = 0");
+$node_publisher->safe_psql('postgres', "UPDATE tab1 SET a = 4 WHERE a = 1");
+
+$node_publisher->wait_for_catchup('sub1');
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT c, a FROM tab1 ORDER BY 1, 2");
+is( $result, qq(sub1_tab1|2
+sub1_tab1|3
+sub1_tab1|4
+sub1_tab1|6),
+ 'update of tab1 (delete from tab1_def + insert into tab1_1) replicated');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT a FROM tab1_2_2 ORDER BY 1");
+is( $result, qq(4
+6), 'updates of tab1 (delete + insert) replicated into tab1_2_2 correctly');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab1_1 ORDER BY 1, 2");
+is( $result, qq(sub2_tab1_1|2
+sub2_tab1_1|3), 'tab1_1 unchanged');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab1_2 ORDER BY 1, 2");
+is( $result, qq(sub2_tab1_2|4
+sub2_tab1_2|6), 'insert into tab1_2 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT a FROM tab1_def ORDER BY 1");
+is($result, qq(), 'delete from tab1_def replicated');
+
+# delete
+$node_publisher->safe_psql('postgres',
+ "DELETE FROM tab1 WHERE a IN (2, 3, 5)");
+$node_publisher->safe_psql('postgres', "DELETE FROM tab1_2");
+
+$node_publisher->wait_for_catchup('sub1');
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber1->safe_psql('postgres', "SELECT a FROM tab1");
+is($result, qq(), 'delete from tab1_1, tab1_2 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab1_1");
+is($result, qq(), 'delete from tab1_1 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab1_2");
+is($result, qq(), 'delete from tab1_2 replicated');
+
+# truncate
+$node_subscriber1->safe_psql('postgres',
+ "INSERT INTO tab1 (a) VALUES (1), (2), (5)");
+$node_subscriber2->safe_psql('postgres', "INSERT INTO tab1_2 (a) VALUES (2)");
+$node_publisher->safe_psql('postgres', "TRUNCATE tab1_2");
+
+$node_publisher->wait_for_catchup('sub1');
+$node_publisher->wait_for_catchup('sub2');
+
+$result =
+ $node_subscriber1->safe_psql('postgres', "SELECT a FROM tab1 ORDER BY 1");
+is( $result, qq(1
+2), 'truncate of tab1_2 replicated');
+
+$result =
+ $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab1_2 ORDER BY 1");
+is($result, qq(), 'truncate of tab1_2 replicated');
+
+$node_publisher->safe_psql('postgres', "TRUNCATE tab1");
+
+$node_publisher->wait_for_catchup('sub1');
+$node_publisher->wait_for_catchup('sub2');
+
+$result =
+ $node_subscriber1->safe_psql('postgres', "SELECT a FROM tab1 ORDER BY 1");
+is($result, qq(), 'truncate of tab1_1 replicated');
+$result =
+ $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab1 ORDER BY 1");
+is($result, qq(), 'truncate of tab1 replicated');
+
+# Check that subscriber handles cases where update/delete target tuple
+# is missing. We have to look for the DEBUG1 log messages about that,
+# so temporarily bump up the log verbosity.
+$node_subscriber1->append_conf('postgresql.conf',
+ "log_min_messages = debug1");
+$node_subscriber1->reload;
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab1 VALUES (1, 'foo'), (4, 'bar'), (10, 'baz')");
+
+$node_publisher->wait_for_catchup('sub1');
+$node_publisher->wait_for_catchup('sub2');
+
+$node_subscriber1->safe_psql('postgres', "DELETE FROM tab1");
+
+# Note that the current location of the log file is not grabbed immediately
+# after reloading the configuration, but after sending one SQL command to
+# the node so as we are sure that the reloading has taken effect.
+my $log_location = -s $node_subscriber1->logfile;
+
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab1 SET b = 'quux' WHERE a = 4");
+$node_publisher->safe_psql('postgres', "DELETE FROM tab1");
+
+$node_publisher->wait_for_catchup('sub1');
+$node_publisher->wait_for_catchup('sub2');
+
+my $logfile = slurp_file($node_subscriber1->logfile(), $log_location);
+ok( $logfile =~
+ qr/logical replication did not find row to be updated in replication target relation's partition "tab1_2_2"/,
+ 'update target row is missing in tab1_2_2');
+ok( $logfile =~
+ qr/logical replication did not find row to be deleted in replication target relation "tab1_1"/,
+ 'delete target row is missing in tab1_1');
+ok( $logfile =~
+ qr/logical replication did not find row to be deleted in replication target relation "tab1_2_2"/,
+ 'delete target row is missing in tab1_2_2');
+ok( $logfile =~
+ qr/logical replication did not find row to be deleted in replication target relation "tab1_def"/,
+ 'delete target row is missing in tab1_def');
+
+$node_subscriber1->append_conf('postgresql.conf',
+ "log_min_messages = warning");
+$node_subscriber1->reload;
+
+# Tests for replication using root table identity and schema
+
+# publisher
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION pub1");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab2 (a int PRIMARY KEY, b text) PARTITION BY LIST (a)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab2_1 (b text, a int NOT NULL)");
+$node_publisher->safe_psql('postgres',
+ "ALTER TABLE tab2 ATTACH PARTITION tab2_1 FOR VALUES IN (0, 1, 2, 3)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab2_2 PARTITION OF tab2 FOR VALUES IN (5, 6)");
+
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab3 (a int PRIMARY KEY, b text) PARTITION BY LIST (a)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab3_1 PARTITION OF tab3 FOR VALUES IN (0, 1, 2, 3, 5, 6)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab4 (a int PRIMARY KEY) PARTITION BY LIST (a)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab4_1 PARTITION OF tab4 FOR VALUES IN (0, 1) PARTITION BY LIST (a)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab4_1_1 PARTITION OF tab4_1 FOR VALUES IN (0, 1)");
+$node_publisher->safe_psql('postgres',
+ "ALTER PUBLICATION pub_all SET (publish_via_partition_root = true)");
+# Note: tab3_1's parent is not in the publication, in which case its
+# changes are published using own identity. For tab2, even though both parent
+# and child tables are present but changes will be replicated via the parent's
+# identity and only once.
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION pub_viaroot FOR TABLE tab2, tab2_1, tab3_1 WITH (publish_via_partition_root = true)"
+);
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION pub_lower_level FOR TABLE tab4_1 WITH (publish_via_partition_root = true)"
+);
+
+# prepare data for the initial sync
+$node_publisher->safe_psql('postgres', "INSERT INTO tab2 VALUES (1)");
+
+# subscriber 1
+$node_subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION sub1");
+$node_subscriber1->safe_psql('postgres',
+ "CREATE TABLE tab2 (a int PRIMARY KEY, c text DEFAULT 'sub1_tab2', b text) PARTITION BY RANGE (a)"
+);
+$node_subscriber1->safe_psql('postgres',
+ "CREATE TABLE tab2_1 (c text DEFAULT 'sub1_tab2', b text, a int NOT NULL)"
+);
+$node_subscriber1->safe_psql('postgres',
+ "ALTER TABLE tab2 ATTACH PARTITION tab2_1 FOR VALUES FROM (0) TO (10)");
+$node_subscriber1->safe_psql('postgres',
+ "CREATE TABLE tab3_1 (c text DEFAULT 'sub1_tab3_1', b text, a int NOT NULL PRIMARY KEY)"
+);
+$node_subscriber1->safe_psql('postgres',
+ "CREATE SUBSCRIPTION sub_viaroot CONNECTION '$publisher_connstr' PUBLICATION pub_viaroot"
+);
+
+# subscriber 2
+$node_subscriber2->safe_psql('postgres', "DROP TABLE tab1");
+$node_subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab1 (a int PRIMARY KEY, c text DEFAULT 'sub2_tab1', b text) PARTITION BY HASH (a)"
+);
+# Note: tab1's partitions are named tab1_1 and tab1_2 on the publisher.
+$node_subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab1_part1 (b text, c text, a int NOT NULL)");
+$node_subscriber2->safe_psql('postgres',
+ "ALTER TABLE tab1 ATTACH PARTITION tab1_part1 FOR VALUES WITH (MODULUS 2, REMAINDER 0)"
+);
+$node_subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab1_part2 PARTITION OF tab1 FOR VALUES WITH (MODULUS 2, REMAINDER 1)"
+);
+$node_subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab2 (a int PRIMARY KEY, c text DEFAULT 'sub2_tab2', b text)"
+);
+$node_subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab3 (a int PRIMARY KEY, c text DEFAULT 'sub2_tab3', b text)"
+);
+$node_subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab3_1 (a int PRIMARY KEY, c text DEFAULT 'sub2_tab3_1', b text)"
+);
+$node_subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab4 (a int PRIMARY KEY)"
+);
+$node_subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab4_1 (a int PRIMARY KEY)"
+);
+# Publication that sub2 points to now publishes via root, so must update
+# subscription target relations.
+$node_subscriber2->safe_psql('postgres',
+ "ALTER SUBSCRIPTION sub2 SET PUBLICATION pub_lower_level, pub_all");
+
+# Wait for initial sync of all subscriptions
+$node_subscriber1->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+$node_subscriber2->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# check that data is synced correctly
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT c, a FROM tab2");
+is( $result, qq(sub1_tab2|1), 'initial data synced for pub_viaroot');
+
+# insert
+$node_publisher->safe_psql('postgres', "INSERT INTO tab1 VALUES (1), (0)");
+$node_publisher->safe_psql('postgres', "INSERT INTO tab1_1 (a) VALUES (3)");
+$node_publisher->safe_psql('postgres', "INSERT INTO tab1_2 VALUES (5)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab2 VALUES (0), (3), (5)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab3 VALUES (1), (0), (3), (5)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab4 VALUES (0)");
+
+$node_publisher->wait_for_catchup('sub_viaroot');
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT c, a FROM tab2 ORDER BY 1, 2");
+is( $result, qq(sub1_tab2|0
+sub1_tab2|1
+sub1_tab2|3
+sub1_tab2|5), 'inserts into tab2 replicated');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT c, a FROM tab3_1 ORDER BY 1, 2");
+is( $result, qq(sub1_tab3_1|0
+sub1_tab3_1|1
+sub1_tab3_1|3
+sub1_tab3_1|5), 'inserts into tab3_1 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab1 ORDER BY 1, 2");
+is( $result, qq(sub2_tab1|0
+sub2_tab1|1
+sub2_tab1|3
+sub2_tab1|5), 'inserts into tab1 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab2 ORDER BY 1, 2");
+is( $result, qq(sub2_tab2|0
+sub2_tab2|1
+sub2_tab2|3
+sub2_tab2|5), 'inserts into tab2 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab3 ORDER BY 1, 2");
+is( $result, qq(sub2_tab3|0
+sub2_tab3|1
+sub2_tab3|3
+sub2_tab3|5), 'inserts into tab3 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT a FROM tab4 ORDER BY 1");
+is( $result, qq(0), 'inserts into tab4 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT a FROM tab4_1 ORDER BY 1");
+is( $result, qq(), 'inserts into tab4_1 replicated');
+
+# now switch the order of publications in the list, try again, the result
+# should be the same (no dependence on order of pulications)
+$node_subscriber2->safe_psql('postgres',
+ "ALTER SUBSCRIPTION sub2 SET PUBLICATION pub_all, pub_lower_level");
+
+# make sure the subscription on the second subscriber is synced, before
+# continuing
+$node_subscriber2->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Insert a change into the leaf partition, should be replicated through
+# the partition root (thanks to the FOR ALL TABLES partition).
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab4 VALUES (1)");
+
+$node_publisher->wait_for_catchup('sub2');
+
+# tab4 change should be replicated through the root partition, which
+# maps to the tab4 relation on subscriber.
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT a FROM tab4 ORDER BY 1");
+is( $result, qq(0
+1), 'inserts into tab4 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT a FROM tab4_1 ORDER BY 1");
+is( $result, qq(), 'inserts into tab4_1 replicated');
+
+# update (replicated as update)
+$node_publisher->safe_psql('postgres', "UPDATE tab1 SET a = 6 WHERE a = 5");
+$node_publisher->safe_psql('postgres', "UPDATE tab2 SET a = 6 WHERE a = 5");
+$node_publisher->safe_psql('postgres', "UPDATE tab3 SET a = 6 WHERE a = 5");
+
+$node_publisher->wait_for_catchup('sub_viaroot');
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT c, a FROM tab2 ORDER BY 1, 2");
+is( $result, qq(sub1_tab2|0
+sub1_tab2|1
+sub1_tab2|3
+sub1_tab2|6), 'update of tab2 replicated');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT c, a FROM tab3_1 ORDER BY 1, 2");
+is( $result, qq(sub1_tab3_1|0
+sub1_tab3_1|1
+sub1_tab3_1|3
+sub1_tab3_1|6), 'update of tab3_1 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab1 ORDER BY 1, 2");
+is( $result, qq(sub2_tab1|0
+sub2_tab1|1
+sub2_tab1|3
+sub2_tab1|6), 'inserts into tab1 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab2 ORDER BY 1, 2");
+is( $result, qq(sub2_tab2|0
+sub2_tab2|1
+sub2_tab2|3
+sub2_tab2|6), 'inserts into tab2 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab3 ORDER BY 1, 2");
+is( $result, qq(sub2_tab3|0
+sub2_tab3|1
+sub2_tab3|3
+sub2_tab3|6), 'inserts into tab3 replicated');
+
+# update (replicated as delete+insert)
+$node_publisher->safe_psql('postgres', "UPDATE tab1 SET a = 2 WHERE a = 6");
+$node_publisher->safe_psql('postgres', "UPDATE tab2 SET a = 2 WHERE a = 6");
+$node_publisher->safe_psql('postgres', "UPDATE tab3 SET a = 2 WHERE a = 6");
+
+$node_publisher->wait_for_catchup('sub_viaroot');
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT c, a FROM tab2 ORDER BY 1, 2");
+is( $result, qq(sub1_tab2|0
+sub1_tab2|1
+sub1_tab2|2
+sub1_tab2|3), 'update of tab2 replicated');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT c, a FROM tab3_1 ORDER BY 1, 2");
+is( $result, qq(sub1_tab3_1|0
+sub1_tab3_1|1
+sub1_tab3_1|2
+sub1_tab3_1|3), 'update of tab3_1 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab1 ORDER BY 1, 2");
+is( $result, qq(sub2_tab1|0
+sub2_tab1|1
+sub2_tab1|2
+sub2_tab1|3), 'update of tab1 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab2 ORDER BY 1, 2");
+is( $result, qq(sub2_tab2|0
+sub2_tab2|1
+sub2_tab2|2
+sub2_tab2|3), 'update of tab2 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a FROM tab3 ORDER BY 1, 2");
+is( $result, qq(sub2_tab3|0
+sub2_tab3|1
+sub2_tab3|2
+sub2_tab3|3), 'update of tab3 replicated');
+
+# delete
+$node_publisher->safe_psql('postgres', "DELETE FROM tab1");
+$node_publisher->safe_psql('postgres', "DELETE FROM tab2");
+$node_publisher->safe_psql('postgres', "DELETE FROM tab3");
+
+$node_publisher->wait_for_catchup('sub_viaroot');
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber1->safe_psql('postgres', "SELECT a FROM tab2");
+is($result, qq(), 'delete tab2 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab1");
+is($result, qq(), 'delete from tab1 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab2");
+is($result, qq(), 'delete from tab2 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab3");
+is($result, qq(), 'delete from tab3 replicated');
+
+# truncate
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab1 VALUES (1), (2), (5)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab2 VALUES (1), (2), (5)");
+# these will NOT be replicated
+$node_publisher->safe_psql('postgres', "TRUNCATE tab1_2, tab2_1, tab3_1");
+
+$node_publisher->wait_for_catchup('sub_viaroot');
+$node_publisher->wait_for_catchup('sub2');
+
+$result =
+ $node_subscriber1->safe_psql('postgres', "SELECT a FROM tab2 ORDER BY 1");
+is( $result, qq(1
+2
+5), 'truncate of tab2_1 NOT replicated');
+
+$result =
+ $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab1 ORDER BY 1");
+is( $result, qq(1
+2
+5), 'truncate of tab1_2 NOT replicated');
+
+$result =
+ $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab2 ORDER BY 1");
+is( $result, qq(1
+2
+5), 'truncate of tab2_1 NOT replicated');
+
+$node_publisher->safe_psql('postgres', "TRUNCATE tab1, tab2, tab3");
+
+$node_publisher->wait_for_catchup('sub_viaroot');
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber1->safe_psql('postgres', "SELECT a FROM tab2");
+is($result, qq(), 'truncate of tab2 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab1");
+is($result, qq(), 'truncate of tab1 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab2");
+is($result, qq(), 'truncate of tab2 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab3");
+is($result, qq(), 'truncate of tab3 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab3_1");
+is($result, qq(), 'truncate of tab3_1 replicated');
+
+# check that the map to convert tuples from leaf partition to the root
+# table is correctly rebuilt when a new column is added
+$node_publisher->safe_psql('postgres',
+ "ALTER TABLE tab2 DROP b, ADD COLUMN c text DEFAULT 'pub_tab2', ADD b text"
+);
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab2 (a, b) VALUES (1, 'xxx'), (3, 'yyy'), (5, 'zzz')");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab2 (a, b, c) VALUES (6, 'aaa', 'xxx_c')");
+
+$node_publisher->wait_for_catchup('sub_viaroot');
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber1->safe_psql('postgres',
+ "SELECT c, a, b FROM tab2 ORDER BY 1, 2");
+is( $result, qq(pub_tab2|1|xxx
+pub_tab2|3|yyy
+pub_tab2|5|zzz
+xxx_c|6|aaa), 'inserts into tab2 replicated');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT c, a, b FROM tab2 ORDER BY 1, 2");
+is( $result, qq(pub_tab2|1|xxx
+pub_tab2|3|yyy
+pub_tab2|5|zzz
+xxx_c|6|aaa), 'inserts into tab2 replicated');
+
+# Check that subscriber handles cases where update/delete target tuple
+# is missing. We have to look for the DEBUG1 log messages about that,
+# so temporarily bump up the log verbosity.
+$node_subscriber1->append_conf('postgresql.conf',
+ "log_min_messages = debug1");
+$node_subscriber1->reload;
+
+$node_subscriber1->safe_psql('postgres', "DELETE FROM tab2");
+
+# Note that the current location of the log file is not grabbed immediately
+# after reloading the configuration, but after sending one SQL command to
+# the node so as we are sure that the reloading has taken effect.
+$log_location = -s $node_subscriber1->logfile;
+
+$node_publisher->safe_psql('postgres',
+ "UPDATE tab2 SET b = 'quux' WHERE a = 5");
+$node_publisher->safe_psql('postgres', "DELETE FROM tab2 WHERE a = 1");
+
+$node_publisher->wait_for_catchup('sub_viaroot');
+$node_publisher->wait_for_catchup('sub2');
+
+$logfile = slurp_file($node_subscriber1->logfile(), $log_location);
+ok( $logfile =~
+ qr/logical replication did not find row to be updated in replication target relation's partition "tab2_1"/,
+ 'update target row is missing in tab2_1');
+ok( $logfile =~
+ qr/logical replication did not find row to be deleted in replication target relation "tab2_1"/,
+ 'delete target row is missing in tab2_1');
+
+$node_subscriber1->append_conf('postgresql.conf',
+ "log_min_messages = warning");
+$node_subscriber1->reload;
+
+# Test that replication continues to work correctly after altering the
+# partition of a partitioned target table.
+
+$node_publisher->safe_psql(
+ 'postgres', q{
+ CREATE TABLE tab5 (a int NOT NULL, b int);
+ CREATE UNIQUE INDEX tab5_a_idx ON tab5 (a);
+ ALTER TABLE tab5 REPLICA IDENTITY USING INDEX tab5_a_idx;});
+
+$node_subscriber2->safe_psql(
+ 'postgres', q{
+ CREATE TABLE tab5 (a int NOT NULL, b int, c int) PARTITION BY LIST (a);
+ CREATE TABLE tab5_1 PARTITION OF tab5 DEFAULT;
+ CREATE UNIQUE INDEX tab5_a_idx ON tab5 (a);
+ ALTER TABLE tab5 REPLICA IDENTITY USING INDEX tab5_a_idx;
+ ALTER TABLE tab5_1 REPLICA IDENTITY USING INDEX tab5_1_a_idx;});
+
+$node_subscriber2->safe_psql('postgres',
+ "ALTER SUBSCRIPTION sub2 REFRESH PUBLICATION");
+
+$node_subscriber2->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Make partition map cache
+$node_publisher->safe_psql('postgres', "INSERT INTO tab5 VALUES (1, 1)");
+$node_publisher->safe_psql('postgres', "UPDATE tab5 SET a = 2 WHERE a = 1");
+
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT a, b FROM tab5 ORDER BY 1");
+is($result, qq(2|1), 'updates of tab5 replicated correctly');
+
+# Change the column order of partition on subscriber
+$node_subscriber2->safe_psql(
+ 'postgres', q{
+ ALTER TABLE tab5 DETACH PARTITION tab5_1;
+ ALTER TABLE tab5_1 DROP COLUMN b;
+ ALTER TABLE tab5_1 ADD COLUMN b int;
+ ALTER TABLE tab5 ATTACH PARTITION tab5_1 DEFAULT});
+
+$node_publisher->safe_psql('postgres', "UPDATE tab5 SET a = 3 WHERE a = 2");
+
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT a, b, c FROM tab5 ORDER BY 1");
+is($result, qq(3|1|), 'updates of tab5 replicated correctly after altering table on subscriber');
+
+# Test that replication into the partitioned target table continues to
+# work correctly when the published table is altered.
+$node_publisher->safe_psql(
+ 'postgres', q{
+ ALTER TABLE tab5 DROP COLUMN b, ADD COLUMN c INT;
+ ALTER TABLE tab5 ADD COLUMN b INT;});
+
+$node_publisher->safe_psql('postgres', "UPDATE tab5 SET c = 1 WHERE a = 3");
+
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT a, b, c FROM tab5 ORDER BY 1");
+is($result, qq(3||1), 'updates of tab5 replicated correctly after altering table on publisher');
+
+# Test that replication works correctly as long as the leaf partition
+# has the necessary REPLICA IDENTITY, even though the actual target
+# partitioned table does not.
+$node_subscriber2->safe_psql('postgres',
+ "ALTER TABLE tab5 REPLICA IDENTITY NOTHING");
+
+$node_publisher->safe_psql('postgres', "UPDATE tab5 SET a = 4 WHERE a = 3");
+
+$node_publisher->wait_for_catchup('sub2');
+
+$result = $node_subscriber2->safe_psql('postgres',
+ "SELECT a, b, c FROM tab5_1 ORDER BY 1");
+is($result, qq(4||1), 'updates of tab5 replicated correctly');
diff --git a/src/test/subscription/t/014_binary.pl b/src/test/subscription/t/014_binary.pl
new file mode 100644
index 0000000..7260378
--- /dev/null
+++ b/src/test/subscription/t/014_binary.pl
@@ -0,0 +1,137 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Binary mode logical replication test
+
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 5;
+
+# Create and initialize a publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create and initialize subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create tables on both sides of the replication
+my $ddl = qq(
+ CREATE TABLE public.test_numerical (
+ a INTEGER PRIMARY KEY,
+ b NUMERIC,
+ c FLOAT,
+ d BIGINT
+ );
+ CREATE TABLE public.test_arrays (
+ a INTEGER[] PRIMARY KEY,
+ b NUMERIC[],
+ c TEXT[]
+ ););
+
+$node_publisher->safe_psql('postgres', $ddl);
+$node_subscriber->safe_psql('postgres', $ddl);
+
+# Configure logical replication
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tpub FOR ALL TABLES");
+
+my $publisher_connstring = $node_publisher->connstr . ' dbname=postgres';
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tsub CONNECTION '$publisher_connstring' "
+ . "PUBLICATION tpub WITH (slot_name = tpub_slot, binary = true)");
+
+# Ensure nodes are in sync with each other
+$node_publisher->wait_for_catchup('tsub');
+$node_subscriber->poll_query_until('postgres',
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('s', 'r');"
+) or die "Timed out while waiting for subscriber to synchronize data";
+
+# Insert some content and make sure it's replicated across
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO public.test_arrays (a, b, c) VALUES
+ ('{1,2,3}', '{1.1, 1.2, 1.3}', '{"one", "two", "three"}'),
+ ('{3,1,2}', '{1.3, 1.1, 1.2}', '{"three", "one", "two"}');
+
+ INSERT INTO public.test_numerical (a, b, c, d) VALUES
+ (1, 1.2, 1.3, 10),
+ (2, 2.2, 2.3, 20),
+ (3, 3.2, 3.3, 30);
+ ));
+
+$node_publisher->wait_for_catchup('tsub');
+
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT a, b, c, d FROM test_numerical ORDER BY a");
+
+is( $result, '1|1.2|1.3|10
+2|2.2|2.3|20
+3|3.2|3.3|30', 'check replicated data on subscriber');
+
+# Test updates as well
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ UPDATE public.test_arrays SET b[1] = 42, c = NULL;
+ UPDATE public.test_numerical SET b = 42, c = NULL;
+ ));
+
+$node_publisher->wait_for_catchup('tsub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT a, b, c FROM test_arrays ORDER BY a");
+
+is( $result, '{1,2,3}|{42,1.2,1.3}|
+{3,1,2}|{42,1.1,1.2}|', 'check updated replicated data on subscriber');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT a, b, c, d FROM test_numerical ORDER BY a");
+
+is( $result, '1|42||10
+2|42||20
+3|42||30', 'check updated replicated data on subscriber');
+
+# Test to reset back to text formatting, and then to binary again
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tsub SET (binary = false);");
+
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO public.test_numerical (a, b, c, d) VALUES
+ (4, 4.2, 4.3, 40);
+ ));
+
+$node_publisher->wait_for_catchup('tsub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT a, b, c, d FROM test_numerical ORDER BY a");
+
+is( $result, '1|42||10
+2|42||20
+3|42||30
+4|4.2|4.3|40', 'check replicated data on subscriber');
+
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tsub SET (binary = true);");
+
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO public.test_arrays (a, b, c) VALUES
+ ('{2,3,1}', '{1.2, 1.3, 1.1}', '{"two", "three", "one"}');
+ ));
+
+$node_publisher->wait_for_catchup('tsub');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT a, b, c FROM test_arrays ORDER BY a");
+
+is( $result, '{1,2,3}|{42,1.2,1.3}|
+{2,3,1}|{1.2,1.3,1.1}|{two,three,one}
+{3,1,2}|{42,1.1,1.2}|', 'check replicated data on subscriber');
+
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
diff --git a/src/test/subscription/t/015_stream.pl b/src/test/subscription/t/015_stream.pl
new file mode 100644
index 0000000..998650a
--- /dev/null
+++ b/src/test/subscription/t/015_stream.pl
@@ -0,0 +1,135 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test streaming of simple large transaction
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# Create publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->append_conf('postgresql.conf',
+ 'logical_decoding_work_mem = 64kB');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create some preexisting content on publisher
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE test_tab (a int primary key, b varchar)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE test_tab (a int primary key, b text, c timestamptz DEFAULT now(), d bigint DEFAULT 999)"
+);
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub FOR TABLE test_tab");
+
+my $appname = 'tap_sub';
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub WITH (streaming = on)"
+);
+
+$node_publisher->wait_for_catchup($appname);
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+my $result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c), count(d = 999) FROM test_tab");
+is($result, qq(2|2|2), 'check initial data was copied to subscriber');
+
+# Interleave a pair of transactions, each exceeding the 64kB limit.
+my $in = '';
+my $out = '';
+
+my $timer = IPC::Run::timeout($TestLib::timeout_default);
+
+my $h = $node_publisher->background_psql('postgres', \$in, \$out, $timer,
+ on_error_stop => 0);
+
+$in .= q{
+BEGIN;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3, 5000) s(i);
+UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0;
+DELETE FROM test_tab WHERE mod(a,3) = 0;
+};
+$h->pump_nb;
+
+$node_publisher->safe_psql(
+ 'postgres', q{
+BEGIN;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(5001, 9999) s(i);
+DELETE FROM test_tab WHERE a > 5000;
+COMMIT;
+});
+
+$in .= q{
+COMMIT;
+\q
+};
+$h->finish; # errors make the next test fail, so ignore them here
+
+$node_publisher->wait_for_catchup($appname);
+
+$result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c), count(d = 999) FROM test_tab");
+is($result, qq(3334|3334|3334), 'check extra columns contain local defaults');
+
+# Test the streaming in binary mode
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub SET (binary = on)");
+
+# Insert, update and delete enough rows to exceed the 64kB limit.
+$node_publisher->safe_psql(
+ 'postgres', q{
+BEGIN;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(5001, 10000) s(i);
+UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0;
+DELETE FROM test_tab WHERE mod(a,3) = 0;
+COMMIT;
+});
+
+$node_publisher->wait_for_catchup($appname);
+
+$result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c), count(d = 999) FROM test_tab");
+is($result, qq(6667|6667|6667), 'check extra columns contain local defaults');
+
+# Change the local values of the extra columns on the subscriber,
+# update publisher, and check that subscriber retains the expected
+# values. This is to ensure that non-streaming transactions behave
+# properly after a streaming transaction.
+$node_subscriber->safe_psql('postgres',
+ "UPDATE test_tab SET c = 'epoch'::timestamptz + 987654321 * interval '1s'"
+);
+$node_publisher->safe_psql('postgres',
+ "UPDATE test_tab SET b = md5(a::text)");
+
+$node_publisher->wait_for_catchup($appname);
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(extract(epoch from c) = 987654321), count(d = 999) FROM test_tab"
+);
+is($result, qq(6667|6667|6667),
+ 'check extra columns contain locally changed data');
+
+$node_subscriber->stop;
+$node_publisher->stop;
diff --git a/src/test/subscription/t/016_stream_subxact.pl b/src/test/subscription/t/016_stream_subxact.pl
new file mode 100644
index 0000000..0245b06
--- /dev/null
+++ b/src/test/subscription/t/016_stream_subxact.pl
@@ -0,0 +1,93 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test streaming of large transaction containing large subtransactions
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 2;
+
+# Create publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->append_conf('postgresql.conf',
+ 'logical_decoding_work_mem = 64kB');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create some preexisting content on publisher
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE test_tab (a int primary key, b varchar)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE test_tab (a int primary key, b text, c timestamptz DEFAULT now(), d bigint DEFAULT 999)"
+);
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub FOR TABLE test_tab");
+
+my $appname = 'tap_sub';
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub WITH (streaming = on)"
+);
+
+$node_publisher->wait_for_catchup($appname);
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+my $result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c), count(d = 999) FROM test_tab");
+is($result, qq(2|2|2), 'check initial data was copied to subscriber');
+
+# Insert, update and delete enough rows to exceed 64kB limit.
+$node_publisher->safe_psql(
+ 'postgres', q{
+BEGIN;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series( 3, 500) s(i);
+UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0;
+DELETE FROM test_tab WHERE mod(a,3) = 0;
+SAVEPOINT s1;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(501, 1000) s(i);
+UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0;
+DELETE FROM test_tab WHERE mod(a,3) = 0;
+SAVEPOINT s2;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(1001, 1500) s(i);
+UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0;
+DELETE FROM test_tab WHERE mod(a,3) = 0;
+SAVEPOINT s3;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(1501, 2000) s(i);
+UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0;
+DELETE FROM test_tab WHERE mod(a,3) = 0;
+SAVEPOINT s4;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(2001, 2500) s(i);
+UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0;
+DELETE FROM test_tab WHERE mod(a,3) = 0;
+COMMIT;
+});
+
+$node_publisher->wait_for_catchup($appname);
+
+$result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c), count(d = 999) FROM test_tab");
+is($result, qq(1667|1667|1667),
+ 'check data was copied to subscriber in streaming mode and extra columns contain local defaults'
+);
+
+$node_subscriber->stop;
+$node_publisher->stop;
diff --git a/src/test/subscription/t/017_stream_ddl.pl b/src/test/subscription/t/017_stream_ddl.pl
new file mode 100644
index 0000000..35b1468
--- /dev/null
+++ b/src/test/subscription/t/017_stream_ddl.pl
@@ -0,0 +1,129 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test streaming of large transaction with DDL and subtransactions
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 3;
+
+# Create publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->append_conf('postgresql.conf',
+ 'logical_decoding_work_mem = 64kB');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create some preexisting content on publisher
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE test_tab (a int primary key, b varchar)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE test_tab (a int primary key, b text, c INT, d INT, e INT, f INT)"
+);
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub FOR TABLE test_tab");
+
+my $appname = 'tap_sub';
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub WITH (streaming = on)"
+);
+
+$node_publisher->wait_for_catchup($appname);
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+my $result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c), count(d = 999) FROM test_tab");
+is($result, qq(2|0|0), 'check initial data was copied to subscriber');
+
+# a small (non-streamed) transaction with DDL and DML
+$node_publisher->safe_psql(
+ 'postgres', q{
+BEGIN;
+INSERT INTO test_tab VALUES (3, md5(3::text));
+ALTER TABLE test_tab ADD COLUMN c INT;
+SAVEPOINT s1;
+INSERT INTO test_tab VALUES (4, md5(4::text), -4);
+COMMIT;
+});
+
+# large (streamed) transaction with DDL and DML
+$node_publisher->safe_psql(
+ 'postgres', q{
+BEGIN;
+INSERT INTO test_tab SELECT i, md5(i::text), -i FROM generate_series(5, 1000) s(i);
+ALTER TABLE test_tab ADD COLUMN d INT;
+SAVEPOINT s1;
+INSERT INTO test_tab SELECT i, md5(i::text), -i, 2*i FROM generate_series(1001, 2000) s(i);
+COMMIT;
+});
+
+# a small (non-streamed) transaction with DDL and DML
+$node_publisher->safe_psql(
+ 'postgres', q{
+BEGIN;
+INSERT INTO test_tab VALUES (2001, md5(2001::text), -2001, 2*2001);
+ALTER TABLE test_tab ADD COLUMN e INT;
+SAVEPOINT s1;
+INSERT INTO test_tab VALUES (2002, md5(2002::text), -2002, 2*2002, -3*2002);
+COMMIT;
+});
+
+$node_publisher->wait_for_catchup($appname);
+
+$result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c), count(d), count(e) FROM test_tab");
+is($result, qq(2002|1999|1002|1),
+ 'check data was copied to subscriber in streaming mode and extra columns contain local defaults'
+);
+
+# A large (streamed) transaction with DDL and DML. One of the DDL is performed
+# after DML to ensure that we invalidate the schema sent for test_tab so that
+# the next transaction has to send the schema again.
+$node_publisher->safe_psql(
+ 'postgres', q{
+BEGIN;
+INSERT INTO test_tab SELECT i, md5(i::text), -i, 2*i, -3*i FROM generate_series(2003,5000) s(i);
+ALTER TABLE test_tab ADD COLUMN f INT;
+COMMIT;
+});
+
+# A small transaction that won't get streamed. This is just to ensure that we
+# send the schema again to reflect the last column added in the previous test.
+$node_publisher->safe_psql(
+ 'postgres', q{
+BEGIN;
+INSERT INTO test_tab SELECT i, md5(i::text), -i, 2*i, -3*i, 4*i FROM generate_series(5001,5005) s(i);
+COMMIT;
+});
+
+$node_publisher->wait_for_catchup($appname);
+
+$result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c), count(d), count(e), count(f) FROM test_tab");
+is($result, qq(5005|5002|4005|3004|5),
+ 'check data was copied to subscriber for both streaming and non-streaming transactions'
+);
+
+$node_subscriber->stop;
+$node_publisher->stop;
diff --git a/src/test/subscription/t/018_stream_subxact_abort.pl b/src/test/subscription/t/018_stream_subxact_abort.pl
new file mode 100644
index 0000000..7fc60b5
--- /dev/null
+++ b/src/test/subscription/t/018_stream_subxact_abort.pl
@@ -0,0 +1,133 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test streaming of large transaction containing multiple subtransactions and rollbacks
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# Create publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->append_conf('postgresql.conf',
+ 'logical_decoding_work_mem = 64kB');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create some preexisting content on publisher
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE test_tab (a int primary key, b varchar)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE test_tab (a int primary key, b text, c INT, d INT, e INT)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub FOR TABLE test_tab");
+
+my $appname = 'tap_sub';
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub WITH (streaming = on)"
+);
+
+$node_publisher->wait_for_catchup($appname);
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+my $result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c) FROM test_tab");
+is($result, qq(2|0), 'check initial data was copied to subscriber');
+
+# large (streamed) transaction with DDL, DML and ROLLBACKs
+$node_publisher->safe_psql(
+ 'postgres', q{
+BEGIN;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3,500) s(i);
+SAVEPOINT s1;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(501,1000) s(i);
+SAVEPOINT s2;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(1001,1500) s(i);
+SAVEPOINT s3;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(1501,2000) s(i);
+ROLLBACK TO s2;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(2001,2500) s(i);
+ROLLBACK TO s1;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(2501,3000) s(i);
+SAVEPOINT s4;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3001,3500) s(i);
+SAVEPOINT s5;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3501,4000) s(i);
+COMMIT;
+});
+
+$node_publisher->wait_for_catchup($appname);
+
+$result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c) FROM test_tab");
+is($result, qq(2000|0),
+ 'check rollback to savepoint was reflected on subscriber and extra columns contain local defaults'
+);
+
+# large (streamed) transaction with subscriber receiving out of order
+# subtransaction ROLLBACKs
+$node_publisher->safe_psql(
+ 'postgres', q{
+BEGIN;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(4001,4500) s(i);
+SAVEPOINT s1;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(5001,5500) s(i);
+SAVEPOINT s2;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(6001,6500) s(i);
+SAVEPOINT s3;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(7001,7500) s(i);
+RELEASE s2;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(8001,8500) s(i);
+ROLLBACK TO s1;
+COMMIT;
+});
+
+$node_publisher->wait_for_catchup($appname);
+
+$result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c) FROM test_tab");
+is($result, qq(2500|0),
+ 'check rollback to savepoint was reflected on subscriber');
+
+# large (streamed) transaction with subscriber receiving rollback
+$node_publisher->safe_psql(
+ 'postgres', q{
+BEGIN;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(8501,9000) s(i);
+SAVEPOINT s1;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(9001,9500) s(i);
+SAVEPOINT s2;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(9501,10000) s(i);
+ROLLBACK;
+});
+
+$node_publisher->wait_for_catchup($appname);
+
+$result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c) FROM test_tab");
+is($result, qq(2500|0), 'check rollback was reflected on subscriber');
+
+$node_subscriber->stop;
+$node_publisher->stop;
diff --git a/src/test/subscription/t/019_stream_subxact_ddl_abort.pl b/src/test/subscription/t/019_stream_subxact_ddl_abort.pl
new file mode 100644
index 0000000..81149b8
--- /dev/null
+++ b/src/test/subscription/t/019_stream_subxact_ddl_abort.pl
@@ -0,0 +1,87 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test streaming of large transaction with subtransactions, DDLs, DMLs, and
+# rollbacks
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 2;
+
+# Create publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->append_conf('postgresql.conf',
+ 'logical_decoding_work_mem = 64kB');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create some preexisting content on publisher
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE test_tab (a int primary key, b varchar)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE test_tab (a int primary key, b text, c INT, d INT, e INT)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub FOR TABLE test_tab");
+
+my $appname = 'tap_sub';
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub WITH (streaming = on)"
+);
+
+$node_publisher->wait_for_catchup($appname);
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+my $result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c) FROM test_tab");
+is($result, qq(2|0), 'check initial data was copied to subscriber');
+
+# large (streamed) transaction with DDL, DML and ROLLBACKs
+$node_publisher->safe_psql(
+ 'postgres', q{
+BEGIN;
+INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3,500) s(i);
+ALTER TABLE test_tab ADD COLUMN c INT;
+SAVEPOINT s1;
+INSERT INTO test_tab SELECT i, md5(i::text), -i FROM generate_series(501,1000) s(i);
+ALTER TABLE test_tab ADD COLUMN d INT;
+SAVEPOINT s2;
+INSERT INTO test_tab SELECT i, md5(i::text), -i, 2*i FROM generate_series(1001,1500) s(i);
+ALTER TABLE test_tab ADD COLUMN e INT;
+SAVEPOINT s3;
+INSERT INTO test_tab SELECT i, md5(i::text), -i, 2*i, -3*i FROM generate_series(1501,2000) s(i);
+ALTER TABLE test_tab DROP COLUMN c;
+ROLLBACK TO s1;
+INSERT INTO test_tab SELECT i, md5(i::text), i FROM generate_series(501,1000) s(i);
+COMMIT;
+});
+
+$node_publisher->wait_for_catchup($appname);
+
+$result =
+ $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), count(c) FROM test_tab");
+is($result, qq(1000|500),
+ 'check rollback to savepoint was reflected on subscriber and extra columns contain local defaults'
+);
+
+$node_subscriber->stop;
+$node_publisher->stop;
diff --git a/src/test/subscription/t/020_messages.pl b/src/test/subscription/t/020_messages.pl
new file mode 100644
index 0000000..0e218e0
--- /dev/null
+++ b/src/test/subscription/t/020_messages.pl
@@ -0,0 +1,148 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Tests that logical decoding messages
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 5;
+
+# Create publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->append_conf('postgresql.conf', 'autovacuum = off');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create some preexisting content on publisher
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_test (a int primary key)");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab_test (a int primary key)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub FOR TABLE tab_test");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub"
+);
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Ensure a transactional logical decoding message shows up on the slot
+$node_subscriber->safe_psql('postgres', "ALTER SUBSCRIPTION tap_sub DISABLE");
+
+# wait for the replication slot to become inactive in the publisher
+$node_publisher->poll_query_until(
+ 'postgres',
+ "SELECT COUNT(*) FROM pg_catalog.pg_replication_slots WHERE slot_name = 'tap_sub' AND active='f'",
+ 1);
+
+$node_publisher->safe_psql('postgres',
+ "SELECT pg_logical_emit_message(true, 'pgoutput', 'a transactional message')"
+);
+
+my $result = $node_publisher->safe_psql(
+ 'postgres', qq(
+ SELECT get_byte(data, 0)
+ FROM pg_logical_slot_peek_binary_changes('tap_sub', NULL, NULL,
+ 'proto_version', '1',
+ 'publication_names', 'tap_pub',
+ 'messages', 'true')
+));
+
+# 66 77 67 == B M C == BEGIN MESSAGE COMMIT
+is( $result, qq(66
+77
+67),
+ 'messages on slot are B M C with message option');
+
+$result = $node_publisher->safe_psql(
+ 'postgres', qq(
+ SELECT get_byte(data, 1), encode(substr(data, 11, 8), 'escape')
+ FROM pg_logical_slot_peek_binary_changes('tap_sub', NULL, NULL,
+ 'proto_version', '1',
+ 'publication_names', 'tap_pub',
+ 'messages', 'true')
+ OFFSET 1 LIMIT 1
+));
+
+is($result, qq(1|pgoutput),
+ "flag transactional is set to 1 and prefix is pgoutput");
+
+$result = $node_publisher->safe_psql(
+ 'postgres', qq(
+ SELECT get_byte(data, 0)
+ FROM pg_logical_slot_get_binary_changes('tap_sub', NULL, NULL,
+ 'proto_version', '1',
+ 'publication_names', 'tap_pub')
+));
+
+# 66 67 == B C == BEGIN COMMIT
+is( $result, qq(66
+67),
+ 'option messages defaults to false so message (M) is not available on slot'
+);
+
+$node_publisher->safe_psql('postgres', "INSERT INTO tab_test VALUES (1)");
+
+my $message_lsn = $node_publisher->safe_psql('postgres',
+ "SELECT pg_logical_emit_message(false, 'pgoutput', 'a non-transactional message')"
+);
+
+$node_publisher->safe_psql('postgres', "INSERT INTO tab_test VALUES (2)");
+
+$result = $node_publisher->safe_psql(
+ 'postgres', qq(
+ SELECT get_byte(data, 0), get_byte(data, 1)
+ FROM pg_logical_slot_get_binary_changes('tap_sub', NULL, NULL,
+ 'proto_version', '1',
+ 'publication_names', 'tap_pub',
+ 'messages', 'true')
+ WHERE lsn = '$message_lsn' AND xid = 0
+));
+
+is($result, qq(77|0), 'non-transactional message on slot is M');
+
+# Ensure a non-transactional logical decoding message shows up on the slot when
+# it is emitted within an aborted transaction. The message won't emit until
+# something advances the LSN, hence, we intentionally forces the server to
+# switch to a new WAL file.
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ BEGIN;
+ SELECT pg_logical_emit_message(false, 'pgoutput',
+ 'a non-transactional message is available even if the transaction is aborted 1');
+ INSERT INTO tab_test VALUES (3);
+ SELECT pg_logical_emit_message(true, 'pgoutput',
+ 'a transactional message is not available if the transaction is aborted');
+ SELECT pg_logical_emit_message(false, 'pgoutput',
+ 'a non-transactional message is available even if the transaction is aborted 2');
+ ROLLBACK;
+ SELECT pg_switch_wal();
+));
+
+$result = $node_publisher->safe_psql(
+ 'postgres', qq(
+ SELECT get_byte(data, 0), get_byte(data, 1)
+ FROM pg_logical_slot_peek_binary_changes('tap_sub', NULL, NULL,
+ 'proto_version', '1',
+ 'publication_names', 'tap_pub',
+ 'messages', 'true')
+));
+
+is( $result, qq(77|0
+77|0),
+ 'non-transactional message on slot from aborted transaction is M');
+
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
diff --git a/src/test/subscription/t/021_alter_sub_pub.pl b/src/test/subscription/t/021_alter_sub_pub.pl
new file mode 100644
index 0000000..104eddb
--- /dev/null
+++ b/src/test/subscription/t/021_alter_sub_pub.pl
@@ -0,0 +1,98 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# This test checks behaviour of ALTER SUBSCRIPTION ... ADD/DROP PUBLICATION
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 3;
+
+# Initialize publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create table on publisher
+$node_publisher->safe_psql('postgres', "CREATE TABLE tab_1 (a int)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_1 SELECT generate_series(1,10)");
+
+# Create table on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_1 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_1 FOR TABLE tab_1");
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_2");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub_1, tap_pub_2"
+);
+
+# Wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Check the initial data of tab_1 is copied to subscriber
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_1");
+is($result, qq(10|1|10), 'check initial data is copied to subscriber');
+
+# Create a new table on publisher
+$node_publisher->safe_psql('postgres', "CREATE TABLE tab_2 (a int)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_2 SELECT generate_series(1,10)");
+
+# Create a new table on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_2 (a int)");
+
+# Add the table to publication
+$node_publisher->safe_psql('postgres',
+ "ALTER PUBLICATION tap_pub_2 ADD TABLE tab_2");
+
+# Dropping tap_pub_1 will refresh the entire publication list
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub DROP PUBLICATION tap_pub_1");
+
+# Wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Check the initial data of tab_drop_refresh was copied to subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_2");
+is($result, qq(10|1|10), 'check initial data is copied to subscriber');
+
+# Re-adding tap_pub_1 will refresh the entire publication list
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub ADD PUBLICATION tap_pub_1");
+
+# Wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Check the initial data of tab_1 was copied to subscriber again
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM tab_1");
+is($result, qq(20|1|10), 'check initial data is copied to subscriber');
+
+# shutdown
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
diff --git a/src/test/subscription/t/100_bugs.pl b/src/test/subscription/t/100_bugs.pl
new file mode 100644
index 0000000..424ffb7
--- /dev/null
+++ b/src/test/subscription/t/100_bugs.pl
@@ -0,0 +1,308 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Tests for various bugs found over time
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 7;
+
+# Bug #15114
+
+# The bug was that determining which columns are part of the replica
+# identity index using RelationGetIndexAttrBitmap() would run
+# eval_const_expressions() on index expressions and predicates across
+# all indexes of the table, which in turn might require a snapshot,
+# but there wasn't one set, so it crashes. There were actually two
+# separate bugs, one on the publisher and one on the subscriber. The
+# fix was to avoid the constant expressions simplification in
+# RelationGetIndexAttrBitmap(), so it's safe to call in more contexts.
+
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab1 (a int PRIMARY KEY, b int)");
+
+$node_publisher->safe_psql('postgres',
+ "CREATE FUNCTION double(x int) RETURNS int IMMUTABLE LANGUAGE SQL AS 'select x * 2'"
+);
+
+# an index with a predicate that lends itself to constant expressions
+# evaluation
+$node_publisher->safe_psql('postgres',
+ "CREATE INDEX ON tab1 (b) WHERE a > double(1)");
+
+# and the same setup on the subscriber
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab1 (a int PRIMARY KEY, b int)");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE FUNCTION double(x int) RETURNS int IMMUTABLE LANGUAGE SQL AS 'select x * 2'"
+);
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE INDEX ON tab1 (b) WHERE a > double(1)");
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION pub1 FOR ALL TABLES");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr' PUBLICATION pub1"
+);
+
+$node_publisher->wait_for_catchup('sub1');
+
+# This would crash, first on the publisher, and then (if the publisher
+# is fixed) on the subscriber.
+$node_publisher->safe_psql('postgres', "INSERT INTO tab1 VALUES (1, 2)");
+
+$node_publisher->wait_for_catchup('sub1');
+
+pass('index predicates do not cause crash');
+
+$node_publisher->stop('fast');
+$node_subscriber->stop('fast');
+
+
+# Handling of temporary and unlogged tables with FOR ALL TABLES publications
+
+# If a FOR ALL TABLES publication exists, temporary and unlogged
+# tables are ignored for publishing changes. The bug was that we
+# would still check in that case that such a table has a replica
+# identity set before accepting updates. If it did not it would cause
+# an error when an update was attempted.
+
+$node_publisher = get_new_node('publisher2');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION pub FOR ALL TABLES");
+
+is( $node_publisher->psql(
+ 'postgres',
+ "CREATE TEMPORARY TABLE tt1 AS SELECT 1 AS a; UPDATE tt1 SET a = 2;"),
+ 0,
+ 'update to temporary table without replica identity with FOR ALL TABLES publication'
+);
+
+is( $node_publisher->psql(
+ 'postgres',
+ "CREATE UNLOGGED TABLE tu1 AS SELECT 1 AS a; UPDATE tu1 SET a = 2;"),
+ 0,
+ 'update to unlogged table without replica identity with FOR ALL TABLES publication'
+);
+
+$node_publisher->stop('fast');
+
+# Bug #16643 - https://postgr.es/m/16643-eaadeb2a1a58d28c@postgresql.org
+#
+# Initial sync doesn't complete; the protocol was not being followed per
+# expectations after commit 07082b08cc5d.
+my $node_twoways = get_new_node('twoways');
+$node_twoways->init(allows_streaming => 'logical');
+$node_twoways->start;
+for my $db (qw(d1 d2))
+{
+ $node_twoways->safe_psql('postgres', "CREATE DATABASE $db");
+ $node_twoways->safe_psql($db, "CREATE TABLE t (f int)");
+ $node_twoways->safe_psql($db, "CREATE TABLE t2 (f int)");
+}
+
+my $rows = 3000;
+$node_twoways->safe_psql(
+ 'd1', qq{
+ INSERT INTO t SELECT * FROM generate_series(1, $rows);
+ INSERT INTO t2 SELECT * FROM generate_series(1, $rows);
+ CREATE PUBLICATION testpub FOR TABLE t;
+ SELECT pg_create_logical_replication_slot('testslot', 'pgoutput');
+ });
+
+$node_twoways->safe_psql('d2',
+ "CREATE SUBSCRIPTION testsub CONNECTION \$\$"
+ . $node_twoways->connstr('d1')
+ . "\$\$ PUBLICATION testpub WITH (create_slot=false, "
+ . "slot_name='testslot')");
+$node_twoways->safe_psql(
+ 'd1', qq{
+ INSERT INTO t SELECT * FROM generate_series(1, $rows);
+ INSERT INTO t2 SELECT * FROM generate_series(1, $rows);
+ });
+$node_twoways->safe_psql('d1', 'ALTER PUBLICATION testpub ADD TABLE t2');
+$node_twoways->safe_psql('d2',
+ 'ALTER SUBSCRIPTION testsub REFRESH PUBLICATION');
+
+# We cannot rely solely on wait_for_catchup() here; it isn't sufficient
+# when tablesync workers might still be running. So in addition to that,
+# verify that tables are synced.
+# XXX maybe this should be integrated in wait_for_catchup() itself.
+$node_twoways->wait_for_catchup('testsub');
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_twoways->poll_query_until('d2', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+is($node_twoways->safe_psql('d2', "SELECT count(f) FROM t"),
+ $rows * 2, "2x$rows rows in t");
+is($node_twoways->safe_psql('d2', "SELECT count(f) FROM t2"),
+ $rows * 2, "2x$rows rows in t2");
+
+# Verify table data is synced with cascaded replication setup. This is mainly
+# to test whether the data written by tablesync worker gets replicated.
+my $node_pub = get_new_node('testpublisher1');
+$node_pub->init(allows_streaming => 'logical');
+$node_pub->start;
+
+my $node_pub_sub = get_new_node('testpublisher_subscriber');
+$node_pub_sub->init(allows_streaming => 'logical');
+$node_pub_sub->start;
+
+my $node_sub = get_new_node('testsubscriber1');
+$node_sub->init(allows_streaming => 'logical');
+$node_sub->start;
+
+# Create the tables in all nodes.
+$node_pub->safe_psql('postgres', "CREATE TABLE tab1 (a int)");
+$node_pub_sub->safe_psql('postgres', "CREATE TABLE tab1 (a int)");
+$node_sub->safe_psql('postgres', "CREATE TABLE tab1 (a int)");
+
+# Create a cascaded replication setup like:
+# N1 - Create publication testpub1.
+# N2 - Create publication testpub2 and also include subscriber which subscribes
+# to testpub1.
+# N3 - Create subscription testsub2 subscribes to testpub2.
+#
+# Note that subscription on N3 needs to be created before subscription on N2 to
+# test whether the data written by tablesync worker of N2 gets replicated.
+$node_pub->safe_psql('postgres',
+ "CREATE PUBLICATION testpub1 FOR TABLE tab1");
+
+$node_pub_sub->safe_psql('postgres',
+ "CREATE PUBLICATION testpub2 FOR TABLE tab1");
+
+my $publisher1_connstr = $node_pub->connstr . ' dbname=postgres';
+my $publisher2_connstr = $node_pub_sub->connstr . ' dbname=postgres';
+
+$node_sub->safe_psql('postgres',
+ "CREATE SUBSCRIPTION testsub2 CONNECTION '$publisher2_connstr' PUBLICATION testpub2"
+);
+
+$node_pub_sub->safe_psql('postgres',
+ "CREATE SUBSCRIPTION testsub1 CONNECTION '$publisher1_connstr' PUBLICATION testpub1"
+);
+
+$node_pub->safe_psql('postgres',
+ "INSERT INTO tab1 values(generate_series(1,10))");
+
+# Verify that the data is cascaded from testpub1 to testsub1 and further from
+# testpub2 (which had testsub1) to testsub2.
+$node_pub->wait_for_catchup('testsub1');
+$node_pub_sub->wait_for_catchup('testsub2');
+
+# Drop subscriptions as we don't need them anymore
+$node_pub_sub->safe_psql('postgres', "DROP SUBSCRIPTION testsub1");
+$node_sub->safe_psql('postgres', "DROP SUBSCRIPTION testsub2");
+
+# Drop publications as we don't need them anymore
+$node_pub->safe_psql('postgres', "DROP PUBLICATION testpub1");
+$node_pub_sub->safe_psql('postgres', "DROP PUBLICATION testpub2");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_pub->safe_psql('postgres', "DROP TABLE tab1");
+$node_pub_sub->safe_psql('postgres', "DROP TABLE tab1");
+$node_sub->safe_psql('postgres', "DROP TABLE tab1");
+
+$node_pub->stop('fast');
+$node_pub_sub->stop('fast');
+$node_sub->stop('fast');
+
+# https://postgr.es/m/OS0PR01MB61133CA11630DAE45BC6AD95FB939%40OS0PR01MB6113.jpnprd01.prod.outlook.com
+
+# The bug was that when changing the REPLICA IDENTITY INDEX to another one, the
+# target table's relcache was not being invalidated. This leads to skipping
+# UPDATE/DELETE operations during apply on the subscriber side as the columns
+# required to search corresponding rows won't get logged.
+$node_publisher = get_new_node('publisher3');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+$node_subscriber = get_new_node('subscriber3');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_replidentity_index(a int not null, b int not null)");
+$node_publisher->safe_psql('postgres',
+ "CREATE UNIQUE INDEX idx_replidentity_index_a ON tab_replidentity_index(a)"
+);
+$node_publisher->safe_psql('postgres',
+ "CREATE UNIQUE INDEX idx_replidentity_index_b ON tab_replidentity_index(b)"
+);
+
+# use index idx_replidentity_index_a as REPLICA IDENTITY on publisher.
+$node_publisher->safe_psql('postgres',
+ "ALTER TABLE tab_replidentity_index REPLICA IDENTITY USING INDEX idx_replidentity_index_a"
+);
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_replidentity_index VALUES(1, 1),(2, 2)");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE tab_replidentity_index(a int not null, b int not null)");
+$node_subscriber->safe_psql('postgres',
+ "CREATE UNIQUE INDEX idx_replidentity_index_a ON tab_replidentity_index(a)"
+);
+$node_subscriber->safe_psql('postgres',
+ "CREATE UNIQUE INDEX idx_replidentity_index_b ON tab_replidentity_index(b)"
+);
+# use index idx_replidentity_index_b as REPLICA IDENTITY on subscriber because
+# it reflects the future scenario we are testing: changing REPLICA IDENTITY
+# INDEX.
+$node_subscriber->safe_psql('postgres',
+ "ALTER TABLE tab_replidentity_index REPLICA IDENTITY USING INDEX idx_replidentity_index_b"
+);
+
+$publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub FOR TABLE tab_replidentity_index");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub"
+);
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+is( $node_subscriber->safe_psql(
+ 'postgres', "SELECT * FROM tab_replidentity_index"),
+ qq(1|1
+2|2),
+ "check initial data on subscriber");
+
+# Set REPLICA IDENTITY to idx_replidentity_index_b on publisher, then run UPDATE and DELETE.
+$node_publisher->safe_psql(
+ 'postgres', qq[
+ ALTER TABLE tab_replidentity_index REPLICA IDENTITY USING INDEX idx_replidentity_index_b;
+ UPDATE tab_replidentity_index SET a = -a WHERE a = 1;
+ DELETE FROM tab_replidentity_index WHERE a = 2;
+]);
+
+$node_publisher->wait_for_catchup('tap_sub');
+is( $node_subscriber->safe_psql(
+ 'postgres', "SELECT * FROM tab_replidentity_index"),
+ qq(-1|1),
+ "update works with REPLICA IDENTITY");
+
+$node_publisher->stop('fast');
+$node_subscriber->stop('fast');